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 @@ --- -![License: GPL-2.0](https://img.shields.io/badge/GPL--2.0-red?style=for-the-badge) +![License: GPLv2](https://img.shields.io/badge/GPL--2.0-red?style=for-the-badge) ![Nintendo Switch](https://img.shields.io/badge/Nintendo_Switch-E60012?style=for-the-badge\&logo=nintendo-switch\&logoColor=white) [![Discord](https://img.shields.io/badge/Discord-5865F2?style=for-the-badge\&logo=discord\&logoColor=white)](https://discord.com/invite/S3eX47dHsB) ![VSCode](https://img.shields.io/badge/VSCode-0078D4?style=for-the-badge\&logo=visual%20studio%20code\&logoColor=white) @@ -13,99 +13,53 @@ ![Downloads](https://img.shields.io/github/downloads/souldbminersmwc/Horizon-OC/total.svg?style=for-the-badge) --- +
-
+### 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 @@ + +![Banner](img/banner.png?raw=true) +===== + +![License](https://img.shields.io/badge/License-GPLv2-blue.svg) +[![Chat on Discord](https://img.shields.io/badge/Discord-5865f2?logo=discord&logoColor=white)](https://discordapp.com/invite/ZdqEhed) +![Made with Notepad++](img/np++.png?raw=true) + +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/<user-defined patch name>/<Hex Build-ID for NRO to patch>.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/<title id>/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/<title id>/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=<nonzero>); 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/<tid>/%s.flag`, flags are now located in `atmosphere/titles/<tid>/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 `<Actual version> (AMS <x>.<y>.<z>)`. ++ 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/<user-defined patch name>/<SHA256 of KIP>.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/<user-defined patch name>/<SHA256 of Kernel>.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/<titleid>/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/<titleid>/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/<user-defined patch name>/<Hex Build-ID for NSO to patch>.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/<title ID>/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/<title ID>/romfs.bin, and all loose files in /atmosphere/titles/<title ID>/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/<title ID>/fsmitm.flag file on the SD card. + + Can be forcibly disabled for any title, by creating a /atmosphere/titles/<title ID>/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/<timestamp>_<title ID>.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<bool> out; + [65001] GetCheatProcessEvent() -> sf::OutCopyHandle out_event; + [65002] GetCheatProcessMetadata() -> sf::Out<CheatProcessMetadata> out_metadata; + [65003] ForceOpenCheatProcess(); + [65004] PauseCheatProcess(); + [65005] ResumeCheatProcess(); + + [65100] GetCheatProcessMappingCount() -> sf::Out<u64> out_count; + [65101] GetCheatProcessMappings(u64 offset) -> sf::OutArray<MemoryInfo> &mappings, sf::Out<u64> 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<MemoryInfo> mapping; + + [65200] GetCheatCount() -> sf::Out<u64> out_count; + [65201] GetCheats(u64 offset) -> sf::OutArray<CheatEntry> &cheats, sf::Out<u64> out_count; + [65202] GetCheatById(u32 cheat_id) -> sf::Out<CheatEntry> cheat; + [65203] ToggleCheat(u32 cheat_id); + [65204] AddCheat(CheatDefinition &cheat, bool enabled) -> sf::Out<u32> out_cheat_id; + [65205] RemoveCheat(u32 cheat_id); + [65206] ReadStaticRegister(u8 which) -> sf::Out<u64> out; + [65207] WriteStaticRegister(u8 which, u64 value); + [65208] ResetStaticRegisters(); + + [65300] GetFrozenAddressCount() -> sf::Out<u64> out_count; + [65301] GetFrozenAddresses(u64 offset) ->sf::OutArray<FrozenAddressEntry> &addresses, sf::Out<u64> out_count; + [65302] GetFrozenAddress(u64 address) -> sf::Out<FrozenAddressEntry> entry; + [65303] EnableFrozenAddress(u64 address, u64 width) -> sf::Out<u64> 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/<program id>/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/<program id>/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/<patchset name>/<nso build id>.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<bool> out; + [65001] AtmosphereGetProgramInfo(ncm::ProgramLocation &loc) -> sf::Out<ProgramInfo> out_program_info, sf::Out<cfg::OverrideStatus> out_status; + [65002] AtmospherePinProgram(ncm::ProgramLocation &loc, cfg::OverrideStatus &override_status) -> sf::Out<PinId> 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<bool> 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<ncm::ProgramLocation> out_loc, sf::Out<cfg::OverrideStatus> out_status; + [65001] AtmosphereGetCurrentLimitInfo(u32 group, u32 resource) -> sf::Out<s64> out_cur_val, sf::Out<s64> 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<os::ProcessId> out; + [65001] AtmosphereHasLaunchedProgram(ncm::ProgramId program_id) -> sf::Out<bool> out; + [65002] AtmosphereGetProcessInfo(os::ProcessId process_id) -> sf::Out<ncm::ProgramLocation> out_loc, sf::Out<cfg::OverrideStatus> 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/<patchset name>/<nro build id>.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<ServiceRecord> record; + [65001] AtmosphereListRecords(u64 offset) -> sf::OutArray<ServiceRecord> &records, sf::Out<u64> out_count; + [65002] AtmosphereGetRecordSize() -> sf::Out<u64> 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<MitmProcessInfo> client_info, sf::OutMoveHandle fwd_h; + [65004] AtmosphereHasMitm(ServiceName service) -> sf::Out<bool> out; + [65005] AtmosphereWaitMitm(ServiceName service); + [65006] AtmosphereDeclareFutureMitm(ServiceName service); + + [65100] AtmosphereHasService(ServiceName service) -> sf::Out<bool> 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<bool> 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/<program_id>/cheats/<build_id>.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 <path to your splash screen image> <path to /atmosphere/package3 on your SD card>` + +## 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_name>.flag` inside `/atmosphere/contents/<program_id>/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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 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. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU 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=<path to>/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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ + +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ + +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef __FS_OFFSETS_H__ +#define __FS_OFFSETS_H__ + +#include <stdint.h> +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ + +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ + +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ +#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 <tolkien@mizi.com> + * + * 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 <Complex> 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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#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 <http://www.gnu.org/licenses/>. + */ + +#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 <http://www.gnu.org/licenses/>. + */ + +#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 <http://www.gnu.org/licenses/>. + */ + +#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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#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 <http://www.gnu.org/licenses/>. + */ + +#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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#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 <http://www.gnu.org/licenses/>. + */ + +#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 <http://www.gnu.org/licenses/>. + */ + +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> + +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef __EMUMMC_H__ +#define __EMUMMC_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdlib.h> +#include <stdint.h> +#include <malloc.h> +#include <stdio.h> +#include <string.h> + +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ + +#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 <string.h> +#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 <http://www.gnu.org/licenses/>. + */ + +/*----------------------------------------------------------------------------/ +/ 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 <stdarg.h> +/*-----------------------------------------------------------------------*/ +/* 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 <stdlib.h> + + +#if FF_USE_LFN == 3 /* Dynamic memory allocation */ + +/*------------------------------------------------------------------------*/ +/* Allocate a memory block */ +/*------------------------------------------------------------------------*/ + +void* ff_memalloc ( /* Returns pointer to the allocated memory block (null if not enough core) */ + UINT msize /* Number of bytes to allocate */ +) +{ + return malloc(msize); /* Allocate a new memory block with POSIX API */ +} + + +/*------------------------------------------------------------------------*/ +/* Free a memory block */ +/*------------------------------------------------------------------------*/ + +void ff_memfree ( + void* mblock /* Pointer to the memory block to free (nothing to do if null) */ +) +{ + free(mblock); /* Free the memory block with POSIX API */ +} + +#endif + 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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdint.h> +#include <malloc.h> +#include <stdio.h> +#include <string.h> +#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 <stddef.h> +#include "../utils/types.h" +#include <elf.h> + +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 <stddef.h> +#include <string.h> +#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 <http://www.gnu.org/licenses/>. + */ + +#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 <http://www.gnu.org/licenses/>. + */ + +#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 <http://www.gnu.org/licenses/>. + */ + +#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 <http://www.gnu.org/licenses/>. + */ + +#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 <http://www.gnu.org/licenses/>. + */ + +#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 <http://www.gnu.org/licenses/>. + */ + +#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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> + +#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 <http://www.gnu.org/licenses/>. + */ + +#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 <http://www.gnu.org/licenses/>. + */ + +#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 <http://www.gnu.org/licenses/>. + */ + +#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 <http://www.gnu.org/licenses/>. + */ + +#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 <http://www.gnu.org/licenses/>. +*/ + +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#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 <m4x@m4xw.net> + * 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 <http://www.gnu.org/licenses/>. + */ + +#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 <http://www.gnu.org/licenses/>. +*/ + +#ifndef _TYPES_H_ +#define _TYPES_H_ + +#include <stdint.h> +#include <stdbool.h> + +#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 <http://www.gnu.org/licenses/>. +*/ + +#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 <http://www.gnu.org/licenses/>. + */ + +#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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_loader_uncompress.hpp" + +namespace ams::secmon::loader { + + namespace { + + constexpr const u8 SecmonProgramLz4[] = { + #embed <program.lz4> + }; + + constexpr const u8 SecmonBootCodeLz4[] = { + #embed <boot_code.lz4> + }; + + } + + 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<u8>() - 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<void (*)()>(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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#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<const u8 *>(src)), m_src_size(src_size), m_src_offset(0), m_dst(static_cast<u8 *>(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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> +#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 <http://www.gnu.org/licenses/>. + */ + +/* 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +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 <exosphere/diag/diag_detailed_assertion_impl.inc> + +} 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 <http://www.gnu.org/licenses/>. + */ + +.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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +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<void *>(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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +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<u32>(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<u64>(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<u64>(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<PageDirectoryEntry *>(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<PageTableEntry *>(l1_table); + + const size_t remaining_in_entry = (PageTableSize / sizeof(PageTableEntry)) - l2_index; + const size_t map_count = std::min<size_t>(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<void>(), 0, mmu::PageSize); + hw::FlushDataCache(MemoryRegionVirtualDramSdmmc1L0DevicePageTable.GetPointer<void>(), 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<void>(), SdmmcL0PageTablePhysical, + MemoryRegionVirtualDramSdmmc1L1DevicePageTable.GetPointer<void>(), 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<void>(), 0, mmu::PageSize); + hw::FlushDataCache(MemoryRegionVirtualDramDcL0DevicePageTable.GetPointer<void>(), 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<void>(), 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#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<u32>(); + 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +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 <http://www.gnu.org/licenses/>. + */ + +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 <http://www.gnu.org/licenses/>. + */ +/* + * (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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#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<ams::impl::FatalErrorContext>(); + } + + } + + 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +#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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#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<u32>(speed_mode)); + AMS_SECMON_LOG(" Bus Width: %u\n", static_cast<u32>(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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#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<u8>() + MemoryRegionVirtualDramSdmmcMappedData.GetSize() - mmu::PageSize; + } + + ALWAYS_INLINE u8 *GetSdCardDmaBuffer() { + return MemoryRegionVirtualDramSdmmcMappedData.GetPointer<u8>(); + } + + 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<u8 *>(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<const u8 *>(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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stdint.h> + +#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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +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 <stdbool.h> +#include <string.h> +#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 <stdarg.h> +/*-----------------------------------------------------------------------*/ +/* 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 <windows.h> +typedef unsigned __int64 QWORD; +#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus) /* C99 or later */ +#define FF_INTDEF 2 +#include <stdint.h> +typedef unsigned int UINT; /* int must be 16-bit or 32-bit */ +typedef unsigned char BYTE; /* char must be 8-bit */ +typedef uint16_t WORD; /* 16-bit unsigned integer */ +typedef uint32_t DWORD; /* 32-bit unsigned integer */ +typedef uint64_t QWORD; /* 64-bit unsigned integer */ +typedef WORD WCHAR; /* UTF-16 character type */ +#else /* Earlier than C99 */ +#define FF_INTDEF 1 +typedef unsigned int UINT; /* int must be 16-bit or 32-bit */ +typedef unsigned char BYTE; /* char must be 8-bit */ +typedef unsigned short WORD; /* 16-bit unsigned integer */ +typedef unsigned long DWORD; /* 32-bit unsigned integer */ +typedef WORD WCHAR; /* UTF-16 character type */ +#endif + + +/* Definitions of volume management */ + +#if FF_MULTI_PARTITION /* Multiple partition configuration */ +typedef struct { + BYTE pd; /* Physical drive number */ + BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */ +} PARTITION; +extern PARTITION VolToPart[]; /* Volume - Partition mapping table */ +#endif + +#if FF_STR_VOLUME_ID +#ifndef FF_VOLUME_STRS +extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */ +#endif +#endif + + + +/* Type of path name strings on FatFs API */ + +#ifndef _INC_TCHAR +#define _INC_TCHAR + +#if FF_USE_LFN && FF_LFN_UNICODE == 1 /* Unicode in UTF-16 encoding */ +typedef WCHAR TCHAR; +#define _T(x) L ## x +#define _TEXT(x) L ## x +#elif FF_USE_LFN && FF_LFN_UNICODE == 2 /* Unicode in UTF-8 encoding */ +typedef char TCHAR; +#define _T(x) u8 ## x +#define _TEXT(x) u8 ## x +#elif FF_USE_LFN && FF_LFN_UNICODE == 3 /* Unicode in UTF-32 encoding */ +typedef DWORD TCHAR; +#define _T(x) U ## x +#define _TEXT(x) U ## x +#elif FF_USE_LFN && (FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3) +#error Wrong FF_LFN_UNICODE setting +#else /* ANSI/OEM code in SBCS/DBCS */ +typedef char TCHAR; +#define _T(x) x +#define _TEXT(x) x +#endif + +#endif + + + +/* Type of file size and LBA variables */ + +#if FF_FS_EXFAT +#if FF_INTDEF != 2 +#error exFAT feature wants C99 or later +#endif +typedef QWORD FSIZE_t; +#if FF_LBA64 +typedef QWORD LBA_t; +#else +typedef DWORD LBA_t; +#endif +#else +#if FF_LBA64 +#error exFAT needs to be enabled when enable 64-bit LBA +#endif +typedef DWORD FSIZE_t; +typedef DWORD LBA_t; +#endif + + + +/* Filesystem object structure (FATFS) */ + +typedef struct { + BYTE fs_type; /* Filesystem type (0:not mounted) */ + BYTE pdrv; /* Associated physical drive */ + BYTE n_fats; /* Number of FATs (1 or 2) */ + BYTE wflag; /* win[] flag (b0:dirty) */ + BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */ + WORD id; /* Volume mount ID */ + WORD n_rootdir; /* Number of root directory entries (FAT12/16) */ + WORD csize; /* Cluster size [sectors] */ +#if FF_MAX_SS != FF_MIN_SS + WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */ +#endif +#if FF_USE_LFN + WCHAR* lfnbuf; /* LFN working buffer */ +#endif +#if FF_FS_EXFAT + BYTE* dirbuf; /* Directory entry block scratchpad buffer for exFAT */ +#endif +#if FF_FS_REENTRANT + FF_SYNC_t sobj; /* Identifier of sync object */ +#endif +#if !FF_FS_READONLY + DWORD last_clst; /* Last allocated cluster */ + DWORD free_clst; /* Number of free clusters */ +#endif +#if FF_FS_RPATH + DWORD cdir; /* Current directory start cluster (0:root) */ +#if FF_FS_EXFAT + DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */ + DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */ + DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */ +#endif +#endif + DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */ + DWORD fsize; /* Size of an FAT [sectors] */ + LBA_t volbase; /* Volume base sector */ + LBA_t fatbase; /* FAT base sector */ + LBA_t dirbase; /* Root directory base sector/cluster */ + LBA_t database; /* Data base sector */ +#if FF_FS_EXFAT + LBA_t bitbase; /* Allocation bitmap base sector */ +#endif + LBA_t winsect; /* Current sector appearing in the win[] */ + BYTE win[FF_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */ +} FATFS; + + + +/* Object ID and allocation information (FFOBJID) */ + +typedef struct { + FATFS* fs; /* Pointer to the hosting volume of this object */ + WORD id; /* Hosting volume mount ID */ + BYTE attr; /* Object attribute */ + BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */ + DWORD sclust; /* Object data start cluster (0:no cluster or root directory) */ + FSIZE_t objsize; /* Object size (valid when sclust != 0) */ +#if FF_FS_EXFAT + DWORD n_cont; /* Size of first fragment - 1 (valid when stat == 3) */ + DWORD n_frag; /* Size of last fragment needs to be written to FAT (valid when not zero) */ + DWORD c_scl; /* Containing directory start cluster (valid when sclust != 0) */ + DWORD c_size; /* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */ + DWORD c_ofs; /* Offset in the containing directory (valid when file object and sclust != 0) */ +#endif +#if FF_FS_LOCK + UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */ +#endif +} FFOBJID; + + + +/* File object structure (FIL) */ + +typedef struct { + FFOBJID obj; /* Object identifier (must be the 1st member to detect invalid object pointer) */ + BYTE flag; /* File status flags */ + BYTE err; /* Abort flag (error code) */ + FSIZE_t fptr; /* File read/write pointer (Zeroed on file open) */ + DWORD clust; /* Current cluster of fpter (invalid when fptr is 0) */ + LBA_t sect; /* Sector number appearing in buf[] (0:invalid) */ +#if !FF_FS_READONLY + LBA_t dir_sect; /* Sector number containing the directory entry (not used at exFAT) */ + BYTE* dir_ptr; /* Pointer to the directory entry in the win[] (not used at exFAT) */ +#endif +#if FF_USE_FASTSEEK + DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */ +#endif +#if !FF_FS_TINY + BYTE buf[FF_MAX_SS]; /* File private data read/write window */ +#endif +} FIL; + + + +/* Directory object structure (DIR) */ + +typedef struct { + FFOBJID obj; /* Object identifier */ + DWORD dptr; /* Current read/write offset */ + DWORD clust; /* Current cluster */ + LBA_t sect; /* Current sector (0:Read operation has terminated) */ + BYTE* dir; /* Pointer to the directory item in the win[] */ + BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */ +#if FF_USE_LFN + DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */ +#endif +#if FF_USE_FIND + const TCHAR* pat; /* Pointer to the name matching pattern */ +#endif +} DIR; + + + +/* File information structure (FILINFO) */ + +typedef struct { + FSIZE_t fsize; /* File size */ + WORD fdate; /* Modified date */ + WORD ftime; /* Modified time */ + BYTE fattrib; /* File attribute */ +#if FF_USE_LFN + TCHAR altname[FF_SFN_BUF + 1];/* Altenative file name */ + TCHAR fname[FF_LFN_BUF + 1]; /* Primary file name */ +#else + TCHAR fname[12 + 1]; /* File name */ +#endif +} FILINFO; + + + +/* Format parameter structure (MKFS_PARM) */ + +typedef struct { + BYTE fmt; /* Format option (FM_FAT, FM_FAT32, FM_EXFAT and FM_SFD) */ + BYTE n_fat; /* Number of FATs */ + UINT align; /* Data area alignment (sector) */ + UINT n_root; /* Number of root directory entries */ + DWORD au_size; /* Cluster size (byte) */ +} MKFS_PARM; + + + +/* File function return code (FRESULT) */ + +typedef enum { + FR_OK = 0, /* (0) Succeeded */ + FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */ + FR_INT_ERR, /* (2) Assertion failed */ + FR_NOT_READY, /* (3) The physical drive cannot work */ + FR_NO_FILE, /* (4) Could not find the file */ + FR_NO_PATH, /* (5) Could not find the path */ + FR_INVALID_NAME, /* (6) The path name format is invalid */ + FR_DENIED, /* (7) Access denied due to prohibited access or directory full */ + FR_EXIST, /* (8) Access denied due to prohibited access */ + FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */ + FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */ + FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */ + FR_NOT_ENABLED, /* (12) The volume has no work area */ + FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */ + FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */ + FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */ + FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */ + FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */ + FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */ + FR_INVALID_PARAMETER /* (19) Given parameter is invalid */ +} FRESULT; + + + +/*--------------------------------------------------------------*/ +/* FatFs module application interface */ + +FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */ +FRESULT f_close (FIL* fp); /* Close an open file object */ +FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */ +FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */ +FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */ +FRESULT f_truncate (FIL* fp); /* Truncate the file */ +FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */ +FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */ +FRESULT f_closedir (DIR* dp); /* Close an open directory */ +FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */ +FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */ +FRESULT f_findnext (DIR* dp, FILINFO* fno); /* Find next file */ +FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */ +FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */ +FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */ +FRESULT f_stat (const TCHAR* path, FILINFO* fno); /* Get file status */ +FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */ +FRESULT f_utime (const TCHAR* path, const FILINFO* fno); /* Change timestamp of a file/dir */ +FRESULT f_chdir (const TCHAR* path); /* Change current directory */ +FRESULT f_chdrive (const TCHAR* path); /* Change current drive */ +FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */ +FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */ +FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */ +FRESULT f_setlabel (const TCHAR* label); /* Set volume label */ +FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */ +FRESULT f_expand (FIL* fp, FSIZE_t fsz, BYTE opt); /* Allocate a contiguous block to the file */ +FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */ +FRESULT f_mkfs (const TCHAR* path, const MKFS_PARM* opt, void* work, UINT len); /* Create a FAT volume */ +FRESULT f_fdisk (BYTE pdrv, const LBA_t ptbl[], void* work); /* Divide a physical drive into some partitions */ +FRESULT f_setcp (WORD cp); /* Set current code page */ +int f_putc (TCHAR c, FIL* fp); /* Put a character to the file */ +int f_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */ +int f_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */ +TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */ + +#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize)) +#define f_error(fp) ((fp)->err) +#define f_tell(fp) ((fp)->fptr) +#define f_size(fp) ((fp)->obj.objsize) +#define f_rewind(fp) f_lseek((fp), 0) +#define f_rewinddir(dp) f_readdir((dp), 0) +#define f_rmdir(path) f_unlink(path) +#define f_unmount(path) f_mount(0, path, 0) + +#ifndef EOF +#define EOF (-1) +#endif + + + + +/*--------------------------------------------------------------*/ +/* Additional user defined functions */ + +/* RTC function */ +#if !FF_FS_READONLY && !FF_FS_NORTC +DWORD get_fattime (void); +#endif + +/* LFN support functions */ +#if FF_USE_LFN >= 1 /* Code conversion (defined in unicode.c) */ +WCHAR ff_oem2uni (WCHAR oem, WORD cp); /* OEM code to Unicode conversion */ +WCHAR ff_uni2oem (DWORD uni, WORD cp); /* Unicode to OEM code conversion */ +DWORD ff_wtoupper (DWORD uni); /* Unicode upper-case conversion */ +#endif +#if FF_USE_LFN == 3 /* Dynamic memory allocation */ +void* ff_memalloc (UINT msize); /* Allocate memory block */ +void ff_memfree (void* mblock); /* Free memory block */ +#endif + +/* Sync functions */ +#if FF_FS_REENTRANT +int ff_cre_syncobj (BYTE vol, FF_SYNC_t* sobj); /* Create a sync object */ +int ff_req_grant (FF_SYNC_t sobj); /* Lock sync object */ +void ff_rel_grant (FF_SYNC_t sobj); /* Unlock sync object */ +int ff_del_syncobj (FF_SYNC_t sobj); /* Delete a sync object */ +#endif + + + + +/*--------------------------------------------------------------*/ +/* Flags and offset address */ + + +/* File access mode and open method flags (3rd argument of f_open) */ +#define FA_READ 0x01 +#define FA_WRITE 0x02 +#define FA_OPEN_EXISTING 0x00 +#define FA_CREATE_NEW 0x04 +#define FA_CREATE_ALWAYS 0x08 +#define FA_OPEN_ALWAYS 0x10 +#define FA_OPEN_APPEND 0x30 + +/* Fast seek controls (2nd argument of f_lseek) */ +#define CREATE_LINKMAP ((FSIZE_t)0 - 1) + +/* Format options (2nd argument of f_mkfs) */ +#define FM_FAT 0x01 +#define FM_FAT32 0x02 +#define FM_EXFAT 0x04 +#define FM_ANY 0x07 +#define FM_SFD 0x08 + +/* Filesystem type (FATFS.fs_type) */ +#define FS_FAT12 1 +#define FS_FAT16 2 +#define FS_FAT32 3 +#define FS_EXFAT 4 + +/* File attribute bits for directory entry (FILINFO.fattrib) */ +#define AM_RDO 0x01 /* Read only */ +#define AM_HID 0x02 /* Hidden */ +#define AM_SYS 0x04 /* System */ +#define AM_DIR 0x10 /* Directory */ +#define AM_ARC 0x20 /* Archive */ + + +#ifdef __cplusplus +} +#endif + +#endif /* FF_DEFINED */ diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/ffconf.h b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/ffconf.h new file mode 100644 index 00000000..174a987e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/ffconf.h @@ -0,0 +1,298 @@ +/*---------------------------------------------------------------------------/ +/ FatFs Functional Configurations +/---------------------------------------------------------------------------*/ + +#define FFCONF_DEF 86606 /* 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 0 +/* 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_USE_FASTSEEK 0 +/* This option switches fast seek function. (0:Disable or 1:Enable) */ + + +#define FF_USE_EXPAND 1 +/* 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 1 +#define FF_MAX_LFN 255 +/* The FF_USE_LFN switches the support for LFN (long file name). +/ +/ 0: Disable LFN. FF_MAX_LFN has no effect. +/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe. +/ 2: Enable LFN with dynamic working buffer on the STACK. +/ 3: Enable LFN with dynamic working buffer on the HEAP. +/ +/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function +/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and +/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled. +/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can +/ be in range of 12 to 255. It is recommended to be set it 255 to fully support LFN +/ specification. +/ When use stack for the working buffer, take care on stack overflow. When use heap +/ memory for the working buffer, memory management functions, ff_memalloc() and +/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */ + + +#define FF_LFN_UNICODE 2 +/* 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 3 +/* 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 "sdmc" +/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings. +/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive +/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each +/ logical drives. Number of items must not be less than FF_VOLUMES. Valid +/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are +/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is +/ not defined, a user defined volume string table needs to be defined as: +/ +/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",... +*/ + + +#define FF_MULTI_PARTITION 0 +/* This option switches support for multiple volumes on the physical drive. +/ By default (0), each logical drive number is bound to the same physical drive +/ number and only an FAT volume found on the physical drive will be mounted. +/ When this function is enabled (1), each logical drive number can be bound to +/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk() +/ funciton will be available. */ + + +#define FF_MIN_SS 512 +#define FF_MAX_SS 512 +/* This set of options configures the range of sector size to be supported. (512, +/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and +/ harddisk. But a larger value may be required for on-board flash memory and some +/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured +/ for variable sector size mode and disk_ioctl() function needs to implement +/ GET_SECTOR_SIZE command. */ + + +#define FF_LBA64 0 +/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable) +/ To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */ + + +#define FF_MIN_GPT 0x100000000 +/* Minimum number of sectors to switch GPT format to create partition in f_mkfs and +/ f_fdisk function. 0x100000000 max. This option has no effect when FF_LBA64 == 0. */ + + +#define FF_USE_TRIM 0 +/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable) +/ To enable Trim function, also CTRL_TRIM command should be implemented to the +/ disk_ioctl() function. */ + + + +/*---------------------------------------------------------------------------/ +/ System Configurations +/---------------------------------------------------------------------------*/ + +#define FF_FS_TINY 0 +/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny) +/ At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes. +/ Instead of private sector buffer eliminated from the file object, common sector +/ buffer in the filesystem object (FATFS) is used for the file data transfer. */ + + +#define FF_FS_EXFAT 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 2019 +/* The option FF_FS_NORTC switches timestamp functiton. If the system does not have +/ any RTC function or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable +/ the timestamp function. Every object modified by FatFs will have a fixed timestamp +/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time. +/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be +/ added to the project to read current time form real-time clock. FF_NORTC_MON, +/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect. +/ These options have no effect in read-only configuration (FF_FS_READONLY = 1). */ + + +#define FF_FS_NOFSINFO 0 +/* If you need to know correct free space on the FAT32 volume, set bit 0 of this +/ option, and f_getfree() function at first time after volume mount will force +/ a full FAT scan. Bit 1 controls the use of last allocated cluster number. +/ +/ bit0=0: Use free cluster count in the FSINFO if available. +/ bit0=1: Do not trust free cluster count in the FSINFO. +/ bit1=0: Use last allocated cluster number in the FSINFO if available. +/ bit1=1: Do not trust last allocated cluster number in the FSINFO. +*/ + + +#define FF_FS_LOCK 0 +/* The option FF_FS_LOCK switches file lock function to control duplicated file open +/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY +/ is 1. +/ +/ 0: Disable file lock function. To avoid volume corruption, application program +/ should avoid illegal open, remove and rename to the open objects. +/ >0: Enable file lock function. The value defines how many files/sub-directories +/ can be opened simultaneously under file lock control. Note that the file +/ lock control is independent of re-entrancy. */ + + +/* #include <somertos.h> // O/S definitions */ +#define FF_FS_REENTRANT 0 +#define FF_FS_TIMEOUT 1000 +#define FF_SYNC_t HANDLE +/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs +/ module itself. Note that regardless of this option, file access to different +/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs() +/ and f_fdisk() function, are always not re-entrant. Only file/directory access +/ to the same volume is under control of this function. +/ +/ 0: Disable re-entrancy. FF_FS_TIMEOUT and FF_SYNC_t have no effect. +/ 1: Enable re-entrancy. Also user provided synchronization handlers, +/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj() +/ function, must be added to the project. Samples are available in +/ option/syscall.c. +/ +/ The FF_FS_TIMEOUT defines timeout period in unit of time tick. +/ The FF_SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*, +/ SemaphoreHandle_t and etc. A header file for O/S definitions needs to be +/ included somewhere in the scope of ff.h. */ + + + +/*--- End of configuration options ---*/ diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/ffsystem.c b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/ffsystem.c new file mode 100644 index 00000000..8d38eddf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/ffsystem.c @@ -0,0 +1,170 @@ +/*------------------------------------------------------------------------*/ +/* Sample Code of OS Dependent Functions for FatFs */ +/* (C)ChaN, 2018 */ +/*------------------------------------------------------------------------*/ + + +#include "ff.h" + + +#if FF_USE_LFN == 3 /* Dynamic memory allocation */ + +/*------------------------------------------------------------------------*/ +/* Allocate a memory block */ +/*------------------------------------------------------------------------*/ + +void* ff_memalloc ( /* Returns pointer to the allocated memory block (null if not enough core) */ + UINT msize /* Number of bytes to allocate */ +) +{ + return malloc(msize); /* Allocate a new memory block with POSIX API */ +} + + +/*------------------------------------------------------------------------*/ +/* Free a memory block */ +/*------------------------------------------------------------------------*/ + +void ff_memfree ( + void* mblock /* Pointer to the memory block to free (nothing to do if null) */ +) +{ + free(mblock); /* Free the memory block with POSIX API */ +} + +#endif + + + +#if FF_FS_REENTRANT /* Mutal exclusion */ + +/*------------------------------------------------------------------------*/ +/* Create a Synchronization Object */ +/*------------------------------------------------------------------------*/ +/* This function is called in f_mount() function to create a new +/ synchronization object for the volume, such as semaphore and mutex. +/ When a 0 is returned, the f_mount() function fails with FR_INT_ERR. +*/ + +//const osMutexDef_t Mutex[FF_VOLUMES]; /* Table of CMSIS-RTOS mutex */ + + +int ff_cre_syncobj ( /* 1:Function succeeded, 0:Could not create the sync object */ + BYTE vol, /* Corresponding volume (logical drive number) */ + FF_SYNC_t* sobj /* Pointer to return the created sync object */ +) +{ + /* Win32 */ + *sobj = CreateMutex(NULL, FALSE, NULL); + return (int)(*sobj != INVALID_HANDLE_VALUE); + + /* uITRON */ +// T_CSEM csem = {TA_TPRI,1,1}; +// *sobj = acre_sem(&csem); +// return (int)(*sobj > 0); + + /* uC/OS-II */ +// OS_ERR err; +// *sobj = OSMutexCreate(0, &err); +// return (int)(err == OS_NO_ERR); + + /* FreeRTOS */ +// *sobj = xSemaphoreCreateMutex(); +// return (int)(*sobj != NULL); + + /* CMSIS-RTOS */ +// *sobj = osMutexCreate(&Mutex[vol]); +// return (int)(*sobj != NULL); +} + + +/*------------------------------------------------------------------------*/ +/* Delete a Synchronization Object */ +/*------------------------------------------------------------------------*/ +/* This function is called in f_mount() function to delete a synchronization +/ object that created with ff_cre_syncobj() function. When a 0 is returned, +/ the f_mount() function fails with FR_INT_ERR. +*/ + +int ff_del_syncobj ( /* 1:Function succeeded, 0:Could not delete due to an error */ + FF_SYNC_t sobj /* Sync object tied to the logical drive to be deleted */ +) +{ + /* Win32 */ + return (int)CloseHandle(sobj); + + /* uITRON */ +// return (int)(del_sem(sobj) == E_OK); + + /* uC/OS-II */ +// OS_ERR err; +// OSMutexDel(sobj, OS_DEL_ALWAYS, &err); +// return (int)(err == OS_NO_ERR); + + /* FreeRTOS */ +// vSemaphoreDelete(sobj); +// return 1; + + /* CMSIS-RTOS */ +// return (int)(osMutexDelete(sobj) == osOK); +} + + +/*------------------------------------------------------------------------*/ +/* Request Grant to Access the Volume */ +/*------------------------------------------------------------------------*/ +/* This function is called on entering file functions to lock the volume. +/ When a 0 is returned, the file function fails with FR_TIMEOUT. +*/ + +int ff_req_grant ( /* 1:Got a grant to access the volume, 0:Could not get a grant */ + FF_SYNC_t sobj /* Sync object to wait */ +) +{ + /* Win32 */ + return (int)(WaitForSingleObject(sobj, FF_FS_TIMEOUT) == WAIT_OBJECT_0); + + /* uITRON */ +// return (int)(wai_sem(sobj) == E_OK); + + /* uC/OS-II */ +// OS_ERR err; +// OSMutexPend(sobj, FF_FS_TIMEOUT, &err)); +// return (int)(err == OS_NO_ERR); + + /* FreeRTOS */ +// return (int)(xSemaphoreTake(sobj, FF_FS_TIMEOUT) == pdTRUE); + + /* CMSIS-RTOS */ +// return (int)(osMutexWait(sobj, FF_FS_TIMEOUT) == osOK); +} + + +/*------------------------------------------------------------------------*/ +/* Release Grant to Access the Volume */ +/*------------------------------------------------------------------------*/ +/* This function is called on leaving file functions to unlock the volume. +*/ + +void ff_rel_grant ( + FF_SYNC_t sobj /* Sync object to be signaled */ +) +{ + /* Win32 */ + ReleaseMutex(sobj); + + /* uITRON */ +// sig_sem(sobj); + + /* uC/OS-II */ +// OSMutexPost(sobj); + + /* FreeRTOS */ +// xSemaphoreGive(sobj); + + /* CMSIS-RTOS */ +// osMutexRelease(sobj); +} + +#endif + diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/ffunicode.c b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/ffunicode.c new file mode 100644 index 00000000..e8b36dbe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fatfs/ffunicode.c @@ -0,0 +1,15593 @@ +/*------------------------------------------------------------------------*/ +/* Unicode handling functions for FatFs R0.13+ */ +/*------------------------------------------------------------------------*/ +/* 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) 2014, 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 if non-LFN configuration */ + +#define MERGE2(a, b) a ## b +#define CVTBL(tbl, cp) MERGE2(tbl, cp) + + +/*------------------------------------------------------------------------*/ +/* Code Conversion Tables */ +/*------------------------------------------------------------------------*/ + +#if FF_CODE_PAGE == 932 || FF_CODE_PAGE == 0 /* Japanese */ +static const WCHAR uni2oem932[] = { /* Unicode --> Shift_JIS pairs */ + 0x00A7, 0x8198, 0x00A8, 0x814E, 0x00B0, 0x818B, 0x00B1, 0x817D, 0x00B4, 0x814C, 0x00B6, 0x81F7, 0x00D7, 0x817E, 0x00F7, 0x8180, + 0x0391, 0x839F, 0x0392, 0x83A0, 0x0393, 0x83A1, 0x0394, 0x83A2, 0x0395, 0x83A3, 0x0396, 0x83A4, 0x0397, 0x83A5, 0x0398, 0x83A6, + 0x0399, 0x83A7, 0x039A, 0x83A8, 0x039B, 0x83A9, 0x039C, 0x83AA, 0x039D, 0x83AB, 0x039E, 0x83AC, 0x039F, 0x83AD, 0x03A0, 0x83AE, + 0x03A1, 0x83AF, 0x03A3, 0x83B0, 0x03A4, 0x83B1, 0x03A5, 0x83B2, 0x03A6, 0x83B3, 0x03A7, 0x83B4, 0x03A8, 0x83B5, 0x03A9, 0x83B6, + 0x03B1, 0x83BF, 0x03B2, 0x83C0, 0x03B3, 0x83C1, 0x03B4, 0x83C2, 0x03B5, 0x83C3, 0x03B6, 0x83C4, 0x03B7, 0x83C5, 0x03B8, 0x83C6, + 0x03B9, 0x83C7, 0x03BA, 0x83C8, 0x03BB, 0x83C9, 0x03BC, 0x83CA, 0x03BD, 0x83CB, 0x03BE, 0x83CC, 0x03BF, 0x83CD, 0x03C0, 0x83CE, + 0x03C1, 0x83CF, 0x03C3, 0x83D0, 0x03C4, 0x83D1, 0x03C5, 0x83D2, 0x03C6, 0x83D3, 0x03C7, 0x83D4, 0x03C8, 0x83D5, 0x03C9, 0x83D6, + 0x0401, 0x8446, 0x0410, 0x8440, 0x0411, 0x8441, 0x0412, 0x8442, 0x0413, 0x8443, 0x0414, 0x8444, 0x0415, 0x8445, 0x0416, 0x8447, + 0x0417, 0x8448, 0x0418, 0x8449, 0x0419, 0x844A, 0x041A, 0x844B, 0x041B, 0x844C, 0x041C, 0x844D, 0x041D, 0x844E, 0x041E, 0x844F, + 0x041F, 0x8450, 0x0420, 0x8451, 0x0421, 0x8452, 0x0422, 0x8453, 0x0423, 0x8454, 0x0424, 0x8455, 0x0425, 0x8456, 0x0426, 0x8457, + 0x0427, 0x8458, 0x0428, 0x8459, 0x0429, 0x845A, 0x042A, 0x845B, 0x042B, 0x845C, 0x042C, 0x845D, 0x042D, 0x845E, 0x042E, 0x845F, + 0x042F, 0x8460, 0x0430, 0x8470, 0x0431, 0x8471, 0x0432, 0x8472, 0x0433, 0x8473, 0x0434, 0x8474, 0x0435, 0x8475, 0x0436, 0x8477, + 0x0437, 0x8478, 0x0438, 0x8479, 0x0439, 0x847A, 0x043A, 0x847B, 0x043B, 0x847C, 0x043C, 0x847D, 0x043D, 0x847E, 0x043E, 0x8480, + 0x043F, 0x8481, 0x0440, 0x8482, 0x0441, 0x8483, 0x0442, 0x8484, 0x0443, 0x8485, 0x0444, 0x8486, 0x0445, 0x8487, 0x0446, 0x8488, + 0x0447, 0x8489, 0x0448, 0x848A, 0x0449, 0x848B, 0x044A, 0x848C, 0x044B, 0x848D, 0x044C, 0x848E, 0x044D, 0x848F, 0x044E, 0x8490, + 0x044F, 0x8491, 0x0451, 0x8476, 0x2010, 0x815D, 0x2015, 0x815C, 0x2018, 0x8165, 0x2019, 0x8166, 0x201C, 0x8167, 0x201D, 0x8168, + 0x2020, 0x81F5, 0x2021, 0x81F6, 0x2025, 0x8164, 0x2026, 0x8163, 0x2030, 0x81F1, 0x2032, 0x818C, 0x2033, 0x818D, 0x203B, 0x81A6, + 0x2103, 0x818E, 0x2116, 0x8782, 0x2121, 0x8784, 0x212B, 0x81F0, 0x2160, 0x8754, 0x2161, 0x8755, 0x2162, 0x8756, 0x2163, 0x8757, + 0x2164, 0x8758, 0x2165, 0x8759, 0x2166, 0x875A, 0x2167, 0x875B, 0x2168, 0x875C, 0x2169, 0x875D, 0x2170, 0xFA40, 0x2171, 0xFA41, + 0x2172, 0xFA42, 0x2173, 0xFA43, 0x2174, 0xFA44, 0x2175, 0xFA45, 0x2176, 0xFA46, 0x2177, 0xFA47, 0x2178, 0xFA48, 0x2179, 0xFA49, + 0x2190, 0x81A9, 0x2191, 0x81AA, 0x2192, 0x81A8, 0x2193, 0x81AB, 0x21D2, 0x81CB, 0x21D4, 0x81CC, 0x2200, 0x81CD, 0x2202, 0x81DD, + 0x2203, 0x81CE, 0x2207, 0x81DE, 0x2208, 0x81B8, 0x220B, 0x81B9, 0x2211, 0x8794, 0x221A, 0x81E3, 0x221D, 0x81E5, 0x221E, 0x8187, + 0x221F, 0x8798, 0x2220, 0x81DA, 0x2225, 0x8161, 0x2227, 0x81C8, 0x2228, 0x81C9, 0x2229, 0x81BF, 0x222A, 0x81BE, 0x222B, 0x81E7, + 0x222C, 0x81E8, 0x222E, 0x8793, 0x2234, 0x8188, 0x2235, 0x81E6, 0x223D, 0x81E4, 0x2252, 0x81E0, 0x2260, 0x8182, 0x2261, 0x81DF, + 0x2266, 0x8185, 0x2267, 0x8186, 0x226A, 0x81E1, 0x226B, 0x81E2, 0x2282, 0x81BC, 0x2283, 0x81BD, 0x2286, 0x81BA, 0x2287, 0x81BB, + 0x22A5, 0x81DB, 0x22BF, 0x8799, 0x2312, 0x81DC, 0x2460, 0x8740, 0x2461, 0x8741, 0x2462, 0x8742, 0x2463, 0x8743, 0x2464, 0x8744, + 0x2465, 0x8745, 0x2466, 0x8746, 0x2467, 0x8747, 0x2468, 0x8748, 0x2469, 0x8749, 0x246A, 0x874A, 0x246B, 0x874B, 0x246C, 0x874C, + 0x246D, 0x874D, 0x246E, 0x874E, 0x246F, 0x874F, 0x2470, 0x8750, 0x2471, 0x8751, 0x2472, 0x8752, 0x2473, 0x8753, 0x2500, 0x849F, + 0x2501, 0x84AA, 0x2502, 0x84A0, 0x2503, 0x84AB, 0x250C, 0x84A1, 0x250F, 0x84AC, 0x2510, 0x84A2, 0x2513, 0x84AD, 0x2514, 0x84A4, + 0x2517, 0x84AF, 0x2518, 0x84A3, 0x251B, 0x84AE, 0x251C, 0x84A5, 0x251D, 0x84BA, 0x2520, 0x84B5, 0x2523, 0x84B0, 0x2524, 0x84A7, + 0x2525, 0x84BC, 0x2528, 0x84B7, 0x252B, 0x84B2, 0x252C, 0x84A6, 0x252F, 0x84B6, 0x2530, 0x84BB, 0x2533, 0x84B1, 0x2534, 0x84A8, + 0x2537, 0x84B8, 0x2538, 0x84BD, 0x253B, 0x84B3, 0x253C, 0x84A9, 0x253F, 0x84B9, 0x2542, 0x84BE, 0x254B, 0x84B4, 0x25A0, 0x81A1, + 0x25A1, 0x81A0, 0x25B2, 0x81A3, 0x25B3, 0x81A2, 0x25BC, 0x81A5, 0x25BD, 0x81A4, 0x25C6, 0x819F, 0x25C7, 0x819E, 0x25CB, 0x819B, + 0x25CE, 0x819D, 0x25CF, 0x819C, 0x25EF, 0x81FC, 0x2605, 0x819A, 0x2606, 0x8199, 0x2640, 0x818A, 0x2642, 0x8189, 0x266A, 0x81F4, + 0x266D, 0x81F3, 0x266F, 0x81F2, 0x3000, 0x8140, 0x3001, 0x8141, 0x3002, 0x8142, 0x3003, 0x8156, 0x3005, 0x8158, 0x3006, 0x8159, + 0x3007, 0x815A, 0x3008, 0x8171, 0x3009, 0x8172, 0x300A, 0x8173, 0x300B, 0x8174, 0x300C, 0x8175, 0x300D, 0x8176, 0x300E, 0x8177, + 0x300F, 0x8178, 0x3010, 0x8179, 0x3011, 0x817A, 0x3012, 0x81A7, 0x3013, 0x81AC, 0x3014, 0x816B, 0x3015, 0x816C, 0x301D, 0x8780, + 0x301F, 0x8781, 0x3041, 0x829F, 0x3042, 0x82A0, 0x3043, 0x82A1, 0x3044, 0x82A2, 0x3045, 0x82A3, 0x3046, 0x82A4, 0x3047, 0x82A5, + 0x3048, 0x82A6, 0x3049, 0x82A7, 0x304A, 0x82A8, 0x304B, 0x82A9, 0x304C, 0x82AA, 0x304D, 0x82AB, 0x304E, 0x82AC, 0x304F, 0x82AD, + 0x3050, 0x82AE, 0x3051, 0x82AF, 0x3052, 0x82B0, 0x3053, 0x82B1, 0x3054, 0x82B2, 0x3055, 0x82B3, 0x3056, 0x82B4, 0x3057, 0x82B5, + 0x3058, 0x82B6, 0x3059, 0x82B7, 0x305A, 0x82B8, 0x305B, 0x82B9, 0x305C, 0x82BA, 0x305D, 0x82BB, 0x305E, 0x82BC, 0x305F, 0x82BD, + 0x3060, 0x82BE, 0x3061, 0x82BF, 0x3062, 0x82C0, 0x3063, 0x82C1, 0x3064, 0x82C2, 0x3065, 0x82C3, 0x3066, 0x82C4, 0x3067, 0x82C5, + 0x3068, 0x82C6, 0x3069, 0x82C7, 0x306A, 0x82C8, 0x306B, 0x82C9, 0x306C, 0x82CA, 0x306D, 0x82CB, 0x306E, 0x82CC, 0x306F, 0x82CD, + 0x3070, 0x82CE, 0x3071, 0x82CF, 0x3072, 0x82D0, 0x3073, 0x82D1, 0x3074, 0x82D2, 0x3075, 0x82D3, 0x3076, 0x82D4, 0x3077, 0x82D5, + 0x3078, 0x82D6, 0x3079, 0x82D7, 0x307A, 0x82D8, 0x307B, 0x82D9, 0x307C, 0x82DA, 0x307D, 0x82DB, 0x307E, 0x82DC, 0x307F, 0x82DD, + 0x3080, 0x82DE, 0x3081, 0x82DF, 0x3082, 0x82E0, 0x3083, 0x82E1, 0x3084, 0x82E2, 0x3085, 0x82E3, 0x3086, 0x82E4, 0x3087, 0x82E5, + 0x3088, 0x82E6, 0x3089, 0x82E7, 0x308A, 0x82E8, 0x308B, 0x82E9, 0x308C, 0x82EA, 0x308D, 0x82EB, 0x308E, 0x82EC, 0x308F, 0x82ED, + 0x3090, 0x82EE, 0x3091, 0x82EF, 0x3092, 0x82F0, 0x3093, 0x82F1, 0x309B, 0x814A, 0x309C, 0x814B, 0x309D, 0x8154, 0x309E, 0x8155, + 0x30A1, 0x8340, 0x30A2, 0x8341, 0x30A3, 0x8342, 0x30A4, 0x8343, 0x30A5, 0x8344, 0x30A6, 0x8345, 0x30A7, 0x8346, 0x30A8, 0x8347, + 0x30A9, 0x8348, 0x30AA, 0x8349, 0x30AB, 0x834A, 0x30AC, 0x834B, 0x30AD, 0x834C, 0x30AE, 0x834D, 0x30AF, 0x834E, 0x30B0, 0x834F, + 0x30B1, 0x8350, 0x30B2, 0x8351, 0x30B3, 0x8352, 0x30B4, 0x8353, 0x30B5, 0x8354, 0x30B6, 0x8355, 0x30B7, 0x8356, 0x30B8, 0x8357, + 0x30B9, 0x8358, 0x30BA, 0x8359, 0x30BB, 0x835A, 0x30BC, 0x835B, 0x30BD, 0x835C, 0x30BE, 0x835D, 0x30BF, 0x835E, 0x30C0, 0x835F, + 0x30C1, 0x8360, 0x30C2, 0x8361, 0x30C3, 0x8362, 0x30C4, 0x8363, 0x30C5, 0x8364, 0x30C6, 0x8365, 0x30C7, 0x8366, 0x30C8, 0x8367, + 0x30C9, 0x8368, 0x30CA, 0x8369, 0x30CB, 0x836A, 0x30CC, 0x836B, 0x30CD, 0x836C, 0x30CE, 0x836D, 0x30CF, 0x836E, 0x30D0, 0x836F, + 0x30D1, 0x8370, 0x30D2, 0x8371, 0x30D3, 0x8372, 0x30D4, 0x8373, 0x30D5, 0x8374, 0x30D6, 0x8375, 0x30D7, 0x8376, 0x30D8, 0x8377, + 0x30D9, 0x8378, 0x30DA, 0x8379, 0x30DB, 0x837A, 0x30DC, 0x837B, 0x30DD, 0x837C, 0x30DE, 0x837D, 0x30DF, 0x837E, 0x30E0, 0x8380, + 0x30E1, 0x8381, 0x30E2, 0x8382, 0x30E3, 0x8383, 0x30E4, 0x8384, 0x30E5, 0x8385, 0x30E6, 0x8386, 0x30E7, 0x8387, 0x30E8, 0x8388, + 0x30E9, 0x8389, 0x30EA, 0x838A, 0x30EB, 0x838B, 0x30EC, 0x838C, 0x30ED, 0x838D, 0x30EE, 0x838E, 0x30EF, 0x838F, 0x30F0, 0x8390, + 0x30F1, 0x8391, 0x30F2, 0x8392, 0x30F3, 0x8393, 0x30F4, 0x8394, 0x30F5, 0x8395, 0x30F6, 0x8396, 0x30FB, 0x8145, 0x30FC, 0x815B, + 0x30FD, 0x8152, 0x30FE, 0x8153, 0x3231, 0x878A, 0x3232, 0x878B, 0x3239, 0x878C, 0x32A4, 0x8785, 0x32A5, 0x8786, 0x32A6, 0x8787, + 0x32A7, 0x8788, 0x32A8, 0x8789, 0x3303, 0x8765, 0x330D, 0x8769, 0x3314, 0x8760, 0x3318, 0x8763, 0x3322, 0x8761, 0x3323, 0x876B, + 0x3326, 0x876A, 0x3327, 0x8764, 0x332B, 0x876C, 0x3336, 0x8766, 0x333B, 0x876E, 0x3349, 0x875F, 0x334A, 0x876D, 0x334D, 0x8762, + 0x3351, 0x8767, 0x3357, 0x8768, 0x337B, 0x877E, 0x337C, 0x878F, 0x337D, 0x878E, 0x337E, 0x878D, 0x338E, 0x8772, 0x338F, 0x8773, + 0x339C, 0x876F, 0x339D, 0x8770, 0x339E, 0x8771, 0x33A1, 0x8775, 0x33C4, 0x8774, 0x33CD, 0x8783, 0x4E00, 0x88EA, 0x4E01, 0x929A, + 0x4E03, 0x8EB5, 0x4E07, 0x969C, 0x4E08, 0x8FE4, 0x4E09, 0x8E4F, 0x4E0A, 0x8FE3, 0x4E0B, 0x89BA, 0x4E0D, 0x9573, 0x4E0E, 0x975E, + 0x4E10, 0x98A0, 0x4E11, 0x894E, 0x4E14, 0x8A8E, 0x4E15, 0x98A1, 0x4E16, 0x90A2, 0x4E17, 0x99C0, 0x4E18, 0x8B75, 0x4E19, 0x95B8, + 0x4E1E, 0x8FE5, 0x4E21, 0x97BC, 0x4E26, 0x95C0, 0x4E28, 0xFA68, 0x4E2A, 0x98A2, 0x4E2D, 0x9286, 0x4E31, 0x98A3, 0x4E32, 0x8BF8, + 0x4E36, 0x98A4, 0x4E38, 0x8ADB, 0x4E39, 0x924F, 0x4E3B, 0x8EE5, 0x4E3C, 0x98A5, 0x4E3F, 0x98A6, 0x4E42, 0x98A7, 0x4E43, 0x9454, + 0x4E45, 0x8B76, 0x4E4B, 0x9456, 0x4E4D, 0x93E1, 0x4E4E, 0x8CC1, 0x4E4F, 0x9652, 0x4E55, 0xE568, 0x4E56, 0x98A8, 0x4E57, 0x8FE6, + 0x4E58, 0x98A9, 0x4E59, 0x89B3, 0x4E5D, 0x8BE3, 0x4E5E, 0x8CEE, 0x4E5F, 0x96E7, 0x4E62, 0x9BA4, 0x4E71, 0x9790, 0x4E73, 0x93FB, + 0x4E7E, 0x8AA3, 0x4E80, 0x8B54, 0x4E82, 0x98AA, 0x4E85, 0x98AB, 0x4E86, 0x97B9, 0x4E88, 0x975C, 0x4E89, 0x9188, 0x4E8A, 0x98AD, + 0x4E8B, 0x8E96, 0x4E8C, 0x93F1, 0x4E8E, 0x98B0, 0x4E91, 0x895D, 0x4E92, 0x8CDD, 0x4E94, 0x8CDC, 0x4E95, 0x88E4, 0x4E98, 0x986A, + 0x4E99, 0x9869, 0x4E9B, 0x8DB1, 0x4E9C, 0x889F, 0x4E9E, 0x98B1, 0x4E9F, 0x98B2, 0x4EA0, 0x98B3, 0x4EA1, 0x9653, 0x4EA2, 0x98B4, + 0x4EA4, 0x8CF0, 0x4EA5, 0x88E5, 0x4EA6, 0x9692, 0x4EA8, 0x8B9C, 0x4EAB, 0x8B9D, 0x4EAC, 0x8B9E, 0x4EAD, 0x92E0, 0x4EAE, 0x97BA, + 0x4EB0, 0x98B5, 0x4EB3, 0x98B6, 0x4EB6, 0x98B7, 0x4EBA, 0x906C, 0x4EC0, 0x8F59, 0x4EC1, 0x906D, 0x4EC2, 0x98BC, 0x4EC4, 0x98BA, + 0x4EC6, 0x98BB, 0x4EC7, 0x8B77, 0x4ECA, 0x8DA1, 0x4ECB, 0x89EE, 0x4ECD, 0x98B9, 0x4ECE, 0x98B8, 0x4ECF, 0x95A7, 0x4ED4, 0x8E65, + 0x4ED5, 0x8E64, 0x4ED6, 0x91BC, 0x4ED7, 0x98BD, 0x4ED8, 0x9574, 0x4ED9, 0x90E5, 0x4EDD, 0x8157, 0x4EDE, 0x98BE, 0x4EDF, 0x98C0, + 0x4EE1, 0xFA69, 0x4EE3, 0x91E3, 0x4EE4, 0x97DF, 0x4EE5, 0x88C8, 0x4EED, 0x98BF, 0x4EEE, 0x89BC, 0x4EF0, 0x8BC2, 0x4EF2, 0x9287, + 0x4EF6, 0x8C8F, 0x4EF7, 0x98C1, 0x4EFB, 0x9443, 0x4EFC, 0xFA6A, 0x4F00, 0xFA6B, 0x4F01, 0x8AE9, 0x4F03, 0xFA6C, 0x4F09, 0x98C2, + 0x4F0A, 0x88C9, 0x4F0D, 0x8CDE, 0x4F0E, 0x8AEA, 0x4F0F, 0x959A, 0x4F10, 0x94B0, 0x4F11, 0x8B78, 0x4F1A, 0x89EF, 0x4F1C, 0x98E5, + 0x4F1D, 0x9360, 0x4F2F, 0x948C, 0x4F30, 0x98C4, 0x4F34, 0x94BA, 0x4F36, 0x97E0, 0x4F38, 0x904C, 0x4F39, 0xFA6D, 0x4F3A, 0x8E66, + 0x4F3C, 0x8E97, 0x4F3D, 0x89BE, 0x4F43, 0x92CF, 0x4F46, 0x9241, 0x4F47, 0x98C8, 0x4F4D, 0x88CA, 0x4F4E, 0x92E1, 0x4F4F, 0x8F5A, + 0x4F50, 0x8DB2, 0x4F51, 0x9743, 0x4F53, 0x91CC, 0x4F55, 0x89BD, 0x4F56, 0xFA6E, 0x4F57, 0x98C7, 0x4F59, 0x975D, 0x4F5A, 0x98C3, + 0x4F5B, 0x98C5, 0x4F5C, 0x8DEC, 0x4F5D, 0x98C6, 0x4F5E, 0x9B43, 0x4F69, 0x98CE, 0x4F6F, 0x98D1, 0x4F70, 0x98CF, 0x4F73, 0x89C0, + 0x4F75, 0x95B9, 0x4F76, 0x98C9, 0x4F7B, 0x98CD, 0x4F7C, 0x8CF1, 0x4F7F, 0x8E67, 0x4F83, 0x8AA4, 0x4F86, 0x98D2, 0x4F88, 0x98CA, + 0x4F8A, 0xFA70, 0x4F8B, 0x97E1, 0x4F8D, 0x8E98, 0x4F8F, 0x98CB, 0x4F91, 0x98D0, 0x4F92, 0xFA6F, 0x4F94, 0xFA72, 0x4F96, 0x98D3, + 0x4F98, 0x98CC, 0x4F9A, 0xFA71, 0x4F9B, 0x8B9F, 0x4F9D, 0x88CB, 0x4FA0, 0x8BA0, 0x4FA1, 0x89BF, 0x4FAB, 0x9B44, 0x4FAD, 0x9699, + 0x4FAE, 0x958E, 0x4FAF, 0x8CF2, 0x4FB5, 0x904E, 0x4FB6, 0x97B5, 0x4FBF, 0x95D6, 0x4FC2, 0x8C57, 0x4FC3, 0x91A3, 0x4FC4, 0x89E2, + 0x4FC9, 0xFA61, 0x4FCA, 0x8F72, 0x4FCD, 0xFA73, 0x4FCE, 0x98D7, 0x4FD0, 0x98DC, 0x4FD1, 0x98DA, 0x4FD4, 0x98D5, 0x4FD7, 0x91AD, + 0x4FD8, 0x98D8, 0x4FDA, 0x98DB, 0x4FDB, 0x98D9, 0x4FDD, 0x95DB, 0x4FDF, 0x98D6, 0x4FE1, 0x904D, 0x4FE3, 0x9693, 0x4FE4, 0x98DD, + 0x4FE5, 0x98DE, 0x4FEE, 0x8F43, 0x4FEF, 0x98EB, 0x4FF3, 0x946F, 0x4FF5, 0x9555, 0x4FF6, 0x98E6, 0x4FF8, 0x95EE, 0x4FFA, 0x89B4, + 0x4FFE, 0x98EA, 0x4FFF, 0xFA76, 0x5005, 0x98E4, 0x5006, 0x98ED, 0x5009, 0x9171, 0x500B, 0x8CC2, 0x500D, 0x947B, 0x500F, 0xE0C5, + 0x5011, 0x98EC, 0x5012, 0x937C, 0x5014, 0x98E1, 0x5016, 0x8CF4, 0x5019, 0x8CF3, 0x501A, 0x98DF, 0x501E, 0xFA77, 0x501F, 0x8ED8, + 0x5021, 0x98E7, 0x5022, 0xFA75, 0x5023, 0x95ED, 0x5024, 0x926C, 0x5025, 0x98E3, 0x5026, 0x8C91, 0x5028, 0x98E0, 0x5029, 0x98E8, + 0x502A, 0x98E2, 0x502B, 0x97CF, 0x502C, 0x98E9, 0x502D, 0x9860, 0x5036, 0x8BE4, 0x5039, 0x8C90, 0x5040, 0xFA74, 0x5042, 0xFA7A, + 0x5043, 0x98EE, 0x5046, 0xFA78, 0x5047, 0x98EF, 0x5048, 0x98F3, 0x5049, 0x88CC, 0x504F, 0x95CE, 0x5050, 0x98F2, 0x5055, 0x98F1, + 0x5056, 0x98F5, 0x505A, 0x98F4, 0x505C, 0x92E2, 0x5065, 0x8C92, 0x506C, 0x98F6, 0x5070, 0xFA79, 0x5072, 0x8EC3, 0x5074, 0x91A4, + 0x5075, 0x92E3, 0x5076, 0x8BF4, 0x5078, 0x98F7, 0x507D, 0x8B55, 0x5080, 0x98F8, 0x5085, 0x98FA, 0x508D, 0x9654, 0x5091, 0x8C86, + 0x5094, 0xFA7B, 0x5098, 0x8E50, 0x5099, 0x94F5, 0x509A, 0x98F9, 0x50AC, 0x8DC3, 0x50AD, 0x9762, 0x50B2, 0x98FC, 0x50B3, 0x9942, + 0x50B4, 0x98FB, 0x50B5, 0x8DC2, 0x50B7, 0x8F9D, 0x50BE, 0x8C58, 0x50C2, 0x9943, 0x50C5, 0x8BCD, 0x50C9, 0x9940, 0x50CA, 0x9941, + 0x50CD, 0x93AD, 0x50CF, 0x919C, 0x50D1, 0x8BA1, 0x50D5, 0x966C, 0x50D6, 0x9944, 0x50D8, 0xFA7D, 0x50DA, 0x97BB, 0x50DE, 0x9945, + 0x50E3, 0x9948, 0x50E5, 0x9946, 0x50E7, 0x916D, 0x50ED, 0x9947, 0x50EE, 0x9949, 0x50F4, 0xFA7C, 0x50F5, 0x994B, 0x50F9, 0x994A, + 0x50FB, 0x95C6, 0x5100, 0x8B56, 0x5101, 0x994D, 0x5102, 0x994E, 0x5104, 0x89AD, 0x5109, 0x994C, 0x5112, 0x8EF2, 0x5114, 0x9951, + 0x5115, 0x9950, 0x5116, 0x994F, 0x5118, 0x98D4, 0x511A, 0x9952, 0x511F, 0x8F9E, 0x5121, 0x9953, 0x512A, 0x9744, 0x5132, 0x96D7, + 0x5137, 0x9955, 0x513A, 0x9954, 0x513B, 0x9957, 0x513C, 0x9956, 0x513F, 0x9958, 0x5140, 0x9959, 0x5141, 0x88F2, 0x5143, 0x8CB3, + 0x5144, 0x8C5A, 0x5145, 0x8F5B, 0x5146, 0x929B, 0x5147, 0x8BA2, 0x5148, 0x90E6, 0x5149, 0x8CF5, 0x514A, 0xFA7E, 0x514B, 0x8D8E, + 0x514C, 0x995B, 0x514D, 0x96C6, 0x514E, 0x9365, 0x5150, 0x8E99, 0x5152, 0x995A, 0x5154, 0x995C, 0x515A, 0x937D, 0x515C, 0x8A95, + 0x5162, 0x995D, 0x5164, 0xFA80, 0x5165, 0x93FC, 0x5168, 0x9153, 0x5169, 0x995F, 0x516A, 0x9960, 0x516B, 0x94AA, 0x516C, 0x8CF6, + 0x516D, 0x985A, 0x516E, 0x9961, 0x5171, 0x8BA4, 0x5175, 0x95BA, 0x5176, 0x91B4, 0x5177, 0x8BEF, 0x5178, 0x9354, 0x517C, 0x8C93, + 0x5180, 0x9962, 0x5182, 0x9963, 0x5185, 0x93E0, 0x5186, 0x897E, 0x5189, 0x9966, 0x518A, 0x8DFB, 0x518C, 0x9965, 0x518D, 0x8DC4, + 0x518F, 0x9967, 0x5190, 0xE3EC, 0x5191, 0x9968, 0x5192, 0x9660, 0x5193, 0x9969, 0x5195, 0x996A, 0x5196, 0x996B, 0x5197, 0x8FE7, + 0x5199, 0x8ECA, 0x519D, 0xFA81, 0x51A0, 0x8AA5, 0x51A2, 0x996E, 0x51A4, 0x996C, 0x51A5, 0x96BB, 0x51A6, 0x996D, 0x51A8, 0x9579, + 0x51A9, 0x996F, 0x51AA, 0x9970, 0x51AB, 0x9971, 0x51AC, 0x937E, 0x51B0, 0x9975, 0x51B1, 0x9973, 0x51B2, 0x9974, 0x51B3, 0x9972, + 0x51B4, 0x8DE1, 0x51B5, 0x9976, 0x51B6, 0x96E8, 0x51B7, 0x97E2, 0x51BD, 0x9977, 0x51BE, 0xFA82, 0x51C4, 0x90A6, 0x51C5, 0x9978, + 0x51C6, 0x8F79, 0x51C9, 0x9979, 0x51CB, 0x929C, 0x51CC, 0x97BD, 0x51CD, 0x9380, 0x51D6, 0x99C3, 0x51DB, 0x997A, 0x51DC, 0xEAA3, + 0x51DD, 0x8BC3, 0x51E0, 0x997B, 0x51E1, 0x967D, 0x51E6, 0x8F88, 0x51E7, 0x91FA, 0x51E9, 0x997D, 0x51EA, 0x93E2, 0x51EC, 0xFA83, + 0x51ED, 0x997E, 0x51F0, 0x9980, 0x51F1, 0x8A4D, 0x51F5, 0x9981, 0x51F6, 0x8BA5, 0x51F8, 0x93CA, 0x51F9, 0x899A, 0x51FA, 0x8F6F, + 0x51FD, 0x949F, 0x51FE, 0x9982, 0x5200, 0x9381, 0x5203, 0x906E, 0x5204, 0x9983, 0x5206, 0x95AA, 0x5207, 0x90D8, 0x5208, 0x8AA0, + 0x520A, 0x8AA7, 0x520B, 0x9984, 0x520E, 0x9986, 0x5211, 0x8C59, 0x5214, 0x9985, 0x5215, 0xFA84, 0x5217, 0x97F1, 0x521D, 0x8F89, + 0x5224, 0x94BB, 0x5225, 0x95CA, 0x5227, 0x9987, 0x5229, 0x9798, 0x522A, 0x9988, 0x522E, 0x9989, 0x5230, 0x939E, 0x5233, 0x998A, + 0x5236, 0x90A7, 0x5237, 0x8DFC, 0x5238, 0x8C94, 0x5239, 0x998B, 0x523A, 0x8E68, 0x523B, 0x8D8F, 0x5243, 0x92E4, 0x5244, 0x998D, + 0x5247, 0x91A5, 0x524A, 0x8DED, 0x524B, 0x998E, 0x524C, 0x998F, 0x524D, 0x914F, 0x524F, 0x998C, 0x5254, 0x9991, 0x5256, 0x9655, + 0x525B, 0x8D84, 0x525E, 0x9990, 0x5263, 0x8C95, 0x5264, 0x8DDC, 0x5265, 0x948D, 0x5269, 0x9994, 0x526A, 0x9992, 0x526F, 0x959B, + 0x5270, 0x8FE8, 0x5271, 0x999B, 0x5272, 0x8A84, 0x5273, 0x9995, 0x5274, 0x9993, 0x5275, 0x916E, 0x527D, 0x9997, 0x527F, 0x9996, + 0x5283, 0x8A63, 0x5287, 0x8C80, 0x5288, 0x999C, 0x5289, 0x97AB, 0x528D, 0x9998, 0x5291, 0x999D, 0x5292, 0x999A, 0x5294, 0x9999, + 0x529B, 0x97CD, 0x529C, 0xFA85, 0x529F, 0x8CF7, 0x52A0, 0x89C1, 0x52A3, 0x97F2, 0x52A6, 0xFA86, 0x52A9, 0x8F95, 0x52AA, 0x9377, + 0x52AB, 0x8D85, 0x52AC, 0x99A0, 0x52AD, 0x99A1, 0x52AF, 0xFB77, 0x52B1, 0x97E3, 0x52B4, 0x984A, 0x52B5, 0x99A3, 0x52B9, 0x8CF8, + 0x52BC, 0x99A2, 0x52BE, 0x8A4E, 0x52C0, 0xFA87, 0x52C1, 0x99A4, 0x52C3, 0x9675, 0x52C5, 0x92BA, 0x52C7, 0x9745, 0x52C9, 0x95D7, + 0x52CD, 0x99A5, 0x52D2, 0xE8D3, 0x52D5, 0x93AE, 0x52D7, 0x99A6, 0x52D8, 0x8AA8, 0x52D9, 0x96B1, 0x52DB, 0xFA88, 0x52DD, 0x8F9F, + 0x52DE, 0x99A7, 0x52DF, 0x95E5, 0x52E0, 0x99AB, 0x52E2, 0x90A8, 0x52E3, 0x99A8, 0x52E4, 0x8BCE, 0x52E6, 0x99A9, 0x52E7, 0x8AA9, + 0x52F2, 0x8C4D, 0x52F3, 0x99AC, 0x52F5, 0x99AD, 0x52F8, 0x99AE, 0x52F9, 0x99AF, 0x52FA, 0x8ED9, 0x52FE, 0x8CF9, 0x52FF, 0x96DC, + 0x5300, 0xFA89, 0x5301, 0x96E6, 0x5302, 0x93F5, 0x5305, 0x95EF, 0x5306, 0x99B0, 0x5307, 0xFA8A, 0x5308, 0x99B1, 0x530D, 0x99B3, + 0x530F, 0x99B5, 0x5310, 0x99B4, 0x5315, 0x99B6, 0x5316, 0x89BB, 0x5317, 0x966B, 0x5319, 0x8DFA, 0x531A, 0x99B7, 0x531D, 0x9178, + 0x5320, 0x8FA0, 0x5321, 0x8BA7, 0x5323, 0x99B8, 0x5324, 0xFA8B, 0x532A, 0x94D9, 0x532F, 0x99B9, 0x5331, 0x99BA, 0x5333, 0x99BB, + 0x5338, 0x99BC, 0x5339, 0x9543, 0x533A, 0x8BE6, 0x533B, 0x88E3, 0x533F, 0x93BD, 0x5340, 0x99BD, 0x5341, 0x8F5C, 0x5343, 0x90E7, + 0x5345, 0x99BF, 0x5346, 0x99BE, 0x5347, 0x8FA1, 0x5348, 0x8CDF, 0x5349, 0x99C1, 0x534A, 0x94BC, 0x534D, 0x99C2, 0x5351, 0x94DA, + 0x5352, 0x91B2, 0x5353, 0x91EC, 0x5354, 0x8BA6, 0x5357, 0x93EC, 0x5358, 0x9250, 0x535A, 0x948E, 0x535C, 0x966D, 0x535E, 0x99C4, + 0x5360, 0x90E8, 0x5366, 0x8C54, 0x5369, 0x99C5, 0x536E, 0x99C6, 0x536F, 0x894B, 0x5370, 0x88F3, 0x5371, 0x8AEB, 0x5372, 0xFA8C, + 0x5373, 0x91A6, 0x5374, 0x8B70, 0x5375, 0x9791, 0x5377, 0x99C9, 0x5378, 0x89B5, 0x537B, 0x99C8, 0x537F, 0x8BA8, 0x5382, 0x99CA, + 0x5384, 0x96EF, 0x5393, 0xFA8D, 0x5396, 0x99CB, 0x5398, 0x97D0, 0x539A, 0x8CFA, 0x539F, 0x8CB4, 0x53A0, 0x99CC, 0x53A5, 0x99CE, + 0x53A6, 0x99CD, 0x53A8, 0x907E, 0x53A9, 0x8958, 0x53AD, 0x897D, 0x53AE, 0x99CF, 0x53B0, 0x99D0, 0x53B2, 0xFA8E, 0x53B3, 0x8CB5, + 0x53B6, 0x99D1, 0x53BB, 0x8B8E, 0x53C2, 0x8E51, 0x53C3, 0x99D2, 0x53C8, 0x9694, 0x53C9, 0x8DB3, 0x53CA, 0x8B79, 0x53CB, 0x9746, + 0x53CC, 0x916F, 0x53CD, 0x94BD, 0x53CE, 0x8EFB, 0x53D4, 0x8F66, 0x53D6, 0x8EE6, 0x53D7, 0x8EF3, 0x53D9, 0x8F96, 0x53DB, 0x94BE, + 0x53DD, 0xFA8F, 0x53DF, 0x99D5, 0x53E1, 0x8962, 0x53E2, 0x9170, 0x53E3, 0x8CFB, 0x53E4, 0x8CC3, 0x53E5, 0x8BE5, 0x53E8, 0x99D9, + 0x53E9, 0x9240, 0x53EA, 0x91FC, 0x53EB, 0x8BA9, 0x53EC, 0x8FA2, 0x53ED, 0x99DA, 0x53EE, 0x99D8, 0x53EF, 0x89C2, 0x53F0, 0x91E4, + 0x53F1, 0x8EB6, 0x53F2, 0x8E6A, 0x53F3, 0x8945, 0x53F6, 0x8A90, 0x53F7, 0x8D86, 0x53F8, 0x8E69, 0x53FA, 0x99DB, 0x5401, 0x99DC, + 0x5403, 0x8B68, 0x5404, 0x8A65, 0x5408, 0x8D87, 0x5409, 0x8B67, 0x540A, 0x92DD, 0x540B, 0x8944, 0x540C, 0x93AF, 0x540D, 0x96BC, + 0x540E, 0x8D40, 0x540F, 0x9799, 0x5410, 0x9366, 0x5411, 0x8CFC, 0x541B, 0x8C4E, 0x541D, 0x99E5, 0x541F, 0x8BE1, 0x5420, 0x9669, + 0x5426, 0x94DB, 0x5429, 0x99E4, 0x542B, 0x8ADC, 0x542C, 0x99DF, 0x542D, 0x99E0, 0x542E, 0x99E2, 0x5436, 0x99E3, 0x5438, 0x8B7A, + 0x5439, 0x9081, 0x543B, 0x95AB, 0x543C, 0x99E1, 0x543D, 0x99DD, 0x543E, 0x8CE1, 0x5440, 0x99DE, 0x5442, 0x9843, 0x5446, 0x95F0, + 0x5448, 0x92E6, 0x5449, 0x8CE0, 0x544A, 0x8D90, 0x544E, 0x99E6, 0x5451, 0x93DB, 0x545F, 0x99EA, 0x5468, 0x8EFC, 0x546A, 0x8EF4, + 0x5470, 0x99ED, 0x5471, 0x99EB, 0x5473, 0x96A1, 0x5475, 0x99E8, 0x5476, 0x99F1, 0x5477, 0x99EC, 0x547B, 0x99EF, 0x547C, 0x8CC4, + 0x547D, 0x96BD, 0x5480, 0x99F0, 0x5484, 0x99F2, 0x5486, 0x99F4, 0x548A, 0xFA92, 0x548B, 0x8DEE, 0x548C, 0x9861, 0x548E, 0x99E9, + 0x548F, 0x99E7, 0x5490, 0x99F3, 0x5492, 0x99EE, 0x549C, 0xFA91, 0x54A2, 0x99F6, 0x54A4, 0x9A42, 0x54A5, 0x99F8, 0x54A8, 0x99FC, + 0x54A9, 0xFA93, 0x54AB, 0x9A40, 0x54AC, 0x99F9, 0x54AF, 0x9A5D, 0x54B2, 0x8DE7, 0x54B3, 0x8A50, 0x54B8, 0x99F7, 0x54BC, 0x9A44, + 0x54BD, 0x88F4, 0x54BE, 0x9A43, 0x54C0, 0x88A3, 0x54C1, 0x9569, 0x54C2, 0x9A41, 0x54C4, 0x99FA, 0x54C7, 0x99F5, 0x54C8, 0x99FB, + 0x54C9, 0x8DC6, 0x54D8, 0x9A45, 0x54E1, 0x88F5, 0x54E2, 0x9A4E, 0x54E5, 0x9A46, 0x54E6, 0x9A47, 0x54E8, 0x8FA3, 0x54E9, 0x9689, + 0x54ED, 0x9A4C, 0x54EE, 0x9A4B, 0x54F2, 0x934E, 0x54FA, 0x9A4D, 0x54FD, 0x9A4A, 0x54FF, 0xFA94, 0x5504, 0x8953, 0x5506, 0x8DB4, + 0x5507, 0x904F, 0x550F, 0x9A48, 0x5510, 0x9382, 0x5514, 0x9A49, 0x5516, 0x88A0, 0x552E, 0x9A53, 0x552F, 0x9742, 0x5531, 0x8FA5, + 0x5533, 0x9A59, 0x5538, 0x9A58, 0x5539, 0x9A4F, 0x553E, 0x91C1, 0x5540, 0x9A50, 0x5544, 0x91ED, 0x5545, 0x9A55, 0x5546, 0x8FA4, + 0x554C, 0x9A52, 0x554F, 0x96E2, 0x5553, 0x8C5B, 0x5556, 0x9A56, 0x5557, 0x9A57, 0x555C, 0x9A54, 0x555D, 0x9A5A, 0x5563, 0x9A51, + 0x557B, 0x9A60, 0x557C, 0x9A65, 0x557E, 0x9A61, 0x5580, 0x9A5C, 0x5583, 0x9A66, 0x5584, 0x9150, 0x5586, 0xFA95, 0x5587, 0x9A68, + 0x5589, 0x8D41, 0x558A, 0x9A5E, 0x558B, 0x929D, 0x5598, 0x9A62, 0x5599, 0x9A5B, 0x559A, 0x8AAB, 0x559C, 0x8AEC, 0x559D, 0x8A85, + 0x559E, 0x9A63, 0x559F, 0x9A5F, 0x55A7, 0x8C96, 0x55A8, 0x9A69, 0x55A9, 0x9A67, 0x55AA, 0x9172, 0x55AB, 0x8B69, 0x55AC, 0x8BAA, + 0x55AE, 0x9A64, 0x55B0, 0x8BF2, 0x55B6, 0x8963, 0x55C4, 0x9A6D, 0x55C5, 0x9A6B, 0x55C7, 0x9AA5, 0x55D4, 0x9A70, 0x55DA, 0x9A6A, + 0x55DC, 0x9A6E, 0x55DF, 0x9A6C, 0x55E3, 0x8E6B, 0x55E4, 0x9A6F, 0x55F7, 0x9A72, 0x55F9, 0x9A77, 0x55FD, 0x9A75, 0x55FE, 0x9A74, + 0x5606, 0x9251, 0x5609, 0x89C3, 0x5614, 0x9A71, 0x5616, 0x9A73, 0x5617, 0x8FA6, 0x5618, 0x8952, 0x561B, 0x9A76, 0x5629, 0x89DC, + 0x562F, 0x9A82, 0x5631, 0x8FFA, 0x5632, 0x9A7D, 0x5634, 0x9A7B, 0x5636, 0x9A7C, 0x5638, 0x9A7E, 0x5642, 0x895C, 0x564C, 0x9158, + 0x564E, 0x9A78, 0x5650, 0x9A79, 0x565B, 0x8A9A, 0x5664, 0x9A81, 0x5668, 0x8AED, 0x566A, 0x9A84, 0x566B, 0x9A80, 0x566C, 0x9A83, + 0x5674, 0x95AC, 0x5678, 0x93D3, 0x567A, 0x94B6, 0x5680, 0x9A86, 0x5686, 0x9A85, 0x5687, 0x8A64, 0x568A, 0x9A87, 0x568F, 0x9A8A, + 0x5694, 0x9A89, 0x56A0, 0x9A88, 0x56A2, 0x9458, 0x56A5, 0x9A8B, 0x56AE, 0x9A8C, 0x56B4, 0x9A8E, 0x56B6, 0x9A8D, 0x56BC, 0x9A90, + 0x56C0, 0x9A93, 0x56C1, 0x9A91, 0x56C2, 0x9A8F, 0x56C3, 0x9A92, 0x56C8, 0x9A94, 0x56CE, 0x9A95, 0x56D1, 0x9A96, 0x56D3, 0x9A97, + 0x56D7, 0x9A98, 0x56D8, 0x9964, 0x56DA, 0x8EFA, 0x56DB, 0x8E6C, 0x56DE, 0x89F1, 0x56E0, 0x88F6, 0x56E3, 0x9263, 0x56EE, 0x9A99, + 0x56F0, 0x8DA2, 0x56F2, 0x88CD, 0x56F3, 0x907D, 0x56F9, 0x9A9A, 0x56FA, 0x8CC5, 0x56FD, 0x8D91, 0x56FF, 0x9A9C, 0x5700, 0x9A9B, + 0x5703, 0x95DE, 0x5704, 0x9A9D, 0x5708, 0x9A9F, 0x5709, 0x9A9E, 0x570B, 0x9AA0, 0x570D, 0x9AA1, 0x570F, 0x8C97, 0x5712, 0x8980, + 0x5713, 0x9AA2, 0x5716, 0x9AA4, 0x5718, 0x9AA3, 0x571C, 0x9AA6, 0x571F, 0x9379, 0x5726, 0x9AA7, 0x5727, 0x88B3, 0x5728, 0x8DDD, + 0x572D, 0x8C5C, 0x5730, 0x926E, 0x5737, 0x9AA8, 0x5738, 0x9AA9, 0x573B, 0x9AAB, 0x5740, 0x9AAC, 0x5742, 0x8DE2, 0x5747, 0x8BCF, + 0x574A, 0x9656, 0x574E, 0x9AAA, 0x574F, 0x9AAD, 0x5750, 0x8DBF, 0x5751, 0x8D42, 0x5759, 0xFA96, 0x5761, 0x9AB1, 0x5764, 0x8DA3, + 0x5765, 0xFA97, 0x5766, 0x9252, 0x5769, 0x9AAE, 0x576A, 0x92D8, 0x577F, 0x9AB2, 0x5782, 0x9082, 0x5788, 0x9AB0, 0x5789, 0x9AB3, + 0x578B, 0x8C5E, 0x5793, 0x9AB4, 0x57A0, 0x9AB5, 0x57A2, 0x8D43, 0x57A3, 0x8A5F, 0x57A4, 0x9AB7, 0x57AA, 0x9AB8, 0x57AC, 0xFA98, + 0x57B0, 0x9AB9, 0x57B3, 0x9AB6, 0x57C0, 0x9AAF, 0x57C3, 0x9ABA, 0x57C6, 0x9ABB, 0x57C7, 0xFA9A, 0x57C8, 0xFA99, 0x57CB, 0x9684, + 0x57CE, 0x8FE9, 0x57D2, 0x9ABD, 0x57D3, 0x9ABE, 0x57D4, 0x9ABC, 0x57D6, 0x9AC0, 0x57DC, 0x9457, 0x57DF, 0x88E6, 0x57E0, 0x9575, + 0x57E3, 0x9AC1, 0x57F4, 0x8FFB, 0x57F7, 0x8EB7, 0x57F9, 0x947C, 0x57FA, 0x8AEE, 0x57FC, 0x8DE9, 0x5800, 0x9678, 0x5802, 0x93B0, + 0x5805, 0x8C98, 0x5806, 0x91CD, 0x580A, 0x9ABF, 0x580B, 0x9AC2, 0x5815, 0x91C2, 0x5819, 0x9AC3, 0x581D, 0x9AC4, 0x5821, 0x9AC6, + 0x5824, 0x92E7, 0x582A, 0x8AAC, 0x582F, 0xEA9F, 0x5830, 0x8981, 0x5831, 0x95F1, 0x5834, 0x8FEA, 0x5835, 0x9367, 0x583A, 0x8DE4, + 0x583D, 0x9ACC, 0x5840, 0x95BB, 0x5841, 0x97DB, 0x584A, 0x89F2, 0x584B, 0x9AC8, 0x5851, 0x9159, 0x5852, 0x9ACB, 0x5854, 0x9383, + 0x5857, 0x9368, 0x5858, 0x9384, 0x5859, 0x94B7, 0x585A, 0x92CB, 0x585E, 0x8DC7, 0x5862, 0x9AC7, 0x5869, 0x8996, 0x586B, 0x9355, + 0x5870, 0x9AC9, 0x5872, 0x9AC5, 0x5875, 0x906F, 0x5879, 0x9ACD, 0x587E, 0x8F6D, 0x5883, 0x8BAB, 0x5885, 0x9ACE, 0x5893, 0x95E6, + 0x5897, 0x919D, 0x589C, 0x92C4, 0x589E, 0xFA9D, 0x589F, 0x9AD0, 0x58A8, 0x966E, 0x58AB, 0x9AD1, 0x58AE, 0x9AD6, 0x58B2, 0xFA9E, + 0x58B3, 0x95AD, 0x58B8, 0x9AD5, 0x58B9, 0x9ACF, 0x58BA, 0x9AD2, 0x58BB, 0x9AD4, 0x58BE, 0x8DA4, 0x58C1, 0x95C7, 0x58C5, 0x9AD7, + 0x58C7, 0x9264, 0x58CA, 0x89F3, 0x58CC, 0x8FEB, 0x58D1, 0x9AD9, 0x58D3, 0x9AD8, 0x58D5, 0x8D88, 0x58D7, 0x9ADA, 0x58D8, 0x9ADC, + 0x58D9, 0x9ADB, 0x58DC, 0x9ADE, 0x58DE, 0x9AD3, 0x58DF, 0x9AE0, 0x58E4, 0x9ADF, 0x58E5, 0x9ADD, 0x58EB, 0x8E6D, 0x58EC, 0x9070, + 0x58EE, 0x9173, 0x58EF, 0x9AE1, 0x58F0, 0x90BA, 0x58F1, 0x88EB, 0x58F2, 0x9484, 0x58F7, 0x92D9, 0x58F9, 0x9AE3, 0x58FA, 0x9AE2, + 0x58FB, 0x9AE4, 0x58FC, 0x9AE5, 0x58FD, 0x9AE6, 0x5902, 0x9AE7, 0x5909, 0x95CF, 0x590A, 0x9AE8, 0x590B, 0xFA9F, 0x590F, 0x89C4, + 0x5910, 0x9AE9, 0x5915, 0x975B, 0x5916, 0x8A4F, 0x5918, 0x99C7, 0x5919, 0x8F67, 0x591A, 0x91BD, 0x591B, 0x9AEA, 0x591C, 0x96E9, + 0x5922, 0x96B2, 0x5925, 0x9AEC, 0x5927, 0x91E5, 0x5929, 0x9356, 0x592A, 0x91BE, 0x592B, 0x9576, 0x592C, 0x9AED, 0x592D, 0x9AEE, + 0x592E, 0x899B, 0x5931, 0x8EB8, 0x5932, 0x9AEF, 0x5937, 0x88CE, 0x5938, 0x9AF0, 0x593E, 0x9AF1, 0x5944, 0x8982, 0x5947, 0x8AEF, + 0x5948, 0x93DE, 0x5949, 0x95F2, 0x594E, 0x9AF5, 0x594F, 0x9174, 0x5950, 0x9AF4, 0x5951, 0x8C5F, 0x5953, 0xFAA0, 0x5954, 0x967A, + 0x5955, 0x9AF3, 0x5957, 0x9385, 0x5958, 0x9AF7, 0x595A, 0x9AF6, 0x595B, 0xFAA1, 0x595D, 0xFAA2, 0x5960, 0x9AF9, 0x5962, 0x9AF8, + 0x5963, 0xFAA3, 0x5965, 0x899C, 0x5967, 0x9AFA, 0x5968, 0x8FA7, 0x5969, 0x9AFC, 0x596A, 0x9244, 0x596C, 0x9AFB, 0x596E, 0x95B1, + 0x5973, 0x8F97, 0x5974, 0x937A, 0x5978, 0x9B40, 0x597D, 0x8D44, 0x5981, 0x9B41, 0x5982, 0x9440, 0x5983, 0x94DC, 0x5984, 0x96CF, + 0x598A, 0x9444, 0x598D, 0x9B4A, 0x5993, 0x8B57, 0x5996, 0x9764, 0x5999, 0x96AD, 0x599B, 0x9BAA, 0x599D, 0x9B42, 0x59A3, 0x9B45, + 0x59A4, 0xFAA4, 0x59A5, 0x91C3, 0x59A8, 0x9657, 0x59AC, 0x9369, 0x59B2, 0x9B46, 0x59B9, 0x9685, 0x59BA, 0xFAA5, 0x59BB, 0x8DC8, + 0x59BE, 0x8FA8, 0x59C6, 0x9B47, 0x59C9, 0x8E6F, 0x59CB, 0x8E6E, 0x59D0, 0x88B7, 0x59D1, 0x8CC6, 0x59D3, 0x90A9, 0x59D4, 0x88CF, + 0x59D9, 0x9B4B, 0x59DA, 0x9B4C, 0x59DC, 0x9B49, 0x59E5, 0x8957, 0x59E6, 0x8AAD, 0x59E8, 0x9B48, 0x59EA, 0x96C3, 0x59EB, 0x9550, + 0x59F6, 0x88A6, 0x59FB, 0x88F7, 0x59FF, 0x8E70, 0x5A01, 0x88D0, 0x5A03, 0x88A1, 0x5A09, 0x9B51, 0x5A11, 0x9B4F, 0x5A18, 0x96BA, + 0x5A1A, 0x9B52, 0x5A1C, 0x9B50, 0x5A1F, 0x9B4E, 0x5A20, 0x9050, 0x5A25, 0x9B4D, 0x5A29, 0x95D8, 0x5A2F, 0x8CE2, 0x5A35, 0x9B56, + 0x5A36, 0x9B57, 0x5A3C, 0x8FA9, 0x5A40, 0x9B53, 0x5A41, 0x984B, 0x5A46, 0x946B, 0x5A49, 0x9B55, 0x5A5A, 0x8DA5, 0x5A62, 0x9B58, + 0x5A66, 0x9577, 0x5A6A, 0x9B59, 0x5A6C, 0x9B54, 0x5A7F, 0x96B9, 0x5A92, 0x947D, 0x5A9A, 0x9B5A, 0x5A9B, 0x9551, 0x5ABC, 0x9B5B, + 0x5ABD, 0x9B5F, 0x5ABE, 0x9B5C, 0x5AC1, 0x89C5, 0x5AC2, 0x9B5E, 0x5AC9, 0x8EB9, 0x5ACB, 0x9B5D, 0x5ACC, 0x8C99, 0x5AD0, 0x9B6B, + 0x5AD6, 0x9B64, 0x5AD7, 0x9B61, 0x5AE1, 0x9284, 0x5AE3, 0x9B60, 0x5AE6, 0x9B62, 0x5AE9, 0x9B63, 0x5AFA, 0x9B65, 0x5AFB, 0x9B66, + 0x5B09, 0x8AF0, 0x5B0B, 0x9B68, 0x5B0C, 0x9B67, 0x5B16, 0x9B69, 0x5B22, 0x8FEC, 0x5B2A, 0x9B6C, 0x5B2C, 0x92DA, 0x5B30, 0x8964, + 0x5B32, 0x9B6A, 0x5B36, 0x9B6D, 0x5B3E, 0x9B6E, 0x5B40, 0x9B71, 0x5B43, 0x9B6F, 0x5B45, 0x9B70, 0x5B50, 0x8E71, 0x5B51, 0x9B72, + 0x5B54, 0x8D45, 0x5B55, 0x9B73, 0x5B56, 0xFAA6, 0x5B57, 0x8E9A, 0x5B58, 0x91B6, 0x5B5A, 0x9B74, 0x5B5B, 0x9B75, 0x5B5C, 0x8E79, + 0x5B5D, 0x8D46, 0x5B5F, 0x96D0, 0x5B63, 0x8B47, 0x5B64, 0x8CC7, 0x5B65, 0x9B76, 0x5B66, 0x8A77, 0x5B69, 0x9B77, 0x5B6B, 0x91B7, + 0x5B70, 0x9B78, 0x5B71, 0x9BA1, 0x5B73, 0x9B79, 0x5B75, 0x9B7A, 0x5B78, 0x9B7B, 0x5B7A, 0x9B7D, 0x5B80, 0x9B7E, 0x5B83, 0x9B80, + 0x5B85, 0x91EE, 0x5B87, 0x8946, 0x5B88, 0x8EE7, 0x5B89, 0x88C0, 0x5B8B, 0x9176, 0x5B8C, 0x8AAE, 0x5B8D, 0x8EB3, 0x5B8F, 0x8D47, + 0x5B95, 0x9386, 0x5B97, 0x8F40, 0x5B98, 0x8AAF, 0x5B99, 0x9288, 0x5B9A, 0x92E8, 0x5B9B, 0x88B6, 0x5B9C, 0x8B58, 0x5B9D, 0x95F3, + 0x5B9F, 0x8EC0, 0x5BA2, 0x8B71, 0x5BA3, 0x90E9, 0x5BA4, 0x8EBA, 0x5BA5, 0x9747, 0x5BA6, 0x9B81, 0x5BAE, 0x8B7B, 0x5BB0, 0x8DC9, + 0x5BB3, 0x8A51, 0x5BB4, 0x8983, 0x5BB5, 0x8FAA, 0x5BB6, 0x89C6, 0x5BB8, 0x9B82, 0x5BB9, 0x9765, 0x5BBF, 0x8F68, 0x5BC0, 0xFAA7, + 0x5BC2, 0x8EE2, 0x5BC3, 0x9B83, 0x5BC4, 0x8AF1, 0x5BC5, 0x93D0, 0x5BC6, 0x96A7, 0x5BC7, 0x9B84, 0x5BC9, 0x9B85, 0x5BCC, 0x9578, + 0x5BD0, 0x9B87, 0x5BD2, 0x8AA6, 0x5BD3, 0x8BF5, 0x5BD4, 0x9B86, 0x5BD8, 0xFAA9, 0x5BDB, 0x8AB0, 0x5BDD, 0x9051, 0x5BDE, 0x9B8B, + 0x5BDF, 0x8E40, 0x5BE1, 0x89C7, 0x5BE2, 0x9B8A, 0x5BE4, 0x9B88, 0x5BE5, 0x9B8C, 0x5BE6, 0x9B89, 0x5BE7, 0x944A, 0x5BE8, 0x9ECB, + 0x5BE9, 0x9052, 0x5BEB, 0x9B8D, 0x5BEC, 0xFAAA, 0x5BEE, 0x97BE, 0x5BF0, 0x9B8E, 0x5BF3, 0x9B90, 0x5BF5, 0x929E, 0x5BF6, 0x9B8F, + 0x5BF8, 0x90A1, 0x5BFA, 0x8E9B, 0x5BFE, 0x91CE, 0x5BFF, 0x8EF5, 0x5C01, 0x9595, 0x5C02, 0x90EA, 0x5C04, 0x8ECB, 0x5C05, 0x9B91, + 0x5C06, 0x8FAB, 0x5C07, 0x9B92, 0x5C08, 0x9B93, 0x5C09, 0x88D1, 0x5C0A, 0x91B8, 0x5C0B, 0x9071, 0x5C0D, 0x9B94, 0x5C0E, 0x93B1, + 0x5C0F, 0x8FAC, 0x5C11, 0x8FAD, 0x5C13, 0x9B95, 0x5C16, 0x90EB, 0x5C1A, 0x8FAE, 0x5C1E, 0xFAAB, 0x5C20, 0x9B96, 0x5C22, 0x9B97, + 0x5C24, 0x96DE, 0x5C28, 0x9B98, 0x5C2D, 0x8BC4, 0x5C31, 0x8F41, 0x5C38, 0x9B99, 0x5C39, 0x9B9A, 0x5C3A, 0x8EDA, 0x5C3B, 0x904B, + 0x5C3C, 0x93F2, 0x5C3D, 0x9073, 0x5C3E, 0x94F6, 0x5C3F, 0x9441, 0x5C40, 0x8BC7, 0x5C41, 0x9B9B, 0x5C45, 0x8B8F, 0x5C46, 0x9B9C, + 0x5C48, 0x8BFC, 0x5C4A, 0x93CD, 0x5C4B, 0x89AE, 0x5C4D, 0x8E72, 0x5C4E, 0x9B9D, 0x5C4F, 0x9BA0, 0x5C50, 0x9B9F, 0x5C51, 0x8BFB, + 0x5C53, 0x9B9E, 0x5C55, 0x9357, 0x5C5E, 0x91AE, 0x5C60, 0x936A, 0x5C61, 0x8EC6, 0x5C64, 0x9177, 0x5C65, 0x979A, 0x5C6C, 0x9BA2, + 0x5C6E, 0x9BA3, 0x5C6F, 0x93D4, 0x5C71, 0x8E52, 0x5C76, 0x9BA5, 0x5C79, 0x9BA6, 0x5C8C, 0x9BA7, 0x5C90, 0x8AF2, 0x5C91, 0x9BA8, + 0x5C94, 0x9BA9, 0x5CA1, 0x89AA, 0x5CA6, 0xFAAC, 0x5CA8, 0x915A, 0x5CA9, 0x8AE2, 0x5CAB, 0x9BAB, 0x5CAC, 0x96A6, 0x5CB1, 0x91D0, + 0x5CB3, 0x8A78, 0x5CB6, 0x9BAD, 0x5CB7, 0x9BAF, 0x5CB8, 0x8ADD, 0x5CBA, 0xFAAD, 0x5CBB, 0x9BAC, 0x5CBC, 0x9BAE, 0x5CBE, 0x9BB1, + 0x5CC5, 0x9BB0, 0x5CC7, 0x9BB2, 0x5CD9, 0x9BB3, 0x5CE0, 0x93BB, 0x5CE1, 0x8BAC, 0x5CE8, 0x89E3, 0x5CE9, 0x9BB4, 0x5CEA, 0x9BB9, + 0x5CED, 0x9BB7, 0x5CEF, 0x95F5, 0x5CF0, 0x95F4, 0x5CF5, 0xFAAE, 0x5CF6, 0x9387, 0x5CFA, 0x9BB6, 0x5CFB, 0x8F73, 0x5CFD, 0x9BB5, + 0x5D07, 0x9092, 0x5D0B, 0x9BBA, 0x5D0E, 0x8DE8, 0x5D11, 0x9BC0, 0x5D14, 0x9BC1, 0x5D15, 0x9BBB, 0x5D16, 0x8A52, 0x5D17, 0x9BBC, + 0x5D18, 0x9BC5, 0x5D19, 0x9BC4, 0x5D1A, 0x9BC3, 0x5D1B, 0x9BBF, 0x5D1F, 0x9BBE, 0x5D22, 0x9BC2, 0x5D27, 0xFAAF, 0x5D29, 0x95F6, + 0x5D42, 0xFAB2, 0x5D4B, 0x9BC9, 0x5D4C, 0x9BC6, 0x5D4E, 0x9BC8, 0x5D50, 0x9792, 0x5D52, 0x9BC7, 0x5D53, 0xFAB0, 0x5D5C, 0x9BBD, + 0x5D69, 0x9093, 0x5D6C, 0x9BCA, 0x5D6D, 0xFAB3, 0x5D6F, 0x8DB5, 0x5D73, 0x9BCB, 0x5D76, 0x9BCC, 0x5D82, 0x9BCF, 0x5D84, 0x9BCE, + 0x5D87, 0x9BCD, 0x5D8B, 0x9388, 0x5D8C, 0x9BB8, 0x5D90, 0x9BD5, 0x5D9D, 0x9BD1, 0x5DA2, 0x9BD0, 0x5DAC, 0x9BD2, 0x5DAE, 0x9BD3, + 0x5DB7, 0x9BD6, 0x5DB8, 0xFAB4, 0x5DB9, 0xFAB5, 0x5DBA, 0x97E4, 0x5DBC, 0x9BD7, 0x5DBD, 0x9BD4, 0x5DC9, 0x9BD8, 0x5DCC, 0x8ADE, + 0x5DCD, 0x9BD9, 0x5DD0, 0xFAB6, 0x5DD2, 0x9BDB, 0x5DD3, 0x9BDA, 0x5DD6, 0x9BDC, 0x5DDB, 0x9BDD, 0x5DDD, 0x90EC, 0x5DDE, 0x8F42, + 0x5DE1, 0x8F84, 0x5DE3, 0x9183, 0x5DE5, 0x8D48, 0x5DE6, 0x8DB6, 0x5DE7, 0x8D49, 0x5DE8, 0x8B90, 0x5DEB, 0x9BDE, 0x5DEE, 0x8DB7, + 0x5DF1, 0x8CC8, 0x5DF2, 0x9BDF, 0x5DF3, 0x96A4, 0x5DF4, 0x9462, 0x5DF5, 0x9BE0, 0x5DF7, 0x8D4A, 0x5DFB, 0x8AAA, 0x5DFD, 0x9246, + 0x5DFE, 0x8BD0, 0x5E02, 0x8E73, 0x5E03, 0x957A, 0x5E06, 0x94BF, 0x5E0B, 0x9BE1, 0x5E0C, 0x8AF3, 0x5E11, 0x9BE4, 0x5E16, 0x929F, + 0x5E19, 0x9BE3, 0x5E1A, 0x9BE2, 0x5E1B, 0x9BE5, 0x5E1D, 0x92E9, 0x5E25, 0x9083, 0x5E2B, 0x8E74, 0x5E2D, 0x90C8, 0x5E2F, 0x91D1, + 0x5E30, 0x8B41, 0x5E33, 0x92A0, 0x5E36, 0x9BE6, 0x5E37, 0x9BE7, 0x5E38, 0x8FED, 0x5E3D, 0x9658, 0x5E40, 0x9BEA, 0x5E43, 0x9BE9, + 0x5E44, 0x9BE8, 0x5E45, 0x959D, 0x5E47, 0x9BF1, 0x5E4C, 0x9679, 0x5E4E, 0x9BEB, 0x5E54, 0x9BED, 0x5E55, 0x968B, 0x5E57, 0x9BEC, + 0x5E5F, 0x9BEE, 0x5E61, 0x94A6, 0x5E62, 0x9BEF, 0x5E63, 0x95BC, 0x5E64, 0x9BF0, 0x5E72, 0x8AB1, 0x5E73, 0x95BD, 0x5E74, 0x944E, + 0x5E75, 0x9BF2, 0x5E76, 0x9BF3, 0x5E78, 0x8D4B, 0x5E79, 0x8AB2, 0x5E7A, 0x9BF4, 0x5E7B, 0x8CB6, 0x5E7C, 0x9763, 0x5E7D, 0x9748, + 0x5E7E, 0x8AF4, 0x5E7F, 0x9BF6, 0x5E81, 0x92A1, 0x5E83, 0x8D4C, 0x5E84, 0x8FAF, 0x5E87, 0x94DD, 0x5E8A, 0x8FB0, 0x5E8F, 0x8F98, + 0x5E95, 0x92EA, 0x5E96, 0x95F7, 0x5E97, 0x9358, 0x5E9A, 0x8D4D, 0x5E9C, 0x957B, 0x5EA0, 0x9BF7, 0x5EA6, 0x9378, 0x5EA7, 0x8DC0, + 0x5EAB, 0x8CC9, 0x5EAD, 0x92EB, 0x5EB5, 0x88C1, 0x5EB6, 0x8F8E, 0x5EB7, 0x8D4E, 0x5EB8, 0x9766, 0x5EC1, 0x9BF8, 0x5EC2, 0x9BF9, + 0x5EC3, 0x9470, 0x5EC8, 0x9BFA, 0x5EC9, 0x97F5, 0x5ECA, 0x984C, 0x5ECF, 0x9BFC, 0x5ED0, 0x9BFB, 0x5ED3, 0x8A66, 0x5ED6, 0x9C40, + 0x5EDA, 0x9C43, 0x5EDB, 0x9C44, 0x5EDD, 0x9C42, 0x5EDF, 0x955F, 0x5EE0, 0x8FB1, 0x5EE1, 0x9C46, 0x5EE2, 0x9C45, 0x5EE3, 0x9C41, + 0x5EE8, 0x9C47, 0x5EE9, 0x9C48, 0x5EEC, 0x9C49, 0x5EF0, 0x9C4C, 0x5EF1, 0x9C4A, 0x5EF3, 0x9C4B, 0x5EF4, 0x9C4D, 0x5EF6, 0x8984, + 0x5EF7, 0x92EC, 0x5EF8, 0x9C4E, 0x5EFA, 0x8C9A, 0x5EFB, 0x89F4, 0x5EFC, 0x9455, 0x5EFE, 0x9C4F, 0x5EFF, 0x93F9, 0x5F01, 0x95D9, + 0x5F03, 0x9C50, 0x5F04, 0x984D, 0x5F09, 0x9C51, 0x5F0A, 0x95BE, 0x5F0B, 0x9C54, 0x5F0C, 0x989F, 0x5F0D, 0x98AF, 0x5F0F, 0x8EAE, + 0x5F10, 0x93F3, 0x5F11, 0x9C55, 0x5F13, 0x8B7C, 0x5F14, 0x92A2, 0x5F15, 0x88F8, 0x5F16, 0x9C56, 0x5F17, 0x95A4, 0x5F18, 0x8D4F, + 0x5F1B, 0x926F, 0x5F1F, 0x92ED, 0x5F21, 0xFAB7, 0x5F25, 0x96ED, 0x5F26, 0x8CB7, 0x5F27, 0x8CCA, 0x5F29, 0x9C57, 0x5F2D, 0x9C58, + 0x5F2F, 0x9C5E, 0x5F31, 0x8EE3, 0x5F34, 0xFAB8, 0x5F35, 0x92A3, 0x5F37, 0x8BAD, 0x5F38, 0x9C59, 0x5F3C, 0x954A, 0x5F3E, 0x9265, + 0x5F41, 0x9C5A, 0x5F45, 0xFA67, 0x5F48, 0x9C5B, 0x5F4A, 0x8BAE, 0x5F4C, 0x9C5C, 0x5F4E, 0x9C5D, 0x5F51, 0x9C5F, 0x5F53, 0x9396, + 0x5F56, 0x9C60, 0x5F57, 0x9C61, 0x5F59, 0x9C62, 0x5F5C, 0x9C53, 0x5F5D, 0x9C52, 0x5F61, 0x9C63, 0x5F62, 0x8C60, 0x5F66, 0x9546, + 0x5F67, 0xFAB9, 0x5F69, 0x8DCA, 0x5F6A, 0x9556, 0x5F6B, 0x92A4, 0x5F6C, 0x956A, 0x5F6D, 0x9C64, 0x5F70, 0x8FB2, 0x5F71, 0x8965, + 0x5F73, 0x9C65, 0x5F77, 0x9C66, 0x5F79, 0x96F0, 0x5F7C, 0x94DE, 0x5F7F, 0x9C69, 0x5F80, 0x899D, 0x5F81, 0x90AA, 0x5F82, 0x9C68, + 0x5F83, 0x9C67, 0x5F84, 0x8C61, 0x5F85, 0x91D2, 0x5F87, 0x9C6D, 0x5F88, 0x9C6B, 0x5F8A, 0x9C6A, 0x5F8B, 0x97A5, 0x5F8C, 0x8CE3, + 0x5F90, 0x8F99, 0x5F91, 0x9C6C, 0x5F92, 0x936B, 0x5F93, 0x8F5D, 0x5F97, 0x93BE, 0x5F98, 0x9C70, 0x5F99, 0x9C6F, 0x5F9E, 0x9C6E, + 0x5FA0, 0x9C71, 0x5FA1, 0x8CE4, 0x5FA8, 0x9C72, 0x5FA9, 0x959C, 0x5FAA, 0x8F7A, 0x5FAD, 0x9C73, 0x5FAE, 0x94F7, 0x5FB3, 0x93BF, + 0x5FB4, 0x92A5, 0x5FB7, 0xFABA, 0x5FB9, 0x934F, 0x5FBC, 0x9C74, 0x5FBD, 0x8B4A, 0x5FC3, 0x9053, 0x5FC5, 0x954B, 0x5FCC, 0x8AF5, + 0x5FCD, 0x9445, 0x5FD6, 0x9C75, 0x5FD7, 0x8E75, 0x5FD8, 0x9659, 0x5FD9, 0x965A, 0x5FDC, 0x899E, 0x5FDD, 0x9C7A, 0x5FDE, 0xFABB, + 0x5FE0, 0x9289, 0x5FE4, 0x9C77, 0x5FEB, 0x89F5, 0x5FF0, 0x9CAB, 0x5FF1, 0x9C79, 0x5FF5, 0x944F, 0x5FF8, 0x9C78, 0x5FFB, 0x9C76, + 0x5FFD, 0x8D9A, 0x5FFF, 0x9C7C, 0x600E, 0x9C83, 0x600F, 0x9C89, 0x6010, 0x9C81, 0x6012, 0x937B, 0x6015, 0x9C86, 0x6016, 0x957C, + 0x6019, 0x9C80, 0x601B, 0x9C85, 0x601C, 0x97E5, 0x601D, 0x8E76, 0x6020, 0x91D3, 0x6021, 0x9C7D, 0x6025, 0x8B7D, 0x6026, 0x9C88, + 0x6027, 0x90AB, 0x6028, 0x8985, 0x6029, 0x9C82, 0x602A, 0x89F6, 0x602B, 0x9C87, 0x602F, 0x8BAF, 0x6031, 0x9C84, 0x603A, 0x9C8A, + 0x6041, 0x9C8C, 0x6042, 0x9C96, 0x6043, 0x9C94, 0x6046, 0x9C91, 0x604A, 0x9C90, 0x604B, 0x97F6, 0x604D, 0x9C92, 0x6050, 0x8BB0, + 0x6052, 0x8D50, 0x6055, 0x8F9A, 0x6059, 0x9C99, 0x605A, 0x9C8B, 0x605D, 0xFABC, 0x605F, 0x9C8F, 0x6060, 0x9C7E, 0x6062, 0x89F8, + 0x6063, 0x9C93, 0x6064, 0x9C95, 0x6065, 0x9270, 0x6068, 0x8DA6, 0x6069, 0x89B6, 0x606A, 0x9C8D, 0x606B, 0x9C98, 0x606C, 0x9C97, + 0x606D, 0x8BB1, 0x606F, 0x91A7, 0x6070, 0x8A86, 0x6075, 0x8C62, 0x6077, 0x9C8E, 0x6081, 0x9C9A, 0x6083, 0x9C9D, 0x6084, 0x9C9F, + 0x6085, 0xFABD, 0x6089, 0x8EBB, 0x608A, 0xFABE, 0x608B, 0x9CA5, 0x608C, 0x92EE, 0x608D, 0x9C9B, 0x6092, 0x9CA3, 0x6094, 0x89F7, + 0x6096, 0x9CA1, 0x6097, 0x9CA2, 0x609A, 0x9C9E, 0x609B, 0x9CA0, 0x609F, 0x8CE5, 0x60A0, 0x9749, 0x60A3, 0x8AB3, 0x60A6, 0x8978, + 0x60A7, 0x9CA4, 0x60A9, 0x9459, 0x60AA, 0x88AB, 0x60B2, 0x94DF, 0x60B3, 0x9C7B, 0x60B4, 0x9CAA, 0x60B5, 0x9CAE, 0x60B6, 0x96E3, + 0x60B8, 0x9CA7, 0x60BC, 0x9389, 0x60BD, 0x9CAC, 0x60C5, 0x8FEE, 0x60C6, 0x9CAD, 0x60C7, 0x93D5, 0x60D1, 0x9866, 0x60D3, 0x9CA9, + 0x60D5, 0xFAC0, 0x60D8, 0x9CAF, 0x60DA, 0x8D9B, 0x60DC, 0x90C9, 0x60DE, 0xFABF, 0x60DF, 0x88D2, 0x60E0, 0x9CA8, 0x60E1, 0x9CA6, + 0x60E3, 0x9179, 0x60E7, 0x9C9C, 0x60E8, 0x8E53, 0x60F0, 0x91C4, 0x60F1, 0x9CBB, 0x60F2, 0xFAC2, 0x60F3, 0x917A, 0x60F4, 0x9CB6, + 0x60F6, 0x9CB3, 0x60F7, 0x9CB4, 0x60F9, 0x8EE4, 0x60FA, 0x9CB7, 0x60FB, 0x9CBA, 0x6100, 0x9CB5, 0x6101, 0x8F44, 0x6103, 0x9CB8, + 0x6106, 0x9CB2, 0x6108, 0x96FA, 0x6109, 0x96F9, 0x610D, 0x9CBC, 0x610E, 0x9CBD, 0x610F, 0x88D3, 0x6111, 0xFAC3, 0x6115, 0x9CB1, + 0x611A, 0x8BF0, 0x611B, 0x88A4, 0x611F, 0x8AB4, 0x6120, 0xFAC1, 0x6121, 0x9CB9, 0x6127, 0x9CC1, 0x6128, 0x9CC0, 0x612C, 0x9CC5, + 0x6130, 0xFAC5, 0x6134, 0x9CC6, 0x6137, 0xFAC4, 0x613C, 0x9CC4, 0x613D, 0x9CC7, 0x613E, 0x9CBF, 0x613F, 0x9CC3, 0x6142, 0x9CC8, + 0x6144, 0x9CC9, 0x6147, 0x9CBE, 0x6148, 0x8E9C, 0x614A, 0x9CC2, 0x614B, 0x91D4, 0x614C, 0x8D51, 0x614D, 0x9CB0, 0x614E, 0x9054, + 0x6153, 0x9CD6, 0x6155, 0x95E7, 0x6158, 0x9CCC, 0x6159, 0x9CCD, 0x615A, 0x9CCE, 0x615D, 0x9CD5, 0x615F, 0x9CD4, 0x6162, 0x969D, + 0x6163, 0x8AB5, 0x6165, 0x9CD2, 0x6167, 0x8C64, 0x6168, 0x8A53, 0x616B, 0x9CCF, 0x616E, 0x97B6, 0x616F, 0x9CD1, 0x6170, 0x88D4, + 0x6171, 0x9CD3, 0x6173, 0x9CCA, 0x6174, 0x9CD0, 0x6175, 0x9CD7, 0x6176, 0x8C63, 0x6177, 0x9CCB, 0x617E, 0x977C, 0x6182, 0x974A, + 0x6187, 0x9CDA, 0x618A, 0x9CDE, 0x618E, 0x919E, 0x6190, 0x97F7, 0x6191, 0x9CDF, 0x6194, 0x9CDC, 0x6196, 0x9CD9, 0x6198, 0xFAC6, + 0x6199, 0x9CD8, 0x619A, 0x9CDD, 0x61A4, 0x95AE, 0x61A7, 0x93B2, 0x61A9, 0x8C65, 0x61AB, 0x9CE0, 0x61AC, 0x9CDB, 0x61AE, 0x9CE1, + 0x61B2, 0x8C9B, 0x61B6, 0x89AF, 0x61BA, 0x9CE9, 0x61BE, 0x8AB6, 0x61C3, 0x9CE7, 0x61C6, 0x9CE8, 0x61C7, 0x8DA7, 0x61C8, 0x9CE6, + 0x61C9, 0x9CE4, 0x61CA, 0x9CE3, 0x61CB, 0x9CEA, 0x61CC, 0x9CE2, 0x61CD, 0x9CEC, 0x61D0, 0x89F9, 0x61E3, 0x9CEE, 0x61E6, 0x9CED, + 0x61F2, 0x92A6, 0x61F4, 0x9CF1, 0x61F6, 0x9CEF, 0x61F7, 0x9CE5, 0x61F8, 0x8C9C, 0x61FA, 0x9CF0, 0x61FC, 0x9CF4, 0x61FD, 0x9CF3, + 0x61FE, 0x9CF5, 0x61FF, 0x9CF2, 0x6200, 0x9CF6, 0x6208, 0x9CF7, 0x6209, 0x9CF8, 0x620A, 0x95E8, 0x620C, 0x9CFA, 0x620D, 0x9CF9, + 0x620E, 0x8F5E, 0x6210, 0x90AC, 0x6211, 0x89E4, 0x6212, 0x89FA, 0x6213, 0xFAC7, 0x6214, 0x9CFB, 0x6216, 0x88BD, 0x621A, 0x90CA, + 0x621B, 0x9CFC, 0x621D, 0xE6C1, 0x621E, 0x9D40, 0x621F, 0x8C81, 0x6221, 0x9D41, 0x6226, 0x90ED, 0x622A, 0x9D42, 0x622E, 0x9D43, + 0x622F, 0x8B59, 0x6230, 0x9D44, 0x6232, 0x9D45, 0x6233, 0x9D46, 0x6234, 0x91D5, 0x6238, 0x8CCB, 0x623B, 0x96DF, 0x623F, 0x965B, + 0x6240, 0x8F8A, 0x6241, 0x9D47, 0x6247, 0x90EE, 0x6248, 0xE7BB, 0x6249, 0x94E0, 0x624B, 0x8EE8, 0x624D, 0x8DCB, 0x624E, 0x9D48, + 0x6253, 0x91C5, 0x6255, 0x95A5, 0x6258, 0x91EF, 0x625B, 0x9D4B, 0x625E, 0x9D49, 0x6260, 0x9D4C, 0x6263, 0x9D4A, 0x6268, 0x9D4D, + 0x626E, 0x95AF, 0x6271, 0x88B5, 0x6276, 0x957D, 0x6279, 0x94E1, 0x627C, 0x9D4E, 0x627E, 0x9D51, 0x627F, 0x8FB3, 0x6280, 0x8B5A, + 0x6282, 0x9D4F, 0x6283, 0x9D56, 0x6284, 0x8FB4, 0x6289, 0x9D50, 0x628A, 0x9463, 0x6291, 0x977D, 0x6292, 0x9D52, 0x6293, 0x9D53, + 0x6294, 0x9D57, 0x6295, 0x938A, 0x6296, 0x9D54, 0x6297, 0x8D52, 0x6298, 0x90DC, 0x629B, 0x9D65, 0x629C, 0x94B2, 0x629E, 0x91F0, + 0x62A6, 0xFAC8, 0x62AB, 0x94E2, 0x62AC, 0x9DAB, 0x62B1, 0x95F8, 0x62B5, 0x92EF, 0x62B9, 0x9695, 0x62BB, 0x9D5A, 0x62BC, 0x899F, + 0x62BD, 0x928A, 0x62C2, 0x9D63, 0x62C5, 0x9253, 0x62C6, 0x9D5D, 0x62C7, 0x9D64, 0x62C8, 0x9D5F, 0x62C9, 0x9D66, 0x62CA, 0x9D62, + 0x62CC, 0x9D61, 0x62CD, 0x948F, 0x62CF, 0x9D5B, 0x62D0, 0x89FB, 0x62D1, 0x9D59, 0x62D2, 0x8B91, 0x62D3, 0x91F1, 0x62D4, 0x9D55, + 0x62D7, 0x9D58, 0x62D8, 0x8D53, 0x62D9, 0x90D9, 0x62DB, 0x8FB5, 0x62DC, 0x9D60, 0x62DD, 0x9471, 0x62E0, 0x8B92, 0x62E1, 0x8A67, + 0x62EC, 0x8A87, 0x62ED, 0x9040, 0x62EE, 0x9D68, 0x62EF, 0x9D6D, 0x62F1, 0x9D69, 0x62F3, 0x8C9D, 0x62F5, 0x9D6E, 0x62F6, 0x8E41, + 0x62F7, 0x8D89, 0x62FE, 0x8F45, 0x62FF, 0x9D5C, 0x6301, 0x8E9D, 0x6302, 0x9D6B, 0x6307, 0x8E77, 0x6308, 0x9D6C, 0x6309, 0x88C2, + 0x630C, 0x9D67, 0x6311, 0x92A7, 0x6319, 0x8B93, 0x631F, 0x8BB2, 0x6327, 0x9D6A, 0x6328, 0x88A5, 0x632B, 0x8DC1, 0x632F, 0x9055, + 0x633A, 0x92F0, 0x633D, 0x94D2, 0x633E, 0x9D70, 0x633F, 0x917D, 0x6349, 0x91A8, 0x634C, 0x8E4A, 0x634D, 0x9D71, 0x634F, 0x9D73, + 0x6350, 0x9D6F, 0x6355, 0x95DF, 0x6357, 0x92BB, 0x635C, 0x917B, 0x6367, 0x95F9, 0x6368, 0x8ECC, 0x6369, 0x9D80, 0x636B, 0x9D7E, + 0x636E, 0x9098, 0x6372, 0x8C9E, 0x6376, 0x9D78, 0x6377, 0x8FB7, 0x637A, 0x93E6, 0x637B, 0x9450, 0x6380, 0x9D76, 0x6383, 0x917C, + 0x6388, 0x8EF6, 0x6389, 0x9D7B, 0x638C, 0x8FB6, 0x638E, 0x9D75, 0x638F, 0x9D7A, 0x6392, 0x9472, 0x6396, 0x9D74, 0x6398, 0x8C40, + 0x639B, 0x8A7C, 0x639F, 0x9D7C, 0x63A0, 0x97A9, 0x63A1, 0x8DCC, 0x63A2, 0x9254, 0x63A3, 0x9D79, 0x63A5, 0x90DA, 0x63A7, 0x8D54, + 0x63A8, 0x9084, 0x63A9, 0x8986, 0x63AA, 0x915B, 0x63AB, 0x9D77, 0x63AC, 0x8B64, 0x63B2, 0x8C66, 0x63B4, 0x92CD, 0x63B5, 0x9D7D, + 0x63BB, 0x917E, 0x63BE, 0x9D81, 0x63C0, 0x9D83, 0x63C3, 0x91B5, 0x63C4, 0x9D89, 0x63C6, 0x9D84, 0x63C9, 0x9D86, 0x63CF, 0x9560, + 0x63D0, 0x92F1, 0x63D2, 0x9D87, 0x63D6, 0x974B, 0x63DA, 0x9767, 0x63DB, 0x8AB7, 0x63E1, 0x88AC, 0x63E3, 0x9D85, 0x63E9, 0x9D82, + 0x63EE, 0x8AF6, 0x63F4, 0x8987, 0x63F5, 0xFAC9, 0x63F6, 0x9D88, 0x63FA, 0x9768, 0x6406, 0x9D8C, 0x640D, 0x91B9, 0x640F, 0x9D93, + 0x6413, 0x9D8D, 0x6416, 0x9D8A, 0x6417, 0x9D91, 0x641C, 0x9D72, 0x6426, 0x9D8E, 0x6428, 0x9D92, 0x642C, 0x94C0, 0x642D, 0x938B, + 0x6434, 0x9D8B, 0x6436, 0x9D8F, 0x643A, 0x8C67, 0x643E, 0x8DEF, 0x6442, 0x90DB, 0x644E, 0x9D97, 0x6458, 0x9345, 0x6460, 0xFACA, + 0x6467, 0x9D94, 0x6469, 0x9680, 0x646F, 0x9D95, 0x6476, 0x9D96, 0x6478, 0x96CC, 0x647A, 0x90A0, 0x6483, 0x8C82, 0x6488, 0x9D9D, + 0x6492, 0x8E54, 0x6493, 0x9D9A, 0x6495, 0x9D99, 0x649A, 0x9451, 0x649D, 0xFACB, 0x649E, 0x93B3, 0x64A4, 0x9350, 0x64A5, 0x9D9B, + 0x64A9, 0x9D9C, 0x64AB, 0x958F, 0x64AD, 0x9464, 0x64AE, 0x8E42, 0x64B0, 0x90EF, 0x64B2, 0x966F, 0x64B9, 0x8A68, 0x64BB, 0x9DA3, + 0x64BC, 0x9D9E, 0x64C1, 0x9769, 0x64C2, 0x9DA5, 0x64C5, 0x9DA1, 0x64C7, 0x9DA2, 0x64CD, 0x9180, 0x64CE, 0xFACC, 0x64D2, 0x9DA0, + 0x64D4, 0x9D5E, 0x64D8, 0x9DA4, 0x64DA, 0x9D9F, 0x64E0, 0x9DA9, 0x64E1, 0x9DAA, 0x64E2, 0x9346, 0x64E3, 0x9DAC, 0x64E6, 0x8E43, + 0x64E7, 0x9DA7, 0x64EC, 0x8B5B, 0x64EF, 0x9DAD, 0x64F1, 0x9DA6, 0x64F2, 0x9DB1, 0x64F4, 0x9DB0, 0x64F6, 0x9DAF, 0x64FA, 0x9DB2, + 0x64FD, 0x9DB4, 0x64FE, 0x8FEF, 0x6500, 0x9DB3, 0x6505, 0x9DB7, 0x6518, 0x9DB5, 0x651C, 0x9DB6, 0x651D, 0x9D90, 0x6523, 0x9DB9, + 0x6524, 0x9DB8, 0x652A, 0x9D98, 0x652B, 0x9DBA, 0x652C, 0x9DAE, 0x652F, 0x8E78, 0x6534, 0x9DBB, 0x6535, 0x9DBC, 0x6536, 0x9DBE, + 0x6537, 0x9DBD, 0x6538, 0x9DBF, 0x6539, 0x89FC, 0x653B, 0x8D55, 0x653E, 0x95FA, 0x653F, 0x90AD, 0x6545, 0x8CCC, 0x6548, 0x9DC1, + 0x654D, 0x9DC4, 0x654E, 0xFACD, 0x654F, 0x9571, 0x6551, 0x8B7E, 0x6555, 0x9DC3, 0x6556, 0x9DC2, 0x6557, 0x9473, 0x6558, 0x9DC5, + 0x6559, 0x8BB3, 0x655D, 0x9DC7, 0x655E, 0x9DC6, 0x6562, 0x8AB8, 0x6563, 0x8E55, 0x6566, 0x93D6, 0x656C, 0x8C68, 0x6570, 0x9094, + 0x6572, 0x9DC8, 0x6574, 0x90AE, 0x6575, 0x9347, 0x6577, 0x957E, 0x6578, 0x9DC9, 0x6582, 0x9DCA, 0x6583, 0x9DCB, 0x6587, 0x95B6, + 0x6588, 0x9B7C, 0x6589, 0x90C4, 0x658C, 0x956B, 0x658E, 0x8DD6, 0x6590, 0x94E3, 0x6591, 0x94C1, 0x6597, 0x936C, 0x6599, 0x97BF, + 0x659B, 0x9DCD, 0x659C, 0x8ECE, 0x659F, 0x9DCE, 0x65A1, 0x88B4, 0x65A4, 0x8BD2, 0x65A5, 0x90CB, 0x65A7, 0x9580, 0x65AB, 0x9DCF, + 0x65AC, 0x8E61, 0x65AD, 0x9266, 0x65AF, 0x8E7A, 0x65B0, 0x9056, 0x65B7, 0x9DD0, 0x65B9, 0x95FB, 0x65BC, 0x8997, 0x65BD, 0x8E7B, + 0x65C1, 0x9DD3, 0x65C3, 0x9DD1, 0x65C4, 0x9DD4, 0x65C5, 0x97B7, 0x65C6, 0x9DD2, 0x65CB, 0x90F9, 0x65CC, 0x9DD5, 0x65CF, 0x91B0, + 0x65D2, 0x9DD6, 0x65D7, 0x8AF8, 0x65D9, 0x9DD8, 0x65DB, 0x9DD7, 0x65E0, 0x9DD9, 0x65E1, 0x9DDA, 0x65E2, 0x8AF9, 0x65E5, 0x93FA, + 0x65E6, 0x9255, 0x65E7, 0x8B8C, 0x65E8, 0x8E7C, 0x65E9, 0x9181, 0x65EC, 0x8F7B, 0x65ED, 0x88AE, 0x65F1, 0x9DDB, 0x65FA, 0x89A0, + 0x65FB, 0x9DDF, 0x6600, 0xFACE, 0x6602, 0x8D56, 0x6603, 0x9DDE, 0x6606, 0x8DA9, 0x6607, 0x8FB8, 0x6609, 0xFAD1, 0x660A, 0x9DDD, + 0x660C, 0x8FB9, 0x660E, 0x96BE, 0x660F, 0x8DA8, 0x6613, 0x88D5, 0x6614, 0x90CC, 0x6615, 0xFACF, 0x661C, 0x9DE4, 0x661E, 0xFAD3, + 0x661F, 0x90AF, 0x6620, 0x8966, 0x6624, 0xFAD4, 0x6625, 0x8F74, 0x6627, 0x9686, 0x6628, 0x8DF0, 0x662D, 0x8FBA, 0x662E, 0xFAD2, + 0x662F, 0x90A5, 0x6631, 0xFA63, 0x6634, 0x9DE3, 0x6635, 0x9DE1, 0x6636, 0x9DE2, 0x663B, 0xFAD0, 0x663C, 0x928B, 0x663F, 0x9E45, + 0x6641, 0x9DE8, 0x6642, 0x8E9E, 0x6643, 0x8D57, 0x6644, 0x9DE6, 0x6649, 0x9DE7, 0x664B, 0x9057, 0x664F, 0x9DE5, 0x6652, 0x8E4E, + 0x6657, 0xFAD6, 0x6659, 0xFAD7, 0x665D, 0x9DEA, 0x665E, 0x9DE9, 0x665F, 0x9DEE, 0x6662, 0x9DEF, 0x6664, 0x9DEB, 0x6665, 0xFAD5, + 0x6666, 0x8A41, 0x6667, 0x9DEC, 0x6668, 0x9DED, 0x6669, 0x94D3, 0x666E, 0x9581, 0x666F, 0x8C69, 0x6670, 0x9DF0, 0x6673, 0xFAD9, + 0x6674, 0x90B0, 0x6676, 0x8FBB, 0x667A, 0x9271, 0x6681, 0x8BC5, 0x6683, 0x9DF1, 0x6684, 0x9DF5, 0x6687, 0x89C9, 0x6688, 0x9DF2, + 0x6689, 0x9DF4, 0x668E, 0x9DF3, 0x6691, 0x8F8B, 0x6696, 0x9267, 0x6697, 0x88C3, 0x6698, 0x9DF6, 0x6699, 0xFADA, 0x669D, 0x9DF7, + 0x66A0, 0xFADB, 0x66A2, 0x92A8, 0x66A6, 0x97EF, 0x66AB, 0x8E62, 0x66AE, 0x95E9, 0x66B2, 0xFADC, 0x66B4, 0x965C, 0x66B8, 0x9E41, + 0x66B9, 0x9DF9, 0x66BC, 0x9DFC, 0x66BE, 0x9DFB, 0x66BF, 0xFADD, 0x66C1, 0x9DF8, 0x66C4, 0x9E40, 0x66C7, 0x93DC, 0x66C9, 0x9DFA, + 0x66D6, 0x9E42, 0x66D9, 0x8F8C, 0x66DA, 0x9E43, 0x66DC, 0x976A, 0x66DD, 0x9498, 0x66E0, 0x9E44, 0x66E6, 0x9E46, 0x66E9, 0x9E47, + 0x66F0, 0x9E48, 0x66F2, 0x8BC8, 0x66F3, 0x8967, 0x66F4, 0x8D58, 0x66F5, 0x9E49, 0x66F7, 0x9E4A, 0x66F8, 0x8F91, 0x66F9, 0x9182, + 0x66FA, 0xFADE, 0x66FB, 0xFA66, 0x66FC, 0x99D6, 0x66FD, 0x915D, 0x66FE, 0x915C, 0x66FF, 0x91D6, 0x6700, 0x8DC5, 0x6703, 0x98F0, + 0x6708, 0x8C8E, 0x6709, 0x974C, 0x670B, 0x95FC, 0x670D, 0x959E, 0x670E, 0xFADF, 0x670F, 0x9E4B, 0x6714, 0x8DF1, 0x6715, 0x92BD, + 0x6716, 0x9E4C, 0x6717, 0x984E, 0x671B, 0x965D, 0x671D, 0x92A9, 0x671E, 0x9E4D, 0x671F, 0x8AFA, 0x6726, 0x9E4E, 0x6727, 0x9E4F, + 0x6728, 0x96D8, 0x672A, 0x96A2, 0x672B, 0x9696, 0x672C, 0x967B, 0x672D, 0x8E44, 0x672E, 0x9E51, 0x6731, 0x8EE9, 0x6734, 0x9670, + 0x6736, 0x9E53, 0x6737, 0x9E56, 0x6738, 0x9E55, 0x673A, 0x8AF7, 0x673D, 0x8B80, 0x673F, 0x9E52, 0x6741, 0x9E54, 0x6746, 0x9E57, + 0x6749, 0x9099, 0x674E, 0x979B, 0x674F, 0x88C7, 0x6750, 0x8DDE, 0x6751, 0x91BA, 0x6753, 0x8EDB, 0x6756, 0x8FF1, 0x6759, 0x9E5A, + 0x675C, 0x936D, 0x675E, 0x9E58, 0x675F, 0x91A9, 0x6760, 0x9E59, 0x6761, 0x8FF0, 0x6762, 0x96DB, 0x6763, 0x9E5B, 0x6764, 0x9E5C, + 0x6765, 0x9788, 0x6766, 0xFAE1, 0x676A, 0x9E61, 0x676D, 0x8D59, 0x676F, 0x9474, 0x6770, 0x9E5E, 0x6771, 0x938C, 0x6772, 0x9DDC, + 0x6773, 0x9DE0, 0x6775, 0x8B6E, 0x6777, 0x9466, 0x677C, 0x9E60, 0x677E, 0x8FBC, 0x677F, 0x94C2, 0x6785, 0x9E66, 0x6787, 0x94F8, + 0x6789, 0x9E5D, 0x678B, 0x9E63, 0x678C, 0x9E62, 0x6790, 0x90CD, 0x6795, 0x968D, 0x6797, 0x97D1, 0x679A, 0x9687, 0x679C, 0x89CA, + 0x679D, 0x8E7D, 0x67A0, 0x9867, 0x67A1, 0x9E65, 0x67A2, 0x9095, 0x67A6, 0x9E64, 0x67A9, 0x9E5F, 0x67AF, 0x8CCD, 0x67B3, 0x9E6B, + 0x67B4, 0x9E69, 0x67B6, 0x89CB, 0x67B7, 0x9E67, 0x67B8, 0x9E6D, 0x67B9, 0x9E73, 0x67BB, 0xFAE2, 0x67C0, 0xFAE4, 0x67C1, 0x91C6, + 0x67C4, 0x95BF, 0x67C6, 0x9E75, 0x67CA, 0x9541, 0x67CE, 0x9E74, 0x67CF, 0x9490, 0x67D0, 0x965E, 0x67D1, 0x8AB9, 0x67D3, 0x90F5, + 0x67D4, 0x8F5F, 0x67D8, 0x92D1, 0x67DA, 0x974D, 0x67DD, 0x9E70, 0x67DE, 0x9E6F, 0x67E2, 0x9E71, 0x67E4, 0x9E6E, 0x67E7, 0x9E76, + 0x67E9, 0x9E6C, 0x67EC, 0x9E6A, 0x67EE, 0x9E72, 0x67EF, 0x9E68, 0x67F1, 0x928C, 0x67F3, 0x96F6, 0x67F4, 0x8EC4, 0x67F5, 0x8DF2, + 0x67FB, 0x8DB8, 0x67FE, 0x968F, 0x67FF, 0x8A60, 0x6801, 0xFAE5, 0x6802, 0x92CC, 0x6803, 0x93C8, 0x6804, 0x8968, 0x6813, 0x90F0, + 0x6816, 0x90B2, 0x6817, 0x8C49, 0x681E, 0x9E78, 0x6821, 0x8D5A, 0x6822, 0x8A9C, 0x6829, 0x9E7A, 0x682A, 0x8A94, 0x682B, 0x9E81, + 0x6832, 0x9E7D, 0x6834, 0x90F1, 0x6838, 0x8A6A, 0x6839, 0x8DAA, 0x683C, 0x8A69, 0x683D, 0x8DCD, 0x6840, 0x9E7B, 0x6841, 0x8C85, + 0x6842, 0x8C6A, 0x6843, 0x938D, 0x6844, 0xFAE6, 0x6846, 0x9E79, 0x6848, 0x88C4, 0x684D, 0x9E7C, 0x684E, 0x9E7E, 0x6850, 0x8BCB, + 0x6851, 0x8C4B, 0x6852, 0xFAE3, 0x6853, 0x8ABA, 0x6854, 0x8B6A, 0x6859, 0x9E82, 0x685C, 0x8DF7, 0x685D, 0x9691, 0x685F, 0x8E56, + 0x6863, 0x9E83, 0x6867, 0x954F, 0x6874, 0x9E8F, 0x6876, 0x89B1, 0x6877, 0x9E84, 0x687E, 0x9E95, 0x687F, 0x9E85, 0x6881, 0x97C0, + 0x6883, 0x9E8C, 0x6885, 0x947E, 0x688D, 0x9E94, 0x688F, 0x9E87, 0x6893, 0x88B2, 0x6894, 0x9E89, 0x6897, 0x8D5B, 0x689B, 0x9E8B, + 0x689D, 0x9E8A, 0x689F, 0x9E86, 0x68A0, 0x9E91, 0x68A2, 0x8FBD, 0x68A6, 0x9AEB, 0x68A7, 0x8CE6, 0x68A8, 0x979C, 0x68AD, 0x9E88, + 0x68AF, 0x92F2, 0x68B0, 0x8A42, 0x68B1, 0x8DAB, 0x68B3, 0x9E80, 0x68B5, 0x9E90, 0x68B6, 0x8A81, 0x68B9, 0x9E8E, 0x68BA, 0x9E92, + 0x68BC, 0x938E, 0x68C4, 0x8AFC, 0x68C6, 0x9EB0, 0x68C8, 0xFA64, 0x68C9, 0x96C7, 0x68CA, 0x9E97, 0x68CB, 0x8AFB, 0x68CD, 0x9E9E, + 0x68CF, 0xFAE7, 0x68D2, 0x965F, 0x68D4, 0x9E9F, 0x68D5, 0x9EA1, 0x68D7, 0x9EA5, 0x68D8, 0x9E99, 0x68DA, 0x9249, 0x68DF, 0x938F, + 0x68E0, 0x9EA9, 0x68E1, 0x9E9C, 0x68E3, 0x9EA6, 0x68E7, 0x9EA0, 0x68EE, 0x9058, 0x68EF, 0x9EAA, 0x68F2, 0x90B1, 0x68F9, 0x9EA8, + 0x68FA, 0x8ABB, 0x6900, 0x986F, 0x6901, 0x9E96, 0x6904, 0x9EA4, 0x6905, 0x88D6, 0x6908, 0x9E98, 0x690B, 0x96B8, 0x690C, 0x9E9D, + 0x690D, 0x9041, 0x690E, 0x92C5, 0x690F, 0x9E93, 0x6912, 0x9EA3, 0x6919, 0x909A, 0x691A, 0x9EAD, 0x691B, 0x8A91, 0x691C, 0x8C9F, + 0x6921, 0x9EAF, 0x6922, 0x9E9A, 0x6923, 0x9EAE, 0x6925, 0x9EA7, 0x6926, 0x9E9B, 0x6928, 0x9EAB, 0x692A, 0x9EAC, 0x6930, 0x9EBD, + 0x6934, 0x93CC, 0x6936, 0x9EA2, 0x6939, 0x9EB9, 0x693D, 0x9EBB, 0x693F, 0x92D6, 0x694A, 0x976B, 0x6953, 0x9596, 0x6954, 0x9EB6, + 0x6955, 0x91C8, 0x6959, 0x9EBC, 0x695A, 0x915E, 0x695C, 0x9EB3, 0x695D, 0x9EC0, 0x695E, 0x9EBF, 0x6960, 0x93ED, 0x6961, 0x9EBE, + 0x6962, 0x93E8, 0x6968, 0xFAE9, 0x696A, 0x9EC2, 0x696B, 0x9EB5, 0x696D, 0x8BC6, 0x696E, 0x9EB8, 0x696F, 0x8F7C, 0x6973, 0x9480, + 0x6974, 0x9EBA, 0x6975, 0x8BC9, 0x6977, 0x9EB2, 0x6978, 0x9EB4, 0x6979, 0x9EB1, 0x697C, 0x984F, 0x697D, 0x8A79, 0x697E, 0x9EB7, + 0x6981, 0x9EC1, 0x6982, 0x8A54, 0x698A, 0x8DE5, 0x698E, 0x897C, 0x6991, 0x9ED2, 0x6994, 0x9850, 0x6995, 0x9ED5, 0x6998, 0xFAEB, + 0x699B, 0x9059, 0x699C, 0x9ED4, 0x69A0, 0x9ED3, 0x69A7, 0x9ED0, 0x69AE, 0x9EC4, 0x69B1, 0x9EE1, 0x69B2, 0x9EC3, 0x69B4, 0x9ED6, + 0x69BB, 0x9ECE, 0x69BE, 0x9EC9, 0x69BF, 0x9EC6, 0x69C1, 0x9EC7, 0x69C3, 0x9ECF, 0x69C7, 0xEAA0, 0x69CA, 0x9ECC, 0x69CB, 0x8D5C, + 0x69CC, 0x92C6, 0x69CD, 0x9184, 0x69CE, 0x9ECA, 0x69D0, 0x9EC5, 0x69D3, 0x9EC8, 0x69D8, 0x976C, 0x69D9, 0x968A, 0x69DD, 0x9ECD, + 0x69DE, 0x9ED7, 0x69E2, 0xFAEC, 0x69E7, 0x9EDF, 0x69E8, 0x9ED8, 0x69EB, 0x9EE5, 0x69ED, 0x9EE3, 0x69F2, 0x9EDE, 0x69F9, 0x9EDD, + 0x69FB, 0x92CE, 0x69FD, 0x9185, 0x69FF, 0x9EDB, 0x6A02, 0x9ED9, 0x6A05, 0x9EE0, 0x6A0A, 0x9EE6, 0x6A0B, 0x94F3, 0x6A0C, 0x9EEC, + 0x6A12, 0x9EE7, 0x6A13, 0x9EEA, 0x6A14, 0x9EE4, 0x6A17, 0x9294, 0x6A19, 0x9557, 0x6A1B, 0x9EDA, 0x6A1E, 0x9EE2, 0x6A1F, 0x8FBE, + 0x6A21, 0x96CD, 0x6A22, 0x9EF6, 0x6A23, 0x9EE9, 0x6A29, 0x8CA0, 0x6A2A, 0x89A1, 0x6A2B, 0x8A7E, 0x6A2E, 0x9ED1, 0x6A30, 0xFAED, + 0x6A35, 0x8FBF, 0x6A36, 0x9EEE, 0x6A38, 0x9EF5, 0x6A39, 0x8EF7, 0x6A3A, 0x8A92, 0x6A3D, 0x924D, 0x6A44, 0x9EEB, 0x6A46, 0xFAEF, + 0x6A47, 0x9EF0, 0x6A48, 0x9EF4, 0x6A4B, 0x8BB4, 0x6A58, 0x8B6B, 0x6A59, 0x9EF2, 0x6A5F, 0x8B40, 0x6A61, 0x93C9, 0x6A62, 0x9EF1, + 0x6A66, 0x9EF3, 0x6A6B, 0xFAEE, 0x6A72, 0x9EED, 0x6A73, 0xFAF0, 0x6A78, 0x9EEF, 0x6A7E, 0xFAF1, 0x6A7F, 0x8A80, 0x6A80, 0x9268, + 0x6A84, 0x9EFA, 0x6A8D, 0x9EF8, 0x6A8E, 0x8CE7, 0x6A90, 0x9EF7, 0x6A97, 0x9F40, 0x6A9C, 0x9E77, 0x6AA0, 0x9EF9, 0x6AA2, 0x9EFB, + 0x6AA3, 0x9EFC, 0x6AAA, 0x9F4B, 0x6AAC, 0x9F47, 0x6AAE, 0x9E8D, 0x6AB3, 0x9F46, 0x6AB8, 0x9F45, 0x6ABB, 0x9F42, 0x6AC1, 0x9EE8, + 0x6AC2, 0x9F44, 0x6AC3, 0x9F43, 0x6AD1, 0x9F49, 0x6AD3, 0x9845, 0x6ADA, 0x9F4C, 0x6ADB, 0x8BF9, 0x6ADE, 0x9F48, 0x6ADF, 0x9F4A, + 0x6AE2, 0xFAF2, 0x6AE4, 0xFAF3, 0x6AE8, 0x94A5, 0x6AEA, 0x9F4D, 0x6AFA, 0x9F51, 0x6AFB, 0x9F4E, 0x6B04, 0x9793, 0x6B05, 0x9F4F, + 0x6B0A, 0x9EDC, 0x6B12, 0x9F52, 0x6B16, 0x9F53, 0x6B1D, 0x8954, 0x6B1F, 0x9F55, 0x6B20, 0x8C87, 0x6B21, 0x8E9F, 0x6B23, 0x8BD3, + 0x6B27, 0x89A2, 0x6B32, 0x977E, 0x6B37, 0x9F57, 0x6B38, 0x9F56, 0x6B39, 0x9F59, 0x6B3A, 0x8B5C, 0x6B3D, 0x8BD4, 0x6B3E, 0x8ABC, + 0x6B43, 0x9F5C, 0x6B47, 0x9F5B, 0x6B49, 0x9F5D, 0x6B4C, 0x89CC, 0x6B4E, 0x9256, 0x6B50, 0x9F5E, 0x6B53, 0x8ABD, 0x6B54, 0x9F60, + 0x6B59, 0x9F5F, 0x6B5B, 0x9F61, 0x6B5F, 0x9F62, 0x6B61, 0x9F63, 0x6B62, 0x8E7E, 0x6B63, 0x90B3, 0x6B64, 0x8D9F, 0x6B66, 0x9590, + 0x6B69, 0x95E0, 0x6B6A, 0x9863, 0x6B6F, 0x8E95, 0x6B73, 0x8DCE, 0x6B74, 0x97F0, 0x6B78, 0x9F64, 0x6B79, 0x9F65, 0x6B7B, 0x8E80, + 0x6B7F, 0x9F66, 0x6B80, 0x9F67, 0x6B83, 0x9F69, 0x6B84, 0x9F68, 0x6B86, 0x9677, 0x6B89, 0x8F7D, 0x6B8A, 0x8EEA, 0x6B8B, 0x8E63, + 0x6B8D, 0x9F6A, 0x6B95, 0x9F6C, 0x6B96, 0x9042, 0x6B98, 0x9F6B, 0x6B9E, 0x9F6D, 0x6BA4, 0x9F6E, 0x6BAA, 0x9F6F, 0x6BAB, 0x9F70, + 0x6BAF, 0x9F71, 0x6BB1, 0x9F73, 0x6BB2, 0x9F72, 0x6BB3, 0x9F74, 0x6BB4, 0x89A3, 0x6BB5, 0x9269, 0x6BB7, 0x9F75, 0x6BBA, 0x8E45, + 0x6BBB, 0x8A6B, 0x6BBC, 0x9F76, 0x6BBF, 0x9361, 0x6BC0, 0x9ACA, 0x6BC5, 0x8B42, 0x6BC6, 0x9F77, 0x6BCB, 0x9F78, 0x6BCD, 0x95EA, + 0x6BCE, 0x9688, 0x6BD2, 0x93C5, 0x6BD3, 0x9F79, 0x6BD4, 0x94E4, 0x6BD6, 0xFAF4, 0x6BD8, 0x94F9, 0x6BDB, 0x96D1, 0x6BDF, 0x9F7A, + 0x6BEB, 0x9F7C, 0x6BEC, 0x9F7B, 0x6BEF, 0x9F7E, 0x6BF3, 0x9F7D, 0x6C08, 0x9F81, 0x6C0F, 0x8E81, 0x6C11, 0x96AF, 0x6C13, 0x9F82, + 0x6C14, 0x9F83, 0x6C17, 0x8B43, 0x6C1B, 0x9F84, 0x6C23, 0x9F86, 0x6C24, 0x9F85, 0x6C34, 0x9085, 0x6C37, 0x9558, 0x6C38, 0x8969, + 0x6C3E, 0x94C3, 0x6C3F, 0xFAF5, 0x6C40, 0x92F3, 0x6C41, 0x8F60, 0x6C42, 0x8B81, 0x6C4E, 0x94C4, 0x6C50, 0x8EAC, 0x6C55, 0x9F88, + 0x6C57, 0x8ABE, 0x6C5A, 0x8998, 0x6C5C, 0xFAF6, 0x6C5D, 0x93F0, 0x6C5E, 0x9F87, 0x6C5F, 0x8D5D, 0x6C60, 0x9272, 0x6C62, 0x9F89, + 0x6C68, 0x9F91, 0x6C6A, 0x9F8A, 0x6C6F, 0xFAF8, 0x6C70, 0x91BF, 0x6C72, 0x8B82, 0x6C73, 0x9F92, 0x6C7A, 0x8C88, 0x6C7D, 0x8B44, + 0x6C7E, 0x9F90, 0x6C81, 0x9F8E, 0x6C82, 0x9F8B, 0x6C83, 0x9780, 0x6C86, 0xFAF7, 0x6C88, 0x92BE, 0x6C8C, 0x93D7, 0x6C8D, 0x9F8C, + 0x6C90, 0x9F94, 0x6C92, 0x9F93, 0x6C93, 0x8C42, 0x6C96, 0x89AB, 0x6C99, 0x8DB9, 0x6C9A, 0x9F8D, 0x6C9B, 0x9F8F, 0x6CA1, 0x9676, + 0x6CA2, 0x91F2, 0x6CAB, 0x9697, 0x6CAE, 0x9F9C, 0x6CB1, 0x9F9D, 0x6CB3, 0x89CD, 0x6CB8, 0x95A6, 0x6CB9, 0x96FB, 0x6CBA, 0x9F9F, + 0x6CBB, 0x8EA1, 0x6CBC, 0x8FC0, 0x6CBD, 0x9F98, 0x6CBE, 0x9F9E, 0x6CBF, 0x8988, 0x6CC1, 0x8BB5, 0x6CC4, 0x9F95, 0x6CC5, 0x9F9A, + 0x6CC9, 0x90F2, 0x6CCA, 0x9491, 0x6CCC, 0x94E5, 0x6CD3, 0x9F97, 0x6CD5, 0x9640, 0x6CD7, 0x9F99, 0x6CD9, 0x9FA2, 0x6CDA, 0xFAF9, + 0x6CDB, 0x9FA0, 0x6CDD, 0x9F9B, 0x6CE1, 0x9641, 0x6CE2, 0x9467, 0x6CE3, 0x8B83, 0x6CE5, 0x9344, 0x6CE8, 0x928D, 0x6CEA, 0x9FA3, + 0x6CEF, 0x9FA1, 0x6CF0, 0x91D7, 0x6CF1, 0x9F96, 0x6CF3, 0x896A, 0x6D04, 0xFAFA, 0x6D0B, 0x976D, 0x6D0C, 0x9FAE, 0x6D12, 0x9FAD, + 0x6D17, 0x90F4, 0x6D19, 0x9FAA, 0x6D1B, 0x978C, 0x6D1E, 0x93B4, 0x6D1F, 0x9FA4, 0x6D25, 0x92C3, 0x6D29, 0x896B, 0x6D2A, 0x8D5E, + 0x6D2B, 0x9FA7, 0x6D32, 0x8F46, 0x6D33, 0x9FAC, 0x6D35, 0x9FAB, 0x6D36, 0x9FA6, 0x6D38, 0x9FA9, 0x6D3B, 0x8A88, 0x6D3D, 0x9FA8, + 0x6D3E, 0x9468, 0x6D41, 0x97AC, 0x6D44, 0x8FF2, 0x6D45, 0x90F3, 0x6D59, 0x9FB4, 0x6D5A, 0x9FB2, 0x6D5C, 0x956C, 0x6D63, 0x9FAF, + 0x6D64, 0x9FB1, 0x6D66, 0x8959, 0x6D69, 0x8D5F, 0x6D6A, 0x9851, 0x6D6C, 0x8A5C, 0x6D6E, 0x9582, 0x6D6F, 0xFAFC, 0x6D74, 0x9781, + 0x6D77, 0x8A43, 0x6D78, 0x905A, 0x6D79, 0x9FB3, 0x6D85, 0x9FB8, 0x6D87, 0xFAFB, 0x6D88, 0x8FC1, 0x6D8C, 0x974F, 0x6D8E, 0x9FB5, + 0x6D93, 0x9FB0, 0x6D95, 0x9FB6, 0x6D96, 0xFB40, 0x6D99, 0x97DC, 0x6D9B, 0x9393, 0x6D9C, 0x93C0, 0x6DAC, 0xFB41, 0x6DAF, 0x8A55, + 0x6DB2, 0x8974, 0x6DB5, 0x9FBC, 0x6DB8, 0x9FBF, 0x6DBC, 0x97C1, 0x6DC0, 0x9784, 0x6DC5, 0x9FC6, 0x6DC6, 0x9FC0, 0x6DC7, 0x9FBD, + 0x6DCB, 0x97D2, 0x6DCC, 0x9FC3, 0x6DCF, 0xFB42, 0x6DD1, 0x8F69, 0x6DD2, 0x9FC5, 0x6DD5, 0x9FCA, 0x6DD8, 0x9391, 0x6DD9, 0x9FC8, + 0x6DDE, 0x9FC2, 0x6DE1, 0x9257, 0x6DE4, 0x9FC9, 0x6DE6, 0x9FBE, 0x6DE8, 0x9FC4, 0x6DEA, 0x9FCB, 0x6DEB, 0x88FA, 0x6DEC, 0x9FC1, + 0x6DEE, 0x9FCC, 0x6DF1, 0x905B, 0x6DF2, 0xFB44, 0x6DF3, 0x8F7E, 0x6DF5, 0x95A3, 0x6DF7, 0x8DAC, 0x6DF8, 0xFB43, 0x6DF9, 0x9FB9, + 0x6DFA, 0x9FC7, 0x6DFB, 0x9359, 0x6DFC, 0xFB45, 0x6E05, 0x90B4, 0x6E07, 0x8A89, 0x6E08, 0x8DCF, 0x6E09, 0x8FC2, 0x6E0A, 0x9FBB, + 0x6E0B, 0x8F61, 0x6E13, 0x8C6B, 0x6E15, 0x9FBA, 0x6E19, 0x9FD0, 0x6E1A, 0x8F8D, 0x6E1B, 0x8CB8, 0x6E1D, 0x9FDF, 0x6E1F, 0x9FD9, + 0x6E20, 0x8B94, 0x6E21, 0x936E, 0x6E23, 0x9FD4, 0x6E24, 0x9FDD, 0x6E25, 0x88AD, 0x6E26, 0x8951, 0x6E27, 0xFB48, 0x6E29, 0x89B7, + 0x6E2B, 0x9FD6, 0x6E2C, 0x91AA, 0x6E2D, 0x9FCD, 0x6E2E, 0x9FCF, 0x6E2F, 0x8D60, 0x6E38, 0x9FE0, 0x6E39, 0xFB46, 0x6E3A, 0x9FDB, + 0x6E3C, 0xFB49, 0x6E3E, 0x9FD3, 0x6E43, 0x9FDA, 0x6E4A, 0x96A9, 0x6E4D, 0x9FD8, 0x6E4E, 0x9FDC, 0x6E56, 0x8CCE, 0x6E58, 0x8FC3, + 0x6E5B, 0x9258, 0x6E5C, 0xFB47, 0x6E5F, 0x9FD2, 0x6E67, 0x974E, 0x6E6B, 0x9FD5, 0x6E6E, 0x9FCE, 0x6E6F, 0x9392, 0x6E72, 0x9FD1, + 0x6E76, 0x9FD7, 0x6E7E, 0x9870, 0x6E7F, 0x8EBC, 0x6E80, 0x969E, 0x6E82, 0x9FE1, 0x6E8C, 0x94AC, 0x6E8F, 0x9FED, 0x6E90, 0x8CB9, + 0x6E96, 0x8F80, 0x6E98, 0x9FE3, 0x6E9C, 0x97AD, 0x6E9D, 0x8D61, 0x6E9F, 0x9FF0, 0x6EA2, 0x88EC, 0x6EA5, 0x9FEE, 0x6EAA, 0x9FE2, + 0x6EAF, 0x9FE8, 0x6EB2, 0x9FEA, 0x6EB6, 0x976E, 0x6EB7, 0x9FE5, 0x6EBA, 0x934D, 0x6EBD, 0x9FE7, 0x6EBF, 0xFB4A, 0x6EC2, 0x9FEF, + 0x6EC4, 0x9FE9, 0x6EC5, 0x96C5, 0x6EC9, 0x9FE4, 0x6ECB, 0x8EA0, 0x6ECC, 0x9FFC, 0x6ED1, 0x8A8A, 0x6ED3, 0x9FE6, 0x6ED4, 0x9FEB, + 0x6ED5, 0x9FEC, 0x6EDD, 0x91EA, 0x6EDE, 0x91D8, 0x6EEC, 0x9FF4, 0x6EEF, 0x9FFA, 0x6EF2, 0x9FF8, 0x6EF4, 0x9348, 0x6EF7, 0xE042, + 0x6EF8, 0x9FF5, 0x6EFE, 0x9FF6, 0x6EFF, 0x9FDE, 0x6F01, 0x8B99, 0x6F02, 0x9559, 0x6F06, 0x8EBD, 0x6F09, 0x8D97, 0x6F0F, 0x9852, + 0x6F11, 0x9FF2, 0x6F13, 0xE041, 0x6F14, 0x8989, 0x6F15, 0x9186, 0x6F20, 0x9499, 0x6F22, 0x8ABF, 0x6F23, 0x97F8, 0x6F2B, 0x969F, + 0x6F2C, 0x92D0, 0x6F31, 0x9FF9, 0x6F32, 0x9FFB, 0x6F38, 0x9151, 0x6F3E, 0xE040, 0x6F3F, 0x9FF7, 0x6F41, 0x9FF1, 0x6F45, 0x8AC1, + 0x6F54, 0x8C89, 0x6F58, 0xE04E, 0x6F5B, 0xE049, 0x6F5C, 0x90F6, 0x6F5F, 0x8A83, 0x6F64, 0x8F81, 0x6F66, 0xE052, 0x6F6D, 0xE04B, + 0x6F6E, 0x92AA, 0x6F6F, 0xE048, 0x6F70, 0x92D7, 0x6F74, 0xE06B, 0x6F78, 0xE045, 0x6F7A, 0xE044, 0x6F7C, 0xE04D, 0x6F80, 0xE047, + 0x6F81, 0xE046, 0x6F82, 0xE04C, 0x6F84, 0x909F, 0x6F86, 0xE043, 0x6F88, 0xFB4B, 0x6F8E, 0xE04F, 0x6F91, 0xE050, 0x6F97, 0x8AC0, + 0x6FA1, 0xE055, 0x6FA3, 0xE054, 0x6FA4, 0xE056, 0x6FAA, 0xE059, 0x6FB1, 0x9362, 0x6FB3, 0xE053, 0x6FB5, 0xFB4C, 0x6FB9, 0xE057, + 0x6FC0, 0x8C83, 0x6FC1, 0x91F7, 0x6FC2, 0xE051, 0x6FC3, 0x945A, 0x6FC6, 0xE058, 0x6FD4, 0xE05D, 0x6FD5, 0xE05B, 0x6FD8, 0xE05E, + 0x6FDB, 0xE061, 0x6FDF, 0xE05A, 0x6FE0, 0x8D8A, 0x6FE1, 0x9447, 0x6FE4, 0x9FB7, 0x6FEB, 0x9794, 0x6FEC, 0xE05C, 0x6FEE, 0xE060, + 0x6FEF, 0x91F3, 0x6FF1, 0xE05F, 0x6FF3, 0xE04A, 0x6FF5, 0xFB4D, 0x6FF6, 0xE889, 0x6FFA, 0xE064, 0x6FFE, 0xE068, 0x7001, 0xE066, + 0x7005, 0xFB4E, 0x7007, 0xFB4F, 0x7009, 0xE062, 0x700B, 0xE063, 0x700F, 0xE067, 0x7011, 0xE065, 0x7015, 0x956D, 0x7018, 0xE06D, + 0x701A, 0xE06A, 0x701B, 0xE069, 0x701D, 0xE06C, 0x701E, 0x93D2, 0x701F, 0xE06E, 0x7026, 0x9295, 0x7027, 0x91EB, 0x7028, 0xFB50, + 0x702C, 0x90A3, 0x7030, 0xE06F, 0x7032, 0xE071, 0x703E, 0xE070, 0x704C, 0x9FF3, 0x7051, 0xE072, 0x7058, 0x93E5, 0x7063, 0xE073, + 0x706B, 0x89CE, 0x706F, 0x9394, 0x7070, 0x8A44, 0x7078, 0x8B84, 0x707C, 0x8EDC, 0x707D, 0x8DD0, 0x7085, 0xFB51, 0x7089, 0x9846, + 0x708A, 0x9086, 0x708E, 0x898A, 0x7092, 0xE075, 0x7099, 0xE074, 0x70AB, 0xFB52, 0x70AC, 0xE078, 0x70AD, 0x9259, 0x70AE, 0xE07B, + 0x70AF, 0xE076, 0x70B3, 0xE07A, 0x70B8, 0xE079, 0x70B9, 0x935F, 0x70BA, 0x88D7, 0x70BB, 0xFA62, 0x70C8, 0x97F3, 0x70CB, 0xE07D, + 0x70CF, 0x8947, 0x70D9, 0xE080, 0x70DD, 0xE07E, 0x70DF, 0xE07C, 0x70F1, 0xE077, 0x70F9, 0x9642, 0x70FD, 0xE082, 0x7104, 0xFB54, + 0x7109, 0xE081, 0x710F, 0xFB53, 0x7114, 0x898B, 0x7119, 0xE084, 0x711A, 0x95B0, 0x711C, 0xE083, 0x7121, 0x96B3, 0x7126, 0x8FC5, + 0x7136, 0x9152, 0x713C, 0x8FC4, 0x7146, 0xFB56, 0x7147, 0xFB57, 0x7149, 0x97F9, 0x714C, 0xE08A, 0x714E, 0x90F7, 0x7155, 0xE086, + 0x7156, 0xE08B, 0x7159, 0x898C, 0x715C, 0xFB55, 0x7162, 0xE089, 0x7164, 0x9481, 0x7165, 0xE085, 0x7166, 0xE088, 0x7167, 0x8FC6, + 0x7169, 0x94CF, 0x716C, 0xE08C, 0x716E, 0x8ECF, 0x717D, 0x90F8, 0x7184, 0xE08F, 0x7188, 0xE087, 0x718A, 0x8C46, 0x718F, 0xE08D, + 0x7194, 0x976F, 0x7195, 0xE090, 0x7199, 0xEAA4, 0x719F, 0x8F6E, 0x71A8, 0xE091, 0x71AC, 0xE092, 0x71B1, 0x944D, 0x71B9, 0xE094, + 0x71BE, 0xE095, 0x71C1, 0xFB59, 0x71C3, 0x9452, 0x71C8, 0x9395, 0x71C9, 0xE097, 0x71CE, 0xE099, 0x71D0, 0x97D3, 0x71D2, 0xE096, + 0x71D4, 0xE098, 0x71D5, 0x898D, 0x71D7, 0xE093, 0x71DF, 0x9A7A, 0x71E0, 0xE09A, 0x71E5, 0x9187, 0x71E6, 0x8E57, 0x71E7, 0xE09C, + 0x71EC, 0xE09B, 0x71ED, 0x9043, 0x71EE, 0x99D7, 0x71F5, 0xE09D, 0x71F9, 0xE09F, 0x71FB, 0xE08E, 0x71FC, 0xE09E, 0x71FE, 0xFB5A, + 0x71FF, 0xE0A0, 0x7206, 0x949A, 0x720D, 0xE0A1, 0x7210, 0xE0A2, 0x721B, 0xE0A3, 0x7228, 0xE0A4, 0x722A, 0x92DC, 0x722C, 0xE0A6, + 0x722D, 0xE0A5, 0x7230, 0xE0A7, 0x7232, 0xE0A8, 0x7235, 0x8EDD, 0x7236, 0x9583, 0x723A, 0x96EA, 0x723B, 0xE0A9, 0x723C, 0xE0AA, + 0x723D, 0x9175, 0x723E, 0x8EA2, 0x723F, 0xE0AB, 0x7240, 0xE0AC, 0x7246, 0xE0AD, 0x7247, 0x95D0, 0x7248, 0x94C5, 0x724B, 0xE0AE, + 0x724C, 0x9476, 0x7252, 0x92AB, 0x7258, 0xE0AF, 0x7259, 0x89E5, 0x725B, 0x8B8D, 0x725D, 0x96C4, 0x725F, 0x96B4, 0x7261, 0x89B2, + 0x7262, 0x9853, 0x7267, 0x9671, 0x7269, 0x95A8, 0x7272, 0x90B5, 0x7274, 0xE0B0, 0x7279, 0x93C1, 0x727D, 0x8CA1, 0x727E, 0xE0B1, + 0x7280, 0x8DD2, 0x7281, 0xE0B3, 0x7282, 0xE0B2, 0x7287, 0xE0B4, 0x7292, 0xE0B5, 0x7296, 0xE0B6, 0x72A0, 0x8B5D, 0x72A2, 0xE0B7, + 0x72A7, 0xE0B8, 0x72AC, 0x8CA2, 0x72AF, 0x94C6, 0x72B1, 0xFB5B, 0x72B2, 0xE0BA, 0x72B6, 0x8FF3, 0x72B9, 0xE0B9, 0x72BE, 0xFB5C, + 0x72C2, 0x8BB6, 0x72C3, 0xE0BB, 0x72C4, 0xE0BD, 0x72C6, 0xE0BC, 0x72CE, 0xE0BE, 0x72D0, 0x8CCF, 0x72D2, 0xE0BF, 0x72D7, 0x8BE7, + 0x72D9, 0x915F, 0x72DB, 0x8D9D, 0x72E0, 0xE0C1, 0x72E1, 0xE0C2, 0x72E2, 0xE0C0, 0x72E9, 0x8EEB, 0x72EC, 0x93C6, 0x72ED, 0x8BB7, + 0x72F7, 0xE0C4, 0x72F8, 0x924B, 0x72F9, 0xE0C3, 0x72FC, 0x9854, 0x72FD, 0x9482, 0x730A, 0xE0C7, 0x7316, 0xE0C9, 0x7317, 0xE0C6, + 0x731B, 0x96D2, 0x731C, 0xE0C8, 0x731D, 0xE0CA, 0x731F, 0x97C2, 0x7324, 0xFB5D, 0x7325, 0xE0CE, 0x7329, 0xE0CD, 0x732A, 0x9296, + 0x732B, 0x944C, 0x732E, 0x8CA3, 0x732F, 0xE0CC, 0x7334, 0xE0CB, 0x7336, 0x9750, 0x7337, 0x9751, 0x733E, 0xE0CF, 0x733F, 0x898E, + 0x7344, 0x8D96, 0x7345, 0x8E82, 0x734E, 0xE0D0, 0x734F, 0xE0D1, 0x7357, 0xE0D3, 0x7363, 0x8F62, 0x7368, 0xE0D5, 0x736A, 0xE0D4, + 0x7370, 0xE0D6, 0x7372, 0x8A6C, 0x7375, 0xE0D8, 0x7377, 0xFB5F, 0x7378, 0xE0D7, 0x737A, 0xE0DA, 0x737B, 0xE0D9, 0x7384, 0x8CBA, + 0x7387, 0x97A6, 0x7389, 0x8BCA, 0x738B, 0x89A4, 0x7396, 0x8BE8, 0x73A9, 0x8ADF, 0x73B2, 0x97E6, 0x73B3, 0xE0DC, 0x73BB, 0xE0DE, + 0x73BD, 0xFB60, 0x73C0, 0xE0DF, 0x73C2, 0x89CF, 0x73C8, 0xE0DB, 0x73C9, 0xFB61, 0x73CA, 0x8E58, 0x73CD, 0x92BF, 0x73CE, 0xE0DD, + 0x73D2, 0xFB64, 0x73D6, 0xFB62, 0x73DE, 0xE0E2, 0x73E0, 0x8EEC, 0x73E3, 0xFB63, 0x73E5, 0xE0E0, 0x73EA, 0x8C5D, 0x73ED, 0x94C7, + 0x73EE, 0xE0E1, 0x73F1, 0xE0FC, 0x73F5, 0xFB66, 0x73F8, 0xE0E7, 0x73FE, 0x8CBB, 0x7403, 0x8B85, 0x7405, 0xE0E4, 0x7406, 0x979D, + 0x7407, 0xFB65, 0x7409, 0x97AE, 0x7422, 0x91F4, 0x7425, 0xE0E6, 0x7426, 0xFB67, 0x7429, 0xFB69, 0x742A, 0xFB68, 0x742E, 0xFB6A, + 0x7432, 0xE0E8, 0x7433, 0x97D4, 0x7434, 0x8BD5, 0x7435, 0x94FA, 0x7436, 0x9469, 0x743A, 0xE0E9, 0x743F, 0xE0EB, 0x7441, 0xE0EE, + 0x7455, 0xE0EA, 0x7459, 0xE0ED, 0x745A, 0x8CE8, 0x745B, 0x896C, 0x745C, 0xE0EF, 0x745E, 0x9090, 0x745F, 0xE0EC, 0x7460, 0x97DA, + 0x7462, 0xFB6B, 0x7463, 0xE0F2, 0x7464, 0xEAA2, 0x7469, 0xE0F0, 0x746A, 0xE0F3, 0x746F, 0xE0E5, 0x7470, 0xE0F1, 0x7473, 0x8DBA, + 0x7476, 0xE0F4, 0x747E, 0xE0F5, 0x7483, 0x979E, 0x7489, 0xFB6C, 0x748B, 0xE0F6, 0x749E, 0xE0F7, 0x749F, 0xFB6D, 0x74A2, 0xE0E3, + 0x74A7, 0xE0F8, 0x74B0, 0x8AC2, 0x74BD, 0x8EA3, 0x74CA, 0xE0F9, 0x74CF, 0xE0FA, 0x74D4, 0xE0FB, 0x74DC, 0x895A, 0x74E0, 0xE140, + 0x74E2, 0x955A, 0x74E3, 0xE141, 0x74E6, 0x8AA2, 0x74E7, 0xE142, 0x74E9, 0xE143, 0x74EE, 0xE144, 0x74F0, 0xE146, 0x74F1, 0xE147, + 0x74F2, 0xE145, 0x74F6, 0x9572, 0x74F7, 0xE149, 0x74F8, 0xE148, 0x7501, 0xFB6E, 0x7503, 0xE14B, 0x7504, 0xE14A, 0x7505, 0xE14C, + 0x750C, 0xE14D, 0x750D, 0xE14F, 0x750E, 0xE14E, 0x7511, 0x8D99, 0x7513, 0xE151, 0x7515, 0xE150, 0x7518, 0x8AC3, 0x751A, 0x9072, + 0x751C, 0x935B, 0x751E, 0xE152, 0x751F, 0x90B6, 0x7523, 0x8E59, 0x7525, 0x8999, 0x7526, 0xE153, 0x7528, 0x9770, 0x752B, 0x95E1, + 0x752C, 0xE154, 0x752F, 0xFAA8, 0x7530, 0x9363, 0x7531, 0x9752, 0x7532, 0x8D62, 0x7533, 0x905C, 0x7537, 0x926A, 0x7538, 0x99B2, + 0x753A, 0x92AC, 0x753B, 0x89E6, 0x753C, 0xE155, 0x7544, 0xE156, 0x7546, 0xE15B, 0x7549, 0xE159, 0x754A, 0xE158, 0x754B, 0x9DC0, + 0x754C, 0x8A45, 0x754D, 0xE157, 0x754F, 0x88D8, 0x7551, 0x94A8, 0x7554, 0x94C8, 0x7559, 0x97AF, 0x755A, 0xE15C, 0x755B, 0xE15A, + 0x755C, 0x927B, 0x755D, 0x90A4, 0x7560, 0x94A9, 0x7562, 0x954C, 0x7564, 0xE15E, 0x7565, 0x97AA, 0x7566, 0x8C6C, 0x7567, 0xE15F, + 0x7569, 0xE15D, 0x756A, 0x94D4, 0x756B, 0xE160, 0x756D, 0xE161, 0x756F, 0xFB6F, 0x7570, 0x88D9, 0x7573, 0x8FF4, 0x7574, 0xE166, + 0x7576, 0xE163, 0x7577, 0x93EB, 0x7578, 0xE162, 0x757F, 0x8B45, 0x7582, 0xE169, 0x7586, 0xE164, 0x7587, 0xE165, 0x7589, 0xE168, + 0x758A, 0xE167, 0x758B, 0x9544, 0x758E, 0x9161, 0x758F, 0x9160, 0x7591, 0x8B5E, 0x7594, 0xE16A, 0x759A, 0xE16B, 0x759D, 0xE16C, + 0x75A3, 0xE16E, 0x75A5, 0xE16D, 0x75AB, 0x8975, 0x75B1, 0xE176, 0x75B2, 0x94E6, 0x75B3, 0xE170, 0x75B5, 0xE172, 0x75B8, 0xE174, + 0x75B9, 0x905D, 0x75BC, 0xE175, 0x75BD, 0xE173, 0x75BE, 0x8EBE, 0x75C2, 0xE16F, 0x75C3, 0xE171, 0x75C5, 0x9561, 0x75C7, 0x8FC7, + 0x75CA, 0xE178, 0x75CD, 0xE177, 0x75D2, 0xE179, 0x75D4, 0x8EA4, 0x75D5, 0x8DAD, 0x75D8, 0x9397, 0x75D9, 0xE17A, 0x75DB, 0x92C9, + 0x75DE, 0xE17C, 0x75E2, 0x979F, 0x75E3, 0xE17B, 0x75E9, 0x9189, 0x75F0, 0xE182, 0x75F2, 0xE184, 0x75F3, 0xE185, 0x75F4, 0x9273, + 0x75FA, 0xE183, 0x75FC, 0xE180, 0x75FE, 0xE17D, 0x75FF, 0xE17E, 0x7601, 0xE181, 0x7609, 0xE188, 0x760B, 0xE186, 0x760D, 0xE187, + 0x761F, 0xE189, 0x7620, 0xE18B, 0x7621, 0xE18C, 0x7622, 0xE18D, 0x7624, 0xE18E, 0x7627, 0xE18A, 0x7630, 0xE190, 0x7634, 0xE18F, + 0x763B, 0xE191, 0x7642, 0x97C3, 0x7646, 0xE194, 0x7647, 0xE192, 0x7648, 0xE193, 0x764C, 0x8AE0, 0x7652, 0x96FC, 0x7656, 0x95C8, + 0x7658, 0xE196, 0x765C, 0xE195, 0x7661, 0xE197, 0x7662, 0xE198, 0x7667, 0xE19C, 0x7668, 0xE199, 0x7669, 0xE19A, 0x766A, 0xE19B, + 0x766C, 0xE19D, 0x7670, 0xE19E, 0x7672, 0xE19F, 0x7676, 0xE1A0, 0x7678, 0xE1A1, 0x767A, 0x94AD, 0x767B, 0x936F, 0x767C, 0xE1A2, + 0x767D, 0x9492, 0x767E, 0x9553, 0x7680, 0xE1A3, 0x7682, 0xFB70, 0x7683, 0xE1A4, 0x7684, 0x9349, 0x7686, 0x8A46, 0x7687, 0x8D63, + 0x7688, 0xE1A5, 0x768B, 0xE1A6, 0x768E, 0xE1A7, 0x7690, 0x8E48, 0x7693, 0xE1A9, 0x7696, 0xE1A8, 0x7699, 0xE1AA, 0x769A, 0xE1AB, + 0x769B, 0xFB73, 0x769C, 0xFB71, 0x769E, 0xFB72, 0x76A6, 0xFB74, 0x76AE, 0x94E7, 0x76B0, 0xE1AC, 0x76B4, 0xE1AD, 0x76B7, 0xEA89, + 0x76B8, 0xE1AE, 0x76B9, 0xE1AF, 0x76BA, 0xE1B0, 0x76BF, 0x8E4D, 0x76C2, 0xE1B1, 0x76C3, 0x9475, 0x76C6, 0x967E, 0x76C8, 0x896D, + 0x76CA, 0x8976, 0x76CD, 0xE1B2, 0x76D2, 0xE1B4, 0x76D6, 0xE1B3, 0x76D7, 0x9390, 0x76DB, 0x90B7, 0x76DC, 0x9F58, 0x76DE, 0xE1B5, + 0x76DF, 0x96BF, 0x76E1, 0xE1B6, 0x76E3, 0x8AC4, 0x76E4, 0x94D5, 0x76E5, 0xE1B7, 0x76E7, 0xE1B8, 0x76EA, 0xE1B9, 0x76EE, 0x96DA, + 0x76F2, 0x96D3, 0x76F4, 0x92BC, 0x76F8, 0x918A, 0x76FB, 0xE1BB, 0x76FE, 0x8F82, 0x7701, 0x8FC8, 0x7704, 0xE1BE, 0x7707, 0xE1BD, + 0x7708, 0xE1BC, 0x7709, 0x94FB, 0x770B, 0x8AC5, 0x770C, 0x8CA7, 0x771B, 0xE1C4, 0x771E, 0xE1C1, 0x771F, 0x905E, 0x7720, 0x96B0, + 0x7724, 0xE1C0, 0x7725, 0xE1C2, 0x7726, 0xE1C3, 0x7729, 0xE1BF, 0x7737, 0xE1C5, 0x7738, 0xE1C6, 0x773A, 0x92AD, 0x773C, 0x8AE1, + 0x7740, 0x9285, 0x7746, 0xFB76, 0x7747, 0xE1C7, 0x775A, 0xE1C8, 0x775B, 0xE1CB, 0x7761, 0x9087, 0x7763, 0x93C2, 0x7765, 0xE1CC, + 0x7766, 0x9672, 0x7768, 0xE1C9, 0x776B, 0xE1CA, 0x7779, 0xE1CF, 0x777E, 0xE1CE, 0x777F, 0xE1CD, 0x778B, 0xE1D1, 0x778E, 0xE1D0, + 0x7791, 0xE1D2, 0x779E, 0xE1D4, 0x77A0, 0xE1D3, 0x77A5, 0x95CB, 0x77AC, 0x8F75, 0x77AD, 0x97C4, 0x77B0, 0xE1D5, 0x77B3, 0x93B5, + 0x77B6, 0xE1D6, 0x77B9, 0xE1D7, 0x77BB, 0xE1DB, 0x77BC, 0xE1D9, 0x77BD, 0xE1DA, 0x77BF, 0xE1D8, 0x77C7, 0xE1DC, 0x77CD, 0xE1DD, + 0x77D7, 0xE1DE, 0x77DA, 0xE1DF, 0x77DB, 0x96B5, 0x77DC, 0xE1E0, 0x77E2, 0x96EE, 0x77E3, 0xE1E1, 0x77E5, 0x926D, 0x77E7, 0x948A, + 0x77E9, 0x8BE9, 0x77ED, 0x925A, 0x77EE, 0xE1E2, 0x77EF, 0x8BB8, 0x77F3, 0x90CE, 0x77FC, 0xE1E3, 0x7802, 0x8DBB, 0x780C, 0xE1E4, + 0x7812, 0xE1E5, 0x7814, 0x8CA4, 0x7815, 0x8DD3, 0x7820, 0xE1E7, 0x7821, 0xFB78, 0x7825, 0x9375, 0x7826, 0x8DD4, 0x7827, 0x8B6D, + 0x7832, 0x9643, 0x7834, 0x946A, 0x783A, 0x9376, 0x783F, 0x8D7B, 0x7845, 0xE1E9, 0x784E, 0xFB79, 0x785D, 0x8FC9, 0x7864, 0xFB7A, + 0x786B, 0x97B0, 0x786C, 0x8D64, 0x786F, 0x8CA5, 0x7872, 0x94A1, 0x7874, 0xE1EB, 0x787A, 0xFB7B, 0x787C, 0xE1ED, 0x7881, 0x8CE9, + 0x7886, 0xE1EC, 0x7887, 0x92F4, 0x788C, 0xE1EF, 0x788D, 0x8A56, 0x788E, 0xE1EA, 0x7891, 0x94E8, 0x7893, 0x894F, 0x7895, 0x8DEA, + 0x7897, 0x9871, 0x789A, 0xE1EE, 0x78A3, 0xE1F0, 0x78A7, 0x95C9, 0x78A9, 0x90D7, 0x78AA, 0xE1F2, 0x78AF, 0xE1F3, 0x78B5, 0xE1F1, + 0x78BA, 0x8A6D, 0x78BC, 0xE1F9, 0x78BE, 0xE1F8, 0x78C1, 0x8EA5, 0x78C5, 0xE1FA, 0x78C6, 0xE1F5, 0x78CA, 0xE1FB, 0x78CB, 0xE1F6, + 0x78D0, 0x94D6, 0x78D1, 0xE1F4, 0x78D4, 0xE1F7, 0x78DA, 0xE241, 0x78E7, 0xE240, 0x78E8, 0x9681, 0x78EC, 0xE1FC, 0x78EF, 0x88E9, + 0x78F4, 0xE243, 0x78FD, 0xE242, 0x7901, 0x8FCA, 0x7907, 0xE244, 0x790E, 0x9162, 0x7911, 0xE246, 0x7912, 0xE245, 0x7919, 0xE247, + 0x7926, 0xE1E6, 0x792A, 0xE1E8, 0x792B, 0xE249, 0x792C, 0xE248, 0x7930, 0xFB7C, 0x793A, 0x8EA6, 0x793C, 0x97E7, 0x793E, 0x8ED0, + 0x7940, 0xE24A, 0x7941, 0x8C56, 0x7947, 0x8B5F, 0x7948, 0x8B46, 0x7949, 0x8E83, 0x7950, 0x9753, 0x7953, 0xE250, 0x7955, 0xE24F, + 0x7956, 0x9163, 0x7957, 0xE24C, 0x795A, 0xE24E, 0x795D, 0x8F6A, 0x795E, 0x905F, 0x795F, 0xE24D, 0x7960, 0xE24B, 0x7962, 0x9449, + 0x7965, 0x8FCB, 0x7968, 0x955B, 0x796D, 0x8DD5, 0x7977, 0x9398, 0x797A, 0xE251, 0x797F, 0xE252, 0x7980, 0xE268, 0x7981, 0x8BD6, + 0x7984, 0x985C, 0x7985, 0x9154, 0x798A, 0xE253, 0x798D, 0x89D0, 0x798E, 0x92F5, 0x798F, 0x959F, 0x7994, 0xFB81, 0x799B, 0xFB83, + 0x799D, 0xE254, 0x79A6, 0x8B9A, 0x79A7, 0xE255, 0x79AA, 0xE257, 0x79AE, 0xE258, 0x79B0, 0x9448, 0x79B3, 0xE259, 0x79B9, 0xE25A, + 0x79BA, 0xE25B, 0x79BD, 0x8BD7, 0x79BE, 0x89D1, 0x79BF, 0x93C3, 0x79C0, 0x8F47, 0x79C1, 0x8E84, 0x79C9, 0xE25C, 0x79CB, 0x8F48, + 0x79D1, 0x89C8, 0x79D2, 0x9562, 0x79D5, 0xE25D, 0x79D8, 0x94E9, 0x79DF, 0x9164, 0x79E1, 0xE260, 0x79E3, 0xE261, 0x79E4, 0x9489, + 0x79E6, 0x9060, 0x79E7, 0xE25E, 0x79E9, 0x9281, 0x79EC, 0xE25F, 0x79F0, 0x8FCC, 0x79FB, 0x88DA, 0x7A00, 0x8B48, 0x7A08, 0xE262, + 0x7A0B, 0x92F6, 0x7A0D, 0xE263, 0x7A0E, 0x90C5, 0x7A14, 0x96AB, 0x7A17, 0x9542, 0x7A18, 0xE264, 0x7A19, 0xE265, 0x7A1A, 0x9274, + 0x7A1C, 0x97C5, 0x7A1F, 0xE267, 0x7A20, 0xE266, 0x7A2E, 0x8EED, 0x7A31, 0xE269, 0x7A32, 0x88EE, 0x7A37, 0xE26C, 0x7A3B, 0xE26A, + 0x7A3C, 0x89D2, 0x7A3D, 0x8C6D, 0x7A3E, 0xE26B, 0x7A3F, 0x8D65, 0x7A40, 0x8D92, 0x7A42, 0x95E4, 0x7A43, 0xE26D, 0x7A46, 0x9673, + 0x7A49, 0xE26F, 0x7A4D, 0x90CF, 0x7A4E, 0x896E, 0x7A4F, 0x89B8, 0x7A50, 0x88AA, 0x7A57, 0xE26E, 0x7A61, 0xE270, 0x7A62, 0xE271, + 0x7A63, 0x8FF5, 0x7A69, 0xE272, 0x7A6B, 0x8A6E, 0x7A70, 0xE274, 0x7A74, 0x8C8A, 0x7A76, 0x8B86, 0x7A79, 0xE275, 0x7A7A, 0x8BF3, + 0x7A7D, 0xE276, 0x7A7F, 0x90FA, 0x7A81, 0x93CB, 0x7A83, 0x90DE, 0x7A84, 0x8DF3, 0x7A88, 0xE277, 0x7A92, 0x9282, 0x7A93, 0x918B, + 0x7A95, 0xE279, 0x7A96, 0xE27B, 0x7A97, 0xE278, 0x7A98, 0xE27A, 0x7A9F, 0x8C41, 0x7AA9, 0xE27C, 0x7AAA, 0x8C45, 0x7AAE, 0x8B87, + 0x7AAF, 0x9771, 0x7AB0, 0xE27E, 0x7AB6, 0xE280, 0x7ABA, 0x894D, 0x7ABF, 0xE283, 0x7AC3, 0x8A96, 0x7AC4, 0xE282, 0x7AC5, 0xE281, + 0x7AC7, 0xE285, 0x7AC8, 0xE27D, 0x7ACA, 0xE286, 0x7ACB, 0x97A7, 0x7ACD, 0xE287, 0x7ACF, 0xE288, 0x7AD1, 0xFB84, 0x7AD2, 0x9AF2, + 0x7AD3, 0xE28A, 0x7AD5, 0xE289, 0x7AD9, 0xE28B, 0x7ADA, 0xE28C, 0x7ADC, 0x97B3, 0x7ADD, 0xE28D, 0x7ADF, 0xE8ED, 0x7AE0, 0x8FCD, + 0x7AE1, 0xE28E, 0x7AE2, 0xE28F, 0x7AE3, 0x8F76, 0x7AE5, 0x93B6, 0x7AE6, 0xE290, 0x7AE7, 0xFB85, 0x7AEA, 0x9247, 0x7AEB, 0xFB87, + 0x7AED, 0xE291, 0x7AEF, 0x925B, 0x7AF0, 0xE292, 0x7AF6, 0x8BA3, 0x7AF8, 0x995E, 0x7AF9, 0x927C, 0x7AFA, 0x8EB1, 0x7AFF, 0x8AC6, + 0x7B02, 0xE293, 0x7B04, 0xE2A0, 0x7B06, 0xE296, 0x7B08, 0x8B88, 0x7B0A, 0xE295, 0x7B0B, 0xE2A2, 0x7B0F, 0xE294, 0x7B11, 0x8FCE, + 0x7B18, 0xE298, 0x7B19, 0xE299, 0x7B1B, 0x934A, 0x7B1E, 0xE29A, 0x7B20, 0x8A7D, 0x7B25, 0x9079, 0x7B26, 0x9584, 0x7B28, 0xE29C, + 0x7B2C, 0x91E6, 0x7B33, 0xE297, 0x7B35, 0xE29B, 0x7B36, 0xE29D, 0x7B39, 0x8DF9, 0x7B45, 0xE2A4, 0x7B46, 0x954D, 0x7B48, 0x94A4, + 0x7B49, 0x9399, 0x7B4B, 0x8BD8, 0x7B4C, 0xE2A3, 0x7B4D, 0xE2A1, 0x7B4F, 0x94B3, 0x7B50, 0xE29E, 0x7B51, 0x927D, 0x7B52, 0x939B, + 0x7B54, 0x939A, 0x7B56, 0x8DF4, 0x7B5D, 0xE2B6, 0x7B65, 0xE2A6, 0x7B67, 0xE2A8, 0x7B6C, 0xE2AB, 0x7B6E, 0xE2AC, 0x7B70, 0xE2A9, + 0x7B71, 0xE2AA, 0x7B74, 0xE2A7, 0x7B75, 0xE2A5, 0x7B7A, 0xE29F, 0x7B86, 0x95CD, 0x7B87, 0x89D3, 0x7B8B, 0xE2B3, 0x7B8D, 0xE2B0, + 0x7B8F, 0xE2B5, 0x7B92, 0xE2B4, 0x7B94, 0x9493, 0x7B95, 0x96A5, 0x7B97, 0x8E5A, 0x7B98, 0xE2AE, 0x7B99, 0xE2B7, 0x7B9A, 0xE2B2, + 0x7B9C, 0xE2B1, 0x7B9D, 0xE2AD, 0x7B9E, 0xFB88, 0x7B9F, 0xE2AF, 0x7BA1, 0x8AC7, 0x7BAA, 0x925C, 0x7BAD, 0x90FB, 0x7BB1, 0x94A0, + 0x7BB4, 0xE2BC, 0x7BB8, 0x94A2, 0x7BC0, 0x90DF, 0x7BC1, 0xE2B9, 0x7BC4, 0x94CD, 0x7BC6, 0xE2BD, 0x7BC7, 0x95D1, 0x7BC9, 0x927A, + 0x7BCB, 0xE2B8, 0x7BCC, 0xE2BA, 0x7BCF, 0xE2BB, 0x7BDD, 0xE2BE, 0x7BE0, 0x8EC2, 0x7BE4, 0x93C4, 0x7BE5, 0xE2C3, 0x7BE6, 0xE2C2, + 0x7BE9, 0xE2BF, 0x7BED, 0x9855, 0x7BF3, 0xE2C8, 0x7BF6, 0xE2CC, 0x7BF7, 0xE2C9, 0x7C00, 0xE2C5, 0x7C07, 0xE2C6, 0x7C0D, 0xE2CB, + 0x7C11, 0xE2C0, 0x7C12, 0x99D3, 0x7C13, 0xE2C7, 0x7C14, 0xE2C1, 0x7C17, 0xE2CA, 0x7C1F, 0xE2D0, 0x7C21, 0x8AC8, 0x7C23, 0xE2CD, + 0x7C27, 0xE2CE, 0x7C2A, 0xE2CF, 0x7C2B, 0xE2D2, 0x7C37, 0xE2D1, 0x7C38, 0x94F4, 0x7C3D, 0xE2D3, 0x7C3E, 0x97FA, 0x7C3F, 0x95EB, + 0x7C40, 0xE2D8, 0x7C43, 0xE2D5, 0x7C4C, 0xE2D4, 0x7C4D, 0x90D0, 0x7C4F, 0xE2D7, 0x7C50, 0xE2D9, 0x7C54, 0xE2D6, 0x7C56, 0xE2DD, + 0x7C58, 0xE2DA, 0x7C5F, 0xE2DB, 0x7C60, 0xE2C4, 0x7C64, 0xE2DC, 0x7C65, 0xE2DE, 0x7C6C, 0xE2DF, 0x7C73, 0x95C4, 0x7C75, 0xE2E0, + 0x7C7E, 0x96E0, 0x7C81, 0x8BCC, 0x7C82, 0x8C48, 0x7C83, 0xE2E1, 0x7C89, 0x95B2, 0x7C8B, 0x9088, 0x7C8D, 0x96AE, 0x7C90, 0xE2E2, + 0x7C92, 0x97B1, 0x7C95, 0x9494, 0x7C97, 0x9165, 0x7C98, 0x9453, 0x7C9B, 0x8F6C, 0x7C9F, 0x88BE, 0x7CA1, 0xE2E7, 0x7CA2, 0xE2E5, + 0x7CA4, 0xE2E3, 0x7CA5, 0x8A9F, 0x7CA7, 0x8FCF, 0x7CA8, 0xE2E8, 0x7CAB, 0xE2E6, 0x7CAD, 0xE2E4, 0x7CAE, 0xE2EC, 0x7CB1, 0xE2EB, + 0x7CB2, 0xE2EA, 0x7CB3, 0xE2E9, 0x7CB9, 0xE2ED, 0x7CBD, 0xE2EE, 0x7CBE, 0x90B8, 0x7CC0, 0xE2EF, 0x7CC2, 0xE2F1, 0x7CC5, 0xE2F0, + 0x7CCA, 0x8CD0, 0x7CCE, 0x9157, 0x7CD2, 0xE2F3, 0x7CD6, 0x939C, 0x7CD8, 0xE2F2, 0x7CDC, 0xE2F4, 0x7CDE, 0x95B3, 0x7CDF, 0x918C, + 0x7CE0, 0x8D66, 0x7CE2, 0xE2F5, 0x7CE7, 0x97C6, 0x7CEF, 0xE2F7, 0x7CF2, 0xE2F8, 0x7CF4, 0xE2F9, 0x7CF6, 0xE2FA, 0x7CF8, 0x8E85, + 0x7CFA, 0xE2FB, 0x7CFB, 0x8C6E, 0x7CFE, 0x8B8A, 0x7D00, 0x8B49, 0x7D02, 0xE340, 0x7D04, 0x96F1, 0x7D05, 0x8D67, 0x7D06, 0xE2FC, + 0x7D0A, 0xE343, 0x7D0B, 0x96E4, 0x7D0D, 0x945B, 0x7D10, 0x9552, 0x7D14, 0x8F83, 0x7D15, 0xE342, 0x7D17, 0x8ED1, 0x7D18, 0x8D68, + 0x7D19, 0x8E86, 0x7D1A, 0x8B89, 0x7D1B, 0x95B4, 0x7D1C, 0xE341, 0x7D20, 0x9166, 0x7D21, 0x9661, 0x7D22, 0x8DF5, 0x7D2B, 0x8E87, + 0x7D2C, 0x92DB, 0x7D2E, 0xE346, 0x7D2F, 0x97DD, 0x7D30, 0x8DD7, 0x7D32, 0xE347, 0x7D33, 0x9061, 0x7D35, 0xE349, 0x7D39, 0x8FD0, + 0x7D3A, 0x8DAE, 0x7D3F, 0xE348, 0x7D42, 0x8F49, 0x7D43, 0x8CBC, 0x7D44, 0x9167, 0x7D45, 0xE344, 0x7D46, 0xE34A, 0x7D48, 0xFB8A, + 0x7D4B, 0xE345, 0x7D4C, 0x8C6F, 0x7D4E, 0xE34D, 0x7D4F, 0xE351, 0x7D50, 0x8C8B, 0x7D56, 0xE34C, 0x7D5B, 0xE355, 0x7D5C, 0xFB8B, + 0x7D5E, 0x8D69, 0x7D61, 0x978D, 0x7D62, 0x88BA, 0x7D63, 0xE352, 0x7D66, 0x8B8B, 0x7D68, 0xE34F, 0x7D6E, 0xE350, 0x7D71, 0x939D, + 0x7D72, 0xE34E, 0x7D73, 0xE34B, 0x7D75, 0x8A47, 0x7D76, 0x90E2, 0x7D79, 0x8CA6, 0x7D7D, 0xE357, 0x7D89, 0xE354, 0x7D8F, 0xE356, + 0x7D93, 0xE353, 0x7D99, 0x8C70, 0x7D9A, 0x91B1, 0x7D9B, 0xE358, 0x7D9C, 0x918E, 0x7D9F, 0xE365, 0x7DA0, 0xFB8D, 0x7DA2, 0xE361, + 0x7DA3, 0xE35B, 0x7DAB, 0xE35F, 0x7DAC, 0x8EF8, 0x7DAD, 0x88DB, 0x7DAE, 0xE35A, 0x7DAF, 0xE362, 0x7DB0, 0xE366, 0x7DB1, 0x8D6A, + 0x7DB2, 0x96D4, 0x7DB4, 0x92D4, 0x7DB5, 0xE35C, 0x7DB7, 0xFB8C, 0x7DB8, 0xE364, 0x7DBA, 0xE359, 0x7DBB, 0x925D, 0x7DBD, 0xE35E, + 0x7DBE, 0x88BB, 0x7DBF, 0x96C8, 0x7DC7, 0xE35D, 0x7DCA, 0x8BD9, 0x7DCB, 0x94EA, 0x7DCF, 0x918D, 0x7DD1, 0x97CE, 0x7DD2, 0x8F8F, + 0x7DD5, 0xE38E, 0x7DD6, 0xFB8E, 0x7DD8, 0xE367, 0x7DDA, 0x90FC, 0x7DDC, 0xE363, 0x7DDD, 0xE368, 0x7DDE, 0xE36A, 0x7DE0, 0x92F7, + 0x7DE1, 0xE36D, 0x7DE4, 0xE369, 0x7DE8, 0x95D2, 0x7DE9, 0x8AC9, 0x7DEC, 0x96C9, 0x7DEF, 0x88DC, 0x7DF2, 0xE36C, 0x7DF4, 0x97FB, + 0x7DFB, 0xE36B, 0x7E01, 0x898F, 0x7E04, 0x93EA, 0x7E05, 0xE36E, 0x7E09, 0xE375, 0x7E0A, 0xE36F, 0x7E0B, 0xE376, 0x7E12, 0xE372, + 0x7E1B, 0x949B, 0x7E1E, 0x8EC8, 0x7E1F, 0xE374, 0x7E21, 0xE371, 0x7E22, 0xE377, 0x7E23, 0xE370, 0x7E26, 0x8F63, 0x7E2B, 0x9644, + 0x7E2E, 0x8F6B, 0x7E31, 0xE373, 0x7E32, 0xE380, 0x7E35, 0xE37B, 0x7E37, 0xE37E, 0x7E39, 0xE37C, 0x7E3A, 0xE381, 0x7E3B, 0xE37A, + 0x7E3D, 0xE360, 0x7E3E, 0x90D1, 0x7E41, 0x94C9, 0x7E43, 0xE37D, 0x7E46, 0xE378, 0x7E4A, 0x9140, 0x7E4B, 0x8C71, 0x7E4D, 0x8F4A, + 0x7E52, 0xFB8F, 0x7E54, 0x9044, 0x7E55, 0x9155, 0x7E56, 0xE384, 0x7E59, 0xE386, 0x7E5A, 0xE387, 0x7E5D, 0xE383, 0x7E5E, 0xE385, + 0x7E66, 0xE379, 0x7E67, 0xE382, 0x7E69, 0xE38A, 0x7E6A, 0xE389, 0x7E6D, 0x969A, 0x7E70, 0x8C4A, 0x7E79, 0xE388, 0x7E7B, 0xE38C, + 0x7E7C, 0xE38B, 0x7E7D, 0xE38F, 0x7E7F, 0xE391, 0x7E82, 0x8E5B, 0x7E83, 0xE38D, 0x7E88, 0xE392, 0x7E89, 0xE393, 0x7E8A, 0xFA5C, + 0x7E8C, 0xE394, 0x7E8E, 0xE39A, 0x7E8F, 0x935A, 0x7E90, 0xE396, 0x7E92, 0xE395, 0x7E93, 0xE397, 0x7E94, 0xE398, 0x7E96, 0xE399, + 0x7E9B, 0xE39B, 0x7E9C, 0xE39C, 0x7F36, 0x8ACA, 0x7F38, 0xE39D, 0x7F3A, 0xE39E, 0x7F45, 0xE39F, 0x7F47, 0xFB90, 0x7F4C, 0xE3A0, + 0x7F4D, 0xE3A1, 0x7F4E, 0xE3A2, 0x7F50, 0xE3A3, 0x7F51, 0xE3A4, 0x7F54, 0xE3A6, 0x7F55, 0xE3A5, 0x7F58, 0xE3A7, 0x7F5F, 0xE3A8, + 0x7F60, 0xE3A9, 0x7F67, 0xE3AC, 0x7F68, 0xE3AA, 0x7F69, 0xE3AB, 0x7F6A, 0x8DDF, 0x7F6B, 0x8C72, 0x7F6E, 0x9275, 0x7F70, 0x94B1, + 0x7F72, 0x8F90, 0x7F75, 0x946C, 0x7F77, 0x94EB, 0x7F78, 0xE3AD, 0x7F79, 0x9CEB, 0x7F82, 0xE3AE, 0x7F83, 0xE3B0, 0x7F85, 0x9785, + 0x7F86, 0xE3AF, 0x7F87, 0xE3B2, 0x7F88, 0xE3B1, 0x7F8A, 0x9772, 0x7F8C, 0xE3B3, 0x7F8E, 0x94FC, 0x7F94, 0xE3B4, 0x7F9A, 0xE3B7, + 0x7F9D, 0xE3B6, 0x7F9E, 0xE3B5, 0x7FA1, 0xFB91, 0x7FA3, 0xE3B8, 0x7FA4, 0x8C51, 0x7FA8, 0x9141, 0x7FA9, 0x8B60, 0x7FAE, 0xE3BC, + 0x7FAF, 0xE3B9, 0x7FB2, 0xE3BA, 0x7FB6, 0xE3BD, 0x7FB8, 0xE3BE, 0x7FB9, 0xE3BB, 0x7FBD, 0x8948, 0x7FC1, 0x89A5, 0x7FC5, 0xE3C0, + 0x7FC6, 0xE3C1, 0x7FCA, 0xE3C2, 0x7FCC, 0x9782, 0x7FD2, 0x8F4B, 0x7FD4, 0xE3C4, 0x7FD5, 0xE3C3, 0x7FE0, 0x9089, 0x7FE1, 0xE3C5, + 0x7FE6, 0xE3C6, 0x7FE9, 0xE3C7, 0x7FEB, 0x8AE3, 0x7FF0, 0x8ACB, 0x7FF3, 0xE3C8, 0x7FF9, 0xE3C9, 0x7FFB, 0x967C, 0x7FFC, 0x9783, + 0x8000, 0x9773, 0x8001, 0x9856, 0x8003, 0x8D6C, 0x8004, 0xE3CC, 0x8005, 0x8ED2, 0x8006, 0xE3CB, 0x800B, 0xE3CD, 0x800C, 0x8EA7, + 0x8010, 0x91CF, 0x8012, 0xE3CE, 0x8015, 0x8D6B, 0x8017, 0x96D5, 0x8018, 0xE3CF, 0x8019, 0xE3D0, 0x801C, 0xE3D1, 0x8021, 0xE3D2, + 0x8028, 0xE3D3, 0x8033, 0x8EA8, 0x8036, 0x96EB, 0x803B, 0xE3D5, 0x803D, 0x925E, 0x803F, 0xE3D4, 0x8046, 0xE3D7, 0x804A, 0xE3D6, + 0x8052, 0xE3D8, 0x8056, 0x90B9, 0x8058, 0xE3D9, 0x805A, 0xE3DA, 0x805E, 0x95B7, 0x805F, 0xE3DB, 0x8061, 0x918F, 0x8062, 0xE3DC, + 0x8068, 0xE3DD, 0x806F, 0x97FC, 0x8070, 0xE3E0, 0x8072, 0xE3DF, 0x8073, 0xE3DE, 0x8074, 0x92AE, 0x8076, 0xE3E1, 0x8077, 0x9045, + 0x8079, 0xE3E2, 0x807D, 0xE3E3, 0x807E, 0x9857, 0x807F, 0xE3E4, 0x8084, 0xE3E5, 0x8085, 0xE3E7, 0x8086, 0xE3E6, 0x8087, 0x94A3, + 0x8089, 0x93F7, 0x808B, 0x985D, 0x808C, 0x94A7, 0x8093, 0xE3E9, 0x8096, 0x8FD1, 0x8098, 0x9549, 0x809A, 0xE3EA, 0x809B, 0xE3E8, + 0x809D, 0x8ACC, 0x80A1, 0x8CD2, 0x80A2, 0x8E88, 0x80A5, 0x94EC, 0x80A9, 0x8CA8, 0x80AA, 0x9662, 0x80AC, 0xE3ED, 0x80AD, 0xE3EB, + 0x80AF, 0x8D6D, 0x80B1, 0x8D6E, 0x80B2, 0x88E7, 0x80B4, 0x8DE6, 0x80BA, 0x9478, 0x80C3, 0x88DD, 0x80C4, 0xE3F2, 0x80C6, 0x925F, + 0x80CC, 0x9477, 0x80CE, 0x91D9, 0x80D6, 0xE3F4, 0x80D9, 0xE3F0, 0x80DA, 0xE3F3, 0x80DB, 0xE3EE, 0x80DD, 0xE3F1, 0x80DE, 0x9645, + 0x80E1, 0x8CD3, 0x80E4, 0x88FB, 0x80E5, 0xE3EF, 0x80EF, 0xE3F6, 0x80F1, 0xE3F7, 0x80F4, 0x93B7, 0x80F8, 0x8BB9, 0x80FC, 0xE445, + 0x80FD, 0x945C, 0x8102, 0x8E89, 0x8105, 0x8BBA, 0x8106, 0x90C6, 0x8107, 0x9865, 0x8108, 0x96AC, 0x8109, 0xE3F5, 0x810A, 0x90D2, + 0x811A, 0x8B72, 0x811B, 0xE3F8, 0x8123, 0xE3FA, 0x8129, 0xE3F9, 0x812F, 0xE3FB, 0x8131, 0x9245, 0x8133, 0x945D, 0x8139, 0x92AF, + 0x813E, 0xE442, 0x8146, 0xE441, 0x814B, 0xE3FC, 0x814E, 0x9074, 0x8150, 0x9585, 0x8151, 0xE444, 0x8153, 0xE443, 0x8154, 0x8D6F, + 0x8155, 0x9872, 0x815F, 0xE454, 0x8165, 0xE448, 0x8166, 0xE449, 0x816B, 0x8EEE, 0x816E, 0xE447, 0x8170, 0x8D98, 0x8171, 0xE446, + 0x8174, 0xE44A, 0x8178, 0x92B0, 0x8179, 0x95A0, 0x817A, 0x9142, 0x817F, 0x91DA, 0x8180, 0xE44E, 0x8182, 0xE44F, 0x8183, 0xE44B, + 0x8188, 0xE44C, 0x818A, 0xE44D, 0x818F, 0x8D70, 0x8193, 0xE455, 0x8195, 0xE451, 0x819A, 0x9586, 0x819C, 0x968C, 0x819D, 0x9547, + 0x81A0, 0xE450, 0x81A3, 0xE453, 0x81A4, 0xE452, 0x81A8, 0x9663, 0x81A9, 0xE456, 0x81B0, 0xE457, 0x81B3, 0x9156, 0x81B5, 0xE458, + 0x81B8, 0xE45A, 0x81BA, 0xE45E, 0x81BD, 0xE45B, 0x81BE, 0xE459, 0x81BF, 0x945E, 0x81C0, 0xE45C, 0x81C2, 0xE45D, 0x81C6, 0x89B0, + 0x81C8, 0xE464, 0x81C9, 0xE45F, 0x81CD, 0xE460, 0x81D1, 0xE461, 0x81D3, 0x919F, 0x81D8, 0xE463, 0x81D9, 0xE462, 0x81DA, 0xE465, + 0x81DF, 0xE466, 0x81E0, 0xE467, 0x81E3, 0x9062, 0x81E5, 0x89E7, 0x81E7, 0xE468, 0x81E8, 0x97D5, 0x81EA, 0x8EA9, 0x81ED, 0x8F4C, + 0x81F3, 0x8E8A, 0x81F4, 0x9276, 0x81FA, 0xE469, 0x81FB, 0xE46A, 0x81FC, 0x8950, 0x81FE, 0xE46B, 0x8201, 0xE46C, 0x8202, 0xE46D, + 0x8205, 0xE46E, 0x8207, 0xE46F, 0x8208, 0x8BBB, 0x8209, 0x9DA8, 0x820A, 0xE470, 0x820C, 0x90E3, 0x820D, 0xE471, 0x820E, 0x8EC9, + 0x8210, 0xE472, 0x8212, 0x98AE, 0x8216, 0xE473, 0x8217, 0x95DC, 0x8218, 0x8ADA, 0x821B, 0x9143, 0x821C, 0x8F77, 0x821E, 0x9591, + 0x821F, 0x8F4D, 0x8229, 0xE474, 0x822A, 0x8D71, 0x822B, 0xE475, 0x822C, 0x94CA, 0x822E, 0xE484, 0x8233, 0xE477, 0x8235, 0x91C7, + 0x8236, 0x9495, 0x8237, 0x8CBD, 0x8238, 0xE476, 0x8239, 0x9144, 0x8240, 0xE478, 0x8247, 0x92F8, 0x8258, 0xE47A, 0x8259, 0xE479, + 0x825A, 0xE47C, 0x825D, 0xE47B, 0x825F, 0xE47D, 0x8262, 0xE480, 0x8264, 0xE47E, 0x8266, 0x8ACD, 0x8268, 0xE481, 0x826A, 0xE482, + 0x826B, 0xE483, 0x826E, 0x8DAF, 0x826F, 0x97C7, 0x8271, 0xE485, 0x8272, 0x9046, 0x8276, 0x8990, 0x8277, 0xE486, 0x8278, 0xE487, + 0x827E, 0xE488, 0x828B, 0x88F0, 0x828D, 0xE489, 0x8292, 0xE48A, 0x8299, 0x9587, 0x829D, 0x8EC5, 0x829F, 0xE48C, 0x82A5, 0x8A48, + 0x82A6, 0x88B0, 0x82AB, 0xE48B, 0x82AC, 0xE48E, 0x82AD, 0x946D, 0x82AF, 0x9063, 0x82B1, 0x89D4, 0x82B3, 0x9646, 0x82B8, 0x8C7C, + 0x82B9, 0x8BDA, 0x82BB, 0xE48D, 0x82BD, 0x89E8, 0x82C5, 0x8AA1, 0x82D1, 0x8991, 0x82D2, 0xE492, 0x82D3, 0x97E8, 0x82D4, 0x91DB, + 0x82D7, 0x9563, 0x82D9, 0xE49E, 0x82DB, 0x89D5, 0x82DC, 0xE49C, 0x82DE, 0xE49A, 0x82DF, 0xE491, 0x82E1, 0xE48F, 0x82E3, 0xE490, + 0x82E5, 0x8EE1, 0x82E6, 0x8BEA, 0x82E7, 0x9297, 0x82EB, 0x93CF, 0x82F1, 0x8970, 0x82F3, 0xE494, 0x82F4, 0xE493, 0x82F9, 0xE499, + 0x82FA, 0xE495, 0x82FB, 0xE498, 0x8301, 0xFB93, 0x8302, 0x96CE, 0x8303, 0xE497, 0x8304, 0x89D6, 0x8305, 0x8A9D, 0x8306, 0xE49B, + 0x8309, 0xE49D, 0x830E, 0x8C73, 0x8316, 0xE4A1, 0x8317, 0xE4AA, 0x8318, 0xE4AB, 0x831C, 0x88A9, 0x8323, 0xE4B2, 0x8328, 0x88EF, + 0x832B, 0xE4A9, 0x832F, 0xE4A8, 0x8331, 0xE4A3, 0x8332, 0xE4A2, 0x8334, 0xE4A0, 0x8335, 0xE49F, 0x8336, 0x9283, 0x8338, 0x91F9, + 0x8339, 0xE4A5, 0x8340, 0xE4A4, 0x8345, 0xE4A7, 0x8349, 0x9190, 0x834A, 0x8C74, 0x834F, 0x8960, 0x8350, 0xE4A6, 0x8352, 0x8D72, + 0x8358, 0x9191, 0x8362, 0xFB94, 0x8373, 0xE4B8, 0x8375, 0xE4B9, 0x8377, 0x89D7, 0x837B, 0x89AC, 0x837C, 0xE4B6, 0x837F, 0xFB95, + 0x8385, 0xE4AC, 0x8387, 0xE4B4, 0x8389, 0xE4BB, 0x838A, 0xE4B5, 0x838E, 0xE4B3, 0x8393, 0xE496, 0x8396, 0xE4B1, 0x839A, 0xE4AD, + 0x839E, 0x8ACE, 0x839F, 0xE4AF, 0x83A0, 0xE4BA, 0x83A2, 0xE4B0, 0x83A8, 0xE4BC, 0x83AA, 0xE4AE, 0x83AB, 0x949C, 0x83B1, 0x9789, + 0x83B5, 0xE4B7, 0x83BD, 0xE4CD, 0x83C1, 0xE4C5, 0x83C5, 0x909B, 0x83C7, 0xFB96, 0x83CA, 0x8B65, 0x83CC, 0x8BDB, 0x83CE, 0xE4C0, + 0x83D3, 0x89D9, 0x83D6, 0x8FD2, 0x83D8, 0xE4C3, 0x83DC, 0x8DD8, 0x83DF, 0x9370, 0x83E0, 0xE4C8, 0x83E9, 0x95EC, 0x83EB, 0xE4BF, + 0x83EF, 0x89D8, 0x83F0, 0x8CD4, 0x83F1, 0x9548, 0x83F2, 0xE4C9, 0x83F4, 0xE4BD, 0x83F6, 0xFB97, 0x83F7, 0xE4C6, 0x83FB, 0xE4D0, + 0x83FD, 0xE4C1, 0x8403, 0xE4C2, 0x8404, 0x93B8, 0x8407, 0xE4C7, 0x840B, 0xE4C4, 0x840C, 0x9647, 0x840D, 0xE4CA, 0x840E, 0x88DE, + 0x8413, 0xE4BE, 0x8420, 0xE4CC, 0x8422, 0xE4CB, 0x8429, 0x948B, 0x842A, 0xE4D2, 0x842C, 0xE4DD, 0x8431, 0x8A9E, 0x8435, 0xE4E0, + 0x8438, 0xE4CE, 0x843C, 0xE4D3, 0x843D, 0x978E, 0x8446, 0xE4DC, 0x8448, 0xFB98, 0x8449, 0x9774, 0x844E, 0x97A8, 0x8457, 0x9298, + 0x845B, 0x8A8B, 0x8461, 0x9592, 0x8462, 0xE4E2, 0x8463, 0x939F, 0x8466, 0x88AF, 0x8469, 0xE4DB, 0x846B, 0xE4D7, 0x846C, 0x9192, + 0x846D, 0xE4D1, 0x846E, 0xE4D9, 0x846F, 0xE4DE, 0x8471, 0x944B, 0x8475, 0x88A8, 0x8477, 0xE4D6, 0x8479, 0xE4DF, 0x847A, 0x9598, + 0x8482, 0xE4DA, 0x8484, 0xE4D5, 0x848B, 0x8FD3, 0x8490, 0x8F4E, 0x8494, 0x8EAA, 0x8499, 0x96D6, 0x849C, 0x9566, 0x849F, 0xE4E5, + 0x84A1, 0xE4EE, 0x84AD, 0xE4D8, 0x84B2, 0x8A97, 0x84B4, 0xFB99, 0x84B8, 0x8FF6, 0x84B9, 0xE4E3, 0x84BB, 0xE4E8, 0x84BC, 0x9193, + 0x84BF, 0xE4E4, 0x84C1, 0xE4EB, 0x84C4, 0x927E, 0x84C6, 0xE4EC, 0x84C9, 0x9775, 0x84CA, 0xE4E1, 0x84CB, 0x8A57, 0x84CD, 0xE4E7, + 0x84D0, 0xE4EA, 0x84D1, 0x96AA, 0x84D6, 0xE4ED, 0x84D9, 0xE4E6, 0x84DA, 0xE4E9, 0x84DC, 0xFA60, 0x84EC, 0x9648, 0x84EE, 0x9840, + 0x84F4, 0xE4F1, 0x84FC, 0xE4F8, 0x84FF, 0xE4F0, 0x8500, 0x8EC1, 0x8506, 0xE4CF, 0x8511, 0x95CC, 0x8513, 0x96A0, 0x8514, 0xE4F7, + 0x8515, 0xE4F6, 0x8517, 0xE4F2, 0x8518, 0xE4F3, 0x851A, 0x8955, 0x851F, 0xE4F5, 0x8521, 0xE4EF, 0x8526, 0x92D3, 0x852C, 0xE4F4, + 0x852D, 0x88FC, 0x8535, 0x91A0, 0x853D, 0x95C1, 0x8540, 0xE4F9, 0x8541, 0xE540, 0x8543, 0x94D7, 0x8548, 0xE4FC, 0x8549, 0x8FD4, + 0x854A, 0x8EC7, 0x854B, 0xE542, 0x854E, 0x8BBC, 0x8553, 0xFB9A, 0x8555, 0xE543, 0x8557, 0x9599, 0x8558, 0xE4FB, 0x8559, 0xFB9B, + 0x855A, 0xE4D4, 0x8563, 0xE4FA, 0x8568, 0x986E, 0x8569, 0x93A0, 0x856A, 0x9593, 0x856B, 0xFB9C, 0x856D, 0xE54A, 0x8577, 0xE550, + 0x857E, 0xE551, 0x8580, 0xE544, 0x8584, 0x9496, 0x8587, 0xE54E, 0x8588, 0xE546, 0x858A, 0xE548, 0x8590, 0xE552, 0x8591, 0xE547, + 0x8594, 0xE54B, 0x8597, 0x8992, 0x8599, 0x93E3, 0x859B, 0xE54C, 0x859C, 0xE54F, 0x85A4, 0xE545, 0x85A6, 0x9145, 0x85A8, 0xE549, + 0x85A9, 0x8E46, 0x85AA, 0x9064, 0x85AB, 0x8C4F, 0x85AC, 0x96F2, 0x85AE, 0x96F7, 0x85AF, 0x8F92, 0x85B0, 0xFB9E, 0x85B9, 0xE556, + 0x85BA, 0xE554, 0x85C1, 0x986D, 0x85C9, 0xE553, 0x85CD, 0x9795, 0x85CF, 0xE555, 0x85D0, 0xE557, 0x85D5, 0xE558, 0x85DC, 0xE55B, + 0x85DD, 0xE559, 0x85E4, 0x93A1, 0x85E5, 0xE55A, 0x85E9, 0x94CB, 0x85EA, 0xE54D, 0x85F7, 0x8F93, 0x85F9, 0xE55C, 0x85FA, 0xE561, + 0x85FB, 0x9194, 0x85FE, 0xE560, 0x8602, 0xE541, 0x8606, 0xE562, 0x8607, 0x9168, 0x860A, 0xE55D, 0x860B, 0xE55F, 0x8613, 0xE55E, + 0x8616, 0x9F50, 0x8617, 0x9F41, 0x861A, 0xE564, 0x8622, 0xE563, 0x862D, 0x9796, 0x862F, 0xE1BA, 0x8630, 0xE565, 0x863F, 0xE566, + 0x864D, 0xE567, 0x864E, 0x8CD5, 0x8650, 0x8B73, 0x8654, 0xE569, 0x8655, 0x997C, 0x865A, 0x8B95, 0x865C, 0x97B8, 0x865E, 0x8BF1, + 0x865F, 0xE56A, 0x8667, 0xE56B, 0x866B, 0x928E, 0x8671, 0xE56C, 0x8679, 0x93F8, 0x867B, 0x88B8, 0x868A, 0x89E1, 0x868B, 0xE571, + 0x868C, 0xE572, 0x8693, 0xE56D, 0x8695, 0x8E5C, 0x86A3, 0xE56E, 0x86A4, 0x9461, 0x86A9, 0xE56F, 0x86AA, 0xE570, 0x86AB, 0xE57A, + 0x86AF, 0xE574, 0x86B0, 0xE577, 0x86B6, 0xE573, 0x86C4, 0xE575, 0x86C6, 0xE576, 0x86C7, 0x8ED6, 0x86C9, 0xE578, 0x86CB, 0x9260, + 0x86CD, 0x8C75, 0x86CE, 0x8A61, 0x86D4, 0xE57B, 0x86D9, 0x8A5E, 0x86DB, 0xE581, 0x86DE, 0xE57C, 0x86DF, 0xE580, 0x86E4, 0x94B8, + 0x86E9, 0xE57D, 0x86EC, 0xE57E, 0x86ED, 0x9567, 0x86EE, 0x94D8, 0x86EF, 0xE582, 0x86F8, 0x91FB, 0x86F9, 0xE58C, 0x86FB, 0xE588, + 0x86FE, 0x89E9, 0x8700, 0xE586, 0x8702, 0x9649, 0x8703, 0xE587, 0x8706, 0xE584, 0x8708, 0xE585, 0x8709, 0xE58A, 0x870A, 0xE58D, + 0x870D, 0xE58B, 0x8711, 0xE589, 0x8712, 0xE583, 0x8718, 0x9277, 0x871A, 0xE594, 0x871C, 0x96A8, 0x8725, 0xE592, 0x8729, 0xE593, + 0x8734, 0xE58E, 0x8737, 0xE590, 0x873B, 0xE591, 0x873F, 0xE58F, 0x8749, 0x90E4, 0x874B, 0x9858, 0x874C, 0xE598, 0x874E, 0xE599, + 0x8753, 0xE59F, 0x8755, 0x9049, 0x8757, 0xE59B, 0x8759, 0xE59E, 0x875F, 0xE596, 0x8760, 0xE595, 0x8763, 0xE5A0, 0x8766, 0x89DA, + 0x8768, 0xE59C, 0x876A, 0xE5A1, 0x876E, 0xE59D, 0x8774, 0xE59A, 0x8776, 0x92B1, 0x8778, 0xE597, 0x877F, 0x9488, 0x8782, 0xE5A5, + 0x878D, 0x975A, 0x879F, 0xE5A4, 0x87A2, 0xE5A3, 0x87AB, 0xE5AC, 0x87AF, 0xE5A6, 0x87B3, 0xE5AE, 0x87BA, 0x9786, 0x87BB, 0xE5B1, + 0x87BD, 0xE5A8, 0x87C0, 0xE5A9, 0x87C4, 0xE5AD, 0x87C6, 0xE5B0, 0x87C7, 0xE5AF, 0x87CB, 0xE5A7, 0x87D0, 0xE5AA, 0x87D2, 0xE5BB, + 0x87E0, 0xE5B4, 0x87EF, 0xE5B2, 0x87F2, 0xE5B3, 0x87F6, 0xE5B8, 0x87F7, 0xE5B9, 0x87F9, 0x8A49, 0x87FB, 0x8B61, 0x87FE, 0xE5B7, + 0x8805, 0xE5A2, 0x8807, 0xFBA1, 0x880D, 0xE5B6, 0x880E, 0xE5BA, 0x880F, 0xE5B5, 0x8811, 0xE5BC, 0x8815, 0xE5BE, 0x8816, 0xE5BD, + 0x8821, 0xE5C0, 0x8822, 0xE5BF, 0x8823, 0xE579, 0x8827, 0xE5C4, 0x8831, 0xE5C1, 0x8836, 0xE5C2, 0x8839, 0xE5C3, 0x883B, 0xE5C5, + 0x8840, 0x8C8C, 0x8842, 0xE5C7, 0x8844, 0xE5C6, 0x8846, 0x8F4F, 0x884C, 0x8D73, 0x884D, 0x9FA5, 0x8852, 0xE5C8, 0x8853, 0x8F70, + 0x8857, 0x8A58, 0x8859, 0xE5C9, 0x885B, 0x8971, 0x885D, 0x8FD5, 0x885E, 0xE5CA, 0x8861, 0x8D74, 0x8862, 0xE5CB, 0x8863, 0x88DF, + 0x8868, 0x955C, 0x886B, 0xE5CC, 0x8870, 0x908A, 0x8872, 0xE5D3, 0x8875, 0xE5D0, 0x8877, 0x928F, 0x887D, 0xE5D1, 0x887E, 0xE5CE, + 0x887F, 0x8BDC, 0x8881, 0xE5CD, 0x8882, 0xE5D4, 0x8888, 0x8C55, 0x888B, 0x91DC, 0x888D, 0xE5DA, 0x8892, 0xE5D6, 0x8896, 0x91B3, + 0x8897, 0xE5D5, 0x8899, 0xE5D8, 0x889E, 0xE5CF, 0x88A2, 0xE5D9, 0x88A4, 0xE5DB, 0x88AB, 0x94ED, 0x88AE, 0xE5D7, 0x88B0, 0xE5DC, + 0x88B1, 0xE5DE, 0x88B4, 0x8CD1, 0x88B5, 0xE5D2, 0x88B7, 0x88BF, 0x88BF, 0xE5DD, 0x88C1, 0x8DD9, 0x88C2, 0x97F4, 0x88C3, 0xE5DF, + 0x88C4, 0xE5E0, 0x88C5, 0x9195, 0x88CF, 0x97A0, 0x88D4, 0xE5E1, 0x88D5, 0x9754, 0x88D8, 0xE5E2, 0x88D9, 0xE5E3, 0x88DC, 0x95E2, + 0x88DD, 0xE5E4, 0x88DF, 0x8DBE, 0x88E1, 0x97A1, 0x88E8, 0xE5E9, 0x88F2, 0xE5EA, 0x88F3, 0x8FD6, 0x88F4, 0xE5E8, 0x88F5, 0xFBA2, + 0x88F8, 0x9787, 0x88F9, 0xE5E5, 0x88FC, 0xE5E7, 0x88FD, 0x90BB, 0x88FE, 0x909E, 0x8902, 0xE5E6, 0x8904, 0xE5EB, 0x8907, 0x95A1, + 0x890A, 0xE5ED, 0x890C, 0xE5EC, 0x8910, 0x8A8C, 0x8912, 0x964A, 0x8913, 0xE5EE, 0x891C, 0xFA5D, 0x891D, 0xE5FA, 0x891E, 0xE5F0, + 0x8925, 0xE5F1, 0x892A, 0xE5F2, 0x892B, 0xE5F3, 0x8936, 0xE5F7, 0x8938, 0xE5F8, 0x893B, 0xE5F6, 0x8941, 0xE5F4, 0x8943, 0xE5EF, + 0x8944, 0xE5F5, 0x894C, 0xE5F9, 0x894D, 0xE8B5, 0x8956, 0x89A6, 0x895E, 0xE5FC, 0x895F, 0x8BDD, 0x8960, 0xE5FB, 0x8964, 0xE641, + 0x8966, 0xE640, 0x896A, 0xE643, 0x896D, 0xE642, 0x896F, 0xE644, 0x8972, 0x8F50, 0x8974, 0xE645, 0x8977, 0xE646, 0x897E, 0xE647, + 0x897F, 0x90BC, 0x8981, 0x9776, 0x8983, 0xE648, 0x8986, 0x95A2, 0x8987, 0x9465, 0x8988, 0xE649, 0x898A, 0xE64A, 0x898B, 0x8CA9, + 0x898F, 0x8B4B, 0x8993, 0xE64B, 0x8996, 0x8E8B, 0x8997, 0x9460, 0x8998, 0xE64C, 0x899A, 0x8A6F, 0x89A1, 0xE64D, 0x89A6, 0xE64F, + 0x89A7, 0x9797, 0x89A9, 0xE64E, 0x89AA, 0x9065, 0x89AC, 0xE650, 0x89AF, 0xE651, 0x89B2, 0xE652, 0x89B3, 0x8ACF, 0x89BA, 0xE653, + 0x89BD, 0xE654, 0x89BF, 0xE655, 0x89C0, 0xE656, 0x89D2, 0x8A70, 0x89DA, 0xE657, 0x89DC, 0xE658, 0x89DD, 0xE659, 0x89E3, 0x89F0, + 0x89E6, 0x9047, 0x89E7, 0xE65A, 0x89F4, 0xE65B, 0x89F8, 0xE65C, 0x8A00, 0x8CBE, 0x8A02, 0x92F9, 0x8A03, 0xE65D, 0x8A08, 0x8C76, + 0x8A0A, 0x9075, 0x8A0C, 0xE660, 0x8A0E, 0x93A2, 0x8A10, 0xE65F, 0x8A12, 0xFBA3, 0x8A13, 0x8C50, 0x8A16, 0xE65E, 0x8A17, 0x91F5, + 0x8A18, 0x8B4C, 0x8A1B, 0xE661, 0x8A1D, 0xE662, 0x8A1F, 0x8FD7, 0x8A23, 0x8C8D, 0x8A25, 0xE663, 0x8A2A, 0x964B, 0x8A2D, 0x90DD, + 0x8A31, 0x8B96, 0x8A33, 0x96F3, 0x8A34, 0x9169, 0x8A36, 0xE664, 0x8A37, 0xFBA4, 0x8A3A, 0x9066, 0x8A3B, 0x9290, 0x8A3C, 0x8FD8, + 0x8A41, 0xE665, 0x8A46, 0xE668, 0x8A48, 0xE669, 0x8A50, 0x8DBC, 0x8A51, 0x91C0, 0x8A52, 0xE667, 0x8A54, 0x8FD9, 0x8A55, 0x955D, + 0x8A5B, 0xE666, 0x8A5E, 0x8E8C, 0x8A60, 0x8972, 0x8A62, 0xE66D, 0x8A63, 0x8C77, 0x8A66, 0x8E8E, 0x8A69, 0x8E8D, 0x8A6B, 0x986C, + 0x8A6C, 0xE66C, 0x8A6D, 0xE66B, 0x8A6E, 0x9146, 0x8A70, 0x8B6C, 0x8A71, 0x9862, 0x8A72, 0x8A59, 0x8A73, 0x8FDA, 0x8A79, 0xFBA5, + 0x8A7C, 0xE66A, 0x8A82, 0xE66F, 0x8A84, 0xE670, 0x8A85, 0xE66E, 0x8A87, 0x8CD6, 0x8A89, 0x975F, 0x8A8C, 0x8E8F, 0x8A8D, 0x9446, + 0x8A91, 0xE673, 0x8A93, 0x90BE, 0x8A95, 0x9261, 0x8A98, 0x9755, 0x8A9A, 0xE676, 0x8A9E, 0x8CEA, 0x8AA0, 0x90BD, 0x8AA1, 0xE672, + 0x8AA3, 0xE677, 0x8AA4, 0x8CEB, 0x8AA5, 0xE674, 0x8AA6, 0xE675, 0x8AA7, 0xFBA6, 0x8AA8, 0xE671, 0x8AAC, 0x90E0, 0x8AAD, 0x93C7, + 0x8AB0, 0x924E, 0x8AB2, 0x89DB, 0x8AB9, 0x94EE, 0x8ABC, 0x8B62, 0x8ABE, 0xFBA7, 0x8ABF, 0x92B2, 0x8AC2, 0xE67A, 0x8AC4, 0xE678, + 0x8AC7, 0x926B, 0x8ACB, 0x90BF, 0x8ACC, 0x8AD0, 0x8ACD, 0xE679, 0x8ACF, 0x907A, 0x8AD2, 0x97C8, 0x8AD6, 0x985F, 0x8ADA, 0xE67B, + 0x8ADB, 0xE687, 0x8ADC, 0x92B3, 0x8ADE, 0xE686, 0x8ADF, 0xFBA8, 0x8AE0, 0xE683, 0x8AE1, 0xE68B, 0x8AE2, 0xE684, 0x8AE4, 0xE680, + 0x8AE6, 0x92FA, 0x8AE7, 0xE67E, 0x8AEB, 0xE67C, 0x8AED, 0x9740, 0x8AEE, 0x8E90, 0x8AF1, 0xE681, 0x8AF3, 0xE67D, 0x8AF6, 0xFBAA, + 0x8AF7, 0xE685, 0x8AF8, 0x8F94, 0x8AFA, 0x8CBF, 0x8AFE, 0x91F8, 0x8B00, 0x9664, 0x8B01, 0x8979, 0x8B02, 0x88E0, 0x8B04, 0x93A3, + 0x8B07, 0xE689, 0x8B0C, 0xE688, 0x8B0E, 0x93E4, 0x8B10, 0xE68D, 0x8B14, 0xE682, 0x8B16, 0xE68C, 0x8B17, 0xE68E, 0x8B19, 0x8CAA, + 0x8B1A, 0xE68A, 0x8B1B, 0x8D75, 0x8B1D, 0x8ED3, 0x8B20, 0xE68F, 0x8B21, 0x9777, 0x8B26, 0xE692, 0x8B28, 0xE695, 0x8B2B, 0xE693, + 0x8B2C, 0x9554, 0x8B33, 0xE690, 0x8B39, 0x8BDE, 0x8B3E, 0xE694, 0x8B41, 0xE696, 0x8B49, 0xE69A, 0x8B4C, 0xE697, 0x8B4E, 0xE699, + 0x8B4F, 0xE698, 0x8B53, 0xFBAB, 0x8B56, 0xE69B, 0x8B58, 0x8EAF, 0x8B5A, 0xE69D, 0x8B5B, 0xE69C, 0x8B5C, 0x9588, 0x8B5F, 0xE69F, + 0x8B66, 0x8C78, 0x8B6B, 0xE69E, 0x8B6C, 0xE6A0, 0x8B6F, 0xE6A1, 0x8B70, 0x8B63, 0x8B71, 0xE3BF, 0x8B72, 0x8FF7, 0x8B74, 0xE6A2, + 0x8B77, 0x8CEC, 0x8B7D, 0xE6A3, 0x8B7F, 0xFBAC, 0x8B80, 0xE6A4, 0x8B83, 0x8E5D, 0x8B8A, 0x9DCC, 0x8B8C, 0xE6A5, 0x8B8E, 0xE6A6, + 0x8B90, 0x8F51, 0x8B92, 0xE6A7, 0x8B93, 0xE6A8, 0x8B96, 0xE6A9, 0x8B99, 0xE6AA, 0x8B9A, 0xE6AB, 0x8C37, 0x924A, 0x8C3A, 0xE6AC, + 0x8C3F, 0xE6AE, 0x8C41, 0xE6AD, 0x8C46, 0x93A4, 0x8C48, 0xE6AF, 0x8C4A, 0x964C, 0x8C4C, 0xE6B0, 0x8C4E, 0xE6B1, 0x8C50, 0xE6B2, + 0x8C55, 0xE6B3, 0x8C5A, 0x93D8, 0x8C61, 0x8FDB, 0x8C62, 0xE6B4, 0x8C6A, 0x8D8B, 0x8C6B, 0x98AC, 0x8C6C, 0xE6B5, 0x8C78, 0xE6B6, + 0x8C79, 0x955E, 0x8C7A, 0xE6B7, 0x8C7C, 0xE6BF, 0x8C82, 0xE6B8, 0x8C85, 0xE6BA, 0x8C89, 0xE6B9, 0x8C8A, 0xE6BB, 0x8C8C, 0x9665, + 0x8C8D, 0xE6BC, 0x8C8E, 0xE6BD, 0x8C94, 0xE6BE, 0x8C98, 0xE6C0, 0x8C9D, 0x8A4C, 0x8C9E, 0x92E5, 0x8CA0, 0x9589, 0x8CA1, 0x8DE0, + 0x8CA2, 0x8D76, 0x8CA7, 0x956E, 0x8CA8, 0x89DD, 0x8CA9, 0x94CC, 0x8CAA, 0xE6C3, 0x8CAB, 0x8AD1, 0x8CAC, 0x90D3, 0x8CAD, 0xE6C2, + 0x8CAE, 0xE6C7, 0x8CAF, 0x9299, 0x8CB0, 0x96E1, 0x8CB2, 0xE6C5, 0x8CB3, 0xE6C6, 0x8CB4, 0x8B4D, 0x8CB6, 0xE6C8, 0x8CB7, 0x9483, + 0x8CB8, 0x91DD, 0x8CBB, 0x94EF, 0x8CBC, 0x935C, 0x8CBD, 0xE6C4, 0x8CBF, 0x9666, 0x8CC0, 0x89EA, 0x8CC1, 0xE6CA, 0x8CC2, 0x9847, + 0x8CC3, 0x92C0, 0x8CC4, 0x9864, 0x8CC7, 0x8E91, 0x8CC8, 0xE6C9, 0x8CCA, 0x91AF, 0x8CCD, 0xE6DA, 0x8CCE, 0x9147, 0x8CD1, 0x93F6, + 0x8CD3, 0x956F, 0x8CDA, 0xE6CD, 0x8CDB, 0x8E5E, 0x8CDC, 0x8E92, 0x8CDE, 0x8FDC, 0x8CE0, 0x9485, 0x8CE2, 0x8CAB, 0x8CE3, 0xE6CC, + 0x8CE4, 0xE6CB, 0x8CE6, 0x958A, 0x8CEA, 0x8EBF, 0x8CED, 0x9371, 0x8CF0, 0xFBAD, 0x8CF4, 0xFBAE, 0x8CFA, 0xE6CF, 0x8CFB, 0xE6D0, + 0x8CFC, 0x8D77, 0x8CFD, 0xE6CE, 0x8D04, 0xE6D1, 0x8D05, 0xE6D2, 0x8D07, 0xE6D4, 0x8D08, 0x91A1, 0x8D0A, 0xE6D3, 0x8D0B, 0x8AE4, + 0x8D0D, 0xE6D6, 0x8D0F, 0xE6D5, 0x8D10, 0xE6D7, 0x8D12, 0xFBAF, 0x8D13, 0xE6D9, 0x8D14, 0xE6DB, 0x8D16, 0xE6DC, 0x8D64, 0x90D4, + 0x8D66, 0x8ECD, 0x8D67, 0xE6DD, 0x8D6B, 0x8A71, 0x8D6D, 0xE6DE, 0x8D70, 0x9196, 0x8D71, 0xE6DF, 0x8D73, 0xE6E0, 0x8D74, 0x958B, + 0x8D76, 0xFBB0, 0x8D77, 0x8B4E, 0x8D81, 0xE6E1, 0x8D85, 0x92B4, 0x8D8A, 0x897A, 0x8D99, 0xE6E2, 0x8DA3, 0x8EEF, 0x8DA8, 0x9096, + 0x8DB3, 0x91AB, 0x8DBA, 0xE6E5, 0x8DBE, 0xE6E4, 0x8DC2, 0xE6E3, 0x8DCB, 0xE6EB, 0x8DCC, 0xE6E9, 0x8DCF, 0xE6E6, 0x8DD6, 0xE6E8, + 0x8DDA, 0xE6E7, 0x8DDB, 0xE6EA, 0x8DDD, 0x8B97, 0x8DDF, 0xE6EE, 0x8DE1, 0x90D5, 0x8DE3, 0xE6EF, 0x8DE8, 0x8CD7, 0x8DEA, 0xE6EC, + 0x8DEB, 0xE6ED, 0x8DEF, 0x9848, 0x8DF3, 0x92B5, 0x8DF5, 0x9148, 0x8DFC, 0xE6F0, 0x8DFF, 0xE6F3, 0x8E08, 0xE6F1, 0x8E09, 0xE6F2, + 0x8E0A, 0x9778, 0x8E0F, 0x93A5, 0x8E10, 0xE6F6, 0x8E1D, 0xE6F4, 0x8E1E, 0xE6F5, 0x8E1F, 0xE6F7, 0x8E2A, 0xE748, 0x8E30, 0xE6FA, + 0x8E34, 0xE6FB, 0x8E35, 0xE6F9, 0x8E42, 0xE6F8, 0x8E44, 0x92FB, 0x8E47, 0xE740, 0x8E48, 0xE744, 0x8E49, 0xE741, 0x8E4A, 0xE6FC, + 0x8E4C, 0xE742, 0x8E50, 0xE743, 0x8E55, 0xE74A, 0x8E59, 0xE745, 0x8E5F, 0x90D6, 0x8E60, 0xE747, 0x8E63, 0xE749, 0x8E64, 0xE746, + 0x8E72, 0xE74C, 0x8E74, 0x8F52, 0x8E76, 0xE74B, 0x8E7C, 0xE74D, 0x8E81, 0xE74E, 0x8E84, 0xE751, 0x8E85, 0xE750, 0x8E87, 0xE74F, + 0x8E8A, 0xE753, 0x8E8B, 0xE752, 0x8E8D, 0x96F4, 0x8E91, 0xE755, 0x8E93, 0xE754, 0x8E94, 0xE756, 0x8E99, 0xE757, 0x8EA1, 0xE759, + 0x8EAA, 0xE758, 0x8EAB, 0x9067, 0x8EAC, 0xE75A, 0x8EAF, 0x8BEB, 0x8EB0, 0xE75B, 0x8EB1, 0xE75D, 0x8EBE, 0xE75E, 0x8EC5, 0xE75F, + 0x8EC6, 0xE75C, 0x8EC8, 0xE760, 0x8ECA, 0x8ED4, 0x8ECB, 0xE761, 0x8ECC, 0x8B4F, 0x8ECD, 0x8C52, 0x8ECF, 0xFBB2, 0x8ED2, 0x8CAC, + 0x8EDB, 0xE762, 0x8EDF, 0x93EE, 0x8EE2, 0x935D, 0x8EE3, 0xE763, 0x8EEB, 0xE766, 0x8EF8, 0x8EB2, 0x8EFB, 0xE765, 0x8EFC, 0xE764, + 0x8EFD, 0x8C79, 0x8EFE, 0xE767, 0x8F03, 0x8A72, 0x8F05, 0xE769, 0x8F09, 0x8DDA, 0x8F0A, 0xE768, 0x8F0C, 0xE771, 0x8F12, 0xE76B, + 0x8F13, 0xE76D, 0x8F14, 0x95E3, 0x8F15, 0xE76A, 0x8F19, 0xE76C, 0x8F1B, 0xE770, 0x8F1C, 0xE76E, 0x8F1D, 0x8B50, 0x8F1F, 0xE76F, + 0x8F26, 0xE772, 0x8F29, 0x9479, 0x8F2A, 0x97D6, 0x8F2F, 0x8F53, 0x8F33, 0xE773, 0x8F38, 0x9741, 0x8F39, 0xE775, 0x8F3B, 0xE774, + 0x8F3E, 0xE778, 0x8F3F, 0x9760, 0x8F42, 0xE777, 0x8F44, 0x8A8D, 0x8F45, 0xE776, 0x8F46, 0xE77B, 0x8F49, 0xE77A, 0x8F4C, 0xE779, + 0x8F4D, 0x9351, 0x8F4E, 0xE77C, 0x8F57, 0xE77D, 0x8F5C, 0xE77E, 0x8F5F, 0x8D8C, 0x8F61, 0x8C44, 0x8F62, 0xE780, 0x8F63, 0xE781, + 0x8F64, 0xE782, 0x8F9B, 0x9068, 0x8F9C, 0xE783, 0x8F9E, 0x8EAB, 0x8F9F, 0xE784, 0x8FA3, 0xE785, 0x8FA7, 0x999F, 0x8FA8, 0x999E, + 0x8FAD, 0xE786, 0x8FAE, 0xE390, 0x8FAF, 0xE787, 0x8FB0, 0x9243, 0x8FB1, 0x904A, 0x8FB2, 0x945F, 0x8FB7, 0xE788, 0x8FBA, 0x95D3, + 0x8FBB, 0x92D2, 0x8FBC, 0x8D9E, 0x8FBF, 0x9248, 0x8FC2, 0x8949, 0x8FC4, 0x9698, 0x8FC5, 0x9076, 0x8FCE, 0x8C7D, 0x8FD1, 0x8BDF, + 0x8FD4, 0x95D4, 0x8FDA, 0xE789, 0x8FE2, 0xE78B, 0x8FE5, 0xE78A, 0x8FE6, 0x89DE, 0x8FE9, 0x93F4, 0x8FEA, 0xE78C, 0x8FEB, 0x9497, + 0x8FED, 0x9352, 0x8FEF, 0xE78D, 0x8FF0, 0x8F71, 0x8FF4, 0xE78F, 0x8FF7, 0x96C0, 0x8FF8, 0xE79E, 0x8FF9, 0xE791, 0x8FFA, 0xE792, + 0x8FFD, 0x92C7, 0x9000, 0x91DE, 0x9001, 0x9197, 0x9003, 0x93A6, 0x9005, 0xE790, 0x9006, 0x8B74, 0x900B, 0xE799, 0x900D, 0xE796, + 0x900E, 0xE7A3, 0x900F, 0x93A7, 0x9010, 0x9280, 0x9011, 0xE793, 0x9013, 0x92FC, 0x9014, 0x9372, 0x9015, 0xE794, 0x9016, 0xE798, + 0x9017, 0x9080, 0x9019, 0x9487, 0x901A, 0x92CA, 0x901D, 0x90C0, 0x901E, 0xE797, 0x901F, 0x91AC, 0x9020, 0x91A2, 0x9021, 0xE795, + 0x9022, 0x88A7, 0x9023, 0x9841, 0x9027, 0xE79A, 0x902E, 0x91DF, 0x9031, 0x8F54, 0x9032, 0x9069, 0x9035, 0xE79C, 0x9036, 0xE79B, + 0x9038, 0x88ED, 0x9039, 0xE79D, 0x903C, 0x954E, 0x903E, 0xE7A5, 0x9041, 0x93D9, 0x9042, 0x908B, 0x9045, 0x9278, 0x9047, 0x8BF6, + 0x9049, 0xE7A4, 0x904A, 0x9756, 0x904B, 0x895E, 0x904D, 0x95D5, 0x904E, 0x89DF, 0x904F, 0xE79F, 0x9050, 0xE7A0, 0x9051, 0xE7A1, + 0x9052, 0xE7A2, 0x9053, 0x93B9, 0x9054, 0x9242, 0x9055, 0x88E1, 0x9056, 0xE7A6, 0x9058, 0xE7A7, 0x9059, 0xEAA1, 0x905C, 0x91BB, + 0x905E, 0xE7A8, 0x9060, 0x8993, 0x9061, 0x916B, 0x9063, 0x8CAD, 0x9065, 0x9779, 0x9067, 0xFBB5, 0x9068, 0xE7A9, 0x9069, 0x934B, + 0x906D, 0x9198, 0x906E, 0x8ED5, 0x906F, 0xE7AA, 0x9072, 0xE7AD, 0x9075, 0x8F85, 0x9076, 0xE7AB, 0x9077, 0x914A, 0x9078, 0x9149, + 0x907A, 0x88E2, 0x907C, 0x97C9, 0x907D, 0xE7AF, 0x907F, 0x94F0, 0x9080, 0xE7B1, 0x9081, 0xE7B0, 0x9082, 0xE7AE, 0x9083, 0xE284, + 0x9084, 0x8AD2, 0x9087, 0xE78E, 0x9089, 0xE7B3, 0x908A, 0xE7B2, 0x908F, 0xE7B4, 0x9091, 0x9757, 0x90A3, 0x93DF, 0x90A6, 0x964D, + 0x90A8, 0xE7B5, 0x90AA, 0x8ED7, 0x90AF, 0xE7B6, 0x90B1, 0xE7B7, 0x90B5, 0xE7B8, 0x90B8, 0x9340, 0x90C1, 0x88E8, 0x90CA, 0x8D78, + 0x90CE, 0x9859, 0x90DB, 0xE7BC, 0x90DE, 0xFBB6, 0x90E1, 0x8C53, 0x90E2, 0xE7B9, 0x90E4, 0xE7BA, 0x90E8, 0x9594, 0x90ED, 0x8A73, + 0x90F5, 0x9758, 0x90F7, 0x8BBD, 0x90FD, 0x9373, 0x9102, 0xE7BD, 0x9112, 0xE7BE, 0x9115, 0xFBB8, 0x9119, 0xE7BF, 0x9127, 0xFBB9, + 0x912D, 0x9341, 0x9130, 0xE7C1, 0x9132, 0xE7C0, 0x9149, 0x93D1, 0x914A, 0xE7C2, 0x914B, 0x8F55, 0x914C, 0x8EDE, 0x914D, 0x947A, + 0x914E, 0x9291, 0x9152, 0x8EF0, 0x9154, 0x908C, 0x9156, 0xE7C3, 0x9158, 0xE7C4, 0x9162, 0x907C, 0x9163, 0xE7C5, 0x9165, 0xE7C6, + 0x9169, 0xE7C7, 0x916A, 0x978F, 0x916C, 0x8F56, 0x9172, 0xE7C9, 0x9173, 0xE7C8, 0x9175, 0x8D79, 0x9177, 0x8D93, 0x9178, 0x8E5F, + 0x9182, 0xE7CC, 0x9187, 0x8F86, 0x9189, 0xE7CB, 0x918B, 0xE7CA, 0x918D, 0x91E7, 0x9190, 0x8CED, 0x9192, 0x90C1, 0x9197, 0x94AE, + 0x919C, 0x8F58, 0x91A2, 0xE7CD, 0x91A4, 0x8FDD, 0x91AA, 0xE7D0, 0x91AB, 0xE7CE, 0x91AF, 0xE7CF, 0x91B4, 0xE7D2, 0x91B5, 0xE7D1, + 0x91B8, 0x8FF8, 0x91BA, 0xE7D3, 0x91C0, 0xE7D4, 0x91C1, 0xE7D5, 0x91C6, 0x94CE, 0x91C7, 0x8DD1, 0x91C8, 0x8EDF, 0x91C9, 0xE7D6, + 0x91CB, 0xE7D7, 0x91CC, 0x97A2, 0x91CD, 0x8F64, 0x91CE, 0x96EC, 0x91CF, 0x97CA, 0x91D0, 0xE7D8, 0x91D1, 0x8BE0, 0x91D6, 0xE7D9, + 0x91D7, 0xFBBB, 0x91D8, 0x9342, 0x91DA, 0xFBBA, 0x91DB, 0xE7DC, 0x91DC, 0x8A98, 0x91DD, 0x906A, 0x91DE, 0xFBBC, 0x91DF, 0xE7DA, + 0x91E1, 0xE7DB, 0x91E3, 0x92DE, 0x91E4, 0xFBBF, 0x91E5, 0xFBC0, 0x91E6, 0x9674, 0x91E7, 0x8BFA, 0x91ED, 0xFBBD, 0x91EE, 0xFBBE, + 0x91F5, 0xE7DE, 0x91F6, 0xE7DF, 0x91FC, 0xE7DD, 0x91FF, 0xE7E1, 0x9206, 0xFBC1, 0x920A, 0xFBC3, 0x920D, 0x93DD, 0x920E, 0x8A62, + 0x9210, 0xFBC2, 0x9211, 0xE7E5, 0x9214, 0xE7E2, 0x9215, 0xE7E4, 0x921E, 0xE7E0, 0x9229, 0xE86E, 0x922C, 0xE7E3, 0x9234, 0x97E9, + 0x9237, 0x8CD8, 0x9239, 0xFBCA, 0x923A, 0xFBC4, 0x923C, 0xFBC6, 0x923F, 0xE7ED, 0x9240, 0xFBC5, 0x9244, 0x9353, 0x9245, 0xE7E8, + 0x9248, 0xE7EB, 0x9249, 0xE7E9, 0x924B, 0xE7EE, 0x924E, 0xFBC7, 0x9250, 0xE7EF, 0x9251, 0xFBC9, 0x9257, 0xE7E7, 0x9259, 0xFBC8, + 0x925A, 0xE7F4, 0x925B, 0x8994, 0x925E, 0xE7E6, 0x9262, 0x94AB, 0x9264, 0xE7EA, 0x9266, 0x8FDE, 0x9267, 0xFBCB, 0x9271, 0x8D7A, + 0x9277, 0xFBCD, 0x9278, 0xFBCE, 0x927E, 0x9667, 0x9280, 0x8BE2, 0x9283, 0x8F65, 0x9285, 0x93BA, 0x9288, 0xFA5F, 0x9291, 0x914C, + 0x9293, 0xE7F2, 0x9295, 0xE7EC, 0x9296, 0xE7F1, 0x9298, 0x96C1, 0x929A, 0x92B6, 0x929B, 0xE7F3, 0x929C, 0xE7F0, 0x92A7, 0xFBCC, + 0x92AD, 0x914B, 0x92B7, 0xE7F7, 0x92B9, 0xE7F6, 0x92CF, 0xE7F5, 0x92D0, 0xFBD2, 0x92D2, 0x964E, 0x92D3, 0xFBD6, 0x92D5, 0xFBD4, + 0x92D7, 0xFBD0, 0x92D9, 0xFBD1, 0x92E0, 0xFBD5, 0x92E4, 0x8F9B, 0x92E7, 0xFBCF, 0x92E9, 0xE7F8, 0x92EA, 0x95DD, 0x92ED, 0x8973, + 0x92F2, 0x9565, 0x92F3, 0x9292, 0x92F8, 0x8B98, 0x92F9, 0xFA65, 0x92FA, 0xE7FA, 0x92FB, 0xFBD9, 0x92FC, 0x8D7C, 0x92FF, 0xFBDC, + 0x9302, 0xFBDE, 0x9306, 0x8E4B, 0x930F, 0xE7F9, 0x9310, 0x908D, 0x9318, 0x908E, 0x9319, 0xE840, 0x931A, 0xE842, 0x931D, 0xFBDD, + 0x931E, 0xFBDB, 0x9320, 0x8FF9, 0x9321, 0xFBD8, 0x9322, 0xE841, 0x9323, 0xE843, 0x9325, 0xFBD7, 0x9326, 0x8BD1, 0x9328, 0x9564, + 0x932B, 0x8EE0, 0x932C, 0x9842, 0x932E, 0xE7FC, 0x932F, 0x8DF6, 0x9332, 0x985E, 0x9335, 0xE845, 0x933A, 0xE844, 0x933B, 0xE846, + 0x9344, 0xE7FB, 0x9348, 0xFA5E, 0x934B, 0x93E7, 0x934D, 0x9374, 0x9354, 0x92D5, 0x9356, 0xE84B, 0x9357, 0xFBE0, 0x935B, 0x9262, + 0x935C, 0xE847, 0x9360, 0xE848, 0x936C, 0x8C4C, 0x936E, 0xE84A, 0x9370, 0xFBDF, 0x9375, 0x8CAE, 0x937C, 0xE849, 0x937E, 0x8FDF, + 0x938C, 0x8A99, 0x9394, 0xE84F, 0x9396, 0x8DBD, 0x9397, 0x9199, 0x939A, 0x92C8, 0x93A4, 0xFBE1, 0x93A7, 0x8A5A, 0x93AC, 0xE84D, + 0x93AD, 0xE84E, 0x93AE, 0x92C1, 0x93B0, 0xE84C, 0x93B9, 0xE850, 0x93C3, 0xE856, 0x93C6, 0xFBE2, 0x93C8, 0xE859, 0x93D0, 0xE858, + 0x93D1, 0x934C, 0x93D6, 0xE851, 0x93D7, 0xE852, 0x93D8, 0xE855, 0x93DD, 0xE857, 0x93DE, 0xFBE3, 0x93E1, 0x8BBE, 0x93E4, 0xE85A, + 0x93E5, 0xE854, 0x93E8, 0xE853, 0x93F8, 0xFBE4, 0x9403, 0xE85E, 0x9407, 0xE85F, 0x9410, 0xE860, 0x9413, 0xE85D, 0x9414, 0xE85C, + 0x9418, 0x8FE0, 0x9419, 0x93A8, 0x941A, 0xE85B, 0x9421, 0xE864, 0x942B, 0xE862, 0x9431, 0xFBE5, 0x9435, 0xE863, 0x9436, 0xE861, + 0x9438, 0x91F6, 0x943A, 0xE865, 0x9441, 0xE866, 0x9444, 0xE868, 0x9445, 0xFBE6, 0x9448, 0xFBE7, 0x9451, 0x8AD3, 0x9452, 0xE867, + 0x9453, 0x96F8, 0x945A, 0xE873, 0x945B, 0xE869, 0x945E, 0xE86C, 0x9460, 0xE86A, 0x9462, 0xE86B, 0x946A, 0xE86D, 0x9470, 0xE86F, + 0x9475, 0xE870, 0x9477, 0xE871, 0x947C, 0xE874, 0x947D, 0xE872, 0x947E, 0xE875, 0x947F, 0xE877, 0x9481, 0xE876, 0x9577, 0x92B7, + 0x9580, 0x96E5, 0x9582, 0xE878, 0x9583, 0x914D, 0x9587, 0xE879, 0x9589, 0x95C2, 0x958A, 0xE87A, 0x958B, 0x8A4A, 0x958F, 0x895B, + 0x9591, 0x8AD5, 0x9592, 0xFBE8, 0x9593, 0x8AD4, 0x9594, 0xE87B, 0x9596, 0xE87C, 0x9598, 0xE87D, 0x9599, 0xE87E, 0x95A0, 0xE880, + 0x95A2, 0x8AD6, 0x95A3, 0x8A74, 0x95A4, 0x8D7D, 0x95A5, 0x94B4, 0x95A7, 0xE882, 0x95A8, 0xE881, 0x95AD, 0xE883, 0x95B2, 0x897B, + 0x95B9, 0xE886, 0x95BB, 0xE885, 0x95BC, 0xE884, 0x95BE, 0xE887, 0x95C3, 0xE88A, 0x95C7, 0x88C5, 0x95CA, 0xE888, 0x95CC, 0xE88C, + 0x95CD, 0xE88B, 0x95D4, 0xE88E, 0x95D5, 0xE88D, 0x95D6, 0xE88F, 0x95D8, 0x93AC, 0x95DC, 0xE890, 0x95E1, 0xE891, 0x95E2, 0xE893, + 0x95E5, 0xE892, 0x961C, 0x958C, 0x9621, 0xE894, 0x9628, 0xE895, 0x962A, 0x8DE3, 0x962E, 0xE896, 0x962F, 0xE897, 0x9632, 0x9668, + 0x963B, 0x916A, 0x963F, 0x88A2, 0x9640, 0x91C9, 0x9642, 0xE898, 0x9644, 0x958D, 0x964B, 0xE89B, 0x964C, 0xE899, 0x964D, 0x8D7E, + 0x964F, 0xE89A, 0x9650, 0x8CC0, 0x965B, 0x95C3, 0x965C, 0xE89D, 0x965D, 0xE89F, 0x965E, 0xE89E, 0x965F, 0xE8A0, 0x9662, 0x8940, + 0x9663, 0x9077, 0x9664, 0x8F9C, 0x9665, 0x8AD7, 0x9666, 0xE8A1, 0x966A, 0x9486, 0x966C, 0xE8A3, 0x9670, 0x8941, 0x9672, 0xE8A2, + 0x9673, 0x92C2, 0x9675, 0x97CB, 0x9676, 0x93A9, 0x9677, 0xE89C, 0x9678, 0x97A4, 0x967A, 0x8CAF, 0x967D, 0x977A, 0x9685, 0x8BF7, + 0x9686, 0x97B2, 0x9688, 0x8C47, 0x968A, 0x91E0, 0x968B, 0xE440, 0x968D, 0xE8A4, 0x968E, 0x8A4B, 0x968F, 0x908F, 0x9694, 0x8A75, + 0x9695, 0xE8A6, 0x9697, 0xE8A7, 0x9698, 0xE8A5, 0x9699, 0x8C84, 0x969B, 0x8DDB, 0x969C, 0x8FE1, 0x969D, 0xFBEB, 0x96A0, 0x8942, + 0x96A3, 0x97D7, 0x96A7, 0xE8A9, 0x96A8, 0xE7AC, 0x96AA, 0xE8A8, 0x96AF, 0xFBEC, 0x96B0, 0xE8AC, 0x96B1, 0xE8AA, 0x96B2, 0xE8AB, + 0x96B4, 0xE8AD, 0x96B6, 0xE8AE, 0x96B7, 0x97EA, 0x96B8, 0xE8AF, 0x96B9, 0xE8B0, 0x96BB, 0x90C7, 0x96BC, 0x94B9, 0x96C0, 0x909D, + 0x96C1, 0x8AE5, 0x96C4, 0x9759, 0x96C5, 0x89EB, 0x96C6, 0x8F57, 0x96C7, 0x8CD9, 0x96C9, 0xE8B3, 0x96CB, 0xE8B2, 0x96CC, 0x8E93, + 0x96CD, 0xE8B4, 0x96CE, 0xE8B1, 0x96D1, 0x8E47, 0x96D5, 0xE8B8, 0x96D6, 0xE5AB, 0x96D9, 0x99D4, 0x96DB, 0x9097, 0x96DC, 0xE8B6, + 0x96E2, 0x97A3, 0x96E3, 0x93EF, 0x96E8, 0x894A, 0x96EA, 0x90E1, 0x96EB, 0x8EB4, 0x96F0, 0x95B5, 0x96F2, 0x895F, 0x96F6, 0x97EB, + 0x96F7, 0x978B, 0x96F9, 0xE8B9, 0x96FB, 0x9364, 0x9700, 0x8EF9, 0x9704, 0xE8BA, 0x9706, 0xE8BB, 0x9707, 0x906B, 0x9708, 0xE8BC, + 0x970A, 0x97EC, 0x970D, 0xE8B7, 0x970E, 0xE8BE, 0x970F, 0xE8C0, 0x9711, 0xE8BF, 0x9713, 0xE8BD, 0x9716, 0xE8C1, 0x9719, 0xE8C2, + 0x971C, 0x919A, 0x971E, 0x89E0, 0x9724, 0xE8C3, 0x9727, 0x96B6, 0x972A, 0xE8C4, 0x9730, 0xE8C5, 0x9732, 0x9849, 0x9733, 0xFBED, + 0x9738, 0x9E50, 0x9739, 0xE8C6, 0x973B, 0xFBEE, 0x973D, 0xE8C7, 0x973E, 0xE8C8, 0x9742, 0xE8CC, 0x9743, 0xFBEF, 0x9744, 0xE8C9, + 0x9746, 0xE8CA, 0x9748, 0xE8CB, 0x9749, 0xE8CD, 0x974D, 0xFBF0, 0x974F, 0xFBF1, 0x9751, 0xFBF2, 0x9752, 0x90C2, 0x9755, 0xFBF3, + 0x9756, 0x96F5, 0x9759, 0x90C3, 0x975C, 0xE8CE, 0x975E, 0x94F1, 0x9760, 0xE8CF, 0x9761, 0xEA72, 0x9762, 0x96CA, 0x9764, 0xE8D0, + 0x9766, 0xE8D1, 0x9768, 0xE8D2, 0x9769, 0x8A76, 0x976B, 0xE8D4, 0x976D, 0x9078, 0x9771, 0xE8D5, 0x9774, 0x8C43, 0x9779, 0xE8D6, + 0x977A, 0xE8DA, 0x977C, 0xE8D8, 0x9781, 0xE8D9, 0x9784, 0x8A93, 0x9785, 0xE8D7, 0x9786, 0xE8DB, 0x978B, 0xE8DC, 0x978D, 0x88C6, + 0x978F, 0xE8DD, 0x9790, 0xE8DE, 0x9798, 0x8FE2, 0x979C, 0xE8DF, 0x97A0, 0x8B66, 0x97A3, 0xE8E2, 0x97A6, 0xE8E1, 0x97A8, 0xE8E0, + 0x97AB, 0xE691, 0x97AD, 0x95DA, 0x97B3, 0xE8E3, 0x97B4, 0xE8E4, 0x97C3, 0xE8E5, 0x97C6, 0xE8E6, 0x97C8, 0xE8E7, 0x97CB, 0xE8E8, + 0x97D3, 0x8AD8, 0x97DC, 0xE8E9, 0x97ED, 0xE8EA, 0x97EE, 0x9442, 0x97F2, 0xE8EC, 0x97F3, 0x89B9, 0x97F5, 0xE8EF, 0x97F6, 0xE8EE, + 0x97FB, 0x8943, 0x97FF, 0x8BBF, 0x9801, 0x95C5, 0x9802, 0x92B8, 0x9803, 0x8DA0, 0x9805, 0x8D80, 0x9806, 0x8F87, 0x9808, 0x907B, + 0x980C, 0xE8F1, 0x980F, 0xE8F0, 0x9810, 0x9761, 0x9811, 0x8AE6, 0x9812, 0x94D0, 0x9813, 0x93DA, 0x9817, 0x909C, 0x9818, 0x97CC, + 0x981A, 0x8C7A, 0x9821, 0xE8F4, 0x9824, 0xE8F3, 0x982C, 0x966A, 0x982D, 0x93AA, 0x9834, 0x896F, 0x9837, 0xE8F5, 0x9838, 0xE8F2, + 0x983B, 0x9570, 0x983C, 0x978A, 0x983D, 0xE8F6, 0x9846, 0xE8F7, 0x984B, 0xE8F9, 0x984C, 0x91E8, 0x984D, 0x8A7A, 0x984E, 0x8A7B, + 0x984F, 0xE8F8, 0x9854, 0x8AE7, 0x9855, 0x8CB0, 0x9857, 0xFBF4, 0x9858, 0x8AE8, 0x985B, 0x935E, 0x985E, 0x97DE, 0x9865, 0xFBF5, + 0x9867, 0x8CDA, 0x986B, 0xE8FA, 0x986F, 0xE8FB, 0x9870, 0xE8FC, 0x9871, 0xE940, 0x9873, 0xE942, 0x9874, 0xE941, 0x98A8, 0x9597, + 0x98AA, 0xE943, 0x98AF, 0xE944, 0x98B1, 0xE945, 0x98B6, 0xE946, 0x98C3, 0xE948, 0x98C4, 0xE947, 0x98C6, 0xE949, 0x98DB, 0x94F2, + 0x98DC, 0xE3CA, 0x98DF, 0x9048, 0x98E2, 0x8B51, 0x98E9, 0xE94A, 0x98EB, 0xE94B, 0x98ED, 0x99AA, 0x98EE, 0x9F5A, 0x98EF, 0x94D1, + 0x98F2, 0x88F9, 0x98F4, 0x88B9, 0x98FC, 0x8E94, 0x98FD, 0x964F, 0x98FE, 0x8FFC, 0x9903, 0xE94C, 0x9905, 0x96DD, 0x9909, 0xE94D, + 0x990A, 0x977B, 0x990C, 0x8961, 0x9910, 0x8E60, 0x9912, 0xE94E, 0x9913, 0x89EC, 0x9914, 0xE94F, 0x9918, 0xE950, 0x991D, 0xE952, + 0x991E, 0xE953, 0x9920, 0xE955, 0x9921, 0xE951, 0x9924, 0xE954, 0x9927, 0xFBF8, 0x9928, 0x8AD9, 0x992C, 0xE956, 0x992E, 0xE957, + 0x993D, 0xE958, 0x993E, 0xE959, 0x9942, 0xE95A, 0x9945, 0xE95C, 0x9949, 0xE95B, 0x994B, 0xE95E, 0x994C, 0xE961, 0x9950, 0xE95D, + 0x9951, 0xE95F, 0x9952, 0xE960, 0x9955, 0xE962, 0x9957, 0x8BC0, 0x9996, 0x8EF1, 0x9997, 0xE963, 0x9998, 0xE964, 0x9999, 0x8D81, + 0x999E, 0xFBFA, 0x99A5, 0xE965, 0x99A8, 0x8A5D, 0x99AC, 0x946E, 0x99AD, 0xE966, 0x99AE, 0xE967, 0x99B3, 0x9279, 0x99B4, 0x93E9, + 0x99BC, 0xE968, 0x99C1, 0x949D, 0x99C4, 0x91CA, 0x99C5, 0x8977, 0x99C6, 0x8BEC, 0x99C8, 0x8BED, 0x99D0, 0x9293, 0x99D1, 0xE96D, + 0x99D2, 0x8BEE, 0x99D5, 0x89ED, 0x99D8, 0xE96C, 0x99DB, 0xE96A, 0x99DD, 0xE96B, 0x99DF, 0xE969, 0x99E2, 0xE977, 0x99ED, 0xE96E, + 0x99EE, 0xE96F, 0x99F1, 0xE970, 0x99F2, 0xE971, 0x99F8, 0xE973, 0x99FB, 0xE972, 0x99FF, 0x8F78, 0x9A01, 0xE974, 0x9A05, 0xE976, + 0x9A0E, 0x8B52, 0x9A0F, 0xE975, 0x9A12, 0x919B, 0x9A13, 0x8CB1, 0x9A19, 0xE978, 0x9A28, 0x91CB, 0x9A2B, 0xE979, 0x9A30, 0x93AB, + 0x9A37, 0xE97A, 0x9A3E, 0xE980, 0x9A40, 0xE97D, 0x9A42, 0xE97C, 0x9A43, 0xE97E, 0x9A45, 0xE97B, 0x9A4D, 0xE982, 0x9A4E, 0xFBFB, + 0x9A55, 0xE981, 0x9A57, 0xE984, 0x9A5A, 0x8BC1, 0x9A5B, 0xE983, 0x9A5F, 0xE985, 0x9A62, 0xE986, 0x9A64, 0xE988, 0x9A65, 0xE987, + 0x9A69, 0xE989, 0x9A6A, 0xE98B, 0x9A6B, 0xE98A, 0x9AA8, 0x8D9C, 0x9AAD, 0xE98C, 0x9AB0, 0xE98D, 0x9AB8, 0x8A5B, 0x9ABC, 0xE98E, + 0x9AC0, 0xE98F, 0x9AC4, 0x9091, 0x9ACF, 0xE990, 0x9AD1, 0xE991, 0x9AD3, 0xE992, 0x9AD4, 0xE993, 0x9AD8, 0x8D82, 0x9AD9, 0xFBFC, + 0x9ADC, 0xFC40, 0x9ADE, 0xE994, 0x9ADF, 0xE995, 0x9AE2, 0xE996, 0x9AE3, 0xE997, 0x9AE6, 0xE998, 0x9AEA, 0x94AF, 0x9AEB, 0xE99A, + 0x9AED, 0x9545, 0x9AEE, 0xE99B, 0x9AEF, 0xE999, 0x9AF1, 0xE99D, 0x9AF4, 0xE99C, 0x9AF7, 0xE99E, 0x9AFB, 0xE99F, 0x9B06, 0xE9A0, + 0x9B18, 0xE9A1, 0x9B1A, 0xE9A2, 0x9B1F, 0xE9A3, 0x9B22, 0xE9A4, 0x9B23, 0xE9A5, 0x9B25, 0xE9A6, 0x9B27, 0xE9A7, 0x9B28, 0xE9A8, + 0x9B29, 0xE9A9, 0x9B2A, 0xE9AA, 0x9B2E, 0xE9AB, 0x9B2F, 0xE9AC, 0x9B31, 0x9F54, 0x9B32, 0xE9AD, 0x9B3B, 0xE2F6, 0x9B3C, 0x8B53, + 0x9B41, 0x8A40, 0x9B42, 0x8DB0, 0x9B43, 0xE9AF, 0x9B44, 0xE9AE, 0x9B45, 0x96A3, 0x9B4D, 0xE9B1, 0x9B4E, 0xE9B2, 0x9B4F, 0xE9B0, + 0x9B51, 0xE9B3, 0x9B54, 0x9682, 0x9B58, 0xE9B4, 0x9B5A, 0x8B9B, 0x9B6F, 0x9844, 0x9B72, 0xFC42, 0x9B74, 0xE9B5, 0x9B75, 0xFC41, + 0x9B83, 0xE9B7, 0x9B8E, 0x88BC, 0x9B8F, 0xFC43, 0x9B91, 0xE9B8, 0x9B92, 0x95A9, 0x9B93, 0xE9B6, 0x9B96, 0xE9B9, 0x9B97, 0xE9BA, + 0x9B9F, 0xE9BB, 0x9BA0, 0xE9BC, 0x9BA8, 0xE9BD, 0x9BAA, 0x968E, 0x9BAB, 0x8E4C, 0x9BAD, 0x8DF8, 0x9BAE, 0x914E, 0x9BB1, 0xFC44, + 0x9BB4, 0xE9BE, 0x9BB9, 0xE9C1, 0x9BBB, 0xFC45, 0x9BC0, 0xE9BF, 0x9BC6, 0xE9C2, 0x9BC9, 0x8CEF, 0x9BCA, 0xE9C0, 0x9BCF, 0xE9C3, + 0x9BD1, 0xE9C4, 0x9BD2, 0xE9C5, 0x9BD4, 0xE9C9, 0x9BD6, 0x8E49, 0x9BDB, 0x91E2, 0x9BE1, 0xE9CA, 0x9BE2, 0xE9C7, 0x9BE3, 0xE9C6, + 0x9BE4, 0xE9C8, 0x9BE8, 0x8C7E, 0x9BF0, 0xE9CE, 0x9BF1, 0xE9CD, 0x9BF2, 0xE9CC, 0x9BF5, 0x88B1, 0x9C00, 0xFC46, 0x9C04, 0xE9D8, + 0x9C06, 0xE9D4, 0x9C08, 0xE9D5, 0x9C09, 0xE9D1, 0x9C0A, 0xE9D7, 0x9C0C, 0xE9D3, 0x9C0D, 0x8A82, 0x9C10, 0x986B, 0x9C12, 0xE9D6, + 0x9C13, 0xE9D2, 0x9C14, 0xE9D0, 0x9C15, 0xE9CF, 0x9C1B, 0xE9DA, 0x9C21, 0xE9DD, 0x9C24, 0xE9DC, 0x9C25, 0xE9DB, 0x9C2D, 0x9568, + 0x9C2E, 0xE9D9, 0x9C2F, 0x88F1, 0x9C30, 0xE9DE, 0x9C32, 0xE9E0, 0x9C39, 0x8A8F, 0x9C3A, 0xE9CB, 0x9C3B, 0x8956, 0x9C3E, 0xE9E2, + 0x9C46, 0xE9E1, 0x9C47, 0xE9DF, 0x9C48, 0x924C, 0x9C52, 0x9690, 0x9C57, 0x97D8, 0x9C5A, 0xE9E3, 0x9C60, 0xE9E4, 0x9C67, 0xE9E5, + 0x9C76, 0xE9E6, 0x9C78, 0xE9E7, 0x9CE5, 0x92B9, 0x9CE7, 0xE9E8, 0x9CE9, 0x94B5, 0x9CEB, 0xE9ED, 0x9CEC, 0xE9E9, 0x9CF0, 0xE9EA, + 0x9CF3, 0x9650, 0x9CF4, 0x96C2, 0x9CF6, 0x93CE, 0x9D03, 0xE9EE, 0x9D06, 0xE9EF, 0x9D07, 0x93BC, 0x9D08, 0xE9EC, 0x9D09, 0xE9EB, + 0x9D0E, 0x89A8, 0x9D12, 0xE9F7, 0x9D15, 0xE9F6, 0x9D1B, 0x8995, 0x9D1F, 0xE9F4, 0x9D23, 0xE9F3, 0x9D26, 0xE9F1, 0x9D28, 0x8A9B, + 0x9D2A, 0xE9F0, 0x9D2B, 0x8EB0, 0x9D2C, 0x89A7, 0x9D3B, 0x8D83, 0x9D3E, 0xE9FA, 0x9D3F, 0xE9F9, 0x9D41, 0xE9F8, 0x9D44, 0xE9F5, + 0x9D46, 0xE9FB, 0x9D48, 0xE9FC, 0x9D50, 0xEA44, 0x9D51, 0xEA43, 0x9D59, 0xEA45, 0x9D5C, 0x894C, 0x9D5D, 0xEA40, 0x9D5E, 0xEA41, + 0x9D60, 0x8D94, 0x9D61, 0x96B7, 0x9D64, 0xEA42, 0x9D6B, 0xFC48, 0x9D6C, 0x9651, 0x9D6F, 0xEA4A, 0x9D70, 0xFC47, 0x9D72, 0xEA46, + 0x9D7A, 0xEA4B, 0x9D87, 0xEA48, 0x9D89, 0xEA47, 0x9D8F, 0x8C7B, 0x9D9A, 0xEA4C, 0x9DA4, 0xEA4D, 0x9DA9, 0xEA4E, 0x9DAB, 0xEA49, + 0x9DAF, 0xE9F2, 0x9DB2, 0xEA4F, 0x9DB4, 0x92DF, 0x9DB8, 0xEA53, 0x9DBA, 0xEA54, 0x9DBB, 0xEA52, 0x9DC1, 0xEA51, 0x9DC2, 0xEA57, + 0x9DC4, 0xEA50, 0x9DC6, 0xEA55, 0x9DCF, 0xEA56, 0x9DD3, 0xEA59, 0x9DD9, 0xEA58, 0x9DE6, 0xEA5B, 0x9DED, 0xEA5C, 0x9DEF, 0xEA5D, + 0x9DF2, 0x9868, 0x9DF8, 0xEA5A, 0x9DF9, 0x91E9, 0x9DFA, 0x8DEB, 0x9DFD, 0xEA5E, 0x9E19, 0xFC4A, 0x9E1A, 0xEA5F, 0x9E1B, 0xEA60, + 0x9E1E, 0xEA61, 0x9E75, 0xEA62, 0x9E78, 0x8CB2, 0x9E79, 0xEA63, 0x9E7D, 0xEA64, 0x9E7F, 0x8EAD, 0x9E81, 0xEA65, 0x9E88, 0xEA66, + 0x9E8B, 0xEA67, 0x9E8C, 0xEA68, 0x9E91, 0xEA6B, 0x9E92, 0xEA69, 0x9E93, 0x985B, 0x9E95, 0xEA6A, 0x9E97, 0x97ED, 0x9E9D, 0xEA6C, + 0x9E9F, 0x97D9, 0x9EA5, 0xEA6D, 0x9EA6, 0x949E, 0x9EA9, 0xEA6E, 0x9EAA, 0xEA70, 0x9EAD, 0xEA71, 0x9EB8, 0xEA6F, 0x9EB9, 0x8D8D, + 0x9EBA, 0x96CB, 0x9EBB, 0x9683, 0x9EBC, 0x9BF5, 0x9EBE, 0x9F80, 0x9EBF, 0x969B, 0x9EC4, 0x89A9, 0x9ECC, 0xEA73, 0x9ECD, 0x8B6F, + 0x9ECE, 0xEA74, 0x9ECF, 0xEA75, 0x9ED0, 0xEA76, 0x9ED1, 0xFC4B, 0x9ED2, 0x8D95, 0x9ED4, 0xEA77, 0x9ED8, 0xE0D2, 0x9ED9, 0x96D9, + 0x9EDB, 0x91E1, 0x9EDC, 0xEA78, 0x9EDD, 0xEA7A, 0x9EDE, 0xEA79, 0x9EE0, 0xEA7B, 0x9EE5, 0xEA7C, 0x9EE8, 0xEA7D, 0x9EEF, 0xEA7E, + 0x9EF4, 0xEA80, 0x9EF6, 0xEA81, 0x9EF7, 0xEA82, 0x9EF9, 0xEA83, 0x9EFB, 0xEA84, 0x9EFC, 0xEA85, 0x9EFD, 0xEA86, 0x9F07, 0xEA87, + 0x9F08, 0xEA88, 0x9F0E, 0x9343, 0x9F13, 0x8CDB, 0x9F15, 0xEA8A, 0x9F20, 0x916C, 0x9F21, 0xEA8B, 0x9F2C, 0xEA8C, 0x9F3B, 0x9540, + 0x9F3E, 0xEA8D, 0x9F4A, 0xEA8E, 0x9F4B, 0xE256, 0x9F4E, 0xE6D8, 0x9F4F, 0xE8EB, 0x9F52, 0xEA8F, 0x9F54, 0xEA90, 0x9F5F, 0xEA92, + 0x9F60, 0xEA93, 0x9F61, 0xEA94, 0x9F62, 0x97EE, 0x9F63, 0xEA91, 0x9F66, 0xEA95, 0x9F67, 0xEA96, 0x9F6A, 0xEA98, 0x9F6C, 0xEA97, + 0x9F72, 0xEA9A, 0x9F76, 0xEA9B, 0x9F77, 0xEA99, 0x9F8D, 0x97B4, 0x9F95, 0xEA9C, 0x9F9C, 0xEA9D, 0x9F9D, 0xE273, 0x9FA0, 0xEA9E, + 0xF929, 0xFAE0, 0xF9DC, 0xFBE9, 0xFA0E, 0xFA90, 0xFA0F, 0xFA9B, 0xFA10, 0xFA9C, 0xFA11, 0xFAB1, 0xFA12, 0xFAD8, 0xFA13, 0xFAE8, + 0xFA14, 0xFAEA, 0xFA15, 0xFB58, 0xFA16, 0xFB5E, 0xFA17, 0xFB75, 0xFA18, 0xFB7D, 0xFA19, 0xFB7E, 0xFA1A, 0xFB80, 0xFA1B, 0xFB82, + 0xFA1C, 0xFB86, 0xFA1D, 0xFB89, 0xFA1E, 0xFB92, 0xFA1F, 0xFB9D, 0xFA20, 0xFB9F, 0xFA21, 0xFBA0, 0xFA22, 0xFBA9, 0xFA23, 0xFBB1, + 0xFA24, 0xFBB3, 0xFA25, 0xFBB4, 0xFA26, 0xFBB7, 0xFA27, 0xFBD3, 0xFA28, 0xFBDA, 0xFA29, 0xFBEA, 0xFA2A, 0xFBF6, 0xFA2B, 0xFBF7, + 0xFA2C, 0xFBF9, 0xFA2D, 0xFC49, 0xFF01, 0x8149, 0xFF02, 0xFA57, 0xFF03, 0x8194, 0xFF04, 0x8190, 0xFF05, 0x8193, 0xFF06, 0x8195, + 0xFF07, 0xFA56, 0xFF08, 0x8169, 0xFF09, 0x816A, 0xFF0A, 0x8196, 0xFF0B, 0x817B, 0xFF0C, 0x8143, 0xFF0D, 0x817C, 0xFF0E, 0x8144, + 0xFF0F, 0x815E, 0xFF10, 0x824F, 0xFF11, 0x8250, 0xFF12, 0x8251, 0xFF13, 0x8252, 0xFF14, 0x8253, 0xFF15, 0x8254, 0xFF16, 0x8255, + 0xFF17, 0x8256, 0xFF18, 0x8257, 0xFF19, 0x8258, 0xFF1A, 0x8146, 0xFF1B, 0x8147, 0xFF1C, 0x8183, 0xFF1D, 0x8181, 0xFF1E, 0x8184, + 0xFF1F, 0x8148, 0xFF20, 0x8197, 0xFF21, 0x8260, 0xFF22, 0x8261, 0xFF23, 0x8262, 0xFF24, 0x8263, 0xFF25, 0x8264, 0xFF26, 0x8265, + 0xFF27, 0x8266, 0xFF28, 0x8267, 0xFF29, 0x8268, 0xFF2A, 0x8269, 0xFF2B, 0x826A, 0xFF2C, 0x826B, 0xFF2D, 0x826C, 0xFF2E, 0x826D, + 0xFF2F, 0x826E, 0xFF30, 0x826F, 0xFF31, 0x8270, 0xFF32, 0x8271, 0xFF33, 0x8272, 0xFF34, 0x8273, 0xFF35, 0x8274, 0xFF36, 0x8275, + 0xFF37, 0x8276, 0xFF38, 0x8277, 0xFF39, 0x8278, 0xFF3A, 0x8279, 0xFF3B, 0x816D, 0xFF3C, 0x815F, 0xFF3D, 0x816E, 0xFF3E, 0x814F, + 0xFF3F, 0x8151, 0xFF40, 0x814D, 0xFF41, 0x8281, 0xFF42, 0x8282, 0xFF43, 0x8283, 0xFF44, 0x8284, 0xFF45, 0x8285, 0xFF46, 0x8286, + 0xFF47, 0x8287, 0xFF48, 0x8288, 0xFF49, 0x8289, 0xFF4A, 0x828A, 0xFF4B, 0x828B, 0xFF4C, 0x828C, 0xFF4D, 0x828D, 0xFF4E, 0x828E, + 0xFF4F, 0x828F, 0xFF50, 0x8290, 0xFF51, 0x8291, 0xFF52, 0x8292, 0xFF53, 0x8293, 0xFF54, 0x8294, 0xFF55, 0x8295, 0xFF56, 0x8296, + 0xFF57, 0x8297, 0xFF58, 0x8298, 0xFF59, 0x8299, 0xFF5A, 0x829A, 0xFF5B, 0x816F, 0xFF5C, 0x8162, 0xFF5D, 0x8170, 0xFF5E, 0x8160, + 0xFF61, 0x00A1, 0xFF62, 0x00A2, 0xFF63, 0x00A3, 0xFF64, 0x00A4, 0xFF65, 0x00A5, 0xFF66, 0x00A6, 0xFF67, 0x00A7, 0xFF68, 0x00A8, + 0xFF69, 0x00A9, 0xFF6A, 0x00AA, 0xFF6B, 0x00AB, 0xFF6C, 0x00AC, 0xFF6D, 0x00AD, 0xFF6E, 0x00AE, 0xFF6F, 0x00AF, 0xFF70, 0x00B0, + 0xFF71, 0x00B1, 0xFF72, 0x00B2, 0xFF73, 0x00B3, 0xFF74, 0x00B4, 0xFF75, 0x00B5, 0xFF76, 0x00B6, 0xFF77, 0x00B7, 0xFF78, 0x00B8, + 0xFF79, 0x00B9, 0xFF7A, 0x00BA, 0xFF7B, 0x00BB, 0xFF7C, 0x00BC, 0xFF7D, 0x00BD, 0xFF7E, 0x00BE, 0xFF7F, 0x00BF, 0xFF80, 0x00C0, + 0xFF81, 0x00C1, 0xFF82, 0x00C2, 0xFF83, 0x00C3, 0xFF84, 0x00C4, 0xFF85, 0x00C5, 0xFF86, 0x00C6, 0xFF87, 0x00C7, 0xFF88, 0x00C8, + 0xFF89, 0x00C9, 0xFF8A, 0x00CA, 0xFF8B, 0x00CB, 0xFF8C, 0x00CC, 0xFF8D, 0x00CD, 0xFF8E, 0x00CE, 0xFF8F, 0x00CF, 0xFF90, 0x00D0, + 0xFF91, 0x00D1, 0xFF92, 0x00D2, 0xFF93, 0x00D3, 0xFF94, 0x00D4, 0xFF95, 0x00D5, 0xFF96, 0x00D6, 0xFF97, 0x00D7, 0xFF98, 0x00D8, + 0xFF99, 0x00D9, 0xFF9A, 0x00DA, 0xFF9B, 0x00DB, 0xFF9C, 0x00DC, 0xFF9D, 0x00DD, 0xFF9E, 0x00DE, 0xFF9F, 0x00DF, 0xFFE0, 0x8191, + 0xFFE1, 0x8192, 0xFFE2, 0x81CA, 0xFFE3, 0x8150, 0xFFE4, 0xFA55, 0xFFE5, 0x818F, 0, 0 +}; + +static const WCHAR oem2uni932[] = { /* Shift_JIS --> Unicode pairs */ + 0x00A1, 0xFF61, 0x00A2, 0xFF62, 0x00A3, 0xFF63, 0x00A4, 0xFF64, 0x00A5, 0xFF65, 0x00A6, 0xFF66, 0x00A7, 0xFF67, 0x00A8, 0xFF68, + 0x00A9, 0xFF69, 0x00AA, 0xFF6A, 0x00AB, 0xFF6B, 0x00AC, 0xFF6C, 0x00AD, 0xFF6D, 0x00AE, 0xFF6E, 0x00AF, 0xFF6F, 0x00B0, 0xFF70, + 0x00B1, 0xFF71, 0x00B2, 0xFF72, 0x00B3, 0xFF73, 0x00B4, 0xFF74, 0x00B5, 0xFF75, 0x00B6, 0xFF76, 0x00B7, 0xFF77, 0x00B8, 0xFF78, + 0x00B9, 0xFF79, 0x00BA, 0xFF7A, 0x00BB, 0xFF7B, 0x00BC, 0xFF7C, 0x00BD, 0xFF7D, 0x00BE, 0xFF7E, 0x00BF, 0xFF7F, 0x00C0, 0xFF80, + 0x00C1, 0xFF81, 0x00C2, 0xFF82, 0x00C3, 0xFF83, 0x00C4, 0xFF84, 0x00C5, 0xFF85, 0x00C6, 0xFF86, 0x00C7, 0xFF87, 0x00C8, 0xFF88, + 0x00C9, 0xFF89, 0x00CA, 0xFF8A, 0x00CB, 0xFF8B, 0x00CC, 0xFF8C, 0x00CD, 0xFF8D, 0x00CE, 0xFF8E, 0x00CF, 0xFF8F, 0x00D0, 0xFF90, + 0x00D1, 0xFF91, 0x00D2, 0xFF92, 0x00D3, 0xFF93, 0x00D4, 0xFF94, 0x00D5, 0xFF95, 0x00D6, 0xFF96, 0x00D7, 0xFF97, 0x00D8, 0xFF98, + 0x00D9, 0xFF99, 0x00DA, 0xFF9A, 0x00DB, 0xFF9B, 0x00DC, 0xFF9C, 0x00DD, 0xFF9D, 0x00DE, 0xFF9E, 0x00DF, 0xFF9F, 0x8140, 0x3000, + 0x8141, 0x3001, 0x8142, 0x3002, 0x8143, 0xFF0C, 0x8144, 0xFF0E, 0x8145, 0x30FB, 0x8146, 0xFF1A, 0x8147, 0xFF1B, 0x8148, 0xFF1F, + 0x8149, 0xFF01, 0x814A, 0x309B, 0x814B, 0x309C, 0x814C, 0x00B4, 0x814D, 0xFF40, 0x814E, 0x00A8, 0x814F, 0xFF3E, 0x8150, 0xFFE3, + 0x8151, 0xFF3F, 0x8152, 0x30FD, 0x8153, 0x30FE, 0x8154, 0x309D, 0x8155, 0x309E, 0x8156, 0x3003, 0x8157, 0x4EDD, 0x8158, 0x3005, + 0x8159, 0x3006, 0x815A, 0x3007, 0x815B, 0x30FC, 0x815C, 0x2015, 0x815D, 0x2010, 0x815E, 0xFF0F, 0x815F, 0xFF3C, 0x8160, 0xFF5E, + 0x8161, 0x2225, 0x8162, 0xFF5C, 0x8163, 0x2026, 0x8164, 0x2025, 0x8165, 0x2018, 0x8166, 0x2019, 0x8167, 0x201C, 0x8168, 0x201D, + 0x8169, 0xFF08, 0x816A, 0xFF09, 0x816B, 0x3014, 0x816C, 0x3015, 0x816D, 0xFF3B, 0x816E, 0xFF3D, 0x816F, 0xFF5B, 0x8170, 0xFF5D, + 0x8171, 0x3008, 0x8172, 0x3009, 0x8173, 0x300A, 0x8174, 0x300B, 0x8175, 0x300C, 0x8176, 0x300D, 0x8177, 0x300E, 0x8178, 0x300F, + 0x8179, 0x3010, 0x817A, 0x3011, 0x817B, 0xFF0B, 0x817C, 0xFF0D, 0x817D, 0x00B1, 0x817E, 0x00D7, 0x8180, 0x00F7, 0x8181, 0xFF1D, + 0x8182, 0x2260, 0x8183, 0xFF1C, 0x8184, 0xFF1E, 0x8185, 0x2266, 0x8186, 0x2267, 0x8187, 0x221E, 0x8188, 0x2234, 0x8189, 0x2642, + 0x818A, 0x2640, 0x818B, 0x00B0, 0x818C, 0x2032, 0x818D, 0x2033, 0x818E, 0x2103, 0x818F, 0xFFE5, 0x8190, 0xFF04, 0x8191, 0xFFE0, + 0x8192, 0xFFE1, 0x8193, 0xFF05, 0x8194, 0xFF03, 0x8195, 0xFF06, 0x8196, 0xFF0A, 0x8197, 0xFF20, 0x8198, 0x00A7, 0x8199, 0x2606, + 0x819A, 0x2605, 0x819B, 0x25CB, 0x819C, 0x25CF, 0x819D, 0x25CE, 0x819E, 0x25C7, 0x819F, 0x25C6, 0x81A0, 0x25A1, 0x81A1, 0x25A0, + 0x81A2, 0x25B3, 0x81A3, 0x25B2, 0x81A4, 0x25BD, 0x81A5, 0x25BC, 0x81A6, 0x203B, 0x81A7, 0x3012, 0x81A8, 0x2192, 0x81A9, 0x2190, + 0x81AA, 0x2191, 0x81AB, 0x2193, 0x81AC, 0x3013, 0x81B8, 0x2208, 0x81B9, 0x220B, 0x81BA, 0x2286, 0x81BB, 0x2287, 0x81BC, 0x2282, + 0x81BD, 0x2283, 0x81BE, 0x222A, 0x81BF, 0x2229, 0x81C8, 0x2227, 0x81C9, 0x2228, 0x81CA, 0xFFE2, 0x81CB, 0x21D2, 0x81CC, 0x21D4, + 0x81CD, 0x2200, 0x81CE, 0x2203, 0x81DA, 0x2220, 0x81DB, 0x22A5, 0x81DC, 0x2312, 0x81DD, 0x2202, 0x81DE, 0x2207, 0x81DF, 0x2261, + 0x81E0, 0x2252, 0x81E1, 0x226A, 0x81E2, 0x226B, 0x81E3, 0x221A, 0x81E4, 0x223D, 0x81E5, 0x221D, 0x81E6, 0x2235, 0x81E7, 0x222B, + 0x81E8, 0x222C, 0x81F0, 0x212B, 0x81F1, 0x2030, 0x81F2, 0x266F, 0x81F3, 0x266D, 0x81F4, 0x266A, 0x81F5, 0x2020, 0x81F6, 0x2021, + 0x81F7, 0x00B6, 0x81FC, 0x25EF, 0x824F, 0xFF10, 0x8250, 0xFF11, 0x8251, 0xFF12, 0x8252, 0xFF13, 0x8253, 0xFF14, 0x8254, 0xFF15, + 0x8255, 0xFF16, 0x8256, 0xFF17, 0x8257, 0xFF18, 0x8258, 0xFF19, 0x8260, 0xFF21, 0x8261, 0xFF22, 0x8262, 0xFF23, 0x8263, 0xFF24, + 0x8264, 0xFF25, 0x8265, 0xFF26, 0x8266, 0xFF27, 0x8267, 0xFF28, 0x8268, 0xFF29, 0x8269, 0xFF2A, 0x826A, 0xFF2B, 0x826B, 0xFF2C, + 0x826C, 0xFF2D, 0x826D, 0xFF2E, 0x826E, 0xFF2F, 0x826F, 0xFF30, 0x8270, 0xFF31, 0x8271, 0xFF32, 0x8272, 0xFF33, 0x8273, 0xFF34, + 0x8274, 0xFF35, 0x8275, 0xFF36, 0x8276, 0xFF37, 0x8277, 0xFF38, 0x8278, 0xFF39, 0x8279, 0xFF3A, 0x8281, 0xFF41, 0x8282, 0xFF42, + 0x8283, 0xFF43, 0x8284, 0xFF44, 0x8285, 0xFF45, 0x8286, 0xFF46, 0x8287, 0xFF47, 0x8288, 0xFF48, 0x8289, 0xFF49, 0x828A, 0xFF4A, + 0x828B, 0xFF4B, 0x828C, 0xFF4C, 0x828D, 0xFF4D, 0x828E, 0xFF4E, 0x828F, 0xFF4F, 0x8290, 0xFF50, 0x8291, 0xFF51, 0x8292, 0xFF52, + 0x8293, 0xFF53, 0x8294, 0xFF54, 0x8295, 0xFF55, 0x8296, 0xFF56, 0x8297, 0xFF57, 0x8298, 0xFF58, 0x8299, 0xFF59, 0x829A, 0xFF5A, + 0x829F, 0x3041, 0x82A0, 0x3042, 0x82A1, 0x3043, 0x82A2, 0x3044, 0x82A3, 0x3045, 0x82A4, 0x3046, 0x82A5, 0x3047, 0x82A6, 0x3048, + 0x82A7, 0x3049, 0x82A8, 0x304A, 0x82A9, 0x304B, 0x82AA, 0x304C, 0x82AB, 0x304D, 0x82AC, 0x304E, 0x82AD, 0x304F, 0x82AE, 0x3050, + 0x82AF, 0x3051, 0x82B0, 0x3052, 0x82B1, 0x3053, 0x82B2, 0x3054, 0x82B3, 0x3055, 0x82B4, 0x3056, 0x82B5, 0x3057, 0x82B6, 0x3058, + 0x82B7, 0x3059, 0x82B8, 0x305A, 0x82B9, 0x305B, 0x82BA, 0x305C, 0x82BB, 0x305D, 0x82BC, 0x305E, 0x82BD, 0x305F, 0x82BE, 0x3060, + 0x82BF, 0x3061, 0x82C0, 0x3062, 0x82C1, 0x3063, 0x82C2, 0x3064, 0x82C3, 0x3065, 0x82C4, 0x3066, 0x82C5, 0x3067, 0x82C6, 0x3068, + 0x82C7, 0x3069, 0x82C8, 0x306A, 0x82C9, 0x306B, 0x82CA, 0x306C, 0x82CB, 0x306D, 0x82CC, 0x306E, 0x82CD, 0x306F, 0x82CE, 0x3070, + 0x82CF, 0x3071, 0x82D0, 0x3072, 0x82D1, 0x3073, 0x82D2, 0x3074, 0x82D3, 0x3075, 0x82D4, 0x3076, 0x82D5, 0x3077, 0x82D6, 0x3078, + 0x82D7, 0x3079, 0x82D8, 0x307A, 0x82D9, 0x307B, 0x82DA, 0x307C, 0x82DB, 0x307D, 0x82DC, 0x307E, 0x82DD, 0x307F, 0x82DE, 0x3080, + 0x82DF, 0x3081, 0x82E0, 0x3082, 0x82E1, 0x3083, 0x82E2, 0x3084, 0x82E3, 0x3085, 0x82E4, 0x3086, 0x82E5, 0x3087, 0x82E6, 0x3088, + 0x82E7, 0x3089, 0x82E8, 0x308A, 0x82E9, 0x308B, 0x82EA, 0x308C, 0x82EB, 0x308D, 0x82EC, 0x308E, 0x82ED, 0x308F, 0x82EE, 0x3090, + 0x82EF, 0x3091, 0x82F0, 0x3092, 0x82F1, 0x3093, 0x8340, 0x30A1, 0x8341, 0x30A2, 0x8342, 0x30A3, 0x8343, 0x30A4, 0x8344, 0x30A5, + 0x8345, 0x30A6, 0x8346, 0x30A7, 0x8347, 0x30A8, 0x8348, 0x30A9, 0x8349, 0x30AA, 0x834A, 0x30AB, 0x834B, 0x30AC, 0x834C, 0x30AD, + 0x834D, 0x30AE, 0x834E, 0x30AF, 0x834F, 0x30B0, 0x8350, 0x30B1, 0x8351, 0x30B2, 0x8352, 0x30B3, 0x8353, 0x30B4, 0x8354, 0x30B5, + 0x8355, 0x30B6, 0x8356, 0x30B7, 0x8357, 0x30B8, 0x8358, 0x30B9, 0x8359, 0x30BA, 0x835A, 0x30BB, 0x835B, 0x30BC, 0x835C, 0x30BD, + 0x835D, 0x30BE, 0x835E, 0x30BF, 0x835F, 0x30C0, 0x8360, 0x30C1, 0x8361, 0x30C2, 0x8362, 0x30C3, 0x8363, 0x30C4, 0x8364, 0x30C5, + 0x8365, 0x30C6, 0x8366, 0x30C7, 0x8367, 0x30C8, 0x8368, 0x30C9, 0x8369, 0x30CA, 0x836A, 0x30CB, 0x836B, 0x30CC, 0x836C, 0x30CD, + 0x836D, 0x30CE, 0x836E, 0x30CF, 0x836F, 0x30D0, 0x8370, 0x30D1, 0x8371, 0x30D2, 0x8372, 0x30D3, 0x8373, 0x30D4, 0x8374, 0x30D5, + 0x8375, 0x30D6, 0x8376, 0x30D7, 0x8377, 0x30D8, 0x8378, 0x30D9, 0x8379, 0x30DA, 0x837A, 0x30DB, 0x837B, 0x30DC, 0x837C, 0x30DD, + 0x837D, 0x30DE, 0x837E, 0x30DF, 0x8380, 0x30E0, 0x8381, 0x30E1, 0x8382, 0x30E2, 0x8383, 0x30E3, 0x8384, 0x30E4, 0x8385, 0x30E5, + 0x8386, 0x30E6, 0x8387, 0x30E7, 0x8388, 0x30E8, 0x8389, 0x30E9, 0x838A, 0x30EA, 0x838B, 0x30EB, 0x838C, 0x30EC, 0x838D, 0x30ED, + 0x838E, 0x30EE, 0x838F, 0x30EF, 0x8390, 0x30F0, 0x8391, 0x30F1, 0x8392, 0x30F2, 0x8393, 0x30F3, 0x8394, 0x30F4, 0x8395, 0x30F5, + 0x8396, 0x30F6, 0x839F, 0x0391, 0x83A0, 0x0392, 0x83A1, 0x0393, 0x83A2, 0x0394, 0x83A3, 0x0395, 0x83A4, 0x0396, 0x83A5, 0x0397, + 0x83A6, 0x0398, 0x83A7, 0x0399, 0x83A8, 0x039A, 0x83A9, 0x039B, 0x83AA, 0x039C, 0x83AB, 0x039D, 0x83AC, 0x039E, 0x83AD, 0x039F, + 0x83AE, 0x03A0, 0x83AF, 0x03A1, 0x83B0, 0x03A3, 0x83B1, 0x03A4, 0x83B2, 0x03A5, 0x83B3, 0x03A6, 0x83B4, 0x03A7, 0x83B5, 0x03A8, + 0x83B6, 0x03A9, 0x83BF, 0x03B1, 0x83C0, 0x03B2, 0x83C1, 0x03B3, 0x83C2, 0x03B4, 0x83C3, 0x03B5, 0x83C4, 0x03B6, 0x83C5, 0x03B7, + 0x83C6, 0x03B8, 0x83C7, 0x03B9, 0x83C8, 0x03BA, 0x83C9, 0x03BB, 0x83CA, 0x03BC, 0x83CB, 0x03BD, 0x83CC, 0x03BE, 0x83CD, 0x03BF, + 0x83CE, 0x03C0, 0x83CF, 0x03C1, 0x83D0, 0x03C3, 0x83D1, 0x03C4, 0x83D2, 0x03C5, 0x83D3, 0x03C6, 0x83D4, 0x03C7, 0x83D5, 0x03C8, + 0x83D6, 0x03C9, 0x8440, 0x0410, 0x8441, 0x0411, 0x8442, 0x0412, 0x8443, 0x0413, 0x8444, 0x0414, 0x8445, 0x0415, 0x8446, 0x0401, + 0x8447, 0x0416, 0x8448, 0x0417, 0x8449, 0x0418, 0x844A, 0x0419, 0x844B, 0x041A, 0x844C, 0x041B, 0x844D, 0x041C, 0x844E, 0x041D, + 0x844F, 0x041E, 0x8450, 0x041F, 0x8451, 0x0420, 0x8452, 0x0421, 0x8453, 0x0422, 0x8454, 0x0423, 0x8455, 0x0424, 0x8456, 0x0425, + 0x8457, 0x0426, 0x8458, 0x0427, 0x8459, 0x0428, 0x845A, 0x0429, 0x845B, 0x042A, 0x845C, 0x042B, 0x845D, 0x042C, 0x845E, 0x042D, + 0x845F, 0x042E, 0x8460, 0x042F, 0x8470, 0x0430, 0x8471, 0x0431, 0x8472, 0x0432, 0x8473, 0x0433, 0x8474, 0x0434, 0x8475, 0x0435, + 0x8476, 0x0451, 0x8477, 0x0436, 0x8478, 0x0437, 0x8479, 0x0438, 0x847A, 0x0439, 0x847B, 0x043A, 0x847C, 0x043B, 0x847D, 0x043C, + 0x847E, 0x043D, 0x8480, 0x043E, 0x8481, 0x043F, 0x8482, 0x0440, 0x8483, 0x0441, 0x8484, 0x0442, 0x8485, 0x0443, 0x8486, 0x0444, + 0x8487, 0x0445, 0x8488, 0x0446, 0x8489, 0x0447, 0x848A, 0x0448, 0x848B, 0x0449, 0x848C, 0x044A, 0x848D, 0x044B, 0x848E, 0x044C, + 0x848F, 0x044D, 0x8490, 0x044E, 0x8491, 0x044F, 0x849F, 0x2500, 0x84A0, 0x2502, 0x84A1, 0x250C, 0x84A2, 0x2510, 0x84A3, 0x2518, + 0x84A4, 0x2514, 0x84A5, 0x251C, 0x84A6, 0x252C, 0x84A7, 0x2524, 0x84A8, 0x2534, 0x84A9, 0x253C, 0x84AA, 0x2501, 0x84AB, 0x2503, + 0x84AC, 0x250F, 0x84AD, 0x2513, 0x84AE, 0x251B, 0x84AF, 0x2517, 0x84B0, 0x2523, 0x84B1, 0x2533, 0x84B2, 0x252B, 0x84B3, 0x253B, + 0x84B4, 0x254B, 0x84B5, 0x2520, 0x84B6, 0x252F, 0x84B7, 0x2528, 0x84B8, 0x2537, 0x84B9, 0x253F, 0x84BA, 0x251D, 0x84BB, 0x2530, + 0x84BC, 0x2525, 0x84BD, 0x2538, 0x84BE, 0x2542, 0x8740, 0x2460, 0x8741, 0x2461, 0x8742, 0x2462, 0x8743, 0x2463, 0x8744, 0x2464, + 0x8745, 0x2465, 0x8746, 0x2466, 0x8747, 0x2467, 0x8748, 0x2468, 0x8749, 0x2469, 0x874A, 0x246A, 0x874B, 0x246B, 0x874C, 0x246C, + 0x874D, 0x246D, 0x874E, 0x246E, 0x874F, 0x246F, 0x8750, 0x2470, 0x8751, 0x2471, 0x8752, 0x2472, 0x8753, 0x2473, 0x8754, 0x2160, + 0x8755, 0x2161, 0x8756, 0x2162, 0x8757, 0x2163, 0x8758, 0x2164, 0x8759, 0x2165, 0x875A, 0x2166, 0x875B, 0x2167, 0x875C, 0x2168, + 0x875D, 0x2169, 0x875F, 0x3349, 0x8760, 0x3314, 0x8761, 0x3322, 0x8762, 0x334D, 0x8763, 0x3318, 0x8764, 0x3327, 0x8765, 0x3303, + 0x8766, 0x3336, 0x8767, 0x3351, 0x8768, 0x3357, 0x8769, 0x330D, 0x876A, 0x3326, 0x876B, 0x3323, 0x876C, 0x332B, 0x876D, 0x334A, + 0x876E, 0x333B, 0x876F, 0x339C, 0x8770, 0x339D, 0x8771, 0x339E, 0x8772, 0x338E, 0x8773, 0x338F, 0x8774, 0x33C4, 0x8775, 0x33A1, + 0x877E, 0x337B, 0x8780, 0x301D, 0x8781, 0x301F, 0x8782, 0x2116, 0x8783, 0x33CD, 0x8784, 0x2121, 0x8785, 0x32A4, 0x8786, 0x32A5, + 0x8787, 0x32A6, 0x8788, 0x32A7, 0x8789, 0x32A8, 0x878A, 0x3231, 0x878B, 0x3232, 0x878C, 0x3239, 0x878D, 0x337E, 0x878E, 0x337D, + 0x878F, 0x337C, 0x8793, 0x222E, 0x8794, 0x2211, 0x8798, 0x221F, 0x8799, 0x22BF, 0x889F, 0x4E9C, 0x88A0, 0x5516, 0x88A1, 0x5A03, + 0x88A2, 0x963F, 0x88A3, 0x54C0, 0x88A4, 0x611B, 0x88A5, 0x6328, 0x88A6, 0x59F6, 0x88A7, 0x9022, 0x88A8, 0x8475, 0x88A9, 0x831C, + 0x88AA, 0x7A50, 0x88AB, 0x60AA, 0x88AC, 0x63E1, 0x88AD, 0x6E25, 0x88AE, 0x65ED, 0x88AF, 0x8466, 0x88B0, 0x82A6, 0x88B1, 0x9BF5, + 0x88B2, 0x6893, 0x88B3, 0x5727, 0x88B4, 0x65A1, 0x88B5, 0x6271, 0x88B6, 0x5B9B, 0x88B7, 0x59D0, 0x88B8, 0x867B, 0x88B9, 0x98F4, + 0x88BA, 0x7D62, 0x88BB, 0x7DBE, 0x88BC, 0x9B8E, 0x88BD, 0x6216, 0x88BE, 0x7C9F, 0x88BF, 0x88B7, 0x88C0, 0x5B89, 0x88C1, 0x5EB5, + 0x88C2, 0x6309, 0x88C3, 0x6697, 0x88C4, 0x6848, 0x88C5, 0x95C7, 0x88C6, 0x978D, 0x88C7, 0x674F, 0x88C8, 0x4EE5, 0x88C9, 0x4F0A, + 0x88CA, 0x4F4D, 0x88CB, 0x4F9D, 0x88CC, 0x5049, 0x88CD, 0x56F2, 0x88CE, 0x5937, 0x88CF, 0x59D4, 0x88D0, 0x5A01, 0x88D1, 0x5C09, + 0x88D2, 0x60DF, 0x88D3, 0x610F, 0x88D4, 0x6170, 0x88D5, 0x6613, 0x88D6, 0x6905, 0x88D7, 0x70BA, 0x88D8, 0x754F, 0x88D9, 0x7570, + 0x88DA, 0x79FB, 0x88DB, 0x7DAD, 0x88DC, 0x7DEF, 0x88DD, 0x80C3, 0x88DE, 0x840E, 0x88DF, 0x8863, 0x88E0, 0x8B02, 0x88E1, 0x9055, + 0x88E2, 0x907A, 0x88E3, 0x533B, 0x88E4, 0x4E95, 0x88E5, 0x4EA5, 0x88E6, 0x57DF, 0x88E7, 0x80B2, 0x88E8, 0x90C1, 0x88E9, 0x78EF, + 0x88EA, 0x4E00, 0x88EB, 0x58F1, 0x88EC, 0x6EA2, 0x88ED, 0x9038, 0x88EE, 0x7A32, 0x88EF, 0x8328, 0x88F0, 0x828B, 0x88F1, 0x9C2F, + 0x88F2, 0x5141, 0x88F3, 0x5370, 0x88F4, 0x54BD, 0x88F5, 0x54E1, 0x88F6, 0x56E0, 0x88F7, 0x59FB, 0x88F8, 0x5F15, 0x88F9, 0x98F2, + 0x88FA, 0x6DEB, 0x88FB, 0x80E4, 0x88FC, 0x852D, 0x8940, 0x9662, 0x8941, 0x9670, 0x8942, 0x96A0, 0x8943, 0x97FB, 0x8944, 0x540B, + 0x8945, 0x53F3, 0x8946, 0x5B87, 0x8947, 0x70CF, 0x8948, 0x7FBD, 0x8949, 0x8FC2, 0x894A, 0x96E8, 0x894B, 0x536F, 0x894C, 0x9D5C, + 0x894D, 0x7ABA, 0x894E, 0x4E11, 0x894F, 0x7893, 0x8950, 0x81FC, 0x8951, 0x6E26, 0x8952, 0x5618, 0x8953, 0x5504, 0x8954, 0x6B1D, + 0x8955, 0x851A, 0x8956, 0x9C3B, 0x8957, 0x59E5, 0x8958, 0x53A9, 0x8959, 0x6D66, 0x895A, 0x74DC, 0x895B, 0x958F, 0x895C, 0x5642, + 0x895D, 0x4E91, 0x895E, 0x904B, 0x895F, 0x96F2, 0x8960, 0x834F, 0x8961, 0x990C, 0x8962, 0x53E1, 0x8963, 0x55B6, 0x8964, 0x5B30, + 0x8965, 0x5F71, 0x8966, 0x6620, 0x8967, 0x66F3, 0x8968, 0x6804, 0x8969, 0x6C38, 0x896A, 0x6CF3, 0x896B, 0x6D29, 0x896C, 0x745B, + 0x896D, 0x76C8, 0x896E, 0x7A4E, 0x896F, 0x9834, 0x8970, 0x82F1, 0x8971, 0x885B, 0x8972, 0x8A60, 0x8973, 0x92ED, 0x8974, 0x6DB2, + 0x8975, 0x75AB, 0x8976, 0x76CA, 0x8977, 0x99C5, 0x8978, 0x60A6, 0x8979, 0x8B01, 0x897A, 0x8D8A, 0x897B, 0x95B2, 0x897C, 0x698E, + 0x897D, 0x53AD, 0x897E, 0x5186, 0x8980, 0x5712, 0x8981, 0x5830, 0x8982, 0x5944, 0x8983, 0x5BB4, 0x8984, 0x5EF6, 0x8985, 0x6028, + 0x8986, 0x63A9, 0x8987, 0x63F4, 0x8988, 0x6CBF, 0x8989, 0x6F14, 0x898A, 0x708E, 0x898B, 0x7114, 0x898C, 0x7159, 0x898D, 0x71D5, + 0x898E, 0x733F, 0x898F, 0x7E01, 0x8990, 0x8276, 0x8991, 0x82D1, 0x8992, 0x8597, 0x8993, 0x9060, 0x8994, 0x925B, 0x8995, 0x9D1B, + 0x8996, 0x5869, 0x8997, 0x65BC, 0x8998, 0x6C5A, 0x8999, 0x7525, 0x899A, 0x51F9, 0x899B, 0x592E, 0x899C, 0x5965, 0x899D, 0x5F80, + 0x899E, 0x5FDC, 0x899F, 0x62BC, 0x89A0, 0x65FA, 0x89A1, 0x6A2A, 0x89A2, 0x6B27, 0x89A3, 0x6BB4, 0x89A4, 0x738B, 0x89A5, 0x7FC1, + 0x89A6, 0x8956, 0x89A7, 0x9D2C, 0x89A8, 0x9D0E, 0x89A9, 0x9EC4, 0x89AA, 0x5CA1, 0x89AB, 0x6C96, 0x89AC, 0x837B, 0x89AD, 0x5104, + 0x89AE, 0x5C4B, 0x89AF, 0x61B6, 0x89B0, 0x81C6, 0x89B1, 0x6876, 0x89B2, 0x7261, 0x89B3, 0x4E59, 0x89B4, 0x4FFA, 0x89B5, 0x5378, + 0x89B6, 0x6069, 0x89B7, 0x6E29, 0x89B8, 0x7A4F, 0x89B9, 0x97F3, 0x89BA, 0x4E0B, 0x89BB, 0x5316, 0x89BC, 0x4EEE, 0x89BD, 0x4F55, + 0x89BE, 0x4F3D, 0x89BF, 0x4FA1, 0x89C0, 0x4F73, 0x89C1, 0x52A0, 0x89C2, 0x53EF, 0x89C3, 0x5609, 0x89C4, 0x590F, 0x89C5, 0x5AC1, + 0x89C6, 0x5BB6, 0x89C7, 0x5BE1, 0x89C8, 0x79D1, 0x89C9, 0x6687, 0x89CA, 0x679C, 0x89CB, 0x67B6, 0x89CC, 0x6B4C, 0x89CD, 0x6CB3, + 0x89CE, 0x706B, 0x89CF, 0x73C2, 0x89D0, 0x798D, 0x89D1, 0x79BE, 0x89D2, 0x7A3C, 0x89D3, 0x7B87, 0x89D4, 0x82B1, 0x89D5, 0x82DB, + 0x89D6, 0x8304, 0x89D7, 0x8377, 0x89D8, 0x83EF, 0x89D9, 0x83D3, 0x89DA, 0x8766, 0x89DB, 0x8AB2, 0x89DC, 0x5629, 0x89DD, 0x8CA8, + 0x89DE, 0x8FE6, 0x89DF, 0x904E, 0x89E0, 0x971E, 0x89E1, 0x868A, 0x89E2, 0x4FC4, 0x89E3, 0x5CE8, 0x89E4, 0x6211, 0x89E5, 0x7259, + 0x89E6, 0x753B, 0x89E7, 0x81E5, 0x89E8, 0x82BD, 0x89E9, 0x86FE, 0x89EA, 0x8CC0, 0x89EB, 0x96C5, 0x89EC, 0x9913, 0x89ED, 0x99D5, + 0x89EE, 0x4ECB, 0x89EF, 0x4F1A, 0x89F0, 0x89E3, 0x89F1, 0x56DE, 0x89F2, 0x584A, 0x89F3, 0x58CA, 0x89F4, 0x5EFB, 0x89F5, 0x5FEB, + 0x89F6, 0x602A, 0x89F7, 0x6094, 0x89F8, 0x6062, 0x89F9, 0x61D0, 0x89FA, 0x6212, 0x89FB, 0x62D0, 0x89FC, 0x6539, 0x8A40, 0x9B41, + 0x8A41, 0x6666, 0x8A42, 0x68B0, 0x8A43, 0x6D77, 0x8A44, 0x7070, 0x8A45, 0x754C, 0x8A46, 0x7686, 0x8A47, 0x7D75, 0x8A48, 0x82A5, + 0x8A49, 0x87F9, 0x8A4A, 0x958B, 0x8A4B, 0x968E, 0x8A4C, 0x8C9D, 0x8A4D, 0x51F1, 0x8A4E, 0x52BE, 0x8A4F, 0x5916, 0x8A50, 0x54B3, + 0x8A51, 0x5BB3, 0x8A52, 0x5D16, 0x8A53, 0x6168, 0x8A54, 0x6982, 0x8A55, 0x6DAF, 0x8A56, 0x788D, 0x8A57, 0x84CB, 0x8A58, 0x8857, + 0x8A59, 0x8A72, 0x8A5A, 0x93A7, 0x8A5B, 0x9AB8, 0x8A5C, 0x6D6C, 0x8A5D, 0x99A8, 0x8A5E, 0x86D9, 0x8A5F, 0x57A3, 0x8A60, 0x67FF, + 0x8A61, 0x86CE, 0x8A62, 0x920E, 0x8A63, 0x5283, 0x8A64, 0x5687, 0x8A65, 0x5404, 0x8A66, 0x5ED3, 0x8A67, 0x62E1, 0x8A68, 0x64B9, + 0x8A69, 0x683C, 0x8A6A, 0x6838, 0x8A6B, 0x6BBB, 0x8A6C, 0x7372, 0x8A6D, 0x78BA, 0x8A6E, 0x7A6B, 0x8A6F, 0x899A, 0x8A70, 0x89D2, + 0x8A71, 0x8D6B, 0x8A72, 0x8F03, 0x8A73, 0x90ED, 0x8A74, 0x95A3, 0x8A75, 0x9694, 0x8A76, 0x9769, 0x8A77, 0x5B66, 0x8A78, 0x5CB3, + 0x8A79, 0x697D, 0x8A7A, 0x984D, 0x8A7B, 0x984E, 0x8A7C, 0x639B, 0x8A7D, 0x7B20, 0x8A7E, 0x6A2B, 0x8A80, 0x6A7F, 0x8A81, 0x68B6, + 0x8A82, 0x9C0D, 0x8A83, 0x6F5F, 0x8A84, 0x5272, 0x8A85, 0x559D, 0x8A86, 0x6070, 0x8A87, 0x62EC, 0x8A88, 0x6D3B, 0x8A89, 0x6E07, + 0x8A8A, 0x6ED1, 0x8A8B, 0x845B, 0x8A8C, 0x8910, 0x8A8D, 0x8F44, 0x8A8E, 0x4E14, 0x8A8F, 0x9C39, 0x8A90, 0x53F6, 0x8A91, 0x691B, + 0x8A92, 0x6A3A, 0x8A93, 0x9784, 0x8A94, 0x682A, 0x8A95, 0x515C, 0x8A96, 0x7AC3, 0x8A97, 0x84B2, 0x8A98, 0x91DC, 0x8A99, 0x938C, + 0x8A9A, 0x565B, 0x8A9B, 0x9D28, 0x8A9C, 0x6822, 0x8A9D, 0x8305, 0x8A9E, 0x8431, 0x8A9F, 0x7CA5, 0x8AA0, 0x5208, 0x8AA1, 0x82C5, + 0x8AA2, 0x74E6, 0x8AA3, 0x4E7E, 0x8AA4, 0x4F83, 0x8AA5, 0x51A0, 0x8AA6, 0x5BD2, 0x8AA7, 0x520A, 0x8AA8, 0x52D8, 0x8AA9, 0x52E7, + 0x8AAA, 0x5DFB, 0x8AAB, 0x559A, 0x8AAC, 0x582A, 0x8AAD, 0x59E6, 0x8AAE, 0x5B8C, 0x8AAF, 0x5B98, 0x8AB0, 0x5BDB, 0x8AB1, 0x5E72, + 0x8AB2, 0x5E79, 0x8AB3, 0x60A3, 0x8AB4, 0x611F, 0x8AB5, 0x6163, 0x8AB6, 0x61BE, 0x8AB7, 0x63DB, 0x8AB8, 0x6562, 0x8AB9, 0x67D1, + 0x8ABA, 0x6853, 0x8ABB, 0x68FA, 0x8ABC, 0x6B3E, 0x8ABD, 0x6B53, 0x8ABE, 0x6C57, 0x8ABF, 0x6F22, 0x8AC0, 0x6F97, 0x8AC1, 0x6F45, + 0x8AC2, 0x74B0, 0x8AC3, 0x7518, 0x8AC4, 0x76E3, 0x8AC5, 0x770B, 0x8AC6, 0x7AFF, 0x8AC7, 0x7BA1, 0x8AC8, 0x7C21, 0x8AC9, 0x7DE9, + 0x8ACA, 0x7F36, 0x8ACB, 0x7FF0, 0x8ACC, 0x809D, 0x8ACD, 0x8266, 0x8ACE, 0x839E, 0x8ACF, 0x89B3, 0x8AD0, 0x8ACC, 0x8AD1, 0x8CAB, + 0x8AD2, 0x9084, 0x8AD3, 0x9451, 0x8AD4, 0x9593, 0x8AD5, 0x9591, 0x8AD6, 0x95A2, 0x8AD7, 0x9665, 0x8AD8, 0x97D3, 0x8AD9, 0x9928, + 0x8ADA, 0x8218, 0x8ADB, 0x4E38, 0x8ADC, 0x542B, 0x8ADD, 0x5CB8, 0x8ADE, 0x5DCC, 0x8ADF, 0x73A9, 0x8AE0, 0x764C, 0x8AE1, 0x773C, + 0x8AE2, 0x5CA9, 0x8AE3, 0x7FEB, 0x8AE4, 0x8D0B, 0x8AE5, 0x96C1, 0x8AE6, 0x9811, 0x8AE7, 0x9854, 0x8AE8, 0x9858, 0x8AE9, 0x4F01, + 0x8AEA, 0x4F0E, 0x8AEB, 0x5371, 0x8AEC, 0x559C, 0x8AED, 0x5668, 0x8AEE, 0x57FA, 0x8AEF, 0x5947, 0x8AF0, 0x5B09, 0x8AF1, 0x5BC4, + 0x8AF2, 0x5C90, 0x8AF3, 0x5E0C, 0x8AF4, 0x5E7E, 0x8AF5, 0x5FCC, 0x8AF6, 0x63EE, 0x8AF7, 0x673A, 0x8AF8, 0x65D7, 0x8AF9, 0x65E2, + 0x8AFA, 0x671F, 0x8AFB, 0x68CB, 0x8AFC, 0x68C4, 0x8B40, 0x6A5F, 0x8B41, 0x5E30, 0x8B42, 0x6BC5, 0x8B43, 0x6C17, 0x8B44, 0x6C7D, + 0x8B45, 0x757F, 0x8B46, 0x7948, 0x8B47, 0x5B63, 0x8B48, 0x7A00, 0x8B49, 0x7D00, 0x8B4A, 0x5FBD, 0x8B4B, 0x898F, 0x8B4C, 0x8A18, + 0x8B4D, 0x8CB4, 0x8B4E, 0x8D77, 0x8B4F, 0x8ECC, 0x8B50, 0x8F1D, 0x8B51, 0x98E2, 0x8B52, 0x9A0E, 0x8B53, 0x9B3C, 0x8B54, 0x4E80, + 0x8B55, 0x507D, 0x8B56, 0x5100, 0x8B57, 0x5993, 0x8B58, 0x5B9C, 0x8B59, 0x622F, 0x8B5A, 0x6280, 0x8B5B, 0x64EC, 0x8B5C, 0x6B3A, + 0x8B5D, 0x72A0, 0x8B5E, 0x7591, 0x8B5F, 0x7947, 0x8B60, 0x7FA9, 0x8B61, 0x87FB, 0x8B62, 0x8ABC, 0x8B63, 0x8B70, 0x8B64, 0x63AC, + 0x8B65, 0x83CA, 0x8B66, 0x97A0, 0x8B67, 0x5409, 0x8B68, 0x5403, 0x8B69, 0x55AB, 0x8B6A, 0x6854, 0x8B6B, 0x6A58, 0x8B6C, 0x8A70, + 0x8B6D, 0x7827, 0x8B6E, 0x6775, 0x8B6F, 0x9ECD, 0x8B70, 0x5374, 0x8B71, 0x5BA2, 0x8B72, 0x811A, 0x8B73, 0x8650, 0x8B74, 0x9006, + 0x8B75, 0x4E18, 0x8B76, 0x4E45, 0x8B77, 0x4EC7, 0x8B78, 0x4F11, 0x8B79, 0x53CA, 0x8B7A, 0x5438, 0x8B7B, 0x5BAE, 0x8B7C, 0x5F13, + 0x8B7D, 0x6025, 0x8B7E, 0x6551, 0x8B80, 0x673D, 0x8B81, 0x6C42, 0x8B82, 0x6C72, 0x8B83, 0x6CE3, 0x8B84, 0x7078, 0x8B85, 0x7403, + 0x8B86, 0x7A76, 0x8B87, 0x7AAE, 0x8B88, 0x7B08, 0x8B89, 0x7D1A, 0x8B8A, 0x7CFE, 0x8B8B, 0x7D66, 0x8B8C, 0x65E7, 0x8B8D, 0x725B, + 0x8B8E, 0x53BB, 0x8B8F, 0x5C45, 0x8B90, 0x5DE8, 0x8B91, 0x62D2, 0x8B92, 0x62E0, 0x8B93, 0x6319, 0x8B94, 0x6E20, 0x8B95, 0x865A, + 0x8B96, 0x8A31, 0x8B97, 0x8DDD, 0x8B98, 0x92F8, 0x8B99, 0x6F01, 0x8B9A, 0x79A6, 0x8B9B, 0x9B5A, 0x8B9C, 0x4EA8, 0x8B9D, 0x4EAB, + 0x8B9E, 0x4EAC, 0x8B9F, 0x4F9B, 0x8BA0, 0x4FA0, 0x8BA1, 0x50D1, 0x8BA2, 0x5147, 0x8BA3, 0x7AF6, 0x8BA4, 0x5171, 0x8BA5, 0x51F6, + 0x8BA6, 0x5354, 0x8BA7, 0x5321, 0x8BA8, 0x537F, 0x8BA9, 0x53EB, 0x8BAA, 0x55AC, 0x8BAB, 0x5883, 0x8BAC, 0x5CE1, 0x8BAD, 0x5F37, + 0x8BAE, 0x5F4A, 0x8BAF, 0x602F, 0x8BB0, 0x6050, 0x8BB1, 0x606D, 0x8BB2, 0x631F, 0x8BB3, 0x6559, 0x8BB4, 0x6A4B, 0x8BB5, 0x6CC1, + 0x8BB6, 0x72C2, 0x8BB7, 0x72ED, 0x8BB8, 0x77EF, 0x8BB9, 0x80F8, 0x8BBA, 0x8105, 0x8BBB, 0x8208, 0x8BBC, 0x854E, 0x8BBD, 0x90F7, + 0x8BBE, 0x93E1, 0x8BBF, 0x97FF, 0x8BC0, 0x9957, 0x8BC1, 0x9A5A, 0x8BC2, 0x4EF0, 0x8BC3, 0x51DD, 0x8BC4, 0x5C2D, 0x8BC5, 0x6681, + 0x8BC6, 0x696D, 0x8BC7, 0x5C40, 0x8BC8, 0x66F2, 0x8BC9, 0x6975, 0x8BCA, 0x7389, 0x8BCB, 0x6850, 0x8BCC, 0x7C81, 0x8BCD, 0x50C5, + 0x8BCE, 0x52E4, 0x8BCF, 0x5747, 0x8BD0, 0x5DFE, 0x8BD1, 0x9326, 0x8BD2, 0x65A4, 0x8BD3, 0x6B23, 0x8BD4, 0x6B3D, 0x8BD5, 0x7434, + 0x8BD6, 0x7981, 0x8BD7, 0x79BD, 0x8BD8, 0x7B4B, 0x8BD9, 0x7DCA, 0x8BDA, 0x82B9, 0x8BDB, 0x83CC, 0x8BDC, 0x887F, 0x8BDD, 0x895F, + 0x8BDE, 0x8B39, 0x8BDF, 0x8FD1, 0x8BE0, 0x91D1, 0x8BE1, 0x541F, 0x8BE2, 0x9280, 0x8BE3, 0x4E5D, 0x8BE4, 0x5036, 0x8BE5, 0x53E5, + 0x8BE6, 0x533A, 0x8BE7, 0x72D7, 0x8BE8, 0x7396, 0x8BE9, 0x77E9, 0x8BEA, 0x82E6, 0x8BEB, 0x8EAF, 0x8BEC, 0x99C6, 0x8BED, 0x99C8, + 0x8BEE, 0x99D2, 0x8BEF, 0x5177, 0x8BF0, 0x611A, 0x8BF1, 0x865E, 0x8BF2, 0x55B0, 0x8BF3, 0x7A7A, 0x8BF4, 0x5076, 0x8BF5, 0x5BD3, + 0x8BF6, 0x9047, 0x8BF7, 0x9685, 0x8BF8, 0x4E32, 0x8BF9, 0x6ADB, 0x8BFA, 0x91E7, 0x8BFB, 0x5C51, 0x8BFC, 0x5C48, 0x8C40, 0x6398, + 0x8C41, 0x7A9F, 0x8C42, 0x6C93, 0x8C43, 0x9774, 0x8C44, 0x8F61, 0x8C45, 0x7AAA, 0x8C46, 0x718A, 0x8C47, 0x9688, 0x8C48, 0x7C82, + 0x8C49, 0x6817, 0x8C4A, 0x7E70, 0x8C4B, 0x6851, 0x8C4C, 0x936C, 0x8C4D, 0x52F2, 0x8C4E, 0x541B, 0x8C4F, 0x85AB, 0x8C50, 0x8A13, + 0x8C51, 0x7FA4, 0x8C52, 0x8ECD, 0x8C53, 0x90E1, 0x8C54, 0x5366, 0x8C55, 0x8888, 0x8C56, 0x7941, 0x8C57, 0x4FC2, 0x8C58, 0x50BE, + 0x8C59, 0x5211, 0x8C5A, 0x5144, 0x8C5B, 0x5553, 0x8C5C, 0x572D, 0x8C5D, 0x73EA, 0x8C5E, 0x578B, 0x8C5F, 0x5951, 0x8C60, 0x5F62, + 0x8C61, 0x5F84, 0x8C62, 0x6075, 0x8C63, 0x6176, 0x8C64, 0x6167, 0x8C65, 0x61A9, 0x8C66, 0x63B2, 0x8C67, 0x643A, 0x8C68, 0x656C, + 0x8C69, 0x666F, 0x8C6A, 0x6842, 0x8C6B, 0x6E13, 0x8C6C, 0x7566, 0x8C6D, 0x7A3D, 0x8C6E, 0x7CFB, 0x8C6F, 0x7D4C, 0x8C70, 0x7D99, + 0x8C71, 0x7E4B, 0x8C72, 0x7F6B, 0x8C73, 0x830E, 0x8C74, 0x834A, 0x8C75, 0x86CD, 0x8C76, 0x8A08, 0x8C77, 0x8A63, 0x8C78, 0x8B66, + 0x8C79, 0x8EFD, 0x8C7A, 0x981A, 0x8C7B, 0x9D8F, 0x8C7C, 0x82B8, 0x8C7D, 0x8FCE, 0x8C7E, 0x9BE8, 0x8C80, 0x5287, 0x8C81, 0x621F, + 0x8C82, 0x6483, 0x8C83, 0x6FC0, 0x8C84, 0x9699, 0x8C85, 0x6841, 0x8C86, 0x5091, 0x8C87, 0x6B20, 0x8C88, 0x6C7A, 0x8C89, 0x6F54, + 0x8C8A, 0x7A74, 0x8C8B, 0x7D50, 0x8C8C, 0x8840, 0x8C8D, 0x8A23, 0x8C8E, 0x6708, 0x8C8F, 0x4EF6, 0x8C90, 0x5039, 0x8C91, 0x5026, + 0x8C92, 0x5065, 0x8C93, 0x517C, 0x8C94, 0x5238, 0x8C95, 0x5263, 0x8C96, 0x55A7, 0x8C97, 0x570F, 0x8C98, 0x5805, 0x8C99, 0x5ACC, + 0x8C9A, 0x5EFA, 0x8C9B, 0x61B2, 0x8C9C, 0x61F8, 0x8C9D, 0x62F3, 0x8C9E, 0x6372, 0x8C9F, 0x691C, 0x8CA0, 0x6A29, 0x8CA1, 0x727D, + 0x8CA2, 0x72AC, 0x8CA3, 0x732E, 0x8CA4, 0x7814, 0x8CA5, 0x786F, 0x8CA6, 0x7D79, 0x8CA7, 0x770C, 0x8CA8, 0x80A9, 0x8CA9, 0x898B, + 0x8CAA, 0x8B19, 0x8CAB, 0x8CE2, 0x8CAC, 0x8ED2, 0x8CAD, 0x9063, 0x8CAE, 0x9375, 0x8CAF, 0x967A, 0x8CB0, 0x9855, 0x8CB1, 0x9A13, + 0x8CB2, 0x9E78, 0x8CB3, 0x5143, 0x8CB4, 0x539F, 0x8CB5, 0x53B3, 0x8CB6, 0x5E7B, 0x8CB7, 0x5F26, 0x8CB8, 0x6E1B, 0x8CB9, 0x6E90, + 0x8CBA, 0x7384, 0x8CBB, 0x73FE, 0x8CBC, 0x7D43, 0x8CBD, 0x8237, 0x8CBE, 0x8A00, 0x8CBF, 0x8AFA, 0x8CC0, 0x9650, 0x8CC1, 0x4E4E, + 0x8CC2, 0x500B, 0x8CC3, 0x53E4, 0x8CC4, 0x547C, 0x8CC5, 0x56FA, 0x8CC6, 0x59D1, 0x8CC7, 0x5B64, 0x8CC8, 0x5DF1, 0x8CC9, 0x5EAB, + 0x8CCA, 0x5F27, 0x8CCB, 0x6238, 0x8CCC, 0x6545, 0x8CCD, 0x67AF, 0x8CCE, 0x6E56, 0x8CCF, 0x72D0, 0x8CD0, 0x7CCA, 0x8CD1, 0x88B4, + 0x8CD2, 0x80A1, 0x8CD3, 0x80E1, 0x8CD4, 0x83F0, 0x8CD5, 0x864E, 0x8CD6, 0x8A87, 0x8CD7, 0x8DE8, 0x8CD8, 0x9237, 0x8CD9, 0x96C7, + 0x8CDA, 0x9867, 0x8CDB, 0x9F13, 0x8CDC, 0x4E94, 0x8CDD, 0x4E92, 0x8CDE, 0x4F0D, 0x8CDF, 0x5348, 0x8CE0, 0x5449, 0x8CE1, 0x543E, + 0x8CE2, 0x5A2F, 0x8CE3, 0x5F8C, 0x8CE4, 0x5FA1, 0x8CE5, 0x609F, 0x8CE6, 0x68A7, 0x8CE7, 0x6A8E, 0x8CE8, 0x745A, 0x8CE9, 0x7881, + 0x8CEA, 0x8A9E, 0x8CEB, 0x8AA4, 0x8CEC, 0x8B77, 0x8CED, 0x9190, 0x8CEE, 0x4E5E, 0x8CEF, 0x9BC9, 0x8CF0, 0x4EA4, 0x8CF1, 0x4F7C, + 0x8CF2, 0x4FAF, 0x8CF3, 0x5019, 0x8CF4, 0x5016, 0x8CF5, 0x5149, 0x8CF6, 0x516C, 0x8CF7, 0x529F, 0x8CF8, 0x52B9, 0x8CF9, 0x52FE, + 0x8CFA, 0x539A, 0x8CFB, 0x53E3, 0x8CFC, 0x5411, 0x8D40, 0x540E, 0x8D41, 0x5589, 0x8D42, 0x5751, 0x8D43, 0x57A2, 0x8D44, 0x597D, + 0x8D45, 0x5B54, 0x8D46, 0x5B5D, 0x8D47, 0x5B8F, 0x8D48, 0x5DE5, 0x8D49, 0x5DE7, 0x8D4A, 0x5DF7, 0x8D4B, 0x5E78, 0x8D4C, 0x5E83, + 0x8D4D, 0x5E9A, 0x8D4E, 0x5EB7, 0x8D4F, 0x5F18, 0x8D50, 0x6052, 0x8D51, 0x614C, 0x8D52, 0x6297, 0x8D53, 0x62D8, 0x8D54, 0x63A7, + 0x8D55, 0x653B, 0x8D56, 0x6602, 0x8D57, 0x6643, 0x8D58, 0x66F4, 0x8D59, 0x676D, 0x8D5A, 0x6821, 0x8D5B, 0x6897, 0x8D5C, 0x69CB, + 0x8D5D, 0x6C5F, 0x8D5E, 0x6D2A, 0x8D5F, 0x6D69, 0x8D60, 0x6E2F, 0x8D61, 0x6E9D, 0x8D62, 0x7532, 0x8D63, 0x7687, 0x8D64, 0x786C, + 0x8D65, 0x7A3F, 0x8D66, 0x7CE0, 0x8D67, 0x7D05, 0x8D68, 0x7D18, 0x8D69, 0x7D5E, 0x8D6A, 0x7DB1, 0x8D6B, 0x8015, 0x8D6C, 0x8003, + 0x8D6D, 0x80AF, 0x8D6E, 0x80B1, 0x8D6F, 0x8154, 0x8D70, 0x818F, 0x8D71, 0x822A, 0x8D72, 0x8352, 0x8D73, 0x884C, 0x8D74, 0x8861, + 0x8D75, 0x8B1B, 0x8D76, 0x8CA2, 0x8D77, 0x8CFC, 0x8D78, 0x90CA, 0x8D79, 0x9175, 0x8D7A, 0x9271, 0x8D7B, 0x783F, 0x8D7C, 0x92FC, + 0x8D7D, 0x95A4, 0x8D7E, 0x964D, 0x8D80, 0x9805, 0x8D81, 0x9999, 0x8D82, 0x9AD8, 0x8D83, 0x9D3B, 0x8D84, 0x525B, 0x8D85, 0x52AB, + 0x8D86, 0x53F7, 0x8D87, 0x5408, 0x8D88, 0x58D5, 0x8D89, 0x62F7, 0x8D8A, 0x6FE0, 0x8D8B, 0x8C6A, 0x8D8C, 0x8F5F, 0x8D8D, 0x9EB9, + 0x8D8E, 0x514B, 0x8D8F, 0x523B, 0x8D90, 0x544A, 0x8D91, 0x56FD, 0x8D92, 0x7A40, 0x8D93, 0x9177, 0x8D94, 0x9D60, 0x8D95, 0x9ED2, + 0x8D96, 0x7344, 0x8D97, 0x6F09, 0x8D98, 0x8170, 0x8D99, 0x7511, 0x8D9A, 0x5FFD, 0x8D9B, 0x60DA, 0x8D9C, 0x9AA8, 0x8D9D, 0x72DB, + 0x8D9E, 0x8FBC, 0x8D9F, 0x6B64, 0x8DA0, 0x9803, 0x8DA1, 0x4ECA, 0x8DA2, 0x56F0, 0x8DA3, 0x5764, 0x8DA4, 0x58BE, 0x8DA5, 0x5A5A, + 0x8DA6, 0x6068, 0x8DA7, 0x61C7, 0x8DA8, 0x660F, 0x8DA9, 0x6606, 0x8DAA, 0x6839, 0x8DAB, 0x68B1, 0x8DAC, 0x6DF7, 0x8DAD, 0x75D5, + 0x8DAE, 0x7D3A, 0x8DAF, 0x826E, 0x8DB0, 0x9B42, 0x8DB1, 0x4E9B, 0x8DB2, 0x4F50, 0x8DB3, 0x53C9, 0x8DB4, 0x5506, 0x8DB5, 0x5D6F, + 0x8DB6, 0x5DE6, 0x8DB7, 0x5DEE, 0x8DB8, 0x67FB, 0x8DB9, 0x6C99, 0x8DBA, 0x7473, 0x8DBB, 0x7802, 0x8DBC, 0x8A50, 0x8DBD, 0x9396, + 0x8DBE, 0x88DF, 0x8DBF, 0x5750, 0x8DC0, 0x5EA7, 0x8DC1, 0x632B, 0x8DC2, 0x50B5, 0x8DC3, 0x50AC, 0x8DC4, 0x518D, 0x8DC5, 0x6700, + 0x8DC6, 0x54C9, 0x8DC7, 0x585E, 0x8DC8, 0x59BB, 0x8DC9, 0x5BB0, 0x8DCA, 0x5F69, 0x8DCB, 0x624D, 0x8DCC, 0x63A1, 0x8DCD, 0x683D, + 0x8DCE, 0x6B73, 0x8DCF, 0x6E08, 0x8DD0, 0x707D, 0x8DD1, 0x91C7, 0x8DD2, 0x7280, 0x8DD3, 0x7815, 0x8DD4, 0x7826, 0x8DD5, 0x796D, + 0x8DD6, 0x658E, 0x8DD7, 0x7D30, 0x8DD8, 0x83DC, 0x8DD9, 0x88C1, 0x8DDA, 0x8F09, 0x8DDB, 0x969B, 0x8DDC, 0x5264, 0x8DDD, 0x5728, + 0x8DDE, 0x6750, 0x8DDF, 0x7F6A, 0x8DE0, 0x8CA1, 0x8DE1, 0x51B4, 0x8DE2, 0x5742, 0x8DE3, 0x962A, 0x8DE4, 0x583A, 0x8DE5, 0x698A, + 0x8DE6, 0x80B4, 0x8DE7, 0x54B2, 0x8DE8, 0x5D0E, 0x8DE9, 0x57FC, 0x8DEA, 0x7895, 0x8DEB, 0x9DFA, 0x8DEC, 0x4F5C, 0x8DED, 0x524A, + 0x8DEE, 0x548B, 0x8DEF, 0x643E, 0x8DF0, 0x6628, 0x8DF1, 0x6714, 0x8DF2, 0x67F5, 0x8DF3, 0x7A84, 0x8DF4, 0x7B56, 0x8DF5, 0x7D22, + 0x8DF6, 0x932F, 0x8DF7, 0x685C, 0x8DF8, 0x9BAD, 0x8DF9, 0x7B39, 0x8DFA, 0x5319, 0x8DFB, 0x518A, 0x8DFC, 0x5237, 0x8E40, 0x5BDF, + 0x8E41, 0x62F6, 0x8E42, 0x64AE, 0x8E43, 0x64E6, 0x8E44, 0x672D, 0x8E45, 0x6BBA, 0x8E46, 0x85A9, 0x8E47, 0x96D1, 0x8E48, 0x7690, + 0x8E49, 0x9BD6, 0x8E4A, 0x634C, 0x8E4B, 0x9306, 0x8E4C, 0x9BAB, 0x8E4D, 0x76BF, 0x8E4E, 0x6652, 0x8E4F, 0x4E09, 0x8E50, 0x5098, + 0x8E51, 0x53C2, 0x8E52, 0x5C71, 0x8E53, 0x60E8, 0x8E54, 0x6492, 0x8E55, 0x6563, 0x8E56, 0x685F, 0x8E57, 0x71E6, 0x8E58, 0x73CA, + 0x8E59, 0x7523, 0x8E5A, 0x7B97, 0x8E5B, 0x7E82, 0x8E5C, 0x8695, 0x8E5D, 0x8B83, 0x8E5E, 0x8CDB, 0x8E5F, 0x9178, 0x8E60, 0x9910, + 0x8E61, 0x65AC, 0x8E62, 0x66AB, 0x8E63, 0x6B8B, 0x8E64, 0x4ED5, 0x8E65, 0x4ED4, 0x8E66, 0x4F3A, 0x8E67, 0x4F7F, 0x8E68, 0x523A, + 0x8E69, 0x53F8, 0x8E6A, 0x53F2, 0x8E6B, 0x55E3, 0x8E6C, 0x56DB, 0x8E6D, 0x58EB, 0x8E6E, 0x59CB, 0x8E6F, 0x59C9, 0x8E70, 0x59FF, + 0x8E71, 0x5B50, 0x8E72, 0x5C4D, 0x8E73, 0x5E02, 0x8E74, 0x5E2B, 0x8E75, 0x5FD7, 0x8E76, 0x601D, 0x8E77, 0x6307, 0x8E78, 0x652F, + 0x8E79, 0x5B5C, 0x8E7A, 0x65AF, 0x8E7B, 0x65BD, 0x8E7C, 0x65E8, 0x8E7D, 0x679D, 0x8E7E, 0x6B62, 0x8E80, 0x6B7B, 0x8E81, 0x6C0F, + 0x8E82, 0x7345, 0x8E83, 0x7949, 0x8E84, 0x79C1, 0x8E85, 0x7CF8, 0x8E86, 0x7D19, 0x8E87, 0x7D2B, 0x8E88, 0x80A2, 0x8E89, 0x8102, + 0x8E8A, 0x81F3, 0x8E8B, 0x8996, 0x8E8C, 0x8A5E, 0x8E8D, 0x8A69, 0x8E8E, 0x8A66, 0x8E8F, 0x8A8C, 0x8E90, 0x8AEE, 0x8E91, 0x8CC7, + 0x8E92, 0x8CDC, 0x8E93, 0x96CC, 0x8E94, 0x98FC, 0x8E95, 0x6B6F, 0x8E96, 0x4E8B, 0x8E97, 0x4F3C, 0x8E98, 0x4F8D, 0x8E99, 0x5150, + 0x8E9A, 0x5B57, 0x8E9B, 0x5BFA, 0x8E9C, 0x6148, 0x8E9D, 0x6301, 0x8E9E, 0x6642, 0x8E9F, 0x6B21, 0x8EA0, 0x6ECB, 0x8EA1, 0x6CBB, + 0x8EA2, 0x723E, 0x8EA3, 0x74BD, 0x8EA4, 0x75D4, 0x8EA5, 0x78C1, 0x8EA6, 0x793A, 0x8EA7, 0x800C, 0x8EA8, 0x8033, 0x8EA9, 0x81EA, + 0x8EAA, 0x8494, 0x8EAB, 0x8F9E, 0x8EAC, 0x6C50, 0x8EAD, 0x9E7F, 0x8EAE, 0x5F0F, 0x8EAF, 0x8B58, 0x8EB0, 0x9D2B, 0x8EB1, 0x7AFA, + 0x8EB2, 0x8EF8, 0x8EB3, 0x5B8D, 0x8EB4, 0x96EB, 0x8EB5, 0x4E03, 0x8EB6, 0x53F1, 0x8EB7, 0x57F7, 0x8EB8, 0x5931, 0x8EB9, 0x5AC9, + 0x8EBA, 0x5BA4, 0x8EBB, 0x6089, 0x8EBC, 0x6E7F, 0x8EBD, 0x6F06, 0x8EBE, 0x75BE, 0x8EBF, 0x8CEA, 0x8EC0, 0x5B9F, 0x8EC1, 0x8500, + 0x8EC2, 0x7BE0, 0x8EC3, 0x5072, 0x8EC4, 0x67F4, 0x8EC5, 0x829D, 0x8EC6, 0x5C61, 0x8EC7, 0x854A, 0x8EC8, 0x7E1E, 0x8EC9, 0x820E, + 0x8ECA, 0x5199, 0x8ECB, 0x5C04, 0x8ECC, 0x6368, 0x8ECD, 0x8D66, 0x8ECE, 0x659C, 0x8ECF, 0x716E, 0x8ED0, 0x793E, 0x8ED1, 0x7D17, + 0x8ED2, 0x8005, 0x8ED3, 0x8B1D, 0x8ED4, 0x8ECA, 0x8ED5, 0x906E, 0x8ED6, 0x86C7, 0x8ED7, 0x90AA, 0x8ED8, 0x501F, 0x8ED9, 0x52FA, + 0x8EDA, 0x5C3A, 0x8EDB, 0x6753, 0x8EDC, 0x707C, 0x8EDD, 0x7235, 0x8EDE, 0x914C, 0x8EDF, 0x91C8, 0x8EE0, 0x932B, 0x8EE1, 0x82E5, + 0x8EE2, 0x5BC2, 0x8EE3, 0x5F31, 0x8EE4, 0x60F9, 0x8EE5, 0x4E3B, 0x8EE6, 0x53D6, 0x8EE7, 0x5B88, 0x8EE8, 0x624B, 0x8EE9, 0x6731, + 0x8EEA, 0x6B8A, 0x8EEB, 0x72E9, 0x8EEC, 0x73E0, 0x8EED, 0x7A2E, 0x8EEE, 0x816B, 0x8EEF, 0x8DA3, 0x8EF0, 0x9152, 0x8EF1, 0x9996, + 0x8EF2, 0x5112, 0x8EF3, 0x53D7, 0x8EF4, 0x546A, 0x8EF5, 0x5BFF, 0x8EF6, 0x6388, 0x8EF7, 0x6A39, 0x8EF8, 0x7DAC, 0x8EF9, 0x9700, + 0x8EFA, 0x56DA, 0x8EFB, 0x53CE, 0x8EFC, 0x5468, 0x8F40, 0x5B97, 0x8F41, 0x5C31, 0x8F42, 0x5DDE, 0x8F43, 0x4FEE, 0x8F44, 0x6101, + 0x8F45, 0x62FE, 0x8F46, 0x6D32, 0x8F47, 0x79C0, 0x8F48, 0x79CB, 0x8F49, 0x7D42, 0x8F4A, 0x7E4D, 0x8F4B, 0x7FD2, 0x8F4C, 0x81ED, + 0x8F4D, 0x821F, 0x8F4E, 0x8490, 0x8F4F, 0x8846, 0x8F50, 0x8972, 0x8F51, 0x8B90, 0x8F52, 0x8E74, 0x8F53, 0x8F2F, 0x8F54, 0x9031, + 0x8F55, 0x914B, 0x8F56, 0x916C, 0x8F57, 0x96C6, 0x8F58, 0x919C, 0x8F59, 0x4EC0, 0x8F5A, 0x4F4F, 0x8F5B, 0x5145, 0x8F5C, 0x5341, + 0x8F5D, 0x5F93, 0x8F5E, 0x620E, 0x8F5F, 0x67D4, 0x8F60, 0x6C41, 0x8F61, 0x6E0B, 0x8F62, 0x7363, 0x8F63, 0x7E26, 0x8F64, 0x91CD, + 0x8F65, 0x9283, 0x8F66, 0x53D4, 0x8F67, 0x5919, 0x8F68, 0x5BBF, 0x8F69, 0x6DD1, 0x8F6A, 0x795D, 0x8F6B, 0x7E2E, 0x8F6C, 0x7C9B, + 0x8F6D, 0x587E, 0x8F6E, 0x719F, 0x8F6F, 0x51FA, 0x8F70, 0x8853, 0x8F71, 0x8FF0, 0x8F72, 0x4FCA, 0x8F73, 0x5CFB, 0x8F74, 0x6625, + 0x8F75, 0x77AC, 0x8F76, 0x7AE3, 0x8F77, 0x821C, 0x8F78, 0x99FF, 0x8F79, 0x51C6, 0x8F7A, 0x5FAA, 0x8F7B, 0x65EC, 0x8F7C, 0x696F, + 0x8F7D, 0x6B89, 0x8F7E, 0x6DF3, 0x8F80, 0x6E96, 0x8F81, 0x6F64, 0x8F82, 0x76FE, 0x8F83, 0x7D14, 0x8F84, 0x5DE1, 0x8F85, 0x9075, + 0x8F86, 0x9187, 0x8F87, 0x9806, 0x8F88, 0x51E6, 0x8F89, 0x521D, 0x8F8A, 0x6240, 0x8F8B, 0x6691, 0x8F8C, 0x66D9, 0x8F8D, 0x6E1A, + 0x8F8E, 0x5EB6, 0x8F8F, 0x7DD2, 0x8F90, 0x7F72, 0x8F91, 0x66F8, 0x8F92, 0x85AF, 0x8F93, 0x85F7, 0x8F94, 0x8AF8, 0x8F95, 0x52A9, + 0x8F96, 0x53D9, 0x8F97, 0x5973, 0x8F98, 0x5E8F, 0x8F99, 0x5F90, 0x8F9A, 0x6055, 0x8F9B, 0x92E4, 0x8F9C, 0x9664, 0x8F9D, 0x50B7, + 0x8F9E, 0x511F, 0x8F9F, 0x52DD, 0x8FA0, 0x5320, 0x8FA1, 0x5347, 0x8FA2, 0x53EC, 0x8FA3, 0x54E8, 0x8FA4, 0x5546, 0x8FA5, 0x5531, + 0x8FA6, 0x5617, 0x8FA7, 0x5968, 0x8FA8, 0x59BE, 0x8FA9, 0x5A3C, 0x8FAA, 0x5BB5, 0x8FAB, 0x5C06, 0x8FAC, 0x5C0F, 0x8FAD, 0x5C11, + 0x8FAE, 0x5C1A, 0x8FAF, 0x5E84, 0x8FB0, 0x5E8A, 0x8FB1, 0x5EE0, 0x8FB2, 0x5F70, 0x8FB3, 0x627F, 0x8FB4, 0x6284, 0x8FB5, 0x62DB, + 0x8FB6, 0x638C, 0x8FB7, 0x6377, 0x8FB8, 0x6607, 0x8FB9, 0x660C, 0x8FBA, 0x662D, 0x8FBB, 0x6676, 0x8FBC, 0x677E, 0x8FBD, 0x68A2, + 0x8FBE, 0x6A1F, 0x8FBF, 0x6A35, 0x8FC0, 0x6CBC, 0x8FC1, 0x6D88, 0x8FC2, 0x6E09, 0x8FC3, 0x6E58, 0x8FC4, 0x713C, 0x8FC5, 0x7126, + 0x8FC6, 0x7167, 0x8FC7, 0x75C7, 0x8FC8, 0x7701, 0x8FC9, 0x785D, 0x8FCA, 0x7901, 0x8FCB, 0x7965, 0x8FCC, 0x79F0, 0x8FCD, 0x7AE0, + 0x8FCE, 0x7B11, 0x8FCF, 0x7CA7, 0x8FD0, 0x7D39, 0x8FD1, 0x8096, 0x8FD2, 0x83D6, 0x8FD3, 0x848B, 0x8FD4, 0x8549, 0x8FD5, 0x885D, + 0x8FD6, 0x88F3, 0x8FD7, 0x8A1F, 0x8FD8, 0x8A3C, 0x8FD9, 0x8A54, 0x8FDA, 0x8A73, 0x8FDB, 0x8C61, 0x8FDC, 0x8CDE, 0x8FDD, 0x91A4, + 0x8FDE, 0x9266, 0x8FDF, 0x937E, 0x8FE0, 0x9418, 0x8FE1, 0x969C, 0x8FE2, 0x9798, 0x8FE3, 0x4E0A, 0x8FE4, 0x4E08, 0x8FE5, 0x4E1E, + 0x8FE6, 0x4E57, 0x8FE7, 0x5197, 0x8FE8, 0x5270, 0x8FE9, 0x57CE, 0x8FEA, 0x5834, 0x8FEB, 0x58CC, 0x8FEC, 0x5B22, 0x8FED, 0x5E38, + 0x8FEE, 0x60C5, 0x8FEF, 0x64FE, 0x8FF0, 0x6761, 0x8FF1, 0x6756, 0x8FF2, 0x6D44, 0x8FF3, 0x72B6, 0x8FF4, 0x7573, 0x8FF5, 0x7A63, + 0x8FF6, 0x84B8, 0x8FF7, 0x8B72, 0x8FF8, 0x91B8, 0x8FF9, 0x9320, 0x8FFA, 0x5631, 0x8FFB, 0x57F4, 0x8FFC, 0x98FE, 0x9040, 0x62ED, + 0x9041, 0x690D, 0x9042, 0x6B96, 0x9043, 0x71ED, 0x9044, 0x7E54, 0x9045, 0x8077, 0x9046, 0x8272, 0x9047, 0x89E6, 0x9048, 0x98DF, + 0x9049, 0x8755, 0x904A, 0x8FB1, 0x904B, 0x5C3B, 0x904C, 0x4F38, 0x904D, 0x4FE1, 0x904E, 0x4FB5, 0x904F, 0x5507, 0x9050, 0x5A20, + 0x9051, 0x5BDD, 0x9052, 0x5BE9, 0x9053, 0x5FC3, 0x9054, 0x614E, 0x9055, 0x632F, 0x9056, 0x65B0, 0x9057, 0x664B, 0x9058, 0x68EE, + 0x9059, 0x699B, 0x905A, 0x6D78, 0x905B, 0x6DF1, 0x905C, 0x7533, 0x905D, 0x75B9, 0x905E, 0x771F, 0x905F, 0x795E, 0x9060, 0x79E6, + 0x9061, 0x7D33, 0x9062, 0x81E3, 0x9063, 0x82AF, 0x9064, 0x85AA, 0x9065, 0x89AA, 0x9066, 0x8A3A, 0x9067, 0x8EAB, 0x9068, 0x8F9B, + 0x9069, 0x9032, 0x906A, 0x91DD, 0x906B, 0x9707, 0x906C, 0x4EBA, 0x906D, 0x4EC1, 0x906E, 0x5203, 0x906F, 0x5875, 0x9070, 0x58EC, + 0x9071, 0x5C0B, 0x9072, 0x751A, 0x9073, 0x5C3D, 0x9074, 0x814E, 0x9075, 0x8A0A, 0x9076, 0x8FC5, 0x9077, 0x9663, 0x9078, 0x976D, + 0x9079, 0x7B25, 0x907A, 0x8ACF, 0x907B, 0x9808, 0x907C, 0x9162, 0x907D, 0x56F3, 0x907E, 0x53A8, 0x9080, 0x9017, 0x9081, 0x5439, + 0x9082, 0x5782, 0x9083, 0x5E25, 0x9084, 0x63A8, 0x9085, 0x6C34, 0x9086, 0x708A, 0x9087, 0x7761, 0x9088, 0x7C8B, 0x9089, 0x7FE0, + 0x908A, 0x8870, 0x908B, 0x9042, 0x908C, 0x9154, 0x908D, 0x9310, 0x908E, 0x9318, 0x908F, 0x968F, 0x9090, 0x745E, 0x9091, 0x9AC4, + 0x9092, 0x5D07, 0x9093, 0x5D69, 0x9094, 0x6570, 0x9095, 0x67A2, 0x9096, 0x8DA8, 0x9097, 0x96DB, 0x9098, 0x636E, 0x9099, 0x6749, + 0x909A, 0x6919, 0x909B, 0x83C5, 0x909C, 0x9817, 0x909D, 0x96C0, 0x909E, 0x88FE, 0x909F, 0x6F84, 0x90A0, 0x647A, 0x90A1, 0x5BF8, + 0x90A2, 0x4E16, 0x90A3, 0x702C, 0x90A4, 0x755D, 0x90A5, 0x662F, 0x90A6, 0x51C4, 0x90A7, 0x5236, 0x90A8, 0x52E2, 0x90A9, 0x59D3, + 0x90AA, 0x5F81, 0x90AB, 0x6027, 0x90AC, 0x6210, 0x90AD, 0x653F, 0x90AE, 0x6574, 0x90AF, 0x661F, 0x90B0, 0x6674, 0x90B1, 0x68F2, + 0x90B2, 0x6816, 0x90B3, 0x6B63, 0x90B4, 0x6E05, 0x90B5, 0x7272, 0x90B6, 0x751F, 0x90B7, 0x76DB, 0x90B8, 0x7CBE, 0x90B9, 0x8056, + 0x90BA, 0x58F0, 0x90BB, 0x88FD, 0x90BC, 0x897F, 0x90BD, 0x8AA0, 0x90BE, 0x8A93, 0x90BF, 0x8ACB, 0x90C0, 0x901D, 0x90C1, 0x9192, + 0x90C2, 0x9752, 0x90C3, 0x9759, 0x90C4, 0x6589, 0x90C5, 0x7A0E, 0x90C6, 0x8106, 0x90C7, 0x96BB, 0x90C8, 0x5E2D, 0x90C9, 0x60DC, + 0x90CA, 0x621A, 0x90CB, 0x65A5, 0x90CC, 0x6614, 0x90CD, 0x6790, 0x90CE, 0x77F3, 0x90CF, 0x7A4D, 0x90D0, 0x7C4D, 0x90D1, 0x7E3E, + 0x90D2, 0x810A, 0x90D3, 0x8CAC, 0x90D4, 0x8D64, 0x90D5, 0x8DE1, 0x90D6, 0x8E5F, 0x90D7, 0x78A9, 0x90D8, 0x5207, 0x90D9, 0x62D9, + 0x90DA, 0x63A5, 0x90DB, 0x6442, 0x90DC, 0x6298, 0x90DD, 0x8A2D, 0x90DE, 0x7A83, 0x90DF, 0x7BC0, 0x90E0, 0x8AAC, 0x90E1, 0x96EA, + 0x90E2, 0x7D76, 0x90E3, 0x820C, 0x90E4, 0x8749, 0x90E5, 0x4ED9, 0x90E6, 0x5148, 0x90E7, 0x5343, 0x90E8, 0x5360, 0x90E9, 0x5BA3, + 0x90EA, 0x5C02, 0x90EB, 0x5C16, 0x90EC, 0x5DDD, 0x90ED, 0x6226, 0x90EE, 0x6247, 0x90EF, 0x64B0, 0x90F0, 0x6813, 0x90F1, 0x6834, + 0x90F2, 0x6CC9, 0x90F3, 0x6D45, 0x90F4, 0x6D17, 0x90F5, 0x67D3, 0x90F6, 0x6F5C, 0x90F7, 0x714E, 0x90F8, 0x717D, 0x90F9, 0x65CB, + 0x90FA, 0x7A7F, 0x90FB, 0x7BAD, 0x90FC, 0x7DDA, 0x9140, 0x7E4A, 0x9141, 0x7FA8, 0x9142, 0x817A, 0x9143, 0x821B, 0x9144, 0x8239, + 0x9145, 0x85A6, 0x9146, 0x8A6E, 0x9147, 0x8CCE, 0x9148, 0x8DF5, 0x9149, 0x9078, 0x914A, 0x9077, 0x914B, 0x92AD, 0x914C, 0x9291, + 0x914D, 0x9583, 0x914E, 0x9BAE, 0x914F, 0x524D, 0x9150, 0x5584, 0x9151, 0x6F38, 0x9152, 0x7136, 0x9153, 0x5168, 0x9154, 0x7985, + 0x9155, 0x7E55, 0x9156, 0x81B3, 0x9157, 0x7CCE, 0x9158, 0x564C, 0x9159, 0x5851, 0x915A, 0x5CA8, 0x915B, 0x63AA, 0x915C, 0x66FE, + 0x915D, 0x66FD, 0x915E, 0x695A, 0x915F, 0x72D9, 0x9160, 0x758F, 0x9161, 0x758E, 0x9162, 0x790E, 0x9163, 0x7956, 0x9164, 0x79DF, + 0x9165, 0x7C97, 0x9166, 0x7D20, 0x9167, 0x7D44, 0x9168, 0x8607, 0x9169, 0x8A34, 0x916A, 0x963B, 0x916B, 0x9061, 0x916C, 0x9F20, + 0x916D, 0x50E7, 0x916E, 0x5275, 0x916F, 0x53CC, 0x9170, 0x53E2, 0x9171, 0x5009, 0x9172, 0x55AA, 0x9173, 0x58EE, 0x9174, 0x594F, + 0x9175, 0x723D, 0x9176, 0x5B8B, 0x9177, 0x5C64, 0x9178, 0x531D, 0x9179, 0x60E3, 0x917A, 0x60F3, 0x917B, 0x635C, 0x917C, 0x6383, + 0x917D, 0x633F, 0x917E, 0x63BB, 0x9180, 0x64CD, 0x9181, 0x65E9, 0x9182, 0x66F9, 0x9183, 0x5DE3, 0x9184, 0x69CD, 0x9185, 0x69FD, + 0x9186, 0x6F15, 0x9187, 0x71E5, 0x9188, 0x4E89, 0x9189, 0x75E9, 0x918A, 0x76F8, 0x918B, 0x7A93, 0x918C, 0x7CDF, 0x918D, 0x7DCF, + 0x918E, 0x7D9C, 0x918F, 0x8061, 0x9190, 0x8349, 0x9191, 0x8358, 0x9192, 0x846C, 0x9193, 0x84BC, 0x9194, 0x85FB, 0x9195, 0x88C5, + 0x9196, 0x8D70, 0x9197, 0x9001, 0x9198, 0x906D, 0x9199, 0x9397, 0x919A, 0x971C, 0x919B, 0x9A12, 0x919C, 0x50CF, 0x919D, 0x5897, + 0x919E, 0x618E, 0x919F, 0x81D3, 0x91A0, 0x8535, 0x91A1, 0x8D08, 0x91A2, 0x9020, 0x91A3, 0x4FC3, 0x91A4, 0x5074, 0x91A5, 0x5247, + 0x91A6, 0x5373, 0x91A7, 0x606F, 0x91A8, 0x6349, 0x91A9, 0x675F, 0x91AA, 0x6E2C, 0x91AB, 0x8DB3, 0x91AC, 0x901F, 0x91AD, 0x4FD7, + 0x91AE, 0x5C5E, 0x91AF, 0x8CCA, 0x91B0, 0x65CF, 0x91B1, 0x7D9A, 0x91B2, 0x5352, 0x91B3, 0x8896, 0x91B4, 0x5176, 0x91B5, 0x63C3, + 0x91B6, 0x5B58, 0x91B7, 0x5B6B, 0x91B8, 0x5C0A, 0x91B9, 0x640D, 0x91BA, 0x6751, 0x91BB, 0x905C, 0x91BC, 0x4ED6, 0x91BD, 0x591A, + 0x91BE, 0x592A, 0x91BF, 0x6C70, 0x91C0, 0x8A51, 0x91C1, 0x553E, 0x91C2, 0x5815, 0x91C3, 0x59A5, 0x91C4, 0x60F0, 0x91C5, 0x6253, + 0x91C6, 0x67C1, 0x91C7, 0x8235, 0x91C8, 0x6955, 0x91C9, 0x9640, 0x91CA, 0x99C4, 0x91CB, 0x9A28, 0x91CC, 0x4F53, 0x91CD, 0x5806, + 0x91CE, 0x5BFE, 0x91CF, 0x8010, 0x91D0, 0x5CB1, 0x91D1, 0x5E2F, 0x91D2, 0x5F85, 0x91D3, 0x6020, 0x91D4, 0x614B, 0x91D5, 0x6234, + 0x91D6, 0x66FF, 0x91D7, 0x6CF0, 0x91D8, 0x6EDE, 0x91D9, 0x80CE, 0x91DA, 0x817F, 0x91DB, 0x82D4, 0x91DC, 0x888B, 0x91DD, 0x8CB8, + 0x91DE, 0x9000, 0x91DF, 0x902E, 0x91E0, 0x968A, 0x91E1, 0x9EDB, 0x91E2, 0x9BDB, 0x91E3, 0x4EE3, 0x91E4, 0x53F0, 0x91E5, 0x5927, + 0x91E6, 0x7B2C, 0x91E7, 0x918D, 0x91E8, 0x984C, 0x91E9, 0x9DF9, 0x91EA, 0x6EDD, 0x91EB, 0x7027, 0x91EC, 0x5353, 0x91ED, 0x5544, + 0x91EE, 0x5B85, 0x91EF, 0x6258, 0x91F0, 0x629E, 0x91F1, 0x62D3, 0x91F2, 0x6CA2, 0x91F3, 0x6FEF, 0x91F4, 0x7422, 0x91F5, 0x8A17, + 0x91F6, 0x9438, 0x91F7, 0x6FC1, 0x91F8, 0x8AFE, 0x91F9, 0x8338, 0x91FA, 0x51E7, 0x91FB, 0x86F8, 0x91FC, 0x53EA, 0x9240, 0x53E9, + 0x9241, 0x4F46, 0x9242, 0x9054, 0x9243, 0x8FB0, 0x9244, 0x596A, 0x9245, 0x8131, 0x9246, 0x5DFD, 0x9247, 0x7AEA, 0x9248, 0x8FBF, + 0x9249, 0x68DA, 0x924A, 0x8C37, 0x924B, 0x72F8, 0x924C, 0x9C48, 0x924D, 0x6A3D, 0x924E, 0x8AB0, 0x924F, 0x4E39, 0x9250, 0x5358, + 0x9251, 0x5606, 0x9252, 0x5766, 0x9253, 0x62C5, 0x9254, 0x63A2, 0x9255, 0x65E6, 0x9256, 0x6B4E, 0x9257, 0x6DE1, 0x9258, 0x6E5B, + 0x9259, 0x70AD, 0x925A, 0x77ED, 0x925B, 0x7AEF, 0x925C, 0x7BAA, 0x925D, 0x7DBB, 0x925E, 0x803D, 0x925F, 0x80C6, 0x9260, 0x86CB, + 0x9261, 0x8A95, 0x9262, 0x935B, 0x9263, 0x56E3, 0x9264, 0x58C7, 0x9265, 0x5F3E, 0x9266, 0x65AD, 0x9267, 0x6696, 0x9268, 0x6A80, + 0x9269, 0x6BB5, 0x926A, 0x7537, 0x926B, 0x8AC7, 0x926C, 0x5024, 0x926D, 0x77E5, 0x926E, 0x5730, 0x926F, 0x5F1B, 0x9270, 0x6065, + 0x9271, 0x667A, 0x9272, 0x6C60, 0x9273, 0x75F4, 0x9274, 0x7A1A, 0x9275, 0x7F6E, 0x9276, 0x81F4, 0x9277, 0x8718, 0x9278, 0x9045, + 0x9279, 0x99B3, 0x927A, 0x7BC9, 0x927B, 0x755C, 0x927C, 0x7AF9, 0x927D, 0x7B51, 0x927E, 0x84C4, 0x9280, 0x9010, 0x9281, 0x79E9, + 0x9282, 0x7A92, 0x9283, 0x8336, 0x9284, 0x5AE1, 0x9285, 0x7740, 0x9286, 0x4E2D, 0x9287, 0x4EF2, 0x9288, 0x5B99, 0x9289, 0x5FE0, + 0x928A, 0x62BD, 0x928B, 0x663C, 0x928C, 0x67F1, 0x928D, 0x6CE8, 0x928E, 0x866B, 0x928F, 0x8877, 0x9290, 0x8A3B, 0x9291, 0x914E, + 0x9292, 0x92F3, 0x9293, 0x99D0, 0x9294, 0x6A17, 0x9295, 0x7026, 0x9296, 0x732A, 0x9297, 0x82E7, 0x9298, 0x8457, 0x9299, 0x8CAF, + 0x929A, 0x4E01, 0x929B, 0x5146, 0x929C, 0x51CB, 0x929D, 0x558B, 0x929E, 0x5BF5, 0x929F, 0x5E16, 0x92A0, 0x5E33, 0x92A1, 0x5E81, + 0x92A2, 0x5F14, 0x92A3, 0x5F35, 0x92A4, 0x5F6B, 0x92A5, 0x5FB4, 0x92A6, 0x61F2, 0x92A7, 0x6311, 0x92A8, 0x66A2, 0x92A9, 0x671D, + 0x92AA, 0x6F6E, 0x92AB, 0x7252, 0x92AC, 0x753A, 0x92AD, 0x773A, 0x92AE, 0x8074, 0x92AF, 0x8139, 0x92B0, 0x8178, 0x92B1, 0x8776, + 0x92B2, 0x8ABF, 0x92B3, 0x8ADC, 0x92B4, 0x8D85, 0x92B5, 0x8DF3, 0x92B6, 0x929A, 0x92B7, 0x9577, 0x92B8, 0x9802, 0x92B9, 0x9CE5, + 0x92BA, 0x52C5, 0x92BB, 0x6357, 0x92BC, 0x76F4, 0x92BD, 0x6715, 0x92BE, 0x6C88, 0x92BF, 0x73CD, 0x92C0, 0x8CC3, 0x92C1, 0x93AE, + 0x92C2, 0x9673, 0x92C3, 0x6D25, 0x92C4, 0x589C, 0x92C5, 0x690E, 0x92C6, 0x69CC, 0x92C7, 0x8FFD, 0x92C8, 0x939A, 0x92C9, 0x75DB, + 0x92CA, 0x901A, 0x92CB, 0x585A, 0x92CC, 0x6802, 0x92CD, 0x63B4, 0x92CE, 0x69FB, 0x92CF, 0x4F43, 0x92D0, 0x6F2C, 0x92D1, 0x67D8, + 0x92D2, 0x8FBB, 0x92D3, 0x8526, 0x92D4, 0x7DB4, 0x92D5, 0x9354, 0x92D6, 0x693F, 0x92D7, 0x6F70, 0x92D8, 0x576A, 0x92D9, 0x58F7, + 0x92DA, 0x5B2C, 0x92DB, 0x7D2C, 0x92DC, 0x722A, 0x92DD, 0x540A, 0x92DE, 0x91E3, 0x92DF, 0x9DB4, 0x92E0, 0x4EAD, 0x92E1, 0x4F4E, + 0x92E2, 0x505C, 0x92E3, 0x5075, 0x92E4, 0x5243, 0x92E5, 0x8C9E, 0x92E6, 0x5448, 0x92E7, 0x5824, 0x92E8, 0x5B9A, 0x92E9, 0x5E1D, + 0x92EA, 0x5E95, 0x92EB, 0x5EAD, 0x92EC, 0x5EF7, 0x92ED, 0x5F1F, 0x92EE, 0x608C, 0x92EF, 0x62B5, 0x92F0, 0x633A, 0x92F1, 0x63D0, + 0x92F2, 0x68AF, 0x92F3, 0x6C40, 0x92F4, 0x7887, 0x92F5, 0x798E, 0x92F6, 0x7A0B, 0x92F7, 0x7DE0, 0x92F8, 0x8247, 0x92F9, 0x8A02, + 0x92FA, 0x8AE6, 0x92FB, 0x8E44, 0x92FC, 0x9013, 0x9340, 0x90B8, 0x9341, 0x912D, 0x9342, 0x91D8, 0x9343, 0x9F0E, 0x9344, 0x6CE5, + 0x9345, 0x6458, 0x9346, 0x64E2, 0x9347, 0x6575, 0x9348, 0x6EF4, 0x9349, 0x7684, 0x934A, 0x7B1B, 0x934B, 0x9069, 0x934C, 0x93D1, + 0x934D, 0x6EBA, 0x934E, 0x54F2, 0x934F, 0x5FB9, 0x9350, 0x64A4, 0x9351, 0x8F4D, 0x9352, 0x8FED, 0x9353, 0x9244, 0x9354, 0x5178, + 0x9355, 0x586B, 0x9356, 0x5929, 0x9357, 0x5C55, 0x9358, 0x5E97, 0x9359, 0x6DFB, 0x935A, 0x7E8F, 0x935B, 0x751C, 0x935C, 0x8CBC, + 0x935D, 0x8EE2, 0x935E, 0x985B, 0x935F, 0x70B9, 0x9360, 0x4F1D, 0x9361, 0x6BBF, 0x9362, 0x6FB1, 0x9363, 0x7530, 0x9364, 0x96FB, + 0x9365, 0x514E, 0x9366, 0x5410, 0x9367, 0x5835, 0x9368, 0x5857, 0x9369, 0x59AC, 0x936A, 0x5C60, 0x936B, 0x5F92, 0x936C, 0x6597, + 0x936D, 0x675C, 0x936E, 0x6E21, 0x936F, 0x767B, 0x9370, 0x83DF, 0x9371, 0x8CED, 0x9372, 0x9014, 0x9373, 0x90FD, 0x9374, 0x934D, + 0x9375, 0x7825, 0x9376, 0x783A, 0x9377, 0x52AA, 0x9378, 0x5EA6, 0x9379, 0x571F, 0x937A, 0x5974, 0x937B, 0x6012, 0x937C, 0x5012, + 0x937D, 0x515A, 0x937E, 0x51AC, 0x9380, 0x51CD, 0x9381, 0x5200, 0x9382, 0x5510, 0x9383, 0x5854, 0x9384, 0x5858, 0x9385, 0x5957, + 0x9386, 0x5B95, 0x9387, 0x5CF6, 0x9388, 0x5D8B, 0x9389, 0x60BC, 0x938A, 0x6295, 0x938B, 0x642D, 0x938C, 0x6771, 0x938D, 0x6843, + 0x938E, 0x68BC, 0x938F, 0x68DF, 0x9390, 0x76D7, 0x9391, 0x6DD8, 0x9392, 0x6E6F, 0x9393, 0x6D9B, 0x9394, 0x706F, 0x9395, 0x71C8, + 0x9396, 0x5F53, 0x9397, 0x75D8, 0x9398, 0x7977, 0x9399, 0x7B49, 0x939A, 0x7B54, 0x939B, 0x7B52, 0x939C, 0x7CD6, 0x939D, 0x7D71, + 0x939E, 0x5230, 0x939F, 0x8463, 0x93A0, 0x8569, 0x93A1, 0x85E4, 0x93A2, 0x8A0E, 0x93A3, 0x8B04, 0x93A4, 0x8C46, 0x93A5, 0x8E0F, + 0x93A6, 0x9003, 0x93A7, 0x900F, 0x93A8, 0x9419, 0x93A9, 0x9676, 0x93AA, 0x982D, 0x93AB, 0x9A30, 0x93AC, 0x95D8, 0x93AD, 0x50CD, + 0x93AE, 0x52D5, 0x93AF, 0x540C, 0x93B0, 0x5802, 0x93B1, 0x5C0E, 0x93B2, 0x61A7, 0x93B3, 0x649E, 0x93B4, 0x6D1E, 0x93B5, 0x77B3, + 0x93B6, 0x7AE5, 0x93B7, 0x80F4, 0x93B8, 0x8404, 0x93B9, 0x9053, 0x93BA, 0x9285, 0x93BB, 0x5CE0, 0x93BC, 0x9D07, 0x93BD, 0x533F, + 0x93BE, 0x5F97, 0x93BF, 0x5FB3, 0x93C0, 0x6D9C, 0x93C1, 0x7279, 0x93C2, 0x7763, 0x93C3, 0x79BF, 0x93C4, 0x7BE4, 0x93C5, 0x6BD2, + 0x93C6, 0x72EC, 0x93C7, 0x8AAD, 0x93C8, 0x6803, 0x93C9, 0x6A61, 0x93CA, 0x51F8, 0x93CB, 0x7A81, 0x93CC, 0x6934, 0x93CD, 0x5C4A, + 0x93CE, 0x9CF6, 0x93CF, 0x82EB, 0x93D0, 0x5BC5, 0x93D1, 0x9149, 0x93D2, 0x701E, 0x93D3, 0x5678, 0x93D4, 0x5C6F, 0x93D5, 0x60C7, + 0x93D6, 0x6566, 0x93D7, 0x6C8C, 0x93D8, 0x8C5A, 0x93D9, 0x9041, 0x93DA, 0x9813, 0x93DB, 0x5451, 0x93DC, 0x66C7, 0x93DD, 0x920D, + 0x93DE, 0x5948, 0x93DF, 0x90A3, 0x93E0, 0x5185, 0x93E1, 0x4E4D, 0x93E2, 0x51EA, 0x93E3, 0x8599, 0x93E4, 0x8B0E, 0x93E5, 0x7058, + 0x93E6, 0x637A, 0x93E7, 0x934B, 0x93E8, 0x6962, 0x93E9, 0x99B4, 0x93EA, 0x7E04, 0x93EB, 0x7577, 0x93EC, 0x5357, 0x93ED, 0x6960, + 0x93EE, 0x8EDF, 0x93EF, 0x96E3, 0x93F0, 0x6C5D, 0x93F1, 0x4E8C, 0x93F2, 0x5C3C, 0x93F3, 0x5F10, 0x93F4, 0x8FE9, 0x93F5, 0x5302, + 0x93F6, 0x8CD1, 0x93F7, 0x8089, 0x93F8, 0x8679, 0x93F9, 0x5EFF, 0x93FA, 0x65E5, 0x93FB, 0x4E73, 0x93FC, 0x5165, 0x9440, 0x5982, + 0x9441, 0x5C3F, 0x9442, 0x97EE, 0x9443, 0x4EFB, 0x9444, 0x598A, 0x9445, 0x5FCD, 0x9446, 0x8A8D, 0x9447, 0x6FE1, 0x9448, 0x79B0, + 0x9449, 0x7962, 0x944A, 0x5BE7, 0x944B, 0x8471, 0x944C, 0x732B, 0x944D, 0x71B1, 0x944E, 0x5E74, 0x944F, 0x5FF5, 0x9450, 0x637B, + 0x9451, 0x649A, 0x9452, 0x71C3, 0x9453, 0x7C98, 0x9454, 0x4E43, 0x9455, 0x5EFC, 0x9456, 0x4E4B, 0x9457, 0x57DC, 0x9458, 0x56A2, + 0x9459, 0x60A9, 0x945A, 0x6FC3, 0x945B, 0x7D0D, 0x945C, 0x80FD, 0x945D, 0x8133, 0x945E, 0x81BF, 0x945F, 0x8FB2, 0x9460, 0x8997, + 0x9461, 0x86A4, 0x9462, 0x5DF4, 0x9463, 0x628A, 0x9464, 0x64AD, 0x9465, 0x8987, 0x9466, 0x6777, 0x9467, 0x6CE2, 0x9468, 0x6D3E, + 0x9469, 0x7436, 0x946A, 0x7834, 0x946B, 0x5A46, 0x946C, 0x7F75, 0x946D, 0x82AD, 0x946E, 0x99AC, 0x946F, 0x4FF3, 0x9470, 0x5EC3, + 0x9471, 0x62DD, 0x9472, 0x6392, 0x9473, 0x6557, 0x9474, 0x676F, 0x9475, 0x76C3, 0x9476, 0x724C, 0x9477, 0x80CC, 0x9478, 0x80BA, + 0x9479, 0x8F29, 0x947A, 0x914D, 0x947B, 0x500D, 0x947C, 0x57F9, 0x947D, 0x5A92, 0x947E, 0x6885, 0x9480, 0x6973, 0x9481, 0x7164, + 0x9482, 0x72FD, 0x9483, 0x8CB7, 0x9484, 0x58F2, 0x9485, 0x8CE0, 0x9486, 0x966A, 0x9487, 0x9019, 0x9488, 0x877F, 0x9489, 0x79E4, + 0x948A, 0x77E7, 0x948B, 0x8429, 0x948C, 0x4F2F, 0x948D, 0x5265, 0x948E, 0x535A, 0x948F, 0x62CD, 0x9490, 0x67CF, 0x9491, 0x6CCA, + 0x9492, 0x767D, 0x9493, 0x7B94, 0x9494, 0x7C95, 0x9495, 0x8236, 0x9496, 0x8584, 0x9497, 0x8FEB, 0x9498, 0x66DD, 0x9499, 0x6F20, + 0x949A, 0x7206, 0x949B, 0x7E1B, 0x949C, 0x83AB, 0x949D, 0x99C1, 0x949E, 0x9EA6, 0x949F, 0x51FD, 0x94A0, 0x7BB1, 0x94A1, 0x7872, + 0x94A2, 0x7BB8, 0x94A3, 0x8087, 0x94A4, 0x7B48, 0x94A5, 0x6AE8, 0x94A6, 0x5E61, 0x94A7, 0x808C, 0x94A8, 0x7551, 0x94A9, 0x7560, + 0x94AA, 0x516B, 0x94AB, 0x9262, 0x94AC, 0x6E8C, 0x94AD, 0x767A, 0x94AE, 0x9197, 0x94AF, 0x9AEA, 0x94B0, 0x4F10, 0x94B1, 0x7F70, + 0x94B2, 0x629C, 0x94B3, 0x7B4F, 0x94B4, 0x95A5, 0x94B5, 0x9CE9, 0x94B6, 0x567A, 0x94B7, 0x5859, 0x94B8, 0x86E4, 0x94B9, 0x96BC, + 0x94BA, 0x4F34, 0x94BB, 0x5224, 0x94BC, 0x534A, 0x94BD, 0x53CD, 0x94BE, 0x53DB, 0x94BF, 0x5E06, 0x94C0, 0x642C, 0x94C1, 0x6591, + 0x94C2, 0x677F, 0x94C3, 0x6C3E, 0x94C4, 0x6C4E, 0x94C5, 0x7248, 0x94C6, 0x72AF, 0x94C7, 0x73ED, 0x94C8, 0x7554, 0x94C9, 0x7E41, + 0x94CA, 0x822C, 0x94CB, 0x85E9, 0x94CC, 0x8CA9, 0x94CD, 0x7BC4, 0x94CE, 0x91C6, 0x94CF, 0x7169, 0x94D0, 0x9812, 0x94D1, 0x98EF, + 0x94D2, 0x633D, 0x94D3, 0x6669, 0x94D4, 0x756A, 0x94D5, 0x76E4, 0x94D6, 0x78D0, 0x94D7, 0x8543, 0x94D8, 0x86EE, 0x94D9, 0x532A, + 0x94DA, 0x5351, 0x94DB, 0x5426, 0x94DC, 0x5983, 0x94DD, 0x5E87, 0x94DE, 0x5F7C, 0x94DF, 0x60B2, 0x94E0, 0x6249, 0x94E1, 0x6279, + 0x94E2, 0x62AB, 0x94E3, 0x6590, 0x94E4, 0x6BD4, 0x94E5, 0x6CCC, 0x94E6, 0x75B2, 0x94E7, 0x76AE, 0x94E8, 0x7891, 0x94E9, 0x79D8, + 0x94EA, 0x7DCB, 0x94EB, 0x7F77, 0x94EC, 0x80A5, 0x94ED, 0x88AB, 0x94EE, 0x8AB9, 0x94EF, 0x8CBB, 0x94F0, 0x907F, 0x94F1, 0x975E, + 0x94F2, 0x98DB, 0x94F3, 0x6A0B, 0x94F4, 0x7C38, 0x94F5, 0x5099, 0x94F6, 0x5C3E, 0x94F7, 0x5FAE, 0x94F8, 0x6787, 0x94F9, 0x6BD8, + 0x94FA, 0x7435, 0x94FB, 0x7709, 0x94FC, 0x7F8E, 0x9540, 0x9F3B, 0x9541, 0x67CA, 0x9542, 0x7A17, 0x9543, 0x5339, 0x9544, 0x758B, + 0x9545, 0x9AED, 0x9546, 0x5F66, 0x9547, 0x819D, 0x9548, 0x83F1, 0x9549, 0x8098, 0x954A, 0x5F3C, 0x954B, 0x5FC5, 0x954C, 0x7562, + 0x954D, 0x7B46, 0x954E, 0x903C, 0x954F, 0x6867, 0x9550, 0x59EB, 0x9551, 0x5A9B, 0x9552, 0x7D10, 0x9553, 0x767E, 0x9554, 0x8B2C, + 0x9555, 0x4FF5, 0x9556, 0x5F6A, 0x9557, 0x6A19, 0x9558, 0x6C37, 0x9559, 0x6F02, 0x955A, 0x74E2, 0x955B, 0x7968, 0x955C, 0x8868, + 0x955D, 0x8A55, 0x955E, 0x8C79, 0x955F, 0x5EDF, 0x9560, 0x63CF, 0x9561, 0x75C5, 0x9562, 0x79D2, 0x9563, 0x82D7, 0x9564, 0x9328, + 0x9565, 0x92F2, 0x9566, 0x849C, 0x9567, 0x86ED, 0x9568, 0x9C2D, 0x9569, 0x54C1, 0x956A, 0x5F6C, 0x956B, 0x658C, 0x956C, 0x6D5C, + 0x956D, 0x7015, 0x956E, 0x8CA7, 0x956F, 0x8CD3, 0x9570, 0x983B, 0x9571, 0x654F, 0x9572, 0x74F6, 0x9573, 0x4E0D, 0x9574, 0x4ED8, + 0x9575, 0x57E0, 0x9576, 0x592B, 0x9577, 0x5A66, 0x9578, 0x5BCC, 0x9579, 0x51A8, 0x957A, 0x5E03, 0x957B, 0x5E9C, 0x957C, 0x6016, + 0x957D, 0x6276, 0x957E, 0x6577, 0x9580, 0x65A7, 0x9581, 0x666E, 0x9582, 0x6D6E, 0x9583, 0x7236, 0x9584, 0x7B26, 0x9585, 0x8150, + 0x9586, 0x819A, 0x9587, 0x8299, 0x9588, 0x8B5C, 0x9589, 0x8CA0, 0x958A, 0x8CE6, 0x958B, 0x8D74, 0x958C, 0x961C, 0x958D, 0x9644, + 0x958E, 0x4FAE, 0x958F, 0x64AB, 0x9590, 0x6B66, 0x9591, 0x821E, 0x9592, 0x8461, 0x9593, 0x856A, 0x9594, 0x90E8, 0x9595, 0x5C01, + 0x9596, 0x6953, 0x9597, 0x98A8, 0x9598, 0x847A, 0x9599, 0x8557, 0x959A, 0x4F0F, 0x959B, 0x526F, 0x959C, 0x5FA9, 0x959D, 0x5E45, + 0x959E, 0x670D, 0x959F, 0x798F, 0x95A0, 0x8179, 0x95A1, 0x8907, 0x95A2, 0x8986, 0x95A3, 0x6DF5, 0x95A4, 0x5F17, 0x95A5, 0x6255, + 0x95A6, 0x6CB8, 0x95A7, 0x4ECF, 0x95A8, 0x7269, 0x95A9, 0x9B92, 0x95AA, 0x5206, 0x95AB, 0x543B, 0x95AC, 0x5674, 0x95AD, 0x58B3, + 0x95AE, 0x61A4, 0x95AF, 0x626E, 0x95B0, 0x711A, 0x95B1, 0x596E, 0x95B2, 0x7C89, 0x95B3, 0x7CDE, 0x95B4, 0x7D1B, 0x95B5, 0x96F0, + 0x95B6, 0x6587, 0x95B7, 0x805E, 0x95B8, 0x4E19, 0x95B9, 0x4F75, 0x95BA, 0x5175, 0x95BB, 0x5840, 0x95BC, 0x5E63, 0x95BD, 0x5E73, + 0x95BE, 0x5F0A, 0x95BF, 0x67C4, 0x95C0, 0x4E26, 0x95C1, 0x853D, 0x95C2, 0x9589, 0x95C3, 0x965B, 0x95C4, 0x7C73, 0x95C5, 0x9801, + 0x95C6, 0x50FB, 0x95C7, 0x58C1, 0x95C8, 0x7656, 0x95C9, 0x78A7, 0x95CA, 0x5225, 0x95CB, 0x77A5, 0x95CC, 0x8511, 0x95CD, 0x7B86, + 0x95CE, 0x504F, 0x95CF, 0x5909, 0x95D0, 0x7247, 0x95D1, 0x7BC7, 0x95D2, 0x7DE8, 0x95D3, 0x8FBA, 0x95D4, 0x8FD4, 0x95D5, 0x904D, + 0x95D6, 0x4FBF, 0x95D7, 0x52C9, 0x95D8, 0x5A29, 0x95D9, 0x5F01, 0x95DA, 0x97AD, 0x95DB, 0x4FDD, 0x95DC, 0x8217, 0x95DD, 0x92EA, + 0x95DE, 0x5703, 0x95DF, 0x6355, 0x95E0, 0x6B69, 0x95E1, 0x752B, 0x95E2, 0x88DC, 0x95E3, 0x8F14, 0x95E4, 0x7A42, 0x95E5, 0x52DF, + 0x95E6, 0x5893, 0x95E7, 0x6155, 0x95E8, 0x620A, 0x95E9, 0x66AE, 0x95EA, 0x6BCD, 0x95EB, 0x7C3F, 0x95EC, 0x83E9, 0x95ED, 0x5023, + 0x95EE, 0x4FF8, 0x95EF, 0x5305, 0x95F0, 0x5446, 0x95F1, 0x5831, 0x95F2, 0x5949, 0x95F3, 0x5B9D, 0x95F4, 0x5CF0, 0x95F5, 0x5CEF, + 0x95F6, 0x5D29, 0x95F7, 0x5E96, 0x95F8, 0x62B1, 0x95F9, 0x6367, 0x95FA, 0x653E, 0x95FB, 0x65B9, 0x95FC, 0x670B, 0x9640, 0x6CD5, + 0x9641, 0x6CE1, 0x9642, 0x70F9, 0x9643, 0x7832, 0x9644, 0x7E2B, 0x9645, 0x80DE, 0x9646, 0x82B3, 0x9647, 0x840C, 0x9648, 0x84EC, + 0x9649, 0x8702, 0x964A, 0x8912, 0x964B, 0x8A2A, 0x964C, 0x8C4A, 0x964D, 0x90A6, 0x964E, 0x92D2, 0x964F, 0x98FD, 0x9650, 0x9CF3, + 0x9651, 0x9D6C, 0x9652, 0x4E4F, 0x9653, 0x4EA1, 0x9654, 0x508D, 0x9655, 0x5256, 0x9656, 0x574A, 0x9657, 0x59A8, 0x9658, 0x5E3D, + 0x9659, 0x5FD8, 0x965A, 0x5FD9, 0x965B, 0x623F, 0x965C, 0x66B4, 0x965D, 0x671B, 0x965E, 0x67D0, 0x965F, 0x68D2, 0x9660, 0x5192, + 0x9661, 0x7D21, 0x9662, 0x80AA, 0x9663, 0x81A8, 0x9664, 0x8B00, 0x9665, 0x8C8C, 0x9666, 0x8CBF, 0x9667, 0x927E, 0x9668, 0x9632, + 0x9669, 0x5420, 0x966A, 0x982C, 0x966B, 0x5317, 0x966C, 0x50D5, 0x966D, 0x535C, 0x966E, 0x58A8, 0x966F, 0x64B2, 0x9670, 0x6734, + 0x9671, 0x7267, 0x9672, 0x7766, 0x9673, 0x7A46, 0x9674, 0x91E6, 0x9675, 0x52C3, 0x9676, 0x6CA1, 0x9677, 0x6B86, 0x9678, 0x5800, + 0x9679, 0x5E4C, 0x967A, 0x5954, 0x967B, 0x672C, 0x967C, 0x7FFB, 0x967D, 0x51E1, 0x967E, 0x76C6, 0x9680, 0x6469, 0x9681, 0x78E8, + 0x9682, 0x9B54, 0x9683, 0x9EBB, 0x9684, 0x57CB, 0x9685, 0x59B9, 0x9686, 0x6627, 0x9687, 0x679A, 0x9688, 0x6BCE, 0x9689, 0x54E9, + 0x968A, 0x69D9, 0x968B, 0x5E55, 0x968C, 0x819C, 0x968D, 0x6795, 0x968E, 0x9BAA, 0x968F, 0x67FE, 0x9690, 0x9C52, 0x9691, 0x685D, + 0x9692, 0x4EA6, 0x9693, 0x4FE3, 0x9694, 0x53C8, 0x9695, 0x62B9, 0x9696, 0x672B, 0x9697, 0x6CAB, 0x9698, 0x8FC4, 0x9699, 0x4FAD, + 0x969A, 0x7E6D, 0x969B, 0x9EBF, 0x969C, 0x4E07, 0x969D, 0x6162, 0x969E, 0x6E80, 0x969F, 0x6F2B, 0x96A0, 0x8513, 0x96A1, 0x5473, + 0x96A2, 0x672A, 0x96A3, 0x9B45, 0x96A4, 0x5DF3, 0x96A5, 0x7B95, 0x96A6, 0x5CAC, 0x96A7, 0x5BC6, 0x96A8, 0x871C, 0x96A9, 0x6E4A, + 0x96AA, 0x84D1, 0x96AB, 0x7A14, 0x96AC, 0x8108, 0x96AD, 0x5999, 0x96AE, 0x7C8D, 0x96AF, 0x6C11, 0x96B0, 0x7720, 0x96B1, 0x52D9, + 0x96B2, 0x5922, 0x96B3, 0x7121, 0x96B4, 0x725F, 0x96B5, 0x77DB, 0x96B6, 0x9727, 0x96B7, 0x9D61, 0x96B8, 0x690B, 0x96B9, 0x5A7F, + 0x96BA, 0x5A18, 0x96BB, 0x51A5, 0x96BC, 0x540D, 0x96BD, 0x547D, 0x96BE, 0x660E, 0x96BF, 0x76DF, 0x96C0, 0x8FF7, 0x96C1, 0x9298, + 0x96C2, 0x9CF4, 0x96C3, 0x59EA, 0x96C4, 0x725D, 0x96C5, 0x6EC5, 0x96C6, 0x514D, 0x96C7, 0x68C9, 0x96C8, 0x7DBF, 0x96C9, 0x7DEC, + 0x96CA, 0x9762, 0x96CB, 0x9EBA, 0x96CC, 0x6478, 0x96CD, 0x6A21, 0x96CE, 0x8302, 0x96CF, 0x5984, 0x96D0, 0x5B5F, 0x96D1, 0x6BDB, + 0x96D2, 0x731B, 0x96D3, 0x76F2, 0x96D4, 0x7DB2, 0x96D5, 0x8017, 0x96D6, 0x8499, 0x96D7, 0x5132, 0x96D8, 0x6728, 0x96D9, 0x9ED9, + 0x96DA, 0x76EE, 0x96DB, 0x6762, 0x96DC, 0x52FF, 0x96DD, 0x9905, 0x96DE, 0x5C24, 0x96DF, 0x623B, 0x96E0, 0x7C7E, 0x96E1, 0x8CB0, + 0x96E2, 0x554F, 0x96E3, 0x60B6, 0x96E4, 0x7D0B, 0x96E5, 0x9580, 0x96E6, 0x5301, 0x96E7, 0x4E5F, 0x96E8, 0x51B6, 0x96E9, 0x591C, + 0x96EA, 0x723A, 0x96EB, 0x8036, 0x96EC, 0x91CE, 0x96ED, 0x5F25, 0x96EE, 0x77E2, 0x96EF, 0x5384, 0x96F0, 0x5F79, 0x96F1, 0x7D04, + 0x96F2, 0x85AC, 0x96F3, 0x8A33, 0x96F4, 0x8E8D, 0x96F5, 0x9756, 0x96F6, 0x67F3, 0x96F7, 0x85AE, 0x96F8, 0x9453, 0x96F9, 0x6109, + 0x96FA, 0x6108, 0x96FB, 0x6CB9, 0x96FC, 0x7652, 0x9740, 0x8AED, 0x9741, 0x8F38, 0x9742, 0x552F, 0x9743, 0x4F51, 0x9744, 0x512A, + 0x9745, 0x52C7, 0x9746, 0x53CB, 0x9747, 0x5BA5, 0x9748, 0x5E7D, 0x9749, 0x60A0, 0x974A, 0x6182, 0x974B, 0x63D6, 0x974C, 0x6709, + 0x974D, 0x67DA, 0x974E, 0x6E67, 0x974F, 0x6D8C, 0x9750, 0x7336, 0x9751, 0x7337, 0x9752, 0x7531, 0x9753, 0x7950, 0x9754, 0x88D5, + 0x9755, 0x8A98, 0x9756, 0x904A, 0x9757, 0x9091, 0x9758, 0x90F5, 0x9759, 0x96C4, 0x975A, 0x878D, 0x975B, 0x5915, 0x975C, 0x4E88, + 0x975D, 0x4F59, 0x975E, 0x4E0E, 0x975F, 0x8A89, 0x9760, 0x8F3F, 0x9761, 0x9810, 0x9762, 0x50AD, 0x9763, 0x5E7C, 0x9764, 0x5996, + 0x9765, 0x5BB9, 0x9766, 0x5EB8, 0x9767, 0x63DA, 0x9768, 0x63FA, 0x9769, 0x64C1, 0x976A, 0x66DC, 0x976B, 0x694A, 0x976C, 0x69D8, + 0x976D, 0x6D0B, 0x976E, 0x6EB6, 0x976F, 0x7194, 0x9770, 0x7528, 0x9771, 0x7AAF, 0x9772, 0x7F8A, 0x9773, 0x8000, 0x9774, 0x8449, + 0x9775, 0x84C9, 0x9776, 0x8981, 0x9777, 0x8B21, 0x9778, 0x8E0A, 0x9779, 0x9065, 0x977A, 0x967D, 0x977B, 0x990A, 0x977C, 0x617E, + 0x977D, 0x6291, 0x977E, 0x6B32, 0x9780, 0x6C83, 0x9781, 0x6D74, 0x9782, 0x7FCC, 0x9783, 0x7FFC, 0x9784, 0x6DC0, 0x9785, 0x7F85, + 0x9786, 0x87BA, 0x9787, 0x88F8, 0x9788, 0x6765, 0x9789, 0x83B1, 0x978A, 0x983C, 0x978B, 0x96F7, 0x978C, 0x6D1B, 0x978D, 0x7D61, + 0x978E, 0x843D, 0x978F, 0x916A, 0x9790, 0x4E71, 0x9791, 0x5375, 0x9792, 0x5D50, 0x9793, 0x6B04, 0x9794, 0x6FEB, 0x9795, 0x85CD, + 0x9796, 0x862D, 0x9797, 0x89A7, 0x9798, 0x5229, 0x9799, 0x540F, 0x979A, 0x5C65, 0x979B, 0x674E, 0x979C, 0x68A8, 0x979D, 0x7406, + 0x979E, 0x7483, 0x979F, 0x75E2, 0x97A0, 0x88CF, 0x97A1, 0x88E1, 0x97A2, 0x91CC, 0x97A3, 0x96E2, 0x97A4, 0x9678, 0x97A5, 0x5F8B, + 0x97A6, 0x7387, 0x97A7, 0x7ACB, 0x97A8, 0x844E, 0x97A9, 0x63A0, 0x97AA, 0x7565, 0x97AB, 0x5289, 0x97AC, 0x6D41, 0x97AD, 0x6E9C, + 0x97AE, 0x7409, 0x97AF, 0x7559, 0x97B0, 0x786B, 0x97B1, 0x7C92, 0x97B2, 0x9686, 0x97B3, 0x7ADC, 0x97B4, 0x9F8D, 0x97B5, 0x4FB6, + 0x97B6, 0x616E, 0x97B7, 0x65C5, 0x97B8, 0x865C, 0x97B9, 0x4E86, 0x97BA, 0x4EAE, 0x97BB, 0x50DA, 0x97BC, 0x4E21, 0x97BD, 0x51CC, + 0x97BE, 0x5BEE, 0x97BF, 0x6599, 0x97C0, 0x6881, 0x97C1, 0x6DBC, 0x97C2, 0x731F, 0x97C3, 0x7642, 0x97C4, 0x77AD, 0x97C5, 0x7A1C, + 0x97C6, 0x7CE7, 0x97C7, 0x826F, 0x97C8, 0x8AD2, 0x97C9, 0x907C, 0x97CA, 0x91CF, 0x97CB, 0x9675, 0x97CC, 0x9818, 0x97CD, 0x529B, + 0x97CE, 0x7DD1, 0x97CF, 0x502B, 0x97D0, 0x5398, 0x97D1, 0x6797, 0x97D2, 0x6DCB, 0x97D3, 0x71D0, 0x97D4, 0x7433, 0x97D5, 0x81E8, + 0x97D6, 0x8F2A, 0x97D7, 0x96A3, 0x97D8, 0x9C57, 0x97D9, 0x9E9F, 0x97DA, 0x7460, 0x97DB, 0x5841, 0x97DC, 0x6D99, 0x97DD, 0x7D2F, + 0x97DE, 0x985E, 0x97DF, 0x4EE4, 0x97E0, 0x4F36, 0x97E1, 0x4F8B, 0x97E2, 0x51B7, 0x97E3, 0x52B1, 0x97E4, 0x5DBA, 0x97E5, 0x601C, + 0x97E6, 0x73B2, 0x97E7, 0x793C, 0x97E8, 0x82D3, 0x97E9, 0x9234, 0x97EA, 0x96B7, 0x97EB, 0x96F6, 0x97EC, 0x970A, 0x97ED, 0x9E97, + 0x97EE, 0x9F62, 0x97EF, 0x66A6, 0x97F0, 0x6B74, 0x97F1, 0x5217, 0x97F2, 0x52A3, 0x97F3, 0x70C8, 0x97F4, 0x88C2, 0x97F5, 0x5EC9, + 0x97F6, 0x604B, 0x97F7, 0x6190, 0x97F8, 0x6F23, 0x97F9, 0x7149, 0x97FA, 0x7C3E, 0x97FB, 0x7DF4, 0x97FC, 0x806F, 0x9840, 0x84EE, + 0x9841, 0x9023, 0x9842, 0x932C, 0x9843, 0x5442, 0x9844, 0x9B6F, 0x9845, 0x6AD3, 0x9846, 0x7089, 0x9847, 0x8CC2, 0x9848, 0x8DEF, + 0x9849, 0x9732, 0x984A, 0x52B4, 0x984B, 0x5A41, 0x984C, 0x5ECA, 0x984D, 0x5F04, 0x984E, 0x6717, 0x984F, 0x697C, 0x9850, 0x6994, + 0x9851, 0x6D6A, 0x9852, 0x6F0F, 0x9853, 0x7262, 0x9854, 0x72FC, 0x9855, 0x7BED, 0x9856, 0x8001, 0x9857, 0x807E, 0x9858, 0x874B, + 0x9859, 0x90CE, 0x985A, 0x516D, 0x985B, 0x9E93, 0x985C, 0x7984, 0x985D, 0x808B, 0x985E, 0x9332, 0x985F, 0x8AD6, 0x9860, 0x502D, + 0x9861, 0x548C, 0x9862, 0x8A71, 0x9863, 0x6B6A, 0x9864, 0x8CC4, 0x9865, 0x8107, 0x9866, 0x60D1, 0x9867, 0x67A0, 0x9868, 0x9DF2, + 0x9869, 0x4E99, 0x986A, 0x4E98, 0x986B, 0x9C10, 0x986C, 0x8A6B, 0x986D, 0x85C1, 0x986E, 0x8568, 0x986F, 0x6900, 0x9870, 0x6E7E, + 0x9871, 0x7897, 0x9872, 0x8155, 0x989F, 0x5F0C, 0x98A0, 0x4E10, 0x98A1, 0x4E15, 0x98A2, 0x4E2A, 0x98A3, 0x4E31, 0x98A4, 0x4E36, + 0x98A5, 0x4E3C, 0x98A6, 0x4E3F, 0x98A7, 0x4E42, 0x98A8, 0x4E56, 0x98A9, 0x4E58, 0x98AA, 0x4E82, 0x98AB, 0x4E85, 0x98AC, 0x8C6B, + 0x98AD, 0x4E8A, 0x98AE, 0x8212, 0x98AF, 0x5F0D, 0x98B0, 0x4E8E, 0x98B1, 0x4E9E, 0x98B2, 0x4E9F, 0x98B3, 0x4EA0, 0x98B4, 0x4EA2, + 0x98B5, 0x4EB0, 0x98B6, 0x4EB3, 0x98B7, 0x4EB6, 0x98B8, 0x4ECE, 0x98B9, 0x4ECD, 0x98BA, 0x4EC4, 0x98BB, 0x4EC6, 0x98BC, 0x4EC2, + 0x98BD, 0x4ED7, 0x98BE, 0x4EDE, 0x98BF, 0x4EED, 0x98C0, 0x4EDF, 0x98C1, 0x4EF7, 0x98C2, 0x4F09, 0x98C3, 0x4F5A, 0x98C4, 0x4F30, + 0x98C5, 0x4F5B, 0x98C6, 0x4F5D, 0x98C7, 0x4F57, 0x98C8, 0x4F47, 0x98C9, 0x4F76, 0x98CA, 0x4F88, 0x98CB, 0x4F8F, 0x98CC, 0x4F98, + 0x98CD, 0x4F7B, 0x98CE, 0x4F69, 0x98CF, 0x4F70, 0x98D0, 0x4F91, 0x98D1, 0x4F6F, 0x98D2, 0x4F86, 0x98D3, 0x4F96, 0x98D4, 0x5118, + 0x98D5, 0x4FD4, 0x98D6, 0x4FDF, 0x98D7, 0x4FCE, 0x98D8, 0x4FD8, 0x98D9, 0x4FDB, 0x98DA, 0x4FD1, 0x98DB, 0x4FDA, 0x98DC, 0x4FD0, + 0x98DD, 0x4FE4, 0x98DE, 0x4FE5, 0x98DF, 0x501A, 0x98E0, 0x5028, 0x98E1, 0x5014, 0x98E2, 0x502A, 0x98E3, 0x5025, 0x98E4, 0x5005, + 0x98E5, 0x4F1C, 0x98E6, 0x4FF6, 0x98E7, 0x5021, 0x98E8, 0x5029, 0x98E9, 0x502C, 0x98EA, 0x4FFE, 0x98EB, 0x4FEF, 0x98EC, 0x5011, + 0x98ED, 0x5006, 0x98EE, 0x5043, 0x98EF, 0x5047, 0x98F0, 0x6703, 0x98F1, 0x5055, 0x98F2, 0x5050, 0x98F3, 0x5048, 0x98F4, 0x505A, + 0x98F5, 0x5056, 0x98F6, 0x506C, 0x98F7, 0x5078, 0x98F8, 0x5080, 0x98F9, 0x509A, 0x98FA, 0x5085, 0x98FB, 0x50B4, 0x98FC, 0x50B2, + 0x9940, 0x50C9, 0x9941, 0x50CA, 0x9942, 0x50B3, 0x9943, 0x50C2, 0x9944, 0x50D6, 0x9945, 0x50DE, 0x9946, 0x50E5, 0x9947, 0x50ED, + 0x9948, 0x50E3, 0x9949, 0x50EE, 0x994A, 0x50F9, 0x994B, 0x50F5, 0x994C, 0x5109, 0x994D, 0x5101, 0x994E, 0x5102, 0x994F, 0x5116, + 0x9950, 0x5115, 0x9951, 0x5114, 0x9952, 0x511A, 0x9953, 0x5121, 0x9954, 0x513A, 0x9955, 0x5137, 0x9956, 0x513C, 0x9957, 0x513B, + 0x9958, 0x513F, 0x9959, 0x5140, 0x995A, 0x5152, 0x995B, 0x514C, 0x995C, 0x5154, 0x995D, 0x5162, 0x995E, 0x7AF8, 0x995F, 0x5169, + 0x9960, 0x516A, 0x9961, 0x516E, 0x9962, 0x5180, 0x9963, 0x5182, 0x9964, 0x56D8, 0x9965, 0x518C, 0x9966, 0x5189, 0x9967, 0x518F, + 0x9968, 0x5191, 0x9969, 0x5193, 0x996A, 0x5195, 0x996B, 0x5196, 0x996C, 0x51A4, 0x996D, 0x51A6, 0x996E, 0x51A2, 0x996F, 0x51A9, + 0x9970, 0x51AA, 0x9971, 0x51AB, 0x9972, 0x51B3, 0x9973, 0x51B1, 0x9974, 0x51B2, 0x9975, 0x51B0, 0x9976, 0x51B5, 0x9977, 0x51BD, + 0x9978, 0x51C5, 0x9979, 0x51C9, 0x997A, 0x51DB, 0x997B, 0x51E0, 0x997C, 0x8655, 0x997D, 0x51E9, 0x997E, 0x51ED, 0x9980, 0x51F0, + 0x9981, 0x51F5, 0x9982, 0x51FE, 0x9983, 0x5204, 0x9984, 0x520B, 0x9985, 0x5214, 0x9986, 0x520E, 0x9987, 0x5227, 0x9988, 0x522A, + 0x9989, 0x522E, 0x998A, 0x5233, 0x998B, 0x5239, 0x998C, 0x524F, 0x998D, 0x5244, 0x998E, 0x524B, 0x998F, 0x524C, 0x9990, 0x525E, + 0x9991, 0x5254, 0x9992, 0x526A, 0x9993, 0x5274, 0x9994, 0x5269, 0x9995, 0x5273, 0x9996, 0x527F, 0x9997, 0x527D, 0x9998, 0x528D, + 0x9999, 0x5294, 0x999A, 0x5292, 0x999B, 0x5271, 0x999C, 0x5288, 0x999D, 0x5291, 0x999E, 0x8FA8, 0x999F, 0x8FA7, 0x99A0, 0x52AC, + 0x99A1, 0x52AD, 0x99A2, 0x52BC, 0x99A3, 0x52B5, 0x99A4, 0x52C1, 0x99A5, 0x52CD, 0x99A6, 0x52D7, 0x99A7, 0x52DE, 0x99A8, 0x52E3, + 0x99A9, 0x52E6, 0x99AA, 0x98ED, 0x99AB, 0x52E0, 0x99AC, 0x52F3, 0x99AD, 0x52F5, 0x99AE, 0x52F8, 0x99AF, 0x52F9, 0x99B0, 0x5306, + 0x99B1, 0x5308, 0x99B2, 0x7538, 0x99B3, 0x530D, 0x99B4, 0x5310, 0x99B5, 0x530F, 0x99B6, 0x5315, 0x99B7, 0x531A, 0x99B8, 0x5323, + 0x99B9, 0x532F, 0x99BA, 0x5331, 0x99BB, 0x5333, 0x99BC, 0x5338, 0x99BD, 0x5340, 0x99BE, 0x5346, 0x99BF, 0x5345, 0x99C0, 0x4E17, + 0x99C1, 0x5349, 0x99C2, 0x534D, 0x99C3, 0x51D6, 0x99C4, 0x535E, 0x99C5, 0x5369, 0x99C6, 0x536E, 0x99C7, 0x5918, 0x99C8, 0x537B, + 0x99C9, 0x5377, 0x99CA, 0x5382, 0x99CB, 0x5396, 0x99CC, 0x53A0, 0x99CD, 0x53A6, 0x99CE, 0x53A5, 0x99CF, 0x53AE, 0x99D0, 0x53B0, + 0x99D1, 0x53B6, 0x99D2, 0x53C3, 0x99D3, 0x7C12, 0x99D4, 0x96D9, 0x99D5, 0x53DF, 0x99D6, 0x66FC, 0x99D7, 0x71EE, 0x99D8, 0x53EE, + 0x99D9, 0x53E8, 0x99DA, 0x53ED, 0x99DB, 0x53FA, 0x99DC, 0x5401, 0x99DD, 0x543D, 0x99DE, 0x5440, 0x99DF, 0x542C, 0x99E0, 0x542D, + 0x99E1, 0x543C, 0x99E2, 0x542E, 0x99E3, 0x5436, 0x99E4, 0x5429, 0x99E5, 0x541D, 0x99E6, 0x544E, 0x99E7, 0x548F, 0x99E8, 0x5475, + 0x99E9, 0x548E, 0x99EA, 0x545F, 0x99EB, 0x5471, 0x99EC, 0x5477, 0x99ED, 0x5470, 0x99EE, 0x5492, 0x99EF, 0x547B, 0x99F0, 0x5480, + 0x99F1, 0x5476, 0x99F2, 0x5484, 0x99F3, 0x5490, 0x99F4, 0x5486, 0x99F5, 0x54C7, 0x99F6, 0x54A2, 0x99F7, 0x54B8, 0x99F8, 0x54A5, + 0x99F9, 0x54AC, 0x99FA, 0x54C4, 0x99FB, 0x54C8, 0x99FC, 0x54A8, 0x9A40, 0x54AB, 0x9A41, 0x54C2, 0x9A42, 0x54A4, 0x9A43, 0x54BE, + 0x9A44, 0x54BC, 0x9A45, 0x54D8, 0x9A46, 0x54E5, 0x9A47, 0x54E6, 0x9A48, 0x550F, 0x9A49, 0x5514, 0x9A4A, 0x54FD, 0x9A4B, 0x54EE, + 0x9A4C, 0x54ED, 0x9A4D, 0x54FA, 0x9A4E, 0x54E2, 0x9A4F, 0x5539, 0x9A50, 0x5540, 0x9A51, 0x5563, 0x9A52, 0x554C, 0x9A53, 0x552E, + 0x9A54, 0x555C, 0x9A55, 0x5545, 0x9A56, 0x5556, 0x9A57, 0x5557, 0x9A58, 0x5538, 0x9A59, 0x5533, 0x9A5A, 0x555D, 0x9A5B, 0x5599, + 0x9A5C, 0x5580, 0x9A5D, 0x54AF, 0x9A5E, 0x558A, 0x9A5F, 0x559F, 0x9A60, 0x557B, 0x9A61, 0x557E, 0x9A62, 0x5598, 0x9A63, 0x559E, + 0x9A64, 0x55AE, 0x9A65, 0x557C, 0x9A66, 0x5583, 0x9A67, 0x55A9, 0x9A68, 0x5587, 0x9A69, 0x55A8, 0x9A6A, 0x55DA, 0x9A6B, 0x55C5, + 0x9A6C, 0x55DF, 0x9A6D, 0x55C4, 0x9A6E, 0x55DC, 0x9A6F, 0x55E4, 0x9A70, 0x55D4, 0x9A71, 0x5614, 0x9A72, 0x55F7, 0x9A73, 0x5616, + 0x9A74, 0x55FE, 0x9A75, 0x55FD, 0x9A76, 0x561B, 0x9A77, 0x55F9, 0x9A78, 0x564E, 0x9A79, 0x5650, 0x9A7A, 0x71DF, 0x9A7B, 0x5634, + 0x9A7C, 0x5636, 0x9A7D, 0x5632, 0x9A7E, 0x5638, 0x9A80, 0x566B, 0x9A81, 0x5664, 0x9A82, 0x562F, 0x9A83, 0x566C, 0x9A84, 0x566A, + 0x9A85, 0x5686, 0x9A86, 0x5680, 0x9A87, 0x568A, 0x9A88, 0x56A0, 0x9A89, 0x5694, 0x9A8A, 0x568F, 0x9A8B, 0x56A5, 0x9A8C, 0x56AE, + 0x9A8D, 0x56B6, 0x9A8E, 0x56B4, 0x9A8F, 0x56C2, 0x9A90, 0x56BC, 0x9A91, 0x56C1, 0x9A92, 0x56C3, 0x9A93, 0x56C0, 0x9A94, 0x56C8, + 0x9A95, 0x56CE, 0x9A96, 0x56D1, 0x9A97, 0x56D3, 0x9A98, 0x56D7, 0x9A99, 0x56EE, 0x9A9A, 0x56F9, 0x9A9B, 0x5700, 0x9A9C, 0x56FF, + 0x9A9D, 0x5704, 0x9A9E, 0x5709, 0x9A9F, 0x5708, 0x9AA0, 0x570B, 0x9AA1, 0x570D, 0x9AA2, 0x5713, 0x9AA3, 0x5718, 0x9AA4, 0x5716, + 0x9AA5, 0x55C7, 0x9AA6, 0x571C, 0x9AA7, 0x5726, 0x9AA8, 0x5737, 0x9AA9, 0x5738, 0x9AAA, 0x574E, 0x9AAB, 0x573B, 0x9AAC, 0x5740, + 0x9AAD, 0x574F, 0x9AAE, 0x5769, 0x9AAF, 0x57C0, 0x9AB0, 0x5788, 0x9AB1, 0x5761, 0x9AB2, 0x577F, 0x9AB3, 0x5789, 0x9AB4, 0x5793, + 0x9AB5, 0x57A0, 0x9AB6, 0x57B3, 0x9AB7, 0x57A4, 0x9AB8, 0x57AA, 0x9AB9, 0x57B0, 0x9ABA, 0x57C3, 0x9ABB, 0x57C6, 0x9ABC, 0x57D4, + 0x9ABD, 0x57D2, 0x9ABE, 0x57D3, 0x9ABF, 0x580A, 0x9AC0, 0x57D6, 0x9AC1, 0x57E3, 0x9AC2, 0x580B, 0x9AC3, 0x5819, 0x9AC4, 0x581D, + 0x9AC5, 0x5872, 0x9AC6, 0x5821, 0x9AC7, 0x5862, 0x9AC8, 0x584B, 0x9AC9, 0x5870, 0x9ACA, 0x6BC0, 0x9ACB, 0x5852, 0x9ACC, 0x583D, + 0x9ACD, 0x5879, 0x9ACE, 0x5885, 0x9ACF, 0x58B9, 0x9AD0, 0x589F, 0x9AD1, 0x58AB, 0x9AD2, 0x58BA, 0x9AD3, 0x58DE, 0x9AD4, 0x58BB, + 0x9AD5, 0x58B8, 0x9AD6, 0x58AE, 0x9AD7, 0x58C5, 0x9AD8, 0x58D3, 0x9AD9, 0x58D1, 0x9ADA, 0x58D7, 0x9ADB, 0x58D9, 0x9ADC, 0x58D8, + 0x9ADD, 0x58E5, 0x9ADE, 0x58DC, 0x9ADF, 0x58E4, 0x9AE0, 0x58DF, 0x9AE1, 0x58EF, 0x9AE2, 0x58FA, 0x9AE3, 0x58F9, 0x9AE4, 0x58FB, + 0x9AE5, 0x58FC, 0x9AE6, 0x58FD, 0x9AE7, 0x5902, 0x9AE8, 0x590A, 0x9AE9, 0x5910, 0x9AEA, 0x591B, 0x9AEB, 0x68A6, 0x9AEC, 0x5925, + 0x9AED, 0x592C, 0x9AEE, 0x592D, 0x9AEF, 0x5932, 0x9AF0, 0x5938, 0x9AF1, 0x593E, 0x9AF2, 0x7AD2, 0x9AF3, 0x5955, 0x9AF4, 0x5950, + 0x9AF5, 0x594E, 0x9AF6, 0x595A, 0x9AF7, 0x5958, 0x9AF8, 0x5962, 0x9AF9, 0x5960, 0x9AFA, 0x5967, 0x9AFB, 0x596C, 0x9AFC, 0x5969, + 0x9B40, 0x5978, 0x9B41, 0x5981, 0x9B42, 0x599D, 0x9B43, 0x4F5E, 0x9B44, 0x4FAB, 0x9B45, 0x59A3, 0x9B46, 0x59B2, 0x9B47, 0x59C6, + 0x9B48, 0x59E8, 0x9B49, 0x59DC, 0x9B4A, 0x598D, 0x9B4B, 0x59D9, 0x9B4C, 0x59DA, 0x9B4D, 0x5A25, 0x9B4E, 0x5A1F, 0x9B4F, 0x5A11, + 0x9B50, 0x5A1C, 0x9B51, 0x5A09, 0x9B52, 0x5A1A, 0x9B53, 0x5A40, 0x9B54, 0x5A6C, 0x9B55, 0x5A49, 0x9B56, 0x5A35, 0x9B57, 0x5A36, + 0x9B58, 0x5A62, 0x9B59, 0x5A6A, 0x9B5A, 0x5A9A, 0x9B5B, 0x5ABC, 0x9B5C, 0x5ABE, 0x9B5D, 0x5ACB, 0x9B5E, 0x5AC2, 0x9B5F, 0x5ABD, + 0x9B60, 0x5AE3, 0x9B61, 0x5AD7, 0x9B62, 0x5AE6, 0x9B63, 0x5AE9, 0x9B64, 0x5AD6, 0x9B65, 0x5AFA, 0x9B66, 0x5AFB, 0x9B67, 0x5B0C, + 0x9B68, 0x5B0B, 0x9B69, 0x5B16, 0x9B6A, 0x5B32, 0x9B6B, 0x5AD0, 0x9B6C, 0x5B2A, 0x9B6D, 0x5B36, 0x9B6E, 0x5B3E, 0x9B6F, 0x5B43, + 0x9B70, 0x5B45, 0x9B71, 0x5B40, 0x9B72, 0x5B51, 0x9B73, 0x5B55, 0x9B74, 0x5B5A, 0x9B75, 0x5B5B, 0x9B76, 0x5B65, 0x9B77, 0x5B69, + 0x9B78, 0x5B70, 0x9B79, 0x5B73, 0x9B7A, 0x5B75, 0x9B7B, 0x5B78, 0x9B7C, 0x6588, 0x9B7D, 0x5B7A, 0x9B7E, 0x5B80, 0x9B80, 0x5B83, + 0x9B81, 0x5BA6, 0x9B82, 0x5BB8, 0x9B83, 0x5BC3, 0x9B84, 0x5BC7, 0x9B85, 0x5BC9, 0x9B86, 0x5BD4, 0x9B87, 0x5BD0, 0x9B88, 0x5BE4, + 0x9B89, 0x5BE6, 0x9B8A, 0x5BE2, 0x9B8B, 0x5BDE, 0x9B8C, 0x5BE5, 0x9B8D, 0x5BEB, 0x9B8E, 0x5BF0, 0x9B8F, 0x5BF6, 0x9B90, 0x5BF3, + 0x9B91, 0x5C05, 0x9B92, 0x5C07, 0x9B93, 0x5C08, 0x9B94, 0x5C0D, 0x9B95, 0x5C13, 0x9B96, 0x5C20, 0x9B97, 0x5C22, 0x9B98, 0x5C28, + 0x9B99, 0x5C38, 0x9B9A, 0x5C39, 0x9B9B, 0x5C41, 0x9B9C, 0x5C46, 0x9B9D, 0x5C4E, 0x9B9E, 0x5C53, 0x9B9F, 0x5C50, 0x9BA0, 0x5C4F, + 0x9BA1, 0x5B71, 0x9BA2, 0x5C6C, 0x9BA3, 0x5C6E, 0x9BA4, 0x4E62, 0x9BA5, 0x5C76, 0x9BA6, 0x5C79, 0x9BA7, 0x5C8C, 0x9BA8, 0x5C91, + 0x9BA9, 0x5C94, 0x9BAA, 0x599B, 0x9BAB, 0x5CAB, 0x9BAC, 0x5CBB, 0x9BAD, 0x5CB6, 0x9BAE, 0x5CBC, 0x9BAF, 0x5CB7, 0x9BB0, 0x5CC5, + 0x9BB1, 0x5CBE, 0x9BB2, 0x5CC7, 0x9BB3, 0x5CD9, 0x9BB4, 0x5CE9, 0x9BB5, 0x5CFD, 0x9BB6, 0x5CFA, 0x9BB7, 0x5CED, 0x9BB8, 0x5D8C, + 0x9BB9, 0x5CEA, 0x9BBA, 0x5D0B, 0x9BBB, 0x5D15, 0x9BBC, 0x5D17, 0x9BBD, 0x5D5C, 0x9BBE, 0x5D1F, 0x9BBF, 0x5D1B, 0x9BC0, 0x5D11, + 0x9BC1, 0x5D14, 0x9BC2, 0x5D22, 0x9BC3, 0x5D1A, 0x9BC4, 0x5D19, 0x9BC5, 0x5D18, 0x9BC6, 0x5D4C, 0x9BC7, 0x5D52, 0x9BC8, 0x5D4E, + 0x9BC9, 0x5D4B, 0x9BCA, 0x5D6C, 0x9BCB, 0x5D73, 0x9BCC, 0x5D76, 0x9BCD, 0x5D87, 0x9BCE, 0x5D84, 0x9BCF, 0x5D82, 0x9BD0, 0x5DA2, + 0x9BD1, 0x5D9D, 0x9BD2, 0x5DAC, 0x9BD3, 0x5DAE, 0x9BD4, 0x5DBD, 0x9BD5, 0x5D90, 0x9BD6, 0x5DB7, 0x9BD7, 0x5DBC, 0x9BD8, 0x5DC9, + 0x9BD9, 0x5DCD, 0x9BDA, 0x5DD3, 0x9BDB, 0x5DD2, 0x9BDC, 0x5DD6, 0x9BDD, 0x5DDB, 0x9BDE, 0x5DEB, 0x9BDF, 0x5DF2, 0x9BE0, 0x5DF5, + 0x9BE1, 0x5E0B, 0x9BE2, 0x5E1A, 0x9BE3, 0x5E19, 0x9BE4, 0x5E11, 0x9BE5, 0x5E1B, 0x9BE6, 0x5E36, 0x9BE7, 0x5E37, 0x9BE8, 0x5E44, + 0x9BE9, 0x5E43, 0x9BEA, 0x5E40, 0x9BEB, 0x5E4E, 0x9BEC, 0x5E57, 0x9BED, 0x5E54, 0x9BEE, 0x5E5F, 0x9BEF, 0x5E62, 0x9BF0, 0x5E64, + 0x9BF1, 0x5E47, 0x9BF2, 0x5E75, 0x9BF3, 0x5E76, 0x9BF4, 0x5E7A, 0x9BF5, 0x9EBC, 0x9BF6, 0x5E7F, 0x9BF7, 0x5EA0, 0x9BF8, 0x5EC1, + 0x9BF9, 0x5EC2, 0x9BFA, 0x5EC8, 0x9BFB, 0x5ED0, 0x9BFC, 0x5ECF, 0x9C40, 0x5ED6, 0x9C41, 0x5EE3, 0x9C42, 0x5EDD, 0x9C43, 0x5EDA, + 0x9C44, 0x5EDB, 0x9C45, 0x5EE2, 0x9C46, 0x5EE1, 0x9C47, 0x5EE8, 0x9C48, 0x5EE9, 0x9C49, 0x5EEC, 0x9C4A, 0x5EF1, 0x9C4B, 0x5EF3, + 0x9C4C, 0x5EF0, 0x9C4D, 0x5EF4, 0x9C4E, 0x5EF8, 0x9C4F, 0x5EFE, 0x9C50, 0x5F03, 0x9C51, 0x5F09, 0x9C52, 0x5F5D, 0x9C53, 0x5F5C, + 0x9C54, 0x5F0B, 0x9C55, 0x5F11, 0x9C56, 0x5F16, 0x9C57, 0x5F29, 0x9C58, 0x5F2D, 0x9C59, 0x5F38, 0x9C5A, 0x5F41, 0x9C5B, 0x5F48, + 0x9C5C, 0x5F4C, 0x9C5D, 0x5F4E, 0x9C5E, 0x5F2F, 0x9C5F, 0x5F51, 0x9C60, 0x5F56, 0x9C61, 0x5F57, 0x9C62, 0x5F59, 0x9C63, 0x5F61, + 0x9C64, 0x5F6D, 0x9C65, 0x5F73, 0x9C66, 0x5F77, 0x9C67, 0x5F83, 0x9C68, 0x5F82, 0x9C69, 0x5F7F, 0x9C6A, 0x5F8A, 0x9C6B, 0x5F88, + 0x9C6C, 0x5F91, 0x9C6D, 0x5F87, 0x9C6E, 0x5F9E, 0x9C6F, 0x5F99, 0x9C70, 0x5F98, 0x9C71, 0x5FA0, 0x9C72, 0x5FA8, 0x9C73, 0x5FAD, + 0x9C74, 0x5FBC, 0x9C75, 0x5FD6, 0x9C76, 0x5FFB, 0x9C77, 0x5FE4, 0x9C78, 0x5FF8, 0x9C79, 0x5FF1, 0x9C7A, 0x5FDD, 0x9C7B, 0x60B3, + 0x9C7C, 0x5FFF, 0x9C7D, 0x6021, 0x9C7E, 0x6060, 0x9C80, 0x6019, 0x9C81, 0x6010, 0x9C82, 0x6029, 0x9C83, 0x600E, 0x9C84, 0x6031, + 0x9C85, 0x601B, 0x9C86, 0x6015, 0x9C87, 0x602B, 0x9C88, 0x6026, 0x9C89, 0x600F, 0x9C8A, 0x603A, 0x9C8B, 0x605A, 0x9C8C, 0x6041, + 0x9C8D, 0x606A, 0x9C8E, 0x6077, 0x9C8F, 0x605F, 0x9C90, 0x604A, 0x9C91, 0x6046, 0x9C92, 0x604D, 0x9C93, 0x6063, 0x9C94, 0x6043, + 0x9C95, 0x6064, 0x9C96, 0x6042, 0x9C97, 0x606C, 0x9C98, 0x606B, 0x9C99, 0x6059, 0x9C9A, 0x6081, 0x9C9B, 0x608D, 0x9C9C, 0x60E7, + 0x9C9D, 0x6083, 0x9C9E, 0x609A, 0x9C9F, 0x6084, 0x9CA0, 0x609B, 0x9CA1, 0x6096, 0x9CA2, 0x6097, 0x9CA3, 0x6092, 0x9CA4, 0x60A7, + 0x9CA5, 0x608B, 0x9CA6, 0x60E1, 0x9CA7, 0x60B8, 0x9CA8, 0x60E0, 0x9CA9, 0x60D3, 0x9CAA, 0x60B4, 0x9CAB, 0x5FF0, 0x9CAC, 0x60BD, + 0x9CAD, 0x60C6, 0x9CAE, 0x60B5, 0x9CAF, 0x60D8, 0x9CB0, 0x614D, 0x9CB1, 0x6115, 0x9CB2, 0x6106, 0x9CB3, 0x60F6, 0x9CB4, 0x60F7, + 0x9CB5, 0x6100, 0x9CB6, 0x60F4, 0x9CB7, 0x60FA, 0x9CB8, 0x6103, 0x9CB9, 0x6121, 0x9CBA, 0x60FB, 0x9CBB, 0x60F1, 0x9CBC, 0x610D, + 0x9CBD, 0x610E, 0x9CBE, 0x6147, 0x9CBF, 0x613E, 0x9CC0, 0x6128, 0x9CC1, 0x6127, 0x9CC2, 0x614A, 0x9CC3, 0x613F, 0x9CC4, 0x613C, + 0x9CC5, 0x612C, 0x9CC6, 0x6134, 0x9CC7, 0x613D, 0x9CC8, 0x6142, 0x9CC9, 0x6144, 0x9CCA, 0x6173, 0x9CCB, 0x6177, 0x9CCC, 0x6158, + 0x9CCD, 0x6159, 0x9CCE, 0x615A, 0x9CCF, 0x616B, 0x9CD0, 0x6174, 0x9CD1, 0x616F, 0x9CD2, 0x6165, 0x9CD3, 0x6171, 0x9CD4, 0x615F, + 0x9CD5, 0x615D, 0x9CD6, 0x6153, 0x9CD7, 0x6175, 0x9CD8, 0x6199, 0x9CD9, 0x6196, 0x9CDA, 0x6187, 0x9CDB, 0x61AC, 0x9CDC, 0x6194, + 0x9CDD, 0x619A, 0x9CDE, 0x618A, 0x9CDF, 0x6191, 0x9CE0, 0x61AB, 0x9CE1, 0x61AE, 0x9CE2, 0x61CC, 0x9CE3, 0x61CA, 0x9CE4, 0x61C9, + 0x9CE5, 0x61F7, 0x9CE6, 0x61C8, 0x9CE7, 0x61C3, 0x9CE8, 0x61C6, 0x9CE9, 0x61BA, 0x9CEA, 0x61CB, 0x9CEB, 0x7F79, 0x9CEC, 0x61CD, + 0x9CED, 0x61E6, 0x9CEE, 0x61E3, 0x9CEF, 0x61F6, 0x9CF0, 0x61FA, 0x9CF1, 0x61F4, 0x9CF2, 0x61FF, 0x9CF3, 0x61FD, 0x9CF4, 0x61FC, + 0x9CF5, 0x61FE, 0x9CF6, 0x6200, 0x9CF7, 0x6208, 0x9CF8, 0x6209, 0x9CF9, 0x620D, 0x9CFA, 0x620C, 0x9CFB, 0x6214, 0x9CFC, 0x621B, + 0x9D40, 0x621E, 0x9D41, 0x6221, 0x9D42, 0x622A, 0x9D43, 0x622E, 0x9D44, 0x6230, 0x9D45, 0x6232, 0x9D46, 0x6233, 0x9D47, 0x6241, + 0x9D48, 0x624E, 0x9D49, 0x625E, 0x9D4A, 0x6263, 0x9D4B, 0x625B, 0x9D4C, 0x6260, 0x9D4D, 0x6268, 0x9D4E, 0x627C, 0x9D4F, 0x6282, + 0x9D50, 0x6289, 0x9D51, 0x627E, 0x9D52, 0x6292, 0x9D53, 0x6293, 0x9D54, 0x6296, 0x9D55, 0x62D4, 0x9D56, 0x6283, 0x9D57, 0x6294, + 0x9D58, 0x62D7, 0x9D59, 0x62D1, 0x9D5A, 0x62BB, 0x9D5B, 0x62CF, 0x9D5C, 0x62FF, 0x9D5D, 0x62C6, 0x9D5E, 0x64D4, 0x9D5F, 0x62C8, + 0x9D60, 0x62DC, 0x9D61, 0x62CC, 0x9D62, 0x62CA, 0x9D63, 0x62C2, 0x9D64, 0x62C7, 0x9D65, 0x629B, 0x9D66, 0x62C9, 0x9D67, 0x630C, + 0x9D68, 0x62EE, 0x9D69, 0x62F1, 0x9D6A, 0x6327, 0x9D6B, 0x6302, 0x9D6C, 0x6308, 0x9D6D, 0x62EF, 0x9D6E, 0x62F5, 0x9D6F, 0x6350, + 0x9D70, 0x633E, 0x9D71, 0x634D, 0x9D72, 0x641C, 0x9D73, 0x634F, 0x9D74, 0x6396, 0x9D75, 0x638E, 0x9D76, 0x6380, 0x9D77, 0x63AB, + 0x9D78, 0x6376, 0x9D79, 0x63A3, 0x9D7A, 0x638F, 0x9D7B, 0x6389, 0x9D7C, 0x639F, 0x9D7D, 0x63B5, 0x9D7E, 0x636B, 0x9D80, 0x6369, + 0x9D81, 0x63BE, 0x9D82, 0x63E9, 0x9D83, 0x63C0, 0x9D84, 0x63C6, 0x9D85, 0x63E3, 0x9D86, 0x63C9, 0x9D87, 0x63D2, 0x9D88, 0x63F6, + 0x9D89, 0x63C4, 0x9D8A, 0x6416, 0x9D8B, 0x6434, 0x9D8C, 0x6406, 0x9D8D, 0x6413, 0x9D8E, 0x6426, 0x9D8F, 0x6436, 0x9D90, 0x651D, + 0x9D91, 0x6417, 0x9D92, 0x6428, 0x9D93, 0x640F, 0x9D94, 0x6467, 0x9D95, 0x646F, 0x9D96, 0x6476, 0x9D97, 0x644E, 0x9D98, 0x652A, + 0x9D99, 0x6495, 0x9D9A, 0x6493, 0x9D9B, 0x64A5, 0x9D9C, 0x64A9, 0x9D9D, 0x6488, 0x9D9E, 0x64BC, 0x9D9F, 0x64DA, 0x9DA0, 0x64D2, + 0x9DA1, 0x64C5, 0x9DA2, 0x64C7, 0x9DA3, 0x64BB, 0x9DA4, 0x64D8, 0x9DA5, 0x64C2, 0x9DA6, 0x64F1, 0x9DA7, 0x64E7, 0x9DA8, 0x8209, + 0x9DA9, 0x64E0, 0x9DAA, 0x64E1, 0x9DAB, 0x62AC, 0x9DAC, 0x64E3, 0x9DAD, 0x64EF, 0x9DAE, 0x652C, 0x9DAF, 0x64F6, 0x9DB0, 0x64F4, + 0x9DB1, 0x64F2, 0x9DB2, 0x64FA, 0x9DB3, 0x6500, 0x9DB4, 0x64FD, 0x9DB5, 0x6518, 0x9DB6, 0x651C, 0x9DB7, 0x6505, 0x9DB8, 0x6524, + 0x9DB9, 0x6523, 0x9DBA, 0x652B, 0x9DBB, 0x6534, 0x9DBC, 0x6535, 0x9DBD, 0x6537, 0x9DBE, 0x6536, 0x9DBF, 0x6538, 0x9DC0, 0x754B, + 0x9DC1, 0x6548, 0x9DC2, 0x6556, 0x9DC3, 0x6555, 0x9DC4, 0x654D, 0x9DC5, 0x6558, 0x9DC6, 0x655E, 0x9DC7, 0x655D, 0x9DC8, 0x6572, + 0x9DC9, 0x6578, 0x9DCA, 0x6582, 0x9DCB, 0x6583, 0x9DCC, 0x8B8A, 0x9DCD, 0x659B, 0x9DCE, 0x659F, 0x9DCF, 0x65AB, 0x9DD0, 0x65B7, + 0x9DD1, 0x65C3, 0x9DD2, 0x65C6, 0x9DD3, 0x65C1, 0x9DD4, 0x65C4, 0x9DD5, 0x65CC, 0x9DD6, 0x65D2, 0x9DD7, 0x65DB, 0x9DD8, 0x65D9, + 0x9DD9, 0x65E0, 0x9DDA, 0x65E1, 0x9DDB, 0x65F1, 0x9DDC, 0x6772, 0x9DDD, 0x660A, 0x9DDE, 0x6603, 0x9DDF, 0x65FB, 0x9DE0, 0x6773, + 0x9DE1, 0x6635, 0x9DE2, 0x6636, 0x9DE3, 0x6634, 0x9DE4, 0x661C, 0x9DE5, 0x664F, 0x9DE6, 0x6644, 0x9DE7, 0x6649, 0x9DE8, 0x6641, + 0x9DE9, 0x665E, 0x9DEA, 0x665D, 0x9DEB, 0x6664, 0x9DEC, 0x6667, 0x9DED, 0x6668, 0x9DEE, 0x665F, 0x9DEF, 0x6662, 0x9DF0, 0x6670, + 0x9DF1, 0x6683, 0x9DF2, 0x6688, 0x9DF3, 0x668E, 0x9DF4, 0x6689, 0x9DF5, 0x6684, 0x9DF6, 0x6698, 0x9DF7, 0x669D, 0x9DF8, 0x66C1, + 0x9DF9, 0x66B9, 0x9DFA, 0x66C9, 0x9DFB, 0x66BE, 0x9DFC, 0x66BC, 0x9E40, 0x66C4, 0x9E41, 0x66B8, 0x9E42, 0x66D6, 0x9E43, 0x66DA, + 0x9E44, 0x66E0, 0x9E45, 0x663F, 0x9E46, 0x66E6, 0x9E47, 0x66E9, 0x9E48, 0x66F0, 0x9E49, 0x66F5, 0x9E4A, 0x66F7, 0x9E4B, 0x670F, + 0x9E4C, 0x6716, 0x9E4D, 0x671E, 0x9E4E, 0x6726, 0x9E4F, 0x6727, 0x9E50, 0x9738, 0x9E51, 0x672E, 0x9E52, 0x673F, 0x9E53, 0x6736, + 0x9E54, 0x6741, 0x9E55, 0x6738, 0x9E56, 0x6737, 0x9E57, 0x6746, 0x9E58, 0x675E, 0x9E59, 0x6760, 0x9E5A, 0x6759, 0x9E5B, 0x6763, + 0x9E5C, 0x6764, 0x9E5D, 0x6789, 0x9E5E, 0x6770, 0x9E5F, 0x67A9, 0x9E60, 0x677C, 0x9E61, 0x676A, 0x9E62, 0x678C, 0x9E63, 0x678B, + 0x9E64, 0x67A6, 0x9E65, 0x67A1, 0x9E66, 0x6785, 0x9E67, 0x67B7, 0x9E68, 0x67EF, 0x9E69, 0x67B4, 0x9E6A, 0x67EC, 0x9E6B, 0x67B3, + 0x9E6C, 0x67E9, 0x9E6D, 0x67B8, 0x9E6E, 0x67E4, 0x9E6F, 0x67DE, 0x9E70, 0x67DD, 0x9E71, 0x67E2, 0x9E72, 0x67EE, 0x9E73, 0x67B9, + 0x9E74, 0x67CE, 0x9E75, 0x67C6, 0x9E76, 0x67E7, 0x9E77, 0x6A9C, 0x9E78, 0x681E, 0x9E79, 0x6846, 0x9E7A, 0x6829, 0x9E7B, 0x6840, + 0x9E7C, 0x684D, 0x9E7D, 0x6832, 0x9E7E, 0x684E, 0x9E80, 0x68B3, 0x9E81, 0x682B, 0x9E82, 0x6859, 0x9E83, 0x6863, 0x9E84, 0x6877, + 0x9E85, 0x687F, 0x9E86, 0x689F, 0x9E87, 0x688F, 0x9E88, 0x68AD, 0x9E89, 0x6894, 0x9E8A, 0x689D, 0x9E8B, 0x689B, 0x9E8C, 0x6883, + 0x9E8D, 0x6AAE, 0x9E8E, 0x68B9, 0x9E8F, 0x6874, 0x9E90, 0x68B5, 0x9E91, 0x68A0, 0x9E92, 0x68BA, 0x9E93, 0x690F, 0x9E94, 0x688D, + 0x9E95, 0x687E, 0x9E96, 0x6901, 0x9E97, 0x68CA, 0x9E98, 0x6908, 0x9E99, 0x68D8, 0x9E9A, 0x6922, 0x9E9B, 0x6926, 0x9E9C, 0x68E1, + 0x9E9D, 0x690C, 0x9E9E, 0x68CD, 0x9E9F, 0x68D4, 0x9EA0, 0x68E7, 0x9EA1, 0x68D5, 0x9EA2, 0x6936, 0x9EA3, 0x6912, 0x9EA4, 0x6904, + 0x9EA5, 0x68D7, 0x9EA6, 0x68E3, 0x9EA7, 0x6925, 0x9EA8, 0x68F9, 0x9EA9, 0x68E0, 0x9EAA, 0x68EF, 0x9EAB, 0x6928, 0x9EAC, 0x692A, + 0x9EAD, 0x691A, 0x9EAE, 0x6923, 0x9EAF, 0x6921, 0x9EB0, 0x68C6, 0x9EB1, 0x6979, 0x9EB2, 0x6977, 0x9EB3, 0x695C, 0x9EB4, 0x6978, + 0x9EB5, 0x696B, 0x9EB6, 0x6954, 0x9EB7, 0x697E, 0x9EB8, 0x696E, 0x9EB9, 0x6939, 0x9EBA, 0x6974, 0x9EBB, 0x693D, 0x9EBC, 0x6959, + 0x9EBD, 0x6930, 0x9EBE, 0x6961, 0x9EBF, 0x695E, 0x9EC0, 0x695D, 0x9EC1, 0x6981, 0x9EC2, 0x696A, 0x9EC3, 0x69B2, 0x9EC4, 0x69AE, + 0x9EC5, 0x69D0, 0x9EC6, 0x69BF, 0x9EC7, 0x69C1, 0x9EC8, 0x69D3, 0x9EC9, 0x69BE, 0x9ECA, 0x69CE, 0x9ECB, 0x5BE8, 0x9ECC, 0x69CA, + 0x9ECD, 0x69DD, 0x9ECE, 0x69BB, 0x9ECF, 0x69C3, 0x9ED0, 0x69A7, 0x9ED1, 0x6A2E, 0x9ED2, 0x6991, 0x9ED3, 0x69A0, 0x9ED4, 0x699C, + 0x9ED5, 0x6995, 0x9ED6, 0x69B4, 0x9ED7, 0x69DE, 0x9ED8, 0x69E8, 0x9ED9, 0x6A02, 0x9EDA, 0x6A1B, 0x9EDB, 0x69FF, 0x9EDC, 0x6B0A, + 0x9EDD, 0x69F9, 0x9EDE, 0x69F2, 0x9EDF, 0x69E7, 0x9EE0, 0x6A05, 0x9EE1, 0x69B1, 0x9EE2, 0x6A1E, 0x9EE3, 0x69ED, 0x9EE4, 0x6A14, + 0x9EE5, 0x69EB, 0x9EE6, 0x6A0A, 0x9EE7, 0x6A12, 0x9EE8, 0x6AC1, 0x9EE9, 0x6A23, 0x9EEA, 0x6A13, 0x9EEB, 0x6A44, 0x9EEC, 0x6A0C, + 0x9EED, 0x6A72, 0x9EEE, 0x6A36, 0x9EEF, 0x6A78, 0x9EF0, 0x6A47, 0x9EF1, 0x6A62, 0x9EF2, 0x6A59, 0x9EF3, 0x6A66, 0x9EF4, 0x6A48, + 0x9EF5, 0x6A38, 0x9EF6, 0x6A22, 0x9EF7, 0x6A90, 0x9EF8, 0x6A8D, 0x9EF9, 0x6AA0, 0x9EFA, 0x6A84, 0x9EFB, 0x6AA2, 0x9EFC, 0x6AA3, + 0x9F40, 0x6A97, 0x9F41, 0x8617, 0x9F42, 0x6ABB, 0x9F43, 0x6AC3, 0x9F44, 0x6AC2, 0x9F45, 0x6AB8, 0x9F46, 0x6AB3, 0x9F47, 0x6AAC, + 0x9F48, 0x6ADE, 0x9F49, 0x6AD1, 0x9F4A, 0x6ADF, 0x9F4B, 0x6AAA, 0x9F4C, 0x6ADA, 0x9F4D, 0x6AEA, 0x9F4E, 0x6AFB, 0x9F4F, 0x6B05, + 0x9F50, 0x8616, 0x9F51, 0x6AFA, 0x9F52, 0x6B12, 0x9F53, 0x6B16, 0x9F54, 0x9B31, 0x9F55, 0x6B1F, 0x9F56, 0x6B38, 0x9F57, 0x6B37, + 0x9F58, 0x76DC, 0x9F59, 0x6B39, 0x9F5A, 0x98EE, 0x9F5B, 0x6B47, 0x9F5C, 0x6B43, 0x9F5D, 0x6B49, 0x9F5E, 0x6B50, 0x9F5F, 0x6B59, + 0x9F60, 0x6B54, 0x9F61, 0x6B5B, 0x9F62, 0x6B5F, 0x9F63, 0x6B61, 0x9F64, 0x6B78, 0x9F65, 0x6B79, 0x9F66, 0x6B7F, 0x9F67, 0x6B80, + 0x9F68, 0x6B84, 0x9F69, 0x6B83, 0x9F6A, 0x6B8D, 0x9F6B, 0x6B98, 0x9F6C, 0x6B95, 0x9F6D, 0x6B9E, 0x9F6E, 0x6BA4, 0x9F6F, 0x6BAA, + 0x9F70, 0x6BAB, 0x9F71, 0x6BAF, 0x9F72, 0x6BB2, 0x9F73, 0x6BB1, 0x9F74, 0x6BB3, 0x9F75, 0x6BB7, 0x9F76, 0x6BBC, 0x9F77, 0x6BC6, + 0x9F78, 0x6BCB, 0x9F79, 0x6BD3, 0x9F7A, 0x6BDF, 0x9F7B, 0x6BEC, 0x9F7C, 0x6BEB, 0x9F7D, 0x6BF3, 0x9F7E, 0x6BEF, 0x9F80, 0x9EBE, + 0x9F81, 0x6C08, 0x9F82, 0x6C13, 0x9F83, 0x6C14, 0x9F84, 0x6C1B, 0x9F85, 0x6C24, 0x9F86, 0x6C23, 0x9F87, 0x6C5E, 0x9F88, 0x6C55, + 0x9F89, 0x6C62, 0x9F8A, 0x6C6A, 0x9F8B, 0x6C82, 0x9F8C, 0x6C8D, 0x9F8D, 0x6C9A, 0x9F8E, 0x6C81, 0x9F8F, 0x6C9B, 0x9F90, 0x6C7E, + 0x9F91, 0x6C68, 0x9F92, 0x6C73, 0x9F93, 0x6C92, 0x9F94, 0x6C90, 0x9F95, 0x6CC4, 0x9F96, 0x6CF1, 0x9F97, 0x6CD3, 0x9F98, 0x6CBD, + 0x9F99, 0x6CD7, 0x9F9A, 0x6CC5, 0x9F9B, 0x6CDD, 0x9F9C, 0x6CAE, 0x9F9D, 0x6CB1, 0x9F9E, 0x6CBE, 0x9F9F, 0x6CBA, 0x9FA0, 0x6CDB, + 0x9FA1, 0x6CEF, 0x9FA2, 0x6CD9, 0x9FA3, 0x6CEA, 0x9FA4, 0x6D1F, 0x9FA5, 0x884D, 0x9FA6, 0x6D36, 0x9FA7, 0x6D2B, 0x9FA8, 0x6D3D, + 0x9FA9, 0x6D38, 0x9FAA, 0x6D19, 0x9FAB, 0x6D35, 0x9FAC, 0x6D33, 0x9FAD, 0x6D12, 0x9FAE, 0x6D0C, 0x9FAF, 0x6D63, 0x9FB0, 0x6D93, + 0x9FB1, 0x6D64, 0x9FB2, 0x6D5A, 0x9FB3, 0x6D79, 0x9FB4, 0x6D59, 0x9FB5, 0x6D8E, 0x9FB6, 0x6D95, 0x9FB7, 0x6FE4, 0x9FB8, 0x6D85, + 0x9FB9, 0x6DF9, 0x9FBA, 0x6E15, 0x9FBB, 0x6E0A, 0x9FBC, 0x6DB5, 0x9FBD, 0x6DC7, 0x9FBE, 0x6DE6, 0x9FBF, 0x6DB8, 0x9FC0, 0x6DC6, + 0x9FC1, 0x6DEC, 0x9FC2, 0x6DDE, 0x9FC3, 0x6DCC, 0x9FC4, 0x6DE8, 0x9FC5, 0x6DD2, 0x9FC6, 0x6DC5, 0x9FC7, 0x6DFA, 0x9FC8, 0x6DD9, + 0x9FC9, 0x6DE4, 0x9FCA, 0x6DD5, 0x9FCB, 0x6DEA, 0x9FCC, 0x6DEE, 0x9FCD, 0x6E2D, 0x9FCE, 0x6E6E, 0x9FCF, 0x6E2E, 0x9FD0, 0x6E19, + 0x9FD1, 0x6E72, 0x9FD2, 0x6E5F, 0x9FD3, 0x6E3E, 0x9FD4, 0x6E23, 0x9FD5, 0x6E6B, 0x9FD6, 0x6E2B, 0x9FD7, 0x6E76, 0x9FD8, 0x6E4D, + 0x9FD9, 0x6E1F, 0x9FDA, 0x6E43, 0x9FDB, 0x6E3A, 0x9FDC, 0x6E4E, 0x9FDD, 0x6E24, 0x9FDE, 0x6EFF, 0x9FDF, 0x6E1D, 0x9FE0, 0x6E38, + 0x9FE1, 0x6E82, 0x9FE2, 0x6EAA, 0x9FE3, 0x6E98, 0x9FE4, 0x6EC9, 0x9FE5, 0x6EB7, 0x9FE6, 0x6ED3, 0x9FE7, 0x6EBD, 0x9FE8, 0x6EAF, + 0x9FE9, 0x6EC4, 0x9FEA, 0x6EB2, 0x9FEB, 0x6ED4, 0x9FEC, 0x6ED5, 0x9FED, 0x6E8F, 0x9FEE, 0x6EA5, 0x9FEF, 0x6EC2, 0x9FF0, 0x6E9F, + 0x9FF1, 0x6F41, 0x9FF2, 0x6F11, 0x9FF3, 0x704C, 0x9FF4, 0x6EEC, 0x9FF5, 0x6EF8, 0x9FF6, 0x6EFE, 0x9FF7, 0x6F3F, 0x9FF8, 0x6EF2, + 0x9FF9, 0x6F31, 0x9FFA, 0x6EEF, 0x9FFB, 0x6F32, 0x9FFC, 0x6ECC, 0xE040, 0x6F3E, 0xE041, 0x6F13, 0xE042, 0x6EF7, 0xE043, 0x6F86, + 0xE044, 0x6F7A, 0xE045, 0x6F78, 0xE046, 0x6F81, 0xE047, 0x6F80, 0xE048, 0x6F6F, 0xE049, 0x6F5B, 0xE04A, 0x6FF3, 0xE04B, 0x6F6D, + 0xE04C, 0x6F82, 0xE04D, 0x6F7C, 0xE04E, 0x6F58, 0xE04F, 0x6F8E, 0xE050, 0x6F91, 0xE051, 0x6FC2, 0xE052, 0x6F66, 0xE053, 0x6FB3, + 0xE054, 0x6FA3, 0xE055, 0x6FA1, 0xE056, 0x6FA4, 0xE057, 0x6FB9, 0xE058, 0x6FC6, 0xE059, 0x6FAA, 0xE05A, 0x6FDF, 0xE05B, 0x6FD5, + 0xE05C, 0x6FEC, 0xE05D, 0x6FD4, 0xE05E, 0x6FD8, 0xE05F, 0x6FF1, 0xE060, 0x6FEE, 0xE061, 0x6FDB, 0xE062, 0x7009, 0xE063, 0x700B, + 0xE064, 0x6FFA, 0xE065, 0x7011, 0xE066, 0x7001, 0xE067, 0x700F, 0xE068, 0x6FFE, 0xE069, 0x701B, 0xE06A, 0x701A, 0xE06B, 0x6F74, + 0xE06C, 0x701D, 0xE06D, 0x7018, 0xE06E, 0x701F, 0xE06F, 0x7030, 0xE070, 0x703E, 0xE071, 0x7032, 0xE072, 0x7051, 0xE073, 0x7063, + 0xE074, 0x7099, 0xE075, 0x7092, 0xE076, 0x70AF, 0xE077, 0x70F1, 0xE078, 0x70AC, 0xE079, 0x70B8, 0xE07A, 0x70B3, 0xE07B, 0x70AE, + 0xE07C, 0x70DF, 0xE07D, 0x70CB, 0xE07E, 0x70DD, 0xE080, 0x70D9, 0xE081, 0x7109, 0xE082, 0x70FD, 0xE083, 0x711C, 0xE084, 0x7119, + 0xE085, 0x7165, 0xE086, 0x7155, 0xE087, 0x7188, 0xE088, 0x7166, 0xE089, 0x7162, 0xE08A, 0x714C, 0xE08B, 0x7156, 0xE08C, 0x716C, + 0xE08D, 0x718F, 0xE08E, 0x71FB, 0xE08F, 0x7184, 0xE090, 0x7195, 0xE091, 0x71A8, 0xE092, 0x71AC, 0xE093, 0x71D7, 0xE094, 0x71B9, + 0xE095, 0x71BE, 0xE096, 0x71D2, 0xE097, 0x71C9, 0xE098, 0x71D4, 0xE099, 0x71CE, 0xE09A, 0x71E0, 0xE09B, 0x71EC, 0xE09C, 0x71E7, + 0xE09D, 0x71F5, 0xE09E, 0x71FC, 0xE09F, 0x71F9, 0xE0A0, 0x71FF, 0xE0A1, 0x720D, 0xE0A2, 0x7210, 0xE0A3, 0x721B, 0xE0A4, 0x7228, + 0xE0A5, 0x722D, 0xE0A6, 0x722C, 0xE0A7, 0x7230, 0xE0A8, 0x7232, 0xE0A9, 0x723B, 0xE0AA, 0x723C, 0xE0AB, 0x723F, 0xE0AC, 0x7240, + 0xE0AD, 0x7246, 0xE0AE, 0x724B, 0xE0AF, 0x7258, 0xE0B0, 0x7274, 0xE0B1, 0x727E, 0xE0B2, 0x7282, 0xE0B3, 0x7281, 0xE0B4, 0x7287, + 0xE0B5, 0x7292, 0xE0B6, 0x7296, 0xE0B7, 0x72A2, 0xE0B8, 0x72A7, 0xE0B9, 0x72B9, 0xE0BA, 0x72B2, 0xE0BB, 0x72C3, 0xE0BC, 0x72C6, + 0xE0BD, 0x72C4, 0xE0BE, 0x72CE, 0xE0BF, 0x72D2, 0xE0C0, 0x72E2, 0xE0C1, 0x72E0, 0xE0C2, 0x72E1, 0xE0C3, 0x72F9, 0xE0C4, 0x72F7, + 0xE0C5, 0x500F, 0xE0C6, 0x7317, 0xE0C7, 0x730A, 0xE0C8, 0x731C, 0xE0C9, 0x7316, 0xE0CA, 0x731D, 0xE0CB, 0x7334, 0xE0CC, 0x732F, + 0xE0CD, 0x7329, 0xE0CE, 0x7325, 0xE0CF, 0x733E, 0xE0D0, 0x734E, 0xE0D1, 0x734F, 0xE0D2, 0x9ED8, 0xE0D3, 0x7357, 0xE0D4, 0x736A, + 0xE0D5, 0x7368, 0xE0D6, 0x7370, 0xE0D7, 0x7378, 0xE0D8, 0x7375, 0xE0D9, 0x737B, 0xE0DA, 0x737A, 0xE0DB, 0x73C8, 0xE0DC, 0x73B3, + 0xE0DD, 0x73CE, 0xE0DE, 0x73BB, 0xE0DF, 0x73C0, 0xE0E0, 0x73E5, 0xE0E1, 0x73EE, 0xE0E2, 0x73DE, 0xE0E3, 0x74A2, 0xE0E4, 0x7405, + 0xE0E5, 0x746F, 0xE0E6, 0x7425, 0xE0E7, 0x73F8, 0xE0E8, 0x7432, 0xE0E9, 0x743A, 0xE0EA, 0x7455, 0xE0EB, 0x743F, 0xE0EC, 0x745F, + 0xE0ED, 0x7459, 0xE0EE, 0x7441, 0xE0EF, 0x745C, 0xE0F0, 0x7469, 0xE0F1, 0x7470, 0xE0F2, 0x7463, 0xE0F3, 0x746A, 0xE0F4, 0x7476, + 0xE0F5, 0x747E, 0xE0F6, 0x748B, 0xE0F7, 0x749E, 0xE0F8, 0x74A7, 0xE0F9, 0x74CA, 0xE0FA, 0x74CF, 0xE0FB, 0x74D4, 0xE0FC, 0x73F1, + 0xE140, 0x74E0, 0xE141, 0x74E3, 0xE142, 0x74E7, 0xE143, 0x74E9, 0xE144, 0x74EE, 0xE145, 0x74F2, 0xE146, 0x74F0, 0xE147, 0x74F1, + 0xE148, 0x74F8, 0xE149, 0x74F7, 0xE14A, 0x7504, 0xE14B, 0x7503, 0xE14C, 0x7505, 0xE14D, 0x750C, 0xE14E, 0x750E, 0xE14F, 0x750D, + 0xE150, 0x7515, 0xE151, 0x7513, 0xE152, 0x751E, 0xE153, 0x7526, 0xE154, 0x752C, 0xE155, 0x753C, 0xE156, 0x7544, 0xE157, 0x754D, + 0xE158, 0x754A, 0xE159, 0x7549, 0xE15A, 0x755B, 0xE15B, 0x7546, 0xE15C, 0x755A, 0xE15D, 0x7569, 0xE15E, 0x7564, 0xE15F, 0x7567, + 0xE160, 0x756B, 0xE161, 0x756D, 0xE162, 0x7578, 0xE163, 0x7576, 0xE164, 0x7586, 0xE165, 0x7587, 0xE166, 0x7574, 0xE167, 0x758A, + 0xE168, 0x7589, 0xE169, 0x7582, 0xE16A, 0x7594, 0xE16B, 0x759A, 0xE16C, 0x759D, 0xE16D, 0x75A5, 0xE16E, 0x75A3, 0xE16F, 0x75C2, + 0xE170, 0x75B3, 0xE171, 0x75C3, 0xE172, 0x75B5, 0xE173, 0x75BD, 0xE174, 0x75B8, 0xE175, 0x75BC, 0xE176, 0x75B1, 0xE177, 0x75CD, + 0xE178, 0x75CA, 0xE179, 0x75D2, 0xE17A, 0x75D9, 0xE17B, 0x75E3, 0xE17C, 0x75DE, 0xE17D, 0x75FE, 0xE17E, 0x75FF, 0xE180, 0x75FC, + 0xE181, 0x7601, 0xE182, 0x75F0, 0xE183, 0x75FA, 0xE184, 0x75F2, 0xE185, 0x75F3, 0xE186, 0x760B, 0xE187, 0x760D, 0xE188, 0x7609, + 0xE189, 0x761F, 0xE18A, 0x7627, 0xE18B, 0x7620, 0xE18C, 0x7621, 0xE18D, 0x7622, 0xE18E, 0x7624, 0xE18F, 0x7634, 0xE190, 0x7630, + 0xE191, 0x763B, 0xE192, 0x7647, 0xE193, 0x7648, 0xE194, 0x7646, 0xE195, 0x765C, 0xE196, 0x7658, 0xE197, 0x7661, 0xE198, 0x7662, + 0xE199, 0x7668, 0xE19A, 0x7669, 0xE19B, 0x766A, 0xE19C, 0x7667, 0xE19D, 0x766C, 0xE19E, 0x7670, 0xE19F, 0x7672, 0xE1A0, 0x7676, + 0xE1A1, 0x7678, 0xE1A2, 0x767C, 0xE1A3, 0x7680, 0xE1A4, 0x7683, 0xE1A5, 0x7688, 0xE1A6, 0x768B, 0xE1A7, 0x768E, 0xE1A8, 0x7696, + 0xE1A9, 0x7693, 0xE1AA, 0x7699, 0xE1AB, 0x769A, 0xE1AC, 0x76B0, 0xE1AD, 0x76B4, 0xE1AE, 0x76B8, 0xE1AF, 0x76B9, 0xE1B0, 0x76BA, + 0xE1B1, 0x76C2, 0xE1B2, 0x76CD, 0xE1B3, 0x76D6, 0xE1B4, 0x76D2, 0xE1B5, 0x76DE, 0xE1B6, 0x76E1, 0xE1B7, 0x76E5, 0xE1B8, 0x76E7, + 0xE1B9, 0x76EA, 0xE1BA, 0x862F, 0xE1BB, 0x76FB, 0xE1BC, 0x7708, 0xE1BD, 0x7707, 0xE1BE, 0x7704, 0xE1BF, 0x7729, 0xE1C0, 0x7724, + 0xE1C1, 0x771E, 0xE1C2, 0x7725, 0xE1C3, 0x7726, 0xE1C4, 0x771B, 0xE1C5, 0x7737, 0xE1C6, 0x7738, 0xE1C7, 0x7747, 0xE1C8, 0x775A, + 0xE1C9, 0x7768, 0xE1CA, 0x776B, 0xE1CB, 0x775B, 0xE1CC, 0x7765, 0xE1CD, 0x777F, 0xE1CE, 0x777E, 0xE1CF, 0x7779, 0xE1D0, 0x778E, + 0xE1D1, 0x778B, 0xE1D2, 0x7791, 0xE1D3, 0x77A0, 0xE1D4, 0x779E, 0xE1D5, 0x77B0, 0xE1D6, 0x77B6, 0xE1D7, 0x77B9, 0xE1D8, 0x77BF, + 0xE1D9, 0x77BC, 0xE1DA, 0x77BD, 0xE1DB, 0x77BB, 0xE1DC, 0x77C7, 0xE1DD, 0x77CD, 0xE1DE, 0x77D7, 0xE1DF, 0x77DA, 0xE1E0, 0x77DC, + 0xE1E1, 0x77E3, 0xE1E2, 0x77EE, 0xE1E3, 0x77FC, 0xE1E4, 0x780C, 0xE1E5, 0x7812, 0xE1E6, 0x7926, 0xE1E7, 0x7820, 0xE1E8, 0x792A, + 0xE1E9, 0x7845, 0xE1EA, 0x788E, 0xE1EB, 0x7874, 0xE1EC, 0x7886, 0xE1ED, 0x787C, 0xE1EE, 0x789A, 0xE1EF, 0x788C, 0xE1F0, 0x78A3, + 0xE1F1, 0x78B5, 0xE1F2, 0x78AA, 0xE1F3, 0x78AF, 0xE1F4, 0x78D1, 0xE1F5, 0x78C6, 0xE1F6, 0x78CB, 0xE1F7, 0x78D4, 0xE1F8, 0x78BE, + 0xE1F9, 0x78BC, 0xE1FA, 0x78C5, 0xE1FB, 0x78CA, 0xE1FC, 0x78EC, 0xE240, 0x78E7, 0xE241, 0x78DA, 0xE242, 0x78FD, 0xE243, 0x78F4, + 0xE244, 0x7907, 0xE245, 0x7912, 0xE246, 0x7911, 0xE247, 0x7919, 0xE248, 0x792C, 0xE249, 0x792B, 0xE24A, 0x7940, 0xE24B, 0x7960, + 0xE24C, 0x7957, 0xE24D, 0x795F, 0xE24E, 0x795A, 0xE24F, 0x7955, 0xE250, 0x7953, 0xE251, 0x797A, 0xE252, 0x797F, 0xE253, 0x798A, + 0xE254, 0x799D, 0xE255, 0x79A7, 0xE256, 0x9F4B, 0xE257, 0x79AA, 0xE258, 0x79AE, 0xE259, 0x79B3, 0xE25A, 0x79B9, 0xE25B, 0x79BA, + 0xE25C, 0x79C9, 0xE25D, 0x79D5, 0xE25E, 0x79E7, 0xE25F, 0x79EC, 0xE260, 0x79E1, 0xE261, 0x79E3, 0xE262, 0x7A08, 0xE263, 0x7A0D, + 0xE264, 0x7A18, 0xE265, 0x7A19, 0xE266, 0x7A20, 0xE267, 0x7A1F, 0xE268, 0x7980, 0xE269, 0x7A31, 0xE26A, 0x7A3B, 0xE26B, 0x7A3E, + 0xE26C, 0x7A37, 0xE26D, 0x7A43, 0xE26E, 0x7A57, 0xE26F, 0x7A49, 0xE270, 0x7A61, 0xE271, 0x7A62, 0xE272, 0x7A69, 0xE273, 0x9F9D, + 0xE274, 0x7A70, 0xE275, 0x7A79, 0xE276, 0x7A7D, 0xE277, 0x7A88, 0xE278, 0x7A97, 0xE279, 0x7A95, 0xE27A, 0x7A98, 0xE27B, 0x7A96, + 0xE27C, 0x7AA9, 0xE27D, 0x7AC8, 0xE27E, 0x7AB0, 0xE280, 0x7AB6, 0xE281, 0x7AC5, 0xE282, 0x7AC4, 0xE283, 0x7ABF, 0xE284, 0x9083, + 0xE285, 0x7AC7, 0xE286, 0x7ACA, 0xE287, 0x7ACD, 0xE288, 0x7ACF, 0xE289, 0x7AD5, 0xE28A, 0x7AD3, 0xE28B, 0x7AD9, 0xE28C, 0x7ADA, + 0xE28D, 0x7ADD, 0xE28E, 0x7AE1, 0xE28F, 0x7AE2, 0xE290, 0x7AE6, 0xE291, 0x7AED, 0xE292, 0x7AF0, 0xE293, 0x7B02, 0xE294, 0x7B0F, + 0xE295, 0x7B0A, 0xE296, 0x7B06, 0xE297, 0x7B33, 0xE298, 0x7B18, 0xE299, 0x7B19, 0xE29A, 0x7B1E, 0xE29B, 0x7B35, 0xE29C, 0x7B28, + 0xE29D, 0x7B36, 0xE29E, 0x7B50, 0xE29F, 0x7B7A, 0xE2A0, 0x7B04, 0xE2A1, 0x7B4D, 0xE2A2, 0x7B0B, 0xE2A3, 0x7B4C, 0xE2A4, 0x7B45, + 0xE2A5, 0x7B75, 0xE2A6, 0x7B65, 0xE2A7, 0x7B74, 0xE2A8, 0x7B67, 0xE2A9, 0x7B70, 0xE2AA, 0x7B71, 0xE2AB, 0x7B6C, 0xE2AC, 0x7B6E, + 0xE2AD, 0x7B9D, 0xE2AE, 0x7B98, 0xE2AF, 0x7B9F, 0xE2B0, 0x7B8D, 0xE2B1, 0x7B9C, 0xE2B2, 0x7B9A, 0xE2B3, 0x7B8B, 0xE2B4, 0x7B92, + 0xE2B5, 0x7B8F, 0xE2B6, 0x7B5D, 0xE2B7, 0x7B99, 0xE2B8, 0x7BCB, 0xE2B9, 0x7BC1, 0xE2BA, 0x7BCC, 0xE2BB, 0x7BCF, 0xE2BC, 0x7BB4, + 0xE2BD, 0x7BC6, 0xE2BE, 0x7BDD, 0xE2BF, 0x7BE9, 0xE2C0, 0x7C11, 0xE2C1, 0x7C14, 0xE2C2, 0x7BE6, 0xE2C3, 0x7BE5, 0xE2C4, 0x7C60, + 0xE2C5, 0x7C00, 0xE2C6, 0x7C07, 0xE2C7, 0x7C13, 0xE2C8, 0x7BF3, 0xE2C9, 0x7BF7, 0xE2CA, 0x7C17, 0xE2CB, 0x7C0D, 0xE2CC, 0x7BF6, + 0xE2CD, 0x7C23, 0xE2CE, 0x7C27, 0xE2CF, 0x7C2A, 0xE2D0, 0x7C1F, 0xE2D1, 0x7C37, 0xE2D2, 0x7C2B, 0xE2D3, 0x7C3D, 0xE2D4, 0x7C4C, + 0xE2D5, 0x7C43, 0xE2D6, 0x7C54, 0xE2D7, 0x7C4F, 0xE2D8, 0x7C40, 0xE2D9, 0x7C50, 0xE2DA, 0x7C58, 0xE2DB, 0x7C5F, 0xE2DC, 0x7C64, + 0xE2DD, 0x7C56, 0xE2DE, 0x7C65, 0xE2DF, 0x7C6C, 0xE2E0, 0x7C75, 0xE2E1, 0x7C83, 0xE2E2, 0x7C90, 0xE2E3, 0x7CA4, 0xE2E4, 0x7CAD, + 0xE2E5, 0x7CA2, 0xE2E6, 0x7CAB, 0xE2E7, 0x7CA1, 0xE2E8, 0x7CA8, 0xE2E9, 0x7CB3, 0xE2EA, 0x7CB2, 0xE2EB, 0x7CB1, 0xE2EC, 0x7CAE, + 0xE2ED, 0x7CB9, 0xE2EE, 0x7CBD, 0xE2EF, 0x7CC0, 0xE2F0, 0x7CC5, 0xE2F1, 0x7CC2, 0xE2F2, 0x7CD8, 0xE2F3, 0x7CD2, 0xE2F4, 0x7CDC, + 0xE2F5, 0x7CE2, 0xE2F6, 0x9B3B, 0xE2F7, 0x7CEF, 0xE2F8, 0x7CF2, 0xE2F9, 0x7CF4, 0xE2FA, 0x7CF6, 0xE2FB, 0x7CFA, 0xE2FC, 0x7D06, + 0xE340, 0x7D02, 0xE341, 0x7D1C, 0xE342, 0x7D15, 0xE343, 0x7D0A, 0xE344, 0x7D45, 0xE345, 0x7D4B, 0xE346, 0x7D2E, 0xE347, 0x7D32, + 0xE348, 0x7D3F, 0xE349, 0x7D35, 0xE34A, 0x7D46, 0xE34B, 0x7D73, 0xE34C, 0x7D56, 0xE34D, 0x7D4E, 0xE34E, 0x7D72, 0xE34F, 0x7D68, + 0xE350, 0x7D6E, 0xE351, 0x7D4F, 0xE352, 0x7D63, 0xE353, 0x7D93, 0xE354, 0x7D89, 0xE355, 0x7D5B, 0xE356, 0x7D8F, 0xE357, 0x7D7D, + 0xE358, 0x7D9B, 0xE359, 0x7DBA, 0xE35A, 0x7DAE, 0xE35B, 0x7DA3, 0xE35C, 0x7DB5, 0xE35D, 0x7DC7, 0xE35E, 0x7DBD, 0xE35F, 0x7DAB, + 0xE360, 0x7E3D, 0xE361, 0x7DA2, 0xE362, 0x7DAF, 0xE363, 0x7DDC, 0xE364, 0x7DB8, 0xE365, 0x7D9F, 0xE366, 0x7DB0, 0xE367, 0x7DD8, + 0xE368, 0x7DDD, 0xE369, 0x7DE4, 0xE36A, 0x7DDE, 0xE36B, 0x7DFB, 0xE36C, 0x7DF2, 0xE36D, 0x7DE1, 0xE36E, 0x7E05, 0xE36F, 0x7E0A, + 0xE370, 0x7E23, 0xE371, 0x7E21, 0xE372, 0x7E12, 0xE373, 0x7E31, 0xE374, 0x7E1F, 0xE375, 0x7E09, 0xE376, 0x7E0B, 0xE377, 0x7E22, + 0xE378, 0x7E46, 0xE379, 0x7E66, 0xE37A, 0x7E3B, 0xE37B, 0x7E35, 0xE37C, 0x7E39, 0xE37D, 0x7E43, 0xE37E, 0x7E37, 0xE380, 0x7E32, + 0xE381, 0x7E3A, 0xE382, 0x7E67, 0xE383, 0x7E5D, 0xE384, 0x7E56, 0xE385, 0x7E5E, 0xE386, 0x7E59, 0xE387, 0x7E5A, 0xE388, 0x7E79, + 0xE389, 0x7E6A, 0xE38A, 0x7E69, 0xE38B, 0x7E7C, 0xE38C, 0x7E7B, 0xE38D, 0x7E83, 0xE38E, 0x7DD5, 0xE38F, 0x7E7D, 0xE390, 0x8FAE, + 0xE391, 0x7E7F, 0xE392, 0x7E88, 0xE393, 0x7E89, 0xE394, 0x7E8C, 0xE395, 0x7E92, 0xE396, 0x7E90, 0xE397, 0x7E93, 0xE398, 0x7E94, + 0xE399, 0x7E96, 0xE39A, 0x7E8E, 0xE39B, 0x7E9B, 0xE39C, 0x7E9C, 0xE39D, 0x7F38, 0xE39E, 0x7F3A, 0xE39F, 0x7F45, 0xE3A0, 0x7F4C, + 0xE3A1, 0x7F4D, 0xE3A2, 0x7F4E, 0xE3A3, 0x7F50, 0xE3A4, 0x7F51, 0xE3A5, 0x7F55, 0xE3A6, 0x7F54, 0xE3A7, 0x7F58, 0xE3A8, 0x7F5F, + 0xE3A9, 0x7F60, 0xE3AA, 0x7F68, 0xE3AB, 0x7F69, 0xE3AC, 0x7F67, 0xE3AD, 0x7F78, 0xE3AE, 0x7F82, 0xE3AF, 0x7F86, 0xE3B0, 0x7F83, + 0xE3B1, 0x7F88, 0xE3B2, 0x7F87, 0xE3B3, 0x7F8C, 0xE3B4, 0x7F94, 0xE3B5, 0x7F9E, 0xE3B6, 0x7F9D, 0xE3B7, 0x7F9A, 0xE3B8, 0x7FA3, + 0xE3B9, 0x7FAF, 0xE3BA, 0x7FB2, 0xE3BB, 0x7FB9, 0xE3BC, 0x7FAE, 0xE3BD, 0x7FB6, 0xE3BE, 0x7FB8, 0xE3BF, 0x8B71, 0xE3C0, 0x7FC5, + 0xE3C1, 0x7FC6, 0xE3C2, 0x7FCA, 0xE3C3, 0x7FD5, 0xE3C4, 0x7FD4, 0xE3C5, 0x7FE1, 0xE3C6, 0x7FE6, 0xE3C7, 0x7FE9, 0xE3C8, 0x7FF3, + 0xE3C9, 0x7FF9, 0xE3CA, 0x98DC, 0xE3CB, 0x8006, 0xE3CC, 0x8004, 0xE3CD, 0x800B, 0xE3CE, 0x8012, 0xE3CF, 0x8018, 0xE3D0, 0x8019, + 0xE3D1, 0x801C, 0xE3D2, 0x8021, 0xE3D3, 0x8028, 0xE3D4, 0x803F, 0xE3D5, 0x803B, 0xE3D6, 0x804A, 0xE3D7, 0x8046, 0xE3D8, 0x8052, + 0xE3D9, 0x8058, 0xE3DA, 0x805A, 0xE3DB, 0x805F, 0xE3DC, 0x8062, 0xE3DD, 0x8068, 0xE3DE, 0x8073, 0xE3DF, 0x8072, 0xE3E0, 0x8070, + 0xE3E1, 0x8076, 0xE3E2, 0x8079, 0xE3E3, 0x807D, 0xE3E4, 0x807F, 0xE3E5, 0x8084, 0xE3E6, 0x8086, 0xE3E7, 0x8085, 0xE3E8, 0x809B, + 0xE3E9, 0x8093, 0xE3EA, 0x809A, 0xE3EB, 0x80AD, 0xE3EC, 0x5190, 0xE3ED, 0x80AC, 0xE3EE, 0x80DB, 0xE3EF, 0x80E5, 0xE3F0, 0x80D9, + 0xE3F1, 0x80DD, 0xE3F2, 0x80C4, 0xE3F3, 0x80DA, 0xE3F4, 0x80D6, 0xE3F5, 0x8109, 0xE3F6, 0x80EF, 0xE3F7, 0x80F1, 0xE3F8, 0x811B, + 0xE3F9, 0x8129, 0xE3FA, 0x8123, 0xE3FB, 0x812F, 0xE3FC, 0x814B, 0xE440, 0x968B, 0xE441, 0x8146, 0xE442, 0x813E, 0xE443, 0x8153, + 0xE444, 0x8151, 0xE445, 0x80FC, 0xE446, 0x8171, 0xE447, 0x816E, 0xE448, 0x8165, 0xE449, 0x8166, 0xE44A, 0x8174, 0xE44B, 0x8183, + 0xE44C, 0x8188, 0xE44D, 0x818A, 0xE44E, 0x8180, 0xE44F, 0x8182, 0xE450, 0x81A0, 0xE451, 0x8195, 0xE452, 0x81A4, 0xE453, 0x81A3, + 0xE454, 0x815F, 0xE455, 0x8193, 0xE456, 0x81A9, 0xE457, 0x81B0, 0xE458, 0x81B5, 0xE459, 0x81BE, 0xE45A, 0x81B8, 0xE45B, 0x81BD, + 0xE45C, 0x81C0, 0xE45D, 0x81C2, 0xE45E, 0x81BA, 0xE45F, 0x81C9, 0xE460, 0x81CD, 0xE461, 0x81D1, 0xE462, 0x81D9, 0xE463, 0x81D8, + 0xE464, 0x81C8, 0xE465, 0x81DA, 0xE466, 0x81DF, 0xE467, 0x81E0, 0xE468, 0x81E7, 0xE469, 0x81FA, 0xE46A, 0x81FB, 0xE46B, 0x81FE, + 0xE46C, 0x8201, 0xE46D, 0x8202, 0xE46E, 0x8205, 0xE46F, 0x8207, 0xE470, 0x820A, 0xE471, 0x820D, 0xE472, 0x8210, 0xE473, 0x8216, + 0xE474, 0x8229, 0xE475, 0x822B, 0xE476, 0x8238, 0xE477, 0x8233, 0xE478, 0x8240, 0xE479, 0x8259, 0xE47A, 0x8258, 0xE47B, 0x825D, + 0xE47C, 0x825A, 0xE47D, 0x825F, 0xE47E, 0x8264, 0xE480, 0x8262, 0xE481, 0x8268, 0xE482, 0x826A, 0xE483, 0x826B, 0xE484, 0x822E, + 0xE485, 0x8271, 0xE486, 0x8277, 0xE487, 0x8278, 0xE488, 0x827E, 0xE489, 0x828D, 0xE48A, 0x8292, 0xE48B, 0x82AB, 0xE48C, 0x829F, + 0xE48D, 0x82BB, 0xE48E, 0x82AC, 0xE48F, 0x82E1, 0xE490, 0x82E3, 0xE491, 0x82DF, 0xE492, 0x82D2, 0xE493, 0x82F4, 0xE494, 0x82F3, + 0xE495, 0x82FA, 0xE496, 0x8393, 0xE497, 0x8303, 0xE498, 0x82FB, 0xE499, 0x82F9, 0xE49A, 0x82DE, 0xE49B, 0x8306, 0xE49C, 0x82DC, + 0xE49D, 0x8309, 0xE49E, 0x82D9, 0xE49F, 0x8335, 0xE4A0, 0x8334, 0xE4A1, 0x8316, 0xE4A2, 0x8332, 0xE4A3, 0x8331, 0xE4A4, 0x8340, + 0xE4A5, 0x8339, 0xE4A6, 0x8350, 0xE4A7, 0x8345, 0xE4A8, 0x832F, 0xE4A9, 0x832B, 0xE4AA, 0x8317, 0xE4AB, 0x8318, 0xE4AC, 0x8385, + 0xE4AD, 0x839A, 0xE4AE, 0x83AA, 0xE4AF, 0x839F, 0xE4B0, 0x83A2, 0xE4B1, 0x8396, 0xE4B2, 0x8323, 0xE4B3, 0x838E, 0xE4B4, 0x8387, + 0xE4B5, 0x838A, 0xE4B6, 0x837C, 0xE4B7, 0x83B5, 0xE4B8, 0x8373, 0xE4B9, 0x8375, 0xE4BA, 0x83A0, 0xE4BB, 0x8389, 0xE4BC, 0x83A8, + 0xE4BD, 0x83F4, 0xE4BE, 0x8413, 0xE4BF, 0x83EB, 0xE4C0, 0x83CE, 0xE4C1, 0x83FD, 0xE4C2, 0x8403, 0xE4C3, 0x83D8, 0xE4C4, 0x840B, + 0xE4C5, 0x83C1, 0xE4C6, 0x83F7, 0xE4C7, 0x8407, 0xE4C8, 0x83E0, 0xE4C9, 0x83F2, 0xE4CA, 0x840D, 0xE4CB, 0x8422, 0xE4CC, 0x8420, + 0xE4CD, 0x83BD, 0xE4CE, 0x8438, 0xE4CF, 0x8506, 0xE4D0, 0x83FB, 0xE4D1, 0x846D, 0xE4D2, 0x842A, 0xE4D3, 0x843C, 0xE4D4, 0x855A, + 0xE4D5, 0x8484, 0xE4D6, 0x8477, 0xE4D7, 0x846B, 0xE4D8, 0x84AD, 0xE4D9, 0x846E, 0xE4DA, 0x8482, 0xE4DB, 0x8469, 0xE4DC, 0x8446, + 0xE4DD, 0x842C, 0xE4DE, 0x846F, 0xE4DF, 0x8479, 0xE4E0, 0x8435, 0xE4E1, 0x84CA, 0xE4E2, 0x8462, 0xE4E3, 0x84B9, 0xE4E4, 0x84BF, + 0xE4E5, 0x849F, 0xE4E6, 0x84D9, 0xE4E7, 0x84CD, 0xE4E8, 0x84BB, 0xE4E9, 0x84DA, 0xE4EA, 0x84D0, 0xE4EB, 0x84C1, 0xE4EC, 0x84C6, + 0xE4ED, 0x84D6, 0xE4EE, 0x84A1, 0xE4EF, 0x8521, 0xE4F0, 0x84FF, 0xE4F1, 0x84F4, 0xE4F2, 0x8517, 0xE4F3, 0x8518, 0xE4F4, 0x852C, + 0xE4F5, 0x851F, 0xE4F6, 0x8515, 0xE4F7, 0x8514, 0xE4F8, 0x84FC, 0xE4F9, 0x8540, 0xE4FA, 0x8563, 0xE4FB, 0x8558, 0xE4FC, 0x8548, + 0xE540, 0x8541, 0xE541, 0x8602, 0xE542, 0x854B, 0xE543, 0x8555, 0xE544, 0x8580, 0xE545, 0x85A4, 0xE546, 0x8588, 0xE547, 0x8591, + 0xE548, 0x858A, 0xE549, 0x85A8, 0xE54A, 0x856D, 0xE54B, 0x8594, 0xE54C, 0x859B, 0xE54D, 0x85EA, 0xE54E, 0x8587, 0xE54F, 0x859C, + 0xE550, 0x8577, 0xE551, 0x857E, 0xE552, 0x8590, 0xE553, 0x85C9, 0xE554, 0x85BA, 0xE555, 0x85CF, 0xE556, 0x85B9, 0xE557, 0x85D0, + 0xE558, 0x85D5, 0xE559, 0x85DD, 0xE55A, 0x85E5, 0xE55B, 0x85DC, 0xE55C, 0x85F9, 0xE55D, 0x860A, 0xE55E, 0x8613, 0xE55F, 0x860B, + 0xE560, 0x85FE, 0xE561, 0x85FA, 0xE562, 0x8606, 0xE563, 0x8622, 0xE564, 0x861A, 0xE565, 0x8630, 0xE566, 0x863F, 0xE567, 0x864D, + 0xE568, 0x4E55, 0xE569, 0x8654, 0xE56A, 0x865F, 0xE56B, 0x8667, 0xE56C, 0x8671, 0xE56D, 0x8693, 0xE56E, 0x86A3, 0xE56F, 0x86A9, + 0xE570, 0x86AA, 0xE571, 0x868B, 0xE572, 0x868C, 0xE573, 0x86B6, 0xE574, 0x86AF, 0xE575, 0x86C4, 0xE576, 0x86C6, 0xE577, 0x86B0, + 0xE578, 0x86C9, 0xE579, 0x8823, 0xE57A, 0x86AB, 0xE57B, 0x86D4, 0xE57C, 0x86DE, 0xE57D, 0x86E9, 0xE57E, 0x86EC, 0xE580, 0x86DF, + 0xE581, 0x86DB, 0xE582, 0x86EF, 0xE583, 0x8712, 0xE584, 0x8706, 0xE585, 0x8708, 0xE586, 0x8700, 0xE587, 0x8703, 0xE588, 0x86FB, + 0xE589, 0x8711, 0xE58A, 0x8709, 0xE58B, 0x870D, 0xE58C, 0x86F9, 0xE58D, 0x870A, 0xE58E, 0x8734, 0xE58F, 0x873F, 0xE590, 0x8737, + 0xE591, 0x873B, 0xE592, 0x8725, 0xE593, 0x8729, 0xE594, 0x871A, 0xE595, 0x8760, 0xE596, 0x875F, 0xE597, 0x8778, 0xE598, 0x874C, + 0xE599, 0x874E, 0xE59A, 0x8774, 0xE59B, 0x8757, 0xE59C, 0x8768, 0xE59D, 0x876E, 0xE59E, 0x8759, 0xE59F, 0x8753, 0xE5A0, 0x8763, + 0xE5A1, 0x876A, 0xE5A2, 0x8805, 0xE5A3, 0x87A2, 0xE5A4, 0x879F, 0xE5A5, 0x8782, 0xE5A6, 0x87AF, 0xE5A7, 0x87CB, 0xE5A8, 0x87BD, + 0xE5A9, 0x87C0, 0xE5AA, 0x87D0, 0xE5AB, 0x96D6, 0xE5AC, 0x87AB, 0xE5AD, 0x87C4, 0xE5AE, 0x87B3, 0xE5AF, 0x87C7, 0xE5B0, 0x87C6, + 0xE5B1, 0x87BB, 0xE5B2, 0x87EF, 0xE5B3, 0x87F2, 0xE5B4, 0x87E0, 0xE5B5, 0x880F, 0xE5B6, 0x880D, 0xE5B7, 0x87FE, 0xE5B8, 0x87F6, + 0xE5B9, 0x87F7, 0xE5BA, 0x880E, 0xE5BB, 0x87D2, 0xE5BC, 0x8811, 0xE5BD, 0x8816, 0xE5BE, 0x8815, 0xE5BF, 0x8822, 0xE5C0, 0x8821, + 0xE5C1, 0x8831, 0xE5C2, 0x8836, 0xE5C3, 0x8839, 0xE5C4, 0x8827, 0xE5C5, 0x883B, 0xE5C6, 0x8844, 0xE5C7, 0x8842, 0xE5C8, 0x8852, + 0xE5C9, 0x8859, 0xE5CA, 0x885E, 0xE5CB, 0x8862, 0xE5CC, 0x886B, 0xE5CD, 0x8881, 0xE5CE, 0x887E, 0xE5CF, 0x889E, 0xE5D0, 0x8875, + 0xE5D1, 0x887D, 0xE5D2, 0x88B5, 0xE5D3, 0x8872, 0xE5D4, 0x8882, 0xE5D5, 0x8897, 0xE5D6, 0x8892, 0xE5D7, 0x88AE, 0xE5D8, 0x8899, + 0xE5D9, 0x88A2, 0xE5DA, 0x888D, 0xE5DB, 0x88A4, 0xE5DC, 0x88B0, 0xE5DD, 0x88BF, 0xE5DE, 0x88B1, 0xE5DF, 0x88C3, 0xE5E0, 0x88C4, + 0xE5E1, 0x88D4, 0xE5E2, 0x88D8, 0xE5E3, 0x88D9, 0xE5E4, 0x88DD, 0xE5E5, 0x88F9, 0xE5E6, 0x8902, 0xE5E7, 0x88FC, 0xE5E8, 0x88F4, + 0xE5E9, 0x88E8, 0xE5EA, 0x88F2, 0xE5EB, 0x8904, 0xE5EC, 0x890C, 0xE5ED, 0x890A, 0xE5EE, 0x8913, 0xE5EF, 0x8943, 0xE5F0, 0x891E, + 0xE5F1, 0x8925, 0xE5F2, 0x892A, 0xE5F3, 0x892B, 0xE5F4, 0x8941, 0xE5F5, 0x8944, 0xE5F6, 0x893B, 0xE5F7, 0x8936, 0xE5F8, 0x8938, + 0xE5F9, 0x894C, 0xE5FA, 0x891D, 0xE5FB, 0x8960, 0xE5FC, 0x895E, 0xE640, 0x8966, 0xE641, 0x8964, 0xE642, 0x896D, 0xE643, 0x896A, + 0xE644, 0x896F, 0xE645, 0x8974, 0xE646, 0x8977, 0xE647, 0x897E, 0xE648, 0x8983, 0xE649, 0x8988, 0xE64A, 0x898A, 0xE64B, 0x8993, + 0xE64C, 0x8998, 0xE64D, 0x89A1, 0xE64E, 0x89A9, 0xE64F, 0x89A6, 0xE650, 0x89AC, 0xE651, 0x89AF, 0xE652, 0x89B2, 0xE653, 0x89BA, + 0xE654, 0x89BD, 0xE655, 0x89BF, 0xE656, 0x89C0, 0xE657, 0x89DA, 0xE658, 0x89DC, 0xE659, 0x89DD, 0xE65A, 0x89E7, 0xE65B, 0x89F4, + 0xE65C, 0x89F8, 0xE65D, 0x8A03, 0xE65E, 0x8A16, 0xE65F, 0x8A10, 0xE660, 0x8A0C, 0xE661, 0x8A1B, 0xE662, 0x8A1D, 0xE663, 0x8A25, + 0xE664, 0x8A36, 0xE665, 0x8A41, 0xE666, 0x8A5B, 0xE667, 0x8A52, 0xE668, 0x8A46, 0xE669, 0x8A48, 0xE66A, 0x8A7C, 0xE66B, 0x8A6D, + 0xE66C, 0x8A6C, 0xE66D, 0x8A62, 0xE66E, 0x8A85, 0xE66F, 0x8A82, 0xE670, 0x8A84, 0xE671, 0x8AA8, 0xE672, 0x8AA1, 0xE673, 0x8A91, + 0xE674, 0x8AA5, 0xE675, 0x8AA6, 0xE676, 0x8A9A, 0xE677, 0x8AA3, 0xE678, 0x8AC4, 0xE679, 0x8ACD, 0xE67A, 0x8AC2, 0xE67B, 0x8ADA, + 0xE67C, 0x8AEB, 0xE67D, 0x8AF3, 0xE67E, 0x8AE7, 0xE680, 0x8AE4, 0xE681, 0x8AF1, 0xE682, 0x8B14, 0xE683, 0x8AE0, 0xE684, 0x8AE2, + 0xE685, 0x8AF7, 0xE686, 0x8ADE, 0xE687, 0x8ADB, 0xE688, 0x8B0C, 0xE689, 0x8B07, 0xE68A, 0x8B1A, 0xE68B, 0x8AE1, 0xE68C, 0x8B16, + 0xE68D, 0x8B10, 0xE68E, 0x8B17, 0xE68F, 0x8B20, 0xE690, 0x8B33, 0xE691, 0x97AB, 0xE692, 0x8B26, 0xE693, 0x8B2B, 0xE694, 0x8B3E, + 0xE695, 0x8B28, 0xE696, 0x8B41, 0xE697, 0x8B4C, 0xE698, 0x8B4F, 0xE699, 0x8B4E, 0xE69A, 0x8B49, 0xE69B, 0x8B56, 0xE69C, 0x8B5B, + 0xE69D, 0x8B5A, 0xE69E, 0x8B6B, 0xE69F, 0x8B5F, 0xE6A0, 0x8B6C, 0xE6A1, 0x8B6F, 0xE6A2, 0x8B74, 0xE6A3, 0x8B7D, 0xE6A4, 0x8B80, + 0xE6A5, 0x8B8C, 0xE6A6, 0x8B8E, 0xE6A7, 0x8B92, 0xE6A8, 0x8B93, 0xE6A9, 0x8B96, 0xE6AA, 0x8B99, 0xE6AB, 0x8B9A, 0xE6AC, 0x8C3A, + 0xE6AD, 0x8C41, 0xE6AE, 0x8C3F, 0xE6AF, 0x8C48, 0xE6B0, 0x8C4C, 0xE6B1, 0x8C4E, 0xE6B2, 0x8C50, 0xE6B3, 0x8C55, 0xE6B4, 0x8C62, + 0xE6B5, 0x8C6C, 0xE6B6, 0x8C78, 0xE6B7, 0x8C7A, 0xE6B8, 0x8C82, 0xE6B9, 0x8C89, 0xE6BA, 0x8C85, 0xE6BB, 0x8C8A, 0xE6BC, 0x8C8D, + 0xE6BD, 0x8C8E, 0xE6BE, 0x8C94, 0xE6BF, 0x8C7C, 0xE6C0, 0x8C98, 0xE6C1, 0x621D, 0xE6C2, 0x8CAD, 0xE6C3, 0x8CAA, 0xE6C4, 0x8CBD, + 0xE6C5, 0x8CB2, 0xE6C6, 0x8CB3, 0xE6C7, 0x8CAE, 0xE6C8, 0x8CB6, 0xE6C9, 0x8CC8, 0xE6CA, 0x8CC1, 0xE6CB, 0x8CE4, 0xE6CC, 0x8CE3, + 0xE6CD, 0x8CDA, 0xE6CE, 0x8CFD, 0xE6CF, 0x8CFA, 0xE6D0, 0x8CFB, 0xE6D1, 0x8D04, 0xE6D2, 0x8D05, 0xE6D3, 0x8D0A, 0xE6D4, 0x8D07, + 0xE6D5, 0x8D0F, 0xE6D6, 0x8D0D, 0xE6D7, 0x8D10, 0xE6D8, 0x9F4E, 0xE6D9, 0x8D13, 0xE6DA, 0x8CCD, 0xE6DB, 0x8D14, 0xE6DC, 0x8D16, + 0xE6DD, 0x8D67, 0xE6DE, 0x8D6D, 0xE6DF, 0x8D71, 0xE6E0, 0x8D73, 0xE6E1, 0x8D81, 0xE6E2, 0x8D99, 0xE6E3, 0x8DC2, 0xE6E4, 0x8DBE, + 0xE6E5, 0x8DBA, 0xE6E6, 0x8DCF, 0xE6E7, 0x8DDA, 0xE6E8, 0x8DD6, 0xE6E9, 0x8DCC, 0xE6EA, 0x8DDB, 0xE6EB, 0x8DCB, 0xE6EC, 0x8DEA, + 0xE6ED, 0x8DEB, 0xE6EE, 0x8DDF, 0xE6EF, 0x8DE3, 0xE6F0, 0x8DFC, 0xE6F1, 0x8E08, 0xE6F2, 0x8E09, 0xE6F3, 0x8DFF, 0xE6F4, 0x8E1D, + 0xE6F5, 0x8E1E, 0xE6F6, 0x8E10, 0xE6F7, 0x8E1F, 0xE6F8, 0x8E42, 0xE6F9, 0x8E35, 0xE6FA, 0x8E30, 0xE6FB, 0x8E34, 0xE6FC, 0x8E4A, + 0xE740, 0x8E47, 0xE741, 0x8E49, 0xE742, 0x8E4C, 0xE743, 0x8E50, 0xE744, 0x8E48, 0xE745, 0x8E59, 0xE746, 0x8E64, 0xE747, 0x8E60, + 0xE748, 0x8E2A, 0xE749, 0x8E63, 0xE74A, 0x8E55, 0xE74B, 0x8E76, 0xE74C, 0x8E72, 0xE74D, 0x8E7C, 0xE74E, 0x8E81, 0xE74F, 0x8E87, + 0xE750, 0x8E85, 0xE751, 0x8E84, 0xE752, 0x8E8B, 0xE753, 0x8E8A, 0xE754, 0x8E93, 0xE755, 0x8E91, 0xE756, 0x8E94, 0xE757, 0x8E99, + 0xE758, 0x8EAA, 0xE759, 0x8EA1, 0xE75A, 0x8EAC, 0xE75B, 0x8EB0, 0xE75C, 0x8EC6, 0xE75D, 0x8EB1, 0xE75E, 0x8EBE, 0xE75F, 0x8EC5, + 0xE760, 0x8EC8, 0xE761, 0x8ECB, 0xE762, 0x8EDB, 0xE763, 0x8EE3, 0xE764, 0x8EFC, 0xE765, 0x8EFB, 0xE766, 0x8EEB, 0xE767, 0x8EFE, + 0xE768, 0x8F0A, 0xE769, 0x8F05, 0xE76A, 0x8F15, 0xE76B, 0x8F12, 0xE76C, 0x8F19, 0xE76D, 0x8F13, 0xE76E, 0x8F1C, 0xE76F, 0x8F1F, + 0xE770, 0x8F1B, 0xE771, 0x8F0C, 0xE772, 0x8F26, 0xE773, 0x8F33, 0xE774, 0x8F3B, 0xE775, 0x8F39, 0xE776, 0x8F45, 0xE777, 0x8F42, + 0xE778, 0x8F3E, 0xE779, 0x8F4C, 0xE77A, 0x8F49, 0xE77B, 0x8F46, 0xE77C, 0x8F4E, 0xE77D, 0x8F57, 0xE77E, 0x8F5C, 0xE780, 0x8F62, + 0xE781, 0x8F63, 0xE782, 0x8F64, 0xE783, 0x8F9C, 0xE784, 0x8F9F, 0xE785, 0x8FA3, 0xE786, 0x8FAD, 0xE787, 0x8FAF, 0xE788, 0x8FB7, + 0xE789, 0x8FDA, 0xE78A, 0x8FE5, 0xE78B, 0x8FE2, 0xE78C, 0x8FEA, 0xE78D, 0x8FEF, 0xE78E, 0x9087, 0xE78F, 0x8FF4, 0xE790, 0x9005, + 0xE791, 0x8FF9, 0xE792, 0x8FFA, 0xE793, 0x9011, 0xE794, 0x9015, 0xE795, 0x9021, 0xE796, 0x900D, 0xE797, 0x901E, 0xE798, 0x9016, + 0xE799, 0x900B, 0xE79A, 0x9027, 0xE79B, 0x9036, 0xE79C, 0x9035, 0xE79D, 0x9039, 0xE79E, 0x8FF8, 0xE79F, 0x904F, 0xE7A0, 0x9050, + 0xE7A1, 0x9051, 0xE7A2, 0x9052, 0xE7A3, 0x900E, 0xE7A4, 0x9049, 0xE7A5, 0x903E, 0xE7A6, 0x9056, 0xE7A7, 0x9058, 0xE7A8, 0x905E, + 0xE7A9, 0x9068, 0xE7AA, 0x906F, 0xE7AB, 0x9076, 0xE7AC, 0x96A8, 0xE7AD, 0x9072, 0xE7AE, 0x9082, 0xE7AF, 0x907D, 0xE7B0, 0x9081, + 0xE7B1, 0x9080, 0xE7B2, 0x908A, 0xE7B3, 0x9089, 0xE7B4, 0x908F, 0xE7B5, 0x90A8, 0xE7B6, 0x90AF, 0xE7B7, 0x90B1, 0xE7B8, 0x90B5, + 0xE7B9, 0x90E2, 0xE7BA, 0x90E4, 0xE7BB, 0x6248, 0xE7BC, 0x90DB, 0xE7BD, 0x9102, 0xE7BE, 0x9112, 0xE7BF, 0x9119, 0xE7C0, 0x9132, + 0xE7C1, 0x9130, 0xE7C2, 0x914A, 0xE7C3, 0x9156, 0xE7C4, 0x9158, 0xE7C5, 0x9163, 0xE7C6, 0x9165, 0xE7C7, 0x9169, 0xE7C8, 0x9173, + 0xE7C9, 0x9172, 0xE7CA, 0x918B, 0xE7CB, 0x9189, 0xE7CC, 0x9182, 0xE7CD, 0x91A2, 0xE7CE, 0x91AB, 0xE7CF, 0x91AF, 0xE7D0, 0x91AA, + 0xE7D1, 0x91B5, 0xE7D2, 0x91B4, 0xE7D3, 0x91BA, 0xE7D4, 0x91C0, 0xE7D5, 0x91C1, 0xE7D6, 0x91C9, 0xE7D7, 0x91CB, 0xE7D8, 0x91D0, + 0xE7D9, 0x91D6, 0xE7DA, 0x91DF, 0xE7DB, 0x91E1, 0xE7DC, 0x91DB, 0xE7DD, 0x91FC, 0xE7DE, 0x91F5, 0xE7DF, 0x91F6, 0xE7E0, 0x921E, + 0xE7E1, 0x91FF, 0xE7E2, 0x9214, 0xE7E3, 0x922C, 0xE7E4, 0x9215, 0xE7E5, 0x9211, 0xE7E6, 0x925E, 0xE7E7, 0x9257, 0xE7E8, 0x9245, + 0xE7E9, 0x9249, 0xE7EA, 0x9264, 0xE7EB, 0x9248, 0xE7EC, 0x9295, 0xE7ED, 0x923F, 0xE7EE, 0x924B, 0xE7EF, 0x9250, 0xE7F0, 0x929C, + 0xE7F1, 0x9296, 0xE7F2, 0x9293, 0xE7F3, 0x929B, 0xE7F4, 0x925A, 0xE7F5, 0x92CF, 0xE7F6, 0x92B9, 0xE7F7, 0x92B7, 0xE7F8, 0x92E9, + 0xE7F9, 0x930F, 0xE7FA, 0x92FA, 0xE7FB, 0x9344, 0xE7FC, 0x932E, 0xE840, 0x9319, 0xE841, 0x9322, 0xE842, 0x931A, 0xE843, 0x9323, + 0xE844, 0x933A, 0xE845, 0x9335, 0xE846, 0x933B, 0xE847, 0x935C, 0xE848, 0x9360, 0xE849, 0x937C, 0xE84A, 0x936E, 0xE84B, 0x9356, + 0xE84C, 0x93B0, 0xE84D, 0x93AC, 0xE84E, 0x93AD, 0xE84F, 0x9394, 0xE850, 0x93B9, 0xE851, 0x93D6, 0xE852, 0x93D7, 0xE853, 0x93E8, + 0xE854, 0x93E5, 0xE855, 0x93D8, 0xE856, 0x93C3, 0xE857, 0x93DD, 0xE858, 0x93D0, 0xE859, 0x93C8, 0xE85A, 0x93E4, 0xE85B, 0x941A, + 0xE85C, 0x9414, 0xE85D, 0x9413, 0xE85E, 0x9403, 0xE85F, 0x9407, 0xE860, 0x9410, 0xE861, 0x9436, 0xE862, 0x942B, 0xE863, 0x9435, + 0xE864, 0x9421, 0xE865, 0x943A, 0xE866, 0x9441, 0xE867, 0x9452, 0xE868, 0x9444, 0xE869, 0x945B, 0xE86A, 0x9460, 0xE86B, 0x9462, + 0xE86C, 0x945E, 0xE86D, 0x946A, 0xE86E, 0x9229, 0xE86F, 0x9470, 0xE870, 0x9475, 0xE871, 0x9477, 0xE872, 0x947D, 0xE873, 0x945A, + 0xE874, 0x947C, 0xE875, 0x947E, 0xE876, 0x9481, 0xE877, 0x947F, 0xE878, 0x9582, 0xE879, 0x9587, 0xE87A, 0x958A, 0xE87B, 0x9594, + 0xE87C, 0x9596, 0xE87D, 0x9598, 0xE87E, 0x9599, 0xE880, 0x95A0, 0xE881, 0x95A8, 0xE882, 0x95A7, 0xE883, 0x95AD, 0xE884, 0x95BC, + 0xE885, 0x95BB, 0xE886, 0x95B9, 0xE887, 0x95BE, 0xE888, 0x95CA, 0xE889, 0x6FF6, 0xE88A, 0x95C3, 0xE88B, 0x95CD, 0xE88C, 0x95CC, + 0xE88D, 0x95D5, 0xE88E, 0x95D4, 0xE88F, 0x95D6, 0xE890, 0x95DC, 0xE891, 0x95E1, 0xE892, 0x95E5, 0xE893, 0x95E2, 0xE894, 0x9621, + 0xE895, 0x9628, 0xE896, 0x962E, 0xE897, 0x962F, 0xE898, 0x9642, 0xE899, 0x964C, 0xE89A, 0x964F, 0xE89B, 0x964B, 0xE89C, 0x9677, + 0xE89D, 0x965C, 0xE89E, 0x965E, 0xE89F, 0x965D, 0xE8A0, 0x965F, 0xE8A1, 0x9666, 0xE8A2, 0x9672, 0xE8A3, 0x966C, 0xE8A4, 0x968D, + 0xE8A5, 0x9698, 0xE8A6, 0x9695, 0xE8A7, 0x9697, 0xE8A8, 0x96AA, 0xE8A9, 0x96A7, 0xE8AA, 0x96B1, 0xE8AB, 0x96B2, 0xE8AC, 0x96B0, + 0xE8AD, 0x96B4, 0xE8AE, 0x96B6, 0xE8AF, 0x96B8, 0xE8B0, 0x96B9, 0xE8B1, 0x96CE, 0xE8B2, 0x96CB, 0xE8B3, 0x96C9, 0xE8B4, 0x96CD, + 0xE8B5, 0x894D, 0xE8B6, 0x96DC, 0xE8B7, 0x970D, 0xE8B8, 0x96D5, 0xE8B9, 0x96F9, 0xE8BA, 0x9704, 0xE8BB, 0x9706, 0xE8BC, 0x9708, + 0xE8BD, 0x9713, 0xE8BE, 0x970E, 0xE8BF, 0x9711, 0xE8C0, 0x970F, 0xE8C1, 0x9716, 0xE8C2, 0x9719, 0xE8C3, 0x9724, 0xE8C4, 0x972A, + 0xE8C5, 0x9730, 0xE8C6, 0x9739, 0xE8C7, 0x973D, 0xE8C8, 0x973E, 0xE8C9, 0x9744, 0xE8CA, 0x9746, 0xE8CB, 0x9748, 0xE8CC, 0x9742, + 0xE8CD, 0x9749, 0xE8CE, 0x975C, 0xE8CF, 0x9760, 0xE8D0, 0x9764, 0xE8D1, 0x9766, 0xE8D2, 0x9768, 0xE8D3, 0x52D2, 0xE8D4, 0x976B, + 0xE8D5, 0x9771, 0xE8D6, 0x9779, 0xE8D7, 0x9785, 0xE8D8, 0x977C, 0xE8D9, 0x9781, 0xE8DA, 0x977A, 0xE8DB, 0x9786, 0xE8DC, 0x978B, + 0xE8DD, 0x978F, 0xE8DE, 0x9790, 0xE8DF, 0x979C, 0xE8E0, 0x97A8, 0xE8E1, 0x97A6, 0xE8E2, 0x97A3, 0xE8E3, 0x97B3, 0xE8E4, 0x97B4, + 0xE8E5, 0x97C3, 0xE8E6, 0x97C6, 0xE8E7, 0x97C8, 0xE8E8, 0x97CB, 0xE8E9, 0x97DC, 0xE8EA, 0x97ED, 0xE8EB, 0x9F4F, 0xE8EC, 0x97F2, + 0xE8ED, 0x7ADF, 0xE8EE, 0x97F6, 0xE8EF, 0x97F5, 0xE8F0, 0x980F, 0xE8F1, 0x980C, 0xE8F2, 0x9838, 0xE8F3, 0x9824, 0xE8F4, 0x9821, + 0xE8F5, 0x9837, 0xE8F6, 0x983D, 0xE8F7, 0x9846, 0xE8F8, 0x984F, 0xE8F9, 0x984B, 0xE8FA, 0x986B, 0xE8FB, 0x986F, 0xE8FC, 0x9870, + 0xE940, 0x9871, 0xE941, 0x9874, 0xE942, 0x9873, 0xE943, 0x98AA, 0xE944, 0x98AF, 0xE945, 0x98B1, 0xE946, 0x98B6, 0xE947, 0x98C4, + 0xE948, 0x98C3, 0xE949, 0x98C6, 0xE94A, 0x98E9, 0xE94B, 0x98EB, 0xE94C, 0x9903, 0xE94D, 0x9909, 0xE94E, 0x9912, 0xE94F, 0x9914, + 0xE950, 0x9918, 0xE951, 0x9921, 0xE952, 0x991D, 0xE953, 0x991E, 0xE954, 0x9924, 0xE955, 0x9920, 0xE956, 0x992C, 0xE957, 0x992E, + 0xE958, 0x993D, 0xE959, 0x993E, 0xE95A, 0x9942, 0xE95B, 0x9949, 0xE95C, 0x9945, 0xE95D, 0x9950, 0xE95E, 0x994B, 0xE95F, 0x9951, + 0xE960, 0x9952, 0xE961, 0x994C, 0xE962, 0x9955, 0xE963, 0x9997, 0xE964, 0x9998, 0xE965, 0x99A5, 0xE966, 0x99AD, 0xE967, 0x99AE, + 0xE968, 0x99BC, 0xE969, 0x99DF, 0xE96A, 0x99DB, 0xE96B, 0x99DD, 0xE96C, 0x99D8, 0xE96D, 0x99D1, 0xE96E, 0x99ED, 0xE96F, 0x99EE, + 0xE970, 0x99F1, 0xE971, 0x99F2, 0xE972, 0x99FB, 0xE973, 0x99F8, 0xE974, 0x9A01, 0xE975, 0x9A0F, 0xE976, 0x9A05, 0xE977, 0x99E2, + 0xE978, 0x9A19, 0xE979, 0x9A2B, 0xE97A, 0x9A37, 0xE97B, 0x9A45, 0xE97C, 0x9A42, 0xE97D, 0x9A40, 0xE97E, 0x9A43, 0xE980, 0x9A3E, + 0xE981, 0x9A55, 0xE982, 0x9A4D, 0xE983, 0x9A5B, 0xE984, 0x9A57, 0xE985, 0x9A5F, 0xE986, 0x9A62, 0xE987, 0x9A65, 0xE988, 0x9A64, + 0xE989, 0x9A69, 0xE98A, 0x9A6B, 0xE98B, 0x9A6A, 0xE98C, 0x9AAD, 0xE98D, 0x9AB0, 0xE98E, 0x9ABC, 0xE98F, 0x9AC0, 0xE990, 0x9ACF, + 0xE991, 0x9AD1, 0xE992, 0x9AD3, 0xE993, 0x9AD4, 0xE994, 0x9ADE, 0xE995, 0x9ADF, 0xE996, 0x9AE2, 0xE997, 0x9AE3, 0xE998, 0x9AE6, + 0xE999, 0x9AEF, 0xE99A, 0x9AEB, 0xE99B, 0x9AEE, 0xE99C, 0x9AF4, 0xE99D, 0x9AF1, 0xE99E, 0x9AF7, 0xE99F, 0x9AFB, 0xE9A0, 0x9B06, + 0xE9A1, 0x9B18, 0xE9A2, 0x9B1A, 0xE9A3, 0x9B1F, 0xE9A4, 0x9B22, 0xE9A5, 0x9B23, 0xE9A6, 0x9B25, 0xE9A7, 0x9B27, 0xE9A8, 0x9B28, + 0xE9A9, 0x9B29, 0xE9AA, 0x9B2A, 0xE9AB, 0x9B2E, 0xE9AC, 0x9B2F, 0xE9AD, 0x9B32, 0xE9AE, 0x9B44, 0xE9AF, 0x9B43, 0xE9B0, 0x9B4F, + 0xE9B1, 0x9B4D, 0xE9B2, 0x9B4E, 0xE9B3, 0x9B51, 0xE9B4, 0x9B58, 0xE9B5, 0x9B74, 0xE9B6, 0x9B93, 0xE9B7, 0x9B83, 0xE9B8, 0x9B91, + 0xE9B9, 0x9B96, 0xE9BA, 0x9B97, 0xE9BB, 0x9B9F, 0xE9BC, 0x9BA0, 0xE9BD, 0x9BA8, 0xE9BE, 0x9BB4, 0xE9BF, 0x9BC0, 0xE9C0, 0x9BCA, + 0xE9C1, 0x9BB9, 0xE9C2, 0x9BC6, 0xE9C3, 0x9BCF, 0xE9C4, 0x9BD1, 0xE9C5, 0x9BD2, 0xE9C6, 0x9BE3, 0xE9C7, 0x9BE2, 0xE9C8, 0x9BE4, + 0xE9C9, 0x9BD4, 0xE9CA, 0x9BE1, 0xE9CB, 0x9C3A, 0xE9CC, 0x9BF2, 0xE9CD, 0x9BF1, 0xE9CE, 0x9BF0, 0xE9CF, 0x9C15, 0xE9D0, 0x9C14, + 0xE9D1, 0x9C09, 0xE9D2, 0x9C13, 0xE9D3, 0x9C0C, 0xE9D4, 0x9C06, 0xE9D5, 0x9C08, 0xE9D6, 0x9C12, 0xE9D7, 0x9C0A, 0xE9D8, 0x9C04, + 0xE9D9, 0x9C2E, 0xE9DA, 0x9C1B, 0xE9DB, 0x9C25, 0xE9DC, 0x9C24, 0xE9DD, 0x9C21, 0xE9DE, 0x9C30, 0xE9DF, 0x9C47, 0xE9E0, 0x9C32, + 0xE9E1, 0x9C46, 0xE9E2, 0x9C3E, 0xE9E3, 0x9C5A, 0xE9E4, 0x9C60, 0xE9E5, 0x9C67, 0xE9E6, 0x9C76, 0xE9E7, 0x9C78, 0xE9E8, 0x9CE7, + 0xE9E9, 0x9CEC, 0xE9EA, 0x9CF0, 0xE9EB, 0x9D09, 0xE9EC, 0x9D08, 0xE9ED, 0x9CEB, 0xE9EE, 0x9D03, 0xE9EF, 0x9D06, 0xE9F0, 0x9D2A, + 0xE9F1, 0x9D26, 0xE9F2, 0x9DAF, 0xE9F3, 0x9D23, 0xE9F4, 0x9D1F, 0xE9F5, 0x9D44, 0xE9F6, 0x9D15, 0xE9F7, 0x9D12, 0xE9F8, 0x9D41, + 0xE9F9, 0x9D3F, 0xE9FA, 0x9D3E, 0xE9FB, 0x9D46, 0xE9FC, 0x9D48, 0xEA40, 0x9D5D, 0xEA41, 0x9D5E, 0xEA42, 0x9D64, 0xEA43, 0x9D51, + 0xEA44, 0x9D50, 0xEA45, 0x9D59, 0xEA46, 0x9D72, 0xEA47, 0x9D89, 0xEA48, 0x9D87, 0xEA49, 0x9DAB, 0xEA4A, 0x9D6F, 0xEA4B, 0x9D7A, + 0xEA4C, 0x9D9A, 0xEA4D, 0x9DA4, 0xEA4E, 0x9DA9, 0xEA4F, 0x9DB2, 0xEA50, 0x9DC4, 0xEA51, 0x9DC1, 0xEA52, 0x9DBB, 0xEA53, 0x9DB8, + 0xEA54, 0x9DBA, 0xEA55, 0x9DC6, 0xEA56, 0x9DCF, 0xEA57, 0x9DC2, 0xEA58, 0x9DD9, 0xEA59, 0x9DD3, 0xEA5A, 0x9DF8, 0xEA5B, 0x9DE6, + 0xEA5C, 0x9DED, 0xEA5D, 0x9DEF, 0xEA5E, 0x9DFD, 0xEA5F, 0x9E1A, 0xEA60, 0x9E1B, 0xEA61, 0x9E1E, 0xEA62, 0x9E75, 0xEA63, 0x9E79, + 0xEA64, 0x9E7D, 0xEA65, 0x9E81, 0xEA66, 0x9E88, 0xEA67, 0x9E8B, 0xEA68, 0x9E8C, 0xEA69, 0x9E92, 0xEA6A, 0x9E95, 0xEA6B, 0x9E91, + 0xEA6C, 0x9E9D, 0xEA6D, 0x9EA5, 0xEA6E, 0x9EA9, 0xEA6F, 0x9EB8, 0xEA70, 0x9EAA, 0xEA71, 0x9EAD, 0xEA72, 0x9761, 0xEA73, 0x9ECC, + 0xEA74, 0x9ECE, 0xEA75, 0x9ECF, 0xEA76, 0x9ED0, 0xEA77, 0x9ED4, 0xEA78, 0x9EDC, 0xEA79, 0x9EDE, 0xEA7A, 0x9EDD, 0xEA7B, 0x9EE0, + 0xEA7C, 0x9EE5, 0xEA7D, 0x9EE8, 0xEA7E, 0x9EEF, 0xEA80, 0x9EF4, 0xEA81, 0x9EF6, 0xEA82, 0x9EF7, 0xEA83, 0x9EF9, 0xEA84, 0x9EFB, + 0xEA85, 0x9EFC, 0xEA86, 0x9EFD, 0xEA87, 0x9F07, 0xEA88, 0x9F08, 0xEA89, 0x76B7, 0xEA8A, 0x9F15, 0xEA8B, 0x9F21, 0xEA8C, 0x9F2C, + 0xEA8D, 0x9F3E, 0xEA8E, 0x9F4A, 0xEA8F, 0x9F52, 0xEA90, 0x9F54, 0xEA91, 0x9F63, 0xEA92, 0x9F5F, 0xEA93, 0x9F60, 0xEA94, 0x9F61, + 0xEA95, 0x9F66, 0xEA96, 0x9F67, 0xEA97, 0x9F6C, 0xEA98, 0x9F6A, 0xEA99, 0x9F77, 0xEA9A, 0x9F72, 0xEA9B, 0x9F76, 0xEA9C, 0x9F95, + 0xEA9D, 0x9F9C, 0xEA9E, 0x9FA0, 0xEA9F, 0x582F, 0xEAA0, 0x69C7, 0xEAA1, 0x9059, 0xEAA2, 0x7464, 0xEAA3, 0x51DC, 0xEAA4, 0x7199, + 0xFA40, 0x2170, 0xFA41, 0x2171, 0xFA42, 0x2172, 0xFA43, 0x2173, 0xFA44, 0x2174, 0xFA45, 0x2175, 0xFA46, 0x2176, 0xFA47, 0x2177, + 0xFA48, 0x2178, 0xFA49, 0x2179, 0xFA55, 0xFFE4, 0xFA56, 0xFF07, 0xFA57, 0xFF02, 0xFA5C, 0x7E8A, 0xFA5D, 0x891C, 0xFA5E, 0x9348, + 0xFA5F, 0x9288, 0xFA60, 0x84DC, 0xFA61, 0x4FC9, 0xFA62, 0x70BB, 0xFA63, 0x6631, 0xFA64, 0x68C8, 0xFA65, 0x92F9, 0xFA66, 0x66FB, + 0xFA67, 0x5F45, 0xFA68, 0x4E28, 0xFA69, 0x4EE1, 0xFA6A, 0x4EFC, 0xFA6B, 0x4F00, 0xFA6C, 0x4F03, 0xFA6D, 0x4F39, 0xFA6E, 0x4F56, + 0xFA6F, 0x4F92, 0xFA70, 0x4F8A, 0xFA71, 0x4F9A, 0xFA72, 0x4F94, 0xFA73, 0x4FCD, 0xFA74, 0x5040, 0xFA75, 0x5022, 0xFA76, 0x4FFF, + 0xFA77, 0x501E, 0xFA78, 0x5046, 0xFA79, 0x5070, 0xFA7A, 0x5042, 0xFA7B, 0x5094, 0xFA7C, 0x50F4, 0xFA7D, 0x50D8, 0xFA7E, 0x514A, + 0xFA80, 0x5164, 0xFA81, 0x519D, 0xFA82, 0x51BE, 0xFA83, 0x51EC, 0xFA84, 0x5215, 0xFA85, 0x529C, 0xFA86, 0x52A6, 0xFA87, 0x52C0, + 0xFA88, 0x52DB, 0xFA89, 0x5300, 0xFA8A, 0x5307, 0xFA8B, 0x5324, 0xFA8C, 0x5372, 0xFA8D, 0x5393, 0xFA8E, 0x53B2, 0xFA8F, 0x53DD, + 0xFA90, 0xFA0E, 0xFA91, 0x549C, 0xFA92, 0x548A, 0xFA93, 0x54A9, 0xFA94, 0x54FF, 0xFA95, 0x5586, 0xFA96, 0x5759, 0xFA97, 0x5765, + 0xFA98, 0x57AC, 0xFA99, 0x57C8, 0xFA9A, 0x57C7, 0xFA9B, 0xFA0F, 0xFA9C, 0xFA10, 0xFA9D, 0x589E, 0xFA9E, 0x58B2, 0xFA9F, 0x590B, + 0xFAA0, 0x5953, 0xFAA1, 0x595B, 0xFAA2, 0x595D, 0xFAA3, 0x5963, 0xFAA4, 0x59A4, 0xFAA5, 0x59BA, 0xFAA6, 0x5B56, 0xFAA7, 0x5BC0, + 0xFAA8, 0x752F, 0xFAA9, 0x5BD8, 0xFAAA, 0x5BEC, 0xFAAB, 0x5C1E, 0xFAAC, 0x5CA6, 0xFAAD, 0x5CBA, 0xFAAE, 0x5CF5, 0xFAAF, 0x5D27, + 0xFAB0, 0x5D53, 0xFAB1, 0xFA11, 0xFAB2, 0x5D42, 0xFAB3, 0x5D6D, 0xFAB4, 0x5DB8, 0xFAB5, 0x5DB9, 0xFAB6, 0x5DD0, 0xFAB7, 0x5F21, + 0xFAB8, 0x5F34, 0xFAB9, 0x5F67, 0xFABA, 0x5FB7, 0xFABB, 0x5FDE, 0xFABC, 0x605D, 0xFABD, 0x6085, 0xFABE, 0x608A, 0xFABF, 0x60DE, + 0xFAC0, 0x60D5, 0xFAC1, 0x6120, 0xFAC2, 0x60F2, 0xFAC3, 0x6111, 0xFAC4, 0x6137, 0xFAC5, 0x6130, 0xFAC6, 0x6198, 0xFAC7, 0x6213, + 0xFAC8, 0x62A6, 0xFAC9, 0x63F5, 0xFACA, 0x6460, 0xFACB, 0x649D, 0xFACC, 0x64CE, 0xFACD, 0x654E, 0xFACE, 0x6600, 0xFACF, 0x6615, + 0xFAD0, 0x663B, 0xFAD1, 0x6609, 0xFAD2, 0x662E, 0xFAD3, 0x661E, 0xFAD4, 0x6624, 0xFAD5, 0x6665, 0xFAD6, 0x6657, 0xFAD7, 0x6659, + 0xFAD8, 0xFA12, 0xFAD9, 0x6673, 0xFADA, 0x6699, 0xFADB, 0x66A0, 0xFADC, 0x66B2, 0xFADD, 0x66BF, 0xFADE, 0x66FA, 0xFADF, 0x670E, + 0xFAE0, 0xF929, 0xFAE1, 0x6766, 0xFAE2, 0x67BB, 0xFAE3, 0x6852, 0xFAE4, 0x67C0, 0xFAE5, 0x6801, 0xFAE6, 0x6844, 0xFAE7, 0x68CF, + 0xFAE8, 0xFA13, 0xFAE9, 0x6968, 0xFAEA, 0xFA14, 0xFAEB, 0x6998, 0xFAEC, 0x69E2, 0xFAED, 0x6A30, 0xFAEE, 0x6A6B, 0xFAEF, 0x6A46, + 0xFAF0, 0x6A73, 0xFAF1, 0x6A7E, 0xFAF2, 0x6AE2, 0xFAF3, 0x6AE4, 0xFAF4, 0x6BD6, 0xFAF5, 0x6C3F, 0xFAF6, 0x6C5C, 0xFAF7, 0x6C86, + 0xFAF8, 0x6C6F, 0xFAF9, 0x6CDA, 0xFAFA, 0x6D04, 0xFAFB, 0x6D87, 0xFAFC, 0x6D6F, 0xFB40, 0x6D96, 0xFB41, 0x6DAC, 0xFB42, 0x6DCF, + 0xFB43, 0x6DF8, 0xFB44, 0x6DF2, 0xFB45, 0x6DFC, 0xFB46, 0x6E39, 0xFB47, 0x6E5C, 0xFB48, 0x6E27, 0xFB49, 0x6E3C, 0xFB4A, 0x6EBF, + 0xFB4B, 0x6F88, 0xFB4C, 0x6FB5, 0xFB4D, 0x6FF5, 0xFB4E, 0x7005, 0xFB4F, 0x7007, 0xFB50, 0x7028, 0xFB51, 0x7085, 0xFB52, 0x70AB, + 0xFB53, 0x710F, 0xFB54, 0x7104, 0xFB55, 0x715C, 0xFB56, 0x7146, 0xFB57, 0x7147, 0xFB58, 0xFA15, 0xFB59, 0x71C1, 0xFB5A, 0x71FE, + 0xFB5B, 0x72B1, 0xFB5C, 0x72BE, 0xFB5D, 0x7324, 0xFB5E, 0xFA16, 0xFB5F, 0x7377, 0xFB60, 0x73BD, 0xFB61, 0x73C9, 0xFB62, 0x73D6, + 0xFB63, 0x73E3, 0xFB64, 0x73D2, 0xFB65, 0x7407, 0xFB66, 0x73F5, 0xFB67, 0x7426, 0xFB68, 0x742A, 0xFB69, 0x7429, 0xFB6A, 0x742E, + 0xFB6B, 0x7462, 0xFB6C, 0x7489, 0xFB6D, 0x749F, 0xFB6E, 0x7501, 0xFB6F, 0x756F, 0xFB70, 0x7682, 0xFB71, 0x769C, 0xFB72, 0x769E, + 0xFB73, 0x769B, 0xFB74, 0x76A6, 0xFB75, 0xFA17, 0xFB76, 0x7746, 0xFB77, 0x52AF, 0xFB78, 0x7821, 0xFB79, 0x784E, 0xFB7A, 0x7864, + 0xFB7B, 0x787A, 0xFB7C, 0x7930, 0xFB7D, 0xFA18, 0xFB7E, 0xFA19, 0xFB80, 0xFA1A, 0xFB81, 0x7994, 0xFB82, 0xFA1B, 0xFB83, 0x799B, + 0xFB84, 0x7AD1, 0xFB85, 0x7AE7, 0xFB86, 0xFA1C, 0xFB87, 0x7AEB, 0xFB88, 0x7B9E, 0xFB89, 0xFA1D, 0xFB8A, 0x7D48, 0xFB8B, 0x7D5C, + 0xFB8C, 0x7DB7, 0xFB8D, 0x7DA0, 0xFB8E, 0x7DD6, 0xFB8F, 0x7E52, 0xFB90, 0x7F47, 0xFB91, 0x7FA1, 0xFB92, 0xFA1E, 0xFB93, 0x8301, + 0xFB94, 0x8362, 0xFB95, 0x837F, 0xFB96, 0x83C7, 0xFB97, 0x83F6, 0xFB98, 0x8448, 0xFB99, 0x84B4, 0xFB9A, 0x8553, 0xFB9B, 0x8559, + 0xFB9C, 0x856B, 0xFB9D, 0xFA1F, 0xFB9E, 0x85B0, 0xFB9F, 0xFA20, 0xFBA0, 0xFA21, 0xFBA1, 0x8807, 0xFBA2, 0x88F5, 0xFBA3, 0x8A12, + 0xFBA4, 0x8A37, 0xFBA5, 0x8A79, 0xFBA6, 0x8AA7, 0xFBA7, 0x8ABE, 0xFBA8, 0x8ADF, 0xFBA9, 0xFA22, 0xFBAA, 0x8AF6, 0xFBAB, 0x8B53, + 0xFBAC, 0x8B7F, 0xFBAD, 0x8CF0, 0xFBAE, 0x8CF4, 0xFBAF, 0x8D12, 0xFBB0, 0x8D76, 0xFBB1, 0xFA23, 0xFBB2, 0x8ECF, 0xFBB3, 0xFA24, + 0xFBB4, 0xFA25, 0xFBB5, 0x9067, 0xFBB6, 0x90DE, 0xFBB7, 0xFA26, 0xFBB8, 0x9115, 0xFBB9, 0x9127, 0xFBBA, 0x91DA, 0xFBBB, 0x91D7, + 0xFBBC, 0x91DE, 0xFBBD, 0x91ED, 0xFBBE, 0x91EE, 0xFBBF, 0x91E4, 0xFBC0, 0x91E5, 0xFBC1, 0x9206, 0xFBC2, 0x9210, 0xFBC3, 0x920A, + 0xFBC4, 0x923A, 0xFBC5, 0x9240, 0xFBC6, 0x923C, 0xFBC7, 0x924E, 0xFBC8, 0x9259, 0xFBC9, 0x9251, 0xFBCA, 0x9239, 0xFBCB, 0x9267, + 0xFBCC, 0x92A7, 0xFBCD, 0x9277, 0xFBCE, 0x9278, 0xFBCF, 0x92E7, 0xFBD0, 0x92D7, 0xFBD1, 0x92D9, 0xFBD2, 0x92D0, 0xFBD3, 0xFA27, + 0xFBD4, 0x92D5, 0xFBD5, 0x92E0, 0xFBD6, 0x92D3, 0xFBD7, 0x9325, 0xFBD8, 0x9321, 0xFBD9, 0x92FB, 0xFBDA, 0xFA28, 0xFBDB, 0x931E, + 0xFBDC, 0x92FF, 0xFBDD, 0x931D, 0xFBDE, 0x9302, 0xFBDF, 0x9370, 0xFBE0, 0x9357, 0xFBE1, 0x93A4, 0xFBE2, 0x93C6, 0xFBE3, 0x93DE, + 0xFBE4, 0x93F8, 0xFBE5, 0x9431, 0xFBE6, 0x9445, 0xFBE7, 0x9448, 0xFBE8, 0x9592, 0xFBE9, 0xF9DC, 0xFBEA, 0xFA29, 0xFBEB, 0x969D, + 0xFBEC, 0x96AF, 0xFBED, 0x9733, 0xFBEE, 0x973B, 0xFBEF, 0x9743, 0xFBF0, 0x974D, 0xFBF1, 0x974F, 0xFBF2, 0x9751, 0xFBF3, 0x9755, + 0xFBF4, 0x9857, 0xFBF5, 0x9865, 0xFBF6, 0xFA2A, 0xFBF7, 0xFA2B, 0xFBF8, 0x9927, 0xFBF9, 0xFA2C, 0xFBFA, 0x999E, 0xFBFB, 0x9A4E, + 0xFBFC, 0x9AD9, 0xFC40, 0x9ADC, 0xFC41, 0x9B75, 0xFC42, 0x9B72, 0xFC43, 0x9B8F, 0xFC44, 0x9BB1, 0xFC45, 0x9BBB, 0xFC46, 0x9C00, + 0xFC47, 0x9D70, 0xFC48, 0x9D6B, 0xFC49, 0xFA2D, 0xFC4A, 0x9E19, 0xFC4B, 0x9ED1, 0, 0 +}; +#endif + +#if FF_CODE_PAGE == 936 || FF_CODE_PAGE == 0 /* Simplified Chinese */ +static const WCHAR uni2oem936[] = { /* Unicode --> GBK pairs */ + 0x00A4, 0xA1E8, 0x00A7, 0xA1EC, 0x00A8, 0xA1A7, 0x00B0, 0xA1E3, 0x00B1, 0xA1C0, 0x00B7, 0xA1A4, 0x00D7, 0xA1C1, 0x00E0, 0xA8A4, + 0x00E1, 0xA8A2, 0x00E8, 0xA8A8, 0x00E9, 0xA8A6, 0x00EA, 0xA8BA, 0x00EC, 0xA8AC, 0x00ED, 0xA8AA, 0x00F2, 0xA8B0, 0x00F3, 0xA8AE, + 0x00F7, 0xA1C2, 0x00F9, 0xA8B4, 0x00FA, 0xA8B2, 0x00FC, 0xA8B9, 0x0101, 0xA8A1, 0x0113, 0xA8A5, 0x011B, 0xA8A7, 0x012B, 0xA8A9, + 0x0144, 0xA8BD, 0x0148, 0xA8BE, 0x014D, 0xA8AD, 0x016B, 0xA8B1, 0x01CE, 0xA8A3, 0x01D0, 0xA8AB, 0x01D2, 0xA8AF, 0x01D4, 0xA8B3, + 0x01D6, 0xA8B5, 0x01D8, 0xA8B6, 0x01DA, 0xA8B7, 0x01DC, 0xA8B8, 0x0251, 0xA8BB, 0x0261, 0xA8C0, 0x02C7, 0xA1A6, 0x02C9, 0xA1A5, + 0x02CA, 0xA840, 0x02CB, 0xA841, 0x02D9, 0xA842, 0x0391, 0xA6A1, 0x0392, 0xA6A2, 0x0393, 0xA6A3, 0x0394, 0xA6A4, 0x0395, 0xA6A5, + 0x0396, 0xA6A6, 0x0397, 0xA6A7, 0x0398, 0xA6A8, 0x0399, 0xA6A9, 0x039A, 0xA6AA, 0x039B, 0xA6AB, 0x039C, 0xA6AC, 0x039D, 0xA6AD, + 0x039E, 0xA6AE, 0x039F, 0xA6AF, 0x03A0, 0xA6B0, 0x03A1, 0xA6B1, 0x03A3, 0xA6B2, 0x03A4, 0xA6B3, 0x03A5, 0xA6B4, 0x03A6, 0xA6B5, + 0x03A7, 0xA6B6, 0x03A8, 0xA6B7, 0x03A9, 0xA6B8, 0x03B1, 0xA6C1, 0x03B2, 0xA6C2, 0x03B3, 0xA6C3, 0x03B4, 0xA6C4, 0x03B5, 0xA6C5, + 0x03B6, 0xA6C6, 0x03B7, 0xA6C7, 0x03B8, 0xA6C8, 0x03B9, 0xA6C9, 0x03BA, 0xA6CA, 0x03BB, 0xA6CB, 0x03BC, 0xA6CC, 0x03BD, 0xA6CD, + 0x03BE, 0xA6CE, 0x03BF, 0xA6CF, 0x03C0, 0xA6D0, 0x03C1, 0xA6D1, 0x03C3, 0xA6D2, 0x03C4, 0xA6D3, 0x03C5, 0xA6D4, 0x03C6, 0xA6D5, + 0x03C7, 0xA6D6, 0x03C8, 0xA6D7, 0x03C9, 0xA6D8, 0x0401, 0xA7A7, 0x0410, 0xA7A1, 0x0411, 0xA7A2, 0x0412, 0xA7A3, 0x0413, 0xA7A4, + 0x0414, 0xA7A5, 0x0415, 0xA7A6, 0x0416, 0xA7A8, 0x0417, 0xA7A9, 0x0418, 0xA7AA, 0x0419, 0xA7AB, 0x041A, 0xA7AC, 0x041B, 0xA7AD, + 0x041C, 0xA7AE, 0x041D, 0xA7AF, 0x041E, 0xA7B0, 0x041F, 0xA7B1, 0x0420, 0xA7B2, 0x0421, 0xA7B3, 0x0422, 0xA7B4, 0x0423, 0xA7B5, + 0x0424, 0xA7B6, 0x0425, 0xA7B7, 0x0426, 0xA7B8, 0x0427, 0xA7B9, 0x0428, 0xA7BA, 0x0429, 0xA7BB, 0x042A, 0xA7BC, 0x042B, 0xA7BD, + 0x042C, 0xA7BE, 0x042D, 0xA7BF, 0x042E, 0xA7C0, 0x042F, 0xA7C1, 0x0430, 0xA7D1, 0x0431, 0xA7D2, 0x0432, 0xA7D3, 0x0433, 0xA7D4, + 0x0434, 0xA7D5, 0x0435, 0xA7D6, 0x0436, 0xA7D8, 0x0437, 0xA7D9, 0x0438, 0xA7DA, 0x0439, 0xA7DB, 0x043A, 0xA7DC, 0x043B, 0xA7DD, + 0x043C, 0xA7DE, 0x043D, 0xA7DF, 0x043E, 0xA7E0, 0x043F, 0xA7E1, 0x0440, 0xA7E2, 0x0441, 0xA7E3, 0x0442, 0xA7E4, 0x0443, 0xA7E5, + 0x0444, 0xA7E6, 0x0445, 0xA7E7, 0x0446, 0xA7E8, 0x0447, 0xA7E9, 0x0448, 0xA7EA, 0x0449, 0xA7EB, 0x044A, 0xA7EC, 0x044B, 0xA7ED, + 0x044C, 0xA7EE, 0x044D, 0xA7EF, 0x044E, 0xA7F0, 0x044F, 0xA7F1, 0x0451, 0xA7D7, 0x2010, 0xA95C, 0x2013, 0xA843, 0x2014, 0xA1AA, + 0x2015, 0xA844, 0x2016, 0xA1AC, 0x2018, 0xA1AE, 0x2019, 0xA1AF, 0x201C, 0xA1B0, 0x201D, 0xA1B1, 0x2025, 0xA845, 0x2026, 0xA1AD, + 0x2030, 0xA1EB, 0x2032, 0xA1E4, 0x2033, 0xA1E5, 0x2035, 0xA846, 0x203B, 0xA1F9, 0x20AC, 0x0080, 0x2103, 0xA1E6, 0x2105, 0xA847, + 0x2109, 0xA848, 0x2116, 0xA1ED, 0x2121, 0xA959, 0x2160, 0xA2F1, 0x2161, 0xA2F2, 0x2162, 0xA2F3, 0x2163, 0xA2F4, 0x2164, 0xA2F5, + 0x2165, 0xA2F6, 0x2166, 0xA2F7, 0x2167, 0xA2F8, 0x2168, 0xA2F9, 0x2169, 0xA2FA, 0x216A, 0xA2FB, 0x216B, 0xA2FC, 0x2170, 0xA2A1, + 0x2171, 0xA2A2, 0x2172, 0xA2A3, 0x2173, 0xA2A4, 0x2174, 0xA2A5, 0x2175, 0xA2A6, 0x2176, 0xA2A7, 0x2177, 0xA2A8, 0x2178, 0xA2A9, + 0x2179, 0xA2AA, 0x2190, 0xA1FB, 0x2191, 0xA1FC, 0x2192, 0xA1FA, 0x2193, 0xA1FD, 0x2196, 0xA849, 0x2197, 0xA84A, 0x2198, 0xA84B, + 0x2199, 0xA84C, 0x2208, 0xA1CA, 0x220F, 0xA1C7, 0x2211, 0xA1C6, 0x2215, 0xA84D, 0x221A, 0xA1CC, 0x221D, 0xA1D8, 0x221E, 0xA1DE, + 0x221F, 0xA84E, 0x2220, 0xA1CF, 0x2223, 0xA84F, 0x2225, 0xA1CE, 0x2227, 0xA1C4, 0x2228, 0xA1C5, 0x2229, 0xA1C9, 0x222A, 0xA1C8, + 0x222B, 0xA1D2, 0x222E, 0xA1D3, 0x2234, 0xA1E0, 0x2235, 0xA1DF, 0x2236, 0xA1C3, 0x2237, 0xA1CB, 0x223D, 0xA1D7, 0x2248, 0xA1D6, + 0x224C, 0xA1D5, 0x2252, 0xA850, 0x2260, 0xA1D9, 0x2261, 0xA1D4, 0x2264, 0xA1DC, 0x2265, 0xA1DD, 0x2266, 0xA851, 0x2267, 0xA852, + 0x226E, 0xA1DA, 0x226F, 0xA1DB, 0x2295, 0xA892, 0x2299, 0xA1D1, 0x22A5, 0xA1CD, 0x22BF, 0xA853, 0x2312, 0xA1D0, 0x2460, 0xA2D9, + 0x2461, 0xA2DA, 0x2462, 0xA2DB, 0x2463, 0xA2DC, 0x2464, 0xA2DD, 0x2465, 0xA2DE, 0x2466, 0xA2DF, 0x2467, 0xA2E0, 0x2468, 0xA2E1, + 0x2469, 0xA2E2, 0x2474, 0xA2C5, 0x2475, 0xA2C6, 0x2476, 0xA2C7, 0x2477, 0xA2C8, 0x2478, 0xA2C9, 0x2479, 0xA2CA, 0x247A, 0xA2CB, + 0x247B, 0xA2CC, 0x247C, 0xA2CD, 0x247D, 0xA2CE, 0x247E, 0xA2CF, 0x247F, 0xA2D0, 0x2480, 0xA2D1, 0x2481, 0xA2D2, 0x2482, 0xA2D3, + 0x2483, 0xA2D4, 0x2484, 0xA2D5, 0x2485, 0xA2D6, 0x2486, 0xA2D7, 0x2487, 0xA2D8, 0x2488, 0xA2B1, 0x2489, 0xA2B2, 0x248A, 0xA2B3, + 0x248B, 0xA2B4, 0x248C, 0xA2B5, 0x248D, 0xA2B6, 0x248E, 0xA2B7, 0x248F, 0xA2B8, 0x2490, 0xA2B9, 0x2491, 0xA2BA, 0x2492, 0xA2BB, + 0x2493, 0xA2BC, 0x2494, 0xA2BD, 0x2495, 0xA2BE, 0x2496, 0xA2BF, 0x2497, 0xA2C0, 0x2498, 0xA2C1, 0x2499, 0xA2C2, 0x249A, 0xA2C3, + 0x249B, 0xA2C4, 0x2500, 0xA9A4, 0x2501, 0xA9A5, 0x2502, 0xA9A6, 0x2503, 0xA9A7, 0x2504, 0xA9A8, 0x2505, 0xA9A9, 0x2506, 0xA9AA, + 0x2507, 0xA9AB, 0x2508, 0xA9AC, 0x2509, 0xA9AD, 0x250A, 0xA9AE, 0x250B, 0xA9AF, 0x250C, 0xA9B0, 0x250D, 0xA9B1, 0x250E, 0xA9B2, + 0x250F, 0xA9B3, 0x2510, 0xA9B4, 0x2511, 0xA9B5, 0x2512, 0xA9B6, 0x2513, 0xA9B7, 0x2514, 0xA9B8, 0x2515, 0xA9B9, 0x2516, 0xA9BA, + 0x2517, 0xA9BB, 0x2518, 0xA9BC, 0x2519, 0xA9BD, 0x251A, 0xA9BE, 0x251B, 0xA9BF, 0x251C, 0xA9C0, 0x251D, 0xA9C1, 0x251E, 0xA9C2, + 0x251F, 0xA9C3, 0x2520, 0xA9C4, 0x2521, 0xA9C5, 0x2522, 0xA9C6, 0x2523, 0xA9C7, 0x2524, 0xA9C8, 0x2525, 0xA9C9, 0x2526, 0xA9CA, + 0x2527, 0xA9CB, 0x2528, 0xA9CC, 0x2529, 0xA9CD, 0x252A, 0xA9CE, 0x252B, 0xA9CF, 0x252C, 0xA9D0, 0x252D, 0xA9D1, 0x252E, 0xA9D2, + 0x252F, 0xA9D3, 0x2530, 0xA9D4, 0x2531, 0xA9D5, 0x2532, 0xA9D6, 0x2533, 0xA9D7, 0x2534, 0xA9D8, 0x2535, 0xA9D9, 0x2536, 0xA9DA, + 0x2537, 0xA9DB, 0x2538, 0xA9DC, 0x2539, 0xA9DD, 0x253A, 0xA9DE, 0x253B, 0xA9DF, 0x253C, 0xA9E0, 0x253D, 0xA9E1, 0x253E, 0xA9E2, + 0x253F, 0xA9E3, 0x2540, 0xA9E4, 0x2541, 0xA9E5, 0x2542, 0xA9E6, 0x2543, 0xA9E7, 0x2544, 0xA9E8, 0x2545, 0xA9E9, 0x2546, 0xA9EA, + 0x2547, 0xA9EB, 0x2548, 0xA9EC, 0x2549, 0xA9ED, 0x254A, 0xA9EE, 0x254B, 0xA9EF, 0x2550, 0xA854, 0x2551, 0xA855, 0x2552, 0xA856, + 0x2553, 0xA857, 0x2554, 0xA858, 0x2555, 0xA859, 0x2556, 0xA85A, 0x2557, 0xA85B, 0x2558, 0xA85C, 0x2559, 0xA85D, 0x255A, 0xA85E, + 0x255B, 0xA85F, 0x255C, 0xA860, 0x255D, 0xA861, 0x255E, 0xA862, 0x255F, 0xA863, 0x2560, 0xA864, 0x2561, 0xA865, 0x2562, 0xA866, + 0x2563, 0xA867, 0x2564, 0xA868, 0x2565, 0xA869, 0x2566, 0xA86A, 0x2567, 0xA86B, 0x2568, 0xA86C, 0x2569, 0xA86D, 0x256A, 0xA86E, + 0x256B, 0xA86F, 0x256C, 0xA870, 0x256D, 0xA871, 0x256E, 0xA872, 0x256F, 0xA873, 0x2570, 0xA874, 0x2571, 0xA875, 0x2572, 0xA876, + 0x2573, 0xA877, 0x2581, 0xA878, 0x2582, 0xA879, 0x2583, 0xA87A, 0x2584, 0xA87B, 0x2585, 0xA87C, 0x2586, 0xA87D, 0x2587, 0xA87E, + 0x2588, 0xA880, 0x2589, 0xA881, 0x258A, 0xA882, 0x258B, 0xA883, 0x258C, 0xA884, 0x258D, 0xA885, 0x258E, 0xA886, 0x258F, 0xA887, + 0x2593, 0xA888, 0x2594, 0xA889, 0x2595, 0xA88A, 0x25A0, 0xA1F6, 0x25A1, 0xA1F5, 0x25B2, 0xA1F8, 0x25B3, 0xA1F7, 0x25BC, 0xA88B, + 0x25BD, 0xA88C, 0x25C6, 0xA1F4, 0x25C7, 0xA1F3, 0x25CB, 0xA1F0, 0x25CE, 0xA1F2, 0x25CF, 0xA1F1, 0x25E2, 0xA88D, 0x25E3, 0xA88E, + 0x25E4, 0xA88F, 0x25E5, 0xA890, 0x2605, 0xA1EF, 0x2606, 0xA1EE, 0x2609, 0xA891, 0x2640, 0xA1E2, 0x2642, 0xA1E1, 0x3000, 0xA1A1, + 0x3001, 0xA1A2, 0x3002, 0xA1A3, 0x3003, 0xA1A8, 0x3005, 0xA1A9, 0x3006, 0xA965, 0x3007, 0xA996, 0x3008, 0xA1B4, 0x3009, 0xA1B5, + 0x300A, 0xA1B6, 0x300B, 0xA1B7, 0x300C, 0xA1B8, 0x300D, 0xA1B9, 0x300E, 0xA1BA, 0x300F, 0xA1BB, 0x3010, 0xA1BE, 0x3011, 0xA1BF, + 0x3012, 0xA893, 0x3013, 0xA1FE, 0x3014, 0xA1B2, 0x3015, 0xA1B3, 0x3016, 0xA1BC, 0x3017, 0xA1BD, 0x301D, 0xA894, 0x301E, 0xA895, + 0x3021, 0xA940, 0x3022, 0xA941, 0x3023, 0xA942, 0x3024, 0xA943, 0x3025, 0xA944, 0x3026, 0xA945, 0x3027, 0xA946, 0x3028, 0xA947, + 0x3029, 0xA948, 0x3041, 0xA4A1, 0x3042, 0xA4A2, 0x3043, 0xA4A3, 0x3044, 0xA4A4, 0x3045, 0xA4A5, 0x3046, 0xA4A6, 0x3047, 0xA4A7, + 0x3048, 0xA4A8, 0x3049, 0xA4A9, 0x304A, 0xA4AA, 0x304B, 0xA4AB, 0x304C, 0xA4AC, 0x304D, 0xA4AD, 0x304E, 0xA4AE, 0x304F, 0xA4AF, + 0x3050, 0xA4B0, 0x3051, 0xA4B1, 0x3052, 0xA4B2, 0x3053, 0xA4B3, 0x3054, 0xA4B4, 0x3055, 0xA4B5, 0x3056, 0xA4B6, 0x3057, 0xA4B7, + 0x3058, 0xA4B8, 0x3059, 0xA4B9, 0x305A, 0xA4BA, 0x305B, 0xA4BB, 0x305C, 0xA4BC, 0x305D, 0xA4BD, 0x305E, 0xA4BE, 0x305F, 0xA4BF, + 0x3060, 0xA4C0, 0x3061, 0xA4C1, 0x3062, 0xA4C2, 0x3063, 0xA4C3, 0x3064, 0xA4C4, 0x3065, 0xA4C5, 0x3066, 0xA4C6, 0x3067, 0xA4C7, + 0x3068, 0xA4C8, 0x3069, 0xA4C9, 0x306A, 0xA4CA, 0x306B, 0xA4CB, 0x306C, 0xA4CC, 0x306D, 0xA4CD, 0x306E, 0xA4CE, 0x306F, 0xA4CF, + 0x3070, 0xA4D0, 0x3071, 0xA4D1, 0x3072, 0xA4D2, 0x3073, 0xA4D3, 0x3074, 0xA4D4, 0x3075, 0xA4D5, 0x3076, 0xA4D6, 0x3077, 0xA4D7, + 0x3078, 0xA4D8, 0x3079, 0xA4D9, 0x307A, 0xA4DA, 0x307B, 0xA4DB, 0x307C, 0xA4DC, 0x307D, 0xA4DD, 0x307E, 0xA4DE, 0x307F, 0xA4DF, + 0x3080, 0xA4E0, 0x3081, 0xA4E1, 0x3082, 0xA4E2, 0x3083, 0xA4E3, 0x3084, 0xA4E4, 0x3085, 0xA4E5, 0x3086, 0xA4E6, 0x3087, 0xA4E7, + 0x3088, 0xA4E8, 0x3089, 0xA4E9, 0x308A, 0xA4EA, 0x308B, 0xA4EB, 0x308C, 0xA4EC, 0x308D, 0xA4ED, 0x308E, 0xA4EE, 0x308F, 0xA4EF, + 0x3090, 0xA4F0, 0x3091, 0xA4F1, 0x3092, 0xA4F2, 0x3093, 0xA4F3, 0x309B, 0xA961, 0x309C, 0xA962, 0x309D, 0xA966, 0x309E, 0xA967, + 0x30A1, 0xA5A1, 0x30A2, 0xA5A2, 0x30A3, 0xA5A3, 0x30A4, 0xA5A4, 0x30A5, 0xA5A5, 0x30A6, 0xA5A6, 0x30A7, 0xA5A7, 0x30A8, 0xA5A8, + 0x30A9, 0xA5A9, 0x30AA, 0xA5AA, 0x30AB, 0xA5AB, 0x30AC, 0xA5AC, 0x30AD, 0xA5AD, 0x30AE, 0xA5AE, 0x30AF, 0xA5AF, 0x30B0, 0xA5B0, + 0x30B1, 0xA5B1, 0x30B2, 0xA5B2, 0x30B3, 0xA5B3, 0x30B4, 0xA5B4, 0x30B5, 0xA5B5, 0x30B6, 0xA5B6, 0x30B7, 0xA5B7, 0x30B8, 0xA5B8, + 0x30B9, 0xA5B9, 0x30BA, 0xA5BA, 0x30BB, 0xA5BB, 0x30BC, 0xA5BC, 0x30BD, 0xA5BD, 0x30BE, 0xA5BE, 0x30BF, 0xA5BF, 0x30C0, 0xA5C0, + 0x30C1, 0xA5C1, 0x30C2, 0xA5C2, 0x30C3, 0xA5C3, 0x30C4, 0xA5C4, 0x30C5, 0xA5C5, 0x30C6, 0xA5C6, 0x30C7, 0xA5C7, 0x30C8, 0xA5C8, + 0x30C9, 0xA5C9, 0x30CA, 0xA5CA, 0x30CB, 0xA5CB, 0x30CC, 0xA5CC, 0x30CD, 0xA5CD, 0x30CE, 0xA5CE, 0x30CF, 0xA5CF, 0x30D0, 0xA5D0, + 0x30D1, 0xA5D1, 0x30D2, 0xA5D2, 0x30D3, 0xA5D3, 0x30D4, 0xA5D4, 0x30D5, 0xA5D5, 0x30D6, 0xA5D6, 0x30D7, 0xA5D7, 0x30D8, 0xA5D8, + 0x30D9, 0xA5D9, 0x30DA, 0xA5DA, 0x30DB, 0xA5DB, 0x30DC, 0xA5DC, 0x30DD, 0xA5DD, 0x30DE, 0xA5DE, 0x30DF, 0xA5DF, 0x30E0, 0xA5E0, + 0x30E1, 0xA5E1, 0x30E2, 0xA5E2, 0x30E3, 0xA5E3, 0x30E4, 0xA5E4, 0x30E5, 0xA5E5, 0x30E6, 0xA5E6, 0x30E7, 0xA5E7, 0x30E8, 0xA5E8, + 0x30E9, 0xA5E9, 0x30EA, 0xA5EA, 0x30EB, 0xA5EB, 0x30EC, 0xA5EC, 0x30ED, 0xA5ED, 0x30EE, 0xA5EE, 0x30EF, 0xA5EF, 0x30F0, 0xA5F0, + 0x30F1, 0xA5F1, 0x30F2, 0xA5F2, 0x30F3, 0xA5F3, 0x30F4, 0xA5F4, 0x30F5, 0xA5F5, 0x30F6, 0xA5F6, 0x30FC, 0xA960, 0x30FD, 0xA963, + 0x30FE, 0xA964, 0x3105, 0xA8C5, 0x3106, 0xA8C6, 0x3107, 0xA8C7, 0x3108, 0xA8C8, 0x3109, 0xA8C9, 0x310A, 0xA8CA, 0x310B, 0xA8CB, + 0x310C, 0xA8CC, 0x310D, 0xA8CD, 0x310E, 0xA8CE, 0x310F, 0xA8CF, 0x3110, 0xA8D0, 0x3111, 0xA8D1, 0x3112, 0xA8D2, 0x3113, 0xA8D3, + 0x3114, 0xA8D4, 0x3115, 0xA8D5, 0x3116, 0xA8D6, 0x3117, 0xA8D7, 0x3118, 0xA8D8, 0x3119, 0xA8D9, 0x311A, 0xA8DA, 0x311B, 0xA8DB, + 0x311C, 0xA8DC, 0x311D, 0xA8DD, 0x311E, 0xA8DE, 0x311F, 0xA8DF, 0x3120, 0xA8E0, 0x3121, 0xA8E1, 0x3122, 0xA8E2, 0x3123, 0xA8E3, + 0x3124, 0xA8E4, 0x3125, 0xA8E5, 0x3126, 0xA8E6, 0x3127, 0xA8E7, 0x3128, 0xA8E8, 0x3129, 0xA8E9, 0x3220, 0xA2E5, 0x3221, 0xA2E6, + 0x3222, 0xA2E7, 0x3223, 0xA2E8, 0x3224, 0xA2E9, 0x3225, 0xA2EA, 0x3226, 0xA2EB, 0x3227, 0xA2EC, 0x3228, 0xA2ED, 0x3229, 0xA2EE, + 0x3231, 0xA95A, 0x32A3, 0xA949, 0x338E, 0xA94A, 0x338F, 0xA94B, 0x339C, 0xA94C, 0x339D, 0xA94D, 0x339E, 0xA94E, 0x33A1, 0xA94F, + 0x33C4, 0xA950, 0x33CE, 0xA951, 0x33D1, 0xA952, 0x33D2, 0xA953, 0x33D5, 0xA954, 0x4E00, 0xD2BB, 0x4E01, 0xB6A1, 0x4E02, 0x8140, + 0x4E03, 0xC6DF, 0x4E04, 0x8141, 0x4E05, 0x8142, 0x4E06, 0x8143, 0x4E07, 0xCDF2, 0x4E08, 0xD5C9, 0x4E09, 0xC8FD, 0x4E0A, 0xC9CF, + 0x4E0B, 0xCFC2, 0x4E0C, 0xD8A2, 0x4E0D, 0xB2BB, 0x4E0E, 0xD3EB, 0x4E0F, 0x8144, 0x4E10, 0xD8A4, 0x4E11, 0xB3F3, 0x4E12, 0x8145, + 0x4E13, 0xD7A8, 0x4E14, 0xC7D2, 0x4E15, 0xD8A7, 0x4E16, 0xCAC0, 0x4E17, 0x8146, 0x4E18, 0xC7F0, 0x4E19, 0xB1FB, 0x4E1A, 0xD2B5, + 0x4E1B, 0xB4D4, 0x4E1C, 0xB6AB, 0x4E1D, 0xCBBF, 0x4E1E, 0xD8A9, 0x4E1F, 0x8147, 0x4E20, 0x8148, 0x4E21, 0x8149, 0x4E22, 0xB6AA, + 0x4E23, 0x814A, 0x4E24, 0xC1BD, 0x4E25, 0xD1CF, 0x4E26, 0x814B, 0x4E27, 0xC9A5, 0x4E28, 0xD8AD, 0x4E29, 0x814C, 0x4E2A, 0xB8F6, + 0x4E2B, 0xD1BE, 0x4E2C, 0xE3DC, 0x4E2D, 0xD6D0, 0x4E2E, 0x814D, 0x4E2F, 0x814E, 0x4E30, 0xB7E1, 0x4E31, 0x814F, 0x4E32, 0xB4AE, + 0x4E33, 0x8150, 0x4E34, 0xC1D9, 0x4E35, 0x8151, 0x4E36, 0xD8BC, 0x4E37, 0x8152, 0x4E38, 0xCDE8, 0x4E39, 0xB5A4, 0x4E3A, 0xCEAA, + 0x4E3B, 0xD6F7, 0x4E3C, 0x8153, 0x4E3D, 0xC0F6, 0x4E3E, 0xBED9, 0x4E3F, 0xD8AF, 0x4E40, 0x8154, 0x4E41, 0x8155, 0x4E42, 0x8156, + 0x4E43, 0xC4CB, 0x4E44, 0x8157, 0x4E45, 0xBEC3, 0x4E46, 0x8158, 0x4E47, 0xD8B1, 0x4E48, 0xC3B4, 0x4E49, 0xD2E5, 0x4E4A, 0x8159, + 0x4E4B, 0xD6AE, 0x4E4C, 0xCEDA, 0x4E4D, 0xD5A7, 0x4E4E, 0xBAF5, 0x4E4F, 0xB7A6, 0x4E50, 0xC0D6, 0x4E51, 0x815A, 0x4E52, 0xC6B9, + 0x4E53, 0xC5D2, 0x4E54, 0xC7C7, 0x4E55, 0x815B, 0x4E56, 0xB9D4, 0x4E57, 0x815C, 0x4E58, 0xB3CB, 0x4E59, 0xD2D2, 0x4E5A, 0x815D, + 0x4E5B, 0x815E, 0x4E5C, 0xD8BF, 0x4E5D, 0xBEC5, 0x4E5E, 0xC6F2, 0x4E5F, 0xD2B2, 0x4E60, 0xCFB0, 0x4E61, 0xCFE7, 0x4E62, 0x815F, + 0x4E63, 0x8160, 0x4E64, 0x8161, 0x4E65, 0x8162, 0x4E66, 0xCAE9, 0x4E67, 0x8163, 0x4E68, 0x8164, 0x4E69, 0xD8C0, 0x4E6A, 0x8165, + 0x4E6B, 0x8166, 0x4E6C, 0x8167, 0x4E6D, 0x8168, 0x4E6E, 0x8169, 0x4E6F, 0x816A, 0x4E70, 0xC2F2, 0x4E71, 0xC2D2, 0x4E72, 0x816B, + 0x4E73, 0xC8E9, 0x4E74, 0x816C, 0x4E75, 0x816D, 0x4E76, 0x816E, 0x4E77, 0x816F, 0x4E78, 0x8170, 0x4E79, 0x8171, 0x4E7A, 0x8172, + 0x4E7B, 0x8173, 0x4E7C, 0x8174, 0x4E7D, 0x8175, 0x4E7E, 0xC7AC, 0x4E7F, 0x8176, 0x4E80, 0x8177, 0x4E81, 0x8178, 0x4E82, 0x8179, + 0x4E83, 0x817A, 0x4E84, 0x817B, 0x4E85, 0x817C, 0x4E86, 0xC1CB, 0x4E87, 0x817D, 0x4E88, 0xD3E8, 0x4E89, 0xD5F9, 0x4E8A, 0x817E, + 0x4E8B, 0xCAC2, 0x4E8C, 0xB6FE, 0x4E8D, 0xD8A1, 0x4E8E, 0xD3DA, 0x4E8F, 0xBFF7, 0x4E90, 0x8180, 0x4E91, 0xD4C6, 0x4E92, 0xBBA5, + 0x4E93, 0xD8C1, 0x4E94, 0xCEE5, 0x4E95, 0xBEAE, 0x4E96, 0x8181, 0x4E97, 0x8182, 0x4E98, 0xD8A8, 0x4E99, 0x8183, 0x4E9A, 0xD1C7, + 0x4E9B, 0xD0A9, 0x4E9C, 0x8184, 0x4E9D, 0x8185, 0x4E9E, 0x8186, 0x4E9F, 0xD8BD, 0x4EA0, 0xD9EF, 0x4EA1, 0xCDF6, 0x4EA2, 0xBFBA, + 0x4EA3, 0x8187, 0x4EA4, 0xBDBB, 0x4EA5, 0xBAA5, 0x4EA6, 0xD2E0, 0x4EA7, 0xB2FA, 0x4EA8, 0xBAE0, 0x4EA9, 0xC4B6, 0x4EAA, 0x8188, + 0x4EAB, 0xCFED, 0x4EAC, 0xBEA9, 0x4EAD, 0xCDA4, 0x4EAE, 0xC1C1, 0x4EAF, 0x8189, 0x4EB0, 0x818A, 0x4EB1, 0x818B, 0x4EB2, 0xC7D7, + 0x4EB3, 0xD9F1, 0x4EB4, 0x818C, 0x4EB5, 0xD9F4, 0x4EB6, 0x818D, 0x4EB7, 0x818E, 0x4EB8, 0x818F, 0x4EB9, 0x8190, 0x4EBA, 0xC8CB, + 0x4EBB, 0xD8E9, 0x4EBC, 0x8191, 0x4EBD, 0x8192, 0x4EBE, 0x8193, 0x4EBF, 0xD2DA, 0x4EC0, 0xCAB2, 0x4EC1, 0xC8CA, 0x4EC2, 0xD8EC, + 0x4EC3, 0xD8EA, 0x4EC4, 0xD8C6, 0x4EC5, 0xBDF6, 0x4EC6, 0xC6CD, 0x4EC7, 0xB3F0, 0x4EC8, 0x8194, 0x4EC9, 0xD8EB, 0x4ECA, 0xBDF1, + 0x4ECB, 0xBDE9, 0x4ECC, 0x8195, 0x4ECD, 0xC8D4, 0x4ECE, 0xB4D3, 0x4ECF, 0x8196, 0x4ED0, 0x8197, 0x4ED1, 0xC2D8, 0x4ED2, 0x8198, + 0x4ED3, 0xB2D6, 0x4ED4, 0xD7D0, 0x4ED5, 0xCACB, 0x4ED6, 0xCBFB, 0x4ED7, 0xD5CC, 0x4ED8, 0xB8B6, 0x4ED9, 0xCFC9, 0x4EDA, 0x8199, + 0x4EDB, 0x819A, 0x4EDC, 0x819B, 0x4EDD, 0xD9DA, 0x4EDE, 0xD8F0, 0x4EDF, 0xC7AA, 0x4EE0, 0x819C, 0x4EE1, 0xD8EE, 0x4EE2, 0x819D, + 0x4EE3, 0xB4FA, 0x4EE4, 0xC1EE, 0x4EE5, 0xD2D4, 0x4EE6, 0x819E, 0x4EE7, 0x819F, 0x4EE8, 0xD8ED, 0x4EE9, 0x81A0, 0x4EEA, 0xD2C7, + 0x4EEB, 0xD8EF, 0x4EEC, 0xC3C7, 0x4EED, 0x81A1, 0x4EEE, 0x81A2, 0x4EEF, 0x81A3, 0x4EF0, 0xD1F6, 0x4EF1, 0x81A4, 0x4EF2, 0xD6D9, + 0x4EF3, 0xD8F2, 0x4EF4, 0x81A5, 0x4EF5, 0xD8F5, 0x4EF6, 0xBCFE, 0x4EF7, 0xBCDB, 0x4EF8, 0x81A6, 0x4EF9, 0x81A7, 0x4EFA, 0x81A8, + 0x4EFB, 0xC8CE, 0x4EFC, 0x81A9, 0x4EFD, 0xB7DD, 0x4EFE, 0x81AA, 0x4EFF, 0xB7C2, 0x4F00, 0x81AB, 0x4F01, 0xC6F3, 0x4F02, 0x81AC, + 0x4F03, 0x81AD, 0x4F04, 0x81AE, 0x4F05, 0x81AF, 0x4F06, 0x81B0, 0x4F07, 0x81B1, 0x4F08, 0x81B2, 0x4F09, 0xD8F8, 0x4F0A, 0xD2C1, + 0x4F0B, 0x81B3, 0x4F0C, 0x81B4, 0x4F0D, 0xCEE9, 0x4F0E, 0xBCBF, 0x4F0F, 0xB7FC, 0x4F10, 0xB7A5, 0x4F11, 0xD0DD, 0x4F12, 0x81B5, + 0x4F13, 0x81B6, 0x4F14, 0x81B7, 0x4F15, 0x81B8, 0x4F16, 0x81B9, 0x4F17, 0xD6DA, 0x4F18, 0xD3C5, 0x4F19, 0xBBEF, 0x4F1A, 0xBBE1, + 0x4F1B, 0xD8F1, 0x4F1C, 0x81BA, 0x4F1D, 0x81BB, 0x4F1E, 0xC9A1, 0x4F1F, 0xCEB0, 0x4F20, 0xB4AB, 0x4F21, 0x81BC, 0x4F22, 0xD8F3, + 0x4F23, 0x81BD, 0x4F24, 0xC9CB, 0x4F25, 0xD8F6, 0x4F26, 0xC2D7, 0x4F27, 0xD8F7, 0x4F28, 0x81BE, 0x4F29, 0x81BF, 0x4F2A, 0xCEB1, + 0x4F2B, 0xD8F9, 0x4F2C, 0x81C0, 0x4F2D, 0x81C1, 0x4F2E, 0x81C2, 0x4F2F, 0xB2AE, 0x4F30, 0xB9C0, 0x4F31, 0x81C3, 0x4F32, 0xD9A3, + 0x4F33, 0x81C4, 0x4F34, 0xB0E9, 0x4F35, 0x81C5, 0x4F36, 0xC1E6, 0x4F37, 0x81C6, 0x4F38, 0xC9EC, 0x4F39, 0x81C7, 0x4F3A, 0xCBC5, + 0x4F3B, 0x81C8, 0x4F3C, 0xCBC6, 0x4F3D, 0xD9A4, 0x4F3E, 0x81C9, 0x4F3F, 0x81CA, 0x4F40, 0x81CB, 0x4F41, 0x81CC, 0x4F42, 0x81CD, + 0x4F43, 0xB5E8, 0x4F44, 0x81CE, 0x4F45, 0x81CF, 0x4F46, 0xB5AB, 0x4F47, 0x81D0, 0x4F48, 0x81D1, 0x4F49, 0x81D2, 0x4F4A, 0x81D3, + 0x4F4B, 0x81D4, 0x4F4C, 0x81D5, 0x4F4D, 0xCEBB, 0x4F4E, 0xB5CD, 0x4F4F, 0xD7A1, 0x4F50, 0xD7F4, 0x4F51, 0xD3D3, 0x4F52, 0x81D6, + 0x4F53, 0xCCE5, 0x4F54, 0x81D7, 0x4F55, 0xBACE, 0x4F56, 0x81D8, 0x4F57, 0xD9A2, 0x4F58, 0xD9DC, 0x4F59, 0xD3E0, 0x4F5A, 0xD8FD, + 0x4F5B, 0xB7F0, 0x4F5C, 0xD7F7, 0x4F5D, 0xD8FE, 0x4F5E, 0xD8FA, 0x4F5F, 0xD9A1, 0x4F60, 0xC4E3, 0x4F61, 0x81D9, 0x4F62, 0x81DA, + 0x4F63, 0xD3B6, 0x4F64, 0xD8F4, 0x4F65, 0xD9DD, 0x4F66, 0x81DB, 0x4F67, 0xD8FB, 0x4F68, 0x81DC, 0x4F69, 0xC5E5, 0x4F6A, 0x81DD, + 0x4F6B, 0x81DE, 0x4F6C, 0xC0D0, 0x4F6D, 0x81DF, 0x4F6E, 0x81E0, 0x4F6F, 0xD1F0, 0x4F70, 0xB0DB, 0x4F71, 0x81E1, 0x4F72, 0x81E2, + 0x4F73, 0xBCD1, 0x4F74, 0xD9A6, 0x4F75, 0x81E3, 0x4F76, 0xD9A5, 0x4F77, 0x81E4, 0x4F78, 0x81E5, 0x4F79, 0x81E6, 0x4F7A, 0x81E7, + 0x4F7B, 0xD9AC, 0x4F7C, 0xD9AE, 0x4F7D, 0x81E8, 0x4F7E, 0xD9AB, 0x4F7F, 0xCAB9, 0x4F80, 0x81E9, 0x4F81, 0x81EA, 0x4F82, 0x81EB, + 0x4F83, 0xD9A9, 0x4F84, 0xD6B6, 0x4F85, 0x81EC, 0x4F86, 0x81ED, 0x4F87, 0x81EE, 0x4F88, 0xB3DE, 0x4F89, 0xD9A8, 0x4F8A, 0x81EF, + 0x4F8B, 0xC0FD, 0x4F8C, 0x81F0, 0x4F8D, 0xCACC, 0x4F8E, 0x81F1, 0x4F8F, 0xD9AA, 0x4F90, 0x81F2, 0x4F91, 0xD9A7, 0x4F92, 0x81F3, + 0x4F93, 0x81F4, 0x4F94, 0xD9B0, 0x4F95, 0x81F5, 0x4F96, 0x81F6, 0x4F97, 0xB6B1, 0x4F98, 0x81F7, 0x4F99, 0x81F8, 0x4F9A, 0x81F9, + 0x4F9B, 0xB9A9, 0x4F9C, 0x81FA, 0x4F9D, 0xD2C0, 0x4F9E, 0x81FB, 0x4F9F, 0x81FC, 0x4FA0, 0xCFC0, 0x4FA1, 0x81FD, 0x4FA2, 0x81FE, + 0x4FA3, 0xC2C2, 0x4FA4, 0x8240, 0x4FA5, 0xBDC4, 0x4FA6, 0xD5EC, 0x4FA7, 0xB2E0, 0x4FA8, 0xC7C8, 0x4FA9, 0xBFEB, 0x4FAA, 0xD9AD, + 0x4FAB, 0x8241, 0x4FAC, 0xD9AF, 0x4FAD, 0x8242, 0x4FAE, 0xCEEA, 0x4FAF, 0xBAEE, 0x4FB0, 0x8243, 0x4FB1, 0x8244, 0x4FB2, 0x8245, + 0x4FB3, 0x8246, 0x4FB4, 0x8247, 0x4FB5, 0xC7D6, 0x4FB6, 0x8248, 0x4FB7, 0x8249, 0x4FB8, 0x824A, 0x4FB9, 0x824B, 0x4FBA, 0x824C, + 0x4FBB, 0x824D, 0x4FBC, 0x824E, 0x4FBD, 0x824F, 0x4FBE, 0x8250, 0x4FBF, 0xB1E3, 0x4FC0, 0x8251, 0x4FC1, 0x8252, 0x4FC2, 0x8253, + 0x4FC3, 0xB4D9, 0x4FC4, 0xB6ED, 0x4FC5, 0xD9B4, 0x4FC6, 0x8254, 0x4FC7, 0x8255, 0x4FC8, 0x8256, 0x4FC9, 0x8257, 0x4FCA, 0xBFA1, + 0x4FCB, 0x8258, 0x4FCC, 0x8259, 0x4FCD, 0x825A, 0x4FCE, 0xD9DE, 0x4FCF, 0xC7CE, 0x4FD0, 0xC0FE, 0x4FD1, 0xD9B8, 0x4FD2, 0x825B, + 0x4FD3, 0x825C, 0x4FD4, 0x825D, 0x4FD5, 0x825E, 0x4FD6, 0x825F, 0x4FD7, 0xCBD7, 0x4FD8, 0xB7FD, 0x4FD9, 0x8260, 0x4FDA, 0xD9B5, + 0x4FDB, 0x8261, 0x4FDC, 0xD9B7, 0x4FDD, 0xB1A3, 0x4FDE, 0xD3E1, 0x4FDF, 0xD9B9, 0x4FE0, 0x8262, 0x4FE1, 0xD0C5, 0x4FE2, 0x8263, + 0x4FE3, 0xD9B6, 0x4FE4, 0x8264, 0x4FE5, 0x8265, 0x4FE6, 0xD9B1, 0x4FE7, 0x8266, 0x4FE8, 0xD9B2, 0x4FE9, 0xC1A9, 0x4FEA, 0xD9B3, + 0x4FEB, 0x8267, 0x4FEC, 0x8268, 0x4FED, 0xBCF3, 0x4FEE, 0xD0DE, 0x4FEF, 0xB8A9, 0x4FF0, 0x8269, 0x4FF1, 0xBEE3, 0x4FF2, 0x826A, + 0x4FF3, 0xD9BD, 0x4FF4, 0x826B, 0x4FF5, 0x826C, 0x4FF6, 0x826D, 0x4FF7, 0x826E, 0x4FF8, 0xD9BA, 0x4FF9, 0x826F, 0x4FFA, 0xB0B3, + 0x4FFB, 0x8270, 0x4FFC, 0x8271, 0x4FFD, 0x8272, 0x4FFE, 0xD9C2, 0x4FFF, 0x8273, 0x5000, 0x8274, 0x5001, 0x8275, 0x5002, 0x8276, + 0x5003, 0x8277, 0x5004, 0x8278, 0x5005, 0x8279, 0x5006, 0x827A, 0x5007, 0x827B, 0x5008, 0x827C, 0x5009, 0x827D, 0x500A, 0x827E, + 0x500B, 0x8280, 0x500C, 0xD9C4, 0x500D, 0xB1B6, 0x500E, 0x8281, 0x500F, 0xD9BF, 0x5010, 0x8282, 0x5011, 0x8283, 0x5012, 0xB5B9, + 0x5013, 0x8284, 0x5014, 0xBEF3, 0x5015, 0x8285, 0x5016, 0x8286, 0x5017, 0x8287, 0x5018, 0xCCC8, 0x5019, 0xBAF2, 0x501A, 0xD2D0, + 0x501B, 0x8288, 0x501C, 0xD9C3, 0x501D, 0x8289, 0x501E, 0x828A, 0x501F, 0xBDE8, 0x5020, 0x828B, 0x5021, 0xB3AB, 0x5022, 0x828C, + 0x5023, 0x828D, 0x5024, 0x828E, 0x5025, 0xD9C5, 0x5026, 0xBEEB, 0x5027, 0x828F, 0x5028, 0xD9C6, 0x5029, 0xD9BB, 0x502A, 0xC4DF, + 0x502B, 0x8290, 0x502C, 0xD9BE, 0x502D, 0xD9C1, 0x502E, 0xD9C0, 0x502F, 0x8291, 0x5030, 0x8292, 0x5031, 0x8293, 0x5032, 0x8294, + 0x5033, 0x8295, 0x5034, 0x8296, 0x5035, 0x8297, 0x5036, 0x8298, 0x5037, 0x8299, 0x5038, 0x829A, 0x5039, 0x829B, 0x503A, 0xD5AE, + 0x503B, 0x829C, 0x503C, 0xD6B5, 0x503D, 0x829D, 0x503E, 0xC7E3, 0x503F, 0x829E, 0x5040, 0x829F, 0x5041, 0x82A0, 0x5042, 0x82A1, + 0x5043, 0xD9C8, 0x5044, 0x82A2, 0x5045, 0x82A3, 0x5046, 0x82A4, 0x5047, 0xBCD9, 0x5048, 0xD9CA, 0x5049, 0x82A5, 0x504A, 0x82A6, + 0x504B, 0x82A7, 0x504C, 0xD9BC, 0x504D, 0x82A8, 0x504E, 0xD9CB, 0x504F, 0xC6AB, 0x5050, 0x82A9, 0x5051, 0x82AA, 0x5052, 0x82AB, + 0x5053, 0x82AC, 0x5054, 0x82AD, 0x5055, 0xD9C9, 0x5056, 0x82AE, 0x5057, 0x82AF, 0x5058, 0x82B0, 0x5059, 0x82B1, 0x505A, 0xD7F6, + 0x505B, 0x82B2, 0x505C, 0xCDA3, 0x505D, 0x82B3, 0x505E, 0x82B4, 0x505F, 0x82B5, 0x5060, 0x82B6, 0x5061, 0x82B7, 0x5062, 0x82B8, + 0x5063, 0x82B9, 0x5064, 0x82BA, 0x5065, 0xBDA1, 0x5066, 0x82BB, 0x5067, 0x82BC, 0x5068, 0x82BD, 0x5069, 0x82BE, 0x506A, 0x82BF, + 0x506B, 0x82C0, 0x506C, 0xD9CC, 0x506D, 0x82C1, 0x506E, 0x82C2, 0x506F, 0x82C3, 0x5070, 0x82C4, 0x5071, 0x82C5, 0x5072, 0x82C6, + 0x5073, 0x82C7, 0x5074, 0x82C8, 0x5075, 0x82C9, 0x5076, 0xC5BC, 0x5077, 0xCDB5, 0x5078, 0x82CA, 0x5079, 0x82CB, 0x507A, 0x82CC, + 0x507B, 0xD9CD, 0x507C, 0x82CD, 0x507D, 0x82CE, 0x507E, 0xD9C7, 0x507F, 0xB3A5, 0x5080, 0xBFFE, 0x5081, 0x82CF, 0x5082, 0x82D0, + 0x5083, 0x82D1, 0x5084, 0x82D2, 0x5085, 0xB8B5, 0x5086, 0x82D3, 0x5087, 0x82D4, 0x5088, 0xC0FC, 0x5089, 0x82D5, 0x508A, 0x82D6, + 0x508B, 0x82D7, 0x508C, 0x82D8, 0x508D, 0xB0F8, 0x508E, 0x82D9, 0x508F, 0x82DA, 0x5090, 0x82DB, 0x5091, 0x82DC, 0x5092, 0x82DD, + 0x5093, 0x82DE, 0x5094, 0x82DF, 0x5095, 0x82E0, 0x5096, 0x82E1, 0x5097, 0x82E2, 0x5098, 0x82E3, 0x5099, 0x82E4, 0x509A, 0x82E5, + 0x509B, 0x82E6, 0x509C, 0x82E7, 0x509D, 0x82E8, 0x509E, 0x82E9, 0x509F, 0x82EA, 0x50A0, 0x82EB, 0x50A1, 0x82EC, 0x50A2, 0x82ED, + 0x50A3, 0xB4F6, 0x50A4, 0x82EE, 0x50A5, 0xD9CE, 0x50A6, 0x82EF, 0x50A7, 0xD9CF, 0x50A8, 0xB4A2, 0x50A9, 0xD9D0, 0x50AA, 0x82F0, + 0x50AB, 0x82F1, 0x50AC, 0xB4DF, 0x50AD, 0x82F2, 0x50AE, 0x82F3, 0x50AF, 0x82F4, 0x50B0, 0x82F5, 0x50B1, 0x82F6, 0x50B2, 0xB0C1, + 0x50B3, 0x82F7, 0x50B4, 0x82F8, 0x50B5, 0x82F9, 0x50B6, 0x82FA, 0x50B7, 0x82FB, 0x50B8, 0x82FC, 0x50B9, 0x82FD, 0x50BA, 0xD9D1, + 0x50BB, 0xC9B5, 0x50BC, 0x82FE, 0x50BD, 0x8340, 0x50BE, 0x8341, 0x50BF, 0x8342, 0x50C0, 0x8343, 0x50C1, 0x8344, 0x50C2, 0x8345, + 0x50C3, 0x8346, 0x50C4, 0x8347, 0x50C5, 0x8348, 0x50C6, 0x8349, 0x50C7, 0x834A, 0x50C8, 0x834B, 0x50C9, 0x834C, 0x50CA, 0x834D, + 0x50CB, 0x834E, 0x50CC, 0x834F, 0x50CD, 0x8350, 0x50CE, 0x8351, 0x50CF, 0xCFF1, 0x50D0, 0x8352, 0x50D1, 0x8353, 0x50D2, 0x8354, + 0x50D3, 0x8355, 0x50D4, 0x8356, 0x50D5, 0x8357, 0x50D6, 0xD9D2, 0x50D7, 0x8358, 0x50D8, 0x8359, 0x50D9, 0x835A, 0x50DA, 0xC1C5, + 0x50DB, 0x835B, 0x50DC, 0x835C, 0x50DD, 0x835D, 0x50DE, 0x835E, 0x50DF, 0x835F, 0x50E0, 0x8360, 0x50E1, 0x8361, 0x50E2, 0x8362, + 0x50E3, 0x8363, 0x50E4, 0x8364, 0x50E5, 0x8365, 0x50E6, 0xD9D6, 0x50E7, 0xC9AE, 0x50E8, 0x8366, 0x50E9, 0x8367, 0x50EA, 0x8368, + 0x50EB, 0x8369, 0x50EC, 0xD9D5, 0x50ED, 0xD9D4, 0x50EE, 0xD9D7, 0x50EF, 0x836A, 0x50F0, 0x836B, 0x50F1, 0x836C, 0x50F2, 0x836D, + 0x50F3, 0xCBDB, 0x50F4, 0x836E, 0x50F5, 0xBDA9, 0x50F6, 0x836F, 0x50F7, 0x8370, 0x50F8, 0x8371, 0x50F9, 0x8372, 0x50FA, 0x8373, + 0x50FB, 0xC6A7, 0x50FC, 0x8374, 0x50FD, 0x8375, 0x50FE, 0x8376, 0x50FF, 0x8377, 0x5100, 0x8378, 0x5101, 0x8379, 0x5102, 0x837A, + 0x5103, 0x837B, 0x5104, 0x837C, 0x5105, 0x837D, 0x5106, 0xD9D3, 0x5107, 0xD9D8, 0x5108, 0x837E, 0x5109, 0x8380, 0x510A, 0x8381, + 0x510B, 0xD9D9, 0x510C, 0x8382, 0x510D, 0x8383, 0x510E, 0x8384, 0x510F, 0x8385, 0x5110, 0x8386, 0x5111, 0x8387, 0x5112, 0xC8E5, + 0x5113, 0x8388, 0x5114, 0x8389, 0x5115, 0x838A, 0x5116, 0x838B, 0x5117, 0x838C, 0x5118, 0x838D, 0x5119, 0x838E, 0x511A, 0x838F, + 0x511B, 0x8390, 0x511C, 0x8391, 0x511D, 0x8392, 0x511E, 0x8393, 0x511F, 0x8394, 0x5120, 0x8395, 0x5121, 0xC0DC, 0x5122, 0x8396, + 0x5123, 0x8397, 0x5124, 0x8398, 0x5125, 0x8399, 0x5126, 0x839A, 0x5127, 0x839B, 0x5128, 0x839C, 0x5129, 0x839D, 0x512A, 0x839E, + 0x512B, 0x839F, 0x512C, 0x83A0, 0x512D, 0x83A1, 0x512E, 0x83A2, 0x512F, 0x83A3, 0x5130, 0x83A4, 0x5131, 0x83A5, 0x5132, 0x83A6, + 0x5133, 0x83A7, 0x5134, 0x83A8, 0x5135, 0x83A9, 0x5136, 0x83AA, 0x5137, 0x83AB, 0x5138, 0x83AC, 0x5139, 0x83AD, 0x513A, 0x83AE, + 0x513B, 0x83AF, 0x513C, 0x83B0, 0x513D, 0x83B1, 0x513E, 0x83B2, 0x513F, 0xB6F9, 0x5140, 0xD8A3, 0x5141, 0xD4CA, 0x5142, 0x83B3, + 0x5143, 0xD4AA, 0x5144, 0xD0D6, 0x5145, 0xB3E4, 0x5146, 0xD5D7, 0x5147, 0x83B4, 0x5148, 0xCFC8, 0x5149, 0xB9E2, 0x514A, 0x83B5, + 0x514B, 0xBFCB, 0x514C, 0x83B6, 0x514D, 0xC3E2, 0x514E, 0x83B7, 0x514F, 0x83B8, 0x5150, 0x83B9, 0x5151, 0xB6D2, 0x5152, 0x83BA, + 0x5153, 0x83BB, 0x5154, 0xCDC3, 0x5155, 0xD9EE, 0x5156, 0xD9F0, 0x5157, 0x83BC, 0x5158, 0x83BD, 0x5159, 0x83BE, 0x515A, 0xB5B3, + 0x515B, 0x83BF, 0x515C, 0xB6B5, 0x515D, 0x83C0, 0x515E, 0x83C1, 0x515F, 0x83C2, 0x5160, 0x83C3, 0x5161, 0x83C4, 0x5162, 0xBEA4, + 0x5163, 0x83C5, 0x5164, 0x83C6, 0x5165, 0xC8EB, 0x5166, 0x83C7, 0x5167, 0x83C8, 0x5168, 0xC8AB, 0x5169, 0x83C9, 0x516A, 0x83CA, + 0x516B, 0xB0CB, 0x516C, 0xB9AB, 0x516D, 0xC1F9, 0x516E, 0xD9E2, 0x516F, 0x83CB, 0x5170, 0xC0BC, 0x5171, 0xB9B2, 0x5172, 0x83CC, + 0x5173, 0xB9D8, 0x5174, 0xD0CB, 0x5175, 0xB1F8, 0x5176, 0xC6E4, 0x5177, 0xBEDF, 0x5178, 0xB5E4, 0x5179, 0xD7C8, 0x517A, 0x83CD, + 0x517B, 0xD1F8, 0x517C, 0xBCE6, 0x517D, 0xCADE, 0x517E, 0x83CE, 0x517F, 0x83CF, 0x5180, 0xBCBD, 0x5181, 0xD9E6, 0x5182, 0xD8E7, + 0x5183, 0x83D0, 0x5184, 0x83D1, 0x5185, 0xC4DA, 0x5186, 0x83D2, 0x5187, 0x83D3, 0x5188, 0xB8D4, 0x5189, 0xC8BD, 0x518A, 0x83D4, + 0x518B, 0x83D5, 0x518C, 0xB2E1, 0x518D, 0xD4D9, 0x518E, 0x83D6, 0x518F, 0x83D7, 0x5190, 0x83D8, 0x5191, 0x83D9, 0x5192, 0xC3B0, + 0x5193, 0x83DA, 0x5194, 0x83DB, 0x5195, 0xC3E1, 0x5196, 0xDAA2, 0x5197, 0xC8DF, 0x5198, 0x83DC, 0x5199, 0xD0B4, 0x519A, 0x83DD, + 0x519B, 0xBEFC, 0x519C, 0xC5A9, 0x519D, 0x83DE, 0x519E, 0x83DF, 0x519F, 0x83E0, 0x51A0, 0xB9DA, 0x51A1, 0x83E1, 0x51A2, 0xDAA3, + 0x51A3, 0x83E2, 0x51A4, 0xD4A9, 0x51A5, 0xDAA4, 0x51A6, 0x83E3, 0x51A7, 0x83E4, 0x51A8, 0x83E5, 0x51A9, 0x83E6, 0x51AA, 0x83E7, + 0x51AB, 0xD9FB, 0x51AC, 0xB6AC, 0x51AD, 0x83E8, 0x51AE, 0x83E9, 0x51AF, 0xB7EB, 0x51B0, 0xB1F9, 0x51B1, 0xD9FC, 0x51B2, 0xB3E5, + 0x51B3, 0xBEF6, 0x51B4, 0x83EA, 0x51B5, 0xBFF6, 0x51B6, 0xD2B1, 0x51B7, 0xC0E4, 0x51B8, 0x83EB, 0x51B9, 0x83EC, 0x51BA, 0x83ED, + 0x51BB, 0xB6B3, 0x51BC, 0xD9FE, 0x51BD, 0xD9FD, 0x51BE, 0x83EE, 0x51BF, 0x83EF, 0x51C0, 0xBEBB, 0x51C1, 0x83F0, 0x51C2, 0x83F1, + 0x51C3, 0x83F2, 0x51C4, 0xC6E0, 0x51C5, 0x83F3, 0x51C6, 0xD7BC, 0x51C7, 0xDAA1, 0x51C8, 0x83F4, 0x51C9, 0xC1B9, 0x51CA, 0x83F5, + 0x51CB, 0xB5F2, 0x51CC, 0xC1E8, 0x51CD, 0x83F6, 0x51CE, 0x83F7, 0x51CF, 0xBCF5, 0x51D0, 0x83F8, 0x51D1, 0xB4D5, 0x51D2, 0x83F9, + 0x51D3, 0x83FA, 0x51D4, 0x83FB, 0x51D5, 0x83FC, 0x51D6, 0x83FD, 0x51D7, 0x83FE, 0x51D8, 0x8440, 0x51D9, 0x8441, 0x51DA, 0x8442, + 0x51DB, 0xC1DD, 0x51DC, 0x8443, 0x51DD, 0xC4FD, 0x51DE, 0x8444, 0x51DF, 0x8445, 0x51E0, 0xBCB8, 0x51E1, 0xB7B2, 0x51E2, 0x8446, + 0x51E3, 0x8447, 0x51E4, 0xB7EF, 0x51E5, 0x8448, 0x51E6, 0x8449, 0x51E7, 0x844A, 0x51E8, 0x844B, 0x51E9, 0x844C, 0x51EA, 0x844D, + 0x51EB, 0xD9EC, 0x51EC, 0x844E, 0x51ED, 0xC6BE, 0x51EE, 0x844F, 0x51EF, 0xBFAD, 0x51F0, 0xBBCB, 0x51F1, 0x8450, 0x51F2, 0x8451, + 0x51F3, 0xB5CA, 0x51F4, 0x8452, 0x51F5, 0xDBC9, 0x51F6, 0xD0D7, 0x51F7, 0x8453, 0x51F8, 0xCDB9, 0x51F9, 0xB0BC, 0x51FA, 0xB3F6, + 0x51FB, 0xBBF7, 0x51FC, 0xDBCA, 0x51FD, 0xBAAF, 0x51FE, 0x8454, 0x51FF, 0xD4E4, 0x5200, 0xB5B6, 0x5201, 0xB5F3, 0x5202, 0xD8D6, + 0x5203, 0xC8D0, 0x5204, 0x8455, 0x5205, 0x8456, 0x5206, 0xB7D6, 0x5207, 0xC7D0, 0x5208, 0xD8D7, 0x5209, 0x8457, 0x520A, 0xBFAF, + 0x520B, 0x8458, 0x520C, 0x8459, 0x520D, 0xDBBB, 0x520E, 0xD8D8, 0x520F, 0x845A, 0x5210, 0x845B, 0x5211, 0xD0CC, 0x5212, 0xBBAE, + 0x5213, 0x845C, 0x5214, 0x845D, 0x5215, 0x845E, 0x5216, 0xEBBE, 0x5217, 0xC1D0, 0x5218, 0xC1F5, 0x5219, 0xD4F2, 0x521A, 0xB8D5, + 0x521B, 0xB4B4, 0x521C, 0x845F, 0x521D, 0xB3F5, 0x521E, 0x8460, 0x521F, 0x8461, 0x5220, 0xC9BE, 0x5221, 0x8462, 0x5222, 0x8463, + 0x5223, 0x8464, 0x5224, 0xC5D0, 0x5225, 0x8465, 0x5226, 0x8466, 0x5227, 0x8467, 0x5228, 0xC5D9, 0x5229, 0xC0FB, 0x522A, 0x8468, + 0x522B, 0xB1F0, 0x522C, 0x8469, 0x522D, 0xD8D9, 0x522E, 0xB9CE, 0x522F, 0x846A, 0x5230, 0xB5BD, 0x5231, 0x846B, 0x5232, 0x846C, + 0x5233, 0xD8DA, 0x5234, 0x846D, 0x5235, 0x846E, 0x5236, 0xD6C6, 0x5237, 0xCBA2, 0x5238, 0xC8AF, 0x5239, 0xC9B2, 0x523A, 0xB4CC, + 0x523B, 0xBFCC, 0x523C, 0x846F, 0x523D, 0xB9F4, 0x523E, 0x8470, 0x523F, 0xD8DB, 0x5240, 0xD8DC, 0x5241, 0xB6E7, 0x5242, 0xBCC1, + 0x5243, 0xCCEA, 0x5244, 0x8471, 0x5245, 0x8472, 0x5246, 0x8473, 0x5247, 0x8474, 0x5248, 0x8475, 0x5249, 0x8476, 0x524A, 0xCFF7, + 0x524B, 0x8477, 0x524C, 0xD8DD, 0x524D, 0xC7B0, 0x524E, 0x8478, 0x524F, 0x8479, 0x5250, 0xB9D0, 0x5251, 0xBDA3, 0x5252, 0x847A, + 0x5253, 0x847B, 0x5254, 0xCCDE, 0x5255, 0x847C, 0x5256, 0xC6CA, 0x5257, 0x847D, 0x5258, 0x847E, 0x5259, 0x8480, 0x525A, 0x8481, + 0x525B, 0x8482, 0x525C, 0xD8E0, 0x525D, 0x8483, 0x525E, 0xD8DE, 0x525F, 0x8484, 0x5260, 0x8485, 0x5261, 0xD8DF, 0x5262, 0x8486, + 0x5263, 0x8487, 0x5264, 0x8488, 0x5265, 0xB0FE, 0x5266, 0x8489, 0x5267, 0xBEE7, 0x5268, 0x848A, 0x5269, 0xCAA3, 0x526A, 0xBCF4, + 0x526B, 0x848B, 0x526C, 0x848C, 0x526D, 0x848D, 0x526E, 0x848E, 0x526F, 0xB8B1, 0x5270, 0x848F, 0x5271, 0x8490, 0x5272, 0xB8EE, + 0x5273, 0x8491, 0x5274, 0x8492, 0x5275, 0x8493, 0x5276, 0x8494, 0x5277, 0x8495, 0x5278, 0x8496, 0x5279, 0x8497, 0x527A, 0x8498, + 0x527B, 0x8499, 0x527C, 0x849A, 0x527D, 0xD8E2, 0x527E, 0x849B, 0x527F, 0xBDCB, 0x5280, 0x849C, 0x5281, 0xD8E4, 0x5282, 0xD8E3, + 0x5283, 0x849D, 0x5284, 0x849E, 0x5285, 0x849F, 0x5286, 0x84A0, 0x5287, 0x84A1, 0x5288, 0xC5FC, 0x5289, 0x84A2, 0x528A, 0x84A3, + 0x528B, 0x84A4, 0x528C, 0x84A5, 0x528D, 0x84A6, 0x528E, 0x84A7, 0x528F, 0x84A8, 0x5290, 0xD8E5, 0x5291, 0x84A9, 0x5292, 0x84AA, + 0x5293, 0xD8E6, 0x5294, 0x84AB, 0x5295, 0x84AC, 0x5296, 0x84AD, 0x5297, 0x84AE, 0x5298, 0x84AF, 0x5299, 0x84B0, 0x529A, 0x84B1, + 0x529B, 0xC1A6, 0x529C, 0x84B2, 0x529D, 0xC8B0, 0x529E, 0xB0EC, 0x529F, 0xB9A6, 0x52A0, 0xBCD3, 0x52A1, 0xCEF1, 0x52A2, 0xDBBD, + 0x52A3, 0xC1D3, 0x52A4, 0x84B3, 0x52A5, 0x84B4, 0x52A6, 0x84B5, 0x52A7, 0x84B6, 0x52A8, 0xB6AF, 0x52A9, 0xD6FA, 0x52AA, 0xC5AC, + 0x52AB, 0xBDD9, 0x52AC, 0xDBBE, 0x52AD, 0xDBBF, 0x52AE, 0x84B7, 0x52AF, 0x84B8, 0x52B0, 0x84B9, 0x52B1, 0xC0F8, 0x52B2, 0xBEA2, + 0x52B3, 0xC0CD, 0x52B4, 0x84BA, 0x52B5, 0x84BB, 0x52B6, 0x84BC, 0x52B7, 0x84BD, 0x52B8, 0x84BE, 0x52B9, 0x84BF, 0x52BA, 0x84C0, + 0x52BB, 0x84C1, 0x52BC, 0x84C2, 0x52BD, 0x84C3, 0x52BE, 0xDBC0, 0x52BF, 0xCAC6, 0x52C0, 0x84C4, 0x52C1, 0x84C5, 0x52C2, 0x84C6, + 0x52C3, 0xB2AA, 0x52C4, 0x84C7, 0x52C5, 0x84C8, 0x52C6, 0x84C9, 0x52C7, 0xD3C2, 0x52C8, 0x84CA, 0x52C9, 0xC3E3, 0x52CA, 0x84CB, + 0x52CB, 0xD1AB, 0x52CC, 0x84CC, 0x52CD, 0x84CD, 0x52CE, 0x84CE, 0x52CF, 0x84CF, 0x52D0, 0xDBC2, 0x52D1, 0x84D0, 0x52D2, 0xC0D5, + 0x52D3, 0x84D1, 0x52D4, 0x84D2, 0x52D5, 0x84D3, 0x52D6, 0xDBC3, 0x52D7, 0x84D4, 0x52D8, 0xBFB1, 0x52D9, 0x84D5, 0x52DA, 0x84D6, + 0x52DB, 0x84D7, 0x52DC, 0x84D8, 0x52DD, 0x84D9, 0x52DE, 0x84DA, 0x52DF, 0xC4BC, 0x52E0, 0x84DB, 0x52E1, 0x84DC, 0x52E2, 0x84DD, + 0x52E3, 0x84DE, 0x52E4, 0xC7DA, 0x52E5, 0x84DF, 0x52E6, 0x84E0, 0x52E7, 0x84E1, 0x52E8, 0x84E2, 0x52E9, 0x84E3, 0x52EA, 0x84E4, + 0x52EB, 0x84E5, 0x52EC, 0x84E6, 0x52ED, 0x84E7, 0x52EE, 0x84E8, 0x52EF, 0x84E9, 0x52F0, 0xDBC4, 0x52F1, 0x84EA, 0x52F2, 0x84EB, + 0x52F3, 0x84EC, 0x52F4, 0x84ED, 0x52F5, 0x84EE, 0x52F6, 0x84EF, 0x52F7, 0x84F0, 0x52F8, 0x84F1, 0x52F9, 0xD9E8, 0x52FA, 0xC9D7, + 0x52FB, 0x84F2, 0x52FC, 0x84F3, 0x52FD, 0x84F4, 0x52FE, 0xB9B4, 0x52FF, 0xCEF0, 0x5300, 0xD4C8, 0x5301, 0x84F5, 0x5302, 0x84F6, + 0x5303, 0x84F7, 0x5304, 0x84F8, 0x5305, 0xB0FC, 0x5306, 0xB4D2, 0x5307, 0x84F9, 0x5308, 0xD0D9, 0x5309, 0x84FA, 0x530A, 0x84FB, + 0x530B, 0x84FC, 0x530C, 0x84FD, 0x530D, 0xD9E9, 0x530E, 0x84FE, 0x530F, 0xDECB, 0x5310, 0xD9EB, 0x5311, 0x8540, 0x5312, 0x8541, + 0x5313, 0x8542, 0x5314, 0x8543, 0x5315, 0xD8B0, 0x5316, 0xBBAF, 0x5317, 0xB1B1, 0x5318, 0x8544, 0x5319, 0xB3D7, 0x531A, 0xD8CE, + 0x531B, 0x8545, 0x531C, 0x8546, 0x531D, 0xD4D1, 0x531E, 0x8547, 0x531F, 0x8548, 0x5320, 0xBDB3, 0x5321, 0xBFEF, 0x5322, 0x8549, + 0x5323, 0xCFBB, 0x5324, 0x854A, 0x5325, 0x854B, 0x5326, 0xD8D0, 0x5327, 0x854C, 0x5328, 0x854D, 0x5329, 0x854E, 0x532A, 0xB7CB, + 0x532B, 0x854F, 0x532C, 0x8550, 0x532D, 0x8551, 0x532E, 0xD8D1, 0x532F, 0x8552, 0x5330, 0x8553, 0x5331, 0x8554, 0x5332, 0x8555, + 0x5333, 0x8556, 0x5334, 0x8557, 0x5335, 0x8558, 0x5336, 0x8559, 0x5337, 0x855A, 0x5338, 0x855B, 0x5339, 0xC6A5, 0x533A, 0xC7F8, + 0x533B, 0xD2BD, 0x533C, 0x855C, 0x533D, 0x855D, 0x533E, 0xD8D2, 0x533F, 0xC4E4, 0x5340, 0x855E, 0x5341, 0xCAAE, 0x5342, 0x855F, + 0x5343, 0xC7A7, 0x5344, 0x8560, 0x5345, 0xD8A6, 0x5346, 0x8561, 0x5347, 0xC9FD, 0x5348, 0xCEE7, 0x5349, 0xBBDC, 0x534A, 0xB0EB, + 0x534B, 0x8562, 0x534C, 0x8563, 0x534D, 0x8564, 0x534E, 0xBBAA, 0x534F, 0xD0AD, 0x5350, 0x8565, 0x5351, 0xB1B0, 0x5352, 0xD7E4, + 0x5353, 0xD7BF, 0x5354, 0x8566, 0x5355, 0xB5A5, 0x5356, 0xC2F4, 0x5357, 0xC4CF, 0x5358, 0x8567, 0x5359, 0x8568, 0x535A, 0xB2A9, + 0x535B, 0x8569, 0x535C, 0xB2B7, 0x535D, 0x856A, 0x535E, 0xB1E5, 0x535F, 0xDFB2, 0x5360, 0xD5BC, 0x5361, 0xBFA8, 0x5362, 0xC2AC, + 0x5363, 0xD8D5, 0x5364, 0xC2B1, 0x5365, 0x856B, 0x5366, 0xD8D4, 0x5367, 0xCED4, 0x5368, 0x856C, 0x5369, 0xDAE0, 0x536A, 0x856D, + 0x536B, 0xCEC0, 0x536C, 0x856E, 0x536D, 0x856F, 0x536E, 0xD8B4, 0x536F, 0xC3AE, 0x5370, 0xD3A1, 0x5371, 0xCEA3, 0x5372, 0x8570, + 0x5373, 0xBCB4, 0x5374, 0xC8B4, 0x5375, 0xC2D1, 0x5376, 0x8571, 0x5377, 0xBEED, 0x5378, 0xD0B6, 0x5379, 0x8572, 0x537A, 0xDAE1, + 0x537B, 0x8573, 0x537C, 0x8574, 0x537D, 0x8575, 0x537E, 0x8576, 0x537F, 0xC7E4, 0x5380, 0x8577, 0x5381, 0x8578, 0x5382, 0xB3A7, + 0x5383, 0x8579, 0x5384, 0xB6F2, 0x5385, 0xCCFC, 0x5386, 0xC0FA, 0x5387, 0x857A, 0x5388, 0x857B, 0x5389, 0xC0F7, 0x538A, 0x857C, + 0x538B, 0xD1B9, 0x538C, 0xD1E1, 0x538D, 0xD8C7, 0x538E, 0x857D, 0x538F, 0x857E, 0x5390, 0x8580, 0x5391, 0x8581, 0x5392, 0x8582, + 0x5393, 0x8583, 0x5394, 0x8584, 0x5395, 0xB2DE, 0x5396, 0x8585, 0x5397, 0x8586, 0x5398, 0xC0E5, 0x5399, 0x8587, 0x539A, 0xBAF1, + 0x539B, 0x8588, 0x539C, 0x8589, 0x539D, 0xD8C8, 0x539E, 0x858A, 0x539F, 0xD4AD, 0x53A0, 0x858B, 0x53A1, 0x858C, 0x53A2, 0xCFE1, + 0x53A3, 0xD8C9, 0x53A4, 0x858D, 0x53A5, 0xD8CA, 0x53A6, 0xCFC3, 0x53A7, 0x858E, 0x53A8, 0xB3F8, 0x53A9, 0xBEC7, 0x53AA, 0x858F, + 0x53AB, 0x8590, 0x53AC, 0x8591, 0x53AD, 0x8592, 0x53AE, 0xD8CB, 0x53AF, 0x8593, 0x53B0, 0x8594, 0x53B1, 0x8595, 0x53B2, 0x8596, + 0x53B3, 0x8597, 0x53B4, 0x8598, 0x53B5, 0x8599, 0x53B6, 0xDBCC, 0x53B7, 0x859A, 0x53B8, 0x859B, 0x53B9, 0x859C, 0x53BA, 0x859D, + 0x53BB, 0xC8A5, 0x53BC, 0x859E, 0x53BD, 0x859F, 0x53BE, 0x85A0, 0x53BF, 0xCFD8, 0x53C0, 0x85A1, 0x53C1, 0xC8FE, 0x53C2, 0xB2CE, + 0x53C3, 0x85A2, 0x53C4, 0x85A3, 0x53C5, 0x85A4, 0x53C6, 0x85A5, 0x53C7, 0x85A6, 0x53C8, 0xD3D6, 0x53C9, 0xB2E6, 0x53CA, 0xBCB0, + 0x53CB, 0xD3D1, 0x53CC, 0xCBAB, 0x53CD, 0xB7B4, 0x53CE, 0x85A7, 0x53CF, 0x85A8, 0x53D0, 0x85A9, 0x53D1, 0xB7A2, 0x53D2, 0x85AA, + 0x53D3, 0x85AB, 0x53D4, 0xCAE5, 0x53D5, 0x85AC, 0x53D6, 0xC8A1, 0x53D7, 0xCADC, 0x53D8, 0xB1E4, 0x53D9, 0xD0F0, 0x53DA, 0x85AD, + 0x53DB, 0xC5D1, 0x53DC, 0x85AE, 0x53DD, 0x85AF, 0x53DE, 0x85B0, 0x53DF, 0xDBC5, 0x53E0, 0xB5FE, 0x53E1, 0x85B1, 0x53E2, 0x85B2, + 0x53E3, 0xBFDA, 0x53E4, 0xB9C5, 0x53E5, 0xBEE4, 0x53E6, 0xC1ED, 0x53E7, 0x85B3, 0x53E8, 0xDFB6, 0x53E9, 0xDFB5, 0x53EA, 0xD6BB, + 0x53EB, 0xBDD0, 0x53EC, 0xD5D9, 0x53ED, 0xB0C8, 0x53EE, 0xB6A3, 0x53EF, 0xBFC9, 0x53F0, 0xCCA8, 0x53F1, 0xDFB3, 0x53F2, 0xCAB7, + 0x53F3, 0xD3D2, 0x53F4, 0x85B4, 0x53F5, 0xD8CF, 0x53F6, 0xD2B6, 0x53F7, 0xBAC5, 0x53F8, 0xCBBE, 0x53F9, 0xCCBE, 0x53FA, 0x85B5, + 0x53FB, 0xDFB7, 0x53FC, 0xB5F0, 0x53FD, 0xDFB4, 0x53FE, 0x85B6, 0x53FF, 0x85B7, 0x5400, 0x85B8, 0x5401, 0xD3F5, 0x5402, 0x85B9, + 0x5403, 0xB3D4, 0x5404, 0xB8F7, 0x5405, 0x85BA, 0x5406, 0xDFBA, 0x5407, 0x85BB, 0x5408, 0xBACF, 0x5409, 0xBCAA, 0x540A, 0xB5F5, + 0x540B, 0x85BC, 0x540C, 0xCDAC, 0x540D, 0xC3FB, 0x540E, 0xBAF3, 0x540F, 0xC0F4, 0x5410, 0xCDC2, 0x5411, 0xCFF2, 0x5412, 0xDFB8, + 0x5413, 0xCFC5, 0x5414, 0x85BD, 0x5415, 0xC2C0, 0x5416, 0xDFB9, 0x5417, 0xC2F0, 0x5418, 0x85BE, 0x5419, 0x85BF, 0x541A, 0x85C0, + 0x541B, 0xBEFD, 0x541C, 0x85C1, 0x541D, 0xC1DF, 0x541E, 0xCDCC, 0x541F, 0xD2F7, 0x5420, 0xB7CD, 0x5421, 0xDFC1, 0x5422, 0x85C2, + 0x5423, 0xDFC4, 0x5424, 0x85C3, 0x5425, 0x85C4, 0x5426, 0xB7F1, 0x5427, 0xB0C9, 0x5428, 0xB6D6, 0x5429, 0xB7D4, 0x542A, 0x85C5, + 0x542B, 0xBAAC, 0x542C, 0xCCFD, 0x542D, 0xBFD4, 0x542E, 0xCBB1, 0x542F, 0xC6F4, 0x5430, 0x85C6, 0x5431, 0xD6A8, 0x5432, 0xDFC5, + 0x5433, 0x85C7, 0x5434, 0xCEE2, 0x5435, 0xB3B3, 0x5436, 0x85C8, 0x5437, 0x85C9, 0x5438, 0xCEFC, 0x5439, 0xB4B5, 0x543A, 0x85CA, + 0x543B, 0xCEC7, 0x543C, 0xBAF0, 0x543D, 0x85CB, 0x543E, 0xCEE1, 0x543F, 0x85CC, 0x5440, 0xD1BD, 0x5441, 0x85CD, 0x5442, 0x85CE, + 0x5443, 0xDFC0, 0x5444, 0x85CF, 0x5445, 0x85D0, 0x5446, 0xB4F4, 0x5447, 0x85D1, 0x5448, 0xB3CA, 0x5449, 0x85D2, 0x544A, 0xB8E6, + 0x544B, 0xDFBB, 0x544C, 0x85D3, 0x544D, 0x85D4, 0x544E, 0x85D5, 0x544F, 0x85D6, 0x5450, 0xC4C5, 0x5451, 0x85D7, 0x5452, 0xDFBC, + 0x5453, 0xDFBD, 0x5454, 0xDFBE, 0x5455, 0xC5BB, 0x5456, 0xDFBF, 0x5457, 0xDFC2, 0x5458, 0xD4B1, 0x5459, 0xDFC3, 0x545A, 0x85D8, + 0x545B, 0xC7BA, 0x545C, 0xCED8, 0x545D, 0x85D9, 0x545E, 0x85DA, 0x545F, 0x85DB, 0x5460, 0x85DC, 0x5461, 0x85DD, 0x5462, 0xC4D8, + 0x5463, 0x85DE, 0x5464, 0xDFCA, 0x5465, 0x85DF, 0x5466, 0xDFCF, 0x5467, 0x85E0, 0x5468, 0xD6DC, 0x5469, 0x85E1, 0x546A, 0x85E2, + 0x546B, 0x85E3, 0x546C, 0x85E4, 0x546D, 0x85E5, 0x546E, 0x85E6, 0x546F, 0x85E7, 0x5470, 0x85E8, 0x5471, 0xDFC9, 0x5472, 0xDFDA, + 0x5473, 0xCEB6, 0x5474, 0x85E9, 0x5475, 0xBAC7, 0x5476, 0xDFCE, 0x5477, 0xDFC8, 0x5478, 0xC5DE, 0x5479, 0x85EA, 0x547A, 0x85EB, + 0x547B, 0xC9EB, 0x547C, 0xBAF4, 0x547D, 0xC3FC, 0x547E, 0x85EC, 0x547F, 0x85ED, 0x5480, 0xBED7, 0x5481, 0x85EE, 0x5482, 0xDFC6, + 0x5483, 0x85EF, 0x5484, 0xDFCD, 0x5485, 0x85F0, 0x5486, 0xC5D8, 0x5487, 0x85F1, 0x5488, 0x85F2, 0x5489, 0x85F3, 0x548A, 0x85F4, + 0x548B, 0xD5A6, 0x548C, 0xBACD, 0x548D, 0x85F5, 0x548E, 0xBECC, 0x548F, 0xD3BD, 0x5490, 0xB8C0, 0x5491, 0x85F6, 0x5492, 0xD6E4, + 0x5493, 0x85F7, 0x5494, 0xDFC7, 0x5495, 0xB9BE, 0x5496, 0xBFA7, 0x5497, 0x85F8, 0x5498, 0x85F9, 0x5499, 0xC1FC, 0x549A, 0xDFCB, + 0x549B, 0xDFCC, 0x549C, 0x85FA, 0x549D, 0xDFD0, 0x549E, 0x85FB, 0x549F, 0x85FC, 0x54A0, 0x85FD, 0x54A1, 0x85FE, 0x54A2, 0x8640, + 0x54A3, 0xDFDB, 0x54A4, 0xDFE5, 0x54A5, 0x8641, 0x54A6, 0xDFD7, 0x54A7, 0xDFD6, 0x54A8, 0xD7C9, 0x54A9, 0xDFE3, 0x54AA, 0xDFE4, + 0x54AB, 0xE5EB, 0x54AC, 0xD2A7, 0x54AD, 0xDFD2, 0x54AE, 0x8642, 0x54AF, 0xBFA9, 0x54B0, 0x8643, 0x54B1, 0xD4DB, 0x54B2, 0x8644, + 0x54B3, 0xBFC8, 0x54B4, 0xDFD4, 0x54B5, 0x8645, 0x54B6, 0x8646, 0x54B7, 0x8647, 0x54B8, 0xCFCC, 0x54B9, 0x8648, 0x54BA, 0x8649, + 0x54BB, 0xDFDD, 0x54BC, 0x864A, 0x54BD, 0xD1CA, 0x54BE, 0x864B, 0x54BF, 0xDFDE, 0x54C0, 0xB0A7, 0x54C1, 0xC6B7, 0x54C2, 0xDFD3, + 0x54C3, 0x864C, 0x54C4, 0xBAE5, 0x54C5, 0x864D, 0x54C6, 0xB6DF, 0x54C7, 0xCDDB, 0x54C8, 0xB9FE, 0x54C9, 0xD4D5, 0x54CA, 0x864E, + 0x54CB, 0x864F, 0x54CC, 0xDFDF, 0x54CD, 0xCFEC, 0x54CE, 0xB0A5, 0x54CF, 0xDFE7, 0x54D0, 0xDFD1, 0x54D1, 0xD1C6, 0x54D2, 0xDFD5, + 0x54D3, 0xDFD8, 0x54D4, 0xDFD9, 0x54D5, 0xDFDC, 0x54D6, 0x8650, 0x54D7, 0xBBA9, 0x54D8, 0x8651, 0x54D9, 0xDFE0, 0x54DA, 0xDFE1, + 0x54DB, 0x8652, 0x54DC, 0xDFE2, 0x54DD, 0xDFE6, 0x54DE, 0xDFE8, 0x54DF, 0xD3B4, 0x54E0, 0x8653, 0x54E1, 0x8654, 0x54E2, 0x8655, + 0x54E3, 0x8656, 0x54E4, 0x8657, 0x54E5, 0xB8E7, 0x54E6, 0xC5B6, 0x54E7, 0xDFEA, 0x54E8, 0xC9DA, 0x54E9, 0xC1A8, 0x54EA, 0xC4C4, + 0x54EB, 0x8658, 0x54EC, 0x8659, 0x54ED, 0xBFDE, 0x54EE, 0xCFF8, 0x54EF, 0x865A, 0x54F0, 0x865B, 0x54F1, 0x865C, 0x54F2, 0xD5DC, + 0x54F3, 0xDFEE, 0x54F4, 0x865D, 0x54F5, 0x865E, 0x54F6, 0x865F, 0x54F7, 0x8660, 0x54F8, 0x8661, 0x54F9, 0x8662, 0x54FA, 0xB2B8, + 0x54FB, 0x8663, 0x54FC, 0xBADF, 0x54FD, 0xDFEC, 0x54FE, 0x8664, 0x54FF, 0xDBC1, 0x5500, 0x8665, 0x5501, 0xD1E4, 0x5502, 0x8666, + 0x5503, 0x8667, 0x5504, 0x8668, 0x5505, 0x8669, 0x5506, 0xCBF4, 0x5507, 0xB4BD, 0x5508, 0x866A, 0x5509, 0xB0A6, 0x550A, 0x866B, + 0x550B, 0x866C, 0x550C, 0x866D, 0x550D, 0x866E, 0x550E, 0x866F, 0x550F, 0xDFF1, 0x5510, 0xCCC6, 0x5511, 0xDFF2, 0x5512, 0x8670, + 0x5513, 0x8671, 0x5514, 0xDFED, 0x5515, 0x8672, 0x5516, 0x8673, 0x5517, 0x8674, 0x5518, 0x8675, 0x5519, 0x8676, 0x551A, 0x8677, + 0x551B, 0xDFE9, 0x551C, 0x8678, 0x551D, 0x8679, 0x551E, 0x867A, 0x551F, 0x867B, 0x5520, 0xDFEB, 0x5521, 0x867C, 0x5522, 0xDFEF, + 0x5523, 0xDFF0, 0x5524, 0xBBBD, 0x5525, 0x867D, 0x5526, 0x867E, 0x5527, 0xDFF3, 0x5528, 0x8680, 0x5529, 0x8681, 0x552A, 0xDFF4, + 0x552B, 0x8682, 0x552C, 0xBBA3, 0x552D, 0x8683, 0x552E, 0xCADB, 0x552F, 0xCEA8, 0x5530, 0xE0A7, 0x5531, 0xB3AA, 0x5532, 0x8684, + 0x5533, 0xE0A6, 0x5534, 0x8685, 0x5535, 0x8686, 0x5536, 0x8687, 0x5537, 0xE0A1, 0x5538, 0x8688, 0x5539, 0x8689, 0x553A, 0x868A, + 0x553B, 0x868B, 0x553C, 0xDFFE, 0x553D, 0x868C, 0x553E, 0xCDD9, 0x553F, 0xDFFC, 0x5540, 0x868D, 0x5541, 0xDFFA, 0x5542, 0x868E, + 0x5543, 0xBFD0, 0x5544, 0xD7C4, 0x5545, 0x868F, 0x5546, 0xC9CC, 0x5547, 0x8690, 0x5548, 0x8691, 0x5549, 0xDFF8, 0x554A, 0xB0A1, + 0x554B, 0x8692, 0x554C, 0x8693, 0x554D, 0x8694, 0x554E, 0x8695, 0x554F, 0x8696, 0x5550, 0xDFFD, 0x5551, 0x8697, 0x5552, 0x8698, + 0x5553, 0x8699, 0x5554, 0x869A, 0x5555, 0xDFFB, 0x5556, 0xE0A2, 0x5557, 0x869B, 0x5558, 0x869C, 0x5559, 0x869D, 0x555A, 0x869E, + 0x555B, 0x869F, 0x555C, 0xE0A8, 0x555D, 0x86A0, 0x555E, 0x86A1, 0x555F, 0x86A2, 0x5560, 0x86A3, 0x5561, 0xB7C8, 0x5562, 0x86A4, + 0x5563, 0x86A5, 0x5564, 0xC6A1, 0x5565, 0xC9B6, 0x5566, 0xC0B2, 0x5567, 0xDFF5, 0x5568, 0x86A6, 0x5569, 0x86A7, 0x556A, 0xC5BE, + 0x556B, 0x86A8, 0x556C, 0xD8C4, 0x556D, 0xDFF9, 0x556E, 0xC4F6, 0x556F, 0x86A9, 0x5570, 0x86AA, 0x5571, 0x86AB, 0x5572, 0x86AC, + 0x5573, 0x86AD, 0x5574, 0x86AE, 0x5575, 0xE0A3, 0x5576, 0xE0A4, 0x5577, 0xE0A5, 0x5578, 0xD0A5, 0x5579, 0x86AF, 0x557A, 0x86B0, + 0x557B, 0xE0B4, 0x557C, 0xCCE4, 0x557D, 0x86B1, 0x557E, 0xE0B1, 0x557F, 0x86B2, 0x5580, 0xBFA6, 0x5581, 0xE0AF, 0x5582, 0xCEB9, + 0x5583, 0xE0AB, 0x5584, 0xC9C6, 0x5585, 0x86B3, 0x5586, 0x86B4, 0x5587, 0xC0AE, 0x5588, 0xE0AE, 0x5589, 0xBAED, 0x558A, 0xBAB0, + 0x558B, 0xE0A9, 0x558C, 0x86B5, 0x558D, 0x86B6, 0x558E, 0x86B7, 0x558F, 0xDFF6, 0x5590, 0x86B8, 0x5591, 0xE0B3, 0x5592, 0x86B9, + 0x5593, 0x86BA, 0x5594, 0xE0B8, 0x5595, 0x86BB, 0x5596, 0x86BC, 0x5597, 0x86BD, 0x5598, 0xB4AD, 0x5599, 0xE0B9, 0x559A, 0x86BE, + 0x559B, 0x86BF, 0x559C, 0xCFB2, 0x559D, 0xBAC8, 0x559E, 0x86C0, 0x559F, 0xE0B0, 0x55A0, 0x86C1, 0x55A1, 0x86C2, 0x55A2, 0x86C3, + 0x55A3, 0x86C4, 0x55A4, 0x86C5, 0x55A5, 0x86C6, 0x55A6, 0x86C7, 0x55A7, 0xD0FA, 0x55A8, 0x86C8, 0x55A9, 0x86C9, 0x55AA, 0x86CA, + 0x55AB, 0x86CB, 0x55AC, 0x86CC, 0x55AD, 0x86CD, 0x55AE, 0x86CE, 0x55AF, 0x86CF, 0x55B0, 0x86D0, 0x55B1, 0xE0AC, 0x55B2, 0x86D1, + 0x55B3, 0xD4FB, 0x55B4, 0x86D2, 0x55B5, 0xDFF7, 0x55B6, 0x86D3, 0x55B7, 0xC5E7, 0x55B8, 0x86D4, 0x55B9, 0xE0AD, 0x55BA, 0x86D5, + 0x55BB, 0xD3F7, 0x55BC, 0x86D6, 0x55BD, 0xE0B6, 0x55BE, 0xE0B7, 0x55BF, 0x86D7, 0x55C0, 0x86D8, 0x55C1, 0x86D9, 0x55C2, 0x86DA, + 0x55C3, 0x86DB, 0x55C4, 0xE0C4, 0x55C5, 0xD0E1, 0x55C6, 0x86DC, 0x55C7, 0x86DD, 0x55C8, 0x86DE, 0x55C9, 0xE0BC, 0x55CA, 0x86DF, + 0x55CB, 0x86E0, 0x55CC, 0xE0C9, 0x55CD, 0xE0CA, 0x55CE, 0x86E1, 0x55CF, 0x86E2, 0x55D0, 0x86E3, 0x55D1, 0xE0BE, 0x55D2, 0xE0AA, + 0x55D3, 0xC9A4, 0x55D4, 0xE0C1, 0x55D5, 0x86E4, 0x55D6, 0xE0B2, 0x55D7, 0x86E5, 0x55D8, 0x86E6, 0x55D9, 0x86E7, 0x55DA, 0x86E8, + 0x55DB, 0x86E9, 0x55DC, 0xCAC8, 0x55DD, 0xE0C3, 0x55DE, 0x86EA, 0x55DF, 0xE0B5, 0x55E0, 0x86EB, 0x55E1, 0xCECB, 0x55E2, 0x86EC, + 0x55E3, 0xCBC3, 0x55E4, 0xE0CD, 0x55E5, 0xE0C6, 0x55E6, 0xE0C2, 0x55E7, 0x86ED, 0x55E8, 0xE0CB, 0x55E9, 0x86EE, 0x55EA, 0xE0BA, + 0x55EB, 0xE0BF, 0x55EC, 0xE0C0, 0x55ED, 0x86EF, 0x55EE, 0x86F0, 0x55EF, 0xE0C5, 0x55F0, 0x86F1, 0x55F1, 0x86F2, 0x55F2, 0xE0C7, + 0x55F3, 0xE0C8, 0x55F4, 0x86F3, 0x55F5, 0xE0CC, 0x55F6, 0x86F4, 0x55F7, 0xE0BB, 0x55F8, 0x86F5, 0x55F9, 0x86F6, 0x55FA, 0x86F7, + 0x55FB, 0x86F8, 0x55FC, 0x86F9, 0x55FD, 0xCBD4, 0x55FE, 0xE0D5, 0x55FF, 0x86FA, 0x5600, 0xE0D6, 0x5601, 0xE0D2, 0x5602, 0x86FB, + 0x5603, 0x86FC, 0x5604, 0x86FD, 0x5605, 0x86FE, 0x5606, 0x8740, 0x5607, 0x8741, 0x5608, 0xE0D0, 0x5609, 0xBCCE, 0x560A, 0x8742, + 0x560B, 0x8743, 0x560C, 0xE0D1, 0x560D, 0x8744, 0x560E, 0xB8C2, 0x560F, 0xD8C5, 0x5610, 0x8745, 0x5611, 0x8746, 0x5612, 0x8747, + 0x5613, 0x8748, 0x5614, 0x8749, 0x5615, 0x874A, 0x5616, 0x874B, 0x5617, 0x874C, 0x5618, 0xD0EA, 0x5619, 0x874D, 0x561A, 0x874E, + 0x561B, 0xC2EF, 0x561C, 0x874F, 0x561D, 0x8750, 0x561E, 0xE0CF, 0x561F, 0xE0BD, 0x5620, 0x8751, 0x5621, 0x8752, 0x5622, 0x8753, + 0x5623, 0xE0D4, 0x5624, 0xE0D3, 0x5625, 0x8754, 0x5626, 0x8755, 0x5627, 0xE0D7, 0x5628, 0x8756, 0x5629, 0x8757, 0x562A, 0x8758, + 0x562B, 0x8759, 0x562C, 0xE0DC, 0x562D, 0xE0D8, 0x562E, 0x875A, 0x562F, 0x875B, 0x5630, 0x875C, 0x5631, 0xD6F6, 0x5632, 0xB3B0, + 0x5633, 0x875D, 0x5634, 0xD7EC, 0x5635, 0x875E, 0x5636, 0xCBBB, 0x5637, 0x875F, 0x5638, 0x8760, 0x5639, 0xE0DA, 0x563A, 0x8761, + 0x563B, 0xCEFB, 0x563C, 0x8762, 0x563D, 0x8763, 0x563E, 0x8764, 0x563F, 0xBAD9, 0x5640, 0x8765, 0x5641, 0x8766, 0x5642, 0x8767, + 0x5643, 0x8768, 0x5644, 0x8769, 0x5645, 0x876A, 0x5646, 0x876B, 0x5647, 0x876C, 0x5648, 0x876D, 0x5649, 0x876E, 0x564A, 0x876F, + 0x564B, 0x8770, 0x564C, 0xE0E1, 0x564D, 0xE0DD, 0x564E, 0xD2AD, 0x564F, 0x8771, 0x5650, 0x8772, 0x5651, 0x8773, 0x5652, 0x8774, + 0x5653, 0x8775, 0x5654, 0xE0E2, 0x5655, 0x8776, 0x5656, 0x8777, 0x5657, 0xE0DB, 0x5658, 0xE0D9, 0x5659, 0xE0DF, 0x565A, 0x8778, + 0x565B, 0x8779, 0x565C, 0xE0E0, 0x565D, 0x877A, 0x565E, 0x877B, 0x565F, 0x877C, 0x5660, 0x877D, 0x5661, 0x877E, 0x5662, 0xE0DE, + 0x5663, 0x8780, 0x5664, 0xE0E4, 0x5665, 0x8781, 0x5666, 0x8782, 0x5667, 0x8783, 0x5668, 0xC6F7, 0x5669, 0xD8AC, 0x566A, 0xD4EB, + 0x566B, 0xE0E6, 0x566C, 0xCAC9, 0x566D, 0x8784, 0x566E, 0x8785, 0x566F, 0x8786, 0x5670, 0x8787, 0x5671, 0xE0E5, 0x5672, 0x8788, + 0x5673, 0x8789, 0x5674, 0x878A, 0x5675, 0x878B, 0x5676, 0xB8C1, 0x5677, 0x878C, 0x5678, 0x878D, 0x5679, 0x878E, 0x567A, 0x878F, + 0x567B, 0xE0E7, 0x567C, 0xE0E8, 0x567D, 0x8790, 0x567E, 0x8791, 0x567F, 0x8792, 0x5680, 0x8793, 0x5681, 0x8794, 0x5682, 0x8795, + 0x5683, 0x8796, 0x5684, 0x8797, 0x5685, 0xE0E9, 0x5686, 0xE0E3, 0x5687, 0x8798, 0x5688, 0x8799, 0x5689, 0x879A, 0x568A, 0x879B, + 0x568B, 0x879C, 0x568C, 0x879D, 0x568D, 0x879E, 0x568E, 0xBABF, 0x568F, 0xCCE7, 0x5690, 0x879F, 0x5691, 0x87A0, 0x5692, 0x87A1, + 0x5693, 0xE0EA, 0x5694, 0x87A2, 0x5695, 0x87A3, 0x5696, 0x87A4, 0x5697, 0x87A5, 0x5698, 0x87A6, 0x5699, 0x87A7, 0x569A, 0x87A8, + 0x569B, 0x87A9, 0x569C, 0x87AA, 0x569D, 0x87AB, 0x569E, 0x87AC, 0x569F, 0x87AD, 0x56A0, 0x87AE, 0x56A1, 0x87AF, 0x56A2, 0x87B0, + 0x56A3, 0xCFF9, 0x56A4, 0x87B1, 0x56A5, 0x87B2, 0x56A6, 0x87B3, 0x56A7, 0x87B4, 0x56A8, 0x87B5, 0x56A9, 0x87B6, 0x56AA, 0x87B7, + 0x56AB, 0x87B8, 0x56AC, 0x87B9, 0x56AD, 0x87BA, 0x56AE, 0x87BB, 0x56AF, 0xE0EB, 0x56B0, 0x87BC, 0x56B1, 0x87BD, 0x56B2, 0x87BE, + 0x56B3, 0x87BF, 0x56B4, 0x87C0, 0x56B5, 0x87C1, 0x56B6, 0x87C2, 0x56B7, 0xC8C2, 0x56B8, 0x87C3, 0x56B9, 0x87C4, 0x56BA, 0x87C5, + 0x56BB, 0x87C6, 0x56BC, 0xBDC0, 0x56BD, 0x87C7, 0x56BE, 0x87C8, 0x56BF, 0x87C9, 0x56C0, 0x87CA, 0x56C1, 0x87CB, 0x56C2, 0x87CC, + 0x56C3, 0x87CD, 0x56C4, 0x87CE, 0x56C5, 0x87CF, 0x56C6, 0x87D0, 0x56C7, 0x87D1, 0x56C8, 0x87D2, 0x56C9, 0x87D3, 0x56CA, 0xC4D2, + 0x56CB, 0x87D4, 0x56CC, 0x87D5, 0x56CD, 0x87D6, 0x56CE, 0x87D7, 0x56CF, 0x87D8, 0x56D0, 0x87D9, 0x56D1, 0x87DA, 0x56D2, 0x87DB, + 0x56D3, 0x87DC, 0x56D4, 0xE0EC, 0x56D5, 0x87DD, 0x56D6, 0x87DE, 0x56D7, 0xE0ED, 0x56D8, 0x87DF, 0x56D9, 0x87E0, 0x56DA, 0xC7F4, + 0x56DB, 0xCBC4, 0x56DC, 0x87E1, 0x56DD, 0xE0EE, 0x56DE, 0xBBD8, 0x56DF, 0xD8B6, 0x56E0, 0xD2F2, 0x56E1, 0xE0EF, 0x56E2, 0xCDC5, + 0x56E3, 0x87E2, 0x56E4, 0xB6DA, 0x56E5, 0x87E3, 0x56E6, 0x87E4, 0x56E7, 0x87E5, 0x56E8, 0x87E6, 0x56E9, 0x87E7, 0x56EA, 0x87E8, + 0x56EB, 0xE0F1, 0x56EC, 0x87E9, 0x56ED, 0xD4B0, 0x56EE, 0x87EA, 0x56EF, 0x87EB, 0x56F0, 0xC0A7, 0x56F1, 0xB4D1, 0x56F2, 0x87EC, + 0x56F3, 0x87ED, 0x56F4, 0xCEA7, 0x56F5, 0xE0F0, 0x56F6, 0x87EE, 0x56F7, 0x87EF, 0x56F8, 0x87F0, 0x56F9, 0xE0F2, 0x56FA, 0xB9CC, + 0x56FB, 0x87F1, 0x56FC, 0x87F2, 0x56FD, 0xB9FA, 0x56FE, 0xCDBC, 0x56FF, 0xE0F3, 0x5700, 0x87F3, 0x5701, 0x87F4, 0x5702, 0x87F5, + 0x5703, 0xC6D4, 0x5704, 0xE0F4, 0x5705, 0x87F6, 0x5706, 0xD4B2, 0x5707, 0x87F7, 0x5708, 0xC8A6, 0x5709, 0xE0F6, 0x570A, 0xE0F5, + 0x570B, 0x87F8, 0x570C, 0x87F9, 0x570D, 0x87FA, 0x570E, 0x87FB, 0x570F, 0x87FC, 0x5710, 0x87FD, 0x5711, 0x87FE, 0x5712, 0x8840, + 0x5713, 0x8841, 0x5714, 0x8842, 0x5715, 0x8843, 0x5716, 0x8844, 0x5717, 0x8845, 0x5718, 0x8846, 0x5719, 0x8847, 0x571A, 0x8848, + 0x571B, 0x8849, 0x571C, 0xE0F7, 0x571D, 0x884A, 0x571E, 0x884B, 0x571F, 0xCDC1, 0x5720, 0x884C, 0x5721, 0x884D, 0x5722, 0x884E, + 0x5723, 0xCAA5, 0x5724, 0x884F, 0x5725, 0x8850, 0x5726, 0x8851, 0x5727, 0x8852, 0x5728, 0xD4DA, 0x5729, 0xDBD7, 0x572A, 0xDBD9, + 0x572B, 0x8853, 0x572C, 0xDBD8, 0x572D, 0xB9E7, 0x572E, 0xDBDC, 0x572F, 0xDBDD, 0x5730, 0xB5D8, 0x5731, 0x8854, 0x5732, 0x8855, + 0x5733, 0xDBDA, 0x5734, 0x8856, 0x5735, 0x8857, 0x5736, 0x8858, 0x5737, 0x8859, 0x5738, 0x885A, 0x5739, 0xDBDB, 0x573A, 0xB3A1, + 0x573B, 0xDBDF, 0x573C, 0x885B, 0x573D, 0x885C, 0x573E, 0xBBF8, 0x573F, 0x885D, 0x5740, 0xD6B7, 0x5741, 0x885E, 0x5742, 0xDBE0, + 0x5743, 0x885F, 0x5744, 0x8860, 0x5745, 0x8861, 0x5746, 0x8862, 0x5747, 0xBEF9, 0x5748, 0x8863, 0x5749, 0x8864, 0x574A, 0xB7BB, + 0x574B, 0x8865, 0x574C, 0xDBD0, 0x574D, 0xCCAE, 0x574E, 0xBFB2, 0x574F, 0xBBB5, 0x5750, 0xD7F8, 0x5751, 0xBFD3, 0x5752, 0x8866, + 0x5753, 0x8867, 0x5754, 0x8868, 0x5755, 0x8869, 0x5756, 0x886A, 0x5757, 0xBFE9, 0x5758, 0x886B, 0x5759, 0x886C, 0x575A, 0xBCE1, + 0x575B, 0xCCB3, 0x575C, 0xDBDE, 0x575D, 0xB0D3, 0x575E, 0xCEEB, 0x575F, 0xB7D8, 0x5760, 0xD7B9, 0x5761, 0xC6C2, 0x5762, 0x886D, + 0x5763, 0x886E, 0x5764, 0xC0A4, 0x5765, 0x886F, 0x5766, 0xCCB9, 0x5767, 0x8870, 0x5768, 0xDBE7, 0x5769, 0xDBE1, 0x576A, 0xC6BA, + 0x576B, 0xDBE3, 0x576C, 0x8871, 0x576D, 0xDBE8, 0x576E, 0x8872, 0x576F, 0xC5F7, 0x5770, 0x8873, 0x5771, 0x8874, 0x5772, 0x8875, + 0x5773, 0xDBEA, 0x5774, 0x8876, 0x5775, 0x8877, 0x5776, 0xDBE9, 0x5777, 0xBFC0, 0x5778, 0x8878, 0x5779, 0x8879, 0x577A, 0x887A, + 0x577B, 0xDBE6, 0x577C, 0xDBE5, 0x577D, 0x887B, 0x577E, 0x887C, 0x577F, 0x887D, 0x5780, 0x887E, 0x5781, 0x8880, 0x5782, 0xB4B9, + 0x5783, 0xC0AC, 0x5784, 0xC2A2, 0x5785, 0xDBE2, 0x5786, 0xDBE4, 0x5787, 0x8881, 0x5788, 0x8882, 0x5789, 0x8883, 0x578A, 0x8884, + 0x578B, 0xD0CD, 0x578C, 0xDBED, 0x578D, 0x8885, 0x578E, 0x8886, 0x578F, 0x8887, 0x5790, 0x8888, 0x5791, 0x8889, 0x5792, 0xC0DD, + 0x5793, 0xDBF2, 0x5794, 0x888A, 0x5795, 0x888B, 0x5796, 0x888C, 0x5797, 0x888D, 0x5798, 0x888E, 0x5799, 0x888F, 0x579A, 0x8890, + 0x579B, 0xB6E2, 0x579C, 0x8891, 0x579D, 0x8892, 0x579E, 0x8893, 0x579F, 0x8894, 0x57A0, 0xDBF3, 0x57A1, 0xDBD2, 0x57A2, 0xB9B8, + 0x57A3, 0xD4AB, 0x57A4, 0xDBEC, 0x57A5, 0x8895, 0x57A6, 0xBFD1, 0x57A7, 0xDBF0, 0x57A8, 0x8896, 0x57A9, 0xDBD1, 0x57AA, 0x8897, + 0x57AB, 0xB5E6, 0x57AC, 0x8898, 0x57AD, 0xDBEB, 0x57AE, 0xBFE5, 0x57AF, 0x8899, 0x57B0, 0x889A, 0x57B1, 0x889B, 0x57B2, 0xDBEE, + 0x57B3, 0x889C, 0x57B4, 0xDBF1, 0x57B5, 0x889D, 0x57B6, 0x889E, 0x57B7, 0x889F, 0x57B8, 0xDBF9, 0x57B9, 0x88A0, 0x57BA, 0x88A1, + 0x57BB, 0x88A2, 0x57BC, 0x88A3, 0x57BD, 0x88A4, 0x57BE, 0x88A5, 0x57BF, 0x88A6, 0x57C0, 0x88A7, 0x57C1, 0x88A8, 0x57C2, 0xB9A1, + 0x57C3, 0xB0A3, 0x57C4, 0x88A9, 0x57C5, 0x88AA, 0x57C6, 0x88AB, 0x57C7, 0x88AC, 0x57C8, 0x88AD, 0x57C9, 0x88AE, 0x57CA, 0x88AF, + 0x57CB, 0xC2F1, 0x57CC, 0x88B0, 0x57CD, 0x88B1, 0x57CE, 0xB3C7, 0x57CF, 0xDBEF, 0x57D0, 0x88B2, 0x57D1, 0x88B3, 0x57D2, 0xDBF8, + 0x57D3, 0x88B4, 0x57D4, 0xC6D2, 0x57D5, 0xDBF4, 0x57D6, 0x88B5, 0x57D7, 0x88B6, 0x57D8, 0xDBF5, 0x57D9, 0xDBF7, 0x57DA, 0xDBF6, + 0x57DB, 0x88B7, 0x57DC, 0x88B8, 0x57DD, 0xDBFE, 0x57DE, 0x88B9, 0x57DF, 0xD3F2, 0x57E0, 0xB2BA, 0x57E1, 0x88BA, 0x57E2, 0x88BB, + 0x57E3, 0x88BC, 0x57E4, 0xDBFD, 0x57E5, 0x88BD, 0x57E6, 0x88BE, 0x57E7, 0x88BF, 0x57E8, 0x88C0, 0x57E9, 0x88C1, 0x57EA, 0x88C2, + 0x57EB, 0x88C3, 0x57EC, 0x88C4, 0x57ED, 0xDCA4, 0x57EE, 0x88C5, 0x57EF, 0xDBFB, 0x57F0, 0x88C6, 0x57F1, 0x88C7, 0x57F2, 0x88C8, + 0x57F3, 0x88C9, 0x57F4, 0xDBFA, 0x57F5, 0x88CA, 0x57F6, 0x88CB, 0x57F7, 0x88CC, 0x57F8, 0xDBFC, 0x57F9, 0xC5E0, 0x57FA, 0xBBF9, + 0x57FB, 0x88CD, 0x57FC, 0x88CE, 0x57FD, 0xDCA3, 0x57FE, 0x88CF, 0x57FF, 0x88D0, 0x5800, 0xDCA5, 0x5801, 0x88D1, 0x5802, 0xCCC3, + 0x5803, 0x88D2, 0x5804, 0x88D3, 0x5805, 0x88D4, 0x5806, 0xB6D1, 0x5807, 0xDDC0, 0x5808, 0x88D5, 0x5809, 0x88D6, 0x580A, 0x88D7, + 0x580B, 0xDCA1, 0x580C, 0x88D8, 0x580D, 0xDCA2, 0x580E, 0x88D9, 0x580F, 0x88DA, 0x5810, 0x88DB, 0x5811, 0xC7B5, 0x5812, 0x88DC, + 0x5813, 0x88DD, 0x5814, 0x88DE, 0x5815, 0xB6E9, 0x5816, 0x88DF, 0x5817, 0x88E0, 0x5818, 0x88E1, 0x5819, 0xDCA7, 0x581A, 0x88E2, + 0x581B, 0x88E3, 0x581C, 0x88E4, 0x581D, 0x88E5, 0x581E, 0xDCA6, 0x581F, 0x88E6, 0x5820, 0xDCA9, 0x5821, 0xB1A4, 0x5822, 0x88E7, + 0x5823, 0x88E8, 0x5824, 0xB5CC, 0x5825, 0x88E9, 0x5826, 0x88EA, 0x5827, 0x88EB, 0x5828, 0x88EC, 0x5829, 0x88ED, 0x582A, 0xBFB0, + 0x582B, 0x88EE, 0x582C, 0x88EF, 0x582D, 0x88F0, 0x582E, 0x88F1, 0x582F, 0x88F2, 0x5830, 0xD1DF, 0x5831, 0x88F3, 0x5832, 0x88F4, + 0x5833, 0x88F5, 0x5834, 0x88F6, 0x5835, 0xB6C2, 0x5836, 0x88F7, 0x5837, 0x88F8, 0x5838, 0x88F9, 0x5839, 0x88FA, 0x583A, 0x88FB, + 0x583B, 0x88FC, 0x583C, 0x88FD, 0x583D, 0x88FE, 0x583E, 0x8940, 0x583F, 0x8941, 0x5840, 0x8942, 0x5841, 0x8943, 0x5842, 0x8944, + 0x5843, 0x8945, 0x5844, 0xDCA8, 0x5845, 0x8946, 0x5846, 0x8947, 0x5847, 0x8948, 0x5848, 0x8949, 0x5849, 0x894A, 0x584A, 0x894B, + 0x584B, 0x894C, 0x584C, 0xCBFA, 0x584D, 0xEBF3, 0x584E, 0x894D, 0x584F, 0x894E, 0x5850, 0x894F, 0x5851, 0xCBDC, 0x5852, 0x8950, + 0x5853, 0x8951, 0x5854, 0xCBFE, 0x5855, 0x8952, 0x5856, 0x8953, 0x5857, 0x8954, 0x5858, 0xCCC1, 0x5859, 0x8955, 0x585A, 0x8956, + 0x585B, 0x8957, 0x585C, 0x8958, 0x585D, 0x8959, 0x585E, 0xC8FB, 0x585F, 0x895A, 0x5860, 0x895B, 0x5861, 0x895C, 0x5862, 0x895D, + 0x5863, 0x895E, 0x5864, 0x895F, 0x5865, 0xDCAA, 0x5866, 0x8960, 0x5867, 0x8961, 0x5868, 0x8962, 0x5869, 0x8963, 0x586A, 0x8964, + 0x586B, 0xCCEE, 0x586C, 0xDCAB, 0x586D, 0x8965, 0x586E, 0x8966, 0x586F, 0x8967, 0x5870, 0x8968, 0x5871, 0x8969, 0x5872, 0x896A, + 0x5873, 0x896B, 0x5874, 0x896C, 0x5875, 0x896D, 0x5876, 0x896E, 0x5877, 0x896F, 0x5878, 0x8970, 0x5879, 0x8971, 0x587A, 0x8972, + 0x587B, 0x8973, 0x587C, 0x8974, 0x587D, 0x8975, 0x587E, 0xDBD3, 0x587F, 0x8976, 0x5880, 0xDCAF, 0x5881, 0xDCAC, 0x5882, 0x8977, + 0x5883, 0xBEB3, 0x5884, 0x8978, 0x5885, 0xCAFB, 0x5886, 0x8979, 0x5887, 0x897A, 0x5888, 0x897B, 0x5889, 0xDCAD, 0x588A, 0x897C, + 0x588B, 0x897D, 0x588C, 0x897E, 0x588D, 0x8980, 0x588E, 0x8981, 0x588F, 0x8982, 0x5890, 0x8983, 0x5891, 0x8984, 0x5892, 0xC9CA, + 0x5893, 0xC4B9, 0x5894, 0x8985, 0x5895, 0x8986, 0x5896, 0x8987, 0x5897, 0x8988, 0x5898, 0x8989, 0x5899, 0xC7BD, 0x589A, 0xDCAE, + 0x589B, 0x898A, 0x589C, 0x898B, 0x589D, 0x898C, 0x589E, 0xD4F6, 0x589F, 0xD0E6, 0x58A0, 0x898D, 0x58A1, 0x898E, 0x58A2, 0x898F, + 0x58A3, 0x8990, 0x58A4, 0x8991, 0x58A5, 0x8992, 0x58A6, 0x8993, 0x58A7, 0x8994, 0x58A8, 0xC4AB, 0x58A9, 0xB6D5, 0x58AA, 0x8995, + 0x58AB, 0x8996, 0x58AC, 0x8997, 0x58AD, 0x8998, 0x58AE, 0x8999, 0x58AF, 0x899A, 0x58B0, 0x899B, 0x58B1, 0x899C, 0x58B2, 0x899D, + 0x58B3, 0x899E, 0x58B4, 0x899F, 0x58B5, 0x89A0, 0x58B6, 0x89A1, 0x58B7, 0x89A2, 0x58B8, 0x89A3, 0x58B9, 0x89A4, 0x58BA, 0x89A5, + 0x58BB, 0x89A6, 0x58BC, 0xDBD4, 0x58BD, 0x89A7, 0x58BE, 0x89A8, 0x58BF, 0x89A9, 0x58C0, 0x89AA, 0x58C1, 0xB1DA, 0x58C2, 0x89AB, + 0x58C3, 0x89AC, 0x58C4, 0x89AD, 0x58C5, 0xDBD5, 0x58C6, 0x89AE, 0x58C7, 0x89AF, 0x58C8, 0x89B0, 0x58C9, 0x89B1, 0x58CA, 0x89B2, + 0x58CB, 0x89B3, 0x58CC, 0x89B4, 0x58CD, 0x89B5, 0x58CE, 0x89B6, 0x58CF, 0x89B7, 0x58D0, 0x89B8, 0x58D1, 0xDBD6, 0x58D2, 0x89B9, + 0x58D3, 0x89BA, 0x58D4, 0x89BB, 0x58D5, 0xBABE, 0x58D6, 0x89BC, 0x58D7, 0x89BD, 0x58D8, 0x89BE, 0x58D9, 0x89BF, 0x58DA, 0x89C0, + 0x58DB, 0x89C1, 0x58DC, 0x89C2, 0x58DD, 0x89C3, 0x58DE, 0x89C4, 0x58DF, 0x89C5, 0x58E0, 0x89C6, 0x58E1, 0x89C7, 0x58E2, 0x89C8, + 0x58E3, 0x89C9, 0x58E4, 0xC8C0, 0x58E5, 0x89CA, 0x58E6, 0x89CB, 0x58E7, 0x89CC, 0x58E8, 0x89CD, 0x58E9, 0x89CE, 0x58EA, 0x89CF, + 0x58EB, 0xCABF, 0x58EC, 0xC8C9, 0x58ED, 0x89D0, 0x58EE, 0xD7B3, 0x58EF, 0x89D1, 0x58F0, 0xC9F9, 0x58F1, 0x89D2, 0x58F2, 0x89D3, + 0x58F3, 0xBFC7, 0x58F4, 0x89D4, 0x58F5, 0x89D5, 0x58F6, 0xBAF8, 0x58F7, 0x89D6, 0x58F8, 0x89D7, 0x58F9, 0xD2BC, 0x58FA, 0x89D8, + 0x58FB, 0x89D9, 0x58FC, 0x89DA, 0x58FD, 0x89DB, 0x58FE, 0x89DC, 0x58FF, 0x89DD, 0x5900, 0x89DE, 0x5901, 0x89DF, 0x5902, 0xE2BA, + 0x5903, 0x89E0, 0x5904, 0xB4A6, 0x5905, 0x89E1, 0x5906, 0x89E2, 0x5907, 0xB1B8, 0x5908, 0x89E3, 0x5909, 0x89E4, 0x590A, 0x89E5, + 0x590B, 0x89E6, 0x590C, 0x89E7, 0x590D, 0xB8B4, 0x590E, 0x89E8, 0x590F, 0xCFC4, 0x5910, 0x89E9, 0x5911, 0x89EA, 0x5912, 0x89EB, + 0x5913, 0x89EC, 0x5914, 0xD9E7, 0x5915, 0xCFA6, 0x5916, 0xCDE2, 0x5917, 0x89ED, 0x5918, 0x89EE, 0x5919, 0xD9ED, 0x591A, 0xB6E0, + 0x591B, 0x89EF, 0x591C, 0xD2B9, 0x591D, 0x89F0, 0x591E, 0x89F1, 0x591F, 0xB9BB, 0x5920, 0x89F2, 0x5921, 0x89F3, 0x5922, 0x89F4, + 0x5923, 0x89F5, 0x5924, 0xE2B9, 0x5925, 0xE2B7, 0x5926, 0x89F6, 0x5927, 0xB4F3, 0x5928, 0x89F7, 0x5929, 0xCCEC, 0x592A, 0xCCAB, + 0x592B, 0xB7F2, 0x592C, 0x89F8, 0x592D, 0xD8B2, 0x592E, 0xD1EB, 0x592F, 0xBABB, 0x5930, 0x89F9, 0x5931, 0xCAA7, 0x5932, 0x89FA, + 0x5933, 0x89FB, 0x5934, 0xCDB7, 0x5935, 0x89FC, 0x5936, 0x89FD, 0x5937, 0xD2C4, 0x5938, 0xBFE4, 0x5939, 0xBCD0, 0x593A, 0xB6E1, + 0x593B, 0x89FE, 0x593C, 0xDEC5, 0x593D, 0x8A40, 0x593E, 0x8A41, 0x593F, 0x8A42, 0x5940, 0x8A43, 0x5941, 0xDEC6, 0x5942, 0xDBBC, + 0x5943, 0x8A44, 0x5944, 0xD1D9, 0x5945, 0x8A45, 0x5946, 0x8A46, 0x5947, 0xC6E6, 0x5948, 0xC4CE, 0x5949, 0xB7EE, 0x594A, 0x8A47, + 0x594B, 0xB7DC, 0x594C, 0x8A48, 0x594D, 0x8A49, 0x594E, 0xBFFC, 0x594F, 0xD7E0, 0x5950, 0x8A4A, 0x5951, 0xC6F5, 0x5952, 0x8A4B, + 0x5953, 0x8A4C, 0x5954, 0xB1BC, 0x5955, 0xDEC8, 0x5956, 0xBDB1, 0x5957, 0xCCD7, 0x5958, 0xDECA, 0x5959, 0x8A4D, 0x595A, 0xDEC9, + 0x595B, 0x8A4E, 0x595C, 0x8A4F, 0x595D, 0x8A50, 0x595E, 0x8A51, 0x595F, 0x8A52, 0x5960, 0xB5EC, 0x5961, 0x8A53, 0x5962, 0xC9DD, + 0x5963, 0x8A54, 0x5964, 0x8A55, 0x5965, 0xB0C2, 0x5966, 0x8A56, 0x5967, 0x8A57, 0x5968, 0x8A58, 0x5969, 0x8A59, 0x596A, 0x8A5A, + 0x596B, 0x8A5B, 0x596C, 0x8A5C, 0x596D, 0x8A5D, 0x596E, 0x8A5E, 0x596F, 0x8A5F, 0x5970, 0x8A60, 0x5971, 0x8A61, 0x5972, 0x8A62, + 0x5973, 0xC5AE, 0x5974, 0xC5AB, 0x5975, 0x8A63, 0x5976, 0xC4CC, 0x5977, 0x8A64, 0x5978, 0xBCE9, 0x5979, 0xCBFD, 0x597A, 0x8A65, + 0x597B, 0x8A66, 0x597C, 0x8A67, 0x597D, 0xBAC3, 0x597E, 0x8A68, 0x597F, 0x8A69, 0x5980, 0x8A6A, 0x5981, 0xE5F9, 0x5982, 0xC8E7, + 0x5983, 0xE5FA, 0x5984, 0xCDFD, 0x5985, 0x8A6B, 0x5986, 0xD7B1, 0x5987, 0xB8BE, 0x5988, 0xC2E8, 0x5989, 0x8A6C, 0x598A, 0xC8D1, + 0x598B, 0x8A6D, 0x598C, 0x8A6E, 0x598D, 0xE5FB, 0x598E, 0x8A6F, 0x598F, 0x8A70, 0x5990, 0x8A71, 0x5991, 0x8A72, 0x5992, 0xB6CA, + 0x5993, 0xBCCB, 0x5994, 0x8A73, 0x5995, 0x8A74, 0x5996, 0xD1FD, 0x5997, 0xE6A1, 0x5998, 0x8A75, 0x5999, 0xC3EE, 0x599A, 0x8A76, + 0x599B, 0x8A77, 0x599C, 0x8A78, 0x599D, 0x8A79, 0x599E, 0xE6A4, 0x599F, 0x8A7A, 0x59A0, 0x8A7B, 0x59A1, 0x8A7C, 0x59A2, 0x8A7D, + 0x59A3, 0xE5FE, 0x59A4, 0xE6A5, 0x59A5, 0xCDD7, 0x59A6, 0x8A7E, 0x59A7, 0x8A80, 0x59A8, 0xB7C1, 0x59A9, 0xE5FC, 0x59AA, 0xE5FD, + 0x59AB, 0xE6A3, 0x59AC, 0x8A81, 0x59AD, 0x8A82, 0x59AE, 0xC4DD, 0x59AF, 0xE6A8, 0x59B0, 0x8A83, 0x59B1, 0x8A84, 0x59B2, 0xE6A7, + 0x59B3, 0x8A85, 0x59B4, 0x8A86, 0x59B5, 0x8A87, 0x59B6, 0x8A88, 0x59B7, 0x8A89, 0x59B8, 0x8A8A, 0x59B9, 0xC3C3, 0x59BA, 0x8A8B, + 0x59BB, 0xC6DE, 0x59BC, 0x8A8C, 0x59BD, 0x8A8D, 0x59BE, 0xE6AA, 0x59BF, 0x8A8E, 0x59C0, 0x8A8F, 0x59C1, 0x8A90, 0x59C2, 0x8A91, + 0x59C3, 0x8A92, 0x59C4, 0x8A93, 0x59C5, 0x8A94, 0x59C6, 0xC4B7, 0x59C7, 0x8A95, 0x59C8, 0x8A96, 0x59C9, 0x8A97, 0x59CA, 0xE6A2, + 0x59CB, 0xCABC, 0x59CC, 0x8A98, 0x59CD, 0x8A99, 0x59CE, 0x8A9A, 0x59CF, 0x8A9B, 0x59D0, 0xBDE3, 0x59D1, 0xB9C3, 0x59D2, 0xE6A6, + 0x59D3, 0xD0D5, 0x59D4, 0xCEAF, 0x59D5, 0x8A9C, 0x59D6, 0x8A9D, 0x59D7, 0xE6A9, 0x59D8, 0xE6B0, 0x59D9, 0x8A9E, 0x59DA, 0xD2A6, + 0x59DB, 0x8A9F, 0x59DC, 0xBDAA, 0x59DD, 0xE6AD, 0x59DE, 0x8AA0, 0x59DF, 0x8AA1, 0x59E0, 0x8AA2, 0x59E1, 0x8AA3, 0x59E2, 0x8AA4, + 0x59E3, 0xE6AF, 0x59E4, 0x8AA5, 0x59E5, 0xC0D1, 0x59E6, 0x8AA6, 0x59E7, 0x8AA7, 0x59E8, 0xD2CC, 0x59E9, 0x8AA8, 0x59EA, 0x8AA9, + 0x59EB, 0x8AAA, 0x59EC, 0xBCA7, 0x59ED, 0x8AAB, 0x59EE, 0x8AAC, 0x59EF, 0x8AAD, 0x59F0, 0x8AAE, 0x59F1, 0x8AAF, 0x59F2, 0x8AB0, + 0x59F3, 0x8AB1, 0x59F4, 0x8AB2, 0x59F5, 0x8AB3, 0x59F6, 0x8AB4, 0x59F7, 0x8AB5, 0x59F8, 0x8AB6, 0x59F9, 0xE6B1, 0x59FA, 0x8AB7, + 0x59FB, 0xD2F6, 0x59FC, 0x8AB8, 0x59FD, 0x8AB9, 0x59FE, 0x8ABA, 0x59FF, 0xD7CB, 0x5A00, 0x8ABB, 0x5A01, 0xCDFE, 0x5A02, 0x8ABC, + 0x5A03, 0xCDDE, 0x5A04, 0xC2A6, 0x5A05, 0xE6AB, 0x5A06, 0xE6AC, 0x5A07, 0xBDBF, 0x5A08, 0xE6AE, 0x5A09, 0xE6B3, 0x5A0A, 0x8ABD, + 0x5A0B, 0x8ABE, 0x5A0C, 0xE6B2, 0x5A0D, 0x8ABF, 0x5A0E, 0x8AC0, 0x5A0F, 0x8AC1, 0x5A10, 0x8AC2, 0x5A11, 0xE6B6, 0x5A12, 0x8AC3, + 0x5A13, 0xE6B8, 0x5A14, 0x8AC4, 0x5A15, 0x8AC5, 0x5A16, 0x8AC6, 0x5A17, 0x8AC7, 0x5A18, 0xC4EF, 0x5A19, 0x8AC8, 0x5A1A, 0x8AC9, + 0x5A1B, 0x8ACA, 0x5A1C, 0xC4C8, 0x5A1D, 0x8ACB, 0x5A1E, 0x8ACC, 0x5A1F, 0xBEEA, 0x5A20, 0xC9EF, 0x5A21, 0x8ACD, 0x5A22, 0x8ACE, + 0x5A23, 0xE6B7, 0x5A24, 0x8ACF, 0x5A25, 0xB6F0, 0x5A26, 0x8AD0, 0x5A27, 0x8AD1, 0x5A28, 0x8AD2, 0x5A29, 0xC3E4, 0x5A2A, 0x8AD3, + 0x5A2B, 0x8AD4, 0x5A2C, 0x8AD5, 0x5A2D, 0x8AD6, 0x5A2E, 0x8AD7, 0x5A2F, 0x8AD8, 0x5A30, 0x8AD9, 0x5A31, 0xD3E9, 0x5A32, 0xE6B4, + 0x5A33, 0x8ADA, 0x5A34, 0xE6B5, 0x5A35, 0x8ADB, 0x5A36, 0xC8A2, 0x5A37, 0x8ADC, 0x5A38, 0x8ADD, 0x5A39, 0x8ADE, 0x5A3A, 0x8ADF, + 0x5A3B, 0x8AE0, 0x5A3C, 0xE6BD, 0x5A3D, 0x8AE1, 0x5A3E, 0x8AE2, 0x5A3F, 0x8AE3, 0x5A40, 0xE6B9, 0x5A41, 0x8AE4, 0x5A42, 0x8AE5, + 0x5A43, 0x8AE6, 0x5A44, 0x8AE7, 0x5A45, 0x8AE8, 0x5A46, 0xC6C5, 0x5A47, 0x8AE9, 0x5A48, 0x8AEA, 0x5A49, 0xCDF1, 0x5A4A, 0xE6BB, + 0x5A4B, 0x8AEB, 0x5A4C, 0x8AEC, 0x5A4D, 0x8AED, 0x5A4E, 0x8AEE, 0x5A4F, 0x8AEF, 0x5A50, 0x8AF0, 0x5A51, 0x8AF1, 0x5A52, 0x8AF2, + 0x5A53, 0x8AF3, 0x5A54, 0x8AF4, 0x5A55, 0xE6BC, 0x5A56, 0x8AF5, 0x5A57, 0x8AF6, 0x5A58, 0x8AF7, 0x5A59, 0x8AF8, 0x5A5A, 0xBBE9, + 0x5A5B, 0x8AF9, 0x5A5C, 0x8AFA, 0x5A5D, 0x8AFB, 0x5A5E, 0x8AFC, 0x5A5F, 0x8AFD, 0x5A60, 0x8AFE, 0x5A61, 0x8B40, 0x5A62, 0xE6BE, + 0x5A63, 0x8B41, 0x5A64, 0x8B42, 0x5A65, 0x8B43, 0x5A66, 0x8B44, 0x5A67, 0xE6BA, 0x5A68, 0x8B45, 0x5A69, 0x8B46, 0x5A6A, 0xC0B7, + 0x5A6B, 0x8B47, 0x5A6C, 0x8B48, 0x5A6D, 0x8B49, 0x5A6E, 0x8B4A, 0x5A6F, 0x8B4B, 0x5A70, 0x8B4C, 0x5A71, 0x8B4D, 0x5A72, 0x8B4E, + 0x5A73, 0x8B4F, 0x5A74, 0xD3A4, 0x5A75, 0xE6BF, 0x5A76, 0xC9F4, 0x5A77, 0xE6C3, 0x5A78, 0x8B50, 0x5A79, 0x8B51, 0x5A7A, 0xE6C4, + 0x5A7B, 0x8B52, 0x5A7C, 0x8B53, 0x5A7D, 0x8B54, 0x5A7E, 0x8B55, 0x5A7F, 0xD0F6, 0x5A80, 0x8B56, 0x5A81, 0x8B57, 0x5A82, 0x8B58, + 0x5A83, 0x8B59, 0x5A84, 0x8B5A, 0x5A85, 0x8B5B, 0x5A86, 0x8B5C, 0x5A87, 0x8B5D, 0x5A88, 0x8B5E, 0x5A89, 0x8B5F, 0x5A8A, 0x8B60, + 0x5A8B, 0x8B61, 0x5A8C, 0x8B62, 0x5A8D, 0x8B63, 0x5A8E, 0x8B64, 0x5A8F, 0x8B65, 0x5A90, 0x8B66, 0x5A91, 0x8B67, 0x5A92, 0xC3BD, + 0x5A93, 0x8B68, 0x5A94, 0x8B69, 0x5A95, 0x8B6A, 0x5A96, 0x8B6B, 0x5A97, 0x8B6C, 0x5A98, 0x8B6D, 0x5A99, 0x8B6E, 0x5A9A, 0xC3C4, + 0x5A9B, 0xE6C2, 0x5A9C, 0x8B6F, 0x5A9D, 0x8B70, 0x5A9E, 0x8B71, 0x5A9F, 0x8B72, 0x5AA0, 0x8B73, 0x5AA1, 0x8B74, 0x5AA2, 0x8B75, + 0x5AA3, 0x8B76, 0x5AA4, 0x8B77, 0x5AA5, 0x8B78, 0x5AA6, 0x8B79, 0x5AA7, 0x8B7A, 0x5AA8, 0x8B7B, 0x5AA9, 0x8B7C, 0x5AAA, 0xE6C1, + 0x5AAB, 0x8B7D, 0x5AAC, 0x8B7E, 0x5AAD, 0x8B80, 0x5AAE, 0x8B81, 0x5AAF, 0x8B82, 0x5AB0, 0x8B83, 0x5AB1, 0x8B84, 0x5AB2, 0xE6C7, + 0x5AB3, 0xCFB1, 0x5AB4, 0x8B85, 0x5AB5, 0xEBF4, 0x5AB6, 0x8B86, 0x5AB7, 0x8B87, 0x5AB8, 0xE6CA, 0x5AB9, 0x8B88, 0x5ABA, 0x8B89, + 0x5ABB, 0x8B8A, 0x5ABC, 0x8B8B, 0x5ABD, 0x8B8C, 0x5ABE, 0xE6C5, 0x5ABF, 0x8B8D, 0x5AC0, 0x8B8E, 0x5AC1, 0xBCDE, 0x5AC2, 0xC9A9, + 0x5AC3, 0x8B8F, 0x5AC4, 0x8B90, 0x5AC5, 0x8B91, 0x5AC6, 0x8B92, 0x5AC7, 0x8B93, 0x5AC8, 0x8B94, 0x5AC9, 0xBCB5, 0x5ACA, 0x8B95, + 0x5ACB, 0x8B96, 0x5ACC, 0xCFD3, 0x5ACD, 0x8B97, 0x5ACE, 0x8B98, 0x5ACF, 0x8B99, 0x5AD0, 0x8B9A, 0x5AD1, 0x8B9B, 0x5AD2, 0xE6C8, + 0x5AD3, 0x8B9C, 0x5AD4, 0xE6C9, 0x5AD5, 0x8B9D, 0x5AD6, 0xE6CE, 0x5AD7, 0x8B9E, 0x5AD8, 0xE6D0, 0x5AD9, 0x8B9F, 0x5ADA, 0x8BA0, + 0x5ADB, 0x8BA1, 0x5ADC, 0xE6D1, 0x5ADD, 0x8BA2, 0x5ADE, 0x8BA3, 0x5ADF, 0x8BA4, 0x5AE0, 0xE6CB, 0x5AE1, 0xB5D5, 0x5AE2, 0x8BA5, + 0x5AE3, 0xE6CC, 0x5AE4, 0x8BA6, 0x5AE5, 0x8BA7, 0x5AE6, 0xE6CF, 0x5AE7, 0x8BA8, 0x5AE8, 0x8BA9, 0x5AE9, 0xC4DB, 0x5AEA, 0x8BAA, + 0x5AEB, 0xE6C6, 0x5AEC, 0x8BAB, 0x5AED, 0x8BAC, 0x5AEE, 0x8BAD, 0x5AEF, 0x8BAE, 0x5AF0, 0x8BAF, 0x5AF1, 0xE6CD, 0x5AF2, 0x8BB0, + 0x5AF3, 0x8BB1, 0x5AF4, 0x8BB2, 0x5AF5, 0x8BB3, 0x5AF6, 0x8BB4, 0x5AF7, 0x8BB5, 0x5AF8, 0x8BB6, 0x5AF9, 0x8BB7, 0x5AFA, 0x8BB8, + 0x5AFB, 0x8BB9, 0x5AFC, 0x8BBA, 0x5AFD, 0x8BBB, 0x5AFE, 0x8BBC, 0x5AFF, 0x8BBD, 0x5B00, 0x8BBE, 0x5B01, 0x8BBF, 0x5B02, 0x8BC0, + 0x5B03, 0x8BC1, 0x5B04, 0x8BC2, 0x5B05, 0x8BC3, 0x5B06, 0x8BC4, 0x5B07, 0x8BC5, 0x5B08, 0x8BC6, 0x5B09, 0xE6D2, 0x5B0A, 0x8BC7, + 0x5B0B, 0x8BC8, 0x5B0C, 0x8BC9, 0x5B0D, 0x8BCA, 0x5B0E, 0x8BCB, 0x5B0F, 0x8BCC, 0x5B10, 0x8BCD, 0x5B11, 0x8BCE, 0x5B12, 0x8BCF, + 0x5B13, 0x8BD0, 0x5B14, 0x8BD1, 0x5B15, 0x8BD2, 0x5B16, 0xE6D4, 0x5B17, 0xE6D3, 0x5B18, 0x8BD3, 0x5B19, 0x8BD4, 0x5B1A, 0x8BD5, + 0x5B1B, 0x8BD6, 0x5B1C, 0x8BD7, 0x5B1D, 0x8BD8, 0x5B1E, 0x8BD9, 0x5B1F, 0x8BDA, 0x5B20, 0x8BDB, 0x5B21, 0x8BDC, 0x5B22, 0x8BDD, + 0x5B23, 0x8BDE, 0x5B24, 0x8BDF, 0x5B25, 0x8BE0, 0x5B26, 0x8BE1, 0x5B27, 0x8BE2, 0x5B28, 0x8BE3, 0x5B29, 0x8BE4, 0x5B2A, 0x8BE5, + 0x5B2B, 0x8BE6, 0x5B2C, 0x8BE7, 0x5B2D, 0x8BE8, 0x5B2E, 0x8BE9, 0x5B2F, 0x8BEA, 0x5B30, 0x8BEB, 0x5B31, 0x8BEC, 0x5B32, 0xE6D5, + 0x5B33, 0x8BED, 0x5B34, 0xD9F8, 0x5B35, 0x8BEE, 0x5B36, 0x8BEF, 0x5B37, 0xE6D6, 0x5B38, 0x8BF0, 0x5B39, 0x8BF1, 0x5B3A, 0x8BF2, + 0x5B3B, 0x8BF3, 0x5B3C, 0x8BF4, 0x5B3D, 0x8BF5, 0x5B3E, 0x8BF6, 0x5B3F, 0x8BF7, 0x5B40, 0xE6D7, 0x5B41, 0x8BF8, 0x5B42, 0x8BF9, + 0x5B43, 0x8BFA, 0x5B44, 0x8BFB, 0x5B45, 0x8BFC, 0x5B46, 0x8BFD, 0x5B47, 0x8BFE, 0x5B48, 0x8C40, 0x5B49, 0x8C41, 0x5B4A, 0x8C42, + 0x5B4B, 0x8C43, 0x5B4C, 0x8C44, 0x5B4D, 0x8C45, 0x5B4E, 0x8C46, 0x5B4F, 0x8C47, 0x5B50, 0xD7D3, 0x5B51, 0xE6DD, 0x5B52, 0x8C48, + 0x5B53, 0xE6DE, 0x5B54, 0xBFD7, 0x5B55, 0xD4D0, 0x5B56, 0x8C49, 0x5B57, 0xD7D6, 0x5B58, 0xB4E6, 0x5B59, 0xCBEF, 0x5B5A, 0xE6DA, + 0x5B5B, 0xD8C3, 0x5B5C, 0xD7CE, 0x5B5D, 0xD0A2, 0x5B5E, 0x8C4A, 0x5B5F, 0xC3CF, 0x5B60, 0x8C4B, 0x5B61, 0x8C4C, 0x5B62, 0xE6DF, + 0x5B63, 0xBCBE, 0x5B64, 0xB9C2, 0x5B65, 0xE6DB, 0x5B66, 0xD1A7, 0x5B67, 0x8C4D, 0x5B68, 0x8C4E, 0x5B69, 0xBAA2, 0x5B6A, 0xC2CF, + 0x5B6B, 0x8C4F, 0x5B6C, 0xD8AB, 0x5B6D, 0x8C50, 0x5B6E, 0x8C51, 0x5B6F, 0x8C52, 0x5B70, 0xCAEB, 0x5B71, 0xE5EE, 0x5B72, 0x8C53, + 0x5B73, 0xE6DC, 0x5B74, 0x8C54, 0x5B75, 0xB7F5, 0x5B76, 0x8C55, 0x5B77, 0x8C56, 0x5B78, 0x8C57, 0x5B79, 0x8C58, 0x5B7A, 0xC8E6, + 0x5B7B, 0x8C59, 0x5B7C, 0x8C5A, 0x5B7D, 0xC4F5, 0x5B7E, 0x8C5B, 0x5B7F, 0x8C5C, 0x5B80, 0xE5B2, 0x5B81, 0xC4FE, 0x5B82, 0x8C5D, + 0x5B83, 0xCBFC, 0x5B84, 0xE5B3, 0x5B85, 0xD5AC, 0x5B86, 0x8C5E, 0x5B87, 0xD3EE, 0x5B88, 0xCAD8, 0x5B89, 0xB0B2, 0x5B8A, 0x8C5F, + 0x5B8B, 0xCBCE, 0x5B8C, 0xCDEA, 0x5B8D, 0x8C60, 0x5B8E, 0x8C61, 0x5B8F, 0xBAEA, 0x5B90, 0x8C62, 0x5B91, 0x8C63, 0x5B92, 0x8C64, + 0x5B93, 0xE5B5, 0x5B94, 0x8C65, 0x5B95, 0xE5B4, 0x5B96, 0x8C66, 0x5B97, 0xD7DA, 0x5B98, 0xB9D9, 0x5B99, 0xD6E6, 0x5B9A, 0xB6A8, + 0x5B9B, 0xCDF0, 0x5B9C, 0xD2CB, 0x5B9D, 0xB1A6, 0x5B9E, 0xCAB5, 0x5B9F, 0x8C67, 0x5BA0, 0xB3E8, 0x5BA1, 0xC9F3, 0x5BA2, 0xBFCD, + 0x5BA3, 0xD0FB, 0x5BA4, 0xCAD2, 0x5BA5, 0xE5B6, 0x5BA6, 0xBBC2, 0x5BA7, 0x8C68, 0x5BA8, 0x8C69, 0x5BA9, 0x8C6A, 0x5BAA, 0xCFDC, + 0x5BAB, 0xB9AC, 0x5BAC, 0x8C6B, 0x5BAD, 0x8C6C, 0x5BAE, 0x8C6D, 0x5BAF, 0x8C6E, 0x5BB0, 0xD4D7, 0x5BB1, 0x8C6F, 0x5BB2, 0x8C70, + 0x5BB3, 0xBAA6, 0x5BB4, 0xD1E7, 0x5BB5, 0xCFFC, 0x5BB6, 0xBCD2, 0x5BB7, 0x8C71, 0x5BB8, 0xE5B7, 0x5BB9, 0xC8DD, 0x5BBA, 0x8C72, + 0x5BBB, 0x8C73, 0x5BBC, 0x8C74, 0x5BBD, 0xBFED, 0x5BBE, 0xB1F6, 0x5BBF, 0xCBDE, 0x5BC0, 0x8C75, 0x5BC1, 0x8C76, 0x5BC2, 0xBCC5, + 0x5BC3, 0x8C77, 0x5BC4, 0xBCC4, 0x5BC5, 0xD2FA, 0x5BC6, 0xC3DC, 0x5BC7, 0xBFDC, 0x5BC8, 0x8C78, 0x5BC9, 0x8C79, 0x5BCA, 0x8C7A, + 0x5BCB, 0x8C7B, 0x5BCC, 0xB8BB, 0x5BCD, 0x8C7C, 0x5BCE, 0x8C7D, 0x5BCF, 0x8C7E, 0x5BD0, 0xC3C2, 0x5BD1, 0x8C80, 0x5BD2, 0xBAAE, + 0x5BD3, 0xD4A2, 0x5BD4, 0x8C81, 0x5BD5, 0x8C82, 0x5BD6, 0x8C83, 0x5BD7, 0x8C84, 0x5BD8, 0x8C85, 0x5BD9, 0x8C86, 0x5BDA, 0x8C87, + 0x5BDB, 0x8C88, 0x5BDC, 0x8C89, 0x5BDD, 0xC7DE, 0x5BDE, 0xC4AF, 0x5BDF, 0xB2EC, 0x5BE0, 0x8C8A, 0x5BE1, 0xB9D1, 0x5BE2, 0x8C8B, + 0x5BE3, 0x8C8C, 0x5BE4, 0xE5BB, 0x5BE5, 0xC1C8, 0x5BE6, 0x8C8D, 0x5BE7, 0x8C8E, 0x5BE8, 0xD5AF, 0x5BE9, 0x8C8F, 0x5BEA, 0x8C90, + 0x5BEB, 0x8C91, 0x5BEC, 0x8C92, 0x5BED, 0x8C93, 0x5BEE, 0xE5BC, 0x5BEF, 0x8C94, 0x5BF0, 0xE5BE, 0x5BF1, 0x8C95, 0x5BF2, 0x8C96, + 0x5BF3, 0x8C97, 0x5BF4, 0x8C98, 0x5BF5, 0x8C99, 0x5BF6, 0x8C9A, 0x5BF7, 0x8C9B, 0x5BF8, 0xB4E7, 0x5BF9, 0xB6D4, 0x5BFA, 0xCBC2, + 0x5BFB, 0xD1B0, 0x5BFC, 0xB5BC, 0x5BFD, 0x8C9C, 0x5BFE, 0x8C9D, 0x5BFF, 0xCAD9, 0x5C00, 0x8C9E, 0x5C01, 0xB7E2, 0x5C02, 0x8C9F, + 0x5C03, 0x8CA0, 0x5C04, 0xC9E4, 0x5C05, 0x8CA1, 0x5C06, 0xBDAB, 0x5C07, 0x8CA2, 0x5C08, 0x8CA3, 0x5C09, 0xCEBE, 0x5C0A, 0xD7F0, + 0x5C0B, 0x8CA4, 0x5C0C, 0x8CA5, 0x5C0D, 0x8CA6, 0x5C0E, 0x8CA7, 0x5C0F, 0xD0A1, 0x5C10, 0x8CA8, 0x5C11, 0xC9D9, 0x5C12, 0x8CA9, + 0x5C13, 0x8CAA, 0x5C14, 0xB6FB, 0x5C15, 0xE6D8, 0x5C16, 0xBCE2, 0x5C17, 0x8CAB, 0x5C18, 0xB3BE, 0x5C19, 0x8CAC, 0x5C1A, 0xC9D0, + 0x5C1B, 0x8CAD, 0x5C1C, 0xE6D9, 0x5C1D, 0xB3A2, 0x5C1E, 0x8CAE, 0x5C1F, 0x8CAF, 0x5C20, 0x8CB0, 0x5C21, 0x8CB1, 0x5C22, 0xDECC, + 0x5C23, 0x8CB2, 0x5C24, 0xD3C8, 0x5C25, 0xDECD, 0x5C26, 0x8CB3, 0x5C27, 0xD2A2, 0x5C28, 0x8CB4, 0x5C29, 0x8CB5, 0x5C2A, 0x8CB6, + 0x5C2B, 0x8CB7, 0x5C2C, 0xDECE, 0x5C2D, 0x8CB8, 0x5C2E, 0x8CB9, 0x5C2F, 0x8CBA, 0x5C30, 0x8CBB, 0x5C31, 0xBECD, 0x5C32, 0x8CBC, + 0x5C33, 0x8CBD, 0x5C34, 0xDECF, 0x5C35, 0x8CBE, 0x5C36, 0x8CBF, 0x5C37, 0x8CC0, 0x5C38, 0xCAAC, 0x5C39, 0xD2FC, 0x5C3A, 0xB3DF, + 0x5C3B, 0xE5EA, 0x5C3C, 0xC4E1, 0x5C3D, 0xBEA1, 0x5C3E, 0xCEB2, 0x5C3F, 0xC4F2, 0x5C40, 0xBED6, 0x5C41, 0xC6A8, 0x5C42, 0xB2E3, + 0x5C43, 0x8CC1, 0x5C44, 0x8CC2, 0x5C45, 0xBED3, 0x5C46, 0x8CC3, 0x5C47, 0x8CC4, 0x5C48, 0xC7FC, 0x5C49, 0xCCEB, 0x5C4A, 0xBDEC, + 0x5C4B, 0xCEDD, 0x5C4C, 0x8CC5, 0x5C4D, 0x8CC6, 0x5C4E, 0xCABA, 0x5C4F, 0xC6C1, 0x5C50, 0xE5EC, 0x5C51, 0xD0BC, 0x5C52, 0x8CC7, + 0x5C53, 0x8CC8, 0x5C54, 0x8CC9, 0x5C55, 0xD5B9, 0x5C56, 0x8CCA, 0x5C57, 0x8CCB, 0x5C58, 0x8CCC, 0x5C59, 0xE5ED, 0x5C5A, 0x8CCD, + 0x5C5B, 0x8CCE, 0x5C5C, 0x8CCF, 0x5C5D, 0x8CD0, 0x5C5E, 0xCAF4, 0x5C5F, 0x8CD1, 0x5C60, 0xCDC0, 0x5C61, 0xC2C5, 0x5C62, 0x8CD2, + 0x5C63, 0xE5EF, 0x5C64, 0x8CD3, 0x5C65, 0xC2C4, 0x5C66, 0xE5F0, 0x5C67, 0x8CD4, 0x5C68, 0x8CD5, 0x5C69, 0x8CD6, 0x5C6A, 0x8CD7, + 0x5C6B, 0x8CD8, 0x5C6C, 0x8CD9, 0x5C6D, 0x8CDA, 0x5C6E, 0xE5F8, 0x5C6F, 0xCDCD, 0x5C70, 0x8CDB, 0x5C71, 0xC9BD, 0x5C72, 0x8CDC, + 0x5C73, 0x8CDD, 0x5C74, 0x8CDE, 0x5C75, 0x8CDF, 0x5C76, 0x8CE0, 0x5C77, 0x8CE1, 0x5C78, 0x8CE2, 0x5C79, 0xD2D9, 0x5C7A, 0xE1A8, + 0x5C7B, 0x8CE3, 0x5C7C, 0x8CE4, 0x5C7D, 0x8CE5, 0x5C7E, 0x8CE6, 0x5C7F, 0xD3EC, 0x5C80, 0x8CE7, 0x5C81, 0xCBEA, 0x5C82, 0xC6F1, + 0x5C83, 0x8CE8, 0x5C84, 0x8CE9, 0x5C85, 0x8CEA, 0x5C86, 0x8CEB, 0x5C87, 0x8CEC, 0x5C88, 0xE1AC, 0x5C89, 0x8CED, 0x5C8A, 0x8CEE, + 0x5C8B, 0x8CEF, 0x5C8C, 0xE1A7, 0x5C8D, 0xE1A9, 0x5C8E, 0x8CF0, 0x5C8F, 0x8CF1, 0x5C90, 0xE1AA, 0x5C91, 0xE1AF, 0x5C92, 0x8CF2, + 0x5C93, 0x8CF3, 0x5C94, 0xB2ED, 0x5C95, 0x8CF4, 0x5C96, 0xE1AB, 0x5C97, 0xB8DA, 0x5C98, 0xE1AD, 0x5C99, 0xE1AE, 0x5C9A, 0xE1B0, + 0x5C9B, 0xB5BA, 0x5C9C, 0xE1B1, 0x5C9D, 0x8CF5, 0x5C9E, 0x8CF6, 0x5C9F, 0x8CF7, 0x5CA0, 0x8CF8, 0x5CA1, 0x8CF9, 0x5CA2, 0xE1B3, + 0x5CA3, 0xE1B8, 0x5CA4, 0x8CFA, 0x5CA5, 0x8CFB, 0x5CA6, 0x8CFC, 0x5CA7, 0x8CFD, 0x5CA8, 0x8CFE, 0x5CA9, 0xD1D2, 0x5CAA, 0x8D40, + 0x5CAB, 0xE1B6, 0x5CAC, 0xE1B5, 0x5CAD, 0xC1EB, 0x5CAE, 0x8D41, 0x5CAF, 0x8D42, 0x5CB0, 0x8D43, 0x5CB1, 0xE1B7, 0x5CB2, 0x8D44, + 0x5CB3, 0xD4C0, 0x5CB4, 0x8D45, 0x5CB5, 0xE1B2, 0x5CB6, 0x8D46, 0x5CB7, 0xE1BA, 0x5CB8, 0xB0B6, 0x5CB9, 0x8D47, 0x5CBA, 0x8D48, + 0x5CBB, 0x8D49, 0x5CBC, 0x8D4A, 0x5CBD, 0xE1B4, 0x5CBE, 0x8D4B, 0x5CBF, 0xBFF9, 0x5CC0, 0x8D4C, 0x5CC1, 0xE1B9, 0x5CC2, 0x8D4D, + 0x5CC3, 0x8D4E, 0x5CC4, 0xE1BB, 0x5CC5, 0x8D4F, 0x5CC6, 0x8D50, 0x5CC7, 0x8D51, 0x5CC8, 0x8D52, 0x5CC9, 0x8D53, 0x5CCA, 0x8D54, + 0x5CCB, 0xE1BE, 0x5CCC, 0x8D55, 0x5CCD, 0x8D56, 0x5CCE, 0x8D57, 0x5CCF, 0x8D58, 0x5CD0, 0x8D59, 0x5CD1, 0x8D5A, 0x5CD2, 0xE1BC, + 0x5CD3, 0x8D5B, 0x5CD4, 0x8D5C, 0x5CD5, 0x8D5D, 0x5CD6, 0x8D5E, 0x5CD7, 0x8D5F, 0x5CD8, 0x8D60, 0x5CD9, 0xD6C5, 0x5CDA, 0x8D61, + 0x5CDB, 0x8D62, 0x5CDC, 0x8D63, 0x5CDD, 0x8D64, 0x5CDE, 0x8D65, 0x5CDF, 0x8D66, 0x5CE0, 0x8D67, 0x5CE1, 0xCFBF, 0x5CE2, 0x8D68, + 0x5CE3, 0x8D69, 0x5CE4, 0xE1BD, 0x5CE5, 0xE1BF, 0x5CE6, 0xC2CD, 0x5CE7, 0x8D6A, 0x5CE8, 0xB6EB, 0x5CE9, 0x8D6B, 0x5CEA, 0xD3F8, + 0x5CEB, 0x8D6C, 0x5CEC, 0x8D6D, 0x5CED, 0xC7CD, 0x5CEE, 0x8D6E, 0x5CEF, 0x8D6F, 0x5CF0, 0xB7E5, 0x5CF1, 0x8D70, 0x5CF2, 0x8D71, + 0x5CF3, 0x8D72, 0x5CF4, 0x8D73, 0x5CF5, 0x8D74, 0x5CF6, 0x8D75, 0x5CF7, 0x8D76, 0x5CF8, 0x8D77, 0x5CF9, 0x8D78, 0x5CFA, 0x8D79, + 0x5CFB, 0xBEFE, 0x5CFC, 0x8D7A, 0x5CFD, 0x8D7B, 0x5CFE, 0x8D7C, 0x5CFF, 0x8D7D, 0x5D00, 0x8D7E, 0x5D01, 0x8D80, 0x5D02, 0xE1C0, + 0x5D03, 0xE1C1, 0x5D04, 0x8D81, 0x5D05, 0x8D82, 0x5D06, 0xE1C7, 0x5D07, 0xB3E7, 0x5D08, 0x8D83, 0x5D09, 0x8D84, 0x5D0A, 0x8D85, + 0x5D0B, 0x8D86, 0x5D0C, 0x8D87, 0x5D0D, 0x8D88, 0x5D0E, 0xC6E9, 0x5D0F, 0x8D89, 0x5D10, 0x8D8A, 0x5D11, 0x8D8B, 0x5D12, 0x8D8C, + 0x5D13, 0x8D8D, 0x5D14, 0xB4DE, 0x5D15, 0x8D8E, 0x5D16, 0xD1C2, 0x5D17, 0x8D8F, 0x5D18, 0x8D90, 0x5D19, 0x8D91, 0x5D1A, 0x8D92, + 0x5D1B, 0xE1C8, 0x5D1C, 0x8D93, 0x5D1D, 0x8D94, 0x5D1E, 0xE1C6, 0x5D1F, 0x8D95, 0x5D20, 0x8D96, 0x5D21, 0x8D97, 0x5D22, 0x8D98, + 0x5D23, 0x8D99, 0x5D24, 0xE1C5, 0x5D25, 0x8D9A, 0x5D26, 0xE1C3, 0x5D27, 0xE1C2, 0x5D28, 0x8D9B, 0x5D29, 0xB1C0, 0x5D2A, 0x8D9C, + 0x5D2B, 0x8D9D, 0x5D2C, 0x8D9E, 0x5D2D, 0xD5B8, 0x5D2E, 0xE1C4, 0x5D2F, 0x8D9F, 0x5D30, 0x8DA0, 0x5D31, 0x8DA1, 0x5D32, 0x8DA2, + 0x5D33, 0x8DA3, 0x5D34, 0xE1CB, 0x5D35, 0x8DA4, 0x5D36, 0x8DA5, 0x5D37, 0x8DA6, 0x5D38, 0x8DA7, 0x5D39, 0x8DA8, 0x5D3A, 0x8DA9, + 0x5D3B, 0x8DAA, 0x5D3C, 0x8DAB, 0x5D3D, 0xE1CC, 0x5D3E, 0xE1CA, 0x5D3F, 0x8DAC, 0x5D40, 0x8DAD, 0x5D41, 0x8DAE, 0x5D42, 0x8DAF, + 0x5D43, 0x8DB0, 0x5D44, 0x8DB1, 0x5D45, 0x8DB2, 0x5D46, 0x8DB3, 0x5D47, 0xEFFA, 0x5D48, 0x8DB4, 0x5D49, 0x8DB5, 0x5D4A, 0xE1D3, + 0x5D4B, 0xE1D2, 0x5D4C, 0xC7B6, 0x5D4D, 0x8DB6, 0x5D4E, 0x8DB7, 0x5D4F, 0x8DB8, 0x5D50, 0x8DB9, 0x5D51, 0x8DBA, 0x5D52, 0x8DBB, + 0x5D53, 0x8DBC, 0x5D54, 0x8DBD, 0x5D55, 0x8DBE, 0x5D56, 0x8DBF, 0x5D57, 0x8DC0, 0x5D58, 0xE1C9, 0x5D59, 0x8DC1, 0x5D5A, 0x8DC2, + 0x5D5B, 0xE1CE, 0x5D5C, 0x8DC3, 0x5D5D, 0xE1D0, 0x5D5E, 0x8DC4, 0x5D5F, 0x8DC5, 0x5D60, 0x8DC6, 0x5D61, 0x8DC7, 0x5D62, 0x8DC8, + 0x5D63, 0x8DC9, 0x5D64, 0x8DCA, 0x5D65, 0x8DCB, 0x5D66, 0x8DCC, 0x5D67, 0x8DCD, 0x5D68, 0x8DCE, 0x5D69, 0xE1D4, 0x5D6A, 0x8DCF, + 0x5D6B, 0xE1D1, 0x5D6C, 0xE1CD, 0x5D6D, 0x8DD0, 0x5D6E, 0x8DD1, 0x5D6F, 0xE1CF, 0x5D70, 0x8DD2, 0x5D71, 0x8DD3, 0x5D72, 0x8DD4, + 0x5D73, 0x8DD5, 0x5D74, 0xE1D5, 0x5D75, 0x8DD6, 0x5D76, 0x8DD7, 0x5D77, 0x8DD8, 0x5D78, 0x8DD9, 0x5D79, 0x8DDA, 0x5D7A, 0x8DDB, + 0x5D7B, 0x8DDC, 0x5D7C, 0x8DDD, 0x5D7D, 0x8DDE, 0x5D7E, 0x8DDF, 0x5D7F, 0x8DE0, 0x5D80, 0x8DE1, 0x5D81, 0x8DE2, 0x5D82, 0xE1D6, + 0x5D83, 0x8DE3, 0x5D84, 0x8DE4, 0x5D85, 0x8DE5, 0x5D86, 0x8DE6, 0x5D87, 0x8DE7, 0x5D88, 0x8DE8, 0x5D89, 0x8DE9, 0x5D8A, 0x8DEA, + 0x5D8B, 0x8DEB, 0x5D8C, 0x8DEC, 0x5D8D, 0x8DED, 0x5D8E, 0x8DEE, 0x5D8F, 0x8DEF, 0x5D90, 0x8DF0, 0x5D91, 0x8DF1, 0x5D92, 0x8DF2, + 0x5D93, 0x8DF3, 0x5D94, 0x8DF4, 0x5D95, 0x8DF5, 0x5D96, 0x8DF6, 0x5D97, 0x8DF7, 0x5D98, 0x8DF8, 0x5D99, 0xE1D7, 0x5D9A, 0x8DF9, + 0x5D9B, 0x8DFA, 0x5D9C, 0x8DFB, 0x5D9D, 0xE1D8, 0x5D9E, 0x8DFC, 0x5D9F, 0x8DFD, 0x5DA0, 0x8DFE, 0x5DA1, 0x8E40, 0x5DA2, 0x8E41, + 0x5DA3, 0x8E42, 0x5DA4, 0x8E43, 0x5DA5, 0x8E44, 0x5DA6, 0x8E45, 0x5DA7, 0x8E46, 0x5DA8, 0x8E47, 0x5DA9, 0x8E48, 0x5DAA, 0x8E49, + 0x5DAB, 0x8E4A, 0x5DAC, 0x8E4B, 0x5DAD, 0x8E4C, 0x5DAE, 0x8E4D, 0x5DAF, 0x8E4E, 0x5DB0, 0x8E4F, 0x5DB1, 0x8E50, 0x5DB2, 0x8E51, + 0x5DB3, 0x8E52, 0x5DB4, 0x8E53, 0x5DB5, 0x8E54, 0x5DB6, 0x8E55, 0x5DB7, 0xE1DA, 0x5DB8, 0x8E56, 0x5DB9, 0x8E57, 0x5DBA, 0x8E58, + 0x5DBB, 0x8E59, 0x5DBC, 0x8E5A, 0x5DBD, 0x8E5B, 0x5DBE, 0x8E5C, 0x5DBF, 0x8E5D, 0x5DC0, 0x8E5E, 0x5DC1, 0x8E5F, 0x5DC2, 0x8E60, + 0x5DC3, 0x8E61, 0x5DC4, 0x8E62, 0x5DC5, 0xE1DB, 0x5DC6, 0x8E63, 0x5DC7, 0x8E64, 0x5DC8, 0x8E65, 0x5DC9, 0x8E66, 0x5DCA, 0x8E67, + 0x5DCB, 0x8E68, 0x5DCC, 0x8E69, 0x5DCD, 0xCEA1, 0x5DCE, 0x8E6A, 0x5DCF, 0x8E6B, 0x5DD0, 0x8E6C, 0x5DD1, 0x8E6D, 0x5DD2, 0x8E6E, + 0x5DD3, 0x8E6F, 0x5DD4, 0x8E70, 0x5DD5, 0x8E71, 0x5DD6, 0x8E72, 0x5DD7, 0x8E73, 0x5DD8, 0x8E74, 0x5DD9, 0x8E75, 0x5DDA, 0x8E76, + 0x5DDB, 0xE7DD, 0x5DDC, 0x8E77, 0x5DDD, 0xB4A8, 0x5DDE, 0xD6DD, 0x5DDF, 0x8E78, 0x5DE0, 0x8E79, 0x5DE1, 0xD1B2, 0x5DE2, 0xB3B2, + 0x5DE3, 0x8E7A, 0x5DE4, 0x8E7B, 0x5DE5, 0xB9A4, 0x5DE6, 0xD7F3, 0x5DE7, 0xC7C9, 0x5DE8, 0xBEDE, 0x5DE9, 0xB9AE, 0x5DEA, 0x8E7C, + 0x5DEB, 0xCED7, 0x5DEC, 0x8E7D, 0x5DED, 0x8E7E, 0x5DEE, 0xB2EE, 0x5DEF, 0xDBCF, 0x5DF0, 0x8E80, 0x5DF1, 0xBCBA, 0x5DF2, 0xD2D1, + 0x5DF3, 0xCBC8, 0x5DF4, 0xB0CD, 0x5DF5, 0x8E81, 0x5DF6, 0x8E82, 0x5DF7, 0xCFEF, 0x5DF8, 0x8E83, 0x5DF9, 0x8E84, 0x5DFA, 0x8E85, + 0x5DFB, 0x8E86, 0x5DFC, 0x8E87, 0x5DFD, 0xD9E3, 0x5DFE, 0xBDED, 0x5DFF, 0x8E88, 0x5E00, 0x8E89, 0x5E01, 0xB1D2, 0x5E02, 0xCAD0, + 0x5E03, 0xB2BC, 0x5E04, 0x8E8A, 0x5E05, 0xCBA7, 0x5E06, 0xB7AB, 0x5E07, 0x8E8B, 0x5E08, 0xCAA6, 0x5E09, 0x8E8C, 0x5E0A, 0x8E8D, + 0x5E0B, 0x8E8E, 0x5E0C, 0xCFA3, 0x5E0D, 0x8E8F, 0x5E0E, 0x8E90, 0x5E0F, 0xE0F8, 0x5E10, 0xD5CA, 0x5E11, 0xE0FB, 0x5E12, 0x8E91, + 0x5E13, 0x8E92, 0x5E14, 0xE0FA, 0x5E15, 0xC5C1, 0x5E16, 0xCCFB, 0x5E17, 0x8E93, 0x5E18, 0xC1B1, 0x5E19, 0xE0F9, 0x5E1A, 0xD6E3, + 0x5E1B, 0xB2AF, 0x5E1C, 0xD6C4, 0x5E1D, 0xB5DB, 0x5E1E, 0x8E94, 0x5E1F, 0x8E95, 0x5E20, 0x8E96, 0x5E21, 0x8E97, 0x5E22, 0x8E98, + 0x5E23, 0x8E99, 0x5E24, 0x8E9A, 0x5E25, 0x8E9B, 0x5E26, 0xB4F8, 0x5E27, 0xD6A1, 0x5E28, 0x8E9C, 0x5E29, 0x8E9D, 0x5E2A, 0x8E9E, + 0x5E2B, 0x8E9F, 0x5E2C, 0x8EA0, 0x5E2D, 0xCFAF, 0x5E2E, 0xB0EF, 0x5E2F, 0x8EA1, 0x5E30, 0x8EA2, 0x5E31, 0xE0FC, 0x5E32, 0x8EA3, + 0x5E33, 0x8EA4, 0x5E34, 0x8EA5, 0x5E35, 0x8EA6, 0x5E36, 0x8EA7, 0x5E37, 0xE1A1, 0x5E38, 0xB3A3, 0x5E39, 0x8EA8, 0x5E3A, 0x8EA9, + 0x5E3B, 0xE0FD, 0x5E3C, 0xE0FE, 0x5E3D, 0xC3B1, 0x5E3E, 0x8EAA, 0x5E3F, 0x8EAB, 0x5E40, 0x8EAC, 0x5E41, 0x8EAD, 0x5E42, 0xC3DD, + 0x5E43, 0x8EAE, 0x5E44, 0xE1A2, 0x5E45, 0xB7F9, 0x5E46, 0x8EAF, 0x5E47, 0x8EB0, 0x5E48, 0x8EB1, 0x5E49, 0x8EB2, 0x5E4A, 0x8EB3, + 0x5E4B, 0x8EB4, 0x5E4C, 0xBBCF, 0x5E4D, 0x8EB5, 0x5E4E, 0x8EB6, 0x5E4F, 0x8EB7, 0x5E50, 0x8EB8, 0x5E51, 0x8EB9, 0x5E52, 0x8EBA, + 0x5E53, 0x8EBB, 0x5E54, 0xE1A3, 0x5E55, 0xC4BB, 0x5E56, 0x8EBC, 0x5E57, 0x8EBD, 0x5E58, 0x8EBE, 0x5E59, 0x8EBF, 0x5E5A, 0x8EC0, + 0x5E5B, 0xE1A4, 0x5E5C, 0x8EC1, 0x5E5D, 0x8EC2, 0x5E5E, 0xE1A5, 0x5E5F, 0x8EC3, 0x5E60, 0x8EC4, 0x5E61, 0xE1A6, 0x5E62, 0xB4B1, + 0x5E63, 0x8EC5, 0x5E64, 0x8EC6, 0x5E65, 0x8EC7, 0x5E66, 0x8EC8, 0x5E67, 0x8EC9, 0x5E68, 0x8ECA, 0x5E69, 0x8ECB, 0x5E6A, 0x8ECC, + 0x5E6B, 0x8ECD, 0x5E6C, 0x8ECE, 0x5E6D, 0x8ECF, 0x5E6E, 0x8ED0, 0x5E6F, 0x8ED1, 0x5E70, 0x8ED2, 0x5E71, 0x8ED3, 0x5E72, 0xB8C9, + 0x5E73, 0xC6BD, 0x5E74, 0xC4EA, 0x5E75, 0x8ED4, 0x5E76, 0xB2A2, 0x5E77, 0x8ED5, 0x5E78, 0xD0D2, 0x5E79, 0x8ED6, 0x5E7A, 0xE7DB, + 0x5E7B, 0xBBC3, 0x5E7C, 0xD3D7, 0x5E7D, 0xD3C4, 0x5E7E, 0x8ED7, 0x5E7F, 0xB9E3, 0x5E80, 0xE2CF, 0x5E81, 0x8ED8, 0x5E82, 0x8ED9, + 0x5E83, 0x8EDA, 0x5E84, 0xD7AF, 0x5E85, 0x8EDB, 0x5E86, 0xC7EC, 0x5E87, 0xB1D3, 0x5E88, 0x8EDC, 0x5E89, 0x8EDD, 0x5E8A, 0xB4B2, + 0x5E8B, 0xE2D1, 0x5E8C, 0x8EDE, 0x5E8D, 0x8EDF, 0x5E8E, 0x8EE0, 0x5E8F, 0xD0F2, 0x5E90, 0xC2AE, 0x5E91, 0xE2D0, 0x5E92, 0x8EE1, + 0x5E93, 0xBFE2, 0x5E94, 0xD3A6, 0x5E95, 0xB5D7, 0x5E96, 0xE2D2, 0x5E97, 0xB5EA, 0x5E98, 0x8EE2, 0x5E99, 0xC3ED, 0x5E9A, 0xB8FD, + 0x5E9B, 0x8EE3, 0x5E9C, 0xB8AE, 0x5E9D, 0x8EE4, 0x5E9E, 0xC5D3, 0x5E9F, 0xB7CF, 0x5EA0, 0xE2D4, 0x5EA1, 0x8EE5, 0x5EA2, 0x8EE6, + 0x5EA3, 0x8EE7, 0x5EA4, 0x8EE8, 0x5EA5, 0xE2D3, 0x5EA6, 0xB6C8, 0x5EA7, 0xD7F9, 0x5EA8, 0x8EE9, 0x5EA9, 0x8EEA, 0x5EAA, 0x8EEB, + 0x5EAB, 0x8EEC, 0x5EAC, 0x8EED, 0x5EAD, 0xCDA5, 0x5EAE, 0x8EEE, 0x5EAF, 0x8EEF, 0x5EB0, 0x8EF0, 0x5EB1, 0x8EF1, 0x5EB2, 0x8EF2, + 0x5EB3, 0xE2D8, 0x5EB4, 0x8EF3, 0x5EB5, 0xE2D6, 0x5EB6, 0xCAFC, 0x5EB7, 0xBFB5, 0x5EB8, 0xD3B9, 0x5EB9, 0xE2D5, 0x5EBA, 0x8EF4, + 0x5EBB, 0x8EF5, 0x5EBC, 0x8EF6, 0x5EBD, 0x8EF7, 0x5EBE, 0xE2D7, 0x5EBF, 0x8EF8, 0x5EC0, 0x8EF9, 0x5EC1, 0x8EFA, 0x5EC2, 0x8EFB, + 0x5EC3, 0x8EFC, 0x5EC4, 0x8EFD, 0x5EC5, 0x8EFE, 0x5EC6, 0x8F40, 0x5EC7, 0x8F41, 0x5EC8, 0x8F42, 0x5EC9, 0xC1AE, 0x5ECA, 0xC0C8, + 0x5ECB, 0x8F43, 0x5ECC, 0x8F44, 0x5ECD, 0x8F45, 0x5ECE, 0x8F46, 0x5ECF, 0x8F47, 0x5ED0, 0x8F48, 0x5ED1, 0xE2DB, 0x5ED2, 0xE2DA, + 0x5ED3, 0xC0AA, 0x5ED4, 0x8F49, 0x5ED5, 0x8F4A, 0x5ED6, 0xC1CE, 0x5ED7, 0x8F4B, 0x5ED8, 0x8F4C, 0x5ED9, 0x8F4D, 0x5EDA, 0x8F4E, + 0x5EDB, 0xE2DC, 0x5EDC, 0x8F4F, 0x5EDD, 0x8F50, 0x5EDE, 0x8F51, 0x5EDF, 0x8F52, 0x5EE0, 0x8F53, 0x5EE1, 0x8F54, 0x5EE2, 0x8F55, + 0x5EE3, 0x8F56, 0x5EE4, 0x8F57, 0x5EE5, 0x8F58, 0x5EE6, 0x8F59, 0x5EE7, 0x8F5A, 0x5EE8, 0xE2DD, 0x5EE9, 0x8F5B, 0x5EEA, 0xE2DE, + 0x5EEB, 0x8F5C, 0x5EEC, 0x8F5D, 0x5EED, 0x8F5E, 0x5EEE, 0x8F5F, 0x5EEF, 0x8F60, 0x5EF0, 0x8F61, 0x5EF1, 0x8F62, 0x5EF2, 0x8F63, + 0x5EF3, 0x8F64, 0x5EF4, 0xDBC8, 0x5EF5, 0x8F65, 0x5EF6, 0xD1D3, 0x5EF7, 0xCDA2, 0x5EF8, 0x8F66, 0x5EF9, 0x8F67, 0x5EFA, 0xBDA8, + 0x5EFB, 0x8F68, 0x5EFC, 0x8F69, 0x5EFD, 0x8F6A, 0x5EFE, 0xDEC3, 0x5EFF, 0xD8A5, 0x5F00, 0xBFAA, 0x5F01, 0xDBCD, 0x5F02, 0xD2EC, + 0x5F03, 0xC6FA, 0x5F04, 0xC5AA, 0x5F05, 0x8F6B, 0x5F06, 0x8F6C, 0x5F07, 0x8F6D, 0x5F08, 0xDEC4, 0x5F09, 0x8F6E, 0x5F0A, 0xB1D7, + 0x5F0B, 0xDFAE, 0x5F0C, 0x8F6F, 0x5F0D, 0x8F70, 0x5F0E, 0x8F71, 0x5F0F, 0xCABD, 0x5F10, 0x8F72, 0x5F11, 0xDFB1, 0x5F12, 0x8F73, + 0x5F13, 0xB9AD, 0x5F14, 0x8F74, 0x5F15, 0xD2FD, 0x5F16, 0x8F75, 0x5F17, 0xB8A5, 0x5F18, 0xBAEB, 0x5F19, 0x8F76, 0x5F1A, 0x8F77, + 0x5F1B, 0xB3DA, 0x5F1C, 0x8F78, 0x5F1D, 0x8F79, 0x5F1E, 0x8F7A, 0x5F1F, 0xB5DC, 0x5F20, 0xD5C5, 0x5F21, 0x8F7B, 0x5F22, 0x8F7C, + 0x5F23, 0x8F7D, 0x5F24, 0x8F7E, 0x5F25, 0xC3D6, 0x5F26, 0xCFD2, 0x5F27, 0xBBA1, 0x5F28, 0x8F80, 0x5F29, 0xE5F3, 0x5F2A, 0xE5F2, + 0x5F2B, 0x8F81, 0x5F2C, 0x8F82, 0x5F2D, 0xE5F4, 0x5F2E, 0x8F83, 0x5F2F, 0xCDE4, 0x5F30, 0x8F84, 0x5F31, 0xC8F5, 0x5F32, 0x8F85, + 0x5F33, 0x8F86, 0x5F34, 0x8F87, 0x5F35, 0x8F88, 0x5F36, 0x8F89, 0x5F37, 0x8F8A, 0x5F38, 0x8F8B, 0x5F39, 0xB5AF, 0x5F3A, 0xC7BF, + 0x5F3B, 0x8F8C, 0x5F3C, 0xE5F6, 0x5F3D, 0x8F8D, 0x5F3E, 0x8F8E, 0x5F3F, 0x8F8F, 0x5F40, 0xECB0, 0x5F41, 0x8F90, 0x5F42, 0x8F91, + 0x5F43, 0x8F92, 0x5F44, 0x8F93, 0x5F45, 0x8F94, 0x5F46, 0x8F95, 0x5F47, 0x8F96, 0x5F48, 0x8F97, 0x5F49, 0x8F98, 0x5F4A, 0x8F99, + 0x5F4B, 0x8F9A, 0x5F4C, 0x8F9B, 0x5F4D, 0x8F9C, 0x5F4E, 0x8F9D, 0x5F4F, 0x8F9E, 0x5F50, 0xE5E6, 0x5F51, 0x8F9F, 0x5F52, 0xB9E9, + 0x5F53, 0xB5B1, 0x5F54, 0x8FA0, 0x5F55, 0xC2BC, 0x5F56, 0xE5E8, 0x5F57, 0xE5E7, 0x5F58, 0xE5E9, 0x5F59, 0x8FA1, 0x5F5A, 0x8FA2, + 0x5F5B, 0x8FA3, 0x5F5C, 0x8FA4, 0x5F5D, 0xD2CD, 0x5F5E, 0x8FA5, 0x5F5F, 0x8FA6, 0x5F60, 0x8FA7, 0x5F61, 0xE1EA, 0x5F62, 0xD0CE, + 0x5F63, 0x8FA8, 0x5F64, 0xCDAE, 0x5F65, 0x8FA9, 0x5F66, 0xD1E5, 0x5F67, 0x8FAA, 0x5F68, 0x8FAB, 0x5F69, 0xB2CA, 0x5F6A, 0xB1EB, + 0x5F6B, 0x8FAC, 0x5F6C, 0xB1F2, 0x5F6D, 0xC5ED, 0x5F6E, 0x8FAD, 0x5F6F, 0x8FAE, 0x5F70, 0xD5C3, 0x5F71, 0xD3B0, 0x5F72, 0x8FAF, + 0x5F73, 0xE1DC, 0x5F74, 0x8FB0, 0x5F75, 0x8FB1, 0x5F76, 0x8FB2, 0x5F77, 0xE1DD, 0x5F78, 0x8FB3, 0x5F79, 0xD2DB, 0x5F7A, 0x8FB4, + 0x5F7B, 0xB3B9, 0x5F7C, 0xB1CB, 0x5F7D, 0x8FB5, 0x5F7E, 0x8FB6, 0x5F7F, 0x8FB7, 0x5F80, 0xCDF9, 0x5F81, 0xD5F7, 0x5F82, 0xE1DE, + 0x5F83, 0x8FB8, 0x5F84, 0xBEB6, 0x5F85, 0xB4FD, 0x5F86, 0x8FB9, 0x5F87, 0xE1DF, 0x5F88, 0xBADC, 0x5F89, 0xE1E0, 0x5F8A, 0xBBB2, + 0x5F8B, 0xC2C9, 0x5F8C, 0xE1E1, 0x5F8D, 0x8FBA, 0x5F8E, 0x8FBB, 0x5F8F, 0x8FBC, 0x5F90, 0xD0EC, 0x5F91, 0x8FBD, 0x5F92, 0xCDBD, + 0x5F93, 0x8FBE, 0x5F94, 0x8FBF, 0x5F95, 0xE1E2, 0x5F96, 0x8FC0, 0x5F97, 0xB5C3, 0x5F98, 0xC5C7, 0x5F99, 0xE1E3, 0x5F9A, 0x8FC1, + 0x5F9B, 0x8FC2, 0x5F9C, 0xE1E4, 0x5F9D, 0x8FC3, 0x5F9E, 0x8FC4, 0x5F9F, 0x8FC5, 0x5FA0, 0x8FC6, 0x5FA1, 0xD3F9, 0x5FA2, 0x8FC7, + 0x5FA3, 0x8FC8, 0x5FA4, 0x8FC9, 0x5FA5, 0x8FCA, 0x5FA6, 0x8FCB, 0x5FA7, 0x8FCC, 0x5FA8, 0xE1E5, 0x5FA9, 0x8FCD, 0x5FAA, 0xD1AD, + 0x5FAB, 0x8FCE, 0x5FAC, 0x8FCF, 0x5FAD, 0xE1E6, 0x5FAE, 0xCEA2, 0x5FAF, 0x8FD0, 0x5FB0, 0x8FD1, 0x5FB1, 0x8FD2, 0x5FB2, 0x8FD3, + 0x5FB3, 0x8FD4, 0x5FB4, 0x8FD5, 0x5FB5, 0xE1E7, 0x5FB6, 0x8FD6, 0x5FB7, 0xB5C2, 0x5FB8, 0x8FD7, 0x5FB9, 0x8FD8, 0x5FBA, 0x8FD9, + 0x5FBB, 0x8FDA, 0x5FBC, 0xE1E8, 0x5FBD, 0xBBD5, 0x5FBE, 0x8FDB, 0x5FBF, 0x8FDC, 0x5FC0, 0x8FDD, 0x5FC1, 0x8FDE, 0x5FC2, 0x8FDF, + 0x5FC3, 0xD0C4, 0x5FC4, 0xE2E0, 0x5FC5, 0xB1D8, 0x5FC6, 0xD2E4, 0x5FC7, 0x8FE0, 0x5FC8, 0x8FE1, 0x5FC9, 0xE2E1, 0x5FCA, 0x8FE2, + 0x5FCB, 0x8FE3, 0x5FCC, 0xBCC9, 0x5FCD, 0xC8CC, 0x5FCE, 0x8FE4, 0x5FCF, 0xE2E3, 0x5FD0, 0xECFE, 0x5FD1, 0xECFD, 0x5FD2, 0xDFAF, + 0x5FD3, 0x8FE5, 0x5FD4, 0x8FE6, 0x5FD5, 0x8FE7, 0x5FD6, 0xE2E2, 0x5FD7, 0xD6BE, 0x5FD8, 0xCDFC, 0x5FD9, 0xC3A6, 0x5FDA, 0x8FE8, + 0x5FDB, 0x8FE9, 0x5FDC, 0x8FEA, 0x5FDD, 0xE3C3, 0x5FDE, 0x8FEB, 0x5FDF, 0x8FEC, 0x5FE0, 0xD6D2, 0x5FE1, 0xE2E7, 0x5FE2, 0x8FED, + 0x5FE3, 0x8FEE, 0x5FE4, 0xE2E8, 0x5FE5, 0x8FEF, 0x5FE6, 0x8FF0, 0x5FE7, 0xD3C7, 0x5FE8, 0x8FF1, 0x5FE9, 0x8FF2, 0x5FEA, 0xE2EC, + 0x5FEB, 0xBFEC, 0x5FEC, 0x8FF3, 0x5FED, 0xE2ED, 0x5FEE, 0xE2E5, 0x5FEF, 0x8FF4, 0x5FF0, 0x8FF5, 0x5FF1, 0xB3C0, 0x5FF2, 0x8FF6, + 0x5FF3, 0x8FF7, 0x5FF4, 0x8FF8, 0x5FF5, 0xC4EE, 0x5FF6, 0x8FF9, 0x5FF7, 0x8FFA, 0x5FF8, 0xE2EE, 0x5FF9, 0x8FFB, 0x5FFA, 0x8FFC, + 0x5FFB, 0xD0C3, 0x5FFC, 0x8FFD, 0x5FFD, 0xBAF6, 0x5FFE, 0xE2E9, 0x5FFF, 0xB7DE, 0x6000, 0xBBB3, 0x6001, 0xCCAC, 0x6002, 0xCBCB, + 0x6003, 0xE2E4, 0x6004, 0xE2E6, 0x6005, 0xE2EA, 0x6006, 0xE2EB, 0x6007, 0x8FFE, 0x6008, 0x9040, 0x6009, 0x9041, 0x600A, 0xE2F7, + 0x600B, 0x9042, 0x600C, 0x9043, 0x600D, 0xE2F4, 0x600E, 0xD4F5, 0x600F, 0xE2F3, 0x6010, 0x9044, 0x6011, 0x9045, 0x6012, 0xC5AD, + 0x6013, 0x9046, 0x6014, 0xD5FA, 0x6015, 0xC5C2, 0x6016, 0xB2C0, 0x6017, 0x9047, 0x6018, 0x9048, 0x6019, 0xE2EF, 0x601A, 0x9049, + 0x601B, 0xE2F2, 0x601C, 0xC1AF, 0x601D, 0xCBBC, 0x601E, 0x904A, 0x601F, 0x904B, 0x6020, 0xB5A1, 0x6021, 0xE2F9, 0x6022, 0x904C, + 0x6023, 0x904D, 0x6024, 0x904E, 0x6025, 0xBCB1, 0x6026, 0xE2F1, 0x6027, 0xD0D4, 0x6028, 0xD4B9, 0x6029, 0xE2F5, 0x602A, 0xB9D6, + 0x602B, 0xE2F6, 0x602C, 0x904F, 0x602D, 0x9050, 0x602E, 0x9051, 0x602F, 0xC7D3, 0x6030, 0x9052, 0x6031, 0x9053, 0x6032, 0x9054, + 0x6033, 0x9055, 0x6034, 0x9056, 0x6035, 0xE2F0, 0x6036, 0x9057, 0x6037, 0x9058, 0x6038, 0x9059, 0x6039, 0x905A, 0x603A, 0x905B, + 0x603B, 0xD7DC, 0x603C, 0xEDA1, 0x603D, 0x905C, 0x603E, 0x905D, 0x603F, 0xE2F8, 0x6040, 0x905E, 0x6041, 0xEDA5, 0x6042, 0xE2FE, + 0x6043, 0xCAD1, 0x6044, 0x905F, 0x6045, 0x9060, 0x6046, 0x9061, 0x6047, 0x9062, 0x6048, 0x9063, 0x6049, 0x9064, 0x604A, 0x9065, + 0x604B, 0xC1B5, 0x604C, 0x9066, 0x604D, 0xBBD0, 0x604E, 0x9067, 0x604F, 0x9068, 0x6050, 0xBFD6, 0x6051, 0x9069, 0x6052, 0xBAE3, + 0x6053, 0x906A, 0x6054, 0x906B, 0x6055, 0xCBA1, 0x6056, 0x906C, 0x6057, 0x906D, 0x6058, 0x906E, 0x6059, 0xEDA6, 0x605A, 0xEDA3, + 0x605B, 0x906F, 0x605C, 0x9070, 0x605D, 0xEDA2, 0x605E, 0x9071, 0x605F, 0x9072, 0x6060, 0x9073, 0x6061, 0x9074, 0x6062, 0xBBD6, + 0x6063, 0xEDA7, 0x6064, 0xD0F4, 0x6065, 0x9075, 0x6066, 0x9076, 0x6067, 0xEDA4, 0x6068, 0xBADE, 0x6069, 0xB6F7, 0x606A, 0xE3A1, + 0x606B, 0xB6B2, 0x606C, 0xCCF1, 0x606D, 0xB9A7, 0x606E, 0x9077, 0x606F, 0xCFA2, 0x6070, 0xC7A1, 0x6071, 0x9078, 0x6072, 0x9079, + 0x6073, 0xBFD2, 0x6074, 0x907A, 0x6075, 0x907B, 0x6076, 0xB6F1, 0x6077, 0x907C, 0x6078, 0xE2FA, 0x6079, 0xE2FB, 0x607A, 0xE2FD, + 0x607B, 0xE2FC, 0x607C, 0xC4D5, 0x607D, 0xE3A2, 0x607E, 0x907D, 0x607F, 0xD3C1, 0x6080, 0x907E, 0x6081, 0x9080, 0x6082, 0x9081, + 0x6083, 0xE3A7, 0x6084, 0xC7C4, 0x6085, 0x9082, 0x6086, 0x9083, 0x6087, 0x9084, 0x6088, 0x9085, 0x6089, 0xCFA4, 0x608A, 0x9086, + 0x608B, 0x9087, 0x608C, 0xE3A9, 0x608D, 0xBAB7, 0x608E, 0x9088, 0x608F, 0x9089, 0x6090, 0x908A, 0x6091, 0x908B, 0x6092, 0xE3A8, + 0x6093, 0x908C, 0x6094, 0xBBDA, 0x6095, 0x908D, 0x6096, 0xE3A3, 0x6097, 0x908E, 0x6098, 0x908F, 0x6099, 0x9090, 0x609A, 0xE3A4, + 0x609B, 0xE3AA, 0x609C, 0x9091, 0x609D, 0xE3A6, 0x609E, 0x9092, 0x609F, 0xCEF2, 0x60A0, 0xD3C6, 0x60A1, 0x9093, 0x60A2, 0x9094, + 0x60A3, 0xBBBC, 0x60A4, 0x9095, 0x60A5, 0x9096, 0x60A6, 0xD4C3, 0x60A7, 0x9097, 0x60A8, 0xC4FA, 0x60A9, 0x9098, 0x60AA, 0x9099, + 0x60AB, 0xEDA8, 0x60AC, 0xD0FC, 0x60AD, 0xE3A5, 0x60AE, 0x909A, 0x60AF, 0xC3F5, 0x60B0, 0x909B, 0x60B1, 0xE3AD, 0x60B2, 0xB1AF, + 0x60B3, 0x909C, 0x60B4, 0xE3B2, 0x60B5, 0x909D, 0x60B6, 0x909E, 0x60B7, 0x909F, 0x60B8, 0xBCC2, 0x60B9, 0x90A0, 0x60BA, 0x90A1, + 0x60BB, 0xE3AC, 0x60BC, 0xB5BF, 0x60BD, 0x90A2, 0x60BE, 0x90A3, 0x60BF, 0x90A4, 0x60C0, 0x90A5, 0x60C1, 0x90A6, 0x60C2, 0x90A7, + 0x60C3, 0x90A8, 0x60C4, 0x90A9, 0x60C5, 0xC7E9, 0x60C6, 0xE3B0, 0x60C7, 0x90AA, 0x60C8, 0x90AB, 0x60C9, 0x90AC, 0x60CA, 0xBEAA, + 0x60CB, 0xCDEF, 0x60CC, 0x90AD, 0x60CD, 0x90AE, 0x60CE, 0x90AF, 0x60CF, 0x90B0, 0x60D0, 0x90B1, 0x60D1, 0xBBF3, 0x60D2, 0x90B2, + 0x60D3, 0x90B3, 0x60D4, 0x90B4, 0x60D5, 0xCCE8, 0x60D6, 0x90B5, 0x60D7, 0x90B6, 0x60D8, 0xE3AF, 0x60D9, 0x90B7, 0x60DA, 0xE3B1, + 0x60DB, 0x90B8, 0x60DC, 0xCFA7, 0x60DD, 0xE3AE, 0x60DE, 0x90B9, 0x60DF, 0xCEA9, 0x60E0, 0xBBDD, 0x60E1, 0x90BA, 0x60E2, 0x90BB, + 0x60E3, 0x90BC, 0x60E4, 0x90BD, 0x60E5, 0x90BE, 0x60E6, 0xB5EB, 0x60E7, 0xBEE5, 0x60E8, 0xB2D2, 0x60E9, 0xB3CD, 0x60EA, 0x90BF, + 0x60EB, 0xB1B9, 0x60EC, 0xE3AB, 0x60ED, 0xB2D1, 0x60EE, 0xB5AC, 0x60EF, 0xB9DF, 0x60F0, 0xB6E8, 0x60F1, 0x90C0, 0x60F2, 0x90C1, + 0x60F3, 0xCFEB, 0x60F4, 0xE3B7, 0x60F5, 0x90C2, 0x60F6, 0xBBCC, 0x60F7, 0x90C3, 0x60F8, 0x90C4, 0x60F9, 0xC8C7, 0x60FA, 0xD0CA, + 0x60FB, 0x90C5, 0x60FC, 0x90C6, 0x60FD, 0x90C7, 0x60FE, 0x90C8, 0x60FF, 0x90C9, 0x6100, 0xE3B8, 0x6101, 0xB3EE, 0x6102, 0x90CA, + 0x6103, 0x90CB, 0x6104, 0x90CC, 0x6105, 0x90CD, 0x6106, 0xEDA9, 0x6107, 0x90CE, 0x6108, 0xD3FA, 0x6109, 0xD3E4, 0x610A, 0x90CF, + 0x610B, 0x90D0, 0x610C, 0x90D1, 0x610D, 0xEDAA, 0x610E, 0xE3B9, 0x610F, 0xD2E2, 0x6110, 0x90D2, 0x6111, 0x90D3, 0x6112, 0x90D4, + 0x6113, 0x90D5, 0x6114, 0x90D6, 0x6115, 0xE3B5, 0x6116, 0x90D7, 0x6117, 0x90D8, 0x6118, 0x90D9, 0x6119, 0x90DA, 0x611A, 0xD3DE, + 0x611B, 0x90DB, 0x611C, 0x90DC, 0x611D, 0x90DD, 0x611E, 0x90DE, 0x611F, 0xB8D0, 0x6120, 0xE3B3, 0x6121, 0x90DF, 0x6122, 0x90E0, + 0x6123, 0xE3B6, 0x6124, 0xB7DF, 0x6125, 0x90E1, 0x6126, 0xE3B4, 0x6127, 0xC0A2, 0x6128, 0x90E2, 0x6129, 0x90E3, 0x612A, 0x90E4, + 0x612B, 0xE3BA, 0x612C, 0x90E5, 0x612D, 0x90E6, 0x612E, 0x90E7, 0x612F, 0x90E8, 0x6130, 0x90E9, 0x6131, 0x90EA, 0x6132, 0x90EB, + 0x6133, 0x90EC, 0x6134, 0x90ED, 0x6135, 0x90EE, 0x6136, 0x90EF, 0x6137, 0x90F0, 0x6138, 0x90F1, 0x6139, 0x90F2, 0x613A, 0x90F3, + 0x613B, 0x90F4, 0x613C, 0x90F5, 0x613D, 0x90F6, 0x613E, 0x90F7, 0x613F, 0xD4B8, 0x6140, 0x90F8, 0x6141, 0x90F9, 0x6142, 0x90FA, + 0x6143, 0x90FB, 0x6144, 0x90FC, 0x6145, 0x90FD, 0x6146, 0x90FE, 0x6147, 0x9140, 0x6148, 0xB4C8, 0x6149, 0x9141, 0x614A, 0xE3BB, + 0x614B, 0x9142, 0x614C, 0xBBC5, 0x614D, 0x9143, 0x614E, 0xC9F7, 0x614F, 0x9144, 0x6150, 0x9145, 0x6151, 0xC9E5, 0x6152, 0x9146, + 0x6153, 0x9147, 0x6154, 0x9148, 0x6155, 0xC4BD, 0x6156, 0x9149, 0x6157, 0x914A, 0x6158, 0x914B, 0x6159, 0x914C, 0x615A, 0x914D, + 0x615B, 0x914E, 0x615C, 0x914F, 0x615D, 0xEDAB, 0x615E, 0x9150, 0x615F, 0x9151, 0x6160, 0x9152, 0x6161, 0x9153, 0x6162, 0xC2FD, + 0x6163, 0x9154, 0x6164, 0x9155, 0x6165, 0x9156, 0x6166, 0x9157, 0x6167, 0xBBDB, 0x6168, 0xBFAE, 0x6169, 0x9158, 0x616A, 0x9159, + 0x616B, 0x915A, 0x616C, 0x915B, 0x616D, 0x915C, 0x616E, 0x915D, 0x616F, 0x915E, 0x6170, 0xCEBF, 0x6171, 0x915F, 0x6172, 0x9160, + 0x6173, 0x9161, 0x6174, 0x9162, 0x6175, 0xE3BC, 0x6176, 0x9163, 0x6177, 0xBFB6, 0x6178, 0x9164, 0x6179, 0x9165, 0x617A, 0x9166, + 0x617B, 0x9167, 0x617C, 0x9168, 0x617D, 0x9169, 0x617E, 0x916A, 0x617F, 0x916B, 0x6180, 0x916C, 0x6181, 0x916D, 0x6182, 0x916E, + 0x6183, 0x916F, 0x6184, 0x9170, 0x6185, 0x9171, 0x6186, 0x9172, 0x6187, 0x9173, 0x6188, 0x9174, 0x6189, 0x9175, 0x618A, 0x9176, + 0x618B, 0xB1EF, 0x618C, 0x9177, 0x618D, 0x9178, 0x618E, 0xD4F7, 0x618F, 0x9179, 0x6190, 0x917A, 0x6191, 0x917B, 0x6192, 0x917C, + 0x6193, 0x917D, 0x6194, 0xE3BE, 0x6195, 0x917E, 0x6196, 0x9180, 0x6197, 0x9181, 0x6198, 0x9182, 0x6199, 0x9183, 0x619A, 0x9184, + 0x619B, 0x9185, 0x619C, 0x9186, 0x619D, 0xEDAD, 0x619E, 0x9187, 0x619F, 0x9188, 0x61A0, 0x9189, 0x61A1, 0x918A, 0x61A2, 0x918B, + 0x61A3, 0x918C, 0x61A4, 0x918D, 0x61A5, 0x918E, 0x61A6, 0x918F, 0x61A7, 0xE3BF, 0x61A8, 0xBAA9, 0x61A9, 0xEDAC, 0x61AA, 0x9190, + 0x61AB, 0x9191, 0x61AC, 0xE3BD, 0x61AD, 0x9192, 0x61AE, 0x9193, 0x61AF, 0x9194, 0x61B0, 0x9195, 0x61B1, 0x9196, 0x61B2, 0x9197, + 0x61B3, 0x9198, 0x61B4, 0x9199, 0x61B5, 0x919A, 0x61B6, 0x919B, 0x61B7, 0xE3C0, 0x61B8, 0x919C, 0x61B9, 0x919D, 0x61BA, 0x919E, + 0x61BB, 0x919F, 0x61BC, 0x91A0, 0x61BD, 0x91A1, 0x61BE, 0xBAB6, 0x61BF, 0x91A2, 0x61C0, 0x91A3, 0x61C1, 0x91A4, 0x61C2, 0xB6AE, + 0x61C3, 0x91A5, 0x61C4, 0x91A6, 0x61C5, 0x91A7, 0x61C6, 0x91A8, 0x61C7, 0x91A9, 0x61C8, 0xD0B8, 0x61C9, 0x91AA, 0x61CA, 0xB0C3, + 0x61CB, 0xEDAE, 0x61CC, 0x91AB, 0x61CD, 0x91AC, 0x61CE, 0x91AD, 0x61CF, 0x91AE, 0x61D0, 0x91AF, 0x61D1, 0xEDAF, 0x61D2, 0xC0C1, + 0x61D3, 0x91B0, 0x61D4, 0xE3C1, 0x61D5, 0x91B1, 0x61D6, 0x91B2, 0x61D7, 0x91B3, 0x61D8, 0x91B4, 0x61D9, 0x91B5, 0x61DA, 0x91B6, + 0x61DB, 0x91B7, 0x61DC, 0x91B8, 0x61DD, 0x91B9, 0x61DE, 0x91BA, 0x61DF, 0x91BB, 0x61E0, 0x91BC, 0x61E1, 0x91BD, 0x61E2, 0x91BE, + 0x61E3, 0x91BF, 0x61E4, 0x91C0, 0x61E5, 0x91C1, 0x61E6, 0xC5B3, 0x61E7, 0x91C2, 0x61E8, 0x91C3, 0x61E9, 0x91C4, 0x61EA, 0x91C5, + 0x61EB, 0x91C6, 0x61EC, 0x91C7, 0x61ED, 0x91C8, 0x61EE, 0x91C9, 0x61EF, 0x91CA, 0x61F0, 0x91CB, 0x61F1, 0x91CC, 0x61F2, 0x91CD, + 0x61F3, 0x91CE, 0x61F4, 0x91CF, 0x61F5, 0xE3C2, 0x61F6, 0x91D0, 0x61F7, 0x91D1, 0x61F8, 0x91D2, 0x61F9, 0x91D3, 0x61FA, 0x91D4, + 0x61FB, 0x91D5, 0x61FC, 0x91D6, 0x61FD, 0x91D7, 0x61FE, 0x91D8, 0x61FF, 0xDCB2, 0x6200, 0x91D9, 0x6201, 0x91DA, 0x6202, 0x91DB, + 0x6203, 0x91DC, 0x6204, 0x91DD, 0x6205, 0x91DE, 0x6206, 0xEDB0, 0x6207, 0x91DF, 0x6208, 0xB8EA, 0x6209, 0x91E0, 0x620A, 0xCEEC, + 0x620B, 0xEAA7, 0x620C, 0xD0E7, 0x620D, 0xCAF9, 0x620E, 0xC8D6, 0x620F, 0xCFB7, 0x6210, 0xB3C9, 0x6211, 0xCED2, 0x6212, 0xBDE4, + 0x6213, 0x91E1, 0x6214, 0x91E2, 0x6215, 0xE3DE, 0x6216, 0xBBF2, 0x6217, 0xEAA8, 0x6218, 0xD5BD, 0x6219, 0x91E3, 0x621A, 0xC6DD, + 0x621B, 0xEAA9, 0x621C, 0x91E4, 0x621D, 0x91E5, 0x621E, 0x91E6, 0x621F, 0xEAAA, 0x6220, 0x91E7, 0x6221, 0xEAAC, 0x6222, 0xEAAB, + 0x6223, 0x91E8, 0x6224, 0xEAAE, 0x6225, 0xEAAD, 0x6226, 0x91E9, 0x6227, 0x91EA, 0x6228, 0x91EB, 0x6229, 0x91EC, 0x622A, 0xBDD8, + 0x622B, 0x91ED, 0x622C, 0xEAAF, 0x622D, 0x91EE, 0x622E, 0xC2BE, 0x622F, 0x91EF, 0x6230, 0x91F0, 0x6231, 0x91F1, 0x6232, 0x91F2, + 0x6233, 0xB4C1, 0x6234, 0xB4F7, 0x6235, 0x91F3, 0x6236, 0x91F4, 0x6237, 0xBBA7, 0x6238, 0x91F5, 0x6239, 0x91F6, 0x623A, 0x91F7, + 0x623B, 0x91F8, 0x623C, 0x91F9, 0x623D, 0xECE6, 0x623E, 0xECE5, 0x623F, 0xB7BF, 0x6240, 0xCBF9, 0x6241, 0xB1E2, 0x6242, 0x91FA, + 0x6243, 0xECE7, 0x6244, 0x91FB, 0x6245, 0x91FC, 0x6246, 0x91FD, 0x6247, 0xC9C8, 0x6248, 0xECE8, 0x6249, 0xECE9, 0x624A, 0x91FE, + 0x624B, 0xCAD6, 0x624C, 0xDED0, 0x624D, 0xB2C5, 0x624E, 0xD4FA, 0x624F, 0x9240, 0x6250, 0x9241, 0x6251, 0xC6CB, 0x6252, 0xB0C7, + 0x6253, 0xB4F2, 0x6254, 0xC8D3, 0x6255, 0x9242, 0x6256, 0x9243, 0x6257, 0x9244, 0x6258, 0xCDD0, 0x6259, 0x9245, 0x625A, 0x9246, + 0x625B, 0xBFB8, 0x625C, 0x9247, 0x625D, 0x9248, 0x625E, 0x9249, 0x625F, 0x924A, 0x6260, 0x924B, 0x6261, 0x924C, 0x6262, 0x924D, + 0x6263, 0xBFDB, 0x6264, 0x924E, 0x6265, 0x924F, 0x6266, 0xC7A4, 0x6267, 0xD6B4, 0x6268, 0x9250, 0x6269, 0xC0A9, 0x626A, 0xDED1, + 0x626B, 0xC9A8, 0x626C, 0xD1EF, 0x626D, 0xC5A4, 0x626E, 0xB0E7, 0x626F, 0xB3B6, 0x6270, 0xC8C5, 0x6271, 0x9251, 0x6272, 0x9252, + 0x6273, 0xB0E2, 0x6274, 0x9253, 0x6275, 0x9254, 0x6276, 0xB7F6, 0x6277, 0x9255, 0x6278, 0x9256, 0x6279, 0xC5FA, 0x627A, 0x9257, + 0x627B, 0x9258, 0x627C, 0xB6F3, 0x627D, 0x9259, 0x627E, 0xD5D2, 0x627F, 0xB3D0, 0x6280, 0xBCBC, 0x6281, 0x925A, 0x6282, 0x925B, + 0x6283, 0x925C, 0x6284, 0xB3AD, 0x6285, 0x925D, 0x6286, 0x925E, 0x6287, 0x925F, 0x6288, 0x9260, 0x6289, 0xBEF1, 0x628A, 0xB0D1, + 0x628B, 0x9261, 0x628C, 0x9262, 0x628D, 0x9263, 0x628E, 0x9264, 0x628F, 0x9265, 0x6290, 0x9266, 0x6291, 0xD2D6, 0x6292, 0xCAE3, + 0x6293, 0xD7A5, 0x6294, 0x9267, 0x6295, 0xCDB6, 0x6296, 0xB6B6, 0x6297, 0xBFB9, 0x6298, 0xD5DB, 0x6299, 0x9268, 0x629A, 0xB8A7, + 0x629B, 0xC5D7, 0x629C, 0x9269, 0x629D, 0x926A, 0x629E, 0x926B, 0x629F, 0xDED2, 0x62A0, 0xBFD9, 0x62A1, 0xC2D5, 0x62A2, 0xC7C0, + 0x62A3, 0x926C, 0x62A4, 0xBBA4, 0x62A5, 0xB1A8, 0x62A6, 0x926D, 0x62A7, 0x926E, 0x62A8, 0xC5EA, 0x62A9, 0x926F, 0x62AA, 0x9270, + 0x62AB, 0xC5FB, 0x62AC, 0xCCA7, 0x62AD, 0x9271, 0x62AE, 0x9272, 0x62AF, 0x9273, 0x62B0, 0x9274, 0x62B1, 0xB1A7, 0x62B2, 0x9275, + 0x62B3, 0x9276, 0x62B4, 0x9277, 0x62B5, 0xB5D6, 0x62B6, 0x9278, 0x62B7, 0x9279, 0x62B8, 0x927A, 0x62B9, 0xC4A8, 0x62BA, 0x927B, + 0x62BB, 0xDED3, 0x62BC, 0xD1BA, 0x62BD, 0xB3E9, 0x62BE, 0x927C, 0x62BF, 0xC3F2, 0x62C0, 0x927D, 0x62C1, 0x927E, 0x62C2, 0xB7F7, + 0x62C3, 0x9280, 0x62C4, 0xD6F4, 0x62C5, 0xB5A3, 0x62C6, 0xB2F0, 0x62C7, 0xC4B4, 0x62C8, 0xC4E9, 0x62C9, 0xC0AD, 0x62CA, 0xDED4, + 0x62CB, 0x9281, 0x62CC, 0xB0E8, 0x62CD, 0xC5C4, 0x62CE, 0xC1E0, 0x62CF, 0x9282, 0x62D0, 0xB9D5, 0x62D1, 0x9283, 0x62D2, 0xBEDC, + 0x62D3, 0xCDD8, 0x62D4, 0xB0CE, 0x62D5, 0x9284, 0x62D6, 0xCDCF, 0x62D7, 0xDED6, 0x62D8, 0xBED0, 0x62D9, 0xD7BE, 0x62DA, 0xDED5, + 0x62DB, 0xD5D0, 0x62DC, 0xB0DD, 0x62DD, 0x9285, 0x62DE, 0x9286, 0x62DF, 0xC4E2, 0x62E0, 0x9287, 0x62E1, 0x9288, 0x62E2, 0xC2A3, + 0x62E3, 0xBCF0, 0x62E4, 0x9289, 0x62E5, 0xD3B5, 0x62E6, 0xC0B9, 0x62E7, 0xC5A1, 0x62E8, 0xB2A6, 0x62E9, 0xD4F1, 0x62EA, 0x928A, + 0x62EB, 0x928B, 0x62EC, 0xC0A8, 0x62ED, 0xCAC3, 0x62EE, 0xDED7, 0x62EF, 0xD5FC, 0x62F0, 0x928C, 0x62F1, 0xB9B0, 0x62F2, 0x928D, + 0x62F3, 0xC8AD, 0x62F4, 0xCBA9, 0x62F5, 0x928E, 0x62F6, 0xDED9, 0x62F7, 0xBFBD, 0x62F8, 0x928F, 0x62F9, 0x9290, 0x62FA, 0x9291, + 0x62FB, 0x9292, 0x62FC, 0xC6B4, 0x62FD, 0xD7A7, 0x62FE, 0xCAB0, 0x62FF, 0xC4C3, 0x6300, 0x9293, 0x6301, 0xB3D6, 0x6302, 0xB9D2, + 0x6303, 0x9294, 0x6304, 0x9295, 0x6305, 0x9296, 0x6306, 0x9297, 0x6307, 0xD6B8, 0x6308, 0xEAFC, 0x6309, 0xB0B4, 0x630A, 0x9298, + 0x630B, 0x9299, 0x630C, 0x929A, 0x630D, 0x929B, 0x630E, 0xBFE6, 0x630F, 0x929C, 0x6310, 0x929D, 0x6311, 0xCCF4, 0x6312, 0x929E, + 0x6313, 0x929F, 0x6314, 0x92A0, 0x6315, 0x92A1, 0x6316, 0xCDDA, 0x6317, 0x92A2, 0x6318, 0x92A3, 0x6319, 0x92A4, 0x631A, 0xD6BF, + 0x631B, 0xC2CE, 0x631C, 0x92A5, 0x631D, 0xCECE, 0x631E, 0xCCA2, 0x631F, 0xD0AE, 0x6320, 0xC4D3, 0x6321, 0xB5B2, 0x6322, 0xDED8, + 0x6323, 0xD5F5, 0x6324, 0xBCB7, 0x6325, 0xBBD3, 0x6326, 0x92A6, 0x6327, 0x92A7, 0x6328, 0xB0A4, 0x6329, 0x92A8, 0x632A, 0xC5B2, + 0x632B, 0xB4EC, 0x632C, 0x92A9, 0x632D, 0x92AA, 0x632E, 0x92AB, 0x632F, 0xD5F1, 0x6330, 0x92AC, 0x6331, 0x92AD, 0x6332, 0xEAFD, + 0x6333, 0x92AE, 0x6334, 0x92AF, 0x6335, 0x92B0, 0x6336, 0x92B1, 0x6337, 0x92B2, 0x6338, 0x92B3, 0x6339, 0xDEDA, 0x633A, 0xCDA6, + 0x633B, 0x92B4, 0x633C, 0x92B5, 0x633D, 0xCDEC, 0x633E, 0x92B6, 0x633F, 0x92B7, 0x6340, 0x92B8, 0x6341, 0x92B9, 0x6342, 0xCEE6, + 0x6343, 0xDEDC, 0x6344, 0x92BA, 0x6345, 0xCDB1, 0x6346, 0xC0A6, 0x6347, 0x92BB, 0x6348, 0x92BC, 0x6349, 0xD7BD, 0x634A, 0x92BD, + 0x634B, 0xDEDB, 0x634C, 0xB0C6, 0x634D, 0xBAB4, 0x634E, 0xC9D3, 0x634F, 0xC4F3, 0x6350, 0xBEE8, 0x6351, 0x92BE, 0x6352, 0x92BF, + 0x6353, 0x92C0, 0x6354, 0x92C1, 0x6355, 0xB2B6, 0x6356, 0x92C2, 0x6357, 0x92C3, 0x6358, 0x92C4, 0x6359, 0x92C5, 0x635A, 0x92C6, + 0x635B, 0x92C7, 0x635C, 0x92C8, 0x635D, 0x92C9, 0x635E, 0xC0CC, 0x635F, 0xCBF0, 0x6360, 0x92CA, 0x6361, 0xBCF1, 0x6362, 0xBBBB, + 0x6363, 0xB5B7, 0x6364, 0x92CB, 0x6365, 0x92CC, 0x6366, 0x92CD, 0x6367, 0xC5F5, 0x6368, 0x92CE, 0x6369, 0xDEE6, 0x636A, 0x92CF, + 0x636B, 0x92D0, 0x636C, 0x92D1, 0x636D, 0xDEE3, 0x636E, 0xBEDD, 0x636F, 0x92D2, 0x6370, 0x92D3, 0x6371, 0xDEDF, 0x6372, 0x92D4, + 0x6373, 0x92D5, 0x6374, 0x92D6, 0x6375, 0x92D7, 0x6376, 0xB4B7, 0x6377, 0xBDDD, 0x6378, 0x92D8, 0x6379, 0x92D9, 0x637A, 0xDEE0, + 0x637B, 0xC4ED, 0x637C, 0x92DA, 0x637D, 0x92DB, 0x637E, 0x92DC, 0x637F, 0x92DD, 0x6380, 0xCFC6, 0x6381, 0x92DE, 0x6382, 0xB5E0, + 0x6383, 0x92DF, 0x6384, 0x92E0, 0x6385, 0x92E1, 0x6386, 0x92E2, 0x6387, 0xB6DE, 0x6388, 0xCADA, 0x6389, 0xB5F4, 0x638A, 0xDEE5, + 0x638B, 0x92E3, 0x638C, 0xD5C6, 0x638D, 0x92E4, 0x638E, 0xDEE1, 0x638F, 0xCCCD, 0x6390, 0xC6FE, 0x6391, 0x92E5, 0x6392, 0xC5C5, + 0x6393, 0x92E6, 0x6394, 0x92E7, 0x6395, 0x92E8, 0x6396, 0xD2B4, 0x6397, 0x92E9, 0x6398, 0xBEF2, 0x6399, 0x92EA, 0x639A, 0x92EB, + 0x639B, 0x92EC, 0x639C, 0x92ED, 0x639D, 0x92EE, 0x639E, 0x92EF, 0x639F, 0x92F0, 0x63A0, 0xC2D3, 0x63A1, 0x92F1, 0x63A2, 0xCCBD, + 0x63A3, 0xB3B8, 0x63A4, 0x92F2, 0x63A5, 0xBDD3, 0x63A6, 0x92F3, 0x63A7, 0xBFD8, 0x63A8, 0xCDC6, 0x63A9, 0xD1DA, 0x63AA, 0xB4EB, + 0x63AB, 0x92F4, 0x63AC, 0xDEE4, 0x63AD, 0xDEDD, 0x63AE, 0xDEE7, 0x63AF, 0x92F5, 0x63B0, 0xEAFE, 0x63B1, 0x92F6, 0x63B2, 0x92F7, + 0x63B3, 0xC2B0, 0x63B4, 0xDEE2, 0x63B5, 0x92F8, 0x63B6, 0x92F9, 0x63B7, 0xD6C0, 0x63B8, 0xB5A7, 0x63B9, 0x92FA, 0x63BA, 0xB2F4, + 0x63BB, 0x92FB, 0x63BC, 0xDEE8, 0x63BD, 0x92FC, 0x63BE, 0xDEF2, 0x63BF, 0x92FD, 0x63C0, 0x92FE, 0x63C1, 0x9340, 0x63C2, 0x9341, + 0x63C3, 0x9342, 0x63C4, 0xDEED, 0x63C5, 0x9343, 0x63C6, 0xDEF1, 0x63C7, 0x9344, 0x63C8, 0x9345, 0x63C9, 0xC8E0, 0x63CA, 0x9346, + 0x63CB, 0x9347, 0x63CC, 0x9348, 0x63CD, 0xD7E1, 0x63CE, 0xDEEF, 0x63CF, 0xC3E8, 0x63D0, 0xCCE1, 0x63D1, 0x9349, 0x63D2, 0xB2E5, + 0x63D3, 0x934A, 0x63D4, 0x934B, 0x63D5, 0x934C, 0x63D6, 0xD2BE, 0x63D7, 0x934D, 0x63D8, 0x934E, 0x63D9, 0x934F, 0x63DA, 0x9350, + 0x63DB, 0x9351, 0x63DC, 0x9352, 0x63DD, 0x9353, 0x63DE, 0xDEEE, 0x63DF, 0x9354, 0x63E0, 0xDEEB, 0x63E1, 0xCED5, 0x63E2, 0x9355, + 0x63E3, 0xB4A7, 0x63E4, 0x9356, 0x63E5, 0x9357, 0x63E6, 0x9358, 0x63E7, 0x9359, 0x63E8, 0x935A, 0x63E9, 0xBFAB, 0x63EA, 0xBEBE, + 0x63EB, 0x935B, 0x63EC, 0x935C, 0x63ED, 0xBDD2, 0x63EE, 0x935D, 0x63EF, 0x935E, 0x63F0, 0x935F, 0x63F1, 0x9360, 0x63F2, 0xDEE9, + 0x63F3, 0x9361, 0x63F4, 0xD4AE, 0x63F5, 0x9362, 0x63F6, 0xDEDE, 0x63F7, 0x9363, 0x63F8, 0xDEEA, 0x63F9, 0x9364, 0x63FA, 0x9365, + 0x63FB, 0x9366, 0x63FC, 0x9367, 0x63FD, 0xC0BF, 0x63FE, 0x9368, 0x63FF, 0xDEEC, 0x6400, 0xB2F3, 0x6401, 0xB8E9, 0x6402, 0xC2A7, + 0x6403, 0x9369, 0x6404, 0x936A, 0x6405, 0xBDC1, 0x6406, 0x936B, 0x6407, 0x936C, 0x6408, 0x936D, 0x6409, 0x936E, 0x640A, 0x936F, + 0x640B, 0xDEF5, 0x640C, 0xDEF8, 0x640D, 0x9370, 0x640E, 0x9371, 0x640F, 0xB2AB, 0x6410, 0xB4A4, 0x6411, 0x9372, 0x6412, 0x9373, + 0x6413, 0xB4EA, 0x6414, 0xC9A6, 0x6415, 0x9374, 0x6416, 0x9375, 0x6417, 0x9376, 0x6418, 0x9377, 0x6419, 0x9378, 0x641A, 0x9379, + 0x641B, 0xDEF6, 0x641C, 0xCBD1, 0x641D, 0x937A, 0x641E, 0xB8E3, 0x641F, 0x937B, 0x6420, 0xDEF7, 0x6421, 0xDEFA, 0x6422, 0x937C, + 0x6423, 0x937D, 0x6424, 0x937E, 0x6425, 0x9380, 0x6426, 0xDEF9, 0x6427, 0x9381, 0x6428, 0x9382, 0x6429, 0x9383, 0x642A, 0xCCC2, + 0x642B, 0x9384, 0x642C, 0xB0E1, 0x642D, 0xB4EE, 0x642E, 0x9385, 0x642F, 0x9386, 0x6430, 0x9387, 0x6431, 0x9388, 0x6432, 0x9389, + 0x6433, 0x938A, 0x6434, 0xE5BA, 0x6435, 0x938B, 0x6436, 0x938C, 0x6437, 0x938D, 0x6438, 0x938E, 0x6439, 0x938F, 0x643A, 0xD0AF, + 0x643B, 0x9390, 0x643C, 0x9391, 0x643D, 0xB2EB, 0x643E, 0x9392, 0x643F, 0xEBA1, 0x6440, 0x9393, 0x6441, 0xDEF4, 0x6442, 0x9394, + 0x6443, 0x9395, 0x6444, 0xC9E3, 0x6445, 0xDEF3, 0x6446, 0xB0DA, 0x6447, 0xD2A1, 0x6448, 0xB1F7, 0x6449, 0x9396, 0x644A, 0xCCAF, + 0x644B, 0x9397, 0x644C, 0x9398, 0x644D, 0x9399, 0x644E, 0x939A, 0x644F, 0x939B, 0x6450, 0x939C, 0x6451, 0x939D, 0x6452, 0xDEF0, + 0x6453, 0x939E, 0x6454, 0xCBA4, 0x6455, 0x939F, 0x6456, 0x93A0, 0x6457, 0x93A1, 0x6458, 0xD5AA, 0x6459, 0x93A2, 0x645A, 0x93A3, + 0x645B, 0x93A4, 0x645C, 0x93A5, 0x645D, 0x93A6, 0x645E, 0xDEFB, 0x645F, 0x93A7, 0x6460, 0x93A8, 0x6461, 0x93A9, 0x6462, 0x93AA, + 0x6463, 0x93AB, 0x6464, 0x93AC, 0x6465, 0x93AD, 0x6466, 0x93AE, 0x6467, 0xB4DD, 0x6468, 0x93AF, 0x6469, 0xC4A6, 0x646A, 0x93B0, + 0x646B, 0x93B1, 0x646C, 0x93B2, 0x646D, 0xDEFD, 0x646E, 0x93B3, 0x646F, 0x93B4, 0x6470, 0x93B5, 0x6471, 0x93B6, 0x6472, 0x93B7, + 0x6473, 0x93B8, 0x6474, 0x93B9, 0x6475, 0x93BA, 0x6476, 0x93BB, 0x6477, 0x93BC, 0x6478, 0xC3FE, 0x6479, 0xC4A1, 0x647A, 0xDFA1, + 0x647B, 0x93BD, 0x647C, 0x93BE, 0x647D, 0x93BF, 0x647E, 0x93C0, 0x647F, 0x93C1, 0x6480, 0x93C2, 0x6481, 0x93C3, 0x6482, 0xC1CC, + 0x6483, 0x93C4, 0x6484, 0xDEFC, 0x6485, 0xBEEF, 0x6486, 0x93C5, 0x6487, 0xC6B2, 0x6488, 0x93C6, 0x6489, 0x93C7, 0x648A, 0x93C8, + 0x648B, 0x93C9, 0x648C, 0x93CA, 0x648D, 0x93CB, 0x648E, 0x93CC, 0x648F, 0x93CD, 0x6490, 0x93CE, 0x6491, 0xB3C5, 0x6492, 0xC8F6, + 0x6493, 0x93CF, 0x6494, 0x93D0, 0x6495, 0xCBBA, 0x6496, 0xDEFE, 0x6497, 0x93D1, 0x6498, 0x93D2, 0x6499, 0xDFA4, 0x649A, 0x93D3, + 0x649B, 0x93D4, 0x649C, 0x93D5, 0x649D, 0x93D6, 0x649E, 0xD7B2, 0x649F, 0x93D7, 0x64A0, 0x93D8, 0x64A1, 0x93D9, 0x64A2, 0x93DA, + 0x64A3, 0x93DB, 0x64A4, 0xB3B7, 0x64A5, 0x93DC, 0x64A6, 0x93DD, 0x64A7, 0x93DE, 0x64A8, 0x93DF, 0x64A9, 0xC1C3, 0x64AA, 0x93E0, + 0x64AB, 0x93E1, 0x64AC, 0xC7CB, 0x64AD, 0xB2A5, 0x64AE, 0xB4E9, 0x64AF, 0x93E2, 0x64B0, 0xD7AB, 0x64B1, 0x93E3, 0x64B2, 0x93E4, + 0x64B3, 0x93E5, 0x64B4, 0x93E6, 0x64B5, 0xC4EC, 0x64B6, 0x93E7, 0x64B7, 0xDFA2, 0x64B8, 0xDFA3, 0x64B9, 0x93E8, 0x64BA, 0xDFA5, + 0x64BB, 0x93E9, 0x64BC, 0xBAB3, 0x64BD, 0x93EA, 0x64BE, 0x93EB, 0x64BF, 0x93EC, 0x64C0, 0xDFA6, 0x64C1, 0x93ED, 0x64C2, 0xC0DE, + 0x64C3, 0x93EE, 0x64C4, 0x93EF, 0x64C5, 0xC9C3, 0x64C6, 0x93F0, 0x64C7, 0x93F1, 0x64C8, 0x93F2, 0x64C9, 0x93F3, 0x64CA, 0x93F4, + 0x64CB, 0x93F5, 0x64CC, 0x93F6, 0x64CD, 0xB2D9, 0x64CE, 0xC7E6, 0x64CF, 0x93F7, 0x64D0, 0xDFA7, 0x64D1, 0x93F8, 0x64D2, 0xC7DC, + 0x64D3, 0x93F9, 0x64D4, 0x93FA, 0x64D5, 0x93FB, 0x64D6, 0x93FC, 0x64D7, 0xDFA8, 0x64D8, 0xEBA2, 0x64D9, 0x93FD, 0x64DA, 0x93FE, + 0x64DB, 0x9440, 0x64DC, 0x9441, 0x64DD, 0x9442, 0x64DE, 0xCBD3, 0x64DF, 0x9443, 0x64E0, 0x9444, 0x64E1, 0x9445, 0x64E2, 0xDFAA, + 0x64E3, 0x9446, 0x64E4, 0xDFA9, 0x64E5, 0x9447, 0x64E6, 0xB2C1, 0x64E7, 0x9448, 0x64E8, 0x9449, 0x64E9, 0x944A, 0x64EA, 0x944B, + 0x64EB, 0x944C, 0x64EC, 0x944D, 0x64ED, 0x944E, 0x64EE, 0x944F, 0x64EF, 0x9450, 0x64F0, 0x9451, 0x64F1, 0x9452, 0x64F2, 0x9453, + 0x64F3, 0x9454, 0x64F4, 0x9455, 0x64F5, 0x9456, 0x64F6, 0x9457, 0x64F7, 0x9458, 0x64F8, 0x9459, 0x64F9, 0x945A, 0x64FA, 0x945B, + 0x64FB, 0x945C, 0x64FC, 0x945D, 0x64FD, 0x945E, 0x64FE, 0x945F, 0x64FF, 0x9460, 0x6500, 0xC5CA, 0x6501, 0x9461, 0x6502, 0x9462, + 0x6503, 0x9463, 0x6504, 0x9464, 0x6505, 0x9465, 0x6506, 0x9466, 0x6507, 0x9467, 0x6508, 0x9468, 0x6509, 0xDFAB, 0x650A, 0x9469, + 0x650B, 0x946A, 0x650C, 0x946B, 0x650D, 0x946C, 0x650E, 0x946D, 0x650F, 0x946E, 0x6510, 0x946F, 0x6511, 0x9470, 0x6512, 0xD4DC, + 0x6513, 0x9471, 0x6514, 0x9472, 0x6515, 0x9473, 0x6516, 0x9474, 0x6517, 0x9475, 0x6518, 0xC8C1, 0x6519, 0x9476, 0x651A, 0x9477, + 0x651B, 0x9478, 0x651C, 0x9479, 0x651D, 0x947A, 0x651E, 0x947B, 0x651F, 0x947C, 0x6520, 0x947D, 0x6521, 0x947E, 0x6522, 0x9480, + 0x6523, 0x9481, 0x6524, 0x9482, 0x6525, 0xDFAC, 0x6526, 0x9483, 0x6527, 0x9484, 0x6528, 0x9485, 0x6529, 0x9486, 0x652A, 0x9487, + 0x652B, 0xBEF0, 0x652C, 0x9488, 0x652D, 0x9489, 0x652E, 0xDFAD, 0x652F, 0xD6A7, 0x6530, 0x948A, 0x6531, 0x948B, 0x6532, 0x948C, + 0x6533, 0x948D, 0x6534, 0xEAB7, 0x6535, 0xEBB6, 0x6536, 0xCAD5, 0x6537, 0x948E, 0x6538, 0xD8FC, 0x6539, 0xB8C4, 0x653A, 0x948F, + 0x653B, 0xB9A5, 0x653C, 0x9490, 0x653D, 0x9491, 0x653E, 0xB7C5, 0x653F, 0xD5FE, 0x6540, 0x9492, 0x6541, 0x9493, 0x6542, 0x9494, + 0x6543, 0x9495, 0x6544, 0x9496, 0x6545, 0xB9CA, 0x6546, 0x9497, 0x6547, 0x9498, 0x6548, 0xD0A7, 0x6549, 0xF4CD, 0x654A, 0x9499, + 0x654B, 0x949A, 0x654C, 0xB5D0, 0x654D, 0x949B, 0x654E, 0x949C, 0x654F, 0xC3F4, 0x6550, 0x949D, 0x6551, 0xBEC8, 0x6552, 0x949E, + 0x6553, 0x949F, 0x6554, 0x94A0, 0x6555, 0xEBB7, 0x6556, 0xB0BD, 0x6557, 0x94A1, 0x6558, 0x94A2, 0x6559, 0xBDCC, 0x655A, 0x94A3, + 0x655B, 0xC1B2, 0x655C, 0x94A4, 0x655D, 0xB1D6, 0x655E, 0xB3A8, 0x655F, 0x94A5, 0x6560, 0x94A6, 0x6561, 0x94A7, 0x6562, 0xB8D2, + 0x6563, 0xC9A2, 0x6564, 0x94A8, 0x6565, 0x94A9, 0x6566, 0xB6D8, 0x6567, 0x94AA, 0x6568, 0x94AB, 0x6569, 0x94AC, 0x656A, 0x94AD, + 0x656B, 0xEBB8, 0x656C, 0xBEB4, 0x656D, 0x94AE, 0x656E, 0x94AF, 0x656F, 0x94B0, 0x6570, 0xCAFD, 0x6571, 0x94B1, 0x6572, 0xC7C3, + 0x6573, 0x94B2, 0x6574, 0xD5FB, 0x6575, 0x94B3, 0x6576, 0x94B4, 0x6577, 0xB7F3, 0x6578, 0x94B5, 0x6579, 0x94B6, 0x657A, 0x94B7, + 0x657B, 0x94B8, 0x657C, 0x94B9, 0x657D, 0x94BA, 0x657E, 0x94BB, 0x657F, 0x94BC, 0x6580, 0x94BD, 0x6581, 0x94BE, 0x6582, 0x94BF, + 0x6583, 0x94C0, 0x6584, 0x94C1, 0x6585, 0x94C2, 0x6586, 0x94C3, 0x6587, 0xCEC4, 0x6588, 0x94C4, 0x6589, 0x94C5, 0x658A, 0x94C6, + 0x658B, 0xD5AB, 0x658C, 0xB1F3, 0x658D, 0x94C7, 0x658E, 0x94C8, 0x658F, 0x94C9, 0x6590, 0xECB3, 0x6591, 0xB0DF, 0x6592, 0x94CA, + 0x6593, 0xECB5, 0x6594, 0x94CB, 0x6595, 0x94CC, 0x6596, 0x94CD, 0x6597, 0xB6B7, 0x6598, 0x94CE, 0x6599, 0xC1CF, 0x659A, 0x94CF, + 0x659B, 0xF5FA, 0x659C, 0xD0B1, 0x659D, 0x94D0, 0x659E, 0x94D1, 0x659F, 0xD5E5, 0x65A0, 0x94D2, 0x65A1, 0xCED3, 0x65A2, 0x94D3, + 0x65A3, 0x94D4, 0x65A4, 0xBDEF, 0x65A5, 0xB3E2, 0x65A6, 0x94D5, 0x65A7, 0xB8AB, 0x65A8, 0x94D6, 0x65A9, 0xD5B6, 0x65AA, 0x94D7, + 0x65AB, 0xEDBD, 0x65AC, 0x94D8, 0x65AD, 0xB6CF, 0x65AE, 0x94D9, 0x65AF, 0xCBB9, 0x65B0, 0xD0C2, 0x65B1, 0x94DA, 0x65B2, 0x94DB, + 0x65B3, 0x94DC, 0x65B4, 0x94DD, 0x65B5, 0x94DE, 0x65B6, 0x94DF, 0x65B7, 0x94E0, 0x65B8, 0x94E1, 0x65B9, 0xB7BD, 0x65BA, 0x94E2, + 0x65BB, 0x94E3, 0x65BC, 0xECB6, 0x65BD, 0xCAA9, 0x65BE, 0x94E4, 0x65BF, 0x94E5, 0x65C0, 0x94E6, 0x65C1, 0xC5D4, 0x65C2, 0x94E7, + 0x65C3, 0xECB9, 0x65C4, 0xECB8, 0x65C5, 0xC2C3, 0x65C6, 0xECB7, 0x65C7, 0x94E8, 0x65C8, 0x94E9, 0x65C9, 0x94EA, 0x65CA, 0x94EB, + 0x65CB, 0xD0FD, 0x65CC, 0xECBA, 0x65CD, 0x94EC, 0x65CE, 0xECBB, 0x65CF, 0xD7E5, 0x65D0, 0x94ED, 0x65D1, 0x94EE, 0x65D2, 0xECBC, + 0x65D3, 0x94EF, 0x65D4, 0x94F0, 0x65D5, 0x94F1, 0x65D6, 0xECBD, 0x65D7, 0xC6EC, 0x65D8, 0x94F2, 0x65D9, 0x94F3, 0x65DA, 0x94F4, + 0x65DB, 0x94F5, 0x65DC, 0x94F6, 0x65DD, 0x94F7, 0x65DE, 0x94F8, 0x65DF, 0x94F9, 0x65E0, 0xCEDE, 0x65E1, 0x94FA, 0x65E2, 0xBCC8, + 0x65E3, 0x94FB, 0x65E4, 0x94FC, 0x65E5, 0xC8D5, 0x65E6, 0xB5A9, 0x65E7, 0xBEC9, 0x65E8, 0xD6BC, 0x65E9, 0xD4E7, 0x65EA, 0x94FD, + 0x65EB, 0x94FE, 0x65EC, 0xD1AE, 0x65ED, 0xD0F1, 0x65EE, 0xEAB8, 0x65EF, 0xEAB9, 0x65F0, 0xEABA, 0x65F1, 0xBAB5, 0x65F2, 0x9540, + 0x65F3, 0x9541, 0x65F4, 0x9542, 0x65F5, 0x9543, 0x65F6, 0xCAB1, 0x65F7, 0xBFF5, 0x65F8, 0x9544, 0x65F9, 0x9545, 0x65FA, 0xCDFA, + 0x65FB, 0x9546, 0x65FC, 0x9547, 0x65FD, 0x9548, 0x65FE, 0x9549, 0x65FF, 0x954A, 0x6600, 0xEAC0, 0x6601, 0x954B, 0x6602, 0xB0BA, + 0x6603, 0xEABE, 0x6604, 0x954C, 0x6605, 0x954D, 0x6606, 0xC0A5, 0x6607, 0x954E, 0x6608, 0x954F, 0x6609, 0x9550, 0x660A, 0xEABB, + 0x660B, 0x9551, 0x660C, 0xB2FD, 0x660D, 0x9552, 0x660E, 0xC3F7, 0x660F, 0xBBE8, 0x6610, 0x9553, 0x6611, 0x9554, 0x6612, 0x9555, + 0x6613, 0xD2D7, 0x6614, 0xCEF4, 0x6615, 0xEABF, 0x6616, 0x9556, 0x6617, 0x9557, 0x6618, 0x9558, 0x6619, 0xEABC, 0x661A, 0x9559, + 0x661B, 0x955A, 0x661C, 0x955B, 0x661D, 0xEAC3, 0x661E, 0x955C, 0x661F, 0xD0C7, 0x6620, 0xD3B3, 0x6621, 0x955D, 0x6622, 0x955E, + 0x6623, 0x955F, 0x6624, 0x9560, 0x6625, 0xB4BA, 0x6626, 0x9561, 0x6627, 0xC3C1, 0x6628, 0xD7F2, 0x6629, 0x9562, 0x662A, 0x9563, + 0x662B, 0x9564, 0x662C, 0x9565, 0x662D, 0xD5D1, 0x662E, 0x9566, 0x662F, 0xCAC7, 0x6630, 0x9567, 0x6631, 0xEAC5, 0x6632, 0x9568, + 0x6633, 0x9569, 0x6634, 0xEAC4, 0x6635, 0xEAC7, 0x6636, 0xEAC6, 0x6637, 0x956A, 0x6638, 0x956B, 0x6639, 0x956C, 0x663A, 0x956D, + 0x663B, 0x956E, 0x663C, 0xD6E7, 0x663D, 0x956F, 0x663E, 0xCFD4, 0x663F, 0x9570, 0x6640, 0x9571, 0x6641, 0xEACB, 0x6642, 0x9572, + 0x6643, 0xBBCE, 0x6644, 0x9573, 0x6645, 0x9574, 0x6646, 0x9575, 0x6647, 0x9576, 0x6648, 0x9577, 0x6649, 0x9578, 0x664A, 0x9579, + 0x664B, 0xBDFA, 0x664C, 0xC9CE, 0x664D, 0x957A, 0x664E, 0x957B, 0x664F, 0xEACC, 0x6650, 0x957C, 0x6651, 0x957D, 0x6652, 0xC9B9, + 0x6653, 0xCFFE, 0x6654, 0xEACA, 0x6655, 0xD4CE, 0x6656, 0xEACD, 0x6657, 0xEACF, 0x6658, 0x957E, 0x6659, 0x9580, 0x665A, 0xCDED, + 0x665B, 0x9581, 0x665C, 0x9582, 0x665D, 0x9583, 0x665E, 0x9584, 0x665F, 0xEAC9, 0x6660, 0x9585, 0x6661, 0xEACE, 0x6662, 0x9586, + 0x6663, 0x9587, 0x6664, 0xCEEE, 0x6665, 0x9588, 0x6666, 0xBBDE, 0x6667, 0x9589, 0x6668, 0xB3BF, 0x6669, 0x958A, 0x666A, 0x958B, + 0x666B, 0x958C, 0x666C, 0x958D, 0x666D, 0x958E, 0x666E, 0xC6D5, 0x666F, 0xBEB0, 0x6670, 0xCEFA, 0x6671, 0x958F, 0x6672, 0x9590, + 0x6673, 0x9591, 0x6674, 0xC7E7, 0x6675, 0x9592, 0x6676, 0xBEA7, 0x6677, 0xEAD0, 0x6678, 0x9593, 0x6679, 0x9594, 0x667A, 0xD6C7, + 0x667B, 0x9595, 0x667C, 0x9596, 0x667D, 0x9597, 0x667E, 0xC1C0, 0x667F, 0x9598, 0x6680, 0x9599, 0x6681, 0x959A, 0x6682, 0xD4DD, + 0x6683, 0x959B, 0x6684, 0xEAD1, 0x6685, 0x959C, 0x6686, 0x959D, 0x6687, 0xCFBE, 0x6688, 0x959E, 0x6689, 0x959F, 0x668A, 0x95A0, + 0x668B, 0x95A1, 0x668C, 0xEAD2, 0x668D, 0x95A2, 0x668E, 0x95A3, 0x668F, 0x95A4, 0x6690, 0x95A5, 0x6691, 0xCAEE, 0x6692, 0x95A6, + 0x6693, 0x95A7, 0x6694, 0x95A8, 0x6695, 0x95A9, 0x6696, 0xC5AF, 0x6697, 0xB0B5, 0x6698, 0x95AA, 0x6699, 0x95AB, 0x669A, 0x95AC, + 0x669B, 0x95AD, 0x669C, 0x95AE, 0x669D, 0xEAD4, 0x669E, 0x95AF, 0x669F, 0x95B0, 0x66A0, 0x95B1, 0x66A1, 0x95B2, 0x66A2, 0x95B3, + 0x66A3, 0x95B4, 0x66A4, 0x95B5, 0x66A5, 0x95B6, 0x66A6, 0x95B7, 0x66A7, 0xEAD3, 0x66A8, 0xF4DF, 0x66A9, 0x95B8, 0x66AA, 0x95B9, + 0x66AB, 0x95BA, 0x66AC, 0x95BB, 0x66AD, 0x95BC, 0x66AE, 0xC4BA, 0x66AF, 0x95BD, 0x66B0, 0x95BE, 0x66B1, 0x95BF, 0x66B2, 0x95C0, + 0x66B3, 0x95C1, 0x66B4, 0xB1A9, 0x66B5, 0x95C2, 0x66B6, 0x95C3, 0x66B7, 0x95C4, 0x66B8, 0x95C5, 0x66B9, 0xE5DF, 0x66BA, 0x95C6, + 0x66BB, 0x95C7, 0x66BC, 0x95C8, 0x66BD, 0x95C9, 0x66BE, 0xEAD5, 0x66BF, 0x95CA, 0x66C0, 0x95CB, 0x66C1, 0x95CC, 0x66C2, 0x95CD, + 0x66C3, 0x95CE, 0x66C4, 0x95CF, 0x66C5, 0x95D0, 0x66C6, 0x95D1, 0x66C7, 0x95D2, 0x66C8, 0x95D3, 0x66C9, 0x95D4, 0x66CA, 0x95D5, + 0x66CB, 0x95D6, 0x66CC, 0x95D7, 0x66CD, 0x95D8, 0x66CE, 0x95D9, 0x66CF, 0x95DA, 0x66D0, 0x95DB, 0x66D1, 0x95DC, 0x66D2, 0x95DD, + 0x66D3, 0x95DE, 0x66D4, 0x95DF, 0x66D5, 0x95E0, 0x66D6, 0x95E1, 0x66D7, 0x95E2, 0x66D8, 0x95E3, 0x66D9, 0xCAEF, 0x66DA, 0x95E4, + 0x66DB, 0xEAD6, 0x66DC, 0xEAD7, 0x66DD, 0xC6D8, 0x66DE, 0x95E5, 0x66DF, 0x95E6, 0x66E0, 0x95E7, 0x66E1, 0x95E8, 0x66E2, 0x95E9, + 0x66E3, 0x95EA, 0x66E4, 0x95EB, 0x66E5, 0x95EC, 0x66E6, 0xEAD8, 0x66E7, 0x95ED, 0x66E8, 0x95EE, 0x66E9, 0xEAD9, 0x66EA, 0x95EF, + 0x66EB, 0x95F0, 0x66EC, 0x95F1, 0x66ED, 0x95F2, 0x66EE, 0x95F3, 0x66EF, 0x95F4, 0x66F0, 0xD4BB, 0x66F1, 0x95F5, 0x66F2, 0xC7FA, + 0x66F3, 0xD2B7, 0x66F4, 0xB8FC, 0x66F5, 0x95F6, 0x66F6, 0x95F7, 0x66F7, 0xEAC2, 0x66F8, 0x95F8, 0x66F9, 0xB2DC, 0x66FA, 0x95F9, + 0x66FB, 0x95FA, 0x66FC, 0xC2FC, 0x66FD, 0x95FB, 0x66FE, 0xD4F8, 0x66FF, 0xCCE6, 0x6700, 0xD7EE, 0x6701, 0x95FC, 0x6702, 0x95FD, + 0x6703, 0x95FE, 0x6704, 0x9640, 0x6705, 0x9641, 0x6706, 0x9642, 0x6707, 0x9643, 0x6708, 0xD4C2, 0x6709, 0xD3D0, 0x670A, 0xEBC3, + 0x670B, 0xC5F3, 0x670C, 0x9644, 0x670D, 0xB7FE, 0x670E, 0x9645, 0x670F, 0x9646, 0x6710, 0xEBD4, 0x6711, 0x9647, 0x6712, 0x9648, + 0x6713, 0x9649, 0x6714, 0xCBB7, 0x6715, 0xEBDE, 0x6716, 0x964A, 0x6717, 0xC0CA, 0x6718, 0x964B, 0x6719, 0x964C, 0x671A, 0x964D, + 0x671B, 0xCDFB, 0x671C, 0x964E, 0x671D, 0xB3AF, 0x671E, 0x964F, 0x671F, 0xC6DA, 0x6720, 0x9650, 0x6721, 0x9651, 0x6722, 0x9652, + 0x6723, 0x9653, 0x6724, 0x9654, 0x6725, 0x9655, 0x6726, 0xEBFC, 0x6727, 0x9656, 0x6728, 0xC4BE, 0x6729, 0x9657, 0x672A, 0xCEB4, + 0x672B, 0xC4A9, 0x672C, 0xB1BE, 0x672D, 0xD4FD, 0x672E, 0x9658, 0x672F, 0xCAF5, 0x6730, 0x9659, 0x6731, 0xD6EC, 0x6732, 0x965A, + 0x6733, 0x965B, 0x6734, 0xC6D3, 0x6735, 0xB6E4, 0x6736, 0x965C, 0x6737, 0x965D, 0x6738, 0x965E, 0x6739, 0x965F, 0x673A, 0xBBFA, + 0x673B, 0x9660, 0x673C, 0x9661, 0x673D, 0xD0E0, 0x673E, 0x9662, 0x673F, 0x9663, 0x6740, 0xC9B1, 0x6741, 0x9664, 0x6742, 0xD4D3, + 0x6743, 0xC8A8, 0x6744, 0x9665, 0x6745, 0x9666, 0x6746, 0xB8CB, 0x6747, 0x9667, 0x6748, 0xE8BE, 0x6749, 0xC9BC, 0x674A, 0x9668, + 0x674B, 0x9669, 0x674C, 0xE8BB, 0x674D, 0x966A, 0x674E, 0xC0EE, 0x674F, 0xD0D3, 0x6750, 0xB2C4, 0x6751, 0xB4E5, 0x6752, 0x966B, + 0x6753, 0xE8BC, 0x6754, 0x966C, 0x6755, 0x966D, 0x6756, 0xD5C8, 0x6757, 0x966E, 0x6758, 0x966F, 0x6759, 0x9670, 0x675A, 0x9671, + 0x675B, 0x9672, 0x675C, 0xB6C5, 0x675D, 0x9673, 0x675E, 0xE8BD, 0x675F, 0xCAF8, 0x6760, 0xB8DC, 0x6761, 0xCCF5, 0x6762, 0x9674, + 0x6763, 0x9675, 0x6764, 0x9676, 0x6765, 0xC0B4, 0x6766, 0x9677, 0x6767, 0x9678, 0x6768, 0xD1EE, 0x6769, 0xE8BF, 0x676A, 0xE8C2, + 0x676B, 0x9679, 0x676C, 0x967A, 0x676D, 0xBABC, 0x676E, 0x967B, 0x676F, 0xB1AD, 0x6770, 0xBDDC, 0x6771, 0x967C, 0x6772, 0xEABD, + 0x6773, 0xE8C3, 0x6774, 0x967D, 0x6775, 0xE8C6, 0x6776, 0x967E, 0x6777, 0xE8CB, 0x6778, 0x9680, 0x6779, 0x9681, 0x677A, 0x9682, + 0x677B, 0x9683, 0x677C, 0xE8CC, 0x677D, 0x9684, 0x677E, 0xCBC9, 0x677F, 0xB0E5, 0x6780, 0x9685, 0x6781, 0xBCAB, 0x6782, 0x9686, + 0x6783, 0x9687, 0x6784, 0xB9B9, 0x6785, 0x9688, 0x6786, 0x9689, 0x6787, 0xE8C1, 0x6788, 0x968A, 0x6789, 0xCDF7, 0x678A, 0x968B, + 0x678B, 0xE8CA, 0x678C, 0x968C, 0x678D, 0x968D, 0x678E, 0x968E, 0x678F, 0x968F, 0x6790, 0xCEF6, 0x6791, 0x9690, 0x6792, 0x9691, + 0x6793, 0x9692, 0x6794, 0x9693, 0x6795, 0xD5ED, 0x6796, 0x9694, 0x6797, 0xC1D6, 0x6798, 0xE8C4, 0x6799, 0x9695, 0x679A, 0xC3B6, + 0x679B, 0x9696, 0x679C, 0xB9FB, 0x679D, 0xD6A6, 0x679E, 0xE8C8, 0x679F, 0x9697, 0x67A0, 0x9698, 0x67A1, 0x9699, 0x67A2, 0xCAE0, + 0x67A3, 0xD4E6, 0x67A4, 0x969A, 0x67A5, 0xE8C0, 0x67A6, 0x969B, 0x67A7, 0xE8C5, 0x67A8, 0xE8C7, 0x67A9, 0x969C, 0x67AA, 0xC7B9, + 0x67AB, 0xB7E3, 0x67AC, 0x969D, 0x67AD, 0xE8C9, 0x67AE, 0x969E, 0x67AF, 0xBFDD, 0x67B0, 0xE8D2, 0x67B1, 0x969F, 0x67B2, 0x96A0, + 0x67B3, 0xE8D7, 0x67B4, 0x96A1, 0x67B5, 0xE8D5, 0x67B6, 0xBCDC, 0x67B7, 0xBCCF, 0x67B8, 0xE8DB, 0x67B9, 0x96A2, 0x67BA, 0x96A3, + 0x67BB, 0x96A4, 0x67BC, 0x96A5, 0x67BD, 0x96A6, 0x67BE, 0x96A7, 0x67BF, 0x96A8, 0x67C0, 0x96A9, 0x67C1, 0xE8DE, 0x67C2, 0x96AA, + 0x67C3, 0xE8DA, 0x67C4, 0xB1FA, 0x67C5, 0x96AB, 0x67C6, 0x96AC, 0x67C7, 0x96AD, 0x67C8, 0x96AE, 0x67C9, 0x96AF, 0x67CA, 0x96B0, + 0x67CB, 0x96B1, 0x67CC, 0x96B2, 0x67CD, 0x96B3, 0x67CE, 0x96B4, 0x67CF, 0xB0D8, 0x67D0, 0xC4B3, 0x67D1, 0xB8CC, 0x67D2, 0xC6E2, + 0x67D3, 0xC8BE, 0x67D4, 0xC8E1, 0x67D5, 0x96B5, 0x67D6, 0x96B6, 0x67D7, 0x96B7, 0x67D8, 0xE8CF, 0x67D9, 0xE8D4, 0x67DA, 0xE8D6, + 0x67DB, 0x96B8, 0x67DC, 0xB9F1, 0x67DD, 0xE8D8, 0x67DE, 0xD7F5, 0x67DF, 0x96B9, 0x67E0, 0xC4FB, 0x67E1, 0x96BA, 0x67E2, 0xE8DC, + 0x67E3, 0x96BB, 0x67E4, 0x96BC, 0x67E5, 0xB2E9, 0x67E6, 0x96BD, 0x67E7, 0x96BE, 0x67E8, 0x96BF, 0x67E9, 0xE8D1, 0x67EA, 0x96C0, + 0x67EB, 0x96C1, 0x67EC, 0xBCED, 0x67ED, 0x96C2, 0x67EE, 0x96C3, 0x67EF, 0xBFC2, 0x67F0, 0xE8CD, 0x67F1, 0xD6F9, 0x67F2, 0x96C4, + 0x67F3, 0xC1F8, 0x67F4, 0xB2F1, 0x67F5, 0x96C5, 0x67F6, 0x96C6, 0x67F7, 0x96C7, 0x67F8, 0x96C8, 0x67F9, 0x96C9, 0x67FA, 0x96CA, + 0x67FB, 0x96CB, 0x67FC, 0x96CC, 0x67FD, 0xE8DF, 0x67FE, 0x96CD, 0x67FF, 0xCAC1, 0x6800, 0xE8D9, 0x6801, 0x96CE, 0x6802, 0x96CF, + 0x6803, 0x96D0, 0x6804, 0x96D1, 0x6805, 0xD5A4, 0x6806, 0x96D2, 0x6807, 0xB1EA, 0x6808, 0xD5BB, 0x6809, 0xE8CE, 0x680A, 0xE8D0, + 0x680B, 0xB6B0, 0x680C, 0xE8D3, 0x680D, 0x96D3, 0x680E, 0xE8DD, 0x680F, 0xC0B8, 0x6810, 0x96D4, 0x6811, 0xCAF7, 0x6812, 0x96D5, + 0x6813, 0xCBA8, 0x6814, 0x96D6, 0x6815, 0x96D7, 0x6816, 0xC6DC, 0x6817, 0xC0F5, 0x6818, 0x96D8, 0x6819, 0x96D9, 0x681A, 0x96DA, + 0x681B, 0x96DB, 0x681C, 0x96DC, 0x681D, 0xE8E9, 0x681E, 0x96DD, 0x681F, 0x96DE, 0x6820, 0x96DF, 0x6821, 0xD0A3, 0x6822, 0x96E0, + 0x6823, 0x96E1, 0x6824, 0x96E2, 0x6825, 0x96E3, 0x6826, 0x96E4, 0x6827, 0x96E5, 0x6828, 0x96E6, 0x6829, 0xE8F2, 0x682A, 0xD6EA, + 0x682B, 0x96E7, 0x682C, 0x96E8, 0x682D, 0x96E9, 0x682E, 0x96EA, 0x682F, 0x96EB, 0x6830, 0x96EC, 0x6831, 0x96ED, 0x6832, 0xE8E0, + 0x6833, 0xE8E1, 0x6834, 0x96EE, 0x6835, 0x96EF, 0x6836, 0x96F0, 0x6837, 0xD1F9, 0x6838, 0xBACB, 0x6839, 0xB8F9, 0x683A, 0x96F1, + 0x683B, 0x96F2, 0x683C, 0xB8F1, 0x683D, 0xD4D4, 0x683E, 0xE8EF, 0x683F, 0x96F3, 0x6840, 0xE8EE, 0x6841, 0xE8EC, 0x6842, 0xB9F0, + 0x6843, 0xCCD2, 0x6844, 0xE8E6, 0x6845, 0xCEA6, 0x6846, 0xBFF2, 0x6847, 0x96F4, 0x6848, 0xB0B8, 0x6849, 0xE8F1, 0x684A, 0xE8F0, + 0x684B, 0x96F5, 0x684C, 0xD7C0, 0x684D, 0x96F6, 0x684E, 0xE8E4, 0x684F, 0x96F7, 0x6850, 0xCDA9, 0x6851, 0xC9A3, 0x6852, 0x96F8, + 0x6853, 0xBBB8, 0x6854, 0xBDDB, 0x6855, 0xE8EA, 0x6856, 0x96F9, 0x6857, 0x96FA, 0x6858, 0x96FB, 0x6859, 0x96FC, 0x685A, 0x96FD, + 0x685B, 0x96FE, 0x685C, 0x9740, 0x685D, 0x9741, 0x685E, 0x9742, 0x685F, 0x9743, 0x6860, 0xE8E2, 0x6861, 0xE8E3, 0x6862, 0xE8E5, + 0x6863, 0xB5B5, 0x6864, 0xE8E7, 0x6865, 0xC7C5, 0x6866, 0xE8EB, 0x6867, 0xE8ED, 0x6868, 0xBDB0, 0x6869, 0xD7AE, 0x686A, 0x9744, + 0x686B, 0xE8F8, 0x686C, 0x9745, 0x686D, 0x9746, 0x686E, 0x9747, 0x686F, 0x9748, 0x6870, 0x9749, 0x6871, 0x974A, 0x6872, 0x974B, + 0x6873, 0x974C, 0x6874, 0xE8F5, 0x6875, 0x974D, 0x6876, 0xCDB0, 0x6877, 0xE8F6, 0x6878, 0x974E, 0x6879, 0x974F, 0x687A, 0x9750, + 0x687B, 0x9751, 0x687C, 0x9752, 0x687D, 0x9753, 0x687E, 0x9754, 0x687F, 0x9755, 0x6880, 0x9756, 0x6881, 0xC1BA, 0x6882, 0x9757, + 0x6883, 0xE8E8, 0x6884, 0x9758, 0x6885, 0xC3B7, 0x6886, 0xB0F0, 0x6887, 0x9759, 0x6888, 0x975A, 0x6889, 0x975B, 0x688A, 0x975C, + 0x688B, 0x975D, 0x688C, 0x975E, 0x688D, 0x975F, 0x688E, 0x9760, 0x688F, 0xE8F4, 0x6890, 0x9761, 0x6891, 0x9762, 0x6892, 0x9763, + 0x6893, 0xE8F7, 0x6894, 0x9764, 0x6895, 0x9765, 0x6896, 0x9766, 0x6897, 0xB9A3, 0x6898, 0x9767, 0x6899, 0x9768, 0x689A, 0x9769, + 0x689B, 0x976A, 0x689C, 0x976B, 0x689D, 0x976C, 0x689E, 0x976D, 0x689F, 0x976E, 0x68A0, 0x976F, 0x68A1, 0x9770, 0x68A2, 0xC9D2, + 0x68A3, 0x9771, 0x68A4, 0x9772, 0x68A5, 0x9773, 0x68A6, 0xC3CE, 0x68A7, 0xCEE0, 0x68A8, 0xC0E6, 0x68A9, 0x9774, 0x68AA, 0x9775, + 0x68AB, 0x9776, 0x68AC, 0x9777, 0x68AD, 0xCBF3, 0x68AE, 0x9778, 0x68AF, 0xCCDD, 0x68B0, 0xD0B5, 0x68B1, 0x9779, 0x68B2, 0x977A, + 0x68B3, 0xCAE1, 0x68B4, 0x977B, 0x68B5, 0xE8F3, 0x68B6, 0x977C, 0x68B7, 0x977D, 0x68B8, 0x977E, 0x68B9, 0x9780, 0x68BA, 0x9781, + 0x68BB, 0x9782, 0x68BC, 0x9783, 0x68BD, 0x9784, 0x68BE, 0x9785, 0x68BF, 0x9786, 0x68C0, 0xBCEC, 0x68C1, 0x9787, 0x68C2, 0xE8F9, + 0x68C3, 0x9788, 0x68C4, 0x9789, 0x68C5, 0x978A, 0x68C6, 0x978B, 0x68C7, 0x978C, 0x68C8, 0x978D, 0x68C9, 0xC3DE, 0x68CA, 0x978E, + 0x68CB, 0xC6E5, 0x68CC, 0x978F, 0x68CD, 0xB9F7, 0x68CE, 0x9790, 0x68CF, 0x9791, 0x68D0, 0x9792, 0x68D1, 0x9793, 0x68D2, 0xB0F4, + 0x68D3, 0x9794, 0x68D4, 0x9795, 0x68D5, 0xD7D8, 0x68D6, 0x9796, 0x68D7, 0x9797, 0x68D8, 0xBCAC, 0x68D9, 0x9798, 0x68DA, 0xC5EF, + 0x68DB, 0x9799, 0x68DC, 0x979A, 0x68DD, 0x979B, 0x68DE, 0x979C, 0x68DF, 0x979D, 0x68E0, 0xCCC4, 0x68E1, 0x979E, 0x68E2, 0x979F, + 0x68E3, 0xE9A6, 0x68E4, 0x97A0, 0x68E5, 0x97A1, 0x68E6, 0x97A2, 0x68E7, 0x97A3, 0x68E8, 0x97A4, 0x68E9, 0x97A5, 0x68EA, 0x97A6, + 0x68EB, 0x97A7, 0x68EC, 0x97A8, 0x68ED, 0x97A9, 0x68EE, 0xC9AD, 0x68EF, 0x97AA, 0x68F0, 0xE9A2, 0x68F1, 0xC0E2, 0x68F2, 0x97AB, + 0x68F3, 0x97AC, 0x68F4, 0x97AD, 0x68F5, 0xBFC3, 0x68F6, 0x97AE, 0x68F7, 0x97AF, 0x68F8, 0x97B0, 0x68F9, 0xE8FE, 0x68FA, 0xB9D7, + 0x68FB, 0x97B1, 0x68FC, 0xE8FB, 0x68FD, 0x97B2, 0x68FE, 0x97B3, 0x68FF, 0x97B4, 0x6900, 0x97B5, 0x6901, 0xE9A4, 0x6902, 0x97B6, + 0x6903, 0x97B7, 0x6904, 0x97B8, 0x6905, 0xD2CE, 0x6906, 0x97B9, 0x6907, 0x97BA, 0x6908, 0x97BB, 0x6909, 0x97BC, 0x690A, 0x97BD, + 0x690B, 0xE9A3, 0x690C, 0x97BE, 0x690D, 0xD6B2, 0x690E, 0xD7B5, 0x690F, 0x97BF, 0x6910, 0xE9A7, 0x6911, 0x97C0, 0x6912, 0xBDB7, + 0x6913, 0x97C1, 0x6914, 0x97C2, 0x6915, 0x97C3, 0x6916, 0x97C4, 0x6917, 0x97C5, 0x6918, 0x97C6, 0x6919, 0x97C7, 0x691A, 0x97C8, + 0x691B, 0x97C9, 0x691C, 0x97CA, 0x691D, 0x97CB, 0x691E, 0x97CC, 0x691F, 0xE8FC, 0x6920, 0xE8FD, 0x6921, 0x97CD, 0x6922, 0x97CE, + 0x6923, 0x97CF, 0x6924, 0xE9A1, 0x6925, 0x97D0, 0x6926, 0x97D1, 0x6927, 0x97D2, 0x6928, 0x97D3, 0x6929, 0x97D4, 0x692A, 0x97D5, + 0x692B, 0x97D6, 0x692C, 0x97D7, 0x692D, 0xCDD6, 0x692E, 0x97D8, 0x692F, 0x97D9, 0x6930, 0xD2AC, 0x6931, 0x97DA, 0x6932, 0x97DB, + 0x6933, 0x97DC, 0x6934, 0xE9B2, 0x6935, 0x97DD, 0x6936, 0x97DE, 0x6937, 0x97DF, 0x6938, 0x97E0, 0x6939, 0xE9A9, 0x693A, 0x97E1, + 0x693B, 0x97E2, 0x693C, 0x97E3, 0x693D, 0xB4AA, 0x693E, 0x97E4, 0x693F, 0xB4BB, 0x6940, 0x97E5, 0x6941, 0x97E6, 0x6942, 0xE9AB, + 0x6943, 0x97E7, 0x6944, 0x97E8, 0x6945, 0x97E9, 0x6946, 0x97EA, 0x6947, 0x97EB, 0x6948, 0x97EC, 0x6949, 0x97ED, 0x694A, 0x97EE, + 0x694B, 0x97EF, 0x694C, 0x97F0, 0x694D, 0x97F1, 0x694E, 0x97F2, 0x694F, 0x97F3, 0x6950, 0x97F4, 0x6951, 0x97F5, 0x6952, 0x97F6, + 0x6953, 0x97F7, 0x6954, 0xD0A8, 0x6955, 0x97F8, 0x6956, 0x97F9, 0x6957, 0xE9A5, 0x6958, 0x97FA, 0x6959, 0x97FB, 0x695A, 0xB3FE, + 0x695B, 0x97FC, 0x695C, 0x97FD, 0x695D, 0xE9AC, 0x695E, 0xC0E3, 0x695F, 0x97FE, 0x6960, 0xE9AA, 0x6961, 0x9840, 0x6962, 0x9841, + 0x6963, 0xE9B9, 0x6964, 0x9842, 0x6965, 0x9843, 0x6966, 0xE9B8, 0x6967, 0x9844, 0x6968, 0x9845, 0x6969, 0x9846, 0x696A, 0x9847, + 0x696B, 0xE9AE, 0x696C, 0x9848, 0x696D, 0x9849, 0x696E, 0xE8FA, 0x696F, 0x984A, 0x6970, 0x984B, 0x6971, 0xE9A8, 0x6972, 0x984C, + 0x6973, 0x984D, 0x6974, 0x984E, 0x6975, 0x984F, 0x6976, 0x9850, 0x6977, 0xBFAC, 0x6978, 0xE9B1, 0x6979, 0xE9BA, 0x697A, 0x9851, + 0x697B, 0x9852, 0x697C, 0xC2A5, 0x697D, 0x9853, 0x697E, 0x9854, 0x697F, 0x9855, 0x6980, 0xE9AF, 0x6981, 0x9856, 0x6982, 0xB8C5, + 0x6983, 0x9857, 0x6984, 0xE9AD, 0x6985, 0x9858, 0x6986, 0xD3DC, 0x6987, 0xE9B4, 0x6988, 0xE9B5, 0x6989, 0xE9B7, 0x698A, 0x9859, + 0x698B, 0x985A, 0x698C, 0x985B, 0x698D, 0xE9C7, 0x698E, 0x985C, 0x698F, 0x985D, 0x6990, 0x985E, 0x6991, 0x985F, 0x6992, 0x9860, + 0x6993, 0x9861, 0x6994, 0xC0C6, 0x6995, 0xE9C5, 0x6996, 0x9862, 0x6997, 0x9863, 0x6998, 0xE9B0, 0x6999, 0x9864, 0x699A, 0x9865, + 0x699B, 0xE9BB, 0x699C, 0xB0F1, 0x699D, 0x9866, 0x699E, 0x9867, 0x699F, 0x9868, 0x69A0, 0x9869, 0x69A1, 0x986A, 0x69A2, 0x986B, + 0x69A3, 0x986C, 0x69A4, 0x986D, 0x69A5, 0x986E, 0x69A6, 0x986F, 0x69A7, 0xE9BC, 0x69A8, 0xD5A5, 0x69A9, 0x9870, 0x69AA, 0x9871, + 0x69AB, 0xE9BE, 0x69AC, 0x9872, 0x69AD, 0xE9BF, 0x69AE, 0x9873, 0x69AF, 0x9874, 0x69B0, 0x9875, 0x69B1, 0xE9C1, 0x69B2, 0x9876, + 0x69B3, 0x9877, 0x69B4, 0xC1F1, 0x69B5, 0x9878, 0x69B6, 0x9879, 0x69B7, 0xC8B6, 0x69B8, 0x987A, 0x69B9, 0x987B, 0x69BA, 0x987C, + 0x69BB, 0xE9BD, 0x69BC, 0x987D, 0x69BD, 0x987E, 0x69BE, 0x9880, 0x69BF, 0x9881, 0x69C0, 0x9882, 0x69C1, 0xE9C2, 0x69C2, 0x9883, + 0x69C3, 0x9884, 0x69C4, 0x9885, 0x69C5, 0x9886, 0x69C6, 0x9887, 0x69C7, 0x9888, 0x69C8, 0x9889, 0x69C9, 0x988A, 0x69CA, 0xE9C3, + 0x69CB, 0x988B, 0x69CC, 0xE9B3, 0x69CD, 0x988C, 0x69CE, 0xE9B6, 0x69CF, 0x988D, 0x69D0, 0xBBB1, 0x69D1, 0x988E, 0x69D2, 0x988F, + 0x69D3, 0x9890, 0x69D4, 0xE9C0, 0x69D5, 0x9891, 0x69D6, 0x9892, 0x69D7, 0x9893, 0x69D8, 0x9894, 0x69D9, 0x9895, 0x69DA, 0x9896, + 0x69DB, 0xBCF7, 0x69DC, 0x9897, 0x69DD, 0x9898, 0x69DE, 0x9899, 0x69DF, 0xE9C4, 0x69E0, 0xE9C6, 0x69E1, 0x989A, 0x69E2, 0x989B, + 0x69E3, 0x989C, 0x69E4, 0x989D, 0x69E5, 0x989E, 0x69E6, 0x989F, 0x69E7, 0x98A0, 0x69E8, 0x98A1, 0x69E9, 0x98A2, 0x69EA, 0x98A3, + 0x69EB, 0x98A4, 0x69EC, 0x98A5, 0x69ED, 0xE9CA, 0x69EE, 0x98A6, 0x69EF, 0x98A7, 0x69F0, 0x98A8, 0x69F1, 0x98A9, 0x69F2, 0xE9CE, + 0x69F3, 0x98AA, 0x69F4, 0x98AB, 0x69F5, 0x98AC, 0x69F6, 0x98AD, 0x69F7, 0x98AE, 0x69F8, 0x98AF, 0x69F9, 0x98B0, 0x69FA, 0x98B1, + 0x69FB, 0x98B2, 0x69FC, 0x98B3, 0x69FD, 0xB2DB, 0x69FE, 0x98B4, 0x69FF, 0xE9C8, 0x6A00, 0x98B5, 0x6A01, 0x98B6, 0x6A02, 0x98B7, + 0x6A03, 0x98B8, 0x6A04, 0x98B9, 0x6A05, 0x98BA, 0x6A06, 0x98BB, 0x6A07, 0x98BC, 0x6A08, 0x98BD, 0x6A09, 0x98BE, 0x6A0A, 0xB7AE, + 0x6A0B, 0x98BF, 0x6A0C, 0x98C0, 0x6A0D, 0x98C1, 0x6A0E, 0x98C2, 0x6A0F, 0x98C3, 0x6A10, 0x98C4, 0x6A11, 0x98C5, 0x6A12, 0x98C6, + 0x6A13, 0x98C7, 0x6A14, 0x98C8, 0x6A15, 0x98C9, 0x6A16, 0x98CA, 0x6A17, 0xE9CB, 0x6A18, 0xE9CC, 0x6A19, 0x98CB, 0x6A1A, 0x98CC, + 0x6A1B, 0x98CD, 0x6A1C, 0x98CE, 0x6A1D, 0x98CF, 0x6A1E, 0x98D0, 0x6A1F, 0xD5C1, 0x6A20, 0x98D1, 0x6A21, 0xC4A3, 0x6A22, 0x98D2, + 0x6A23, 0x98D3, 0x6A24, 0x98D4, 0x6A25, 0x98D5, 0x6A26, 0x98D6, 0x6A27, 0x98D7, 0x6A28, 0xE9D8, 0x6A29, 0x98D8, 0x6A2A, 0xBAE1, + 0x6A2B, 0x98D9, 0x6A2C, 0x98DA, 0x6A2D, 0x98DB, 0x6A2E, 0x98DC, 0x6A2F, 0xE9C9, 0x6A30, 0x98DD, 0x6A31, 0xD3A3, 0x6A32, 0x98DE, + 0x6A33, 0x98DF, 0x6A34, 0x98E0, 0x6A35, 0xE9D4, 0x6A36, 0x98E1, 0x6A37, 0x98E2, 0x6A38, 0x98E3, 0x6A39, 0x98E4, 0x6A3A, 0x98E5, + 0x6A3B, 0x98E6, 0x6A3C, 0x98E7, 0x6A3D, 0xE9D7, 0x6A3E, 0xE9D0, 0x6A3F, 0x98E8, 0x6A40, 0x98E9, 0x6A41, 0x98EA, 0x6A42, 0x98EB, + 0x6A43, 0x98EC, 0x6A44, 0xE9CF, 0x6A45, 0x98ED, 0x6A46, 0x98EE, 0x6A47, 0xC7C1, 0x6A48, 0x98EF, 0x6A49, 0x98F0, 0x6A4A, 0x98F1, + 0x6A4B, 0x98F2, 0x6A4C, 0x98F3, 0x6A4D, 0x98F4, 0x6A4E, 0x98F5, 0x6A4F, 0x98F6, 0x6A50, 0xE9D2, 0x6A51, 0x98F7, 0x6A52, 0x98F8, + 0x6A53, 0x98F9, 0x6A54, 0x98FA, 0x6A55, 0x98FB, 0x6A56, 0x98FC, 0x6A57, 0x98FD, 0x6A58, 0xE9D9, 0x6A59, 0xB3C8, 0x6A5A, 0x98FE, + 0x6A5B, 0xE9D3, 0x6A5C, 0x9940, 0x6A5D, 0x9941, 0x6A5E, 0x9942, 0x6A5F, 0x9943, 0x6A60, 0x9944, 0x6A61, 0xCFF0, 0x6A62, 0x9945, + 0x6A63, 0x9946, 0x6A64, 0x9947, 0x6A65, 0xE9CD, 0x6A66, 0x9948, 0x6A67, 0x9949, 0x6A68, 0x994A, 0x6A69, 0x994B, 0x6A6A, 0x994C, + 0x6A6B, 0x994D, 0x6A6C, 0x994E, 0x6A6D, 0x994F, 0x6A6E, 0x9950, 0x6A6F, 0x9951, 0x6A70, 0x9952, 0x6A71, 0xB3F7, 0x6A72, 0x9953, + 0x6A73, 0x9954, 0x6A74, 0x9955, 0x6A75, 0x9956, 0x6A76, 0x9957, 0x6A77, 0x9958, 0x6A78, 0x9959, 0x6A79, 0xE9D6, 0x6A7A, 0x995A, + 0x6A7B, 0x995B, 0x6A7C, 0xE9DA, 0x6A7D, 0x995C, 0x6A7E, 0x995D, 0x6A7F, 0x995E, 0x6A80, 0xCCB4, 0x6A81, 0x995F, 0x6A82, 0x9960, + 0x6A83, 0x9961, 0x6A84, 0xCFAD, 0x6A85, 0x9962, 0x6A86, 0x9963, 0x6A87, 0x9964, 0x6A88, 0x9965, 0x6A89, 0x9966, 0x6A8A, 0x9967, + 0x6A8B, 0x9968, 0x6A8C, 0x9969, 0x6A8D, 0x996A, 0x6A8E, 0xE9D5, 0x6A8F, 0x996B, 0x6A90, 0xE9DC, 0x6A91, 0xE9DB, 0x6A92, 0x996C, + 0x6A93, 0x996D, 0x6A94, 0x996E, 0x6A95, 0x996F, 0x6A96, 0x9970, 0x6A97, 0xE9DE, 0x6A98, 0x9971, 0x6A99, 0x9972, 0x6A9A, 0x9973, + 0x6A9B, 0x9974, 0x6A9C, 0x9975, 0x6A9D, 0x9976, 0x6A9E, 0x9977, 0x6A9F, 0x9978, 0x6AA0, 0xE9D1, 0x6AA1, 0x9979, 0x6AA2, 0x997A, + 0x6AA3, 0x997B, 0x6AA4, 0x997C, 0x6AA5, 0x997D, 0x6AA6, 0x997E, 0x6AA7, 0x9980, 0x6AA8, 0x9981, 0x6AA9, 0xE9DD, 0x6AAA, 0x9982, + 0x6AAB, 0xE9DF, 0x6AAC, 0xC3CA, 0x6AAD, 0x9983, 0x6AAE, 0x9984, 0x6AAF, 0x9985, 0x6AB0, 0x9986, 0x6AB1, 0x9987, 0x6AB2, 0x9988, + 0x6AB3, 0x9989, 0x6AB4, 0x998A, 0x6AB5, 0x998B, 0x6AB6, 0x998C, 0x6AB7, 0x998D, 0x6AB8, 0x998E, 0x6AB9, 0x998F, 0x6ABA, 0x9990, + 0x6ABB, 0x9991, 0x6ABC, 0x9992, 0x6ABD, 0x9993, 0x6ABE, 0x9994, 0x6ABF, 0x9995, 0x6AC0, 0x9996, 0x6AC1, 0x9997, 0x6AC2, 0x9998, + 0x6AC3, 0x9999, 0x6AC4, 0x999A, 0x6AC5, 0x999B, 0x6AC6, 0x999C, 0x6AC7, 0x999D, 0x6AC8, 0x999E, 0x6AC9, 0x999F, 0x6ACA, 0x99A0, + 0x6ACB, 0x99A1, 0x6ACC, 0x99A2, 0x6ACD, 0x99A3, 0x6ACE, 0x99A4, 0x6ACF, 0x99A5, 0x6AD0, 0x99A6, 0x6AD1, 0x99A7, 0x6AD2, 0x99A8, + 0x6AD3, 0x99A9, 0x6AD4, 0x99AA, 0x6AD5, 0x99AB, 0x6AD6, 0x99AC, 0x6AD7, 0x99AD, 0x6AD8, 0x99AE, 0x6AD9, 0x99AF, 0x6ADA, 0x99B0, + 0x6ADB, 0x99B1, 0x6ADC, 0x99B2, 0x6ADD, 0x99B3, 0x6ADE, 0x99B4, 0x6ADF, 0x99B5, 0x6AE0, 0x99B6, 0x6AE1, 0x99B7, 0x6AE2, 0x99B8, + 0x6AE3, 0x99B9, 0x6AE4, 0x99BA, 0x6AE5, 0x99BB, 0x6AE6, 0x99BC, 0x6AE7, 0x99BD, 0x6AE8, 0x99BE, 0x6AE9, 0x99BF, 0x6AEA, 0x99C0, + 0x6AEB, 0x99C1, 0x6AEC, 0x99C2, 0x6AED, 0x99C3, 0x6AEE, 0x99C4, 0x6AEF, 0x99C5, 0x6AF0, 0x99C6, 0x6AF1, 0x99C7, 0x6AF2, 0x99C8, + 0x6AF3, 0x99C9, 0x6AF4, 0x99CA, 0x6AF5, 0x99CB, 0x6AF6, 0x99CC, 0x6AF7, 0x99CD, 0x6AF8, 0x99CE, 0x6AF9, 0x99CF, 0x6AFA, 0x99D0, + 0x6AFB, 0x99D1, 0x6AFC, 0x99D2, 0x6AFD, 0x99D3, 0x6AFE, 0x99D4, 0x6AFF, 0x99D5, 0x6B00, 0x99D6, 0x6B01, 0x99D7, 0x6B02, 0x99D8, + 0x6B03, 0x99D9, 0x6B04, 0x99DA, 0x6B05, 0x99DB, 0x6B06, 0x99DC, 0x6B07, 0x99DD, 0x6B08, 0x99DE, 0x6B09, 0x99DF, 0x6B0A, 0x99E0, + 0x6B0B, 0x99E1, 0x6B0C, 0x99E2, 0x6B0D, 0x99E3, 0x6B0E, 0x99E4, 0x6B0F, 0x99E5, 0x6B10, 0x99E6, 0x6B11, 0x99E7, 0x6B12, 0x99E8, + 0x6B13, 0x99E9, 0x6B14, 0x99EA, 0x6B15, 0x99EB, 0x6B16, 0x99EC, 0x6B17, 0x99ED, 0x6B18, 0x99EE, 0x6B19, 0x99EF, 0x6B1A, 0x99F0, + 0x6B1B, 0x99F1, 0x6B1C, 0x99F2, 0x6B1D, 0x99F3, 0x6B1E, 0x99F4, 0x6B1F, 0x99F5, 0x6B20, 0xC7B7, 0x6B21, 0xB4CE, 0x6B22, 0xBBB6, + 0x6B23, 0xD0C0, 0x6B24, 0xECA3, 0x6B25, 0x99F6, 0x6B26, 0x99F7, 0x6B27, 0xC5B7, 0x6B28, 0x99F8, 0x6B29, 0x99F9, 0x6B2A, 0x99FA, + 0x6B2B, 0x99FB, 0x6B2C, 0x99FC, 0x6B2D, 0x99FD, 0x6B2E, 0x99FE, 0x6B2F, 0x9A40, 0x6B30, 0x9A41, 0x6B31, 0x9A42, 0x6B32, 0xD3FB, + 0x6B33, 0x9A43, 0x6B34, 0x9A44, 0x6B35, 0x9A45, 0x6B36, 0x9A46, 0x6B37, 0xECA4, 0x6B38, 0x9A47, 0x6B39, 0xECA5, 0x6B3A, 0xC6DB, + 0x6B3B, 0x9A48, 0x6B3C, 0x9A49, 0x6B3D, 0x9A4A, 0x6B3E, 0xBFEE, 0x6B3F, 0x9A4B, 0x6B40, 0x9A4C, 0x6B41, 0x9A4D, 0x6B42, 0x9A4E, + 0x6B43, 0xECA6, 0x6B44, 0x9A4F, 0x6B45, 0x9A50, 0x6B46, 0xECA7, 0x6B47, 0xD0AA, 0x6B48, 0x9A51, 0x6B49, 0xC7B8, 0x6B4A, 0x9A52, + 0x6B4B, 0x9A53, 0x6B4C, 0xB8E8, 0x6B4D, 0x9A54, 0x6B4E, 0x9A55, 0x6B4F, 0x9A56, 0x6B50, 0x9A57, 0x6B51, 0x9A58, 0x6B52, 0x9A59, + 0x6B53, 0x9A5A, 0x6B54, 0x9A5B, 0x6B55, 0x9A5C, 0x6B56, 0x9A5D, 0x6B57, 0x9A5E, 0x6B58, 0x9A5F, 0x6B59, 0xECA8, 0x6B5A, 0x9A60, + 0x6B5B, 0x9A61, 0x6B5C, 0x9A62, 0x6B5D, 0x9A63, 0x6B5E, 0x9A64, 0x6B5F, 0x9A65, 0x6B60, 0x9A66, 0x6B61, 0x9A67, 0x6B62, 0xD6B9, + 0x6B63, 0xD5FD, 0x6B64, 0xB4CB, 0x6B65, 0xB2BD, 0x6B66, 0xCEE4, 0x6B67, 0xC6E7, 0x6B68, 0x9A68, 0x6B69, 0x9A69, 0x6B6A, 0xCDE1, + 0x6B6B, 0x9A6A, 0x6B6C, 0x9A6B, 0x6B6D, 0x9A6C, 0x6B6E, 0x9A6D, 0x6B6F, 0x9A6E, 0x6B70, 0x9A6F, 0x6B71, 0x9A70, 0x6B72, 0x9A71, + 0x6B73, 0x9A72, 0x6B74, 0x9A73, 0x6B75, 0x9A74, 0x6B76, 0x9A75, 0x6B77, 0x9A76, 0x6B78, 0x9A77, 0x6B79, 0xB4F5, 0x6B7A, 0x9A78, + 0x6B7B, 0xCBC0, 0x6B7C, 0xBCDF, 0x6B7D, 0x9A79, 0x6B7E, 0x9A7A, 0x6B7F, 0x9A7B, 0x6B80, 0x9A7C, 0x6B81, 0xE9E2, 0x6B82, 0xE9E3, + 0x6B83, 0xD1EA, 0x6B84, 0xE9E5, 0x6B85, 0x9A7D, 0x6B86, 0xB4F9, 0x6B87, 0xE9E4, 0x6B88, 0x9A7E, 0x6B89, 0xD1B3, 0x6B8A, 0xCAE2, + 0x6B8B, 0xB2D0, 0x6B8C, 0x9A80, 0x6B8D, 0xE9E8, 0x6B8E, 0x9A81, 0x6B8F, 0x9A82, 0x6B90, 0x9A83, 0x6B91, 0x9A84, 0x6B92, 0xE9E6, + 0x6B93, 0xE9E7, 0x6B94, 0x9A85, 0x6B95, 0x9A86, 0x6B96, 0xD6B3, 0x6B97, 0x9A87, 0x6B98, 0x9A88, 0x6B99, 0x9A89, 0x6B9A, 0xE9E9, + 0x6B9B, 0xE9EA, 0x6B9C, 0x9A8A, 0x6B9D, 0x9A8B, 0x6B9E, 0x9A8C, 0x6B9F, 0x9A8D, 0x6BA0, 0x9A8E, 0x6BA1, 0xE9EB, 0x6BA2, 0x9A8F, + 0x6BA3, 0x9A90, 0x6BA4, 0x9A91, 0x6BA5, 0x9A92, 0x6BA6, 0x9A93, 0x6BA7, 0x9A94, 0x6BA8, 0x9A95, 0x6BA9, 0x9A96, 0x6BAA, 0xE9EC, + 0x6BAB, 0x9A97, 0x6BAC, 0x9A98, 0x6BAD, 0x9A99, 0x6BAE, 0x9A9A, 0x6BAF, 0x9A9B, 0x6BB0, 0x9A9C, 0x6BB1, 0x9A9D, 0x6BB2, 0x9A9E, + 0x6BB3, 0xECAF, 0x6BB4, 0xC5B9, 0x6BB5, 0xB6CE, 0x6BB6, 0x9A9F, 0x6BB7, 0xD2F3, 0x6BB8, 0x9AA0, 0x6BB9, 0x9AA1, 0x6BBA, 0x9AA2, + 0x6BBB, 0x9AA3, 0x6BBC, 0x9AA4, 0x6BBD, 0x9AA5, 0x6BBE, 0x9AA6, 0x6BBF, 0xB5EE, 0x6BC0, 0x9AA7, 0x6BC1, 0xBBD9, 0x6BC2, 0xECB1, + 0x6BC3, 0x9AA8, 0x6BC4, 0x9AA9, 0x6BC5, 0xD2E3, 0x6BC6, 0x9AAA, 0x6BC7, 0x9AAB, 0x6BC8, 0x9AAC, 0x6BC9, 0x9AAD, 0x6BCA, 0x9AAE, + 0x6BCB, 0xCEE3, 0x6BCC, 0x9AAF, 0x6BCD, 0xC4B8, 0x6BCE, 0x9AB0, 0x6BCF, 0xC3BF, 0x6BD0, 0x9AB1, 0x6BD1, 0x9AB2, 0x6BD2, 0xB6BE, + 0x6BD3, 0xD8B9, 0x6BD4, 0xB1C8, 0x6BD5, 0xB1CF, 0x6BD6, 0xB1D1, 0x6BD7, 0xC5FE, 0x6BD8, 0x9AB3, 0x6BD9, 0xB1D0, 0x6BDA, 0x9AB4, + 0x6BDB, 0xC3AB, 0x6BDC, 0x9AB5, 0x6BDD, 0x9AB6, 0x6BDE, 0x9AB7, 0x6BDF, 0x9AB8, 0x6BE0, 0x9AB9, 0x6BE1, 0xD5B1, 0x6BE2, 0x9ABA, + 0x6BE3, 0x9ABB, 0x6BE4, 0x9ABC, 0x6BE5, 0x9ABD, 0x6BE6, 0x9ABE, 0x6BE7, 0x9ABF, 0x6BE8, 0x9AC0, 0x6BE9, 0x9AC1, 0x6BEA, 0xEBA4, + 0x6BEB, 0xBAC1, 0x6BEC, 0x9AC2, 0x6BED, 0x9AC3, 0x6BEE, 0x9AC4, 0x6BEF, 0xCCBA, 0x6BF0, 0x9AC5, 0x6BF1, 0x9AC6, 0x6BF2, 0x9AC7, + 0x6BF3, 0xEBA5, 0x6BF4, 0x9AC8, 0x6BF5, 0xEBA7, 0x6BF6, 0x9AC9, 0x6BF7, 0x9ACA, 0x6BF8, 0x9ACB, 0x6BF9, 0xEBA8, 0x6BFA, 0x9ACC, + 0x6BFB, 0x9ACD, 0x6BFC, 0x9ACE, 0x6BFD, 0xEBA6, 0x6BFE, 0x9ACF, 0x6BFF, 0x9AD0, 0x6C00, 0x9AD1, 0x6C01, 0x9AD2, 0x6C02, 0x9AD3, + 0x6C03, 0x9AD4, 0x6C04, 0x9AD5, 0x6C05, 0xEBA9, 0x6C06, 0xEBAB, 0x6C07, 0xEBAA, 0x6C08, 0x9AD6, 0x6C09, 0x9AD7, 0x6C0A, 0x9AD8, + 0x6C0B, 0x9AD9, 0x6C0C, 0x9ADA, 0x6C0D, 0xEBAC, 0x6C0E, 0x9ADB, 0x6C0F, 0xCACF, 0x6C10, 0xD8B5, 0x6C11, 0xC3F1, 0x6C12, 0x9ADC, + 0x6C13, 0xC3A5, 0x6C14, 0xC6F8, 0x6C15, 0xEBAD, 0x6C16, 0xC4CA, 0x6C17, 0x9ADD, 0x6C18, 0xEBAE, 0x6C19, 0xEBAF, 0x6C1A, 0xEBB0, + 0x6C1B, 0xB7D5, 0x6C1C, 0x9ADE, 0x6C1D, 0x9ADF, 0x6C1E, 0x9AE0, 0x6C1F, 0xB7FA, 0x6C20, 0x9AE1, 0x6C21, 0xEBB1, 0x6C22, 0xC7E2, + 0x6C23, 0x9AE2, 0x6C24, 0xEBB3, 0x6C25, 0x9AE3, 0x6C26, 0xBAA4, 0x6C27, 0xD1F5, 0x6C28, 0xB0B1, 0x6C29, 0xEBB2, 0x6C2A, 0xEBB4, + 0x6C2B, 0x9AE4, 0x6C2C, 0x9AE5, 0x6C2D, 0x9AE6, 0x6C2E, 0xB5AA, 0x6C2F, 0xC2C8, 0x6C30, 0xC7E8, 0x6C31, 0x9AE7, 0x6C32, 0xEBB5, + 0x6C33, 0x9AE8, 0x6C34, 0xCBAE, 0x6C35, 0xE3DF, 0x6C36, 0x9AE9, 0x6C37, 0x9AEA, 0x6C38, 0xD3C0, 0x6C39, 0x9AEB, 0x6C3A, 0x9AEC, + 0x6C3B, 0x9AED, 0x6C3C, 0x9AEE, 0x6C3D, 0xD9DB, 0x6C3E, 0x9AEF, 0x6C3F, 0x9AF0, 0x6C40, 0xCDA1, 0x6C41, 0xD6AD, 0x6C42, 0xC7F3, + 0x6C43, 0x9AF1, 0x6C44, 0x9AF2, 0x6C45, 0x9AF3, 0x6C46, 0xD9E0, 0x6C47, 0xBBE3, 0x6C48, 0x9AF4, 0x6C49, 0xBABA, 0x6C4A, 0xE3E2, + 0x6C4B, 0x9AF5, 0x6C4C, 0x9AF6, 0x6C4D, 0x9AF7, 0x6C4E, 0x9AF8, 0x6C4F, 0x9AF9, 0x6C50, 0xCFAB, 0x6C51, 0x9AFA, 0x6C52, 0x9AFB, + 0x6C53, 0x9AFC, 0x6C54, 0xE3E0, 0x6C55, 0xC9C7, 0x6C56, 0x9AFD, 0x6C57, 0xBAB9, 0x6C58, 0x9AFE, 0x6C59, 0x9B40, 0x6C5A, 0x9B41, + 0x6C5B, 0xD1B4, 0x6C5C, 0xE3E1, 0x6C5D, 0xC8EA, 0x6C5E, 0xB9AF, 0x6C5F, 0xBDAD, 0x6C60, 0xB3D8, 0x6C61, 0xCEDB, 0x6C62, 0x9B42, + 0x6C63, 0x9B43, 0x6C64, 0xCCC0, 0x6C65, 0x9B44, 0x6C66, 0x9B45, 0x6C67, 0x9B46, 0x6C68, 0xE3E8, 0x6C69, 0xE3E9, 0x6C6A, 0xCDF4, + 0x6C6B, 0x9B47, 0x6C6C, 0x9B48, 0x6C6D, 0x9B49, 0x6C6E, 0x9B4A, 0x6C6F, 0x9B4B, 0x6C70, 0xCCAD, 0x6C71, 0x9B4C, 0x6C72, 0xBCB3, + 0x6C73, 0x9B4D, 0x6C74, 0xE3EA, 0x6C75, 0x9B4E, 0x6C76, 0xE3EB, 0x6C77, 0x9B4F, 0x6C78, 0x9B50, 0x6C79, 0xD0DA, 0x6C7A, 0x9B51, + 0x6C7B, 0x9B52, 0x6C7C, 0x9B53, 0x6C7D, 0xC6FB, 0x6C7E, 0xB7DA, 0x6C7F, 0x9B54, 0x6C80, 0x9B55, 0x6C81, 0xC7DF, 0x6C82, 0xD2CA, + 0x6C83, 0xCED6, 0x6C84, 0x9B56, 0x6C85, 0xE3E4, 0x6C86, 0xE3EC, 0x6C87, 0x9B57, 0x6C88, 0xC9F2, 0x6C89, 0xB3C1, 0x6C8A, 0x9B58, + 0x6C8B, 0x9B59, 0x6C8C, 0xE3E7, 0x6C8D, 0x9B5A, 0x6C8E, 0x9B5B, 0x6C8F, 0xC6E3, 0x6C90, 0xE3E5, 0x6C91, 0x9B5C, 0x6C92, 0x9B5D, + 0x6C93, 0xEDB3, 0x6C94, 0xE3E6, 0x6C95, 0x9B5E, 0x6C96, 0x9B5F, 0x6C97, 0x9B60, 0x6C98, 0x9B61, 0x6C99, 0xC9B3, 0x6C9A, 0x9B62, + 0x6C9B, 0xC5E6, 0x6C9C, 0x9B63, 0x6C9D, 0x9B64, 0x6C9E, 0x9B65, 0x6C9F, 0xB9B5, 0x6CA0, 0x9B66, 0x6CA1, 0xC3BB, 0x6CA2, 0x9B67, + 0x6CA3, 0xE3E3, 0x6CA4, 0xC5BD, 0x6CA5, 0xC1A4, 0x6CA6, 0xC2D9, 0x6CA7, 0xB2D7, 0x6CA8, 0x9B68, 0x6CA9, 0xE3ED, 0x6CAA, 0xBBA6, + 0x6CAB, 0xC4AD, 0x6CAC, 0x9B69, 0x6CAD, 0xE3F0, 0x6CAE, 0xBEDA, 0x6CAF, 0x9B6A, 0x6CB0, 0x9B6B, 0x6CB1, 0xE3FB, 0x6CB2, 0xE3F5, + 0x6CB3, 0xBAD3, 0x6CB4, 0x9B6C, 0x6CB5, 0x9B6D, 0x6CB6, 0x9B6E, 0x6CB7, 0x9B6F, 0x6CB8, 0xB7D0, 0x6CB9, 0xD3CD, 0x6CBA, 0x9B70, + 0x6CBB, 0xD6CE, 0x6CBC, 0xD5D3, 0x6CBD, 0xB9C1, 0x6CBE, 0xD5B4, 0x6CBF, 0xD1D8, 0x6CC0, 0x9B71, 0x6CC1, 0x9B72, 0x6CC2, 0x9B73, + 0x6CC3, 0x9B74, 0x6CC4, 0xD0B9, 0x6CC5, 0xC7F6, 0x6CC6, 0x9B75, 0x6CC7, 0x9B76, 0x6CC8, 0x9B77, 0x6CC9, 0xC8AA, 0x6CCA, 0xB2B4, + 0x6CCB, 0x9B78, 0x6CCC, 0xC3DA, 0x6CCD, 0x9B79, 0x6CCE, 0x9B7A, 0x6CCF, 0x9B7B, 0x6CD0, 0xE3EE, 0x6CD1, 0x9B7C, 0x6CD2, 0x9B7D, + 0x6CD3, 0xE3FC, 0x6CD4, 0xE3EF, 0x6CD5, 0xB7A8, 0x6CD6, 0xE3F7, 0x6CD7, 0xE3F4, 0x6CD8, 0x9B7E, 0x6CD9, 0x9B80, 0x6CDA, 0x9B81, + 0x6CDB, 0xB7BA, 0x6CDC, 0x9B82, 0x6CDD, 0x9B83, 0x6CDE, 0xC5A2, 0x6CDF, 0x9B84, 0x6CE0, 0xE3F6, 0x6CE1, 0xC5DD, 0x6CE2, 0xB2A8, + 0x6CE3, 0xC6FC, 0x6CE4, 0x9B85, 0x6CE5, 0xC4E0, 0x6CE6, 0x9B86, 0x6CE7, 0x9B87, 0x6CE8, 0xD7A2, 0x6CE9, 0x9B88, 0x6CEA, 0xC0E1, + 0x6CEB, 0xE3F9, 0x6CEC, 0x9B89, 0x6CED, 0x9B8A, 0x6CEE, 0xE3FA, 0x6CEF, 0xE3FD, 0x6CF0, 0xCCA9, 0x6CF1, 0xE3F3, 0x6CF2, 0x9B8B, + 0x6CF3, 0xD3BE, 0x6CF4, 0x9B8C, 0x6CF5, 0xB1C3, 0x6CF6, 0xEDB4, 0x6CF7, 0xE3F1, 0x6CF8, 0xE3F2, 0x6CF9, 0x9B8D, 0x6CFA, 0xE3F8, + 0x6CFB, 0xD0BA, 0x6CFC, 0xC6C3, 0x6CFD, 0xD4F3, 0x6CFE, 0xE3FE, 0x6CFF, 0x9B8E, 0x6D00, 0x9B8F, 0x6D01, 0xBDE0, 0x6D02, 0x9B90, + 0x6D03, 0x9B91, 0x6D04, 0xE4A7, 0x6D05, 0x9B92, 0x6D06, 0x9B93, 0x6D07, 0xE4A6, 0x6D08, 0x9B94, 0x6D09, 0x9B95, 0x6D0A, 0x9B96, + 0x6D0B, 0xD1F3, 0x6D0C, 0xE4A3, 0x6D0D, 0x9B97, 0x6D0E, 0xE4A9, 0x6D0F, 0x9B98, 0x6D10, 0x9B99, 0x6D11, 0x9B9A, 0x6D12, 0xC8F7, + 0x6D13, 0x9B9B, 0x6D14, 0x9B9C, 0x6D15, 0x9B9D, 0x6D16, 0x9B9E, 0x6D17, 0xCFB4, 0x6D18, 0x9B9F, 0x6D19, 0xE4A8, 0x6D1A, 0xE4AE, + 0x6D1B, 0xC2E5, 0x6D1C, 0x9BA0, 0x6D1D, 0x9BA1, 0x6D1E, 0xB6B4, 0x6D1F, 0x9BA2, 0x6D20, 0x9BA3, 0x6D21, 0x9BA4, 0x6D22, 0x9BA5, + 0x6D23, 0x9BA6, 0x6D24, 0x9BA7, 0x6D25, 0xBDF2, 0x6D26, 0x9BA8, 0x6D27, 0xE4A2, 0x6D28, 0x9BA9, 0x6D29, 0x9BAA, 0x6D2A, 0xBAE9, + 0x6D2B, 0xE4AA, 0x6D2C, 0x9BAB, 0x6D2D, 0x9BAC, 0x6D2E, 0xE4AC, 0x6D2F, 0x9BAD, 0x6D30, 0x9BAE, 0x6D31, 0xB6FD, 0x6D32, 0xD6DE, + 0x6D33, 0xE4B2, 0x6D34, 0x9BAF, 0x6D35, 0xE4AD, 0x6D36, 0x9BB0, 0x6D37, 0x9BB1, 0x6D38, 0x9BB2, 0x6D39, 0xE4A1, 0x6D3A, 0x9BB3, + 0x6D3B, 0xBBEE, 0x6D3C, 0xCDDD, 0x6D3D, 0xC7A2, 0x6D3E, 0xC5C9, 0x6D3F, 0x9BB4, 0x6D40, 0x9BB5, 0x6D41, 0xC1F7, 0x6D42, 0x9BB6, + 0x6D43, 0xE4A4, 0x6D44, 0x9BB7, 0x6D45, 0xC7B3, 0x6D46, 0xBDAC, 0x6D47, 0xBDBD, 0x6D48, 0xE4A5, 0x6D49, 0x9BB8, 0x6D4A, 0xD7C7, + 0x6D4B, 0xB2E2, 0x6D4C, 0x9BB9, 0x6D4D, 0xE4AB, 0x6D4E, 0xBCC3, 0x6D4F, 0xE4AF, 0x6D50, 0x9BBA, 0x6D51, 0xBBEB, 0x6D52, 0xE4B0, + 0x6D53, 0xC5A8, 0x6D54, 0xE4B1, 0x6D55, 0x9BBB, 0x6D56, 0x9BBC, 0x6D57, 0x9BBD, 0x6D58, 0x9BBE, 0x6D59, 0xD5E3, 0x6D5A, 0xBFA3, + 0x6D5B, 0x9BBF, 0x6D5C, 0xE4BA, 0x6D5D, 0x9BC0, 0x6D5E, 0xE4B7, 0x6D5F, 0x9BC1, 0x6D60, 0xE4BB, 0x6D61, 0x9BC2, 0x6D62, 0x9BC3, + 0x6D63, 0xE4BD, 0x6D64, 0x9BC4, 0x6D65, 0x9BC5, 0x6D66, 0xC6D6, 0x6D67, 0x9BC6, 0x6D68, 0x9BC7, 0x6D69, 0xBAC6, 0x6D6A, 0xC0CB, + 0x6D6B, 0x9BC8, 0x6D6C, 0x9BC9, 0x6D6D, 0x9BCA, 0x6D6E, 0xB8A1, 0x6D6F, 0xE4B4, 0x6D70, 0x9BCB, 0x6D71, 0x9BCC, 0x6D72, 0x9BCD, + 0x6D73, 0x9BCE, 0x6D74, 0xD4A1, 0x6D75, 0x9BCF, 0x6D76, 0x9BD0, 0x6D77, 0xBAA3, 0x6D78, 0xBDFE, 0x6D79, 0x9BD1, 0x6D7A, 0x9BD2, + 0x6D7B, 0x9BD3, 0x6D7C, 0xE4BC, 0x6D7D, 0x9BD4, 0x6D7E, 0x9BD5, 0x6D7F, 0x9BD6, 0x6D80, 0x9BD7, 0x6D81, 0x9BD8, 0x6D82, 0xCDBF, + 0x6D83, 0x9BD9, 0x6D84, 0x9BDA, 0x6D85, 0xC4F9, 0x6D86, 0x9BDB, 0x6D87, 0x9BDC, 0x6D88, 0xCFFB, 0x6D89, 0xC9E6, 0x6D8A, 0x9BDD, + 0x6D8B, 0x9BDE, 0x6D8C, 0xD3BF, 0x6D8D, 0x9BDF, 0x6D8E, 0xCFD1, 0x6D8F, 0x9BE0, 0x6D90, 0x9BE1, 0x6D91, 0xE4B3, 0x6D92, 0x9BE2, + 0x6D93, 0xE4B8, 0x6D94, 0xE4B9, 0x6D95, 0xCCE9, 0x6D96, 0x9BE3, 0x6D97, 0x9BE4, 0x6D98, 0x9BE5, 0x6D99, 0x9BE6, 0x6D9A, 0x9BE7, + 0x6D9B, 0xCCCE, 0x6D9C, 0x9BE8, 0x6D9D, 0xC0D4, 0x6D9E, 0xE4B5, 0x6D9F, 0xC1B0, 0x6DA0, 0xE4B6, 0x6DA1, 0xCED0, 0x6DA2, 0x9BE9, + 0x6DA3, 0xBBC1, 0x6DA4, 0xB5D3, 0x6DA5, 0x9BEA, 0x6DA6, 0xC8F3, 0x6DA7, 0xBDA7, 0x6DA8, 0xD5C7, 0x6DA9, 0xC9AC, 0x6DAA, 0xB8A2, + 0x6DAB, 0xE4CA, 0x6DAC, 0x9BEB, 0x6DAD, 0x9BEC, 0x6DAE, 0xE4CC, 0x6DAF, 0xD1C4, 0x6DB0, 0x9BED, 0x6DB1, 0x9BEE, 0x6DB2, 0xD2BA, + 0x6DB3, 0x9BEF, 0x6DB4, 0x9BF0, 0x6DB5, 0xBAAD, 0x6DB6, 0x9BF1, 0x6DB7, 0x9BF2, 0x6DB8, 0xBAD4, 0x6DB9, 0x9BF3, 0x6DBA, 0x9BF4, + 0x6DBB, 0x9BF5, 0x6DBC, 0x9BF6, 0x6DBD, 0x9BF7, 0x6DBE, 0x9BF8, 0x6DBF, 0xE4C3, 0x6DC0, 0xB5ED, 0x6DC1, 0x9BF9, 0x6DC2, 0x9BFA, + 0x6DC3, 0x9BFB, 0x6DC4, 0xD7CD, 0x6DC5, 0xE4C0, 0x6DC6, 0xCFFD, 0x6DC7, 0xE4BF, 0x6DC8, 0x9BFC, 0x6DC9, 0x9BFD, 0x6DCA, 0x9BFE, + 0x6DCB, 0xC1DC, 0x6DCC, 0xCCCA, 0x6DCD, 0x9C40, 0x6DCE, 0x9C41, 0x6DCF, 0x9C42, 0x6DD0, 0x9C43, 0x6DD1, 0xCAE7, 0x6DD2, 0x9C44, + 0x6DD3, 0x9C45, 0x6DD4, 0x9C46, 0x6DD5, 0x9C47, 0x6DD6, 0xC4D7, 0x6DD7, 0x9C48, 0x6DD8, 0xCCD4, 0x6DD9, 0xE4C8, 0x6DDA, 0x9C49, + 0x6DDB, 0x9C4A, 0x6DDC, 0x9C4B, 0x6DDD, 0xE4C7, 0x6DDE, 0xE4C1, 0x6DDF, 0x9C4C, 0x6DE0, 0xE4C4, 0x6DE1, 0xB5AD, 0x6DE2, 0x9C4D, + 0x6DE3, 0x9C4E, 0x6DE4, 0xD3D9, 0x6DE5, 0x9C4F, 0x6DE6, 0xE4C6, 0x6DE7, 0x9C50, 0x6DE8, 0x9C51, 0x6DE9, 0x9C52, 0x6DEA, 0x9C53, + 0x6DEB, 0xD2F9, 0x6DEC, 0xB4E3, 0x6DED, 0x9C54, 0x6DEE, 0xBBB4, 0x6DEF, 0x9C55, 0x6DF0, 0x9C56, 0x6DF1, 0xC9EE, 0x6DF2, 0x9C57, + 0x6DF3, 0xB4BE, 0x6DF4, 0x9C58, 0x6DF5, 0x9C59, 0x6DF6, 0x9C5A, 0x6DF7, 0xBBEC, 0x6DF8, 0x9C5B, 0x6DF9, 0xD1CD, 0x6DFA, 0x9C5C, + 0x6DFB, 0xCCED, 0x6DFC, 0xEDB5, 0x6DFD, 0x9C5D, 0x6DFE, 0x9C5E, 0x6DFF, 0x9C5F, 0x6E00, 0x9C60, 0x6E01, 0x9C61, 0x6E02, 0x9C62, + 0x6E03, 0x9C63, 0x6E04, 0x9C64, 0x6E05, 0xC7E5, 0x6E06, 0x9C65, 0x6E07, 0x9C66, 0x6E08, 0x9C67, 0x6E09, 0x9C68, 0x6E0A, 0xD4A8, + 0x6E0B, 0x9C69, 0x6E0C, 0xE4CB, 0x6E0D, 0xD7D5, 0x6E0E, 0xE4C2, 0x6E0F, 0x9C6A, 0x6E10, 0xBDA5, 0x6E11, 0xE4C5, 0x6E12, 0x9C6B, + 0x6E13, 0x9C6C, 0x6E14, 0xD3E6, 0x6E15, 0x9C6D, 0x6E16, 0xE4C9, 0x6E17, 0xC9F8, 0x6E18, 0x9C6E, 0x6E19, 0x9C6F, 0x6E1A, 0xE4BE, + 0x6E1B, 0x9C70, 0x6E1C, 0x9C71, 0x6E1D, 0xD3E5, 0x6E1E, 0x9C72, 0x6E1F, 0x9C73, 0x6E20, 0xC7FE, 0x6E21, 0xB6C9, 0x6E22, 0x9C74, + 0x6E23, 0xD4FC, 0x6E24, 0xB2B3, 0x6E25, 0xE4D7, 0x6E26, 0x9C75, 0x6E27, 0x9C76, 0x6E28, 0x9C77, 0x6E29, 0xCEC2, 0x6E2A, 0x9C78, + 0x6E2B, 0xE4CD, 0x6E2C, 0x9C79, 0x6E2D, 0xCEBC, 0x6E2E, 0x9C7A, 0x6E2F, 0xB8DB, 0x6E30, 0x9C7B, 0x6E31, 0x9C7C, 0x6E32, 0xE4D6, + 0x6E33, 0x9C7D, 0x6E34, 0xBFCA, 0x6E35, 0x9C7E, 0x6E36, 0x9C80, 0x6E37, 0x9C81, 0x6E38, 0xD3CE, 0x6E39, 0x9C82, 0x6E3A, 0xC3EC, + 0x6E3B, 0x9C83, 0x6E3C, 0x9C84, 0x6E3D, 0x9C85, 0x6E3E, 0x9C86, 0x6E3F, 0x9C87, 0x6E40, 0x9C88, 0x6E41, 0x9C89, 0x6E42, 0x9C8A, + 0x6E43, 0xC5C8, 0x6E44, 0xE4D8, 0x6E45, 0x9C8B, 0x6E46, 0x9C8C, 0x6E47, 0x9C8D, 0x6E48, 0x9C8E, 0x6E49, 0x9C8F, 0x6E4A, 0x9C90, + 0x6E4B, 0x9C91, 0x6E4C, 0x9C92, 0x6E4D, 0xCDC4, 0x6E4E, 0xE4CF, 0x6E4F, 0x9C93, 0x6E50, 0x9C94, 0x6E51, 0x9C95, 0x6E52, 0x9C96, + 0x6E53, 0xE4D4, 0x6E54, 0xE4D5, 0x6E55, 0x9C97, 0x6E56, 0xBAFE, 0x6E57, 0x9C98, 0x6E58, 0xCFE6, 0x6E59, 0x9C99, 0x6E5A, 0x9C9A, + 0x6E5B, 0xD5BF, 0x6E5C, 0x9C9B, 0x6E5D, 0x9C9C, 0x6E5E, 0x9C9D, 0x6E5F, 0xE4D2, 0x6E60, 0x9C9E, 0x6E61, 0x9C9F, 0x6E62, 0x9CA0, + 0x6E63, 0x9CA1, 0x6E64, 0x9CA2, 0x6E65, 0x9CA3, 0x6E66, 0x9CA4, 0x6E67, 0x9CA5, 0x6E68, 0x9CA6, 0x6E69, 0x9CA7, 0x6E6A, 0x9CA8, + 0x6E6B, 0xE4D0, 0x6E6C, 0x9CA9, 0x6E6D, 0x9CAA, 0x6E6E, 0xE4CE, 0x6E6F, 0x9CAB, 0x6E70, 0x9CAC, 0x6E71, 0x9CAD, 0x6E72, 0x9CAE, + 0x6E73, 0x9CAF, 0x6E74, 0x9CB0, 0x6E75, 0x9CB1, 0x6E76, 0x9CB2, 0x6E77, 0x9CB3, 0x6E78, 0x9CB4, 0x6E79, 0x9CB5, 0x6E7A, 0x9CB6, + 0x6E7B, 0x9CB7, 0x6E7C, 0x9CB8, 0x6E7D, 0x9CB9, 0x6E7E, 0xCDE5, 0x6E7F, 0xCAAA, 0x6E80, 0x9CBA, 0x6E81, 0x9CBB, 0x6E82, 0x9CBC, + 0x6E83, 0xC0A3, 0x6E84, 0x9CBD, 0x6E85, 0xBDA6, 0x6E86, 0xE4D3, 0x6E87, 0x9CBE, 0x6E88, 0x9CBF, 0x6E89, 0xB8C8, 0x6E8A, 0x9CC0, + 0x6E8B, 0x9CC1, 0x6E8C, 0x9CC2, 0x6E8D, 0x9CC3, 0x6E8E, 0x9CC4, 0x6E8F, 0xE4E7, 0x6E90, 0xD4B4, 0x6E91, 0x9CC5, 0x6E92, 0x9CC6, + 0x6E93, 0x9CC7, 0x6E94, 0x9CC8, 0x6E95, 0x9CC9, 0x6E96, 0x9CCA, 0x6E97, 0x9CCB, 0x6E98, 0xE4DB, 0x6E99, 0x9CCC, 0x6E9A, 0x9CCD, + 0x6E9B, 0x9CCE, 0x6E9C, 0xC1EF, 0x6E9D, 0x9CCF, 0x6E9E, 0x9CD0, 0x6E9F, 0xE4E9, 0x6EA0, 0x9CD1, 0x6EA1, 0x9CD2, 0x6EA2, 0xD2E7, + 0x6EA3, 0x9CD3, 0x6EA4, 0x9CD4, 0x6EA5, 0xE4DF, 0x6EA6, 0x9CD5, 0x6EA7, 0xE4E0, 0x6EA8, 0x9CD6, 0x6EA9, 0x9CD7, 0x6EAA, 0xCFAA, + 0x6EAB, 0x9CD8, 0x6EAC, 0x9CD9, 0x6EAD, 0x9CDA, 0x6EAE, 0x9CDB, 0x6EAF, 0xCBDD, 0x6EB0, 0x9CDC, 0x6EB1, 0xE4DA, 0x6EB2, 0xE4D1, + 0x6EB3, 0x9CDD, 0x6EB4, 0xE4E5, 0x6EB5, 0x9CDE, 0x6EB6, 0xC8DC, 0x6EB7, 0xE4E3, 0x6EB8, 0x9CDF, 0x6EB9, 0x9CE0, 0x6EBA, 0xC4E7, + 0x6EBB, 0xE4E2, 0x6EBC, 0x9CE1, 0x6EBD, 0xE4E1, 0x6EBE, 0x9CE2, 0x6EBF, 0x9CE3, 0x6EC0, 0x9CE4, 0x6EC1, 0xB3FC, 0x6EC2, 0xE4E8, + 0x6EC3, 0x9CE5, 0x6EC4, 0x9CE6, 0x6EC5, 0x9CE7, 0x6EC6, 0x9CE8, 0x6EC7, 0xB5E1, 0x6EC8, 0x9CE9, 0x6EC9, 0x9CEA, 0x6ECA, 0x9CEB, + 0x6ECB, 0xD7CC, 0x6ECC, 0x9CEC, 0x6ECD, 0x9CED, 0x6ECE, 0x9CEE, 0x6ECF, 0xE4E6, 0x6ED0, 0x9CEF, 0x6ED1, 0xBBAC, 0x6ED2, 0x9CF0, + 0x6ED3, 0xD7D2, 0x6ED4, 0xCCCF, 0x6ED5, 0xEBF8, 0x6ED6, 0x9CF1, 0x6ED7, 0xE4E4, 0x6ED8, 0x9CF2, 0x6ED9, 0x9CF3, 0x6EDA, 0xB9F6, + 0x6EDB, 0x9CF4, 0x6EDC, 0x9CF5, 0x6EDD, 0x9CF6, 0x6EDE, 0xD6CD, 0x6EDF, 0xE4D9, 0x6EE0, 0xE4DC, 0x6EE1, 0xC2FA, 0x6EE2, 0xE4DE, + 0x6EE3, 0x9CF7, 0x6EE4, 0xC2CB, 0x6EE5, 0xC0C4, 0x6EE6, 0xC2D0, 0x6EE7, 0x9CF8, 0x6EE8, 0xB1F5, 0x6EE9, 0xCCB2, 0x6EEA, 0x9CF9, + 0x6EEB, 0x9CFA, 0x6EEC, 0x9CFB, 0x6EED, 0x9CFC, 0x6EEE, 0x9CFD, 0x6EEF, 0x9CFE, 0x6EF0, 0x9D40, 0x6EF1, 0x9D41, 0x6EF2, 0x9D42, + 0x6EF3, 0x9D43, 0x6EF4, 0xB5CE, 0x6EF5, 0x9D44, 0x6EF6, 0x9D45, 0x6EF7, 0x9D46, 0x6EF8, 0x9D47, 0x6EF9, 0xE4EF, 0x6EFA, 0x9D48, + 0x6EFB, 0x9D49, 0x6EFC, 0x9D4A, 0x6EFD, 0x9D4B, 0x6EFE, 0x9D4C, 0x6EFF, 0x9D4D, 0x6F00, 0x9D4E, 0x6F01, 0x9D4F, 0x6F02, 0xC6AF, + 0x6F03, 0x9D50, 0x6F04, 0x9D51, 0x6F05, 0x9D52, 0x6F06, 0xC6E1, 0x6F07, 0x9D53, 0x6F08, 0x9D54, 0x6F09, 0xE4F5, 0x6F0A, 0x9D55, + 0x6F0B, 0x9D56, 0x6F0C, 0x9D57, 0x6F0D, 0x9D58, 0x6F0E, 0x9D59, 0x6F0F, 0xC2A9, 0x6F10, 0x9D5A, 0x6F11, 0x9D5B, 0x6F12, 0x9D5C, + 0x6F13, 0xC0EC, 0x6F14, 0xD1DD, 0x6F15, 0xE4EE, 0x6F16, 0x9D5D, 0x6F17, 0x9D5E, 0x6F18, 0x9D5F, 0x6F19, 0x9D60, 0x6F1A, 0x9D61, + 0x6F1B, 0x9D62, 0x6F1C, 0x9D63, 0x6F1D, 0x9D64, 0x6F1E, 0x9D65, 0x6F1F, 0x9D66, 0x6F20, 0xC4AE, 0x6F21, 0x9D67, 0x6F22, 0x9D68, + 0x6F23, 0x9D69, 0x6F24, 0xE4ED, 0x6F25, 0x9D6A, 0x6F26, 0x9D6B, 0x6F27, 0x9D6C, 0x6F28, 0x9D6D, 0x6F29, 0xE4F6, 0x6F2A, 0xE4F4, + 0x6F2B, 0xC2FE, 0x6F2C, 0x9D6E, 0x6F2D, 0xE4DD, 0x6F2E, 0x9D6F, 0x6F2F, 0xE4F0, 0x6F30, 0x9D70, 0x6F31, 0xCAFE, 0x6F32, 0x9D71, + 0x6F33, 0xD5C4, 0x6F34, 0x9D72, 0x6F35, 0x9D73, 0x6F36, 0xE4F1, 0x6F37, 0x9D74, 0x6F38, 0x9D75, 0x6F39, 0x9D76, 0x6F3A, 0x9D77, + 0x6F3B, 0x9D78, 0x6F3C, 0x9D79, 0x6F3D, 0x9D7A, 0x6F3E, 0xD1FA, 0x6F3F, 0x9D7B, 0x6F40, 0x9D7C, 0x6F41, 0x9D7D, 0x6F42, 0x9D7E, + 0x6F43, 0x9D80, 0x6F44, 0x9D81, 0x6F45, 0x9D82, 0x6F46, 0xE4EB, 0x6F47, 0xE4EC, 0x6F48, 0x9D83, 0x6F49, 0x9D84, 0x6F4A, 0x9D85, + 0x6F4B, 0xE4F2, 0x6F4C, 0x9D86, 0x6F4D, 0xCEAB, 0x6F4E, 0x9D87, 0x6F4F, 0x9D88, 0x6F50, 0x9D89, 0x6F51, 0x9D8A, 0x6F52, 0x9D8B, + 0x6F53, 0x9D8C, 0x6F54, 0x9D8D, 0x6F55, 0x9D8E, 0x6F56, 0x9D8F, 0x6F57, 0x9D90, 0x6F58, 0xC5CB, 0x6F59, 0x9D91, 0x6F5A, 0x9D92, + 0x6F5B, 0x9D93, 0x6F5C, 0xC7B1, 0x6F5D, 0x9D94, 0x6F5E, 0xC2BA, 0x6F5F, 0x9D95, 0x6F60, 0x9D96, 0x6F61, 0x9D97, 0x6F62, 0xE4EA, + 0x6F63, 0x9D98, 0x6F64, 0x9D99, 0x6F65, 0x9D9A, 0x6F66, 0xC1CA, 0x6F67, 0x9D9B, 0x6F68, 0x9D9C, 0x6F69, 0x9D9D, 0x6F6A, 0x9D9E, + 0x6F6B, 0x9D9F, 0x6F6C, 0x9DA0, 0x6F6D, 0xCCB6, 0x6F6E, 0xB3B1, 0x6F6F, 0x9DA1, 0x6F70, 0x9DA2, 0x6F71, 0x9DA3, 0x6F72, 0xE4FB, + 0x6F73, 0x9DA4, 0x6F74, 0xE4F3, 0x6F75, 0x9DA5, 0x6F76, 0x9DA6, 0x6F77, 0x9DA7, 0x6F78, 0xE4FA, 0x6F79, 0x9DA8, 0x6F7A, 0xE4FD, + 0x6F7B, 0x9DA9, 0x6F7C, 0xE4FC, 0x6F7D, 0x9DAA, 0x6F7E, 0x9DAB, 0x6F7F, 0x9DAC, 0x6F80, 0x9DAD, 0x6F81, 0x9DAE, 0x6F82, 0x9DAF, + 0x6F83, 0x9DB0, 0x6F84, 0xB3CE, 0x6F85, 0x9DB1, 0x6F86, 0x9DB2, 0x6F87, 0x9DB3, 0x6F88, 0xB3BA, 0x6F89, 0xE4F7, 0x6F8A, 0x9DB4, + 0x6F8B, 0x9DB5, 0x6F8C, 0xE4F9, 0x6F8D, 0xE4F8, 0x6F8E, 0xC5EC, 0x6F8F, 0x9DB6, 0x6F90, 0x9DB7, 0x6F91, 0x9DB8, 0x6F92, 0x9DB9, + 0x6F93, 0x9DBA, 0x6F94, 0x9DBB, 0x6F95, 0x9DBC, 0x6F96, 0x9DBD, 0x6F97, 0x9DBE, 0x6F98, 0x9DBF, 0x6F99, 0x9DC0, 0x6F9A, 0x9DC1, + 0x6F9B, 0x9DC2, 0x6F9C, 0xC0BD, 0x6F9D, 0x9DC3, 0x6F9E, 0x9DC4, 0x6F9F, 0x9DC5, 0x6FA0, 0x9DC6, 0x6FA1, 0xD4E8, 0x6FA2, 0x9DC7, + 0x6FA3, 0x9DC8, 0x6FA4, 0x9DC9, 0x6FA5, 0x9DCA, 0x6FA6, 0x9DCB, 0x6FA7, 0xE5A2, 0x6FA8, 0x9DCC, 0x6FA9, 0x9DCD, 0x6FAA, 0x9DCE, + 0x6FAB, 0x9DCF, 0x6FAC, 0x9DD0, 0x6FAD, 0x9DD1, 0x6FAE, 0x9DD2, 0x6FAF, 0x9DD3, 0x6FB0, 0x9DD4, 0x6FB1, 0x9DD5, 0x6FB2, 0x9DD6, + 0x6FB3, 0xB0C4, 0x6FB4, 0x9DD7, 0x6FB5, 0x9DD8, 0x6FB6, 0xE5A4, 0x6FB7, 0x9DD9, 0x6FB8, 0x9DDA, 0x6FB9, 0xE5A3, 0x6FBA, 0x9DDB, + 0x6FBB, 0x9DDC, 0x6FBC, 0x9DDD, 0x6FBD, 0x9DDE, 0x6FBE, 0x9DDF, 0x6FBF, 0x9DE0, 0x6FC0, 0xBCA4, 0x6FC1, 0x9DE1, 0x6FC2, 0xE5A5, + 0x6FC3, 0x9DE2, 0x6FC4, 0x9DE3, 0x6FC5, 0x9DE4, 0x6FC6, 0x9DE5, 0x6FC7, 0x9DE6, 0x6FC8, 0x9DE7, 0x6FC9, 0xE5A1, 0x6FCA, 0x9DE8, + 0x6FCB, 0x9DE9, 0x6FCC, 0x9DEA, 0x6FCD, 0x9DEB, 0x6FCE, 0x9DEC, 0x6FCF, 0x9DED, 0x6FD0, 0x9DEE, 0x6FD1, 0xE4FE, 0x6FD2, 0xB1F4, + 0x6FD3, 0x9DEF, 0x6FD4, 0x9DF0, 0x6FD5, 0x9DF1, 0x6FD6, 0x9DF2, 0x6FD7, 0x9DF3, 0x6FD8, 0x9DF4, 0x6FD9, 0x9DF5, 0x6FDA, 0x9DF6, + 0x6FDB, 0x9DF7, 0x6FDC, 0x9DF8, 0x6FDD, 0x9DF9, 0x6FDE, 0xE5A8, 0x6FDF, 0x9DFA, 0x6FE0, 0xE5A9, 0x6FE1, 0xE5A6, 0x6FE2, 0x9DFB, + 0x6FE3, 0x9DFC, 0x6FE4, 0x9DFD, 0x6FE5, 0x9DFE, 0x6FE6, 0x9E40, 0x6FE7, 0x9E41, 0x6FE8, 0x9E42, 0x6FE9, 0x9E43, 0x6FEA, 0x9E44, + 0x6FEB, 0x9E45, 0x6FEC, 0x9E46, 0x6FED, 0x9E47, 0x6FEE, 0xE5A7, 0x6FEF, 0xE5AA, 0x6FF0, 0x9E48, 0x6FF1, 0x9E49, 0x6FF2, 0x9E4A, + 0x6FF3, 0x9E4B, 0x6FF4, 0x9E4C, 0x6FF5, 0x9E4D, 0x6FF6, 0x9E4E, 0x6FF7, 0x9E4F, 0x6FF8, 0x9E50, 0x6FF9, 0x9E51, 0x6FFA, 0x9E52, + 0x6FFB, 0x9E53, 0x6FFC, 0x9E54, 0x6FFD, 0x9E55, 0x6FFE, 0x9E56, 0x6FFF, 0x9E57, 0x7000, 0x9E58, 0x7001, 0x9E59, 0x7002, 0x9E5A, + 0x7003, 0x9E5B, 0x7004, 0x9E5C, 0x7005, 0x9E5D, 0x7006, 0x9E5E, 0x7007, 0x9E5F, 0x7008, 0x9E60, 0x7009, 0x9E61, 0x700A, 0x9E62, + 0x700B, 0x9E63, 0x700C, 0x9E64, 0x700D, 0x9E65, 0x700E, 0x9E66, 0x700F, 0x9E67, 0x7010, 0x9E68, 0x7011, 0xC6D9, 0x7012, 0x9E69, + 0x7013, 0x9E6A, 0x7014, 0x9E6B, 0x7015, 0x9E6C, 0x7016, 0x9E6D, 0x7017, 0x9E6E, 0x7018, 0x9E6F, 0x7019, 0x9E70, 0x701A, 0xE5AB, + 0x701B, 0xE5AD, 0x701C, 0x9E71, 0x701D, 0x9E72, 0x701E, 0x9E73, 0x701F, 0x9E74, 0x7020, 0x9E75, 0x7021, 0x9E76, 0x7022, 0x9E77, + 0x7023, 0xE5AC, 0x7024, 0x9E78, 0x7025, 0x9E79, 0x7026, 0x9E7A, 0x7027, 0x9E7B, 0x7028, 0x9E7C, 0x7029, 0x9E7D, 0x702A, 0x9E7E, + 0x702B, 0x9E80, 0x702C, 0x9E81, 0x702D, 0x9E82, 0x702E, 0x9E83, 0x702F, 0x9E84, 0x7030, 0x9E85, 0x7031, 0x9E86, 0x7032, 0x9E87, + 0x7033, 0x9E88, 0x7034, 0x9E89, 0x7035, 0xE5AF, 0x7036, 0x9E8A, 0x7037, 0x9E8B, 0x7038, 0x9E8C, 0x7039, 0xE5AE, 0x703A, 0x9E8D, + 0x703B, 0x9E8E, 0x703C, 0x9E8F, 0x703D, 0x9E90, 0x703E, 0x9E91, 0x703F, 0x9E92, 0x7040, 0x9E93, 0x7041, 0x9E94, 0x7042, 0x9E95, + 0x7043, 0x9E96, 0x7044, 0x9E97, 0x7045, 0x9E98, 0x7046, 0x9E99, 0x7047, 0x9E9A, 0x7048, 0x9E9B, 0x7049, 0x9E9C, 0x704A, 0x9E9D, + 0x704B, 0x9E9E, 0x704C, 0xB9E0, 0x704D, 0x9E9F, 0x704E, 0x9EA0, 0x704F, 0xE5B0, 0x7050, 0x9EA1, 0x7051, 0x9EA2, 0x7052, 0x9EA3, + 0x7053, 0x9EA4, 0x7054, 0x9EA5, 0x7055, 0x9EA6, 0x7056, 0x9EA7, 0x7057, 0x9EA8, 0x7058, 0x9EA9, 0x7059, 0x9EAA, 0x705A, 0x9EAB, + 0x705B, 0x9EAC, 0x705C, 0x9EAD, 0x705D, 0x9EAE, 0x705E, 0xE5B1, 0x705F, 0x9EAF, 0x7060, 0x9EB0, 0x7061, 0x9EB1, 0x7062, 0x9EB2, + 0x7063, 0x9EB3, 0x7064, 0x9EB4, 0x7065, 0x9EB5, 0x7066, 0x9EB6, 0x7067, 0x9EB7, 0x7068, 0x9EB8, 0x7069, 0x9EB9, 0x706A, 0x9EBA, + 0x706B, 0xBBF0, 0x706C, 0xECE1, 0x706D, 0xC3F0, 0x706E, 0x9EBB, 0x706F, 0xB5C6, 0x7070, 0xBBD2, 0x7071, 0x9EBC, 0x7072, 0x9EBD, + 0x7073, 0x9EBE, 0x7074, 0x9EBF, 0x7075, 0xC1E9, 0x7076, 0xD4EE, 0x7077, 0x9EC0, 0x7078, 0xBEC4, 0x7079, 0x9EC1, 0x707A, 0x9EC2, + 0x707B, 0x9EC3, 0x707C, 0xD7C6, 0x707D, 0x9EC4, 0x707E, 0xD4D6, 0x707F, 0xB2D3, 0x7080, 0xECBE, 0x7081, 0x9EC5, 0x7082, 0x9EC6, + 0x7083, 0x9EC7, 0x7084, 0x9EC8, 0x7085, 0xEAC1, 0x7086, 0x9EC9, 0x7087, 0x9ECA, 0x7088, 0x9ECB, 0x7089, 0xC2AF, 0x708A, 0xB4B6, + 0x708B, 0x9ECC, 0x708C, 0x9ECD, 0x708D, 0x9ECE, 0x708E, 0xD1D7, 0x708F, 0x9ECF, 0x7090, 0x9ED0, 0x7091, 0x9ED1, 0x7092, 0xB3B4, + 0x7093, 0x9ED2, 0x7094, 0xC8B2, 0x7095, 0xBFBB, 0x7096, 0xECC0, 0x7097, 0x9ED3, 0x7098, 0x9ED4, 0x7099, 0xD6CB, 0x709A, 0x9ED5, + 0x709B, 0x9ED6, 0x709C, 0xECBF, 0x709D, 0xECC1, 0x709E, 0x9ED7, 0x709F, 0x9ED8, 0x70A0, 0x9ED9, 0x70A1, 0x9EDA, 0x70A2, 0x9EDB, + 0x70A3, 0x9EDC, 0x70A4, 0x9EDD, 0x70A5, 0x9EDE, 0x70A6, 0x9EDF, 0x70A7, 0x9EE0, 0x70A8, 0x9EE1, 0x70A9, 0x9EE2, 0x70AA, 0x9EE3, + 0x70AB, 0xECC5, 0x70AC, 0xBEE6, 0x70AD, 0xCCBF, 0x70AE, 0xC5DA, 0x70AF, 0xBEBC, 0x70B0, 0x9EE4, 0x70B1, 0xECC6, 0x70B2, 0x9EE5, + 0x70B3, 0xB1FE, 0x70B4, 0x9EE6, 0x70B5, 0x9EE7, 0x70B6, 0x9EE8, 0x70B7, 0xECC4, 0x70B8, 0xD5A8, 0x70B9, 0xB5E3, 0x70BA, 0x9EE9, + 0x70BB, 0xECC2, 0x70BC, 0xC1B6, 0x70BD, 0xB3E3, 0x70BE, 0x9EEA, 0x70BF, 0x9EEB, 0x70C0, 0xECC3, 0x70C1, 0xCBB8, 0x70C2, 0xC0C3, + 0x70C3, 0xCCFE, 0x70C4, 0x9EEC, 0x70C5, 0x9EED, 0x70C6, 0x9EEE, 0x70C7, 0x9EEF, 0x70C8, 0xC1D2, 0x70C9, 0x9EF0, 0x70CA, 0xECC8, + 0x70CB, 0x9EF1, 0x70CC, 0x9EF2, 0x70CD, 0x9EF3, 0x70CE, 0x9EF4, 0x70CF, 0x9EF5, 0x70D0, 0x9EF6, 0x70D1, 0x9EF7, 0x70D2, 0x9EF8, + 0x70D3, 0x9EF9, 0x70D4, 0x9EFA, 0x70D5, 0x9EFB, 0x70D6, 0x9EFC, 0x70D7, 0x9EFD, 0x70D8, 0xBAE6, 0x70D9, 0xC0D3, 0x70DA, 0x9EFE, + 0x70DB, 0xD6F2, 0x70DC, 0x9F40, 0x70DD, 0x9F41, 0x70DE, 0x9F42, 0x70DF, 0xD1CC, 0x70E0, 0x9F43, 0x70E1, 0x9F44, 0x70E2, 0x9F45, + 0x70E3, 0x9F46, 0x70E4, 0xBFBE, 0x70E5, 0x9F47, 0x70E6, 0xB7B3, 0x70E7, 0xC9D5, 0x70E8, 0xECC7, 0x70E9, 0xBBE2, 0x70EA, 0x9F48, + 0x70EB, 0xCCCC, 0x70EC, 0xBDFD, 0x70ED, 0xC8C8, 0x70EE, 0x9F49, 0x70EF, 0xCFA9, 0x70F0, 0x9F4A, 0x70F1, 0x9F4B, 0x70F2, 0x9F4C, + 0x70F3, 0x9F4D, 0x70F4, 0x9F4E, 0x70F5, 0x9F4F, 0x70F6, 0x9F50, 0x70F7, 0xCDE9, 0x70F8, 0x9F51, 0x70F9, 0xC5EB, 0x70FA, 0x9F52, + 0x70FB, 0x9F53, 0x70FC, 0x9F54, 0x70FD, 0xB7E9, 0x70FE, 0x9F55, 0x70FF, 0x9F56, 0x7100, 0x9F57, 0x7101, 0x9F58, 0x7102, 0x9F59, + 0x7103, 0x9F5A, 0x7104, 0x9F5B, 0x7105, 0x9F5C, 0x7106, 0x9F5D, 0x7107, 0x9F5E, 0x7108, 0x9F5F, 0x7109, 0xD1C9, 0x710A, 0xBAB8, + 0x710B, 0x9F60, 0x710C, 0x9F61, 0x710D, 0x9F62, 0x710E, 0x9F63, 0x710F, 0x9F64, 0x7110, 0xECC9, 0x7111, 0x9F65, 0x7112, 0x9F66, + 0x7113, 0xECCA, 0x7114, 0x9F67, 0x7115, 0xBBC0, 0x7116, 0xECCB, 0x7117, 0x9F68, 0x7118, 0xECE2, 0x7119, 0xB1BA, 0x711A, 0xB7D9, + 0x711B, 0x9F69, 0x711C, 0x9F6A, 0x711D, 0x9F6B, 0x711E, 0x9F6C, 0x711F, 0x9F6D, 0x7120, 0x9F6E, 0x7121, 0x9F6F, 0x7122, 0x9F70, + 0x7123, 0x9F71, 0x7124, 0x9F72, 0x7125, 0x9F73, 0x7126, 0xBDB9, 0x7127, 0x9F74, 0x7128, 0x9F75, 0x7129, 0x9F76, 0x712A, 0x9F77, + 0x712B, 0x9F78, 0x712C, 0x9F79, 0x712D, 0x9F7A, 0x712E, 0x9F7B, 0x712F, 0xECCC, 0x7130, 0xD1E6, 0x7131, 0xECCD, 0x7132, 0x9F7C, + 0x7133, 0x9F7D, 0x7134, 0x9F7E, 0x7135, 0x9F80, 0x7136, 0xC8BB, 0x7137, 0x9F81, 0x7138, 0x9F82, 0x7139, 0x9F83, 0x713A, 0x9F84, + 0x713B, 0x9F85, 0x713C, 0x9F86, 0x713D, 0x9F87, 0x713E, 0x9F88, 0x713F, 0x9F89, 0x7140, 0x9F8A, 0x7141, 0x9F8B, 0x7142, 0x9F8C, + 0x7143, 0x9F8D, 0x7144, 0x9F8E, 0x7145, 0xECD1, 0x7146, 0x9F8F, 0x7147, 0x9F90, 0x7148, 0x9F91, 0x7149, 0x9F92, 0x714A, 0xECD3, + 0x714B, 0x9F93, 0x714C, 0xBBCD, 0x714D, 0x9F94, 0x714E, 0xBCE5, 0x714F, 0x9F95, 0x7150, 0x9F96, 0x7151, 0x9F97, 0x7152, 0x9F98, + 0x7153, 0x9F99, 0x7154, 0x9F9A, 0x7155, 0x9F9B, 0x7156, 0x9F9C, 0x7157, 0x9F9D, 0x7158, 0x9F9E, 0x7159, 0x9F9F, 0x715A, 0x9FA0, + 0x715B, 0x9FA1, 0x715C, 0xECCF, 0x715D, 0x9FA2, 0x715E, 0xC9B7, 0x715F, 0x9FA3, 0x7160, 0x9FA4, 0x7161, 0x9FA5, 0x7162, 0x9FA6, + 0x7163, 0x9FA7, 0x7164, 0xC3BA, 0x7165, 0x9FA8, 0x7166, 0xECE3, 0x7167, 0xD5D5, 0x7168, 0xECD0, 0x7169, 0x9FA9, 0x716A, 0x9FAA, + 0x716B, 0x9FAB, 0x716C, 0x9FAC, 0x716D, 0x9FAD, 0x716E, 0xD6F3, 0x716F, 0x9FAE, 0x7170, 0x9FAF, 0x7171, 0x9FB0, 0x7172, 0xECD2, + 0x7173, 0xECCE, 0x7174, 0x9FB1, 0x7175, 0x9FB2, 0x7176, 0x9FB3, 0x7177, 0x9FB4, 0x7178, 0xECD4, 0x7179, 0x9FB5, 0x717A, 0xECD5, + 0x717B, 0x9FB6, 0x717C, 0x9FB7, 0x717D, 0xC9BF, 0x717E, 0x9FB8, 0x717F, 0x9FB9, 0x7180, 0x9FBA, 0x7181, 0x9FBB, 0x7182, 0x9FBC, + 0x7183, 0x9FBD, 0x7184, 0xCFA8, 0x7185, 0x9FBE, 0x7186, 0x9FBF, 0x7187, 0x9FC0, 0x7188, 0x9FC1, 0x7189, 0x9FC2, 0x718A, 0xD0DC, + 0x718B, 0x9FC3, 0x718C, 0x9FC4, 0x718D, 0x9FC5, 0x718E, 0x9FC6, 0x718F, 0xD1AC, 0x7190, 0x9FC7, 0x7191, 0x9FC8, 0x7192, 0x9FC9, + 0x7193, 0x9FCA, 0x7194, 0xC8DB, 0x7195, 0x9FCB, 0x7196, 0x9FCC, 0x7197, 0x9FCD, 0x7198, 0xECD6, 0x7199, 0xCEF5, 0x719A, 0x9FCE, + 0x719B, 0x9FCF, 0x719C, 0x9FD0, 0x719D, 0x9FD1, 0x719E, 0x9FD2, 0x719F, 0xCAEC, 0x71A0, 0xECDA, 0x71A1, 0x9FD3, 0x71A2, 0x9FD4, + 0x71A3, 0x9FD5, 0x71A4, 0x9FD6, 0x71A5, 0x9FD7, 0x71A6, 0x9FD8, 0x71A7, 0x9FD9, 0x71A8, 0xECD9, 0x71A9, 0x9FDA, 0x71AA, 0x9FDB, + 0x71AB, 0x9FDC, 0x71AC, 0xB0BE, 0x71AD, 0x9FDD, 0x71AE, 0x9FDE, 0x71AF, 0x9FDF, 0x71B0, 0x9FE0, 0x71B1, 0x9FE1, 0x71B2, 0x9FE2, + 0x71B3, 0xECD7, 0x71B4, 0x9FE3, 0x71B5, 0xECD8, 0x71B6, 0x9FE4, 0x71B7, 0x9FE5, 0x71B8, 0x9FE6, 0x71B9, 0xECE4, 0x71BA, 0x9FE7, + 0x71BB, 0x9FE8, 0x71BC, 0x9FE9, 0x71BD, 0x9FEA, 0x71BE, 0x9FEB, 0x71BF, 0x9FEC, 0x71C0, 0x9FED, 0x71C1, 0x9FEE, 0x71C2, 0x9FEF, + 0x71C3, 0xC8BC, 0x71C4, 0x9FF0, 0x71C5, 0x9FF1, 0x71C6, 0x9FF2, 0x71C7, 0x9FF3, 0x71C8, 0x9FF4, 0x71C9, 0x9FF5, 0x71CA, 0x9FF6, + 0x71CB, 0x9FF7, 0x71CC, 0x9FF8, 0x71CD, 0x9FF9, 0x71CE, 0xC1C7, 0x71CF, 0x9FFA, 0x71D0, 0x9FFB, 0x71D1, 0x9FFC, 0x71D2, 0x9FFD, + 0x71D3, 0x9FFE, 0x71D4, 0xECDC, 0x71D5, 0xD1E0, 0x71D6, 0xA040, 0x71D7, 0xA041, 0x71D8, 0xA042, 0x71D9, 0xA043, 0x71DA, 0xA044, + 0x71DB, 0xA045, 0x71DC, 0xA046, 0x71DD, 0xA047, 0x71DE, 0xA048, 0x71DF, 0xA049, 0x71E0, 0xECDB, 0x71E1, 0xA04A, 0x71E2, 0xA04B, + 0x71E3, 0xA04C, 0x71E4, 0xA04D, 0x71E5, 0xD4EF, 0x71E6, 0xA04E, 0x71E7, 0xECDD, 0x71E8, 0xA04F, 0x71E9, 0xA050, 0x71EA, 0xA051, + 0x71EB, 0xA052, 0x71EC, 0xA053, 0x71ED, 0xA054, 0x71EE, 0xDBC6, 0x71EF, 0xA055, 0x71F0, 0xA056, 0x71F1, 0xA057, 0x71F2, 0xA058, + 0x71F3, 0xA059, 0x71F4, 0xA05A, 0x71F5, 0xA05B, 0x71F6, 0xA05C, 0x71F7, 0xA05D, 0x71F8, 0xA05E, 0x71F9, 0xECDE, 0x71FA, 0xA05F, + 0x71FB, 0xA060, 0x71FC, 0xA061, 0x71FD, 0xA062, 0x71FE, 0xA063, 0x71FF, 0xA064, 0x7200, 0xA065, 0x7201, 0xA066, 0x7202, 0xA067, + 0x7203, 0xA068, 0x7204, 0xA069, 0x7205, 0xA06A, 0x7206, 0xB1AC, 0x7207, 0xA06B, 0x7208, 0xA06C, 0x7209, 0xA06D, 0x720A, 0xA06E, + 0x720B, 0xA06F, 0x720C, 0xA070, 0x720D, 0xA071, 0x720E, 0xA072, 0x720F, 0xA073, 0x7210, 0xA074, 0x7211, 0xA075, 0x7212, 0xA076, + 0x7213, 0xA077, 0x7214, 0xA078, 0x7215, 0xA079, 0x7216, 0xA07A, 0x7217, 0xA07B, 0x7218, 0xA07C, 0x7219, 0xA07D, 0x721A, 0xA07E, + 0x721B, 0xA080, 0x721C, 0xA081, 0x721D, 0xECDF, 0x721E, 0xA082, 0x721F, 0xA083, 0x7220, 0xA084, 0x7221, 0xA085, 0x7222, 0xA086, + 0x7223, 0xA087, 0x7224, 0xA088, 0x7225, 0xA089, 0x7226, 0xA08A, 0x7227, 0xA08B, 0x7228, 0xECE0, 0x7229, 0xA08C, 0x722A, 0xD7A6, + 0x722B, 0xA08D, 0x722C, 0xC5C0, 0x722D, 0xA08E, 0x722E, 0xA08F, 0x722F, 0xA090, 0x7230, 0xEBBC, 0x7231, 0xB0AE, 0x7232, 0xA091, + 0x7233, 0xA092, 0x7234, 0xA093, 0x7235, 0xBEF4, 0x7236, 0xB8B8, 0x7237, 0xD2AF, 0x7238, 0xB0D6, 0x7239, 0xB5F9, 0x723A, 0xA094, + 0x723B, 0xD8B3, 0x723C, 0xA095, 0x723D, 0xCBAC, 0x723E, 0xA096, 0x723F, 0xE3DD, 0x7240, 0xA097, 0x7241, 0xA098, 0x7242, 0xA099, + 0x7243, 0xA09A, 0x7244, 0xA09B, 0x7245, 0xA09C, 0x7246, 0xA09D, 0x7247, 0xC6AC, 0x7248, 0xB0E6, 0x7249, 0xA09E, 0x724A, 0xA09F, + 0x724B, 0xA0A0, 0x724C, 0xC5C6, 0x724D, 0xEBB9, 0x724E, 0xA0A1, 0x724F, 0xA0A2, 0x7250, 0xA0A3, 0x7251, 0xA0A4, 0x7252, 0xEBBA, + 0x7253, 0xA0A5, 0x7254, 0xA0A6, 0x7255, 0xA0A7, 0x7256, 0xEBBB, 0x7257, 0xA0A8, 0x7258, 0xA0A9, 0x7259, 0xD1C0, 0x725A, 0xA0AA, + 0x725B, 0xC5A3, 0x725C, 0xA0AB, 0x725D, 0xEAF2, 0x725E, 0xA0AC, 0x725F, 0xC4B2, 0x7260, 0xA0AD, 0x7261, 0xC4B5, 0x7262, 0xC0CE, + 0x7263, 0xA0AE, 0x7264, 0xA0AF, 0x7265, 0xA0B0, 0x7266, 0xEAF3, 0x7267, 0xC4C1, 0x7268, 0xA0B1, 0x7269, 0xCEEF, 0x726A, 0xA0B2, + 0x726B, 0xA0B3, 0x726C, 0xA0B4, 0x726D, 0xA0B5, 0x726E, 0xEAF0, 0x726F, 0xEAF4, 0x7270, 0xA0B6, 0x7271, 0xA0B7, 0x7272, 0xC9FC, + 0x7273, 0xA0B8, 0x7274, 0xA0B9, 0x7275, 0xC7A3, 0x7276, 0xA0BA, 0x7277, 0xA0BB, 0x7278, 0xA0BC, 0x7279, 0xCCD8, 0x727A, 0xCEFE, + 0x727B, 0xA0BD, 0x727C, 0xA0BE, 0x727D, 0xA0BF, 0x727E, 0xEAF5, 0x727F, 0xEAF6, 0x7280, 0xCFAC, 0x7281, 0xC0E7, 0x7282, 0xA0C0, + 0x7283, 0xA0C1, 0x7284, 0xEAF7, 0x7285, 0xA0C2, 0x7286, 0xA0C3, 0x7287, 0xA0C4, 0x7288, 0xA0C5, 0x7289, 0xA0C6, 0x728A, 0xB6BF, + 0x728B, 0xEAF8, 0x728C, 0xA0C7, 0x728D, 0xEAF9, 0x728E, 0xA0C8, 0x728F, 0xEAFA, 0x7290, 0xA0C9, 0x7291, 0xA0CA, 0x7292, 0xEAFB, + 0x7293, 0xA0CB, 0x7294, 0xA0CC, 0x7295, 0xA0CD, 0x7296, 0xA0CE, 0x7297, 0xA0CF, 0x7298, 0xA0D0, 0x7299, 0xA0D1, 0x729A, 0xA0D2, + 0x729B, 0xA0D3, 0x729C, 0xA0D4, 0x729D, 0xA0D5, 0x729E, 0xA0D6, 0x729F, 0xEAF1, 0x72A0, 0xA0D7, 0x72A1, 0xA0D8, 0x72A2, 0xA0D9, + 0x72A3, 0xA0DA, 0x72A4, 0xA0DB, 0x72A5, 0xA0DC, 0x72A6, 0xA0DD, 0x72A7, 0xA0DE, 0x72A8, 0xA0DF, 0x72A9, 0xA0E0, 0x72AA, 0xA0E1, + 0x72AB, 0xA0E2, 0x72AC, 0xC8AE, 0x72AD, 0xE1EB, 0x72AE, 0xA0E3, 0x72AF, 0xB7B8, 0x72B0, 0xE1EC, 0x72B1, 0xA0E4, 0x72B2, 0xA0E5, + 0x72B3, 0xA0E6, 0x72B4, 0xE1ED, 0x72B5, 0xA0E7, 0x72B6, 0xD7B4, 0x72B7, 0xE1EE, 0x72B8, 0xE1EF, 0x72B9, 0xD3CC, 0x72BA, 0xA0E8, + 0x72BB, 0xA0E9, 0x72BC, 0xA0EA, 0x72BD, 0xA0EB, 0x72BE, 0xA0EC, 0x72BF, 0xA0ED, 0x72C0, 0xA0EE, 0x72C1, 0xE1F1, 0x72C2, 0xBFF1, + 0x72C3, 0xE1F0, 0x72C4, 0xB5D2, 0x72C5, 0xA0EF, 0x72C6, 0xA0F0, 0x72C7, 0xA0F1, 0x72C8, 0xB1B7, 0x72C9, 0xA0F2, 0x72CA, 0xA0F3, + 0x72CB, 0xA0F4, 0x72CC, 0xA0F5, 0x72CD, 0xE1F3, 0x72CE, 0xE1F2, 0x72CF, 0xA0F6, 0x72D0, 0xBAFC, 0x72D1, 0xA0F7, 0x72D2, 0xE1F4, + 0x72D3, 0xA0F8, 0x72D4, 0xA0F9, 0x72D5, 0xA0FA, 0x72D6, 0xA0FB, 0x72D7, 0xB9B7, 0x72D8, 0xA0FC, 0x72D9, 0xBED1, 0x72DA, 0xA0FD, + 0x72DB, 0xA0FE, 0x72DC, 0xAA40, 0x72DD, 0xAA41, 0x72DE, 0xC4FC, 0x72DF, 0xAA42, 0x72E0, 0xBADD, 0x72E1, 0xBDC6, 0x72E2, 0xAA43, + 0x72E3, 0xAA44, 0x72E4, 0xAA45, 0x72E5, 0xAA46, 0x72E6, 0xAA47, 0x72E7, 0xAA48, 0x72E8, 0xE1F5, 0x72E9, 0xE1F7, 0x72EA, 0xAA49, + 0x72EB, 0xAA4A, 0x72EC, 0xB6C0, 0x72ED, 0xCFC1, 0x72EE, 0xCAA8, 0x72EF, 0xE1F6, 0x72F0, 0xD5F8, 0x72F1, 0xD3FC, 0x72F2, 0xE1F8, + 0x72F3, 0xE1FC, 0x72F4, 0xE1F9, 0x72F5, 0xAA4B, 0x72F6, 0xAA4C, 0x72F7, 0xE1FA, 0x72F8, 0xC0EA, 0x72F9, 0xAA4D, 0x72FA, 0xE1FE, + 0x72FB, 0xE2A1, 0x72FC, 0xC0C7, 0x72FD, 0xAA4E, 0x72FE, 0xAA4F, 0x72FF, 0xAA50, 0x7300, 0xAA51, 0x7301, 0xE1FB, 0x7302, 0xAA52, + 0x7303, 0xE1FD, 0x7304, 0xAA53, 0x7305, 0xAA54, 0x7306, 0xAA55, 0x7307, 0xAA56, 0x7308, 0xAA57, 0x7309, 0xAA58, 0x730A, 0xE2A5, + 0x730B, 0xAA59, 0x730C, 0xAA5A, 0x730D, 0xAA5B, 0x730E, 0xC1D4, 0x730F, 0xAA5C, 0x7310, 0xAA5D, 0x7311, 0xAA5E, 0x7312, 0xAA5F, + 0x7313, 0xE2A3, 0x7314, 0xAA60, 0x7315, 0xE2A8, 0x7316, 0xB2FE, 0x7317, 0xE2A2, 0x7318, 0xAA61, 0x7319, 0xAA62, 0x731A, 0xAA63, + 0x731B, 0xC3CD, 0x731C, 0xB2C2, 0x731D, 0xE2A7, 0x731E, 0xE2A6, 0x731F, 0xAA64, 0x7320, 0xAA65, 0x7321, 0xE2A4, 0x7322, 0xE2A9, + 0x7323, 0xAA66, 0x7324, 0xAA67, 0x7325, 0xE2AB, 0x7326, 0xAA68, 0x7327, 0xAA69, 0x7328, 0xAA6A, 0x7329, 0xD0C9, 0x732A, 0xD6ED, + 0x732B, 0xC3A8, 0x732C, 0xE2AC, 0x732D, 0xAA6B, 0x732E, 0xCFD7, 0x732F, 0xAA6C, 0x7330, 0xAA6D, 0x7331, 0xE2AE, 0x7332, 0xAA6E, + 0x7333, 0xAA6F, 0x7334, 0xBAEF, 0x7335, 0xAA70, 0x7336, 0xAA71, 0x7337, 0xE9E0, 0x7338, 0xE2AD, 0x7339, 0xE2AA, 0x733A, 0xAA72, + 0x733B, 0xAA73, 0x733C, 0xAA74, 0x733D, 0xAA75, 0x733E, 0xBBAB, 0x733F, 0xD4B3, 0x7340, 0xAA76, 0x7341, 0xAA77, 0x7342, 0xAA78, + 0x7343, 0xAA79, 0x7344, 0xAA7A, 0x7345, 0xAA7B, 0x7346, 0xAA7C, 0x7347, 0xAA7D, 0x7348, 0xAA7E, 0x7349, 0xAA80, 0x734A, 0xAA81, + 0x734B, 0xAA82, 0x734C, 0xAA83, 0x734D, 0xE2B0, 0x734E, 0xAA84, 0x734F, 0xAA85, 0x7350, 0xE2AF, 0x7351, 0xAA86, 0x7352, 0xE9E1, + 0x7353, 0xAA87, 0x7354, 0xAA88, 0x7355, 0xAA89, 0x7356, 0xAA8A, 0x7357, 0xE2B1, 0x7358, 0xAA8B, 0x7359, 0xAA8C, 0x735A, 0xAA8D, + 0x735B, 0xAA8E, 0x735C, 0xAA8F, 0x735D, 0xAA90, 0x735E, 0xAA91, 0x735F, 0xAA92, 0x7360, 0xE2B2, 0x7361, 0xAA93, 0x7362, 0xAA94, + 0x7363, 0xAA95, 0x7364, 0xAA96, 0x7365, 0xAA97, 0x7366, 0xAA98, 0x7367, 0xAA99, 0x7368, 0xAA9A, 0x7369, 0xAA9B, 0x736A, 0xAA9C, + 0x736B, 0xAA9D, 0x736C, 0xE2B3, 0x736D, 0xCCA1, 0x736E, 0xAA9E, 0x736F, 0xE2B4, 0x7370, 0xAA9F, 0x7371, 0xAAA0, 0x7372, 0xAB40, + 0x7373, 0xAB41, 0x7374, 0xAB42, 0x7375, 0xAB43, 0x7376, 0xAB44, 0x7377, 0xAB45, 0x7378, 0xAB46, 0x7379, 0xAB47, 0x737A, 0xAB48, + 0x737B, 0xAB49, 0x737C, 0xAB4A, 0x737D, 0xAB4B, 0x737E, 0xE2B5, 0x737F, 0xAB4C, 0x7380, 0xAB4D, 0x7381, 0xAB4E, 0x7382, 0xAB4F, + 0x7383, 0xAB50, 0x7384, 0xD0FE, 0x7385, 0xAB51, 0x7386, 0xAB52, 0x7387, 0xC2CA, 0x7388, 0xAB53, 0x7389, 0xD3F1, 0x738A, 0xAB54, + 0x738B, 0xCDF5, 0x738C, 0xAB55, 0x738D, 0xAB56, 0x738E, 0xE7E0, 0x738F, 0xAB57, 0x7390, 0xAB58, 0x7391, 0xE7E1, 0x7392, 0xAB59, + 0x7393, 0xAB5A, 0x7394, 0xAB5B, 0x7395, 0xAB5C, 0x7396, 0xBEC1, 0x7397, 0xAB5D, 0x7398, 0xAB5E, 0x7399, 0xAB5F, 0x739A, 0xAB60, + 0x739B, 0xC2EA, 0x739C, 0xAB61, 0x739D, 0xAB62, 0x739E, 0xAB63, 0x739F, 0xE7E4, 0x73A0, 0xAB64, 0x73A1, 0xAB65, 0x73A2, 0xE7E3, + 0x73A3, 0xAB66, 0x73A4, 0xAB67, 0x73A5, 0xAB68, 0x73A6, 0xAB69, 0x73A7, 0xAB6A, 0x73A8, 0xAB6B, 0x73A9, 0xCDE6, 0x73AA, 0xAB6C, + 0x73AB, 0xC3B5, 0x73AC, 0xAB6D, 0x73AD, 0xAB6E, 0x73AE, 0xE7E2, 0x73AF, 0xBBB7, 0x73B0, 0xCFD6, 0x73B1, 0xAB6F, 0x73B2, 0xC1E1, + 0x73B3, 0xE7E9, 0x73B4, 0xAB70, 0x73B5, 0xAB71, 0x73B6, 0xAB72, 0x73B7, 0xE7E8, 0x73B8, 0xAB73, 0x73B9, 0xAB74, 0x73BA, 0xE7F4, + 0x73BB, 0xB2A3, 0x73BC, 0xAB75, 0x73BD, 0xAB76, 0x73BE, 0xAB77, 0x73BF, 0xAB78, 0x73C0, 0xE7EA, 0x73C1, 0xAB79, 0x73C2, 0xE7E6, + 0x73C3, 0xAB7A, 0x73C4, 0xAB7B, 0x73C5, 0xAB7C, 0x73C6, 0xAB7D, 0x73C7, 0xAB7E, 0x73C8, 0xE7EC, 0x73C9, 0xE7EB, 0x73CA, 0xC9BA, + 0x73CB, 0xAB80, 0x73CC, 0xAB81, 0x73CD, 0xD5E4, 0x73CE, 0xAB82, 0x73CF, 0xE7E5, 0x73D0, 0xB7A9, 0x73D1, 0xE7E7, 0x73D2, 0xAB83, + 0x73D3, 0xAB84, 0x73D4, 0xAB85, 0x73D5, 0xAB86, 0x73D6, 0xAB87, 0x73D7, 0xAB88, 0x73D8, 0xAB89, 0x73D9, 0xE7EE, 0x73DA, 0xAB8A, + 0x73DB, 0xAB8B, 0x73DC, 0xAB8C, 0x73DD, 0xAB8D, 0x73DE, 0xE7F3, 0x73DF, 0xAB8E, 0x73E0, 0xD6E9, 0x73E1, 0xAB8F, 0x73E2, 0xAB90, + 0x73E3, 0xAB91, 0x73E4, 0xAB92, 0x73E5, 0xE7ED, 0x73E6, 0xAB93, 0x73E7, 0xE7F2, 0x73E8, 0xAB94, 0x73E9, 0xE7F1, 0x73EA, 0xAB95, + 0x73EB, 0xAB96, 0x73EC, 0xAB97, 0x73ED, 0xB0E0, 0x73EE, 0xAB98, 0x73EF, 0xAB99, 0x73F0, 0xAB9A, 0x73F1, 0xAB9B, 0x73F2, 0xE7F5, + 0x73F3, 0xAB9C, 0x73F4, 0xAB9D, 0x73F5, 0xAB9E, 0x73F6, 0xAB9F, 0x73F7, 0xABA0, 0x73F8, 0xAC40, 0x73F9, 0xAC41, 0x73FA, 0xAC42, + 0x73FB, 0xAC43, 0x73FC, 0xAC44, 0x73FD, 0xAC45, 0x73FE, 0xAC46, 0x73FF, 0xAC47, 0x7400, 0xAC48, 0x7401, 0xAC49, 0x7402, 0xAC4A, + 0x7403, 0xC7F2, 0x7404, 0xAC4B, 0x7405, 0xC0C5, 0x7406, 0xC0ED, 0x7407, 0xAC4C, 0x7408, 0xAC4D, 0x7409, 0xC1F0, 0x740A, 0xE7F0, + 0x740B, 0xAC4E, 0x740C, 0xAC4F, 0x740D, 0xAC50, 0x740E, 0xAC51, 0x740F, 0xE7F6, 0x7410, 0xCBF6, 0x7411, 0xAC52, 0x7412, 0xAC53, + 0x7413, 0xAC54, 0x7414, 0xAC55, 0x7415, 0xAC56, 0x7416, 0xAC57, 0x7417, 0xAC58, 0x7418, 0xAC59, 0x7419, 0xAC5A, 0x741A, 0xE8A2, + 0x741B, 0xE8A1, 0x741C, 0xAC5B, 0x741D, 0xAC5C, 0x741E, 0xAC5D, 0x741F, 0xAC5E, 0x7420, 0xAC5F, 0x7421, 0xAC60, 0x7422, 0xD7C1, + 0x7423, 0xAC61, 0x7424, 0xAC62, 0x7425, 0xE7FA, 0x7426, 0xE7F9, 0x7427, 0xAC63, 0x7428, 0xE7FB, 0x7429, 0xAC64, 0x742A, 0xE7F7, + 0x742B, 0xAC65, 0x742C, 0xE7FE, 0x742D, 0xAC66, 0x742E, 0xE7FD, 0x742F, 0xAC67, 0x7430, 0xE7FC, 0x7431, 0xAC68, 0x7432, 0xAC69, + 0x7433, 0xC1D5, 0x7434, 0xC7D9, 0x7435, 0xC5FD, 0x7436, 0xC5C3, 0x7437, 0xAC6A, 0x7438, 0xAC6B, 0x7439, 0xAC6C, 0x743A, 0xAC6D, + 0x743B, 0xAC6E, 0x743C, 0xC7ED, 0x743D, 0xAC6F, 0x743E, 0xAC70, 0x743F, 0xAC71, 0x7440, 0xAC72, 0x7441, 0xE8A3, 0x7442, 0xAC73, + 0x7443, 0xAC74, 0x7444, 0xAC75, 0x7445, 0xAC76, 0x7446, 0xAC77, 0x7447, 0xAC78, 0x7448, 0xAC79, 0x7449, 0xAC7A, 0x744A, 0xAC7B, + 0x744B, 0xAC7C, 0x744C, 0xAC7D, 0x744D, 0xAC7E, 0x744E, 0xAC80, 0x744F, 0xAC81, 0x7450, 0xAC82, 0x7451, 0xAC83, 0x7452, 0xAC84, + 0x7453, 0xAC85, 0x7454, 0xAC86, 0x7455, 0xE8A6, 0x7456, 0xAC87, 0x7457, 0xE8A5, 0x7458, 0xAC88, 0x7459, 0xE8A7, 0x745A, 0xBAF7, + 0x745B, 0xE7F8, 0x745C, 0xE8A4, 0x745D, 0xAC89, 0x745E, 0xC8F0, 0x745F, 0xC9AA, 0x7460, 0xAC8A, 0x7461, 0xAC8B, 0x7462, 0xAC8C, + 0x7463, 0xAC8D, 0x7464, 0xAC8E, 0x7465, 0xAC8F, 0x7466, 0xAC90, 0x7467, 0xAC91, 0x7468, 0xAC92, 0x7469, 0xAC93, 0x746A, 0xAC94, + 0x746B, 0xAC95, 0x746C, 0xAC96, 0x746D, 0xE8A9, 0x746E, 0xAC97, 0x746F, 0xAC98, 0x7470, 0xB9E5, 0x7471, 0xAC99, 0x7472, 0xAC9A, + 0x7473, 0xAC9B, 0x7474, 0xAC9C, 0x7475, 0xAC9D, 0x7476, 0xD1FE, 0x7477, 0xE8A8, 0x7478, 0xAC9E, 0x7479, 0xAC9F, 0x747A, 0xACA0, + 0x747B, 0xAD40, 0x747C, 0xAD41, 0x747D, 0xAD42, 0x747E, 0xE8AA, 0x747F, 0xAD43, 0x7480, 0xE8AD, 0x7481, 0xE8AE, 0x7482, 0xAD44, + 0x7483, 0xC1A7, 0x7484, 0xAD45, 0x7485, 0xAD46, 0x7486, 0xAD47, 0x7487, 0xE8AF, 0x7488, 0xAD48, 0x7489, 0xAD49, 0x748A, 0xAD4A, + 0x748B, 0xE8B0, 0x748C, 0xAD4B, 0x748D, 0xAD4C, 0x748E, 0xE8AC, 0x748F, 0xAD4D, 0x7490, 0xE8B4, 0x7491, 0xAD4E, 0x7492, 0xAD4F, + 0x7493, 0xAD50, 0x7494, 0xAD51, 0x7495, 0xAD52, 0x7496, 0xAD53, 0x7497, 0xAD54, 0x7498, 0xAD55, 0x7499, 0xAD56, 0x749A, 0xAD57, + 0x749B, 0xAD58, 0x749C, 0xE8AB, 0x749D, 0xAD59, 0x749E, 0xE8B1, 0x749F, 0xAD5A, 0x74A0, 0xAD5B, 0x74A1, 0xAD5C, 0x74A2, 0xAD5D, + 0x74A3, 0xAD5E, 0x74A4, 0xAD5F, 0x74A5, 0xAD60, 0x74A6, 0xAD61, 0x74A7, 0xE8B5, 0x74A8, 0xE8B2, 0x74A9, 0xE8B3, 0x74AA, 0xAD62, + 0x74AB, 0xAD63, 0x74AC, 0xAD64, 0x74AD, 0xAD65, 0x74AE, 0xAD66, 0x74AF, 0xAD67, 0x74B0, 0xAD68, 0x74B1, 0xAD69, 0x74B2, 0xAD6A, + 0x74B3, 0xAD6B, 0x74B4, 0xAD6C, 0x74B5, 0xAD6D, 0x74B6, 0xAD6E, 0x74B7, 0xAD6F, 0x74B8, 0xAD70, 0x74B9, 0xAD71, 0x74BA, 0xE8B7, + 0x74BB, 0xAD72, 0x74BC, 0xAD73, 0x74BD, 0xAD74, 0x74BE, 0xAD75, 0x74BF, 0xAD76, 0x74C0, 0xAD77, 0x74C1, 0xAD78, 0x74C2, 0xAD79, + 0x74C3, 0xAD7A, 0x74C4, 0xAD7B, 0x74C5, 0xAD7C, 0x74C6, 0xAD7D, 0x74C7, 0xAD7E, 0x74C8, 0xAD80, 0x74C9, 0xAD81, 0x74CA, 0xAD82, + 0x74CB, 0xAD83, 0x74CC, 0xAD84, 0x74CD, 0xAD85, 0x74CE, 0xAD86, 0x74CF, 0xAD87, 0x74D0, 0xAD88, 0x74D1, 0xAD89, 0x74D2, 0xE8B6, + 0x74D3, 0xAD8A, 0x74D4, 0xAD8B, 0x74D5, 0xAD8C, 0x74D6, 0xAD8D, 0x74D7, 0xAD8E, 0x74D8, 0xAD8F, 0x74D9, 0xAD90, 0x74DA, 0xAD91, + 0x74DB, 0xAD92, 0x74DC, 0xB9CF, 0x74DD, 0xAD93, 0x74DE, 0xF0AC, 0x74DF, 0xAD94, 0x74E0, 0xF0AD, 0x74E1, 0xAD95, 0x74E2, 0xC6B0, + 0x74E3, 0xB0EA, 0x74E4, 0xC8BF, 0x74E5, 0xAD96, 0x74E6, 0xCDDF, 0x74E7, 0xAD97, 0x74E8, 0xAD98, 0x74E9, 0xAD99, 0x74EA, 0xAD9A, + 0x74EB, 0xAD9B, 0x74EC, 0xAD9C, 0x74ED, 0xAD9D, 0x74EE, 0xCECD, 0x74EF, 0xEAB1, 0x74F0, 0xAD9E, 0x74F1, 0xAD9F, 0x74F2, 0xADA0, + 0x74F3, 0xAE40, 0x74F4, 0xEAB2, 0x74F5, 0xAE41, 0x74F6, 0xC6BF, 0x74F7, 0xB4C9, 0x74F8, 0xAE42, 0x74F9, 0xAE43, 0x74FA, 0xAE44, + 0x74FB, 0xAE45, 0x74FC, 0xAE46, 0x74FD, 0xAE47, 0x74FE, 0xAE48, 0x74FF, 0xEAB3, 0x7500, 0xAE49, 0x7501, 0xAE4A, 0x7502, 0xAE4B, + 0x7503, 0xAE4C, 0x7504, 0xD5E7, 0x7505, 0xAE4D, 0x7506, 0xAE4E, 0x7507, 0xAE4F, 0x7508, 0xAE50, 0x7509, 0xAE51, 0x750A, 0xAE52, + 0x750B, 0xAE53, 0x750C, 0xAE54, 0x750D, 0xDDF9, 0x750E, 0xAE55, 0x750F, 0xEAB4, 0x7510, 0xAE56, 0x7511, 0xEAB5, 0x7512, 0xAE57, + 0x7513, 0xEAB6, 0x7514, 0xAE58, 0x7515, 0xAE59, 0x7516, 0xAE5A, 0x7517, 0xAE5B, 0x7518, 0xB8CA, 0x7519, 0xDFB0, 0x751A, 0xC9F5, + 0x751B, 0xAE5C, 0x751C, 0xCCF0, 0x751D, 0xAE5D, 0x751E, 0xAE5E, 0x751F, 0xC9FA, 0x7520, 0xAE5F, 0x7521, 0xAE60, 0x7522, 0xAE61, + 0x7523, 0xAE62, 0x7524, 0xAE63, 0x7525, 0xC9FB, 0x7526, 0xAE64, 0x7527, 0xAE65, 0x7528, 0xD3C3, 0x7529, 0xCBA6, 0x752A, 0xAE66, + 0x752B, 0xB8A6, 0x752C, 0xF0AE, 0x752D, 0xB1C2, 0x752E, 0xAE67, 0x752F, 0xE5B8, 0x7530, 0xCCEF, 0x7531, 0xD3C9, 0x7532, 0xBCD7, + 0x7533, 0xC9EA, 0x7534, 0xAE68, 0x7535, 0xB5E7, 0x7536, 0xAE69, 0x7537, 0xC4D0, 0x7538, 0xB5E9, 0x7539, 0xAE6A, 0x753A, 0xEEAE, + 0x753B, 0xBBAD, 0x753C, 0xAE6B, 0x753D, 0xAE6C, 0x753E, 0xE7DE, 0x753F, 0xAE6D, 0x7540, 0xEEAF, 0x7541, 0xAE6E, 0x7542, 0xAE6F, + 0x7543, 0xAE70, 0x7544, 0xAE71, 0x7545, 0xB3A9, 0x7546, 0xAE72, 0x7547, 0xAE73, 0x7548, 0xEEB2, 0x7549, 0xAE74, 0x754A, 0xAE75, + 0x754B, 0xEEB1, 0x754C, 0xBDE7, 0x754D, 0xAE76, 0x754E, 0xEEB0, 0x754F, 0xCEB7, 0x7550, 0xAE77, 0x7551, 0xAE78, 0x7552, 0xAE79, + 0x7553, 0xAE7A, 0x7554, 0xC5CF, 0x7555, 0xAE7B, 0x7556, 0xAE7C, 0x7557, 0xAE7D, 0x7558, 0xAE7E, 0x7559, 0xC1F4, 0x755A, 0xDBCE, + 0x755B, 0xEEB3, 0x755C, 0xD0F3, 0x755D, 0xAE80, 0x755E, 0xAE81, 0x755F, 0xAE82, 0x7560, 0xAE83, 0x7561, 0xAE84, 0x7562, 0xAE85, + 0x7563, 0xAE86, 0x7564, 0xAE87, 0x7565, 0xC2D4, 0x7566, 0xC6E8, 0x7567, 0xAE88, 0x7568, 0xAE89, 0x7569, 0xAE8A, 0x756A, 0xB7AC, + 0x756B, 0xAE8B, 0x756C, 0xAE8C, 0x756D, 0xAE8D, 0x756E, 0xAE8E, 0x756F, 0xAE8F, 0x7570, 0xAE90, 0x7571, 0xAE91, 0x7572, 0xEEB4, + 0x7573, 0xAE92, 0x7574, 0xB3EB, 0x7575, 0xAE93, 0x7576, 0xAE94, 0x7577, 0xAE95, 0x7578, 0xBBFB, 0x7579, 0xEEB5, 0x757A, 0xAE96, + 0x757B, 0xAE97, 0x757C, 0xAE98, 0x757D, 0xAE99, 0x757E, 0xAE9A, 0x757F, 0xE7DC, 0x7580, 0xAE9B, 0x7581, 0xAE9C, 0x7582, 0xAE9D, + 0x7583, 0xEEB6, 0x7584, 0xAE9E, 0x7585, 0xAE9F, 0x7586, 0xBDAE, 0x7587, 0xAEA0, 0x7588, 0xAF40, 0x7589, 0xAF41, 0x758A, 0xAF42, + 0x758B, 0xF1E2, 0x758C, 0xAF43, 0x758D, 0xAF44, 0x758E, 0xAF45, 0x758F, 0xCAE8, 0x7590, 0xAF46, 0x7591, 0xD2C9, 0x7592, 0xF0DA, + 0x7593, 0xAF47, 0x7594, 0xF0DB, 0x7595, 0xAF48, 0x7596, 0xF0DC, 0x7597, 0xC1C6, 0x7598, 0xAF49, 0x7599, 0xB8ED, 0x759A, 0xBECE, + 0x759B, 0xAF4A, 0x759C, 0xAF4B, 0x759D, 0xF0DE, 0x759E, 0xAF4C, 0x759F, 0xC5B1, 0x75A0, 0xF0DD, 0x75A1, 0xD1F1, 0x75A2, 0xAF4D, + 0x75A3, 0xF0E0, 0x75A4, 0xB0CC, 0x75A5, 0xBDEA, 0x75A6, 0xAF4E, 0x75A7, 0xAF4F, 0x75A8, 0xAF50, 0x75A9, 0xAF51, 0x75AA, 0xAF52, + 0x75AB, 0xD2DF, 0x75AC, 0xF0DF, 0x75AD, 0xAF53, 0x75AE, 0xB4AF, 0x75AF, 0xB7E8, 0x75B0, 0xF0E6, 0x75B1, 0xF0E5, 0x75B2, 0xC6A3, + 0x75B3, 0xF0E1, 0x75B4, 0xF0E2, 0x75B5, 0xB4C3, 0x75B6, 0xAF54, 0x75B7, 0xAF55, 0x75B8, 0xF0E3, 0x75B9, 0xD5EE, 0x75BA, 0xAF56, + 0x75BB, 0xAF57, 0x75BC, 0xCCDB, 0x75BD, 0xBED2, 0x75BE, 0xBCB2, 0x75BF, 0xAF58, 0x75C0, 0xAF59, 0x75C1, 0xAF5A, 0x75C2, 0xF0E8, + 0x75C3, 0xF0E7, 0x75C4, 0xF0E4, 0x75C5, 0xB2A1, 0x75C6, 0xAF5B, 0x75C7, 0xD6A2, 0x75C8, 0xD3B8, 0x75C9, 0xBEB7, 0x75CA, 0xC8AC, + 0x75CB, 0xAF5C, 0x75CC, 0xAF5D, 0x75CD, 0xF0EA, 0x75CE, 0xAF5E, 0x75CF, 0xAF5F, 0x75D0, 0xAF60, 0x75D1, 0xAF61, 0x75D2, 0xD1F7, + 0x75D3, 0xAF62, 0x75D4, 0xD6CC, 0x75D5, 0xBADB, 0x75D6, 0xF0E9, 0x75D7, 0xAF63, 0x75D8, 0xB6BB, 0x75D9, 0xAF64, 0x75DA, 0xAF65, + 0x75DB, 0xCDB4, 0x75DC, 0xAF66, 0x75DD, 0xAF67, 0x75DE, 0xC6A6, 0x75DF, 0xAF68, 0x75E0, 0xAF69, 0x75E1, 0xAF6A, 0x75E2, 0xC1A1, + 0x75E3, 0xF0EB, 0x75E4, 0xF0EE, 0x75E5, 0xAF6B, 0x75E6, 0xF0ED, 0x75E7, 0xF0F0, 0x75E8, 0xF0EC, 0x75E9, 0xAF6C, 0x75EA, 0xBBBE, + 0x75EB, 0xF0EF, 0x75EC, 0xAF6D, 0x75ED, 0xAF6E, 0x75EE, 0xAF6F, 0x75EF, 0xAF70, 0x75F0, 0xCCB5, 0x75F1, 0xF0F2, 0x75F2, 0xAF71, + 0x75F3, 0xAF72, 0x75F4, 0xB3D5, 0x75F5, 0xAF73, 0x75F6, 0xAF74, 0x75F7, 0xAF75, 0x75F8, 0xAF76, 0x75F9, 0xB1D4, 0x75FA, 0xAF77, + 0x75FB, 0xAF78, 0x75FC, 0xF0F3, 0x75FD, 0xAF79, 0x75FE, 0xAF7A, 0x75FF, 0xF0F4, 0x7600, 0xF0F6, 0x7601, 0xB4E1, 0x7602, 0xAF7B, + 0x7603, 0xF0F1, 0x7604, 0xAF7C, 0x7605, 0xF0F7, 0x7606, 0xAF7D, 0x7607, 0xAF7E, 0x7608, 0xAF80, 0x7609, 0xAF81, 0x760A, 0xF0FA, + 0x760B, 0xAF82, 0x760C, 0xF0F8, 0x760D, 0xAF83, 0x760E, 0xAF84, 0x760F, 0xAF85, 0x7610, 0xF0F5, 0x7611, 0xAF86, 0x7612, 0xAF87, + 0x7613, 0xAF88, 0x7614, 0xAF89, 0x7615, 0xF0FD, 0x7616, 0xAF8A, 0x7617, 0xF0F9, 0x7618, 0xF0FC, 0x7619, 0xF0FE, 0x761A, 0xAF8B, + 0x761B, 0xF1A1, 0x761C, 0xAF8C, 0x761D, 0xAF8D, 0x761E, 0xAF8E, 0x761F, 0xCEC1, 0x7620, 0xF1A4, 0x7621, 0xAF8F, 0x7622, 0xF1A3, + 0x7623, 0xAF90, 0x7624, 0xC1F6, 0x7625, 0xF0FB, 0x7626, 0xCADD, 0x7627, 0xAF91, 0x7628, 0xAF92, 0x7629, 0xB4F1, 0x762A, 0xB1F1, + 0x762B, 0xCCB1, 0x762C, 0xAF93, 0x762D, 0xF1A6, 0x762E, 0xAF94, 0x762F, 0xAF95, 0x7630, 0xF1A7, 0x7631, 0xAF96, 0x7632, 0xAF97, + 0x7633, 0xF1AC, 0x7634, 0xD5CE, 0x7635, 0xF1A9, 0x7636, 0xAF98, 0x7637, 0xAF99, 0x7638, 0xC8B3, 0x7639, 0xAF9A, 0x763A, 0xAF9B, + 0x763B, 0xAF9C, 0x763C, 0xF1A2, 0x763D, 0xAF9D, 0x763E, 0xF1AB, 0x763F, 0xF1A8, 0x7640, 0xF1A5, 0x7641, 0xAF9E, 0x7642, 0xAF9F, + 0x7643, 0xF1AA, 0x7644, 0xAFA0, 0x7645, 0xB040, 0x7646, 0xB041, 0x7647, 0xB042, 0x7648, 0xB043, 0x7649, 0xB044, 0x764A, 0xB045, + 0x764B, 0xB046, 0x764C, 0xB0A9, 0x764D, 0xF1AD, 0x764E, 0xB047, 0x764F, 0xB048, 0x7650, 0xB049, 0x7651, 0xB04A, 0x7652, 0xB04B, + 0x7653, 0xB04C, 0x7654, 0xF1AF, 0x7655, 0xB04D, 0x7656, 0xF1B1, 0x7657, 0xB04E, 0x7658, 0xB04F, 0x7659, 0xB050, 0x765A, 0xB051, + 0x765B, 0xB052, 0x765C, 0xF1B0, 0x765D, 0xB053, 0x765E, 0xF1AE, 0x765F, 0xB054, 0x7660, 0xB055, 0x7661, 0xB056, 0x7662, 0xB057, + 0x7663, 0xD1A2, 0x7664, 0xB058, 0x7665, 0xB059, 0x7666, 0xB05A, 0x7667, 0xB05B, 0x7668, 0xB05C, 0x7669, 0xB05D, 0x766A, 0xB05E, + 0x766B, 0xF1B2, 0x766C, 0xB05F, 0x766D, 0xB060, 0x766E, 0xB061, 0x766F, 0xF1B3, 0x7670, 0xB062, 0x7671, 0xB063, 0x7672, 0xB064, + 0x7673, 0xB065, 0x7674, 0xB066, 0x7675, 0xB067, 0x7676, 0xB068, 0x7677, 0xB069, 0x7678, 0xB9EF, 0x7679, 0xB06A, 0x767A, 0xB06B, + 0x767B, 0xB5C7, 0x767C, 0xB06C, 0x767D, 0xB0D7, 0x767E, 0xB0D9, 0x767F, 0xB06D, 0x7680, 0xB06E, 0x7681, 0xB06F, 0x7682, 0xD4ED, + 0x7683, 0xB070, 0x7684, 0xB5C4, 0x7685, 0xB071, 0x7686, 0xBDD4, 0x7687, 0xBBCA, 0x7688, 0xF0A7, 0x7689, 0xB072, 0x768A, 0xB073, + 0x768B, 0xB8DE, 0x768C, 0xB074, 0x768D, 0xB075, 0x768E, 0xF0A8, 0x768F, 0xB076, 0x7690, 0xB077, 0x7691, 0xB0A8, 0x7692, 0xB078, + 0x7693, 0xF0A9, 0x7694, 0xB079, 0x7695, 0xB07A, 0x7696, 0xCDEE, 0x7697, 0xB07B, 0x7698, 0xB07C, 0x7699, 0xF0AA, 0x769A, 0xB07D, + 0x769B, 0xB07E, 0x769C, 0xB080, 0x769D, 0xB081, 0x769E, 0xB082, 0x769F, 0xB083, 0x76A0, 0xB084, 0x76A1, 0xB085, 0x76A2, 0xB086, + 0x76A3, 0xB087, 0x76A4, 0xF0AB, 0x76A5, 0xB088, 0x76A6, 0xB089, 0x76A7, 0xB08A, 0x76A8, 0xB08B, 0x76A9, 0xB08C, 0x76AA, 0xB08D, + 0x76AB, 0xB08E, 0x76AC, 0xB08F, 0x76AD, 0xB090, 0x76AE, 0xC6A4, 0x76AF, 0xB091, 0x76B0, 0xB092, 0x76B1, 0xD6E5, 0x76B2, 0xF1E4, + 0x76B3, 0xB093, 0x76B4, 0xF1E5, 0x76B5, 0xB094, 0x76B6, 0xB095, 0x76B7, 0xB096, 0x76B8, 0xB097, 0x76B9, 0xB098, 0x76BA, 0xB099, + 0x76BB, 0xB09A, 0x76BC, 0xB09B, 0x76BD, 0xB09C, 0x76BE, 0xB09D, 0x76BF, 0xC3F3, 0x76C0, 0xB09E, 0x76C1, 0xB09F, 0x76C2, 0xD3DB, + 0x76C3, 0xB0A0, 0x76C4, 0xB140, 0x76C5, 0xD6D1, 0x76C6, 0xC5E8, 0x76C7, 0xB141, 0x76C8, 0xD3AF, 0x76C9, 0xB142, 0x76CA, 0xD2E6, + 0x76CB, 0xB143, 0x76CC, 0xB144, 0x76CD, 0xEEC1, 0x76CE, 0xB0BB, 0x76CF, 0xD5B5, 0x76D0, 0xD1CE, 0x76D1, 0xBCE0, 0x76D2, 0xBAD0, + 0x76D3, 0xB145, 0x76D4, 0xBFF8, 0x76D5, 0xB146, 0x76D6, 0xB8C7, 0x76D7, 0xB5C1, 0x76D8, 0xC5CC, 0x76D9, 0xB147, 0x76DA, 0xB148, + 0x76DB, 0xCAA2, 0x76DC, 0xB149, 0x76DD, 0xB14A, 0x76DE, 0xB14B, 0x76DF, 0xC3CB, 0x76E0, 0xB14C, 0x76E1, 0xB14D, 0x76E2, 0xB14E, + 0x76E3, 0xB14F, 0x76E4, 0xB150, 0x76E5, 0xEEC2, 0x76E6, 0xB151, 0x76E7, 0xB152, 0x76E8, 0xB153, 0x76E9, 0xB154, 0x76EA, 0xB155, + 0x76EB, 0xB156, 0x76EC, 0xB157, 0x76ED, 0xB158, 0x76EE, 0xC4BF, 0x76EF, 0xB6A2, 0x76F0, 0xB159, 0x76F1, 0xEDEC, 0x76F2, 0xC3A4, + 0x76F3, 0xB15A, 0x76F4, 0xD6B1, 0x76F5, 0xB15B, 0x76F6, 0xB15C, 0x76F7, 0xB15D, 0x76F8, 0xCFE0, 0x76F9, 0xEDEF, 0x76FA, 0xB15E, + 0x76FB, 0xB15F, 0x76FC, 0xC5CE, 0x76FD, 0xB160, 0x76FE, 0xB6DC, 0x76FF, 0xB161, 0x7700, 0xB162, 0x7701, 0xCAA1, 0x7702, 0xB163, + 0x7703, 0xB164, 0x7704, 0xEDED, 0x7705, 0xB165, 0x7706, 0xB166, 0x7707, 0xEDF0, 0x7708, 0xEDF1, 0x7709, 0xC3BC, 0x770A, 0xB167, + 0x770B, 0xBFB4, 0x770C, 0xB168, 0x770D, 0xEDEE, 0x770E, 0xB169, 0x770F, 0xB16A, 0x7710, 0xB16B, 0x7711, 0xB16C, 0x7712, 0xB16D, + 0x7713, 0xB16E, 0x7714, 0xB16F, 0x7715, 0xB170, 0x7716, 0xB171, 0x7717, 0xB172, 0x7718, 0xB173, 0x7719, 0xEDF4, 0x771A, 0xEDF2, + 0x771B, 0xB174, 0x771C, 0xB175, 0x771D, 0xB176, 0x771E, 0xB177, 0x771F, 0xD5E6, 0x7720, 0xC3DF, 0x7721, 0xB178, 0x7722, 0xEDF3, + 0x7723, 0xB179, 0x7724, 0xB17A, 0x7725, 0xB17B, 0x7726, 0xEDF6, 0x7727, 0xB17C, 0x7728, 0xD5A3, 0x7729, 0xD1A3, 0x772A, 0xB17D, + 0x772B, 0xB17E, 0x772C, 0xB180, 0x772D, 0xEDF5, 0x772E, 0xB181, 0x772F, 0xC3D0, 0x7730, 0xB182, 0x7731, 0xB183, 0x7732, 0xB184, + 0x7733, 0xB185, 0x7734, 0xB186, 0x7735, 0xEDF7, 0x7736, 0xBFF4, 0x7737, 0xBEEC, 0x7738, 0xEDF8, 0x7739, 0xB187, 0x773A, 0xCCF7, + 0x773B, 0xB188, 0x773C, 0xD1DB, 0x773D, 0xB189, 0x773E, 0xB18A, 0x773F, 0xB18B, 0x7740, 0xD7C5, 0x7741, 0xD5F6, 0x7742, 0xB18C, + 0x7743, 0xEDFC, 0x7744, 0xB18D, 0x7745, 0xB18E, 0x7746, 0xB18F, 0x7747, 0xEDFB, 0x7748, 0xB190, 0x7749, 0xB191, 0x774A, 0xB192, + 0x774B, 0xB193, 0x774C, 0xB194, 0x774D, 0xB195, 0x774E, 0xB196, 0x774F, 0xB197, 0x7750, 0xEDF9, 0x7751, 0xEDFA, 0x7752, 0xB198, + 0x7753, 0xB199, 0x7754, 0xB19A, 0x7755, 0xB19B, 0x7756, 0xB19C, 0x7757, 0xB19D, 0x7758, 0xB19E, 0x7759, 0xB19F, 0x775A, 0xEDFD, + 0x775B, 0xBEA6, 0x775C, 0xB1A0, 0x775D, 0xB240, 0x775E, 0xB241, 0x775F, 0xB242, 0x7760, 0xB243, 0x7761, 0xCBAF, 0x7762, 0xEEA1, + 0x7763, 0xB6BD, 0x7764, 0xB244, 0x7765, 0xEEA2, 0x7766, 0xC4C0, 0x7767, 0xB245, 0x7768, 0xEDFE, 0x7769, 0xB246, 0x776A, 0xB247, + 0x776B, 0xBDDE, 0x776C, 0xB2C7, 0x776D, 0xB248, 0x776E, 0xB249, 0x776F, 0xB24A, 0x7770, 0xB24B, 0x7771, 0xB24C, 0x7772, 0xB24D, + 0x7773, 0xB24E, 0x7774, 0xB24F, 0x7775, 0xB250, 0x7776, 0xB251, 0x7777, 0xB252, 0x7778, 0xB253, 0x7779, 0xB6C3, 0x777A, 0xB254, + 0x777B, 0xB255, 0x777C, 0xB256, 0x777D, 0xEEA5, 0x777E, 0xD8BA, 0x777F, 0xEEA3, 0x7780, 0xEEA6, 0x7781, 0xB257, 0x7782, 0xB258, + 0x7783, 0xB259, 0x7784, 0xC3E9, 0x7785, 0xB3F2, 0x7786, 0xB25A, 0x7787, 0xB25B, 0x7788, 0xB25C, 0x7789, 0xB25D, 0x778A, 0xB25E, + 0x778B, 0xB25F, 0x778C, 0xEEA7, 0x778D, 0xEEA4, 0x778E, 0xCFB9, 0x778F, 0xB260, 0x7790, 0xB261, 0x7791, 0xEEA8, 0x7792, 0xC2F7, + 0x7793, 0xB262, 0x7794, 0xB263, 0x7795, 0xB264, 0x7796, 0xB265, 0x7797, 0xB266, 0x7798, 0xB267, 0x7799, 0xB268, 0x779A, 0xB269, + 0x779B, 0xB26A, 0x779C, 0xB26B, 0x779D, 0xB26C, 0x779E, 0xB26D, 0x779F, 0xEEA9, 0x77A0, 0xEEAA, 0x77A1, 0xB26E, 0x77A2, 0xDEAB, + 0x77A3, 0xB26F, 0x77A4, 0xB270, 0x77A5, 0xC6B3, 0x77A6, 0xB271, 0x77A7, 0xC7C6, 0x77A8, 0xB272, 0x77A9, 0xD6F5, 0x77AA, 0xB5C9, + 0x77AB, 0xB273, 0x77AC, 0xCBB2, 0x77AD, 0xB274, 0x77AE, 0xB275, 0x77AF, 0xB276, 0x77B0, 0xEEAB, 0x77B1, 0xB277, 0x77B2, 0xB278, + 0x77B3, 0xCDAB, 0x77B4, 0xB279, 0x77B5, 0xEEAC, 0x77B6, 0xB27A, 0x77B7, 0xB27B, 0x77B8, 0xB27C, 0x77B9, 0xB27D, 0x77BA, 0xB27E, + 0x77BB, 0xD5B0, 0x77BC, 0xB280, 0x77BD, 0xEEAD, 0x77BE, 0xB281, 0x77BF, 0xF6C4, 0x77C0, 0xB282, 0x77C1, 0xB283, 0x77C2, 0xB284, + 0x77C3, 0xB285, 0x77C4, 0xB286, 0x77C5, 0xB287, 0x77C6, 0xB288, 0x77C7, 0xB289, 0x77C8, 0xB28A, 0x77C9, 0xB28B, 0x77CA, 0xB28C, + 0x77CB, 0xB28D, 0x77CC, 0xB28E, 0x77CD, 0xDBC7, 0x77CE, 0xB28F, 0x77CF, 0xB290, 0x77D0, 0xB291, 0x77D1, 0xB292, 0x77D2, 0xB293, + 0x77D3, 0xB294, 0x77D4, 0xB295, 0x77D5, 0xB296, 0x77D6, 0xB297, 0x77D7, 0xB4A3, 0x77D8, 0xB298, 0x77D9, 0xB299, 0x77DA, 0xB29A, + 0x77DB, 0xC3AC, 0x77DC, 0xF1E6, 0x77DD, 0xB29B, 0x77DE, 0xB29C, 0x77DF, 0xB29D, 0x77E0, 0xB29E, 0x77E1, 0xB29F, 0x77E2, 0xCAB8, + 0x77E3, 0xD2D3, 0x77E4, 0xB2A0, 0x77E5, 0xD6AA, 0x77E6, 0xB340, 0x77E7, 0xEFF2, 0x77E8, 0xB341, 0x77E9, 0xBED8, 0x77EA, 0xB342, + 0x77EB, 0xBDC3, 0x77EC, 0xEFF3, 0x77ED, 0xB6CC, 0x77EE, 0xB0AB, 0x77EF, 0xB343, 0x77F0, 0xB344, 0x77F1, 0xB345, 0x77F2, 0xB346, + 0x77F3, 0xCAAF, 0x77F4, 0xB347, 0x77F5, 0xB348, 0x77F6, 0xEDB6, 0x77F7, 0xB349, 0x77F8, 0xEDB7, 0x77F9, 0xB34A, 0x77FA, 0xB34B, + 0x77FB, 0xB34C, 0x77FC, 0xB34D, 0x77FD, 0xCEF9, 0x77FE, 0xB7AF, 0x77FF, 0xBFF3, 0x7800, 0xEDB8, 0x7801, 0xC2EB, 0x7802, 0xC9B0, + 0x7803, 0xB34E, 0x7804, 0xB34F, 0x7805, 0xB350, 0x7806, 0xB351, 0x7807, 0xB352, 0x7808, 0xB353, 0x7809, 0xEDB9, 0x780A, 0xB354, + 0x780B, 0xB355, 0x780C, 0xC6F6, 0x780D, 0xBFB3, 0x780E, 0xB356, 0x780F, 0xB357, 0x7810, 0xB358, 0x7811, 0xEDBC, 0x7812, 0xC5F8, + 0x7813, 0xB359, 0x7814, 0xD1D0, 0x7815, 0xB35A, 0x7816, 0xD7A9, 0x7817, 0xEDBA, 0x7818, 0xEDBB, 0x7819, 0xB35B, 0x781A, 0xD1E2, + 0x781B, 0xB35C, 0x781C, 0xEDBF, 0x781D, 0xEDC0, 0x781E, 0xB35D, 0x781F, 0xEDC4, 0x7820, 0xB35E, 0x7821, 0xB35F, 0x7822, 0xB360, + 0x7823, 0xEDC8, 0x7824, 0xB361, 0x7825, 0xEDC6, 0x7826, 0xEDCE, 0x7827, 0xD5E8, 0x7828, 0xB362, 0x7829, 0xEDC9, 0x782A, 0xB363, + 0x782B, 0xB364, 0x782C, 0xEDC7, 0x782D, 0xEDBE, 0x782E, 0xB365, 0x782F, 0xB366, 0x7830, 0xC5E9, 0x7831, 0xB367, 0x7832, 0xB368, + 0x7833, 0xB369, 0x7834, 0xC6C6, 0x7835, 0xB36A, 0x7836, 0xB36B, 0x7837, 0xC9E9, 0x7838, 0xD4D2, 0x7839, 0xEDC1, 0x783A, 0xEDC2, + 0x783B, 0xEDC3, 0x783C, 0xEDC5, 0x783D, 0xB36C, 0x783E, 0xC0F9, 0x783F, 0xB36D, 0x7840, 0xB4A1, 0x7841, 0xB36E, 0x7842, 0xB36F, + 0x7843, 0xB370, 0x7844, 0xB371, 0x7845, 0xB9E8, 0x7846, 0xB372, 0x7847, 0xEDD0, 0x7848, 0xB373, 0x7849, 0xB374, 0x784A, 0xB375, + 0x784B, 0xB376, 0x784C, 0xEDD1, 0x784D, 0xB377, 0x784E, 0xEDCA, 0x784F, 0xB378, 0x7850, 0xEDCF, 0x7851, 0xB379, 0x7852, 0xCEF8, + 0x7853, 0xB37A, 0x7854, 0xB37B, 0x7855, 0xCBB6, 0x7856, 0xEDCC, 0x7857, 0xEDCD, 0x7858, 0xB37C, 0x7859, 0xB37D, 0x785A, 0xB37E, + 0x785B, 0xB380, 0x785C, 0xB381, 0x785D, 0xCFF5, 0x785E, 0xB382, 0x785F, 0xB383, 0x7860, 0xB384, 0x7861, 0xB385, 0x7862, 0xB386, + 0x7863, 0xB387, 0x7864, 0xB388, 0x7865, 0xB389, 0x7866, 0xB38A, 0x7867, 0xB38B, 0x7868, 0xB38C, 0x7869, 0xB38D, 0x786A, 0xEDD2, + 0x786B, 0xC1F2, 0x786C, 0xD3B2, 0x786D, 0xEDCB, 0x786E, 0xC8B7, 0x786F, 0xB38E, 0x7870, 0xB38F, 0x7871, 0xB390, 0x7872, 0xB391, + 0x7873, 0xB392, 0x7874, 0xB393, 0x7875, 0xB394, 0x7876, 0xB395, 0x7877, 0xBCEF, 0x7878, 0xB396, 0x7879, 0xB397, 0x787A, 0xB398, + 0x787B, 0xB399, 0x787C, 0xC5F0, 0x787D, 0xB39A, 0x787E, 0xB39B, 0x787F, 0xB39C, 0x7880, 0xB39D, 0x7881, 0xB39E, 0x7882, 0xB39F, + 0x7883, 0xB3A0, 0x7884, 0xB440, 0x7885, 0xB441, 0x7886, 0xB442, 0x7887, 0xEDD6, 0x7888, 0xB443, 0x7889, 0xB5EF, 0x788A, 0xB444, + 0x788B, 0xB445, 0x788C, 0xC2B5, 0x788D, 0xB0AD, 0x788E, 0xCBE9, 0x788F, 0xB446, 0x7890, 0xB447, 0x7891, 0xB1AE, 0x7892, 0xB448, + 0x7893, 0xEDD4, 0x7894, 0xB449, 0x7895, 0xB44A, 0x7896, 0xB44B, 0x7897, 0xCDEB, 0x7898, 0xB5E2, 0x7899, 0xB44C, 0x789A, 0xEDD5, + 0x789B, 0xEDD3, 0x789C, 0xEDD7, 0x789D, 0xB44D, 0x789E, 0xB44E, 0x789F, 0xB5FA, 0x78A0, 0xB44F, 0x78A1, 0xEDD8, 0x78A2, 0xB450, + 0x78A3, 0xEDD9, 0x78A4, 0xB451, 0x78A5, 0xEDDC, 0x78A6, 0xB452, 0x78A7, 0xB1CC, 0x78A8, 0xB453, 0x78A9, 0xB454, 0x78AA, 0xB455, + 0x78AB, 0xB456, 0x78AC, 0xB457, 0x78AD, 0xB458, 0x78AE, 0xB459, 0x78AF, 0xB45A, 0x78B0, 0xC5F6, 0x78B1, 0xBCEE, 0x78B2, 0xEDDA, + 0x78B3, 0xCCBC, 0x78B4, 0xB2EA, 0x78B5, 0xB45B, 0x78B6, 0xB45C, 0x78B7, 0xB45D, 0x78B8, 0xB45E, 0x78B9, 0xEDDB, 0x78BA, 0xB45F, + 0x78BB, 0xB460, 0x78BC, 0xB461, 0x78BD, 0xB462, 0x78BE, 0xC4EB, 0x78BF, 0xB463, 0x78C0, 0xB464, 0x78C1, 0xB4C5, 0x78C2, 0xB465, + 0x78C3, 0xB466, 0x78C4, 0xB467, 0x78C5, 0xB0F5, 0x78C6, 0xB468, 0x78C7, 0xB469, 0x78C8, 0xB46A, 0x78C9, 0xEDDF, 0x78CA, 0xC0DA, + 0x78CB, 0xB4E8, 0x78CC, 0xB46B, 0x78CD, 0xB46C, 0x78CE, 0xB46D, 0x78CF, 0xB46E, 0x78D0, 0xC5CD, 0x78D1, 0xB46F, 0x78D2, 0xB470, + 0x78D3, 0xB471, 0x78D4, 0xEDDD, 0x78D5, 0xBFC4, 0x78D6, 0xB472, 0x78D7, 0xB473, 0x78D8, 0xB474, 0x78D9, 0xEDDE, 0x78DA, 0xB475, + 0x78DB, 0xB476, 0x78DC, 0xB477, 0x78DD, 0xB478, 0x78DE, 0xB479, 0x78DF, 0xB47A, 0x78E0, 0xB47B, 0x78E1, 0xB47C, 0x78E2, 0xB47D, + 0x78E3, 0xB47E, 0x78E4, 0xB480, 0x78E5, 0xB481, 0x78E6, 0xB482, 0x78E7, 0xB483, 0x78E8, 0xC4A5, 0x78E9, 0xB484, 0x78EA, 0xB485, + 0x78EB, 0xB486, 0x78EC, 0xEDE0, 0x78ED, 0xB487, 0x78EE, 0xB488, 0x78EF, 0xB489, 0x78F0, 0xB48A, 0x78F1, 0xB48B, 0x78F2, 0xEDE1, + 0x78F3, 0xB48C, 0x78F4, 0xEDE3, 0x78F5, 0xB48D, 0x78F6, 0xB48E, 0x78F7, 0xC1D7, 0x78F8, 0xB48F, 0x78F9, 0xB490, 0x78FA, 0xBBC7, + 0x78FB, 0xB491, 0x78FC, 0xB492, 0x78FD, 0xB493, 0x78FE, 0xB494, 0x78FF, 0xB495, 0x7900, 0xB496, 0x7901, 0xBDB8, 0x7902, 0xB497, + 0x7903, 0xB498, 0x7904, 0xB499, 0x7905, 0xEDE2, 0x7906, 0xB49A, 0x7907, 0xB49B, 0x7908, 0xB49C, 0x7909, 0xB49D, 0x790A, 0xB49E, + 0x790B, 0xB49F, 0x790C, 0xB4A0, 0x790D, 0xB540, 0x790E, 0xB541, 0x790F, 0xB542, 0x7910, 0xB543, 0x7911, 0xB544, 0x7912, 0xB545, + 0x7913, 0xEDE4, 0x7914, 0xB546, 0x7915, 0xB547, 0x7916, 0xB548, 0x7917, 0xB549, 0x7918, 0xB54A, 0x7919, 0xB54B, 0x791A, 0xB54C, + 0x791B, 0xB54D, 0x791C, 0xB54E, 0x791D, 0xB54F, 0x791E, 0xEDE6, 0x791F, 0xB550, 0x7920, 0xB551, 0x7921, 0xB552, 0x7922, 0xB553, + 0x7923, 0xB554, 0x7924, 0xEDE5, 0x7925, 0xB555, 0x7926, 0xB556, 0x7927, 0xB557, 0x7928, 0xB558, 0x7929, 0xB559, 0x792A, 0xB55A, + 0x792B, 0xB55B, 0x792C, 0xB55C, 0x792D, 0xB55D, 0x792E, 0xB55E, 0x792F, 0xB55F, 0x7930, 0xB560, 0x7931, 0xB561, 0x7932, 0xB562, + 0x7933, 0xB563, 0x7934, 0xEDE7, 0x7935, 0xB564, 0x7936, 0xB565, 0x7937, 0xB566, 0x7938, 0xB567, 0x7939, 0xB568, 0x793A, 0xCABE, + 0x793B, 0xECEA, 0x793C, 0xC0F1, 0x793D, 0xB569, 0x793E, 0xC9E7, 0x793F, 0xB56A, 0x7940, 0xECEB, 0x7941, 0xC6EE, 0x7942, 0xB56B, + 0x7943, 0xB56C, 0x7944, 0xB56D, 0x7945, 0xB56E, 0x7946, 0xECEC, 0x7947, 0xB56F, 0x7948, 0xC6ED, 0x7949, 0xECED, 0x794A, 0xB570, + 0x794B, 0xB571, 0x794C, 0xB572, 0x794D, 0xB573, 0x794E, 0xB574, 0x794F, 0xB575, 0x7950, 0xB576, 0x7951, 0xB577, 0x7952, 0xB578, + 0x7953, 0xECF0, 0x7954, 0xB579, 0x7955, 0xB57A, 0x7956, 0xD7E6, 0x7957, 0xECF3, 0x7958, 0xB57B, 0x7959, 0xB57C, 0x795A, 0xECF1, + 0x795B, 0xECEE, 0x795C, 0xECEF, 0x795D, 0xD7A3, 0x795E, 0xC9F1, 0x795F, 0xCBEE, 0x7960, 0xECF4, 0x7961, 0xB57D, 0x7962, 0xECF2, + 0x7963, 0xB57E, 0x7964, 0xB580, 0x7965, 0xCFE9, 0x7966, 0xB581, 0x7967, 0xECF6, 0x7968, 0xC6B1, 0x7969, 0xB582, 0x796A, 0xB583, + 0x796B, 0xB584, 0x796C, 0xB585, 0x796D, 0xBCC0, 0x796E, 0xB586, 0x796F, 0xECF5, 0x7970, 0xB587, 0x7971, 0xB588, 0x7972, 0xB589, + 0x7973, 0xB58A, 0x7974, 0xB58B, 0x7975, 0xB58C, 0x7976, 0xB58D, 0x7977, 0xB5BB, 0x7978, 0xBBF6, 0x7979, 0xB58E, 0x797A, 0xECF7, + 0x797B, 0xB58F, 0x797C, 0xB590, 0x797D, 0xB591, 0x797E, 0xB592, 0x797F, 0xB593, 0x7980, 0xD9F7, 0x7981, 0xBDFB, 0x7982, 0xB594, + 0x7983, 0xB595, 0x7984, 0xC2BB, 0x7985, 0xECF8, 0x7986, 0xB596, 0x7987, 0xB597, 0x7988, 0xB598, 0x7989, 0xB599, 0x798A, 0xECF9, + 0x798B, 0xB59A, 0x798C, 0xB59B, 0x798D, 0xB59C, 0x798E, 0xB59D, 0x798F, 0xB8A3, 0x7990, 0xB59E, 0x7991, 0xB59F, 0x7992, 0xB5A0, + 0x7993, 0xB640, 0x7994, 0xB641, 0x7995, 0xB642, 0x7996, 0xB643, 0x7997, 0xB644, 0x7998, 0xB645, 0x7999, 0xB646, 0x799A, 0xECFA, + 0x799B, 0xB647, 0x799C, 0xB648, 0x799D, 0xB649, 0x799E, 0xB64A, 0x799F, 0xB64B, 0x79A0, 0xB64C, 0x79A1, 0xB64D, 0x79A2, 0xB64E, + 0x79A3, 0xB64F, 0x79A4, 0xB650, 0x79A5, 0xB651, 0x79A6, 0xB652, 0x79A7, 0xECFB, 0x79A8, 0xB653, 0x79A9, 0xB654, 0x79AA, 0xB655, + 0x79AB, 0xB656, 0x79AC, 0xB657, 0x79AD, 0xB658, 0x79AE, 0xB659, 0x79AF, 0xB65A, 0x79B0, 0xB65B, 0x79B1, 0xB65C, 0x79B2, 0xB65D, + 0x79B3, 0xECFC, 0x79B4, 0xB65E, 0x79B5, 0xB65F, 0x79B6, 0xB660, 0x79B7, 0xB661, 0x79B8, 0xB662, 0x79B9, 0xD3ED, 0x79BA, 0xD8AE, + 0x79BB, 0xC0EB, 0x79BC, 0xB663, 0x79BD, 0xC7DD, 0x79BE, 0xBACC, 0x79BF, 0xB664, 0x79C0, 0xD0E3, 0x79C1, 0xCBBD, 0x79C2, 0xB665, + 0x79C3, 0xCDBA, 0x79C4, 0xB666, 0x79C5, 0xB667, 0x79C6, 0xB8D1, 0x79C7, 0xB668, 0x79C8, 0xB669, 0x79C9, 0xB1FC, 0x79CA, 0xB66A, + 0x79CB, 0xC7EF, 0x79CC, 0xB66B, 0x79CD, 0xD6D6, 0x79CE, 0xB66C, 0x79CF, 0xB66D, 0x79D0, 0xB66E, 0x79D1, 0xBFC6, 0x79D2, 0xC3EB, + 0x79D3, 0xB66F, 0x79D4, 0xB670, 0x79D5, 0xEFF5, 0x79D6, 0xB671, 0x79D7, 0xB672, 0x79D8, 0xC3D8, 0x79D9, 0xB673, 0x79DA, 0xB674, + 0x79DB, 0xB675, 0x79DC, 0xB676, 0x79DD, 0xB677, 0x79DE, 0xB678, 0x79DF, 0xD7E2, 0x79E0, 0xB679, 0x79E1, 0xB67A, 0x79E2, 0xB67B, + 0x79E3, 0xEFF7, 0x79E4, 0xB3D3, 0x79E5, 0xB67C, 0x79E6, 0xC7D8, 0x79E7, 0xD1ED, 0x79E8, 0xB67D, 0x79E9, 0xD6C8, 0x79EA, 0xB67E, + 0x79EB, 0xEFF8, 0x79EC, 0xB680, 0x79ED, 0xEFF6, 0x79EE, 0xB681, 0x79EF, 0xBBFD, 0x79F0, 0xB3C6, 0x79F1, 0xB682, 0x79F2, 0xB683, + 0x79F3, 0xB684, 0x79F4, 0xB685, 0x79F5, 0xB686, 0x79F6, 0xB687, 0x79F7, 0xB688, 0x79F8, 0xBDD5, 0x79F9, 0xB689, 0x79FA, 0xB68A, + 0x79FB, 0xD2C6, 0x79FC, 0xB68B, 0x79FD, 0xBBE0, 0x79FE, 0xB68C, 0x79FF, 0xB68D, 0x7A00, 0xCFA1, 0x7A01, 0xB68E, 0x7A02, 0xEFFC, + 0x7A03, 0xEFFB, 0x7A04, 0xB68F, 0x7A05, 0xB690, 0x7A06, 0xEFF9, 0x7A07, 0xB691, 0x7A08, 0xB692, 0x7A09, 0xB693, 0x7A0A, 0xB694, + 0x7A0B, 0xB3CC, 0x7A0C, 0xB695, 0x7A0D, 0xC9D4, 0x7A0E, 0xCBB0, 0x7A0F, 0xB696, 0x7A10, 0xB697, 0x7A11, 0xB698, 0x7A12, 0xB699, + 0x7A13, 0xB69A, 0x7A14, 0xEFFE, 0x7A15, 0xB69B, 0x7A16, 0xB69C, 0x7A17, 0xB0DE, 0x7A18, 0xB69D, 0x7A19, 0xB69E, 0x7A1A, 0xD6C9, + 0x7A1B, 0xB69F, 0x7A1C, 0xB6A0, 0x7A1D, 0xB740, 0x7A1E, 0xEFFD, 0x7A1F, 0xB741, 0x7A20, 0xB3ED, 0x7A21, 0xB742, 0x7A22, 0xB743, + 0x7A23, 0xF6D5, 0x7A24, 0xB744, 0x7A25, 0xB745, 0x7A26, 0xB746, 0x7A27, 0xB747, 0x7A28, 0xB748, 0x7A29, 0xB749, 0x7A2A, 0xB74A, + 0x7A2B, 0xB74B, 0x7A2C, 0xB74C, 0x7A2D, 0xB74D, 0x7A2E, 0xB74E, 0x7A2F, 0xB74F, 0x7A30, 0xB750, 0x7A31, 0xB751, 0x7A32, 0xB752, + 0x7A33, 0xCEC8, 0x7A34, 0xB753, 0x7A35, 0xB754, 0x7A36, 0xB755, 0x7A37, 0xF0A2, 0x7A38, 0xB756, 0x7A39, 0xF0A1, 0x7A3A, 0xB757, + 0x7A3B, 0xB5BE, 0x7A3C, 0xBCDA, 0x7A3D, 0xBBFC, 0x7A3E, 0xB758, 0x7A3F, 0xB8E5, 0x7A40, 0xB759, 0x7A41, 0xB75A, 0x7A42, 0xB75B, + 0x7A43, 0xB75C, 0x7A44, 0xB75D, 0x7A45, 0xB75E, 0x7A46, 0xC4C2, 0x7A47, 0xB75F, 0x7A48, 0xB760, 0x7A49, 0xB761, 0x7A4A, 0xB762, + 0x7A4B, 0xB763, 0x7A4C, 0xB764, 0x7A4D, 0xB765, 0x7A4E, 0xB766, 0x7A4F, 0xB767, 0x7A50, 0xB768, 0x7A51, 0xF0A3, 0x7A52, 0xB769, + 0x7A53, 0xB76A, 0x7A54, 0xB76B, 0x7A55, 0xB76C, 0x7A56, 0xB76D, 0x7A57, 0xCBEB, 0x7A58, 0xB76E, 0x7A59, 0xB76F, 0x7A5A, 0xB770, + 0x7A5B, 0xB771, 0x7A5C, 0xB772, 0x7A5D, 0xB773, 0x7A5E, 0xB774, 0x7A5F, 0xB775, 0x7A60, 0xB776, 0x7A61, 0xB777, 0x7A62, 0xB778, + 0x7A63, 0xB779, 0x7A64, 0xB77A, 0x7A65, 0xB77B, 0x7A66, 0xB77C, 0x7A67, 0xB77D, 0x7A68, 0xB77E, 0x7A69, 0xB780, 0x7A6A, 0xB781, + 0x7A6B, 0xB782, 0x7A6C, 0xB783, 0x7A6D, 0xB784, 0x7A6E, 0xB785, 0x7A6F, 0xB786, 0x7A70, 0xF0A6, 0x7A71, 0xB787, 0x7A72, 0xB788, + 0x7A73, 0xB789, 0x7A74, 0xD1A8, 0x7A75, 0xB78A, 0x7A76, 0xBEBF, 0x7A77, 0xC7EE, 0x7A78, 0xF1B6, 0x7A79, 0xF1B7, 0x7A7A, 0xBFD5, + 0x7A7B, 0xB78B, 0x7A7C, 0xB78C, 0x7A7D, 0xB78D, 0x7A7E, 0xB78E, 0x7A7F, 0xB4A9, 0x7A80, 0xF1B8, 0x7A81, 0xCDBB, 0x7A82, 0xB78F, + 0x7A83, 0xC7D4, 0x7A84, 0xD5AD, 0x7A85, 0xB790, 0x7A86, 0xF1B9, 0x7A87, 0xB791, 0x7A88, 0xF1BA, 0x7A89, 0xB792, 0x7A8A, 0xB793, + 0x7A8B, 0xB794, 0x7A8C, 0xB795, 0x7A8D, 0xC7CF, 0x7A8E, 0xB796, 0x7A8F, 0xB797, 0x7A90, 0xB798, 0x7A91, 0xD2A4, 0x7A92, 0xD6CF, + 0x7A93, 0xB799, 0x7A94, 0xB79A, 0x7A95, 0xF1BB, 0x7A96, 0xBDD1, 0x7A97, 0xB4B0, 0x7A98, 0xBEBD, 0x7A99, 0xB79B, 0x7A9A, 0xB79C, + 0x7A9B, 0xB79D, 0x7A9C, 0xB4DC, 0x7A9D, 0xCED1, 0x7A9E, 0xB79E, 0x7A9F, 0xBFDF, 0x7AA0, 0xF1BD, 0x7AA1, 0xB79F, 0x7AA2, 0xB7A0, + 0x7AA3, 0xB840, 0x7AA4, 0xB841, 0x7AA5, 0xBFFA, 0x7AA6, 0xF1BC, 0x7AA7, 0xB842, 0x7AA8, 0xF1BF, 0x7AA9, 0xB843, 0x7AAA, 0xB844, + 0x7AAB, 0xB845, 0x7AAC, 0xF1BE, 0x7AAD, 0xF1C0, 0x7AAE, 0xB846, 0x7AAF, 0xB847, 0x7AB0, 0xB848, 0x7AB1, 0xB849, 0x7AB2, 0xB84A, + 0x7AB3, 0xF1C1, 0x7AB4, 0xB84B, 0x7AB5, 0xB84C, 0x7AB6, 0xB84D, 0x7AB7, 0xB84E, 0x7AB8, 0xB84F, 0x7AB9, 0xB850, 0x7ABA, 0xB851, + 0x7ABB, 0xB852, 0x7ABC, 0xB853, 0x7ABD, 0xB854, 0x7ABE, 0xB855, 0x7ABF, 0xC1FE, 0x7AC0, 0xB856, 0x7AC1, 0xB857, 0x7AC2, 0xB858, + 0x7AC3, 0xB859, 0x7AC4, 0xB85A, 0x7AC5, 0xB85B, 0x7AC6, 0xB85C, 0x7AC7, 0xB85D, 0x7AC8, 0xB85E, 0x7AC9, 0xB85F, 0x7ACA, 0xB860, + 0x7ACB, 0xC1A2, 0x7ACC, 0xB861, 0x7ACD, 0xB862, 0x7ACE, 0xB863, 0x7ACF, 0xB864, 0x7AD0, 0xB865, 0x7AD1, 0xB866, 0x7AD2, 0xB867, + 0x7AD3, 0xB868, 0x7AD4, 0xB869, 0x7AD5, 0xB86A, 0x7AD6, 0xCAFA, 0x7AD7, 0xB86B, 0x7AD8, 0xB86C, 0x7AD9, 0xD5BE, 0x7ADA, 0xB86D, + 0x7ADB, 0xB86E, 0x7ADC, 0xB86F, 0x7ADD, 0xB870, 0x7ADE, 0xBEBA, 0x7ADF, 0xBEB9, 0x7AE0, 0xD5C2, 0x7AE1, 0xB871, 0x7AE2, 0xB872, + 0x7AE3, 0xBFA2, 0x7AE4, 0xB873, 0x7AE5, 0xCDAF, 0x7AE6, 0xF1B5, 0x7AE7, 0xB874, 0x7AE8, 0xB875, 0x7AE9, 0xB876, 0x7AEA, 0xB877, + 0x7AEB, 0xB878, 0x7AEC, 0xB879, 0x7AED, 0xBDDF, 0x7AEE, 0xB87A, 0x7AEF, 0xB6CB, 0x7AF0, 0xB87B, 0x7AF1, 0xB87C, 0x7AF2, 0xB87D, + 0x7AF3, 0xB87E, 0x7AF4, 0xB880, 0x7AF5, 0xB881, 0x7AF6, 0xB882, 0x7AF7, 0xB883, 0x7AF8, 0xB884, 0x7AF9, 0xD6F1, 0x7AFA, 0xF3C3, + 0x7AFB, 0xB885, 0x7AFC, 0xB886, 0x7AFD, 0xF3C4, 0x7AFE, 0xB887, 0x7AFF, 0xB8CD, 0x7B00, 0xB888, 0x7B01, 0xB889, 0x7B02, 0xB88A, + 0x7B03, 0xF3C6, 0x7B04, 0xF3C7, 0x7B05, 0xB88B, 0x7B06, 0xB0CA, 0x7B07, 0xB88C, 0x7B08, 0xF3C5, 0x7B09, 0xB88D, 0x7B0A, 0xF3C9, + 0x7B0B, 0xCBF1, 0x7B0C, 0xB88E, 0x7B0D, 0xB88F, 0x7B0E, 0xB890, 0x7B0F, 0xF3CB, 0x7B10, 0xB891, 0x7B11, 0xD0A6, 0x7B12, 0xB892, + 0x7B13, 0xB893, 0x7B14, 0xB1CA, 0x7B15, 0xF3C8, 0x7B16, 0xB894, 0x7B17, 0xB895, 0x7B18, 0xB896, 0x7B19, 0xF3CF, 0x7B1A, 0xB897, + 0x7B1B, 0xB5D1, 0x7B1C, 0xB898, 0x7B1D, 0xB899, 0x7B1E, 0xF3D7, 0x7B1F, 0xB89A, 0x7B20, 0xF3D2, 0x7B21, 0xB89B, 0x7B22, 0xB89C, + 0x7B23, 0xB89D, 0x7B24, 0xF3D4, 0x7B25, 0xF3D3, 0x7B26, 0xB7FB, 0x7B27, 0xB89E, 0x7B28, 0xB1BF, 0x7B29, 0xB89F, 0x7B2A, 0xF3CE, + 0x7B2B, 0xF3CA, 0x7B2C, 0xB5DA, 0x7B2D, 0xB8A0, 0x7B2E, 0xF3D0, 0x7B2F, 0xB940, 0x7B30, 0xB941, 0x7B31, 0xF3D1, 0x7B32, 0xB942, + 0x7B33, 0xF3D5, 0x7B34, 0xB943, 0x7B35, 0xB944, 0x7B36, 0xB945, 0x7B37, 0xB946, 0x7B38, 0xF3CD, 0x7B39, 0xB947, 0x7B3A, 0xBCE3, + 0x7B3B, 0xB948, 0x7B3C, 0xC1FD, 0x7B3D, 0xB949, 0x7B3E, 0xF3D6, 0x7B3F, 0xB94A, 0x7B40, 0xB94B, 0x7B41, 0xB94C, 0x7B42, 0xB94D, + 0x7B43, 0xB94E, 0x7B44, 0xB94F, 0x7B45, 0xF3DA, 0x7B46, 0xB950, 0x7B47, 0xF3CC, 0x7B48, 0xB951, 0x7B49, 0xB5C8, 0x7B4A, 0xB952, + 0x7B4B, 0xBDEE, 0x7B4C, 0xF3DC, 0x7B4D, 0xB953, 0x7B4E, 0xB954, 0x7B4F, 0xB7A4, 0x7B50, 0xBFF0, 0x7B51, 0xD6FE, 0x7B52, 0xCDB2, + 0x7B53, 0xB955, 0x7B54, 0xB4F0, 0x7B55, 0xB956, 0x7B56, 0xB2DF, 0x7B57, 0xB957, 0x7B58, 0xF3D8, 0x7B59, 0xB958, 0x7B5A, 0xF3D9, + 0x7B5B, 0xC9B8, 0x7B5C, 0xB959, 0x7B5D, 0xF3DD, 0x7B5E, 0xB95A, 0x7B5F, 0xB95B, 0x7B60, 0xF3DE, 0x7B61, 0xB95C, 0x7B62, 0xF3E1, + 0x7B63, 0xB95D, 0x7B64, 0xB95E, 0x7B65, 0xB95F, 0x7B66, 0xB960, 0x7B67, 0xB961, 0x7B68, 0xB962, 0x7B69, 0xB963, 0x7B6A, 0xB964, + 0x7B6B, 0xB965, 0x7B6C, 0xB966, 0x7B6D, 0xB967, 0x7B6E, 0xF3DF, 0x7B6F, 0xB968, 0x7B70, 0xB969, 0x7B71, 0xF3E3, 0x7B72, 0xF3E2, + 0x7B73, 0xB96A, 0x7B74, 0xB96B, 0x7B75, 0xF3DB, 0x7B76, 0xB96C, 0x7B77, 0xBFEA, 0x7B78, 0xB96D, 0x7B79, 0xB3EF, 0x7B7A, 0xB96E, + 0x7B7B, 0xF3E0, 0x7B7C, 0xB96F, 0x7B7D, 0xB970, 0x7B7E, 0xC7A9, 0x7B7F, 0xB971, 0x7B80, 0xBCF2, 0x7B81, 0xB972, 0x7B82, 0xB973, + 0x7B83, 0xB974, 0x7B84, 0xB975, 0x7B85, 0xF3EB, 0x7B86, 0xB976, 0x7B87, 0xB977, 0x7B88, 0xB978, 0x7B89, 0xB979, 0x7B8A, 0xB97A, + 0x7B8B, 0xB97B, 0x7B8C, 0xB97C, 0x7B8D, 0xB9BF, 0x7B8E, 0xB97D, 0x7B8F, 0xB97E, 0x7B90, 0xF3E4, 0x7B91, 0xB980, 0x7B92, 0xB981, + 0x7B93, 0xB982, 0x7B94, 0xB2AD, 0x7B95, 0xBBFE, 0x7B96, 0xB983, 0x7B97, 0xCBE3, 0x7B98, 0xB984, 0x7B99, 0xB985, 0x7B9A, 0xB986, + 0x7B9B, 0xB987, 0x7B9C, 0xF3ED, 0x7B9D, 0xF3E9, 0x7B9E, 0xB988, 0x7B9F, 0xB989, 0x7BA0, 0xB98A, 0x7BA1, 0xB9DC, 0x7BA2, 0xF3EE, + 0x7BA3, 0xB98B, 0x7BA4, 0xB98C, 0x7BA5, 0xB98D, 0x7BA6, 0xF3E5, 0x7BA7, 0xF3E6, 0x7BA8, 0xF3EA, 0x7BA9, 0xC2E1, 0x7BAA, 0xF3EC, + 0x7BAB, 0xF3EF, 0x7BAC, 0xF3E8, 0x7BAD, 0xBCFD, 0x7BAE, 0xB98E, 0x7BAF, 0xB98F, 0x7BB0, 0xB990, 0x7BB1, 0xCFE4, 0x7BB2, 0xB991, + 0x7BB3, 0xB992, 0x7BB4, 0xF3F0, 0x7BB5, 0xB993, 0x7BB6, 0xB994, 0x7BB7, 0xB995, 0x7BB8, 0xF3E7, 0x7BB9, 0xB996, 0x7BBA, 0xB997, + 0x7BBB, 0xB998, 0x7BBC, 0xB999, 0x7BBD, 0xB99A, 0x7BBE, 0xB99B, 0x7BBF, 0xB99C, 0x7BC0, 0xB99D, 0x7BC1, 0xF3F2, 0x7BC2, 0xB99E, + 0x7BC3, 0xB99F, 0x7BC4, 0xB9A0, 0x7BC5, 0xBA40, 0x7BC6, 0xD7AD, 0x7BC7, 0xC6AA, 0x7BC8, 0xBA41, 0x7BC9, 0xBA42, 0x7BCA, 0xBA43, + 0x7BCB, 0xBA44, 0x7BCC, 0xF3F3, 0x7BCD, 0xBA45, 0x7BCE, 0xBA46, 0x7BCF, 0xBA47, 0x7BD0, 0xBA48, 0x7BD1, 0xF3F1, 0x7BD2, 0xBA49, + 0x7BD3, 0xC2A8, 0x7BD4, 0xBA4A, 0x7BD5, 0xBA4B, 0x7BD6, 0xBA4C, 0x7BD7, 0xBA4D, 0x7BD8, 0xBA4E, 0x7BD9, 0xB8DD, 0x7BDA, 0xF3F5, + 0x7BDB, 0xBA4F, 0x7BDC, 0xBA50, 0x7BDD, 0xF3F4, 0x7BDE, 0xBA51, 0x7BDF, 0xBA52, 0x7BE0, 0xBA53, 0x7BE1, 0xB4DB, 0x7BE2, 0xBA54, + 0x7BE3, 0xBA55, 0x7BE4, 0xBA56, 0x7BE5, 0xF3F6, 0x7BE6, 0xF3F7, 0x7BE7, 0xBA57, 0x7BE8, 0xBA58, 0x7BE9, 0xBA59, 0x7BEA, 0xF3F8, + 0x7BEB, 0xBA5A, 0x7BEC, 0xBA5B, 0x7BED, 0xBA5C, 0x7BEE, 0xC0BA, 0x7BEF, 0xBA5D, 0x7BF0, 0xBA5E, 0x7BF1, 0xC0E9, 0x7BF2, 0xBA5F, + 0x7BF3, 0xBA60, 0x7BF4, 0xBA61, 0x7BF5, 0xBA62, 0x7BF6, 0xBA63, 0x7BF7, 0xC5F1, 0x7BF8, 0xBA64, 0x7BF9, 0xBA65, 0x7BFA, 0xBA66, + 0x7BFB, 0xBA67, 0x7BFC, 0xF3FB, 0x7BFD, 0xBA68, 0x7BFE, 0xF3FA, 0x7BFF, 0xBA69, 0x7C00, 0xBA6A, 0x7C01, 0xBA6B, 0x7C02, 0xBA6C, + 0x7C03, 0xBA6D, 0x7C04, 0xBA6E, 0x7C05, 0xBA6F, 0x7C06, 0xBA70, 0x7C07, 0xB4D8, 0x7C08, 0xBA71, 0x7C09, 0xBA72, 0x7C0A, 0xBA73, + 0x7C0B, 0xF3FE, 0x7C0C, 0xF3F9, 0x7C0D, 0xBA74, 0x7C0E, 0xBA75, 0x7C0F, 0xF3FC, 0x7C10, 0xBA76, 0x7C11, 0xBA77, 0x7C12, 0xBA78, + 0x7C13, 0xBA79, 0x7C14, 0xBA7A, 0x7C15, 0xBA7B, 0x7C16, 0xF3FD, 0x7C17, 0xBA7C, 0x7C18, 0xBA7D, 0x7C19, 0xBA7E, 0x7C1A, 0xBA80, + 0x7C1B, 0xBA81, 0x7C1C, 0xBA82, 0x7C1D, 0xBA83, 0x7C1E, 0xBA84, 0x7C1F, 0xF4A1, 0x7C20, 0xBA85, 0x7C21, 0xBA86, 0x7C22, 0xBA87, + 0x7C23, 0xBA88, 0x7C24, 0xBA89, 0x7C25, 0xBA8A, 0x7C26, 0xF4A3, 0x7C27, 0xBBC9, 0x7C28, 0xBA8B, 0x7C29, 0xBA8C, 0x7C2A, 0xF4A2, + 0x7C2B, 0xBA8D, 0x7C2C, 0xBA8E, 0x7C2D, 0xBA8F, 0x7C2E, 0xBA90, 0x7C2F, 0xBA91, 0x7C30, 0xBA92, 0x7C31, 0xBA93, 0x7C32, 0xBA94, + 0x7C33, 0xBA95, 0x7C34, 0xBA96, 0x7C35, 0xBA97, 0x7C36, 0xBA98, 0x7C37, 0xBA99, 0x7C38, 0xF4A4, 0x7C39, 0xBA9A, 0x7C3A, 0xBA9B, + 0x7C3B, 0xBA9C, 0x7C3C, 0xBA9D, 0x7C3D, 0xBA9E, 0x7C3E, 0xBA9F, 0x7C3F, 0xB2BE, 0x7C40, 0xF4A6, 0x7C41, 0xF4A5, 0x7C42, 0xBAA0, + 0x7C43, 0xBB40, 0x7C44, 0xBB41, 0x7C45, 0xBB42, 0x7C46, 0xBB43, 0x7C47, 0xBB44, 0x7C48, 0xBB45, 0x7C49, 0xBB46, 0x7C4A, 0xBB47, + 0x7C4B, 0xBB48, 0x7C4C, 0xBB49, 0x7C4D, 0xBCAE, 0x7C4E, 0xBB4A, 0x7C4F, 0xBB4B, 0x7C50, 0xBB4C, 0x7C51, 0xBB4D, 0x7C52, 0xBB4E, + 0x7C53, 0xBB4F, 0x7C54, 0xBB50, 0x7C55, 0xBB51, 0x7C56, 0xBB52, 0x7C57, 0xBB53, 0x7C58, 0xBB54, 0x7C59, 0xBB55, 0x7C5A, 0xBB56, + 0x7C5B, 0xBB57, 0x7C5C, 0xBB58, 0x7C5D, 0xBB59, 0x7C5E, 0xBB5A, 0x7C5F, 0xBB5B, 0x7C60, 0xBB5C, 0x7C61, 0xBB5D, 0x7C62, 0xBB5E, + 0x7C63, 0xBB5F, 0x7C64, 0xBB60, 0x7C65, 0xBB61, 0x7C66, 0xBB62, 0x7C67, 0xBB63, 0x7C68, 0xBB64, 0x7C69, 0xBB65, 0x7C6A, 0xBB66, + 0x7C6B, 0xBB67, 0x7C6C, 0xBB68, 0x7C6D, 0xBB69, 0x7C6E, 0xBB6A, 0x7C6F, 0xBB6B, 0x7C70, 0xBB6C, 0x7C71, 0xBB6D, 0x7C72, 0xBB6E, + 0x7C73, 0xC3D7, 0x7C74, 0xD9E1, 0x7C75, 0xBB6F, 0x7C76, 0xBB70, 0x7C77, 0xBB71, 0x7C78, 0xBB72, 0x7C79, 0xBB73, 0x7C7A, 0xBB74, + 0x7C7B, 0xC0E0, 0x7C7C, 0xF4CC, 0x7C7D, 0xD7D1, 0x7C7E, 0xBB75, 0x7C7F, 0xBB76, 0x7C80, 0xBB77, 0x7C81, 0xBB78, 0x7C82, 0xBB79, + 0x7C83, 0xBB7A, 0x7C84, 0xBB7B, 0x7C85, 0xBB7C, 0x7C86, 0xBB7D, 0x7C87, 0xBB7E, 0x7C88, 0xBB80, 0x7C89, 0xB7DB, 0x7C8A, 0xBB81, + 0x7C8B, 0xBB82, 0x7C8C, 0xBB83, 0x7C8D, 0xBB84, 0x7C8E, 0xBB85, 0x7C8F, 0xBB86, 0x7C90, 0xBB87, 0x7C91, 0xF4CE, 0x7C92, 0xC1A3, + 0x7C93, 0xBB88, 0x7C94, 0xBB89, 0x7C95, 0xC6C9, 0x7C96, 0xBB8A, 0x7C97, 0xB4D6, 0x7C98, 0xD5B3, 0x7C99, 0xBB8B, 0x7C9A, 0xBB8C, + 0x7C9B, 0xBB8D, 0x7C9C, 0xF4D0, 0x7C9D, 0xF4CF, 0x7C9E, 0xF4D1, 0x7C9F, 0xCBDA, 0x7CA0, 0xBB8E, 0x7CA1, 0xBB8F, 0x7CA2, 0xF4D2, + 0x7CA3, 0xBB90, 0x7CA4, 0xD4C1, 0x7CA5, 0xD6E0, 0x7CA6, 0xBB91, 0x7CA7, 0xBB92, 0x7CA8, 0xBB93, 0x7CA9, 0xBB94, 0x7CAA, 0xB7E0, + 0x7CAB, 0xBB95, 0x7CAC, 0xBB96, 0x7CAD, 0xBB97, 0x7CAE, 0xC1B8, 0x7CAF, 0xBB98, 0x7CB0, 0xBB99, 0x7CB1, 0xC1BB, 0x7CB2, 0xF4D3, + 0x7CB3, 0xBEAC, 0x7CB4, 0xBB9A, 0x7CB5, 0xBB9B, 0x7CB6, 0xBB9C, 0x7CB7, 0xBB9D, 0x7CB8, 0xBB9E, 0x7CB9, 0xB4E2, 0x7CBA, 0xBB9F, + 0x7CBB, 0xBBA0, 0x7CBC, 0xF4D4, 0x7CBD, 0xF4D5, 0x7CBE, 0xBEAB, 0x7CBF, 0xBC40, 0x7CC0, 0xBC41, 0x7CC1, 0xF4D6, 0x7CC2, 0xBC42, + 0x7CC3, 0xBC43, 0x7CC4, 0xBC44, 0x7CC5, 0xF4DB, 0x7CC6, 0xBC45, 0x7CC7, 0xF4D7, 0x7CC8, 0xF4DA, 0x7CC9, 0xBC46, 0x7CCA, 0xBAFD, + 0x7CCB, 0xBC47, 0x7CCC, 0xF4D8, 0x7CCD, 0xF4D9, 0x7CCE, 0xBC48, 0x7CCF, 0xBC49, 0x7CD0, 0xBC4A, 0x7CD1, 0xBC4B, 0x7CD2, 0xBC4C, + 0x7CD3, 0xBC4D, 0x7CD4, 0xBC4E, 0x7CD5, 0xB8E2, 0x7CD6, 0xCCC7, 0x7CD7, 0xF4DC, 0x7CD8, 0xBC4F, 0x7CD9, 0xB2DA, 0x7CDA, 0xBC50, + 0x7CDB, 0xBC51, 0x7CDC, 0xC3D3, 0x7CDD, 0xBC52, 0x7CDE, 0xBC53, 0x7CDF, 0xD4E3, 0x7CE0, 0xBFB7, 0x7CE1, 0xBC54, 0x7CE2, 0xBC55, + 0x7CE3, 0xBC56, 0x7CE4, 0xBC57, 0x7CE5, 0xBC58, 0x7CE6, 0xBC59, 0x7CE7, 0xBC5A, 0x7CE8, 0xF4DD, 0x7CE9, 0xBC5B, 0x7CEA, 0xBC5C, + 0x7CEB, 0xBC5D, 0x7CEC, 0xBC5E, 0x7CED, 0xBC5F, 0x7CEE, 0xBC60, 0x7CEF, 0xC5B4, 0x7CF0, 0xBC61, 0x7CF1, 0xBC62, 0x7CF2, 0xBC63, + 0x7CF3, 0xBC64, 0x7CF4, 0xBC65, 0x7CF5, 0xBC66, 0x7CF6, 0xBC67, 0x7CF7, 0xBC68, 0x7CF8, 0xF4E9, 0x7CF9, 0xBC69, 0x7CFA, 0xBC6A, + 0x7CFB, 0xCFB5, 0x7CFC, 0xBC6B, 0x7CFD, 0xBC6C, 0x7CFE, 0xBC6D, 0x7CFF, 0xBC6E, 0x7D00, 0xBC6F, 0x7D01, 0xBC70, 0x7D02, 0xBC71, + 0x7D03, 0xBC72, 0x7D04, 0xBC73, 0x7D05, 0xBC74, 0x7D06, 0xBC75, 0x7D07, 0xBC76, 0x7D08, 0xBC77, 0x7D09, 0xBC78, 0x7D0A, 0xCEC9, + 0x7D0B, 0xBC79, 0x7D0C, 0xBC7A, 0x7D0D, 0xBC7B, 0x7D0E, 0xBC7C, 0x7D0F, 0xBC7D, 0x7D10, 0xBC7E, 0x7D11, 0xBC80, 0x7D12, 0xBC81, + 0x7D13, 0xBC82, 0x7D14, 0xBC83, 0x7D15, 0xBC84, 0x7D16, 0xBC85, 0x7D17, 0xBC86, 0x7D18, 0xBC87, 0x7D19, 0xBC88, 0x7D1A, 0xBC89, + 0x7D1B, 0xBC8A, 0x7D1C, 0xBC8B, 0x7D1D, 0xBC8C, 0x7D1E, 0xBC8D, 0x7D1F, 0xBC8E, 0x7D20, 0xCBD8, 0x7D21, 0xBC8F, 0x7D22, 0xCBF7, + 0x7D23, 0xBC90, 0x7D24, 0xBC91, 0x7D25, 0xBC92, 0x7D26, 0xBC93, 0x7D27, 0xBDF4, 0x7D28, 0xBC94, 0x7D29, 0xBC95, 0x7D2A, 0xBC96, + 0x7D2B, 0xD7CF, 0x7D2C, 0xBC97, 0x7D2D, 0xBC98, 0x7D2E, 0xBC99, 0x7D2F, 0xC0DB, 0x7D30, 0xBC9A, 0x7D31, 0xBC9B, 0x7D32, 0xBC9C, + 0x7D33, 0xBC9D, 0x7D34, 0xBC9E, 0x7D35, 0xBC9F, 0x7D36, 0xBCA0, 0x7D37, 0xBD40, 0x7D38, 0xBD41, 0x7D39, 0xBD42, 0x7D3A, 0xBD43, + 0x7D3B, 0xBD44, 0x7D3C, 0xBD45, 0x7D3D, 0xBD46, 0x7D3E, 0xBD47, 0x7D3F, 0xBD48, 0x7D40, 0xBD49, 0x7D41, 0xBD4A, 0x7D42, 0xBD4B, + 0x7D43, 0xBD4C, 0x7D44, 0xBD4D, 0x7D45, 0xBD4E, 0x7D46, 0xBD4F, 0x7D47, 0xBD50, 0x7D48, 0xBD51, 0x7D49, 0xBD52, 0x7D4A, 0xBD53, + 0x7D4B, 0xBD54, 0x7D4C, 0xBD55, 0x7D4D, 0xBD56, 0x7D4E, 0xBD57, 0x7D4F, 0xBD58, 0x7D50, 0xBD59, 0x7D51, 0xBD5A, 0x7D52, 0xBD5B, + 0x7D53, 0xBD5C, 0x7D54, 0xBD5D, 0x7D55, 0xBD5E, 0x7D56, 0xBD5F, 0x7D57, 0xBD60, 0x7D58, 0xBD61, 0x7D59, 0xBD62, 0x7D5A, 0xBD63, + 0x7D5B, 0xBD64, 0x7D5C, 0xBD65, 0x7D5D, 0xBD66, 0x7D5E, 0xBD67, 0x7D5F, 0xBD68, 0x7D60, 0xBD69, 0x7D61, 0xBD6A, 0x7D62, 0xBD6B, + 0x7D63, 0xBD6C, 0x7D64, 0xBD6D, 0x7D65, 0xBD6E, 0x7D66, 0xBD6F, 0x7D67, 0xBD70, 0x7D68, 0xBD71, 0x7D69, 0xBD72, 0x7D6A, 0xBD73, + 0x7D6B, 0xBD74, 0x7D6C, 0xBD75, 0x7D6D, 0xBD76, 0x7D6E, 0xD0F5, 0x7D6F, 0xBD77, 0x7D70, 0xBD78, 0x7D71, 0xBD79, 0x7D72, 0xBD7A, + 0x7D73, 0xBD7B, 0x7D74, 0xBD7C, 0x7D75, 0xBD7D, 0x7D76, 0xBD7E, 0x7D77, 0xF4EA, 0x7D78, 0xBD80, 0x7D79, 0xBD81, 0x7D7A, 0xBD82, + 0x7D7B, 0xBD83, 0x7D7C, 0xBD84, 0x7D7D, 0xBD85, 0x7D7E, 0xBD86, 0x7D7F, 0xBD87, 0x7D80, 0xBD88, 0x7D81, 0xBD89, 0x7D82, 0xBD8A, + 0x7D83, 0xBD8B, 0x7D84, 0xBD8C, 0x7D85, 0xBD8D, 0x7D86, 0xBD8E, 0x7D87, 0xBD8F, 0x7D88, 0xBD90, 0x7D89, 0xBD91, 0x7D8A, 0xBD92, + 0x7D8B, 0xBD93, 0x7D8C, 0xBD94, 0x7D8D, 0xBD95, 0x7D8E, 0xBD96, 0x7D8F, 0xBD97, 0x7D90, 0xBD98, 0x7D91, 0xBD99, 0x7D92, 0xBD9A, + 0x7D93, 0xBD9B, 0x7D94, 0xBD9C, 0x7D95, 0xBD9D, 0x7D96, 0xBD9E, 0x7D97, 0xBD9F, 0x7D98, 0xBDA0, 0x7D99, 0xBE40, 0x7D9A, 0xBE41, + 0x7D9B, 0xBE42, 0x7D9C, 0xBE43, 0x7D9D, 0xBE44, 0x7D9E, 0xBE45, 0x7D9F, 0xBE46, 0x7DA0, 0xBE47, 0x7DA1, 0xBE48, 0x7DA2, 0xBE49, + 0x7DA3, 0xBE4A, 0x7DA4, 0xBE4B, 0x7DA5, 0xBE4C, 0x7DA6, 0xF4EB, 0x7DA7, 0xBE4D, 0x7DA8, 0xBE4E, 0x7DA9, 0xBE4F, 0x7DAA, 0xBE50, + 0x7DAB, 0xBE51, 0x7DAC, 0xBE52, 0x7DAD, 0xBE53, 0x7DAE, 0xF4EC, 0x7DAF, 0xBE54, 0x7DB0, 0xBE55, 0x7DB1, 0xBE56, 0x7DB2, 0xBE57, + 0x7DB3, 0xBE58, 0x7DB4, 0xBE59, 0x7DB5, 0xBE5A, 0x7DB6, 0xBE5B, 0x7DB7, 0xBE5C, 0x7DB8, 0xBE5D, 0x7DB9, 0xBE5E, 0x7DBA, 0xBE5F, + 0x7DBB, 0xBE60, 0x7DBC, 0xBE61, 0x7DBD, 0xBE62, 0x7DBE, 0xBE63, 0x7DBF, 0xBE64, 0x7DC0, 0xBE65, 0x7DC1, 0xBE66, 0x7DC2, 0xBE67, + 0x7DC3, 0xBE68, 0x7DC4, 0xBE69, 0x7DC5, 0xBE6A, 0x7DC6, 0xBE6B, 0x7DC7, 0xBE6C, 0x7DC8, 0xBE6D, 0x7DC9, 0xBE6E, 0x7DCA, 0xBE6F, + 0x7DCB, 0xBE70, 0x7DCC, 0xBE71, 0x7DCD, 0xBE72, 0x7DCE, 0xBE73, 0x7DCF, 0xBE74, 0x7DD0, 0xBE75, 0x7DD1, 0xBE76, 0x7DD2, 0xBE77, + 0x7DD3, 0xBE78, 0x7DD4, 0xBE79, 0x7DD5, 0xBE7A, 0x7DD6, 0xBE7B, 0x7DD7, 0xBE7C, 0x7DD8, 0xBE7D, 0x7DD9, 0xBE7E, 0x7DDA, 0xBE80, + 0x7DDB, 0xBE81, 0x7DDC, 0xBE82, 0x7DDD, 0xBE83, 0x7DDE, 0xBE84, 0x7DDF, 0xBE85, 0x7DE0, 0xBE86, 0x7DE1, 0xBE87, 0x7DE2, 0xBE88, + 0x7DE3, 0xBE89, 0x7DE4, 0xBE8A, 0x7DE5, 0xBE8B, 0x7DE6, 0xBE8C, 0x7DE7, 0xBE8D, 0x7DE8, 0xBE8E, 0x7DE9, 0xBE8F, 0x7DEA, 0xBE90, + 0x7DEB, 0xBE91, 0x7DEC, 0xBE92, 0x7DED, 0xBE93, 0x7DEE, 0xBE94, 0x7DEF, 0xBE95, 0x7DF0, 0xBE96, 0x7DF1, 0xBE97, 0x7DF2, 0xBE98, + 0x7DF3, 0xBE99, 0x7DF4, 0xBE9A, 0x7DF5, 0xBE9B, 0x7DF6, 0xBE9C, 0x7DF7, 0xBE9D, 0x7DF8, 0xBE9E, 0x7DF9, 0xBE9F, 0x7DFA, 0xBEA0, + 0x7DFB, 0xBF40, 0x7DFC, 0xBF41, 0x7DFD, 0xBF42, 0x7DFE, 0xBF43, 0x7DFF, 0xBF44, 0x7E00, 0xBF45, 0x7E01, 0xBF46, 0x7E02, 0xBF47, + 0x7E03, 0xBF48, 0x7E04, 0xBF49, 0x7E05, 0xBF4A, 0x7E06, 0xBF4B, 0x7E07, 0xBF4C, 0x7E08, 0xBF4D, 0x7E09, 0xBF4E, 0x7E0A, 0xBF4F, + 0x7E0B, 0xBF50, 0x7E0C, 0xBF51, 0x7E0D, 0xBF52, 0x7E0E, 0xBF53, 0x7E0F, 0xBF54, 0x7E10, 0xBF55, 0x7E11, 0xBF56, 0x7E12, 0xBF57, + 0x7E13, 0xBF58, 0x7E14, 0xBF59, 0x7E15, 0xBF5A, 0x7E16, 0xBF5B, 0x7E17, 0xBF5C, 0x7E18, 0xBF5D, 0x7E19, 0xBF5E, 0x7E1A, 0xBF5F, + 0x7E1B, 0xBF60, 0x7E1C, 0xBF61, 0x7E1D, 0xBF62, 0x7E1E, 0xBF63, 0x7E1F, 0xBF64, 0x7E20, 0xBF65, 0x7E21, 0xBF66, 0x7E22, 0xBF67, + 0x7E23, 0xBF68, 0x7E24, 0xBF69, 0x7E25, 0xBF6A, 0x7E26, 0xBF6B, 0x7E27, 0xBF6C, 0x7E28, 0xBF6D, 0x7E29, 0xBF6E, 0x7E2A, 0xBF6F, + 0x7E2B, 0xBF70, 0x7E2C, 0xBF71, 0x7E2D, 0xBF72, 0x7E2E, 0xBF73, 0x7E2F, 0xBF74, 0x7E30, 0xBF75, 0x7E31, 0xBF76, 0x7E32, 0xBF77, + 0x7E33, 0xBF78, 0x7E34, 0xBF79, 0x7E35, 0xBF7A, 0x7E36, 0xBF7B, 0x7E37, 0xBF7C, 0x7E38, 0xBF7D, 0x7E39, 0xBF7E, 0x7E3A, 0xBF80, + 0x7E3B, 0xF7E3, 0x7E3C, 0xBF81, 0x7E3D, 0xBF82, 0x7E3E, 0xBF83, 0x7E3F, 0xBF84, 0x7E40, 0xBF85, 0x7E41, 0xB7B1, 0x7E42, 0xBF86, + 0x7E43, 0xBF87, 0x7E44, 0xBF88, 0x7E45, 0xBF89, 0x7E46, 0xBF8A, 0x7E47, 0xF4ED, 0x7E48, 0xBF8B, 0x7E49, 0xBF8C, 0x7E4A, 0xBF8D, + 0x7E4B, 0xBF8E, 0x7E4C, 0xBF8F, 0x7E4D, 0xBF90, 0x7E4E, 0xBF91, 0x7E4F, 0xBF92, 0x7E50, 0xBF93, 0x7E51, 0xBF94, 0x7E52, 0xBF95, + 0x7E53, 0xBF96, 0x7E54, 0xBF97, 0x7E55, 0xBF98, 0x7E56, 0xBF99, 0x7E57, 0xBF9A, 0x7E58, 0xBF9B, 0x7E59, 0xBF9C, 0x7E5A, 0xBF9D, + 0x7E5B, 0xBF9E, 0x7E5C, 0xBF9F, 0x7E5D, 0xBFA0, 0x7E5E, 0xC040, 0x7E5F, 0xC041, 0x7E60, 0xC042, 0x7E61, 0xC043, 0x7E62, 0xC044, + 0x7E63, 0xC045, 0x7E64, 0xC046, 0x7E65, 0xC047, 0x7E66, 0xC048, 0x7E67, 0xC049, 0x7E68, 0xC04A, 0x7E69, 0xC04B, 0x7E6A, 0xC04C, + 0x7E6B, 0xC04D, 0x7E6C, 0xC04E, 0x7E6D, 0xC04F, 0x7E6E, 0xC050, 0x7E6F, 0xC051, 0x7E70, 0xC052, 0x7E71, 0xC053, 0x7E72, 0xC054, + 0x7E73, 0xC055, 0x7E74, 0xC056, 0x7E75, 0xC057, 0x7E76, 0xC058, 0x7E77, 0xC059, 0x7E78, 0xC05A, 0x7E79, 0xC05B, 0x7E7A, 0xC05C, + 0x7E7B, 0xC05D, 0x7E7C, 0xC05E, 0x7E7D, 0xC05F, 0x7E7E, 0xC060, 0x7E7F, 0xC061, 0x7E80, 0xC062, 0x7E81, 0xC063, 0x7E82, 0xD7EB, + 0x7E83, 0xC064, 0x7E84, 0xC065, 0x7E85, 0xC066, 0x7E86, 0xC067, 0x7E87, 0xC068, 0x7E88, 0xC069, 0x7E89, 0xC06A, 0x7E8A, 0xC06B, + 0x7E8B, 0xC06C, 0x7E8C, 0xC06D, 0x7E8D, 0xC06E, 0x7E8E, 0xC06F, 0x7E8F, 0xC070, 0x7E90, 0xC071, 0x7E91, 0xC072, 0x7E92, 0xC073, + 0x7E93, 0xC074, 0x7E94, 0xC075, 0x7E95, 0xC076, 0x7E96, 0xC077, 0x7E97, 0xC078, 0x7E98, 0xC079, 0x7E99, 0xC07A, 0x7E9A, 0xC07B, + 0x7E9B, 0xF4EE, 0x7E9C, 0xC07C, 0x7E9D, 0xC07D, 0x7E9E, 0xC07E, 0x7E9F, 0xE6F9, 0x7EA0, 0xBEC0, 0x7EA1, 0xE6FA, 0x7EA2, 0xBAEC, + 0x7EA3, 0xE6FB, 0x7EA4, 0xCFCB, 0x7EA5, 0xE6FC, 0x7EA6, 0xD4BC, 0x7EA7, 0xBCB6, 0x7EA8, 0xE6FD, 0x7EA9, 0xE6FE, 0x7EAA, 0xBCCD, + 0x7EAB, 0xC8D2, 0x7EAC, 0xCEB3, 0x7EAD, 0xE7A1, 0x7EAE, 0xC080, 0x7EAF, 0xB4BF, 0x7EB0, 0xE7A2, 0x7EB1, 0xC9B4, 0x7EB2, 0xB8D9, + 0x7EB3, 0xC4C9, 0x7EB4, 0xC081, 0x7EB5, 0xD7DD, 0x7EB6, 0xC2DA, 0x7EB7, 0xB7D7, 0x7EB8, 0xD6BD, 0x7EB9, 0xCEC6, 0x7EBA, 0xB7C4, + 0x7EBB, 0xC082, 0x7EBC, 0xC083, 0x7EBD, 0xC5A6, 0x7EBE, 0xE7A3, 0x7EBF, 0xCFDF, 0x7EC0, 0xE7A4, 0x7EC1, 0xE7A5, 0x7EC2, 0xE7A6, + 0x7EC3, 0xC1B7, 0x7EC4, 0xD7E9, 0x7EC5, 0xC9F0, 0x7EC6, 0xCFB8, 0x7EC7, 0xD6AF, 0x7EC8, 0xD6D5, 0x7EC9, 0xE7A7, 0x7ECA, 0xB0ED, + 0x7ECB, 0xE7A8, 0x7ECC, 0xE7A9, 0x7ECD, 0xC9DC, 0x7ECE, 0xD2EF, 0x7ECF, 0xBEAD, 0x7ED0, 0xE7AA, 0x7ED1, 0xB0F3, 0x7ED2, 0xC8DE, + 0x7ED3, 0xBDE1, 0x7ED4, 0xE7AB, 0x7ED5, 0xC8C6, 0x7ED6, 0xC084, 0x7ED7, 0xE7AC, 0x7ED8, 0xBBE6, 0x7ED9, 0xB8F8, 0x7EDA, 0xD1A4, + 0x7EDB, 0xE7AD, 0x7EDC, 0xC2E7, 0x7EDD, 0xBEF8, 0x7EDE, 0xBDCA, 0x7EDF, 0xCDB3, 0x7EE0, 0xE7AE, 0x7EE1, 0xE7AF, 0x7EE2, 0xBEEE, + 0x7EE3, 0xD0E5, 0x7EE4, 0xC085, 0x7EE5, 0xCBE7, 0x7EE6, 0xCCD0, 0x7EE7, 0xBCCC, 0x7EE8, 0xE7B0, 0x7EE9, 0xBCA8, 0x7EEA, 0xD0F7, + 0x7EEB, 0xE7B1, 0x7EEC, 0xC086, 0x7EED, 0xD0F8, 0x7EEE, 0xE7B2, 0x7EEF, 0xE7B3, 0x7EF0, 0xB4C2, 0x7EF1, 0xE7B4, 0x7EF2, 0xE7B5, + 0x7EF3, 0xC9FE, 0x7EF4, 0xCEAC, 0x7EF5, 0xC3E0, 0x7EF6, 0xE7B7, 0x7EF7, 0xB1C1, 0x7EF8, 0xB3F1, 0x7EF9, 0xC087, 0x7EFA, 0xE7B8, + 0x7EFB, 0xE7B9, 0x7EFC, 0xD7DB, 0x7EFD, 0xD5C0, 0x7EFE, 0xE7BA, 0x7EFF, 0xC2CC, 0x7F00, 0xD7BA, 0x7F01, 0xE7BB, 0x7F02, 0xE7BC, + 0x7F03, 0xE7BD, 0x7F04, 0xBCEA, 0x7F05, 0xC3E5, 0x7F06, 0xC0C2, 0x7F07, 0xE7BE, 0x7F08, 0xE7BF, 0x7F09, 0xBCA9, 0x7F0A, 0xC088, + 0x7F0B, 0xE7C0, 0x7F0C, 0xE7C1, 0x7F0D, 0xE7B6, 0x7F0E, 0xB6D0, 0x7F0F, 0xE7C2, 0x7F10, 0xC089, 0x7F11, 0xE7C3, 0x7F12, 0xE7C4, + 0x7F13, 0xBBBA, 0x7F14, 0xB5DE, 0x7F15, 0xC2C6, 0x7F16, 0xB1E0, 0x7F17, 0xE7C5, 0x7F18, 0xD4B5, 0x7F19, 0xE7C6, 0x7F1A, 0xB8BF, + 0x7F1B, 0xE7C8, 0x7F1C, 0xE7C7, 0x7F1D, 0xB7EC, 0x7F1E, 0xC08A, 0x7F1F, 0xE7C9, 0x7F20, 0xB2F8, 0x7F21, 0xE7CA, 0x7F22, 0xE7CB, + 0x7F23, 0xE7CC, 0x7F24, 0xE7CD, 0x7F25, 0xE7CE, 0x7F26, 0xE7CF, 0x7F27, 0xE7D0, 0x7F28, 0xD3A7, 0x7F29, 0xCBF5, 0x7F2A, 0xE7D1, + 0x7F2B, 0xE7D2, 0x7F2C, 0xE7D3, 0x7F2D, 0xE7D4, 0x7F2E, 0xC9C9, 0x7F2F, 0xE7D5, 0x7F30, 0xE7D6, 0x7F31, 0xE7D7, 0x7F32, 0xE7D8, + 0x7F33, 0xE7D9, 0x7F34, 0xBDC9, 0x7F35, 0xE7DA, 0x7F36, 0xF3BE, 0x7F37, 0xC08B, 0x7F38, 0xB8D7, 0x7F39, 0xC08C, 0x7F3A, 0xC8B1, + 0x7F3B, 0xC08D, 0x7F3C, 0xC08E, 0x7F3D, 0xC08F, 0x7F3E, 0xC090, 0x7F3F, 0xC091, 0x7F40, 0xC092, 0x7F41, 0xC093, 0x7F42, 0xF3BF, + 0x7F43, 0xC094, 0x7F44, 0xF3C0, 0x7F45, 0xF3C1, 0x7F46, 0xC095, 0x7F47, 0xC096, 0x7F48, 0xC097, 0x7F49, 0xC098, 0x7F4A, 0xC099, + 0x7F4B, 0xC09A, 0x7F4C, 0xC09B, 0x7F4D, 0xC09C, 0x7F4E, 0xC09D, 0x7F4F, 0xC09E, 0x7F50, 0xB9DE, 0x7F51, 0xCDF8, 0x7F52, 0xC09F, + 0x7F53, 0xC0A0, 0x7F54, 0xD8E8, 0x7F55, 0xBAB1, 0x7F56, 0xC140, 0x7F57, 0xC2DE, 0x7F58, 0xEEB7, 0x7F59, 0xC141, 0x7F5A, 0xB7A3, + 0x7F5B, 0xC142, 0x7F5C, 0xC143, 0x7F5D, 0xC144, 0x7F5E, 0xC145, 0x7F5F, 0xEEB9, 0x7F60, 0xC146, 0x7F61, 0xEEB8, 0x7F62, 0xB0D5, + 0x7F63, 0xC147, 0x7F64, 0xC148, 0x7F65, 0xC149, 0x7F66, 0xC14A, 0x7F67, 0xC14B, 0x7F68, 0xEEBB, 0x7F69, 0xD5D6, 0x7F6A, 0xD7EF, + 0x7F6B, 0xC14C, 0x7F6C, 0xC14D, 0x7F6D, 0xC14E, 0x7F6E, 0xD6C3, 0x7F6F, 0xC14F, 0x7F70, 0xC150, 0x7F71, 0xEEBD, 0x7F72, 0xCAF0, + 0x7F73, 0xC151, 0x7F74, 0xEEBC, 0x7F75, 0xC152, 0x7F76, 0xC153, 0x7F77, 0xC154, 0x7F78, 0xC155, 0x7F79, 0xEEBE, 0x7F7A, 0xC156, + 0x7F7B, 0xC157, 0x7F7C, 0xC158, 0x7F7D, 0xC159, 0x7F7E, 0xEEC0, 0x7F7F, 0xC15A, 0x7F80, 0xC15B, 0x7F81, 0xEEBF, 0x7F82, 0xC15C, + 0x7F83, 0xC15D, 0x7F84, 0xC15E, 0x7F85, 0xC15F, 0x7F86, 0xC160, 0x7F87, 0xC161, 0x7F88, 0xC162, 0x7F89, 0xC163, 0x7F8A, 0xD1F2, + 0x7F8B, 0xC164, 0x7F8C, 0xC7BC, 0x7F8D, 0xC165, 0x7F8E, 0xC3C0, 0x7F8F, 0xC166, 0x7F90, 0xC167, 0x7F91, 0xC168, 0x7F92, 0xC169, + 0x7F93, 0xC16A, 0x7F94, 0xB8E1, 0x7F95, 0xC16B, 0x7F96, 0xC16C, 0x7F97, 0xC16D, 0x7F98, 0xC16E, 0x7F99, 0xC16F, 0x7F9A, 0xC1E7, + 0x7F9B, 0xC170, 0x7F9C, 0xC171, 0x7F9D, 0xF4C6, 0x7F9E, 0xD0DF, 0x7F9F, 0xF4C7, 0x7FA0, 0xC172, 0x7FA1, 0xCFDB, 0x7FA2, 0xC173, + 0x7FA3, 0xC174, 0x7FA4, 0xC8BA, 0x7FA5, 0xC175, 0x7FA6, 0xC176, 0x7FA7, 0xF4C8, 0x7FA8, 0xC177, 0x7FA9, 0xC178, 0x7FAA, 0xC179, + 0x7FAB, 0xC17A, 0x7FAC, 0xC17B, 0x7FAD, 0xC17C, 0x7FAE, 0xC17D, 0x7FAF, 0xF4C9, 0x7FB0, 0xF4CA, 0x7FB1, 0xC17E, 0x7FB2, 0xF4CB, + 0x7FB3, 0xC180, 0x7FB4, 0xC181, 0x7FB5, 0xC182, 0x7FB6, 0xC183, 0x7FB7, 0xC184, 0x7FB8, 0xD9FA, 0x7FB9, 0xB8FE, 0x7FBA, 0xC185, + 0x7FBB, 0xC186, 0x7FBC, 0xE5F1, 0x7FBD, 0xD3F0, 0x7FBE, 0xC187, 0x7FBF, 0xF4E0, 0x7FC0, 0xC188, 0x7FC1, 0xCECC, 0x7FC2, 0xC189, + 0x7FC3, 0xC18A, 0x7FC4, 0xC18B, 0x7FC5, 0xB3E1, 0x7FC6, 0xC18C, 0x7FC7, 0xC18D, 0x7FC8, 0xC18E, 0x7FC9, 0xC18F, 0x7FCA, 0xF1B4, + 0x7FCB, 0xC190, 0x7FCC, 0xD2EE, 0x7FCD, 0xC191, 0x7FCE, 0xF4E1, 0x7FCF, 0xC192, 0x7FD0, 0xC193, 0x7FD1, 0xC194, 0x7FD2, 0xC195, + 0x7FD3, 0xC196, 0x7FD4, 0xCFE8, 0x7FD5, 0xF4E2, 0x7FD6, 0xC197, 0x7FD7, 0xC198, 0x7FD8, 0xC7CC, 0x7FD9, 0xC199, 0x7FDA, 0xC19A, + 0x7FDB, 0xC19B, 0x7FDC, 0xC19C, 0x7FDD, 0xC19D, 0x7FDE, 0xC19E, 0x7FDF, 0xB5D4, 0x7FE0, 0xB4E4, 0x7FE1, 0xF4E4, 0x7FE2, 0xC19F, + 0x7FE3, 0xC1A0, 0x7FE4, 0xC240, 0x7FE5, 0xF4E3, 0x7FE6, 0xF4E5, 0x7FE7, 0xC241, 0x7FE8, 0xC242, 0x7FE9, 0xF4E6, 0x7FEA, 0xC243, + 0x7FEB, 0xC244, 0x7FEC, 0xC245, 0x7FED, 0xC246, 0x7FEE, 0xF4E7, 0x7FEF, 0xC247, 0x7FF0, 0xBAB2, 0x7FF1, 0xB0BF, 0x7FF2, 0xC248, + 0x7FF3, 0xF4E8, 0x7FF4, 0xC249, 0x7FF5, 0xC24A, 0x7FF6, 0xC24B, 0x7FF7, 0xC24C, 0x7FF8, 0xC24D, 0x7FF9, 0xC24E, 0x7FFA, 0xC24F, + 0x7FFB, 0xB7AD, 0x7FFC, 0xD2ED, 0x7FFD, 0xC250, 0x7FFE, 0xC251, 0x7FFF, 0xC252, 0x8000, 0xD2AB, 0x8001, 0xC0CF, 0x8002, 0xC253, + 0x8003, 0xBFBC, 0x8004, 0xEBA3, 0x8005, 0xD5DF, 0x8006, 0xEAC8, 0x8007, 0xC254, 0x8008, 0xC255, 0x8009, 0xC256, 0x800A, 0xC257, + 0x800B, 0xF1F3, 0x800C, 0xB6F8, 0x800D, 0xCBA3, 0x800E, 0xC258, 0x800F, 0xC259, 0x8010, 0xC4CD, 0x8011, 0xC25A, 0x8012, 0xF1E7, + 0x8013, 0xC25B, 0x8014, 0xF1E8, 0x8015, 0xB8FB, 0x8016, 0xF1E9, 0x8017, 0xBAC4, 0x8018, 0xD4C5, 0x8019, 0xB0D2, 0x801A, 0xC25C, + 0x801B, 0xC25D, 0x801C, 0xF1EA, 0x801D, 0xC25E, 0x801E, 0xC25F, 0x801F, 0xC260, 0x8020, 0xF1EB, 0x8021, 0xC261, 0x8022, 0xF1EC, + 0x8023, 0xC262, 0x8024, 0xC263, 0x8025, 0xF1ED, 0x8026, 0xF1EE, 0x8027, 0xF1EF, 0x8028, 0xF1F1, 0x8029, 0xF1F0, 0x802A, 0xC5D5, + 0x802B, 0xC264, 0x802C, 0xC265, 0x802D, 0xC266, 0x802E, 0xC267, 0x802F, 0xC268, 0x8030, 0xC269, 0x8031, 0xF1F2, 0x8032, 0xC26A, + 0x8033, 0xB6FA, 0x8034, 0xC26B, 0x8035, 0xF1F4, 0x8036, 0xD2AE, 0x8037, 0xDEC7, 0x8038, 0xCBCA, 0x8039, 0xC26C, 0x803A, 0xC26D, + 0x803B, 0xB3DC, 0x803C, 0xC26E, 0x803D, 0xB5A2, 0x803E, 0xC26F, 0x803F, 0xB9A2, 0x8040, 0xC270, 0x8041, 0xC271, 0x8042, 0xC4F4, + 0x8043, 0xF1F5, 0x8044, 0xC272, 0x8045, 0xC273, 0x8046, 0xF1F6, 0x8047, 0xC274, 0x8048, 0xC275, 0x8049, 0xC276, 0x804A, 0xC1C4, + 0x804B, 0xC1FB, 0x804C, 0xD6B0, 0x804D, 0xF1F7, 0x804E, 0xC277, 0x804F, 0xC278, 0x8050, 0xC279, 0x8051, 0xC27A, 0x8052, 0xF1F8, + 0x8053, 0xC27B, 0x8054, 0xC1AA, 0x8055, 0xC27C, 0x8056, 0xC27D, 0x8057, 0xC27E, 0x8058, 0xC6B8, 0x8059, 0xC280, 0x805A, 0xBEDB, + 0x805B, 0xC281, 0x805C, 0xC282, 0x805D, 0xC283, 0x805E, 0xC284, 0x805F, 0xC285, 0x8060, 0xC286, 0x8061, 0xC287, 0x8062, 0xC288, + 0x8063, 0xC289, 0x8064, 0xC28A, 0x8065, 0xC28B, 0x8066, 0xC28C, 0x8067, 0xC28D, 0x8068, 0xC28E, 0x8069, 0xF1F9, 0x806A, 0xB4CF, + 0x806B, 0xC28F, 0x806C, 0xC290, 0x806D, 0xC291, 0x806E, 0xC292, 0x806F, 0xC293, 0x8070, 0xC294, 0x8071, 0xF1FA, 0x8072, 0xC295, + 0x8073, 0xC296, 0x8074, 0xC297, 0x8075, 0xC298, 0x8076, 0xC299, 0x8077, 0xC29A, 0x8078, 0xC29B, 0x8079, 0xC29C, 0x807A, 0xC29D, + 0x807B, 0xC29E, 0x807C, 0xC29F, 0x807D, 0xC2A0, 0x807E, 0xC340, 0x807F, 0xEDB2, 0x8080, 0xEDB1, 0x8081, 0xC341, 0x8082, 0xC342, + 0x8083, 0xCBE0, 0x8084, 0xD2DE, 0x8085, 0xC343, 0x8086, 0xCBC1, 0x8087, 0xD5D8, 0x8088, 0xC344, 0x8089, 0xC8E2, 0x808A, 0xC345, + 0x808B, 0xC0DF, 0x808C, 0xBCA1, 0x808D, 0xC346, 0x808E, 0xC347, 0x808F, 0xC348, 0x8090, 0xC349, 0x8091, 0xC34A, 0x8092, 0xC34B, + 0x8093, 0xEBC1, 0x8094, 0xC34C, 0x8095, 0xC34D, 0x8096, 0xD0A4, 0x8097, 0xC34E, 0x8098, 0xD6E2, 0x8099, 0xC34F, 0x809A, 0xB6C7, + 0x809B, 0xB8D8, 0x809C, 0xEBC0, 0x809D, 0xB8CE, 0x809E, 0xC350, 0x809F, 0xEBBF, 0x80A0, 0xB3A6, 0x80A1, 0xB9C9, 0x80A2, 0xD6AB, + 0x80A3, 0xC351, 0x80A4, 0xB7F4, 0x80A5, 0xB7CA, 0x80A6, 0xC352, 0x80A7, 0xC353, 0x80A8, 0xC354, 0x80A9, 0xBCE7, 0x80AA, 0xB7BE, + 0x80AB, 0xEBC6, 0x80AC, 0xC355, 0x80AD, 0xEBC7, 0x80AE, 0xB0B9, 0x80AF, 0xBFCF, 0x80B0, 0xC356, 0x80B1, 0xEBC5, 0x80B2, 0xD3FD, + 0x80B3, 0xC357, 0x80B4, 0xEBC8, 0x80B5, 0xC358, 0x80B6, 0xC359, 0x80B7, 0xEBC9, 0x80B8, 0xC35A, 0x80B9, 0xC35B, 0x80BA, 0xB7CE, + 0x80BB, 0xC35C, 0x80BC, 0xEBC2, 0x80BD, 0xEBC4, 0x80BE, 0xC9F6, 0x80BF, 0xD6D7, 0x80C0, 0xD5CD, 0x80C1, 0xD0B2, 0x80C2, 0xEBCF, + 0x80C3, 0xCEB8, 0x80C4, 0xEBD0, 0x80C5, 0xC35D, 0x80C6, 0xB5A8, 0x80C7, 0xC35E, 0x80C8, 0xC35F, 0x80C9, 0xC360, 0x80CA, 0xC361, + 0x80CB, 0xC362, 0x80CC, 0xB1B3, 0x80CD, 0xEBD2, 0x80CE, 0xCCA5, 0x80CF, 0xC363, 0x80D0, 0xC364, 0x80D1, 0xC365, 0x80D2, 0xC366, + 0x80D3, 0xC367, 0x80D4, 0xC368, 0x80D5, 0xC369, 0x80D6, 0xC5D6, 0x80D7, 0xEBD3, 0x80D8, 0xC36A, 0x80D9, 0xEBD1, 0x80DA, 0xC5DF, + 0x80DB, 0xEBCE, 0x80DC, 0xCAA4, 0x80DD, 0xEBD5, 0x80DE, 0xB0FB, 0x80DF, 0xC36B, 0x80E0, 0xC36C, 0x80E1, 0xBAFA, 0x80E2, 0xC36D, + 0x80E3, 0xC36E, 0x80E4, 0xD8B7, 0x80E5, 0xF1E3, 0x80E6, 0xC36F, 0x80E7, 0xEBCA, 0x80E8, 0xEBCB, 0x80E9, 0xEBCC, 0x80EA, 0xEBCD, + 0x80EB, 0xEBD6, 0x80EC, 0xE6C0, 0x80ED, 0xEBD9, 0x80EE, 0xC370, 0x80EF, 0xBFE8, 0x80F0, 0xD2C8, 0x80F1, 0xEBD7, 0x80F2, 0xEBDC, + 0x80F3, 0xB8EC, 0x80F4, 0xEBD8, 0x80F5, 0xC371, 0x80F6, 0xBDBA, 0x80F7, 0xC372, 0x80F8, 0xD0D8, 0x80F9, 0xC373, 0x80FA, 0xB0B7, + 0x80FB, 0xC374, 0x80FC, 0xEBDD, 0x80FD, 0xC4DC, 0x80FE, 0xC375, 0x80FF, 0xC376, 0x8100, 0xC377, 0x8101, 0xC378, 0x8102, 0xD6AC, + 0x8103, 0xC379, 0x8104, 0xC37A, 0x8105, 0xC37B, 0x8106, 0xB4E0, 0x8107, 0xC37C, 0x8108, 0xC37D, 0x8109, 0xC2F6, 0x810A, 0xBCB9, + 0x810B, 0xC37E, 0x810C, 0xC380, 0x810D, 0xEBDA, 0x810E, 0xEBDB, 0x810F, 0xD4E0, 0x8110, 0xC6EA, 0x8111, 0xC4D4, 0x8112, 0xEBDF, + 0x8113, 0xC5A7, 0x8114, 0xD9F5, 0x8115, 0xC381, 0x8116, 0xB2B1, 0x8117, 0xC382, 0x8118, 0xEBE4, 0x8119, 0xC383, 0x811A, 0xBDC5, + 0x811B, 0xC384, 0x811C, 0xC385, 0x811D, 0xC386, 0x811E, 0xEBE2, 0x811F, 0xC387, 0x8120, 0xC388, 0x8121, 0xC389, 0x8122, 0xC38A, + 0x8123, 0xC38B, 0x8124, 0xC38C, 0x8125, 0xC38D, 0x8126, 0xC38E, 0x8127, 0xC38F, 0x8128, 0xC390, 0x8129, 0xC391, 0x812A, 0xC392, + 0x812B, 0xC393, 0x812C, 0xEBE3, 0x812D, 0xC394, 0x812E, 0xC395, 0x812F, 0xB8AC, 0x8130, 0xC396, 0x8131, 0xCDD1, 0x8132, 0xEBE5, + 0x8133, 0xC397, 0x8134, 0xC398, 0x8135, 0xC399, 0x8136, 0xEBE1, 0x8137, 0xC39A, 0x8138, 0xC1B3, 0x8139, 0xC39B, 0x813A, 0xC39C, + 0x813B, 0xC39D, 0x813C, 0xC39E, 0x813D, 0xC39F, 0x813E, 0xC6A2, 0x813F, 0xC3A0, 0x8140, 0xC440, 0x8141, 0xC441, 0x8142, 0xC442, + 0x8143, 0xC443, 0x8144, 0xC444, 0x8145, 0xC445, 0x8146, 0xCCF3, 0x8147, 0xC446, 0x8148, 0xEBE6, 0x8149, 0xC447, 0x814A, 0xC0B0, + 0x814B, 0xD2B8, 0x814C, 0xEBE7, 0x814D, 0xC448, 0x814E, 0xC449, 0x814F, 0xC44A, 0x8150, 0xB8AF, 0x8151, 0xB8AD, 0x8152, 0xC44B, + 0x8153, 0xEBE8, 0x8154, 0xC7BB, 0x8155, 0xCDF3, 0x8156, 0xC44C, 0x8157, 0xC44D, 0x8158, 0xC44E, 0x8159, 0xEBEA, 0x815A, 0xEBEB, + 0x815B, 0xC44F, 0x815C, 0xC450, 0x815D, 0xC451, 0x815E, 0xC452, 0x815F, 0xC453, 0x8160, 0xEBED, 0x8161, 0xC454, 0x8162, 0xC455, + 0x8163, 0xC456, 0x8164, 0xC457, 0x8165, 0xD0C8, 0x8166, 0xC458, 0x8167, 0xEBF2, 0x8168, 0xC459, 0x8169, 0xEBEE, 0x816A, 0xC45A, + 0x816B, 0xC45B, 0x816C, 0xC45C, 0x816D, 0xEBF1, 0x816E, 0xC8F9, 0x816F, 0xC45D, 0x8170, 0xD1FC, 0x8171, 0xEBEC, 0x8172, 0xC45E, + 0x8173, 0xC45F, 0x8174, 0xEBE9, 0x8175, 0xC460, 0x8176, 0xC461, 0x8177, 0xC462, 0x8178, 0xC463, 0x8179, 0xB8B9, 0x817A, 0xCFD9, + 0x817B, 0xC4E5, 0x817C, 0xEBEF, 0x817D, 0xEBF0, 0x817E, 0xCCDA, 0x817F, 0xCDC8, 0x8180, 0xB0F2, 0x8181, 0xC464, 0x8182, 0xEBF6, + 0x8183, 0xC465, 0x8184, 0xC466, 0x8185, 0xC467, 0x8186, 0xC468, 0x8187, 0xC469, 0x8188, 0xEBF5, 0x8189, 0xC46A, 0x818A, 0xB2B2, + 0x818B, 0xC46B, 0x818C, 0xC46C, 0x818D, 0xC46D, 0x818E, 0xC46E, 0x818F, 0xB8E0, 0x8190, 0xC46F, 0x8191, 0xEBF7, 0x8192, 0xC470, + 0x8193, 0xC471, 0x8194, 0xC472, 0x8195, 0xC473, 0x8196, 0xC474, 0x8197, 0xC475, 0x8198, 0xB1EC, 0x8199, 0xC476, 0x819A, 0xC477, + 0x819B, 0xCCC5, 0x819C, 0xC4A4, 0x819D, 0xCFA5, 0x819E, 0xC478, 0x819F, 0xC479, 0x81A0, 0xC47A, 0x81A1, 0xC47B, 0x81A2, 0xC47C, + 0x81A3, 0xEBF9, 0x81A4, 0xC47D, 0x81A5, 0xC47E, 0x81A6, 0xECA2, 0x81A7, 0xC480, 0x81A8, 0xC5F2, 0x81A9, 0xC481, 0x81AA, 0xEBFA, + 0x81AB, 0xC482, 0x81AC, 0xC483, 0x81AD, 0xC484, 0x81AE, 0xC485, 0x81AF, 0xC486, 0x81B0, 0xC487, 0x81B1, 0xC488, 0x81B2, 0xC489, + 0x81B3, 0xC9C5, 0x81B4, 0xC48A, 0x81B5, 0xC48B, 0x81B6, 0xC48C, 0x81B7, 0xC48D, 0x81B8, 0xC48E, 0x81B9, 0xC48F, 0x81BA, 0xE2DF, + 0x81BB, 0xEBFE, 0x81BC, 0xC490, 0x81BD, 0xC491, 0x81BE, 0xC492, 0x81BF, 0xC493, 0x81C0, 0xCDCE, 0x81C1, 0xECA1, 0x81C2, 0xB1DB, + 0x81C3, 0xD3B7, 0x81C4, 0xC494, 0x81C5, 0xC495, 0x81C6, 0xD2DC, 0x81C7, 0xC496, 0x81C8, 0xC497, 0x81C9, 0xC498, 0x81CA, 0xEBFD, + 0x81CB, 0xC499, 0x81CC, 0xEBFB, 0x81CD, 0xC49A, 0x81CE, 0xC49B, 0x81CF, 0xC49C, 0x81D0, 0xC49D, 0x81D1, 0xC49E, 0x81D2, 0xC49F, + 0x81D3, 0xC4A0, 0x81D4, 0xC540, 0x81D5, 0xC541, 0x81D6, 0xC542, 0x81D7, 0xC543, 0x81D8, 0xC544, 0x81D9, 0xC545, 0x81DA, 0xC546, + 0x81DB, 0xC547, 0x81DC, 0xC548, 0x81DD, 0xC549, 0x81DE, 0xC54A, 0x81DF, 0xC54B, 0x81E0, 0xC54C, 0x81E1, 0xC54D, 0x81E2, 0xC54E, + 0x81E3, 0xB3BC, 0x81E4, 0xC54F, 0x81E5, 0xC550, 0x81E6, 0xC551, 0x81E7, 0xEAB0, 0x81E8, 0xC552, 0x81E9, 0xC553, 0x81EA, 0xD7D4, + 0x81EB, 0xC554, 0x81EC, 0xF4AB, 0x81ED, 0xB3F4, 0x81EE, 0xC555, 0x81EF, 0xC556, 0x81F0, 0xC557, 0x81F1, 0xC558, 0x81F2, 0xC559, + 0x81F3, 0xD6C1, 0x81F4, 0xD6C2, 0x81F5, 0xC55A, 0x81F6, 0xC55B, 0x81F7, 0xC55C, 0x81F8, 0xC55D, 0x81F9, 0xC55E, 0x81FA, 0xC55F, + 0x81FB, 0xD5E9, 0x81FC, 0xBECA, 0x81FD, 0xC560, 0x81FE, 0xF4A7, 0x81FF, 0xC561, 0x8200, 0xD2A8, 0x8201, 0xF4A8, 0x8202, 0xF4A9, + 0x8203, 0xC562, 0x8204, 0xF4AA, 0x8205, 0xBECB, 0x8206, 0xD3DF, 0x8207, 0xC563, 0x8208, 0xC564, 0x8209, 0xC565, 0x820A, 0xC566, + 0x820B, 0xC567, 0x820C, 0xC9E0, 0x820D, 0xC9E1, 0x820E, 0xC568, 0x820F, 0xC569, 0x8210, 0xF3C2, 0x8211, 0xC56A, 0x8212, 0xCAE6, + 0x8213, 0xC56B, 0x8214, 0xCCF2, 0x8215, 0xC56C, 0x8216, 0xC56D, 0x8217, 0xC56E, 0x8218, 0xC56F, 0x8219, 0xC570, 0x821A, 0xC571, + 0x821B, 0xE2B6, 0x821C, 0xCBB4, 0x821D, 0xC572, 0x821E, 0xCEE8, 0x821F, 0xD6DB, 0x8220, 0xC573, 0x8221, 0xF4AD, 0x8222, 0xF4AE, + 0x8223, 0xF4AF, 0x8224, 0xC574, 0x8225, 0xC575, 0x8226, 0xC576, 0x8227, 0xC577, 0x8228, 0xF4B2, 0x8229, 0xC578, 0x822A, 0xBABD, + 0x822B, 0xF4B3, 0x822C, 0xB0E3, 0x822D, 0xF4B0, 0x822E, 0xC579, 0x822F, 0xF4B1, 0x8230, 0xBDA2, 0x8231, 0xB2D5, 0x8232, 0xC57A, + 0x8233, 0xF4B6, 0x8234, 0xF4B7, 0x8235, 0xB6E6, 0x8236, 0xB2B0, 0x8237, 0xCFCF, 0x8238, 0xF4B4, 0x8239, 0xB4AC, 0x823A, 0xC57B, + 0x823B, 0xF4B5, 0x823C, 0xC57C, 0x823D, 0xC57D, 0x823E, 0xF4B8, 0x823F, 0xC57E, 0x8240, 0xC580, 0x8241, 0xC581, 0x8242, 0xC582, + 0x8243, 0xC583, 0x8244, 0xF4B9, 0x8245, 0xC584, 0x8246, 0xC585, 0x8247, 0xCDA7, 0x8248, 0xC586, 0x8249, 0xF4BA, 0x824A, 0xC587, + 0x824B, 0xF4BB, 0x824C, 0xC588, 0x824D, 0xC589, 0x824E, 0xC58A, 0x824F, 0xF4BC, 0x8250, 0xC58B, 0x8251, 0xC58C, 0x8252, 0xC58D, + 0x8253, 0xC58E, 0x8254, 0xC58F, 0x8255, 0xC590, 0x8256, 0xC591, 0x8257, 0xC592, 0x8258, 0xCBD2, 0x8259, 0xC593, 0x825A, 0xF4BD, + 0x825B, 0xC594, 0x825C, 0xC595, 0x825D, 0xC596, 0x825E, 0xC597, 0x825F, 0xF4BE, 0x8260, 0xC598, 0x8261, 0xC599, 0x8262, 0xC59A, + 0x8263, 0xC59B, 0x8264, 0xC59C, 0x8265, 0xC59D, 0x8266, 0xC59E, 0x8267, 0xC59F, 0x8268, 0xF4BF, 0x8269, 0xC5A0, 0x826A, 0xC640, + 0x826B, 0xC641, 0x826C, 0xC642, 0x826D, 0xC643, 0x826E, 0xF4DE, 0x826F, 0xC1BC, 0x8270, 0xBCE8, 0x8271, 0xC644, 0x8272, 0xC9AB, + 0x8273, 0xD1DE, 0x8274, 0xE5F5, 0x8275, 0xC645, 0x8276, 0xC646, 0x8277, 0xC647, 0x8278, 0xC648, 0x8279, 0xDCB3, 0x827A, 0xD2D5, + 0x827B, 0xC649, 0x827C, 0xC64A, 0x827D, 0xDCB4, 0x827E, 0xB0AC, 0x827F, 0xDCB5, 0x8280, 0xC64B, 0x8281, 0xC64C, 0x8282, 0xBDDA, + 0x8283, 0xC64D, 0x8284, 0xDCB9, 0x8285, 0xC64E, 0x8286, 0xC64F, 0x8287, 0xC650, 0x8288, 0xD8C2, 0x8289, 0xC651, 0x828A, 0xDCB7, + 0x828B, 0xD3F3, 0x828C, 0xC652, 0x828D, 0xC9D6, 0x828E, 0xDCBA, 0x828F, 0xDCB6, 0x8290, 0xC653, 0x8291, 0xDCBB, 0x8292, 0xC3A2, + 0x8293, 0xC654, 0x8294, 0xC655, 0x8295, 0xC656, 0x8296, 0xC657, 0x8297, 0xDCBC, 0x8298, 0xDCC5, 0x8299, 0xDCBD, 0x829A, 0xC658, + 0x829B, 0xC659, 0x829C, 0xCEDF, 0x829D, 0xD6A5, 0x829E, 0xC65A, 0x829F, 0xDCCF, 0x82A0, 0xC65B, 0x82A1, 0xDCCD, 0x82A2, 0xC65C, + 0x82A3, 0xC65D, 0x82A4, 0xDCD2, 0x82A5, 0xBDE6, 0x82A6, 0xC2AB, 0x82A7, 0xC65E, 0x82A8, 0xDCB8, 0x82A9, 0xDCCB, 0x82AA, 0xDCCE, + 0x82AB, 0xDCBE, 0x82AC, 0xB7D2, 0x82AD, 0xB0C5, 0x82AE, 0xDCC7, 0x82AF, 0xD0BE, 0x82B0, 0xDCC1, 0x82B1, 0xBBA8, 0x82B2, 0xC65F, + 0x82B3, 0xB7BC, 0x82B4, 0xDCCC, 0x82B5, 0xC660, 0x82B6, 0xC661, 0x82B7, 0xDCC6, 0x82B8, 0xDCBF, 0x82B9, 0xC7DB, 0x82BA, 0xC662, + 0x82BB, 0xC663, 0x82BC, 0xC664, 0x82BD, 0xD1BF, 0x82BE, 0xDCC0, 0x82BF, 0xC665, 0x82C0, 0xC666, 0x82C1, 0xDCCA, 0x82C2, 0xC667, + 0x82C3, 0xC668, 0x82C4, 0xDCD0, 0x82C5, 0xC669, 0x82C6, 0xC66A, 0x82C7, 0xCEAD, 0x82C8, 0xDCC2, 0x82C9, 0xC66B, 0x82CA, 0xDCC3, + 0x82CB, 0xDCC8, 0x82CC, 0xDCC9, 0x82CD, 0xB2D4, 0x82CE, 0xDCD1, 0x82CF, 0xCBD5, 0x82D0, 0xC66C, 0x82D1, 0xD4B7, 0x82D2, 0xDCDB, + 0x82D3, 0xDCDF, 0x82D4, 0xCCA6, 0x82D5, 0xDCE6, 0x82D6, 0xC66D, 0x82D7, 0xC3E7, 0x82D8, 0xDCDC, 0x82D9, 0xC66E, 0x82DA, 0xC66F, + 0x82DB, 0xBFC1, 0x82DC, 0xDCD9, 0x82DD, 0xC670, 0x82DE, 0xB0FA, 0x82DF, 0xB9B6, 0x82E0, 0xDCE5, 0x82E1, 0xDCD3, 0x82E2, 0xC671, + 0x82E3, 0xDCC4, 0x82E4, 0xDCD6, 0x82E5, 0xC8F4, 0x82E6, 0xBFE0, 0x82E7, 0xC672, 0x82E8, 0xC673, 0x82E9, 0xC674, 0x82EA, 0xC675, + 0x82EB, 0xC9BB, 0x82EC, 0xC676, 0x82ED, 0xC677, 0x82EE, 0xC678, 0x82EF, 0xB1BD, 0x82F0, 0xC679, 0x82F1, 0xD3A2, 0x82F2, 0xC67A, + 0x82F3, 0xC67B, 0x82F4, 0xDCDA, 0x82F5, 0xC67C, 0x82F6, 0xC67D, 0x82F7, 0xDCD5, 0x82F8, 0xC67E, 0x82F9, 0xC6BB, 0x82FA, 0xC680, + 0x82FB, 0xDCDE, 0x82FC, 0xC681, 0x82FD, 0xC682, 0x82FE, 0xC683, 0x82FF, 0xC684, 0x8300, 0xC685, 0x8301, 0xD7C2, 0x8302, 0xC3AF, + 0x8303, 0xB7B6, 0x8304, 0xC7D1, 0x8305, 0xC3A9, 0x8306, 0xDCE2, 0x8307, 0xDCD8, 0x8308, 0xDCEB, 0x8309, 0xDCD4, 0x830A, 0xC686, + 0x830B, 0xC687, 0x830C, 0xDCDD, 0x830D, 0xC688, 0x830E, 0xBEA5, 0x830F, 0xDCD7, 0x8310, 0xC689, 0x8311, 0xDCE0, 0x8312, 0xC68A, + 0x8313, 0xC68B, 0x8314, 0xDCE3, 0x8315, 0xDCE4, 0x8316, 0xC68C, 0x8317, 0xDCF8, 0x8318, 0xC68D, 0x8319, 0xC68E, 0x831A, 0xDCE1, + 0x831B, 0xDDA2, 0x831C, 0xDCE7, 0x831D, 0xC68F, 0x831E, 0xC690, 0x831F, 0xC691, 0x8320, 0xC692, 0x8321, 0xC693, 0x8322, 0xC694, + 0x8323, 0xC695, 0x8324, 0xC696, 0x8325, 0xC697, 0x8326, 0xC698, 0x8327, 0xBCEB, 0x8328, 0xB4C4, 0x8329, 0xC699, 0x832A, 0xC69A, + 0x832B, 0xC3A3, 0x832C, 0xB2E7, 0x832D, 0xDCFA, 0x832E, 0xC69B, 0x832F, 0xDCF2, 0x8330, 0xC69C, 0x8331, 0xDCEF, 0x8332, 0xC69D, + 0x8333, 0xDCFC, 0x8334, 0xDCEE, 0x8335, 0xD2F0, 0x8336, 0xB2E8, 0x8337, 0xC69E, 0x8338, 0xC8D7, 0x8339, 0xC8E3, 0x833A, 0xDCFB, + 0x833B, 0xC69F, 0x833C, 0xDCED, 0x833D, 0xC6A0, 0x833E, 0xC740, 0x833F, 0xC741, 0x8340, 0xDCF7, 0x8341, 0xC742, 0x8342, 0xC743, + 0x8343, 0xDCF5, 0x8344, 0xC744, 0x8345, 0xC745, 0x8346, 0xBEA3, 0x8347, 0xDCF4, 0x8348, 0xC746, 0x8349, 0xB2DD, 0x834A, 0xC747, + 0x834B, 0xC748, 0x834C, 0xC749, 0x834D, 0xC74A, 0x834E, 0xC74B, 0x834F, 0xDCF3, 0x8350, 0xBCF6, 0x8351, 0xDCE8, 0x8352, 0xBBC4, + 0x8353, 0xC74C, 0x8354, 0xC0F3, 0x8355, 0xC74D, 0x8356, 0xC74E, 0x8357, 0xC74F, 0x8358, 0xC750, 0x8359, 0xC751, 0x835A, 0xBCD4, + 0x835B, 0xDCE9, 0x835C, 0xDCEA, 0x835D, 0xC752, 0x835E, 0xDCF1, 0x835F, 0xDCF6, 0x8360, 0xDCF9, 0x8361, 0xB5B4, 0x8362, 0xC753, + 0x8363, 0xC8D9, 0x8364, 0xBBE7, 0x8365, 0xDCFE, 0x8366, 0xDCFD, 0x8367, 0xD3AB, 0x8368, 0xDDA1, 0x8369, 0xDDA3, 0x836A, 0xDDA5, + 0x836B, 0xD2F1, 0x836C, 0xDDA4, 0x836D, 0xDDA6, 0x836E, 0xDDA7, 0x836F, 0xD2A9, 0x8370, 0xC754, 0x8371, 0xC755, 0x8372, 0xC756, + 0x8373, 0xC757, 0x8374, 0xC758, 0x8375, 0xC759, 0x8376, 0xC75A, 0x8377, 0xBAC9, 0x8378, 0xDDA9, 0x8379, 0xC75B, 0x837A, 0xC75C, + 0x837B, 0xDDB6, 0x837C, 0xDDB1, 0x837D, 0xDDB4, 0x837E, 0xC75D, 0x837F, 0xC75E, 0x8380, 0xC75F, 0x8381, 0xC760, 0x8382, 0xC761, + 0x8383, 0xC762, 0x8384, 0xC763, 0x8385, 0xDDB0, 0x8386, 0xC6CE, 0x8387, 0xC764, 0x8388, 0xC765, 0x8389, 0xC0F2, 0x838A, 0xC766, + 0x838B, 0xC767, 0x838C, 0xC768, 0x838D, 0xC769, 0x838E, 0xC9AF, 0x838F, 0xC76A, 0x8390, 0xC76B, 0x8391, 0xC76C, 0x8392, 0xDCEC, + 0x8393, 0xDDAE, 0x8394, 0xC76D, 0x8395, 0xC76E, 0x8396, 0xC76F, 0x8397, 0xC770, 0x8398, 0xDDB7, 0x8399, 0xC771, 0x839A, 0xC772, + 0x839B, 0xDCF0, 0x839C, 0xDDAF, 0x839D, 0xC773, 0x839E, 0xDDB8, 0x839F, 0xC774, 0x83A0, 0xDDAC, 0x83A1, 0xC775, 0x83A2, 0xC776, + 0x83A3, 0xC777, 0x83A4, 0xC778, 0x83A5, 0xC779, 0x83A6, 0xC77A, 0x83A7, 0xC77B, 0x83A8, 0xDDB9, 0x83A9, 0xDDB3, 0x83AA, 0xDDAD, + 0x83AB, 0xC4AA, 0x83AC, 0xC77C, 0x83AD, 0xC77D, 0x83AE, 0xC77E, 0x83AF, 0xC780, 0x83B0, 0xDDA8, 0x83B1, 0xC0B3, 0x83B2, 0xC1AB, + 0x83B3, 0xDDAA, 0x83B4, 0xDDAB, 0x83B5, 0xC781, 0x83B6, 0xDDB2, 0x83B7, 0xBBF1, 0x83B8, 0xDDB5, 0x83B9, 0xD3A8, 0x83BA, 0xDDBA, + 0x83BB, 0xC782, 0x83BC, 0xDDBB, 0x83BD, 0xC3A7, 0x83BE, 0xC783, 0x83BF, 0xC784, 0x83C0, 0xDDD2, 0x83C1, 0xDDBC, 0x83C2, 0xC785, + 0x83C3, 0xC786, 0x83C4, 0xC787, 0x83C5, 0xDDD1, 0x83C6, 0xC788, 0x83C7, 0xB9BD, 0x83C8, 0xC789, 0x83C9, 0xC78A, 0x83CA, 0xBED5, + 0x83CB, 0xC78B, 0x83CC, 0xBEFA, 0x83CD, 0xC78C, 0x83CE, 0xC78D, 0x83CF, 0xBACA, 0x83D0, 0xC78E, 0x83D1, 0xC78F, 0x83D2, 0xC790, + 0x83D3, 0xC791, 0x83D4, 0xDDCA, 0x83D5, 0xC792, 0x83D6, 0xDDC5, 0x83D7, 0xC793, 0x83D8, 0xDDBF, 0x83D9, 0xC794, 0x83DA, 0xC795, + 0x83DB, 0xC796, 0x83DC, 0xB2CB, 0x83DD, 0xDDC3, 0x83DE, 0xC797, 0x83DF, 0xDDCB, 0x83E0, 0xB2A4, 0x83E1, 0xDDD5, 0x83E2, 0xC798, + 0x83E3, 0xC799, 0x83E4, 0xC79A, 0x83E5, 0xDDBE, 0x83E6, 0xC79B, 0x83E7, 0xC79C, 0x83E8, 0xC79D, 0x83E9, 0xC6D0, 0x83EA, 0xDDD0, + 0x83EB, 0xC79E, 0x83EC, 0xC79F, 0x83ED, 0xC7A0, 0x83EE, 0xC840, 0x83EF, 0xC841, 0x83F0, 0xDDD4, 0x83F1, 0xC1E2, 0x83F2, 0xB7C6, + 0x83F3, 0xC842, 0x83F4, 0xC843, 0x83F5, 0xC844, 0x83F6, 0xC845, 0x83F7, 0xC846, 0x83F8, 0xDDCE, 0x83F9, 0xDDCF, 0x83FA, 0xC847, + 0x83FB, 0xC848, 0x83FC, 0xC849, 0x83FD, 0xDDC4, 0x83FE, 0xC84A, 0x83FF, 0xC84B, 0x8400, 0xC84C, 0x8401, 0xDDBD, 0x8402, 0xC84D, + 0x8403, 0xDDCD, 0x8404, 0xCCD1, 0x8405, 0xC84E, 0x8406, 0xDDC9, 0x8407, 0xC84F, 0x8408, 0xC850, 0x8409, 0xC851, 0x840A, 0xC852, + 0x840B, 0xDDC2, 0x840C, 0xC3C8, 0x840D, 0xC6BC, 0x840E, 0xCEAE, 0x840F, 0xDDCC, 0x8410, 0xC853, 0x8411, 0xDDC8, 0x8412, 0xC854, + 0x8413, 0xC855, 0x8414, 0xC856, 0x8415, 0xC857, 0x8416, 0xC858, 0x8417, 0xC859, 0x8418, 0xDDC1, 0x8419, 0xC85A, 0x841A, 0xC85B, + 0x841B, 0xC85C, 0x841C, 0xDDC6, 0x841D, 0xC2DC, 0x841E, 0xC85D, 0x841F, 0xC85E, 0x8420, 0xC85F, 0x8421, 0xC860, 0x8422, 0xC861, + 0x8423, 0xC862, 0x8424, 0xD3A9, 0x8425, 0xD3AA, 0x8426, 0xDDD3, 0x8427, 0xCFF4, 0x8428, 0xC8F8, 0x8429, 0xC863, 0x842A, 0xC864, + 0x842B, 0xC865, 0x842C, 0xC866, 0x842D, 0xC867, 0x842E, 0xC868, 0x842F, 0xC869, 0x8430, 0xC86A, 0x8431, 0xDDE6, 0x8432, 0xC86B, + 0x8433, 0xC86C, 0x8434, 0xC86D, 0x8435, 0xC86E, 0x8436, 0xC86F, 0x8437, 0xC870, 0x8438, 0xDDC7, 0x8439, 0xC871, 0x843A, 0xC872, + 0x843B, 0xC873, 0x843C, 0xDDE0, 0x843D, 0xC2E4, 0x843E, 0xC874, 0x843F, 0xC875, 0x8440, 0xC876, 0x8441, 0xC877, 0x8442, 0xC878, + 0x8443, 0xC879, 0x8444, 0xC87A, 0x8445, 0xC87B, 0x8446, 0xDDE1, 0x8447, 0xC87C, 0x8448, 0xC87D, 0x8449, 0xC87E, 0x844A, 0xC880, + 0x844B, 0xC881, 0x844C, 0xC882, 0x844D, 0xC883, 0x844E, 0xC884, 0x844F, 0xC885, 0x8450, 0xC886, 0x8451, 0xDDD7, 0x8452, 0xC887, + 0x8453, 0xC888, 0x8454, 0xC889, 0x8455, 0xC88A, 0x8456, 0xC88B, 0x8457, 0xD6F8, 0x8458, 0xC88C, 0x8459, 0xDDD9, 0x845A, 0xDDD8, + 0x845B, 0xB8F0, 0x845C, 0xDDD6, 0x845D, 0xC88D, 0x845E, 0xC88E, 0x845F, 0xC88F, 0x8460, 0xC890, 0x8461, 0xC6CF, 0x8462, 0xC891, + 0x8463, 0xB6AD, 0x8464, 0xC892, 0x8465, 0xC893, 0x8466, 0xC894, 0x8467, 0xC895, 0x8468, 0xC896, 0x8469, 0xDDE2, 0x846A, 0xC897, + 0x846B, 0xBAF9, 0x846C, 0xD4E1, 0x846D, 0xDDE7, 0x846E, 0xC898, 0x846F, 0xC899, 0x8470, 0xC89A, 0x8471, 0xB4D0, 0x8472, 0xC89B, + 0x8473, 0xDDDA, 0x8474, 0xC89C, 0x8475, 0xBFFB, 0x8476, 0xDDE3, 0x8477, 0xC89D, 0x8478, 0xDDDF, 0x8479, 0xC89E, 0x847A, 0xDDDD, + 0x847B, 0xC89F, 0x847C, 0xC8A0, 0x847D, 0xC940, 0x847E, 0xC941, 0x847F, 0xC942, 0x8480, 0xC943, 0x8481, 0xC944, 0x8482, 0xB5D9, + 0x8483, 0xC945, 0x8484, 0xC946, 0x8485, 0xC947, 0x8486, 0xC948, 0x8487, 0xDDDB, 0x8488, 0xDDDC, 0x8489, 0xDDDE, 0x848A, 0xC949, + 0x848B, 0xBDAF, 0x848C, 0xDDE4, 0x848D, 0xC94A, 0x848E, 0xDDE5, 0x848F, 0xC94B, 0x8490, 0xC94C, 0x8491, 0xC94D, 0x8492, 0xC94E, + 0x8493, 0xC94F, 0x8494, 0xC950, 0x8495, 0xC951, 0x8496, 0xC952, 0x8497, 0xDDF5, 0x8498, 0xC953, 0x8499, 0xC3C9, 0x849A, 0xC954, + 0x849B, 0xC955, 0x849C, 0xCBE2, 0x849D, 0xC956, 0x849E, 0xC957, 0x849F, 0xC958, 0x84A0, 0xC959, 0x84A1, 0xDDF2, 0x84A2, 0xC95A, + 0x84A3, 0xC95B, 0x84A4, 0xC95C, 0x84A5, 0xC95D, 0x84A6, 0xC95E, 0x84A7, 0xC95F, 0x84A8, 0xC960, 0x84A9, 0xC961, 0x84AA, 0xC962, + 0x84AB, 0xC963, 0x84AC, 0xC964, 0x84AD, 0xC965, 0x84AE, 0xC966, 0x84AF, 0xD8E1, 0x84B0, 0xC967, 0x84B1, 0xC968, 0x84B2, 0xC6D1, + 0x84B3, 0xC969, 0x84B4, 0xDDF4, 0x84B5, 0xC96A, 0x84B6, 0xC96B, 0x84B7, 0xC96C, 0x84B8, 0xD5F4, 0x84B9, 0xDDF3, 0x84BA, 0xDDF0, + 0x84BB, 0xC96D, 0x84BC, 0xC96E, 0x84BD, 0xDDEC, 0x84BE, 0xC96F, 0x84BF, 0xDDEF, 0x84C0, 0xC970, 0x84C1, 0xDDE8, 0x84C2, 0xC971, + 0x84C3, 0xC972, 0x84C4, 0xD0EE, 0x84C5, 0xC973, 0x84C6, 0xC974, 0x84C7, 0xC975, 0x84C8, 0xC976, 0x84C9, 0xC8D8, 0x84CA, 0xDDEE, + 0x84CB, 0xC977, 0x84CC, 0xC978, 0x84CD, 0xDDE9, 0x84CE, 0xC979, 0x84CF, 0xC97A, 0x84D0, 0xDDEA, 0x84D1, 0xCBF2, 0x84D2, 0xC97B, + 0x84D3, 0xDDED, 0x84D4, 0xC97C, 0x84D5, 0xC97D, 0x84D6, 0xB1CD, 0x84D7, 0xC97E, 0x84D8, 0xC980, 0x84D9, 0xC981, 0x84DA, 0xC982, + 0x84DB, 0xC983, 0x84DC, 0xC984, 0x84DD, 0xC0B6, 0x84DE, 0xC985, 0x84DF, 0xBCBB, 0x84E0, 0xDDF1, 0x84E1, 0xC986, 0x84E2, 0xC987, + 0x84E3, 0xDDF7, 0x84E4, 0xC988, 0x84E5, 0xDDF6, 0x84E6, 0xDDEB, 0x84E7, 0xC989, 0x84E8, 0xC98A, 0x84E9, 0xC98B, 0x84EA, 0xC98C, + 0x84EB, 0xC98D, 0x84EC, 0xC5EE, 0x84ED, 0xC98E, 0x84EE, 0xC98F, 0x84EF, 0xC990, 0x84F0, 0xDDFB, 0x84F1, 0xC991, 0x84F2, 0xC992, + 0x84F3, 0xC993, 0x84F4, 0xC994, 0x84F5, 0xC995, 0x84F6, 0xC996, 0x84F7, 0xC997, 0x84F8, 0xC998, 0x84F9, 0xC999, 0x84FA, 0xC99A, + 0x84FB, 0xC99B, 0x84FC, 0xDEA4, 0x84FD, 0xC99C, 0x84FE, 0xC99D, 0x84FF, 0xDEA3, 0x8500, 0xC99E, 0x8501, 0xC99F, 0x8502, 0xC9A0, + 0x8503, 0xCA40, 0x8504, 0xCA41, 0x8505, 0xCA42, 0x8506, 0xCA43, 0x8507, 0xCA44, 0x8508, 0xCA45, 0x8509, 0xCA46, 0x850A, 0xCA47, + 0x850B, 0xCA48, 0x850C, 0xDDF8, 0x850D, 0xCA49, 0x850E, 0xCA4A, 0x850F, 0xCA4B, 0x8510, 0xCA4C, 0x8511, 0xC3EF, 0x8512, 0xCA4D, + 0x8513, 0xC2FB, 0x8514, 0xCA4E, 0x8515, 0xCA4F, 0x8516, 0xCA50, 0x8517, 0xD5E1, 0x8518, 0xCA51, 0x8519, 0xCA52, 0x851A, 0xCEB5, + 0x851B, 0xCA53, 0x851C, 0xCA54, 0x851D, 0xCA55, 0x851E, 0xCA56, 0x851F, 0xDDFD, 0x8520, 0xCA57, 0x8521, 0xB2CC, 0x8522, 0xCA58, + 0x8523, 0xCA59, 0x8524, 0xCA5A, 0x8525, 0xCA5B, 0x8526, 0xCA5C, 0x8527, 0xCA5D, 0x8528, 0xCA5E, 0x8529, 0xCA5F, 0x852A, 0xCA60, + 0x852B, 0xC4E8, 0x852C, 0xCADF, 0x852D, 0xCA61, 0x852E, 0xCA62, 0x852F, 0xCA63, 0x8530, 0xCA64, 0x8531, 0xCA65, 0x8532, 0xCA66, + 0x8533, 0xCA67, 0x8534, 0xCA68, 0x8535, 0xCA69, 0x8536, 0xCA6A, 0x8537, 0xC7BE, 0x8538, 0xDDFA, 0x8539, 0xDDFC, 0x853A, 0xDDFE, + 0x853B, 0xDEA2, 0x853C, 0xB0AA, 0x853D, 0xB1CE, 0x853E, 0xCA6B, 0x853F, 0xCA6C, 0x8540, 0xCA6D, 0x8541, 0xCA6E, 0x8542, 0xCA6F, + 0x8543, 0xDEAC, 0x8544, 0xCA70, 0x8545, 0xCA71, 0x8546, 0xCA72, 0x8547, 0xCA73, 0x8548, 0xDEA6, 0x8549, 0xBDB6, 0x854A, 0xC8EF, + 0x854B, 0xCA74, 0x854C, 0xCA75, 0x854D, 0xCA76, 0x854E, 0xCA77, 0x854F, 0xCA78, 0x8550, 0xCA79, 0x8551, 0xCA7A, 0x8552, 0xCA7B, + 0x8553, 0xCA7C, 0x8554, 0xCA7D, 0x8555, 0xCA7E, 0x8556, 0xDEA1, 0x8557, 0xCA80, 0x8558, 0xCA81, 0x8559, 0xDEA5, 0x855A, 0xCA82, + 0x855B, 0xCA83, 0x855C, 0xCA84, 0x855D, 0xCA85, 0x855E, 0xDEA9, 0x855F, 0xCA86, 0x8560, 0xCA87, 0x8561, 0xCA88, 0x8562, 0xCA89, + 0x8563, 0xCA8A, 0x8564, 0xDEA8, 0x8565, 0xCA8B, 0x8566, 0xCA8C, 0x8567, 0xCA8D, 0x8568, 0xDEA7, 0x8569, 0xCA8E, 0x856A, 0xCA8F, + 0x856B, 0xCA90, 0x856C, 0xCA91, 0x856D, 0xCA92, 0x856E, 0xCA93, 0x856F, 0xCA94, 0x8570, 0xCA95, 0x8571, 0xCA96, 0x8572, 0xDEAD, + 0x8573, 0xCA97, 0x8574, 0xD4CC, 0x8575, 0xCA98, 0x8576, 0xCA99, 0x8577, 0xCA9A, 0x8578, 0xCA9B, 0x8579, 0xDEB3, 0x857A, 0xDEAA, + 0x857B, 0xDEAE, 0x857C, 0xCA9C, 0x857D, 0xCA9D, 0x857E, 0xC0D9, 0x857F, 0xCA9E, 0x8580, 0xCA9F, 0x8581, 0xCAA0, 0x8582, 0xCB40, + 0x8583, 0xCB41, 0x8584, 0xB1A1, 0x8585, 0xDEB6, 0x8586, 0xCB42, 0x8587, 0xDEB1, 0x8588, 0xCB43, 0x8589, 0xCB44, 0x858A, 0xCB45, + 0x858B, 0xCB46, 0x858C, 0xCB47, 0x858D, 0xCB48, 0x858E, 0xCB49, 0x858F, 0xDEB2, 0x8590, 0xCB4A, 0x8591, 0xCB4B, 0x8592, 0xCB4C, + 0x8593, 0xCB4D, 0x8594, 0xCB4E, 0x8595, 0xCB4F, 0x8596, 0xCB50, 0x8597, 0xCB51, 0x8598, 0xCB52, 0x8599, 0xCB53, 0x859A, 0xCB54, + 0x859B, 0xD1A6, 0x859C, 0xDEB5, 0x859D, 0xCB55, 0x859E, 0xCB56, 0x859F, 0xCB57, 0x85A0, 0xCB58, 0x85A1, 0xCB59, 0x85A2, 0xCB5A, + 0x85A3, 0xCB5B, 0x85A4, 0xDEAF, 0x85A5, 0xCB5C, 0x85A6, 0xCB5D, 0x85A7, 0xCB5E, 0x85A8, 0xDEB0, 0x85A9, 0xCB5F, 0x85AA, 0xD0BD, + 0x85AB, 0xCB60, 0x85AC, 0xCB61, 0x85AD, 0xCB62, 0x85AE, 0xDEB4, 0x85AF, 0xCAED, 0x85B0, 0xDEB9, 0x85B1, 0xCB63, 0x85B2, 0xCB64, + 0x85B3, 0xCB65, 0x85B4, 0xCB66, 0x85B5, 0xCB67, 0x85B6, 0xCB68, 0x85B7, 0xDEB8, 0x85B8, 0xCB69, 0x85B9, 0xDEB7, 0x85BA, 0xCB6A, + 0x85BB, 0xCB6B, 0x85BC, 0xCB6C, 0x85BD, 0xCB6D, 0x85BE, 0xCB6E, 0x85BF, 0xCB6F, 0x85C0, 0xCB70, 0x85C1, 0xDEBB, 0x85C2, 0xCB71, + 0x85C3, 0xCB72, 0x85C4, 0xCB73, 0x85C5, 0xCB74, 0x85C6, 0xCB75, 0x85C7, 0xCB76, 0x85C8, 0xCB77, 0x85C9, 0xBDE5, 0x85CA, 0xCB78, + 0x85CB, 0xCB79, 0x85CC, 0xCB7A, 0x85CD, 0xCB7B, 0x85CE, 0xCB7C, 0x85CF, 0xB2D8, 0x85D0, 0xC3EA, 0x85D1, 0xCB7D, 0x85D2, 0xCB7E, + 0x85D3, 0xDEBA, 0x85D4, 0xCB80, 0x85D5, 0xC5BA, 0x85D6, 0xCB81, 0x85D7, 0xCB82, 0x85D8, 0xCB83, 0x85D9, 0xCB84, 0x85DA, 0xCB85, + 0x85DB, 0xCB86, 0x85DC, 0xDEBC, 0x85DD, 0xCB87, 0x85DE, 0xCB88, 0x85DF, 0xCB89, 0x85E0, 0xCB8A, 0x85E1, 0xCB8B, 0x85E2, 0xCB8C, + 0x85E3, 0xCB8D, 0x85E4, 0xCCD9, 0x85E5, 0xCB8E, 0x85E6, 0xCB8F, 0x85E7, 0xCB90, 0x85E8, 0xCB91, 0x85E9, 0xB7AA, 0x85EA, 0xCB92, + 0x85EB, 0xCB93, 0x85EC, 0xCB94, 0x85ED, 0xCB95, 0x85EE, 0xCB96, 0x85EF, 0xCB97, 0x85F0, 0xCB98, 0x85F1, 0xCB99, 0x85F2, 0xCB9A, + 0x85F3, 0xCB9B, 0x85F4, 0xCB9C, 0x85F5, 0xCB9D, 0x85F6, 0xCB9E, 0x85F7, 0xCB9F, 0x85F8, 0xCBA0, 0x85F9, 0xCC40, 0x85FA, 0xCC41, + 0x85FB, 0xD4E5, 0x85FC, 0xCC42, 0x85FD, 0xCC43, 0x85FE, 0xCC44, 0x85FF, 0xDEBD, 0x8600, 0xCC45, 0x8601, 0xCC46, 0x8602, 0xCC47, + 0x8603, 0xCC48, 0x8604, 0xCC49, 0x8605, 0xDEBF, 0x8606, 0xCC4A, 0x8607, 0xCC4B, 0x8608, 0xCC4C, 0x8609, 0xCC4D, 0x860A, 0xCC4E, + 0x860B, 0xCC4F, 0x860C, 0xCC50, 0x860D, 0xCC51, 0x860E, 0xCC52, 0x860F, 0xCC53, 0x8610, 0xCC54, 0x8611, 0xC4A2, 0x8612, 0xCC55, + 0x8613, 0xCC56, 0x8614, 0xCC57, 0x8615, 0xCC58, 0x8616, 0xDEC1, 0x8617, 0xCC59, 0x8618, 0xCC5A, 0x8619, 0xCC5B, 0x861A, 0xCC5C, + 0x861B, 0xCC5D, 0x861C, 0xCC5E, 0x861D, 0xCC5F, 0x861E, 0xCC60, 0x861F, 0xCC61, 0x8620, 0xCC62, 0x8621, 0xCC63, 0x8622, 0xCC64, + 0x8623, 0xCC65, 0x8624, 0xCC66, 0x8625, 0xCC67, 0x8626, 0xCC68, 0x8627, 0xDEBE, 0x8628, 0xCC69, 0x8629, 0xDEC0, 0x862A, 0xCC6A, + 0x862B, 0xCC6B, 0x862C, 0xCC6C, 0x862D, 0xCC6D, 0x862E, 0xCC6E, 0x862F, 0xCC6F, 0x8630, 0xCC70, 0x8631, 0xCC71, 0x8632, 0xCC72, + 0x8633, 0xCC73, 0x8634, 0xCC74, 0x8635, 0xCC75, 0x8636, 0xCC76, 0x8637, 0xCC77, 0x8638, 0xD5BA, 0x8639, 0xCC78, 0x863A, 0xCC79, + 0x863B, 0xCC7A, 0x863C, 0xDEC2, 0x863D, 0xCC7B, 0x863E, 0xCC7C, 0x863F, 0xCC7D, 0x8640, 0xCC7E, 0x8641, 0xCC80, 0x8642, 0xCC81, + 0x8643, 0xCC82, 0x8644, 0xCC83, 0x8645, 0xCC84, 0x8646, 0xCC85, 0x8647, 0xCC86, 0x8648, 0xCC87, 0x8649, 0xCC88, 0x864A, 0xCC89, + 0x864B, 0xCC8A, 0x864C, 0xCC8B, 0x864D, 0xF2AE, 0x864E, 0xBBA2, 0x864F, 0xC2B2, 0x8650, 0xC5B0, 0x8651, 0xC2C7, 0x8652, 0xCC8C, + 0x8653, 0xCC8D, 0x8654, 0xF2AF, 0x8655, 0xCC8E, 0x8656, 0xCC8F, 0x8657, 0xCC90, 0x8658, 0xCC91, 0x8659, 0xCC92, 0x865A, 0xD0E9, + 0x865B, 0xCC93, 0x865C, 0xCC94, 0x865D, 0xCC95, 0x865E, 0xD3DD, 0x865F, 0xCC96, 0x8660, 0xCC97, 0x8661, 0xCC98, 0x8662, 0xEBBD, + 0x8663, 0xCC99, 0x8664, 0xCC9A, 0x8665, 0xCC9B, 0x8666, 0xCC9C, 0x8667, 0xCC9D, 0x8668, 0xCC9E, 0x8669, 0xCC9F, 0x866A, 0xCCA0, + 0x866B, 0xB3E6, 0x866C, 0xF2B0, 0x866D, 0xCD40, 0x866E, 0xF2B1, 0x866F, 0xCD41, 0x8670, 0xCD42, 0x8671, 0xCAAD, 0x8672, 0xCD43, + 0x8673, 0xCD44, 0x8674, 0xCD45, 0x8675, 0xCD46, 0x8676, 0xCD47, 0x8677, 0xCD48, 0x8678, 0xCD49, 0x8679, 0xBAE7, 0x867A, 0xF2B3, + 0x867B, 0xF2B5, 0x867C, 0xF2B4, 0x867D, 0xCBE4, 0x867E, 0xCFBA, 0x867F, 0xF2B2, 0x8680, 0xCAB4, 0x8681, 0xD2CF, 0x8682, 0xC2EC, + 0x8683, 0xCD4A, 0x8684, 0xCD4B, 0x8685, 0xCD4C, 0x8686, 0xCD4D, 0x8687, 0xCD4E, 0x8688, 0xCD4F, 0x8689, 0xCD50, 0x868A, 0xCEC3, + 0x868B, 0xF2B8, 0x868C, 0xB0F6, 0x868D, 0xF2B7, 0x868E, 0xCD51, 0x868F, 0xCD52, 0x8690, 0xCD53, 0x8691, 0xCD54, 0x8692, 0xCD55, + 0x8693, 0xF2BE, 0x8694, 0xCD56, 0x8695, 0xB2CF, 0x8696, 0xCD57, 0x8697, 0xCD58, 0x8698, 0xCD59, 0x8699, 0xCD5A, 0x869A, 0xCD5B, + 0x869B, 0xCD5C, 0x869C, 0xD1C1, 0x869D, 0xF2BA, 0x869E, 0xCD5D, 0x869F, 0xCD5E, 0x86A0, 0xCD5F, 0x86A1, 0xCD60, 0x86A2, 0xCD61, + 0x86A3, 0xF2BC, 0x86A4, 0xD4E9, 0x86A5, 0xCD62, 0x86A6, 0xCD63, 0x86A7, 0xF2BB, 0x86A8, 0xF2B6, 0x86A9, 0xF2BF, 0x86AA, 0xF2BD, + 0x86AB, 0xCD64, 0x86AC, 0xF2B9, 0x86AD, 0xCD65, 0x86AE, 0xCD66, 0x86AF, 0xF2C7, 0x86B0, 0xF2C4, 0x86B1, 0xF2C6, 0x86B2, 0xCD67, + 0x86B3, 0xCD68, 0x86B4, 0xF2CA, 0x86B5, 0xF2C2, 0x86B6, 0xF2C0, 0x86B7, 0xCD69, 0x86B8, 0xCD6A, 0x86B9, 0xCD6B, 0x86BA, 0xF2C5, + 0x86BB, 0xCD6C, 0x86BC, 0xCD6D, 0x86BD, 0xCD6E, 0x86BE, 0xCD6F, 0x86BF, 0xCD70, 0x86C0, 0xD6FB, 0x86C1, 0xCD71, 0x86C2, 0xCD72, + 0x86C3, 0xCD73, 0x86C4, 0xF2C1, 0x86C5, 0xCD74, 0x86C6, 0xC7F9, 0x86C7, 0xC9DF, 0x86C8, 0xCD75, 0x86C9, 0xF2C8, 0x86CA, 0xB9C6, + 0x86CB, 0xB5B0, 0x86CC, 0xCD76, 0x86CD, 0xCD77, 0x86CE, 0xF2C3, 0x86CF, 0xF2C9, 0x86D0, 0xF2D0, 0x86D1, 0xF2D6, 0x86D2, 0xCD78, + 0x86D3, 0xCD79, 0x86D4, 0xBBD7, 0x86D5, 0xCD7A, 0x86D6, 0xCD7B, 0x86D7, 0xCD7C, 0x86D8, 0xF2D5, 0x86D9, 0xCDDC, 0x86DA, 0xCD7D, + 0x86DB, 0xD6EB, 0x86DC, 0xCD7E, 0x86DD, 0xCD80, 0x86DE, 0xF2D2, 0x86DF, 0xF2D4, 0x86E0, 0xCD81, 0x86E1, 0xCD82, 0x86E2, 0xCD83, + 0x86E3, 0xCD84, 0x86E4, 0xB8F2, 0x86E5, 0xCD85, 0x86E6, 0xCD86, 0x86E7, 0xCD87, 0x86E8, 0xCD88, 0x86E9, 0xF2CB, 0x86EA, 0xCD89, + 0x86EB, 0xCD8A, 0x86EC, 0xCD8B, 0x86ED, 0xF2CE, 0x86EE, 0xC2F9, 0x86EF, 0xCD8C, 0x86F0, 0xD5DD, 0x86F1, 0xF2CC, 0x86F2, 0xF2CD, + 0x86F3, 0xF2CF, 0x86F4, 0xF2D3, 0x86F5, 0xCD8D, 0x86F6, 0xCD8E, 0x86F7, 0xCD8F, 0x86F8, 0xF2D9, 0x86F9, 0xD3BC, 0x86FA, 0xCD90, + 0x86FB, 0xCD91, 0x86FC, 0xCD92, 0x86FD, 0xCD93, 0x86FE, 0xB6EA, 0x86FF, 0xCD94, 0x8700, 0xCAF1, 0x8701, 0xCD95, 0x8702, 0xB7E4, + 0x8703, 0xF2D7, 0x8704, 0xCD96, 0x8705, 0xCD97, 0x8706, 0xCD98, 0x8707, 0xF2D8, 0x8708, 0xF2DA, 0x8709, 0xF2DD, 0x870A, 0xF2DB, + 0x870B, 0xCD99, 0x870C, 0xCD9A, 0x870D, 0xF2DC, 0x870E, 0xCD9B, 0x870F, 0xCD9C, 0x8710, 0xCD9D, 0x8711, 0xCD9E, 0x8712, 0xD1D1, + 0x8713, 0xF2D1, 0x8714, 0xCD9F, 0x8715, 0xCDC9, 0x8716, 0xCDA0, 0x8717, 0xCECF, 0x8718, 0xD6A9, 0x8719, 0xCE40, 0x871A, 0xF2E3, + 0x871B, 0xCE41, 0x871C, 0xC3DB, 0x871D, 0xCE42, 0x871E, 0xF2E0, 0x871F, 0xCE43, 0x8720, 0xCE44, 0x8721, 0xC0AF, 0x8722, 0xF2EC, + 0x8723, 0xF2DE, 0x8724, 0xCE45, 0x8725, 0xF2E1, 0x8726, 0xCE46, 0x8727, 0xCE47, 0x8728, 0xCE48, 0x8729, 0xF2E8, 0x872A, 0xCE49, + 0x872B, 0xCE4A, 0x872C, 0xCE4B, 0x872D, 0xCE4C, 0x872E, 0xF2E2, 0x872F, 0xCE4D, 0x8730, 0xCE4E, 0x8731, 0xF2E7, 0x8732, 0xCE4F, + 0x8733, 0xCE50, 0x8734, 0xF2E6, 0x8735, 0xCE51, 0x8736, 0xCE52, 0x8737, 0xF2E9, 0x8738, 0xCE53, 0x8739, 0xCE54, 0x873A, 0xCE55, + 0x873B, 0xF2DF, 0x873C, 0xCE56, 0x873D, 0xCE57, 0x873E, 0xF2E4, 0x873F, 0xF2EA, 0x8740, 0xCE58, 0x8741, 0xCE59, 0x8742, 0xCE5A, + 0x8743, 0xCE5B, 0x8744, 0xCE5C, 0x8745, 0xCE5D, 0x8746, 0xCE5E, 0x8747, 0xD3AC, 0x8748, 0xF2E5, 0x8749, 0xB2F5, 0x874A, 0xCE5F, + 0x874B, 0xCE60, 0x874C, 0xF2F2, 0x874D, 0xCE61, 0x874E, 0xD0AB, 0x874F, 0xCE62, 0x8750, 0xCE63, 0x8751, 0xCE64, 0x8752, 0xCE65, + 0x8753, 0xF2F5, 0x8754, 0xCE66, 0x8755, 0xCE67, 0x8756, 0xCE68, 0x8757, 0xBBC8, 0x8758, 0xCE69, 0x8759, 0xF2F9, 0x875A, 0xCE6A, + 0x875B, 0xCE6B, 0x875C, 0xCE6C, 0x875D, 0xCE6D, 0x875E, 0xCE6E, 0x875F, 0xCE6F, 0x8760, 0xF2F0, 0x8761, 0xCE70, 0x8762, 0xCE71, + 0x8763, 0xF2F6, 0x8764, 0xF2F8, 0x8765, 0xF2FA, 0x8766, 0xCE72, 0x8767, 0xCE73, 0x8768, 0xCE74, 0x8769, 0xCE75, 0x876A, 0xCE76, + 0x876B, 0xCE77, 0x876C, 0xCE78, 0x876D, 0xCE79, 0x876E, 0xF2F3, 0x876F, 0xCE7A, 0x8770, 0xF2F1, 0x8771, 0xCE7B, 0x8772, 0xCE7C, + 0x8773, 0xCE7D, 0x8774, 0xBAFB, 0x8775, 0xCE7E, 0x8776, 0xB5FB, 0x8777, 0xCE80, 0x8778, 0xCE81, 0x8779, 0xCE82, 0x877A, 0xCE83, + 0x877B, 0xF2EF, 0x877C, 0xF2F7, 0x877D, 0xF2ED, 0x877E, 0xF2EE, 0x877F, 0xCE84, 0x8780, 0xCE85, 0x8781, 0xCE86, 0x8782, 0xF2EB, + 0x8783, 0xF3A6, 0x8784, 0xCE87, 0x8785, 0xF3A3, 0x8786, 0xCE88, 0x8787, 0xCE89, 0x8788, 0xF3A2, 0x8789, 0xCE8A, 0x878A, 0xCE8B, + 0x878B, 0xF2F4, 0x878C, 0xCE8C, 0x878D, 0xC8DA, 0x878E, 0xCE8D, 0x878F, 0xCE8E, 0x8790, 0xCE8F, 0x8791, 0xCE90, 0x8792, 0xCE91, + 0x8793, 0xF2FB, 0x8794, 0xCE92, 0x8795, 0xCE93, 0x8796, 0xCE94, 0x8797, 0xF3A5, 0x8798, 0xCE95, 0x8799, 0xCE96, 0x879A, 0xCE97, + 0x879B, 0xCE98, 0x879C, 0xCE99, 0x879D, 0xCE9A, 0x879E, 0xCE9B, 0x879F, 0xC3F8, 0x87A0, 0xCE9C, 0x87A1, 0xCE9D, 0x87A2, 0xCE9E, + 0x87A3, 0xCE9F, 0x87A4, 0xCEA0, 0x87A5, 0xCF40, 0x87A6, 0xCF41, 0x87A7, 0xCF42, 0x87A8, 0xF2FD, 0x87A9, 0xCF43, 0x87AA, 0xCF44, + 0x87AB, 0xF3A7, 0x87AC, 0xF3A9, 0x87AD, 0xF3A4, 0x87AE, 0xCF45, 0x87AF, 0xF2FC, 0x87B0, 0xCF46, 0x87B1, 0xCF47, 0x87B2, 0xCF48, + 0x87B3, 0xF3AB, 0x87B4, 0xCF49, 0x87B5, 0xF3AA, 0x87B6, 0xCF4A, 0x87B7, 0xCF4B, 0x87B8, 0xCF4C, 0x87B9, 0xCF4D, 0x87BA, 0xC2DD, + 0x87BB, 0xCF4E, 0x87BC, 0xCF4F, 0x87BD, 0xF3AE, 0x87BE, 0xCF50, 0x87BF, 0xCF51, 0x87C0, 0xF3B0, 0x87C1, 0xCF52, 0x87C2, 0xCF53, + 0x87C3, 0xCF54, 0x87C4, 0xCF55, 0x87C5, 0xCF56, 0x87C6, 0xF3A1, 0x87C7, 0xCF57, 0x87C8, 0xCF58, 0x87C9, 0xCF59, 0x87CA, 0xF3B1, + 0x87CB, 0xF3AC, 0x87CC, 0xCF5A, 0x87CD, 0xCF5B, 0x87CE, 0xCF5C, 0x87CF, 0xCF5D, 0x87D0, 0xCF5E, 0x87D1, 0xF3AF, 0x87D2, 0xF2FE, + 0x87D3, 0xF3AD, 0x87D4, 0xCF5F, 0x87D5, 0xCF60, 0x87D6, 0xCF61, 0x87D7, 0xCF62, 0x87D8, 0xCF63, 0x87D9, 0xCF64, 0x87DA, 0xCF65, + 0x87DB, 0xF3B2, 0x87DC, 0xCF66, 0x87DD, 0xCF67, 0x87DE, 0xCF68, 0x87DF, 0xCF69, 0x87E0, 0xF3B4, 0x87E1, 0xCF6A, 0x87E2, 0xCF6B, + 0x87E3, 0xCF6C, 0x87E4, 0xCF6D, 0x87E5, 0xF3A8, 0x87E6, 0xCF6E, 0x87E7, 0xCF6F, 0x87E8, 0xCF70, 0x87E9, 0xCF71, 0x87EA, 0xF3B3, + 0x87EB, 0xCF72, 0x87EC, 0xCF73, 0x87ED, 0xCF74, 0x87EE, 0xF3B5, 0x87EF, 0xCF75, 0x87F0, 0xCF76, 0x87F1, 0xCF77, 0x87F2, 0xCF78, + 0x87F3, 0xCF79, 0x87F4, 0xCF7A, 0x87F5, 0xCF7B, 0x87F6, 0xCF7C, 0x87F7, 0xCF7D, 0x87F8, 0xCF7E, 0x87F9, 0xD0B7, 0x87FA, 0xCF80, + 0x87FB, 0xCF81, 0x87FC, 0xCF82, 0x87FD, 0xCF83, 0x87FE, 0xF3B8, 0x87FF, 0xCF84, 0x8800, 0xCF85, 0x8801, 0xCF86, 0x8802, 0xCF87, + 0x8803, 0xD9F9, 0x8804, 0xCF88, 0x8805, 0xCF89, 0x8806, 0xCF8A, 0x8807, 0xCF8B, 0x8808, 0xCF8C, 0x8809, 0xCF8D, 0x880A, 0xF3B9, + 0x880B, 0xCF8E, 0x880C, 0xCF8F, 0x880D, 0xCF90, 0x880E, 0xCF91, 0x880F, 0xCF92, 0x8810, 0xCF93, 0x8811, 0xCF94, 0x8812, 0xCF95, + 0x8813, 0xF3B7, 0x8814, 0xCF96, 0x8815, 0xC8E4, 0x8816, 0xF3B6, 0x8817, 0xCF97, 0x8818, 0xCF98, 0x8819, 0xCF99, 0x881A, 0xCF9A, + 0x881B, 0xF3BA, 0x881C, 0xCF9B, 0x881D, 0xCF9C, 0x881E, 0xCF9D, 0x881F, 0xCF9E, 0x8820, 0xCF9F, 0x8821, 0xF3BB, 0x8822, 0xB4C0, + 0x8823, 0xCFA0, 0x8824, 0xD040, 0x8825, 0xD041, 0x8826, 0xD042, 0x8827, 0xD043, 0x8828, 0xD044, 0x8829, 0xD045, 0x882A, 0xD046, + 0x882B, 0xD047, 0x882C, 0xD048, 0x882D, 0xD049, 0x882E, 0xD04A, 0x882F, 0xD04B, 0x8830, 0xD04C, 0x8831, 0xD04D, 0x8832, 0xEEC3, + 0x8833, 0xD04E, 0x8834, 0xD04F, 0x8835, 0xD050, 0x8836, 0xD051, 0x8837, 0xD052, 0x8838, 0xD053, 0x8839, 0xF3BC, 0x883A, 0xD054, + 0x883B, 0xD055, 0x883C, 0xF3BD, 0x883D, 0xD056, 0x883E, 0xD057, 0x883F, 0xD058, 0x8840, 0xD1AA, 0x8841, 0xD059, 0x8842, 0xD05A, + 0x8843, 0xD05B, 0x8844, 0xF4AC, 0x8845, 0xD0C6, 0x8846, 0xD05C, 0x8847, 0xD05D, 0x8848, 0xD05E, 0x8849, 0xD05F, 0x884A, 0xD060, + 0x884B, 0xD061, 0x884C, 0xD0D0, 0x884D, 0xD1DC, 0x884E, 0xD062, 0x884F, 0xD063, 0x8850, 0xD064, 0x8851, 0xD065, 0x8852, 0xD066, + 0x8853, 0xD067, 0x8854, 0xCFCE, 0x8855, 0xD068, 0x8856, 0xD069, 0x8857, 0xBDD6, 0x8858, 0xD06A, 0x8859, 0xD1C3, 0x885A, 0xD06B, + 0x885B, 0xD06C, 0x885C, 0xD06D, 0x885D, 0xD06E, 0x885E, 0xD06F, 0x885F, 0xD070, 0x8860, 0xD071, 0x8861, 0xBAE2, 0x8862, 0xE1E9, + 0x8863, 0xD2C2, 0x8864, 0xF1C2, 0x8865, 0xB2B9, 0x8866, 0xD072, 0x8867, 0xD073, 0x8868, 0xB1ED, 0x8869, 0xF1C3, 0x886A, 0xD074, + 0x886B, 0xC9C0, 0x886C, 0xB3C4, 0x886D, 0xD075, 0x886E, 0xD9F2, 0x886F, 0xD076, 0x8870, 0xCBA5, 0x8871, 0xD077, 0x8872, 0xF1C4, + 0x8873, 0xD078, 0x8874, 0xD079, 0x8875, 0xD07A, 0x8876, 0xD07B, 0x8877, 0xD6D4, 0x8878, 0xD07C, 0x8879, 0xD07D, 0x887A, 0xD07E, + 0x887B, 0xD080, 0x887C, 0xD081, 0x887D, 0xF1C5, 0x887E, 0xF4C0, 0x887F, 0xF1C6, 0x8880, 0xD082, 0x8881, 0xD4AC, 0x8882, 0xF1C7, + 0x8883, 0xD083, 0x8884, 0xB0C0, 0x8885, 0xF4C1, 0x8886, 0xD084, 0x8887, 0xD085, 0x8888, 0xF4C2, 0x8889, 0xD086, 0x888A, 0xD087, + 0x888B, 0xB4FC, 0x888C, 0xD088, 0x888D, 0xC5DB, 0x888E, 0xD089, 0x888F, 0xD08A, 0x8890, 0xD08B, 0x8891, 0xD08C, 0x8892, 0xCCBB, + 0x8893, 0xD08D, 0x8894, 0xD08E, 0x8895, 0xD08F, 0x8896, 0xD0E4, 0x8897, 0xD090, 0x8898, 0xD091, 0x8899, 0xD092, 0x889A, 0xD093, + 0x889B, 0xD094, 0x889C, 0xCDE0, 0x889D, 0xD095, 0x889E, 0xD096, 0x889F, 0xD097, 0x88A0, 0xD098, 0x88A1, 0xD099, 0x88A2, 0xF1C8, + 0x88A3, 0xD09A, 0x88A4, 0xD9F3, 0x88A5, 0xD09B, 0x88A6, 0xD09C, 0x88A7, 0xD09D, 0x88A8, 0xD09E, 0x88A9, 0xD09F, 0x88AA, 0xD0A0, + 0x88AB, 0xB1BB, 0x88AC, 0xD140, 0x88AD, 0xCFAE, 0x88AE, 0xD141, 0x88AF, 0xD142, 0x88B0, 0xD143, 0x88B1, 0xB8A4, 0x88B2, 0xD144, + 0x88B3, 0xD145, 0x88B4, 0xD146, 0x88B5, 0xD147, 0x88B6, 0xD148, 0x88B7, 0xF1CA, 0x88B8, 0xD149, 0x88B9, 0xD14A, 0x88BA, 0xD14B, + 0x88BB, 0xD14C, 0x88BC, 0xF1CB, 0x88BD, 0xD14D, 0x88BE, 0xD14E, 0x88BF, 0xD14F, 0x88C0, 0xD150, 0x88C1, 0xB2C3, 0x88C2, 0xC1D1, + 0x88C3, 0xD151, 0x88C4, 0xD152, 0x88C5, 0xD7B0, 0x88C6, 0xF1C9, 0x88C7, 0xD153, 0x88C8, 0xD154, 0x88C9, 0xF1CC, 0x88CA, 0xD155, + 0x88CB, 0xD156, 0x88CC, 0xD157, 0x88CD, 0xD158, 0x88CE, 0xF1CE, 0x88CF, 0xD159, 0x88D0, 0xD15A, 0x88D1, 0xD15B, 0x88D2, 0xD9F6, + 0x88D3, 0xD15C, 0x88D4, 0xD2E1, 0x88D5, 0xD4A3, 0x88D6, 0xD15D, 0x88D7, 0xD15E, 0x88D8, 0xF4C3, 0x88D9, 0xC8B9, 0x88DA, 0xD15F, + 0x88DB, 0xD160, 0x88DC, 0xD161, 0x88DD, 0xD162, 0x88DE, 0xD163, 0x88DF, 0xF4C4, 0x88E0, 0xD164, 0x88E1, 0xD165, 0x88E2, 0xF1CD, + 0x88E3, 0xF1CF, 0x88E4, 0xBFE3, 0x88E5, 0xF1D0, 0x88E6, 0xD166, 0x88E7, 0xD167, 0x88E8, 0xF1D4, 0x88E9, 0xD168, 0x88EA, 0xD169, + 0x88EB, 0xD16A, 0x88EC, 0xD16B, 0x88ED, 0xD16C, 0x88EE, 0xD16D, 0x88EF, 0xD16E, 0x88F0, 0xF1D6, 0x88F1, 0xF1D1, 0x88F2, 0xD16F, + 0x88F3, 0xC9D1, 0x88F4, 0xC5E1, 0x88F5, 0xD170, 0x88F6, 0xD171, 0x88F7, 0xD172, 0x88F8, 0xC2E3, 0x88F9, 0xB9FC, 0x88FA, 0xD173, + 0x88FB, 0xD174, 0x88FC, 0xF1D3, 0x88FD, 0xD175, 0x88FE, 0xF1D5, 0x88FF, 0xD176, 0x8900, 0xD177, 0x8901, 0xD178, 0x8902, 0xB9D3, + 0x8903, 0xD179, 0x8904, 0xD17A, 0x8905, 0xD17B, 0x8906, 0xD17C, 0x8907, 0xD17D, 0x8908, 0xD17E, 0x8909, 0xD180, 0x890A, 0xF1DB, + 0x890B, 0xD181, 0x890C, 0xD182, 0x890D, 0xD183, 0x890E, 0xD184, 0x890F, 0xD185, 0x8910, 0xBAD6, 0x8911, 0xD186, 0x8912, 0xB0FD, + 0x8913, 0xF1D9, 0x8914, 0xD187, 0x8915, 0xD188, 0x8916, 0xD189, 0x8917, 0xD18A, 0x8918, 0xD18B, 0x8919, 0xF1D8, 0x891A, 0xF1D2, + 0x891B, 0xF1DA, 0x891C, 0xD18C, 0x891D, 0xD18D, 0x891E, 0xD18E, 0x891F, 0xD18F, 0x8920, 0xD190, 0x8921, 0xF1D7, 0x8922, 0xD191, + 0x8923, 0xD192, 0x8924, 0xD193, 0x8925, 0xC8EC, 0x8926, 0xD194, 0x8927, 0xD195, 0x8928, 0xD196, 0x8929, 0xD197, 0x892A, 0xCDCA, + 0x892B, 0xF1DD, 0x892C, 0xD198, 0x892D, 0xD199, 0x892E, 0xD19A, 0x892F, 0xD19B, 0x8930, 0xE5BD, 0x8931, 0xD19C, 0x8932, 0xD19D, + 0x8933, 0xD19E, 0x8934, 0xF1DC, 0x8935, 0xD19F, 0x8936, 0xF1DE, 0x8937, 0xD1A0, 0x8938, 0xD240, 0x8939, 0xD241, 0x893A, 0xD242, + 0x893B, 0xD243, 0x893C, 0xD244, 0x893D, 0xD245, 0x893E, 0xD246, 0x893F, 0xD247, 0x8940, 0xD248, 0x8941, 0xF1DF, 0x8942, 0xD249, + 0x8943, 0xD24A, 0x8944, 0xCFE5, 0x8945, 0xD24B, 0x8946, 0xD24C, 0x8947, 0xD24D, 0x8948, 0xD24E, 0x8949, 0xD24F, 0x894A, 0xD250, + 0x894B, 0xD251, 0x894C, 0xD252, 0x894D, 0xD253, 0x894E, 0xD254, 0x894F, 0xD255, 0x8950, 0xD256, 0x8951, 0xD257, 0x8952, 0xD258, + 0x8953, 0xD259, 0x8954, 0xD25A, 0x8955, 0xD25B, 0x8956, 0xD25C, 0x8957, 0xD25D, 0x8958, 0xD25E, 0x8959, 0xD25F, 0x895A, 0xD260, + 0x895B, 0xD261, 0x895C, 0xD262, 0x895D, 0xD263, 0x895E, 0xF4C5, 0x895F, 0xBDF3, 0x8960, 0xD264, 0x8961, 0xD265, 0x8962, 0xD266, + 0x8963, 0xD267, 0x8964, 0xD268, 0x8965, 0xD269, 0x8966, 0xF1E0, 0x8967, 0xD26A, 0x8968, 0xD26B, 0x8969, 0xD26C, 0x896A, 0xD26D, + 0x896B, 0xD26E, 0x896C, 0xD26F, 0x896D, 0xD270, 0x896E, 0xD271, 0x896F, 0xD272, 0x8970, 0xD273, 0x8971, 0xD274, 0x8972, 0xD275, + 0x8973, 0xD276, 0x8974, 0xD277, 0x8975, 0xD278, 0x8976, 0xD279, 0x8977, 0xD27A, 0x8978, 0xD27B, 0x8979, 0xD27C, 0x897A, 0xD27D, + 0x897B, 0xF1E1, 0x897C, 0xD27E, 0x897D, 0xD280, 0x897E, 0xD281, 0x897F, 0xCEF7, 0x8980, 0xD282, 0x8981, 0xD2AA, 0x8982, 0xD283, + 0x8983, 0xF1FB, 0x8984, 0xD284, 0x8985, 0xD285, 0x8986, 0xB8B2, 0x8987, 0xD286, 0x8988, 0xD287, 0x8989, 0xD288, 0x898A, 0xD289, + 0x898B, 0xD28A, 0x898C, 0xD28B, 0x898D, 0xD28C, 0x898E, 0xD28D, 0x898F, 0xD28E, 0x8990, 0xD28F, 0x8991, 0xD290, 0x8992, 0xD291, + 0x8993, 0xD292, 0x8994, 0xD293, 0x8995, 0xD294, 0x8996, 0xD295, 0x8997, 0xD296, 0x8998, 0xD297, 0x8999, 0xD298, 0x899A, 0xD299, + 0x899B, 0xD29A, 0x899C, 0xD29B, 0x899D, 0xD29C, 0x899E, 0xD29D, 0x899F, 0xD29E, 0x89A0, 0xD29F, 0x89A1, 0xD2A0, 0x89A2, 0xD340, + 0x89A3, 0xD341, 0x89A4, 0xD342, 0x89A5, 0xD343, 0x89A6, 0xD344, 0x89A7, 0xD345, 0x89A8, 0xD346, 0x89A9, 0xD347, 0x89AA, 0xD348, + 0x89AB, 0xD349, 0x89AC, 0xD34A, 0x89AD, 0xD34B, 0x89AE, 0xD34C, 0x89AF, 0xD34D, 0x89B0, 0xD34E, 0x89B1, 0xD34F, 0x89B2, 0xD350, + 0x89B3, 0xD351, 0x89B4, 0xD352, 0x89B5, 0xD353, 0x89B6, 0xD354, 0x89B7, 0xD355, 0x89B8, 0xD356, 0x89B9, 0xD357, 0x89BA, 0xD358, + 0x89BB, 0xD359, 0x89BC, 0xD35A, 0x89BD, 0xD35B, 0x89BE, 0xD35C, 0x89BF, 0xD35D, 0x89C0, 0xD35E, 0x89C1, 0xBCFB, 0x89C2, 0xB9DB, + 0x89C3, 0xD35F, 0x89C4, 0xB9E6, 0x89C5, 0xC3D9, 0x89C6, 0xCAD3, 0x89C7, 0xEAE8, 0x89C8, 0xC0C0, 0x89C9, 0xBEF5, 0x89CA, 0xEAE9, + 0x89CB, 0xEAEA, 0x89CC, 0xEAEB, 0x89CD, 0xD360, 0x89CE, 0xEAEC, 0x89CF, 0xEAED, 0x89D0, 0xEAEE, 0x89D1, 0xEAEF, 0x89D2, 0xBDC7, + 0x89D3, 0xD361, 0x89D4, 0xD362, 0x89D5, 0xD363, 0x89D6, 0xF5FB, 0x89D7, 0xD364, 0x89D8, 0xD365, 0x89D9, 0xD366, 0x89DA, 0xF5FD, + 0x89DB, 0xD367, 0x89DC, 0xF5FE, 0x89DD, 0xD368, 0x89DE, 0xF5FC, 0x89DF, 0xD369, 0x89E0, 0xD36A, 0x89E1, 0xD36B, 0x89E2, 0xD36C, + 0x89E3, 0xBDE2, 0x89E4, 0xD36D, 0x89E5, 0xF6A1, 0x89E6, 0xB4A5, 0x89E7, 0xD36E, 0x89E8, 0xD36F, 0x89E9, 0xD370, 0x89EA, 0xD371, + 0x89EB, 0xF6A2, 0x89EC, 0xD372, 0x89ED, 0xD373, 0x89EE, 0xD374, 0x89EF, 0xF6A3, 0x89F0, 0xD375, 0x89F1, 0xD376, 0x89F2, 0xD377, + 0x89F3, 0xECB2, 0x89F4, 0xD378, 0x89F5, 0xD379, 0x89F6, 0xD37A, 0x89F7, 0xD37B, 0x89F8, 0xD37C, 0x89F9, 0xD37D, 0x89FA, 0xD37E, + 0x89FB, 0xD380, 0x89FC, 0xD381, 0x89FD, 0xD382, 0x89FE, 0xD383, 0x89FF, 0xD384, 0x8A00, 0xD1D4, 0x8A01, 0xD385, 0x8A02, 0xD386, + 0x8A03, 0xD387, 0x8A04, 0xD388, 0x8A05, 0xD389, 0x8A06, 0xD38A, 0x8A07, 0xD9EA, 0x8A08, 0xD38B, 0x8A09, 0xD38C, 0x8A0A, 0xD38D, + 0x8A0B, 0xD38E, 0x8A0C, 0xD38F, 0x8A0D, 0xD390, 0x8A0E, 0xD391, 0x8A0F, 0xD392, 0x8A10, 0xD393, 0x8A11, 0xD394, 0x8A12, 0xD395, + 0x8A13, 0xD396, 0x8A14, 0xD397, 0x8A15, 0xD398, 0x8A16, 0xD399, 0x8A17, 0xD39A, 0x8A18, 0xD39B, 0x8A19, 0xD39C, 0x8A1A, 0xD39D, + 0x8A1B, 0xD39E, 0x8A1C, 0xD39F, 0x8A1D, 0xD3A0, 0x8A1E, 0xD440, 0x8A1F, 0xD441, 0x8A20, 0xD442, 0x8A21, 0xD443, 0x8A22, 0xD444, + 0x8A23, 0xD445, 0x8A24, 0xD446, 0x8A25, 0xD447, 0x8A26, 0xD448, 0x8A27, 0xD449, 0x8A28, 0xD44A, 0x8A29, 0xD44B, 0x8A2A, 0xD44C, + 0x8A2B, 0xD44D, 0x8A2C, 0xD44E, 0x8A2D, 0xD44F, 0x8A2E, 0xD450, 0x8A2F, 0xD451, 0x8A30, 0xD452, 0x8A31, 0xD453, 0x8A32, 0xD454, + 0x8A33, 0xD455, 0x8A34, 0xD456, 0x8A35, 0xD457, 0x8A36, 0xD458, 0x8A37, 0xD459, 0x8A38, 0xD45A, 0x8A39, 0xD45B, 0x8A3A, 0xD45C, + 0x8A3B, 0xD45D, 0x8A3C, 0xD45E, 0x8A3D, 0xD45F, 0x8A3E, 0xF6A4, 0x8A3F, 0xD460, 0x8A40, 0xD461, 0x8A41, 0xD462, 0x8A42, 0xD463, + 0x8A43, 0xD464, 0x8A44, 0xD465, 0x8A45, 0xD466, 0x8A46, 0xD467, 0x8A47, 0xD468, 0x8A48, 0xEEBA, 0x8A49, 0xD469, 0x8A4A, 0xD46A, + 0x8A4B, 0xD46B, 0x8A4C, 0xD46C, 0x8A4D, 0xD46D, 0x8A4E, 0xD46E, 0x8A4F, 0xD46F, 0x8A50, 0xD470, 0x8A51, 0xD471, 0x8A52, 0xD472, + 0x8A53, 0xD473, 0x8A54, 0xD474, 0x8A55, 0xD475, 0x8A56, 0xD476, 0x8A57, 0xD477, 0x8A58, 0xD478, 0x8A59, 0xD479, 0x8A5A, 0xD47A, + 0x8A5B, 0xD47B, 0x8A5C, 0xD47C, 0x8A5D, 0xD47D, 0x8A5E, 0xD47E, 0x8A5F, 0xD480, 0x8A60, 0xD481, 0x8A61, 0xD482, 0x8A62, 0xD483, + 0x8A63, 0xD484, 0x8A64, 0xD485, 0x8A65, 0xD486, 0x8A66, 0xD487, 0x8A67, 0xD488, 0x8A68, 0xD489, 0x8A69, 0xD48A, 0x8A6A, 0xD48B, + 0x8A6B, 0xD48C, 0x8A6C, 0xD48D, 0x8A6D, 0xD48E, 0x8A6E, 0xD48F, 0x8A6F, 0xD490, 0x8A70, 0xD491, 0x8A71, 0xD492, 0x8A72, 0xD493, + 0x8A73, 0xD494, 0x8A74, 0xD495, 0x8A75, 0xD496, 0x8A76, 0xD497, 0x8A77, 0xD498, 0x8A78, 0xD499, 0x8A79, 0xD5B2, 0x8A7A, 0xD49A, + 0x8A7B, 0xD49B, 0x8A7C, 0xD49C, 0x8A7D, 0xD49D, 0x8A7E, 0xD49E, 0x8A7F, 0xD49F, 0x8A80, 0xD4A0, 0x8A81, 0xD540, 0x8A82, 0xD541, + 0x8A83, 0xD542, 0x8A84, 0xD543, 0x8A85, 0xD544, 0x8A86, 0xD545, 0x8A87, 0xD546, 0x8A88, 0xD547, 0x8A89, 0xD3FE, 0x8A8A, 0xCCDC, + 0x8A8B, 0xD548, 0x8A8C, 0xD549, 0x8A8D, 0xD54A, 0x8A8E, 0xD54B, 0x8A8F, 0xD54C, 0x8A90, 0xD54D, 0x8A91, 0xD54E, 0x8A92, 0xD54F, + 0x8A93, 0xCAC4, 0x8A94, 0xD550, 0x8A95, 0xD551, 0x8A96, 0xD552, 0x8A97, 0xD553, 0x8A98, 0xD554, 0x8A99, 0xD555, 0x8A9A, 0xD556, + 0x8A9B, 0xD557, 0x8A9C, 0xD558, 0x8A9D, 0xD559, 0x8A9E, 0xD55A, 0x8A9F, 0xD55B, 0x8AA0, 0xD55C, 0x8AA1, 0xD55D, 0x8AA2, 0xD55E, + 0x8AA3, 0xD55F, 0x8AA4, 0xD560, 0x8AA5, 0xD561, 0x8AA6, 0xD562, 0x8AA7, 0xD563, 0x8AA8, 0xD564, 0x8AA9, 0xD565, 0x8AAA, 0xD566, + 0x8AAB, 0xD567, 0x8AAC, 0xD568, 0x8AAD, 0xD569, 0x8AAE, 0xD56A, 0x8AAF, 0xD56B, 0x8AB0, 0xD56C, 0x8AB1, 0xD56D, 0x8AB2, 0xD56E, + 0x8AB3, 0xD56F, 0x8AB4, 0xD570, 0x8AB5, 0xD571, 0x8AB6, 0xD572, 0x8AB7, 0xD573, 0x8AB8, 0xD574, 0x8AB9, 0xD575, 0x8ABA, 0xD576, + 0x8ABB, 0xD577, 0x8ABC, 0xD578, 0x8ABD, 0xD579, 0x8ABE, 0xD57A, 0x8ABF, 0xD57B, 0x8AC0, 0xD57C, 0x8AC1, 0xD57D, 0x8AC2, 0xD57E, + 0x8AC3, 0xD580, 0x8AC4, 0xD581, 0x8AC5, 0xD582, 0x8AC6, 0xD583, 0x8AC7, 0xD584, 0x8AC8, 0xD585, 0x8AC9, 0xD586, 0x8ACA, 0xD587, + 0x8ACB, 0xD588, 0x8ACC, 0xD589, 0x8ACD, 0xD58A, 0x8ACE, 0xD58B, 0x8ACF, 0xD58C, 0x8AD0, 0xD58D, 0x8AD1, 0xD58E, 0x8AD2, 0xD58F, + 0x8AD3, 0xD590, 0x8AD4, 0xD591, 0x8AD5, 0xD592, 0x8AD6, 0xD593, 0x8AD7, 0xD594, 0x8AD8, 0xD595, 0x8AD9, 0xD596, 0x8ADA, 0xD597, + 0x8ADB, 0xD598, 0x8ADC, 0xD599, 0x8ADD, 0xD59A, 0x8ADE, 0xD59B, 0x8ADF, 0xD59C, 0x8AE0, 0xD59D, 0x8AE1, 0xD59E, 0x8AE2, 0xD59F, + 0x8AE3, 0xD5A0, 0x8AE4, 0xD640, 0x8AE5, 0xD641, 0x8AE6, 0xD642, 0x8AE7, 0xD643, 0x8AE8, 0xD644, 0x8AE9, 0xD645, 0x8AEA, 0xD646, + 0x8AEB, 0xD647, 0x8AEC, 0xD648, 0x8AED, 0xD649, 0x8AEE, 0xD64A, 0x8AEF, 0xD64B, 0x8AF0, 0xD64C, 0x8AF1, 0xD64D, 0x8AF2, 0xD64E, + 0x8AF3, 0xD64F, 0x8AF4, 0xD650, 0x8AF5, 0xD651, 0x8AF6, 0xD652, 0x8AF7, 0xD653, 0x8AF8, 0xD654, 0x8AF9, 0xD655, 0x8AFA, 0xD656, + 0x8AFB, 0xD657, 0x8AFC, 0xD658, 0x8AFD, 0xD659, 0x8AFE, 0xD65A, 0x8AFF, 0xD65B, 0x8B00, 0xD65C, 0x8B01, 0xD65D, 0x8B02, 0xD65E, + 0x8B03, 0xD65F, 0x8B04, 0xD660, 0x8B05, 0xD661, 0x8B06, 0xD662, 0x8B07, 0xE5C0, 0x8B08, 0xD663, 0x8B09, 0xD664, 0x8B0A, 0xD665, + 0x8B0B, 0xD666, 0x8B0C, 0xD667, 0x8B0D, 0xD668, 0x8B0E, 0xD669, 0x8B0F, 0xD66A, 0x8B10, 0xD66B, 0x8B11, 0xD66C, 0x8B12, 0xD66D, + 0x8B13, 0xD66E, 0x8B14, 0xD66F, 0x8B15, 0xD670, 0x8B16, 0xD671, 0x8B17, 0xD672, 0x8B18, 0xD673, 0x8B19, 0xD674, 0x8B1A, 0xD675, + 0x8B1B, 0xD676, 0x8B1C, 0xD677, 0x8B1D, 0xD678, 0x8B1E, 0xD679, 0x8B1F, 0xD67A, 0x8B20, 0xD67B, 0x8B21, 0xD67C, 0x8B22, 0xD67D, + 0x8B23, 0xD67E, 0x8B24, 0xD680, 0x8B25, 0xD681, 0x8B26, 0xF6A5, 0x8B27, 0xD682, 0x8B28, 0xD683, 0x8B29, 0xD684, 0x8B2A, 0xD685, + 0x8B2B, 0xD686, 0x8B2C, 0xD687, 0x8B2D, 0xD688, 0x8B2E, 0xD689, 0x8B2F, 0xD68A, 0x8B30, 0xD68B, 0x8B31, 0xD68C, 0x8B32, 0xD68D, + 0x8B33, 0xD68E, 0x8B34, 0xD68F, 0x8B35, 0xD690, 0x8B36, 0xD691, 0x8B37, 0xD692, 0x8B38, 0xD693, 0x8B39, 0xD694, 0x8B3A, 0xD695, + 0x8B3B, 0xD696, 0x8B3C, 0xD697, 0x8B3D, 0xD698, 0x8B3E, 0xD699, 0x8B3F, 0xD69A, 0x8B40, 0xD69B, 0x8B41, 0xD69C, 0x8B42, 0xD69D, + 0x8B43, 0xD69E, 0x8B44, 0xD69F, 0x8B45, 0xD6A0, 0x8B46, 0xD740, 0x8B47, 0xD741, 0x8B48, 0xD742, 0x8B49, 0xD743, 0x8B4A, 0xD744, + 0x8B4B, 0xD745, 0x8B4C, 0xD746, 0x8B4D, 0xD747, 0x8B4E, 0xD748, 0x8B4F, 0xD749, 0x8B50, 0xD74A, 0x8B51, 0xD74B, 0x8B52, 0xD74C, + 0x8B53, 0xD74D, 0x8B54, 0xD74E, 0x8B55, 0xD74F, 0x8B56, 0xD750, 0x8B57, 0xD751, 0x8B58, 0xD752, 0x8B59, 0xD753, 0x8B5A, 0xD754, + 0x8B5B, 0xD755, 0x8B5C, 0xD756, 0x8B5D, 0xD757, 0x8B5E, 0xD758, 0x8B5F, 0xD759, 0x8B60, 0xD75A, 0x8B61, 0xD75B, 0x8B62, 0xD75C, + 0x8B63, 0xD75D, 0x8B64, 0xD75E, 0x8B65, 0xD75F, 0x8B66, 0xBEAF, 0x8B67, 0xD760, 0x8B68, 0xD761, 0x8B69, 0xD762, 0x8B6A, 0xD763, + 0x8B6B, 0xD764, 0x8B6C, 0xC6A9, 0x8B6D, 0xD765, 0x8B6E, 0xD766, 0x8B6F, 0xD767, 0x8B70, 0xD768, 0x8B71, 0xD769, 0x8B72, 0xD76A, + 0x8B73, 0xD76B, 0x8B74, 0xD76C, 0x8B75, 0xD76D, 0x8B76, 0xD76E, 0x8B77, 0xD76F, 0x8B78, 0xD770, 0x8B79, 0xD771, 0x8B7A, 0xD772, + 0x8B7B, 0xD773, 0x8B7C, 0xD774, 0x8B7D, 0xD775, 0x8B7E, 0xD776, 0x8B7F, 0xD777, 0x8B80, 0xD778, 0x8B81, 0xD779, 0x8B82, 0xD77A, + 0x8B83, 0xD77B, 0x8B84, 0xD77C, 0x8B85, 0xD77D, 0x8B86, 0xD77E, 0x8B87, 0xD780, 0x8B88, 0xD781, 0x8B89, 0xD782, 0x8B8A, 0xD783, + 0x8B8B, 0xD784, 0x8B8C, 0xD785, 0x8B8D, 0xD786, 0x8B8E, 0xD787, 0x8B8F, 0xD788, 0x8B90, 0xD789, 0x8B91, 0xD78A, 0x8B92, 0xD78B, + 0x8B93, 0xD78C, 0x8B94, 0xD78D, 0x8B95, 0xD78E, 0x8B96, 0xD78F, 0x8B97, 0xD790, 0x8B98, 0xD791, 0x8B99, 0xD792, 0x8B9A, 0xD793, + 0x8B9B, 0xD794, 0x8B9C, 0xD795, 0x8B9D, 0xD796, 0x8B9E, 0xD797, 0x8B9F, 0xD798, 0x8BA0, 0xDAA5, 0x8BA1, 0xBCC6, 0x8BA2, 0xB6A9, + 0x8BA3, 0xB8BC, 0x8BA4, 0xC8CF, 0x8BA5, 0xBCA5, 0x8BA6, 0xDAA6, 0x8BA7, 0xDAA7, 0x8BA8, 0xCCD6, 0x8BA9, 0xC8C3, 0x8BAA, 0xDAA8, + 0x8BAB, 0xC6FD, 0x8BAC, 0xD799, 0x8BAD, 0xD1B5, 0x8BAE, 0xD2E9, 0x8BAF, 0xD1B6, 0x8BB0, 0xBCC7, 0x8BB1, 0xD79A, 0x8BB2, 0xBDB2, + 0x8BB3, 0xBBE4, 0x8BB4, 0xDAA9, 0x8BB5, 0xDAAA, 0x8BB6, 0xD1C8, 0x8BB7, 0xDAAB, 0x8BB8, 0xD0ED, 0x8BB9, 0xB6EF, 0x8BBA, 0xC2DB, + 0x8BBB, 0xD79B, 0x8BBC, 0xCBCF, 0x8BBD, 0xB7ED, 0x8BBE, 0xC9E8, 0x8BBF, 0xB7C3, 0x8BC0, 0xBEF7, 0x8BC1, 0xD6A4, 0x8BC2, 0xDAAC, + 0x8BC3, 0xDAAD, 0x8BC4, 0xC6C0, 0x8BC5, 0xD7E7, 0x8BC6, 0xCAB6, 0x8BC7, 0xD79C, 0x8BC8, 0xD5A9, 0x8BC9, 0xCBDF, 0x8BCA, 0xD5EF, + 0x8BCB, 0xDAAE, 0x8BCC, 0xD6DF, 0x8BCD, 0xB4CA, 0x8BCE, 0xDAB0, 0x8BCF, 0xDAAF, 0x8BD0, 0xD79D, 0x8BD1, 0xD2EB, 0x8BD2, 0xDAB1, + 0x8BD3, 0xDAB2, 0x8BD4, 0xDAB3, 0x8BD5, 0xCAD4, 0x8BD6, 0xDAB4, 0x8BD7, 0xCAAB, 0x8BD8, 0xDAB5, 0x8BD9, 0xDAB6, 0x8BDA, 0xB3CF, + 0x8BDB, 0xD6EF, 0x8BDC, 0xDAB7, 0x8BDD, 0xBBB0, 0x8BDE, 0xB5AE, 0x8BDF, 0xDAB8, 0x8BE0, 0xDAB9, 0x8BE1, 0xB9EE, 0x8BE2, 0xD1AF, + 0x8BE3, 0xD2E8, 0x8BE4, 0xDABA, 0x8BE5, 0xB8C3, 0x8BE6, 0xCFEA, 0x8BE7, 0xB2EF, 0x8BE8, 0xDABB, 0x8BE9, 0xDABC, 0x8BEA, 0xD79E, + 0x8BEB, 0xBDEB, 0x8BEC, 0xCEDC, 0x8BED, 0xD3EF, 0x8BEE, 0xDABD, 0x8BEF, 0xCEF3, 0x8BF0, 0xDABE, 0x8BF1, 0xD3D5, 0x8BF2, 0xBBE5, + 0x8BF3, 0xDABF, 0x8BF4, 0xCBB5, 0x8BF5, 0xCBD0, 0x8BF6, 0xDAC0, 0x8BF7, 0xC7EB, 0x8BF8, 0xD6EE, 0x8BF9, 0xDAC1, 0x8BFA, 0xC5B5, + 0x8BFB, 0xB6C1, 0x8BFC, 0xDAC2, 0x8BFD, 0xB7CC, 0x8BFE, 0xBFCE, 0x8BFF, 0xDAC3, 0x8C00, 0xDAC4, 0x8C01, 0xCBAD, 0x8C02, 0xDAC5, + 0x8C03, 0xB5F7, 0x8C04, 0xDAC6, 0x8C05, 0xC1C2, 0x8C06, 0xD7BB, 0x8C07, 0xDAC7, 0x8C08, 0xCCB8, 0x8C09, 0xD79F, 0x8C0A, 0xD2EA, + 0x8C0B, 0xC4B1, 0x8C0C, 0xDAC8, 0x8C0D, 0xB5FD, 0x8C0E, 0xBBD1, 0x8C0F, 0xDAC9, 0x8C10, 0xD0B3, 0x8C11, 0xDACA, 0x8C12, 0xDACB, + 0x8C13, 0xCEBD, 0x8C14, 0xDACC, 0x8C15, 0xDACD, 0x8C16, 0xDACE, 0x8C17, 0xB2F7, 0x8C18, 0xDAD1, 0x8C19, 0xDACF, 0x8C1A, 0xD1E8, + 0x8C1B, 0xDAD0, 0x8C1C, 0xC3D5, 0x8C1D, 0xDAD2, 0x8C1E, 0xD7A0, 0x8C1F, 0xDAD3, 0x8C20, 0xDAD4, 0x8C21, 0xDAD5, 0x8C22, 0xD0BB, + 0x8C23, 0xD2A5, 0x8C24, 0xB0F9, 0x8C25, 0xDAD6, 0x8C26, 0xC7AB, 0x8C27, 0xDAD7, 0x8C28, 0xBDF7, 0x8C29, 0xC3A1, 0x8C2A, 0xDAD8, + 0x8C2B, 0xDAD9, 0x8C2C, 0xC3FD, 0x8C2D, 0xCCB7, 0x8C2E, 0xDADA, 0x8C2F, 0xDADB, 0x8C30, 0xC0BE, 0x8C31, 0xC6D7, 0x8C32, 0xDADC, + 0x8C33, 0xDADD, 0x8C34, 0xC7B4, 0x8C35, 0xDADE, 0x8C36, 0xDADF, 0x8C37, 0xB9C8, 0x8C38, 0xD840, 0x8C39, 0xD841, 0x8C3A, 0xD842, + 0x8C3B, 0xD843, 0x8C3C, 0xD844, 0x8C3D, 0xD845, 0x8C3E, 0xD846, 0x8C3F, 0xD847, 0x8C40, 0xD848, 0x8C41, 0xBBED, 0x8C42, 0xD849, + 0x8C43, 0xD84A, 0x8C44, 0xD84B, 0x8C45, 0xD84C, 0x8C46, 0xB6B9, 0x8C47, 0xF4F8, 0x8C48, 0xD84D, 0x8C49, 0xF4F9, 0x8C4A, 0xD84E, + 0x8C4B, 0xD84F, 0x8C4C, 0xCDE3, 0x8C4D, 0xD850, 0x8C4E, 0xD851, 0x8C4F, 0xD852, 0x8C50, 0xD853, 0x8C51, 0xD854, 0x8C52, 0xD855, + 0x8C53, 0xD856, 0x8C54, 0xD857, 0x8C55, 0xF5B9, 0x8C56, 0xD858, 0x8C57, 0xD859, 0x8C58, 0xD85A, 0x8C59, 0xD85B, 0x8C5A, 0xEBE0, + 0x8C5B, 0xD85C, 0x8C5C, 0xD85D, 0x8C5D, 0xD85E, 0x8C5E, 0xD85F, 0x8C5F, 0xD860, 0x8C60, 0xD861, 0x8C61, 0xCFF3, 0x8C62, 0xBBBF, + 0x8C63, 0xD862, 0x8C64, 0xD863, 0x8C65, 0xD864, 0x8C66, 0xD865, 0x8C67, 0xD866, 0x8C68, 0xD867, 0x8C69, 0xD868, 0x8C6A, 0xBAC0, + 0x8C6B, 0xD4A5, 0x8C6C, 0xD869, 0x8C6D, 0xD86A, 0x8C6E, 0xD86B, 0x8C6F, 0xD86C, 0x8C70, 0xD86D, 0x8C71, 0xD86E, 0x8C72, 0xD86F, + 0x8C73, 0xE1D9, 0x8C74, 0xD870, 0x8C75, 0xD871, 0x8C76, 0xD872, 0x8C77, 0xD873, 0x8C78, 0xF5F4, 0x8C79, 0xB1AA, 0x8C7A, 0xB2F2, + 0x8C7B, 0xD874, 0x8C7C, 0xD875, 0x8C7D, 0xD876, 0x8C7E, 0xD877, 0x8C7F, 0xD878, 0x8C80, 0xD879, 0x8C81, 0xD87A, 0x8C82, 0xF5F5, + 0x8C83, 0xD87B, 0x8C84, 0xD87C, 0x8C85, 0xF5F7, 0x8C86, 0xD87D, 0x8C87, 0xD87E, 0x8C88, 0xD880, 0x8C89, 0xBAD1, 0x8C8A, 0xF5F6, + 0x8C8B, 0xD881, 0x8C8C, 0xC3B2, 0x8C8D, 0xD882, 0x8C8E, 0xD883, 0x8C8F, 0xD884, 0x8C90, 0xD885, 0x8C91, 0xD886, 0x8C92, 0xD887, + 0x8C93, 0xD888, 0x8C94, 0xF5F9, 0x8C95, 0xD889, 0x8C96, 0xD88A, 0x8C97, 0xD88B, 0x8C98, 0xF5F8, 0x8C99, 0xD88C, 0x8C9A, 0xD88D, + 0x8C9B, 0xD88E, 0x8C9C, 0xD88F, 0x8C9D, 0xD890, 0x8C9E, 0xD891, 0x8C9F, 0xD892, 0x8CA0, 0xD893, 0x8CA1, 0xD894, 0x8CA2, 0xD895, + 0x8CA3, 0xD896, 0x8CA4, 0xD897, 0x8CA5, 0xD898, 0x8CA6, 0xD899, 0x8CA7, 0xD89A, 0x8CA8, 0xD89B, 0x8CA9, 0xD89C, 0x8CAA, 0xD89D, + 0x8CAB, 0xD89E, 0x8CAC, 0xD89F, 0x8CAD, 0xD8A0, 0x8CAE, 0xD940, 0x8CAF, 0xD941, 0x8CB0, 0xD942, 0x8CB1, 0xD943, 0x8CB2, 0xD944, + 0x8CB3, 0xD945, 0x8CB4, 0xD946, 0x8CB5, 0xD947, 0x8CB6, 0xD948, 0x8CB7, 0xD949, 0x8CB8, 0xD94A, 0x8CB9, 0xD94B, 0x8CBA, 0xD94C, + 0x8CBB, 0xD94D, 0x8CBC, 0xD94E, 0x8CBD, 0xD94F, 0x8CBE, 0xD950, 0x8CBF, 0xD951, 0x8CC0, 0xD952, 0x8CC1, 0xD953, 0x8CC2, 0xD954, + 0x8CC3, 0xD955, 0x8CC4, 0xD956, 0x8CC5, 0xD957, 0x8CC6, 0xD958, 0x8CC7, 0xD959, 0x8CC8, 0xD95A, 0x8CC9, 0xD95B, 0x8CCA, 0xD95C, + 0x8CCB, 0xD95D, 0x8CCC, 0xD95E, 0x8CCD, 0xD95F, 0x8CCE, 0xD960, 0x8CCF, 0xD961, 0x8CD0, 0xD962, 0x8CD1, 0xD963, 0x8CD2, 0xD964, + 0x8CD3, 0xD965, 0x8CD4, 0xD966, 0x8CD5, 0xD967, 0x8CD6, 0xD968, 0x8CD7, 0xD969, 0x8CD8, 0xD96A, 0x8CD9, 0xD96B, 0x8CDA, 0xD96C, + 0x8CDB, 0xD96D, 0x8CDC, 0xD96E, 0x8CDD, 0xD96F, 0x8CDE, 0xD970, 0x8CDF, 0xD971, 0x8CE0, 0xD972, 0x8CE1, 0xD973, 0x8CE2, 0xD974, + 0x8CE3, 0xD975, 0x8CE4, 0xD976, 0x8CE5, 0xD977, 0x8CE6, 0xD978, 0x8CE7, 0xD979, 0x8CE8, 0xD97A, 0x8CE9, 0xD97B, 0x8CEA, 0xD97C, + 0x8CEB, 0xD97D, 0x8CEC, 0xD97E, 0x8CED, 0xD980, 0x8CEE, 0xD981, 0x8CEF, 0xD982, 0x8CF0, 0xD983, 0x8CF1, 0xD984, 0x8CF2, 0xD985, + 0x8CF3, 0xD986, 0x8CF4, 0xD987, 0x8CF5, 0xD988, 0x8CF6, 0xD989, 0x8CF7, 0xD98A, 0x8CF8, 0xD98B, 0x8CF9, 0xD98C, 0x8CFA, 0xD98D, + 0x8CFB, 0xD98E, 0x8CFC, 0xD98F, 0x8CFD, 0xD990, 0x8CFE, 0xD991, 0x8CFF, 0xD992, 0x8D00, 0xD993, 0x8D01, 0xD994, 0x8D02, 0xD995, + 0x8D03, 0xD996, 0x8D04, 0xD997, 0x8D05, 0xD998, 0x8D06, 0xD999, 0x8D07, 0xD99A, 0x8D08, 0xD99B, 0x8D09, 0xD99C, 0x8D0A, 0xD99D, + 0x8D0B, 0xD99E, 0x8D0C, 0xD99F, 0x8D0D, 0xD9A0, 0x8D0E, 0xDA40, 0x8D0F, 0xDA41, 0x8D10, 0xDA42, 0x8D11, 0xDA43, 0x8D12, 0xDA44, + 0x8D13, 0xDA45, 0x8D14, 0xDA46, 0x8D15, 0xDA47, 0x8D16, 0xDA48, 0x8D17, 0xDA49, 0x8D18, 0xDA4A, 0x8D19, 0xDA4B, 0x8D1A, 0xDA4C, + 0x8D1B, 0xDA4D, 0x8D1C, 0xDA4E, 0x8D1D, 0xB1B4, 0x8D1E, 0xD5EA, 0x8D1F, 0xB8BA, 0x8D20, 0xDA4F, 0x8D21, 0xB9B1, 0x8D22, 0xB2C6, + 0x8D23, 0xD4F0, 0x8D24, 0xCFCD, 0x8D25, 0xB0DC, 0x8D26, 0xD5CB, 0x8D27, 0xBBF5, 0x8D28, 0xD6CA, 0x8D29, 0xB7B7, 0x8D2A, 0xCCB0, + 0x8D2B, 0xC6B6, 0x8D2C, 0xB1E1, 0x8D2D, 0xB9BA, 0x8D2E, 0xD6FC, 0x8D2F, 0xB9E1, 0x8D30, 0xB7A1, 0x8D31, 0xBCFA, 0x8D32, 0xEADA, + 0x8D33, 0xEADB, 0x8D34, 0xCCF9, 0x8D35, 0xB9F3, 0x8D36, 0xEADC, 0x8D37, 0xB4FB, 0x8D38, 0xC3B3, 0x8D39, 0xB7D1, 0x8D3A, 0xBAD8, + 0x8D3B, 0xEADD, 0x8D3C, 0xD4F4, 0x8D3D, 0xEADE, 0x8D3E, 0xBCD6, 0x8D3F, 0xBBDF, 0x8D40, 0xEADF, 0x8D41, 0xC1DE, 0x8D42, 0xC2B8, + 0x8D43, 0xD4DF, 0x8D44, 0xD7CA, 0x8D45, 0xEAE0, 0x8D46, 0xEAE1, 0x8D47, 0xEAE4, 0x8D48, 0xEAE2, 0x8D49, 0xEAE3, 0x8D4A, 0xC9DE, + 0x8D4B, 0xB8B3, 0x8D4C, 0xB6C4, 0x8D4D, 0xEAE5, 0x8D4E, 0xCAEA, 0x8D4F, 0xC9CD, 0x8D50, 0xB4CD, 0x8D51, 0xDA50, 0x8D52, 0xDA51, + 0x8D53, 0xE2D9, 0x8D54, 0xC5E2, 0x8D55, 0xEAE6, 0x8D56, 0xC0B5, 0x8D57, 0xDA52, 0x8D58, 0xD7B8, 0x8D59, 0xEAE7, 0x8D5A, 0xD7AC, + 0x8D5B, 0xC8FC, 0x8D5C, 0xD8D3, 0x8D5D, 0xD8CD, 0x8D5E, 0xD4DE, 0x8D5F, 0xDA53, 0x8D60, 0xD4F9, 0x8D61, 0xC9C4, 0x8D62, 0xD3AE, + 0x8D63, 0xB8D3, 0x8D64, 0xB3E0, 0x8D65, 0xDA54, 0x8D66, 0xC9E2, 0x8D67, 0xF4F6, 0x8D68, 0xDA55, 0x8D69, 0xDA56, 0x8D6A, 0xDA57, + 0x8D6B, 0xBAD5, 0x8D6C, 0xDA58, 0x8D6D, 0xF4F7, 0x8D6E, 0xDA59, 0x8D6F, 0xDA5A, 0x8D70, 0xD7DF, 0x8D71, 0xDA5B, 0x8D72, 0xDA5C, + 0x8D73, 0xF4F1, 0x8D74, 0xB8B0, 0x8D75, 0xD5D4, 0x8D76, 0xB8CF, 0x8D77, 0xC6F0, 0x8D78, 0xDA5D, 0x8D79, 0xDA5E, 0x8D7A, 0xDA5F, + 0x8D7B, 0xDA60, 0x8D7C, 0xDA61, 0x8D7D, 0xDA62, 0x8D7E, 0xDA63, 0x8D7F, 0xDA64, 0x8D80, 0xDA65, 0x8D81, 0xB3C3, 0x8D82, 0xDA66, + 0x8D83, 0xDA67, 0x8D84, 0xF4F2, 0x8D85, 0xB3AC, 0x8D86, 0xDA68, 0x8D87, 0xDA69, 0x8D88, 0xDA6A, 0x8D89, 0xDA6B, 0x8D8A, 0xD4BD, + 0x8D8B, 0xC7F7, 0x8D8C, 0xDA6C, 0x8D8D, 0xDA6D, 0x8D8E, 0xDA6E, 0x8D8F, 0xDA6F, 0x8D90, 0xDA70, 0x8D91, 0xF4F4, 0x8D92, 0xDA71, + 0x8D93, 0xDA72, 0x8D94, 0xF4F3, 0x8D95, 0xDA73, 0x8D96, 0xDA74, 0x8D97, 0xDA75, 0x8D98, 0xDA76, 0x8D99, 0xDA77, 0x8D9A, 0xDA78, + 0x8D9B, 0xDA79, 0x8D9C, 0xDA7A, 0x8D9D, 0xDA7B, 0x8D9E, 0xDA7C, 0x8D9F, 0xCCCB, 0x8DA0, 0xDA7D, 0x8DA1, 0xDA7E, 0x8DA2, 0xDA80, + 0x8DA3, 0xC8A4, 0x8DA4, 0xDA81, 0x8DA5, 0xDA82, 0x8DA6, 0xDA83, 0x8DA7, 0xDA84, 0x8DA8, 0xDA85, 0x8DA9, 0xDA86, 0x8DAA, 0xDA87, + 0x8DAB, 0xDA88, 0x8DAC, 0xDA89, 0x8DAD, 0xDA8A, 0x8DAE, 0xDA8B, 0x8DAF, 0xDA8C, 0x8DB0, 0xDA8D, 0x8DB1, 0xF4F5, 0x8DB2, 0xDA8E, + 0x8DB3, 0xD7E3, 0x8DB4, 0xC5BF, 0x8DB5, 0xF5C0, 0x8DB6, 0xDA8F, 0x8DB7, 0xDA90, 0x8DB8, 0xF5BB, 0x8DB9, 0xDA91, 0x8DBA, 0xF5C3, + 0x8DBB, 0xDA92, 0x8DBC, 0xF5C2, 0x8DBD, 0xDA93, 0x8DBE, 0xD6BA, 0x8DBF, 0xF5C1, 0x8DC0, 0xDA94, 0x8DC1, 0xDA95, 0x8DC2, 0xDA96, + 0x8DC3, 0xD4BE, 0x8DC4, 0xF5C4, 0x8DC5, 0xDA97, 0x8DC6, 0xF5CC, 0x8DC7, 0xDA98, 0x8DC8, 0xDA99, 0x8DC9, 0xDA9A, 0x8DCA, 0xDA9B, + 0x8DCB, 0xB0CF, 0x8DCC, 0xB5F8, 0x8DCD, 0xDA9C, 0x8DCE, 0xF5C9, 0x8DCF, 0xF5CA, 0x8DD0, 0xDA9D, 0x8DD1, 0xC5DC, 0x8DD2, 0xDA9E, + 0x8DD3, 0xDA9F, 0x8DD4, 0xDAA0, 0x8DD5, 0xDB40, 0x8DD6, 0xF5C5, 0x8DD7, 0xF5C6, 0x8DD8, 0xDB41, 0x8DD9, 0xDB42, 0x8DDA, 0xF5C7, + 0x8DDB, 0xF5CB, 0x8DDC, 0xDB43, 0x8DDD, 0xBEE0, 0x8DDE, 0xF5C8, 0x8DDF, 0xB8FA, 0x8DE0, 0xDB44, 0x8DE1, 0xDB45, 0x8DE2, 0xDB46, + 0x8DE3, 0xF5D0, 0x8DE4, 0xF5D3, 0x8DE5, 0xDB47, 0x8DE6, 0xDB48, 0x8DE7, 0xDB49, 0x8DE8, 0xBFE7, 0x8DE9, 0xDB4A, 0x8DEA, 0xB9F2, + 0x8DEB, 0xF5BC, 0x8DEC, 0xF5CD, 0x8DED, 0xDB4B, 0x8DEE, 0xDB4C, 0x8DEF, 0xC2B7, 0x8DF0, 0xDB4D, 0x8DF1, 0xDB4E, 0x8DF2, 0xDB4F, + 0x8DF3, 0xCCF8, 0x8DF4, 0xDB50, 0x8DF5, 0xBCF9, 0x8DF6, 0xDB51, 0x8DF7, 0xF5CE, 0x8DF8, 0xF5CF, 0x8DF9, 0xF5D1, 0x8DFA, 0xB6E5, + 0x8DFB, 0xF5D2, 0x8DFC, 0xDB52, 0x8DFD, 0xF5D5, 0x8DFE, 0xDB53, 0x8DFF, 0xDB54, 0x8E00, 0xDB55, 0x8E01, 0xDB56, 0x8E02, 0xDB57, + 0x8E03, 0xDB58, 0x8E04, 0xDB59, 0x8E05, 0xF5BD, 0x8E06, 0xDB5A, 0x8E07, 0xDB5B, 0x8E08, 0xDB5C, 0x8E09, 0xF5D4, 0x8E0A, 0xD3BB, + 0x8E0B, 0xDB5D, 0x8E0C, 0xB3EC, 0x8E0D, 0xDB5E, 0x8E0E, 0xDB5F, 0x8E0F, 0xCCA4, 0x8E10, 0xDB60, 0x8E11, 0xDB61, 0x8E12, 0xDB62, + 0x8E13, 0xDB63, 0x8E14, 0xF5D6, 0x8E15, 0xDB64, 0x8E16, 0xDB65, 0x8E17, 0xDB66, 0x8E18, 0xDB67, 0x8E19, 0xDB68, 0x8E1A, 0xDB69, + 0x8E1B, 0xDB6A, 0x8E1C, 0xDB6B, 0x8E1D, 0xF5D7, 0x8E1E, 0xBEE1, 0x8E1F, 0xF5D8, 0x8E20, 0xDB6C, 0x8E21, 0xDB6D, 0x8E22, 0xCCDF, + 0x8E23, 0xF5DB, 0x8E24, 0xDB6E, 0x8E25, 0xDB6F, 0x8E26, 0xDB70, 0x8E27, 0xDB71, 0x8E28, 0xDB72, 0x8E29, 0xB2C8, 0x8E2A, 0xD7D9, + 0x8E2B, 0xDB73, 0x8E2C, 0xF5D9, 0x8E2D, 0xDB74, 0x8E2E, 0xF5DA, 0x8E2F, 0xF5DC, 0x8E30, 0xDB75, 0x8E31, 0xF5E2, 0x8E32, 0xDB76, + 0x8E33, 0xDB77, 0x8E34, 0xDB78, 0x8E35, 0xF5E0, 0x8E36, 0xDB79, 0x8E37, 0xDB7A, 0x8E38, 0xDB7B, 0x8E39, 0xF5DF, 0x8E3A, 0xF5DD, + 0x8E3B, 0xDB7C, 0x8E3C, 0xDB7D, 0x8E3D, 0xF5E1, 0x8E3E, 0xDB7E, 0x8E3F, 0xDB80, 0x8E40, 0xF5DE, 0x8E41, 0xF5E4, 0x8E42, 0xF5E5, + 0x8E43, 0xDB81, 0x8E44, 0xCCE3, 0x8E45, 0xDB82, 0x8E46, 0xDB83, 0x8E47, 0xE5BF, 0x8E48, 0xB5B8, 0x8E49, 0xF5E3, 0x8E4A, 0xF5E8, + 0x8E4B, 0xCCA3, 0x8E4C, 0xDB84, 0x8E4D, 0xDB85, 0x8E4E, 0xDB86, 0x8E4F, 0xDB87, 0x8E50, 0xDB88, 0x8E51, 0xF5E6, 0x8E52, 0xF5E7, + 0x8E53, 0xDB89, 0x8E54, 0xDB8A, 0x8E55, 0xDB8B, 0x8E56, 0xDB8C, 0x8E57, 0xDB8D, 0x8E58, 0xDB8E, 0x8E59, 0xF5BE, 0x8E5A, 0xDB8F, + 0x8E5B, 0xDB90, 0x8E5C, 0xDB91, 0x8E5D, 0xDB92, 0x8E5E, 0xDB93, 0x8E5F, 0xDB94, 0x8E60, 0xDB95, 0x8E61, 0xDB96, 0x8E62, 0xDB97, + 0x8E63, 0xDB98, 0x8E64, 0xDB99, 0x8E65, 0xDB9A, 0x8E66, 0xB1C4, 0x8E67, 0xDB9B, 0x8E68, 0xDB9C, 0x8E69, 0xF5BF, 0x8E6A, 0xDB9D, + 0x8E6B, 0xDB9E, 0x8E6C, 0xB5C5, 0x8E6D, 0xB2E4, 0x8E6E, 0xDB9F, 0x8E6F, 0xF5EC, 0x8E70, 0xF5E9, 0x8E71, 0xDBA0, 0x8E72, 0xB6D7, + 0x8E73, 0xDC40, 0x8E74, 0xF5ED, 0x8E75, 0xDC41, 0x8E76, 0xF5EA, 0x8E77, 0xDC42, 0x8E78, 0xDC43, 0x8E79, 0xDC44, 0x8E7A, 0xDC45, + 0x8E7B, 0xDC46, 0x8E7C, 0xF5EB, 0x8E7D, 0xDC47, 0x8E7E, 0xDC48, 0x8E7F, 0xB4DA, 0x8E80, 0xDC49, 0x8E81, 0xD4EA, 0x8E82, 0xDC4A, + 0x8E83, 0xDC4B, 0x8E84, 0xDC4C, 0x8E85, 0xF5EE, 0x8E86, 0xDC4D, 0x8E87, 0xB3F9, 0x8E88, 0xDC4E, 0x8E89, 0xDC4F, 0x8E8A, 0xDC50, + 0x8E8B, 0xDC51, 0x8E8C, 0xDC52, 0x8E8D, 0xDC53, 0x8E8E, 0xDC54, 0x8E8F, 0xF5EF, 0x8E90, 0xF5F1, 0x8E91, 0xDC55, 0x8E92, 0xDC56, + 0x8E93, 0xDC57, 0x8E94, 0xF5F0, 0x8E95, 0xDC58, 0x8E96, 0xDC59, 0x8E97, 0xDC5A, 0x8E98, 0xDC5B, 0x8E99, 0xDC5C, 0x8E9A, 0xDC5D, + 0x8E9B, 0xDC5E, 0x8E9C, 0xF5F2, 0x8E9D, 0xDC5F, 0x8E9E, 0xF5F3, 0x8E9F, 0xDC60, 0x8EA0, 0xDC61, 0x8EA1, 0xDC62, 0x8EA2, 0xDC63, + 0x8EA3, 0xDC64, 0x8EA4, 0xDC65, 0x8EA5, 0xDC66, 0x8EA6, 0xDC67, 0x8EA7, 0xDC68, 0x8EA8, 0xDC69, 0x8EA9, 0xDC6A, 0x8EAA, 0xDC6B, + 0x8EAB, 0xC9ED, 0x8EAC, 0xB9AA, 0x8EAD, 0xDC6C, 0x8EAE, 0xDC6D, 0x8EAF, 0xC7FB, 0x8EB0, 0xDC6E, 0x8EB1, 0xDC6F, 0x8EB2, 0xB6E3, + 0x8EB3, 0xDC70, 0x8EB4, 0xDC71, 0x8EB5, 0xDC72, 0x8EB6, 0xDC73, 0x8EB7, 0xDC74, 0x8EB8, 0xDC75, 0x8EB9, 0xDC76, 0x8EBA, 0xCCC9, + 0x8EBB, 0xDC77, 0x8EBC, 0xDC78, 0x8EBD, 0xDC79, 0x8EBE, 0xDC7A, 0x8EBF, 0xDC7B, 0x8EC0, 0xDC7C, 0x8EC1, 0xDC7D, 0x8EC2, 0xDC7E, + 0x8EC3, 0xDC80, 0x8EC4, 0xDC81, 0x8EC5, 0xDC82, 0x8EC6, 0xDC83, 0x8EC7, 0xDC84, 0x8EC8, 0xDC85, 0x8EC9, 0xDC86, 0x8ECA, 0xDC87, + 0x8ECB, 0xDC88, 0x8ECC, 0xDC89, 0x8ECD, 0xDC8A, 0x8ECE, 0xEAA6, 0x8ECF, 0xDC8B, 0x8ED0, 0xDC8C, 0x8ED1, 0xDC8D, 0x8ED2, 0xDC8E, + 0x8ED3, 0xDC8F, 0x8ED4, 0xDC90, 0x8ED5, 0xDC91, 0x8ED6, 0xDC92, 0x8ED7, 0xDC93, 0x8ED8, 0xDC94, 0x8ED9, 0xDC95, 0x8EDA, 0xDC96, + 0x8EDB, 0xDC97, 0x8EDC, 0xDC98, 0x8EDD, 0xDC99, 0x8EDE, 0xDC9A, 0x8EDF, 0xDC9B, 0x8EE0, 0xDC9C, 0x8EE1, 0xDC9D, 0x8EE2, 0xDC9E, + 0x8EE3, 0xDC9F, 0x8EE4, 0xDCA0, 0x8EE5, 0xDD40, 0x8EE6, 0xDD41, 0x8EE7, 0xDD42, 0x8EE8, 0xDD43, 0x8EE9, 0xDD44, 0x8EEA, 0xDD45, + 0x8EEB, 0xDD46, 0x8EEC, 0xDD47, 0x8EED, 0xDD48, 0x8EEE, 0xDD49, 0x8EEF, 0xDD4A, 0x8EF0, 0xDD4B, 0x8EF1, 0xDD4C, 0x8EF2, 0xDD4D, + 0x8EF3, 0xDD4E, 0x8EF4, 0xDD4F, 0x8EF5, 0xDD50, 0x8EF6, 0xDD51, 0x8EF7, 0xDD52, 0x8EF8, 0xDD53, 0x8EF9, 0xDD54, 0x8EFA, 0xDD55, + 0x8EFB, 0xDD56, 0x8EFC, 0xDD57, 0x8EFD, 0xDD58, 0x8EFE, 0xDD59, 0x8EFF, 0xDD5A, 0x8F00, 0xDD5B, 0x8F01, 0xDD5C, 0x8F02, 0xDD5D, + 0x8F03, 0xDD5E, 0x8F04, 0xDD5F, 0x8F05, 0xDD60, 0x8F06, 0xDD61, 0x8F07, 0xDD62, 0x8F08, 0xDD63, 0x8F09, 0xDD64, 0x8F0A, 0xDD65, + 0x8F0B, 0xDD66, 0x8F0C, 0xDD67, 0x8F0D, 0xDD68, 0x8F0E, 0xDD69, 0x8F0F, 0xDD6A, 0x8F10, 0xDD6B, 0x8F11, 0xDD6C, 0x8F12, 0xDD6D, + 0x8F13, 0xDD6E, 0x8F14, 0xDD6F, 0x8F15, 0xDD70, 0x8F16, 0xDD71, 0x8F17, 0xDD72, 0x8F18, 0xDD73, 0x8F19, 0xDD74, 0x8F1A, 0xDD75, + 0x8F1B, 0xDD76, 0x8F1C, 0xDD77, 0x8F1D, 0xDD78, 0x8F1E, 0xDD79, 0x8F1F, 0xDD7A, 0x8F20, 0xDD7B, 0x8F21, 0xDD7C, 0x8F22, 0xDD7D, + 0x8F23, 0xDD7E, 0x8F24, 0xDD80, 0x8F25, 0xDD81, 0x8F26, 0xDD82, 0x8F27, 0xDD83, 0x8F28, 0xDD84, 0x8F29, 0xDD85, 0x8F2A, 0xDD86, + 0x8F2B, 0xDD87, 0x8F2C, 0xDD88, 0x8F2D, 0xDD89, 0x8F2E, 0xDD8A, 0x8F2F, 0xDD8B, 0x8F30, 0xDD8C, 0x8F31, 0xDD8D, 0x8F32, 0xDD8E, + 0x8F33, 0xDD8F, 0x8F34, 0xDD90, 0x8F35, 0xDD91, 0x8F36, 0xDD92, 0x8F37, 0xDD93, 0x8F38, 0xDD94, 0x8F39, 0xDD95, 0x8F3A, 0xDD96, + 0x8F3B, 0xDD97, 0x8F3C, 0xDD98, 0x8F3D, 0xDD99, 0x8F3E, 0xDD9A, 0x8F3F, 0xDD9B, 0x8F40, 0xDD9C, 0x8F41, 0xDD9D, 0x8F42, 0xDD9E, + 0x8F43, 0xDD9F, 0x8F44, 0xDDA0, 0x8F45, 0xDE40, 0x8F46, 0xDE41, 0x8F47, 0xDE42, 0x8F48, 0xDE43, 0x8F49, 0xDE44, 0x8F4A, 0xDE45, + 0x8F4B, 0xDE46, 0x8F4C, 0xDE47, 0x8F4D, 0xDE48, 0x8F4E, 0xDE49, 0x8F4F, 0xDE4A, 0x8F50, 0xDE4B, 0x8F51, 0xDE4C, 0x8F52, 0xDE4D, + 0x8F53, 0xDE4E, 0x8F54, 0xDE4F, 0x8F55, 0xDE50, 0x8F56, 0xDE51, 0x8F57, 0xDE52, 0x8F58, 0xDE53, 0x8F59, 0xDE54, 0x8F5A, 0xDE55, + 0x8F5B, 0xDE56, 0x8F5C, 0xDE57, 0x8F5D, 0xDE58, 0x8F5E, 0xDE59, 0x8F5F, 0xDE5A, 0x8F60, 0xDE5B, 0x8F61, 0xDE5C, 0x8F62, 0xDE5D, + 0x8F63, 0xDE5E, 0x8F64, 0xDE5F, 0x8F65, 0xDE60, 0x8F66, 0xB3B5, 0x8F67, 0xD4FE, 0x8F68, 0xB9EC, 0x8F69, 0xD0F9, 0x8F6A, 0xDE61, + 0x8F6B, 0xE9ED, 0x8F6C, 0xD7AA, 0x8F6D, 0xE9EE, 0x8F6E, 0xC2D6, 0x8F6F, 0xC8ED, 0x8F70, 0xBAE4, 0x8F71, 0xE9EF, 0x8F72, 0xE9F0, + 0x8F73, 0xE9F1, 0x8F74, 0xD6E1, 0x8F75, 0xE9F2, 0x8F76, 0xE9F3, 0x8F77, 0xE9F5, 0x8F78, 0xE9F4, 0x8F79, 0xE9F6, 0x8F7A, 0xE9F7, + 0x8F7B, 0xC7E1, 0x8F7C, 0xE9F8, 0x8F7D, 0xD4D8, 0x8F7E, 0xE9F9, 0x8F7F, 0xBDCE, 0x8F80, 0xDE62, 0x8F81, 0xE9FA, 0x8F82, 0xE9FB, + 0x8F83, 0xBDCF, 0x8F84, 0xE9FC, 0x8F85, 0xB8A8, 0x8F86, 0xC1BE, 0x8F87, 0xE9FD, 0x8F88, 0xB1B2, 0x8F89, 0xBBD4, 0x8F8A, 0xB9F5, + 0x8F8B, 0xE9FE, 0x8F8C, 0xDE63, 0x8F8D, 0xEAA1, 0x8F8E, 0xEAA2, 0x8F8F, 0xEAA3, 0x8F90, 0xB7F8, 0x8F91, 0xBCAD, 0x8F92, 0xDE64, + 0x8F93, 0xCAE4, 0x8F94, 0xE0CE, 0x8F95, 0xD4AF, 0x8F96, 0xCFBD, 0x8F97, 0xD5B7, 0x8F98, 0xEAA4, 0x8F99, 0xD5DE, 0x8F9A, 0xEAA5, + 0x8F9B, 0xD0C1, 0x8F9C, 0xB9BC, 0x8F9D, 0xDE65, 0x8F9E, 0xB4C7, 0x8F9F, 0xB1D9, 0x8FA0, 0xDE66, 0x8FA1, 0xDE67, 0x8FA2, 0xDE68, + 0x8FA3, 0xC0B1, 0x8FA4, 0xDE69, 0x8FA5, 0xDE6A, 0x8FA6, 0xDE6B, 0x8FA7, 0xDE6C, 0x8FA8, 0xB1E6, 0x8FA9, 0xB1E7, 0x8FAA, 0xDE6D, + 0x8FAB, 0xB1E8, 0x8FAC, 0xDE6E, 0x8FAD, 0xDE6F, 0x8FAE, 0xDE70, 0x8FAF, 0xDE71, 0x8FB0, 0xB3BD, 0x8FB1, 0xC8E8, 0x8FB2, 0xDE72, + 0x8FB3, 0xDE73, 0x8FB4, 0xDE74, 0x8FB5, 0xDE75, 0x8FB6, 0xE5C1, 0x8FB7, 0xDE76, 0x8FB8, 0xDE77, 0x8FB9, 0xB1DF, 0x8FBA, 0xDE78, + 0x8FBB, 0xDE79, 0x8FBC, 0xDE7A, 0x8FBD, 0xC1C9, 0x8FBE, 0xB4EF, 0x8FBF, 0xDE7B, 0x8FC0, 0xDE7C, 0x8FC1, 0xC7A8, 0x8FC2, 0xD3D8, + 0x8FC3, 0xDE7D, 0x8FC4, 0xC6F9, 0x8FC5, 0xD1B8, 0x8FC6, 0xDE7E, 0x8FC7, 0xB9FD, 0x8FC8, 0xC2F5, 0x8FC9, 0xDE80, 0x8FCA, 0xDE81, + 0x8FCB, 0xDE82, 0x8FCC, 0xDE83, 0x8FCD, 0xDE84, 0x8FCE, 0xD3AD, 0x8FCF, 0xDE85, 0x8FD0, 0xD4CB, 0x8FD1, 0xBDFC, 0x8FD2, 0xDE86, + 0x8FD3, 0xE5C2, 0x8FD4, 0xB7B5, 0x8FD5, 0xE5C3, 0x8FD6, 0xDE87, 0x8FD7, 0xDE88, 0x8FD8, 0xBBB9, 0x8FD9, 0xD5E2, 0x8FDA, 0xDE89, + 0x8FDB, 0xBDF8, 0x8FDC, 0xD4B6, 0x8FDD, 0xCEA5, 0x8FDE, 0xC1AC, 0x8FDF, 0xB3D9, 0x8FE0, 0xDE8A, 0x8FE1, 0xDE8B, 0x8FE2, 0xCCF6, + 0x8FE3, 0xDE8C, 0x8FE4, 0xE5C6, 0x8FE5, 0xE5C4, 0x8FE6, 0xE5C8, 0x8FE7, 0xDE8D, 0x8FE8, 0xE5CA, 0x8FE9, 0xE5C7, 0x8FEA, 0xB5CF, + 0x8FEB, 0xC6C8, 0x8FEC, 0xDE8E, 0x8FED, 0xB5FC, 0x8FEE, 0xE5C5, 0x8FEF, 0xDE8F, 0x8FF0, 0xCAF6, 0x8FF1, 0xDE90, 0x8FF2, 0xDE91, + 0x8FF3, 0xE5C9, 0x8FF4, 0xDE92, 0x8FF5, 0xDE93, 0x8FF6, 0xDE94, 0x8FF7, 0xC3D4, 0x8FF8, 0xB1C5, 0x8FF9, 0xBCA3, 0x8FFA, 0xDE95, + 0x8FFB, 0xDE96, 0x8FFC, 0xDE97, 0x8FFD, 0xD7B7, 0x8FFE, 0xDE98, 0x8FFF, 0xDE99, 0x9000, 0xCDCB, 0x9001, 0xCBCD, 0x9002, 0xCACA, + 0x9003, 0xCCD3, 0x9004, 0xE5CC, 0x9005, 0xE5CB, 0x9006, 0xC4E6, 0x9007, 0xDE9A, 0x9008, 0xDE9B, 0x9009, 0xD1A1, 0x900A, 0xD1B7, + 0x900B, 0xE5CD, 0x900C, 0xDE9C, 0x900D, 0xE5D0, 0x900E, 0xDE9D, 0x900F, 0xCDB8, 0x9010, 0xD6F0, 0x9011, 0xE5CF, 0x9012, 0xB5DD, + 0x9013, 0xDE9E, 0x9014, 0xCDBE, 0x9015, 0xDE9F, 0x9016, 0xE5D1, 0x9017, 0xB6BA, 0x9018, 0xDEA0, 0x9019, 0xDF40, 0x901A, 0xCDA8, + 0x901B, 0xB9E4, 0x901C, 0xDF41, 0x901D, 0xCAC5, 0x901E, 0xB3D1, 0x901F, 0xCBD9, 0x9020, 0xD4EC, 0x9021, 0xE5D2, 0x9022, 0xB7EA, + 0x9023, 0xDF42, 0x9024, 0xDF43, 0x9025, 0xDF44, 0x9026, 0xE5CE, 0x9027, 0xDF45, 0x9028, 0xDF46, 0x9029, 0xDF47, 0x902A, 0xDF48, + 0x902B, 0xDF49, 0x902C, 0xDF4A, 0x902D, 0xE5D5, 0x902E, 0xB4FE, 0x902F, 0xE5D6, 0x9030, 0xDF4B, 0x9031, 0xDF4C, 0x9032, 0xDF4D, + 0x9033, 0xDF4E, 0x9034, 0xDF4F, 0x9035, 0xE5D3, 0x9036, 0xE5D4, 0x9037, 0xDF50, 0x9038, 0xD2DD, 0x9039, 0xDF51, 0x903A, 0xDF52, + 0x903B, 0xC2DF, 0x903C, 0xB1C6, 0x903D, 0xDF53, 0x903E, 0xD3E2, 0x903F, 0xDF54, 0x9040, 0xDF55, 0x9041, 0xB6DD, 0x9042, 0xCBEC, + 0x9043, 0xDF56, 0x9044, 0xE5D7, 0x9045, 0xDF57, 0x9046, 0xDF58, 0x9047, 0xD3F6, 0x9048, 0xDF59, 0x9049, 0xDF5A, 0x904A, 0xDF5B, + 0x904B, 0xDF5C, 0x904C, 0xDF5D, 0x904D, 0xB1E9, 0x904E, 0xDF5E, 0x904F, 0xB6F4, 0x9050, 0xE5DA, 0x9051, 0xE5D8, 0x9052, 0xE5D9, + 0x9053, 0xB5C0, 0x9054, 0xDF5F, 0x9055, 0xDF60, 0x9056, 0xDF61, 0x9057, 0xD2C5, 0x9058, 0xE5DC, 0x9059, 0xDF62, 0x905A, 0xDF63, + 0x905B, 0xE5DE, 0x905C, 0xDF64, 0x905D, 0xDF65, 0x905E, 0xDF66, 0x905F, 0xDF67, 0x9060, 0xDF68, 0x9061, 0xDF69, 0x9062, 0xE5DD, + 0x9063, 0xC7B2, 0x9064, 0xDF6A, 0x9065, 0xD2A3, 0x9066, 0xDF6B, 0x9067, 0xDF6C, 0x9068, 0xE5DB, 0x9069, 0xDF6D, 0x906A, 0xDF6E, + 0x906B, 0xDF6F, 0x906C, 0xDF70, 0x906D, 0xD4E2, 0x906E, 0xD5DA, 0x906F, 0xDF71, 0x9070, 0xDF72, 0x9071, 0xDF73, 0x9072, 0xDF74, + 0x9073, 0xDF75, 0x9074, 0xE5E0, 0x9075, 0xD7F1, 0x9076, 0xDF76, 0x9077, 0xDF77, 0x9078, 0xDF78, 0x9079, 0xDF79, 0x907A, 0xDF7A, + 0x907B, 0xDF7B, 0x907C, 0xDF7C, 0x907D, 0xE5E1, 0x907E, 0xDF7D, 0x907F, 0xB1DC, 0x9080, 0xD1FB, 0x9081, 0xDF7E, 0x9082, 0xE5E2, + 0x9083, 0xE5E4, 0x9084, 0xDF80, 0x9085, 0xDF81, 0x9086, 0xDF82, 0x9087, 0xDF83, 0x9088, 0xE5E3, 0x9089, 0xDF84, 0x908A, 0xDF85, + 0x908B, 0xE5E5, 0x908C, 0xDF86, 0x908D, 0xDF87, 0x908E, 0xDF88, 0x908F, 0xDF89, 0x9090, 0xDF8A, 0x9091, 0xD2D8, 0x9092, 0xDF8B, + 0x9093, 0xB5CB, 0x9094, 0xDF8C, 0x9095, 0xE7DF, 0x9096, 0xDF8D, 0x9097, 0xDAF5, 0x9098, 0xDF8E, 0x9099, 0xDAF8, 0x909A, 0xDF8F, + 0x909B, 0xDAF6, 0x909C, 0xDF90, 0x909D, 0xDAF7, 0x909E, 0xDF91, 0x909F, 0xDF92, 0x90A0, 0xDF93, 0x90A1, 0xDAFA, 0x90A2, 0xD0CF, + 0x90A3, 0xC4C7, 0x90A4, 0xDF94, 0x90A5, 0xDF95, 0x90A6, 0xB0EE, 0x90A7, 0xDF96, 0x90A8, 0xDF97, 0x90A9, 0xDF98, 0x90AA, 0xD0B0, + 0x90AB, 0xDF99, 0x90AC, 0xDAF9, 0x90AD, 0xDF9A, 0x90AE, 0xD3CA, 0x90AF, 0xBAAA, 0x90B0, 0xDBA2, 0x90B1, 0xC7F1, 0x90B2, 0xDF9B, + 0x90B3, 0xDAFC, 0x90B4, 0xDAFB, 0x90B5, 0xC9DB, 0x90B6, 0xDAFD, 0x90B7, 0xDF9C, 0x90B8, 0xDBA1, 0x90B9, 0xD7DE, 0x90BA, 0xDAFE, + 0x90BB, 0xC1DA, 0x90BC, 0xDF9D, 0x90BD, 0xDF9E, 0x90BE, 0xDBA5, 0x90BF, 0xDF9F, 0x90C0, 0xDFA0, 0x90C1, 0xD3F4, 0x90C2, 0xE040, + 0x90C3, 0xE041, 0x90C4, 0xDBA7, 0x90C5, 0xDBA4, 0x90C6, 0xE042, 0x90C7, 0xDBA8, 0x90C8, 0xE043, 0x90C9, 0xE044, 0x90CA, 0xBDBC, + 0x90CB, 0xE045, 0x90CC, 0xE046, 0x90CD, 0xE047, 0x90CE, 0xC0C9, 0x90CF, 0xDBA3, 0x90D0, 0xDBA6, 0x90D1, 0xD6A3, 0x90D2, 0xE048, + 0x90D3, 0xDBA9, 0x90D4, 0xE049, 0x90D5, 0xE04A, 0x90D6, 0xE04B, 0x90D7, 0xDBAD, 0x90D8, 0xE04C, 0x90D9, 0xE04D, 0x90DA, 0xE04E, + 0x90DB, 0xDBAE, 0x90DC, 0xDBAC, 0x90DD, 0xBAC2, 0x90DE, 0xE04F, 0x90DF, 0xE050, 0x90E0, 0xE051, 0x90E1, 0xBFA4, 0x90E2, 0xDBAB, + 0x90E3, 0xE052, 0x90E4, 0xE053, 0x90E5, 0xE054, 0x90E6, 0xDBAA, 0x90E7, 0xD4C7, 0x90E8, 0xB2BF, 0x90E9, 0xE055, 0x90EA, 0xE056, + 0x90EB, 0xDBAF, 0x90EC, 0xE057, 0x90ED, 0xB9F9, 0x90EE, 0xE058, 0x90EF, 0xDBB0, 0x90F0, 0xE059, 0x90F1, 0xE05A, 0x90F2, 0xE05B, + 0x90F3, 0xE05C, 0x90F4, 0xB3BB, 0x90F5, 0xE05D, 0x90F6, 0xE05E, 0x90F7, 0xE05F, 0x90F8, 0xB5A6, 0x90F9, 0xE060, 0x90FA, 0xE061, + 0x90FB, 0xE062, 0x90FC, 0xE063, 0x90FD, 0xB6BC, 0x90FE, 0xDBB1, 0x90FF, 0xE064, 0x9100, 0xE065, 0x9101, 0xE066, 0x9102, 0xB6F5, + 0x9103, 0xE067, 0x9104, 0xDBB2, 0x9105, 0xE068, 0x9106, 0xE069, 0x9107, 0xE06A, 0x9108, 0xE06B, 0x9109, 0xE06C, 0x910A, 0xE06D, + 0x910B, 0xE06E, 0x910C, 0xE06F, 0x910D, 0xE070, 0x910E, 0xE071, 0x910F, 0xE072, 0x9110, 0xE073, 0x9111, 0xE074, 0x9112, 0xE075, + 0x9113, 0xE076, 0x9114, 0xE077, 0x9115, 0xE078, 0x9116, 0xE079, 0x9117, 0xE07A, 0x9118, 0xE07B, 0x9119, 0xB1C9, 0x911A, 0xE07C, + 0x911B, 0xE07D, 0x911C, 0xE07E, 0x911D, 0xE080, 0x911E, 0xDBB4, 0x911F, 0xE081, 0x9120, 0xE082, 0x9121, 0xE083, 0x9122, 0xDBB3, + 0x9123, 0xDBB5, 0x9124, 0xE084, 0x9125, 0xE085, 0x9126, 0xE086, 0x9127, 0xE087, 0x9128, 0xE088, 0x9129, 0xE089, 0x912A, 0xE08A, + 0x912B, 0xE08B, 0x912C, 0xE08C, 0x912D, 0xE08D, 0x912E, 0xE08E, 0x912F, 0xDBB7, 0x9130, 0xE08F, 0x9131, 0xDBB6, 0x9132, 0xE090, + 0x9133, 0xE091, 0x9134, 0xE092, 0x9135, 0xE093, 0x9136, 0xE094, 0x9137, 0xE095, 0x9138, 0xE096, 0x9139, 0xDBB8, 0x913A, 0xE097, + 0x913B, 0xE098, 0x913C, 0xE099, 0x913D, 0xE09A, 0x913E, 0xE09B, 0x913F, 0xE09C, 0x9140, 0xE09D, 0x9141, 0xE09E, 0x9142, 0xE09F, + 0x9143, 0xDBB9, 0x9144, 0xE0A0, 0x9145, 0xE140, 0x9146, 0xDBBA, 0x9147, 0xE141, 0x9148, 0xE142, 0x9149, 0xD3CF, 0x914A, 0xF4FA, + 0x914B, 0xC7F5, 0x914C, 0xD7C3, 0x914D, 0xC5E4, 0x914E, 0xF4FC, 0x914F, 0xF4FD, 0x9150, 0xF4FB, 0x9151, 0xE143, 0x9152, 0xBEC6, + 0x9153, 0xE144, 0x9154, 0xE145, 0x9155, 0xE146, 0x9156, 0xE147, 0x9157, 0xD0EF, 0x9158, 0xE148, 0x9159, 0xE149, 0x915A, 0xB7D3, + 0x915B, 0xE14A, 0x915C, 0xE14B, 0x915D, 0xD4CD, 0x915E, 0xCCAA, 0x915F, 0xE14C, 0x9160, 0xE14D, 0x9161, 0xF5A2, 0x9162, 0xF5A1, + 0x9163, 0xBAA8, 0x9164, 0xF4FE, 0x9165, 0xCBD6, 0x9166, 0xE14E, 0x9167, 0xE14F, 0x9168, 0xE150, 0x9169, 0xF5A4, 0x916A, 0xC0D2, + 0x916B, 0xE151, 0x916C, 0xB3EA, 0x916D, 0xE152, 0x916E, 0xCDAA, 0x916F, 0xF5A5, 0x9170, 0xF5A3, 0x9171, 0xBDB4, 0x9172, 0xF5A8, + 0x9173, 0xE153, 0x9174, 0xF5A9, 0x9175, 0xBDCD, 0x9176, 0xC3B8, 0x9177, 0xBFE1, 0x9178, 0xCBE1, 0x9179, 0xF5AA, 0x917A, 0xE154, + 0x917B, 0xE155, 0x917C, 0xE156, 0x917D, 0xF5A6, 0x917E, 0xF5A7, 0x917F, 0xC4F0, 0x9180, 0xE157, 0x9181, 0xE158, 0x9182, 0xE159, + 0x9183, 0xE15A, 0x9184, 0xE15B, 0x9185, 0xF5AC, 0x9186, 0xE15C, 0x9187, 0xB4BC, 0x9188, 0xE15D, 0x9189, 0xD7ED, 0x918A, 0xE15E, + 0x918B, 0xB4D7, 0x918C, 0xF5AB, 0x918D, 0xF5AE, 0x918E, 0xE15F, 0x918F, 0xE160, 0x9190, 0xF5AD, 0x9191, 0xF5AF, 0x9192, 0xD0D1, + 0x9193, 0xE161, 0x9194, 0xE162, 0x9195, 0xE163, 0x9196, 0xE164, 0x9197, 0xE165, 0x9198, 0xE166, 0x9199, 0xE167, 0x919A, 0xC3D1, + 0x919B, 0xC8A9, 0x919C, 0xE168, 0x919D, 0xE169, 0x919E, 0xE16A, 0x919F, 0xE16B, 0x91A0, 0xE16C, 0x91A1, 0xE16D, 0x91A2, 0xF5B0, + 0x91A3, 0xF5B1, 0x91A4, 0xE16E, 0x91A5, 0xE16F, 0x91A6, 0xE170, 0x91A7, 0xE171, 0x91A8, 0xE172, 0x91A9, 0xE173, 0x91AA, 0xF5B2, + 0x91AB, 0xE174, 0x91AC, 0xE175, 0x91AD, 0xF5B3, 0x91AE, 0xF5B4, 0x91AF, 0xF5B5, 0x91B0, 0xE176, 0x91B1, 0xE177, 0x91B2, 0xE178, + 0x91B3, 0xE179, 0x91B4, 0xF5B7, 0x91B5, 0xF5B6, 0x91B6, 0xE17A, 0x91B7, 0xE17B, 0x91B8, 0xE17C, 0x91B9, 0xE17D, 0x91BA, 0xF5B8, + 0x91BB, 0xE17E, 0x91BC, 0xE180, 0x91BD, 0xE181, 0x91BE, 0xE182, 0x91BF, 0xE183, 0x91C0, 0xE184, 0x91C1, 0xE185, 0x91C2, 0xE186, + 0x91C3, 0xE187, 0x91C4, 0xE188, 0x91C5, 0xE189, 0x91C6, 0xE18A, 0x91C7, 0xB2C9, 0x91C8, 0xE18B, 0x91C9, 0xD3D4, 0x91CA, 0xCACD, + 0x91CB, 0xE18C, 0x91CC, 0xC0EF, 0x91CD, 0xD6D8, 0x91CE, 0xD2B0, 0x91CF, 0xC1BF, 0x91D0, 0xE18D, 0x91D1, 0xBDF0, 0x91D2, 0xE18E, + 0x91D3, 0xE18F, 0x91D4, 0xE190, 0x91D5, 0xE191, 0x91D6, 0xE192, 0x91D7, 0xE193, 0x91D8, 0xE194, 0x91D9, 0xE195, 0x91DA, 0xE196, + 0x91DB, 0xE197, 0x91DC, 0xB8AA, 0x91DD, 0xE198, 0x91DE, 0xE199, 0x91DF, 0xE19A, 0x91E0, 0xE19B, 0x91E1, 0xE19C, 0x91E2, 0xE19D, + 0x91E3, 0xE19E, 0x91E4, 0xE19F, 0x91E5, 0xE1A0, 0x91E6, 0xE240, 0x91E7, 0xE241, 0x91E8, 0xE242, 0x91E9, 0xE243, 0x91EA, 0xE244, + 0x91EB, 0xE245, 0x91EC, 0xE246, 0x91ED, 0xE247, 0x91EE, 0xE248, 0x91EF, 0xE249, 0x91F0, 0xE24A, 0x91F1, 0xE24B, 0x91F2, 0xE24C, + 0x91F3, 0xE24D, 0x91F4, 0xE24E, 0x91F5, 0xE24F, 0x91F6, 0xE250, 0x91F7, 0xE251, 0x91F8, 0xE252, 0x91F9, 0xE253, 0x91FA, 0xE254, + 0x91FB, 0xE255, 0x91FC, 0xE256, 0x91FD, 0xE257, 0x91FE, 0xE258, 0x91FF, 0xE259, 0x9200, 0xE25A, 0x9201, 0xE25B, 0x9202, 0xE25C, + 0x9203, 0xE25D, 0x9204, 0xE25E, 0x9205, 0xE25F, 0x9206, 0xE260, 0x9207, 0xE261, 0x9208, 0xE262, 0x9209, 0xE263, 0x920A, 0xE264, + 0x920B, 0xE265, 0x920C, 0xE266, 0x920D, 0xE267, 0x920E, 0xE268, 0x920F, 0xE269, 0x9210, 0xE26A, 0x9211, 0xE26B, 0x9212, 0xE26C, + 0x9213, 0xE26D, 0x9214, 0xE26E, 0x9215, 0xE26F, 0x9216, 0xE270, 0x9217, 0xE271, 0x9218, 0xE272, 0x9219, 0xE273, 0x921A, 0xE274, + 0x921B, 0xE275, 0x921C, 0xE276, 0x921D, 0xE277, 0x921E, 0xE278, 0x921F, 0xE279, 0x9220, 0xE27A, 0x9221, 0xE27B, 0x9222, 0xE27C, + 0x9223, 0xE27D, 0x9224, 0xE27E, 0x9225, 0xE280, 0x9226, 0xE281, 0x9227, 0xE282, 0x9228, 0xE283, 0x9229, 0xE284, 0x922A, 0xE285, + 0x922B, 0xE286, 0x922C, 0xE287, 0x922D, 0xE288, 0x922E, 0xE289, 0x922F, 0xE28A, 0x9230, 0xE28B, 0x9231, 0xE28C, 0x9232, 0xE28D, + 0x9233, 0xE28E, 0x9234, 0xE28F, 0x9235, 0xE290, 0x9236, 0xE291, 0x9237, 0xE292, 0x9238, 0xE293, 0x9239, 0xE294, 0x923A, 0xE295, + 0x923B, 0xE296, 0x923C, 0xE297, 0x923D, 0xE298, 0x923E, 0xE299, 0x923F, 0xE29A, 0x9240, 0xE29B, 0x9241, 0xE29C, 0x9242, 0xE29D, + 0x9243, 0xE29E, 0x9244, 0xE29F, 0x9245, 0xE2A0, 0x9246, 0xE340, 0x9247, 0xE341, 0x9248, 0xE342, 0x9249, 0xE343, 0x924A, 0xE344, + 0x924B, 0xE345, 0x924C, 0xE346, 0x924D, 0xE347, 0x924E, 0xE348, 0x924F, 0xE349, 0x9250, 0xE34A, 0x9251, 0xE34B, 0x9252, 0xE34C, + 0x9253, 0xE34D, 0x9254, 0xE34E, 0x9255, 0xE34F, 0x9256, 0xE350, 0x9257, 0xE351, 0x9258, 0xE352, 0x9259, 0xE353, 0x925A, 0xE354, + 0x925B, 0xE355, 0x925C, 0xE356, 0x925D, 0xE357, 0x925E, 0xE358, 0x925F, 0xE359, 0x9260, 0xE35A, 0x9261, 0xE35B, 0x9262, 0xE35C, + 0x9263, 0xE35D, 0x9264, 0xE35E, 0x9265, 0xE35F, 0x9266, 0xE360, 0x9267, 0xE361, 0x9268, 0xE362, 0x9269, 0xE363, 0x926A, 0xE364, + 0x926B, 0xE365, 0x926C, 0xE366, 0x926D, 0xE367, 0x926E, 0xE368, 0x926F, 0xE369, 0x9270, 0xE36A, 0x9271, 0xE36B, 0x9272, 0xE36C, + 0x9273, 0xE36D, 0x9274, 0xBCF8, 0x9275, 0xE36E, 0x9276, 0xE36F, 0x9277, 0xE370, 0x9278, 0xE371, 0x9279, 0xE372, 0x927A, 0xE373, + 0x927B, 0xE374, 0x927C, 0xE375, 0x927D, 0xE376, 0x927E, 0xE377, 0x927F, 0xE378, 0x9280, 0xE379, 0x9281, 0xE37A, 0x9282, 0xE37B, + 0x9283, 0xE37C, 0x9284, 0xE37D, 0x9285, 0xE37E, 0x9286, 0xE380, 0x9287, 0xE381, 0x9288, 0xE382, 0x9289, 0xE383, 0x928A, 0xE384, + 0x928B, 0xE385, 0x928C, 0xE386, 0x928D, 0xE387, 0x928E, 0xF6C6, 0x928F, 0xE388, 0x9290, 0xE389, 0x9291, 0xE38A, 0x9292, 0xE38B, + 0x9293, 0xE38C, 0x9294, 0xE38D, 0x9295, 0xE38E, 0x9296, 0xE38F, 0x9297, 0xE390, 0x9298, 0xE391, 0x9299, 0xE392, 0x929A, 0xE393, + 0x929B, 0xE394, 0x929C, 0xE395, 0x929D, 0xE396, 0x929E, 0xE397, 0x929F, 0xE398, 0x92A0, 0xE399, 0x92A1, 0xE39A, 0x92A2, 0xE39B, + 0x92A3, 0xE39C, 0x92A4, 0xE39D, 0x92A5, 0xE39E, 0x92A6, 0xE39F, 0x92A7, 0xE3A0, 0x92A8, 0xE440, 0x92A9, 0xE441, 0x92AA, 0xE442, + 0x92AB, 0xE443, 0x92AC, 0xE444, 0x92AD, 0xE445, 0x92AE, 0xF6C7, 0x92AF, 0xE446, 0x92B0, 0xE447, 0x92B1, 0xE448, 0x92B2, 0xE449, + 0x92B3, 0xE44A, 0x92B4, 0xE44B, 0x92B5, 0xE44C, 0x92B6, 0xE44D, 0x92B7, 0xE44E, 0x92B8, 0xE44F, 0x92B9, 0xE450, 0x92BA, 0xE451, + 0x92BB, 0xE452, 0x92BC, 0xE453, 0x92BD, 0xE454, 0x92BE, 0xE455, 0x92BF, 0xE456, 0x92C0, 0xE457, 0x92C1, 0xE458, 0x92C2, 0xE459, + 0x92C3, 0xE45A, 0x92C4, 0xE45B, 0x92C5, 0xE45C, 0x92C6, 0xE45D, 0x92C7, 0xE45E, 0x92C8, 0xF6C8, 0x92C9, 0xE45F, 0x92CA, 0xE460, + 0x92CB, 0xE461, 0x92CC, 0xE462, 0x92CD, 0xE463, 0x92CE, 0xE464, 0x92CF, 0xE465, 0x92D0, 0xE466, 0x92D1, 0xE467, 0x92D2, 0xE468, + 0x92D3, 0xE469, 0x92D4, 0xE46A, 0x92D5, 0xE46B, 0x92D6, 0xE46C, 0x92D7, 0xE46D, 0x92D8, 0xE46E, 0x92D9, 0xE46F, 0x92DA, 0xE470, + 0x92DB, 0xE471, 0x92DC, 0xE472, 0x92DD, 0xE473, 0x92DE, 0xE474, 0x92DF, 0xE475, 0x92E0, 0xE476, 0x92E1, 0xE477, 0x92E2, 0xE478, + 0x92E3, 0xE479, 0x92E4, 0xE47A, 0x92E5, 0xE47B, 0x92E6, 0xE47C, 0x92E7, 0xE47D, 0x92E8, 0xE47E, 0x92E9, 0xE480, 0x92EA, 0xE481, + 0x92EB, 0xE482, 0x92EC, 0xE483, 0x92ED, 0xE484, 0x92EE, 0xE485, 0x92EF, 0xE486, 0x92F0, 0xE487, 0x92F1, 0xE488, 0x92F2, 0xE489, + 0x92F3, 0xE48A, 0x92F4, 0xE48B, 0x92F5, 0xE48C, 0x92F6, 0xE48D, 0x92F7, 0xE48E, 0x92F8, 0xE48F, 0x92F9, 0xE490, 0x92FA, 0xE491, + 0x92FB, 0xE492, 0x92FC, 0xE493, 0x92FD, 0xE494, 0x92FE, 0xE495, 0x92FF, 0xE496, 0x9300, 0xE497, 0x9301, 0xE498, 0x9302, 0xE499, + 0x9303, 0xE49A, 0x9304, 0xE49B, 0x9305, 0xE49C, 0x9306, 0xE49D, 0x9307, 0xE49E, 0x9308, 0xE49F, 0x9309, 0xE4A0, 0x930A, 0xE540, + 0x930B, 0xE541, 0x930C, 0xE542, 0x930D, 0xE543, 0x930E, 0xE544, 0x930F, 0xE545, 0x9310, 0xE546, 0x9311, 0xE547, 0x9312, 0xE548, + 0x9313, 0xE549, 0x9314, 0xE54A, 0x9315, 0xE54B, 0x9316, 0xE54C, 0x9317, 0xE54D, 0x9318, 0xE54E, 0x9319, 0xE54F, 0x931A, 0xE550, + 0x931B, 0xE551, 0x931C, 0xE552, 0x931D, 0xE553, 0x931E, 0xE554, 0x931F, 0xE555, 0x9320, 0xE556, 0x9321, 0xE557, 0x9322, 0xE558, + 0x9323, 0xE559, 0x9324, 0xE55A, 0x9325, 0xE55B, 0x9326, 0xE55C, 0x9327, 0xE55D, 0x9328, 0xE55E, 0x9329, 0xE55F, 0x932A, 0xE560, + 0x932B, 0xE561, 0x932C, 0xE562, 0x932D, 0xE563, 0x932E, 0xE564, 0x932F, 0xE565, 0x9330, 0xE566, 0x9331, 0xE567, 0x9332, 0xE568, + 0x9333, 0xE569, 0x9334, 0xE56A, 0x9335, 0xE56B, 0x9336, 0xE56C, 0x9337, 0xE56D, 0x9338, 0xE56E, 0x9339, 0xE56F, 0x933A, 0xE570, + 0x933B, 0xE571, 0x933C, 0xE572, 0x933D, 0xE573, 0x933E, 0xF6C9, 0x933F, 0xE574, 0x9340, 0xE575, 0x9341, 0xE576, 0x9342, 0xE577, + 0x9343, 0xE578, 0x9344, 0xE579, 0x9345, 0xE57A, 0x9346, 0xE57B, 0x9347, 0xE57C, 0x9348, 0xE57D, 0x9349, 0xE57E, 0x934A, 0xE580, + 0x934B, 0xE581, 0x934C, 0xE582, 0x934D, 0xE583, 0x934E, 0xE584, 0x934F, 0xE585, 0x9350, 0xE586, 0x9351, 0xE587, 0x9352, 0xE588, + 0x9353, 0xE589, 0x9354, 0xE58A, 0x9355, 0xE58B, 0x9356, 0xE58C, 0x9357, 0xE58D, 0x9358, 0xE58E, 0x9359, 0xE58F, 0x935A, 0xE590, + 0x935B, 0xE591, 0x935C, 0xE592, 0x935D, 0xE593, 0x935E, 0xE594, 0x935F, 0xE595, 0x9360, 0xE596, 0x9361, 0xE597, 0x9362, 0xE598, + 0x9363, 0xE599, 0x9364, 0xE59A, 0x9365, 0xE59B, 0x9366, 0xE59C, 0x9367, 0xE59D, 0x9368, 0xE59E, 0x9369, 0xE59F, 0x936A, 0xF6CA, + 0x936B, 0xE5A0, 0x936C, 0xE640, 0x936D, 0xE641, 0x936E, 0xE642, 0x936F, 0xE643, 0x9370, 0xE644, 0x9371, 0xE645, 0x9372, 0xE646, + 0x9373, 0xE647, 0x9374, 0xE648, 0x9375, 0xE649, 0x9376, 0xE64A, 0x9377, 0xE64B, 0x9378, 0xE64C, 0x9379, 0xE64D, 0x937A, 0xE64E, + 0x937B, 0xE64F, 0x937C, 0xE650, 0x937D, 0xE651, 0x937E, 0xE652, 0x937F, 0xE653, 0x9380, 0xE654, 0x9381, 0xE655, 0x9382, 0xE656, + 0x9383, 0xE657, 0x9384, 0xE658, 0x9385, 0xE659, 0x9386, 0xE65A, 0x9387, 0xE65B, 0x9388, 0xE65C, 0x9389, 0xE65D, 0x938A, 0xE65E, + 0x938B, 0xE65F, 0x938C, 0xE660, 0x938D, 0xE661, 0x938E, 0xE662, 0x938F, 0xF6CC, 0x9390, 0xE663, 0x9391, 0xE664, 0x9392, 0xE665, + 0x9393, 0xE666, 0x9394, 0xE667, 0x9395, 0xE668, 0x9396, 0xE669, 0x9397, 0xE66A, 0x9398, 0xE66B, 0x9399, 0xE66C, 0x939A, 0xE66D, + 0x939B, 0xE66E, 0x939C, 0xE66F, 0x939D, 0xE670, 0x939E, 0xE671, 0x939F, 0xE672, 0x93A0, 0xE673, 0x93A1, 0xE674, 0x93A2, 0xE675, + 0x93A3, 0xE676, 0x93A4, 0xE677, 0x93A5, 0xE678, 0x93A6, 0xE679, 0x93A7, 0xE67A, 0x93A8, 0xE67B, 0x93A9, 0xE67C, 0x93AA, 0xE67D, + 0x93AB, 0xE67E, 0x93AC, 0xE680, 0x93AD, 0xE681, 0x93AE, 0xE682, 0x93AF, 0xE683, 0x93B0, 0xE684, 0x93B1, 0xE685, 0x93B2, 0xE686, + 0x93B3, 0xE687, 0x93B4, 0xE688, 0x93B5, 0xE689, 0x93B6, 0xE68A, 0x93B7, 0xE68B, 0x93B8, 0xE68C, 0x93B9, 0xE68D, 0x93BA, 0xE68E, + 0x93BB, 0xE68F, 0x93BC, 0xE690, 0x93BD, 0xE691, 0x93BE, 0xE692, 0x93BF, 0xE693, 0x93C0, 0xE694, 0x93C1, 0xE695, 0x93C2, 0xE696, + 0x93C3, 0xE697, 0x93C4, 0xE698, 0x93C5, 0xE699, 0x93C6, 0xE69A, 0x93C7, 0xE69B, 0x93C8, 0xE69C, 0x93C9, 0xE69D, 0x93CA, 0xF6CB, + 0x93CB, 0xE69E, 0x93CC, 0xE69F, 0x93CD, 0xE6A0, 0x93CE, 0xE740, 0x93CF, 0xE741, 0x93D0, 0xE742, 0x93D1, 0xE743, 0x93D2, 0xE744, + 0x93D3, 0xE745, 0x93D4, 0xE746, 0x93D5, 0xE747, 0x93D6, 0xF7E9, 0x93D7, 0xE748, 0x93D8, 0xE749, 0x93D9, 0xE74A, 0x93DA, 0xE74B, + 0x93DB, 0xE74C, 0x93DC, 0xE74D, 0x93DD, 0xE74E, 0x93DE, 0xE74F, 0x93DF, 0xE750, 0x93E0, 0xE751, 0x93E1, 0xE752, 0x93E2, 0xE753, + 0x93E3, 0xE754, 0x93E4, 0xE755, 0x93E5, 0xE756, 0x93E6, 0xE757, 0x93E7, 0xE758, 0x93E8, 0xE759, 0x93E9, 0xE75A, 0x93EA, 0xE75B, + 0x93EB, 0xE75C, 0x93EC, 0xE75D, 0x93ED, 0xE75E, 0x93EE, 0xE75F, 0x93EF, 0xE760, 0x93F0, 0xE761, 0x93F1, 0xE762, 0x93F2, 0xE763, + 0x93F3, 0xE764, 0x93F4, 0xE765, 0x93F5, 0xE766, 0x93F6, 0xE767, 0x93F7, 0xE768, 0x93F8, 0xE769, 0x93F9, 0xE76A, 0x93FA, 0xE76B, + 0x93FB, 0xE76C, 0x93FC, 0xE76D, 0x93FD, 0xE76E, 0x93FE, 0xE76F, 0x93FF, 0xE770, 0x9400, 0xE771, 0x9401, 0xE772, 0x9402, 0xE773, + 0x9403, 0xE774, 0x9404, 0xE775, 0x9405, 0xE776, 0x9406, 0xE777, 0x9407, 0xE778, 0x9408, 0xE779, 0x9409, 0xE77A, 0x940A, 0xE77B, + 0x940B, 0xE77C, 0x940C, 0xE77D, 0x940D, 0xE77E, 0x940E, 0xE780, 0x940F, 0xE781, 0x9410, 0xE782, 0x9411, 0xE783, 0x9412, 0xE784, + 0x9413, 0xE785, 0x9414, 0xE786, 0x9415, 0xE787, 0x9416, 0xE788, 0x9417, 0xE789, 0x9418, 0xE78A, 0x9419, 0xE78B, 0x941A, 0xE78C, + 0x941B, 0xE78D, 0x941C, 0xE78E, 0x941D, 0xE78F, 0x941E, 0xE790, 0x941F, 0xE791, 0x9420, 0xE792, 0x9421, 0xE793, 0x9422, 0xE794, + 0x9423, 0xE795, 0x9424, 0xE796, 0x9425, 0xE797, 0x9426, 0xE798, 0x9427, 0xE799, 0x9428, 0xE79A, 0x9429, 0xE79B, 0x942A, 0xE79C, + 0x942B, 0xE79D, 0x942C, 0xE79E, 0x942D, 0xE79F, 0x942E, 0xE7A0, 0x942F, 0xE840, 0x9430, 0xE841, 0x9431, 0xE842, 0x9432, 0xE843, + 0x9433, 0xE844, 0x9434, 0xE845, 0x9435, 0xE846, 0x9436, 0xE847, 0x9437, 0xE848, 0x9438, 0xE849, 0x9439, 0xE84A, 0x943A, 0xE84B, + 0x943B, 0xE84C, 0x943C, 0xE84D, 0x943D, 0xE84E, 0x943E, 0xF6CD, 0x943F, 0xE84F, 0x9440, 0xE850, 0x9441, 0xE851, 0x9442, 0xE852, + 0x9443, 0xE853, 0x9444, 0xE854, 0x9445, 0xE855, 0x9446, 0xE856, 0x9447, 0xE857, 0x9448, 0xE858, 0x9449, 0xE859, 0x944A, 0xE85A, + 0x944B, 0xE85B, 0x944C, 0xE85C, 0x944D, 0xE85D, 0x944E, 0xE85E, 0x944F, 0xE85F, 0x9450, 0xE860, 0x9451, 0xE861, 0x9452, 0xE862, + 0x9453, 0xE863, 0x9454, 0xE864, 0x9455, 0xE865, 0x9456, 0xE866, 0x9457, 0xE867, 0x9458, 0xE868, 0x9459, 0xE869, 0x945A, 0xE86A, + 0x945B, 0xE86B, 0x945C, 0xE86C, 0x945D, 0xE86D, 0x945E, 0xE86E, 0x945F, 0xE86F, 0x9460, 0xE870, 0x9461, 0xE871, 0x9462, 0xE872, + 0x9463, 0xE873, 0x9464, 0xE874, 0x9465, 0xE875, 0x9466, 0xE876, 0x9467, 0xE877, 0x9468, 0xE878, 0x9469, 0xE879, 0x946A, 0xE87A, + 0x946B, 0xF6CE, 0x946C, 0xE87B, 0x946D, 0xE87C, 0x946E, 0xE87D, 0x946F, 0xE87E, 0x9470, 0xE880, 0x9471, 0xE881, 0x9472, 0xE882, + 0x9473, 0xE883, 0x9474, 0xE884, 0x9475, 0xE885, 0x9476, 0xE886, 0x9477, 0xE887, 0x9478, 0xE888, 0x9479, 0xE889, 0x947A, 0xE88A, + 0x947B, 0xE88B, 0x947C, 0xE88C, 0x947D, 0xE88D, 0x947E, 0xE88E, 0x947F, 0xE88F, 0x9480, 0xE890, 0x9481, 0xE891, 0x9482, 0xE892, + 0x9483, 0xE893, 0x9484, 0xE894, 0x9485, 0xEEC4, 0x9486, 0xEEC5, 0x9487, 0xEEC6, 0x9488, 0xD5EB, 0x9489, 0xB6A4, 0x948A, 0xEEC8, + 0x948B, 0xEEC7, 0x948C, 0xEEC9, 0x948D, 0xEECA, 0x948E, 0xC7A5, 0x948F, 0xEECB, 0x9490, 0xEECC, 0x9491, 0xE895, 0x9492, 0xB7B0, + 0x9493, 0xB5F6, 0x9494, 0xEECD, 0x9495, 0xEECF, 0x9496, 0xE896, 0x9497, 0xEECE, 0x9498, 0xE897, 0x9499, 0xB8C6, 0x949A, 0xEED0, + 0x949B, 0xEED1, 0x949C, 0xEED2, 0x949D, 0xB6DB, 0x949E, 0xB3AE, 0x949F, 0xD6D3, 0x94A0, 0xC4C6, 0x94A1, 0xB1B5, 0x94A2, 0xB8D6, + 0x94A3, 0xEED3, 0x94A4, 0xEED4, 0x94A5, 0xD4BF, 0x94A6, 0xC7D5, 0x94A7, 0xBEFB, 0x94A8, 0xCED9, 0x94A9, 0xB9B3, 0x94AA, 0xEED6, + 0x94AB, 0xEED5, 0x94AC, 0xEED8, 0x94AD, 0xEED7, 0x94AE, 0xC5A5, 0x94AF, 0xEED9, 0x94B0, 0xEEDA, 0x94B1, 0xC7AE, 0x94B2, 0xEEDB, + 0x94B3, 0xC7AF, 0x94B4, 0xEEDC, 0x94B5, 0xB2A7, 0x94B6, 0xEEDD, 0x94B7, 0xEEDE, 0x94B8, 0xEEDF, 0x94B9, 0xEEE0, 0x94BA, 0xEEE1, + 0x94BB, 0xD7EA, 0x94BC, 0xEEE2, 0x94BD, 0xEEE3, 0x94BE, 0xBCD8, 0x94BF, 0xEEE4, 0x94C0, 0xD3CB, 0x94C1, 0xCCFA, 0x94C2, 0xB2AC, + 0x94C3, 0xC1E5, 0x94C4, 0xEEE5, 0x94C5, 0xC7A6, 0x94C6, 0xC3AD, 0x94C7, 0xE898, 0x94C8, 0xEEE6, 0x94C9, 0xEEE7, 0x94CA, 0xEEE8, + 0x94CB, 0xEEE9, 0x94CC, 0xEEEA, 0x94CD, 0xEEEB, 0x94CE, 0xEEEC, 0x94CF, 0xE899, 0x94D0, 0xEEED, 0x94D1, 0xEEEE, 0x94D2, 0xEEEF, + 0x94D3, 0xE89A, 0x94D4, 0xE89B, 0x94D5, 0xEEF0, 0x94D6, 0xEEF1, 0x94D7, 0xEEF2, 0x94D8, 0xEEF4, 0x94D9, 0xEEF3, 0x94DA, 0xE89C, + 0x94DB, 0xEEF5, 0x94DC, 0xCDAD, 0x94DD, 0xC2C1, 0x94DE, 0xEEF6, 0x94DF, 0xEEF7, 0x94E0, 0xEEF8, 0x94E1, 0xD5A1, 0x94E2, 0xEEF9, + 0x94E3, 0xCFB3, 0x94E4, 0xEEFA, 0x94E5, 0xEEFB, 0x94E6, 0xE89D, 0x94E7, 0xEEFC, 0x94E8, 0xEEFD, 0x94E9, 0xEFA1, 0x94EA, 0xEEFE, + 0x94EB, 0xEFA2, 0x94EC, 0xB8F5, 0x94ED, 0xC3FA, 0x94EE, 0xEFA3, 0x94EF, 0xEFA4, 0x94F0, 0xBDC2, 0x94F1, 0xD2BF, 0x94F2, 0xB2F9, + 0x94F3, 0xEFA5, 0x94F4, 0xEFA6, 0x94F5, 0xEFA7, 0x94F6, 0xD2F8, 0x94F7, 0xEFA8, 0x94F8, 0xD6FD, 0x94F9, 0xEFA9, 0x94FA, 0xC6CC, + 0x94FB, 0xE89E, 0x94FC, 0xEFAA, 0x94FD, 0xEFAB, 0x94FE, 0xC1B4, 0x94FF, 0xEFAC, 0x9500, 0xCFFA, 0x9501, 0xCBF8, 0x9502, 0xEFAE, + 0x9503, 0xEFAD, 0x9504, 0xB3FA, 0x9505, 0xB9F8, 0x9506, 0xEFAF, 0x9507, 0xEFB0, 0x9508, 0xD0E2, 0x9509, 0xEFB1, 0x950A, 0xEFB2, + 0x950B, 0xB7E6, 0x950C, 0xD0BF, 0x950D, 0xEFB3, 0x950E, 0xEFB4, 0x950F, 0xEFB5, 0x9510, 0xC8F1, 0x9511, 0xCCE0, 0x9512, 0xEFB6, + 0x9513, 0xEFB7, 0x9514, 0xEFB8, 0x9515, 0xEFB9, 0x9516, 0xEFBA, 0x9517, 0xD5E0, 0x9518, 0xEFBB, 0x9519, 0xB4ED, 0x951A, 0xC3AA, + 0x951B, 0xEFBC, 0x951C, 0xE89F, 0x951D, 0xEFBD, 0x951E, 0xEFBE, 0x951F, 0xEFBF, 0x9520, 0xE8A0, 0x9521, 0xCEFD, 0x9522, 0xEFC0, + 0x9523, 0xC2E0, 0x9524, 0xB4B8, 0x9525, 0xD7B6, 0x9526, 0xBDF5, 0x9527, 0xE940, 0x9528, 0xCFC7, 0x9529, 0xEFC3, 0x952A, 0xEFC1, + 0x952B, 0xEFC2, 0x952C, 0xEFC4, 0x952D, 0xB6A7, 0x952E, 0xBCFC, 0x952F, 0xBEE2, 0x9530, 0xC3CC, 0x9531, 0xEFC5, 0x9532, 0xEFC6, + 0x9533, 0xE941, 0x9534, 0xEFC7, 0x9535, 0xEFCF, 0x9536, 0xEFC8, 0x9537, 0xEFC9, 0x9538, 0xEFCA, 0x9539, 0xC7C2, 0x953A, 0xEFF1, + 0x953B, 0xB6CD, 0x953C, 0xEFCB, 0x953D, 0xE942, 0x953E, 0xEFCC, 0x953F, 0xEFCD, 0x9540, 0xB6C6, 0x9541, 0xC3BE, 0x9542, 0xEFCE, + 0x9543, 0xE943, 0x9544, 0xEFD0, 0x9545, 0xEFD1, 0x9546, 0xEFD2, 0x9547, 0xD5F2, 0x9548, 0xE944, 0x9549, 0xEFD3, 0x954A, 0xC4F7, + 0x954B, 0xE945, 0x954C, 0xEFD4, 0x954D, 0xC4F8, 0x954E, 0xEFD5, 0x954F, 0xEFD6, 0x9550, 0xB8E4, 0x9551, 0xB0F7, 0x9552, 0xEFD7, + 0x9553, 0xEFD8, 0x9554, 0xEFD9, 0x9555, 0xE946, 0x9556, 0xEFDA, 0x9557, 0xEFDB, 0x9558, 0xEFDC, 0x9559, 0xEFDD, 0x955A, 0xE947, + 0x955B, 0xEFDE, 0x955C, 0xBEB5, 0x955D, 0xEFE1, 0x955E, 0xEFDF, 0x955F, 0xEFE0, 0x9560, 0xE948, 0x9561, 0xEFE2, 0x9562, 0xEFE3, + 0x9563, 0xC1CD, 0x9564, 0xEFE4, 0x9565, 0xEFE5, 0x9566, 0xEFE6, 0x9567, 0xEFE7, 0x9568, 0xEFE8, 0x9569, 0xEFE9, 0x956A, 0xEFEA, + 0x956B, 0xEFEB, 0x956C, 0xEFEC, 0x956D, 0xC0D8, 0x956E, 0xE949, 0x956F, 0xEFED, 0x9570, 0xC1AD, 0x9571, 0xEFEE, 0x9572, 0xEFEF, + 0x9573, 0xEFF0, 0x9574, 0xE94A, 0x9575, 0xE94B, 0x9576, 0xCFE2, 0x9577, 0xE94C, 0x9578, 0xE94D, 0x9579, 0xE94E, 0x957A, 0xE94F, + 0x957B, 0xE950, 0x957C, 0xE951, 0x957D, 0xE952, 0x957E, 0xE953, 0x957F, 0xB3A4, 0x9580, 0xE954, 0x9581, 0xE955, 0x9582, 0xE956, + 0x9583, 0xE957, 0x9584, 0xE958, 0x9585, 0xE959, 0x9586, 0xE95A, 0x9587, 0xE95B, 0x9588, 0xE95C, 0x9589, 0xE95D, 0x958A, 0xE95E, + 0x958B, 0xE95F, 0x958C, 0xE960, 0x958D, 0xE961, 0x958E, 0xE962, 0x958F, 0xE963, 0x9590, 0xE964, 0x9591, 0xE965, 0x9592, 0xE966, + 0x9593, 0xE967, 0x9594, 0xE968, 0x9595, 0xE969, 0x9596, 0xE96A, 0x9597, 0xE96B, 0x9598, 0xE96C, 0x9599, 0xE96D, 0x959A, 0xE96E, + 0x959B, 0xE96F, 0x959C, 0xE970, 0x959D, 0xE971, 0x959E, 0xE972, 0x959F, 0xE973, 0x95A0, 0xE974, 0x95A1, 0xE975, 0x95A2, 0xE976, + 0x95A3, 0xE977, 0x95A4, 0xE978, 0x95A5, 0xE979, 0x95A6, 0xE97A, 0x95A7, 0xE97B, 0x95A8, 0xE97C, 0x95A9, 0xE97D, 0x95AA, 0xE97E, + 0x95AB, 0xE980, 0x95AC, 0xE981, 0x95AD, 0xE982, 0x95AE, 0xE983, 0x95AF, 0xE984, 0x95B0, 0xE985, 0x95B1, 0xE986, 0x95B2, 0xE987, + 0x95B3, 0xE988, 0x95B4, 0xE989, 0x95B5, 0xE98A, 0x95B6, 0xE98B, 0x95B7, 0xE98C, 0x95B8, 0xE98D, 0x95B9, 0xE98E, 0x95BA, 0xE98F, + 0x95BB, 0xE990, 0x95BC, 0xE991, 0x95BD, 0xE992, 0x95BE, 0xE993, 0x95BF, 0xE994, 0x95C0, 0xE995, 0x95C1, 0xE996, 0x95C2, 0xE997, + 0x95C3, 0xE998, 0x95C4, 0xE999, 0x95C5, 0xE99A, 0x95C6, 0xE99B, 0x95C7, 0xE99C, 0x95C8, 0xE99D, 0x95C9, 0xE99E, 0x95CA, 0xE99F, + 0x95CB, 0xE9A0, 0x95CC, 0xEA40, 0x95CD, 0xEA41, 0x95CE, 0xEA42, 0x95CF, 0xEA43, 0x95D0, 0xEA44, 0x95D1, 0xEA45, 0x95D2, 0xEA46, + 0x95D3, 0xEA47, 0x95D4, 0xEA48, 0x95D5, 0xEA49, 0x95D6, 0xEA4A, 0x95D7, 0xEA4B, 0x95D8, 0xEA4C, 0x95D9, 0xEA4D, 0x95DA, 0xEA4E, + 0x95DB, 0xEA4F, 0x95DC, 0xEA50, 0x95DD, 0xEA51, 0x95DE, 0xEA52, 0x95DF, 0xEA53, 0x95E0, 0xEA54, 0x95E1, 0xEA55, 0x95E2, 0xEA56, + 0x95E3, 0xEA57, 0x95E4, 0xEA58, 0x95E5, 0xEA59, 0x95E6, 0xEA5A, 0x95E7, 0xEA5B, 0x95E8, 0xC3C5, 0x95E9, 0xE3C5, 0x95EA, 0xC9C1, + 0x95EB, 0xE3C6, 0x95EC, 0xEA5C, 0x95ED, 0xB1D5, 0x95EE, 0xCECA, 0x95EF, 0xB4B3, 0x95F0, 0xC8F2, 0x95F1, 0xE3C7, 0x95F2, 0xCFD0, + 0x95F3, 0xE3C8, 0x95F4, 0xBCE4, 0x95F5, 0xE3C9, 0x95F6, 0xE3CA, 0x95F7, 0xC3C6, 0x95F8, 0xD5A2, 0x95F9, 0xC4D6, 0x95FA, 0xB9EB, + 0x95FB, 0xCEC5, 0x95FC, 0xE3CB, 0x95FD, 0xC3F6, 0x95FE, 0xE3CC, 0x95FF, 0xEA5D, 0x9600, 0xB7A7, 0x9601, 0xB8F3, 0x9602, 0xBAD2, + 0x9603, 0xE3CD, 0x9604, 0xE3CE, 0x9605, 0xD4C4, 0x9606, 0xE3CF, 0x9607, 0xEA5E, 0x9608, 0xE3D0, 0x9609, 0xD1CB, 0x960A, 0xE3D1, + 0x960B, 0xE3D2, 0x960C, 0xE3D3, 0x960D, 0xE3D4, 0x960E, 0xD1D6, 0x960F, 0xE3D5, 0x9610, 0xB2FB, 0x9611, 0xC0BB, 0x9612, 0xE3D6, + 0x9613, 0xEA5F, 0x9614, 0xC0AB, 0x9615, 0xE3D7, 0x9616, 0xE3D8, 0x9617, 0xE3D9, 0x9618, 0xEA60, 0x9619, 0xE3DA, 0x961A, 0xE3DB, + 0x961B, 0xEA61, 0x961C, 0xB8B7, 0x961D, 0xDAE2, 0x961E, 0xEA62, 0x961F, 0xB6D3, 0x9620, 0xEA63, 0x9621, 0xDAE4, 0x9622, 0xDAE3, + 0x9623, 0xEA64, 0x9624, 0xEA65, 0x9625, 0xEA66, 0x9626, 0xEA67, 0x9627, 0xEA68, 0x9628, 0xEA69, 0x9629, 0xEA6A, 0x962A, 0xDAE6, + 0x962B, 0xEA6B, 0x962C, 0xEA6C, 0x962D, 0xEA6D, 0x962E, 0xC8EE, 0x962F, 0xEA6E, 0x9630, 0xEA6F, 0x9631, 0xDAE5, 0x9632, 0xB7C0, + 0x9633, 0xD1F4, 0x9634, 0xD2F5, 0x9635, 0xD5F3, 0x9636, 0xBDD7, 0x9637, 0xEA70, 0x9638, 0xEA71, 0x9639, 0xEA72, 0x963A, 0xEA73, + 0x963B, 0xD7E8, 0x963C, 0xDAE8, 0x963D, 0xDAE7, 0x963E, 0xEA74, 0x963F, 0xB0A2, 0x9640, 0xCDD3, 0x9641, 0xEA75, 0x9642, 0xDAE9, + 0x9643, 0xEA76, 0x9644, 0xB8BD, 0x9645, 0xBCCA, 0x9646, 0xC2BD, 0x9647, 0xC2A4, 0x9648, 0xB3C2, 0x9649, 0xDAEA, 0x964A, 0xEA77, + 0x964B, 0xC2AA, 0x964C, 0xC4B0, 0x964D, 0xBDB5, 0x964E, 0xEA78, 0x964F, 0xEA79, 0x9650, 0xCFDE, 0x9651, 0xEA7A, 0x9652, 0xEA7B, + 0x9653, 0xEA7C, 0x9654, 0xDAEB, 0x9655, 0xC9C2, 0x9656, 0xEA7D, 0x9657, 0xEA7E, 0x9658, 0xEA80, 0x9659, 0xEA81, 0x965A, 0xEA82, + 0x965B, 0xB1DD, 0x965C, 0xEA83, 0x965D, 0xEA84, 0x965E, 0xEA85, 0x965F, 0xDAEC, 0x9660, 0xEA86, 0x9661, 0xB6B8, 0x9662, 0xD4BA, + 0x9663, 0xEA87, 0x9664, 0xB3FD, 0x9665, 0xEA88, 0x9666, 0xEA89, 0x9667, 0xDAED, 0x9668, 0xD4C9, 0x9669, 0xCFD5, 0x966A, 0xC5E3, + 0x966B, 0xEA8A, 0x966C, 0xDAEE, 0x966D, 0xEA8B, 0x966E, 0xEA8C, 0x966F, 0xEA8D, 0x9670, 0xEA8E, 0x9671, 0xEA8F, 0x9672, 0xDAEF, + 0x9673, 0xEA90, 0x9674, 0xDAF0, 0x9675, 0xC1EA, 0x9676, 0xCCD5, 0x9677, 0xCFDD, 0x9678, 0xEA91, 0x9679, 0xEA92, 0x967A, 0xEA93, + 0x967B, 0xEA94, 0x967C, 0xEA95, 0x967D, 0xEA96, 0x967E, 0xEA97, 0x967F, 0xEA98, 0x9680, 0xEA99, 0x9681, 0xEA9A, 0x9682, 0xEA9B, + 0x9683, 0xEA9C, 0x9684, 0xEA9D, 0x9685, 0xD3E7, 0x9686, 0xC2A1, 0x9687, 0xEA9E, 0x9688, 0xDAF1, 0x9689, 0xEA9F, 0x968A, 0xEAA0, + 0x968B, 0xCBE5, 0x968C, 0xEB40, 0x968D, 0xDAF2, 0x968E, 0xEB41, 0x968F, 0xCBE6, 0x9690, 0xD2FE, 0x9691, 0xEB42, 0x9692, 0xEB43, + 0x9693, 0xEB44, 0x9694, 0xB8F4, 0x9695, 0xEB45, 0x9696, 0xEB46, 0x9697, 0xDAF3, 0x9698, 0xB0AF, 0x9699, 0xCFB6, 0x969A, 0xEB47, + 0x969B, 0xEB48, 0x969C, 0xD5CF, 0x969D, 0xEB49, 0x969E, 0xEB4A, 0x969F, 0xEB4B, 0x96A0, 0xEB4C, 0x96A1, 0xEB4D, 0x96A2, 0xEB4E, + 0x96A3, 0xEB4F, 0x96A4, 0xEB50, 0x96A5, 0xEB51, 0x96A6, 0xEB52, 0x96A7, 0xCBED, 0x96A8, 0xEB53, 0x96A9, 0xEB54, 0x96AA, 0xEB55, + 0x96AB, 0xEB56, 0x96AC, 0xEB57, 0x96AD, 0xEB58, 0x96AE, 0xEB59, 0x96AF, 0xEB5A, 0x96B0, 0xDAF4, 0x96B1, 0xEB5B, 0x96B2, 0xEB5C, + 0x96B3, 0xE3C4, 0x96B4, 0xEB5D, 0x96B5, 0xEB5E, 0x96B6, 0xC1A5, 0x96B7, 0xEB5F, 0x96B8, 0xEB60, 0x96B9, 0xF6BF, 0x96BA, 0xEB61, + 0x96BB, 0xEB62, 0x96BC, 0xF6C0, 0x96BD, 0xF6C1, 0x96BE, 0xC4D1, 0x96BF, 0xEB63, 0x96C0, 0xC8B8, 0x96C1, 0xD1E3, 0x96C2, 0xEB64, + 0x96C3, 0xEB65, 0x96C4, 0xD0DB, 0x96C5, 0xD1C5, 0x96C6, 0xBCAF, 0x96C7, 0xB9CD, 0x96C8, 0xEB66, 0x96C9, 0xEFF4, 0x96CA, 0xEB67, + 0x96CB, 0xEB68, 0x96CC, 0xB4C6, 0x96CD, 0xD3BA, 0x96CE, 0xF6C2, 0x96CF, 0xB3FB, 0x96D0, 0xEB69, 0x96D1, 0xEB6A, 0x96D2, 0xF6C3, + 0x96D3, 0xEB6B, 0x96D4, 0xEB6C, 0x96D5, 0xB5F1, 0x96D6, 0xEB6D, 0x96D7, 0xEB6E, 0x96D8, 0xEB6F, 0x96D9, 0xEB70, 0x96DA, 0xEB71, + 0x96DB, 0xEB72, 0x96DC, 0xEB73, 0x96DD, 0xEB74, 0x96DE, 0xEB75, 0x96DF, 0xEB76, 0x96E0, 0xF6C5, 0x96E1, 0xEB77, 0x96E2, 0xEB78, + 0x96E3, 0xEB79, 0x96E4, 0xEB7A, 0x96E5, 0xEB7B, 0x96E6, 0xEB7C, 0x96E7, 0xEB7D, 0x96E8, 0xD3EA, 0x96E9, 0xF6A7, 0x96EA, 0xD1A9, + 0x96EB, 0xEB7E, 0x96EC, 0xEB80, 0x96ED, 0xEB81, 0x96EE, 0xEB82, 0x96EF, 0xF6A9, 0x96F0, 0xEB83, 0x96F1, 0xEB84, 0x96F2, 0xEB85, + 0x96F3, 0xF6A8, 0x96F4, 0xEB86, 0x96F5, 0xEB87, 0x96F6, 0xC1E3, 0x96F7, 0xC0D7, 0x96F8, 0xEB88, 0x96F9, 0xB1A2, 0x96FA, 0xEB89, + 0x96FB, 0xEB8A, 0x96FC, 0xEB8B, 0x96FD, 0xEB8C, 0x96FE, 0xCEED, 0x96FF, 0xEB8D, 0x9700, 0xD0E8, 0x9701, 0xF6AB, 0x9702, 0xEB8E, + 0x9703, 0xEB8F, 0x9704, 0xCFF6, 0x9705, 0xEB90, 0x9706, 0xF6AA, 0x9707, 0xD5F0, 0x9708, 0xF6AC, 0x9709, 0xC3B9, 0x970A, 0xEB91, + 0x970B, 0xEB92, 0x970C, 0xEB93, 0x970D, 0xBBF4, 0x970E, 0xF6AE, 0x970F, 0xF6AD, 0x9710, 0xEB94, 0x9711, 0xEB95, 0x9712, 0xEB96, + 0x9713, 0xC4DE, 0x9714, 0xEB97, 0x9715, 0xEB98, 0x9716, 0xC1D8, 0x9717, 0xEB99, 0x9718, 0xEB9A, 0x9719, 0xEB9B, 0x971A, 0xEB9C, + 0x971B, 0xEB9D, 0x971C, 0xCBAA, 0x971D, 0xEB9E, 0x971E, 0xCFBC, 0x971F, 0xEB9F, 0x9720, 0xEBA0, 0x9721, 0xEC40, 0x9722, 0xEC41, + 0x9723, 0xEC42, 0x9724, 0xEC43, 0x9725, 0xEC44, 0x9726, 0xEC45, 0x9727, 0xEC46, 0x9728, 0xEC47, 0x9729, 0xEC48, 0x972A, 0xF6AF, + 0x972B, 0xEC49, 0x972C, 0xEC4A, 0x972D, 0xF6B0, 0x972E, 0xEC4B, 0x972F, 0xEC4C, 0x9730, 0xF6B1, 0x9731, 0xEC4D, 0x9732, 0xC2B6, + 0x9733, 0xEC4E, 0x9734, 0xEC4F, 0x9735, 0xEC50, 0x9736, 0xEC51, 0x9737, 0xEC52, 0x9738, 0xB0D4, 0x9739, 0xC5F9, 0x973A, 0xEC53, + 0x973B, 0xEC54, 0x973C, 0xEC55, 0x973D, 0xEC56, 0x973E, 0xF6B2, 0x973F, 0xEC57, 0x9740, 0xEC58, 0x9741, 0xEC59, 0x9742, 0xEC5A, + 0x9743, 0xEC5B, 0x9744, 0xEC5C, 0x9745, 0xEC5D, 0x9746, 0xEC5E, 0x9747, 0xEC5F, 0x9748, 0xEC60, 0x9749, 0xEC61, 0x974A, 0xEC62, + 0x974B, 0xEC63, 0x974C, 0xEC64, 0x974D, 0xEC65, 0x974E, 0xEC66, 0x974F, 0xEC67, 0x9750, 0xEC68, 0x9751, 0xEC69, 0x9752, 0xC7E0, + 0x9753, 0xF6A6, 0x9754, 0xEC6A, 0x9755, 0xEC6B, 0x9756, 0xBEB8, 0x9757, 0xEC6C, 0x9758, 0xEC6D, 0x9759, 0xBEB2, 0x975A, 0xEC6E, + 0x975B, 0xB5E5, 0x975C, 0xEC6F, 0x975D, 0xEC70, 0x975E, 0xB7C7, 0x975F, 0xEC71, 0x9760, 0xBFBF, 0x9761, 0xC3D2, 0x9762, 0xC3E6, + 0x9763, 0xEC72, 0x9764, 0xEC73, 0x9765, 0xD8CC, 0x9766, 0xEC74, 0x9767, 0xEC75, 0x9768, 0xEC76, 0x9769, 0xB8EF, 0x976A, 0xEC77, + 0x976B, 0xEC78, 0x976C, 0xEC79, 0x976D, 0xEC7A, 0x976E, 0xEC7B, 0x976F, 0xEC7C, 0x9770, 0xEC7D, 0x9771, 0xEC7E, 0x9772, 0xEC80, + 0x9773, 0xBDF9, 0x9774, 0xD1A5, 0x9775, 0xEC81, 0x9776, 0xB0D0, 0x9777, 0xEC82, 0x9778, 0xEC83, 0x9779, 0xEC84, 0x977A, 0xEC85, + 0x977B, 0xEC86, 0x977C, 0xF7B0, 0x977D, 0xEC87, 0x977E, 0xEC88, 0x977F, 0xEC89, 0x9780, 0xEC8A, 0x9781, 0xEC8B, 0x9782, 0xEC8C, + 0x9783, 0xEC8D, 0x9784, 0xEC8E, 0x9785, 0xF7B1, 0x9786, 0xEC8F, 0x9787, 0xEC90, 0x9788, 0xEC91, 0x9789, 0xEC92, 0x978A, 0xEC93, + 0x978B, 0xD0AC, 0x978C, 0xEC94, 0x978D, 0xB0B0, 0x978E, 0xEC95, 0x978F, 0xEC96, 0x9790, 0xEC97, 0x9791, 0xF7B2, 0x9792, 0xF7B3, + 0x9793, 0xEC98, 0x9794, 0xF7B4, 0x9795, 0xEC99, 0x9796, 0xEC9A, 0x9797, 0xEC9B, 0x9798, 0xC7CA, 0x9799, 0xEC9C, 0x979A, 0xEC9D, + 0x979B, 0xEC9E, 0x979C, 0xEC9F, 0x979D, 0xECA0, 0x979E, 0xED40, 0x979F, 0xED41, 0x97A0, 0xBECF, 0x97A1, 0xED42, 0x97A2, 0xED43, + 0x97A3, 0xF7B7, 0x97A4, 0xED44, 0x97A5, 0xED45, 0x97A6, 0xED46, 0x97A7, 0xED47, 0x97A8, 0xED48, 0x97A9, 0xED49, 0x97AA, 0xED4A, + 0x97AB, 0xF7B6, 0x97AC, 0xED4B, 0x97AD, 0xB1DE, 0x97AE, 0xED4C, 0x97AF, 0xF7B5, 0x97B0, 0xED4D, 0x97B1, 0xED4E, 0x97B2, 0xF7B8, + 0x97B3, 0xED4F, 0x97B4, 0xF7B9, 0x97B5, 0xED50, 0x97B6, 0xED51, 0x97B7, 0xED52, 0x97B8, 0xED53, 0x97B9, 0xED54, 0x97BA, 0xED55, + 0x97BB, 0xED56, 0x97BC, 0xED57, 0x97BD, 0xED58, 0x97BE, 0xED59, 0x97BF, 0xED5A, 0x97C0, 0xED5B, 0x97C1, 0xED5C, 0x97C2, 0xED5D, + 0x97C3, 0xED5E, 0x97C4, 0xED5F, 0x97C5, 0xED60, 0x97C6, 0xED61, 0x97C7, 0xED62, 0x97C8, 0xED63, 0x97C9, 0xED64, 0x97CA, 0xED65, + 0x97CB, 0xED66, 0x97CC, 0xED67, 0x97CD, 0xED68, 0x97CE, 0xED69, 0x97CF, 0xED6A, 0x97D0, 0xED6B, 0x97D1, 0xED6C, 0x97D2, 0xED6D, + 0x97D3, 0xED6E, 0x97D4, 0xED6F, 0x97D5, 0xED70, 0x97D6, 0xED71, 0x97D7, 0xED72, 0x97D8, 0xED73, 0x97D9, 0xED74, 0x97DA, 0xED75, + 0x97DB, 0xED76, 0x97DC, 0xED77, 0x97DD, 0xED78, 0x97DE, 0xED79, 0x97DF, 0xED7A, 0x97E0, 0xED7B, 0x97E1, 0xED7C, 0x97E2, 0xED7D, + 0x97E3, 0xED7E, 0x97E4, 0xED80, 0x97E5, 0xED81, 0x97E6, 0xCEA4, 0x97E7, 0xC8CD, 0x97E8, 0xED82, 0x97E9, 0xBAAB, 0x97EA, 0xE8B8, + 0x97EB, 0xE8B9, 0x97EC, 0xE8BA, 0x97ED, 0xBEC2, 0x97EE, 0xED83, 0x97EF, 0xED84, 0x97F0, 0xED85, 0x97F1, 0xED86, 0x97F2, 0xED87, + 0x97F3, 0xD2F4, 0x97F4, 0xED88, 0x97F5, 0xD4CF, 0x97F6, 0xC9D8, 0x97F7, 0xED89, 0x97F8, 0xED8A, 0x97F9, 0xED8B, 0x97FA, 0xED8C, + 0x97FB, 0xED8D, 0x97FC, 0xED8E, 0x97FD, 0xED8F, 0x97FE, 0xED90, 0x97FF, 0xED91, 0x9800, 0xED92, 0x9801, 0xED93, 0x9802, 0xED94, + 0x9803, 0xED95, 0x9804, 0xED96, 0x9805, 0xED97, 0x9806, 0xED98, 0x9807, 0xED99, 0x9808, 0xED9A, 0x9809, 0xED9B, 0x980A, 0xED9C, + 0x980B, 0xED9D, 0x980C, 0xED9E, 0x980D, 0xED9F, 0x980E, 0xEDA0, 0x980F, 0xEE40, 0x9810, 0xEE41, 0x9811, 0xEE42, 0x9812, 0xEE43, + 0x9813, 0xEE44, 0x9814, 0xEE45, 0x9815, 0xEE46, 0x9816, 0xEE47, 0x9817, 0xEE48, 0x9818, 0xEE49, 0x9819, 0xEE4A, 0x981A, 0xEE4B, + 0x981B, 0xEE4C, 0x981C, 0xEE4D, 0x981D, 0xEE4E, 0x981E, 0xEE4F, 0x981F, 0xEE50, 0x9820, 0xEE51, 0x9821, 0xEE52, 0x9822, 0xEE53, + 0x9823, 0xEE54, 0x9824, 0xEE55, 0x9825, 0xEE56, 0x9826, 0xEE57, 0x9827, 0xEE58, 0x9828, 0xEE59, 0x9829, 0xEE5A, 0x982A, 0xEE5B, + 0x982B, 0xEE5C, 0x982C, 0xEE5D, 0x982D, 0xEE5E, 0x982E, 0xEE5F, 0x982F, 0xEE60, 0x9830, 0xEE61, 0x9831, 0xEE62, 0x9832, 0xEE63, + 0x9833, 0xEE64, 0x9834, 0xEE65, 0x9835, 0xEE66, 0x9836, 0xEE67, 0x9837, 0xEE68, 0x9838, 0xEE69, 0x9839, 0xEE6A, 0x983A, 0xEE6B, + 0x983B, 0xEE6C, 0x983C, 0xEE6D, 0x983D, 0xEE6E, 0x983E, 0xEE6F, 0x983F, 0xEE70, 0x9840, 0xEE71, 0x9841, 0xEE72, 0x9842, 0xEE73, + 0x9843, 0xEE74, 0x9844, 0xEE75, 0x9845, 0xEE76, 0x9846, 0xEE77, 0x9847, 0xEE78, 0x9848, 0xEE79, 0x9849, 0xEE7A, 0x984A, 0xEE7B, + 0x984B, 0xEE7C, 0x984C, 0xEE7D, 0x984D, 0xEE7E, 0x984E, 0xEE80, 0x984F, 0xEE81, 0x9850, 0xEE82, 0x9851, 0xEE83, 0x9852, 0xEE84, + 0x9853, 0xEE85, 0x9854, 0xEE86, 0x9855, 0xEE87, 0x9856, 0xEE88, 0x9857, 0xEE89, 0x9858, 0xEE8A, 0x9859, 0xEE8B, 0x985A, 0xEE8C, + 0x985B, 0xEE8D, 0x985C, 0xEE8E, 0x985D, 0xEE8F, 0x985E, 0xEE90, 0x985F, 0xEE91, 0x9860, 0xEE92, 0x9861, 0xEE93, 0x9862, 0xEE94, + 0x9863, 0xEE95, 0x9864, 0xEE96, 0x9865, 0xEE97, 0x9866, 0xEE98, 0x9867, 0xEE99, 0x9868, 0xEE9A, 0x9869, 0xEE9B, 0x986A, 0xEE9C, + 0x986B, 0xEE9D, 0x986C, 0xEE9E, 0x986D, 0xEE9F, 0x986E, 0xEEA0, 0x986F, 0xEF40, 0x9870, 0xEF41, 0x9871, 0xEF42, 0x9872, 0xEF43, + 0x9873, 0xEF44, 0x9874, 0xEF45, 0x9875, 0xD2B3, 0x9876, 0xB6A5, 0x9877, 0xC7EA, 0x9878, 0xF1FC, 0x9879, 0xCFEE, 0x987A, 0xCBB3, + 0x987B, 0xD0EB, 0x987C, 0xE7EF, 0x987D, 0xCDE7, 0x987E, 0xB9CB, 0x987F, 0xB6D9, 0x9880, 0xF1FD, 0x9881, 0xB0E4, 0x9882, 0xCBCC, + 0x9883, 0xF1FE, 0x9884, 0xD4A4, 0x9885, 0xC2AD, 0x9886, 0xC1EC, 0x9887, 0xC6C4, 0x9888, 0xBEB1, 0x9889, 0xF2A1, 0x988A, 0xBCD5, + 0x988B, 0xEF46, 0x988C, 0xF2A2, 0x988D, 0xF2A3, 0x988E, 0xEF47, 0x988F, 0xF2A4, 0x9890, 0xD2C3, 0x9891, 0xC6B5, 0x9892, 0xEF48, + 0x9893, 0xCDC7, 0x9894, 0xF2A5, 0x9895, 0xEF49, 0x9896, 0xD3B1, 0x9897, 0xBFC5, 0x9898, 0xCCE2, 0x9899, 0xEF4A, 0x989A, 0xF2A6, + 0x989B, 0xF2A7, 0x989C, 0xD1D5, 0x989D, 0xB6EE, 0x989E, 0xF2A8, 0x989F, 0xF2A9, 0x98A0, 0xB5DF, 0x98A1, 0xF2AA, 0x98A2, 0xF2AB, + 0x98A3, 0xEF4B, 0x98A4, 0xB2FC, 0x98A5, 0xF2AC, 0x98A6, 0xF2AD, 0x98A7, 0xC8A7, 0x98A8, 0xEF4C, 0x98A9, 0xEF4D, 0x98AA, 0xEF4E, + 0x98AB, 0xEF4F, 0x98AC, 0xEF50, 0x98AD, 0xEF51, 0x98AE, 0xEF52, 0x98AF, 0xEF53, 0x98B0, 0xEF54, 0x98B1, 0xEF55, 0x98B2, 0xEF56, + 0x98B3, 0xEF57, 0x98B4, 0xEF58, 0x98B5, 0xEF59, 0x98B6, 0xEF5A, 0x98B7, 0xEF5B, 0x98B8, 0xEF5C, 0x98B9, 0xEF5D, 0x98BA, 0xEF5E, + 0x98BB, 0xEF5F, 0x98BC, 0xEF60, 0x98BD, 0xEF61, 0x98BE, 0xEF62, 0x98BF, 0xEF63, 0x98C0, 0xEF64, 0x98C1, 0xEF65, 0x98C2, 0xEF66, + 0x98C3, 0xEF67, 0x98C4, 0xEF68, 0x98C5, 0xEF69, 0x98C6, 0xEF6A, 0x98C7, 0xEF6B, 0x98C8, 0xEF6C, 0x98C9, 0xEF6D, 0x98CA, 0xEF6E, + 0x98CB, 0xEF6F, 0x98CC, 0xEF70, 0x98CD, 0xEF71, 0x98CE, 0xB7E7, 0x98CF, 0xEF72, 0x98D0, 0xEF73, 0x98D1, 0xECA9, 0x98D2, 0xECAA, + 0x98D3, 0xECAB, 0x98D4, 0xEF74, 0x98D5, 0xECAC, 0x98D6, 0xEF75, 0x98D7, 0xEF76, 0x98D8, 0xC6AE, 0x98D9, 0xECAD, 0x98DA, 0xECAE, + 0x98DB, 0xEF77, 0x98DC, 0xEF78, 0x98DD, 0xEF79, 0x98DE, 0xB7C9, 0x98DF, 0xCAB3, 0x98E0, 0xEF7A, 0x98E1, 0xEF7B, 0x98E2, 0xEF7C, + 0x98E3, 0xEF7D, 0x98E4, 0xEF7E, 0x98E5, 0xEF80, 0x98E6, 0xEF81, 0x98E7, 0xE2B8, 0x98E8, 0xF7CF, 0x98E9, 0xEF82, 0x98EA, 0xEF83, + 0x98EB, 0xEF84, 0x98EC, 0xEF85, 0x98ED, 0xEF86, 0x98EE, 0xEF87, 0x98EF, 0xEF88, 0x98F0, 0xEF89, 0x98F1, 0xEF8A, 0x98F2, 0xEF8B, + 0x98F3, 0xEF8C, 0x98F4, 0xEF8D, 0x98F5, 0xEF8E, 0x98F6, 0xEF8F, 0x98F7, 0xEF90, 0x98F8, 0xEF91, 0x98F9, 0xEF92, 0x98FA, 0xEF93, + 0x98FB, 0xEF94, 0x98FC, 0xEF95, 0x98FD, 0xEF96, 0x98FE, 0xEF97, 0x98FF, 0xEF98, 0x9900, 0xEF99, 0x9901, 0xEF9A, 0x9902, 0xEF9B, + 0x9903, 0xEF9C, 0x9904, 0xEF9D, 0x9905, 0xEF9E, 0x9906, 0xEF9F, 0x9907, 0xEFA0, 0x9908, 0xF040, 0x9909, 0xF041, 0x990A, 0xF042, + 0x990B, 0xF043, 0x990C, 0xF044, 0x990D, 0xF7D0, 0x990E, 0xF045, 0x990F, 0xF046, 0x9910, 0xB2CD, 0x9911, 0xF047, 0x9912, 0xF048, + 0x9913, 0xF049, 0x9914, 0xF04A, 0x9915, 0xF04B, 0x9916, 0xF04C, 0x9917, 0xF04D, 0x9918, 0xF04E, 0x9919, 0xF04F, 0x991A, 0xF050, + 0x991B, 0xF051, 0x991C, 0xF052, 0x991D, 0xF053, 0x991E, 0xF054, 0x991F, 0xF055, 0x9920, 0xF056, 0x9921, 0xF057, 0x9922, 0xF058, + 0x9923, 0xF059, 0x9924, 0xF05A, 0x9925, 0xF05B, 0x9926, 0xF05C, 0x9927, 0xF05D, 0x9928, 0xF05E, 0x9929, 0xF05F, 0x992A, 0xF060, + 0x992B, 0xF061, 0x992C, 0xF062, 0x992D, 0xF063, 0x992E, 0xF7D1, 0x992F, 0xF064, 0x9930, 0xF065, 0x9931, 0xF066, 0x9932, 0xF067, + 0x9933, 0xF068, 0x9934, 0xF069, 0x9935, 0xF06A, 0x9936, 0xF06B, 0x9937, 0xF06C, 0x9938, 0xF06D, 0x9939, 0xF06E, 0x993A, 0xF06F, + 0x993B, 0xF070, 0x993C, 0xF071, 0x993D, 0xF072, 0x993E, 0xF073, 0x993F, 0xF074, 0x9940, 0xF075, 0x9941, 0xF076, 0x9942, 0xF077, + 0x9943, 0xF078, 0x9944, 0xF079, 0x9945, 0xF07A, 0x9946, 0xF07B, 0x9947, 0xF07C, 0x9948, 0xF07D, 0x9949, 0xF07E, 0x994A, 0xF080, + 0x994B, 0xF081, 0x994C, 0xF082, 0x994D, 0xF083, 0x994E, 0xF084, 0x994F, 0xF085, 0x9950, 0xF086, 0x9951, 0xF087, 0x9952, 0xF088, + 0x9953, 0xF089, 0x9954, 0xF7D3, 0x9955, 0xF7D2, 0x9956, 0xF08A, 0x9957, 0xF08B, 0x9958, 0xF08C, 0x9959, 0xF08D, 0x995A, 0xF08E, + 0x995B, 0xF08F, 0x995C, 0xF090, 0x995D, 0xF091, 0x995E, 0xF092, 0x995F, 0xF093, 0x9960, 0xF094, 0x9961, 0xF095, 0x9962, 0xF096, + 0x9963, 0xE2BB, 0x9964, 0xF097, 0x9965, 0xBCA2, 0x9966, 0xF098, 0x9967, 0xE2BC, 0x9968, 0xE2BD, 0x9969, 0xE2BE, 0x996A, 0xE2BF, + 0x996B, 0xE2C0, 0x996C, 0xE2C1, 0x996D, 0xB7B9, 0x996E, 0xD2FB, 0x996F, 0xBDA4, 0x9970, 0xCACE, 0x9971, 0xB1A5, 0x9972, 0xCBC7, + 0x9973, 0xF099, 0x9974, 0xE2C2, 0x9975, 0xB6FC, 0x9976, 0xC8C4, 0x9977, 0xE2C3, 0x9978, 0xF09A, 0x9979, 0xF09B, 0x997A, 0xBDC8, + 0x997B, 0xF09C, 0x997C, 0xB1FD, 0x997D, 0xE2C4, 0x997E, 0xF09D, 0x997F, 0xB6F6, 0x9980, 0xE2C5, 0x9981, 0xC4D9, 0x9982, 0xF09E, + 0x9983, 0xF09F, 0x9984, 0xE2C6, 0x9985, 0xCFDA, 0x9986, 0xB9DD, 0x9987, 0xE2C7, 0x9988, 0xC0A1, 0x9989, 0xF0A0, 0x998A, 0xE2C8, + 0x998B, 0xB2F6, 0x998C, 0xF140, 0x998D, 0xE2C9, 0x998E, 0xF141, 0x998F, 0xC1F3, 0x9990, 0xE2CA, 0x9991, 0xE2CB, 0x9992, 0xC2F8, + 0x9993, 0xE2CC, 0x9994, 0xE2CD, 0x9995, 0xE2CE, 0x9996, 0xCAD7, 0x9997, 0xD8B8, 0x9998, 0xD9E5, 0x9999, 0xCFE3, 0x999A, 0xF142, + 0x999B, 0xF143, 0x999C, 0xF144, 0x999D, 0xF145, 0x999E, 0xF146, 0x999F, 0xF147, 0x99A0, 0xF148, 0x99A1, 0xF149, 0x99A2, 0xF14A, + 0x99A3, 0xF14B, 0x99A4, 0xF14C, 0x99A5, 0xF0A5, 0x99A6, 0xF14D, 0x99A7, 0xF14E, 0x99A8, 0xDCB0, 0x99A9, 0xF14F, 0x99AA, 0xF150, + 0x99AB, 0xF151, 0x99AC, 0xF152, 0x99AD, 0xF153, 0x99AE, 0xF154, 0x99AF, 0xF155, 0x99B0, 0xF156, 0x99B1, 0xF157, 0x99B2, 0xF158, + 0x99B3, 0xF159, 0x99B4, 0xF15A, 0x99B5, 0xF15B, 0x99B6, 0xF15C, 0x99B7, 0xF15D, 0x99B8, 0xF15E, 0x99B9, 0xF15F, 0x99BA, 0xF160, + 0x99BB, 0xF161, 0x99BC, 0xF162, 0x99BD, 0xF163, 0x99BE, 0xF164, 0x99BF, 0xF165, 0x99C0, 0xF166, 0x99C1, 0xF167, 0x99C2, 0xF168, + 0x99C3, 0xF169, 0x99C4, 0xF16A, 0x99C5, 0xF16B, 0x99C6, 0xF16C, 0x99C7, 0xF16D, 0x99C8, 0xF16E, 0x99C9, 0xF16F, 0x99CA, 0xF170, + 0x99CB, 0xF171, 0x99CC, 0xF172, 0x99CD, 0xF173, 0x99CE, 0xF174, 0x99CF, 0xF175, 0x99D0, 0xF176, 0x99D1, 0xF177, 0x99D2, 0xF178, + 0x99D3, 0xF179, 0x99D4, 0xF17A, 0x99D5, 0xF17B, 0x99D6, 0xF17C, 0x99D7, 0xF17D, 0x99D8, 0xF17E, 0x99D9, 0xF180, 0x99DA, 0xF181, + 0x99DB, 0xF182, 0x99DC, 0xF183, 0x99DD, 0xF184, 0x99DE, 0xF185, 0x99DF, 0xF186, 0x99E0, 0xF187, 0x99E1, 0xF188, 0x99E2, 0xF189, + 0x99E3, 0xF18A, 0x99E4, 0xF18B, 0x99E5, 0xF18C, 0x99E6, 0xF18D, 0x99E7, 0xF18E, 0x99E8, 0xF18F, 0x99E9, 0xF190, 0x99EA, 0xF191, + 0x99EB, 0xF192, 0x99EC, 0xF193, 0x99ED, 0xF194, 0x99EE, 0xF195, 0x99EF, 0xF196, 0x99F0, 0xF197, 0x99F1, 0xF198, 0x99F2, 0xF199, + 0x99F3, 0xF19A, 0x99F4, 0xF19B, 0x99F5, 0xF19C, 0x99F6, 0xF19D, 0x99F7, 0xF19E, 0x99F8, 0xF19F, 0x99F9, 0xF1A0, 0x99FA, 0xF240, + 0x99FB, 0xF241, 0x99FC, 0xF242, 0x99FD, 0xF243, 0x99FE, 0xF244, 0x99FF, 0xF245, 0x9A00, 0xF246, 0x9A01, 0xF247, 0x9A02, 0xF248, + 0x9A03, 0xF249, 0x9A04, 0xF24A, 0x9A05, 0xF24B, 0x9A06, 0xF24C, 0x9A07, 0xF24D, 0x9A08, 0xF24E, 0x9A09, 0xF24F, 0x9A0A, 0xF250, + 0x9A0B, 0xF251, 0x9A0C, 0xF252, 0x9A0D, 0xF253, 0x9A0E, 0xF254, 0x9A0F, 0xF255, 0x9A10, 0xF256, 0x9A11, 0xF257, 0x9A12, 0xF258, + 0x9A13, 0xF259, 0x9A14, 0xF25A, 0x9A15, 0xF25B, 0x9A16, 0xF25C, 0x9A17, 0xF25D, 0x9A18, 0xF25E, 0x9A19, 0xF25F, 0x9A1A, 0xF260, + 0x9A1B, 0xF261, 0x9A1C, 0xF262, 0x9A1D, 0xF263, 0x9A1E, 0xF264, 0x9A1F, 0xF265, 0x9A20, 0xF266, 0x9A21, 0xF267, 0x9A22, 0xF268, + 0x9A23, 0xF269, 0x9A24, 0xF26A, 0x9A25, 0xF26B, 0x9A26, 0xF26C, 0x9A27, 0xF26D, 0x9A28, 0xF26E, 0x9A29, 0xF26F, 0x9A2A, 0xF270, + 0x9A2B, 0xF271, 0x9A2C, 0xF272, 0x9A2D, 0xF273, 0x9A2E, 0xF274, 0x9A2F, 0xF275, 0x9A30, 0xF276, 0x9A31, 0xF277, 0x9A32, 0xF278, + 0x9A33, 0xF279, 0x9A34, 0xF27A, 0x9A35, 0xF27B, 0x9A36, 0xF27C, 0x9A37, 0xF27D, 0x9A38, 0xF27E, 0x9A39, 0xF280, 0x9A3A, 0xF281, + 0x9A3B, 0xF282, 0x9A3C, 0xF283, 0x9A3D, 0xF284, 0x9A3E, 0xF285, 0x9A3F, 0xF286, 0x9A40, 0xF287, 0x9A41, 0xF288, 0x9A42, 0xF289, + 0x9A43, 0xF28A, 0x9A44, 0xF28B, 0x9A45, 0xF28C, 0x9A46, 0xF28D, 0x9A47, 0xF28E, 0x9A48, 0xF28F, 0x9A49, 0xF290, 0x9A4A, 0xF291, + 0x9A4B, 0xF292, 0x9A4C, 0xF293, 0x9A4D, 0xF294, 0x9A4E, 0xF295, 0x9A4F, 0xF296, 0x9A50, 0xF297, 0x9A51, 0xF298, 0x9A52, 0xF299, + 0x9A53, 0xF29A, 0x9A54, 0xF29B, 0x9A55, 0xF29C, 0x9A56, 0xF29D, 0x9A57, 0xF29E, 0x9A58, 0xF29F, 0x9A59, 0xF2A0, 0x9A5A, 0xF340, + 0x9A5B, 0xF341, 0x9A5C, 0xF342, 0x9A5D, 0xF343, 0x9A5E, 0xF344, 0x9A5F, 0xF345, 0x9A60, 0xF346, 0x9A61, 0xF347, 0x9A62, 0xF348, + 0x9A63, 0xF349, 0x9A64, 0xF34A, 0x9A65, 0xF34B, 0x9A66, 0xF34C, 0x9A67, 0xF34D, 0x9A68, 0xF34E, 0x9A69, 0xF34F, 0x9A6A, 0xF350, + 0x9A6B, 0xF351, 0x9A6C, 0xC2ED, 0x9A6D, 0xD4A6, 0x9A6E, 0xCDD4, 0x9A6F, 0xD1B1, 0x9A70, 0xB3DB, 0x9A71, 0xC7FD, 0x9A72, 0xF352, + 0x9A73, 0xB2B5, 0x9A74, 0xC2BF, 0x9A75, 0xE6E0, 0x9A76, 0xCABB, 0x9A77, 0xE6E1, 0x9A78, 0xE6E2, 0x9A79, 0xBED4, 0x9A7A, 0xE6E3, + 0x9A7B, 0xD7A4, 0x9A7C, 0xCDD5, 0x9A7D, 0xE6E5, 0x9A7E, 0xBCDD, 0x9A7F, 0xE6E4, 0x9A80, 0xE6E6, 0x9A81, 0xE6E7, 0x9A82, 0xC2EE, + 0x9A83, 0xF353, 0x9A84, 0xBDBE, 0x9A85, 0xE6E8, 0x9A86, 0xC2E6, 0x9A87, 0xBAA7, 0x9A88, 0xE6E9, 0x9A89, 0xF354, 0x9A8A, 0xE6EA, + 0x9A8B, 0xB3D2, 0x9A8C, 0xD1E9, 0x9A8D, 0xF355, 0x9A8E, 0xF356, 0x9A8F, 0xBFA5, 0x9A90, 0xE6EB, 0x9A91, 0xC6EF, 0x9A92, 0xE6EC, + 0x9A93, 0xE6ED, 0x9A94, 0xF357, 0x9A95, 0xF358, 0x9A96, 0xE6EE, 0x9A97, 0xC6AD, 0x9A98, 0xE6EF, 0x9A99, 0xF359, 0x9A9A, 0xC9A7, + 0x9A9B, 0xE6F0, 0x9A9C, 0xE6F1, 0x9A9D, 0xE6F2, 0x9A9E, 0xE5B9, 0x9A9F, 0xE6F3, 0x9AA0, 0xE6F4, 0x9AA1, 0xC2E2, 0x9AA2, 0xE6F5, + 0x9AA3, 0xE6F6, 0x9AA4, 0xD6E8, 0x9AA5, 0xE6F7, 0x9AA6, 0xF35A, 0x9AA7, 0xE6F8, 0x9AA8, 0xB9C7, 0x9AA9, 0xF35B, 0x9AAA, 0xF35C, + 0x9AAB, 0xF35D, 0x9AAC, 0xF35E, 0x9AAD, 0xF35F, 0x9AAE, 0xF360, 0x9AAF, 0xF361, 0x9AB0, 0xF7BB, 0x9AB1, 0xF7BA, 0x9AB2, 0xF362, + 0x9AB3, 0xF363, 0x9AB4, 0xF364, 0x9AB5, 0xF365, 0x9AB6, 0xF7BE, 0x9AB7, 0xF7BC, 0x9AB8, 0xBAA1, 0x9AB9, 0xF366, 0x9ABA, 0xF7BF, + 0x9ABB, 0xF367, 0x9ABC, 0xF7C0, 0x9ABD, 0xF368, 0x9ABE, 0xF369, 0x9ABF, 0xF36A, 0x9AC0, 0xF7C2, 0x9AC1, 0xF7C1, 0x9AC2, 0xF7C4, + 0x9AC3, 0xF36B, 0x9AC4, 0xF36C, 0x9AC5, 0xF7C3, 0x9AC6, 0xF36D, 0x9AC7, 0xF36E, 0x9AC8, 0xF36F, 0x9AC9, 0xF370, 0x9ACA, 0xF371, + 0x9ACB, 0xF7C5, 0x9ACC, 0xF7C6, 0x9ACD, 0xF372, 0x9ACE, 0xF373, 0x9ACF, 0xF374, 0x9AD0, 0xF375, 0x9AD1, 0xF7C7, 0x9AD2, 0xF376, + 0x9AD3, 0xCBE8, 0x9AD4, 0xF377, 0x9AD5, 0xF378, 0x9AD6, 0xF379, 0x9AD7, 0xF37A, 0x9AD8, 0xB8DF, 0x9AD9, 0xF37B, 0x9ADA, 0xF37C, + 0x9ADB, 0xF37D, 0x9ADC, 0xF37E, 0x9ADD, 0xF380, 0x9ADE, 0xF381, 0x9ADF, 0xF7D4, 0x9AE0, 0xF382, 0x9AE1, 0xF7D5, 0x9AE2, 0xF383, + 0x9AE3, 0xF384, 0x9AE4, 0xF385, 0x9AE5, 0xF386, 0x9AE6, 0xF7D6, 0x9AE7, 0xF387, 0x9AE8, 0xF388, 0x9AE9, 0xF389, 0x9AEA, 0xF38A, + 0x9AEB, 0xF7D8, 0x9AEC, 0xF38B, 0x9AED, 0xF7DA, 0x9AEE, 0xF38C, 0x9AEF, 0xF7D7, 0x9AF0, 0xF38D, 0x9AF1, 0xF38E, 0x9AF2, 0xF38F, + 0x9AF3, 0xF390, 0x9AF4, 0xF391, 0x9AF5, 0xF392, 0x9AF6, 0xF393, 0x9AF7, 0xF394, 0x9AF8, 0xF395, 0x9AF9, 0xF7DB, 0x9AFA, 0xF396, + 0x9AFB, 0xF7D9, 0x9AFC, 0xF397, 0x9AFD, 0xF398, 0x9AFE, 0xF399, 0x9AFF, 0xF39A, 0x9B00, 0xF39B, 0x9B01, 0xF39C, 0x9B02, 0xF39D, + 0x9B03, 0xD7D7, 0x9B04, 0xF39E, 0x9B05, 0xF39F, 0x9B06, 0xF3A0, 0x9B07, 0xF440, 0x9B08, 0xF7DC, 0x9B09, 0xF441, 0x9B0A, 0xF442, + 0x9B0B, 0xF443, 0x9B0C, 0xF444, 0x9B0D, 0xF445, 0x9B0E, 0xF446, 0x9B0F, 0xF7DD, 0x9B10, 0xF447, 0x9B11, 0xF448, 0x9B12, 0xF449, + 0x9B13, 0xF7DE, 0x9B14, 0xF44A, 0x9B15, 0xF44B, 0x9B16, 0xF44C, 0x9B17, 0xF44D, 0x9B18, 0xF44E, 0x9B19, 0xF44F, 0x9B1A, 0xF450, + 0x9B1B, 0xF451, 0x9B1C, 0xF452, 0x9B1D, 0xF453, 0x9B1E, 0xF454, 0x9B1F, 0xF7DF, 0x9B20, 0xF455, 0x9B21, 0xF456, 0x9B22, 0xF457, + 0x9B23, 0xF7E0, 0x9B24, 0xF458, 0x9B25, 0xF459, 0x9B26, 0xF45A, 0x9B27, 0xF45B, 0x9B28, 0xF45C, 0x9B29, 0xF45D, 0x9B2A, 0xF45E, + 0x9B2B, 0xF45F, 0x9B2C, 0xF460, 0x9B2D, 0xF461, 0x9B2E, 0xF462, 0x9B2F, 0xDBCB, 0x9B30, 0xF463, 0x9B31, 0xF464, 0x9B32, 0xD8AA, + 0x9B33, 0xF465, 0x9B34, 0xF466, 0x9B35, 0xF467, 0x9B36, 0xF468, 0x9B37, 0xF469, 0x9B38, 0xF46A, 0x9B39, 0xF46B, 0x9B3A, 0xF46C, + 0x9B3B, 0xE5F7, 0x9B3C, 0xB9ED, 0x9B3D, 0xF46D, 0x9B3E, 0xF46E, 0x9B3F, 0xF46F, 0x9B40, 0xF470, 0x9B41, 0xBFFD, 0x9B42, 0xBBEA, + 0x9B43, 0xF7C9, 0x9B44, 0xC6C7, 0x9B45, 0xF7C8, 0x9B46, 0xF471, 0x9B47, 0xF7CA, 0x9B48, 0xF7CC, 0x9B49, 0xF7CB, 0x9B4A, 0xF472, + 0x9B4B, 0xF473, 0x9B4C, 0xF474, 0x9B4D, 0xF7CD, 0x9B4E, 0xF475, 0x9B4F, 0xCEBA, 0x9B50, 0xF476, 0x9B51, 0xF7CE, 0x9B52, 0xF477, + 0x9B53, 0xF478, 0x9B54, 0xC4A7, 0x9B55, 0xF479, 0x9B56, 0xF47A, 0x9B57, 0xF47B, 0x9B58, 0xF47C, 0x9B59, 0xF47D, 0x9B5A, 0xF47E, + 0x9B5B, 0xF480, 0x9B5C, 0xF481, 0x9B5D, 0xF482, 0x9B5E, 0xF483, 0x9B5F, 0xF484, 0x9B60, 0xF485, 0x9B61, 0xF486, 0x9B62, 0xF487, + 0x9B63, 0xF488, 0x9B64, 0xF489, 0x9B65, 0xF48A, 0x9B66, 0xF48B, 0x9B67, 0xF48C, 0x9B68, 0xF48D, 0x9B69, 0xF48E, 0x9B6A, 0xF48F, + 0x9B6B, 0xF490, 0x9B6C, 0xF491, 0x9B6D, 0xF492, 0x9B6E, 0xF493, 0x9B6F, 0xF494, 0x9B70, 0xF495, 0x9B71, 0xF496, 0x9B72, 0xF497, + 0x9B73, 0xF498, 0x9B74, 0xF499, 0x9B75, 0xF49A, 0x9B76, 0xF49B, 0x9B77, 0xF49C, 0x9B78, 0xF49D, 0x9B79, 0xF49E, 0x9B7A, 0xF49F, + 0x9B7B, 0xF4A0, 0x9B7C, 0xF540, 0x9B7D, 0xF541, 0x9B7E, 0xF542, 0x9B7F, 0xF543, 0x9B80, 0xF544, 0x9B81, 0xF545, 0x9B82, 0xF546, + 0x9B83, 0xF547, 0x9B84, 0xF548, 0x9B85, 0xF549, 0x9B86, 0xF54A, 0x9B87, 0xF54B, 0x9B88, 0xF54C, 0x9B89, 0xF54D, 0x9B8A, 0xF54E, + 0x9B8B, 0xF54F, 0x9B8C, 0xF550, 0x9B8D, 0xF551, 0x9B8E, 0xF552, 0x9B8F, 0xF553, 0x9B90, 0xF554, 0x9B91, 0xF555, 0x9B92, 0xF556, + 0x9B93, 0xF557, 0x9B94, 0xF558, 0x9B95, 0xF559, 0x9B96, 0xF55A, 0x9B97, 0xF55B, 0x9B98, 0xF55C, 0x9B99, 0xF55D, 0x9B9A, 0xF55E, + 0x9B9B, 0xF55F, 0x9B9C, 0xF560, 0x9B9D, 0xF561, 0x9B9E, 0xF562, 0x9B9F, 0xF563, 0x9BA0, 0xF564, 0x9BA1, 0xF565, 0x9BA2, 0xF566, + 0x9BA3, 0xF567, 0x9BA4, 0xF568, 0x9BA5, 0xF569, 0x9BA6, 0xF56A, 0x9BA7, 0xF56B, 0x9BA8, 0xF56C, 0x9BA9, 0xF56D, 0x9BAA, 0xF56E, + 0x9BAB, 0xF56F, 0x9BAC, 0xF570, 0x9BAD, 0xF571, 0x9BAE, 0xF572, 0x9BAF, 0xF573, 0x9BB0, 0xF574, 0x9BB1, 0xF575, 0x9BB2, 0xF576, + 0x9BB3, 0xF577, 0x9BB4, 0xF578, 0x9BB5, 0xF579, 0x9BB6, 0xF57A, 0x9BB7, 0xF57B, 0x9BB8, 0xF57C, 0x9BB9, 0xF57D, 0x9BBA, 0xF57E, + 0x9BBB, 0xF580, 0x9BBC, 0xF581, 0x9BBD, 0xF582, 0x9BBE, 0xF583, 0x9BBF, 0xF584, 0x9BC0, 0xF585, 0x9BC1, 0xF586, 0x9BC2, 0xF587, + 0x9BC3, 0xF588, 0x9BC4, 0xF589, 0x9BC5, 0xF58A, 0x9BC6, 0xF58B, 0x9BC7, 0xF58C, 0x9BC8, 0xF58D, 0x9BC9, 0xF58E, 0x9BCA, 0xF58F, + 0x9BCB, 0xF590, 0x9BCC, 0xF591, 0x9BCD, 0xF592, 0x9BCE, 0xF593, 0x9BCF, 0xF594, 0x9BD0, 0xF595, 0x9BD1, 0xF596, 0x9BD2, 0xF597, + 0x9BD3, 0xF598, 0x9BD4, 0xF599, 0x9BD5, 0xF59A, 0x9BD6, 0xF59B, 0x9BD7, 0xF59C, 0x9BD8, 0xF59D, 0x9BD9, 0xF59E, 0x9BDA, 0xF59F, + 0x9BDB, 0xF5A0, 0x9BDC, 0xF640, 0x9BDD, 0xF641, 0x9BDE, 0xF642, 0x9BDF, 0xF643, 0x9BE0, 0xF644, 0x9BE1, 0xF645, 0x9BE2, 0xF646, + 0x9BE3, 0xF647, 0x9BE4, 0xF648, 0x9BE5, 0xF649, 0x9BE6, 0xF64A, 0x9BE7, 0xF64B, 0x9BE8, 0xF64C, 0x9BE9, 0xF64D, 0x9BEA, 0xF64E, + 0x9BEB, 0xF64F, 0x9BEC, 0xF650, 0x9BED, 0xF651, 0x9BEE, 0xF652, 0x9BEF, 0xF653, 0x9BF0, 0xF654, 0x9BF1, 0xF655, 0x9BF2, 0xF656, + 0x9BF3, 0xF657, 0x9BF4, 0xF658, 0x9BF5, 0xF659, 0x9BF6, 0xF65A, 0x9BF7, 0xF65B, 0x9BF8, 0xF65C, 0x9BF9, 0xF65D, 0x9BFA, 0xF65E, + 0x9BFB, 0xF65F, 0x9BFC, 0xF660, 0x9BFD, 0xF661, 0x9BFE, 0xF662, 0x9BFF, 0xF663, 0x9C00, 0xF664, 0x9C01, 0xF665, 0x9C02, 0xF666, + 0x9C03, 0xF667, 0x9C04, 0xF668, 0x9C05, 0xF669, 0x9C06, 0xF66A, 0x9C07, 0xF66B, 0x9C08, 0xF66C, 0x9C09, 0xF66D, 0x9C0A, 0xF66E, + 0x9C0B, 0xF66F, 0x9C0C, 0xF670, 0x9C0D, 0xF671, 0x9C0E, 0xF672, 0x9C0F, 0xF673, 0x9C10, 0xF674, 0x9C11, 0xF675, 0x9C12, 0xF676, + 0x9C13, 0xF677, 0x9C14, 0xF678, 0x9C15, 0xF679, 0x9C16, 0xF67A, 0x9C17, 0xF67B, 0x9C18, 0xF67C, 0x9C19, 0xF67D, 0x9C1A, 0xF67E, + 0x9C1B, 0xF680, 0x9C1C, 0xF681, 0x9C1D, 0xF682, 0x9C1E, 0xF683, 0x9C1F, 0xF684, 0x9C20, 0xF685, 0x9C21, 0xF686, 0x9C22, 0xF687, + 0x9C23, 0xF688, 0x9C24, 0xF689, 0x9C25, 0xF68A, 0x9C26, 0xF68B, 0x9C27, 0xF68C, 0x9C28, 0xF68D, 0x9C29, 0xF68E, 0x9C2A, 0xF68F, + 0x9C2B, 0xF690, 0x9C2C, 0xF691, 0x9C2D, 0xF692, 0x9C2E, 0xF693, 0x9C2F, 0xF694, 0x9C30, 0xF695, 0x9C31, 0xF696, 0x9C32, 0xF697, + 0x9C33, 0xF698, 0x9C34, 0xF699, 0x9C35, 0xF69A, 0x9C36, 0xF69B, 0x9C37, 0xF69C, 0x9C38, 0xF69D, 0x9C39, 0xF69E, 0x9C3A, 0xF69F, + 0x9C3B, 0xF6A0, 0x9C3C, 0xF740, 0x9C3D, 0xF741, 0x9C3E, 0xF742, 0x9C3F, 0xF743, 0x9C40, 0xF744, 0x9C41, 0xF745, 0x9C42, 0xF746, + 0x9C43, 0xF747, 0x9C44, 0xF748, 0x9C45, 0xF749, 0x9C46, 0xF74A, 0x9C47, 0xF74B, 0x9C48, 0xF74C, 0x9C49, 0xF74D, 0x9C4A, 0xF74E, + 0x9C4B, 0xF74F, 0x9C4C, 0xF750, 0x9C4D, 0xF751, 0x9C4E, 0xF752, 0x9C4F, 0xF753, 0x9C50, 0xF754, 0x9C51, 0xF755, 0x9C52, 0xF756, + 0x9C53, 0xF757, 0x9C54, 0xF758, 0x9C55, 0xF759, 0x9C56, 0xF75A, 0x9C57, 0xF75B, 0x9C58, 0xF75C, 0x9C59, 0xF75D, 0x9C5A, 0xF75E, + 0x9C5B, 0xF75F, 0x9C5C, 0xF760, 0x9C5D, 0xF761, 0x9C5E, 0xF762, 0x9C5F, 0xF763, 0x9C60, 0xF764, 0x9C61, 0xF765, 0x9C62, 0xF766, + 0x9C63, 0xF767, 0x9C64, 0xF768, 0x9C65, 0xF769, 0x9C66, 0xF76A, 0x9C67, 0xF76B, 0x9C68, 0xF76C, 0x9C69, 0xF76D, 0x9C6A, 0xF76E, + 0x9C6B, 0xF76F, 0x9C6C, 0xF770, 0x9C6D, 0xF771, 0x9C6E, 0xF772, 0x9C6F, 0xF773, 0x9C70, 0xF774, 0x9C71, 0xF775, 0x9C72, 0xF776, + 0x9C73, 0xF777, 0x9C74, 0xF778, 0x9C75, 0xF779, 0x9C76, 0xF77A, 0x9C77, 0xF77B, 0x9C78, 0xF77C, 0x9C79, 0xF77D, 0x9C7A, 0xF77E, + 0x9C7B, 0xF780, 0x9C7C, 0xD3E3, 0x9C7D, 0xF781, 0x9C7E, 0xF782, 0x9C7F, 0xF6CF, 0x9C80, 0xF783, 0x9C81, 0xC2B3, 0x9C82, 0xF6D0, + 0x9C83, 0xF784, 0x9C84, 0xF785, 0x9C85, 0xF6D1, 0x9C86, 0xF6D2, 0x9C87, 0xF6D3, 0x9C88, 0xF6D4, 0x9C89, 0xF786, 0x9C8A, 0xF787, + 0x9C8B, 0xF6D6, 0x9C8C, 0xF788, 0x9C8D, 0xB1AB, 0x9C8E, 0xF6D7, 0x9C8F, 0xF789, 0x9C90, 0xF6D8, 0x9C91, 0xF6D9, 0x9C92, 0xF6DA, + 0x9C93, 0xF78A, 0x9C94, 0xF6DB, 0x9C95, 0xF6DC, 0x9C96, 0xF78B, 0x9C97, 0xF78C, 0x9C98, 0xF78D, 0x9C99, 0xF78E, 0x9C9A, 0xF6DD, + 0x9C9B, 0xF6DE, 0x9C9C, 0xCFCA, 0x9C9D, 0xF78F, 0x9C9E, 0xF6DF, 0x9C9F, 0xF6E0, 0x9CA0, 0xF6E1, 0x9CA1, 0xF6E2, 0x9CA2, 0xF6E3, + 0x9CA3, 0xF6E4, 0x9CA4, 0xC0F0, 0x9CA5, 0xF6E5, 0x9CA6, 0xF6E6, 0x9CA7, 0xF6E7, 0x9CA8, 0xF6E8, 0x9CA9, 0xF6E9, 0x9CAA, 0xF790, + 0x9CAB, 0xF6EA, 0x9CAC, 0xF791, 0x9CAD, 0xF6EB, 0x9CAE, 0xF6EC, 0x9CAF, 0xF792, 0x9CB0, 0xF6ED, 0x9CB1, 0xF6EE, 0x9CB2, 0xF6EF, + 0x9CB3, 0xF6F0, 0x9CB4, 0xF6F1, 0x9CB5, 0xF6F2, 0x9CB6, 0xF6F3, 0x9CB7, 0xF6F4, 0x9CB8, 0xBEA8, 0x9CB9, 0xF793, 0x9CBA, 0xF6F5, + 0x9CBB, 0xF6F6, 0x9CBC, 0xF6F7, 0x9CBD, 0xF6F8, 0x9CBE, 0xF794, 0x9CBF, 0xF795, 0x9CC0, 0xF796, 0x9CC1, 0xF797, 0x9CC2, 0xF798, + 0x9CC3, 0xC8FA, 0x9CC4, 0xF6F9, 0x9CC5, 0xF6FA, 0x9CC6, 0xF6FB, 0x9CC7, 0xF6FC, 0x9CC8, 0xF799, 0x9CC9, 0xF79A, 0x9CCA, 0xF6FD, + 0x9CCB, 0xF6FE, 0x9CCC, 0xF7A1, 0x9CCD, 0xF7A2, 0x9CCE, 0xF7A3, 0x9CCF, 0xF7A4, 0x9CD0, 0xF7A5, 0x9CD1, 0xF79B, 0x9CD2, 0xF79C, + 0x9CD3, 0xF7A6, 0x9CD4, 0xF7A7, 0x9CD5, 0xF7A8, 0x9CD6, 0xB1EE, 0x9CD7, 0xF7A9, 0x9CD8, 0xF7AA, 0x9CD9, 0xF7AB, 0x9CDA, 0xF79D, + 0x9CDB, 0xF79E, 0x9CDC, 0xF7AC, 0x9CDD, 0xF7AD, 0x9CDE, 0xC1DB, 0x9CDF, 0xF7AE, 0x9CE0, 0xF79F, 0x9CE1, 0xF7A0, 0x9CE2, 0xF7AF, + 0x9CE3, 0xF840, 0x9CE4, 0xF841, 0x9CE5, 0xF842, 0x9CE6, 0xF843, 0x9CE7, 0xF844, 0x9CE8, 0xF845, 0x9CE9, 0xF846, 0x9CEA, 0xF847, + 0x9CEB, 0xF848, 0x9CEC, 0xF849, 0x9CED, 0xF84A, 0x9CEE, 0xF84B, 0x9CEF, 0xF84C, 0x9CF0, 0xF84D, 0x9CF1, 0xF84E, 0x9CF2, 0xF84F, + 0x9CF3, 0xF850, 0x9CF4, 0xF851, 0x9CF5, 0xF852, 0x9CF6, 0xF853, 0x9CF7, 0xF854, 0x9CF8, 0xF855, 0x9CF9, 0xF856, 0x9CFA, 0xF857, + 0x9CFB, 0xF858, 0x9CFC, 0xF859, 0x9CFD, 0xF85A, 0x9CFE, 0xF85B, 0x9CFF, 0xF85C, 0x9D00, 0xF85D, 0x9D01, 0xF85E, 0x9D02, 0xF85F, + 0x9D03, 0xF860, 0x9D04, 0xF861, 0x9D05, 0xF862, 0x9D06, 0xF863, 0x9D07, 0xF864, 0x9D08, 0xF865, 0x9D09, 0xF866, 0x9D0A, 0xF867, + 0x9D0B, 0xF868, 0x9D0C, 0xF869, 0x9D0D, 0xF86A, 0x9D0E, 0xF86B, 0x9D0F, 0xF86C, 0x9D10, 0xF86D, 0x9D11, 0xF86E, 0x9D12, 0xF86F, + 0x9D13, 0xF870, 0x9D14, 0xF871, 0x9D15, 0xF872, 0x9D16, 0xF873, 0x9D17, 0xF874, 0x9D18, 0xF875, 0x9D19, 0xF876, 0x9D1A, 0xF877, + 0x9D1B, 0xF878, 0x9D1C, 0xF879, 0x9D1D, 0xF87A, 0x9D1E, 0xF87B, 0x9D1F, 0xF87C, 0x9D20, 0xF87D, 0x9D21, 0xF87E, 0x9D22, 0xF880, + 0x9D23, 0xF881, 0x9D24, 0xF882, 0x9D25, 0xF883, 0x9D26, 0xF884, 0x9D27, 0xF885, 0x9D28, 0xF886, 0x9D29, 0xF887, 0x9D2A, 0xF888, + 0x9D2B, 0xF889, 0x9D2C, 0xF88A, 0x9D2D, 0xF88B, 0x9D2E, 0xF88C, 0x9D2F, 0xF88D, 0x9D30, 0xF88E, 0x9D31, 0xF88F, 0x9D32, 0xF890, + 0x9D33, 0xF891, 0x9D34, 0xF892, 0x9D35, 0xF893, 0x9D36, 0xF894, 0x9D37, 0xF895, 0x9D38, 0xF896, 0x9D39, 0xF897, 0x9D3A, 0xF898, + 0x9D3B, 0xF899, 0x9D3C, 0xF89A, 0x9D3D, 0xF89B, 0x9D3E, 0xF89C, 0x9D3F, 0xF89D, 0x9D40, 0xF89E, 0x9D41, 0xF89F, 0x9D42, 0xF8A0, + 0x9D43, 0xF940, 0x9D44, 0xF941, 0x9D45, 0xF942, 0x9D46, 0xF943, 0x9D47, 0xF944, 0x9D48, 0xF945, 0x9D49, 0xF946, 0x9D4A, 0xF947, + 0x9D4B, 0xF948, 0x9D4C, 0xF949, 0x9D4D, 0xF94A, 0x9D4E, 0xF94B, 0x9D4F, 0xF94C, 0x9D50, 0xF94D, 0x9D51, 0xF94E, 0x9D52, 0xF94F, + 0x9D53, 0xF950, 0x9D54, 0xF951, 0x9D55, 0xF952, 0x9D56, 0xF953, 0x9D57, 0xF954, 0x9D58, 0xF955, 0x9D59, 0xF956, 0x9D5A, 0xF957, + 0x9D5B, 0xF958, 0x9D5C, 0xF959, 0x9D5D, 0xF95A, 0x9D5E, 0xF95B, 0x9D5F, 0xF95C, 0x9D60, 0xF95D, 0x9D61, 0xF95E, 0x9D62, 0xF95F, + 0x9D63, 0xF960, 0x9D64, 0xF961, 0x9D65, 0xF962, 0x9D66, 0xF963, 0x9D67, 0xF964, 0x9D68, 0xF965, 0x9D69, 0xF966, 0x9D6A, 0xF967, + 0x9D6B, 0xF968, 0x9D6C, 0xF969, 0x9D6D, 0xF96A, 0x9D6E, 0xF96B, 0x9D6F, 0xF96C, 0x9D70, 0xF96D, 0x9D71, 0xF96E, 0x9D72, 0xF96F, + 0x9D73, 0xF970, 0x9D74, 0xF971, 0x9D75, 0xF972, 0x9D76, 0xF973, 0x9D77, 0xF974, 0x9D78, 0xF975, 0x9D79, 0xF976, 0x9D7A, 0xF977, + 0x9D7B, 0xF978, 0x9D7C, 0xF979, 0x9D7D, 0xF97A, 0x9D7E, 0xF97B, 0x9D7F, 0xF97C, 0x9D80, 0xF97D, 0x9D81, 0xF97E, 0x9D82, 0xF980, + 0x9D83, 0xF981, 0x9D84, 0xF982, 0x9D85, 0xF983, 0x9D86, 0xF984, 0x9D87, 0xF985, 0x9D88, 0xF986, 0x9D89, 0xF987, 0x9D8A, 0xF988, + 0x9D8B, 0xF989, 0x9D8C, 0xF98A, 0x9D8D, 0xF98B, 0x9D8E, 0xF98C, 0x9D8F, 0xF98D, 0x9D90, 0xF98E, 0x9D91, 0xF98F, 0x9D92, 0xF990, + 0x9D93, 0xF991, 0x9D94, 0xF992, 0x9D95, 0xF993, 0x9D96, 0xF994, 0x9D97, 0xF995, 0x9D98, 0xF996, 0x9D99, 0xF997, 0x9D9A, 0xF998, + 0x9D9B, 0xF999, 0x9D9C, 0xF99A, 0x9D9D, 0xF99B, 0x9D9E, 0xF99C, 0x9D9F, 0xF99D, 0x9DA0, 0xF99E, 0x9DA1, 0xF99F, 0x9DA2, 0xF9A0, + 0x9DA3, 0xFA40, 0x9DA4, 0xFA41, 0x9DA5, 0xFA42, 0x9DA6, 0xFA43, 0x9DA7, 0xFA44, 0x9DA8, 0xFA45, 0x9DA9, 0xFA46, 0x9DAA, 0xFA47, + 0x9DAB, 0xFA48, 0x9DAC, 0xFA49, 0x9DAD, 0xFA4A, 0x9DAE, 0xFA4B, 0x9DAF, 0xFA4C, 0x9DB0, 0xFA4D, 0x9DB1, 0xFA4E, 0x9DB2, 0xFA4F, + 0x9DB3, 0xFA50, 0x9DB4, 0xFA51, 0x9DB5, 0xFA52, 0x9DB6, 0xFA53, 0x9DB7, 0xFA54, 0x9DB8, 0xFA55, 0x9DB9, 0xFA56, 0x9DBA, 0xFA57, + 0x9DBB, 0xFA58, 0x9DBC, 0xFA59, 0x9DBD, 0xFA5A, 0x9DBE, 0xFA5B, 0x9DBF, 0xFA5C, 0x9DC0, 0xFA5D, 0x9DC1, 0xFA5E, 0x9DC2, 0xFA5F, + 0x9DC3, 0xFA60, 0x9DC4, 0xFA61, 0x9DC5, 0xFA62, 0x9DC6, 0xFA63, 0x9DC7, 0xFA64, 0x9DC8, 0xFA65, 0x9DC9, 0xFA66, 0x9DCA, 0xFA67, + 0x9DCB, 0xFA68, 0x9DCC, 0xFA69, 0x9DCD, 0xFA6A, 0x9DCE, 0xFA6B, 0x9DCF, 0xFA6C, 0x9DD0, 0xFA6D, 0x9DD1, 0xFA6E, 0x9DD2, 0xFA6F, + 0x9DD3, 0xFA70, 0x9DD4, 0xFA71, 0x9DD5, 0xFA72, 0x9DD6, 0xFA73, 0x9DD7, 0xFA74, 0x9DD8, 0xFA75, 0x9DD9, 0xFA76, 0x9DDA, 0xFA77, + 0x9DDB, 0xFA78, 0x9DDC, 0xFA79, 0x9DDD, 0xFA7A, 0x9DDE, 0xFA7B, 0x9DDF, 0xFA7C, 0x9DE0, 0xFA7D, 0x9DE1, 0xFA7E, 0x9DE2, 0xFA80, + 0x9DE3, 0xFA81, 0x9DE4, 0xFA82, 0x9DE5, 0xFA83, 0x9DE6, 0xFA84, 0x9DE7, 0xFA85, 0x9DE8, 0xFA86, 0x9DE9, 0xFA87, 0x9DEA, 0xFA88, + 0x9DEB, 0xFA89, 0x9DEC, 0xFA8A, 0x9DED, 0xFA8B, 0x9DEE, 0xFA8C, 0x9DEF, 0xFA8D, 0x9DF0, 0xFA8E, 0x9DF1, 0xFA8F, 0x9DF2, 0xFA90, + 0x9DF3, 0xFA91, 0x9DF4, 0xFA92, 0x9DF5, 0xFA93, 0x9DF6, 0xFA94, 0x9DF7, 0xFA95, 0x9DF8, 0xFA96, 0x9DF9, 0xFA97, 0x9DFA, 0xFA98, + 0x9DFB, 0xFA99, 0x9DFC, 0xFA9A, 0x9DFD, 0xFA9B, 0x9DFE, 0xFA9C, 0x9DFF, 0xFA9D, 0x9E00, 0xFA9E, 0x9E01, 0xFA9F, 0x9E02, 0xFAA0, + 0x9E03, 0xFB40, 0x9E04, 0xFB41, 0x9E05, 0xFB42, 0x9E06, 0xFB43, 0x9E07, 0xFB44, 0x9E08, 0xFB45, 0x9E09, 0xFB46, 0x9E0A, 0xFB47, + 0x9E0B, 0xFB48, 0x9E0C, 0xFB49, 0x9E0D, 0xFB4A, 0x9E0E, 0xFB4B, 0x9E0F, 0xFB4C, 0x9E10, 0xFB4D, 0x9E11, 0xFB4E, 0x9E12, 0xFB4F, + 0x9E13, 0xFB50, 0x9E14, 0xFB51, 0x9E15, 0xFB52, 0x9E16, 0xFB53, 0x9E17, 0xFB54, 0x9E18, 0xFB55, 0x9E19, 0xFB56, 0x9E1A, 0xFB57, + 0x9E1B, 0xFB58, 0x9E1C, 0xFB59, 0x9E1D, 0xFB5A, 0x9E1E, 0xFB5B, 0x9E1F, 0xC4F1, 0x9E20, 0xF0AF, 0x9E21, 0xBCA6, 0x9E22, 0xF0B0, + 0x9E23, 0xC3F9, 0x9E24, 0xFB5C, 0x9E25, 0xC5B8, 0x9E26, 0xD1BB, 0x9E27, 0xFB5D, 0x9E28, 0xF0B1, 0x9E29, 0xF0B2, 0x9E2A, 0xF0B3, + 0x9E2B, 0xF0B4, 0x9E2C, 0xF0B5, 0x9E2D, 0xD1BC, 0x9E2E, 0xFB5E, 0x9E2F, 0xD1EC, 0x9E30, 0xFB5F, 0x9E31, 0xF0B7, 0x9E32, 0xF0B6, + 0x9E33, 0xD4A7, 0x9E34, 0xFB60, 0x9E35, 0xCDD2, 0x9E36, 0xF0B8, 0x9E37, 0xF0BA, 0x9E38, 0xF0B9, 0x9E39, 0xF0BB, 0x9E3A, 0xF0BC, + 0x9E3B, 0xFB61, 0x9E3C, 0xFB62, 0x9E3D, 0xB8EB, 0x9E3E, 0xF0BD, 0x9E3F, 0xBAE8, 0x9E40, 0xFB63, 0x9E41, 0xF0BE, 0x9E42, 0xF0BF, + 0x9E43, 0xBEE9, 0x9E44, 0xF0C0, 0x9E45, 0xB6EC, 0x9E46, 0xF0C1, 0x9E47, 0xF0C2, 0x9E48, 0xF0C3, 0x9E49, 0xF0C4, 0x9E4A, 0xC8B5, + 0x9E4B, 0xF0C5, 0x9E4C, 0xF0C6, 0x9E4D, 0xFB64, 0x9E4E, 0xF0C7, 0x9E4F, 0xC5F4, 0x9E50, 0xFB65, 0x9E51, 0xF0C8, 0x9E52, 0xFB66, + 0x9E53, 0xFB67, 0x9E54, 0xFB68, 0x9E55, 0xF0C9, 0x9E56, 0xFB69, 0x9E57, 0xF0CA, 0x9E58, 0xF7BD, 0x9E59, 0xFB6A, 0x9E5A, 0xF0CB, + 0x9E5B, 0xF0CC, 0x9E5C, 0xF0CD, 0x9E5D, 0xFB6B, 0x9E5E, 0xF0CE, 0x9E5F, 0xFB6C, 0x9E60, 0xFB6D, 0x9E61, 0xFB6E, 0x9E62, 0xFB6F, + 0x9E63, 0xF0CF, 0x9E64, 0xBAD7, 0x9E65, 0xFB70, 0x9E66, 0xF0D0, 0x9E67, 0xF0D1, 0x9E68, 0xF0D2, 0x9E69, 0xF0D3, 0x9E6A, 0xF0D4, + 0x9E6B, 0xF0D5, 0x9E6C, 0xF0D6, 0x9E6D, 0xF0D8, 0x9E6E, 0xFB71, 0x9E6F, 0xFB72, 0x9E70, 0xD3A5, 0x9E71, 0xF0D7, 0x9E72, 0xFB73, + 0x9E73, 0xF0D9, 0x9E74, 0xFB74, 0x9E75, 0xFB75, 0x9E76, 0xFB76, 0x9E77, 0xFB77, 0x9E78, 0xFB78, 0x9E79, 0xFB79, 0x9E7A, 0xFB7A, + 0x9E7B, 0xFB7B, 0x9E7C, 0xFB7C, 0x9E7D, 0xFB7D, 0x9E7E, 0xF5BA, 0x9E7F, 0xC2B9, 0x9E80, 0xFB7E, 0x9E81, 0xFB80, 0x9E82, 0xF7E4, + 0x9E83, 0xFB81, 0x9E84, 0xFB82, 0x9E85, 0xFB83, 0x9E86, 0xFB84, 0x9E87, 0xF7E5, 0x9E88, 0xF7E6, 0x9E89, 0xFB85, 0x9E8A, 0xFB86, + 0x9E8B, 0xF7E7, 0x9E8C, 0xFB87, 0x9E8D, 0xFB88, 0x9E8E, 0xFB89, 0x9E8F, 0xFB8A, 0x9E90, 0xFB8B, 0x9E91, 0xFB8C, 0x9E92, 0xF7E8, + 0x9E93, 0xC2B4, 0x9E94, 0xFB8D, 0x9E95, 0xFB8E, 0x9E96, 0xFB8F, 0x9E97, 0xFB90, 0x9E98, 0xFB91, 0x9E99, 0xFB92, 0x9E9A, 0xFB93, + 0x9E9B, 0xFB94, 0x9E9C, 0xFB95, 0x9E9D, 0xF7EA, 0x9E9E, 0xFB96, 0x9E9F, 0xF7EB, 0x9EA0, 0xFB97, 0x9EA1, 0xFB98, 0x9EA2, 0xFB99, + 0x9EA3, 0xFB9A, 0x9EA4, 0xFB9B, 0x9EA5, 0xFB9C, 0x9EA6, 0xC2F3, 0x9EA7, 0xFB9D, 0x9EA8, 0xFB9E, 0x9EA9, 0xFB9F, 0x9EAA, 0xFBA0, + 0x9EAB, 0xFC40, 0x9EAC, 0xFC41, 0x9EAD, 0xFC42, 0x9EAE, 0xFC43, 0x9EAF, 0xFC44, 0x9EB0, 0xFC45, 0x9EB1, 0xFC46, 0x9EB2, 0xFC47, + 0x9EB3, 0xFC48, 0x9EB4, 0xF4F0, 0x9EB5, 0xFC49, 0x9EB6, 0xFC4A, 0x9EB7, 0xFC4B, 0x9EB8, 0xF4EF, 0x9EB9, 0xFC4C, 0x9EBA, 0xFC4D, + 0x9EBB, 0xC2E9, 0x9EBC, 0xFC4E, 0x9EBD, 0xF7E1, 0x9EBE, 0xF7E2, 0x9EBF, 0xFC4F, 0x9EC0, 0xFC50, 0x9EC1, 0xFC51, 0x9EC2, 0xFC52, + 0x9EC3, 0xFC53, 0x9EC4, 0xBBC6, 0x9EC5, 0xFC54, 0x9EC6, 0xFC55, 0x9EC7, 0xFC56, 0x9EC8, 0xFC57, 0x9EC9, 0xD9E4, 0x9ECA, 0xFC58, + 0x9ECB, 0xFC59, 0x9ECC, 0xFC5A, 0x9ECD, 0xCAF2, 0x9ECE, 0xC0E8, 0x9ECF, 0xF0A4, 0x9ED0, 0xFC5B, 0x9ED1, 0xBADA, 0x9ED2, 0xFC5C, + 0x9ED3, 0xFC5D, 0x9ED4, 0xC7AD, 0x9ED5, 0xFC5E, 0x9ED6, 0xFC5F, 0x9ED7, 0xFC60, 0x9ED8, 0xC4AC, 0x9ED9, 0xFC61, 0x9EDA, 0xFC62, + 0x9EDB, 0xF7EC, 0x9EDC, 0xF7ED, 0x9EDD, 0xF7EE, 0x9EDE, 0xFC63, 0x9EDF, 0xF7F0, 0x9EE0, 0xF7EF, 0x9EE1, 0xFC64, 0x9EE2, 0xF7F1, + 0x9EE3, 0xFC65, 0x9EE4, 0xFC66, 0x9EE5, 0xF7F4, 0x9EE6, 0xFC67, 0x9EE7, 0xF7F3, 0x9EE8, 0xFC68, 0x9EE9, 0xF7F2, 0x9EEA, 0xF7F5, + 0x9EEB, 0xFC69, 0x9EEC, 0xFC6A, 0x9EED, 0xFC6B, 0x9EEE, 0xFC6C, 0x9EEF, 0xF7F6, 0x9EF0, 0xFC6D, 0x9EF1, 0xFC6E, 0x9EF2, 0xFC6F, + 0x9EF3, 0xFC70, 0x9EF4, 0xFC71, 0x9EF5, 0xFC72, 0x9EF6, 0xFC73, 0x9EF7, 0xFC74, 0x9EF8, 0xFC75, 0x9EF9, 0xEDE9, 0x9EFA, 0xFC76, + 0x9EFB, 0xEDEA, 0x9EFC, 0xEDEB, 0x9EFD, 0xFC77, 0x9EFE, 0xF6BC, 0x9EFF, 0xFC78, 0x9F00, 0xFC79, 0x9F01, 0xFC7A, 0x9F02, 0xFC7B, + 0x9F03, 0xFC7C, 0x9F04, 0xFC7D, 0x9F05, 0xFC7E, 0x9F06, 0xFC80, 0x9F07, 0xFC81, 0x9F08, 0xFC82, 0x9F09, 0xFC83, 0x9F0A, 0xFC84, + 0x9F0B, 0xF6BD, 0x9F0C, 0xFC85, 0x9F0D, 0xF6BE, 0x9F0E, 0xB6A6, 0x9F0F, 0xFC86, 0x9F10, 0xD8BE, 0x9F11, 0xFC87, 0x9F12, 0xFC88, + 0x9F13, 0xB9C4, 0x9F14, 0xFC89, 0x9F15, 0xFC8A, 0x9F16, 0xFC8B, 0x9F17, 0xD8BB, 0x9F18, 0xFC8C, 0x9F19, 0xDCB1, 0x9F1A, 0xFC8D, + 0x9F1B, 0xFC8E, 0x9F1C, 0xFC8F, 0x9F1D, 0xFC90, 0x9F1E, 0xFC91, 0x9F1F, 0xFC92, 0x9F20, 0xCAF3, 0x9F21, 0xFC93, 0x9F22, 0xF7F7, + 0x9F23, 0xFC94, 0x9F24, 0xFC95, 0x9F25, 0xFC96, 0x9F26, 0xFC97, 0x9F27, 0xFC98, 0x9F28, 0xFC99, 0x9F29, 0xFC9A, 0x9F2A, 0xFC9B, + 0x9F2B, 0xFC9C, 0x9F2C, 0xF7F8, 0x9F2D, 0xFC9D, 0x9F2E, 0xFC9E, 0x9F2F, 0xF7F9, 0x9F30, 0xFC9F, 0x9F31, 0xFCA0, 0x9F32, 0xFD40, + 0x9F33, 0xFD41, 0x9F34, 0xFD42, 0x9F35, 0xFD43, 0x9F36, 0xFD44, 0x9F37, 0xF7FB, 0x9F38, 0xFD45, 0x9F39, 0xF7FA, 0x9F3A, 0xFD46, + 0x9F3B, 0xB1C7, 0x9F3C, 0xFD47, 0x9F3D, 0xF7FC, 0x9F3E, 0xF7FD, 0x9F3F, 0xFD48, 0x9F40, 0xFD49, 0x9F41, 0xFD4A, 0x9F42, 0xFD4B, + 0x9F43, 0xFD4C, 0x9F44, 0xF7FE, 0x9F45, 0xFD4D, 0x9F46, 0xFD4E, 0x9F47, 0xFD4F, 0x9F48, 0xFD50, 0x9F49, 0xFD51, 0x9F4A, 0xFD52, + 0x9F4B, 0xFD53, 0x9F4C, 0xFD54, 0x9F4D, 0xFD55, 0x9F4E, 0xFD56, 0x9F4F, 0xFD57, 0x9F50, 0xC6EB, 0x9F51, 0xECB4, 0x9F52, 0xFD58, + 0x9F53, 0xFD59, 0x9F54, 0xFD5A, 0x9F55, 0xFD5B, 0x9F56, 0xFD5C, 0x9F57, 0xFD5D, 0x9F58, 0xFD5E, 0x9F59, 0xFD5F, 0x9F5A, 0xFD60, + 0x9F5B, 0xFD61, 0x9F5C, 0xFD62, 0x9F5D, 0xFD63, 0x9F5E, 0xFD64, 0x9F5F, 0xFD65, 0x9F60, 0xFD66, 0x9F61, 0xFD67, 0x9F62, 0xFD68, + 0x9F63, 0xFD69, 0x9F64, 0xFD6A, 0x9F65, 0xFD6B, 0x9F66, 0xFD6C, 0x9F67, 0xFD6D, 0x9F68, 0xFD6E, 0x9F69, 0xFD6F, 0x9F6A, 0xFD70, + 0x9F6B, 0xFD71, 0x9F6C, 0xFD72, 0x9F6D, 0xFD73, 0x9F6E, 0xFD74, 0x9F6F, 0xFD75, 0x9F70, 0xFD76, 0x9F71, 0xFD77, 0x9F72, 0xFD78, + 0x9F73, 0xFD79, 0x9F74, 0xFD7A, 0x9F75, 0xFD7B, 0x9F76, 0xFD7C, 0x9F77, 0xFD7D, 0x9F78, 0xFD7E, 0x9F79, 0xFD80, 0x9F7A, 0xFD81, + 0x9F7B, 0xFD82, 0x9F7C, 0xFD83, 0x9F7D, 0xFD84, 0x9F7E, 0xFD85, 0x9F7F, 0xB3DD, 0x9F80, 0xF6B3, 0x9F81, 0xFD86, 0x9F82, 0xFD87, + 0x9F83, 0xF6B4, 0x9F84, 0xC1E4, 0x9F85, 0xF6B5, 0x9F86, 0xF6B6, 0x9F87, 0xF6B7, 0x9F88, 0xF6B8, 0x9F89, 0xF6B9, 0x9F8A, 0xF6BA, + 0x9F8B, 0xC8A3, 0x9F8C, 0xF6BB, 0x9F8D, 0xFD88, 0x9F8E, 0xFD89, 0x9F8F, 0xFD8A, 0x9F90, 0xFD8B, 0x9F91, 0xFD8C, 0x9F92, 0xFD8D, + 0x9F93, 0xFD8E, 0x9F94, 0xFD8F, 0x9F95, 0xFD90, 0x9F96, 0xFD91, 0x9F97, 0xFD92, 0x9F98, 0xFD93, 0x9F99, 0xC1FA, 0x9F9A, 0xB9A8, + 0x9F9B, 0xEDE8, 0x9F9C, 0xFD94, 0x9F9D, 0xFD95, 0x9F9E, 0xFD96, 0x9F9F, 0xB9EA, 0x9FA0, 0xD9DF, 0x9FA1, 0xFD97, 0x9FA2, 0xFD98, + 0x9FA3, 0xFD99, 0x9FA4, 0xFD9A, 0x9FA5, 0xFD9B, 0xF92C, 0xFD9C, 0xF979, 0xFD9D, 0xF995, 0xFD9E, 0xF9E7, 0xFD9F, 0xF9F1, 0xFDA0, + 0xFA0C, 0xFE40, 0xFA0D, 0xFE41, 0xFA0E, 0xFE42, 0xFA0F, 0xFE43, 0xFA11, 0xFE44, 0xFA13, 0xFE45, 0xFA14, 0xFE46, 0xFA18, 0xFE47, + 0xFA1F, 0xFE48, 0xFA20, 0xFE49, 0xFA21, 0xFE4A, 0xFA23, 0xFE4B, 0xFA24, 0xFE4C, 0xFA27, 0xFE4D, 0xFA28, 0xFE4E, 0xFA29, 0xFE4F, + 0xFE30, 0xA955, 0xFE31, 0xA6F2, 0xFE33, 0xA6F4, 0xFE34, 0xA6F5, 0xFE35, 0xA6E0, 0xFE36, 0xA6E1, 0xFE37, 0xA6F0, 0xFE38, 0xA6F1, + 0xFE39, 0xA6E2, 0xFE3A, 0xA6E3, 0xFE3B, 0xA6EE, 0xFE3C, 0xA6EF, 0xFE3D, 0xA6E6, 0xFE3E, 0xA6E7, 0xFE3F, 0xA6E4, 0xFE40, 0xA6E5, + 0xFE41, 0xA6E8, 0xFE42, 0xA6E9, 0xFE43, 0xA6EA, 0xFE44, 0xA6EB, 0xFE49, 0xA968, 0xFE4A, 0xA969, 0xFE4B, 0xA96A, 0xFE4C, 0xA96B, + 0xFE4D, 0xA96C, 0xFE4E, 0xA96D, 0xFE4F, 0xA96E, 0xFE50, 0xA96F, 0xFE51, 0xA970, 0xFE52, 0xA971, 0xFE54, 0xA972, 0xFE55, 0xA973, + 0xFE56, 0xA974, 0xFE57, 0xA975, 0xFE59, 0xA976, 0xFE5A, 0xA977, 0xFE5B, 0xA978, 0xFE5C, 0xA979, 0xFE5D, 0xA97A, 0xFE5E, 0xA97B, + 0xFE5F, 0xA97C, 0xFE60, 0xA97D, 0xFE61, 0xA97E, 0xFE62, 0xA980, 0xFE63, 0xA981, 0xFE64, 0xA982, 0xFE65, 0xA983, 0xFE66, 0xA984, + 0xFE68, 0xA985, 0xFE69, 0xA986, 0xFE6A, 0xA987, 0xFE6B, 0xA988, 0xFF01, 0xA3A1, 0xFF02, 0xA3A2, 0xFF03, 0xA3A3, 0xFF04, 0xA1E7, + 0xFF05, 0xA3A5, 0xFF06, 0xA3A6, 0xFF07, 0xA3A7, 0xFF08, 0xA3A8, 0xFF09, 0xA3A9, 0xFF0A, 0xA3AA, 0xFF0B, 0xA3AB, 0xFF0C, 0xA3AC, + 0xFF0D, 0xA3AD, 0xFF0E, 0xA3AE, 0xFF0F, 0xA3AF, 0xFF10, 0xA3B0, 0xFF11, 0xA3B1, 0xFF12, 0xA3B2, 0xFF13, 0xA3B3, 0xFF14, 0xA3B4, + 0xFF15, 0xA3B5, 0xFF16, 0xA3B6, 0xFF17, 0xA3B7, 0xFF18, 0xA3B8, 0xFF19, 0xA3B9, 0xFF1A, 0xA3BA, 0xFF1B, 0xA3BB, 0xFF1C, 0xA3BC, + 0xFF1D, 0xA3BD, 0xFF1E, 0xA3BE, 0xFF1F, 0xA3BF, 0xFF20, 0xA3C0, 0xFF21, 0xA3C1, 0xFF22, 0xA3C2, 0xFF23, 0xA3C3, 0xFF24, 0xA3C4, + 0xFF25, 0xA3C5, 0xFF26, 0xA3C6, 0xFF27, 0xA3C7, 0xFF28, 0xA3C8, 0xFF29, 0xA3C9, 0xFF2A, 0xA3CA, 0xFF2B, 0xA3CB, 0xFF2C, 0xA3CC, + 0xFF2D, 0xA3CD, 0xFF2E, 0xA3CE, 0xFF2F, 0xA3CF, 0xFF30, 0xA3D0, 0xFF31, 0xA3D1, 0xFF32, 0xA3D2, 0xFF33, 0xA3D3, 0xFF34, 0xA3D4, + 0xFF35, 0xA3D5, 0xFF36, 0xA3D6, 0xFF37, 0xA3D7, 0xFF38, 0xA3D8, 0xFF39, 0xA3D9, 0xFF3A, 0xA3DA, 0xFF3B, 0xA3DB, 0xFF3C, 0xA3DC, + 0xFF3D, 0xA3DD, 0xFF3E, 0xA3DE, 0xFF3F, 0xA3DF, 0xFF40, 0xA3E0, 0xFF41, 0xA3E1, 0xFF42, 0xA3E2, 0xFF43, 0xA3E3, 0xFF44, 0xA3E4, + 0xFF45, 0xA3E5, 0xFF46, 0xA3E6, 0xFF47, 0xA3E7, 0xFF48, 0xA3E8, 0xFF49, 0xA3E9, 0xFF4A, 0xA3EA, 0xFF4B, 0xA3EB, 0xFF4C, 0xA3EC, + 0xFF4D, 0xA3ED, 0xFF4E, 0xA3EE, 0xFF4F, 0xA3EF, 0xFF50, 0xA3F0, 0xFF51, 0xA3F1, 0xFF52, 0xA3F2, 0xFF53, 0xA3F3, 0xFF54, 0xA3F4, + 0xFF55, 0xA3F5, 0xFF56, 0xA3F6, 0xFF57, 0xA3F7, 0xFF58, 0xA3F8, 0xFF59, 0xA3F9, 0xFF5A, 0xA3FA, 0xFF5B, 0xA3FB, 0xFF5C, 0xA3FC, + 0xFF5D, 0xA3FD, 0xFF5E, 0xA1AB, 0xFFE0, 0xA1E9, 0xFFE1, 0xA1EA, 0xFFE2, 0xA956, 0xFFE3, 0xA3FE, 0xFFE4, 0xA957, 0xFFE5, 0xA3A4, + 0, 0 +}; + +static const WCHAR oem2uni936[] = { /* GBK --> Unicode pairs */ + 0x0080, 0x20AC, 0x8140, 0x4E02, 0x8141, 0x4E04, 0x8142, 0x4E05, 0x8143, 0x4E06, 0x8144, 0x4E0F, 0x8145, 0x4E12, 0x8146, 0x4E17, + 0x8147, 0x4E1F, 0x8148, 0x4E20, 0x8149, 0x4E21, 0x814A, 0x4E23, 0x814B, 0x4E26, 0x814C, 0x4E29, 0x814D, 0x4E2E, 0x814E, 0x4E2F, + 0x814F, 0x4E31, 0x8150, 0x4E33, 0x8151, 0x4E35, 0x8152, 0x4E37, 0x8153, 0x4E3C, 0x8154, 0x4E40, 0x8155, 0x4E41, 0x8156, 0x4E42, + 0x8157, 0x4E44, 0x8158, 0x4E46, 0x8159, 0x4E4A, 0x815A, 0x4E51, 0x815B, 0x4E55, 0x815C, 0x4E57, 0x815D, 0x4E5A, 0x815E, 0x4E5B, + 0x815F, 0x4E62, 0x8160, 0x4E63, 0x8161, 0x4E64, 0x8162, 0x4E65, 0x8163, 0x4E67, 0x8164, 0x4E68, 0x8165, 0x4E6A, 0x8166, 0x4E6B, + 0x8167, 0x4E6C, 0x8168, 0x4E6D, 0x8169, 0x4E6E, 0x816A, 0x4E6F, 0x816B, 0x4E72, 0x816C, 0x4E74, 0x816D, 0x4E75, 0x816E, 0x4E76, + 0x816F, 0x4E77, 0x8170, 0x4E78, 0x8171, 0x4E79, 0x8172, 0x4E7A, 0x8173, 0x4E7B, 0x8174, 0x4E7C, 0x8175, 0x4E7D, 0x8176, 0x4E7F, + 0x8177, 0x4E80, 0x8178, 0x4E81, 0x8179, 0x4E82, 0x817A, 0x4E83, 0x817B, 0x4E84, 0x817C, 0x4E85, 0x817D, 0x4E87, 0x817E, 0x4E8A, + 0x8180, 0x4E90, 0x8181, 0x4E96, 0x8182, 0x4E97, 0x8183, 0x4E99, 0x8184, 0x4E9C, 0x8185, 0x4E9D, 0x8186, 0x4E9E, 0x8187, 0x4EA3, + 0x8188, 0x4EAA, 0x8189, 0x4EAF, 0x818A, 0x4EB0, 0x818B, 0x4EB1, 0x818C, 0x4EB4, 0x818D, 0x4EB6, 0x818E, 0x4EB7, 0x818F, 0x4EB8, + 0x8190, 0x4EB9, 0x8191, 0x4EBC, 0x8192, 0x4EBD, 0x8193, 0x4EBE, 0x8194, 0x4EC8, 0x8195, 0x4ECC, 0x8196, 0x4ECF, 0x8197, 0x4ED0, + 0x8198, 0x4ED2, 0x8199, 0x4EDA, 0x819A, 0x4EDB, 0x819B, 0x4EDC, 0x819C, 0x4EE0, 0x819D, 0x4EE2, 0x819E, 0x4EE6, 0x819F, 0x4EE7, + 0x81A0, 0x4EE9, 0x81A1, 0x4EED, 0x81A2, 0x4EEE, 0x81A3, 0x4EEF, 0x81A4, 0x4EF1, 0x81A5, 0x4EF4, 0x81A6, 0x4EF8, 0x81A7, 0x4EF9, + 0x81A8, 0x4EFA, 0x81A9, 0x4EFC, 0x81AA, 0x4EFE, 0x81AB, 0x4F00, 0x81AC, 0x4F02, 0x81AD, 0x4F03, 0x81AE, 0x4F04, 0x81AF, 0x4F05, + 0x81B0, 0x4F06, 0x81B1, 0x4F07, 0x81B2, 0x4F08, 0x81B3, 0x4F0B, 0x81B4, 0x4F0C, 0x81B5, 0x4F12, 0x81B6, 0x4F13, 0x81B7, 0x4F14, + 0x81B8, 0x4F15, 0x81B9, 0x4F16, 0x81BA, 0x4F1C, 0x81BB, 0x4F1D, 0x81BC, 0x4F21, 0x81BD, 0x4F23, 0x81BE, 0x4F28, 0x81BF, 0x4F29, + 0x81C0, 0x4F2C, 0x81C1, 0x4F2D, 0x81C2, 0x4F2E, 0x81C3, 0x4F31, 0x81C4, 0x4F33, 0x81C5, 0x4F35, 0x81C6, 0x4F37, 0x81C7, 0x4F39, + 0x81C8, 0x4F3B, 0x81C9, 0x4F3E, 0x81CA, 0x4F3F, 0x81CB, 0x4F40, 0x81CC, 0x4F41, 0x81CD, 0x4F42, 0x81CE, 0x4F44, 0x81CF, 0x4F45, + 0x81D0, 0x4F47, 0x81D1, 0x4F48, 0x81D2, 0x4F49, 0x81D3, 0x4F4A, 0x81D4, 0x4F4B, 0x81D5, 0x4F4C, 0x81D6, 0x4F52, 0x81D7, 0x4F54, + 0x81D8, 0x4F56, 0x81D9, 0x4F61, 0x81DA, 0x4F62, 0x81DB, 0x4F66, 0x81DC, 0x4F68, 0x81DD, 0x4F6A, 0x81DE, 0x4F6B, 0x81DF, 0x4F6D, + 0x81E0, 0x4F6E, 0x81E1, 0x4F71, 0x81E2, 0x4F72, 0x81E3, 0x4F75, 0x81E4, 0x4F77, 0x81E5, 0x4F78, 0x81E6, 0x4F79, 0x81E7, 0x4F7A, + 0x81E8, 0x4F7D, 0x81E9, 0x4F80, 0x81EA, 0x4F81, 0x81EB, 0x4F82, 0x81EC, 0x4F85, 0x81ED, 0x4F86, 0x81EE, 0x4F87, 0x81EF, 0x4F8A, + 0x81F0, 0x4F8C, 0x81F1, 0x4F8E, 0x81F2, 0x4F90, 0x81F3, 0x4F92, 0x81F4, 0x4F93, 0x81F5, 0x4F95, 0x81F6, 0x4F96, 0x81F7, 0x4F98, + 0x81F8, 0x4F99, 0x81F9, 0x4F9A, 0x81FA, 0x4F9C, 0x81FB, 0x4F9E, 0x81FC, 0x4F9F, 0x81FD, 0x4FA1, 0x81FE, 0x4FA2, 0x8240, 0x4FA4, + 0x8241, 0x4FAB, 0x8242, 0x4FAD, 0x8243, 0x4FB0, 0x8244, 0x4FB1, 0x8245, 0x4FB2, 0x8246, 0x4FB3, 0x8247, 0x4FB4, 0x8248, 0x4FB6, + 0x8249, 0x4FB7, 0x824A, 0x4FB8, 0x824B, 0x4FB9, 0x824C, 0x4FBA, 0x824D, 0x4FBB, 0x824E, 0x4FBC, 0x824F, 0x4FBD, 0x8250, 0x4FBE, + 0x8251, 0x4FC0, 0x8252, 0x4FC1, 0x8253, 0x4FC2, 0x8254, 0x4FC6, 0x8255, 0x4FC7, 0x8256, 0x4FC8, 0x8257, 0x4FC9, 0x8258, 0x4FCB, + 0x8259, 0x4FCC, 0x825A, 0x4FCD, 0x825B, 0x4FD2, 0x825C, 0x4FD3, 0x825D, 0x4FD4, 0x825E, 0x4FD5, 0x825F, 0x4FD6, 0x8260, 0x4FD9, + 0x8261, 0x4FDB, 0x8262, 0x4FE0, 0x8263, 0x4FE2, 0x8264, 0x4FE4, 0x8265, 0x4FE5, 0x8266, 0x4FE7, 0x8267, 0x4FEB, 0x8268, 0x4FEC, + 0x8269, 0x4FF0, 0x826A, 0x4FF2, 0x826B, 0x4FF4, 0x826C, 0x4FF5, 0x826D, 0x4FF6, 0x826E, 0x4FF7, 0x826F, 0x4FF9, 0x8270, 0x4FFB, + 0x8271, 0x4FFC, 0x8272, 0x4FFD, 0x8273, 0x4FFF, 0x8274, 0x5000, 0x8275, 0x5001, 0x8276, 0x5002, 0x8277, 0x5003, 0x8278, 0x5004, + 0x8279, 0x5005, 0x827A, 0x5006, 0x827B, 0x5007, 0x827C, 0x5008, 0x827D, 0x5009, 0x827E, 0x500A, 0x8280, 0x500B, 0x8281, 0x500E, + 0x8282, 0x5010, 0x8283, 0x5011, 0x8284, 0x5013, 0x8285, 0x5015, 0x8286, 0x5016, 0x8287, 0x5017, 0x8288, 0x501B, 0x8289, 0x501D, + 0x828A, 0x501E, 0x828B, 0x5020, 0x828C, 0x5022, 0x828D, 0x5023, 0x828E, 0x5024, 0x828F, 0x5027, 0x8290, 0x502B, 0x8291, 0x502F, + 0x8292, 0x5030, 0x8293, 0x5031, 0x8294, 0x5032, 0x8295, 0x5033, 0x8296, 0x5034, 0x8297, 0x5035, 0x8298, 0x5036, 0x8299, 0x5037, + 0x829A, 0x5038, 0x829B, 0x5039, 0x829C, 0x503B, 0x829D, 0x503D, 0x829E, 0x503F, 0x829F, 0x5040, 0x82A0, 0x5041, 0x82A1, 0x5042, + 0x82A2, 0x5044, 0x82A3, 0x5045, 0x82A4, 0x5046, 0x82A5, 0x5049, 0x82A6, 0x504A, 0x82A7, 0x504B, 0x82A8, 0x504D, 0x82A9, 0x5050, + 0x82AA, 0x5051, 0x82AB, 0x5052, 0x82AC, 0x5053, 0x82AD, 0x5054, 0x82AE, 0x5056, 0x82AF, 0x5057, 0x82B0, 0x5058, 0x82B1, 0x5059, + 0x82B2, 0x505B, 0x82B3, 0x505D, 0x82B4, 0x505E, 0x82B5, 0x505F, 0x82B6, 0x5060, 0x82B7, 0x5061, 0x82B8, 0x5062, 0x82B9, 0x5063, + 0x82BA, 0x5064, 0x82BB, 0x5066, 0x82BC, 0x5067, 0x82BD, 0x5068, 0x82BE, 0x5069, 0x82BF, 0x506A, 0x82C0, 0x506B, 0x82C1, 0x506D, + 0x82C2, 0x506E, 0x82C3, 0x506F, 0x82C4, 0x5070, 0x82C5, 0x5071, 0x82C6, 0x5072, 0x82C7, 0x5073, 0x82C8, 0x5074, 0x82C9, 0x5075, + 0x82CA, 0x5078, 0x82CB, 0x5079, 0x82CC, 0x507A, 0x82CD, 0x507C, 0x82CE, 0x507D, 0x82CF, 0x5081, 0x82D0, 0x5082, 0x82D1, 0x5083, + 0x82D2, 0x5084, 0x82D3, 0x5086, 0x82D4, 0x5087, 0x82D5, 0x5089, 0x82D6, 0x508A, 0x82D7, 0x508B, 0x82D8, 0x508C, 0x82D9, 0x508E, + 0x82DA, 0x508F, 0x82DB, 0x5090, 0x82DC, 0x5091, 0x82DD, 0x5092, 0x82DE, 0x5093, 0x82DF, 0x5094, 0x82E0, 0x5095, 0x82E1, 0x5096, + 0x82E2, 0x5097, 0x82E3, 0x5098, 0x82E4, 0x5099, 0x82E5, 0x509A, 0x82E6, 0x509B, 0x82E7, 0x509C, 0x82E8, 0x509D, 0x82E9, 0x509E, + 0x82EA, 0x509F, 0x82EB, 0x50A0, 0x82EC, 0x50A1, 0x82ED, 0x50A2, 0x82EE, 0x50A4, 0x82EF, 0x50A6, 0x82F0, 0x50AA, 0x82F1, 0x50AB, + 0x82F2, 0x50AD, 0x82F3, 0x50AE, 0x82F4, 0x50AF, 0x82F5, 0x50B0, 0x82F6, 0x50B1, 0x82F7, 0x50B3, 0x82F8, 0x50B4, 0x82F9, 0x50B5, + 0x82FA, 0x50B6, 0x82FB, 0x50B7, 0x82FC, 0x50B8, 0x82FD, 0x50B9, 0x82FE, 0x50BC, 0x8340, 0x50BD, 0x8341, 0x50BE, 0x8342, 0x50BF, + 0x8343, 0x50C0, 0x8344, 0x50C1, 0x8345, 0x50C2, 0x8346, 0x50C3, 0x8347, 0x50C4, 0x8348, 0x50C5, 0x8349, 0x50C6, 0x834A, 0x50C7, + 0x834B, 0x50C8, 0x834C, 0x50C9, 0x834D, 0x50CA, 0x834E, 0x50CB, 0x834F, 0x50CC, 0x8350, 0x50CD, 0x8351, 0x50CE, 0x8352, 0x50D0, + 0x8353, 0x50D1, 0x8354, 0x50D2, 0x8355, 0x50D3, 0x8356, 0x50D4, 0x8357, 0x50D5, 0x8358, 0x50D7, 0x8359, 0x50D8, 0x835A, 0x50D9, + 0x835B, 0x50DB, 0x835C, 0x50DC, 0x835D, 0x50DD, 0x835E, 0x50DE, 0x835F, 0x50DF, 0x8360, 0x50E0, 0x8361, 0x50E1, 0x8362, 0x50E2, + 0x8363, 0x50E3, 0x8364, 0x50E4, 0x8365, 0x50E5, 0x8366, 0x50E8, 0x8367, 0x50E9, 0x8368, 0x50EA, 0x8369, 0x50EB, 0x836A, 0x50EF, + 0x836B, 0x50F0, 0x836C, 0x50F1, 0x836D, 0x50F2, 0x836E, 0x50F4, 0x836F, 0x50F6, 0x8370, 0x50F7, 0x8371, 0x50F8, 0x8372, 0x50F9, + 0x8373, 0x50FA, 0x8374, 0x50FC, 0x8375, 0x50FD, 0x8376, 0x50FE, 0x8377, 0x50FF, 0x8378, 0x5100, 0x8379, 0x5101, 0x837A, 0x5102, + 0x837B, 0x5103, 0x837C, 0x5104, 0x837D, 0x5105, 0x837E, 0x5108, 0x8380, 0x5109, 0x8381, 0x510A, 0x8382, 0x510C, 0x8383, 0x510D, + 0x8384, 0x510E, 0x8385, 0x510F, 0x8386, 0x5110, 0x8387, 0x5111, 0x8388, 0x5113, 0x8389, 0x5114, 0x838A, 0x5115, 0x838B, 0x5116, + 0x838C, 0x5117, 0x838D, 0x5118, 0x838E, 0x5119, 0x838F, 0x511A, 0x8390, 0x511B, 0x8391, 0x511C, 0x8392, 0x511D, 0x8393, 0x511E, + 0x8394, 0x511F, 0x8395, 0x5120, 0x8396, 0x5122, 0x8397, 0x5123, 0x8398, 0x5124, 0x8399, 0x5125, 0x839A, 0x5126, 0x839B, 0x5127, + 0x839C, 0x5128, 0x839D, 0x5129, 0x839E, 0x512A, 0x839F, 0x512B, 0x83A0, 0x512C, 0x83A1, 0x512D, 0x83A2, 0x512E, 0x83A3, 0x512F, + 0x83A4, 0x5130, 0x83A5, 0x5131, 0x83A6, 0x5132, 0x83A7, 0x5133, 0x83A8, 0x5134, 0x83A9, 0x5135, 0x83AA, 0x5136, 0x83AB, 0x5137, + 0x83AC, 0x5138, 0x83AD, 0x5139, 0x83AE, 0x513A, 0x83AF, 0x513B, 0x83B0, 0x513C, 0x83B1, 0x513D, 0x83B2, 0x513E, 0x83B3, 0x5142, + 0x83B4, 0x5147, 0x83B5, 0x514A, 0x83B6, 0x514C, 0x83B7, 0x514E, 0x83B8, 0x514F, 0x83B9, 0x5150, 0x83BA, 0x5152, 0x83BB, 0x5153, + 0x83BC, 0x5157, 0x83BD, 0x5158, 0x83BE, 0x5159, 0x83BF, 0x515B, 0x83C0, 0x515D, 0x83C1, 0x515E, 0x83C2, 0x515F, 0x83C3, 0x5160, + 0x83C4, 0x5161, 0x83C5, 0x5163, 0x83C6, 0x5164, 0x83C7, 0x5166, 0x83C8, 0x5167, 0x83C9, 0x5169, 0x83CA, 0x516A, 0x83CB, 0x516F, + 0x83CC, 0x5172, 0x83CD, 0x517A, 0x83CE, 0x517E, 0x83CF, 0x517F, 0x83D0, 0x5183, 0x83D1, 0x5184, 0x83D2, 0x5186, 0x83D3, 0x5187, + 0x83D4, 0x518A, 0x83D5, 0x518B, 0x83D6, 0x518E, 0x83D7, 0x518F, 0x83D8, 0x5190, 0x83D9, 0x5191, 0x83DA, 0x5193, 0x83DB, 0x5194, + 0x83DC, 0x5198, 0x83DD, 0x519A, 0x83DE, 0x519D, 0x83DF, 0x519E, 0x83E0, 0x519F, 0x83E1, 0x51A1, 0x83E2, 0x51A3, 0x83E3, 0x51A6, + 0x83E4, 0x51A7, 0x83E5, 0x51A8, 0x83E6, 0x51A9, 0x83E7, 0x51AA, 0x83E8, 0x51AD, 0x83E9, 0x51AE, 0x83EA, 0x51B4, 0x83EB, 0x51B8, + 0x83EC, 0x51B9, 0x83ED, 0x51BA, 0x83EE, 0x51BE, 0x83EF, 0x51BF, 0x83F0, 0x51C1, 0x83F1, 0x51C2, 0x83F2, 0x51C3, 0x83F3, 0x51C5, + 0x83F4, 0x51C8, 0x83F5, 0x51CA, 0x83F6, 0x51CD, 0x83F7, 0x51CE, 0x83F8, 0x51D0, 0x83F9, 0x51D2, 0x83FA, 0x51D3, 0x83FB, 0x51D4, + 0x83FC, 0x51D5, 0x83FD, 0x51D6, 0x83FE, 0x51D7, 0x8440, 0x51D8, 0x8441, 0x51D9, 0x8442, 0x51DA, 0x8443, 0x51DC, 0x8444, 0x51DE, + 0x8445, 0x51DF, 0x8446, 0x51E2, 0x8447, 0x51E3, 0x8448, 0x51E5, 0x8449, 0x51E6, 0x844A, 0x51E7, 0x844B, 0x51E8, 0x844C, 0x51E9, + 0x844D, 0x51EA, 0x844E, 0x51EC, 0x844F, 0x51EE, 0x8450, 0x51F1, 0x8451, 0x51F2, 0x8452, 0x51F4, 0x8453, 0x51F7, 0x8454, 0x51FE, + 0x8455, 0x5204, 0x8456, 0x5205, 0x8457, 0x5209, 0x8458, 0x520B, 0x8459, 0x520C, 0x845A, 0x520F, 0x845B, 0x5210, 0x845C, 0x5213, + 0x845D, 0x5214, 0x845E, 0x5215, 0x845F, 0x521C, 0x8460, 0x521E, 0x8461, 0x521F, 0x8462, 0x5221, 0x8463, 0x5222, 0x8464, 0x5223, + 0x8465, 0x5225, 0x8466, 0x5226, 0x8467, 0x5227, 0x8468, 0x522A, 0x8469, 0x522C, 0x846A, 0x522F, 0x846B, 0x5231, 0x846C, 0x5232, + 0x846D, 0x5234, 0x846E, 0x5235, 0x846F, 0x523C, 0x8470, 0x523E, 0x8471, 0x5244, 0x8472, 0x5245, 0x8473, 0x5246, 0x8474, 0x5247, + 0x8475, 0x5248, 0x8476, 0x5249, 0x8477, 0x524B, 0x8478, 0x524E, 0x8479, 0x524F, 0x847A, 0x5252, 0x847B, 0x5253, 0x847C, 0x5255, + 0x847D, 0x5257, 0x847E, 0x5258, 0x8480, 0x5259, 0x8481, 0x525A, 0x8482, 0x525B, 0x8483, 0x525D, 0x8484, 0x525F, 0x8485, 0x5260, + 0x8486, 0x5262, 0x8487, 0x5263, 0x8488, 0x5264, 0x8489, 0x5266, 0x848A, 0x5268, 0x848B, 0x526B, 0x848C, 0x526C, 0x848D, 0x526D, + 0x848E, 0x526E, 0x848F, 0x5270, 0x8490, 0x5271, 0x8491, 0x5273, 0x8492, 0x5274, 0x8493, 0x5275, 0x8494, 0x5276, 0x8495, 0x5277, + 0x8496, 0x5278, 0x8497, 0x5279, 0x8498, 0x527A, 0x8499, 0x527B, 0x849A, 0x527C, 0x849B, 0x527E, 0x849C, 0x5280, 0x849D, 0x5283, + 0x849E, 0x5284, 0x849F, 0x5285, 0x84A0, 0x5286, 0x84A1, 0x5287, 0x84A2, 0x5289, 0x84A3, 0x528A, 0x84A4, 0x528B, 0x84A5, 0x528C, + 0x84A6, 0x528D, 0x84A7, 0x528E, 0x84A8, 0x528F, 0x84A9, 0x5291, 0x84AA, 0x5292, 0x84AB, 0x5294, 0x84AC, 0x5295, 0x84AD, 0x5296, + 0x84AE, 0x5297, 0x84AF, 0x5298, 0x84B0, 0x5299, 0x84B1, 0x529A, 0x84B2, 0x529C, 0x84B3, 0x52A4, 0x84B4, 0x52A5, 0x84B5, 0x52A6, + 0x84B6, 0x52A7, 0x84B7, 0x52AE, 0x84B8, 0x52AF, 0x84B9, 0x52B0, 0x84BA, 0x52B4, 0x84BB, 0x52B5, 0x84BC, 0x52B6, 0x84BD, 0x52B7, + 0x84BE, 0x52B8, 0x84BF, 0x52B9, 0x84C0, 0x52BA, 0x84C1, 0x52BB, 0x84C2, 0x52BC, 0x84C3, 0x52BD, 0x84C4, 0x52C0, 0x84C5, 0x52C1, + 0x84C6, 0x52C2, 0x84C7, 0x52C4, 0x84C8, 0x52C5, 0x84C9, 0x52C6, 0x84CA, 0x52C8, 0x84CB, 0x52CA, 0x84CC, 0x52CC, 0x84CD, 0x52CD, + 0x84CE, 0x52CE, 0x84CF, 0x52CF, 0x84D0, 0x52D1, 0x84D1, 0x52D3, 0x84D2, 0x52D4, 0x84D3, 0x52D5, 0x84D4, 0x52D7, 0x84D5, 0x52D9, + 0x84D6, 0x52DA, 0x84D7, 0x52DB, 0x84D8, 0x52DC, 0x84D9, 0x52DD, 0x84DA, 0x52DE, 0x84DB, 0x52E0, 0x84DC, 0x52E1, 0x84DD, 0x52E2, + 0x84DE, 0x52E3, 0x84DF, 0x52E5, 0x84E0, 0x52E6, 0x84E1, 0x52E7, 0x84E2, 0x52E8, 0x84E3, 0x52E9, 0x84E4, 0x52EA, 0x84E5, 0x52EB, + 0x84E6, 0x52EC, 0x84E7, 0x52ED, 0x84E8, 0x52EE, 0x84E9, 0x52EF, 0x84EA, 0x52F1, 0x84EB, 0x52F2, 0x84EC, 0x52F3, 0x84ED, 0x52F4, + 0x84EE, 0x52F5, 0x84EF, 0x52F6, 0x84F0, 0x52F7, 0x84F1, 0x52F8, 0x84F2, 0x52FB, 0x84F3, 0x52FC, 0x84F4, 0x52FD, 0x84F5, 0x5301, + 0x84F6, 0x5302, 0x84F7, 0x5303, 0x84F8, 0x5304, 0x84F9, 0x5307, 0x84FA, 0x5309, 0x84FB, 0x530A, 0x84FC, 0x530B, 0x84FD, 0x530C, + 0x84FE, 0x530E, 0x8540, 0x5311, 0x8541, 0x5312, 0x8542, 0x5313, 0x8543, 0x5314, 0x8544, 0x5318, 0x8545, 0x531B, 0x8546, 0x531C, + 0x8547, 0x531E, 0x8548, 0x531F, 0x8549, 0x5322, 0x854A, 0x5324, 0x854B, 0x5325, 0x854C, 0x5327, 0x854D, 0x5328, 0x854E, 0x5329, + 0x854F, 0x532B, 0x8550, 0x532C, 0x8551, 0x532D, 0x8552, 0x532F, 0x8553, 0x5330, 0x8554, 0x5331, 0x8555, 0x5332, 0x8556, 0x5333, + 0x8557, 0x5334, 0x8558, 0x5335, 0x8559, 0x5336, 0x855A, 0x5337, 0x855B, 0x5338, 0x855C, 0x533C, 0x855D, 0x533D, 0x855E, 0x5340, + 0x855F, 0x5342, 0x8560, 0x5344, 0x8561, 0x5346, 0x8562, 0x534B, 0x8563, 0x534C, 0x8564, 0x534D, 0x8565, 0x5350, 0x8566, 0x5354, + 0x8567, 0x5358, 0x8568, 0x5359, 0x8569, 0x535B, 0x856A, 0x535D, 0x856B, 0x5365, 0x856C, 0x5368, 0x856D, 0x536A, 0x856E, 0x536C, + 0x856F, 0x536D, 0x8570, 0x5372, 0x8571, 0x5376, 0x8572, 0x5379, 0x8573, 0x537B, 0x8574, 0x537C, 0x8575, 0x537D, 0x8576, 0x537E, + 0x8577, 0x5380, 0x8578, 0x5381, 0x8579, 0x5383, 0x857A, 0x5387, 0x857B, 0x5388, 0x857C, 0x538A, 0x857D, 0x538E, 0x857E, 0x538F, + 0x8580, 0x5390, 0x8581, 0x5391, 0x8582, 0x5392, 0x8583, 0x5393, 0x8584, 0x5394, 0x8585, 0x5396, 0x8586, 0x5397, 0x8587, 0x5399, + 0x8588, 0x539B, 0x8589, 0x539C, 0x858A, 0x539E, 0x858B, 0x53A0, 0x858C, 0x53A1, 0x858D, 0x53A4, 0x858E, 0x53A7, 0x858F, 0x53AA, + 0x8590, 0x53AB, 0x8591, 0x53AC, 0x8592, 0x53AD, 0x8593, 0x53AF, 0x8594, 0x53B0, 0x8595, 0x53B1, 0x8596, 0x53B2, 0x8597, 0x53B3, + 0x8598, 0x53B4, 0x8599, 0x53B5, 0x859A, 0x53B7, 0x859B, 0x53B8, 0x859C, 0x53B9, 0x859D, 0x53BA, 0x859E, 0x53BC, 0x859F, 0x53BD, + 0x85A0, 0x53BE, 0x85A1, 0x53C0, 0x85A2, 0x53C3, 0x85A3, 0x53C4, 0x85A4, 0x53C5, 0x85A5, 0x53C6, 0x85A6, 0x53C7, 0x85A7, 0x53CE, + 0x85A8, 0x53CF, 0x85A9, 0x53D0, 0x85AA, 0x53D2, 0x85AB, 0x53D3, 0x85AC, 0x53D5, 0x85AD, 0x53DA, 0x85AE, 0x53DC, 0x85AF, 0x53DD, + 0x85B0, 0x53DE, 0x85B1, 0x53E1, 0x85B2, 0x53E2, 0x85B3, 0x53E7, 0x85B4, 0x53F4, 0x85B5, 0x53FA, 0x85B6, 0x53FE, 0x85B7, 0x53FF, + 0x85B8, 0x5400, 0x85B9, 0x5402, 0x85BA, 0x5405, 0x85BB, 0x5407, 0x85BC, 0x540B, 0x85BD, 0x5414, 0x85BE, 0x5418, 0x85BF, 0x5419, + 0x85C0, 0x541A, 0x85C1, 0x541C, 0x85C2, 0x5422, 0x85C3, 0x5424, 0x85C4, 0x5425, 0x85C5, 0x542A, 0x85C6, 0x5430, 0x85C7, 0x5433, + 0x85C8, 0x5436, 0x85C9, 0x5437, 0x85CA, 0x543A, 0x85CB, 0x543D, 0x85CC, 0x543F, 0x85CD, 0x5441, 0x85CE, 0x5442, 0x85CF, 0x5444, + 0x85D0, 0x5445, 0x85D1, 0x5447, 0x85D2, 0x5449, 0x85D3, 0x544C, 0x85D4, 0x544D, 0x85D5, 0x544E, 0x85D6, 0x544F, 0x85D7, 0x5451, + 0x85D8, 0x545A, 0x85D9, 0x545D, 0x85DA, 0x545E, 0x85DB, 0x545F, 0x85DC, 0x5460, 0x85DD, 0x5461, 0x85DE, 0x5463, 0x85DF, 0x5465, + 0x85E0, 0x5467, 0x85E1, 0x5469, 0x85E2, 0x546A, 0x85E3, 0x546B, 0x85E4, 0x546C, 0x85E5, 0x546D, 0x85E6, 0x546E, 0x85E7, 0x546F, + 0x85E8, 0x5470, 0x85E9, 0x5474, 0x85EA, 0x5479, 0x85EB, 0x547A, 0x85EC, 0x547E, 0x85ED, 0x547F, 0x85EE, 0x5481, 0x85EF, 0x5483, + 0x85F0, 0x5485, 0x85F1, 0x5487, 0x85F2, 0x5488, 0x85F3, 0x5489, 0x85F4, 0x548A, 0x85F5, 0x548D, 0x85F6, 0x5491, 0x85F7, 0x5493, + 0x85F8, 0x5497, 0x85F9, 0x5498, 0x85FA, 0x549C, 0x85FB, 0x549E, 0x85FC, 0x549F, 0x85FD, 0x54A0, 0x85FE, 0x54A1, 0x8640, 0x54A2, + 0x8641, 0x54A5, 0x8642, 0x54AE, 0x8643, 0x54B0, 0x8644, 0x54B2, 0x8645, 0x54B5, 0x8646, 0x54B6, 0x8647, 0x54B7, 0x8648, 0x54B9, + 0x8649, 0x54BA, 0x864A, 0x54BC, 0x864B, 0x54BE, 0x864C, 0x54C3, 0x864D, 0x54C5, 0x864E, 0x54CA, 0x864F, 0x54CB, 0x8650, 0x54D6, + 0x8651, 0x54D8, 0x8652, 0x54DB, 0x8653, 0x54E0, 0x8654, 0x54E1, 0x8655, 0x54E2, 0x8656, 0x54E3, 0x8657, 0x54E4, 0x8658, 0x54EB, + 0x8659, 0x54EC, 0x865A, 0x54EF, 0x865B, 0x54F0, 0x865C, 0x54F1, 0x865D, 0x54F4, 0x865E, 0x54F5, 0x865F, 0x54F6, 0x8660, 0x54F7, + 0x8661, 0x54F8, 0x8662, 0x54F9, 0x8663, 0x54FB, 0x8664, 0x54FE, 0x8665, 0x5500, 0x8666, 0x5502, 0x8667, 0x5503, 0x8668, 0x5504, + 0x8669, 0x5505, 0x866A, 0x5508, 0x866B, 0x550A, 0x866C, 0x550B, 0x866D, 0x550C, 0x866E, 0x550D, 0x866F, 0x550E, 0x8670, 0x5512, + 0x8671, 0x5513, 0x8672, 0x5515, 0x8673, 0x5516, 0x8674, 0x5517, 0x8675, 0x5518, 0x8676, 0x5519, 0x8677, 0x551A, 0x8678, 0x551C, + 0x8679, 0x551D, 0x867A, 0x551E, 0x867B, 0x551F, 0x867C, 0x5521, 0x867D, 0x5525, 0x867E, 0x5526, 0x8680, 0x5528, 0x8681, 0x5529, + 0x8682, 0x552B, 0x8683, 0x552D, 0x8684, 0x5532, 0x8685, 0x5534, 0x8686, 0x5535, 0x8687, 0x5536, 0x8688, 0x5538, 0x8689, 0x5539, + 0x868A, 0x553A, 0x868B, 0x553B, 0x868C, 0x553D, 0x868D, 0x5540, 0x868E, 0x5542, 0x868F, 0x5545, 0x8690, 0x5547, 0x8691, 0x5548, + 0x8692, 0x554B, 0x8693, 0x554C, 0x8694, 0x554D, 0x8695, 0x554E, 0x8696, 0x554F, 0x8697, 0x5551, 0x8698, 0x5552, 0x8699, 0x5553, + 0x869A, 0x5554, 0x869B, 0x5557, 0x869C, 0x5558, 0x869D, 0x5559, 0x869E, 0x555A, 0x869F, 0x555B, 0x86A0, 0x555D, 0x86A1, 0x555E, + 0x86A2, 0x555F, 0x86A3, 0x5560, 0x86A4, 0x5562, 0x86A5, 0x5563, 0x86A6, 0x5568, 0x86A7, 0x5569, 0x86A8, 0x556B, 0x86A9, 0x556F, + 0x86AA, 0x5570, 0x86AB, 0x5571, 0x86AC, 0x5572, 0x86AD, 0x5573, 0x86AE, 0x5574, 0x86AF, 0x5579, 0x86B0, 0x557A, 0x86B1, 0x557D, + 0x86B2, 0x557F, 0x86B3, 0x5585, 0x86B4, 0x5586, 0x86B5, 0x558C, 0x86B6, 0x558D, 0x86B7, 0x558E, 0x86B8, 0x5590, 0x86B9, 0x5592, + 0x86BA, 0x5593, 0x86BB, 0x5595, 0x86BC, 0x5596, 0x86BD, 0x5597, 0x86BE, 0x559A, 0x86BF, 0x559B, 0x86C0, 0x559E, 0x86C1, 0x55A0, + 0x86C2, 0x55A1, 0x86C3, 0x55A2, 0x86C4, 0x55A3, 0x86C5, 0x55A4, 0x86C6, 0x55A5, 0x86C7, 0x55A6, 0x86C8, 0x55A8, 0x86C9, 0x55A9, + 0x86CA, 0x55AA, 0x86CB, 0x55AB, 0x86CC, 0x55AC, 0x86CD, 0x55AD, 0x86CE, 0x55AE, 0x86CF, 0x55AF, 0x86D0, 0x55B0, 0x86D1, 0x55B2, + 0x86D2, 0x55B4, 0x86D3, 0x55B6, 0x86D4, 0x55B8, 0x86D5, 0x55BA, 0x86D6, 0x55BC, 0x86D7, 0x55BF, 0x86D8, 0x55C0, 0x86D9, 0x55C1, + 0x86DA, 0x55C2, 0x86DB, 0x55C3, 0x86DC, 0x55C6, 0x86DD, 0x55C7, 0x86DE, 0x55C8, 0x86DF, 0x55CA, 0x86E0, 0x55CB, 0x86E1, 0x55CE, + 0x86E2, 0x55CF, 0x86E3, 0x55D0, 0x86E4, 0x55D5, 0x86E5, 0x55D7, 0x86E6, 0x55D8, 0x86E7, 0x55D9, 0x86E8, 0x55DA, 0x86E9, 0x55DB, + 0x86EA, 0x55DE, 0x86EB, 0x55E0, 0x86EC, 0x55E2, 0x86ED, 0x55E7, 0x86EE, 0x55E9, 0x86EF, 0x55ED, 0x86F0, 0x55EE, 0x86F1, 0x55F0, + 0x86F2, 0x55F1, 0x86F3, 0x55F4, 0x86F4, 0x55F6, 0x86F5, 0x55F8, 0x86F6, 0x55F9, 0x86F7, 0x55FA, 0x86F8, 0x55FB, 0x86F9, 0x55FC, + 0x86FA, 0x55FF, 0x86FB, 0x5602, 0x86FC, 0x5603, 0x86FD, 0x5604, 0x86FE, 0x5605, 0x8740, 0x5606, 0x8741, 0x5607, 0x8742, 0x560A, + 0x8743, 0x560B, 0x8744, 0x560D, 0x8745, 0x5610, 0x8746, 0x5611, 0x8747, 0x5612, 0x8748, 0x5613, 0x8749, 0x5614, 0x874A, 0x5615, + 0x874B, 0x5616, 0x874C, 0x5617, 0x874D, 0x5619, 0x874E, 0x561A, 0x874F, 0x561C, 0x8750, 0x561D, 0x8751, 0x5620, 0x8752, 0x5621, + 0x8753, 0x5622, 0x8754, 0x5625, 0x8755, 0x5626, 0x8756, 0x5628, 0x8757, 0x5629, 0x8758, 0x562A, 0x8759, 0x562B, 0x875A, 0x562E, + 0x875B, 0x562F, 0x875C, 0x5630, 0x875D, 0x5633, 0x875E, 0x5635, 0x875F, 0x5637, 0x8760, 0x5638, 0x8761, 0x563A, 0x8762, 0x563C, + 0x8763, 0x563D, 0x8764, 0x563E, 0x8765, 0x5640, 0x8766, 0x5641, 0x8767, 0x5642, 0x8768, 0x5643, 0x8769, 0x5644, 0x876A, 0x5645, + 0x876B, 0x5646, 0x876C, 0x5647, 0x876D, 0x5648, 0x876E, 0x5649, 0x876F, 0x564A, 0x8770, 0x564B, 0x8771, 0x564F, 0x8772, 0x5650, + 0x8773, 0x5651, 0x8774, 0x5652, 0x8775, 0x5653, 0x8776, 0x5655, 0x8777, 0x5656, 0x8778, 0x565A, 0x8779, 0x565B, 0x877A, 0x565D, + 0x877B, 0x565E, 0x877C, 0x565F, 0x877D, 0x5660, 0x877E, 0x5661, 0x8780, 0x5663, 0x8781, 0x5665, 0x8782, 0x5666, 0x8783, 0x5667, + 0x8784, 0x566D, 0x8785, 0x566E, 0x8786, 0x566F, 0x8787, 0x5670, 0x8788, 0x5672, 0x8789, 0x5673, 0x878A, 0x5674, 0x878B, 0x5675, + 0x878C, 0x5677, 0x878D, 0x5678, 0x878E, 0x5679, 0x878F, 0x567A, 0x8790, 0x567D, 0x8791, 0x567E, 0x8792, 0x567F, 0x8793, 0x5680, + 0x8794, 0x5681, 0x8795, 0x5682, 0x8796, 0x5683, 0x8797, 0x5684, 0x8798, 0x5687, 0x8799, 0x5688, 0x879A, 0x5689, 0x879B, 0x568A, + 0x879C, 0x568B, 0x879D, 0x568C, 0x879E, 0x568D, 0x879F, 0x5690, 0x87A0, 0x5691, 0x87A1, 0x5692, 0x87A2, 0x5694, 0x87A3, 0x5695, + 0x87A4, 0x5696, 0x87A5, 0x5697, 0x87A6, 0x5698, 0x87A7, 0x5699, 0x87A8, 0x569A, 0x87A9, 0x569B, 0x87AA, 0x569C, 0x87AB, 0x569D, + 0x87AC, 0x569E, 0x87AD, 0x569F, 0x87AE, 0x56A0, 0x87AF, 0x56A1, 0x87B0, 0x56A2, 0x87B1, 0x56A4, 0x87B2, 0x56A5, 0x87B3, 0x56A6, + 0x87B4, 0x56A7, 0x87B5, 0x56A8, 0x87B6, 0x56A9, 0x87B7, 0x56AA, 0x87B8, 0x56AB, 0x87B9, 0x56AC, 0x87BA, 0x56AD, 0x87BB, 0x56AE, + 0x87BC, 0x56B0, 0x87BD, 0x56B1, 0x87BE, 0x56B2, 0x87BF, 0x56B3, 0x87C0, 0x56B4, 0x87C1, 0x56B5, 0x87C2, 0x56B6, 0x87C3, 0x56B8, + 0x87C4, 0x56B9, 0x87C5, 0x56BA, 0x87C6, 0x56BB, 0x87C7, 0x56BD, 0x87C8, 0x56BE, 0x87C9, 0x56BF, 0x87CA, 0x56C0, 0x87CB, 0x56C1, + 0x87CC, 0x56C2, 0x87CD, 0x56C3, 0x87CE, 0x56C4, 0x87CF, 0x56C5, 0x87D0, 0x56C6, 0x87D1, 0x56C7, 0x87D2, 0x56C8, 0x87D3, 0x56C9, + 0x87D4, 0x56CB, 0x87D5, 0x56CC, 0x87D6, 0x56CD, 0x87D7, 0x56CE, 0x87D8, 0x56CF, 0x87D9, 0x56D0, 0x87DA, 0x56D1, 0x87DB, 0x56D2, + 0x87DC, 0x56D3, 0x87DD, 0x56D5, 0x87DE, 0x56D6, 0x87DF, 0x56D8, 0x87E0, 0x56D9, 0x87E1, 0x56DC, 0x87E2, 0x56E3, 0x87E3, 0x56E5, + 0x87E4, 0x56E6, 0x87E5, 0x56E7, 0x87E6, 0x56E8, 0x87E7, 0x56E9, 0x87E8, 0x56EA, 0x87E9, 0x56EC, 0x87EA, 0x56EE, 0x87EB, 0x56EF, + 0x87EC, 0x56F2, 0x87ED, 0x56F3, 0x87EE, 0x56F6, 0x87EF, 0x56F7, 0x87F0, 0x56F8, 0x87F1, 0x56FB, 0x87F2, 0x56FC, 0x87F3, 0x5700, + 0x87F4, 0x5701, 0x87F5, 0x5702, 0x87F6, 0x5705, 0x87F7, 0x5707, 0x87F8, 0x570B, 0x87F9, 0x570C, 0x87FA, 0x570D, 0x87FB, 0x570E, + 0x87FC, 0x570F, 0x87FD, 0x5710, 0x87FE, 0x5711, 0x8840, 0x5712, 0x8841, 0x5713, 0x8842, 0x5714, 0x8843, 0x5715, 0x8844, 0x5716, + 0x8845, 0x5717, 0x8846, 0x5718, 0x8847, 0x5719, 0x8848, 0x571A, 0x8849, 0x571B, 0x884A, 0x571D, 0x884B, 0x571E, 0x884C, 0x5720, + 0x884D, 0x5721, 0x884E, 0x5722, 0x884F, 0x5724, 0x8850, 0x5725, 0x8851, 0x5726, 0x8852, 0x5727, 0x8853, 0x572B, 0x8854, 0x5731, + 0x8855, 0x5732, 0x8856, 0x5734, 0x8857, 0x5735, 0x8858, 0x5736, 0x8859, 0x5737, 0x885A, 0x5738, 0x885B, 0x573C, 0x885C, 0x573D, + 0x885D, 0x573F, 0x885E, 0x5741, 0x885F, 0x5743, 0x8860, 0x5744, 0x8861, 0x5745, 0x8862, 0x5746, 0x8863, 0x5748, 0x8864, 0x5749, + 0x8865, 0x574B, 0x8866, 0x5752, 0x8867, 0x5753, 0x8868, 0x5754, 0x8869, 0x5755, 0x886A, 0x5756, 0x886B, 0x5758, 0x886C, 0x5759, + 0x886D, 0x5762, 0x886E, 0x5763, 0x886F, 0x5765, 0x8870, 0x5767, 0x8871, 0x576C, 0x8872, 0x576E, 0x8873, 0x5770, 0x8874, 0x5771, + 0x8875, 0x5772, 0x8876, 0x5774, 0x8877, 0x5775, 0x8878, 0x5778, 0x8879, 0x5779, 0x887A, 0x577A, 0x887B, 0x577D, 0x887C, 0x577E, + 0x887D, 0x577F, 0x887E, 0x5780, 0x8880, 0x5781, 0x8881, 0x5787, 0x8882, 0x5788, 0x8883, 0x5789, 0x8884, 0x578A, 0x8885, 0x578D, + 0x8886, 0x578E, 0x8887, 0x578F, 0x8888, 0x5790, 0x8889, 0x5791, 0x888A, 0x5794, 0x888B, 0x5795, 0x888C, 0x5796, 0x888D, 0x5797, + 0x888E, 0x5798, 0x888F, 0x5799, 0x8890, 0x579A, 0x8891, 0x579C, 0x8892, 0x579D, 0x8893, 0x579E, 0x8894, 0x579F, 0x8895, 0x57A5, + 0x8896, 0x57A8, 0x8897, 0x57AA, 0x8898, 0x57AC, 0x8899, 0x57AF, 0x889A, 0x57B0, 0x889B, 0x57B1, 0x889C, 0x57B3, 0x889D, 0x57B5, + 0x889E, 0x57B6, 0x889F, 0x57B7, 0x88A0, 0x57B9, 0x88A1, 0x57BA, 0x88A2, 0x57BB, 0x88A3, 0x57BC, 0x88A4, 0x57BD, 0x88A5, 0x57BE, + 0x88A6, 0x57BF, 0x88A7, 0x57C0, 0x88A8, 0x57C1, 0x88A9, 0x57C4, 0x88AA, 0x57C5, 0x88AB, 0x57C6, 0x88AC, 0x57C7, 0x88AD, 0x57C8, + 0x88AE, 0x57C9, 0x88AF, 0x57CA, 0x88B0, 0x57CC, 0x88B1, 0x57CD, 0x88B2, 0x57D0, 0x88B3, 0x57D1, 0x88B4, 0x57D3, 0x88B5, 0x57D6, + 0x88B6, 0x57D7, 0x88B7, 0x57DB, 0x88B8, 0x57DC, 0x88B9, 0x57DE, 0x88BA, 0x57E1, 0x88BB, 0x57E2, 0x88BC, 0x57E3, 0x88BD, 0x57E5, + 0x88BE, 0x57E6, 0x88BF, 0x57E7, 0x88C0, 0x57E8, 0x88C1, 0x57E9, 0x88C2, 0x57EA, 0x88C3, 0x57EB, 0x88C4, 0x57EC, 0x88C5, 0x57EE, + 0x88C6, 0x57F0, 0x88C7, 0x57F1, 0x88C8, 0x57F2, 0x88C9, 0x57F3, 0x88CA, 0x57F5, 0x88CB, 0x57F6, 0x88CC, 0x57F7, 0x88CD, 0x57FB, + 0x88CE, 0x57FC, 0x88CF, 0x57FE, 0x88D0, 0x57FF, 0x88D1, 0x5801, 0x88D2, 0x5803, 0x88D3, 0x5804, 0x88D4, 0x5805, 0x88D5, 0x5808, + 0x88D6, 0x5809, 0x88D7, 0x580A, 0x88D8, 0x580C, 0x88D9, 0x580E, 0x88DA, 0x580F, 0x88DB, 0x5810, 0x88DC, 0x5812, 0x88DD, 0x5813, + 0x88DE, 0x5814, 0x88DF, 0x5816, 0x88E0, 0x5817, 0x88E1, 0x5818, 0x88E2, 0x581A, 0x88E3, 0x581B, 0x88E4, 0x581C, 0x88E5, 0x581D, + 0x88E6, 0x581F, 0x88E7, 0x5822, 0x88E8, 0x5823, 0x88E9, 0x5825, 0x88EA, 0x5826, 0x88EB, 0x5827, 0x88EC, 0x5828, 0x88ED, 0x5829, + 0x88EE, 0x582B, 0x88EF, 0x582C, 0x88F0, 0x582D, 0x88F1, 0x582E, 0x88F2, 0x582F, 0x88F3, 0x5831, 0x88F4, 0x5832, 0x88F5, 0x5833, + 0x88F6, 0x5834, 0x88F7, 0x5836, 0x88F8, 0x5837, 0x88F9, 0x5838, 0x88FA, 0x5839, 0x88FB, 0x583A, 0x88FC, 0x583B, 0x88FD, 0x583C, + 0x88FE, 0x583D, 0x8940, 0x583E, 0x8941, 0x583F, 0x8942, 0x5840, 0x8943, 0x5841, 0x8944, 0x5842, 0x8945, 0x5843, 0x8946, 0x5845, + 0x8947, 0x5846, 0x8948, 0x5847, 0x8949, 0x5848, 0x894A, 0x5849, 0x894B, 0x584A, 0x894C, 0x584B, 0x894D, 0x584E, 0x894E, 0x584F, + 0x894F, 0x5850, 0x8950, 0x5852, 0x8951, 0x5853, 0x8952, 0x5855, 0x8953, 0x5856, 0x8954, 0x5857, 0x8955, 0x5859, 0x8956, 0x585A, + 0x8957, 0x585B, 0x8958, 0x585C, 0x8959, 0x585D, 0x895A, 0x585F, 0x895B, 0x5860, 0x895C, 0x5861, 0x895D, 0x5862, 0x895E, 0x5863, + 0x895F, 0x5864, 0x8960, 0x5866, 0x8961, 0x5867, 0x8962, 0x5868, 0x8963, 0x5869, 0x8964, 0x586A, 0x8965, 0x586D, 0x8966, 0x586E, + 0x8967, 0x586F, 0x8968, 0x5870, 0x8969, 0x5871, 0x896A, 0x5872, 0x896B, 0x5873, 0x896C, 0x5874, 0x896D, 0x5875, 0x896E, 0x5876, + 0x896F, 0x5877, 0x8970, 0x5878, 0x8971, 0x5879, 0x8972, 0x587A, 0x8973, 0x587B, 0x8974, 0x587C, 0x8975, 0x587D, 0x8976, 0x587F, + 0x8977, 0x5882, 0x8978, 0x5884, 0x8979, 0x5886, 0x897A, 0x5887, 0x897B, 0x5888, 0x897C, 0x588A, 0x897D, 0x588B, 0x897E, 0x588C, + 0x8980, 0x588D, 0x8981, 0x588E, 0x8982, 0x588F, 0x8983, 0x5890, 0x8984, 0x5891, 0x8985, 0x5894, 0x8986, 0x5895, 0x8987, 0x5896, + 0x8988, 0x5897, 0x8989, 0x5898, 0x898A, 0x589B, 0x898B, 0x589C, 0x898C, 0x589D, 0x898D, 0x58A0, 0x898E, 0x58A1, 0x898F, 0x58A2, + 0x8990, 0x58A3, 0x8991, 0x58A4, 0x8992, 0x58A5, 0x8993, 0x58A6, 0x8994, 0x58A7, 0x8995, 0x58AA, 0x8996, 0x58AB, 0x8997, 0x58AC, + 0x8998, 0x58AD, 0x8999, 0x58AE, 0x899A, 0x58AF, 0x899B, 0x58B0, 0x899C, 0x58B1, 0x899D, 0x58B2, 0x899E, 0x58B3, 0x899F, 0x58B4, + 0x89A0, 0x58B5, 0x89A1, 0x58B6, 0x89A2, 0x58B7, 0x89A3, 0x58B8, 0x89A4, 0x58B9, 0x89A5, 0x58BA, 0x89A6, 0x58BB, 0x89A7, 0x58BD, + 0x89A8, 0x58BE, 0x89A9, 0x58BF, 0x89AA, 0x58C0, 0x89AB, 0x58C2, 0x89AC, 0x58C3, 0x89AD, 0x58C4, 0x89AE, 0x58C6, 0x89AF, 0x58C7, + 0x89B0, 0x58C8, 0x89B1, 0x58C9, 0x89B2, 0x58CA, 0x89B3, 0x58CB, 0x89B4, 0x58CC, 0x89B5, 0x58CD, 0x89B6, 0x58CE, 0x89B7, 0x58CF, + 0x89B8, 0x58D0, 0x89B9, 0x58D2, 0x89BA, 0x58D3, 0x89BB, 0x58D4, 0x89BC, 0x58D6, 0x89BD, 0x58D7, 0x89BE, 0x58D8, 0x89BF, 0x58D9, + 0x89C0, 0x58DA, 0x89C1, 0x58DB, 0x89C2, 0x58DC, 0x89C3, 0x58DD, 0x89C4, 0x58DE, 0x89C5, 0x58DF, 0x89C6, 0x58E0, 0x89C7, 0x58E1, + 0x89C8, 0x58E2, 0x89C9, 0x58E3, 0x89CA, 0x58E5, 0x89CB, 0x58E6, 0x89CC, 0x58E7, 0x89CD, 0x58E8, 0x89CE, 0x58E9, 0x89CF, 0x58EA, + 0x89D0, 0x58ED, 0x89D1, 0x58EF, 0x89D2, 0x58F1, 0x89D3, 0x58F2, 0x89D4, 0x58F4, 0x89D5, 0x58F5, 0x89D6, 0x58F7, 0x89D7, 0x58F8, + 0x89D8, 0x58FA, 0x89D9, 0x58FB, 0x89DA, 0x58FC, 0x89DB, 0x58FD, 0x89DC, 0x58FE, 0x89DD, 0x58FF, 0x89DE, 0x5900, 0x89DF, 0x5901, + 0x89E0, 0x5903, 0x89E1, 0x5905, 0x89E2, 0x5906, 0x89E3, 0x5908, 0x89E4, 0x5909, 0x89E5, 0x590A, 0x89E6, 0x590B, 0x89E7, 0x590C, + 0x89E8, 0x590E, 0x89E9, 0x5910, 0x89EA, 0x5911, 0x89EB, 0x5912, 0x89EC, 0x5913, 0x89ED, 0x5917, 0x89EE, 0x5918, 0x89EF, 0x591B, + 0x89F0, 0x591D, 0x89F1, 0x591E, 0x89F2, 0x5920, 0x89F3, 0x5921, 0x89F4, 0x5922, 0x89F5, 0x5923, 0x89F6, 0x5926, 0x89F7, 0x5928, + 0x89F8, 0x592C, 0x89F9, 0x5930, 0x89FA, 0x5932, 0x89FB, 0x5933, 0x89FC, 0x5935, 0x89FD, 0x5936, 0x89FE, 0x593B, 0x8A40, 0x593D, + 0x8A41, 0x593E, 0x8A42, 0x593F, 0x8A43, 0x5940, 0x8A44, 0x5943, 0x8A45, 0x5945, 0x8A46, 0x5946, 0x8A47, 0x594A, 0x8A48, 0x594C, + 0x8A49, 0x594D, 0x8A4A, 0x5950, 0x8A4B, 0x5952, 0x8A4C, 0x5953, 0x8A4D, 0x5959, 0x8A4E, 0x595B, 0x8A4F, 0x595C, 0x8A50, 0x595D, + 0x8A51, 0x595E, 0x8A52, 0x595F, 0x8A53, 0x5961, 0x8A54, 0x5963, 0x8A55, 0x5964, 0x8A56, 0x5966, 0x8A57, 0x5967, 0x8A58, 0x5968, + 0x8A59, 0x5969, 0x8A5A, 0x596A, 0x8A5B, 0x596B, 0x8A5C, 0x596C, 0x8A5D, 0x596D, 0x8A5E, 0x596E, 0x8A5F, 0x596F, 0x8A60, 0x5970, + 0x8A61, 0x5971, 0x8A62, 0x5972, 0x8A63, 0x5975, 0x8A64, 0x5977, 0x8A65, 0x597A, 0x8A66, 0x597B, 0x8A67, 0x597C, 0x8A68, 0x597E, + 0x8A69, 0x597F, 0x8A6A, 0x5980, 0x8A6B, 0x5985, 0x8A6C, 0x5989, 0x8A6D, 0x598B, 0x8A6E, 0x598C, 0x8A6F, 0x598E, 0x8A70, 0x598F, + 0x8A71, 0x5990, 0x8A72, 0x5991, 0x8A73, 0x5994, 0x8A74, 0x5995, 0x8A75, 0x5998, 0x8A76, 0x599A, 0x8A77, 0x599B, 0x8A78, 0x599C, + 0x8A79, 0x599D, 0x8A7A, 0x599F, 0x8A7B, 0x59A0, 0x8A7C, 0x59A1, 0x8A7D, 0x59A2, 0x8A7E, 0x59A6, 0x8A80, 0x59A7, 0x8A81, 0x59AC, + 0x8A82, 0x59AD, 0x8A83, 0x59B0, 0x8A84, 0x59B1, 0x8A85, 0x59B3, 0x8A86, 0x59B4, 0x8A87, 0x59B5, 0x8A88, 0x59B6, 0x8A89, 0x59B7, + 0x8A8A, 0x59B8, 0x8A8B, 0x59BA, 0x8A8C, 0x59BC, 0x8A8D, 0x59BD, 0x8A8E, 0x59BF, 0x8A8F, 0x59C0, 0x8A90, 0x59C1, 0x8A91, 0x59C2, + 0x8A92, 0x59C3, 0x8A93, 0x59C4, 0x8A94, 0x59C5, 0x8A95, 0x59C7, 0x8A96, 0x59C8, 0x8A97, 0x59C9, 0x8A98, 0x59CC, 0x8A99, 0x59CD, + 0x8A9A, 0x59CE, 0x8A9B, 0x59CF, 0x8A9C, 0x59D5, 0x8A9D, 0x59D6, 0x8A9E, 0x59D9, 0x8A9F, 0x59DB, 0x8AA0, 0x59DE, 0x8AA1, 0x59DF, + 0x8AA2, 0x59E0, 0x8AA3, 0x59E1, 0x8AA4, 0x59E2, 0x8AA5, 0x59E4, 0x8AA6, 0x59E6, 0x8AA7, 0x59E7, 0x8AA8, 0x59E9, 0x8AA9, 0x59EA, + 0x8AAA, 0x59EB, 0x8AAB, 0x59ED, 0x8AAC, 0x59EE, 0x8AAD, 0x59EF, 0x8AAE, 0x59F0, 0x8AAF, 0x59F1, 0x8AB0, 0x59F2, 0x8AB1, 0x59F3, + 0x8AB2, 0x59F4, 0x8AB3, 0x59F5, 0x8AB4, 0x59F6, 0x8AB5, 0x59F7, 0x8AB6, 0x59F8, 0x8AB7, 0x59FA, 0x8AB8, 0x59FC, 0x8AB9, 0x59FD, + 0x8ABA, 0x59FE, 0x8ABB, 0x5A00, 0x8ABC, 0x5A02, 0x8ABD, 0x5A0A, 0x8ABE, 0x5A0B, 0x8ABF, 0x5A0D, 0x8AC0, 0x5A0E, 0x8AC1, 0x5A0F, + 0x8AC2, 0x5A10, 0x8AC3, 0x5A12, 0x8AC4, 0x5A14, 0x8AC5, 0x5A15, 0x8AC6, 0x5A16, 0x8AC7, 0x5A17, 0x8AC8, 0x5A19, 0x8AC9, 0x5A1A, + 0x8ACA, 0x5A1B, 0x8ACB, 0x5A1D, 0x8ACC, 0x5A1E, 0x8ACD, 0x5A21, 0x8ACE, 0x5A22, 0x8ACF, 0x5A24, 0x8AD0, 0x5A26, 0x8AD1, 0x5A27, + 0x8AD2, 0x5A28, 0x8AD3, 0x5A2A, 0x8AD4, 0x5A2B, 0x8AD5, 0x5A2C, 0x8AD6, 0x5A2D, 0x8AD7, 0x5A2E, 0x8AD8, 0x5A2F, 0x8AD9, 0x5A30, + 0x8ADA, 0x5A33, 0x8ADB, 0x5A35, 0x8ADC, 0x5A37, 0x8ADD, 0x5A38, 0x8ADE, 0x5A39, 0x8ADF, 0x5A3A, 0x8AE0, 0x5A3B, 0x8AE1, 0x5A3D, + 0x8AE2, 0x5A3E, 0x8AE3, 0x5A3F, 0x8AE4, 0x5A41, 0x8AE5, 0x5A42, 0x8AE6, 0x5A43, 0x8AE7, 0x5A44, 0x8AE8, 0x5A45, 0x8AE9, 0x5A47, + 0x8AEA, 0x5A48, 0x8AEB, 0x5A4B, 0x8AEC, 0x5A4C, 0x8AED, 0x5A4D, 0x8AEE, 0x5A4E, 0x8AEF, 0x5A4F, 0x8AF0, 0x5A50, 0x8AF1, 0x5A51, + 0x8AF2, 0x5A52, 0x8AF3, 0x5A53, 0x8AF4, 0x5A54, 0x8AF5, 0x5A56, 0x8AF6, 0x5A57, 0x8AF7, 0x5A58, 0x8AF8, 0x5A59, 0x8AF9, 0x5A5B, + 0x8AFA, 0x5A5C, 0x8AFB, 0x5A5D, 0x8AFC, 0x5A5E, 0x8AFD, 0x5A5F, 0x8AFE, 0x5A60, 0x8B40, 0x5A61, 0x8B41, 0x5A63, 0x8B42, 0x5A64, + 0x8B43, 0x5A65, 0x8B44, 0x5A66, 0x8B45, 0x5A68, 0x8B46, 0x5A69, 0x8B47, 0x5A6B, 0x8B48, 0x5A6C, 0x8B49, 0x5A6D, 0x8B4A, 0x5A6E, + 0x8B4B, 0x5A6F, 0x8B4C, 0x5A70, 0x8B4D, 0x5A71, 0x8B4E, 0x5A72, 0x8B4F, 0x5A73, 0x8B50, 0x5A78, 0x8B51, 0x5A79, 0x8B52, 0x5A7B, + 0x8B53, 0x5A7C, 0x8B54, 0x5A7D, 0x8B55, 0x5A7E, 0x8B56, 0x5A80, 0x8B57, 0x5A81, 0x8B58, 0x5A82, 0x8B59, 0x5A83, 0x8B5A, 0x5A84, + 0x8B5B, 0x5A85, 0x8B5C, 0x5A86, 0x8B5D, 0x5A87, 0x8B5E, 0x5A88, 0x8B5F, 0x5A89, 0x8B60, 0x5A8A, 0x8B61, 0x5A8B, 0x8B62, 0x5A8C, + 0x8B63, 0x5A8D, 0x8B64, 0x5A8E, 0x8B65, 0x5A8F, 0x8B66, 0x5A90, 0x8B67, 0x5A91, 0x8B68, 0x5A93, 0x8B69, 0x5A94, 0x8B6A, 0x5A95, + 0x8B6B, 0x5A96, 0x8B6C, 0x5A97, 0x8B6D, 0x5A98, 0x8B6E, 0x5A99, 0x8B6F, 0x5A9C, 0x8B70, 0x5A9D, 0x8B71, 0x5A9E, 0x8B72, 0x5A9F, + 0x8B73, 0x5AA0, 0x8B74, 0x5AA1, 0x8B75, 0x5AA2, 0x8B76, 0x5AA3, 0x8B77, 0x5AA4, 0x8B78, 0x5AA5, 0x8B79, 0x5AA6, 0x8B7A, 0x5AA7, + 0x8B7B, 0x5AA8, 0x8B7C, 0x5AA9, 0x8B7D, 0x5AAB, 0x8B7E, 0x5AAC, 0x8B80, 0x5AAD, 0x8B81, 0x5AAE, 0x8B82, 0x5AAF, 0x8B83, 0x5AB0, + 0x8B84, 0x5AB1, 0x8B85, 0x5AB4, 0x8B86, 0x5AB6, 0x8B87, 0x5AB7, 0x8B88, 0x5AB9, 0x8B89, 0x5ABA, 0x8B8A, 0x5ABB, 0x8B8B, 0x5ABC, + 0x8B8C, 0x5ABD, 0x8B8D, 0x5ABF, 0x8B8E, 0x5AC0, 0x8B8F, 0x5AC3, 0x8B90, 0x5AC4, 0x8B91, 0x5AC5, 0x8B92, 0x5AC6, 0x8B93, 0x5AC7, + 0x8B94, 0x5AC8, 0x8B95, 0x5ACA, 0x8B96, 0x5ACB, 0x8B97, 0x5ACD, 0x8B98, 0x5ACE, 0x8B99, 0x5ACF, 0x8B9A, 0x5AD0, 0x8B9B, 0x5AD1, + 0x8B9C, 0x5AD3, 0x8B9D, 0x5AD5, 0x8B9E, 0x5AD7, 0x8B9F, 0x5AD9, 0x8BA0, 0x5ADA, 0x8BA1, 0x5ADB, 0x8BA2, 0x5ADD, 0x8BA3, 0x5ADE, + 0x8BA4, 0x5ADF, 0x8BA5, 0x5AE2, 0x8BA6, 0x5AE4, 0x8BA7, 0x5AE5, 0x8BA8, 0x5AE7, 0x8BA9, 0x5AE8, 0x8BAA, 0x5AEA, 0x8BAB, 0x5AEC, + 0x8BAC, 0x5AED, 0x8BAD, 0x5AEE, 0x8BAE, 0x5AEF, 0x8BAF, 0x5AF0, 0x8BB0, 0x5AF2, 0x8BB1, 0x5AF3, 0x8BB2, 0x5AF4, 0x8BB3, 0x5AF5, + 0x8BB4, 0x5AF6, 0x8BB5, 0x5AF7, 0x8BB6, 0x5AF8, 0x8BB7, 0x5AF9, 0x8BB8, 0x5AFA, 0x8BB9, 0x5AFB, 0x8BBA, 0x5AFC, 0x8BBB, 0x5AFD, + 0x8BBC, 0x5AFE, 0x8BBD, 0x5AFF, 0x8BBE, 0x5B00, 0x8BBF, 0x5B01, 0x8BC0, 0x5B02, 0x8BC1, 0x5B03, 0x8BC2, 0x5B04, 0x8BC3, 0x5B05, + 0x8BC4, 0x5B06, 0x8BC5, 0x5B07, 0x8BC6, 0x5B08, 0x8BC7, 0x5B0A, 0x8BC8, 0x5B0B, 0x8BC9, 0x5B0C, 0x8BCA, 0x5B0D, 0x8BCB, 0x5B0E, + 0x8BCC, 0x5B0F, 0x8BCD, 0x5B10, 0x8BCE, 0x5B11, 0x8BCF, 0x5B12, 0x8BD0, 0x5B13, 0x8BD1, 0x5B14, 0x8BD2, 0x5B15, 0x8BD3, 0x5B18, + 0x8BD4, 0x5B19, 0x8BD5, 0x5B1A, 0x8BD6, 0x5B1B, 0x8BD7, 0x5B1C, 0x8BD8, 0x5B1D, 0x8BD9, 0x5B1E, 0x8BDA, 0x5B1F, 0x8BDB, 0x5B20, + 0x8BDC, 0x5B21, 0x8BDD, 0x5B22, 0x8BDE, 0x5B23, 0x8BDF, 0x5B24, 0x8BE0, 0x5B25, 0x8BE1, 0x5B26, 0x8BE2, 0x5B27, 0x8BE3, 0x5B28, + 0x8BE4, 0x5B29, 0x8BE5, 0x5B2A, 0x8BE6, 0x5B2B, 0x8BE7, 0x5B2C, 0x8BE8, 0x5B2D, 0x8BE9, 0x5B2E, 0x8BEA, 0x5B2F, 0x8BEB, 0x5B30, + 0x8BEC, 0x5B31, 0x8BED, 0x5B33, 0x8BEE, 0x5B35, 0x8BEF, 0x5B36, 0x8BF0, 0x5B38, 0x8BF1, 0x5B39, 0x8BF2, 0x5B3A, 0x8BF3, 0x5B3B, + 0x8BF4, 0x5B3C, 0x8BF5, 0x5B3D, 0x8BF6, 0x5B3E, 0x8BF7, 0x5B3F, 0x8BF8, 0x5B41, 0x8BF9, 0x5B42, 0x8BFA, 0x5B43, 0x8BFB, 0x5B44, + 0x8BFC, 0x5B45, 0x8BFD, 0x5B46, 0x8BFE, 0x5B47, 0x8C40, 0x5B48, 0x8C41, 0x5B49, 0x8C42, 0x5B4A, 0x8C43, 0x5B4B, 0x8C44, 0x5B4C, + 0x8C45, 0x5B4D, 0x8C46, 0x5B4E, 0x8C47, 0x5B4F, 0x8C48, 0x5B52, 0x8C49, 0x5B56, 0x8C4A, 0x5B5E, 0x8C4B, 0x5B60, 0x8C4C, 0x5B61, + 0x8C4D, 0x5B67, 0x8C4E, 0x5B68, 0x8C4F, 0x5B6B, 0x8C50, 0x5B6D, 0x8C51, 0x5B6E, 0x8C52, 0x5B6F, 0x8C53, 0x5B72, 0x8C54, 0x5B74, + 0x8C55, 0x5B76, 0x8C56, 0x5B77, 0x8C57, 0x5B78, 0x8C58, 0x5B79, 0x8C59, 0x5B7B, 0x8C5A, 0x5B7C, 0x8C5B, 0x5B7E, 0x8C5C, 0x5B7F, + 0x8C5D, 0x5B82, 0x8C5E, 0x5B86, 0x8C5F, 0x5B8A, 0x8C60, 0x5B8D, 0x8C61, 0x5B8E, 0x8C62, 0x5B90, 0x8C63, 0x5B91, 0x8C64, 0x5B92, + 0x8C65, 0x5B94, 0x8C66, 0x5B96, 0x8C67, 0x5B9F, 0x8C68, 0x5BA7, 0x8C69, 0x5BA8, 0x8C6A, 0x5BA9, 0x8C6B, 0x5BAC, 0x8C6C, 0x5BAD, + 0x8C6D, 0x5BAE, 0x8C6E, 0x5BAF, 0x8C6F, 0x5BB1, 0x8C70, 0x5BB2, 0x8C71, 0x5BB7, 0x8C72, 0x5BBA, 0x8C73, 0x5BBB, 0x8C74, 0x5BBC, + 0x8C75, 0x5BC0, 0x8C76, 0x5BC1, 0x8C77, 0x5BC3, 0x8C78, 0x5BC8, 0x8C79, 0x5BC9, 0x8C7A, 0x5BCA, 0x8C7B, 0x5BCB, 0x8C7C, 0x5BCD, + 0x8C7D, 0x5BCE, 0x8C7E, 0x5BCF, 0x8C80, 0x5BD1, 0x8C81, 0x5BD4, 0x8C82, 0x5BD5, 0x8C83, 0x5BD6, 0x8C84, 0x5BD7, 0x8C85, 0x5BD8, + 0x8C86, 0x5BD9, 0x8C87, 0x5BDA, 0x8C88, 0x5BDB, 0x8C89, 0x5BDC, 0x8C8A, 0x5BE0, 0x8C8B, 0x5BE2, 0x8C8C, 0x5BE3, 0x8C8D, 0x5BE6, + 0x8C8E, 0x5BE7, 0x8C8F, 0x5BE9, 0x8C90, 0x5BEA, 0x8C91, 0x5BEB, 0x8C92, 0x5BEC, 0x8C93, 0x5BED, 0x8C94, 0x5BEF, 0x8C95, 0x5BF1, + 0x8C96, 0x5BF2, 0x8C97, 0x5BF3, 0x8C98, 0x5BF4, 0x8C99, 0x5BF5, 0x8C9A, 0x5BF6, 0x8C9B, 0x5BF7, 0x8C9C, 0x5BFD, 0x8C9D, 0x5BFE, + 0x8C9E, 0x5C00, 0x8C9F, 0x5C02, 0x8CA0, 0x5C03, 0x8CA1, 0x5C05, 0x8CA2, 0x5C07, 0x8CA3, 0x5C08, 0x8CA4, 0x5C0B, 0x8CA5, 0x5C0C, + 0x8CA6, 0x5C0D, 0x8CA7, 0x5C0E, 0x8CA8, 0x5C10, 0x8CA9, 0x5C12, 0x8CAA, 0x5C13, 0x8CAB, 0x5C17, 0x8CAC, 0x5C19, 0x8CAD, 0x5C1B, + 0x8CAE, 0x5C1E, 0x8CAF, 0x5C1F, 0x8CB0, 0x5C20, 0x8CB1, 0x5C21, 0x8CB2, 0x5C23, 0x8CB3, 0x5C26, 0x8CB4, 0x5C28, 0x8CB5, 0x5C29, + 0x8CB6, 0x5C2A, 0x8CB7, 0x5C2B, 0x8CB8, 0x5C2D, 0x8CB9, 0x5C2E, 0x8CBA, 0x5C2F, 0x8CBB, 0x5C30, 0x8CBC, 0x5C32, 0x8CBD, 0x5C33, + 0x8CBE, 0x5C35, 0x8CBF, 0x5C36, 0x8CC0, 0x5C37, 0x8CC1, 0x5C43, 0x8CC2, 0x5C44, 0x8CC3, 0x5C46, 0x8CC4, 0x5C47, 0x8CC5, 0x5C4C, + 0x8CC6, 0x5C4D, 0x8CC7, 0x5C52, 0x8CC8, 0x5C53, 0x8CC9, 0x5C54, 0x8CCA, 0x5C56, 0x8CCB, 0x5C57, 0x8CCC, 0x5C58, 0x8CCD, 0x5C5A, + 0x8CCE, 0x5C5B, 0x8CCF, 0x5C5C, 0x8CD0, 0x5C5D, 0x8CD1, 0x5C5F, 0x8CD2, 0x5C62, 0x8CD3, 0x5C64, 0x8CD4, 0x5C67, 0x8CD5, 0x5C68, + 0x8CD6, 0x5C69, 0x8CD7, 0x5C6A, 0x8CD8, 0x5C6B, 0x8CD9, 0x5C6C, 0x8CDA, 0x5C6D, 0x8CDB, 0x5C70, 0x8CDC, 0x5C72, 0x8CDD, 0x5C73, + 0x8CDE, 0x5C74, 0x8CDF, 0x5C75, 0x8CE0, 0x5C76, 0x8CE1, 0x5C77, 0x8CE2, 0x5C78, 0x8CE3, 0x5C7B, 0x8CE4, 0x5C7C, 0x8CE5, 0x5C7D, + 0x8CE6, 0x5C7E, 0x8CE7, 0x5C80, 0x8CE8, 0x5C83, 0x8CE9, 0x5C84, 0x8CEA, 0x5C85, 0x8CEB, 0x5C86, 0x8CEC, 0x5C87, 0x8CED, 0x5C89, + 0x8CEE, 0x5C8A, 0x8CEF, 0x5C8B, 0x8CF0, 0x5C8E, 0x8CF1, 0x5C8F, 0x8CF2, 0x5C92, 0x8CF3, 0x5C93, 0x8CF4, 0x5C95, 0x8CF5, 0x5C9D, + 0x8CF6, 0x5C9E, 0x8CF7, 0x5C9F, 0x8CF8, 0x5CA0, 0x8CF9, 0x5CA1, 0x8CFA, 0x5CA4, 0x8CFB, 0x5CA5, 0x8CFC, 0x5CA6, 0x8CFD, 0x5CA7, + 0x8CFE, 0x5CA8, 0x8D40, 0x5CAA, 0x8D41, 0x5CAE, 0x8D42, 0x5CAF, 0x8D43, 0x5CB0, 0x8D44, 0x5CB2, 0x8D45, 0x5CB4, 0x8D46, 0x5CB6, + 0x8D47, 0x5CB9, 0x8D48, 0x5CBA, 0x8D49, 0x5CBB, 0x8D4A, 0x5CBC, 0x8D4B, 0x5CBE, 0x8D4C, 0x5CC0, 0x8D4D, 0x5CC2, 0x8D4E, 0x5CC3, + 0x8D4F, 0x5CC5, 0x8D50, 0x5CC6, 0x8D51, 0x5CC7, 0x8D52, 0x5CC8, 0x8D53, 0x5CC9, 0x8D54, 0x5CCA, 0x8D55, 0x5CCC, 0x8D56, 0x5CCD, + 0x8D57, 0x5CCE, 0x8D58, 0x5CCF, 0x8D59, 0x5CD0, 0x8D5A, 0x5CD1, 0x8D5B, 0x5CD3, 0x8D5C, 0x5CD4, 0x8D5D, 0x5CD5, 0x8D5E, 0x5CD6, + 0x8D5F, 0x5CD7, 0x8D60, 0x5CD8, 0x8D61, 0x5CDA, 0x8D62, 0x5CDB, 0x8D63, 0x5CDC, 0x8D64, 0x5CDD, 0x8D65, 0x5CDE, 0x8D66, 0x5CDF, + 0x8D67, 0x5CE0, 0x8D68, 0x5CE2, 0x8D69, 0x5CE3, 0x8D6A, 0x5CE7, 0x8D6B, 0x5CE9, 0x8D6C, 0x5CEB, 0x8D6D, 0x5CEC, 0x8D6E, 0x5CEE, + 0x8D6F, 0x5CEF, 0x8D70, 0x5CF1, 0x8D71, 0x5CF2, 0x8D72, 0x5CF3, 0x8D73, 0x5CF4, 0x8D74, 0x5CF5, 0x8D75, 0x5CF6, 0x8D76, 0x5CF7, + 0x8D77, 0x5CF8, 0x8D78, 0x5CF9, 0x8D79, 0x5CFA, 0x8D7A, 0x5CFC, 0x8D7B, 0x5CFD, 0x8D7C, 0x5CFE, 0x8D7D, 0x5CFF, 0x8D7E, 0x5D00, + 0x8D80, 0x5D01, 0x8D81, 0x5D04, 0x8D82, 0x5D05, 0x8D83, 0x5D08, 0x8D84, 0x5D09, 0x8D85, 0x5D0A, 0x8D86, 0x5D0B, 0x8D87, 0x5D0C, + 0x8D88, 0x5D0D, 0x8D89, 0x5D0F, 0x8D8A, 0x5D10, 0x8D8B, 0x5D11, 0x8D8C, 0x5D12, 0x8D8D, 0x5D13, 0x8D8E, 0x5D15, 0x8D8F, 0x5D17, + 0x8D90, 0x5D18, 0x8D91, 0x5D19, 0x8D92, 0x5D1A, 0x8D93, 0x5D1C, 0x8D94, 0x5D1D, 0x8D95, 0x5D1F, 0x8D96, 0x5D20, 0x8D97, 0x5D21, + 0x8D98, 0x5D22, 0x8D99, 0x5D23, 0x8D9A, 0x5D25, 0x8D9B, 0x5D28, 0x8D9C, 0x5D2A, 0x8D9D, 0x5D2B, 0x8D9E, 0x5D2C, 0x8D9F, 0x5D2F, + 0x8DA0, 0x5D30, 0x8DA1, 0x5D31, 0x8DA2, 0x5D32, 0x8DA3, 0x5D33, 0x8DA4, 0x5D35, 0x8DA5, 0x5D36, 0x8DA6, 0x5D37, 0x8DA7, 0x5D38, + 0x8DA8, 0x5D39, 0x8DA9, 0x5D3A, 0x8DAA, 0x5D3B, 0x8DAB, 0x5D3C, 0x8DAC, 0x5D3F, 0x8DAD, 0x5D40, 0x8DAE, 0x5D41, 0x8DAF, 0x5D42, + 0x8DB0, 0x5D43, 0x8DB1, 0x5D44, 0x8DB2, 0x5D45, 0x8DB3, 0x5D46, 0x8DB4, 0x5D48, 0x8DB5, 0x5D49, 0x8DB6, 0x5D4D, 0x8DB7, 0x5D4E, + 0x8DB8, 0x5D4F, 0x8DB9, 0x5D50, 0x8DBA, 0x5D51, 0x8DBB, 0x5D52, 0x8DBC, 0x5D53, 0x8DBD, 0x5D54, 0x8DBE, 0x5D55, 0x8DBF, 0x5D56, + 0x8DC0, 0x5D57, 0x8DC1, 0x5D59, 0x8DC2, 0x5D5A, 0x8DC3, 0x5D5C, 0x8DC4, 0x5D5E, 0x8DC5, 0x5D5F, 0x8DC6, 0x5D60, 0x8DC7, 0x5D61, + 0x8DC8, 0x5D62, 0x8DC9, 0x5D63, 0x8DCA, 0x5D64, 0x8DCB, 0x5D65, 0x8DCC, 0x5D66, 0x8DCD, 0x5D67, 0x8DCE, 0x5D68, 0x8DCF, 0x5D6A, + 0x8DD0, 0x5D6D, 0x8DD1, 0x5D6E, 0x8DD2, 0x5D70, 0x8DD3, 0x5D71, 0x8DD4, 0x5D72, 0x8DD5, 0x5D73, 0x8DD6, 0x5D75, 0x8DD7, 0x5D76, + 0x8DD8, 0x5D77, 0x8DD9, 0x5D78, 0x8DDA, 0x5D79, 0x8DDB, 0x5D7A, 0x8DDC, 0x5D7B, 0x8DDD, 0x5D7C, 0x8DDE, 0x5D7D, 0x8DDF, 0x5D7E, + 0x8DE0, 0x5D7F, 0x8DE1, 0x5D80, 0x8DE2, 0x5D81, 0x8DE3, 0x5D83, 0x8DE4, 0x5D84, 0x8DE5, 0x5D85, 0x8DE6, 0x5D86, 0x8DE7, 0x5D87, + 0x8DE8, 0x5D88, 0x8DE9, 0x5D89, 0x8DEA, 0x5D8A, 0x8DEB, 0x5D8B, 0x8DEC, 0x5D8C, 0x8DED, 0x5D8D, 0x8DEE, 0x5D8E, 0x8DEF, 0x5D8F, + 0x8DF0, 0x5D90, 0x8DF1, 0x5D91, 0x8DF2, 0x5D92, 0x8DF3, 0x5D93, 0x8DF4, 0x5D94, 0x8DF5, 0x5D95, 0x8DF6, 0x5D96, 0x8DF7, 0x5D97, + 0x8DF8, 0x5D98, 0x8DF9, 0x5D9A, 0x8DFA, 0x5D9B, 0x8DFB, 0x5D9C, 0x8DFC, 0x5D9E, 0x8DFD, 0x5D9F, 0x8DFE, 0x5DA0, 0x8E40, 0x5DA1, + 0x8E41, 0x5DA2, 0x8E42, 0x5DA3, 0x8E43, 0x5DA4, 0x8E44, 0x5DA5, 0x8E45, 0x5DA6, 0x8E46, 0x5DA7, 0x8E47, 0x5DA8, 0x8E48, 0x5DA9, + 0x8E49, 0x5DAA, 0x8E4A, 0x5DAB, 0x8E4B, 0x5DAC, 0x8E4C, 0x5DAD, 0x8E4D, 0x5DAE, 0x8E4E, 0x5DAF, 0x8E4F, 0x5DB0, 0x8E50, 0x5DB1, + 0x8E51, 0x5DB2, 0x8E52, 0x5DB3, 0x8E53, 0x5DB4, 0x8E54, 0x5DB5, 0x8E55, 0x5DB6, 0x8E56, 0x5DB8, 0x8E57, 0x5DB9, 0x8E58, 0x5DBA, + 0x8E59, 0x5DBB, 0x8E5A, 0x5DBC, 0x8E5B, 0x5DBD, 0x8E5C, 0x5DBE, 0x8E5D, 0x5DBF, 0x8E5E, 0x5DC0, 0x8E5F, 0x5DC1, 0x8E60, 0x5DC2, + 0x8E61, 0x5DC3, 0x8E62, 0x5DC4, 0x8E63, 0x5DC6, 0x8E64, 0x5DC7, 0x8E65, 0x5DC8, 0x8E66, 0x5DC9, 0x8E67, 0x5DCA, 0x8E68, 0x5DCB, + 0x8E69, 0x5DCC, 0x8E6A, 0x5DCE, 0x8E6B, 0x5DCF, 0x8E6C, 0x5DD0, 0x8E6D, 0x5DD1, 0x8E6E, 0x5DD2, 0x8E6F, 0x5DD3, 0x8E70, 0x5DD4, + 0x8E71, 0x5DD5, 0x8E72, 0x5DD6, 0x8E73, 0x5DD7, 0x8E74, 0x5DD8, 0x8E75, 0x5DD9, 0x8E76, 0x5DDA, 0x8E77, 0x5DDC, 0x8E78, 0x5DDF, + 0x8E79, 0x5DE0, 0x8E7A, 0x5DE3, 0x8E7B, 0x5DE4, 0x8E7C, 0x5DEA, 0x8E7D, 0x5DEC, 0x8E7E, 0x5DED, 0x8E80, 0x5DF0, 0x8E81, 0x5DF5, + 0x8E82, 0x5DF6, 0x8E83, 0x5DF8, 0x8E84, 0x5DF9, 0x8E85, 0x5DFA, 0x8E86, 0x5DFB, 0x8E87, 0x5DFC, 0x8E88, 0x5DFF, 0x8E89, 0x5E00, + 0x8E8A, 0x5E04, 0x8E8B, 0x5E07, 0x8E8C, 0x5E09, 0x8E8D, 0x5E0A, 0x8E8E, 0x5E0B, 0x8E8F, 0x5E0D, 0x8E90, 0x5E0E, 0x8E91, 0x5E12, + 0x8E92, 0x5E13, 0x8E93, 0x5E17, 0x8E94, 0x5E1E, 0x8E95, 0x5E1F, 0x8E96, 0x5E20, 0x8E97, 0x5E21, 0x8E98, 0x5E22, 0x8E99, 0x5E23, + 0x8E9A, 0x5E24, 0x8E9B, 0x5E25, 0x8E9C, 0x5E28, 0x8E9D, 0x5E29, 0x8E9E, 0x5E2A, 0x8E9F, 0x5E2B, 0x8EA0, 0x5E2C, 0x8EA1, 0x5E2F, + 0x8EA2, 0x5E30, 0x8EA3, 0x5E32, 0x8EA4, 0x5E33, 0x8EA5, 0x5E34, 0x8EA6, 0x5E35, 0x8EA7, 0x5E36, 0x8EA8, 0x5E39, 0x8EA9, 0x5E3A, + 0x8EAA, 0x5E3E, 0x8EAB, 0x5E3F, 0x8EAC, 0x5E40, 0x8EAD, 0x5E41, 0x8EAE, 0x5E43, 0x8EAF, 0x5E46, 0x8EB0, 0x5E47, 0x8EB1, 0x5E48, + 0x8EB2, 0x5E49, 0x8EB3, 0x5E4A, 0x8EB4, 0x5E4B, 0x8EB5, 0x5E4D, 0x8EB6, 0x5E4E, 0x8EB7, 0x5E4F, 0x8EB8, 0x5E50, 0x8EB9, 0x5E51, + 0x8EBA, 0x5E52, 0x8EBB, 0x5E53, 0x8EBC, 0x5E56, 0x8EBD, 0x5E57, 0x8EBE, 0x5E58, 0x8EBF, 0x5E59, 0x8EC0, 0x5E5A, 0x8EC1, 0x5E5C, + 0x8EC2, 0x5E5D, 0x8EC3, 0x5E5F, 0x8EC4, 0x5E60, 0x8EC5, 0x5E63, 0x8EC6, 0x5E64, 0x8EC7, 0x5E65, 0x8EC8, 0x5E66, 0x8EC9, 0x5E67, + 0x8ECA, 0x5E68, 0x8ECB, 0x5E69, 0x8ECC, 0x5E6A, 0x8ECD, 0x5E6B, 0x8ECE, 0x5E6C, 0x8ECF, 0x5E6D, 0x8ED0, 0x5E6E, 0x8ED1, 0x5E6F, + 0x8ED2, 0x5E70, 0x8ED3, 0x5E71, 0x8ED4, 0x5E75, 0x8ED5, 0x5E77, 0x8ED6, 0x5E79, 0x8ED7, 0x5E7E, 0x8ED8, 0x5E81, 0x8ED9, 0x5E82, + 0x8EDA, 0x5E83, 0x8EDB, 0x5E85, 0x8EDC, 0x5E88, 0x8EDD, 0x5E89, 0x8EDE, 0x5E8C, 0x8EDF, 0x5E8D, 0x8EE0, 0x5E8E, 0x8EE1, 0x5E92, + 0x8EE2, 0x5E98, 0x8EE3, 0x5E9B, 0x8EE4, 0x5E9D, 0x8EE5, 0x5EA1, 0x8EE6, 0x5EA2, 0x8EE7, 0x5EA3, 0x8EE8, 0x5EA4, 0x8EE9, 0x5EA8, + 0x8EEA, 0x5EA9, 0x8EEB, 0x5EAA, 0x8EEC, 0x5EAB, 0x8EED, 0x5EAC, 0x8EEE, 0x5EAE, 0x8EEF, 0x5EAF, 0x8EF0, 0x5EB0, 0x8EF1, 0x5EB1, + 0x8EF2, 0x5EB2, 0x8EF3, 0x5EB4, 0x8EF4, 0x5EBA, 0x8EF5, 0x5EBB, 0x8EF6, 0x5EBC, 0x8EF7, 0x5EBD, 0x8EF8, 0x5EBF, 0x8EF9, 0x5EC0, + 0x8EFA, 0x5EC1, 0x8EFB, 0x5EC2, 0x8EFC, 0x5EC3, 0x8EFD, 0x5EC4, 0x8EFE, 0x5EC5, 0x8F40, 0x5EC6, 0x8F41, 0x5EC7, 0x8F42, 0x5EC8, + 0x8F43, 0x5ECB, 0x8F44, 0x5ECC, 0x8F45, 0x5ECD, 0x8F46, 0x5ECE, 0x8F47, 0x5ECF, 0x8F48, 0x5ED0, 0x8F49, 0x5ED4, 0x8F4A, 0x5ED5, + 0x8F4B, 0x5ED7, 0x8F4C, 0x5ED8, 0x8F4D, 0x5ED9, 0x8F4E, 0x5EDA, 0x8F4F, 0x5EDC, 0x8F50, 0x5EDD, 0x8F51, 0x5EDE, 0x8F52, 0x5EDF, + 0x8F53, 0x5EE0, 0x8F54, 0x5EE1, 0x8F55, 0x5EE2, 0x8F56, 0x5EE3, 0x8F57, 0x5EE4, 0x8F58, 0x5EE5, 0x8F59, 0x5EE6, 0x8F5A, 0x5EE7, + 0x8F5B, 0x5EE9, 0x8F5C, 0x5EEB, 0x8F5D, 0x5EEC, 0x8F5E, 0x5EED, 0x8F5F, 0x5EEE, 0x8F60, 0x5EEF, 0x8F61, 0x5EF0, 0x8F62, 0x5EF1, + 0x8F63, 0x5EF2, 0x8F64, 0x5EF3, 0x8F65, 0x5EF5, 0x8F66, 0x5EF8, 0x8F67, 0x5EF9, 0x8F68, 0x5EFB, 0x8F69, 0x5EFC, 0x8F6A, 0x5EFD, + 0x8F6B, 0x5F05, 0x8F6C, 0x5F06, 0x8F6D, 0x5F07, 0x8F6E, 0x5F09, 0x8F6F, 0x5F0C, 0x8F70, 0x5F0D, 0x8F71, 0x5F0E, 0x8F72, 0x5F10, + 0x8F73, 0x5F12, 0x8F74, 0x5F14, 0x8F75, 0x5F16, 0x8F76, 0x5F19, 0x8F77, 0x5F1A, 0x8F78, 0x5F1C, 0x8F79, 0x5F1D, 0x8F7A, 0x5F1E, + 0x8F7B, 0x5F21, 0x8F7C, 0x5F22, 0x8F7D, 0x5F23, 0x8F7E, 0x5F24, 0x8F80, 0x5F28, 0x8F81, 0x5F2B, 0x8F82, 0x5F2C, 0x8F83, 0x5F2E, + 0x8F84, 0x5F30, 0x8F85, 0x5F32, 0x8F86, 0x5F33, 0x8F87, 0x5F34, 0x8F88, 0x5F35, 0x8F89, 0x5F36, 0x8F8A, 0x5F37, 0x8F8B, 0x5F38, + 0x8F8C, 0x5F3B, 0x8F8D, 0x5F3D, 0x8F8E, 0x5F3E, 0x8F8F, 0x5F3F, 0x8F90, 0x5F41, 0x8F91, 0x5F42, 0x8F92, 0x5F43, 0x8F93, 0x5F44, + 0x8F94, 0x5F45, 0x8F95, 0x5F46, 0x8F96, 0x5F47, 0x8F97, 0x5F48, 0x8F98, 0x5F49, 0x8F99, 0x5F4A, 0x8F9A, 0x5F4B, 0x8F9B, 0x5F4C, + 0x8F9C, 0x5F4D, 0x8F9D, 0x5F4E, 0x8F9E, 0x5F4F, 0x8F9F, 0x5F51, 0x8FA0, 0x5F54, 0x8FA1, 0x5F59, 0x8FA2, 0x5F5A, 0x8FA3, 0x5F5B, + 0x8FA4, 0x5F5C, 0x8FA5, 0x5F5E, 0x8FA6, 0x5F5F, 0x8FA7, 0x5F60, 0x8FA8, 0x5F63, 0x8FA9, 0x5F65, 0x8FAA, 0x5F67, 0x8FAB, 0x5F68, + 0x8FAC, 0x5F6B, 0x8FAD, 0x5F6E, 0x8FAE, 0x5F6F, 0x8FAF, 0x5F72, 0x8FB0, 0x5F74, 0x8FB1, 0x5F75, 0x8FB2, 0x5F76, 0x8FB3, 0x5F78, + 0x8FB4, 0x5F7A, 0x8FB5, 0x5F7D, 0x8FB6, 0x5F7E, 0x8FB7, 0x5F7F, 0x8FB8, 0x5F83, 0x8FB9, 0x5F86, 0x8FBA, 0x5F8D, 0x8FBB, 0x5F8E, + 0x8FBC, 0x5F8F, 0x8FBD, 0x5F91, 0x8FBE, 0x5F93, 0x8FBF, 0x5F94, 0x8FC0, 0x5F96, 0x8FC1, 0x5F9A, 0x8FC2, 0x5F9B, 0x8FC3, 0x5F9D, + 0x8FC4, 0x5F9E, 0x8FC5, 0x5F9F, 0x8FC6, 0x5FA0, 0x8FC7, 0x5FA2, 0x8FC8, 0x5FA3, 0x8FC9, 0x5FA4, 0x8FCA, 0x5FA5, 0x8FCB, 0x5FA6, + 0x8FCC, 0x5FA7, 0x8FCD, 0x5FA9, 0x8FCE, 0x5FAB, 0x8FCF, 0x5FAC, 0x8FD0, 0x5FAF, 0x8FD1, 0x5FB0, 0x8FD2, 0x5FB1, 0x8FD3, 0x5FB2, + 0x8FD4, 0x5FB3, 0x8FD5, 0x5FB4, 0x8FD6, 0x5FB6, 0x8FD7, 0x5FB8, 0x8FD8, 0x5FB9, 0x8FD9, 0x5FBA, 0x8FDA, 0x5FBB, 0x8FDB, 0x5FBE, + 0x8FDC, 0x5FBF, 0x8FDD, 0x5FC0, 0x8FDE, 0x5FC1, 0x8FDF, 0x5FC2, 0x8FE0, 0x5FC7, 0x8FE1, 0x5FC8, 0x8FE2, 0x5FCA, 0x8FE3, 0x5FCB, + 0x8FE4, 0x5FCE, 0x8FE5, 0x5FD3, 0x8FE6, 0x5FD4, 0x8FE7, 0x5FD5, 0x8FE8, 0x5FDA, 0x8FE9, 0x5FDB, 0x8FEA, 0x5FDC, 0x8FEB, 0x5FDE, + 0x8FEC, 0x5FDF, 0x8FED, 0x5FE2, 0x8FEE, 0x5FE3, 0x8FEF, 0x5FE5, 0x8FF0, 0x5FE6, 0x8FF1, 0x5FE8, 0x8FF2, 0x5FE9, 0x8FF3, 0x5FEC, + 0x8FF4, 0x5FEF, 0x8FF5, 0x5FF0, 0x8FF6, 0x5FF2, 0x8FF7, 0x5FF3, 0x8FF8, 0x5FF4, 0x8FF9, 0x5FF6, 0x8FFA, 0x5FF7, 0x8FFB, 0x5FF9, + 0x8FFC, 0x5FFA, 0x8FFD, 0x5FFC, 0x8FFE, 0x6007, 0x9040, 0x6008, 0x9041, 0x6009, 0x9042, 0x600B, 0x9043, 0x600C, 0x9044, 0x6010, + 0x9045, 0x6011, 0x9046, 0x6013, 0x9047, 0x6017, 0x9048, 0x6018, 0x9049, 0x601A, 0x904A, 0x601E, 0x904B, 0x601F, 0x904C, 0x6022, + 0x904D, 0x6023, 0x904E, 0x6024, 0x904F, 0x602C, 0x9050, 0x602D, 0x9051, 0x602E, 0x9052, 0x6030, 0x9053, 0x6031, 0x9054, 0x6032, + 0x9055, 0x6033, 0x9056, 0x6034, 0x9057, 0x6036, 0x9058, 0x6037, 0x9059, 0x6038, 0x905A, 0x6039, 0x905B, 0x603A, 0x905C, 0x603D, + 0x905D, 0x603E, 0x905E, 0x6040, 0x905F, 0x6044, 0x9060, 0x6045, 0x9061, 0x6046, 0x9062, 0x6047, 0x9063, 0x6048, 0x9064, 0x6049, + 0x9065, 0x604A, 0x9066, 0x604C, 0x9067, 0x604E, 0x9068, 0x604F, 0x9069, 0x6051, 0x906A, 0x6053, 0x906B, 0x6054, 0x906C, 0x6056, + 0x906D, 0x6057, 0x906E, 0x6058, 0x906F, 0x605B, 0x9070, 0x605C, 0x9071, 0x605E, 0x9072, 0x605F, 0x9073, 0x6060, 0x9074, 0x6061, + 0x9075, 0x6065, 0x9076, 0x6066, 0x9077, 0x606E, 0x9078, 0x6071, 0x9079, 0x6072, 0x907A, 0x6074, 0x907B, 0x6075, 0x907C, 0x6077, + 0x907D, 0x607E, 0x907E, 0x6080, 0x9080, 0x6081, 0x9081, 0x6082, 0x9082, 0x6085, 0x9083, 0x6086, 0x9084, 0x6087, 0x9085, 0x6088, + 0x9086, 0x608A, 0x9087, 0x608B, 0x9088, 0x608E, 0x9089, 0x608F, 0x908A, 0x6090, 0x908B, 0x6091, 0x908C, 0x6093, 0x908D, 0x6095, + 0x908E, 0x6097, 0x908F, 0x6098, 0x9090, 0x6099, 0x9091, 0x609C, 0x9092, 0x609E, 0x9093, 0x60A1, 0x9094, 0x60A2, 0x9095, 0x60A4, + 0x9096, 0x60A5, 0x9097, 0x60A7, 0x9098, 0x60A9, 0x9099, 0x60AA, 0x909A, 0x60AE, 0x909B, 0x60B0, 0x909C, 0x60B3, 0x909D, 0x60B5, + 0x909E, 0x60B6, 0x909F, 0x60B7, 0x90A0, 0x60B9, 0x90A1, 0x60BA, 0x90A2, 0x60BD, 0x90A3, 0x60BE, 0x90A4, 0x60BF, 0x90A5, 0x60C0, + 0x90A6, 0x60C1, 0x90A7, 0x60C2, 0x90A8, 0x60C3, 0x90A9, 0x60C4, 0x90AA, 0x60C7, 0x90AB, 0x60C8, 0x90AC, 0x60C9, 0x90AD, 0x60CC, + 0x90AE, 0x60CD, 0x90AF, 0x60CE, 0x90B0, 0x60CF, 0x90B1, 0x60D0, 0x90B2, 0x60D2, 0x90B3, 0x60D3, 0x90B4, 0x60D4, 0x90B5, 0x60D6, + 0x90B6, 0x60D7, 0x90B7, 0x60D9, 0x90B8, 0x60DB, 0x90B9, 0x60DE, 0x90BA, 0x60E1, 0x90BB, 0x60E2, 0x90BC, 0x60E3, 0x90BD, 0x60E4, + 0x90BE, 0x60E5, 0x90BF, 0x60EA, 0x90C0, 0x60F1, 0x90C1, 0x60F2, 0x90C2, 0x60F5, 0x90C3, 0x60F7, 0x90C4, 0x60F8, 0x90C5, 0x60FB, + 0x90C6, 0x60FC, 0x90C7, 0x60FD, 0x90C8, 0x60FE, 0x90C9, 0x60FF, 0x90CA, 0x6102, 0x90CB, 0x6103, 0x90CC, 0x6104, 0x90CD, 0x6105, + 0x90CE, 0x6107, 0x90CF, 0x610A, 0x90D0, 0x610B, 0x90D1, 0x610C, 0x90D2, 0x6110, 0x90D3, 0x6111, 0x90D4, 0x6112, 0x90D5, 0x6113, + 0x90D6, 0x6114, 0x90D7, 0x6116, 0x90D8, 0x6117, 0x90D9, 0x6118, 0x90DA, 0x6119, 0x90DB, 0x611B, 0x90DC, 0x611C, 0x90DD, 0x611D, + 0x90DE, 0x611E, 0x90DF, 0x6121, 0x90E0, 0x6122, 0x90E1, 0x6125, 0x90E2, 0x6128, 0x90E3, 0x6129, 0x90E4, 0x612A, 0x90E5, 0x612C, + 0x90E6, 0x612D, 0x90E7, 0x612E, 0x90E8, 0x612F, 0x90E9, 0x6130, 0x90EA, 0x6131, 0x90EB, 0x6132, 0x90EC, 0x6133, 0x90ED, 0x6134, + 0x90EE, 0x6135, 0x90EF, 0x6136, 0x90F0, 0x6137, 0x90F1, 0x6138, 0x90F2, 0x6139, 0x90F3, 0x613A, 0x90F4, 0x613B, 0x90F5, 0x613C, + 0x90F6, 0x613D, 0x90F7, 0x613E, 0x90F8, 0x6140, 0x90F9, 0x6141, 0x90FA, 0x6142, 0x90FB, 0x6143, 0x90FC, 0x6144, 0x90FD, 0x6145, + 0x90FE, 0x6146, 0x9140, 0x6147, 0x9141, 0x6149, 0x9142, 0x614B, 0x9143, 0x614D, 0x9144, 0x614F, 0x9145, 0x6150, 0x9146, 0x6152, + 0x9147, 0x6153, 0x9148, 0x6154, 0x9149, 0x6156, 0x914A, 0x6157, 0x914B, 0x6158, 0x914C, 0x6159, 0x914D, 0x615A, 0x914E, 0x615B, + 0x914F, 0x615C, 0x9150, 0x615E, 0x9151, 0x615F, 0x9152, 0x6160, 0x9153, 0x6161, 0x9154, 0x6163, 0x9155, 0x6164, 0x9156, 0x6165, + 0x9157, 0x6166, 0x9158, 0x6169, 0x9159, 0x616A, 0x915A, 0x616B, 0x915B, 0x616C, 0x915C, 0x616D, 0x915D, 0x616E, 0x915E, 0x616F, + 0x915F, 0x6171, 0x9160, 0x6172, 0x9161, 0x6173, 0x9162, 0x6174, 0x9163, 0x6176, 0x9164, 0x6178, 0x9165, 0x6179, 0x9166, 0x617A, + 0x9167, 0x617B, 0x9168, 0x617C, 0x9169, 0x617D, 0x916A, 0x617E, 0x916B, 0x617F, 0x916C, 0x6180, 0x916D, 0x6181, 0x916E, 0x6182, + 0x916F, 0x6183, 0x9170, 0x6184, 0x9171, 0x6185, 0x9172, 0x6186, 0x9173, 0x6187, 0x9174, 0x6188, 0x9175, 0x6189, 0x9176, 0x618A, + 0x9177, 0x618C, 0x9178, 0x618D, 0x9179, 0x618F, 0x917A, 0x6190, 0x917B, 0x6191, 0x917C, 0x6192, 0x917D, 0x6193, 0x917E, 0x6195, + 0x9180, 0x6196, 0x9181, 0x6197, 0x9182, 0x6198, 0x9183, 0x6199, 0x9184, 0x619A, 0x9185, 0x619B, 0x9186, 0x619C, 0x9187, 0x619E, + 0x9188, 0x619F, 0x9189, 0x61A0, 0x918A, 0x61A1, 0x918B, 0x61A2, 0x918C, 0x61A3, 0x918D, 0x61A4, 0x918E, 0x61A5, 0x918F, 0x61A6, + 0x9190, 0x61AA, 0x9191, 0x61AB, 0x9192, 0x61AD, 0x9193, 0x61AE, 0x9194, 0x61AF, 0x9195, 0x61B0, 0x9196, 0x61B1, 0x9197, 0x61B2, + 0x9198, 0x61B3, 0x9199, 0x61B4, 0x919A, 0x61B5, 0x919B, 0x61B6, 0x919C, 0x61B8, 0x919D, 0x61B9, 0x919E, 0x61BA, 0x919F, 0x61BB, + 0x91A0, 0x61BC, 0x91A1, 0x61BD, 0x91A2, 0x61BF, 0x91A3, 0x61C0, 0x91A4, 0x61C1, 0x91A5, 0x61C3, 0x91A6, 0x61C4, 0x91A7, 0x61C5, + 0x91A8, 0x61C6, 0x91A9, 0x61C7, 0x91AA, 0x61C9, 0x91AB, 0x61CC, 0x91AC, 0x61CD, 0x91AD, 0x61CE, 0x91AE, 0x61CF, 0x91AF, 0x61D0, + 0x91B0, 0x61D3, 0x91B1, 0x61D5, 0x91B2, 0x61D6, 0x91B3, 0x61D7, 0x91B4, 0x61D8, 0x91B5, 0x61D9, 0x91B6, 0x61DA, 0x91B7, 0x61DB, + 0x91B8, 0x61DC, 0x91B9, 0x61DD, 0x91BA, 0x61DE, 0x91BB, 0x61DF, 0x91BC, 0x61E0, 0x91BD, 0x61E1, 0x91BE, 0x61E2, 0x91BF, 0x61E3, + 0x91C0, 0x61E4, 0x91C1, 0x61E5, 0x91C2, 0x61E7, 0x91C3, 0x61E8, 0x91C4, 0x61E9, 0x91C5, 0x61EA, 0x91C6, 0x61EB, 0x91C7, 0x61EC, + 0x91C8, 0x61ED, 0x91C9, 0x61EE, 0x91CA, 0x61EF, 0x91CB, 0x61F0, 0x91CC, 0x61F1, 0x91CD, 0x61F2, 0x91CE, 0x61F3, 0x91CF, 0x61F4, + 0x91D0, 0x61F6, 0x91D1, 0x61F7, 0x91D2, 0x61F8, 0x91D3, 0x61F9, 0x91D4, 0x61FA, 0x91D5, 0x61FB, 0x91D6, 0x61FC, 0x91D7, 0x61FD, + 0x91D8, 0x61FE, 0x91D9, 0x6200, 0x91DA, 0x6201, 0x91DB, 0x6202, 0x91DC, 0x6203, 0x91DD, 0x6204, 0x91DE, 0x6205, 0x91DF, 0x6207, + 0x91E0, 0x6209, 0x91E1, 0x6213, 0x91E2, 0x6214, 0x91E3, 0x6219, 0x91E4, 0x621C, 0x91E5, 0x621D, 0x91E6, 0x621E, 0x91E7, 0x6220, + 0x91E8, 0x6223, 0x91E9, 0x6226, 0x91EA, 0x6227, 0x91EB, 0x6228, 0x91EC, 0x6229, 0x91ED, 0x622B, 0x91EE, 0x622D, 0x91EF, 0x622F, + 0x91F0, 0x6230, 0x91F1, 0x6231, 0x91F2, 0x6232, 0x91F3, 0x6235, 0x91F4, 0x6236, 0x91F5, 0x6238, 0x91F6, 0x6239, 0x91F7, 0x623A, + 0x91F8, 0x623B, 0x91F9, 0x623C, 0x91FA, 0x6242, 0x91FB, 0x6244, 0x91FC, 0x6245, 0x91FD, 0x6246, 0x91FE, 0x624A, 0x9240, 0x624F, + 0x9241, 0x6250, 0x9242, 0x6255, 0x9243, 0x6256, 0x9244, 0x6257, 0x9245, 0x6259, 0x9246, 0x625A, 0x9247, 0x625C, 0x9248, 0x625D, + 0x9249, 0x625E, 0x924A, 0x625F, 0x924B, 0x6260, 0x924C, 0x6261, 0x924D, 0x6262, 0x924E, 0x6264, 0x924F, 0x6265, 0x9250, 0x6268, + 0x9251, 0x6271, 0x9252, 0x6272, 0x9253, 0x6274, 0x9254, 0x6275, 0x9255, 0x6277, 0x9256, 0x6278, 0x9257, 0x627A, 0x9258, 0x627B, + 0x9259, 0x627D, 0x925A, 0x6281, 0x925B, 0x6282, 0x925C, 0x6283, 0x925D, 0x6285, 0x925E, 0x6286, 0x925F, 0x6287, 0x9260, 0x6288, + 0x9261, 0x628B, 0x9262, 0x628C, 0x9263, 0x628D, 0x9264, 0x628E, 0x9265, 0x628F, 0x9266, 0x6290, 0x9267, 0x6294, 0x9268, 0x6299, + 0x9269, 0x629C, 0x926A, 0x629D, 0x926B, 0x629E, 0x926C, 0x62A3, 0x926D, 0x62A6, 0x926E, 0x62A7, 0x926F, 0x62A9, 0x9270, 0x62AA, + 0x9271, 0x62AD, 0x9272, 0x62AE, 0x9273, 0x62AF, 0x9274, 0x62B0, 0x9275, 0x62B2, 0x9276, 0x62B3, 0x9277, 0x62B4, 0x9278, 0x62B6, + 0x9279, 0x62B7, 0x927A, 0x62B8, 0x927B, 0x62BA, 0x927C, 0x62BE, 0x927D, 0x62C0, 0x927E, 0x62C1, 0x9280, 0x62C3, 0x9281, 0x62CB, + 0x9282, 0x62CF, 0x9283, 0x62D1, 0x9284, 0x62D5, 0x9285, 0x62DD, 0x9286, 0x62DE, 0x9287, 0x62E0, 0x9288, 0x62E1, 0x9289, 0x62E4, + 0x928A, 0x62EA, 0x928B, 0x62EB, 0x928C, 0x62F0, 0x928D, 0x62F2, 0x928E, 0x62F5, 0x928F, 0x62F8, 0x9290, 0x62F9, 0x9291, 0x62FA, + 0x9292, 0x62FB, 0x9293, 0x6300, 0x9294, 0x6303, 0x9295, 0x6304, 0x9296, 0x6305, 0x9297, 0x6306, 0x9298, 0x630A, 0x9299, 0x630B, + 0x929A, 0x630C, 0x929B, 0x630D, 0x929C, 0x630F, 0x929D, 0x6310, 0x929E, 0x6312, 0x929F, 0x6313, 0x92A0, 0x6314, 0x92A1, 0x6315, + 0x92A2, 0x6317, 0x92A3, 0x6318, 0x92A4, 0x6319, 0x92A5, 0x631C, 0x92A6, 0x6326, 0x92A7, 0x6327, 0x92A8, 0x6329, 0x92A9, 0x632C, + 0x92AA, 0x632D, 0x92AB, 0x632E, 0x92AC, 0x6330, 0x92AD, 0x6331, 0x92AE, 0x6333, 0x92AF, 0x6334, 0x92B0, 0x6335, 0x92B1, 0x6336, + 0x92B2, 0x6337, 0x92B3, 0x6338, 0x92B4, 0x633B, 0x92B5, 0x633C, 0x92B6, 0x633E, 0x92B7, 0x633F, 0x92B8, 0x6340, 0x92B9, 0x6341, + 0x92BA, 0x6344, 0x92BB, 0x6347, 0x92BC, 0x6348, 0x92BD, 0x634A, 0x92BE, 0x6351, 0x92BF, 0x6352, 0x92C0, 0x6353, 0x92C1, 0x6354, + 0x92C2, 0x6356, 0x92C3, 0x6357, 0x92C4, 0x6358, 0x92C5, 0x6359, 0x92C6, 0x635A, 0x92C7, 0x635B, 0x92C8, 0x635C, 0x92C9, 0x635D, + 0x92CA, 0x6360, 0x92CB, 0x6364, 0x92CC, 0x6365, 0x92CD, 0x6366, 0x92CE, 0x6368, 0x92CF, 0x636A, 0x92D0, 0x636B, 0x92D1, 0x636C, + 0x92D2, 0x636F, 0x92D3, 0x6370, 0x92D4, 0x6372, 0x92D5, 0x6373, 0x92D6, 0x6374, 0x92D7, 0x6375, 0x92D8, 0x6378, 0x92D9, 0x6379, + 0x92DA, 0x637C, 0x92DB, 0x637D, 0x92DC, 0x637E, 0x92DD, 0x637F, 0x92DE, 0x6381, 0x92DF, 0x6383, 0x92E0, 0x6384, 0x92E1, 0x6385, + 0x92E2, 0x6386, 0x92E3, 0x638B, 0x92E4, 0x638D, 0x92E5, 0x6391, 0x92E6, 0x6393, 0x92E7, 0x6394, 0x92E8, 0x6395, 0x92E9, 0x6397, + 0x92EA, 0x6399, 0x92EB, 0x639A, 0x92EC, 0x639B, 0x92ED, 0x639C, 0x92EE, 0x639D, 0x92EF, 0x639E, 0x92F0, 0x639F, 0x92F1, 0x63A1, + 0x92F2, 0x63A4, 0x92F3, 0x63A6, 0x92F4, 0x63AB, 0x92F5, 0x63AF, 0x92F6, 0x63B1, 0x92F7, 0x63B2, 0x92F8, 0x63B5, 0x92F9, 0x63B6, + 0x92FA, 0x63B9, 0x92FB, 0x63BB, 0x92FC, 0x63BD, 0x92FD, 0x63BF, 0x92FE, 0x63C0, 0x9340, 0x63C1, 0x9341, 0x63C2, 0x9342, 0x63C3, + 0x9343, 0x63C5, 0x9344, 0x63C7, 0x9345, 0x63C8, 0x9346, 0x63CA, 0x9347, 0x63CB, 0x9348, 0x63CC, 0x9349, 0x63D1, 0x934A, 0x63D3, + 0x934B, 0x63D4, 0x934C, 0x63D5, 0x934D, 0x63D7, 0x934E, 0x63D8, 0x934F, 0x63D9, 0x9350, 0x63DA, 0x9351, 0x63DB, 0x9352, 0x63DC, + 0x9353, 0x63DD, 0x9354, 0x63DF, 0x9355, 0x63E2, 0x9356, 0x63E4, 0x9357, 0x63E5, 0x9358, 0x63E6, 0x9359, 0x63E7, 0x935A, 0x63E8, + 0x935B, 0x63EB, 0x935C, 0x63EC, 0x935D, 0x63EE, 0x935E, 0x63EF, 0x935F, 0x63F0, 0x9360, 0x63F1, 0x9361, 0x63F3, 0x9362, 0x63F5, + 0x9363, 0x63F7, 0x9364, 0x63F9, 0x9365, 0x63FA, 0x9366, 0x63FB, 0x9367, 0x63FC, 0x9368, 0x63FE, 0x9369, 0x6403, 0x936A, 0x6404, + 0x936B, 0x6406, 0x936C, 0x6407, 0x936D, 0x6408, 0x936E, 0x6409, 0x936F, 0x640A, 0x9370, 0x640D, 0x9371, 0x640E, 0x9372, 0x6411, + 0x9373, 0x6412, 0x9374, 0x6415, 0x9375, 0x6416, 0x9376, 0x6417, 0x9377, 0x6418, 0x9378, 0x6419, 0x9379, 0x641A, 0x937A, 0x641D, + 0x937B, 0x641F, 0x937C, 0x6422, 0x937D, 0x6423, 0x937E, 0x6424, 0x9380, 0x6425, 0x9381, 0x6427, 0x9382, 0x6428, 0x9383, 0x6429, + 0x9384, 0x642B, 0x9385, 0x642E, 0x9386, 0x642F, 0x9387, 0x6430, 0x9388, 0x6431, 0x9389, 0x6432, 0x938A, 0x6433, 0x938B, 0x6435, + 0x938C, 0x6436, 0x938D, 0x6437, 0x938E, 0x6438, 0x938F, 0x6439, 0x9390, 0x643B, 0x9391, 0x643C, 0x9392, 0x643E, 0x9393, 0x6440, + 0x9394, 0x6442, 0x9395, 0x6443, 0x9396, 0x6449, 0x9397, 0x644B, 0x9398, 0x644C, 0x9399, 0x644D, 0x939A, 0x644E, 0x939B, 0x644F, + 0x939C, 0x6450, 0x939D, 0x6451, 0x939E, 0x6453, 0x939F, 0x6455, 0x93A0, 0x6456, 0x93A1, 0x6457, 0x93A2, 0x6459, 0x93A3, 0x645A, + 0x93A4, 0x645B, 0x93A5, 0x645C, 0x93A6, 0x645D, 0x93A7, 0x645F, 0x93A8, 0x6460, 0x93A9, 0x6461, 0x93AA, 0x6462, 0x93AB, 0x6463, + 0x93AC, 0x6464, 0x93AD, 0x6465, 0x93AE, 0x6466, 0x93AF, 0x6468, 0x93B0, 0x646A, 0x93B1, 0x646B, 0x93B2, 0x646C, 0x93B3, 0x646E, + 0x93B4, 0x646F, 0x93B5, 0x6470, 0x93B6, 0x6471, 0x93B7, 0x6472, 0x93B8, 0x6473, 0x93B9, 0x6474, 0x93BA, 0x6475, 0x93BB, 0x6476, + 0x93BC, 0x6477, 0x93BD, 0x647B, 0x93BE, 0x647C, 0x93BF, 0x647D, 0x93C0, 0x647E, 0x93C1, 0x647F, 0x93C2, 0x6480, 0x93C3, 0x6481, + 0x93C4, 0x6483, 0x93C5, 0x6486, 0x93C6, 0x6488, 0x93C7, 0x6489, 0x93C8, 0x648A, 0x93C9, 0x648B, 0x93CA, 0x648C, 0x93CB, 0x648D, + 0x93CC, 0x648E, 0x93CD, 0x648F, 0x93CE, 0x6490, 0x93CF, 0x6493, 0x93D0, 0x6494, 0x93D1, 0x6497, 0x93D2, 0x6498, 0x93D3, 0x649A, + 0x93D4, 0x649B, 0x93D5, 0x649C, 0x93D6, 0x649D, 0x93D7, 0x649F, 0x93D8, 0x64A0, 0x93D9, 0x64A1, 0x93DA, 0x64A2, 0x93DB, 0x64A3, + 0x93DC, 0x64A5, 0x93DD, 0x64A6, 0x93DE, 0x64A7, 0x93DF, 0x64A8, 0x93E0, 0x64AA, 0x93E1, 0x64AB, 0x93E2, 0x64AF, 0x93E3, 0x64B1, + 0x93E4, 0x64B2, 0x93E5, 0x64B3, 0x93E6, 0x64B4, 0x93E7, 0x64B6, 0x93E8, 0x64B9, 0x93E9, 0x64BB, 0x93EA, 0x64BD, 0x93EB, 0x64BE, + 0x93EC, 0x64BF, 0x93ED, 0x64C1, 0x93EE, 0x64C3, 0x93EF, 0x64C4, 0x93F0, 0x64C6, 0x93F1, 0x64C7, 0x93F2, 0x64C8, 0x93F3, 0x64C9, + 0x93F4, 0x64CA, 0x93F5, 0x64CB, 0x93F6, 0x64CC, 0x93F7, 0x64CF, 0x93F8, 0x64D1, 0x93F9, 0x64D3, 0x93FA, 0x64D4, 0x93FB, 0x64D5, + 0x93FC, 0x64D6, 0x93FD, 0x64D9, 0x93FE, 0x64DA, 0x9440, 0x64DB, 0x9441, 0x64DC, 0x9442, 0x64DD, 0x9443, 0x64DF, 0x9444, 0x64E0, + 0x9445, 0x64E1, 0x9446, 0x64E3, 0x9447, 0x64E5, 0x9448, 0x64E7, 0x9449, 0x64E8, 0x944A, 0x64E9, 0x944B, 0x64EA, 0x944C, 0x64EB, + 0x944D, 0x64EC, 0x944E, 0x64ED, 0x944F, 0x64EE, 0x9450, 0x64EF, 0x9451, 0x64F0, 0x9452, 0x64F1, 0x9453, 0x64F2, 0x9454, 0x64F3, + 0x9455, 0x64F4, 0x9456, 0x64F5, 0x9457, 0x64F6, 0x9458, 0x64F7, 0x9459, 0x64F8, 0x945A, 0x64F9, 0x945B, 0x64FA, 0x945C, 0x64FB, + 0x945D, 0x64FC, 0x945E, 0x64FD, 0x945F, 0x64FE, 0x9460, 0x64FF, 0x9461, 0x6501, 0x9462, 0x6502, 0x9463, 0x6503, 0x9464, 0x6504, + 0x9465, 0x6505, 0x9466, 0x6506, 0x9467, 0x6507, 0x9468, 0x6508, 0x9469, 0x650A, 0x946A, 0x650B, 0x946B, 0x650C, 0x946C, 0x650D, + 0x946D, 0x650E, 0x946E, 0x650F, 0x946F, 0x6510, 0x9470, 0x6511, 0x9471, 0x6513, 0x9472, 0x6514, 0x9473, 0x6515, 0x9474, 0x6516, + 0x9475, 0x6517, 0x9476, 0x6519, 0x9477, 0x651A, 0x9478, 0x651B, 0x9479, 0x651C, 0x947A, 0x651D, 0x947B, 0x651E, 0x947C, 0x651F, + 0x947D, 0x6520, 0x947E, 0x6521, 0x9480, 0x6522, 0x9481, 0x6523, 0x9482, 0x6524, 0x9483, 0x6526, 0x9484, 0x6527, 0x9485, 0x6528, + 0x9486, 0x6529, 0x9487, 0x652A, 0x9488, 0x652C, 0x9489, 0x652D, 0x948A, 0x6530, 0x948B, 0x6531, 0x948C, 0x6532, 0x948D, 0x6533, + 0x948E, 0x6537, 0x948F, 0x653A, 0x9490, 0x653C, 0x9491, 0x653D, 0x9492, 0x6540, 0x9493, 0x6541, 0x9494, 0x6542, 0x9495, 0x6543, + 0x9496, 0x6544, 0x9497, 0x6546, 0x9498, 0x6547, 0x9499, 0x654A, 0x949A, 0x654B, 0x949B, 0x654D, 0x949C, 0x654E, 0x949D, 0x6550, + 0x949E, 0x6552, 0x949F, 0x6553, 0x94A0, 0x6554, 0x94A1, 0x6557, 0x94A2, 0x6558, 0x94A3, 0x655A, 0x94A4, 0x655C, 0x94A5, 0x655F, + 0x94A6, 0x6560, 0x94A7, 0x6561, 0x94A8, 0x6564, 0x94A9, 0x6565, 0x94AA, 0x6567, 0x94AB, 0x6568, 0x94AC, 0x6569, 0x94AD, 0x656A, + 0x94AE, 0x656D, 0x94AF, 0x656E, 0x94B0, 0x656F, 0x94B1, 0x6571, 0x94B2, 0x6573, 0x94B3, 0x6575, 0x94B4, 0x6576, 0x94B5, 0x6578, + 0x94B6, 0x6579, 0x94B7, 0x657A, 0x94B8, 0x657B, 0x94B9, 0x657C, 0x94BA, 0x657D, 0x94BB, 0x657E, 0x94BC, 0x657F, 0x94BD, 0x6580, + 0x94BE, 0x6581, 0x94BF, 0x6582, 0x94C0, 0x6583, 0x94C1, 0x6584, 0x94C2, 0x6585, 0x94C3, 0x6586, 0x94C4, 0x6588, 0x94C5, 0x6589, + 0x94C6, 0x658A, 0x94C7, 0x658D, 0x94C8, 0x658E, 0x94C9, 0x658F, 0x94CA, 0x6592, 0x94CB, 0x6594, 0x94CC, 0x6595, 0x94CD, 0x6596, + 0x94CE, 0x6598, 0x94CF, 0x659A, 0x94D0, 0x659D, 0x94D1, 0x659E, 0x94D2, 0x65A0, 0x94D3, 0x65A2, 0x94D4, 0x65A3, 0x94D5, 0x65A6, + 0x94D6, 0x65A8, 0x94D7, 0x65AA, 0x94D8, 0x65AC, 0x94D9, 0x65AE, 0x94DA, 0x65B1, 0x94DB, 0x65B2, 0x94DC, 0x65B3, 0x94DD, 0x65B4, + 0x94DE, 0x65B5, 0x94DF, 0x65B6, 0x94E0, 0x65B7, 0x94E1, 0x65B8, 0x94E2, 0x65BA, 0x94E3, 0x65BB, 0x94E4, 0x65BE, 0x94E5, 0x65BF, + 0x94E6, 0x65C0, 0x94E7, 0x65C2, 0x94E8, 0x65C7, 0x94E9, 0x65C8, 0x94EA, 0x65C9, 0x94EB, 0x65CA, 0x94EC, 0x65CD, 0x94ED, 0x65D0, + 0x94EE, 0x65D1, 0x94EF, 0x65D3, 0x94F0, 0x65D4, 0x94F1, 0x65D5, 0x94F2, 0x65D8, 0x94F3, 0x65D9, 0x94F4, 0x65DA, 0x94F5, 0x65DB, + 0x94F6, 0x65DC, 0x94F7, 0x65DD, 0x94F8, 0x65DE, 0x94F9, 0x65DF, 0x94FA, 0x65E1, 0x94FB, 0x65E3, 0x94FC, 0x65E4, 0x94FD, 0x65EA, + 0x94FE, 0x65EB, 0x9540, 0x65F2, 0x9541, 0x65F3, 0x9542, 0x65F4, 0x9543, 0x65F5, 0x9544, 0x65F8, 0x9545, 0x65F9, 0x9546, 0x65FB, + 0x9547, 0x65FC, 0x9548, 0x65FD, 0x9549, 0x65FE, 0x954A, 0x65FF, 0x954B, 0x6601, 0x954C, 0x6604, 0x954D, 0x6605, 0x954E, 0x6607, + 0x954F, 0x6608, 0x9550, 0x6609, 0x9551, 0x660B, 0x9552, 0x660D, 0x9553, 0x6610, 0x9554, 0x6611, 0x9555, 0x6612, 0x9556, 0x6616, + 0x9557, 0x6617, 0x9558, 0x6618, 0x9559, 0x661A, 0x955A, 0x661B, 0x955B, 0x661C, 0x955C, 0x661E, 0x955D, 0x6621, 0x955E, 0x6622, + 0x955F, 0x6623, 0x9560, 0x6624, 0x9561, 0x6626, 0x9562, 0x6629, 0x9563, 0x662A, 0x9564, 0x662B, 0x9565, 0x662C, 0x9566, 0x662E, + 0x9567, 0x6630, 0x9568, 0x6632, 0x9569, 0x6633, 0x956A, 0x6637, 0x956B, 0x6638, 0x956C, 0x6639, 0x956D, 0x663A, 0x956E, 0x663B, + 0x956F, 0x663D, 0x9570, 0x663F, 0x9571, 0x6640, 0x9572, 0x6642, 0x9573, 0x6644, 0x9574, 0x6645, 0x9575, 0x6646, 0x9576, 0x6647, + 0x9577, 0x6648, 0x9578, 0x6649, 0x9579, 0x664A, 0x957A, 0x664D, 0x957B, 0x664E, 0x957C, 0x6650, 0x957D, 0x6651, 0x957E, 0x6658, + 0x9580, 0x6659, 0x9581, 0x665B, 0x9582, 0x665C, 0x9583, 0x665D, 0x9584, 0x665E, 0x9585, 0x6660, 0x9586, 0x6662, 0x9587, 0x6663, + 0x9588, 0x6665, 0x9589, 0x6667, 0x958A, 0x6669, 0x958B, 0x666A, 0x958C, 0x666B, 0x958D, 0x666C, 0x958E, 0x666D, 0x958F, 0x6671, + 0x9590, 0x6672, 0x9591, 0x6673, 0x9592, 0x6675, 0x9593, 0x6678, 0x9594, 0x6679, 0x9595, 0x667B, 0x9596, 0x667C, 0x9597, 0x667D, + 0x9598, 0x667F, 0x9599, 0x6680, 0x959A, 0x6681, 0x959B, 0x6683, 0x959C, 0x6685, 0x959D, 0x6686, 0x959E, 0x6688, 0x959F, 0x6689, + 0x95A0, 0x668A, 0x95A1, 0x668B, 0x95A2, 0x668D, 0x95A3, 0x668E, 0x95A4, 0x668F, 0x95A5, 0x6690, 0x95A6, 0x6692, 0x95A7, 0x6693, + 0x95A8, 0x6694, 0x95A9, 0x6695, 0x95AA, 0x6698, 0x95AB, 0x6699, 0x95AC, 0x669A, 0x95AD, 0x669B, 0x95AE, 0x669C, 0x95AF, 0x669E, + 0x95B0, 0x669F, 0x95B1, 0x66A0, 0x95B2, 0x66A1, 0x95B3, 0x66A2, 0x95B4, 0x66A3, 0x95B5, 0x66A4, 0x95B6, 0x66A5, 0x95B7, 0x66A6, + 0x95B8, 0x66A9, 0x95B9, 0x66AA, 0x95BA, 0x66AB, 0x95BB, 0x66AC, 0x95BC, 0x66AD, 0x95BD, 0x66AF, 0x95BE, 0x66B0, 0x95BF, 0x66B1, + 0x95C0, 0x66B2, 0x95C1, 0x66B3, 0x95C2, 0x66B5, 0x95C3, 0x66B6, 0x95C4, 0x66B7, 0x95C5, 0x66B8, 0x95C6, 0x66BA, 0x95C7, 0x66BB, + 0x95C8, 0x66BC, 0x95C9, 0x66BD, 0x95CA, 0x66BF, 0x95CB, 0x66C0, 0x95CC, 0x66C1, 0x95CD, 0x66C2, 0x95CE, 0x66C3, 0x95CF, 0x66C4, + 0x95D0, 0x66C5, 0x95D1, 0x66C6, 0x95D2, 0x66C7, 0x95D3, 0x66C8, 0x95D4, 0x66C9, 0x95D5, 0x66CA, 0x95D6, 0x66CB, 0x95D7, 0x66CC, + 0x95D8, 0x66CD, 0x95D9, 0x66CE, 0x95DA, 0x66CF, 0x95DB, 0x66D0, 0x95DC, 0x66D1, 0x95DD, 0x66D2, 0x95DE, 0x66D3, 0x95DF, 0x66D4, + 0x95E0, 0x66D5, 0x95E1, 0x66D6, 0x95E2, 0x66D7, 0x95E3, 0x66D8, 0x95E4, 0x66DA, 0x95E5, 0x66DE, 0x95E6, 0x66DF, 0x95E7, 0x66E0, + 0x95E8, 0x66E1, 0x95E9, 0x66E2, 0x95EA, 0x66E3, 0x95EB, 0x66E4, 0x95EC, 0x66E5, 0x95ED, 0x66E7, 0x95EE, 0x66E8, 0x95EF, 0x66EA, + 0x95F0, 0x66EB, 0x95F1, 0x66EC, 0x95F2, 0x66ED, 0x95F3, 0x66EE, 0x95F4, 0x66EF, 0x95F5, 0x66F1, 0x95F6, 0x66F5, 0x95F7, 0x66F6, + 0x95F8, 0x66F8, 0x95F9, 0x66FA, 0x95FA, 0x66FB, 0x95FB, 0x66FD, 0x95FC, 0x6701, 0x95FD, 0x6702, 0x95FE, 0x6703, 0x9640, 0x6704, + 0x9641, 0x6705, 0x9642, 0x6706, 0x9643, 0x6707, 0x9644, 0x670C, 0x9645, 0x670E, 0x9646, 0x670F, 0x9647, 0x6711, 0x9648, 0x6712, + 0x9649, 0x6713, 0x964A, 0x6716, 0x964B, 0x6718, 0x964C, 0x6719, 0x964D, 0x671A, 0x964E, 0x671C, 0x964F, 0x671E, 0x9650, 0x6720, + 0x9651, 0x6721, 0x9652, 0x6722, 0x9653, 0x6723, 0x9654, 0x6724, 0x9655, 0x6725, 0x9656, 0x6727, 0x9657, 0x6729, 0x9658, 0x672E, + 0x9659, 0x6730, 0x965A, 0x6732, 0x965B, 0x6733, 0x965C, 0x6736, 0x965D, 0x6737, 0x965E, 0x6738, 0x965F, 0x6739, 0x9660, 0x673B, + 0x9661, 0x673C, 0x9662, 0x673E, 0x9663, 0x673F, 0x9664, 0x6741, 0x9665, 0x6744, 0x9666, 0x6745, 0x9667, 0x6747, 0x9668, 0x674A, + 0x9669, 0x674B, 0x966A, 0x674D, 0x966B, 0x6752, 0x966C, 0x6754, 0x966D, 0x6755, 0x966E, 0x6757, 0x966F, 0x6758, 0x9670, 0x6759, + 0x9671, 0x675A, 0x9672, 0x675B, 0x9673, 0x675D, 0x9674, 0x6762, 0x9675, 0x6763, 0x9676, 0x6764, 0x9677, 0x6766, 0x9678, 0x6767, + 0x9679, 0x676B, 0x967A, 0x676C, 0x967B, 0x676E, 0x967C, 0x6771, 0x967D, 0x6774, 0x967E, 0x6776, 0x9680, 0x6778, 0x9681, 0x6779, + 0x9682, 0x677A, 0x9683, 0x677B, 0x9684, 0x677D, 0x9685, 0x6780, 0x9686, 0x6782, 0x9687, 0x6783, 0x9688, 0x6785, 0x9689, 0x6786, + 0x968A, 0x6788, 0x968B, 0x678A, 0x968C, 0x678C, 0x968D, 0x678D, 0x968E, 0x678E, 0x968F, 0x678F, 0x9690, 0x6791, 0x9691, 0x6792, + 0x9692, 0x6793, 0x9693, 0x6794, 0x9694, 0x6796, 0x9695, 0x6799, 0x9696, 0x679B, 0x9697, 0x679F, 0x9698, 0x67A0, 0x9699, 0x67A1, + 0x969A, 0x67A4, 0x969B, 0x67A6, 0x969C, 0x67A9, 0x969D, 0x67AC, 0x969E, 0x67AE, 0x969F, 0x67B1, 0x96A0, 0x67B2, 0x96A1, 0x67B4, + 0x96A2, 0x67B9, 0x96A3, 0x67BA, 0x96A4, 0x67BB, 0x96A5, 0x67BC, 0x96A6, 0x67BD, 0x96A7, 0x67BE, 0x96A8, 0x67BF, 0x96A9, 0x67C0, + 0x96AA, 0x67C2, 0x96AB, 0x67C5, 0x96AC, 0x67C6, 0x96AD, 0x67C7, 0x96AE, 0x67C8, 0x96AF, 0x67C9, 0x96B0, 0x67CA, 0x96B1, 0x67CB, + 0x96B2, 0x67CC, 0x96B3, 0x67CD, 0x96B4, 0x67CE, 0x96B5, 0x67D5, 0x96B6, 0x67D6, 0x96B7, 0x67D7, 0x96B8, 0x67DB, 0x96B9, 0x67DF, + 0x96BA, 0x67E1, 0x96BB, 0x67E3, 0x96BC, 0x67E4, 0x96BD, 0x67E6, 0x96BE, 0x67E7, 0x96BF, 0x67E8, 0x96C0, 0x67EA, 0x96C1, 0x67EB, + 0x96C2, 0x67ED, 0x96C3, 0x67EE, 0x96C4, 0x67F2, 0x96C5, 0x67F5, 0x96C6, 0x67F6, 0x96C7, 0x67F7, 0x96C8, 0x67F8, 0x96C9, 0x67F9, + 0x96CA, 0x67FA, 0x96CB, 0x67FB, 0x96CC, 0x67FC, 0x96CD, 0x67FE, 0x96CE, 0x6801, 0x96CF, 0x6802, 0x96D0, 0x6803, 0x96D1, 0x6804, + 0x96D2, 0x6806, 0x96D3, 0x680D, 0x96D4, 0x6810, 0x96D5, 0x6812, 0x96D6, 0x6814, 0x96D7, 0x6815, 0x96D8, 0x6818, 0x96D9, 0x6819, + 0x96DA, 0x681A, 0x96DB, 0x681B, 0x96DC, 0x681C, 0x96DD, 0x681E, 0x96DE, 0x681F, 0x96DF, 0x6820, 0x96E0, 0x6822, 0x96E1, 0x6823, + 0x96E2, 0x6824, 0x96E3, 0x6825, 0x96E4, 0x6826, 0x96E5, 0x6827, 0x96E6, 0x6828, 0x96E7, 0x682B, 0x96E8, 0x682C, 0x96E9, 0x682D, + 0x96EA, 0x682E, 0x96EB, 0x682F, 0x96EC, 0x6830, 0x96ED, 0x6831, 0x96EE, 0x6834, 0x96EF, 0x6835, 0x96F0, 0x6836, 0x96F1, 0x683A, + 0x96F2, 0x683B, 0x96F3, 0x683F, 0x96F4, 0x6847, 0x96F5, 0x684B, 0x96F6, 0x684D, 0x96F7, 0x684F, 0x96F8, 0x6852, 0x96F9, 0x6856, + 0x96FA, 0x6857, 0x96FB, 0x6858, 0x96FC, 0x6859, 0x96FD, 0x685A, 0x96FE, 0x685B, 0x9740, 0x685C, 0x9741, 0x685D, 0x9742, 0x685E, + 0x9743, 0x685F, 0x9744, 0x686A, 0x9745, 0x686C, 0x9746, 0x686D, 0x9747, 0x686E, 0x9748, 0x686F, 0x9749, 0x6870, 0x974A, 0x6871, + 0x974B, 0x6872, 0x974C, 0x6873, 0x974D, 0x6875, 0x974E, 0x6878, 0x974F, 0x6879, 0x9750, 0x687A, 0x9751, 0x687B, 0x9752, 0x687C, + 0x9753, 0x687D, 0x9754, 0x687E, 0x9755, 0x687F, 0x9756, 0x6880, 0x9757, 0x6882, 0x9758, 0x6884, 0x9759, 0x6887, 0x975A, 0x6888, + 0x975B, 0x6889, 0x975C, 0x688A, 0x975D, 0x688B, 0x975E, 0x688C, 0x975F, 0x688D, 0x9760, 0x688E, 0x9761, 0x6890, 0x9762, 0x6891, + 0x9763, 0x6892, 0x9764, 0x6894, 0x9765, 0x6895, 0x9766, 0x6896, 0x9767, 0x6898, 0x9768, 0x6899, 0x9769, 0x689A, 0x976A, 0x689B, + 0x976B, 0x689C, 0x976C, 0x689D, 0x976D, 0x689E, 0x976E, 0x689F, 0x976F, 0x68A0, 0x9770, 0x68A1, 0x9771, 0x68A3, 0x9772, 0x68A4, + 0x9773, 0x68A5, 0x9774, 0x68A9, 0x9775, 0x68AA, 0x9776, 0x68AB, 0x9777, 0x68AC, 0x9778, 0x68AE, 0x9779, 0x68B1, 0x977A, 0x68B2, + 0x977B, 0x68B4, 0x977C, 0x68B6, 0x977D, 0x68B7, 0x977E, 0x68B8, 0x9780, 0x68B9, 0x9781, 0x68BA, 0x9782, 0x68BB, 0x9783, 0x68BC, + 0x9784, 0x68BD, 0x9785, 0x68BE, 0x9786, 0x68BF, 0x9787, 0x68C1, 0x9788, 0x68C3, 0x9789, 0x68C4, 0x978A, 0x68C5, 0x978B, 0x68C6, + 0x978C, 0x68C7, 0x978D, 0x68C8, 0x978E, 0x68CA, 0x978F, 0x68CC, 0x9790, 0x68CE, 0x9791, 0x68CF, 0x9792, 0x68D0, 0x9793, 0x68D1, + 0x9794, 0x68D3, 0x9795, 0x68D4, 0x9796, 0x68D6, 0x9797, 0x68D7, 0x9798, 0x68D9, 0x9799, 0x68DB, 0x979A, 0x68DC, 0x979B, 0x68DD, + 0x979C, 0x68DE, 0x979D, 0x68DF, 0x979E, 0x68E1, 0x979F, 0x68E2, 0x97A0, 0x68E4, 0x97A1, 0x68E5, 0x97A2, 0x68E6, 0x97A3, 0x68E7, + 0x97A4, 0x68E8, 0x97A5, 0x68E9, 0x97A6, 0x68EA, 0x97A7, 0x68EB, 0x97A8, 0x68EC, 0x97A9, 0x68ED, 0x97AA, 0x68EF, 0x97AB, 0x68F2, + 0x97AC, 0x68F3, 0x97AD, 0x68F4, 0x97AE, 0x68F6, 0x97AF, 0x68F7, 0x97B0, 0x68F8, 0x97B1, 0x68FB, 0x97B2, 0x68FD, 0x97B3, 0x68FE, + 0x97B4, 0x68FF, 0x97B5, 0x6900, 0x97B6, 0x6902, 0x97B7, 0x6903, 0x97B8, 0x6904, 0x97B9, 0x6906, 0x97BA, 0x6907, 0x97BB, 0x6908, + 0x97BC, 0x6909, 0x97BD, 0x690A, 0x97BE, 0x690C, 0x97BF, 0x690F, 0x97C0, 0x6911, 0x97C1, 0x6913, 0x97C2, 0x6914, 0x97C3, 0x6915, + 0x97C4, 0x6916, 0x97C5, 0x6917, 0x97C6, 0x6918, 0x97C7, 0x6919, 0x97C8, 0x691A, 0x97C9, 0x691B, 0x97CA, 0x691C, 0x97CB, 0x691D, + 0x97CC, 0x691E, 0x97CD, 0x6921, 0x97CE, 0x6922, 0x97CF, 0x6923, 0x97D0, 0x6925, 0x97D1, 0x6926, 0x97D2, 0x6927, 0x97D3, 0x6928, + 0x97D4, 0x6929, 0x97D5, 0x692A, 0x97D6, 0x692B, 0x97D7, 0x692C, 0x97D8, 0x692E, 0x97D9, 0x692F, 0x97DA, 0x6931, 0x97DB, 0x6932, + 0x97DC, 0x6933, 0x97DD, 0x6935, 0x97DE, 0x6936, 0x97DF, 0x6937, 0x97E0, 0x6938, 0x97E1, 0x693A, 0x97E2, 0x693B, 0x97E3, 0x693C, + 0x97E4, 0x693E, 0x97E5, 0x6940, 0x97E6, 0x6941, 0x97E7, 0x6943, 0x97E8, 0x6944, 0x97E9, 0x6945, 0x97EA, 0x6946, 0x97EB, 0x6947, + 0x97EC, 0x6948, 0x97ED, 0x6949, 0x97EE, 0x694A, 0x97EF, 0x694B, 0x97F0, 0x694C, 0x97F1, 0x694D, 0x97F2, 0x694E, 0x97F3, 0x694F, + 0x97F4, 0x6950, 0x97F5, 0x6951, 0x97F6, 0x6952, 0x97F7, 0x6953, 0x97F8, 0x6955, 0x97F9, 0x6956, 0x97FA, 0x6958, 0x97FB, 0x6959, + 0x97FC, 0x695B, 0x97FD, 0x695C, 0x97FE, 0x695F, 0x9840, 0x6961, 0x9841, 0x6962, 0x9842, 0x6964, 0x9843, 0x6965, 0x9844, 0x6967, + 0x9845, 0x6968, 0x9846, 0x6969, 0x9847, 0x696A, 0x9848, 0x696C, 0x9849, 0x696D, 0x984A, 0x696F, 0x984B, 0x6970, 0x984C, 0x6972, + 0x984D, 0x6973, 0x984E, 0x6974, 0x984F, 0x6975, 0x9850, 0x6976, 0x9851, 0x697A, 0x9852, 0x697B, 0x9853, 0x697D, 0x9854, 0x697E, + 0x9855, 0x697F, 0x9856, 0x6981, 0x9857, 0x6983, 0x9858, 0x6985, 0x9859, 0x698A, 0x985A, 0x698B, 0x985B, 0x698C, 0x985C, 0x698E, + 0x985D, 0x698F, 0x985E, 0x6990, 0x985F, 0x6991, 0x9860, 0x6992, 0x9861, 0x6993, 0x9862, 0x6996, 0x9863, 0x6997, 0x9864, 0x6999, + 0x9865, 0x699A, 0x9866, 0x699D, 0x9867, 0x699E, 0x9868, 0x699F, 0x9869, 0x69A0, 0x986A, 0x69A1, 0x986B, 0x69A2, 0x986C, 0x69A3, + 0x986D, 0x69A4, 0x986E, 0x69A5, 0x986F, 0x69A6, 0x9870, 0x69A9, 0x9871, 0x69AA, 0x9872, 0x69AC, 0x9873, 0x69AE, 0x9874, 0x69AF, + 0x9875, 0x69B0, 0x9876, 0x69B2, 0x9877, 0x69B3, 0x9878, 0x69B5, 0x9879, 0x69B6, 0x987A, 0x69B8, 0x987B, 0x69B9, 0x987C, 0x69BA, + 0x987D, 0x69BC, 0x987E, 0x69BD, 0x9880, 0x69BE, 0x9881, 0x69BF, 0x9882, 0x69C0, 0x9883, 0x69C2, 0x9884, 0x69C3, 0x9885, 0x69C4, + 0x9886, 0x69C5, 0x9887, 0x69C6, 0x9888, 0x69C7, 0x9889, 0x69C8, 0x988A, 0x69C9, 0x988B, 0x69CB, 0x988C, 0x69CD, 0x988D, 0x69CF, + 0x988E, 0x69D1, 0x988F, 0x69D2, 0x9890, 0x69D3, 0x9891, 0x69D5, 0x9892, 0x69D6, 0x9893, 0x69D7, 0x9894, 0x69D8, 0x9895, 0x69D9, + 0x9896, 0x69DA, 0x9897, 0x69DC, 0x9898, 0x69DD, 0x9899, 0x69DE, 0x989A, 0x69E1, 0x989B, 0x69E2, 0x989C, 0x69E3, 0x989D, 0x69E4, + 0x989E, 0x69E5, 0x989F, 0x69E6, 0x98A0, 0x69E7, 0x98A1, 0x69E8, 0x98A2, 0x69E9, 0x98A3, 0x69EA, 0x98A4, 0x69EB, 0x98A5, 0x69EC, + 0x98A6, 0x69EE, 0x98A7, 0x69EF, 0x98A8, 0x69F0, 0x98A9, 0x69F1, 0x98AA, 0x69F3, 0x98AB, 0x69F4, 0x98AC, 0x69F5, 0x98AD, 0x69F6, + 0x98AE, 0x69F7, 0x98AF, 0x69F8, 0x98B0, 0x69F9, 0x98B1, 0x69FA, 0x98B2, 0x69FB, 0x98B3, 0x69FC, 0x98B4, 0x69FE, 0x98B5, 0x6A00, + 0x98B6, 0x6A01, 0x98B7, 0x6A02, 0x98B8, 0x6A03, 0x98B9, 0x6A04, 0x98BA, 0x6A05, 0x98BB, 0x6A06, 0x98BC, 0x6A07, 0x98BD, 0x6A08, + 0x98BE, 0x6A09, 0x98BF, 0x6A0B, 0x98C0, 0x6A0C, 0x98C1, 0x6A0D, 0x98C2, 0x6A0E, 0x98C3, 0x6A0F, 0x98C4, 0x6A10, 0x98C5, 0x6A11, + 0x98C6, 0x6A12, 0x98C7, 0x6A13, 0x98C8, 0x6A14, 0x98C9, 0x6A15, 0x98CA, 0x6A16, 0x98CB, 0x6A19, 0x98CC, 0x6A1A, 0x98CD, 0x6A1B, + 0x98CE, 0x6A1C, 0x98CF, 0x6A1D, 0x98D0, 0x6A1E, 0x98D1, 0x6A20, 0x98D2, 0x6A22, 0x98D3, 0x6A23, 0x98D4, 0x6A24, 0x98D5, 0x6A25, + 0x98D6, 0x6A26, 0x98D7, 0x6A27, 0x98D8, 0x6A29, 0x98D9, 0x6A2B, 0x98DA, 0x6A2C, 0x98DB, 0x6A2D, 0x98DC, 0x6A2E, 0x98DD, 0x6A30, + 0x98DE, 0x6A32, 0x98DF, 0x6A33, 0x98E0, 0x6A34, 0x98E1, 0x6A36, 0x98E2, 0x6A37, 0x98E3, 0x6A38, 0x98E4, 0x6A39, 0x98E5, 0x6A3A, + 0x98E6, 0x6A3B, 0x98E7, 0x6A3C, 0x98E8, 0x6A3F, 0x98E9, 0x6A40, 0x98EA, 0x6A41, 0x98EB, 0x6A42, 0x98EC, 0x6A43, 0x98ED, 0x6A45, + 0x98EE, 0x6A46, 0x98EF, 0x6A48, 0x98F0, 0x6A49, 0x98F1, 0x6A4A, 0x98F2, 0x6A4B, 0x98F3, 0x6A4C, 0x98F4, 0x6A4D, 0x98F5, 0x6A4E, + 0x98F6, 0x6A4F, 0x98F7, 0x6A51, 0x98F8, 0x6A52, 0x98F9, 0x6A53, 0x98FA, 0x6A54, 0x98FB, 0x6A55, 0x98FC, 0x6A56, 0x98FD, 0x6A57, + 0x98FE, 0x6A5A, 0x9940, 0x6A5C, 0x9941, 0x6A5D, 0x9942, 0x6A5E, 0x9943, 0x6A5F, 0x9944, 0x6A60, 0x9945, 0x6A62, 0x9946, 0x6A63, + 0x9947, 0x6A64, 0x9948, 0x6A66, 0x9949, 0x6A67, 0x994A, 0x6A68, 0x994B, 0x6A69, 0x994C, 0x6A6A, 0x994D, 0x6A6B, 0x994E, 0x6A6C, + 0x994F, 0x6A6D, 0x9950, 0x6A6E, 0x9951, 0x6A6F, 0x9952, 0x6A70, 0x9953, 0x6A72, 0x9954, 0x6A73, 0x9955, 0x6A74, 0x9956, 0x6A75, + 0x9957, 0x6A76, 0x9958, 0x6A77, 0x9959, 0x6A78, 0x995A, 0x6A7A, 0x995B, 0x6A7B, 0x995C, 0x6A7D, 0x995D, 0x6A7E, 0x995E, 0x6A7F, + 0x995F, 0x6A81, 0x9960, 0x6A82, 0x9961, 0x6A83, 0x9962, 0x6A85, 0x9963, 0x6A86, 0x9964, 0x6A87, 0x9965, 0x6A88, 0x9966, 0x6A89, + 0x9967, 0x6A8A, 0x9968, 0x6A8B, 0x9969, 0x6A8C, 0x996A, 0x6A8D, 0x996B, 0x6A8F, 0x996C, 0x6A92, 0x996D, 0x6A93, 0x996E, 0x6A94, + 0x996F, 0x6A95, 0x9970, 0x6A96, 0x9971, 0x6A98, 0x9972, 0x6A99, 0x9973, 0x6A9A, 0x9974, 0x6A9B, 0x9975, 0x6A9C, 0x9976, 0x6A9D, + 0x9977, 0x6A9E, 0x9978, 0x6A9F, 0x9979, 0x6AA1, 0x997A, 0x6AA2, 0x997B, 0x6AA3, 0x997C, 0x6AA4, 0x997D, 0x6AA5, 0x997E, 0x6AA6, + 0x9980, 0x6AA7, 0x9981, 0x6AA8, 0x9982, 0x6AAA, 0x9983, 0x6AAD, 0x9984, 0x6AAE, 0x9985, 0x6AAF, 0x9986, 0x6AB0, 0x9987, 0x6AB1, + 0x9988, 0x6AB2, 0x9989, 0x6AB3, 0x998A, 0x6AB4, 0x998B, 0x6AB5, 0x998C, 0x6AB6, 0x998D, 0x6AB7, 0x998E, 0x6AB8, 0x998F, 0x6AB9, + 0x9990, 0x6ABA, 0x9991, 0x6ABB, 0x9992, 0x6ABC, 0x9993, 0x6ABD, 0x9994, 0x6ABE, 0x9995, 0x6ABF, 0x9996, 0x6AC0, 0x9997, 0x6AC1, + 0x9998, 0x6AC2, 0x9999, 0x6AC3, 0x999A, 0x6AC4, 0x999B, 0x6AC5, 0x999C, 0x6AC6, 0x999D, 0x6AC7, 0x999E, 0x6AC8, 0x999F, 0x6AC9, + 0x99A0, 0x6ACA, 0x99A1, 0x6ACB, 0x99A2, 0x6ACC, 0x99A3, 0x6ACD, 0x99A4, 0x6ACE, 0x99A5, 0x6ACF, 0x99A6, 0x6AD0, 0x99A7, 0x6AD1, + 0x99A8, 0x6AD2, 0x99A9, 0x6AD3, 0x99AA, 0x6AD4, 0x99AB, 0x6AD5, 0x99AC, 0x6AD6, 0x99AD, 0x6AD7, 0x99AE, 0x6AD8, 0x99AF, 0x6AD9, + 0x99B0, 0x6ADA, 0x99B1, 0x6ADB, 0x99B2, 0x6ADC, 0x99B3, 0x6ADD, 0x99B4, 0x6ADE, 0x99B5, 0x6ADF, 0x99B6, 0x6AE0, 0x99B7, 0x6AE1, + 0x99B8, 0x6AE2, 0x99B9, 0x6AE3, 0x99BA, 0x6AE4, 0x99BB, 0x6AE5, 0x99BC, 0x6AE6, 0x99BD, 0x6AE7, 0x99BE, 0x6AE8, 0x99BF, 0x6AE9, + 0x99C0, 0x6AEA, 0x99C1, 0x6AEB, 0x99C2, 0x6AEC, 0x99C3, 0x6AED, 0x99C4, 0x6AEE, 0x99C5, 0x6AEF, 0x99C6, 0x6AF0, 0x99C7, 0x6AF1, + 0x99C8, 0x6AF2, 0x99C9, 0x6AF3, 0x99CA, 0x6AF4, 0x99CB, 0x6AF5, 0x99CC, 0x6AF6, 0x99CD, 0x6AF7, 0x99CE, 0x6AF8, 0x99CF, 0x6AF9, + 0x99D0, 0x6AFA, 0x99D1, 0x6AFB, 0x99D2, 0x6AFC, 0x99D3, 0x6AFD, 0x99D4, 0x6AFE, 0x99D5, 0x6AFF, 0x99D6, 0x6B00, 0x99D7, 0x6B01, + 0x99D8, 0x6B02, 0x99D9, 0x6B03, 0x99DA, 0x6B04, 0x99DB, 0x6B05, 0x99DC, 0x6B06, 0x99DD, 0x6B07, 0x99DE, 0x6B08, 0x99DF, 0x6B09, + 0x99E0, 0x6B0A, 0x99E1, 0x6B0B, 0x99E2, 0x6B0C, 0x99E3, 0x6B0D, 0x99E4, 0x6B0E, 0x99E5, 0x6B0F, 0x99E6, 0x6B10, 0x99E7, 0x6B11, + 0x99E8, 0x6B12, 0x99E9, 0x6B13, 0x99EA, 0x6B14, 0x99EB, 0x6B15, 0x99EC, 0x6B16, 0x99ED, 0x6B17, 0x99EE, 0x6B18, 0x99EF, 0x6B19, + 0x99F0, 0x6B1A, 0x99F1, 0x6B1B, 0x99F2, 0x6B1C, 0x99F3, 0x6B1D, 0x99F4, 0x6B1E, 0x99F5, 0x6B1F, 0x99F6, 0x6B25, 0x99F7, 0x6B26, + 0x99F8, 0x6B28, 0x99F9, 0x6B29, 0x99FA, 0x6B2A, 0x99FB, 0x6B2B, 0x99FC, 0x6B2C, 0x99FD, 0x6B2D, 0x99FE, 0x6B2E, 0x9A40, 0x6B2F, + 0x9A41, 0x6B30, 0x9A42, 0x6B31, 0x9A43, 0x6B33, 0x9A44, 0x6B34, 0x9A45, 0x6B35, 0x9A46, 0x6B36, 0x9A47, 0x6B38, 0x9A48, 0x6B3B, + 0x9A49, 0x6B3C, 0x9A4A, 0x6B3D, 0x9A4B, 0x6B3F, 0x9A4C, 0x6B40, 0x9A4D, 0x6B41, 0x9A4E, 0x6B42, 0x9A4F, 0x6B44, 0x9A50, 0x6B45, + 0x9A51, 0x6B48, 0x9A52, 0x6B4A, 0x9A53, 0x6B4B, 0x9A54, 0x6B4D, 0x9A55, 0x6B4E, 0x9A56, 0x6B4F, 0x9A57, 0x6B50, 0x9A58, 0x6B51, + 0x9A59, 0x6B52, 0x9A5A, 0x6B53, 0x9A5B, 0x6B54, 0x9A5C, 0x6B55, 0x9A5D, 0x6B56, 0x9A5E, 0x6B57, 0x9A5F, 0x6B58, 0x9A60, 0x6B5A, + 0x9A61, 0x6B5B, 0x9A62, 0x6B5C, 0x9A63, 0x6B5D, 0x9A64, 0x6B5E, 0x9A65, 0x6B5F, 0x9A66, 0x6B60, 0x9A67, 0x6B61, 0x9A68, 0x6B68, + 0x9A69, 0x6B69, 0x9A6A, 0x6B6B, 0x9A6B, 0x6B6C, 0x9A6C, 0x6B6D, 0x9A6D, 0x6B6E, 0x9A6E, 0x6B6F, 0x9A6F, 0x6B70, 0x9A70, 0x6B71, + 0x9A71, 0x6B72, 0x9A72, 0x6B73, 0x9A73, 0x6B74, 0x9A74, 0x6B75, 0x9A75, 0x6B76, 0x9A76, 0x6B77, 0x9A77, 0x6B78, 0x9A78, 0x6B7A, + 0x9A79, 0x6B7D, 0x9A7A, 0x6B7E, 0x9A7B, 0x6B7F, 0x9A7C, 0x6B80, 0x9A7D, 0x6B85, 0x9A7E, 0x6B88, 0x9A80, 0x6B8C, 0x9A81, 0x6B8E, + 0x9A82, 0x6B8F, 0x9A83, 0x6B90, 0x9A84, 0x6B91, 0x9A85, 0x6B94, 0x9A86, 0x6B95, 0x9A87, 0x6B97, 0x9A88, 0x6B98, 0x9A89, 0x6B99, + 0x9A8A, 0x6B9C, 0x9A8B, 0x6B9D, 0x9A8C, 0x6B9E, 0x9A8D, 0x6B9F, 0x9A8E, 0x6BA0, 0x9A8F, 0x6BA2, 0x9A90, 0x6BA3, 0x9A91, 0x6BA4, + 0x9A92, 0x6BA5, 0x9A93, 0x6BA6, 0x9A94, 0x6BA7, 0x9A95, 0x6BA8, 0x9A96, 0x6BA9, 0x9A97, 0x6BAB, 0x9A98, 0x6BAC, 0x9A99, 0x6BAD, + 0x9A9A, 0x6BAE, 0x9A9B, 0x6BAF, 0x9A9C, 0x6BB0, 0x9A9D, 0x6BB1, 0x9A9E, 0x6BB2, 0x9A9F, 0x6BB6, 0x9AA0, 0x6BB8, 0x9AA1, 0x6BB9, + 0x9AA2, 0x6BBA, 0x9AA3, 0x6BBB, 0x9AA4, 0x6BBC, 0x9AA5, 0x6BBD, 0x9AA6, 0x6BBE, 0x9AA7, 0x6BC0, 0x9AA8, 0x6BC3, 0x9AA9, 0x6BC4, + 0x9AAA, 0x6BC6, 0x9AAB, 0x6BC7, 0x9AAC, 0x6BC8, 0x9AAD, 0x6BC9, 0x9AAE, 0x6BCA, 0x9AAF, 0x6BCC, 0x9AB0, 0x6BCE, 0x9AB1, 0x6BD0, + 0x9AB2, 0x6BD1, 0x9AB3, 0x6BD8, 0x9AB4, 0x6BDA, 0x9AB5, 0x6BDC, 0x9AB6, 0x6BDD, 0x9AB7, 0x6BDE, 0x9AB8, 0x6BDF, 0x9AB9, 0x6BE0, + 0x9ABA, 0x6BE2, 0x9ABB, 0x6BE3, 0x9ABC, 0x6BE4, 0x9ABD, 0x6BE5, 0x9ABE, 0x6BE6, 0x9ABF, 0x6BE7, 0x9AC0, 0x6BE8, 0x9AC1, 0x6BE9, + 0x9AC2, 0x6BEC, 0x9AC3, 0x6BED, 0x9AC4, 0x6BEE, 0x9AC5, 0x6BF0, 0x9AC6, 0x6BF1, 0x9AC7, 0x6BF2, 0x9AC8, 0x6BF4, 0x9AC9, 0x6BF6, + 0x9ACA, 0x6BF7, 0x9ACB, 0x6BF8, 0x9ACC, 0x6BFA, 0x9ACD, 0x6BFB, 0x9ACE, 0x6BFC, 0x9ACF, 0x6BFE, 0x9AD0, 0x6BFF, 0x9AD1, 0x6C00, + 0x9AD2, 0x6C01, 0x9AD3, 0x6C02, 0x9AD4, 0x6C03, 0x9AD5, 0x6C04, 0x9AD6, 0x6C08, 0x9AD7, 0x6C09, 0x9AD8, 0x6C0A, 0x9AD9, 0x6C0B, + 0x9ADA, 0x6C0C, 0x9ADB, 0x6C0E, 0x9ADC, 0x6C12, 0x9ADD, 0x6C17, 0x9ADE, 0x6C1C, 0x9ADF, 0x6C1D, 0x9AE0, 0x6C1E, 0x9AE1, 0x6C20, + 0x9AE2, 0x6C23, 0x9AE3, 0x6C25, 0x9AE4, 0x6C2B, 0x9AE5, 0x6C2C, 0x9AE6, 0x6C2D, 0x9AE7, 0x6C31, 0x9AE8, 0x6C33, 0x9AE9, 0x6C36, + 0x9AEA, 0x6C37, 0x9AEB, 0x6C39, 0x9AEC, 0x6C3A, 0x9AED, 0x6C3B, 0x9AEE, 0x6C3C, 0x9AEF, 0x6C3E, 0x9AF0, 0x6C3F, 0x9AF1, 0x6C43, + 0x9AF2, 0x6C44, 0x9AF3, 0x6C45, 0x9AF4, 0x6C48, 0x9AF5, 0x6C4B, 0x9AF6, 0x6C4C, 0x9AF7, 0x6C4D, 0x9AF8, 0x6C4E, 0x9AF9, 0x6C4F, + 0x9AFA, 0x6C51, 0x9AFB, 0x6C52, 0x9AFC, 0x6C53, 0x9AFD, 0x6C56, 0x9AFE, 0x6C58, 0x9B40, 0x6C59, 0x9B41, 0x6C5A, 0x9B42, 0x6C62, + 0x9B43, 0x6C63, 0x9B44, 0x6C65, 0x9B45, 0x6C66, 0x9B46, 0x6C67, 0x9B47, 0x6C6B, 0x9B48, 0x6C6C, 0x9B49, 0x6C6D, 0x9B4A, 0x6C6E, + 0x9B4B, 0x6C6F, 0x9B4C, 0x6C71, 0x9B4D, 0x6C73, 0x9B4E, 0x6C75, 0x9B4F, 0x6C77, 0x9B50, 0x6C78, 0x9B51, 0x6C7A, 0x9B52, 0x6C7B, + 0x9B53, 0x6C7C, 0x9B54, 0x6C7F, 0x9B55, 0x6C80, 0x9B56, 0x6C84, 0x9B57, 0x6C87, 0x9B58, 0x6C8A, 0x9B59, 0x6C8B, 0x9B5A, 0x6C8D, + 0x9B5B, 0x6C8E, 0x9B5C, 0x6C91, 0x9B5D, 0x6C92, 0x9B5E, 0x6C95, 0x9B5F, 0x6C96, 0x9B60, 0x6C97, 0x9B61, 0x6C98, 0x9B62, 0x6C9A, + 0x9B63, 0x6C9C, 0x9B64, 0x6C9D, 0x9B65, 0x6C9E, 0x9B66, 0x6CA0, 0x9B67, 0x6CA2, 0x9B68, 0x6CA8, 0x9B69, 0x6CAC, 0x9B6A, 0x6CAF, + 0x9B6B, 0x6CB0, 0x9B6C, 0x6CB4, 0x9B6D, 0x6CB5, 0x9B6E, 0x6CB6, 0x9B6F, 0x6CB7, 0x9B70, 0x6CBA, 0x9B71, 0x6CC0, 0x9B72, 0x6CC1, + 0x9B73, 0x6CC2, 0x9B74, 0x6CC3, 0x9B75, 0x6CC6, 0x9B76, 0x6CC7, 0x9B77, 0x6CC8, 0x9B78, 0x6CCB, 0x9B79, 0x6CCD, 0x9B7A, 0x6CCE, + 0x9B7B, 0x6CCF, 0x9B7C, 0x6CD1, 0x9B7D, 0x6CD2, 0x9B7E, 0x6CD8, 0x9B80, 0x6CD9, 0x9B81, 0x6CDA, 0x9B82, 0x6CDC, 0x9B83, 0x6CDD, + 0x9B84, 0x6CDF, 0x9B85, 0x6CE4, 0x9B86, 0x6CE6, 0x9B87, 0x6CE7, 0x9B88, 0x6CE9, 0x9B89, 0x6CEC, 0x9B8A, 0x6CED, 0x9B8B, 0x6CF2, + 0x9B8C, 0x6CF4, 0x9B8D, 0x6CF9, 0x9B8E, 0x6CFF, 0x9B8F, 0x6D00, 0x9B90, 0x6D02, 0x9B91, 0x6D03, 0x9B92, 0x6D05, 0x9B93, 0x6D06, + 0x9B94, 0x6D08, 0x9B95, 0x6D09, 0x9B96, 0x6D0A, 0x9B97, 0x6D0D, 0x9B98, 0x6D0F, 0x9B99, 0x6D10, 0x9B9A, 0x6D11, 0x9B9B, 0x6D13, + 0x9B9C, 0x6D14, 0x9B9D, 0x6D15, 0x9B9E, 0x6D16, 0x9B9F, 0x6D18, 0x9BA0, 0x6D1C, 0x9BA1, 0x6D1D, 0x9BA2, 0x6D1F, 0x9BA3, 0x6D20, + 0x9BA4, 0x6D21, 0x9BA5, 0x6D22, 0x9BA6, 0x6D23, 0x9BA7, 0x6D24, 0x9BA8, 0x6D26, 0x9BA9, 0x6D28, 0x9BAA, 0x6D29, 0x9BAB, 0x6D2C, + 0x9BAC, 0x6D2D, 0x9BAD, 0x6D2F, 0x9BAE, 0x6D30, 0x9BAF, 0x6D34, 0x9BB0, 0x6D36, 0x9BB1, 0x6D37, 0x9BB2, 0x6D38, 0x9BB3, 0x6D3A, + 0x9BB4, 0x6D3F, 0x9BB5, 0x6D40, 0x9BB6, 0x6D42, 0x9BB7, 0x6D44, 0x9BB8, 0x6D49, 0x9BB9, 0x6D4C, 0x9BBA, 0x6D50, 0x9BBB, 0x6D55, + 0x9BBC, 0x6D56, 0x9BBD, 0x6D57, 0x9BBE, 0x6D58, 0x9BBF, 0x6D5B, 0x9BC0, 0x6D5D, 0x9BC1, 0x6D5F, 0x9BC2, 0x6D61, 0x9BC3, 0x6D62, + 0x9BC4, 0x6D64, 0x9BC5, 0x6D65, 0x9BC6, 0x6D67, 0x9BC7, 0x6D68, 0x9BC8, 0x6D6B, 0x9BC9, 0x6D6C, 0x9BCA, 0x6D6D, 0x9BCB, 0x6D70, + 0x9BCC, 0x6D71, 0x9BCD, 0x6D72, 0x9BCE, 0x6D73, 0x9BCF, 0x6D75, 0x9BD0, 0x6D76, 0x9BD1, 0x6D79, 0x9BD2, 0x6D7A, 0x9BD3, 0x6D7B, + 0x9BD4, 0x6D7D, 0x9BD5, 0x6D7E, 0x9BD6, 0x6D7F, 0x9BD7, 0x6D80, 0x9BD8, 0x6D81, 0x9BD9, 0x6D83, 0x9BDA, 0x6D84, 0x9BDB, 0x6D86, + 0x9BDC, 0x6D87, 0x9BDD, 0x6D8A, 0x9BDE, 0x6D8B, 0x9BDF, 0x6D8D, 0x9BE0, 0x6D8F, 0x9BE1, 0x6D90, 0x9BE2, 0x6D92, 0x9BE3, 0x6D96, + 0x9BE4, 0x6D97, 0x9BE5, 0x6D98, 0x9BE6, 0x6D99, 0x9BE7, 0x6D9A, 0x9BE8, 0x6D9C, 0x9BE9, 0x6DA2, 0x9BEA, 0x6DA5, 0x9BEB, 0x6DAC, + 0x9BEC, 0x6DAD, 0x9BED, 0x6DB0, 0x9BEE, 0x6DB1, 0x9BEF, 0x6DB3, 0x9BF0, 0x6DB4, 0x9BF1, 0x6DB6, 0x9BF2, 0x6DB7, 0x9BF3, 0x6DB9, + 0x9BF4, 0x6DBA, 0x9BF5, 0x6DBB, 0x9BF6, 0x6DBC, 0x9BF7, 0x6DBD, 0x9BF8, 0x6DBE, 0x9BF9, 0x6DC1, 0x9BFA, 0x6DC2, 0x9BFB, 0x6DC3, + 0x9BFC, 0x6DC8, 0x9BFD, 0x6DC9, 0x9BFE, 0x6DCA, 0x9C40, 0x6DCD, 0x9C41, 0x6DCE, 0x9C42, 0x6DCF, 0x9C43, 0x6DD0, 0x9C44, 0x6DD2, + 0x9C45, 0x6DD3, 0x9C46, 0x6DD4, 0x9C47, 0x6DD5, 0x9C48, 0x6DD7, 0x9C49, 0x6DDA, 0x9C4A, 0x6DDB, 0x9C4B, 0x6DDC, 0x9C4C, 0x6DDF, + 0x9C4D, 0x6DE2, 0x9C4E, 0x6DE3, 0x9C4F, 0x6DE5, 0x9C50, 0x6DE7, 0x9C51, 0x6DE8, 0x9C52, 0x6DE9, 0x9C53, 0x6DEA, 0x9C54, 0x6DED, + 0x9C55, 0x6DEF, 0x9C56, 0x6DF0, 0x9C57, 0x6DF2, 0x9C58, 0x6DF4, 0x9C59, 0x6DF5, 0x9C5A, 0x6DF6, 0x9C5B, 0x6DF8, 0x9C5C, 0x6DFA, + 0x9C5D, 0x6DFD, 0x9C5E, 0x6DFE, 0x9C5F, 0x6DFF, 0x9C60, 0x6E00, 0x9C61, 0x6E01, 0x9C62, 0x6E02, 0x9C63, 0x6E03, 0x9C64, 0x6E04, + 0x9C65, 0x6E06, 0x9C66, 0x6E07, 0x9C67, 0x6E08, 0x9C68, 0x6E09, 0x9C69, 0x6E0B, 0x9C6A, 0x6E0F, 0x9C6B, 0x6E12, 0x9C6C, 0x6E13, + 0x9C6D, 0x6E15, 0x9C6E, 0x6E18, 0x9C6F, 0x6E19, 0x9C70, 0x6E1B, 0x9C71, 0x6E1C, 0x9C72, 0x6E1E, 0x9C73, 0x6E1F, 0x9C74, 0x6E22, + 0x9C75, 0x6E26, 0x9C76, 0x6E27, 0x9C77, 0x6E28, 0x9C78, 0x6E2A, 0x9C79, 0x6E2C, 0x9C7A, 0x6E2E, 0x9C7B, 0x6E30, 0x9C7C, 0x6E31, + 0x9C7D, 0x6E33, 0x9C7E, 0x6E35, 0x9C80, 0x6E36, 0x9C81, 0x6E37, 0x9C82, 0x6E39, 0x9C83, 0x6E3B, 0x9C84, 0x6E3C, 0x9C85, 0x6E3D, + 0x9C86, 0x6E3E, 0x9C87, 0x6E3F, 0x9C88, 0x6E40, 0x9C89, 0x6E41, 0x9C8A, 0x6E42, 0x9C8B, 0x6E45, 0x9C8C, 0x6E46, 0x9C8D, 0x6E47, + 0x9C8E, 0x6E48, 0x9C8F, 0x6E49, 0x9C90, 0x6E4A, 0x9C91, 0x6E4B, 0x9C92, 0x6E4C, 0x9C93, 0x6E4F, 0x9C94, 0x6E50, 0x9C95, 0x6E51, + 0x9C96, 0x6E52, 0x9C97, 0x6E55, 0x9C98, 0x6E57, 0x9C99, 0x6E59, 0x9C9A, 0x6E5A, 0x9C9B, 0x6E5C, 0x9C9C, 0x6E5D, 0x9C9D, 0x6E5E, + 0x9C9E, 0x6E60, 0x9C9F, 0x6E61, 0x9CA0, 0x6E62, 0x9CA1, 0x6E63, 0x9CA2, 0x6E64, 0x9CA3, 0x6E65, 0x9CA4, 0x6E66, 0x9CA5, 0x6E67, + 0x9CA6, 0x6E68, 0x9CA7, 0x6E69, 0x9CA8, 0x6E6A, 0x9CA9, 0x6E6C, 0x9CAA, 0x6E6D, 0x9CAB, 0x6E6F, 0x9CAC, 0x6E70, 0x9CAD, 0x6E71, + 0x9CAE, 0x6E72, 0x9CAF, 0x6E73, 0x9CB0, 0x6E74, 0x9CB1, 0x6E75, 0x9CB2, 0x6E76, 0x9CB3, 0x6E77, 0x9CB4, 0x6E78, 0x9CB5, 0x6E79, + 0x9CB6, 0x6E7A, 0x9CB7, 0x6E7B, 0x9CB8, 0x6E7C, 0x9CB9, 0x6E7D, 0x9CBA, 0x6E80, 0x9CBB, 0x6E81, 0x9CBC, 0x6E82, 0x9CBD, 0x6E84, + 0x9CBE, 0x6E87, 0x9CBF, 0x6E88, 0x9CC0, 0x6E8A, 0x9CC1, 0x6E8B, 0x9CC2, 0x6E8C, 0x9CC3, 0x6E8D, 0x9CC4, 0x6E8E, 0x9CC5, 0x6E91, + 0x9CC6, 0x6E92, 0x9CC7, 0x6E93, 0x9CC8, 0x6E94, 0x9CC9, 0x6E95, 0x9CCA, 0x6E96, 0x9CCB, 0x6E97, 0x9CCC, 0x6E99, 0x9CCD, 0x6E9A, + 0x9CCE, 0x6E9B, 0x9CCF, 0x6E9D, 0x9CD0, 0x6E9E, 0x9CD1, 0x6EA0, 0x9CD2, 0x6EA1, 0x9CD3, 0x6EA3, 0x9CD4, 0x6EA4, 0x9CD5, 0x6EA6, + 0x9CD6, 0x6EA8, 0x9CD7, 0x6EA9, 0x9CD8, 0x6EAB, 0x9CD9, 0x6EAC, 0x9CDA, 0x6EAD, 0x9CDB, 0x6EAE, 0x9CDC, 0x6EB0, 0x9CDD, 0x6EB3, + 0x9CDE, 0x6EB5, 0x9CDF, 0x6EB8, 0x9CE0, 0x6EB9, 0x9CE1, 0x6EBC, 0x9CE2, 0x6EBE, 0x9CE3, 0x6EBF, 0x9CE4, 0x6EC0, 0x9CE5, 0x6EC3, + 0x9CE6, 0x6EC4, 0x9CE7, 0x6EC5, 0x9CE8, 0x6EC6, 0x9CE9, 0x6EC8, 0x9CEA, 0x6EC9, 0x9CEB, 0x6ECA, 0x9CEC, 0x6ECC, 0x9CED, 0x6ECD, + 0x9CEE, 0x6ECE, 0x9CEF, 0x6ED0, 0x9CF0, 0x6ED2, 0x9CF1, 0x6ED6, 0x9CF2, 0x6ED8, 0x9CF3, 0x6ED9, 0x9CF4, 0x6EDB, 0x9CF5, 0x6EDC, + 0x9CF6, 0x6EDD, 0x9CF7, 0x6EE3, 0x9CF8, 0x6EE7, 0x9CF9, 0x6EEA, 0x9CFA, 0x6EEB, 0x9CFB, 0x6EEC, 0x9CFC, 0x6EED, 0x9CFD, 0x6EEE, + 0x9CFE, 0x6EEF, 0x9D40, 0x6EF0, 0x9D41, 0x6EF1, 0x9D42, 0x6EF2, 0x9D43, 0x6EF3, 0x9D44, 0x6EF5, 0x9D45, 0x6EF6, 0x9D46, 0x6EF7, + 0x9D47, 0x6EF8, 0x9D48, 0x6EFA, 0x9D49, 0x6EFB, 0x9D4A, 0x6EFC, 0x9D4B, 0x6EFD, 0x9D4C, 0x6EFE, 0x9D4D, 0x6EFF, 0x9D4E, 0x6F00, + 0x9D4F, 0x6F01, 0x9D50, 0x6F03, 0x9D51, 0x6F04, 0x9D52, 0x6F05, 0x9D53, 0x6F07, 0x9D54, 0x6F08, 0x9D55, 0x6F0A, 0x9D56, 0x6F0B, + 0x9D57, 0x6F0C, 0x9D58, 0x6F0D, 0x9D59, 0x6F0E, 0x9D5A, 0x6F10, 0x9D5B, 0x6F11, 0x9D5C, 0x6F12, 0x9D5D, 0x6F16, 0x9D5E, 0x6F17, + 0x9D5F, 0x6F18, 0x9D60, 0x6F19, 0x9D61, 0x6F1A, 0x9D62, 0x6F1B, 0x9D63, 0x6F1C, 0x9D64, 0x6F1D, 0x9D65, 0x6F1E, 0x9D66, 0x6F1F, + 0x9D67, 0x6F21, 0x9D68, 0x6F22, 0x9D69, 0x6F23, 0x9D6A, 0x6F25, 0x9D6B, 0x6F26, 0x9D6C, 0x6F27, 0x9D6D, 0x6F28, 0x9D6E, 0x6F2C, + 0x9D6F, 0x6F2E, 0x9D70, 0x6F30, 0x9D71, 0x6F32, 0x9D72, 0x6F34, 0x9D73, 0x6F35, 0x9D74, 0x6F37, 0x9D75, 0x6F38, 0x9D76, 0x6F39, + 0x9D77, 0x6F3A, 0x9D78, 0x6F3B, 0x9D79, 0x6F3C, 0x9D7A, 0x6F3D, 0x9D7B, 0x6F3F, 0x9D7C, 0x6F40, 0x9D7D, 0x6F41, 0x9D7E, 0x6F42, + 0x9D80, 0x6F43, 0x9D81, 0x6F44, 0x9D82, 0x6F45, 0x9D83, 0x6F48, 0x9D84, 0x6F49, 0x9D85, 0x6F4A, 0x9D86, 0x6F4C, 0x9D87, 0x6F4E, + 0x9D88, 0x6F4F, 0x9D89, 0x6F50, 0x9D8A, 0x6F51, 0x9D8B, 0x6F52, 0x9D8C, 0x6F53, 0x9D8D, 0x6F54, 0x9D8E, 0x6F55, 0x9D8F, 0x6F56, + 0x9D90, 0x6F57, 0x9D91, 0x6F59, 0x9D92, 0x6F5A, 0x9D93, 0x6F5B, 0x9D94, 0x6F5D, 0x9D95, 0x6F5F, 0x9D96, 0x6F60, 0x9D97, 0x6F61, + 0x9D98, 0x6F63, 0x9D99, 0x6F64, 0x9D9A, 0x6F65, 0x9D9B, 0x6F67, 0x9D9C, 0x6F68, 0x9D9D, 0x6F69, 0x9D9E, 0x6F6A, 0x9D9F, 0x6F6B, + 0x9DA0, 0x6F6C, 0x9DA1, 0x6F6F, 0x9DA2, 0x6F70, 0x9DA3, 0x6F71, 0x9DA4, 0x6F73, 0x9DA5, 0x6F75, 0x9DA6, 0x6F76, 0x9DA7, 0x6F77, + 0x9DA8, 0x6F79, 0x9DA9, 0x6F7B, 0x9DAA, 0x6F7D, 0x9DAB, 0x6F7E, 0x9DAC, 0x6F7F, 0x9DAD, 0x6F80, 0x9DAE, 0x6F81, 0x9DAF, 0x6F82, + 0x9DB0, 0x6F83, 0x9DB1, 0x6F85, 0x9DB2, 0x6F86, 0x9DB3, 0x6F87, 0x9DB4, 0x6F8A, 0x9DB5, 0x6F8B, 0x9DB6, 0x6F8F, 0x9DB7, 0x6F90, + 0x9DB8, 0x6F91, 0x9DB9, 0x6F92, 0x9DBA, 0x6F93, 0x9DBB, 0x6F94, 0x9DBC, 0x6F95, 0x9DBD, 0x6F96, 0x9DBE, 0x6F97, 0x9DBF, 0x6F98, + 0x9DC0, 0x6F99, 0x9DC1, 0x6F9A, 0x9DC2, 0x6F9B, 0x9DC3, 0x6F9D, 0x9DC4, 0x6F9E, 0x9DC5, 0x6F9F, 0x9DC6, 0x6FA0, 0x9DC7, 0x6FA2, + 0x9DC8, 0x6FA3, 0x9DC9, 0x6FA4, 0x9DCA, 0x6FA5, 0x9DCB, 0x6FA6, 0x9DCC, 0x6FA8, 0x9DCD, 0x6FA9, 0x9DCE, 0x6FAA, 0x9DCF, 0x6FAB, + 0x9DD0, 0x6FAC, 0x9DD1, 0x6FAD, 0x9DD2, 0x6FAE, 0x9DD3, 0x6FAF, 0x9DD4, 0x6FB0, 0x9DD5, 0x6FB1, 0x9DD6, 0x6FB2, 0x9DD7, 0x6FB4, + 0x9DD8, 0x6FB5, 0x9DD9, 0x6FB7, 0x9DDA, 0x6FB8, 0x9DDB, 0x6FBA, 0x9DDC, 0x6FBB, 0x9DDD, 0x6FBC, 0x9DDE, 0x6FBD, 0x9DDF, 0x6FBE, + 0x9DE0, 0x6FBF, 0x9DE1, 0x6FC1, 0x9DE2, 0x6FC3, 0x9DE3, 0x6FC4, 0x9DE4, 0x6FC5, 0x9DE5, 0x6FC6, 0x9DE6, 0x6FC7, 0x9DE7, 0x6FC8, + 0x9DE8, 0x6FCA, 0x9DE9, 0x6FCB, 0x9DEA, 0x6FCC, 0x9DEB, 0x6FCD, 0x9DEC, 0x6FCE, 0x9DED, 0x6FCF, 0x9DEE, 0x6FD0, 0x9DEF, 0x6FD3, + 0x9DF0, 0x6FD4, 0x9DF1, 0x6FD5, 0x9DF2, 0x6FD6, 0x9DF3, 0x6FD7, 0x9DF4, 0x6FD8, 0x9DF5, 0x6FD9, 0x9DF6, 0x6FDA, 0x9DF7, 0x6FDB, + 0x9DF8, 0x6FDC, 0x9DF9, 0x6FDD, 0x9DFA, 0x6FDF, 0x9DFB, 0x6FE2, 0x9DFC, 0x6FE3, 0x9DFD, 0x6FE4, 0x9DFE, 0x6FE5, 0x9E40, 0x6FE6, + 0x9E41, 0x6FE7, 0x9E42, 0x6FE8, 0x9E43, 0x6FE9, 0x9E44, 0x6FEA, 0x9E45, 0x6FEB, 0x9E46, 0x6FEC, 0x9E47, 0x6FED, 0x9E48, 0x6FF0, + 0x9E49, 0x6FF1, 0x9E4A, 0x6FF2, 0x9E4B, 0x6FF3, 0x9E4C, 0x6FF4, 0x9E4D, 0x6FF5, 0x9E4E, 0x6FF6, 0x9E4F, 0x6FF7, 0x9E50, 0x6FF8, + 0x9E51, 0x6FF9, 0x9E52, 0x6FFA, 0x9E53, 0x6FFB, 0x9E54, 0x6FFC, 0x9E55, 0x6FFD, 0x9E56, 0x6FFE, 0x9E57, 0x6FFF, 0x9E58, 0x7000, + 0x9E59, 0x7001, 0x9E5A, 0x7002, 0x9E5B, 0x7003, 0x9E5C, 0x7004, 0x9E5D, 0x7005, 0x9E5E, 0x7006, 0x9E5F, 0x7007, 0x9E60, 0x7008, + 0x9E61, 0x7009, 0x9E62, 0x700A, 0x9E63, 0x700B, 0x9E64, 0x700C, 0x9E65, 0x700D, 0x9E66, 0x700E, 0x9E67, 0x700F, 0x9E68, 0x7010, + 0x9E69, 0x7012, 0x9E6A, 0x7013, 0x9E6B, 0x7014, 0x9E6C, 0x7015, 0x9E6D, 0x7016, 0x9E6E, 0x7017, 0x9E6F, 0x7018, 0x9E70, 0x7019, + 0x9E71, 0x701C, 0x9E72, 0x701D, 0x9E73, 0x701E, 0x9E74, 0x701F, 0x9E75, 0x7020, 0x9E76, 0x7021, 0x9E77, 0x7022, 0x9E78, 0x7024, + 0x9E79, 0x7025, 0x9E7A, 0x7026, 0x9E7B, 0x7027, 0x9E7C, 0x7028, 0x9E7D, 0x7029, 0x9E7E, 0x702A, 0x9E80, 0x702B, 0x9E81, 0x702C, + 0x9E82, 0x702D, 0x9E83, 0x702E, 0x9E84, 0x702F, 0x9E85, 0x7030, 0x9E86, 0x7031, 0x9E87, 0x7032, 0x9E88, 0x7033, 0x9E89, 0x7034, + 0x9E8A, 0x7036, 0x9E8B, 0x7037, 0x9E8C, 0x7038, 0x9E8D, 0x703A, 0x9E8E, 0x703B, 0x9E8F, 0x703C, 0x9E90, 0x703D, 0x9E91, 0x703E, + 0x9E92, 0x703F, 0x9E93, 0x7040, 0x9E94, 0x7041, 0x9E95, 0x7042, 0x9E96, 0x7043, 0x9E97, 0x7044, 0x9E98, 0x7045, 0x9E99, 0x7046, + 0x9E9A, 0x7047, 0x9E9B, 0x7048, 0x9E9C, 0x7049, 0x9E9D, 0x704A, 0x9E9E, 0x704B, 0x9E9F, 0x704D, 0x9EA0, 0x704E, 0x9EA1, 0x7050, + 0x9EA2, 0x7051, 0x9EA3, 0x7052, 0x9EA4, 0x7053, 0x9EA5, 0x7054, 0x9EA6, 0x7055, 0x9EA7, 0x7056, 0x9EA8, 0x7057, 0x9EA9, 0x7058, + 0x9EAA, 0x7059, 0x9EAB, 0x705A, 0x9EAC, 0x705B, 0x9EAD, 0x705C, 0x9EAE, 0x705D, 0x9EAF, 0x705F, 0x9EB0, 0x7060, 0x9EB1, 0x7061, + 0x9EB2, 0x7062, 0x9EB3, 0x7063, 0x9EB4, 0x7064, 0x9EB5, 0x7065, 0x9EB6, 0x7066, 0x9EB7, 0x7067, 0x9EB8, 0x7068, 0x9EB9, 0x7069, + 0x9EBA, 0x706A, 0x9EBB, 0x706E, 0x9EBC, 0x7071, 0x9EBD, 0x7072, 0x9EBE, 0x7073, 0x9EBF, 0x7074, 0x9EC0, 0x7077, 0x9EC1, 0x7079, + 0x9EC2, 0x707A, 0x9EC3, 0x707B, 0x9EC4, 0x707D, 0x9EC5, 0x7081, 0x9EC6, 0x7082, 0x9EC7, 0x7083, 0x9EC8, 0x7084, 0x9EC9, 0x7086, + 0x9ECA, 0x7087, 0x9ECB, 0x7088, 0x9ECC, 0x708B, 0x9ECD, 0x708C, 0x9ECE, 0x708D, 0x9ECF, 0x708F, 0x9ED0, 0x7090, 0x9ED1, 0x7091, + 0x9ED2, 0x7093, 0x9ED3, 0x7097, 0x9ED4, 0x7098, 0x9ED5, 0x709A, 0x9ED6, 0x709B, 0x9ED7, 0x709E, 0x9ED8, 0x709F, 0x9ED9, 0x70A0, + 0x9EDA, 0x70A1, 0x9EDB, 0x70A2, 0x9EDC, 0x70A3, 0x9EDD, 0x70A4, 0x9EDE, 0x70A5, 0x9EDF, 0x70A6, 0x9EE0, 0x70A7, 0x9EE1, 0x70A8, + 0x9EE2, 0x70A9, 0x9EE3, 0x70AA, 0x9EE4, 0x70B0, 0x9EE5, 0x70B2, 0x9EE6, 0x70B4, 0x9EE7, 0x70B5, 0x9EE8, 0x70B6, 0x9EE9, 0x70BA, + 0x9EEA, 0x70BE, 0x9EEB, 0x70BF, 0x9EEC, 0x70C4, 0x9EED, 0x70C5, 0x9EEE, 0x70C6, 0x9EEF, 0x70C7, 0x9EF0, 0x70C9, 0x9EF1, 0x70CB, + 0x9EF2, 0x70CC, 0x9EF3, 0x70CD, 0x9EF4, 0x70CE, 0x9EF5, 0x70CF, 0x9EF6, 0x70D0, 0x9EF7, 0x70D1, 0x9EF8, 0x70D2, 0x9EF9, 0x70D3, + 0x9EFA, 0x70D4, 0x9EFB, 0x70D5, 0x9EFC, 0x70D6, 0x9EFD, 0x70D7, 0x9EFE, 0x70DA, 0x9F40, 0x70DC, 0x9F41, 0x70DD, 0x9F42, 0x70DE, + 0x9F43, 0x70E0, 0x9F44, 0x70E1, 0x9F45, 0x70E2, 0x9F46, 0x70E3, 0x9F47, 0x70E5, 0x9F48, 0x70EA, 0x9F49, 0x70EE, 0x9F4A, 0x70F0, + 0x9F4B, 0x70F1, 0x9F4C, 0x70F2, 0x9F4D, 0x70F3, 0x9F4E, 0x70F4, 0x9F4F, 0x70F5, 0x9F50, 0x70F6, 0x9F51, 0x70F8, 0x9F52, 0x70FA, + 0x9F53, 0x70FB, 0x9F54, 0x70FC, 0x9F55, 0x70FE, 0x9F56, 0x70FF, 0x9F57, 0x7100, 0x9F58, 0x7101, 0x9F59, 0x7102, 0x9F5A, 0x7103, + 0x9F5B, 0x7104, 0x9F5C, 0x7105, 0x9F5D, 0x7106, 0x9F5E, 0x7107, 0x9F5F, 0x7108, 0x9F60, 0x710B, 0x9F61, 0x710C, 0x9F62, 0x710D, + 0x9F63, 0x710E, 0x9F64, 0x710F, 0x9F65, 0x7111, 0x9F66, 0x7112, 0x9F67, 0x7114, 0x9F68, 0x7117, 0x9F69, 0x711B, 0x9F6A, 0x711C, + 0x9F6B, 0x711D, 0x9F6C, 0x711E, 0x9F6D, 0x711F, 0x9F6E, 0x7120, 0x9F6F, 0x7121, 0x9F70, 0x7122, 0x9F71, 0x7123, 0x9F72, 0x7124, + 0x9F73, 0x7125, 0x9F74, 0x7127, 0x9F75, 0x7128, 0x9F76, 0x7129, 0x9F77, 0x712A, 0x9F78, 0x712B, 0x9F79, 0x712C, 0x9F7A, 0x712D, + 0x9F7B, 0x712E, 0x9F7C, 0x7132, 0x9F7D, 0x7133, 0x9F7E, 0x7134, 0x9F80, 0x7135, 0x9F81, 0x7137, 0x9F82, 0x7138, 0x9F83, 0x7139, + 0x9F84, 0x713A, 0x9F85, 0x713B, 0x9F86, 0x713C, 0x9F87, 0x713D, 0x9F88, 0x713E, 0x9F89, 0x713F, 0x9F8A, 0x7140, 0x9F8B, 0x7141, + 0x9F8C, 0x7142, 0x9F8D, 0x7143, 0x9F8E, 0x7144, 0x9F8F, 0x7146, 0x9F90, 0x7147, 0x9F91, 0x7148, 0x9F92, 0x7149, 0x9F93, 0x714B, + 0x9F94, 0x714D, 0x9F95, 0x714F, 0x9F96, 0x7150, 0x9F97, 0x7151, 0x9F98, 0x7152, 0x9F99, 0x7153, 0x9F9A, 0x7154, 0x9F9B, 0x7155, + 0x9F9C, 0x7156, 0x9F9D, 0x7157, 0x9F9E, 0x7158, 0x9F9F, 0x7159, 0x9FA0, 0x715A, 0x9FA1, 0x715B, 0x9FA2, 0x715D, 0x9FA3, 0x715F, + 0x9FA4, 0x7160, 0x9FA5, 0x7161, 0x9FA6, 0x7162, 0x9FA7, 0x7163, 0x9FA8, 0x7165, 0x9FA9, 0x7169, 0x9FAA, 0x716A, 0x9FAB, 0x716B, + 0x9FAC, 0x716C, 0x9FAD, 0x716D, 0x9FAE, 0x716F, 0x9FAF, 0x7170, 0x9FB0, 0x7171, 0x9FB1, 0x7174, 0x9FB2, 0x7175, 0x9FB3, 0x7176, + 0x9FB4, 0x7177, 0x9FB5, 0x7179, 0x9FB6, 0x717B, 0x9FB7, 0x717C, 0x9FB8, 0x717E, 0x9FB9, 0x717F, 0x9FBA, 0x7180, 0x9FBB, 0x7181, + 0x9FBC, 0x7182, 0x9FBD, 0x7183, 0x9FBE, 0x7185, 0x9FBF, 0x7186, 0x9FC0, 0x7187, 0x9FC1, 0x7188, 0x9FC2, 0x7189, 0x9FC3, 0x718B, + 0x9FC4, 0x718C, 0x9FC5, 0x718D, 0x9FC6, 0x718E, 0x9FC7, 0x7190, 0x9FC8, 0x7191, 0x9FC9, 0x7192, 0x9FCA, 0x7193, 0x9FCB, 0x7195, + 0x9FCC, 0x7196, 0x9FCD, 0x7197, 0x9FCE, 0x719A, 0x9FCF, 0x719B, 0x9FD0, 0x719C, 0x9FD1, 0x719D, 0x9FD2, 0x719E, 0x9FD3, 0x71A1, + 0x9FD4, 0x71A2, 0x9FD5, 0x71A3, 0x9FD6, 0x71A4, 0x9FD7, 0x71A5, 0x9FD8, 0x71A6, 0x9FD9, 0x71A7, 0x9FDA, 0x71A9, 0x9FDB, 0x71AA, + 0x9FDC, 0x71AB, 0x9FDD, 0x71AD, 0x9FDE, 0x71AE, 0x9FDF, 0x71AF, 0x9FE0, 0x71B0, 0x9FE1, 0x71B1, 0x9FE2, 0x71B2, 0x9FE3, 0x71B4, + 0x9FE4, 0x71B6, 0x9FE5, 0x71B7, 0x9FE6, 0x71B8, 0x9FE7, 0x71BA, 0x9FE8, 0x71BB, 0x9FE9, 0x71BC, 0x9FEA, 0x71BD, 0x9FEB, 0x71BE, + 0x9FEC, 0x71BF, 0x9FED, 0x71C0, 0x9FEE, 0x71C1, 0x9FEF, 0x71C2, 0x9FF0, 0x71C4, 0x9FF1, 0x71C5, 0x9FF2, 0x71C6, 0x9FF3, 0x71C7, + 0x9FF4, 0x71C8, 0x9FF5, 0x71C9, 0x9FF6, 0x71CA, 0x9FF7, 0x71CB, 0x9FF8, 0x71CC, 0x9FF9, 0x71CD, 0x9FFA, 0x71CF, 0x9FFB, 0x71D0, + 0x9FFC, 0x71D1, 0x9FFD, 0x71D2, 0x9FFE, 0x71D3, 0xA040, 0x71D6, 0xA041, 0x71D7, 0xA042, 0x71D8, 0xA043, 0x71D9, 0xA044, 0x71DA, + 0xA045, 0x71DB, 0xA046, 0x71DC, 0xA047, 0x71DD, 0xA048, 0x71DE, 0xA049, 0x71DF, 0xA04A, 0x71E1, 0xA04B, 0x71E2, 0xA04C, 0x71E3, + 0xA04D, 0x71E4, 0xA04E, 0x71E6, 0xA04F, 0x71E8, 0xA050, 0x71E9, 0xA051, 0x71EA, 0xA052, 0x71EB, 0xA053, 0x71EC, 0xA054, 0x71ED, + 0xA055, 0x71EF, 0xA056, 0x71F0, 0xA057, 0x71F1, 0xA058, 0x71F2, 0xA059, 0x71F3, 0xA05A, 0x71F4, 0xA05B, 0x71F5, 0xA05C, 0x71F6, + 0xA05D, 0x71F7, 0xA05E, 0x71F8, 0xA05F, 0x71FA, 0xA060, 0x71FB, 0xA061, 0x71FC, 0xA062, 0x71FD, 0xA063, 0x71FE, 0xA064, 0x71FF, + 0xA065, 0x7200, 0xA066, 0x7201, 0xA067, 0x7202, 0xA068, 0x7203, 0xA069, 0x7204, 0xA06A, 0x7205, 0xA06B, 0x7207, 0xA06C, 0x7208, + 0xA06D, 0x7209, 0xA06E, 0x720A, 0xA06F, 0x720B, 0xA070, 0x720C, 0xA071, 0x720D, 0xA072, 0x720E, 0xA073, 0x720F, 0xA074, 0x7210, + 0xA075, 0x7211, 0xA076, 0x7212, 0xA077, 0x7213, 0xA078, 0x7214, 0xA079, 0x7215, 0xA07A, 0x7216, 0xA07B, 0x7217, 0xA07C, 0x7218, + 0xA07D, 0x7219, 0xA07E, 0x721A, 0xA080, 0x721B, 0xA081, 0x721C, 0xA082, 0x721E, 0xA083, 0x721F, 0xA084, 0x7220, 0xA085, 0x7221, + 0xA086, 0x7222, 0xA087, 0x7223, 0xA088, 0x7224, 0xA089, 0x7225, 0xA08A, 0x7226, 0xA08B, 0x7227, 0xA08C, 0x7229, 0xA08D, 0x722B, + 0xA08E, 0x722D, 0xA08F, 0x722E, 0xA090, 0x722F, 0xA091, 0x7232, 0xA092, 0x7233, 0xA093, 0x7234, 0xA094, 0x723A, 0xA095, 0x723C, + 0xA096, 0x723E, 0xA097, 0x7240, 0xA098, 0x7241, 0xA099, 0x7242, 0xA09A, 0x7243, 0xA09B, 0x7244, 0xA09C, 0x7245, 0xA09D, 0x7246, + 0xA09E, 0x7249, 0xA09F, 0x724A, 0xA0A0, 0x724B, 0xA0A1, 0x724E, 0xA0A2, 0x724F, 0xA0A3, 0x7250, 0xA0A4, 0x7251, 0xA0A5, 0x7253, + 0xA0A6, 0x7254, 0xA0A7, 0x7255, 0xA0A8, 0x7257, 0xA0A9, 0x7258, 0xA0AA, 0x725A, 0xA0AB, 0x725C, 0xA0AC, 0x725E, 0xA0AD, 0x7260, + 0xA0AE, 0x7263, 0xA0AF, 0x7264, 0xA0B0, 0x7265, 0xA0B1, 0x7268, 0xA0B2, 0x726A, 0xA0B3, 0x726B, 0xA0B4, 0x726C, 0xA0B5, 0x726D, + 0xA0B6, 0x7270, 0xA0B7, 0x7271, 0xA0B8, 0x7273, 0xA0B9, 0x7274, 0xA0BA, 0x7276, 0xA0BB, 0x7277, 0xA0BC, 0x7278, 0xA0BD, 0x727B, + 0xA0BE, 0x727C, 0xA0BF, 0x727D, 0xA0C0, 0x7282, 0xA0C1, 0x7283, 0xA0C2, 0x7285, 0xA0C3, 0x7286, 0xA0C4, 0x7287, 0xA0C5, 0x7288, + 0xA0C6, 0x7289, 0xA0C7, 0x728C, 0xA0C8, 0x728E, 0xA0C9, 0x7290, 0xA0CA, 0x7291, 0xA0CB, 0x7293, 0xA0CC, 0x7294, 0xA0CD, 0x7295, + 0xA0CE, 0x7296, 0xA0CF, 0x7297, 0xA0D0, 0x7298, 0xA0D1, 0x7299, 0xA0D2, 0x729A, 0xA0D3, 0x729B, 0xA0D4, 0x729C, 0xA0D5, 0x729D, + 0xA0D6, 0x729E, 0xA0D7, 0x72A0, 0xA0D8, 0x72A1, 0xA0D9, 0x72A2, 0xA0DA, 0x72A3, 0xA0DB, 0x72A4, 0xA0DC, 0x72A5, 0xA0DD, 0x72A6, + 0xA0DE, 0x72A7, 0xA0DF, 0x72A8, 0xA0E0, 0x72A9, 0xA0E1, 0x72AA, 0xA0E2, 0x72AB, 0xA0E3, 0x72AE, 0xA0E4, 0x72B1, 0xA0E5, 0x72B2, + 0xA0E6, 0x72B3, 0xA0E7, 0x72B5, 0xA0E8, 0x72BA, 0xA0E9, 0x72BB, 0xA0EA, 0x72BC, 0xA0EB, 0x72BD, 0xA0EC, 0x72BE, 0xA0ED, 0x72BF, + 0xA0EE, 0x72C0, 0xA0EF, 0x72C5, 0xA0F0, 0x72C6, 0xA0F1, 0x72C7, 0xA0F2, 0x72C9, 0xA0F3, 0x72CA, 0xA0F4, 0x72CB, 0xA0F5, 0x72CC, + 0xA0F6, 0x72CF, 0xA0F7, 0x72D1, 0xA0F8, 0x72D3, 0xA0F9, 0x72D4, 0xA0FA, 0x72D5, 0xA0FB, 0x72D6, 0xA0FC, 0x72D8, 0xA0FD, 0x72DA, + 0xA0FE, 0x72DB, 0xA1A1, 0x3000, 0xA1A2, 0x3001, 0xA1A3, 0x3002, 0xA1A4, 0x00B7, 0xA1A5, 0x02C9, 0xA1A6, 0x02C7, 0xA1A7, 0x00A8, + 0xA1A8, 0x3003, 0xA1A9, 0x3005, 0xA1AA, 0x2014, 0xA1AB, 0xFF5E, 0xA1AC, 0x2016, 0xA1AD, 0x2026, 0xA1AE, 0x2018, 0xA1AF, 0x2019, + 0xA1B0, 0x201C, 0xA1B1, 0x201D, 0xA1B2, 0x3014, 0xA1B3, 0x3015, 0xA1B4, 0x3008, 0xA1B5, 0x3009, 0xA1B6, 0x300A, 0xA1B7, 0x300B, + 0xA1B8, 0x300C, 0xA1B9, 0x300D, 0xA1BA, 0x300E, 0xA1BB, 0x300F, 0xA1BC, 0x3016, 0xA1BD, 0x3017, 0xA1BE, 0x3010, 0xA1BF, 0x3011, + 0xA1C0, 0x00B1, 0xA1C1, 0x00D7, 0xA1C2, 0x00F7, 0xA1C3, 0x2236, 0xA1C4, 0x2227, 0xA1C5, 0x2228, 0xA1C6, 0x2211, 0xA1C7, 0x220F, + 0xA1C8, 0x222A, 0xA1C9, 0x2229, 0xA1CA, 0x2208, 0xA1CB, 0x2237, 0xA1CC, 0x221A, 0xA1CD, 0x22A5, 0xA1CE, 0x2225, 0xA1CF, 0x2220, + 0xA1D0, 0x2312, 0xA1D1, 0x2299, 0xA1D2, 0x222B, 0xA1D3, 0x222E, 0xA1D4, 0x2261, 0xA1D5, 0x224C, 0xA1D6, 0x2248, 0xA1D7, 0x223D, + 0xA1D8, 0x221D, 0xA1D9, 0x2260, 0xA1DA, 0x226E, 0xA1DB, 0x226F, 0xA1DC, 0x2264, 0xA1DD, 0x2265, 0xA1DE, 0x221E, 0xA1DF, 0x2235, + 0xA1E0, 0x2234, 0xA1E1, 0x2642, 0xA1E2, 0x2640, 0xA1E3, 0x00B0, 0xA1E4, 0x2032, 0xA1E5, 0x2033, 0xA1E6, 0x2103, 0xA1E7, 0xFF04, + 0xA1E8, 0x00A4, 0xA1E9, 0xFFE0, 0xA1EA, 0xFFE1, 0xA1EB, 0x2030, 0xA1EC, 0x00A7, 0xA1ED, 0x2116, 0xA1EE, 0x2606, 0xA1EF, 0x2605, + 0xA1F0, 0x25CB, 0xA1F1, 0x25CF, 0xA1F2, 0x25CE, 0xA1F3, 0x25C7, 0xA1F4, 0x25C6, 0xA1F5, 0x25A1, 0xA1F6, 0x25A0, 0xA1F7, 0x25B3, + 0xA1F8, 0x25B2, 0xA1F9, 0x203B, 0xA1FA, 0x2192, 0xA1FB, 0x2190, 0xA1FC, 0x2191, 0xA1FD, 0x2193, 0xA1FE, 0x3013, 0xA2A1, 0x2170, + 0xA2A2, 0x2171, 0xA2A3, 0x2172, 0xA2A4, 0x2173, 0xA2A5, 0x2174, 0xA2A6, 0x2175, 0xA2A7, 0x2176, 0xA2A8, 0x2177, 0xA2A9, 0x2178, + 0xA2AA, 0x2179, 0xA2B1, 0x2488, 0xA2B2, 0x2489, 0xA2B3, 0x248A, 0xA2B4, 0x248B, 0xA2B5, 0x248C, 0xA2B6, 0x248D, 0xA2B7, 0x248E, + 0xA2B8, 0x248F, 0xA2B9, 0x2490, 0xA2BA, 0x2491, 0xA2BB, 0x2492, 0xA2BC, 0x2493, 0xA2BD, 0x2494, 0xA2BE, 0x2495, 0xA2BF, 0x2496, + 0xA2C0, 0x2497, 0xA2C1, 0x2498, 0xA2C2, 0x2499, 0xA2C3, 0x249A, 0xA2C4, 0x249B, 0xA2C5, 0x2474, 0xA2C6, 0x2475, 0xA2C7, 0x2476, + 0xA2C8, 0x2477, 0xA2C9, 0x2478, 0xA2CA, 0x2479, 0xA2CB, 0x247A, 0xA2CC, 0x247B, 0xA2CD, 0x247C, 0xA2CE, 0x247D, 0xA2CF, 0x247E, + 0xA2D0, 0x247F, 0xA2D1, 0x2480, 0xA2D2, 0x2481, 0xA2D3, 0x2482, 0xA2D4, 0x2483, 0xA2D5, 0x2484, 0xA2D6, 0x2485, 0xA2D7, 0x2486, + 0xA2D8, 0x2487, 0xA2D9, 0x2460, 0xA2DA, 0x2461, 0xA2DB, 0x2462, 0xA2DC, 0x2463, 0xA2DD, 0x2464, 0xA2DE, 0x2465, 0xA2DF, 0x2466, + 0xA2E0, 0x2467, 0xA2E1, 0x2468, 0xA2E2, 0x2469, 0xA2E5, 0x3220, 0xA2E6, 0x3221, 0xA2E7, 0x3222, 0xA2E8, 0x3223, 0xA2E9, 0x3224, + 0xA2EA, 0x3225, 0xA2EB, 0x3226, 0xA2EC, 0x3227, 0xA2ED, 0x3228, 0xA2EE, 0x3229, 0xA2F1, 0x2160, 0xA2F2, 0x2161, 0xA2F3, 0x2162, + 0xA2F4, 0x2163, 0xA2F5, 0x2164, 0xA2F6, 0x2165, 0xA2F7, 0x2166, 0xA2F8, 0x2167, 0xA2F9, 0x2168, 0xA2FA, 0x2169, 0xA2FB, 0x216A, + 0xA2FC, 0x216B, 0xA3A1, 0xFF01, 0xA3A2, 0xFF02, 0xA3A3, 0xFF03, 0xA3A4, 0xFFE5, 0xA3A5, 0xFF05, 0xA3A6, 0xFF06, 0xA3A7, 0xFF07, + 0xA3A8, 0xFF08, 0xA3A9, 0xFF09, 0xA3AA, 0xFF0A, 0xA3AB, 0xFF0B, 0xA3AC, 0xFF0C, 0xA3AD, 0xFF0D, 0xA3AE, 0xFF0E, 0xA3AF, 0xFF0F, + 0xA3B0, 0xFF10, 0xA3B1, 0xFF11, 0xA3B2, 0xFF12, 0xA3B3, 0xFF13, 0xA3B4, 0xFF14, 0xA3B5, 0xFF15, 0xA3B6, 0xFF16, 0xA3B7, 0xFF17, + 0xA3B8, 0xFF18, 0xA3B9, 0xFF19, 0xA3BA, 0xFF1A, 0xA3BB, 0xFF1B, 0xA3BC, 0xFF1C, 0xA3BD, 0xFF1D, 0xA3BE, 0xFF1E, 0xA3BF, 0xFF1F, + 0xA3C0, 0xFF20, 0xA3C1, 0xFF21, 0xA3C2, 0xFF22, 0xA3C3, 0xFF23, 0xA3C4, 0xFF24, 0xA3C5, 0xFF25, 0xA3C6, 0xFF26, 0xA3C7, 0xFF27, + 0xA3C8, 0xFF28, 0xA3C9, 0xFF29, 0xA3CA, 0xFF2A, 0xA3CB, 0xFF2B, 0xA3CC, 0xFF2C, 0xA3CD, 0xFF2D, 0xA3CE, 0xFF2E, 0xA3CF, 0xFF2F, + 0xA3D0, 0xFF30, 0xA3D1, 0xFF31, 0xA3D2, 0xFF32, 0xA3D3, 0xFF33, 0xA3D4, 0xFF34, 0xA3D5, 0xFF35, 0xA3D6, 0xFF36, 0xA3D7, 0xFF37, + 0xA3D8, 0xFF38, 0xA3D9, 0xFF39, 0xA3DA, 0xFF3A, 0xA3DB, 0xFF3B, 0xA3DC, 0xFF3C, 0xA3DD, 0xFF3D, 0xA3DE, 0xFF3E, 0xA3DF, 0xFF3F, + 0xA3E0, 0xFF40, 0xA3E1, 0xFF41, 0xA3E2, 0xFF42, 0xA3E3, 0xFF43, 0xA3E4, 0xFF44, 0xA3E5, 0xFF45, 0xA3E6, 0xFF46, 0xA3E7, 0xFF47, + 0xA3E8, 0xFF48, 0xA3E9, 0xFF49, 0xA3EA, 0xFF4A, 0xA3EB, 0xFF4B, 0xA3EC, 0xFF4C, 0xA3ED, 0xFF4D, 0xA3EE, 0xFF4E, 0xA3EF, 0xFF4F, + 0xA3F0, 0xFF50, 0xA3F1, 0xFF51, 0xA3F2, 0xFF52, 0xA3F3, 0xFF53, 0xA3F4, 0xFF54, 0xA3F5, 0xFF55, 0xA3F6, 0xFF56, 0xA3F7, 0xFF57, + 0xA3F8, 0xFF58, 0xA3F9, 0xFF59, 0xA3FA, 0xFF5A, 0xA3FB, 0xFF5B, 0xA3FC, 0xFF5C, 0xA3FD, 0xFF5D, 0xA3FE, 0xFFE3, 0xA4A1, 0x3041, + 0xA4A2, 0x3042, 0xA4A3, 0x3043, 0xA4A4, 0x3044, 0xA4A5, 0x3045, 0xA4A6, 0x3046, 0xA4A7, 0x3047, 0xA4A8, 0x3048, 0xA4A9, 0x3049, + 0xA4AA, 0x304A, 0xA4AB, 0x304B, 0xA4AC, 0x304C, 0xA4AD, 0x304D, 0xA4AE, 0x304E, 0xA4AF, 0x304F, 0xA4B0, 0x3050, 0xA4B1, 0x3051, + 0xA4B2, 0x3052, 0xA4B3, 0x3053, 0xA4B4, 0x3054, 0xA4B5, 0x3055, 0xA4B6, 0x3056, 0xA4B7, 0x3057, 0xA4B8, 0x3058, 0xA4B9, 0x3059, + 0xA4BA, 0x305A, 0xA4BB, 0x305B, 0xA4BC, 0x305C, 0xA4BD, 0x305D, 0xA4BE, 0x305E, 0xA4BF, 0x305F, 0xA4C0, 0x3060, 0xA4C1, 0x3061, + 0xA4C2, 0x3062, 0xA4C3, 0x3063, 0xA4C4, 0x3064, 0xA4C5, 0x3065, 0xA4C6, 0x3066, 0xA4C7, 0x3067, 0xA4C8, 0x3068, 0xA4C9, 0x3069, + 0xA4CA, 0x306A, 0xA4CB, 0x306B, 0xA4CC, 0x306C, 0xA4CD, 0x306D, 0xA4CE, 0x306E, 0xA4CF, 0x306F, 0xA4D0, 0x3070, 0xA4D1, 0x3071, + 0xA4D2, 0x3072, 0xA4D3, 0x3073, 0xA4D4, 0x3074, 0xA4D5, 0x3075, 0xA4D6, 0x3076, 0xA4D7, 0x3077, 0xA4D8, 0x3078, 0xA4D9, 0x3079, + 0xA4DA, 0x307A, 0xA4DB, 0x307B, 0xA4DC, 0x307C, 0xA4DD, 0x307D, 0xA4DE, 0x307E, 0xA4DF, 0x307F, 0xA4E0, 0x3080, 0xA4E1, 0x3081, + 0xA4E2, 0x3082, 0xA4E3, 0x3083, 0xA4E4, 0x3084, 0xA4E5, 0x3085, 0xA4E6, 0x3086, 0xA4E7, 0x3087, 0xA4E8, 0x3088, 0xA4E9, 0x3089, + 0xA4EA, 0x308A, 0xA4EB, 0x308B, 0xA4EC, 0x308C, 0xA4ED, 0x308D, 0xA4EE, 0x308E, 0xA4EF, 0x308F, 0xA4F0, 0x3090, 0xA4F1, 0x3091, + 0xA4F2, 0x3092, 0xA4F3, 0x3093, 0xA5A1, 0x30A1, 0xA5A2, 0x30A2, 0xA5A3, 0x30A3, 0xA5A4, 0x30A4, 0xA5A5, 0x30A5, 0xA5A6, 0x30A6, + 0xA5A7, 0x30A7, 0xA5A8, 0x30A8, 0xA5A9, 0x30A9, 0xA5AA, 0x30AA, 0xA5AB, 0x30AB, 0xA5AC, 0x30AC, 0xA5AD, 0x30AD, 0xA5AE, 0x30AE, + 0xA5AF, 0x30AF, 0xA5B0, 0x30B0, 0xA5B1, 0x30B1, 0xA5B2, 0x30B2, 0xA5B3, 0x30B3, 0xA5B4, 0x30B4, 0xA5B5, 0x30B5, 0xA5B6, 0x30B6, + 0xA5B7, 0x30B7, 0xA5B8, 0x30B8, 0xA5B9, 0x30B9, 0xA5BA, 0x30BA, 0xA5BB, 0x30BB, 0xA5BC, 0x30BC, 0xA5BD, 0x30BD, 0xA5BE, 0x30BE, + 0xA5BF, 0x30BF, 0xA5C0, 0x30C0, 0xA5C1, 0x30C1, 0xA5C2, 0x30C2, 0xA5C3, 0x30C3, 0xA5C4, 0x30C4, 0xA5C5, 0x30C5, 0xA5C6, 0x30C6, + 0xA5C7, 0x30C7, 0xA5C8, 0x30C8, 0xA5C9, 0x30C9, 0xA5CA, 0x30CA, 0xA5CB, 0x30CB, 0xA5CC, 0x30CC, 0xA5CD, 0x30CD, 0xA5CE, 0x30CE, + 0xA5CF, 0x30CF, 0xA5D0, 0x30D0, 0xA5D1, 0x30D1, 0xA5D2, 0x30D2, 0xA5D3, 0x30D3, 0xA5D4, 0x30D4, 0xA5D5, 0x30D5, 0xA5D6, 0x30D6, + 0xA5D7, 0x30D7, 0xA5D8, 0x30D8, 0xA5D9, 0x30D9, 0xA5DA, 0x30DA, 0xA5DB, 0x30DB, 0xA5DC, 0x30DC, 0xA5DD, 0x30DD, 0xA5DE, 0x30DE, + 0xA5DF, 0x30DF, 0xA5E0, 0x30E0, 0xA5E1, 0x30E1, 0xA5E2, 0x30E2, 0xA5E3, 0x30E3, 0xA5E4, 0x30E4, 0xA5E5, 0x30E5, 0xA5E6, 0x30E6, + 0xA5E7, 0x30E7, 0xA5E8, 0x30E8, 0xA5E9, 0x30E9, 0xA5EA, 0x30EA, 0xA5EB, 0x30EB, 0xA5EC, 0x30EC, 0xA5ED, 0x30ED, 0xA5EE, 0x30EE, + 0xA5EF, 0x30EF, 0xA5F0, 0x30F0, 0xA5F1, 0x30F1, 0xA5F2, 0x30F2, 0xA5F3, 0x30F3, 0xA5F4, 0x30F4, 0xA5F5, 0x30F5, 0xA5F6, 0x30F6, + 0xA6A1, 0x0391, 0xA6A2, 0x0392, 0xA6A3, 0x0393, 0xA6A4, 0x0394, 0xA6A5, 0x0395, 0xA6A6, 0x0396, 0xA6A7, 0x0397, 0xA6A8, 0x0398, + 0xA6A9, 0x0399, 0xA6AA, 0x039A, 0xA6AB, 0x039B, 0xA6AC, 0x039C, 0xA6AD, 0x039D, 0xA6AE, 0x039E, 0xA6AF, 0x039F, 0xA6B0, 0x03A0, + 0xA6B1, 0x03A1, 0xA6B2, 0x03A3, 0xA6B3, 0x03A4, 0xA6B4, 0x03A5, 0xA6B5, 0x03A6, 0xA6B6, 0x03A7, 0xA6B7, 0x03A8, 0xA6B8, 0x03A9, + 0xA6C1, 0x03B1, 0xA6C2, 0x03B2, 0xA6C3, 0x03B3, 0xA6C4, 0x03B4, 0xA6C5, 0x03B5, 0xA6C6, 0x03B6, 0xA6C7, 0x03B7, 0xA6C8, 0x03B8, + 0xA6C9, 0x03B9, 0xA6CA, 0x03BA, 0xA6CB, 0x03BB, 0xA6CC, 0x03BC, 0xA6CD, 0x03BD, 0xA6CE, 0x03BE, 0xA6CF, 0x03BF, 0xA6D0, 0x03C0, + 0xA6D1, 0x03C1, 0xA6D2, 0x03C3, 0xA6D3, 0x03C4, 0xA6D4, 0x03C5, 0xA6D5, 0x03C6, 0xA6D6, 0x03C7, 0xA6D7, 0x03C8, 0xA6D8, 0x03C9, + 0xA6E0, 0xFE35, 0xA6E1, 0xFE36, 0xA6E2, 0xFE39, 0xA6E3, 0xFE3A, 0xA6E4, 0xFE3F, 0xA6E5, 0xFE40, 0xA6E6, 0xFE3D, 0xA6E7, 0xFE3E, + 0xA6E8, 0xFE41, 0xA6E9, 0xFE42, 0xA6EA, 0xFE43, 0xA6EB, 0xFE44, 0xA6EE, 0xFE3B, 0xA6EF, 0xFE3C, 0xA6F0, 0xFE37, 0xA6F1, 0xFE38, + 0xA6F2, 0xFE31, 0xA6F4, 0xFE33, 0xA6F5, 0xFE34, 0xA7A1, 0x0410, 0xA7A2, 0x0411, 0xA7A3, 0x0412, 0xA7A4, 0x0413, 0xA7A5, 0x0414, + 0xA7A6, 0x0415, 0xA7A7, 0x0401, 0xA7A8, 0x0416, 0xA7A9, 0x0417, 0xA7AA, 0x0418, 0xA7AB, 0x0419, 0xA7AC, 0x041A, 0xA7AD, 0x041B, + 0xA7AE, 0x041C, 0xA7AF, 0x041D, 0xA7B0, 0x041E, 0xA7B1, 0x041F, 0xA7B2, 0x0420, 0xA7B3, 0x0421, 0xA7B4, 0x0422, 0xA7B5, 0x0423, + 0xA7B6, 0x0424, 0xA7B7, 0x0425, 0xA7B8, 0x0426, 0xA7B9, 0x0427, 0xA7BA, 0x0428, 0xA7BB, 0x0429, 0xA7BC, 0x042A, 0xA7BD, 0x042B, + 0xA7BE, 0x042C, 0xA7BF, 0x042D, 0xA7C0, 0x042E, 0xA7C1, 0x042F, 0xA7D1, 0x0430, 0xA7D2, 0x0431, 0xA7D3, 0x0432, 0xA7D4, 0x0433, + 0xA7D5, 0x0434, 0xA7D6, 0x0435, 0xA7D7, 0x0451, 0xA7D8, 0x0436, 0xA7D9, 0x0437, 0xA7DA, 0x0438, 0xA7DB, 0x0439, 0xA7DC, 0x043A, + 0xA7DD, 0x043B, 0xA7DE, 0x043C, 0xA7DF, 0x043D, 0xA7E0, 0x043E, 0xA7E1, 0x043F, 0xA7E2, 0x0440, 0xA7E3, 0x0441, 0xA7E4, 0x0442, + 0xA7E5, 0x0443, 0xA7E6, 0x0444, 0xA7E7, 0x0445, 0xA7E8, 0x0446, 0xA7E9, 0x0447, 0xA7EA, 0x0448, 0xA7EB, 0x0449, 0xA7EC, 0x044A, + 0xA7ED, 0x044B, 0xA7EE, 0x044C, 0xA7EF, 0x044D, 0xA7F0, 0x044E, 0xA7F1, 0x044F, 0xA840, 0x02CA, 0xA841, 0x02CB, 0xA842, 0x02D9, + 0xA843, 0x2013, 0xA844, 0x2015, 0xA845, 0x2025, 0xA846, 0x2035, 0xA847, 0x2105, 0xA848, 0x2109, 0xA849, 0x2196, 0xA84A, 0x2197, + 0xA84B, 0x2198, 0xA84C, 0x2199, 0xA84D, 0x2215, 0xA84E, 0x221F, 0xA84F, 0x2223, 0xA850, 0x2252, 0xA851, 0x2266, 0xA852, 0x2267, + 0xA853, 0x22BF, 0xA854, 0x2550, 0xA855, 0x2551, 0xA856, 0x2552, 0xA857, 0x2553, 0xA858, 0x2554, 0xA859, 0x2555, 0xA85A, 0x2556, + 0xA85B, 0x2557, 0xA85C, 0x2558, 0xA85D, 0x2559, 0xA85E, 0x255A, 0xA85F, 0x255B, 0xA860, 0x255C, 0xA861, 0x255D, 0xA862, 0x255E, + 0xA863, 0x255F, 0xA864, 0x2560, 0xA865, 0x2561, 0xA866, 0x2562, 0xA867, 0x2563, 0xA868, 0x2564, 0xA869, 0x2565, 0xA86A, 0x2566, + 0xA86B, 0x2567, 0xA86C, 0x2568, 0xA86D, 0x2569, 0xA86E, 0x256A, 0xA86F, 0x256B, 0xA870, 0x256C, 0xA871, 0x256D, 0xA872, 0x256E, + 0xA873, 0x256F, 0xA874, 0x2570, 0xA875, 0x2571, 0xA876, 0x2572, 0xA877, 0x2573, 0xA878, 0x2581, 0xA879, 0x2582, 0xA87A, 0x2583, + 0xA87B, 0x2584, 0xA87C, 0x2585, 0xA87D, 0x2586, 0xA87E, 0x2587, 0xA880, 0x2588, 0xA881, 0x2589, 0xA882, 0x258A, 0xA883, 0x258B, + 0xA884, 0x258C, 0xA885, 0x258D, 0xA886, 0x258E, 0xA887, 0x258F, 0xA888, 0x2593, 0xA889, 0x2594, 0xA88A, 0x2595, 0xA88B, 0x25BC, + 0xA88C, 0x25BD, 0xA88D, 0x25E2, 0xA88E, 0x25E3, 0xA88F, 0x25E4, 0xA890, 0x25E5, 0xA891, 0x2609, 0xA892, 0x2295, 0xA893, 0x3012, + 0xA894, 0x301D, 0xA895, 0x301E, 0xA8A1, 0x0101, 0xA8A2, 0x00E1, 0xA8A3, 0x01CE, 0xA8A4, 0x00E0, 0xA8A5, 0x0113, 0xA8A6, 0x00E9, + 0xA8A7, 0x011B, 0xA8A8, 0x00E8, 0xA8A9, 0x012B, 0xA8AA, 0x00ED, 0xA8AB, 0x01D0, 0xA8AC, 0x00EC, 0xA8AD, 0x014D, 0xA8AE, 0x00F3, + 0xA8AF, 0x01D2, 0xA8B0, 0x00F2, 0xA8B1, 0x016B, 0xA8B2, 0x00FA, 0xA8B3, 0x01D4, 0xA8B4, 0x00F9, 0xA8B5, 0x01D6, 0xA8B6, 0x01D8, + 0xA8B7, 0x01DA, 0xA8B8, 0x01DC, 0xA8B9, 0x00FC, 0xA8BA, 0x00EA, 0xA8BB, 0x0251, 0xA8BD, 0x0144, 0xA8BE, 0x0148, 0xA8C0, 0x0261, + 0xA8C5, 0x3105, 0xA8C6, 0x3106, 0xA8C7, 0x3107, 0xA8C8, 0x3108, 0xA8C9, 0x3109, 0xA8CA, 0x310A, 0xA8CB, 0x310B, 0xA8CC, 0x310C, + 0xA8CD, 0x310D, 0xA8CE, 0x310E, 0xA8CF, 0x310F, 0xA8D0, 0x3110, 0xA8D1, 0x3111, 0xA8D2, 0x3112, 0xA8D3, 0x3113, 0xA8D4, 0x3114, + 0xA8D5, 0x3115, 0xA8D6, 0x3116, 0xA8D7, 0x3117, 0xA8D8, 0x3118, 0xA8D9, 0x3119, 0xA8DA, 0x311A, 0xA8DB, 0x311B, 0xA8DC, 0x311C, + 0xA8DD, 0x311D, 0xA8DE, 0x311E, 0xA8DF, 0x311F, 0xA8E0, 0x3120, 0xA8E1, 0x3121, 0xA8E2, 0x3122, 0xA8E3, 0x3123, 0xA8E4, 0x3124, + 0xA8E5, 0x3125, 0xA8E6, 0x3126, 0xA8E7, 0x3127, 0xA8E8, 0x3128, 0xA8E9, 0x3129, 0xA940, 0x3021, 0xA941, 0x3022, 0xA942, 0x3023, + 0xA943, 0x3024, 0xA944, 0x3025, 0xA945, 0x3026, 0xA946, 0x3027, 0xA947, 0x3028, 0xA948, 0x3029, 0xA949, 0x32A3, 0xA94A, 0x338E, + 0xA94B, 0x338F, 0xA94C, 0x339C, 0xA94D, 0x339D, 0xA94E, 0x339E, 0xA94F, 0x33A1, 0xA950, 0x33C4, 0xA951, 0x33CE, 0xA952, 0x33D1, + 0xA953, 0x33D2, 0xA954, 0x33D5, 0xA955, 0xFE30, 0xA956, 0xFFE2, 0xA957, 0xFFE4, 0xA959, 0x2121, 0xA95A, 0x3231, 0xA95C, 0x2010, + 0xA960, 0x30FC, 0xA961, 0x309B, 0xA962, 0x309C, 0xA963, 0x30FD, 0xA964, 0x30FE, 0xA965, 0x3006, 0xA966, 0x309D, 0xA967, 0x309E, + 0xA968, 0xFE49, 0xA969, 0xFE4A, 0xA96A, 0xFE4B, 0xA96B, 0xFE4C, 0xA96C, 0xFE4D, 0xA96D, 0xFE4E, 0xA96E, 0xFE4F, 0xA96F, 0xFE50, + 0xA970, 0xFE51, 0xA971, 0xFE52, 0xA972, 0xFE54, 0xA973, 0xFE55, 0xA974, 0xFE56, 0xA975, 0xFE57, 0xA976, 0xFE59, 0xA977, 0xFE5A, + 0xA978, 0xFE5B, 0xA979, 0xFE5C, 0xA97A, 0xFE5D, 0xA97B, 0xFE5E, 0xA97C, 0xFE5F, 0xA97D, 0xFE60, 0xA97E, 0xFE61, 0xA980, 0xFE62, + 0xA981, 0xFE63, 0xA982, 0xFE64, 0xA983, 0xFE65, 0xA984, 0xFE66, 0xA985, 0xFE68, 0xA986, 0xFE69, 0xA987, 0xFE6A, 0xA988, 0xFE6B, + 0xA996, 0x3007, 0xA9A4, 0x2500, 0xA9A5, 0x2501, 0xA9A6, 0x2502, 0xA9A7, 0x2503, 0xA9A8, 0x2504, 0xA9A9, 0x2505, 0xA9AA, 0x2506, + 0xA9AB, 0x2507, 0xA9AC, 0x2508, 0xA9AD, 0x2509, 0xA9AE, 0x250A, 0xA9AF, 0x250B, 0xA9B0, 0x250C, 0xA9B1, 0x250D, 0xA9B2, 0x250E, + 0xA9B3, 0x250F, 0xA9B4, 0x2510, 0xA9B5, 0x2511, 0xA9B6, 0x2512, 0xA9B7, 0x2513, 0xA9B8, 0x2514, 0xA9B9, 0x2515, 0xA9BA, 0x2516, + 0xA9BB, 0x2517, 0xA9BC, 0x2518, 0xA9BD, 0x2519, 0xA9BE, 0x251A, 0xA9BF, 0x251B, 0xA9C0, 0x251C, 0xA9C1, 0x251D, 0xA9C2, 0x251E, + 0xA9C3, 0x251F, 0xA9C4, 0x2520, 0xA9C5, 0x2521, 0xA9C6, 0x2522, 0xA9C7, 0x2523, 0xA9C8, 0x2524, 0xA9C9, 0x2525, 0xA9CA, 0x2526, + 0xA9CB, 0x2527, 0xA9CC, 0x2528, 0xA9CD, 0x2529, 0xA9CE, 0x252A, 0xA9CF, 0x252B, 0xA9D0, 0x252C, 0xA9D1, 0x252D, 0xA9D2, 0x252E, + 0xA9D3, 0x252F, 0xA9D4, 0x2530, 0xA9D5, 0x2531, 0xA9D6, 0x2532, 0xA9D7, 0x2533, 0xA9D8, 0x2534, 0xA9D9, 0x2535, 0xA9DA, 0x2536, + 0xA9DB, 0x2537, 0xA9DC, 0x2538, 0xA9DD, 0x2539, 0xA9DE, 0x253A, 0xA9DF, 0x253B, 0xA9E0, 0x253C, 0xA9E1, 0x253D, 0xA9E2, 0x253E, + 0xA9E3, 0x253F, 0xA9E4, 0x2540, 0xA9E5, 0x2541, 0xA9E6, 0x2542, 0xA9E7, 0x2543, 0xA9E8, 0x2544, 0xA9E9, 0x2545, 0xA9EA, 0x2546, + 0xA9EB, 0x2547, 0xA9EC, 0x2548, 0xA9ED, 0x2549, 0xA9EE, 0x254A, 0xA9EF, 0x254B, 0xAA40, 0x72DC, 0xAA41, 0x72DD, 0xAA42, 0x72DF, + 0xAA43, 0x72E2, 0xAA44, 0x72E3, 0xAA45, 0x72E4, 0xAA46, 0x72E5, 0xAA47, 0x72E6, 0xAA48, 0x72E7, 0xAA49, 0x72EA, 0xAA4A, 0x72EB, + 0xAA4B, 0x72F5, 0xAA4C, 0x72F6, 0xAA4D, 0x72F9, 0xAA4E, 0x72FD, 0xAA4F, 0x72FE, 0xAA50, 0x72FF, 0xAA51, 0x7300, 0xAA52, 0x7302, + 0xAA53, 0x7304, 0xAA54, 0x7305, 0xAA55, 0x7306, 0xAA56, 0x7307, 0xAA57, 0x7308, 0xAA58, 0x7309, 0xAA59, 0x730B, 0xAA5A, 0x730C, + 0xAA5B, 0x730D, 0xAA5C, 0x730F, 0xAA5D, 0x7310, 0xAA5E, 0x7311, 0xAA5F, 0x7312, 0xAA60, 0x7314, 0xAA61, 0x7318, 0xAA62, 0x7319, + 0xAA63, 0x731A, 0xAA64, 0x731F, 0xAA65, 0x7320, 0xAA66, 0x7323, 0xAA67, 0x7324, 0xAA68, 0x7326, 0xAA69, 0x7327, 0xAA6A, 0x7328, + 0xAA6B, 0x732D, 0xAA6C, 0x732F, 0xAA6D, 0x7330, 0xAA6E, 0x7332, 0xAA6F, 0x7333, 0xAA70, 0x7335, 0xAA71, 0x7336, 0xAA72, 0x733A, + 0xAA73, 0x733B, 0xAA74, 0x733C, 0xAA75, 0x733D, 0xAA76, 0x7340, 0xAA77, 0x7341, 0xAA78, 0x7342, 0xAA79, 0x7343, 0xAA7A, 0x7344, + 0xAA7B, 0x7345, 0xAA7C, 0x7346, 0xAA7D, 0x7347, 0xAA7E, 0x7348, 0xAA80, 0x7349, 0xAA81, 0x734A, 0xAA82, 0x734B, 0xAA83, 0x734C, + 0xAA84, 0x734E, 0xAA85, 0x734F, 0xAA86, 0x7351, 0xAA87, 0x7353, 0xAA88, 0x7354, 0xAA89, 0x7355, 0xAA8A, 0x7356, 0xAA8B, 0x7358, + 0xAA8C, 0x7359, 0xAA8D, 0x735A, 0xAA8E, 0x735B, 0xAA8F, 0x735C, 0xAA90, 0x735D, 0xAA91, 0x735E, 0xAA92, 0x735F, 0xAA93, 0x7361, + 0xAA94, 0x7362, 0xAA95, 0x7363, 0xAA96, 0x7364, 0xAA97, 0x7365, 0xAA98, 0x7366, 0xAA99, 0x7367, 0xAA9A, 0x7368, 0xAA9B, 0x7369, + 0xAA9C, 0x736A, 0xAA9D, 0x736B, 0xAA9E, 0x736E, 0xAA9F, 0x7370, 0xAAA0, 0x7371, 0xAB40, 0x7372, 0xAB41, 0x7373, 0xAB42, 0x7374, + 0xAB43, 0x7375, 0xAB44, 0x7376, 0xAB45, 0x7377, 0xAB46, 0x7378, 0xAB47, 0x7379, 0xAB48, 0x737A, 0xAB49, 0x737B, 0xAB4A, 0x737C, + 0xAB4B, 0x737D, 0xAB4C, 0x737F, 0xAB4D, 0x7380, 0xAB4E, 0x7381, 0xAB4F, 0x7382, 0xAB50, 0x7383, 0xAB51, 0x7385, 0xAB52, 0x7386, + 0xAB53, 0x7388, 0xAB54, 0x738A, 0xAB55, 0x738C, 0xAB56, 0x738D, 0xAB57, 0x738F, 0xAB58, 0x7390, 0xAB59, 0x7392, 0xAB5A, 0x7393, + 0xAB5B, 0x7394, 0xAB5C, 0x7395, 0xAB5D, 0x7397, 0xAB5E, 0x7398, 0xAB5F, 0x7399, 0xAB60, 0x739A, 0xAB61, 0x739C, 0xAB62, 0x739D, + 0xAB63, 0x739E, 0xAB64, 0x73A0, 0xAB65, 0x73A1, 0xAB66, 0x73A3, 0xAB67, 0x73A4, 0xAB68, 0x73A5, 0xAB69, 0x73A6, 0xAB6A, 0x73A7, + 0xAB6B, 0x73A8, 0xAB6C, 0x73AA, 0xAB6D, 0x73AC, 0xAB6E, 0x73AD, 0xAB6F, 0x73B1, 0xAB70, 0x73B4, 0xAB71, 0x73B5, 0xAB72, 0x73B6, + 0xAB73, 0x73B8, 0xAB74, 0x73B9, 0xAB75, 0x73BC, 0xAB76, 0x73BD, 0xAB77, 0x73BE, 0xAB78, 0x73BF, 0xAB79, 0x73C1, 0xAB7A, 0x73C3, + 0xAB7B, 0x73C4, 0xAB7C, 0x73C5, 0xAB7D, 0x73C6, 0xAB7E, 0x73C7, 0xAB80, 0x73CB, 0xAB81, 0x73CC, 0xAB82, 0x73CE, 0xAB83, 0x73D2, + 0xAB84, 0x73D3, 0xAB85, 0x73D4, 0xAB86, 0x73D5, 0xAB87, 0x73D6, 0xAB88, 0x73D7, 0xAB89, 0x73D8, 0xAB8A, 0x73DA, 0xAB8B, 0x73DB, + 0xAB8C, 0x73DC, 0xAB8D, 0x73DD, 0xAB8E, 0x73DF, 0xAB8F, 0x73E1, 0xAB90, 0x73E2, 0xAB91, 0x73E3, 0xAB92, 0x73E4, 0xAB93, 0x73E6, + 0xAB94, 0x73E8, 0xAB95, 0x73EA, 0xAB96, 0x73EB, 0xAB97, 0x73EC, 0xAB98, 0x73EE, 0xAB99, 0x73EF, 0xAB9A, 0x73F0, 0xAB9B, 0x73F1, + 0xAB9C, 0x73F3, 0xAB9D, 0x73F4, 0xAB9E, 0x73F5, 0xAB9F, 0x73F6, 0xABA0, 0x73F7, 0xAC40, 0x73F8, 0xAC41, 0x73F9, 0xAC42, 0x73FA, + 0xAC43, 0x73FB, 0xAC44, 0x73FC, 0xAC45, 0x73FD, 0xAC46, 0x73FE, 0xAC47, 0x73FF, 0xAC48, 0x7400, 0xAC49, 0x7401, 0xAC4A, 0x7402, + 0xAC4B, 0x7404, 0xAC4C, 0x7407, 0xAC4D, 0x7408, 0xAC4E, 0x740B, 0xAC4F, 0x740C, 0xAC50, 0x740D, 0xAC51, 0x740E, 0xAC52, 0x7411, + 0xAC53, 0x7412, 0xAC54, 0x7413, 0xAC55, 0x7414, 0xAC56, 0x7415, 0xAC57, 0x7416, 0xAC58, 0x7417, 0xAC59, 0x7418, 0xAC5A, 0x7419, + 0xAC5B, 0x741C, 0xAC5C, 0x741D, 0xAC5D, 0x741E, 0xAC5E, 0x741F, 0xAC5F, 0x7420, 0xAC60, 0x7421, 0xAC61, 0x7423, 0xAC62, 0x7424, + 0xAC63, 0x7427, 0xAC64, 0x7429, 0xAC65, 0x742B, 0xAC66, 0x742D, 0xAC67, 0x742F, 0xAC68, 0x7431, 0xAC69, 0x7432, 0xAC6A, 0x7437, + 0xAC6B, 0x7438, 0xAC6C, 0x7439, 0xAC6D, 0x743A, 0xAC6E, 0x743B, 0xAC6F, 0x743D, 0xAC70, 0x743E, 0xAC71, 0x743F, 0xAC72, 0x7440, + 0xAC73, 0x7442, 0xAC74, 0x7443, 0xAC75, 0x7444, 0xAC76, 0x7445, 0xAC77, 0x7446, 0xAC78, 0x7447, 0xAC79, 0x7448, 0xAC7A, 0x7449, + 0xAC7B, 0x744A, 0xAC7C, 0x744B, 0xAC7D, 0x744C, 0xAC7E, 0x744D, 0xAC80, 0x744E, 0xAC81, 0x744F, 0xAC82, 0x7450, 0xAC83, 0x7451, + 0xAC84, 0x7452, 0xAC85, 0x7453, 0xAC86, 0x7454, 0xAC87, 0x7456, 0xAC88, 0x7458, 0xAC89, 0x745D, 0xAC8A, 0x7460, 0xAC8B, 0x7461, + 0xAC8C, 0x7462, 0xAC8D, 0x7463, 0xAC8E, 0x7464, 0xAC8F, 0x7465, 0xAC90, 0x7466, 0xAC91, 0x7467, 0xAC92, 0x7468, 0xAC93, 0x7469, + 0xAC94, 0x746A, 0xAC95, 0x746B, 0xAC96, 0x746C, 0xAC97, 0x746E, 0xAC98, 0x746F, 0xAC99, 0x7471, 0xAC9A, 0x7472, 0xAC9B, 0x7473, + 0xAC9C, 0x7474, 0xAC9D, 0x7475, 0xAC9E, 0x7478, 0xAC9F, 0x7479, 0xACA0, 0x747A, 0xAD40, 0x747B, 0xAD41, 0x747C, 0xAD42, 0x747D, + 0xAD43, 0x747F, 0xAD44, 0x7482, 0xAD45, 0x7484, 0xAD46, 0x7485, 0xAD47, 0x7486, 0xAD48, 0x7488, 0xAD49, 0x7489, 0xAD4A, 0x748A, + 0xAD4B, 0x748C, 0xAD4C, 0x748D, 0xAD4D, 0x748F, 0xAD4E, 0x7491, 0xAD4F, 0x7492, 0xAD50, 0x7493, 0xAD51, 0x7494, 0xAD52, 0x7495, + 0xAD53, 0x7496, 0xAD54, 0x7497, 0xAD55, 0x7498, 0xAD56, 0x7499, 0xAD57, 0x749A, 0xAD58, 0x749B, 0xAD59, 0x749D, 0xAD5A, 0x749F, + 0xAD5B, 0x74A0, 0xAD5C, 0x74A1, 0xAD5D, 0x74A2, 0xAD5E, 0x74A3, 0xAD5F, 0x74A4, 0xAD60, 0x74A5, 0xAD61, 0x74A6, 0xAD62, 0x74AA, + 0xAD63, 0x74AB, 0xAD64, 0x74AC, 0xAD65, 0x74AD, 0xAD66, 0x74AE, 0xAD67, 0x74AF, 0xAD68, 0x74B0, 0xAD69, 0x74B1, 0xAD6A, 0x74B2, + 0xAD6B, 0x74B3, 0xAD6C, 0x74B4, 0xAD6D, 0x74B5, 0xAD6E, 0x74B6, 0xAD6F, 0x74B7, 0xAD70, 0x74B8, 0xAD71, 0x74B9, 0xAD72, 0x74BB, + 0xAD73, 0x74BC, 0xAD74, 0x74BD, 0xAD75, 0x74BE, 0xAD76, 0x74BF, 0xAD77, 0x74C0, 0xAD78, 0x74C1, 0xAD79, 0x74C2, 0xAD7A, 0x74C3, + 0xAD7B, 0x74C4, 0xAD7C, 0x74C5, 0xAD7D, 0x74C6, 0xAD7E, 0x74C7, 0xAD80, 0x74C8, 0xAD81, 0x74C9, 0xAD82, 0x74CA, 0xAD83, 0x74CB, + 0xAD84, 0x74CC, 0xAD85, 0x74CD, 0xAD86, 0x74CE, 0xAD87, 0x74CF, 0xAD88, 0x74D0, 0xAD89, 0x74D1, 0xAD8A, 0x74D3, 0xAD8B, 0x74D4, + 0xAD8C, 0x74D5, 0xAD8D, 0x74D6, 0xAD8E, 0x74D7, 0xAD8F, 0x74D8, 0xAD90, 0x74D9, 0xAD91, 0x74DA, 0xAD92, 0x74DB, 0xAD93, 0x74DD, + 0xAD94, 0x74DF, 0xAD95, 0x74E1, 0xAD96, 0x74E5, 0xAD97, 0x74E7, 0xAD98, 0x74E8, 0xAD99, 0x74E9, 0xAD9A, 0x74EA, 0xAD9B, 0x74EB, + 0xAD9C, 0x74EC, 0xAD9D, 0x74ED, 0xAD9E, 0x74F0, 0xAD9F, 0x74F1, 0xADA0, 0x74F2, 0xAE40, 0x74F3, 0xAE41, 0x74F5, 0xAE42, 0x74F8, + 0xAE43, 0x74F9, 0xAE44, 0x74FA, 0xAE45, 0x74FB, 0xAE46, 0x74FC, 0xAE47, 0x74FD, 0xAE48, 0x74FE, 0xAE49, 0x7500, 0xAE4A, 0x7501, + 0xAE4B, 0x7502, 0xAE4C, 0x7503, 0xAE4D, 0x7505, 0xAE4E, 0x7506, 0xAE4F, 0x7507, 0xAE50, 0x7508, 0xAE51, 0x7509, 0xAE52, 0x750A, + 0xAE53, 0x750B, 0xAE54, 0x750C, 0xAE55, 0x750E, 0xAE56, 0x7510, 0xAE57, 0x7512, 0xAE58, 0x7514, 0xAE59, 0x7515, 0xAE5A, 0x7516, + 0xAE5B, 0x7517, 0xAE5C, 0x751B, 0xAE5D, 0x751D, 0xAE5E, 0x751E, 0xAE5F, 0x7520, 0xAE60, 0x7521, 0xAE61, 0x7522, 0xAE62, 0x7523, + 0xAE63, 0x7524, 0xAE64, 0x7526, 0xAE65, 0x7527, 0xAE66, 0x752A, 0xAE67, 0x752E, 0xAE68, 0x7534, 0xAE69, 0x7536, 0xAE6A, 0x7539, + 0xAE6B, 0x753C, 0xAE6C, 0x753D, 0xAE6D, 0x753F, 0xAE6E, 0x7541, 0xAE6F, 0x7542, 0xAE70, 0x7543, 0xAE71, 0x7544, 0xAE72, 0x7546, + 0xAE73, 0x7547, 0xAE74, 0x7549, 0xAE75, 0x754A, 0xAE76, 0x754D, 0xAE77, 0x7550, 0xAE78, 0x7551, 0xAE79, 0x7552, 0xAE7A, 0x7553, + 0xAE7B, 0x7555, 0xAE7C, 0x7556, 0xAE7D, 0x7557, 0xAE7E, 0x7558, 0xAE80, 0x755D, 0xAE81, 0x755E, 0xAE82, 0x755F, 0xAE83, 0x7560, + 0xAE84, 0x7561, 0xAE85, 0x7562, 0xAE86, 0x7563, 0xAE87, 0x7564, 0xAE88, 0x7567, 0xAE89, 0x7568, 0xAE8A, 0x7569, 0xAE8B, 0x756B, + 0xAE8C, 0x756C, 0xAE8D, 0x756D, 0xAE8E, 0x756E, 0xAE8F, 0x756F, 0xAE90, 0x7570, 0xAE91, 0x7571, 0xAE92, 0x7573, 0xAE93, 0x7575, + 0xAE94, 0x7576, 0xAE95, 0x7577, 0xAE96, 0x757A, 0xAE97, 0x757B, 0xAE98, 0x757C, 0xAE99, 0x757D, 0xAE9A, 0x757E, 0xAE9B, 0x7580, + 0xAE9C, 0x7581, 0xAE9D, 0x7582, 0xAE9E, 0x7584, 0xAE9F, 0x7585, 0xAEA0, 0x7587, 0xAF40, 0x7588, 0xAF41, 0x7589, 0xAF42, 0x758A, + 0xAF43, 0x758C, 0xAF44, 0x758D, 0xAF45, 0x758E, 0xAF46, 0x7590, 0xAF47, 0x7593, 0xAF48, 0x7595, 0xAF49, 0x7598, 0xAF4A, 0x759B, + 0xAF4B, 0x759C, 0xAF4C, 0x759E, 0xAF4D, 0x75A2, 0xAF4E, 0x75A6, 0xAF4F, 0x75A7, 0xAF50, 0x75A8, 0xAF51, 0x75A9, 0xAF52, 0x75AA, + 0xAF53, 0x75AD, 0xAF54, 0x75B6, 0xAF55, 0x75B7, 0xAF56, 0x75BA, 0xAF57, 0x75BB, 0xAF58, 0x75BF, 0xAF59, 0x75C0, 0xAF5A, 0x75C1, + 0xAF5B, 0x75C6, 0xAF5C, 0x75CB, 0xAF5D, 0x75CC, 0xAF5E, 0x75CE, 0xAF5F, 0x75CF, 0xAF60, 0x75D0, 0xAF61, 0x75D1, 0xAF62, 0x75D3, + 0xAF63, 0x75D7, 0xAF64, 0x75D9, 0xAF65, 0x75DA, 0xAF66, 0x75DC, 0xAF67, 0x75DD, 0xAF68, 0x75DF, 0xAF69, 0x75E0, 0xAF6A, 0x75E1, + 0xAF6B, 0x75E5, 0xAF6C, 0x75E9, 0xAF6D, 0x75EC, 0xAF6E, 0x75ED, 0xAF6F, 0x75EE, 0xAF70, 0x75EF, 0xAF71, 0x75F2, 0xAF72, 0x75F3, + 0xAF73, 0x75F5, 0xAF74, 0x75F6, 0xAF75, 0x75F7, 0xAF76, 0x75F8, 0xAF77, 0x75FA, 0xAF78, 0x75FB, 0xAF79, 0x75FD, 0xAF7A, 0x75FE, + 0xAF7B, 0x7602, 0xAF7C, 0x7604, 0xAF7D, 0x7606, 0xAF7E, 0x7607, 0xAF80, 0x7608, 0xAF81, 0x7609, 0xAF82, 0x760B, 0xAF83, 0x760D, + 0xAF84, 0x760E, 0xAF85, 0x760F, 0xAF86, 0x7611, 0xAF87, 0x7612, 0xAF88, 0x7613, 0xAF89, 0x7614, 0xAF8A, 0x7616, 0xAF8B, 0x761A, + 0xAF8C, 0x761C, 0xAF8D, 0x761D, 0xAF8E, 0x761E, 0xAF8F, 0x7621, 0xAF90, 0x7623, 0xAF91, 0x7627, 0xAF92, 0x7628, 0xAF93, 0x762C, + 0xAF94, 0x762E, 0xAF95, 0x762F, 0xAF96, 0x7631, 0xAF97, 0x7632, 0xAF98, 0x7636, 0xAF99, 0x7637, 0xAF9A, 0x7639, 0xAF9B, 0x763A, + 0xAF9C, 0x763B, 0xAF9D, 0x763D, 0xAF9E, 0x7641, 0xAF9F, 0x7642, 0xAFA0, 0x7644, 0xB040, 0x7645, 0xB041, 0x7646, 0xB042, 0x7647, + 0xB043, 0x7648, 0xB044, 0x7649, 0xB045, 0x764A, 0xB046, 0x764B, 0xB047, 0x764E, 0xB048, 0x764F, 0xB049, 0x7650, 0xB04A, 0x7651, + 0xB04B, 0x7652, 0xB04C, 0x7653, 0xB04D, 0x7655, 0xB04E, 0x7657, 0xB04F, 0x7658, 0xB050, 0x7659, 0xB051, 0x765A, 0xB052, 0x765B, + 0xB053, 0x765D, 0xB054, 0x765F, 0xB055, 0x7660, 0xB056, 0x7661, 0xB057, 0x7662, 0xB058, 0x7664, 0xB059, 0x7665, 0xB05A, 0x7666, + 0xB05B, 0x7667, 0xB05C, 0x7668, 0xB05D, 0x7669, 0xB05E, 0x766A, 0xB05F, 0x766C, 0xB060, 0x766D, 0xB061, 0x766E, 0xB062, 0x7670, + 0xB063, 0x7671, 0xB064, 0x7672, 0xB065, 0x7673, 0xB066, 0x7674, 0xB067, 0x7675, 0xB068, 0x7676, 0xB069, 0x7677, 0xB06A, 0x7679, + 0xB06B, 0x767A, 0xB06C, 0x767C, 0xB06D, 0x767F, 0xB06E, 0x7680, 0xB06F, 0x7681, 0xB070, 0x7683, 0xB071, 0x7685, 0xB072, 0x7689, + 0xB073, 0x768A, 0xB074, 0x768C, 0xB075, 0x768D, 0xB076, 0x768F, 0xB077, 0x7690, 0xB078, 0x7692, 0xB079, 0x7694, 0xB07A, 0x7695, + 0xB07B, 0x7697, 0xB07C, 0x7698, 0xB07D, 0x769A, 0xB07E, 0x769B, 0xB080, 0x769C, 0xB081, 0x769D, 0xB082, 0x769E, 0xB083, 0x769F, + 0xB084, 0x76A0, 0xB085, 0x76A1, 0xB086, 0x76A2, 0xB087, 0x76A3, 0xB088, 0x76A5, 0xB089, 0x76A6, 0xB08A, 0x76A7, 0xB08B, 0x76A8, + 0xB08C, 0x76A9, 0xB08D, 0x76AA, 0xB08E, 0x76AB, 0xB08F, 0x76AC, 0xB090, 0x76AD, 0xB091, 0x76AF, 0xB092, 0x76B0, 0xB093, 0x76B3, + 0xB094, 0x76B5, 0xB095, 0x76B6, 0xB096, 0x76B7, 0xB097, 0x76B8, 0xB098, 0x76B9, 0xB099, 0x76BA, 0xB09A, 0x76BB, 0xB09B, 0x76BC, + 0xB09C, 0x76BD, 0xB09D, 0x76BE, 0xB09E, 0x76C0, 0xB09F, 0x76C1, 0xB0A0, 0x76C3, 0xB0A1, 0x554A, 0xB0A2, 0x963F, 0xB0A3, 0x57C3, + 0xB0A4, 0x6328, 0xB0A5, 0x54CE, 0xB0A6, 0x5509, 0xB0A7, 0x54C0, 0xB0A8, 0x7691, 0xB0A9, 0x764C, 0xB0AA, 0x853C, 0xB0AB, 0x77EE, + 0xB0AC, 0x827E, 0xB0AD, 0x788D, 0xB0AE, 0x7231, 0xB0AF, 0x9698, 0xB0B0, 0x978D, 0xB0B1, 0x6C28, 0xB0B2, 0x5B89, 0xB0B3, 0x4FFA, + 0xB0B4, 0x6309, 0xB0B5, 0x6697, 0xB0B6, 0x5CB8, 0xB0B7, 0x80FA, 0xB0B8, 0x6848, 0xB0B9, 0x80AE, 0xB0BA, 0x6602, 0xB0BB, 0x76CE, + 0xB0BC, 0x51F9, 0xB0BD, 0x6556, 0xB0BE, 0x71AC, 0xB0BF, 0x7FF1, 0xB0C0, 0x8884, 0xB0C1, 0x50B2, 0xB0C2, 0x5965, 0xB0C3, 0x61CA, + 0xB0C4, 0x6FB3, 0xB0C5, 0x82AD, 0xB0C6, 0x634C, 0xB0C7, 0x6252, 0xB0C8, 0x53ED, 0xB0C9, 0x5427, 0xB0CA, 0x7B06, 0xB0CB, 0x516B, + 0xB0CC, 0x75A4, 0xB0CD, 0x5DF4, 0xB0CE, 0x62D4, 0xB0CF, 0x8DCB, 0xB0D0, 0x9776, 0xB0D1, 0x628A, 0xB0D2, 0x8019, 0xB0D3, 0x575D, + 0xB0D4, 0x9738, 0xB0D5, 0x7F62, 0xB0D6, 0x7238, 0xB0D7, 0x767D, 0xB0D8, 0x67CF, 0xB0D9, 0x767E, 0xB0DA, 0x6446, 0xB0DB, 0x4F70, + 0xB0DC, 0x8D25, 0xB0DD, 0x62DC, 0xB0DE, 0x7A17, 0xB0DF, 0x6591, 0xB0E0, 0x73ED, 0xB0E1, 0x642C, 0xB0E2, 0x6273, 0xB0E3, 0x822C, + 0xB0E4, 0x9881, 0xB0E5, 0x677F, 0xB0E6, 0x7248, 0xB0E7, 0x626E, 0xB0E8, 0x62CC, 0xB0E9, 0x4F34, 0xB0EA, 0x74E3, 0xB0EB, 0x534A, + 0xB0EC, 0x529E, 0xB0ED, 0x7ECA, 0xB0EE, 0x90A6, 0xB0EF, 0x5E2E, 0xB0F0, 0x6886, 0xB0F1, 0x699C, 0xB0F2, 0x8180, 0xB0F3, 0x7ED1, + 0xB0F4, 0x68D2, 0xB0F5, 0x78C5, 0xB0F6, 0x868C, 0xB0F7, 0x9551, 0xB0F8, 0x508D, 0xB0F9, 0x8C24, 0xB0FA, 0x82DE, 0xB0FB, 0x80DE, + 0xB0FC, 0x5305, 0xB0FD, 0x8912, 0xB0FE, 0x5265, 0xB140, 0x76C4, 0xB141, 0x76C7, 0xB142, 0x76C9, 0xB143, 0x76CB, 0xB144, 0x76CC, + 0xB145, 0x76D3, 0xB146, 0x76D5, 0xB147, 0x76D9, 0xB148, 0x76DA, 0xB149, 0x76DC, 0xB14A, 0x76DD, 0xB14B, 0x76DE, 0xB14C, 0x76E0, + 0xB14D, 0x76E1, 0xB14E, 0x76E2, 0xB14F, 0x76E3, 0xB150, 0x76E4, 0xB151, 0x76E6, 0xB152, 0x76E7, 0xB153, 0x76E8, 0xB154, 0x76E9, + 0xB155, 0x76EA, 0xB156, 0x76EB, 0xB157, 0x76EC, 0xB158, 0x76ED, 0xB159, 0x76F0, 0xB15A, 0x76F3, 0xB15B, 0x76F5, 0xB15C, 0x76F6, + 0xB15D, 0x76F7, 0xB15E, 0x76FA, 0xB15F, 0x76FB, 0xB160, 0x76FD, 0xB161, 0x76FF, 0xB162, 0x7700, 0xB163, 0x7702, 0xB164, 0x7703, + 0xB165, 0x7705, 0xB166, 0x7706, 0xB167, 0x770A, 0xB168, 0x770C, 0xB169, 0x770E, 0xB16A, 0x770F, 0xB16B, 0x7710, 0xB16C, 0x7711, + 0xB16D, 0x7712, 0xB16E, 0x7713, 0xB16F, 0x7714, 0xB170, 0x7715, 0xB171, 0x7716, 0xB172, 0x7717, 0xB173, 0x7718, 0xB174, 0x771B, + 0xB175, 0x771C, 0xB176, 0x771D, 0xB177, 0x771E, 0xB178, 0x7721, 0xB179, 0x7723, 0xB17A, 0x7724, 0xB17B, 0x7725, 0xB17C, 0x7727, + 0xB17D, 0x772A, 0xB17E, 0x772B, 0xB180, 0x772C, 0xB181, 0x772E, 0xB182, 0x7730, 0xB183, 0x7731, 0xB184, 0x7732, 0xB185, 0x7733, + 0xB186, 0x7734, 0xB187, 0x7739, 0xB188, 0x773B, 0xB189, 0x773D, 0xB18A, 0x773E, 0xB18B, 0x773F, 0xB18C, 0x7742, 0xB18D, 0x7744, + 0xB18E, 0x7745, 0xB18F, 0x7746, 0xB190, 0x7748, 0xB191, 0x7749, 0xB192, 0x774A, 0xB193, 0x774B, 0xB194, 0x774C, 0xB195, 0x774D, + 0xB196, 0x774E, 0xB197, 0x774F, 0xB198, 0x7752, 0xB199, 0x7753, 0xB19A, 0x7754, 0xB19B, 0x7755, 0xB19C, 0x7756, 0xB19D, 0x7757, + 0xB19E, 0x7758, 0xB19F, 0x7759, 0xB1A0, 0x775C, 0xB1A1, 0x8584, 0xB1A2, 0x96F9, 0xB1A3, 0x4FDD, 0xB1A4, 0x5821, 0xB1A5, 0x9971, + 0xB1A6, 0x5B9D, 0xB1A7, 0x62B1, 0xB1A8, 0x62A5, 0xB1A9, 0x66B4, 0xB1AA, 0x8C79, 0xB1AB, 0x9C8D, 0xB1AC, 0x7206, 0xB1AD, 0x676F, + 0xB1AE, 0x7891, 0xB1AF, 0x60B2, 0xB1B0, 0x5351, 0xB1B1, 0x5317, 0xB1B2, 0x8F88, 0xB1B3, 0x80CC, 0xB1B4, 0x8D1D, 0xB1B5, 0x94A1, + 0xB1B6, 0x500D, 0xB1B7, 0x72C8, 0xB1B8, 0x5907, 0xB1B9, 0x60EB, 0xB1BA, 0x7119, 0xB1BB, 0x88AB, 0xB1BC, 0x5954, 0xB1BD, 0x82EF, + 0xB1BE, 0x672C, 0xB1BF, 0x7B28, 0xB1C0, 0x5D29, 0xB1C1, 0x7EF7, 0xB1C2, 0x752D, 0xB1C3, 0x6CF5, 0xB1C4, 0x8E66, 0xB1C5, 0x8FF8, + 0xB1C6, 0x903C, 0xB1C7, 0x9F3B, 0xB1C8, 0x6BD4, 0xB1C9, 0x9119, 0xB1CA, 0x7B14, 0xB1CB, 0x5F7C, 0xB1CC, 0x78A7, 0xB1CD, 0x84D6, + 0xB1CE, 0x853D, 0xB1CF, 0x6BD5, 0xB1D0, 0x6BD9, 0xB1D1, 0x6BD6, 0xB1D2, 0x5E01, 0xB1D3, 0x5E87, 0xB1D4, 0x75F9, 0xB1D5, 0x95ED, + 0xB1D6, 0x655D, 0xB1D7, 0x5F0A, 0xB1D8, 0x5FC5, 0xB1D9, 0x8F9F, 0xB1DA, 0x58C1, 0xB1DB, 0x81C2, 0xB1DC, 0x907F, 0xB1DD, 0x965B, + 0xB1DE, 0x97AD, 0xB1DF, 0x8FB9, 0xB1E0, 0x7F16, 0xB1E1, 0x8D2C, 0xB1E2, 0x6241, 0xB1E3, 0x4FBF, 0xB1E4, 0x53D8, 0xB1E5, 0x535E, + 0xB1E6, 0x8FA8, 0xB1E7, 0x8FA9, 0xB1E8, 0x8FAB, 0xB1E9, 0x904D, 0xB1EA, 0x6807, 0xB1EB, 0x5F6A, 0xB1EC, 0x8198, 0xB1ED, 0x8868, + 0xB1EE, 0x9CD6, 0xB1EF, 0x618B, 0xB1F0, 0x522B, 0xB1F1, 0x762A, 0xB1F2, 0x5F6C, 0xB1F3, 0x658C, 0xB1F4, 0x6FD2, 0xB1F5, 0x6EE8, + 0xB1F6, 0x5BBE, 0xB1F7, 0x6448, 0xB1F8, 0x5175, 0xB1F9, 0x51B0, 0xB1FA, 0x67C4, 0xB1FB, 0x4E19, 0xB1FC, 0x79C9, 0xB1FD, 0x997C, + 0xB1FE, 0x70B3, 0xB240, 0x775D, 0xB241, 0x775E, 0xB242, 0x775F, 0xB243, 0x7760, 0xB244, 0x7764, 0xB245, 0x7767, 0xB246, 0x7769, + 0xB247, 0x776A, 0xB248, 0x776D, 0xB249, 0x776E, 0xB24A, 0x776F, 0xB24B, 0x7770, 0xB24C, 0x7771, 0xB24D, 0x7772, 0xB24E, 0x7773, + 0xB24F, 0x7774, 0xB250, 0x7775, 0xB251, 0x7776, 0xB252, 0x7777, 0xB253, 0x7778, 0xB254, 0x777A, 0xB255, 0x777B, 0xB256, 0x777C, + 0xB257, 0x7781, 0xB258, 0x7782, 0xB259, 0x7783, 0xB25A, 0x7786, 0xB25B, 0x7787, 0xB25C, 0x7788, 0xB25D, 0x7789, 0xB25E, 0x778A, + 0xB25F, 0x778B, 0xB260, 0x778F, 0xB261, 0x7790, 0xB262, 0x7793, 0xB263, 0x7794, 0xB264, 0x7795, 0xB265, 0x7796, 0xB266, 0x7797, + 0xB267, 0x7798, 0xB268, 0x7799, 0xB269, 0x779A, 0xB26A, 0x779B, 0xB26B, 0x779C, 0xB26C, 0x779D, 0xB26D, 0x779E, 0xB26E, 0x77A1, + 0xB26F, 0x77A3, 0xB270, 0x77A4, 0xB271, 0x77A6, 0xB272, 0x77A8, 0xB273, 0x77AB, 0xB274, 0x77AD, 0xB275, 0x77AE, 0xB276, 0x77AF, + 0xB277, 0x77B1, 0xB278, 0x77B2, 0xB279, 0x77B4, 0xB27A, 0x77B6, 0xB27B, 0x77B7, 0xB27C, 0x77B8, 0xB27D, 0x77B9, 0xB27E, 0x77BA, + 0xB280, 0x77BC, 0xB281, 0x77BE, 0xB282, 0x77C0, 0xB283, 0x77C1, 0xB284, 0x77C2, 0xB285, 0x77C3, 0xB286, 0x77C4, 0xB287, 0x77C5, + 0xB288, 0x77C6, 0xB289, 0x77C7, 0xB28A, 0x77C8, 0xB28B, 0x77C9, 0xB28C, 0x77CA, 0xB28D, 0x77CB, 0xB28E, 0x77CC, 0xB28F, 0x77CE, + 0xB290, 0x77CF, 0xB291, 0x77D0, 0xB292, 0x77D1, 0xB293, 0x77D2, 0xB294, 0x77D3, 0xB295, 0x77D4, 0xB296, 0x77D5, 0xB297, 0x77D6, + 0xB298, 0x77D8, 0xB299, 0x77D9, 0xB29A, 0x77DA, 0xB29B, 0x77DD, 0xB29C, 0x77DE, 0xB29D, 0x77DF, 0xB29E, 0x77E0, 0xB29F, 0x77E1, + 0xB2A0, 0x77E4, 0xB2A1, 0x75C5, 0xB2A2, 0x5E76, 0xB2A3, 0x73BB, 0xB2A4, 0x83E0, 0xB2A5, 0x64AD, 0xB2A6, 0x62E8, 0xB2A7, 0x94B5, + 0xB2A8, 0x6CE2, 0xB2A9, 0x535A, 0xB2AA, 0x52C3, 0xB2AB, 0x640F, 0xB2AC, 0x94C2, 0xB2AD, 0x7B94, 0xB2AE, 0x4F2F, 0xB2AF, 0x5E1B, + 0xB2B0, 0x8236, 0xB2B1, 0x8116, 0xB2B2, 0x818A, 0xB2B3, 0x6E24, 0xB2B4, 0x6CCA, 0xB2B5, 0x9A73, 0xB2B6, 0x6355, 0xB2B7, 0x535C, + 0xB2B8, 0x54FA, 0xB2B9, 0x8865, 0xB2BA, 0x57E0, 0xB2BB, 0x4E0D, 0xB2BC, 0x5E03, 0xB2BD, 0x6B65, 0xB2BE, 0x7C3F, 0xB2BF, 0x90E8, + 0xB2C0, 0x6016, 0xB2C1, 0x64E6, 0xB2C2, 0x731C, 0xB2C3, 0x88C1, 0xB2C4, 0x6750, 0xB2C5, 0x624D, 0xB2C6, 0x8D22, 0xB2C7, 0x776C, + 0xB2C8, 0x8E29, 0xB2C9, 0x91C7, 0xB2CA, 0x5F69, 0xB2CB, 0x83DC, 0xB2CC, 0x8521, 0xB2CD, 0x9910, 0xB2CE, 0x53C2, 0xB2CF, 0x8695, + 0xB2D0, 0x6B8B, 0xB2D1, 0x60ED, 0xB2D2, 0x60E8, 0xB2D3, 0x707F, 0xB2D4, 0x82CD, 0xB2D5, 0x8231, 0xB2D6, 0x4ED3, 0xB2D7, 0x6CA7, + 0xB2D8, 0x85CF, 0xB2D9, 0x64CD, 0xB2DA, 0x7CD9, 0xB2DB, 0x69FD, 0xB2DC, 0x66F9, 0xB2DD, 0x8349, 0xB2DE, 0x5395, 0xB2DF, 0x7B56, + 0xB2E0, 0x4FA7, 0xB2E1, 0x518C, 0xB2E2, 0x6D4B, 0xB2E3, 0x5C42, 0xB2E4, 0x8E6D, 0xB2E5, 0x63D2, 0xB2E6, 0x53C9, 0xB2E7, 0x832C, + 0xB2E8, 0x8336, 0xB2E9, 0x67E5, 0xB2EA, 0x78B4, 0xB2EB, 0x643D, 0xB2EC, 0x5BDF, 0xB2ED, 0x5C94, 0xB2EE, 0x5DEE, 0xB2EF, 0x8BE7, + 0xB2F0, 0x62C6, 0xB2F1, 0x67F4, 0xB2F2, 0x8C7A, 0xB2F3, 0x6400, 0xB2F4, 0x63BA, 0xB2F5, 0x8749, 0xB2F6, 0x998B, 0xB2F7, 0x8C17, + 0xB2F8, 0x7F20, 0xB2F9, 0x94F2, 0xB2FA, 0x4EA7, 0xB2FB, 0x9610, 0xB2FC, 0x98A4, 0xB2FD, 0x660C, 0xB2FE, 0x7316, 0xB340, 0x77E6, + 0xB341, 0x77E8, 0xB342, 0x77EA, 0xB343, 0x77EF, 0xB344, 0x77F0, 0xB345, 0x77F1, 0xB346, 0x77F2, 0xB347, 0x77F4, 0xB348, 0x77F5, + 0xB349, 0x77F7, 0xB34A, 0x77F9, 0xB34B, 0x77FA, 0xB34C, 0x77FB, 0xB34D, 0x77FC, 0xB34E, 0x7803, 0xB34F, 0x7804, 0xB350, 0x7805, + 0xB351, 0x7806, 0xB352, 0x7807, 0xB353, 0x7808, 0xB354, 0x780A, 0xB355, 0x780B, 0xB356, 0x780E, 0xB357, 0x780F, 0xB358, 0x7810, + 0xB359, 0x7813, 0xB35A, 0x7815, 0xB35B, 0x7819, 0xB35C, 0x781B, 0xB35D, 0x781E, 0xB35E, 0x7820, 0xB35F, 0x7821, 0xB360, 0x7822, + 0xB361, 0x7824, 0xB362, 0x7828, 0xB363, 0x782A, 0xB364, 0x782B, 0xB365, 0x782E, 0xB366, 0x782F, 0xB367, 0x7831, 0xB368, 0x7832, + 0xB369, 0x7833, 0xB36A, 0x7835, 0xB36B, 0x7836, 0xB36C, 0x783D, 0xB36D, 0x783F, 0xB36E, 0x7841, 0xB36F, 0x7842, 0xB370, 0x7843, + 0xB371, 0x7844, 0xB372, 0x7846, 0xB373, 0x7848, 0xB374, 0x7849, 0xB375, 0x784A, 0xB376, 0x784B, 0xB377, 0x784D, 0xB378, 0x784F, + 0xB379, 0x7851, 0xB37A, 0x7853, 0xB37B, 0x7854, 0xB37C, 0x7858, 0xB37D, 0x7859, 0xB37E, 0x785A, 0xB380, 0x785B, 0xB381, 0x785C, + 0xB382, 0x785E, 0xB383, 0x785F, 0xB384, 0x7860, 0xB385, 0x7861, 0xB386, 0x7862, 0xB387, 0x7863, 0xB388, 0x7864, 0xB389, 0x7865, + 0xB38A, 0x7866, 0xB38B, 0x7867, 0xB38C, 0x7868, 0xB38D, 0x7869, 0xB38E, 0x786F, 0xB38F, 0x7870, 0xB390, 0x7871, 0xB391, 0x7872, + 0xB392, 0x7873, 0xB393, 0x7874, 0xB394, 0x7875, 0xB395, 0x7876, 0xB396, 0x7878, 0xB397, 0x7879, 0xB398, 0x787A, 0xB399, 0x787B, + 0xB39A, 0x787D, 0xB39B, 0x787E, 0xB39C, 0x787F, 0xB39D, 0x7880, 0xB39E, 0x7881, 0xB39F, 0x7882, 0xB3A0, 0x7883, 0xB3A1, 0x573A, + 0xB3A2, 0x5C1D, 0xB3A3, 0x5E38, 0xB3A4, 0x957F, 0xB3A5, 0x507F, 0xB3A6, 0x80A0, 0xB3A7, 0x5382, 0xB3A8, 0x655E, 0xB3A9, 0x7545, + 0xB3AA, 0x5531, 0xB3AB, 0x5021, 0xB3AC, 0x8D85, 0xB3AD, 0x6284, 0xB3AE, 0x949E, 0xB3AF, 0x671D, 0xB3B0, 0x5632, 0xB3B1, 0x6F6E, + 0xB3B2, 0x5DE2, 0xB3B3, 0x5435, 0xB3B4, 0x7092, 0xB3B5, 0x8F66, 0xB3B6, 0x626F, 0xB3B7, 0x64A4, 0xB3B8, 0x63A3, 0xB3B9, 0x5F7B, + 0xB3BA, 0x6F88, 0xB3BB, 0x90F4, 0xB3BC, 0x81E3, 0xB3BD, 0x8FB0, 0xB3BE, 0x5C18, 0xB3BF, 0x6668, 0xB3C0, 0x5FF1, 0xB3C1, 0x6C89, + 0xB3C2, 0x9648, 0xB3C3, 0x8D81, 0xB3C4, 0x886C, 0xB3C5, 0x6491, 0xB3C6, 0x79F0, 0xB3C7, 0x57CE, 0xB3C8, 0x6A59, 0xB3C9, 0x6210, + 0xB3CA, 0x5448, 0xB3CB, 0x4E58, 0xB3CC, 0x7A0B, 0xB3CD, 0x60E9, 0xB3CE, 0x6F84, 0xB3CF, 0x8BDA, 0xB3D0, 0x627F, 0xB3D1, 0x901E, + 0xB3D2, 0x9A8B, 0xB3D3, 0x79E4, 0xB3D4, 0x5403, 0xB3D5, 0x75F4, 0xB3D6, 0x6301, 0xB3D7, 0x5319, 0xB3D8, 0x6C60, 0xB3D9, 0x8FDF, + 0xB3DA, 0x5F1B, 0xB3DB, 0x9A70, 0xB3DC, 0x803B, 0xB3DD, 0x9F7F, 0xB3DE, 0x4F88, 0xB3DF, 0x5C3A, 0xB3E0, 0x8D64, 0xB3E1, 0x7FC5, + 0xB3E2, 0x65A5, 0xB3E3, 0x70BD, 0xB3E4, 0x5145, 0xB3E5, 0x51B2, 0xB3E6, 0x866B, 0xB3E7, 0x5D07, 0xB3E8, 0x5BA0, 0xB3E9, 0x62BD, + 0xB3EA, 0x916C, 0xB3EB, 0x7574, 0xB3EC, 0x8E0C, 0xB3ED, 0x7A20, 0xB3EE, 0x6101, 0xB3EF, 0x7B79, 0xB3F0, 0x4EC7, 0xB3F1, 0x7EF8, + 0xB3F2, 0x7785, 0xB3F3, 0x4E11, 0xB3F4, 0x81ED, 0xB3F5, 0x521D, 0xB3F6, 0x51FA, 0xB3F7, 0x6A71, 0xB3F8, 0x53A8, 0xB3F9, 0x8E87, + 0xB3FA, 0x9504, 0xB3FB, 0x96CF, 0xB3FC, 0x6EC1, 0xB3FD, 0x9664, 0xB3FE, 0x695A, 0xB440, 0x7884, 0xB441, 0x7885, 0xB442, 0x7886, + 0xB443, 0x7888, 0xB444, 0x788A, 0xB445, 0x788B, 0xB446, 0x788F, 0xB447, 0x7890, 0xB448, 0x7892, 0xB449, 0x7894, 0xB44A, 0x7895, + 0xB44B, 0x7896, 0xB44C, 0x7899, 0xB44D, 0x789D, 0xB44E, 0x789E, 0xB44F, 0x78A0, 0xB450, 0x78A2, 0xB451, 0x78A4, 0xB452, 0x78A6, + 0xB453, 0x78A8, 0xB454, 0x78A9, 0xB455, 0x78AA, 0xB456, 0x78AB, 0xB457, 0x78AC, 0xB458, 0x78AD, 0xB459, 0x78AE, 0xB45A, 0x78AF, + 0xB45B, 0x78B5, 0xB45C, 0x78B6, 0xB45D, 0x78B7, 0xB45E, 0x78B8, 0xB45F, 0x78BA, 0xB460, 0x78BB, 0xB461, 0x78BC, 0xB462, 0x78BD, + 0xB463, 0x78BF, 0xB464, 0x78C0, 0xB465, 0x78C2, 0xB466, 0x78C3, 0xB467, 0x78C4, 0xB468, 0x78C6, 0xB469, 0x78C7, 0xB46A, 0x78C8, + 0xB46B, 0x78CC, 0xB46C, 0x78CD, 0xB46D, 0x78CE, 0xB46E, 0x78CF, 0xB46F, 0x78D1, 0xB470, 0x78D2, 0xB471, 0x78D3, 0xB472, 0x78D6, + 0xB473, 0x78D7, 0xB474, 0x78D8, 0xB475, 0x78DA, 0xB476, 0x78DB, 0xB477, 0x78DC, 0xB478, 0x78DD, 0xB479, 0x78DE, 0xB47A, 0x78DF, + 0xB47B, 0x78E0, 0xB47C, 0x78E1, 0xB47D, 0x78E2, 0xB47E, 0x78E3, 0xB480, 0x78E4, 0xB481, 0x78E5, 0xB482, 0x78E6, 0xB483, 0x78E7, + 0xB484, 0x78E9, 0xB485, 0x78EA, 0xB486, 0x78EB, 0xB487, 0x78ED, 0xB488, 0x78EE, 0xB489, 0x78EF, 0xB48A, 0x78F0, 0xB48B, 0x78F1, + 0xB48C, 0x78F3, 0xB48D, 0x78F5, 0xB48E, 0x78F6, 0xB48F, 0x78F8, 0xB490, 0x78F9, 0xB491, 0x78FB, 0xB492, 0x78FC, 0xB493, 0x78FD, + 0xB494, 0x78FE, 0xB495, 0x78FF, 0xB496, 0x7900, 0xB497, 0x7902, 0xB498, 0x7903, 0xB499, 0x7904, 0xB49A, 0x7906, 0xB49B, 0x7907, + 0xB49C, 0x7908, 0xB49D, 0x7909, 0xB49E, 0x790A, 0xB49F, 0x790B, 0xB4A0, 0x790C, 0xB4A1, 0x7840, 0xB4A2, 0x50A8, 0xB4A3, 0x77D7, + 0xB4A4, 0x6410, 0xB4A5, 0x89E6, 0xB4A6, 0x5904, 0xB4A7, 0x63E3, 0xB4A8, 0x5DDD, 0xB4A9, 0x7A7F, 0xB4AA, 0x693D, 0xB4AB, 0x4F20, + 0xB4AC, 0x8239, 0xB4AD, 0x5598, 0xB4AE, 0x4E32, 0xB4AF, 0x75AE, 0xB4B0, 0x7A97, 0xB4B1, 0x5E62, 0xB4B2, 0x5E8A, 0xB4B3, 0x95EF, + 0xB4B4, 0x521B, 0xB4B5, 0x5439, 0xB4B6, 0x708A, 0xB4B7, 0x6376, 0xB4B8, 0x9524, 0xB4B9, 0x5782, 0xB4BA, 0x6625, 0xB4BB, 0x693F, + 0xB4BC, 0x9187, 0xB4BD, 0x5507, 0xB4BE, 0x6DF3, 0xB4BF, 0x7EAF, 0xB4C0, 0x8822, 0xB4C1, 0x6233, 0xB4C2, 0x7EF0, 0xB4C3, 0x75B5, + 0xB4C4, 0x8328, 0xB4C5, 0x78C1, 0xB4C6, 0x96CC, 0xB4C7, 0x8F9E, 0xB4C8, 0x6148, 0xB4C9, 0x74F7, 0xB4CA, 0x8BCD, 0xB4CB, 0x6B64, + 0xB4CC, 0x523A, 0xB4CD, 0x8D50, 0xB4CE, 0x6B21, 0xB4CF, 0x806A, 0xB4D0, 0x8471, 0xB4D1, 0x56F1, 0xB4D2, 0x5306, 0xB4D3, 0x4ECE, + 0xB4D4, 0x4E1B, 0xB4D5, 0x51D1, 0xB4D6, 0x7C97, 0xB4D7, 0x918B, 0xB4D8, 0x7C07, 0xB4D9, 0x4FC3, 0xB4DA, 0x8E7F, 0xB4DB, 0x7BE1, + 0xB4DC, 0x7A9C, 0xB4DD, 0x6467, 0xB4DE, 0x5D14, 0xB4DF, 0x50AC, 0xB4E0, 0x8106, 0xB4E1, 0x7601, 0xB4E2, 0x7CB9, 0xB4E3, 0x6DEC, + 0xB4E4, 0x7FE0, 0xB4E5, 0x6751, 0xB4E6, 0x5B58, 0xB4E7, 0x5BF8, 0xB4E8, 0x78CB, 0xB4E9, 0x64AE, 0xB4EA, 0x6413, 0xB4EB, 0x63AA, + 0xB4EC, 0x632B, 0xB4ED, 0x9519, 0xB4EE, 0x642D, 0xB4EF, 0x8FBE, 0xB4F0, 0x7B54, 0xB4F1, 0x7629, 0xB4F2, 0x6253, 0xB4F3, 0x5927, + 0xB4F4, 0x5446, 0xB4F5, 0x6B79, 0xB4F6, 0x50A3, 0xB4F7, 0x6234, 0xB4F8, 0x5E26, 0xB4F9, 0x6B86, 0xB4FA, 0x4EE3, 0xB4FB, 0x8D37, + 0xB4FC, 0x888B, 0xB4FD, 0x5F85, 0xB4FE, 0x902E, 0xB540, 0x790D, 0xB541, 0x790E, 0xB542, 0x790F, 0xB543, 0x7910, 0xB544, 0x7911, + 0xB545, 0x7912, 0xB546, 0x7914, 0xB547, 0x7915, 0xB548, 0x7916, 0xB549, 0x7917, 0xB54A, 0x7918, 0xB54B, 0x7919, 0xB54C, 0x791A, + 0xB54D, 0x791B, 0xB54E, 0x791C, 0xB54F, 0x791D, 0xB550, 0x791F, 0xB551, 0x7920, 0xB552, 0x7921, 0xB553, 0x7922, 0xB554, 0x7923, + 0xB555, 0x7925, 0xB556, 0x7926, 0xB557, 0x7927, 0xB558, 0x7928, 0xB559, 0x7929, 0xB55A, 0x792A, 0xB55B, 0x792B, 0xB55C, 0x792C, + 0xB55D, 0x792D, 0xB55E, 0x792E, 0xB55F, 0x792F, 0xB560, 0x7930, 0xB561, 0x7931, 0xB562, 0x7932, 0xB563, 0x7933, 0xB564, 0x7935, + 0xB565, 0x7936, 0xB566, 0x7937, 0xB567, 0x7938, 0xB568, 0x7939, 0xB569, 0x793D, 0xB56A, 0x793F, 0xB56B, 0x7942, 0xB56C, 0x7943, + 0xB56D, 0x7944, 0xB56E, 0x7945, 0xB56F, 0x7947, 0xB570, 0x794A, 0xB571, 0x794B, 0xB572, 0x794C, 0xB573, 0x794D, 0xB574, 0x794E, + 0xB575, 0x794F, 0xB576, 0x7950, 0xB577, 0x7951, 0xB578, 0x7952, 0xB579, 0x7954, 0xB57A, 0x7955, 0xB57B, 0x7958, 0xB57C, 0x7959, + 0xB57D, 0x7961, 0xB57E, 0x7963, 0xB580, 0x7964, 0xB581, 0x7966, 0xB582, 0x7969, 0xB583, 0x796A, 0xB584, 0x796B, 0xB585, 0x796C, + 0xB586, 0x796E, 0xB587, 0x7970, 0xB588, 0x7971, 0xB589, 0x7972, 0xB58A, 0x7973, 0xB58B, 0x7974, 0xB58C, 0x7975, 0xB58D, 0x7976, + 0xB58E, 0x7979, 0xB58F, 0x797B, 0xB590, 0x797C, 0xB591, 0x797D, 0xB592, 0x797E, 0xB593, 0x797F, 0xB594, 0x7982, 0xB595, 0x7983, + 0xB596, 0x7986, 0xB597, 0x7987, 0xB598, 0x7988, 0xB599, 0x7989, 0xB59A, 0x798B, 0xB59B, 0x798C, 0xB59C, 0x798D, 0xB59D, 0x798E, + 0xB59E, 0x7990, 0xB59F, 0x7991, 0xB5A0, 0x7992, 0xB5A1, 0x6020, 0xB5A2, 0x803D, 0xB5A3, 0x62C5, 0xB5A4, 0x4E39, 0xB5A5, 0x5355, + 0xB5A6, 0x90F8, 0xB5A7, 0x63B8, 0xB5A8, 0x80C6, 0xB5A9, 0x65E6, 0xB5AA, 0x6C2E, 0xB5AB, 0x4F46, 0xB5AC, 0x60EE, 0xB5AD, 0x6DE1, + 0xB5AE, 0x8BDE, 0xB5AF, 0x5F39, 0xB5B0, 0x86CB, 0xB5B1, 0x5F53, 0xB5B2, 0x6321, 0xB5B3, 0x515A, 0xB5B4, 0x8361, 0xB5B5, 0x6863, + 0xB5B6, 0x5200, 0xB5B7, 0x6363, 0xB5B8, 0x8E48, 0xB5B9, 0x5012, 0xB5BA, 0x5C9B, 0xB5BB, 0x7977, 0xB5BC, 0x5BFC, 0xB5BD, 0x5230, + 0xB5BE, 0x7A3B, 0xB5BF, 0x60BC, 0xB5C0, 0x9053, 0xB5C1, 0x76D7, 0xB5C2, 0x5FB7, 0xB5C3, 0x5F97, 0xB5C4, 0x7684, 0xB5C5, 0x8E6C, + 0xB5C6, 0x706F, 0xB5C7, 0x767B, 0xB5C8, 0x7B49, 0xB5C9, 0x77AA, 0xB5CA, 0x51F3, 0xB5CB, 0x9093, 0xB5CC, 0x5824, 0xB5CD, 0x4F4E, + 0xB5CE, 0x6EF4, 0xB5CF, 0x8FEA, 0xB5D0, 0x654C, 0xB5D1, 0x7B1B, 0xB5D2, 0x72C4, 0xB5D3, 0x6DA4, 0xB5D4, 0x7FDF, 0xB5D5, 0x5AE1, + 0xB5D6, 0x62B5, 0xB5D7, 0x5E95, 0xB5D8, 0x5730, 0xB5D9, 0x8482, 0xB5DA, 0x7B2C, 0xB5DB, 0x5E1D, 0xB5DC, 0x5F1F, 0xB5DD, 0x9012, + 0xB5DE, 0x7F14, 0xB5DF, 0x98A0, 0xB5E0, 0x6382, 0xB5E1, 0x6EC7, 0xB5E2, 0x7898, 0xB5E3, 0x70B9, 0xB5E4, 0x5178, 0xB5E5, 0x975B, + 0xB5E6, 0x57AB, 0xB5E7, 0x7535, 0xB5E8, 0x4F43, 0xB5E9, 0x7538, 0xB5EA, 0x5E97, 0xB5EB, 0x60E6, 0xB5EC, 0x5960, 0xB5ED, 0x6DC0, + 0xB5EE, 0x6BBF, 0xB5EF, 0x7889, 0xB5F0, 0x53FC, 0xB5F1, 0x96D5, 0xB5F2, 0x51CB, 0xB5F3, 0x5201, 0xB5F4, 0x6389, 0xB5F5, 0x540A, + 0xB5F6, 0x9493, 0xB5F7, 0x8C03, 0xB5F8, 0x8DCC, 0xB5F9, 0x7239, 0xB5FA, 0x789F, 0xB5FB, 0x8776, 0xB5FC, 0x8FED, 0xB5FD, 0x8C0D, + 0xB5FE, 0x53E0, 0xB640, 0x7993, 0xB641, 0x7994, 0xB642, 0x7995, 0xB643, 0x7996, 0xB644, 0x7997, 0xB645, 0x7998, 0xB646, 0x7999, + 0xB647, 0x799B, 0xB648, 0x799C, 0xB649, 0x799D, 0xB64A, 0x799E, 0xB64B, 0x799F, 0xB64C, 0x79A0, 0xB64D, 0x79A1, 0xB64E, 0x79A2, + 0xB64F, 0x79A3, 0xB650, 0x79A4, 0xB651, 0x79A5, 0xB652, 0x79A6, 0xB653, 0x79A8, 0xB654, 0x79A9, 0xB655, 0x79AA, 0xB656, 0x79AB, + 0xB657, 0x79AC, 0xB658, 0x79AD, 0xB659, 0x79AE, 0xB65A, 0x79AF, 0xB65B, 0x79B0, 0xB65C, 0x79B1, 0xB65D, 0x79B2, 0xB65E, 0x79B4, + 0xB65F, 0x79B5, 0xB660, 0x79B6, 0xB661, 0x79B7, 0xB662, 0x79B8, 0xB663, 0x79BC, 0xB664, 0x79BF, 0xB665, 0x79C2, 0xB666, 0x79C4, + 0xB667, 0x79C5, 0xB668, 0x79C7, 0xB669, 0x79C8, 0xB66A, 0x79CA, 0xB66B, 0x79CC, 0xB66C, 0x79CE, 0xB66D, 0x79CF, 0xB66E, 0x79D0, + 0xB66F, 0x79D3, 0xB670, 0x79D4, 0xB671, 0x79D6, 0xB672, 0x79D7, 0xB673, 0x79D9, 0xB674, 0x79DA, 0xB675, 0x79DB, 0xB676, 0x79DC, + 0xB677, 0x79DD, 0xB678, 0x79DE, 0xB679, 0x79E0, 0xB67A, 0x79E1, 0xB67B, 0x79E2, 0xB67C, 0x79E5, 0xB67D, 0x79E8, 0xB67E, 0x79EA, + 0xB680, 0x79EC, 0xB681, 0x79EE, 0xB682, 0x79F1, 0xB683, 0x79F2, 0xB684, 0x79F3, 0xB685, 0x79F4, 0xB686, 0x79F5, 0xB687, 0x79F6, + 0xB688, 0x79F7, 0xB689, 0x79F9, 0xB68A, 0x79FA, 0xB68B, 0x79FC, 0xB68C, 0x79FE, 0xB68D, 0x79FF, 0xB68E, 0x7A01, 0xB68F, 0x7A04, + 0xB690, 0x7A05, 0xB691, 0x7A07, 0xB692, 0x7A08, 0xB693, 0x7A09, 0xB694, 0x7A0A, 0xB695, 0x7A0C, 0xB696, 0x7A0F, 0xB697, 0x7A10, + 0xB698, 0x7A11, 0xB699, 0x7A12, 0xB69A, 0x7A13, 0xB69B, 0x7A15, 0xB69C, 0x7A16, 0xB69D, 0x7A18, 0xB69E, 0x7A19, 0xB69F, 0x7A1B, + 0xB6A0, 0x7A1C, 0xB6A1, 0x4E01, 0xB6A2, 0x76EF, 0xB6A3, 0x53EE, 0xB6A4, 0x9489, 0xB6A5, 0x9876, 0xB6A6, 0x9F0E, 0xB6A7, 0x952D, + 0xB6A8, 0x5B9A, 0xB6A9, 0x8BA2, 0xB6AA, 0x4E22, 0xB6AB, 0x4E1C, 0xB6AC, 0x51AC, 0xB6AD, 0x8463, 0xB6AE, 0x61C2, 0xB6AF, 0x52A8, + 0xB6B0, 0x680B, 0xB6B1, 0x4F97, 0xB6B2, 0x606B, 0xB6B3, 0x51BB, 0xB6B4, 0x6D1E, 0xB6B5, 0x515C, 0xB6B6, 0x6296, 0xB6B7, 0x6597, + 0xB6B8, 0x9661, 0xB6B9, 0x8C46, 0xB6BA, 0x9017, 0xB6BB, 0x75D8, 0xB6BC, 0x90FD, 0xB6BD, 0x7763, 0xB6BE, 0x6BD2, 0xB6BF, 0x728A, + 0xB6C0, 0x72EC, 0xB6C1, 0x8BFB, 0xB6C2, 0x5835, 0xB6C3, 0x7779, 0xB6C4, 0x8D4C, 0xB6C5, 0x675C, 0xB6C6, 0x9540, 0xB6C7, 0x809A, + 0xB6C8, 0x5EA6, 0xB6C9, 0x6E21, 0xB6CA, 0x5992, 0xB6CB, 0x7AEF, 0xB6CC, 0x77ED, 0xB6CD, 0x953B, 0xB6CE, 0x6BB5, 0xB6CF, 0x65AD, + 0xB6D0, 0x7F0E, 0xB6D1, 0x5806, 0xB6D2, 0x5151, 0xB6D3, 0x961F, 0xB6D4, 0x5BF9, 0xB6D5, 0x58A9, 0xB6D6, 0x5428, 0xB6D7, 0x8E72, + 0xB6D8, 0x6566, 0xB6D9, 0x987F, 0xB6DA, 0x56E4, 0xB6DB, 0x949D, 0xB6DC, 0x76FE, 0xB6DD, 0x9041, 0xB6DE, 0x6387, 0xB6DF, 0x54C6, + 0xB6E0, 0x591A, 0xB6E1, 0x593A, 0xB6E2, 0x579B, 0xB6E3, 0x8EB2, 0xB6E4, 0x6735, 0xB6E5, 0x8DFA, 0xB6E6, 0x8235, 0xB6E7, 0x5241, + 0xB6E8, 0x60F0, 0xB6E9, 0x5815, 0xB6EA, 0x86FE, 0xB6EB, 0x5CE8, 0xB6EC, 0x9E45, 0xB6ED, 0x4FC4, 0xB6EE, 0x989D, 0xB6EF, 0x8BB9, + 0xB6F0, 0x5A25, 0xB6F1, 0x6076, 0xB6F2, 0x5384, 0xB6F3, 0x627C, 0xB6F4, 0x904F, 0xB6F5, 0x9102, 0xB6F6, 0x997F, 0xB6F7, 0x6069, + 0xB6F8, 0x800C, 0xB6F9, 0x513F, 0xB6FA, 0x8033, 0xB6FB, 0x5C14, 0xB6FC, 0x9975, 0xB6FD, 0x6D31, 0xB6FE, 0x4E8C, 0xB740, 0x7A1D, + 0xB741, 0x7A1F, 0xB742, 0x7A21, 0xB743, 0x7A22, 0xB744, 0x7A24, 0xB745, 0x7A25, 0xB746, 0x7A26, 0xB747, 0x7A27, 0xB748, 0x7A28, + 0xB749, 0x7A29, 0xB74A, 0x7A2A, 0xB74B, 0x7A2B, 0xB74C, 0x7A2C, 0xB74D, 0x7A2D, 0xB74E, 0x7A2E, 0xB74F, 0x7A2F, 0xB750, 0x7A30, + 0xB751, 0x7A31, 0xB752, 0x7A32, 0xB753, 0x7A34, 0xB754, 0x7A35, 0xB755, 0x7A36, 0xB756, 0x7A38, 0xB757, 0x7A3A, 0xB758, 0x7A3E, + 0xB759, 0x7A40, 0xB75A, 0x7A41, 0xB75B, 0x7A42, 0xB75C, 0x7A43, 0xB75D, 0x7A44, 0xB75E, 0x7A45, 0xB75F, 0x7A47, 0xB760, 0x7A48, + 0xB761, 0x7A49, 0xB762, 0x7A4A, 0xB763, 0x7A4B, 0xB764, 0x7A4C, 0xB765, 0x7A4D, 0xB766, 0x7A4E, 0xB767, 0x7A4F, 0xB768, 0x7A50, + 0xB769, 0x7A52, 0xB76A, 0x7A53, 0xB76B, 0x7A54, 0xB76C, 0x7A55, 0xB76D, 0x7A56, 0xB76E, 0x7A58, 0xB76F, 0x7A59, 0xB770, 0x7A5A, + 0xB771, 0x7A5B, 0xB772, 0x7A5C, 0xB773, 0x7A5D, 0xB774, 0x7A5E, 0xB775, 0x7A5F, 0xB776, 0x7A60, 0xB777, 0x7A61, 0xB778, 0x7A62, + 0xB779, 0x7A63, 0xB77A, 0x7A64, 0xB77B, 0x7A65, 0xB77C, 0x7A66, 0xB77D, 0x7A67, 0xB77E, 0x7A68, 0xB780, 0x7A69, 0xB781, 0x7A6A, + 0xB782, 0x7A6B, 0xB783, 0x7A6C, 0xB784, 0x7A6D, 0xB785, 0x7A6E, 0xB786, 0x7A6F, 0xB787, 0x7A71, 0xB788, 0x7A72, 0xB789, 0x7A73, + 0xB78A, 0x7A75, 0xB78B, 0x7A7B, 0xB78C, 0x7A7C, 0xB78D, 0x7A7D, 0xB78E, 0x7A7E, 0xB78F, 0x7A82, 0xB790, 0x7A85, 0xB791, 0x7A87, + 0xB792, 0x7A89, 0xB793, 0x7A8A, 0xB794, 0x7A8B, 0xB795, 0x7A8C, 0xB796, 0x7A8E, 0xB797, 0x7A8F, 0xB798, 0x7A90, 0xB799, 0x7A93, + 0xB79A, 0x7A94, 0xB79B, 0x7A99, 0xB79C, 0x7A9A, 0xB79D, 0x7A9B, 0xB79E, 0x7A9E, 0xB79F, 0x7AA1, 0xB7A0, 0x7AA2, 0xB7A1, 0x8D30, + 0xB7A2, 0x53D1, 0xB7A3, 0x7F5A, 0xB7A4, 0x7B4F, 0xB7A5, 0x4F10, 0xB7A6, 0x4E4F, 0xB7A7, 0x9600, 0xB7A8, 0x6CD5, 0xB7A9, 0x73D0, + 0xB7AA, 0x85E9, 0xB7AB, 0x5E06, 0xB7AC, 0x756A, 0xB7AD, 0x7FFB, 0xB7AE, 0x6A0A, 0xB7AF, 0x77FE, 0xB7B0, 0x9492, 0xB7B1, 0x7E41, + 0xB7B2, 0x51E1, 0xB7B3, 0x70E6, 0xB7B4, 0x53CD, 0xB7B5, 0x8FD4, 0xB7B6, 0x8303, 0xB7B7, 0x8D29, 0xB7B8, 0x72AF, 0xB7B9, 0x996D, + 0xB7BA, 0x6CDB, 0xB7BB, 0x574A, 0xB7BC, 0x82B3, 0xB7BD, 0x65B9, 0xB7BE, 0x80AA, 0xB7BF, 0x623F, 0xB7C0, 0x9632, 0xB7C1, 0x59A8, + 0xB7C2, 0x4EFF, 0xB7C3, 0x8BBF, 0xB7C4, 0x7EBA, 0xB7C5, 0x653E, 0xB7C6, 0x83F2, 0xB7C7, 0x975E, 0xB7C8, 0x5561, 0xB7C9, 0x98DE, + 0xB7CA, 0x80A5, 0xB7CB, 0x532A, 0xB7CC, 0x8BFD, 0xB7CD, 0x5420, 0xB7CE, 0x80BA, 0xB7CF, 0x5E9F, 0xB7D0, 0x6CB8, 0xB7D1, 0x8D39, + 0xB7D2, 0x82AC, 0xB7D3, 0x915A, 0xB7D4, 0x5429, 0xB7D5, 0x6C1B, 0xB7D6, 0x5206, 0xB7D7, 0x7EB7, 0xB7D8, 0x575F, 0xB7D9, 0x711A, + 0xB7DA, 0x6C7E, 0xB7DB, 0x7C89, 0xB7DC, 0x594B, 0xB7DD, 0x4EFD, 0xB7DE, 0x5FFF, 0xB7DF, 0x6124, 0xB7E0, 0x7CAA, 0xB7E1, 0x4E30, + 0xB7E2, 0x5C01, 0xB7E3, 0x67AB, 0xB7E4, 0x8702, 0xB7E5, 0x5CF0, 0xB7E6, 0x950B, 0xB7E7, 0x98CE, 0xB7E8, 0x75AF, 0xB7E9, 0x70FD, + 0xB7EA, 0x9022, 0xB7EB, 0x51AF, 0xB7EC, 0x7F1D, 0xB7ED, 0x8BBD, 0xB7EE, 0x5949, 0xB7EF, 0x51E4, 0xB7F0, 0x4F5B, 0xB7F1, 0x5426, + 0xB7F2, 0x592B, 0xB7F3, 0x6577, 0xB7F4, 0x80A4, 0xB7F5, 0x5B75, 0xB7F6, 0x6276, 0xB7F7, 0x62C2, 0xB7F8, 0x8F90, 0xB7F9, 0x5E45, + 0xB7FA, 0x6C1F, 0xB7FB, 0x7B26, 0xB7FC, 0x4F0F, 0xB7FD, 0x4FD8, 0xB7FE, 0x670D, 0xB840, 0x7AA3, 0xB841, 0x7AA4, 0xB842, 0x7AA7, + 0xB843, 0x7AA9, 0xB844, 0x7AAA, 0xB845, 0x7AAB, 0xB846, 0x7AAE, 0xB847, 0x7AAF, 0xB848, 0x7AB0, 0xB849, 0x7AB1, 0xB84A, 0x7AB2, + 0xB84B, 0x7AB4, 0xB84C, 0x7AB5, 0xB84D, 0x7AB6, 0xB84E, 0x7AB7, 0xB84F, 0x7AB8, 0xB850, 0x7AB9, 0xB851, 0x7ABA, 0xB852, 0x7ABB, + 0xB853, 0x7ABC, 0xB854, 0x7ABD, 0xB855, 0x7ABE, 0xB856, 0x7AC0, 0xB857, 0x7AC1, 0xB858, 0x7AC2, 0xB859, 0x7AC3, 0xB85A, 0x7AC4, + 0xB85B, 0x7AC5, 0xB85C, 0x7AC6, 0xB85D, 0x7AC7, 0xB85E, 0x7AC8, 0xB85F, 0x7AC9, 0xB860, 0x7ACA, 0xB861, 0x7ACC, 0xB862, 0x7ACD, + 0xB863, 0x7ACE, 0xB864, 0x7ACF, 0xB865, 0x7AD0, 0xB866, 0x7AD1, 0xB867, 0x7AD2, 0xB868, 0x7AD3, 0xB869, 0x7AD4, 0xB86A, 0x7AD5, + 0xB86B, 0x7AD7, 0xB86C, 0x7AD8, 0xB86D, 0x7ADA, 0xB86E, 0x7ADB, 0xB86F, 0x7ADC, 0xB870, 0x7ADD, 0xB871, 0x7AE1, 0xB872, 0x7AE2, + 0xB873, 0x7AE4, 0xB874, 0x7AE7, 0xB875, 0x7AE8, 0xB876, 0x7AE9, 0xB877, 0x7AEA, 0xB878, 0x7AEB, 0xB879, 0x7AEC, 0xB87A, 0x7AEE, + 0xB87B, 0x7AF0, 0xB87C, 0x7AF1, 0xB87D, 0x7AF2, 0xB87E, 0x7AF3, 0xB880, 0x7AF4, 0xB881, 0x7AF5, 0xB882, 0x7AF6, 0xB883, 0x7AF7, + 0xB884, 0x7AF8, 0xB885, 0x7AFB, 0xB886, 0x7AFC, 0xB887, 0x7AFE, 0xB888, 0x7B00, 0xB889, 0x7B01, 0xB88A, 0x7B02, 0xB88B, 0x7B05, + 0xB88C, 0x7B07, 0xB88D, 0x7B09, 0xB88E, 0x7B0C, 0xB88F, 0x7B0D, 0xB890, 0x7B0E, 0xB891, 0x7B10, 0xB892, 0x7B12, 0xB893, 0x7B13, + 0xB894, 0x7B16, 0xB895, 0x7B17, 0xB896, 0x7B18, 0xB897, 0x7B1A, 0xB898, 0x7B1C, 0xB899, 0x7B1D, 0xB89A, 0x7B1F, 0xB89B, 0x7B21, + 0xB89C, 0x7B22, 0xB89D, 0x7B23, 0xB89E, 0x7B27, 0xB89F, 0x7B29, 0xB8A0, 0x7B2D, 0xB8A1, 0x6D6E, 0xB8A2, 0x6DAA, 0xB8A3, 0x798F, + 0xB8A4, 0x88B1, 0xB8A5, 0x5F17, 0xB8A6, 0x752B, 0xB8A7, 0x629A, 0xB8A8, 0x8F85, 0xB8A9, 0x4FEF, 0xB8AA, 0x91DC, 0xB8AB, 0x65A7, + 0xB8AC, 0x812F, 0xB8AD, 0x8151, 0xB8AE, 0x5E9C, 0xB8AF, 0x8150, 0xB8B0, 0x8D74, 0xB8B1, 0x526F, 0xB8B2, 0x8986, 0xB8B3, 0x8D4B, + 0xB8B4, 0x590D, 0xB8B5, 0x5085, 0xB8B6, 0x4ED8, 0xB8B7, 0x961C, 0xB8B8, 0x7236, 0xB8B9, 0x8179, 0xB8BA, 0x8D1F, 0xB8BB, 0x5BCC, + 0xB8BC, 0x8BA3, 0xB8BD, 0x9644, 0xB8BE, 0x5987, 0xB8BF, 0x7F1A, 0xB8C0, 0x5490, 0xB8C1, 0x5676, 0xB8C2, 0x560E, 0xB8C3, 0x8BE5, + 0xB8C4, 0x6539, 0xB8C5, 0x6982, 0xB8C6, 0x9499, 0xB8C7, 0x76D6, 0xB8C8, 0x6E89, 0xB8C9, 0x5E72, 0xB8CA, 0x7518, 0xB8CB, 0x6746, + 0xB8CC, 0x67D1, 0xB8CD, 0x7AFF, 0xB8CE, 0x809D, 0xB8CF, 0x8D76, 0xB8D0, 0x611F, 0xB8D1, 0x79C6, 0xB8D2, 0x6562, 0xB8D3, 0x8D63, + 0xB8D4, 0x5188, 0xB8D5, 0x521A, 0xB8D6, 0x94A2, 0xB8D7, 0x7F38, 0xB8D8, 0x809B, 0xB8D9, 0x7EB2, 0xB8DA, 0x5C97, 0xB8DB, 0x6E2F, + 0xB8DC, 0x6760, 0xB8DD, 0x7BD9, 0xB8DE, 0x768B, 0xB8DF, 0x9AD8, 0xB8E0, 0x818F, 0xB8E1, 0x7F94, 0xB8E2, 0x7CD5, 0xB8E3, 0x641E, + 0xB8E4, 0x9550, 0xB8E5, 0x7A3F, 0xB8E6, 0x544A, 0xB8E7, 0x54E5, 0xB8E8, 0x6B4C, 0xB8E9, 0x6401, 0xB8EA, 0x6208, 0xB8EB, 0x9E3D, + 0xB8EC, 0x80F3, 0xB8ED, 0x7599, 0xB8EE, 0x5272, 0xB8EF, 0x9769, 0xB8F0, 0x845B, 0xB8F1, 0x683C, 0xB8F2, 0x86E4, 0xB8F3, 0x9601, + 0xB8F4, 0x9694, 0xB8F5, 0x94EC, 0xB8F6, 0x4E2A, 0xB8F7, 0x5404, 0xB8F8, 0x7ED9, 0xB8F9, 0x6839, 0xB8FA, 0x8DDF, 0xB8FB, 0x8015, + 0xB8FC, 0x66F4, 0xB8FD, 0x5E9A, 0xB8FE, 0x7FB9, 0xB940, 0x7B2F, 0xB941, 0x7B30, 0xB942, 0x7B32, 0xB943, 0x7B34, 0xB944, 0x7B35, + 0xB945, 0x7B36, 0xB946, 0x7B37, 0xB947, 0x7B39, 0xB948, 0x7B3B, 0xB949, 0x7B3D, 0xB94A, 0x7B3F, 0xB94B, 0x7B40, 0xB94C, 0x7B41, + 0xB94D, 0x7B42, 0xB94E, 0x7B43, 0xB94F, 0x7B44, 0xB950, 0x7B46, 0xB951, 0x7B48, 0xB952, 0x7B4A, 0xB953, 0x7B4D, 0xB954, 0x7B4E, + 0xB955, 0x7B53, 0xB956, 0x7B55, 0xB957, 0x7B57, 0xB958, 0x7B59, 0xB959, 0x7B5C, 0xB95A, 0x7B5E, 0xB95B, 0x7B5F, 0xB95C, 0x7B61, + 0xB95D, 0x7B63, 0xB95E, 0x7B64, 0xB95F, 0x7B65, 0xB960, 0x7B66, 0xB961, 0x7B67, 0xB962, 0x7B68, 0xB963, 0x7B69, 0xB964, 0x7B6A, + 0xB965, 0x7B6B, 0xB966, 0x7B6C, 0xB967, 0x7B6D, 0xB968, 0x7B6F, 0xB969, 0x7B70, 0xB96A, 0x7B73, 0xB96B, 0x7B74, 0xB96C, 0x7B76, + 0xB96D, 0x7B78, 0xB96E, 0x7B7A, 0xB96F, 0x7B7C, 0xB970, 0x7B7D, 0xB971, 0x7B7F, 0xB972, 0x7B81, 0xB973, 0x7B82, 0xB974, 0x7B83, + 0xB975, 0x7B84, 0xB976, 0x7B86, 0xB977, 0x7B87, 0xB978, 0x7B88, 0xB979, 0x7B89, 0xB97A, 0x7B8A, 0xB97B, 0x7B8B, 0xB97C, 0x7B8C, + 0xB97D, 0x7B8E, 0xB97E, 0x7B8F, 0xB980, 0x7B91, 0xB981, 0x7B92, 0xB982, 0x7B93, 0xB983, 0x7B96, 0xB984, 0x7B98, 0xB985, 0x7B99, + 0xB986, 0x7B9A, 0xB987, 0x7B9B, 0xB988, 0x7B9E, 0xB989, 0x7B9F, 0xB98A, 0x7BA0, 0xB98B, 0x7BA3, 0xB98C, 0x7BA4, 0xB98D, 0x7BA5, + 0xB98E, 0x7BAE, 0xB98F, 0x7BAF, 0xB990, 0x7BB0, 0xB991, 0x7BB2, 0xB992, 0x7BB3, 0xB993, 0x7BB5, 0xB994, 0x7BB6, 0xB995, 0x7BB7, + 0xB996, 0x7BB9, 0xB997, 0x7BBA, 0xB998, 0x7BBB, 0xB999, 0x7BBC, 0xB99A, 0x7BBD, 0xB99B, 0x7BBE, 0xB99C, 0x7BBF, 0xB99D, 0x7BC0, + 0xB99E, 0x7BC2, 0xB99F, 0x7BC3, 0xB9A0, 0x7BC4, 0xB9A1, 0x57C2, 0xB9A2, 0x803F, 0xB9A3, 0x6897, 0xB9A4, 0x5DE5, 0xB9A5, 0x653B, + 0xB9A6, 0x529F, 0xB9A7, 0x606D, 0xB9A8, 0x9F9A, 0xB9A9, 0x4F9B, 0xB9AA, 0x8EAC, 0xB9AB, 0x516C, 0xB9AC, 0x5BAB, 0xB9AD, 0x5F13, + 0xB9AE, 0x5DE9, 0xB9AF, 0x6C5E, 0xB9B0, 0x62F1, 0xB9B1, 0x8D21, 0xB9B2, 0x5171, 0xB9B3, 0x94A9, 0xB9B4, 0x52FE, 0xB9B5, 0x6C9F, + 0xB9B6, 0x82DF, 0xB9B7, 0x72D7, 0xB9B8, 0x57A2, 0xB9B9, 0x6784, 0xB9BA, 0x8D2D, 0xB9BB, 0x591F, 0xB9BC, 0x8F9C, 0xB9BD, 0x83C7, + 0xB9BE, 0x5495, 0xB9BF, 0x7B8D, 0xB9C0, 0x4F30, 0xB9C1, 0x6CBD, 0xB9C2, 0x5B64, 0xB9C3, 0x59D1, 0xB9C4, 0x9F13, 0xB9C5, 0x53E4, + 0xB9C6, 0x86CA, 0xB9C7, 0x9AA8, 0xB9C8, 0x8C37, 0xB9C9, 0x80A1, 0xB9CA, 0x6545, 0xB9CB, 0x987E, 0xB9CC, 0x56FA, 0xB9CD, 0x96C7, + 0xB9CE, 0x522E, 0xB9CF, 0x74DC, 0xB9D0, 0x5250, 0xB9D1, 0x5BE1, 0xB9D2, 0x6302, 0xB9D3, 0x8902, 0xB9D4, 0x4E56, 0xB9D5, 0x62D0, + 0xB9D6, 0x602A, 0xB9D7, 0x68FA, 0xB9D8, 0x5173, 0xB9D9, 0x5B98, 0xB9DA, 0x51A0, 0xB9DB, 0x89C2, 0xB9DC, 0x7BA1, 0xB9DD, 0x9986, + 0xB9DE, 0x7F50, 0xB9DF, 0x60EF, 0xB9E0, 0x704C, 0xB9E1, 0x8D2F, 0xB9E2, 0x5149, 0xB9E3, 0x5E7F, 0xB9E4, 0x901B, 0xB9E5, 0x7470, + 0xB9E6, 0x89C4, 0xB9E7, 0x572D, 0xB9E8, 0x7845, 0xB9E9, 0x5F52, 0xB9EA, 0x9F9F, 0xB9EB, 0x95FA, 0xB9EC, 0x8F68, 0xB9ED, 0x9B3C, + 0xB9EE, 0x8BE1, 0xB9EF, 0x7678, 0xB9F0, 0x6842, 0xB9F1, 0x67DC, 0xB9F2, 0x8DEA, 0xB9F3, 0x8D35, 0xB9F4, 0x523D, 0xB9F5, 0x8F8A, + 0xB9F6, 0x6EDA, 0xB9F7, 0x68CD, 0xB9F8, 0x9505, 0xB9F9, 0x90ED, 0xB9FA, 0x56FD, 0xB9FB, 0x679C, 0xB9FC, 0x88F9, 0xB9FD, 0x8FC7, + 0xB9FE, 0x54C8, 0xBA40, 0x7BC5, 0xBA41, 0x7BC8, 0xBA42, 0x7BC9, 0xBA43, 0x7BCA, 0xBA44, 0x7BCB, 0xBA45, 0x7BCD, 0xBA46, 0x7BCE, + 0xBA47, 0x7BCF, 0xBA48, 0x7BD0, 0xBA49, 0x7BD2, 0xBA4A, 0x7BD4, 0xBA4B, 0x7BD5, 0xBA4C, 0x7BD6, 0xBA4D, 0x7BD7, 0xBA4E, 0x7BD8, + 0xBA4F, 0x7BDB, 0xBA50, 0x7BDC, 0xBA51, 0x7BDE, 0xBA52, 0x7BDF, 0xBA53, 0x7BE0, 0xBA54, 0x7BE2, 0xBA55, 0x7BE3, 0xBA56, 0x7BE4, + 0xBA57, 0x7BE7, 0xBA58, 0x7BE8, 0xBA59, 0x7BE9, 0xBA5A, 0x7BEB, 0xBA5B, 0x7BEC, 0xBA5C, 0x7BED, 0xBA5D, 0x7BEF, 0xBA5E, 0x7BF0, + 0xBA5F, 0x7BF2, 0xBA60, 0x7BF3, 0xBA61, 0x7BF4, 0xBA62, 0x7BF5, 0xBA63, 0x7BF6, 0xBA64, 0x7BF8, 0xBA65, 0x7BF9, 0xBA66, 0x7BFA, + 0xBA67, 0x7BFB, 0xBA68, 0x7BFD, 0xBA69, 0x7BFF, 0xBA6A, 0x7C00, 0xBA6B, 0x7C01, 0xBA6C, 0x7C02, 0xBA6D, 0x7C03, 0xBA6E, 0x7C04, + 0xBA6F, 0x7C05, 0xBA70, 0x7C06, 0xBA71, 0x7C08, 0xBA72, 0x7C09, 0xBA73, 0x7C0A, 0xBA74, 0x7C0D, 0xBA75, 0x7C0E, 0xBA76, 0x7C10, + 0xBA77, 0x7C11, 0xBA78, 0x7C12, 0xBA79, 0x7C13, 0xBA7A, 0x7C14, 0xBA7B, 0x7C15, 0xBA7C, 0x7C17, 0xBA7D, 0x7C18, 0xBA7E, 0x7C19, + 0xBA80, 0x7C1A, 0xBA81, 0x7C1B, 0xBA82, 0x7C1C, 0xBA83, 0x7C1D, 0xBA84, 0x7C1E, 0xBA85, 0x7C20, 0xBA86, 0x7C21, 0xBA87, 0x7C22, + 0xBA88, 0x7C23, 0xBA89, 0x7C24, 0xBA8A, 0x7C25, 0xBA8B, 0x7C28, 0xBA8C, 0x7C29, 0xBA8D, 0x7C2B, 0xBA8E, 0x7C2C, 0xBA8F, 0x7C2D, + 0xBA90, 0x7C2E, 0xBA91, 0x7C2F, 0xBA92, 0x7C30, 0xBA93, 0x7C31, 0xBA94, 0x7C32, 0xBA95, 0x7C33, 0xBA96, 0x7C34, 0xBA97, 0x7C35, + 0xBA98, 0x7C36, 0xBA99, 0x7C37, 0xBA9A, 0x7C39, 0xBA9B, 0x7C3A, 0xBA9C, 0x7C3B, 0xBA9D, 0x7C3C, 0xBA9E, 0x7C3D, 0xBA9F, 0x7C3E, + 0xBAA0, 0x7C42, 0xBAA1, 0x9AB8, 0xBAA2, 0x5B69, 0xBAA3, 0x6D77, 0xBAA4, 0x6C26, 0xBAA5, 0x4EA5, 0xBAA6, 0x5BB3, 0xBAA7, 0x9A87, + 0xBAA8, 0x9163, 0xBAA9, 0x61A8, 0xBAAA, 0x90AF, 0xBAAB, 0x97E9, 0xBAAC, 0x542B, 0xBAAD, 0x6DB5, 0xBAAE, 0x5BD2, 0xBAAF, 0x51FD, + 0xBAB0, 0x558A, 0xBAB1, 0x7F55, 0xBAB2, 0x7FF0, 0xBAB3, 0x64BC, 0xBAB4, 0x634D, 0xBAB5, 0x65F1, 0xBAB6, 0x61BE, 0xBAB7, 0x608D, + 0xBAB8, 0x710A, 0xBAB9, 0x6C57, 0xBABA, 0x6C49, 0xBABB, 0x592F, 0xBABC, 0x676D, 0xBABD, 0x822A, 0xBABE, 0x58D5, 0xBABF, 0x568E, + 0xBAC0, 0x8C6A, 0xBAC1, 0x6BEB, 0xBAC2, 0x90DD, 0xBAC3, 0x597D, 0xBAC4, 0x8017, 0xBAC5, 0x53F7, 0xBAC6, 0x6D69, 0xBAC7, 0x5475, + 0xBAC8, 0x559D, 0xBAC9, 0x8377, 0xBACA, 0x83CF, 0xBACB, 0x6838, 0xBACC, 0x79BE, 0xBACD, 0x548C, 0xBACE, 0x4F55, 0xBACF, 0x5408, + 0xBAD0, 0x76D2, 0xBAD1, 0x8C89, 0xBAD2, 0x9602, 0xBAD3, 0x6CB3, 0xBAD4, 0x6DB8, 0xBAD5, 0x8D6B, 0xBAD6, 0x8910, 0xBAD7, 0x9E64, + 0xBAD8, 0x8D3A, 0xBAD9, 0x563F, 0xBADA, 0x9ED1, 0xBADB, 0x75D5, 0xBADC, 0x5F88, 0xBADD, 0x72E0, 0xBADE, 0x6068, 0xBADF, 0x54FC, + 0xBAE0, 0x4EA8, 0xBAE1, 0x6A2A, 0xBAE2, 0x8861, 0xBAE3, 0x6052, 0xBAE4, 0x8F70, 0xBAE5, 0x54C4, 0xBAE6, 0x70D8, 0xBAE7, 0x8679, + 0xBAE8, 0x9E3F, 0xBAE9, 0x6D2A, 0xBAEA, 0x5B8F, 0xBAEB, 0x5F18, 0xBAEC, 0x7EA2, 0xBAED, 0x5589, 0xBAEE, 0x4FAF, 0xBAEF, 0x7334, + 0xBAF0, 0x543C, 0xBAF1, 0x539A, 0xBAF2, 0x5019, 0xBAF3, 0x540E, 0xBAF4, 0x547C, 0xBAF5, 0x4E4E, 0xBAF6, 0x5FFD, 0xBAF7, 0x745A, + 0xBAF8, 0x58F6, 0xBAF9, 0x846B, 0xBAFA, 0x80E1, 0xBAFB, 0x8774, 0xBAFC, 0x72D0, 0xBAFD, 0x7CCA, 0xBAFE, 0x6E56, 0xBB40, 0x7C43, + 0xBB41, 0x7C44, 0xBB42, 0x7C45, 0xBB43, 0x7C46, 0xBB44, 0x7C47, 0xBB45, 0x7C48, 0xBB46, 0x7C49, 0xBB47, 0x7C4A, 0xBB48, 0x7C4B, + 0xBB49, 0x7C4C, 0xBB4A, 0x7C4E, 0xBB4B, 0x7C4F, 0xBB4C, 0x7C50, 0xBB4D, 0x7C51, 0xBB4E, 0x7C52, 0xBB4F, 0x7C53, 0xBB50, 0x7C54, + 0xBB51, 0x7C55, 0xBB52, 0x7C56, 0xBB53, 0x7C57, 0xBB54, 0x7C58, 0xBB55, 0x7C59, 0xBB56, 0x7C5A, 0xBB57, 0x7C5B, 0xBB58, 0x7C5C, + 0xBB59, 0x7C5D, 0xBB5A, 0x7C5E, 0xBB5B, 0x7C5F, 0xBB5C, 0x7C60, 0xBB5D, 0x7C61, 0xBB5E, 0x7C62, 0xBB5F, 0x7C63, 0xBB60, 0x7C64, + 0xBB61, 0x7C65, 0xBB62, 0x7C66, 0xBB63, 0x7C67, 0xBB64, 0x7C68, 0xBB65, 0x7C69, 0xBB66, 0x7C6A, 0xBB67, 0x7C6B, 0xBB68, 0x7C6C, + 0xBB69, 0x7C6D, 0xBB6A, 0x7C6E, 0xBB6B, 0x7C6F, 0xBB6C, 0x7C70, 0xBB6D, 0x7C71, 0xBB6E, 0x7C72, 0xBB6F, 0x7C75, 0xBB70, 0x7C76, + 0xBB71, 0x7C77, 0xBB72, 0x7C78, 0xBB73, 0x7C79, 0xBB74, 0x7C7A, 0xBB75, 0x7C7E, 0xBB76, 0x7C7F, 0xBB77, 0x7C80, 0xBB78, 0x7C81, + 0xBB79, 0x7C82, 0xBB7A, 0x7C83, 0xBB7B, 0x7C84, 0xBB7C, 0x7C85, 0xBB7D, 0x7C86, 0xBB7E, 0x7C87, 0xBB80, 0x7C88, 0xBB81, 0x7C8A, + 0xBB82, 0x7C8B, 0xBB83, 0x7C8C, 0xBB84, 0x7C8D, 0xBB85, 0x7C8E, 0xBB86, 0x7C8F, 0xBB87, 0x7C90, 0xBB88, 0x7C93, 0xBB89, 0x7C94, + 0xBB8A, 0x7C96, 0xBB8B, 0x7C99, 0xBB8C, 0x7C9A, 0xBB8D, 0x7C9B, 0xBB8E, 0x7CA0, 0xBB8F, 0x7CA1, 0xBB90, 0x7CA3, 0xBB91, 0x7CA6, + 0xBB92, 0x7CA7, 0xBB93, 0x7CA8, 0xBB94, 0x7CA9, 0xBB95, 0x7CAB, 0xBB96, 0x7CAC, 0xBB97, 0x7CAD, 0xBB98, 0x7CAF, 0xBB99, 0x7CB0, + 0xBB9A, 0x7CB4, 0xBB9B, 0x7CB5, 0xBB9C, 0x7CB6, 0xBB9D, 0x7CB7, 0xBB9E, 0x7CB8, 0xBB9F, 0x7CBA, 0xBBA0, 0x7CBB, 0xBBA1, 0x5F27, + 0xBBA2, 0x864E, 0xBBA3, 0x552C, 0xBBA4, 0x62A4, 0xBBA5, 0x4E92, 0xBBA6, 0x6CAA, 0xBBA7, 0x6237, 0xBBA8, 0x82B1, 0xBBA9, 0x54D7, + 0xBBAA, 0x534E, 0xBBAB, 0x733E, 0xBBAC, 0x6ED1, 0xBBAD, 0x753B, 0xBBAE, 0x5212, 0xBBAF, 0x5316, 0xBBB0, 0x8BDD, 0xBBB1, 0x69D0, + 0xBBB2, 0x5F8A, 0xBBB3, 0x6000, 0xBBB4, 0x6DEE, 0xBBB5, 0x574F, 0xBBB6, 0x6B22, 0xBBB7, 0x73AF, 0xBBB8, 0x6853, 0xBBB9, 0x8FD8, + 0xBBBA, 0x7F13, 0xBBBB, 0x6362, 0xBBBC, 0x60A3, 0xBBBD, 0x5524, 0xBBBE, 0x75EA, 0xBBBF, 0x8C62, 0xBBC0, 0x7115, 0xBBC1, 0x6DA3, + 0xBBC2, 0x5BA6, 0xBBC3, 0x5E7B, 0xBBC4, 0x8352, 0xBBC5, 0x614C, 0xBBC6, 0x9EC4, 0xBBC7, 0x78FA, 0xBBC8, 0x8757, 0xBBC9, 0x7C27, + 0xBBCA, 0x7687, 0xBBCB, 0x51F0, 0xBBCC, 0x60F6, 0xBBCD, 0x714C, 0xBBCE, 0x6643, 0xBBCF, 0x5E4C, 0xBBD0, 0x604D, 0xBBD1, 0x8C0E, + 0xBBD2, 0x7070, 0xBBD3, 0x6325, 0xBBD4, 0x8F89, 0xBBD5, 0x5FBD, 0xBBD6, 0x6062, 0xBBD7, 0x86D4, 0xBBD8, 0x56DE, 0xBBD9, 0x6BC1, + 0xBBDA, 0x6094, 0xBBDB, 0x6167, 0xBBDC, 0x5349, 0xBBDD, 0x60E0, 0xBBDE, 0x6666, 0xBBDF, 0x8D3F, 0xBBE0, 0x79FD, 0xBBE1, 0x4F1A, + 0xBBE2, 0x70E9, 0xBBE3, 0x6C47, 0xBBE4, 0x8BB3, 0xBBE5, 0x8BF2, 0xBBE6, 0x7ED8, 0xBBE7, 0x8364, 0xBBE8, 0x660F, 0xBBE9, 0x5A5A, + 0xBBEA, 0x9B42, 0xBBEB, 0x6D51, 0xBBEC, 0x6DF7, 0xBBED, 0x8C41, 0xBBEE, 0x6D3B, 0xBBEF, 0x4F19, 0xBBF0, 0x706B, 0xBBF1, 0x83B7, + 0xBBF2, 0x6216, 0xBBF3, 0x60D1, 0xBBF4, 0x970D, 0xBBF5, 0x8D27, 0xBBF6, 0x7978, 0xBBF7, 0x51FB, 0xBBF8, 0x573E, 0xBBF9, 0x57FA, + 0xBBFA, 0x673A, 0xBBFB, 0x7578, 0xBBFC, 0x7A3D, 0xBBFD, 0x79EF, 0xBBFE, 0x7B95, 0xBC40, 0x7CBF, 0xBC41, 0x7CC0, 0xBC42, 0x7CC2, + 0xBC43, 0x7CC3, 0xBC44, 0x7CC4, 0xBC45, 0x7CC6, 0xBC46, 0x7CC9, 0xBC47, 0x7CCB, 0xBC48, 0x7CCE, 0xBC49, 0x7CCF, 0xBC4A, 0x7CD0, + 0xBC4B, 0x7CD1, 0xBC4C, 0x7CD2, 0xBC4D, 0x7CD3, 0xBC4E, 0x7CD4, 0xBC4F, 0x7CD8, 0xBC50, 0x7CDA, 0xBC51, 0x7CDB, 0xBC52, 0x7CDD, + 0xBC53, 0x7CDE, 0xBC54, 0x7CE1, 0xBC55, 0x7CE2, 0xBC56, 0x7CE3, 0xBC57, 0x7CE4, 0xBC58, 0x7CE5, 0xBC59, 0x7CE6, 0xBC5A, 0x7CE7, + 0xBC5B, 0x7CE9, 0xBC5C, 0x7CEA, 0xBC5D, 0x7CEB, 0xBC5E, 0x7CEC, 0xBC5F, 0x7CED, 0xBC60, 0x7CEE, 0xBC61, 0x7CF0, 0xBC62, 0x7CF1, + 0xBC63, 0x7CF2, 0xBC64, 0x7CF3, 0xBC65, 0x7CF4, 0xBC66, 0x7CF5, 0xBC67, 0x7CF6, 0xBC68, 0x7CF7, 0xBC69, 0x7CF9, 0xBC6A, 0x7CFA, + 0xBC6B, 0x7CFC, 0xBC6C, 0x7CFD, 0xBC6D, 0x7CFE, 0xBC6E, 0x7CFF, 0xBC6F, 0x7D00, 0xBC70, 0x7D01, 0xBC71, 0x7D02, 0xBC72, 0x7D03, + 0xBC73, 0x7D04, 0xBC74, 0x7D05, 0xBC75, 0x7D06, 0xBC76, 0x7D07, 0xBC77, 0x7D08, 0xBC78, 0x7D09, 0xBC79, 0x7D0B, 0xBC7A, 0x7D0C, + 0xBC7B, 0x7D0D, 0xBC7C, 0x7D0E, 0xBC7D, 0x7D0F, 0xBC7E, 0x7D10, 0xBC80, 0x7D11, 0xBC81, 0x7D12, 0xBC82, 0x7D13, 0xBC83, 0x7D14, + 0xBC84, 0x7D15, 0xBC85, 0x7D16, 0xBC86, 0x7D17, 0xBC87, 0x7D18, 0xBC88, 0x7D19, 0xBC89, 0x7D1A, 0xBC8A, 0x7D1B, 0xBC8B, 0x7D1C, + 0xBC8C, 0x7D1D, 0xBC8D, 0x7D1E, 0xBC8E, 0x7D1F, 0xBC8F, 0x7D21, 0xBC90, 0x7D23, 0xBC91, 0x7D24, 0xBC92, 0x7D25, 0xBC93, 0x7D26, + 0xBC94, 0x7D28, 0xBC95, 0x7D29, 0xBC96, 0x7D2A, 0xBC97, 0x7D2C, 0xBC98, 0x7D2D, 0xBC99, 0x7D2E, 0xBC9A, 0x7D30, 0xBC9B, 0x7D31, + 0xBC9C, 0x7D32, 0xBC9D, 0x7D33, 0xBC9E, 0x7D34, 0xBC9F, 0x7D35, 0xBCA0, 0x7D36, 0xBCA1, 0x808C, 0xBCA2, 0x9965, 0xBCA3, 0x8FF9, + 0xBCA4, 0x6FC0, 0xBCA5, 0x8BA5, 0xBCA6, 0x9E21, 0xBCA7, 0x59EC, 0xBCA8, 0x7EE9, 0xBCA9, 0x7F09, 0xBCAA, 0x5409, 0xBCAB, 0x6781, + 0xBCAC, 0x68D8, 0xBCAD, 0x8F91, 0xBCAE, 0x7C4D, 0xBCAF, 0x96C6, 0xBCB0, 0x53CA, 0xBCB1, 0x6025, 0xBCB2, 0x75BE, 0xBCB3, 0x6C72, + 0xBCB4, 0x5373, 0xBCB5, 0x5AC9, 0xBCB6, 0x7EA7, 0xBCB7, 0x6324, 0xBCB8, 0x51E0, 0xBCB9, 0x810A, 0xBCBA, 0x5DF1, 0xBCBB, 0x84DF, + 0xBCBC, 0x6280, 0xBCBD, 0x5180, 0xBCBE, 0x5B63, 0xBCBF, 0x4F0E, 0xBCC0, 0x796D, 0xBCC1, 0x5242, 0xBCC2, 0x60B8, 0xBCC3, 0x6D4E, + 0xBCC4, 0x5BC4, 0xBCC5, 0x5BC2, 0xBCC6, 0x8BA1, 0xBCC7, 0x8BB0, 0xBCC8, 0x65E2, 0xBCC9, 0x5FCC, 0xBCCA, 0x9645, 0xBCCB, 0x5993, + 0xBCCC, 0x7EE7, 0xBCCD, 0x7EAA, 0xBCCE, 0x5609, 0xBCCF, 0x67B7, 0xBCD0, 0x5939, 0xBCD1, 0x4F73, 0xBCD2, 0x5BB6, 0xBCD3, 0x52A0, + 0xBCD4, 0x835A, 0xBCD5, 0x988A, 0xBCD6, 0x8D3E, 0xBCD7, 0x7532, 0xBCD8, 0x94BE, 0xBCD9, 0x5047, 0xBCDA, 0x7A3C, 0xBCDB, 0x4EF7, + 0xBCDC, 0x67B6, 0xBCDD, 0x9A7E, 0xBCDE, 0x5AC1, 0xBCDF, 0x6B7C, 0xBCE0, 0x76D1, 0xBCE1, 0x575A, 0xBCE2, 0x5C16, 0xBCE3, 0x7B3A, + 0xBCE4, 0x95F4, 0xBCE5, 0x714E, 0xBCE6, 0x517C, 0xBCE7, 0x80A9, 0xBCE8, 0x8270, 0xBCE9, 0x5978, 0xBCEA, 0x7F04, 0xBCEB, 0x8327, + 0xBCEC, 0x68C0, 0xBCED, 0x67EC, 0xBCEE, 0x78B1, 0xBCEF, 0x7877, 0xBCF0, 0x62E3, 0xBCF1, 0x6361, 0xBCF2, 0x7B80, 0xBCF3, 0x4FED, + 0xBCF4, 0x526A, 0xBCF5, 0x51CF, 0xBCF6, 0x8350, 0xBCF7, 0x69DB, 0xBCF8, 0x9274, 0xBCF9, 0x8DF5, 0xBCFA, 0x8D31, 0xBCFB, 0x89C1, + 0xBCFC, 0x952E, 0xBCFD, 0x7BAD, 0xBCFE, 0x4EF6, 0xBD40, 0x7D37, 0xBD41, 0x7D38, 0xBD42, 0x7D39, 0xBD43, 0x7D3A, 0xBD44, 0x7D3B, + 0xBD45, 0x7D3C, 0xBD46, 0x7D3D, 0xBD47, 0x7D3E, 0xBD48, 0x7D3F, 0xBD49, 0x7D40, 0xBD4A, 0x7D41, 0xBD4B, 0x7D42, 0xBD4C, 0x7D43, + 0xBD4D, 0x7D44, 0xBD4E, 0x7D45, 0xBD4F, 0x7D46, 0xBD50, 0x7D47, 0xBD51, 0x7D48, 0xBD52, 0x7D49, 0xBD53, 0x7D4A, 0xBD54, 0x7D4B, + 0xBD55, 0x7D4C, 0xBD56, 0x7D4D, 0xBD57, 0x7D4E, 0xBD58, 0x7D4F, 0xBD59, 0x7D50, 0xBD5A, 0x7D51, 0xBD5B, 0x7D52, 0xBD5C, 0x7D53, + 0xBD5D, 0x7D54, 0xBD5E, 0x7D55, 0xBD5F, 0x7D56, 0xBD60, 0x7D57, 0xBD61, 0x7D58, 0xBD62, 0x7D59, 0xBD63, 0x7D5A, 0xBD64, 0x7D5B, + 0xBD65, 0x7D5C, 0xBD66, 0x7D5D, 0xBD67, 0x7D5E, 0xBD68, 0x7D5F, 0xBD69, 0x7D60, 0xBD6A, 0x7D61, 0xBD6B, 0x7D62, 0xBD6C, 0x7D63, + 0xBD6D, 0x7D64, 0xBD6E, 0x7D65, 0xBD6F, 0x7D66, 0xBD70, 0x7D67, 0xBD71, 0x7D68, 0xBD72, 0x7D69, 0xBD73, 0x7D6A, 0xBD74, 0x7D6B, + 0xBD75, 0x7D6C, 0xBD76, 0x7D6D, 0xBD77, 0x7D6F, 0xBD78, 0x7D70, 0xBD79, 0x7D71, 0xBD7A, 0x7D72, 0xBD7B, 0x7D73, 0xBD7C, 0x7D74, + 0xBD7D, 0x7D75, 0xBD7E, 0x7D76, 0xBD80, 0x7D78, 0xBD81, 0x7D79, 0xBD82, 0x7D7A, 0xBD83, 0x7D7B, 0xBD84, 0x7D7C, 0xBD85, 0x7D7D, + 0xBD86, 0x7D7E, 0xBD87, 0x7D7F, 0xBD88, 0x7D80, 0xBD89, 0x7D81, 0xBD8A, 0x7D82, 0xBD8B, 0x7D83, 0xBD8C, 0x7D84, 0xBD8D, 0x7D85, + 0xBD8E, 0x7D86, 0xBD8F, 0x7D87, 0xBD90, 0x7D88, 0xBD91, 0x7D89, 0xBD92, 0x7D8A, 0xBD93, 0x7D8B, 0xBD94, 0x7D8C, 0xBD95, 0x7D8D, + 0xBD96, 0x7D8E, 0xBD97, 0x7D8F, 0xBD98, 0x7D90, 0xBD99, 0x7D91, 0xBD9A, 0x7D92, 0xBD9B, 0x7D93, 0xBD9C, 0x7D94, 0xBD9D, 0x7D95, + 0xBD9E, 0x7D96, 0xBD9F, 0x7D97, 0xBDA0, 0x7D98, 0xBDA1, 0x5065, 0xBDA2, 0x8230, 0xBDA3, 0x5251, 0xBDA4, 0x996F, 0xBDA5, 0x6E10, + 0xBDA6, 0x6E85, 0xBDA7, 0x6DA7, 0xBDA8, 0x5EFA, 0xBDA9, 0x50F5, 0xBDAA, 0x59DC, 0xBDAB, 0x5C06, 0xBDAC, 0x6D46, 0xBDAD, 0x6C5F, + 0xBDAE, 0x7586, 0xBDAF, 0x848B, 0xBDB0, 0x6868, 0xBDB1, 0x5956, 0xBDB2, 0x8BB2, 0xBDB3, 0x5320, 0xBDB4, 0x9171, 0xBDB5, 0x964D, + 0xBDB6, 0x8549, 0xBDB7, 0x6912, 0xBDB8, 0x7901, 0xBDB9, 0x7126, 0xBDBA, 0x80F6, 0xBDBB, 0x4EA4, 0xBDBC, 0x90CA, 0xBDBD, 0x6D47, + 0xBDBE, 0x9A84, 0xBDBF, 0x5A07, 0xBDC0, 0x56BC, 0xBDC1, 0x6405, 0xBDC2, 0x94F0, 0xBDC3, 0x77EB, 0xBDC4, 0x4FA5, 0xBDC5, 0x811A, + 0xBDC6, 0x72E1, 0xBDC7, 0x89D2, 0xBDC8, 0x997A, 0xBDC9, 0x7F34, 0xBDCA, 0x7EDE, 0xBDCB, 0x527F, 0xBDCC, 0x6559, 0xBDCD, 0x9175, + 0xBDCE, 0x8F7F, 0xBDCF, 0x8F83, 0xBDD0, 0x53EB, 0xBDD1, 0x7A96, 0xBDD2, 0x63ED, 0xBDD3, 0x63A5, 0xBDD4, 0x7686, 0xBDD5, 0x79F8, + 0xBDD6, 0x8857, 0xBDD7, 0x9636, 0xBDD8, 0x622A, 0xBDD9, 0x52AB, 0xBDDA, 0x8282, 0xBDDB, 0x6854, 0xBDDC, 0x6770, 0xBDDD, 0x6377, + 0xBDDE, 0x776B, 0xBDDF, 0x7AED, 0xBDE0, 0x6D01, 0xBDE1, 0x7ED3, 0xBDE2, 0x89E3, 0xBDE3, 0x59D0, 0xBDE4, 0x6212, 0xBDE5, 0x85C9, + 0xBDE6, 0x82A5, 0xBDE7, 0x754C, 0xBDE8, 0x501F, 0xBDE9, 0x4ECB, 0xBDEA, 0x75A5, 0xBDEB, 0x8BEB, 0xBDEC, 0x5C4A, 0xBDED, 0x5DFE, + 0xBDEE, 0x7B4B, 0xBDEF, 0x65A4, 0xBDF0, 0x91D1, 0xBDF1, 0x4ECA, 0xBDF2, 0x6D25, 0xBDF3, 0x895F, 0xBDF4, 0x7D27, 0xBDF5, 0x9526, + 0xBDF6, 0x4EC5, 0xBDF7, 0x8C28, 0xBDF8, 0x8FDB, 0xBDF9, 0x9773, 0xBDFA, 0x664B, 0xBDFB, 0x7981, 0xBDFC, 0x8FD1, 0xBDFD, 0x70EC, + 0xBDFE, 0x6D78, 0xBE40, 0x7D99, 0xBE41, 0x7D9A, 0xBE42, 0x7D9B, 0xBE43, 0x7D9C, 0xBE44, 0x7D9D, 0xBE45, 0x7D9E, 0xBE46, 0x7D9F, + 0xBE47, 0x7DA0, 0xBE48, 0x7DA1, 0xBE49, 0x7DA2, 0xBE4A, 0x7DA3, 0xBE4B, 0x7DA4, 0xBE4C, 0x7DA5, 0xBE4D, 0x7DA7, 0xBE4E, 0x7DA8, + 0xBE4F, 0x7DA9, 0xBE50, 0x7DAA, 0xBE51, 0x7DAB, 0xBE52, 0x7DAC, 0xBE53, 0x7DAD, 0xBE54, 0x7DAF, 0xBE55, 0x7DB0, 0xBE56, 0x7DB1, + 0xBE57, 0x7DB2, 0xBE58, 0x7DB3, 0xBE59, 0x7DB4, 0xBE5A, 0x7DB5, 0xBE5B, 0x7DB6, 0xBE5C, 0x7DB7, 0xBE5D, 0x7DB8, 0xBE5E, 0x7DB9, + 0xBE5F, 0x7DBA, 0xBE60, 0x7DBB, 0xBE61, 0x7DBC, 0xBE62, 0x7DBD, 0xBE63, 0x7DBE, 0xBE64, 0x7DBF, 0xBE65, 0x7DC0, 0xBE66, 0x7DC1, + 0xBE67, 0x7DC2, 0xBE68, 0x7DC3, 0xBE69, 0x7DC4, 0xBE6A, 0x7DC5, 0xBE6B, 0x7DC6, 0xBE6C, 0x7DC7, 0xBE6D, 0x7DC8, 0xBE6E, 0x7DC9, + 0xBE6F, 0x7DCA, 0xBE70, 0x7DCB, 0xBE71, 0x7DCC, 0xBE72, 0x7DCD, 0xBE73, 0x7DCE, 0xBE74, 0x7DCF, 0xBE75, 0x7DD0, 0xBE76, 0x7DD1, + 0xBE77, 0x7DD2, 0xBE78, 0x7DD3, 0xBE79, 0x7DD4, 0xBE7A, 0x7DD5, 0xBE7B, 0x7DD6, 0xBE7C, 0x7DD7, 0xBE7D, 0x7DD8, 0xBE7E, 0x7DD9, + 0xBE80, 0x7DDA, 0xBE81, 0x7DDB, 0xBE82, 0x7DDC, 0xBE83, 0x7DDD, 0xBE84, 0x7DDE, 0xBE85, 0x7DDF, 0xBE86, 0x7DE0, 0xBE87, 0x7DE1, + 0xBE88, 0x7DE2, 0xBE89, 0x7DE3, 0xBE8A, 0x7DE4, 0xBE8B, 0x7DE5, 0xBE8C, 0x7DE6, 0xBE8D, 0x7DE7, 0xBE8E, 0x7DE8, 0xBE8F, 0x7DE9, + 0xBE90, 0x7DEA, 0xBE91, 0x7DEB, 0xBE92, 0x7DEC, 0xBE93, 0x7DED, 0xBE94, 0x7DEE, 0xBE95, 0x7DEF, 0xBE96, 0x7DF0, 0xBE97, 0x7DF1, + 0xBE98, 0x7DF2, 0xBE99, 0x7DF3, 0xBE9A, 0x7DF4, 0xBE9B, 0x7DF5, 0xBE9C, 0x7DF6, 0xBE9D, 0x7DF7, 0xBE9E, 0x7DF8, 0xBE9F, 0x7DF9, + 0xBEA0, 0x7DFA, 0xBEA1, 0x5C3D, 0xBEA2, 0x52B2, 0xBEA3, 0x8346, 0xBEA4, 0x5162, 0xBEA5, 0x830E, 0xBEA6, 0x775B, 0xBEA7, 0x6676, + 0xBEA8, 0x9CB8, 0xBEA9, 0x4EAC, 0xBEAA, 0x60CA, 0xBEAB, 0x7CBE, 0xBEAC, 0x7CB3, 0xBEAD, 0x7ECF, 0xBEAE, 0x4E95, 0xBEAF, 0x8B66, + 0xBEB0, 0x666F, 0xBEB1, 0x9888, 0xBEB2, 0x9759, 0xBEB3, 0x5883, 0xBEB4, 0x656C, 0xBEB5, 0x955C, 0xBEB6, 0x5F84, 0xBEB7, 0x75C9, + 0xBEB8, 0x9756, 0xBEB9, 0x7ADF, 0xBEBA, 0x7ADE, 0xBEBB, 0x51C0, 0xBEBC, 0x70AF, 0xBEBD, 0x7A98, 0xBEBE, 0x63EA, 0xBEBF, 0x7A76, + 0xBEC0, 0x7EA0, 0xBEC1, 0x7396, 0xBEC2, 0x97ED, 0xBEC3, 0x4E45, 0xBEC4, 0x7078, 0xBEC5, 0x4E5D, 0xBEC6, 0x9152, 0xBEC7, 0x53A9, + 0xBEC8, 0x6551, 0xBEC9, 0x65E7, 0xBECA, 0x81FC, 0xBECB, 0x8205, 0xBECC, 0x548E, 0xBECD, 0x5C31, 0xBECE, 0x759A, 0xBECF, 0x97A0, + 0xBED0, 0x62D8, 0xBED1, 0x72D9, 0xBED2, 0x75BD, 0xBED3, 0x5C45, 0xBED4, 0x9A79, 0xBED5, 0x83CA, 0xBED6, 0x5C40, 0xBED7, 0x5480, + 0xBED8, 0x77E9, 0xBED9, 0x4E3E, 0xBEDA, 0x6CAE, 0xBEDB, 0x805A, 0xBEDC, 0x62D2, 0xBEDD, 0x636E, 0xBEDE, 0x5DE8, 0xBEDF, 0x5177, + 0xBEE0, 0x8DDD, 0xBEE1, 0x8E1E, 0xBEE2, 0x952F, 0xBEE3, 0x4FF1, 0xBEE4, 0x53E5, 0xBEE5, 0x60E7, 0xBEE6, 0x70AC, 0xBEE7, 0x5267, + 0xBEE8, 0x6350, 0xBEE9, 0x9E43, 0xBEEA, 0x5A1F, 0xBEEB, 0x5026, 0xBEEC, 0x7737, 0xBEED, 0x5377, 0xBEEE, 0x7EE2, 0xBEEF, 0x6485, + 0xBEF0, 0x652B, 0xBEF1, 0x6289, 0xBEF2, 0x6398, 0xBEF3, 0x5014, 0xBEF4, 0x7235, 0xBEF5, 0x89C9, 0xBEF6, 0x51B3, 0xBEF7, 0x8BC0, + 0xBEF8, 0x7EDD, 0xBEF9, 0x5747, 0xBEFA, 0x83CC, 0xBEFB, 0x94A7, 0xBEFC, 0x519B, 0xBEFD, 0x541B, 0xBEFE, 0x5CFB, 0xBF40, 0x7DFB, + 0xBF41, 0x7DFC, 0xBF42, 0x7DFD, 0xBF43, 0x7DFE, 0xBF44, 0x7DFF, 0xBF45, 0x7E00, 0xBF46, 0x7E01, 0xBF47, 0x7E02, 0xBF48, 0x7E03, + 0xBF49, 0x7E04, 0xBF4A, 0x7E05, 0xBF4B, 0x7E06, 0xBF4C, 0x7E07, 0xBF4D, 0x7E08, 0xBF4E, 0x7E09, 0xBF4F, 0x7E0A, 0xBF50, 0x7E0B, + 0xBF51, 0x7E0C, 0xBF52, 0x7E0D, 0xBF53, 0x7E0E, 0xBF54, 0x7E0F, 0xBF55, 0x7E10, 0xBF56, 0x7E11, 0xBF57, 0x7E12, 0xBF58, 0x7E13, + 0xBF59, 0x7E14, 0xBF5A, 0x7E15, 0xBF5B, 0x7E16, 0xBF5C, 0x7E17, 0xBF5D, 0x7E18, 0xBF5E, 0x7E19, 0xBF5F, 0x7E1A, 0xBF60, 0x7E1B, + 0xBF61, 0x7E1C, 0xBF62, 0x7E1D, 0xBF63, 0x7E1E, 0xBF64, 0x7E1F, 0xBF65, 0x7E20, 0xBF66, 0x7E21, 0xBF67, 0x7E22, 0xBF68, 0x7E23, + 0xBF69, 0x7E24, 0xBF6A, 0x7E25, 0xBF6B, 0x7E26, 0xBF6C, 0x7E27, 0xBF6D, 0x7E28, 0xBF6E, 0x7E29, 0xBF6F, 0x7E2A, 0xBF70, 0x7E2B, + 0xBF71, 0x7E2C, 0xBF72, 0x7E2D, 0xBF73, 0x7E2E, 0xBF74, 0x7E2F, 0xBF75, 0x7E30, 0xBF76, 0x7E31, 0xBF77, 0x7E32, 0xBF78, 0x7E33, + 0xBF79, 0x7E34, 0xBF7A, 0x7E35, 0xBF7B, 0x7E36, 0xBF7C, 0x7E37, 0xBF7D, 0x7E38, 0xBF7E, 0x7E39, 0xBF80, 0x7E3A, 0xBF81, 0x7E3C, + 0xBF82, 0x7E3D, 0xBF83, 0x7E3E, 0xBF84, 0x7E3F, 0xBF85, 0x7E40, 0xBF86, 0x7E42, 0xBF87, 0x7E43, 0xBF88, 0x7E44, 0xBF89, 0x7E45, + 0xBF8A, 0x7E46, 0xBF8B, 0x7E48, 0xBF8C, 0x7E49, 0xBF8D, 0x7E4A, 0xBF8E, 0x7E4B, 0xBF8F, 0x7E4C, 0xBF90, 0x7E4D, 0xBF91, 0x7E4E, + 0xBF92, 0x7E4F, 0xBF93, 0x7E50, 0xBF94, 0x7E51, 0xBF95, 0x7E52, 0xBF96, 0x7E53, 0xBF97, 0x7E54, 0xBF98, 0x7E55, 0xBF99, 0x7E56, + 0xBF9A, 0x7E57, 0xBF9B, 0x7E58, 0xBF9C, 0x7E59, 0xBF9D, 0x7E5A, 0xBF9E, 0x7E5B, 0xBF9F, 0x7E5C, 0xBFA0, 0x7E5D, 0xBFA1, 0x4FCA, + 0xBFA2, 0x7AE3, 0xBFA3, 0x6D5A, 0xBFA4, 0x90E1, 0xBFA5, 0x9A8F, 0xBFA6, 0x5580, 0xBFA7, 0x5496, 0xBFA8, 0x5361, 0xBFA9, 0x54AF, + 0xBFAA, 0x5F00, 0xBFAB, 0x63E9, 0xBFAC, 0x6977, 0xBFAD, 0x51EF, 0xBFAE, 0x6168, 0xBFAF, 0x520A, 0xBFB0, 0x582A, 0xBFB1, 0x52D8, + 0xBFB2, 0x574E, 0xBFB3, 0x780D, 0xBFB4, 0x770B, 0xBFB5, 0x5EB7, 0xBFB6, 0x6177, 0xBFB7, 0x7CE0, 0xBFB8, 0x625B, 0xBFB9, 0x6297, + 0xBFBA, 0x4EA2, 0xBFBB, 0x7095, 0xBFBC, 0x8003, 0xBFBD, 0x62F7, 0xBFBE, 0x70E4, 0xBFBF, 0x9760, 0xBFC0, 0x5777, 0xBFC1, 0x82DB, + 0xBFC2, 0x67EF, 0xBFC3, 0x68F5, 0xBFC4, 0x78D5, 0xBFC5, 0x9897, 0xBFC6, 0x79D1, 0xBFC7, 0x58F3, 0xBFC8, 0x54B3, 0xBFC9, 0x53EF, + 0xBFCA, 0x6E34, 0xBFCB, 0x514B, 0xBFCC, 0x523B, 0xBFCD, 0x5BA2, 0xBFCE, 0x8BFE, 0xBFCF, 0x80AF, 0xBFD0, 0x5543, 0xBFD1, 0x57A6, + 0xBFD2, 0x6073, 0xBFD3, 0x5751, 0xBFD4, 0x542D, 0xBFD5, 0x7A7A, 0xBFD6, 0x6050, 0xBFD7, 0x5B54, 0xBFD8, 0x63A7, 0xBFD9, 0x62A0, + 0xBFDA, 0x53E3, 0xBFDB, 0x6263, 0xBFDC, 0x5BC7, 0xBFDD, 0x67AF, 0xBFDE, 0x54ED, 0xBFDF, 0x7A9F, 0xBFE0, 0x82E6, 0xBFE1, 0x9177, + 0xBFE2, 0x5E93, 0xBFE3, 0x88E4, 0xBFE4, 0x5938, 0xBFE5, 0x57AE, 0xBFE6, 0x630E, 0xBFE7, 0x8DE8, 0xBFE8, 0x80EF, 0xBFE9, 0x5757, + 0xBFEA, 0x7B77, 0xBFEB, 0x4FA9, 0xBFEC, 0x5FEB, 0xBFED, 0x5BBD, 0xBFEE, 0x6B3E, 0xBFEF, 0x5321, 0xBFF0, 0x7B50, 0xBFF1, 0x72C2, + 0xBFF2, 0x6846, 0xBFF3, 0x77FF, 0xBFF4, 0x7736, 0xBFF5, 0x65F7, 0xBFF6, 0x51B5, 0xBFF7, 0x4E8F, 0xBFF8, 0x76D4, 0xBFF9, 0x5CBF, + 0xBFFA, 0x7AA5, 0xBFFB, 0x8475, 0xBFFC, 0x594E, 0xBFFD, 0x9B41, 0xBFFE, 0x5080, 0xC040, 0x7E5E, 0xC041, 0x7E5F, 0xC042, 0x7E60, + 0xC043, 0x7E61, 0xC044, 0x7E62, 0xC045, 0x7E63, 0xC046, 0x7E64, 0xC047, 0x7E65, 0xC048, 0x7E66, 0xC049, 0x7E67, 0xC04A, 0x7E68, + 0xC04B, 0x7E69, 0xC04C, 0x7E6A, 0xC04D, 0x7E6B, 0xC04E, 0x7E6C, 0xC04F, 0x7E6D, 0xC050, 0x7E6E, 0xC051, 0x7E6F, 0xC052, 0x7E70, + 0xC053, 0x7E71, 0xC054, 0x7E72, 0xC055, 0x7E73, 0xC056, 0x7E74, 0xC057, 0x7E75, 0xC058, 0x7E76, 0xC059, 0x7E77, 0xC05A, 0x7E78, + 0xC05B, 0x7E79, 0xC05C, 0x7E7A, 0xC05D, 0x7E7B, 0xC05E, 0x7E7C, 0xC05F, 0x7E7D, 0xC060, 0x7E7E, 0xC061, 0x7E7F, 0xC062, 0x7E80, + 0xC063, 0x7E81, 0xC064, 0x7E83, 0xC065, 0x7E84, 0xC066, 0x7E85, 0xC067, 0x7E86, 0xC068, 0x7E87, 0xC069, 0x7E88, 0xC06A, 0x7E89, + 0xC06B, 0x7E8A, 0xC06C, 0x7E8B, 0xC06D, 0x7E8C, 0xC06E, 0x7E8D, 0xC06F, 0x7E8E, 0xC070, 0x7E8F, 0xC071, 0x7E90, 0xC072, 0x7E91, + 0xC073, 0x7E92, 0xC074, 0x7E93, 0xC075, 0x7E94, 0xC076, 0x7E95, 0xC077, 0x7E96, 0xC078, 0x7E97, 0xC079, 0x7E98, 0xC07A, 0x7E99, + 0xC07B, 0x7E9A, 0xC07C, 0x7E9C, 0xC07D, 0x7E9D, 0xC07E, 0x7E9E, 0xC080, 0x7EAE, 0xC081, 0x7EB4, 0xC082, 0x7EBB, 0xC083, 0x7EBC, + 0xC084, 0x7ED6, 0xC085, 0x7EE4, 0xC086, 0x7EEC, 0xC087, 0x7EF9, 0xC088, 0x7F0A, 0xC089, 0x7F10, 0xC08A, 0x7F1E, 0xC08B, 0x7F37, + 0xC08C, 0x7F39, 0xC08D, 0x7F3B, 0xC08E, 0x7F3C, 0xC08F, 0x7F3D, 0xC090, 0x7F3E, 0xC091, 0x7F3F, 0xC092, 0x7F40, 0xC093, 0x7F41, + 0xC094, 0x7F43, 0xC095, 0x7F46, 0xC096, 0x7F47, 0xC097, 0x7F48, 0xC098, 0x7F49, 0xC099, 0x7F4A, 0xC09A, 0x7F4B, 0xC09B, 0x7F4C, + 0xC09C, 0x7F4D, 0xC09D, 0x7F4E, 0xC09E, 0x7F4F, 0xC09F, 0x7F52, 0xC0A0, 0x7F53, 0xC0A1, 0x9988, 0xC0A2, 0x6127, 0xC0A3, 0x6E83, + 0xC0A4, 0x5764, 0xC0A5, 0x6606, 0xC0A6, 0x6346, 0xC0A7, 0x56F0, 0xC0A8, 0x62EC, 0xC0A9, 0x6269, 0xC0AA, 0x5ED3, 0xC0AB, 0x9614, + 0xC0AC, 0x5783, 0xC0AD, 0x62C9, 0xC0AE, 0x5587, 0xC0AF, 0x8721, 0xC0B0, 0x814A, 0xC0B1, 0x8FA3, 0xC0B2, 0x5566, 0xC0B3, 0x83B1, + 0xC0B4, 0x6765, 0xC0B5, 0x8D56, 0xC0B6, 0x84DD, 0xC0B7, 0x5A6A, 0xC0B8, 0x680F, 0xC0B9, 0x62E6, 0xC0BA, 0x7BEE, 0xC0BB, 0x9611, + 0xC0BC, 0x5170, 0xC0BD, 0x6F9C, 0xC0BE, 0x8C30, 0xC0BF, 0x63FD, 0xC0C0, 0x89C8, 0xC0C1, 0x61D2, 0xC0C2, 0x7F06, 0xC0C3, 0x70C2, + 0xC0C4, 0x6EE5, 0xC0C5, 0x7405, 0xC0C6, 0x6994, 0xC0C7, 0x72FC, 0xC0C8, 0x5ECA, 0xC0C9, 0x90CE, 0xC0CA, 0x6717, 0xC0CB, 0x6D6A, + 0xC0CC, 0x635E, 0xC0CD, 0x52B3, 0xC0CE, 0x7262, 0xC0CF, 0x8001, 0xC0D0, 0x4F6C, 0xC0D1, 0x59E5, 0xC0D2, 0x916A, 0xC0D3, 0x70D9, + 0xC0D4, 0x6D9D, 0xC0D5, 0x52D2, 0xC0D6, 0x4E50, 0xC0D7, 0x96F7, 0xC0D8, 0x956D, 0xC0D9, 0x857E, 0xC0DA, 0x78CA, 0xC0DB, 0x7D2F, + 0xC0DC, 0x5121, 0xC0DD, 0x5792, 0xC0DE, 0x64C2, 0xC0DF, 0x808B, 0xC0E0, 0x7C7B, 0xC0E1, 0x6CEA, 0xC0E2, 0x68F1, 0xC0E3, 0x695E, + 0xC0E4, 0x51B7, 0xC0E5, 0x5398, 0xC0E6, 0x68A8, 0xC0E7, 0x7281, 0xC0E8, 0x9ECE, 0xC0E9, 0x7BF1, 0xC0EA, 0x72F8, 0xC0EB, 0x79BB, + 0xC0EC, 0x6F13, 0xC0ED, 0x7406, 0xC0EE, 0x674E, 0xC0EF, 0x91CC, 0xC0F0, 0x9CA4, 0xC0F1, 0x793C, 0xC0F2, 0x8389, 0xC0F3, 0x8354, + 0xC0F4, 0x540F, 0xC0F5, 0x6817, 0xC0F6, 0x4E3D, 0xC0F7, 0x5389, 0xC0F8, 0x52B1, 0xC0F9, 0x783E, 0xC0FA, 0x5386, 0xC0FB, 0x5229, + 0xC0FC, 0x5088, 0xC0FD, 0x4F8B, 0xC0FE, 0x4FD0, 0xC140, 0x7F56, 0xC141, 0x7F59, 0xC142, 0x7F5B, 0xC143, 0x7F5C, 0xC144, 0x7F5D, + 0xC145, 0x7F5E, 0xC146, 0x7F60, 0xC147, 0x7F63, 0xC148, 0x7F64, 0xC149, 0x7F65, 0xC14A, 0x7F66, 0xC14B, 0x7F67, 0xC14C, 0x7F6B, + 0xC14D, 0x7F6C, 0xC14E, 0x7F6D, 0xC14F, 0x7F6F, 0xC150, 0x7F70, 0xC151, 0x7F73, 0xC152, 0x7F75, 0xC153, 0x7F76, 0xC154, 0x7F77, + 0xC155, 0x7F78, 0xC156, 0x7F7A, 0xC157, 0x7F7B, 0xC158, 0x7F7C, 0xC159, 0x7F7D, 0xC15A, 0x7F7F, 0xC15B, 0x7F80, 0xC15C, 0x7F82, + 0xC15D, 0x7F83, 0xC15E, 0x7F84, 0xC15F, 0x7F85, 0xC160, 0x7F86, 0xC161, 0x7F87, 0xC162, 0x7F88, 0xC163, 0x7F89, 0xC164, 0x7F8B, + 0xC165, 0x7F8D, 0xC166, 0x7F8F, 0xC167, 0x7F90, 0xC168, 0x7F91, 0xC169, 0x7F92, 0xC16A, 0x7F93, 0xC16B, 0x7F95, 0xC16C, 0x7F96, + 0xC16D, 0x7F97, 0xC16E, 0x7F98, 0xC16F, 0x7F99, 0xC170, 0x7F9B, 0xC171, 0x7F9C, 0xC172, 0x7FA0, 0xC173, 0x7FA2, 0xC174, 0x7FA3, + 0xC175, 0x7FA5, 0xC176, 0x7FA6, 0xC177, 0x7FA8, 0xC178, 0x7FA9, 0xC179, 0x7FAA, 0xC17A, 0x7FAB, 0xC17B, 0x7FAC, 0xC17C, 0x7FAD, + 0xC17D, 0x7FAE, 0xC17E, 0x7FB1, 0xC180, 0x7FB3, 0xC181, 0x7FB4, 0xC182, 0x7FB5, 0xC183, 0x7FB6, 0xC184, 0x7FB7, 0xC185, 0x7FBA, + 0xC186, 0x7FBB, 0xC187, 0x7FBE, 0xC188, 0x7FC0, 0xC189, 0x7FC2, 0xC18A, 0x7FC3, 0xC18B, 0x7FC4, 0xC18C, 0x7FC6, 0xC18D, 0x7FC7, + 0xC18E, 0x7FC8, 0xC18F, 0x7FC9, 0xC190, 0x7FCB, 0xC191, 0x7FCD, 0xC192, 0x7FCF, 0xC193, 0x7FD0, 0xC194, 0x7FD1, 0xC195, 0x7FD2, + 0xC196, 0x7FD3, 0xC197, 0x7FD6, 0xC198, 0x7FD7, 0xC199, 0x7FD9, 0xC19A, 0x7FDA, 0xC19B, 0x7FDB, 0xC19C, 0x7FDC, 0xC19D, 0x7FDD, + 0xC19E, 0x7FDE, 0xC19F, 0x7FE2, 0xC1A0, 0x7FE3, 0xC1A1, 0x75E2, 0xC1A2, 0x7ACB, 0xC1A3, 0x7C92, 0xC1A4, 0x6CA5, 0xC1A5, 0x96B6, + 0xC1A6, 0x529B, 0xC1A7, 0x7483, 0xC1A8, 0x54E9, 0xC1A9, 0x4FE9, 0xC1AA, 0x8054, 0xC1AB, 0x83B2, 0xC1AC, 0x8FDE, 0xC1AD, 0x9570, + 0xC1AE, 0x5EC9, 0xC1AF, 0x601C, 0xC1B0, 0x6D9F, 0xC1B1, 0x5E18, 0xC1B2, 0x655B, 0xC1B3, 0x8138, 0xC1B4, 0x94FE, 0xC1B5, 0x604B, + 0xC1B6, 0x70BC, 0xC1B7, 0x7EC3, 0xC1B8, 0x7CAE, 0xC1B9, 0x51C9, 0xC1BA, 0x6881, 0xC1BB, 0x7CB1, 0xC1BC, 0x826F, 0xC1BD, 0x4E24, + 0xC1BE, 0x8F86, 0xC1BF, 0x91CF, 0xC1C0, 0x667E, 0xC1C1, 0x4EAE, 0xC1C2, 0x8C05, 0xC1C3, 0x64A9, 0xC1C4, 0x804A, 0xC1C5, 0x50DA, + 0xC1C6, 0x7597, 0xC1C7, 0x71CE, 0xC1C8, 0x5BE5, 0xC1C9, 0x8FBD, 0xC1CA, 0x6F66, 0xC1CB, 0x4E86, 0xC1CC, 0x6482, 0xC1CD, 0x9563, + 0xC1CE, 0x5ED6, 0xC1CF, 0x6599, 0xC1D0, 0x5217, 0xC1D1, 0x88C2, 0xC1D2, 0x70C8, 0xC1D3, 0x52A3, 0xC1D4, 0x730E, 0xC1D5, 0x7433, + 0xC1D6, 0x6797, 0xC1D7, 0x78F7, 0xC1D8, 0x9716, 0xC1D9, 0x4E34, 0xC1DA, 0x90BB, 0xC1DB, 0x9CDE, 0xC1DC, 0x6DCB, 0xC1DD, 0x51DB, + 0xC1DE, 0x8D41, 0xC1DF, 0x541D, 0xC1E0, 0x62CE, 0xC1E1, 0x73B2, 0xC1E2, 0x83F1, 0xC1E3, 0x96F6, 0xC1E4, 0x9F84, 0xC1E5, 0x94C3, + 0xC1E6, 0x4F36, 0xC1E7, 0x7F9A, 0xC1E8, 0x51CC, 0xC1E9, 0x7075, 0xC1EA, 0x9675, 0xC1EB, 0x5CAD, 0xC1EC, 0x9886, 0xC1ED, 0x53E6, + 0xC1EE, 0x4EE4, 0xC1EF, 0x6E9C, 0xC1F0, 0x7409, 0xC1F1, 0x69B4, 0xC1F2, 0x786B, 0xC1F3, 0x998F, 0xC1F4, 0x7559, 0xC1F5, 0x5218, + 0xC1F6, 0x7624, 0xC1F7, 0x6D41, 0xC1F8, 0x67F3, 0xC1F9, 0x516D, 0xC1FA, 0x9F99, 0xC1FB, 0x804B, 0xC1FC, 0x5499, 0xC1FD, 0x7B3C, + 0xC1FE, 0x7ABF, 0xC240, 0x7FE4, 0xC241, 0x7FE7, 0xC242, 0x7FE8, 0xC243, 0x7FEA, 0xC244, 0x7FEB, 0xC245, 0x7FEC, 0xC246, 0x7FED, + 0xC247, 0x7FEF, 0xC248, 0x7FF2, 0xC249, 0x7FF4, 0xC24A, 0x7FF5, 0xC24B, 0x7FF6, 0xC24C, 0x7FF7, 0xC24D, 0x7FF8, 0xC24E, 0x7FF9, + 0xC24F, 0x7FFA, 0xC250, 0x7FFD, 0xC251, 0x7FFE, 0xC252, 0x7FFF, 0xC253, 0x8002, 0xC254, 0x8007, 0xC255, 0x8008, 0xC256, 0x8009, + 0xC257, 0x800A, 0xC258, 0x800E, 0xC259, 0x800F, 0xC25A, 0x8011, 0xC25B, 0x8013, 0xC25C, 0x801A, 0xC25D, 0x801B, 0xC25E, 0x801D, + 0xC25F, 0x801E, 0xC260, 0x801F, 0xC261, 0x8021, 0xC262, 0x8023, 0xC263, 0x8024, 0xC264, 0x802B, 0xC265, 0x802C, 0xC266, 0x802D, + 0xC267, 0x802E, 0xC268, 0x802F, 0xC269, 0x8030, 0xC26A, 0x8032, 0xC26B, 0x8034, 0xC26C, 0x8039, 0xC26D, 0x803A, 0xC26E, 0x803C, + 0xC26F, 0x803E, 0xC270, 0x8040, 0xC271, 0x8041, 0xC272, 0x8044, 0xC273, 0x8045, 0xC274, 0x8047, 0xC275, 0x8048, 0xC276, 0x8049, + 0xC277, 0x804E, 0xC278, 0x804F, 0xC279, 0x8050, 0xC27A, 0x8051, 0xC27B, 0x8053, 0xC27C, 0x8055, 0xC27D, 0x8056, 0xC27E, 0x8057, + 0xC280, 0x8059, 0xC281, 0x805B, 0xC282, 0x805C, 0xC283, 0x805D, 0xC284, 0x805E, 0xC285, 0x805F, 0xC286, 0x8060, 0xC287, 0x8061, + 0xC288, 0x8062, 0xC289, 0x8063, 0xC28A, 0x8064, 0xC28B, 0x8065, 0xC28C, 0x8066, 0xC28D, 0x8067, 0xC28E, 0x8068, 0xC28F, 0x806B, + 0xC290, 0x806C, 0xC291, 0x806D, 0xC292, 0x806E, 0xC293, 0x806F, 0xC294, 0x8070, 0xC295, 0x8072, 0xC296, 0x8073, 0xC297, 0x8074, + 0xC298, 0x8075, 0xC299, 0x8076, 0xC29A, 0x8077, 0xC29B, 0x8078, 0xC29C, 0x8079, 0xC29D, 0x807A, 0xC29E, 0x807B, 0xC29F, 0x807C, + 0xC2A0, 0x807D, 0xC2A1, 0x9686, 0xC2A2, 0x5784, 0xC2A3, 0x62E2, 0xC2A4, 0x9647, 0xC2A5, 0x697C, 0xC2A6, 0x5A04, 0xC2A7, 0x6402, + 0xC2A8, 0x7BD3, 0xC2A9, 0x6F0F, 0xC2AA, 0x964B, 0xC2AB, 0x82A6, 0xC2AC, 0x5362, 0xC2AD, 0x9885, 0xC2AE, 0x5E90, 0xC2AF, 0x7089, + 0xC2B0, 0x63B3, 0xC2B1, 0x5364, 0xC2B2, 0x864F, 0xC2B3, 0x9C81, 0xC2B4, 0x9E93, 0xC2B5, 0x788C, 0xC2B6, 0x9732, 0xC2B7, 0x8DEF, + 0xC2B8, 0x8D42, 0xC2B9, 0x9E7F, 0xC2BA, 0x6F5E, 0xC2BB, 0x7984, 0xC2BC, 0x5F55, 0xC2BD, 0x9646, 0xC2BE, 0x622E, 0xC2BF, 0x9A74, + 0xC2C0, 0x5415, 0xC2C1, 0x94DD, 0xC2C2, 0x4FA3, 0xC2C3, 0x65C5, 0xC2C4, 0x5C65, 0xC2C5, 0x5C61, 0xC2C6, 0x7F15, 0xC2C7, 0x8651, + 0xC2C8, 0x6C2F, 0xC2C9, 0x5F8B, 0xC2CA, 0x7387, 0xC2CB, 0x6EE4, 0xC2CC, 0x7EFF, 0xC2CD, 0x5CE6, 0xC2CE, 0x631B, 0xC2CF, 0x5B6A, + 0xC2D0, 0x6EE6, 0xC2D1, 0x5375, 0xC2D2, 0x4E71, 0xC2D3, 0x63A0, 0xC2D4, 0x7565, 0xC2D5, 0x62A1, 0xC2D6, 0x8F6E, 0xC2D7, 0x4F26, + 0xC2D8, 0x4ED1, 0xC2D9, 0x6CA6, 0xC2DA, 0x7EB6, 0xC2DB, 0x8BBA, 0xC2DC, 0x841D, 0xC2DD, 0x87BA, 0xC2DE, 0x7F57, 0xC2DF, 0x903B, + 0xC2E0, 0x9523, 0xC2E1, 0x7BA9, 0xC2E2, 0x9AA1, 0xC2E3, 0x88F8, 0xC2E4, 0x843D, 0xC2E5, 0x6D1B, 0xC2E6, 0x9A86, 0xC2E7, 0x7EDC, + 0xC2E8, 0x5988, 0xC2E9, 0x9EBB, 0xC2EA, 0x739B, 0xC2EB, 0x7801, 0xC2EC, 0x8682, 0xC2ED, 0x9A6C, 0xC2EE, 0x9A82, 0xC2EF, 0x561B, + 0xC2F0, 0x5417, 0xC2F1, 0x57CB, 0xC2F2, 0x4E70, 0xC2F3, 0x9EA6, 0xC2F4, 0x5356, 0xC2F5, 0x8FC8, 0xC2F6, 0x8109, 0xC2F7, 0x7792, + 0xC2F8, 0x9992, 0xC2F9, 0x86EE, 0xC2FA, 0x6EE1, 0xC2FB, 0x8513, 0xC2FC, 0x66FC, 0xC2FD, 0x6162, 0xC2FE, 0x6F2B, 0xC340, 0x807E, + 0xC341, 0x8081, 0xC342, 0x8082, 0xC343, 0x8085, 0xC344, 0x8088, 0xC345, 0x808A, 0xC346, 0x808D, 0xC347, 0x808E, 0xC348, 0x808F, + 0xC349, 0x8090, 0xC34A, 0x8091, 0xC34B, 0x8092, 0xC34C, 0x8094, 0xC34D, 0x8095, 0xC34E, 0x8097, 0xC34F, 0x8099, 0xC350, 0x809E, + 0xC351, 0x80A3, 0xC352, 0x80A6, 0xC353, 0x80A7, 0xC354, 0x80A8, 0xC355, 0x80AC, 0xC356, 0x80B0, 0xC357, 0x80B3, 0xC358, 0x80B5, + 0xC359, 0x80B6, 0xC35A, 0x80B8, 0xC35B, 0x80B9, 0xC35C, 0x80BB, 0xC35D, 0x80C5, 0xC35E, 0x80C7, 0xC35F, 0x80C8, 0xC360, 0x80C9, + 0xC361, 0x80CA, 0xC362, 0x80CB, 0xC363, 0x80CF, 0xC364, 0x80D0, 0xC365, 0x80D1, 0xC366, 0x80D2, 0xC367, 0x80D3, 0xC368, 0x80D4, + 0xC369, 0x80D5, 0xC36A, 0x80D8, 0xC36B, 0x80DF, 0xC36C, 0x80E0, 0xC36D, 0x80E2, 0xC36E, 0x80E3, 0xC36F, 0x80E6, 0xC370, 0x80EE, + 0xC371, 0x80F5, 0xC372, 0x80F7, 0xC373, 0x80F9, 0xC374, 0x80FB, 0xC375, 0x80FE, 0xC376, 0x80FF, 0xC377, 0x8100, 0xC378, 0x8101, + 0xC379, 0x8103, 0xC37A, 0x8104, 0xC37B, 0x8105, 0xC37C, 0x8107, 0xC37D, 0x8108, 0xC37E, 0x810B, 0xC380, 0x810C, 0xC381, 0x8115, + 0xC382, 0x8117, 0xC383, 0x8119, 0xC384, 0x811B, 0xC385, 0x811C, 0xC386, 0x811D, 0xC387, 0x811F, 0xC388, 0x8120, 0xC389, 0x8121, + 0xC38A, 0x8122, 0xC38B, 0x8123, 0xC38C, 0x8124, 0xC38D, 0x8125, 0xC38E, 0x8126, 0xC38F, 0x8127, 0xC390, 0x8128, 0xC391, 0x8129, + 0xC392, 0x812A, 0xC393, 0x812B, 0xC394, 0x812D, 0xC395, 0x812E, 0xC396, 0x8130, 0xC397, 0x8133, 0xC398, 0x8134, 0xC399, 0x8135, + 0xC39A, 0x8137, 0xC39B, 0x8139, 0xC39C, 0x813A, 0xC39D, 0x813B, 0xC39E, 0x813C, 0xC39F, 0x813D, 0xC3A0, 0x813F, 0xC3A1, 0x8C29, + 0xC3A2, 0x8292, 0xC3A3, 0x832B, 0xC3A4, 0x76F2, 0xC3A5, 0x6C13, 0xC3A6, 0x5FD9, 0xC3A7, 0x83BD, 0xC3A8, 0x732B, 0xC3A9, 0x8305, + 0xC3AA, 0x951A, 0xC3AB, 0x6BDB, 0xC3AC, 0x77DB, 0xC3AD, 0x94C6, 0xC3AE, 0x536F, 0xC3AF, 0x8302, 0xC3B0, 0x5192, 0xC3B1, 0x5E3D, + 0xC3B2, 0x8C8C, 0xC3B3, 0x8D38, 0xC3B4, 0x4E48, 0xC3B5, 0x73AB, 0xC3B6, 0x679A, 0xC3B7, 0x6885, 0xC3B8, 0x9176, 0xC3B9, 0x9709, + 0xC3BA, 0x7164, 0xC3BB, 0x6CA1, 0xC3BC, 0x7709, 0xC3BD, 0x5A92, 0xC3BE, 0x9541, 0xC3BF, 0x6BCF, 0xC3C0, 0x7F8E, 0xC3C1, 0x6627, + 0xC3C2, 0x5BD0, 0xC3C3, 0x59B9, 0xC3C4, 0x5A9A, 0xC3C5, 0x95E8, 0xC3C6, 0x95F7, 0xC3C7, 0x4EEC, 0xC3C8, 0x840C, 0xC3C9, 0x8499, + 0xC3CA, 0x6AAC, 0xC3CB, 0x76DF, 0xC3CC, 0x9530, 0xC3CD, 0x731B, 0xC3CE, 0x68A6, 0xC3CF, 0x5B5F, 0xC3D0, 0x772F, 0xC3D1, 0x919A, + 0xC3D2, 0x9761, 0xC3D3, 0x7CDC, 0xC3D4, 0x8FF7, 0xC3D5, 0x8C1C, 0xC3D6, 0x5F25, 0xC3D7, 0x7C73, 0xC3D8, 0x79D8, 0xC3D9, 0x89C5, + 0xC3DA, 0x6CCC, 0xC3DB, 0x871C, 0xC3DC, 0x5BC6, 0xC3DD, 0x5E42, 0xC3DE, 0x68C9, 0xC3DF, 0x7720, 0xC3E0, 0x7EF5, 0xC3E1, 0x5195, + 0xC3E2, 0x514D, 0xC3E3, 0x52C9, 0xC3E4, 0x5A29, 0xC3E5, 0x7F05, 0xC3E6, 0x9762, 0xC3E7, 0x82D7, 0xC3E8, 0x63CF, 0xC3E9, 0x7784, + 0xC3EA, 0x85D0, 0xC3EB, 0x79D2, 0xC3EC, 0x6E3A, 0xC3ED, 0x5E99, 0xC3EE, 0x5999, 0xC3EF, 0x8511, 0xC3F0, 0x706D, 0xC3F1, 0x6C11, + 0xC3F2, 0x62BF, 0xC3F3, 0x76BF, 0xC3F4, 0x654F, 0xC3F5, 0x60AF, 0xC3F6, 0x95FD, 0xC3F7, 0x660E, 0xC3F8, 0x879F, 0xC3F9, 0x9E23, + 0xC3FA, 0x94ED, 0xC3FB, 0x540D, 0xC3FC, 0x547D, 0xC3FD, 0x8C2C, 0xC3FE, 0x6478, 0xC440, 0x8140, 0xC441, 0x8141, 0xC442, 0x8142, + 0xC443, 0x8143, 0xC444, 0x8144, 0xC445, 0x8145, 0xC446, 0x8147, 0xC447, 0x8149, 0xC448, 0x814D, 0xC449, 0x814E, 0xC44A, 0x814F, + 0xC44B, 0x8152, 0xC44C, 0x8156, 0xC44D, 0x8157, 0xC44E, 0x8158, 0xC44F, 0x815B, 0xC450, 0x815C, 0xC451, 0x815D, 0xC452, 0x815E, + 0xC453, 0x815F, 0xC454, 0x8161, 0xC455, 0x8162, 0xC456, 0x8163, 0xC457, 0x8164, 0xC458, 0x8166, 0xC459, 0x8168, 0xC45A, 0x816A, + 0xC45B, 0x816B, 0xC45C, 0x816C, 0xC45D, 0x816F, 0xC45E, 0x8172, 0xC45F, 0x8173, 0xC460, 0x8175, 0xC461, 0x8176, 0xC462, 0x8177, + 0xC463, 0x8178, 0xC464, 0x8181, 0xC465, 0x8183, 0xC466, 0x8184, 0xC467, 0x8185, 0xC468, 0x8186, 0xC469, 0x8187, 0xC46A, 0x8189, + 0xC46B, 0x818B, 0xC46C, 0x818C, 0xC46D, 0x818D, 0xC46E, 0x818E, 0xC46F, 0x8190, 0xC470, 0x8192, 0xC471, 0x8193, 0xC472, 0x8194, + 0xC473, 0x8195, 0xC474, 0x8196, 0xC475, 0x8197, 0xC476, 0x8199, 0xC477, 0x819A, 0xC478, 0x819E, 0xC479, 0x819F, 0xC47A, 0x81A0, + 0xC47B, 0x81A1, 0xC47C, 0x81A2, 0xC47D, 0x81A4, 0xC47E, 0x81A5, 0xC480, 0x81A7, 0xC481, 0x81A9, 0xC482, 0x81AB, 0xC483, 0x81AC, + 0xC484, 0x81AD, 0xC485, 0x81AE, 0xC486, 0x81AF, 0xC487, 0x81B0, 0xC488, 0x81B1, 0xC489, 0x81B2, 0xC48A, 0x81B4, 0xC48B, 0x81B5, + 0xC48C, 0x81B6, 0xC48D, 0x81B7, 0xC48E, 0x81B8, 0xC48F, 0x81B9, 0xC490, 0x81BC, 0xC491, 0x81BD, 0xC492, 0x81BE, 0xC493, 0x81BF, + 0xC494, 0x81C4, 0xC495, 0x81C5, 0xC496, 0x81C7, 0xC497, 0x81C8, 0xC498, 0x81C9, 0xC499, 0x81CB, 0xC49A, 0x81CD, 0xC49B, 0x81CE, + 0xC49C, 0x81CF, 0xC49D, 0x81D0, 0xC49E, 0x81D1, 0xC49F, 0x81D2, 0xC4A0, 0x81D3, 0xC4A1, 0x6479, 0xC4A2, 0x8611, 0xC4A3, 0x6A21, + 0xC4A4, 0x819C, 0xC4A5, 0x78E8, 0xC4A6, 0x6469, 0xC4A7, 0x9B54, 0xC4A8, 0x62B9, 0xC4A9, 0x672B, 0xC4AA, 0x83AB, 0xC4AB, 0x58A8, + 0xC4AC, 0x9ED8, 0xC4AD, 0x6CAB, 0xC4AE, 0x6F20, 0xC4AF, 0x5BDE, 0xC4B0, 0x964C, 0xC4B1, 0x8C0B, 0xC4B2, 0x725F, 0xC4B3, 0x67D0, + 0xC4B4, 0x62C7, 0xC4B5, 0x7261, 0xC4B6, 0x4EA9, 0xC4B7, 0x59C6, 0xC4B8, 0x6BCD, 0xC4B9, 0x5893, 0xC4BA, 0x66AE, 0xC4BB, 0x5E55, + 0xC4BC, 0x52DF, 0xC4BD, 0x6155, 0xC4BE, 0x6728, 0xC4BF, 0x76EE, 0xC4C0, 0x7766, 0xC4C1, 0x7267, 0xC4C2, 0x7A46, 0xC4C3, 0x62FF, + 0xC4C4, 0x54EA, 0xC4C5, 0x5450, 0xC4C6, 0x94A0, 0xC4C7, 0x90A3, 0xC4C8, 0x5A1C, 0xC4C9, 0x7EB3, 0xC4CA, 0x6C16, 0xC4CB, 0x4E43, + 0xC4CC, 0x5976, 0xC4CD, 0x8010, 0xC4CE, 0x5948, 0xC4CF, 0x5357, 0xC4D0, 0x7537, 0xC4D1, 0x96BE, 0xC4D2, 0x56CA, 0xC4D3, 0x6320, + 0xC4D4, 0x8111, 0xC4D5, 0x607C, 0xC4D6, 0x95F9, 0xC4D7, 0x6DD6, 0xC4D8, 0x5462, 0xC4D9, 0x9981, 0xC4DA, 0x5185, 0xC4DB, 0x5AE9, + 0xC4DC, 0x80FD, 0xC4DD, 0x59AE, 0xC4DE, 0x9713, 0xC4DF, 0x502A, 0xC4E0, 0x6CE5, 0xC4E1, 0x5C3C, 0xC4E2, 0x62DF, 0xC4E3, 0x4F60, + 0xC4E4, 0x533F, 0xC4E5, 0x817B, 0xC4E6, 0x9006, 0xC4E7, 0x6EBA, 0xC4E8, 0x852B, 0xC4E9, 0x62C8, 0xC4EA, 0x5E74, 0xC4EB, 0x78BE, + 0xC4EC, 0x64B5, 0xC4ED, 0x637B, 0xC4EE, 0x5FF5, 0xC4EF, 0x5A18, 0xC4F0, 0x917F, 0xC4F1, 0x9E1F, 0xC4F2, 0x5C3F, 0xC4F3, 0x634F, + 0xC4F4, 0x8042, 0xC4F5, 0x5B7D, 0xC4F6, 0x556E, 0xC4F7, 0x954A, 0xC4F8, 0x954D, 0xC4F9, 0x6D85, 0xC4FA, 0x60A8, 0xC4FB, 0x67E0, + 0xC4FC, 0x72DE, 0xC4FD, 0x51DD, 0xC4FE, 0x5B81, 0xC540, 0x81D4, 0xC541, 0x81D5, 0xC542, 0x81D6, 0xC543, 0x81D7, 0xC544, 0x81D8, + 0xC545, 0x81D9, 0xC546, 0x81DA, 0xC547, 0x81DB, 0xC548, 0x81DC, 0xC549, 0x81DD, 0xC54A, 0x81DE, 0xC54B, 0x81DF, 0xC54C, 0x81E0, + 0xC54D, 0x81E1, 0xC54E, 0x81E2, 0xC54F, 0x81E4, 0xC550, 0x81E5, 0xC551, 0x81E6, 0xC552, 0x81E8, 0xC553, 0x81E9, 0xC554, 0x81EB, + 0xC555, 0x81EE, 0xC556, 0x81EF, 0xC557, 0x81F0, 0xC558, 0x81F1, 0xC559, 0x81F2, 0xC55A, 0x81F5, 0xC55B, 0x81F6, 0xC55C, 0x81F7, + 0xC55D, 0x81F8, 0xC55E, 0x81F9, 0xC55F, 0x81FA, 0xC560, 0x81FD, 0xC561, 0x81FF, 0xC562, 0x8203, 0xC563, 0x8207, 0xC564, 0x8208, + 0xC565, 0x8209, 0xC566, 0x820A, 0xC567, 0x820B, 0xC568, 0x820E, 0xC569, 0x820F, 0xC56A, 0x8211, 0xC56B, 0x8213, 0xC56C, 0x8215, + 0xC56D, 0x8216, 0xC56E, 0x8217, 0xC56F, 0x8218, 0xC570, 0x8219, 0xC571, 0x821A, 0xC572, 0x821D, 0xC573, 0x8220, 0xC574, 0x8224, + 0xC575, 0x8225, 0xC576, 0x8226, 0xC577, 0x8227, 0xC578, 0x8229, 0xC579, 0x822E, 0xC57A, 0x8232, 0xC57B, 0x823A, 0xC57C, 0x823C, + 0xC57D, 0x823D, 0xC57E, 0x823F, 0xC580, 0x8240, 0xC581, 0x8241, 0xC582, 0x8242, 0xC583, 0x8243, 0xC584, 0x8245, 0xC585, 0x8246, + 0xC586, 0x8248, 0xC587, 0x824A, 0xC588, 0x824C, 0xC589, 0x824D, 0xC58A, 0x824E, 0xC58B, 0x8250, 0xC58C, 0x8251, 0xC58D, 0x8252, + 0xC58E, 0x8253, 0xC58F, 0x8254, 0xC590, 0x8255, 0xC591, 0x8256, 0xC592, 0x8257, 0xC593, 0x8259, 0xC594, 0x825B, 0xC595, 0x825C, + 0xC596, 0x825D, 0xC597, 0x825E, 0xC598, 0x8260, 0xC599, 0x8261, 0xC59A, 0x8262, 0xC59B, 0x8263, 0xC59C, 0x8264, 0xC59D, 0x8265, + 0xC59E, 0x8266, 0xC59F, 0x8267, 0xC5A0, 0x8269, 0xC5A1, 0x62E7, 0xC5A2, 0x6CDE, 0xC5A3, 0x725B, 0xC5A4, 0x626D, 0xC5A5, 0x94AE, + 0xC5A6, 0x7EBD, 0xC5A7, 0x8113, 0xC5A8, 0x6D53, 0xC5A9, 0x519C, 0xC5AA, 0x5F04, 0xC5AB, 0x5974, 0xC5AC, 0x52AA, 0xC5AD, 0x6012, + 0xC5AE, 0x5973, 0xC5AF, 0x6696, 0xC5B0, 0x8650, 0xC5B1, 0x759F, 0xC5B2, 0x632A, 0xC5B3, 0x61E6, 0xC5B4, 0x7CEF, 0xC5B5, 0x8BFA, + 0xC5B6, 0x54E6, 0xC5B7, 0x6B27, 0xC5B8, 0x9E25, 0xC5B9, 0x6BB4, 0xC5BA, 0x85D5, 0xC5BB, 0x5455, 0xC5BC, 0x5076, 0xC5BD, 0x6CA4, + 0xC5BE, 0x556A, 0xC5BF, 0x8DB4, 0xC5C0, 0x722C, 0xC5C1, 0x5E15, 0xC5C2, 0x6015, 0xC5C3, 0x7436, 0xC5C4, 0x62CD, 0xC5C5, 0x6392, + 0xC5C6, 0x724C, 0xC5C7, 0x5F98, 0xC5C8, 0x6E43, 0xC5C9, 0x6D3E, 0xC5CA, 0x6500, 0xC5CB, 0x6F58, 0xC5CC, 0x76D8, 0xC5CD, 0x78D0, + 0xC5CE, 0x76FC, 0xC5CF, 0x7554, 0xC5D0, 0x5224, 0xC5D1, 0x53DB, 0xC5D2, 0x4E53, 0xC5D3, 0x5E9E, 0xC5D4, 0x65C1, 0xC5D5, 0x802A, + 0xC5D6, 0x80D6, 0xC5D7, 0x629B, 0xC5D8, 0x5486, 0xC5D9, 0x5228, 0xC5DA, 0x70AE, 0xC5DB, 0x888D, 0xC5DC, 0x8DD1, 0xC5DD, 0x6CE1, + 0xC5DE, 0x5478, 0xC5DF, 0x80DA, 0xC5E0, 0x57F9, 0xC5E1, 0x88F4, 0xC5E2, 0x8D54, 0xC5E3, 0x966A, 0xC5E4, 0x914D, 0xC5E5, 0x4F69, + 0xC5E6, 0x6C9B, 0xC5E7, 0x55B7, 0xC5E8, 0x76C6, 0xC5E9, 0x7830, 0xC5EA, 0x62A8, 0xC5EB, 0x70F9, 0xC5EC, 0x6F8E, 0xC5ED, 0x5F6D, + 0xC5EE, 0x84EC, 0xC5EF, 0x68DA, 0xC5F0, 0x787C, 0xC5F1, 0x7BF7, 0xC5F2, 0x81A8, 0xC5F3, 0x670B, 0xC5F4, 0x9E4F, 0xC5F5, 0x6367, + 0xC5F6, 0x78B0, 0xC5F7, 0x576F, 0xC5F8, 0x7812, 0xC5F9, 0x9739, 0xC5FA, 0x6279, 0xC5FB, 0x62AB, 0xC5FC, 0x5288, 0xC5FD, 0x7435, + 0xC5FE, 0x6BD7, 0xC640, 0x826A, 0xC641, 0x826B, 0xC642, 0x826C, 0xC643, 0x826D, 0xC644, 0x8271, 0xC645, 0x8275, 0xC646, 0x8276, + 0xC647, 0x8277, 0xC648, 0x8278, 0xC649, 0x827B, 0xC64A, 0x827C, 0xC64B, 0x8280, 0xC64C, 0x8281, 0xC64D, 0x8283, 0xC64E, 0x8285, + 0xC64F, 0x8286, 0xC650, 0x8287, 0xC651, 0x8289, 0xC652, 0x828C, 0xC653, 0x8290, 0xC654, 0x8293, 0xC655, 0x8294, 0xC656, 0x8295, + 0xC657, 0x8296, 0xC658, 0x829A, 0xC659, 0x829B, 0xC65A, 0x829E, 0xC65B, 0x82A0, 0xC65C, 0x82A2, 0xC65D, 0x82A3, 0xC65E, 0x82A7, + 0xC65F, 0x82B2, 0xC660, 0x82B5, 0xC661, 0x82B6, 0xC662, 0x82BA, 0xC663, 0x82BB, 0xC664, 0x82BC, 0xC665, 0x82BF, 0xC666, 0x82C0, + 0xC667, 0x82C2, 0xC668, 0x82C3, 0xC669, 0x82C5, 0xC66A, 0x82C6, 0xC66B, 0x82C9, 0xC66C, 0x82D0, 0xC66D, 0x82D6, 0xC66E, 0x82D9, + 0xC66F, 0x82DA, 0xC670, 0x82DD, 0xC671, 0x82E2, 0xC672, 0x82E7, 0xC673, 0x82E8, 0xC674, 0x82E9, 0xC675, 0x82EA, 0xC676, 0x82EC, + 0xC677, 0x82ED, 0xC678, 0x82EE, 0xC679, 0x82F0, 0xC67A, 0x82F2, 0xC67B, 0x82F3, 0xC67C, 0x82F5, 0xC67D, 0x82F6, 0xC67E, 0x82F8, + 0xC680, 0x82FA, 0xC681, 0x82FC, 0xC682, 0x82FD, 0xC683, 0x82FE, 0xC684, 0x82FF, 0xC685, 0x8300, 0xC686, 0x830A, 0xC687, 0x830B, + 0xC688, 0x830D, 0xC689, 0x8310, 0xC68A, 0x8312, 0xC68B, 0x8313, 0xC68C, 0x8316, 0xC68D, 0x8318, 0xC68E, 0x8319, 0xC68F, 0x831D, + 0xC690, 0x831E, 0xC691, 0x831F, 0xC692, 0x8320, 0xC693, 0x8321, 0xC694, 0x8322, 0xC695, 0x8323, 0xC696, 0x8324, 0xC697, 0x8325, + 0xC698, 0x8326, 0xC699, 0x8329, 0xC69A, 0x832A, 0xC69B, 0x832E, 0xC69C, 0x8330, 0xC69D, 0x8332, 0xC69E, 0x8337, 0xC69F, 0x833B, + 0xC6A0, 0x833D, 0xC6A1, 0x5564, 0xC6A2, 0x813E, 0xC6A3, 0x75B2, 0xC6A4, 0x76AE, 0xC6A5, 0x5339, 0xC6A6, 0x75DE, 0xC6A7, 0x50FB, + 0xC6A8, 0x5C41, 0xC6A9, 0x8B6C, 0xC6AA, 0x7BC7, 0xC6AB, 0x504F, 0xC6AC, 0x7247, 0xC6AD, 0x9A97, 0xC6AE, 0x98D8, 0xC6AF, 0x6F02, + 0xC6B0, 0x74E2, 0xC6B1, 0x7968, 0xC6B2, 0x6487, 0xC6B3, 0x77A5, 0xC6B4, 0x62FC, 0xC6B5, 0x9891, 0xC6B6, 0x8D2B, 0xC6B7, 0x54C1, + 0xC6B8, 0x8058, 0xC6B9, 0x4E52, 0xC6BA, 0x576A, 0xC6BB, 0x82F9, 0xC6BC, 0x840D, 0xC6BD, 0x5E73, 0xC6BE, 0x51ED, 0xC6BF, 0x74F6, + 0xC6C0, 0x8BC4, 0xC6C1, 0x5C4F, 0xC6C2, 0x5761, 0xC6C3, 0x6CFC, 0xC6C4, 0x9887, 0xC6C5, 0x5A46, 0xC6C6, 0x7834, 0xC6C7, 0x9B44, + 0xC6C8, 0x8FEB, 0xC6C9, 0x7C95, 0xC6CA, 0x5256, 0xC6CB, 0x6251, 0xC6CC, 0x94FA, 0xC6CD, 0x4EC6, 0xC6CE, 0x8386, 0xC6CF, 0x8461, + 0xC6D0, 0x83E9, 0xC6D1, 0x84B2, 0xC6D2, 0x57D4, 0xC6D3, 0x6734, 0xC6D4, 0x5703, 0xC6D5, 0x666E, 0xC6D6, 0x6D66, 0xC6D7, 0x8C31, + 0xC6D8, 0x66DD, 0xC6D9, 0x7011, 0xC6DA, 0x671F, 0xC6DB, 0x6B3A, 0xC6DC, 0x6816, 0xC6DD, 0x621A, 0xC6DE, 0x59BB, 0xC6DF, 0x4E03, + 0xC6E0, 0x51C4, 0xC6E1, 0x6F06, 0xC6E2, 0x67D2, 0xC6E3, 0x6C8F, 0xC6E4, 0x5176, 0xC6E5, 0x68CB, 0xC6E6, 0x5947, 0xC6E7, 0x6B67, + 0xC6E8, 0x7566, 0xC6E9, 0x5D0E, 0xC6EA, 0x8110, 0xC6EB, 0x9F50, 0xC6EC, 0x65D7, 0xC6ED, 0x7948, 0xC6EE, 0x7941, 0xC6EF, 0x9A91, + 0xC6F0, 0x8D77, 0xC6F1, 0x5C82, 0xC6F2, 0x4E5E, 0xC6F3, 0x4F01, 0xC6F4, 0x542F, 0xC6F5, 0x5951, 0xC6F6, 0x780C, 0xC6F7, 0x5668, + 0xC6F8, 0x6C14, 0xC6F9, 0x8FC4, 0xC6FA, 0x5F03, 0xC6FB, 0x6C7D, 0xC6FC, 0x6CE3, 0xC6FD, 0x8BAB, 0xC6FE, 0x6390, 0xC740, 0x833E, + 0xC741, 0x833F, 0xC742, 0x8341, 0xC743, 0x8342, 0xC744, 0x8344, 0xC745, 0x8345, 0xC746, 0x8348, 0xC747, 0x834A, 0xC748, 0x834B, + 0xC749, 0x834C, 0xC74A, 0x834D, 0xC74B, 0x834E, 0xC74C, 0x8353, 0xC74D, 0x8355, 0xC74E, 0x8356, 0xC74F, 0x8357, 0xC750, 0x8358, + 0xC751, 0x8359, 0xC752, 0x835D, 0xC753, 0x8362, 0xC754, 0x8370, 0xC755, 0x8371, 0xC756, 0x8372, 0xC757, 0x8373, 0xC758, 0x8374, + 0xC759, 0x8375, 0xC75A, 0x8376, 0xC75B, 0x8379, 0xC75C, 0x837A, 0xC75D, 0x837E, 0xC75E, 0x837F, 0xC75F, 0x8380, 0xC760, 0x8381, + 0xC761, 0x8382, 0xC762, 0x8383, 0xC763, 0x8384, 0xC764, 0x8387, 0xC765, 0x8388, 0xC766, 0x838A, 0xC767, 0x838B, 0xC768, 0x838C, + 0xC769, 0x838D, 0xC76A, 0x838F, 0xC76B, 0x8390, 0xC76C, 0x8391, 0xC76D, 0x8394, 0xC76E, 0x8395, 0xC76F, 0x8396, 0xC770, 0x8397, + 0xC771, 0x8399, 0xC772, 0x839A, 0xC773, 0x839D, 0xC774, 0x839F, 0xC775, 0x83A1, 0xC776, 0x83A2, 0xC777, 0x83A3, 0xC778, 0x83A4, + 0xC779, 0x83A5, 0xC77A, 0x83A6, 0xC77B, 0x83A7, 0xC77C, 0x83AC, 0xC77D, 0x83AD, 0xC77E, 0x83AE, 0xC780, 0x83AF, 0xC781, 0x83B5, + 0xC782, 0x83BB, 0xC783, 0x83BE, 0xC784, 0x83BF, 0xC785, 0x83C2, 0xC786, 0x83C3, 0xC787, 0x83C4, 0xC788, 0x83C6, 0xC789, 0x83C8, + 0xC78A, 0x83C9, 0xC78B, 0x83CB, 0xC78C, 0x83CD, 0xC78D, 0x83CE, 0xC78E, 0x83D0, 0xC78F, 0x83D1, 0xC790, 0x83D2, 0xC791, 0x83D3, + 0xC792, 0x83D5, 0xC793, 0x83D7, 0xC794, 0x83D9, 0xC795, 0x83DA, 0xC796, 0x83DB, 0xC797, 0x83DE, 0xC798, 0x83E2, 0xC799, 0x83E3, + 0xC79A, 0x83E4, 0xC79B, 0x83E6, 0xC79C, 0x83E7, 0xC79D, 0x83E8, 0xC79E, 0x83EB, 0xC79F, 0x83EC, 0xC7A0, 0x83ED, 0xC7A1, 0x6070, + 0xC7A2, 0x6D3D, 0xC7A3, 0x7275, 0xC7A4, 0x6266, 0xC7A5, 0x948E, 0xC7A6, 0x94C5, 0xC7A7, 0x5343, 0xC7A8, 0x8FC1, 0xC7A9, 0x7B7E, + 0xC7AA, 0x4EDF, 0xC7AB, 0x8C26, 0xC7AC, 0x4E7E, 0xC7AD, 0x9ED4, 0xC7AE, 0x94B1, 0xC7AF, 0x94B3, 0xC7B0, 0x524D, 0xC7B1, 0x6F5C, + 0xC7B2, 0x9063, 0xC7B3, 0x6D45, 0xC7B4, 0x8C34, 0xC7B5, 0x5811, 0xC7B6, 0x5D4C, 0xC7B7, 0x6B20, 0xC7B8, 0x6B49, 0xC7B9, 0x67AA, + 0xC7BA, 0x545B, 0xC7BB, 0x8154, 0xC7BC, 0x7F8C, 0xC7BD, 0x5899, 0xC7BE, 0x8537, 0xC7BF, 0x5F3A, 0xC7C0, 0x62A2, 0xC7C1, 0x6A47, + 0xC7C2, 0x9539, 0xC7C3, 0x6572, 0xC7C4, 0x6084, 0xC7C5, 0x6865, 0xC7C6, 0x77A7, 0xC7C7, 0x4E54, 0xC7C8, 0x4FA8, 0xC7C9, 0x5DE7, + 0xC7CA, 0x9798, 0xC7CB, 0x64AC, 0xC7CC, 0x7FD8, 0xC7CD, 0x5CED, 0xC7CE, 0x4FCF, 0xC7CF, 0x7A8D, 0xC7D0, 0x5207, 0xC7D1, 0x8304, + 0xC7D2, 0x4E14, 0xC7D3, 0x602F, 0xC7D4, 0x7A83, 0xC7D5, 0x94A6, 0xC7D6, 0x4FB5, 0xC7D7, 0x4EB2, 0xC7D8, 0x79E6, 0xC7D9, 0x7434, + 0xC7DA, 0x52E4, 0xC7DB, 0x82B9, 0xC7DC, 0x64D2, 0xC7DD, 0x79BD, 0xC7DE, 0x5BDD, 0xC7DF, 0x6C81, 0xC7E0, 0x9752, 0xC7E1, 0x8F7B, + 0xC7E2, 0x6C22, 0xC7E3, 0x503E, 0xC7E4, 0x537F, 0xC7E5, 0x6E05, 0xC7E6, 0x64CE, 0xC7E7, 0x6674, 0xC7E8, 0x6C30, 0xC7E9, 0x60C5, + 0xC7EA, 0x9877, 0xC7EB, 0x8BF7, 0xC7EC, 0x5E86, 0xC7ED, 0x743C, 0xC7EE, 0x7A77, 0xC7EF, 0x79CB, 0xC7F0, 0x4E18, 0xC7F1, 0x90B1, + 0xC7F2, 0x7403, 0xC7F3, 0x6C42, 0xC7F4, 0x56DA, 0xC7F5, 0x914B, 0xC7F6, 0x6CC5, 0xC7F7, 0x8D8B, 0xC7F8, 0x533A, 0xC7F9, 0x86C6, + 0xC7FA, 0x66F2, 0xC7FB, 0x8EAF, 0xC7FC, 0x5C48, 0xC7FD, 0x9A71, 0xC7FE, 0x6E20, 0xC840, 0x83EE, 0xC841, 0x83EF, 0xC842, 0x83F3, + 0xC843, 0x83F4, 0xC844, 0x83F5, 0xC845, 0x83F6, 0xC846, 0x83F7, 0xC847, 0x83FA, 0xC848, 0x83FB, 0xC849, 0x83FC, 0xC84A, 0x83FE, + 0xC84B, 0x83FF, 0xC84C, 0x8400, 0xC84D, 0x8402, 0xC84E, 0x8405, 0xC84F, 0x8407, 0xC850, 0x8408, 0xC851, 0x8409, 0xC852, 0x840A, + 0xC853, 0x8410, 0xC854, 0x8412, 0xC855, 0x8413, 0xC856, 0x8414, 0xC857, 0x8415, 0xC858, 0x8416, 0xC859, 0x8417, 0xC85A, 0x8419, + 0xC85B, 0x841A, 0xC85C, 0x841B, 0xC85D, 0x841E, 0xC85E, 0x841F, 0xC85F, 0x8420, 0xC860, 0x8421, 0xC861, 0x8422, 0xC862, 0x8423, + 0xC863, 0x8429, 0xC864, 0x842A, 0xC865, 0x842B, 0xC866, 0x842C, 0xC867, 0x842D, 0xC868, 0x842E, 0xC869, 0x842F, 0xC86A, 0x8430, + 0xC86B, 0x8432, 0xC86C, 0x8433, 0xC86D, 0x8434, 0xC86E, 0x8435, 0xC86F, 0x8436, 0xC870, 0x8437, 0xC871, 0x8439, 0xC872, 0x843A, + 0xC873, 0x843B, 0xC874, 0x843E, 0xC875, 0x843F, 0xC876, 0x8440, 0xC877, 0x8441, 0xC878, 0x8442, 0xC879, 0x8443, 0xC87A, 0x8444, + 0xC87B, 0x8445, 0xC87C, 0x8447, 0xC87D, 0x8448, 0xC87E, 0x8449, 0xC880, 0x844A, 0xC881, 0x844B, 0xC882, 0x844C, 0xC883, 0x844D, + 0xC884, 0x844E, 0xC885, 0x844F, 0xC886, 0x8450, 0xC887, 0x8452, 0xC888, 0x8453, 0xC889, 0x8454, 0xC88A, 0x8455, 0xC88B, 0x8456, + 0xC88C, 0x8458, 0xC88D, 0x845D, 0xC88E, 0x845E, 0xC88F, 0x845F, 0xC890, 0x8460, 0xC891, 0x8462, 0xC892, 0x8464, 0xC893, 0x8465, + 0xC894, 0x8466, 0xC895, 0x8467, 0xC896, 0x8468, 0xC897, 0x846A, 0xC898, 0x846E, 0xC899, 0x846F, 0xC89A, 0x8470, 0xC89B, 0x8472, + 0xC89C, 0x8474, 0xC89D, 0x8477, 0xC89E, 0x8479, 0xC89F, 0x847B, 0xC8A0, 0x847C, 0xC8A1, 0x53D6, 0xC8A2, 0x5A36, 0xC8A3, 0x9F8B, + 0xC8A4, 0x8DA3, 0xC8A5, 0x53BB, 0xC8A6, 0x5708, 0xC8A7, 0x98A7, 0xC8A8, 0x6743, 0xC8A9, 0x919B, 0xC8AA, 0x6CC9, 0xC8AB, 0x5168, + 0xC8AC, 0x75CA, 0xC8AD, 0x62F3, 0xC8AE, 0x72AC, 0xC8AF, 0x5238, 0xC8B0, 0x529D, 0xC8B1, 0x7F3A, 0xC8B2, 0x7094, 0xC8B3, 0x7638, + 0xC8B4, 0x5374, 0xC8B5, 0x9E4A, 0xC8B6, 0x69B7, 0xC8B7, 0x786E, 0xC8B8, 0x96C0, 0xC8B9, 0x88D9, 0xC8BA, 0x7FA4, 0xC8BB, 0x7136, + 0xC8BC, 0x71C3, 0xC8BD, 0x5189, 0xC8BE, 0x67D3, 0xC8BF, 0x74E4, 0xC8C0, 0x58E4, 0xC8C1, 0x6518, 0xC8C2, 0x56B7, 0xC8C3, 0x8BA9, + 0xC8C4, 0x9976, 0xC8C5, 0x6270, 0xC8C6, 0x7ED5, 0xC8C7, 0x60F9, 0xC8C8, 0x70ED, 0xC8C9, 0x58EC, 0xC8CA, 0x4EC1, 0xC8CB, 0x4EBA, + 0xC8CC, 0x5FCD, 0xC8CD, 0x97E7, 0xC8CE, 0x4EFB, 0xC8CF, 0x8BA4, 0xC8D0, 0x5203, 0xC8D1, 0x598A, 0xC8D2, 0x7EAB, 0xC8D3, 0x6254, + 0xC8D4, 0x4ECD, 0xC8D5, 0x65E5, 0xC8D6, 0x620E, 0xC8D7, 0x8338, 0xC8D8, 0x84C9, 0xC8D9, 0x8363, 0xC8DA, 0x878D, 0xC8DB, 0x7194, + 0xC8DC, 0x6EB6, 0xC8DD, 0x5BB9, 0xC8DE, 0x7ED2, 0xC8DF, 0x5197, 0xC8E0, 0x63C9, 0xC8E1, 0x67D4, 0xC8E2, 0x8089, 0xC8E3, 0x8339, + 0xC8E4, 0x8815, 0xC8E5, 0x5112, 0xC8E6, 0x5B7A, 0xC8E7, 0x5982, 0xC8E8, 0x8FB1, 0xC8E9, 0x4E73, 0xC8EA, 0x6C5D, 0xC8EB, 0x5165, + 0xC8EC, 0x8925, 0xC8ED, 0x8F6F, 0xC8EE, 0x962E, 0xC8EF, 0x854A, 0xC8F0, 0x745E, 0xC8F1, 0x9510, 0xC8F2, 0x95F0, 0xC8F3, 0x6DA6, + 0xC8F4, 0x82E5, 0xC8F5, 0x5F31, 0xC8F6, 0x6492, 0xC8F7, 0x6D12, 0xC8F8, 0x8428, 0xC8F9, 0x816E, 0xC8FA, 0x9CC3, 0xC8FB, 0x585E, + 0xC8FC, 0x8D5B, 0xC8FD, 0x4E09, 0xC8FE, 0x53C1, 0xC940, 0x847D, 0xC941, 0x847E, 0xC942, 0x847F, 0xC943, 0x8480, 0xC944, 0x8481, + 0xC945, 0x8483, 0xC946, 0x8484, 0xC947, 0x8485, 0xC948, 0x8486, 0xC949, 0x848A, 0xC94A, 0x848D, 0xC94B, 0x848F, 0xC94C, 0x8490, + 0xC94D, 0x8491, 0xC94E, 0x8492, 0xC94F, 0x8493, 0xC950, 0x8494, 0xC951, 0x8495, 0xC952, 0x8496, 0xC953, 0x8498, 0xC954, 0x849A, + 0xC955, 0x849B, 0xC956, 0x849D, 0xC957, 0x849E, 0xC958, 0x849F, 0xC959, 0x84A0, 0xC95A, 0x84A2, 0xC95B, 0x84A3, 0xC95C, 0x84A4, + 0xC95D, 0x84A5, 0xC95E, 0x84A6, 0xC95F, 0x84A7, 0xC960, 0x84A8, 0xC961, 0x84A9, 0xC962, 0x84AA, 0xC963, 0x84AB, 0xC964, 0x84AC, + 0xC965, 0x84AD, 0xC966, 0x84AE, 0xC967, 0x84B0, 0xC968, 0x84B1, 0xC969, 0x84B3, 0xC96A, 0x84B5, 0xC96B, 0x84B6, 0xC96C, 0x84B7, + 0xC96D, 0x84BB, 0xC96E, 0x84BC, 0xC96F, 0x84BE, 0xC970, 0x84C0, 0xC971, 0x84C2, 0xC972, 0x84C3, 0xC973, 0x84C5, 0xC974, 0x84C6, + 0xC975, 0x84C7, 0xC976, 0x84C8, 0xC977, 0x84CB, 0xC978, 0x84CC, 0xC979, 0x84CE, 0xC97A, 0x84CF, 0xC97B, 0x84D2, 0xC97C, 0x84D4, + 0xC97D, 0x84D5, 0xC97E, 0x84D7, 0xC980, 0x84D8, 0xC981, 0x84D9, 0xC982, 0x84DA, 0xC983, 0x84DB, 0xC984, 0x84DC, 0xC985, 0x84DE, + 0xC986, 0x84E1, 0xC987, 0x84E2, 0xC988, 0x84E4, 0xC989, 0x84E7, 0xC98A, 0x84E8, 0xC98B, 0x84E9, 0xC98C, 0x84EA, 0xC98D, 0x84EB, + 0xC98E, 0x84ED, 0xC98F, 0x84EE, 0xC990, 0x84EF, 0xC991, 0x84F1, 0xC992, 0x84F2, 0xC993, 0x84F3, 0xC994, 0x84F4, 0xC995, 0x84F5, + 0xC996, 0x84F6, 0xC997, 0x84F7, 0xC998, 0x84F8, 0xC999, 0x84F9, 0xC99A, 0x84FA, 0xC99B, 0x84FB, 0xC99C, 0x84FD, 0xC99D, 0x84FE, + 0xC99E, 0x8500, 0xC99F, 0x8501, 0xC9A0, 0x8502, 0xC9A1, 0x4F1E, 0xC9A2, 0x6563, 0xC9A3, 0x6851, 0xC9A4, 0x55D3, 0xC9A5, 0x4E27, + 0xC9A6, 0x6414, 0xC9A7, 0x9A9A, 0xC9A8, 0x626B, 0xC9A9, 0x5AC2, 0xC9AA, 0x745F, 0xC9AB, 0x8272, 0xC9AC, 0x6DA9, 0xC9AD, 0x68EE, + 0xC9AE, 0x50E7, 0xC9AF, 0x838E, 0xC9B0, 0x7802, 0xC9B1, 0x6740, 0xC9B2, 0x5239, 0xC9B3, 0x6C99, 0xC9B4, 0x7EB1, 0xC9B5, 0x50BB, + 0xC9B6, 0x5565, 0xC9B7, 0x715E, 0xC9B8, 0x7B5B, 0xC9B9, 0x6652, 0xC9BA, 0x73CA, 0xC9BB, 0x82EB, 0xC9BC, 0x6749, 0xC9BD, 0x5C71, + 0xC9BE, 0x5220, 0xC9BF, 0x717D, 0xC9C0, 0x886B, 0xC9C1, 0x95EA, 0xC9C2, 0x9655, 0xC9C3, 0x64C5, 0xC9C4, 0x8D61, 0xC9C5, 0x81B3, + 0xC9C6, 0x5584, 0xC9C7, 0x6C55, 0xC9C8, 0x6247, 0xC9C9, 0x7F2E, 0xC9CA, 0x5892, 0xC9CB, 0x4F24, 0xC9CC, 0x5546, 0xC9CD, 0x8D4F, + 0xC9CE, 0x664C, 0xC9CF, 0x4E0A, 0xC9D0, 0x5C1A, 0xC9D1, 0x88F3, 0xC9D2, 0x68A2, 0xC9D3, 0x634E, 0xC9D4, 0x7A0D, 0xC9D5, 0x70E7, + 0xC9D6, 0x828D, 0xC9D7, 0x52FA, 0xC9D8, 0x97F6, 0xC9D9, 0x5C11, 0xC9DA, 0x54E8, 0xC9DB, 0x90B5, 0xC9DC, 0x7ECD, 0xC9DD, 0x5962, + 0xC9DE, 0x8D4A, 0xC9DF, 0x86C7, 0xC9E0, 0x820C, 0xC9E1, 0x820D, 0xC9E2, 0x8D66, 0xC9E3, 0x6444, 0xC9E4, 0x5C04, 0xC9E5, 0x6151, + 0xC9E6, 0x6D89, 0xC9E7, 0x793E, 0xC9E8, 0x8BBE, 0xC9E9, 0x7837, 0xC9EA, 0x7533, 0xC9EB, 0x547B, 0xC9EC, 0x4F38, 0xC9ED, 0x8EAB, + 0xC9EE, 0x6DF1, 0xC9EF, 0x5A20, 0xC9F0, 0x7EC5, 0xC9F1, 0x795E, 0xC9F2, 0x6C88, 0xC9F3, 0x5BA1, 0xC9F4, 0x5A76, 0xC9F5, 0x751A, + 0xC9F6, 0x80BE, 0xC9F7, 0x614E, 0xC9F8, 0x6E17, 0xC9F9, 0x58F0, 0xC9FA, 0x751F, 0xC9FB, 0x7525, 0xC9FC, 0x7272, 0xC9FD, 0x5347, + 0xC9FE, 0x7EF3, 0xCA40, 0x8503, 0xCA41, 0x8504, 0xCA42, 0x8505, 0xCA43, 0x8506, 0xCA44, 0x8507, 0xCA45, 0x8508, 0xCA46, 0x8509, + 0xCA47, 0x850A, 0xCA48, 0x850B, 0xCA49, 0x850D, 0xCA4A, 0x850E, 0xCA4B, 0x850F, 0xCA4C, 0x8510, 0xCA4D, 0x8512, 0xCA4E, 0x8514, + 0xCA4F, 0x8515, 0xCA50, 0x8516, 0xCA51, 0x8518, 0xCA52, 0x8519, 0xCA53, 0x851B, 0xCA54, 0x851C, 0xCA55, 0x851D, 0xCA56, 0x851E, + 0xCA57, 0x8520, 0xCA58, 0x8522, 0xCA59, 0x8523, 0xCA5A, 0x8524, 0xCA5B, 0x8525, 0xCA5C, 0x8526, 0xCA5D, 0x8527, 0xCA5E, 0x8528, + 0xCA5F, 0x8529, 0xCA60, 0x852A, 0xCA61, 0x852D, 0xCA62, 0x852E, 0xCA63, 0x852F, 0xCA64, 0x8530, 0xCA65, 0x8531, 0xCA66, 0x8532, + 0xCA67, 0x8533, 0xCA68, 0x8534, 0xCA69, 0x8535, 0xCA6A, 0x8536, 0xCA6B, 0x853E, 0xCA6C, 0x853F, 0xCA6D, 0x8540, 0xCA6E, 0x8541, + 0xCA6F, 0x8542, 0xCA70, 0x8544, 0xCA71, 0x8545, 0xCA72, 0x8546, 0xCA73, 0x8547, 0xCA74, 0x854B, 0xCA75, 0x854C, 0xCA76, 0x854D, + 0xCA77, 0x854E, 0xCA78, 0x854F, 0xCA79, 0x8550, 0xCA7A, 0x8551, 0xCA7B, 0x8552, 0xCA7C, 0x8553, 0xCA7D, 0x8554, 0xCA7E, 0x8555, + 0xCA80, 0x8557, 0xCA81, 0x8558, 0xCA82, 0x855A, 0xCA83, 0x855B, 0xCA84, 0x855C, 0xCA85, 0x855D, 0xCA86, 0x855F, 0xCA87, 0x8560, + 0xCA88, 0x8561, 0xCA89, 0x8562, 0xCA8A, 0x8563, 0xCA8B, 0x8565, 0xCA8C, 0x8566, 0xCA8D, 0x8567, 0xCA8E, 0x8569, 0xCA8F, 0x856A, + 0xCA90, 0x856B, 0xCA91, 0x856C, 0xCA92, 0x856D, 0xCA93, 0x856E, 0xCA94, 0x856F, 0xCA95, 0x8570, 0xCA96, 0x8571, 0xCA97, 0x8573, + 0xCA98, 0x8575, 0xCA99, 0x8576, 0xCA9A, 0x8577, 0xCA9B, 0x8578, 0xCA9C, 0x857C, 0xCA9D, 0x857D, 0xCA9E, 0x857F, 0xCA9F, 0x8580, + 0xCAA0, 0x8581, 0xCAA1, 0x7701, 0xCAA2, 0x76DB, 0xCAA3, 0x5269, 0xCAA4, 0x80DC, 0xCAA5, 0x5723, 0xCAA6, 0x5E08, 0xCAA7, 0x5931, + 0xCAA8, 0x72EE, 0xCAA9, 0x65BD, 0xCAAA, 0x6E7F, 0xCAAB, 0x8BD7, 0xCAAC, 0x5C38, 0xCAAD, 0x8671, 0xCAAE, 0x5341, 0xCAAF, 0x77F3, + 0xCAB0, 0x62FE, 0xCAB1, 0x65F6, 0xCAB2, 0x4EC0, 0xCAB3, 0x98DF, 0xCAB4, 0x8680, 0xCAB5, 0x5B9E, 0xCAB6, 0x8BC6, 0xCAB7, 0x53F2, + 0xCAB8, 0x77E2, 0xCAB9, 0x4F7F, 0xCABA, 0x5C4E, 0xCABB, 0x9A76, 0xCABC, 0x59CB, 0xCABD, 0x5F0F, 0xCABE, 0x793A, 0xCABF, 0x58EB, + 0xCAC0, 0x4E16, 0xCAC1, 0x67FF, 0xCAC2, 0x4E8B, 0xCAC3, 0x62ED, 0xCAC4, 0x8A93, 0xCAC5, 0x901D, 0xCAC6, 0x52BF, 0xCAC7, 0x662F, + 0xCAC8, 0x55DC, 0xCAC9, 0x566C, 0xCACA, 0x9002, 0xCACB, 0x4ED5, 0xCACC, 0x4F8D, 0xCACD, 0x91CA, 0xCACE, 0x9970, 0xCACF, 0x6C0F, + 0xCAD0, 0x5E02, 0xCAD1, 0x6043, 0xCAD2, 0x5BA4, 0xCAD3, 0x89C6, 0xCAD4, 0x8BD5, 0xCAD5, 0x6536, 0xCAD6, 0x624B, 0xCAD7, 0x9996, + 0xCAD8, 0x5B88, 0xCAD9, 0x5BFF, 0xCADA, 0x6388, 0xCADB, 0x552E, 0xCADC, 0x53D7, 0xCADD, 0x7626, 0xCADE, 0x517D, 0xCADF, 0x852C, + 0xCAE0, 0x67A2, 0xCAE1, 0x68B3, 0xCAE2, 0x6B8A, 0xCAE3, 0x6292, 0xCAE4, 0x8F93, 0xCAE5, 0x53D4, 0xCAE6, 0x8212, 0xCAE7, 0x6DD1, + 0xCAE8, 0x758F, 0xCAE9, 0x4E66, 0xCAEA, 0x8D4E, 0xCAEB, 0x5B70, 0xCAEC, 0x719F, 0xCAED, 0x85AF, 0xCAEE, 0x6691, 0xCAEF, 0x66D9, + 0xCAF0, 0x7F72, 0xCAF1, 0x8700, 0xCAF2, 0x9ECD, 0xCAF3, 0x9F20, 0xCAF4, 0x5C5E, 0xCAF5, 0x672F, 0xCAF6, 0x8FF0, 0xCAF7, 0x6811, + 0xCAF8, 0x675F, 0xCAF9, 0x620D, 0xCAFA, 0x7AD6, 0xCAFB, 0x5885, 0xCAFC, 0x5EB6, 0xCAFD, 0x6570, 0xCAFE, 0x6F31, 0xCB40, 0x8582, + 0xCB41, 0x8583, 0xCB42, 0x8586, 0xCB43, 0x8588, 0xCB44, 0x8589, 0xCB45, 0x858A, 0xCB46, 0x858B, 0xCB47, 0x858C, 0xCB48, 0x858D, + 0xCB49, 0x858E, 0xCB4A, 0x8590, 0xCB4B, 0x8591, 0xCB4C, 0x8592, 0xCB4D, 0x8593, 0xCB4E, 0x8594, 0xCB4F, 0x8595, 0xCB50, 0x8596, + 0xCB51, 0x8597, 0xCB52, 0x8598, 0xCB53, 0x8599, 0xCB54, 0x859A, 0xCB55, 0x859D, 0xCB56, 0x859E, 0xCB57, 0x859F, 0xCB58, 0x85A0, + 0xCB59, 0x85A1, 0xCB5A, 0x85A2, 0xCB5B, 0x85A3, 0xCB5C, 0x85A5, 0xCB5D, 0x85A6, 0xCB5E, 0x85A7, 0xCB5F, 0x85A9, 0xCB60, 0x85AB, + 0xCB61, 0x85AC, 0xCB62, 0x85AD, 0xCB63, 0x85B1, 0xCB64, 0x85B2, 0xCB65, 0x85B3, 0xCB66, 0x85B4, 0xCB67, 0x85B5, 0xCB68, 0x85B6, + 0xCB69, 0x85B8, 0xCB6A, 0x85BA, 0xCB6B, 0x85BB, 0xCB6C, 0x85BC, 0xCB6D, 0x85BD, 0xCB6E, 0x85BE, 0xCB6F, 0x85BF, 0xCB70, 0x85C0, + 0xCB71, 0x85C2, 0xCB72, 0x85C3, 0xCB73, 0x85C4, 0xCB74, 0x85C5, 0xCB75, 0x85C6, 0xCB76, 0x85C7, 0xCB77, 0x85C8, 0xCB78, 0x85CA, + 0xCB79, 0x85CB, 0xCB7A, 0x85CC, 0xCB7B, 0x85CD, 0xCB7C, 0x85CE, 0xCB7D, 0x85D1, 0xCB7E, 0x85D2, 0xCB80, 0x85D4, 0xCB81, 0x85D6, + 0xCB82, 0x85D7, 0xCB83, 0x85D8, 0xCB84, 0x85D9, 0xCB85, 0x85DA, 0xCB86, 0x85DB, 0xCB87, 0x85DD, 0xCB88, 0x85DE, 0xCB89, 0x85DF, + 0xCB8A, 0x85E0, 0xCB8B, 0x85E1, 0xCB8C, 0x85E2, 0xCB8D, 0x85E3, 0xCB8E, 0x85E5, 0xCB8F, 0x85E6, 0xCB90, 0x85E7, 0xCB91, 0x85E8, + 0xCB92, 0x85EA, 0xCB93, 0x85EB, 0xCB94, 0x85EC, 0xCB95, 0x85ED, 0xCB96, 0x85EE, 0xCB97, 0x85EF, 0xCB98, 0x85F0, 0xCB99, 0x85F1, + 0xCB9A, 0x85F2, 0xCB9B, 0x85F3, 0xCB9C, 0x85F4, 0xCB9D, 0x85F5, 0xCB9E, 0x85F6, 0xCB9F, 0x85F7, 0xCBA0, 0x85F8, 0xCBA1, 0x6055, + 0xCBA2, 0x5237, 0xCBA3, 0x800D, 0xCBA4, 0x6454, 0xCBA5, 0x8870, 0xCBA6, 0x7529, 0xCBA7, 0x5E05, 0xCBA8, 0x6813, 0xCBA9, 0x62F4, + 0xCBAA, 0x971C, 0xCBAB, 0x53CC, 0xCBAC, 0x723D, 0xCBAD, 0x8C01, 0xCBAE, 0x6C34, 0xCBAF, 0x7761, 0xCBB0, 0x7A0E, 0xCBB1, 0x542E, + 0xCBB2, 0x77AC, 0xCBB3, 0x987A, 0xCBB4, 0x821C, 0xCBB5, 0x8BF4, 0xCBB6, 0x7855, 0xCBB7, 0x6714, 0xCBB8, 0x70C1, 0xCBB9, 0x65AF, + 0xCBBA, 0x6495, 0xCBBB, 0x5636, 0xCBBC, 0x601D, 0xCBBD, 0x79C1, 0xCBBE, 0x53F8, 0xCBBF, 0x4E1D, 0xCBC0, 0x6B7B, 0xCBC1, 0x8086, + 0xCBC2, 0x5BFA, 0xCBC3, 0x55E3, 0xCBC4, 0x56DB, 0xCBC5, 0x4F3A, 0xCBC6, 0x4F3C, 0xCBC7, 0x9972, 0xCBC8, 0x5DF3, 0xCBC9, 0x677E, + 0xCBCA, 0x8038, 0xCBCB, 0x6002, 0xCBCC, 0x9882, 0xCBCD, 0x9001, 0xCBCE, 0x5B8B, 0xCBCF, 0x8BBC, 0xCBD0, 0x8BF5, 0xCBD1, 0x641C, + 0xCBD2, 0x8258, 0xCBD3, 0x64DE, 0xCBD4, 0x55FD, 0xCBD5, 0x82CF, 0xCBD6, 0x9165, 0xCBD7, 0x4FD7, 0xCBD8, 0x7D20, 0xCBD9, 0x901F, + 0xCBDA, 0x7C9F, 0xCBDB, 0x50F3, 0xCBDC, 0x5851, 0xCBDD, 0x6EAF, 0xCBDE, 0x5BBF, 0xCBDF, 0x8BC9, 0xCBE0, 0x8083, 0xCBE1, 0x9178, + 0xCBE2, 0x849C, 0xCBE3, 0x7B97, 0xCBE4, 0x867D, 0xCBE5, 0x968B, 0xCBE6, 0x968F, 0xCBE7, 0x7EE5, 0xCBE8, 0x9AD3, 0xCBE9, 0x788E, + 0xCBEA, 0x5C81, 0xCBEB, 0x7A57, 0xCBEC, 0x9042, 0xCBED, 0x96A7, 0xCBEE, 0x795F, 0xCBEF, 0x5B59, 0xCBF0, 0x635F, 0xCBF1, 0x7B0B, + 0xCBF2, 0x84D1, 0xCBF3, 0x68AD, 0xCBF4, 0x5506, 0xCBF5, 0x7F29, 0xCBF6, 0x7410, 0xCBF7, 0x7D22, 0xCBF8, 0x9501, 0xCBF9, 0x6240, + 0xCBFA, 0x584C, 0xCBFB, 0x4ED6, 0xCBFC, 0x5B83, 0xCBFD, 0x5979, 0xCBFE, 0x5854, 0xCC40, 0x85F9, 0xCC41, 0x85FA, 0xCC42, 0x85FC, + 0xCC43, 0x85FD, 0xCC44, 0x85FE, 0xCC45, 0x8600, 0xCC46, 0x8601, 0xCC47, 0x8602, 0xCC48, 0x8603, 0xCC49, 0x8604, 0xCC4A, 0x8606, + 0xCC4B, 0x8607, 0xCC4C, 0x8608, 0xCC4D, 0x8609, 0xCC4E, 0x860A, 0xCC4F, 0x860B, 0xCC50, 0x860C, 0xCC51, 0x860D, 0xCC52, 0x860E, + 0xCC53, 0x860F, 0xCC54, 0x8610, 0xCC55, 0x8612, 0xCC56, 0x8613, 0xCC57, 0x8614, 0xCC58, 0x8615, 0xCC59, 0x8617, 0xCC5A, 0x8618, + 0xCC5B, 0x8619, 0xCC5C, 0x861A, 0xCC5D, 0x861B, 0xCC5E, 0x861C, 0xCC5F, 0x861D, 0xCC60, 0x861E, 0xCC61, 0x861F, 0xCC62, 0x8620, + 0xCC63, 0x8621, 0xCC64, 0x8622, 0xCC65, 0x8623, 0xCC66, 0x8624, 0xCC67, 0x8625, 0xCC68, 0x8626, 0xCC69, 0x8628, 0xCC6A, 0x862A, + 0xCC6B, 0x862B, 0xCC6C, 0x862C, 0xCC6D, 0x862D, 0xCC6E, 0x862E, 0xCC6F, 0x862F, 0xCC70, 0x8630, 0xCC71, 0x8631, 0xCC72, 0x8632, + 0xCC73, 0x8633, 0xCC74, 0x8634, 0xCC75, 0x8635, 0xCC76, 0x8636, 0xCC77, 0x8637, 0xCC78, 0x8639, 0xCC79, 0x863A, 0xCC7A, 0x863B, + 0xCC7B, 0x863D, 0xCC7C, 0x863E, 0xCC7D, 0x863F, 0xCC7E, 0x8640, 0xCC80, 0x8641, 0xCC81, 0x8642, 0xCC82, 0x8643, 0xCC83, 0x8644, + 0xCC84, 0x8645, 0xCC85, 0x8646, 0xCC86, 0x8647, 0xCC87, 0x8648, 0xCC88, 0x8649, 0xCC89, 0x864A, 0xCC8A, 0x864B, 0xCC8B, 0x864C, + 0xCC8C, 0x8652, 0xCC8D, 0x8653, 0xCC8E, 0x8655, 0xCC8F, 0x8656, 0xCC90, 0x8657, 0xCC91, 0x8658, 0xCC92, 0x8659, 0xCC93, 0x865B, + 0xCC94, 0x865C, 0xCC95, 0x865D, 0xCC96, 0x865F, 0xCC97, 0x8660, 0xCC98, 0x8661, 0xCC99, 0x8663, 0xCC9A, 0x8664, 0xCC9B, 0x8665, + 0xCC9C, 0x8666, 0xCC9D, 0x8667, 0xCC9E, 0x8668, 0xCC9F, 0x8669, 0xCCA0, 0x866A, 0xCCA1, 0x736D, 0xCCA2, 0x631E, 0xCCA3, 0x8E4B, + 0xCCA4, 0x8E0F, 0xCCA5, 0x80CE, 0xCCA6, 0x82D4, 0xCCA7, 0x62AC, 0xCCA8, 0x53F0, 0xCCA9, 0x6CF0, 0xCCAA, 0x915E, 0xCCAB, 0x592A, + 0xCCAC, 0x6001, 0xCCAD, 0x6C70, 0xCCAE, 0x574D, 0xCCAF, 0x644A, 0xCCB0, 0x8D2A, 0xCCB1, 0x762B, 0xCCB2, 0x6EE9, 0xCCB3, 0x575B, + 0xCCB4, 0x6A80, 0xCCB5, 0x75F0, 0xCCB6, 0x6F6D, 0xCCB7, 0x8C2D, 0xCCB8, 0x8C08, 0xCCB9, 0x5766, 0xCCBA, 0x6BEF, 0xCCBB, 0x8892, + 0xCCBC, 0x78B3, 0xCCBD, 0x63A2, 0xCCBE, 0x53F9, 0xCCBF, 0x70AD, 0xCCC0, 0x6C64, 0xCCC1, 0x5858, 0xCCC2, 0x642A, 0xCCC3, 0x5802, + 0xCCC4, 0x68E0, 0xCCC5, 0x819B, 0xCCC6, 0x5510, 0xCCC7, 0x7CD6, 0xCCC8, 0x5018, 0xCCC9, 0x8EBA, 0xCCCA, 0x6DCC, 0xCCCB, 0x8D9F, + 0xCCCC, 0x70EB, 0xCCCD, 0x638F, 0xCCCE, 0x6D9B, 0xCCCF, 0x6ED4, 0xCCD0, 0x7EE6, 0xCCD1, 0x8404, 0xCCD2, 0x6843, 0xCCD3, 0x9003, + 0xCCD4, 0x6DD8, 0xCCD5, 0x9676, 0xCCD6, 0x8BA8, 0xCCD7, 0x5957, 0xCCD8, 0x7279, 0xCCD9, 0x85E4, 0xCCDA, 0x817E, 0xCCDB, 0x75BC, + 0xCCDC, 0x8A8A, 0xCCDD, 0x68AF, 0xCCDE, 0x5254, 0xCCDF, 0x8E22, 0xCCE0, 0x9511, 0xCCE1, 0x63D0, 0xCCE2, 0x9898, 0xCCE3, 0x8E44, + 0xCCE4, 0x557C, 0xCCE5, 0x4F53, 0xCCE6, 0x66FF, 0xCCE7, 0x568F, 0xCCE8, 0x60D5, 0xCCE9, 0x6D95, 0xCCEA, 0x5243, 0xCCEB, 0x5C49, + 0xCCEC, 0x5929, 0xCCED, 0x6DFB, 0xCCEE, 0x586B, 0xCCEF, 0x7530, 0xCCF0, 0x751C, 0xCCF1, 0x606C, 0xCCF2, 0x8214, 0xCCF3, 0x8146, + 0xCCF4, 0x6311, 0xCCF5, 0x6761, 0xCCF6, 0x8FE2, 0xCCF7, 0x773A, 0xCCF8, 0x8DF3, 0xCCF9, 0x8D34, 0xCCFA, 0x94C1, 0xCCFB, 0x5E16, + 0xCCFC, 0x5385, 0xCCFD, 0x542C, 0xCCFE, 0x70C3, 0xCD40, 0x866D, 0xCD41, 0x866F, 0xCD42, 0x8670, 0xCD43, 0x8672, 0xCD44, 0x8673, + 0xCD45, 0x8674, 0xCD46, 0x8675, 0xCD47, 0x8676, 0xCD48, 0x8677, 0xCD49, 0x8678, 0xCD4A, 0x8683, 0xCD4B, 0x8684, 0xCD4C, 0x8685, + 0xCD4D, 0x8686, 0xCD4E, 0x8687, 0xCD4F, 0x8688, 0xCD50, 0x8689, 0xCD51, 0x868E, 0xCD52, 0x868F, 0xCD53, 0x8690, 0xCD54, 0x8691, + 0xCD55, 0x8692, 0xCD56, 0x8694, 0xCD57, 0x8696, 0xCD58, 0x8697, 0xCD59, 0x8698, 0xCD5A, 0x8699, 0xCD5B, 0x869A, 0xCD5C, 0x869B, + 0xCD5D, 0x869E, 0xCD5E, 0x869F, 0xCD5F, 0x86A0, 0xCD60, 0x86A1, 0xCD61, 0x86A2, 0xCD62, 0x86A5, 0xCD63, 0x86A6, 0xCD64, 0x86AB, + 0xCD65, 0x86AD, 0xCD66, 0x86AE, 0xCD67, 0x86B2, 0xCD68, 0x86B3, 0xCD69, 0x86B7, 0xCD6A, 0x86B8, 0xCD6B, 0x86B9, 0xCD6C, 0x86BB, + 0xCD6D, 0x86BC, 0xCD6E, 0x86BD, 0xCD6F, 0x86BE, 0xCD70, 0x86BF, 0xCD71, 0x86C1, 0xCD72, 0x86C2, 0xCD73, 0x86C3, 0xCD74, 0x86C5, + 0xCD75, 0x86C8, 0xCD76, 0x86CC, 0xCD77, 0x86CD, 0xCD78, 0x86D2, 0xCD79, 0x86D3, 0xCD7A, 0x86D5, 0xCD7B, 0x86D6, 0xCD7C, 0x86D7, + 0xCD7D, 0x86DA, 0xCD7E, 0x86DC, 0xCD80, 0x86DD, 0xCD81, 0x86E0, 0xCD82, 0x86E1, 0xCD83, 0x86E2, 0xCD84, 0x86E3, 0xCD85, 0x86E5, + 0xCD86, 0x86E6, 0xCD87, 0x86E7, 0xCD88, 0x86E8, 0xCD89, 0x86EA, 0xCD8A, 0x86EB, 0xCD8B, 0x86EC, 0xCD8C, 0x86EF, 0xCD8D, 0x86F5, + 0xCD8E, 0x86F6, 0xCD8F, 0x86F7, 0xCD90, 0x86FA, 0xCD91, 0x86FB, 0xCD92, 0x86FC, 0xCD93, 0x86FD, 0xCD94, 0x86FF, 0xCD95, 0x8701, + 0xCD96, 0x8704, 0xCD97, 0x8705, 0xCD98, 0x8706, 0xCD99, 0x870B, 0xCD9A, 0x870C, 0xCD9B, 0x870E, 0xCD9C, 0x870F, 0xCD9D, 0x8710, + 0xCD9E, 0x8711, 0xCD9F, 0x8714, 0xCDA0, 0x8716, 0xCDA1, 0x6C40, 0xCDA2, 0x5EF7, 0xCDA3, 0x505C, 0xCDA4, 0x4EAD, 0xCDA5, 0x5EAD, + 0xCDA6, 0x633A, 0xCDA7, 0x8247, 0xCDA8, 0x901A, 0xCDA9, 0x6850, 0xCDAA, 0x916E, 0xCDAB, 0x77B3, 0xCDAC, 0x540C, 0xCDAD, 0x94DC, + 0xCDAE, 0x5F64, 0xCDAF, 0x7AE5, 0xCDB0, 0x6876, 0xCDB1, 0x6345, 0xCDB2, 0x7B52, 0xCDB3, 0x7EDF, 0xCDB4, 0x75DB, 0xCDB5, 0x5077, + 0xCDB6, 0x6295, 0xCDB7, 0x5934, 0xCDB8, 0x900F, 0xCDB9, 0x51F8, 0xCDBA, 0x79C3, 0xCDBB, 0x7A81, 0xCDBC, 0x56FE, 0xCDBD, 0x5F92, + 0xCDBE, 0x9014, 0xCDBF, 0x6D82, 0xCDC0, 0x5C60, 0xCDC1, 0x571F, 0xCDC2, 0x5410, 0xCDC3, 0x5154, 0xCDC4, 0x6E4D, 0xCDC5, 0x56E2, + 0xCDC6, 0x63A8, 0xCDC7, 0x9893, 0xCDC8, 0x817F, 0xCDC9, 0x8715, 0xCDCA, 0x892A, 0xCDCB, 0x9000, 0xCDCC, 0x541E, 0xCDCD, 0x5C6F, + 0xCDCE, 0x81C0, 0xCDCF, 0x62D6, 0xCDD0, 0x6258, 0xCDD1, 0x8131, 0xCDD2, 0x9E35, 0xCDD3, 0x9640, 0xCDD4, 0x9A6E, 0xCDD5, 0x9A7C, + 0xCDD6, 0x692D, 0xCDD7, 0x59A5, 0xCDD8, 0x62D3, 0xCDD9, 0x553E, 0xCDDA, 0x6316, 0xCDDB, 0x54C7, 0xCDDC, 0x86D9, 0xCDDD, 0x6D3C, + 0xCDDE, 0x5A03, 0xCDDF, 0x74E6, 0xCDE0, 0x889C, 0xCDE1, 0x6B6A, 0xCDE2, 0x5916, 0xCDE3, 0x8C4C, 0xCDE4, 0x5F2F, 0xCDE5, 0x6E7E, + 0xCDE6, 0x73A9, 0xCDE7, 0x987D, 0xCDE8, 0x4E38, 0xCDE9, 0x70F7, 0xCDEA, 0x5B8C, 0xCDEB, 0x7897, 0xCDEC, 0x633D, 0xCDED, 0x665A, + 0xCDEE, 0x7696, 0xCDEF, 0x60CB, 0xCDF0, 0x5B9B, 0xCDF1, 0x5A49, 0xCDF2, 0x4E07, 0xCDF3, 0x8155, 0xCDF4, 0x6C6A, 0xCDF5, 0x738B, + 0xCDF6, 0x4EA1, 0xCDF7, 0x6789, 0xCDF8, 0x7F51, 0xCDF9, 0x5F80, 0xCDFA, 0x65FA, 0xCDFB, 0x671B, 0xCDFC, 0x5FD8, 0xCDFD, 0x5984, + 0xCDFE, 0x5A01, 0xCE40, 0x8719, 0xCE41, 0x871B, 0xCE42, 0x871D, 0xCE43, 0x871F, 0xCE44, 0x8720, 0xCE45, 0x8724, 0xCE46, 0x8726, + 0xCE47, 0x8727, 0xCE48, 0x8728, 0xCE49, 0x872A, 0xCE4A, 0x872B, 0xCE4B, 0x872C, 0xCE4C, 0x872D, 0xCE4D, 0x872F, 0xCE4E, 0x8730, + 0xCE4F, 0x8732, 0xCE50, 0x8733, 0xCE51, 0x8735, 0xCE52, 0x8736, 0xCE53, 0x8738, 0xCE54, 0x8739, 0xCE55, 0x873A, 0xCE56, 0x873C, + 0xCE57, 0x873D, 0xCE58, 0x8740, 0xCE59, 0x8741, 0xCE5A, 0x8742, 0xCE5B, 0x8743, 0xCE5C, 0x8744, 0xCE5D, 0x8745, 0xCE5E, 0x8746, + 0xCE5F, 0x874A, 0xCE60, 0x874B, 0xCE61, 0x874D, 0xCE62, 0x874F, 0xCE63, 0x8750, 0xCE64, 0x8751, 0xCE65, 0x8752, 0xCE66, 0x8754, + 0xCE67, 0x8755, 0xCE68, 0x8756, 0xCE69, 0x8758, 0xCE6A, 0x875A, 0xCE6B, 0x875B, 0xCE6C, 0x875C, 0xCE6D, 0x875D, 0xCE6E, 0x875E, + 0xCE6F, 0x875F, 0xCE70, 0x8761, 0xCE71, 0x8762, 0xCE72, 0x8766, 0xCE73, 0x8767, 0xCE74, 0x8768, 0xCE75, 0x8769, 0xCE76, 0x876A, + 0xCE77, 0x876B, 0xCE78, 0x876C, 0xCE79, 0x876D, 0xCE7A, 0x876F, 0xCE7B, 0x8771, 0xCE7C, 0x8772, 0xCE7D, 0x8773, 0xCE7E, 0x8775, + 0xCE80, 0x8777, 0xCE81, 0x8778, 0xCE82, 0x8779, 0xCE83, 0x877A, 0xCE84, 0x877F, 0xCE85, 0x8780, 0xCE86, 0x8781, 0xCE87, 0x8784, + 0xCE88, 0x8786, 0xCE89, 0x8787, 0xCE8A, 0x8789, 0xCE8B, 0x878A, 0xCE8C, 0x878C, 0xCE8D, 0x878E, 0xCE8E, 0x878F, 0xCE8F, 0x8790, + 0xCE90, 0x8791, 0xCE91, 0x8792, 0xCE92, 0x8794, 0xCE93, 0x8795, 0xCE94, 0x8796, 0xCE95, 0x8798, 0xCE96, 0x8799, 0xCE97, 0x879A, + 0xCE98, 0x879B, 0xCE99, 0x879C, 0xCE9A, 0x879D, 0xCE9B, 0x879E, 0xCE9C, 0x87A0, 0xCE9D, 0x87A1, 0xCE9E, 0x87A2, 0xCE9F, 0x87A3, + 0xCEA0, 0x87A4, 0xCEA1, 0x5DCD, 0xCEA2, 0x5FAE, 0xCEA3, 0x5371, 0xCEA4, 0x97E6, 0xCEA5, 0x8FDD, 0xCEA6, 0x6845, 0xCEA7, 0x56F4, + 0xCEA8, 0x552F, 0xCEA9, 0x60DF, 0xCEAA, 0x4E3A, 0xCEAB, 0x6F4D, 0xCEAC, 0x7EF4, 0xCEAD, 0x82C7, 0xCEAE, 0x840E, 0xCEAF, 0x59D4, + 0xCEB0, 0x4F1F, 0xCEB1, 0x4F2A, 0xCEB2, 0x5C3E, 0xCEB3, 0x7EAC, 0xCEB4, 0x672A, 0xCEB5, 0x851A, 0xCEB6, 0x5473, 0xCEB7, 0x754F, + 0xCEB8, 0x80C3, 0xCEB9, 0x5582, 0xCEBA, 0x9B4F, 0xCEBB, 0x4F4D, 0xCEBC, 0x6E2D, 0xCEBD, 0x8C13, 0xCEBE, 0x5C09, 0xCEBF, 0x6170, + 0xCEC0, 0x536B, 0xCEC1, 0x761F, 0xCEC2, 0x6E29, 0xCEC3, 0x868A, 0xCEC4, 0x6587, 0xCEC5, 0x95FB, 0xCEC6, 0x7EB9, 0xCEC7, 0x543B, + 0xCEC8, 0x7A33, 0xCEC9, 0x7D0A, 0xCECA, 0x95EE, 0xCECB, 0x55E1, 0xCECC, 0x7FC1, 0xCECD, 0x74EE, 0xCECE, 0x631D, 0xCECF, 0x8717, + 0xCED0, 0x6DA1, 0xCED1, 0x7A9D, 0xCED2, 0x6211, 0xCED3, 0x65A1, 0xCED4, 0x5367, 0xCED5, 0x63E1, 0xCED6, 0x6C83, 0xCED7, 0x5DEB, + 0xCED8, 0x545C, 0xCED9, 0x94A8, 0xCEDA, 0x4E4C, 0xCEDB, 0x6C61, 0xCEDC, 0x8BEC, 0xCEDD, 0x5C4B, 0xCEDE, 0x65E0, 0xCEDF, 0x829C, + 0xCEE0, 0x68A7, 0xCEE1, 0x543E, 0xCEE2, 0x5434, 0xCEE3, 0x6BCB, 0xCEE4, 0x6B66, 0xCEE5, 0x4E94, 0xCEE6, 0x6342, 0xCEE7, 0x5348, + 0xCEE8, 0x821E, 0xCEE9, 0x4F0D, 0xCEEA, 0x4FAE, 0xCEEB, 0x575E, 0xCEEC, 0x620A, 0xCEED, 0x96FE, 0xCEEE, 0x6664, 0xCEEF, 0x7269, + 0xCEF0, 0x52FF, 0xCEF1, 0x52A1, 0xCEF2, 0x609F, 0xCEF3, 0x8BEF, 0xCEF4, 0x6614, 0xCEF5, 0x7199, 0xCEF6, 0x6790, 0xCEF7, 0x897F, + 0xCEF8, 0x7852, 0xCEF9, 0x77FD, 0xCEFA, 0x6670, 0xCEFB, 0x563B, 0xCEFC, 0x5438, 0xCEFD, 0x9521, 0xCEFE, 0x727A, 0xCF40, 0x87A5, + 0xCF41, 0x87A6, 0xCF42, 0x87A7, 0xCF43, 0x87A9, 0xCF44, 0x87AA, 0xCF45, 0x87AE, 0xCF46, 0x87B0, 0xCF47, 0x87B1, 0xCF48, 0x87B2, + 0xCF49, 0x87B4, 0xCF4A, 0x87B6, 0xCF4B, 0x87B7, 0xCF4C, 0x87B8, 0xCF4D, 0x87B9, 0xCF4E, 0x87BB, 0xCF4F, 0x87BC, 0xCF50, 0x87BE, + 0xCF51, 0x87BF, 0xCF52, 0x87C1, 0xCF53, 0x87C2, 0xCF54, 0x87C3, 0xCF55, 0x87C4, 0xCF56, 0x87C5, 0xCF57, 0x87C7, 0xCF58, 0x87C8, + 0xCF59, 0x87C9, 0xCF5A, 0x87CC, 0xCF5B, 0x87CD, 0xCF5C, 0x87CE, 0xCF5D, 0x87CF, 0xCF5E, 0x87D0, 0xCF5F, 0x87D4, 0xCF60, 0x87D5, + 0xCF61, 0x87D6, 0xCF62, 0x87D7, 0xCF63, 0x87D8, 0xCF64, 0x87D9, 0xCF65, 0x87DA, 0xCF66, 0x87DC, 0xCF67, 0x87DD, 0xCF68, 0x87DE, + 0xCF69, 0x87DF, 0xCF6A, 0x87E1, 0xCF6B, 0x87E2, 0xCF6C, 0x87E3, 0xCF6D, 0x87E4, 0xCF6E, 0x87E6, 0xCF6F, 0x87E7, 0xCF70, 0x87E8, + 0xCF71, 0x87E9, 0xCF72, 0x87EB, 0xCF73, 0x87EC, 0xCF74, 0x87ED, 0xCF75, 0x87EF, 0xCF76, 0x87F0, 0xCF77, 0x87F1, 0xCF78, 0x87F2, + 0xCF79, 0x87F3, 0xCF7A, 0x87F4, 0xCF7B, 0x87F5, 0xCF7C, 0x87F6, 0xCF7D, 0x87F7, 0xCF7E, 0x87F8, 0xCF80, 0x87FA, 0xCF81, 0x87FB, + 0xCF82, 0x87FC, 0xCF83, 0x87FD, 0xCF84, 0x87FF, 0xCF85, 0x8800, 0xCF86, 0x8801, 0xCF87, 0x8802, 0xCF88, 0x8804, 0xCF89, 0x8805, + 0xCF8A, 0x8806, 0xCF8B, 0x8807, 0xCF8C, 0x8808, 0xCF8D, 0x8809, 0xCF8E, 0x880B, 0xCF8F, 0x880C, 0xCF90, 0x880D, 0xCF91, 0x880E, + 0xCF92, 0x880F, 0xCF93, 0x8810, 0xCF94, 0x8811, 0xCF95, 0x8812, 0xCF96, 0x8814, 0xCF97, 0x8817, 0xCF98, 0x8818, 0xCF99, 0x8819, + 0xCF9A, 0x881A, 0xCF9B, 0x881C, 0xCF9C, 0x881D, 0xCF9D, 0x881E, 0xCF9E, 0x881F, 0xCF9F, 0x8820, 0xCFA0, 0x8823, 0xCFA1, 0x7A00, + 0xCFA2, 0x606F, 0xCFA3, 0x5E0C, 0xCFA4, 0x6089, 0xCFA5, 0x819D, 0xCFA6, 0x5915, 0xCFA7, 0x60DC, 0xCFA8, 0x7184, 0xCFA9, 0x70EF, + 0xCFAA, 0x6EAA, 0xCFAB, 0x6C50, 0xCFAC, 0x7280, 0xCFAD, 0x6A84, 0xCFAE, 0x88AD, 0xCFAF, 0x5E2D, 0xCFB0, 0x4E60, 0xCFB1, 0x5AB3, + 0xCFB2, 0x559C, 0xCFB3, 0x94E3, 0xCFB4, 0x6D17, 0xCFB5, 0x7CFB, 0xCFB6, 0x9699, 0xCFB7, 0x620F, 0xCFB8, 0x7EC6, 0xCFB9, 0x778E, + 0xCFBA, 0x867E, 0xCFBB, 0x5323, 0xCFBC, 0x971E, 0xCFBD, 0x8F96, 0xCFBE, 0x6687, 0xCFBF, 0x5CE1, 0xCFC0, 0x4FA0, 0xCFC1, 0x72ED, + 0xCFC2, 0x4E0B, 0xCFC3, 0x53A6, 0xCFC4, 0x590F, 0xCFC5, 0x5413, 0xCFC6, 0x6380, 0xCFC7, 0x9528, 0xCFC8, 0x5148, 0xCFC9, 0x4ED9, + 0xCFCA, 0x9C9C, 0xCFCB, 0x7EA4, 0xCFCC, 0x54B8, 0xCFCD, 0x8D24, 0xCFCE, 0x8854, 0xCFCF, 0x8237, 0xCFD0, 0x95F2, 0xCFD1, 0x6D8E, + 0xCFD2, 0x5F26, 0xCFD3, 0x5ACC, 0xCFD4, 0x663E, 0xCFD5, 0x9669, 0xCFD6, 0x73B0, 0xCFD7, 0x732E, 0xCFD8, 0x53BF, 0xCFD9, 0x817A, + 0xCFDA, 0x9985, 0xCFDB, 0x7FA1, 0xCFDC, 0x5BAA, 0xCFDD, 0x9677, 0xCFDE, 0x9650, 0xCFDF, 0x7EBF, 0xCFE0, 0x76F8, 0xCFE1, 0x53A2, + 0xCFE2, 0x9576, 0xCFE3, 0x9999, 0xCFE4, 0x7BB1, 0xCFE5, 0x8944, 0xCFE6, 0x6E58, 0xCFE7, 0x4E61, 0xCFE8, 0x7FD4, 0xCFE9, 0x7965, + 0xCFEA, 0x8BE6, 0xCFEB, 0x60F3, 0xCFEC, 0x54CD, 0xCFED, 0x4EAB, 0xCFEE, 0x9879, 0xCFEF, 0x5DF7, 0xCFF0, 0x6A61, 0xCFF1, 0x50CF, + 0xCFF2, 0x5411, 0xCFF3, 0x8C61, 0xCFF4, 0x8427, 0xCFF5, 0x785D, 0xCFF6, 0x9704, 0xCFF7, 0x524A, 0xCFF8, 0x54EE, 0xCFF9, 0x56A3, + 0xCFFA, 0x9500, 0xCFFB, 0x6D88, 0xCFFC, 0x5BB5, 0xCFFD, 0x6DC6, 0xCFFE, 0x6653, 0xD040, 0x8824, 0xD041, 0x8825, 0xD042, 0x8826, + 0xD043, 0x8827, 0xD044, 0x8828, 0xD045, 0x8829, 0xD046, 0x882A, 0xD047, 0x882B, 0xD048, 0x882C, 0xD049, 0x882D, 0xD04A, 0x882E, + 0xD04B, 0x882F, 0xD04C, 0x8830, 0xD04D, 0x8831, 0xD04E, 0x8833, 0xD04F, 0x8834, 0xD050, 0x8835, 0xD051, 0x8836, 0xD052, 0x8837, + 0xD053, 0x8838, 0xD054, 0x883A, 0xD055, 0x883B, 0xD056, 0x883D, 0xD057, 0x883E, 0xD058, 0x883F, 0xD059, 0x8841, 0xD05A, 0x8842, + 0xD05B, 0x8843, 0xD05C, 0x8846, 0xD05D, 0x8847, 0xD05E, 0x8848, 0xD05F, 0x8849, 0xD060, 0x884A, 0xD061, 0x884B, 0xD062, 0x884E, + 0xD063, 0x884F, 0xD064, 0x8850, 0xD065, 0x8851, 0xD066, 0x8852, 0xD067, 0x8853, 0xD068, 0x8855, 0xD069, 0x8856, 0xD06A, 0x8858, + 0xD06B, 0x885A, 0xD06C, 0x885B, 0xD06D, 0x885C, 0xD06E, 0x885D, 0xD06F, 0x885E, 0xD070, 0x885F, 0xD071, 0x8860, 0xD072, 0x8866, + 0xD073, 0x8867, 0xD074, 0x886A, 0xD075, 0x886D, 0xD076, 0x886F, 0xD077, 0x8871, 0xD078, 0x8873, 0xD079, 0x8874, 0xD07A, 0x8875, + 0xD07B, 0x8876, 0xD07C, 0x8878, 0xD07D, 0x8879, 0xD07E, 0x887A, 0xD080, 0x887B, 0xD081, 0x887C, 0xD082, 0x8880, 0xD083, 0x8883, + 0xD084, 0x8886, 0xD085, 0x8887, 0xD086, 0x8889, 0xD087, 0x888A, 0xD088, 0x888C, 0xD089, 0x888E, 0xD08A, 0x888F, 0xD08B, 0x8890, + 0xD08C, 0x8891, 0xD08D, 0x8893, 0xD08E, 0x8894, 0xD08F, 0x8895, 0xD090, 0x8897, 0xD091, 0x8898, 0xD092, 0x8899, 0xD093, 0x889A, + 0xD094, 0x889B, 0xD095, 0x889D, 0xD096, 0x889E, 0xD097, 0x889F, 0xD098, 0x88A0, 0xD099, 0x88A1, 0xD09A, 0x88A3, 0xD09B, 0x88A5, + 0xD09C, 0x88A6, 0xD09D, 0x88A7, 0xD09E, 0x88A8, 0xD09F, 0x88A9, 0xD0A0, 0x88AA, 0xD0A1, 0x5C0F, 0xD0A2, 0x5B5D, 0xD0A3, 0x6821, + 0xD0A4, 0x8096, 0xD0A5, 0x5578, 0xD0A6, 0x7B11, 0xD0A7, 0x6548, 0xD0A8, 0x6954, 0xD0A9, 0x4E9B, 0xD0AA, 0x6B47, 0xD0AB, 0x874E, + 0xD0AC, 0x978B, 0xD0AD, 0x534F, 0xD0AE, 0x631F, 0xD0AF, 0x643A, 0xD0B0, 0x90AA, 0xD0B1, 0x659C, 0xD0B2, 0x80C1, 0xD0B3, 0x8C10, + 0xD0B4, 0x5199, 0xD0B5, 0x68B0, 0xD0B6, 0x5378, 0xD0B7, 0x87F9, 0xD0B8, 0x61C8, 0xD0B9, 0x6CC4, 0xD0BA, 0x6CFB, 0xD0BB, 0x8C22, + 0xD0BC, 0x5C51, 0xD0BD, 0x85AA, 0xD0BE, 0x82AF, 0xD0BF, 0x950C, 0xD0C0, 0x6B23, 0xD0C1, 0x8F9B, 0xD0C2, 0x65B0, 0xD0C3, 0x5FFB, + 0xD0C4, 0x5FC3, 0xD0C5, 0x4FE1, 0xD0C6, 0x8845, 0xD0C7, 0x661F, 0xD0C8, 0x8165, 0xD0C9, 0x7329, 0xD0CA, 0x60FA, 0xD0CB, 0x5174, + 0xD0CC, 0x5211, 0xD0CD, 0x578B, 0xD0CE, 0x5F62, 0xD0CF, 0x90A2, 0xD0D0, 0x884C, 0xD0D1, 0x9192, 0xD0D2, 0x5E78, 0xD0D3, 0x674F, + 0xD0D4, 0x6027, 0xD0D5, 0x59D3, 0xD0D6, 0x5144, 0xD0D7, 0x51F6, 0xD0D8, 0x80F8, 0xD0D9, 0x5308, 0xD0DA, 0x6C79, 0xD0DB, 0x96C4, + 0xD0DC, 0x718A, 0xD0DD, 0x4F11, 0xD0DE, 0x4FEE, 0xD0DF, 0x7F9E, 0xD0E0, 0x673D, 0xD0E1, 0x55C5, 0xD0E2, 0x9508, 0xD0E3, 0x79C0, + 0xD0E4, 0x8896, 0xD0E5, 0x7EE3, 0xD0E6, 0x589F, 0xD0E7, 0x620C, 0xD0E8, 0x9700, 0xD0E9, 0x865A, 0xD0EA, 0x5618, 0xD0EB, 0x987B, + 0xD0EC, 0x5F90, 0xD0ED, 0x8BB8, 0xD0EE, 0x84C4, 0xD0EF, 0x9157, 0xD0F0, 0x53D9, 0xD0F1, 0x65ED, 0xD0F2, 0x5E8F, 0xD0F3, 0x755C, + 0xD0F4, 0x6064, 0xD0F5, 0x7D6E, 0xD0F6, 0x5A7F, 0xD0F7, 0x7EEA, 0xD0F8, 0x7EED, 0xD0F9, 0x8F69, 0xD0FA, 0x55A7, 0xD0FB, 0x5BA3, + 0xD0FC, 0x60AC, 0xD0FD, 0x65CB, 0xD0FE, 0x7384, 0xD140, 0x88AC, 0xD141, 0x88AE, 0xD142, 0x88AF, 0xD143, 0x88B0, 0xD144, 0x88B2, + 0xD145, 0x88B3, 0xD146, 0x88B4, 0xD147, 0x88B5, 0xD148, 0x88B6, 0xD149, 0x88B8, 0xD14A, 0x88B9, 0xD14B, 0x88BA, 0xD14C, 0x88BB, + 0xD14D, 0x88BD, 0xD14E, 0x88BE, 0xD14F, 0x88BF, 0xD150, 0x88C0, 0xD151, 0x88C3, 0xD152, 0x88C4, 0xD153, 0x88C7, 0xD154, 0x88C8, + 0xD155, 0x88CA, 0xD156, 0x88CB, 0xD157, 0x88CC, 0xD158, 0x88CD, 0xD159, 0x88CF, 0xD15A, 0x88D0, 0xD15B, 0x88D1, 0xD15C, 0x88D3, + 0xD15D, 0x88D6, 0xD15E, 0x88D7, 0xD15F, 0x88DA, 0xD160, 0x88DB, 0xD161, 0x88DC, 0xD162, 0x88DD, 0xD163, 0x88DE, 0xD164, 0x88E0, + 0xD165, 0x88E1, 0xD166, 0x88E6, 0xD167, 0x88E7, 0xD168, 0x88E9, 0xD169, 0x88EA, 0xD16A, 0x88EB, 0xD16B, 0x88EC, 0xD16C, 0x88ED, + 0xD16D, 0x88EE, 0xD16E, 0x88EF, 0xD16F, 0x88F2, 0xD170, 0x88F5, 0xD171, 0x88F6, 0xD172, 0x88F7, 0xD173, 0x88FA, 0xD174, 0x88FB, + 0xD175, 0x88FD, 0xD176, 0x88FF, 0xD177, 0x8900, 0xD178, 0x8901, 0xD179, 0x8903, 0xD17A, 0x8904, 0xD17B, 0x8905, 0xD17C, 0x8906, + 0xD17D, 0x8907, 0xD17E, 0x8908, 0xD180, 0x8909, 0xD181, 0x890B, 0xD182, 0x890C, 0xD183, 0x890D, 0xD184, 0x890E, 0xD185, 0x890F, + 0xD186, 0x8911, 0xD187, 0x8914, 0xD188, 0x8915, 0xD189, 0x8916, 0xD18A, 0x8917, 0xD18B, 0x8918, 0xD18C, 0x891C, 0xD18D, 0x891D, + 0xD18E, 0x891E, 0xD18F, 0x891F, 0xD190, 0x8920, 0xD191, 0x8922, 0xD192, 0x8923, 0xD193, 0x8924, 0xD194, 0x8926, 0xD195, 0x8927, + 0xD196, 0x8928, 0xD197, 0x8929, 0xD198, 0x892C, 0xD199, 0x892D, 0xD19A, 0x892E, 0xD19B, 0x892F, 0xD19C, 0x8931, 0xD19D, 0x8932, + 0xD19E, 0x8933, 0xD19F, 0x8935, 0xD1A0, 0x8937, 0xD1A1, 0x9009, 0xD1A2, 0x7663, 0xD1A3, 0x7729, 0xD1A4, 0x7EDA, 0xD1A5, 0x9774, + 0xD1A6, 0x859B, 0xD1A7, 0x5B66, 0xD1A8, 0x7A74, 0xD1A9, 0x96EA, 0xD1AA, 0x8840, 0xD1AB, 0x52CB, 0xD1AC, 0x718F, 0xD1AD, 0x5FAA, + 0xD1AE, 0x65EC, 0xD1AF, 0x8BE2, 0xD1B0, 0x5BFB, 0xD1B1, 0x9A6F, 0xD1B2, 0x5DE1, 0xD1B3, 0x6B89, 0xD1B4, 0x6C5B, 0xD1B5, 0x8BAD, + 0xD1B6, 0x8BAF, 0xD1B7, 0x900A, 0xD1B8, 0x8FC5, 0xD1B9, 0x538B, 0xD1BA, 0x62BC, 0xD1BB, 0x9E26, 0xD1BC, 0x9E2D, 0xD1BD, 0x5440, + 0xD1BE, 0x4E2B, 0xD1BF, 0x82BD, 0xD1C0, 0x7259, 0xD1C1, 0x869C, 0xD1C2, 0x5D16, 0xD1C3, 0x8859, 0xD1C4, 0x6DAF, 0xD1C5, 0x96C5, + 0xD1C6, 0x54D1, 0xD1C7, 0x4E9A, 0xD1C8, 0x8BB6, 0xD1C9, 0x7109, 0xD1CA, 0x54BD, 0xD1CB, 0x9609, 0xD1CC, 0x70DF, 0xD1CD, 0x6DF9, + 0xD1CE, 0x76D0, 0xD1CF, 0x4E25, 0xD1D0, 0x7814, 0xD1D1, 0x8712, 0xD1D2, 0x5CA9, 0xD1D3, 0x5EF6, 0xD1D4, 0x8A00, 0xD1D5, 0x989C, + 0xD1D6, 0x960E, 0xD1D7, 0x708E, 0xD1D8, 0x6CBF, 0xD1D9, 0x5944, 0xD1DA, 0x63A9, 0xD1DB, 0x773C, 0xD1DC, 0x884D, 0xD1DD, 0x6F14, + 0xD1DE, 0x8273, 0xD1DF, 0x5830, 0xD1E0, 0x71D5, 0xD1E1, 0x538C, 0xD1E2, 0x781A, 0xD1E3, 0x96C1, 0xD1E4, 0x5501, 0xD1E5, 0x5F66, + 0xD1E6, 0x7130, 0xD1E7, 0x5BB4, 0xD1E8, 0x8C1A, 0xD1E9, 0x9A8C, 0xD1EA, 0x6B83, 0xD1EB, 0x592E, 0xD1EC, 0x9E2F, 0xD1ED, 0x79E7, + 0xD1EE, 0x6768, 0xD1EF, 0x626C, 0xD1F0, 0x4F6F, 0xD1F1, 0x75A1, 0xD1F2, 0x7F8A, 0xD1F3, 0x6D0B, 0xD1F4, 0x9633, 0xD1F5, 0x6C27, + 0xD1F6, 0x4EF0, 0xD1F7, 0x75D2, 0xD1F8, 0x517B, 0xD1F9, 0x6837, 0xD1FA, 0x6F3E, 0xD1FB, 0x9080, 0xD1FC, 0x8170, 0xD1FD, 0x5996, + 0xD1FE, 0x7476, 0xD240, 0x8938, 0xD241, 0x8939, 0xD242, 0x893A, 0xD243, 0x893B, 0xD244, 0x893C, 0xD245, 0x893D, 0xD246, 0x893E, + 0xD247, 0x893F, 0xD248, 0x8940, 0xD249, 0x8942, 0xD24A, 0x8943, 0xD24B, 0x8945, 0xD24C, 0x8946, 0xD24D, 0x8947, 0xD24E, 0x8948, + 0xD24F, 0x8949, 0xD250, 0x894A, 0xD251, 0x894B, 0xD252, 0x894C, 0xD253, 0x894D, 0xD254, 0x894E, 0xD255, 0x894F, 0xD256, 0x8950, + 0xD257, 0x8951, 0xD258, 0x8952, 0xD259, 0x8953, 0xD25A, 0x8954, 0xD25B, 0x8955, 0xD25C, 0x8956, 0xD25D, 0x8957, 0xD25E, 0x8958, + 0xD25F, 0x8959, 0xD260, 0x895A, 0xD261, 0x895B, 0xD262, 0x895C, 0xD263, 0x895D, 0xD264, 0x8960, 0xD265, 0x8961, 0xD266, 0x8962, + 0xD267, 0x8963, 0xD268, 0x8964, 0xD269, 0x8965, 0xD26A, 0x8967, 0xD26B, 0x8968, 0xD26C, 0x8969, 0xD26D, 0x896A, 0xD26E, 0x896B, + 0xD26F, 0x896C, 0xD270, 0x896D, 0xD271, 0x896E, 0xD272, 0x896F, 0xD273, 0x8970, 0xD274, 0x8971, 0xD275, 0x8972, 0xD276, 0x8973, + 0xD277, 0x8974, 0xD278, 0x8975, 0xD279, 0x8976, 0xD27A, 0x8977, 0xD27B, 0x8978, 0xD27C, 0x8979, 0xD27D, 0x897A, 0xD27E, 0x897C, + 0xD280, 0x897D, 0xD281, 0x897E, 0xD282, 0x8980, 0xD283, 0x8982, 0xD284, 0x8984, 0xD285, 0x8985, 0xD286, 0x8987, 0xD287, 0x8988, + 0xD288, 0x8989, 0xD289, 0x898A, 0xD28A, 0x898B, 0xD28B, 0x898C, 0xD28C, 0x898D, 0xD28D, 0x898E, 0xD28E, 0x898F, 0xD28F, 0x8990, + 0xD290, 0x8991, 0xD291, 0x8992, 0xD292, 0x8993, 0xD293, 0x8994, 0xD294, 0x8995, 0xD295, 0x8996, 0xD296, 0x8997, 0xD297, 0x8998, + 0xD298, 0x8999, 0xD299, 0x899A, 0xD29A, 0x899B, 0xD29B, 0x899C, 0xD29C, 0x899D, 0xD29D, 0x899E, 0xD29E, 0x899F, 0xD29F, 0x89A0, + 0xD2A0, 0x89A1, 0xD2A1, 0x6447, 0xD2A2, 0x5C27, 0xD2A3, 0x9065, 0xD2A4, 0x7A91, 0xD2A5, 0x8C23, 0xD2A6, 0x59DA, 0xD2A7, 0x54AC, + 0xD2A8, 0x8200, 0xD2A9, 0x836F, 0xD2AA, 0x8981, 0xD2AB, 0x8000, 0xD2AC, 0x6930, 0xD2AD, 0x564E, 0xD2AE, 0x8036, 0xD2AF, 0x7237, + 0xD2B0, 0x91CE, 0xD2B1, 0x51B6, 0xD2B2, 0x4E5F, 0xD2B3, 0x9875, 0xD2B4, 0x6396, 0xD2B5, 0x4E1A, 0xD2B6, 0x53F6, 0xD2B7, 0x66F3, + 0xD2B8, 0x814B, 0xD2B9, 0x591C, 0xD2BA, 0x6DB2, 0xD2BB, 0x4E00, 0xD2BC, 0x58F9, 0xD2BD, 0x533B, 0xD2BE, 0x63D6, 0xD2BF, 0x94F1, + 0xD2C0, 0x4F9D, 0xD2C1, 0x4F0A, 0xD2C2, 0x8863, 0xD2C3, 0x9890, 0xD2C4, 0x5937, 0xD2C5, 0x9057, 0xD2C6, 0x79FB, 0xD2C7, 0x4EEA, + 0xD2C8, 0x80F0, 0xD2C9, 0x7591, 0xD2CA, 0x6C82, 0xD2CB, 0x5B9C, 0xD2CC, 0x59E8, 0xD2CD, 0x5F5D, 0xD2CE, 0x6905, 0xD2CF, 0x8681, + 0xD2D0, 0x501A, 0xD2D1, 0x5DF2, 0xD2D2, 0x4E59, 0xD2D3, 0x77E3, 0xD2D4, 0x4EE5, 0xD2D5, 0x827A, 0xD2D6, 0x6291, 0xD2D7, 0x6613, + 0xD2D8, 0x9091, 0xD2D9, 0x5C79, 0xD2DA, 0x4EBF, 0xD2DB, 0x5F79, 0xD2DC, 0x81C6, 0xD2DD, 0x9038, 0xD2DE, 0x8084, 0xD2DF, 0x75AB, + 0xD2E0, 0x4EA6, 0xD2E1, 0x88D4, 0xD2E2, 0x610F, 0xD2E3, 0x6BC5, 0xD2E4, 0x5FC6, 0xD2E5, 0x4E49, 0xD2E6, 0x76CA, 0xD2E7, 0x6EA2, + 0xD2E8, 0x8BE3, 0xD2E9, 0x8BAE, 0xD2EA, 0x8C0A, 0xD2EB, 0x8BD1, 0xD2EC, 0x5F02, 0xD2ED, 0x7FFC, 0xD2EE, 0x7FCC, 0xD2EF, 0x7ECE, + 0xD2F0, 0x8335, 0xD2F1, 0x836B, 0xD2F2, 0x56E0, 0xD2F3, 0x6BB7, 0xD2F4, 0x97F3, 0xD2F5, 0x9634, 0xD2F6, 0x59FB, 0xD2F7, 0x541F, + 0xD2F8, 0x94F6, 0xD2F9, 0x6DEB, 0xD2FA, 0x5BC5, 0xD2FB, 0x996E, 0xD2FC, 0x5C39, 0xD2FD, 0x5F15, 0xD2FE, 0x9690, 0xD340, 0x89A2, + 0xD341, 0x89A3, 0xD342, 0x89A4, 0xD343, 0x89A5, 0xD344, 0x89A6, 0xD345, 0x89A7, 0xD346, 0x89A8, 0xD347, 0x89A9, 0xD348, 0x89AA, + 0xD349, 0x89AB, 0xD34A, 0x89AC, 0xD34B, 0x89AD, 0xD34C, 0x89AE, 0xD34D, 0x89AF, 0xD34E, 0x89B0, 0xD34F, 0x89B1, 0xD350, 0x89B2, + 0xD351, 0x89B3, 0xD352, 0x89B4, 0xD353, 0x89B5, 0xD354, 0x89B6, 0xD355, 0x89B7, 0xD356, 0x89B8, 0xD357, 0x89B9, 0xD358, 0x89BA, + 0xD359, 0x89BB, 0xD35A, 0x89BC, 0xD35B, 0x89BD, 0xD35C, 0x89BE, 0xD35D, 0x89BF, 0xD35E, 0x89C0, 0xD35F, 0x89C3, 0xD360, 0x89CD, + 0xD361, 0x89D3, 0xD362, 0x89D4, 0xD363, 0x89D5, 0xD364, 0x89D7, 0xD365, 0x89D8, 0xD366, 0x89D9, 0xD367, 0x89DB, 0xD368, 0x89DD, + 0xD369, 0x89DF, 0xD36A, 0x89E0, 0xD36B, 0x89E1, 0xD36C, 0x89E2, 0xD36D, 0x89E4, 0xD36E, 0x89E7, 0xD36F, 0x89E8, 0xD370, 0x89E9, + 0xD371, 0x89EA, 0xD372, 0x89EC, 0xD373, 0x89ED, 0xD374, 0x89EE, 0xD375, 0x89F0, 0xD376, 0x89F1, 0xD377, 0x89F2, 0xD378, 0x89F4, + 0xD379, 0x89F5, 0xD37A, 0x89F6, 0xD37B, 0x89F7, 0xD37C, 0x89F8, 0xD37D, 0x89F9, 0xD37E, 0x89FA, 0xD380, 0x89FB, 0xD381, 0x89FC, + 0xD382, 0x89FD, 0xD383, 0x89FE, 0xD384, 0x89FF, 0xD385, 0x8A01, 0xD386, 0x8A02, 0xD387, 0x8A03, 0xD388, 0x8A04, 0xD389, 0x8A05, + 0xD38A, 0x8A06, 0xD38B, 0x8A08, 0xD38C, 0x8A09, 0xD38D, 0x8A0A, 0xD38E, 0x8A0B, 0xD38F, 0x8A0C, 0xD390, 0x8A0D, 0xD391, 0x8A0E, + 0xD392, 0x8A0F, 0xD393, 0x8A10, 0xD394, 0x8A11, 0xD395, 0x8A12, 0xD396, 0x8A13, 0xD397, 0x8A14, 0xD398, 0x8A15, 0xD399, 0x8A16, + 0xD39A, 0x8A17, 0xD39B, 0x8A18, 0xD39C, 0x8A19, 0xD39D, 0x8A1A, 0xD39E, 0x8A1B, 0xD39F, 0x8A1C, 0xD3A0, 0x8A1D, 0xD3A1, 0x5370, + 0xD3A2, 0x82F1, 0xD3A3, 0x6A31, 0xD3A4, 0x5A74, 0xD3A5, 0x9E70, 0xD3A6, 0x5E94, 0xD3A7, 0x7F28, 0xD3A8, 0x83B9, 0xD3A9, 0x8424, + 0xD3AA, 0x8425, 0xD3AB, 0x8367, 0xD3AC, 0x8747, 0xD3AD, 0x8FCE, 0xD3AE, 0x8D62, 0xD3AF, 0x76C8, 0xD3B0, 0x5F71, 0xD3B1, 0x9896, + 0xD3B2, 0x786C, 0xD3B3, 0x6620, 0xD3B4, 0x54DF, 0xD3B5, 0x62E5, 0xD3B6, 0x4F63, 0xD3B7, 0x81C3, 0xD3B8, 0x75C8, 0xD3B9, 0x5EB8, + 0xD3BA, 0x96CD, 0xD3BB, 0x8E0A, 0xD3BC, 0x86F9, 0xD3BD, 0x548F, 0xD3BE, 0x6CF3, 0xD3BF, 0x6D8C, 0xD3C0, 0x6C38, 0xD3C1, 0x607F, + 0xD3C2, 0x52C7, 0xD3C3, 0x7528, 0xD3C4, 0x5E7D, 0xD3C5, 0x4F18, 0xD3C6, 0x60A0, 0xD3C7, 0x5FE7, 0xD3C8, 0x5C24, 0xD3C9, 0x7531, + 0xD3CA, 0x90AE, 0xD3CB, 0x94C0, 0xD3CC, 0x72B9, 0xD3CD, 0x6CB9, 0xD3CE, 0x6E38, 0xD3CF, 0x9149, 0xD3D0, 0x6709, 0xD3D1, 0x53CB, + 0xD3D2, 0x53F3, 0xD3D3, 0x4F51, 0xD3D4, 0x91C9, 0xD3D5, 0x8BF1, 0xD3D6, 0x53C8, 0xD3D7, 0x5E7C, 0xD3D8, 0x8FC2, 0xD3D9, 0x6DE4, + 0xD3DA, 0x4E8E, 0xD3DB, 0x76C2, 0xD3DC, 0x6986, 0xD3DD, 0x865E, 0xD3DE, 0x611A, 0xD3DF, 0x8206, 0xD3E0, 0x4F59, 0xD3E1, 0x4FDE, + 0xD3E2, 0x903E, 0xD3E3, 0x9C7C, 0xD3E4, 0x6109, 0xD3E5, 0x6E1D, 0xD3E6, 0x6E14, 0xD3E7, 0x9685, 0xD3E8, 0x4E88, 0xD3E9, 0x5A31, + 0xD3EA, 0x96E8, 0xD3EB, 0x4E0E, 0xD3EC, 0x5C7F, 0xD3ED, 0x79B9, 0xD3EE, 0x5B87, 0xD3EF, 0x8BED, 0xD3F0, 0x7FBD, 0xD3F1, 0x7389, + 0xD3F2, 0x57DF, 0xD3F3, 0x828B, 0xD3F4, 0x90C1, 0xD3F5, 0x5401, 0xD3F6, 0x9047, 0xD3F7, 0x55BB, 0xD3F8, 0x5CEA, 0xD3F9, 0x5FA1, + 0xD3FA, 0x6108, 0xD3FB, 0x6B32, 0xD3FC, 0x72F1, 0xD3FD, 0x80B2, 0xD3FE, 0x8A89, 0xD440, 0x8A1E, 0xD441, 0x8A1F, 0xD442, 0x8A20, + 0xD443, 0x8A21, 0xD444, 0x8A22, 0xD445, 0x8A23, 0xD446, 0x8A24, 0xD447, 0x8A25, 0xD448, 0x8A26, 0xD449, 0x8A27, 0xD44A, 0x8A28, + 0xD44B, 0x8A29, 0xD44C, 0x8A2A, 0xD44D, 0x8A2B, 0xD44E, 0x8A2C, 0xD44F, 0x8A2D, 0xD450, 0x8A2E, 0xD451, 0x8A2F, 0xD452, 0x8A30, + 0xD453, 0x8A31, 0xD454, 0x8A32, 0xD455, 0x8A33, 0xD456, 0x8A34, 0xD457, 0x8A35, 0xD458, 0x8A36, 0xD459, 0x8A37, 0xD45A, 0x8A38, + 0xD45B, 0x8A39, 0xD45C, 0x8A3A, 0xD45D, 0x8A3B, 0xD45E, 0x8A3C, 0xD45F, 0x8A3D, 0xD460, 0x8A3F, 0xD461, 0x8A40, 0xD462, 0x8A41, + 0xD463, 0x8A42, 0xD464, 0x8A43, 0xD465, 0x8A44, 0xD466, 0x8A45, 0xD467, 0x8A46, 0xD468, 0x8A47, 0xD469, 0x8A49, 0xD46A, 0x8A4A, + 0xD46B, 0x8A4B, 0xD46C, 0x8A4C, 0xD46D, 0x8A4D, 0xD46E, 0x8A4E, 0xD46F, 0x8A4F, 0xD470, 0x8A50, 0xD471, 0x8A51, 0xD472, 0x8A52, + 0xD473, 0x8A53, 0xD474, 0x8A54, 0xD475, 0x8A55, 0xD476, 0x8A56, 0xD477, 0x8A57, 0xD478, 0x8A58, 0xD479, 0x8A59, 0xD47A, 0x8A5A, + 0xD47B, 0x8A5B, 0xD47C, 0x8A5C, 0xD47D, 0x8A5D, 0xD47E, 0x8A5E, 0xD480, 0x8A5F, 0xD481, 0x8A60, 0xD482, 0x8A61, 0xD483, 0x8A62, + 0xD484, 0x8A63, 0xD485, 0x8A64, 0xD486, 0x8A65, 0xD487, 0x8A66, 0xD488, 0x8A67, 0xD489, 0x8A68, 0xD48A, 0x8A69, 0xD48B, 0x8A6A, + 0xD48C, 0x8A6B, 0xD48D, 0x8A6C, 0xD48E, 0x8A6D, 0xD48F, 0x8A6E, 0xD490, 0x8A6F, 0xD491, 0x8A70, 0xD492, 0x8A71, 0xD493, 0x8A72, + 0xD494, 0x8A73, 0xD495, 0x8A74, 0xD496, 0x8A75, 0xD497, 0x8A76, 0xD498, 0x8A77, 0xD499, 0x8A78, 0xD49A, 0x8A7A, 0xD49B, 0x8A7B, + 0xD49C, 0x8A7C, 0xD49D, 0x8A7D, 0xD49E, 0x8A7E, 0xD49F, 0x8A7F, 0xD4A0, 0x8A80, 0xD4A1, 0x6D74, 0xD4A2, 0x5BD3, 0xD4A3, 0x88D5, + 0xD4A4, 0x9884, 0xD4A5, 0x8C6B, 0xD4A6, 0x9A6D, 0xD4A7, 0x9E33, 0xD4A8, 0x6E0A, 0xD4A9, 0x51A4, 0xD4AA, 0x5143, 0xD4AB, 0x57A3, + 0xD4AC, 0x8881, 0xD4AD, 0x539F, 0xD4AE, 0x63F4, 0xD4AF, 0x8F95, 0xD4B0, 0x56ED, 0xD4B1, 0x5458, 0xD4B2, 0x5706, 0xD4B3, 0x733F, + 0xD4B4, 0x6E90, 0xD4B5, 0x7F18, 0xD4B6, 0x8FDC, 0xD4B7, 0x82D1, 0xD4B8, 0x613F, 0xD4B9, 0x6028, 0xD4BA, 0x9662, 0xD4BB, 0x66F0, + 0xD4BC, 0x7EA6, 0xD4BD, 0x8D8A, 0xD4BE, 0x8DC3, 0xD4BF, 0x94A5, 0xD4C0, 0x5CB3, 0xD4C1, 0x7CA4, 0xD4C2, 0x6708, 0xD4C3, 0x60A6, + 0xD4C4, 0x9605, 0xD4C5, 0x8018, 0xD4C6, 0x4E91, 0xD4C7, 0x90E7, 0xD4C8, 0x5300, 0xD4C9, 0x9668, 0xD4CA, 0x5141, 0xD4CB, 0x8FD0, + 0xD4CC, 0x8574, 0xD4CD, 0x915D, 0xD4CE, 0x6655, 0xD4CF, 0x97F5, 0xD4D0, 0x5B55, 0xD4D1, 0x531D, 0xD4D2, 0x7838, 0xD4D3, 0x6742, + 0xD4D4, 0x683D, 0xD4D5, 0x54C9, 0xD4D6, 0x707E, 0xD4D7, 0x5BB0, 0xD4D8, 0x8F7D, 0xD4D9, 0x518D, 0xD4DA, 0x5728, 0xD4DB, 0x54B1, + 0xD4DC, 0x6512, 0xD4DD, 0x6682, 0xD4DE, 0x8D5E, 0xD4DF, 0x8D43, 0xD4E0, 0x810F, 0xD4E1, 0x846C, 0xD4E2, 0x906D, 0xD4E3, 0x7CDF, + 0xD4E4, 0x51FF, 0xD4E5, 0x85FB, 0xD4E6, 0x67A3, 0xD4E7, 0x65E9, 0xD4E8, 0x6FA1, 0xD4E9, 0x86A4, 0xD4EA, 0x8E81, 0xD4EB, 0x566A, + 0xD4EC, 0x9020, 0xD4ED, 0x7682, 0xD4EE, 0x7076, 0xD4EF, 0x71E5, 0xD4F0, 0x8D23, 0xD4F1, 0x62E9, 0xD4F2, 0x5219, 0xD4F3, 0x6CFD, + 0xD4F4, 0x8D3C, 0xD4F5, 0x600E, 0xD4F6, 0x589E, 0xD4F7, 0x618E, 0xD4F8, 0x66FE, 0xD4F9, 0x8D60, 0xD4FA, 0x624E, 0xD4FB, 0x55B3, + 0xD4FC, 0x6E23, 0xD4FD, 0x672D, 0xD4FE, 0x8F67, 0xD540, 0x8A81, 0xD541, 0x8A82, 0xD542, 0x8A83, 0xD543, 0x8A84, 0xD544, 0x8A85, + 0xD545, 0x8A86, 0xD546, 0x8A87, 0xD547, 0x8A88, 0xD548, 0x8A8B, 0xD549, 0x8A8C, 0xD54A, 0x8A8D, 0xD54B, 0x8A8E, 0xD54C, 0x8A8F, + 0xD54D, 0x8A90, 0xD54E, 0x8A91, 0xD54F, 0x8A92, 0xD550, 0x8A94, 0xD551, 0x8A95, 0xD552, 0x8A96, 0xD553, 0x8A97, 0xD554, 0x8A98, + 0xD555, 0x8A99, 0xD556, 0x8A9A, 0xD557, 0x8A9B, 0xD558, 0x8A9C, 0xD559, 0x8A9D, 0xD55A, 0x8A9E, 0xD55B, 0x8A9F, 0xD55C, 0x8AA0, + 0xD55D, 0x8AA1, 0xD55E, 0x8AA2, 0xD55F, 0x8AA3, 0xD560, 0x8AA4, 0xD561, 0x8AA5, 0xD562, 0x8AA6, 0xD563, 0x8AA7, 0xD564, 0x8AA8, + 0xD565, 0x8AA9, 0xD566, 0x8AAA, 0xD567, 0x8AAB, 0xD568, 0x8AAC, 0xD569, 0x8AAD, 0xD56A, 0x8AAE, 0xD56B, 0x8AAF, 0xD56C, 0x8AB0, + 0xD56D, 0x8AB1, 0xD56E, 0x8AB2, 0xD56F, 0x8AB3, 0xD570, 0x8AB4, 0xD571, 0x8AB5, 0xD572, 0x8AB6, 0xD573, 0x8AB7, 0xD574, 0x8AB8, + 0xD575, 0x8AB9, 0xD576, 0x8ABA, 0xD577, 0x8ABB, 0xD578, 0x8ABC, 0xD579, 0x8ABD, 0xD57A, 0x8ABE, 0xD57B, 0x8ABF, 0xD57C, 0x8AC0, + 0xD57D, 0x8AC1, 0xD57E, 0x8AC2, 0xD580, 0x8AC3, 0xD581, 0x8AC4, 0xD582, 0x8AC5, 0xD583, 0x8AC6, 0xD584, 0x8AC7, 0xD585, 0x8AC8, + 0xD586, 0x8AC9, 0xD587, 0x8ACA, 0xD588, 0x8ACB, 0xD589, 0x8ACC, 0xD58A, 0x8ACD, 0xD58B, 0x8ACE, 0xD58C, 0x8ACF, 0xD58D, 0x8AD0, + 0xD58E, 0x8AD1, 0xD58F, 0x8AD2, 0xD590, 0x8AD3, 0xD591, 0x8AD4, 0xD592, 0x8AD5, 0xD593, 0x8AD6, 0xD594, 0x8AD7, 0xD595, 0x8AD8, + 0xD596, 0x8AD9, 0xD597, 0x8ADA, 0xD598, 0x8ADB, 0xD599, 0x8ADC, 0xD59A, 0x8ADD, 0xD59B, 0x8ADE, 0xD59C, 0x8ADF, 0xD59D, 0x8AE0, + 0xD59E, 0x8AE1, 0xD59F, 0x8AE2, 0xD5A0, 0x8AE3, 0xD5A1, 0x94E1, 0xD5A2, 0x95F8, 0xD5A3, 0x7728, 0xD5A4, 0x6805, 0xD5A5, 0x69A8, + 0xD5A6, 0x548B, 0xD5A7, 0x4E4D, 0xD5A8, 0x70B8, 0xD5A9, 0x8BC8, 0xD5AA, 0x6458, 0xD5AB, 0x658B, 0xD5AC, 0x5B85, 0xD5AD, 0x7A84, + 0xD5AE, 0x503A, 0xD5AF, 0x5BE8, 0xD5B0, 0x77BB, 0xD5B1, 0x6BE1, 0xD5B2, 0x8A79, 0xD5B3, 0x7C98, 0xD5B4, 0x6CBE, 0xD5B5, 0x76CF, + 0xD5B6, 0x65A9, 0xD5B7, 0x8F97, 0xD5B8, 0x5D2D, 0xD5B9, 0x5C55, 0xD5BA, 0x8638, 0xD5BB, 0x6808, 0xD5BC, 0x5360, 0xD5BD, 0x6218, + 0xD5BE, 0x7AD9, 0xD5BF, 0x6E5B, 0xD5C0, 0x7EFD, 0xD5C1, 0x6A1F, 0xD5C2, 0x7AE0, 0xD5C3, 0x5F70, 0xD5C4, 0x6F33, 0xD5C5, 0x5F20, + 0xD5C6, 0x638C, 0xD5C7, 0x6DA8, 0xD5C8, 0x6756, 0xD5C9, 0x4E08, 0xD5CA, 0x5E10, 0xD5CB, 0x8D26, 0xD5CC, 0x4ED7, 0xD5CD, 0x80C0, + 0xD5CE, 0x7634, 0xD5CF, 0x969C, 0xD5D0, 0x62DB, 0xD5D1, 0x662D, 0xD5D2, 0x627E, 0xD5D3, 0x6CBC, 0xD5D4, 0x8D75, 0xD5D5, 0x7167, + 0xD5D6, 0x7F69, 0xD5D7, 0x5146, 0xD5D8, 0x8087, 0xD5D9, 0x53EC, 0xD5DA, 0x906E, 0xD5DB, 0x6298, 0xD5DC, 0x54F2, 0xD5DD, 0x86F0, + 0xD5DE, 0x8F99, 0xD5DF, 0x8005, 0xD5E0, 0x9517, 0xD5E1, 0x8517, 0xD5E2, 0x8FD9, 0xD5E3, 0x6D59, 0xD5E4, 0x73CD, 0xD5E5, 0x659F, + 0xD5E6, 0x771F, 0xD5E7, 0x7504, 0xD5E8, 0x7827, 0xD5E9, 0x81FB, 0xD5EA, 0x8D1E, 0xD5EB, 0x9488, 0xD5EC, 0x4FA6, 0xD5ED, 0x6795, + 0xD5EE, 0x75B9, 0xD5EF, 0x8BCA, 0xD5F0, 0x9707, 0xD5F1, 0x632F, 0xD5F2, 0x9547, 0xD5F3, 0x9635, 0xD5F4, 0x84B8, 0xD5F5, 0x6323, + 0xD5F6, 0x7741, 0xD5F7, 0x5F81, 0xD5F8, 0x72F0, 0xD5F9, 0x4E89, 0xD5FA, 0x6014, 0xD5FB, 0x6574, 0xD5FC, 0x62EF, 0xD5FD, 0x6B63, + 0xD5FE, 0x653F, 0xD640, 0x8AE4, 0xD641, 0x8AE5, 0xD642, 0x8AE6, 0xD643, 0x8AE7, 0xD644, 0x8AE8, 0xD645, 0x8AE9, 0xD646, 0x8AEA, + 0xD647, 0x8AEB, 0xD648, 0x8AEC, 0xD649, 0x8AED, 0xD64A, 0x8AEE, 0xD64B, 0x8AEF, 0xD64C, 0x8AF0, 0xD64D, 0x8AF1, 0xD64E, 0x8AF2, + 0xD64F, 0x8AF3, 0xD650, 0x8AF4, 0xD651, 0x8AF5, 0xD652, 0x8AF6, 0xD653, 0x8AF7, 0xD654, 0x8AF8, 0xD655, 0x8AF9, 0xD656, 0x8AFA, + 0xD657, 0x8AFB, 0xD658, 0x8AFC, 0xD659, 0x8AFD, 0xD65A, 0x8AFE, 0xD65B, 0x8AFF, 0xD65C, 0x8B00, 0xD65D, 0x8B01, 0xD65E, 0x8B02, + 0xD65F, 0x8B03, 0xD660, 0x8B04, 0xD661, 0x8B05, 0xD662, 0x8B06, 0xD663, 0x8B08, 0xD664, 0x8B09, 0xD665, 0x8B0A, 0xD666, 0x8B0B, + 0xD667, 0x8B0C, 0xD668, 0x8B0D, 0xD669, 0x8B0E, 0xD66A, 0x8B0F, 0xD66B, 0x8B10, 0xD66C, 0x8B11, 0xD66D, 0x8B12, 0xD66E, 0x8B13, + 0xD66F, 0x8B14, 0xD670, 0x8B15, 0xD671, 0x8B16, 0xD672, 0x8B17, 0xD673, 0x8B18, 0xD674, 0x8B19, 0xD675, 0x8B1A, 0xD676, 0x8B1B, + 0xD677, 0x8B1C, 0xD678, 0x8B1D, 0xD679, 0x8B1E, 0xD67A, 0x8B1F, 0xD67B, 0x8B20, 0xD67C, 0x8B21, 0xD67D, 0x8B22, 0xD67E, 0x8B23, + 0xD680, 0x8B24, 0xD681, 0x8B25, 0xD682, 0x8B27, 0xD683, 0x8B28, 0xD684, 0x8B29, 0xD685, 0x8B2A, 0xD686, 0x8B2B, 0xD687, 0x8B2C, + 0xD688, 0x8B2D, 0xD689, 0x8B2E, 0xD68A, 0x8B2F, 0xD68B, 0x8B30, 0xD68C, 0x8B31, 0xD68D, 0x8B32, 0xD68E, 0x8B33, 0xD68F, 0x8B34, + 0xD690, 0x8B35, 0xD691, 0x8B36, 0xD692, 0x8B37, 0xD693, 0x8B38, 0xD694, 0x8B39, 0xD695, 0x8B3A, 0xD696, 0x8B3B, 0xD697, 0x8B3C, + 0xD698, 0x8B3D, 0xD699, 0x8B3E, 0xD69A, 0x8B3F, 0xD69B, 0x8B40, 0xD69C, 0x8B41, 0xD69D, 0x8B42, 0xD69E, 0x8B43, 0xD69F, 0x8B44, + 0xD6A0, 0x8B45, 0xD6A1, 0x5E27, 0xD6A2, 0x75C7, 0xD6A3, 0x90D1, 0xD6A4, 0x8BC1, 0xD6A5, 0x829D, 0xD6A6, 0x679D, 0xD6A7, 0x652F, + 0xD6A8, 0x5431, 0xD6A9, 0x8718, 0xD6AA, 0x77E5, 0xD6AB, 0x80A2, 0xD6AC, 0x8102, 0xD6AD, 0x6C41, 0xD6AE, 0x4E4B, 0xD6AF, 0x7EC7, + 0xD6B0, 0x804C, 0xD6B1, 0x76F4, 0xD6B2, 0x690D, 0xD6B3, 0x6B96, 0xD6B4, 0x6267, 0xD6B5, 0x503C, 0xD6B6, 0x4F84, 0xD6B7, 0x5740, + 0xD6B8, 0x6307, 0xD6B9, 0x6B62, 0xD6BA, 0x8DBE, 0xD6BB, 0x53EA, 0xD6BC, 0x65E8, 0xD6BD, 0x7EB8, 0xD6BE, 0x5FD7, 0xD6BF, 0x631A, + 0xD6C0, 0x63B7, 0xD6C1, 0x81F3, 0xD6C2, 0x81F4, 0xD6C3, 0x7F6E, 0xD6C4, 0x5E1C, 0xD6C5, 0x5CD9, 0xD6C6, 0x5236, 0xD6C7, 0x667A, + 0xD6C8, 0x79E9, 0xD6C9, 0x7A1A, 0xD6CA, 0x8D28, 0xD6CB, 0x7099, 0xD6CC, 0x75D4, 0xD6CD, 0x6EDE, 0xD6CE, 0x6CBB, 0xD6CF, 0x7A92, + 0xD6D0, 0x4E2D, 0xD6D1, 0x76C5, 0xD6D2, 0x5FE0, 0xD6D3, 0x949F, 0xD6D4, 0x8877, 0xD6D5, 0x7EC8, 0xD6D6, 0x79CD, 0xD6D7, 0x80BF, + 0xD6D8, 0x91CD, 0xD6D9, 0x4EF2, 0xD6DA, 0x4F17, 0xD6DB, 0x821F, 0xD6DC, 0x5468, 0xD6DD, 0x5DDE, 0xD6DE, 0x6D32, 0xD6DF, 0x8BCC, + 0xD6E0, 0x7CA5, 0xD6E1, 0x8F74, 0xD6E2, 0x8098, 0xD6E3, 0x5E1A, 0xD6E4, 0x5492, 0xD6E5, 0x76B1, 0xD6E6, 0x5B99, 0xD6E7, 0x663C, + 0xD6E8, 0x9AA4, 0xD6E9, 0x73E0, 0xD6EA, 0x682A, 0xD6EB, 0x86DB, 0xD6EC, 0x6731, 0xD6ED, 0x732A, 0xD6EE, 0x8BF8, 0xD6EF, 0x8BDB, + 0xD6F0, 0x9010, 0xD6F1, 0x7AF9, 0xD6F2, 0x70DB, 0xD6F3, 0x716E, 0xD6F4, 0x62C4, 0xD6F5, 0x77A9, 0xD6F6, 0x5631, 0xD6F7, 0x4E3B, + 0xD6F8, 0x8457, 0xD6F9, 0x67F1, 0xD6FA, 0x52A9, 0xD6FB, 0x86C0, 0xD6FC, 0x8D2E, 0xD6FD, 0x94F8, 0xD6FE, 0x7B51, 0xD740, 0x8B46, + 0xD741, 0x8B47, 0xD742, 0x8B48, 0xD743, 0x8B49, 0xD744, 0x8B4A, 0xD745, 0x8B4B, 0xD746, 0x8B4C, 0xD747, 0x8B4D, 0xD748, 0x8B4E, + 0xD749, 0x8B4F, 0xD74A, 0x8B50, 0xD74B, 0x8B51, 0xD74C, 0x8B52, 0xD74D, 0x8B53, 0xD74E, 0x8B54, 0xD74F, 0x8B55, 0xD750, 0x8B56, + 0xD751, 0x8B57, 0xD752, 0x8B58, 0xD753, 0x8B59, 0xD754, 0x8B5A, 0xD755, 0x8B5B, 0xD756, 0x8B5C, 0xD757, 0x8B5D, 0xD758, 0x8B5E, + 0xD759, 0x8B5F, 0xD75A, 0x8B60, 0xD75B, 0x8B61, 0xD75C, 0x8B62, 0xD75D, 0x8B63, 0xD75E, 0x8B64, 0xD75F, 0x8B65, 0xD760, 0x8B67, + 0xD761, 0x8B68, 0xD762, 0x8B69, 0xD763, 0x8B6A, 0xD764, 0x8B6B, 0xD765, 0x8B6D, 0xD766, 0x8B6E, 0xD767, 0x8B6F, 0xD768, 0x8B70, + 0xD769, 0x8B71, 0xD76A, 0x8B72, 0xD76B, 0x8B73, 0xD76C, 0x8B74, 0xD76D, 0x8B75, 0xD76E, 0x8B76, 0xD76F, 0x8B77, 0xD770, 0x8B78, + 0xD771, 0x8B79, 0xD772, 0x8B7A, 0xD773, 0x8B7B, 0xD774, 0x8B7C, 0xD775, 0x8B7D, 0xD776, 0x8B7E, 0xD777, 0x8B7F, 0xD778, 0x8B80, + 0xD779, 0x8B81, 0xD77A, 0x8B82, 0xD77B, 0x8B83, 0xD77C, 0x8B84, 0xD77D, 0x8B85, 0xD77E, 0x8B86, 0xD780, 0x8B87, 0xD781, 0x8B88, + 0xD782, 0x8B89, 0xD783, 0x8B8A, 0xD784, 0x8B8B, 0xD785, 0x8B8C, 0xD786, 0x8B8D, 0xD787, 0x8B8E, 0xD788, 0x8B8F, 0xD789, 0x8B90, + 0xD78A, 0x8B91, 0xD78B, 0x8B92, 0xD78C, 0x8B93, 0xD78D, 0x8B94, 0xD78E, 0x8B95, 0xD78F, 0x8B96, 0xD790, 0x8B97, 0xD791, 0x8B98, + 0xD792, 0x8B99, 0xD793, 0x8B9A, 0xD794, 0x8B9B, 0xD795, 0x8B9C, 0xD796, 0x8B9D, 0xD797, 0x8B9E, 0xD798, 0x8B9F, 0xD799, 0x8BAC, + 0xD79A, 0x8BB1, 0xD79B, 0x8BBB, 0xD79C, 0x8BC7, 0xD79D, 0x8BD0, 0xD79E, 0x8BEA, 0xD79F, 0x8C09, 0xD7A0, 0x8C1E, 0xD7A1, 0x4F4F, + 0xD7A2, 0x6CE8, 0xD7A3, 0x795D, 0xD7A4, 0x9A7B, 0xD7A5, 0x6293, 0xD7A6, 0x722A, 0xD7A7, 0x62FD, 0xD7A8, 0x4E13, 0xD7A9, 0x7816, + 0xD7AA, 0x8F6C, 0xD7AB, 0x64B0, 0xD7AC, 0x8D5A, 0xD7AD, 0x7BC6, 0xD7AE, 0x6869, 0xD7AF, 0x5E84, 0xD7B0, 0x88C5, 0xD7B1, 0x5986, + 0xD7B2, 0x649E, 0xD7B3, 0x58EE, 0xD7B4, 0x72B6, 0xD7B5, 0x690E, 0xD7B6, 0x9525, 0xD7B7, 0x8FFD, 0xD7B8, 0x8D58, 0xD7B9, 0x5760, + 0xD7BA, 0x7F00, 0xD7BB, 0x8C06, 0xD7BC, 0x51C6, 0xD7BD, 0x6349, 0xD7BE, 0x62D9, 0xD7BF, 0x5353, 0xD7C0, 0x684C, 0xD7C1, 0x7422, + 0xD7C2, 0x8301, 0xD7C3, 0x914C, 0xD7C4, 0x5544, 0xD7C5, 0x7740, 0xD7C6, 0x707C, 0xD7C7, 0x6D4A, 0xD7C8, 0x5179, 0xD7C9, 0x54A8, + 0xD7CA, 0x8D44, 0xD7CB, 0x59FF, 0xD7CC, 0x6ECB, 0xD7CD, 0x6DC4, 0xD7CE, 0x5B5C, 0xD7CF, 0x7D2B, 0xD7D0, 0x4ED4, 0xD7D1, 0x7C7D, + 0xD7D2, 0x6ED3, 0xD7D3, 0x5B50, 0xD7D4, 0x81EA, 0xD7D5, 0x6E0D, 0xD7D6, 0x5B57, 0xD7D7, 0x9B03, 0xD7D8, 0x68D5, 0xD7D9, 0x8E2A, + 0xD7DA, 0x5B97, 0xD7DB, 0x7EFC, 0xD7DC, 0x603B, 0xD7DD, 0x7EB5, 0xD7DE, 0x90B9, 0xD7DF, 0x8D70, 0xD7E0, 0x594F, 0xD7E1, 0x63CD, + 0xD7E2, 0x79DF, 0xD7E3, 0x8DB3, 0xD7E4, 0x5352, 0xD7E5, 0x65CF, 0xD7E6, 0x7956, 0xD7E7, 0x8BC5, 0xD7E8, 0x963B, 0xD7E9, 0x7EC4, + 0xD7EA, 0x94BB, 0xD7EB, 0x7E82, 0xD7EC, 0x5634, 0xD7ED, 0x9189, 0xD7EE, 0x6700, 0xD7EF, 0x7F6A, 0xD7F0, 0x5C0A, 0xD7F1, 0x9075, + 0xD7F2, 0x6628, 0xD7F3, 0x5DE6, 0xD7F4, 0x4F50, 0xD7F5, 0x67DE, 0xD7F6, 0x505A, 0xD7F7, 0x4F5C, 0xD7F8, 0x5750, 0xD7F9, 0x5EA7, + 0xD840, 0x8C38, 0xD841, 0x8C39, 0xD842, 0x8C3A, 0xD843, 0x8C3B, 0xD844, 0x8C3C, 0xD845, 0x8C3D, 0xD846, 0x8C3E, 0xD847, 0x8C3F, + 0xD848, 0x8C40, 0xD849, 0x8C42, 0xD84A, 0x8C43, 0xD84B, 0x8C44, 0xD84C, 0x8C45, 0xD84D, 0x8C48, 0xD84E, 0x8C4A, 0xD84F, 0x8C4B, + 0xD850, 0x8C4D, 0xD851, 0x8C4E, 0xD852, 0x8C4F, 0xD853, 0x8C50, 0xD854, 0x8C51, 0xD855, 0x8C52, 0xD856, 0x8C53, 0xD857, 0x8C54, + 0xD858, 0x8C56, 0xD859, 0x8C57, 0xD85A, 0x8C58, 0xD85B, 0x8C59, 0xD85C, 0x8C5B, 0xD85D, 0x8C5C, 0xD85E, 0x8C5D, 0xD85F, 0x8C5E, + 0xD860, 0x8C5F, 0xD861, 0x8C60, 0xD862, 0x8C63, 0xD863, 0x8C64, 0xD864, 0x8C65, 0xD865, 0x8C66, 0xD866, 0x8C67, 0xD867, 0x8C68, + 0xD868, 0x8C69, 0xD869, 0x8C6C, 0xD86A, 0x8C6D, 0xD86B, 0x8C6E, 0xD86C, 0x8C6F, 0xD86D, 0x8C70, 0xD86E, 0x8C71, 0xD86F, 0x8C72, + 0xD870, 0x8C74, 0xD871, 0x8C75, 0xD872, 0x8C76, 0xD873, 0x8C77, 0xD874, 0x8C7B, 0xD875, 0x8C7C, 0xD876, 0x8C7D, 0xD877, 0x8C7E, + 0xD878, 0x8C7F, 0xD879, 0x8C80, 0xD87A, 0x8C81, 0xD87B, 0x8C83, 0xD87C, 0x8C84, 0xD87D, 0x8C86, 0xD87E, 0x8C87, 0xD880, 0x8C88, + 0xD881, 0x8C8B, 0xD882, 0x8C8D, 0xD883, 0x8C8E, 0xD884, 0x8C8F, 0xD885, 0x8C90, 0xD886, 0x8C91, 0xD887, 0x8C92, 0xD888, 0x8C93, + 0xD889, 0x8C95, 0xD88A, 0x8C96, 0xD88B, 0x8C97, 0xD88C, 0x8C99, 0xD88D, 0x8C9A, 0xD88E, 0x8C9B, 0xD88F, 0x8C9C, 0xD890, 0x8C9D, + 0xD891, 0x8C9E, 0xD892, 0x8C9F, 0xD893, 0x8CA0, 0xD894, 0x8CA1, 0xD895, 0x8CA2, 0xD896, 0x8CA3, 0xD897, 0x8CA4, 0xD898, 0x8CA5, + 0xD899, 0x8CA6, 0xD89A, 0x8CA7, 0xD89B, 0x8CA8, 0xD89C, 0x8CA9, 0xD89D, 0x8CAA, 0xD89E, 0x8CAB, 0xD89F, 0x8CAC, 0xD8A0, 0x8CAD, + 0xD8A1, 0x4E8D, 0xD8A2, 0x4E0C, 0xD8A3, 0x5140, 0xD8A4, 0x4E10, 0xD8A5, 0x5EFF, 0xD8A6, 0x5345, 0xD8A7, 0x4E15, 0xD8A8, 0x4E98, + 0xD8A9, 0x4E1E, 0xD8AA, 0x9B32, 0xD8AB, 0x5B6C, 0xD8AC, 0x5669, 0xD8AD, 0x4E28, 0xD8AE, 0x79BA, 0xD8AF, 0x4E3F, 0xD8B0, 0x5315, + 0xD8B1, 0x4E47, 0xD8B2, 0x592D, 0xD8B3, 0x723B, 0xD8B4, 0x536E, 0xD8B5, 0x6C10, 0xD8B6, 0x56DF, 0xD8B7, 0x80E4, 0xD8B8, 0x9997, + 0xD8B9, 0x6BD3, 0xD8BA, 0x777E, 0xD8BB, 0x9F17, 0xD8BC, 0x4E36, 0xD8BD, 0x4E9F, 0xD8BE, 0x9F10, 0xD8BF, 0x4E5C, 0xD8C0, 0x4E69, + 0xD8C1, 0x4E93, 0xD8C2, 0x8288, 0xD8C3, 0x5B5B, 0xD8C4, 0x556C, 0xD8C5, 0x560F, 0xD8C6, 0x4EC4, 0xD8C7, 0x538D, 0xD8C8, 0x539D, + 0xD8C9, 0x53A3, 0xD8CA, 0x53A5, 0xD8CB, 0x53AE, 0xD8CC, 0x9765, 0xD8CD, 0x8D5D, 0xD8CE, 0x531A, 0xD8CF, 0x53F5, 0xD8D0, 0x5326, + 0xD8D1, 0x532E, 0xD8D2, 0x533E, 0xD8D3, 0x8D5C, 0xD8D4, 0x5366, 0xD8D5, 0x5363, 0xD8D6, 0x5202, 0xD8D7, 0x5208, 0xD8D8, 0x520E, + 0xD8D9, 0x522D, 0xD8DA, 0x5233, 0xD8DB, 0x523F, 0xD8DC, 0x5240, 0xD8DD, 0x524C, 0xD8DE, 0x525E, 0xD8DF, 0x5261, 0xD8E0, 0x525C, + 0xD8E1, 0x84AF, 0xD8E2, 0x527D, 0xD8E3, 0x5282, 0xD8E4, 0x5281, 0xD8E5, 0x5290, 0xD8E6, 0x5293, 0xD8E7, 0x5182, 0xD8E8, 0x7F54, + 0xD8E9, 0x4EBB, 0xD8EA, 0x4EC3, 0xD8EB, 0x4EC9, 0xD8EC, 0x4EC2, 0xD8ED, 0x4EE8, 0xD8EE, 0x4EE1, 0xD8EF, 0x4EEB, 0xD8F0, 0x4EDE, + 0xD8F1, 0x4F1B, 0xD8F2, 0x4EF3, 0xD8F3, 0x4F22, 0xD8F4, 0x4F64, 0xD8F5, 0x4EF5, 0xD8F6, 0x4F25, 0xD8F7, 0x4F27, 0xD8F8, 0x4F09, + 0xD8F9, 0x4F2B, 0xD8FA, 0x4F5E, 0xD8FB, 0x4F67, 0xD8FC, 0x6538, 0xD8FD, 0x4F5A, 0xD8FE, 0x4F5D, 0xD940, 0x8CAE, 0xD941, 0x8CAF, + 0xD942, 0x8CB0, 0xD943, 0x8CB1, 0xD944, 0x8CB2, 0xD945, 0x8CB3, 0xD946, 0x8CB4, 0xD947, 0x8CB5, 0xD948, 0x8CB6, 0xD949, 0x8CB7, + 0xD94A, 0x8CB8, 0xD94B, 0x8CB9, 0xD94C, 0x8CBA, 0xD94D, 0x8CBB, 0xD94E, 0x8CBC, 0xD94F, 0x8CBD, 0xD950, 0x8CBE, 0xD951, 0x8CBF, + 0xD952, 0x8CC0, 0xD953, 0x8CC1, 0xD954, 0x8CC2, 0xD955, 0x8CC3, 0xD956, 0x8CC4, 0xD957, 0x8CC5, 0xD958, 0x8CC6, 0xD959, 0x8CC7, + 0xD95A, 0x8CC8, 0xD95B, 0x8CC9, 0xD95C, 0x8CCA, 0xD95D, 0x8CCB, 0xD95E, 0x8CCC, 0xD95F, 0x8CCD, 0xD960, 0x8CCE, 0xD961, 0x8CCF, + 0xD962, 0x8CD0, 0xD963, 0x8CD1, 0xD964, 0x8CD2, 0xD965, 0x8CD3, 0xD966, 0x8CD4, 0xD967, 0x8CD5, 0xD968, 0x8CD6, 0xD969, 0x8CD7, + 0xD96A, 0x8CD8, 0xD96B, 0x8CD9, 0xD96C, 0x8CDA, 0xD96D, 0x8CDB, 0xD96E, 0x8CDC, 0xD96F, 0x8CDD, 0xD970, 0x8CDE, 0xD971, 0x8CDF, + 0xD972, 0x8CE0, 0xD973, 0x8CE1, 0xD974, 0x8CE2, 0xD975, 0x8CE3, 0xD976, 0x8CE4, 0xD977, 0x8CE5, 0xD978, 0x8CE6, 0xD979, 0x8CE7, + 0xD97A, 0x8CE8, 0xD97B, 0x8CE9, 0xD97C, 0x8CEA, 0xD97D, 0x8CEB, 0xD97E, 0x8CEC, 0xD980, 0x8CED, 0xD981, 0x8CEE, 0xD982, 0x8CEF, + 0xD983, 0x8CF0, 0xD984, 0x8CF1, 0xD985, 0x8CF2, 0xD986, 0x8CF3, 0xD987, 0x8CF4, 0xD988, 0x8CF5, 0xD989, 0x8CF6, 0xD98A, 0x8CF7, + 0xD98B, 0x8CF8, 0xD98C, 0x8CF9, 0xD98D, 0x8CFA, 0xD98E, 0x8CFB, 0xD98F, 0x8CFC, 0xD990, 0x8CFD, 0xD991, 0x8CFE, 0xD992, 0x8CFF, + 0xD993, 0x8D00, 0xD994, 0x8D01, 0xD995, 0x8D02, 0xD996, 0x8D03, 0xD997, 0x8D04, 0xD998, 0x8D05, 0xD999, 0x8D06, 0xD99A, 0x8D07, + 0xD99B, 0x8D08, 0xD99C, 0x8D09, 0xD99D, 0x8D0A, 0xD99E, 0x8D0B, 0xD99F, 0x8D0C, 0xD9A0, 0x8D0D, 0xD9A1, 0x4F5F, 0xD9A2, 0x4F57, + 0xD9A3, 0x4F32, 0xD9A4, 0x4F3D, 0xD9A5, 0x4F76, 0xD9A6, 0x4F74, 0xD9A7, 0x4F91, 0xD9A8, 0x4F89, 0xD9A9, 0x4F83, 0xD9AA, 0x4F8F, + 0xD9AB, 0x4F7E, 0xD9AC, 0x4F7B, 0xD9AD, 0x4FAA, 0xD9AE, 0x4F7C, 0xD9AF, 0x4FAC, 0xD9B0, 0x4F94, 0xD9B1, 0x4FE6, 0xD9B2, 0x4FE8, + 0xD9B3, 0x4FEA, 0xD9B4, 0x4FC5, 0xD9B5, 0x4FDA, 0xD9B6, 0x4FE3, 0xD9B7, 0x4FDC, 0xD9B8, 0x4FD1, 0xD9B9, 0x4FDF, 0xD9BA, 0x4FF8, + 0xD9BB, 0x5029, 0xD9BC, 0x504C, 0xD9BD, 0x4FF3, 0xD9BE, 0x502C, 0xD9BF, 0x500F, 0xD9C0, 0x502E, 0xD9C1, 0x502D, 0xD9C2, 0x4FFE, + 0xD9C3, 0x501C, 0xD9C4, 0x500C, 0xD9C5, 0x5025, 0xD9C6, 0x5028, 0xD9C7, 0x507E, 0xD9C8, 0x5043, 0xD9C9, 0x5055, 0xD9CA, 0x5048, + 0xD9CB, 0x504E, 0xD9CC, 0x506C, 0xD9CD, 0x507B, 0xD9CE, 0x50A5, 0xD9CF, 0x50A7, 0xD9D0, 0x50A9, 0xD9D1, 0x50BA, 0xD9D2, 0x50D6, + 0xD9D3, 0x5106, 0xD9D4, 0x50ED, 0xD9D5, 0x50EC, 0xD9D6, 0x50E6, 0xD9D7, 0x50EE, 0xD9D8, 0x5107, 0xD9D9, 0x510B, 0xD9DA, 0x4EDD, + 0xD9DB, 0x6C3D, 0xD9DC, 0x4F58, 0xD9DD, 0x4F65, 0xD9DE, 0x4FCE, 0xD9DF, 0x9FA0, 0xD9E0, 0x6C46, 0xD9E1, 0x7C74, 0xD9E2, 0x516E, + 0xD9E3, 0x5DFD, 0xD9E4, 0x9EC9, 0xD9E5, 0x9998, 0xD9E6, 0x5181, 0xD9E7, 0x5914, 0xD9E8, 0x52F9, 0xD9E9, 0x530D, 0xD9EA, 0x8A07, + 0xD9EB, 0x5310, 0xD9EC, 0x51EB, 0xD9ED, 0x5919, 0xD9EE, 0x5155, 0xD9EF, 0x4EA0, 0xD9F0, 0x5156, 0xD9F1, 0x4EB3, 0xD9F2, 0x886E, + 0xD9F3, 0x88A4, 0xD9F4, 0x4EB5, 0xD9F5, 0x8114, 0xD9F6, 0x88D2, 0xD9F7, 0x7980, 0xD9F8, 0x5B34, 0xD9F9, 0x8803, 0xD9FA, 0x7FB8, + 0xD9FB, 0x51AB, 0xD9FC, 0x51B1, 0xD9FD, 0x51BD, 0xD9FE, 0x51BC, 0xDA40, 0x8D0E, 0xDA41, 0x8D0F, 0xDA42, 0x8D10, 0xDA43, 0x8D11, + 0xDA44, 0x8D12, 0xDA45, 0x8D13, 0xDA46, 0x8D14, 0xDA47, 0x8D15, 0xDA48, 0x8D16, 0xDA49, 0x8D17, 0xDA4A, 0x8D18, 0xDA4B, 0x8D19, + 0xDA4C, 0x8D1A, 0xDA4D, 0x8D1B, 0xDA4E, 0x8D1C, 0xDA4F, 0x8D20, 0xDA50, 0x8D51, 0xDA51, 0x8D52, 0xDA52, 0x8D57, 0xDA53, 0x8D5F, + 0xDA54, 0x8D65, 0xDA55, 0x8D68, 0xDA56, 0x8D69, 0xDA57, 0x8D6A, 0xDA58, 0x8D6C, 0xDA59, 0x8D6E, 0xDA5A, 0x8D6F, 0xDA5B, 0x8D71, + 0xDA5C, 0x8D72, 0xDA5D, 0x8D78, 0xDA5E, 0x8D79, 0xDA5F, 0x8D7A, 0xDA60, 0x8D7B, 0xDA61, 0x8D7C, 0xDA62, 0x8D7D, 0xDA63, 0x8D7E, + 0xDA64, 0x8D7F, 0xDA65, 0x8D80, 0xDA66, 0x8D82, 0xDA67, 0x8D83, 0xDA68, 0x8D86, 0xDA69, 0x8D87, 0xDA6A, 0x8D88, 0xDA6B, 0x8D89, + 0xDA6C, 0x8D8C, 0xDA6D, 0x8D8D, 0xDA6E, 0x8D8E, 0xDA6F, 0x8D8F, 0xDA70, 0x8D90, 0xDA71, 0x8D92, 0xDA72, 0x8D93, 0xDA73, 0x8D95, + 0xDA74, 0x8D96, 0xDA75, 0x8D97, 0xDA76, 0x8D98, 0xDA77, 0x8D99, 0xDA78, 0x8D9A, 0xDA79, 0x8D9B, 0xDA7A, 0x8D9C, 0xDA7B, 0x8D9D, + 0xDA7C, 0x8D9E, 0xDA7D, 0x8DA0, 0xDA7E, 0x8DA1, 0xDA80, 0x8DA2, 0xDA81, 0x8DA4, 0xDA82, 0x8DA5, 0xDA83, 0x8DA6, 0xDA84, 0x8DA7, + 0xDA85, 0x8DA8, 0xDA86, 0x8DA9, 0xDA87, 0x8DAA, 0xDA88, 0x8DAB, 0xDA89, 0x8DAC, 0xDA8A, 0x8DAD, 0xDA8B, 0x8DAE, 0xDA8C, 0x8DAF, + 0xDA8D, 0x8DB0, 0xDA8E, 0x8DB2, 0xDA8F, 0x8DB6, 0xDA90, 0x8DB7, 0xDA91, 0x8DB9, 0xDA92, 0x8DBB, 0xDA93, 0x8DBD, 0xDA94, 0x8DC0, + 0xDA95, 0x8DC1, 0xDA96, 0x8DC2, 0xDA97, 0x8DC5, 0xDA98, 0x8DC7, 0xDA99, 0x8DC8, 0xDA9A, 0x8DC9, 0xDA9B, 0x8DCA, 0xDA9C, 0x8DCD, + 0xDA9D, 0x8DD0, 0xDA9E, 0x8DD2, 0xDA9F, 0x8DD3, 0xDAA0, 0x8DD4, 0xDAA1, 0x51C7, 0xDAA2, 0x5196, 0xDAA3, 0x51A2, 0xDAA4, 0x51A5, + 0xDAA5, 0x8BA0, 0xDAA6, 0x8BA6, 0xDAA7, 0x8BA7, 0xDAA8, 0x8BAA, 0xDAA9, 0x8BB4, 0xDAAA, 0x8BB5, 0xDAAB, 0x8BB7, 0xDAAC, 0x8BC2, + 0xDAAD, 0x8BC3, 0xDAAE, 0x8BCB, 0xDAAF, 0x8BCF, 0xDAB0, 0x8BCE, 0xDAB1, 0x8BD2, 0xDAB2, 0x8BD3, 0xDAB3, 0x8BD4, 0xDAB4, 0x8BD6, + 0xDAB5, 0x8BD8, 0xDAB6, 0x8BD9, 0xDAB7, 0x8BDC, 0xDAB8, 0x8BDF, 0xDAB9, 0x8BE0, 0xDABA, 0x8BE4, 0xDABB, 0x8BE8, 0xDABC, 0x8BE9, + 0xDABD, 0x8BEE, 0xDABE, 0x8BF0, 0xDABF, 0x8BF3, 0xDAC0, 0x8BF6, 0xDAC1, 0x8BF9, 0xDAC2, 0x8BFC, 0xDAC3, 0x8BFF, 0xDAC4, 0x8C00, + 0xDAC5, 0x8C02, 0xDAC6, 0x8C04, 0xDAC7, 0x8C07, 0xDAC8, 0x8C0C, 0xDAC9, 0x8C0F, 0xDACA, 0x8C11, 0xDACB, 0x8C12, 0xDACC, 0x8C14, + 0xDACD, 0x8C15, 0xDACE, 0x8C16, 0xDACF, 0x8C19, 0xDAD0, 0x8C1B, 0xDAD1, 0x8C18, 0xDAD2, 0x8C1D, 0xDAD3, 0x8C1F, 0xDAD4, 0x8C20, + 0xDAD5, 0x8C21, 0xDAD6, 0x8C25, 0xDAD7, 0x8C27, 0xDAD8, 0x8C2A, 0xDAD9, 0x8C2B, 0xDADA, 0x8C2E, 0xDADB, 0x8C2F, 0xDADC, 0x8C32, + 0xDADD, 0x8C33, 0xDADE, 0x8C35, 0xDADF, 0x8C36, 0xDAE0, 0x5369, 0xDAE1, 0x537A, 0xDAE2, 0x961D, 0xDAE3, 0x9622, 0xDAE4, 0x9621, + 0xDAE5, 0x9631, 0xDAE6, 0x962A, 0xDAE7, 0x963D, 0xDAE8, 0x963C, 0xDAE9, 0x9642, 0xDAEA, 0x9649, 0xDAEB, 0x9654, 0xDAEC, 0x965F, + 0xDAED, 0x9667, 0xDAEE, 0x966C, 0xDAEF, 0x9672, 0xDAF0, 0x9674, 0xDAF1, 0x9688, 0xDAF2, 0x968D, 0xDAF3, 0x9697, 0xDAF4, 0x96B0, + 0xDAF5, 0x9097, 0xDAF6, 0x909B, 0xDAF7, 0x909D, 0xDAF8, 0x9099, 0xDAF9, 0x90AC, 0xDAFA, 0x90A1, 0xDAFB, 0x90B4, 0xDAFC, 0x90B3, + 0xDAFD, 0x90B6, 0xDAFE, 0x90BA, 0xDB40, 0x8DD5, 0xDB41, 0x8DD8, 0xDB42, 0x8DD9, 0xDB43, 0x8DDC, 0xDB44, 0x8DE0, 0xDB45, 0x8DE1, + 0xDB46, 0x8DE2, 0xDB47, 0x8DE5, 0xDB48, 0x8DE6, 0xDB49, 0x8DE7, 0xDB4A, 0x8DE9, 0xDB4B, 0x8DED, 0xDB4C, 0x8DEE, 0xDB4D, 0x8DF0, + 0xDB4E, 0x8DF1, 0xDB4F, 0x8DF2, 0xDB50, 0x8DF4, 0xDB51, 0x8DF6, 0xDB52, 0x8DFC, 0xDB53, 0x8DFE, 0xDB54, 0x8DFF, 0xDB55, 0x8E00, + 0xDB56, 0x8E01, 0xDB57, 0x8E02, 0xDB58, 0x8E03, 0xDB59, 0x8E04, 0xDB5A, 0x8E06, 0xDB5B, 0x8E07, 0xDB5C, 0x8E08, 0xDB5D, 0x8E0B, + 0xDB5E, 0x8E0D, 0xDB5F, 0x8E0E, 0xDB60, 0x8E10, 0xDB61, 0x8E11, 0xDB62, 0x8E12, 0xDB63, 0x8E13, 0xDB64, 0x8E15, 0xDB65, 0x8E16, + 0xDB66, 0x8E17, 0xDB67, 0x8E18, 0xDB68, 0x8E19, 0xDB69, 0x8E1A, 0xDB6A, 0x8E1B, 0xDB6B, 0x8E1C, 0xDB6C, 0x8E20, 0xDB6D, 0x8E21, + 0xDB6E, 0x8E24, 0xDB6F, 0x8E25, 0xDB70, 0x8E26, 0xDB71, 0x8E27, 0xDB72, 0x8E28, 0xDB73, 0x8E2B, 0xDB74, 0x8E2D, 0xDB75, 0x8E30, + 0xDB76, 0x8E32, 0xDB77, 0x8E33, 0xDB78, 0x8E34, 0xDB79, 0x8E36, 0xDB7A, 0x8E37, 0xDB7B, 0x8E38, 0xDB7C, 0x8E3B, 0xDB7D, 0x8E3C, + 0xDB7E, 0x8E3E, 0xDB80, 0x8E3F, 0xDB81, 0x8E43, 0xDB82, 0x8E45, 0xDB83, 0x8E46, 0xDB84, 0x8E4C, 0xDB85, 0x8E4D, 0xDB86, 0x8E4E, + 0xDB87, 0x8E4F, 0xDB88, 0x8E50, 0xDB89, 0x8E53, 0xDB8A, 0x8E54, 0xDB8B, 0x8E55, 0xDB8C, 0x8E56, 0xDB8D, 0x8E57, 0xDB8E, 0x8E58, + 0xDB8F, 0x8E5A, 0xDB90, 0x8E5B, 0xDB91, 0x8E5C, 0xDB92, 0x8E5D, 0xDB93, 0x8E5E, 0xDB94, 0x8E5F, 0xDB95, 0x8E60, 0xDB96, 0x8E61, + 0xDB97, 0x8E62, 0xDB98, 0x8E63, 0xDB99, 0x8E64, 0xDB9A, 0x8E65, 0xDB9B, 0x8E67, 0xDB9C, 0x8E68, 0xDB9D, 0x8E6A, 0xDB9E, 0x8E6B, + 0xDB9F, 0x8E6E, 0xDBA0, 0x8E71, 0xDBA1, 0x90B8, 0xDBA2, 0x90B0, 0xDBA3, 0x90CF, 0xDBA4, 0x90C5, 0xDBA5, 0x90BE, 0xDBA6, 0x90D0, + 0xDBA7, 0x90C4, 0xDBA8, 0x90C7, 0xDBA9, 0x90D3, 0xDBAA, 0x90E6, 0xDBAB, 0x90E2, 0xDBAC, 0x90DC, 0xDBAD, 0x90D7, 0xDBAE, 0x90DB, + 0xDBAF, 0x90EB, 0xDBB0, 0x90EF, 0xDBB1, 0x90FE, 0xDBB2, 0x9104, 0xDBB3, 0x9122, 0xDBB4, 0x911E, 0xDBB5, 0x9123, 0xDBB6, 0x9131, + 0xDBB7, 0x912F, 0xDBB8, 0x9139, 0xDBB9, 0x9143, 0xDBBA, 0x9146, 0xDBBB, 0x520D, 0xDBBC, 0x5942, 0xDBBD, 0x52A2, 0xDBBE, 0x52AC, + 0xDBBF, 0x52AD, 0xDBC0, 0x52BE, 0xDBC1, 0x54FF, 0xDBC2, 0x52D0, 0xDBC3, 0x52D6, 0xDBC4, 0x52F0, 0xDBC5, 0x53DF, 0xDBC6, 0x71EE, + 0xDBC7, 0x77CD, 0xDBC8, 0x5EF4, 0xDBC9, 0x51F5, 0xDBCA, 0x51FC, 0xDBCB, 0x9B2F, 0xDBCC, 0x53B6, 0xDBCD, 0x5F01, 0xDBCE, 0x755A, + 0xDBCF, 0x5DEF, 0xDBD0, 0x574C, 0xDBD1, 0x57A9, 0xDBD2, 0x57A1, 0xDBD3, 0x587E, 0xDBD4, 0x58BC, 0xDBD5, 0x58C5, 0xDBD6, 0x58D1, + 0xDBD7, 0x5729, 0xDBD8, 0x572C, 0xDBD9, 0x572A, 0xDBDA, 0x5733, 0xDBDB, 0x5739, 0xDBDC, 0x572E, 0xDBDD, 0x572F, 0xDBDE, 0x575C, + 0xDBDF, 0x573B, 0xDBE0, 0x5742, 0xDBE1, 0x5769, 0xDBE2, 0x5785, 0xDBE3, 0x576B, 0xDBE4, 0x5786, 0xDBE5, 0x577C, 0xDBE6, 0x577B, + 0xDBE7, 0x5768, 0xDBE8, 0x576D, 0xDBE9, 0x5776, 0xDBEA, 0x5773, 0xDBEB, 0x57AD, 0xDBEC, 0x57A4, 0xDBED, 0x578C, 0xDBEE, 0x57B2, + 0xDBEF, 0x57CF, 0xDBF0, 0x57A7, 0xDBF1, 0x57B4, 0xDBF2, 0x5793, 0xDBF3, 0x57A0, 0xDBF4, 0x57D5, 0xDBF5, 0x57D8, 0xDBF6, 0x57DA, + 0xDBF7, 0x57D9, 0xDBF8, 0x57D2, 0xDBF9, 0x57B8, 0xDBFA, 0x57F4, 0xDBFB, 0x57EF, 0xDBFC, 0x57F8, 0xDBFD, 0x57E4, 0xDBFE, 0x57DD, + 0xDC40, 0x8E73, 0xDC41, 0x8E75, 0xDC42, 0x8E77, 0xDC43, 0x8E78, 0xDC44, 0x8E79, 0xDC45, 0x8E7A, 0xDC46, 0x8E7B, 0xDC47, 0x8E7D, + 0xDC48, 0x8E7E, 0xDC49, 0x8E80, 0xDC4A, 0x8E82, 0xDC4B, 0x8E83, 0xDC4C, 0x8E84, 0xDC4D, 0x8E86, 0xDC4E, 0x8E88, 0xDC4F, 0x8E89, + 0xDC50, 0x8E8A, 0xDC51, 0x8E8B, 0xDC52, 0x8E8C, 0xDC53, 0x8E8D, 0xDC54, 0x8E8E, 0xDC55, 0x8E91, 0xDC56, 0x8E92, 0xDC57, 0x8E93, + 0xDC58, 0x8E95, 0xDC59, 0x8E96, 0xDC5A, 0x8E97, 0xDC5B, 0x8E98, 0xDC5C, 0x8E99, 0xDC5D, 0x8E9A, 0xDC5E, 0x8E9B, 0xDC5F, 0x8E9D, + 0xDC60, 0x8E9F, 0xDC61, 0x8EA0, 0xDC62, 0x8EA1, 0xDC63, 0x8EA2, 0xDC64, 0x8EA3, 0xDC65, 0x8EA4, 0xDC66, 0x8EA5, 0xDC67, 0x8EA6, + 0xDC68, 0x8EA7, 0xDC69, 0x8EA8, 0xDC6A, 0x8EA9, 0xDC6B, 0x8EAA, 0xDC6C, 0x8EAD, 0xDC6D, 0x8EAE, 0xDC6E, 0x8EB0, 0xDC6F, 0x8EB1, + 0xDC70, 0x8EB3, 0xDC71, 0x8EB4, 0xDC72, 0x8EB5, 0xDC73, 0x8EB6, 0xDC74, 0x8EB7, 0xDC75, 0x8EB8, 0xDC76, 0x8EB9, 0xDC77, 0x8EBB, + 0xDC78, 0x8EBC, 0xDC79, 0x8EBD, 0xDC7A, 0x8EBE, 0xDC7B, 0x8EBF, 0xDC7C, 0x8EC0, 0xDC7D, 0x8EC1, 0xDC7E, 0x8EC2, 0xDC80, 0x8EC3, + 0xDC81, 0x8EC4, 0xDC82, 0x8EC5, 0xDC83, 0x8EC6, 0xDC84, 0x8EC7, 0xDC85, 0x8EC8, 0xDC86, 0x8EC9, 0xDC87, 0x8ECA, 0xDC88, 0x8ECB, + 0xDC89, 0x8ECC, 0xDC8A, 0x8ECD, 0xDC8B, 0x8ECF, 0xDC8C, 0x8ED0, 0xDC8D, 0x8ED1, 0xDC8E, 0x8ED2, 0xDC8F, 0x8ED3, 0xDC90, 0x8ED4, + 0xDC91, 0x8ED5, 0xDC92, 0x8ED6, 0xDC93, 0x8ED7, 0xDC94, 0x8ED8, 0xDC95, 0x8ED9, 0xDC96, 0x8EDA, 0xDC97, 0x8EDB, 0xDC98, 0x8EDC, + 0xDC99, 0x8EDD, 0xDC9A, 0x8EDE, 0xDC9B, 0x8EDF, 0xDC9C, 0x8EE0, 0xDC9D, 0x8EE1, 0xDC9E, 0x8EE2, 0xDC9F, 0x8EE3, 0xDCA0, 0x8EE4, + 0xDCA1, 0x580B, 0xDCA2, 0x580D, 0xDCA3, 0x57FD, 0xDCA4, 0x57ED, 0xDCA5, 0x5800, 0xDCA6, 0x581E, 0xDCA7, 0x5819, 0xDCA8, 0x5844, + 0xDCA9, 0x5820, 0xDCAA, 0x5865, 0xDCAB, 0x586C, 0xDCAC, 0x5881, 0xDCAD, 0x5889, 0xDCAE, 0x589A, 0xDCAF, 0x5880, 0xDCB0, 0x99A8, + 0xDCB1, 0x9F19, 0xDCB2, 0x61FF, 0xDCB3, 0x8279, 0xDCB4, 0x827D, 0xDCB5, 0x827F, 0xDCB6, 0x828F, 0xDCB7, 0x828A, 0xDCB8, 0x82A8, + 0xDCB9, 0x8284, 0xDCBA, 0x828E, 0xDCBB, 0x8291, 0xDCBC, 0x8297, 0xDCBD, 0x8299, 0xDCBE, 0x82AB, 0xDCBF, 0x82B8, 0xDCC0, 0x82BE, + 0xDCC1, 0x82B0, 0xDCC2, 0x82C8, 0xDCC3, 0x82CA, 0xDCC4, 0x82E3, 0xDCC5, 0x8298, 0xDCC6, 0x82B7, 0xDCC7, 0x82AE, 0xDCC8, 0x82CB, + 0xDCC9, 0x82CC, 0xDCCA, 0x82C1, 0xDCCB, 0x82A9, 0xDCCC, 0x82B4, 0xDCCD, 0x82A1, 0xDCCE, 0x82AA, 0xDCCF, 0x829F, 0xDCD0, 0x82C4, + 0xDCD1, 0x82CE, 0xDCD2, 0x82A4, 0xDCD3, 0x82E1, 0xDCD4, 0x8309, 0xDCD5, 0x82F7, 0xDCD6, 0x82E4, 0xDCD7, 0x830F, 0xDCD8, 0x8307, + 0xDCD9, 0x82DC, 0xDCDA, 0x82F4, 0xDCDB, 0x82D2, 0xDCDC, 0x82D8, 0xDCDD, 0x830C, 0xDCDE, 0x82FB, 0xDCDF, 0x82D3, 0xDCE0, 0x8311, + 0xDCE1, 0x831A, 0xDCE2, 0x8306, 0xDCE3, 0x8314, 0xDCE4, 0x8315, 0xDCE5, 0x82E0, 0xDCE6, 0x82D5, 0xDCE7, 0x831C, 0xDCE8, 0x8351, + 0xDCE9, 0x835B, 0xDCEA, 0x835C, 0xDCEB, 0x8308, 0xDCEC, 0x8392, 0xDCED, 0x833C, 0xDCEE, 0x8334, 0xDCEF, 0x8331, 0xDCF0, 0x839B, + 0xDCF1, 0x835E, 0xDCF2, 0x832F, 0xDCF3, 0x834F, 0xDCF4, 0x8347, 0xDCF5, 0x8343, 0xDCF6, 0x835F, 0xDCF7, 0x8340, 0xDCF8, 0x8317, + 0xDCF9, 0x8360, 0xDCFA, 0x832D, 0xDCFB, 0x833A, 0xDCFC, 0x8333, 0xDCFD, 0x8366, 0xDCFE, 0x8365, 0xDD40, 0x8EE5, 0xDD41, 0x8EE6, + 0xDD42, 0x8EE7, 0xDD43, 0x8EE8, 0xDD44, 0x8EE9, 0xDD45, 0x8EEA, 0xDD46, 0x8EEB, 0xDD47, 0x8EEC, 0xDD48, 0x8EED, 0xDD49, 0x8EEE, + 0xDD4A, 0x8EEF, 0xDD4B, 0x8EF0, 0xDD4C, 0x8EF1, 0xDD4D, 0x8EF2, 0xDD4E, 0x8EF3, 0xDD4F, 0x8EF4, 0xDD50, 0x8EF5, 0xDD51, 0x8EF6, + 0xDD52, 0x8EF7, 0xDD53, 0x8EF8, 0xDD54, 0x8EF9, 0xDD55, 0x8EFA, 0xDD56, 0x8EFB, 0xDD57, 0x8EFC, 0xDD58, 0x8EFD, 0xDD59, 0x8EFE, + 0xDD5A, 0x8EFF, 0xDD5B, 0x8F00, 0xDD5C, 0x8F01, 0xDD5D, 0x8F02, 0xDD5E, 0x8F03, 0xDD5F, 0x8F04, 0xDD60, 0x8F05, 0xDD61, 0x8F06, + 0xDD62, 0x8F07, 0xDD63, 0x8F08, 0xDD64, 0x8F09, 0xDD65, 0x8F0A, 0xDD66, 0x8F0B, 0xDD67, 0x8F0C, 0xDD68, 0x8F0D, 0xDD69, 0x8F0E, + 0xDD6A, 0x8F0F, 0xDD6B, 0x8F10, 0xDD6C, 0x8F11, 0xDD6D, 0x8F12, 0xDD6E, 0x8F13, 0xDD6F, 0x8F14, 0xDD70, 0x8F15, 0xDD71, 0x8F16, + 0xDD72, 0x8F17, 0xDD73, 0x8F18, 0xDD74, 0x8F19, 0xDD75, 0x8F1A, 0xDD76, 0x8F1B, 0xDD77, 0x8F1C, 0xDD78, 0x8F1D, 0xDD79, 0x8F1E, + 0xDD7A, 0x8F1F, 0xDD7B, 0x8F20, 0xDD7C, 0x8F21, 0xDD7D, 0x8F22, 0xDD7E, 0x8F23, 0xDD80, 0x8F24, 0xDD81, 0x8F25, 0xDD82, 0x8F26, + 0xDD83, 0x8F27, 0xDD84, 0x8F28, 0xDD85, 0x8F29, 0xDD86, 0x8F2A, 0xDD87, 0x8F2B, 0xDD88, 0x8F2C, 0xDD89, 0x8F2D, 0xDD8A, 0x8F2E, + 0xDD8B, 0x8F2F, 0xDD8C, 0x8F30, 0xDD8D, 0x8F31, 0xDD8E, 0x8F32, 0xDD8F, 0x8F33, 0xDD90, 0x8F34, 0xDD91, 0x8F35, 0xDD92, 0x8F36, + 0xDD93, 0x8F37, 0xDD94, 0x8F38, 0xDD95, 0x8F39, 0xDD96, 0x8F3A, 0xDD97, 0x8F3B, 0xDD98, 0x8F3C, 0xDD99, 0x8F3D, 0xDD9A, 0x8F3E, + 0xDD9B, 0x8F3F, 0xDD9C, 0x8F40, 0xDD9D, 0x8F41, 0xDD9E, 0x8F42, 0xDD9F, 0x8F43, 0xDDA0, 0x8F44, 0xDDA1, 0x8368, 0xDDA2, 0x831B, + 0xDDA3, 0x8369, 0xDDA4, 0x836C, 0xDDA5, 0x836A, 0xDDA6, 0x836D, 0xDDA7, 0x836E, 0xDDA8, 0x83B0, 0xDDA9, 0x8378, 0xDDAA, 0x83B3, + 0xDDAB, 0x83B4, 0xDDAC, 0x83A0, 0xDDAD, 0x83AA, 0xDDAE, 0x8393, 0xDDAF, 0x839C, 0xDDB0, 0x8385, 0xDDB1, 0x837C, 0xDDB2, 0x83B6, + 0xDDB3, 0x83A9, 0xDDB4, 0x837D, 0xDDB5, 0x83B8, 0xDDB6, 0x837B, 0xDDB7, 0x8398, 0xDDB8, 0x839E, 0xDDB9, 0x83A8, 0xDDBA, 0x83BA, + 0xDDBB, 0x83BC, 0xDDBC, 0x83C1, 0xDDBD, 0x8401, 0xDDBE, 0x83E5, 0xDDBF, 0x83D8, 0xDDC0, 0x5807, 0xDDC1, 0x8418, 0xDDC2, 0x840B, + 0xDDC3, 0x83DD, 0xDDC4, 0x83FD, 0xDDC5, 0x83D6, 0xDDC6, 0x841C, 0xDDC7, 0x8438, 0xDDC8, 0x8411, 0xDDC9, 0x8406, 0xDDCA, 0x83D4, + 0xDDCB, 0x83DF, 0xDDCC, 0x840F, 0xDDCD, 0x8403, 0xDDCE, 0x83F8, 0xDDCF, 0x83F9, 0xDDD0, 0x83EA, 0xDDD1, 0x83C5, 0xDDD2, 0x83C0, + 0xDDD3, 0x8426, 0xDDD4, 0x83F0, 0xDDD5, 0x83E1, 0xDDD6, 0x845C, 0xDDD7, 0x8451, 0xDDD8, 0x845A, 0xDDD9, 0x8459, 0xDDDA, 0x8473, + 0xDDDB, 0x8487, 0xDDDC, 0x8488, 0xDDDD, 0x847A, 0xDDDE, 0x8489, 0xDDDF, 0x8478, 0xDDE0, 0x843C, 0xDDE1, 0x8446, 0xDDE2, 0x8469, + 0xDDE3, 0x8476, 0xDDE4, 0x848C, 0xDDE5, 0x848E, 0xDDE6, 0x8431, 0xDDE7, 0x846D, 0xDDE8, 0x84C1, 0xDDE9, 0x84CD, 0xDDEA, 0x84D0, + 0xDDEB, 0x84E6, 0xDDEC, 0x84BD, 0xDDED, 0x84D3, 0xDDEE, 0x84CA, 0xDDEF, 0x84BF, 0xDDF0, 0x84BA, 0xDDF1, 0x84E0, 0xDDF2, 0x84A1, + 0xDDF3, 0x84B9, 0xDDF4, 0x84B4, 0xDDF5, 0x8497, 0xDDF6, 0x84E5, 0xDDF7, 0x84E3, 0xDDF8, 0x850C, 0xDDF9, 0x750D, 0xDDFA, 0x8538, + 0xDDFB, 0x84F0, 0xDDFC, 0x8539, 0xDDFD, 0x851F, 0xDDFE, 0x853A, 0xDE40, 0x8F45, 0xDE41, 0x8F46, 0xDE42, 0x8F47, 0xDE43, 0x8F48, + 0xDE44, 0x8F49, 0xDE45, 0x8F4A, 0xDE46, 0x8F4B, 0xDE47, 0x8F4C, 0xDE48, 0x8F4D, 0xDE49, 0x8F4E, 0xDE4A, 0x8F4F, 0xDE4B, 0x8F50, + 0xDE4C, 0x8F51, 0xDE4D, 0x8F52, 0xDE4E, 0x8F53, 0xDE4F, 0x8F54, 0xDE50, 0x8F55, 0xDE51, 0x8F56, 0xDE52, 0x8F57, 0xDE53, 0x8F58, + 0xDE54, 0x8F59, 0xDE55, 0x8F5A, 0xDE56, 0x8F5B, 0xDE57, 0x8F5C, 0xDE58, 0x8F5D, 0xDE59, 0x8F5E, 0xDE5A, 0x8F5F, 0xDE5B, 0x8F60, + 0xDE5C, 0x8F61, 0xDE5D, 0x8F62, 0xDE5E, 0x8F63, 0xDE5F, 0x8F64, 0xDE60, 0x8F65, 0xDE61, 0x8F6A, 0xDE62, 0x8F80, 0xDE63, 0x8F8C, + 0xDE64, 0x8F92, 0xDE65, 0x8F9D, 0xDE66, 0x8FA0, 0xDE67, 0x8FA1, 0xDE68, 0x8FA2, 0xDE69, 0x8FA4, 0xDE6A, 0x8FA5, 0xDE6B, 0x8FA6, + 0xDE6C, 0x8FA7, 0xDE6D, 0x8FAA, 0xDE6E, 0x8FAC, 0xDE6F, 0x8FAD, 0xDE70, 0x8FAE, 0xDE71, 0x8FAF, 0xDE72, 0x8FB2, 0xDE73, 0x8FB3, + 0xDE74, 0x8FB4, 0xDE75, 0x8FB5, 0xDE76, 0x8FB7, 0xDE77, 0x8FB8, 0xDE78, 0x8FBA, 0xDE79, 0x8FBB, 0xDE7A, 0x8FBC, 0xDE7B, 0x8FBF, + 0xDE7C, 0x8FC0, 0xDE7D, 0x8FC3, 0xDE7E, 0x8FC6, 0xDE80, 0x8FC9, 0xDE81, 0x8FCA, 0xDE82, 0x8FCB, 0xDE83, 0x8FCC, 0xDE84, 0x8FCD, + 0xDE85, 0x8FCF, 0xDE86, 0x8FD2, 0xDE87, 0x8FD6, 0xDE88, 0x8FD7, 0xDE89, 0x8FDA, 0xDE8A, 0x8FE0, 0xDE8B, 0x8FE1, 0xDE8C, 0x8FE3, + 0xDE8D, 0x8FE7, 0xDE8E, 0x8FEC, 0xDE8F, 0x8FEF, 0xDE90, 0x8FF1, 0xDE91, 0x8FF2, 0xDE92, 0x8FF4, 0xDE93, 0x8FF5, 0xDE94, 0x8FF6, + 0xDE95, 0x8FFA, 0xDE96, 0x8FFB, 0xDE97, 0x8FFC, 0xDE98, 0x8FFE, 0xDE99, 0x8FFF, 0xDE9A, 0x9007, 0xDE9B, 0x9008, 0xDE9C, 0x900C, + 0xDE9D, 0x900E, 0xDE9E, 0x9013, 0xDE9F, 0x9015, 0xDEA0, 0x9018, 0xDEA1, 0x8556, 0xDEA2, 0x853B, 0xDEA3, 0x84FF, 0xDEA4, 0x84FC, + 0xDEA5, 0x8559, 0xDEA6, 0x8548, 0xDEA7, 0x8568, 0xDEA8, 0x8564, 0xDEA9, 0x855E, 0xDEAA, 0x857A, 0xDEAB, 0x77A2, 0xDEAC, 0x8543, + 0xDEAD, 0x8572, 0xDEAE, 0x857B, 0xDEAF, 0x85A4, 0xDEB0, 0x85A8, 0xDEB1, 0x8587, 0xDEB2, 0x858F, 0xDEB3, 0x8579, 0xDEB4, 0x85AE, + 0xDEB5, 0x859C, 0xDEB6, 0x8585, 0xDEB7, 0x85B9, 0xDEB8, 0x85B7, 0xDEB9, 0x85B0, 0xDEBA, 0x85D3, 0xDEBB, 0x85C1, 0xDEBC, 0x85DC, + 0xDEBD, 0x85FF, 0xDEBE, 0x8627, 0xDEBF, 0x8605, 0xDEC0, 0x8629, 0xDEC1, 0x8616, 0xDEC2, 0x863C, 0xDEC3, 0x5EFE, 0xDEC4, 0x5F08, + 0xDEC5, 0x593C, 0xDEC6, 0x5941, 0xDEC7, 0x8037, 0xDEC8, 0x5955, 0xDEC9, 0x595A, 0xDECA, 0x5958, 0xDECB, 0x530F, 0xDECC, 0x5C22, + 0xDECD, 0x5C25, 0xDECE, 0x5C2C, 0xDECF, 0x5C34, 0xDED0, 0x624C, 0xDED1, 0x626A, 0xDED2, 0x629F, 0xDED3, 0x62BB, 0xDED4, 0x62CA, + 0xDED5, 0x62DA, 0xDED6, 0x62D7, 0xDED7, 0x62EE, 0xDED8, 0x6322, 0xDED9, 0x62F6, 0xDEDA, 0x6339, 0xDEDB, 0x634B, 0xDEDC, 0x6343, + 0xDEDD, 0x63AD, 0xDEDE, 0x63F6, 0xDEDF, 0x6371, 0xDEE0, 0x637A, 0xDEE1, 0x638E, 0xDEE2, 0x63B4, 0xDEE3, 0x636D, 0xDEE4, 0x63AC, + 0xDEE5, 0x638A, 0xDEE6, 0x6369, 0xDEE7, 0x63AE, 0xDEE8, 0x63BC, 0xDEE9, 0x63F2, 0xDEEA, 0x63F8, 0xDEEB, 0x63E0, 0xDEEC, 0x63FF, + 0xDEED, 0x63C4, 0xDEEE, 0x63DE, 0xDEEF, 0x63CE, 0xDEF0, 0x6452, 0xDEF1, 0x63C6, 0xDEF2, 0x63BE, 0xDEF3, 0x6445, 0xDEF4, 0x6441, + 0xDEF5, 0x640B, 0xDEF6, 0x641B, 0xDEF7, 0x6420, 0xDEF8, 0x640C, 0xDEF9, 0x6426, 0xDEFA, 0x6421, 0xDEFB, 0x645E, 0xDEFC, 0x6484, + 0xDEFD, 0x646D, 0xDEFE, 0x6496, 0xDF40, 0x9019, 0xDF41, 0x901C, 0xDF42, 0x9023, 0xDF43, 0x9024, 0xDF44, 0x9025, 0xDF45, 0x9027, + 0xDF46, 0x9028, 0xDF47, 0x9029, 0xDF48, 0x902A, 0xDF49, 0x902B, 0xDF4A, 0x902C, 0xDF4B, 0x9030, 0xDF4C, 0x9031, 0xDF4D, 0x9032, + 0xDF4E, 0x9033, 0xDF4F, 0x9034, 0xDF50, 0x9037, 0xDF51, 0x9039, 0xDF52, 0x903A, 0xDF53, 0x903D, 0xDF54, 0x903F, 0xDF55, 0x9040, + 0xDF56, 0x9043, 0xDF57, 0x9045, 0xDF58, 0x9046, 0xDF59, 0x9048, 0xDF5A, 0x9049, 0xDF5B, 0x904A, 0xDF5C, 0x904B, 0xDF5D, 0x904C, + 0xDF5E, 0x904E, 0xDF5F, 0x9054, 0xDF60, 0x9055, 0xDF61, 0x9056, 0xDF62, 0x9059, 0xDF63, 0x905A, 0xDF64, 0x905C, 0xDF65, 0x905D, + 0xDF66, 0x905E, 0xDF67, 0x905F, 0xDF68, 0x9060, 0xDF69, 0x9061, 0xDF6A, 0x9064, 0xDF6B, 0x9066, 0xDF6C, 0x9067, 0xDF6D, 0x9069, + 0xDF6E, 0x906A, 0xDF6F, 0x906B, 0xDF70, 0x906C, 0xDF71, 0x906F, 0xDF72, 0x9070, 0xDF73, 0x9071, 0xDF74, 0x9072, 0xDF75, 0x9073, + 0xDF76, 0x9076, 0xDF77, 0x9077, 0xDF78, 0x9078, 0xDF79, 0x9079, 0xDF7A, 0x907A, 0xDF7B, 0x907B, 0xDF7C, 0x907C, 0xDF7D, 0x907E, + 0xDF7E, 0x9081, 0xDF80, 0x9084, 0xDF81, 0x9085, 0xDF82, 0x9086, 0xDF83, 0x9087, 0xDF84, 0x9089, 0xDF85, 0x908A, 0xDF86, 0x908C, + 0xDF87, 0x908D, 0xDF88, 0x908E, 0xDF89, 0x908F, 0xDF8A, 0x9090, 0xDF8B, 0x9092, 0xDF8C, 0x9094, 0xDF8D, 0x9096, 0xDF8E, 0x9098, + 0xDF8F, 0x909A, 0xDF90, 0x909C, 0xDF91, 0x909E, 0xDF92, 0x909F, 0xDF93, 0x90A0, 0xDF94, 0x90A4, 0xDF95, 0x90A5, 0xDF96, 0x90A7, + 0xDF97, 0x90A8, 0xDF98, 0x90A9, 0xDF99, 0x90AB, 0xDF9A, 0x90AD, 0xDF9B, 0x90B2, 0xDF9C, 0x90B7, 0xDF9D, 0x90BC, 0xDF9E, 0x90BD, + 0xDF9F, 0x90BF, 0xDFA0, 0x90C0, 0xDFA1, 0x647A, 0xDFA2, 0x64B7, 0xDFA3, 0x64B8, 0xDFA4, 0x6499, 0xDFA5, 0x64BA, 0xDFA6, 0x64C0, + 0xDFA7, 0x64D0, 0xDFA8, 0x64D7, 0xDFA9, 0x64E4, 0xDFAA, 0x64E2, 0xDFAB, 0x6509, 0xDFAC, 0x6525, 0xDFAD, 0x652E, 0xDFAE, 0x5F0B, + 0xDFAF, 0x5FD2, 0xDFB0, 0x7519, 0xDFB1, 0x5F11, 0xDFB2, 0x535F, 0xDFB3, 0x53F1, 0xDFB4, 0x53FD, 0xDFB5, 0x53E9, 0xDFB6, 0x53E8, + 0xDFB7, 0x53FB, 0xDFB8, 0x5412, 0xDFB9, 0x5416, 0xDFBA, 0x5406, 0xDFBB, 0x544B, 0xDFBC, 0x5452, 0xDFBD, 0x5453, 0xDFBE, 0x5454, + 0xDFBF, 0x5456, 0xDFC0, 0x5443, 0xDFC1, 0x5421, 0xDFC2, 0x5457, 0xDFC3, 0x5459, 0xDFC4, 0x5423, 0xDFC5, 0x5432, 0xDFC6, 0x5482, + 0xDFC7, 0x5494, 0xDFC8, 0x5477, 0xDFC9, 0x5471, 0xDFCA, 0x5464, 0xDFCB, 0x549A, 0xDFCC, 0x549B, 0xDFCD, 0x5484, 0xDFCE, 0x5476, + 0xDFCF, 0x5466, 0xDFD0, 0x549D, 0xDFD1, 0x54D0, 0xDFD2, 0x54AD, 0xDFD3, 0x54C2, 0xDFD4, 0x54B4, 0xDFD5, 0x54D2, 0xDFD6, 0x54A7, + 0xDFD7, 0x54A6, 0xDFD8, 0x54D3, 0xDFD9, 0x54D4, 0xDFDA, 0x5472, 0xDFDB, 0x54A3, 0xDFDC, 0x54D5, 0xDFDD, 0x54BB, 0xDFDE, 0x54BF, + 0xDFDF, 0x54CC, 0xDFE0, 0x54D9, 0xDFE1, 0x54DA, 0xDFE2, 0x54DC, 0xDFE3, 0x54A9, 0xDFE4, 0x54AA, 0xDFE5, 0x54A4, 0xDFE6, 0x54DD, + 0xDFE7, 0x54CF, 0xDFE8, 0x54DE, 0xDFE9, 0x551B, 0xDFEA, 0x54E7, 0xDFEB, 0x5520, 0xDFEC, 0x54FD, 0xDFED, 0x5514, 0xDFEE, 0x54F3, + 0xDFEF, 0x5522, 0xDFF0, 0x5523, 0xDFF1, 0x550F, 0xDFF2, 0x5511, 0xDFF3, 0x5527, 0xDFF4, 0x552A, 0xDFF5, 0x5567, 0xDFF6, 0x558F, + 0xDFF7, 0x55B5, 0xDFF8, 0x5549, 0xDFF9, 0x556D, 0xDFFA, 0x5541, 0xDFFB, 0x5555, 0xDFFC, 0x553F, 0xDFFD, 0x5550, 0xDFFE, 0x553C, + 0xE040, 0x90C2, 0xE041, 0x90C3, 0xE042, 0x90C6, 0xE043, 0x90C8, 0xE044, 0x90C9, 0xE045, 0x90CB, 0xE046, 0x90CC, 0xE047, 0x90CD, + 0xE048, 0x90D2, 0xE049, 0x90D4, 0xE04A, 0x90D5, 0xE04B, 0x90D6, 0xE04C, 0x90D8, 0xE04D, 0x90D9, 0xE04E, 0x90DA, 0xE04F, 0x90DE, + 0xE050, 0x90DF, 0xE051, 0x90E0, 0xE052, 0x90E3, 0xE053, 0x90E4, 0xE054, 0x90E5, 0xE055, 0x90E9, 0xE056, 0x90EA, 0xE057, 0x90EC, + 0xE058, 0x90EE, 0xE059, 0x90F0, 0xE05A, 0x90F1, 0xE05B, 0x90F2, 0xE05C, 0x90F3, 0xE05D, 0x90F5, 0xE05E, 0x90F6, 0xE05F, 0x90F7, + 0xE060, 0x90F9, 0xE061, 0x90FA, 0xE062, 0x90FB, 0xE063, 0x90FC, 0xE064, 0x90FF, 0xE065, 0x9100, 0xE066, 0x9101, 0xE067, 0x9103, + 0xE068, 0x9105, 0xE069, 0x9106, 0xE06A, 0x9107, 0xE06B, 0x9108, 0xE06C, 0x9109, 0xE06D, 0x910A, 0xE06E, 0x910B, 0xE06F, 0x910C, + 0xE070, 0x910D, 0xE071, 0x910E, 0xE072, 0x910F, 0xE073, 0x9110, 0xE074, 0x9111, 0xE075, 0x9112, 0xE076, 0x9113, 0xE077, 0x9114, + 0xE078, 0x9115, 0xE079, 0x9116, 0xE07A, 0x9117, 0xE07B, 0x9118, 0xE07C, 0x911A, 0xE07D, 0x911B, 0xE07E, 0x911C, 0xE080, 0x911D, + 0xE081, 0x911F, 0xE082, 0x9120, 0xE083, 0x9121, 0xE084, 0x9124, 0xE085, 0x9125, 0xE086, 0x9126, 0xE087, 0x9127, 0xE088, 0x9128, + 0xE089, 0x9129, 0xE08A, 0x912A, 0xE08B, 0x912B, 0xE08C, 0x912C, 0xE08D, 0x912D, 0xE08E, 0x912E, 0xE08F, 0x9130, 0xE090, 0x9132, + 0xE091, 0x9133, 0xE092, 0x9134, 0xE093, 0x9135, 0xE094, 0x9136, 0xE095, 0x9137, 0xE096, 0x9138, 0xE097, 0x913A, 0xE098, 0x913B, + 0xE099, 0x913C, 0xE09A, 0x913D, 0xE09B, 0x913E, 0xE09C, 0x913F, 0xE09D, 0x9140, 0xE09E, 0x9141, 0xE09F, 0x9142, 0xE0A0, 0x9144, + 0xE0A1, 0x5537, 0xE0A2, 0x5556, 0xE0A3, 0x5575, 0xE0A4, 0x5576, 0xE0A5, 0x5577, 0xE0A6, 0x5533, 0xE0A7, 0x5530, 0xE0A8, 0x555C, + 0xE0A9, 0x558B, 0xE0AA, 0x55D2, 0xE0AB, 0x5583, 0xE0AC, 0x55B1, 0xE0AD, 0x55B9, 0xE0AE, 0x5588, 0xE0AF, 0x5581, 0xE0B0, 0x559F, + 0xE0B1, 0x557E, 0xE0B2, 0x55D6, 0xE0B3, 0x5591, 0xE0B4, 0x557B, 0xE0B5, 0x55DF, 0xE0B6, 0x55BD, 0xE0B7, 0x55BE, 0xE0B8, 0x5594, + 0xE0B9, 0x5599, 0xE0BA, 0x55EA, 0xE0BB, 0x55F7, 0xE0BC, 0x55C9, 0xE0BD, 0x561F, 0xE0BE, 0x55D1, 0xE0BF, 0x55EB, 0xE0C0, 0x55EC, + 0xE0C1, 0x55D4, 0xE0C2, 0x55E6, 0xE0C3, 0x55DD, 0xE0C4, 0x55C4, 0xE0C5, 0x55EF, 0xE0C6, 0x55E5, 0xE0C7, 0x55F2, 0xE0C8, 0x55F3, + 0xE0C9, 0x55CC, 0xE0CA, 0x55CD, 0xE0CB, 0x55E8, 0xE0CC, 0x55F5, 0xE0CD, 0x55E4, 0xE0CE, 0x8F94, 0xE0CF, 0x561E, 0xE0D0, 0x5608, + 0xE0D1, 0x560C, 0xE0D2, 0x5601, 0xE0D3, 0x5624, 0xE0D4, 0x5623, 0xE0D5, 0x55FE, 0xE0D6, 0x5600, 0xE0D7, 0x5627, 0xE0D8, 0x562D, + 0xE0D9, 0x5658, 0xE0DA, 0x5639, 0xE0DB, 0x5657, 0xE0DC, 0x562C, 0xE0DD, 0x564D, 0xE0DE, 0x5662, 0xE0DF, 0x5659, 0xE0E0, 0x565C, + 0xE0E1, 0x564C, 0xE0E2, 0x5654, 0xE0E3, 0x5686, 0xE0E4, 0x5664, 0xE0E5, 0x5671, 0xE0E6, 0x566B, 0xE0E7, 0x567B, 0xE0E8, 0x567C, + 0xE0E9, 0x5685, 0xE0EA, 0x5693, 0xE0EB, 0x56AF, 0xE0EC, 0x56D4, 0xE0ED, 0x56D7, 0xE0EE, 0x56DD, 0xE0EF, 0x56E1, 0xE0F0, 0x56F5, + 0xE0F1, 0x56EB, 0xE0F2, 0x56F9, 0xE0F3, 0x56FF, 0xE0F4, 0x5704, 0xE0F5, 0x570A, 0xE0F6, 0x5709, 0xE0F7, 0x571C, 0xE0F8, 0x5E0F, + 0xE0F9, 0x5E19, 0xE0FA, 0x5E14, 0xE0FB, 0x5E11, 0xE0FC, 0x5E31, 0xE0FD, 0x5E3B, 0xE0FE, 0x5E3C, 0xE140, 0x9145, 0xE141, 0x9147, + 0xE142, 0x9148, 0xE143, 0x9151, 0xE144, 0x9153, 0xE145, 0x9154, 0xE146, 0x9155, 0xE147, 0x9156, 0xE148, 0x9158, 0xE149, 0x9159, + 0xE14A, 0x915B, 0xE14B, 0x915C, 0xE14C, 0x915F, 0xE14D, 0x9160, 0xE14E, 0x9166, 0xE14F, 0x9167, 0xE150, 0x9168, 0xE151, 0x916B, + 0xE152, 0x916D, 0xE153, 0x9173, 0xE154, 0x917A, 0xE155, 0x917B, 0xE156, 0x917C, 0xE157, 0x9180, 0xE158, 0x9181, 0xE159, 0x9182, + 0xE15A, 0x9183, 0xE15B, 0x9184, 0xE15C, 0x9186, 0xE15D, 0x9188, 0xE15E, 0x918A, 0xE15F, 0x918E, 0xE160, 0x918F, 0xE161, 0x9193, + 0xE162, 0x9194, 0xE163, 0x9195, 0xE164, 0x9196, 0xE165, 0x9197, 0xE166, 0x9198, 0xE167, 0x9199, 0xE168, 0x919C, 0xE169, 0x919D, + 0xE16A, 0x919E, 0xE16B, 0x919F, 0xE16C, 0x91A0, 0xE16D, 0x91A1, 0xE16E, 0x91A4, 0xE16F, 0x91A5, 0xE170, 0x91A6, 0xE171, 0x91A7, + 0xE172, 0x91A8, 0xE173, 0x91A9, 0xE174, 0x91AB, 0xE175, 0x91AC, 0xE176, 0x91B0, 0xE177, 0x91B1, 0xE178, 0x91B2, 0xE179, 0x91B3, + 0xE17A, 0x91B6, 0xE17B, 0x91B7, 0xE17C, 0x91B8, 0xE17D, 0x91B9, 0xE17E, 0x91BB, 0xE180, 0x91BC, 0xE181, 0x91BD, 0xE182, 0x91BE, + 0xE183, 0x91BF, 0xE184, 0x91C0, 0xE185, 0x91C1, 0xE186, 0x91C2, 0xE187, 0x91C3, 0xE188, 0x91C4, 0xE189, 0x91C5, 0xE18A, 0x91C6, + 0xE18B, 0x91C8, 0xE18C, 0x91CB, 0xE18D, 0x91D0, 0xE18E, 0x91D2, 0xE18F, 0x91D3, 0xE190, 0x91D4, 0xE191, 0x91D5, 0xE192, 0x91D6, + 0xE193, 0x91D7, 0xE194, 0x91D8, 0xE195, 0x91D9, 0xE196, 0x91DA, 0xE197, 0x91DB, 0xE198, 0x91DD, 0xE199, 0x91DE, 0xE19A, 0x91DF, + 0xE19B, 0x91E0, 0xE19C, 0x91E1, 0xE19D, 0x91E2, 0xE19E, 0x91E3, 0xE19F, 0x91E4, 0xE1A0, 0x91E5, 0xE1A1, 0x5E37, 0xE1A2, 0x5E44, + 0xE1A3, 0x5E54, 0xE1A4, 0x5E5B, 0xE1A5, 0x5E5E, 0xE1A6, 0x5E61, 0xE1A7, 0x5C8C, 0xE1A8, 0x5C7A, 0xE1A9, 0x5C8D, 0xE1AA, 0x5C90, + 0xE1AB, 0x5C96, 0xE1AC, 0x5C88, 0xE1AD, 0x5C98, 0xE1AE, 0x5C99, 0xE1AF, 0x5C91, 0xE1B0, 0x5C9A, 0xE1B1, 0x5C9C, 0xE1B2, 0x5CB5, + 0xE1B3, 0x5CA2, 0xE1B4, 0x5CBD, 0xE1B5, 0x5CAC, 0xE1B6, 0x5CAB, 0xE1B7, 0x5CB1, 0xE1B8, 0x5CA3, 0xE1B9, 0x5CC1, 0xE1BA, 0x5CB7, + 0xE1BB, 0x5CC4, 0xE1BC, 0x5CD2, 0xE1BD, 0x5CE4, 0xE1BE, 0x5CCB, 0xE1BF, 0x5CE5, 0xE1C0, 0x5D02, 0xE1C1, 0x5D03, 0xE1C2, 0x5D27, + 0xE1C3, 0x5D26, 0xE1C4, 0x5D2E, 0xE1C5, 0x5D24, 0xE1C6, 0x5D1E, 0xE1C7, 0x5D06, 0xE1C8, 0x5D1B, 0xE1C9, 0x5D58, 0xE1CA, 0x5D3E, + 0xE1CB, 0x5D34, 0xE1CC, 0x5D3D, 0xE1CD, 0x5D6C, 0xE1CE, 0x5D5B, 0xE1CF, 0x5D6F, 0xE1D0, 0x5D5D, 0xE1D1, 0x5D6B, 0xE1D2, 0x5D4B, + 0xE1D3, 0x5D4A, 0xE1D4, 0x5D69, 0xE1D5, 0x5D74, 0xE1D6, 0x5D82, 0xE1D7, 0x5D99, 0xE1D8, 0x5D9D, 0xE1D9, 0x8C73, 0xE1DA, 0x5DB7, + 0xE1DB, 0x5DC5, 0xE1DC, 0x5F73, 0xE1DD, 0x5F77, 0xE1DE, 0x5F82, 0xE1DF, 0x5F87, 0xE1E0, 0x5F89, 0xE1E1, 0x5F8C, 0xE1E2, 0x5F95, + 0xE1E3, 0x5F99, 0xE1E4, 0x5F9C, 0xE1E5, 0x5FA8, 0xE1E6, 0x5FAD, 0xE1E7, 0x5FB5, 0xE1E8, 0x5FBC, 0xE1E9, 0x8862, 0xE1EA, 0x5F61, + 0xE1EB, 0x72AD, 0xE1EC, 0x72B0, 0xE1ED, 0x72B4, 0xE1EE, 0x72B7, 0xE1EF, 0x72B8, 0xE1F0, 0x72C3, 0xE1F1, 0x72C1, 0xE1F2, 0x72CE, + 0xE1F3, 0x72CD, 0xE1F4, 0x72D2, 0xE1F5, 0x72E8, 0xE1F6, 0x72EF, 0xE1F7, 0x72E9, 0xE1F8, 0x72F2, 0xE1F9, 0x72F4, 0xE1FA, 0x72F7, + 0xE1FB, 0x7301, 0xE1FC, 0x72F3, 0xE1FD, 0x7303, 0xE1FE, 0x72FA, 0xE240, 0x91E6, 0xE241, 0x91E7, 0xE242, 0x91E8, 0xE243, 0x91E9, + 0xE244, 0x91EA, 0xE245, 0x91EB, 0xE246, 0x91EC, 0xE247, 0x91ED, 0xE248, 0x91EE, 0xE249, 0x91EF, 0xE24A, 0x91F0, 0xE24B, 0x91F1, + 0xE24C, 0x91F2, 0xE24D, 0x91F3, 0xE24E, 0x91F4, 0xE24F, 0x91F5, 0xE250, 0x91F6, 0xE251, 0x91F7, 0xE252, 0x91F8, 0xE253, 0x91F9, + 0xE254, 0x91FA, 0xE255, 0x91FB, 0xE256, 0x91FC, 0xE257, 0x91FD, 0xE258, 0x91FE, 0xE259, 0x91FF, 0xE25A, 0x9200, 0xE25B, 0x9201, + 0xE25C, 0x9202, 0xE25D, 0x9203, 0xE25E, 0x9204, 0xE25F, 0x9205, 0xE260, 0x9206, 0xE261, 0x9207, 0xE262, 0x9208, 0xE263, 0x9209, + 0xE264, 0x920A, 0xE265, 0x920B, 0xE266, 0x920C, 0xE267, 0x920D, 0xE268, 0x920E, 0xE269, 0x920F, 0xE26A, 0x9210, 0xE26B, 0x9211, + 0xE26C, 0x9212, 0xE26D, 0x9213, 0xE26E, 0x9214, 0xE26F, 0x9215, 0xE270, 0x9216, 0xE271, 0x9217, 0xE272, 0x9218, 0xE273, 0x9219, + 0xE274, 0x921A, 0xE275, 0x921B, 0xE276, 0x921C, 0xE277, 0x921D, 0xE278, 0x921E, 0xE279, 0x921F, 0xE27A, 0x9220, 0xE27B, 0x9221, + 0xE27C, 0x9222, 0xE27D, 0x9223, 0xE27E, 0x9224, 0xE280, 0x9225, 0xE281, 0x9226, 0xE282, 0x9227, 0xE283, 0x9228, 0xE284, 0x9229, + 0xE285, 0x922A, 0xE286, 0x922B, 0xE287, 0x922C, 0xE288, 0x922D, 0xE289, 0x922E, 0xE28A, 0x922F, 0xE28B, 0x9230, 0xE28C, 0x9231, + 0xE28D, 0x9232, 0xE28E, 0x9233, 0xE28F, 0x9234, 0xE290, 0x9235, 0xE291, 0x9236, 0xE292, 0x9237, 0xE293, 0x9238, 0xE294, 0x9239, + 0xE295, 0x923A, 0xE296, 0x923B, 0xE297, 0x923C, 0xE298, 0x923D, 0xE299, 0x923E, 0xE29A, 0x923F, 0xE29B, 0x9240, 0xE29C, 0x9241, + 0xE29D, 0x9242, 0xE29E, 0x9243, 0xE29F, 0x9244, 0xE2A0, 0x9245, 0xE2A1, 0x72FB, 0xE2A2, 0x7317, 0xE2A3, 0x7313, 0xE2A4, 0x7321, + 0xE2A5, 0x730A, 0xE2A6, 0x731E, 0xE2A7, 0x731D, 0xE2A8, 0x7315, 0xE2A9, 0x7322, 0xE2AA, 0x7339, 0xE2AB, 0x7325, 0xE2AC, 0x732C, + 0xE2AD, 0x7338, 0xE2AE, 0x7331, 0xE2AF, 0x7350, 0xE2B0, 0x734D, 0xE2B1, 0x7357, 0xE2B2, 0x7360, 0xE2B3, 0x736C, 0xE2B4, 0x736F, + 0xE2B5, 0x737E, 0xE2B6, 0x821B, 0xE2B7, 0x5925, 0xE2B8, 0x98E7, 0xE2B9, 0x5924, 0xE2BA, 0x5902, 0xE2BB, 0x9963, 0xE2BC, 0x9967, + 0xE2BD, 0x9968, 0xE2BE, 0x9969, 0xE2BF, 0x996A, 0xE2C0, 0x996B, 0xE2C1, 0x996C, 0xE2C2, 0x9974, 0xE2C3, 0x9977, 0xE2C4, 0x997D, + 0xE2C5, 0x9980, 0xE2C6, 0x9984, 0xE2C7, 0x9987, 0xE2C8, 0x998A, 0xE2C9, 0x998D, 0xE2CA, 0x9990, 0xE2CB, 0x9991, 0xE2CC, 0x9993, + 0xE2CD, 0x9994, 0xE2CE, 0x9995, 0xE2CF, 0x5E80, 0xE2D0, 0x5E91, 0xE2D1, 0x5E8B, 0xE2D2, 0x5E96, 0xE2D3, 0x5EA5, 0xE2D4, 0x5EA0, + 0xE2D5, 0x5EB9, 0xE2D6, 0x5EB5, 0xE2D7, 0x5EBE, 0xE2D8, 0x5EB3, 0xE2D9, 0x8D53, 0xE2DA, 0x5ED2, 0xE2DB, 0x5ED1, 0xE2DC, 0x5EDB, + 0xE2DD, 0x5EE8, 0xE2DE, 0x5EEA, 0xE2DF, 0x81BA, 0xE2E0, 0x5FC4, 0xE2E1, 0x5FC9, 0xE2E2, 0x5FD6, 0xE2E3, 0x5FCF, 0xE2E4, 0x6003, + 0xE2E5, 0x5FEE, 0xE2E6, 0x6004, 0xE2E7, 0x5FE1, 0xE2E8, 0x5FE4, 0xE2E9, 0x5FFE, 0xE2EA, 0x6005, 0xE2EB, 0x6006, 0xE2EC, 0x5FEA, + 0xE2ED, 0x5FED, 0xE2EE, 0x5FF8, 0xE2EF, 0x6019, 0xE2F0, 0x6035, 0xE2F1, 0x6026, 0xE2F2, 0x601B, 0xE2F3, 0x600F, 0xE2F4, 0x600D, + 0xE2F5, 0x6029, 0xE2F6, 0x602B, 0xE2F7, 0x600A, 0xE2F8, 0x603F, 0xE2F9, 0x6021, 0xE2FA, 0x6078, 0xE2FB, 0x6079, 0xE2FC, 0x607B, + 0xE2FD, 0x607A, 0xE2FE, 0x6042, 0xE340, 0x9246, 0xE341, 0x9247, 0xE342, 0x9248, 0xE343, 0x9249, 0xE344, 0x924A, 0xE345, 0x924B, + 0xE346, 0x924C, 0xE347, 0x924D, 0xE348, 0x924E, 0xE349, 0x924F, 0xE34A, 0x9250, 0xE34B, 0x9251, 0xE34C, 0x9252, 0xE34D, 0x9253, + 0xE34E, 0x9254, 0xE34F, 0x9255, 0xE350, 0x9256, 0xE351, 0x9257, 0xE352, 0x9258, 0xE353, 0x9259, 0xE354, 0x925A, 0xE355, 0x925B, + 0xE356, 0x925C, 0xE357, 0x925D, 0xE358, 0x925E, 0xE359, 0x925F, 0xE35A, 0x9260, 0xE35B, 0x9261, 0xE35C, 0x9262, 0xE35D, 0x9263, + 0xE35E, 0x9264, 0xE35F, 0x9265, 0xE360, 0x9266, 0xE361, 0x9267, 0xE362, 0x9268, 0xE363, 0x9269, 0xE364, 0x926A, 0xE365, 0x926B, + 0xE366, 0x926C, 0xE367, 0x926D, 0xE368, 0x926E, 0xE369, 0x926F, 0xE36A, 0x9270, 0xE36B, 0x9271, 0xE36C, 0x9272, 0xE36D, 0x9273, + 0xE36E, 0x9275, 0xE36F, 0x9276, 0xE370, 0x9277, 0xE371, 0x9278, 0xE372, 0x9279, 0xE373, 0x927A, 0xE374, 0x927B, 0xE375, 0x927C, + 0xE376, 0x927D, 0xE377, 0x927E, 0xE378, 0x927F, 0xE379, 0x9280, 0xE37A, 0x9281, 0xE37B, 0x9282, 0xE37C, 0x9283, 0xE37D, 0x9284, + 0xE37E, 0x9285, 0xE380, 0x9286, 0xE381, 0x9287, 0xE382, 0x9288, 0xE383, 0x9289, 0xE384, 0x928A, 0xE385, 0x928B, 0xE386, 0x928C, + 0xE387, 0x928D, 0xE388, 0x928F, 0xE389, 0x9290, 0xE38A, 0x9291, 0xE38B, 0x9292, 0xE38C, 0x9293, 0xE38D, 0x9294, 0xE38E, 0x9295, + 0xE38F, 0x9296, 0xE390, 0x9297, 0xE391, 0x9298, 0xE392, 0x9299, 0xE393, 0x929A, 0xE394, 0x929B, 0xE395, 0x929C, 0xE396, 0x929D, + 0xE397, 0x929E, 0xE398, 0x929F, 0xE399, 0x92A0, 0xE39A, 0x92A1, 0xE39B, 0x92A2, 0xE39C, 0x92A3, 0xE39D, 0x92A4, 0xE39E, 0x92A5, + 0xE39F, 0x92A6, 0xE3A0, 0x92A7, 0xE3A1, 0x606A, 0xE3A2, 0x607D, 0xE3A3, 0x6096, 0xE3A4, 0x609A, 0xE3A5, 0x60AD, 0xE3A6, 0x609D, + 0xE3A7, 0x6083, 0xE3A8, 0x6092, 0xE3A9, 0x608C, 0xE3AA, 0x609B, 0xE3AB, 0x60EC, 0xE3AC, 0x60BB, 0xE3AD, 0x60B1, 0xE3AE, 0x60DD, + 0xE3AF, 0x60D8, 0xE3B0, 0x60C6, 0xE3B1, 0x60DA, 0xE3B2, 0x60B4, 0xE3B3, 0x6120, 0xE3B4, 0x6126, 0xE3B5, 0x6115, 0xE3B6, 0x6123, + 0xE3B7, 0x60F4, 0xE3B8, 0x6100, 0xE3B9, 0x610E, 0xE3BA, 0x612B, 0xE3BB, 0x614A, 0xE3BC, 0x6175, 0xE3BD, 0x61AC, 0xE3BE, 0x6194, + 0xE3BF, 0x61A7, 0xE3C0, 0x61B7, 0xE3C1, 0x61D4, 0xE3C2, 0x61F5, 0xE3C3, 0x5FDD, 0xE3C4, 0x96B3, 0xE3C5, 0x95E9, 0xE3C6, 0x95EB, + 0xE3C7, 0x95F1, 0xE3C8, 0x95F3, 0xE3C9, 0x95F5, 0xE3CA, 0x95F6, 0xE3CB, 0x95FC, 0xE3CC, 0x95FE, 0xE3CD, 0x9603, 0xE3CE, 0x9604, + 0xE3CF, 0x9606, 0xE3D0, 0x9608, 0xE3D1, 0x960A, 0xE3D2, 0x960B, 0xE3D3, 0x960C, 0xE3D4, 0x960D, 0xE3D5, 0x960F, 0xE3D6, 0x9612, + 0xE3D7, 0x9615, 0xE3D8, 0x9616, 0xE3D9, 0x9617, 0xE3DA, 0x9619, 0xE3DB, 0x961A, 0xE3DC, 0x4E2C, 0xE3DD, 0x723F, 0xE3DE, 0x6215, + 0xE3DF, 0x6C35, 0xE3E0, 0x6C54, 0xE3E1, 0x6C5C, 0xE3E2, 0x6C4A, 0xE3E3, 0x6CA3, 0xE3E4, 0x6C85, 0xE3E5, 0x6C90, 0xE3E6, 0x6C94, + 0xE3E7, 0x6C8C, 0xE3E8, 0x6C68, 0xE3E9, 0x6C69, 0xE3EA, 0x6C74, 0xE3EB, 0x6C76, 0xE3EC, 0x6C86, 0xE3ED, 0x6CA9, 0xE3EE, 0x6CD0, + 0xE3EF, 0x6CD4, 0xE3F0, 0x6CAD, 0xE3F1, 0x6CF7, 0xE3F2, 0x6CF8, 0xE3F3, 0x6CF1, 0xE3F4, 0x6CD7, 0xE3F5, 0x6CB2, 0xE3F6, 0x6CE0, + 0xE3F7, 0x6CD6, 0xE3F8, 0x6CFA, 0xE3F9, 0x6CEB, 0xE3FA, 0x6CEE, 0xE3FB, 0x6CB1, 0xE3FC, 0x6CD3, 0xE3FD, 0x6CEF, 0xE3FE, 0x6CFE, + 0xE440, 0x92A8, 0xE441, 0x92A9, 0xE442, 0x92AA, 0xE443, 0x92AB, 0xE444, 0x92AC, 0xE445, 0x92AD, 0xE446, 0x92AF, 0xE447, 0x92B0, + 0xE448, 0x92B1, 0xE449, 0x92B2, 0xE44A, 0x92B3, 0xE44B, 0x92B4, 0xE44C, 0x92B5, 0xE44D, 0x92B6, 0xE44E, 0x92B7, 0xE44F, 0x92B8, + 0xE450, 0x92B9, 0xE451, 0x92BA, 0xE452, 0x92BB, 0xE453, 0x92BC, 0xE454, 0x92BD, 0xE455, 0x92BE, 0xE456, 0x92BF, 0xE457, 0x92C0, + 0xE458, 0x92C1, 0xE459, 0x92C2, 0xE45A, 0x92C3, 0xE45B, 0x92C4, 0xE45C, 0x92C5, 0xE45D, 0x92C6, 0xE45E, 0x92C7, 0xE45F, 0x92C9, + 0xE460, 0x92CA, 0xE461, 0x92CB, 0xE462, 0x92CC, 0xE463, 0x92CD, 0xE464, 0x92CE, 0xE465, 0x92CF, 0xE466, 0x92D0, 0xE467, 0x92D1, + 0xE468, 0x92D2, 0xE469, 0x92D3, 0xE46A, 0x92D4, 0xE46B, 0x92D5, 0xE46C, 0x92D6, 0xE46D, 0x92D7, 0xE46E, 0x92D8, 0xE46F, 0x92D9, + 0xE470, 0x92DA, 0xE471, 0x92DB, 0xE472, 0x92DC, 0xE473, 0x92DD, 0xE474, 0x92DE, 0xE475, 0x92DF, 0xE476, 0x92E0, 0xE477, 0x92E1, + 0xE478, 0x92E2, 0xE479, 0x92E3, 0xE47A, 0x92E4, 0xE47B, 0x92E5, 0xE47C, 0x92E6, 0xE47D, 0x92E7, 0xE47E, 0x92E8, 0xE480, 0x92E9, + 0xE481, 0x92EA, 0xE482, 0x92EB, 0xE483, 0x92EC, 0xE484, 0x92ED, 0xE485, 0x92EE, 0xE486, 0x92EF, 0xE487, 0x92F0, 0xE488, 0x92F1, + 0xE489, 0x92F2, 0xE48A, 0x92F3, 0xE48B, 0x92F4, 0xE48C, 0x92F5, 0xE48D, 0x92F6, 0xE48E, 0x92F7, 0xE48F, 0x92F8, 0xE490, 0x92F9, + 0xE491, 0x92FA, 0xE492, 0x92FB, 0xE493, 0x92FC, 0xE494, 0x92FD, 0xE495, 0x92FE, 0xE496, 0x92FF, 0xE497, 0x9300, 0xE498, 0x9301, + 0xE499, 0x9302, 0xE49A, 0x9303, 0xE49B, 0x9304, 0xE49C, 0x9305, 0xE49D, 0x9306, 0xE49E, 0x9307, 0xE49F, 0x9308, 0xE4A0, 0x9309, + 0xE4A1, 0x6D39, 0xE4A2, 0x6D27, 0xE4A3, 0x6D0C, 0xE4A4, 0x6D43, 0xE4A5, 0x6D48, 0xE4A6, 0x6D07, 0xE4A7, 0x6D04, 0xE4A8, 0x6D19, + 0xE4A9, 0x6D0E, 0xE4AA, 0x6D2B, 0xE4AB, 0x6D4D, 0xE4AC, 0x6D2E, 0xE4AD, 0x6D35, 0xE4AE, 0x6D1A, 0xE4AF, 0x6D4F, 0xE4B0, 0x6D52, + 0xE4B1, 0x6D54, 0xE4B2, 0x6D33, 0xE4B3, 0x6D91, 0xE4B4, 0x6D6F, 0xE4B5, 0x6D9E, 0xE4B6, 0x6DA0, 0xE4B7, 0x6D5E, 0xE4B8, 0x6D93, + 0xE4B9, 0x6D94, 0xE4BA, 0x6D5C, 0xE4BB, 0x6D60, 0xE4BC, 0x6D7C, 0xE4BD, 0x6D63, 0xE4BE, 0x6E1A, 0xE4BF, 0x6DC7, 0xE4C0, 0x6DC5, + 0xE4C1, 0x6DDE, 0xE4C2, 0x6E0E, 0xE4C3, 0x6DBF, 0xE4C4, 0x6DE0, 0xE4C5, 0x6E11, 0xE4C6, 0x6DE6, 0xE4C7, 0x6DDD, 0xE4C8, 0x6DD9, + 0xE4C9, 0x6E16, 0xE4CA, 0x6DAB, 0xE4CB, 0x6E0C, 0xE4CC, 0x6DAE, 0xE4CD, 0x6E2B, 0xE4CE, 0x6E6E, 0xE4CF, 0x6E4E, 0xE4D0, 0x6E6B, + 0xE4D1, 0x6EB2, 0xE4D2, 0x6E5F, 0xE4D3, 0x6E86, 0xE4D4, 0x6E53, 0xE4D5, 0x6E54, 0xE4D6, 0x6E32, 0xE4D7, 0x6E25, 0xE4D8, 0x6E44, + 0xE4D9, 0x6EDF, 0xE4DA, 0x6EB1, 0xE4DB, 0x6E98, 0xE4DC, 0x6EE0, 0xE4DD, 0x6F2D, 0xE4DE, 0x6EE2, 0xE4DF, 0x6EA5, 0xE4E0, 0x6EA7, + 0xE4E1, 0x6EBD, 0xE4E2, 0x6EBB, 0xE4E3, 0x6EB7, 0xE4E4, 0x6ED7, 0xE4E5, 0x6EB4, 0xE4E6, 0x6ECF, 0xE4E7, 0x6E8F, 0xE4E8, 0x6EC2, + 0xE4E9, 0x6E9F, 0xE4EA, 0x6F62, 0xE4EB, 0x6F46, 0xE4EC, 0x6F47, 0xE4ED, 0x6F24, 0xE4EE, 0x6F15, 0xE4EF, 0x6EF9, 0xE4F0, 0x6F2F, + 0xE4F1, 0x6F36, 0xE4F2, 0x6F4B, 0xE4F3, 0x6F74, 0xE4F4, 0x6F2A, 0xE4F5, 0x6F09, 0xE4F6, 0x6F29, 0xE4F7, 0x6F89, 0xE4F8, 0x6F8D, + 0xE4F9, 0x6F8C, 0xE4FA, 0x6F78, 0xE4FB, 0x6F72, 0xE4FC, 0x6F7C, 0xE4FD, 0x6F7A, 0xE4FE, 0x6FD1, 0xE540, 0x930A, 0xE541, 0x930B, + 0xE542, 0x930C, 0xE543, 0x930D, 0xE544, 0x930E, 0xE545, 0x930F, 0xE546, 0x9310, 0xE547, 0x9311, 0xE548, 0x9312, 0xE549, 0x9313, + 0xE54A, 0x9314, 0xE54B, 0x9315, 0xE54C, 0x9316, 0xE54D, 0x9317, 0xE54E, 0x9318, 0xE54F, 0x9319, 0xE550, 0x931A, 0xE551, 0x931B, + 0xE552, 0x931C, 0xE553, 0x931D, 0xE554, 0x931E, 0xE555, 0x931F, 0xE556, 0x9320, 0xE557, 0x9321, 0xE558, 0x9322, 0xE559, 0x9323, + 0xE55A, 0x9324, 0xE55B, 0x9325, 0xE55C, 0x9326, 0xE55D, 0x9327, 0xE55E, 0x9328, 0xE55F, 0x9329, 0xE560, 0x932A, 0xE561, 0x932B, + 0xE562, 0x932C, 0xE563, 0x932D, 0xE564, 0x932E, 0xE565, 0x932F, 0xE566, 0x9330, 0xE567, 0x9331, 0xE568, 0x9332, 0xE569, 0x9333, + 0xE56A, 0x9334, 0xE56B, 0x9335, 0xE56C, 0x9336, 0xE56D, 0x9337, 0xE56E, 0x9338, 0xE56F, 0x9339, 0xE570, 0x933A, 0xE571, 0x933B, + 0xE572, 0x933C, 0xE573, 0x933D, 0xE574, 0x933F, 0xE575, 0x9340, 0xE576, 0x9341, 0xE577, 0x9342, 0xE578, 0x9343, 0xE579, 0x9344, + 0xE57A, 0x9345, 0xE57B, 0x9346, 0xE57C, 0x9347, 0xE57D, 0x9348, 0xE57E, 0x9349, 0xE580, 0x934A, 0xE581, 0x934B, 0xE582, 0x934C, + 0xE583, 0x934D, 0xE584, 0x934E, 0xE585, 0x934F, 0xE586, 0x9350, 0xE587, 0x9351, 0xE588, 0x9352, 0xE589, 0x9353, 0xE58A, 0x9354, + 0xE58B, 0x9355, 0xE58C, 0x9356, 0xE58D, 0x9357, 0xE58E, 0x9358, 0xE58F, 0x9359, 0xE590, 0x935A, 0xE591, 0x935B, 0xE592, 0x935C, + 0xE593, 0x935D, 0xE594, 0x935E, 0xE595, 0x935F, 0xE596, 0x9360, 0xE597, 0x9361, 0xE598, 0x9362, 0xE599, 0x9363, 0xE59A, 0x9364, + 0xE59B, 0x9365, 0xE59C, 0x9366, 0xE59D, 0x9367, 0xE59E, 0x9368, 0xE59F, 0x9369, 0xE5A0, 0x936B, 0xE5A1, 0x6FC9, 0xE5A2, 0x6FA7, + 0xE5A3, 0x6FB9, 0xE5A4, 0x6FB6, 0xE5A5, 0x6FC2, 0xE5A6, 0x6FE1, 0xE5A7, 0x6FEE, 0xE5A8, 0x6FDE, 0xE5A9, 0x6FE0, 0xE5AA, 0x6FEF, + 0xE5AB, 0x701A, 0xE5AC, 0x7023, 0xE5AD, 0x701B, 0xE5AE, 0x7039, 0xE5AF, 0x7035, 0xE5B0, 0x704F, 0xE5B1, 0x705E, 0xE5B2, 0x5B80, + 0xE5B3, 0x5B84, 0xE5B4, 0x5B95, 0xE5B5, 0x5B93, 0xE5B6, 0x5BA5, 0xE5B7, 0x5BB8, 0xE5B8, 0x752F, 0xE5B9, 0x9A9E, 0xE5BA, 0x6434, + 0xE5BB, 0x5BE4, 0xE5BC, 0x5BEE, 0xE5BD, 0x8930, 0xE5BE, 0x5BF0, 0xE5BF, 0x8E47, 0xE5C0, 0x8B07, 0xE5C1, 0x8FB6, 0xE5C2, 0x8FD3, + 0xE5C3, 0x8FD5, 0xE5C4, 0x8FE5, 0xE5C5, 0x8FEE, 0xE5C6, 0x8FE4, 0xE5C7, 0x8FE9, 0xE5C8, 0x8FE6, 0xE5C9, 0x8FF3, 0xE5CA, 0x8FE8, + 0xE5CB, 0x9005, 0xE5CC, 0x9004, 0xE5CD, 0x900B, 0xE5CE, 0x9026, 0xE5CF, 0x9011, 0xE5D0, 0x900D, 0xE5D1, 0x9016, 0xE5D2, 0x9021, + 0xE5D3, 0x9035, 0xE5D4, 0x9036, 0xE5D5, 0x902D, 0xE5D6, 0x902F, 0xE5D7, 0x9044, 0xE5D8, 0x9051, 0xE5D9, 0x9052, 0xE5DA, 0x9050, + 0xE5DB, 0x9068, 0xE5DC, 0x9058, 0xE5DD, 0x9062, 0xE5DE, 0x905B, 0xE5DF, 0x66B9, 0xE5E0, 0x9074, 0xE5E1, 0x907D, 0xE5E2, 0x9082, + 0xE5E3, 0x9088, 0xE5E4, 0x9083, 0xE5E5, 0x908B, 0xE5E6, 0x5F50, 0xE5E7, 0x5F57, 0xE5E8, 0x5F56, 0xE5E9, 0x5F58, 0xE5EA, 0x5C3B, + 0xE5EB, 0x54AB, 0xE5EC, 0x5C50, 0xE5ED, 0x5C59, 0xE5EE, 0x5B71, 0xE5EF, 0x5C63, 0xE5F0, 0x5C66, 0xE5F1, 0x7FBC, 0xE5F2, 0x5F2A, + 0xE5F3, 0x5F29, 0xE5F4, 0x5F2D, 0xE5F5, 0x8274, 0xE5F6, 0x5F3C, 0xE5F7, 0x9B3B, 0xE5F8, 0x5C6E, 0xE5F9, 0x5981, 0xE5FA, 0x5983, + 0xE5FB, 0x598D, 0xE5FC, 0x59A9, 0xE5FD, 0x59AA, 0xE5FE, 0x59A3, 0xE640, 0x936C, 0xE641, 0x936D, 0xE642, 0x936E, 0xE643, 0x936F, + 0xE644, 0x9370, 0xE645, 0x9371, 0xE646, 0x9372, 0xE647, 0x9373, 0xE648, 0x9374, 0xE649, 0x9375, 0xE64A, 0x9376, 0xE64B, 0x9377, + 0xE64C, 0x9378, 0xE64D, 0x9379, 0xE64E, 0x937A, 0xE64F, 0x937B, 0xE650, 0x937C, 0xE651, 0x937D, 0xE652, 0x937E, 0xE653, 0x937F, + 0xE654, 0x9380, 0xE655, 0x9381, 0xE656, 0x9382, 0xE657, 0x9383, 0xE658, 0x9384, 0xE659, 0x9385, 0xE65A, 0x9386, 0xE65B, 0x9387, + 0xE65C, 0x9388, 0xE65D, 0x9389, 0xE65E, 0x938A, 0xE65F, 0x938B, 0xE660, 0x938C, 0xE661, 0x938D, 0xE662, 0x938E, 0xE663, 0x9390, + 0xE664, 0x9391, 0xE665, 0x9392, 0xE666, 0x9393, 0xE667, 0x9394, 0xE668, 0x9395, 0xE669, 0x9396, 0xE66A, 0x9397, 0xE66B, 0x9398, + 0xE66C, 0x9399, 0xE66D, 0x939A, 0xE66E, 0x939B, 0xE66F, 0x939C, 0xE670, 0x939D, 0xE671, 0x939E, 0xE672, 0x939F, 0xE673, 0x93A0, + 0xE674, 0x93A1, 0xE675, 0x93A2, 0xE676, 0x93A3, 0xE677, 0x93A4, 0xE678, 0x93A5, 0xE679, 0x93A6, 0xE67A, 0x93A7, 0xE67B, 0x93A8, + 0xE67C, 0x93A9, 0xE67D, 0x93AA, 0xE67E, 0x93AB, 0xE680, 0x93AC, 0xE681, 0x93AD, 0xE682, 0x93AE, 0xE683, 0x93AF, 0xE684, 0x93B0, + 0xE685, 0x93B1, 0xE686, 0x93B2, 0xE687, 0x93B3, 0xE688, 0x93B4, 0xE689, 0x93B5, 0xE68A, 0x93B6, 0xE68B, 0x93B7, 0xE68C, 0x93B8, + 0xE68D, 0x93B9, 0xE68E, 0x93BA, 0xE68F, 0x93BB, 0xE690, 0x93BC, 0xE691, 0x93BD, 0xE692, 0x93BE, 0xE693, 0x93BF, 0xE694, 0x93C0, + 0xE695, 0x93C1, 0xE696, 0x93C2, 0xE697, 0x93C3, 0xE698, 0x93C4, 0xE699, 0x93C5, 0xE69A, 0x93C6, 0xE69B, 0x93C7, 0xE69C, 0x93C8, + 0xE69D, 0x93C9, 0xE69E, 0x93CB, 0xE69F, 0x93CC, 0xE6A0, 0x93CD, 0xE6A1, 0x5997, 0xE6A2, 0x59CA, 0xE6A3, 0x59AB, 0xE6A4, 0x599E, + 0xE6A5, 0x59A4, 0xE6A6, 0x59D2, 0xE6A7, 0x59B2, 0xE6A8, 0x59AF, 0xE6A9, 0x59D7, 0xE6AA, 0x59BE, 0xE6AB, 0x5A05, 0xE6AC, 0x5A06, + 0xE6AD, 0x59DD, 0xE6AE, 0x5A08, 0xE6AF, 0x59E3, 0xE6B0, 0x59D8, 0xE6B1, 0x59F9, 0xE6B2, 0x5A0C, 0xE6B3, 0x5A09, 0xE6B4, 0x5A32, + 0xE6B5, 0x5A34, 0xE6B6, 0x5A11, 0xE6B7, 0x5A23, 0xE6B8, 0x5A13, 0xE6B9, 0x5A40, 0xE6BA, 0x5A67, 0xE6BB, 0x5A4A, 0xE6BC, 0x5A55, + 0xE6BD, 0x5A3C, 0xE6BE, 0x5A62, 0xE6BF, 0x5A75, 0xE6C0, 0x80EC, 0xE6C1, 0x5AAA, 0xE6C2, 0x5A9B, 0xE6C3, 0x5A77, 0xE6C4, 0x5A7A, + 0xE6C5, 0x5ABE, 0xE6C6, 0x5AEB, 0xE6C7, 0x5AB2, 0xE6C8, 0x5AD2, 0xE6C9, 0x5AD4, 0xE6CA, 0x5AB8, 0xE6CB, 0x5AE0, 0xE6CC, 0x5AE3, + 0xE6CD, 0x5AF1, 0xE6CE, 0x5AD6, 0xE6CF, 0x5AE6, 0xE6D0, 0x5AD8, 0xE6D1, 0x5ADC, 0xE6D2, 0x5B09, 0xE6D3, 0x5B17, 0xE6D4, 0x5B16, + 0xE6D5, 0x5B32, 0xE6D6, 0x5B37, 0xE6D7, 0x5B40, 0xE6D8, 0x5C15, 0xE6D9, 0x5C1C, 0xE6DA, 0x5B5A, 0xE6DB, 0x5B65, 0xE6DC, 0x5B73, + 0xE6DD, 0x5B51, 0xE6DE, 0x5B53, 0xE6DF, 0x5B62, 0xE6E0, 0x9A75, 0xE6E1, 0x9A77, 0xE6E2, 0x9A78, 0xE6E3, 0x9A7A, 0xE6E4, 0x9A7F, + 0xE6E5, 0x9A7D, 0xE6E6, 0x9A80, 0xE6E7, 0x9A81, 0xE6E8, 0x9A85, 0xE6E9, 0x9A88, 0xE6EA, 0x9A8A, 0xE6EB, 0x9A90, 0xE6EC, 0x9A92, + 0xE6ED, 0x9A93, 0xE6EE, 0x9A96, 0xE6EF, 0x9A98, 0xE6F0, 0x9A9B, 0xE6F1, 0x9A9C, 0xE6F2, 0x9A9D, 0xE6F3, 0x9A9F, 0xE6F4, 0x9AA0, + 0xE6F5, 0x9AA2, 0xE6F6, 0x9AA3, 0xE6F7, 0x9AA5, 0xE6F8, 0x9AA7, 0xE6F9, 0x7E9F, 0xE6FA, 0x7EA1, 0xE6FB, 0x7EA3, 0xE6FC, 0x7EA5, + 0xE6FD, 0x7EA8, 0xE6FE, 0x7EA9, 0xE740, 0x93CE, 0xE741, 0x93CF, 0xE742, 0x93D0, 0xE743, 0x93D1, 0xE744, 0x93D2, 0xE745, 0x93D3, + 0xE746, 0x93D4, 0xE747, 0x93D5, 0xE748, 0x93D7, 0xE749, 0x93D8, 0xE74A, 0x93D9, 0xE74B, 0x93DA, 0xE74C, 0x93DB, 0xE74D, 0x93DC, + 0xE74E, 0x93DD, 0xE74F, 0x93DE, 0xE750, 0x93DF, 0xE751, 0x93E0, 0xE752, 0x93E1, 0xE753, 0x93E2, 0xE754, 0x93E3, 0xE755, 0x93E4, + 0xE756, 0x93E5, 0xE757, 0x93E6, 0xE758, 0x93E7, 0xE759, 0x93E8, 0xE75A, 0x93E9, 0xE75B, 0x93EA, 0xE75C, 0x93EB, 0xE75D, 0x93EC, + 0xE75E, 0x93ED, 0xE75F, 0x93EE, 0xE760, 0x93EF, 0xE761, 0x93F0, 0xE762, 0x93F1, 0xE763, 0x93F2, 0xE764, 0x93F3, 0xE765, 0x93F4, + 0xE766, 0x93F5, 0xE767, 0x93F6, 0xE768, 0x93F7, 0xE769, 0x93F8, 0xE76A, 0x93F9, 0xE76B, 0x93FA, 0xE76C, 0x93FB, 0xE76D, 0x93FC, + 0xE76E, 0x93FD, 0xE76F, 0x93FE, 0xE770, 0x93FF, 0xE771, 0x9400, 0xE772, 0x9401, 0xE773, 0x9402, 0xE774, 0x9403, 0xE775, 0x9404, + 0xE776, 0x9405, 0xE777, 0x9406, 0xE778, 0x9407, 0xE779, 0x9408, 0xE77A, 0x9409, 0xE77B, 0x940A, 0xE77C, 0x940B, 0xE77D, 0x940C, + 0xE77E, 0x940D, 0xE780, 0x940E, 0xE781, 0x940F, 0xE782, 0x9410, 0xE783, 0x9411, 0xE784, 0x9412, 0xE785, 0x9413, 0xE786, 0x9414, + 0xE787, 0x9415, 0xE788, 0x9416, 0xE789, 0x9417, 0xE78A, 0x9418, 0xE78B, 0x9419, 0xE78C, 0x941A, 0xE78D, 0x941B, 0xE78E, 0x941C, + 0xE78F, 0x941D, 0xE790, 0x941E, 0xE791, 0x941F, 0xE792, 0x9420, 0xE793, 0x9421, 0xE794, 0x9422, 0xE795, 0x9423, 0xE796, 0x9424, + 0xE797, 0x9425, 0xE798, 0x9426, 0xE799, 0x9427, 0xE79A, 0x9428, 0xE79B, 0x9429, 0xE79C, 0x942A, 0xE79D, 0x942B, 0xE79E, 0x942C, + 0xE79F, 0x942D, 0xE7A0, 0x942E, 0xE7A1, 0x7EAD, 0xE7A2, 0x7EB0, 0xE7A3, 0x7EBE, 0xE7A4, 0x7EC0, 0xE7A5, 0x7EC1, 0xE7A6, 0x7EC2, + 0xE7A7, 0x7EC9, 0xE7A8, 0x7ECB, 0xE7A9, 0x7ECC, 0xE7AA, 0x7ED0, 0xE7AB, 0x7ED4, 0xE7AC, 0x7ED7, 0xE7AD, 0x7EDB, 0xE7AE, 0x7EE0, + 0xE7AF, 0x7EE1, 0xE7B0, 0x7EE8, 0xE7B1, 0x7EEB, 0xE7B2, 0x7EEE, 0xE7B3, 0x7EEF, 0xE7B4, 0x7EF1, 0xE7B5, 0x7EF2, 0xE7B6, 0x7F0D, + 0xE7B7, 0x7EF6, 0xE7B8, 0x7EFA, 0xE7B9, 0x7EFB, 0xE7BA, 0x7EFE, 0xE7BB, 0x7F01, 0xE7BC, 0x7F02, 0xE7BD, 0x7F03, 0xE7BE, 0x7F07, + 0xE7BF, 0x7F08, 0xE7C0, 0x7F0B, 0xE7C1, 0x7F0C, 0xE7C2, 0x7F0F, 0xE7C3, 0x7F11, 0xE7C4, 0x7F12, 0xE7C5, 0x7F17, 0xE7C6, 0x7F19, + 0xE7C7, 0x7F1C, 0xE7C8, 0x7F1B, 0xE7C9, 0x7F1F, 0xE7CA, 0x7F21, 0xE7CB, 0x7F22, 0xE7CC, 0x7F23, 0xE7CD, 0x7F24, 0xE7CE, 0x7F25, + 0xE7CF, 0x7F26, 0xE7D0, 0x7F27, 0xE7D1, 0x7F2A, 0xE7D2, 0x7F2B, 0xE7D3, 0x7F2C, 0xE7D4, 0x7F2D, 0xE7D5, 0x7F2F, 0xE7D6, 0x7F30, + 0xE7D7, 0x7F31, 0xE7D8, 0x7F32, 0xE7D9, 0x7F33, 0xE7DA, 0x7F35, 0xE7DB, 0x5E7A, 0xE7DC, 0x757F, 0xE7DD, 0x5DDB, 0xE7DE, 0x753E, + 0xE7DF, 0x9095, 0xE7E0, 0x738E, 0xE7E1, 0x7391, 0xE7E2, 0x73AE, 0xE7E3, 0x73A2, 0xE7E4, 0x739F, 0xE7E5, 0x73CF, 0xE7E6, 0x73C2, + 0xE7E7, 0x73D1, 0xE7E8, 0x73B7, 0xE7E9, 0x73B3, 0xE7EA, 0x73C0, 0xE7EB, 0x73C9, 0xE7EC, 0x73C8, 0xE7ED, 0x73E5, 0xE7EE, 0x73D9, + 0xE7EF, 0x987C, 0xE7F0, 0x740A, 0xE7F1, 0x73E9, 0xE7F2, 0x73E7, 0xE7F3, 0x73DE, 0xE7F4, 0x73BA, 0xE7F5, 0x73F2, 0xE7F6, 0x740F, + 0xE7F7, 0x742A, 0xE7F8, 0x745B, 0xE7F9, 0x7426, 0xE7FA, 0x7425, 0xE7FB, 0x7428, 0xE7FC, 0x7430, 0xE7FD, 0x742E, 0xE7FE, 0x742C, + 0xE840, 0x942F, 0xE841, 0x9430, 0xE842, 0x9431, 0xE843, 0x9432, 0xE844, 0x9433, 0xE845, 0x9434, 0xE846, 0x9435, 0xE847, 0x9436, + 0xE848, 0x9437, 0xE849, 0x9438, 0xE84A, 0x9439, 0xE84B, 0x943A, 0xE84C, 0x943B, 0xE84D, 0x943C, 0xE84E, 0x943D, 0xE84F, 0x943F, + 0xE850, 0x9440, 0xE851, 0x9441, 0xE852, 0x9442, 0xE853, 0x9443, 0xE854, 0x9444, 0xE855, 0x9445, 0xE856, 0x9446, 0xE857, 0x9447, + 0xE858, 0x9448, 0xE859, 0x9449, 0xE85A, 0x944A, 0xE85B, 0x944B, 0xE85C, 0x944C, 0xE85D, 0x944D, 0xE85E, 0x944E, 0xE85F, 0x944F, + 0xE860, 0x9450, 0xE861, 0x9451, 0xE862, 0x9452, 0xE863, 0x9453, 0xE864, 0x9454, 0xE865, 0x9455, 0xE866, 0x9456, 0xE867, 0x9457, + 0xE868, 0x9458, 0xE869, 0x9459, 0xE86A, 0x945A, 0xE86B, 0x945B, 0xE86C, 0x945C, 0xE86D, 0x945D, 0xE86E, 0x945E, 0xE86F, 0x945F, + 0xE870, 0x9460, 0xE871, 0x9461, 0xE872, 0x9462, 0xE873, 0x9463, 0xE874, 0x9464, 0xE875, 0x9465, 0xE876, 0x9466, 0xE877, 0x9467, + 0xE878, 0x9468, 0xE879, 0x9469, 0xE87A, 0x946A, 0xE87B, 0x946C, 0xE87C, 0x946D, 0xE87D, 0x946E, 0xE87E, 0x946F, 0xE880, 0x9470, + 0xE881, 0x9471, 0xE882, 0x9472, 0xE883, 0x9473, 0xE884, 0x9474, 0xE885, 0x9475, 0xE886, 0x9476, 0xE887, 0x9477, 0xE888, 0x9478, + 0xE889, 0x9479, 0xE88A, 0x947A, 0xE88B, 0x947B, 0xE88C, 0x947C, 0xE88D, 0x947D, 0xE88E, 0x947E, 0xE88F, 0x947F, 0xE890, 0x9480, + 0xE891, 0x9481, 0xE892, 0x9482, 0xE893, 0x9483, 0xE894, 0x9484, 0xE895, 0x9491, 0xE896, 0x9496, 0xE897, 0x9498, 0xE898, 0x94C7, + 0xE899, 0x94CF, 0xE89A, 0x94D3, 0xE89B, 0x94D4, 0xE89C, 0x94DA, 0xE89D, 0x94E6, 0xE89E, 0x94FB, 0xE89F, 0x951C, 0xE8A0, 0x9520, + 0xE8A1, 0x741B, 0xE8A2, 0x741A, 0xE8A3, 0x7441, 0xE8A4, 0x745C, 0xE8A5, 0x7457, 0xE8A6, 0x7455, 0xE8A7, 0x7459, 0xE8A8, 0x7477, + 0xE8A9, 0x746D, 0xE8AA, 0x747E, 0xE8AB, 0x749C, 0xE8AC, 0x748E, 0xE8AD, 0x7480, 0xE8AE, 0x7481, 0xE8AF, 0x7487, 0xE8B0, 0x748B, + 0xE8B1, 0x749E, 0xE8B2, 0x74A8, 0xE8B3, 0x74A9, 0xE8B4, 0x7490, 0xE8B5, 0x74A7, 0xE8B6, 0x74D2, 0xE8B7, 0x74BA, 0xE8B8, 0x97EA, + 0xE8B9, 0x97EB, 0xE8BA, 0x97EC, 0xE8BB, 0x674C, 0xE8BC, 0x6753, 0xE8BD, 0x675E, 0xE8BE, 0x6748, 0xE8BF, 0x6769, 0xE8C0, 0x67A5, + 0xE8C1, 0x6787, 0xE8C2, 0x676A, 0xE8C3, 0x6773, 0xE8C4, 0x6798, 0xE8C5, 0x67A7, 0xE8C6, 0x6775, 0xE8C7, 0x67A8, 0xE8C8, 0x679E, + 0xE8C9, 0x67AD, 0xE8CA, 0x678B, 0xE8CB, 0x6777, 0xE8CC, 0x677C, 0xE8CD, 0x67F0, 0xE8CE, 0x6809, 0xE8CF, 0x67D8, 0xE8D0, 0x680A, + 0xE8D1, 0x67E9, 0xE8D2, 0x67B0, 0xE8D3, 0x680C, 0xE8D4, 0x67D9, 0xE8D5, 0x67B5, 0xE8D6, 0x67DA, 0xE8D7, 0x67B3, 0xE8D8, 0x67DD, + 0xE8D9, 0x6800, 0xE8DA, 0x67C3, 0xE8DB, 0x67B8, 0xE8DC, 0x67E2, 0xE8DD, 0x680E, 0xE8DE, 0x67C1, 0xE8DF, 0x67FD, 0xE8E0, 0x6832, + 0xE8E1, 0x6833, 0xE8E2, 0x6860, 0xE8E3, 0x6861, 0xE8E4, 0x684E, 0xE8E5, 0x6862, 0xE8E6, 0x6844, 0xE8E7, 0x6864, 0xE8E8, 0x6883, + 0xE8E9, 0x681D, 0xE8EA, 0x6855, 0xE8EB, 0x6866, 0xE8EC, 0x6841, 0xE8ED, 0x6867, 0xE8EE, 0x6840, 0xE8EF, 0x683E, 0xE8F0, 0x684A, + 0xE8F1, 0x6849, 0xE8F2, 0x6829, 0xE8F3, 0x68B5, 0xE8F4, 0x688F, 0xE8F5, 0x6874, 0xE8F6, 0x6877, 0xE8F7, 0x6893, 0xE8F8, 0x686B, + 0xE8F9, 0x68C2, 0xE8FA, 0x696E, 0xE8FB, 0x68FC, 0xE8FC, 0x691F, 0xE8FD, 0x6920, 0xE8FE, 0x68F9, 0xE940, 0x9527, 0xE941, 0x9533, + 0xE942, 0x953D, 0xE943, 0x9543, 0xE944, 0x9548, 0xE945, 0x954B, 0xE946, 0x9555, 0xE947, 0x955A, 0xE948, 0x9560, 0xE949, 0x956E, + 0xE94A, 0x9574, 0xE94B, 0x9575, 0xE94C, 0x9577, 0xE94D, 0x9578, 0xE94E, 0x9579, 0xE94F, 0x957A, 0xE950, 0x957B, 0xE951, 0x957C, + 0xE952, 0x957D, 0xE953, 0x957E, 0xE954, 0x9580, 0xE955, 0x9581, 0xE956, 0x9582, 0xE957, 0x9583, 0xE958, 0x9584, 0xE959, 0x9585, + 0xE95A, 0x9586, 0xE95B, 0x9587, 0xE95C, 0x9588, 0xE95D, 0x9589, 0xE95E, 0x958A, 0xE95F, 0x958B, 0xE960, 0x958C, 0xE961, 0x958D, + 0xE962, 0x958E, 0xE963, 0x958F, 0xE964, 0x9590, 0xE965, 0x9591, 0xE966, 0x9592, 0xE967, 0x9593, 0xE968, 0x9594, 0xE969, 0x9595, + 0xE96A, 0x9596, 0xE96B, 0x9597, 0xE96C, 0x9598, 0xE96D, 0x9599, 0xE96E, 0x959A, 0xE96F, 0x959B, 0xE970, 0x959C, 0xE971, 0x959D, + 0xE972, 0x959E, 0xE973, 0x959F, 0xE974, 0x95A0, 0xE975, 0x95A1, 0xE976, 0x95A2, 0xE977, 0x95A3, 0xE978, 0x95A4, 0xE979, 0x95A5, + 0xE97A, 0x95A6, 0xE97B, 0x95A7, 0xE97C, 0x95A8, 0xE97D, 0x95A9, 0xE97E, 0x95AA, 0xE980, 0x95AB, 0xE981, 0x95AC, 0xE982, 0x95AD, + 0xE983, 0x95AE, 0xE984, 0x95AF, 0xE985, 0x95B0, 0xE986, 0x95B1, 0xE987, 0x95B2, 0xE988, 0x95B3, 0xE989, 0x95B4, 0xE98A, 0x95B5, + 0xE98B, 0x95B6, 0xE98C, 0x95B7, 0xE98D, 0x95B8, 0xE98E, 0x95B9, 0xE98F, 0x95BA, 0xE990, 0x95BB, 0xE991, 0x95BC, 0xE992, 0x95BD, + 0xE993, 0x95BE, 0xE994, 0x95BF, 0xE995, 0x95C0, 0xE996, 0x95C1, 0xE997, 0x95C2, 0xE998, 0x95C3, 0xE999, 0x95C4, 0xE99A, 0x95C5, + 0xE99B, 0x95C6, 0xE99C, 0x95C7, 0xE99D, 0x95C8, 0xE99E, 0x95C9, 0xE99F, 0x95CA, 0xE9A0, 0x95CB, 0xE9A1, 0x6924, 0xE9A2, 0x68F0, + 0xE9A3, 0x690B, 0xE9A4, 0x6901, 0xE9A5, 0x6957, 0xE9A6, 0x68E3, 0xE9A7, 0x6910, 0xE9A8, 0x6971, 0xE9A9, 0x6939, 0xE9AA, 0x6960, + 0xE9AB, 0x6942, 0xE9AC, 0x695D, 0xE9AD, 0x6984, 0xE9AE, 0x696B, 0xE9AF, 0x6980, 0xE9B0, 0x6998, 0xE9B1, 0x6978, 0xE9B2, 0x6934, + 0xE9B3, 0x69CC, 0xE9B4, 0x6987, 0xE9B5, 0x6988, 0xE9B6, 0x69CE, 0xE9B7, 0x6989, 0xE9B8, 0x6966, 0xE9B9, 0x6963, 0xE9BA, 0x6979, + 0xE9BB, 0x699B, 0xE9BC, 0x69A7, 0xE9BD, 0x69BB, 0xE9BE, 0x69AB, 0xE9BF, 0x69AD, 0xE9C0, 0x69D4, 0xE9C1, 0x69B1, 0xE9C2, 0x69C1, + 0xE9C3, 0x69CA, 0xE9C4, 0x69DF, 0xE9C5, 0x6995, 0xE9C6, 0x69E0, 0xE9C7, 0x698D, 0xE9C8, 0x69FF, 0xE9C9, 0x6A2F, 0xE9CA, 0x69ED, + 0xE9CB, 0x6A17, 0xE9CC, 0x6A18, 0xE9CD, 0x6A65, 0xE9CE, 0x69F2, 0xE9CF, 0x6A44, 0xE9D0, 0x6A3E, 0xE9D1, 0x6AA0, 0xE9D2, 0x6A50, + 0xE9D3, 0x6A5B, 0xE9D4, 0x6A35, 0xE9D5, 0x6A8E, 0xE9D6, 0x6A79, 0xE9D7, 0x6A3D, 0xE9D8, 0x6A28, 0xE9D9, 0x6A58, 0xE9DA, 0x6A7C, + 0xE9DB, 0x6A91, 0xE9DC, 0x6A90, 0xE9DD, 0x6AA9, 0xE9DE, 0x6A97, 0xE9DF, 0x6AAB, 0xE9E0, 0x7337, 0xE9E1, 0x7352, 0xE9E2, 0x6B81, + 0xE9E3, 0x6B82, 0xE9E4, 0x6B87, 0xE9E5, 0x6B84, 0xE9E6, 0x6B92, 0xE9E7, 0x6B93, 0xE9E8, 0x6B8D, 0xE9E9, 0x6B9A, 0xE9EA, 0x6B9B, + 0xE9EB, 0x6BA1, 0xE9EC, 0x6BAA, 0xE9ED, 0x8F6B, 0xE9EE, 0x8F6D, 0xE9EF, 0x8F71, 0xE9F0, 0x8F72, 0xE9F1, 0x8F73, 0xE9F2, 0x8F75, + 0xE9F3, 0x8F76, 0xE9F4, 0x8F78, 0xE9F5, 0x8F77, 0xE9F6, 0x8F79, 0xE9F7, 0x8F7A, 0xE9F8, 0x8F7C, 0xE9F9, 0x8F7E, 0xE9FA, 0x8F81, + 0xE9FB, 0x8F82, 0xE9FC, 0x8F84, 0xE9FD, 0x8F87, 0xE9FE, 0x8F8B, 0xEA40, 0x95CC, 0xEA41, 0x95CD, 0xEA42, 0x95CE, 0xEA43, 0x95CF, + 0xEA44, 0x95D0, 0xEA45, 0x95D1, 0xEA46, 0x95D2, 0xEA47, 0x95D3, 0xEA48, 0x95D4, 0xEA49, 0x95D5, 0xEA4A, 0x95D6, 0xEA4B, 0x95D7, + 0xEA4C, 0x95D8, 0xEA4D, 0x95D9, 0xEA4E, 0x95DA, 0xEA4F, 0x95DB, 0xEA50, 0x95DC, 0xEA51, 0x95DD, 0xEA52, 0x95DE, 0xEA53, 0x95DF, + 0xEA54, 0x95E0, 0xEA55, 0x95E1, 0xEA56, 0x95E2, 0xEA57, 0x95E3, 0xEA58, 0x95E4, 0xEA59, 0x95E5, 0xEA5A, 0x95E6, 0xEA5B, 0x95E7, + 0xEA5C, 0x95EC, 0xEA5D, 0x95FF, 0xEA5E, 0x9607, 0xEA5F, 0x9613, 0xEA60, 0x9618, 0xEA61, 0x961B, 0xEA62, 0x961E, 0xEA63, 0x9620, + 0xEA64, 0x9623, 0xEA65, 0x9624, 0xEA66, 0x9625, 0xEA67, 0x9626, 0xEA68, 0x9627, 0xEA69, 0x9628, 0xEA6A, 0x9629, 0xEA6B, 0x962B, + 0xEA6C, 0x962C, 0xEA6D, 0x962D, 0xEA6E, 0x962F, 0xEA6F, 0x9630, 0xEA70, 0x9637, 0xEA71, 0x9638, 0xEA72, 0x9639, 0xEA73, 0x963A, + 0xEA74, 0x963E, 0xEA75, 0x9641, 0xEA76, 0x9643, 0xEA77, 0x964A, 0xEA78, 0x964E, 0xEA79, 0x964F, 0xEA7A, 0x9651, 0xEA7B, 0x9652, + 0xEA7C, 0x9653, 0xEA7D, 0x9656, 0xEA7E, 0x9657, 0xEA80, 0x9658, 0xEA81, 0x9659, 0xEA82, 0x965A, 0xEA83, 0x965C, 0xEA84, 0x965D, + 0xEA85, 0x965E, 0xEA86, 0x9660, 0xEA87, 0x9663, 0xEA88, 0x9665, 0xEA89, 0x9666, 0xEA8A, 0x966B, 0xEA8B, 0x966D, 0xEA8C, 0x966E, + 0xEA8D, 0x966F, 0xEA8E, 0x9670, 0xEA8F, 0x9671, 0xEA90, 0x9673, 0xEA91, 0x9678, 0xEA92, 0x9679, 0xEA93, 0x967A, 0xEA94, 0x967B, + 0xEA95, 0x967C, 0xEA96, 0x967D, 0xEA97, 0x967E, 0xEA98, 0x967F, 0xEA99, 0x9680, 0xEA9A, 0x9681, 0xEA9B, 0x9682, 0xEA9C, 0x9683, + 0xEA9D, 0x9684, 0xEA9E, 0x9687, 0xEA9F, 0x9689, 0xEAA0, 0x968A, 0xEAA1, 0x8F8D, 0xEAA2, 0x8F8E, 0xEAA3, 0x8F8F, 0xEAA4, 0x8F98, + 0xEAA5, 0x8F9A, 0xEAA6, 0x8ECE, 0xEAA7, 0x620B, 0xEAA8, 0x6217, 0xEAA9, 0x621B, 0xEAAA, 0x621F, 0xEAAB, 0x6222, 0xEAAC, 0x6221, + 0xEAAD, 0x6225, 0xEAAE, 0x6224, 0xEAAF, 0x622C, 0xEAB0, 0x81E7, 0xEAB1, 0x74EF, 0xEAB2, 0x74F4, 0xEAB3, 0x74FF, 0xEAB4, 0x750F, + 0xEAB5, 0x7511, 0xEAB6, 0x7513, 0xEAB7, 0x6534, 0xEAB8, 0x65EE, 0xEAB9, 0x65EF, 0xEABA, 0x65F0, 0xEABB, 0x660A, 0xEABC, 0x6619, + 0xEABD, 0x6772, 0xEABE, 0x6603, 0xEABF, 0x6615, 0xEAC0, 0x6600, 0xEAC1, 0x7085, 0xEAC2, 0x66F7, 0xEAC3, 0x661D, 0xEAC4, 0x6634, + 0xEAC5, 0x6631, 0xEAC6, 0x6636, 0xEAC7, 0x6635, 0xEAC8, 0x8006, 0xEAC9, 0x665F, 0xEACA, 0x6654, 0xEACB, 0x6641, 0xEACC, 0x664F, + 0xEACD, 0x6656, 0xEACE, 0x6661, 0xEACF, 0x6657, 0xEAD0, 0x6677, 0xEAD1, 0x6684, 0xEAD2, 0x668C, 0xEAD3, 0x66A7, 0xEAD4, 0x669D, + 0xEAD5, 0x66BE, 0xEAD6, 0x66DB, 0xEAD7, 0x66DC, 0xEAD8, 0x66E6, 0xEAD9, 0x66E9, 0xEADA, 0x8D32, 0xEADB, 0x8D33, 0xEADC, 0x8D36, + 0xEADD, 0x8D3B, 0xEADE, 0x8D3D, 0xEADF, 0x8D40, 0xEAE0, 0x8D45, 0xEAE1, 0x8D46, 0xEAE2, 0x8D48, 0xEAE3, 0x8D49, 0xEAE4, 0x8D47, + 0xEAE5, 0x8D4D, 0xEAE6, 0x8D55, 0xEAE7, 0x8D59, 0xEAE8, 0x89C7, 0xEAE9, 0x89CA, 0xEAEA, 0x89CB, 0xEAEB, 0x89CC, 0xEAEC, 0x89CE, + 0xEAED, 0x89CF, 0xEAEE, 0x89D0, 0xEAEF, 0x89D1, 0xEAF0, 0x726E, 0xEAF1, 0x729F, 0xEAF2, 0x725D, 0xEAF3, 0x7266, 0xEAF4, 0x726F, + 0xEAF5, 0x727E, 0xEAF6, 0x727F, 0xEAF7, 0x7284, 0xEAF8, 0x728B, 0xEAF9, 0x728D, 0xEAFA, 0x728F, 0xEAFB, 0x7292, 0xEAFC, 0x6308, + 0xEAFD, 0x6332, 0xEAFE, 0x63B0, 0xEB40, 0x968C, 0xEB41, 0x968E, 0xEB42, 0x9691, 0xEB43, 0x9692, 0xEB44, 0x9693, 0xEB45, 0x9695, + 0xEB46, 0x9696, 0xEB47, 0x969A, 0xEB48, 0x969B, 0xEB49, 0x969D, 0xEB4A, 0x969E, 0xEB4B, 0x969F, 0xEB4C, 0x96A0, 0xEB4D, 0x96A1, + 0xEB4E, 0x96A2, 0xEB4F, 0x96A3, 0xEB50, 0x96A4, 0xEB51, 0x96A5, 0xEB52, 0x96A6, 0xEB53, 0x96A8, 0xEB54, 0x96A9, 0xEB55, 0x96AA, + 0xEB56, 0x96AB, 0xEB57, 0x96AC, 0xEB58, 0x96AD, 0xEB59, 0x96AE, 0xEB5A, 0x96AF, 0xEB5B, 0x96B1, 0xEB5C, 0x96B2, 0xEB5D, 0x96B4, + 0xEB5E, 0x96B5, 0xEB5F, 0x96B7, 0xEB60, 0x96B8, 0xEB61, 0x96BA, 0xEB62, 0x96BB, 0xEB63, 0x96BF, 0xEB64, 0x96C2, 0xEB65, 0x96C3, + 0xEB66, 0x96C8, 0xEB67, 0x96CA, 0xEB68, 0x96CB, 0xEB69, 0x96D0, 0xEB6A, 0x96D1, 0xEB6B, 0x96D3, 0xEB6C, 0x96D4, 0xEB6D, 0x96D6, + 0xEB6E, 0x96D7, 0xEB6F, 0x96D8, 0xEB70, 0x96D9, 0xEB71, 0x96DA, 0xEB72, 0x96DB, 0xEB73, 0x96DC, 0xEB74, 0x96DD, 0xEB75, 0x96DE, + 0xEB76, 0x96DF, 0xEB77, 0x96E1, 0xEB78, 0x96E2, 0xEB79, 0x96E3, 0xEB7A, 0x96E4, 0xEB7B, 0x96E5, 0xEB7C, 0x96E6, 0xEB7D, 0x96E7, + 0xEB7E, 0x96EB, 0xEB80, 0x96EC, 0xEB81, 0x96ED, 0xEB82, 0x96EE, 0xEB83, 0x96F0, 0xEB84, 0x96F1, 0xEB85, 0x96F2, 0xEB86, 0x96F4, + 0xEB87, 0x96F5, 0xEB88, 0x96F8, 0xEB89, 0x96FA, 0xEB8A, 0x96FB, 0xEB8B, 0x96FC, 0xEB8C, 0x96FD, 0xEB8D, 0x96FF, 0xEB8E, 0x9702, + 0xEB8F, 0x9703, 0xEB90, 0x9705, 0xEB91, 0x970A, 0xEB92, 0x970B, 0xEB93, 0x970C, 0xEB94, 0x9710, 0xEB95, 0x9711, 0xEB96, 0x9712, + 0xEB97, 0x9714, 0xEB98, 0x9715, 0xEB99, 0x9717, 0xEB9A, 0x9718, 0xEB9B, 0x9719, 0xEB9C, 0x971A, 0xEB9D, 0x971B, 0xEB9E, 0x971D, + 0xEB9F, 0x971F, 0xEBA0, 0x9720, 0xEBA1, 0x643F, 0xEBA2, 0x64D8, 0xEBA3, 0x8004, 0xEBA4, 0x6BEA, 0xEBA5, 0x6BF3, 0xEBA6, 0x6BFD, + 0xEBA7, 0x6BF5, 0xEBA8, 0x6BF9, 0xEBA9, 0x6C05, 0xEBAA, 0x6C07, 0xEBAB, 0x6C06, 0xEBAC, 0x6C0D, 0xEBAD, 0x6C15, 0xEBAE, 0x6C18, + 0xEBAF, 0x6C19, 0xEBB0, 0x6C1A, 0xEBB1, 0x6C21, 0xEBB2, 0x6C29, 0xEBB3, 0x6C24, 0xEBB4, 0x6C2A, 0xEBB5, 0x6C32, 0xEBB6, 0x6535, + 0xEBB7, 0x6555, 0xEBB8, 0x656B, 0xEBB9, 0x724D, 0xEBBA, 0x7252, 0xEBBB, 0x7256, 0xEBBC, 0x7230, 0xEBBD, 0x8662, 0xEBBE, 0x5216, + 0xEBBF, 0x809F, 0xEBC0, 0x809C, 0xEBC1, 0x8093, 0xEBC2, 0x80BC, 0xEBC3, 0x670A, 0xEBC4, 0x80BD, 0xEBC5, 0x80B1, 0xEBC6, 0x80AB, + 0xEBC7, 0x80AD, 0xEBC8, 0x80B4, 0xEBC9, 0x80B7, 0xEBCA, 0x80E7, 0xEBCB, 0x80E8, 0xEBCC, 0x80E9, 0xEBCD, 0x80EA, 0xEBCE, 0x80DB, + 0xEBCF, 0x80C2, 0xEBD0, 0x80C4, 0xEBD1, 0x80D9, 0xEBD2, 0x80CD, 0xEBD3, 0x80D7, 0xEBD4, 0x6710, 0xEBD5, 0x80DD, 0xEBD6, 0x80EB, + 0xEBD7, 0x80F1, 0xEBD8, 0x80F4, 0xEBD9, 0x80ED, 0xEBDA, 0x810D, 0xEBDB, 0x810E, 0xEBDC, 0x80F2, 0xEBDD, 0x80FC, 0xEBDE, 0x6715, + 0xEBDF, 0x8112, 0xEBE0, 0x8C5A, 0xEBE1, 0x8136, 0xEBE2, 0x811E, 0xEBE3, 0x812C, 0xEBE4, 0x8118, 0xEBE5, 0x8132, 0xEBE6, 0x8148, + 0xEBE7, 0x814C, 0xEBE8, 0x8153, 0xEBE9, 0x8174, 0xEBEA, 0x8159, 0xEBEB, 0x815A, 0xEBEC, 0x8171, 0xEBED, 0x8160, 0xEBEE, 0x8169, + 0xEBEF, 0x817C, 0xEBF0, 0x817D, 0xEBF1, 0x816D, 0xEBF2, 0x8167, 0xEBF3, 0x584D, 0xEBF4, 0x5AB5, 0xEBF5, 0x8188, 0xEBF6, 0x8182, + 0xEBF7, 0x8191, 0xEBF8, 0x6ED5, 0xEBF9, 0x81A3, 0xEBFA, 0x81AA, 0xEBFB, 0x81CC, 0xEBFC, 0x6726, 0xEBFD, 0x81CA, 0xEBFE, 0x81BB, + 0xEC40, 0x9721, 0xEC41, 0x9722, 0xEC42, 0x9723, 0xEC43, 0x9724, 0xEC44, 0x9725, 0xEC45, 0x9726, 0xEC46, 0x9727, 0xEC47, 0x9728, + 0xEC48, 0x9729, 0xEC49, 0x972B, 0xEC4A, 0x972C, 0xEC4B, 0x972E, 0xEC4C, 0x972F, 0xEC4D, 0x9731, 0xEC4E, 0x9733, 0xEC4F, 0x9734, + 0xEC50, 0x9735, 0xEC51, 0x9736, 0xEC52, 0x9737, 0xEC53, 0x973A, 0xEC54, 0x973B, 0xEC55, 0x973C, 0xEC56, 0x973D, 0xEC57, 0x973F, + 0xEC58, 0x9740, 0xEC59, 0x9741, 0xEC5A, 0x9742, 0xEC5B, 0x9743, 0xEC5C, 0x9744, 0xEC5D, 0x9745, 0xEC5E, 0x9746, 0xEC5F, 0x9747, + 0xEC60, 0x9748, 0xEC61, 0x9749, 0xEC62, 0x974A, 0xEC63, 0x974B, 0xEC64, 0x974C, 0xEC65, 0x974D, 0xEC66, 0x974E, 0xEC67, 0x974F, + 0xEC68, 0x9750, 0xEC69, 0x9751, 0xEC6A, 0x9754, 0xEC6B, 0x9755, 0xEC6C, 0x9757, 0xEC6D, 0x9758, 0xEC6E, 0x975A, 0xEC6F, 0x975C, + 0xEC70, 0x975D, 0xEC71, 0x975F, 0xEC72, 0x9763, 0xEC73, 0x9764, 0xEC74, 0x9766, 0xEC75, 0x9767, 0xEC76, 0x9768, 0xEC77, 0x976A, + 0xEC78, 0x976B, 0xEC79, 0x976C, 0xEC7A, 0x976D, 0xEC7B, 0x976E, 0xEC7C, 0x976F, 0xEC7D, 0x9770, 0xEC7E, 0x9771, 0xEC80, 0x9772, + 0xEC81, 0x9775, 0xEC82, 0x9777, 0xEC83, 0x9778, 0xEC84, 0x9779, 0xEC85, 0x977A, 0xEC86, 0x977B, 0xEC87, 0x977D, 0xEC88, 0x977E, + 0xEC89, 0x977F, 0xEC8A, 0x9780, 0xEC8B, 0x9781, 0xEC8C, 0x9782, 0xEC8D, 0x9783, 0xEC8E, 0x9784, 0xEC8F, 0x9786, 0xEC90, 0x9787, + 0xEC91, 0x9788, 0xEC92, 0x9789, 0xEC93, 0x978A, 0xEC94, 0x978C, 0xEC95, 0x978E, 0xEC96, 0x978F, 0xEC97, 0x9790, 0xEC98, 0x9793, + 0xEC99, 0x9795, 0xEC9A, 0x9796, 0xEC9B, 0x9797, 0xEC9C, 0x9799, 0xEC9D, 0x979A, 0xEC9E, 0x979B, 0xEC9F, 0x979C, 0xECA0, 0x979D, + 0xECA1, 0x81C1, 0xECA2, 0x81A6, 0xECA3, 0x6B24, 0xECA4, 0x6B37, 0xECA5, 0x6B39, 0xECA6, 0x6B43, 0xECA7, 0x6B46, 0xECA8, 0x6B59, + 0xECA9, 0x98D1, 0xECAA, 0x98D2, 0xECAB, 0x98D3, 0xECAC, 0x98D5, 0xECAD, 0x98D9, 0xECAE, 0x98DA, 0xECAF, 0x6BB3, 0xECB0, 0x5F40, + 0xECB1, 0x6BC2, 0xECB2, 0x89F3, 0xECB3, 0x6590, 0xECB4, 0x9F51, 0xECB5, 0x6593, 0xECB6, 0x65BC, 0xECB7, 0x65C6, 0xECB8, 0x65C4, + 0xECB9, 0x65C3, 0xECBA, 0x65CC, 0xECBB, 0x65CE, 0xECBC, 0x65D2, 0xECBD, 0x65D6, 0xECBE, 0x7080, 0xECBF, 0x709C, 0xECC0, 0x7096, + 0xECC1, 0x709D, 0xECC2, 0x70BB, 0xECC3, 0x70C0, 0xECC4, 0x70B7, 0xECC5, 0x70AB, 0xECC6, 0x70B1, 0xECC7, 0x70E8, 0xECC8, 0x70CA, + 0xECC9, 0x7110, 0xECCA, 0x7113, 0xECCB, 0x7116, 0xECCC, 0x712F, 0xECCD, 0x7131, 0xECCE, 0x7173, 0xECCF, 0x715C, 0xECD0, 0x7168, + 0xECD1, 0x7145, 0xECD2, 0x7172, 0xECD3, 0x714A, 0xECD4, 0x7178, 0xECD5, 0x717A, 0xECD6, 0x7198, 0xECD7, 0x71B3, 0xECD8, 0x71B5, + 0xECD9, 0x71A8, 0xECDA, 0x71A0, 0xECDB, 0x71E0, 0xECDC, 0x71D4, 0xECDD, 0x71E7, 0xECDE, 0x71F9, 0xECDF, 0x721D, 0xECE0, 0x7228, + 0xECE1, 0x706C, 0xECE2, 0x7118, 0xECE3, 0x7166, 0xECE4, 0x71B9, 0xECE5, 0x623E, 0xECE6, 0x623D, 0xECE7, 0x6243, 0xECE8, 0x6248, + 0xECE9, 0x6249, 0xECEA, 0x793B, 0xECEB, 0x7940, 0xECEC, 0x7946, 0xECED, 0x7949, 0xECEE, 0x795B, 0xECEF, 0x795C, 0xECF0, 0x7953, + 0xECF1, 0x795A, 0xECF2, 0x7962, 0xECF3, 0x7957, 0xECF4, 0x7960, 0xECF5, 0x796F, 0xECF6, 0x7967, 0xECF7, 0x797A, 0xECF8, 0x7985, + 0xECF9, 0x798A, 0xECFA, 0x799A, 0xECFB, 0x79A7, 0xECFC, 0x79B3, 0xECFD, 0x5FD1, 0xECFE, 0x5FD0, 0xED40, 0x979E, 0xED41, 0x979F, + 0xED42, 0x97A1, 0xED43, 0x97A2, 0xED44, 0x97A4, 0xED45, 0x97A5, 0xED46, 0x97A6, 0xED47, 0x97A7, 0xED48, 0x97A8, 0xED49, 0x97A9, + 0xED4A, 0x97AA, 0xED4B, 0x97AC, 0xED4C, 0x97AE, 0xED4D, 0x97B0, 0xED4E, 0x97B1, 0xED4F, 0x97B3, 0xED50, 0x97B5, 0xED51, 0x97B6, + 0xED52, 0x97B7, 0xED53, 0x97B8, 0xED54, 0x97B9, 0xED55, 0x97BA, 0xED56, 0x97BB, 0xED57, 0x97BC, 0xED58, 0x97BD, 0xED59, 0x97BE, + 0xED5A, 0x97BF, 0xED5B, 0x97C0, 0xED5C, 0x97C1, 0xED5D, 0x97C2, 0xED5E, 0x97C3, 0xED5F, 0x97C4, 0xED60, 0x97C5, 0xED61, 0x97C6, + 0xED62, 0x97C7, 0xED63, 0x97C8, 0xED64, 0x97C9, 0xED65, 0x97CA, 0xED66, 0x97CB, 0xED67, 0x97CC, 0xED68, 0x97CD, 0xED69, 0x97CE, + 0xED6A, 0x97CF, 0xED6B, 0x97D0, 0xED6C, 0x97D1, 0xED6D, 0x97D2, 0xED6E, 0x97D3, 0xED6F, 0x97D4, 0xED70, 0x97D5, 0xED71, 0x97D6, + 0xED72, 0x97D7, 0xED73, 0x97D8, 0xED74, 0x97D9, 0xED75, 0x97DA, 0xED76, 0x97DB, 0xED77, 0x97DC, 0xED78, 0x97DD, 0xED79, 0x97DE, + 0xED7A, 0x97DF, 0xED7B, 0x97E0, 0xED7C, 0x97E1, 0xED7D, 0x97E2, 0xED7E, 0x97E3, 0xED80, 0x97E4, 0xED81, 0x97E5, 0xED82, 0x97E8, + 0xED83, 0x97EE, 0xED84, 0x97EF, 0xED85, 0x97F0, 0xED86, 0x97F1, 0xED87, 0x97F2, 0xED88, 0x97F4, 0xED89, 0x97F7, 0xED8A, 0x97F8, + 0xED8B, 0x97F9, 0xED8C, 0x97FA, 0xED8D, 0x97FB, 0xED8E, 0x97FC, 0xED8F, 0x97FD, 0xED90, 0x97FE, 0xED91, 0x97FF, 0xED92, 0x9800, + 0xED93, 0x9801, 0xED94, 0x9802, 0xED95, 0x9803, 0xED96, 0x9804, 0xED97, 0x9805, 0xED98, 0x9806, 0xED99, 0x9807, 0xED9A, 0x9808, + 0xED9B, 0x9809, 0xED9C, 0x980A, 0xED9D, 0x980B, 0xED9E, 0x980C, 0xED9F, 0x980D, 0xEDA0, 0x980E, 0xEDA1, 0x603C, 0xEDA2, 0x605D, + 0xEDA3, 0x605A, 0xEDA4, 0x6067, 0xEDA5, 0x6041, 0xEDA6, 0x6059, 0xEDA7, 0x6063, 0xEDA8, 0x60AB, 0xEDA9, 0x6106, 0xEDAA, 0x610D, + 0xEDAB, 0x615D, 0xEDAC, 0x61A9, 0xEDAD, 0x619D, 0xEDAE, 0x61CB, 0xEDAF, 0x61D1, 0xEDB0, 0x6206, 0xEDB1, 0x8080, 0xEDB2, 0x807F, + 0xEDB3, 0x6C93, 0xEDB4, 0x6CF6, 0xEDB5, 0x6DFC, 0xEDB6, 0x77F6, 0xEDB7, 0x77F8, 0xEDB8, 0x7800, 0xEDB9, 0x7809, 0xEDBA, 0x7817, + 0xEDBB, 0x7818, 0xEDBC, 0x7811, 0xEDBD, 0x65AB, 0xEDBE, 0x782D, 0xEDBF, 0x781C, 0xEDC0, 0x781D, 0xEDC1, 0x7839, 0xEDC2, 0x783A, + 0xEDC3, 0x783B, 0xEDC4, 0x781F, 0xEDC5, 0x783C, 0xEDC6, 0x7825, 0xEDC7, 0x782C, 0xEDC8, 0x7823, 0xEDC9, 0x7829, 0xEDCA, 0x784E, + 0xEDCB, 0x786D, 0xEDCC, 0x7856, 0xEDCD, 0x7857, 0xEDCE, 0x7826, 0xEDCF, 0x7850, 0xEDD0, 0x7847, 0xEDD1, 0x784C, 0xEDD2, 0x786A, + 0xEDD3, 0x789B, 0xEDD4, 0x7893, 0xEDD5, 0x789A, 0xEDD6, 0x7887, 0xEDD7, 0x789C, 0xEDD8, 0x78A1, 0xEDD9, 0x78A3, 0xEDDA, 0x78B2, + 0xEDDB, 0x78B9, 0xEDDC, 0x78A5, 0xEDDD, 0x78D4, 0xEDDE, 0x78D9, 0xEDDF, 0x78C9, 0xEDE0, 0x78EC, 0xEDE1, 0x78F2, 0xEDE2, 0x7905, + 0xEDE3, 0x78F4, 0xEDE4, 0x7913, 0xEDE5, 0x7924, 0xEDE6, 0x791E, 0xEDE7, 0x7934, 0xEDE8, 0x9F9B, 0xEDE9, 0x9EF9, 0xEDEA, 0x9EFB, + 0xEDEB, 0x9EFC, 0xEDEC, 0x76F1, 0xEDED, 0x7704, 0xEDEE, 0x770D, 0xEDEF, 0x76F9, 0xEDF0, 0x7707, 0xEDF1, 0x7708, 0xEDF2, 0x771A, + 0xEDF3, 0x7722, 0xEDF4, 0x7719, 0xEDF5, 0x772D, 0xEDF6, 0x7726, 0xEDF7, 0x7735, 0xEDF8, 0x7738, 0xEDF9, 0x7750, 0xEDFA, 0x7751, + 0xEDFB, 0x7747, 0xEDFC, 0x7743, 0xEDFD, 0x775A, 0xEDFE, 0x7768, 0xEE40, 0x980F, 0xEE41, 0x9810, 0xEE42, 0x9811, 0xEE43, 0x9812, + 0xEE44, 0x9813, 0xEE45, 0x9814, 0xEE46, 0x9815, 0xEE47, 0x9816, 0xEE48, 0x9817, 0xEE49, 0x9818, 0xEE4A, 0x9819, 0xEE4B, 0x981A, + 0xEE4C, 0x981B, 0xEE4D, 0x981C, 0xEE4E, 0x981D, 0xEE4F, 0x981E, 0xEE50, 0x981F, 0xEE51, 0x9820, 0xEE52, 0x9821, 0xEE53, 0x9822, + 0xEE54, 0x9823, 0xEE55, 0x9824, 0xEE56, 0x9825, 0xEE57, 0x9826, 0xEE58, 0x9827, 0xEE59, 0x9828, 0xEE5A, 0x9829, 0xEE5B, 0x982A, + 0xEE5C, 0x982B, 0xEE5D, 0x982C, 0xEE5E, 0x982D, 0xEE5F, 0x982E, 0xEE60, 0x982F, 0xEE61, 0x9830, 0xEE62, 0x9831, 0xEE63, 0x9832, + 0xEE64, 0x9833, 0xEE65, 0x9834, 0xEE66, 0x9835, 0xEE67, 0x9836, 0xEE68, 0x9837, 0xEE69, 0x9838, 0xEE6A, 0x9839, 0xEE6B, 0x983A, + 0xEE6C, 0x983B, 0xEE6D, 0x983C, 0xEE6E, 0x983D, 0xEE6F, 0x983E, 0xEE70, 0x983F, 0xEE71, 0x9840, 0xEE72, 0x9841, 0xEE73, 0x9842, + 0xEE74, 0x9843, 0xEE75, 0x9844, 0xEE76, 0x9845, 0xEE77, 0x9846, 0xEE78, 0x9847, 0xEE79, 0x9848, 0xEE7A, 0x9849, 0xEE7B, 0x984A, + 0xEE7C, 0x984B, 0xEE7D, 0x984C, 0xEE7E, 0x984D, 0xEE80, 0x984E, 0xEE81, 0x984F, 0xEE82, 0x9850, 0xEE83, 0x9851, 0xEE84, 0x9852, + 0xEE85, 0x9853, 0xEE86, 0x9854, 0xEE87, 0x9855, 0xEE88, 0x9856, 0xEE89, 0x9857, 0xEE8A, 0x9858, 0xEE8B, 0x9859, 0xEE8C, 0x985A, + 0xEE8D, 0x985B, 0xEE8E, 0x985C, 0xEE8F, 0x985D, 0xEE90, 0x985E, 0xEE91, 0x985F, 0xEE92, 0x9860, 0xEE93, 0x9861, 0xEE94, 0x9862, + 0xEE95, 0x9863, 0xEE96, 0x9864, 0xEE97, 0x9865, 0xEE98, 0x9866, 0xEE99, 0x9867, 0xEE9A, 0x9868, 0xEE9B, 0x9869, 0xEE9C, 0x986A, + 0xEE9D, 0x986B, 0xEE9E, 0x986C, 0xEE9F, 0x986D, 0xEEA0, 0x986E, 0xEEA1, 0x7762, 0xEEA2, 0x7765, 0xEEA3, 0x777F, 0xEEA4, 0x778D, + 0xEEA5, 0x777D, 0xEEA6, 0x7780, 0xEEA7, 0x778C, 0xEEA8, 0x7791, 0xEEA9, 0x779F, 0xEEAA, 0x77A0, 0xEEAB, 0x77B0, 0xEEAC, 0x77B5, + 0xEEAD, 0x77BD, 0xEEAE, 0x753A, 0xEEAF, 0x7540, 0xEEB0, 0x754E, 0xEEB1, 0x754B, 0xEEB2, 0x7548, 0xEEB3, 0x755B, 0xEEB4, 0x7572, + 0xEEB5, 0x7579, 0xEEB6, 0x7583, 0xEEB7, 0x7F58, 0xEEB8, 0x7F61, 0xEEB9, 0x7F5F, 0xEEBA, 0x8A48, 0xEEBB, 0x7F68, 0xEEBC, 0x7F74, + 0xEEBD, 0x7F71, 0xEEBE, 0x7F79, 0xEEBF, 0x7F81, 0xEEC0, 0x7F7E, 0xEEC1, 0x76CD, 0xEEC2, 0x76E5, 0xEEC3, 0x8832, 0xEEC4, 0x9485, + 0xEEC5, 0x9486, 0xEEC6, 0x9487, 0xEEC7, 0x948B, 0xEEC8, 0x948A, 0xEEC9, 0x948C, 0xEECA, 0x948D, 0xEECB, 0x948F, 0xEECC, 0x9490, + 0xEECD, 0x9494, 0xEECE, 0x9497, 0xEECF, 0x9495, 0xEED0, 0x949A, 0xEED1, 0x949B, 0xEED2, 0x949C, 0xEED3, 0x94A3, 0xEED4, 0x94A4, + 0xEED5, 0x94AB, 0xEED6, 0x94AA, 0xEED7, 0x94AD, 0xEED8, 0x94AC, 0xEED9, 0x94AF, 0xEEDA, 0x94B0, 0xEEDB, 0x94B2, 0xEEDC, 0x94B4, + 0xEEDD, 0x94B6, 0xEEDE, 0x94B7, 0xEEDF, 0x94B8, 0xEEE0, 0x94B9, 0xEEE1, 0x94BA, 0xEEE2, 0x94BC, 0xEEE3, 0x94BD, 0xEEE4, 0x94BF, + 0xEEE5, 0x94C4, 0xEEE6, 0x94C8, 0xEEE7, 0x94C9, 0xEEE8, 0x94CA, 0xEEE9, 0x94CB, 0xEEEA, 0x94CC, 0xEEEB, 0x94CD, 0xEEEC, 0x94CE, + 0xEEED, 0x94D0, 0xEEEE, 0x94D1, 0xEEEF, 0x94D2, 0xEEF0, 0x94D5, 0xEEF1, 0x94D6, 0xEEF2, 0x94D7, 0xEEF3, 0x94D9, 0xEEF4, 0x94D8, + 0xEEF5, 0x94DB, 0xEEF6, 0x94DE, 0xEEF7, 0x94DF, 0xEEF8, 0x94E0, 0xEEF9, 0x94E2, 0xEEFA, 0x94E4, 0xEEFB, 0x94E5, 0xEEFC, 0x94E7, + 0xEEFD, 0x94E8, 0xEEFE, 0x94EA, 0xEF40, 0x986F, 0xEF41, 0x9870, 0xEF42, 0x9871, 0xEF43, 0x9872, 0xEF44, 0x9873, 0xEF45, 0x9874, + 0xEF46, 0x988B, 0xEF47, 0x988E, 0xEF48, 0x9892, 0xEF49, 0x9895, 0xEF4A, 0x9899, 0xEF4B, 0x98A3, 0xEF4C, 0x98A8, 0xEF4D, 0x98A9, + 0xEF4E, 0x98AA, 0xEF4F, 0x98AB, 0xEF50, 0x98AC, 0xEF51, 0x98AD, 0xEF52, 0x98AE, 0xEF53, 0x98AF, 0xEF54, 0x98B0, 0xEF55, 0x98B1, + 0xEF56, 0x98B2, 0xEF57, 0x98B3, 0xEF58, 0x98B4, 0xEF59, 0x98B5, 0xEF5A, 0x98B6, 0xEF5B, 0x98B7, 0xEF5C, 0x98B8, 0xEF5D, 0x98B9, + 0xEF5E, 0x98BA, 0xEF5F, 0x98BB, 0xEF60, 0x98BC, 0xEF61, 0x98BD, 0xEF62, 0x98BE, 0xEF63, 0x98BF, 0xEF64, 0x98C0, 0xEF65, 0x98C1, + 0xEF66, 0x98C2, 0xEF67, 0x98C3, 0xEF68, 0x98C4, 0xEF69, 0x98C5, 0xEF6A, 0x98C6, 0xEF6B, 0x98C7, 0xEF6C, 0x98C8, 0xEF6D, 0x98C9, + 0xEF6E, 0x98CA, 0xEF6F, 0x98CB, 0xEF70, 0x98CC, 0xEF71, 0x98CD, 0xEF72, 0x98CF, 0xEF73, 0x98D0, 0xEF74, 0x98D4, 0xEF75, 0x98D6, + 0xEF76, 0x98D7, 0xEF77, 0x98DB, 0xEF78, 0x98DC, 0xEF79, 0x98DD, 0xEF7A, 0x98E0, 0xEF7B, 0x98E1, 0xEF7C, 0x98E2, 0xEF7D, 0x98E3, + 0xEF7E, 0x98E4, 0xEF80, 0x98E5, 0xEF81, 0x98E6, 0xEF82, 0x98E9, 0xEF83, 0x98EA, 0xEF84, 0x98EB, 0xEF85, 0x98EC, 0xEF86, 0x98ED, + 0xEF87, 0x98EE, 0xEF88, 0x98EF, 0xEF89, 0x98F0, 0xEF8A, 0x98F1, 0xEF8B, 0x98F2, 0xEF8C, 0x98F3, 0xEF8D, 0x98F4, 0xEF8E, 0x98F5, + 0xEF8F, 0x98F6, 0xEF90, 0x98F7, 0xEF91, 0x98F8, 0xEF92, 0x98F9, 0xEF93, 0x98FA, 0xEF94, 0x98FB, 0xEF95, 0x98FC, 0xEF96, 0x98FD, + 0xEF97, 0x98FE, 0xEF98, 0x98FF, 0xEF99, 0x9900, 0xEF9A, 0x9901, 0xEF9B, 0x9902, 0xEF9C, 0x9903, 0xEF9D, 0x9904, 0xEF9E, 0x9905, + 0xEF9F, 0x9906, 0xEFA0, 0x9907, 0xEFA1, 0x94E9, 0xEFA2, 0x94EB, 0xEFA3, 0x94EE, 0xEFA4, 0x94EF, 0xEFA5, 0x94F3, 0xEFA6, 0x94F4, + 0xEFA7, 0x94F5, 0xEFA8, 0x94F7, 0xEFA9, 0x94F9, 0xEFAA, 0x94FC, 0xEFAB, 0x94FD, 0xEFAC, 0x94FF, 0xEFAD, 0x9503, 0xEFAE, 0x9502, + 0xEFAF, 0x9506, 0xEFB0, 0x9507, 0xEFB1, 0x9509, 0xEFB2, 0x950A, 0xEFB3, 0x950D, 0xEFB4, 0x950E, 0xEFB5, 0x950F, 0xEFB6, 0x9512, + 0xEFB7, 0x9513, 0xEFB8, 0x9514, 0xEFB9, 0x9515, 0xEFBA, 0x9516, 0xEFBB, 0x9518, 0xEFBC, 0x951B, 0xEFBD, 0x951D, 0xEFBE, 0x951E, + 0xEFBF, 0x951F, 0xEFC0, 0x9522, 0xEFC1, 0x952A, 0xEFC2, 0x952B, 0xEFC3, 0x9529, 0xEFC4, 0x952C, 0xEFC5, 0x9531, 0xEFC6, 0x9532, + 0xEFC7, 0x9534, 0xEFC8, 0x9536, 0xEFC9, 0x9537, 0xEFCA, 0x9538, 0xEFCB, 0x953C, 0xEFCC, 0x953E, 0xEFCD, 0x953F, 0xEFCE, 0x9542, + 0xEFCF, 0x9535, 0xEFD0, 0x9544, 0xEFD1, 0x9545, 0xEFD2, 0x9546, 0xEFD3, 0x9549, 0xEFD4, 0x954C, 0xEFD5, 0x954E, 0xEFD6, 0x954F, + 0xEFD7, 0x9552, 0xEFD8, 0x9553, 0xEFD9, 0x9554, 0xEFDA, 0x9556, 0xEFDB, 0x9557, 0xEFDC, 0x9558, 0xEFDD, 0x9559, 0xEFDE, 0x955B, + 0xEFDF, 0x955E, 0xEFE0, 0x955F, 0xEFE1, 0x955D, 0xEFE2, 0x9561, 0xEFE3, 0x9562, 0xEFE4, 0x9564, 0xEFE5, 0x9565, 0xEFE6, 0x9566, + 0xEFE7, 0x9567, 0xEFE8, 0x9568, 0xEFE9, 0x9569, 0xEFEA, 0x956A, 0xEFEB, 0x956B, 0xEFEC, 0x956C, 0xEFED, 0x956F, 0xEFEE, 0x9571, + 0xEFEF, 0x9572, 0xEFF0, 0x9573, 0xEFF1, 0x953A, 0xEFF2, 0x77E7, 0xEFF3, 0x77EC, 0xEFF4, 0x96C9, 0xEFF5, 0x79D5, 0xEFF6, 0x79ED, + 0xEFF7, 0x79E3, 0xEFF8, 0x79EB, 0xEFF9, 0x7A06, 0xEFFA, 0x5D47, 0xEFFB, 0x7A03, 0xEFFC, 0x7A02, 0xEFFD, 0x7A1E, 0xEFFE, 0x7A14, + 0xF040, 0x9908, 0xF041, 0x9909, 0xF042, 0x990A, 0xF043, 0x990B, 0xF044, 0x990C, 0xF045, 0x990E, 0xF046, 0x990F, 0xF047, 0x9911, + 0xF048, 0x9912, 0xF049, 0x9913, 0xF04A, 0x9914, 0xF04B, 0x9915, 0xF04C, 0x9916, 0xF04D, 0x9917, 0xF04E, 0x9918, 0xF04F, 0x9919, + 0xF050, 0x991A, 0xF051, 0x991B, 0xF052, 0x991C, 0xF053, 0x991D, 0xF054, 0x991E, 0xF055, 0x991F, 0xF056, 0x9920, 0xF057, 0x9921, + 0xF058, 0x9922, 0xF059, 0x9923, 0xF05A, 0x9924, 0xF05B, 0x9925, 0xF05C, 0x9926, 0xF05D, 0x9927, 0xF05E, 0x9928, 0xF05F, 0x9929, + 0xF060, 0x992A, 0xF061, 0x992B, 0xF062, 0x992C, 0xF063, 0x992D, 0xF064, 0x992F, 0xF065, 0x9930, 0xF066, 0x9931, 0xF067, 0x9932, + 0xF068, 0x9933, 0xF069, 0x9934, 0xF06A, 0x9935, 0xF06B, 0x9936, 0xF06C, 0x9937, 0xF06D, 0x9938, 0xF06E, 0x9939, 0xF06F, 0x993A, + 0xF070, 0x993B, 0xF071, 0x993C, 0xF072, 0x993D, 0xF073, 0x993E, 0xF074, 0x993F, 0xF075, 0x9940, 0xF076, 0x9941, 0xF077, 0x9942, + 0xF078, 0x9943, 0xF079, 0x9944, 0xF07A, 0x9945, 0xF07B, 0x9946, 0xF07C, 0x9947, 0xF07D, 0x9948, 0xF07E, 0x9949, 0xF080, 0x994A, + 0xF081, 0x994B, 0xF082, 0x994C, 0xF083, 0x994D, 0xF084, 0x994E, 0xF085, 0x994F, 0xF086, 0x9950, 0xF087, 0x9951, 0xF088, 0x9952, + 0xF089, 0x9953, 0xF08A, 0x9956, 0xF08B, 0x9957, 0xF08C, 0x9958, 0xF08D, 0x9959, 0xF08E, 0x995A, 0xF08F, 0x995B, 0xF090, 0x995C, + 0xF091, 0x995D, 0xF092, 0x995E, 0xF093, 0x995F, 0xF094, 0x9960, 0xF095, 0x9961, 0xF096, 0x9962, 0xF097, 0x9964, 0xF098, 0x9966, + 0xF099, 0x9973, 0xF09A, 0x9978, 0xF09B, 0x9979, 0xF09C, 0x997B, 0xF09D, 0x997E, 0xF09E, 0x9982, 0xF09F, 0x9983, 0xF0A0, 0x9989, + 0xF0A1, 0x7A39, 0xF0A2, 0x7A37, 0xF0A3, 0x7A51, 0xF0A4, 0x9ECF, 0xF0A5, 0x99A5, 0xF0A6, 0x7A70, 0xF0A7, 0x7688, 0xF0A8, 0x768E, + 0xF0A9, 0x7693, 0xF0AA, 0x7699, 0xF0AB, 0x76A4, 0xF0AC, 0x74DE, 0xF0AD, 0x74E0, 0xF0AE, 0x752C, 0xF0AF, 0x9E20, 0xF0B0, 0x9E22, + 0xF0B1, 0x9E28, 0xF0B2, 0x9E29, 0xF0B3, 0x9E2A, 0xF0B4, 0x9E2B, 0xF0B5, 0x9E2C, 0xF0B6, 0x9E32, 0xF0B7, 0x9E31, 0xF0B8, 0x9E36, + 0xF0B9, 0x9E38, 0xF0BA, 0x9E37, 0xF0BB, 0x9E39, 0xF0BC, 0x9E3A, 0xF0BD, 0x9E3E, 0xF0BE, 0x9E41, 0xF0BF, 0x9E42, 0xF0C0, 0x9E44, + 0xF0C1, 0x9E46, 0xF0C2, 0x9E47, 0xF0C3, 0x9E48, 0xF0C4, 0x9E49, 0xF0C5, 0x9E4B, 0xF0C6, 0x9E4C, 0xF0C7, 0x9E4E, 0xF0C8, 0x9E51, + 0xF0C9, 0x9E55, 0xF0CA, 0x9E57, 0xF0CB, 0x9E5A, 0xF0CC, 0x9E5B, 0xF0CD, 0x9E5C, 0xF0CE, 0x9E5E, 0xF0CF, 0x9E63, 0xF0D0, 0x9E66, + 0xF0D1, 0x9E67, 0xF0D2, 0x9E68, 0xF0D3, 0x9E69, 0xF0D4, 0x9E6A, 0xF0D5, 0x9E6B, 0xF0D6, 0x9E6C, 0xF0D7, 0x9E71, 0xF0D8, 0x9E6D, + 0xF0D9, 0x9E73, 0xF0DA, 0x7592, 0xF0DB, 0x7594, 0xF0DC, 0x7596, 0xF0DD, 0x75A0, 0xF0DE, 0x759D, 0xF0DF, 0x75AC, 0xF0E0, 0x75A3, + 0xF0E1, 0x75B3, 0xF0E2, 0x75B4, 0xF0E3, 0x75B8, 0xF0E4, 0x75C4, 0xF0E5, 0x75B1, 0xF0E6, 0x75B0, 0xF0E7, 0x75C3, 0xF0E8, 0x75C2, + 0xF0E9, 0x75D6, 0xF0EA, 0x75CD, 0xF0EB, 0x75E3, 0xF0EC, 0x75E8, 0xF0ED, 0x75E6, 0xF0EE, 0x75E4, 0xF0EF, 0x75EB, 0xF0F0, 0x75E7, + 0xF0F1, 0x7603, 0xF0F2, 0x75F1, 0xF0F3, 0x75FC, 0xF0F4, 0x75FF, 0xF0F5, 0x7610, 0xF0F6, 0x7600, 0xF0F7, 0x7605, 0xF0F8, 0x760C, + 0xF0F9, 0x7617, 0xF0FA, 0x760A, 0xF0FB, 0x7625, 0xF0FC, 0x7618, 0xF0FD, 0x7615, 0xF0FE, 0x7619, 0xF140, 0x998C, 0xF141, 0x998E, + 0xF142, 0x999A, 0xF143, 0x999B, 0xF144, 0x999C, 0xF145, 0x999D, 0xF146, 0x999E, 0xF147, 0x999F, 0xF148, 0x99A0, 0xF149, 0x99A1, + 0xF14A, 0x99A2, 0xF14B, 0x99A3, 0xF14C, 0x99A4, 0xF14D, 0x99A6, 0xF14E, 0x99A7, 0xF14F, 0x99A9, 0xF150, 0x99AA, 0xF151, 0x99AB, + 0xF152, 0x99AC, 0xF153, 0x99AD, 0xF154, 0x99AE, 0xF155, 0x99AF, 0xF156, 0x99B0, 0xF157, 0x99B1, 0xF158, 0x99B2, 0xF159, 0x99B3, + 0xF15A, 0x99B4, 0xF15B, 0x99B5, 0xF15C, 0x99B6, 0xF15D, 0x99B7, 0xF15E, 0x99B8, 0xF15F, 0x99B9, 0xF160, 0x99BA, 0xF161, 0x99BB, + 0xF162, 0x99BC, 0xF163, 0x99BD, 0xF164, 0x99BE, 0xF165, 0x99BF, 0xF166, 0x99C0, 0xF167, 0x99C1, 0xF168, 0x99C2, 0xF169, 0x99C3, + 0xF16A, 0x99C4, 0xF16B, 0x99C5, 0xF16C, 0x99C6, 0xF16D, 0x99C7, 0xF16E, 0x99C8, 0xF16F, 0x99C9, 0xF170, 0x99CA, 0xF171, 0x99CB, + 0xF172, 0x99CC, 0xF173, 0x99CD, 0xF174, 0x99CE, 0xF175, 0x99CF, 0xF176, 0x99D0, 0xF177, 0x99D1, 0xF178, 0x99D2, 0xF179, 0x99D3, + 0xF17A, 0x99D4, 0xF17B, 0x99D5, 0xF17C, 0x99D6, 0xF17D, 0x99D7, 0xF17E, 0x99D8, 0xF180, 0x99D9, 0xF181, 0x99DA, 0xF182, 0x99DB, + 0xF183, 0x99DC, 0xF184, 0x99DD, 0xF185, 0x99DE, 0xF186, 0x99DF, 0xF187, 0x99E0, 0xF188, 0x99E1, 0xF189, 0x99E2, 0xF18A, 0x99E3, + 0xF18B, 0x99E4, 0xF18C, 0x99E5, 0xF18D, 0x99E6, 0xF18E, 0x99E7, 0xF18F, 0x99E8, 0xF190, 0x99E9, 0xF191, 0x99EA, 0xF192, 0x99EB, + 0xF193, 0x99EC, 0xF194, 0x99ED, 0xF195, 0x99EE, 0xF196, 0x99EF, 0xF197, 0x99F0, 0xF198, 0x99F1, 0xF199, 0x99F2, 0xF19A, 0x99F3, + 0xF19B, 0x99F4, 0xF19C, 0x99F5, 0xF19D, 0x99F6, 0xF19E, 0x99F7, 0xF19F, 0x99F8, 0xF1A0, 0x99F9, 0xF1A1, 0x761B, 0xF1A2, 0x763C, + 0xF1A3, 0x7622, 0xF1A4, 0x7620, 0xF1A5, 0x7640, 0xF1A6, 0x762D, 0xF1A7, 0x7630, 0xF1A8, 0x763F, 0xF1A9, 0x7635, 0xF1AA, 0x7643, + 0xF1AB, 0x763E, 0xF1AC, 0x7633, 0xF1AD, 0x764D, 0xF1AE, 0x765E, 0xF1AF, 0x7654, 0xF1B0, 0x765C, 0xF1B1, 0x7656, 0xF1B2, 0x766B, + 0xF1B3, 0x766F, 0xF1B4, 0x7FCA, 0xF1B5, 0x7AE6, 0xF1B6, 0x7A78, 0xF1B7, 0x7A79, 0xF1B8, 0x7A80, 0xF1B9, 0x7A86, 0xF1BA, 0x7A88, + 0xF1BB, 0x7A95, 0xF1BC, 0x7AA6, 0xF1BD, 0x7AA0, 0xF1BE, 0x7AAC, 0xF1BF, 0x7AA8, 0xF1C0, 0x7AAD, 0xF1C1, 0x7AB3, 0xF1C2, 0x8864, + 0xF1C3, 0x8869, 0xF1C4, 0x8872, 0xF1C5, 0x887D, 0xF1C6, 0x887F, 0xF1C7, 0x8882, 0xF1C8, 0x88A2, 0xF1C9, 0x88C6, 0xF1CA, 0x88B7, + 0xF1CB, 0x88BC, 0xF1CC, 0x88C9, 0xF1CD, 0x88E2, 0xF1CE, 0x88CE, 0xF1CF, 0x88E3, 0xF1D0, 0x88E5, 0xF1D1, 0x88F1, 0xF1D2, 0x891A, + 0xF1D3, 0x88FC, 0xF1D4, 0x88E8, 0xF1D5, 0x88FE, 0xF1D6, 0x88F0, 0xF1D7, 0x8921, 0xF1D8, 0x8919, 0xF1D9, 0x8913, 0xF1DA, 0x891B, + 0xF1DB, 0x890A, 0xF1DC, 0x8934, 0xF1DD, 0x892B, 0xF1DE, 0x8936, 0xF1DF, 0x8941, 0xF1E0, 0x8966, 0xF1E1, 0x897B, 0xF1E2, 0x758B, + 0xF1E3, 0x80E5, 0xF1E4, 0x76B2, 0xF1E5, 0x76B4, 0xF1E6, 0x77DC, 0xF1E7, 0x8012, 0xF1E8, 0x8014, 0xF1E9, 0x8016, 0xF1EA, 0x801C, + 0xF1EB, 0x8020, 0xF1EC, 0x8022, 0xF1ED, 0x8025, 0xF1EE, 0x8026, 0xF1EF, 0x8027, 0xF1F0, 0x8029, 0xF1F1, 0x8028, 0xF1F2, 0x8031, + 0xF1F3, 0x800B, 0xF1F4, 0x8035, 0xF1F5, 0x8043, 0xF1F6, 0x8046, 0xF1F7, 0x804D, 0xF1F8, 0x8052, 0xF1F9, 0x8069, 0xF1FA, 0x8071, + 0xF1FB, 0x8983, 0xF1FC, 0x9878, 0xF1FD, 0x9880, 0xF1FE, 0x9883, 0xF240, 0x99FA, 0xF241, 0x99FB, 0xF242, 0x99FC, 0xF243, 0x99FD, + 0xF244, 0x99FE, 0xF245, 0x99FF, 0xF246, 0x9A00, 0xF247, 0x9A01, 0xF248, 0x9A02, 0xF249, 0x9A03, 0xF24A, 0x9A04, 0xF24B, 0x9A05, + 0xF24C, 0x9A06, 0xF24D, 0x9A07, 0xF24E, 0x9A08, 0xF24F, 0x9A09, 0xF250, 0x9A0A, 0xF251, 0x9A0B, 0xF252, 0x9A0C, 0xF253, 0x9A0D, + 0xF254, 0x9A0E, 0xF255, 0x9A0F, 0xF256, 0x9A10, 0xF257, 0x9A11, 0xF258, 0x9A12, 0xF259, 0x9A13, 0xF25A, 0x9A14, 0xF25B, 0x9A15, + 0xF25C, 0x9A16, 0xF25D, 0x9A17, 0xF25E, 0x9A18, 0xF25F, 0x9A19, 0xF260, 0x9A1A, 0xF261, 0x9A1B, 0xF262, 0x9A1C, 0xF263, 0x9A1D, + 0xF264, 0x9A1E, 0xF265, 0x9A1F, 0xF266, 0x9A20, 0xF267, 0x9A21, 0xF268, 0x9A22, 0xF269, 0x9A23, 0xF26A, 0x9A24, 0xF26B, 0x9A25, + 0xF26C, 0x9A26, 0xF26D, 0x9A27, 0xF26E, 0x9A28, 0xF26F, 0x9A29, 0xF270, 0x9A2A, 0xF271, 0x9A2B, 0xF272, 0x9A2C, 0xF273, 0x9A2D, + 0xF274, 0x9A2E, 0xF275, 0x9A2F, 0xF276, 0x9A30, 0xF277, 0x9A31, 0xF278, 0x9A32, 0xF279, 0x9A33, 0xF27A, 0x9A34, 0xF27B, 0x9A35, + 0xF27C, 0x9A36, 0xF27D, 0x9A37, 0xF27E, 0x9A38, 0xF280, 0x9A39, 0xF281, 0x9A3A, 0xF282, 0x9A3B, 0xF283, 0x9A3C, 0xF284, 0x9A3D, + 0xF285, 0x9A3E, 0xF286, 0x9A3F, 0xF287, 0x9A40, 0xF288, 0x9A41, 0xF289, 0x9A42, 0xF28A, 0x9A43, 0xF28B, 0x9A44, 0xF28C, 0x9A45, + 0xF28D, 0x9A46, 0xF28E, 0x9A47, 0xF28F, 0x9A48, 0xF290, 0x9A49, 0xF291, 0x9A4A, 0xF292, 0x9A4B, 0xF293, 0x9A4C, 0xF294, 0x9A4D, + 0xF295, 0x9A4E, 0xF296, 0x9A4F, 0xF297, 0x9A50, 0xF298, 0x9A51, 0xF299, 0x9A52, 0xF29A, 0x9A53, 0xF29B, 0x9A54, 0xF29C, 0x9A55, + 0xF29D, 0x9A56, 0xF29E, 0x9A57, 0xF29F, 0x9A58, 0xF2A0, 0x9A59, 0xF2A1, 0x9889, 0xF2A2, 0x988C, 0xF2A3, 0x988D, 0xF2A4, 0x988F, + 0xF2A5, 0x9894, 0xF2A6, 0x989A, 0xF2A7, 0x989B, 0xF2A8, 0x989E, 0xF2A9, 0x989F, 0xF2AA, 0x98A1, 0xF2AB, 0x98A2, 0xF2AC, 0x98A5, + 0xF2AD, 0x98A6, 0xF2AE, 0x864D, 0xF2AF, 0x8654, 0xF2B0, 0x866C, 0xF2B1, 0x866E, 0xF2B2, 0x867F, 0xF2B3, 0x867A, 0xF2B4, 0x867C, + 0xF2B5, 0x867B, 0xF2B6, 0x86A8, 0xF2B7, 0x868D, 0xF2B8, 0x868B, 0xF2B9, 0x86AC, 0xF2BA, 0x869D, 0xF2BB, 0x86A7, 0xF2BC, 0x86A3, + 0xF2BD, 0x86AA, 0xF2BE, 0x8693, 0xF2BF, 0x86A9, 0xF2C0, 0x86B6, 0xF2C1, 0x86C4, 0xF2C2, 0x86B5, 0xF2C3, 0x86CE, 0xF2C4, 0x86B0, + 0xF2C5, 0x86BA, 0xF2C6, 0x86B1, 0xF2C7, 0x86AF, 0xF2C8, 0x86C9, 0xF2C9, 0x86CF, 0xF2CA, 0x86B4, 0xF2CB, 0x86E9, 0xF2CC, 0x86F1, + 0xF2CD, 0x86F2, 0xF2CE, 0x86ED, 0xF2CF, 0x86F3, 0xF2D0, 0x86D0, 0xF2D1, 0x8713, 0xF2D2, 0x86DE, 0xF2D3, 0x86F4, 0xF2D4, 0x86DF, + 0xF2D5, 0x86D8, 0xF2D6, 0x86D1, 0xF2D7, 0x8703, 0xF2D8, 0x8707, 0xF2D9, 0x86F8, 0xF2DA, 0x8708, 0xF2DB, 0x870A, 0xF2DC, 0x870D, + 0xF2DD, 0x8709, 0xF2DE, 0x8723, 0xF2DF, 0x873B, 0xF2E0, 0x871E, 0xF2E1, 0x8725, 0xF2E2, 0x872E, 0xF2E3, 0x871A, 0xF2E4, 0x873E, + 0xF2E5, 0x8748, 0xF2E6, 0x8734, 0xF2E7, 0x8731, 0xF2E8, 0x8729, 0xF2E9, 0x8737, 0xF2EA, 0x873F, 0xF2EB, 0x8782, 0xF2EC, 0x8722, + 0xF2ED, 0x877D, 0xF2EE, 0x877E, 0xF2EF, 0x877B, 0xF2F0, 0x8760, 0xF2F1, 0x8770, 0xF2F2, 0x874C, 0xF2F3, 0x876E, 0xF2F4, 0x878B, + 0xF2F5, 0x8753, 0xF2F6, 0x8763, 0xF2F7, 0x877C, 0xF2F8, 0x8764, 0xF2F9, 0x8759, 0xF2FA, 0x8765, 0xF2FB, 0x8793, 0xF2FC, 0x87AF, + 0xF2FD, 0x87A8, 0xF2FE, 0x87D2, 0xF340, 0x9A5A, 0xF341, 0x9A5B, 0xF342, 0x9A5C, 0xF343, 0x9A5D, 0xF344, 0x9A5E, 0xF345, 0x9A5F, + 0xF346, 0x9A60, 0xF347, 0x9A61, 0xF348, 0x9A62, 0xF349, 0x9A63, 0xF34A, 0x9A64, 0xF34B, 0x9A65, 0xF34C, 0x9A66, 0xF34D, 0x9A67, + 0xF34E, 0x9A68, 0xF34F, 0x9A69, 0xF350, 0x9A6A, 0xF351, 0x9A6B, 0xF352, 0x9A72, 0xF353, 0x9A83, 0xF354, 0x9A89, 0xF355, 0x9A8D, + 0xF356, 0x9A8E, 0xF357, 0x9A94, 0xF358, 0x9A95, 0xF359, 0x9A99, 0xF35A, 0x9AA6, 0xF35B, 0x9AA9, 0xF35C, 0x9AAA, 0xF35D, 0x9AAB, + 0xF35E, 0x9AAC, 0xF35F, 0x9AAD, 0xF360, 0x9AAE, 0xF361, 0x9AAF, 0xF362, 0x9AB2, 0xF363, 0x9AB3, 0xF364, 0x9AB4, 0xF365, 0x9AB5, + 0xF366, 0x9AB9, 0xF367, 0x9ABB, 0xF368, 0x9ABD, 0xF369, 0x9ABE, 0xF36A, 0x9ABF, 0xF36B, 0x9AC3, 0xF36C, 0x9AC4, 0xF36D, 0x9AC6, + 0xF36E, 0x9AC7, 0xF36F, 0x9AC8, 0xF370, 0x9AC9, 0xF371, 0x9ACA, 0xF372, 0x9ACD, 0xF373, 0x9ACE, 0xF374, 0x9ACF, 0xF375, 0x9AD0, + 0xF376, 0x9AD2, 0xF377, 0x9AD4, 0xF378, 0x9AD5, 0xF379, 0x9AD6, 0xF37A, 0x9AD7, 0xF37B, 0x9AD9, 0xF37C, 0x9ADA, 0xF37D, 0x9ADB, + 0xF37E, 0x9ADC, 0xF380, 0x9ADD, 0xF381, 0x9ADE, 0xF382, 0x9AE0, 0xF383, 0x9AE2, 0xF384, 0x9AE3, 0xF385, 0x9AE4, 0xF386, 0x9AE5, + 0xF387, 0x9AE7, 0xF388, 0x9AE8, 0xF389, 0x9AE9, 0xF38A, 0x9AEA, 0xF38B, 0x9AEC, 0xF38C, 0x9AEE, 0xF38D, 0x9AF0, 0xF38E, 0x9AF1, + 0xF38F, 0x9AF2, 0xF390, 0x9AF3, 0xF391, 0x9AF4, 0xF392, 0x9AF5, 0xF393, 0x9AF6, 0xF394, 0x9AF7, 0xF395, 0x9AF8, 0xF396, 0x9AFA, + 0xF397, 0x9AFC, 0xF398, 0x9AFD, 0xF399, 0x9AFE, 0xF39A, 0x9AFF, 0xF39B, 0x9B00, 0xF39C, 0x9B01, 0xF39D, 0x9B02, 0xF39E, 0x9B04, + 0xF39F, 0x9B05, 0xF3A0, 0x9B06, 0xF3A1, 0x87C6, 0xF3A2, 0x8788, 0xF3A3, 0x8785, 0xF3A4, 0x87AD, 0xF3A5, 0x8797, 0xF3A6, 0x8783, + 0xF3A7, 0x87AB, 0xF3A8, 0x87E5, 0xF3A9, 0x87AC, 0xF3AA, 0x87B5, 0xF3AB, 0x87B3, 0xF3AC, 0x87CB, 0xF3AD, 0x87D3, 0xF3AE, 0x87BD, + 0xF3AF, 0x87D1, 0xF3B0, 0x87C0, 0xF3B1, 0x87CA, 0xF3B2, 0x87DB, 0xF3B3, 0x87EA, 0xF3B4, 0x87E0, 0xF3B5, 0x87EE, 0xF3B6, 0x8816, + 0xF3B7, 0x8813, 0xF3B8, 0x87FE, 0xF3B9, 0x880A, 0xF3BA, 0x881B, 0xF3BB, 0x8821, 0xF3BC, 0x8839, 0xF3BD, 0x883C, 0xF3BE, 0x7F36, + 0xF3BF, 0x7F42, 0xF3C0, 0x7F44, 0xF3C1, 0x7F45, 0xF3C2, 0x8210, 0xF3C3, 0x7AFA, 0xF3C4, 0x7AFD, 0xF3C5, 0x7B08, 0xF3C6, 0x7B03, + 0xF3C7, 0x7B04, 0xF3C8, 0x7B15, 0xF3C9, 0x7B0A, 0xF3CA, 0x7B2B, 0xF3CB, 0x7B0F, 0xF3CC, 0x7B47, 0xF3CD, 0x7B38, 0xF3CE, 0x7B2A, + 0xF3CF, 0x7B19, 0xF3D0, 0x7B2E, 0xF3D1, 0x7B31, 0xF3D2, 0x7B20, 0xF3D3, 0x7B25, 0xF3D4, 0x7B24, 0xF3D5, 0x7B33, 0xF3D6, 0x7B3E, + 0xF3D7, 0x7B1E, 0xF3D8, 0x7B58, 0xF3D9, 0x7B5A, 0xF3DA, 0x7B45, 0xF3DB, 0x7B75, 0xF3DC, 0x7B4C, 0xF3DD, 0x7B5D, 0xF3DE, 0x7B60, + 0xF3DF, 0x7B6E, 0xF3E0, 0x7B7B, 0xF3E1, 0x7B62, 0xF3E2, 0x7B72, 0xF3E3, 0x7B71, 0xF3E4, 0x7B90, 0xF3E5, 0x7BA6, 0xF3E6, 0x7BA7, + 0xF3E7, 0x7BB8, 0xF3E8, 0x7BAC, 0xF3E9, 0x7B9D, 0xF3EA, 0x7BA8, 0xF3EB, 0x7B85, 0xF3EC, 0x7BAA, 0xF3ED, 0x7B9C, 0xF3EE, 0x7BA2, + 0xF3EF, 0x7BAB, 0xF3F0, 0x7BB4, 0xF3F1, 0x7BD1, 0xF3F2, 0x7BC1, 0xF3F3, 0x7BCC, 0xF3F4, 0x7BDD, 0xF3F5, 0x7BDA, 0xF3F6, 0x7BE5, + 0xF3F7, 0x7BE6, 0xF3F8, 0x7BEA, 0xF3F9, 0x7C0C, 0xF3FA, 0x7BFE, 0xF3FB, 0x7BFC, 0xF3FC, 0x7C0F, 0xF3FD, 0x7C16, 0xF3FE, 0x7C0B, + 0xF440, 0x9B07, 0xF441, 0x9B09, 0xF442, 0x9B0A, 0xF443, 0x9B0B, 0xF444, 0x9B0C, 0xF445, 0x9B0D, 0xF446, 0x9B0E, 0xF447, 0x9B10, + 0xF448, 0x9B11, 0xF449, 0x9B12, 0xF44A, 0x9B14, 0xF44B, 0x9B15, 0xF44C, 0x9B16, 0xF44D, 0x9B17, 0xF44E, 0x9B18, 0xF44F, 0x9B19, + 0xF450, 0x9B1A, 0xF451, 0x9B1B, 0xF452, 0x9B1C, 0xF453, 0x9B1D, 0xF454, 0x9B1E, 0xF455, 0x9B20, 0xF456, 0x9B21, 0xF457, 0x9B22, + 0xF458, 0x9B24, 0xF459, 0x9B25, 0xF45A, 0x9B26, 0xF45B, 0x9B27, 0xF45C, 0x9B28, 0xF45D, 0x9B29, 0xF45E, 0x9B2A, 0xF45F, 0x9B2B, + 0xF460, 0x9B2C, 0xF461, 0x9B2D, 0xF462, 0x9B2E, 0xF463, 0x9B30, 0xF464, 0x9B31, 0xF465, 0x9B33, 0xF466, 0x9B34, 0xF467, 0x9B35, + 0xF468, 0x9B36, 0xF469, 0x9B37, 0xF46A, 0x9B38, 0xF46B, 0x9B39, 0xF46C, 0x9B3A, 0xF46D, 0x9B3D, 0xF46E, 0x9B3E, 0xF46F, 0x9B3F, + 0xF470, 0x9B40, 0xF471, 0x9B46, 0xF472, 0x9B4A, 0xF473, 0x9B4B, 0xF474, 0x9B4C, 0xF475, 0x9B4E, 0xF476, 0x9B50, 0xF477, 0x9B52, + 0xF478, 0x9B53, 0xF479, 0x9B55, 0xF47A, 0x9B56, 0xF47B, 0x9B57, 0xF47C, 0x9B58, 0xF47D, 0x9B59, 0xF47E, 0x9B5A, 0xF480, 0x9B5B, + 0xF481, 0x9B5C, 0xF482, 0x9B5D, 0xF483, 0x9B5E, 0xF484, 0x9B5F, 0xF485, 0x9B60, 0xF486, 0x9B61, 0xF487, 0x9B62, 0xF488, 0x9B63, + 0xF489, 0x9B64, 0xF48A, 0x9B65, 0xF48B, 0x9B66, 0xF48C, 0x9B67, 0xF48D, 0x9B68, 0xF48E, 0x9B69, 0xF48F, 0x9B6A, 0xF490, 0x9B6B, + 0xF491, 0x9B6C, 0xF492, 0x9B6D, 0xF493, 0x9B6E, 0xF494, 0x9B6F, 0xF495, 0x9B70, 0xF496, 0x9B71, 0xF497, 0x9B72, 0xF498, 0x9B73, + 0xF499, 0x9B74, 0xF49A, 0x9B75, 0xF49B, 0x9B76, 0xF49C, 0x9B77, 0xF49D, 0x9B78, 0xF49E, 0x9B79, 0xF49F, 0x9B7A, 0xF4A0, 0x9B7B, + 0xF4A1, 0x7C1F, 0xF4A2, 0x7C2A, 0xF4A3, 0x7C26, 0xF4A4, 0x7C38, 0xF4A5, 0x7C41, 0xF4A6, 0x7C40, 0xF4A7, 0x81FE, 0xF4A8, 0x8201, + 0xF4A9, 0x8202, 0xF4AA, 0x8204, 0xF4AB, 0x81EC, 0xF4AC, 0x8844, 0xF4AD, 0x8221, 0xF4AE, 0x8222, 0xF4AF, 0x8223, 0xF4B0, 0x822D, + 0xF4B1, 0x822F, 0xF4B2, 0x8228, 0xF4B3, 0x822B, 0xF4B4, 0x8238, 0xF4B5, 0x823B, 0xF4B6, 0x8233, 0xF4B7, 0x8234, 0xF4B8, 0x823E, + 0xF4B9, 0x8244, 0xF4BA, 0x8249, 0xF4BB, 0x824B, 0xF4BC, 0x824F, 0xF4BD, 0x825A, 0xF4BE, 0x825F, 0xF4BF, 0x8268, 0xF4C0, 0x887E, + 0xF4C1, 0x8885, 0xF4C2, 0x8888, 0xF4C3, 0x88D8, 0xF4C4, 0x88DF, 0xF4C5, 0x895E, 0xF4C6, 0x7F9D, 0xF4C7, 0x7F9F, 0xF4C8, 0x7FA7, + 0xF4C9, 0x7FAF, 0xF4CA, 0x7FB0, 0xF4CB, 0x7FB2, 0xF4CC, 0x7C7C, 0xF4CD, 0x6549, 0xF4CE, 0x7C91, 0xF4CF, 0x7C9D, 0xF4D0, 0x7C9C, + 0xF4D1, 0x7C9E, 0xF4D2, 0x7CA2, 0xF4D3, 0x7CB2, 0xF4D4, 0x7CBC, 0xF4D5, 0x7CBD, 0xF4D6, 0x7CC1, 0xF4D7, 0x7CC7, 0xF4D8, 0x7CCC, + 0xF4D9, 0x7CCD, 0xF4DA, 0x7CC8, 0xF4DB, 0x7CC5, 0xF4DC, 0x7CD7, 0xF4DD, 0x7CE8, 0xF4DE, 0x826E, 0xF4DF, 0x66A8, 0xF4E0, 0x7FBF, + 0xF4E1, 0x7FCE, 0xF4E2, 0x7FD5, 0xF4E3, 0x7FE5, 0xF4E4, 0x7FE1, 0xF4E5, 0x7FE6, 0xF4E6, 0x7FE9, 0xF4E7, 0x7FEE, 0xF4E8, 0x7FF3, + 0xF4E9, 0x7CF8, 0xF4EA, 0x7D77, 0xF4EB, 0x7DA6, 0xF4EC, 0x7DAE, 0xF4ED, 0x7E47, 0xF4EE, 0x7E9B, 0xF4EF, 0x9EB8, 0xF4F0, 0x9EB4, + 0xF4F1, 0x8D73, 0xF4F2, 0x8D84, 0xF4F3, 0x8D94, 0xF4F4, 0x8D91, 0xF4F5, 0x8DB1, 0xF4F6, 0x8D67, 0xF4F7, 0x8D6D, 0xF4F8, 0x8C47, + 0xF4F9, 0x8C49, 0xF4FA, 0x914A, 0xF4FB, 0x9150, 0xF4FC, 0x914E, 0xF4FD, 0x914F, 0xF4FE, 0x9164, 0xF540, 0x9B7C, 0xF541, 0x9B7D, + 0xF542, 0x9B7E, 0xF543, 0x9B7F, 0xF544, 0x9B80, 0xF545, 0x9B81, 0xF546, 0x9B82, 0xF547, 0x9B83, 0xF548, 0x9B84, 0xF549, 0x9B85, + 0xF54A, 0x9B86, 0xF54B, 0x9B87, 0xF54C, 0x9B88, 0xF54D, 0x9B89, 0xF54E, 0x9B8A, 0xF54F, 0x9B8B, 0xF550, 0x9B8C, 0xF551, 0x9B8D, + 0xF552, 0x9B8E, 0xF553, 0x9B8F, 0xF554, 0x9B90, 0xF555, 0x9B91, 0xF556, 0x9B92, 0xF557, 0x9B93, 0xF558, 0x9B94, 0xF559, 0x9B95, + 0xF55A, 0x9B96, 0xF55B, 0x9B97, 0xF55C, 0x9B98, 0xF55D, 0x9B99, 0xF55E, 0x9B9A, 0xF55F, 0x9B9B, 0xF560, 0x9B9C, 0xF561, 0x9B9D, + 0xF562, 0x9B9E, 0xF563, 0x9B9F, 0xF564, 0x9BA0, 0xF565, 0x9BA1, 0xF566, 0x9BA2, 0xF567, 0x9BA3, 0xF568, 0x9BA4, 0xF569, 0x9BA5, + 0xF56A, 0x9BA6, 0xF56B, 0x9BA7, 0xF56C, 0x9BA8, 0xF56D, 0x9BA9, 0xF56E, 0x9BAA, 0xF56F, 0x9BAB, 0xF570, 0x9BAC, 0xF571, 0x9BAD, + 0xF572, 0x9BAE, 0xF573, 0x9BAF, 0xF574, 0x9BB0, 0xF575, 0x9BB1, 0xF576, 0x9BB2, 0xF577, 0x9BB3, 0xF578, 0x9BB4, 0xF579, 0x9BB5, + 0xF57A, 0x9BB6, 0xF57B, 0x9BB7, 0xF57C, 0x9BB8, 0xF57D, 0x9BB9, 0xF57E, 0x9BBA, 0xF580, 0x9BBB, 0xF581, 0x9BBC, 0xF582, 0x9BBD, + 0xF583, 0x9BBE, 0xF584, 0x9BBF, 0xF585, 0x9BC0, 0xF586, 0x9BC1, 0xF587, 0x9BC2, 0xF588, 0x9BC3, 0xF589, 0x9BC4, 0xF58A, 0x9BC5, + 0xF58B, 0x9BC6, 0xF58C, 0x9BC7, 0xF58D, 0x9BC8, 0xF58E, 0x9BC9, 0xF58F, 0x9BCA, 0xF590, 0x9BCB, 0xF591, 0x9BCC, 0xF592, 0x9BCD, + 0xF593, 0x9BCE, 0xF594, 0x9BCF, 0xF595, 0x9BD0, 0xF596, 0x9BD1, 0xF597, 0x9BD2, 0xF598, 0x9BD3, 0xF599, 0x9BD4, 0xF59A, 0x9BD5, + 0xF59B, 0x9BD6, 0xF59C, 0x9BD7, 0xF59D, 0x9BD8, 0xF59E, 0x9BD9, 0xF59F, 0x9BDA, 0xF5A0, 0x9BDB, 0xF5A1, 0x9162, 0xF5A2, 0x9161, + 0xF5A3, 0x9170, 0xF5A4, 0x9169, 0xF5A5, 0x916F, 0xF5A6, 0x917D, 0xF5A7, 0x917E, 0xF5A8, 0x9172, 0xF5A9, 0x9174, 0xF5AA, 0x9179, + 0xF5AB, 0x918C, 0xF5AC, 0x9185, 0xF5AD, 0x9190, 0xF5AE, 0x918D, 0xF5AF, 0x9191, 0xF5B0, 0x91A2, 0xF5B1, 0x91A3, 0xF5B2, 0x91AA, + 0xF5B3, 0x91AD, 0xF5B4, 0x91AE, 0xF5B5, 0x91AF, 0xF5B6, 0x91B5, 0xF5B7, 0x91B4, 0xF5B8, 0x91BA, 0xF5B9, 0x8C55, 0xF5BA, 0x9E7E, + 0xF5BB, 0x8DB8, 0xF5BC, 0x8DEB, 0xF5BD, 0x8E05, 0xF5BE, 0x8E59, 0xF5BF, 0x8E69, 0xF5C0, 0x8DB5, 0xF5C1, 0x8DBF, 0xF5C2, 0x8DBC, + 0xF5C3, 0x8DBA, 0xF5C4, 0x8DC4, 0xF5C5, 0x8DD6, 0xF5C6, 0x8DD7, 0xF5C7, 0x8DDA, 0xF5C8, 0x8DDE, 0xF5C9, 0x8DCE, 0xF5CA, 0x8DCF, + 0xF5CB, 0x8DDB, 0xF5CC, 0x8DC6, 0xF5CD, 0x8DEC, 0xF5CE, 0x8DF7, 0xF5CF, 0x8DF8, 0xF5D0, 0x8DE3, 0xF5D1, 0x8DF9, 0xF5D2, 0x8DFB, + 0xF5D3, 0x8DE4, 0xF5D4, 0x8E09, 0xF5D5, 0x8DFD, 0xF5D6, 0x8E14, 0xF5D7, 0x8E1D, 0xF5D8, 0x8E1F, 0xF5D9, 0x8E2C, 0xF5DA, 0x8E2E, + 0xF5DB, 0x8E23, 0xF5DC, 0x8E2F, 0xF5DD, 0x8E3A, 0xF5DE, 0x8E40, 0xF5DF, 0x8E39, 0xF5E0, 0x8E35, 0xF5E1, 0x8E3D, 0xF5E2, 0x8E31, + 0xF5E3, 0x8E49, 0xF5E4, 0x8E41, 0xF5E5, 0x8E42, 0xF5E6, 0x8E51, 0xF5E7, 0x8E52, 0xF5E8, 0x8E4A, 0xF5E9, 0x8E70, 0xF5EA, 0x8E76, + 0xF5EB, 0x8E7C, 0xF5EC, 0x8E6F, 0xF5ED, 0x8E74, 0xF5EE, 0x8E85, 0xF5EF, 0x8E8F, 0xF5F0, 0x8E94, 0xF5F1, 0x8E90, 0xF5F2, 0x8E9C, + 0xF5F3, 0x8E9E, 0xF5F4, 0x8C78, 0xF5F5, 0x8C82, 0xF5F6, 0x8C8A, 0xF5F7, 0x8C85, 0xF5F8, 0x8C98, 0xF5F9, 0x8C94, 0xF5FA, 0x659B, + 0xF5FB, 0x89D6, 0xF5FC, 0x89DE, 0xF5FD, 0x89DA, 0xF5FE, 0x89DC, 0xF640, 0x9BDC, 0xF641, 0x9BDD, 0xF642, 0x9BDE, 0xF643, 0x9BDF, + 0xF644, 0x9BE0, 0xF645, 0x9BE1, 0xF646, 0x9BE2, 0xF647, 0x9BE3, 0xF648, 0x9BE4, 0xF649, 0x9BE5, 0xF64A, 0x9BE6, 0xF64B, 0x9BE7, + 0xF64C, 0x9BE8, 0xF64D, 0x9BE9, 0xF64E, 0x9BEA, 0xF64F, 0x9BEB, 0xF650, 0x9BEC, 0xF651, 0x9BED, 0xF652, 0x9BEE, 0xF653, 0x9BEF, + 0xF654, 0x9BF0, 0xF655, 0x9BF1, 0xF656, 0x9BF2, 0xF657, 0x9BF3, 0xF658, 0x9BF4, 0xF659, 0x9BF5, 0xF65A, 0x9BF6, 0xF65B, 0x9BF7, + 0xF65C, 0x9BF8, 0xF65D, 0x9BF9, 0xF65E, 0x9BFA, 0xF65F, 0x9BFB, 0xF660, 0x9BFC, 0xF661, 0x9BFD, 0xF662, 0x9BFE, 0xF663, 0x9BFF, + 0xF664, 0x9C00, 0xF665, 0x9C01, 0xF666, 0x9C02, 0xF667, 0x9C03, 0xF668, 0x9C04, 0xF669, 0x9C05, 0xF66A, 0x9C06, 0xF66B, 0x9C07, + 0xF66C, 0x9C08, 0xF66D, 0x9C09, 0xF66E, 0x9C0A, 0xF66F, 0x9C0B, 0xF670, 0x9C0C, 0xF671, 0x9C0D, 0xF672, 0x9C0E, 0xF673, 0x9C0F, + 0xF674, 0x9C10, 0xF675, 0x9C11, 0xF676, 0x9C12, 0xF677, 0x9C13, 0xF678, 0x9C14, 0xF679, 0x9C15, 0xF67A, 0x9C16, 0xF67B, 0x9C17, + 0xF67C, 0x9C18, 0xF67D, 0x9C19, 0xF67E, 0x9C1A, 0xF680, 0x9C1B, 0xF681, 0x9C1C, 0xF682, 0x9C1D, 0xF683, 0x9C1E, 0xF684, 0x9C1F, + 0xF685, 0x9C20, 0xF686, 0x9C21, 0xF687, 0x9C22, 0xF688, 0x9C23, 0xF689, 0x9C24, 0xF68A, 0x9C25, 0xF68B, 0x9C26, 0xF68C, 0x9C27, + 0xF68D, 0x9C28, 0xF68E, 0x9C29, 0xF68F, 0x9C2A, 0xF690, 0x9C2B, 0xF691, 0x9C2C, 0xF692, 0x9C2D, 0xF693, 0x9C2E, 0xF694, 0x9C2F, + 0xF695, 0x9C30, 0xF696, 0x9C31, 0xF697, 0x9C32, 0xF698, 0x9C33, 0xF699, 0x9C34, 0xF69A, 0x9C35, 0xF69B, 0x9C36, 0xF69C, 0x9C37, + 0xF69D, 0x9C38, 0xF69E, 0x9C39, 0xF69F, 0x9C3A, 0xF6A0, 0x9C3B, 0xF6A1, 0x89E5, 0xF6A2, 0x89EB, 0xF6A3, 0x89EF, 0xF6A4, 0x8A3E, + 0xF6A5, 0x8B26, 0xF6A6, 0x9753, 0xF6A7, 0x96E9, 0xF6A8, 0x96F3, 0xF6A9, 0x96EF, 0xF6AA, 0x9706, 0xF6AB, 0x9701, 0xF6AC, 0x9708, + 0xF6AD, 0x970F, 0xF6AE, 0x970E, 0xF6AF, 0x972A, 0xF6B0, 0x972D, 0xF6B1, 0x9730, 0xF6B2, 0x973E, 0xF6B3, 0x9F80, 0xF6B4, 0x9F83, + 0xF6B5, 0x9F85, 0xF6B6, 0x9F86, 0xF6B7, 0x9F87, 0xF6B8, 0x9F88, 0xF6B9, 0x9F89, 0xF6BA, 0x9F8A, 0xF6BB, 0x9F8C, 0xF6BC, 0x9EFE, + 0xF6BD, 0x9F0B, 0xF6BE, 0x9F0D, 0xF6BF, 0x96B9, 0xF6C0, 0x96BC, 0xF6C1, 0x96BD, 0xF6C2, 0x96CE, 0xF6C3, 0x96D2, 0xF6C4, 0x77BF, + 0xF6C5, 0x96E0, 0xF6C6, 0x928E, 0xF6C7, 0x92AE, 0xF6C8, 0x92C8, 0xF6C9, 0x933E, 0xF6CA, 0x936A, 0xF6CB, 0x93CA, 0xF6CC, 0x938F, + 0xF6CD, 0x943E, 0xF6CE, 0x946B, 0xF6CF, 0x9C7F, 0xF6D0, 0x9C82, 0xF6D1, 0x9C85, 0xF6D2, 0x9C86, 0xF6D3, 0x9C87, 0xF6D4, 0x9C88, + 0xF6D5, 0x7A23, 0xF6D6, 0x9C8B, 0xF6D7, 0x9C8E, 0xF6D8, 0x9C90, 0xF6D9, 0x9C91, 0xF6DA, 0x9C92, 0xF6DB, 0x9C94, 0xF6DC, 0x9C95, + 0xF6DD, 0x9C9A, 0xF6DE, 0x9C9B, 0xF6DF, 0x9C9E, 0xF6E0, 0x9C9F, 0xF6E1, 0x9CA0, 0xF6E2, 0x9CA1, 0xF6E3, 0x9CA2, 0xF6E4, 0x9CA3, + 0xF6E5, 0x9CA5, 0xF6E6, 0x9CA6, 0xF6E7, 0x9CA7, 0xF6E8, 0x9CA8, 0xF6E9, 0x9CA9, 0xF6EA, 0x9CAB, 0xF6EB, 0x9CAD, 0xF6EC, 0x9CAE, + 0xF6ED, 0x9CB0, 0xF6EE, 0x9CB1, 0xF6EF, 0x9CB2, 0xF6F0, 0x9CB3, 0xF6F1, 0x9CB4, 0xF6F2, 0x9CB5, 0xF6F3, 0x9CB6, 0xF6F4, 0x9CB7, + 0xF6F5, 0x9CBA, 0xF6F6, 0x9CBB, 0xF6F7, 0x9CBC, 0xF6F8, 0x9CBD, 0xF6F9, 0x9CC4, 0xF6FA, 0x9CC5, 0xF6FB, 0x9CC6, 0xF6FC, 0x9CC7, + 0xF6FD, 0x9CCA, 0xF6FE, 0x9CCB, 0xF740, 0x9C3C, 0xF741, 0x9C3D, 0xF742, 0x9C3E, 0xF743, 0x9C3F, 0xF744, 0x9C40, 0xF745, 0x9C41, + 0xF746, 0x9C42, 0xF747, 0x9C43, 0xF748, 0x9C44, 0xF749, 0x9C45, 0xF74A, 0x9C46, 0xF74B, 0x9C47, 0xF74C, 0x9C48, 0xF74D, 0x9C49, + 0xF74E, 0x9C4A, 0xF74F, 0x9C4B, 0xF750, 0x9C4C, 0xF751, 0x9C4D, 0xF752, 0x9C4E, 0xF753, 0x9C4F, 0xF754, 0x9C50, 0xF755, 0x9C51, + 0xF756, 0x9C52, 0xF757, 0x9C53, 0xF758, 0x9C54, 0xF759, 0x9C55, 0xF75A, 0x9C56, 0xF75B, 0x9C57, 0xF75C, 0x9C58, 0xF75D, 0x9C59, + 0xF75E, 0x9C5A, 0xF75F, 0x9C5B, 0xF760, 0x9C5C, 0xF761, 0x9C5D, 0xF762, 0x9C5E, 0xF763, 0x9C5F, 0xF764, 0x9C60, 0xF765, 0x9C61, + 0xF766, 0x9C62, 0xF767, 0x9C63, 0xF768, 0x9C64, 0xF769, 0x9C65, 0xF76A, 0x9C66, 0xF76B, 0x9C67, 0xF76C, 0x9C68, 0xF76D, 0x9C69, + 0xF76E, 0x9C6A, 0xF76F, 0x9C6B, 0xF770, 0x9C6C, 0xF771, 0x9C6D, 0xF772, 0x9C6E, 0xF773, 0x9C6F, 0xF774, 0x9C70, 0xF775, 0x9C71, + 0xF776, 0x9C72, 0xF777, 0x9C73, 0xF778, 0x9C74, 0xF779, 0x9C75, 0xF77A, 0x9C76, 0xF77B, 0x9C77, 0xF77C, 0x9C78, 0xF77D, 0x9C79, + 0xF77E, 0x9C7A, 0xF780, 0x9C7B, 0xF781, 0x9C7D, 0xF782, 0x9C7E, 0xF783, 0x9C80, 0xF784, 0x9C83, 0xF785, 0x9C84, 0xF786, 0x9C89, + 0xF787, 0x9C8A, 0xF788, 0x9C8C, 0xF789, 0x9C8F, 0xF78A, 0x9C93, 0xF78B, 0x9C96, 0xF78C, 0x9C97, 0xF78D, 0x9C98, 0xF78E, 0x9C99, + 0xF78F, 0x9C9D, 0xF790, 0x9CAA, 0xF791, 0x9CAC, 0xF792, 0x9CAF, 0xF793, 0x9CB9, 0xF794, 0x9CBE, 0xF795, 0x9CBF, 0xF796, 0x9CC0, + 0xF797, 0x9CC1, 0xF798, 0x9CC2, 0xF799, 0x9CC8, 0xF79A, 0x9CC9, 0xF79B, 0x9CD1, 0xF79C, 0x9CD2, 0xF79D, 0x9CDA, 0xF79E, 0x9CDB, + 0xF79F, 0x9CE0, 0xF7A0, 0x9CE1, 0xF7A1, 0x9CCC, 0xF7A2, 0x9CCD, 0xF7A3, 0x9CCE, 0xF7A4, 0x9CCF, 0xF7A5, 0x9CD0, 0xF7A6, 0x9CD3, + 0xF7A7, 0x9CD4, 0xF7A8, 0x9CD5, 0xF7A9, 0x9CD7, 0xF7AA, 0x9CD8, 0xF7AB, 0x9CD9, 0xF7AC, 0x9CDC, 0xF7AD, 0x9CDD, 0xF7AE, 0x9CDF, + 0xF7AF, 0x9CE2, 0xF7B0, 0x977C, 0xF7B1, 0x9785, 0xF7B2, 0x9791, 0xF7B3, 0x9792, 0xF7B4, 0x9794, 0xF7B5, 0x97AF, 0xF7B6, 0x97AB, + 0xF7B7, 0x97A3, 0xF7B8, 0x97B2, 0xF7B9, 0x97B4, 0xF7BA, 0x9AB1, 0xF7BB, 0x9AB0, 0xF7BC, 0x9AB7, 0xF7BD, 0x9E58, 0xF7BE, 0x9AB6, + 0xF7BF, 0x9ABA, 0xF7C0, 0x9ABC, 0xF7C1, 0x9AC1, 0xF7C2, 0x9AC0, 0xF7C3, 0x9AC5, 0xF7C4, 0x9AC2, 0xF7C5, 0x9ACB, 0xF7C6, 0x9ACC, + 0xF7C7, 0x9AD1, 0xF7C8, 0x9B45, 0xF7C9, 0x9B43, 0xF7CA, 0x9B47, 0xF7CB, 0x9B49, 0xF7CC, 0x9B48, 0xF7CD, 0x9B4D, 0xF7CE, 0x9B51, + 0xF7CF, 0x98E8, 0xF7D0, 0x990D, 0xF7D1, 0x992E, 0xF7D2, 0x9955, 0xF7D3, 0x9954, 0xF7D4, 0x9ADF, 0xF7D5, 0x9AE1, 0xF7D6, 0x9AE6, + 0xF7D7, 0x9AEF, 0xF7D8, 0x9AEB, 0xF7D9, 0x9AFB, 0xF7DA, 0x9AED, 0xF7DB, 0x9AF9, 0xF7DC, 0x9B08, 0xF7DD, 0x9B0F, 0xF7DE, 0x9B13, + 0xF7DF, 0x9B1F, 0xF7E0, 0x9B23, 0xF7E1, 0x9EBD, 0xF7E2, 0x9EBE, 0xF7E3, 0x7E3B, 0xF7E4, 0x9E82, 0xF7E5, 0x9E87, 0xF7E6, 0x9E88, + 0xF7E7, 0x9E8B, 0xF7E8, 0x9E92, 0xF7E9, 0x93D6, 0xF7EA, 0x9E9D, 0xF7EB, 0x9E9F, 0xF7EC, 0x9EDB, 0xF7ED, 0x9EDC, 0xF7EE, 0x9EDD, + 0xF7EF, 0x9EE0, 0xF7F0, 0x9EDF, 0xF7F1, 0x9EE2, 0xF7F2, 0x9EE9, 0xF7F3, 0x9EE7, 0xF7F4, 0x9EE5, 0xF7F5, 0x9EEA, 0xF7F6, 0x9EEF, + 0xF7F7, 0x9F22, 0xF7F8, 0x9F2C, 0xF7F9, 0x9F2F, 0xF7FA, 0x9F39, 0xF7FB, 0x9F37, 0xF7FC, 0x9F3D, 0xF7FD, 0x9F3E, 0xF7FE, 0x9F44, + 0xF840, 0x9CE3, 0xF841, 0x9CE4, 0xF842, 0x9CE5, 0xF843, 0x9CE6, 0xF844, 0x9CE7, 0xF845, 0x9CE8, 0xF846, 0x9CE9, 0xF847, 0x9CEA, + 0xF848, 0x9CEB, 0xF849, 0x9CEC, 0xF84A, 0x9CED, 0xF84B, 0x9CEE, 0xF84C, 0x9CEF, 0xF84D, 0x9CF0, 0xF84E, 0x9CF1, 0xF84F, 0x9CF2, + 0xF850, 0x9CF3, 0xF851, 0x9CF4, 0xF852, 0x9CF5, 0xF853, 0x9CF6, 0xF854, 0x9CF7, 0xF855, 0x9CF8, 0xF856, 0x9CF9, 0xF857, 0x9CFA, + 0xF858, 0x9CFB, 0xF859, 0x9CFC, 0xF85A, 0x9CFD, 0xF85B, 0x9CFE, 0xF85C, 0x9CFF, 0xF85D, 0x9D00, 0xF85E, 0x9D01, 0xF85F, 0x9D02, + 0xF860, 0x9D03, 0xF861, 0x9D04, 0xF862, 0x9D05, 0xF863, 0x9D06, 0xF864, 0x9D07, 0xF865, 0x9D08, 0xF866, 0x9D09, 0xF867, 0x9D0A, + 0xF868, 0x9D0B, 0xF869, 0x9D0C, 0xF86A, 0x9D0D, 0xF86B, 0x9D0E, 0xF86C, 0x9D0F, 0xF86D, 0x9D10, 0xF86E, 0x9D11, 0xF86F, 0x9D12, + 0xF870, 0x9D13, 0xF871, 0x9D14, 0xF872, 0x9D15, 0xF873, 0x9D16, 0xF874, 0x9D17, 0xF875, 0x9D18, 0xF876, 0x9D19, 0xF877, 0x9D1A, + 0xF878, 0x9D1B, 0xF879, 0x9D1C, 0xF87A, 0x9D1D, 0xF87B, 0x9D1E, 0xF87C, 0x9D1F, 0xF87D, 0x9D20, 0xF87E, 0x9D21, 0xF880, 0x9D22, + 0xF881, 0x9D23, 0xF882, 0x9D24, 0xF883, 0x9D25, 0xF884, 0x9D26, 0xF885, 0x9D27, 0xF886, 0x9D28, 0xF887, 0x9D29, 0xF888, 0x9D2A, + 0xF889, 0x9D2B, 0xF88A, 0x9D2C, 0xF88B, 0x9D2D, 0xF88C, 0x9D2E, 0xF88D, 0x9D2F, 0xF88E, 0x9D30, 0xF88F, 0x9D31, 0xF890, 0x9D32, + 0xF891, 0x9D33, 0xF892, 0x9D34, 0xF893, 0x9D35, 0xF894, 0x9D36, 0xF895, 0x9D37, 0xF896, 0x9D38, 0xF897, 0x9D39, 0xF898, 0x9D3A, + 0xF899, 0x9D3B, 0xF89A, 0x9D3C, 0xF89B, 0x9D3D, 0xF89C, 0x9D3E, 0xF89D, 0x9D3F, 0xF89E, 0x9D40, 0xF89F, 0x9D41, 0xF8A0, 0x9D42, + 0xF940, 0x9D43, 0xF941, 0x9D44, 0xF942, 0x9D45, 0xF943, 0x9D46, 0xF944, 0x9D47, 0xF945, 0x9D48, 0xF946, 0x9D49, 0xF947, 0x9D4A, + 0xF948, 0x9D4B, 0xF949, 0x9D4C, 0xF94A, 0x9D4D, 0xF94B, 0x9D4E, 0xF94C, 0x9D4F, 0xF94D, 0x9D50, 0xF94E, 0x9D51, 0xF94F, 0x9D52, + 0xF950, 0x9D53, 0xF951, 0x9D54, 0xF952, 0x9D55, 0xF953, 0x9D56, 0xF954, 0x9D57, 0xF955, 0x9D58, 0xF956, 0x9D59, 0xF957, 0x9D5A, + 0xF958, 0x9D5B, 0xF959, 0x9D5C, 0xF95A, 0x9D5D, 0xF95B, 0x9D5E, 0xF95C, 0x9D5F, 0xF95D, 0x9D60, 0xF95E, 0x9D61, 0xF95F, 0x9D62, + 0xF960, 0x9D63, 0xF961, 0x9D64, 0xF962, 0x9D65, 0xF963, 0x9D66, 0xF964, 0x9D67, 0xF965, 0x9D68, 0xF966, 0x9D69, 0xF967, 0x9D6A, + 0xF968, 0x9D6B, 0xF969, 0x9D6C, 0xF96A, 0x9D6D, 0xF96B, 0x9D6E, 0xF96C, 0x9D6F, 0xF96D, 0x9D70, 0xF96E, 0x9D71, 0xF96F, 0x9D72, + 0xF970, 0x9D73, 0xF971, 0x9D74, 0xF972, 0x9D75, 0xF973, 0x9D76, 0xF974, 0x9D77, 0xF975, 0x9D78, 0xF976, 0x9D79, 0xF977, 0x9D7A, + 0xF978, 0x9D7B, 0xF979, 0x9D7C, 0xF97A, 0x9D7D, 0xF97B, 0x9D7E, 0xF97C, 0x9D7F, 0xF97D, 0x9D80, 0xF97E, 0x9D81, 0xF980, 0x9D82, + 0xF981, 0x9D83, 0xF982, 0x9D84, 0xF983, 0x9D85, 0xF984, 0x9D86, 0xF985, 0x9D87, 0xF986, 0x9D88, 0xF987, 0x9D89, 0xF988, 0x9D8A, + 0xF989, 0x9D8B, 0xF98A, 0x9D8C, 0xF98B, 0x9D8D, 0xF98C, 0x9D8E, 0xF98D, 0x9D8F, 0xF98E, 0x9D90, 0xF98F, 0x9D91, 0xF990, 0x9D92, + 0xF991, 0x9D93, 0xF992, 0x9D94, 0xF993, 0x9D95, 0xF994, 0x9D96, 0xF995, 0x9D97, 0xF996, 0x9D98, 0xF997, 0x9D99, 0xF998, 0x9D9A, + 0xF999, 0x9D9B, 0xF99A, 0x9D9C, 0xF99B, 0x9D9D, 0xF99C, 0x9D9E, 0xF99D, 0x9D9F, 0xF99E, 0x9DA0, 0xF99F, 0x9DA1, 0xF9A0, 0x9DA2, + 0xFA40, 0x9DA3, 0xFA41, 0x9DA4, 0xFA42, 0x9DA5, 0xFA43, 0x9DA6, 0xFA44, 0x9DA7, 0xFA45, 0x9DA8, 0xFA46, 0x9DA9, 0xFA47, 0x9DAA, + 0xFA48, 0x9DAB, 0xFA49, 0x9DAC, 0xFA4A, 0x9DAD, 0xFA4B, 0x9DAE, 0xFA4C, 0x9DAF, 0xFA4D, 0x9DB0, 0xFA4E, 0x9DB1, 0xFA4F, 0x9DB2, + 0xFA50, 0x9DB3, 0xFA51, 0x9DB4, 0xFA52, 0x9DB5, 0xFA53, 0x9DB6, 0xFA54, 0x9DB7, 0xFA55, 0x9DB8, 0xFA56, 0x9DB9, 0xFA57, 0x9DBA, + 0xFA58, 0x9DBB, 0xFA59, 0x9DBC, 0xFA5A, 0x9DBD, 0xFA5B, 0x9DBE, 0xFA5C, 0x9DBF, 0xFA5D, 0x9DC0, 0xFA5E, 0x9DC1, 0xFA5F, 0x9DC2, + 0xFA60, 0x9DC3, 0xFA61, 0x9DC4, 0xFA62, 0x9DC5, 0xFA63, 0x9DC6, 0xFA64, 0x9DC7, 0xFA65, 0x9DC8, 0xFA66, 0x9DC9, 0xFA67, 0x9DCA, + 0xFA68, 0x9DCB, 0xFA69, 0x9DCC, 0xFA6A, 0x9DCD, 0xFA6B, 0x9DCE, 0xFA6C, 0x9DCF, 0xFA6D, 0x9DD0, 0xFA6E, 0x9DD1, 0xFA6F, 0x9DD2, + 0xFA70, 0x9DD3, 0xFA71, 0x9DD4, 0xFA72, 0x9DD5, 0xFA73, 0x9DD6, 0xFA74, 0x9DD7, 0xFA75, 0x9DD8, 0xFA76, 0x9DD9, 0xFA77, 0x9DDA, + 0xFA78, 0x9DDB, 0xFA79, 0x9DDC, 0xFA7A, 0x9DDD, 0xFA7B, 0x9DDE, 0xFA7C, 0x9DDF, 0xFA7D, 0x9DE0, 0xFA7E, 0x9DE1, 0xFA80, 0x9DE2, + 0xFA81, 0x9DE3, 0xFA82, 0x9DE4, 0xFA83, 0x9DE5, 0xFA84, 0x9DE6, 0xFA85, 0x9DE7, 0xFA86, 0x9DE8, 0xFA87, 0x9DE9, 0xFA88, 0x9DEA, + 0xFA89, 0x9DEB, 0xFA8A, 0x9DEC, 0xFA8B, 0x9DED, 0xFA8C, 0x9DEE, 0xFA8D, 0x9DEF, 0xFA8E, 0x9DF0, 0xFA8F, 0x9DF1, 0xFA90, 0x9DF2, + 0xFA91, 0x9DF3, 0xFA92, 0x9DF4, 0xFA93, 0x9DF5, 0xFA94, 0x9DF6, 0xFA95, 0x9DF7, 0xFA96, 0x9DF8, 0xFA97, 0x9DF9, 0xFA98, 0x9DFA, + 0xFA99, 0x9DFB, 0xFA9A, 0x9DFC, 0xFA9B, 0x9DFD, 0xFA9C, 0x9DFE, 0xFA9D, 0x9DFF, 0xFA9E, 0x9E00, 0xFA9F, 0x9E01, 0xFAA0, 0x9E02, + 0xFB40, 0x9E03, 0xFB41, 0x9E04, 0xFB42, 0x9E05, 0xFB43, 0x9E06, 0xFB44, 0x9E07, 0xFB45, 0x9E08, 0xFB46, 0x9E09, 0xFB47, 0x9E0A, + 0xFB48, 0x9E0B, 0xFB49, 0x9E0C, 0xFB4A, 0x9E0D, 0xFB4B, 0x9E0E, 0xFB4C, 0x9E0F, 0xFB4D, 0x9E10, 0xFB4E, 0x9E11, 0xFB4F, 0x9E12, + 0xFB50, 0x9E13, 0xFB51, 0x9E14, 0xFB52, 0x9E15, 0xFB53, 0x9E16, 0xFB54, 0x9E17, 0xFB55, 0x9E18, 0xFB56, 0x9E19, 0xFB57, 0x9E1A, + 0xFB58, 0x9E1B, 0xFB59, 0x9E1C, 0xFB5A, 0x9E1D, 0xFB5B, 0x9E1E, 0xFB5C, 0x9E24, 0xFB5D, 0x9E27, 0xFB5E, 0x9E2E, 0xFB5F, 0x9E30, + 0xFB60, 0x9E34, 0xFB61, 0x9E3B, 0xFB62, 0x9E3C, 0xFB63, 0x9E40, 0xFB64, 0x9E4D, 0xFB65, 0x9E50, 0xFB66, 0x9E52, 0xFB67, 0x9E53, + 0xFB68, 0x9E54, 0xFB69, 0x9E56, 0xFB6A, 0x9E59, 0xFB6B, 0x9E5D, 0xFB6C, 0x9E5F, 0xFB6D, 0x9E60, 0xFB6E, 0x9E61, 0xFB6F, 0x9E62, + 0xFB70, 0x9E65, 0xFB71, 0x9E6E, 0xFB72, 0x9E6F, 0xFB73, 0x9E72, 0xFB74, 0x9E74, 0xFB75, 0x9E75, 0xFB76, 0x9E76, 0xFB77, 0x9E77, + 0xFB78, 0x9E78, 0xFB79, 0x9E79, 0xFB7A, 0x9E7A, 0xFB7B, 0x9E7B, 0xFB7C, 0x9E7C, 0xFB7D, 0x9E7D, 0xFB7E, 0x9E80, 0xFB80, 0x9E81, + 0xFB81, 0x9E83, 0xFB82, 0x9E84, 0xFB83, 0x9E85, 0xFB84, 0x9E86, 0xFB85, 0x9E89, 0xFB86, 0x9E8A, 0xFB87, 0x9E8C, 0xFB88, 0x9E8D, + 0xFB89, 0x9E8E, 0xFB8A, 0x9E8F, 0xFB8B, 0x9E90, 0xFB8C, 0x9E91, 0xFB8D, 0x9E94, 0xFB8E, 0x9E95, 0xFB8F, 0x9E96, 0xFB90, 0x9E97, + 0xFB91, 0x9E98, 0xFB92, 0x9E99, 0xFB93, 0x9E9A, 0xFB94, 0x9E9B, 0xFB95, 0x9E9C, 0xFB96, 0x9E9E, 0xFB97, 0x9EA0, 0xFB98, 0x9EA1, + 0xFB99, 0x9EA2, 0xFB9A, 0x9EA3, 0xFB9B, 0x9EA4, 0xFB9C, 0x9EA5, 0xFB9D, 0x9EA7, 0xFB9E, 0x9EA8, 0xFB9F, 0x9EA9, 0xFBA0, 0x9EAA, + 0xFC40, 0x9EAB, 0xFC41, 0x9EAC, 0xFC42, 0x9EAD, 0xFC43, 0x9EAE, 0xFC44, 0x9EAF, 0xFC45, 0x9EB0, 0xFC46, 0x9EB1, 0xFC47, 0x9EB2, + 0xFC48, 0x9EB3, 0xFC49, 0x9EB5, 0xFC4A, 0x9EB6, 0xFC4B, 0x9EB7, 0xFC4C, 0x9EB9, 0xFC4D, 0x9EBA, 0xFC4E, 0x9EBC, 0xFC4F, 0x9EBF, + 0xFC50, 0x9EC0, 0xFC51, 0x9EC1, 0xFC52, 0x9EC2, 0xFC53, 0x9EC3, 0xFC54, 0x9EC5, 0xFC55, 0x9EC6, 0xFC56, 0x9EC7, 0xFC57, 0x9EC8, + 0xFC58, 0x9ECA, 0xFC59, 0x9ECB, 0xFC5A, 0x9ECC, 0xFC5B, 0x9ED0, 0xFC5C, 0x9ED2, 0xFC5D, 0x9ED3, 0xFC5E, 0x9ED5, 0xFC5F, 0x9ED6, + 0xFC60, 0x9ED7, 0xFC61, 0x9ED9, 0xFC62, 0x9EDA, 0xFC63, 0x9EDE, 0xFC64, 0x9EE1, 0xFC65, 0x9EE3, 0xFC66, 0x9EE4, 0xFC67, 0x9EE6, + 0xFC68, 0x9EE8, 0xFC69, 0x9EEB, 0xFC6A, 0x9EEC, 0xFC6B, 0x9EED, 0xFC6C, 0x9EEE, 0xFC6D, 0x9EF0, 0xFC6E, 0x9EF1, 0xFC6F, 0x9EF2, + 0xFC70, 0x9EF3, 0xFC71, 0x9EF4, 0xFC72, 0x9EF5, 0xFC73, 0x9EF6, 0xFC74, 0x9EF7, 0xFC75, 0x9EF8, 0xFC76, 0x9EFA, 0xFC77, 0x9EFD, + 0xFC78, 0x9EFF, 0xFC79, 0x9F00, 0xFC7A, 0x9F01, 0xFC7B, 0x9F02, 0xFC7C, 0x9F03, 0xFC7D, 0x9F04, 0xFC7E, 0x9F05, 0xFC80, 0x9F06, + 0xFC81, 0x9F07, 0xFC82, 0x9F08, 0xFC83, 0x9F09, 0xFC84, 0x9F0A, 0xFC85, 0x9F0C, 0xFC86, 0x9F0F, 0xFC87, 0x9F11, 0xFC88, 0x9F12, + 0xFC89, 0x9F14, 0xFC8A, 0x9F15, 0xFC8B, 0x9F16, 0xFC8C, 0x9F18, 0xFC8D, 0x9F1A, 0xFC8E, 0x9F1B, 0xFC8F, 0x9F1C, 0xFC90, 0x9F1D, + 0xFC91, 0x9F1E, 0xFC92, 0x9F1F, 0xFC93, 0x9F21, 0xFC94, 0x9F23, 0xFC95, 0x9F24, 0xFC96, 0x9F25, 0xFC97, 0x9F26, 0xFC98, 0x9F27, + 0xFC99, 0x9F28, 0xFC9A, 0x9F29, 0xFC9B, 0x9F2A, 0xFC9C, 0x9F2B, 0xFC9D, 0x9F2D, 0xFC9E, 0x9F2E, 0xFC9F, 0x9F30, 0xFCA0, 0x9F31, + 0xFD40, 0x9F32, 0xFD41, 0x9F33, 0xFD42, 0x9F34, 0xFD43, 0x9F35, 0xFD44, 0x9F36, 0xFD45, 0x9F38, 0xFD46, 0x9F3A, 0xFD47, 0x9F3C, + 0xFD48, 0x9F3F, 0xFD49, 0x9F40, 0xFD4A, 0x9F41, 0xFD4B, 0x9F42, 0xFD4C, 0x9F43, 0xFD4D, 0x9F45, 0xFD4E, 0x9F46, 0xFD4F, 0x9F47, + 0xFD50, 0x9F48, 0xFD51, 0x9F49, 0xFD52, 0x9F4A, 0xFD53, 0x9F4B, 0xFD54, 0x9F4C, 0xFD55, 0x9F4D, 0xFD56, 0x9F4E, 0xFD57, 0x9F4F, + 0xFD58, 0x9F52, 0xFD59, 0x9F53, 0xFD5A, 0x9F54, 0xFD5B, 0x9F55, 0xFD5C, 0x9F56, 0xFD5D, 0x9F57, 0xFD5E, 0x9F58, 0xFD5F, 0x9F59, + 0xFD60, 0x9F5A, 0xFD61, 0x9F5B, 0xFD62, 0x9F5C, 0xFD63, 0x9F5D, 0xFD64, 0x9F5E, 0xFD65, 0x9F5F, 0xFD66, 0x9F60, 0xFD67, 0x9F61, + 0xFD68, 0x9F62, 0xFD69, 0x9F63, 0xFD6A, 0x9F64, 0xFD6B, 0x9F65, 0xFD6C, 0x9F66, 0xFD6D, 0x9F67, 0xFD6E, 0x9F68, 0xFD6F, 0x9F69, + 0xFD70, 0x9F6A, 0xFD71, 0x9F6B, 0xFD72, 0x9F6C, 0xFD73, 0x9F6D, 0xFD74, 0x9F6E, 0xFD75, 0x9F6F, 0xFD76, 0x9F70, 0xFD77, 0x9F71, + 0xFD78, 0x9F72, 0xFD79, 0x9F73, 0xFD7A, 0x9F74, 0xFD7B, 0x9F75, 0xFD7C, 0x9F76, 0xFD7D, 0x9F77, 0xFD7E, 0x9F78, 0xFD80, 0x9F79, + 0xFD81, 0x9F7A, 0xFD82, 0x9F7B, 0xFD83, 0x9F7C, 0xFD84, 0x9F7D, 0xFD85, 0x9F7E, 0xFD86, 0x9F81, 0xFD87, 0x9F82, 0xFD88, 0x9F8D, + 0xFD89, 0x9F8E, 0xFD8A, 0x9F8F, 0xFD8B, 0x9F90, 0xFD8C, 0x9F91, 0xFD8D, 0x9F92, 0xFD8E, 0x9F93, 0xFD8F, 0x9F94, 0xFD90, 0x9F95, + 0xFD91, 0x9F96, 0xFD92, 0x9F97, 0xFD93, 0x9F98, 0xFD94, 0x9F9C, 0xFD95, 0x9F9D, 0xFD96, 0x9F9E, 0xFD97, 0x9FA1, 0xFD98, 0x9FA2, + 0xFD99, 0x9FA3, 0xFD9A, 0x9FA4, 0xFD9B, 0x9FA5, 0xFD9C, 0xF92C, 0xFD9D, 0xF979, 0xFD9E, 0xF995, 0xFD9F, 0xF9E7, 0xFDA0, 0xF9F1, + 0xFE40, 0xFA0C, 0xFE41, 0xFA0D, 0xFE42, 0xFA0E, 0xFE43, 0xFA0F, 0xFE44, 0xFA11, 0xFE45, 0xFA13, 0xFE46, 0xFA14, 0xFE47, 0xFA18, + 0xFE48, 0xFA1F, 0xFE49, 0xFA20, 0xFE4A, 0xFA21, 0xFE4B, 0xFA23, 0xFE4C, 0xFA24, 0xFE4D, 0xFA27, 0xFE4E, 0xFA28, 0xFE4F, 0xFA29, + 0, 0 +}; +#endif + +#if FF_CODE_PAGE == 949 || FF_CODE_PAGE == 0 /* Korean */ +static const WCHAR uni2oem949[] = { /* Unicode --> Korean pairs */ + 0x00A1, 0xA2AE, 0x00A4, 0xA2B4, 0x00A7, 0xA1D7, 0x00A8, 0xA1A7, 0x00AA, 0xA8A3, 0x00AD, 0xA1A9, 0x00AE, 0xA2E7, 0x00B0, 0xA1C6, + 0x00B1, 0xA1BE, 0x00B2, 0xA9F7, 0x00B3, 0xA9F8, 0x00B4, 0xA2A5, 0x00B6, 0xA2D2, 0x00B7, 0xA1A4, 0x00B8, 0xA2AC, 0x00B9, 0xA9F6, + 0x00BA, 0xA8AC, 0x00BC, 0xA8F9, 0x00BD, 0xA8F6, 0x00BE, 0xA8FA, 0x00BF, 0xA2AF, 0x00C6, 0xA8A1, 0x00D0, 0xA8A2, 0x00D7, 0xA1BF, + 0x00D8, 0xA8AA, 0x00DE, 0xA8AD, 0x00DF, 0xA9AC, 0x00E6, 0xA9A1, 0x00F0, 0xA9A3, 0x00F7, 0xA1C0, 0x00F8, 0xA9AA, 0x00FE, 0xA9AD, + 0x0111, 0xA9A2, 0x0126, 0xA8A4, 0x0127, 0xA9A4, 0x0131, 0xA9A5, 0x0132, 0xA8A6, 0x0133, 0xA9A6, 0x0138, 0xA9A7, 0x013F, 0xA8A8, + 0x0140, 0xA9A8, 0x0141, 0xA8A9, 0x0142, 0xA9A9, 0x0149, 0xA9B0, 0x014A, 0xA8AF, 0x014B, 0xA9AF, 0x0152, 0xA8AB, 0x0153, 0xA9AB, + 0x0166, 0xA8AE, 0x0167, 0xA9AE, 0x02C7, 0xA2A7, 0x02D0, 0xA2B0, 0x02D8, 0xA2A8, 0x02D9, 0xA2AB, 0x02DA, 0xA2AA, 0x02DB, 0xA2AD, + 0x02DD, 0xA2A9, 0x0391, 0xA5C1, 0x0392, 0xA5C2, 0x0393, 0xA5C3, 0x0394, 0xA5C4, 0x0395, 0xA5C5, 0x0396, 0xA5C6, 0x0397, 0xA5C7, + 0x0398, 0xA5C8, 0x0399, 0xA5C9, 0x039A, 0xA5CA, 0x039B, 0xA5CB, 0x039C, 0xA5CC, 0x039D, 0xA5CD, 0x039E, 0xA5CE, 0x039F, 0xA5CF, + 0x03A0, 0xA5D0, 0x03A1, 0xA5D1, 0x03A3, 0xA5D2, 0x03A4, 0xA5D3, 0x03A5, 0xA5D4, 0x03A6, 0xA5D5, 0x03A7, 0xA5D6, 0x03A8, 0xA5D7, + 0x03A9, 0xA5D8, 0x03B1, 0xA5E1, 0x03B2, 0xA5E2, 0x03B3, 0xA5E3, 0x03B4, 0xA5E4, 0x03B5, 0xA5E5, 0x03B6, 0xA5E6, 0x03B7, 0xA5E7, + 0x03B8, 0xA5E8, 0x03B9, 0xA5E9, 0x03BA, 0xA5EA, 0x03BB, 0xA5EB, 0x03BC, 0xA5EC, 0x03BD, 0xA5ED, 0x03BE, 0xA5EE, 0x03BF, 0xA5EF, + 0x03C0, 0xA5F0, 0x03C1, 0xA5F1, 0x03C3, 0xA5F2, 0x03C4, 0xA5F3, 0x03C5, 0xA5F4, 0x03C6, 0xA5F5, 0x03C7, 0xA5F6, 0x03C8, 0xA5F7, + 0x03C9, 0xA5F8, 0x0401, 0xACA7, 0x0410, 0xACA1, 0x0411, 0xACA2, 0x0412, 0xACA3, 0x0413, 0xACA4, 0x0414, 0xACA5, 0x0415, 0xACA6, + 0x0416, 0xACA8, 0x0417, 0xACA9, 0x0418, 0xACAA, 0x0419, 0xACAB, 0x041A, 0xACAC, 0x041B, 0xACAD, 0x041C, 0xACAE, 0x041D, 0xACAF, + 0x041E, 0xACB0, 0x041F, 0xACB1, 0x0420, 0xACB2, 0x0421, 0xACB3, 0x0422, 0xACB4, 0x0423, 0xACB5, 0x0424, 0xACB6, 0x0425, 0xACB7, + 0x0426, 0xACB8, 0x0427, 0xACB9, 0x0428, 0xACBA, 0x0429, 0xACBB, 0x042A, 0xACBC, 0x042B, 0xACBD, 0x042C, 0xACBE, 0x042D, 0xACBF, + 0x042E, 0xACC0, 0x042F, 0xACC1, 0x0430, 0xACD1, 0x0431, 0xACD2, 0x0432, 0xACD3, 0x0433, 0xACD4, 0x0434, 0xACD5, 0x0435, 0xACD6, + 0x0436, 0xACD8, 0x0437, 0xACD9, 0x0438, 0xACDA, 0x0439, 0xACDB, 0x043A, 0xACDC, 0x043B, 0xACDD, 0x043C, 0xACDE, 0x043D, 0xACDF, + 0x043E, 0xACE0, 0x043F, 0xACE1, 0x0440, 0xACE2, 0x0441, 0xACE3, 0x0442, 0xACE4, 0x0443, 0xACE5, 0x0444, 0xACE6, 0x0445, 0xACE7, + 0x0446, 0xACE8, 0x0447, 0xACE9, 0x0448, 0xACEA, 0x0449, 0xACEB, 0x044A, 0xACEC, 0x044B, 0xACED, 0x044C, 0xACEE, 0x044D, 0xACEF, + 0x044E, 0xACF0, 0x044F, 0xACF1, 0x0451, 0xACD7, 0x2015, 0xA1AA, 0x2018, 0xA1AE, 0x2019, 0xA1AF, 0x201C, 0xA1B0, 0x201D, 0xA1B1, + 0x2020, 0xA2D3, 0x2021, 0xA2D4, 0x2025, 0xA1A5, 0x2026, 0xA1A6, 0x2030, 0xA2B6, 0x2032, 0xA1C7, 0x2033, 0xA1C8, 0x203B, 0xA1D8, + 0x2074, 0xA9F9, 0x207F, 0xA9FA, 0x2081, 0xA9FB, 0x2082, 0xA9FC, 0x2083, 0xA9FD, 0x2084, 0xA9FE, 0x20AC, 0xA2E6, 0x2103, 0xA1C9, + 0x2109, 0xA2B5, 0x2113, 0xA7A4, 0x2116, 0xA2E0, 0x2121, 0xA2E5, 0x2122, 0xA2E2, 0x2126, 0xA7D9, 0x212B, 0xA1CA, 0x2153, 0xA8F7, + 0x2154, 0xA8F8, 0x215B, 0xA8FB, 0x215C, 0xA8FC, 0x215D, 0xA8FD, 0x215E, 0xA8FE, 0x2160, 0xA5B0, 0x2161, 0xA5B1, 0x2162, 0xA5B2, + 0x2163, 0xA5B3, 0x2164, 0xA5B4, 0x2165, 0xA5B5, 0x2166, 0xA5B6, 0x2167, 0xA5B7, 0x2168, 0xA5B8, 0x2169, 0xA5B9, 0x2170, 0xA5A1, + 0x2171, 0xA5A2, 0x2172, 0xA5A3, 0x2173, 0xA5A4, 0x2174, 0xA5A5, 0x2175, 0xA5A6, 0x2176, 0xA5A7, 0x2177, 0xA5A8, 0x2178, 0xA5A9, + 0x2179, 0xA5AA, 0x2190, 0xA1E7, 0x2191, 0xA1E8, 0x2192, 0xA1E6, 0x2193, 0xA1E9, 0x2194, 0xA1EA, 0x2195, 0xA2D5, 0x2196, 0xA2D8, + 0x2197, 0xA2D6, 0x2198, 0xA2D9, 0x2199, 0xA2D7, 0x21D2, 0xA2A1, 0x21D4, 0xA2A2, 0x2200, 0xA2A3, 0x2202, 0xA1D3, 0x2203, 0xA2A4, + 0x2207, 0xA1D4, 0x2208, 0xA1F4, 0x220B, 0xA1F5, 0x220F, 0xA2B3, 0x2211, 0xA2B2, 0x221A, 0xA1EE, 0x221D, 0xA1F0, 0x221E, 0xA1C4, + 0x2220, 0xA1D0, 0x2225, 0xA1AB, 0x2227, 0xA1FC, 0x2228, 0xA1FD, 0x2229, 0xA1FB, 0x222A, 0xA1FA, 0x222B, 0xA1F2, 0x222C, 0xA1F3, + 0x222E, 0xA2B1, 0x2234, 0xA1C5, 0x2235, 0xA1F1, 0x223C, 0xA1AD, 0x223D, 0xA1EF, 0x2252, 0xA1D6, 0x2260, 0xA1C1, 0x2261, 0xA1D5, + 0x2264, 0xA1C2, 0x2265, 0xA1C3, 0x226A, 0xA1EC, 0x226B, 0xA1ED, 0x2282, 0xA1F8, 0x2283, 0xA1F9, 0x2286, 0xA1F6, 0x2287, 0xA1F7, + 0x2299, 0xA2C1, 0x22A5, 0xA1D1, 0x2312, 0xA1D2, 0x2460, 0xA8E7, 0x2461, 0xA8E8, 0x2462, 0xA8E9, 0x2463, 0xA8EA, 0x2464, 0xA8EB, + 0x2465, 0xA8EC, 0x2466, 0xA8ED, 0x2467, 0xA8EE, 0x2468, 0xA8EF, 0x2469, 0xA8F0, 0x246A, 0xA8F1, 0x246B, 0xA8F2, 0x246C, 0xA8F3, + 0x246D, 0xA8F4, 0x246E, 0xA8F5, 0x2474, 0xA9E7, 0x2475, 0xA9E8, 0x2476, 0xA9E9, 0x2477, 0xA9EA, 0x2478, 0xA9EB, 0x2479, 0xA9EC, + 0x247A, 0xA9ED, 0x247B, 0xA9EE, 0x247C, 0xA9EF, 0x247D, 0xA9F0, 0x247E, 0xA9F1, 0x247F, 0xA9F2, 0x2480, 0xA9F3, 0x2481, 0xA9F4, + 0x2482, 0xA9F5, 0x249C, 0xA9CD, 0x249D, 0xA9CE, 0x249E, 0xA9CF, 0x249F, 0xA9D0, 0x24A0, 0xA9D1, 0x24A1, 0xA9D2, 0x24A2, 0xA9D3, + 0x24A3, 0xA9D4, 0x24A4, 0xA9D5, 0x24A5, 0xA9D6, 0x24A6, 0xA9D7, 0x24A7, 0xA9D8, 0x24A8, 0xA9D9, 0x24A9, 0xA9DA, 0x24AA, 0xA9DB, + 0x24AB, 0xA9DC, 0x24AC, 0xA9DD, 0x24AD, 0xA9DE, 0x24AE, 0xA9DF, 0x24AF, 0xA9E0, 0x24B0, 0xA9E1, 0x24B1, 0xA9E2, 0x24B2, 0xA9E3, + 0x24B3, 0xA9E4, 0x24B4, 0xA9E5, 0x24B5, 0xA9E6, 0x24D0, 0xA8CD, 0x24D1, 0xA8CE, 0x24D2, 0xA8CF, 0x24D3, 0xA8D0, 0x24D4, 0xA8D1, + 0x24D5, 0xA8D2, 0x24D6, 0xA8D3, 0x24D7, 0xA8D4, 0x24D8, 0xA8D5, 0x24D9, 0xA8D6, 0x24DA, 0xA8D7, 0x24DB, 0xA8D8, 0x24DC, 0xA8D9, + 0x24DD, 0xA8DA, 0x24DE, 0xA8DB, 0x24DF, 0xA8DC, 0x24E0, 0xA8DD, 0x24E1, 0xA8DE, 0x24E2, 0xA8DF, 0x24E3, 0xA8E0, 0x24E4, 0xA8E1, + 0x24E5, 0xA8E2, 0x24E6, 0xA8E3, 0x24E7, 0xA8E4, 0x24E8, 0xA8E5, 0x24E9, 0xA8E6, 0x2500, 0xA6A1, 0x2501, 0xA6AC, 0x2502, 0xA6A2, + 0x2503, 0xA6AD, 0x250C, 0xA6A3, 0x250D, 0xA6C8, 0x250E, 0xA6C7, 0x250F, 0xA6AE, 0x2510, 0xA6A4, 0x2511, 0xA6C2, 0x2512, 0xA6C1, + 0x2513, 0xA6AF, 0x2514, 0xA6A6, 0x2515, 0xA6C6, 0x2516, 0xA6C5, 0x2517, 0xA6B1, 0x2518, 0xA6A5, 0x2519, 0xA6C4, 0x251A, 0xA6C3, + 0x251B, 0xA6B0, 0x251C, 0xA6A7, 0x251D, 0xA6BC, 0x251E, 0xA6C9, 0x251F, 0xA6CA, 0x2520, 0xA6B7, 0x2521, 0xA6CB, 0x2522, 0xA6CC, + 0x2523, 0xA6B2, 0x2524, 0xA6A9, 0x2525, 0xA6BE, 0x2526, 0xA6CD, 0x2527, 0xA6CE, 0x2528, 0xA6B9, 0x2529, 0xA6CF, 0x252A, 0xA6D0, + 0x252B, 0xA6B4, 0x252C, 0xA6A8, 0x252D, 0xA6D1, 0x252E, 0xA6D2, 0x252F, 0xA6B8, 0x2530, 0xA6BD, 0x2531, 0xA6D3, 0x2532, 0xA6D4, + 0x2533, 0xA6B3, 0x2534, 0xA6AA, 0x2535, 0xA6D5, 0x2536, 0xA6D6, 0x2537, 0xA6BA, 0x2538, 0xA6BF, 0x2539, 0xA6D7, 0x253A, 0xA6D8, + 0x253B, 0xA6B5, 0x253C, 0xA6AB, 0x253D, 0xA6D9, 0x253E, 0xA6DA, 0x253F, 0xA6BB, 0x2540, 0xA6DB, 0x2541, 0xA6DC, 0x2542, 0xA6C0, + 0x2543, 0xA6DD, 0x2544, 0xA6DE, 0x2545, 0xA6DF, 0x2546, 0xA6E0, 0x2547, 0xA6E1, 0x2548, 0xA6E2, 0x2549, 0xA6E3, 0x254A, 0xA6E4, + 0x254B, 0xA6B6, 0x2592, 0xA2C6, 0x25A0, 0xA1E1, 0x25A1, 0xA1E0, 0x25A3, 0xA2C3, 0x25A4, 0xA2C7, 0x25A5, 0xA2C8, 0x25A6, 0xA2CB, + 0x25A7, 0xA2CA, 0x25A8, 0xA2C9, 0x25A9, 0xA2CC, 0x25B2, 0xA1E3, 0x25B3, 0xA1E2, 0x25B6, 0xA2BA, 0x25B7, 0xA2B9, 0x25BC, 0xA1E5, + 0x25BD, 0xA1E4, 0x25C0, 0xA2B8, 0x25C1, 0xA2B7, 0x25C6, 0xA1DF, 0x25C7, 0xA1DE, 0x25C8, 0xA2C2, 0x25CB, 0xA1DB, 0x25CE, 0xA1DD, + 0x25CF, 0xA1DC, 0x25D0, 0xA2C4, 0x25D1, 0xA2C5, 0x2605, 0xA1DA, 0x2606, 0xA1D9, 0x260E, 0xA2CF, 0x260F, 0xA2CE, 0x261C, 0xA2D0, + 0x261E, 0xA2D1, 0x2640, 0xA1CF, 0x2642, 0xA1CE, 0x2660, 0xA2BC, 0x2661, 0xA2BD, 0x2663, 0xA2C0, 0x2664, 0xA2BB, 0x2665, 0xA2BE, + 0x2667, 0xA2BF, 0x2668, 0xA2CD, 0x2669, 0xA2DB, 0x266A, 0xA2DC, 0x266C, 0xA2DD, 0x266D, 0xA2DA, 0x3000, 0xA1A1, 0x3001, 0xA1A2, + 0x3002, 0xA1A3, 0x3003, 0xA1A8, 0x3008, 0xA1B4, 0x3009, 0xA1B5, 0x300A, 0xA1B6, 0x300B, 0xA1B7, 0x300C, 0xA1B8, 0x300D, 0xA1B9, + 0x300E, 0xA1BA, 0x300F, 0xA1BB, 0x3010, 0xA1BC, 0x3011, 0xA1BD, 0x3013, 0xA1EB, 0x3014, 0xA1B2, 0x3015, 0xA1B3, 0x3041, 0xAAA1, + 0x3042, 0xAAA2, 0x3043, 0xAAA3, 0x3044, 0xAAA4, 0x3045, 0xAAA5, 0x3046, 0xAAA6, 0x3047, 0xAAA7, 0x3048, 0xAAA8, 0x3049, 0xAAA9, + 0x304A, 0xAAAA, 0x304B, 0xAAAB, 0x304C, 0xAAAC, 0x304D, 0xAAAD, 0x304E, 0xAAAE, 0x304F, 0xAAAF, 0x3050, 0xAAB0, 0x3051, 0xAAB1, + 0x3052, 0xAAB2, 0x3053, 0xAAB3, 0x3054, 0xAAB4, 0x3055, 0xAAB5, 0x3056, 0xAAB6, 0x3057, 0xAAB7, 0x3058, 0xAAB8, 0x3059, 0xAAB9, + 0x305A, 0xAABA, 0x305B, 0xAABB, 0x305C, 0xAABC, 0x305D, 0xAABD, 0x305E, 0xAABE, 0x305F, 0xAABF, 0x3060, 0xAAC0, 0x3061, 0xAAC1, + 0x3062, 0xAAC2, 0x3063, 0xAAC3, 0x3064, 0xAAC4, 0x3065, 0xAAC5, 0x3066, 0xAAC6, 0x3067, 0xAAC7, 0x3068, 0xAAC8, 0x3069, 0xAAC9, + 0x306A, 0xAACA, 0x306B, 0xAACB, 0x306C, 0xAACC, 0x306D, 0xAACD, 0x306E, 0xAACE, 0x306F, 0xAACF, 0x3070, 0xAAD0, 0x3071, 0xAAD1, + 0x3072, 0xAAD2, 0x3073, 0xAAD3, 0x3074, 0xAAD4, 0x3075, 0xAAD5, 0x3076, 0xAAD6, 0x3077, 0xAAD7, 0x3078, 0xAAD8, 0x3079, 0xAAD9, + 0x307A, 0xAADA, 0x307B, 0xAADB, 0x307C, 0xAADC, 0x307D, 0xAADD, 0x307E, 0xAADE, 0x307F, 0xAADF, 0x3080, 0xAAE0, 0x3081, 0xAAE1, + 0x3082, 0xAAE2, 0x3083, 0xAAE3, 0x3084, 0xAAE4, 0x3085, 0xAAE5, 0x3086, 0xAAE6, 0x3087, 0xAAE7, 0x3088, 0xAAE8, 0x3089, 0xAAE9, + 0x308A, 0xAAEA, 0x308B, 0xAAEB, 0x308C, 0xAAEC, 0x308D, 0xAAED, 0x308E, 0xAAEE, 0x308F, 0xAAEF, 0x3090, 0xAAF0, 0x3091, 0xAAF1, + 0x3092, 0xAAF2, 0x3093, 0xAAF3, 0x30A1, 0xABA1, 0x30A2, 0xABA2, 0x30A3, 0xABA3, 0x30A4, 0xABA4, 0x30A5, 0xABA5, 0x30A6, 0xABA6, + 0x30A7, 0xABA7, 0x30A8, 0xABA8, 0x30A9, 0xABA9, 0x30AA, 0xABAA, 0x30AB, 0xABAB, 0x30AC, 0xABAC, 0x30AD, 0xABAD, 0x30AE, 0xABAE, + 0x30AF, 0xABAF, 0x30B0, 0xABB0, 0x30B1, 0xABB1, 0x30B2, 0xABB2, 0x30B3, 0xABB3, 0x30B4, 0xABB4, 0x30B5, 0xABB5, 0x30B6, 0xABB6, + 0x30B7, 0xABB7, 0x30B8, 0xABB8, 0x30B9, 0xABB9, 0x30BA, 0xABBA, 0x30BB, 0xABBB, 0x30BC, 0xABBC, 0x30BD, 0xABBD, 0x30BE, 0xABBE, + 0x30BF, 0xABBF, 0x30C0, 0xABC0, 0x30C1, 0xABC1, 0x30C2, 0xABC2, 0x30C3, 0xABC3, 0x30C4, 0xABC4, 0x30C5, 0xABC5, 0x30C6, 0xABC6, + 0x30C7, 0xABC7, 0x30C8, 0xABC8, 0x30C9, 0xABC9, 0x30CA, 0xABCA, 0x30CB, 0xABCB, 0x30CC, 0xABCC, 0x30CD, 0xABCD, 0x30CE, 0xABCE, + 0x30CF, 0xABCF, 0x30D0, 0xABD0, 0x30D1, 0xABD1, 0x30D2, 0xABD2, 0x30D3, 0xABD3, 0x30D4, 0xABD4, 0x30D5, 0xABD5, 0x30D6, 0xABD6, + 0x30D7, 0xABD7, 0x30D8, 0xABD8, 0x30D9, 0xABD9, 0x30DA, 0xABDA, 0x30DB, 0xABDB, 0x30DC, 0xABDC, 0x30DD, 0xABDD, 0x30DE, 0xABDE, + 0x30DF, 0xABDF, 0x30E0, 0xABE0, 0x30E1, 0xABE1, 0x30E2, 0xABE2, 0x30E3, 0xABE3, 0x30E4, 0xABE4, 0x30E5, 0xABE5, 0x30E6, 0xABE6, + 0x30E7, 0xABE7, 0x30E8, 0xABE8, 0x30E9, 0xABE9, 0x30EA, 0xABEA, 0x30EB, 0xABEB, 0x30EC, 0xABEC, 0x30ED, 0xABED, 0x30EE, 0xABEE, + 0x30EF, 0xABEF, 0x30F0, 0xABF0, 0x30F1, 0xABF1, 0x30F2, 0xABF2, 0x30F3, 0xABF3, 0x30F4, 0xABF4, 0x30F5, 0xABF5, 0x30F6, 0xABF6, + 0x3131, 0xA4A1, 0x3132, 0xA4A2, 0x3133, 0xA4A3, 0x3134, 0xA4A4, 0x3135, 0xA4A5, 0x3136, 0xA4A6, 0x3137, 0xA4A7, 0x3138, 0xA4A8, + 0x3139, 0xA4A9, 0x313A, 0xA4AA, 0x313B, 0xA4AB, 0x313C, 0xA4AC, 0x313D, 0xA4AD, 0x313E, 0xA4AE, 0x313F, 0xA4AF, 0x3140, 0xA4B0, + 0x3141, 0xA4B1, 0x3142, 0xA4B2, 0x3143, 0xA4B3, 0x3144, 0xA4B4, 0x3145, 0xA4B5, 0x3146, 0xA4B6, 0x3147, 0xA4B7, 0x3148, 0xA4B8, + 0x3149, 0xA4B9, 0x314A, 0xA4BA, 0x314B, 0xA4BB, 0x314C, 0xA4BC, 0x314D, 0xA4BD, 0x314E, 0xA4BE, 0x314F, 0xA4BF, 0x3150, 0xA4C0, + 0x3151, 0xA4C1, 0x3152, 0xA4C2, 0x3153, 0xA4C3, 0x3154, 0xA4C4, 0x3155, 0xA4C5, 0x3156, 0xA4C6, 0x3157, 0xA4C7, 0x3158, 0xA4C8, + 0x3159, 0xA4C9, 0x315A, 0xA4CA, 0x315B, 0xA4CB, 0x315C, 0xA4CC, 0x315D, 0xA4CD, 0x315E, 0xA4CE, 0x315F, 0xA4CF, 0x3160, 0xA4D0, + 0x3161, 0xA4D1, 0x3162, 0xA4D2, 0x3163, 0xA4D3, 0x3164, 0xA4D4, 0x3165, 0xA4D5, 0x3166, 0xA4D6, 0x3167, 0xA4D7, 0x3168, 0xA4D8, + 0x3169, 0xA4D9, 0x316A, 0xA4DA, 0x316B, 0xA4DB, 0x316C, 0xA4DC, 0x316D, 0xA4DD, 0x316E, 0xA4DE, 0x316F, 0xA4DF, 0x3170, 0xA4E0, + 0x3171, 0xA4E1, 0x3172, 0xA4E2, 0x3173, 0xA4E3, 0x3174, 0xA4E4, 0x3175, 0xA4E5, 0x3176, 0xA4E6, 0x3177, 0xA4E7, 0x3178, 0xA4E8, + 0x3179, 0xA4E9, 0x317A, 0xA4EA, 0x317B, 0xA4EB, 0x317C, 0xA4EC, 0x317D, 0xA4ED, 0x317E, 0xA4EE, 0x317F, 0xA4EF, 0x3180, 0xA4F0, + 0x3181, 0xA4F1, 0x3182, 0xA4F2, 0x3183, 0xA4F3, 0x3184, 0xA4F4, 0x3185, 0xA4F5, 0x3186, 0xA4F6, 0x3187, 0xA4F7, 0x3188, 0xA4F8, + 0x3189, 0xA4F9, 0x318A, 0xA4FA, 0x318B, 0xA4FB, 0x318C, 0xA4FC, 0x318D, 0xA4FD, 0x318E, 0xA4FE, 0x3200, 0xA9B1, 0x3201, 0xA9B2, + 0x3202, 0xA9B3, 0x3203, 0xA9B4, 0x3204, 0xA9B5, 0x3205, 0xA9B6, 0x3206, 0xA9B7, 0x3207, 0xA9B8, 0x3208, 0xA9B9, 0x3209, 0xA9BA, + 0x320A, 0xA9BB, 0x320B, 0xA9BC, 0x320C, 0xA9BD, 0x320D, 0xA9BE, 0x320E, 0xA9BF, 0x320F, 0xA9C0, 0x3210, 0xA9C1, 0x3211, 0xA9C2, + 0x3212, 0xA9C3, 0x3213, 0xA9C4, 0x3214, 0xA9C5, 0x3215, 0xA9C6, 0x3216, 0xA9C7, 0x3217, 0xA9C8, 0x3218, 0xA9C9, 0x3219, 0xA9CA, + 0x321A, 0xA9CB, 0x321B, 0xA9CC, 0x321C, 0xA2DF, 0x3260, 0xA8B1, 0x3261, 0xA8B2, 0x3262, 0xA8B3, 0x3263, 0xA8B4, 0x3264, 0xA8B5, + 0x3265, 0xA8B6, 0x3266, 0xA8B7, 0x3267, 0xA8B8, 0x3268, 0xA8B9, 0x3269, 0xA8BA, 0x326A, 0xA8BB, 0x326B, 0xA8BC, 0x326C, 0xA8BD, + 0x326D, 0xA8BE, 0x326E, 0xA8BF, 0x326F, 0xA8C0, 0x3270, 0xA8C1, 0x3271, 0xA8C2, 0x3272, 0xA8C3, 0x3273, 0xA8C4, 0x3274, 0xA8C5, + 0x3275, 0xA8C6, 0x3276, 0xA8C7, 0x3277, 0xA8C8, 0x3278, 0xA8C9, 0x3279, 0xA8CA, 0x327A, 0xA8CB, 0x327B, 0xA8CC, 0x327F, 0xA2DE, + 0x3380, 0xA7C9, 0x3381, 0xA7CA, 0x3382, 0xA7CB, 0x3383, 0xA7CC, 0x3384, 0xA7CD, 0x3388, 0xA7BA, 0x3389, 0xA7BB, 0x338A, 0xA7DC, + 0x338B, 0xA7DD, 0x338C, 0xA7DE, 0x338D, 0xA7B6, 0x338E, 0xA7B7, 0x338F, 0xA7B8, 0x3390, 0xA7D4, 0x3391, 0xA7D5, 0x3392, 0xA7D6, + 0x3393, 0xA7D7, 0x3394, 0xA7D8, 0x3395, 0xA7A1, 0x3396, 0xA7A2, 0x3397, 0xA7A3, 0x3398, 0xA7A5, 0x3399, 0xA7AB, 0x339A, 0xA7AC, + 0x339B, 0xA7AD, 0x339C, 0xA7AE, 0x339D, 0xA7AF, 0x339E, 0xA7B0, 0x339F, 0xA7B1, 0x33A0, 0xA7B2, 0x33A1, 0xA7B3, 0x33A2, 0xA7B4, + 0x33A3, 0xA7A7, 0x33A4, 0xA7A8, 0x33A5, 0xA7A9, 0x33A6, 0xA7AA, 0x33A7, 0xA7BD, 0x33A8, 0xA7BE, 0x33A9, 0xA7E5, 0x33AA, 0xA7E6, + 0x33AB, 0xA7E7, 0x33AC, 0xA7E8, 0x33AD, 0xA7E1, 0x33AE, 0xA7E2, 0x33AF, 0xA7E3, 0x33B0, 0xA7BF, 0x33B1, 0xA7C0, 0x33B2, 0xA7C1, + 0x33B3, 0xA7C2, 0x33B4, 0xA7C3, 0x33B5, 0xA7C4, 0x33B6, 0xA7C5, 0x33B7, 0xA7C6, 0x33B8, 0xA7C7, 0x33B9, 0xA7C8, 0x33BA, 0xA7CE, + 0x33BB, 0xA7CF, 0x33BC, 0xA7D0, 0x33BD, 0xA7D1, 0x33BE, 0xA7D2, 0x33BF, 0xA7D3, 0x33C0, 0xA7DA, 0x33C1, 0xA7DB, 0x33C2, 0xA2E3, + 0x33C3, 0xA7EC, 0x33C4, 0xA7A6, 0x33C5, 0xA7E0, 0x33C6, 0xA7EF, 0x33C7, 0xA2E1, 0x33C8, 0xA7BC, 0x33C9, 0xA7ED, 0x33CA, 0xA7B5, + 0x33CF, 0xA7B9, 0x33D0, 0xA7EA, 0x33D3, 0xA7EB, 0x33D6, 0xA7DF, 0x33D8, 0xA2E4, 0x33DB, 0xA7E4, 0x33DC, 0xA7EE, 0x33DD, 0xA7E9, + 0x4E00, 0xECE9, 0x4E01, 0xEFCB, 0x4E03, 0xF6D2, 0x4E07, 0xD8B2, 0x4E08, 0xEDDB, 0x4E09, 0xDFB2, 0x4E0A, 0xDFBE, 0x4E0B, 0xF9BB, + 0x4E0D, 0xDCF4, 0x4E11, 0xF5E4, 0x4E14, 0xF3A6, 0x4E15, 0xDDE0, 0x4E16, 0xE1A6, 0x4E18, 0xCEF8, 0x4E19, 0xDCB0, 0x4E1E, 0xE3AA, + 0x4E2D, 0xF1E9, 0x4E32, 0xCDFA, 0x4E38, 0xFCAF, 0x4E39, 0xD3A1, 0x4E3B, 0xF1AB, 0x4E42, 0xE7D1, 0x4E43, 0xD2AC, 0x4E45, 0xCEF9, + 0x4E4B, 0xF1FD, 0x4E4D, 0xDEBF, 0x4E4E, 0xFBBA, 0x4E4F, 0xF9B9, 0x4E56, 0xCED2, 0x4E58, 0xE3AB, 0x4E59, 0xEBE0, 0x4E5D, 0xCEFA, + 0x4E5E, 0xCBF7, 0x4E5F, 0xE5A5, 0x4E6B, 0xCAE1, 0x4E6D, 0xD4CC, 0x4E73, 0xEAE1, 0x4E76, 0xDCE3, 0x4E77, 0xDFAD, 0x4E7E, 0xCBEB, + 0x4E82, 0xD5AF, 0x4E86, 0xD6F5, 0x4E88, 0xE5F8, 0x4E8B, 0xDEC0, 0x4E8C, 0xECA3, 0x4E8E, 0xE9CD, 0x4E90, 0xEAA7, 0x4E91, 0xE9F6, + 0x4E92, 0xFBBB, 0x4E94, 0xE7E9, 0x4E95, 0xEFCC, 0x4E98, 0xD0E6, 0x4E9B, 0xDEC1, 0x4E9E, 0xE4AC, 0x4EA1, 0xD8CC, 0x4EA2, 0xF9F1, + 0x4EA4, 0xCEDF, 0x4EA5, 0xFAA4, 0x4EA6, 0xE6B2, 0x4EA8, 0xFAFB, 0x4EAB, 0xFABD, 0x4EAC, 0xCCC8, 0x4EAD, 0xEFCD, 0x4EAE, 0xD5D5, + 0x4EB6, 0xD3A2, 0x4EBA, 0xECD1, 0x4EC0, 0xE4A7, 0x4EC1, 0xECD2, 0x4EC4, 0xF6B1, 0x4EC7, 0xCEFB, 0x4ECA, 0xD0D1, 0x4ECB, 0xCBBF, + 0x4ECD, 0xEDA4, 0x4ED4, 0xEDA8, 0x4ED5, 0xDEC2, 0x4ED6, 0xF6E2, 0x4ED7, 0xEDDC, 0x4ED8, 0xDCF5, 0x4ED9, 0xE0B9, 0x4EDD, 0xD4CE, + 0x4EDF, 0xF4B5, 0x4EE3, 0xD3DB, 0x4EE4, 0xD6B5, 0x4EE5, 0xECA4, 0x4EF0, 0xE4E6, 0x4EF2, 0xF1EA, 0x4EF6, 0xCBEC, 0x4EF7, 0xCBC0, + 0x4EFB, 0xECF2, 0x4F01, 0xD0EA, 0x4F09, 0xF9F2, 0x4F0A, 0xECA5, 0x4F0B, 0xD0DF, 0x4F0D, 0xE7EA, 0x4F0E, 0xD0EB, 0x4F0F, 0xDCD1, + 0x4F10, 0xDBE9, 0x4F11, 0xFDCC, 0x4F2F, 0xDBD7, 0x4F34, 0xDAE1, 0x4F36, 0xD6B6, 0x4F38, 0xE3DF, 0x4F3A, 0xDEC3, 0x4F3C, 0xDEC4, + 0x4F3D, 0xCAA1, 0x4F43, 0xEEEC, 0x4F46, 0xD3A3, 0x4F47, 0xEEB7, 0x4F48, 0xF8CF, 0x4F4D, 0xEAC8, 0x4F4E, 0xEEB8, 0x4F4F, 0xF1AC, + 0x4F50, 0xF1A5, 0x4F51, 0xE9CE, 0x4F55, 0xF9BC, 0x4F59, 0xE5F9, 0x4F5A, 0xECEA, 0x4F5B, 0xDDD6, 0x4F5C, 0xEDC2, 0x4F69, 0xF8A5, + 0x4F6F, 0xE5BA, 0x4F70, 0xDBD8, 0x4F73, 0xCAA2, 0x4F76, 0xD1CD, 0x4F7A, 0xEEED, 0x4F7E, 0xECEB, 0x4F7F, 0xDEC5, 0x4F81, 0xE3E0, + 0x4F83, 0xCAC9, 0x4F84, 0xF2E9, 0x4F86, 0xD5CE, 0x4F88, 0xF6B6, 0x4F8A, 0xCEC2, 0x4F8B, 0xD6C7, 0x4F8D, 0xE3B4, 0x4F8F, 0xF1AD, + 0x4F91, 0xEAE2, 0x4F96, 0xD7C2, 0x4F98, 0xF3A7, 0x4F9B, 0xCDEA, 0x4F9D, 0xEBEE, 0x4FAE, 0xD9B2, 0x4FAF, 0xFDA5, 0x4FB5, 0xF6D5, + 0x4FB6, 0xD5E2, 0x4FBF, 0xF8B5, 0x4FC2, 0xCCF5, 0x4FC3, 0xF5B5, 0x4FC4, 0xE4AD, 0x4FC9, 0xE7EB, 0x4FCA, 0xF1D5, 0x4FCE, 0xF0BB, + 0x4FD1, 0xE9B5, 0x4FD3, 0xCCC9, 0x4FD4, 0xFAD5, 0x4FD7, 0xE1D4, 0x4FDA, 0xD7D6, 0x4FDD, 0xDCC1, 0x4FDF, 0xDEC6, 0x4FE0, 0xFAEF, + 0x4FE1, 0xE3E1, 0x4FEE, 0xE1F3, 0x4FEF, 0xDCF6, 0x4FF1, 0xCEFC, 0x4FF3, 0xDBC4, 0x4FF5, 0xF8F1, 0x4FF8, 0xDCE4, 0x4FFA, 0xE5EF, + 0x5002, 0xDCB1, 0x5006, 0xD5D6, 0x5009, 0xF3DA, 0x500B, 0xCBC1, 0x500D, 0xDBC3, 0x5011, 0xD9FA, 0x5012, 0xD3EE, 0x5016, 0xFAB8, + 0x5019, 0xFDA6, 0x501A, 0xEBEF, 0x501C, 0xF4A6, 0x501E, 0xCCCA, 0x501F, 0xF3A8, 0x5021, 0xF3DB, 0x5023, 0xDBA7, 0x5024, 0xF6B7, + 0x5026, 0xCFE6, 0x5027, 0xF0F2, 0x5028, 0xCBDA, 0x502A, 0xE7D2, 0x502B, 0xD7C3, 0x502C, 0xF6F0, 0x502D, 0xE8DE, 0x503B, 0xE5A6, + 0x5043, 0xE5E7, 0x5047, 0xCAA3, 0x5048, 0xCCA7, 0x5049, 0xEAC9, 0x504F, 0xF8B6, 0x5055, 0xFAA5, 0x505A, 0xF1AE, 0x505C, 0xEFCE, + 0x5065, 0xCBED, 0x5074, 0xF6B0, 0x5075, 0xEFCF, 0x5076, 0xE9CF, 0x5078, 0xF7DE, 0x5080, 0xCED3, 0x5085, 0xDCF7, 0x508D, 0xDBA8, + 0x5091, 0xCBF8, 0x5098, 0xDFA1, 0x5099, 0xDDE1, 0x50AC, 0xF5CA, 0x50AD, 0xE9B6, 0x50B2, 0xE7EC, 0x50B3, 0xEEEE, 0x50B5, 0xF3F0, + 0x50B7, 0xDFBF, 0x50BE, 0xCCCB, 0x50C5, 0xD0C1, 0x50C9, 0xF4D2, 0x50CA, 0xE0BA, 0x50CF, 0xDFC0, 0x50D1, 0xCEE0, 0x50D5, 0xDCD2, + 0x50D6, 0xFDEA, 0x50DA, 0xD6F6, 0x50DE, 0xEACA, 0x50E5, 0xE8E9, 0x50E7, 0xE3AC, 0x50ED, 0xF3D0, 0x50F9, 0xCAA4, 0x50FB, 0xDBF8, + 0x50FF, 0xDEC7, 0x5100, 0xEBF0, 0x5101, 0xF1D6, 0x5104, 0xE5E2, 0x5106, 0xCCCC, 0x5109, 0xCBFB, 0x5112, 0xEAE3, 0x511F, 0xDFC1, + 0x5121, 0xD6ED, 0x512A, 0xE9D0, 0x5132, 0xEEB9, 0x5137, 0xD5E3, 0x513A, 0xD1D3, 0x513C, 0xE5F0, 0x5140, 0xE8B4, 0x5141, 0xEBC3, + 0x5143, 0xEAAA, 0x5144, 0xFAFC, 0x5145, 0xF5F6, 0x5146, 0xF0BC, 0x5147, 0xFDD4, 0x5148, 0xE0BB, 0x5149, 0xCEC3, 0x514B, 0xD0BA, + 0x514C, 0xF7BA, 0x514D, 0xD8F3, 0x514E, 0xF7CD, 0x5152, 0xE4AE, 0x515C, 0xD4DF, 0x5162, 0xD0E7, 0x5165, 0xECFD, 0x5167, 0xD2AE, + 0x5168, 0xEEEF, 0x5169, 0xD5D7, 0x516A, 0xEAE4, 0x516B, 0xF8A2, 0x516C, 0xCDEB, 0x516D, 0xD7BF, 0x516E, 0xFBB1, 0x5171, 0xCDEC, + 0x5175, 0xDCB2, 0x5176, 0xD0EC, 0x5177, 0xCEFD, 0x5178, 0xEEF0, 0x517C, 0xCCC2, 0x5180, 0xD0ED, 0x5186, 0xE5F7, 0x518A, 0xF3FC, + 0x518D, 0xEEA2, 0x5192, 0xD9B3, 0x5195, 0xD8F4, 0x5197, 0xE9B7, 0x51A0, 0xCEAE, 0x51A5, 0xD9A2, 0x51AA, 0xD8F1, 0x51AC, 0xD4CF, + 0x51B6, 0xE5A7, 0x51B7, 0xD5D2, 0x51BD, 0xD6A9, 0x51C4, 0xF4A2, 0x51C6, 0xF1D7, 0x51C9, 0xD5D8, 0x51CB, 0xF0BD, 0x51CC, 0xD7D0, + 0x51CD, 0xD4D0, 0x51DC, 0xD7CF, 0x51DD, 0xEBEA, 0x51DE, 0xFDEB, 0x51E1, 0xDBED, 0x51F0, 0xFCC5, 0x51F1, 0xCBC2, 0x51F6, 0xFDD5, + 0x51F8, 0xF4C8, 0x51F9, 0xE8EA, 0x51FA, 0xF5F3, 0x51FD, 0xF9DE, 0x5200, 0xD3EF, 0x5203, 0xECD3, 0x5206, 0xDDC2, 0x5207, 0xEFB7, + 0x5208, 0xE7D4, 0x520A, 0xCACA, 0x520E, 0xD9FB, 0x5211, 0xFAFD, 0x5217, 0xD6AA, 0x521D, 0xF4F8, 0x5224, 0xF7F7, 0x5225, 0xDCAC, + 0x5229, 0xD7D7, 0x522A, 0xDFA2, 0x522E, 0xCEBE, 0x5230, 0xD3F0, 0x5236, 0xF0A4, 0x5237, 0xE1EC, 0x5238, 0xCFE7, 0x5239, 0xF3CB, + 0x523A, 0xEDA9, 0x523B, 0xCABE, 0x5243, 0xF4EF, 0x5247, 0xF6CE, 0x524A, 0xDEFB, 0x524B, 0xD0BB, 0x524C, 0xD5B7, 0x524D, 0xEEF1, + 0x5254, 0xF4A8, 0x5256, 0xDCF8, 0x525B, 0xCBA7, 0x525D, 0xDACE, 0x5261, 0xE0E6, 0x5269, 0xEDA5, 0x526A, 0xEEF2, 0x526F, 0xDCF9, + 0x5272, 0xF9DC, 0x5275, 0xF3DC, 0x527D, 0xF8F2, 0x527F, 0xF4F9, 0x5283, 0xFCF1, 0x5287, 0xD0BC, 0x5288, 0xDBF9, 0x5289, 0xD7B1, + 0x528D, 0xCBFC, 0x5291, 0xF0A5, 0x5292, 0xCBFD, 0x529B, 0xD5F4, 0x529F, 0xCDED, 0x52A0, 0xCAA5, 0x52A3, 0xD6AB, 0x52A4, 0xD0C2, + 0x52A9, 0xF0BE, 0x52AA, 0xD2BD, 0x52AB, 0xCCA4, 0x52BE, 0xFAB6, 0x52C1, 0xCCCD, 0x52C3, 0xDAFA, 0x52C5, 0xF6CF, 0x52C7, 0xE9B8, + 0x52C9, 0xD8F5, 0x52CD, 0xCCCE, 0x52D2, 0xD7CD, 0x52D5, 0xD4D1, 0x52D6, 0xE9ED, 0x52D8, 0xCAEB, 0x52D9, 0xD9E2, 0x52DB, 0xFDB2, + 0x52DD, 0xE3AD, 0x52DE, 0xD6CC, 0x52DF, 0xD9B4, 0x52E2, 0xE1A7, 0x52E3, 0xEED3, 0x52E4, 0xD0C3, 0x52F3, 0xFDB3, 0x52F5, 0xD5E4, + 0x52F8, 0xCFE8, 0x52FA, 0xEDC3, 0x52FB, 0xD0B2, 0x52FE, 0xCEFE, 0x52FF, 0xDAA8, 0x5305, 0xF8D0, 0x5308, 0xFDD6, 0x530D, 0xF8D1, + 0x530F, 0xF8D2, 0x5310, 0xDCD3, 0x5315, 0xDDE2, 0x5316, 0xFBF9, 0x5317, 0xDDC1, 0x5319, 0xE3B5, 0x5320, 0xEDDD, 0x5321, 0xCEC4, + 0x5323, 0xCBA1, 0x532A, 0xDDE3, 0x532F, 0xFCDD, 0x5339, 0xF9AF, 0x533F, 0xD2FB, 0x5340, 0xCFA1, 0x5341, 0xE4A8, 0x5343, 0xF4B6, + 0x5344, 0xECFE, 0x5347, 0xE3AE, 0x5348, 0xE7ED, 0x5349, 0xFDC1, 0x534A, 0xDAE2, 0x534D, 0xD8B3, 0x5351, 0xDDE4, 0x5352, 0xF0EF, + 0x5353, 0xF6F1, 0x5354, 0xFAF0, 0x5357, 0xD1F5, 0x535A, 0xDACF, 0x535C, 0xDCD4, 0x535E, 0xDCA6, 0x5360, 0xEFBF, 0x5366, 0xCECF, + 0x5368, 0xE0D9, 0x536F, 0xD9D6, 0x5370, 0xECD4, 0x5371, 0xEACB, 0x5374, 0xCABF, 0x5375, 0xD5B0, 0x5377, 0xCFE9, 0x537D, 0xF1ED, + 0x537F, 0xCCCF, 0x5384, 0xE4F8, 0x5393, 0xE4ED, 0x5398, 0xD7D8, 0x539A, 0xFDA7, 0x539F, 0xEAAB, 0x53A0, 0xF6B2, 0x53A5, 0xCFF0, + 0x53A6, 0xF9BD, 0x53AD, 0xE6F4, 0x53BB, 0xCBDB, 0x53C3, 0xF3D1, 0x53C8, 0xE9D1, 0x53C9, 0xF3A9, 0x53CA, 0xD0E0, 0x53CB, 0xE9D2, + 0x53CD, 0xDAE3, 0x53D4, 0xE2D2, 0x53D6, 0xF6A2, 0x53D7, 0xE1F4, 0x53DB, 0xDAE4, 0x53E1, 0xE7D5, 0x53E2, 0xF5BF, 0x53E3, 0xCFA2, + 0x53E4, 0xCDAF, 0x53E5, 0xCFA3, 0x53E9, 0xCDB0, 0x53EA, 0xF1FE, 0x53EB, 0xD0A3, 0x53EC, 0xE1AF, 0x53ED, 0xF8A3, 0x53EF, 0xCAA6, + 0x53F0, 0xF7BB, 0x53F1, 0xF2EA, 0x53F2, 0xDEC8, 0x53F3, 0xE9D3, 0x53F8, 0xDEC9, 0x5403, 0xFDDE, 0x5404, 0xCAC0, 0x5408, 0xF9EA, + 0x5409, 0xD1CE, 0x540A, 0xEED4, 0x540C, 0xD4D2, 0x540D, 0xD9A3, 0x540E, 0xFDA8, 0x540F, 0xD7D9, 0x5410, 0xF7CE, 0x5411, 0xFABE, + 0x541B, 0xCFD6, 0x541D, 0xD7F0, 0x541F, 0xEBE1, 0x5420, 0xF8C5, 0x5426, 0xDCFA, 0x5429, 0xDDC3, 0x542B, 0xF9DF, 0x5433, 0xE7EF, + 0x5438, 0xFDE5, 0x5439, 0xF6A3, 0x543B, 0xD9FC, 0x543C, 0xFDA9, 0x543E, 0xE7EE, 0x5442, 0xD5E5, 0x5448, 0xEFD0, 0x544A, 0xCDB1, + 0x5451, 0xF7A2, 0x5468, 0xF1B2, 0x546A, 0xF1B1, 0x5471, 0xCDB2, 0x5473, 0xDAAB, 0x5475, 0xCAA7, 0x547B, 0xE3E2, 0x547C, 0xFBBC, + 0x547D, 0xD9A4, 0x5480, 0xEEBA, 0x5486, 0xF8D3, 0x548C, 0xFBFA, 0x548E, 0xCFA4, 0x5490, 0xDCFB, 0x54A4, 0xF6E3, 0x54A8, 0xEDAA, + 0x54AB, 0xF2A1, 0x54AC, 0xCEE1, 0x54B3, 0xFAA6, 0x54B8, 0xF9E0, 0x54BD, 0xECD6, 0x54C0, 0xE4EE, 0x54C1, 0xF9A1, 0x54C4, 0xFBEF, + 0x54C8, 0xF9EB, 0x54C9, 0xEEA3, 0x54E1, 0xEAAC, 0x54E5, 0xCAA8, 0x54E8, 0xF4FA, 0x54ED, 0xCDD6, 0x54EE, 0xFCF6, 0x54F2, 0xF4C9, + 0x54FA, 0xF8D4, 0x5504, 0xF8A6, 0x5506, 0xDECA, 0x5507, 0xF2C6, 0x550E, 0xD7DA, 0x5510, 0xD3D0, 0x551C, 0xD8C5, 0x552F, 0xEAE6, + 0x5531, 0xF3DD, 0x5535, 0xE4DA, 0x553E, 0xF6E4, 0x5544, 0xF6F2, 0x5546, 0xDFC2, 0x554F, 0xD9FD, 0x5553, 0xCCF6, 0x5556, 0xD3BA, + 0x555E, 0xE4AF, 0x5563, 0xF9E1, 0x557C, 0xF0A6, 0x5580, 0xCBD3, 0x5584, 0xE0BC, 0x5586, 0xF4CA, 0x5587, 0xD4FA, 0x5589, 0xFDAA, + 0x558A, 0xF9E2, 0x5598, 0xF4B7, 0x5599, 0xFDC2, 0x559A, 0xFCB0, 0x559C, 0xFDEC, 0x559D, 0xCAE2, 0x55A7, 0xFDBD, 0x55A9, 0xEAE7, + 0x55AA, 0xDFC3, 0x55AB, 0xD1D2, 0x55AC, 0xCEE2, 0x55AE, 0xD3A4, 0x55C5, 0xFDAB, 0x55C7, 0xDFE0, 0x55D4, 0xF2C7, 0x55DA, 0xE7F0, + 0x55DC, 0xD0EE, 0x55DF, 0xF3AA, 0x55E3, 0xDECB, 0x55E4, 0xF6B8, 0x55FD, 0xE1F5, 0x55FE, 0xF1B3, 0x5606, 0xF7A3, 0x5609, 0xCAA9, + 0x5614, 0xCFA5, 0x5617, 0xDFC4, 0x562F, 0xE1B0, 0x5632, 0xF0BF, 0x5634, 0xF6A4, 0x5636, 0xE3B6, 0x5653, 0xFAC6, 0x5668, 0xD0EF, + 0x566B, 0xFDED, 0x5674, 0xDDC4, 0x5686, 0xFCF7, 0x56A5, 0xE6BF, 0x56AC, 0xDEAD, 0x56AE, 0xFABF, 0x56B4, 0xE5F1, 0x56BC, 0xEDC4, + 0x56CA, 0xD2A5, 0x56CD, 0xFDEE, 0x56D1, 0xF5B6, 0x56DA, 0xE1F6, 0x56DB, 0xDECC, 0x56DE, 0xFCDE, 0x56E0, 0xECD7, 0x56F0, 0xCDDD, + 0x56F9, 0xD6B7, 0x56FA, 0xCDB3, 0x5703, 0xF8D5, 0x5704, 0xE5D8, 0x5708, 0xCFEA, 0x570B, 0xCFD0, 0x570D, 0xEACC, 0x5712, 0xEAAE, + 0x5713, 0xEAAD, 0x5716, 0xD3F1, 0x5718, 0xD3A5, 0x571F, 0xF7CF, 0x5728, 0xEEA4, 0x572D, 0xD0A4, 0x5730, 0xF2A2, 0x573B, 0xD0F0, + 0x5740, 0xF2A3, 0x5742, 0xF7F8, 0x5747, 0xD0B3, 0x574A, 0xDBA9, 0x574D, 0xD3BB, 0x574E, 0xCAEC, 0x5750, 0xF1A6, 0x5751, 0xCBD5, + 0x5761, 0xF7E7, 0x5764, 0xCDDE, 0x5766, 0xF7A4, 0x576A, 0xF8C0, 0x576E, 0xD3DD, 0x5770, 0xCCD0, 0x5775, 0xCFA6, 0x577C, 0xF6F3, + 0x5782, 0xE1F7, 0x5788, 0xD3DC, 0x578B, 0xFAFE, 0x5793, 0xFAA7, 0x57A0, 0xEBD9, 0x57A2, 0xCFA7, 0x57A3, 0xEAAF, 0x57C3, 0xE4EF, + 0x57C7, 0xE9B9, 0x57C8, 0xF1D8, 0x57CB, 0xD8D8, 0x57CE, 0xE0F2, 0x57DF, 0xE6B4, 0x57E0, 0xDCFC, 0x57F0, 0xF3F1, 0x57F4, 0xE3D0, + 0x57F7, 0xF2FB, 0x57F9, 0xDBC6, 0x57FA, 0xD0F1, 0x57FC, 0xD0F2, 0x5800, 0xCFDC, 0x5802, 0xD3D1, 0x5805, 0xCCB1, 0x5806, 0xF7D8, + 0x5808, 0xCBA8, 0x5809, 0xEBBC, 0x580A, 0xE4BE, 0x581E, 0xF4DC, 0x5821, 0xDCC2, 0x5824, 0xF0A7, 0x5827, 0xE6C0, 0x582A, 0xCAED, + 0x582F, 0xE8EB, 0x5830, 0xE5E8, 0x5831, 0xDCC3, 0x5834, 0xEDDE, 0x5835, 0xD3F2, 0x583A, 0xCCF7, 0x584A, 0xCED4, 0x584B, 0xE7AB, + 0x584F, 0xCBC3, 0x5851, 0xE1B1, 0x5854, 0xF7B2, 0x5857, 0xD3F3, 0x5858, 0xD3D2, 0x585A, 0xF5C0, 0x585E, 0xDFDD, 0x5861, 0xEEF3, + 0x5862, 0xE7F1, 0x5864, 0xFDB4, 0x5875, 0xF2C8, 0x5879, 0xF3D2, 0x587C, 0xEEF4, 0x587E, 0xE2D3, 0x5883, 0xCCD1, 0x5885, 0xDFEA, + 0x5889, 0xE9BA, 0x5893, 0xD9D7, 0x589C, 0xF5CD, 0x589E, 0xF1F2, 0x589F, 0xFAC7, 0x58A8, 0xD9F8, 0x58A9, 0xD4C2, 0x58AE, 0xF6E5, + 0x58B3, 0xDDC5, 0x58BA, 0xE7F2, 0x58BB, 0xEDDF, 0x58BE, 0xCACB, 0x58C1, 0xDBFA, 0x58C5, 0xE8B5, 0x58C7, 0xD3A6, 0x58CE, 0xFDB5, + 0x58D1, 0xF9C9, 0x58D3, 0xE4E2, 0x58D5, 0xFBBD, 0x58D8, 0xD7A4, 0x58D9, 0xCEC5, 0x58DE, 0xCED5, 0x58DF, 0xD6E6, 0x58E4, 0xE5BD, + 0x58EB, 0xDECD, 0x58EC, 0xECF3, 0x58EF, 0xEDE0, 0x58F9, 0xECEC, 0x58FA, 0xFBBE, 0x58FB, 0xDFEB, 0x58FD, 0xE1F8, 0x590F, 0xF9BE, + 0x5914, 0xD0F3, 0x5915, 0xE0AA, 0x5916, 0xE8E2, 0x5919, 0xE2D4, 0x591A, 0xD2FD, 0x591C, 0xE5A8, 0x5922, 0xD9D3, 0x5927, 0xD3DE, + 0x5929, 0xF4B8, 0x592A, 0xF7BC, 0x592B, 0xDCFD, 0x592D, 0xE8EC, 0x592E, 0xE4E7, 0x5931, 0xE3F7, 0x5937, 0xECA8, 0x593E, 0xFAF1, + 0x5944, 0xE5F2, 0x5947, 0xD0F4, 0x5948, 0xD2AF, 0x5949, 0xDCE5, 0x594E, 0xD0A5, 0x594F, 0xF1B4, 0x5950, 0xFCB1, 0x5951, 0xCCF8, + 0x5954, 0xDDC6, 0x5955, 0xFAD1, 0x5957, 0xF7DF, 0x595A, 0xFAA8, 0x5960, 0xEEF5, 0x5962, 0xDECE, 0x5967, 0xE7F3, 0x596A, 0xF7AC, + 0x596B, 0xEBC4, 0x596C, 0xEDE1, 0x596D, 0xE0AB, 0x596E, 0xDDC7, 0x5973, 0xD2B3, 0x5974, 0xD2BF, 0x5978, 0xCACC, 0x597D, 0xFBBF, + 0x5982, 0xE5FD, 0x5983, 0xDDE5, 0x5984, 0xD8CD, 0x598A, 0xECF4, 0x5993, 0xD0F5, 0x5996, 0xE8ED, 0x5997, 0xD0D2, 0x5999, 0xD9D8, + 0x59A5, 0xF6E6, 0x59A8, 0xDBAA, 0x59AC, 0xF7E0, 0x59B9, 0xD8D9, 0x59BB, 0xF4A3, 0x59BE, 0xF4DD, 0x59C3, 0xEFD1, 0x59C6, 0xD9B5, + 0x59C9, 0xEDAB, 0x59CB, 0xE3B7, 0x59D0, 0xEEBB, 0x59D1, 0xCDB4, 0x59D3, 0xE0F3, 0x59D4, 0xEACD, 0x59D9, 0xECF5, 0x59DA, 0xE8EE, + 0x59DC, 0xCBA9, 0x59DD, 0xF1AF, 0x59E6, 0xCACD, 0x59E8, 0xECA9, 0x59EA, 0xF2EB, 0x59EC, 0xFDEF, 0x59EE, 0xF9F3, 0x59F8, 0xE6C1, + 0x59FB, 0xECD8, 0x59FF, 0xEDAC, 0x5A01, 0xEACE, 0x5A03, 0xE8DF, 0x5A11, 0xDECF, 0x5A18, 0xD2A6, 0x5A1B, 0xE7F4, 0x5A1C, 0xD1D6, + 0x5A1F, 0xE6C2, 0x5A20, 0xE3E3, 0x5A25, 0xE4B0, 0x5A29, 0xD8B4, 0x5A36, 0xF6A5, 0x5A3C, 0xF3DE, 0x5A41, 0xD7A5, 0x5A46, 0xF7E8, + 0x5A49, 0xE8C6, 0x5A5A, 0xFBE6, 0x5A62, 0xDDE6, 0x5A66, 0xDCFE, 0x5A92, 0xD8DA, 0x5A9A, 0xDAAC, 0x5A9B, 0xEAB0, 0x5AA4, 0xE3B8, + 0x5AC1, 0xCAAA, 0x5AC2, 0xE1F9, 0x5AC4, 0xEAB1, 0x5AC9, 0xF2EC, 0x5ACC, 0xFAEE, 0x5AE1, 0xEED5, 0x5AE6, 0xF9F4, 0x5AE9, 0xD2EC, + 0x5B05, 0xFBFB, 0x5B09, 0xFDF0, 0x5B0B, 0xE0BD, 0x5B0C, 0xCEE3, 0x5B16, 0xF8C6, 0x5B2A, 0xDEAE, 0x5B40, 0xDFC5, 0x5B43, 0xE5BE, + 0x5B50, 0xEDAD, 0x5B51, 0xFAEA, 0x5B54, 0xCDEE, 0x5B55, 0xEDA6, 0x5B57, 0xEDAE, 0x5B58, 0xF0ED, 0x5B5A, 0xDDA1, 0x5B5C, 0xEDAF, + 0x5B5D, 0xFCF8, 0x5B5F, 0xD8EB, 0x5B63, 0xCCF9, 0x5B64, 0xCDB5, 0x5B69, 0xFAA9, 0x5B6B, 0xE1DD, 0x5B70, 0xE2D5, 0x5B71, 0xEDCF, + 0x5B75, 0xDDA2, 0x5B78, 0xF9CA, 0x5B7A, 0xEAE8, 0x5B7C, 0xE5ED, 0x5B85, 0xD3EB, 0x5B87, 0xE9D4, 0x5B88, 0xE1FA, 0x5B89, 0xE4CC, + 0x5B8B, 0xE1E4, 0x5B8C, 0xE8C7, 0x5B8F, 0xCEDB, 0x5B93, 0xDCD5, 0x5B95, 0xF7B5, 0x5B96, 0xFCF3, 0x5B97, 0xF0F3, 0x5B98, 0xCEAF, + 0x5B99, 0xF1B5, 0x5B9A, 0xEFD2, 0x5B9B, 0xE8C8, 0x5B9C, 0xEBF1, 0x5BA2, 0xCBD4, 0x5BA3, 0xE0BE, 0x5BA4, 0xE3F8, 0x5BA5, 0xEAE9, + 0x5BA6, 0xFCB2, 0x5BAC, 0xE0F4, 0x5BAE, 0xCFE0, 0x5BB0, 0xEEA5, 0x5BB3, 0xFAAA, 0x5BB4, 0xE6C3, 0x5BB5, 0xE1B2, 0x5BB6, 0xCAAB, + 0x5BB8, 0xE3E4, 0x5BB9, 0xE9BB, 0x5BBF, 0xE2D6, 0x5BC0, 0xF3F2, 0x5BC2, 0xEED6, 0x5BC3, 0xEAB2, 0x5BC4, 0xD0F6, 0x5BC5, 0xECD9, + 0x5BC6, 0xDACB, 0x5BC7, 0xCFA8, 0x5BCC, 0xDDA3, 0x5BD0, 0xD8DB, 0x5BD2, 0xF9CE, 0x5BD3, 0xE9D5, 0x5BD4, 0xE3D1, 0x5BD7, 0xD2BC, + 0x5BDE, 0xD8AC, 0x5BDF, 0xF3CC, 0x5BE1, 0xCDFB, 0x5BE2, 0xF6D6, 0x5BE4, 0xE7F5, 0x5BE5, 0xE8EF, 0x5BE6, 0xE3F9, 0x5BE7, 0xD2BB, + 0x5BE8, 0xF3F3, 0x5BE9, 0xE3FB, 0x5BEB, 0xDED0, 0x5BEC, 0xCEB0, 0x5BEE, 0xD6F7, 0x5BEF, 0xF1D9, 0x5BF5, 0xF5C1, 0x5BF6, 0xDCC4, + 0x5BF8, 0xF5BB, 0x5BFA, 0xDED1, 0x5C01, 0xDCE6, 0x5C04, 0xDED2, 0x5C07, 0xEDE2, 0x5C08, 0xEEF6, 0x5C09, 0xEACF, 0x5C0A, 0xF0EE, + 0x5C0B, 0xE3FC, 0x5C0D, 0xD3DF, 0x5C0E, 0xD3F4, 0x5C0F, 0xE1B3, 0x5C11, 0xE1B4, 0x5C16, 0xF4D3, 0x5C19, 0xDFC6, 0x5C24, 0xE9D6, + 0x5C28, 0xDBAB, 0x5C31, 0xF6A6, 0x5C38, 0xE3B9, 0x5C39, 0xEBC5, 0x5C3A, 0xF4A9, 0x5C3B, 0xCDB6, 0x5C3C, 0xD2F9, 0x5C3E, 0xDAAD, + 0x5C3F, 0xD2E3, 0x5C40, 0xCFD1, 0x5C45, 0xCBDC, 0x5C46, 0xCCFA, 0x5C48, 0xCFDD, 0x5C4B, 0xE8A9, 0x5C4D, 0xE3BB, 0x5C4E, 0xE3BA, + 0x5C51, 0xE0DA, 0x5C55, 0xEEF7, 0x5C5B, 0xDCB3, 0x5C60, 0xD3F5, 0x5C62, 0xD7A6, 0x5C64, 0xF6B5, 0x5C65, 0xD7DB, 0x5C6C, 0xE1D5, + 0x5C6F, 0xD4EA, 0x5C71, 0xDFA3, 0x5C79, 0xFDDF, 0x5C90, 0xD0F7, 0x5C91, 0xEDD4, 0x5CA1, 0xCBAA, 0x5CA9, 0xE4DB, 0x5CAB, 0xE1FB, + 0x5CAC, 0xCBA2, 0x5CB1, 0xD3E0, 0x5CB3, 0xE4BF, 0x5CB5, 0xFBC0, 0x5CB7, 0xDABE, 0x5CB8, 0xE4CD, 0x5CBA, 0xD6B9, 0x5CBE, 0xEFC0, + 0x5CC0, 0xE1FC, 0x5CD9, 0xF6B9, 0x5CE0, 0xDFC7, 0x5CE8, 0xE4B1, 0x5CEF, 0xDCE7, 0x5CF0, 0xDCE8, 0x5CF4, 0xFAD6, 0x5CF6, 0xD3F6, + 0x5CFB, 0xF1DA, 0x5CFD, 0xFAF2, 0x5D07, 0xE2FD, 0x5D0D, 0xD5CF, 0x5D0E, 0xD0F8, 0x5D11, 0xCDDF, 0x5D14, 0xF5CB, 0x5D16, 0xE4F0, + 0x5D17, 0xCBAB, 0x5D19, 0xD7C4, 0x5D27, 0xE2FE, 0x5D29, 0xDDDA, 0x5D4B, 0xDAAE, 0x5D4C, 0xCAEE, 0x5D50, 0xD5B9, 0x5D69, 0xE3A1, + 0x5D6C, 0xE8E3, 0x5D6F, 0xF3AB, 0x5D87, 0xCFA9, 0x5D8B, 0xD3F7, 0x5D9D, 0xD4F1, 0x5DA0, 0xCEE4, 0x5DA2, 0xE8F2, 0x5DAA, 0xE5F5, + 0x5DB8, 0xE7AE, 0x5DBA, 0xD6BA, 0x5DBC, 0xDFEC, 0x5DBD, 0xE4C0, 0x5DCD, 0xE8E4, 0x5DD2, 0xD8B5, 0x5DD6, 0xE4DC, 0x5DDD, 0xF4B9, + 0x5DDE, 0xF1B6, 0x5DE1, 0xE2DE, 0x5DE2, 0xE1B5, 0x5DE5, 0xCDEF, 0x5DE6, 0xF1A7, 0x5DE7, 0xCEE5, 0x5DE8, 0xCBDD, 0x5DEB, 0xD9E3, + 0x5DEE, 0xF3AC, 0x5DF1, 0xD0F9, 0x5DF2, 0xECAB, 0x5DF3, 0xDED3, 0x5DF4, 0xF7E9, 0x5DF7, 0xF9F5, 0x5DFD, 0xE1DE, 0x5DFE, 0xCBEE, + 0x5E02, 0xE3BC, 0x5E03, 0xF8D6, 0x5E06, 0xDBEE, 0x5E0C, 0xFDF1, 0x5E11, 0xF7B6, 0x5E16, 0xF4DE, 0x5E19, 0xF2ED, 0x5E1B, 0xDBD9, + 0x5E1D, 0xF0A8, 0x5E25, 0xE1FD, 0x5E2B, 0xDED4, 0x5E2D, 0xE0AC, 0x5E33, 0xEDE3, 0x5E36, 0xD3E1, 0x5E38, 0xDFC8, 0x5E3D, 0xD9B6, + 0x5E3F, 0xFDAC, 0x5E40, 0xEFD3, 0x5E44, 0xE4C1, 0x5E45, 0xF8EB, 0x5E47, 0xDBAC, 0x5E4C, 0xFCC6, 0x5E55, 0xD8AD, 0x5E5F, 0xF6BA, + 0x5E61, 0xDBDF, 0x5E62, 0xD3D3, 0x5E63, 0xF8C7, 0x5E72, 0xCACE, 0x5E73, 0xF8C1, 0x5E74, 0xD2B4, 0x5E77, 0xDCB4, 0x5E78, 0xFAB9, + 0x5E79, 0xCACF, 0x5E7B, 0xFCB3, 0x5E7C, 0xEAEA, 0x5E7D, 0xEAEB, 0x5E7E, 0xD0FA, 0x5E84, 0xEDE4, 0x5E87, 0xDDE7, 0x5E8A, 0xDFC9, + 0x5E8F, 0xDFED, 0x5E95, 0xEEBC, 0x5E97, 0xEFC1, 0x5E9A, 0xCCD2, 0x5E9C, 0xDDA4, 0x5EA0, 0xDFCA, 0x5EA6, 0xD3F8, 0x5EA7, 0xF1A8, + 0x5EAB, 0xCDB7, 0x5EAD, 0xEFD4, 0x5EB5, 0xE4DD, 0x5EB6, 0xDFEE, 0x5EB7, 0xCBAC, 0x5EB8, 0xE9BC, 0x5EBE, 0xEAEC, 0x5EC2, 0xDFCB, + 0x5EC8, 0xF9BF, 0x5EC9, 0xD6AF, 0x5ECA, 0xD5C6, 0x5ED0, 0xCFAA, 0x5ED3, 0xCEA9, 0x5ED6, 0xD6F8, 0x5EDA, 0xF1B7, 0x5EDB, 0xEEF8, + 0x5EDF, 0xD9D9, 0x5EE0, 0xF3DF, 0x5EE2, 0xF8C8, 0x5EE3, 0xCEC6, 0x5EEC, 0xD5E6, 0x5EF3, 0xF4E6, 0x5EF6, 0xE6C5, 0x5EF7, 0xEFD5, + 0x5EFA, 0xCBEF, 0x5EFB, 0xFCDF, 0x5F01, 0xDCA7, 0x5F04, 0xD6E7, 0x5F0A, 0xF8C9, 0x5F0F, 0xE3D2, 0x5F11, 0xE3BD, 0x5F13, 0xCFE1, + 0x5F14, 0xF0C0, 0x5F15, 0xECDA, 0x5F17, 0xDDD7, 0x5F18, 0xFBF0, 0x5F1B, 0xECAC, 0x5F1F, 0xF0A9, 0x5F26, 0xFAD7, 0x5F27, 0xFBC1, + 0x5F29, 0xD2C0, 0x5F31, 0xE5B0, 0x5F35, 0xEDE5, 0x5F3A, 0xCBAD, 0x5F3C, 0xF9B0, 0x5F48, 0xF7A5, 0x5F4A, 0xCBAE, 0x5F4C, 0xDAAF, + 0x5F4E, 0xD8B6, 0x5F56, 0xD3A7, 0x5F57, 0xFBB2, 0x5F59, 0xFDC4, 0x5F5B, 0xECAD, 0x5F62, 0xFBA1, 0x5F66, 0xE5E9, 0x5F67, 0xE9EE, + 0x5F69, 0xF3F4, 0x5F6A, 0xF8F3, 0x5F6B, 0xF0C1, 0x5F6C, 0xDEAF, 0x5F6D, 0xF8B0, 0x5F70, 0xF3E0, 0x5F71, 0xE7AF, 0x5F77, 0xDBAD, + 0x5F79, 0xE6B5, 0x5F7C, 0xF9A8, 0x5F7F, 0xDDD8, 0x5F80, 0xE8D9, 0x5F81, 0xEFD6, 0x5F85, 0xD3E2, 0x5F87, 0xE2DF, 0x5F8A, 0xFCE0, + 0x5F8B, 0xD7C8, 0x5F8C, 0xFDAD, 0x5F90, 0xDFEF, 0x5F91, 0xCCD3, 0x5F92, 0xD3F9, 0x5F97, 0xD4F0, 0x5F98, 0xDBC7, 0x5F99, 0xDED5, + 0x5F9E, 0xF0F4, 0x5FA0, 0xD5D0, 0x5FA1, 0xE5D9, 0x5FA8, 0xFCC7, 0x5FA9, 0xDCD6, 0x5FAA, 0xE2E0, 0x5FAE, 0xDAB0, 0x5FB5, 0xF3A3, + 0x5FB7, 0xD3EC, 0x5FB9, 0xF4CB, 0x5FBD, 0xFDC5, 0x5FC3, 0xE3FD, 0x5FC5, 0xF9B1, 0x5FCC, 0xD0FB, 0x5FCD, 0xECDB, 0x5FD6, 0xF5BC, + 0x5FD7, 0xF2A4, 0x5FD8, 0xD8CE, 0x5FD9, 0xD8CF, 0x5FE0, 0xF5F7, 0x5FEB, 0xF6E1, 0x5FF5, 0xD2B7, 0x5FFD, 0xFBEC, 0x5FFF, 0xDDC8, + 0x600F, 0xE4E8, 0x6012, 0xD2C1, 0x6016, 0xF8D7, 0x601C, 0xD6BB, 0x601D, 0xDED6, 0x6020, 0xF7BD, 0x6021, 0xECAE, 0x6025, 0xD0E1, + 0x6027, 0xE0F5, 0x6028, 0xEAB3, 0x602A, 0xCED6, 0x602F, 0xCCA5, 0x6041, 0xECF6, 0x6042, 0xE2E1, 0x6043, 0xE3BE, 0x604D, 0xFCC8, + 0x6050, 0xCDF0, 0x6052, 0xF9F6, 0x6055, 0xDFF0, 0x6059, 0xE5BF, 0x605D, 0xCEBF, 0x6062, 0xFCE1, 0x6063, 0xEDB0, 0x6064, 0xFDD1, + 0x6065, 0xF6BB, 0x6068, 0xF9CF, 0x6069, 0xEBDA, 0x606A, 0xCAC1, 0x606C, 0xD2B8, 0x606D, 0xCDF1, 0x606F, 0xE3D3, 0x6070, 0xFDE6, + 0x6085, 0xE6ED, 0x6089, 0xE3FA, 0x608C, 0xF0AA, 0x608D, 0xF9D0, 0x6094, 0xFCE2, 0x6096, 0xF8A7, 0x609A, 0xE1E5, 0x609B, 0xEEF9, + 0x609F, 0xE7F6, 0x60A0, 0xEAED, 0x60A3, 0xFCB4, 0x60A4, 0xF5C2, 0x60A7, 0xD7DC, 0x60B0, 0xF0F5, 0x60B2, 0xDDE8, 0x60B3, 0xD3ED, + 0x60B4, 0xF5FC, 0x60B6, 0xDABF, 0x60B8, 0xCCFB, 0x60BC, 0xD3FA, 0x60BD, 0xF4A4, 0x60C5, 0xEFD7, 0x60C7, 0xD4C3, 0x60D1, 0xFBE3, + 0x60DA, 0xFBED, 0x60DC, 0xE0AD, 0x60DF, 0xEAEE, 0x60E0, 0xFBB3, 0x60E1, 0xE4C2, 0x60F0, 0xF6E7, 0x60F1, 0xD2DD, 0x60F3, 0xDFCC, + 0x60F6, 0xFCC9, 0x60F9, 0xE5A9, 0x60FA, 0xE0F6, 0x60FB, 0xF6B3, 0x6101, 0xE1FE, 0x6106, 0xCBF0, 0x6108, 0xEAEF, 0x6109, 0xEAF0, + 0x610D, 0xDAC0, 0x610E, 0xF8B4, 0x610F, 0xEBF2, 0x6115, 0xE4C3, 0x611A, 0xE9D7, 0x611B, 0xE4F1, 0x611F, 0xCAEF, 0x6127, 0xCED7, + 0x6130, 0xFCCA, 0x6134, 0xF3E1, 0x6137, 0xCBC4, 0x613C, 0xE3E5, 0x613E, 0xCBC5, 0x613F, 0xEAB4, 0x6142, 0xE9BD, 0x6144, 0xD7C9, + 0x6147, 0xEBDB, 0x6148, 0xEDB1, 0x614A, 0xCCC3, 0x614B, 0xF7BE, 0x614C, 0xFCCB, 0x6153, 0xF8F4, 0x6155, 0xD9B7, 0x6158, 0xF3D3, + 0x6159, 0xF3D4, 0x615D, 0xF7E4, 0x615F, 0xF7D1, 0x6162, 0xD8B7, 0x6163, 0xCEB1, 0x6164, 0xCAC2, 0x6167, 0xFBB4, 0x6168, 0xCBC6, + 0x616B, 0xF0F6, 0x616E, 0xD5E7, 0x6170, 0xEAD0, 0x6176, 0xCCD4, 0x6177, 0xCBAF, 0x617D, 0xF4AA, 0x617E, 0xE9AF, 0x6181, 0xF5C3, + 0x6182, 0xE9D8, 0x618A, 0xDDE9, 0x618E, 0xF1F3, 0x6190, 0xD5FB, 0x6191, 0xDEBB, 0x6194, 0xF4FB, 0x6198, 0xFDF3, 0x6199, 0xFDF2, + 0x619A, 0xF7A6, 0x61A4, 0xDDC9, 0x61A7, 0xD4D3, 0x61A9, 0xCCA8, 0x61AB, 0xDAC1, 0x61AC, 0xCCD5, 0x61AE, 0xD9E4, 0x61B2, 0xFACA, + 0x61B6, 0xE5E3, 0x61BA, 0xD3BC, 0x61BE, 0xCAF0, 0x61C3, 0xD0C4, 0x61C7, 0xCAD0, 0x61C8, 0xFAAB, 0x61C9, 0xEBEB, 0x61CA, 0xE7F8, + 0x61CB, 0xD9E5, 0x61E6, 0xD1D7, 0x61F2, 0xF3A4, 0x61F6, 0xD4FB, 0x61F7, 0xFCE3, 0x61F8, 0xFAD8, 0x61FA, 0xF3D5, 0x61FC, 0xCFAB, + 0x61FF, 0xEBF3, 0x6200, 0xD5FC, 0x6207, 0xD3D4, 0x6208, 0xCDFC, 0x620A, 0xD9E6, 0x620C, 0xE2F9, 0x620D, 0xE2A1, 0x620E, 0xEBD4, + 0x6210, 0xE0F7, 0x6211, 0xE4B2, 0x6212, 0xCCFC, 0x6216, 0xFBE4, 0x621A, 0xF4AB, 0x621F, 0xD0BD, 0x6221, 0xCAF1, 0x622A, 0xEFB8, + 0x622E, 0xD7C0, 0x6230, 0xEEFA, 0x6231, 0xFDF4, 0x6234, 0xD3E3, 0x6236, 0xFBC2, 0x623E, 0xD5E8, 0x623F, 0xDBAE, 0x6240, 0xE1B6, + 0x6241, 0xF8B7, 0x6247, 0xE0BF, 0x6248, 0xFBC3, 0x6249, 0xDDEA, 0x624B, 0xE2A2, 0x624D, 0xEEA6, 0x6253, 0xF6E8, 0x6258, 0xF6F5, + 0x626E, 0xDDCA, 0x6271, 0xD0E2, 0x6276, 0xDDA6, 0x6279, 0xDDEB, 0x627C, 0xE4F9, 0x627F, 0xE3AF, 0x6280, 0xD0FC, 0x6284, 0xF4FC, + 0x6289, 0xCCBC, 0x628A, 0xF7EA, 0x6291, 0xE5E4, 0x6292, 0xDFF1, 0x6295, 0xF7E1, 0x6297, 0xF9F7, 0x6298, 0xEFB9, 0x629B, 0xF8D8, + 0x62AB, 0xF9A9, 0x62B1, 0xF8D9, 0x62B5, 0xEEBD, 0x62B9, 0xD8C6, 0x62BC, 0xE4E3, 0x62BD, 0xF5CE, 0x62C2, 0xDDD9, 0x62C7, 0xD9E7, + 0x62C8, 0xD2B9, 0x62C9, 0xD5C3, 0x62CC, 0xDAE5, 0x62CD, 0xDAD0, 0x62CF, 0xD1D9, 0x62D0, 0xCED8, 0x62D2, 0xCBDE, 0x62D3, 0xF4AC, + 0x62D4, 0xDAFB, 0x62D6, 0xF6E9, 0x62D7, 0xE8F3, 0x62D8, 0xCFAC, 0x62D9, 0xF0F0, 0x62DB, 0xF4FD, 0x62DC, 0xDBC8, 0x62EC, 0xCEC0, + 0x62ED, 0xE3D4, 0x62EE, 0xD1CF, 0x62EF, 0xF1F5, 0x62F1, 0xCDF2, 0x62F3, 0xCFEB, 0x62F7, 0xCDB8, 0x62FE, 0xE3A6, 0x62FF, 0xD1DA, + 0x6301, 0xF2A5, 0x6307, 0xF2A6, 0x6309, 0xE4CE, 0x6311, 0xD3FB, 0x632B, 0xF1A9, 0x632F, 0xF2C9, 0x633A, 0xEFD8, 0x633B, 0xE6C9, + 0x633D, 0xD8B8, 0x633E, 0xFAF3, 0x6349, 0xF3B5, 0x634C, 0xF8A4, 0x634F, 0xD1F3, 0x6350, 0xE6C8, 0x6355, 0xF8DA, 0x6367, 0xDCE9, + 0x6368, 0xDED7, 0x636E, 0xCBDF, 0x6372, 0xCFEC, 0x6377, 0xF4DF, 0x637A, 0xD1F4, 0x637B, 0xD2BA, 0x637F, 0xDFF2, 0x6383, 0xE1B7, + 0x6388, 0xE2A3, 0x6389, 0xD3FC, 0x638C, 0xEDE6, 0x6392, 0xDBC9, 0x6396, 0xE4FA, 0x6398, 0xCFDE, 0x639B, 0xCED0, 0x63A0, 0xD5D3, + 0x63A1, 0xF3F5, 0x63A2, 0xF7AE, 0x63A5, 0xEFC8, 0x63A7, 0xCDF3, 0x63A8, 0xF5CF, 0x63A9, 0xE5F3, 0x63AA, 0xF0C2, 0x63C0, 0xCAD1, + 0x63C4, 0xEAF1, 0x63C6, 0xD0A6, 0x63CF, 0xD9DA, 0x63D0, 0xF0AB, 0x63D6, 0xEBE7, 0x63DA, 0xE5C0, 0x63DB, 0xFCB5, 0x63E1, 0xE4C4, + 0x63ED, 0xCCA9, 0x63EE, 0xFDC6, 0x63F4, 0xEAB5, 0x63F6, 0xE5AA, 0x63F7, 0xDFBA, 0x640D, 0xE1DF, 0x640F, 0xDAD1, 0x6414, 0xE1B8, + 0x6416, 0xE8F4, 0x6417, 0xD3FD, 0x641C, 0xE2A4, 0x6422, 0xF2CA, 0x642C, 0xDAE6, 0x642D, 0xF7B3, 0x643A, 0xFDCD, 0x643E, 0xF3B6, + 0x6458, 0xEED7, 0x6460, 0xF5C4, 0x6469, 0xD8A4, 0x646F, 0xF2A7, 0x6478, 0xD9B8, 0x6479, 0xD9B9, 0x647A, 0xEFC9, 0x6488, 0xD6CE, + 0x6491, 0xF7CB, 0x6492, 0xDFAE, 0x6493, 0xE8F5, 0x649A, 0xD2B5, 0x649E, 0xD3D5, 0x64A4, 0xF4CC, 0x64A5, 0xDAFC, 0x64AB, 0xD9E8, + 0x64AD, 0xF7EB, 0x64AE, 0xF5C9, 0x64B0, 0xF3BC, 0x64B2, 0xDAD2, 0x64BB, 0xD3B5, 0x64C1, 0xE8B6, 0x64C4, 0xD6CF, 0x64C5, 0xF4BA, + 0x64C7, 0xF7C9, 0x64CA, 0xCCAA, 0x64CD, 0xF0C3, 0x64CE, 0xCCD6, 0x64D2, 0xD0D3, 0x64D4, 0xD3BD, 0x64D8, 0xDBFB, 0x64DA, 0xCBE0, + 0x64E1, 0xD3E4, 0x64E2, 0xF6F7, 0x64E5, 0xD5BA, 0x64E6, 0xF3CD, 0x64E7, 0xCBE1, 0x64EC, 0xEBF4, 0x64F2, 0xF4AD, 0x64F4, 0xFCAA, + 0x64FA, 0xF7EC, 0x64FE, 0xE8F6, 0x6500, 0xDAE7, 0x6504, 0xF7CC, 0x6518, 0xE5C1, 0x651D, 0xE0EE, 0x6523, 0xD5FD, 0x652A, 0xCEE6, + 0x652B, 0xFCAB, 0x652C, 0xD5BB, 0x652F, 0xF2A8, 0x6536, 0xE2A5, 0x6537, 0xCDB9, 0x6538, 0xEAF2, 0x6539, 0xCBC7, 0x653B, 0xCDF4, + 0x653E, 0xDBAF, 0x653F, 0xEFD9, 0x6545, 0xCDBA, 0x6548, 0xFCF9, 0x654D, 0xDFF3, 0x654E, 0xCEE7, 0x654F, 0xDAC2, 0x6551, 0xCFAD, + 0x6556, 0xE7F9, 0x6557, 0xF8A8, 0x655E, 0xF3E2, 0x6562, 0xCAF2, 0x6563, 0xDFA4, 0x6566, 0xD4C4, 0x656C, 0xCCD7, 0x656D, 0xE5C2, + 0x6572, 0xCDBB, 0x6574, 0xEFDA, 0x6575, 0xEED8, 0x6577, 0xDDA7, 0x6578, 0xE2A6, 0x657E, 0xE0C0, 0x6582, 0xD6B0, 0x6583, 0xF8CA, + 0x6585, 0xFCFA, 0x6587, 0xD9FE, 0x658C, 0xDEB0, 0x6590, 0xDDEC, 0x6591, 0xDAE8, 0x6597, 0xD4E0, 0x6599, 0xD6F9, 0x659B, 0xCDD7, + 0x659C, 0xDED8, 0x659F, 0xF2F8, 0x65A1, 0xE4D6, 0x65A4, 0xD0C5, 0x65A5, 0xF4AE, 0x65A7, 0xDDA8, 0x65AB, 0xEDC5, 0x65AC, 0xF3D6, + 0x65AF, 0xDED9, 0x65B0, 0xE3E6, 0x65B7, 0xD3A8, 0x65B9, 0xDBB0, 0x65BC, 0xE5DA, 0x65BD, 0xE3BF, 0x65C1, 0xDBB1, 0x65C5, 0xD5E9, + 0x65CB, 0xE0C1, 0x65CC, 0xEFDB, 0x65CF, 0xF0E9, 0x65D2, 0xD7B2, 0x65D7, 0xD0FD, 0x65E0, 0xD9E9, 0x65E3, 0xD0FE, 0x65E5, 0xECED, + 0x65E6, 0xD3A9, 0x65E8, 0xF2A9, 0x65E9, 0xF0C4, 0x65EC, 0xE2E2, 0x65ED, 0xE9EF, 0x65F1, 0xF9D1, 0x65F4, 0xE9D9, 0x65FA, 0xE8DA, + 0x65FB, 0xDAC3, 0x65FC, 0xDAC4, 0x65FD, 0xD4C5, 0x65FF, 0xE7FA, 0x6606, 0xCDE0, 0x6607, 0xE3B0, 0x6609, 0xDBB2, 0x660A, 0xFBC4, + 0x660C, 0xF3E3, 0x660E, 0xD9A5, 0x660F, 0xFBE7, 0x6610, 0xDDCB, 0x6611, 0xD0D4, 0x6613, 0xE6B6, 0x6614, 0xE0AE, 0x6615, 0xFDDA, + 0x661E, 0xDCB5, 0x661F, 0xE0F8, 0x6620, 0xE7B1, 0x6625, 0xF5F0, 0x6627, 0xD8DC, 0x6628, 0xEDC6, 0x662D, 0xE1B9, 0x662F, 0xE3C0, + 0x6630, 0xF9C0, 0x6631, 0xE9F0, 0x6634, 0xD9DB, 0x6636, 0xF3E4, 0x663A, 0xDCB6, 0x663B, 0xE4E9, 0x6641, 0xF0C5, 0x6642, 0xE3C1, + 0x6643, 0xFCCC, 0x6644, 0xFCCD, 0x6649, 0xF2CB, 0x664B, 0xF2CC, 0x664F, 0xE4CF, 0x6659, 0xF1DB, 0x665B, 0xFAD9, 0x665D, 0xF1B8, + 0x665E, 0xFDF5, 0x665F, 0xE0F9, 0x6664, 0xE7FB, 0x6665, 0xFCB7, 0x6666, 0xFCE4, 0x6667, 0xFBC5, 0x6668, 0xE3E7, 0x6669, 0xD8B9, + 0x666B, 0xF6F8, 0x666E, 0xDCC5, 0x666F, 0xCCD8, 0x6673, 0xE0AF, 0x6674, 0xF4E7, 0x6676, 0xEFDC, 0x6677, 0xCFFC, 0x6678, 0xEFDD, + 0x667A, 0xF2AA, 0x6684, 0xFDBE, 0x6687, 0xCAAC, 0x6688, 0xFDBB, 0x6689, 0xFDC7, 0x668E, 0xE7B2, 0x6690, 0xEAD1, 0x6691, 0xDFF4, + 0x6696, 0xD1EC, 0x6697, 0xE4DE, 0x6698, 0xE5C3, 0x669D, 0xD9A6, 0x66A0, 0xCDBC, 0x66A2, 0xF3E5, 0x66AB, 0xEDD5, 0x66AE, 0xD9BA, + 0x66B2, 0xEDE7, 0x66B3, 0xFBB5, 0x66B4, 0xF8EC, 0x66B9, 0xE0E7, 0x66BB, 0xCCD9, 0x66BE, 0xD4C6, 0x66C4, 0xE7A5, 0x66C6, 0xD5F5, + 0x66C7, 0xD3BE, 0x66C9, 0xFCFB, 0x66D6, 0xE4F2, 0x66D9, 0xDFF5, 0x66DC, 0xE8F8, 0x66DD, 0xF8ED, 0x66E0, 0xCEC7, 0x66E6, 0xFDF6, + 0x66F0, 0xE8D8, 0x66F2, 0xCDD8, 0x66F3, 0xE7D6, 0x66F4, 0xCCDA, 0x66F7, 0xCAE3, 0x66F8, 0xDFF6, 0x66F9, 0xF0C7, 0x66FA, 0xF0C6, + 0x66FC, 0xD8BA, 0x66FE, 0xF1F4, 0x66FF, 0xF4F0, 0x6700, 0xF5CC, 0x6703, 0xFCE5, 0x6708, 0xEAC5, 0x6709, 0xEAF3, 0x670B, 0xDDDB, + 0x670D, 0xDCD7, 0x6714, 0xDEFD, 0x6715, 0xF2F9, 0x6717, 0xD5C7, 0x671B, 0xD8D0, 0x671D, 0xF0C8, 0x671E, 0xD1A1, 0x671F, 0xD1A2, + 0x6726, 0xD9D4, 0x6727, 0xD6E8, 0x6728, 0xD9CA, 0x672A, 0xDAB1, 0x672B, 0xD8C7, 0x672C, 0xDCE2, 0x672D, 0xF3CE, 0x672E, 0xF5F4, + 0x6731, 0xF1B9, 0x6734, 0xDAD3, 0x6736, 0xF6EA, 0x673A, 0xCFF5, 0x673D, 0xFDAE, 0x6746, 0xCAD2, 0x6749, 0xDFB4, 0x674E, 0xD7DD, + 0x674F, 0xFABA, 0x6750, 0xEEA7, 0x6751, 0xF5BD, 0x6753, 0xF8F5, 0x6756, 0xEDE8, 0x675C, 0xD4E1, 0x675E, 0xD1A3, 0x675F, 0xE1D6, + 0x676D, 0xF9F8, 0x676F, 0xDBCA, 0x6770, 0xCBF9, 0x6771, 0xD4D4, 0x6773, 0xD9DC, 0x6775, 0xEEBE, 0x6777, 0xF7ED, 0x677B, 0xD2EE, + 0x677E, 0xE1E6, 0x677F, 0xF7F9, 0x6787, 0xDDED, 0x6789, 0xE8DB, 0x678B, 0xDBB3, 0x678F, 0xD1F7, 0x6790, 0xE0B0, 0x6793, 0xD4E2, + 0x6795, 0xF6D7, 0x6797, 0xD7F9, 0x679A, 0xD8DD, 0x679C, 0xCDFD, 0x679D, 0xF2AB, 0x67AF, 0xCDBD, 0x67B0, 0xF8C2, 0x67B3, 0xF2AC, + 0x67B6, 0xCAAD, 0x67B7, 0xCAAE, 0x67B8, 0xCFAE, 0x67BE, 0xE3C2, 0x67C4, 0xDCB7, 0x67CF, 0xDBDA, 0x67D0, 0xD9BB, 0x67D1, 0xCAF3, + 0x67D2, 0xF6D3, 0x67D3, 0xE6F8, 0x67D4, 0xEAF5, 0x67DA, 0xEAF6, 0x67DD, 0xF6F9, 0x67E9, 0xCFAF, 0x67EC, 0xCAD3, 0x67EF, 0xCAAF, + 0x67F0, 0xD2B0, 0x67F1, 0xF1BA, 0x67F3, 0xD7B3, 0x67F4, 0xE3C3, 0x67F5, 0xF3FD, 0x67F6, 0xDEDA, 0x67FB, 0xDEDB, 0x67FE, 0xEFDE, + 0x6812, 0xE2E3, 0x6813, 0xEEFB, 0x6816, 0xDFF7, 0x6817, 0xD7CA, 0x6821, 0xCEE8, 0x6822, 0xDBDB, 0x682A, 0xF1BB, 0x682F, 0xE9F1, + 0x6838, 0xFAB7, 0x6839, 0xD0C6, 0x683C, 0xCCAB, 0x683D, 0xEEA8, 0x6840, 0xCBFA, 0x6841, 0xF9F9, 0x6842, 0xCCFD, 0x6843, 0xD3FE, + 0x6848, 0xE4D0, 0x684E, 0xF2EE, 0x6850, 0xD4D5, 0x6851, 0xDFCD, 0x6853, 0xFCB8, 0x6854, 0xD1D0, 0x686D, 0xF2CD, 0x6876, 0xF7D2, + 0x687F, 0xCAD4, 0x6881, 0xD5D9, 0x6885, 0xD8DE, 0x688F, 0xCDD9, 0x6893, 0xEEA9, 0x6894, 0xF6BC, 0x6897, 0xCCDB, 0x689D, 0xF0C9, + 0x689F, 0xFCFC, 0x68A1, 0xE8C9, 0x68A2, 0xF4FE, 0x68A7, 0xE7FC, 0x68A8, 0xD7DE, 0x68AD, 0xDEDC, 0x68AF, 0xF0AC, 0x68B0, 0xCCFE, + 0x68B1, 0xCDE1, 0x68B3, 0xE1BA, 0x68B5, 0xDBEF, 0x68B6, 0xDAB2, 0x68C4, 0xD1A5, 0x68C5, 0xDCB8, 0x68C9, 0xD8F6, 0x68CB, 0xD1A4, + 0x68CD, 0xCDE2, 0x68D2, 0xDCEA, 0x68D5, 0xF0F7, 0x68D7, 0xF0CA, 0x68D8, 0xD0BE, 0x68DA, 0xDDDC, 0x68DF, 0xD4D6, 0x68E0, 0xD3D6, + 0x68E7, 0xEDD0, 0x68E8, 0xCDA1, 0x68EE, 0xDFB5, 0x68F2, 0xDFF8, 0x68F9, 0xD4A1, 0x68FA, 0xCEB2, 0x6900, 0xE8CA, 0x6905, 0xEBF5, + 0x690D, 0xE3D5, 0x690E, 0xF5D0, 0x6912, 0xF5A1, 0x6927, 0xD9A7, 0x6930, 0xE5AB, 0x693D, 0xE6CB, 0x693F, 0xF5F1, 0x694A, 0xE5C5, + 0x6953, 0xF9A3, 0x6954, 0xE0DB, 0x6955, 0xF6EB, 0x6957, 0xCBF1, 0x6959, 0xD9EA, 0x695A, 0xF5A2, 0x695E, 0xD7D1, 0x6960, 0xD1F8, + 0x6961, 0xEAF8, 0x6962, 0xEAF9, 0x6963, 0xDAB3, 0x6968, 0xEFDF, 0x696B, 0xF1EF, 0x696D, 0xE5F6, 0x696E, 0xEEBF, 0x696F, 0xE2E4, + 0x6975, 0xD0BF, 0x6977, 0xFAAC, 0x6978, 0xF5D1, 0x6979, 0xE7B3, 0x6995, 0xE9BE, 0x699B, 0xF2CE, 0x699C, 0xDBB4, 0x69A5, 0xFCCE, + 0x69A7, 0xDDEE, 0x69AE, 0xE7B4, 0x69B4, 0xD7B4, 0x69BB, 0xF7B4, 0x69C1, 0xCDBE, 0x69C3, 0xDAE9, 0x69CB, 0xCFB0, 0x69CC, 0xF7D9, + 0x69CD, 0xF3E6, 0x69D0, 0xCED9, 0x69E8, 0xCEAA, 0x69EA, 0xCBC8, 0x69FB, 0xD0A7, 0x69FD, 0xF0CB, 0x69FF, 0xD0C7, 0x6A02, 0xE4C5, + 0x6A0A, 0xDBE0, 0x6A11, 0xD5DA, 0x6A13, 0xD7A7, 0x6A17, 0xEEC0, 0x6A19, 0xF8F6, 0x6A1E, 0xF5D2, 0x6A1F, 0xEDE9, 0x6A21, 0xD9BC, + 0x6A23, 0xE5C6, 0x6A35, 0xF5A3, 0x6A38, 0xDAD4, 0x6A39, 0xE2A7, 0x6A3A, 0xFBFC, 0x6A3D, 0xF1DC, 0x6A44, 0xCAF4, 0x6A48, 0xE8FA, + 0x6A4B, 0xCEE9, 0x6A52, 0xE9F8, 0x6A53, 0xE2E5, 0x6A58, 0xD0B9, 0x6A59, 0xD4F2, 0x6A5F, 0xD1A6, 0x6A61, 0xDFCE, 0x6A6B, 0xFCF4, + 0x6A80, 0xD3AA, 0x6A84, 0xCCAC, 0x6A89, 0xEFE0, 0x6A8D, 0xE5E5, 0x6A8E, 0xD0D5, 0x6A97, 0xDBFC, 0x6A9C, 0xFCE6, 0x6AA2, 0xCBFE, + 0x6AA3, 0xEDEA, 0x6AB3, 0xDEB1, 0x6ABB, 0xF9E3, 0x6AC2, 0xD4A2, 0x6AC3, 0xCFF6, 0x6AD3, 0xD6D0, 0x6ADA, 0xD5EA, 0x6ADB, 0xF1EE, + 0x6AF6, 0xFACB, 0x6AFB, 0xE5A1, 0x6B04, 0xD5B1, 0x6B0A, 0xCFED, 0x6B0C, 0xEDEB, 0x6B12, 0xD5B2, 0x6B16, 0xD5BC, 0x6B20, 0xFDE2, + 0x6B21, 0xF3AD, 0x6B23, 0xFDDB, 0x6B32, 0xE9B0, 0x6B3A, 0xD1A7, 0x6B3D, 0xFDE3, 0x6B3E, 0xCEB3, 0x6B46, 0xFDE4, 0x6B47, 0xFACE, + 0x6B4C, 0xCAB0, 0x6B4E, 0xF7A7, 0x6B50, 0xCFB1, 0x6B5F, 0xE6A2, 0x6B61, 0xFCB6, 0x6B62, 0xF2AD, 0x6B63, 0xEFE1, 0x6B64, 0xF3AE, + 0x6B65, 0xDCC6, 0x6B66, 0xD9EB, 0x6B6A, 0xE8E0, 0x6B72, 0xE1A8, 0x6B77, 0xD5F6, 0x6B78, 0xCFFD, 0x6B7B, 0xDEDD, 0x6B7F, 0xD9D1, + 0x6B83, 0xE4EA, 0x6B84, 0xF2CF, 0x6B86, 0xF7BF, 0x6B89, 0xE2E6, 0x6B8A, 0xE2A8, 0x6B96, 0xE3D6, 0x6B98, 0xEDD1, 0x6B9E, 0xE9F9, + 0x6BAE, 0xD6B1, 0x6BAF, 0xDEB2, 0x6BB2, 0xE0E8, 0x6BB5, 0xD3AB, 0x6BB7, 0xEBDC, 0x6BBA, 0xDFAF, 0x6BBC, 0xCAC3, 0x6BBF, 0xEEFC, + 0x6BC1, 0xFDC3, 0x6BC5, 0xEBF6, 0x6BC6, 0xCFB2, 0x6BCB, 0xD9EC, 0x6BCD, 0xD9BD, 0x6BCF, 0xD8DF, 0x6BD2, 0xD4B8, 0x6BD3, 0xEBBE, + 0x6BD4, 0xDDEF, 0x6BD6, 0xDDF0, 0x6BD7, 0xDDF1, 0x6BD8, 0xDDF2, 0x6BDB, 0xD9BE, 0x6BEB, 0xFBC6, 0x6BEC, 0xCFB3, 0x6C08, 0xEEFD, + 0x6C0F, 0xE4AB, 0x6C11, 0xDAC5, 0x6C13, 0xD8EC, 0x6C23, 0xD1A8, 0x6C34, 0xE2A9, 0x6C37, 0xDEBC, 0x6C38, 0xE7B5, 0x6C3E, 0xDBF0, + 0x6C40, 0xEFE2, 0x6C41, 0xF1F0, 0x6C42, 0xCFB4, 0x6C4E, 0xDBF1, 0x6C50, 0xE0B1, 0x6C55, 0xDFA5, 0x6C57, 0xF9D2, 0x6C5A, 0xE7FD, + 0x6C5D, 0xE6A3, 0x6C5E, 0xFBF1, 0x6C5F, 0xCBB0, 0x6C60, 0xF2AE, 0x6C68, 0xCDE7, 0x6C6A, 0xE8DC, 0x6C6D, 0xE7D7, 0x6C70, 0xF7C0, + 0x6C72, 0xD0E3, 0x6C76, 0xDAA1, 0x6C7A, 0xCCBD, 0x6C7D, 0xD1A9, 0x6C7E, 0xDDCC, 0x6C81, 0xE3FE, 0x6C82, 0xD1AA, 0x6C83, 0xE8AA, + 0x6C85, 0xEAB6, 0x6C86, 0xF9FA, 0x6C87, 0xE6CC, 0x6C88, 0xF6D8, 0x6C8C, 0xD4C7, 0x6C90, 0xD9CB, 0x6C92, 0xD9D2, 0x6C93, 0xD3CB, + 0x6C94, 0xD8F7, 0x6C95, 0xDAA9, 0x6C96, 0xF5F8, 0x6C99, 0xDEDE, 0x6C9A, 0xF2AF, 0x6C9B, 0xF8A9, 0x6CAB, 0xD8C8, 0x6CAE, 0xEEC1, + 0x6CB3, 0xF9C1, 0x6CB8, 0xDDF3, 0x6CB9, 0xEAFA, 0x6CBB, 0xF6BD, 0x6CBC, 0xE1BB, 0x6CBD, 0xCDBF, 0x6CBE, 0xF4D4, 0x6CBF, 0xE6CD, + 0x6CC1, 0xFCCF, 0x6CC2, 0xFBA2, 0x6CC4, 0xE0DC, 0x6CC9, 0xF4BB, 0x6CCA, 0xDAD5, 0x6CCC, 0xF9B2, 0x6CD3, 0xFBF2, 0x6CD5, 0xDBF6, + 0x6CD7, 0xDEDF, 0x6CDB, 0xDBF2, 0x6CE1, 0xF8DC, 0x6CE2, 0xF7EE, 0x6CE3, 0xEBE8, 0x6CE5, 0xD2FA, 0x6CE8, 0xF1BC, 0x6CEB, 0xFADA, + 0x6CEE, 0xDAEA, 0x6CEF, 0xDAC6, 0x6CF0, 0xF7C1, 0x6CF3, 0xE7B6, 0x6D0B, 0xE5C7, 0x6D0C, 0xD6AC, 0x6D11, 0xDCC7, 0x6D17, 0xE1A9, + 0x6D19, 0xE2AA, 0x6D1B, 0xD5A6, 0x6D1E, 0xD4D7, 0x6D25, 0xF2D0, 0x6D27, 0xEAFB, 0x6D29, 0xE0DD, 0x6D2A, 0xFBF3, 0x6D32, 0xF1BD, + 0x6D35, 0xE2E7, 0x6D36, 0xFDD7, 0x6D38, 0xCEC8, 0x6D39, 0xEAB7, 0x6D3B, 0xFCC0, 0x6D3D, 0xFDE7, 0x6D3E, 0xF7EF, 0x6D41, 0xD7B5, + 0x6D59, 0xEFBA, 0x6D5A, 0xF1DD, 0x6D5C, 0xDEB3, 0x6D63, 0xE8CB, 0x6D66, 0xF8DD, 0x6D69, 0xFBC7, 0x6D6A, 0xD5C8, 0x6D6C, 0xD7DF, + 0x6D6E, 0xDDA9, 0x6D74, 0xE9B1, 0x6D77, 0xFAAD, 0x6D78, 0xF6D9, 0x6D79, 0xFAF4, 0x6D7F, 0xF8AA, 0x6D85, 0xE6EE, 0x6D87, 0xCCDC, + 0x6D88, 0xE1BC, 0x6D89, 0xE0EF, 0x6D8C, 0xE9BF, 0x6D8D, 0xFCFD, 0x6D8E, 0xE6CE, 0x6D91, 0xE1D7, 0x6D93, 0xE6CF, 0x6D95, 0xF4F1, + 0x6DAF, 0xE4F3, 0x6DB2, 0xE4FB, 0x6DB5, 0xF9E4, 0x6DC0, 0xEFE3, 0x6DC3, 0xCFEE, 0x6DC4, 0xF6BE, 0x6DC5, 0xE0B2, 0x6DC6, 0xFCFE, + 0x6DC7, 0xD1AB, 0x6DCB, 0xD7FA, 0x6DCF, 0xFBC8, 0x6DD1, 0xE2D7, 0x6DD8, 0xD4A3, 0x6DD9, 0xF0F8, 0x6DDA, 0xD7A8, 0x6DDE, 0xE1E7, + 0x6DE1, 0xD3BF, 0x6DE8, 0xEFE4, 0x6DEA, 0xD7C5, 0x6DEB, 0xEBE2, 0x6DEE, 0xFCE7, 0x6DF1, 0xE4A2, 0x6DF3, 0xE2E8, 0x6DF5, 0xE6D0, + 0x6DF7, 0xFBE8, 0x6DF8, 0xF4E8, 0x6DF9, 0xE5F4, 0x6DFA, 0xF4BC, 0x6DFB, 0xF4D5, 0x6E17, 0xDFB6, 0x6E19, 0xFCB9, 0x6E1A, 0xEEC2, + 0x6E1B, 0xCAF5, 0x6E1F, 0xEFE5, 0x6E20, 0xCBE2, 0x6E21, 0xD4A4, 0x6E23, 0xDEE0, 0x6E24, 0xDAFD, 0x6E25, 0xE4C6, 0x6E26, 0xE8BE, + 0x6E2B, 0xE0DE, 0x6E2C, 0xF6B4, 0x6E2D, 0xEAD2, 0x6E2F, 0xF9FB, 0x6E32, 0xE0C2, 0x6E34, 0xCAE4, 0x6E36, 0xE7B7, 0x6E38, 0xEAFD, + 0x6E3A, 0xD9DD, 0x6E3C, 0xDAB4, 0x6E3D, 0xEEAA, 0x6E3E, 0xFBE9, 0x6E43, 0xDBCB, 0x6E44, 0xDAB5, 0x6E4A, 0xF1BE, 0x6E4D, 0xD3AC, + 0x6E56, 0xFBC9, 0x6E58, 0xDFCF, 0x6E5B, 0xD3C0, 0x6E5C, 0xE3D7, 0x6E5E, 0xEFE6, 0x6E5F, 0xFCD0, 0x6E67, 0xE9C0, 0x6E6B, 0xF5D3, + 0x6E6E, 0xECDC, 0x6E6F, 0xF7B7, 0x6E72, 0xEAB8, 0x6E73, 0xD1F9, 0x6E7A, 0xDCC8, 0x6E90, 0xEAB9, 0x6E96, 0xF1DE, 0x6E9C, 0xD7B6, + 0x6E9D, 0xCFB5, 0x6E9F, 0xD9A8, 0x6EA2, 0xECEE, 0x6EA5, 0xDDAA, 0x6EAA, 0xCDA2, 0x6EAB, 0xE8AE, 0x6EAF, 0xE1BD, 0x6EB1, 0xF2D1, + 0x6EB6, 0xE9C1, 0x6EBA, 0xD2FC, 0x6EC2, 0xDBB5, 0x6EC4, 0xF3E7, 0x6EC5, 0xD8FE, 0x6EC9, 0xFCD1, 0x6ECB, 0xEDB2, 0x6ECC, 0xF4AF, + 0x6ECE, 0xFBA3, 0x6ED1, 0xFCC1, 0x6ED3, 0xEEAB, 0x6ED4, 0xD4A5, 0x6EEF, 0xF4F2, 0x6EF4, 0xEED9, 0x6EF8, 0xFBCA, 0x6EFE, 0xCDE3, + 0x6EFF, 0xD8BB, 0x6F01, 0xE5DB, 0x6F02, 0xF8F7, 0x6F06, 0xF6D4, 0x6F0F, 0xD7A9, 0x6F11, 0xCBC9, 0x6F14, 0xE6D1, 0x6F15, 0xF0CC, + 0x6F20, 0xD8AE, 0x6F22, 0xF9D3, 0x6F23, 0xD5FE, 0x6F2B, 0xD8BC, 0x6F2C, 0xF2B0, 0x6F31, 0xE2AB, 0x6F32, 0xF3E8, 0x6F38, 0xEFC2, + 0x6F3F, 0xEDEC, 0x6F41, 0xE7B8, 0x6F51, 0xDAFE, 0x6F54, 0xCCBE, 0x6F57, 0xF2FC, 0x6F58, 0xDAEB, 0x6F5A, 0xE2D8, 0x6F5B, 0xEDD6, + 0x6F5E, 0xD6D1, 0x6F5F, 0xE0B3, 0x6F62, 0xFCD2, 0x6F64, 0xEBC8, 0x6F6D, 0xD3C1, 0x6F6E, 0xF0CD, 0x6F70, 0xCFF7, 0x6F7A, 0xEDD2, + 0x6F7C, 0xD4D8, 0x6F7D, 0xDCC9, 0x6F7E, 0xD7F1, 0x6F81, 0xDFBB, 0x6F84, 0xF3A5, 0x6F88, 0xF4CD, 0x6F8D, 0xF1BF, 0x6F8E, 0xF8B1, + 0x6F90, 0xE9FA, 0x6F94, 0xFBCB, 0x6F97, 0xCAD5, 0x6FA3, 0xF9D4, 0x6FA4, 0xF7CA, 0x6FA7, 0xD6C8, 0x6FAE, 0xFCE8, 0x6FAF, 0xF3BD, + 0x6FB1, 0xEEFE, 0x6FB3, 0xE7FE, 0x6FB9, 0xD3C2, 0x6FBE, 0xD3B6, 0x6FC0, 0xCCAD, 0x6FC1, 0xF6FA, 0x6FC2, 0xD6B2, 0x6FC3, 0xD2D8, + 0x6FCA, 0xE7D8, 0x6FD5, 0xE3A5, 0x6FDA, 0xE7B9, 0x6FDF, 0xF0AD, 0x6FE0, 0xFBCC, 0x6FE1, 0xEBA1, 0x6FE4, 0xD4A6, 0x6FE9, 0xFBCD, + 0x6FEB, 0xD5BD, 0x6FEC, 0xF1DF, 0x6FEF, 0xF6FB, 0x6FF1, 0xDEB4, 0x6FFE, 0xD5EB, 0x7001, 0xE5C8, 0x7005, 0xFBA4, 0x7006, 0xD4B9, + 0x7009, 0xDEE1, 0x700B, 0xE4A3, 0x700F, 0xD7B7, 0x7011, 0xF8EE, 0x7015, 0xDEB5, 0x7018, 0xD6D2, 0x701A, 0xF9D5, 0x701B, 0xE7BA, + 0x701C, 0xEBD5, 0x701D, 0xD5F7, 0x701E, 0xEFE7, 0x701F, 0xE1BE, 0x7023, 0xFAAE, 0x7027, 0xD6E9, 0x7028, 0xD6EE, 0x702F, 0xE7BB, + 0x7037, 0xECCB, 0x703E, 0xD5B3, 0x704C, 0xCEB4, 0x7050, 0xFBA5, 0x7051, 0xE1EE, 0x7058, 0xF7A8, 0x705D, 0xFBCE, 0x7063, 0xD8BD, + 0x706B, 0xFBFD, 0x7070, 0xFCE9, 0x7078, 0xCFB6, 0x707C, 0xEDC7, 0x707D, 0xEEAC, 0x7085, 0xCCDD, 0x708A, 0xF6A7, 0x708E, 0xE6FA, + 0x7092, 0xF5A4, 0x7098, 0xFDDC, 0x7099, 0xEDB3, 0x709A, 0xCEC9, 0x70A1, 0xEFE8, 0x70A4, 0xE1BF, 0x70AB, 0xFADB, 0x70AC, 0xCBE3, + 0x70AD, 0xF7A9, 0x70AF, 0xFBA6, 0x70B3, 0xDCB9, 0x70B7, 0xF1C0, 0x70B8, 0xEDC8, 0x70B9, 0xEFC3, 0x70C8, 0xD6AD, 0x70CB, 0xFDCE, + 0x70CF, 0xE8A1, 0x70D8, 0xFBF4, 0x70D9, 0xD5A7, 0x70DD, 0xF1F6, 0x70DF, 0xE6D3, 0x70F1, 0xCCDE, 0x70F9, 0xF8B2, 0x70FD, 0xDCEB, + 0x7104, 0xFDB6, 0x7109, 0xE5EA, 0x710C, 0xF1E0, 0x7119, 0xDBCC, 0x711A, 0xDDCD, 0x711E, 0xD4C8, 0x7121, 0xD9ED, 0x7126, 0xF5A5, + 0x7130, 0xE6FB, 0x7136, 0xE6D4, 0x7147, 0xFDC8, 0x7149, 0xD6A1, 0x714A, 0xFDBF, 0x714C, 0xFCD3, 0x714E, 0xEFA1, 0x7150, 0xE7BC, + 0x7156, 0xD1EE, 0x7159, 0xE6D5, 0x715C, 0xE9F2, 0x715E, 0xDFB0, 0x7164, 0xD8E0, 0x7165, 0xFCBA, 0x7166, 0xFDAF, 0x7167, 0xF0CE, + 0x7169, 0xDBE1, 0x716C, 0xE5C9, 0x716E, 0xEDB4, 0x717D, 0xE0C3, 0x7184, 0xE3D8, 0x7189, 0xE9FB, 0x718A, 0xEAA8, 0x718F, 0xFDB7, + 0x7192, 0xFBA7, 0x7194, 0xE9C2, 0x7199, 0xFDF7, 0x719F, 0xE2D9, 0x71A2, 0xDCEC, 0x71AC, 0xE8A2, 0x71B1, 0xE6F0, 0x71B9, 0xFDF8, + 0x71BA, 0xFDF9, 0x71BE, 0xF6BF, 0x71C1, 0xE7A7, 0x71C3, 0xE6D7, 0x71C8, 0xD4F3, 0x71C9, 0xD4C9, 0x71CE, 0xD6FA, 0x71D0, 0xD7F2, + 0x71D2, 0xE1C0, 0x71D4, 0xDBE2, 0x71D5, 0xE6D8, 0x71DF, 0xE7BD, 0x71E5, 0xF0CF, 0x71E6, 0xF3BE, 0x71E7, 0xE2AC, 0x71ED, 0xF5B7, + 0x71EE, 0xE0F0, 0x71FB, 0xFDB8, 0x71FC, 0xE3E8, 0x71FE, 0xD4A7, 0x71FF, 0xE8FC, 0x7200, 0xFAD2, 0x7206, 0xF8EF, 0x7210, 0xD6D3, + 0x721B, 0xD5B4, 0x722A, 0xF0D0, 0x722C, 0xF7F0, 0x722D, 0xEEB3, 0x7230, 0xEABA, 0x7232, 0xEAD3, 0x7235, 0xEDC9, 0x7236, 0xDDAB, + 0x723A, 0xE5AC, 0x723B, 0xFDA1, 0x723D, 0xDFD0, 0x723E, 0xECB3, 0x7240, 0xDFD1, 0x7246, 0xEDED, 0x7247, 0xF8B8, 0x7248, 0xF7FA, + 0x724C, 0xF8AB, 0x7252, 0xF4E0, 0x7258, 0xD4BA, 0x7259, 0xE4B3, 0x725B, 0xE9DA, 0x725D, 0xDEB6, 0x725F, 0xD9BF, 0x7261, 0xD9C0, + 0x7262, 0xD6EF, 0x7267, 0xD9CC, 0x7269, 0xDAAA, 0x7272, 0xDFE5, 0x7279, 0xF7E5, 0x727D, 0xCCB2, 0x7280, 0xDFF9, 0x7281, 0xD7E0, + 0x72A2, 0xD4BB, 0x72A7, 0xFDFA, 0x72AC, 0xCCB3, 0x72AF, 0xDBF3, 0x72C0, 0xDFD2, 0x72C2, 0xCECA, 0x72C4, 0xEEDA, 0x72CE, 0xE4E4, + 0x72D0, 0xFBCF, 0x72D7, 0xCFB7, 0x72D9, 0xEEC3, 0x72E1, 0xCEEA, 0x72E9, 0xE2AD, 0x72F8, 0xD7E1, 0x72F9, 0xFAF5, 0x72FC, 0xD5C9, + 0x72FD, 0xF8AC, 0x730A, 0xE7D9, 0x7316, 0xF3E9, 0x731B, 0xD8ED, 0x731C, 0xE3C4, 0x731D, 0xF0F1, 0x7325, 0xE8E5, 0x7329, 0xE0FA, + 0x732A, 0xEEC4, 0x732B, 0xD9DE, 0x7336, 0xEBA2, 0x7337, 0xEBA3, 0x733E, 0xFCC2, 0x733F, 0xEABB, 0x7344, 0xE8AB, 0x7345, 0xDEE2, + 0x7350, 0xEDEF, 0x7352, 0xE8A3, 0x7357, 0xCFF1, 0x7368, 0xD4BC, 0x736A, 0xFCEA, 0x7370, 0xE7BE, 0x7372, 0xFCF2, 0x7375, 0xD6B4, + 0x7378, 0xE2AE, 0x737A, 0xD3B7, 0x737B, 0xFACC, 0x7384, 0xFADC, 0x7386, 0xEDB5, 0x7387, 0xE1E3, 0x7389, 0xE8AC, 0x738B, 0xE8DD, + 0x738E, 0xEFE9, 0x7394, 0xF4BD, 0x7396, 0xCFB8, 0x7397, 0xE9DB, 0x7398, 0xD1AC, 0x739F, 0xDAC7, 0x73A7, 0xEBC9, 0x73A9, 0xE8CC, + 0x73AD, 0xDEB7, 0x73B2, 0xD6BC, 0x73B3, 0xD3E5, 0x73B9, 0xFADD, 0x73C0, 0xDAD6, 0x73C2, 0xCAB1, 0x73C9, 0xDAC8, 0x73CA, 0xDFA6, + 0x73CC, 0xF9B3, 0x73CD, 0xF2D2, 0x73CF, 0xCAC4, 0x73D6, 0xCECB, 0x73D9, 0xCDF5, 0x73DD, 0xFDB0, 0x73DE, 0xD5A8, 0x73E0, 0xF1C1, + 0x73E3, 0xE2E9, 0x73E4, 0xDCCA, 0x73E5, 0xECB4, 0x73E6, 0xFAC0, 0x73E9, 0xFBA8, 0x73EA, 0xD0A8, 0x73ED, 0xDAEC, 0x73F7, 0xD9EE, + 0x73F9, 0xE0FB, 0x73FD, 0xEFEA, 0x73FE, 0xFADE, 0x7401, 0xE0C4, 0x7403, 0xCFB9, 0x7405, 0xD5CA, 0x7406, 0xD7E2, 0x7407, 0xE2AF, + 0x7409, 0xD7B8, 0x7413, 0xE8CD, 0x741B, 0xF6DA, 0x7420, 0xEFA2, 0x7421, 0xE2DA, 0x7422, 0xF6FC, 0x7425, 0xFBD0, 0x7426, 0xD1AD, + 0x7428, 0xCDE4, 0x742A, 0xD1AE, 0x742B, 0xDCED, 0x742C, 0xE8CE, 0x742E, 0xF0F9, 0x742F, 0xCEB5, 0x7430, 0xE6FC, 0x7433, 0xD7FB, + 0x7434, 0xD0D6, 0x7435, 0xDDF5, 0x7436, 0xF7F1, 0x7438, 0xF6FD, 0x743A, 0xDBF7, 0x743F, 0xFBEA, 0x7440, 0xE9DC, 0x7441, 0xD9C1, + 0x7443, 0xF5F2, 0x7444, 0xE0C5, 0x744B, 0xEAD4, 0x7455, 0xF9C2, 0x7457, 0xEABC, 0x7459, 0xD2C5, 0x745A, 0xFBD1, 0x745B, 0xE7C0, + 0x745C, 0xEBA5, 0x745E, 0xDFFA, 0x745F, 0xE3A2, 0x7460, 0xD7B9, 0x7462, 0xE9C3, 0x7464, 0xE8FD, 0x7465, 0xE8AF, 0x7468, 0xF2D3, + 0x7469, 0xFBA9, 0x746A, 0xD8A5, 0x746F, 0xD5CB, 0x747E, 0xD0C8, 0x7482, 0xD1AF, 0x7483, 0xD7E3, 0x7487, 0xE0C6, 0x7489, 0xD6A2, + 0x748B, 0xEDF0, 0x7498, 0xD7F3, 0x749C, 0xFCD4, 0x749E, 0xDAD7, 0x749F, 0xCCDF, 0x74A1, 0xF2D4, 0x74A3, 0xD1B0, 0x74A5, 0xCCE0, + 0x74A7, 0xDBFD, 0x74A8, 0xF3BF, 0x74AA, 0xF0D1, 0x74B0, 0xFCBB, 0x74B2, 0xE2B0, 0x74B5, 0xE6A5, 0x74B9, 0xE2DB, 0x74BD, 0xDFDE, + 0x74BF, 0xE0C7, 0x74C6, 0xF2EF, 0x74CA, 0xCCE1, 0x74CF, 0xD6EA, 0x74D4, 0xE7C2, 0x74D8, 0xCEB6, 0x74DA, 0xF3C0, 0x74DC, 0xCDFE, + 0x74E0, 0xFBD2, 0x74E2, 0xF8F8, 0x74E3, 0xF7FB, 0x74E6, 0xE8BF, 0x74EE, 0xE8B7, 0x74F7, 0xEDB6, 0x7501, 0xDCBA, 0x7504, 0xCCB4, + 0x7511, 0xF1F7, 0x7515, 0xE8B8, 0x7518, 0xCAF6, 0x751A, 0xE4A4, 0x751B, 0xF4D6, 0x751F, 0xDFE6, 0x7523, 0xDFA7, 0x7525, 0xDFE7, + 0x7526, 0xE1C1, 0x7528, 0xE9C4, 0x752B, 0xDCCB, 0x752C, 0xE9C5, 0x7530, 0xEFA3, 0x7531, 0xEBA6, 0x7532, 0xCBA3, 0x7533, 0xE3E9, + 0x7537, 0xD1FB, 0x7538, 0xEFA4, 0x753A, 0xEFEB, 0x7547, 0xD0B4, 0x754C, 0xCDA3, 0x754F, 0xE8E6, 0x7551, 0xEFA5, 0x7553, 0xD3CC, + 0x7554, 0xDAED, 0x7559, 0xD7BA, 0x755B, 0xF2D5, 0x755C, 0xF5E5, 0x755D, 0xD9EF, 0x7562, 0xF9B4, 0x7565, 0xD5D4, 0x7566, 0xFDCF, + 0x756A, 0xDBE3, 0x756F, 0xF1E1, 0x7570, 0xECB6, 0x7575, 0xFBFE, 0x7576, 0xD3D7, 0x7578, 0xD1B1, 0x757A, 0xCBB1, 0x757F, 0xD1B2, + 0x7586, 0xCBB2, 0x7587, 0xF1C2, 0x758A, 0xF4E1, 0x758B, 0xF9B5, 0x758E, 0xE1C3, 0x758F, 0xE1C2, 0x7591, 0xEBF7, 0x759D, 0xDFA8, + 0x75A5, 0xCBCA, 0x75AB, 0xE6B9, 0x75B1, 0xF8DE, 0x75B2, 0xF9AA, 0x75B3, 0xCAF7, 0x75B5, 0xEDB7, 0x75B8, 0xD3B8, 0x75B9, 0xF2D6, + 0x75BC, 0xD4D9, 0x75BD, 0xEEC5, 0x75BE, 0xF2F0, 0x75C2, 0xCAB2, 0x75C5, 0xDCBB, 0x75C7, 0xF1F8, 0x75CD, 0xECB7, 0x75D2, 0xE5CA, + 0x75D4, 0xF6C0, 0x75D5, 0xFDDD, 0x75D8, 0xD4E3, 0x75D9, 0xCCE2, 0x75DB, 0xF7D4, 0x75E2, 0xD7E5, 0x75F0, 0xD3C3, 0x75F2, 0xD8A6, + 0x75F4, 0xF6C1, 0x75FA, 0xDDF6, 0x75FC, 0xCDC0, 0x7600, 0xE5DC, 0x760D, 0xE5CB, 0x7619, 0xE1C4, 0x761F, 0xE8B0, 0x7620, 0xF4B0, + 0x7621, 0xF3EA, 0x7622, 0xDAEE, 0x7624, 0xD7BB, 0x7626, 0xE2B1, 0x763B, 0xD7AA, 0x7642, 0xD6FB, 0x764C, 0xE4DF, 0x764E, 0xCAD6, + 0x7652, 0xEBA8, 0x7656, 0xDBFE, 0x7661, 0xF6C2, 0x7664, 0xEFBB, 0x7669, 0xD4FD, 0x766C, 0xE0C8, 0x7670, 0xE8B9, 0x7672, 0xEFA6, + 0x7678, 0xCDA4, 0x767B, 0xD4F4, 0x767C, 0xDBA1, 0x767D, 0xDBDC, 0x767E, 0xDBDD, 0x7684, 0xEEDC, 0x7686, 0xCBCB, 0x7687, 0xFCD5, + 0x768E, 0xCEEB, 0x7690, 0xCDC1, 0x7693, 0xFBD3, 0x76AE, 0xF9AB, 0x76BA, 0xF5D4, 0x76BF, 0xD9A9, 0x76C2, 0xE9DD, 0x76C3, 0xDBCD, + 0x76C6, 0xDDCE, 0x76C8, 0xE7C3, 0x76CA, 0xECCC, 0x76D2, 0xF9EC, 0x76D6, 0xCBCC, 0x76DB, 0xE0FC, 0x76DC, 0xD4A8, 0x76DE, 0xEDD3, + 0x76DF, 0xD8EF, 0x76E1, 0xF2D7, 0x76E3, 0xCAF8, 0x76E4, 0xDAEF, 0x76E7, 0xD6D4, 0x76EE, 0xD9CD, 0x76F2, 0xD8EE, 0x76F4, 0xF2C1, + 0x76F8, 0xDFD3, 0x76FC, 0xDAF0, 0x76FE, 0xE2EA, 0x7701, 0xE0FD, 0x7704, 0xD8F8, 0x7708, 0xF7AF, 0x7709, 0xDAB6, 0x770B, 0xCAD7, + 0x771E, 0xF2D8, 0x7720, 0xD8F9, 0x7729, 0xFADF, 0x7737, 0xCFEF, 0x7738, 0xD9C2, 0x773A, 0xF0D2, 0x773C, 0xE4D1, 0x7740, 0xF3B7, + 0x774D, 0xFAE0, 0x775B, 0xEFEC, 0x7761, 0xE2B2, 0x7763, 0xD4BD, 0x7766, 0xD9CE, 0x776B, 0xF4E2, 0x7779, 0xD4A9, 0x777E, 0xCDC2, + 0x777F, 0xE7DA, 0x778B, 0xF2D9, 0x7791, 0xD9AA, 0x779E, 0xD8BE, 0x77A5, 0xDCAD, 0x77AC, 0xE2EB, 0x77AD, 0xD6FC, 0x77B0, 0xCAF9, + 0x77B3, 0xD4DA, 0x77BB, 0xF4D7, 0x77BC, 0xCCA1, 0x77BF, 0xCFBA, 0x77D7, 0xF5B8, 0x77DB, 0xD9C3, 0x77DC, 0xD0E8, 0x77E2, 0xE3C5, + 0x77E3, 0xEBF8, 0x77E5, 0xF2B1, 0x77E9, 0xCFBB, 0x77ED, 0xD3AD, 0x77EE, 0xE8E1, 0x77EF, 0xCEEC, 0x77F3, 0xE0B4, 0x7802, 0xDEE3, + 0x7812, 0xDDF7, 0x7825, 0xF2B2, 0x7826, 0xF3F6, 0x7827, 0xF6DB, 0x782C, 0xD7FE, 0x7832, 0xF8DF, 0x7834, 0xF7F2, 0x7845, 0xD0A9, + 0x784F, 0xE6DA, 0x785D, 0xF5A6, 0x786B, 0xD7BC, 0x786C, 0xCCE3, 0x786F, 0xE6DB, 0x787C, 0xDDDD, 0x7881, 0xD1B3, 0x7887, 0xEFED, + 0x788C, 0xD6DE, 0x788D, 0xE4F4, 0x788E, 0xE1EF, 0x7891, 0xDDF8, 0x7897, 0xE8CF, 0x78A3, 0xCAE5, 0x78A7, 0xDCA1, 0x78A9, 0xE0B5, + 0x78BA, 0xFCAC, 0x78BB, 0xFCAD, 0x78BC, 0xD8A7, 0x78C1, 0xEDB8, 0x78C5, 0xDBB6, 0x78CA, 0xD6F0, 0x78CB, 0xF3AF, 0x78CE, 0xCDA5, + 0x78D0, 0xDAF1, 0x78E8, 0xD8A8, 0x78EC, 0xCCE4, 0x78EF, 0xD1B4, 0x78F5, 0xCAD8, 0x78FB, 0xDAF2, 0x7901, 0xF5A7, 0x790E, 0xF5A8, + 0x7916, 0xE6A6, 0x792A, 0xD5EC, 0x792B, 0xD5F8, 0x792C, 0xDAF3, 0x793A, 0xE3C6, 0x793E, 0xDEE4, 0x7940, 0xDEE5, 0x7941, 0xD1B5, + 0x7947, 0xD1B6, 0x7948, 0xD1B7, 0x7949, 0xF2B3, 0x7950, 0xE9DE, 0x7956, 0xF0D3, 0x7957, 0xF2B4, 0x795A, 0xF0D4, 0x795B, 0xCBE4, + 0x795C, 0xFBD4, 0x795D, 0xF5E6, 0x795E, 0xE3EA, 0x7960, 0xDEE6, 0x7965, 0xDFD4, 0x7968, 0xF8F9, 0x796D, 0xF0AE, 0x797A, 0xD1B8, + 0x797F, 0xD6DF, 0x7981, 0xD0D7, 0x798D, 0xFCA1, 0x798E, 0xEFEE, 0x798F, 0xDCD8, 0x7991, 0xE9DF, 0x79A6, 0xE5DD, 0x79A7, 0xFDFB, + 0x79AA, 0xE0C9, 0x79AE, 0xD6C9, 0x79B1, 0xD4AA, 0x79B3, 0xE5CC, 0x79B9, 0xE9E0, 0x79BD, 0xD0D8, 0x79BE, 0xFCA2, 0x79BF, 0xD4BE, + 0x79C0, 0xE2B3, 0x79C1, 0xDEE7, 0x79C9, 0xDCBC, 0x79CA, 0xD2B6, 0x79CB, 0xF5D5, 0x79D1, 0xCEA1, 0x79D2, 0xF5A9, 0x79D5, 0xDDF9, + 0x79D8, 0xDDFA, 0x79DF, 0xF0D5, 0x79E4, 0xF6DF, 0x79E6, 0xF2DA, 0x79E7, 0xE4EB, 0x79E9, 0xF2F1, 0x79FB, 0xECB9, 0x7A00, 0xFDFC, + 0x7A05, 0xE1AA, 0x7A08, 0xCAD9, 0x7A0B, 0xEFEF, 0x7A0D, 0xF5AA, 0x7A14, 0xECF9, 0x7A17, 0xF8AD, 0x7A19, 0xF2C2, 0x7A1A, 0xF6C3, + 0x7A1C, 0xD7D2, 0x7A1F, 0xF9A2, 0x7A20, 0xF0D6, 0x7A2E, 0xF0FA, 0x7A31, 0xF6E0, 0x7A36, 0xE9F3, 0x7A37, 0xF2C3, 0x7A3B, 0xD4AB, + 0x7A3C, 0xCAB3, 0x7A3D, 0xCDA6, 0x7A3F, 0xCDC3, 0x7A40, 0xCDDA, 0x7A46, 0xD9CF, 0x7A49, 0xF6C4, 0x7A4D, 0xEEDD, 0x7A4E, 0xE7C4, + 0x7A57, 0xE2B4, 0x7A61, 0xDFE2, 0x7A62, 0xE7DB, 0x7A69, 0xE8B1, 0x7A6B, 0xFCAE, 0x7A70, 0xE5CD, 0x7A74, 0xFAEB, 0x7A76, 0xCFBC, + 0x7A79, 0xCFE2, 0x7A7A, 0xCDF6, 0x7A7D, 0xEFF0, 0x7A7F, 0xF4BE, 0x7A81, 0xD4CD, 0x7A84, 0xF3B8, 0x7A88, 0xE9A1, 0x7A92, 0xF2F2, + 0x7A93, 0xF3EB, 0x7A95, 0xF0D7, 0x7A98, 0xCFD7, 0x7A9F, 0xCFDF, 0x7AA9, 0xE8C0, 0x7AAA, 0xE8C1, 0x7AAE, 0xCFE3, 0x7AAF, 0xE9A2, + 0x7ABA, 0xD0AA, 0x7AC4, 0xF3C1, 0x7AC5, 0xD0AB, 0x7AC7, 0xD4E4, 0x7ACA, 0xEFBC, 0x7ACB, 0xD8A1, 0x7AD7, 0xD9DF, 0x7AD9, 0xF3D7, + 0x7ADD, 0xDCBD, 0x7ADF, 0xCCE5, 0x7AE0, 0xEDF1, 0x7AE3, 0xF1E2, 0x7AE5, 0xD4DB, 0x7AEA, 0xE2B5, 0x7AED, 0xCAE6, 0x7AEF, 0xD3AE, + 0x7AF6, 0xCCE6, 0x7AF9, 0xF1D3, 0x7AFA, 0xF5E7, 0x7AFF, 0xCADA, 0x7B0F, 0xFBEE, 0x7B11, 0xE1C5, 0x7B19, 0xDFE9, 0x7B1B, 0xEEDE, + 0x7B1E, 0xF7C2, 0x7B20, 0xD8A2, 0x7B26, 0xDDAC, 0x7B2C, 0xF0AF, 0x7B2D, 0xD6BD, 0x7B39, 0xE1AB, 0x7B46, 0xF9B6, 0x7B49, 0xD4F5, + 0x7B4B, 0xD0C9, 0x7B4C, 0xEFA7, 0x7B4D, 0xE2EC, 0x7B4F, 0xDBEA, 0x7B50, 0xCECC, 0x7B51, 0xF5E8, 0x7B52, 0xF7D5, 0x7B54, 0xD3CD, + 0x7B56, 0xF3FE, 0x7B60, 0xD0B5, 0x7B6C, 0xE0FE, 0x7B6E, 0xDFFB, 0x7B75, 0xE6DD, 0x7B7D, 0xE8A4, 0x7B87, 0xCBCD, 0x7B8B, 0xEFA8, + 0x7B8F, 0xEEB4, 0x7B94, 0xDAD8, 0x7B95, 0xD1B9, 0x7B97, 0xDFA9, 0x7B9A, 0xF3B0, 0x7B9D, 0xCCC4, 0x7BA1, 0xCEB7, 0x7BAD, 0xEFA9, + 0x7BB1, 0xDFD5, 0x7BB4, 0xEDD7, 0x7BB8, 0xEEC6, 0x7BC0, 0xEFBD, 0x7BC1, 0xFCD6, 0x7BC4, 0xDBF4, 0x7BC6, 0xEFAA, 0x7BC7, 0xF8B9, + 0x7BC9, 0xF5E9, 0x7BD2, 0xE3D9, 0x7BE0, 0xE1C6, 0x7BE4, 0xD4BF, 0x7BE9, 0xDEE8, 0x7C07, 0xF0EA, 0x7C12, 0xF3C2, 0x7C1E, 0xD3AF, + 0x7C21, 0xCADB, 0x7C27, 0xFCD7, 0x7C2A, 0xEDD8, 0x7C2B, 0xE1C7, 0x7C3D, 0xF4D8, 0x7C3E, 0xD6B3, 0x7C3F, 0xDDAD, 0x7C43, 0xD5BE, + 0x7C4C, 0xF1C3, 0x7C4D, 0xEEDF, 0x7C60, 0xD6EB, 0x7C64, 0xF4D9, 0x7C6C, 0xD7E6, 0x7C73, 0xDAB7, 0x7C83, 0xDDFB, 0x7C89, 0xDDCF, + 0x7C92, 0xD8A3, 0x7C95, 0xDAD9, 0x7C97, 0xF0D8, 0x7C98, 0xEFC4, 0x7C9F, 0xE1D8, 0x7CA5, 0xF1D4, 0x7CA7, 0xEDF2, 0x7CAE, 0xD5DB, + 0x7CB1, 0xD5DC, 0x7CB2, 0xF3C4, 0x7CB3, 0xCBD7, 0x7CB9, 0xE2B6, 0x7CBE, 0xEFF1, 0x7CCA, 0xFBD5, 0x7CD6, 0xD3D8, 0x7CDE, 0xDDD0, + 0x7CDF, 0xF0D9, 0x7CE0, 0xCBB3, 0x7CE7, 0xD5DD, 0x7CFB, 0xCDA7, 0x7CFE, 0xD0AC, 0x7D00, 0xD1BA, 0x7D02, 0xF1C4, 0x7D04, 0xE5B3, + 0x7D05, 0xFBF5, 0x7D06, 0xE9E1, 0x7D07, 0xFDE0, 0x7D08, 0xFCBC, 0x7D0A, 0xDAA2, 0x7D0B, 0xDAA3, 0x7D0D, 0xD2A1, 0x7D10, 0xD2EF, + 0x7D14, 0xE2ED, 0x7D17, 0xDEE9, 0x7D18, 0xCEDC, 0x7D19, 0xF2B5, 0x7D1A, 0xD0E4, 0x7D1B, 0xDDD1, 0x7D20, 0xE1C8, 0x7D21, 0xDBB7, + 0x7D22, 0xDFE3, 0x7D2B, 0xEDB9, 0x7D2C, 0xF1C5, 0x7D2E, 0xF3CF, 0x7D2F, 0xD7AB, 0x7D30, 0xE1AC, 0x7D33, 0xE3EB, 0x7D35, 0xEEC7, + 0x7D39, 0xE1C9, 0x7D3A, 0xCAFA, 0x7D42, 0xF0FB, 0x7D43, 0xFAE1, 0x7D44, 0xF0DA, 0x7D45, 0xCCE7, 0x7D46, 0xDAF4, 0x7D50, 0xCCBF, + 0x7D5E, 0xCEED, 0x7D61, 0xD5A9, 0x7D62, 0xFAE2, 0x7D66, 0xD0E5, 0x7D68, 0xEBD6, 0x7D6A, 0xECDF, 0x7D6E, 0xDFFC, 0x7D71, 0xF7D6, + 0x7D72, 0xDEEA, 0x7D73, 0xCBB4, 0x7D76, 0xEFBE, 0x7D79, 0xCCB5, 0x7D7F, 0xCFBD, 0x7D8E, 0xEFF2, 0x7D8F, 0xE2B7, 0x7D93, 0xCCE8, + 0x7D9C, 0xF0FC, 0x7DA0, 0xD6E0, 0x7DA2, 0xF1C6, 0x7DAC, 0xE2B8, 0x7DAD, 0xEBAB, 0x7DB1, 0xCBB5, 0x7DB2, 0xD8D1, 0x7DB4, 0xF4CE, + 0x7DB5, 0xF3F7, 0x7DB8, 0xD7C6, 0x7DBA, 0xD1BB, 0x7DBB, 0xF7AA, 0x7DBD, 0xEDCA, 0x7DBE, 0xD7D3, 0x7DBF, 0xD8FA, 0x7DC7, 0xF6C5, + 0x7DCA, 0xD1CC, 0x7DCB, 0xDDFC, 0x7DD6, 0xDFFD, 0x7DD8, 0xF9E5, 0x7DDA, 0xE0CA, 0x7DDD, 0xF2FD, 0x7DDE, 0xD3B0, 0x7DE0, 0xF4F3, + 0x7DE1, 0xDAC9, 0x7DE3, 0xE6DE, 0x7DE8, 0xF8BA, 0x7DE9, 0xE8D0, 0x7DEC, 0xD8FB, 0x7DEF, 0xEAD5, 0x7DF4, 0xD6A3, 0x7DFB, 0xF6C6, + 0x7E09, 0xF2DB, 0x7E0A, 0xE4FC, 0x7E15, 0xE8B2, 0x7E1B, 0xDADA, 0x7E1D, 0xF2DC, 0x7E1E, 0xFBD6, 0x7E1F, 0xE9B2, 0x7E21, 0xEEAD, + 0x7E23, 0xFAE3, 0x7E2B, 0xDCEE, 0x7E2E, 0xF5EA, 0x7E2F, 0xE6E0, 0x7E31, 0xF0FD, 0x7E37, 0xD7AC, 0x7E3D, 0xF5C5, 0x7E3E, 0xEEE0, + 0x7E41, 0xDBE5, 0x7E43, 0xDDDE, 0x7E46, 0xD9F0, 0x7E47, 0xE9A3, 0x7E52, 0xF1F9, 0x7E54, 0xF2C4, 0x7E55, 0xE0CB, 0x7E5E, 0xE9A4, + 0x7E61, 0xE2B9, 0x7E69, 0xE3B1, 0x7E6A, 0xFCEB, 0x7E6B, 0xCDA8, 0x7E6D, 0xCCB6, 0x7E70, 0xF0DB, 0x7E79, 0xE6BA, 0x7E7C, 0xCDA9, + 0x7E82, 0xF3C3, 0x7E8C, 0xE1D9, 0x7E8F, 0xEFAB, 0x7E93, 0xE7C5, 0x7E96, 0xE0E9, 0x7E98, 0xF3C5, 0x7E9B, 0xD4C0, 0x7E9C, 0xD5BF, + 0x7F36, 0xDDAE, 0x7F38, 0xF9FC, 0x7F3A, 0xCCC0, 0x7F4C, 0xE5A2, 0x7F50, 0xCEB8, 0x7F54, 0xD8D2, 0x7F55, 0xF9D6, 0x7F6A, 0xF1AA, + 0x7F6B, 0xCED1, 0x7F6E, 0xF6C7, 0x7F70, 0xDBEB, 0x7F72, 0xDFFE, 0x7F75, 0xD8E1, 0x7F77, 0xF7F3, 0x7F79, 0xD7E7, 0x7F85, 0xD4FE, + 0x7F88, 0xD1BC, 0x7F8A, 0xE5CF, 0x7F8C, 0xCBB6, 0x7F8E, 0xDAB8, 0x7F94, 0xCDC4, 0x7F9A, 0xD6BE, 0x7F9E, 0xE2BA, 0x7FA4, 0xCFD8, + 0x7FA8, 0xE0CC, 0x7FA9, 0xEBF9, 0x7FB2, 0xFDFD, 0x7FB8, 0xD7E8, 0x7FB9, 0xCBD8, 0x7FBD, 0xE9E2, 0x7FC1, 0xE8BA, 0x7FC5, 0xE3C7, + 0x7FCA, 0xECCD, 0x7FCC, 0xECCE, 0x7FCE, 0xD6BF, 0x7FD2, 0xE3A7, 0x7FD4, 0xDFD6, 0x7FD5, 0xFDE8, 0x7FDF, 0xEEE1, 0x7FE0, 0xF6A8, + 0x7FE1, 0xDDFD, 0x7FE9, 0xF8BB, 0x7FEB, 0xE8D1, 0x7FF0, 0xF9D7, 0x7FF9, 0xCEEE, 0x7FFC, 0xECCF, 0x8000, 0xE9A5, 0x8001, 0xD6D5, + 0x8003, 0xCDC5, 0x8005, 0xEDBA, 0x8006, 0xD1BD, 0x8009, 0xCFBE, 0x800C, 0xECBB, 0x8010, 0xD2B1, 0x8015, 0xCCE9, 0x8017, 0xD9C4, + 0x8018, 0xE9FC, 0x802D, 0xD1BE, 0x8033, 0xECBC, 0x8036, 0xE5AD, 0x803D, 0xF7B0, 0x803F, 0xCCEA, 0x8043, 0xD3C4, 0x8046, 0xD6C0, + 0x804A, 0xD6FD, 0x8056, 0xE1A1, 0x8058, 0xDEBD, 0x805A, 0xF6A9, 0x805E, 0xDAA4, 0x806F, 0xD6A4, 0x8070, 0xF5C6, 0x8072, 0xE1A2, + 0x8073, 0xE9C6, 0x8077, 0xF2C5, 0x807D, 0xF4E9, 0x807E, 0xD6EC, 0x807F, 0xEBD3, 0x8084, 0xECBD, 0x8085, 0xE2DC, 0x8086, 0xDEEB, + 0x8087, 0xF0DC, 0x8089, 0xEBBF, 0x808B, 0xD7CE, 0x808C, 0xD1BF, 0x8096, 0xF5AB, 0x809B, 0xF9FD, 0x809D, 0xCADC, 0x80A1, 0xCDC6, + 0x80A2, 0xF2B6, 0x80A5, 0xDDFE, 0x80A9, 0xCCB7, 0x80AA, 0xDBB8, 0x80AF, 0xD0E9, 0x80B1, 0xCEDD, 0x80B2, 0xEBC0, 0x80B4, 0xFDA2, + 0x80BA, 0xF8CB, 0x80C3, 0xEAD6, 0x80C4, 0xF1B0, 0x80CC, 0xDBCE, 0x80CE, 0xF7C3, 0x80DA, 0xDBCF, 0x80DB, 0xCBA4, 0x80DE, 0xF8E0, + 0x80E1, 0xFBD7, 0x80E4, 0xEBCA, 0x80E5, 0xE0A1, 0x80F1, 0xCECD, 0x80F4, 0xD4DC, 0x80F8, 0xFDD8, 0x80FD, 0xD2F6, 0x8102, 0xF2B7, + 0x8105, 0xFAF6, 0x8106, 0xF6AA, 0x8107, 0xFAF7, 0x8108, 0xD8E6, 0x810A, 0xF4B1, 0x8118, 0xE8D2, 0x811A, 0xCAC5, 0x811B, 0xCCEB, + 0x8123, 0xE2EE, 0x8129, 0xE2BB, 0x812B, 0xF7AD, 0x812F, 0xF8E1, 0x8139, 0xF3EC, 0x813E, 0xDEA1, 0x814B, 0xE4FD, 0x814E, 0xE3EC, + 0x8150, 0xDDAF, 0x8151, 0xDDB0, 0x8154, 0xCBB7, 0x8155, 0xE8D3, 0x8165, 0xE1A3, 0x8166, 0xD2E0, 0x816B, 0xF0FE, 0x8170, 0xE9A6, + 0x8171, 0xCBF2, 0x8178, 0xEDF3, 0x8179, 0xDCD9, 0x817A, 0xE0CD, 0x817F, 0xF7DA, 0x8180, 0xDBB9, 0x8188, 0xCCAE, 0x818A, 0xDADB, + 0x818F, 0xCDC7, 0x819A, 0xDDB1, 0x819C, 0xD8AF, 0x819D, 0xE3A3, 0x81A0, 0xCEEF, 0x81A3, 0xF2F3, 0x81A8, 0xF8B3, 0x81B3, 0xE0CE, + 0x81B5, 0xF5FD, 0x81BA, 0xEBEC, 0x81BD, 0xD3C5, 0x81BE, 0xFCEC, 0x81BF, 0xD2DB, 0x81C0, 0xD4EB, 0x81C2, 0xDEA2, 0x81C6, 0xE5E6, + 0x81CD, 0xF0B0, 0x81D8, 0xD5C4, 0x81DF, 0xEDF4, 0x81E3, 0xE3ED, 0x81E5, 0xE8C2, 0x81E7, 0xEDF5, 0x81E8, 0xD7FC, 0x81EA, 0xEDBB, + 0x81ED, 0xF6AB, 0x81F3, 0xF2B8, 0x81F4, 0xF6C8, 0x81FA, 0xD3E6, 0x81FB, 0xF2DD, 0x81FC, 0xCFBF, 0x81FE, 0xEBAC, 0x8205, 0xCFC0, + 0x8207, 0xE6A8, 0x8208, 0xFDE9, 0x820A, 0xCFC1, 0x820C, 0xE0DF, 0x820D, 0xDEEC, 0x8212, 0xE0A2, 0x821B, 0xF4BF, 0x821C, 0xE2EF, + 0x821E, 0xD9F1, 0x821F, 0xF1C7, 0x8221, 0xCBB8, 0x822A, 0xF9FE, 0x822B, 0xDBBA, 0x822C, 0xDAF5, 0x8235, 0xF6EC, 0x8236, 0xDADC, + 0x8237, 0xFAE4, 0x8239, 0xE0CF, 0x8240, 0xDDB2, 0x8245, 0xE6A9, 0x8247, 0xEFF3, 0x8259, 0xF3ED, 0x8264, 0xEBFA, 0x8266, 0xF9E6, + 0x826E, 0xCADD, 0x826F, 0xD5DE, 0x8271, 0xCADE, 0x8272, 0xDFE4, 0x8276, 0xE6FD, 0x8278, 0xF5AC, 0x827E, 0xE4F5, 0x828B, 0xE9E3, + 0x828D, 0xEDCB, 0x828E, 0xCFE4, 0x8292, 0xD8D3, 0x8299, 0xDDB3, 0x829A, 0xD4EC, 0x829D, 0xF2B9, 0x829F, 0xDFB7, 0x82A5, 0xCBCE, + 0x82A6, 0xFBD8, 0x82A9, 0xD0D9, 0x82AC, 0xDDD2, 0x82AD, 0xF7F4, 0x82AE, 0xE7DC, 0x82AF, 0xE4A5, 0x82B1, 0xFCA3, 0x82B3, 0xDBBB, + 0x82B7, 0xF2BA, 0x82B8, 0xE9FD, 0x82B9, 0xD0CA, 0x82BB, 0xF5D6, 0x82BC, 0xD9C5, 0x82BD, 0xE4B4, 0x82BF, 0xEDA7, 0x82D1, 0xEABD, + 0x82D2, 0xE6FE, 0x82D4, 0xF7C4, 0x82D5, 0xF5AD, 0x82D7, 0xD9E0, 0x82DB, 0xCAB4, 0x82DE, 0xF8E2, 0x82DF, 0xCFC2, 0x82E1, 0xECBE, + 0x82E5, 0xE5B4, 0x82E6, 0xCDC8, 0x82E7, 0xEEC8, 0x82F1, 0xE7C8, 0x82FD, 0xCDC9, 0x82FE, 0xF9B7, 0x8301, 0xF1E8, 0x8302, 0xD9F2, + 0x8303, 0xDBF5, 0x8304, 0xCAB5, 0x8305, 0xD9C6, 0x8309, 0xD8C9, 0x8317, 0xD9AB, 0x8328, 0xEDBC, 0x832B, 0xD8D4, 0x832F, 0xDCDA, + 0x8331, 0xE2BC, 0x8334, 0xFCED, 0x8335, 0xECE0, 0x8336, 0xD2FE, 0x8338, 0xE9C7, 0x8339, 0xE6AA, 0x8340, 0xE2F0, 0x8347, 0xFABB, + 0x8349, 0xF5AE, 0x834A, 0xFBAA, 0x834F, 0xECFB, 0x8351, 0xECBF, 0x8352, 0xFCD8, 0x8373, 0xD4E5, 0x8377, 0xF9C3, 0x837B, 0xEEE2, + 0x8389, 0xD7E9, 0x838A, 0xEDF6, 0x838E, 0xDEED, 0x8396, 0xCCEC, 0x8398, 0xE3EE, 0x839E, 0xE8D4, 0x83A2, 0xFAF8, 0x83A9, 0xDDB4, + 0x83AA, 0xE4B5, 0x83AB, 0xD8B0, 0x83BD, 0xD8D5, 0x83C1, 0xF4EA, 0x83C5, 0xCEB9, 0x83C9, 0xD6E1, 0x83CA, 0xCFD2, 0x83CC, 0xD0B6, + 0x83D3, 0xCEA2, 0x83D6, 0xF3EE, 0x83DC, 0xF3F8, 0x83E9, 0xDCCC, 0x83EB, 0xD0CB, 0x83EF, 0xFCA4, 0x83F0, 0xCDCA, 0x83F1, 0xD7D4, + 0x83F2, 0xDEA3, 0x83F4, 0xE4E0, 0x83F9, 0xEEC9, 0x83FD, 0xE2DD, 0x8403, 0xF5FE, 0x8404, 0xD4AC, 0x840A, 0xD5D1, 0x840C, 0xD8F0, + 0x840D, 0xF8C3, 0x840E, 0xEAD7, 0x8429, 0xF5D7, 0x842C, 0xD8BF, 0x8431, 0xFDC0, 0x8438, 0xEBAD, 0x843D, 0xD5AA, 0x8449, 0xE7A8, + 0x8457, 0xEECA, 0x845B, 0xCAE7, 0x8461, 0xF8E3, 0x8463, 0xD4DD, 0x8466, 0xEAD8, 0x846B, 0xFBD9, 0x846C, 0xEDF7, 0x846F, 0xE5B5, + 0x8475, 0xD0AD, 0x847A, 0xF1F1, 0x8490, 0xE2BD, 0x8494, 0xE3C8, 0x8499, 0xD9D5, 0x849C, 0xDFAA, 0x84A1, 0xDBBC, 0x84B2, 0xF8E4, + 0x84B8, 0xF1FA, 0x84BB, 0xE5B6, 0x84BC, 0xF3EF, 0x84BF, 0xFBDA, 0x84C0, 0xE1E0, 0x84C2, 0xD9AC, 0x84C4, 0xF5EB, 0x84C6, 0xE0B6, + 0x84C9, 0xE9C8, 0x84CB, 0xCBCF, 0x84CD, 0xE3C9, 0x84D1, 0xDEEE, 0x84DA, 0xE2BE, 0x84EC, 0xDCEF, 0x84EE, 0xD6A5, 0x84F4, 0xE2F1, + 0x84FC, 0xD6FE, 0x8511, 0xD9A1, 0x8513, 0xD8C0, 0x8514, 0xDCDB, 0x8517, 0xEDBD, 0x8518, 0xDFB8, 0x851A, 0xEAA5, 0x851E, 0xD7AD, + 0x8521, 0xF3F9, 0x8523, 0xEDF8, 0x8525, 0xF5C7, 0x852C, 0xE1CA, 0x852D, 0xEBE3, 0x852F, 0xF2DE, 0x853D, 0xF8CC, 0x853F, 0xEAD9, + 0x8541, 0xD3C6, 0x8543, 0xDBE6, 0x8549, 0xF5AF, 0x854E, 0xCEF0, 0x8553, 0xE9FE, 0x8559, 0xFBB6, 0x8563, 0xE2F2, 0x8568, 0xCFF2, + 0x8569, 0xF7B9, 0x856A, 0xD9F3, 0x856D, 0xE1CB, 0x8584, 0xDADD, 0x8587, 0xDAB9, 0x858F, 0xEBFB, 0x8591, 0xCBB9, 0x8594, 0xEDF9, + 0x859B, 0xE0E0, 0x85A6, 0xF4C0, 0x85A8, 0xFDBC, 0x85A9, 0xDFB1, 0x85AA, 0xE3EF, 0x85AF, 0xE0A3, 0x85B0, 0xFDB9, 0x85BA, 0xF0B1, + 0x85C1, 0xCDCB, 0x85C9, 0xEDBE, 0x85CD, 0xD5C0, 0x85CE, 0xE3F0, 0x85CF, 0xEDFA, 0x85D5, 0xE9E4, 0x85DC, 0xD5ED, 0x85DD, 0xE7DD, + 0x85E4, 0xD4F6, 0x85E5, 0xE5B7, 0x85E9, 0xDBE7, 0x85EA, 0xE2BF, 0x85F7, 0xEECB, 0x85FA, 0xD7F4, 0x85FB, 0xF0DD, 0x85FF, 0xCEAB, + 0x8602, 0xE7DE, 0x8606, 0xD6D6, 0x8607, 0xE1CC, 0x860A, 0xE8B3, 0x8616, 0xE5EE, 0x8617, 0xDCA2, 0x861A, 0xE0D0, 0x862D, 0xD5B5, + 0x863F, 0xD5A1, 0x864E, 0xFBDB, 0x8650, 0xF9CB, 0x8654, 0xCBF3, 0x8655, 0xF4A5, 0x865B, 0xFAC8, 0x865C, 0xD6D7, 0x865E, 0xE9E5, + 0x865F, 0xFBDC, 0x8667, 0xFDD0, 0x8679, 0xFBF6, 0x868A, 0xDAA5, 0x868C, 0xDBBD, 0x8693, 0xECE2, 0x86A3, 0xCDF7, 0x86A4, 0xF0DE, + 0x86A9, 0xF6C9, 0x86C7, 0xDEEF, 0x86CB, 0xD3B1, 0x86D4, 0xFCEE, 0x86D9, 0xE8C3, 0x86DB, 0xF1C8, 0x86DF, 0xCEF1, 0x86E4, 0xF9ED, + 0x86ED, 0xF2F4, 0x86FE, 0xE4B6, 0x8700, 0xF5B9, 0x8702, 0xDCF0, 0x8703, 0xE3F1, 0x8708, 0xE8A5, 0x8718, 0xF2BB, 0x871A, 0xDEA4, + 0x871C, 0xDACC, 0x874E, 0xCAE9, 0x8755, 0xE3DA, 0x8757, 0xFCD9, 0x875F, 0xEADA, 0x8766, 0xF9C4, 0x8768, 0xE3A4, 0x8774, 0xFBDD, + 0x8776, 0xEFCA, 0x8778, 0xE8C4, 0x8782, 0xD5CC, 0x878D, 0xEBD7, 0x879F, 0xD9AD, 0x87A2, 0xFBAB, 0x87B3, 0xD3D9, 0x87BA, 0xD5A2, + 0x87C4, 0xF6DE, 0x87E0, 0xDAF6, 0x87EC, 0xE0D1, 0x87EF, 0xE9A8, 0x87F2, 0xF5F9, 0x87F9, 0xFAAF, 0x87FB, 0xEBFC, 0x87FE, 0xE0EA, + 0x8805, 0xE3B2, 0x881F, 0xD5C5, 0x8822, 0xF1E3, 0x8823, 0xD5EE, 0x8831, 0xCDCC, 0x8836, 0xEDD9, 0x883B, 0xD8C1, 0x8840, 0xFAEC, + 0x8846, 0xF1EB, 0x884C, 0xFABC, 0x884D, 0xE6E2, 0x8852, 0xFAE5, 0x8853, 0xE2FA, 0x8857, 0xCAB6, 0x8859, 0xE4B7, 0x885B, 0xEADB, + 0x885D, 0xF5FA, 0x8861, 0xFBAC, 0x8862, 0xCFC3, 0x8863, 0xEBFD, 0x8868, 0xF8FA, 0x886B, 0xDFB9, 0x8870, 0xE1F1, 0x8872, 0xD2A4, + 0x8877, 0xF5FB, 0x887E, 0xD0DA, 0x887F, 0xD0DB, 0x8881, 0xEABE, 0x8882, 0xD9B1, 0x8888, 0xCAB7, 0x888B, 0xD3E7, 0x888D, 0xF8E5, + 0x8892, 0xD3B2, 0x8896, 0xE2C0, 0x8897, 0xF2DF, 0x889E, 0xCDE5, 0x88AB, 0xF9AC, 0x88B4, 0xCDCD, 0x88C1, 0xEEAE, 0x88C2, 0xD6AE, + 0x88CF, 0xD7EA, 0x88D4, 0xE7E0, 0x88D5, 0xEBAE, 0x88D9, 0xCFD9, 0x88DC, 0xDCCD, 0x88DD, 0xEDFB, 0x88DF, 0xDEF0, 0x88E1, 0xD7EB, + 0x88E8, 0xDEA5, 0x88F3, 0xDFD7, 0x88F4, 0xDBD0, 0x88F5, 0xDBD1, 0x88F8, 0xD5A3, 0x88FD, 0xF0B2, 0x8907, 0xDCDC, 0x8910, 0xCAE8, + 0x8912, 0xF8E6, 0x8913, 0xDCCE, 0x8918, 0xEADC, 0x8919, 0xDBD2, 0x8925, 0xE9B3, 0x892A, 0xF7DB, 0x8936, 0xE3A8, 0x8938, 0xD7AE, + 0x893B, 0xE0E1, 0x8941, 0xCBBA, 0x8944, 0xE5D1, 0x895F, 0xD0DC, 0x8964, 0xD5C1, 0x896A, 0xD8CA, 0x8972, 0xE3A9, 0x897F, 0xE0A4, + 0x8981, 0xE9A9, 0x8983, 0xD3C7, 0x8986, 0xDCDD, 0x8987, 0xF8AE, 0x898B, 0xCCB8, 0x898F, 0xD0AE, 0x8993, 0xD8F2, 0x8996, 0xE3CA, + 0x89A1, 0xCCAF, 0x89A9, 0xD4AD, 0x89AA, 0xF6D1, 0x89B2, 0xD0CC, 0x89BA, 0xCAC6, 0x89BD, 0xD5C2, 0x89C0, 0xCEBA, 0x89D2, 0xCAC7, + 0x89E3, 0xFAB0, 0x89F4, 0xDFD8, 0x89F8, 0xF5BA, 0x8A00, 0xE5EB, 0x8A02, 0xEFF4, 0x8A03, 0xDDB5, 0x8A08, 0xCDAA, 0x8A0A, 0xE3F2, + 0x8A0C, 0xFBF7, 0x8A0E, 0xF7D0, 0x8A13, 0xFDBA, 0x8A16, 0xFDE1, 0x8A17, 0xF6FE, 0x8A18, 0xD1C0, 0x8A1B, 0xE8C5, 0x8A1D, 0xE4B8, + 0x8A1F, 0xE1E8, 0x8A23, 0xCCC1, 0x8A25, 0xD2ED, 0x8A2A, 0xDBBE, 0x8A2D, 0xE0E2, 0x8A31, 0xFAC9, 0x8A34, 0xE1CD, 0x8A36, 0xCAB8, + 0x8A3A, 0xF2E0, 0x8A3B, 0xF1C9, 0x8A50, 0xDEF1, 0x8A54, 0xF0DF, 0x8A55, 0xF8C4, 0x8A5B, 0xEECC, 0x8A5E, 0xDEF2, 0x8A60, 0xE7C9, + 0x8A62, 0xE2F3, 0x8A63, 0xE7E1, 0x8A66, 0xE3CB, 0x8A69, 0xE3CC, 0x8A6D, 0xCFF8, 0x8A6E, 0xEFAC, 0x8A70, 0xFDFE, 0x8A71, 0xFCA5, + 0x8A72, 0xFAB1, 0x8A73, 0xDFD9, 0x8A75, 0xE0D2, 0x8A79, 0xF4DA, 0x8A85, 0xF1CA, 0x8A87, 0xCEA3, 0x8A8C, 0xF2BC, 0x8A8D, 0xECE3, + 0x8A93, 0xE0A5, 0x8A95, 0xF7AB, 0x8A98, 0xEBAF, 0x8A9E, 0xE5DE, 0x8AA0, 0xE1A4, 0x8AA1, 0xCDAB, 0x8AA3, 0xD9F4, 0x8AA4, 0xE8A6, + 0x8AA5, 0xCDCE, 0x8AA6, 0xE1E9, 0x8AA8, 0xFCEF, 0x8AAA, 0xE0E3, 0x8AB0, 0xE2C1, 0x8AB2, 0xCEA4, 0x8AB9, 0xDEA6, 0x8ABC, 0xEBFE, + 0x8ABE, 0xEBDD, 0x8ABF, 0xF0E0, 0x8AC2, 0xF4DB, 0x8AC4, 0xE2F4, 0x8AC7, 0xD3C8, 0x8ACB, 0xF4EB, 0x8ACD, 0xEEB5, 0x8ACF, 0xF5D8, + 0x8AD2, 0xD5DF, 0x8AD6, 0xD6E5, 0x8ADB, 0xEBB0, 0x8ADC, 0xF4E3, 0x8AE1, 0xE3CD, 0x8AE6, 0xF4F4, 0x8AE7, 0xFAB2, 0x8AEA, 0xEFF5, + 0x8AEB, 0xCADF, 0x8AED, 0xEBB1, 0x8AEE, 0xEDBF, 0x8AF1, 0xFDC9, 0x8AF6, 0xE4A6, 0x8AF7, 0xF9A4, 0x8AF8, 0xF0B3, 0x8AFA, 0xE5EC, + 0x8AFE, 0xD1E7, 0x8B00, 0xD9C7, 0x8B01, 0xE4D7, 0x8B02, 0xEADD, 0x8B04, 0xD4F7, 0x8B0E, 0xDABA, 0x8B10, 0xDACD, 0x8B14, 0xF9CC, + 0x8B16, 0xE1DA, 0x8B17, 0xDBBF, 0x8B19, 0xCCC5, 0x8B1A, 0xECD0, 0x8B1B, 0xCBBB, 0x8B1D, 0xDEF3, 0x8B20, 0xE9AA, 0x8B28, 0xD9C8, + 0x8B2B, 0xEEE3, 0x8B2C, 0xD7BD, 0x8B33, 0xCFC4, 0x8B39, 0xD0CD, 0x8B41, 0xFCA6, 0x8B49, 0xF1FB, 0x8B4E, 0xFDD2, 0x8B4F, 0xD1C1, + 0x8B58, 0xE3DB, 0x8B5A, 0xD3C9, 0x8B5C, 0xDCCF, 0x8B66, 0xCCED, 0x8B6C, 0xDEA7, 0x8B6F, 0xE6BB, 0x8B70, 0xECA1, 0x8B74, 0xCCB9, + 0x8B77, 0xFBDE, 0x8B7D, 0xE7E2, 0x8B80, 0xD4C1, 0x8B8A, 0xDCA8, 0x8B90, 0xE2C2, 0x8B92, 0xF3D8, 0x8B93, 0xE5D3, 0x8B96, 0xF3D9, + 0x8B9A, 0xF3C6, 0x8C37, 0xCDDB, 0x8C3F, 0xCDAC, 0x8C41, 0xFCC3, 0x8C46, 0xD4E7, 0x8C48, 0xD1C2, 0x8C4A, 0xF9A5, 0x8C4C, 0xE8D5, + 0x8C55, 0xE3CE, 0x8C5A, 0xD4CA, 0x8C61, 0xDFDA, 0x8C6A, 0xFBDF, 0x8C6B, 0xE7E3, 0x8C79, 0xF8FB, 0x8C7A, 0xE3CF, 0x8C82, 0xF5B0, + 0x8C8A, 0xD8E7, 0x8C8C, 0xD9C9, 0x8C9D, 0xF8AF, 0x8C9E, 0xEFF6, 0x8CA0, 0xDDB6, 0x8CA1, 0xEEAF, 0x8CA2, 0xCDF8, 0x8CA7, 0xDEB8, + 0x8CA8, 0xFCA7, 0x8CA9, 0xF7FC, 0x8CAA, 0xF7B1, 0x8CAB, 0xCEBB, 0x8CAC, 0xF4A1, 0x8CAF, 0xEECD, 0x8CB0, 0xE1AE, 0x8CB3, 0xECC3, + 0x8CB4, 0xCFFE, 0x8CB6, 0xF8BF, 0x8CB7, 0xD8E2, 0x8CB8, 0xD3E8, 0x8CBB, 0xDEA8, 0x8CBC, 0xF4E4, 0x8CBD, 0xECC2, 0x8CBF, 0xD9F5, + 0x8CC0, 0xF9C5, 0x8CC1, 0xDDD3, 0x8CC2, 0xD6F1, 0x8CC3, 0xECFC, 0x8CC4, 0xFCF0, 0x8CC7, 0xEDC0, 0x8CC8, 0xCAB9, 0x8CCA, 0xEEE4, + 0x8CD1, 0xF2E1, 0x8CD3, 0xDEB9, 0x8CDA, 0xD6F2, 0x8CDC, 0xDEF4, 0x8CDE, 0xDFDB, 0x8CE0, 0xDBD3, 0x8CE2, 0xFAE7, 0x8CE3, 0xD8E3, + 0x8CE4, 0xF4C1, 0x8CE6, 0xDDB7, 0x8CEA, 0xF2F5, 0x8CED, 0xD4AE, 0x8CF4, 0xD6F3, 0x8CFB, 0xDDB8, 0x8CFC, 0xCFC5, 0x8CFD, 0xDFDF, + 0x8D04, 0xF2BE, 0x8D05, 0xF6A1, 0x8D07, 0xEBCB, 0x8D08, 0xF1FC, 0x8D0A, 0xF3C7, 0x8D0D, 0xE0EB, 0x8D13, 0xEDFC, 0x8D16, 0xE1DB, + 0x8D64, 0xEEE5, 0x8D66, 0xDEF5, 0x8D6B, 0xFAD3, 0x8D70, 0xF1CB, 0x8D73, 0xD0AF, 0x8D74, 0xDDB9, 0x8D77, 0xD1C3, 0x8D85, 0xF5B1, + 0x8D8A, 0xEAC6, 0x8D99, 0xF0E1, 0x8DA3, 0xF6AC, 0x8DA8, 0xF5D9, 0x8DB3, 0xF0EB, 0x8DBA, 0xDDBA, 0x8DBE, 0xF2BF, 0x8DC6, 0xF7C5, + 0x8DCB, 0xDBA2, 0x8DCC, 0xF2F6, 0x8DCF, 0xCABA, 0x8DDB, 0xF7F5, 0x8DDD, 0xCBE5, 0x8DE1, 0xEEE6, 0x8DE3, 0xE0D3, 0x8DE8, 0xCEA5, + 0x8DEF, 0xD6D8, 0x8DF3, 0xD4AF, 0x8E0A, 0xE9C9, 0x8E0F, 0xD3CE, 0x8E10, 0xF4C2, 0x8E1E, 0xCBE6, 0x8E2A, 0xF1A1, 0x8E30, 0xEBB2, + 0x8E35, 0xF1A2, 0x8E42, 0xEBB3, 0x8E44, 0xF0B4, 0x8E47, 0xCBF4, 0x8E48, 0xD4B0, 0x8E49, 0xF3B2, 0x8E4A, 0xFBB7, 0x8E59, 0xF5EC, + 0x8E5F, 0xEEE7, 0x8E60, 0xF4B2, 0x8E74, 0xF5ED, 0x8E76, 0xCFF3, 0x8E81, 0xF0E2, 0x8E87, 0xEECE, 0x8E8A, 0xF1CC, 0x8E8D, 0xE5B8, + 0x8EAA, 0xD7F5, 0x8EAB, 0xE3F3, 0x8EAC, 0xCFE5, 0x8EC0, 0xCFC6, 0x8ECA, 0xF3B3, 0x8ECB, 0xE4D8, 0x8ECC, 0xCFF9, 0x8ECD, 0xCFDA, + 0x8ED2, 0xFACD, 0x8EDF, 0xE6E3, 0x8EEB, 0xF2E2, 0x8EF8, 0xF5EE, 0x8EFB, 0xCABB, 0x8EFE, 0xE3DC, 0x8F03, 0xCEF2, 0x8F05, 0xD6D9, + 0x8F09, 0xEEB0, 0x8F12, 0xF4E5, 0x8F13, 0xD8C2, 0x8F14, 0xDCD0, 0x8F15, 0xCCEE, 0x8F1B, 0xD5E0, 0x8F1C, 0xF6CA, 0x8F1D, 0xFDCA, + 0x8F1E, 0xD8D6, 0x8F1F, 0xF4CF, 0x8F26, 0xD6A6, 0x8F27, 0xDCBE, 0x8F29, 0xDBD4, 0x8F2A, 0xD7C7, 0x8F2F, 0xF2FE, 0x8F33, 0xF1CD, + 0x8F38, 0xE2C3, 0x8F39, 0xDCDE, 0x8F3B, 0xDCDF, 0x8F3E, 0xEFAD, 0x8F3F, 0xE6AB, 0x8F44, 0xF9DD, 0x8F45, 0xEABF, 0x8F49, 0xEFAE, + 0x8F4D, 0xF4D0, 0x8F4E, 0xCEF3, 0x8F5D, 0xE6AC, 0x8F5F, 0xCEDE, 0x8F62, 0xD5F9, 0x8F9B, 0xE3F4, 0x8F9C, 0xCDD0, 0x8FA3, 0xD5B8, + 0x8FA6, 0xF7FD, 0x8FA8, 0xDCA9, 0x8FAD, 0xDEF6, 0x8FAF, 0xDCAA, 0x8FB0, 0xF2E3, 0x8FB1, 0xE9B4, 0x8FB2, 0xD2DC, 0x8FC2, 0xE9E6, + 0x8FC5, 0xE3F6, 0x8FCE, 0xE7CA, 0x8FD1, 0xD0CE, 0x8FD4, 0xDAF7, 0x8FE6, 0xCABC, 0x8FEA, 0xEEE8, 0x8FEB, 0xDADE, 0x8FED, 0xF2F7, + 0x8FF0, 0xE2FB, 0x8FF2, 0xCCA6, 0x8FF7, 0xDABB, 0x8FF9, 0xEEE9, 0x8FFD, 0xF5DA, 0x9000, 0xF7DC, 0x9001, 0xE1EA, 0x9002, 0xCEC1, + 0x9003, 0xD4B1, 0x9005, 0xFDB1, 0x9006, 0xE6BD, 0x9008, 0xFBAD, 0x900B, 0xF8E7, 0x900D, 0xE1CE, 0x900F, 0xF7E2, 0x9010, 0xF5EF, + 0x9011, 0xCFC7, 0x9014, 0xD4B2, 0x9015, 0xCCEF, 0x9017, 0xD4E8, 0x9019, 0xEECF, 0x901A, 0xF7D7, 0x901D, 0xE0A6, 0x901E, 0xD6C1, + 0x901F, 0xE1DC, 0x9020, 0xF0E3, 0x9021, 0xF1E4, 0x9022, 0xDCF1, 0x9023, 0xD6A7, 0x902E, 0xF4F5, 0x9031, 0xF1CE, 0x9032, 0xF2E4, + 0x9035, 0xD0B0, 0x9038, 0xECEF, 0x903C, 0xF9BA, 0x903E, 0xEBB5, 0x9041, 0xD4ED, 0x9042, 0xE2C4, 0x9047, 0xE9E7, 0x904A, 0xEBB4, + 0x904B, 0xEAA1, 0x904D, 0xF8BC, 0x904E, 0xCEA6, 0x9050, 0xF9C6, 0x9051, 0xFCDA, 0x9053, 0xD4B3, 0x9054, 0xD3B9, 0x9055, 0xEADE, + 0x9059, 0xE9AB, 0x905C, 0xE1E1, 0x905D, 0xD3CF, 0x905E, 0xF4F6, 0x9060, 0xEAC0, 0x9061, 0xE1CF, 0x9063, 0xCCBA, 0x9069, 0xEEEA, + 0x906D, 0xF0E4, 0x906E, 0xF3B4, 0x906F, 0xD4EE, 0x9072, 0xF2C0, 0x9075, 0xF1E5, 0x9077, 0xF4C3, 0x9078, 0xE0D4, 0x907A, 0xEBB6, + 0x907C, 0xD7A1, 0x907D, 0xCBE8, 0x907F, 0xF9AD, 0x9080, 0xE9AD, 0x9081, 0xD8E4, 0x9082, 0xFAB3, 0x9083, 0xE2C5, 0x9084, 0xFCBD, + 0x9087, 0xECC4, 0x9088, 0xD8B1, 0x908A, 0xDCAB, 0x908F, 0xD5A4, 0x9091, 0xEBE9, 0x9095, 0xE8BB, 0x9099, 0xD8D7, 0x90A2, 0xFBAE, + 0x90A3, 0xD1E1, 0x90A6, 0xDBC0, 0x90A8, 0xF5BE, 0x90AA, 0xDEF7, 0x90AF, 0xCAFB, 0x90B0, 0xF7C6, 0x90B1, 0xCFC8, 0x90B5, 0xE1D0, + 0x90B8, 0xEED0, 0x90C1, 0xE9F4, 0x90CA, 0xCEF4, 0x90DE, 0xD5CD, 0x90E1, 0xCFDB, 0x90E8, 0xDDBB, 0x90ED, 0xCEAC, 0x90F5, 0xE9E8, + 0x90FD, 0xD4B4, 0x9102, 0xE4C7, 0x9112, 0xF5DB, 0x9115, 0xFAC1, 0x9119, 0xDEA9, 0x9127, 0xD4F8, 0x912D, 0xEFF7, 0x9132, 0xD3B3, + 0x9149, 0xEBB7, 0x914A, 0xEFF8, 0x914B, 0xF5DC, 0x914C, 0xEDCC, 0x914D, 0xDBD5, 0x914E, 0xF1CF, 0x9152, 0xF1D0, 0x9162, 0xF5B2, + 0x9169, 0xD9AE, 0x916A, 0xD5AC, 0x916C, 0xE2C6, 0x9175, 0xFDA3, 0x9177, 0xFBE5, 0x9178, 0xDFAB, 0x9187, 0xE2F5, 0x9189, 0xF6AD, + 0x918B, 0xF5B3, 0x918D, 0xF0B5, 0x9192, 0xE1A5, 0x919C, 0xF5DD, 0x91AB, 0xECA2, 0x91AC, 0xEDFD, 0x91AE, 0xF5B4, 0x91AF, 0xFBB8, + 0x91B1, 0xDBA3, 0x91B4, 0xD6CA, 0x91B5, 0xCBD9, 0x91C0, 0xE5D4, 0x91C7, 0xF3FA, 0x91C9, 0xEBB8, 0x91CB, 0xE0B7, 0x91CC, 0xD7EC, + 0x91CD, 0xF1EC, 0x91CE, 0xE5AF, 0x91CF, 0xD5E1, 0x91D0, 0xD7ED, 0x91D1, 0xD1D1, 0x91D7, 0xE1F2, 0x91D8, 0xEFF9, 0x91DC, 0xDDBC, + 0x91DD, 0xF6DC, 0x91E3, 0xF0E5, 0x91E7, 0xF4C4, 0x91EA, 0xE9E9, 0x91F5, 0xF3FB, 0x920D, 0xD4EF, 0x9210, 0xCCA2, 0x9211, 0xF7FE, + 0x9212, 0xDFBC, 0x9217, 0xEBCD, 0x921E, 0xD0B7, 0x9234, 0xD6C2, 0x923A, 0xE8AD, 0x923F, 0xEFAF, 0x9240, 0xCBA5, 0x9245, 0xCBE9, + 0x9249, 0xFAE8, 0x9257, 0xCCC6, 0x925B, 0xE6E7, 0x925E, 0xEAC7, 0x9262, 0xDBA4, 0x9264, 0xCFC9, 0x9265, 0xE2FC, 0x9266, 0xEFFA, + 0x9280, 0xEBDE, 0x9283, 0xF5C8, 0x9285, 0xD4DE, 0x9291, 0xE0D5, 0x9293, 0xEFB0, 0x9296, 0xE2C7, 0x9298, 0xD9AF, 0x929C, 0xF9E7, + 0x92B3, 0xE7E5, 0x92B6, 0xCFCA, 0x92B7, 0xE1D1, 0x92B9, 0xE2C8, 0x92CC, 0xEFFB, 0x92CF, 0xFAF9, 0x92D2, 0xDCF2, 0x92E4, 0xE0A7, + 0x92EA, 0xF8E8, 0x92F8, 0xCBEA, 0x92FC, 0xCBBC, 0x9304, 0xD6E2, 0x9310, 0xF5DE, 0x9318, 0xF5DF, 0x931A, 0xEEB6, 0x931E, 0xE2F6, + 0x931F, 0xD3CA, 0x9320, 0xEFFC, 0x9321, 0xD1C4, 0x9322, 0xEFB1, 0x9324, 0xD1C5, 0x9326, 0xD0DE, 0x9328, 0xD9E1, 0x932B, 0xE0B8, + 0x932E, 0xCDD1, 0x932F, 0xF3B9, 0x9348, 0xE7CC, 0x934A, 0xD6A8, 0x934B, 0xCEA7, 0x934D, 0xD4B5, 0x9354, 0xE4C8, 0x935B, 0xD3B4, + 0x936E, 0xEBB9, 0x9375, 0xCBF5, 0x937C, 0xF6DD, 0x937E, 0xF1A3, 0x938C, 0xCCC7, 0x9394, 0xE9CA, 0x9396, 0xE1F0, 0x939A, 0xF5E0, + 0x93A3, 0xFBAF, 0x93A7, 0xCBD1, 0x93AC, 0xFBE0, 0x93AD, 0xF2E5, 0x93B0, 0xECF0, 0x93C3, 0xF0EC, 0x93D1, 0xEEEB, 0x93DE, 0xE9CB, + 0x93E1, 0xCCF0, 0x93E4, 0xD7AF, 0x93F6, 0xF3A1, 0x9404, 0xFCF5, 0x9418, 0xF1A4, 0x9425, 0xE0D6, 0x942B, 0xEFB2, 0x9435, 0xF4D1, + 0x9438, 0xF7A1, 0x9444, 0xF1D1, 0x9451, 0xCAFC, 0x9452, 0xCAFD, 0x945B, 0xCECE, 0x947D, 0xF3C8, 0x947F, 0xF3BA, 0x9577, 0xEDFE, + 0x9580, 0xDAA6, 0x9583, 0xE0EC, 0x9589, 0xF8CD, 0x958B, 0xCBD2, 0x958F, 0xEBCE, 0x9591, 0xF9D8, 0x9592, 0xF9D9, 0x9593, 0xCAE0, + 0x9594, 0xDACA, 0x9598, 0xCBA6, 0x95A3, 0xCAC8, 0x95A4, 0xF9EE, 0x95A5, 0xDBEC, 0x95A8, 0xD0B1, 0x95AD, 0xD5EF, 0x95B1, 0xE6F3, + 0x95BB, 0xE7A2, 0x95BC, 0xE4D9, 0x95C7, 0xE4E1, 0x95CA, 0xFCC4, 0x95D4, 0xF9EF, 0x95D5, 0xCFF4, 0x95D6, 0xF7E6, 0x95DC, 0xCEBC, + 0x95E1, 0xF4C5, 0x95E2, 0xDCA3, 0x961C, 0xDDBD, 0x9621, 0xF4C6, 0x962A, 0xF8A1, 0x962E, 0xE8D6, 0x9632, 0xDBC1, 0x963B, 0xF0E6, + 0x963F, 0xE4B9, 0x9640, 0xF6ED, 0x9642, 0xF9AE, 0x9644, 0xDDBE, 0x964B, 0xD7B0, 0x964C, 0xD8E8, 0x964D, 0xCBBD, 0x9650, 0xF9DA, + 0x965B, 0xF8CE, 0x965C, 0xF9F0, 0x965D, 0xE0ED, 0x965E, 0xE3B3, 0x965F, 0xF4B3, 0x9662, 0xEAC2, 0x9663, 0xF2E6, 0x9664, 0xF0B6, + 0x966A, 0xDBD6, 0x9670, 0xEBE4, 0x9673, 0xF2E7, 0x9675, 0xD7D5, 0x9676, 0xD4B6, 0x9677, 0xF9E8, 0x9678, 0xD7C1, 0x967D, 0xE5D5, + 0x9685, 0xE9EA, 0x9686, 0xD7CC, 0x968A, 0xD3E9, 0x968B, 0xE2C9, 0x968D, 0xFCDB, 0x968E, 0xCDAD, 0x9694, 0xCCB0, 0x9695, 0xEAA2, + 0x9698, 0xE4F6, 0x9699, 0xD0C0, 0x969B, 0xF0B7, 0x969C, 0xEEA1, 0x96A3, 0xD7F6, 0x96A7, 0xE2CA, 0x96A8, 0xE2CB, 0x96AA, 0xFACF, + 0x96B1, 0xEBDF, 0x96B7, 0xD6CB, 0x96BB, 0xF4B4, 0x96C0, 0xEDCD, 0x96C1, 0xE4D2, 0x96C4, 0xEAA9, 0x96C5, 0xE4BA, 0x96C6, 0xF3A2, + 0x96C7, 0xCDD2, 0x96C9, 0xF6CB, 0x96CB, 0xF1E6, 0x96CC, 0xEDC1, 0x96CD, 0xE8BC, 0x96CE, 0xEED1, 0x96D5, 0xF0E7, 0x96D6, 0xE2CC, + 0x96D9, 0xE4AA, 0x96DB, 0xF5E1, 0x96DC, 0xEDDA, 0x96E2, 0xD7EE, 0x96E3, 0xD1F1, 0x96E8, 0xE9EB, 0x96E9, 0xE9EC, 0x96EA, 0xE0E4, + 0x96EF, 0xDAA7, 0x96F0, 0xDDD4, 0x96F2, 0xEAA3, 0x96F6, 0xD6C3, 0x96F7, 0xD6F4, 0x96F9, 0xDADF, 0x96FB, 0xEFB3, 0x9700, 0xE2CD, + 0x9706, 0xEFFD, 0x9707, 0xF2E8, 0x9711, 0xEFC5, 0x9713, 0xE7E7, 0x9716, 0xD7FD, 0x9719, 0xE7CE, 0x971C, 0xDFDC, 0x971E, 0xF9C7, + 0x9727, 0xD9F6, 0x9730, 0xDFAC, 0x9732, 0xD6DA, 0x9739, 0xDCA4, 0x973D, 0xF0B8, 0x9742, 0xD5FA, 0x9744, 0xE4F7, 0x9748, 0xD6C4, + 0x9751, 0xF4EC, 0x9756, 0xEFFE, 0x975C, 0xF0A1, 0x975E, 0xDEAA, 0x9761, 0xDABC, 0x9762, 0xD8FC, 0x9769, 0xFAD4, 0x976D, 0xECE5, + 0x9774, 0xFCA8, 0x9777, 0xECE6, 0x977A, 0xD8CB, 0x978B, 0xFBB9, 0x978D, 0xE4D3, 0x978F, 0xCDF9, 0x97A0, 0xCFD3, 0x97A8, 0xCAEA, + 0x97AB, 0xCFD4, 0x97AD, 0xF8BD, 0x97C6, 0xF4C7, 0x97CB, 0xEADF, 0x97D3, 0xF9DB, 0x97DC, 0xD4B7, 0x97F3, 0xEBE5, 0x97F6, 0xE1D2, + 0x97FB, 0xEAA4, 0x97FF, 0xFAC2, 0x9800, 0xFBE1, 0x9801, 0xFAED, 0x9802, 0xF0A2, 0x9803, 0xCCF1, 0x9805, 0xFAA3, 0x9806, 0xE2F7, + 0x9808, 0xE2CE, 0x980A, 0xE9F5, 0x980C, 0xE1EB, 0x9810, 0xE7E8, 0x9811, 0xE8D7, 0x9812, 0xDAF8, 0x9813, 0xD4CB, 0x9817, 0xF7F6, + 0x9818, 0xD6C5, 0x982D, 0xD4E9, 0x9830, 0xFAFA, 0x9838, 0xCCF2, 0x9839, 0xF7DD, 0x983B, 0xDEBA, 0x9846, 0xCEA8, 0x984C, 0xF0B9, + 0x984D, 0xE4FE, 0x984E, 0xE4C9, 0x9854, 0xE4D4, 0x9858, 0xEAC3, 0x985A, 0xEFB4, 0x985E, 0xD7BE, 0x9865, 0xFBE2, 0x9867, 0xCDD3, + 0x986B, 0xEFB5, 0x986F, 0xFAE9, 0x98A8, 0xF9A6, 0x98AF, 0xDFBD, 0x98B1, 0xF7C7, 0x98C4, 0xF8FD, 0x98C7, 0xF8FC, 0x98DB, 0xDEAB, + 0x98DC, 0xDBE8, 0x98DF, 0xE3DD, 0x98E1, 0xE1E2, 0x98E2, 0xD1C6, 0x98ED, 0xF6D0, 0x98EE, 0xEBE6, 0x98EF, 0xDAF9, 0x98F4, 0xECC7, + 0x98FC, 0xDEF8, 0x98FD, 0xF8E9, 0x98FE, 0xE3DE, 0x9903, 0xCEF5, 0x9909, 0xFAC3, 0x990A, 0xE5D7, 0x990C, 0xECC8, 0x9910, 0xF3C9, + 0x9913, 0xE4BB, 0x9918, 0xE6AE, 0x991E, 0xEFB6, 0x9920, 0xDCBF, 0x9928, 0xCEBD, 0x9945, 0xD8C3, 0x9949, 0xD0CF, 0x994B, 0xCFFA, + 0x994C, 0xF3CA, 0x994D, 0xE0D7, 0x9951, 0xD1C7, 0x9952, 0xE9AE, 0x9954, 0xE8BD, 0x9957, 0xFAC4, 0x9996, 0xE2CF, 0x9999, 0xFAC5, + 0x999D, 0xF9B8, 0x99A5, 0xDCE0, 0x99A8, 0xFBB0, 0x99AC, 0xD8A9, 0x99AD, 0xE5DF, 0x99AE, 0xF9A7, 0x99B1, 0xF6EE, 0x99B3, 0xF6CC, + 0x99B4, 0xE2F8, 0x99B9, 0xECF1, 0x99C1, 0xDAE0, 0x99D0, 0xF1D2, 0x99D1, 0xD2CC, 0x99D2, 0xCFCB, 0x99D5, 0xCABD, 0x99D9, 0xDDBF, + 0x99DD, 0xF6EF, 0x99DF, 0xDEF9, 0x99ED, 0xFAB4, 0x99F1, 0xD5AD, 0x99FF, 0xF1E7, 0x9A01, 0xDEBE, 0x9A08, 0xDCC0, 0x9A0E, 0xD1C8, + 0x9A0F, 0xD1C9, 0x9A19, 0xF8BE, 0x9A2B, 0xCBF6, 0x9A30, 0xD4F9, 0x9A36, 0xF5E2, 0x9A37, 0xE1D3, 0x9A40, 0xD8E9, 0x9A43, 0xF8FE, + 0x9A45, 0xCFCC, 0x9A4D, 0xFDA4, 0x9A55, 0xCEF6, 0x9A57, 0xFAD0, 0x9A5A, 0xCCF3, 0x9A5B, 0xE6BE, 0x9A5F, 0xF6AE, 0x9A62, 0xD5F0, + 0x9A65, 0xD1CA, 0x9A69, 0xFCBE, 0x9A6A, 0xD5F1, 0x9AA8, 0xCDE9, 0x9AB8, 0xFAB5, 0x9AD3, 0xE2D0, 0x9AD4, 0xF4F7, 0x9AD8, 0xCDD4, + 0x9AE5, 0xE7A3, 0x9AEE, 0xDBA5, 0x9B1A, 0xE2D1, 0x9B27, 0xD7A2, 0x9B2A, 0xF7E3, 0x9B31, 0xEAA6, 0x9B3C, 0xD0A1, 0x9B41, 0xCEDA, + 0x9B42, 0xFBEB, 0x9B43, 0xDBA6, 0x9B44, 0xDBDE, 0x9B45, 0xD8E5, 0x9B4F, 0xEAE0, 0x9B54, 0xD8AA, 0x9B5A, 0xE5E0, 0x9B6F, 0xD6DB, + 0x9B8E, 0xEFC6, 0x9B91, 0xF8EA, 0x9B9F, 0xE4D5, 0x9BAB, 0xCEF7, 0x9BAE, 0xE0D8, 0x9BC9, 0xD7EF, 0x9BD6, 0xF4ED, 0x9BE4, 0xCDE6, + 0x9BE8, 0xCCF4, 0x9C0D, 0xF5E3, 0x9C10, 0xE4CA, 0x9C12, 0xDCE1, 0x9C15, 0xF9C8, 0x9C25, 0xFCBF, 0x9C32, 0xE8A7, 0x9C3B, 0xD8C4, + 0x9C47, 0xCBBE, 0x9C49, 0xDCAE, 0x9C57, 0xD7F7, 0x9CE5, 0xF0E8, 0x9CE7, 0xDDC0, 0x9CE9, 0xCFCD, 0x9CF3, 0xDCF3, 0x9CF4, 0xD9B0, + 0x9CF6, 0xE6E9, 0x9D09, 0xE4BC, 0x9D1B, 0xEAC4, 0x9D26, 0xE4EC, 0x9D28, 0xE4E5, 0x9D3B, 0xFBF8, 0x9D51, 0xCCBB, 0x9D5D, 0xE4BD, + 0x9D60, 0xCDDC, 0x9D61, 0xD9F7, 0x9D6C, 0xDDDF, 0x9D72, 0xEDCE, 0x9DA9, 0xD9D0, 0x9DAF, 0xE5A3, 0x9DB4, 0xF9CD, 0x9DC4, 0xCDAE, + 0x9DD7, 0xCFCE, 0x9DF2, 0xF6AF, 0x9DF8, 0xFDD3, 0x9DF9, 0xEBED, 0x9DFA, 0xD6DC, 0x9E1A, 0xE5A4, 0x9E1E, 0xD5B6, 0x9E75, 0xD6DD, + 0x9E79, 0xF9E9, 0x9E7D, 0xE7A4, 0x9E7F, 0xD6E3, 0x9E92, 0xD1CB, 0x9E93, 0xD6E4, 0x9E97, 0xD5F2, 0x9E9D, 0xDEFA, 0x9E9F, 0xD7F8, + 0x9EA5, 0xD8EA, 0x9EB4, 0xCFD5, 0x9EB5, 0xD8FD, 0x9EBB, 0xD8AB, 0x9EBE, 0xFDCB, 0x9EC3, 0xFCDC, 0x9ECD, 0xE0A8, 0x9ECE, 0xD5F3, + 0x9ED1, 0xFDD9, 0x9ED4, 0xCCA3, 0x9ED8, 0xD9F9, 0x9EDB, 0xD3EA, 0x9EDC, 0xF5F5, 0x9EDE, 0xEFC7, 0x9EE8, 0xD3DA, 0x9EF4, 0xDABD, + 0x9F07, 0xE8A8, 0x9F08, 0xDCAF, 0x9F0E, 0xF0A3, 0x9F13, 0xCDD5, 0x9F20, 0xE0A9, 0x9F3B, 0xDEAC, 0x9F4A, 0xF0BA, 0x9F4B, 0xEEB1, + 0x9F4E, 0xEEB2, 0x9F52, 0xF6CD, 0x9F5F, 0xEED2, 0x9F61, 0xD6C6, 0x9F67, 0xE0E5, 0x9F6A, 0xF3BB, 0x9F6C, 0xE5E1, 0x9F77, 0xE4CB, + 0x9F8D, 0xD7A3, 0x9F90, 0xDBC2, 0x9F95, 0xCAFE, 0x9F9C, 0xCFCF, 0xAC00, 0xB0A1, 0xAC01, 0xB0A2, 0xAC02, 0x8141, 0xAC03, 0x8142, + 0xAC04, 0xB0A3, 0xAC05, 0x8143, 0xAC06, 0x8144, 0xAC07, 0xB0A4, 0xAC08, 0xB0A5, 0xAC09, 0xB0A6, 0xAC0A, 0xB0A7, 0xAC0B, 0x8145, + 0xAC0C, 0x8146, 0xAC0D, 0x8147, 0xAC0E, 0x8148, 0xAC0F, 0x8149, 0xAC10, 0xB0A8, 0xAC11, 0xB0A9, 0xAC12, 0xB0AA, 0xAC13, 0xB0AB, + 0xAC14, 0xB0AC, 0xAC15, 0xB0AD, 0xAC16, 0xB0AE, 0xAC17, 0xB0AF, 0xAC18, 0x814A, 0xAC19, 0xB0B0, 0xAC1A, 0xB0B1, 0xAC1B, 0xB0B2, + 0xAC1C, 0xB0B3, 0xAC1D, 0xB0B4, 0xAC1E, 0x814B, 0xAC1F, 0x814C, 0xAC20, 0xB0B5, 0xAC21, 0x814D, 0xAC22, 0x814E, 0xAC23, 0x814F, + 0xAC24, 0xB0B6, 0xAC25, 0x8150, 0xAC26, 0x8151, 0xAC27, 0x8152, 0xAC28, 0x8153, 0xAC29, 0x8154, 0xAC2A, 0x8155, 0xAC2B, 0x8156, + 0xAC2C, 0xB0B7, 0xAC2D, 0xB0B8, 0xAC2E, 0x8157, 0xAC2F, 0xB0B9, 0xAC30, 0xB0BA, 0xAC31, 0xB0BB, 0xAC32, 0x8158, 0xAC33, 0x8159, + 0xAC34, 0x815A, 0xAC35, 0x8161, 0xAC36, 0x8162, 0xAC37, 0x8163, 0xAC38, 0xB0BC, 0xAC39, 0xB0BD, 0xAC3A, 0x8164, 0xAC3B, 0x8165, + 0xAC3C, 0xB0BE, 0xAC3D, 0x8166, 0xAC3E, 0x8167, 0xAC3F, 0x8168, 0xAC40, 0xB0BF, 0xAC41, 0x8169, 0xAC42, 0x816A, 0xAC43, 0x816B, + 0xAC44, 0x816C, 0xAC45, 0x816D, 0xAC46, 0x816E, 0xAC47, 0x816F, 0xAC48, 0x8170, 0xAC49, 0x8171, 0xAC4A, 0x8172, 0xAC4B, 0xB0C0, + 0xAC4C, 0x8173, 0xAC4D, 0xB0C1, 0xAC4E, 0x8174, 0xAC4F, 0x8175, 0xAC50, 0x8176, 0xAC51, 0x8177, 0xAC52, 0x8178, 0xAC53, 0x8179, + 0xAC54, 0xB0C2, 0xAC55, 0x817A, 0xAC56, 0x8181, 0xAC57, 0x8182, 0xAC58, 0xB0C3, 0xAC59, 0x8183, 0xAC5A, 0x8184, 0xAC5B, 0x8185, + 0xAC5C, 0xB0C4, 0xAC5D, 0x8186, 0xAC5E, 0x8187, 0xAC5F, 0x8188, 0xAC60, 0x8189, 0xAC61, 0x818A, 0xAC62, 0x818B, 0xAC63, 0x818C, + 0xAC64, 0x818D, 0xAC65, 0x818E, 0xAC66, 0x818F, 0xAC67, 0x8190, 0xAC68, 0x8191, 0xAC69, 0x8192, 0xAC6A, 0x8193, 0xAC6B, 0x8194, + 0xAC6C, 0x8195, 0xAC6D, 0x8196, 0xAC6E, 0x8197, 0xAC6F, 0x8198, 0xAC70, 0xB0C5, 0xAC71, 0xB0C6, 0xAC72, 0x8199, 0xAC73, 0x819A, + 0xAC74, 0xB0C7, 0xAC75, 0x819B, 0xAC76, 0x819C, 0xAC77, 0xB0C8, 0xAC78, 0xB0C9, 0xAC79, 0x819D, 0xAC7A, 0xB0CA, 0xAC7B, 0x819E, + 0xAC7C, 0x819F, 0xAC7D, 0x81A0, 0xAC7E, 0x81A1, 0xAC7F, 0x81A2, 0xAC80, 0xB0CB, 0xAC81, 0xB0CC, 0xAC82, 0x81A3, 0xAC83, 0xB0CD, + 0xAC84, 0xB0CE, 0xAC85, 0xB0CF, 0xAC86, 0xB0D0, 0xAC87, 0x81A4, 0xAC88, 0x81A5, 0xAC89, 0xB0D1, 0xAC8A, 0xB0D2, 0xAC8B, 0xB0D3, + 0xAC8C, 0xB0D4, 0xAC8D, 0x81A6, 0xAC8E, 0x81A7, 0xAC8F, 0x81A8, 0xAC90, 0xB0D5, 0xAC91, 0x81A9, 0xAC92, 0x81AA, 0xAC93, 0x81AB, + 0xAC94, 0xB0D6, 0xAC95, 0x81AC, 0xAC96, 0x81AD, 0xAC97, 0x81AE, 0xAC98, 0x81AF, 0xAC99, 0x81B0, 0xAC9A, 0x81B1, 0xAC9B, 0x81B2, + 0xAC9C, 0xB0D7, 0xAC9D, 0xB0D8, 0xAC9E, 0x81B3, 0xAC9F, 0xB0D9, 0xACA0, 0xB0DA, 0xACA1, 0xB0DB, 0xACA2, 0x81B4, 0xACA3, 0x81B5, + 0xACA4, 0x81B6, 0xACA5, 0x81B7, 0xACA6, 0x81B8, 0xACA7, 0x81B9, 0xACA8, 0xB0DC, 0xACA9, 0xB0DD, 0xACAA, 0xB0DE, 0xACAB, 0x81BA, + 0xACAC, 0xB0DF, 0xACAD, 0x81BB, 0xACAE, 0x81BC, 0xACAF, 0xB0E0, 0xACB0, 0xB0E1, 0xACB1, 0x81BD, 0xACB2, 0x81BE, 0xACB3, 0x81BF, + 0xACB4, 0x81C0, 0xACB5, 0x81C1, 0xACB6, 0x81C2, 0xACB7, 0x81C3, 0xACB8, 0xB0E2, 0xACB9, 0xB0E3, 0xACBA, 0x81C4, 0xACBB, 0xB0E4, + 0xACBC, 0xB0E5, 0xACBD, 0xB0E6, 0xACBE, 0x81C5, 0xACBF, 0x81C6, 0xACC0, 0x81C7, 0xACC1, 0xB0E7, 0xACC2, 0x81C8, 0xACC3, 0x81C9, + 0xACC4, 0xB0E8, 0xACC5, 0x81CA, 0xACC6, 0x81CB, 0xACC7, 0x81CC, 0xACC8, 0xB0E9, 0xACC9, 0x81CD, 0xACCA, 0x81CE, 0xACCB, 0x81CF, + 0xACCC, 0xB0EA, 0xACCD, 0x81D0, 0xACCE, 0x81D1, 0xACCF, 0x81D2, 0xACD0, 0x81D3, 0xACD1, 0x81D4, 0xACD2, 0x81D5, 0xACD3, 0x81D6, + 0xACD4, 0x81D7, 0xACD5, 0xB0EB, 0xACD6, 0x81D8, 0xACD7, 0xB0EC, 0xACD8, 0x81D9, 0xACD9, 0x81DA, 0xACDA, 0x81DB, 0xACDB, 0x81DC, + 0xACDC, 0x81DD, 0xACDD, 0x81DE, 0xACDE, 0x81DF, 0xACDF, 0x81E0, 0xACE0, 0xB0ED, 0xACE1, 0xB0EE, 0xACE2, 0x81E1, 0xACE3, 0x81E2, + 0xACE4, 0xB0EF, 0xACE5, 0x81E3, 0xACE6, 0x81E4, 0xACE7, 0xB0F0, 0xACE8, 0xB0F1, 0xACE9, 0x81E5, 0xACEA, 0xB0F2, 0xACEB, 0x81E6, + 0xACEC, 0xB0F3, 0xACED, 0x81E7, 0xACEE, 0x81E8, 0xACEF, 0xB0F4, 0xACF0, 0xB0F5, 0xACF1, 0xB0F6, 0xACF2, 0x81E9, 0xACF3, 0xB0F7, + 0xACF4, 0x81EA, 0xACF5, 0xB0F8, 0xACF6, 0xB0F9, 0xACF7, 0x81EB, 0xACF8, 0x81EC, 0xACF9, 0x81ED, 0xACFA, 0x81EE, 0xACFB, 0x81EF, + 0xACFC, 0xB0FA, 0xACFD, 0xB0FB, 0xACFE, 0x81F0, 0xACFF, 0x81F1, 0xAD00, 0xB0FC, 0xAD01, 0x81F2, 0xAD02, 0x81F3, 0xAD03, 0x81F4, + 0xAD04, 0xB0FD, 0xAD05, 0x81F5, 0xAD06, 0xB0FE, 0xAD07, 0x81F6, 0xAD08, 0x81F7, 0xAD09, 0x81F8, 0xAD0A, 0x81F9, 0xAD0B, 0x81FA, + 0xAD0C, 0xB1A1, 0xAD0D, 0xB1A2, 0xAD0E, 0x81FB, 0xAD0F, 0xB1A3, 0xAD10, 0x81FC, 0xAD11, 0xB1A4, 0xAD12, 0x81FD, 0xAD13, 0x81FE, + 0xAD14, 0x8241, 0xAD15, 0x8242, 0xAD16, 0x8243, 0xAD17, 0x8244, 0xAD18, 0xB1A5, 0xAD19, 0x8245, 0xAD1A, 0x8246, 0xAD1B, 0x8247, + 0xAD1C, 0xB1A6, 0xAD1D, 0x8248, 0xAD1E, 0x8249, 0xAD1F, 0x824A, 0xAD20, 0xB1A7, 0xAD21, 0x824B, 0xAD22, 0x824C, 0xAD23, 0x824D, + 0xAD24, 0x824E, 0xAD25, 0x824F, 0xAD26, 0x8250, 0xAD27, 0x8251, 0xAD28, 0x8252, 0xAD29, 0xB1A8, 0xAD2A, 0x8253, 0xAD2B, 0x8254, + 0xAD2C, 0xB1A9, 0xAD2D, 0xB1AA, 0xAD2E, 0x8255, 0xAD2F, 0x8256, 0xAD30, 0x8257, 0xAD31, 0x8258, 0xAD32, 0x8259, 0xAD33, 0x825A, + 0xAD34, 0xB1AB, 0xAD35, 0xB1AC, 0xAD36, 0x8261, 0xAD37, 0x8262, 0xAD38, 0xB1AD, 0xAD39, 0x8263, 0xAD3A, 0x8264, 0xAD3B, 0x8265, + 0xAD3C, 0xB1AE, 0xAD3D, 0x8266, 0xAD3E, 0x8267, 0xAD3F, 0x8268, 0xAD40, 0x8269, 0xAD41, 0x826A, 0xAD42, 0x826B, 0xAD43, 0x826C, + 0xAD44, 0xB1AF, 0xAD45, 0xB1B0, 0xAD46, 0x826D, 0xAD47, 0xB1B1, 0xAD48, 0x826E, 0xAD49, 0xB1B2, 0xAD4A, 0x826F, 0xAD4B, 0x8270, + 0xAD4C, 0x8271, 0xAD4D, 0x8272, 0xAD4E, 0x8273, 0xAD4F, 0x8274, 0xAD50, 0xB1B3, 0xAD51, 0x8275, 0xAD52, 0x8276, 0xAD53, 0x8277, + 0xAD54, 0xB1B4, 0xAD55, 0x8278, 0xAD56, 0x8279, 0xAD57, 0x827A, 0xAD58, 0xB1B5, 0xAD59, 0x8281, 0xAD5A, 0x8282, 0xAD5B, 0x8283, + 0xAD5C, 0x8284, 0xAD5D, 0x8285, 0xAD5E, 0x8286, 0xAD5F, 0x8287, 0xAD60, 0x8288, 0xAD61, 0xB1B6, 0xAD62, 0x8289, 0xAD63, 0xB1B7, + 0xAD64, 0x828A, 0xAD65, 0x828B, 0xAD66, 0x828C, 0xAD67, 0x828D, 0xAD68, 0x828E, 0xAD69, 0x828F, 0xAD6A, 0x8290, 0xAD6B, 0x8291, + 0xAD6C, 0xB1B8, 0xAD6D, 0xB1B9, 0xAD6E, 0x8292, 0xAD6F, 0x8293, 0xAD70, 0xB1BA, 0xAD71, 0x8294, 0xAD72, 0x8295, 0xAD73, 0xB1BB, + 0xAD74, 0xB1BC, 0xAD75, 0xB1BD, 0xAD76, 0xB1BE, 0xAD77, 0x8296, 0xAD78, 0x8297, 0xAD79, 0x8298, 0xAD7A, 0x8299, 0xAD7B, 0xB1BF, + 0xAD7C, 0xB1C0, 0xAD7D, 0xB1C1, 0xAD7E, 0x829A, 0xAD7F, 0xB1C2, 0xAD80, 0x829B, 0xAD81, 0xB1C3, 0xAD82, 0xB1C4, 0xAD83, 0x829C, + 0xAD84, 0x829D, 0xAD85, 0x829E, 0xAD86, 0x829F, 0xAD87, 0x82A0, 0xAD88, 0xB1C5, 0xAD89, 0xB1C6, 0xAD8A, 0x82A1, 0xAD8B, 0x82A2, + 0xAD8C, 0xB1C7, 0xAD8D, 0x82A3, 0xAD8E, 0x82A4, 0xAD8F, 0x82A5, 0xAD90, 0xB1C8, 0xAD91, 0x82A6, 0xAD92, 0x82A7, 0xAD93, 0x82A8, + 0xAD94, 0x82A9, 0xAD95, 0x82AA, 0xAD96, 0x82AB, 0xAD97, 0x82AC, 0xAD98, 0x82AD, 0xAD99, 0x82AE, 0xAD9A, 0x82AF, 0xAD9B, 0x82B0, + 0xAD9C, 0xB1C9, 0xAD9D, 0xB1CA, 0xAD9E, 0x82B1, 0xAD9F, 0x82B2, 0xADA0, 0x82B3, 0xADA1, 0x82B4, 0xADA2, 0x82B5, 0xADA3, 0x82B6, + 0xADA4, 0xB1CB, 0xADA5, 0x82B7, 0xADA6, 0x82B8, 0xADA7, 0x82B9, 0xADA8, 0x82BA, 0xADA9, 0x82BB, 0xADAA, 0x82BC, 0xADAB, 0x82BD, + 0xADAC, 0x82BE, 0xADAD, 0x82BF, 0xADAE, 0x82C0, 0xADAF, 0x82C1, 0xADB0, 0x82C2, 0xADB1, 0x82C3, 0xADB2, 0x82C4, 0xADB3, 0x82C5, + 0xADB4, 0x82C6, 0xADB5, 0x82C7, 0xADB6, 0x82C8, 0xADB7, 0xB1CC, 0xADB8, 0x82C9, 0xADB9, 0x82CA, 0xADBA, 0x82CB, 0xADBB, 0x82CC, + 0xADBC, 0x82CD, 0xADBD, 0x82CE, 0xADBE, 0x82CF, 0xADBF, 0x82D0, 0xADC0, 0xB1CD, 0xADC1, 0xB1CE, 0xADC2, 0x82D1, 0xADC3, 0x82D2, + 0xADC4, 0xB1CF, 0xADC5, 0x82D3, 0xADC6, 0x82D4, 0xADC7, 0x82D5, 0xADC8, 0xB1D0, 0xADC9, 0x82D6, 0xADCA, 0x82D7, 0xADCB, 0x82D8, + 0xADCC, 0x82D9, 0xADCD, 0x82DA, 0xADCE, 0x82DB, 0xADCF, 0x82DC, 0xADD0, 0xB1D1, 0xADD1, 0xB1D2, 0xADD2, 0x82DD, 0xADD3, 0xB1D3, + 0xADD4, 0x82DE, 0xADD5, 0x82DF, 0xADD6, 0x82E0, 0xADD7, 0x82E1, 0xADD8, 0x82E2, 0xADD9, 0x82E3, 0xADDA, 0x82E4, 0xADDB, 0x82E5, + 0xADDC, 0xB1D4, 0xADDD, 0x82E6, 0xADDE, 0x82E7, 0xADDF, 0x82E8, 0xADE0, 0xB1D5, 0xADE1, 0x82E9, 0xADE2, 0x82EA, 0xADE3, 0x82EB, + 0xADE4, 0xB1D6, 0xADE5, 0x82EC, 0xADE6, 0x82ED, 0xADE7, 0x82EE, 0xADE8, 0x82EF, 0xADE9, 0x82F0, 0xADEA, 0x82F1, 0xADEB, 0x82F2, + 0xADEC, 0x82F3, 0xADED, 0x82F4, 0xADEE, 0x82F5, 0xADEF, 0x82F6, 0xADF0, 0x82F7, 0xADF1, 0x82F8, 0xADF2, 0x82F9, 0xADF3, 0x82FA, + 0xADF4, 0x82FB, 0xADF5, 0x82FC, 0xADF6, 0x82FD, 0xADF7, 0x82FE, 0xADF8, 0xB1D7, 0xADF9, 0xB1D8, 0xADFA, 0x8341, 0xADFB, 0x8342, + 0xADFC, 0xB1D9, 0xADFD, 0x8343, 0xADFE, 0x8344, 0xADFF, 0xB1DA, 0xAE00, 0xB1DB, 0xAE01, 0xB1DC, 0xAE02, 0x8345, 0xAE03, 0x8346, + 0xAE04, 0x8347, 0xAE05, 0x8348, 0xAE06, 0x8349, 0xAE07, 0x834A, 0xAE08, 0xB1DD, 0xAE09, 0xB1DE, 0xAE0A, 0x834B, 0xAE0B, 0xB1DF, + 0xAE0C, 0x834C, 0xAE0D, 0xB1E0, 0xAE0E, 0x834D, 0xAE0F, 0x834E, 0xAE10, 0x834F, 0xAE11, 0x8350, 0xAE12, 0x8351, 0xAE13, 0x8352, + 0xAE14, 0xB1E1, 0xAE15, 0x8353, 0xAE16, 0x8354, 0xAE17, 0x8355, 0xAE18, 0x8356, 0xAE19, 0x8357, 0xAE1A, 0x8358, 0xAE1B, 0x8359, + 0xAE1C, 0x835A, 0xAE1D, 0x8361, 0xAE1E, 0x8362, 0xAE1F, 0x8363, 0xAE20, 0x8364, 0xAE21, 0x8365, 0xAE22, 0x8366, 0xAE23, 0x8367, + 0xAE24, 0x8368, 0xAE25, 0x8369, 0xAE26, 0x836A, 0xAE27, 0x836B, 0xAE28, 0x836C, 0xAE29, 0x836D, 0xAE2A, 0x836E, 0xAE2B, 0x836F, + 0xAE2C, 0x8370, 0xAE2D, 0x8371, 0xAE2E, 0x8372, 0xAE2F, 0x8373, 0xAE30, 0xB1E2, 0xAE31, 0xB1E3, 0xAE32, 0x8374, 0xAE33, 0x8375, + 0xAE34, 0xB1E4, 0xAE35, 0x8376, 0xAE36, 0x8377, 0xAE37, 0xB1E5, 0xAE38, 0xB1E6, 0xAE39, 0x8378, 0xAE3A, 0xB1E7, 0xAE3B, 0x8379, + 0xAE3C, 0x837A, 0xAE3D, 0x8381, 0xAE3E, 0x8382, 0xAE3F, 0x8383, 0xAE40, 0xB1E8, 0xAE41, 0xB1E9, 0xAE42, 0x8384, 0xAE43, 0xB1EA, + 0xAE44, 0x8385, 0xAE45, 0xB1EB, 0xAE46, 0xB1EC, 0xAE47, 0x8386, 0xAE48, 0x8387, 0xAE49, 0x8388, 0xAE4A, 0xB1ED, 0xAE4B, 0x8389, + 0xAE4C, 0xB1EE, 0xAE4D, 0xB1EF, 0xAE4E, 0xB1F0, 0xAE4F, 0x838A, 0xAE50, 0xB1F1, 0xAE51, 0x838B, 0xAE52, 0x838C, 0xAE53, 0x838D, + 0xAE54, 0xB1F2, 0xAE55, 0x838E, 0xAE56, 0xB1F3, 0xAE57, 0x838F, 0xAE58, 0x8390, 0xAE59, 0x8391, 0xAE5A, 0x8392, 0xAE5B, 0x8393, + 0xAE5C, 0xB1F4, 0xAE5D, 0xB1F5, 0xAE5E, 0x8394, 0xAE5F, 0xB1F6, 0xAE60, 0xB1F7, 0xAE61, 0xB1F8, 0xAE62, 0x8395, 0xAE63, 0x8396, + 0xAE64, 0x8397, 0xAE65, 0xB1F9, 0xAE66, 0x8398, 0xAE67, 0x8399, 0xAE68, 0xB1FA, 0xAE69, 0xB1FB, 0xAE6A, 0x839A, 0xAE6B, 0x839B, + 0xAE6C, 0xB1FC, 0xAE6D, 0x839C, 0xAE6E, 0x839D, 0xAE6F, 0x839E, 0xAE70, 0xB1FD, 0xAE71, 0x839F, 0xAE72, 0x83A0, 0xAE73, 0x83A1, + 0xAE74, 0x83A2, 0xAE75, 0x83A3, 0xAE76, 0x83A4, 0xAE77, 0x83A5, 0xAE78, 0xB1FE, 0xAE79, 0xB2A1, 0xAE7A, 0x83A6, 0xAE7B, 0xB2A2, + 0xAE7C, 0xB2A3, 0xAE7D, 0xB2A4, 0xAE7E, 0x83A7, 0xAE7F, 0x83A8, 0xAE80, 0x83A9, 0xAE81, 0x83AA, 0xAE82, 0x83AB, 0xAE83, 0x83AC, + 0xAE84, 0xB2A5, 0xAE85, 0xB2A6, 0xAE86, 0x83AD, 0xAE87, 0x83AE, 0xAE88, 0x83AF, 0xAE89, 0x83B0, 0xAE8A, 0x83B1, 0xAE8B, 0x83B2, + 0xAE8C, 0xB2A7, 0xAE8D, 0x83B3, 0xAE8E, 0x83B4, 0xAE8F, 0x83B5, 0xAE90, 0x83B6, 0xAE91, 0x83B7, 0xAE92, 0x83B8, 0xAE93, 0x83B9, + 0xAE94, 0x83BA, 0xAE95, 0x83BB, 0xAE96, 0x83BC, 0xAE97, 0x83BD, 0xAE98, 0x83BE, 0xAE99, 0x83BF, 0xAE9A, 0x83C0, 0xAE9B, 0x83C1, + 0xAE9C, 0x83C2, 0xAE9D, 0x83C3, 0xAE9E, 0x83C4, 0xAE9F, 0x83C5, 0xAEA0, 0x83C6, 0xAEA1, 0x83C7, 0xAEA2, 0x83C8, 0xAEA3, 0x83C9, + 0xAEA4, 0x83CA, 0xAEA5, 0x83CB, 0xAEA6, 0x83CC, 0xAEA7, 0x83CD, 0xAEA8, 0x83CE, 0xAEA9, 0x83CF, 0xAEAA, 0x83D0, 0xAEAB, 0x83D1, + 0xAEAC, 0x83D2, 0xAEAD, 0x83D3, 0xAEAE, 0x83D4, 0xAEAF, 0x83D5, 0xAEB0, 0x83D6, 0xAEB1, 0x83D7, 0xAEB2, 0x83D8, 0xAEB3, 0x83D9, + 0xAEB4, 0x83DA, 0xAEB5, 0x83DB, 0xAEB6, 0x83DC, 0xAEB7, 0x83DD, 0xAEB8, 0x83DE, 0xAEB9, 0x83DF, 0xAEBA, 0x83E0, 0xAEBB, 0x83E1, + 0xAEBC, 0xB2A8, 0xAEBD, 0xB2A9, 0xAEBE, 0xB2AA, 0xAEBF, 0x83E2, 0xAEC0, 0xB2AB, 0xAEC1, 0x83E3, 0xAEC2, 0x83E4, 0xAEC3, 0x83E5, + 0xAEC4, 0xB2AC, 0xAEC5, 0x83E6, 0xAEC6, 0x83E7, 0xAEC7, 0x83E8, 0xAEC8, 0x83E9, 0xAEC9, 0x83EA, 0xAECA, 0x83EB, 0xAECB, 0x83EC, + 0xAECC, 0xB2AD, 0xAECD, 0xB2AE, 0xAECE, 0x83ED, 0xAECF, 0xB2AF, 0xAED0, 0xB2B0, 0xAED1, 0xB2B1, 0xAED2, 0x83EE, 0xAED3, 0x83EF, + 0xAED4, 0x83F0, 0xAED5, 0x83F1, 0xAED6, 0x83F2, 0xAED7, 0x83F3, 0xAED8, 0xB2B2, 0xAED9, 0xB2B3, 0xAEDA, 0x83F4, 0xAEDB, 0x83F5, + 0xAEDC, 0xB2B4, 0xAEDD, 0x83F6, 0xAEDE, 0x83F7, 0xAEDF, 0x83F8, 0xAEE0, 0x83F9, 0xAEE1, 0x83FA, 0xAEE2, 0x83FB, 0xAEE3, 0x83FC, + 0xAEE4, 0x83FD, 0xAEE5, 0x83FE, 0xAEE6, 0x8441, 0xAEE7, 0x8442, 0xAEE8, 0xB2B5, 0xAEE9, 0x8443, 0xAEEA, 0x8444, 0xAEEB, 0xB2B6, + 0xAEEC, 0x8445, 0xAEED, 0xB2B7, 0xAEEE, 0x8446, 0xAEEF, 0x8447, 0xAEF0, 0x8448, 0xAEF1, 0x8449, 0xAEF2, 0x844A, 0xAEF3, 0x844B, + 0xAEF4, 0xB2B8, 0xAEF5, 0x844C, 0xAEF6, 0x844D, 0xAEF7, 0x844E, 0xAEF8, 0xB2B9, 0xAEF9, 0x844F, 0xAEFA, 0x8450, 0xAEFB, 0x8451, + 0xAEFC, 0xB2BA, 0xAEFD, 0x8452, 0xAEFE, 0x8453, 0xAEFF, 0x8454, 0xAF00, 0x8455, 0xAF01, 0x8456, 0xAF02, 0x8457, 0xAF03, 0x8458, + 0xAF04, 0x8459, 0xAF05, 0x845A, 0xAF06, 0x8461, 0xAF07, 0xB2BB, 0xAF08, 0xB2BC, 0xAF09, 0x8462, 0xAF0A, 0x8463, 0xAF0B, 0x8464, + 0xAF0C, 0x8465, 0xAF0D, 0xB2BD, 0xAF0E, 0x8466, 0xAF0F, 0x8467, 0xAF10, 0xB2BE, 0xAF11, 0x8468, 0xAF12, 0x8469, 0xAF13, 0x846A, + 0xAF14, 0x846B, 0xAF15, 0x846C, 0xAF16, 0x846D, 0xAF17, 0x846E, 0xAF18, 0x846F, 0xAF19, 0x8470, 0xAF1A, 0x8471, 0xAF1B, 0x8472, + 0xAF1C, 0x8473, 0xAF1D, 0x8474, 0xAF1E, 0x8475, 0xAF1F, 0x8476, 0xAF20, 0x8477, 0xAF21, 0x8478, 0xAF22, 0x8479, 0xAF23, 0x847A, + 0xAF24, 0x8481, 0xAF25, 0x8482, 0xAF26, 0x8483, 0xAF27, 0x8484, 0xAF28, 0x8485, 0xAF29, 0x8486, 0xAF2A, 0x8487, 0xAF2B, 0x8488, + 0xAF2C, 0xB2BF, 0xAF2D, 0xB2C0, 0xAF2E, 0x8489, 0xAF2F, 0x848A, 0xAF30, 0xB2C1, 0xAF31, 0x848B, 0xAF32, 0xB2C2, 0xAF33, 0x848C, + 0xAF34, 0xB2C3, 0xAF35, 0x848D, 0xAF36, 0x848E, 0xAF37, 0x848F, 0xAF38, 0x8490, 0xAF39, 0x8491, 0xAF3A, 0x8492, 0xAF3B, 0x8493, + 0xAF3C, 0xB2C4, 0xAF3D, 0xB2C5, 0xAF3E, 0x8494, 0xAF3F, 0xB2C6, 0xAF40, 0x8495, 0xAF41, 0xB2C7, 0xAF42, 0xB2C8, 0xAF43, 0xB2C9, + 0xAF44, 0x8496, 0xAF45, 0x8497, 0xAF46, 0x8498, 0xAF47, 0x8499, 0xAF48, 0xB2CA, 0xAF49, 0xB2CB, 0xAF4A, 0x849A, 0xAF4B, 0x849B, + 0xAF4C, 0x849C, 0xAF4D, 0x849D, 0xAF4E, 0x849E, 0xAF4F, 0x849F, 0xAF50, 0xB2CC, 0xAF51, 0x84A0, 0xAF52, 0x84A1, 0xAF53, 0x84A2, + 0xAF54, 0x84A3, 0xAF55, 0x84A4, 0xAF56, 0x84A5, 0xAF57, 0x84A6, 0xAF58, 0x84A7, 0xAF59, 0x84A8, 0xAF5A, 0x84A9, 0xAF5B, 0x84AA, + 0xAF5C, 0xB2CD, 0xAF5D, 0xB2CE, 0xAF5E, 0x84AB, 0xAF5F, 0x84AC, 0xAF60, 0x84AD, 0xAF61, 0x84AE, 0xAF62, 0x84AF, 0xAF63, 0x84B0, + 0xAF64, 0xB2CF, 0xAF65, 0xB2D0, 0xAF66, 0x84B1, 0xAF67, 0x84B2, 0xAF68, 0x84B3, 0xAF69, 0x84B4, 0xAF6A, 0x84B5, 0xAF6B, 0x84B6, + 0xAF6C, 0x84B7, 0xAF6D, 0x84B8, 0xAF6E, 0x84B9, 0xAF6F, 0x84BA, 0xAF70, 0x84BB, 0xAF71, 0x84BC, 0xAF72, 0x84BD, 0xAF73, 0x84BE, + 0xAF74, 0x84BF, 0xAF75, 0x84C0, 0xAF76, 0x84C1, 0xAF77, 0x84C2, 0xAF78, 0x84C3, 0xAF79, 0xB2D1, 0xAF7A, 0x84C4, 0xAF7B, 0x84C5, + 0xAF7C, 0x84C6, 0xAF7D, 0x84C7, 0xAF7E, 0x84C8, 0xAF7F, 0x84C9, 0xAF80, 0xB2D2, 0xAF81, 0x84CA, 0xAF82, 0x84CB, 0xAF83, 0x84CC, + 0xAF84, 0xB2D3, 0xAF85, 0x84CD, 0xAF86, 0x84CE, 0xAF87, 0x84CF, 0xAF88, 0xB2D4, 0xAF89, 0x84D0, 0xAF8A, 0x84D1, 0xAF8B, 0x84D2, + 0xAF8C, 0x84D3, 0xAF8D, 0x84D4, 0xAF8E, 0x84D5, 0xAF8F, 0x84D6, 0xAF90, 0xB2D5, 0xAF91, 0xB2D6, 0xAF92, 0x84D7, 0xAF93, 0x84D8, + 0xAF94, 0x84D9, 0xAF95, 0xB2D7, 0xAF96, 0x84DA, 0xAF97, 0x84DB, 0xAF98, 0x84DC, 0xAF99, 0x84DD, 0xAF9A, 0x84DE, 0xAF9B, 0x84DF, + 0xAF9C, 0xB2D8, 0xAF9D, 0x84E0, 0xAF9E, 0x84E1, 0xAF9F, 0x84E2, 0xAFA0, 0x84E3, 0xAFA1, 0x84E4, 0xAFA2, 0x84E5, 0xAFA3, 0x84E6, + 0xAFA4, 0x84E7, 0xAFA5, 0x84E8, 0xAFA6, 0x84E9, 0xAFA7, 0x84EA, 0xAFA8, 0x84EB, 0xAFA9, 0x84EC, 0xAFAA, 0x84ED, 0xAFAB, 0x84EE, + 0xAFAC, 0x84EF, 0xAFAD, 0x84F0, 0xAFAE, 0x84F1, 0xAFAF, 0x84F2, 0xAFB0, 0x84F3, 0xAFB1, 0x84F4, 0xAFB2, 0x84F5, 0xAFB3, 0x84F6, + 0xAFB4, 0x84F7, 0xAFB5, 0x84F8, 0xAFB6, 0x84F9, 0xAFB7, 0x84FA, 0xAFB8, 0xB2D9, 0xAFB9, 0xB2DA, 0xAFBA, 0x84FB, 0xAFBB, 0x84FC, + 0xAFBC, 0xB2DB, 0xAFBD, 0x84FD, 0xAFBE, 0x84FE, 0xAFBF, 0x8541, 0xAFC0, 0xB2DC, 0xAFC1, 0x8542, 0xAFC2, 0x8543, 0xAFC3, 0x8544, + 0xAFC4, 0x8545, 0xAFC5, 0x8546, 0xAFC6, 0x8547, 0xAFC7, 0xB2DD, 0xAFC8, 0xB2DE, 0xAFC9, 0xB2DF, 0xAFCA, 0x8548, 0xAFCB, 0xB2E0, + 0xAFCC, 0x8549, 0xAFCD, 0xB2E1, 0xAFCE, 0xB2E2, 0xAFCF, 0x854A, 0xAFD0, 0x854B, 0xAFD1, 0x854C, 0xAFD2, 0x854D, 0xAFD3, 0x854E, + 0xAFD4, 0xB2E3, 0xAFD5, 0x854F, 0xAFD6, 0x8550, 0xAFD7, 0x8551, 0xAFD8, 0x8552, 0xAFD9, 0x8553, 0xAFDA, 0x8554, 0xAFDB, 0x8555, + 0xAFDC, 0xB2E4, 0xAFDD, 0x8556, 0xAFDE, 0x8557, 0xAFDF, 0x8558, 0xAFE0, 0x8559, 0xAFE1, 0x855A, 0xAFE2, 0x8561, 0xAFE3, 0x8562, + 0xAFE4, 0x8563, 0xAFE5, 0x8564, 0xAFE6, 0x8565, 0xAFE7, 0x8566, 0xAFE8, 0xB2E5, 0xAFE9, 0xB2E6, 0xAFEA, 0x8567, 0xAFEB, 0x8568, + 0xAFEC, 0x8569, 0xAFED, 0x856A, 0xAFEE, 0x856B, 0xAFEF, 0x856C, 0xAFF0, 0xB2E7, 0xAFF1, 0xB2E8, 0xAFF2, 0x856D, 0xAFF3, 0x856E, + 0xAFF4, 0xB2E9, 0xAFF5, 0x856F, 0xAFF6, 0x8570, 0xAFF7, 0x8571, 0xAFF8, 0xB2EA, 0xAFF9, 0x8572, 0xAFFA, 0x8573, 0xAFFB, 0x8574, + 0xAFFC, 0x8575, 0xAFFD, 0x8576, 0xAFFE, 0x8577, 0xAFFF, 0x8578, 0xB000, 0xB2EB, 0xB001, 0xB2EC, 0xB002, 0x8579, 0xB003, 0x857A, + 0xB004, 0xB2ED, 0xB005, 0x8581, 0xB006, 0x8582, 0xB007, 0x8583, 0xB008, 0x8584, 0xB009, 0x8585, 0xB00A, 0x8586, 0xB00B, 0x8587, + 0xB00C, 0xB2EE, 0xB00D, 0x8588, 0xB00E, 0x8589, 0xB00F, 0x858A, 0xB010, 0xB2EF, 0xB011, 0x858B, 0xB012, 0x858C, 0xB013, 0x858D, + 0xB014, 0xB2F0, 0xB015, 0x858E, 0xB016, 0x858F, 0xB017, 0x8590, 0xB018, 0x8591, 0xB019, 0x8592, 0xB01A, 0x8593, 0xB01B, 0x8594, + 0xB01C, 0xB2F1, 0xB01D, 0xB2F2, 0xB01E, 0x8595, 0xB01F, 0x8596, 0xB020, 0x8597, 0xB021, 0x8598, 0xB022, 0x8599, 0xB023, 0x859A, + 0xB024, 0x859B, 0xB025, 0x859C, 0xB026, 0x859D, 0xB027, 0x859E, 0xB028, 0xB2F3, 0xB029, 0x859F, 0xB02A, 0x85A0, 0xB02B, 0x85A1, + 0xB02C, 0x85A2, 0xB02D, 0x85A3, 0xB02E, 0x85A4, 0xB02F, 0x85A5, 0xB030, 0x85A6, 0xB031, 0x85A7, 0xB032, 0x85A8, 0xB033, 0x85A9, + 0xB034, 0x85AA, 0xB035, 0x85AB, 0xB036, 0x85AC, 0xB037, 0x85AD, 0xB038, 0x85AE, 0xB039, 0x85AF, 0xB03A, 0x85B0, 0xB03B, 0x85B1, + 0xB03C, 0x85B2, 0xB03D, 0x85B3, 0xB03E, 0x85B4, 0xB03F, 0x85B5, 0xB040, 0x85B6, 0xB041, 0x85B7, 0xB042, 0x85B8, 0xB043, 0x85B9, + 0xB044, 0xB2F4, 0xB045, 0xB2F5, 0xB046, 0x85BA, 0xB047, 0x85BB, 0xB048, 0xB2F6, 0xB049, 0x85BC, 0xB04A, 0xB2F7, 0xB04B, 0x85BD, + 0xB04C, 0xB2F8, 0xB04D, 0x85BE, 0xB04E, 0xB2F9, 0xB04F, 0x85BF, 0xB050, 0x85C0, 0xB051, 0x85C1, 0xB052, 0x85C2, 0xB053, 0xB2FA, + 0xB054, 0xB2FB, 0xB055, 0xB2FC, 0xB056, 0x85C3, 0xB057, 0xB2FD, 0xB058, 0x85C4, 0xB059, 0xB2FE, 0xB05A, 0x85C5, 0xB05B, 0x85C6, + 0xB05C, 0x85C7, 0xB05D, 0xB3A1, 0xB05E, 0x85C8, 0xB05F, 0x85C9, 0xB060, 0x85CA, 0xB061, 0x85CB, 0xB062, 0x85CC, 0xB063, 0x85CD, + 0xB064, 0x85CE, 0xB065, 0x85CF, 0xB066, 0x85D0, 0xB067, 0x85D1, 0xB068, 0x85D2, 0xB069, 0x85D3, 0xB06A, 0x85D4, 0xB06B, 0x85D5, + 0xB06C, 0x85D6, 0xB06D, 0x85D7, 0xB06E, 0x85D8, 0xB06F, 0x85D9, 0xB070, 0x85DA, 0xB071, 0x85DB, 0xB072, 0x85DC, 0xB073, 0x85DD, + 0xB074, 0x85DE, 0xB075, 0x85DF, 0xB076, 0x85E0, 0xB077, 0x85E1, 0xB078, 0x85E2, 0xB079, 0x85E3, 0xB07A, 0x85E4, 0xB07B, 0x85E5, + 0xB07C, 0xB3A2, 0xB07D, 0xB3A3, 0xB07E, 0x85E6, 0xB07F, 0x85E7, 0xB080, 0xB3A4, 0xB081, 0x85E8, 0xB082, 0x85E9, 0xB083, 0x85EA, + 0xB084, 0xB3A5, 0xB085, 0x85EB, 0xB086, 0x85EC, 0xB087, 0x85ED, 0xB088, 0x85EE, 0xB089, 0x85EF, 0xB08A, 0x85F0, 0xB08B, 0x85F1, + 0xB08C, 0xB3A6, 0xB08D, 0xB3A7, 0xB08E, 0x85F2, 0xB08F, 0xB3A8, 0xB090, 0x85F3, 0xB091, 0xB3A9, 0xB092, 0x85F4, 0xB093, 0x85F5, + 0xB094, 0x85F6, 0xB095, 0x85F7, 0xB096, 0x85F8, 0xB097, 0x85F9, 0xB098, 0xB3AA, 0xB099, 0xB3AB, 0xB09A, 0xB3AC, 0xB09B, 0x85FA, + 0xB09C, 0xB3AD, 0xB09D, 0x85FB, 0xB09E, 0x85FC, 0xB09F, 0xB3AE, 0xB0A0, 0xB3AF, 0xB0A1, 0xB3B0, 0xB0A2, 0xB3B1, 0xB0A3, 0x85FD, + 0xB0A4, 0x85FE, 0xB0A5, 0x8641, 0xB0A6, 0x8642, 0xB0A7, 0x8643, 0xB0A8, 0xB3B2, 0xB0A9, 0xB3B3, 0xB0AA, 0x8644, 0xB0AB, 0xB3B4, + 0xB0AC, 0xB3B5, 0xB0AD, 0xB3B6, 0xB0AE, 0xB3B7, 0xB0AF, 0xB3B8, 0xB0B0, 0x8645, 0xB0B1, 0xB3B9, 0xB0B2, 0x8646, 0xB0B3, 0xB3BA, + 0xB0B4, 0xB3BB, 0xB0B5, 0xB3BC, 0xB0B6, 0x8647, 0xB0B7, 0x8648, 0xB0B8, 0xB3BD, 0xB0B9, 0x8649, 0xB0BA, 0x864A, 0xB0BB, 0x864B, + 0xB0BC, 0xB3BE, 0xB0BD, 0x864C, 0xB0BE, 0x864D, 0xB0BF, 0x864E, 0xB0C0, 0x864F, 0xB0C1, 0x8650, 0xB0C2, 0x8651, 0xB0C3, 0x8652, + 0xB0C4, 0xB3BF, 0xB0C5, 0xB3C0, 0xB0C6, 0x8653, 0xB0C7, 0xB3C1, 0xB0C8, 0xB3C2, 0xB0C9, 0xB3C3, 0xB0CA, 0x8654, 0xB0CB, 0x8655, + 0xB0CC, 0x8656, 0xB0CD, 0x8657, 0xB0CE, 0x8658, 0xB0CF, 0x8659, 0xB0D0, 0xB3C4, 0xB0D1, 0xB3C5, 0xB0D2, 0x865A, 0xB0D3, 0x8661, + 0xB0D4, 0xB3C6, 0xB0D5, 0x8662, 0xB0D6, 0x8663, 0xB0D7, 0x8664, 0xB0D8, 0xB3C7, 0xB0D9, 0x8665, 0xB0DA, 0x8666, 0xB0DB, 0x8667, + 0xB0DC, 0x8668, 0xB0DD, 0x8669, 0xB0DE, 0x866A, 0xB0DF, 0x866B, 0xB0E0, 0xB3C8, 0xB0E1, 0x866C, 0xB0E2, 0x866D, 0xB0E3, 0x866E, + 0xB0E4, 0x866F, 0xB0E5, 0xB3C9, 0xB0E6, 0x8670, 0xB0E7, 0x8671, 0xB0E8, 0x8672, 0xB0E9, 0x8673, 0xB0EA, 0x8674, 0xB0EB, 0x8675, + 0xB0EC, 0x8676, 0xB0ED, 0x8677, 0xB0EE, 0x8678, 0xB0EF, 0x8679, 0xB0F0, 0x867A, 0xB0F1, 0x8681, 0xB0F2, 0x8682, 0xB0F3, 0x8683, + 0xB0F4, 0x8684, 0xB0F5, 0x8685, 0xB0F6, 0x8686, 0xB0F7, 0x8687, 0xB0F8, 0x8688, 0xB0F9, 0x8689, 0xB0FA, 0x868A, 0xB0FB, 0x868B, + 0xB0FC, 0x868C, 0xB0FD, 0x868D, 0xB0FE, 0x868E, 0xB0FF, 0x868F, 0xB100, 0x8690, 0xB101, 0x8691, 0xB102, 0x8692, 0xB103, 0x8693, + 0xB104, 0x8694, 0xB105, 0x8695, 0xB106, 0x8696, 0xB107, 0x8697, 0xB108, 0xB3CA, 0xB109, 0xB3CB, 0xB10A, 0x8698, 0xB10B, 0xB3CC, + 0xB10C, 0xB3CD, 0xB10D, 0x8699, 0xB10E, 0x869A, 0xB10F, 0x869B, 0xB110, 0xB3CE, 0xB111, 0x869C, 0xB112, 0xB3CF, 0xB113, 0xB3D0, + 0xB114, 0x869D, 0xB115, 0x869E, 0xB116, 0x869F, 0xB117, 0x86A0, 0xB118, 0xB3D1, 0xB119, 0xB3D2, 0xB11A, 0x86A1, 0xB11B, 0xB3D3, + 0xB11C, 0xB3D4, 0xB11D, 0xB3D5, 0xB11E, 0x86A2, 0xB11F, 0x86A3, 0xB120, 0x86A4, 0xB121, 0x86A5, 0xB122, 0x86A6, 0xB123, 0xB3D6, + 0xB124, 0xB3D7, 0xB125, 0xB3D8, 0xB126, 0x86A7, 0xB127, 0x86A8, 0xB128, 0xB3D9, 0xB129, 0x86A9, 0xB12A, 0x86AA, 0xB12B, 0x86AB, + 0xB12C, 0xB3DA, 0xB12D, 0x86AC, 0xB12E, 0x86AD, 0xB12F, 0x86AE, 0xB130, 0x86AF, 0xB131, 0x86B0, 0xB132, 0x86B1, 0xB133, 0x86B2, + 0xB134, 0xB3DB, 0xB135, 0xB3DC, 0xB136, 0x86B3, 0xB137, 0xB3DD, 0xB138, 0xB3DE, 0xB139, 0xB3DF, 0xB13A, 0x86B4, 0xB13B, 0x86B5, + 0xB13C, 0x86B6, 0xB13D, 0x86B7, 0xB13E, 0x86B8, 0xB13F, 0x86B9, 0xB140, 0xB3E0, 0xB141, 0xB3E1, 0xB142, 0x86BA, 0xB143, 0x86BB, + 0xB144, 0xB3E2, 0xB145, 0x86BC, 0xB146, 0x86BD, 0xB147, 0x86BE, 0xB148, 0xB3E3, 0xB149, 0x86BF, 0xB14A, 0x86C0, 0xB14B, 0x86C1, + 0xB14C, 0x86C2, 0xB14D, 0x86C3, 0xB14E, 0x86C4, 0xB14F, 0x86C5, 0xB150, 0xB3E4, 0xB151, 0xB3E5, 0xB152, 0x86C6, 0xB153, 0x86C7, + 0xB154, 0xB3E6, 0xB155, 0xB3E7, 0xB156, 0x86C8, 0xB157, 0x86C9, 0xB158, 0xB3E8, 0xB159, 0x86CA, 0xB15A, 0x86CB, 0xB15B, 0x86CC, + 0xB15C, 0xB3E9, 0xB15D, 0x86CD, 0xB15E, 0x86CE, 0xB15F, 0x86CF, 0xB160, 0xB3EA, 0xB161, 0x86D0, 0xB162, 0x86D1, 0xB163, 0x86D2, + 0xB164, 0x86D3, 0xB165, 0x86D4, 0xB166, 0x86D5, 0xB167, 0x86D6, 0xB168, 0x86D7, 0xB169, 0x86D8, 0xB16A, 0x86D9, 0xB16B, 0x86DA, + 0xB16C, 0x86DB, 0xB16D, 0x86DC, 0xB16E, 0x86DD, 0xB16F, 0x86DE, 0xB170, 0x86DF, 0xB171, 0x86E0, 0xB172, 0x86E1, 0xB173, 0x86E2, + 0xB174, 0x86E3, 0xB175, 0x86E4, 0xB176, 0x86E5, 0xB177, 0x86E6, 0xB178, 0xB3EB, 0xB179, 0xB3EC, 0xB17A, 0x86E7, 0xB17B, 0x86E8, + 0xB17C, 0xB3ED, 0xB17D, 0x86E9, 0xB17E, 0x86EA, 0xB17F, 0x86EB, 0xB180, 0xB3EE, 0xB181, 0x86EC, 0xB182, 0xB3EF, 0xB183, 0x86ED, + 0xB184, 0x86EE, 0xB185, 0x86EF, 0xB186, 0x86F0, 0xB187, 0x86F1, 0xB188, 0xB3F0, 0xB189, 0xB3F1, 0xB18A, 0x86F2, 0xB18B, 0xB3F2, + 0xB18C, 0x86F3, 0xB18D, 0xB3F3, 0xB18E, 0x86F4, 0xB18F, 0x86F5, 0xB190, 0x86F6, 0xB191, 0x86F7, 0xB192, 0xB3F4, 0xB193, 0xB3F5, + 0xB194, 0xB3F6, 0xB195, 0x86F8, 0xB196, 0x86F9, 0xB197, 0x86FA, 0xB198, 0xB3F7, 0xB199, 0x86FB, 0xB19A, 0x86FC, 0xB19B, 0x86FD, + 0xB19C, 0xB3F8, 0xB19D, 0x86FE, 0xB19E, 0x8741, 0xB19F, 0x8742, 0xB1A0, 0x8743, 0xB1A1, 0x8744, 0xB1A2, 0x8745, 0xB1A3, 0x8746, + 0xB1A4, 0x8747, 0xB1A5, 0x8748, 0xB1A6, 0x8749, 0xB1A7, 0x874A, 0xB1A8, 0xB3F9, 0xB1A9, 0x874B, 0xB1AA, 0x874C, 0xB1AB, 0x874D, + 0xB1AC, 0x874E, 0xB1AD, 0x874F, 0xB1AE, 0x8750, 0xB1AF, 0x8751, 0xB1B0, 0x8752, 0xB1B1, 0x8753, 0xB1B2, 0x8754, 0xB1B3, 0x8755, + 0xB1B4, 0x8756, 0xB1B5, 0x8757, 0xB1B6, 0x8758, 0xB1B7, 0x8759, 0xB1B8, 0x875A, 0xB1B9, 0x8761, 0xB1BA, 0x8762, 0xB1BB, 0x8763, + 0xB1BC, 0x8764, 0xB1BD, 0x8765, 0xB1BE, 0x8766, 0xB1BF, 0x8767, 0xB1C0, 0x8768, 0xB1C1, 0x8769, 0xB1C2, 0x876A, 0xB1C3, 0x876B, + 0xB1C4, 0x876C, 0xB1C5, 0x876D, 0xB1C6, 0x876E, 0xB1C7, 0x876F, 0xB1C8, 0x8770, 0xB1C9, 0x8771, 0xB1CA, 0x8772, 0xB1CB, 0x8773, + 0xB1CC, 0xB3FA, 0xB1CD, 0x8774, 0xB1CE, 0x8775, 0xB1CF, 0x8776, 0xB1D0, 0xB3FB, 0xB1D1, 0x8777, 0xB1D2, 0x8778, 0xB1D3, 0x8779, + 0xB1D4, 0xB3FC, 0xB1D5, 0x877A, 0xB1D6, 0x8781, 0xB1D7, 0x8782, 0xB1D8, 0x8783, 0xB1D9, 0x8784, 0xB1DA, 0x8785, 0xB1DB, 0x8786, + 0xB1DC, 0xB3FD, 0xB1DD, 0xB3FE, 0xB1DE, 0x8787, 0xB1DF, 0xB4A1, 0xB1E0, 0x8788, 0xB1E1, 0x8789, 0xB1E2, 0x878A, 0xB1E3, 0x878B, + 0xB1E4, 0x878C, 0xB1E5, 0x878D, 0xB1E6, 0x878E, 0xB1E7, 0x878F, 0xB1E8, 0xB4A2, 0xB1E9, 0xB4A3, 0xB1EA, 0x8790, 0xB1EB, 0x8791, + 0xB1EC, 0xB4A4, 0xB1ED, 0x8792, 0xB1EE, 0x8793, 0xB1EF, 0x8794, 0xB1F0, 0xB4A5, 0xB1F1, 0x8795, 0xB1F2, 0x8796, 0xB1F3, 0x8797, + 0xB1F4, 0x8798, 0xB1F5, 0x8799, 0xB1F6, 0x879A, 0xB1F7, 0x879B, 0xB1F8, 0x879C, 0xB1F9, 0xB4A6, 0xB1FA, 0x879D, 0xB1FB, 0xB4A7, + 0xB1FC, 0x879E, 0xB1FD, 0xB4A8, 0xB1FE, 0x879F, 0xB1FF, 0x87A0, 0xB200, 0x87A1, 0xB201, 0x87A2, 0xB202, 0x87A3, 0xB203, 0x87A4, + 0xB204, 0xB4A9, 0xB205, 0xB4AA, 0xB206, 0x87A5, 0xB207, 0x87A6, 0xB208, 0xB4AB, 0xB209, 0x87A7, 0xB20A, 0x87A8, 0xB20B, 0xB4AC, + 0xB20C, 0xB4AD, 0xB20D, 0x87A9, 0xB20E, 0x87AA, 0xB20F, 0x87AB, 0xB210, 0x87AC, 0xB211, 0x87AD, 0xB212, 0x87AE, 0xB213, 0x87AF, + 0xB214, 0xB4AE, 0xB215, 0xB4AF, 0xB216, 0x87B0, 0xB217, 0xB4B0, 0xB218, 0x87B1, 0xB219, 0xB4B1, 0xB21A, 0x87B2, 0xB21B, 0x87B3, + 0xB21C, 0x87B4, 0xB21D, 0x87B5, 0xB21E, 0x87B6, 0xB21F, 0x87B7, 0xB220, 0xB4B2, 0xB221, 0x87B8, 0xB222, 0x87B9, 0xB223, 0x87BA, + 0xB224, 0x87BB, 0xB225, 0x87BC, 0xB226, 0x87BD, 0xB227, 0x87BE, 0xB228, 0x87BF, 0xB229, 0x87C0, 0xB22A, 0x87C1, 0xB22B, 0x87C2, + 0xB22C, 0x87C3, 0xB22D, 0x87C4, 0xB22E, 0x87C5, 0xB22F, 0x87C6, 0xB230, 0x87C7, 0xB231, 0x87C8, 0xB232, 0x87C9, 0xB233, 0x87CA, + 0xB234, 0xB4B3, 0xB235, 0x87CB, 0xB236, 0x87CC, 0xB237, 0x87CD, 0xB238, 0x87CE, 0xB239, 0x87CF, 0xB23A, 0x87D0, 0xB23B, 0x87D1, + 0xB23C, 0xB4B4, 0xB23D, 0x87D2, 0xB23E, 0x87D3, 0xB23F, 0x87D4, 0xB240, 0x87D5, 0xB241, 0x87D6, 0xB242, 0x87D7, 0xB243, 0x87D8, + 0xB244, 0x87D9, 0xB245, 0x87DA, 0xB246, 0x87DB, 0xB247, 0x87DC, 0xB248, 0x87DD, 0xB249, 0x87DE, 0xB24A, 0x87DF, 0xB24B, 0x87E0, + 0xB24C, 0x87E1, 0xB24D, 0x87E2, 0xB24E, 0x87E3, 0xB24F, 0x87E4, 0xB250, 0x87E5, 0xB251, 0x87E6, 0xB252, 0x87E7, 0xB253, 0x87E8, + 0xB254, 0x87E9, 0xB255, 0x87EA, 0xB256, 0x87EB, 0xB257, 0x87EC, 0xB258, 0xB4B5, 0xB259, 0x87ED, 0xB25A, 0x87EE, 0xB25B, 0x87EF, + 0xB25C, 0xB4B6, 0xB25D, 0x87F0, 0xB25E, 0x87F1, 0xB25F, 0x87F2, 0xB260, 0xB4B7, 0xB261, 0x87F3, 0xB262, 0x87F4, 0xB263, 0x87F5, + 0xB264, 0x87F6, 0xB265, 0x87F7, 0xB266, 0x87F8, 0xB267, 0x87F9, 0xB268, 0xB4B8, 0xB269, 0xB4B9, 0xB26A, 0x87FA, 0xB26B, 0x87FB, + 0xB26C, 0x87FC, 0xB26D, 0x87FD, 0xB26E, 0x87FE, 0xB26F, 0x8841, 0xB270, 0x8842, 0xB271, 0x8843, 0xB272, 0x8844, 0xB273, 0x8845, + 0xB274, 0xB4BA, 0xB275, 0xB4BB, 0xB276, 0x8846, 0xB277, 0x8847, 0xB278, 0x8848, 0xB279, 0x8849, 0xB27A, 0x884A, 0xB27B, 0x884B, + 0xB27C, 0xB4BC, 0xB27D, 0x884C, 0xB27E, 0x884D, 0xB27F, 0x884E, 0xB280, 0x884F, 0xB281, 0x8850, 0xB282, 0x8851, 0xB283, 0x8852, + 0xB284, 0xB4BD, 0xB285, 0xB4BE, 0xB286, 0x8853, 0xB287, 0x8854, 0xB288, 0x8855, 0xB289, 0xB4BF, 0xB28A, 0x8856, 0xB28B, 0x8857, + 0xB28C, 0x8858, 0xB28D, 0x8859, 0xB28E, 0x885A, 0xB28F, 0x8861, 0xB290, 0xB4C0, 0xB291, 0xB4C1, 0xB292, 0x8862, 0xB293, 0x8863, + 0xB294, 0xB4C2, 0xB295, 0x8864, 0xB296, 0x8865, 0xB297, 0x8866, 0xB298, 0xB4C3, 0xB299, 0xB4C4, 0xB29A, 0xB4C5, 0xB29B, 0x8867, + 0xB29C, 0x8868, 0xB29D, 0x8869, 0xB29E, 0x886A, 0xB29F, 0x886B, 0xB2A0, 0xB4C6, 0xB2A1, 0xB4C7, 0xB2A2, 0x886C, 0xB2A3, 0xB4C8, + 0xB2A4, 0x886D, 0xB2A5, 0xB4C9, 0xB2A6, 0xB4CA, 0xB2A7, 0x886E, 0xB2A8, 0x886F, 0xB2A9, 0x8870, 0xB2AA, 0xB4CB, 0xB2AB, 0x8871, + 0xB2AC, 0xB4CC, 0xB2AD, 0x8872, 0xB2AE, 0x8873, 0xB2AF, 0x8874, 0xB2B0, 0xB4CD, 0xB2B1, 0x8875, 0xB2B2, 0x8876, 0xB2B3, 0x8877, + 0xB2B4, 0xB4CE, 0xB2B5, 0x8878, 0xB2B6, 0x8879, 0xB2B7, 0x887A, 0xB2B8, 0x8881, 0xB2B9, 0x8882, 0xB2BA, 0x8883, 0xB2BB, 0x8884, + 0xB2BC, 0x8885, 0xB2BD, 0x8886, 0xB2BE, 0x8887, 0xB2BF, 0x8888, 0xB2C0, 0x8889, 0xB2C1, 0x888A, 0xB2C2, 0x888B, 0xB2C3, 0x888C, + 0xB2C4, 0x888D, 0xB2C5, 0x888E, 0xB2C6, 0x888F, 0xB2C7, 0x8890, 0xB2C8, 0xB4CF, 0xB2C9, 0xB4D0, 0xB2CA, 0x8891, 0xB2CB, 0x8892, + 0xB2CC, 0xB4D1, 0xB2CD, 0x8893, 0xB2CE, 0x8894, 0xB2CF, 0x8895, 0xB2D0, 0xB4D2, 0xB2D1, 0x8896, 0xB2D2, 0xB4D3, 0xB2D3, 0x8897, + 0xB2D4, 0x8898, 0xB2D5, 0x8899, 0xB2D6, 0x889A, 0xB2D7, 0x889B, 0xB2D8, 0xB4D4, 0xB2D9, 0xB4D5, 0xB2DA, 0x889C, 0xB2DB, 0xB4D6, + 0xB2DC, 0x889D, 0xB2DD, 0xB4D7, 0xB2DE, 0x889E, 0xB2DF, 0x889F, 0xB2E0, 0x88A0, 0xB2E1, 0x88A1, 0xB2E2, 0xB4D8, 0xB2E3, 0x88A2, + 0xB2E4, 0xB4D9, 0xB2E5, 0xB4DA, 0xB2E6, 0xB4DB, 0xB2E7, 0x88A3, 0xB2E8, 0xB4DC, 0xB2E9, 0x88A4, 0xB2EA, 0x88A5, 0xB2EB, 0xB4DD, + 0xB2EC, 0xB4DE, 0xB2ED, 0xB4DF, 0xB2EE, 0xB4E0, 0xB2EF, 0xB4E1, 0xB2F0, 0x88A6, 0xB2F1, 0x88A7, 0xB2F2, 0x88A8, 0xB2F3, 0xB4E2, + 0xB2F4, 0xB4E3, 0xB2F5, 0xB4E4, 0xB2F6, 0x88A9, 0xB2F7, 0xB4E5, 0xB2F8, 0xB4E6, 0xB2F9, 0xB4E7, 0xB2FA, 0xB4E8, 0xB2FB, 0xB4E9, + 0xB2FC, 0x88AA, 0xB2FD, 0x88AB, 0xB2FE, 0x88AC, 0xB2FF, 0xB4EA, 0xB300, 0xB4EB, 0xB301, 0xB4EC, 0xB302, 0x88AD, 0xB303, 0x88AE, + 0xB304, 0xB4ED, 0xB305, 0x88AF, 0xB306, 0x88B0, 0xB307, 0x88B1, 0xB308, 0xB4EE, 0xB309, 0x88B2, 0xB30A, 0x88B3, 0xB30B, 0x88B4, + 0xB30C, 0x88B5, 0xB30D, 0x88B6, 0xB30E, 0x88B7, 0xB30F, 0x88B8, 0xB310, 0xB4EF, 0xB311, 0xB4F0, 0xB312, 0x88B9, 0xB313, 0xB4F1, + 0xB314, 0xB4F2, 0xB315, 0xB4F3, 0xB316, 0x88BA, 0xB317, 0x88BB, 0xB318, 0x88BC, 0xB319, 0x88BD, 0xB31A, 0x88BE, 0xB31B, 0x88BF, + 0xB31C, 0xB4F4, 0xB31D, 0x88C0, 0xB31E, 0x88C1, 0xB31F, 0x88C2, 0xB320, 0x88C3, 0xB321, 0x88C4, 0xB322, 0x88C5, 0xB323, 0x88C6, + 0xB324, 0x88C7, 0xB325, 0x88C8, 0xB326, 0x88C9, 0xB327, 0x88CA, 0xB328, 0x88CB, 0xB329, 0x88CC, 0xB32A, 0x88CD, 0xB32B, 0x88CE, + 0xB32C, 0x88CF, 0xB32D, 0x88D0, 0xB32E, 0x88D1, 0xB32F, 0x88D2, 0xB330, 0x88D3, 0xB331, 0x88D4, 0xB332, 0x88D5, 0xB333, 0x88D6, + 0xB334, 0x88D7, 0xB335, 0x88D8, 0xB336, 0x88D9, 0xB337, 0x88DA, 0xB338, 0x88DB, 0xB339, 0x88DC, 0xB33A, 0x88DD, 0xB33B, 0x88DE, + 0xB33C, 0x88DF, 0xB33D, 0x88E0, 0xB33E, 0x88E1, 0xB33F, 0x88E2, 0xB340, 0x88E3, 0xB341, 0x88E4, 0xB342, 0x88E5, 0xB343, 0x88E6, + 0xB344, 0x88E7, 0xB345, 0x88E8, 0xB346, 0x88E9, 0xB347, 0x88EA, 0xB348, 0x88EB, 0xB349, 0x88EC, 0xB34A, 0x88ED, 0xB34B, 0x88EE, + 0xB34C, 0x88EF, 0xB34D, 0x88F0, 0xB34E, 0x88F1, 0xB34F, 0x88F2, 0xB350, 0x88F3, 0xB351, 0x88F4, 0xB352, 0x88F5, 0xB353, 0x88F6, + 0xB354, 0xB4F5, 0xB355, 0xB4F6, 0xB356, 0xB4F7, 0xB357, 0x88F7, 0xB358, 0xB4F8, 0xB359, 0x88F8, 0xB35A, 0x88F9, 0xB35B, 0xB4F9, + 0xB35C, 0xB4FA, 0xB35D, 0x88FA, 0xB35E, 0xB4FB, 0xB35F, 0xB4FC, 0xB360, 0x88FB, 0xB361, 0x88FC, 0xB362, 0x88FD, 0xB363, 0x88FE, + 0xB364, 0xB4FD, 0xB365, 0xB4FE, 0xB366, 0x8941, 0xB367, 0xB5A1, 0xB368, 0x8942, 0xB369, 0xB5A2, 0xB36A, 0x8943, 0xB36B, 0xB5A3, + 0xB36C, 0x8944, 0xB36D, 0x8945, 0xB36E, 0xB5A4, 0xB36F, 0x8946, 0xB370, 0xB5A5, 0xB371, 0xB5A6, 0xB372, 0x8947, 0xB373, 0x8948, + 0xB374, 0xB5A7, 0xB375, 0x8949, 0xB376, 0x894A, 0xB377, 0x894B, 0xB378, 0xB5A8, 0xB379, 0x894C, 0xB37A, 0x894D, 0xB37B, 0x894E, + 0xB37C, 0x894F, 0xB37D, 0x8950, 0xB37E, 0x8951, 0xB37F, 0x8952, 0xB380, 0xB5A9, 0xB381, 0xB5AA, 0xB382, 0x8953, 0xB383, 0xB5AB, + 0xB384, 0xB5AC, 0xB385, 0xB5AD, 0xB386, 0x8954, 0xB387, 0x8955, 0xB388, 0x8956, 0xB389, 0x8957, 0xB38A, 0x8958, 0xB38B, 0x8959, + 0xB38C, 0xB5AE, 0xB38D, 0x895A, 0xB38E, 0x8961, 0xB38F, 0x8962, 0xB390, 0xB5AF, 0xB391, 0x8963, 0xB392, 0x8964, 0xB393, 0x8965, + 0xB394, 0xB5B0, 0xB395, 0x8966, 0xB396, 0x8967, 0xB397, 0x8968, 0xB398, 0x8969, 0xB399, 0x896A, 0xB39A, 0x896B, 0xB39B, 0x896C, + 0xB39C, 0x896D, 0xB39D, 0x896E, 0xB39E, 0x896F, 0xB39F, 0x8970, 0xB3A0, 0xB5B1, 0xB3A1, 0xB5B2, 0xB3A2, 0x8971, 0xB3A3, 0x8972, + 0xB3A4, 0x8973, 0xB3A5, 0x8974, 0xB3A6, 0x8975, 0xB3A7, 0x8976, 0xB3A8, 0xB5B3, 0xB3A9, 0x8977, 0xB3AA, 0x8978, 0xB3AB, 0x8979, + 0xB3AC, 0xB5B4, 0xB3AD, 0x897A, 0xB3AE, 0x8981, 0xB3AF, 0x8982, 0xB3B0, 0x8983, 0xB3B1, 0x8984, 0xB3B2, 0x8985, 0xB3B3, 0x8986, + 0xB3B4, 0x8987, 0xB3B5, 0x8988, 0xB3B6, 0x8989, 0xB3B7, 0x898A, 0xB3B8, 0x898B, 0xB3B9, 0x898C, 0xB3BA, 0x898D, 0xB3BB, 0x898E, + 0xB3BC, 0x898F, 0xB3BD, 0x8990, 0xB3BE, 0x8991, 0xB3BF, 0x8992, 0xB3C0, 0x8993, 0xB3C1, 0x8994, 0xB3C2, 0x8995, 0xB3C3, 0x8996, + 0xB3C4, 0xB5B5, 0xB3C5, 0xB5B6, 0xB3C6, 0x8997, 0xB3C7, 0x8998, 0xB3C8, 0xB5B7, 0xB3C9, 0x8999, 0xB3CA, 0x899A, 0xB3CB, 0xB5B8, + 0xB3CC, 0xB5B9, 0xB3CD, 0x899B, 0xB3CE, 0xB5BA, 0xB3CF, 0x899C, 0xB3D0, 0xB5BB, 0xB3D1, 0x899D, 0xB3D2, 0x899E, 0xB3D3, 0x899F, + 0xB3D4, 0xB5BC, 0xB3D5, 0xB5BD, 0xB3D6, 0x89A0, 0xB3D7, 0xB5BE, 0xB3D8, 0x89A1, 0xB3D9, 0xB5BF, 0xB3DA, 0x89A2, 0xB3DB, 0xB5C0, + 0xB3DC, 0x89A3, 0xB3DD, 0xB5C1, 0xB3DE, 0x89A4, 0xB3DF, 0x89A5, 0xB3E0, 0xB5C2, 0xB3E1, 0x89A6, 0xB3E2, 0x89A7, 0xB3E3, 0x89A8, + 0xB3E4, 0xB5C3, 0xB3E5, 0x89A9, 0xB3E6, 0x89AA, 0xB3E7, 0x89AB, 0xB3E8, 0xB5C4, 0xB3E9, 0x89AC, 0xB3EA, 0x89AD, 0xB3EB, 0x89AE, + 0xB3EC, 0x89AF, 0xB3ED, 0x89B0, 0xB3EE, 0x89B1, 0xB3EF, 0x89B2, 0xB3F0, 0x89B3, 0xB3F1, 0x89B4, 0xB3F2, 0x89B5, 0xB3F3, 0x89B6, + 0xB3F4, 0x89B7, 0xB3F5, 0x89B8, 0xB3F6, 0x89B9, 0xB3F7, 0x89BA, 0xB3F8, 0x89BB, 0xB3F9, 0x89BC, 0xB3FA, 0x89BD, 0xB3FB, 0x89BE, + 0xB3FC, 0xB5C5, 0xB3FD, 0x89BF, 0xB3FE, 0x89C0, 0xB3FF, 0x89C1, 0xB400, 0x89C2, 0xB401, 0x89C3, 0xB402, 0x89C4, 0xB403, 0x89C5, + 0xB404, 0x89C6, 0xB405, 0x89C7, 0xB406, 0x89C8, 0xB407, 0x89C9, 0xB408, 0x89CA, 0xB409, 0x89CB, 0xB40A, 0x89CC, 0xB40B, 0x89CD, + 0xB40C, 0x89CE, 0xB40D, 0x89CF, 0xB40E, 0x89D0, 0xB40F, 0x89D1, 0xB410, 0xB5C6, 0xB411, 0x89D2, 0xB412, 0x89D3, 0xB413, 0x89D4, + 0xB414, 0x89D5, 0xB415, 0x89D6, 0xB416, 0x89D7, 0xB417, 0x89D8, 0xB418, 0xB5C7, 0xB419, 0x89D9, 0xB41A, 0x89DA, 0xB41B, 0x89DB, + 0xB41C, 0xB5C8, 0xB41D, 0x89DC, 0xB41E, 0x89DD, 0xB41F, 0x89DE, 0xB420, 0xB5C9, 0xB421, 0x89DF, 0xB422, 0x89E0, 0xB423, 0x89E1, + 0xB424, 0x89E2, 0xB425, 0x89E3, 0xB426, 0x89E4, 0xB427, 0x89E5, 0xB428, 0xB5CA, 0xB429, 0xB5CB, 0xB42A, 0x89E6, 0xB42B, 0xB5CC, + 0xB42C, 0x89E7, 0xB42D, 0x89E8, 0xB42E, 0x89E9, 0xB42F, 0x89EA, 0xB430, 0x89EB, 0xB431, 0x89EC, 0xB432, 0x89ED, 0xB433, 0x89EE, + 0xB434, 0xB5CD, 0xB435, 0x89EF, 0xB436, 0x89F0, 0xB437, 0x89F1, 0xB438, 0x89F2, 0xB439, 0x89F3, 0xB43A, 0x89F4, 0xB43B, 0x89F5, + 0xB43C, 0x89F6, 0xB43D, 0x89F7, 0xB43E, 0x89F8, 0xB43F, 0x89F9, 0xB440, 0x89FA, 0xB441, 0x89FB, 0xB442, 0x89FC, 0xB443, 0x89FD, + 0xB444, 0x89FE, 0xB445, 0x8A41, 0xB446, 0x8A42, 0xB447, 0x8A43, 0xB448, 0x8A44, 0xB449, 0x8A45, 0xB44A, 0x8A46, 0xB44B, 0x8A47, + 0xB44C, 0x8A48, 0xB44D, 0x8A49, 0xB44E, 0x8A4A, 0xB44F, 0x8A4B, 0xB450, 0xB5CE, 0xB451, 0xB5CF, 0xB452, 0x8A4C, 0xB453, 0x8A4D, + 0xB454, 0xB5D0, 0xB455, 0x8A4E, 0xB456, 0x8A4F, 0xB457, 0x8A50, 0xB458, 0xB5D1, 0xB459, 0x8A51, 0xB45A, 0x8A52, 0xB45B, 0x8A53, + 0xB45C, 0x8A54, 0xB45D, 0x8A55, 0xB45E, 0x8A56, 0xB45F, 0x8A57, 0xB460, 0xB5D2, 0xB461, 0xB5D3, 0xB462, 0x8A58, 0xB463, 0xB5D4, + 0xB464, 0x8A59, 0xB465, 0xB5D5, 0xB466, 0x8A5A, 0xB467, 0x8A61, 0xB468, 0x8A62, 0xB469, 0x8A63, 0xB46A, 0x8A64, 0xB46B, 0x8A65, + 0xB46C, 0xB5D6, 0xB46D, 0x8A66, 0xB46E, 0x8A67, 0xB46F, 0x8A68, 0xB470, 0x8A69, 0xB471, 0x8A6A, 0xB472, 0x8A6B, 0xB473, 0x8A6C, + 0xB474, 0x8A6D, 0xB475, 0x8A6E, 0xB476, 0x8A6F, 0xB477, 0x8A70, 0xB478, 0x8A71, 0xB479, 0x8A72, 0xB47A, 0x8A73, 0xB47B, 0x8A74, + 0xB47C, 0x8A75, 0xB47D, 0x8A76, 0xB47E, 0x8A77, 0xB47F, 0x8A78, 0xB480, 0xB5D7, 0xB481, 0x8A79, 0xB482, 0x8A7A, 0xB483, 0x8A81, + 0xB484, 0x8A82, 0xB485, 0x8A83, 0xB486, 0x8A84, 0xB487, 0x8A85, 0xB488, 0xB5D8, 0xB489, 0x8A86, 0xB48A, 0x8A87, 0xB48B, 0x8A88, + 0xB48C, 0x8A89, 0xB48D, 0x8A8A, 0xB48E, 0x8A8B, 0xB48F, 0x8A8C, 0xB490, 0x8A8D, 0xB491, 0x8A8E, 0xB492, 0x8A8F, 0xB493, 0x8A90, + 0xB494, 0x8A91, 0xB495, 0x8A92, 0xB496, 0x8A93, 0xB497, 0x8A94, 0xB498, 0x8A95, 0xB499, 0x8A96, 0xB49A, 0x8A97, 0xB49B, 0x8A98, + 0xB49C, 0x8A99, 0xB49D, 0xB5D9, 0xB49E, 0x8A9A, 0xB49F, 0x8A9B, 0xB4A0, 0x8A9C, 0xB4A1, 0x8A9D, 0xB4A2, 0x8A9E, 0xB4A3, 0x8A9F, + 0xB4A4, 0xB5DA, 0xB4A5, 0x8AA0, 0xB4A6, 0x8AA1, 0xB4A7, 0x8AA2, 0xB4A8, 0xB5DB, 0xB4A9, 0x8AA3, 0xB4AA, 0x8AA4, 0xB4AB, 0x8AA5, + 0xB4AC, 0xB5DC, 0xB4AD, 0x8AA6, 0xB4AE, 0x8AA7, 0xB4AF, 0x8AA8, 0xB4B0, 0x8AA9, 0xB4B1, 0x8AAA, 0xB4B2, 0x8AAB, 0xB4B3, 0x8AAC, + 0xB4B4, 0x8AAD, 0xB4B5, 0xB5DD, 0xB4B6, 0x8AAE, 0xB4B7, 0xB5DE, 0xB4B8, 0x8AAF, 0xB4B9, 0xB5DF, 0xB4BA, 0x8AB0, 0xB4BB, 0x8AB1, + 0xB4BC, 0x8AB2, 0xB4BD, 0x8AB3, 0xB4BE, 0x8AB4, 0xB4BF, 0x8AB5, 0xB4C0, 0xB5E0, 0xB4C1, 0x8AB6, 0xB4C2, 0x8AB7, 0xB4C3, 0x8AB8, + 0xB4C4, 0xB5E1, 0xB4C5, 0x8AB9, 0xB4C6, 0x8ABA, 0xB4C7, 0x8ABB, 0xB4C8, 0xB5E2, 0xB4C9, 0x8ABC, 0xB4CA, 0x8ABD, 0xB4CB, 0x8ABE, + 0xB4CC, 0x8ABF, 0xB4CD, 0x8AC0, 0xB4CE, 0x8AC1, 0xB4CF, 0x8AC2, 0xB4D0, 0xB5E3, 0xB4D1, 0x8AC3, 0xB4D2, 0x8AC4, 0xB4D3, 0x8AC5, + 0xB4D4, 0x8AC6, 0xB4D5, 0xB5E4, 0xB4D6, 0x8AC7, 0xB4D7, 0x8AC8, 0xB4D8, 0x8AC9, 0xB4D9, 0x8ACA, 0xB4DA, 0x8ACB, 0xB4DB, 0x8ACC, + 0xB4DC, 0xB5E5, 0xB4DD, 0xB5E6, 0xB4DE, 0x8ACD, 0xB4DF, 0x8ACE, 0xB4E0, 0xB5E7, 0xB4E1, 0x8ACF, 0xB4E2, 0x8AD0, 0xB4E3, 0xB5E8, + 0xB4E4, 0xB5E9, 0xB4E5, 0x8AD1, 0xB4E6, 0xB5EA, 0xB4E7, 0x8AD2, 0xB4E8, 0x8AD3, 0xB4E9, 0x8AD4, 0xB4EA, 0x8AD5, 0xB4EB, 0x8AD6, + 0xB4EC, 0xB5EB, 0xB4ED, 0xB5EC, 0xB4EE, 0x8AD7, 0xB4EF, 0xB5ED, 0xB4F0, 0x8AD8, 0xB4F1, 0xB5EE, 0xB4F2, 0x8AD9, 0xB4F3, 0x8ADA, + 0xB4F4, 0x8ADB, 0xB4F5, 0x8ADC, 0xB4F6, 0x8ADD, 0xB4F7, 0x8ADE, 0xB4F8, 0xB5EF, 0xB4F9, 0x8ADF, 0xB4FA, 0x8AE0, 0xB4FB, 0x8AE1, + 0xB4FC, 0x8AE2, 0xB4FD, 0x8AE3, 0xB4FE, 0x8AE4, 0xB4FF, 0x8AE5, 0xB500, 0x8AE6, 0xB501, 0x8AE7, 0xB502, 0x8AE8, 0xB503, 0x8AE9, + 0xB504, 0x8AEA, 0xB505, 0x8AEB, 0xB506, 0x8AEC, 0xB507, 0x8AED, 0xB508, 0x8AEE, 0xB509, 0x8AEF, 0xB50A, 0x8AF0, 0xB50B, 0x8AF1, + 0xB50C, 0x8AF2, 0xB50D, 0x8AF3, 0xB50E, 0x8AF4, 0xB50F, 0x8AF5, 0xB510, 0x8AF6, 0xB511, 0x8AF7, 0xB512, 0x8AF8, 0xB513, 0x8AF9, + 0xB514, 0xB5F0, 0xB515, 0xB5F1, 0xB516, 0x8AFA, 0xB517, 0x8AFB, 0xB518, 0xB5F2, 0xB519, 0x8AFC, 0xB51A, 0x8AFD, 0xB51B, 0xB5F3, + 0xB51C, 0xB5F4, 0xB51D, 0x8AFE, 0xB51E, 0x8B41, 0xB51F, 0x8B42, 0xB520, 0x8B43, 0xB521, 0x8B44, 0xB522, 0x8B45, 0xB523, 0x8B46, + 0xB524, 0xB5F5, 0xB525, 0xB5F6, 0xB526, 0x8B47, 0xB527, 0xB5F7, 0xB528, 0xB5F8, 0xB529, 0xB5F9, 0xB52A, 0xB5FA, 0xB52B, 0x8B48, + 0xB52C, 0x8B49, 0xB52D, 0x8B4A, 0xB52E, 0x8B4B, 0xB52F, 0x8B4C, 0xB530, 0xB5FB, 0xB531, 0xB5FC, 0xB532, 0x8B4D, 0xB533, 0x8B4E, + 0xB534, 0xB5FD, 0xB535, 0x8B4F, 0xB536, 0x8B50, 0xB537, 0x8B51, 0xB538, 0xB5FE, 0xB539, 0x8B52, 0xB53A, 0x8B53, 0xB53B, 0x8B54, + 0xB53C, 0x8B55, 0xB53D, 0x8B56, 0xB53E, 0x8B57, 0xB53F, 0x8B58, 0xB540, 0xB6A1, 0xB541, 0xB6A2, 0xB542, 0x8B59, 0xB543, 0xB6A3, + 0xB544, 0xB6A4, 0xB545, 0xB6A5, 0xB546, 0x8B5A, 0xB547, 0x8B61, 0xB548, 0x8B62, 0xB549, 0x8B63, 0xB54A, 0x8B64, 0xB54B, 0xB6A6, + 0xB54C, 0xB6A7, 0xB54D, 0xB6A8, 0xB54E, 0x8B65, 0xB54F, 0x8B66, 0xB550, 0xB6A9, 0xB551, 0x8B67, 0xB552, 0x8B68, 0xB553, 0x8B69, + 0xB554, 0xB6AA, 0xB555, 0x8B6A, 0xB556, 0x8B6B, 0xB557, 0x8B6C, 0xB558, 0x8B6D, 0xB559, 0x8B6E, 0xB55A, 0x8B6F, 0xB55B, 0x8B70, + 0xB55C, 0xB6AB, 0xB55D, 0xB6AC, 0xB55E, 0x8B71, 0xB55F, 0xB6AD, 0xB560, 0xB6AE, 0xB561, 0xB6AF, 0xB562, 0x8B72, 0xB563, 0x8B73, + 0xB564, 0x8B74, 0xB565, 0x8B75, 0xB566, 0x8B76, 0xB567, 0x8B77, 0xB568, 0x8B78, 0xB569, 0x8B79, 0xB56A, 0x8B7A, 0xB56B, 0x8B81, + 0xB56C, 0x8B82, 0xB56D, 0x8B83, 0xB56E, 0x8B84, 0xB56F, 0x8B85, 0xB570, 0x8B86, 0xB571, 0x8B87, 0xB572, 0x8B88, 0xB573, 0x8B89, + 0xB574, 0x8B8A, 0xB575, 0x8B8B, 0xB576, 0x8B8C, 0xB577, 0x8B8D, 0xB578, 0x8B8E, 0xB579, 0x8B8F, 0xB57A, 0x8B90, 0xB57B, 0x8B91, + 0xB57C, 0x8B92, 0xB57D, 0x8B93, 0xB57E, 0x8B94, 0xB57F, 0x8B95, 0xB580, 0x8B96, 0xB581, 0x8B97, 0xB582, 0x8B98, 0xB583, 0x8B99, + 0xB584, 0x8B9A, 0xB585, 0x8B9B, 0xB586, 0x8B9C, 0xB587, 0x8B9D, 0xB588, 0x8B9E, 0xB589, 0x8B9F, 0xB58A, 0x8BA0, 0xB58B, 0x8BA1, + 0xB58C, 0x8BA2, 0xB58D, 0x8BA3, 0xB58E, 0x8BA4, 0xB58F, 0x8BA5, 0xB590, 0x8BA6, 0xB591, 0x8BA7, 0xB592, 0x8BA8, 0xB593, 0x8BA9, + 0xB594, 0x8BAA, 0xB595, 0x8BAB, 0xB596, 0x8BAC, 0xB597, 0x8BAD, 0xB598, 0x8BAE, 0xB599, 0x8BAF, 0xB59A, 0x8BB0, 0xB59B, 0x8BB1, + 0xB59C, 0x8BB2, 0xB59D, 0x8BB3, 0xB59E, 0x8BB4, 0xB59F, 0x8BB5, 0xB5A0, 0xB6B0, 0xB5A1, 0xB6B1, 0xB5A2, 0x8BB6, 0xB5A3, 0x8BB7, + 0xB5A4, 0xB6B2, 0xB5A5, 0x8BB8, 0xB5A6, 0x8BB9, 0xB5A7, 0x8BBA, 0xB5A8, 0xB6B3, 0xB5A9, 0x8BBB, 0xB5AA, 0xB6B4, 0xB5AB, 0xB6B5, + 0xB5AC, 0x8BBC, 0xB5AD, 0x8BBD, 0xB5AE, 0x8BBE, 0xB5AF, 0x8BBF, 0xB5B0, 0xB6B6, 0xB5B1, 0xB6B7, 0xB5B2, 0x8BC0, 0xB5B3, 0xB6B8, + 0xB5B4, 0xB6B9, 0xB5B5, 0xB6BA, 0xB5B6, 0x8BC1, 0xB5B7, 0x8BC2, 0xB5B8, 0x8BC3, 0xB5B9, 0x8BC4, 0xB5BA, 0x8BC5, 0xB5BB, 0xB6BB, + 0xB5BC, 0xB6BC, 0xB5BD, 0xB6BD, 0xB5BE, 0x8BC6, 0xB5BF, 0x8BC7, 0xB5C0, 0xB6BE, 0xB5C1, 0x8BC8, 0xB5C2, 0x8BC9, 0xB5C3, 0x8BCA, + 0xB5C4, 0xB6BF, 0xB5C5, 0x8BCB, 0xB5C6, 0x8BCC, 0xB5C7, 0x8BCD, 0xB5C8, 0x8BCE, 0xB5C9, 0x8BCF, 0xB5CA, 0x8BD0, 0xB5CB, 0x8BD1, + 0xB5CC, 0xB6C0, 0xB5CD, 0xB6C1, 0xB5CE, 0x8BD2, 0xB5CF, 0xB6C2, 0xB5D0, 0xB6C3, 0xB5D1, 0xB6C4, 0xB5D2, 0x8BD3, 0xB5D3, 0x8BD4, + 0xB5D4, 0x8BD5, 0xB5D5, 0x8BD6, 0xB5D6, 0x8BD7, 0xB5D7, 0x8BD8, 0xB5D8, 0xB6C5, 0xB5D9, 0x8BD9, 0xB5DA, 0x8BDA, 0xB5DB, 0x8BDB, + 0xB5DC, 0x8BDC, 0xB5DD, 0x8BDD, 0xB5DE, 0x8BDE, 0xB5DF, 0x8BDF, 0xB5E0, 0x8BE0, 0xB5E1, 0x8BE1, 0xB5E2, 0x8BE2, 0xB5E3, 0x8BE3, + 0xB5E4, 0x8BE4, 0xB5E5, 0x8BE5, 0xB5E6, 0x8BE6, 0xB5E7, 0x8BE7, 0xB5E8, 0x8BE8, 0xB5E9, 0x8BE9, 0xB5EA, 0x8BEA, 0xB5EB, 0x8BEB, + 0xB5EC, 0xB6C6, 0xB5ED, 0x8BEC, 0xB5EE, 0x8BED, 0xB5EF, 0x8BEE, 0xB5F0, 0x8BEF, 0xB5F1, 0x8BF0, 0xB5F2, 0x8BF1, 0xB5F3, 0x8BF2, + 0xB5F4, 0x8BF3, 0xB5F5, 0x8BF4, 0xB5F6, 0x8BF5, 0xB5F7, 0x8BF6, 0xB5F8, 0x8BF7, 0xB5F9, 0x8BF8, 0xB5FA, 0x8BF9, 0xB5FB, 0x8BFA, + 0xB5FC, 0x8BFB, 0xB5FD, 0x8BFC, 0xB5FE, 0x8BFD, 0xB5FF, 0x8BFE, 0xB600, 0x8C41, 0xB601, 0x8C42, 0xB602, 0x8C43, 0xB603, 0x8C44, + 0xB604, 0x8C45, 0xB605, 0x8C46, 0xB606, 0x8C47, 0xB607, 0x8C48, 0xB608, 0x8C49, 0xB609, 0x8C4A, 0xB60A, 0x8C4B, 0xB60B, 0x8C4C, + 0xB60C, 0x8C4D, 0xB60D, 0x8C4E, 0xB60E, 0x8C4F, 0xB60F, 0x8C50, 0xB610, 0xB6C7, 0xB611, 0xB6C8, 0xB612, 0x8C51, 0xB613, 0x8C52, + 0xB614, 0xB6C9, 0xB615, 0x8C53, 0xB616, 0x8C54, 0xB617, 0x8C55, 0xB618, 0xB6CA, 0xB619, 0x8C56, 0xB61A, 0x8C57, 0xB61B, 0x8C58, + 0xB61C, 0x8C59, 0xB61D, 0x8C5A, 0xB61E, 0x8C61, 0xB61F, 0x8C62, 0xB620, 0x8C63, 0xB621, 0x8C64, 0xB622, 0x8C65, 0xB623, 0x8C66, + 0xB624, 0x8C67, 0xB625, 0xB6CB, 0xB626, 0x8C68, 0xB627, 0x8C69, 0xB628, 0x8C6A, 0xB629, 0x8C6B, 0xB62A, 0x8C6C, 0xB62B, 0x8C6D, + 0xB62C, 0xB6CC, 0xB62D, 0x8C6E, 0xB62E, 0x8C6F, 0xB62F, 0x8C70, 0xB630, 0x8C71, 0xB631, 0x8C72, 0xB632, 0x8C73, 0xB633, 0x8C74, + 0xB634, 0xB6CD, 0xB635, 0x8C75, 0xB636, 0x8C76, 0xB637, 0x8C77, 0xB638, 0x8C78, 0xB639, 0x8C79, 0xB63A, 0x8C7A, 0xB63B, 0x8C81, + 0xB63C, 0x8C82, 0xB63D, 0x8C83, 0xB63E, 0x8C84, 0xB63F, 0x8C85, 0xB640, 0x8C86, 0xB641, 0x8C87, 0xB642, 0x8C88, 0xB643, 0x8C89, + 0xB644, 0x8C8A, 0xB645, 0x8C8B, 0xB646, 0x8C8C, 0xB647, 0x8C8D, 0xB648, 0xB6CE, 0xB649, 0x8C8E, 0xB64A, 0x8C8F, 0xB64B, 0x8C90, + 0xB64C, 0x8C91, 0xB64D, 0x8C92, 0xB64E, 0x8C93, 0xB64F, 0x8C94, 0xB650, 0x8C95, 0xB651, 0x8C96, 0xB652, 0x8C97, 0xB653, 0x8C98, + 0xB654, 0x8C99, 0xB655, 0x8C9A, 0xB656, 0x8C9B, 0xB657, 0x8C9C, 0xB658, 0x8C9D, 0xB659, 0x8C9E, 0xB65A, 0x8C9F, 0xB65B, 0x8CA0, + 0xB65C, 0x8CA1, 0xB65D, 0x8CA2, 0xB65E, 0x8CA3, 0xB65F, 0x8CA4, 0xB660, 0x8CA5, 0xB661, 0x8CA6, 0xB662, 0x8CA7, 0xB663, 0x8CA8, + 0xB664, 0xB6CF, 0xB665, 0x8CA9, 0xB666, 0x8CAA, 0xB667, 0x8CAB, 0xB668, 0xB6D0, 0xB669, 0x8CAC, 0xB66A, 0x8CAD, 0xB66B, 0x8CAE, + 0xB66C, 0x8CAF, 0xB66D, 0x8CB0, 0xB66E, 0x8CB1, 0xB66F, 0x8CB2, 0xB670, 0x8CB3, 0xB671, 0x8CB4, 0xB672, 0x8CB5, 0xB673, 0x8CB6, + 0xB674, 0x8CB7, 0xB675, 0x8CB8, 0xB676, 0x8CB9, 0xB677, 0x8CBA, 0xB678, 0x8CBB, 0xB679, 0x8CBC, 0xB67A, 0x8CBD, 0xB67B, 0x8CBE, + 0xB67C, 0x8CBF, 0xB67D, 0x8CC0, 0xB67E, 0x8CC1, 0xB67F, 0x8CC2, 0xB680, 0x8CC3, 0xB681, 0x8CC4, 0xB682, 0x8CC5, 0xB683, 0x8CC6, + 0xB684, 0x8CC7, 0xB685, 0x8CC8, 0xB686, 0x8CC9, 0xB687, 0x8CCA, 0xB688, 0x8CCB, 0xB689, 0x8CCC, 0xB68A, 0x8CCD, 0xB68B, 0x8CCE, + 0xB68C, 0x8CCF, 0xB68D, 0x8CD0, 0xB68E, 0x8CD1, 0xB68F, 0x8CD2, 0xB690, 0x8CD3, 0xB691, 0x8CD4, 0xB692, 0x8CD5, 0xB693, 0x8CD6, + 0xB694, 0x8CD7, 0xB695, 0x8CD8, 0xB696, 0x8CD9, 0xB697, 0x8CDA, 0xB698, 0x8CDB, 0xB699, 0x8CDC, 0xB69A, 0x8CDD, 0xB69B, 0x8CDE, + 0xB69C, 0xB6D1, 0xB69D, 0xB6D2, 0xB69E, 0x8CDF, 0xB69F, 0x8CE0, 0xB6A0, 0xB6D3, 0xB6A1, 0x8CE1, 0xB6A2, 0x8CE2, 0xB6A3, 0x8CE3, + 0xB6A4, 0xB6D4, 0xB6A5, 0x8CE4, 0xB6A6, 0x8CE5, 0xB6A7, 0x8CE6, 0xB6A8, 0x8CE7, 0xB6A9, 0x8CE8, 0xB6AA, 0x8CE9, 0xB6AB, 0xB6D5, + 0xB6AC, 0xB6D6, 0xB6AD, 0x8CEA, 0xB6AE, 0x8CEB, 0xB6AF, 0x8CEC, 0xB6B0, 0x8CED, 0xB6B1, 0xB6D7, 0xB6B2, 0x8CEE, 0xB6B3, 0x8CEF, + 0xB6B4, 0x8CF0, 0xB6B5, 0x8CF1, 0xB6B6, 0x8CF2, 0xB6B7, 0x8CF3, 0xB6B8, 0x8CF4, 0xB6B9, 0x8CF5, 0xB6BA, 0x8CF6, 0xB6BB, 0x8CF7, + 0xB6BC, 0x8CF8, 0xB6BD, 0x8CF9, 0xB6BE, 0x8CFA, 0xB6BF, 0x8CFB, 0xB6C0, 0x8CFC, 0xB6C1, 0x8CFD, 0xB6C2, 0x8CFE, 0xB6C3, 0x8D41, + 0xB6C4, 0x8D42, 0xB6C5, 0x8D43, 0xB6C6, 0x8D44, 0xB6C7, 0x8D45, 0xB6C8, 0x8D46, 0xB6C9, 0x8D47, 0xB6CA, 0x8D48, 0xB6CB, 0x8D49, + 0xB6CC, 0x8D4A, 0xB6CD, 0x8D4B, 0xB6CE, 0x8D4C, 0xB6CF, 0x8D4D, 0xB6D0, 0x8D4E, 0xB6D1, 0x8D4F, 0xB6D2, 0x8D50, 0xB6D3, 0x8D51, + 0xB6D4, 0xB6D8, 0xB6D5, 0x8D52, 0xB6D6, 0x8D53, 0xB6D7, 0x8D54, 0xB6D8, 0x8D55, 0xB6D9, 0x8D56, 0xB6DA, 0x8D57, 0xB6DB, 0x8D58, + 0xB6DC, 0x8D59, 0xB6DD, 0x8D5A, 0xB6DE, 0x8D61, 0xB6DF, 0x8D62, 0xB6E0, 0x8D63, 0xB6E1, 0x8D64, 0xB6E2, 0x8D65, 0xB6E3, 0x8D66, + 0xB6E4, 0x8D67, 0xB6E5, 0x8D68, 0xB6E6, 0x8D69, 0xB6E7, 0x8D6A, 0xB6E8, 0x8D6B, 0xB6E9, 0x8D6C, 0xB6EA, 0x8D6D, 0xB6EB, 0x8D6E, + 0xB6EC, 0x8D6F, 0xB6ED, 0x8D70, 0xB6EE, 0x8D71, 0xB6EF, 0x8D72, 0xB6F0, 0xB6D9, 0xB6F1, 0x8D73, 0xB6F2, 0x8D74, 0xB6F3, 0x8D75, + 0xB6F4, 0xB6DA, 0xB6F5, 0x8D76, 0xB6F6, 0x8D77, 0xB6F7, 0x8D78, 0xB6F8, 0xB6DB, 0xB6F9, 0x8D79, 0xB6FA, 0x8D7A, 0xB6FB, 0x8D81, + 0xB6FC, 0x8D82, 0xB6FD, 0x8D83, 0xB6FE, 0x8D84, 0xB6FF, 0x8D85, 0xB700, 0xB6DC, 0xB701, 0xB6DD, 0xB702, 0x8D86, 0xB703, 0x8D87, + 0xB704, 0x8D88, 0xB705, 0xB6DE, 0xB706, 0x8D89, 0xB707, 0x8D8A, 0xB708, 0x8D8B, 0xB709, 0x8D8C, 0xB70A, 0x8D8D, 0xB70B, 0x8D8E, + 0xB70C, 0x8D8F, 0xB70D, 0x8D90, 0xB70E, 0x8D91, 0xB70F, 0x8D92, 0xB710, 0x8D93, 0xB711, 0x8D94, 0xB712, 0x8D95, 0xB713, 0x8D96, + 0xB714, 0x8D97, 0xB715, 0x8D98, 0xB716, 0x8D99, 0xB717, 0x8D9A, 0xB718, 0x8D9B, 0xB719, 0x8D9C, 0xB71A, 0x8D9D, 0xB71B, 0x8D9E, + 0xB71C, 0x8D9F, 0xB71D, 0x8DA0, 0xB71E, 0x8DA1, 0xB71F, 0x8DA2, 0xB720, 0x8DA3, 0xB721, 0x8DA4, 0xB722, 0x8DA5, 0xB723, 0x8DA6, + 0xB724, 0x8DA7, 0xB725, 0x8DA8, 0xB726, 0x8DA9, 0xB727, 0x8DAA, 0xB728, 0xB6DF, 0xB729, 0xB6E0, 0xB72A, 0x8DAB, 0xB72B, 0x8DAC, + 0xB72C, 0xB6E1, 0xB72D, 0x8DAD, 0xB72E, 0x8DAE, 0xB72F, 0xB6E2, 0xB730, 0xB6E3, 0xB731, 0x8DAF, 0xB732, 0x8DB0, 0xB733, 0x8DB1, + 0xB734, 0x8DB2, 0xB735, 0x8DB3, 0xB736, 0x8DB4, 0xB737, 0x8DB5, 0xB738, 0xB6E4, 0xB739, 0xB6E5, 0xB73A, 0x8DB6, 0xB73B, 0xB6E6, + 0xB73C, 0x8DB7, 0xB73D, 0x8DB8, 0xB73E, 0x8DB9, 0xB73F, 0x8DBA, 0xB740, 0x8DBB, 0xB741, 0x8DBC, 0xB742, 0x8DBD, 0xB743, 0x8DBE, + 0xB744, 0xB6E7, 0xB745, 0x8DBF, 0xB746, 0x8DC0, 0xB747, 0x8DC1, 0xB748, 0xB6E8, 0xB749, 0x8DC2, 0xB74A, 0x8DC3, 0xB74B, 0x8DC4, + 0xB74C, 0xB6E9, 0xB74D, 0x8DC5, 0xB74E, 0x8DC6, 0xB74F, 0x8DC7, 0xB750, 0x8DC8, 0xB751, 0x8DC9, 0xB752, 0x8DCA, 0xB753, 0x8DCB, + 0xB754, 0xB6EA, 0xB755, 0xB6EB, 0xB756, 0x8DCC, 0xB757, 0x8DCD, 0xB758, 0x8DCE, 0xB759, 0x8DCF, 0xB75A, 0x8DD0, 0xB75B, 0x8DD1, + 0xB75C, 0x8DD2, 0xB75D, 0x8DD3, 0xB75E, 0x8DD4, 0xB75F, 0x8DD5, 0xB760, 0xB6EC, 0xB761, 0x8DD6, 0xB762, 0x8DD7, 0xB763, 0x8DD8, + 0xB764, 0xB6ED, 0xB765, 0x8DD9, 0xB766, 0x8DDA, 0xB767, 0x8DDB, 0xB768, 0xB6EE, 0xB769, 0x8DDC, 0xB76A, 0x8DDD, 0xB76B, 0x8DDE, + 0xB76C, 0x8DDF, 0xB76D, 0x8DE0, 0xB76E, 0x8DE1, 0xB76F, 0x8DE2, 0xB770, 0xB6EF, 0xB771, 0xB6F0, 0xB772, 0x8DE3, 0xB773, 0xB6F1, + 0xB774, 0x8DE4, 0xB775, 0xB6F2, 0xB776, 0x8DE5, 0xB777, 0x8DE6, 0xB778, 0x8DE7, 0xB779, 0x8DE8, 0xB77A, 0x8DE9, 0xB77B, 0x8DEA, + 0xB77C, 0xB6F3, 0xB77D, 0xB6F4, 0xB77E, 0x8DEB, 0xB77F, 0x8DEC, 0xB780, 0xB6F5, 0xB781, 0x8DED, 0xB782, 0x8DEE, 0xB783, 0x8DEF, + 0xB784, 0xB6F6, 0xB785, 0x8DF0, 0xB786, 0x8DF1, 0xB787, 0x8DF2, 0xB788, 0x8DF3, 0xB789, 0x8DF4, 0xB78A, 0x8DF5, 0xB78B, 0x8DF6, + 0xB78C, 0xB6F7, 0xB78D, 0xB6F8, 0xB78E, 0x8DF7, 0xB78F, 0xB6F9, 0xB790, 0xB6FA, 0xB791, 0xB6FB, 0xB792, 0xB6FC, 0xB793, 0x8DF8, + 0xB794, 0x8DF9, 0xB795, 0x8DFA, 0xB796, 0xB6FD, 0xB797, 0xB6FE, 0xB798, 0xB7A1, 0xB799, 0xB7A2, 0xB79A, 0x8DFB, 0xB79B, 0x8DFC, + 0xB79C, 0xB7A3, 0xB79D, 0x8DFD, 0xB79E, 0x8DFE, 0xB79F, 0x8E41, 0xB7A0, 0xB7A4, 0xB7A1, 0x8E42, 0xB7A2, 0x8E43, 0xB7A3, 0x8E44, + 0xB7A4, 0x8E45, 0xB7A5, 0x8E46, 0xB7A6, 0x8E47, 0xB7A7, 0x8E48, 0xB7A8, 0xB7A5, 0xB7A9, 0xB7A6, 0xB7AA, 0x8E49, 0xB7AB, 0xB7A7, + 0xB7AC, 0xB7A8, 0xB7AD, 0xB7A9, 0xB7AE, 0x8E4A, 0xB7AF, 0x8E4B, 0xB7B0, 0x8E4C, 0xB7B1, 0x8E4D, 0xB7B2, 0x8E4E, 0xB7B3, 0x8E4F, + 0xB7B4, 0xB7AA, 0xB7B5, 0xB7AB, 0xB7B6, 0x8E50, 0xB7B7, 0x8E51, 0xB7B8, 0xB7AC, 0xB7B9, 0x8E52, 0xB7BA, 0x8E53, 0xB7BB, 0x8E54, + 0xB7BC, 0x8E55, 0xB7BD, 0x8E56, 0xB7BE, 0x8E57, 0xB7BF, 0x8E58, 0xB7C0, 0x8E59, 0xB7C1, 0x8E5A, 0xB7C2, 0x8E61, 0xB7C3, 0x8E62, + 0xB7C4, 0x8E63, 0xB7C5, 0x8E64, 0xB7C6, 0x8E65, 0xB7C7, 0xB7AD, 0xB7C8, 0x8E66, 0xB7C9, 0xB7AE, 0xB7CA, 0x8E67, 0xB7CB, 0x8E68, + 0xB7CC, 0x8E69, 0xB7CD, 0x8E6A, 0xB7CE, 0x8E6B, 0xB7CF, 0x8E6C, 0xB7D0, 0x8E6D, 0xB7D1, 0x8E6E, 0xB7D2, 0x8E6F, 0xB7D3, 0x8E70, + 0xB7D4, 0x8E71, 0xB7D5, 0x8E72, 0xB7D6, 0x8E73, 0xB7D7, 0x8E74, 0xB7D8, 0x8E75, 0xB7D9, 0x8E76, 0xB7DA, 0x8E77, 0xB7DB, 0x8E78, + 0xB7DC, 0x8E79, 0xB7DD, 0x8E7A, 0xB7DE, 0x8E81, 0xB7DF, 0x8E82, 0xB7E0, 0x8E83, 0xB7E1, 0x8E84, 0xB7E2, 0x8E85, 0xB7E3, 0x8E86, + 0xB7E4, 0x8E87, 0xB7E5, 0x8E88, 0xB7E6, 0x8E89, 0xB7E7, 0x8E8A, 0xB7E8, 0x8E8B, 0xB7E9, 0x8E8C, 0xB7EA, 0x8E8D, 0xB7EB, 0x8E8E, + 0xB7EC, 0xB7AF, 0xB7ED, 0xB7B0, 0xB7EE, 0x8E8F, 0xB7EF, 0x8E90, 0xB7F0, 0xB7B1, 0xB7F1, 0x8E91, 0xB7F2, 0x8E92, 0xB7F3, 0x8E93, + 0xB7F4, 0xB7B2, 0xB7F5, 0x8E94, 0xB7F6, 0x8E95, 0xB7F7, 0x8E96, 0xB7F8, 0x8E97, 0xB7F9, 0x8E98, 0xB7FA, 0x8E99, 0xB7FB, 0x8E9A, + 0xB7FC, 0xB7B3, 0xB7FD, 0xB7B4, 0xB7FE, 0x8E9B, 0xB7FF, 0xB7B5, 0xB800, 0xB7B6, 0xB801, 0xB7B7, 0xB802, 0x8E9C, 0xB803, 0x8E9D, + 0xB804, 0x8E9E, 0xB805, 0x8E9F, 0xB806, 0x8EA0, 0xB807, 0xB7B8, 0xB808, 0xB7B9, 0xB809, 0xB7BA, 0xB80A, 0x8EA1, 0xB80B, 0x8EA2, + 0xB80C, 0xB7BB, 0xB80D, 0x8EA3, 0xB80E, 0x8EA4, 0xB80F, 0x8EA5, 0xB810, 0xB7BC, 0xB811, 0x8EA6, 0xB812, 0x8EA7, 0xB813, 0x8EA8, + 0xB814, 0x8EA9, 0xB815, 0x8EAA, 0xB816, 0x8EAB, 0xB817, 0x8EAC, 0xB818, 0xB7BD, 0xB819, 0xB7BE, 0xB81A, 0x8EAD, 0xB81B, 0xB7BF, + 0xB81C, 0x8EAE, 0xB81D, 0xB7C0, 0xB81E, 0x8EAF, 0xB81F, 0x8EB0, 0xB820, 0x8EB1, 0xB821, 0x8EB2, 0xB822, 0x8EB3, 0xB823, 0x8EB4, + 0xB824, 0xB7C1, 0xB825, 0xB7C2, 0xB826, 0x8EB5, 0xB827, 0x8EB6, 0xB828, 0xB7C3, 0xB829, 0x8EB7, 0xB82A, 0x8EB8, 0xB82B, 0x8EB9, + 0xB82C, 0xB7C4, 0xB82D, 0x8EBA, 0xB82E, 0x8EBB, 0xB82F, 0x8EBC, 0xB830, 0x8EBD, 0xB831, 0x8EBE, 0xB832, 0x8EBF, 0xB833, 0x8EC0, + 0xB834, 0xB7C5, 0xB835, 0xB7C6, 0xB836, 0x8EC1, 0xB837, 0xB7C7, 0xB838, 0xB7C8, 0xB839, 0xB7C9, 0xB83A, 0x8EC2, 0xB83B, 0x8EC3, + 0xB83C, 0x8EC4, 0xB83D, 0x8EC5, 0xB83E, 0x8EC6, 0xB83F, 0x8EC7, 0xB840, 0xB7CA, 0xB841, 0x8EC8, 0xB842, 0x8EC9, 0xB843, 0x8ECA, + 0xB844, 0xB7CB, 0xB845, 0x8ECB, 0xB846, 0x8ECC, 0xB847, 0x8ECD, 0xB848, 0x8ECE, 0xB849, 0x8ECF, 0xB84A, 0x8ED0, 0xB84B, 0x8ED1, + 0xB84C, 0x8ED2, 0xB84D, 0x8ED3, 0xB84E, 0x8ED4, 0xB84F, 0x8ED5, 0xB850, 0x8ED6, 0xB851, 0xB7CC, 0xB852, 0x8ED7, 0xB853, 0xB7CD, + 0xB854, 0x8ED8, 0xB855, 0x8ED9, 0xB856, 0x8EDA, 0xB857, 0x8EDB, 0xB858, 0x8EDC, 0xB859, 0x8EDD, 0xB85A, 0x8EDE, 0xB85B, 0x8EDF, + 0xB85C, 0xB7CE, 0xB85D, 0xB7CF, 0xB85E, 0x8EE0, 0xB85F, 0x8EE1, 0xB860, 0xB7D0, 0xB861, 0x8EE2, 0xB862, 0x8EE3, 0xB863, 0x8EE4, + 0xB864, 0xB7D1, 0xB865, 0x8EE5, 0xB866, 0x8EE6, 0xB867, 0x8EE7, 0xB868, 0x8EE8, 0xB869, 0x8EE9, 0xB86A, 0x8EEA, 0xB86B, 0x8EEB, + 0xB86C, 0xB7D2, 0xB86D, 0xB7D3, 0xB86E, 0x8EEC, 0xB86F, 0xB7D4, 0xB870, 0x8EED, 0xB871, 0xB7D5, 0xB872, 0x8EEE, 0xB873, 0x8EEF, + 0xB874, 0x8EF0, 0xB875, 0x8EF1, 0xB876, 0x8EF2, 0xB877, 0x8EF3, 0xB878, 0xB7D6, 0xB879, 0x8EF4, 0xB87A, 0x8EF5, 0xB87B, 0x8EF6, + 0xB87C, 0xB7D7, 0xB87D, 0x8EF7, 0xB87E, 0x8EF8, 0xB87F, 0x8EF9, 0xB880, 0x8EFA, 0xB881, 0x8EFB, 0xB882, 0x8EFC, 0xB883, 0x8EFD, + 0xB884, 0x8EFE, 0xB885, 0x8F41, 0xB886, 0x8F42, 0xB887, 0x8F43, 0xB888, 0x8F44, 0xB889, 0x8F45, 0xB88A, 0x8F46, 0xB88B, 0x8F47, + 0xB88C, 0x8F48, 0xB88D, 0xB7D8, 0xB88E, 0x8F49, 0xB88F, 0x8F4A, 0xB890, 0x8F4B, 0xB891, 0x8F4C, 0xB892, 0x8F4D, 0xB893, 0x8F4E, + 0xB894, 0x8F4F, 0xB895, 0x8F50, 0xB896, 0x8F51, 0xB897, 0x8F52, 0xB898, 0x8F53, 0xB899, 0x8F54, 0xB89A, 0x8F55, 0xB89B, 0x8F56, + 0xB89C, 0x8F57, 0xB89D, 0x8F58, 0xB89E, 0x8F59, 0xB89F, 0x8F5A, 0xB8A0, 0x8F61, 0xB8A1, 0x8F62, 0xB8A2, 0x8F63, 0xB8A3, 0x8F64, + 0xB8A4, 0x8F65, 0xB8A5, 0x8F66, 0xB8A6, 0x8F67, 0xB8A7, 0x8F68, 0xB8A8, 0xB7D9, 0xB8A9, 0x8F69, 0xB8AA, 0x8F6A, 0xB8AB, 0x8F6B, + 0xB8AC, 0x8F6C, 0xB8AD, 0x8F6D, 0xB8AE, 0x8F6E, 0xB8AF, 0x8F6F, 0xB8B0, 0xB7DA, 0xB8B1, 0x8F70, 0xB8B2, 0x8F71, 0xB8B3, 0x8F72, + 0xB8B4, 0xB7DB, 0xB8B5, 0x8F73, 0xB8B6, 0x8F74, 0xB8B7, 0x8F75, 0xB8B8, 0xB7DC, 0xB8B9, 0x8F76, 0xB8BA, 0x8F77, 0xB8BB, 0x8F78, + 0xB8BC, 0x8F79, 0xB8BD, 0x8F7A, 0xB8BE, 0x8F81, 0xB8BF, 0x8F82, 0xB8C0, 0xB7DD, 0xB8C1, 0xB7DE, 0xB8C2, 0x8F83, 0xB8C3, 0xB7DF, + 0xB8C4, 0x8F84, 0xB8C5, 0xB7E0, 0xB8C6, 0x8F85, 0xB8C7, 0x8F86, 0xB8C8, 0x8F87, 0xB8C9, 0x8F88, 0xB8CA, 0x8F89, 0xB8CB, 0x8F8A, + 0xB8CC, 0xB7E1, 0xB8CD, 0x8F8B, 0xB8CE, 0x8F8C, 0xB8CF, 0x8F8D, 0xB8D0, 0xB7E2, 0xB8D1, 0x8F8E, 0xB8D2, 0x8F8F, 0xB8D3, 0x8F90, + 0xB8D4, 0xB7E3, 0xB8D5, 0x8F91, 0xB8D6, 0x8F92, 0xB8D7, 0x8F93, 0xB8D8, 0x8F94, 0xB8D9, 0x8F95, 0xB8DA, 0x8F96, 0xB8DB, 0x8F97, + 0xB8DC, 0x8F98, 0xB8DD, 0xB7E4, 0xB8DE, 0x8F99, 0xB8DF, 0xB7E5, 0xB8E0, 0x8F9A, 0xB8E1, 0xB7E6, 0xB8E2, 0x8F9B, 0xB8E3, 0x8F9C, + 0xB8E4, 0x8F9D, 0xB8E5, 0x8F9E, 0xB8E6, 0x8F9F, 0xB8E7, 0x8FA0, 0xB8E8, 0xB7E7, 0xB8E9, 0xB7E8, 0xB8EA, 0x8FA1, 0xB8EB, 0x8FA2, + 0xB8EC, 0xB7E9, 0xB8ED, 0x8FA3, 0xB8EE, 0x8FA4, 0xB8EF, 0x8FA5, 0xB8F0, 0xB7EA, 0xB8F1, 0x8FA6, 0xB8F2, 0x8FA7, 0xB8F3, 0x8FA8, + 0xB8F4, 0x8FA9, 0xB8F5, 0x8FAA, 0xB8F6, 0x8FAB, 0xB8F7, 0x8FAC, 0xB8F8, 0xB7EB, 0xB8F9, 0xB7EC, 0xB8FA, 0x8FAD, 0xB8FB, 0xB7ED, + 0xB8FC, 0x8FAE, 0xB8FD, 0xB7EE, 0xB8FE, 0x8FAF, 0xB8FF, 0x8FB0, 0xB900, 0x8FB1, 0xB901, 0x8FB2, 0xB902, 0x8FB3, 0xB903, 0x8FB4, + 0xB904, 0xB7EF, 0xB905, 0x8FB5, 0xB906, 0x8FB6, 0xB907, 0x8FB7, 0xB908, 0x8FB8, 0xB909, 0x8FB9, 0xB90A, 0x8FBA, 0xB90B, 0x8FBB, + 0xB90C, 0x8FBC, 0xB90D, 0x8FBD, 0xB90E, 0x8FBE, 0xB90F, 0x8FBF, 0xB910, 0x8FC0, 0xB911, 0x8FC1, 0xB912, 0x8FC2, 0xB913, 0x8FC3, + 0xB914, 0x8FC4, 0xB915, 0x8FC5, 0xB916, 0x8FC6, 0xB917, 0x8FC7, 0xB918, 0xB7F0, 0xB919, 0x8FC8, 0xB91A, 0x8FC9, 0xB91B, 0x8FCA, + 0xB91C, 0x8FCB, 0xB91D, 0x8FCC, 0xB91E, 0x8FCD, 0xB91F, 0x8FCE, 0xB920, 0xB7F1, 0xB921, 0x8FCF, 0xB922, 0x8FD0, 0xB923, 0x8FD1, + 0xB924, 0x8FD2, 0xB925, 0x8FD3, 0xB926, 0x8FD4, 0xB927, 0x8FD5, 0xB928, 0x8FD6, 0xB929, 0x8FD7, 0xB92A, 0x8FD8, 0xB92B, 0x8FD9, + 0xB92C, 0x8FDA, 0xB92D, 0x8FDB, 0xB92E, 0x8FDC, 0xB92F, 0x8FDD, 0xB930, 0x8FDE, 0xB931, 0x8FDF, 0xB932, 0x8FE0, 0xB933, 0x8FE1, + 0xB934, 0x8FE2, 0xB935, 0x8FE3, 0xB936, 0x8FE4, 0xB937, 0x8FE5, 0xB938, 0x8FE6, 0xB939, 0x8FE7, 0xB93A, 0x8FE8, 0xB93B, 0x8FE9, + 0xB93C, 0xB7F2, 0xB93D, 0xB7F3, 0xB93E, 0x8FEA, 0xB93F, 0x8FEB, 0xB940, 0xB7F4, 0xB941, 0x8FEC, 0xB942, 0x8FED, 0xB943, 0x8FEE, + 0xB944, 0xB7F5, 0xB945, 0x8FEF, 0xB946, 0x8FF0, 0xB947, 0x8FF1, 0xB948, 0x8FF2, 0xB949, 0x8FF3, 0xB94A, 0x8FF4, 0xB94B, 0x8FF5, + 0xB94C, 0xB7F6, 0xB94D, 0x8FF6, 0xB94E, 0x8FF7, 0xB94F, 0xB7F7, 0xB950, 0x8FF8, 0xB951, 0xB7F8, 0xB952, 0x8FF9, 0xB953, 0x8FFA, + 0xB954, 0x8FFB, 0xB955, 0x8FFC, 0xB956, 0x8FFD, 0xB957, 0x8FFE, 0xB958, 0xB7F9, 0xB959, 0xB7FA, 0xB95A, 0x9041, 0xB95B, 0x9042, + 0xB95C, 0xB7FB, 0xB95D, 0x9043, 0xB95E, 0x9044, 0xB95F, 0x9045, 0xB960, 0xB7FC, 0xB961, 0x9046, 0xB962, 0x9047, 0xB963, 0x9048, + 0xB964, 0x9049, 0xB965, 0x904A, 0xB966, 0x904B, 0xB967, 0x904C, 0xB968, 0xB7FD, 0xB969, 0xB7FE, 0xB96A, 0x904D, 0xB96B, 0xB8A1, + 0xB96C, 0x904E, 0xB96D, 0xB8A2, 0xB96E, 0x904F, 0xB96F, 0x9050, 0xB970, 0x9051, 0xB971, 0x9052, 0xB972, 0x9053, 0xB973, 0x9054, + 0xB974, 0xB8A3, 0xB975, 0xB8A4, 0xB976, 0x9055, 0xB977, 0x9056, 0xB978, 0xB8A5, 0xB979, 0x9057, 0xB97A, 0x9058, 0xB97B, 0x9059, + 0xB97C, 0xB8A6, 0xB97D, 0x905A, 0xB97E, 0x9061, 0xB97F, 0x9062, 0xB980, 0x9063, 0xB981, 0x9064, 0xB982, 0x9065, 0xB983, 0x9066, + 0xB984, 0xB8A7, 0xB985, 0xB8A8, 0xB986, 0x9067, 0xB987, 0xB8A9, 0xB988, 0x9068, 0xB989, 0xB8AA, 0xB98A, 0xB8AB, 0xB98B, 0x9069, + 0xB98C, 0x906A, 0xB98D, 0xB8AC, 0xB98E, 0xB8AD, 0xB98F, 0x906B, 0xB990, 0x906C, 0xB991, 0x906D, 0xB992, 0x906E, 0xB993, 0x906F, + 0xB994, 0x9070, 0xB995, 0x9071, 0xB996, 0x9072, 0xB997, 0x9073, 0xB998, 0x9074, 0xB999, 0x9075, 0xB99A, 0x9076, 0xB99B, 0x9077, + 0xB99C, 0x9078, 0xB99D, 0x9079, 0xB99E, 0x907A, 0xB99F, 0x9081, 0xB9A0, 0x9082, 0xB9A1, 0x9083, 0xB9A2, 0x9084, 0xB9A3, 0x9085, + 0xB9A4, 0x9086, 0xB9A5, 0x9087, 0xB9A6, 0x9088, 0xB9A7, 0x9089, 0xB9A8, 0x908A, 0xB9A9, 0x908B, 0xB9AA, 0x908C, 0xB9AB, 0x908D, + 0xB9AC, 0xB8AE, 0xB9AD, 0xB8AF, 0xB9AE, 0x908E, 0xB9AF, 0x908F, 0xB9B0, 0xB8B0, 0xB9B1, 0x9090, 0xB9B2, 0x9091, 0xB9B3, 0x9092, + 0xB9B4, 0xB8B1, 0xB9B5, 0x9093, 0xB9B6, 0x9094, 0xB9B7, 0x9095, 0xB9B8, 0x9096, 0xB9B9, 0x9097, 0xB9BA, 0x9098, 0xB9BB, 0x9099, + 0xB9BC, 0xB8B2, 0xB9BD, 0xB8B3, 0xB9BE, 0x909A, 0xB9BF, 0xB8B4, 0xB9C0, 0x909B, 0xB9C1, 0xB8B5, 0xB9C2, 0x909C, 0xB9C3, 0x909D, + 0xB9C4, 0x909E, 0xB9C5, 0x909F, 0xB9C6, 0x90A0, 0xB9C7, 0x90A1, 0xB9C8, 0xB8B6, 0xB9C9, 0xB8B7, 0xB9CA, 0x90A2, 0xB9CB, 0x90A3, + 0xB9CC, 0xB8B8, 0xB9CD, 0x90A4, 0xB9CE, 0xB8B9, 0xB9CF, 0xB8BA, 0xB9D0, 0xB8BB, 0xB9D1, 0xB8BC, 0xB9D2, 0xB8BD, 0xB9D3, 0x90A5, + 0xB9D4, 0x90A6, 0xB9D5, 0x90A7, 0xB9D6, 0x90A8, 0xB9D7, 0x90A9, 0xB9D8, 0xB8BE, 0xB9D9, 0xB8BF, 0xB9DA, 0x90AA, 0xB9DB, 0xB8C0, + 0xB9DC, 0x90AB, 0xB9DD, 0xB8C1, 0xB9DE, 0xB8C2, 0xB9DF, 0x90AC, 0xB9E0, 0x90AD, 0xB9E1, 0xB8C3, 0xB9E2, 0x90AE, 0xB9E3, 0xB8C4, + 0xB9E4, 0xB8C5, 0xB9E5, 0xB8C6, 0xB9E6, 0x90AF, 0xB9E7, 0x90B0, 0xB9E8, 0xB8C7, 0xB9E9, 0x90B1, 0xB9EA, 0x90B2, 0xB9EB, 0x90B3, + 0xB9EC, 0xB8C8, 0xB9ED, 0x90B4, 0xB9EE, 0x90B5, 0xB9EF, 0x90B6, 0xB9F0, 0x90B7, 0xB9F1, 0x90B8, 0xB9F2, 0x90B9, 0xB9F3, 0x90BA, + 0xB9F4, 0xB8C9, 0xB9F5, 0xB8CA, 0xB9F6, 0x90BB, 0xB9F7, 0xB8CB, 0xB9F8, 0xB8CC, 0xB9F9, 0xB8CD, 0xB9FA, 0xB8CE, 0xB9FB, 0x90BC, + 0xB9FC, 0x90BD, 0xB9FD, 0x90BE, 0xB9FE, 0x90BF, 0xB9FF, 0x90C0, 0xBA00, 0xB8CF, 0xBA01, 0xB8D0, 0xBA02, 0x90C1, 0xBA03, 0x90C2, + 0xBA04, 0x90C3, 0xBA05, 0x90C4, 0xBA06, 0x90C5, 0xBA07, 0x90C6, 0xBA08, 0xB8D1, 0xBA09, 0x90C7, 0xBA0A, 0x90C8, 0xBA0B, 0x90C9, + 0xBA0C, 0x90CA, 0xBA0D, 0x90CB, 0xBA0E, 0x90CC, 0xBA0F, 0x90CD, 0xBA10, 0x90CE, 0xBA11, 0x90CF, 0xBA12, 0x90D0, 0xBA13, 0x90D1, + 0xBA14, 0x90D2, 0xBA15, 0xB8D2, 0xBA16, 0x90D3, 0xBA17, 0x90D4, 0xBA18, 0x90D5, 0xBA19, 0x90D6, 0xBA1A, 0x90D7, 0xBA1B, 0x90D8, + 0xBA1C, 0x90D9, 0xBA1D, 0x90DA, 0xBA1E, 0x90DB, 0xBA1F, 0x90DC, 0xBA20, 0x90DD, 0xBA21, 0x90DE, 0xBA22, 0x90DF, 0xBA23, 0x90E0, + 0xBA24, 0x90E1, 0xBA25, 0x90E2, 0xBA26, 0x90E3, 0xBA27, 0x90E4, 0xBA28, 0x90E5, 0xBA29, 0x90E6, 0xBA2A, 0x90E7, 0xBA2B, 0x90E8, + 0xBA2C, 0x90E9, 0xBA2D, 0x90EA, 0xBA2E, 0x90EB, 0xBA2F, 0x90EC, 0xBA30, 0x90ED, 0xBA31, 0x90EE, 0xBA32, 0x90EF, 0xBA33, 0x90F0, + 0xBA34, 0x90F1, 0xBA35, 0x90F2, 0xBA36, 0x90F3, 0xBA37, 0x90F4, 0xBA38, 0xB8D3, 0xBA39, 0xB8D4, 0xBA3A, 0x90F5, 0xBA3B, 0x90F6, + 0xBA3C, 0xB8D5, 0xBA3D, 0x90F7, 0xBA3E, 0x90F8, 0xBA3F, 0x90F9, 0xBA40, 0xB8D6, 0xBA41, 0x90FA, 0xBA42, 0xB8D7, 0xBA43, 0x90FB, + 0xBA44, 0x90FC, 0xBA45, 0x90FD, 0xBA46, 0x90FE, 0xBA47, 0x9141, 0xBA48, 0xB8D8, 0xBA49, 0xB8D9, 0xBA4A, 0x9142, 0xBA4B, 0xB8DA, + 0xBA4C, 0x9143, 0xBA4D, 0xB8DB, 0xBA4E, 0xB8DC, 0xBA4F, 0x9144, 0xBA50, 0x9145, 0xBA51, 0x9146, 0xBA52, 0x9147, 0xBA53, 0xB8DD, + 0xBA54, 0xB8DE, 0xBA55, 0xB8DF, 0xBA56, 0x9148, 0xBA57, 0x9149, 0xBA58, 0xB8E0, 0xBA59, 0x914A, 0xBA5A, 0x914B, 0xBA5B, 0x914C, + 0xBA5C, 0xB8E1, 0xBA5D, 0x914D, 0xBA5E, 0x914E, 0xBA5F, 0x914F, 0xBA60, 0x9150, 0xBA61, 0x9151, 0xBA62, 0x9152, 0xBA63, 0x9153, + 0xBA64, 0xB8E2, 0xBA65, 0xB8E3, 0xBA66, 0x9154, 0xBA67, 0xB8E4, 0xBA68, 0xB8E5, 0xBA69, 0xB8E6, 0xBA6A, 0x9155, 0xBA6B, 0x9156, + 0xBA6C, 0x9157, 0xBA6D, 0x9158, 0xBA6E, 0x9159, 0xBA6F, 0x915A, 0xBA70, 0xB8E7, 0xBA71, 0xB8E8, 0xBA72, 0x9161, 0xBA73, 0x9162, + 0xBA74, 0xB8E9, 0xBA75, 0x9163, 0xBA76, 0x9164, 0xBA77, 0x9165, 0xBA78, 0xB8EA, 0xBA79, 0x9166, 0xBA7A, 0x9167, 0xBA7B, 0x9168, + 0xBA7C, 0x9169, 0xBA7D, 0x916A, 0xBA7E, 0x916B, 0xBA7F, 0x916C, 0xBA80, 0x916D, 0xBA81, 0x916E, 0xBA82, 0x916F, 0xBA83, 0xB8EB, + 0xBA84, 0xB8EC, 0xBA85, 0xB8ED, 0xBA86, 0x9170, 0xBA87, 0xB8EE, 0xBA88, 0x9171, 0xBA89, 0x9172, 0xBA8A, 0x9173, 0xBA8B, 0x9174, + 0xBA8C, 0xB8EF, 0xBA8D, 0x9175, 0xBA8E, 0x9176, 0xBA8F, 0x9177, 0xBA90, 0x9178, 0xBA91, 0x9179, 0xBA92, 0x917A, 0xBA93, 0x9181, + 0xBA94, 0x9182, 0xBA95, 0x9183, 0xBA96, 0x9184, 0xBA97, 0x9185, 0xBA98, 0x9186, 0xBA99, 0x9187, 0xBA9A, 0x9188, 0xBA9B, 0x9189, + 0xBA9C, 0x918A, 0xBA9D, 0x918B, 0xBA9E, 0x918C, 0xBA9F, 0x918D, 0xBAA0, 0x918E, 0xBAA1, 0x918F, 0xBAA2, 0x9190, 0xBAA3, 0x9191, + 0xBAA4, 0x9192, 0xBAA5, 0x9193, 0xBAA6, 0x9194, 0xBAA7, 0x9195, 0xBAA8, 0xB8F0, 0xBAA9, 0xB8F1, 0xBAAA, 0x9196, 0xBAAB, 0xB8F2, + 0xBAAC, 0xB8F3, 0xBAAD, 0x9197, 0xBAAE, 0x9198, 0xBAAF, 0x9199, 0xBAB0, 0xB8F4, 0xBAB1, 0x919A, 0xBAB2, 0xB8F5, 0xBAB3, 0x919B, + 0xBAB4, 0x919C, 0xBAB5, 0x919D, 0xBAB6, 0x919E, 0xBAB7, 0x919F, 0xBAB8, 0xB8F6, 0xBAB9, 0xB8F7, 0xBABA, 0x91A0, 0xBABB, 0xB8F8, + 0xBABC, 0x91A1, 0xBABD, 0xB8F9, 0xBABE, 0x91A2, 0xBABF, 0x91A3, 0xBAC0, 0x91A4, 0xBAC1, 0x91A5, 0xBAC2, 0x91A6, 0xBAC3, 0x91A7, + 0xBAC4, 0xB8FA, 0xBAC5, 0x91A8, 0xBAC6, 0x91A9, 0xBAC7, 0x91AA, 0xBAC8, 0xB8FB, 0xBAC9, 0x91AB, 0xBACA, 0x91AC, 0xBACB, 0x91AD, + 0xBACC, 0x91AE, 0xBACD, 0x91AF, 0xBACE, 0x91B0, 0xBACF, 0x91B1, 0xBAD0, 0x91B2, 0xBAD1, 0x91B3, 0xBAD2, 0x91B4, 0xBAD3, 0x91B5, + 0xBAD4, 0x91B6, 0xBAD5, 0x91B7, 0xBAD6, 0x91B8, 0xBAD7, 0x91B9, 0xBAD8, 0xB8FC, 0xBAD9, 0xB8FD, 0xBADA, 0x91BA, 0xBADB, 0x91BB, + 0xBADC, 0x91BC, 0xBADD, 0x91BD, 0xBADE, 0x91BE, 0xBADF, 0x91BF, 0xBAE0, 0x91C0, 0xBAE1, 0x91C1, 0xBAE2, 0x91C2, 0xBAE3, 0x91C3, + 0xBAE4, 0x91C4, 0xBAE5, 0x91C5, 0xBAE6, 0x91C6, 0xBAE7, 0x91C7, 0xBAE8, 0x91C8, 0xBAE9, 0x91C9, 0xBAEA, 0x91CA, 0xBAEB, 0x91CB, + 0xBAEC, 0x91CC, 0xBAED, 0x91CD, 0xBAEE, 0x91CE, 0xBAEF, 0x91CF, 0xBAF0, 0x91D0, 0xBAF1, 0x91D1, 0xBAF2, 0x91D2, 0xBAF3, 0x91D3, + 0xBAF4, 0x91D4, 0xBAF5, 0x91D5, 0xBAF6, 0x91D6, 0xBAF7, 0x91D7, 0xBAF8, 0x91D8, 0xBAF9, 0x91D9, 0xBAFA, 0x91DA, 0xBAFB, 0x91DB, + 0xBAFC, 0xB8FE, 0xBAFD, 0x91DC, 0xBAFE, 0x91DD, 0xBAFF, 0x91DE, 0xBB00, 0xB9A1, 0xBB01, 0x91DF, 0xBB02, 0x91E0, 0xBB03, 0x91E1, + 0xBB04, 0xB9A2, 0xBB05, 0x91E2, 0xBB06, 0x91E3, 0xBB07, 0x91E4, 0xBB08, 0x91E5, 0xBB09, 0x91E6, 0xBB0A, 0x91E7, 0xBB0B, 0x91E8, + 0xBB0C, 0x91E9, 0xBB0D, 0xB9A3, 0xBB0E, 0x91EA, 0xBB0F, 0xB9A4, 0xBB10, 0x91EB, 0xBB11, 0xB9A5, 0xBB12, 0x91EC, 0xBB13, 0x91ED, + 0xBB14, 0x91EE, 0xBB15, 0x91EF, 0xBB16, 0x91F0, 0xBB17, 0x91F1, 0xBB18, 0xB9A6, 0xBB19, 0x91F2, 0xBB1A, 0x91F3, 0xBB1B, 0x91F4, + 0xBB1C, 0xB9A7, 0xBB1D, 0x91F5, 0xBB1E, 0x91F6, 0xBB1F, 0x91F7, 0xBB20, 0xB9A8, 0xBB21, 0x91F8, 0xBB22, 0x91F9, 0xBB23, 0x91FA, + 0xBB24, 0x91FB, 0xBB25, 0x91FC, 0xBB26, 0x91FD, 0xBB27, 0x91FE, 0xBB28, 0x9241, 0xBB29, 0xB9A9, 0xBB2A, 0x9242, 0xBB2B, 0xB9AA, + 0xBB2C, 0x9243, 0xBB2D, 0x9244, 0xBB2E, 0x9245, 0xBB2F, 0x9246, 0xBB30, 0x9247, 0xBB31, 0x9248, 0xBB32, 0x9249, 0xBB33, 0x924A, + 0xBB34, 0xB9AB, 0xBB35, 0xB9AC, 0xBB36, 0xB9AD, 0xBB37, 0x924B, 0xBB38, 0xB9AE, 0xBB39, 0x924C, 0xBB3A, 0x924D, 0xBB3B, 0xB9AF, + 0xBB3C, 0xB9B0, 0xBB3D, 0xB9B1, 0xBB3E, 0xB9B2, 0xBB3F, 0x924E, 0xBB40, 0x924F, 0xBB41, 0x9250, 0xBB42, 0x9251, 0xBB43, 0x9252, + 0xBB44, 0xB9B3, 0xBB45, 0xB9B4, 0xBB46, 0x9253, 0xBB47, 0xB9B5, 0xBB48, 0x9254, 0xBB49, 0xB9B6, 0xBB4A, 0x9255, 0xBB4B, 0x9256, + 0xBB4C, 0x9257, 0xBB4D, 0xB9B7, 0xBB4E, 0x9258, 0xBB4F, 0xB9B8, 0xBB50, 0xB9B9, 0xBB51, 0x9259, 0xBB52, 0x925A, 0xBB53, 0x9261, + 0xBB54, 0xB9BA, 0xBB55, 0x9262, 0xBB56, 0x9263, 0xBB57, 0x9264, 0xBB58, 0xB9BB, 0xBB59, 0x9265, 0xBB5A, 0x9266, 0xBB5B, 0x9267, + 0xBB5C, 0x9268, 0xBB5D, 0x9269, 0xBB5E, 0x926A, 0xBB5F, 0x926B, 0xBB60, 0x926C, 0xBB61, 0xB9BC, 0xBB62, 0x926D, 0xBB63, 0xB9BD, + 0xBB64, 0x926E, 0xBB65, 0x926F, 0xBB66, 0x9270, 0xBB67, 0x9271, 0xBB68, 0x9272, 0xBB69, 0x9273, 0xBB6A, 0x9274, 0xBB6B, 0x9275, + 0xBB6C, 0xB9BE, 0xBB6D, 0x9276, 0xBB6E, 0x9277, 0xBB6F, 0x9278, 0xBB70, 0x9279, 0xBB71, 0x927A, 0xBB72, 0x9281, 0xBB73, 0x9282, + 0xBB74, 0x9283, 0xBB75, 0x9284, 0xBB76, 0x9285, 0xBB77, 0x9286, 0xBB78, 0x9287, 0xBB79, 0x9288, 0xBB7A, 0x9289, 0xBB7B, 0x928A, + 0xBB7C, 0x928B, 0xBB7D, 0x928C, 0xBB7E, 0x928D, 0xBB7F, 0x928E, 0xBB80, 0x928F, 0xBB81, 0x9290, 0xBB82, 0x9291, 0xBB83, 0x9292, + 0xBB84, 0x9293, 0xBB85, 0x9294, 0xBB86, 0x9295, 0xBB87, 0x9296, 0xBB88, 0xB9BF, 0xBB89, 0x9297, 0xBB8A, 0x9298, 0xBB8B, 0x9299, + 0xBB8C, 0xB9C0, 0xBB8D, 0x929A, 0xBB8E, 0x929B, 0xBB8F, 0x929C, 0xBB90, 0xB9C1, 0xBB91, 0x929D, 0xBB92, 0x929E, 0xBB93, 0x929F, + 0xBB94, 0x92A0, 0xBB95, 0x92A1, 0xBB96, 0x92A2, 0xBB97, 0x92A3, 0xBB98, 0x92A4, 0xBB99, 0x92A5, 0xBB9A, 0x92A6, 0xBB9B, 0x92A7, + 0xBB9C, 0x92A8, 0xBB9D, 0x92A9, 0xBB9E, 0x92AA, 0xBB9F, 0x92AB, 0xBBA0, 0x92AC, 0xBBA1, 0x92AD, 0xBBA2, 0x92AE, 0xBBA3, 0x92AF, + 0xBBA4, 0xB9C2, 0xBBA5, 0x92B0, 0xBBA6, 0x92B1, 0xBBA7, 0x92B2, 0xBBA8, 0xB9C3, 0xBBA9, 0x92B3, 0xBBAA, 0x92B4, 0xBBAB, 0x92B5, + 0xBBAC, 0xB9C4, 0xBBAD, 0x92B6, 0xBBAE, 0x92B7, 0xBBAF, 0x92B8, 0xBBB0, 0x92B9, 0xBBB1, 0x92BA, 0xBBB2, 0x92BB, 0xBBB3, 0x92BC, + 0xBBB4, 0xB9C5, 0xBBB5, 0x92BD, 0xBBB6, 0x92BE, 0xBBB7, 0xB9C6, 0xBBB8, 0x92BF, 0xBBB9, 0x92C0, 0xBBBA, 0x92C1, 0xBBBB, 0x92C2, + 0xBBBC, 0x92C3, 0xBBBD, 0x92C4, 0xBBBE, 0x92C5, 0xBBBF, 0x92C6, 0xBBC0, 0xB9C7, 0xBBC1, 0x92C7, 0xBBC2, 0x92C8, 0xBBC3, 0x92C9, + 0xBBC4, 0xB9C8, 0xBBC5, 0x92CA, 0xBBC6, 0x92CB, 0xBBC7, 0x92CC, 0xBBC8, 0xB9C9, 0xBBC9, 0x92CD, 0xBBCA, 0x92CE, 0xBBCB, 0x92CF, + 0xBBCC, 0x92D0, 0xBBCD, 0x92D1, 0xBBCE, 0x92D2, 0xBBCF, 0x92D3, 0xBBD0, 0xB9CA, 0xBBD1, 0x92D4, 0xBBD2, 0x92D5, 0xBBD3, 0xB9CB, + 0xBBD4, 0x92D6, 0xBBD5, 0x92D7, 0xBBD6, 0x92D8, 0xBBD7, 0x92D9, 0xBBD8, 0x92DA, 0xBBD9, 0x92DB, 0xBBDA, 0x92DC, 0xBBDB, 0x92DD, + 0xBBDC, 0x92DE, 0xBBDD, 0x92DF, 0xBBDE, 0x92E0, 0xBBDF, 0x92E1, 0xBBE0, 0x92E2, 0xBBE1, 0x92E3, 0xBBE2, 0x92E4, 0xBBE3, 0x92E5, + 0xBBE4, 0x92E6, 0xBBE5, 0x92E7, 0xBBE6, 0x92E8, 0xBBE7, 0x92E9, 0xBBE8, 0x92EA, 0xBBE9, 0x92EB, 0xBBEA, 0x92EC, 0xBBEB, 0x92ED, + 0xBBEC, 0x92EE, 0xBBED, 0x92EF, 0xBBEE, 0x92F0, 0xBBEF, 0x92F1, 0xBBF0, 0x92F2, 0xBBF1, 0x92F3, 0xBBF2, 0x92F4, 0xBBF3, 0x92F5, + 0xBBF4, 0x92F6, 0xBBF5, 0x92F7, 0xBBF6, 0x92F8, 0xBBF7, 0x92F9, 0xBBF8, 0xB9CC, 0xBBF9, 0xB9CD, 0xBBFA, 0x92FA, 0xBBFB, 0x92FB, + 0xBBFC, 0xB9CE, 0xBBFD, 0x92FC, 0xBBFE, 0x92FD, 0xBBFF, 0xB9CF, 0xBC00, 0xB9D0, 0xBC01, 0x92FE, 0xBC02, 0xB9D1, 0xBC03, 0x9341, + 0xBC04, 0x9342, 0xBC05, 0x9343, 0xBC06, 0x9344, 0xBC07, 0x9345, 0xBC08, 0xB9D2, 0xBC09, 0xB9D3, 0xBC0A, 0x9346, 0xBC0B, 0xB9D4, + 0xBC0C, 0xB9D5, 0xBC0D, 0xB9D6, 0xBC0E, 0x9347, 0xBC0F, 0xB9D7, 0xBC10, 0x9348, 0xBC11, 0xB9D8, 0xBC12, 0x9349, 0xBC13, 0x934A, + 0xBC14, 0xB9D9, 0xBC15, 0xB9DA, 0xBC16, 0xB9DB, 0xBC17, 0xB9DC, 0xBC18, 0xB9DD, 0xBC19, 0x934B, 0xBC1A, 0x934C, 0xBC1B, 0xB9DE, + 0xBC1C, 0xB9DF, 0xBC1D, 0xB9E0, 0xBC1E, 0xB9E1, 0xBC1F, 0xB9E2, 0xBC20, 0x934D, 0xBC21, 0x934E, 0xBC22, 0x934F, 0xBC23, 0x9350, + 0xBC24, 0xB9E3, 0xBC25, 0xB9E4, 0xBC26, 0x9351, 0xBC27, 0xB9E5, 0xBC28, 0x9352, 0xBC29, 0xB9E6, 0xBC2A, 0x9353, 0xBC2B, 0x9354, + 0xBC2C, 0x9355, 0xBC2D, 0xB9E7, 0xBC2E, 0x9356, 0xBC2F, 0x9357, 0xBC30, 0xB9E8, 0xBC31, 0xB9E9, 0xBC32, 0x9358, 0xBC33, 0x9359, + 0xBC34, 0xB9EA, 0xBC35, 0x935A, 0xBC36, 0x9361, 0xBC37, 0x9362, 0xBC38, 0xB9EB, 0xBC39, 0x9363, 0xBC3A, 0x9364, 0xBC3B, 0x9365, + 0xBC3C, 0x9366, 0xBC3D, 0x9367, 0xBC3E, 0x9368, 0xBC3F, 0x9369, 0xBC40, 0xB9EC, 0xBC41, 0xB9ED, 0xBC42, 0x936A, 0xBC43, 0xB9EE, + 0xBC44, 0xB9EF, 0xBC45, 0xB9F0, 0xBC46, 0x936B, 0xBC47, 0x936C, 0xBC48, 0x936D, 0xBC49, 0xB9F1, 0xBC4A, 0x936E, 0xBC4B, 0x936F, + 0xBC4C, 0xB9F2, 0xBC4D, 0xB9F3, 0xBC4E, 0x9370, 0xBC4F, 0x9371, 0xBC50, 0xB9F4, 0xBC51, 0x9372, 0xBC52, 0x9373, 0xBC53, 0x9374, + 0xBC54, 0x9375, 0xBC55, 0x9376, 0xBC56, 0x9377, 0xBC57, 0x9378, 0xBC58, 0x9379, 0xBC59, 0x937A, 0xBC5A, 0x9381, 0xBC5B, 0x9382, + 0xBC5C, 0x9383, 0xBC5D, 0xB9F5, 0xBC5E, 0x9384, 0xBC5F, 0x9385, 0xBC60, 0x9386, 0xBC61, 0x9387, 0xBC62, 0x9388, 0xBC63, 0x9389, + 0xBC64, 0x938A, 0xBC65, 0x938B, 0xBC66, 0x938C, 0xBC67, 0x938D, 0xBC68, 0x938E, 0xBC69, 0x938F, 0xBC6A, 0x9390, 0xBC6B, 0x9391, + 0xBC6C, 0x9392, 0xBC6D, 0x9393, 0xBC6E, 0x9394, 0xBC6F, 0x9395, 0xBC70, 0x9396, 0xBC71, 0x9397, 0xBC72, 0x9398, 0xBC73, 0x9399, + 0xBC74, 0x939A, 0xBC75, 0x939B, 0xBC76, 0x939C, 0xBC77, 0x939D, 0xBC78, 0x939E, 0xBC79, 0x939F, 0xBC7A, 0x93A0, 0xBC7B, 0x93A1, + 0xBC7C, 0x93A2, 0xBC7D, 0x93A3, 0xBC7E, 0x93A4, 0xBC7F, 0x93A5, 0xBC80, 0x93A6, 0xBC81, 0x93A7, 0xBC82, 0x93A8, 0xBC83, 0x93A9, + 0xBC84, 0xB9F6, 0xBC85, 0xB9F7, 0xBC86, 0x93AA, 0xBC87, 0x93AB, 0xBC88, 0xB9F8, 0xBC89, 0x93AC, 0xBC8A, 0x93AD, 0xBC8B, 0xB9F9, + 0xBC8C, 0xB9FA, 0xBC8D, 0x93AE, 0xBC8E, 0xB9FB, 0xBC8F, 0x93AF, 0xBC90, 0x93B0, 0xBC91, 0x93B1, 0xBC92, 0x93B2, 0xBC93, 0x93B3, + 0xBC94, 0xB9FC, 0xBC95, 0xB9FD, 0xBC96, 0x93B4, 0xBC97, 0xB9FE, 0xBC98, 0x93B5, 0xBC99, 0xBAA1, 0xBC9A, 0xBAA2, 0xBC9B, 0x93B6, + 0xBC9C, 0x93B7, 0xBC9D, 0x93B8, 0xBC9E, 0x93B9, 0xBC9F, 0x93BA, 0xBCA0, 0xBAA3, 0xBCA1, 0xBAA4, 0xBCA2, 0x93BB, 0xBCA3, 0x93BC, + 0xBCA4, 0xBAA5, 0xBCA5, 0x93BD, 0xBCA6, 0x93BE, 0xBCA7, 0xBAA6, 0xBCA8, 0xBAA7, 0xBCA9, 0x93BF, 0xBCAA, 0x93C0, 0xBCAB, 0x93C1, + 0xBCAC, 0x93C2, 0xBCAD, 0x93C3, 0xBCAE, 0x93C4, 0xBCAF, 0x93C5, 0xBCB0, 0xBAA8, 0xBCB1, 0xBAA9, 0xBCB2, 0x93C6, 0xBCB3, 0xBAAA, + 0xBCB4, 0xBAAB, 0xBCB5, 0xBAAC, 0xBCB6, 0x93C7, 0xBCB7, 0x93C8, 0xBCB8, 0x93C9, 0xBCB9, 0x93CA, 0xBCBA, 0x93CB, 0xBCBB, 0x93CC, + 0xBCBC, 0xBAAD, 0xBCBD, 0xBAAE, 0xBCBE, 0x93CD, 0xBCBF, 0x93CE, 0xBCC0, 0xBAAF, 0xBCC1, 0x93CF, 0xBCC2, 0x93D0, 0xBCC3, 0x93D1, + 0xBCC4, 0xBAB0, 0xBCC5, 0x93D2, 0xBCC6, 0x93D3, 0xBCC7, 0x93D4, 0xBCC8, 0x93D5, 0xBCC9, 0x93D6, 0xBCCA, 0x93D7, 0xBCCB, 0x93D8, + 0xBCCC, 0x93D9, 0xBCCD, 0xBAB1, 0xBCCE, 0x93DA, 0xBCCF, 0xBAB2, 0xBCD0, 0xBAB3, 0xBCD1, 0xBAB4, 0xBCD2, 0x93DB, 0xBCD3, 0x93DC, + 0xBCD4, 0x93DD, 0xBCD5, 0xBAB5, 0xBCD6, 0x93DE, 0xBCD7, 0x93DF, 0xBCD8, 0xBAB6, 0xBCD9, 0x93E0, 0xBCDA, 0x93E1, 0xBCDB, 0x93E2, + 0xBCDC, 0xBAB7, 0xBCDD, 0x93E3, 0xBCDE, 0x93E4, 0xBCDF, 0x93E5, 0xBCE0, 0x93E6, 0xBCE1, 0x93E7, 0xBCE2, 0x93E8, 0xBCE3, 0x93E9, + 0xBCE4, 0x93EA, 0xBCE5, 0x93EB, 0xBCE6, 0x93EC, 0xBCE7, 0x93ED, 0xBCE8, 0x93EE, 0xBCE9, 0x93EF, 0xBCEA, 0x93F0, 0xBCEB, 0x93F1, + 0xBCEC, 0x93F2, 0xBCED, 0x93F3, 0xBCEE, 0x93F4, 0xBCEF, 0x93F5, 0xBCF0, 0x93F6, 0xBCF1, 0x93F7, 0xBCF2, 0x93F8, 0xBCF3, 0x93F9, + 0xBCF4, 0xBAB8, 0xBCF5, 0xBAB9, 0xBCF6, 0xBABA, 0xBCF7, 0x93FA, 0xBCF8, 0xBABB, 0xBCF9, 0x93FB, 0xBCFA, 0x93FC, 0xBCFB, 0x93FD, + 0xBCFC, 0xBABC, 0xBCFD, 0x93FE, 0xBCFE, 0x9441, 0xBCFF, 0x9442, 0xBD00, 0x9443, 0xBD01, 0x9444, 0xBD02, 0x9445, 0xBD03, 0x9446, + 0xBD04, 0xBABD, 0xBD05, 0xBABE, 0xBD06, 0x9447, 0xBD07, 0xBABF, 0xBD08, 0x9448, 0xBD09, 0xBAC0, 0xBD0A, 0x9449, 0xBD0B, 0x944A, + 0xBD0C, 0x944B, 0xBD0D, 0x944C, 0xBD0E, 0x944D, 0xBD0F, 0x944E, 0xBD10, 0xBAC1, 0xBD11, 0x944F, 0xBD12, 0x9450, 0xBD13, 0x9451, + 0xBD14, 0xBAC2, 0xBD15, 0x9452, 0xBD16, 0x9453, 0xBD17, 0x9454, 0xBD18, 0x9455, 0xBD19, 0x9456, 0xBD1A, 0x9457, 0xBD1B, 0x9458, + 0xBD1C, 0x9459, 0xBD1D, 0x945A, 0xBD1E, 0x9461, 0xBD1F, 0x9462, 0xBD20, 0x9463, 0xBD21, 0x9464, 0xBD22, 0x9465, 0xBD23, 0x9466, + 0xBD24, 0xBAC3, 0xBD25, 0x9467, 0xBD26, 0x9468, 0xBD27, 0x9469, 0xBD28, 0x946A, 0xBD29, 0x946B, 0xBD2A, 0x946C, 0xBD2B, 0x946D, + 0xBD2C, 0xBAC4, 0xBD2D, 0x946E, 0xBD2E, 0x946F, 0xBD2F, 0x9470, 0xBD30, 0x9471, 0xBD31, 0x9472, 0xBD32, 0x9473, 0xBD33, 0x9474, + 0xBD34, 0x9475, 0xBD35, 0x9476, 0xBD36, 0x9477, 0xBD37, 0x9478, 0xBD38, 0x9479, 0xBD39, 0x947A, 0xBD3A, 0x9481, 0xBD3B, 0x9482, + 0xBD3C, 0x9483, 0xBD3D, 0x9484, 0xBD3E, 0x9485, 0xBD3F, 0x9486, 0xBD40, 0xBAC5, 0xBD41, 0x9487, 0xBD42, 0x9488, 0xBD43, 0x9489, + 0xBD44, 0x948A, 0xBD45, 0x948B, 0xBD46, 0x948C, 0xBD47, 0x948D, 0xBD48, 0xBAC6, 0xBD49, 0xBAC7, 0xBD4A, 0x948E, 0xBD4B, 0x948F, + 0xBD4C, 0xBAC8, 0xBD4D, 0x9490, 0xBD4E, 0x9491, 0xBD4F, 0x9492, 0xBD50, 0xBAC9, 0xBD51, 0x9493, 0xBD52, 0x9494, 0xBD53, 0x9495, + 0xBD54, 0x9496, 0xBD55, 0x9497, 0xBD56, 0x9498, 0xBD57, 0x9499, 0xBD58, 0xBACA, 0xBD59, 0xBACB, 0xBD5A, 0x949A, 0xBD5B, 0x949B, + 0xBD5C, 0x949C, 0xBD5D, 0x949D, 0xBD5E, 0x949E, 0xBD5F, 0x949F, 0xBD60, 0x94A0, 0xBD61, 0x94A1, 0xBD62, 0x94A2, 0xBD63, 0x94A3, + 0xBD64, 0xBACC, 0xBD65, 0x94A4, 0xBD66, 0x94A5, 0xBD67, 0x94A6, 0xBD68, 0xBACD, 0xBD69, 0x94A7, 0xBD6A, 0x94A8, 0xBD6B, 0x94A9, + 0xBD6C, 0x94AA, 0xBD6D, 0x94AB, 0xBD6E, 0x94AC, 0xBD6F, 0x94AD, 0xBD70, 0x94AE, 0xBD71, 0x94AF, 0xBD72, 0x94B0, 0xBD73, 0x94B1, + 0xBD74, 0x94B2, 0xBD75, 0x94B3, 0xBD76, 0x94B4, 0xBD77, 0x94B5, 0xBD78, 0x94B6, 0xBD79, 0x94B7, 0xBD7A, 0x94B8, 0xBD7B, 0x94B9, + 0xBD7C, 0x94BA, 0xBD7D, 0x94BB, 0xBD7E, 0x94BC, 0xBD7F, 0x94BD, 0xBD80, 0xBACE, 0xBD81, 0xBACF, 0xBD82, 0x94BE, 0xBD83, 0x94BF, + 0xBD84, 0xBAD0, 0xBD85, 0x94C0, 0xBD86, 0x94C1, 0xBD87, 0xBAD1, 0xBD88, 0xBAD2, 0xBD89, 0xBAD3, 0xBD8A, 0xBAD4, 0xBD8B, 0x94C2, + 0xBD8C, 0x94C3, 0xBD8D, 0x94C4, 0xBD8E, 0x94C5, 0xBD8F, 0x94C6, 0xBD90, 0xBAD5, 0xBD91, 0xBAD6, 0xBD92, 0x94C7, 0xBD93, 0xBAD7, + 0xBD94, 0x94C8, 0xBD95, 0xBAD8, 0xBD96, 0x94C9, 0xBD97, 0x94CA, 0xBD98, 0x94CB, 0xBD99, 0xBAD9, 0xBD9A, 0xBADA, 0xBD9B, 0x94CC, + 0xBD9C, 0xBADB, 0xBD9D, 0x94CD, 0xBD9E, 0x94CE, 0xBD9F, 0x94CF, 0xBDA0, 0x94D0, 0xBDA1, 0x94D1, 0xBDA2, 0x94D2, 0xBDA3, 0x94D3, + 0xBDA4, 0xBADC, 0xBDA5, 0x94D4, 0xBDA6, 0x94D5, 0xBDA7, 0x94D6, 0xBDA8, 0x94D7, 0xBDA9, 0x94D8, 0xBDAA, 0x94D9, 0xBDAB, 0x94DA, + 0xBDAC, 0x94DB, 0xBDAD, 0x94DC, 0xBDAE, 0x94DD, 0xBDAF, 0x94DE, 0xBDB0, 0xBADD, 0xBDB1, 0x94DF, 0xBDB2, 0x94E0, 0xBDB3, 0x94E1, + 0xBDB4, 0x94E2, 0xBDB5, 0x94E3, 0xBDB6, 0x94E4, 0xBDB7, 0x94E5, 0xBDB8, 0xBADE, 0xBDB9, 0x94E6, 0xBDBA, 0x94E7, 0xBDBB, 0x94E8, + 0xBDBC, 0x94E9, 0xBDBD, 0x94EA, 0xBDBE, 0x94EB, 0xBDBF, 0x94EC, 0xBDC0, 0x94ED, 0xBDC1, 0x94EE, 0xBDC2, 0x94EF, 0xBDC3, 0x94F0, + 0xBDC4, 0x94F1, 0xBDC5, 0x94F2, 0xBDC6, 0x94F3, 0xBDC7, 0x94F4, 0xBDC8, 0x94F5, 0xBDC9, 0x94F6, 0xBDCA, 0x94F7, 0xBDCB, 0x94F8, + 0xBDCC, 0x94F9, 0xBDCD, 0x94FA, 0xBDCE, 0x94FB, 0xBDCF, 0x94FC, 0xBDD0, 0x94FD, 0xBDD1, 0x94FE, 0xBDD2, 0x9541, 0xBDD3, 0x9542, + 0xBDD4, 0xBADF, 0xBDD5, 0xBAE0, 0xBDD6, 0x9543, 0xBDD7, 0x9544, 0xBDD8, 0xBAE1, 0xBDD9, 0x9545, 0xBDDA, 0x9546, 0xBDDB, 0x9547, + 0xBDDC, 0xBAE2, 0xBDDD, 0x9548, 0xBDDE, 0x9549, 0xBDDF, 0x954A, 0xBDE0, 0x954B, 0xBDE1, 0x954C, 0xBDE2, 0x954D, 0xBDE3, 0x954E, + 0xBDE4, 0x954F, 0xBDE5, 0x9550, 0xBDE6, 0x9551, 0xBDE7, 0x9552, 0xBDE8, 0x9553, 0xBDE9, 0xBAE3, 0xBDEA, 0x9554, 0xBDEB, 0x9555, + 0xBDEC, 0x9556, 0xBDED, 0x9557, 0xBDEE, 0x9558, 0xBDEF, 0x9559, 0xBDF0, 0xBAE4, 0xBDF1, 0x955A, 0xBDF2, 0x9561, 0xBDF3, 0x9562, + 0xBDF4, 0xBAE5, 0xBDF5, 0x9563, 0xBDF6, 0x9564, 0xBDF7, 0x9565, 0xBDF8, 0xBAE6, 0xBDF9, 0x9566, 0xBDFA, 0x9567, 0xBDFB, 0x9568, + 0xBDFC, 0x9569, 0xBDFD, 0x956A, 0xBDFE, 0x956B, 0xBDFF, 0x956C, 0xBE00, 0xBAE7, 0xBE01, 0x956D, 0xBE02, 0x956E, 0xBE03, 0xBAE8, + 0xBE04, 0x956F, 0xBE05, 0xBAE9, 0xBE06, 0x9570, 0xBE07, 0x9571, 0xBE08, 0x9572, 0xBE09, 0x9573, 0xBE0A, 0x9574, 0xBE0B, 0x9575, + 0xBE0C, 0xBAEA, 0xBE0D, 0xBAEB, 0xBE0E, 0x9576, 0xBE0F, 0x9577, 0xBE10, 0xBAEC, 0xBE11, 0x9578, 0xBE12, 0x9579, 0xBE13, 0x957A, + 0xBE14, 0xBAED, 0xBE15, 0x9581, 0xBE16, 0x9582, 0xBE17, 0x9583, 0xBE18, 0x9584, 0xBE19, 0x9585, 0xBE1A, 0x9586, 0xBE1B, 0x9587, + 0xBE1C, 0xBAEE, 0xBE1D, 0xBAEF, 0xBE1E, 0x9588, 0xBE1F, 0xBAF0, 0xBE20, 0x9589, 0xBE21, 0x958A, 0xBE22, 0x958B, 0xBE23, 0x958C, + 0xBE24, 0x958D, 0xBE25, 0x958E, 0xBE26, 0x958F, 0xBE27, 0x9590, 0xBE28, 0x9591, 0xBE29, 0x9592, 0xBE2A, 0x9593, 0xBE2B, 0x9594, + 0xBE2C, 0x9595, 0xBE2D, 0x9596, 0xBE2E, 0x9597, 0xBE2F, 0x9598, 0xBE30, 0x9599, 0xBE31, 0x959A, 0xBE32, 0x959B, 0xBE33, 0x959C, + 0xBE34, 0x959D, 0xBE35, 0x959E, 0xBE36, 0x959F, 0xBE37, 0x95A0, 0xBE38, 0x95A1, 0xBE39, 0x95A2, 0xBE3A, 0x95A3, 0xBE3B, 0x95A4, + 0xBE3C, 0x95A5, 0xBE3D, 0x95A6, 0xBE3E, 0x95A7, 0xBE3F, 0x95A8, 0xBE40, 0x95A9, 0xBE41, 0x95AA, 0xBE42, 0x95AB, 0xBE43, 0x95AC, + 0xBE44, 0xBAF1, 0xBE45, 0xBAF2, 0xBE46, 0x95AD, 0xBE47, 0x95AE, 0xBE48, 0xBAF3, 0xBE49, 0x95AF, 0xBE4A, 0x95B0, 0xBE4B, 0x95B1, + 0xBE4C, 0xBAF4, 0xBE4D, 0x95B2, 0xBE4E, 0xBAF5, 0xBE4F, 0x95B3, 0xBE50, 0x95B4, 0xBE51, 0x95B5, 0xBE52, 0x95B6, 0xBE53, 0x95B7, + 0xBE54, 0xBAF6, 0xBE55, 0xBAF7, 0xBE56, 0x95B8, 0xBE57, 0xBAF8, 0xBE58, 0x95B9, 0xBE59, 0xBAF9, 0xBE5A, 0xBAFA, 0xBE5B, 0xBAFB, + 0xBE5C, 0x95BA, 0xBE5D, 0x95BB, 0xBE5E, 0x95BC, 0xBE5F, 0x95BD, 0xBE60, 0xBAFC, 0xBE61, 0xBAFD, 0xBE62, 0x95BE, 0xBE63, 0x95BF, + 0xBE64, 0xBAFE, 0xBE65, 0x95C0, 0xBE66, 0x95C1, 0xBE67, 0x95C2, 0xBE68, 0xBBA1, 0xBE69, 0x95C3, 0xBE6A, 0xBBA2, 0xBE6B, 0x95C4, + 0xBE6C, 0x95C5, 0xBE6D, 0x95C6, 0xBE6E, 0x95C7, 0xBE6F, 0x95C8, 0xBE70, 0xBBA3, 0xBE71, 0xBBA4, 0xBE72, 0x95C9, 0xBE73, 0xBBA5, + 0xBE74, 0xBBA6, 0xBE75, 0xBBA7, 0xBE76, 0x95CA, 0xBE77, 0x95CB, 0xBE78, 0x95CC, 0xBE79, 0x95CD, 0xBE7A, 0x95CE, 0xBE7B, 0xBBA8, + 0xBE7C, 0xBBA9, 0xBE7D, 0xBBAA, 0xBE7E, 0x95CF, 0xBE7F, 0x95D0, 0xBE80, 0xBBAB, 0xBE81, 0x95D1, 0xBE82, 0x95D2, 0xBE83, 0x95D3, + 0xBE84, 0xBBAC, 0xBE85, 0x95D4, 0xBE86, 0x95D5, 0xBE87, 0x95D6, 0xBE88, 0x95D7, 0xBE89, 0x95D8, 0xBE8A, 0x95D9, 0xBE8B, 0x95DA, + 0xBE8C, 0xBBAD, 0xBE8D, 0xBBAE, 0xBE8E, 0x95DB, 0xBE8F, 0xBBAF, 0xBE90, 0xBBB0, 0xBE91, 0xBBB1, 0xBE92, 0x95DC, 0xBE93, 0x95DD, + 0xBE94, 0x95DE, 0xBE95, 0x95DF, 0xBE96, 0x95E0, 0xBE97, 0x95E1, 0xBE98, 0xBBB2, 0xBE99, 0xBBB3, 0xBE9A, 0x95E2, 0xBE9B, 0x95E3, + 0xBE9C, 0x95E4, 0xBE9D, 0x95E5, 0xBE9E, 0x95E6, 0xBE9F, 0x95E7, 0xBEA0, 0x95E8, 0xBEA1, 0x95E9, 0xBEA2, 0x95EA, 0xBEA3, 0x95EB, + 0xBEA4, 0x95EC, 0xBEA5, 0x95ED, 0xBEA6, 0x95EE, 0xBEA7, 0x95EF, 0xBEA8, 0xBBB4, 0xBEA9, 0x95F0, 0xBEAA, 0x95F1, 0xBEAB, 0x95F2, + 0xBEAC, 0x95F3, 0xBEAD, 0x95F4, 0xBEAE, 0x95F5, 0xBEAF, 0x95F6, 0xBEB0, 0x95F7, 0xBEB1, 0x95F8, 0xBEB2, 0x95F9, 0xBEB3, 0x95FA, + 0xBEB4, 0x95FB, 0xBEB5, 0x95FC, 0xBEB6, 0x95FD, 0xBEB7, 0x95FE, 0xBEB8, 0x9641, 0xBEB9, 0x9642, 0xBEBA, 0x9643, 0xBEBB, 0x9644, + 0xBEBC, 0x9645, 0xBEBD, 0x9646, 0xBEBE, 0x9647, 0xBEBF, 0x9648, 0xBEC0, 0x9649, 0xBEC1, 0x964A, 0xBEC2, 0x964B, 0xBEC3, 0x964C, + 0xBEC4, 0x964D, 0xBEC5, 0x964E, 0xBEC6, 0x964F, 0xBEC7, 0x9650, 0xBEC8, 0x9651, 0xBEC9, 0x9652, 0xBECA, 0x9653, 0xBECB, 0x9654, + 0xBECC, 0x9655, 0xBECD, 0x9656, 0xBECE, 0x9657, 0xBECF, 0x9658, 0xBED0, 0xBBB5, 0xBED1, 0xBBB6, 0xBED2, 0x9659, 0xBED3, 0x965A, + 0xBED4, 0xBBB7, 0xBED5, 0x9661, 0xBED6, 0x9662, 0xBED7, 0xBBB8, 0xBED8, 0xBBB9, 0xBED9, 0x9663, 0xBEDA, 0x9664, 0xBEDB, 0x9665, + 0xBEDC, 0x9666, 0xBEDD, 0x9667, 0xBEDE, 0x9668, 0xBEDF, 0x9669, 0xBEE0, 0xBBBA, 0xBEE1, 0x966A, 0xBEE2, 0x966B, 0xBEE3, 0xBBBB, + 0xBEE4, 0xBBBC, 0xBEE5, 0xBBBD, 0xBEE6, 0x966C, 0xBEE7, 0x966D, 0xBEE8, 0x966E, 0xBEE9, 0x966F, 0xBEEA, 0x9670, 0xBEEB, 0x9671, + 0xBEEC, 0xBBBE, 0xBEED, 0x9672, 0xBEEE, 0x9673, 0xBEEF, 0x9674, 0xBEF0, 0x9675, 0xBEF1, 0x9676, 0xBEF2, 0x9677, 0xBEF3, 0x9678, + 0xBEF4, 0x9679, 0xBEF5, 0x967A, 0xBEF6, 0x9681, 0xBEF7, 0x9682, 0xBEF8, 0x9683, 0xBEF9, 0x9684, 0xBEFA, 0x9685, 0xBEFB, 0x9686, + 0xBEFC, 0x9687, 0xBEFD, 0x9688, 0xBEFE, 0x9689, 0xBEFF, 0x968A, 0xBF00, 0x968B, 0xBF01, 0xBBBF, 0xBF02, 0x968C, 0xBF03, 0x968D, + 0xBF04, 0x968E, 0xBF05, 0x968F, 0xBF06, 0x9690, 0xBF07, 0x9691, 0xBF08, 0xBBC0, 0xBF09, 0xBBC1, 0xBF0A, 0x9692, 0xBF0B, 0x9693, + 0xBF0C, 0x9694, 0xBF0D, 0x9695, 0xBF0E, 0x9696, 0xBF0F, 0x9697, 0xBF10, 0x9698, 0xBF11, 0x9699, 0xBF12, 0x969A, 0xBF13, 0x969B, + 0xBF14, 0x969C, 0xBF15, 0x969D, 0xBF16, 0x969E, 0xBF17, 0x969F, 0xBF18, 0xBBC2, 0xBF19, 0xBBC3, 0xBF1A, 0x96A0, 0xBF1B, 0xBBC4, + 0xBF1C, 0xBBC5, 0xBF1D, 0xBBC6, 0xBF1E, 0x96A1, 0xBF1F, 0x96A2, 0xBF20, 0x96A3, 0xBF21, 0x96A4, 0xBF22, 0x96A5, 0xBF23, 0x96A6, + 0xBF24, 0x96A7, 0xBF25, 0x96A8, 0xBF26, 0x96A9, 0xBF27, 0x96AA, 0xBF28, 0x96AB, 0xBF29, 0x96AC, 0xBF2A, 0x96AD, 0xBF2B, 0x96AE, + 0xBF2C, 0x96AF, 0xBF2D, 0x96B0, 0xBF2E, 0x96B1, 0xBF2F, 0x96B2, 0xBF30, 0x96B3, 0xBF31, 0x96B4, 0xBF32, 0x96B5, 0xBF33, 0x96B6, + 0xBF34, 0x96B7, 0xBF35, 0x96B8, 0xBF36, 0x96B9, 0xBF37, 0x96BA, 0xBF38, 0x96BB, 0xBF39, 0x96BC, 0xBF3A, 0x96BD, 0xBF3B, 0x96BE, + 0xBF3C, 0x96BF, 0xBF3D, 0x96C0, 0xBF3E, 0x96C1, 0xBF3F, 0x96C2, 0xBF40, 0xBBC7, 0xBF41, 0xBBC8, 0xBF42, 0x96C3, 0xBF43, 0x96C4, + 0xBF44, 0xBBC9, 0xBF45, 0x96C5, 0xBF46, 0x96C6, 0xBF47, 0x96C7, 0xBF48, 0xBBCA, 0xBF49, 0x96C8, 0xBF4A, 0x96C9, 0xBF4B, 0x96CA, + 0xBF4C, 0x96CB, 0xBF4D, 0x96CC, 0xBF4E, 0x96CD, 0xBF4F, 0x96CE, 0xBF50, 0xBBCB, 0xBF51, 0xBBCC, 0xBF52, 0x96CF, 0xBF53, 0x96D0, + 0xBF54, 0x96D1, 0xBF55, 0xBBCD, 0xBF56, 0x96D2, 0xBF57, 0x96D3, 0xBF58, 0x96D4, 0xBF59, 0x96D5, 0xBF5A, 0x96D6, 0xBF5B, 0x96D7, + 0xBF5C, 0x96D8, 0xBF5D, 0x96D9, 0xBF5E, 0x96DA, 0xBF5F, 0x96DB, 0xBF60, 0x96DC, 0xBF61, 0x96DD, 0xBF62, 0x96DE, 0xBF63, 0x96DF, + 0xBF64, 0x96E0, 0xBF65, 0x96E1, 0xBF66, 0x96E2, 0xBF67, 0x96E3, 0xBF68, 0x96E4, 0xBF69, 0x96E5, 0xBF6A, 0x96E6, 0xBF6B, 0x96E7, + 0xBF6C, 0x96E8, 0xBF6D, 0x96E9, 0xBF6E, 0x96EA, 0xBF6F, 0x96EB, 0xBF70, 0x96EC, 0xBF71, 0x96ED, 0xBF72, 0x96EE, 0xBF73, 0x96EF, + 0xBF74, 0x96F0, 0xBF75, 0x96F1, 0xBF76, 0x96F2, 0xBF77, 0x96F3, 0xBF78, 0x96F4, 0xBF79, 0x96F5, 0xBF7A, 0x96F6, 0xBF7B, 0x96F7, + 0xBF7C, 0x96F8, 0xBF7D, 0x96F9, 0xBF7E, 0x96FA, 0xBF7F, 0x96FB, 0xBF80, 0x96FC, 0xBF81, 0x96FD, 0xBF82, 0x96FE, 0xBF83, 0x9741, + 0xBF84, 0x9742, 0xBF85, 0x9743, 0xBF86, 0x9744, 0xBF87, 0x9745, 0xBF88, 0x9746, 0xBF89, 0x9747, 0xBF8A, 0x9748, 0xBF8B, 0x9749, + 0xBF8C, 0x974A, 0xBF8D, 0x974B, 0xBF8E, 0x974C, 0xBF8F, 0x974D, 0xBF90, 0x974E, 0xBF91, 0x974F, 0xBF92, 0x9750, 0xBF93, 0x9751, + 0xBF94, 0xBBCE, 0xBF95, 0x9752, 0xBF96, 0x9753, 0xBF97, 0x9754, 0xBF98, 0x9755, 0xBF99, 0x9756, 0xBF9A, 0x9757, 0xBF9B, 0x9758, + 0xBF9C, 0x9759, 0xBF9D, 0x975A, 0xBF9E, 0x9761, 0xBF9F, 0x9762, 0xBFA0, 0x9763, 0xBFA1, 0x9764, 0xBFA2, 0x9765, 0xBFA3, 0x9766, + 0xBFA4, 0x9767, 0xBFA5, 0x9768, 0xBFA6, 0x9769, 0xBFA7, 0x976A, 0xBFA8, 0x976B, 0xBFA9, 0x976C, 0xBFAA, 0x976D, 0xBFAB, 0x976E, + 0xBFAC, 0x976F, 0xBFAD, 0x9770, 0xBFAE, 0x9771, 0xBFAF, 0x9772, 0xBFB0, 0xBBCF, 0xBFB1, 0x9773, 0xBFB2, 0x9774, 0xBFB3, 0x9775, + 0xBFB4, 0x9776, 0xBFB5, 0x9777, 0xBFB6, 0x9778, 0xBFB7, 0x9779, 0xBFB8, 0x977A, 0xBFB9, 0x9781, 0xBFBA, 0x9782, 0xBFBB, 0x9783, + 0xBFBC, 0x9784, 0xBFBD, 0x9785, 0xBFBE, 0x9786, 0xBFBF, 0x9787, 0xBFC0, 0x9788, 0xBFC1, 0x9789, 0xBFC2, 0x978A, 0xBFC3, 0x978B, + 0xBFC4, 0x978C, 0xBFC5, 0xBBD0, 0xBFC6, 0x978D, 0xBFC7, 0x978E, 0xBFC8, 0x978F, 0xBFC9, 0x9790, 0xBFCA, 0x9791, 0xBFCB, 0x9792, + 0xBFCC, 0xBBD1, 0xBFCD, 0xBBD2, 0xBFCE, 0x9793, 0xBFCF, 0x9794, 0xBFD0, 0xBBD3, 0xBFD1, 0x9795, 0xBFD2, 0x9796, 0xBFD3, 0x9797, + 0xBFD4, 0xBBD4, 0xBFD5, 0x9798, 0xBFD6, 0x9799, 0xBFD7, 0x979A, 0xBFD8, 0x979B, 0xBFD9, 0x979C, 0xBFDA, 0x979D, 0xBFDB, 0x979E, + 0xBFDC, 0xBBD5, 0xBFDD, 0x979F, 0xBFDE, 0x97A0, 0xBFDF, 0xBBD6, 0xBFE0, 0x97A1, 0xBFE1, 0xBBD7, 0xBFE2, 0x97A2, 0xBFE3, 0x97A3, + 0xBFE4, 0x97A4, 0xBFE5, 0x97A5, 0xBFE6, 0x97A6, 0xBFE7, 0x97A7, 0xBFE8, 0x97A8, 0xBFE9, 0x97A9, 0xBFEA, 0x97AA, 0xBFEB, 0x97AB, + 0xBFEC, 0x97AC, 0xBFED, 0x97AD, 0xBFEE, 0x97AE, 0xBFEF, 0x97AF, 0xBFF0, 0x97B0, 0xBFF1, 0x97B1, 0xBFF2, 0x97B2, 0xBFF3, 0x97B3, + 0xBFF4, 0x97B4, 0xBFF5, 0x97B5, 0xBFF6, 0x97B6, 0xBFF7, 0x97B7, 0xBFF8, 0x97B8, 0xBFF9, 0x97B9, 0xBFFA, 0x97BA, 0xBFFB, 0x97BB, + 0xBFFC, 0x97BC, 0xBFFD, 0x97BD, 0xBFFE, 0x97BE, 0xBFFF, 0x97BF, 0xC000, 0x97C0, 0xC001, 0x97C1, 0xC002, 0x97C2, 0xC003, 0x97C3, + 0xC004, 0x97C4, 0xC005, 0x97C5, 0xC006, 0x97C6, 0xC007, 0x97C7, 0xC008, 0x97C8, 0xC009, 0x97C9, 0xC00A, 0x97CA, 0xC00B, 0x97CB, + 0xC00C, 0x97CC, 0xC00D, 0x97CD, 0xC00E, 0x97CE, 0xC00F, 0x97CF, 0xC010, 0x97D0, 0xC011, 0x97D1, 0xC012, 0x97D2, 0xC013, 0x97D3, + 0xC014, 0x97D4, 0xC015, 0x97D5, 0xC016, 0x97D6, 0xC017, 0x97D7, 0xC018, 0x97D8, 0xC019, 0x97D9, 0xC01A, 0x97DA, 0xC01B, 0x97DB, + 0xC01C, 0x97DC, 0xC01D, 0x97DD, 0xC01E, 0x97DE, 0xC01F, 0x97DF, 0xC020, 0x97E0, 0xC021, 0x97E1, 0xC022, 0x97E2, 0xC023, 0x97E3, + 0xC024, 0x97E4, 0xC025, 0x97E5, 0xC026, 0x97E6, 0xC027, 0x97E7, 0xC028, 0x97E8, 0xC029, 0x97E9, 0xC02A, 0x97EA, 0xC02B, 0x97EB, + 0xC02C, 0x97EC, 0xC02D, 0x97ED, 0xC02E, 0x97EE, 0xC02F, 0x97EF, 0xC030, 0x97F0, 0xC031, 0x97F1, 0xC032, 0x97F2, 0xC033, 0x97F3, + 0xC034, 0x97F4, 0xC035, 0x97F5, 0xC036, 0x97F6, 0xC037, 0x97F7, 0xC038, 0x97F8, 0xC039, 0x97F9, 0xC03A, 0x97FA, 0xC03B, 0x97FB, + 0xC03C, 0xBBD8, 0xC03D, 0x97FC, 0xC03E, 0x97FD, 0xC03F, 0x97FE, 0xC040, 0x9841, 0xC041, 0x9842, 0xC042, 0x9843, 0xC043, 0x9844, + 0xC044, 0x9845, 0xC045, 0x9846, 0xC046, 0x9847, 0xC047, 0x9848, 0xC048, 0x9849, 0xC049, 0x984A, 0xC04A, 0x984B, 0xC04B, 0x984C, + 0xC04C, 0x984D, 0xC04D, 0x984E, 0xC04E, 0x984F, 0xC04F, 0x9850, 0xC050, 0x9851, 0xC051, 0xBBD9, 0xC052, 0x9852, 0xC053, 0x9853, + 0xC054, 0x9854, 0xC055, 0x9855, 0xC056, 0x9856, 0xC057, 0x9857, 0xC058, 0xBBDA, 0xC059, 0x9858, 0xC05A, 0x9859, 0xC05B, 0x985A, + 0xC05C, 0xBBDB, 0xC05D, 0x9861, 0xC05E, 0x9862, 0xC05F, 0x9863, 0xC060, 0xBBDC, 0xC061, 0x9864, 0xC062, 0x9865, 0xC063, 0x9866, + 0xC064, 0x9867, 0xC065, 0x9868, 0xC066, 0x9869, 0xC067, 0x986A, 0xC068, 0xBBDD, 0xC069, 0xBBDE, 0xC06A, 0x986B, 0xC06B, 0x986C, + 0xC06C, 0x986D, 0xC06D, 0x986E, 0xC06E, 0x986F, 0xC06F, 0x9870, 0xC070, 0x9871, 0xC071, 0x9872, 0xC072, 0x9873, 0xC073, 0x9874, + 0xC074, 0x9875, 0xC075, 0x9876, 0xC076, 0x9877, 0xC077, 0x9878, 0xC078, 0x9879, 0xC079, 0x987A, 0xC07A, 0x9881, 0xC07B, 0x9882, + 0xC07C, 0x9883, 0xC07D, 0x9884, 0xC07E, 0x9885, 0xC07F, 0x9886, 0xC080, 0x9887, 0xC081, 0x9888, 0xC082, 0x9889, 0xC083, 0x988A, + 0xC084, 0x988B, 0xC085, 0x988C, 0xC086, 0x988D, 0xC087, 0x988E, 0xC088, 0x988F, 0xC089, 0x9890, 0xC08A, 0x9891, 0xC08B, 0x9892, + 0xC08C, 0x9893, 0xC08D, 0x9894, 0xC08E, 0x9895, 0xC08F, 0x9896, 0xC090, 0xBBDF, 0xC091, 0xBBE0, 0xC092, 0x9897, 0xC093, 0x9898, + 0xC094, 0xBBE1, 0xC095, 0x9899, 0xC096, 0x989A, 0xC097, 0x989B, 0xC098, 0xBBE2, 0xC099, 0x989C, 0xC09A, 0x989D, 0xC09B, 0x989E, + 0xC09C, 0x989F, 0xC09D, 0x98A0, 0xC09E, 0x98A1, 0xC09F, 0x98A2, 0xC0A0, 0xBBE3, 0xC0A1, 0xBBE4, 0xC0A2, 0x98A3, 0xC0A3, 0xBBE5, + 0xC0A4, 0x98A4, 0xC0A5, 0xBBE6, 0xC0A6, 0x98A5, 0xC0A7, 0x98A6, 0xC0A8, 0x98A7, 0xC0A9, 0x98A8, 0xC0AA, 0x98A9, 0xC0AB, 0x98AA, + 0xC0AC, 0xBBE7, 0xC0AD, 0xBBE8, 0xC0AE, 0x98AB, 0xC0AF, 0xBBE9, 0xC0B0, 0xBBEA, 0xC0B1, 0x98AC, 0xC0B2, 0x98AD, 0xC0B3, 0xBBEB, + 0xC0B4, 0xBBEC, 0xC0B5, 0xBBED, 0xC0B6, 0xBBEE, 0xC0B7, 0x98AE, 0xC0B8, 0x98AF, 0xC0B9, 0x98B0, 0xC0BA, 0x98B1, 0xC0BB, 0x98B2, + 0xC0BC, 0xBBEF, 0xC0BD, 0xBBF0, 0xC0BE, 0x98B3, 0xC0BF, 0xBBF1, 0xC0C0, 0xBBF2, 0xC0C1, 0xBBF3, 0xC0C2, 0x98B4, 0xC0C3, 0x98B5, + 0xC0C4, 0x98B6, 0xC0C5, 0xBBF4, 0xC0C6, 0x98B7, 0xC0C7, 0x98B8, 0xC0C8, 0xBBF5, 0xC0C9, 0xBBF6, 0xC0CA, 0x98B9, 0xC0CB, 0x98BA, + 0xC0CC, 0xBBF7, 0xC0CD, 0x98BB, 0xC0CE, 0x98BC, 0xC0CF, 0x98BD, 0xC0D0, 0xBBF8, 0xC0D1, 0x98BE, 0xC0D2, 0x98BF, 0xC0D3, 0x98C0, + 0xC0D4, 0x98C1, 0xC0D5, 0x98C2, 0xC0D6, 0x98C3, 0xC0D7, 0x98C4, 0xC0D8, 0xBBF9, 0xC0D9, 0xBBFA, 0xC0DA, 0x98C5, 0xC0DB, 0xBBFB, + 0xC0DC, 0xBBFC, 0xC0DD, 0xBBFD, 0xC0DE, 0x98C6, 0xC0DF, 0x98C7, 0xC0E0, 0x98C8, 0xC0E1, 0x98C9, 0xC0E2, 0x98CA, 0xC0E3, 0x98CB, + 0xC0E4, 0xBBFE, 0xC0E5, 0xBCA1, 0xC0E6, 0x98CC, 0xC0E7, 0x98CD, 0xC0E8, 0xBCA2, 0xC0E9, 0x98CE, 0xC0EA, 0x98CF, 0xC0EB, 0x98D0, + 0xC0EC, 0xBCA3, 0xC0ED, 0x98D1, 0xC0EE, 0x98D2, 0xC0EF, 0x98D3, 0xC0F0, 0x98D4, 0xC0F1, 0x98D5, 0xC0F2, 0x98D6, 0xC0F3, 0x98D7, + 0xC0F4, 0xBCA4, 0xC0F5, 0xBCA5, 0xC0F6, 0x98D8, 0xC0F7, 0xBCA6, 0xC0F8, 0x98D9, 0xC0F9, 0xBCA7, 0xC0FA, 0x98DA, 0xC0FB, 0x98DB, + 0xC0FC, 0x98DC, 0xC0FD, 0x98DD, 0xC0FE, 0x98DE, 0xC0FF, 0x98DF, 0xC100, 0xBCA8, 0xC101, 0x98E0, 0xC102, 0x98E1, 0xC103, 0x98E2, + 0xC104, 0xBCA9, 0xC105, 0x98E3, 0xC106, 0x98E4, 0xC107, 0x98E5, 0xC108, 0xBCAA, 0xC109, 0x98E6, 0xC10A, 0x98E7, 0xC10B, 0x98E8, + 0xC10C, 0x98E9, 0xC10D, 0x98EA, 0xC10E, 0x98EB, 0xC10F, 0x98EC, 0xC110, 0xBCAB, 0xC111, 0x98ED, 0xC112, 0x98EE, 0xC113, 0x98EF, + 0xC114, 0x98F0, 0xC115, 0xBCAC, 0xC116, 0x98F1, 0xC117, 0x98F2, 0xC118, 0x98F3, 0xC119, 0x98F4, 0xC11A, 0x98F5, 0xC11B, 0x98F6, + 0xC11C, 0xBCAD, 0xC11D, 0xBCAE, 0xC11E, 0xBCAF, 0xC11F, 0xBCB0, 0xC120, 0xBCB1, 0xC121, 0x98F7, 0xC122, 0x98F8, 0xC123, 0xBCB2, + 0xC124, 0xBCB3, 0xC125, 0x98F9, 0xC126, 0xBCB4, 0xC127, 0xBCB5, 0xC128, 0x98FA, 0xC129, 0x98FB, 0xC12A, 0x98FC, 0xC12B, 0x98FD, + 0xC12C, 0xBCB6, 0xC12D, 0xBCB7, 0xC12E, 0x98FE, 0xC12F, 0xBCB8, 0xC130, 0xBCB9, 0xC131, 0xBCBA, 0xC132, 0x9941, 0xC133, 0x9942, + 0xC134, 0x9943, 0xC135, 0x9944, 0xC136, 0xBCBB, 0xC137, 0x9945, 0xC138, 0xBCBC, 0xC139, 0xBCBD, 0xC13A, 0x9946, 0xC13B, 0x9947, + 0xC13C, 0xBCBE, 0xC13D, 0x9948, 0xC13E, 0x9949, 0xC13F, 0x994A, 0xC140, 0xBCBF, 0xC141, 0x994B, 0xC142, 0x994C, 0xC143, 0x994D, + 0xC144, 0x994E, 0xC145, 0x994F, 0xC146, 0x9950, 0xC147, 0x9951, 0xC148, 0xBCC0, 0xC149, 0xBCC1, 0xC14A, 0x9952, 0xC14B, 0xBCC2, + 0xC14C, 0xBCC3, 0xC14D, 0xBCC4, 0xC14E, 0x9953, 0xC14F, 0x9954, 0xC150, 0x9955, 0xC151, 0x9956, 0xC152, 0x9957, 0xC153, 0x9958, + 0xC154, 0xBCC5, 0xC155, 0xBCC6, 0xC156, 0x9959, 0xC157, 0x995A, 0xC158, 0xBCC7, 0xC159, 0x9961, 0xC15A, 0x9962, 0xC15B, 0x9963, + 0xC15C, 0xBCC8, 0xC15D, 0x9964, 0xC15E, 0x9965, 0xC15F, 0x9966, 0xC160, 0x9967, 0xC161, 0x9968, 0xC162, 0x9969, 0xC163, 0x996A, + 0xC164, 0xBCC9, 0xC165, 0xBCCA, 0xC166, 0x996B, 0xC167, 0xBCCB, 0xC168, 0xBCCC, 0xC169, 0xBCCD, 0xC16A, 0x996C, 0xC16B, 0x996D, + 0xC16C, 0x996E, 0xC16D, 0x996F, 0xC16E, 0x9970, 0xC16F, 0x9971, 0xC170, 0xBCCE, 0xC171, 0x9972, 0xC172, 0x9973, 0xC173, 0x9974, + 0xC174, 0xBCCF, 0xC175, 0x9975, 0xC176, 0x9976, 0xC177, 0x9977, 0xC178, 0xBCD0, 0xC179, 0x9978, 0xC17A, 0x9979, 0xC17B, 0x997A, + 0xC17C, 0x9981, 0xC17D, 0x9982, 0xC17E, 0x9983, 0xC17F, 0x9984, 0xC180, 0x9985, 0xC181, 0x9986, 0xC182, 0x9987, 0xC183, 0x9988, + 0xC184, 0x9989, 0xC185, 0xBCD1, 0xC186, 0x998A, 0xC187, 0x998B, 0xC188, 0x998C, 0xC189, 0x998D, 0xC18A, 0x998E, 0xC18B, 0x998F, + 0xC18C, 0xBCD2, 0xC18D, 0xBCD3, 0xC18E, 0xBCD4, 0xC18F, 0x9990, 0xC190, 0xBCD5, 0xC191, 0x9991, 0xC192, 0x9992, 0xC193, 0x9993, + 0xC194, 0xBCD6, 0xC195, 0x9994, 0xC196, 0xBCD7, 0xC197, 0x9995, 0xC198, 0x9996, 0xC199, 0x9997, 0xC19A, 0x9998, 0xC19B, 0x9999, + 0xC19C, 0xBCD8, 0xC19D, 0xBCD9, 0xC19E, 0x999A, 0xC19F, 0xBCDA, 0xC1A0, 0x999B, 0xC1A1, 0xBCDB, 0xC1A2, 0x999C, 0xC1A3, 0x999D, + 0xC1A4, 0x999E, 0xC1A5, 0xBCDC, 0xC1A6, 0x999F, 0xC1A7, 0x99A0, 0xC1A8, 0xBCDD, 0xC1A9, 0xBCDE, 0xC1AA, 0x99A1, 0xC1AB, 0x99A2, + 0xC1AC, 0xBCDF, 0xC1AD, 0x99A3, 0xC1AE, 0x99A4, 0xC1AF, 0x99A5, 0xC1B0, 0xBCE0, 0xC1B1, 0x99A6, 0xC1B2, 0x99A7, 0xC1B3, 0x99A8, + 0xC1B4, 0x99A9, 0xC1B5, 0x99AA, 0xC1B6, 0x99AB, 0xC1B7, 0x99AC, 0xC1B8, 0x99AD, 0xC1B9, 0x99AE, 0xC1BA, 0x99AF, 0xC1BB, 0x99B0, + 0xC1BC, 0x99B1, 0xC1BD, 0xBCE1, 0xC1BE, 0x99B2, 0xC1BF, 0x99B3, 0xC1C0, 0x99B4, 0xC1C1, 0x99B5, 0xC1C2, 0x99B6, 0xC1C3, 0x99B7, + 0xC1C4, 0xBCE2, 0xC1C5, 0x99B8, 0xC1C6, 0x99B9, 0xC1C7, 0x99BA, 0xC1C8, 0xBCE3, 0xC1C9, 0x99BB, 0xC1CA, 0x99BC, 0xC1CB, 0x99BD, + 0xC1CC, 0xBCE4, 0xC1CD, 0x99BE, 0xC1CE, 0x99BF, 0xC1CF, 0x99C0, 0xC1D0, 0x99C1, 0xC1D1, 0x99C2, 0xC1D2, 0x99C3, 0xC1D3, 0x99C4, + 0xC1D4, 0xBCE5, 0xC1D5, 0x99C5, 0xC1D6, 0x99C6, 0xC1D7, 0xBCE6, 0xC1D8, 0xBCE7, 0xC1D9, 0x99C7, 0xC1DA, 0x99C8, 0xC1DB, 0x99C9, + 0xC1DC, 0x99CA, 0xC1DD, 0x99CB, 0xC1DE, 0x99CC, 0xC1DF, 0x99CD, 0xC1E0, 0xBCE8, 0xC1E1, 0x99CE, 0xC1E2, 0x99CF, 0xC1E3, 0x99D0, + 0xC1E4, 0xBCE9, 0xC1E5, 0x99D1, 0xC1E6, 0x99D2, 0xC1E7, 0x99D3, 0xC1E8, 0xBCEA, 0xC1E9, 0x99D4, 0xC1EA, 0x99D5, 0xC1EB, 0x99D6, + 0xC1EC, 0x99D7, 0xC1ED, 0x99D8, 0xC1EE, 0x99D9, 0xC1EF, 0x99DA, 0xC1F0, 0xBCEB, 0xC1F1, 0xBCEC, 0xC1F2, 0x99DB, 0xC1F3, 0xBCED, + 0xC1F4, 0x99DC, 0xC1F5, 0x99DD, 0xC1F6, 0x99DE, 0xC1F7, 0x99DF, 0xC1F8, 0x99E0, 0xC1F9, 0x99E1, 0xC1FA, 0x99E2, 0xC1FB, 0x99E3, + 0xC1FC, 0xBCEE, 0xC1FD, 0xBCEF, 0xC1FE, 0x99E4, 0xC1FF, 0x99E5, 0xC200, 0xBCF0, 0xC201, 0x99E6, 0xC202, 0x99E7, 0xC203, 0x99E8, + 0xC204, 0xBCF1, 0xC205, 0x99E9, 0xC206, 0x99EA, 0xC207, 0x99EB, 0xC208, 0x99EC, 0xC209, 0x99ED, 0xC20A, 0x99EE, 0xC20B, 0x99EF, + 0xC20C, 0xBCF2, 0xC20D, 0xBCF3, 0xC20E, 0x99F0, 0xC20F, 0xBCF4, 0xC210, 0x99F1, 0xC211, 0xBCF5, 0xC212, 0x99F2, 0xC213, 0x99F3, + 0xC214, 0x99F4, 0xC215, 0x99F5, 0xC216, 0x99F6, 0xC217, 0x99F7, 0xC218, 0xBCF6, 0xC219, 0xBCF7, 0xC21A, 0x99F8, 0xC21B, 0x99F9, + 0xC21C, 0xBCF8, 0xC21D, 0x99FA, 0xC21E, 0x99FB, 0xC21F, 0xBCF9, 0xC220, 0xBCFA, 0xC221, 0x99FC, 0xC222, 0x99FD, 0xC223, 0x99FE, + 0xC224, 0x9A41, 0xC225, 0x9A42, 0xC226, 0x9A43, 0xC227, 0x9A44, 0xC228, 0xBCFB, 0xC229, 0xBCFC, 0xC22A, 0x9A45, 0xC22B, 0xBCFD, + 0xC22C, 0x9A46, 0xC22D, 0xBCFE, 0xC22E, 0x9A47, 0xC22F, 0xBDA1, 0xC230, 0x9A48, 0xC231, 0xBDA2, 0xC232, 0xBDA3, 0xC233, 0x9A49, + 0xC234, 0xBDA4, 0xC235, 0x9A4A, 0xC236, 0x9A4B, 0xC237, 0x9A4C, 0xC238, 0x9A4D, 0xC239, 0x9A4E, 0xC23A, 0x9A4F, 0xC23B, 0x9A50, + 0xC23C, 0x9A51, 0xC23D, 0x9A52, 0xC23E, 0x9A53, 0xC23F, 0x9A54, 0xC240, 0x9A55, 0xC241, 0x9A56, 0xC242, 0x9A57, 0xC243, 0x9A58, + 0xC244, 0x9A59, 0xC245, 0x9A5A, 0xC246, 0x9A61, 0xC247, 0x9A62, 0xC248, 0xBDA5, 0xC249, 0x9A63, 0xC24A, 0x9A64, 0xC24B, 0x9A65, + 0xC24C, 0x9A66, 0xC24D, 0x9A67, 0xC24E, 0x9A68, 0xC24F, 0x9A69, 0xC250, 0xBDA6, 0xC251, 0xBDA7, 0xC252, 0x9A6A, 0xC253, 0x9A6B, + 0xC254, 0xBDA8, 0xC255, 0x9A6C, 0xC256, 0x9A6D, 0xC257, 0x9A6E, 0xC258, 0xBDA9, 0xC259, 0x9A6F, 0xC25A, 0x9A70, 0xC25B, 0x9A71, + 0xC25C, 0x9A72, 0xC25D, 0x9A73, 0xC25E, 0x9A74, 0xC25F, 0x9A75, 0xC260, 0xBDAA, 0xC261, 0x9A76, 0xC262, 0x9A77, 0xC263, 0x9A78, + 0xC264, 0x9A79, 0xC265, 0xBDAB, 0xC266, 0x9A7A, 0xC267, 0x9A81, 0xC268, 0x9A82, 0xC269, 0x9A83, 0xC26A, 0x9A84, 0xC26B, 0x9A85, + 0xC26C, 0xBDAC, 0xC26D, 0xBDAD, 0xC26E, 0x9A86, 0xC26F, 0x9A87, 0xC270, 0xBDAE, 0xC271, 0x9A88, 0xC272, 0x9A89, 0xC273, 0x9A8A, + 0xC274, 0xBDAF, 0xC275, 0x9A8B, 0xC276, 0x9A8C, 0xC277, 0x9A8D, 0xC278, 0x9A8E, 0xC279, 0x9A8F, 0xC27A, 0x9A90, 0xC27B, 0x9A91, + 0xC27C, 0xBDB0, 0xC27D, 0xBDB1, 0xC27E, 0x9A92, 0xC27F, 0xBDB2, 0xC280, 0x9A93, 0xC281, 0xBDB3, 0xC282, 0x9A94, 0xC283, 0x9A95, + 0xC284, 0x9A96, 0xC285, 0x9A97, 0xC286, 0x9A98, 0xC287, 0x9A99, 0xC288, 0xBDB4, 0xC289, 0xBDB5, 0xC28A, 0x9A9A, 0xC28B, 0x9A9B, + 0xC28C, 0x9A9C, 0xC28D, 0x9A9D, 0xC28E, 0x9A9E, 0xC28F, 0x9A9F, 0xC290, 0xBDB6, 0xC291, 0x9AA0, 0xC292, 0x9AA1, 0xC293, 0x9AA2, + 0xC294, 0x9AA3, 0xC295, 0x9AA4, 0xC296, 0x9AA5, 0xC297, 0x9AA6, 0xC298, 0xBDB7, 0xC299, 0x9AA7, 0xC29A, 0x9AA8, 0xC29B, 0xBDB8, + 0xC29C, 0x9AA9, 0xC29D, 0xBDB9, 0xC29E, 0x9AAA, 0xC29F, 0x9AAB, 0xC2A0, 0x9AAC, 0xC2A1, 0x9AAD, 0xC2A2, 0x9AAE, 0xC2A3, 0x9AAF, + 0xC2A4, 0xBDBA, 0xC2A5, 0xBDBB, 0xC2A6, 0x9AB0, 0xC2A7, 0x9AB1, 0xC2A8, 0xBDBC, 0xC2A9, 0x9AB2, 0xC2AA, 0x9AB3, 0xC2AB, 0x9AB4, + 0xC2AC, 0xBDBD, 0xC2AD, 0xBDBE, 0xC2AE, 0x9AB5, 0xC2AF, 0x9AB6, 0xC2B0, 0x9AB7, 0xC2B1, 0x9AB8, 0xC2B2, 0x9AB9, 0xC2B3, 0x9ABA, + 0xC2B4, 0xBDBF, 0xC2B5, 0xBDC0, 0xC2B6, 0x9ABB, 0xC2B7, 0xBDC1, 0xC2B8, 0x9ABC, 0xC2B9, 0xBDC2, 0xC2BA, 0x9ABD, 0xC2BB, 0x9ABE, + 0xC2BC, 0x9ABF, 0xC2BD, 0x9AC0, 0xC2BE, 0x9AC1, 0xC2BF, 0x9AC2, 0xC2C0, 0x9AC3, 0xC2C1, 0x9AC4, 0xC2C2, 0x9AC5, 0xC2C3, 0x9AC6, + 0xC2C4, 0x9AC7, 0xC2C5, 0x9AC8, 0xC2C6, 0x9AC9, 0xC2C7, 0x9ACA, 0xC2C8, 0x9ACB, 0xC2C9, 0x9ACC, 0xC2CA, 0x9ACD, 0xC2CB, 0x9ACE, + 0xC2CC, 0x9ACF, 0xC2CD, 0x9AD0, 0xC2CE, 0x9AD1, 0xC2CF, 0x9AD2, 0xC2D0, 0x9AD3, 0xC2D1, 0x9AD4, 0xC2D2, 0x9AD5, 0xC2D3, 0x9AD6, + 0xC2D4, 0x9AD7, 0xC2D5, 0x9AD8, 0xC2D6, 0x9AD9, 0xC2D7, 0x9ADA, 0xC2D8, 0x9ADB, 0xC2D9, 0x9ADC, 0xC2DA, 0x9ADD, 0xC2DB, 0x9ADE, + 0xC2DC, 0xBDC3, 0xC2DD, 0xBDC4, 0xC2DE, 0x9ADF, 0xC2DF, 0x9AE0, 0xC2E0, 0xBDC5, 0xC2E1, 0x9AE1, 0xC2E2, 0x9AE2, 0xC2E3, 0xBDC6, + 0xC2E4, 0xBDC7, 0xC2E5, 0x9AE3, 0xC2E6, 0x9AE4, 0xC2E7, 0x9AE5, 0xC2E8, 0x9AE6, 0xC2E9, 0x9AE7, 0xC2EA, 0x9AE8, 0xC2EB, 0xBDC8, + 0xC2EC, 0xBDC9, 0xC2ED, 0xBDCA, 0xC2EE, 0x9AE9, 0xC2EF, 0xBDCB, 0xC2F0, 0x9AEA, 0xC2F1, 0xBDCC, 0xC2F2, 0x9AEB, 0xC2F3, 0x9AEC, + 0xC2F4, 0x9AED, 0xC2F5, 0x9AEE, 0xC2F6, 0xBDCD, 0xC2F7, 0x9AEF, 0xC2F8, 0xBDCE, 0xC2F9, 0xBDCF, 0xC2FA, 0x9AF0, 0xC2FB, 0xBDD0, + 0xC2FC, 0xBDD1, 0xC2FD, 0x9AF1, 0xC2FE, 0x9AF2, 0xC2FF, 0x9AF3, 0xC300, 0xBDD2, 0xC301, 0x9AF4, 0xC302, 0x9AF5, 0xC303, 0x9AF6, + 0xC304, 0x9AF7, 0xC305, 0x9AF8, 0xC306, 0x9AF9, 0xC307, 0x9AFA, 0xC308, 0xBDD3, 0xC309, 0xBDD4, 0xC30A, 0x9AFB, 0xC30B, 0x9AFC, + 0xC30C, 0xBDD5, 0xC30D, 0xBDD6, 0xC30E, 0x9AFD, 0xC30F, 0x9AFE, 0xC310, 0x9B41, 0xC311, 0x9B42, 0xC312, 0x9B43, 0xC313, 0xBDD7, + 0xC314, 0xBDD8, 0xC315, 0xBDD9, 0xC316, 0x9B44, 0xC317, 0x9B45, 0xC318, 0xBDDA, 0xC319, 0x9B46, 0xC31A, 0x9B47, 0xC31B, 0x9B48, + 0xC31C, 0xBDDB, 0xC31D, 0x9B49, 0xC31E, 0x9B4A, 0xC31F, 0x9B4B, 0xC320, 0x9B4C, 0xC321, 0x9B4D, 0xC322, 0x9B4E, 0xC323, 0x9B4F, + 0xC324, 0xBDDC, 0xC325, 0xBDDD, 0xC326, 0x9B50, 0xC327, 0x9B51, 0xC328, 0xBDDE, 0xC329, 0xBDDF, 0xC32A, 0x9B52, 0xC32B, 0x9B53, + 0xC32C, 0x9B54, 0xC32D, 0x9B55, 0xC32E, 0x9B56, 0xC32F, 0x9B57, 0xC330, 0x9B58, 0xC331, 0x9B59, 0xC332, 0x9B5A, 0xC333, 0x9B61, + 0xC334, 0x9B62, 0xC335, 0x9B63, 0xC336, 0x9B64, 0xC337, 0x9B65, 0xC338, 0x9B66, 0xC339, 0x9B67, 0xC33A, 0x9B68, 0xC33B, 0x9B69, + 0xC33C, 0x9B6A, 0xC33D, 0x9B6B, 0xC33E, 0x9B6C, 0xC33F, 0x9B6D, 0xC340, 0x9B6E, 0xC341, 0x9B6F, 0xC342, 0x9B70, 0xC343, 0x9B71, + 0xC344, 0x9B72, 0xC345, 0xBDE0, 0xC346, 0x9B73, 0xC347, 0x9B74, 0xC348, 0x9B75, 0xC349, 0x9B76, 0xC34A, 0x9B77, 0xC34B, 0x9B78, + 0xC34C, 0x9B79, 0xC34D, 0x9B7A, 0xC34E, 0x9B81, 0xC34F, 0x9B82, 0xC350, 0x9B83, 0xC351, 0x9B84, 0xC352, 0x9B85, 0xC353, 0x9B86, + 0xC354, 0x9B87, 0xC355, 0x9B88, 0xC356, 0x9B89, 0xC357, 0x9B8A, 0xC358, 0x9B8B, 0xC359, 0x9B8C, 0xC35A, 0x9B8D, 0xC35B, 0x9B8E, + 0xC35C, 0x9B8F, 0xC35D, 0x9B90, 0xC35E, 0x9B91, 0xC35F, 0x9B92, 0xC360, 0x9B93, 0xC361, 0x9B94, 0xC362, 0x9B95, 0xC363, 0x9B96, + 0xC364, 0x9B97, 0xC365, 0x9B98, 0xC366, 0x9B99, 0xC367, 0x9B9A, 0xC368, 0xBDE1, 0xC369, 0xBDE2, 0xC36A, 0x9B9B, 0xC36B, 0x9B9C, + 0xC36C, 0xBDE3, 0xC36D, 0x9B9D, 0xC36E, 0x9B9E, 0xC36F, 0x9B9F, 0xC370, 0xBDE4, 0xC371, 0x9BA0, 0xC372, 0xBDE5, 0xC373, 0x9BA1, + 0xC374, 0x9BA2, 0xC375, 0x9BA3, 0xC376, 0x9BA4, 0xC377, 0x9BA5, 0xC378, 0xBDE6, 0xC379, 0xBDE7, 0xC37A, 0x9BA6, 0xC37B, 0x9BA7, + 0xC37C, 0xBDE8, 0xC37D, 0xBDE9, 0xC37E, 0x9BA8, 0xC37F, 0x9BA9, 0xC380, 0x9BAA, 0xC381, 0x9BAB, 0xC382, 0x9BAC, 0xC383, 0x9BAD, + 0xC384, 0xBDEA, 0xC385, 0x9BAE, 0xC386, 0x9BAF, 0xC387, 0x9BB0, 0xC388, 0xBDEB, 0xC389, 0x9BB1, 0xC38A, 0x9BB2, 0xC38B, 0x9BB3, + 0xC38C, 0xBDEC, 0xC38D, 0x9BB4, 0xC38E, 0x9BB5, 0xC38F, 0x9BB6, 0xC390, 0x9BB7, 0xC391, 0x9BB8, 0xC392, 0x9BB9, 0xC393, 0x9BBA, + 0xC394, 0x9BBB, 0xC395, 0x9BBC, 0xC396, 0x9BBD, 0xC397, 0x9BBE, 0xC398, 0x9BBF, 0xC399, 0x9BC0, 0xC39A, 0x9BC1, 0xC39B, 0x9BC2, + 0xC39C, 0x9BC3, 0xC39D, 0x9BC4, 0xC39E, 0x9BC5, 0xC39F, 0x9BC6, 0xC3A0, 0x9BC7, 0xC3A1, 0x9BC8, 0xC3A2, 0x9BC9, 0xC3A3, 0x9BCA, + 0xC3A4, 0x9BCB, 0xC3A5, 0x9BCC, 0xC3A6, 0x9BCD, 0xC3A7, 0x9BCE, 0xC3A8, 0x9BCF, 0xC3A9, 0x9BD0, 0xC3AA, 0x9BD1, 0xC3AB, 0x9BD2, + 0xC3AC, 0x9BD3, 0xC3AD, 0x9BD4, 0xC3AE, 0x9BD5, 0xC3AF, 0x9BD6, 0xC3B0, 0x9BD7, 0xC3B1, 0x9BD8, 0xC3B2, 0x9BD9, 0xC3B3, 0x9BDA, + 0xC3B4, 0x9BDB, 0xC3B5, 0x9BDC, 0xC3B6, 0x9BDD, 0xC3B7, 0x9BDE, 0xC3B8, 0x9BDF, 0xC3B9, 0x9BE0, 0xC3BA, 0x9BE1, 0xC3BB, 0x9BE2, + 0xC3BC, 0x9BE3, 0xC3BD, 0x9BE4, 0xC3BE, 0x9BE5, 0xC3BF, 0x9BE6, 0xC3C0, 0xBDED, 0xC3C1, 0x9BE7, 0xC3C2, 0x9BE8, 0xC3C3, 0x9BE9, + 0xC3C4, 0x9BEA, 0xC3C5, 0x9BEB, 0xC3C6, 0x9BEC, 0xC3C7, 0x9BED, 0xC3C8, 0x9BEE, 0xC3C9, 0x9BEF, 0xC3CA, 0x9BF0, 0xC3CB, 0x9BF1, + 0xC3CC, 0x9BF2, 0xC3CD, 0x9BF3, 0xC3CE, 0x9BF4, 0xC3CF, 0x9BF5, 0xC3D0, 0x9BF6, 0xC3D1, 0x9BF7, 0xC3D2, 0x9BF8, 0xC3D3, 0x9BF9, + 0xC3D4, 0x9BFA, 0xC3D5, 0x9BFB, 0xC3D6, 0x9BFC, 0xC3D7, 0x9BFD, 0xC3D8, 0xBDEE, 0xC3D9, 0xBDEF, 0xC3DA, 0x9BFE, 0xC3DB, 0x9C41, + 0xC3DC, 0xBDF0, 0xC3DD, 0x9C42, 0xC3DE, 0x9C43, 0xC3DF, 0xBDF1, 0xC3E0, 0xBDF2, 0xC3E1, 0x9C44, 0xC3E2, 0xBDF3, 0xC3E3, 0x9C45, + 0xC3E4, 0x9C46, 0xC3E5, 0x9C47, 0xC3E6, 0x9C48, 0xC3E7, 0x9C49, 0xC3E8, 0xBDF4, 0xC3E9, 0xBDF5, 0xC3EA, 0x9C4A, 0xC3EB, 0x9C4B, + 0xC3EC, 0x9C4C, 0xC3ED, 0xBDF6, 0xC3EE, 0x9C4D, 0xC3EF, 0x9C4E, 0xC3F0, 0x9C4F, 0xC3F1, 0x9C50, 0xC3F2, 0x9C51, 0xC3F3, 0x9C52, + 0xC3F4, 0xBDF7, 0xC3F5, 0xBDF8, 0xC3F6, 0x9C53, 0xC3F7, 0x9C54, 0xC3F8, 0xBDF9, 0xC3F9, 0x9C55, 0xC3FA, 0x9C56, 0xC3FB, 0x9C57, + 0xC3FC, 0x9C58, 0xC3FD, 0x9C59, 0xC3FE, 0x9C5A, 0xC3FF, 0x9C61, 0xC400, 0x9C62, 0xC401, 0x9C63, 0xC402, 0x9C64, 0xC403, 0x9C65, + 0xC404, 0x9C66, 0xC405, 0x9C67, 0xC406, 0x9C68, 0xC407, 0x9C69, 0xC408, 0xBDFA, 0xC409, 0x9C6A, 0xC40A, 0x9C6B, 0xC40B, 0x9C6C, + 0xC40C, 0x9C6D, 0xC40D, 0x9C6E, 0xC40E, 0x9C6F, 0xC40F, 0x9C70, 0xC410, 0xBDFB, 0xC411, 0x9C71, 0xC412, 0x9C72, 0xC413, 0x9C73, + 0xC414, 0x9C74, 0xC415, 0x9C75, 0xC416, 0x9C76, 0xC417, 0x9C77, 0xC418, 0x9C78, 0xC419, 0x9C79, 0xC41A, 0x9C7A, 0xC41B, 0x9C81, + 0xC41C, 0x9C82, 0xC41D, 0x9C83, 0xC41E, 0x9C84, 0xC41F, 0x9C85, 0xC420, 0x9C86, 0xC421, 0x9C87, 0xC422, 0x9C88, 0xC423, 0x9C89, + 0xC424, 0xBDFC, 0xC425, 0x9C8A, 0xC426, 0x9C8B, 0xC427, 0x9C8C, 0xC428, 0x9C8D, 0xC429, 0x9C8E, 0xC42A, 0x9C8F, 0xC42B, 0x9C90, + 0xC42C, 0xBDFD, 0xC42D, 0x9C91, 0xC42E, 0x9C92, 0xC42F, 0x9C93, 0xC430, 0xBDFE, 0xC431, 0x9C94, 0xC432, 0x9C95, 0xC433, 0x9C96, + 0xC434, 0xBEA1, 0xC435, 0x9C97, 0xC436, 0x9C98, 0xC437, 0x9C99, 0xC438, 0x9C9A, 0xC439, 0x9C9B, 0xC43A, 0x9C9C, 0xC43B, 0x9C9D, + 0xC43C, 0xBEA2, 0xC43D, 0xBEA3, 0xC43E, 0x9C9E, 0xC43F, 0x9C9F, 0xC440, 0x9CA0, 0xC441, 0x9CA1, 0xC442, 0x9CA2, 0xC443, 0x9CA3, + 0xC444, 0x9CA4, 0xC445, 0x9CA5, 0xC446, 0x9CA6, 0xC447, 0x9CA7, 0xC448, 0xBEA4, 0xC449, 0x9CA8, 0xC44A, 0x9CA9, 0xC44B, 0x9CAA, + 0xC44C, 0x9CAB, 0xC44D, 0x9CAC, 0xC44E, 0x9CAD, 0xC44F, 0x9CAE, 0xC450, 0x9CAF, 0xC451, 0x9CB0, 0xC452, 0x9CB1, 0xC453, 0x9CB2, + 0xC454, 0x9CB3, 0xC455, 0x9CB4, 0xC456, 0x9CB5, 0xC457, 0x9CB6, 0xC458, 0x9CB7, 0xC459, 0x9CB8, 0xC45A, 0x9CB9, 0xC45B, 0x9CBA, + 0xC45C, 0x9CBB, 0xC45D, 0x9CBC, 0xC45E, 0x9CBD, 0xC45F, 0x9CBE, 0xC460, 0x9CBF, 0xC461, 0x9CC0, 0xC462, 0x9CC1, 0xC463, 0x9CC2, + 0xC464, 0xBEA5, 0xC465, 0xBEA6, 0xC466, 0x9CC3, 0xC467, 0x9CC4, 0xC468, 0xBEA7, 0xC469, 0x9CC5, 0xC46A, 0x9CC6, 0xC46B, 0x9CC7, + 0xC46C, 0xBEA8, 0xC46D, 0x9CC8, 0xC46E, 0x9CC9, 0xC46F, 0x9CCA, 0xC470, 0x9CCB, 0xC471, 0x9CCC, 0xC472, 0x9CCD, 0xC473, 0x9CCE, + 0xC474, 0xBEA9, 0xC475, 0xBEAA, 0xC476, 0x9CCF, 0xC477, 0x9CD0, 0xC478, 0x9CD1, 0xC479, 0xBEAB, 0xC47A, 0x9CD2, 0xC47B, 0x9CD3, + 0xC47C, 0x9CD4, 0xC47D, 0x9CD5, 0xC47E, 0x9CD6, 0xC47F, 0x9CD7, 0xC480, 0xBEAC, 0xC481, 0x9CD8, 0xC482, 0x9CD9, 0xC483, 0x9CDA, + 0xC484, 0x9CDB, 0xC485, 0x9CDC, 0xC486, 0x9CDD, 0xC487, 0x9CDE, 0xC488, 0x9CDF, 0xC489, 0x9CE0, 0xC48A, 0x9CE1, 0xC48B, 0x9CE2, + 0xC48C, 0x9CE3, 0xC48D, 0x9CE4, 0xC48E, 0x9CE5, 0xC48F, 0x9CE6, 0xC490, 0x9CE7, 0xC491, 0x9CE8, 0xC492, 0x9CE9, 0xC493, 0x9CEA, + 0xC494, 0xBEAD, 0xC495, 0x9CEB, 0xC496, 0x9CEC, 0xC497, 0x9CED, 0xC498, 0x9CEE, 0xC499, 0x9CEF, 0xC49A, 0x9CF0, 0xC49B, 0x9CF1, + 0xC49C, 0xBEAE, 0xC49D, 0x9CF2, 0xC49E, 0x9CF3, 0xC49F, 0x9CF4, 0xC4A0, 0x9CF5, 0xC4A1, 0x9CF6, 0xC4A2, 0x9CF7, 0xC4A3, 0x9CF8, + 0xC4A4, 0x9CF9, 0xC4A5, 0x9CFA, 0xC4A6, 0x9CFB, 0xC4A7, 0x9CFC, 0xC4A8, 0x9CFD, 0xC4A9, 0x9CFE, 0xC4AA, 0x9D41, 0xC4AB, 0x9D42, + 0xC4AC, 0x9D43, 0xC4AD, 0x9D44, 0xC4AE, 0x9D45, 0xC4AF, 0x9D46, 0xC4B0, 0x9D47, 0xC4B1, 0x9D48, 0xC4B2, 0x9D49, 0xC4B3, 0x9D4A, + 0xC4B4, 0x9D4B, 0xC4B5, 0x9D4C, 0xC4B6, 0x9D4D, 0xC4B7, 0x9D4E, 0xC4B8, 0xBEAF, 0xC4B9, 0x9D4F, 0xC4BA, 0x9D50, 0xC4BB, 0x9D51, + 0xC4BC, 0xBEB0, 0xC4BD, 0x9D52, 0xC4BE, 0x9D53, 0xC4BF, 0x9D54, 0xC4C0, 0x9D55, 0xC4C1, 0x9D56, 0xC4C2, 0x9D57, 0xC4C3, 0x9D58, + 0xC4C4, 0x9D59, 0xC4C5, 0x9D5A, 0xC4C6, 0x9D61, 0xC4C7, 0x9D62, 0xC4C8, 0x9D63, 0xC4C9, 0x9D64, 0xC4CA, 0x9D65, 0xC4CB, 0x9D66, + 0xC4CC, 0x9D67, 0xC4CD, 0x9D68, 0xC4CE, 0x9D69, 0xC4CF, 0x9D6A, 0xC4D0, 0x9D6B, 0xC4D1, 0x9D6C, 0xC4D2, 0x9D6D, 0xC4D3, 0x9D6E, + 0xC4D4, 0x9D6F, 0xC4D5, 0x9D70, 0xC4D6, 0x9D71, 0xC4D7, 0x9D72, 0xC4D8, 0x9D73, 0xC4D9, 0x9D74, 0xC4DA, 0x9D75, 0xC4DB, 0x9D76, + 0xC4DC, 0x9D77, 0xC4DD, 0x9D78, 0xC4DE, 0x9D79, 0xC4DF, 0x9D7A, 0xC4E0, 0x9D81, 0xC4E1, 0x9D82, 0xC4E2, 0x9D83, 0xC4E3, 0x9D84, + 0xC4E4, 0x9D85, 0xC4E5, 0x9D86, 0xC4E6, 0x9D87, 0xC4E7, 0x9D88, 0xC4E8, 0x9D89, 0xC4E9, 0xBEB1, 0xC4EA, 0x9D8A, 0xC4EB, 0x9D8B, + 0xC4EC, 0x9D8C, 0xC4ED, 0x9D8D, 0xC4EE, 0x9D8E, 0xC4EF, 0x9D8F, 0xC4F0, 0xBEB2, 0xC4F1, 0xBEB3, 0xC4F2, 0x9D90, 0xC4F3, 0x9D91, + 0xC4F4, 0xBEB4, 0xC4F5, 0x9D92, 0xC4F6, 0x9D93, 0xC4F7, 0x9D94, 0xC4F8, 0xBEB5, 0xC4F9, 0x9D95, 0xC4FA, 0xBEB6, 0xC4FB, 0x9D96, + 0xC4FC, 0x9D97, 0xC4FD, 0x9D98, 0xC4FE, 0x9D99, 0xC4FF, 0xBEB7, 0xC500, 0xBEB8, 0xC501, 0xBEB9, 0xC502, 0x9D9A, 0xC503, 0x9D9B, + 0xC504, 0x9D9C, 0xC505, 0x9D9D, 0xC506, 0x9D9E, 0xC507, 0x9D9F, 0xC508, 0x9DA0, 0xC509, 0x9DA1, 0xC50A, 0x9DA2, 0xC50B, 0x9DA3, + 0xC50C, 0xBEBA, 0xC50D, 0x9DA4, 0xC50E, 0x9DA5, 0xC50F, 0x9DA6, 0xC510, 0xBEBB, 0xC511, 0x9DA7, 0xC512, 0x9DA8, 0xC513, 0x9DA9, + 0xC514, 0xBEBC, 0xC515, 0x9DAA, 0xC516, 0x9DAB, 0xC517, 0x9DAC, 0xC518, 0x9DAD, 0xC519, 0x9DAE, 0xC51A, 0x9DAF, 0xC51B, 0x9DB0, + 0xC51C, 0xBEBD, 0xC51D, 0x9DB1, 0xC51E, 0x9DB2, 0xC51F, 0x9DB3, 0xC520, 0x9DB4, 0xC521, 0x9DB5, 0xC522, 0x9DB6, 0xC523, 0x9DB7, + 0xC524, 0x9DB8, 0xC525, 0x9DB9, 0xC526, 0x9DBA, 0xC527, 0x9DBB, 0xC528, 0xBEBE, 0xC529, 0xBEBF, 0xC52A, 0x9DBC, 0xC52B, 0x9DBD, + 0xC52C, 0xBEC0, 0xC52D, 0x9DBE, 0xC52E, 0x9DBF, 0xC52F, 0x9DC0, 0xC530, 0xBEC1, 0xC531, 0x9DC1, 0xC532, 0x9DC2, 0xC533, 0x9DC3, + 0xC534, 0x9DC4, 0xC535, 0x9DC5, 0xC536, 0x9DC6, 0xC537, 0x9DC7, 0xC538, 0xBEC2, 0xC539, 0xBEC3, 0xC53A, 0x9DC8, 0xC53B, 0xBEC4, + 0xC53C, 0x9DC9, 0xC53D, 0xBEC5, 0xC53E, 0x9DCA, 0xC53F, 0x9DCB, 0xC540, 0x9DCC, 0xC541, 0x9DCD, 0xC542, 0x9DCE, 0xC543, 0x9DCF, + 0xC544, 0xBEC6, 0xC545, 0xBEC7, 0xC546, 0x9DD0, 0xC547, 0x9DD1, 0xC548, 0xBEC8, 0xC549, 0xBEC9, 0xC54A, 0xBECA, 0xC54B, 0x9DD2, + 0xC54C, 0xBECB, 0xC54D, 0xBECC, 0xC54E, 0xBECD, 0xC54F, 0x9DD3, 0xC550, 0x9DD4, 0xC551, 0x9DD5, 0xC552, 0x9DD6, 0xC553, 0xBECE, + 0xC554, 0xBECF, 0xC555, 0xBED0, 0xC556, 0x9DD7, 0xC557, 0xBED1, 0xC558, 0xBED2, 0xC559, 0xBED3, 0xC55A, 0x9DD8, 0xC55B, 0x9DD9, + 0xC55C, 0x9DDA, 0xC55D, 0xBED4, 0xC55E, 0xBED5, 0xC55F, 0x9DDB, 0xC560, 0xBED6, 0xC561, 0xBED7, 0xC562, 0x9DDC, 0xC563, 0x9DDD, + 0xC564, 0xBED8, 0xC565, 0x9DDE, 0xC566, 0x9DDF, 0xC567, 0x9DE0, 0xC568, 0xBED9, 0xC569, 0x9DE1, 0xC56A, 0x9DE2, 0xC56B, 0x9DE3, + 0xC56C, 0x9DE4, 0xC56D, 0x9DE5, 0xC56E, 0x9DE6, 0xC56F, 0x9DE7, 0xC570, 0xBEDA, 0xC571, 0xBEDB, 0xC572, 0x9DE8, 0xC573, 0xBEDC, + 0xC574, 0xBEDD, 0xC575, 0xBEDE, 0xC576, 0x9DE9, 0xC577, 0x9DEA, 0xC578, 0x9DEB, 0xC579, 0x9DEC, 0xC57A, 0x9DED, 0xC57B, 0x9DEE, + 0xC57C, 0xBEDF, 0xC57D, 0xBEE0, 0xC57E, 0x9DEF, 0xC57F, 0x9DF0, 0xC580, 0xBEE1, 0xC581, 0x9DF1, 0xC582, 0x9DF2, 0xC583, 0x9DF3, + 0xC584, 0xBEE2, 0xC585, 0x9DF4, 0xC586, 0x9DF5, 0xC587, 0xBEE3, 0xC588, 0x9DF6, 0xC589, 0x9DF7, 0xC58A, 0x9DF8, 0xC58B, 0x9DF9, + 0xC58C, 0xBEE4, 0xC58D, 0xBEE5, 0xC58E, 0x9DFA, 0xC58F, 0xBEE6, 0xC590, 0x9DFB, 0xC591, 0xBEE7, 0xC592, 0x9DFC, 0xC593, 0x9DFD, + 0xC594, 0x9DFE, 0xC595, 0xBEE8, 0xC596, 0x9E41, 0xC597, 0xBEE9, 0xC598, 0xBEEA, 0xC599, 0x9E42, 0xC59A, 0x9E43, 0xC59B, 0x9E44, + 0xC59C, 0xBEEB, 0xC59D, 0x9E45, 0xC59E, 0x9E46, 0xC59F, 0x9E47, 0xC5A0, 0xBEEC, 0xC5A1, 0x9E48, 0xC5A2, 0x9E49, 0xC5A3, 0x9E4A, + 0xC5A4, 0x9E4B, 0xC5A5, 0x9E4C, 0xC5A6, 0x9E4D, 0xC5A7, 0x9E4E, 0xC5A8, 0x9E4F, 0xC5A9, 0xBEED, 0xC5AA, 0x9E50, 0xC5AB, 0x9E51, + 0xC5AC, 0x9E52, 0xC5AD, 0x9E53, 0xC5AE, 0x9E54, 0xC5AF, 0x9E55, 0xC5B0, 0x9E56, 0xC5B1, 0x9E57, 0xC5B2, 0x9E58, 0xC5B3, 0x9E59, + 0xC5B4, 0xBEEE, 0xC5B5, 0xBEEF, 0xC5B6, 0x9E5A, 0xC5B7, 0x9E61, 0xC5B8, 0xBEF0, 0xC5B9, 0xBEF1, 0xC5BA, 0x9E62, 0xC5BB, 0xBEF2, + 0xC5BC, 0xBEF3, 0xC5BD, 0xBEF4, 0xC5BE, 0xBEF5, 0xC5BF, 0x9E63, 0xC5C0, 0x9E64, 0xC5C1, 0x9E65, 0xC5C2, 0x9E66, 0xC5C3, 0x9E67, + 0xC5C4, 0xBEF6, 0xC5C5, 0xBEF7, 0xC5C6, 0xBEF8, 0xC5C7, 0xBEF9, 0xC5C8, 0xBEFA, 0xC5C9, 0xBEFB, 0xC5CA, 0xBEFC, 0xC5CB, 0x9E68, + 0xC5CC, 0xBEFD, 0xC5CD, 0x9E69, 0xC5CE, 0xBEFE, 0xC5CF, 0x9E6A, 0xC5D0, 0xBFA1, 0xC5D1, 0xBFA2, 0xC5D2, 0x9E6B, 0xC5D3, 0x9E6C, + 0xC5D4, 0xBFA3, 0xC5D5, 0x9E6D, 0xC5D6, 0x9E6E, 0xC5D7, 0x9E6F, 0xC5D8, 0xBFA4, 0xC5D9, 0x9E70, 0xC5DA, 0x9E71, 0xC5DB, 0x9E72, + 0xC5DC, 0x9E73, 0xC5DD, 0x9E74, 0xC5DE, 0x9E75, 0xC5DF, 0x9E76, 0xC5E0, 0xBFA5, 0xC5E1, 0xBFA6, 0xC5E2, 0x9E77, 0xC5E3, 0xBFA7, + 0xC5E4, 0x9E78, 0xC5E5, 0xBFA8, 0xC5E6, 0x9E79, 0xC5E7, 0x9E7A, 0xC5E8, 0x9E81, 0xC5E9, 0x9E82, 0xC5EA, 0x9E83, 0xC5EB, 0x9E84, + 0xC5EC, 0xBFA9, 0xC5ED, 0xBFAA, 0xC5EE, 0xBFAB, 0xC5EF, 0x9E85, 0xC5F0, 0xBFAC, 0xC5F1, 0x9E86, 0xC5F2, 0x9E87, 0xC5F3, 0x9E88, + 0xC5F4, 0xBFAD, 0xC5F5, 0x9E89, 0xC5F6, 0xBFAE, 0xC5F7, 0xBFAF, 0xC5F8, 0x9E8A, 0xC5F9, 0x9E8B, 0xC5FA, 0x9E8C, 0xC5FB, 0x9E8D, + 0xC5FC, 0xBFB0, 0xC5FD, 0xBFB1, 0xC5FE, 0xBFB2, 0xC5FF, 0xBFB3, 0xC600, 0xBFB4, 0xC601, 0xBFB5, 0xC602, 0x9E8E, 0xC603, 0x9E8F, + 0xC604, 0x9E90, 0xC605, 0xBFB6, 0xC606, 0xBFB7, 0xC607, 0xBFB8, 0xC608, 0xBFB9, 0xC609, 0x9E91, 0xC60A, 0x9E92, 0xC60B, 0x9E93, + 0xC60C, 0xBFBA, 0xC60D, 0x9E94, 0xC60E, 0x9E95, 0xC60F, 0x9E96, 0xC610, 0xBFBB, 0xC611, 0x9E97, 0xC612, 0x9E98, 0xC613, 0x9E99, + 0xC614, 0x9E9A, 0xC615, 0x9E9B, 0xC616, 0x9E9C, 0xC617, 0x9E9D, 0xC618, 0xBFBC, 0xC619, 0xBFBD, 0xC61A, 0x9E9E, 0xC61B, 0xBFBE, + 0xC61C, 0xBFBF, 0xC61D, 0x9E9F, 0xC61E, 0x9EA0, 0xC61F, 0x9EA1, 0xC620, 0x9EA2, 0xC621, 0x9EA3, 0xC622, 0x9EA4, 0xC623, 0x9EA5, + 0xC624, 0xBFC0, 0xC625, 0xBFC1, 0xC626, 0x9EA6, 0xC627, 0x9EA7, 0xC628, 0xBFC2, 0xC629, 0x9EA8, 0xC62A, 0x9EA9, 0xC62B, 0x9EAA, + 0xC62C, 0xBFC3, 0xC62D, 0xBFC4, 0xC62E, 0xBFC5, 0xC62F, 0x9EAB, 0xC630, 0xBFC6, 0xC631, 0x9EAC, 0xC632, 0x9EAD, 0xC633, 0xBFC7, + 0xC634, 0xBFC8, 0xC635, 0xBFC9, 0xC636, 0x9EAE, 0xC637, 0xBFCA, 0xC638, 0x9EAF, 0xC639, 0xBFCB, 0xC63A, 0x9EB0, 0xC63B, 0xBFCC, + 0xC63C, 0x9EB1, 0xC63D, 0x9EB2, 0xC63E, 0x9EB3, 0xC63F, 0x9EB4, 0xC640, 0xBFCD, 0xC641, 0xBFCE, 0xC642, 0x9EB5, 0xC643, 0x9EB6, + 0xC644, 0xBFCF, 0xC645, 0x9EB7, 0xC646, 0x9EB8, 0xC647, 0x9EB9, 0xC648, 0xBFD0, 0xC649, 0x9EBA, 0xC64A, 0x9EBB, 0xC64B, 0x9EBC, + 0xC64C, 0x9EBD, 0xC64D, 0x9EBE, 0xC64E, 0x9EBF, 0xC64F, 0x9EC0, 0xC650, 0xBFD1, 0xC651, 0xBFD2, 0xC652, 0x9EC1, 0xC653, 0xBFD3, + 0xC654, 0xBFD4, 0xC655, 0xBFD5, 0xC656, 0x9EC2, 0xC657, 0x9EC3, 0xC658, 0x9EC4, 0xC659, 0x9EC5, 0xC65A, 0x9EC6, 0xC65B, 0x9EC7, + 0xC65C, 0xBFD6, 0xC65D, 0xBFD7, 0xC65E, 0x9EC8, 0xC65F, 0x9EC9, 0xC660, 0xBFD8, 0xC661, 0x9ECA, 0xC662, 0x9ECB, 0xC663, 0x9ECC, + 0xC664, 0x9ECD, 0xC665, 0x9ECE, 0xC666, 0x9ECF, 0xC667, 0x9ED0, 0xC668, 0x9ED1, 0xC669, 0x9ED2, 0xC66A, 0x9ED3, 0xC66B, 0x9ED4, + 0xC66C, 0xBFD9, 0xC66D, 0x9ED5, 0xC66E, 0x9ED6, 0xC66F, 0xBFDA, 0xC670, 0x9ED7, 0xC671, 0xBFDB, 0xC672, 0x9ED8, 0xC673, 0x9ED9, + 0xC674, 0x9EDA, 0xC675, 0x9EDB, 0xC676, 0x9EDC, 0xC677, 0x9EDD, 0xC678, 0xBFDC, 0xC679, 0xBFDD, 0xC67A, 0x9EDE, 0xC67B, 0x9EDF, + 0xC67C, 0xBFDE, 0xC67D, 0x9EE0, 0xC67E, 0x9EE1, 0xC67F, 0x9EE2, 0xC680, 0xBFDF, 0xC681, 0x9EE3, 0xC682, 0x9EE4, 0xC683, 0x9EE5, + 0xC684, 0x9EE6, 0xC685, 0x9EE7, 0xC686, 0x9EE8, 0xC687, 0x9EE9, 0xC688, 0xBFE0, 0xC689, 0xBFE1, 0xC68A, 0x9EEA, 0xC68B, 0xBFE2, + 0xC68C, 0x9EEB, 0xC68D, 0xBFE3, 0xC68E, 0x9EEC, 0xC68F, 0x9EED, 0xC690, 0x9EEE, 0xC691, 0x9EEF, 0xC692, 0x9EF0, 0xC693, 0x9EF1, + 0xC694, 0xBFE4, 0xC695, 0xBFE5, 0xC696, 0x9EF2, 0xC697, 0x9EF3, 0xC698, 0xBFE6, 0xC699, 0x9EF4, 0xC69A, 0x9EF5, 0xC69B, 0x9EF6, + 0xC69C, 0xBFE7, 0xC69D, 0x9EF7, 0xC69E, 0x9EF8, 0xC69F, 0x9EF9, 0xC6A0, 0x9EFA, 0xC6A1, 0x9EFB, 0xC6A2, 0x9EFC, 0xC6A3, 0x9EFD, + 0xC6A4, 0xBFE8, 0xC6A5, 0xBFE9, 0xC6A6, 0x9EFE, 0xC6A7, 0xBFEA, 0xC6A8, 0x9F41, 0xC6A9, 0xBFEB, 0xC6AA, 0x9F42, 0xC6AB, 0x9F43, + 0xC6AC, 0x9F44, 0xC6AD, 0x9F45, 0xC6AE, 0x9F46, 0xC6AF, 0x9F47, 0xC6B0, 0xBFEC, 0xC6B1, 0xBFED, 0xC6B2, 0x9F48, 0xC6B3, 0x9F49, + 0xC6B4, 0xBFEE, 0xC6B5, 0x9F4A, 0xC6B6, 0x9F4B, 0xC6B7, 0x9F4C, 0xC6B8, 0xBFEF, 0xC6B9, 0xBFF0, 0xC6BA, 0xBFF1, 0xC6BB, 0x9F4D, + 0xC6BC, 0x9F4E, 0xC6BD, 0x9F4F, 0xC6BE, 0x9F50, 0xC6BF, 0x9F51, 0xC6C0, 0xBFF2, 0xC6C1, 0xBFF3, 0xC6C2, 0x9F52, 0xC6C3, 0xBFF4, + 0xC6C4, 0x9F53, 0xC6C5, 0xBFF5, 0xC6C6, 0x9F54, 0xC6C7, 0x9F55, 0xC6C8, 0x9F56, 0xC6C9, 0x9F57, 0xC6CA, 0x9F58, 0xC6CB, 0x9F59, + 0xC6CC, 0xBFF6, 0xC6CD, 0xBFF7, 0xC6CE, 0x9F5A, 0xC6CF, 0x9F61, 0xC6D0, 0xBFF8, 0xC6D1, 0x9F62, 0xC6D2, 0x9F63, 0xC6D3, 0x9F64, + 0xC6D4, 0xBFF9, 0xC6D5, 0x9F65, 0xC6D6, 0x9F66, 0xC6D7, 0x9F67, 0xC6D8, 0x9F68, 0xC6D9, 0x9F69, 0xC6DA, 0x9F6A, 0xC6DB, 0x9F6B, + 0xC6DC, 0xBFFA, 0xC6DD, 0xBFFB, 0xC6DE, 0x9F6C, 0xC6DF, 0x9F6D, 0xC6E0, 0xBFFC, 0xC6E1, 0xBFFD, 0xC6E2, 0x9F6E, 0xC6E3, 0x9F6F, + 0xC6E4, 0x9F70, 0xC6E5, 0x9F71, 0xC6E6, 0x9F72, 0xC6E7, 0x9F73, 0xC6E8, 0xBFFE, 0xC6E9, 0xC0A1, 0xC6EA, 0x9F74, 0xC6EB, 0x9F75, + 0xC6EC, 0xC0A2, 0xC6ED, 0x9F76, 0xC6EE, 0x9F77, 0xC6EF, 0x9F78, 0xC6F0, 0xC0A3, 0xC6F1, 0x9F79, 0xC6F2, 0x9F7A, 0xC6F3, 0x9F81, + 0xC6F4, 0x9F82, 0xC6F5, 0x9F83, 0xC6F6, 0x9F84, 0xC6F7, 0x9F85, 0xC6F8, 0xC0A4, 0xC6F9, 0xC0A5, 0xC6FA, 0x9F86, 0xC6FB, 0x9F87, + 0xC6FC, 0x9F88, 0xC6FD, 0xC0A6, 0xC6FE, 0x9F89, 0xC6FF, 0x9F8A, 0xC700, 0x9F8B, 0xC701, 0x9F8C, 0xC702, 0x9F8D, 0xC703, 0x9F8E, + 0xC704, 0xC0A7, 0xC705, 0xC0A8, 0xC706, 0x9F8F, 0xC707, 0x9F90, 0xC708, 0xC0A9, 0xC709, 0x9F91, 0xC70A, 0x9F92, 0xC70B, 0x9F93, + 0xC70C, 0xC0AA, 0xC70D, 0x9F94, 0xC70E, 0x9F95, 0xC70F, 0x9F96, 0xC710, 0x9F97, 0xC711, 0x9F98, 0xC712, 0x9F99, 0xC713, 0x9F9A, + 0xC714, 0xC0AB, 0xC715, 0xC0AC, 0xC716, 0x9F9B, 0xC717, 0xC0AD, 0xC718, 0x9F9C, 0xC719, 0xC0AE, 0xC71A, 0x9F9D, 0xC71B, 0x9F9E, + 0xC71C, 0x9F9F, 0xC71D, 0x9FA0, 0xC71E, 0x9FA1, 0xC71F, 0x9FA2, 0xC720, 0xC0AF, 0xC721, 0xC0B0, 0xC722, 0x9FA3, 0xC723, 0x9FA4, + 0xC724, 0xC0B1, 0xC725, 0x9FA5, 0xC726, 0x9FA6, 0xC727, 0x9FA7, 0xC728, 0xC0B2, 0xC729, 0x9FA8, 0xC72A, 0x9FA9, 0xC72B, 0x9FAA, + 0xC72C, 0x9FAB, 0xC72D, 0x9FAC, 0xC72E, 0x9FAD, 0xC72F, 0x9FAE, 0xC730, 0xC0B3, 0xC731, 0xC0B4, 0xC732, 0x9FAF, 0xC733, 0xC0B5, + 0xC734, 0x9FB0, 0xC735, 0xC0B6, 0xC736, 0x9FB1, 0xC737, 0xC0B7, 0xC738, 0x9FB2, 0xC739, 0x9FB3, 0xC73A, 0x9FB4, 0xC73B, 0x9FB5, + 0xC73C, 0xC0B8, 0xC73D, 0xC0B9, 0xC73E, 0x9FB6, 0xC73F, 0x9FB7, 0xC740, 0xC0BA, 0xC741, 0x9FB8, 0xC742, 0x9FB9, 0xC743, 0x9FBA, + 0xC744, 0xC0BB, 0xC745, 0x9FBB, 0xC746, 0x9FBC, 0xC747, 0x9FBD, 0xC748, 0x9FBE, 0xC749, 0x9FBF, 0xC74A, 0xC0BC, 0xC74B, 0x9FC0, + 0xC74C, 0xC0BD, 0xC74D, 0xC0BE, 0xC74E, 0x9FC1, 0xC74F, 0xC0BF, 0xC750, 0x9FC2, 0xC751, 0xC0C0, 0xC752, 0xC0C1, 0xC753, 0xC0C2, + 0xC754, 0xC0C3, 0xC755, 0xC0C4, 0xC756, 0xC0C5, 0xC757, 0xC0C6, 0xC758, 0xC0C7, 0xC759, 0x9FC3, 0xC75A, 0x9FC4, 0xC75B, 0x9FC5, + 0xC75C, 0xC0C8, 0xC75D, 0x9FC6, 0xC75E, 0x9FC7, 0xC75F, 0x9FC8, 0xC760, 0xC0C9, 0xC761, 0x9FC9, 0xC762, 0x9FCA, 0xC763, 0x9FCB, + 0xC764, 0x9FCC, 0xC765, 0x9FCD, 0xC766, 0x9FCE, 0xC767, 0x9FCF, 0xC768, 0xC0CA, 0xC769, 0x9FD0, 0xC76A, 0x9FD1, 0xC76B, 0xC0CB, + 0xC76C, 0x9FD2, 0xC76D, 0x9FD3, 0xC76E, 0x9FD4, 0xC76F, 0x9FD5, 0xC770, 0x9FD6, 0xC771, 0x9FD7, 0xC772, 0x9FD8, 0xC773, 0x9FD9, + 0xC774, 0xC0CC, 0xC775, 0xC0CD, 0xC776, 0x9FDA, 0xC777, 0x9FDB, 0xC778, 0xC0CE, 0xC779, 0x9FDC, 0xC77A, 0x9FDD, 0xC77B, 0x9FDE, + 0xC77C, 0xC0CF, 0xC77D, 0xC0D0, 0xC77E, 0xC0D1, 0xC77F, 0x9FDF, 0xC780, 0x9FE0, 0xC781, 0x9FE1, 0xC782, 0x9FE2, 0xC783, 0xC0D2, + 0xC784, 0xC0D3, 0xC785, 0xC0D4, 0xC786, 0x9FE3, 0xC787, 0xC0D5, 0xC788, 0xC0D6, 0xC789, 0xC0D7, 0xC78A, 0xC0D8, 0xC78B, 0x9FE4, + 0xC78C, 0x9FE5, 0xC78D, 0x9FE6, 0xC78E, 0xC0D9, 0xC78F, 0x9FE7, 0xC790, 0xC0DA, 0xC791, 0xC0DB, 0xC792, 0x9FE8, 0xC793, 0x9FE9, + 0xC794, 0xC0DC, 0xC795, 0x9FEA, 0xC796, 0xC0DD, 0xC797, 0xC0DE, 0xC798, 0xC0DF, 0xC799, 0x9FEB, 0xC79A, 0xC0E0, 0xC79B, 0x9FEC, + 0xC79C, 0x9FED, 0xC79D, 0x9FEE, 0xC79E, 0x9FEF, 0xC79F, 0x9FF0, 0xC7A0, 0xC0E1, 0xC7A1, 0xC0E2, 0xC7A2, 0x9FF1, 0xC7A3, 0xC0E3, + 0xC7A4, 0xC0E4, 0xC7A5, 0xC0E5, 0xC7A6, 0xC0E6, 0xC7A7, 0x9FF2, 0xC7A8, 0x9FF3, 0xC7A9, 0x9FF4, 0xC7AA, 0x9FF5, 0xC7AB, 0x9FF6, + 0xC7AC, 0xC0E7, 0xC7AD, 0xC0E8, 0xC7AE, 0x9FF7, 0xC7AF, 0x9FF8, 0xC7B0, 0xC0E9, 0xC7B1, 0x9FF9, 0xC7B2, 0x9FFA, 0xC7B3, 0x9FFB, + 0xC7B4, 0xC0EA, 0xC7B5, 0x9FFC, 0xC7B6, 0x9FFD, 0xC7B7, 0x9FFE, 0xC7B8, 0xA041, 0xC7B9, 0xA042, 0xC7BA, 0xA043, 0xC7BB, 0xA044, + 0xC7BC, 0xC0EB, 0xC7BD, 0xC0EC, 0xC7BE, 0xA045, 0xC7BF, 0xC0ED, 0xC7C0, 0xC0EE, 0xC7C1, 0xC0EF, 0xC7C2, 0xA046, 0xC7C3, 0xA047, + 0xC7C4, 0xA048, 0xC7C5, 0xA049, 0xC7C6, 0xA04A, 0xC7C7, 0xA04B, 0xC7C8, 0xC0F0, 0xC7C9, 0xC0F1, 0xC7CA, 0xA04C, 0xC7CB, 0xA04D, + 0xC7CC, 0xC0F2, 0xC7CD, 0xA04E, 0xC7CE, 0xC0F3, 0xC7CF, 0xA04F, 0xC7D0, 0xC0F4, 0xC7D1, 0xA050, 0xC7D2, 0xA051, 0xC7D3, 0xA052, + 0xC7D4, 0xA053, 0xC7D5, 0xA054, 0xC7D6, 0xA055, 0xC7D7, 0xA056, 0xC7D8, 0xC0F5, 0xC7D9, 0xA057, 0xC7DA, 0xA058, 0xC7DB, 0xA059, + 0xC7DC, 0xA05A, 0xC7DD, 0xC0F6, 0xC7DE, 0xA061, 0xC7DF, 0xA062, 0xC7E0, 0xA063, 0xC7E1, 0xA064, 0xC7E2, 0xA065, 0xC7E3, 0xA066, + 0xC7E4, 0xC0F7, 0xC7E5, 0xA067, 0xC7E6, 0xA068, 0xC7E7, 0xA069, 0xC7E8, 0xC0F8, 0xC7E9, 0xA06A, 0xC7EA, 0xA06B, 0xC7EB, 0xA06C, + 0xC7EC, 0xC0F9, 0xC7ED, 0xA06D, 0xC7EE, 0xA06E, 0xC7EF, 0xA06F, 0xC7F0, 0xA070, 0xC7F1, 0xA071, 0xC7F2, 0xA072, 0xC7F3, 0xA073, + 0xC7F4, 0xA074, 0xC7F5, 0xA075, 0xC7F6, 0xA076, 0xC7F7, 0xA077, 0xC7F8, 0xA078, 0xC7F9, 0xA079, 0xC7FA, 0xA07A, 0xC7FB, 0xA081, + 0xC7FC, 0xA082, 0xC7FD, 0xA083, 0xC7FE, 0xA084, 0xC7FF, 0xA085, 0xC800, 0xC0FA, 0xC801, 0xC0FB, 0xC802, 0xA086, 0xC803, 0xA087, + 0xC804, 0xC0FC, 0xC805, 0xA088, 0xC806, 0xA089, 0xC807, 0xA08A, 0xC808, 0xC0FD, 0xC809, 0xA08B, 0xC80A, 0xC0FE, 0xC80B, 0xA08C, + 0xC80C, 0xA08D, 0xC80D, 0xA08E, 0xC80E, 0xA08F, 0xC80F, 0xA090, 0xC810, 0xC1A1, 0xC811, 0xC1A2, 0xC812, 0xA091, 0xC813, 0xC1A3, + 0xC814, 0xA092, 0xC815, 0xC1A4, 0xC816, 0xC1A5, 0xC817, 0xA093, 0xC818, 0xA094, 0xC819, 0xA095, 0xC81A, 0xA096, 0xC81B, 0xA097, + 0xC81C, 0xC1A6, 0xC81D, 0xC1A7, 0xC81E, 0xA098, 0xC81F, 0xA099, 0xC820, 0xC1A8, 0xC821, 0xA09A, 0xC822, 0xA09B, 0xC823, 0xA09C, + 0xC824, 0xC1A9, 0xC825, 0xA09D, 0xC826, 0xA09E, 0xC827, 0xA09F, 0xC828, 0xA0A0, 0xC829, 0xA0A1, 0xC82A, 0xA0A2, 0xC82B, 0xA0A3, + 0xC82C, 0xC1AA, 0xC82D, 0xC1AB, 0xC82E, 0xA0A4, 0xC82F, 0xC1AC, 0xC830, 0xA0A5, 0xC831, 0xC1AD, 0xC832, 0xA0A6, 0xC833, 0xA0A7, + 0xC834, 0xA0A8, 0xC835, 0xA0A9, 0xC836, 0xA0AA, 0xC837, 0xA0AB, 0xC838, 0xC1AE, 0xC839, 0xA0AC, 0xC83A, 0xA0AD, 0xC83B, 0xA0AE, + 0xC83C, 0xC1AF, 0xC83D, 0xA0AF, 0xC83E, 0xA0B0, 0xC83F, 0xA0B1, 0xC840, 0xC1B0, 0xC841, 0xA0B2, 0xC842, 0xA0B3, 0xC843, 0xA0B4, + 0xC844, 0xA0B5, 0xC845, 0xA0B6, 0xC846, 0xA0B7, 0xC847, 0xA0B8, 0xC848, 0xC1B1, 0xC849, 0xC1B2, 0xC84A, 0xA0B9, 0xC84B, 0xA0BA, + 0xC84C, 0xC1B3, 0xC84D, 0xC1B4, 0xC84E, 0xA0BB, 0xC84F, 0xA0BC, 0xC850, 0xA0BD, 0xC851, 0xA0BE, 0xC852, 0xA0BF, 0xC853, 0xA0C0, + 0xC854, 0xC1B5, 0xC855, 0xA0C1, 0xC856, 0xA0C2, 0xC857, 0xA0C3, 0xC858, 0xA0C4, 0xC859, 0xA0C5, 0xC85A, 0xA0C6, 0xC85B, 0xA0C7, + 0xC85C, 0xA0C8, 0xC85D, 0xA0C9, 0xC85E, 0xA0CA, 0xC85F, 0xA0CB, 0xC860, 0xA0CC, 0xC861, 0xA0CD, 0xC862, 0xA0CE, 0xC863, 0xA0CF, + 0xC864, 0xA0D0, 0xC865, 0xA0D1, 0xC866, 0xA0D2, 0xC867, 0xA0D3, 0xC868, 0xA0D4, 0xC869, 0xA0D5, 0xC86A, 0xA0D6, 0xC86B, 0xA0D7, + 0xC86C, 0xA0D8, 0xC86D, 0xA0D9, 0xC86E, 0xA0DA, 0xC86F, 0xA0DB, 0xC870, 0xC1B6, 0xC871, 0xC1B7, 0xC872, 0xA0DC, 0xC873, 0xA0DD, + 0xC874, 0xC1B8, 0xC875, 0xA0DE, 0xC876, 0xA0DF, 0xC877, 0xA0E0, 0xC878, 0xC1B9, 0xC879, 0xA0E1, 0xC87A, 0xC1BA, 0xC87B, 0xA0E2, + 0xC87C, 0xA0E3, 0xC87D, 0xA0E4, 0xC87E, 0xA0E5, 0xC87F, 0xA0E6, 0xC880, 0xC1BB, 0xC881, 0xC1BC, 0xC882, 0xA0E7, 0xC883, 0xC1BD, + 0xC884, 0xA0E8, 0xC885, 0xC1BE, 0xC886, 0xC1BF, 0xC887, 0xC1C0, 0xC888, 0xA0E9, 0xC889, 0xA0EA, 0xC88A, 0xA0EB, 0xC88B, 0xC1C1, + 0xC88C, 0xC1C2, 0xC88D, 0xC1C3, 0xC88E, 0xA0EC, 0xC88F, 0xA0ED, 0xC890, 0xA0EE, 0xC891, 0xA0EF, 0xC892, 0xA0F0, 0xC893, 0xA0F1, + 0xC894, 0xC1C4, 0xC895, 0xA0F2, 0xC896, 0xA0F3, 0xC897, 0xA0F4, 0xC898, 0xA0F5, 0xC899, 0xA0F6, 0xC89A, 0xA0F7, 0xC89B, 0xA0F8, + 0xC89C, 0xA0F9, 0xC89D, 0xC1C5, 0xC89E, 0xA0FA, 0xC89F, 0xC1C6, 0xC8A0, 0xA0FB, 0xC8A1, 0xC1C7, 0xC8A2, 0xA0FC, 0xC8A3, 0xA0FD, + 0xC8A4, 0xA0FE, 0xC8A5, 0xA141, 0xC8A6, 0xA142, 0xC8A7, 0xA143, 0xC8A8, 0xC1C8, 0xC8A9, 0xA144, 0xC8AA, 0xA145, 0xC8AB, 0xA146, + 0xC8AC, 0xA147, 0xC8AD, 0xA148, 0xC8AE, 0xA149, 0xC8AF, 0xA14A, 0xC8B0, 0xA14B, 0xC8B1, 0xA14C, 0xC8B2, 0xA14D, 0xC8B3, 0xA14E, + 0xC8B4, 0xA14F, 0xC8B5, 0xA150, 0xC8B6, 0xA151, 0xC8B7, 0xA152, 0xC8B8, 0xA153, 0xC8B9, 0xA154, 0xC8BA, 0xA155, 0xC8BB, 0xA156, + 0xC8BC, 0xC1C9, 0xC8BD, 0xC1CA, 0xC8BE, 0xA157, 0xC8BF, 0xA158, 0xC8C0, 0xA159, 0xC8C1, 0xA15A, 0xC8C2, 0xA161, 0xC8C3, 0xA162, + 0xC8C4, 0xC1CB, 0xC8C5, 0xA163, 0xC8C6, 0xA164, 0xC8C7, 0xA165, 0xC8C8, 0xC1CC, 0xC8C9, 0xA166, 0xC8CA, 0xA167, 0xC8CB, 0xA168, + 0xC8CC, 0xC1CD, 0xC8CD, 0xA169, 0xC8CE, 0xA16A, 0xC8CF, 0xA16B, 0xC8D0, 0xA16C, 0xC8D1, 0xA16D, 0xC8D2, 0xA16E, 0xC8D3, 0xA16F, + 0xC8D4, 0xC1CE, 0xC8D5, 0xC1CF, 0xC8D6, 0xA170, 0xC8D7, 0xC1D0, 0xC8D8, 0xA171, 0xC8D9, 0xC1D1, 0xC8DA, 0xA172, 0xC8DB, 0xA173, + 0xC8DC, 0xA174, 0xC8DD, 0xA175, 0xC8DE, 0xA176, 0xC8DF, 0xA177, 0xC8E0, 0xC1D2, 0xC8E1, 0xC1D3, 0xC8E2, 0xA178, 0xC8E3, 0xA179, + 0xC8E4, 0xC1D4, 0xC8E5, 0xA17A, 0xC8E6, 0xA181, 0xC8E7, 0xA182, 0xC8E8, 0xA183, 0xC8E9, 0xA184, 0xC8EA, 0xA185, 0xC8EB, 0xA186, + 0xC8EC, 0xA187, 0xC8ED, 0xA188, 0xC8EE, 0xA189, 0xC8EF, 0xA18A, 0xC8F0, 0xA18B, 0xC8F1, 0xA18C, 0xC8F2, 0xA18D, 0xC8F3, 0xA18E, + 0xC8F4, 0xA18F, 0xC8F5, 0xC1D5, 0xC8F6, 0xA190, 0xC8F7, 0xA191, 0xC8F8, 0xA192, 0xC8F9, 0xA193, 0xC8FA, 0xA194, 0xC8FB, 0xA195, + 0xC8FC, 0xC1D6, 0xC8FD, 0xC1D7, 0xC8FE, 0xA196, 0xC8FF, 0xA197, 0xC900, 0xC1D8, 0xC901, 0xA198, 0xC902, 0xA199, 0xC903, 0xA19A, + 0xC904, 0xC1D9, 0xC905, 0xC1DA, 0xC906, 0xC1DB, 0xC907, 0xA19B, 0xC908, 0xA19C, 0xC909, 0xA19D, 0xC90A, 0xA19E, 0xC90B, 0xA19F, + 0xC90C, 0xC1DC, 0xC90D, 0xC1DD, 0xC90E, 0xA1A0, 0xC90F, 0xC1DE, 0xC910, 0xA241, 0xC911, 0xC1DF, 0xC912, 0xA242, 0xC913, 0xA243, + 0xC914, 0xA244, 0xC915, 0xA245, 0xC916, 0xA246, 0xC917, 0xA247, 0xC918, 0xC1E0, 0xC919, 0xA248, 0xC91A, 0xA249, 0xC91B, 0xA24A, + 0xC91C, 0xA24B, 0xC91D, 0xA24C, 0xC91E, 0xA24D, 0xC91F, 0xA24E, 0xC920, 0xA24F, 0xC921, 0xA250, 0xC922, 0xA251, 0xC923, 0xA252, + 0xC924, 0xA253, 0xC925, 0xA254, 0xC926, 0xA255, 0xC927, 0xA256, 0xC928, 0xA257, 0xC929, 0xA258, 0xC92A, 0xA259, 0xC92B, 0xA25A, + 0xC92C, 0xC1E1, 0xC92D, 0xA261, 0xC92E, 0xA262, 0xC92F, 0xA263, 0xC930, 0xA264, 0xC931, 0xA265, 0xC932, 0xA266, 0xC933, 0xA267, + 0xC934, 0xC1E2, 0xC935, 0xA268, 0xC936, 0xA269, 0xC937, 0xA26A, 0xC938, 0xA26B, 0xC939, 0xA26C, 0xC93A, 0xA26D, 0xC93B, 0xA26E, + 0xC93C, 0xA26F, 0xC93D, 0xA270, 0xC93E, 0xA271, 0xC93F, 0xA272, 0xC940, 0xA273, 0xC941, 0xA274, 0xC942, 0xA275, 0xC943, 0xA276, + 0xC944, 0xA277, 0xC945, 0xA278, 0xC946, 0xA279, 0xC947, 0xA27A, 0xC948, 0xA281, 0xC949, 0xA282, 0xC94A, 0xA283, 0xC94B, 0xA284, + 0xC94C, 0xA285, 0xC94D, 0xA286, 0xC94E, 0xA287, 0xC94F, 0xA288, 0xC950, 0xC1E3, 0xC951, 0xC1E4, 0xC952, 0xA289, 0xC953, 0xA28A, + 0xC954, 0xC1E5, 0xC955, 0xA28B, 0xC956, 0xA28C, 0xC957, 0xA28D, 0xC958, 0xC1E6, 0xC959, 0xA28E, 0xC95A, 0xA28F, 0xC95B, 0xA290, + 0xC95C, 0xA291, 0xC95D, 0xA292, 0xC95E, 0xA293, 0xC95F, 0xA294, 0xC960, 0xC1E7, 0xC961, 0xC1E8, 0xC962, 0xA295, 0xC963, 0xC1E9, + 0xC964, 0xA296, 0xC965, 0xA297, 0xC966, 0xA298, 0xC967, 0xA299, 0xC968, 0xA29A, 0xC969, 0xA29B, 0xC96A, 0xA29C, 0xC96B, 0xA29D, + 0xC96C, 0xC1EA, 0xC96D, 0xA29E, 0xC96E, 0xA29F, 0xC96F, 0xA2A0, 0xC970, 0xC1EB, 0xC971, 0xA341, 0xC972, 0xA342, 0xC973, 0xA343, + 0xC974, 0xC1EC, 0xC975, 0xA344, 0xC976, 0xA345, 0xC977, 0xA346, 0xC978, 0xA347, 0xC979, 0xA348, 0xC97A, 0xA349, 0xC97B, 0xA34A, + 0xC97C, 0xC1ED, 0xC97D, 0xA34B, 0xC97E, 0xA34C, 0xC97F, 0xA34D, 0xC980, 0xA34E, 0xC981, 0xA34F, 0xC982, 0xA350, 0xC983, 0xA351, + 0xC984, 0xA352, 0xC985, 0xA353, 0xC986, 0xA354, 0xC987, 0xA355, 0xC988, 0xC1EE, 0xC989, 0xC1EF, 0xC98A, 0xA356, 0xC98B, 0xA357, + 0xC98C, 0xC1F0, 0xC98D, 0xA358, 0xC98E, 0xA359, 0xC98F, 0xA35A, 0xC990, 0xC1F1, 0xC991, 0xA361, 0xC992, 0xA362, 0xC993, 0xA363, + 0xC994, 0xA364, 0xC995, 0xA365, 0xC996, 0xA366, 0xC997, 0xA367, 0xC998, 0xC1F2, 0xC999, 0xC1F3, 0xC99A, 0xA368, 0xC99B, 0xC1F4, + 0xC99C, 0xA369, 0xC99D, 0xC1F5, 0xC99E, 0xA36A, 0xC99F, 0xA36B, 0xC9A0, 0xA36C, 0xC9A1, 0xA36D, 0xC9A2, 0xA36E, 0xC9A3, 0xA36F, + 0xC9A4, 0xA370, 0xC9A5, 0xA371, 0xC9A6, 0xA372, 0xC9A7, 0xA373, 0xC9A8, 0xA374, 0xC9A9, 0xA375, 0xC9AA, 0xA376, 0xC9AB, 0xA377, + 0xC9AC, 0xA378, 0xC9AD, 0xA379, 0xC9AE, 0xA37A, 0xC9AF, 0xA381, 0xC9B0, 0xA382, 0xC9B1, 0xA383, 0xC9B2, 0xA384, 0xC9B3, 0xA385, + 0xC9B4, 0xA386, 0xC9B5, 0xA387, 0xC9B6, 0xA388, 0xC9B7, 0xA389, 0xC9B8, 0xA38A, 0xC9B9, 0xA38B, 0xC9BA, 0xA38C, 0xC9BB, 0xA38D, + 0xC9BC, 0xA38E, 0xC9BD, 0xA38F, 0xC9BE, 0xA390, 0xC9BF, 0xA391, 0xC9C0, 0xC1F6, 0xC9C1, 0xC1F7, 0xC9C2, 0xA392, 0xC9C3, 0xA393, + 0xC9C4, 0xC1F8, 0xC9C5, 0xA394, 0xC9C6, 0xA395, 0xC9C7, 0xC1F9, 0xC9C8, 0xC1FA, 0xC9C9, 0xA396, 0xC9CA, 0xC1FB, 0xC9CB, 0xA397, + 0xC9CC, 0xA398, 0xC9CD, 0xA399, 0xC9CE, 0xA39A, 0xC9CF, 0xA39B, 0xC9D0, 0xC1FC, 0xC9D1, 0xC1FD, 0xC9D2, 0xA39C, 0xC9D3, 0xC1FE, + 0xC9D4, 0xA39D, 0xC9D5, 0xC2A1, 0xC9D6, 0xC2A2, 0xC9D7, 0xA39E, 0xC9D8, 0xA39F, 0xC9D9, 0xC2A3, 0xC9DA, 0xC2A4, 0xC9DB, 0xA3A0, + 0xC9DC, 0xC2A5, 0xC9DD, 0xC2A6, 0xC9DE, 0xA441, 0xC9DF, 0xA442, 0xC9E0, 0xC2A7, 0xC9E1, 0xA443, 0xC9E2, 0xC2A8, 0xC9E3, 0xA444, + 0xC9E4, 0xC2A9, 0xC9E5, 0xA445, 0xC9E6, 0xA446, 0xC9E7, 0xC2AA, 0xC9E8, 0xA447, 0xC9E9, 0xA448, 0xC9EA, 0xA449, 0xC9EB, 0xA44A, + 0xC9EC, 0xC2AB, 0xC9ED, 0xC2AC, 0xC9EE, 0xA44B, 0xC9EF, 0xC2AD, 0xC9F0, 0xC2AE, 0xC9F1, 0xC2AF, 0xC9F2, 0xA44C, 0xC9F3, 0xA44D, + 0xC9F4, 0xA44E, 0xC9F5, 0xA44F, 0xC9F6, 0xA450, 0xC9F7, 0xA451, 0xC9F8, 0xC2B0, 0xC9F9, 0xC2B1, 0xC9FA, 0xA452, 0xC9FB, 0xA453, + 0xC9FC, 0xC2B2, 0xC9FD, 0xA454, 0xC9FE, 0xA455, 0xC9FF, 0xA456, 0xCA00, 0xC2B3, 0xCA01, 0xA457, 0xCA02, 0xA458, 0xCA03, 0xA459, + 0xCA04, 0xA45A, 0xCA05, 0xA461, 0xCA06, 0xA462, 0xCA07, 0xA463, 0xCA08, 0xC2B4, 0xCA09, 0xC2B5, 0xCA0A, 0xA464, 0xCA0B, 0xC2B6, + 0xCA0C, 0xC2B7, 0xCA0D, 0xC2B8, 0xCA0E, 0xA465, 0xCA0F, 0xA466, 0xCA10, 0xA467, 0xCA11, 0xA468, 0xCA12, 0xA469, 0xCA13, 0xA46A, + 0xCA14, 0xC2B9, 0xCA15, 0xA46B, 0xCA16, 0xA46C, 0xCA17, 0xA46D, 0xCA18, 0xC2BA, 0xCA19, 0xA46E, 0xCA1A, 0xA46F, 0xCA1B, 0xA470, + 0xCA1C, 0xA471, 0xCA1D, 0xA472, 0xCA1E, 0xA473, 0xCA1F, 0xA474, 0xCA20, 0xA475, 0xCA21, 0xA476, 0xCA22, 0xA477, 0xCA23, 0xA478, + 0xCA24, 0xA479, 0xCA25, 0xA47A, 0xCA26, 0xA481, 0xCA27, 0xA482, 0xCA28, 0xA483, 0xCA29, 0xC2BB, 0xCA2A, 0xA484, 0xCA2B, 0xA485, + 0xCA2C, 0xA486, 0xCA2D, 0xA487, 0xCA2E, 0xA488, 0xCA2F, 0xA489, 0xCA30, 0xA48A, 0xCA31, 0xA48B, 0xCA32, 0xA48C, 0xCA33, 0xA48D, + 0xCA34, 0xA48E, 0xCA35, 0xA48F, 0xCA36, 0xA490, 0xCA37, 0xA491, 0xCA38, 0xA492, 0xCA39, 0xA493, 0xCA3A, 0xA494, 0xCA3B, 0xA495, + 0xCA3C, 0xA496, 0xCA3D, 0xA497, 0xCA3E, 0xA498, 0xCA3F, 0xA499, 0xCA40, 0xA49A, 0xCA41, 0xA49B, 0xCA42, 0xA49C, 0xCA43, 0xA49D, + 0xCA44, 0xA49E, 0xCA45, 0xA49F, 0xCA46, 0xA4A0, 0xCA47, 0xA541, 0xCA48, 0xA542, 0xCA49, 0xA543, 0xCA4A, 0xA544, 0xCA4B, 0xA545, + 0xCA4C, 0xC2BC, 0xCA4D, 0xC2BD, 0xCA4E, 0xA546, 0xCA4F, 0xA547, 0xCA50, 0xC2BE, 0xCA51, 0xA548, 0xCA52, 0xA549, 0xCA53, 0xA54A, + 0xCA54, 0xC2BF, 0xCA55, 0xA54B, 0xCA56, 0xA54C, 0xCA57, 0xA54D, 0xCA58, 0xA54E, 0xCA59, 0xA54F, 0xCA5A, 0xA550, 0xCA5B, 0xA551, + 0xCA5C, 0xC2C0, 0xCA5D, 0xC2C1, 0xCA5E, 0xA552, 0xCA5F, 0xC2C2, 0xCA60, 0xC2C3, 0xCA61, 0xC2C4, 0xCA62, 0xA553, 0xCA63, 0xA554, + 0xCA64, 0xA555, 0xCA65, 0xA556, 0xCA66, 0xA557, 0xCA67, 0xA558, 0xCA68, 0xC2C5, 0xCA69, 0xA559, 0xCA6A, 0xA55A, 0xCA6B, 0xA561, + 0xCA6C, 0xA562, 0xCA6D, 0xA563, 0xCA6E, 0xA564, 0xCA6F, 0xA565, 0xCA70, 0xA566, 0xCA71, 0xA567, 0xCA72, 0xA568, 0xCA73, 0xA569, + 0xCA74, 0xA56A, 0xCA75, 0xA56B, 0xCA76, 0xA56C, 0xCA77, 0xA56D, 0xCA78, 0xA56E, 0xCA79, 0xA56F, 0xCA7A, 0xA570, 0xCA7B, 0xA571, + 0xCA7C, 0xA572, 0xCA7D, 0xC2C6, 0xCA7E, 0xA573, 0xCA7F, 0xA574, 0xCA80, 0xA575, 0xCA81, 0xA576, 0xCA82, 0xA577, 0xCA83, 0xA578, + 0xCA84, 0xC2C7, 0xCA85, 0xA579, 0xCA86, 0xA57A, 0xCA87, 0xA581, 0xCA88, 0xA582, 0xCA89, 0xA583, 0xCA8A, 0xA584, 0xCA8B, 0xA585, + 0xCA8C, 0xA586, 0xCA8D, 0xA587, 0xCA8E, 0xA588, 0xCA8F, 0xA589, 0xCA90, 0xA58A, 0xCA91, 0xA58B, 0xCA92, 0xA58C, 0xCA93, 0xA58D, + 0xCA94, 0xA58E, 0xCA95, 0xA58F, 0xCA96, 0xA590, 0xCA97, 0xA591, 0xCA98, 0xC2C8, 0xCA99, 0xA592, 0xCA9A, 0xA593, 0xCA9B, 0xA594, + 0xCA9C, 0xA595, 0xCA9D, 0xA596, 0xCA9E, 0xA597, 0xCA9F, 0xA598, 0xCAA0, 0xA599, 0xCAA1, 0xA59A, 0xCAA2, 0xA59B, 0xCAA3, 0xA59C, + 0xCAA4, 0xA59D, 0xCAA5, 0xA59E, 0xCAA6, 0xA59F, 0xCAA7, 0xA5A0, 0xCAA8, 0xA641, 0xCAA9, 0xA642, 0xCAAA, 0xA643, 0xCAAB, 0xA644, + 0xCAAC, 0xA645, 0xCAAD, 0xA646, 0xCAAE, 0xA647, 0xCAAF, 0xA648, 0xCAB0, 0xA649, 0xCAB1, 0xA64A, 0xCAB2, 0xA64B, 0xCAB3, 0xA64C, + 0xCAB4, 0xA64D, 0xCAB5, 0xA64E, 0xCAB6, 0xA64F, 0xCAB7, 0xA650, 0xCAB8, 0xA651, 0xCAB9, 0xA652, 0xCABA, 0xA653, 0xCABB, 0xA654, + 0xCABC, 0xC2C9, 0xCABD, 0xC2CA, 0xCABE, 0xA655, 0xCABF, 0xA656, 0xCAC0, 0xC2CB, 0xCAC1, 0xA657, 0xCAC2, 0xA658, 0xCAC3, 0xA659, + 0xCAC4, 0xC2CC, 0xCAC5, 0xA65A, 0xCAC6, 0xA661, 0xCAC7, 0xA662, 0xCAC8, 0xA663, 0xCAC9, 0xA664, 0xCACA, 0xA665, 0xCACB, 0xA666, + 0xCACC, 0xC2CD, 0xCACD, 0xC2CE, 0xCACE, 0xA667, 0xCACF, 0xC2CF, 0xCAD0, 0xA668, 0xCAD1, 0xC2D0, 0xCAD2, 0xA669, 0xCAD3, 0xC2D1, + 0xCAD4, 0xA66A, 0xCAD5, 0xA66B, 0xCAD6, 0xA66C, 0xCAD7, 0xA66D, 0xCAD8, 0xC2D2, 0xCAD9, 0xC2D3, 0xCADA, 0xA66E, 0xCADB, 0xA66F, + 0xCADC, 0xA670, 0xCADD, 0xA671, 0xCADE, 0xA672, 0xCADF, 0xA673, 0xCAE0, 0xC2D4, 0xCAE1, 0xA674, 0xCAE2, 0xA675, 0xCAE3, 0xA676, + 0xCAE4, 0xA677, 0xCAE5, 0xA678, 0xCAE6, 0xA679, 0xCAE7, 0xA67A, 0xCAE8, 0xA681, 0xCAE9, 0xA682, 0xCAEA, 0xA683, 0xCAEB, 0xA684, + 0xCAEC, 0xC2D5, 0xCAED, 0xA685, 0xCAEE, 0xA686, 0xCAEF, 0xA687, 0xCAF0, 0xA688, 0xCAF1, 0xA689, 0xCAF2, 0xA68A, 0xCAF3, 0xA68B, + 0xCAF4, 0xC2D6, 0xCAF5, 0xA68C, 0xCAF6, 0xA68D, 0xCAF7, 0xA68E, 0xCAF8, 0xA68F, 0xCAF9, 0xA690, 0xCAFA, 0xA691, 0xCAFB, 0xA692, + 0xCAFC, 0xA693, 0xCAFD, 0xA694, 0xCAFE, 0xA695, 0xCAFF, 0xA696, 0xCB00, 0xA697, 0xCB01, 0xA698, 0xCB02, 0xA699, 0xCB03, 0xA69A, + 0xCB04, 0xA69B, 0xCB05, 0xA69C, 0xCB06, 0xA69D, 0xCB07, 0xA69E, 0xCB08, 0xC2D7, 0xCB09, 0xA69F, 0xCB0A, 0xA6A0, 0xCB0B, 0xA741, + 0xCB0C, 0xA742, 0xCB0D, 0xA743, 0xCB0E, 0xA744, 0xCB0F, 0xA745, 0xCB10, 0xC2D8, 0xCB11, 0xA746, 0xCB12, 0xA747, 0xCB13, 0xA748, + 0xCB14, 0xC2D9, 0xCB15, 0xA749, 0xCB16, 0xA74A, 0xCB17, 0xA74B, 0xCB18, 0xC2DA, 0xCB19, 0xA74C, 0xCB1A, 0xA74D, 0xCB1B, 0xA74E, + 0xCB1C, 0xA74F, 0xCB1D, 0xA750, 0xCB1E, 0xA751, 0xCB1F, 0xA752, 0xCB20, 0xC2DB, 0xCB21, 0xC2DC, 0xCB22, 0xA753, 0xCB23, 0xA754, + 0xCB24, 0xA755, 0xCB25, 0xA756, 0xCB26, 0xA757, 0xCB27, 0xA758, 0xCB28, 0xA759, 0xCB29, 0xA75A, 0xCB2A, 0xA761, 0xCB2B, 0xA762, + 0xCB2C, 0xA763, 0xCB2D, 0xA764, 0xCB2E, 0xA765, 0xCB2F, 0xA766, 0xCB30, 0xA767, 0xCB31, 0xA768, 0xCB32, 0xA769, 0xCB33, 0xA76A, + 0xCB34, 0xA76B, 0xCB35, 0xA76C, 0xCB36, 0xA76D, 0xCB37, 0xA76E, 0xCB38, 0xA76F, 0xCB39, 0xA770, 0xCB3A, 0xA771, 0xCB3B, 0xA772, + 0xCB3C, 0xA773, 0xCB3D, 0xA774, 0xCB3E, 0xA775, 0xCB3F, 0xA776, 0xCB40, 0xA777, 0xCB41, 0xC2DD, 0xCB42, 0xA778, 0xCB43, 0xA779, + 0xCB44, 0xA77A, 0xCB45, 0xA781, 0xCB46, 0xA782, 0xCB47, 0xA783, 0xCB48, 0xC2DE, 0xCB49, 0xC2DF, 0xCB4A, 0xA784, 0xCB4B, 0xA785, + 0xCB4C, 0xC2E0, 0xCB4D, 0xA786, 0xCB4E, 0xA787, 0xCB4F, 0xA788, 0xCB50, 0xC2E1, 0xCB51, 0xA789, 0xCB52, 0xA78A, 0xCB53, 0xA78B, + 0xCB54, 0xA78C, 0xCB55, 0xA78D, 0xCB56, 0xA78E, 0xCB57, 0xA78F, 0xCB58, 0xC2E2, 0xCB59, 0xC2E3, 0xCB5A, 0xA790, 0xCB5B, 0xA791, + 0xCB5C, 0xA792, 0xCB5D, 0xC2E4, 0xCB5E, 0xA793, 0xCB5F, 0xA794, 0xCB60, 0xA795, 0xCB61, 0xA796, 0xCB62, 0xA797, 0xCB63, 0xA798, + 0xCB64, 0xC2E5, 0xCB65, 0xA799, 0xCB66, 0xA79A, 0xCB67, 0xA79B, 0xCB68, 0xA79C, 0xCB69, 0xA79D, 0xCB6A, 0xA79E, 0xCB6B, 0xA79F, + 0xCB6C, 0xA7A0, 0xCB6D, 0xA841, 0xCB6E, 0xA842, 0xCB6F, 0xA843, 0xCB70, 0xA844, 0xCB71, 0xA845, 0xCB72, 0xA846, 0xCB73, 0xA847, + 0xCB74, 0xA848, 0xCB75, 0xA849, 0xCB76, 0xA84A, 0xCB77, 0xA84B, 0xCB78, 0xC2E6, 0xCB79, 0xC2E7, 0xCB7A, 0xA84C, 0xCB7B, 0xA84D, + 0xCB7C, 0xA84E, 0xCB7D, 0xA84F, 0xCB7E, 0xA850, 0xCB7F, 0xA851, 0xCB80, 0xA852, 0xCB81, 0xA853, 0xCB82, 0xA854, 0xCB83, 0xA855, + 0xCB84, 0xA856, 0xCB85, 0xA857, 0xCB86, 0xA858, 0xCB87, 0xA859, 0xCB88, 0xA85A, 0xCB89, 0xA861, 0xCB8A, 0xA862, 0xCB8B, 0xA863, + 0xCB8C, 0xA864, 0xCB8D, 0xA865, 0xCB8E, 0xA866, 0xCB8F, 0xA867, 0xCB90, 0xA868, 0xCB91, 0xA869, 0xCB92, 0xA86A, 0xCB93, 0xA86B, + 0xCB94, 0xA86C, 0xCB95, 0xA86D, 0xCB96, 0xA86E, 0xCB97, 0xA86F, 0xCB98, 0xA870, 0xCB99, 0xA871, 0xCB9A, 0xA872, 0xCB9B, 0xA873, + 0xCB9C, 0xC2E8, 0xCB9D, 0xA874, 0xCB9E, 0xA875, 0xCB9F, 0xA876, 0xCBA0, 0xA877, 0xCBA1, 0xA878, 0xCBA2, 0xA879, 0xCBA3, 0xA87A, + 0xCBA4, 0xA881, 0xCBA5, 0xA882, 0xCBA6, 0xA883, 0xCBA7, 0xA884, 0xCBA8, 0xA885, 0xCBA9, 0xA886, 0xCBAA, 0xA887, 0xCBAB, 0xA888, + 0xCBAC, 0xA889, 0xCBAD, 0xA88A, 0xCBAE, 0xA88B, 0xCBAF, 0xA88C, 0xCBB0, 0xA88D, 0xCBB1, 0xA88E, 0xCBB2, 0xA88F, 0xCBB3, 0xA890, + 0xCBB4, 0xA891, 0xCBB5, 0xA892, 0xCBB6, 0xA893, 0xCBB7, 0xA894, 0xCBB8, 0xC2E9, 0xCBB9, 0xA895, 0xCBBA, 0xA896, 0xCBBB, 0xA897, + 0xCBBC, 0xA898, 0xCBBD, 0xA899, 0xCBBE, 0xA89A, 0xCBBF, 0xA89B, 0xCBC0, 0xA89C, 0xCBC1, 0xA89D, 0xCBC2, 0xA89E, 0xCBC3, 0xA89F, + 0xCBC4, 0xA8A0, 0xCBC5, 0xA941, 0xCBC6, 0xA942, 0xCBC7, 0xA943, 0xCBC8, 0xA944, 0xCBC9, 0xA945, 0xCBCA, 0xA946, 0xCBCB, 0xA947, + 0xCBCC, 0xA948, 0xCBCD, 0xA949, 0xCBCE, 0xA94A, 0xCBCF, 0xA94B, 0xCBD0, 0xA94C, 0xCBD1, 0xA94D, 0xCBD2, 0xA94E, 0xCBD3, 0xA94F, + 0xCBD4, 0xC2EA, 0xCBD5, 0xA950, 0xCBD6, 0xA951, 0xCBD7, 0xA952, 0xCBD8, 0xA953, 0xCBD9, 0xA954, 0xCBDA, 0xA955, 0xCBDB, 0xA956, + 0xCBDC, 0xA957, 0xCBDD, 0xA958, 0xCBDE, 0xA959, 0xCBDF, 0xA95A, 0xCBE0, 0xA961, 0xCBE1, 0xA962, 0xCBE2, 0xA963, 0xCBE3, 0xA964, + 0xCBE4, 0xC2EB, 0xCBE5, 0xA965, 0xCBE6, 0xA966, 0xCBE7, 0xC2EC, 0xCBE8, 0xA967, 0xCBE9, 0xC2ED, 0xCBEA, 0xA968, 0xCBEB, 0xA969, + 0xCBEC, 0xA96A, 0xCBED, 0xA96B, 0xCBEE, 0xA96C, 0xCBEF, 0xA96D, 0xCBF0, 0xA96E, 0xCBF1, 0xA96F, 0xCBF2, 0xA970, 0xCBF3, 0xA971, + 0xCBF4, 0xA972, 0xCBF5, 0xA973, 0xCBF6, 0xA974, 0xCBF7, 0xA975, 0xCBF8, 0xA976, 0xCBF9, 0xA977, 0xCBFA, 0xA978, 0xCBFB, 0xA979, + 0xCBFC, 0xA97A, 0xCBFD, 0xA981, 0xCBFE, 0xA982, 0xCBFF, 0xA983, 0xCC00, 0xA984, 0xCC01, 0xA985, 0xCC02, 0xA986, 0xCC03, 0xA987, + 0xCC04, 0xA988, 0xCC05, 0xA989, 0xCC06, 0xA98A, 0xCC07, 0xA98B, 0xCC08, 0xA98C, 0xCC09, 0xA98D, 0xCC0A, 0xA98E, 0xCC0B, 0xA98F, + 0xCC0C, 0xC2EE, 0xCC0D, 0xC2EF, 0xCC0E, 0xA990, 0xCC0F, 0xA991, 0xCC10, 0xC2F0, 0xCC11, 0xA992, 0xCC12, 0xA993, 0xCC13, 0xA994, + 0xCC14, 0xC2F1, 0xCC15, 0xA995, 0xCC16, 0xA996, 0xCC17, 0xA997, 0xCC18, 0xA998, 0xCC19, 0xA999, 0xCC1A, 0xA99A, 0xCC1B, 0xA99B, + 0xCC1C, 0xC2F2, 0xCC1D, 0xC2F3, 0xCC1E, 0xA99C, 0xCC1F, 0xA99D, 0xCC20, 0xA99E, 0xCC21, 0xC2F4, 0xCC22, 0xC2F5, 0xCC23, 0xA99F, + 0xCC24, 0xA9A0, 0xCC25, 0xAA41, 0xCC26, 0xAA42, 0xCC27, 0xC2F6, 0xCC28, 0xC2F7, 0xCC29, 0xC2F8, 0xCC2A, 0xAA43, 0xCC2B, 0xAA44, + 0xCC2C, 0xC2F9, 0xCC2D, 0xAA45, 0xCC2E, 0xC2FA, 0xCC2F, 0xAA46, 0xCC30, 0xC2FB, 0xCC31, 0xAA47, 0xCC32, 0xAA48, 0xCC33, 0xAA49, + 0xCC34, 0xAA4A, 0xCC35, 0xAA4B, 0xCC36, 0xAA4C, 0xCC37, 0xAA4D, 0xCC38, 0xC2FC, 0xCC39, 0xC2FD, 0xCC3A, 0xAA4E, 0xCC3B, 0xC2FE, + 0xCC3C, 0xC3A1, 0xCC3D, 0xC3A2, 0xCC3E, 0xC3A3, 0xCC3F, 0xAA4F, 0xCC40, 0xAA50, 0xCC41, 0xAA51, 0xCC42, 0xAA52, 0xCC43, 0xAA53, + 0xCC44, 0xC3A4, 0xCC45, 0xC3A5, 0xCC46, 0xAA54, 0xCC47, 0xAA55, 0xCC48, 0xC3A6, 0xCC49, 0xAA56, 0xCC4A, 0xAA57, 0xCC4B, 0xAA58, + 0xCC4C, 0xC3A7, 0xCC4D, 0xAA59, 0xCC4E, 0xAA5A, 0xCC4F, 0xAA61, 0xCC50, 0xAA62, 0xCC51, 0xAA63, 0xCC52, 0xAA64, 0xCC53, 0xAA65, + 0xCC54, 0xC3A8, 0xCC55, 0xC3A9, 0xCC56, 0xAA66, 0xCC57, 0xC3AA, 0xCC58, 0xC3AB, 0xCC59, 0xC3AC, 0xCC5A, 0xAA67, 0xCC5B, 0xAA68, + 0xCC5C, 0xAA69, 0xCC5D, 0xAA6A, 0xCC5E, 0xAA6B, 0xCC5F, 0xAA6C, 0xCC60, 0xC3AD, 0xCC61, 0xAA6D, 0xCC62, 0xAA6E, 0xCC63, 0xAA6F, + 0xCC64, 0xC3AE, 0xCC65, 0xAA70, 0xCC66, 0xC3AF, 0xCC67, 0xAA71, 0xCC68, 0xC3B0, 0xCC69, 0xAA72, 0xCC6A, 0xAA73, 0xCC6B, 0xAA74, + 0xCC6C, 0xAA75, 0xCC6D, 0xAA76, 0xCC6E, 0xAA77, 0xCC6F, 0xAA78, 0xCC70, 0xC3B1, 0xCC71, 0xAA79, 0xCC72, 0xAA7A, 0xCC73, 0xAA81, + 0xCC74, 0xAA82, 0xCC75, 0xC3B2, 0xCC76, 0xAA83, 0xCC77, 0xAA84, 0xCC78, 0xAA85, 0xCC79, 0xAA86, 0xCC7A, 0xAA87, 0xCC7B, 0xAA88, + 0xCC7C, 0xAA89, 0xCC7D, 0xAA8A, 0xCC7E, 0xAA8B, 0xCC7F, 0xAA8C, 0xCC80, 0xAA8D, 0xCC81, 0xAA8E, 0xCC82, 0xAA8F, 0xCC83, 0xAA90, + 0xCC84, 0xAA91, 0xCC85, 0xAA92, 0xCC86, 0xAA93, 0xCC87, 0xAA94, 0xCC88, 0xAA95, 0xCC89, 0xAA96, 0xCC8A, 0xAA97, 0xCC8B, 0xAA98, + 0xCC8C, 0xAA99, 0xCC8D, 0xAA9A, 0xCC8E, 0xAA9B, 0xCC8F, 0xAA9C, 0xCC90, 0xAA9D, 0xCC91, 0xAA9E, 0xCC92, 0xAA9F, 0xCC93, 0xAAA0, + 0xCC94, 0xAB41, 0xCC95, 0xAB42, 0xCC96, 0xAB43, 0xCC97, 0xAB44, 0xCC98, 0xC3B3, 0xCC99, 0xC3B4, 0xCC9A, 0xAB45, 0xCC9B, 0xAB46, + 0xCC9C, 0xC3B5, 0xCC9D, 0xAB47, 0xCC9E, 0xAB48, 0xCC9F, 0xAB49, 0xCCA0, 0xC3B6, 0xCCA1, 0xAB4A, 0xCCA2, 0xAB4B, 0xCCA3, 0xAB4C, + 0xCCA4, 0xAB4D, 0xCCA5, 0xAB4E, 0xCCA6, 0xAB4F, 0xCCA7, 0xAB50, 0xCCA8, 0xC3B7, 0xCCA9, 0xC3B8, 0xCCAA, 0xAB51, 0xCCAB, 0xC3B9, + 0xCCAC, 0xC3BA, 0xCCAD, 0xC3BB, 0xCCAE, 0xAB52, 0xCCAF, 0xAB53, 0xCCB0, 0xAB54, 0xCCB1, 0xAB55, 0xCCB2, 0xAB56, 0xCCB3, 0xAB57, + 0xCCB4, 0xC3BC, 0xCCB5, 0xC3BD, 0xCCB6, 0xAB58, 0xCCB7, 0xAB59, 0xCCB8, 0xC3BE, 0xCCB9, 0xAB5A, 0xCCBA, 0xAB61, 0xCCBB, 0xAB62, + 0xCCBC, 0xC3BF, 0xCCBD, 0xAB63, 0xCCBE, 0xAB64, 0xCCBF, 0xAB65, 0xCCC0, 0xAB66, 0xCCC1, 0xAB67, 0xCCC2, 0xAB68, 0xCCC3, 0xAB69, + 0xCCC4, 0xC3C0, 0xCCC5, 0xC3C1, 0xCCC6, 0xAB6A, 0xCCC7, 0xC3C2, 0xCCC8, 0xAB6B, 0xCCC9, 0xC3C3, 0xCCCA, 0xAB6C, 0xCCCB, 0xAB6D, + 0xCCCC, 0xAB6E, 0xCCCD, 0xAB6F, 0xCCCE, 0xAB70, 0xCCCF, 0xAB71, 0xCCD0, 0xC3C4, 0xCCD1, 0xAB72, 0xCCD2, 0xAB73, 0xCCD3, 0xAB74, + 0xCCD4, 0xC3C5, 0xCCD5, 0xAB75, 0xCCD6, 0xAB76, 0xCCD7, 0xAB77, 0xCCD8, 0xAB78, 0xCCD9, 0xAB79, 0xCCDA, 0xAB7A, 0xCCDB, 0xAB81, + 0xCCDC, 0xAB82, 0xCCDD, 0xAB83, 0xCCDE, 0xAB84, 0xCCDF, 0xAB85, 0xCCE0, 0xAB86, 0xCCE1, 0xAB87, 0xCCE2, 0xAB88, 0xCCE3, 0xAB89, + 0xCCE4, 0xC3C6, 0xCCE5, 0xAB8A, 0xCCE6, 0xAB8B, 0xCCE7, 0xAB8C, 0xCCE8, 0xAB8D, 0xCCE9, 0xAB8E, 0xCCEA, 0xAB8F, 0xCCEB, 0xAB90, + 0xCCEC, 0xC3C7, 0xCCED, 0xAB91, 0xCCEE, 0xAB92, 0xCCEF, 0xAB93, 0xCCF0, 0xC3C8, 0xCCF1, 0xAB94, 0xCCF2, 0xAB95, 0xCCF3, 0xAB96, + 0xCCF4, 0xAB97, 0xCCF5, 0xAB98, 0xCCF6, 0xAB99, 0xCCF7, 0xAB9A, 0xCCF8, 0xAB9B, 0xCCF9, 0xAB9C, 0xCCFA, 0xAB9D, 0xCCFB, 0xAB9E, + 0xCCFC, 0xAB9F, 0xCCFD, 0xABA0, 0xCCFE, 0xAC41, 0xCCFF, 0xAC42, 0xCD00, 0xAC43, 0xCD01, 0xC3C9, 0xCD02, 0xAC44, 0xCD03, 0xAC45, + 0xCD04, 0xAC46, 0xCD05, 0xAC47, 0xCD06, 0xAC48, 0xCD07, 0xAC49, 0xCD08, 0xC3CA, 0xCD09, 0xC3CB, 0xCD0A, 0xAC4A, 0xCD0B, 0xAC4B, + 0xCD0C, 0xC3CC, 0xCD0D, 0xAC4C, 0xCD0E, 0xAC4D, 0xCD0F, 0xAC4E, 0xCD10, 0xC3CD, 0xCD11, 0xAC4F, 0xCD12, 0xAC50, 0xCD13, 0xAC51, + 0xCD14, 0xAC52, 0xCD15, 0xAC53, 0xCD16, 0xAC54, 0xCD17, 0xAC55, 0xCD18, 0xC3CE, 0xCD19, 0xC3CF, 0xCD1A, 0xAC56, 0xCD1B, 0xC3D0, + 0xCD1C, 0xAC57, 0xCD1D, 0xC3D1, 0xCD1E, 0xAC58, 0xCD1F, 0xAC59, 0xCD20, 0xAC5A, 0xCD21, 0xAC61, 0xCD22, 0xAC62, 0xCD23, 0xAC63, + 0xCD24, 0xC3D2, 0xCD25, 0xAC64, 0xCD26, 0xAC65, 0xCD27, 0xAC66, 0xCD28, 0xC3D3, 0xCD29, 0xAC67, 0xCD2A, 0xAC68, 0xCD2B, 0xAC69, + 0xCD2C, 0xC3D4, 0xCD2D, 0xAC6A, 0xCD2E, 0xAC6B, 0xCD2F, 0xAC6C, 0xCD30, 0xAC6D, 0xCD31, 0xAC6E, 0xCD32, 0xAC6F, 0xCD33, 0xAC70, + 0xCD34, 0xAC71, 0xCD35, 0xAC72, 0xCD36, 0xAC73, 0xCD37, 0xAC74, 0xCD38, 0xAC75, 0xCD39, 0xC3D5, 0xCD3A, 0xAC76, 0xCD3B, 0xAC77, + 0xCD3C, 0xAC78, 0xCD3D, 0xAC79, 0xCD3E, 0xAC7A, 0xCD3F, 0xAC81, 0xCD40, 0xAC82, 0xCD41, 0xAC83, 0xCD42, 0xAC84, 0xCD43, 0xAC85, + 0xCD44, 0xAC86, 0xCD45, 0xAC87, 0xCD46, 0xAC88, 0xCD47, 0xAC89, 0xCD48, 0xAC8A, 0xCD49, 0xAC8B, 0xCD4A, 0xAC8C, 0xCD4B, 0xAC8D, + 0xCD4C, 0xAC8E, 0xCD4D, 0xAC8F, 0xCD4E, 0xAC90, 0xCD4F, 0xAC91, 0xCD50, 0xAC92, 0xCD51, 0xAC93, 0xCD52, 0xAC94, 0xCD53, 0xAC95, + 0xCD54, 0xAC96, 0xCD55, 0xAC97, 0xCD56, 0xAC98, 0xCD57, 0xAC99, 0xCD58, 0xAC9A, 0xCD59, 0xAC9B, 0xCD5A, 0xAC9C, 0xCD5B, 0xAC9D, + 0xCD5C, 0xC3D6, 0xCD5D, 0xAC9E, 0xCD5E, 0xAC9F, 0xCD5F, 0xACA0, 0xCD60, 0xC3D7, 0xCD61, 0xAD41, 0xCD62, 0xAD42, 0xCD63, 0xAD43, + 0xCD64, 0xC3D8, 0xCD65, 0xAD44, 0xCD66, 0xAD45, 0xCD67, 0xAD46, 0xCD68, 0xAD47, 0xCD69, 0xAD48, 0xCD6A, 0xAD49, 0xCD6B, 0xAD4A, + 0xCD6C, 0xC3D9, 0xCD6D, 0xC3DA, 0xCD6E, 0xAD4B, 0xCD6F, 0xC3DB, 0xCD70, 0xAD4C, 0xCD71, 0xC3DC, 0xCD72, 0xAD4D, 0xCD73, 0xAD4E, + 0xCD74, 0xAD4F, 0xCD75, 0xAD50, 0xCD76, 0xAD51, 0xCD77, 0xAD52, 0xCD78, 0xC3DD, 0xCD79, 0xAD53, 0xCD7A, 0xAD54, 0xCD7B, 0xAD55, + 0xCD7C, 0xAD56, 0xCD7D, 0xAD57, 0xCD7E, 0xAD58, 0xCD7F, 0xAD59, 0xCD80, 0xAD5A, 0xCD81, 0xAD61, 0xCD82, 0xAD62, 0xCD83, 0xAD63, + 0xCD84, 0xAD64, 0xCD85, 0xAD65, 0xCD86, 0xAD66, 0xCD87, 0xAD67, 0xCD88, 0xC3DE, 0xCD89, 0xAD68, 0xCD8A, 0xAD69, 0xCD8B, 0xAD6A, + 0xCD8C, 0xAD6B, 0xCD8D, 0xAD6C, 0xCD8E, 0xAD6D, 0xCD8F, 0xAD6E, 0xCD90, 0xAD6F, 0xCD91, 0xAD70, 0xCD92, 0xAD71, 0xCD93, 0xAD72, + 0xCD94, 0xC3DF, 0xCD95, 0xC3E0, 0xCD96, 0xAD73, 0xCD97, 0xAD74, 0xCD98, 0xC3E1, 0xCD99, 0xAD75, 0xCD9A, 0xAD76, 0xCD9B, 0xAD77, + 0xCD9C, 0xC3E2, 0xCD9D, 0xAD78, 0xCD9E, 0xAD79, 0xCD9F, 0xAD7A, 0xCDA0, 0xAD81, 0xCDA1, 0xAD82, 0xCDA2, 0xAD83, 0xCDA3, 0xAD84, + 0xCDA4, 0xC3E3, 0xCDA5, 0xC3E4, 0xCDA6, 0xAD85, 0xCDA7, 0xC3E5, 0xCDA8, 0xAD86, 0xCDA9, 0xC3E6, 0xCDAA, 0xAD87, 0xCDAB, 0xAD88, + 0xCDAC, 0xAD89, 0xCDAD, 0xAD8A, 0xCDAE, 0xAD8B, 0xCDAF, 0xAD8C, 0xCDB0, 0xC3E7, 0xCDB1, 0xAD8D, 0xCDB2, 0xAD8E, 0xCDB3, 0xAD8F, + 0xCDB4, 0xAD90, 0xCDB5, 0xAD91, 0xCDB6, 0xAD92, 0xCDB7, 0xAD93, 0xCDB8, 0xAD94, 0xCDB9, 0xAD95, 0xCDBA, 0xAD96, 0xCDBB, 0xAD97, + 0xCDBC, 0xAD98, 0xCDBD, 0xAD99, 0xCDBE, 0xAD9A, 0xCDBF, 0xAD9B, 0xCDC0, 0xAD9C, 0xCDC1, 0xAD9D, 0xCDC2, 0xAD9E, 0xCDC3, 0xAD9F, + 0xCDC4, 0xC3E8, 0xCDC5, 0xADA0, 0xCDC6, 0xAE41, 0xCDC7, 0xAE42, 0xCDC8, 0xAE43, 0xCDC9, 0xAE44, 0xCDCA, 0xAE45, 0xCDCB, 0xAE46, + 0xCDCC, 0xC3E9, 0xCDCD, 0xAE47, 0xCDCE, 0xAE48, 0xCDCF, 0xAE49, 0xCDD0, 0xC3EA, 0xCDD1, 0xAE4A, 0xCDD2, 0xAE4B, 0xCDD3, 0xAE4C, + 0xCDD4, 0xAE4D, 0xCDD5, 0xAE4E, 0xCDD6, 0xAE4F, 0xCDD7, 0xAE50, 0xCDD8, 0xAE51, 0xCDD9, 0xAE52, 0xCDDA, 0xAE53, 0xCDDB, 0xAE54, + 0xCDDC, 0xAE55, 0xCDDD, 0xAE56, 0xCDDE, 0xAE57, 0xCDDF, 0xAE58, 0xCDE0, 0xAE59, 0xCDE1, 0xAE5A, 0xCDE2, 0xAE61, 0xCDE3, 0xAE62, + 0xCDE4, 0xAE63, 0xCDE5, 0xAE64, 0xCDE6, 0xAE65, 0xCDE7, 0xAE66, 0xCDE8, 0xC3EB, 0xCDE9, 0xAE67, 0xCDEA, 0xAE68, 0xCDEB, 0xAE69, + 0xCDEC, 0xC3EC, 0xCDED, 0xAE6A, 0xCDEE, 0xAE6B, 0xCDEF, 0xAE6C, 0xCDF0, 0xC3ED, 0xCDF1, 0xAE6D, 0xCDF2, 0xAE6E, 0xCDF3, 0xAE6F, + 0xCDF4, 0xAE70, 0xCDF5, 0xAE71, 0xCDF6, 0xAE72, 0xCDF7, 0xAE73, 0xCDF8, 0xC3EE, 0xCDF9, 0xC3EF, 0xCDFA, 0xAE74, 0xCDFB, 0xC3F0, + 0xCDFC, 0xAE75, 0xCDFD, 0xC3F1, 0xCDFE, 0xAE76, 0xCDFF, 0xAE77, 0xCE00, 0xAE78, 0xCE01, 0xAE79, 0xCE02, 0xAE7A, 0xCE03, 0xAE81, + 0xCE04, 0xC3F2, 0xCE05, 0xAE82, 0xCE06, 0xAE83, 0xCE07, 0xAE84, 0xCE08, 0xC3F3, 0xCE09, 0xAE85, 0xCE0A, 0xAE86, 0xCE0B, 0xAE87, + 0xCE0C, 0xC3F4, 0xCE0D, 0xAE88, 0xCE0E, 0xAE89, 0xCE0F, 0xAE8A, 0xCE10, 0xAE8B, 0xCE11, 0xAE8C, 0xCE12, 0xAE8D, 0xCE13, 0xAE8E, + 0xCE14, 0xC3F5, 0xCE15, 0xAE8F, 0xCE16, 0xAE90, 0xCE17, 0xAE91, 0xCE18, 0xAE92, 0xCE19, 0xC3F6, 0xCE1A, 0xAE93, 0xCE1B, 0xAE94, + 0xCE1C, 0xAE95, 0xCE1D, 0xAE96, 0xCE1E, 0xAE97, 0xCE1F, 0xAE98, 0xCE20, 0xC3F7, 0xCE21, 0xC3F8, 0xCE22, 0xAE99, 0xCE23, 0xAE9A, + 0xCE24, 0xC3F9, 0xCE25, 0xAE9B, 0xCE26, 0xAE9C, 0xCE27, 0xAE9D, 0xCE28, 0xC3FA, 0xCE29, 0xAE9E, 0xCE2A, 0xAE9F, 0xCE2B, 0xAEA0, + 0xCE2C, 0xAF41, 0xCE2D, 0xAF42, 0xCE2E, 0xAF43, 0xCE2F, 0xAF44, 0xCE30, 0xC3FB, 0xCE31, 0xC3FC, 0xCE32, 0xAF45, 0xCE33, 0xC3FD, + 0xCE34, 0xAF46, 0xCE35, 0xC3FE, 0xCE36, 0xAF47, 0xCE37, 0xAF48, 0xCE38, 0xAF49, 0xCE39, 0xAF4A, 0xCE3A, 0xAF4B, 0xCE3B, 0xAF4C, + 0xCE3C, 0xAF4D, 0xCE3D, 0xAF4E, 0xCE3E, 0xAF4F, 0xCE3F, 0xAF50, 0xCE40, 0xAF51, 0xCE41, 0xAF52, 0xCE42, 0xAF53, 0xCE43, 0xAF54, + 0xCE44, 0xAF55, 0xCE45, 0xAF56, 0xCE46, 0xAF57, 0xCE47, 0xAF58, 0xCE48, 0xAF59, 0xCE49, 0xAF5A, 0xCE4A, 0xAF61, 0xCE4B, 0xAF62, + 0xCE4C, 0xAF63, 0xCE4D, 0xAF64, 0xCE4E, 0xAF65, 0xCE4F, 0xAF66, 0xCE50, 0xAF67, 0xCE51, 0xAF68, 0xCE52, 0xAF69, 0xCE53, 0xAF6A, + 0xCE54, 0xAF6B, 0xCE55, 0xAF6C, 0xCE56, 0xAF6D, 0xCE57, 0xAF6E, 0xCE58, 0xC4A1, 0xCE59, 0xC4A2, 0xCE5A, 0xAF6F, 0xCE5B, 0xAF70, + 0xCE5C, 0xC4A3, 0xCE5D, 0xAF71, 0xCE5E, 0xAF72, 0xCE5F, 0xC4A4, 0xCE60, 0xC4A5, 0xCE61, 0xC4A6, 0xCE62, 0xAF73, 0xCE63, 0xAF74, + 0xCE64, 0xAF75, 0xCE65, 0xAF76, 0xCE66, 0xAF77, 0xCE67, 0xAF78, 0xCE68, 0xC4A7, 0xCE69, 0xC4A8, 0xCE6A, 0xAF79, 0xCE6B, 0xC4A9, + 0xCE6C, 0xAF7A, 0xCE6D, 0xC4AA, 0xCE6E, 0xAF81, 0xCE6F, 0xAF82, 0xCE70, 0xAF83, 0xCE71, 0xAF84, 0xCE72, 0xAF85, 0xCE73, 0xAF86, + 0xCE74, 0xC4AB, 0xCE75, 0xC4AC, 0xCE76, 0xAF87, 0xCE77, 0xAF88, 0xCE78, 0xC4AD, 0xCE79, 0xAF89, 0xCE7A, 0xAF8A, 0xCE7B, 0xAF8B, + 0xCE7C, 0xC4AE, 0xCE7D, 0xAF8C, 0xCE7E, 0xAF8D, 0xCE7F, 0xAF8E, 0xCE80, 0xAF8F, 0xCE81, 0xAF90, 0xCE82, 0xAF91, 0xCE83, 0xAF92, + 0xCE84, 0xC4AF, 0xCE85, 0xC4B0, 0xCE86, 0xAF93, 0xCE87, 0xC4B1, 0xCE88, 0xAF94, 0xCE89, 0xC4B2, 0xCE8A, 0xAF95, 0xCE8B, 0xAF96, + 0xCE8C, 0xAF97, 0xCE8D, 0xAF98, 0xCE8E, 0xAF99, 0xCE8F, 0xAF9A, 0xCE90, 0xC4B3, 0xCE91, 0xC4B4, 0xCE92, 0xAF9B, 0xCE93, 0xAF9C, + 0xCE94, 0xC4B5, 0xCE95, 0xAF9D, 0xCE96, 0xAF9E, 0xCE97, 0xAF9F, 0xCE98, 0xC4B6, 0xCE99, 0xAFA0, 0xCE9A, 0xB041, 0xCE9B, 0xB042, + 0xCE9C, 0xB043, 0xCE9D, 0xB044, 0xCE9E, 0xB045, 0xCE9F, 0xB046, 0xCEA0, 0xC4B7, 0xCEA1, 0xC4B8, 0xCEA2, 0xB047, 0xCEA3, 0xC4B9, + 0xCEA4, 0xC4BA, 0xCEA5, 0xC4BB, 0xCEA6, 0xB048, 0xCEA7, 0xB049, 0xCEA8, 0xB04A, 0xCEA9, 0xB04B, 0xCEAA, 0xB04C, 0xCEAB, 0xB04D, + 0xCEAC, 0xC4BC, 0xCEAD, 0xC4BD, 0xCEAE, 0xB04E, 0xCEAF, 0xB04F, 0xCEB0, 0xB050, 0xCEB1, 0xB051, 0xCEB2, 0xB052, 0xCEB3, 0xB053, + 0xCEB4, 0xB054, 0xCEB5, 0xB055, 0xCEB6, 0xB056, 0xCEB7, 0xB057, 0xCEB8, 0xB058, 0xCEB9, 0xB059, 0xCEBA, 0xB05A, 0xCEBB, 0xB061, + 0xCEBC, 0xB062, 0xCEBD, 0xB063, 0xCEBE, 0xB064, 0xCEBF, 0xB065, 0xCEC0, 0xB066, 0xCEC1, 0xC4BE, 0xCEC2, 0xB067, 0xCEC3, 0xB068, + 0xCEC4, 0xB069, 0xCEC5, 0xB06A, 0xCEC6, 0xB06B, 0xCEC7, 0xB06C, 0xCEC8, 0xB06D, 0xCEC9, 0xB06E, 0xCECA, 0xB06F, 0xCECB, 0xB070, + 0xCECC, 0xB071, 0xCECD, 0xB072, 0xCECE, 0xB073, 0xCECF, 0xB074, 0xCED0, 0xB075, 0xCED1, 0xB076, 0xCED2, 0xB077, 0xCED3, 0xB078, + 0xCED4, 0xB079, 0xCED5, 0xB07A, 0xCED6, 0xB081, 0xCED7, 0xB082, 0xCED8, 0xB083, 0xCED9, 0xB084, 0xCEDA, 0xB085, 0xCEDB, 0xB086, + 0xCEDC, 0xB087, 0xCEDD, 0xB088, 0xCEDE, 0xB089, 0xCEDF, 0xB08A, 0xCEE0, 0xB08B, 0xCEE1, 0xB08C, 0xCEE2, 0xB08D, 0xCEE3, 0xB08E, + 0xCEE4, 0xC4BF, 0xCEE5, 0xC4C0, 0xCEE6, 0xB08F, 0xCEE7, 0xB090, 0xCEE8, 0xC4C1, 0xCEE9, 0xB091, 0xCEEA, 0xB092, 0xCEEB, 0xC4C2, + 0xCEEC, 0xC4C3, 0xCEED, 0xB093, 0xCEEE, 0xB094, 0xCEEF, 0xB095, 0xCEF0, 0xB096, 0xCEF1, 0xB097, 0xCEF2, 0xB098, 0xCEF3, 0xB099, + 0xCEF4, 0xC4C4, 0xCEF5, 0xC4C5, 0xCEF6, 0xB09A, 0xCEF7, 0xC4C6, 0xCEF8, 0xC4C7, 0xCEF9, 0xC4C8, 0xCEFA, 0xB09B, 0xCEFB, 0xB09C, + 0xCEFC, 0xB09D, 0xCEFD, 0xB09E, 0xCEFE, 0xB09F, 0xCEFF, 0xB0A0, 0xCF00, 0xC4C9, 0xCF01, 0xC4CA, 0xCF02, 0xB141, 0xCF03, 0xB142, + 0xCF04, 0xC4CB, 0xCF05, 0xB143, 0xCF06, 0xB144, 0xCF07, 0xB145, 0xCF08, 0xC4CC, 0xCF09, 0xB146, 0xCF0A, 0xB147, 0xCF0B, 0xB148, + 0xCF0C, 0xB149, 0xCF0D, 0xB14A, 0xCF0E, 0xB14B, 0xCF0F, 0xB14C, 0xCF10, 0xC4CD, 0xCF11, 0xC4CE, 0xCF12, 0xB14D, 0xCF13, 0xC4CF, + 0xCF14, 0xB14E, 0xCF15, 0xC4D0, 0xCF16, 0xB14F, 0xCF17, 0xB150, 0xCF18, 0xB151, 0xCF19, 0xB152, 0xCF1A, 0xB153, 0xCF1B, 0xB154, + 0xCF1C, 0xC4D1, 0xCF1D, 0xB155, 0xCF1E, 0xB156, 0xCF1F, 0xB157, 0xCF20, 0xC4D2, 0xCF21, 0xB158, 0xCF22, 0xB159, 0xCF23, 0xB15A, + 0xCF24, 0xC4D3, 0xCF25, 0xB161, 0xCF26, 0xB162, 0xCF27, 0xB163, 0xCF28, 0xB164, 0xCF29, 0xB165, 0xCF2A, 0xB166, 0xCF2B, 0xB167, + 0xCF2C, 0xC4D4, 0xCF2D, 0xC4D5, 0xCF2E, 0xB168, 0xCF2F, 0xC4D6, 0xCF30, 0xC4D7, 0xCF31, 0xC4D8, 0xCF32, 0xB169, 0xCF33, 0xB16A, + 0xCF34, 0xB16B, 0xCF35, 0xB16C, 0xCF36, 0xB16D, 0xCF37, 0xB16E, 0xCF38, 0xC4D9, 0xCF39, 0xB16F, 0xCF3A, 0xB170, 0xCF3B, 0xB171, + 0xCF3C, 0xB172, 0xCF3D, 0xB173, 0xCF3E, 0xB174, 0xCF3F, 0xB175, 0xCF40, 0xB176, 0xCF41, 0xB177, 0xCF42, 0xB178, 0xCF43, 0xB179, + 0xCF44, 0xB17A, 0xCF45, 0xB181, 0xCF46, 0xB182, 0xCF47, 0xB183, 0xCF48, 0xB184, 0xCF49, 0xB185, 0xCF4A, 0xB186, 0xCF4B, 0xB187, + 0xCF4C, 0xB188, 0xCF4D, 0xB189, 0xCF4E, 0xB18A, 0xCF4F, 0xB18B, 0xCF50, 0xB18C, 0xCF51, 0xB18D, 0xCF52, 0xB18E, 0xCF53, 0xB18F, + 0xCF54, 0xC4DA, 0xCF55, 0xC4DB, 0xCF56, 0xB190, 0xCF57, 0xB191, 0xCF58, 0xC4DC, 0xCF59, 0xB192, 0xCF5A, 0xB193, 0xCF5B, 0xB194, + 0xCF5C, 0xC4DD, 0xCF5D, 0xB195, 0xCF5E, 0xB196, 0xCF5F, 0xB197, 0xCF60, 0xB198, 0xCF61, 0xB199, 0xCF62, 0xB19A, 0xCF63, 0xB19B, + 0xCF64, 0xC4DE, 0xCF65, 0xC4DF, 0xCF66, 0xB19C, 0xCF67, 0xC4E0, 0xCF68, 0xB19D, 0xCF69, 0xC4E1, 0xCF6A, 0xB19E, 0xCF6B, 0xB19F, + 0xCF6C, 0xB1A0, 0xCF6D, 0xB241, 0xCF6E, 0xB242, 0xCF6F, 0xB243, 0xCF70, 0xC4E2, 0xCF71, 0xC4E3, 0xCF72, 0xB244, 0xCF73, 0xB245, + 0xCF74, 0xC4E4, 0xCF75, 0xB246, 0xCF76, 0xB247, 0xCF77, 0xB248, 0xCF78, 0xC4E5, 0xCF79, 0xB249, 0xCF7A, 0xB24A, 0xCF7B, 0xB24B, + 0xCF7C, 0xB24C, 0xCF7D, 0xB24D, 0xCF7E, 0xB24E, 0xCF7F, 0xB24F, 0xCF80, 0xC4E6, 0xCF81, 0xB250, 0xCF82, 0xB251, 0xCF83, 0xB252, + 0xCF84, 0xB253, 0xCF85, 0xC4E7, 0xCF86, 0xB254, 0xCF87, 0xB255, 0xCF88, 0xB256, 0xCF89, 0xB257, 0xCF8A, 0xB258, 0xCF8B, 0xB259, + 0xCF8C, 0xC4E8, 0xCF8D, 0xB25A, 0xCF8E, 0xB261, 0xCF8F, 0xB262, 0xCF90, 0xB263, 0xCF91, 0xB264, 0xCF92, 0xB265, 0xCF93, 0xB266, + 0xCF94, 0xB267, 0xCF95, 0xB268, 0xCF96, 0xB269, 0xCF97, 0xB26A, 0xCF98, 0xB26B, 0xCF99, 0xB26C, 0xCF9A, 0xB26D, 0xCF9B, 0xB26E, + 0xCF9C, 0xB26F, 0xCF9D, 0xB270, 0xCF9E, 0xB271, 0xCF9F, 0xB272, 0xCFA0, 0xB273, 0xCFA1, 0xC4E9, 0xCFA2, 0xB274, 0xCFA3, 0xB275, + 0xCFA4, 0xB276, 0xCFA5, 0xB277, 0xCFA6, 0xB278, 0xCFA7, 0xB279, 0xCFA8, 0xC4EA, 0xCFA9, 0xB27A, 0xCFAA, 0xB281, 0xCFAB, 0xB282, + 0xCFAC, 0xB283, 0xCFAD, 0xB284, 0xCFAE, 0xB285, 0xCFAF, 0xB286, 0xCFB0, 0xC4EB, 0xCFB1, 0xB287, 0xCFB2, 0xB288, 0xCFB3, 0xB289, + 0xCFB4, 0xB28A, 0xCFB5, 0xB28B, 0xCFB6, 0xB28C, 0xCFB7, 0xB28D, 0xCFB8, 0xB28E, 0xCFB9, 0xB28F, 0xCFBA, 0xB290, 0xCFBB, 0xB291, + 0xCFBC, 0xB292, 0xCFBD, 0xB293, 0xCFBE, 0xB294, 0xCFBF, 0xB295, 0xCFC0, 0xB296, 0xCFC1, 0xB297, 0xCFC2, 0xB298, 0xCFC3, 0xB299, + 0xCFC4, 0xC4EC, 0xCFC5, 0xB29A, 0xCFC6, 0xB29B, 0xCFC7, 0xB29C, 0xCFC8, 0xB29D, 0xCFC9, 0xB29E, 0xCFCA, 0xB29F, 0xCFCB, 0xB2A0, + 0xCFCC, 0xB341, 0xCFCD, 0xB342, 0xCFCE, 0xB343, 0xCFCF, 0xB344, 0xCFD0, 0xB345, 0xCFD1, 0xB346, 0xCFD2, 0xB347, 0xCFD3, 0xB348, + 0xCFD4, 0xB349, 0xCFD5, 0xB34A, 0xCFD6, 0xB34B, 0xCFD7, 0xB34C, 0xCFD8, 0xB34D, 0xCFD9, 0xB34E, 0xCFDA, 0xB34F, 0xCFDB, 0xB350, + 0xCFDC, 0xB351, 0xCFDD, 0xB352, 0xCFDE, 0xB353, 0xCFDF, 0xB354, 0xCFE0, 0xC4ED, 0xCFE1, 0xC4EE, 0xCFE2, 0xB355, 0xCFE3, 0xB356, + 0xCFE4, 0xC4EF, 0xCFE5, 0xB357, 0xCFE6, 0xB358, 0xCFE7, 0xB359, 0xCFE8, 0xC4F0, 0xCFE9, 0xB35A, 0xCFEA, 0xB361, 0xCFEB, 0xB362, + 0xCFEC, 0xB363, 0xCFED, 0xB364, 0xCFEE, 0xB365, 0xCFEF, 0xB366, 0xCFF0, 0xC4F1, 0xCFF1, 0xC4F2, 0xCFF2, 0xB367, 0xCFF3, 0xC4F3, + 0xCFF4, 0xB368, 0xCFF5, 0xC4F4, 0xCFF6, 0xB369, 0xCFF7, 0xB36A, 0xCFF8, 0xB36B, 0xCFF9, 0xB36C, 0xCFFA, 0xB36D, 0xCFFB, 0xB36E, + 0xCFFC, 0xC4F5, 0xCFFD, 0xB36F, 0xCFFE, 0xB370, 0xCFFF, 0xB371, 0xD000, 0xC4F6, 0xD001, 0xB372, 0xD002, 0xB373, 0xD003, 0xB374, + 0xD004, 0xC4F7, 0xD005, 0xB375, 0xD006, 0xB376, 0xD007, 0xB377, 0xD008, 0xB378, 0xD009, 0xB379, 0xD00A, 0xB37A, 0xD00B, 0xB381, + 0xD00C, 0xB382, 0xD00D, 0xB383, 0xD00E, 0xB384, 0xD00F, 0xB385, 0xD010, 0xB386, 0xD011, 0xC4F8, 0xD012, 0xB387, 0xD013, 0xB388, + 0xD014, 0xB389, 0xD015, 0xB38A, 0xD016, 0xB38B, 0xD017, 0xB38C, 0xD018, 0xC4F9, 0xD019, 0xB38D, 0xD01A, 0xB38E, 0xD01B, 0xB38F, + 0xD01C, 0xB390, 0xD01D, 0xB391, 0xD01E, 0xB392, 0xD01F, 0xB393, 0xD020, 0xB394, 0xD021, 0xB395, 0xD022, 0xB396, 0xD023, 0xB397, + 0xD024, 0xB398, 0xD025, 0xB399, 0xD026, 0xB39A, 0xD027, 0xB39B, 0xD028, 0xB39C, 0xD029, 0xB39D, 0xD02A, 0xB39E, 0xD02B, 0xB39F, + 0xD02C, 0xB3A0, 0xD02D, 0xC4FA, 0xD02E, 0xB441, 0xD02F, 0xB442, 0xD030, 0xB443, 0xD031, 0xB444, 0xD032, 0xB445, 0xD033, 0xB446, + 0xD034, 0xC4FB, 0xD035, 0xC4FC, 0xD036, 0xB447, 0xD037, 0xB448, 0xD038, 0xC4FD, 0xD039, 0xB449, 0xD03A, 0xB44A, 0xD03B, 0xB44B, + 0xD03C, 0xC4FE, 0xD03D, 0xB44C, 0xD03E, 0xB44D, 0xD03F, 0xB44E, 0xD040, 0xB44F, 0xD041, 0xB450, 0xD042, 0xB451, 0xD043, 0xB452, + 0xD044, 0xC5A1, 0xD045, 0xC5A2, 0xD046, 0xB453, 0xD047, 0xC5A3, 0xD048, 0xB454, 0xD049, 0xC5A4, 0xD04A, 0xB455, 0xD04B, 0xB456, + 0xD04C, 0xB457, 0xD04D, 0xB458, 0xD04E, 0xB459, 0xD04F, 0xB45A, 0xD050, 0xC5A5, 0xD051, 0xB461, 0xD052, 0xB462, 0xD053, 0xB463, + 0xD054, 0xC5A6, 0xD055, 0xB464, 0xD056, 0xB465, 0xD057, 0xB466, 0xD058, 0xC5A7, 0xD059, 0xB467, 0xD05A, 0xB468, 0xD05B, 0xB469, + 0xD05C, 0xB46A, 0xD05D, 0xB46B, 0xD05E, 0xB46C, 0xD05F, 0xB46D, 0xD060, 0xC5A8, 0xD061, 0xB46E, 0xD062, 0xB46F, 0xD063, 0xB470, + 0xD064, 0xB471, 0xD065, 0xB472, 0xD066, 0xB473, 0xD067, 0xB474, 0xD068, 0xB475, 0xD069, 0xB476, 0xD06A, 0xB477, 0xD06B, 0xB478, + 0xD06C, 0xC5A9, 0xD06D, 0xC5AA, 0xD06E, 0xB479, 0xD06F, 0xB47A, 0xD070, 0xC5AB, 0xD071, 0xB481, 0xD072, 0xB482, 0xD073, 0xB483, + 0xD074, 0xC5AC, 0xD075, 0xB484, 0xD076, 0xB485, 0xD077, 0xB486, 0xD078, 0xB487, 0xD079, 0xB488, 0xD07A, 0xB489, 0xD07B, 0xB48A, + 0xD07C, 0xC5AD, 0xD07D, 0xC5AE, 0xD07E, 0xB48B, 0xD07F, 0xB48C, 0xD080, 0xB48D, 0xD081, 0xC5AF, 0xD082, 0xB48E, 0xD083, 0xB48F, + 0xD084, 0xB490, 0xD085, 0xB491, 0xD086, 0xB492, 0xD087, 0xB493, 0xD088, 0xB494, 0xD089, 0xB495, 0xD08A, 0xB496, 0xD08B, 0xB497, + 0xD08C, 0xB498, 0xD08D, 0xB499, 0xD08E, 0xB49A, 0xD08F, 0xB49B, 0xD090, 0xB49C, 0xD091, 0xB49D, 0xD092, 0xB49E, 0xD093, 0xB49F, + 0xD094, 0xB4A0, 0xD095, 0xB541, 0xD096, 0xB542, 0xD097, 0xB543, 0xD098, 0xB544, 0xD099, 0xB545, 0xD09A, 0xB546, 0xD09B, 0xB547, + 0xD09C, 0xB548, 0xD09D, 0xB549, 0xD09E, 0xB54A, 0xD09F, 0xB54B, 0xD0A0, 0xB54C, 0xD0A1, 0xB54D, 0xD0A2, 0xB54E, 0xD0A3, 0xB54F, + 0xD0A4, 0xC5B0, 0xD0A5, 0xC5B1, 0xD0A6, 0xB550, 0xD0A7, 0xB551, 0xD0A8, 0xC5B2, 0xD0A9, 0xB552, 0xD0AA, 0xB553, 0xD0AB, 0xB554, + 0xD0AC, 0xC5B3, 0xD0AD, 0xB555, 0xD0AE, 0xB556, 0xD0AF, 0xB557, 0xD0B0, 0xB558, 0xD0B1, 0xB559, 0xD0B2, 0xB55A, 0xD0B3, 0xB561, + 0xD0B4, 0xC5B4, 0xD0B5, 0xC5B5, 0xD0B6, 0xB562, 0xD0B7, 0xC5B6, 0xD0B8, 0xB563, 0xD0B9, 0xC5B7, 0xD0BA, 0xB564, 0xD0BB, 0xB565, + 0xD0BC, 0xB566, 0xD0BD, 0xB567, 0xD0BE, 0xB568, 0xD0BF, 0xB569, 0xD0C0, 0xC5B8, 0xD0C1, 0xC5B9, 0xD0C2, 0xB56A, 0xD0C3, 0xB56B, + 0xD0C4, 0xC5BA, 0xD0C5, 0xB56C, 0xD0C6, 0xB56D, 0xD0C7, 0xB56E, 0xD0C8, 0xC5BB, 0xD0C9, 0xC5BC, 0xD0CA, 0xB56F, 0xD0CB, 0xB570, + 0xD0CC, 0xB571, 0xD0CD, 0xB572, 0xD0CE, 0xB573, 0xD0CF, 0xB574, 0xD0D0, 0xC5BD, 0xD0D1, 0xC5BE, 0xD0D2, 0xB575, 0xD0D3, 0xC5BF, + 0xD0D4, 0xC5C0, 0xD0D5, 0xC5C1, 0xD0D6, 0xB576, 0xD0D7, 0xB577, 0xD0D8, 0xB578, 0xD0D9, 0xB579, 0xD0DA, 0xB57A, 0xD0DB, 0xB581, + 0xD0DC, 0xC5C2, 0xD0DD, 0xC5C3, 0xD0DE, 0xB582, 0xD0DF, 0xB583, 0xD0E0, 0xC5C4, 0xD0E1, 0xB584, 0xD0E2, 0xB585, 0xD0E3, 0xB586, + 0xD0E4, 0xC5C5, 0xD0E5, 0xB587, 0xD0E6, 0xB588, 0xD0E7, 0xB589, 0xD0E8, 0xB58A, 0xD0E9, 0xB58B, 0xD0EA, 0xB58C, 0xD0EB, 0xB58D, + 0xD0EC, 0xC5C6, 0xD0ED, 0xC5C7, 0xD0EE, 0xB58E, 0xD0EF, 0xC5C8, 0xD0F0, 0xC5C9, 0xD0F1, 0xC5CA, 0xD0F2, 0xB58F, 0xD0F3, 0xB590, + 0xD0F4, 0xB591, 0xD0F5, 0xB592, 0xD0F6, 0xB593, 0xD0F7, 0xB594, 0xD0F8, 0xC5CB, 0xD0F9, 0xB595, 0xD0FA, 0xB596, 0xD0FB, 0xB597, + 0xD0FC, 0xB598, 0xD0FD, 0xB599, 0xD0FE, 0xB59A, 0xD0FF, 0xB59B, 0xD100, 0xB59C, 0xD101, 0xB59D, 0xD102, 0xB59E, 0xD103, 0xB59F, + 0xD104, 0xB5A0, 0xD105, 0xB641, 0xD106, 0xB642, 0xD107, 0xB643, 0xD108, 0xB644, 0xD109, 0xB645, 0xD10A, 0xB646, 0xD10B, 0xB647, + 0xD10C, 0xB648, 0xD10D, 0xC5CC, 0xD10E, 0xB649, 0xD10F, 0xB64A, 0xD110, 0xB64B, 0xD111, 0xB64C, 0xD112, 0xB64D, 0xD113, 0xB64E, + 0xD114, 0xB64F, 0xD115, 0xB650, 0xD116, 0xB651, 0xD117, 0xB652, 0xD118, 0xB653, 0xD119, 0xB654, 0xD11A, 0xB655, 0xD11B, 0xB656, + 0xD11C, 0xB657, 0xD11D, 0xB658, 0xD11E, 0xB659, 0xD11F, 0xB65A, 0xD120, 0xB661, 0xD121, 0xB662, 0xD122, 0xB663, 0xD123, 0xB664, + 0xD124, 0xB665, 0xD125, 0xB666, 0xD126, 0xB667, 0xD127, 0xB668, 0xD128, 0xB669, 0xD129, 0xB66A, 0xD12A, 0xB66B, 0xD12B, 0xB66C, + 0xD12C, 0xB66D, 0xD12D, 0xB66E, 0xD12E, 0xB66F, 0xD12F, 0xB670, 0xD130, 0xC5CD, 0xD131, 0xC5CE, 0xD132, 0xB671, 0xD133, 0xB672, + 0xD134, 0xC5CF, 0xD135, 0xB673, 0xD136, 0xB674, 0xD137, 0xB675, 0xD138, 0xC5D0, 0xD139, 0xB676, 0xD13A, 0xC5D1, 0xD13B, 0xB677, + 0xD13C, 0xB678, 0xD13D, 0xB679, 0xD13E, 0xB67A, 0xD13F, 0xB681, 0xD140, 0xC5D2, 0xD141, 0xC5D3, 0xD142, 0xB682, 0xD143, 0xC5D4, + 0xD144, 0xC5D5, 0xD145, 0xC5D6, 0xD146, 0xB683, 0xD147, 0xB684, 0xD148, 0xB685, 0xD149, 0xB686, 0xD14A, 0xB687, 0xD14B, 0xB688, + 0xD14C, 0xC5D7, 0xD14D, 0xC5D8, 0xD14E, 0xB689, 0xD14F, 0xB68A, 0xD150, 0xC5D9, 0xD151, 0xB68B, 0xD152, 0xB68C, 0xD153, 0xB68D, + 0xD154, 0xC5DA, 0xD155, 0xB68E, 0xD156, 0xB68F, 0xD157, 0xB690, 0xD158, 0xB691, 0xD159, 0xB692, 0xD15A, 0xB693, 0xD15B, 0xB694, + 0xD15C, 0xC5DB, 0xD15D, 0xC5DC, 0xD15E, 0xB695, 0xD15F, 0xC5DD, 0xD160, 0xB696, 0xD161, 0xC5DE, 0xD162, 0xB697, 0xD163, 0xB698, + 0xD164, 0xB699, 0xD165, 0xB69A, 0xD166, 0xB69B, 0xD167, 0xB69C, 0xD168, 0xC5DF, 0xD169, 0xB69D, 0xD16A, 0xB69E, 0xD16B, 0xB69F, + 0xD16C, 0xC5E0, 0xD16D, 0xB6A0, 0xD16E, 0xB741, 0xD16F, 0xB742, 0xD170, 0xB743, 0xD171, 0xB744, 0xD172, 0xB745, 0xD173, 0xB746, + 0xD174, 0xB747, 0xD175, 0xB748, 0xD176, 0xB749, 0xD177, 0xB74A, 0xD178, 0xB74B, 0xD179, 0xB74C, 0xD17A, 0xB74D, 0xD17B, 0xB74E, + 0xD17C, 0xC5E1, 0xD17D, 0xB74F, 0xD17E, 0xB750, 0xD17F, 0xB751, 0xD180, 0xB752, 0xD181, 0xB753, 0xD182, 0xB754, 0xD183, 0xB755, + 0xD184, 0xC5E2, 0xD185, 0xB756, 0xD186, 0xB757, 0xD187, 0xB758, 0xD188, 0xC5E3, 0xD189, 0xB759, 0xD18A, 0xB75A, 0xD18B, 0xB761, + 0xD18C, 0xB762, 0xD18D, 0xB763, 0xD18E, 0xB764, 0xD18F, 0xB765, 0xD190, 0xB766, 0xD191, 0xB767, 0xD192, 0xB768, 0xD193, 0xB769, + 0xD194, 0xB76A, 0xD195, 0xB76B, 0xD196, 0xB76C, 0xD197, 0xB76D, 0xD198, 0xB76E, 0xD199, 0xB76F, 0xD19A, 0xB770, 0xD19B, 0xB771, + 0xD19C, 0xB772, 0xD19D, 0xB773, 0xD19E, 0xB774, 0xD19F, 0xB775, 0xD1A0, 0xC5E4, 0xD1A1, 0xC5E5, 0xD1A2, 0xB776, 0xD1A3, 0xB777, + 0xD1A4, 0xC5E6, 0xD1A5, 0xB778, 0xD1A6, 0xB779, 0xD1A7, 0xB77A, 0xD1A8, 0xC5E7, 0xD1A9, 0xB781, 0xD1AA, 0xB782, 0xD1AB, 0xB783, + 0xD1AC, 0xB784, 0xD1AD, 0xB785, 0xD1AE, 0xB786, 0xD1AF, 0xB787, 0xD1B0, 0xC5E8, 0xD1B1, 0xC5E9, 0xD1B2, 0xB788, 0xD1B3, 0xC5EA, + 0xD1B4, 0xB789, 0xD1B5, 0xC5EB, 0xD1B6, 0xB78A, 0xD1B7, 0xB78B, 0xD1B8, 0xB78C, 0xD1B9, 0xB78D, 0xD1BA, 0xC5EC, 0xD1BB, 0xB78E, + 0xD1BC, 0xC5ED, 0xD1BD, 0xB78F, 0xD1BE, 0xB790, 0xD1BF, 0xB791, 0xD1C0, 0xC5EE, 0xD1C1, 0xB792, 0xD1C2, 0xB793, 0xD1C3, 0xB794, + 0xD1C4, 0xB795, 0xD1C5, 0xB796, 0xD1C6, 0xB797, 0xD1C7, 0xB798, 0xD1C8, 0xB799, 0xD1C9, 0xB79A, 0xD1CA, 0xB79B, 0xD1CB, 0xB79C, + 0xD1CC, 0xB79D, 0xD1CD, 0xB79E, 0xD1CE, 0xB79F, 0xD1CF, 0xB7A0, 0xD1D0, 0xB841, 0xD1D1, 0xB842, 0xD1D2, 0xB843, 0xD1D3, 0xB844, + 0xD1D4, 0xB845, 0xD1D5, 0xB846, 0xD1D6, 0xB847, 0xD1D7, 0xB848, 0xD1D8, 0xC5EF, 0xD1D9, 0xB849, 0xD1DA, 0xB84A, 0xD1DB, 0xB84B, + 0xD1DC, 0xB84C, 0xD1DD, 0xB84D, 0xD1DE, 0xB84E, 0xD1DF, 0xB84F, 0xD1E0, 0xB850, 0xD1E1, 0xB851, 0xD1E2, 0xB852, 0xD1E3, 0xB853, + 0xD1E4, 0xB854, 0xD1E5, 0xB855, 0xD1E6, 0xB856, 0xD1E7, 0xB857, 0xD1E8, 0xB858, 0xD1E9, 0xB859, 0xD1EA, 0xB85A, 0xD1EB, 0xB861, + 0xD1EC, 0xB862, 0xD1ED, 0xB863, 0xD1EE, 0xB864, 0xD1EF, 0xB865, 0xD1F0, 0xB866, 0xD1F1, 0xB867, 0xD1F2, 0xB868, 0xD1F3, 0xB869, + 0xD1F4, 0xC5F0, 0xD1F5, 0xB86A, 0xD1F6, 0xB86B, 0xD1F7, 0xB86C, 0xD1F8, 0xC5F1, 0xD1F9, 0xB86D, 0xD1FA, 0xB86E, 0xD1FB, 0xB86F, + 0xD1FC, 0xB870, 0xD1FD, 0xB871, 0xD1FE, 0xB872, 0xD1FF, 0xB873, 0xD200, 0xB874, 0xD201, 0xB875, 0xD202, 0xB876, 0xD203, 0xB877, + 0xD204, 0xB878, 0xD205, 0xB879, 0xD206, 0xB87A, 0xD207, 0xC5F2, 0xD208, 0xB881, 0xD209, 0xC5F3, 0xD20A, 0xB882, 0xD20B, 0xB883, + 0xD20C, 0xB884, 0xD20D, 0xB885, 0xD20E, 0xB886, 0xD20F, 0xB887, 0xD210, 0xC5F4, 0xD211, 0xB888, 0xD212, 0xB889, 0xD213, 0xB88A, + 0xD214, 0xB88B, 0xD215, 0xB88C, 0xD216, 0xB88D, 0xD217, 0xB88E, 0xD218, 0xB88F, 0xD219, 0xB890, 0xD21A, 0xB891, 0xD21B, 0xB892, + 0xD21C, 0xB893, 0xD21D, 0xB894, 0xD21E, 0xB895, 0xD21F, 0xB896, 0xD220, 0xB897, 0xD221, 0xB898, 0xD222, 0xB899, 0xD223, 0xB89A, + 0xD224, 0xB89B, 0xD225, 0xB89C, 0xD226, 0xB89D, 0xD227, 0xB89E, 0xD228, 0xB89F, 0xD229, 0xB8A0, 0xD22A, 0xB941, 0xD22B, 0xB942, + 0xD22C, 0xC5F5, 0xD22D, 0xC5F6, 0xD22E, 0xB943, 0xD22F, 0xB944, 0xD230, 0xC5F7, 0xD231, 0xB945, 0xD232, 0xB946, 0xD233, 0xB947, + 0xD234, 0xC5F8, 0xD235, 0xB948, 0xD236, 0xB949, 0xD237, 0xB94A, 0xD238, 0xB94B, 0xD239, 0xB94C, 0xD23A, 0xB94D, 0xD23B, 0xB94E, + 0xD23C, 0xC5F9, 0xD23D, 0xC5FA, 0xD23E, 0xB94F, 0xD23F, 0xC5FB, 0xD240, 0xB950, 0xD241, 0xC5FC, 0xD242, 0xB951, 0xD243, 0xB952, + 0xD244, 0xB953, 0xD245, 0xB954, 0xD246, 0xB955, 0xD247, 0xB956, 0xD248, 0xC5FD, 0xD249, 0xB957, 0xD24A, 0xB958, 0xD24B, 0xB959, + 0xD24C, 0xB95A, 0xD24D, 0xB961, 0xD24E, 0xB962, 0xD24F, 0xB963, 0xD250, 0xB964, 0xD251, 0xB965, 0xD252, 0xB966, 0xD253, 0xB967, + 0xD254, 0xB968, 0xD255, 0xB969, 0xD256, 0xB96A, 0xD257, 0xB96B, 0xD258, 0xB96C, 0xD259, 0xB96D, 0xD25A, 0xB96E, 0xD25B, 0xB96F, + 0xD25C, 0xC5FE, 0xD25D, 0xB970, 0xD25E, 0xB971, 0xD25F, 0xB972, 0xD260, 0xB973, 0xD261, 0xB974, 0xD262, 0xB975, 0xD263, 0xB976, + 0xD264, 0xC6A1, 0xD265, 0xB977, 0xD266, 0xB978, 0xD267, 0xB979, 0xD268, 0xB97A, 0xD269, 0xB981, 0xD26A, 0xB982, 0xD26B, 0xB983, + 0xD26C, 0xB984, 0xD26D, 0xB985, 0xD26E, 0xB986, 0xD26F, 0xB987, 0xD270, 0xB988, 0xD271, 0xB989, 0xD272, 0xB98A, 0xD273, 0xB98B, + 0xD274, 0xB98C, 0xD275, 0xB98D, 0xD276, 0xB98E, 0xD277, 0xB98F, 0xD278, 0xB990, 0xD279, 0xB991, 0xD27A, 0xB992, 0xD27B, 0xB993, + 0xD27C, 0xB994, 0xD27D, 0xB995, 0xD27E, 0xB996, 0xD27F, 0xB997, 0xD280, 0xC6A2, 0xD281, 0xC6A3, 0xD282, 0xB998, 0xD283, 0xB999, + 0xD284, 0xC6A4, 0xD285, 0xB99A, 0xD286, 0xB99B, 0xD287, 0xB99C, 0xD288, 0xC6A5, 0xD289, 0xB99D, 0xD28A, 0xB99E, 0xD28B, 0xB99F, + 0xD28C, 0xB9A0, 0xD28D, 0xBA41, 0xD28E, 0xBA42, 0xD28F, 0xBA43, 0xD290, 0xC6A6, 0xD291, 0xC6A7, 0xD292, 0xBA44, 0xD293, 0xBA45, + 0xD294, 0xBA46, 0xD295, 0xC6A8, 0xD296, 0xBA47, 0xD297, 0xBA48, 0xD298, 0xBA49, 0xD299, 0xBA4A, 0xD29A, 0xBA4B, 0xD29B, 0xBA4C, + 0xD29C, 0xC6A9, 0xD29D, 0xBA4D, 0xD29E, 0xBA4E, 0xD29F, 0xBA4F, 0xD2A0, 0xC6AA, 0xD2A1, 0xBA50, 0xD2A2, 0xBA51, 0xD2A3, 0xBA52, + 0xD2A4, 0xC6AB, 0xD2A5, 0xBA53, 0xD2A6, 0xBA54, 0xD2A7, 0xBA55, 0xD2A8, 0xBA56, 0xD2A9, 0xBA57, 0xD2AA, 0xBA58, 0xD2AB, 0xBA59, + 0xD2AC, 0xC6AC, 0xD2AD, 0xBA5A, 0xD2AE, 0xBA61, 0xD2AF, 0xBA62, 0xD2B0, 0xBA63, 0xD2B1, 0xC6AD, 0xD2B2, 0xBA64, 0xD2B3, 0xBA65, + 0xD2B4, 0xBA66, 0xD2B5, 0xBA67, 0xD2B6, 0xBA68, 0xD2B7, 0xBA69, 0xD2B8, 0xC6AE, 0xD2B9, 0xC6AF, 0xD2BA, 0xBA6A, 0xD2BB, 0xBA6B, + 0xD2BC, 0xC6B0, 0xD2BD, 0xBA6C, 0xD2BE, 0xBA6D, 0xD2BF, 0xC6B1, 0xD2C0, 0xC6B2, 0xD2C1, 0xBA6E, 0xD2C2, 0xC6B3, 0xD2C3, 0xBA6F, + 0xD2C4, 0xBA70, 0xD2C5, 0xBA71, 0xD2C6, 0xBA72, 0xD2C7, 0xBA73, 0xD2C8, 0xC6B4, 0xD2C9, 0xC6B5, 0xD2CA, 0xBA74, 0xD2CB, 0xC6B6, + 0xD2CC, 0xBA75, 0xD2CD, 0xBA76, 0xD2CE, 0xBA77, 0xD2CF, 0xBA78, 0xD2D0, 0xBA79, 0xD2D1, 0xBA7A, 0xD2D2, 0xBA81, 0xD2D3, 0xBA82, + 0xD2D4, 0xC6B7, 0xD2D5, 0xBA83, 0xD2D6, 0xBA84, 0xD2D7, 0xBA85, 0xD2D8, 0xC6B8, 0xD2D9, 0xBA86, 0xD2DA, 0xBA87, 0xD2DB, 0xBA88, + 0xD2DC, 0xC6B9, 0xD2DD, 0xBA89, 0xD2DE, 0xBA8A, 0xD2DF, 0xBA8B, 0xD2E0, 0xBA8C, 0xD2E1, 0xBA8D, 0xD2E2, 0xBA8E, 0xD2E3, 0xBA8F, + 0xD2E4, 0xC6BA, 0xD2E5, 0xC6BB, 0xD2E6, 0xBA90, 0xD2E7, 0xBA91, 0xD2E8, 0xBA92, 0xD2E9, 0xBA93, 0xD2EA, 0xBA94, 0xD2EB, 0xBA95, + 0xD2EC, 0xBA96, 0xD2ED, 0xBA97, 0xD2EE, 0xBA98, 0xD2EF, 0xBA99, 0xD2F0, 0xC6BC, 0xD2F1, 0xC6BD, 0xD2F2, 0xBA9A, 0xD2F3, 0xBA9B, + 0xD2F4, 0xC6BE, 0xD2F5, 0xBA9C, 0xD2F6, 0xBA9D, 0xD2F7, 0xBA9E, 0xD2F8, 0xC6BF, 0xD2F9, 0xBA9F, 0xD2FA, 0xBAA0, 0xD2FB, 0xBB41, + 0xD2FC, 0xBB42, 0xD2FD, 0xBB43, 0xD2FE, 0xBB44, 0xD2FF, 0xBB45, 0xD300, 0xC6C0, 0xD301, 0xC6C1, 0xD302, 0xBB46, 0xD303, 0xC6C2, + 0xD304, 0xBB47, 0xD305, 0xC6C3, 0xD306, 0xBB48, 0xD307, 0xBB49, 0xD308, 0xBB4A, 0xD309, 0xBB4B, 0xD30A, 0xBB4C, 0xD30B, 0xBB4D, + 0xD30C, 0xC6C4, 0xD30D, 0xC6C5, 0xD30E, 0xC6C6, 0xD30F, 0xBB4E, 0xD310, 0xC6C7, 0xD311, 0xBB4F, 0xD312, 0xBB50, 0xD313, 0xBB51, + 0xD314, 0xC6C8, 0xD315, 0xBB52, 0xD316, 0xC6C9, 0xD317, 0xBB53, 0xD318, 0xBB54, 0xD319, 0xBB55, 0xD31A, 0xBB56, 0xD31B, 0xBB57, + 0xD31C, 0xC6CA, 0xD31D, 0xC6CB, 0xD31E, 0xBB58, 0xD31F, 0xC6CC, 0xD320, 0xC6CD, 0xD321, 0xC6CE, 0xD322, 0xBB59, 0xD323, 0xBB5A, + 0xD324, 0xBB61, 0xD325, 0xC6CF, 0xD326, 0xBB62, 0xD327, 0xBB63, 0xD328, 0xC6D0, 0xD329, 0xC6D1, 0xD32A, 0xBB64, 0xD32B, 0xBB65, + 0xD32C, 0xC6D2, 0xD32D, 0xBB66, 0xD32E, 0xBB67, 0xD32F, 0xBB68, 0xD330, 0xC6D3, 0xD331, 0xBB69, 0xD332, 0xBB6A, 0xD333, 0xBB6B, + 0xD334, 0xBB6C, 0xD335, 0xBB6D, 0xD336, 0xBB6E, 0xD337, 0xBB6F, 0xD338, 0xC6D4, 0xD339, 0xC6D5, 0xD33A, 0xBB70, 0xD33B, 0xC6D6, + 0xD33C, 0xC6D7, 0xD33D, 0xC6D8, 0xD33E, 0xBB71, 0xD33F, 0xBB72, 0xD340, 0xBB73, 0xD341, 0xBB74, 0xD342, 0xBB75, 0xD343, 0xBB76, + 0xD344, 0xC6D9, 0xD345, 0xC6DA, 0xD346, 0xBB77, 0xD347, 0xBB78, 0xD348, 0xBB79, 0xD349, 0xBB7A, 0xD34A, 0xBB81, 0xD34B, 0xBB82, + 0xD34C, 0xBB83, 0xD34D, 0xBB84, 0xD34E, 0xBB85, 0xD34F, 0xBB86, 0xD350, 0xBB87, 0xD351, 0xBB88, 0xD352, 0xBB89, 0xD353, 0xBB8A, + 0xD354, 0xBB8B, 0xD355, 0xBB8C, 0xD356, 0xBB8D, 0xD357, 0xBB8E, 0xD358, 0xBB8F, 0xD359, 0xBB90, 0xD35A, 0xBB91, 0xD35B, 0xBB92, + 0xD35C, 0xBB93, 0xD35D, 0xBB94, 0xD35E, 0xBB95, 0xD35F, 0xBB96, 0xD360, 0xBB97, 0xD361, 0xBB98, 0xD362, 0xBB99, 0xD363, 0xBB9A, + 0xD364, 0xBB9B, 0xD365, 0xBB9C, 0xD366, 0xBB9D, 0xD367, 0xBB9E, 0xD368, 0xBB9F, 0xD369, 0xBBA0, 0xD36A, 0xBC41, 0xD36B, 0xBC42, + 0xD36C, 0xBC43, 0xD36D, 0xBC44, 0xD36E, 0xBC45, 0xD36F, 0xBC46, 0xD370, 0xBC47, 0xD371, 0xBC48, 0xD372, 0xBC49, 0xD373, 0xBC4A, + 0xD374, 0xBC4B, 0xD375, 0xBC4C, 0xD376, 0xBC4D, 0xD377, 0xBC4E, 0xD378, 0xBC4F, 0xD379, 0xBC50, 0xD37A, 0xBC51, 0xD37B, 0xBC52, + 0xD37C, 0xC6DB, 0xD37D, 0xC6DC, 0xD37E, 0xBC53, 0xD37F, 0xBC54, 0xD380, 0xC6DD, 0xD381, 0xBC55, 0xD382, 0xBC56, 0xD383, 0xBC57, + 0xD384, 0xC6DE, 0xD385, 0xBC58, 0xD386, 0xBC59, 0xD387, 0xBC5A, 0xD388, 0xBC61, 0xD389, 0xBC62, 0xD38A, 0xBC63, 0xD38B, 0xBC64, + 0xD38C, 0xC6DF, 0xD38D, 0xC6E0, 0xD38E, 0xBC65, 0xD38F, 0xC6E1, 0xD390, 0xC6E2, 0xD391, 0xC6E3, 0xD392, 0xBC66, 0xD393, 0xBC67, + 0xD394, 0xBC68, 0xD395, 0xBC69, 0xD396, 0xBC6A, 0xD397, 0xBC6B, 0xD398, 0xC6E4, 0xD399, 0xC6E5, 0xD39A, 0xBC6C, 0xD39B, 0xBC6D, + 0xD39C, 0xC6E6, 0xD39D, 0xBC6E, 0xD39E, 0xBC6F, 0xD39F, 0xBC70, 0xD3A0, 0xC6E7, 0xD3A1, 0xBC71, 0xD3A2, 0xBC72, 0xD3A3, 0xBC73, + 0xD3A4, 0xBC74, 0xD3A5, 0xBC75, 0xD3A6, 0xBC76, 0xD3A7, 0xBC77, 0xD3A8, 0xC6E8, 0xD3A9, 0xC6E9, 0xD3AA, 0xBC78, 0xD3AB, 0xC6EA, + 0xD3AC, 0xBC79, 0xD3AD, 0xC6EB, 0xD3AE, 0xBC7A, 0xD3AF, 0xBC81, 0xD3B0, 0xBC82, 0xD3B1, 0xBC83, 0xD3B2, 0xBC84, 0xD3B3, 0xBC85, + 0xD3B4, 0xC6EC, 0xD3B5, 0xBC86, 0xD3B6, 0xBC87, 0xD3B7, 0xBC88, 0xD3B8, 0xC6ED, 0xD3B9, 0xBC89, 0xD3BA, 0xBC8A, 0xD3BB, 0xBC8B, + 0xD3BC, 0xC6EE, 0xD3BD, 0xBC8C, 0xD3BE, 0xBC8D, 0xD3BF, 0xBC8E, 0xD3C0, 0xBC8F, 0xD3C1, 0xBC90, 0xD3C2, 0xBC91, 0xD3C3, 0xBC92, + 0xD3C4, 0xC6EF, 0xD3C5, 0xC6F0, 0xD3C6, 0xBC93, 0xD3C7, 0xBC94, 0xD3C8, 0xC6F1, 0xD3C9, 0xC6F2, 0xD3CA, 0xBC95, 0xD3CB, 0xBC96, + 0xD3CC, 0xBC97, 0xD3CD, 0xBC98, 0xD3CE, 0xBC99, 0xD3CF, 0xBC9A, 0xD3D0, 0xC6F3, 0xD3D1, 0xBC9B, 0xD3D2, 0xBC9C, 0xD3D3, 0xBC9D, + 0xD3D4, 0xBC9E, 0xD3D5, 0xBC9F, 0xD3D6, 0xBCA0, 0xD3D7, 0xBD41, 0xD3D8, 0xC6F4, 0xD3D9, 0xBD42, 0xD3DA, 0xBD43, 0xD3DB, 0xBD44, + 0xD3DC, 0xBD45, 0xD3DD, 0xBD46, 0xD3DE, 0xBD47, 0xD3DF, 0xBD48, 0xD3E0, 0xBD49, 0xD3E1, 0xC6F5, 0xD3E2, 0xBD4A, 0xD3E3, 0xC6F6, + 0xD3E4, 0xBD4B, 0xD3E5, 0xBD4C, 0xD3E6, 0xBD4D, 0xD3E7, 0xBD4E, 0xD3E8, 0xBD4F, 0xD3E9, 0xBD50, 0xD3EA, 0xBD51, 0xD3EB, 0xBD52, + 0xD3EC, 0xC6F7, 0xD3ED, 0xC6F8, 0xD3EE, 0xBD53, 0xD3EF, 0xBD54, 0xD3F0, 0xC6F9, 0xD3F1, 0xBD55, 0xD3F2, 0xBD56, 0xD3F3, 0xBD57, + 0xD3F4, 0xC6FA, 0xD3F5, 0xBD58, 0xD3F6, 0xBD59, 0xD3F7, 0xBD5A, 0xD3F8, 0xBD61, 0xD3F9, 0xBD62, 0xD3FA, 0xBD63, 0xD3FB, 0xBD64, + 0xD3FC, 0xC6FB, 0xD3FD, 0xC6FC, 0xD3FE, 0xBD65, 0xD3FF, 0xC6FD, 0xD400, 0xBD66, 0xD401, 0xC6FE, 0xD402, 0xBD67, 0xD403, 0xBD68, + 0xD404, 0xBD69, 0xD405, 0xBD6A, 0xD406, 0xBD6B, 0xD407, 0xBD6C, 0xD408, 0xC7A1, 0xD409, 0xBD6D, 0xD40A, 0xBD6E, 0xD40B, 0xBD6F, + 0xD40C, 0xBD70, 0xD40D, 0xBD71, 0xD40E, 0xBD72, 0xD40F, 0xBD73, 0xD410, 0xBD74, 0xD411, 0xBD75, 0xD412, 0xBD76, 0xD413, 0xBD77, + 0xD414, 0xBD78, 0xD415, 0xBD79, 0xD416, 0xBD7A, 0xD417, 0xBD81, 0xD418, 0xBD82, 0xD419, 0xBD83, 0xD41A, 0xBD84, 0xD41B, 0xBD85, + 0xD41C, 0xBD86, 0xD41D, 0xC7A2, 0xD41E, 0xBD87, 0xD41F, 0xBD88, 0xD420, 0xBD89, 0xD421, 0xBD8A, 0xD422, 0xBD8B, 0xD423, 0xBD8C, + 0xD424, 0xBD8D, 0xD425, 0xBD8E, 0xD426, 0xBD8F, 0xD427, 0xBD90, 0xD428, 0xBD91, 0xD429, 0xBD92, 0xD42A, 0xBD93, 0xD42B, 0xBD94, + 0xD42C, 0xBD95, 0xD42D, 0xBD96, 0xD42E, 0xBD97, 0xD42F, 0xBD98, 0xD430, 0xBD99, 0xD431, 0xBD9A, 0xD432, 0xBD9B, 0xD433, 0xBD9C, + 0xD434, 0xBD9D, 0xD435, 0xBD9E, 0xD436, 0xBD9F, 0xD437, 0xBDA0, 0xD438, 0xBE41, 0xD439, 0xBE42, 0xD43A, 0xBE43, 0xD43B, 0xBE44, + 0xD43C, 0xBE45, 0xD43D, 0xBE46, 0xD43E, 0xBE47, 0xD43F, 0xBE48, 0xD440, 0xC7A3, 0xD441, 0xBE49, 0xD442, 0xBE4A, 0xD443, 0xBE4B, + 0xD444, 0xC7A4, 0xD445, 0xBE4C, 0xD446, 0xBE4D, 0xD447, 0xBE4E, 0xD448, 0xBE4F, 0xD449, 0xBE50, 0xD44A, 0xBE51, 0xD44B, 0xBE52, + 0xD44C, 0xBE53, 0xD44D, 0xBE54, 0xD44E, 0xBE55, 0xD44F, 0xBE56, 0xD450, 0xBE57, 0xD451, 0xBE58, 0xD452, 0xBE59, 0xD453, 0xBE5A, + 0xD454, 0xBE61, 0xD455, 0xBE62, 0xD456, 0xBE63, 0xD457, 0xBE64, 0xD458, 0xBE65, 0xD459, 0xBE66, 0xD45A, 0xBE67, 0xD45B, 0xBE68, + 0xD45C, 0xC7A5, 0xD45D, 0xBE69, 0xD45E, 0xBE6A, 0xD45F, 0xBE6B, 0xD460, 0xC7A6, 0xD461, 0xBE6C, 0xD462, 0xBE6D, 0xD463, 0xBE6E, + 0xD464, 0xC7A7, 0xD465, 0xBE6F, 0xD466, 0xBE70, 0xD467, 0xBE71, 0xD468, 0xBE72, 0xD469, 0xBE73, 0xD46A, 0xBE74, 0xD46B, 0xBE75, + 0xD46C, 0xBE76, 0xD46D, 0xC7A8, 0xD46E, 0xBE77, 0xD46F, 0xC7A9, 0xD470, 0xBE78, 0xD471, 0xBE79, 0xD472, 0xBE7A, 0xD473, 0xBE81, + 0xD474, 0xBE82, 0xD475, 0xBE83, 0xD476, 0xBE84, 0xD477, 0xBE85, 0xD478, 0xC7AA, 0xD479, 0xC7AB, 0xD47A, 0xBE86, 0xD47B, 0xBE87, + 0xD47C, 0xC7AC, 0xD47D, 0xBE88, 0xD47E, 0xBE89, 0xD47F, 0xC7AD, 0xD480, 0xC7AE, 0xD481, 0xBE8A, 0xD482, 0xC7AF, 0xD483, 0xBE8B, + 0xD484, 0xBE8C, 0xD485, 0xBE8D, 0xD486, 0xBE8E, 0xD487, 0xBE8F, 0xD488, 0xC7B0, 0xD489, 0xC7B1, 0xD48A, 0xBE90, 0xD48B, 0xC7B2, + 0xD48C, 0xBE91, 0xD48D, 0xC7B3, 0xD48E, 0xBE92, 0xD48F, 0xBE93, 0xD490, 0xBE94, 0xD491, 0xBE95, 0xD492, 0xBE96, 0xD493, 0xBE97, + 0xD494, 0xC7B4, 0xD495, 0xBE98, 0xD496, 0xBE99, 0xD497, 0xBE9A, 0xD498, 0xBE9B, 0xD499, 0xBE9C, 0xD49A, 0xBE9D, 0xD49B, 0xBE9E, + 0xD49C, 0xBE9F, 0xD49D, 0xBEA0, 0xD49E, 0xBF41, 0xD49F, 0xBF42, 0xD4A0, 0xBF43, 0xD4A1, 0xBF44, 0xD4A2, 0xBF45, 0xD4A3, 0xBF46, + 0xD4A4, 0xBF47, 0xD4A5, 0xBF48, 0xD4A6, 0xBF49, 0xD4A7, 0xBF4A, 0xD4A8, 0xBF4B, 0xD4A9, 0xC7B5, 0xD4AA, 0xBF4C, 0xD4AB, 0xBF4D, + 0xD4AC, 0xBF4E, 0xD4AD, 0xBF4F, 0xD4AE, 0xBF50, 0xD4AF, 0xBF51, 0xD4B0, 0xBF52, 0xD4B1, 0xBF53, 0xD4B2, 0xBF54, 0xD4B3, 0xBF55, + 0xD4B4, 0xBF56, 0xD4B5, 0xBF57, 0xD4B6, 0xBF58, 0xD4B7, 0xBF59, 0xD4B8, 0xBF5A, 0xD4B9, 0xBF61, 0xD4BA, 0xBF62, 0xD4BB, 0xBF63, + 0xD4BC, 0xBF64, 0xD4BD, 0xBF65, 0xD4BE, 0xBF66, 0xD4BF, 0xBF67, 0xD4C0, 0xBF68, 0xD4C1, 0xBF69, 0xD4C2, 0xBF6A, 0xD4C3, 0xBF6B, + 0xD4C4, 0xBF6C, 0xD4C5, 0xBF6D, 0xD4C6, 0xBF6E, 0xD4C7, 0xBF6F, 0xD4C8, 0xBF70, 0xD4C9, 0xBF71, 0xD4CA, 0xBF72, 0xD4CB, 0xBF73, + 0xD4CC, 0xC7B6, 0xD4CD, 0xBF74, 0xD4CE, 0xBF75, 0xD4CF, 0xBF76, 0xD4D0, 0xC7B7, 0xD4D1, 0xBF77, 0xD4D2, 0xBF78, 0xD4D3, 0xBF79, + 0xD4D4, 0xC7B8, 0xD4D5, 0xBF7A, 0xD4D6, 0xBF81, 0xD4D7, 0xBF82, 0xD4D8, 0xBF83, 0xD4D9, 0xBF84, 0xD4DA, 0xBF85, 0xD4DB, 0xBF86, + 0xD4DC, 0xC7B9, 0xD4DD, 0xBF87, 0xD4DE, 0xBF88, 0xD4DF, 0xC7BA, 0xD4E0, 0xBF89, 0xD4E1, 0xBF8A, 0xD4E2, 0xBF8B, 0xD4E3, 0xBF8C, + 0xD4E4, 0xBF8D, 0xD4E5, 0xBF8E, 0xD4E6, 0xBF8F, 0xD4E7, 0xBF90, 0xD4E8, 0xC7BB, 0xD4E9, 0xBF91, 0xD4EA, 0xBF92, 0xD4EB, 0xBF93, + 0xD4EC, 0xC7BC, 0xD4ED, 0xBF94, 0xD4EE, 0xBF95, 0xD4EF, 0xBF96, 0xD4F0, 0xC7BD, 0xD4F1, 0xBF97, 0xD4F2, 0xBF98, 0xD4F3, 0xBF99, + 0xD4F4, 0xBF9A, 0xD4F5, 0xBF9B, 0xD4F6, 0xBF9C, 0xD4F7, 0xBF9D, 0xD4F8, 0xC7BE, 0xD4F9, 0xBF9E, 0xD4FA, 0xBF9F, 0xD4FB, 0xC7BF, + 0xD4FC, 0xBFA0, 0xD4FD, 0xC7C0, 0xD4FE, 0xC041, 0xD4FF, 0xC042, 0xD500, 0xC043, 0xD501, 0xC044, 0xD502, 0xC045, 0xD503, 0xC046, + 0xD504, 0xC7C1, 0xD505, 0xC047, 0xD506, 0xC048, 0xD507, 0xC049, 0xD508, 0xC7C2, 0xD509, 0xC04A, 0xD50A, 0xC04B, 0xD50B, 0xC04C, + 0xD50C, 0xC7C3, 0xD50D, 0xC04D, 0xD50E, 0xC04E, 0xD50F, 0xC04F, 0xD510, 0xC050, 0xD511, 0xC051, 0xD512, 0xC052, 0xD513, 0xC053, + 0xD514, 0xC7C4, 0xD515, 0xC7C5, 0xD516, 0xC054, 0xD517, 0xC7C6, 0xD518, 0xC055, 0xD519, 0xC056, 0xD51A, 0xC057, 0xD51B, 0xC058, + 0xD51C, 0xC059, 0xD51D, 0xC05A, 0xD51E, 0xC061, 0xD51F, 0xC062, 0xD520, 0xC063, 0xD521, 0xC064, 0xD522, 0xC065, 0xD523, 0xC066, + 0xD524, 0xC067, 0xD525, 0xC068, 0xD526, 0xC069, 0xD527, 0xC06A, 0xD528, 0xC06B, 0xD529, 0xC06C, 0xD52A, 0xC06D, 0xD52B, 0xC06E, + 0xD52C, 0xC06F, 0xD52D, 0xC070, 0xD52E, 0xC071, 0xD52F, 0xC072, 0xD530, 0xC073, 0xD531, 0xC074, 0xD532, 0xC075, 0xD533, 0xC076, + 0xD534, 0xC077, 0xD535, 0xC078, 0xD536, 0xC079, 0xD537, 0xC07A, 0xD538, 0xC081, 0xD539, 0xC082, 0xD53A, 0xC083, 0xD53B, 0xC084, + 0xD53C, 0xC7C7, 0xD53D, 0xC7C8, 0xD53E, 0xC085, 0xD53F, 0xC086, 0xD540, 0xC7C9, 0xD541, 0xC087, 0xD542, 0xC088, 0xD543, 0xC089, + 0xD544, 0xC7CA, 0xD545, 0xC08A, 0xD546, 0xC08B, 0xD547, 0xC08C, 0xD548, 0xC08D, 0xD549, 0xC08E, 0xD54A, 0xC08F, 0xD54B, 0xC090, + 0xD54C, 0xC7CB, 0xD54D, 0xC7CC, 0xD54E, 0xC091, 0xD54F, 0xC7CD, 0xD550, 0xC092, 0xD551, 0xC7CE, 0xD552, 0xC093, 0xD553, 0xC094, + 0xD554, 0xC095, 0xD555, 0xC096, 0xD556, 0xC097, 0xD557, 0xC098, 0xD558, 0xC7CF, 0xD559, 0xC7D0, 0xD55A, 0xC099, 0xD55B, 0xC09A, + 0xD55C, 0xC7D1, 0xD55D, 0xC09B, 0xD55E, 0xC09C, 0xD55F, 0xC09D, 0xD560, 0xC7D2, 0xD561, 0xC09E, 0xD562, 0xC09F, 0xD563, 0xC0A0, + 0xD564, 0xC141, 0xD565, 0xC7D3, 0xD566, 0xC142, 0xD567, 0xC143, 0xD568, 0xC7D4, 0xD569, 0xC7D5, 0xD56A, 0xC144, 0xD56B, 0xC7D6, + 0xD56C, 0xC145, 0xD56D, 0xC7D7, 0xD56E, 0xC146, 0xD56F, 0xC147, 0xD570, 0xC148, 0xD571, 0xC149, 0xD572, 0xC14A, 0xD573, 0xC14B, + 0xD574, 0xC7D8, 0xD575, 0xC7D9, 0xD576, 0xC14C, 0xD577, 0xC14D, 0xD578, 0xC7DA, 0xD579, 0xC14E, 0xD57A, 0xC14F, 0xD57B, 0xC150, + 0xD57C, 0xC7DB, 0xD57D, 0xC151, 0xD57E, 0xC152, 0xD57F, 0xC153, 0xD580, 0xC154, 0xD581, 0xC155, 0xD582, 0xC156, 0xD583, 0xC157, + 0xD584, 0xC7DC, 0xD585, 0xC7DD, 0xD586, 0xC158, 0xD587, 0xC7DE, 0xD588, 0xC7DF, 0xD589, 0xC7E0, 0xD58A, 0xC159, 0xD58B, 0xC15A, + 0xD58C, 0xC161, 0xD58D, 0xC162, 0xD58E, 0xC163, 0xD58F, 0xC164, 0xD590, 0xC7E1, 0xD591, 0xC165, 0xD592, 0xC166, 0xD593, 0xC167, + 0xD594, 0xC168, 0xD595, 0xC169, 0xD596, 0xC16A, 0xD597, 0xC16B, 0xD598, 0xC16C, 0xD599, 0xC16D, 0xD59A, 0xC16E, 0xD59B, 0xC16F, + 0xD59C, 0xC170, 0xD59D, 0xC171, 0xD59E, 0xC172, 0xD59F, 0xC173, 0xD5A0, 0xC174, 0xD5A1, 0xC175, 0xD5A2, 0xC176, 0xD5A3, 0xC177, + 0xD5A4, 0xC178, 0xD5A5, 0xC7E2, 0xD5A6, 0xC179, 0xD5A7, 0xC17A, 0xD5A8, 0xC181, 0xD5A9, 0xC182, 0xD5AA, 0xC183, 0xD5AB, 0xC184, + 0xD5AC, 0xC185, 0xD5AD, 0xC186, 0xD5AE, 0xC187, 0xD5AF, 0xC188, 0xD5B0, 0xC189, 0xD5B1, 0xC18A, 0xD5B2, 0xC18B, 0xD5B3, 0xC18C, + 0xD5B4, 0xC18D, 0xD5B5, 0xC18E, 0xD5B6, 0xC18F, 0xD5B7, 0xC190, 0xD5B8, 0xC191, 0xD5B9, 0xC192, 0xD5BA, 0xC193, 0xD5BB, 0xC194, + 0xD5BC, 0xC195, 0xD5BD, 0xC196, 0xD5BE, 0xC197, 0xD5BF, 0xC198, 0xD5C0, 0xC199, 0xD5C1, 0xC19A, 0xD5C2, 0xC19B, 0xD5C3, 0xC19C, + 0xD5C4, 0xC19D, 0xD5C5, 0xC19E, 0xD5C6, 0xC19F, 0xD5C7, 0xC1A0, 0xD5C8, 0xC7E3, 0xD5C9, 0xC7E4, 0xD5CA, 0xC241, 0xD5CB, 0xC242, + 0xD5CC, 0xC7E5, 0xD5CD, 0xC243, 0xD5CE, 0xC244, 0xD5CF, 0xC245, 0xD5D0, 0xC7E6, 0xD5D1, 0xC246, 0xD5D2, 0xC7E7, 0xD5D3, 0xC247, + 0xD5D4, 0xC248, 0xD5D5, 0xC249, 0xD5D6, 0xC24A, 0xD5D7, 0xC24B, 0xD5D8, 0xC7E8, 0xD5D9, 0xC7E9, 0xD5DA, 0xC24C, 0xD5DB, 0xC7EA, + 0xD5DC, 0xC24D, 0xD5DD, 0xC7EB, 0xD5DE, 0xC24E, 0xD5DF, 0xC24F, 0xD5E0, 0xC250, 0xD5E1, 0xC251, 0xD5E2, 0xC252, 0xD5E3, 0xC253, + 0xD5E4, 0xC7EC, 0xD5E5, 0xC7ED, 0xD5E6, 0xC254, 0xD5E7, 0xC255, 0xD5E8, 0xC7EE, 0xD5E9, 0xC256, 0xD5EA, 0xC257, 0xD5EB, 0xC258, + 0xD5EC, 0xC7EF, 0xD5ED, 0xC259, 0xD5EE, 0xC25A, 0xD5EF, 0xC261, 0xD5F0, 0xC262, 0xD5F1, 0xC263, 0xD5F2, 0xC264, 0xD5F3, 0xC265, + 0xD5F4, 0xC7F0, 0xD5F5, 0xC7F1, 0xD5F6, 0xC266, 0xD5F7, 0xC7F2, 0xD5F8, 0xC267, 0xD5F9, 0xC7F3, 0xD5FA, 0xC268, 0xD5FB, 0xC269, + 0xD5FC, 0xC26A, 0xD5FD, 0xC26B, 0xD5FE, 0xC26C, 0xD5FF, 0xC26D, 0xD600, 0xC7F4, 0xD601, 0xC7F5, 0xD602, 0xC26E, 0xD603, 0xC26F, + 0xD604, 0xC7F6, 0xD605, 0xC270, 0xD606, 0xC271, 0xD607, 0xC272, 0xD608, 0xC7F7, 0xD609, 0xC273, 0xD60A, 0xC274, 0xD60B, 0xC275, + 0xD60C, 0xC276, 0xD60D, 0xC277, 0xD60E, 0xC278, 0xD60F, 0xC279, 0xD610, 0xC7F8, 0xD611, 0xC7F9, 0xD612, 0xC27A, 0xD613, 0xC7FA, + 0xD614, 0xC7FB, 0xD615, 0xC7FC, 0xD616, 0xC281, 0xD617, 0xC282, 0xD618, 0xC283, 0xD619, 0xC284, 0xD61A, 0xC285, 0xD61B, 0xC286, + 0xD61C, 0xC7FD, 0xD61D, 0xC287, 0xD61E, 0xC288, 0xD61F, 0xC289, 0xD620, 0xC7FE, 0xD621, 0xC28A, 0xD622, 0xC28B, 0xD623, 0xC28C, + 0xD624, 0xC8A1, 0xD625, 0xC28D, 0xD626, 0xC28E, 0xD627, 0xC28F, 0xD628, 0xC290, 0xD629, 0xC291, 0xD62A, 0xC292, 0xD62B, 0xC293, + 0xD62C, 0xC294, 0xD62D, 0xC8A2, 0xD62E, 0xC295, 0xD62F, 0xC296, 0xD630, 0xC297, 0xD631, 0xC298, 0xD632, 0xC299, 0xD633, 0xC29A, + 0xD634, 0xC29B, 0xD635, 0xC29C, 0xD636, 0xC29D, 0xD637, 0xC29E, 0xD638, 0xC8A3, 0xD639, 0xC8A4, 0xD63A, 0xC29F, 0xD63B, 0xC2A0, + 0xD63C, 0xC8A5, 0xD63D, 0xC341, 0xD63E, 0xC342, 0xD63F, 0xC343, 0xD640, 0xC8A6, 0xD641, 0xC344, 0xD642, 0xC345, 0xD643, 0xC346, + 0xD644, 0xC347, 0xD645, 0xC8A7, 0xD646, 0xC348, 0xD647, 0xC349, 0xD648, 0xC8A8, 0xD649, 0xC8A9, 0xD64A, 0xC34A, 0xD64B, 0xC8AA, + 0xD64C, 0xC34B, 0xD64D, 0xC8AB, 0xD64E, 0xC34C, 0xD64F, 0xC34D, 0xD650, 0xC34E, 0xD651, 0xC8AC, 0xD652, 0xC34F, 0xD653, 0xC350, + 0xD654, 0xC8AD, 0xD655, 0xC8AE, 0xD656, 0xC351, 0xD657, 0xC352, 0xD658, 0xC8AF, 0xD659, 0xC353, 0xD65A, 0xC354, 0xD65B, 0xC355, + 0xD65C, 0xC8B0, 0xD65D, 0xC356, 0xD65E, 0xC357, 0xD65F, 0xC358, 0xD660, 0xC359, 0xD661, 0xC35A, 0xD662, 0xC361, 0xD663, 0xC362, + 0xD664, 0xC363, 0xD665, 0xC364, 0xD666, 0xC365, 0xD667, 0xC8B1, 0xD668, 0xC366, 0xD669, 0xC8B2, 0xD66A, 0xC367, 0xD66B, 0xC368, + 0xD66C, 0xC369, 0xD66D, 0xC36A, 0xD66E, 0xC36B, 0xD66F, 0xC36C, 0xD670, 0xC8B3, 0xD671, 0xC8B4, 0xD672, 0xC36D, 0xD673, 0xC36E, + 0xD674, 0xC8B5, 0xD675, 0xC36F, 0xD676, 0xC370, 0xD677, 0xC371, 0xD678, 0xC372, 0xD679, 0xC373, 0xD67A, 0xC374, 0xD67B, 0xC375, + 0xD67C, 0xC376, 0xD67D, 0xC377, 0xD67E, 0xC378, 0xD67F, 0xC379, 0xD680, 0xC37A, 0xD681, 0xC381, 0xD682, 0xC382, 0xD683, 0xC8B6, + 0xD684, 0xC383, 0xD685, 0xC8B7, 0xD686, 0xC384, 0xD687, 0xC385, 0xD688, 0xC386, 0xD689, 0xC387, 0xD68A, 0xC388, 0xD68B, 0xC389, + 0xD68C, 0xC8B8, 0xD68D, 0xC8B9, 0xD68E, 0xC38A, 0xD68F, 0xC38B, 0xD690, 0xC8BA, 0xD691, 0xC38C, 0xD692, 0xC38D, 0xD693, 0xC38E, + 0xD694, 0xC8BB, 0xD695, 0xC38F, 0xD696, 0xC390, 0xD697, 0xC391, 0xD698, 0xC392, 0xD699, 0xC393, 0xD69A, 0xC394, 0xD69B, 0xC395, + 0xD69C, 0xC396, 0xD69D, 0xC8BC, 0xD69E, 0xC397, 0xD69F, 0xC8BD, 0xD6A0, 0xC398, 0xD6A1, 0xC8BE, 0xD6A2, 0xC399, 0xD6A3, 0xC39A, + 0xD6A4, 0xC39B, 0xD6A5, 0xC39C, 0xD6A6, 0xC39D, 0xD6A7, 0xC39E, 0xD6A8, 0xC8BF, 0xD6A9, 0xC39F, 0xD6AA, 0xC3A0, 0xD6AB, 0xC441, + 0xD6AC, 0xC8C0, 0xD6AD, 0xC442, 0xD6AE, 0xC443, 0xD6AF, 0xC444, 0xD6B0, 0xC8C1, 0xD6B1, 0xC445, 0xD6B2, 0xC446, 0xD6B3, 0xC447, + 0xD6B4, 0xC448, 0xD6B5, 0xC449, 0xD6B6, 0xC44A, 0xD6B7, 0xC44B, 0xD6B8, 0xC44C, 0xD6B9, 0xC8C2, 0xD6BA, 0xC44D, 0xD6BB, 0xC8C3, + 0xD6BC, 0xC44E, 0xD6BD, 0xC44F, 0xD6BE, 0xC450, 0xD6BF, 0xC451, 0xD6C0, 0xC452, 0xD6C1, 0xC453, 0xD6C2, 0xC454, 0xD6C3, 0xC455, + 0xD6C4, 0xC8C4, 0xD6C5, 0xC8C5, 0xD6C6, 0xC456, 0xD6C7, 0xC457, 0xD6C8, 0xC8C6, 0xD6C9, 0xC458, 0xD6CA, 0xC459, 0xD6CB, 0xC45A, + 0xD6CC, 0xC8C7, 0xD6CD, 0xC461, 0xD6CE, 0xC462, 0xD6CF, 0xC463, 0xD6D0, 0xC464, 0xD6D1, 0xC8C8, 0xD6D2, 0xC465, 0xD6D3, 0xC466, + 0xD6D4, 0xC8C9, 0xD6D5, 0xC467, 0xD6D6, 0xC468, 0xD6D7, 0xC8CA, 0xD6D8, 0xC469, 0xD6D9, 0xC8CB, 0xD6DA, 0xC46A, 0xD6DB, 0xC46B, + 0xD6DC, 0xC46C, 0xD6DD, 0xC46D, 0xD6DE, 0xC46E, 0xD6DF, 0xC46F, 0xD6E0, 0xC8CC, 0xD6E1, 0xC470, 0xD6E2, 0xC471, 0xD6E3, 0xC472, + 0xD6E4, 0xC8CD, 0xD6E5, 0xC473, 0xD6E6, 0xC474, 0xD6E7, 0xC475, 0xD6E8, 0xC8CE, 0xD6E9, 0xC476, 0xD6EA, 0xC477, 0xD6EB, 0xC478, + 0xD6EC, 0xC479, 0xD6ED, 0xC47A, 0xD6EE, 0xC481, 0xD6EF, 0xC482, 0xD6F0, 0xC8CF, 0xD6F1, 0xC483, 0xD6F2, 0xC484, 0xD6F3, 0xC485, + 0xD6F4, 0xC486, 0xD6F5, 0xC8D0, 0xD6F6, 0xC487, 0xD6F7, 0xC488, 0xD6F8, 0xC489, 0xD6F9, 0xC48A, 0xD6FA, 0xC48B, 0xD6FB, 0xC48C, + 0xD6FC, 0xC8D1, 0xD6FD, 0xC8D2, 0xD6FE, 0xC48D, 0xD6FF, 0xC48E, 0xD700, 0xC8D3, 0xD701, 0xC48F, 0xD702, 0xC490, 0xD703, 0xC491, + 0xD704, 0xC8D4, 0xD705, 0xC492, 0xD706, 0xC493, 0xD707, 0xC494, 0xD708, 0xC495, 0xD709, 0xC496, 0xD70A, 0xC497, 0xD70B, 0xC498, + 0xD70C, 0xC499, 0xD70D, 0xC49A, 0xD70E, 0xC49B, 0xD70F, 0xC49C, 0xD710, 0xC49D, 0xD711, 0xC8D5, 0xD712, 0xC49E, 0xD713, 0xC49F, + 0xD714, 0xC4A0, 0xD715, 0xC541, 0xD716, 0xC542, 0xD717, 0xC543, 0xD718, 0xC8D6, 0xD719, 0xC8D7, 0xD71A, 0xC544, 0xD71B, 0xC545, + 0xD71C, 0xC8D8, 0xD71D, 0xC546, 0xD71E, 0xC547, 0xD71F, 0xC548, 0xD720, 0xC8D9, 0xD721, 0xC549, 0xD722, 0xC54A, 0xD723, 0xC54B, + 0xD724, 0xC54C, 0xD725, 0xC54D, 0xD726, 0xC54E, 0xD727, 0xC54F, 0xD728, 0xC8DA, 0xD729, 0xC8DB, 0xD72A, 0xC550, 0xD72B, 0xC8DC, + 0xD72C, 0xC551, 0xD72D, 0xC8DD, 0xD72E, 0xC552, 0xD72F, 0xC553, 0xD730, 0xC554, 0xD731, 0xC555, 0xD732, 0xC556, 0xD733, 0xC557, + 0xD734, 0xC8DE, 0xD735, 0xC8DF, 0xD736, 0xC558, 0xD737, 0xC559, 0xD738, 0xC8E0, 0xD739, 0xC55A, 0xD73A, 0xC561, 0xD73B, 0xC562, + 0xD73C, 0xC8E1, 0xD73D, 0xC563, 0xD73E, 0xC564, 0xD73F, 0xC565, 0xD740, 0xC566, 0xD741, 0xC567, 0xD742, 0xC568, 0xD743, 0xC569, + 0xD744, 0xC8E2, 0xD745, 0xC56A, 0xD746, 0xC56B, 0xD747, 0xC8E3, 0xD748, 0xC56C, 0xD749, 0xC8E4, 0xD74A, 0xC56D, 0xD74B, 0xC56E, + 0xD74C, 0xC56F, 0xD74D, 0xC570, 0xD74E, 0xC571, 0xD74F, 0xC572, 0xD750, 0xC8E5, 0xD751, 0xC8E6, 0xD752, 0xC573, 0xD753, 0xC574, + 0xD754, 0xC8E7, 0xD755, 0xC575, 0xD756, 0xC8E8, 0xD757, 0xC8E9, 0xD758, 0xC8EA, 0xD759, 0xC8EB, 0xD75A, 0xC576, 0xD75B, 0xC577, + 0xD75C, 0xC578, 0xD75D, 0xC579, 0xD75E, 0xC57A, 0xD75F, 0xC581, 0xD760, 0xC8EC, 0xD761, 0xC8ED, 0xD762, 0xC582, 0xD763, 0xC8EE, + 0xD764, 0xC583, 0xD765, 0xC8EF, 0xD766, 0xC584, 0xD767, 0xC585, 0xD768, 0xC586, 0xD769, 0xC8F0, 0xD76A, 0xC587, 0xD76B, 0xC588, + 0xD76C, 0xC8F1, 0xD76D, 0xC589, 0xD76E, 0xC58A, 0xD76F, 0xC58B, 0xD770, 0xC8F2, 0xD771, 0xC58C, 0xD772, 0xC58D, 0xD773, 0xC58E, + 0xD774, 0xC8F3, 0xD775, 0xC58F, 0xD776, 0xC590, 0xD777, 0xC591, 0xD778, 0xC592, 0xD779, 0xC593, 0xD77A, 0xC594, 0xD77B, 0xC595, + 0xD77C, 0xC8F4, 0xD77D, 0xC8F5, 0xD77E, 0xC596, 0xD77F, 0xC597, 0xD780, 0xC598, 0xD781, 0xC8F6, 0xD782, 0xC599, 0xD783, 0xC59A, + 0xD784, 0xC59B, 0xD785, 0xC59C, 0xD786, 0xC59D, 0xD787, 0xC59E, 0xD788, 0xC8F7, 0xD789, 0xC8F8, 0xD78A, 0xC59F, 0xD78B, 0xC5A0, + 0xD78C, 0xC8F9, 0xD78D, 0xC641, 0xD78E, 0xC642, 0xD78F, 0xC643, 0xD790, 0xC8FA, 0xD791, 0xC644, 0xD792, 0xC645, 0xD793, 0xC646, + 0xD794, 0xC647, 0xD795, 0xC648, 0xD796, 0xC649, 0xD797, 0xC64A, 0xD798, 0xC8FB, 0xD799, 0xC8FC, 0xD79A, 0xC64B, 0xD79B, 0xC8FD, + 0xD79C, 0xC64C, 0xD79D, 0xC8FE, 0xD79E, 0xC64D, 0xD79F, 0xC64E, 0xD7A0, 0xC64F, 0xD7A1, 0xC650, 0xD7A2, 0xC651, 0xD7A3, 0xC652, + 0xF900, 0xCBD0, 0xF901, 0xCBD6, 0xF902, 0xCBE7, 0xF903, 0xCDCF, 0xF904, 0xCDE8, 0xF905, 0xCEAD, 0xF906, 0xCFFB, 0xF907, 0xD0A2, + 0xF908, 0xD0B8, 0xF909, 0xD0D0, 0xF90A, 0xD0DD, 0xF90B, 0xD1D4, 0xF90C, 0xD1D5, 0xF90D, 0xD1D8, 0xF90E, 0xD1DB, 0xF90F, 0xD1DC, + 0xF910, 0xD1DD, 0xF911, 0xD1DE, 0xF912, 0xD1DF, 0xF913, 0xD1E0, 0xF914, 0xD1E2, 0xF915, 0xD1E3, 0xF916, 0xD1E4, 0xF917, 0xD1E5, + 0xF918, 0xD1E6, 0xF919, 0xD1E8, 0xF91A, 0xD1E9, 0xF91B, 0xD1EA, 0xF91C, 0xD1EB, 0xF91D, 0xD1ED, 0xF91E, 0xD1EF, 0xF91F, 0xD1F0, + 0xF920, 0xD1F2, 0xF921, 0xD1F6, 0xF922, 0xD1FA, 0xF923, 0xD1FC, 0xF924, 0xD1FD, 0xF925, 0xD1FE, 0xF926, 0xD2A2, 0xF927, 0xD2A3, + 0xF928, 0xD2A7, 0xF929, 0xD2A8, 0xF92A, 0xD2A9, 0xF92B, 0xD2AA, 0xF92C, 0xD2AB, 0xF92D, 0xD2AD, 0xF92E, 0xD2B2, 0xF92F, 0xD2BE, + 0xF930, 0xD2C2, 0xF931, 0xD2C3, 0xF932, 0xD2C4, 0xF933, 0xD2C6, 0xF934, 0xD2C7, 0xF935, 0xD2C8, 0xF936, 0xD2C9, 0xF937, 0xD2CA, + 0xF938, 0xD2CB, 0xF939, 0xD2CD, 0xF93A, 0xD2CE, 0xF93B, 0xD2CF, 0xF93C, 0xD2D0, 0xF93D, 0xD2D1, 0xF93E, 0xD2D2, 0xF93F, 0xD2D3, + 0xF940, 0xD2D4, 0xF941, 0xD2D5, 0xF942, 0xD2D6, 0xF943, 0xD2D7, 0xF944, 0xD2D9, 0xF945, 0xD2DA, 0xF946, 0xD2DE, 0xF947, 0xD2DF, + 0xF948, 0xD2E1, 0xF949, 0xD2E2, 0xF94A, 0xD2E4, 0xF94B, 0xD2E5, 0xF94C, 0xD2E6, 0xF94D, 0xD2E7, 0xF94E, 0xD2E8, 0xF94F, 0xD2E9, + 0xF950, 0xD2EA, 0xF951, 0xD2EB, 0xF952, 0xD2F0, 0xF953, 0xD2F1, 0xF954, 0xD2F2, 0xF955, 0xD2F3, 0xF956, 0xD2F4, 0xF957, 0xD2F5, + 0xF958, 0xD2F7, 0xF959, 0xD2F8, 0xF95A, 0xD4E6, 0xF95B, 0xD4FC, 0xF95C, 0xD5A5, 0xF95D, 0xD5AB, 0xF95E, 0xD5AE, 0xF95F, 0xD6B8, + 0xF960, 0xD6CD, 0xF961, 0xD7CB, 0xF962, 0xD7E4, 0xF963, 0xDBC5, 0xF964, 0xDBE4, 0xF965, 0xDCA5, 0xF966, 0xDDA5, 0xF967, 0xDDD5, + 0xF968, 0xDDF4, 0xF969, 0xDEFC, 0xF96A, 0xDEFE, 0xF96B, 0xDFB3, 0xF96C, 0xDFE1, 0xF96D, 0xDFE8, 0xF96E, 0xE0F1, 0xF96F, 0xE1AD, + 0xF970, 0xE1ED, 0xF971, 0xE3F5, 0xF972, 0xE4A1, 0xF973, 0xE4A9, 0xF974, 0xE5AE, 0xF975, 0xE5B1, 0xF976, 0xE5B2, 0xF977, 0xE5B9, + 0xF978, 0xE5BB, 0xF979, 0xE5BC, 0xF97A, 0xE5C4, 0xF97B, 0xE5CE, 0xF97C, 0xE5D0, 0xF97D, 0xE5D2, 0xF97E, 0xE5D6, 0xF97F, 0xE5FA, + 0xF980, 0xE5FB, 0xF981, 0xE5FC, 0xF982, 0xE5FE, 0xF983, 0xE6A1, 0xF984, 0xE6A4, 0xF985, 0xE6A7, 0xF986, 0xE6AD, 0xF987, 0xE6AF, + 0xF988, 0xE6B0, 0xF989, 0xE6B1, 0xF98A, 0xE6B3, 0xF98B, 0xE6B7, 0xF98C, 0xE6B8, 0xF98D, 0xE6BC, 0xF98E, 0xE6C4, 0xF98F, 0xE6C6, + 0xF990, 0xE6C7, 0xF991, 0xE6CA, 0xF992, 0xE6D2, 0xF993, 0xE6D6, 0xF994, 0xE6D9, 0xF995, 0xE6DC, 0xF996, 0xE6DF, 0xF997, 0xE6E1, + 0xF998, 0xE6E4, 0xF999, 0xE6E5, 0xF99A, 0xE6E6, 0xF99B, 0xE6E8, 0xF99C, 0xE6EA, 0xF99D, 0xE6EB, 0xF99E, 0xE6EC, 0xF99F, 0xE6EF, + 0xF9A0, 0xE6F1, 0xF9A1, 0xE6F2, 0xF9A2, 0xE6F5, 0xF9A3, 0xE6F6, 0xF9A4, 0xE6F7, 0xF9A5, 0xE6F9, 0xF9A6, 0xE7A1, 0xF9A7, 0xE7A6, + 0xF9A8, 0xE7A9, 0xF9A9, 0xE7AA, 0xF9AA, 0xE7AC, 0xF9AB, 0xE7AD, 0xF9AC, 0xE7B0, 0xF9AD, 0xE7BF, 0xF9AE, 0xE7C1, 0xF9AF, 0xE7C6, + 0xF9B0, 0xE7C7, 0xF9B1, 0xE7CB, 0xF9B2, 0xE7CD, 0xF9B3, 0xE7CF, 0xF9B4, 0xE7D0, 0xF9B5, 0xE7D3, 0xF9B6, 0xE7DF, 0xF9B7, 0xE7E4, + 0xF9B8, 0xE7E6, 0xF9B9, 0xE7F7, 0xF9BA, 0xE8E7, 0xF9BB, 0xE8E8, 0xF9BC, 0xE8F0, 0xF9BD, 0xE8F1, 0xF9BE, 0xE8F7, 0xF9BF, 0xE8F9, + 0xF9C0, 0xE8FB, 0xF9C1, 0xE8FE, 0xF9C2, 0xE9A7, 0xF9C3, 0xE9AC, 0xF9C4, 0xE9CC, 0xF9C5, 0xE9F7, 0xF9C6, 0xEAC1, 0xF9C7, 0xEAE5, + 0xF9C8, 0xEAF4, 0xF9C9, 0xEAF7, 0xF9CA, 0xEAFC, 0xF9CB, 0xEAFE, 0xF9CC, 0xEBA4, 0xF9CD, 0xEBA7, 0xF9CE, 0xEBA9, 0xF9CF, 0xEBAA, + 0xF9D0, 0xEBBA, 0xF9D1, 0xEBBB, 0xF9D2, 0xEBBD, 0xF9D3, 0xEBC1, 0xF9D4, 0xEBC2, 0xF9D5, 0xEBC6, 0xF9D6, 0xEBC7, 0xF9D7, 0xEBCC, + 0xF9D8, 0xEBCF, 0xF9D9, 0xEBD0, 0xF9DA, 0xEBD1, 0xF9DB, 0xEBD2, 0xF9DC, 0xEBD8, 0xF9DD, 0xECA6, 0xF9DE, 0xECA7, 0xF9DF, 0xECAA, + 0xF9E0, 0xECAF, 0xF9E1, 0xECB0, 0xF9E2, 0xECB1, 0xF9E3, 0xECB2, 0xF9E4, 0xECB5, 0xF9E5, 0xECB8, 0xF9E6, 0xECBA, 0xF9E7, 0xECC0, + 0xF9E8, 0xECC1, 0xF9E9, 0xECC5, 0xF9EA, 0xECC6, 0xF9EB, 0xECC9, 0xF9EC, 0xECCA, 0xF9ED, 0xECD5, 0xF9EE, 0xECDD, 0xF9EF, 0xECDE, + 0xF9F0, 0xECE1, 0xF9F1, 0xECE4, 0xF9F2, 0xECE7, 0xF9F3, 0xECE8, 0xF9F4, 0xECF7, 0xF9F5, 0xECF8, 0xF9F6, 0xECFA, 0xF9F7, 0xEDA1, + 0xF9F8, 0xEDA2, 0xF9F9, 0xEDA3, 0xF9FA, 0xEDEE, 0xF9FB, 0xEEDB, 0xF9FC, 0xF2BD, 0xF9FD, 0xF2FA, 0xF9FE, 0xF3B1, 0xF9FF, 0xF4A7, + 0xFA00, 0xF4EE, 0xFA01, 0xF6F4, 0xFA02, 0xF6F6, 0xFA03, 0xF7B8, 0xFA04, 0xF7C8, 0xFA05, 0xF7D3, 0xFA06, 0xF8DB, 0xFA07, 0xF8F0, + 0xFA08, 0xFAA1, 0xFA09, 0xFAA2, 0xFA0A, 0xFAE6, 0xFA0B, 0xFCA9, 0xFF01, 0xA3A1, 0xFF02, 0xA3A2, 0xFF03, 0xA3A3, 0xFF04, 0xA3A4, + 0xFF05, 0xA3A5, 0xFF06, 0xA3A6, 0xFF07, 0xA3A7, 0xFF08, 0xA3A8, 0xFF09, 0xA3A9, 0xFF0A, 0xA3AA, 0xFF0B, 0xA3AB, 0xFF0C, 0xA3AC, + 0xFF0D, 0xA3AD, 0xFF0E, 0xA3AE, 0xFF0F, 0xA3AF, 0xFF10, 0xA3B0, 0xFF11, 0xA3B1, 0xFF12, 0xA3B2, 0xFF13, 0xA3B3, 0xFF14, 0xA3B4, + 0xFF15, 0xA3B5, 0xFF16, 0xA3B6, 0xFF17, 0xA3B7, 0xFF18, 0xA3B8, 0xFF19, 0xA3B9, 0xFF1A, 0xA3BA, 0xFF1B, 0xA3BB, 0xFF1C, 0xA3BC, + 0xFF1D, 0xA3BD, 0xFF1E, 0xA3BE, 0xFF1F, 0xA3BF, 0xFF20, 0xA3C0, 0xFF21, 0xA3C1, 0xFF22, 0xA3C2, 0xFF23, 0xA3C3, 0xFF24, 0xA3C4, + 0xFF25, 0xA3C5, 0xFF26, 0xA3C6, 0xFF27, 0xA3C7, 0xFF28, 0xA3C8, 0xFF29, 0xA3C9, 0xFF2A, 0xA3CA, 0xFF2B, 0xA3CB, 0xFF2C, 0xA3CC, + 0xFF2D, 0xA3CD, 0xFF2E, 0xA3CE, 0xFF2F, 0xA3CF, 0xFF30, 0xA3D0, 0xFF31, 0xA3D1, 0xFF32, 0xA3D2, 0xFF33, 0xA3D3, 0xFF34, 0xA3D4, + 0xFF35, 0xA3D5, 0xFF36, 0xA3D6, 0xFF37, 0xA3D7, 0xFF38, 0xA3D8, 0xFF39, 0xA3D9, 0xFF3A, 0xA3DA, 0xFF3B, 0xA3DB, 0xFF3C, 0xA1AC, + 0xFF3D, 0xA3DD, 0xFF3E, 0xA3DE, 0xFF3F, 0xA3DF, 0xFF40, 0xA3E0, 0xFF41, 0xA3E1, 0xFF42, 0xA3E2, 0xFF43, 0xA3E3, 0xFF44, 0xA3E4, + 0xFF45, 0xA3E5, 0xFF46, 0xA3E6, 0xFF47, 0xA3E7, 0xFF48, 0xA3E8, 0xFF49, 0xA3E9, 0xFF4A, 0xA3EA, 0xFF4B, 0xA3EB, 0xFF4C, 0xA3EC, + 0xFF4D, 0xA3ED, 0xFF4E, 0xA3EE, 0xFF4F, 0xA3EF, 0xFF50, 0xA3F0, 0xFF51, 0xA3F1, 0xFF52, 0xA3F2, 0xFF53, 0xA3F3, 0xFF54, 0xA3F4, + 0xFF55, 0xA3F5, 0xFF56, 0xA3F6, 0xFF57, 0xA3F7, 0xFF58, 0xA3F8, 0xFF59, 0xA3F9, 0xFF5A, 0xA3FA, 0xFF5B, 0xA3FB, 0xFF5C, 0xA3FC, + 0xFF5D, 0xA3FD, 0xFF5E, 0xA2A6, 0xFFE0, 0xA1CB, 0xFFE1, 0xA1CC, 0xFFE2, 0xA1FE, 0xFFE3, 0xA3FE, 0xFFE5, 0xA1CD, 0xFFE6, 0xA3DC, + 0, 0 +}; + +static const WCHAR oem2uni949[] = { /* Korean --> Unicode pairs */ + 0x8141, 0xAC02, 0x8142, 0xAC03, 0x8143, 0xAC05, 0x8144, 0xAC06, 0x8145, 0xAC0B, 0x8146, 0xAC0C, 0x8147, 0xAC0D, 0x8148, 0xAC0E, + 0x8149, 0xAC0F, 0x814A, 0xAC18, 0x814B, 0xAC1E, 0x814C, 0xAC1F, 0x814D, 0xAC21, 0x814E, 0xAC22, 0x814F, 0xAC23, 0x8150, 0xAC25, + 0x8151, 0xAC26, 0x8152, 0xAC27, 0x8153, 0xAC28, 0x8154, 0xAC29, 0x8155, 0xAC2A, 0x8156, 0xAC2B, 0x8157, 0xAC2E, 0x8158, 0xAC32, + 0x8159, 0xAC33, 0x815A, 0xAC34, 0x8161, 0xAC35, 0x8162, 0xAC36, 0x8163, 0xAC37, 0x8164, 0xAC3A, 0x8165, 0xAC3B, 0x8166, 0xAC3D, + 0x8167, 0xAC3E, 0x8168, 0xAC3F, 0x8169, 0xAC41, 0x816A, 0xAC42, 0x816B, 0xAC43, 0x816C, 0xAC44, 0x816D, 0xAC45, 0x816E, 0xAC46, + 0x816F, 0xAC47, 0x8170, 0xAC48, 0x8171, 0xAC49, 0x8172, 0xAC4A, 0x8173, 0xAC4C, 0x8174, 0xAC4E, 0x8175, 0xAC4F, 0x8176, 0xAC50, + 0x8177, 0xAC51, 0x8178, 0xAC52, 0x8179, 0xAC53, 0x817A, 0xAC55, 0x8181, 0xAC56, 0x8182, 0xAC57, 0x8183, 0xAC59, 0x8184, 0xAC5A, + 0x8185, 0xAC5B, 0x8186, 0xAC5D, 0x8187, 0xAC5E, 0x8188, 0xAC5F, 0x8189, 0xAC60, 0x818A, 0xAC61, 0x818B, 0xAC62, 0x818C, 0xAC63, + 0x818D, 0xAC64, 0x818E, 0xAC65, 0x818F, 0xAC66, 0x8190, 0xAC67, 0x8191, 0xAC68, 0x8192, 0xAC69, 0x8193, 0xAC6A, 0x8194, 0xAC6B, + 0x8195, 0xAC6C, 0x8196, 0xAC6D, 0x8197, 0xAC6E, 0x8198, 0xAC6F, 0x8199, 0xAC72, 0x819A, 0xAC73, 0x819B, 0xAC75, 0x819C, 0xAC76, + 0x819D, 0xAC79, 0x819E, 0xAC7B, 0x819F, 0xAC7C, 0x81A0, 0xAC7D, 0x81A1, 0xAC7E, 0x81A2, 0xAC7F, 0x81A3, 0xAC82, 0x81A4, 0xAC87, + 0x81A5, 0xAC88, 0x81A6, 0xAC8D, 0x81A7, 0xAC8E, 0x81A8, 0xAC8F, 0x81A9, 0xAC91, 0x81AA, 0xAC92, 0x81AB, 0xAC93, 0x81AC, 0xAC95, + 0x81AD, 0xAC96, 0x81AE, 0xAC97, 0x81AF, 0xAC98, 0x81B0, 0xAC99, 0x81B1, 0xAC9A, 0x81B2, 0xAC9B, 0x81B3, 0xAC9E, 0x81B4, 0xACA2, + 0x81B5, 0xACA3, 0x81B6, 0xACA4, 0x81B7, 0xACA5, 0x81B8, 0xACA6, 0x81B9, 0xACA7, 0x81BA, 0xACAB, 0x81BB, 0xACAD, 0x81BC, 0xACAE, + 0x81BD, 0xACB1, 0x81BE, 0xACB2, 0x81BF, 0xACB3, 0x81C0, 0xACB4, 0x81C1, 0xACB5, 0x81C2, 0xACB6, 0x81C3, 0xACB7, 0x81C4, 0xACBA, + 0x81C5, 0xACBE, 0x81C6, 0xACBF, 0x81C7, 0xACC0, 0x81C8, 0xACC2, 0x81C9, 0xACC3, 0x81CA, 0xACC5, 0x81CB, 0xACC6, 0x81CC, 0xACC7, + 0x81CD, 0xACC9, 0x81CE, 0xACCA, 0x81CF, 0xACCB, 0x81D0, 0xACCD, 0x81D1, 0xACCE, 0x81D2, 0xACCF, 0x81D3, 0xACD0, 0x81D4, 0xACD1, + 0x81D5, 0xACD2, 0x81D6, 0xACD3, 0x81D7, 0xACD4, 0x81D8, 0xACD6, 0x81D9, 0xACD8, 0x81DA, 0xACD9, 0x81DB, 0xACDA, 0x81DC, 0xACDB, + 0x81DD, 0xACDC, 0x81DE, 0xACDD, 0x81DF, 0xACDE, 0x81E0, 0xACDF, 0x81E1, 0xACE2, 0x81E2, 0xACE3, 0x81E3, 0xACE5, 0x81E4, 0xACE6, + 0x81E5, 0xACE9, 0x81E6, 0xACEB, 0x81E7, 0xACED, 0x81E8, 0xACEE, 0x81E9, 0xACF2, 0x81EA, 0xACF4, 0x81EB, 0xACF7, 0x81EC, 0xACF8, + 0x81ED, 0xACF9, 0x81EE, 0xACFA, 0x81EF, 0xACFB, 0x81F0, 0xACFE, 0x81F1, 0xACFF, 0x81F2, 0xAD01, 0x81F3, 0xAD02, 0x81F4, 0xAD03, + 0x81F5, 0xAD05, 0x81F6, 0xAD07, 0x81F7, 0xAD08, 0x81F8, 0xAD09, 0x81F9, 0xAD0A, 0x81FA, 0xAD0B, 0x81FB, 0xAD0E, 0x81FC, 0xAD10, + 0x81FD, 0xAD12, 0x81FE, 0xAD13, 0x8241, 0xAD14, 0x8242, 0xAD15, 0x8243, 0xAD16, 0x8244, 0xAD17, 0x8245, 0xAD19, 0x8246, 0xAD1A, + 0x8247, 0xAD1B, 0x8248, 0xAD1D, 0x8249, 0xAD1E, 0x824A, 0xAD1F, 0x824B, 0xAD21, 0x824C, 0xAD22, 0x824D, 0xAD23, 0x824E, 0xAD24, + 0x824F, 0xAD25, 0x8250, 0xAD26, 0x8251, 0xAD27, 0x8252, 0xAD28, 0x8253, 0xAD2A, 0x8254, 0xAD2B, 0x8255, 0xAD2E, 0x8256, 0xAD2F, + 0x8257, 0xAD30, 0x8258, 0xAD31, 0x8259, 0xAD32, 0x825A, 0xAD33, 0x8261, 0xAD36, 0x8262, 0xAD37, 0x8263, 0xAD39, 0x8264, 0xAD3A, + 0x8265, 0xAD3B, 0x8266, 0xAD3D, 0x8267, 0xAD3E, 0x8268, 0xAD3F, 0x8269, 0xAD40, 0x826A, 0xAD41, 0x826B, 0xAD42, 0x826C, 0xAD43, + 0x826D, 0xAD46, 0x826E, 0xAD48, 0x826F, 0xAD4A, 0x8270, 0xAD4B, 0x8271, 0xAD4C, 0x8272, 0xAD4D, 0x8273, 0xAD4E, 0x8274, 0xAD4F, + 0x8275, 0xAD51, 0x8276, 0xAD52, 0x8277, 0xAD53, 0x8278, 0xAD55, 0x8279, 0xAD56, 0x827A, 0xAD57, 0x8281, 0xAD59, 0x8282, 0xAD5A, + 0x8283, 0xAD5B, 0x8284, 0xAD5C, 0x8285, 0xAD5D, 0x8286, 0xAD5E, 0x8287, 0xAD5F, 0x8288, 0xAD60, 0x8289, 0xAD62, 0x828A, 0xAD64, + 0x828B, 0xAD65, 0x828C, 0xAD66, 0x828D, 0xAD67, 0x828E, 0xAD68, 0x828F, 0xAD69, 0x8290, 0xAD6A, 0x8291, 0xAD6B, 0x8292, 0xAD6E, + 0x8293, 0xAD6F, 0x8294, 0xAD71, 0x8295, 0xAD72, 0x8296, 0xAD77, 0x8297, 0xAD78, 0x8298, 0xAD79, 0x8299, 0xAD7A, 0x829A, 0xAD7E, + 0x829B, 0xAD80, 0x829C, 0xAD83, 0x829D, 0xAD84, 0x829E, 0xAD85, 0x829F, 0xAD86, 0x82A0, 0xAD87, 0x82A1, 0xAD8A, 0x82A2, 0xAD8B, + 0x82A3, 0xAD8D, 0x82A4, 0xAD8E, 0x82A5, 0xAD8F, 0x82A6, 0xAD91, 0x82A7, 0xAD92, 0x82A8, 0xAD93, 0x82A9, 0xAD94, 0x82AA, 0xAD95, + 0x82AB, 0xAD96, 0x82AC, 0xAD97, 0x82AD, 0xAD98, 0x82AE, 0xAD99, 0x82AF, 0xAD9A, 0x82B0, 0xAD9B, 0x82B1, 0xAD9E, 0x82B2, 0xAD9F, + 0x82B3, 0xADA0, 0x82B4, 0xADA1, 0x82B5, 0xADA2, 0x82B6, 0xADA3, 0x82B7, 0xADA5, 0x82B8, 0xADA6, 0x82B9, 0xADA7, 0x82BA, 0xADA8, + 0x82BB, 0xADA9, 0x82BC, 0xADAA, 0x82BD, 0xADAB, 0x82BE, 0xADAC, 0x82BF, 0xADAD, 0x82C0, 0xADAE, 0x82C1, 0xADAF, 0x82C2, 0xADB0, + 0x82C3, 0xADB1, 0x82C4, 0xADB2, 0x82C5, 0xADB3, 0x82C6, 0xADB4, 0x82C7, 0xADB5, 0x82C8, 0xADB6, 0x82C9, 0xADB8, 0x82CA, 0xADB9, + 0x82CB, 0xADBA, 0x82CC, 0xADBB, 0x82CD, 0xADBC, 0x82CE, 0xADBD, 0x82CF, 0xADBE, 0x82D0, 0xADBF, 0x82D1, 0xADC2, 0x82D2, 0xADC3, + 0x82D3, 0xADC5, 0x82D4, 0xADC6, 0x82D5, 0xADC7, 0x82D6, 0xADC9, 0x82D7, 0xADCA, 0x82D8, 0xADCB, 0x82D9, 0xADCC, 0x82DA, 0xADCD, + 0x82DB, 0xADCE, 0x82DC, 0xADCF, 0x82DD, 0xADD2, 0x82DE, 0xADD4, 0x82DF, 0xADD5, 0x82E0, 0xADD6, 0x82E1, 0xADD7, 0x82E2, 0xADD8, + 0x82E3, 0xADD9, 0x82E4, 0xADDA, 0x82E5, 0xADDB, 0x82E6, 0xADDD, 0x82E7, 0xADDE, 0x82E8, 0xADDF, 0x82E9, 0xADE1, 0x82EA, 0xADE2, + 0x82EB, 0xADE3, 0x82EC, 0xADE5, 0x82ED, 0xADE6, 0x82EE, 0xADE7, 0x82EF, 0xADE8, 0x82F0, 0xADE9, 0x82F1, 0xADEA, 0x82F2, 0xADEB, + 0x82F3, 0xADEC, 0x82F4, 0xADED, 0x82F5, 0xADEE, 0x82F6, 0xADEF, 0x82F7, 0xADF0, 0x82F8, 0xADF1, 0x82F9, 0xADF2, 0x82FA, 0xADF3, + 0x82FB, 0xADF4, 0x82FC, 0xADF5, 0x82FD, 0xADF6, 0x82FE, 0xADF7, 0x8341, 0xADFA, 0x8342, 0xADFB, 0x8343, 0xADFD, 0x8344, 0xADFE, + 0x8345, 0xAE02, 0x8346, 0xAE03, 0x8347, 0xAE04, 0x8348, 0xAE05, 0x8349, 0xAE06, 0x834A, 0xAE07, 0x834B, 0xAE0A, 0x834C, 0xAE0C, + 0x834D, 0xAE0E, 0x834E, 0xAE0F, 0x834F, 0xAE10, 0x8350, 0xAE11, 0x8351, 0xAE12, 0x8352, 0xAE13, 0x8353, 0xAE15, 0x8354, 0xAE16, + 0x8355, 0xAE17, 0x8356, 0xAE18, 0x8357, 0xAE19, 0x8358, 0xAE1A, 0x8359, 0xAE1B, 0x835A, 0xAE1C, 0x8361, 0xAE1D, 0x8362, 0xAE1E, + 0x8363, 0xAE1F, 0x8364, 0xAE20, 0x8365, 0xAE21, 0x8366, 0xAE22, 0x8367, 0xAE23, 0x8368, 0xAE24, 0x8369, 0xAE25, 0x836A, 0xAE26, + 0x836B, 0xAE27, 0x836C, 0xAE28, 0x836D, 0xAE29, 0x836E, 0xAE2A, 0x836F, 0xAE2B, 0x8370, 0xAE2C, 0x8371, 0xAE2D, 0x8372, 0xAE2E, + 0x8373, 0xAE2F, 0x8374, 0xAE32, 0x8375, 0xAE33, 0x8376, 0xAE35, 0x8377, 0xAE36, 0x8378, 0xAE39, 0x8379, 0xAE3B, 0x837A, 0xAE3C, + 0x8381, 0xAE3D, 0x8382, 0xAE3E, 0x8383, 0xAE3F, 0x8384, 0xAE42, 0x8385, 0xAE44, 0x8386, 0xAE47, 0x8387, 0xAE48, 0x8388, 0xAE49, + 0x8389, 0xAE4B, 0x838A, 0xAE4F, 0x838B, 0xAE51, 0x838C, 0xAE52, 0x838D, 0xAE53, 0x838E, 0xAE55, 0x838F, 0xAE57, 0x8390, 0xAE58, + 0x8391, 0xAE59, 0x8392, 0xAE5A, 0x8393, 0xAE5B, 0x8394, 0xAE5E, 0x8395, 0xAE62, 0x8396, 0xAE63, 0x8397, 0xAE64, 0x8398, 0xAE66, + 0x8399, 0xAE67, 0x839A, 0xAE6A, 0x839B, 0xAE6B, 0x839C, 0xAE6D, 0x839D, 0xAE6E, 0x839E, 0xAE6F, 0x839F, 0xAE71, 0x83A0, 0xAE72, + 0x83A1, 0xAE73, 0x83A2, 0xAE74, 0x83A3, 0xAE75, 0x83A4, 0xAE76, 0x83A5, 0xAE77, 0x83A6, 0xAE7A, 0x83A7, 0xAE7E, 0x83A8, 0xAE7F, + 0x83A9, 0xAE80, 0x83AA, 0xAE81, 0x83AB, 0xAE82, 0x83AC, 0xAE83, 0x83AD, 0xAE86, 0x83AE, 0xAE87, 0x83AF, 0xAE88, 0x83B0, 0xAE89, + 0x83B1, 0xAE8A, 0x83B2, 0xAE8B, 0x83B3, 0xAE8D, 0x83B4, 0xAE8E, 0x83B5, 0xAE8F, 0x83B6, 0xAE90, 0x83B7, 0xAE91, 0x83B8, 0xAE92, + 0x83B9, 0xAE93, 0x83BA, 0xAE94, 0x83BB, 0xAE95, 0x83BC, 0xAE96, 0x83BD, 0xAE97, 0x83BE, 0xAE98, 0x83BF, 0xAE99, 0x83C0, 0xAE9A, + 0x83C1, 0xAE9B, 0x83C2, 0xAE9C, 0x83C3, 0xAE9D, 0x83C4, 0xAE9E, 0x83C5, 0xAE9F, 0x83C6, 0xAEA0, 0x83C7, 0xAEA1, 0x83C8, 0xAEA2, + 0x83C9, 0xAEA3, 0x83CA, 0xAEA4, 0x83CB, 0xAEA5, 0x83CC, 0xAEA6, 0x83CD, 0xAEA7, 0x83CE, 0xAEA8, 0x83CF, 0xAEA9, 0x83D0, 0xAEAA, + 0x83D1, 0xAEAB, 0x83D2, 0xAEAC, 0x83D3, 0xAEAD, 0x83D4, 0xAEAE, 0x83D5, 0xAEAF, 0x83D6, 0xAEB0, 0x83D7, 0xAEB1, 0x83D8, 0xAEB2, + 0x83D9, 0xAEB3, 0x83DA, 0xAEB4, 0x83DB, 0xAEB5, 0x83DC, 0xAEB6, 0x83DD, 0xAEB7, 0x83DE, 0xAEB8, 0x83DF, 0xAEB9, 0x83E0, 0xAEBA, + 0x83E1, 0xAEBB, 0x83E2, 0xAEBF, 0x83E3, 0xAEC1, 0x83E4, 0xAEC2, 0x83E5, 0xAEC3, 0x83E6, 0xAEC5, 0x83E7, 0xAEC6, 0x83E8, 0xAEC7, + 0x83E9, 0xAEC8, 0x83EA, 0xAEC9, 0x83EB, 0xAECA, 0x83EC, 0xAECB, 0x83ED, 0xAECE, 0x83EE, 0xAED2, 0x83EF, 0xAED3, 0x83F0, 0xAED4, + 0x83F1, 0xAED5, 0x83F2, 0xAED6, 0x83F3, 0xAED7, 0x83F4, 0xAEDA, 0x83F5, 0xAEDB, 0x83F6, 0xAEDD, 0x83F7, 0xAEDE, 0x83F8, 0xAEDF, + 0x83F9, 0xAEE0, 0x83FA, 0xAEE1, 0x83FB, 0xAEE2, 0x83FC, 0xAEE3, 0x83FD, 0xAEE4, 0x83FE, 0xAEE5, 0x8441, 0xAEE6, 0x8442, 0xAEE7, + 0x8443, 0xAEE9, 0x8444, 0xAEEA, 0x8445, 0xAEEC, 0x8446, 0xAEEE, 0x8447, 0xAEEF, 0x8448, 0xAEF0, 0x8449, 0xAEF1, 0x844A, 0xAEF2, + 0x844B, 0xAEF3, 0x844C, 0xAEF5, 0x844D, 0xAEF6, 0x844E, 0xAEF7, 0x844F, 0xAEF9, 0x8450, 0xAEFA, 0x8451, 0xAEFB, 0x8452, 0xAEFD, + 0x8453, 0xAEFE, 0x8454, 0xAEFF, 0x8455, 0xAF00, 0x8456, 0xAF01, 0x8457, 0xAF02, 0x8458, 0xAF03, 0x8459, 0xAF04, 0x845A, 0xAF05, + 0x8461, 0xAF06, 0x8462, 0xAF09, 0x8463, 0xAF0A, 0x8464, 0xAF0B, 0x8465, 0xAF0C, 0x8466, 0xAF0E, 0x8467, 0xAF0F, 0x8468, 0xAF11, + 0x8469, 0xAF12, 0x846A, 0xAF13, 0x846B, 0xAF14, 0x846C, 0xAF15, 0x846D, 0xAF16, 0x846E, 0xAF17, 0x846F, 0xAF18, 0x8470, 0xAF19, + 0x8471, 0xAF1A, 0x8472, 0xAF1B, 0x8473, 0xAF1C, 0x8474, 0xAF1D, 0x8475, 0xAF1E, 0x8476, 0xAF1F, 0x8477, 0xAF20, 0x8478, 0xAF21, + 0x8479, 0xAF22, 0x847A, 0xAF23, 0x8481, 0xAF24, 0x8482, 0xAF25, 0x8483, 0xAF26, 0x8484, 0xAF27, 0x8485, 0xAF28, 0x8486, 0xAF29, + 0x8487, 0xAF2A, 0x8488, 0xAF2B, 0x8489, 0xAF2E, 0x848A, 0xAF2F, 0x848B, 0xAF31, 0x848C, 0xAF33, 0x848D, 0xAF35, 0x848E, 0xAF36, + 0x848F, 0xAF37, 0x8490, 0xAF38, 0x8491, 0xAF39, 0x8492, 0xAF3A, 0x8493, 0xAF3B, 0x8494, 0xAF3E, 0x8495, 0xAF40, 0x8496, 0xAF44, + 0x8497, 0xAF45, 0x8498, 0xAF46, 0x8499, 0xAF47, 0x849A, 0xAF4A, 0x849B, 0xAF4B, 0x849C, 0xAF4C, 0x849D, 0xAF4D, 0x849E, 0xAF4E, + 0x849F, 0xAF4F, 0x84A0, 0xAF51, 0x84A1, 0xAF52, 0x84A2, 0xAF53, 0x84A3, 0xAF54, 0x84A4, 0xAF55, 0x84A5, 0xAF56, 0x84A6, 0xAF57, + 0x84A7, 0xAF58, 0x84A8, 0xAF59, 0x84A9, 0xAF5A, 0x84AA, 0xAF5B, 0x84AB, 0xAF5E, 0x84AC, 0xAF5F, 0x84AD, 0xAF60, 0x84AE, 0xAF61, + 0x84AF, 0xAF62, 0x84B0, 0xAF63, 0x84B1, 0xAF66, 0x84B2, 0xAF67, 0x84B3, 0xAF68, 0x84B4, 0xAF69, 0x84B5, 0xAF6A, 0x84B6, 0xAF6B, + 0x84B7, 0xAF6C, 0x84B8, 0xAF6D, 0x84B9, 0xAF6E, 0x84BA, 0xAF6F, 0x84BB, 0xAF70, 0x84BC, 0xAF71, 0x84BD, 0xAF72, 0x84BE, 0xAF73, + 0x84BF, 0xAF74, 0x84C0, 0xAF75, 0x84C1, 0xAF76, 0x84C2, 0xAF77, 0x84C3, 0xAF78, 0x84C4, 0xAF7A, 0x84C5, 0xAF7B, 0x84C6, 0xAF7C, + 0x84C7, 0xAF7D, 0x84C8, 0xAF7E, 0x84C9, 0xAF7F, 0x84CA, 0xAF81, 0x84CB, 0xAF82, 0x84CC, 0xAF83, 0x84CD, 0xAF85, 0x84CE, 0xAF86, + 0x84CF, 0xAF87, 0x84D0, 0xAF89, 0x84D1, 0xAF8A, 0x84D2, 0xAF8B, 0x84D3, 0xAF8C, 0x84D4, 0xAF8D, 0x84D5, 0xAF8E, 0x84D6, 0xAF8F, + 0x84D7, 0xAF92, 0x84D8, 0xAF93, 0x84D9, 0xAF94, 0x84DA, 0xAF96, 0x84DB, 0xAF97, 0x84DC, 0xAF98, 0x84DD, 0xAF99, 0x84DE, 0xAF9A, + 0x84DF, 0xAF9B, 0x84E0, 0xAF9D, 0x84E1, 0xAF9E, 0x84E2, 0xAF9F, 0x84E3, 0xAFA0, 0x84E4, 0xAFA1, 0x84E5, 0xAFA2, 0x84E6, 0xAFA3, + 0x84E7, 0xAFA4, 0x84E8, 0xAFA5, 0x84E9, 0xAFA6, 0x84EA, 0xAFA7, 0x84EB, 0xAFA8, 0x84EC, 0xAFA9, 0x84ED, 0xAFAA, 0x84EE, 0xAFAB, + 0x84EF, 0xAFAC, 0x84F0, 0xAFAD, 0x84F1, 0xAFAE, 0x84F2, 0xAFAF, 0x84F3, 0xAFB0, 0x84F4, 0xAFB1, 0x84F5, 0xAFB2, 0x84F6, 0xAFB3, + 0x84F7, 0xAFB4, 0x84F8, 0xAFB5, 0x84F9, 0xAFB6, 0x84FA, 0xAFB7, 0x84FB, 0xAFBA, 0x84FC, 0xAFBB, 0x84FD, 0xAFBD, 0x84FE, 0xAFBE, + 0x8541, 0xAFBF, 0x8542, 0xAFC1, 0x8543, 0xAFC2, 0x8544, 0xAFC3, 0x8545, 0xAFC4, 0x8546, 0xAFC5, 0x8547, 0xAFC6, 0x8548, 0xAFCA, + 0x8549, 0xAFCC, 0x854A, 0xAFCF, 0x854B, 0xAFD0, 0x854C, 0xAFD1, 0x854D, 0xAFD2, 0x854E, 0xAFD3, 0x854F, 0xAFD5, 0x8550, 0xAFD6, + 0x8551, 0xAFD7, 0x8552, 0xAFD8, 0x8553, 0xAFD9, 0x8554, 0xAFDA, 0x8555, 0xAFDB, 0x8556, 0xAFDD, 0x8557, 0xAFDE, 0x8558, 0xAFDF, + 0x8559, 0xAFE0, 0x855A, 0xAFE1, 0x8561, 0xAFE2, 0x8562, 0xAFE3, 0x8563, 0xAFE4, 0x8564, 0xAFE5, 0x8565, 0xAFE6, 0x8566, 0xAFE7, + 0x8567, 0xAFEA, 0x8568, 0xAFEB, 0x8569, 0xAFEC, 0x856A, 0xAFED, 0x856B, 0xAFEE, 0x856C, 0xAFEF, 0x856D, 0xAFF2, 0x856E, 0xAFF3, + 0x856F, 0xAFF5, 0x8570, 0xAFF6, 0x8571, 0xAFF7, 0x8572, 0xAFF9, 0x8573, 0xAFFA, 0x8574, 0xAFFB, 0x8575, 0xAFFC, 0x8576, 0xAFFD, + 0x8577, 0xAFFE, 0x8578, 0xAFFF, 0x8579, 0xB002, 0x857A, 0xB003, 0x8581, 0xB005, 0x8582, 0xB006, 0x8583, 0xB007, 0x8584, 0xB008, + 0x8585, 0xB009, 0x8586, 0xB00A, 0x8587, 0xB00B, 0x8588, 0xB00D, 0x8589, 0xB00E, 0x858A, 0xB00F, 0x858B, 0xB011, 0x858C, 0xB012, + 0x858D, 0xB013, 0x858E, 0xB015, 0x858F, 0xB016, 0x8590, 0xB017, 0x8591, 0xB018, 0x8592, 0xB019, 0x8593, 0xB01A, 0x8594, 0xB01B, + 0x8595, 0xB01E, 0x8596, 0xB01F, 0x8597, 0xB020, 0x8598, 0xB021, 0x8599, 0xB022, 0x859A, 0xB023, 0x859B, 0xB024, 0x859C, 0xB025, + 0x859D, 0xB026, 0x859E, 0xB027, 0x859F, 0xB029, 0x85A0, 0xB02A, 0x85A1, 0xB02B, 0x85A2, 0xB02C, 0x85A3, 0xB02D, 0x85A4, 0xB02E, + 0x85A5, 0xB02F, 0x85A6, 0xB030, 0x85A7, 0xB031, 0x85A8, 0xB032, 0x85A9, 0xB033, 0x85AA, 0xB034, 0x85AB, 0xB035, 0x85AC, 0xB036, + 0x85AD, 0xB037, 0x85AE, 0xB038, 0x85AF, 0xB039, 0x85B0, 0xB03A, 0x85B1, 0xB03B, 0x85B2, 0xB03C, 0x85B3, 0xB03D, 0x85B4, 0xB03E, + 0x85B5, 0xB03F, 0x85B6, 0xB040, 0x85B7, 0xB041, 0x85B8, 0xB042, 0x85B9, 0xB043, 0x85BA, 0xB046, 0x85BB, 0xB047, 0x85BC, 0xB049, + 0x85BD, 0xB04B, 0x85BE, 0xB04D, 0x85BF, 0xB04F, 0x85C0, 0xB050, 0x85C1, 0xB051, 0x85C2, 0xB052, 0x85C3, 0xB056, 0x85C4, 0xB058, + 0x85C5, 0xB05A, 0x85C6, 0xB05B, 0x85C7, 0xB05C, 0x85C8, 0xB05E, 0x85C9, 0xB05F, 0x85CA, 0xB060, 0x85CB, 0xB061, 0x85CC, 0xB062, + 0x85CD, 0xB063, 0x85CE, 0xB064, 0x85CF, 0xB065, 0x85D0, 0xB066, 0x85D1, 0xB067, 0x85D2, 0xB068, 0x85D3, 0xB069, 0x85D4, 0xB06A, + 0x85D5, 0xB06B, 0x85D6, 0xB06C, 0x85D7, 0xB06D, 0x85D8, 0xB06E, 0x85D9, 0xB06F, 0x85DA, 0xB070, 0x85DB, 0xB071, 0x85DC, 0xB072, + 0x85DD, 0xB073, 0x85DE, 0xB074, 0x85DF, 0xB075, 0x85E0, 0xB076, 0x85E1, 0xB077, 0x85E2, 0xB078, 0x85E3, 0xB079, 0x85E4, 0xB07A, + 0x85E5, 0xB07B, 0x85E6, 0xB07E, 0x85E7, 0xB07F, 0x85E8, 0xB081, 0x85E9, 0xB082, 0x85EA, 0xB083, 0x85EB, 0xB085, 0x85EC, 0xB086, + 0x85ED, 0xB087, 0x85EE, 0xB088, 0x85EF, 0xB089, 0x85F0, 0xB08A, 0x85F1, 0xB08B, 0x85F2, 0xB08E, 0x85F3, 0xB090, 0x85F4, 0xB092, + 0x85F5, 0xB093, 0x85F6, 0xB094, 0x85F7, 0xB095, 0x85F8, 0xB096, 0x85F9, 0xB097, 0x85FA, 0xB09B, 0x85FB, 0xB09D, 0x85FC, 0xB09E, + 0x85FD, 0xB0A3, 0x85FE, 0xB0A4, 0x8641, 0xB0A5, 0x8642, 0xB0A6, 0x8643, 0xB0A7, 0x8644, 0xB0AA, 0x8645, 0xB0B0, 0x8646, 0xB0B2, + 0x8647, 0xB0B6, 0x8648, 0xB0B7, 0x8649, 0xB0B9, 0x864A, 0xB0BA, 0x864B, 0xB0BB, 0x864C, 0xB0BD, 0x864D, 0xB0BE, 0x864E, 0xB0BF, + 0x864F, 0xB0C0, 0x8650, 0xB0C1, 0x8651, 0xB0C2, 0x8652, 0xB0C3, 0x8653, 0xB0C6, 0x8654, 0xB0CA, 0x8655, 0xB0CB, 0x8656, 0xB0CC, + 0x8657, 0xB0CD, 0x8658, 0xB0CE, 0x8659, 0xB0CF, 0x865A, 0xB0D2, 0x8661, 0xB0D3, 0x8662, 0xB0D5, 0x8663, 0xB0D6, 0x8664, 0xB0D7, + 0x8665, 0xB0D9, 0x8666, 0xB0DA, 0x8667, 0xB0DB, 0x8668, 0xB0DC, 0x8669, 0xB0DD, 0x866A, 0xB0DE, 0x866B, 0xB0DF, 0x866C, 0xB0E1, + 0x866D, 0xB0E2, 0x866E, 0xB0E3, 0x866F, 0xB0E4, 0x8670, 0xB0E6, 0x8671, 0xB0E7, 0x8672, 0xB0E8, 0x8673, 0xB0E9, 0x8674, 0xB0EA, + 0x8675, 0xB0EB, 0x8676, 0xB0EC, 0x8677, 0xB0ED, 0x8678, 0xB0EE, 0x8679, 0xB0EF, 0x867A, 0xB0F0, 0x8681, 0xB0F1, 0x8682, 0xB0F2, + 0x8683, 0xB0F3, 0x8684, 0xB0F4, 0x8685, 0xB0F5, 0x8686, 0xB0F6, 0x8687, 0xB0F7, 0x8688, 0xB0F8, 0x8689, 0xB0F9, 0x868A, 0xB0FA, + 0x868B, 0xB0FB, 0x868C, 0xB0FC, 0x868D, 0xB0FD, 0x868E, 0xB0FE, 0x868F, 0xB0FF, 0x8690, 0xB100, 0x8691, 0xB101, 0x8692, 0xB102, + 0x8693, 0xB103, 0x8694, 0xB104, 0x8695, 0xB105, 0x8696, 0xB106, 0x8697, 0xB107, 0x8698, 0xB10A, 0x8699, 0xB10D, 0x869A, 0xB10E, + 0x869B, 0xB10F, 0x869C, 0xB111, 0x869D, 0xB114, 0x869E, 0xB115, 0x869F, 0xB116, 0x86A0, 0xB117, 0x86A1, 0xB11A, 0x86A2, 0xB11E, + 0x86A3, 0xB11F, 0x86A4, 0xB120, 0x86A5, 0xB121, 0x86A6, 0xB122, 0x86A7, 0xB126, 0x86A8, 0xB127, 0x86A9, 0xB129, 0x86AA, 0xB12A, + 0x86AB, 0xB12B, 0x86AC, 0xB12D, 0x86AD, 0xB12E, 0x86AE, 0xB12F, 0x86AF, 0xB130, 0x86B0, 0xB131, 0x86B1, 0xB132, 0x86B2, 0xB133, + 0x86B3, 0xB136, 0x86B4, 0xB13A, 0x86B5, 0xB13B, 0x86B6, 0xB13C, 0x86B7, 0xB13D, 0x86B8, 0xB13E, 0x86B9, 0xB13F, 0x86BA, 0xB142, + 0x86BB, 0xB143, 0x86BC, 0xB145, 0x86BD, 0xB146, 0x86BE, 0xB147, 0x86BF, 0xB149, 0x86C0, 0xB14A, 0x86C1, 0xB14B, 0x86C2, 0xB14C, + 0x86C3, 0xB14D, 0x86C4, 0xB14E, 0x86C5, 0xB14F, 0x86C6, 0xB152, 0x86C7, 0xB153, 0x86C8, 0xB156, 0x86C9, 0xB157, 0x86CA, 0xB159, + 0x86CB, 0xB15A, 0x86CC, 0xB15B, 0x86CD, 0xB15D, 0x86CE, 0xB15E, 0x86CF, 0xB15F, 0x86D0, 0xB161, 0x86D1, 0xB162, 0x86D2, 0xB163, + 0x86D3, 0xB164, 0x86D4, 0xB165, 0x86D5, 0xB166, 0x86D6, 0xB167, 0x86D7, 0xB168, 0x86D8, 0xB169, 0x86D9, 0xB16A, 0x86DA, 0xB16B, + 0x86DB, 0xB16C, 0x86DC, 0xB16D, 0x86DD, 0xB16E, 0x86DE, 0xB16F, 0x86DF, 0xB170, 0x86E0, 0xB171, 0x86E1, 0xB172, 0x86E2, 0xB173, + 0x86E3, 0xB174, 0x86E4, 0xB175, 0x86E5, 0xB176, 0x86E6, 0xB177, 0x86E7, 0xB17A, 0x86E8, 0xB17B, 0x86E9, 0xB17D, 0x86EA, 0xB17E, + 0x86EB, 0xB17F, 0x86EC, 0xB181, 0x86ED, 0xB183, 0x86EE, 0xB184, 0x86EF, 0xB185, 0x86F0, 0xB186, 0x86F1, 0xB187, 0x86F2, 0xB18A, + 0x86F3, 0xB18C, 0x86F4, 0xB18E, 0x86F5, 0xB18F, 0x86F6, 0xB190, 0x86F7, 0xB191, 0x86F8, 0xB195, 0x86F9, 0xB196, 0x86FA, 0xB197, + 0x86FB, 0xB199, 0x86FC, 0xB19A, 0x86FD, 0xB19B, 0x86FE, 0xB19D, 0x8741, 0xB19E, 0x8742, 0xB19F, 0x8743, 0xB1A0, 0x8744, 0xB1A1, + 0x8745, 0xB1A2, 0x8746, 0xB1A3, 0x8747, 0xB1A4, 0x8748, 0xB1A5, 0x8749, 0xB1A6, 0x874A, 0xB1A7, 0x874B, 0xB1A9, 0x874C, 0xB1AA, + 0x874D, 0xB1AB, 0x874E, 0xB1AC, 0x874F, 0xB1AD, 0x8750, 0xB1AE, 0x8751, 0xB1AF, 0x8752, 0xB1B0, 0x8753, 0xB1B1, 0x8754, 0xB1B2, + 0x8755, 0xB1B3, 0x8756, 0xB1B4, 0x8757, 0xB1B5, 0x8758, 0xB1B6, 0x8759, 0xB1B7, 0x875A, 0xB1B8, 0x8761, 0xB1B9, 0x8762, 0xB1BA, + 0x8763, 0xB1BB, 0x8764, 0xB1BC, 0x8765, 0xB1BD, 0x8766, 0xB1BE, 0x8767, 0xB1BF, 0x8768, 0xB1C0, 0x8769, 0xB1C1, 0x876A, 0xB1C2, + 0x876B, 0xB1C3, 0x876C, 0xB1C4, 0x876D, 0xB1C5, 0x876E, 0xB1C6, 0x876F, 0xB1C7, 0x8770, 0xB1C8, 0x8771, 0xB1C9, 0x8772, 0xB1CA, + 0x8773, 0xB1CB, 0x8774, 0xB1CD, 0x8775, 0xB1CE, 0x8776, 0xB1CF, 0x8777, 0xB1D1, 0x8778, 0xB1D2, 0x8779, 0xB1D3, 0x877A, 0xB1D5, + 0x8781, 0xB1D6, 0x8782, 0xB1D7, 0x8783, 0xB1D8, 0x8784, 0xB1D9, 0x8785, 0xB1DA, 0x8786, 0xB1DB, 0x8787, 0xB1DE, 0x8788, 0xB1E0, + 0x8789, 0xB1E1, 0x878A, 0xB1E2, 0x878B, 0xB1E3, 0x878C, 0xB1E4, 0x878D, 0xB1E5, 0x878E, 0xB1E6, 0x878F, 0xB1E7, 0x8790, 0xB1EA, + 0x8791, 0xB1EB, 0x8792, 0xB1ED, 0x8793, 0xB1EE, 0x8794, 0xB1EF, 0x8795, 0xB1F1, 0x8796, 0xB1F2, 0x8797, 0xB1F3, 0x8798, 0xB1F4, + 0x8799, 0xB1F5, 0x879A, 0xB1F6, 0x879B, 0xB1F7, 0x879C, 0xB1F8, 0x879D, 0xB1FA, 0x879E, 0xB1FC, 0x879F, 0xB1FE, 0x87A0, 0xB1FF, + 0x87A1, 0xB200, 0x87A2, 0xB201, 0x87A3, 0xB202, 0x87A4, 0xB203, 0x87A5, 0xB206, 0x87A6, 0xB207, 0x87A7, 0xB209, 0x87A8, 0xB20A, + 0x87A9, 0xB20D, 0x87AA, 0xB20E, 0x87AB, 0xB20F, 0x87AC, 0xB210, 0x87AD, 0xB211, 0x87AE, 0xB212, 0x87AF, 0xB213, 0x87B0, 0xB216, + 0x87B1, 0xB218, 0x87B2, 0xB21A, 0x87B3, 0xB21B, 0x87B4, 0xB21C, 0x87B5, 0xB21D, 0x87B6, 0xB21E, 0x87B7, 0xB21F, 0x87B8, 0xB221, + 0x87B9, 0xB222, 0x87BA, 0xB223, 0x87BB, 0xB224, 0x87BC, 0xB225, 0x87BD, 0xB226, 0x87BE, 0xB227, 0x87BF, 0xB228, 0x87C0, 0xB229, + 0x87C1, 0xB22A, 0x87C2, 0xB22B, 0x87C3, 0xB22C, 0x87C4, 0xB22D, 0x87C5, 0xB22E, 0x87C6, 0xB22F, 0x87C7, 0xB230, 0x87C8, 0xB231, + 0x87C9, 0xB232, 0x87CA, 0xB233, 0x87CB, 0xB235, 0x87CC, 0xB236, 0x87CD, 0xB237, 0x87CE, 0xB238, 0x87CF, 0xB239, 0x87D0, 0xB23A, + 0x87D1, 0xB23B, 0x87D2, 0xB23D, 0x87D3, 0xB23E, 0x87D4, 0xB23F, 0x87D5, 0xB240, 0x87D6, 0xB241, 0x87D7, 0xB242, 0x87D8, 0xB243, + 0x87D9, 0xB244, 0x87DA, 0xB245, 0x87DB, 0xB246, 0x87DC, 0xB247, 0x87DD, 0xB248, 0x87DE, 0xB249, 0x87DF, 0xB24A, 0x87E0, 0xB24B, + 0x87E1, 0xB24C, 0x87E2, 0xB24D, 0x87E3, 0xB24E, 0x87E4, 0xB24F, 0x87E5, 0xB250, 0x87E6, 0xB251, 0x87E7, 0xB252, 0x87E8, 0xB253, + 0x87E9, 0xB254, 0x87EA, 0xB255, 0x87EB, 0xB256, 0x87EC, 0xB257, 0x87ED, 0xB259, 0x87EE, 0xB25A, 0x87EF, 0xB25B, 0x87F0, 0xB25D, + 0x87F1, 0xB25E, 0x87F2, 0xB25F, 0x87F3, 0xB261, 0x87F4, 0xB262, 0x87F5, 0xB263, 0x87F6, 0xB264, 0x87F7, 0xB265, 0x87F8, 0xB266, + 0x87F9, 0xB267, 0x87FA, 0xB26A, 0x87FB, 0xB26B, 0x87FC, 0xB26C, 0x87FD, 0xB26D, 0x87FE, 0xB26E, 0x8841, 0xB26F, 0x8842, 0xB270, + 0x8843, 0xB271, 0x8844, 0xB272, 0x8845, 0xB273, 0x8846, 0xB276, 0x8847, 0xB277, 0x8848, 0xB278, 0x8849, 0xB279, 0x884A, 0xB27A, + 0x884B, 0xB27B, 0x884C, 0xB27D, 0x884D, 0xB27E, 0x884E, 0xB27F, 0x884F, 0xB280, 0x8850, 0xB281, 0x8851, 0xB282, 0x8852, 0xB283, + 0x8853, 0xB286, 0x8854, 0xB287, 0x8855, 0xB288, 0x8856, 0xB28A, 0x8857, 0xB28B, 0x8858, 0xB28C, 0x8859, 0xB28D, 0x885A, 0xB28E, + 0x8861, 0xB28F, 0x8862, 0xB292, 0x8863, 0xB293, 0x8864, 0xB295, 0x8865, 0xB296, 0x8866, 0xB297, 0x8867, 0xB29B, 0x8868, 0xB29C, + 0x8869, 0xB29D, 0x886A, 0xB29E, 0x886B, 0xB29F, 0x886C, 0xB2A2, 0x886D, 0xB2A4, 0x886E, 0xB2A7, 0x886F, 0xB2A8, 0x8870, 0xB2A9, + 0x8871, 0xB2AB, 0x8872, 0xB2AD, 0x8873, 0xB2AE, 0x8874, 0xB2AF, 0x8875, 0xB2B1, 0x8876, 0xB2B2, 0x8877, 0xB2B3, 0x8878, 0xB2B5, + 0x8879, 0xB2B6, 0x887A, 0xB2B7, 0x8881, 0xB2B8, 0x8882, 0xB2B9, 0x8883, 0xB2BA, 0x8884, 0xB2BB, 0x8885, 0xB2BC, 0x8886, 0xB2BD, + 0x8887, 0xB2BE, 0x8888, 0xB2BF, 0x8889, 0xB2C0, 0x888A, 0xB2C1, 0x888B, 0xB2C2, 0x888C, 0xB2C3, 0x888D, 0xB2C4, 0x888E, 0xB2C5, + 0x888F, 0xB2C6, 0x8890, 0xB2C7, 0x8891, 0xB2CA, 0x8892, 0xB2CB, 0x8893, 0xB2CD, 0x8894, 0xB2CE, 0x8895, 0xB2CF, 0x8896, 0xB2D1, + 0x8897, 0xB2D3, 0x8898, 0xB2D4, 0x8899, 0xB2D5, 0x889A, 0xB2D6, 0x889B, 0xB2D7, 0x889C, 0xB2DA, 0x889D, 0xB2DC, 0x889E, 0xB2DE, + 0x889F, 0xB2DF, 0x88A0, 0xB2E0, 0x88A1, 0xB2E1, 0x88A2, 0xB2E3, 0x88A3, 0xB2E7, 0x88A4, 0xB2E9, 0x88A5, 0xB2EA, 0x88A6, 0xB2F0, + 0x88A7, 0xB2F1, 0x88A8, 0xB2F2, 0x88A9, 0xB2F6, 0x88AA, 0xB2FC, 0x88AB, 0xB2FD, 0x88AC, 0xB2FE, 0x88AD, 0xB302, 0x88AE, 0xB303, + 0x88AF, 0xB305, 0x88B0, 0xB306, 0x88B1, 0xB307, 0x88B2, 0xB309, 0x88B3, 0xB30A, 0x88B4, 0xB30B, 0x88B5, 0xB30C, 0x88B6, 0xB30D, + 0x88B7, 0xB30E, 0x88B8, 0xB30F, 0x88B9, 0xB312, 0x88BA, 0xB316, 0x88BB, 0xB317, 0x88BC, 0xB318, 0x88BD, 0xB319, 0x88BE, 0xB31A, + 0x88BF, 0xB31B, 0x88C0, 0xB31D, 0x88C1, 0xB31E, 0x88C2, 0xB31F, 0x88C3, 0xB320, 0x88C4, 0xB321, 0x88C5, 0xB322, 0x88C6, 0xB323, + 0x88C7, 0xB324, 0x88C8, 0xB325, 0x88C9, 0xB326, 0x88CA, 0xB327, 0x88CB, 0xB328, 0x88CC, 0xB329, 0x88CD, 0xB32A, 0x88CE, 0xB32B, + 0x88CF, 0xB32C, 0x88D0, 0xB32D, 0x88D1, 0xB32E, 0x88D2, 0xB32F, 0x88D3, 0xB330, 0x88D4, 0xB331, 0x88D5, 0xB332, 0x88D6, 0xB333, + 0x88D7, 0xB334, 0x88D8, 0xB335, 0x88D9, 0xB336, 0x88DA, 0xB337, 0x88DB, 0xB338, 0x88DC, 0xB339, 0x88DD, 0xB33A, 0x88DE, 0xB33B, + 0x88DF, 0xB33C, 0x88E0, 0xB33D, 0x88E1, 0xB33E, 0x88E2, 0xB33F, 0x88E3, 0xB340, 0x88E4, 0xB341, 0x88E5, 0xB342, 0x88E6, 0xB343, + 0x88E7, 0xB344, 0x88E8, 0xB345, 0x88E9, 0xB346, 0x88EA, 0xB347, 0x88EB, 0xB348, 0x88EC, 0xB349, 0x88ED, 0xB34A, 0x88EE, 0xB34B, + 0x88EF, 0xB34C, 0x88F0, 0xB34D, 0x88F1, 0xB34E, 0x88F2, 0xB34F, 0x88F3, 0xB350, 0x88F4, 0xB351, 0x88F5, 0xB352, 0x88F6, 0xB353, + 0x88F7, 0xB357, 0x88F8, 0xB359, 0x88F9, 0xB35A, 0x88FA, 0xB35D, 0x88FB, 0xB360, 0x88FC, 0xB361, 0x88FD, 0xB362, 0x88FE, 0xB363, + 0x8941, 0xB366, 0x8942, 0xB368, 0x8943, 0xB36A, 0x8944, 0xB36C, 0x8945, 0xB36D, 0x8946, 0xB36F, 0x8947, 0xB372, 0x8948, 0xB373, + 0x8949, 0xB375, 0x894A, 0xB376, 0x894B, 0xB377, 0x894C, 0xB379, 0x894D, 0xB37A, 0x894E, 0xB37B, 0x894F, 0xB37C, 0x8950, 0xB37D, + 0x8951, 0xB37E, 0x8952, 0xB37F, 0x8953, 0xB382, 0x8954, 0xB386, 0x8955, 0xB387, 0x8956, 0xB388, 0x8957, 0xB389, 0x8958, 0xB38A, + 0x8959, 0xB38B, 0x895A, 0xB38D, 0x8961, 0xB38E, 0x8962, 0xB38F, 0x8963, 0xB391, 0x8964, 0xB392, 0x8965, 0xB393, 0x8966, 0xB395, + 0x8967, 0xB396, 0x8968, 0xB397, 0x8969, 0xB398, 0x896A, 0xB399, 0x896B, 0xB39A, 0x896C, 0xB39B, 0x896D, 0xB39C, 0x896E, 0xB39D, + 0x896F, 0xB39E, 0x8970, 0xB39F, 0x8971, 0xB3A2, 0x8972, 0xB3A3, 0x8973, 0xB3A4, 0x8974, 0xB3A5, 0x8975, 0xB3A6, 0x8976, 0xB3A7, + 0x8977, 0xB3A9, 0x8978, 0xB3AA, 0x8979, 0xB3AB, 0x897A, 0xB3AD, 0x8981, 0xB3AE, 0x8982, 0xB3AF, 0x8983, 0xB3B0, 0x8984, 0xB3B1, + 0x8985, 0xB3B2, 0x8986, 0xB3B3, 0x8987, 0xB3B4, 0x8988, 0xB3B5, 0x8989, 0xB3B6, 0x898A, 0xB3B7, 0x898B, 0xB3B8, 0x898C, 0xB3B9, + 0x898D, 0xB3BA, 0x898E, 0xB3BB, 0x898F, 0xB3BC, 0x8990, 0xB3BD, 0x8991, 0xB3BE, 0x8992, 0xB3BF, 0x8993, 0xB3C0, 0x8994, 0xB3C1, + 0x8995, 0xB3C2, 0x8996, 0xB3C3, 0x8997, 0xB3C6, 0x8998, 0xB3C7, 0x8999, 0xB3C9, 0x899A, 0xB3CA, 0x899B, 0xB3CD, 0x899C, 0xB3CF, + 0x899D, 0xB3D1, 0x899E, 0xB3D2, 0x899F, 0xB3D3, 0x89A0, 0xB3D6, 0x89A1, 0xB3D8, 0x89A2, 0xB3DA, 0x89A3, 0xB3DC, 0x89A4, 0xB3DE, + 0x89A5, 0xB3DF, 0x89A6, 0xB3E1, 0x89A7, 0xB3E2, 0x89A8, 0xB3E3, 0x89A9, 0xB3E5, 0x89AA, 0xB3E6, 0x89AB, 0xB3E7, 0x89AC, 0xB3E9, + 0x89AD, 0xB3EA, 0x89AE, 0xB3EB, 0x89AF, 0xB3EC, 0x89B0, 0xB3ED, 0x89B1, 0xB3EE, 0x89B2, 0xB3EF, 0x89B3, 0xB3F0, 0x89B4, 0xB3F1, + 0x89B5, 0xB3F2, 0x89B6, 0xB3F3, 0x89B7, 0xB3F4, 0x89B8, 0xB3F5, 0x89B9, 0xB3F6, 0x89BA, 0xB3F7, 0x89BB, 0xB3F8, 0x89BC, 0xB3F9, + 0x89BD, 0xB3FA, 0x89BE, 0xB3FB, 0x89BF, 0xB3FD, 0x89C0, 0xB3FE, 0x89C1, 0xB3FF, 0x89C2, 0xB400, 0x89C3, 0xB401, 0x89C4, 0xB402, + 0x89C5, 0xB403, 0x89C6, 0xB404, 0x89C7, 0xB405, 0x89C8, 0xB406, 0x89C9, 0xB407, 0x89CA, 0xB408, 0x89CB, 0xB409, 0x89CC, 0xB40A, + 0x89CD, 0xB40B, 0x89CE, 0xB40C, 0x89CF, 0xB40D, 0x89D0, 0xB40E, 0x89D1, 0xB40F, 0x89D2, 0xB411, 0x89D3, 0xB412, 0x89D4, 0xB413, + 0x89D5, 0xB414, 0x89D6, 0xB415, 0x89D7, 0xB416, 0x89D8, 0xB417, 0x89D9, 0xB419, 0x89DA, 0xB41A, 0x89DB, 0xB41B, 0x89DC, 0xB41D, + 0x89DD, 0xB41E, 0x89DE, 0xB41F, 0x89DF, 0xB421, 0x89E0, 0xB422, 0x89E1, 0xB423, 0x89E2, 0xB424, 0x89E3, 0xB425, 0x89E4, 0xB426, + 0x89E5, 0xB427, 0x89E6, 0xB42A, 0x89E7, 0xB42C, 0x89E8, 0xB42D, 0x89E9, 0xB42E, 0x89EA, 0xB42F, 0x89EB, 0xB430, 0x89EC, 0xB431, + 0x89ED, 0xB432, 0x89EE, 0xB433, 0x89EF, 0xB435, 0x89F0, 0xB436, 0x89F1, 0xB437, 0x89F2, 0xB438, 0x89F3, 0xB439, 0x89F4, 0xB43A, + 0x89F5, 0xB43B, 0x89F6, 0xB43C, 0x89F7, 0xB43D, 0x89F8, 0xB43E, 0x89F9, 0xB43F, 0x89FA, 0xB440, 0x89FB, 0xB441, 0x89FC, 0xB442, + 0x89FD, 0xB443, 0x89FE, 0xB444, 0x8A41, 0xB445, 0x8A42, 0xB446, 0x8A43, 0xB447, 0x8A44, 0xB448, 0x8A45, 0xB449, 0x8A46, 0xB44A, + 0x8A47, 0xB44B, 0x8A48, 0xB44C, 0x8A49, 0xB44D, 0x8A4A, 0xB44E, 0x8A4B, 0xB44F, 0x8A4C, 0xB452, 0x8A4D, 0xB453, 0x8A4E, 0xB455, + 0x8A4F, 0xB456, 0x8A50, 0xB457, 0x8A51, 0xB459, 0x8A52, 0xB45A, 0x8A53, 0xB45B, 0x8A54, 0xB45C, 0x8A55, 0xB45D, 0x8A56, 0xB45E, + 0x8A57, 0xB45F, 0x8A58, 0xB462, 0x8A59, 0xB464, 0x8A5A, 0xB466, 0x8A61, 0xB467, 0x8A62, 0xB468, 0x8A63, 0xB469, 0x8A64, 0xB46A, + 0x8A65, 0xB46B, 0x8A66, 0xB46D, 0x8A67, 0xB46E, 0x8A68, 0xB46F, 0x8A69, 0xB470, 0x8A6A, 0xB471, 0x8A6B, 0xB472, 0x8A6C, 0xB473, + 0x8A6D, 0xB474, 0x8A6E, 0xB475, 0x8A6F, 0xB476, 0x8A70, 0xB477, 0x8A71, 0xB478, 0x8A72, 0xB479, 0x8A73, 0xB47A, 0x8A74, 0xB47B, + 0x8A75, 0xB47C, 0x8A76, 0xB47D, 0x8A77, 0xB47E, 0x8A78, 0xB47F, 0x8A79, 0xB481, 0x8A7A, 0xB482, 0x8A81, 0xB483, 0x8A82, 0xB484, + 0x8A83, 0xB485, 0x8A84, 0xB486, 0x8A85, 0xB487, 0x8A86, 0xB489, 0x8A87, 0xB48A, 0x8A88, 0xB48B, 0x8A89, 0xB48C, 0x8A8A, 0xB48D, + 0x8A8B, 0xB48E, 0x8A8C, 0xB48F, 0x8A8D, 0xB490, 0x8A8E, 0xB491, 0x8A8F, 0xB492, 0x8A90, 0xB493, 0x8A91, 0xB494, 0x8A92, 0xB495, + 0x8A93, 0xB496, 0x8A94, 0xB497, 0x8A95, 0xB498, 0x8A96, 0xB499, 0x8A97, 0xB49A, 0x8A98, 0xB49B, 0x8A99, 0xB49C, 0x8A9A, 0xB49E, + 0x8A9B, 0xB49F, 0x8A9C, 0xB4A0, 0x8A9D, 0xB4A1, 0x8A9E, 0xB4A2, 0x8A9F, 0xB4A3, 0x8AA0, 0xB4A5, 0x8AA1, 0xB4A6, 0x8AA2, 0xB4A7, + 0x8AA3, 0xB4A9, 0x8AA4, 0xB4AA, 0x8AA5, 0xB4AB, 0x8AA6, 0xB4AD, 0x8AA7, 0xB4AE, 0x8AA8, 0xB4AF, 0x8AA9, 0xB4B0, 0x8AAA, 0xB4B1, + 0x8AAB, 0xB4B2, 0x8AAC, 0xB4B3, 0x8AAD, 0xB4B4, 0x8AAE, 0xB4B6, 0x8AAF, 0xB4B8, 0x8AB0, 0xB4BA, 0x8AB1, 0xB4BB, 0x8AB2, 0xB4BC, + 0x8AB3, 0xB4BD, 0x8AB4, 0xB4BE, 0x8AB5, 0xB4BF, 0x8AB6, 0xB4C1, 0x8AB7, 0xB4C2, 0x8AB8, 0xB4C3, 0x8AB9, 0xB4C5, 0x8ABA, 0xB4C6, + 0x8ABB, 0xB4C7, 0x8ABC, 0xB4C9, 0x8ABD, 0xB4CA, 0x8ABE, 0xB4CB, 0x8ABF, 0xB4CC, 0x8AC0, 0xB4CD, 0x8AC1, 0xB4CE, 0x8AC2, 0xB4CF, + 0x8AC3, 0xB4D1, 0x8AC4, 0xB4D2, 0x8AC5, 0xB4D3, 0x8AC6, 0xB4D4, 0x8AC7, 0xB4D6, 0x8AC8, 0xB4D7, 0x8AC9, 0xB4D8, 0x8ACA, 0xB4D9, + 0x8ACB, 0xB4DA, 0x8ACC, 0xB4DB, 0x8ACD, 0xB4DE, 0x8ACE, 0xB4DF, 0x8ACF, 0xB4E1, 0x8AD0, 0xB4E2, 0x8AD1, 0xB4E5, 0x8AD2, 0xB4E7, + 0x8AD3, 0xB4E8, 0x8AD4, 0xB4E9, 0x8AD5, 0xB4EA, 0x8AD6, 0xB4EB, 0x8AD7, 0xB4EE, 0x8AD8, 0xB4F0, 0x8AD9, 0xB4F2, 0x8ADA, 0xB4F3, + 0x8ADB, 0xB4F4, 0x8ADC, 0xB4F5, 0x8ADD, 0xB4F6, 0x8ADE, 0xB4F7, 0x8ADF, 0xB4F9, 0x8AE0, 0xB4FA, 0x8AE1, 0xB4FB, 0x8AE2, 0xB4FC, + 0x8AE3, 0xB4FD, 0x8AE4, 0xB4FE, 0x8AE5, 0xB4FF, 0x8AE6, 0xB500, 0x8AE7, 0xB501, 0x8AE8, 0xB502, 0x8AE9, 0xB503, 0x8AEA, 0xB504, + 0x8AEB, 0xB505, 0x8AEC, 0xB506, 0x8AED, 0xB507, 0x8AEE, 0xB508, 0x8AEF, 0xB509, 0x8AF0, 0xB50A, 0x8AF1, 0xB50B, 0x8AF2, 0xB50C, + 0x8AF3, 0xB50D, 0x8AF4, 0xB50E, 0x8AF5, 0xB50F, 0x8AF6, 0xB510, 0x8AF7, 0xB511, 0x8AF8, 0xB512, 0x8AF9, 0xB513, 0x8AFA, 0xB516, + 0x8AFB, 0xB517, 0x8AFC, 0xB519, 0x8AFD, 0xB51A, 0x8AFE, 0xB51D, 0x8B41, 0xB51E, 0x8B42, 0xB51F, 0x8B43, 0xB520, 0x8B44, 0xB521, + 0x8B45, 0xB522, 0x8B46, 0xB523, 0x8B47, 0xB526, 0x8B48, 0xB52B, 0x8B49, 0xB52C, 0x8B4A, 0xB52D, 0x8B4B, 0xB52E, 0x8B4C, 0xB52F, + 0x8B4D, 0xB532, 0x8B4E, 0xB533, 0x8B4F, 0xB535, 0x8B50, 0xB536, 0x8B51, 0xB537, 0x8B52, 0xB539, 0x8B53, 0xB53A, 0x8B54, 0xB53B, + 0x8B55, 0xB53C, 0x8B56, 0xB53D, 0x8B57, 0xB53E, 0x8B58, 0xB53F, 0x8B59, 0xB542, 0x8B5A, 0xB546, 0x8B61, 0xB547, 0x8B62, 0xB548, + 0x8B63, 0xB549, 0x8B64, 0xB54A, 0x8B65, 0xB54E, 0x8B66, 0xB54F, 0x8B67, 0xB551, 0x8B68, 0xB552, 0x8B69, 0xB553, 0x8B6A, 0xB555, + 0x8B6B, 0xB556, 0x8B6C, 0xB557, 0x8B6D, 0xB558, 0x8B6E, 0xB559, 0x8B6F, 0xB55A, 0x8B70, 0xB55B, 0x8B71, 0xB55E, 0x8B72, 0xB562, + 0x8B73, 0xB563, 0x8B74, 0xB564, 0x8B75, 0xB565, 0x8B76, 0xB566, 0x8B77, 0xB567, 0x8B78, 0xB568, 0x8B79, 0xB569, 0x8B7A, 0xB56A, + 0x8B81, 0xB56B, 0x8B82, 0xB56C, 0x8B83, 0xB56D, 0x8B84, 0xB56E, 0x8B85, 0xB56F, 0x8B86, 0xB570, 0x8B87, 0xB571, 0x8B88, 0xB572, + 0x8B89, 0xB573, 0x8B8A, 0xB574, 0x8B8B, 0xB575, 0x8B8C, 0xB576, 0x8B8D, 0xB577, 0x8B8E, 0xB578, 0x8B8F, 0xB579, 0x8B90, 0xB57A, + 0x8B91, 0xB57B, 0x8B92, 0xB57C, 0x8B93, 0xB57D, 0x8B94, 0xB57E, 0x8B95, 0xB57F, 0x8B96, 0xB580, 0x8B97, 0xB581, 0x8B98, 0xB582, + 0x8B99, 0xB583, 0x8B9A, 0xB584, 0x8B9B, 0xB585, 0x8B9C, 0xB586, 0x8B9D, 0xB587, 0x8B9E, 0xB588, 0x8B9F, 0xB589, 0x8BA0, 0xB58A, + 0x8BA1, 0xB58B, 0x8BA2, 0xB58C, 0x8BA3, 0xB58D, 0x8BA4, 0xB58E, 0x8BA5, 0xB58F, 0x8BA6, 0xB590, 0x8BA7, 0xB591, 0x8BA8, 0xB592, + 0x8BA9, 0xB593, 0x8BAA, 0xB594, 0x8BAB, 0xB595, 0x8BAC, 0xB596, 0x8BAD, 0xB597, 0x8BAE, 0xB598, 0x8BAF, 0xB599, 0x8BB0, 0xB59A, + 0x8BB1, 0xB59B, 0x8BB2, 0xB59C, 0x8BB3, 0xB59D, 0x8BB4, 0xB59E, 0x8BB5, 0xB59F, 0x8BB6, 0xB5A2, 0x8BB7, 0xB5A3, 0x8BB8, 0xB5A5, + 0x8BB9, 0xB5A6, 0x8BBA, 0xB5A7, 0x8BBB, 0xB5A9, 0x8BBC, 0xB5AC, 0x8BBD, 0xB5AD, 0x8BBE, 0xB5AE, 0x8BBF, 0xB5AF, 0x8BC0, 0xB5B2, + 0x8BC1, 0xB5B6, 0x8BC2, 0xB5B7, 0x8BC3, 0xB5B8, 0x8BC4, 0xB5B9, 0x8BC5, 0xB5BA, 0x8BC6, 0xB5BE, 0x8BC7, 0xB5BF, 0x8BC8, 0xB5C1, + 0x8BC9, 0xB5C2, 0x8BCA, 0xB5C3, 0x8BCB, 0xB5C5, 0x8BCC, 0xB5C6, 0x8BCD, 0xB5C7, 0x8BCE, 0xB5C8, 0x8BCF, 0xB5C9, 0x8BD0, 0xB5CA, + 0x8BD1, 0xB5CB, 0x8BD2, 0xB5CE, 0x8BD3, 0xB5D2, 0x8BD4, 0xB5D3, 0x8BD5, 0xB5D4, 0x8BD6, 0xB5D5, 0x8BD7, 0xB5D6, 0x8BD8, 0xB5D7, + 0x8BD9, 0xB5D9, 0x8BDA, 0xB5DA, 0x8BDB, 0xB5DB, 0x8BDC, 0xB5DC, 0x8BDD, 0xB5DD, 0x8BDE, 0xB5DE, 0x8BDF, 0xB5DF, 0x8BE0, 0xB5E0, + 0x8BE1, 0xB5E1, 0x8BE2, 0xB5E2, 0x8BE3, 0xB5E3, 0x8BE4, 0xB5E4, 0x8BE5, 0xB5E5, 0x8BE6, 0xB5E6, 0x8BE7, 0xB5E7, 0x8BE8, 0xB5E8, + 0x8BE9, 0xB5E9, 0x8BEA, 0xB5EA, 0x8BEB, 0xB5EB, 0x8BEC, 0xB5ED, 0x8BED, 0xB5EE, 0x8BEE, 0xB5EF, 0x8BEF, 0xB5F0, 0x8BF0, 0xB5F1, + 0x8BF1, 0xB5F2, 0x8BF2, 0xB5F3, 0x8BF3, 0xB5F4, 0x8BF4, 0xB5F5, 0x8BF5, 0xB5F6, 0x8BF6, 0xB5F7, 0x8BF7, 0xB5F8, 0x8BF8, 0xB5F9, + 0x8BF9, 0xB5FA, 0x8BFA, 0xB5FB, 0x8BFB, 0xB5FC, 0x8BFC, 0xB5FD, 0x8BFD, 0xB5FE, 0x8BFE, 0xB5FF, 0x8C41, 0xB600, 0x8C42, 0xB601, + 0x8C43, 0xB602, 0x8C44, 0xB603, 0x8C45, 0xB604, 0x8C46, 0xB605, 0x8C47, 0xB606, 0x8C48, 0xB607, 0x8C49, 0xB608, 0x8C4A, 0xB609, + 0x8C4B, 0xB60A, 0x8C4C, 0xB60B, 0x8C4D, 0xB60C, 0x8C4E, 0xB60D, 0x8C4F, 0xB60E, 0x8C50, 0xB60F, 0x8C51, 0xB612, 0x8C52, 0xB613, + 0x8C53, 0xB615, 0x8C54, 0xB616, 0x8C55, 0xB617, 0x8C56, 0xB619, 0x8C57, 0xB61A, 0x8C58, 0xB61B, 0x8C59, 0xB61C, 0x8C5A, 0xB61D, + 0x8C61, 0xB61E, 0x8C62, 0xB61F, 0x8C63, 0xB620, 0x8C64, 0xB621, 0x8C65, 0xB622, 0x8C66, 0xB623, 0x8C67, 0xB624, 0x8C68, 0xB626, + 0x8C69, 0xB627, 0x8C6A, 0xB628, 0x8C6B, 0xB629, 0x8C6C, 0xB62A, 0x8C6D, 0xB62B, 0x8C6E, 0xB62D, 0x8C6F, 0xB62E, 0x8C70, 0xB62F, + 0x8C71, 0xB630, 0x8C72, 0xB631, 0x8C73, 0xB632, 0x8C74, 0xB633, 0x8C75, 0xB635, 0x8C76, 0xB636, 0x8C77, 0xB637, 0x8C78, 0xB638, + 0x8C79, 0xB639, 0x8C7A, 0xB63A, 0x8C81, 0xB63B, 0x8C82, 0xB63C, 0x8C83, 0xB63D, 0x8C84, 0xB63E, 0x8C85, 0xB63F, 0x8C86, 0xB640, + 0x8C87, 0xB641, 0x8C88, 0xB642, 0x8C89, 0xB643, 0x8C8A, 0xB644, 0x8C8B, 0xB645, 0x8C8C, 0xB646, 0x8C8D, 0xB647, 0x8C8E, 0xB649, + 0x8C8F, 0xB64A, 0x8C90, 0xB64B, 0x8C91, 0xB64C, 0x8C92, 0xB64D, 0x8C93, 0xB64E, 0x8C94, 0xB64F, 0x8C95, 0xB650, 0x8C96, 0xB651, + 0x8C97, 0xB652, 0x8C98, 0xB653, 0x8C99, 0xB654, 0x8C9A, 0xB655, 0x8C9B, 0xB656, 0x8C9C, 0xB657, 0x8C9D, 0xB658, 0x8C9E, 0xB659, + 0x8C9F, 0xB65A, 0x8CA0, 0xB65B, 0x8CA1, 0xB65C, 0x8CA2, 0xB65D, 0x8CA3, 0xB65E, 0x8CA4, 0xB65F, 0x8CA5, 0xB660, 0x8CA6, 0xB661, + 0x8CA7, 0xB662, 0x8CA8, 0xB663, 0x8CA9, 0xB665, 0x8CAA, 0xB666, 0x8CAB, 0xB667, 0x8CAC, 0xB669, 0x8CAD, 0xB66A, 0x8CAE, 0xB66B, + 0x8CAF, 0xB66C, 0x8CB0, 0xB66D, 0x8CB1, 0xB66E, 0x8CB2, 0xB66F, 0x8CB3, 0xB670, 0x8CB4, 0xB671, 0x8CB5, 0xB672, 0x8CB6, 0xB673, + 0x8CB7, 0xB674, 0x8CB8, 0xB675, 0x8CB9, 0xB676, 0x8CBA, 0xB677, 0x8CBB, 0xB678, 0x8CBC, 0xB679, 0x8CBD, 0xB67A, 0x8CBE, 0xB67B, + 0x8CBF, 0xB67C, 0x8CC0, 0xB67D, 0x8CC1, 0xB67E, 0x8CC2, 0xB67F, 0x8CC3, 0xB680, 0x8CC4, 0xB681, 0x8CC5, 0xB682, 0x8CC6, 0xB683, + 0x8CC7, 0xB684, 0x8CC8, 0xB685, 0x8CC9, 0xB686, 0x8CCA, 0xB687, 0x8CCB, 0xB688, 0x8CCC, 0xB689, 0x8CCD, 0xB68A, 0x8CCE, 0xB68B, + 0x8CCF, 0xB68C, 0x8CD0, 0xB68D, 0x8CD1, 0xB68E, 0x8CD2, 0xB68F, 0x8CD3, 0xB690, 0x8CD4, 0xB691, 0x8CD5, 0xB692, 0x8CD6, 0xB693, + 0x8CD7, 0xB694, 0x8CD8, 0xB695, 0x8CD9, 0xB696, 0x8CDA, 0xB697, 0x8CDB, 0xB698, 0x8CDC, 0xB699, 0x8CDD, 0xB69A, 0x8CDE, 0xB69B, + 0x8CDF, 0xB69E, 0x8CE0, 0xB69F, 0x8CE1, 0xB6A1, 0x8CE2, 0xB6A2, 0x8CE3, 0xB6A3, 0x8CE4, 0xB6A5, 0x8CE5, 0xB6A6, 0x8CE6, 0xB6A7, + 0x8CE7, 0xB6A8, 0x8CE8, 0xB6A9, 0x8CE9, 0xB6AA, 0x8CEA, 0xB6AD, 0x8CEB, 0xB6AE, 0x8CEC, 0xB6AF, 0x8CED, 0xB6B0, 0x8CEE, 0xB6B2, + 0x8CEF, 0xB6B3, 0x8CF0, 0xB6B4, 0x8CF1, 0xB6B5, 0x8CF2, 0xB6B6, 0x8CF3, 0xB6B7, 0x8CF4, 0xB6B8, 0x8CF5, 0xB6B9, 0x8CF6, 0xB6BA, + 0x8CF7, 0xB6BB, 0x8CF8, 0xB6BC, 0x8CF9, 0xB6BD, 0x8CFA, 0xB6BE, 0x8CFB, 0xB6BF, 0x8CFC, 0xB6C0, 0x8CFD, 0xB6C1, 0x8CFE, 0xB6C2, + 0x8D41, 0xB6C3, 0x8D42, 0xB6C4, 0x8D43, 0xB6C5, 0x8D44, 0xB6C6, 0x8D45, 0xB6C7, 0x8D46, 0xB6C8, 0x8D47, 0xB6C9, 0x8D48, 0xB6CA, + 0x8D49, 0xB6CB, 0x8D4A, 0xB6CC, 0x8D4B, 0xB6CD, 0x8D4C, 0xB6CE, 0x8D4D, 0xB6CF, 0x8D4E, 0xB6D0, 0x8D4F, 0xB6D1, 0x8D50, 0xB6D2, + 0x8D51, 0xB6D3, 0x8D52, 0xB6D5, 0x8D53, 0xB6D6, 0x8D54, 0xB6D7, 0x8D55, 0xB6D8, 0x8D56, 0xB6D9, 0x8D57, 0xB6DA, 0x8D58, 0xB6DB, + 0x8D59, 0xB6DC, 0x8D5A, 0xB6DD, 0x8D61, 0xB6DE, 0x8D62, 0xB6DF, 0x8D63, 0xB6E0, 0x8D64, 0xB6E1, 0x8D65, 0xB6E2, 0x8D66, 0xB6E3, + 0x8D67, 0xB6E4, 0x8D68, 0xB6E5, 0x8D69, 0xB6E6, 0x8D6A, 0xB6E7, 0x8D6B, 0xB6E8, 0x8D6C, 0xB6E9, 0x8D6D, 0xB6EA, 0x8D6E, 0xB6EB, + 0x8D6F, 0xB6EC, 0x8D70, 0xB6ED, 0x8D71, 0xB6EE, 0x8D72, 0xB6EF, 0x8D73, 0xB6F1, 0x8D74, 0xB6F2, 0x8D75, 0xB6F3, 0x8D76, 0xB6F5, + 0x8D77, 0xB6F6, 0x8D78, 0xB6F7, 0x8D79, 0xB6F9, 0x8D7A, 0xB6FA, 0x8D81, 0xB6FB, 0x8D82, 0xB6FC, 0x8D83, 0xB6FD, 0x8D84, 0xB6FE, + 0x8D85, 0xB6FF, 0x8D86, 0xB702, 0x8D87, 0xB703, 0x8D88, 0xB704, 0x8D89, 0xB706, 0x8D8A, 0xB707, 0x8D8B, 0xB708, 0x8D8C, 0xB709, + 0x8D8D, 0xB70A, 0x8D8E, 0xB70B, 0x8D8F, 0xB70C, 0x8D90, 0xB70D, 0x8D91, 0xB70E, 0x8D92, 0xB70F, 0x8D93, 0xB710, 0x8D94, 0xB711, + 0x8D95, 0xB712, 0x8D96, 0xB713, 0x8D97, 0xB714, 0x8D98, 0xB715, 0x8D99, 0xB716, 0x8D9A, 0xB717, 0x8D9B, 0xB718, 0x8D9C, 0xB719, + 0x8D9D, 0xB71A, 0x8D9E, 0xB71B, 0x8D9F, 0xB71C, 0x8DA0, 0xB71D, 0x8DA1, 0xB71E, 0x8DA2, 0xB71F, 0x8DA3, 0xB720, 0x8DA4, 0xB721, + 0x8DA5, 0xB722, 0x8DA6, 0xB723, 0x8DA7, 0xB724, 0x8DA8, 0xB725, 0x8DA9, 0xB726, 0x8DAA, 0xB727, 0x8DAB, 0xB72A, 0x8DAC, 0xB72B, + 0x8DAD, 0xB72D, 0x8DAE, 0xB72E, 0x8DAF, 0xB731, 0x8DB0, 0xB732, 0x8DB1, 0xB733, 0x8DB2, 0xB734, 0x8DB3, 0xB735, 0x8DB4, 0xB736, + 0x8DB5, 0xB737, 0x8DB6, 0xB73A, 0x8DB7, 0xB73C, 0x8DB8, 0xB73D, 0x8DB9, 0xB73E, 0x8DBA, 0xB73F, 0x8DBB, 0xB740, 0x8DBC, 0xB741, + 0x8DBD, 0xB742, 0x8DBE, 0xB743, 0x8DBF, 0xB745, 0x8DC0, 0xB746, 0x8DC1, 0xB747, 0x8DC2, 0xB749, 0x8DC3, 0xB74A, 0x8DC4, 0xB74B, + 0x8DC5, 0xB74D, 0x8DC6, 0xB74E, 0x8DC7, 0xB74F, 0x8DC8, 0xB750, 0x8DC9, 0xB751, 0x8DCA, 0xB752, 0x8DCB, 0xB753, 0x8DCC, 0xB756, + 0x8DCD, 0xB757, 0x8DCE, 0xB758, 0x8DCF, 0xB759, 0x8DD0, 0xB75A, 0x8DD1, 0xB75B, 0x8DD2, 0xB75C, 0x8DD3, 0xB75D, 0x8DD4, 0xB75E, + 0x8DD5, 0xB75F, 0x8DD6, 0xB761, 0x8DD7, 0xB762, 0x8DD8, 0xB763, 0x8DD9, 0xB765, 0x8DDA, 0xB766, 0x8DDB, 0xB767, 0x8DDC, 0xB769, + 0x8DDD, 0xB76A, 0x8DDE, 0xB76B, 0x8DDF, 0xB76C, 0x8DE0, 0xB76D, 0x8DE1, 0xB76E, 0x8DE2, 0xB76F, 0x8DE3, 0xB772, 0x8DE4, 0xB774, + 0x8DE5, 0xB776, 0x8DE6, 0xB777, 0x8DE7, 0xB778, 0x8DE8, 0xB779, 0x8DE9, 0xB77A, 0x8DEA, 0xB77B, 0x8DEB, 0xB77E, 0x8DEC, 0xB77F, + 0x8DED, 0xB781, 0x8DEE, 0xB782, 0x8DEF, 0xB783, 0x8DF0, 0xB785, 0x8DF1, 0xB786, 0x8DF2, 0xB787, 0x8DF3, 0xB788, 0x8DF4, 0xB789, + 0x8DF5, 0xB78A, 0x8DF6, 0xB78B, 0x8DF7, 0xB78E, 0x8DF8, 0xB793, 0x8DF9, 0xB794, 0x8DFA, 0xB795, 0x8DFB, 0xB79A, 0x8DFC, 0xB79B, + 0x8DFD, 0xB79D, 0x8DFE, 0xB79E, 0x8E41, 0xB79F, 0x8E42, 0xB7A1, 0x8E43, 0xB7A2, 0x8E44, 0xB7A3, 0x8E45, 0xB7A4, 0x8E46, 0xB7A5, + 0x8E47, 0xB7A6, 0x8E48, 0xB7A7, 0x8E49, 0xB7AA, 0x8E4A, 0xB7AE, 0x8E4B, 0xB7AF, 0x8E4C, 0xB7B0, 0x8E4D, 0xB7B1, 0x8E4E, 0xB7B2, + 0x8E4F, 0xB7B3, 0x8E50, 0xB7B6, 0x8E51, 0xB7B7, 0x8E52, 0xB7B9, 0x8E53, 0xB7BA, 0x8E54, 0xB7BB, 0x8E55, 0xB7BC, 0x8E56, 0xB7BD, + 0x8E57, 0xB7BE, 0x8E58, 0xB7BF, 0x8E59, 0xB7C0, 0x8E5A, 0xB7C1, 0x8E61, 0xB7C2, 0x8E62, 0xB7C3, 0x8E63, 0xB7C4, 0x8E64, 0xB7C5, + 0x8E65, 0xB7C6, 0x8E66, 0xB7C8, 0x8E67, 0xB7CA, 0x8E68, 0xB7CB, 0x8E69, 0xB7CC, 0x8E6A, 0xB7CD, 0x8E6B, 0xB7CE, 0x8E6C, 0xB7CF, + 0x8E6D, 0xB7D0, 0x8E6E, 0xB7D1, 0x8E6F, 0xB7D2, 0x8E70, 0xB7D3, 0x8E71, 0xB7D4, 0x8E72, 0xB7D5, 0x8E73, 0xB7D6, 0x8E74, 0xB7D7, + 0x8E75, 0xB7D8, 0x8E76, 0xB7D9, 0x8E77, 0xB7DA, 0x8E78, 0xB7DB, 0x8E79, 0xB7DC, 0x8E7A, 0xB7DD, 0x8E81, 0xB7DE, 0x8E82, 0xB7DF, + 0x8E83, 0xB7E0, 0x8E84, 0xB7E1, 0x8E85, 0xB7E2, 0x8E86, 0xB7E3, 0x8E87, 0xB7E4, 0x8E88, 0xB7E5, 0x8E89, 0xB7E6, 0x8E8A, 0xB7E7, + 0x8E8B, 0xB7E8, 0x8E8C, 0xB7E9, 0x8E8D, 0xB7EA, 0x8E8E, 0xB7EB, 0x8E8F, 0xB7EE, 0x8E90, 0xB7EF, 0x8E91, 0xB7F1, 0x8E92, 0xB7F2, + 0x8E93, 0xB7F3, 0x8E94, 0xB7F5, 0x8E95, 0xB7F6, 0x8E96, 0xB7F7, 0x8E97, 0xB7F8, 0x8E98, 0xB7F9, 0x8E99, 0xB7FA, 0x8E9A, 0xB7FB, + 0x8E9B, 0xB7FE, 0x8E9C, 0xB802, 0x8E9D, 0xB803, 0x8E9E, 0xB804, 0x8E9F, 0xB805, 0x8EA0, 0xB806, 0x8EA1, 0xB80A, 0x8EA2, 0xB80B, + 0x8EA3, 0xB80D, 0x8EA4, 0xB80E, 0x8EA5, 0xB80F, 0x8EA6, 0xB811, 0x8EA7, 0xB812, 0x8EA8, 0xB813, 0x8EA9, 0xB814, 0x8EAA, 0xB815, + 0x8EAB, 0xB816, 0x8EAC, 0xB817, 0x8EAD, 0xB81A, 0x8EAE, 0xB81C, 0x8EAF, 0xB81E, 0x8EB0, 0xB81F, 0x8EB1, 0xB820, 0x8EB2, 0xB821, + 0x8EB3, 0xB822, 0x8EB4, 0xB823, 0x8EB5, 0xB826, 0x8EB6, 0xB827, 0x8EB7, 0xB829, 0x8EB8, 0xB82A, 0x8EB9, 0xB82B, 0x8EBA, 0xB82D, + 0x8EBB, 0xB82E, 0x8EBC, 0xB82F, 0x8EBD, 0xB830, 0x8EBE, 0xB831, 0x8EBF, 0xB832, 0x8EC0, 0xB833, 0x8EC1, 0xB836, 0x8EC2, 0xB83A, + 0x8EC3, 0xB83B, 0x8EC4, 0xB83C, 0x8EC5, 0xB83D, 0x8EC6, 0xB83E, 0x8EC7, 0xB83F, 0x8EC8, 0xB841, 0x8EC9, 0xB842, 0x8ECA, 0xB843, + 0x8ECB, 0xB845, 0x8ECC, 0xB846, 0x8ECD, 0xB847, 0x8ECE, 0xB848, 0x8ECF, 0xB849, 0x8ED0, 0xB84A, 0x8ED1, 0xB84B, 0x8ED2, 0xB84C, + 0x8ED3, 0xB84D, 0x8ED4, 0xB84E, 0x8ED5, 0xB84F, 0x8ED6, 0xB850, 0x8ED7, 0xB852, 0x8ED8, 0xB854, 0x8ED9, 0xB855, 0x8EDA, 0xB856, + 0x8EDB, 0xB857, 0x8EDC, 0xB858, 0x8EDD, 0xB859, 0x8EDE, 0xB85A, 0x8EDF, 0xB85B, 0x8EE0, 0xB85E, 0x8EE1, 0xB85F, 0x8EE2, 0xB861, + 0x8EE3, 0xB862, 0x8EE4, 0xB863, 0x8EE5, 0xB865, 0x8EE6, 0xB866, 0x8EE7, 0xB867, 0x8EE8, 0xB868, 0x8EE9, 0xB869, 0x8EEA, 0xB86A, + 0x8EEB, 0xB86B, 0x8EEC, 0xB86E, 0x8EED, 0xB870, 0x8EEE, 0xB872, 0x8EEF, 0xB873, 0x8EF0, 0xB874, 0x8EF1, 0xB875, 0x8EF2, 0xB876, + 0x8EF3, 0xB877, 0x8EF4, 0xB879, 0x8EF5, 0xB87A, 0x8EF6, 0xB87B, 0x8EF7, 0xB87D, 0x8EF8, 0xB87E, 0x8EF9, 0xB87F, 0x8EFA, 0xB880, + 0x8EFB, 0xB881, 0x8EFC, 0xB882, 0x8EFD, 0xB883, 0x8EFE, 0xB884, 0x8F41, 0xB885, 0x8F42, 0xB886, 0x8F43, 0xB887, 0x8F44, 0xB888, + 0x8F45, 0xB889, 0x8F46, 0xB88A, 0x8F47, 0xB88B, 0x8F48, 0xB88C, 0x8F49, 0xB88E, 0x8F4A, 0xB88F, 0x8F4B, 0xB890, 0x8F4C, 0xB891, + 0x8F4D, 0xB892, 0x8F4E, 0xB893, 0x8F4F, 0xB894, 0x8F50, 0xB895, 0x8F51, 0xB896, 0x8F52, 0xB897, 0x8F53, 0xB898, 0x8F54, 0xB899, + 0x8F55, 0xB89A, 0x8F56, 0xB89B, 0x8F57, 0xB89C, 0x8F58, 0xB89D, 0x8F59, 0xB89E, 0x8F5A, 0xB89F, 0x8F61, 0xB8A0, 0x8F62, 0xB8A1, + 0x8F63, 0xB8A2, 0x8F64, 0xB8A3, 0x8F65, 0xB8A4, 0x8F66, 0xB8A5, 0x8F67, 0xB8A6, 0x8F68, 0xB8A7, 0x8F69, 0xB8A9, 0x8F6A, 0xB8AA, + 0x8F6B, 0xB8AB, 0x8F6C, 0xB8AC, 0x8F6D, 0xB8AD, 0x8F6E, 0xB8AE, 0x8F6F, 0xB8AF, 0x8F70, 0xB8B1, 0x8F71, 0xB8B2, 0x8F72, 0xB8B3, + 0x8F73, 0xB8B5, 0x8F74, 0xB8B6, 0x8F75, 0xB8B7, 0x8F76, 0xB8B9, 0x8F77, 0xB8BA, 0x8F78, 0xB8BB, 0x8F79, 0xB8BC, 0x8F7A, 0xB8BD, + 0x8F81, 0xB8BE, 0x8F82, 0xB8BF, 0x8F83, 0xB8C2, 0x8F84, 0xB8C4, 0x8F85, 0xB8C6, 0x8F86, 0xB8C7, 0x8F87, 0xB8C8, 0x8F88, 0xB8C9, + 0x8F89, 0xB8CA, 0x8F8A, 0xB8CB, 0x8F8B, 0xB8CD, 0x8F8C, 0xB8CE, 0x8F8D, 0xB8CF, 0x8F8E, 0xB8D1, 0x8F8F, 0xB8D2, 0x8F90, 0xB8D3, + 0x8F91, 0xB8D5, 0x8F92, 0xB8D6, 0x8F93, 0xB8D7, 0x8F94, 0xB8D8, 0x8F95, 0xB8D9, 0x8F96, 0xB8DA, 0x8F97, 0xB8DB, 0x8F98, 0xB8DC, + 0x8F99, 0xB8DE, 0x8F9A, 0xB8E0, 0x8F9B, 0xB8E2, 0x8F9C, 0xB8E3, 0x8F9D, 0xB8E4, 0x8F9E, 0xB8E5, 0x8F9F, 0xB8E6, 0x8FA0, 0xB8E7, + 0x8FA1, 0xB8EA, 0x8FA2, 0xB8EB, 0x8FA3, 0xB8ED, 0x8FA4, 0xB8EE, 0x8FA5, 0xB8EF, 0x8FA6, 0xB8F1, 0x8FA7, 0xB8F2, 0x8FA8, 0xB8F3, + 0x8FA9, 0xB8F4, 0x8FAA, 0xB8F5, 0x8FAB, 0xB8F6, 0x8FAC, 0xB8F7, 0x8FAD, 0xB8FA, 0x8FAE, 0xB8FC, 0x8FAF, 0xB8FE, 0x8FB0, 0xB8FF, + 0x8FB1, 0xB900, 0x8FB2, 0xB901, 0x8FB3, 0xB902, 0x8FB4, 0xB903, 0x8FB5, 0xB905, 0x8FB6, 0xB906, 0x8FB7, 0xB907, 0x8FB8, 0xB908, + 0x8FB9, 0xB909, 0x8FBA, 0xB90A, 0x8FBB, 0xB90B, 0x8FBC, 0xB90C, 0x8FBD, 0xB90D, 0x8FBE, 0xB90E, 0x8FBF, 0xB90F, 0x8FC0, 0xB910, + 0x8FC1, 0xB911, 0x8FC2, 0xB912, 0x8FC3, 0xB913, 0x8FC4, 0xB914, 0x8FC5, 0xB915, 0x8FC6, 0xB916, 0x8FC7, 0xB917, 0x8FC8, 0xB919, + 0x8FC9, 0xB91A, 0x8FCA, 0xB91B, 0x8FCB, 0xB91C, 0x8FCC, 0xB91D, 0x8FCD, 0xB91E, 0x8FCE, 0xB91F, 0x8FCF, 0xB921, 0x8FD0, 0xB922, + 0x8FD1, 0xB923, 0x8FD2, 0xB924, 0x8FD3, 0xB925, 0x8FD4, 0xB926, 0x8FD5, 0xB927, 0x8FD6, 0xB928, 0x8FD7, 0xB929, 0x8FD8, 0xB92A, + 0x8FD9, 0xB92B, 0x8FDA, 0xB92C, 0x8FDB, 0xB92D, 0x8FDC, 0xB92E, 0x8FDD, 0xB92F, 0x8FDE, 0xB930, 0x8FDF, 0xB931, 0x8FE0, 0xB932, + 0x8FE1, 0xB933, 0x8FE2, 0xB934, 0x8FE3, 0xB935, 0x8FE4, 0xB936, 0x8FE5, 0xB937, 0x8FE6, 0xB938, 0x8FE7, 0xB939, 0x8FE8, 0xB93A, + 0x8FE9, 0xB93B, 0x8FEA, 0xB93E, 0x8FEB, 0xB93F, 0x8FEC, 0xB941, 0x8FED, 0xB942, 0x8FEE, 0xB943, 0x8FEF, 0xB945, 0x8FF0, 0xB946, + 0x8FF1, 0xB947, 0x8FF2, 0xB948, 0x8FF3, 0xB949, 0x8FF4, 0xB94A, 0x8FF5, 0xB94B, 0x8FF6, 0xB94D, 0x8FF7, 0xB94E, 0x8FF8, 0xB950, + 0x8FF9, 0xB952, 0x8FFA, 0xB953, 0x8FFB, 0xB954, 0x8FFC, 0xB955, 0x8FFD, 0xB956, 0x8FFE, 0xB957, 0x9041, 0xB95A, 0x9042, 0xB95B, + 0x9043, 0xB95D, 0x9044, 0xB95E, 0x9045, 0xB95F, 0x9046, 0xB961, 0x9047, 0xB962, 0x9048, 0xB963, 0x9049, 0xB964, 0x904A, 0xB965, + 0x904B, 0xB966, 0x904C, 0xB967, 0x904D, 0xB96A, 0x904E, 0xB96C, 0x904F, 0xB96E, 0x9050, 0xB96F, 0x9051, 0xB970, 0x9052, 0xB971, + 0x9053, 0xB972, 0x9054, 0xB973, 0x9055, 0xB976, 0x9056, 0xB977, 0x9057, 0xB979, 0x9058, 0xB97A, 0x9059, 0xB97B, 0x905A, 0xB97D, + 0x9061, 0xB97E, 0x9062, 0xB97F, 0x9063, 0xB980, 0x9064, 0xB981, 0x9065, 0xB982, 0x9066, 0xB983, 0x9067, 0xB986, 0x9068, 0xB988, + 0x9069, 0xB98B, 0x906A, 0xB98C, 0x906B, 0xB98F, 0x906C, 0xB990, 0x906D, 0xB991, 0x906E, 0xB992, 0x906F, 0xB993, 0x9070, 0xB994, + 0x9071, 0xB995, 0x9072, 0xB996, 0x9073, 0xB997, 0x9074, 0xB998, 0x9075, 0xB999, 0x9076, 0xB99A, 0x9077, 0xB99B, 0x9078, 0xB99C, + 0x9079, 0xB99D, 0x907A, 0xB99E, 0x9081, 0xB99F, 0x9082, 0xB9A0, 0x9083, 0xB9A1, 0x9084, 0xB9A2, 0x9085, 0xB9A3, 0x9086, 0xB9A4, + 0x9087, 0xB9A5, 0x9088, 0xB9A6, 0x9089, 0xB9A7, 0x908A, 0xB9A8, 0x908B, 0xB9A9, 0x908C, 0xB9AA, 0x908D, 0xB9AB, 0x908E, 0xB9AE, + 0x908F, 0xB9AF, 0x9090, 0xB9B1, 0x9091, 0xB9B2, 0x9092, 0xB9B3, 0x9093, 0xB9B5, 0x9094, 0xB9B6, 0x9095, 0xB9B7, 0x9096, 0xB9B8, + 0x9097, 0xB9B9, 0x9098, 0xB9BA, 0x9099, 0xB9BB, 0x909A, 0xB9BE, 0x909B, 0xB9C0, 0x909C, 0xB9C2, 0x909D, 0xB9C3, 0x909E, 0xB9C4, + 0x909F, 0xB9C5, 0x90A0, 0xB9C6, 0x90A1, 0xB9C7, 0x90A2, 0xB9CA, 0x90A3, 0xB9CB, 0x90A4, 0xB9CD, 0x90A5, 0xB9D3, 0x90A6, 0xB9D4, + 0x90A7, 0xB9D5, 0x90A8, 0xB9D6, 0x90A9, 0xB9D7, 0x90AA, 0xB9DA, 0x90AB, 0xB9DC, 0x90AC, 0xB9DF, 0x90AD, 0xB9E0, 0x90AE, 0xB9E2, + 0x90AF, 0xB9E6, 0x90B0, 0xB9E7, 0x90B1, 0xB9E9, 0x90B2, 0xB9EA, 0x90B3, 0xB9EB, 0x90B4, 0xB9ED, 0x90B5, 0xB9EE, 0x90B6, 0xB9EF, + 0x90B7, 0xB9F0, 0x90B8, 0xB9F1, 0x90B9, 0xB9F2, 0x90BA, 0xB9F3, 0x90BB, 0xB9F6, 0x90BC, 0xB9FB, 0x90BD, 0xB9FC, 0x90BE, 0xB9FD, + 0x90BF, 0xB9FE, 0x90C0, 0xB9FF, 0x90C1, 0xBA02, 0x90C2, 0xBA03, 0x90C3, 0xBA04, 0x90C4, 0xBA05, 0x90C5, 0xBA06, 0x90C6, 0xBA07, + 0x90C7, 0xBA09, 0x90C8, 0xBA0A, 0x90C9, 0xBA0B, 0x90CA, 0xBA0C, 0x90CB, 0xBA0D, 0x90CC, 0xBA0E, 0x90CD, 0xBA0F, 0x90CE, 0xBA10, + 0x90CF, 0xBA11, 0x90D0, 0xBA12, 0x90D1, 0xBA13, 0x90D2, 0xBA14, 0x90D3, 0xBA16, 0x90D4, 0xBA17, 0x90D5, 0xBA18, 0x90D6, 0xBA19, + 0x90D7, 0xBA1A, 0x90D8, 0xBA1B, 0x90D9, 0xBA1C, 0x90DA, 0xBA1D, 0x90DB, 0xBA1E, 0x90DC, 0xBA1F, 0x90DD, 0xBA20, 0x90DE, 0xBA21, + 0x90DF, 0xBA22, 0x90E0, 0xBA23, 0x90E1, 0xBA24, 0x90E2, 0xBA25, 0x90E3, 0xBA26, 0x90E4, 0xBA27, 0x90E5, 0xBA28, 0x90E6, 0xBA29, + 0x90E7, 0xBA2A, 0x90E8, 0xBA2B, 0x90E9, 0xBA2C, 0x90EA, 0xBA2D, 0x90EB, 0xBA2E, 0x90EC, 0xBA2F, 0x90ED, 0xBA30, 0x90EE, 0xBA31, + 0x90EF, 0xBA32, 0x90F0, 0xBA33, 0x90F1, 0xBA34, 0x90F2, 0xBA35, 0x90F3, 0xBA36, 0x90F4, 0xBA37, 0x90F5, 0xBA3A, 0x90F6, 0xBA3B, + 0x90F7, 0xBA3D, 0x90F8, 0xBA3E, 0x90F9, 0xBA3F, 0x90FA, 0xBA41, 0x90FB, 0xBA43, 0x90FC, 0xBA44, 0x90FD, 0xBA45, 0x90FE, 0xBA46, + 0x9141, 0xBA47, 0x9142, 0xBA4A, 0x9143, 0xBA4C, 0x9144, 0xBA4F, 0x9145, 0xBA50, 0x9146, 0xBA51, 0x9147, 0xBA52, 0x9148, 0xBA56, + 0x9149, 0xBA57, 0x914A, 0xBA59, 0x914B, 0xBA5A, 0x914C, 0xBA5B, 0x914D, 0xBA5D, 0x914E, 0xBA5E, 0x914F, 0xBA5F, 0x9150, 0xBA60, + 0x9151, 0xBA61, 0x9152, 0xBA62, 0x9153, 0xBA63, 0x9154, 0xBA66, 0x9155, 0xBA6A, 0x9156, 0xBA6B, 0x9157, 0xBA6C, 0x9158, 0xBA6D, + 0x9159, 0xBA6E, 0x915A, 0xBA6F, 0x9161, 0xBA72, 0x9162, 0xBA73, 0x9163, 0xBA75, 0x9164, 0xBA76, 0x9165, 0xBA77, 0x9166, 0xBA79, + 0x9167, 0xBA7A, 0x9168, 0xBA7B, 0x9169, 0xBA7C, 0x916A, 0xBA7D, 0x916B, 0xBA7E, 0x916C, 0xBA7F, 0x916D, 0xBA80, 0x916E, 0xBA81, + 0x916F, 0xBA82, 0x9170, 0xBA86, 0x9171, 0xBA88, 0x9172, 0xBA89, 0x9173, 0xBA8A, 0x9174, 0xBA8B, 0x9175, 0xBA8D, 0x9176, 0xBA8E, + 0x9177, 0xBA8F, 0x9178, 0xBA90, 0x9179, 0xBA91, 0x917A, 0xBA92, 0x9181, 0xBA93, 0x9182, 0xBA94, 0x9183, 0xBA95, 0x9184, 0xBA96, + 0x9185, 0xBA97, 0x9186, 0xBA98, 0x9187, 0xBA99, 0x9188, 0xBA9A, 0x9189, 0xBA9B, 0x918A, 0xBA9C, 0x918B, 0xBA9D, 0x918C, 0xBA9E, + 0x918D, 0xBA9F, 0x918E, 0xBAA0, 0x918F, 0xBAA1, 0x9190, 0xBAA2, 0x9191, 0xBAA3, 0x9192, 0xBAA4, 0x9193, 0xBAA5, 0x9194, 0xBAA6, + 0x9195, 0xBAA7, 0x9196, 0xBAAA, 0x9197, 0xBAAD, 0x9198, 0xBAAE, 0x9199, 0xBAAF, 0x919A, 0xBAB1, 0x919B, 0xBAB3, 0x919C, 0xBAB4, + 0x919D, 0xBAB5, 0x919E, 0xBAB6, 0x919F, 0xBAB7, 0x91A0, 0xBABA, 0x91A1, 0xBABC, 0x91A2, 0xBABE, 0x91A3, 0xBABF, 0x91A4, 0xBAC0, + 0x91A5, 0xBAC1, 0x91A6, 0xBAC2, 0x91A7, 0xBAC3, 0x91A8, 0xBAC5, 0x91A9, 0xBAC6, 0x91AA, 0xBAC7, 0x91AB, 0xBAC9, 0x91AC, 0xBACA, + 0x91AD, 0xBACB, 0x91AE, 0xBACC, 0x91AF, 0xBACD, 0x91B0, 0xBACE, 0x91B1, 0xBACF, 0x91B2, 0xBAD0, 0x91B3, 0xBAD1, 0x91B4, 0xBAD2, + 0x91B5, 0xBAD3, 0x91B6, 0xBAD4, 0x91B7, 0xBAD5, 0x91B8, 0xBAD6, 0x91B9, 0xBAD7, 0x91BA, 0xBADA, 0x91BB, 0xBADB, 0x91BC, 0xBADC, + 0x91BD, 0xBADD, 0x91BE, 0xBADE, 0x91BF, 0xBADF, 0x91C0, 0xBAE0, 0x91C1, 0xBAE1, 0x91C2, 0xBAE2, 0x91C3, 0xBAE3, 0x91C4, 0xBAE4, + 0x91C5, 0xBAE5, 0x91C6, 0xBAE6, 0x91C7, 0xBAE7, 0x91C8, 0xBAE8, 0x91C9, 0xBAE9, 0x91CA, 0xBAEA, 0x91CB, 0xBAEB, 0x91CC, 0xBAEC, + 0x91CD, 0xBAED, 0x91CE, 0xBAEE, 0x91CF, 0xBAEF, 0x91D0, 0xBAF0, 0x91D1, 0xBAF1, 0x91D2, 0xBAF2, 0x91D3, 0xBAF3, 0x91D4, 0xBAF4, + 0x91D5, 0xBAF5, 0x91D6, 0xBAF6, 0x91D7, 0xBAF7, 0x91D8, 0xBAF8, 0x91D9, 0xBAF9, 0x91DA, 0xBAFA, 0x91DB, 0xBAFB, 0x91DC, 0xBAFD, + 0x91DD, 0xBAFE, 0x91DE, 0xBAFF, 0x91DF, 0xBB01, 0x91E0, 0xBB02, 0x91E1, 0xBB03, 0x91E2, 0xBB05, 0x91E3, 0xBB06, 0x91E4, 0xBB07, + 0x91E5, 0xBB08, 0x91E6, 0xBB09, 0x91E7, 0xBB0A, 0x91E8, 0xBB0B, 0x91E9, 0xBB0C, 0x91EA, 0xBB0E, 0x91EB, 0xBB10, 0x91EC, 0xBB12, + 0x91ED, 0xBB13, 0x91EE, 0xBB14, 0x91EF, 0xBB15, 0x91F0, 0xBB16, 0x91F1, 0xBB17, 0x91F2, 0xBB19, 0x91F3, 0xBB1A, 0x91F4, 0xBB1B, + 0x91F5, 0xBB1D, 0x91F6, 0xBB1E, 0x91F7, 0xBB1F, 0x91F8, 0xBB21, 0x91F9, 0xBB22, 0x91FA, 0xBB23, 0x91FB, 0xBB24, 0x91FC, 0xBB25, + 0x91FD, 0xBB26, 0x91FE, 0xBB27, 0x9241, 0xBB28, 0x9242, 0xBB2A, 0x9243, 0xBB2C, 0x9244, 0xBB2D, 0x9245, 0xBB2E, 0x9246, 0xBB2F, + 0x9247, 0xBB30, 0x9248, 0xBB31, 0x9249, 0xBB32, 0x924A, 0xBB33, 0x924B, 0xBB37, 0x924C, 0xBB39, 0x924D, 0xBB3A, 0x924E, 0xBB3F, + 0x924F, 0xBB40, 0x9250, 0xBB41, 0x9251, 0xBB42, 0x9252, 0xBB43, 0x9253, 0xBB46, 0x9254, 0xBB48, 0x9255, 0xBB4A, 0x9256, 0xBB4B, + 0x9257, 0xBB4C, 0x9258, 0xBB4E, 0x9259, 0xBB51, 0x925A, 0xBB52, 0x9261, 0xBB53, 0x9262, 0xBB55, 0x9263, 0xBB56, 0x9264, 0xBB57, + 0x9265, 0xBB59, 0x9266, 0xBB5A, 0x9267, 0xBB5B, 0x9268, 0xBB5C, 0x9269, 0xBB5D, 0x926A, 0xBB5E, 0x926B, 0xBB5F, 0x926C, 0xBB60, + 0x926D, 0xBB62, 0x926E, 0xBB64, 0x926F, 0xBB65, 0x9270, 0xBB66, 0x9271, 0xBB67, 0x9272, 0xBB68, 0x9273, 0xBB69, 0x9274, 0xBB6A, + 0x9275, 0xBB6B, 0x9276, 0xBB6D, 0x9277, 0xBB6E, 0x9278, 0xBB6F, 0x9279, 0xBB70, 0x927A, 0xBB71, 0x9281, 0xBB72, 0x9282, 0xBB73, + 0x9283, 0xBB74, 0x9284, 0xBB75, 0x9285, 0xBB76, 0x9286, 0xBB77, 0x9287, 0xBB78, 0x9288, 0xBB79, 0x9289, 0xBB7A, 0x928A, 0xBB7B, + 0x928B, 0xBB7C, 0x928C, 0xBB7D, 0x928D, 0xBB7E, 0x928E, 0xBB7F, 0x928F, 0xBB80, 0x9290, 0xBB81, 0x9291, 0xBB82, 0x9292, 0xBB83, + 0x9293, 0xBB84, 0x9294, 0xBB85, 0x9295, 0xBB86, 0x9296, 0xBB87, 0x9297, 0xBB89, 0x9298, 0xBB8A, 0x9299, 0xBB8B, 0x929A, 0xBB8D, + 0x929B, 0xBB8E, 0x929C, 0xBB8F, 0x929D, 0xBB91, 0x929E, 0xBB92, 0x929F, 0xBB93, 0x92A0, 0xBB94, 0x92A1, 0xBB95, 0x92A2, 0xBB96, + 0x92A3, 0xBB97, 0x92A4, 0xBB98, 0x92A5, 0xBB99, 0x92A6, 0xBB9A, 0x92A7, 0xBB9B, 0x92A8, 0xBB9C, 0x92A9, 0xBB9D, 0x92AA, 0xBB9E, + 0x92AB, 0xBB9F, 0x92AC, 0xBBA0, 0x92AD, 0xBBA1, 0x92AE, 0xBBA2, 0x92AF, 0xBBA3, 0x92B0, 0xBBA5, 0x92B1, 0xBBA6, 0x92B2, 0xBBA7, + 0x92B3, 0xBBA9, 0x92B4, 0xBBAA, 0x92B5, 0xBBAB, 0x92B6, 0xBBAD, 0x92B7, 0xBBAE, 0x92B8, 0xBBAF, 0x92B9, 0xBBB0, 0x92BA, 0xBBB1, + 0x92BB, 0xBBB2, 0x92BC, 0xBBB3, 0x92BD, 0xBBB5, 0x92BE, 0xBBB6, 0x92BF, 0xBBB8, 0x92C0, 0xBBB9, 0x92C1, 0xBBBA, 0x92C2, 0xBBBB, + 0x92C3, 0xBBBC, 0x92C4, 0xBBBD, 0x92C5, 0xBBBE, 0x92C6, 0xBBBF, 0x92C7, 0xBBC1, 0x92C8, 0xBBC2, 0x92C9, 0xBBC3, 0x92CA, 0xBBC5, + 0x92CB, 0xBBC6, 0x92CC, 0xBBC7, 0x92CD, 0xBBC9, 0x92CE, 0xBBCA, 0x92CF, 0xBBCB, 0x92D0, 0xBBCC, 0x92D1, 0xBBCD, 0x92D2, 0xBBCE, + 0x92D3, 0xBBCF, 0x92D4, 0xBBD1, 0x92D5, 0xBBD2, 0x92D6, 0xBBD4, 0x92D7, 0xBBD5, 0x92D8, 0xBBD6, 0x92D9, 0xBBD7, 0x92DA, 0xBBD8, + 0x92DB, 0xBBD9, 0x92DC, 0xBBDA, 0x92DD, 0xBBDB, 0x92DE, 0xBBDC, 0x92DF, 0xBBDD, 0x92E0, 0xBBDE, 0x92E1, 0xBBDF, 0x92E2, 0xBBE0, + 0x92E3, 0xBBE1, 0x92E4, 0xBBE2, 0x92E5, 0xBBE3, 0x92E6, 0xBBE4, 0x92E7, 0xBBE5, 0x92E8, 0xBBE6, 0x92E9, 0xBBE7, 0x92EA, 0xBBE8, + 0x92EB, 0xBBE9, 0x92EC, 0xBBEA, 0x92ED, 0xBBEB, 0x92EE, 0xBBEC, 0x92EF, 0xBBED, 0x92F0, 0xBBEE, 0x92F1, 0xBBEF, 0x92F2, 0xBBF0, + 0x92F3, 0xBBF1, 0x92F4, 0xBBF2, 0x92F5, 0xBBF3, 0x92F6, 0xBBF4, 0x92F7, 0xBBF5, 0x92F8, 0xBBF6, 0x92F9, 0xBBF7, 0x92FA, 0xBBFA, + 0x92FB, 0xBBFB, 0x92FC, 0xBBFD, 0x92FD, 0xBBFE, 0x92FE, 0xBC01, 0x9341, 0xBC03, 0x9342, 0xBC04, 0x9343, 0xBC05, 0x9344, 0xBC06, + 0x9345, 0xBC07, 0x9346, 0xBC0A, 0x9347, 0xBC0E, 0x9348, 0xBC10, 0x9349, 0xBC12, 0x934A, 0xBC13, 0x934B, 0xBC19, 0x934C, 0xBC1A, + 0x934D, 0xBC20, 0x934E, 0xBC21, 0x934F, 0xBC22, 0x9350, 0xBC23, 0x9351, 0xBC26, 0x9352, 0xBC28, 0x9353, 0xBC2A, 0x9354, 0xBC2B, + 0x9355, 0xBC2C, 0x9356, 0xBC2E, 0x9357, 0xBC2F, 0x9358, 0xBC32, 0x9359, 0xBC33, 0x935A, 0xBC35, 0x9361, 0xBC36, 0x9362, 0xBC37, + 0x9363, 0xBC39, 0x9364, 0xBC3A, 0x9365, 0xBC3B, 0x9366, 0xBC3C, 0x9367, 0xBC3D, 0x9368, 0xBC3E, 0x9369, 0xBC3F, 0x936A, 0xBC42, + 0x936B, 0xBC46, 0x936C, 0xBC47, 0x936D, 0xBC48, 0x936E, 0xBC4A, 0x936F, 0xBC4B, 0x9370, 0xBC4E, 0x9371, 0xBC4F, 0x9372, 0xBC51, + 0x9373, 0xBC52, 0x9374, 0xBC53, 0x9375, 0xBC54, 0x9376, 0xBC55, 0x9377, 0xBC56, 0x9378, 0xBC57, 0x9379, 0xBC58, 0x937A, 0xBC59, + 0x9381, 0xBC5A, 0x9382, 0xBC5B, 0x9383, 0xBC5C, 0x9384, 0xBC5E, 0x9385, 0xBC5F, 0x9386, 0xBC60, 0x9387, 0xBC61, 0x9388, 0xBC62, + 0x9389, 0xBC63, 0x938A, 0xBC64, 0x938B, 0xBC65, 0x938C, 0xBC66, 0x938D, 0xBC67, 0x938E, 0xBC68, 0x938F, 0xBC69, 0x9390, 0xBC6A, + 0x9391, 0xBC6B, 0x9392, 0xBC6C, 0x9393, 0xBC6D, 0x9394, 0xBC6E, 0x9395, 0xBC6F, 0x9396, 0xBC70, 0x9397, 0xBC71, 0x9398, 0xBC72, + 0x9399, 0xBC73, 0x939A, 0xBC74, 0x939B, 0xBC75, 0x939C, 0xBC76, 0x939D, 0xBC77, 0x939E, 0xBC78, 0x939F, 0xBC79, 0x93A0, 0xBC7A, + 0x93A1, 0xBC7B, 0x93A2, 0xBC7C, 0x93A3, 0xBC7D, 0x93A4, 0xBC7E, 0x93A5, 0xBC7F, 0x93A6, 0xBC80, 0x93A7, 0xBC81, 0x93A8, 0xBC82, + 0x93A9, 0xBC83, 0x93AA, 0xBC86, 0x93AB, 0xBC87, 0x93AC, 0xBC89, 0x93AD, 0xBC8A, 0x93AE, 0xBC8D, 0x93AF, 0xBC8F, 0x93B0, 0xBC90, + 0x93B1, 0xBC91, 0x93B2, 0xBC92, 0x93B3, 0xBC93, 0x93B4, 0xBC96, 0x93B5, 0xBC98, 0x93B6, 0xBC9B, 0x93B7, 0xBC9C, 0x93B8, 0xBC9D, + 0x93B9, 0xBC9E, 0x93BA, 0xBC9F, 0x93BB, 0xBCA2, 0x93BC, 0xBCA3, 0x93BD, 0xBCA5, 0x93BE, 0xBCA6, 0x93BF, 0xBCA9, 0x93C0, 0xBCAA, + 0x93C1, 0xBCAB, 0x93C2, 0xBCAC, 0x93C3, 0xBCAD, 0x93C4, 0xBCAE, 0x93C5, 0xBCAF, 0x93C6, 0xBCB2, 0x93C7, 0xBCB6, 0x93C8, 0xBCB7, + 0x93C9, 0xBCB8, 0x93CA, 0xBCB9, 0x93CB, 0xBCBA, 0x93CC, 0xBCBB, 0x93CD, 0xBCBE, 0x93CE, 0xBCBF, 0x93CF, 0xBCC1, 0x93D0, 0xBCC2, + 0x93D1, 0xBCC3, 0x93D2, 0xBCC5, 0x93D3, 0xBCC6, 0x93D4, 0xBCC7, 0x93D5, 0xBCC8, 0x93D6, 0xBCC9, 0x93D7, 0xBCCA, 0x93D8, 0xBCCB, + 0x93D9, 0xBCCC, 0x93DA, 0xBCCE, 0x93DB, 0xBCD2, 0x93DC, 0xBCD3, 0x93DD, 0xBCD4, 0x93DE, 0xBCD6, 0x93DF, 0xBCD7, 0x93E0, 0xBCD9, + 0x93E1, 0xBCDA, 0x93E2, 0xBCDB, 0x93E3, 0xBCDD, 0x93E4, 0xBCDE, 0x93E5, 0xBCDF, 0x93E6, 0xBCE0, 0x93E7, 0xBCE1, 0x93E8, 0xBCE2, + 0x93E9, 0xBCE3, 0x93EA, 0xBCE4, 0x93EB, 0xBCE5, 0x93EC, 0xBCE6, 0x93ED, 0xBCE7, 0x93EE, 0xBCE8, 0x93EF, 0xBCE9, 0x93F0, 0xBCEA, + 0x93F1, 0xBCEB, 0x93F2, 0xBCEC, 0x93F3, 0xBCED, 0x93F4, 0xBCEE, 0x93F5, 0xBCEF, 0x93F6, 0xBCF0, 0x93F7, 0xBCF1, 0x93F8, 0xBCF2, + 0x93F9, 0xBCF3, 0x93FA, 0xBCF7, 0x93FB, 0xBCF9, 0x93FC, 0xBCFA, 0x93FD, 0xBCFB, 0x93FE, 0xBCFD, 0x9441, 0xBCFE, 0x9442, 0xBCFF, + 0x9443, 0xBD00, 0x9444, 0xBD01, 0x9445, 0xBD02, 0x9446, 0xBD03, 0x9447, 0xBD06, 0x9448, 0xBD08, 0x9449, 0xBD0A, 0x944A, 0xBD0B, + 0x944B, 0xBD0C, 0x944C, 0xBD0D, 0x944D, 0xBD0E, 0x944E, 0xBD0F, 0x944F, 0xBD11, 0x9450, 0xBD12, 0x9451, 0xBD13, 0x9452, 0xBD15, + 0x9453, 0xBD16, 0x9454, 0xBD17, 0x9455, 0xBD18, 0x9456, 0xBD19, 0x9457, 0xBD1A, 0x9458, 0xBD1B, 0x9459, 0xBD1C, 0x945A, 0xBD1D, + 0x9461, 0xBD1E, 0x9462, 0xBD1F, 0x9463, 0xBD20, 0x9464, 0xBD21, 0x9465, 0xBD22, 0x9466, 0xBD23, 0x9467, 0xBD25, 0x9468, 0xBD26, + 0x9469, 0xBD27, 0x946A, 0xBD28, 0x946B, 0xBD29, 0x946C, 0xBD2A, 0x946D, 0xBD2B, 0x946E, 0xBD2D, 0x946F, 0xBD2E, 0x9470, 0xBD2F, + 0x9471, 0xBD30, 0x9472, 0xBD31, 0x9473, 0xBD32, 0x9474, 0xBD33, 0x9475, 0xBD34, 0x9476, 0xBD35, 0x9477, 0xBD36, 0x9478, 0xBD37, + 0x9479, 0xBD38, 0x947A, 0xBD39, 0x9481, 0xBD3A, 0x9482, 0xBD3B, 0x9483, 0xBD3C, 0x9484, 0xBD3D, 0x9485, 0xBD3E, 0x9486, 0xBD3F, + 0x9487, 0xBD41, 0x9488, 0xBD42, 0x9489, 0xBD43, 0x948A, 0xBD44, 0x948B, 0xBD45, 0x948C, 0xBD46, 0x948D, 0xBD47, 0x948E, 0xBD4A, + 0x948F, 0xBD4B, 0x9490, 0xBD4D, 0x9491, 0xBD4E, 0x9492, 0xBD4F, 0x9493, 0xBD51, 0x9494, 0xBD52, 0x9495, 0xBD53, 0x9496, 0xBD54, + 0x9497, 0xBD55, 0x9498, 0xBD56, 0x9499, 0xBD57, 0x949A, 0xBD5A, 0x949B, 0xBD5B, 0x949C, 0xBD5C, 0x949D, 0xBD5D, 0x949E, 0xBD5E, + 0x949F, 0xBD5F, 0x94A0, 0xBD60, 0x94A1, 0xBD61, 0x94A2, 0xBD62, 0x94A3, 0xBD63, 0x94A4, 0xBD65, 0x94A5, 0xBD66, 0x94A6, 0xBD67, + 0x94A7, 0xBD69, 0x94A8, 0xBD6A, 0x94A9, 0xBD6B, 0x94AA, 0xBD6C, 0x94AB, 0xBD6D, 0x94AC, 0xBD6E, 0x94AD, 0xBD6F, 0x94AE, 0xBD70, + 0x94AF, 0xBD71, 0x94B0, 0xBD72, 0x94B1, 0xBD73, 0x94B2, 0xBD74, 0x94B3, 0xBD75, 0x94B4, 0xBD76, 0x94B5, 0xBD77, 0x94B6, 0xBD78, + 0x94B7, 0xBD79, 0x94B8, 0xBD7A, 0x94B9, 0xBD7B, 0x94BA, 0xBD7C, 0x94BB, 0xBD7D, 0x94BC, 0xBD7E, 0x94BD, 0xBD7F, 0x94BE, 0xBD82, + 0x94BF, 0xBD83, 0x94C0, 0xBD85, 0x94C1, 0xBD86, 0x94C2, 0xBD8B, 0x94C3, 0xBD8C, 0x94C4, 0xBD8D, 0x94C5, 0xBD8E, 0x94C6, 0xBD8F, + 0x94C7, 0xBD92, 0x94C8, 0xBD94, 0x94C9, 0xBD96, 0x94CA, 0xBD97, 0x94CB, 0xBD98, 0x94CC, 0xBD9B, 0x94CD, 0xBD9D, 0x94CE, 0xBD9E, + 0x94CF, 0xBD9F, 0x94D0, 0xBDA0, 0x94D1, 0xBDA1, 0x94D2, 0xBDA2, 0x94D3, 0xBDA3, 0x94D4, 0xBDA5, 0x94D5, 0xBDA6, 0x94D6, 0xBDA7, + 0x94D7, 0xBDA8, 0x94D8, 0xBDA9, 0x94D9, 0xBDAA, 0x94DA, 0xBDAB, 0x94DB, 0xBDAC, 0x94DC, 0xBDAD, 0x94DD, 0xBDAE, 0x94DE, 0xBDAF, + 0x94DF, 0xBDB1, 0x94E0, 0xBDB2, 0x94E1, 0xBDB3, 0x94E2, 0xBDB4, 0x94E3, 0xBDB5, 0x94E4, 0xBDB6, 0x94E5, 0xBDB7, 0x94E6, 0xBDB9, + 0x94E7, 0xBDBA, 0x94E8, 0xBDBB, 0x94E9, 0xBDBC, 0x94EA, 0xBDBD, 0x94EB, 0xBDBE, 0x94EC, 0xBDBF, 0x94ED, 0xBDC0, 0x94EE, 0xBDC1, + 0x94EF, 0xBDC2, 0x94F0, 0xBDC3, 0x94F1, 0xBDC4, 0x94F2, 0xBDC5, 0x94F3, 0xBDC6, 0x94F4, 0xBDC7, 0x94F5, 0xBDC8, 0x94F6, 0xBDC9, + 0x94F7, 0xBDCA, 0x94F8, 0xBDCB, 0x94F9, 0xBDCC, 0x94FA, 0xBDCD, 0x94FB, 0xBDCE, 0x94FC, 0xBDCF, 0x94FD, 0xBDD0, 0x94FE, 0xBDD1, + 0x9541, 0xBDD2, 0x9542, 0xBDD3, 0x9543, 0xBDD6, 0x9544, 0xBDD7, 0x9545, 0xBDD9, 0x9546, 0xBDDA, 0x9547, 0xBDDB, 0x9548, 0xBDDD, + 0x9549, 0xBDDE, 0x954A, 0xBDDF, 0x954B, 0xBDE0, 0x954C, 0xBDE1, 0x954D, 0xBDE2, 0x954E, 0xBDE3, 0x954F, 0xBDE4, 0x9550, 0xBDE5, + 0x9551, 0xBDE6, 0x9552, 0xBDE7, 0x9553, 0xBDE8, 0x9554, 0xBDEA, 0x9555, 0xBDEB, 0x9556, 0xBDEC, 0x9557, 0xBDED, 0x9558, 0xBDEE, + 0x9559, 0xBDEF, 0x955A, 0xBDF1, 0x9561, 0xBDF2, 0x9562, 0xBDF3, 0x9563, 0xBDF5, 0x9564, 0xBDF6, 0x9565, 0xBDF7, 0x9566, 0xBDF9, + 0x9567, 0xBDFA, 0x9568, 0xBDFB, 0x9569, 0xBDFC, 0x956A, 0xBDFD, 0x956B, 0xBDFE, 0x956C, 0xBDFF, 0x956D, 0xBE01, 0x956E, 0xBE02, + 0x956F, 0xBE04, 0x9570, 0xBE06, 0x9571, 0xBE07, 0x9572, 0xBE08, 0x9573, 0xBE09, 0x9574, 0xBE0A, 0x9575, 0xBE0B, 0x9576, 0xBE0E, + 0x9577, 0xBE0F, 0x9578, 0xBE11, 0x9579, 0xBE12, 0x957A, 0xBE13, 0x9581, 0xBE15, 0x9582, 0xBE16, 0x9583, 0xBE17, 0x9584, 0xBE18, + 0x9585, 0xBE19, 0x9586, 0xBE1A, 0x9587, 0xBE1B, 0x9588, 0xBE1E, 0x9589, 0xBE20, 0x958A, 0xBE21, 0x958B, 0xBE22, 0x958C, 0xBE23, + 0x958D, 0xBE24, 0x958E, 0xBE25, 0x958F, 0xBE26, 0x9590, 0xBE27, 0x9591, 0xBE28, 0x9592, 0xBE29, 0x9593, 0xBE2A, 0x9594, 0xBE2B, + 0x9595, 0xBE2C, 0x9596, 0xBE2D, 0x9597, 0xBE2E, 0x9598, 0xBE2F, 0x9599, 0xBE30, 0x959A, 0xBE31, 0x959B, 0xBE32, 0x959C, 0xBE33, + 0x959D, 0xBE34, 0x959E, 0xBE35, 0x959F, 0xBE36, 0x95A0, 0xBE37, 0x95A1, 0xBE38, 0x95A2, 0xBE39, 0x95A3, 0xBE3A, 0x95A4, 0xBE3B, + 0x95A5, 0xBE3C, 0x95A6, 0xBE3D, 0x95A7, 0xBE3E, 0x95A8, 0xBE3F, 0x95A9, 0xBE40, 0x95AA, 0xBE41, 0x95AB, 0xBE42, 0x95AC, 0xBE43, + 0x95AD, 0xBE46, 0x95AE, 0xBE47, 0x95AF, 0xBE49, 0x95B0, 0xBE4A, 0x95B1, 0xBE4B, 0x95B2, 0xBE4D, 0x95B3, 0xBE4F, 0x95B4, 0xBE50, + 0x95B5, 0xBE51, 0x95B6, 0xBE52, 0x95B7, 0xBE53, 0x95B8, 0xBE56, 0x95B9, 0xBE58, 0x95BA, 0xBE5C, 0x95BB, 0xBE5D, 0x95BC, 0xBE5E, + 0x95BD, 0xBE5F, 0x95BE, 0xBE62, 0x95BF, 0xBE63, 0x95C0, 0xBE65, 0x95C1, 0xBE66, 0x95C2, 0xBE67, 0x95C3, 0xBE69, 0x95C4, 0xBE6B, + 0x95C5, 0xBE6C, 0x95C6, 0xBE6D, 0x95C7, 0xBE6E, 0x95C8, 0xBE6F, 0x95C9, 0xBE72, 0x95CA, 0xBE76, 0x95CB, 0xBE77, 0x95CC, 0xBE78, + 0x95CD, 0xBE79, 0x95CE, 0xBE7A, 0x95CF, 0xBE7E, 0x95D0, 0xBE7F, 0x95D1, 0xBE81, 0x95D2, 0xBE82, 0x95D3, 0xBE83, 0x95D4, 0xBE85, + 0x95D5, 0xBE86, 0x95D6, 0xBE87, 0x95D7, 0xBE88, 0x95D8, 0xBE89, 0x95D9, 0xBE8A, 0x95DA, 0xBE8B, 0x95DB, 0xBE8E, 0x95DC, 0xBE92, + 0x95DD, 0xBE93, 0x95DE, 0xBE94, 0x95DF, 0xBE95, 0x95E0, 0xBE96, 0x95E1, 0xBE97, 0x95E2, 0xBE9A, 0x95E3, 0xBE9B, 0x95E4, 0xBE9C, + 0x95E5, 0xBE9D, 0x95E6, 0xBE9E, 0x95E7, 0xBE9F, 0x95E8, 0xBEA0, 0x95E9, 0xBEA1, 0x95EA, 0xBEA2, 0x95EB, 0xBEA3, 0x95EC, 0xBEA4, + 0x95ED, 0xBEA5, 0x95EE, 0xBEA6, 0x95EF, 0xBEA7, 0x95F0, 0xBEA9, 0x95F1, 0xBEAA, 0x95F2, 0xBEAB, 0x95F3, 0xBEAC, 0x95F4, 0xBEAD, + 0x95F5, 0xBEAE, 0x95F6, 0xBEAF, 0x95F7, 0xBEB0, 0x95F8, 0xBEB1, 0x95F9, 0xBEB2, 0x95FA, 0xBEB3, 0x95FB, 0xBEB4, 0x95FC, 0xBEB5, + 0x95FD, 0xBEB6, 0x95FE, 0xBEB7, 0x9641, 0xBEB8, 0x9642, 0xBEB9, 0x9643, 0xBEBA, 0x9644, 0xBEBB, 0x9645, 0xBEBC, 0x9646, 0xBEBD, + 0x9647, 0xBEBE, 0x9648, 0xBEBF, 0x9649, 0xBEC0, 0x964A, 0xBEC1, 0x964B, 0xBEC2, 0x964C, 0xBEC3, 0x964D, 0xBEC4, 0x964E, 0xBEC5, + 0x964F, 0xBEC6, 0x9650, 0xBEC7, 0x9651, 0xBEC8, 0x9652, 0xBEC9, 0x9653, 0xBECA, 0x9654, 0xBECB, 0x9655, 0xBECC, 0x9656, 0xBECD, + 0x9657, 0xBECE, 0x9658, 0xBECF, 0x9659, 0xBED2, 0x965A, 0xBED3, 0x9661, 0xBED5, 0x9662, 0xBED6, 0x9663, 0xBED9, 0x9664, 0xBEDA, + 0x9665, 0xBEDB, 0x9666, 0xBEDC, 0x9667, 0xBEDD, 0x9668, 0xBEDE, 0x9669, 0xBEDF, 0x966A, 0xBEE1, 0x966B, 0xBEE2, 0x966C, 0xBEE6, + 0x966D, 0xBEE7, 0x966E, 0xBEE8, 0x966F, 0xBEE9, 0x9670, 0xBEEA, 0x9671, 0xBEEB, 0x9672, 0xBEED, 0x9673, 0xBEEE, 0x9674, 0xBEEF, + 0x9675, 0xBEF0, 0x9676, 0xBEF1, 0x9677, 0xBEF2, 0x9678, 0xBEF3, 0x9679, 0xBEF4, 0x967A, 0xBEF5, 0x9681, 0xBEF6, 0x9682, 0xBEF7, + 0x9683, 0xBEF8, 0x9684, 0xBEF9, 0x9685, 0xBEFA, 0x9686, 0xBEFB, 0x9687, 0xBEFC, 0x9688, 0xBEFD, 0x9689, 0xBEFE, 0x968A, 0xBEFF, + 0x968B, 0xBF00, 0x968C, 0xBF02, 0x968D, 0xBF03, 0x968E, 0xBF04, 0x968F, 0xBF05, 0x9690, 0xBF06, 0x9691, 0xBF07, 0x9692, 0xBF0A, + 0x9693, 0xBF0B, 0x9694, 0xBF0C, 0x9695, 0xBF0D, 0x9696, 0xBF0E, 0x9697, 0xBF0F, 0x9698, 0xBF10, 0x9699, 0xBF11, 0x969A, 0xBF12, + 0x969B, 0xBF13, 0x969C, 0xBF14, 0x969D, 0xBF15, 0x969E, 0xBF16, 0x969F, 0xBF17, 0x96A0, 0xBF1A, 0x96A1, 0xBF1E, 0x96A2, 0xBF1F, + 0x96A3, 0xBF20, 0x96A4, 0xBF21, 0x96A5, 0xBF22, 0x96A6, 0xBF23, 0x96A7, 0xBF24, 0x96A8, 0xBF25, 0x96A9, 0xBF26, 0x96AA, 0xBF27, + 0x96AB, 0xBF28, 0x96AC, 0xBF29, 0x96AD, 0xBF2A, 0x96AE, 0xBF2B, 0x96AF, 0xBF2C, 0x96B0, 0xBF2D, 0x96B1, 0xBF2E, 0x96B2, 0xBF2F, + 0x96B3, 0xBF30, 0x96B4, 0xBF31, 0x96B5, 0xBF32, 0x96B6, 0xBF33, 0x96B7, 0xBF34, 0x96B8, 0xBF35, 0x96B9, 0xBF36, 0x96BA, 0xBF37, + 0x96BB, 0xBF38, 0x96BC, 0xBF39, 0x96BD, 0xBF3A, 0x96BE, 0xBF3B, 0x96BF, 0xBF3C, 0x96C0, 0xBF3D, 0x96C1, 0xBF3E, 0x96C2, 0xBF3F, + 0x96C3, 0xBF42, 0x96C4, 0xBF43, 0x96C5, 0xBF45, 0x96C6, 0xBF46, 0x96C7, 0xBF47, 0x96C8, 0xBF49, 0x96C9, 0xBF4A, 0x96CA, 0xBF4B, + 0x96CB, 0xBF4C, 0x96CC, 0xBF4D, 0x96CD, 0xBF4E, 0x96CE, 0xBF4F, 0x96CF, 0xBF52, 0x96D0, 0xBF53, 0x96D1, 0xBF54, 0x96D2, 0xBF56, + 0x96D3, 0xBF57, 0x96D4, 0xBF58, 0x96D5, 0xBF59, 0x96D6, 0xBF5A, 0x96D7, 0xBF5B, 0x96D8, 0xBF5C, 0x96D9, 0xBF5D, 0x96DA, 0xBF5E, + 0x96DB, 0xBF5F, 0x96DC, 0xBF60, 0x96DD, 0xBF61, 0x96DE, 0xBF62, 0x96DF, 0xBF63, 0x96E0, 0xBF64, 0x96E1, 0xBF65, 0x96E2, 0xBF66, + 0x96E3, 0xBF67, 0x96E4, 0xBF68, 0x96E5, 0xBF69, 0x96E6, 0xBF6A, 0x96E7, 0xBF6B, 0x96E8, 0xBF6C, 0x96E9, 0xBF6D, 0x96EA, 0xBF6E, + 0x96EB, 0xBF6F, 0x96EC, 0xBF70, 0x96ED, 0xBF71, 0x96EE, 0xBF72, 0x96EF, 0xBF73, 0x96F0, 0xBF74, 0x96F1, 0xBF75, 0x96F2, 0xBF76, + 0x96F3, 0xBF77, 0x96F4, 0xBF78, 0x96F5, 0xBF79, 0x96F6, 0xBF7A, 0x96F7, 0xBF7B, 0x96F8, 0xBF7C, 0x96F9, 0xBF7D, 0x96FA, 0xBF7E, + 0x96FB, 0xBF7F, 0x96FC, 0xBF80, 0x96FD, 0xBF81, 0x96FE, 0xBF82, 0x9741, 0xBF83, 0x9742, 0xBF84, 0x9743, 0xBF85, 0x9744, 0xBF86, + 0x9745, 0xBF87, 0x9746, 0xBF88, 0x9747, 0xBF89, 0x9748, 0xBF8A, 0x9749, 0xBF8B, 0x974A, 0xBF8C, 0x974B, 0xBF8D, 0x974C, 0xBF8E, + 0x974D, 0xBF8F, 0x974E, 0xBF90, 0x974F, 0xBF91, 0x9750, 0xBF92, 0x9751, 0xBF93, 0x9752, 0xBF95, 0x9753, 0xBF96, 0x9754, 0xBF97, + 0x9755, 0xBF98, 0x9756, 0xBF99, 0x9757, 0xBF9A, 0x9758, 0xBF9B, 0x9759, 0xBF9C, 0x975A, 0xBF9D, 0x9761, 0xBF9E, 0x9762, 0xBF9F, + 0x9763, 0xBFA0, 0x9764, 0xBFA1, 0x9765, 0xBFA2, 0x9766, 0xBFA3, 0x9767, 0xBFA4, 0x9768, 0xBFA5, 0x9769, 0xBFA6, 0x976A, 0xBFA7, + 0x976B, 0xBFA8, 0x976C, 0xBFA9, 0x976D, 0xBFAA, 0x976E, 0xBFAB, 0x976F, 0xBFAC, 0x9770, 0xBFAD, 0x9771, 0xBFAE, 0x9772, 0xBFAF, + 0x9773, 0xBFB1, 0x9774, 0xBFB2, 0x9775, 0xBFB3, 0x9776, 0xBFB4, 0x9777, 0xBFB5, 0x9778, 0xBFB6, 0x9779, 0xBFB7, 0x977A, 0xBFB8, + 0x9781, 0xBFB9, 0x9782, 0xBFBA, 0x9783, 0xBFBB, 0x9784, 0xBFBC, 0x9785, 0xBFBD, 0x9786, 0xBFBE, 0x9787, 0xBFBF, 0x9788, 0xBFC0, + 0x9789, 0xBFC1, 0x978A, 0xBFC2, 0x978B, 0xBFC3, 0x978C, 0xBFC4, 0x978D, 0xBFC6, 0x978E, 0xBFC7, 0x978F, 0xBFC8, 0x9790, 0xBFC9, + 0x9791, 0xBFCA, 0x9792, 0xBFCB, 0x9793, 0xBFCE, 0x9794, 0xBFCF, 0x9795, 0xBFD1, 0x9796, 0xBFD2, 0x9797, 0xBFD3, 0x9798, 0xBFD5, + 0x9799, 0xBFD6, 0x979A, 0xBFD7, 0x979B, 0xBFD8, 0x979C, 0xBFD9, 0x979D, 0xBFDA, 0x979E, 0xBFDB, 0x979F, 0xBFDD, 0x97A0, 0xBFDE, + 0x97A1, 0xBFE0, 0x97A2, 0xBFE2, 0x97A3, 0xBFE3, 0x97A4, 0xBFE4, 0x97A5, 0xBFE5, 0x97A6, 0xBFE6, 0x97A7, 0xBFE7, 0x97A8, 0xBFE8, + 0x97A9, 0xBFE9, 0x97AA, 0xBFEA, 0x97AB, 0xBFEB, 0x97AC, 0xBFEC, 0x97AD, 0xBFED, 0x97AE, 0xBFEE, 0x97AF, 0xBFEF, 0x97B0, 0xBFF0, + 0x97B1, 0xBFF1, 0x97B2, 0xBFF2, 0x97B3, 0xBFF3, 0x97B4, 0xBFF4, 0x97B5, 0xBFF5, 0x97B6, 0xBFF6, 0x97B7, 0xBFF7, 0x97B8, 0xBFF8, + 0x97B9, 0xBFF9, 0x97BA, 0xBFFA, 0x97BB, 0xBFFB, 0x97BC, 0xBFFC, 0x97BD, 0xBFFD, 0x97BE, 0xBFFE, 0x97BF, 0xBFFF, 0x97C0, 0xC000, + 0x97C1, 0xC001, 0x97C2, 0xC002, 0x97C3, 0xC003, 0x97C4, 0xC004, 0x97C5, 0xC005, 0x97C6, 0xC006, 0x97C7, 0xC007, 0x97C8, 0xC008, + 0x97C9, 0xC009, 0x97CA, 0xC00A, 0x97CB, 0xC00B, 0x97CC, 0xC00C, 0x97CD, 0xC00D, 0x97CE, 0xC00E, 0x97CF, 0xC00F, 0x97D0, 0xC010, + 0x97D1, 0xC011, 0x97D2, 0xC012, 0x97D3, 0xC013, 0x97D4, 0xC014, 0x97D5, 0xC015, 0x97D6, 0xC016, 0x97D7, 0xC017, 0x97D8, 0xC018, + 0x97D9, 0xC019, 0x97DA, 0xC01A, 0x97DB, 0xC01B, 0x97DC, 0xC01C, 0x97DD, 0xC01D, 0x97DE, 0xC01E, 0x97DF, 0xC01F, 0x97E0, 0xC020, + 0x97E1, 0xC021, 0x97E2, 0xC022, 0x97E3, 0xC023, 0x97E4, 0xC024, 0x97E5, 0xC025, 0x97E6, 0xC026, 0x97E7, 0xC027, 0x97E8, 0xC028, + 0x97E9, 0xC029, 0x97EA, 0xC02A, 0x97EB, 0xC02B, 0x97EC, 0xC02C, 0x97ED, 0xC02D, 0x97EE, 0xC02E, 0x97EF, 0xC02F, 0x97F0, 0xC030, + 0x97F1, 0xC031, 0x97F2, 0xC032, 0x97F3, 0xC033, 0x97F4, 0xC034, 0x97F5, 0xC035, 0x97F6, 0xC036, 0x97F7, 0xC037, 0x97F8, 0xC038, + 0x97F9, 0xC039, 0x97FA, 0xC03A, 0x97FB, 0xC03B, 0x97FC, 0xC03D, 0x97FD, 0xC03E, 0x97FE, 0xC03F, 0x9841, 0xC040, 0x9842, 0xC041, + 0x9843, 0xC042, 0x9844, 0xC043, 0x9845, 0xC044, 0x9846, 0xC045, 0x9847, 0xC046, 0x9848, 0xC047, 0x9849, 0xC048, 0x984A, 0xC049, + 0x984B, 0xC04A, 0x984C, 0xC04B, 0x984D, 0xC04C, 0x984E, 0xC04D, 0x984F, 0xC04E, 0x9850, 0xC04F, 0x9851, 0xC050, 0x9852, 0xC052, + 0x9853, 0xC053, 0x9854, 0xC054, 0x9855, 0xC055, 0x9856, 0xC056, 0x9857, 0xC057, 0x9858, 0xC059, 0x9859, 0xC05A, 0x985A, 0xC05B, + 0x9861, 0xC05D, 0x9862, 0xC05E, 0x9863, 0xC05F, 0x9864, 0xC061, 0x9865, 0xC062, 0x9866, 0xC063, 0x9867, 0xC064, 0x9868, 0xC065, + 0x9869, 0xC066, 0x986A, 0xC067, 0x986B, 0xC06A, 0x986C, 0xC06B, 0x986D, 0xC06C, 0x986E, 0xC06D, 0x986F, 0xC06E, 0x9870, 0xC06F, + 0x9871, 0xC070, 0x9872, 0xC071, 0x9873, 0xC072, 0x9874, 0xC073, 0x9875, 0xC074, 0x9876, 0xC075, 0x9877, 0xC076, 0x9878, 0xC077, + 0x9879, 0xC078, 0x987A, 0xC079, 0x9881, 0xC07A, 0x9882, 0xC07B, 0x9883, 0xC07C, 0x9884, 0xC07D, 0x9885, 0xC07E, 0x9886, 0xC07F, + 0x9887, 0xC080, 0x9888, 0xC081, 0x9889, 0xC082, 0x988A, 0xC083, 0x988B, 0xC084, 0x988C, 0xC085, 0x988D, 0xC086, 0x988E, 0xC087, + 0x988F, 0xC088, 0x9890, 0xC089, 0x9891, 0xC08A, 0x9892, 0xC08B, 0x9893, 0xC08C, 0x9894, 0xC08D, 0x9895, 0xC08E, 0x9896, 0xC08F, + 0x9897, 0xC092, 0x9898, 0xC093, 0x9899, 0xC095, 0x989A, 0xC096, 0x989B, 0xC097, 0x989C, 0xC099, 0x989D, 0xC09A, 0x989E, 0xC09B, + 0x989F, 0xC09C, 0x98A0, 0xC09D, 0x98A1, 0xC09E, 0x98A2, 0xC09F, 0x98A3, 0xC0A2, 0x98A4, 0xC0A4, 0x98A5, 0xC0A6, 0x98A6, 0xC0A7, + 0x98A7, 0xC0A8, 0x98A8, 0xC0A9, 0x98A9, 0xC0AA, 0x98AA, 0xC0AB, 0x98AB, 0xC0AE, 0x98AC, 0xC0B1, 0x98AD, 0xC0B2, 0x98AE, 0xC0B7, + 0x98AF, 0xC0B8, 0x98B0, 0xC0B9, 0x98B1, 0xC0BA, 0x98B2, 0xC0BB, 0x98B3, 0xC0BE, 0x98B4, 0xC0C2, 0x98B5, 0xC0C3, 0x98B6, 0xC0C4, + 0x98B7, 0xC0C6, 0x98B8, 0xC0C7, 0x98B9, 0xC0CA, 0x98BA, 0xC0CB, 0x98BB, 0xC0CD, 0x98BC, 0xC0CE, 0x98BD, 0xC0CF, 0x98BE, 0xC0D1, + 0x98BF, 0xC0D2, 0x98C0, 0xC0D3, 0x98C1, 0xC0D4, 0x98C2, 0xC0D5, 0x98C3, 0xC0D6, 0x98C4, 0xC0D7, 0x98C5, 0xC0DA, 0x98C6, 0xC0DE, + 0x98C7, 0xC0DF, 0x98C8, 0xC0E0, 0x98C9, 0xC0E1, 0x98CA, 0xC0E2, 0x98CB, 0xC0E3, 0x98CC, 0xC0E6, 0x98CD, 0xC0E7, 0x98CE, 0xC0E9, + 0x98CF, 0xC0EA, 0x98D0, 0xC0EB, 0x98D1, 0xC0ED, 0x98D2, 0xC0EE, 0x98D3, 0xC0EF, 0x98D4, 0xC0F0, 0x98D5, 0xC0F1, 0x98D6, 0xC0F2, + 0x98D7, 0xC0F3, 0x98D8, 0xC0F6, 0x98D9, 0xC0F8, 0x98DA, 0xC0FA, 0x98DB, 0xC0FB, 0x98DC, 0xC0FC, 0x98DD, 0xC0FD, 0x98DE, 0xC0FE, + 0x98DF, 0xC0FF, 0x98E0, 0xC101, 0x98E1, 0xC102, 0x98E2, 0xC103, 0x98E3, 0xC105, 0x98E4, 0xC106, 0x98E5, 0xC107, 0x98E6, 0xC109, + 0x98E7, 0xC10A, 0x98E8, 0xC10B, 0x98E9, 0xC10C, 0x98EA, 0xC10D, 0x98EB, 0xC10E, 0x98EC, 0xC10F, 0x98ED, 0xC111, 0x98EE, 0xC112, + 0x98EF, 0xC113, 0x98F0, 0xC114, 0x98F1, 0xC116, 0x98F2, 0xC117, 0x98F3, 0xC118, 0x98F4, 0xC119, 0x98F5, 0xC11A, 0x98F6, 0xC11B, + 0x98F7, 0xC121, 0x98F8, 0xC122, 0x98F9, 0xC125, 0x98FA, 0xC128, 0x98FB, 0xC129, 0x98FC, 0xC12A, 0x98FD, 0xC12B, 0x98FE, 0xC12E, + 0x9941, 0xC132, 0x9942, 0xC133, 0x9943, 0xC134, 0x9944, 0xC135, 0x9945, 0xC137, 0x9946, 0xC13A, 0x9947, 0xC13B, 0x9948, 0xC13D, + 0x9949, 0xC13E, 0x994A, 0xC13F, 0x994B, 0xC141, 0x994C, 0xC142, 0x994D, 0xC143, 0x994E, 0xC144, 0x994F, 0xC145, 0x9950, 0xC146, + 0x9951, 0xC147, 0x9952, 0xC14A, 0x9953, 0xC14E, 0x9954, 0xC14F, 0x9955, 0xC150, 0x9956, 0xC151, 0x9957, 0xC152, 0x9958, 0xC153, + 0x9959, 0xC156, 0x995A, 0xC157, 0x9961, 0xC159, 0x9962, 0xC15A, 0x9963, 0xC15B, 0x9964, 0xC15D, 0x9965, 0xC15E, 0x9966, 0xC15F, + 0x9967, 0xC160, 0x9968, 0xC161, 0x9969, 0xC162, 0x996A, 0xC163, 0x996B, 0xC166, 0x996C, 0xC16A, 0x996D, 0xC16B, 0x996E, 0xC16C, + 0x996F, 0xC16D, 0x9970, 0xC16E, 0x9971, 0xC16F, 0x9972, 0xC171, 0x9973, 0xC172, 0x9974, 0xC173, 0x9975, 0xC175, 0x9976, 0xC176, + 0x9977, 0xC177, 0x9978, 0xC179, 0x9979, 0xC17A, 0x997A, 0xC17B, 0x9981, 0xC17C, 0x9982, 0xC17D, 0x9983, 0xC17E, 0x9984, 0xC17F, + 0x9985, 0xC180, 0x9986, 0xC181, 0x9987, 0xC182, 0x9988, 0xC183, 0x9989, 0xC184, 0x998A, 0xC186, 0x998B, 0xC187, 0x998C, 0xC188, + 0x998D, 0xC189, 0x998E, 0xC18A, 0x998F, 0xC18B, 0x9990, 0xC18F, 0x9991, 0xC191, 0x9992, 0xC192, 0x9993, 0xC193, 0x9994, 0xC195, + 0x9995, 0xC197, 0x9996, 0xC198, 0x9997, 0xC199, 0x9998, 0xC19A, 0x9999, 0xC19B, 0x999A, 0xC19E, 0x999B, 0xC1A0, 0x999C, 0xC1A2, + 0x999D, 0xC1A3, 0x999E, 0xC1A4, 0x999F, 0xC1A6, 0x99A0, 0xC1A7, 0x99A1, 0xC1AA, 0x99A2, 0xC1AB, 0x99A3, 0xC1AD, 0x99A4, 0xC1AE, + 0x99A5, 0xC1AF, 0x99A6, 0xC1B1, 0x99A7, 0xC1B2, 0x99A8, 0xC1B3, 0x99A9, 0xC1B4, 0x99AA, 0xC1B5, 0x99AB, 0xC1B6, 0x99AC, 0xC1B7, + 0x99AD, 0xC1B8, 0x99AE, 0xC1B9, 0x99AF, 0xC1BA, 0x99B0, 0xC1BB, 0x99B1, 0xC1BC, 0x99B2, 0xC1BE, 0x99B3, 0xC1BF, 0x99B4, 0xC1C0, + 0x99B5, 0xC1C1, 0x99B6, 0xC1C2, 0x99B7, 0xC1C3, 0x99B8, 0xC1C5, 0x99B9, 0xC1C6, 0x99BA, 0xC1C7, 0x99BB, 0xC1C9, 0x99BC, 0xC1CA, + 0x99BD, 0xC1CB, 0x99BE, 0xC1CD, 0x99BF, 0xC1CE, 0x99C0, 0xC1CF, 0x99C1, 0xC1D0, 0x99C2, 0xC1D1, 0x99C3, 0xC1D2, 0x99C4, 0xC1D3, + 0x99C5, 0xC1D5, 0x99C6, 0xC1D6, 0x99C7, 0xC1D9, 0x99C8, 0xC1DA, 0x99C9, 0xC1DB, 0x99CA, 0xC1DC, 0x99CB, 0xC1DD, 0x99CC, 0xC1DE, + 0x99CD, 0xC1DF, 0x99CE, 0xC1E1, 0x99CF, 0xC1E2, 0x99D0, 0xC1E3, 0x99D1, 0xC1E5, 0x99D2, 0xC1E6, 0x99D3, 0xC1E7, 0x99D4, 0xC1E9, + 0x99D5, 0xC1EA, 0x99D6, 0xC1EB, 0x99D7, 0xC1EC, 0x99D8, 0xC1ED, 0x99D9, 0xC1EE, 0x99DA, 0xC1EF, 0x99DB, 0xC1F2, 0x99DC, 0xC1F4, + 0x99DD, 0xC1F5, 0x99DE, 0xC1F6, 0x99DF, 0xC1F7, 0x99E0, 0xC1F8, 0x99E1, 0xC1F9, 0x99E2, 0xC1FA, 0x99E3, 0xC1FB, 0x99E4, 0xC1FE, + 0x99E5, 0xC1FF, 0x99E6, 0xC201, 0x99E7, 0xC202, 0x99E8, 0xC203, 0x99E9, 0xC205, 0x99EA, 0xC206, 0x99EB, 0xC207, 0x99EC, 0xC208, + 0x99ED, 0xC209, 0x99EE, 0xC20A, 0x99EF, 0xC20B, 0x99F0, 0xC20E, 0x99F1, 0xC210, 0x99F2, 0xC212, 0x99F3, 0xC213, 0x99F4, 0xC214, + 0x99F5, 0xC215, 0x99F6, 0xC216, 0x99F7, 0xC217, 0x99F8, 0xC21A, 0x99F9, 0xC21B, 0x99FA, 0xC21D, 0x99FB, 0xC21E, 0x99FC, 0xC221, + 0x99FD, 0xC222, 0x99FE, 0xC223, 0x9A41, 0xC224, 0x9A42, 0xC225, 0x9A43, 0xC226, 0x9A44, 0xC227, 0x9A45, 0xC22A, 0x9A46, 0xC22C, + 0x9A47, 0xC22E, 0x9A48, 0xC230, 0x9A49, 0xC233, 0x9A4A, 0xC235, 0x9A4B, 0xC236, 0x9A4C, 0xC237, 0x9A4D, 0xC238, 0x9A4E, 0xC239, + 0x9A4F, 0xC23A, 0x9A50, 0xC23B, 0x9A51, 0xC23C, 0x9A52, 0xC23D, 0x9A53, 0xC23E, 0x9A54, 0xC23F, 0x9A55, 0xC240, 0x9A56, 0xC241, + 0x9A57, 0xC242, 0x9A58, 0xC243, 0x9A59, 0xC244, 0x9A5A, 0xC245, 0x9A61, 0xC246, 0x9A62, 0xC247, 0x9A63, 0xC249, 0x9A64, 0xC24A, + 0x9A65, 0xC24B, 0x9A66, 0xC24C, 0x9A67, 0xC24D, 0x9A68, 0xC24E, 0x9A69, 0xC24F, 0x9A6A, 0xC252, 0x9A6B, 0xC253, 0x9A6C, 0xC255, + 0x9A6D, 0xC256, 0x9A6E, 0xC257, 0x9A6F, 0xC259, 0x9A70, 0xC25A, 0x9A71, 0xC25B, 0x9A72, 0xC25C, 0x9A73, 0xC25D, 0x9A74, 0xC25E, + 0x9A75, 0xC25F, 0x9A76, 0xC261, 0x9A77, 0xC262, 0x9A78, 0xC263, 0x9A79, 0xC264, 0x9A7A, 0xC266, 0x9A81, 0xC267, 0x9A82, 0xC268, + 0x9A83, 0xC269, 0x9A84, 0xC26A, 0x9A85, 0xC26B, 0x9A86, 0xC26E, 0x9A87, 0xC26F, 0x9A88, 0xC271, 0x9A89, 0xC272, 0x9A8A, 0xC273, + 0x9A8B, 0xC275, 0x9A8C, 0xC276, 0x9A8D, 0xC277, 0x9A8E, 0xC278, 0x9A8F, 0xC279, 0x9A90, 0xC27A, 0x9A91, 0xC27B, 0x9A92, 0xC27E, + 0x9A93, 0xC280, 0x9A94, 0xC282, 0x9A95, 0xC283, 0x9A96, 0xC284, 0x9A97, 0xC285, 0x9A98, 0xC286, 0x9A99, 0xC287, 0x9A9A, 0xC28A, + 0x9A9B, 0xC28B, 0x9A9C, 0xC28C, 0x9A9D, 0xC28D, 0x9A9E, 0xC28E, 0x9A9F, 0xC28F, 0x9AA0, 0xC291, 0x9AA1, 0xC292, 0x9AA2, 0xC293, + 0x9AA3, 0xC294, 0x9AA4, 0xC295, 0x9AA5, 0xC296, 0x9AA6, 0xC297, 0x9AA7, 0xC299, 0x9AA8, 0xC29A, 0x9AA9, 0xC29C, 0x9AAA, 0xC29E, + 0x9AAB, 0xC29F, 0x9AAC, 0xC2A0, 0x9AAD, 0xC2A1, 0x9AAE, 0xC2A2, 0x9AAF, 0xC2A3, 0x9AB0, 0xC2A6, 0x9AB1, 0xC2A7, 0x9AB2, 0xC2A9, + 0x9AB3, 0xC2AA, 0x9AB4, 0xC2AB, 0x9AB5, 0xC2AE, 0x9AB6, 0xC2AF, 0x9AB7, 0xC2B0, 0x9AB8, 0xC2B1, 0x9AB9, 0xC2B2, 0x9ABA, 0xC2B3, + 0x9ABB, 0xC2B6, 0x9ABC, 0xC2B8, 0x9ABD, 0xC2BA, 0x9ABE, 0xC2BB, 0x9ABF, 0xC2BC, 0x9AC0, 0xC2BD, 0x9AC1, 0xC2BE, 0x9AC2, 0xC2BF, + 0x9AC3, 0xC2C0, 0x9AC4, 0xC2C1, 0x9AC5, 0xC2C2, 0x9AC6, 0xC2C3, 0x9AC7, 0xC2C4, 0x9AC8, 0xC2C5, 0x9AC9, 0xC2C6, 0x9ACA, 0xC2C7, + 0x9ACB, 0xC2C8, 0x9ACC, 0xC2C9, 0x9ACD, 0xC2CA, 0x9ACE, 0xC2CB, 0x9ACF, 0xC2CC, 0x9AD0, 0xC2CD, 0x9AD1, 0xC2CE, 0x9AD2, 0xC2CF, + 0x9AD3, 0xC2D0, 0x9AD4, 0xC2D1, 0x9AD5, 0xC2D2, 0x9AD6, 0xC2D3, 0x9AD7, 0xC2D4, 0x9AD8, 0xC2D5, 0x9AD9, 0xC2D6, 0x9ADA, 0xC2D7, + 0x9ADB, 0xC2D8, 0x9ADC, 0xC2D9, 0x9ADD, 0xC2DA, 0x9ADE, 0xC2DB, 0x9ADF, 0xC2DE, 0x9AE0, 0xC2DF, 0x9AE1, 0xC2E1, 0x9AE2, 0xC2E2, + 0x9AE3, 0xC2E5, 0x9AE4, 0xC2E6, 0x9AE5, 0xC2E7, 0x9AE6, 0xC2E8, 0x9AE7, 0xC2E9, 0x9AE8, 0xC2EA, 0x9AE9, 0xC2EE, 0x9AEA, 0xC2F0, + 0x9AEB, 0xC2F2, 0x9AEC, 0xC2F3, 0x9AED, 0xC2F4, 0x9AEE, 0xC2F5, 0x9AEF, 0xC2F7, 0x9AF0, 0xC2FA, 0x9AF1, 0xC2FD, 0x9AF2, 0xC2FE, + 0x9AF3, 0xC2FF, 0x9AF4, 0xC301, 0x9AF5, 0xC302, 0x9AF6, 0xC303, 0x9AF7, 0xC304, 0x9AF8, 0xC305, 0x9AF9, 0xC306, 0x9AFA, 0xC307, + 0x9AFB, 0xC30A, 0x9AFC, 0xC30B, 0x9AFD, 0xC30E, 0x9AFE, 0xC30F, 0x9B41, 0xC310, 0x9B42, 0xC311, 0x9B43, 0xC312, 0x9B44, 0xC316, + 0x9B45, 0xC317, 0x9B46, 0xC319, 0x9B47, 0xC31A, 0x9B48, 0xC31B, 0x9B49, 0xC31D, 0x9B4A, 0xC31E, 0x9B4B, 0xC31F, 0x9B4C, 0xC320, + 0x9B4D, 0xC321, 0x9B4E, 0xC322, 0x9B4F, 0xC323, 0x9B50, 0xC326, 0x9B51, 0xC327, 0x9B52, 0xC32A, 0x9B53, 0xC32B, 0x9B54, 0xC32C, + 0x9B55, 0xC32D, 0x9B56, 0xC32E, 0x9B57, 0xC32F, 0x9B58, 0xC330, 0x9B59, 0xC331, 0x9B5A, 0xC332, 0x9B61, 0xC333, 0x9B62, 0xC334, + 0x9B63, 0xC335, 0x9B64, 0xC336, 0x9B65, 0xC337, 0x9B66, 0xC338, 0x9B67, 0xC339, 0x9B68, 0xC33A, 0x9B69, 0xC33B, 0x9B6A, 0xC33C, + 0x9B6B, 0xC33D, 0x9B6C, 0xC33E, 0x9B6D, 0xC33F, 0x9B6E, 0xC340, 0x9B6F, 0xC341, 0x9B70, 0xC342, 0x9B71, 0xC343, 0x9B72, 0xC344, + 0x9B73, 0xC346, 0x9B74, 0xC347, 0x9B75, 0xC348, 0x9B76, 0xC349, 0x9B77, 0xC34A, 0x9B78, 0xC34B, 0x9B79, 0xC34C, 0x9B7A, 0xC34D, + 0x9B81, 0xC34E, 0x9B82, 0xC34F, 0x9B83, 0xC350, 0x9B84, 0xC351, 0x9B85, 0xC352, 0x9B86, 0xC353, 0x9B87, 0xC354, 0x9B88, 0xC355, + 0x9B89, 0xC356, 0x9B8A, 0xC357, 0x9B8B, 0xC358, 0x9B8C, 0xC359, 0x9B8D, 0xC35A, 0x9B8E, 0xC35B, 0x9B8F, 0xC35C, 0x9B90, 0xC35D, + 0x9B91, 0xC35E, 0x9B92, 0xC35F, 0x9B93, 0xC360, 0x9B94, 0xC361, 0x9B95, 0xC362, 0x9B96, 0xC363, 0x9B97, 0xC364, 0x9B98, 0xC365, + 0x9B99, 0xC366, 0x9B9A, 0xC367, 0x9B9B, 0xC36A, 0x9B9C, 0xC36B, 0x9B9D, 0xC36D, 0x9B9E, 0xC36E, 0x9B9F, 0xC36F, 0x9BA0, 0xC371, + 0x9BA1, 0xC373, 0x9BA2, 0xC374, 0x9BA3, 0xC375, 0x9BA4, 0xC376, 0x9BA5, 0xC377, 0x9BA6, 0xC37A, 0x9BA7, 0xC37B, 0x9BA8, 0xC37E, + 0x9BA9, 0xC37F, 0x9BAA, 0xC380, 0x9BAB, 0xC381, 0x9BAC, 0xC382, 0x9BAD, 0xC383, 0x9BAE, 0xC385, 0x9BAF, 0xC386, 0x9BB0, 0xC387, + 0x9BB1, 0xC389, 0x9BB2, 0xC38A, 0x9BB3, 0xC38B, 0x9BB4, 0xC38D, 0x9BB5, 0xC38E, 0x9BB6, 0xC38F, 0x9BB7, 0xC390, 0x9BB8, 0xC391, + 0x9BB9, 0xC392, 0x9BBA, 0xC393, 0x9BBB, 0xC394, 0x9BBC, 0xC395, 0x9BBD, 0xC396, 0x9BBE, 0xC397, 0x9BBF, 0xC398, 0x9BC0, 0xC399, + 0x9BC1, 0xC39A, 0x9BC2, 0xC39B, 0x9BC3, 0xC39C, 0x9BC4, 0xC39D, 0x9BC5, 0xC39E, 0x9BC6, 0xC39F, 0x9BC7, 0xC3A0, 0x9BC8, 0xC3A1, + 0x9BC9, 0xC3A2, 0x9BCA, 0xC3A3, 0x9BCB, 0xC3A4, 0x9BCC, 0xC3A5, 0x9BCD, 0xC3A6, 0x9BCE, 0xC3A7, 0x9BCF, 0xC3A8, 0x9BD0, 0xC3A9, + 0x9BD1, 0xC3AA, 0x9BD2, 0xC3AB, 0x9BD3, 0xC3AC, 0x9BD4, 0xC3AD, 0x9BD5, 0xC3AE, 0x9BD6, 0xC3AF, 0x9BD7, 0xC3B0, 0x9BD8, 0xC3B1, + 0x9BD9, 0xC3B2, 0x9BDA, 0xC3B3, 0x9BDB, 0xC3B4, 0x9BDC, 0xC3B5, 0x9BDD, 0xC3B6, 0x9BDE, 0xC3B7, 0x9BDF, 0xC3B8, 0x9BE0, 0xC3B9, + 0x9BE1, 0xC3BA, 0x9BE2, 0xC3BB, 0x9BE3, 0xC3BC, 0x9BE4, 0xC3BD, 0x9BE5, 0xC3BE, 0x9BE6, 0xC3BF, 0x9BE7, 0xC3C1, 0x9BE8, 0xC3C2, + 0x9BE9, 0xC3C3, 0x9BEA, 0xC3C4, 0x9BEB, 0xC3C5, 0x9BEC, 0xC3C6, 0x9BED, 0xC3C7, 0x9BEE, 0xC3C8, 0x9BEF, 0xC3C9, 0x9BF0, 0xC3CA, + 0x9BF1, 0xC3CB, 0x9BF2, 0xC3CC, 0x9BF3, 0xC3CD, 0x9BF4, 0xC3CE, 0x9BF5, 0xC3CF, 0x9BF6, 0xC3D0, 0x9BF7, 0xC3D1, 0x9BF8, 0xC3D2, + 0x9BF9, 0xC3D3, 0x9BFA, 0xC3D4, 0x9BFB, 0xC3D5, 0x9BFC, 0xC3D6, 0x9BFD, 0xC3D7, 0x9BFE, 0xC3DA, 0x9C41, 0xC3DB, 0x9C42, 0xC3DD, + 0x9C43, 0xC3DE, 0x9C44, 0xC3E1, 0x9C45, 0xC3E3, 0x9C46, 0xC3E4, 0x9C47, 0xC3E5, 0x9C48, 0xC3E6, 0x9C49, 0xC3E7, 0x9C4A, 0xC3EA, + 0x9C4B, 0xC3EB, 0x9C4C, 0xC3EC, 0x9C4D, 0xC3EE, 0x9C4E, 0xC3EF, 0x9C4F, 0xC3F0, 0x9C50, 0xC3F1, 0x9C51, 0xC3F2, 0x9C52, 0xC3F3, + 0x9C53, 0xC3F6, 0x9C54, 0xC3F7, 0x9C55, 0xC3F9, 0x9C56, 0xC3FA, 0x9C57, 0xC3FB, 0x9C58, 0xC3FC, 0x9C59, 0xC3FD, 0x9C5A, 0xC3FE, + 0x9C61, 0xC3FF, 0x9C62, 0xC400, 0x9C63, 0xC401, 0x9C64, 0xC402, 0x9C65, 0xC403, 0x9C66, 0xC404, 0x9C67, 0xC405, 0x9C68, 0xC406, + 0x9C69, 0xC407, 0x9C6A, 0xC409, 0x9C6B, 0xC40A, 0x9C6C, 0xC40B, 0x9C6D, 0xC40C, 0x9C6E, 0xC40D, 0x9C6F, 0xC40E, 0x9C70, 0xC40F, + 0x9C71, 0xC411, 0x9C72, 0xC412, 0x9C73, 0xC413, 0x9C74, 0xC414, 0x9C75, 0xC415, 0x9C76, 0xC416, 0x9C77, 0xC417, 0x9C78, 0xC418, + 0x9C79, 0xC419, 0x9C7A, 0xC41A, 0x9C81, 0xC41B, 0x9C82, 0xC41C, 0x9C83, 0xC41D, 0x9C84, 0xC41E, 0x9C85, 0xC41F, 0x9C86, 0xC420, + 0x9C87, 0xC421, 0x9C88, 0xC422, 0x9C89, 0xC423, 0x9C8A, 0xC425, 0x9C8B, 0xC426, 0x9C8C, 0xC427, 0x9C8D, 0xC428, 0x9C8E, 0xC429, + 0x9C8F, 0xC42A, 0x9C90, 0xC42B, 0x9C91, 0xC42D, 0x9C92, 0xC42E, 0x9C93, 0xC42F, 0x9C94, 0xC431, 0x9C95, 0xC432, 0x9C96, 0xC433, + 0x9C97, 0xC435, 0x9C98, 0xC436, 0x9C99, 0xC437, 0x9C9A, 0xC438, 0x9C9B, 0xC439, 0x9C9C, 0xC43A, 0x9C9D, 0xC43B, 0x9C9E, 0xC43E, + 0x9C9F, 0xC43F, 0x9CA0, 0xC440, 0x9CA1, 0xC441, 0x9CA2, 0xC442, 0x9CA3, 0xC443, 0x9CA4, 0xC444, 0x9CA5, 0xC445, 0x9CA6, 0xC446, + 0x9CA7, 0xC447, 0x9CA8, 0xC449, 0x9CA9, 0xC44A, 0x9CAA, 0xC44B, 0x9CAB, 0xC44C, 0x9CAC, 0xC44D, 0x9CAD, 0xC44E, 0x9CAE, 0xC44F, + 0x9CAF, 0xC450, 0x9CB0, 0xC451, 0x9CB1, 0xC452, 0x9CB2, 0xC453, 0x9CB3, 0xC454, 0x9CB4, 0xC455, 0x9CB5, 0xC456, 0x9CB6, 0xC457, + 0x9CB7, 0xC458, 0x9CB8, 0xC459, 0x9CB9, 0xC45A, 0x9CBA, 0xC45B, 0x9CBB, 0xC45C, 0x9CBC, 0xC45D, 0x9CBD, 0xC45E, 0x9CBE, 0xC45F, + 0x9CBF, 0xC460, 0x9CC0, 0xC461, 0x9CC1, 0xC462, 0x9CC2, 0xC463, 0x9CC3, 0xC466, 0x9CC4, 0xC467, 0x9CC5, 0xC469, 0x9CC6, 0xC46A, + 0x9CC7, 0xC46B, 0x9CC8, 0xC46D, 0x9CC9, 0xC46E, 0x9CCA, 0xC46F, 0x9CCB, 0xC470, 0x9CCC, 0xC471, 0x9CCD, 0xC472, 0x9CCE, 0xC473, + 0x9CCF, 0xC476, 0x9CD0, 0xC477, 0x9CD1, 0xC478, 0x9CD2, 0xC47A, 0x9CD3, 0xC47B, 0x9CD4, 0xC47C, 0x9CD5, 0xC47D, 0x9CD6, 0xC47E, + 0x9CD7, 0xC47F, 0x9CD8, 0xC481, 0x9CD9, 0xC482, 0x9CDA, 0xC483, 0x9CDB, 0xC484, 0x9CDC, 0xC485, 0x9CDD, 0xC486, 0x9CDE, 0xC487, + 0x9CDF, 0xC488, 0x9CE0, 0xC489, 0x9CE1, 0xC48A, 0x9CE2, 0xC48B, 0x9CE3, 0xC48C, 0x9CE4, 0xC48D, 0x9CE5, 0xC48E, 0x9CE6, 0xC48F, + 0x9CE7, 0xC490, 0x9CE8, 0xC491, 0x9CE9, 0xC492, 0x9CEA, 0xC493, 0x9CEB, 0xC495, 0x9CEC, 0xC496, 0x9CED, 0xC497, 0x9CEE, 0xC498, + 0x9CEF, 0xC499, 0x9CF0, 0xC49A, 0x9CF1, 0xC49B, 0x9CF2, 0xC49D, 0x9CF3, 0xC49E, 0x9CF4, 0xC49F, 0x9CF5, 0xC4A0, 0x9CF6, 0xC4A1, + 0x9CF7, 0xC4A2, 0x9CF8, 0xC4A3, 0x9CF9, 0xC4A4, 0x9CFA, 0xC4A5, 0x9CFB, 0xC4A6, 0x9CFC, 0xC4A7, 0x9CFD, 0xC4A8, 0x9CFE, 0xC4A9, + 0x9D41, 0xC4AA, 0x9D42, 0xC4AB, 0x9D43, 0xC4AC, 0x9D44, 0xC4AD, 0x9D45, 0xC4AE, 0x9D46, 0xC4AF, 0x9D47, 0xC4B0, 0x9D48, 0xC4B1, + 0x9D49, 0xC4B2, 0x9D4A, 0xC4B3, 0x9D4B, 0xC4B4, 0x9D4C, 0xC4B5, 0x9D4D, 0xC4B6, 0x9D4E, 0xC4B7, 0x9D4F, 0xC4B9, 0x9D50, 0xC4BA, + 0x9D51, 0xC4BB, 0x9D52, 0xC4BD, 0x9D53, 0xC4BE, 0x9D54, 0xC4BF, 0x9D55, 0xC4C0, 0x9D56, 0xC4C1, 0x9D57, 0xC4C2, 0x9D58, 0xC4C3, + 0x9D59, 0xC4C4, 0x9D5A, 0xC4C5, 0x9D61, 0xC4C6, 0x9D62, 0xC4C7, 0x9D63, 0xC4C8, 0x9D64, 0xC4C9, 0x9D65, 0xC4CA, 0x9D66, 0xC4CB, + 0x9D67, 0xC4CC, 0x9D68, 0xC4CD, 0x9D69, 0xC4CE, 0x9D6A, 0xC4CF, 0x9D6B, 0xC4D0, 0x9D6C, 0xC4D1, 0x9D6D, 0xC4D2, 0x9D6E, 0xC4D3, + 0x9D6F, 0xC4D4, 0x9D70, 0xC4D5, 0x9D71, 0xC4D6, 0x9D72, 0xC4D7, 0x9D73, 0xC4D8, 0x9D74, 0xC4D9, 0x9D75, 0xC4DA, 0x9D76, 0xC4DB, + 0x9D77, 0xC4DC, 0x9D78, 0xC4DD, 0x9D79, 0xC4DE, 0x9D7A, 0xC4DF, 0x9D81, 0xC4E0, 0x9D82, 0xC4E1, 0x9D83, 0xC4E2, 0x9D84, 0xC4E3, + 0x9D85, 0xC4E4, 0x9D86, 0xC4E5, 0x9D87, 0xC4E6, 0x9D88, 0xC4E7, 0x9D89, 0xC4E8, 0x9D8A, 0xC4EA, 0x9D8B, 0xC4EB, 0x9D8C, 0xC4EC, + 0x9D8D, 0xC4ED, 0x9D8E, 0xC4EE, 0x9D8F, 0xC4EF, 0x9D90, 0xC4F2, 0x9D91, 0xC4F3, 0x9D92, 0xC4F5, 0x9D93, 0xC4F6, 0x9D94, 0xC4F7, + 0x9D95, 0xC4F9, 0x9D96, 0xC4FB, 0x9D97, 0xC4FC, 0x9D98, 0xC4FD, 0x9D99, 0xC4FE, 0x9D9A, 0xC502, 0x9D9B, 0xC503, 0x9D9C, 0xC504, + 0x9D9D, 0xC505, 0x9D9E, 0xC506, 0x9D9F, 0xC507, 0x9DA0, 0xC508, 0x9DA1, 0xC509, 0x9DA2, 0xC50A, 0x9DA3, 0xC50B, 0x9DA4, 0xC50D, + 0x9DA5, 0xC50E, 0x9DA6, 0xC50F, 0x9DA7, 0xC511, 0x9DA8, 0xC512, 0x9DA9, 0xC513, 0x9DAA, 0xC515, 0x9DAB, 0xC516, 0x9DAC, 0xC517, + 0x9DAD, 0xC518, 0x9DAE, 0xC519, 0x9DAF, 0xC51A, 0x9DB0, 0xC51B, 0x9DB1, 0xC51D, 0x9DB2, 0xC51E, 0x9DB3, 0xC51F, 0x9DB4, 0xC520, + 0x9DB5, 0xC521, 0x9DB6, 0xC522, 0x9DB7, 0xC523, 0x9DB8, 0xC524, 0x9DB9, 0xC525, 0x9DBA, 0xC526, 0x9DBB, 0xC527, 0x9DBC, 0xC52A, + 0x9DBD, 0xC52B, 0x9DBE, 0xC52D, 0x9DBF, 0xC52E, 0x9DC0, 0xC52F, 0x9DC1, 0xC531, 0x9DC2, 0xC532, 0x9DC3, 0xC533, 0x9DC4, 0xC534, + 0x9DC5, 0xC535, 0x9DC6, 0xC536, 0x9DC7, 0xC537, 0x9DC8, 0xC53A, 0x9DC9, 0xC53C, 0x9DCA, 0xC53E, 0x9DCB, 0xC53F, 0x9DCC, 0xC540, + 0x9DCD, 0xC541, 0x9DCE, 0xC542, 0x9DCF, 0xC543, 0x9DD0, 0xC546, 0x9DD1, 0xC547, 0x9DD2, 0xC54B, 0x9DD3, 0xC54F, 0x9DD4, 0xC550, + 0x9DD5, 0xC551, 0x9DD6, 0xC552, 0x9DD7, 0xC556, 0x9DD8, 0xC55A, 0x9DD9, 0xC55B, 0x9DDA, 0xC55C, 0x9DDB, 0xC55F, 0x9DDC, 0xC562, + 0x9DDD, 0xC563, 0x9DDE, 0xC565, 0x9DDF, 0xC566, 0x9DE0, 0xC567, 0x9DE1, 0xC569, 0x9DE2, 0xC56A, 0x9DE3, 0xC56B, 0x9DE4, 0xC56C, + 0x9DE5, 0xC56D, 0x9DE6, 0xC56E, 0x9DE7, 0xC56F, 0x9DE8, 0xC572, 0x9DE9, 0xC576, 0x9DEA, 0xC577, 0x9DEB, 0xC578, 0x9DEC, 0xC579, + 0x9DED, 0xC57A, 0x9DEE, 0xC57B, 0x9DEF, 0xC57E, 0x9DF0, 0xC57F, 0x9DF1, 0xC581, 0x9DF2, 0xC582, 0x9DF3, 0xC583, 0x9DF4, 0xC585, + 0x9DF5, 0xC586, 0x9DF6, 0xC588, 0x9DF7, 0xC589, 0x9DF8, 0xC58A, 0x9DF9, 0xC58B, 0x9DFA, 0xC58E, 0x9DFB, 0xC590, 0x9DFC, 0xC592, + 0x9DFD, 0xC593, 0x9DFE, 0xC594, 0x9E41, 0xC596, 0x9E42, 0xC599, 0x9E43, 0xC59A, 0x9E44, 0xC59B, 0x9E45, 0xC59D, 0x9E46, 0xC59E, + 0x9E47, 0xC59F, 0x9E48, 0xC5A1, 0x9E49, 0xC5A2, 0x9E4A, 0xC5A3, 0x9E4B, 0xC5A4, 0x9E4C, 0xC5A5, 0x9E4D, 0xC5A6, 0x9E4E, 0xC5A7, + 0x9E4F, 0xC5A8, 0x9E50, 0xC5AA, 0x9E51, 0xC5AB, 0x9E52, 0xC5AC, 0x9E53, 0xC5AD, 0x9E54, 0xC5AE, 0x9E55, 0xC5AF, 0x9E56, 0xC5B0, + 0x9E57, 0xC5B1, 0x9E58, 0xC5B2, 0x9E59, 0xC5B3, 0x9E5A, 0xC5B6, 0x9E61, 0xC5B7, 0x9E62, 0xC5BA, 0x9E63, 0xC5BF, 0x9E64, 0xC5C0, + 0x9E65, 0xC5C1, 0x9E66, 0xC5C2, 0x9E67, 0xC5C3, 0x9E68, 0xC5CB, 0x9E69, 0xC5CD, 0x9E6A, 0xC5CF, 0x9E6B, 0xC5D2, 0x9E6C, 0xC5D3, + 0x9E6D, 0xC5D5, 0x9E6E, 0xC5D6, 0x9E6F, 0xC5D7, 0x9E70, 0xC5D9, 0x9E71, 0xC5DA, 0x9E72, 0xC5DB, 0x9E73, 0xC5DC, 0x9E74, 0xC5DD, + 0x9E75, 0xC5DE, 0x9E76, 0xC5DF, 0x9E77, 0xC5E2, 0x9E78, 0xC5E4, 0x9E79, 0xC5E6, 0x9E7A, 0xC5E7, 0x9E81, 0xC5E8, 0x9E82, 0xC5E9, + 0x9E83, 0xC5EA, 0x9E84, 0xC5EB, 0x9E85, 0xC5EF, 0x9E86, 0xC5F1, 0x9E87, 0xC5F2, 0x9E88, 0xC5F3, 0x9E89, 0xC5F5, 0x9E8A, 0xC5F8, + 0x9E8B, 0xC5F9, 0x9E8C, 0xC5FA, 0x9E8D, 0xC5FB, 0x9E8E, 0xC602, 0x9E8F, 0xC603, 0x9E90, 0xC604, 0x9E91, 0xC609, 0x9E92, 0xC60A, + 0x9E93, 0xC60B, 0x9E94, 0xC60D, 0x9E95, 0xC60E, 0x9E96, 0xC60F, 0x9E97, 0xC611, 0x9E98, 0xC612, 0x9E99, 0xC613, 0x9E9A, 0xC614, + 0x9E9B, 0xC615, 0x9E9C, 0xC616, 0x9E9D, 0xC617, 0x9E9E, 0xC61A, 0x9E9F, 0xC61D, 0x9EA0, 0xC61E, 0x9EA1, 0xC61F, 0x9EA2, 0xC620, + 0x9EA3, 0xC621, 0x9EA4, 0xC622, 0x9EA5, 0xC623, 0x9EA6, 0xC626, 0x9EA7, 0xC627, 0x9EA8, 0xC629, 0x9EA9, 0xC62A, 0x9EAA, 0xC62B, + 0x9EAB, 0xC62F, 0x9EAC, 0xC631, 0x9EAD, 0xC632, 0x9EAE, 0xC636, 0x9EAF, 0xC638, 0x9EB0, 0xC63A, 0x9EB1, 0xC63C, 0x9EB2, 0xC63D, + 0x9EB3, 0xC63E, 0x9EB4, 0xC63F, 0x9EB5, 0xC642, 0x9EB6, 0xC643, 0x9EB7, 0xC645, 0x9EB8, 0xC646, 0x9EB9, 0xC647, 0x9EBA, 0xC649, + 0x9EBB, 0xC64A, 0x9EBC, 0xC64B, 0x9EBD, 0xC64C, 0x9EBE, 0xC64D, 0x9EBF, 0xC64E, 0x9EC0, 0xC64F, 0x9EC1, 0xC652, 0x9EC2, 0xC656, + 0x9EC3, 0xC657, 0x9EC4, 0xC658, 0x9EC5, 0xC659, 0x9EC6, 0xC65A, 0x9EC7, 0xC65B, 0x9EC8, 0xC65E, 0x9EC9, 0xC65F, 0x9ECA, 0xC661, + 0x9ECB, 0xC662, 0x9ECC, 0xC663, 0x9ECD, 0xC664, 0x9ECE, 0xC665, 0x9ECF, 0xC666, 0x9ED0, 0xC667, 0x9ED1, 0xC668, 0x9ED2, 0xC669, + 0x9ED3, 0xC66A, 0x9ED4, 0xC66B, 0x9ED5, 0xC66D, 0x9ED6, 0xC66E, 0x9ED7, 0xC670, 0x9ED8, 0xC672, 0x9ED9, 0xC673, 0x9EDA, 0xC674, + 0x9EDB, 0xC675, 0x9EDC, 0xC676, 0x9EDD, 0xC677, 0x9EDE, 0xC67A, 0x9EDF, 0xC67B, 0x9EE0, 0xC67D, 0x9EE1, 0xC67E, 0x9EE2, 0xC67F, + 0x9EE3, 0xC681, 0x9EE4, 0xC682, 0x9EE5, 0xC683, 0x9EE6, 0xC684, 0x9EE7, 0xC685, 0x9EE8, 0xC686, 0x9EE9, 0xC687, 0x9EEA, 0xC68A, + 0x9EEB, 0xC68C, 0x9EEC, 0xC68E, 0x9EED, 0xC68F, 0x9EEE, 0xC690, 0x9EEF, 0xC691, 0x9EF0, 0xC692, 0x9EF1, 0xC693, 0x9EF2, 0xC696, + 0x9EF3, 0xC697, 0x9EF4, 0xC699, 0x9EF5, 0xC69A, 0x9EF6, 0xC69B, 0x9EF7, 0xC69D, 0x9EF8, 0xC69E, 0x9EF9, 0xC69F, 0x9EFA, 0xC6A0, + 0x9EFB, 0xC6A1, 0x9EFC, 0xC6A2, 0x9EFD, 0xC6A3, 0x9EFE, 0xC6A6, 0x9F41, 0xC6A8, 0x9F42, 0xC6AA, 0x9F43, 0xC6AB, 0x9F44, 0xC6AC, + 0x9F45, 0xC6AD, 0x9F46, 0xC6AE, 0x9F47, 0xC6AF, 0x9F48, 0xC6B2, 0x9F49, 0xC6B3, 0x9F4A, 0xC6B5, 0x9F4B, 0xC6B6, 0x9F4C, 0xC6B7, + 0x9F4D, 0xC6BB, 0x9F4E, 0xC6BC, 0x9F4F, 0xC6BD, 0x9F50, 0xC6BE, 0x9F51, 0xC6BF, 0x9F52, 0xC6C2, 0x9F53, 0xC6C4, 0x9F54, 0xC6C6, + 0x9F55, 0xC6C7, 0x9F56, 0xC6C8, 0x9F57, 0xC6C9, 0x9F58, 0xC6CA, 0x9F59, 0xC6CB, 0x9F5A, 0xC6CE, 0x9F61, 0xC6CF, 0x9F62, 0xC6D1, + 0x9F63, 0xC6D2, 0x9F64, 0xC6D3, 0x9F65, 0xC6D5, 0x9F66, 0xC6D6, 0x9F67, 0xC6D7, 0x9F68, 0xC6D8, 0x9F69, 0xC6D9, 0x9F6A, 0xC6DA, + 0x9F6B, 0xC6DB, 0x9F6C, 0xC6DE, 0x9F6D, 0xC6DF, 0x9F6E, 0xC6E2, 0x9F6F, 0xC6E3, 0x9F70, 0xC6E4, 0x9F71, 0xC6E5, 0x9F72, 0xC6E6, + 0x9F73, 0xC6E7, 0x9F74, 0xC6EA, 0x9F75, 0xC6EB, 0x9F76, 0xC6ED, 0x9F77, 0xC6EE, 0x9F78, 0xC6EF, 0x9F79, 0xC6F1, 0x9F7A, 0xC6F2, + 0x9F81, 0xC6F3, 0x9F82, 0xC6F4, 0x9F83, 0xC6F5, 0x9F84, 0xC6F6, 0x9F85, 0xC6F7, 0x9F86, 0xC6FA, 0x9F87, 0xC6FB, 0x9F88, 0xC6FC, + 0x9F89, 0xC6FE, 0x9F8A, 0xC6FF, 0x9F8B, 0xC700, 0x9F8C, 0xC701, 0x9F8D, 0xC702, 0x9F8E, 0xC703, 0x9F8F, 0xC706, 0x9F90, 0xC707, + 0x9F91, 0xC709, 0x9F92, 0xC70A, 0x9F93, 0xC70B, 0x9F94, 0xC70D, 0x9F95, 0xC70E, 0x9F96, 0xC70F, 0x9F97, 0xC710, 0x9F98, 0xC711, + 0x9F99, 0xC712, 0x9F9A, 0xC713, 0x9F9B, 0xC716, 0x9F9C, 0xC718, 0x9F9D, 0xC71A, 0x9F9E, 0xC71B, 0x9F9F, 0xC71C, 0x9FA0, 0xC71D, + 0x9FA1, 0xC71E, 0x9FA2, 0xC71F, 0x9FA3, 0xC722, 0x9FA4, 0xC723, 0x9FA5, 0xC725, 0x9FA6, 0xC726, 0x9FA7, 0xC727, 0x9FA8, 0xC729, + 0x9FA9, 0xC72A, 0x9FAA, 0xC72B, 0x9FAB, 0xC72C, 0x9FAC, 0xC72D, 0x9FAD, 0xC72E, 0x9FAE, 0xC72F, 0x9FAF, 0xC732, 0x9FB0, 0xC734, + 0x9FB1, 0xC736, 0x9FB2, 0xC738, 0x9FB3, 0xC739, 0x9FB4, 0xC73A, 0x9FB5, 0xC73B, 0x9FB6, 0xC73E, 0x9FB7, 0xC73F, 0x9FB8, 0xC741, + 0x9FB9, 0xC742, 0x9FBA, 0xC743, 0x9FBB, 0xC745, 0x9FBC, 0xC746, 0x9FBD, 0xC747, 0x9FBE, 0xC748, 0x9FBF, 0xC749, 0x9FC0, 0xC74B, + 0x9FC1, 0xC74E, 0x9FC2, 0xC750, 0x9FC3, 0xC759, 0x9FC4, 0xC75A, 0x9FC5, 0xC75B, 0x9FC6, 0xC75D, 0x9FC7, 0xC75E, 0x9FC8, 0xC75F, + 0x9FC9, 0xC761, 0x9FCA, 0xC762, 0x9FCB, 0xC763, 0x9FCC, 0xC764, 0x9FCD, 0xC765, 0x9FCE, 0xC766, 0x9FCF, 0xC767, 0x9FD0, 0xC769, + 0x9FD1, 0xC76A, 0x9FD2, 0xC76C, 0x9FD3, 0xC76D, 0x9FD4, 0xC76E, 0x9FD5, 0xC76F, 0x9FD6, 0xC770, 0x9FD7, 0xC771, 0x9FD8, 0xC772, + 0x9FD9, 0xC773, 0x9FDA, 0xC776, 0x9FDB, 0xC777, 0x9FDC, 0xC779, 0x9FDD, 0xC77A, 0x9FDE, 0xC77B, 0x9FDF, 0xC77F, 0x9FE0, 0xC780, + 0x9FE1, 0xC781, 0x9FE2, 0xC782, 0x9FE3, 0xC786, 0x9FE4, 0xC78B, 0x9FE5, 0xC78C, 0x9FE6, 0xC78D, 0x9FE7, 0xC78F, 0x9FE8, 0xC792, + 0x9FE9, 0xC793, 0x9FEA, 0xC795, 0x9FEB, 0xC799, 0x9FEC, 0xC79B, 0x9FED, 0xC79C, 0x9FEE, 0xC79D, 0x9FEF, 0xC79E, 0x9FF0, 0xC79F, + 0x9FF1, 0xC7A2, 0x9FF2, 0xC7A7, 0x9FF3, 0xC7A8, 0x9FF4, 0xC7A9, 0x9FF5, 0xC7AA, 0x9FF6, 0xC7AB, 0x9FF7, 0xC7AE, 0x9FF8, 0xC7AF, + 0x9FF9, 0xC7B1, 0x9FFA, 0xC7B2, 0x9FFB, 0xC7B3, 0x9FFC, 0xC7B5, 0x9FFD, 0xC7B6, 0x9FFE, 0xC7B7, 0xA041, 0xC7B8, 0xA042, 0xC7B9, + 0xA043, 0xC7BA, 0xA044, 0xC7BB, 0xA045, 0xC7BE, 0xA046, 0xC7C2, 0xA047, 0xC7C3, 0xA048, 0xC7C4, 0xA049, 0xC7C5, 0xA04A, 0xC7C6, + 0xA04B, 0xC7C7, 0xA04C, 0xC7CA, 0xA04D, 0xC7CB, 0xA04E, 0xC7CD, 0xA04F, 0xC7CF, 0xA050, 0xC7D1, 0xA051, 0xC7D2, 0xA052, 0xC7D3, + 0xA053, 0xC7D4, 0xA054, 0xC7D5, 0xA055, 0xC7D6, 0xA056, 0xC7D7, 0xA057, 0xC7D9, 0xA058, 0xC7DA, 0xA059, 0xC7DB, 0xA05A, 0xC7DC, + 0xA061, 0xC7DE, 0xA062, 0xC7DF, 0xA063, 0xC7E0, 0xA064, 0xC7E1, 0xA065, 0xC7E2, 0xA066, 0xC7E3, 0xA067, 0xC7E5, 0xA068, 0xC7E6, + 0xA069, 0xC7E7, 0xA06A, 0xC7E9, 0xA06B, 0xC7EA, 0xA06C, 0xC7EB, 0xA06D, 0xC7ED, 0xA06E, 0xC7EE, 0xA06F, 0xC7EF, 0xA070, 0xC7F0, + 0xA071, 0xC7F1, 0xA072, 0xC7F2, 0xA073, 0xC7F3, 0xA074, 0xC7F4, 0xA075, 0xC7F5, 0xA076, 0xC7F6, 0xA077, 0xC7F7, 0xA078, 0xC7F8, + 0xA079, 0xC7F9, 0xA07A, 0xC7FA, 0xA081, 0xC7FB, 0xA082, 0xC7FC, 0xA083, 0xC7FD, 0xA084, 0xC7FE, 0xA085, 0xC7FF, 0xA086, 0xC802, + 0xA087, 0xC803, 0xA088, 0xC805, 0xA089, 0xC806, 0xA08A, 0xC807, 0xA08B, 0xC809, 0xA08C, 0xC80B, 0xA08D, 0xC80C, 0xA08E, 0xC80D, + 0xA08F, 0xC80E, 0xA090, 0xC80F, 0xA091, 0xC812, 0xA092, 0xC814, 0xA093, 0xC817, 0xA094, 0xC818, 0xA095, 0xC819, 0xA096, 0xC81A, + 0xA097, 0xC81B, 0xA098, 0xC81E, 0xA099, 0xC81F, 0xA09A, 0xC821, 0xA09B, 0xC822, 0xA09C, 0xC823, 0xA09D, 0xC825, 0xA09E, 0xC826, + 0xA09F, 0xC827, 0xA0A0, 0xC828, 0xA0A1, 0xC829, 0xA0A2, 0xC82A, 0xA0A3, 0xC82B, 0xA0A4, 0xC82E, 0xA0A5, 0xC830, 0xA0A6, 0xC832, + 0xA0A7, 0xC833, 0xA0A8, 0xC834, 0xA0A9, 0xC835, 0xA0AA, 0xC836, 0xA0AB, 0xC837, 0xA0AC, 0xC839, 0xA0AD, 0xC83A, 0xA0AE, 0xC83B, + 0xA0AF, 0xC83D, 0xA0B0, 0xC83E, 0xA0B1, 0xC83F, 0xA0B2, 0xC841, 0xA0B3, 0xC842, 0xA0B4, 0xC843, 0xA0B5, 0xC844, 0xA0B6, 0xC845, + 0xA0B7, 0xC846, 0xA0B8, 0xC847, 0xA0B9, 0xC84A, 0xA0BA, 0xC84B, 0xA0BB, 0xC84E, 0xA0BC, 0xC84F, 0xA0BD, 0xC850, 0xA0BE, 0xC851, + 0xA0BF, 0xC852, 0xA0C0, 0xC853, 0xA0C1, 0xC855, 0xA0C2, 0xC856, 0xA0C3, 0xC857, 0xA0C4, 0xC858, 0xA0C5, 0xC859, 0xA0C6, 0xC85A, + 0xA0C7, 0xC85B, 0xA0C8, 0xC85C, 0xA0C9, 0xC85D, 0xA0CA, 0xC85E, 0xA0CB, 0xC85F, 0xA0CC, 0xC860, 0xA0CD, 0xC861, 0xA0CE, 0xC862, + 0xA0CF, 0xC863, 0xA0D0, 0xC864, 0xA0D1, 0xC865, 0xA0D2, 0xC866, 0xA0D3, 0xC867, 0xA0D4, 0xC868, 0xA0D5, 0xC869, 0xA0D6, 0xC86A, + 0xA0D7, 0xC86B, 0xA0D8, 0xC86C, 0xA0D9, 0xC86D, 0xA0DA, 0xC86E, 0xA0DB, 0xC86F, 0xA0DC, 0xC872, 0xA0DD, 0xC873, 0xA0DE, 0xC875, + 0xA0DF, 0xC876, 0xA0E0, 0xC877, 0xA0E1, 0xC879, 0xA0E2, 0xC87B, 0xA0E3, 0xC87C, 0xA0E4, 0xC87D, 0xA0E5, 0xC87E, 0xA0E6, 0xC87F, + 0xA0E7, 0xC882, 0xA0E8, 0xC884, 0xA0E9, 0xC888, 0xA0EA, 0xC889, 0xA0EB, 0xC88A, 0xA0EC, 0xC88E, 0xA0ED, 0xC88F, 0xA0EE, 0xC890, + 0xA0EF, 0xC891, 0xA0F0, 0xC892, 0xA0F1, 0xC893, 0xA0F2, 0xC895, 0xA0F3, 0xC896, 0xA0F4, 0xC897, 0xA0F5, 0xC898, 0xA0F6, 0xC899, + 0xA0F7, 0xC89A, 0xA0F8, 0xC89B, 0xA0F9, 0xC89C, 0xA0FA, 0xC89E, 0xA0FB, 0xC8A0, 0xA0FC, 0xC8A2, 0xA0FD, 0xC8A3, 0xA0FE, 0xC8A4, + 0xA141, 0xC8A5, 0xA142, 0xC8A6, 0xA143, 0xC8A7, 0xA144, 0xC8A9, 0xA145, 0xC8AA, 0xA146, 0xC8AB, 0xA147, 0xC8AC, 0xA148, 0xC8AD, + 0xA149, 0xC8AE, 0xA14A, 0xC8AF, 0xA14B, 0xC8B0, 0xA14C, 0xC8B1, 0xA14D, 0xC8B2, 0xA14E, 0xC8B3, 0xA14F, 0xC8B4, 0xA150, 0xC8B5, + 0xA151, 0xC8B6, 0xA152, 0xC8B7, 0xA153, 0xC8B8, 0xA154, 0xC8B9, 0xA155, 0xC8BA, 0xA156, 0xC8BB, 0xA157, 0xC8BE, 0xA158, 0xC8BF, + 0xA159, 0xC8C0, 0xA15A, 0xC8C1, 0xA161, 0xC8C2, 0xA162, 0xC8C3, 0xA163, 0xC8C5, 0xA164, 0xC8C6, 0xA165, 0xC8C7, 0xA166, 0xC8C9, + 0xA167, 0xC8CA, 0xA168, 0xC8CB, 0xA169, 0xC8CD, 0xA16A, 0xC8CE, 0xA16B, 0xC8CF, 0xA16C, 0xC8D0, 0xA16D, 0xC8D1, 0xA16E, 0xC8D2, + 0xA16F, 0xC8D3, 0xA170, 0xC8D6, 0xA171, 0xC8D8, 0xA172, 0xC8DA, 0xA173, 0xC8DB, 0xA174, 0xC8DC, 0xA175, 0xC8DD, 0xA176, 0xC8DE, + 0xA177, 0xC8DF, 0xA178, 0xC8E2, 0xA179, 0xC8E3, 0xA17A, 0xC8E5, 0xA181, 0xC8E6, 0xA182, 0xC8E7, 0xA183, 0xC8E8, 0xA184, 0xC8E9, + 0xA185, 0xC8EA, 0xA186, 0xC8EB, 0xA187, 0xC8EC, 0xA188, 0xC8ED, 0xA189, 0xC8EE, 0xA18A, 0xC8EF, 0xA18B, 0xC8F0, 0xA18C, 0xC8F1, + 0xA18D, 0xC8F2, 0xA18E, 0xC8F3, 0xA18F, 0xC8F4, 0xA190, 0xC8F6, 0xA191, 0xC8F7, 0xA192, 0xC8F8, 0xA193, 0xC8F9, 0xA194, 0xC8FA, + 0xA195, 0xC8FB, 0xA196, 0xC8FE, 0xA197, 0xC8FF, 0xA198, 0xC901, 0xA199, 0xC902, 0xA19A, 0xC903, 0xA19B, 0xC907, 0xA19C, 0xC908, + 0xA19D, 0xC909, 0xA19E, 0xC90A, 0xA19F, 0xC90B, 0xA1A0, 0xC90E, 0xA1A1, 0x3000, 0xA1A2, 0x3001, 0xA1A3, 0x3002, 0xA1A4, 0x00B7, + 0xA1A5, 0x2025, 0xA1A6, 0x2026, 0xA1A7, 0x00A8, 0xA1A8, 0x3003, 0xA1A9, 0x00AD, 0xA1AA, 0x2015, 0xA1AB, 0x2225, 0xA1AC, 0xFF3C, + 0xA1AD, 0x223C, 0xA1AE, 0x2018, 0xA1AF, 0x2019, 0xA1B0, 0x201C, 0xA1B1, 0x201D, 0xA1B2, 0x3014, 0xA1B3, 0x3015, 0xA1B4, 0x3008, + 0xA1B5, 0x3009, 0xA1B6, 0x300A, 0xA1B7, 0x300B, 0xA1B8, 0x300C, 0xA1B9, 0x300D, 0xA1BA, 0x300E, 0xA1BB, 0x300F, 0xA1BC, 0x3010, + 0xA1BD, 0x3011, 0xA1BE, 0x00B1, 0xA1BF, 0x00D7, 0xA1C0, 0x00F7, 0xA1C1, 0x2260, 0xA1C2, 0x2264, 0xA1C3, 0x2265, 0xA1C4, 0x221E, + 0xA1C5, 0x2234, 0xA1C6, 0x00B0, 0xA1C7, 0x2032, 0xA1C8, 0x2033, 0xA1C9, 0x2103, 0xA1CA, 0x212B, 0xA1CB, 0xFFE0, 0xA1CC, 0xFFE1, + 0xA1CD, 0xFFE5, 0xA1CE, 0x2642, 0xA1CF, 0x2640, 0xA1D0, 0x2220, 0xA1D1, 0x22A5, 0xA1D2, 0x2312, 0xA1D3, 0x2202, 0xA1D4, 0x2207, + 0xA1D5, 0x2261, 0xA1D6, 0x2252, 0xA1D7, 0x00A7, 0xA1D8, 0x203B, 0xA1D9, 0x2606, 0xA1DA, 0x2605, 0xA1DB, 0x25CB, 0xA1DC, 0x25CF, + 0xA1DD, 0x25CE, 0xA1DE, 0x25C7, 0xA1DF, 0x25C6, 0xA1E0, 0x25A1, 0xA1E1, 0x25A0, 0xA1E2, 0x25B3, 0xA1E3, 0x25B2, 0xA1E4, 0x25BD, + 0xA1E5, 0x25BC, 0xA1E6, 0x2192, 0xA1E7, 0x2190, 0xA1E8, 0x2191, 0xA1E9, 0x2193, 0xA1EA, 0x2194, 0xA1EB, 0x3013, 0xA1EC, 0x226A, + 0xA1ED, 0x226B, 0xA1EE, 0x221A, 0xA1EF, 0x223D, 0xA1F0, 0x221D, 0xA1F1, 0x2235, 0xA1F2, 0x222B, 0xA1F3, 0x222C, 0xA1F4, 0x2208, + 0xA1F5, 0x220B, 0xA1F6, 0x2286, 0xA1F7, 0x2287, 0xA1F8, 0x2282, 0xA1F9, 0x2283, 0xA1FA, 0x222A, 0xA1FB, 0x2229, 0xA1FC, 0x2227, + 0xA1FD, 0x2228, 0xA1FE, 0xFFE2, 0xA241, 0xC910, 0xA242, 0xC912, 0xA243, 0xC913, 0xA244, 0xC914, 0xA245, 0xC915, 0xA246, 0xC916, + 0xA247, 0xC917, 0xA248, 0xC919, 0xA249, 0xC91A, 0xA24A, 0xC91B, 0xA24B, 0xC91C, 0xA24C, 0xC91D, 0xA24D, 0xC91E, 0xA24E, 0xC91F, + 0xA24F, 0xC920, 0xA250, 0xC921, 0xA251, 0xC922, 0xA252, 0xC923, 0xA253, 0xC924, 0xA254, 0xC925, 0xA255, 0xC926, 0xA256, 0xC927, + 0xA257, 0xC928, 0xA258, 0xC929, 0xA259, 0xC92A, 0xA25A, 0xC92B, 0xA261, 0xC92D, 0xA262, 0xC92E, 0xA263, 0xC92F, 0xA264, 0xC930, + 0xA265, 0xC931, 0xA266, 0xC932, 0xA267, 0xC933, 0xA268, 0xC935, 0xA269, 0xC936, 0xA26A, 0xC937, 0xA26B, 0xC938, 0xA26C, 0xC939, + 0xA26D, 0xC93A, 0xA26E, 0xC93B, 0xA26F, 0xC93C, 0xA270, 0xC93D, 0xA271, 0xC93E, 0xA272, 0xC93F, 0xA273, 0xC940, 0xA274, 0xC941, + 0xA275, 0xC942, 0xA276, 0xC943, 0xA277, 0xC944, 0xA278, 0xC945, 0xA279, 0xC946, 0xA27A, 0xC947, 0xA281, 0xC948, 0xA282, 0xC949, + 0xA283, 0xC94A, 0xA284, 0xC94B, 0xA285, 0xC94C, 0xA286, 0xC94D, 0xA287, 0xC94E, 0xA288, 0xC94F, 0xA289, 0xC952, 0xA28A, 0xC953, + 0xA28B, 0xC955, 0xA28C, 0xC956, 0xA28D, 0xC957, 0xA28E, 0xC959, 0xA28F, 0xC95A, 0xA290, 0xC95B, 0xA291, 0xC95C, 0xA292, 0xC95D, + 0xA293, 0xC95E, 0xA294, 0xC95F, 0xA295, 0xC962, 0xA296, 0xC964, 0xA297, 0xC965, 0xA298, 0xC966, 0xA299, 0xC967, 0xA29A, 0xC968, + 0xA29B, 0xC969, 0xA29C, 0xC96A, 0xA29D, 0xC96B, 0xA29E, 0xC96D, 0xA29F, 0xC96E, 0xA2A0, 0xC96F, 0xA2A1, 0x21D2, 0xA2A2, 0x21D4, + 0xA2A3, 0x2200, 0xA2A4, 0x2203, 0xA2A5, 0x00B4, 0xA2A6, 0xFF5E, 0xA2A7, 0x02C7, 0xA2A8, 0x02D8, 0xA2A9, 0x02DD, 0xA2AA, 0x02DA, + 0xA2AB, 0x02D9, 0xA2AC, 0x00B8, 0xA2AD, 0x02DB, 0xA2AE, 0x00A1, 0xA2AF, 0x00BF, 0xA2B0, 0x02D0, 0xA2B1, 0x222E, 0xA2B2, 0x2211, + 0xA2B3, 0x220F, 0xA2B4, 0x00A4, 0xA2B5, 0x2109, 0xA2B6, 0x2030, 0xA2B7, 0x25C1, 0xA2B8, 0x25C0, 0xA2B9, 0x25B7, 0xA2BA, 0x25B6, + 0xA2BB, 0x2664, 0xA2BC, 0x2660, 0xA2BD, 0x2661, 0xA2BE, 0x2665, 0xA2BF, 0x2667, 0xA2C0, 0x2663, 0xA2C1, 0x2299, 0xA2C2, 0x25C8, + 0xA2C3, 0x25A3, 0xA2C4, 0x25D0, 0xA2C5, 0x25D1, 0xA2C6, 0x2592, 0xA2C7, 0x25A4, 0xA2C8, 0x25A5, 0xA2C9, 0x25A8, 0xA2CA, 0x25A7, + 0xA2CB, 0x25A6, 0xA2CC, 0x25A9, 0xA2CD, 0x2668, 0xA2CE, 0x260F, 0xA2CF, 0x260E, 0xA2D0, 0x261C, 0xA2D1, 0x261E, 0xA2D2, 0x00B6, + 0xA2D3, 0x2020, 0xA2D4, 0x2021, 0xA2D5, 0x2195, 0xA2D6, 0x2197, 0xA2D7, 0x2199, 0xA2D8, 0x2196, 0xA2D9, 0x2198, 0xA2DA, 0x266D, + 0xA2DB, 0x2669, 0xA2DC, 0x266A, 0xA2DD, 0x266C, 0xA2DE, 0x327F, 0xA2DF, 0x321C, 0xA2E0, 0x2116, 0xA2E1, 0x33C7, 0xA2E2, 0x2122, + 0xA2E3, 0x33C2, 0xA2E4, 0x33D8, 0xA2E5, 0x2121, 0xA2E6, 0x20AC, 0xA2E7, 0x00AE, 0xA341, 0xC971, 0xA342, 0xC972, 0xA343, 0xC973, + 0xA344, 0xC975, 0xA345, 0xC976, 0xA346, 0xC977, 0xA347, 0xC978, 0xA348, 0xC979, 0xA349, 0xC97A, 0xA34A, 0xC97B, 0xA34B, 0xC97D, + 0xA34C, 0xC97E, 0xA34D, 0xC97F, 0xA34E, 0xC980, 0xA34F, 0xC981, 0xA350, 0xC982, 0xA351, 0xC983, 0xA352, 0xC984, 0xA353, 0xC985, + 0xA354, 0xC986, 0xA355, 0xC987, 0xA356, 0xC98A, 0xA357, 0xC98B, 0xA358, 0xC98D, 0xA359, 0xC98E, 0xA35A, 0xC98F, 0xA361, 0xC991, + 0xA362, 0xC992, 0xA363, 0xC993, 0xA364, 0xC994, 0xA365, 0xC995, 0xA366, 0xC996, 0xA367, 0xC997, 0xA368, 0xC99A, 0xA369, 0xC99C, + 0xA36A, 0xC99E, 0xA36B, 0xC99F, 0xA36C, 0xC9A0, 0xA36D, 0xC9A1, 0xA36E, 0xC9A2, 0xA36F, 0xC9A3, 0xA370, 0xC9A4, 0xA371, 0xC9A5, + 0xA372, 0xC9A6, 0xA373, 0xC9A7, 0xA374, 0xC9A8, 0xA375, 0xC9A9, 0xA376, 0xC9AA, 0xA377, 0xC9AB, 0xA378, 0xC9AC, 0xA379, 0xC9AD, + 0xA37A, 0xC9AE, 0xA381, 0xC9AF, 0xA382, 0xC9B0, 0xA383, 0xC9B1, 0xA384, 0xC9B2, 0xA385, 0xC9B3, 0xA386, 0xC9B4, 0xA387, 0xC9B5, + 0xA388, 0xC9B6, 0xA389, 0xC9B7, 0xA38A, 0xC9B8, 0xA38B, 0xC9B9, 0xA38C, 0xC9BA, 0xA38D, 0xC9BB, 0xA38E, 0xC9BC, 0xA38F, 0xC9BD, + 0xA390, 0xC9BE, 0xA391, 0xC9BF, 0xA392, 0xC9C2, 0xA393, 0xC9C3, 0xA394, 0xC9C5, 0xA395, 0xC9C6, 0xA396, 0xC9C9, 0xA397, 0xC9CB, + 0xA398, 0xC9CC, 0xA399, 0xC9CD, 0xA39A, 0xC9CE, 0xA39B, 0xC9CF, 0xA39C, 0xC9D2, 0xA39D, 0xC9D4, 0xA39E, 0xC9D7, 0xA39F, 0xC9D8, + 0xA3A0, 0xC9DB, 0xA3A1, 0xFF01, 0xA3A2, 0xFF02, 0xA3A3, 0xFF03, 0xA3A4, 0xFF04, 0xA3A5, 0xFF05, 0xA3A6, 0xFF06, 0xA3A7, 0xFF07, + 0xA3A8, 0xFF08, 0xA3A9, 0xFF09, 0xA3AA, 0xFF0A, 0xA3AB, 0xFF0B, 0xA3AC, 0xFF0C, 0xA3AD, 0xFF0D, 0xA3AE, 0xFF0E, 0xA3AF, 0xFF0F, + 0xA3B0, 0xFF10, 0xA3B1, 0xFF11, 0xA3B2, 0xFF12, 0xA3B3, 0xFF13, 0xA3B4, 0xFF14, 0xA3B5, 0xFF15, 0xA3B6, 0xFF16, 0xA3B7, 0xFF17, + 0xA3B8, 0xFF18, 0xA3B9, 0xFF19, 0xA3BA, 0xFF1A, 0xA3BB, 0xFF1B, 0xA3BC, 0xFF1C, 0xA3BD, 0xFF1D, 0xA3BE, 0xFF1E, 0xA3BF, 0xFF1F, + 0xA3C0, 0xFF20, 0xA3C1, 0xFF21, 0xA3C2, 0xFF22, 0xA3C3, 0xFF23, 0xA3C4, 0xFF24, 0xA3C5, 0xFF25, 0xA3C6, 0xFF26, 0xA3C7, 0xFF27, + 0xA3C8, 0xFF28, 0xA3C9, 0xFF29, 0xA3CA, 0xFF2A, 0xA3CB, 0xFF2B, 0xA3CC, 0xFF2C, 0xA3CD, 0xFF2D, 0xA3CE, 0xFF2E, 0xA3CF, 0xFF2F, + 0xA3D0, 0xFF30, 0xA3D1, 0xFF31, 0xA3D2, 0xFF32, 0xA3D3, 0xFF33, 0xA3D4, 0xFF34, 0xA3D5, 0xFF35, 0xA3D6, 0xFF36, 0xA3D7, 0xFF37, + 0xA3D8, 0xFF38, 0xA3D9, 0xFF39, 0xA3DA, 0xFF3A, 0xA3DB, 0xFF3B, 0xA3DC, 0xFFE6, 0xA3DD, 0xFF3D, 0xA3DE, 0xFF3E, 0xA3DF, 0xFF3F, + 0xA3E0, 0xFF40, 0xA3E1, 0xFF41, 0xA3E2, 0xFF42, 0xA3E3, 0xFF43, 0xA3E4, 0xFF44, 0xA3E5, 0xFF45, 0xA3E6, 0xFF46, 0xA3E7, 0xFF47, + 0xA3E8, 0xFF48, 0xA3E9, 0xFF49, 0xA3EA, 0xFF4A, 0xA3EB, 0xFF4B, 0xA3EC, 0xFF4C, 0xA3ED, 0xFF4D, 0xA3EE, 0xFF4E, 0xA3EF, 0xFF4F, + 0xA3F0, 0xFF50, 0xA3F1, 0xFF51, 0xA3F2, 0xFF52, 0xA3F3, 0xFF53, 0xA3F4, 0xFF54, 0xA3F5, 0xFF55, 0xA3F6, 0xFF56, 0xA3F7, 0xFF57, + 0xA3F8, 0xFF58, 0xA3F9, 0xFF59, 0xA3FA, 0xFF5A, 0xA3FB, 0xFF5B, 0xA3FC, 0xFF5C, 0xA3FD, 0xFF5D, 0xA3FE, 0xFFE3, 0xA441, 0xC9DE, + 0xA442, 0xC9DF, 0xA443, 0xC9E1, 0xA444, 0xC9E3, 0xA445, 0xC9E5, 0xA446, 0xC9E6, 0xA447, 0xC9E8, 0xA448, 0xC9E9, 0xA449, 0xC9EA, + 0xA44A, 0xC9EB, 0xA44B, 0xC9EE, 0xA44C, 0xC9F2, 0xA44D, 0xC9F3, 0xA44E, 0xC9F4, 0xA44F, 0xC9F5, 0xA450, 0xC9F6, 0xA451, 0xC9F7, + 0xA452, 0xC9FA, 0xA453, 0xC9FB, 0xA454, 0xC9FD, 0xA455, 0xC9FE, 0xA456, 0xC9FF, 0xA457, 0xCA01, 0xA458, 0xCA02, 0xA459, 0xCA03, + 0xA45A, 0xCA04, 0xA461, 0xCA05, 0xA462, 0xCA06, 0xA463, 0xCA07, 0xA464, 0xCA0A, 0xA465, 0xCA0E, 0xA466, 0xCA0F, 0xA467, 0xCA10, + 0xA468, 0xCA11, 0xA469, 0xCA12, 0xA46A, 0xCA13, 0xA46B, 0xCA15, 0xA46C, 0xCA16, 0xA46D, 0xCA17, 0xA46E, 0xCA19, 0xA46F, 0xCA1A, + 0xA470, 0xCA1B, 0xA471, 0xCA1C, 0xA472, 0xCA1D, 0xA473, 0xCA1E, 0xA474, 0xCA1F, 0xA475, 0xCA20, 0xA476, 0xCA21, 0xA477, 0xCA22, + 0xA478, 0xCA23, 0xA479, 0xCA24, 0xA47A, 0xCA25, 0xA481, 0xCA26, 0xA482, 0xCA27, 0xA483, 0xCA28, 0xA484, 0xCA2A, 0xA485, 0xCA2B, + 0xA486, 0xCA2C, 0xA487, 0xCA2D, 0xA488, 0xCA2E, 0xA489, 0xCA2F, 0xA48A, 0xCA30, 0xA48B, 0xCA31, 0xA48C, 0xCA32, 0xA48D, 0xCA33, + 0xA48E, 0xCA34, 0xA48F, 0xCA35, 0xA490, 0xCA36, 0xA491, 0xCA37, 0xA492, 0xCA38, 0xA493, 0xCA39, 0xA494, 0xCA3A, 0xA495, 0xCA3B, + 0xA496, 0xCA3C, 0xA497, 0xCA3D, 0xA498, 0xCA3E, 0xA499, 0xCA3F, 0xA49A, 0xCA40, 0xA49B, 0xCA41, 0xA49C, 0xCA42, 0xA49D, 0xCA43, + 0xA49E, 0xCA44, 0xA49F, 0xCA45, 0xA4A0, 0xCA46, 0xA4A1, 0x3131, 0xA4A2, 0x3132, 0xA4A3, 0x3133, 0xA4A4, 0x3134, 0xA4A5, 0x3135, + 0xA4A6, 0x3136, 0xA4A7, 0x3137, 0xA4A8, 0x3138, 0xA4A9, 0x3139, 0xA4AA, 0x313A, 0xA4AB, 0x313B, 0xA4AC, 0x313C, 0xA4AD, 0x313D, + 0xA4AE, 0x313E, 0xA4AF, 0x313F, 0xA4B0, 0x3140, 0xA4B1, 0x3141, 0xA4B2, 0x3142, 0xA4B3, 0x3143, 0xA4B4, 0x3144, 0xA4B5, 0x3145, + 0xA4B6, 0x3146, 0xA4B7, 0x3147, 0xA4B8, 0x3148, 0xA4B9, 0x3149, 0xA4BA, 0x314A, 0xA4BB, 0x314B, 0xA4BC, 0x314C, 0xA4BD, 0x314D, + 0xA4BE, 0x314E, 0xA4BF, 0x314F, 0xA4C0, 0x3150, 0xA4C1, 0x3151, 0xA4C2, 0x3152, 0xA4C3, 0x3153, 0xA4C4, 0x3154, 0xA4C5, 0x3155, + 0xA4C6, 0x3156, 0xA4C7, 0x3157, 0xA4C8, 0x3158, 0xA4C9, 0x3159, 0xA4CA, 0x315A, 0xA4CB, 0x315B, 0xA4CC, 0x315C, 0xA4CD, 0x315D, + 0xA4CE, 0x315E, 0xA4CF, 0x315F, 0xA4D0, 0x3160, 0xA4D1, 0x3161, 0xA4D2, 0x3162, 0xA4D3, 0x3163, 0xA4D4, 0x3164, 0xA4D5, 0x3165, + 0xA4D6, 0x3166, 0xA4D7, 0x3167, 0xA4D8, 0x3168, 0xA4D9, 0x3169, 0xA4DA, 0x316A, 0xA4DB, 0x316B, 0xA4DC, 0x316C, 0xA4DD, 0x316D, + 0xA4DE, 0x316E, 0xA4DF, 0x316F, 0xA4E0, 0x3170, 0xA4E1, 0x3171, 0xA4E2, 0x3172, 0xA4E3, 0x3173, 0xA4E4, 0x3174, 0xA4E5, 0x3175, + 0xA4E6, 0x3176, 0xA4E7, 0x3177, 0xA4E8, 0x3178, 0xA4E9, 0x3179, 0xA4EA, 0x317A, 0xA4EB, 0x317B, 0xA4EC, 0x317C, 0xA4ED, 0x317D, + 0xA4EE, 0x317E, 0xA4EF, 0x317F, 0xA4F0, 0x3180, 0xA4F1, 0x3181, 0xA4F2, 0x3182, 0xA4F3, 0x3183, 0xA4F4, 0x3184, 0xA4F5, 0x3185, + 0xA4F6, 0x3186, 0xA4F7, 0x3187, 0xA4F8, 0x3188, 0xA4F9, 0x3189, 0xA4FA, 0x318A, 0xA4FB, 0x318B, 0xA4FC, 0x318C, 0xA4FD, 0x318D, + 0xA4FE, 0x318E, 0xA541, 0xCA47, 0xA542, 0xCA48, 0xA543, 0xCA49, 0xA544, 0xCA4A, 0xA545, 0xCA4B, 0xA546, 0xCA4E, 0xA547, 0xCA4F, + 0xA548, 0xCA51, 0xA549, 0xCA52, 0xA54A, 0xCA53, 0xA54B, 0xCA55, 0xA54C, 0xCA56, 0xA54D, 0xCA57, 0xA54E, 0xCA58, 0xA54F, 0xCA59, + 0xA550, 0xCA5A, 0xA551, 0xCA5B, 0xA552, 0xCA5E, 0xA553, 0xCA62, 0xA554, 0xCA63, 0xA555, 0xCA64, 0xA556, 0xCA65, 0xA557, 0xCA66, + 0xA558, 0xCA67, 0xA559, 0xCA69, 0xA55A, 0xCA6A, 0xA561, 0xCA6B, 0xA562, 0xCA6C, 0xA563, 0xCA6D, 0xA564, 0xCA6E, 0xA565, 0xCA6F, + 0xA566, 0xCA70, 0xA567, 0xCA71, 0xA568, 0xCA72, 0xA569, 0xCA73, 0xA56A, 0xCA74, 0xA56B, 0xCA75, 0xA56C, 0xCA76, 0xA56D, 0xCA77, + 0xA56E, 0xCA78, 0xA56F, 0xCA79, 0xA570, 0xCA7A, 0xA571, 0xCA7B, 0xA572, 0xCA7C, 0xA573, 0xCA7E, 0xA574, 0xCA7F, 0xA575, 0xCA80, + 0xA576, 0xCA81, 0xA577, 0xCA82, 0xA578, 0xCA83, 0xA579, 0xCA85, 0xA57A, 0xCA86, 0xA581, 0xCA87, 0xA582, 0xCA88, 0xA583, 0xCA89, + 0xA584, 0xCA8A, 0xA585, 0xCA8B, 0xA586, 0xCA8C, 0xA587, 0xCA8D, 0xA588, 0xCA8E, 0xA589, 0xCA8F, 0xA58A, 0xCA90, 0xA58B, 0xCA91, + 0xA58C, 0xCA92, 0xA58D, 0xCA93, 0xA58E, 0xCA94, 0xA58F, 0xCA95, 0xA590, 0xCA96, 0xA591, 0xCA97, 0xA592, 0xCA99, 0xA593, 0xCA9A, + 0xA594, 0xCA9B, 0xA595, 0xCA9C, 0xA596, 0xCA9D, 0xA597, 0xCA9E, 0xA598, 0xCA9F, 0xA599, 0xCAA0, 0xA59A, 0xCAA1, 0xA59B, 0xCAA2, + 0xA59C, 0xCAA3, 0xA59D, 0xCAA4, 0xA59E, 0xCAA5, 0xA59F, 0xCAA6, 0xA5A0, 0xCAA7, 0xA5A1, 0x2170, 0xA5A2, 0x2171, 0xA5A3, 0x2172, + 0xA5A4, 0x2173, 0xA5A5, 0x2174, 0xA5A6, 0x2175, 0xA5A7, 0x2176, 0xA5A8, 0x2177, 0xA5A9, 0x2178, 0xA5AA, 0x2179, 0xA5B0, 0x2160, + 0xA5B1, 0x2161, 0xA5B2, 0x2162, 0xA5B3, 0x2163, 0xA5B4, 0x2164, 0xA5B5, 0x2165, 0xA5B6, 0x2166, 0xA5B7, 0x2167, 0xA5B8, 0x2168, + 0xA5B9, 0x2169, 0xA5C1, 0x0391, 0xA5C2, 0x0392, 0xA5C3, 0x0393, 0xA5C4, 0x0394, 0xA5C5, 0x0395, 0xA5C6, 0x0396, 0xA5C7, 0x0397, + 0xA5C8, 0x0398, 0xA5C9, 0x0399, 0xA5CA, 0x039A, 0xA5CB, 0x039B, 0xA5CC, 0x039C, 0xA5CD, 0x039D, 0xA5CE, 0x039E, 0xA5CF, 0x039F, + 0xA5D0, 0x03A0, 0xA5D1, 0x03A1, 0xA5D2, 0x03A3, 0xA5D3, 0x03A4, 0xA5D4, 0x03A5, 0xA5D5, 0x03A6, 0xA5D6, 0x03A7, 0xA5D7, 0x03A8, + 0xA5D8, 0x03A9, 0xA5E1, 0x03B1, 0xA5E2, 0x03B2, 0xA5E3, 0x03B3, 0xA5E4, 0x03B4, 0xA5E5, 0x03B5, 0xA5E6, 0x03B6, 0xA5E7, 0x03B7, + 0xA5E8, 0x03B8, 0xA5E9, 0x03B9, 0xA5EA, 0x03BA, 0xA5EB, 0x03BB, 0xA5EC, 0x03BC, 0xA5ED, 0x03BD, 0xA5EE, 0x03BE, 0xA5EF, 0x03BF, + 0xA5F0, 0x03C0, 0xA5F1, 0x03C1, 0xA5F2, 0x03C3, 0xA5F3, 0x03C4, 0xA5F4, 0x03C5, 0xA5F5, 0x03C6, 0xA5F6, 0x03C7, 0xA5F7, 0x03C8, + 0xA5F8, 0x03C9, 0xA641, 0xCAA8, 0xA642, 0xCAA9, 0xA643, 0xCAAA, 0xA644, 0xCAAB, 0xA645, 0xCAAC, 0xA646, 0xCAAD, 0xA647, 0xCAAE, + 0xA648, 0xCAAF, 0xA649, 0xCAB0, 0xA64A, 0xCAB1, 0xA64B, 0xCAB2, 0xA64C, 0xCAB3, 0xA64D, 0xCAB4, 0xA64E, 0xCAB5, 0xA64F, 0xCAB6, + 0xA650, 0xCAB7, 0xA651, 0xCAB8, 0xA652, 0xCAB9, 0xA653, 0xCABA, 0xA654, 0xCABB, 0xA655, 0xCABE, 0xA656, 0xCABF, 0xA657, 0xCAC1, + 0xA658, 0xCAC2, 0xA659, 0xCAC3, 0xA65A, 0xCAC5, 0xA661, 0xCAC6, 0xA662, 0xCAC7, 0xA663, 0xCAC8, 0xA664, 0xCAC9, 0xA665, 0xCACA, + 0xA666, 0xCACB, 0xA667, 0xCACE, 0xA668, 0xCAD0, 0xA669, 0xCAD2, 0xA66A, 0xCAD4, 0xA66B, 0xCAD5, 0xA66C, 0xCAD6, 0xA66D, 0xCAD7, + 0xA66E, 0xCADA, 0xA66F, 0xCADB, 0xA670, 0xCADC, 0xA671, 0xCADD, 0xA672, 0xCADE, 0xA673, 0xCADF, 0xA674, 0xCAE1, 0xA675, 0xCAE2, + 0xA676, 0xCAE3, 0xA677, 0xCAE4, 0xA678, 0xCAE5, 0xA679, 0xCAE6, 0xA67A, 0xCAE7, 0xA681, 0xCAE8, 0xA682, 0xCAE9, 0xA683, 0xCAEA, + 0xA684, 0xCAEB, 0xA685, 0xCAED, 0xA686, 0xCAEE, 0xA687, 0xCAEF, 0xA688, 0xCAF0, 0xA689, 0xCAF1, 0xA68A, 0xCAF2, 0xA68B, 0xCAF3, + 0xA68C, 0xCAF5, 0xA68D, 0xCAF6, 0xA68E, 0xCAF7, 0xA68F, 0xCAF8, 0xA690, 0xCAF9, 0xA691, 0xCAFA, 0xA692, 0xCAFB, 0xA693, 0xCAFC, + 0xA694, 0xCAFD, 0xA695, 0xCAFE, 0xA696, 0xCAFF, 0xA697, 0xCB00, 0xA698, 0xCB01, 0xA699, 0xCB02, 0xA69A, 0xCB03, 0xA69B, 0xCB04, + 0xA69C, 0xCB05, 0xA69D, 0xCB06, 0xA69E, 0xCB07, 0xA69F, 0xCB09, 0xA6A0, 0xCB0A, 0xA6A1, 0x2500, 0xA6A2, 0x2502, 0xA6A3, 0x250C, + 0xA6A4, 0x2510, 0xA6A5, 0x2518, 0xA6A6, 0x2514, 0xA6A7, 0x251C, 0xA6A8, 0x252C, 0xA6A9, 0x2524, 0xA6AA, 0x2534, 0xA6AB, 0x253C, + 0xA6AC, 0x2501, 0xA6AD, 0x2503, 0xA6AE, 0x250F, 0xA6AF, 0x2513, 0xA6B0, 0x251B, 0xA6B1, 0x2517, 0xA6B2, 0x2523, 0xA6B3, 0x2533, + 0xA6B4, 0x252B, 0xA6B5, 0x253B, 0xA6B6, 0x254B, 0xA6B7, 0x2520, 0xA6B8, 0x252F, 0xA6B9, 0x2528, 0xA6BA, 0x2537, 0xA6BB, 0x253F, + 0xA6BC, 0x251D, 0xA6BD, 0x2530, 0xA6BE, 0x2525, 0xA6BF, 0x2538, 0xA6C0, 0x2542, 0xA6C1, 0x2512, 0xA6C2, 0x2511, 0xA6C3, 0x251A, + 0xA6C4, 0x2519, 0xA6C5, 0x2516, 0xA6C6, 0x2515, 0xA6C7, 0x250E, 0xA6C8, 0x250D, 0xA6C9, 0x251E, 0xA6CA, 0x251F, 0xA6CB, 0x2521, + 0xA6CC, 0x2522, 0xA6CD, 0x2526, 0xA6CE, 0x2527, 0xA6CF, 0x2529, 0xA6D0, 0x252A, 0xA6D1, 0x252D, 0xA6D2, 0x252E, 0xA6D3, 0x2531, + 0xA6D4, 0x2532, 0xA6D5, 0x2535, 0xA6D6, 0x2536, 0xA6D7, 0x2539, 0xA6D8, 0x253A, 0xA6D9, 0x253D, 0xA6DA, 0x253E, 0xA6DB, 0x2540, + 0xA6DC, 0x2541, 0xA6DD, 0x2543, 0xA6DE, 0x2544, 0xA6DF, 0x2545, 0xA6E0, 0x2546, 0xA6E1, 0x2547, 0xA6E2, 0x2548, 0xA6E3, 0x2549, + 0xA6E4, 0x254A, 0xA741, 0xCB0B, 0xA742, 0xCB0C, 0xA743, 0xCB0D, 0xA744, 0xCB0E, 0xA745, 0xCB0F, 0xA746, 0xCB11, 0xA747, 0xCB12, + 0xA748, 0xCB13, 0xA749, 0xCB15, 0xA74A, 0xCB16, 0xA74B, 0xCB17, 0xA74C, 0xCB19, 0xA74D, 0xCB1A, 0xA74E, 0xCB1B, 0xA74F, 0xCB1C, + 0xA750, 0xCB1D, 0xA751, 0xCB1E, 0xA752, 0xCB1F, 0xA753, 0xCB22, 0xA754, 0xCB23, 0xA755, 0xCB24, 0xA756, 0xCB25, 0xA757, 0xCB26, + 0xA758, 0xCB27, 0xA759, 0xCB28, 0xA75A, 0xCB29, 0xA761, 0xCB2A, 0xA762, 0xCB2B, 0xA763, 0xCB2C, 0xA764, 0xCB2D, 0xA765, 0xCB2E, + 0xA766, 0xCB2F, 0xA767, 0xCB30, 0xA768, 0xCB31, 0xA769, 0xCB32, 0xA76A, 0xCB33, 0xA76B, 0xCB34, 0xA76C, 0xCB35, 0xA76D, 0xCB36, + 0xA76E, 0xCB37, 0xA76F, 0xCB38, 0xA770, 0xCB39, 0xA771, 0xCB3A, 0xA772, 0xCB3B, 0xA773, 0xCB3C, 0xA774, 0xCB3D, 0xA775, 0xCB3E, + 0xA776, 0xCB3F, 0xA777, 0xCB40, 0xA778, 0xCB42, 0xA779, 0xCB43, 0xA77A, 0xCB44, 0xA781, 0xCB45, 0xA782, 0xCB46, 0xA783, 0xCB47, + 0xA784, 0xCB4A, 0xA785, 0xCB4B, 0xA786, 0xCB4D, 0xA787, 0xCB4E, 0xA788, 0xCB4F, 0xA789, 0xCB51, 0xA78A, 0xCB52, 0xA78B, 0xCB53, + 0xA78C, 0xCB54, 0xA78D, 0xCB55, 0xA78E, 0xCB56, 0xA78F, 0xCB57, 0xA790, 0xCB5A, 0xA791, 0xCB5B, 0xA792, 0xCB5C, 0xA793, 0xCB5E, + 0xA794, 0xCB5F, 0xA795, 0xCB60, 0xA796, 0xCB61, 0xA797, 0xCB62, 0xA798, 0xCB63, 0xA799, 0xCB65, 0xA79A, 0xCB66, 0xA79B, 0xCB67, + 0xA79C, 0xCB68, 0xA79D, 0xCB69, 0xA79E, 0xCB6A, 0xA79F, 0xCB6B, 0xA7A0, 0xCB6C, 0xA7A1, 0x3395, 0xA7A2, 0x3396, 0xA7A3, 0x3397, + 0xA7A4, 0x2113, 0xA7A5, 0x3398, 0xA7A6, 0x33C4, 0xA7A7, 0x33A3, 0xA7A8, 0x33A4, 0xA7A9, 0x33A5, 0xA7AA, 0x33A6, 0xA7AB, 0x3399, + 0xA7AC, 0x339A, 0xA7AD, 0x339B, 0xA7AE, 0x339C, 0xA7AF, 0x339D, 0xA7B0, 0x339E, 0xA7B1, 0x339F, 0xA7B2, 0x33A0, 0xA7B3, 0x33A1, + 0xA7B4, 0x33A2, 0xA7B5, 0x33CA, 0xA7B6, 0x338D, 0xA7B7, 0x338E, 0xA7B8, 0x338F, 0xA7B9, 0x33CF, 0xA7BA, 0x3388, 0xA7BB, 0x3389, + 0xA7BC, 0x33C8, 0xA7BD, 0x33A7, 0xA7BE, 0x33A8, 0xA7BF, 0x33B0, 0xA7C0, 0x33B1, 0xA7C1, 0x33B2, 0xA7C2, 0x33B3, 0xA7C3, 0x33B4, + 0xA7C4, 0x33B5, 0xA7C5, 0x33B6, 0xA7C6, 0x33B7, 0xA7C7, 0x33B8, 0xA7C8, 0x33B9, 0xA7C9, 0x3380, 0xA7CA, 0x3381, 0xA7CB, 0x3382, + 0xA7CC, 0x3383, 0xA7CD, 0x3384, 0xA7CE, 0x33BA, 0xA7CF, 0x33BB, 0xA7D0, 0x33BC, 0xA7D1, 0x33BD, 0xA7D2, 0x33BE, 0xA7D3, 0x33BF, + 0xA7D4, 0x3390, 0xA7D5, 0x3391, 0xA7D6, 0x3392, 0xA7D7, 0x3393, 0xA7D8, 0x3394, 0xA7D9, 0x2126, 0xA7DA, 0x33C0, 0xA7DB, 0x33C1, + 0xA7DC, 0x338A, 0xA7DD, 0x338B, 0xA7DE, 0x338C, 0xA7DF, 0x33D6, 0xA7E0, 0x33C5, 0xA7E1, 0x33AD, 0xA7E2, 0x33AE, 0xA7E3, 0x33AF, + 0xA7E4, 0x33DB, 0xA7E5, 0x33A9, 0xA7E6, 0x33AA, 0xA7E7, 0x33AB, 0xA7E8, 0x33AC, 0xA7E9, 0x33DD, 0xA7EA, 0x33D0, 0xA7EB, 0x33D3, + 0xA7EC, 0x33C3, 0xA7ED, 0x33C9, 0xA7EE, 0x33DC, 0xA7EF, 0x33C6, 0xA841, 0xCB6D, 0xA842, 0xCB6E, 0xA843, 0xCB6F, 0xA844, 0xCB70, + 0xA845, 0xCB71, 0xA846, 0xCB72, 0xA847, 0xCB73, 0xA848, 0xCB74, 0xA849, 0xCB75, 0xA84A, 0xCB76, 0xA84B, 0xCB77, 0xA84C, 0xCB7A, + 0xA84D, 0xCB7B, 0xA84E, 0xCB7C, 0xA84F, 0xCB7D, 0xA850, 0xCB7E, 0xA851, 0xCB7F, 0xA852, 0xCB80, 0xA853, 0xCB81, 0xA854, 0xCB82, + 0xA855, 0xCB83, 0xA856, 0xCB84, 0xA857, 0xCB85, 0xA858, 0xCB86, 0xA859, 0xCB87, 0xA85A, 0xCB88, 0xA861, 0xCB89, 0xA862, 0xCB8A, + 0xA863, 0xCB8B, 0xA864, 0xCB8C, 0xA865, 0xCB8D, 0xA866, 0xCB8E, 0xA867, 0xCB8F, 0xA868, 0xCB90, 0xA869, 0xCB91, 0xA86A, 0xCB92, + 0xA86B, 0xCB93, 0xA86C, 0xCB94, 0xA86D, 0xCB95, 0xA86E, 0xCB96, 0xA86F, 0xCB97, 0xA870, 0xCB98, 0xA871, 0xCB99, 0xA872, 0xCB9A, + 0xA873, 0xCB9B, 0xA874, 0xCB9D, 0xA875, 0xCB9E, 0xA876, 0xCB9F, 0xA877, 0xCBA0, 0xA878, 0xCBA1, 0xA879, 0xCBA2, 0xA87A, 0xCBA3, + 0xA881, 0xCBA4, 0xA882, 0xCBA5, 0xA883, 0xCBA6, 0xA884, 0xCBA7, 0xA885, 0xCBA8, 0xA886, 0xCBA9, 0xA887, 0xCBAA, 0xA888, 0xCBAB, + 0xA889, 0xCBAC, 0xA88A, 0xCBAD, 0xA88B, 0xCBAE, 0xA88C, 0xCBAF, 0xA88D, 0xCBB0, 0xA88E, 0xCBB1, 0xA88F, 0xCBB2, 0xA890, 0xCBB3, + 0xA891, 0xCBB4, 0xA892, 0xCBB5, 0xA893, 0xCBB6, 0xA894, 0xCBB7, 0xA895, 0xCBB9, 0xA896, 0xCBBA, 0xA897, 0xCBBB, 0xA898, 0xCBBC, + 0xA899, 0xCBBD, 0xA89A, 0xCBBE, 0xA89B, 0xCBBF, 0xA89C, 0xCBC0, 0xA89D, 0xCBC1, 0xA89E, 0xCBC2, 0xA89F, 0xCBC3, 0xA8A0, 0xCBC4, + 0xA8A1, 0x00C6, 0xA8A2, 0x00D0, 0xA8A3, 0x00AA, 0xA8A4, 0x0126, 0xA8A6, 0x0132, 0xA8A8, 0x013F, 0xA8A9, 0x0141, 0xA8AA, 0x00D8, + 0xA8AB, 0x0152, 0xA8AC, 0x00BA, 0xA8AD, 0x00DE, 0xA8AE, 0x0166, 0xA8AF, 0x014A, 0xA8B1, 0x3260, 0xA8B2, 0x3261, 0xA8B3, 0x3262, + 0xA8B4, 0x3263, 0xA8B5, 0x3264, 0xA8B6, 0x3265, 0xA8B7, 0x3266, 0xA8B8, 0x3267, 0xA8B9, 0x3268, 0xA8BA, 0x3269, 0xA8BB, 0x326A, + 0xA8BC, 0x326B, 0xA8BD, 0x326C, 0xA8BE, 0x326D, 0xA8BF, 0x326E, 0xA8C0, 0x326F, 0xA8C1, 0x3270, 0xA8C2, 0x3271, 0xA8C3, 0x3272, + 0xA8C4, 0x3273, 0xA8C5, 0x3274, 0xA8C6, 0x3275, 0xA8C7, 0x3276, 0xA8C8, 0x3277, 0xA8C9, 0x3278, 0xA8CA, 0x3279, 0xA8CB, 0x327A, + 0xA8CC, 0x327B, 0xA8CD, 0x24D0, 0xA8CE, 0x24D1, 0xA8CF, 0x24D2, 0xA8D0, 0x24D3, 0xA8D1, 0x24D4, 0xA8D2, 0x24D5, 0xA8D3, 0x24D6, + 0xA8D4, 0x24D7, 0xA8D5, 0x24D8, 0xA8D6, 0x24D9, 0xA8D7, 0x24DA, 0xA8D8, 0x24DB, 0xA8D9, 0x24DC, 0xA8DA, 0x24DD, 0xA8DB, 0x24DE, + 0xA8DC, 0x24DF, 0xA8DD, 0x24E0, 0xA8DE, 0x24E1, 0xA8DF, 0x24E2, 0xA8E0, 0x24E3, 0xA8E1, 0x24E4, 0xA8E2, 0x24E5, 0xA8E3, 0x24E6, + 0xA8E4, 0x24E7, 0xA8E5, 0x24E8, 0xA8E6, 0x24E9, 0xA8E7, 0x2460, 0xA8E8, 0x2461, 0xA8E9, 0x2462, 0xA8EA, 0x2463, 0xA8EB, 0x2464, + 0xA8EC, 0x2465, 0xA8ED, 0x2466, 0xA8EE, 0x2467, 0xA8EF, 0x2468, 0xA8F0, 0x2469, 0xA8F1, 0x246A, 0xA8F2, 0x246B, 0xA8F3, 0x246C, + 0xA8F4, 0x246D, 0xA8F5, 0x246E, 0xA8F6, 0x00BD, 0xA8F7, 0x2153, 0xA8F8, 0x2154, 0xA8F9, 0x00BC, 0xA8FA, 0x00BE, 0xA8FB, 0x215B, + 0xA8FC, 0x215C, 0xA8FD, 0x215D, 0xA8FE, 0x215E, 0xA941, 0xCBC5, 0xA942, 0xCBC6, 0xA943, 0xCBC7, 0xA944, 0xCBC8, 0xA945, 0xCBC9, + 0xA946, 0xCBCA, 0xA947, 0xCBCB, 0xA948, 0xCBCC, 0xA949, 0xCBCD, 0xA94A, 0xCBCE, 0xA94B, 0xCBCF, 0xA94C, 0xCBD0, 0xA94D, 0xCBD1, + 0xA94E, 0xCBD2, 0xA94F, 0xCBD3, 0xA950, 0xCBD5, 0xA951, 0xCBD6, 0xA952, 0xCBD7, 0xA953, 0xCBD8, 0xA954, 0xCBD9, 0xA955, 0xCBDA, + 0xA956, 0xCBDB, 0xA957, 0xCBDC, 0xA958, 0xCBDD, 0xA959, 0xCBDE, 0xA95A, 0xCBDF, 0xA961, 0xCBE0, 0xA962, 0xCBE1, 0xA963, 0xCBE2, + 0xA964, 0xCBE3, 0xA965, 0xCBE5, 0xA966, 0xCBE6, 0xA967, 0xCBE8, 0xA968, 0xCBEA, 0xA969, 0xCBEB, 0xA96A, 0xCBEC, 0xA96B, 0xCBED, + 0xA96C, 0xCBEE, 0xA96D, 0xCBEF, 0xA96E, 0xCBF0, 0xA96F, 0xCBF1, 0xA970, 0xCBF2, 0xA971, 0xCBF3, 0xA972, 0xCBF4, 0xA973, 0xCBF5, + 0xA974, 0xCBF6, 0xA975, 0xCBF7, 0xA976, 0xCBF8, 0xA977, 0xCBF9, 0xA978, 0xCBFA, 0xA979, 0xCBFB, 0xA97A, 0xCBFC, 0xA981, 0xCBFD, + 0xA982, 0xCBFE, 0xA983, 0xCBFF, 0xA984, 0xCC00, 0xA985, 0xCC01, 0xA986, 0xCC02, 0xA987, 0xCC03, 0xA988, 0xCC04, 0xA989, 0xCC05, + 0xA98A, 0xCC06, 0xA98B, 0xCC07, 0xA98C, 0xCC08, 0xA98D, 0xCC09, 0xA98E, 0xCC0A, 0xA98F, 0xCC0B, 0xA990, 0xCC0E, 0xA991, 0xCC0F, + 0xA992, 0xCC11, 0xA993, 0xCC12, 0xA994, 0xCC13, 0xA995, 0xCC15, 0xA996, 0xCC16, 0xA997, 0xCC17, 0xA998, 0xCC18, 0xA999, 0xCC19, + 0xA99A, 0xCC1A, 0xA99B, 0xCC1B, 0xA99C, 0xCC1E, 0xA99D, 0xCC1F, 0xA99E, 0xCC20, 0xA99F, 0xCC23, 0xA9A0, 0xCC24, 0xA9A1, 0x00E6, + 0xA9A2, 0x0111, 0xA9A3, 0x00F0, 0xA9A4, 0x0127, 0xA9A5, 0x0131, 0xA9A6, 0x0133, 0xA9A7, 0x0138, 0xA9A8, 0x0140, 0xA9A9, 0x0142, + 0xA9AA, 0x00F8, 0xA9AB, 0x0153, 0xA9AC, 0x00DF, 0xA9AD, 0x00FE, 0xA9AE, 0x0167, 0xA9AF, 0x014B, 0xA9B0, 0x0149, 0xA9B1, 0x3200, + 0xA9B2, 0x3201, 0xA9B3, 0x3202, 0xA9B4, 0x3203, 0xA9B5, 0x3204, 0xA9B6, 0x3205, 0xA9B7, 0x3206, 0xA9B8, 0x3207, 0xA9B9, 0x3208, + 0xA9BA, 0x3209, 0xA9BB, 0x320A, 0xA9BC, 0x320B, 0xA9BD, 0x320C, 0xA9BE, 0x320D, 0xA9BF, 0x320E, 0xA9C0, 0x320F, 0xA9C1, 0x3210, + 0xA9C2, 0x3211, 0xA9C3, 0x3212, 0xA9C4, 0x3213, 0xA9C5, 0x3214, 0xA9C6, 0x3215, 0xA9C7, 0x3216, 0xA9C8, 0x3217, 0xA9C9, 0x3218, + 0xA9CA, 0x3219, 0xA9CB, 0x321A, 0xA9CC, 0x321B, 0xA9CD, 0x249C, 0xA9CE, 0x249D, 0xA9CF, 0x249E, 0xA9D0, 0x249F, 0xA9D1, 0x24A0, + 0xA9D2, 0x24A1, 0xA9D3, 0x24A2, 0xA9D4, 0x24A3, 0xA9D5, 0x24A4, 0xA9D6, 0x24A5, 0xA9D7, 0x24A6, 0xA9D8, 0x24A7, 0xA9D9, 0x24A8, + 0xA9DA, 0x24A9, 0xA9DB, 0x24AA, 0xA9DC, 0x24AB, 0xA9DD, 0x24AC, 0xA9DE, 0x24AD, 0xA9DF, 0x24AE, 0xA9E0, 0x24AF, 0xA9E1, 0x24B0, + 0xA9E2, 0x24B1, 0xA9E3, 0x24B2, 0xA9E4, 0x24B3, 0xA9E5, 0x24B4, 0xA9E6, 0x24B5, 0xA9E7, 0x2474, 0xA9E8, 0x2475, 0xA9E9, 0x2476, + 0xA9EA, 0x2477, 0xA9EB, 0x2478, 0xA9EC, 0x2479, 0xA9ED, 0x247A, 0xA9EE, 0x247B, 0xA9EF, 0x247C, 0xA9F0, 0x247D, 0xA9F1, 0x247E, + 0xA9F2, 0x247F, 0xA9F3, 0x2480, 0xA9F4, 0x2481, 0xA9F5, 0x2482, 0xA9F6, 0x00B9, 0xA9F7, 0x00B2, 0xA9F8, 0x00B3, 0xA9F9, 0x2074, + 0xA9FA, 0x207F, 0xA9FB, 0x2081, 0xA9FC, 0x2082, 0xA9FD, 0x2083, 0xA9FE, 0x2084, 0xAA41, 0xCC25, 0xAA42, 0xCC26, 0xAA43, 0xCC2A, + 0xAA44, 0xCC2B, 0xAA45, 0xCC2D, 0xAA46, 0xCC2F, 0xAA47, 0xCC31, 0xAA48, 0xCC32, 0xAA49, 0xCC33, 0xAA4A, 0xCC34, 0xAA4B, 0xCC35, + 0xAA4C, 0xCC36, 0xAA4D, 0xCC37, 0xAA4E, 0xCC3A, 0xAA4F, 0xCC3F, 0xAA50, 0xCC40, 0xAA51, 0xCC41, 0xAA52, 0xCC42, 0xAA53, 0xCC43, + 0xAA54, 0xCC46, 0xAA55, 0xCC47, 0xAA56, 0xCC49, 0xAA57, 0xCC4A, 0xAA58, 0xCC4B, 0xAA59, 0xCC4D, 0xAA5A, 0xCC4E, 0xAA61, 0xCC4F, + 0xAA62, 0xCC50, 0xAA63, 0xCC51, 0xAA64, 0xCC52, 0xAA65, 0xCC53, 0xAA66, 0xCC56, 0xAA67, 0xCC5A, 0xAA68, 0xCC5B, 0xAA69, 0xCC5C, + 0xAA6A, 0xCC5D, 0xAA6B, 0xCC5E, 0xAA6C, 0xCC5F, 0xAA6D, 0xCC61, 0xAA6E, 0xCC62, 0xAA6F, 0xCC63, 0xAA70, 0xCC65, 0xAA71, 0xCC67, + 0xAA72, 0xCC69, 0xAA73, 0xCC6A, 0xAA74, 0xCC6B, 0xAA75, 0xCC6C, 0xAA76, 0xCC6D, 0xAA77, 0xCC6E, 0xAA78, 0xCC6F, 0xAA79, 0xCC71, + 0xAA7A, 0xCC72, 0xAA81, 0xCC73, 0xAA82, 0xCC74, 0xAA83, 0xCC76, 0xAA84, 0xCC77, 0xAA85, 0xCC78, 0xAA86, 0xCC79, 0xAA87, 0xCC7A, + 0xAA88, 0xCC7B, 0xAA89, 0xCC7C, 0xAA8A, 0xCC7D, 0xAA8B, 0xCC7E, 0xAA8C, 0xCC7F, 0xAA8D, 0xCC80, 0xAA8E, 0xCC81, 0xAA8F, 0xCC82, + 0xAA90, 0xCC83, 0xAA91, 0xCC84, 0xAA92, 0xCC85, 0xAA93, 0xCC86, 0xAA94, 0xCC87, 0xAA95, 0xCC88, 0xAA96, 0xCC89, 0xAA97, 0xCC8A, + 0xAA98, 0xCC8B, 0xAA99, 0xCC8C, 0xAA9A, 0xCC8D, 0xAA9B, 0xCC8E, 0xAA9C, 0xCC8F, 0xAA9D, 0xCC90, 0xAA9E, 0xCC91, 0xAA9F, 0xCC92, + 0xAAA0, 0xCC93, 0xAAA1, 0x3041, 0xAAA2, 0x3042, 0xAAA3, 0x3043, 0xAAA4, 0x3044, 0xAAA5, 0x3045, 0xAAA6, 0x3046, 0xAAA7, 0x3047, + 0xAAA8, 0x3048, 0xAAA9, 0x3049, 0xAAAA, 0x304A, 0xAAAB, 0x304B, 0xAAAC, 0x304C, 0xAAAD, 0x304D, 0xAAAE, 0x304E, 0xAAAF, 0x304F, + 0xAAB0, 0x3050, 0xAAB1, 0x3051, 0xAAB2, 0x3052, 0xAAB3, 0x3053, 0xAAB4, 0x3054, 0xAAB5, 0x3055, 0xAAB6, 0x3056, 0xAAB7, 0x3057, + 0xAAB8, 0x3058, 0xAAB9, 0x3059, 0xAABA, 0x305A, 0xAABB, 0x305B, 0xAABC, 0x305C, 0xAABD, 0x305D, 0xAABE, 0x305E, 0xAABF, 0x305F, + 0xAAC0, 0x3060, 0xAAC1, 0x3061, 0xAAC2, 0x3062, 0xAAC3, 0x3063, 0xAAC4, 0x3064, 0xAAC5, 0x3065, 0xAAC6, 0x3066, 0xAAC7, 0x3067, + 0xAAC8, 0x3068, 0xAAC9, 0x3069, 0xAACA, 0x306A, 0xAACB, 0x306B, 0xAACC, 0x306C, 0xAACD, 0x306D, 0xAACE, 0x306E, 0xAACF, 0x306F, + 0xAAD0, 0x3070, 0xAAD1, 0x3071, 0xAAD2, 0x3072, 0xAAD3, 0x3073, 0xAAD4, 0x3074, 0xAAD5, 0x3075, 0xAAD6, 0x3076, 0xAAD7, 0x3077, + 0xAAD8, 0x3078, 0xAAD9, 0x3079, 0xAADA, 0x307A, 0xAADB, 0x307B, 0xAADC, 0x307C, 0xAADD, 0x307D, 0xAADE, 0x307E, 0xAADF, 0x307F, + 0xAAE0, 0x3080, 0xAAE1, 0x3081, 0xAAE2, 0x3082, 0xAAE3, 0x3083, 0xAAE4, 0x3084, 0xAAE5, 0x3085, 0xAAE6, 0x3086, 0xAAE7, 0x3087, + 0xAAE8, 0x3088, 0xAAE9, 0x3089, 0xAAEA, 0x308A, 0xAAEB, 0x308B, 0xAAEC, 0x308C, 0xAAED, 0x308D, 0xAAEE, 0x308E, 0xAAEF, 0x308F, + 0xAAF0, 0x3090, 0xAAF1, 0x3091, 0xAAF2, 0x3092, 0xAAF3, 0x3093, 0xAB41, 0xCC94, 0xAB42, 0xCC95, 0xAB43, 0xCC96, 0xAB44, 0xCC97, + 0xAB45, 0xCC9A, 0xAB46, 0xCC9B, 0xAB47, 0xCC9D, 0xAB48, 0xCC9E, 0xAB49, 0xCC9F, 0xAB4A, 0xCCA1, 0xAB4B, 0xCCA2, 0xAB4C, 0xCCA3, + 0xAB4D, 0xCCA4, 0xAB4E, 0xCCA5, 0xAB4F, 0xCCA6, 0xAB50, 0xCCA7, 0xAB51, 0xCCAA, 0xAB52, 0xCCAE, 0xAB53, 0xCCAF, 0xAB54, 0xCCB0, + 0xAB55, 0xCCB1, 0xAB56, 0xCCB2, 0xAB57, 0xCCB3, 0xAB58, 0xCCB6, 0xAB59, 0xCCB7, 0xAB5A, 0xCCB9, 0xAB61, 0xCCBA, 0xAB62, 0xCCBB, + 0xAB63, 0xCCBD, 0xAB64, 0xCCBE, 0xAB65, 0xCCBF, 0xAB66, 0xCCC0, 0xAB67, 0xCCC1, 0xAB68, 0xCCC2, 0xAB69, 0xCCC3, 0xAB6A, 0xCCC6, + 0xAB6B, 0xCCC8, 0xAB6C, 0xCCCA, 0xAB6D, 0xCCCB, 0xAB6E, 0xCCCC, 0xAB6F, 0xCCCD, 0xAB70, 0xCCCE, 0xAB71, 0xCCCF, 0xAB72, 0xCCD1, + 0xAB73, 0xCCD2, 0xAB74, 0xCCD3, 0xAB75, 0xCCD5, 0xAB76, 0xCCD6, 0xAB77, 0xCCD7, 0xAB78, 0xCCD8, 0xAB79, 0xCCD9, 0xAB7A, 0xCCDA, + 0xAB81, 0xCCDB, 0xAB82, 0xCCDC, 0xAB83, 0xCCDD, 0xAB84, 0xCCDE, 0xAB85, 0xCCDF, 0xAB86, 0xCCE0, 0xAB87, 0xCCE1, 0xAB88, 0xCCE2, + 0xAB89, 0xCCE3, 0xAB8A, 0xCCE5, 0xAB8B, 0xCCE6, 0xAB8C, 0xCCE7, 0xAB8D, 0xCCE8, 0xAB8E, 0xCCE9, 0xAB8F, 0xCCEA, 0xAB90, 0xCCEB, + 0xAB91, 0xCCED, 0xAB92, 0xCCEE, 0xAB93, 0xCCEF, 0xAB94, 0xCCF1, 0xAB95, 0xCCF2, 0xAB96, 0xCCF3, 0xAB97, 0xCCF4, 0xAB98, 0xCCF5, + 0xAB99, 0xCCF6, 0xAB9A, 0xCCF7, 0xAB9B, 0xCCF8, 0xAB9C, 0xCCF9, 0xAB9D, 0xCCFA, 0xAB9E, 0xCCFB, 0xAB9F, 0xCCFC, 0xABA0, 0xCCFD, + 0xABA1, 0x30A1, 0xABA2, 0x30A2, 0xABA3, 0x30A3, 0xABA4, 0x30A4, 0xABA5, 0x30A5, 0xABA6, 0x30A6, 0xABA7, 0x30A7, 0xABA8, 0x30A8, + 0xABA9, 0x30A9, 0xABAA, 0x30AA, 0xABAB, 0x30AB, 0xABAC, 0x30AC, 0xABAD, 0x30AD, 0xABAE, 0x30AE, 0xABAF, 0x30AF, 0xABB0, 0x30B0, + 0xABB1, 0x30B1, 0xABB2, 0x30B2, 0xABB3, 0x30B3, 0xABB4, 0x30B4, 0xABB5, 0x30B5, 0xABB6, 0x30B6, 0xABB7, 0x30B7, 0xABB8, 0x30B8, + 0xABB9, 0x30B9, 0xABBA, 0x30BA, 0xABBB, 0x30BB, 0xABBC, 0x30BC, 0xABBD, 0x30BD, 0xABBE, 0x30BE, 0xABBF, 0x30BF, 0xABC0, 0x30C0, + 0xABC1, 0x30C1, 0xABC2, 0x30C2, 0xABC3, 0x30C3, 0xABC4, 0x30C4, 0xABC5, 0x30C5, 0xABC6, 0x30C6, 0xABC7, 0x30C7, 0xABC8, 0x30C8, + 0xABC9, 0x30C9, 0xABCA, 0x30CA, 0xABCB, 0x30CB, 0xABCC, 0x30CC, 0xABCD, 0x30CD, 0xABCE, 0x30CE, 0xABCF, 0x30CF, 0xABD0, 0x30D0, + 0xABD1, 0x30D1, 0xABD2, 0x30D2, 0xABD3, 0x30D3, 0xABD4, 0x30D4, 0xABD5, 0x30D5, 0xABD6, 0x30D6, 0xABD7, 0x30D7, 0xABD8, 0x30D8, + 0xABD9, 0x30D9, 0xABDA, 0x30DA, 0xABDB, 0x30DB, 0xABDC, 0x30DC, 0xABDD, 0x30DD, 0xABDE, 0x30DE, 0xABDF, 0x30DF, 0xABE0, 0x30E0, + 0xABE1, 0x30E1, 0xABE2, 0x30E2, 0xABE3, 0x30E3, 0xABE4, 0x30E4, 0xABE5, 0x30E5, 0xABE6, 0x30E6, 0xABE7, 0x30E7, 0xABE8, 0x30E8, + 0xABE9, 0x30E9, 0xABEA, 0x30EA, 0xABEB, 0x30EB, 0xABEC, 0x30EC, 0xABED, 0x30ED, 0xABEE, 0x30EE, 0xABEF, 0x30EF, 0xABF0, 0x30F0, + 0xABF1, 0x30F1, 0xABF2, 0x30F2, 0xABF3, 0x30F3, 0xABF4, 0x30F4, 0xABF5, 0x30F5, 0xABF6, 0x30F6, 0xAC41, 0xCCFE, 0xAC42, 0xCCFF, + 0xAC43, 0xCD00, 0xAC44, 0xCD02, 0xAC45, 0xCD03, 0xAC46, 0xCD04, 0xAC47, 0xCD05, 0xAC48, 0xCD06, 0xAC49, 0xCD07, 0xAC4A, 0xCD0A, + 0xAC4B, 0xCD0B, 0xAC4C, 0xCD0D, 0xAC4D, 0xCD0E, 0xAC4E, 0xCD0F, 0xAC4F, 0xCD11, 0xAC50, 0xCD12, 0xAC51, 0xCD13, 0xAC52, 0xCD14, + 0xAC53, 0xCD15, 0xAC54, 0xCD16, 0xAC55, 0xCD17, 0xAC56, 0xCD1A, 0xAC57, 0xCD1C, 0xAC58, 0xCD1E, 0xAC59, 0xCD1F, 0xAC5A, 0xCD20, + 0xAC61, 0xCD21, 0xAC62, 0xCD22, 0xAC63, 0xCD23, 0xAC64, 0xCD25, 0xAC65, 0xCD26, 0xAC66, 0xCD27, 0xAC67, 0xCD29, 0xAC68, 0xCD2A, + 0xAC69, 0xCD2B, 0xAC6A, 0xCD2D, 0xAC6B, 0xCD2E, 0xAC6C, 0xCD2F, 0xAC6D, 0xCD30, 0xAC6E, 0xCD31, 0xAC6F, 0xCD32, 0xAC70, 0xCD33, + 0xAC71, 0xCD34, 0xAC72, 0xCD35, 0xAC73, 0xCD36, 0xAC74, 0xCD37, 0xAC75, 0xCD38, 0xAC76, 0xCD3A, 0xAC77, 0xCD3B, 0xAC78, 0xCD3C, + 0xAC79, 0xCD3D, 0xAC7A, 0xCD3E, 0xAC81, 0xCD3F, 0xAC82, 0xCD40, 0xAC83, 0xCD41, 0xAC84, 0xCD42, 0xAC85, 0xCD43, 0xAC86, 0xCD44, + 0xAC87, 0xCD45, 0xAC88, 0xCD46, 0xAC89, 0xCD47, 0xAC8A, 0xCD48, 0xAC8B, 0xCD49, 0xAC8C, 0xCD4A, 0xAC8D, 0xCD4B, 0xAC8E, 0xCD4C, + 0xAC8F, 0xCD4D, 0xAC90, 0xCD4E, 0xAC91, 0xCD4F, 0xAC92, 0xCD50, 0xAC93, 0xCD51, 0xAC94, 0xCD52, 0xAC95, 0xCD53, 0xAC96, 0xCD54, + 0xAC97, 0xCD55, 0xAC98, 0xCD56, 0xAC99, 0xCD57, 0xAC9A, 0xCD58, 0xAC9B, 0xCD59, 0xAC9C, 0xCD5A, 0xAC9D, 0xCD5B, 0xAC9E, 0xCD5D, + 0xAC9F, 0xCD5E, 0xACA0, 0xCD5F, 0xACA1, 0x0410, 0xACA2, 0x0411, 0xACA3, 0x0412, 0xACA4, 0x0413, 0xACA5, 0x0414, 0xACA6, 0x0415, + 0xACA7, 0x0401, 0xACA8, 0x0416, 0xACA9, 0x0417, 0xACAA, 0x0418, 0xACAB, 0x0419, 0xACAC, 0x041A, 0xACAD, 0x041B, 0xACAE, 0x041C, + 0xACAF, 0x041D, 0xACB0, 0x041E, 0xACB1, 0x041F, 0xACB2, 0x0420, 0xACB3, 0x0421, 0xACB4, 0x0422, 0xACB5, 0x0423, 0xACB6, 0x0424, + 0xACB7, 0x0425, 0xACB8, 0x0426, 0xACB9, 0x0427, 0xACBA, 0x0428, 0xACBB, 0x0429, 0xACBC, 0x042A, 0xACBD, 0x042B, 0xACBE, 0x042C, + 0xACBF, 0x042D, 0xACC0, 0x042E, 0xACC1, 0x042F, 0xACD1, 0x0430, 0xACD2, 0x0431, 0xACD3, 0x0432, 0xACD4, 0x0433, 0xACD5, 0x0434, + 0xACD6, 0x0435, 0xACD7, 0x0451, 0xACD8, 0x0436, 0xACD9, 0x0437, 0xACDA, 0x0438, 0xACDB, 0x0439, 0xACDC, 0x043A, 0xACDD, 0x043B, + 0xACDE, 0x043C, 0xACDF, 0x043D, 0xACE0, 0x043E, 0xACE1, 0x043F, 0xACE2, 0x0440, 0xACE3, 0x0441, 0xACE4, 0x0442, 0xACE5, 0x0443, + 0xACE6, 0x0444, 0xACE7, 0x0445, 0xACE8, 0x0446, 0xACE9, 0x0447, 0xACEA, 0x0448, 0xACEB, 0x0449, 0xACEC, 0x044A, 0xACED, 0x044B, + 0xACEE, 0x044C, 0xACEF, 0x044D, 0xACF0, 0x044E, 0xACF1, 0x044F, 0xAD41, 0xCD61, 0xAD42, 0xCD62, 0xAD43, 0xCD63, 0xAD44, 0xCD65, + 0xAD45, 0xCD66, 0xAD46, 0xCD67, 0xAD47, 0xCD68, 0xAD48, 0xCD69, 0xAD49, 0xCD6A, 0xAD4A, 0xCD6B, 0xAD4B, 0xCD6E, 0xAD4C, 0xCD70, + 0xAD4D, 0xCD72, 0xAD4E, 0xCD73, 0xAD4F, 0xCD74, 0xAD50, 0xCD75, 0xAD51, 0xCD76, 0xAD52, 0xCD77, 0xAD53, 0xCD79, 0xAD54, 0xCD7A, + 0xAD55, 0xCD7B, 0xAD56, 0xCD7C, 0xAD57, 0xCD7D, 0xAD58, 0xCD7E, 0xAD59, 0xCD7F, 0xAD5A, 0xCD80, 0xAD61, 0xCD81, 0xAD62, 0xCD82, + 0xAD63, 0xCD83, 0xAD64, 0xCD84, 0xAD65, 0xCD85, 0xAD66, 0xCD86, 0xAD67, 0xCD87, 0xAD68, 0xCD89, 0xAD69, 0xCD8A, 0xAD6A, 0xCD8B, + 0xAD6B, 0xCD8C, 0xAD6C, 0xCD8D, 0xAD6D, 0xCD8E, 0xAD6E, 0xCD8F, 0xAD6F, 0xCD90, 0xAD70, 0xCD91, 0xAD71, 0xCD92, 0xAD72, 0xCD93, + 0xAD73, 0xCD96, 0xAD74, 0xCD97, 0xAD75, 0xCD99, 0xAD76, 0xCD9A, 0xAD77, 0xCD9B, 0xAD78, 0xCD9D, 0xAD79, 0xCD9E, 0xAD7A, 0xCD9F, + 0xAD81, 0xCDA0, 0xAD82, 0xCDA1, 0xAD83, 0xCDA2, 0xAD84, 0xCDA3, 0xAD85, 0xCDA6, 0xAD86, 0xCDA8, 0xAD87, 0xCDAA, 0xAD88, 0xCDAB, + 0xAD89, 0xCDAC, 0xAD8A, 0xCDAD, 0xAD8B, 0xCDAE, 0xAD8C, 0xCDAF, 0xAD8D, 0xCDB1, 0xAD8E, 0xCDB2, 0xAD8F, 0xCDB3, 0xAD90, 0xCDB4, + 0xAD91, 0xCDB5, 0xAD92, 0xCDB6, 0xAD93, 0xCDB7, 0xAD94, 0xCDB8, 0xAD95, 0xCDB9, 0xAD96, 0xCDBA, 0xAD97, 0xCDBB, 0xAD98, 0xCDBC, + 0xAD99, 0xCDBD, 0xAD9A, 0xCDBE, 0xAD9B, 0xCDBF, 0xAD9C, 0xCDC0, 0xAD9D, 0xCDC1, 0xAD9E, 0xCDC2, 0xAD9F, 0xCDC3, 0xADA0, 0xCDC5, + 0xAE41, 0xCDC6, 0xAE42, 0xCDC7, 0xAE43, 0xCDC8, 0xAE44, 0xCDC9, 0xAE45, 0xCDCA, 0xAE46, 0xCDCB, 0xAE47, 0xCDCD, 0xAE48, 0xCDCE, + 0xAE49, 0xCDCF, 0xAE4A, 0xCDD1, 0xAE4B, 0xCDD2, 0xAE4C, 0xCDD3, 0xAE4D, 0xCDD4, 0xAE4E, 0xCDD5, 0xAE4F, 0xCDD6, 0xAE50, 0xCDD7, + 0xAE51, 0xCDD8, 0xAE52, 0xCDD9, 0xAE53, 0xCDDA, 0xAE54, 0xCDDB, 0xAE55, 0xCDDC, 0xAE56, 0xCDDD, 0xAE57, 0xCDDE, 0xAE58, 0xCDDF, + 0xAE59, 0xCDE0, 0xAE5A, 0xCDE1, 0xAE61, 0xCDE2, 0xAE62, 0xCDE3, 0xAE63, 0xCDE4, 0xAE64, 0xCDE5, 0xAE65, 0xCDE6, 0xAE66, 0xCDE7, + 0xAE67, 0xCDE9, 0xAE68, 0xCDEA, 0xAE69, 0xCDEB, 0xAE6A, 0xCDED, 0xAE6B, 0xCDEE, 0xAE6C, 0xCDEF, 0xAE6D, 0xCDF1, 0xAE6E, 0xCDF2, + 0xAE6F, 0xCDF3, 0xAE70, 0xCDF4, 0xAE71, 0xCDF5, 0xAE72, 0xCDF6, 0xAE73, 0xCDF7, 0xAE74, 0xCDFA, 0xAE75, 0xCDFC, 0xAE76, 0xCDFE, + 0xAE77, 0xCDFF, 0xAE78, 0xCE00, 0xAE79, 0xCE01, 0xAE7A, 0xCE02, 0xAE81, 0xCE03, 0xAE82, 0xCE05, 0xAE83, 0xCE06, 0xAE84, 0xCE07, + 0xAE85, 0xCE09, 0xAE86, 0xCE0A, 0xAE87, 0xCE0B, 0xAE88, 0xCE0D, 0xAE89, 0xCE0E, 0xAE8A, 0xCE0F, 0xAE8B, 0xCE10, 0xAE8C, 0xCE11, + 0xAE8D, 0xCE12, 0xAE8E, 0xCE13, 0xAE8F, 0xCE15, 0xAE90, 0xCE16, 0xAE91, 0xCE17, 0xAE92, 0xCE18, 0xAE93, 0xCE1A, 0xAE94, 0xCE1B, + 0xAE95, 0xCE1C, 0xAE96, 0xCE1D, 0xAE97, 0xCE1E, 0xAE98, 0xCE1F, 0xAE99, 0xCE22, 0xAE9A, 0xCE23, 0xAE9B, 0xCE25, 0xAE9C, 0xCE26, + 0xAE9D, 0xCE27, 0xAE9E, 0xCE29, 0xAE9F, 0xCE2A, 0xAEA0, 0xCE2B, 0xAF41, 0xCE2C, 0xAF42, 0xCE2D, 0xAF43, 0xCE2E, 0xAF44, 0xCE2F, + 0xAF45, 0xCE32, 0xAF46, 0xCE34, 0xAF47, 0xCE36, 0xAF48, 0xCE37, 0xAF49, 0xCE38, 0xAF4A, 0xCE39, 0xAF4B, 0xCE3A, 0xAF4C, 0xCE3B, + 0xAF4D, 0xCE3C, 0xAF4E, 0xCE3D, 0xAF4F, 0xCE3E, 0xAF50, 0xCE3F, 0xAF51, 0xCE40, 0xAF52, 0xCE41, 0xAF53, 0xCE42, 0xAF54, 0xCE43, + 0xAF55, 0xCE44, 0xAF56, 0xCE45, 0xAF57, 0xCE46, 0xAF58, 0xCE47, 0xAF59, 0xCE48, 0xAF5A, 0xCE49, 0xAF61, 0xCE4A, 0xAF62, 0xCE4B, + 0xAF63, 0xCE4C, 0xAF64, 0xCE4D, 0xAF65, 0xCE4E, 0xAF66, 0xCE4F, 0xAF67, 0xCE50, 0xAF68, 0xCE51, 0xAF69, 0xCE52, 0xAF6A, 0xCE53, + 0xAF6B, 0xCE54, 0xAF6C, 0xCE55, 0xAF6D, 0xCE56, 0xAF6E, 0xCE57, 0xAF6F, 0xCE5A, 0xAF70, 0xCE5B, 0xAF71, 0xCE5D, 0xAF72, 0xCE5E, + 0xAF73, 0xCE62, 0xAF74, 0xCE63, 0xAF75, 0xCE64, 0xAF76, 0xCE65, 0xAF77, 0xCE66, 0xAF78, 0xCE67, 0xAF79, 0xCE6A, 0xAF7A, 0xCE6C, + 0xAF81, 0xCE6E, 0xAF82, 0xCE6F, 0xAF83, 0xCE70, 0xAF84, 0xCE71, 0xAF85, 0xCE72, 0xAF86, 0xCE73, 0xAF87, 0xCE76, 0xAF88, 0xCE77, + 0xAF89, 0xCE79, 0xAF8A, 0xCE7A, 0xAF8B, 0xCE7B, 0xAF8C, 0xCE7D, 0xAF8D, 0xCE7E, 0xAF8E, 0xCE7F, 0xAF8F, 0xCE80, 0xAF90, 0xCE81, + 0xAF91, 0xCE82, 0xAF92, 0xCE83, 0xAF93, 0xCE86, 0xAF94, 0xCE88, 0xAF95, 0xCE8A, 0xAF96, 0xCE8B, 0xAF97, 0xCE8C, 0xAF98, 0xCE8D, + 0xAF99, 0xCE8E, 0xAF9A, 0xCE8F, 0xAF9B, 0xCE92, 0xAF9C, 0xCE93, 0xAF9D, 0xCE95, 0xAF9E, 0xCE96, 0xAF9F, 0xCE97, 0xAFA0, 0xCE99, + 0xB041, 0xCE9A, 0xB042, 0xCE9B, 0xB043, 0xCE9C, 0xB044, 0xCE9D, 0xB045, 0xCE9E, 0xB046, 0xCE9F, 0xB047, 0xCEA2, 0xB048, 0xCEA6, + 0xB049, 0xCEA7, 0xB04A, 0xCEA8, 0xB04B, 0xCEA9, 0xB04C, 0xCEAA, 0xB04D, 0xCEAB, 0xB04E, 0xCEAE, 0xB04F, 0xCEAF, 0xB050, 0xCEB0, + 0xB051, 0xCEB1, 0xB052, 0xCEB2, 0xB053, 0xCEB3, 0xB054, 0xCEB4, 0xB055, 0xCEB5, 0xB056, 0xCEB6, 0xB057, 0xCEB7, 0xB058, 0xCEB8, + 0xB059, 0xCEB9, 0xB05A, 0xCEBA, 0xB061, 0xCEBB, 0xB062, 0xCEBC, 0xB063, 0xCEBD, 0xB064, 0xCEBE, 0xB065, 0xCEBF, 0xB066, 0xCEC0, + 0xB067, 0xCEC2, 0xB068, 0xCEC3, 0xB069, 0xCEC4, 0xB06A, 0xCEC5, 0xB06B, 0xCEC6, 0xB06C, 0xCEC7, 0xB06D, 0xCEC8, 0xB06E, 0xCEC9, + 0xB06F, 0xCECA, 0xB070, 0xCECB, 0xB071, 0xCECC, 0xB072, 0xCECD, 0xB073, 0xCECE, 0xB074, 0xCECF, 0xB075, 0xCED0, 0xB076, 0xCED1, + 0xB077, 0xCED2, 0xB078, 0xCED3, 0xB079, 0xCED4, 0xB07A, 0xCED5, 0xB081, 0xCED6, 0xB082, 0xCED7, 0xB083, 0xCED8, 0xB084, 0xCED9, + 0xB085, 0xCEDA, 0xB086, 0xCEDB, 0xB087, 0xCEDC, 0xB088, 0xCEDD, 0xB089, 0xCEDE, 0xB08A, 0xCEDF, 0xB08B, 0xCEE0, 0xB08C, 0xCEE1, + 0xB08D, 0xCEE2, 0xB08E, 0xCEE3, 0xB08F, 0xCEE6, 0xB090, 0xCEE7, 0xB091, 0xCEE9, 0xB092, 0xCEEA, 0xB093, 0xCEED, 0xB094, 0xCEEE, + 0xB095, 0xCEEF, 0xB096, 0xCEF0, 0xB097, 0xCEF1, 0xB098, 0xCEF2, 0xB099, 0xCEF3, 0xB09A, 0xCEF6, 0xB09B, 0xCEFA, 0xB09C, 0xCEFB, + 0xB09D, 0xCEFC, 0xB09E, 0xCEFD, 0xB09F, 0xCEFE, 0xB0A0, 0xCEFF, 0xB0A1, 0xAC00, 0xB0A2, 0xAC01, 0xB0A3, 0xAC04, 0xB0A4, 0xAC07, + 0xB0A5, 0xAC08, 0xB0A6, 0xAC09, 0xB0A7, 0xAC0A, 0xB0A8, 0xAC10, 0xB0A9, 0xAC11, 0xB0AA, 0xAC12, 0xB0AB, 0xAC13, 0xB0AC, 0xAC14, + 0xB0AD, 0xAC15, 0xB0AE, 0xAC16, 0xB0AF, 0xAC17, 0xB0B0, 0xAC19, 0xB0B1, 0xAC1A, 0xB0B2, 0xAC1B, 0xB0B3, 0xAC1C, 0xB0B4, 0xAC1D, + 0xB0B5, 0xAC20, 0xB0B6, 0xAC24, 0xB0B7, 0xAC2C, 0xB0B8, 0xAC2D, 0xB0B9, 0xAC2F, 0xB0BA, 0xAC30, 0xB0BB, 0xAC31, 0xB0BC, 0xAC38, + 0xB0BD, 0xAC39, 0xB0BE, 0xAC3C, 0xB0BF, 0xAC40, 0xB0C0, 0xAC4B, 0xB0C1, 0xAC4D, 0xB0C2, 0xAC54, 0xB0C3, 0xAC58, 0xB0C4, 0xAC5C, + 0xB0C5, 0xAC70, 0xB0C6, 0xAC71, 0xB0C7, 0xAC74, 0xB0C8, 0xAC77, 0xB0C9, 0xAC78, 0xB0CA, 0xAC7A, 0xB0CB, 0xAC80, 0xB0CC, 0xAC81, + 0xB0CD, 0xAC83, 0xB0CE, 0xAC84, 0xB0CF, 0xAC85, 0xB0D0, 0xAC86, 0xB0D1, 0xAC89, 0xB0D2, 0xAC8A, 0xB0D3, 0xAC8B, 0xB0D4, 0xAC8C, + 0xB0D5, 0xAC90, 0xB0D6, 0xAC94, 0xB0D7, 0xAC9C, 0xB0D8, 0xAC9D, 0xB0D9, 0xAC9F, 0xB0DA, 0xACA0, 0xB0DB, 0xACA1, 0xB0DC, 0xACA8, + 0xB0DD, 0xACA9, 0xB0DE, 0xACAA, 0xB0DF, 0xACAC, 0xB0E0, 0xACAF, 0xB0E1, 0xACB0, 0xB0E2, 0xACB8, 0xB0E3, 0xACB9, 0xB0E4, 0xACBB, + 0xB0E5, 0xACBC, 0xB0E6, 0xACBD, 0xB0E7, 0xACC1, 0xB0E8, 0xACC4, 0xB0E9, 0xACC8, 0xB0EA, 0xACCC, 0xB0EB, 0xACD5, 0xB0EC, 0xACD7, + 0xB0ED, 0xACE0, 0xB0EE, 0xACE1, 0xB0EF, 0xACE4, 0xB0F0, 0xACE7, 0xB0F1, 0xACE8, 0xB0F2, 0xACEA, 0xB0F3, 0xACEC, 0xB0F4, 0xACEF, + 0xB0F5, 0xACF0, 0xB0F6, 0xACF1, 0xB0F7, 0xACF3, 0xB0F8, 0xACF5, 0xB0F9, 0xACF6, 0xB0FA, 0xACFC, 0xB0FB, 0xACFD, 0xB0FC, 0xAD00, + 0xB0FD, 0xAD04, 0xB0FE, 0xAD06, 0xB141, 0xCF02, 0xB142, 0xCF03, 0xB143, 0xCF05, 0xB144, 0xCF06, 0xB145, 0xCF07, 0xB146, 0xCF09, + 0xB147, 0xCF0A, 0xB148, 0xCF0B, 0xB149, 0xCF0C, 0xB14A, 0xCF0D, 0xB14B, 0xCF0E, 0xB14C, 0xCF0F, 0xB14D, 0xCF12, 0xB14E, 0xCF14, + 0xB14F, 0xCF16, 0xB150, 0xCF17, 0xB151, 0xCF18, 0xB152, 0xCF19, 0xB153, 0xCF1A, 0xB154, 0xCF1B, 0xB155, 0xCF1D, 0xB156, 0xCF1E, + 0xB157, 0xCF1F, 0xB158, 0xCF21, 0xB159, 0xCF22, 0xB15A, 0xCF23, 0xB161, 0xCF25, 0xB162, 0xCF26, 0xB163, 0xCF27, 0xB164, 0xCF28, + 0xB165, 0xCF29, 0xB166, 0xCF2A, 0xB167, 0xCF2B, 0xB168, 0xCF2E, 0xB169, 0xCF32, 0xB16A, 0xCF33, 0xB16B, 0xCF34, 0xB16C, 0xCF35, + 0xB16D, 0xCF36, 0xB16E, 0xCF37, 0xB16F, 0xCF39, 0xB170, 0xCF3A, 0xB171, 0xCF3B, 0xB172, 0xCF3C, 0xB173, 0xCF3D, 0xB174, 0xCF3E, + 0xB175, 0xCF3F, 0xB176, 0xCF40, 0xB177, 0xCF41, 0xB178, 0xCF42, 0xB179, 0xCF43, 0xB17A, 0xCF44, 0xB181, 0xCF45, 0xB182, 0xCF46, + 0xB183, 0xCF47, 0xB184, 0xCF48, 0xB185, 0xCF49, 0xB186, 0xCF4A, 0xB187, 0xCF4B, 0xB188, 0xCF4C, 0xB189, 0xCF4D, 0xB18A, 0xCF4E, + 0xB18B, 0xCF4F, 0xB18C, 0xCF50, 0xB18D, 0xCF51, 0xB18E, 0xCF52, 0xB18F, 0xCF53, 0xB190, 0xCF56, 0xB191, 0xCF57, 0xB192, 0xCF59, + 0xB193, 0xCF5A, 0xB194, 0xCF5B, 0xB195, 0xCF5D, 0xB196, 0xCF5E, 0xB197, 0xCF5F, 0xB198, 0xCF60, 0xB199, 0xCF61, 0xB19A, 0xCF62, + 0xB19B, 0xCF63, 0xB19C, 0xCF66, 0xB19D, 0xCF68, 0xB19E, 0xCF6A, 0xB19F, 0xCF6B, 0xB1A0, 0xCF6C, 0xB1A1, 0xAD0C, 0xB1A2, 0xAD0D, + 0xB1A3, 0xAD0F, 0xB1A4, 0xAD11, 0xB1A5, 0xAD18, 0xB1A6, 0xAD1C, 0xB1A7, 0xAD20, 0xB1A8, 0xAD29, 0xB1A9, 0xAD2C, 0xB1AA, 0xAD2D, + 0xB1AB, 0xAD34, 0xB1AC, 0xAD35, 0xB1AD, 0xAD38, 0xB1AE, 0xAD3C, 0xB1AF, 0xAD44, 0xB1B0, 0xAD45, 0xB1B1, 0xAD47, 0xB1B2, 0xAD49, + 0xB1B3, 0xAD50, 0xB1B4, 0xAD54, 0xB1B5, 0xAD58, 0xB1B6, 0xAD61, 0xB1B7, 0xAD63, 0xB1B8, 0xAD6C, 0xB1B9, 0xAD6D, 0xB1BA, 0xAD70, + 0xB1BB, 0xAD73, 0xB1BC, 0xAD74, 0xB1BD, 0xAD75, 0xB1BE, 0xAD76, 0xB1BF, 0xAD7B, 0xB1C0, 0xAD7C, 0xB1C1, 0xAD7D, 0xB1C2, 0xAD7F, + 0xB1C3, 0xAD81, 0xB1C4, 0xAD82, 0xB1C5, 0xAD88, 0xB1C6, 0xAD89, 0xB1C7, 0xAD8C, 0xB1C8, 0xAD90, 0xB1C9, 0xAD9C, 0xB1CA, 0xAD9D, + 0xB1CB, 0xADA4, 0xB1CC, 0xADB7, 0xB1CD, 0xADC0, 0xB1CE, 0xADC1, 0xB1CF, 0xADC4, 0xB1D0, 0xADC8, 0xB1D1, 0xADD0, 0xB1D2, 0xADD1, + 0xB1D3, 0xADD3, 0xB1D4, 0xADDC, 0xB1D5, 0xADE0, 0xB1D6, 0xADE4, 0xB1D7, 0xADF8, 0xB1D8, 0xADF9, 0xB1D9, 0xADFC, 0xB1DA, 0xADFF, + 0xB1DB, 0xAE00, 0xB1DC, 0xAE01, 0xB1DD, 0xAE08, 0xB1DE, 0xAE09, 0xB1DF, 0xAE0B, 0xB1E0, 0xAE0D, 0xB1E1, 0xAE14, 0xB1E2, 0xAE30, + 0xB1E3, 0xAE31, 0xB1E4, 0xAE34, 0xB1E5, 0xAE37, 0xB1E6, 0xAE38, 0xB1E7, 0xAE3A, 0xB1E8, 0xAE40, 0xB1E9, 0xAE41, 0xB1EA, 0xAE43, + 0xB1EB, 0xAE45, 0xB1EC, 0xAE46, 0xB1ED, 0xAE4A, 0xB1EE, 0xAE4C, 0xB1EF, 0xAE4D, 0xB1F0, 0xAE4E, 0xB1F1, 0xAE50, 0xB1F2, 0xAE54, + 0xB1F3, 0xAE56, 0xB1F4, 0xAE5C, 0xB1F5, 0xAE5D, 0xB1F6, 0xAE5F, 0xB1F7, 0xAE60, 0xB1F8, 0xAE61, 0xB1F9, 0xAE65, 0xB1FA, 0xAE68, + 0xB1FB, 0xAE69, 0xB1FC, 0xAE6C, 0xB1FD, 0xAE70, 0xB1FE, 0xAE78, 0xB241, 0xCF6D, 0xB242, 0xCF6E, 0xB243, 0xCF6F, 0xB244, 0xCF72, + 0xB245, 0xCF73, 0xB246, 0xCF75, 0xB247, 0xCF76, 0xB248, 0xCF77, 0xB249, 0xCF79, 0xB24A, 0xCF7A, 0xB24B, 0xCF7B, 0xB24C, 0xCF7C, + 0xB24D, 0xCF7D, 0xB24E, 0xCF7E, 0xB24F, 0xCF7F, 0xB250, 0xCF81, 0xB251, 0xCF82, 0xB252, 0xCF83, 0xB253, 0xCF84, 0xB254, 0xCF86, + 0xB255, 0xCF87, 0xB256, 0xCF88, 0xB257, 0xCF89, 0xB258, 0xCF8A, 0xB259, 0xCF8B, 0xB25A, 0xCF8D, 0xB261, 0xCF8E, 0xB262, 0xCF8F, + 0xB263, 0xCF90, 0xB264, 0xCF91, 0xB265, 0xCF92, 0xB266, 0xCF93, 0xB267, 0xCF94, 0xB268, 0xCF95, 0xB269, 0xCF96, 0xB26A, 0xCF97, + 0xB26B, 0xCF98, 0xB26C, 0xCF99, 0xB26D, 0xCF9A, 0xB26E, 0xCF9B, 0xB26F, 0xCF9C, 0xB270, 0xCF9D, 0xB271, 0xCF9E, 0xB272, 0xCF9F, + 0xB273, 0xCFA0, 0xB274, 0xCFA2, 0xB275, 0xCFA3, 0xB276, 0xCFA4, 0xB277, 0xCFA5, 0xB278, 0xCFA6, 0xB279, 0xCFA7, 0xB27A, 0xCFA9, + 0xB281, 0xCFAA, 0xB282, 0xCFAB, 0xB283, 0xCFAC, 0xB284, 0xCFAD, 0xB285, 0xCFAE, 0xB286, 0xCFAF, 0xB287, 0xCFB1, 0xB288, 0xCFB2, + 0xB289, 0xCFB3, 0xB28A, 0xCFB4, 0xB28B, 0xCFB5, 0xB28C, 0xCFB6, 0xB28D, 0xCFB7, 0xB28E, 0xCFB8, 0xB28F, 0xCFB9, 0xB290, 0xCFBA, + 0xB291, 0xCFBB, 0xB292, 0xCFBC, 0xB293, 0xCFBD, 0xB294, 0xCFBE, 0xB295, 0xCFBF, 0xB296, 0xCFC0, 0xB297, 0xCFC1, 0xB298, 0xCFC2, + 0xB299, 0xCFC3, 0xB29A, 0xCFC5, 0xB29B, 0xCFC6, 0xB29C, 0xCFC7, 0xB29D, 0xCFC8, 0xB29E, 0xCFC9, 0xB29F, 0xCFCA, 0xB2A0, 0xCFCB, + 0xB2A1, 0xAE79, 0xB2A2, 0xAE7B, 0xB2A3, 0xAE7C, 0xB2A4, 0xAE7D, 0xB2A5, 0xAE84, 0xB2A6, 0xAE85, 0xB2A7, 0xAE8C, 0xB2A8, 0xAEBC, + 0xB2A9, 0xAEBD, 0xB2AA, 0xAEBE, 0xB2AB, 0xAEC0, 0xB2AC, 0xAEC4, 0xB2AD, 0xAECC, 0xB2AE, 0xAECD, 0xB2AF, 0xAECF, 0xB2B0, 0xAED0, + 0xB2B1, 0xAED1, 0xB2B2, 0xAED8, 0xB2B3, 0xAED9, 0xB2B4, 0xAEDC, 0xB2B5, 0xAEE8, 0xB2B6, 0xAEEB, 0xB2B7, 0xAEED, 0xB2B8, 0xAEF4, + 0xB2B9, 0xAEF8, 0xB2BA, 0xAEFC, 0xB2BB, 0xAF07, 0xB2BC, 0xAF08, 0xB2BD, 0xAF0D, 0xB2BE, 0xAF10, 0xB2BF, 0xAF2C, 0xB2C0, 0xAF2D, + 0xB2C1, 0xAF30, 0xB2C2, 0xAF32, 0xB2C3, 0xAF34, 0xB2C4, 0xAF3C, 0xB2C5, 0xAF3D, 0xB2C6, 0xAF3F, 0xB2C7, 0xAF41, 0xB2C8, 0xAF42, + 0xB2C9, 0xAF43, 0xB2CA, 0xAF48, 0xB2CB, 0xAF49, 0xB2CC, 0xAF50, 0xB2CD, 0xAF5C, 0xB2CE, 0xAF5D, 0xB2CF, 0xAF64, 0xB2D0, 0xAF65, + 0xB2D1, 0xAF79, 0xB2D2, 0xAF80, 0xB2D3, 0xAF84, 0xB2D4, 0xAF88, 0xB2D5, 0xAF90, 0xB2D6, 0xAF91, 0xB2D7, 0xAF95, 0xB2D8, 0xAF9C, + 0xB2D9, 0xAFB8, 0xB2DA, 0xAFB9, 0xB2DB, 0xAFBC, 0xB2DC, 0xAFC0, 0xB2DD, 0xAFC7, 0xB2DE, 0xAFC8, 0xB2DF, 0xAFC9, 0xB2E0, 0xAFCB, + 0xB2E1, 0xAFCD, 0xB2E2, 0xAFCE, 0xB2E3, 0xAFD4, 0xB2E4, 0xAFDC, 0xB2E5, 0xAFE8, 0xB2E6, 0xAFE9, 0xB2E7, 0xAFF0, 0xB2E8, 0xAFF1, + 0xB2E9, 0xAFF4, 0xB2EA, 0xAFF8, 0xB2EB, 0xB000, 0xB2EC, 0xB001, 0xB2ED, 0xB004, 0xB2EE, 0xB00C, 0xB2EF, 0xB010, 0xB2F0, 0xB014, + 0xB2F1, 0xB01C, 0xB2F2, 0xB01D, 0xB2F3, 0xB028, 0xB2F4, 0xB044, 0xB2F5, 0xB045, 0xB2F6, 0xB048, 0xB2F7, 0xB04A, 0xB2F8, 0xB04C, + 0xB2F9, 0xB04E, 0xB2FA, 0xB053, 0xB2FB, 0xB054, 0xB2FC, 0xB055, 0xB2FD, 0xB057, 0xB2FE, 0xB059, 0xB341, 0xCFCC, 0xB342, 0xCFCD, + 0xB343, 0xCFCE, 0xB344, 0xCFCF, 0xB345, 0xCFD0, 0xB346, 0xCFD1, 0xB347, 0xCFD2, 0xB348, 0xCFD3, 0xB349, 0xCFD4, 0xB34A, 0xCFD5, + 0xB34B, 0xCFD6, 0xB34C, 0xCFD7, 0xB34D, 0xCFD8, 0xB34E, 0xCFD9, 0xB34F, 0xCFDA, 0xB350, 0xCFDB, 0xB351, 0xCFDC, 0xB352, 0xCFDD, + 0xB353, 0xCFDE, 0xB354, 0xCFDF, 0xB355, 0xCFE2, 0xB356, 0xCFE3, 0xB357, 0xCFE5, 0xB358, 0xCFE6, 0xB359, 0xCFE7, 0xB35A, 0xCFE9, + 0xB361, 0xCFEA, 0xB362, 0xCFEB, 0xB363, 0xCFEC, 0xB364, 0xCFED, 0xB365, 0xCFEE, 0xB366, 0xCFEF, 0xB367, 0xCFF2, 0xB368, 0xCFF4, + 0xB369, 0xCFF6, 0xB36A, 0xCFF7, 0xB36B, 0xCFF8, 0xB36C, 0xCFF9, 0xB36D, 0xCFFA, 0xB36E, 0xCFFB, 0xB36F, 0xCFFD, 0xB370, 0xCFFE, + 0xB371, 0xCFFF, 0xB372, 0xD001, 0xB373, 0xD002, 0xB374, 0xD003, 0xB375, 0xD005, 0xB376, 0xD006, 0xB377, 0xD007, 0xB378, 0xD008, + 0xB379, 0xD009, 0xB37A, 0xD00A, 0xB381, 0xD00B, 0xB382, 0xD00C, 0xB383, 0xD00D, 0xB384, 0xD00E, 0xB385, 0xD00F, 0xB386, 0xD010, + 0xB387, 0xD012, 0xB388, 0xD013, 0xB389, 0xD014, 0xB38A, 0xD015, 0xB38B, 0xD016, 0xB38C, 0xD017, 0xB38D, 0xD019, 0xB38E, 0xD01A, + 0xB38F, 0xD01B, 0xB390, 0xD01C, 0xB391, 0xD01D, 0xB392, 0xD01E, 0xB393, 0xD01F, 0xB394, 0xD020, 0xB395, 0xD021, 0xB396, 0xD022, + 0xB397, 0xD023, 0xB398, 0xD024, 0xB399, 0xD025, 0xB39A, 0xD026, 0xB39B, 0xD027, 0xB39C, 0xD028, 0xB39D, 0xD029, 0xB39E, 0xD02A, + 0xB39F, 0xD02B, 0xB3A0, 0xD02C, 0xB3A1, 0xB05D, 0xB3A2, 0xB07C, 0xB3A3, 0xB07D, 0xB3A4, 0xB080, 0xB3A5, 0xB084, 0xB3A6, 0xB08C, + 0xB3A7, 0xB08D, 0xB3A8, 0xB08F, 0xB3A9, 0xB091, 0xB3AA, 0xB098, 0xB3AB, 0xB099, 0xB3AC, 0xB09A, 0xB3AD, 0xB09C, 0xB3AE, 0xB09F, + 0xB3AF, 0xB0A0, 0xB3B0, 0xB0A1, 0xB3B1, 0xB0A2, 0xB3B2, 0xB0A8, 0xB3B3, 0xB0A9, 0xB3B4, 0xB0AB, 0xB3B5, 0xB0AC, 0xB3B6, 0xB0AD, + 0xB3B7, 0xB0AE, 0xB3B8, 0xB0AF, 0xB3B9, 0xB0B1, 0xB3BA, 0xB0B3, 0xB3BB, 0xB0B4, 0xB3BC, 0xB0B5, 0xB3BD, 0xB0B8, 0xB3BE, 0xB0BC, + 0xB3BF, 0xB0C4, 0xB3C0, 0xB0C5, 0xB3C1, 0xB0C7, 0xB3C2, 0xB0C8, 0xB3C3, 0xB0C9, 0xB3C4, 0xB0D0, 0xB3C5, 0xB0D1, 0xB3C6, 0xB0D4, + 0xB3C7, 0xB0D8, 0xB3C8, 0xB0E0, 0xB3C9, 0xB0E5, 0xB3CA, 0xB108, 0xB3CB, 0xB109, 0xB3CC, 0xB10B, 0xB3CD, 0xB10C, 0xB3CE, 0xB110, + 0xB3CF, 0xB112, 0xB3D0, 0xB113, 0xB3D1, 0xB118, 0xB3D2, 0xB119, 0xB3D3, 0xB11B, 0xB3D4, 0xB11C, 0xB3D5, 0xB11D, 0xB3D6, 0xB123, + 0xB3D7, 0xB124, 0xB3D8, 0xB125, 0xB3D9, 0xB128, 0xB3DA, 0xB12C, 0xB3DB, 0xB134, 0xB3DC, 0xB135, 0xB3DD, 0xB137, 0xB3DE, 0xB138, + 0xB3DF, 0xB139, 0xB3E0, 0xB140, 0xB3E1, 0xB141, 0xB3E2, 0xB144, 0xB3E3, 0xB148, 0xB3E4, 0xB150, 0xB3E5, 0xB151, 0xB3E6, 0xB154, + 0xB3E7, 0xB155, 0xB3E8, 0xB158, 0xB3E9, 0xB15C, 0xB3EA, 0xB160, 0xB3EB, 0xB178, 0xB3EC, 0xB179, 0xB3ED, 0xB17C, 0xB3EE, 0xB180, + 0xB3EF, 0xB182, 0xB3F0, 0xB188, 0xB3F1, 0xB189, 0xB3F2, 0xB18B, 0xB3F3, 0xB18D, 0xB3F4, 0xB192, 0xB3F5, 0xB193, 0xB3F6, 0xB194, + 0xB3F7, 0xB198, 0xB3F8, 0xB19C, 0xB3F9, 0xB1A8, 0xB3FA, 0xB1CC, 0xB3FB, 0xB1D0, 0xB3FC, 0xB1D4, 0xB3FD, 0xB1DC, 0xB3FE, 0xB1DD, + 0xB441, 0xD02E, 0xB442, 0xD02F, 0xB443, 0xD030, 0xB444, 0xD031, 0xB445, 0xD032, 0xB446, 0xD033, 0xB447, 0xD036, 0xB448, 0xD037, + 0xB449, 0xD039, 0xB44A, 0xD03A, 0xB44B, 0xD03B, 0xB44C, 0xD03D, 0xB44D, 0xD03E, 0xB44E, 0xD03F, 0xB44F, 0xD040, 0xB450, 0xD041, + 0xB451, 0xD042, 0xB452, 0xD043, 0xB453, 0xD046, 0xB454, 0xD048, 0xB455, 0xD04A, 0xB456, 0xD04B, 0xB457, 0xD04C, 0xB458, 0xD04D, + 0xB459, 0xD04E, 0xB45A, 0xD04F, 0xB461, 0xD051, 0xB462, 0xD052, 0xB463, 0xD053, 0xB464, 0xD055, 0xB465, 0xD056, 0xB466, 0xD057, + 0xB467, 0xD059, 0xB468, 0xD05A, 0xB469, 0xD05B, 0xB46A, 0xD05C, 0xB46B, 0xD05D, 0xB46C, 0xD05E, 0xB46D, 0xD05F, 0xB46E, 0xD061, + 0xB46F, 0xD062, 0xB470, 0xD063, 0xB471, 0xD064, 0xB472, 0xD065, 0xB473, 0xD066, 0xB474, 0xD067, 0xB475, 0xD068, 0xB476, 0xD069, + 0xB477, 0xD06A, 0xB478, 0xD06B, 0xB479, 0xD06E, 0xB47A, 0xD06F, 0xB481, 0xD071, 0xB482, 0xD072, 0xB483, 0xD073, 0xB484, 0xD075, + 0xB485, 0xD076, 0xB486, 0xD077, 0xB487, 0xD078, 0xB488, 0xD079, 0xB489, 0xD07A, 0xB48A, 0xD07B, 0xB48B, 0xD07E, 0xB48C, 0xD07F, + 0xB48D, 0xD080, 0xB48E, 0xD082, 0xB48F, 0xD083, 0xB490, 0xD084, 0xB491, 0xD085, 0xB492, 0xD086, 0xB493, 0xD087, 0xB494, 0xD088, + 0xB495, 0xD089, 0xB496, 0xD08A, 0xB497, 0xD08B, 0xB498, 0xD08C, 0xB499, 0xD08D, 0xB49A, 0xD08E, 0xB49B, 0xD08F, 0xB49C, 0xD090, + 0xB49D, 0xD091, 0xB49E, 0xD092, 0xB49F, 0xD093, 0xB4A0, 0xD094, 0xB4A1, 0xB1DF, 0xB4A2, 0xB1E8, 0xB4A3, 0xB1E9, 0xB4A4, 0xB1EC, + 0xB4A5, 0xB1F0, 0xB4A6, 0xB1F9, 0xB4A7, 0xB1FB, 0xB4A8, 0xB1FD, 0xB4A9, 0xB204, 0xB4AA, 0xB205, 0xB4AB, 0xB208, 0xB4AC, 0xB20B, + 0xB4AD, 0xB20C, 0xB4AE, 0xB214, 0xB4AF, 0xB215, 0xB4B0, 0xB217, 0xB4B1, 0xB219, 0xB4B2, 0xB220, 0xB4B3, 0xB234, 0xB4B4, 0xB23C, + 0xB4B5, 0xB258, 0xB4B6, 0xB25C, 0xB4B7, 0xB260, 0xB4B8, 0xB268, 0xB4B9, 0xB269, 0xB4BA, 0xB274, 0xB4BB, 0xB275, 0xB4BC, 0xB27C, + 0xB4BD, 0xB284, 0xB4BE, 0xB285, 0xB4BF, 0xB289, 0xB4C0, 0xB290, 0xB4C1, 0xB291, 0xB4C2, 0xB294, 0xB4C3, 0xB298, 0xB4C4, 0xB299, + 0xB4C5, 0xB29A, 0xB4C6, 0xB2A0, 0xB4C7, 0xB2A1, 0xB4C8, 0xB2A3, 0xB4C9, 0xB2A5, 0xB4CA, 0xB2A6, 0xB4CB, 0xB2AA, 0xB4CC, 0xB2AC, + 0xB4CD, 0xB2B0, 0xB4CE, 0xB2B4, 0xB4CF, 0xB2C8, 0xB4D0, 0xB2C9, 0xB4D1, 0xB2CC, 0xB4D2, 0xB2D0, 0xB4D3, 0xB2D2, 0xB4D4, 0xB2D8, + 0xB4D5, 0xB2D9, 0xB4D6, 0xB2DB, 0xB4D7, 0xB2DD, 0xB4D8, 0xB2E2, 0xB4D9, 0xB2E4, 0xB4DA, 0xB2E5, 0xB4DB, 0xB2E6, 0xB4DC, 0xB2E8, + 0xB4DD, 0xB2EB, 0xB4DE, 0xB2EC, 0xB4DF, 0xB2ED, 0xB4E0, 0xB2EE, 0xB4E1, 0xB2EF, 0xB4E2, 0xB2F3, 0xB4E3, 0xB2F4, 0xB4E4, 0xB2F5, + 0xB4E5, 0xB2F7, 0xB4E6, 0xB2F8, 0xB4E7, 0xB2F9, 0xB4E8, 0xB2FA, 0xB4E9, 0xB2FB, 0xB4EA, 0xB2FF, 0xB4EB, 0xB300, 0xB4EC, 0xB301, + 0xB4ED, 0xB304, 0xB4EE, 0xB308, 0xB4EF, 0xB310, 0xB4F0, 0xB311, 0xB4F1, 0xB313, 0xB4F2, 0xB314, 0xB4F3, 0xB315, 0xB4F4, 0xB31C, + 0xB4F5, 0xB354, 0xB4F6, 0xB355, 0xB4F7, 0xB356, 0xB4F8, 0xB358, 0xB4F9, 0xB35B, 0xB4FA, 0xB35C, 0xB4FB, 0xB35E, 0xB4FC, 0xB35F, + 0xB4FD, 0xB364, 0xB4FE, 0xB365, 0xB541, 0xD095, 0xB542, 0xD096, 0xB543, 0xD097, 0xB544, 0xD098, 0xB545, 0xD099, 0xB546, 0xD09A, + 0xB547, 0xD09B, 0xB548, 0xD09C, 0xB549, 0xD09D, 0xB54A, 0xD09E, 0xB54B, 0xD09F, 0xB54C, 0xD0A0, 0xB54D, 0xD0A1, 0xB54E, 0xD0A2, + 0xB54F, 0xD0A3, 0xB550, 0xD0A6, 0xB551, 0xD0A7, 0xB552, 0xD0A9, 0xB553, 0xD0AA, 0xB554, 0xD0AB, 0xB555, 0xD0AD, 0xB556, 0xD0AE, + 0xB557, 0xD0AF, 0xB558, 0xD0B0, 0xB559, 0xD0B1, 0xB55A, 0xD0B2, 0xB561, 0xD0B3, 0xB562, 0xD0B6, 0xB563, 0xD0B8, 0xB564, 0xD0BA, + 0xB565, 0xD0BB, 0xB566, 0xD0BC, 0xB567, 0xD0BD, 0xB568, 0xD0BE, 0xB569, 0xD0BF, 0xB56A, 0xD0C2, 0xB56B, 0xD0C3, 0xB56C, 0xD0C5, + 0xB56D, 0xD0C6, 0xB56E, 0xD0C7, 0xB56F, 0xD0CA, 0xB570, 0xD0CB, 0xB571, 0xD0CC, 0xB572, 0xD0CD, 0xB573, 0xD0CE, 0xB574, 0xD0CF, + 0xB575, 0xD0D2, 0xB576, 0xD0D6, 0xB577, 0xD0D7, 0xB578, 0xD0D8, 0xB579, 0xD0D9, 0xB57A, 0xD0DA, 0xB581, 0xD0DB, 0xB582, 0xD0DE, + 0xB583, 0xD0DF, 0xB584, 0xD0E1, 0xB585, 0xD0E2, 0xB586, 0xD0E3, 0xB587, 0xD0E5, 0xB588, 0xD0E6, 0xB589, 0xD0E7, 0xB58A, 0xD0E8, + 0xB58B, 0xD0E9, 0xB58C, 0xD0EA, 0xB58D, 0xD0EB, 0xB58E, 0xD0EE, 0xB58F, 0xD0F2, 0xB590, 0xD0F3, 0xB591, 0xD0F4, 0xB592, 0xD0F5, + 0xB593, 0xD0F6, 0xB594, 0xD0F7, 0xB595, 0xD0F9, 0xB596, 0xD0FA, 0xB597, 0xD0FB, 0xB598, 0xD0FC, 0xB599, 0xD0FD, 0xB59A, 0xD0FE, + 0xB59B, 0xD0FF, 0xB59C, 0xD100, 0xB59D, 0xD101, 0xB59E, 0xD102, 0xB59F, 0xD103, 0xB5A0, 0xD104, 0xB5A1, 0xB367, 0xB5A2, 0xB369, + 0xB5A3, 0xB36B, 0xB5A4, 0xB36E, 0xB5A5, 0xB370, 0xB5A6, 0xB371, 0xB5A7, 0xB374, 0xB5A8, 0xB378, 0xB5A9, 0xB380, 0xB5AA, 0xB381, + 0xB5AB, 0xB383, 0xB5AC, 0xB384, 0xB5AD, 0xB385, 0xB5AE, 0xB38C, 0xB5AF, 0xB390, 0xB5B0, 0xB394, 0xB5B1, 0xB3A0, 0xB5B2, 0xB3A1, + 0xB5B3, 0xB3A8, 0xB5B4, 0xB3AC, 0xB5B5, 0xB3C4, 0xB5B6, 0xB3C5, 0xB5B7, 0xB3C8, 0xB5B8, 0xB3CB, 0xB5B9, 0xB3CC, 0xB5BA, 0xB3CE, + 0xB5BB, 0xB3D0, 0xB5BC, 0xB3D4, 0xB5BD, 0xB3D5, 0xB5BE, 0xB3D7, 0xB5BF, 0xB3D9, 0xB5C0, 0xB3DB, 0xB5C1, 0xB3DD, 0xB5C2, 0xB3E0, + 0xB5C3, 0xB3E4, 0xB5C4, 0xB3E8, 0xB5C5, 0xB3FC, 0xB5C6, 0xB410, 0xB5C7, 0xB418, 0xB5C8, 0xB41C, 0xB5C9, 0xB420, 0xB5CA, 0xB428, + 0xB5CB, 0xB429, 0xB5CC, 0xB42B, 0xB5CD, 0xB434, 0xB5CE, 0xB450, 0xB5CF, 0xB451, 0xB5D0, 0xB454, 0xB5D1, 0xB458, 0xB5D2, 0xB460, + 0xB5D3, 0xB461, 0xB5D4, 0xB463, 0xB5D5, 0xB465, 0xB5D6, 0xB46C, 0xB5D7, 0xB480, 0xB5D8, 0xB488, 0xB5D9, 0xB49D, 0xB5DA, 0xB4A4, + 0xB5DB, 0xB4A8, 0xB5DC, 0xB4AC, 0xB5DD, 0xB4B5, 0xB5DE, 0xB4B7, 0xB5DF, 0xB4B9, 0xB5E0, 0xB4C0, 0xB5E1, 0xB4C4, 0xB5E2, 0xB4C8, + 0xB5E3, 0xB4D0, 0xB5E4, 0xB4D5, 0xB5E5, 0xB4DC, 0xB5E6, 0xB4DD, 0xB5E7, 0xB4E0, 0xB5E8, 0xB4E3, 0xB5E9, 0xB4E4, 0xB5EA, 0xB4E6, + 0xB5EB, 0xB4EC, 0xB5EC, 0xB4ED, 0xB5ED, 0xB4EF, 0xB5EE, 0xB4F1, 0xB5EF, 0xB4F8, 0xB5F0, 0xB514, 0xB5F1, 0xB515, 0xB5F2, 0xB518, + 0xB5F3, 0xB51B, 0xB5F4, 0xB51C, 0xB5F5, 0xB524, 0xB5F6, 0xB525, 0xB5F7, 0xB527, 0xB5F8, 0xB528, 0xB5F9, 0xB529, 0xB5FA, 0xB52A, + 0xB5FB, 0xB530, 0xB5FC, 0xB531, 0xB5FD, 0xB534, 0xB5FE, 0xB538, 0xB641, 0xD105, 0xB642, 0xD106, 0xB643, 0xD107, 0xB644, 0xD108, + 0xB645, 0xD109, 0xB646, 0xD10A, 0xB647, 0xD10B, 0xB648, 0xD10C, 0xB649, 0xD10E, 0xB64A, 0xD10F, 0xB64B, 0xD110, 0xB64C, 0xD111, + 0xB64D, 0xD112, 0xB64E, 0xD113, 0xB64F, 0xD114, 0xB650, 0xD115, 0xB651, 0xD116, 0xB652, 0xD117, 0xB653, 0xD118, 0xB654, 0xD119, + 0xB655, 0xD11A, 0xB656, 0xD11B, 0xB657, 0xD11C, 0xB658, 0xD11D, 0xB659, 0xD11E, 0xB65A, 0xD11F, 0xB661, 0xD120, 0xB662, 0xD121, + 0xB663, 0xD122, 0xB664, 0xD123, 0xB665, 0xD124, 0xB666, 0xD125, 0xB667, 0xD126, 0xB668, 0xD127, 0xB669, 0xD128, 0xB66A, 0xD129, + 0xB66B, 0xD12A, 0xB66C, 0xD12B, 0xB66D, 0xD12C, 0xB66E, 0xD12D, 0xB66F, 0xD12E, 0xB670, 0xD12F, 0xB671, 0xD132, 0xB672, 0xD133, + 0xB673, 0xD135, 0xB674, 0xD136, 0xB675, 0xD137, 0xB676, 0xD139, 0xB677, 0xD13B, 0xB678, 0xD13C, 0xB679, 0xD13D, 0xB67A, 0xD13E, + 0xB681, 0xD13F, 0xB682, 0xD142, 0xB683, 0xD146, 0xB684, 0xD147, 0xB685, 0xD148, 0xB686, 0xD149, 0xB687, 0xD14A, 0xB688, 0xD14B, + 0xB689, 0xD14E, 0xB68A, 0xD14F, 0xB68B, 0xD151, 0xB68C, 0xD152, 0xB68D, 0xD153, 0xB68E, 0xD155, 0xB68F, 0xD156, 0xB690, 0xD157, + 0xB691, 0xD158, 0xB692, 0xD159, 0xB693, 0xD15A, 0xB694, 0xD15B, 0xB695, 0xD15E, 0xB696, 0xD160, 0xB697, 0xD162, 0xB698, 0xD163, + 0xB699, 0xD164, 0xB69A, 0xD165, 0xB69B, 0xD166, 0xB69C, 0xD167, 0xB69D, 0xD169, 0xB69E, 0xD16A, 0xB69F, 0xD16B, 0xB6A0, 0xD16D, + 0xB6A1, 0xB540, 0xB6A2, 0xB541, 0xB6A3, 0xB543, 0xB6A4, 0xB544, 0xB6A5, 0xB545, 0xB6A6, 0xB54B, 0xB6A7, 0xB54C, 0xB6A8, 0xB54D, + 0xB6A9, 0xB550, 0xB6AA, 0xB554, 0xB6AB, 0xB55C, 0xB6AC, 0xB55D, 0xB6AD, 0xB55F, 0xB6AE, 0xB560, 0xB6AF, 0xB561, 0xB6B0, 0xB5A0, + 0xB6B1, 0xB5A1, 0xB6B2, 0xB5A4, 0xB6B3, 0xB5A8, 0xB6B4, 0xB5AA, 0xB6B5, 0xB5AB, 0xB6B6, 0xB5B0, 0xB6B7, 0xB5B1, 0xB6B8, 0xB5B3, + 0xB6B9, 0xB5B4, 0xB6BA, 0xB5B5, 0xB6BB, 0xB5BB, 0xB6BC, 0xB5BC, 0xB6BD, 0xB5BD, 0xB6BE, 0xB5C0, 0xB6BF, 0xB5C4, 0xB6C0, 0xB5CC, + 0xB6C1, 0xB5CD, 0xB6C2, 0xB5CF, 0xB6C3, 0xB5D0, 0xB6C4, 0xB5D1, 0xB6C5, 0xB5D8, 0xB6C6, 0xB5EC, 0xB6C7, 0xB610, 0xB6C8, 0xB611, + 0xB6C9, 0xB614, 0xB6CA, 0xB618, 0xB6CB, 0xB625, 0xB6CC, 0xB62C, 0xB6CD, 0xB634, 0xB6CE, 0xB648, 0xB6CF, 0xB664, 0xB6D0, 0xB668, + 0xB6D1, 0xB69C, 0xB6D2, 0xB69D, 0xB6D3, 0xB6A0, 0xB6D4, 0xB6A4, 0xB6D5, 0xB6AB, 0xB6D6, 0xB6AC, 0xB6D7, 0xB6B1, 0xB6D8, 0xB6D4, + 0xB6D9, 0xB6F0, 0xB6DA, 0xB6F4, 0xB6DB, 0xB6F8, 0xB6DC, 0xB700, 0xB6DD, 0xB701, 0xB6DE, 0xB705, 0xB6DF, 0xB728, 0xB6E0, 0xB729, + 0xB6E1, 0xB72C, 0xB6E2, 0xB72F, 0xB6E3, 0xB730, 0xB6E4, 0xB738, 0xB6E5, 0xB739, 0xB6E6, 0xB73B, 0xB6E7, 0xB744, 0xB6E8, 0xB748, + 0xB6E9, 0xB74C, 0xB6EA, 0xB754, 0xB6EB, 0xB755, 0xB6EC, 0xB760, 0xB6ED, 0xB764, 0xB6EE, 0xB768, 0xB6EF, 0xB770, 0xB6F0, 0xB771, + 0xB6F1, 0xB773, 0xB6F2, 0xB775, 0xB6F3, 0xB77C, 0xB6F4, 0xB77D, 0xB6F5, 0xB780, 0xB6F6, 0xB784, 0xB6F7, 0xB78C, 0xB6F8, 0xB78D, + 0xB6F9, 0xB78F, 0xB6FA, 0xB790, 0xB6FB, 0xB791, 0xB6FC, 0xB792, 0xB6FD, 0xB796, 0xB6FE, 0xB797, 0xB741, 0xD16E, 0xB742, 0xD16F, + 0xB743, 0xD170, 0xB744, 0xD171, 0xB745, 0xD172, 0xB746, 0xD173, 0xB747, 0xD174, 0xB748, 0xD175, 0xB749, 0xD176, 0xB74A, 0xD177, + 0xB74B, 0xD178, 0xB74C, 0xD179, 0xB74D, 0xD17A, 0xB74E, 0xD17B, 0xB74F, 0xD17D, 0xB750, 0xD17E, 0xB751, 0xD17F, 0xB752, 0xD180, + 0xB753, 0xD181, 0xB754, 0xD182, 0xB755, 0xD183, 0xB756, 0xD185, 0xB757, 0xD186, 0xB758, 0xD187, 0xB759, 0xD189, 0xB75A, 0xD18A, + 0xB761, 0xD18B, 0xB762, 0xD18C, 0xB763, 0xD18D, 0xB764, 0xD18E, 0xB765, 0xD18F, 0xB766, 0xD190, 0xB767, 0xD191, 0xB768, 0xD192, + 0xB769, 0xD193, 0xB76A, 0xD194, 0xB76B, 0xD195, 0xB76C, 0xD196, 0xB76D, 0xD197, 0xB76E, 0xD198, 0xB76F, 0xD199, 0xB770, 0xD19A, + 0xB771, 0xD19B, 0xB772, 0xD19C, 0xB773, 0xD19D, 0xB774, 0xD19E, 0xB775, 0xD19F, 0xB776, 0xD1A2, 0xB777, 0xD1A3, 0xB778, 0xD1A5, + 0xB779, 0xD1A6, 0xB77A, 0xD1A7, 0xB781, 0xD1A9, 0xB782, 0xD1AA, 0xB783, 0xD1AB, 0xB784, 0xD1AC, 0xB785, 0xD1AD, 0xB786, 0xD1AE, + 0xB787, 0xD1AF, 0xB788, 0xD1B2, 0xB789, 0xD1B4, 0xB78A, 0xD1B6, 0xB78B, 0xD1B7, 0xB78C, 0xD1B8, 0xB78D, 0xD1B9, 0xB78E, 0xD1BB, + 0xB78F, 0xD1BD, 0xB790, 0xD1BE, 0xB791, 0xD1BF, 0xB792, 0xD1C1, 0xB793, 0xD1C2, 0xB794, 0xD1C3, 0xB795, 0xD1C4, 0xB796, 0xD1C5, + 0xB797, 0xD1C6, 0xB798, 0xD1C7, 0xB799, 0xD1C8, 0xB79A, 0xD1C9, 0xB79B, 0xD1CA, 0xB79C, 0xD1CB, 0xB79D, 0xD1CC, 0xB79E, 0xD1CD, + 0xB79F, 0xD1CE, 0xB7A0, 0xD1CF, 0xB7A1, 0xB798, 0xB7A2, 0xB799, 0xB7A3, 0xB79C, 0xB7A4, 0xB7A0, 0xB7A5, 0xB7A8, 0xB7A6, 0xB7A9, + 0xB7A7, 0xB7AB, 0xB7A8, 0xB7AC, 0xB7A9, 0xB7AD, 0xB7AA, 0xB7B4, 0xB7AB, 0xB7B5, 0xB7AC, 0xB7B8, 0xB7AD, 0xB7C7, 0xB7AE, 0xB7C9, + 0xB7AF, 0xB7EC, 0xB7B0, 0xB7ED, 0xB7B1, 0xB7F0, 0xB7B2, 0xB7F4, 0xB7B3, 0xB7FC, 0xB7B4, 0xB7FD, 0xB7B5, 0xB7FF, 0xB7B6, 0xB800, + 0xB7B7, 0xB801, 0xB7B8, 0xB807, 0xB7B9, 0xB808, 0xB7BA, 0xB809, 0xB7BB, 0xB80C, 0xB7BC, 0xB810, 0xB7BD, 0xB818, 0xB7BE, 0xB819, + 0xB7BF, 0xB81B, 0xB7C0, 0xB81D, 0xB7C1, 0xB824, 0xB7C2, 0xB825, 0xB7C3, 0xB828, 0xB7C4, 0xB82C, 0xB7C5, 0xB834, 0xB7C6, 0xB835, + 0xB7C7, 0xB837, 0xB7C8, 0xB838, 0xB7C9, 0xB839, 0xB7CA, 0xB840, 0xB7CB, 0xB844, 0xB7CC, 0xB851, 0xB7CD, 0xB853, 0xB7CE, 0xB85C, + 0xB7CF, 0xB85D, 0xB7D0, 0xB860, 0xB7D1, 0xB864, 0xB7D2, 0xB86C, 0xB7D3, 0xB86D, 0xB7D4, 0xB86F, 0xB7D5, 0xB871, 0xB7D6, 0xB878, + 0xB7D7, 0xB87C, 0xB7D8, 0xB88D, 0xB7D9, 0xB8A8, 0xB7DA, 0xB8B0, 0xB7DB, 0xB8B4, 0xB7DC, 0xB8B8, 0xB7DD, 0xB8C0, 0xB7DE, 0xB8C1, + 0xB7DF, 0xB8C3, 0xB7E0, 0xB8C5, 0xB7E1, 0xB8CC, 0xB7E2, 0xB8D0, 0xB7E3, 0xB8D4, 0xB7E4, 0xB8DD, 0xB7E5, 0xB8DF, 0xB7E6, 0xB8E1, + 0xB7E7, 0xB8E8, 0xB7E8, 0xB8E9, 0xB7E9, 0xB8EC, 0xB7EA, 0xB8F0, 0xB7EB, 0xB8F8, 0xB7EC, 0xB8F9, 0xB7ED, 0xB8FB, 0xB7EE, 0xB8FD, + 0xB7EF, 0xB904, 0xB7F0, 0xB918, 0xB7F1, 0xB920, 0xB7F2, 0xB93C, 0xB7F3, 0xB93D, 0xB7F4, 0xB940, 0xB7F5, 0xB944, 0xB7F6, 0xB94C, + 0xB7F7, 0xB94F, 0xB7F8, 0xB951, 0xB7F9, 0xB958, 0xB7FA, 0xB959, 0xB7FB, 0xB95C, 0xB7FC, 0xB960, 0xB7FD, 0xB968, 0xB7FE, 0xB969, + 0xB841, 0xD1D0, 0xB842, 0xD1D1, 0xB843, 0xD1D2, 0xB844, 0xD1D3, 0xB845, 0xD1D4, 0xB846, 0xD1D5, 0xB847, 0xD1D6, 0xB848, 0xD1D7, + 0xB849, 0xD1D9, 0xB84A, 0xD1DA, 0xB84B, 0xD1DB, 0xB84C, 0xD1DC, 0xB84D, 0xD1DD, 0xB84E, 0xD1DE, 0xB84F, 0xD1DF, 0xB850, 0xD1E0, + 0xB851, 0xD1E1, 0xB852, 0xD1E2, 0xB853, 0xD1E3, 0xB854, 0xD1E4, 0xB855, 0xD1E5, 0xB856, 0xD1E6, 0xB857, 0xD1E7, 0xB858, 0xD1E8, + 0xB859, 0xD1E9, 0xB85A, 0xD1EA, 0xB861, 0xD1EB, 0xB862, 0xD1EC, 0xB863, 0xD1ED, 0xB864, 0xD1EE, 0xB865, 0xD1EF, 0xB866, 0xD1F0, + 0xB867, 0xD1F1, 0xB868, 0xD1F2, 0xB869, 0xD1F3, 0xB86A, 0xD1F5, 0xB86B, 0xD1F6, 0xB86C, 0xD1F7, 0xB86D, 0xD1F9, 0xB86E, 0xD1FA, + 0xB86F, 0xD1FB, 0xB870, 0xD1FC, 0xB871, 0xD1FD, 0xB872, 0xD1FE, 0xB873, 0xD1FF, 0xB874, 0xD200, 0xB875, 0xD201, 0xB876, 0xD202, + 0xB877, 0xD203, 0xB878, 0xD204, 0xB879, 0xD205, 0xB87A, 0xD206, 0xB881, 0xD208, 0xB882, 0xD20A, 0xB883, 0xD20B, 0xB884, 0xD20C, + 0xB885, 0xD20D, 0xB886, 0xD20E, 0xB887, 0xD20F, 0xB888, 0xD211, 0xB889, 0xD212, 0xB88A, 0xD213, 0xB88B, 0xD214, 0xB88C, 0xD215, + 0xB88D, 0xD216, 0xB88E, 0xD217, 0xB88F, 0xD218, 0xB890, 0xD219, 0xB891, 0xD21A, 0xB892, 0xD21B, 0xB893, 0xD21C, 0xB894, 0xD21D, + 0xB895, 0xD21E, 0xB896, 0xD21F, 0xB897, 0xD220, 0xB898, 0xD221, 0xB899, 0xD222, 0xB89A, 0xD223, 0xB89B, 0xD224, 0xB89C, 0xD225, + 0xB89D, 0xD226, 0xB89E, 0xD227, 0xB89F, 0xD228, 0xB8A0, 0xD229, 0xB8A1, 0xB96B, 0xB8A2, 0xB96D, 0xB8A3, 0xB974, 0xB8A4, 0xB975, + 0xB8A5, 0xB978, 0xB8A6, 0xB97C, 0xB8A7, 0xB984, 0xB8A8, 0xB985, 0xB8A9, 0xB987, 0xB8AA, 0xB989, 0xB8AB, 0xB98A, 0xB8AC, 0xB98D, + 0xB8AD, 0xB98E, 0xB8AE, 0xB9AC, 0xB8AF, 0xB9AD, 0xB8B0, 0xB9B0, 0xB8B1, 0xB9B4, 0xB8B2, 0xB9BC, 0xB8B3, 0xB9BD, 0xB8B4, 0xB9BF, + 0xB8B5, 0xB9C1, 0xB8B6, 0xB9C8, 0xB8B7, 0xB9C9, 0xB8B8, 0xB9CC, 0xB8B9, 0xB9CE, 0xB8BA, 0xB9CF, 0xB8BB, 0xB9D0, 0xB8BC, 0xB9D1, + 0xB8BD, 0xB9D2, 0xB8BE, 0xB9D8, 0xB8BF, 0xB9D9, 0xB8C0, 0xB9DB, 0xB8C1, 0xB9DD, 0xB8C2, 0xB9DE, 0xB8C3, 0xB9E1, 0xB8C4, 0xB9E3, + 0xB8C5, 0xB9E4, 0xB8C6, 0xB9E5, 0xB8C7, 0xB9E8, 0xB8C8, 0xB9EC, 0xB8C9, 0xB9F4, 0xB8CA, 0xB9F5, 0xB8CB, 0xB9F7, 0xB8CC, 0xB9F8, + 0xB8CD, 0xB9F9, 0xB8CE, 0xB9FA, 0xB8CF, 0xBA00, 0xB8D0, 0xBA01, 0xB8D1, 0xBA08, 0xB8D2, 0xBA15, 0xB8D3, 0xBA38, 0xB8D4, 0xBA39, + 0xB8D5, 0xBA3C, 0xB8D6, 0xBA40, 0xB8D7, 0xBA42, 0xB8D8, 0xBA48, 0xB8D9, 0xBA49, 0xB8DA, 0xBA4B, 0xB8DB, 0xBA4D, 0xB8DC, 0xBA4E, + 0xB8DD, 0xBA53, 0xB8DE, 0xBA54, 0xB8DF, 0xBA55, 0xB8E0, 0xBA58, 0xB8E1, 0xBA5C, 0xB8E2, 0xBA64, 0xB8E3, 0xBA65, 0xB8E4, 0xBA67, + 0xB8E5, 0xBA68, 0xB8E6, 0xBA69, 0xB8E7, 0xBA70, 0xB8E8, 0xBA71, 0xB8E9, 0xBA74, 0xB8EA, 0xBA78, 0xB8EB, 0xBA83, 0xB8EC, 0xBA84, + 0xB8ED, 0xBA85, 0xB8EE, 0xBA87, 0xB8EF, 0xBA8C, 0xB8F0, 0xBAA8, 0xB8F1, 0xBAA9, 0xB8F2, 0xBAAB, 0xB8F3, 0xBAAC, 0xB8F4, 0xBAB0, + 0xB8F5, 0xBAB2, 0xB8F6, 0xBAB8, 0xB8F7, 0xBAB9, 0xB8F8, 0xBABB, 0xB8F9, 0xBABD, 0xB8FA, 0xBAC4, 0xB8FB, 0xBAC8, 0xB8FC, 0xBAD8, + 0xB8FD, 0xBAD9, 0xB8FE, 0xBAFC, 0xB941, 0xD22A, 0xB942, 0xD22B, 0xB943, 0xD22E, 0xB944, 0xD22F, 0xB945, 0xD231, 0xB946, 0xD232, + 0xB947, 0xD233, 0xB948, 0xD235, 0xB949, 0xD236, 0xB94A, 0xD237, 0xB94B, 0xD238, 0xB94C, 0xD239, 0xB94D, 0xD23A, 0xB94E, 0xD23B, + 0xB94F, 0xD23E, 0xB950, 0xD240, 0xB951, 0xD242, 0xB952, 0xD243, 0xB953, 0xD244, 0xB954, 0xD245, 0xB955, 0xD246, 0xB956, 0xD247, + 0xB957, 0xD249, 0xB958, 0xD24A, 0xB959, 0xD24B, 0xB95A, 0xD24C, 0xB961, 0xD24D, 0xB962, 0xD24E, 0xB963, 0xD24F, 0xB964, 0xD250, + 0xB965, 0xD251, 0xB966, 0xD252, 0xB967, 0xD253, 0xB968, 0xD254, 0xB969, 0xD255, 0xB96A, 0xD256, 0xB96B, 0xD257, 0xB96C, 0xD258, + 0xB96D, 0xD259, 0xB96E, 0xD25A, 0xB96F, 0xD25B, 0xB970, 0xD25D, 0xB971, 0xD25E, 0xB972, 0xD25F, 0xB973, 0xD260, 0xB974, 0xD261, + 0xB975, 0xD262, 0xB976, 0xD263, 0xB977, 0xD265, 0xB978, 0xD266, 0xB979, 0xD267, 0xB97A, 0xD268, 0xB981, 0xD269, 0xB982, 0xD26A, + 0xB983, 0xD26B, 0xB984, 0xD26C, 0xB985, 0xD26D, 0xB986, 0xD26E, 0xB987, 0xD26F, 0xB988, 0xD270, 0xB989, 0xD271, 0xB98A, 0xD272, + 0xB98B, 0xD273, 0xB98C, 0xD274, 0xB98D, 0xD275, 0xB98E, 0xD276, 0xB98F, 0xD277, 0xB990, 0xD278, 0xB991, 0xD279, 0xB992, 0xD27A, + 0xB993, 0xD27B, 0xB994, 0xD27C, 0xB995, 0xD27D, 0xB996, 0xD27E, 0xB997, 0xD27F, 0xB998, 0xD282, 0xB999, 0xD283, 0xB99A, 0xD285, + 0xB99B, 0xD286, 0xB99C, 0xD287, 0xB99D, 0xD289, 0xB99E, 0xD28A, 0xB99F, 0xD28B, 0xB9A0, 0xD28C, 0xB9A1, 0xBB00, 0xB9A2, 0xBB04, + 0xB9A3, 0xBB0D, 0xB9A4, 0xBB0F, 0xB9A5, 0xBB11, 0xB9A6, 0xBB18, 0xB9A7, 0xBB1C, 0xB9A8, 0xBB20, 0xB9A9, 0xBB29, 0xB9AA, 0xBB2B, + 0xB9AB, 0xBB34, 0xB9AC, 0xBB35, 0xB9AD, 0xBB36, 0xB9AE, 0xBB38, 0xB9AF, 0xBB3B, 0xB9B0, 0xBB3C, 0xB9B1, 0xBB3D, 0xB9B2, 0xBB3E, + 0xB9B3, 0xBB44, 0xB9B4, 0xBB45, 0xB9B5, 0xBB47, 0xB9B6, 0xBB49, 0xB9B7, 0xBB4D, 0xB9B8, 0xBB4F, 0xB9B9, 0xBB50, 0xB9BA, 0xBB54, + 0xB9BB, 0xBB58, 0xB9BC, 0xBB61, 0xB9BD, 0xBB63, 0xB9BE, 0xBB6C, 0xB9BF, 0xBB88, 0xB9C0, 0xBB8C, 0xB9C1, 0xBB90, 0xB9C2, 0xBBA4, + 0xB9C3, 0xBBA8, 0xB9C4, 0xBBAC, 0xB9C5, 0xBBB4, 0xB9C6, 0xBBB7, 0xB9C7, 0xBBC0, 0xB9C8, 0xBBC4, 0xB9C9, 0xBBC8, 0xB9CA, 0xBBD0, + 0xB9CB, 0xBBD3, 0xB9CC, 0xBBF8, 0xB9CD, 0xBBF9, 0xB9CE, 0xBBFC, 0xB9CF, 0xBBFF, 0xB9D0, 0xBC00, 0xB9D1, 0xBC02, 0xB9D2, 0xBC08, + 0xB9D3, 0xBC09, 0xB9D4, 0xBC0B, 0xB9D5, 0xBC0C, 0xB9D6, 0xBC0D, 0xB9D7, 0xBC0F, 0xB9D8, 0xBC11, 0xB9D9, 0xBC14, 0xB9DA, 0xBC15, + 0xB9DB, 0xBC16, 0xB9DC, 0xBC17, 0xB9DD, 0xBC18, 0xB9DE, 0xBC1B, 0xB9DF, 0xBC1C, 0xB9E0, 0xBC1D, 0xB9E1, 0xBC1E, 0xB9E2, 0xBC1F, + 0xB9E3, 0xBC24, 0xB9E4, 0xBC25, 0xB9E5, 0xBC27, 0xB9E6, 0xBC29, 0xB9E7, 0xBC2D, 0xB9E8, 0xBC30, 0xB9E9, 0xBC31, 0xB9EA, 0xBC34, + 0xB9EB, 0xBC38, 0xB9EC, 0xBC40, 0xB9ED, 0xBC41, 0xB9EE, 0xBC43, 0xB9EF, 0xBC44, 0xB9F0, 0xBC45, 0xB9F1, 0xBC49, 0xB9F2, 0xBC4C, + 0xB9F3, 0xBC4D, 0xB9F4, 0xBC50, 0xB9F5, 0xBC5D, 0xB9F6, 0xBC84, 0xB9F7, 0xBC85, 0xB9F8, 0xBC88, 0xB9F9, 0xBC8B, 0xB9FA, 0xBC8C, + 0xB9FB, 0xBC8E, 0xB9FC, 0xBC94, 0xB9FD, 0xBC95, 0xB9FE, 0xBC97, 0xBA41, 0xD28D, 0xBA42, 0xD28E, 0xBA43, 0xD28F, 0xBA44, 0xD292, + 0xBA45, 0xD293, 0xBA46, 0xD294, 0xBA47, 0xD296, 0xBA48, 0xD297, 0xBA49, 0xD298, 0xBA4A, 0xD299, 0xBA4B, 0xD29A, 0xBA4C, 0xD29B, + 0xBA4D, 0xD29D, 0xBA4E, 0xD29E, 0xBA4F, 0xD29F, 0xBA50, 0xD2A1, 0xBA51, 0xD2A2, 0xBA52, 0xD2A3, 0xBA53, 0xD2A5, 0xBA54, 0xD2A6, + 0xBA55, 0xD2A7, 0xBA56, 0xD2A8, 0xBA57, 0xD2A9, 0xBA58, 0xD2AA, 0xBA59, 0xD2AB, 0xBA5A, 0xD2AD, 0xBA61, 0xD2AE, 0xBA62, 0xD2AF, + 0xBA63, 0xD2B0, 0xBA64, 0xD2B2, 0xBA65, 0xD2B3, 0xBA66, 0xD2B4, 0xBA67, 0xD2B5, 0xBA68, 0xD2B6, 0xBA69, 0xD2B7, 0xBA6A, 0xD2BA, + 0xBA6B, 0xD2BB, 0xBA6C, 0xD2BD, 0xBA6D, 0xD2BE, 0xBA6E, 0xD2C1, 0xBA6F, 0xD2C3, 0xBA70, 0xD2C4, 0xBA71, 0xD2C5, 0xBA72, 0xD2C6, + 0xBA73, 0xD2C7, 0xBA74, 0xD2CA, 0xBA75, 0xD2CC, 0xBA76, 0xD2CD, 0xBA77, 0xD2CE, 0xBA78, 0xD2CF, 0xBA79, 0xD2D0, 0xBA7A, 0xD2D1, + 0xBA81, 0xD2D2, 0xBA82, 0xD2D3, 0xBA83, 0xD2D5, 0xBA84, 0xD2D6, 0xBA85, 0xD2D7, 0xBA86, 0xD2D9, 0xBA87, 0xD2DA, 0xBA88, 0xD2DB, + 0xBA89, 0xD2DD, 0xBA8A, 0xD2DE, 0xBA8B, 0xD2DF, 0xBA8C, 0xD2E0, 0xBA8D, 0xD2E1, 0xBA8E, 0xD2E2, 0xBA8F, 0xD2E3, 0xBA90, 0xD2E6, + 0xBA91, 0xD2E7, 0xBA92, 0xD2E8, 0xBA93, 0xD2E9, 0xBA94, 0xD2EA, 0xBA95, 0xD2EB, 0xBA96, 0xD2EC, 0xBA97, 0xD2ED, 0xBA98, 0xD2EE, + 0xBA99, 0xD2EF, 0xBA9A, 0xD2F2, 0xBA9B, 0xD2F3, 0xBA9C, 0xD2F5, 0xBA9D, 0xD2F6, 0xBA9E, 0xD2F7, 0xBA9F, 0xD2F9, 0xBAA0, 0xD2FA, + 0xBAA1, 0xBC99, 0xBAA2, 0xBC9A, 0xBAA3, 0xBCA0, 0xBAA4, 0xBCA1, 0xBAA5, 0xBCA4, 0xBAA6, 0xBCA7, 0xBAA7, 0xBCA8, 0xBAA8, 0xBCB0, + 0xBAA9, 0xBCB1, 0xBAAA, 0xBCB3, 0xBAAB, 0xBCB4, 0xBAAC, 0xBCB5, 0xBAAD, 0xBCBC, 0xBAAE, 0xBCBD, 0xBAAF, 0xBCC0, 0xBAB0, 0xBCC4, + 0xBAB1, 0xBCCD, 0xBAB2, 0xBCCF, 0xBAB3, 0xBCD0, 0xBAB4, 0xBCD1, 0xBAB5, 0xBCD5, 0xBAB6, 0xBCD8, 0xBAB7, 0xBCDC, 0xBAB8, 0xBCF4, + 0xBAB9, 0xBCF5, 0xBABA, 0xBCF6, 0xBABB, 0xBCF8, 0xBABC, 0xBCFC, 0xBABD, 0xBD04, 0xBABE, 0xBD05, 0xBABF, 0xBD07, 0xBAC0, 0xBD09, + 0xBAC1, 0xBD10, 0xBAC2, 0xBD14, 0xBAC3, 0xBD24, 0xBAC4, 0xBD2C, 0xBAC5, 0xBD40, 0xBAC6, 0xBD48, 0xBAC7, 0xBD49, 0xBAC8, 0xBD4C, + 0xBAC9, 0xBD50, 0xBACA, 0xBD58, 0xBACB, 0xBD59, 0xBACC, 0xBD64, 0xBACD, 0xBD68, 0xBACE, 0xBD80, 0xBACF, 0xBD81, 0xBAD0, 0xBD84, + 0xBAD1, 0xBD87, 0xBAD2, 0xBD88, 0xBAD3, 0xBD89, 0xBAD4, 0xBD8A, 0xBAD5, 0xBD90, 0xBAD6, 0xBD91, 0xBAD7, 0xBD93, 0xBAD8, 0xBD95, + 0xBAD9, 0xBD99, 0xBADA, 0xBD9A, 0xBADB, 0xBD9C, 0xBADC, 0xBDA4, 0xBADD, 0xBDB0, 0xBADE, 0xBDB8, 0xBADF, 0xBDD4, 0xBAE0, 0xBDD5, + 0xBAE1, 0xBDD8, 0xBAE2, 0xBDDC, 0xBAE3, 0xBDE9, 0xBAE4, 0xBDF0, 0xBAE5, 0xBDF4, 0xBAE6, 0xBDF8, 0xBAE7, 0xBE00, 0xBAE8, 0xBE03, + 0xBAE9, 0xBE05, 0xBAEA, 0xBE0C, 0xBAEB, 0xBE0D, 0xBAEC, 0xBE10, 0xBAED, 0xBE14, 0xBAEE, 0xBE1C, 0xBAEF, 0xBE1D, 0xBAF0, 0xBE1F, + 0xBAF1, 0xBE44, 0xBAF2, 0xBE45, 0xBAF3, 0xBE48, 0xBAF4, 0xBE4C, 0xBAF5, 0xBE4E, 0xBAF6, 0xBE54, 0xBAF7, 0xBE55, 0xBAF8, 0xBE57, + 0xBAF9, 0xBE59, 0xBAFA, 0xBE5A, 0xBAFB, 0xBE5B, 0xBAFC, 0xBE60, 0xBAFD, 0xBE61, 0xBAFE, 0xBE64, 0xBB41, 0xD2FB, 0xBB42, 0xD2FC, + 0xBB43, 0xD2FD, 0xBB44, 0xD2FE, 0xBB45, 0xD2FF, 0xBB46, 0xD302, 0xBB47, 0xD304, 0xBB48, 0xD306, 0xBB49, 0xD307, 0xBB4A, 0xD308, + 0xBB4B, 0xD309, 0xBB4C, 0xD30A, 0xBB4D, 0xD30B, 0xBB4E, 0xD30F, 0xBB4F, 0xD311, 0xBB50, 0xD312, 0xBB51, 0xD313, 0xBB52, 0xD315, + 0xBB53, 0xD317, 0xBB54, 0xD318, 0xBB55, 0xD319, 0xBB56, 0xD31A, 0xBB57, 0xD31B, 0xBB58, 0xD31E, 0xBB59, 0xD322, 0xBB5A, 0xD323, + 0xBB61, 0xD324, 0xBB62, 0xD326, 0xBB63, 0xD327, 0xBB64, 0xD32A, 0xBB65, 0xD32B, 0xBB66, 0xD32D, 0xBB67, 0xD32E, 0xBB68, 0xD32F, + 0xBB69, 0xD331, 0xBB6A, 0xD332, 0xBB6B, 0xD333, 0xBB6C, 0xD334, 0xBB6D, 0xD335, 0xBB6E, 0xD336, 0xBB6F, 0xD337, 0xBB70, 0xD33A, + 0xBB71, 0xD33E, 0xBB72, 0xD33F, 0xBB73, 0xD340, 0xBB74, 0xD341, 0xBB75, 0xD342, 0xBB76, 0xD343, 0xBB77, 0xD346, 0xBB78, 0xD347, + 0xBB79, 0xD348, 0xBB7A, 0xD349, 0xBB81, 0xD34A, 0xBB82, 0xD34B, 0xBB83, 0xD34C, 0xBB84, 0xD34D, 0xBB85, 0xD34E, 0xBB86, 0xD34F, + 0xBB87, 0xD350, 0xBB88, 0xD351, 0xBB89, 0xD352, 0xBB8A, 0xD353, 0xBB8B, 0xD354, 0xBB8C, 0xD355, 0xBB8D, 0xD356, 0xBB8E, 0xD357, + 0xBB8F, 0xD358, 0xBB90, 0xD359, 0xBB91, 0xD35A, 0xBB92, 0xD35B, 0xBB93, 0xD35C, 0xBB94, 0xD35D, 0xBB95, 0xD35E, 0xBB96, 0xD35F, + 0xBB97, 0xD360, 0xBB98, 0xD361, 0xBB99, 0xD362, 0xBB9A, 0xD363, 0xBB9B, 0xD364, 0xBB9C, 0xD365, 0xBB9D, 0xD366, 0xBB9E, 0xD367, + 0xBB9F, 0xD368, 0xBBA0, 0xD369, 0xBBA1, 0xBE68, 0xBBA2, 0xBE6A, 0xBBA3, 0xBE70, 0xBBA4, 0xBE71, 0xBBA5, 0xBE73, 0xBBA6, 0xBE74, + 0xBBA7, 0xBE75, 0xBBA8, 0xBE7B, 0xBBA9, 0xBE7C, 0xBBAA, 0xBE7D, 0xBBAB, 0xBE80, 0xBBAC, 0xBE84, 0xBBAD, 0xBE8C, 0xBBAE, 0xBE8D, + 0xBBAF, 0xBE8F, 0xBBB0, 0xBE90, 0xBBB1, 0xBE91, 0xBBB2, 0xBE98, 0xBBB3, 0xBE99, 0xBBB4, 0xBEA8, 0xBBB5, 0xBED0, 0xBBB6, 0xBED1, + 0xBBB7, 0xBED4, 0xBBB8, 0xBED7, 0xBBB9, 0xBED8, 0xBBBA, 0xBEE0, 0xBBBB, 0xBEE3, 0xBBBC, 0xBEE4, 0xBBBD, 0xBEE5, 0xBBBE, 0xBEEC, + 0xBBBF, 0xBF01, 0xBBC0, 0xBF08, 0xBBC1, 0xBF09, 0xBBC2, 0xBF18, 0xBBC3, 0xBF19, 0xBBC4, 0xBF1B, 0xBBC5, 0xBF1C, 0xBBC6, 0xBF1D, + 0xBBC7, 0xBF40, 0xBBC8, 0xBF41, 0xBBC9, 0xBF44, 0xBBCA, 0xBF48, 0xBBCB, 0xBF50, 0xBBCC, 0xBF51, 0xBBCD, 0xBF55, 0xBBCE, 0xBF94, + 0xBBCF, 0xBFB0, 0xBBD0, 0xBFC5, 0xBBD1, 0xBFCC, 0xBBD2, 0xBFCD, 0xBBD3, 0xBFD0, 0xBBD4, 0xBFD4, 0xBBD5, 0xBFDC, 0xBBD6, 0xBFDF, + 0xBBD7, 0xBFE1, 0xBBD8, 0xC03C, 0xBBD9, 0xC051, 0xBBDA, 0xC058, 0xBBDB, 0xC05C, 0xBBDC, 0xC060, 0xBBDD, 0xC068, 0xBBDE, 0xC069, + 0xBBDF, 0xC090, 0xBBE0, 0xC091, 0xBBE1, 0xC094, 0xBBE2, 0xC098, 0xBBE3, 0xC0A0, 0xBBE4, 0xC0A1, 0xBBE5, 0xC0A3, 0xBBE6, 0xC0A5, + 0xBBE7, 0xC0AC, 0xBBE8, 0xC0AD, 0xBBE9, 0xC0AF, 0xBBEA, 0xC0B0, 0xBBEB, 0xC0B3, 0xBBEC, 0xC0B4, 0xBBED, 0xC0B5, 0xBBEE, 0xC0B6, + 0xBBEF, 0xC0BC, 0xBBF0, 0xC0BD, 0xBBF1, 0xC0BF, 0xBBF2, 0xC0C0, 0xBBF3, 0xC0C1, 0xBBF4, 0xC0C5, 0xBBF5, 0xC0C8, 0xBBF6, 0xC0C9, + 0xBBF7, 0xC0CC, 0xBBF8, 0xC0D0, 0xBBF9, 0xC0D8, 0xBBFA, 0xC0D9, 0xBBFB, 0xC0DB, 0xBBFC, 0xC0DC, 0xBBFD, 0xC0DD, 0xBBFE, 0xC0E4, + 0xBC41, 0xD36A, 0xBC42, 0xD36B, 0xBC43, 0xD36C, 0xBC44, 0xD36D, 0xBC45, 0xD36E, 0xBC46, 0xD36F, 0xBC47, 0xD370, 0xBC48, 0xD371, + 0xBC49, 0xD372, 0xBC4A, 0xD373, 0xBC4B, 0xD374, 0xBC4C, 0xD375, 0xBC4D, 0xD376, 0xBC4E, 0xD377, 0xBC4F, 0xD378, 0xBC50, 0xD379, + 0xBC51, 0xD37A, 0xBC52, 0xD37B, 0xBC53, 0xD37E, 0xBC54, 0xD37F, 0xBC55, 0xD381, 0xBC56, 0xD382, 0xBC57, 0xD383, 0xBC58, 0xD385, + 0xBC59, 0xD386, 0xBC5A, 0xD387, 0xBC61, 0xD388, 0xBC62, 0xD389, 0xBC63, 0xD38A, 0xBC64, 0xD38B, 0xBC65, 0xD38E, 0xBC66, 0xD392, + 0xBC67, 0xD393, 0xBC68, 0xD394, 0xBC69, 0xD395, 0xBC6A, 0xD396, 0xBC6B, 0xD397, 0xBC6C, 0xD39A, 0xBC6D, 0xD39B, 0xBC6E, 0xD39D, + 0xBC6F, 0xD39E, 0xBC70, 0xD39F, 0xBC71, 0xD3A1, 0xBC72, 0xD3A2, 0xBC73, 0xD3A3, 0xBC74, 0xD3A4, 0xBC75, 0xD3A5, 0xBC76, 0xD3A6, + 0xBC77, 0xD3A7, 0xBC78, 0xD3AA, 0xBC79, 0xD3AC, 0xBC7A, 0xD3AE, 0xBC81, 0xD3AF, 0xBC82, 0xD3B0, 0xBC83, 0xD3B1, 0xBC84, 0xD3B2, + 0xBC85, 0xD3B3, 0xBC86, 0xD3B5, 0xBC87, 0xD3B6, 0xBC88, 0xD3B7, 0xBC89, 0xD3B9, 0xBC8A, 0xD3BA, 0xBC8B, 0xD3BB, 0xBC8C, 0xD3BD, + 0xBC8D, 0xD3BE, 0xBC8E, 0xD3BF, 0xBC8F, 0xD3C0, 0xBC90, 0xD3C1, 0xBC91, 0xD3C2, 0xBC92, 0xD3C3, 0xBC93, 0xD3C6, 0xBC94, 0xD3C7, + 0xBC95, 0xD3CA, 0xBC96, 0xD3CB, 0xBC97, 0xD3CC, 0xBC98, 0xD3CD, 0xBC99, 0xD3CE, 0xBC9A, 0xD3CF, 0xBC9B, 0xD3D1, 0xBC9C, 0xD3D2, + 0xBC9D, 0xD3D3, 0xBC9E, 0xD3D4, 0xBC9F, 0xD3D5, 0xBCA0, 0xD3D6, 0xBCA1, 0xC0E5, 0xBCA2, 0xC0E8, 0xBCA3, 0xC0EC, 0xBCA4, 0xC0F4, + 0xBCA5, 0xC0F5, 0xBCA6, 0xC0F7, 0xBCA7, 0xC0F9, 0xBCA8, 0xC100, 0xBCA9, 0xC104, 0xBCAA, 0xC108, 0xBCAB, 0xC110, 0xBCAC, 0xC115, + 0xBCAD, 0xC11C, 0xBCAE, 0xC11D, 0xBCAF, 0xC11E, 0xBCB0, 0xC11F, 0xBCB1, 0xC120, 0xBCB2, 0xC123, 0xBCB3, 0xC124, 0xBCB4, 0xC126, + 0xBCB5, 0xC127, 0xBCB6, 0xC12C, 0xBCB7, 0xC12D, 0xBCB8, 0xC12F, 0xBCB9, 0xC130, 0xBCBA, 0xC131, 0xBCBB, 0xC136, 0xBCBC, 0xC138, + 0xBCBD, 0xC139, 0xBCBE, 0xC13C, 0xBCBF, 0xC140, 0xBCC0, 0xC148, 0xBCC1, 0xC149, 0xBCC2, 0xC14B, 0xBCC3, 0xC14C, 0xBCC4, 0xC14D, + 0xBCC5, 0xC154, 0xBCC6, 0xC155, 0xBCC7, 0xC158, 0xBCC8, 0xC15C, 0xBCC9, 0xC164, 0xBCCA, 0xC165, 0xBCCB, 0xC167, 0xBCCC, 0xC168, + 0xBCCD, 0xC169, 0xBCCE, 0xC170, 0xBCCF, 0xC174, 0xBCD0, 0xC178, 0xBCD1, 0xC185, 0xBCD2, 0xC18C, 0xBCD3, 0xC18D, 0xBCD4, 0xC18E, + 0xBCD5, 0xC190, 0xBCD6, 0xC194, 0xBCD7, 0xC196, 0xBCD8, 0xC19C, 0xBCD9, 0xC19D, 0xBCDA, 0xC19F, 0xBCDB, 0xC1A1, 0xBCDC, 0xC1A5, + 0xBCDD, 0xC1A8, 0xBCDE, 0xC1A9, 0xBCDF, 0xC1AC, 0xBCE0, 0xC1B0, 0xBCE1, 0xC1BD, 0xBCE2, 0xC1C4, 0xBCE3, 0xC1C8, 0xBCE4, 0xC1CC, + 0xBCE5, 0xC1D4, 0xBCE6, 0xC1D7, 0xBCE7, 0xC1D8, 0xBCE8, 0xC1E0, 0xBCE9, 0xC1E4, 0xBCEA, 0xC1E8, 0xBCEB, 0xC1F0, 0xBCEC, 0xC1F1, + 0xBCED, 0xC1F3, 0xBCEE, 0xC1FC, 0xBCEF, 0xC1FD, 0xBCF0, 0xC200, 0xBCF1, 0xC204, 0xBCF2, 0xC20C, 0xBCF3, 0xC20D, 0xBCF4, 0xC20F, + 0xBCF5, 0xC211, 0xBCF6, 0xC218, 0xBCF7, 0xC219, 0xBCF8, 0xC21C, 0xBCF9, 0xC21F, 0xBCFA, 0xC220, 0xBCFB, 0xC228, 0xBCFC, 0xC229, + 0xBCFD, 0xC22B, 0xBCFE, 0xC22D, 0xBD41, 0xD3D7, 0xBD42, 0xD3D9, 0xBD43, 0xD3DA, 0xBD44, 0xD3DB, 0xBD45, 0xD3DC, 0xBD46, 0xD3DD, + 0xBD47, 0xD3DE, 0xBD48, 0xD3DF, 0xBD49, 0xD3E0, 0xBD4A, 0xD3E2, 0xBD4B, 0xD3E4, 0xBD4C, 0xD3E5, 0xBD4D, 0xD3E6, 0xBD4E, 0xD3E7, + 0xBD4F, 0xD3E8, 0xBD50, 0xD3E9, 0xBD51, 0xD3EA, 0xBD52, 0xD3EB, 0xBD53, 0xD3EE, 0xBD54, 0xD3EF, 0xBD55, 0xD3F1, 0xBD56, 0xD3F2, + 0xBD57, 0xD3F3, 0xBD58, 0xD3F5, 0xBD59, 0xD3F6, 0xBD5A, 0xD3F7, 0xBD61, 0xD3F8, 0xBD62, 0xD3F9, 0xBD63, 0xD3FA, 0xBD64, 0xD3FB, + 0xBD65, 0xD3FE, 0xBD66, 0xD400, 0xBD67, 0xD402, 0xBD68, 0xD403, 0xBD69, 0xD404, 0xBD6A, 0xD405, 0xBD6B, 0xD406, 0xBD6C, 0xD407, + 0xBD6D, 0xD409, 0xBD6E, 0xD40A, 0xBD6F, 0xD40B, 0xBD70, 0xD40C, 0xBD71, 0xD40D, 0xBD72, 0xD40E, 0xBD73, 0xD40F, 0xBD74, 0xD410, + 0xBD75, 0xD411, 0xBD76, 0xD412, 0xBD77, 0xD413, 0xBD78, 0xD414, 0xBD79, 0xD415, 0xBD7A, 0xD416, 0xBD81, 0xD417, 0xBD82, 0xD418, + 0xBD83, 0xD419, 0xBD84, 0xD41A, 0xBD85, 0xD41B, 0xBD86, 0xD41C, 0xBD87, 0xD41E, 0xBD88, 0xD41F, 0xBD89, 0xD420, 0xBD8A, 0xD421, + 0xBD8B, 0xD422, 0xBD8C, 0xD423, 0xBD8D, 0xD424, 0xBD8E, 0xD425, 0xBD8F, 0xD426, 0xBD90, 0xD427, 0xBD91, 0xD428, 0xBD92, 0xD429, + 0xBD93, 0xD42A, 0xBD94, 0xD42B, 0xBD95, 0xD42C, 0xBD96, 0xD42D, 0xBD97, 0xD42E, 0xBD98, 0xD42F, 0xBD99, 0xD430, 0xBD9A, 0xD431, + 0xBD9B, 0xD432, 0xBD9C, 0xD433, 0xBD9D, 0xD434, 0xBD9E, 0xD435, 0xBD9F, 0xD436, 0xBDA0, 0xD437, 0xBDA1, 0xC22F, 0xBDA2, 0xC231, + 0xBDA3, 0xC232, 0xBDA4, 0xC234, 0xBDA5, 0xC248, 0xBDA6, 0xC250, 0xBDA7, 0xC251, 0xBDA8, 0xC254, 0xBDA9, 0xC258, 0xBDAA, 0xC260, + 0xBDAB, 0xC265, 0xBDAC, 0xC26C, 0xBDAD, 0xC26D, 0xBDAE, 0xC270, 0xBDAF, 0xC274, 0xBDB0, 0xC27C, 0xBDB1, 0xC27D, 0xBDB2, 0xC27F, + 0xBDB3, 0xC281, 0xBDB4, 0xC288, 0xBDB5, 0xC289, 0xBDB6, 0xC290, 0xBDB7, 0xC298, 0xBDB8, 0xC29B, 0xBDB9, 0xC29D, 0xBDBA, 0xC2A4, + 0xBDBB, 0xC2A5, 0xBDBC, 0xC2A8, 0xBDBD, 0xC2AC, 0xBDBE, 0xC2AD, 0xBDBF, 0xC2B4, 0xBDC0, 0xC2B5, 0xBDC1, 0xC2B7, 0xBDC2, 0xC2B9, + 0xBDC3, 0xC2DC, 0xBDC4, 0xC2DD, 0xBDC5, 0xC2E0, 0xBDC6, 0xC2E3, 0xBDC7, 0xC2E4, 0xBDC8, 0xC2EB, 0xBDC9, 0xC2EC, 0xBDCA, 0xC2ED, + 0xBDCB, 0xC2EF, 0xBDCC, 0xC2F1, 0xBDCD, 0xC2F6, 0xBDCE, 0xC2F8, 0xBDCF, 0xC2F9, 0xBDD0, 0xC2FB, 0xBDD1, 0xC2FC, 0xBDD2, 0xC300, + 0xBDD3, 0xC308, 0xBDD4, 0xC309, 0xBDD5, 0xC30C, 0xBDD6, 0xC30D, 0xBDD7, 0xC313, 0xBDD8, 0xC314, 0xBDD9, 0xC315, 0xBDDA, 0xC318, + 0xBDDB, 0xC31C, 0xBDDC, 0xC324, 0xBDDD, 0xC325, 0xBDDE, 0xC328, 0xBDDF, 0xC329, 0xBDE0, 0xC345, 0xBDE1, 0xC368, 0xBDE2, 0xC369, + 0xBDE3, 0xC36C, 0xBDE4, 0xC370, 0xBDE5, 0xC372, 0xBDE6, 0xC378, 0xBDE7, 0xC379, 0xBDE8, 0xC37C, 0xBDE9, 0xC37D, 0xBDEA, 0xC384, + 0xBDEB, 0xC388, 0xBDEC, 0xC38C, 0xBDED, 0xC3C0, 0xBDEE, 0xC3D8, 0xBDEF, 0xC3D9, 0xBDF0, 0xC3DC, 0xBDF1, 0xC3DF, 0xBDF2, 0xC3E0, + 0xBDF3, 0xC3E2, 0xBDF4, 0xC3E8, 0xBDF5, 0xC3E9, 0xBDF6, 0xC3ED, 0xBDF7, 0xC3F4, 0xBDF8, 0xC3F5, 0xBDF9, 0xC3F8, 0xBDFA, 0xC408, + 0xBDFB, 0xC410, 0xBDFC, 0xC424, 0xBDFD, 0xC42C, 0xBDFE, 0xC430, 0xBE41, 0xD438, 0xBE42, 0xD439, 0xBE43, 0xD43A, 0xBE44, 0xD43B, + 0xBE45, 0xD43C, 0xBE46, 0xD43D, 0xBE47, 0xD43E, 0xBE48, 0xD43F, 0xBE49, 0xD441, 0xBE4A, 0xD442, 0xBE4B, 0xD443, 0xBE4C, 0xD445, + 0xBE4D, 0xD446, 0xBE4E, 0xD447, 0xBE4F, 0xD448, 0xBE50, 0xD449, 0xBE51, 0xD44A, 0xBE52, 0xD44B, 0xBE53, 0xD44C, 0xBE54, 0xD44D, + 0xBE55, 0xD44E, 0xBE56, 0xD44F, 0xBE57, 0xD450, 0xBE58, 0xD451, 0xBE59, 0xD452, 0xBE5A, 0xD453, 0xBE61, 0xD454, 0xBE62, 0xD455, + 0xBE63, 0xD456, 0xBE64, 0xD457, 0xBE65, 0xD458, 0xBE66, 0xD459, 0xBE67, 0xD45A, 0xBE68, 0xD45B, 0xBE69, 0xD45D, 0xBE6A, 0xD45E, + 0xBE6B, 0xD45F, 0xBE6C, 0xD461, 0xBE6D, 0xD462, 0xBE6E, 0xD463, 0xBE6F, 0xD465, 0xBE70, 0xD466, 0xBE71, 0xD467, 0xBE72, 0xD468, + 0xBE73, 0xD469, 0xBE74, 0xD46A, 0xBE75, 0xD46B, 0xBE76, 0xD46C, 0xBE77, 0xD46E, 0xBE78, 0xD470, 0xBE79, 0xD471, 0xBE7A, 0xD472, + 0xBE81, 0xD473, 0xBE82, 0xD474, 0xBE83, 0xD475, 0xBE84, 0xD476, 0xBE85, 0xD477, 0xBE86, 0xD47A, 0xBE87, 0xD47B, 0xBE88, 0xD47D, + 0xBE89, 0xD47E, 0xBE8A, 0xD481, 0xBE8B, 0xD483, 0xBE8C, 0xD484, 0xBE8D, 0xD485, 0xBE8E, 0xD486, 0xBE8F, 0xD487, 0xBE90, 0xD48A, + 0xBE91, 0xD48C, 0xBE92, 0xD48E, 0xBE93, 0xD48F, 0xBE94, 0xD490, 0xBE95, 0xD491, 0xBE96, 0xD492, 0xBE97, 0xD493, 0xBE98, 0xD495, + 0xBE99, 0xD496, 0xBE9A, 0xD497, 0xBE9B, 0xD498, 0xBE9C, 0xD499, 0xBE9D, 0xD49A, 0xBE9E, 0xD49B, 0xBE9F, 0xD49C, 0xBEA0, 0xD49D, + 0xBEA1, 0xC434, 0xBEA2, 0xC43C, 0xBEA3, 0xC43D, 0xBEA4, 0xC448, 0xBEA5, 0xC464, 0xBEA6, 0xC465, 0xBEA7, 0xC468, 0xBEA8, 0xC46C, + 0xBEA9, 0xC474, 0xBEAA, 0xC475, 0xBEAB, 0xC479, 0xBEAC, 0xC480, 0xBEAD, 0xC494, 0xBEAE, 0xC49C, 0xBEAF, 0xC4B8, 0xBEB0, 0xC4BC, + 0xBEB1, 0xC4E9, 0xBEB2, 0xC4F0, 0xBEB3, 0xC4F1, 0xBEB4, 0xC4F4, 0xBEB5, 0xC4F8, 0xBEB6, 0xC4FA, 0xBEB7, 0xC4FF, 0xBEB8, 0xC500, + 0xBEB9, 0xC501, 0xBEBA, 0xC50C, 0xBEBB, 0xC510, 0xBEBC, 0xC514, 0xBEBD, 0xC51C, 0xBEBE, 0xC528, 0xBEBF, 0xC529, 0xBEC0, 0xC52C, + 0xBEC1, 0xC530, 0xBEC2, 0xC538, 0xBEC3, 0xC539, 0xBEC4, 0xC53B, 0xBEC5, 0xC53D, 0xBEC6, 0xC544, 0xBEC7, 0xC545, 0xBEC8, 0xC548, + 0xBEC9, 0xC549, 0xBECA, 0xC54A, 0xBECB, 0xC54C, 0xBECC, 0xC54D, 0xBECD, 0xC54E, 0xBECE, 0xC553, 0xBECF, 0xC554, 0xBED0, 0xC555, + 0xBED1, 0xC557, 0xBED2, 0xC558, 0xBED3, 0xC559, 0xBED4, 0xC55D, 0xBED5, 0xC55E, 0xBED6, 0xC560, 0xBED7, 0xC561, 0xBED8, 0xC564, + 0xBED9, 0xC568, 0xBEDA, 0xC570, 0xBEDB, 0xC571, 0xBEDC, 0xC573, 0xBEDD, 0xC574, 0xBEDE, 0xC575, 0xBEDF, 0xC57C, 0xBEE0, 0xC57D, + 0xBEE1, 0xC580, 0xBEE2, 0xC584, 0xBEE3, 0xC587, 0xBEE4, 0xC58C, 0xBEE5, 0xC58D, 0xBEE6, 0xC58F, 0xBEE7, 0xC591, 0xBEE8, 0xC595, + 0xBEE9, 0xC597, 0xBEEA, 0xC598, 0xBEEB, 0xC59C, 0xBEEC, 0xC5A0, 0xBEED, 0xC5A9, 0xBEEE, 0xC5B4, 0xBEEF, 0xC5B5, 0xBEF0, 0xC5B8, + 0xBEF1, 0xC5B9, 0xBEF2, 0xC5BB, 0xBEF3, 0xC5BC, 0xBEF4, 0xC5BD, 0xBEF5, 0xC5BE, 0xBEF6, 0xC5C4, 0xBEF7, 0xC5C5, 0xBEF8, 0xC5C6, + 0xBEF9, 0xC5C7, 0xBEFA, 0xC5C8, 0xBEFB, 0xC5C9, 0xBEFC, 0xC5CA, 0xBEFD, 0xC5CC, 0xBEFE, 0xC5CE, 0xBF41, 0xD49E, 0xBF42, 0xD49F, + 0xBF43, 0xD4A0, 0xBF44, 0xD4A1, 0xBF45, 0xD4A2, 0xBF46, 0xD4A3, 0xBF47, 0xD4A4, 0xBF48, 0xD4A5, 0xBF49, 0xD4A6, 0xBF4A, 0xD4A7, + 0xBF4B, 0xD4A8, 0xBF4C, 0xD4AA, 0xBF4D, 0xD4AB, 0xBF4E, 0xD4AC, 0xBF4F, 0xD4AD, 0xBF50, 0xD4AE, 0xBF51, 0xD4AF, 0xBF52, 0xD4B0, + 0xBF53, 0xD4B1, 0xBF54, 0xD4B2, 0xBF55, 0xD4B3, 0xBF56, 0xD4B4, 0xBF57, 0xD4B5, 0xBF58, 0xD4B6, 0xBF59, 0xD4B7, 0xBF5A, 0xD4B8, + 0xBF61, 0xD4B9, 0xBF62, 0xD4BA, 0xBF63, 0xD4BB, 0xBF64, 0xD4BC, 0xBF65, 0xD4BD, 0xBF66, 0xD4BE, 0xBF67, 0xD4BF, 0xBF68, 0xD4C0, + 0xBF69, 0xD4C1, 0xBF6A, 0xD4C2, 0xBF6B, 0xD4C3, 0xBF6C, 0xD4C4, 0xBF6D, 0xD4C5, 0xBF6E, 0xD4C6, 0xBF6F, 0xD4C7, 0xBF70, 0xD4C8, + 0xBF71, 0xD4C9, 0xBF72, 0xD4CA, 0xBF73, 0xD4CB, 0xBF74, 0xD4CD, 0xBF75, 0xD4CE, 0xBF76, 0xD4CF, 0xBF77, 0xD4D1, 0xBF78, 0xD4D2, + 0xBF79, 0xD4D3, 0xBF7A, 0xD4D5, 0xBF81, 0xD4D6, 0xBF82, 0xD4D7, 0xBF83, 0xD4D8, 0xBF84, 0xD4D9, 0xBF85, 0xD4DA, 0xBF86, 0xD4DB, + 0xBF87, 0xD4DD, 0xBF88, 0xD4DE, 0xBF89, 0xD4E0, 0xBF8A, 0xD4E1, 0xBF8B, 0xD4E2, 0xBF8C, 0xD4E3, 0xBF8D, 0xD4E4, 0xBF8E, 0xD4E5, + 0xBF8F, 0xD4E6, 0xBF90, 0xD4E7, 0xBF91, 0xD4E9, 0xBF92, 0xD4EA, 0xBF93, 0xD4EB, 0xBF94, 0xD4ED, 0xBF95, 0xD4EE, 0xBF96, 0xD4EF, + 0xBF97, 0xD4F1, 0xBF98, 0xD4F2, 0xBF99, 0xD4F3, 0xBF9A, 0xD4F4, 0xBF9B, 0xD4F5, 0xBF9C, 0xD4F6, 0xBF9D, 0xD4F7, 0xBF9E, 0xD4F9, + 0xBF9F, 0xD4FA, 0xBFA0, 0xD4FC, 0xBFA1, 0xC5D0, 0xBFA2, 0xC5D1, 0xBFA3, 0xC5D4, 0xBFA4, 0xC5D8, 0xBFA5, 0xC5E0, 0xBFA6, 0xC5E1, + 0xBFA7, 0xC5E3, 0xBFA8, 0xC5E5, 0xBFA9, 0xC5EC, 0xBFAA, 0xC5ED, 0xBFAB, 0xC5EE, 0xBFAC, 0xC5F0, 0xBFAD, 0xC5F4, 0xBFAE, 0xC5F6, + 0xBFAF, 0xC5F7, 0xBFB0, 0xC5FC, 0xBFB1, 0xC5FD, 0xBFB2, 0xC5FE, 0xBFB3, 0xC5FF, 0xBFB4, 0xC600, 0xBFB5, 0xC601, 0xBFB6, 0xC605, + 0xBFB7, 0xC606, 0xBFB8, 0xC607, 0xBFB9, 0xC608, 0xBFBA, 0xC60C, 0xBFBB, 0xC610, 0xBFBC, 0xC618, 0xBFBD, 0xC619, 0xBFBE, 0xC61B, + 0xBFBF, 0xC61C, 0xBFC0, 0xC624, 0xBFC1, 0xC625, 0xBFC2, 0xC628, 0xBFC3, 0xC62C, 0xBFC4, 0xC62D, 0xBFC5, 0xC62E, 0xBFC6, 0xC630, + 0xBFC7, 0xC633, 0xBFC8, 0xC634, 0xBFC9, 0xC635, 0xBFCA, 0xC637, 0xBFCB, 0xC639, 0xBFCC, 0xC63B, 0xBFCD, 0xC640, 0xBFCE, 0xC641, + 0xBFCF, 0xC644, 0xBFD0, 0xC648, 0xBFD1, 0xC650, 0xBFD2, 0xC651, 0xBFD3, 0xC653, 0xBFD4, 0xC654, 0xBFD5, 0xC655, 0xBFD6, 0xC65C, + 0xBFD7, 0xC65D, 0xBFD8, 0xC660, 0xBFD9, 0xC66C, 0xBFDA, 0xC66F, 0xBFDB, 0xC671, 0xBFDC, 0xC678, 0xBFDD, 0xC679, 0xBFDE, 0xC67C, + 0xBFDF, 0xC680, 0xBFE0, 0xC688, 0xBFE1, 0xC689, 0xBFE2, 0xC68B, 0xBFE3, 0xC68D, 0xBFE4, 0xC694, 0xBFE5, 0xC695, 0xBFE6, 0xC698, + 0xBFE7, 0xC69C, 0xBFE8, 0xC6A4, 0xBFE9, 0xC6A5, 0xBFEA, 0xC6A7, 0xBFEB, 0xC6A9, 0xBFEC, 0xC6B0, 0xBFED, 0xC6B1, 0xBFEE, 0xC6B4, + 0xBFEF, 0xC6B8, 0xBFF0, 0xC6B9, 0xBFF1, 0xC6BA, 0xBFF2, 0xC6C0, 0xBFF3, 0xC6C1, 0xBFF4, 0xC6C3, 0xBFF5, 0xC6C5, 0xBFF6, 0xC6CC, + 0xBFF7, 0xC6CD, 0xBFF8, 0xC6D0, 0xBFF9, 0xC6D4, 0xBFFA, 0xC6DC, 0xBFFB, 0xC6DD, 0xBFFC, 0xC6E0, 0xBFFD, 0xC6E1, 0xBFFE, 0xC6E8, + 0xC041, 0xD4FE, 0xC042, 0xD4FF, 0xC043, 0xD500, 0xC044, 0xD501, 0xC045, 0xD502, 0xC046, 0xD503, 0xC047, 0xD505, 0xC048, 0xD506, + 0xC049, 0xD507, 0xC04A, 0xD509, 0xC04B, 0xD50A, 0xC04C, 0xD50B, 0xC04D, 0xD50D, 0xC04E, 0xD50E, 0xC04F, 0xD50F, 0xC050, 0xD510, + 0xC051, 0xD511, 0xC052, 0xD512, 0xC053, 0xD513, 0xC054, 0xD516, 0xC055, 0xD518, 0xC056, 0xD519, 0xC057, 0xD51A, 0xC058, 0xD51B, + 0xC059, 0xD51C, 0xC05A, 0xD51D, 0xC061, 0xD51E, 0xC062, 0xD51F, 0xC063, 0xD520, 0xC064, 0xD521, 0xC065, 0xD522, 0xC066, 0xD523, + 0xC067, 0xD524, 0xC068, 0xD525, 0xC069, 0xD526, 0xC06A, 0xD527, 0xC06B, 0xD528, 0xC06C, 0xD529, 0xC06D, 0xD52A, 0xC06E, 0xD52B, + 0xC06F, 0xD52C, 0xC070, 0xD52D, 0xC071, 0xD52E, 0xC072, 0xD52F, 0xC073, 0xD530, 0xC074, 0xD531, 0xC075, 0xD532, 0xC076, 0xD533, + 0xC077, 0xD534, 0xC078, 0xD535, 0xC079, 0xD536, 0xC07A, 0xD537, 0xC081, 0xD538, 0xC082, 0xD539, 0xC083, 0xD53A, 0xC084, 0xD53B, + 0xC085, 0xD53E, 0xC086, 0xD53F, 0xC087, 0xD541, 0xC088, 0xD542, 0xC089, 0xD543, 0xC08A, 0xD545, 0xC08B, 0xD546, 0xC08C, 0xD547, + 0xC08D, 0xD548, 0xC08E, 0xD549, 0xC08F, 0xD54A, 0xC090, 0xD54B, 0xC091, 0xD54E, 0xC092, 0xD550, 0xC093, 0xD552, 0xC094, 0xD553, + 0xC095, 0xD554, 0xC096, 0xD555, 0xC097, 0xD556, 0xC098, 0xD557, 0xC099, 0xD55A, 0xC09A, 0xD55B, 0xC09B, 0xD55D, 0xC09C, 0xD55E, + 0xC09D, 0xD55F, 0xC09E, 0xD561, 0xC09F, 0xD562, 0xC0A0, 0xD563, 0xC0A1, 0xC6E9, 0xC0A2, 0xC6EC, 0xC0A3, 0xC6F0, 0xC0A4, 0xC6F8, + 0xC0A5, 0xC6F9, 0xC0A6, 0xC6FD, 0xC0A7, 0xC704, 0xC0A8, 0xC705, 0xC0A9, 0xC708, 0xC0AA, 0xC70C, 0xC0AB, 0xC714, 0xC0AC, 0xC715, + 0xC0AD, 0xC717, 0xC0AE, 0xC719, 0xC0AF, 0xC720, 0xC0B0, 0xC721, 0xC0B1, 0xC724, 0xC0B2, 0xC728, 0xC0B3, 0xC730, 0xC0B4, 0xC731, + 0xC0B5, 0xC733, 0xC0B6, 0xC735, 0xC0B7, 0xC737, 0xC0B8, 0xC73C, 0xC0B9, 0xC73D, 0xC0BA, 0xC740, 0xC0BB, 0xC744, 0xC0BC, 0xC74A, + 0xC0BD, 0xC74C, 0xC0BE, 0xC74D, 0xC0BF, 0xC74F, 0xC0C0, 0xC751, 0xC0C1, 0xC752, 0xC0C2, 0xC753, 0xC0C3, 0xC754, 0xC0C4, 0xC755, + 0xC0C5, 0xC756, 0xC0C6, 0xC757, 0xC0C7, 0xC758, 0xC0C8, 0xC75C, 0xC0C9, 0xC760, 0xC0CA, 0xC768, 0xC0CB, 0xC76B, 0xC0CC, 0xC774, + 0xC0CD, 0xC775, 0xC0CE, 0xC778, 0xC0CF, 0xC77C, 0xC0D0, 0xC77D, 0xC0D1, 0xC77E, 0xC0D2, 0xC783, 0xC0D3, 0xC784, 0xC0D4, 0xC785, + 0xC0D5, 0xC787, 0xC0D6, 0xC788, 0xC0D7, 0xC789, 0xC0D8, 0xC78A, 0xC0D9, 0xC78E, 0xC0DA, 0xC790, 0xC0DB, 0xC791, 0xC0DC, 0xC794, + 0xC0DD, 0xC796, 0xC0DE, 0xC797, 0xC0DF, 0xC798, 0xC0E0, 0xC79A, 0xC0E1, 0xC7A0, 0xC0E2, 0xC7A1, 0xC0E3, 0xC7A3, 0xC0E4, 0xC7A4, + 0xC0E5, 0xC7A5, 0xC0E6, 0xC7A6, 0xC0E7, 0xC7AC, 0xC0E8, 0xC7AD, 0xC0E9, 0xC7B0, 0xC0EA, 0xC7B4, 0xC0EB, 0xC7BC, 0xC0EC, 0xC7BD, + 0xC0ED, 0xC7BF, 0xC0EE, 0xC7C0, 0xC0EF, 0xC7C1, 0xC0F0, 0xC7C8, 0xC0F1, 0xC7C9, 0xC0F2, 0xC7CC, 0xC0F3, 0xC7CE, 0xC0F4, 0xC7D0, + 0xC0F5, 0xC7D8, 0xC0F6, 0xC7DD, 0xC0F7, 0xC7E4, 0xC0F8, 0xC7E8, 0xC0F9, 0xC7EC, 0xC0FA, 0xC800, 0xC0FB, 0xC801, 0xC0FC, 0xC804, + 0xC0FD, 0xC808, 0xC0FE, 0xC80A, 0xC141, 0xD564, 0xC142, 0xD566, 0xC143, 0xD567, 0xC144, 0xD56A, 0xC145, 0xD56C, 0xC146, 0xD56E, + 0xC147, 0xD56F, 0xC148, 0xD570, 0xC149, 0xD571, 0xC14A, 0xD572, 0xC14B, 0xD573, 0xC14C, 0xD576, 0xC14D, 0xD577, 0xC14E, 0xD579, + 0xC14F, 0xD57A, 0xC150, 0xD57B, 0xC151, 0xD57D, 0xC152, 0xD57E, 0xC153, 0xD57F, 0xC154, 0xD580, 0xC155, 0xD581, 0xC156, 0xD582, + 0xC157, 0xD583, 0xC158, 0xD586, 0xC159, 0xD58A, 0xC15A, 0xD58B, 0xC161, 0xD58C, 0xC162, 0xD58D, 0xC163, 0xD58E, 0xC164, 0xD58F, + 0xC165, 0xD591, 0xC166, 0xD592, 0xC167, 0xD593, 0xC168, 0xD594, 0xC169, 0xD595, 0xC16A, 0xD596, 0xC16B, 0xD597, 0xC16C, 0xD598, + 0xC16D, 0xD599, 0xC16E, 0xD59A, 0xC16F, 0xD59B, 0xC170, 0xD59C, 0xC171, 0xD59D, 0xC172, 0xD59E, 0xC173, 0xD59F, 0xC174, 0xD5A0, + 0xC175, 0xD5A1, 0xC176, 0xD5A2, 0xC177, 0xD5A3, 0xC178, 0xD5A4, 0xC179, 0xD5A6, 0xC17A, 0xD5A7, 0xC181, 0xD5A8, 0xC182, 0xD5A9, + 0xC183, 0xD5AA, 0xC184, 0xD5AB, 0xC185, 0xD5AC, 0xC186, 0xD5AD, 0xC187, 0xD5AE, 0xC188, 0xD5AF, 0xC189, 0xD5B0, 0xC18A, 0xD5B1, + 0xC18B, 0xD5B2, 0xC18C, 0xD5B3, 0xC18D, 0xD5B4, 0xC18E, 0xD5B5, 0xC18F, 0xD5B6, 0xC190, 0xD5B7, 0xC191, 0xD5B8, 0xC192, 0xD5B9, + 0xC193, 0xD5BA, 0xC194, 0xD5BB, 0xC195, 0xD5BC, 0xC196, 0xD5BD, 0xC197, 0xD5BE, 0xC198, 0xD5BF, 0xC199, 0xD5C0, 0xC19A, 0xD5C1, + 0xC19B, 0xD5C2, 0xC19C, 0xD5C3, 0xC19D, 0xD5C4, 0xC19E, 0xD5C5, 0xC19F, 0xD5C6, 0xC1A0, 0xD5C7, 0xC1A1, 0xC810, 0xC1A2, 0xC811, + 0xC1A3, 0xC813, 0xC1A4, 0xC815, 0xC1A5, 0xC816, 0xC1A6, 0xC81C, 0xC1A7, 0xC81D, 0xC1A8, 0xC820, 0xC1A9, 0xC824, 0xC1AA, 0xC82C, + 0xC1AB, 0xC82D, 0xC1AC, 0xC82F, 0xC1AD, 0xC831, 0xC1AE, 0xC838, 0xC1AF, 0xC83C, 0xC1B0, 0xC840, 0xC1B1, 0xC848, 0xC1B2, 0xC849, + 0xC1B3, 0xC84C, 0xC1B4, 0xC84D, 0xC1B5, 0xC854, 0xC1B6, 0xC870, 0xC1B7, 0xC871, 0xC1B8, 0xC874, 0xC1B9, 0xC878, 0xC1BA, 0xC87A, + 0xC1BB, 0xC880, 0xC1BC, 0xC881, 0xC1BD, 0xC883, 0xC1BE, 0xC885, 0xC1BF, 0xC886, 0xC1C0, 0xC887, 0xC1C1, 0xC88B, 0xC1C2, 0xC88C, + 0xC1C3, 0xC88D, 0xC1C4, 0xC894, 0xC1C5, 0xC89D, 0xC1C6, 0xC89F, 0xC1C7, 0xC8A1, 0xC1C8, 0xC8A8, 0xC1C9, 0xC8BC, 0xC1CA, 0xC8BD, + 0xC1CB, 0xC8C4, 0xC1CC, 0xC8C8, 0xC1CD, 0xC8CC, 0xC1CE, 0xC8D4, 0xC1CF, 0xC8D5, 0xC1D0, 0xC8D7, 0xC1D1, 0xC8D9, 0xC1D2, 0xC8E0, + 0xC1D3, 0xC8E1, 0xC1D4, 0xC8E4, 0xC1D5, 0xC8F5, 0xC1D6, 0xC8FC, 0xC1D7, 0xC8FD, 0xC1D8, 0xC900, 0xC1D9, 0xC904, 0xC1DA, 0xC905, + 0xC1DB, 0xC906, 0xC1DC, 0xC90C, 0xC1DD, 0xC90D, 0xC1DE, 0xC90F, 0xC1DF, 0xC911, 0xC1E0, 0xC918, 0xC1E1, 0xC92C, 0xC1E2, 0xC934, + 0xC1E3, 0xC950, 0xC1E4, 0xC951, 0xC1E5, 0xC954, 0xC1E6, 0xC958, 0xC1E7, 0xC960, 0xC1E8, 0xC961, 0xC1E9, 0xC963, 0xC1EA, 0xC96C, + 0xC1EB, 0xC970, 0xC1EC, 0xC974, 0xC1ED, 0xC97C, 0xC1EE, 0xC988, 0xC1EF, 0xC989, 0xC1F0, 0xC98C, 0xC1F1, 0xC990, 0xC1F2, 0xC998, + 0xC1F3, 0xC999, 0xC1F4, 0xC99B, 0xC1F5, 0xC99D, 0xC1F6, 0xC9C0, 0xC1F7, 0xC9C1, 0xC1F8, 0xC9C4, 0xC1F9, 0xC9C7, 0xC1FA, 0xC9C8, + 0xC1FB, 0xC9CA, 0xC1FC, 0xC9D0, 0xC1FD, 0xC9D1, 0xC1FE, 0xC9D3, 0xC241, 0xD5CA, 0xC242, 0xD5CB, 0xC243, 0xD5CD, 0xC244, 0xD5CE, + 0xC245, 0xD5CF, 0xC246, 0xD5D1, 0xC247, 0xD5D3, 0xC248, 0xD5D4, 0xC249, 0xD5D5, 0xC24A, 0xD5D6, 0xC24B, 0xD5D7, 0xC24C, 0xD5DA, + 0xC24D, 0xD5DC, 0xC24E, 0xD5DE, 0xC24F, 0xD5DF, 0xC250, 0xD5E0, 0xC251, 0xD5E1, 0xC252, 0xD5E2, 0xC253, 0xD5E3, 0xC254, 0xD5E6, + 0xC255, 0xD5E7, 0xC256, 0xD5E9, 0xC257, 0xD5EA, 0xC258, 0xD5EB, 0xC259, 0xD5ED, 0xC25A, 0xD5EE, 0xC261, 0xD5EF, 0xC262, 0xD5F0, + 0xC263, 0xD5F1, 0xC264, 0xD5F2, 0xC265, 0xD5F3, 0xC266, 0xD5F6, 0xC267, 0xD5F8, 0xC268, 0xD5FA, 0xC269, 0xD5FB, 0xC26A, 0xD5FC, + 0xC26B, 0xD5FD, 0xC26C, 0xD5FE, 0xC26D, 0xD5FF, 0xC26E, 0xD602, 0xC26F, 0xD603, 0xC270, 0xD605, 0xC271, 0xD606, 0xC272, 0xD607, + 0xC273, 0xD609, 0xC274, 0xD60A, 0xC275, 0xD60B, 0xC276, 0xD60C, 0xC277, 0xD60D, 0xC278, 0xD60E, 0xC279, 0xD60F, 0xC27A, 0xD612, + 0xC281, 0xD616, 0xC282, 0xD617, 0xC283, 0xD618, 0xC284, 0xD619, 0xC285, 0xD61A, 0xC286, 0xD61B, 0xC287, 0xD61D, 0xC288, 0xD61E, + 0xC289, 0xD61F, 0xC28A, 0xD621, 0xC28B, 0xD622, 0xC28C, 0xD623, 0xC28D, 0xD625, 0xC28E, 0xD626, 0xC28F, 0xD627, 0xC290, 0xD628, + 0xC291, 0xD629, 0xC292, 0xD62A, 0xC293, 0xD62B, 0xC294, 0xD62C, 0xC295, 0xD62E, 0xC296, 0xD62F, 0xC297, 0xD630, 0xC298, 0xD631, + 0xC299, 0xD632, 0xC29A, 0xD633, 0xC29B, 0xD634, 0xC29C, 0xD635, 0xC29D, 0xD636, 0xC29E, 0xD637, 0xC29F, 0xD63A, 0xC2A0, 0xD63B, + 0xC2A1, 0xC9D5, 0xC2A2, 0xC9D6, 0xC2A3, 0xC9D9, 0xC2A4, 0xC9DA, 0xC2A5, 0xC9DC, 0xC2A6, 0xC9DD, 0xC2A7, 0xC9E0, 0xC2A8, 0xC9E2, + 0xC2A9, 0xC9E4, 0xC2AA, 0xC9E7, 0xC2AB, 0xC9EC, 0xC2AC, 0xC9ED, 0xC2AD, 0xC9EF, 0xC2AE, 0xC9F0, 0xC2AF, 0xC9F1, 0xC2B0, 0xC9F8, + 0xC2B1, 0xC9F9, 0xC2B2, 0xC9FC, 0xC2B3, 0xCA00, 0xC2B4, 0xCA08, 0xC2B5, 0xCA09, 0xC2B6, 0xCA0B, 0xC2B7, 0xCA0C, 0xC2B8, 0xCA0D, + 0xC2B9, 0xCA14, 0xC2BA, 0xCA18, 0xC2BB, 0xCA29, 0xC2BC, 0xCA4C, 0xC2BD, 0xCA4D, 0xC2BE, 0xCA50, 0xC2BF, 0xCA54, 0xC2C0, 0xCA5C, + 0xC2C1, 0xCA5D, 0xC2C2, 0xCA5F, 0xC2C3, 0xCA60, 0xC2C4, 0xCA61, 0xC2C5, 0xCA68, 0xC2C6, 0xCA7D, 0xC2C7, 0xCA84, 0xC2C8, 0xCA98, + 0xC2C9, 0xCABC, 0xC2CA, 0xCABD, 0xC2CB, 0xCAC0, 0xC2CC, 0xCAC4, 0xC2CD, 0xCACC, 0xC2CE, 0xCACD, 0xC2CF, 0xCACF, 0xC2D0, 0xCAD1, + 0xC2D1, 0xCAD3, 0xC2D2, 0xCAD8, 0xC2D3, 0xCAD9, 0xC2D4, 0xCAE0, 0xC2D5, 0xCAEC, 0xC2D6, 0xCAF4, 0xC2D7, 0xCB08, 0xC2D8, 0xCB10, + 0xC2D9, 0xCB14, 0xC2DA, 0xCB18, 0xC2DB, 0xCB20, 0xC2DC, 0xCB21, 0xC2DD, 0xCB41, 0xC2DE, 0xCB48, 0xC2DF, 0xCB49, 0xC2E0, 0xCB4C, + 0xC2E1, 0xCB50, 0xC2E2, 0xCB58, 0xC2E3, 0xCB59, 0xC2E4, 0xCB5D, 0xC2E5, 0xCB64, 0xC2E6, 0xCB78, 0xC2E7, 0xCB79, 0xC2E8, 0xCB9C, + 0xC2E9, 0xCBB8, 0xC2EA, 0xCBD4, 0xC2EB, 0xCBE4, 0xC2EC, 0xCBE7, 0xC2ED, 0xCBE9, 0xC2EE, 0xCC0C, 0xC2EF, 0xCC0D, 0xC2F0, 0xCC10, + 0xC2F1, 0xCC14, 0xC2F2, 0xCC1C, 0xC2F3, 0xCC1D, 0xC2F4, 0xCC21, 0xC2F5, 0xCC22, 0xC2F6, 0xCC27, 0xC2F7, 0xCC28, 0xC2F8, 0xCC29, + 0xC2F9, 0xCC2C, 0xC2FA, 0xCC2E, 0xC2FB, 0xCC30, 0xC2FC, 0xCC38, 0xC2FD, 0xCC39, 0xC2FE, 0xCC3B, 0xC341, 0xD63D, 0xC342, 0xD63E, + 0xC343, 0xD63F, 0xC344, 0xD641, 0xC345, 0xD642, 0xC346, 0xD643, 0xC347, 0xD644, 0xC348, 0xD646, 0xC349, 0xD647, 0xC34A, 0xD64A, + 0xC34B, 0xD64C, 0xC34C, 0xD64E, 0xC34D, 0xD64F, 0xC34E, 0xD650, 0xC34F, 0xD652, 0xC350, 0xD653, 0xC351, 0xD656, 0xC352, 0xD657, + 0xC353, 0xD659, 0xC354, 0xD65A, 0xC355, 0xD65B, 0xC356, 0xD65D, 0xC357, 0xD65E, 0xC358, 0xD65F, 0xC359, 0xD660, 0xC35A, 0xD661, + 0xC361, 0xD662, 0xC362, 0xD663, 0xC363, 0xD664, 0xC364, 0xD665, 0xC365, 0xD666, 0xC366, 0xD668, 0xC367, 0xD66A, 0xC368, 0xD66B, + 0xC369, 0xD66C, 0xC36A, 0xD66D, 0xC36B, 0xD66E, 0xC36C, 0xD66F, 0xC36D, 0xD672, 0xC36E, 0xD673, 0xC36F, 0xD675, 0xC370, 0xD676, + 0xC371, 0xD677, 0xC372, 0xD678, 0xC373, 0xD679, 0xC374, 0xD67A, 0xC375, 0xD67B, 0xC376, 0xD67C, 0xC377, 0xD67D, 0xC378, 0xD67E, + 0xC379, 0xD67F, 0xC37A, 0xD680, 0xC381, 0xD681, 0xC382, 0xD682, 0xC383, 0xD684, 0xC384, 0xD686, 0xC385, 0xD687, 0xC386, 0xD688, + 0xC387, 0xD689, 0xC388, 0xD68A, 0xC389, 0xD68B, 0xC38A, 0xD68E, 0xC38B, 0xD68F, 0xC38C, 0xD691, 0xC38D, 0xD692, 0xC38E, 0xD693, + 0xC38F, 0xD695, 0xC390, 0xD696, 0xC391, 0xD697, 0xC392, 0xD698, 0xC393, 0xD699, 0xC394, 0xD69A, 0xC395, 0xD69B, 0xC396, 0xD69C, + 0xC397, 0xD69E, 0xC398, 0xD6A0, 0xC399, 0xD6A2, 0xC39A, 0xD6A3, 0xC39B, 0xD6A4, 0xC39C, 0xD6A5, 0xC39D, 0xD6A6, 0xC39E, 0xD6A7, + 0xC39F, 0xD6A9, 0xC3A0, 0xD6AA, 0xC3A1, 0xCC3C, 0xC3A2, 0xCC3D, 0xC3A3, 0xCC3E, 0xC3A4, 0xCC44, 0xC3A5, 0xCC45, 0xC3A6, 0xCC48, + 0xC3A7, 0xCC4C, 0xC3A8, 0xCC54, 0xC3A9, 0xCC55, 0xC3AA, 0xCC57, 0xC3AB, 0xCC58, 0xC3AC, 0xCC59, 0xC3AD, 0xCC60, 0xC3AE, 0xCC64, + 0xC3AF, 0xCC66, 0xC3B0, 0xCC68, 0xC3B1, 0xCC70, 0xC3B2, 0xCC75, 0xC3B3, 0xCC98, 0xC3B4, 0xCC99, 0xC3B5, 0xCC9C, 0xC3B6, 0xCCA0, + 0xC3B7, 0xCCA8, 0xC3B8, 0xCCA9, 0xC3B9, 0xCCAB, 0xC3BA, 0xCCAC, 0xC3BB, 0xCCAD, 0xC3BC, 0xCCB4, 0xC3BD, 0xCCB5, 0xC3BE, 0xCCB8, + 0xC3BF, 0xCCBC, 0xC3C0, 0xCCC4, 0xC3C1, 0xCCC5, 0xC3C2, 0xCCC7, 0xC3C3, 0xCCC9, 0xC3C4, 0xCCD0, 0xC3C5, 0xCCD4, 0xC3C6, 0xCCE4, + 0xC3C7, 0xCCEC, 0xC3C8, 0xCCF0, 0xC3C9, 0xCD01, 0xC3CA, 0xCD08, 0xC3CB, 0xCD09, 0xC3CC, 0xCD0C, 0xC3CD, 0xCD10, 0xC3CE, 0xCD18, + 0xC3CF, 0xCD19, 0xC3D0, 0xCD1B, 0xC3D1, 0xCD1D, 0xC3D2, 0xCD24, 0xC3D3, 0xCD28, 0xC3D4, 0xCD2C, 0xC3D5, 0xCD39, 0xC3D6, 0xCD5C, + 0xC3D7, 0xCD60, 0xC3D8, 0xCD64, 0xC3D9, 0xCD6C, 0xC3DA, 0xCD6D, 0xC3DB, 0xCD6F, 0xC3DC, 0xCD71, 0xC3DD, 0xCD78, 0xC3DE, 0xCD88, + 0xC3DF, 0xCD94, 0xC3E0, 0xCD95, 0xC3E1, 0xCD98, 0xC3E2, 0xCD9C, 0xC3E3, 0xCDA4, 0xC3E4, 0xCDA5, 0xC3E5, 0xCDA7, 0xC3E6, 0xCDA9, + 0xC3E7, 0xCDB0, 0xC3E8, 0xCDC4, 0xC3E9, 0xCDCC, 0xC3EA, 0xCDD0, 0xC3EB, 0xCDE8, 0xC3EC, 0xCDEC, 0xC3ED, 0xCDF0, 0xC3EE, 0xCDF8, + 0xC3EF, 0xCDF9, 0xC3F0, 0xCDFB, 0xC3F1, 0xCDFD, 0xC3F2, 0xCE04, 0xC3F3, 0xCE08, 0xC3F4, 0xCE0C, 0xC3F5, 0xCE14, 0xC3F6, 0xCE19, + 0xC3F7, 0xCE20, 0xC3F8, 0xCE21, 0xC3F9, 0xCE24, 0xC3FA, 0xCE28, 0xC3FB, 0xCE30, 0xC3FC, 0xCE31, 0xC3FD, 0xCE33, 0xC3FE, 0xCE35, + 0xC441, 0xD6AB, 0xC442, 0xD6AD, 0xC443, 0xD6AE, 0xC444, 0xD6AF, 0xC445, 0xD6B1, 0xC446, 0xD6B2, 0xC447, 0xD6B3, 0xC448, 0xD6B4, + 0xC449, 0xD6B5, 0xC44A, 0xD6B6, 0xC44B, 0xD6B7, 0xC44C, 0xD6B8, 0xC44D, 0xD6BA, 0xC44E, 0xD6BC, 0xC44F, 0xD6BD, 0xC450, 0xD6BE, + 0xC451, 0xD6BF, 0xC452, 0xD6C0, 0xC453, 0xD6C1, 0xC454, 0xD6C2, 0xC455, 0xD6C3, 0xC456, 0xD6C6, 0xC457, 0xD6C7, 0xC458, 0xD6C9, + 0xC459, 0xD6CA, 0xC45A, 0xD6CB, 0xC461, 0xD6CD, 0xC462, 0xD6CE, 0xC463, 0xD6CF, 0xC464, 0xD6D0, 0xC465, 0xD6D2, 0xC466, 0xD6D3, + 0xC467, 0xD6D5, 0xC468, 0xD6D6, 0xC469, 0xD6D8, 0xC46A, 0xD6DA, 0xC46B, 0xD6DB, 0xC46C, 0xD6DC, 0xC46D, 0xD6DD, 0xC46E, 0xD6DE, + 0xC46F, 0xD6DF, 0xC470, 0xD6E1, 0xC471, 0xD6E2, 0xC472, 0xD6E3, 0xC473, 0xD6E5, 0xC474, 0xD6E6, 0xC475, 0xD6E7, 0xC476, 0xD6E9, + 0xC477, 0xD6EA, 0xC478, 0xD6EB, 0xC479, 0xD6EC, 0xC47A, 0xD6ED, 0xC481, 0xD6EE, 0xC482, 0xD6EF, 0xC483, 0xD6F1, 0xC484, 0xD6F2, + 0xC485, 0xD6F3, 0xC486, 0xD6F4, 0xC487, 0xD6F6, 0xC488, 0xD6F7, 0xC489, 0xD6F8, 0xC48A, 0xD6F9, 0xC48B, 0xD6FA, 0xC48C, 0xD6FB, + 0xC48D, 0xD6FE, 0xC48E, 0xD6FF, 0xC48F, 0xD701, 0xC490, 0xD702, 0xC491, 0xD703, 0xC492, 0xD705, 0xC493, 0xD706, 0xC494, 0xD707, + 0xC495, 0xD708, 0xC496, 0xD709, 0xC497, 0xD70A, 0xC498, 0xD70B, 0xC499, 0xD70C, 0xC49A, 0xD70D, 0xC49B, 0xD70E, 0xC49C, 0xD70F, + 0xC49D, 0xD710, 0xC49E, 0xD712, 0xC49F, 0xD713, 0xC4A0, 0xD714, 0xC4A1, 0xCE58, 0xC4A2, 0xCE59, 0xC4A3, 0xCE5C, 0xC4A4, 0xCE5F, + 0xC4A5, 0xCE60, 0xC4A6, 0xCE61, 0xC4A7, 0xCE68, 0xC4A8, 0xCE69, 0xC4A9, 0xCE6B, 0xC4AA, 0xCE6D, 0xC4AB, 0xCE74, 0xC4AC, 0xCE75, + 0xC4AD, 0xCE78, 0xC4AE, 0xCE7C, 0xC4AF, 0xCE84, 0xC4B0, 0xCE85, 0xC4B1, 0xCE87, 0xC4B2, 0xCE89, 0xC4B3, 0xCE90, 0xC4B4, 0xCE91, + 0xC4B5, 0xCE94, 0xC4B6, 0xCE98, 0xC4B7, 0xCEA0, 0xC4B8, 0xCEA1, 0xC4B9, 0xCEA3, 0xC4BA, 0xCEA4, 0xC4BB, 0xCEA5, 0xC4BC, 0xCEAC, + 0xC4BD, 0xCEAD, 0xC4BE, 0xCEC1, 0xC4BF, 0xCEE4, 0xC4C0, 0xCEE5, 0xC4C1, 0xCEE8, 0xC4C2, 0xCEEB, 0xC4C3, 0xCEEC, 0xC4C4, 0xCEF4, + 0xC4C5, 0xCEF5, 0xC4C6, 0xCEF7, 0xC4C7, 0xCEF8, 0xC4C8, 0xCEF9, 0xC4C9, 0xCF00, 0xC4CA, 0xCF01, 0xC4CB, 0xCF04, 0xC4CC, 0xCF08, + 0xC4CD, 0xCF10, 0xC4CE, 0xCF11, 0xC4CF, 0xCF13, 0xC4D0, 0xCF15, 0xC4D1, 0xCF1C, 0xC4D2, 0xCF20, 0xC4D3, 0xCF24, 0xC4D4, 0xCF2C, + 0xC4D5, 0xCF2D, 0xC4D6, 0xCF2F, 0xC4D7, 0xCF30, 0xC4D8, 0xCF31, 0xC4D9, 0xCF38, 0xC4DA, 0xCF54, 0xC4DB, 0xCF55, 0xC4DC, 0xCF58, + 0xC4DD, 0xCF5C, 0xC4DE, 0xCF64, 0xC4DF, 0xCF65, 0xC4E0, 0xCF67, 0xC4E1, 0xCF69, 0xC4E2, 0xCF70, 0xC4E3, 0xCF71, 0xC4E4, 0xCF74, + 0xC4E5, 0xCF78, 0xC4E6, 0xCF80, 0xC4E7, 0xCF85, 0xC4E8, 0xCF8C, 0xC4E9, 0xCFA1, 0xC4EA, 0xCFA8, 0xC4EB, 0xCFB0, 0xC4EC, 0xCFC4, + 0xC4ED, 0xCFE0, 0xC4EE, 0xCFE1, 0xC4EF, 0xCFE4, 0xC4F0, 0xCFE8, 0xC4F1, 0xCFF0, 0xC4F2, 0xCFF1, 0xC4F3, 0xCFF3, 0xC4F4, 0xCFF5, + 0xC4F5, 0xCFFC, 0xC4F6, 0xD000, 0xC4F7, 0xD004, 0xC4F8, 0xD011, 0xC4F9, 0xD018, 0xC4FA, 0xD02D, 0xC4FB, 0xD034, 0xC4FC, 0xD035, + 0xC4FD, 0xD038, 0xC4FE, 0xD03C, 0xC541, 0xD715, 0xC542, 0xD716, 0xC543, 0xD717, 0xC544, 0xD71A, 0xC545, 0xD71B, 0xC546, 0xD71D, + 0xC547, 0xD71E, 0xC548, 0xD71F, 0xC549, 0xD721, 0xC54A, 0xD722, 0xC54B, 0xD723, 0xC54C, 0xD724, 0xC54D, 0xD725, 0xC54E, 0xD726, + 0xC54F, 0xD727, 0xC550, 0xD72A, 0xC551, 0xD72C, 0xC552, 0xD72E, 0xC553, 0xD72F, 0xC554, 0xD730, 0xC555, 0xD731, 0xC556, 0xD732, + 0xC557, 0xD733, 0xC558, 0xD736, 0xC559, 0xD737, 0xC55A, 0xD739, 0xC561, 0xD73A, 0xC562, 0xD73B, 0xC563, 0xD73D, 0xC564, 0xD73E, + 0xC565, 0xD73F, 0xC566, 0xD740, 0xC567, 0xD741, 0xC568, 0xD742, 0xC569, 0xD743, 0xC56A, 0xD745, 0xC56B, 0xD746, 0xC56C, 0xD748, + 0xC56D, 0xD74A, 0xC56E, 0xD74B, 0xC56F, 0xD74C, 0xC570, 0xD74D, 0xC571, 0xD74E, 0xC572, 0xD74F, 0xC573, 0xD752, 0xC574, 0xD753, + 0xC575, 0xD755, 0xC576, 0xD75A, 0xC577, 0xD75B, 0xC578, 0xD75C, 0xC579, 0xD75D, 0xC57A, 0xD75E, 0xC581, 0xD75F, 0xC582, 0xD762, + 0xC583, 0xD764, 0xC584, 0xD766, 0xC585, 0xD767, 0xC586, 0xD768, 0xC587, 0xD76A, 0xC588, 0xD76B, 0xC589, 0xD76D, 0xC58A, 0xD76E, + 0xC58B, 0xD76F, 0xC58C, 0xD771, 0xC58D, 0xD772, 0xC58E, 0xD773, 0xC58F, 0xD775, 0xC590, 0xD776, 0xC591, 0xD777, 0xC592, 0xD778, + 0xC593, 0xD779, 0xC594, 0xD77A, 0xC595, 0xD77B, 0xC596, 0xD77E, 0xC597, 0xD77F, 0xC598, 0xD780, 0xC599, 0xD782, 0xC59A, 0xD783, + 0xC59B, 0xD784, 0xC59C, 0xD785, 0xC59D, 0xD786, 0xC59E, 0xD787, 0xC59F, 0xD78A, 0xC5A0, 0xD78B, 0xC5A1, 0xD044, 0xC5A2, 0xD045, + 0xC5A3, 0xD047, 0xC5A4, 0xD049, 0xC5A5, 0xD050, 0xC5A6, 0xD054, 0xC5A7, 0xD058, 0xC5A8, 0xD060, 0xC5A9, 0xD06C, 0xC5AA, 0xD06D, + 0xC5AB, 0xD070, 0xC5AC, 0xD074, 0xC5AD, 0xD07C, 0xC5AE, 0xD07D, 0xC5AF, 0xD081, 0xC5B0, 0xD0A4, 0xC5B1, 0xD0A5, 0xC5B2, 0xD0A8, + 0xC5B3, 0xD0AC, 0xC5B4, 0xD0B4, 0xC5B5, 0xD0B5, 0xC5B6, 0xD0B7, 0xC5B7, 0xD0B9, 0xC5B8, 0xD0C0, 0xC5B9, 0xD0C1, 0xC5BA, 0xD0C4, + 0xC5BB, 0xD0C8, 0xC5BC, 0xD0C9, 0xC5BD, 0xD0D0, 0xC5BE, 0xD0D1, 0xC5BF, 0xD0D3, 0xC5C0, 0xD0D4, 0xC5C1, 0xD0D5, 0xC5C2, 0xD0DC, + 0xC5C3, 0xD0DD, 0xC5C4, 0xD0E0, 0xC5C5, 0xD0E4, 0xC5C6, 0xD0EC, 0xC5C7, 0xD0ED, 0xC5C8, 0xD0EF, 0xC5C9, 0xD0F0, 0xC5CA, 0xD0F1, + 0xC5CB, 0xD0F8, 0xC5CC, 0xD10D, 0xC5CD, 0xD130, 0xC5CE, 0xD131, 0xC5CF, 0xD134, 0xC5D0, 0xD138, 0xC5D1, 0xD13A, 0xC5D2, 0xD140, + 0xC5D3, 0xD141, 0xC5D4, 0xD143, 0xC5D5, 0xD144, 0xC5D6, 0xD145, 0xC5D7, 0xD14C, 0xC5D8, 0xD14D, 0xC5D9, 0xD150, 0xC5DA, 0xD154, + 0xC5DB, 0xD15C, 0xC5DC, 0xD15D, 0xC5DD, 0xD15F, 0xC5DE, 0xD161, 0xC5DF, 0xD168, 0xC5E0, 0xD16C, 0xC5E1, 0xD17C, 0xC5E2, 0xD184, + 0xC5E3, 0xD188, 0xC5E4, 0xD1A0, 0xC5E5, 0xD1A1, 0xC5E6, 0xD1A4, 0xC5E7, 0xD1A8, 0xC5E8, 0xD1B0, 0xC5E9, 0xD1B1, 0xC5EA, 0xD1B3, + 0xC5EB, 0xD1B5, 0xC5EC, 0xD1BA, 0xC5ED, 0xD1BC, 0xC5EE, 0xD1C0, 0xC5EF, 0xD1D8, 0xC5F0, 0xD1F4, 0xC5F1, 0xD1F8, 0xC5F2, 0xD207, + 0xC5F3, 0xD209, 0xC5F4, 0xD210, 0xC5F5, 0xD22C, 0xC5F6, 0xD22D, 0xC5F7, 0xD230, 0xC5F8, 0xD234, 0xC5F9, 0xD23C, 0xC5FA, 0xD23D, + 0xC5FB, 0xD23F, 0xC5FC, 0xD241, 0xC5FD, 0xD248, 0xC5FE, 0xD25C, 0xC641, 0xD78D, 0xC642, 0xD78E, 0xC643, 0xD78F, 0xC644, 0xD791, + 0xC645, 0xD792, 0xC646, 0xD793, 0xC647, 0xD794, 0xC648, 0xD795, 0xC649, 0xD796, 0xC64A, 0xD797, 0xC64B, 0xD79A, 0xC64C, 0xD79C, + 0xC64D, 0xD79E, 0xC64E, 0xD79F, 0xC64F, 0xD7A0, 0xC650, 0xD7A1, 0xC651, 0xD7A2, 0xC652, 0xD7A3, 0xC6A1, 0xD264, 0xC6A2, 0xD280, + 0xC6A3, 0xD281, 0xC6A4, 0xD284, 0xC6A5, 0xD288, 0xC6A6, 0xD290, 0xC6A7, 0xD291, 0xC6A8, 0xD295, 0xC6A9, 0xD29C, 0xC6AA, 0xD2A0, + 0xC6AB, 0xD2A4, 0xC6AC, 0xD2AC, 0xC6AD, 0xD2B1, 0xC6AE, 0xD2B8, 0xC6AF, 0xD2B9, 0xC6B0, 0xD2BC, 0xC6B1, 0xD2BF, 0xC6B2, 0xD2C0, + 0xC6B3, 0xD2C2, 0xC6B4, 0xD2C8, 0xC6B5, 0xD2C9, 0xC6B6, 0xD2CB, 0xC6B7, 0xD2D4, 0xC6B8, 0xD2D8, 0xC6B9, 0xD2DC, 0xC6BA, 0xD2E4, + 0xC6BB, 0xD2E5, 0xC6BC, 0xD2F0, 0xC6BD, 0xD2F1, 0xC6BE, 0xD2F4, 0xC6BF, 0xD2F8, 0xC6C0, 0xD300, 0xC6C1, 0xD301, 0xC6C2, 0xD303, + 0xC6C3, 0xD305, 0xC6C4, 0xD30C, 0xC6C5, 0xD30D, 0xC6C6, 0xD30E, 0xC6C7, 0xD310, 0xC6C8, 0xD314, 0xC6C9, 0xD316, 0xC6CA, 0xD31C, + 0xC6CB, 0xD31D, 0xC6CC, 0xD31F, 0xC6CD, 0xD320, 0xC6CE, 0xD321, 0xC6CF, 0xD325, 0xC6D0, 0xD328, 0xC6D1, 0xD329, 0xC6D2, 0xD32C, + 0xC6D3, 0xD330, 0xC6D4, 0xD338, 0xC6D5, 0xD339, 0xC6D6, 0xD33B, 0xC6D7, 0xD33C, 0xC6D8, 0xD33D, 0xC6D9, 0xD344, 0xC6DA, 0xD345, + 0xC6DB, 0xD37C, 0xC6DC, 0xD37D, 0xC6DD, 0xD380, 0xC6DE, 0xD384, 0xC6DF, 0xD38C, 0xC6E0, 0xD38D, 0xC6E1, 0xD38F, 0xC6E2, 0xD390, + 0xC6E3, 0xD391, 0xC6E4, 0xD398, 0xC6E5, 0xD399, 0xC6E6, 0xD39C, 0xC6E7, 0xD3A0, 0xC6E8, 0xD3A8, 0xC6E9, 0xD3A9, 0xC6EA, 0xD3AB, + 0xC6EB, 0xD3AD, 0xC6EC, 0xD3B4, 0xC6ED, 0xD3B8, 0xC6EE, 0xD3BC, 0xC6EF, 0xD3C4, 0xC6F0, 0xD3C5, 0xC6F1, 0xD3C8, 0xC6F2, 0xD3C9, + 0xC6F3, 0xD3D0, 0xC6F4, 0xD3D8, 0xC6F5, 0xD3E1, 0xC6F6, 0xD3E3, 0xC6F7, 0xD3EC, 0xC6F8, 0xD3ED, 0xC6F9, 0xD3F0, 0xC6FA, 0xD3F4, + 0xC6FB, 0xD3FC, 0xC6FC, 0xD3FD, 0xC6FD, 0xD3FF, 0xC6FE, 0xD401, 0xC7A1, 0xD408, 0xC7A2, 0xD41D, 0xC7A3, 0xD440, 0xC7A4, 0xD444, + 0xC7A5, 0xD45C, 0xC7A6, 0xD460, 0xC7A7, 0xD464, 0xC7A8, 0xD46D, 0xC7A9, 0xD46F, 0xC7AA, 0xD478, 0xC7AB, 0xD479, 0xC7AC, 0xD47C, + 0xC7AD, 0xD47F, 0xC7AE, 0xD480, 0xC7AF, 0xD482, 0xC7B0, 0xD488, 0xC7B1, 0xD489, 0xC7B2, 0xD48B, 0xC7B3, 0xD48D, 0xC7B4, 0xD494, + 0xC7B5, 0xD4A9, 0xC7B6, 0xD4CC, 0xC7B7, 0xD4D0, 0xC7B8, 0xD4D4, 0xC7B9, 0xD4DC, 0xC7BA, 0xD4DF, 0xC7BB, 0xD4E8, 0xC7BC, 0xD4EC, + 0xC7BD, 0xD4F0, 0xC7BE, 0xD4F8, 0xC7BF, 0xD4FB, 0xC7C0, 0xD4FD, 0xC7C1, 0xD504, 0xC7C2, 0xD508, 0xC7C3, 0xD50C, 0xC7C4, 0xD514, + 0xC7C5, 0xD515, 0xC7C6, 0xD517, 0xC7C7, 0xD53C, 0xC7C8, 0xD53D, 0xC7C9, 0xD540, 0xC7CA, 0xD544, 0xC7CB, 0xD54C, 0xC7CC, 0xD54D, + 0xC7CD, 0xD54F, 0xC7CE, 0xD551, 0xC7CF, 0xD558, 0xC7D0, 0xD559, 0xC7D1, 0xD55C, 0xC7D2, 0xD560, 0xC7D3, 0xD565, 0xC7D4, 0xD568, + 0xC7D5, 0xD569, 0xC7D6, 0xD56B, 0xC7D7, 0xD56D, 0xC7D8, 0xD574, 0xC7D9, 0xD575, 0xC7DA, 0xD578, 0xC7DB, 0xD57C, 0xC7DC, 0xD584, + 0xC7DD, 0xD585, 0xC7DE, 0xD587, 0xC7DF, 0xD588, 0xC7E0, 0xD589, 0xC7E1, 0xD590, 0xC7E2, 0xD5A5, 0xC7E3, 0xD5C8, 0xC7E4, 0xD5C9, + 0xC7E5, 0xD5CC, 0xC7E6, 0xD5D0, 0xC7E7, 0xD5D2, 0xC7E8, 0xD5D8, 0xC7E9, 0xD5D9, 0xC7EA, 0xD5DB, 0xC7EB, 0xD5DD, 0xC7EC, 0xD5E4, + 0xC7ED, 0xD5E5, 0xC7EE, 0xD5E8, 0xC7EF, 0xD5EC, 0xC7F0, 0xD5F4, 0xC7F1, 0xD5F5, 0xC7F2, 0xD5F7, 0xC7F3, 0xD5F9, 0xC7F4, 0xD600, + 0xC7F5, 0xD601, 0xC7F6, 0xD604, 0xC7F7, 0xD608, 0xC7F8, 0xD610, 0xC7F9, 0xD611, 0xC7FA, 0xD613, 0xC7FB, 0xD614, 0xC7FC, 0xD615, + 0xC7FD, 0xD61C, 0xC7FE, 0xD620, 0xC8A1, 0xD624, 0xC8A2, 0xD62D, 0xC8A3, 0xD638, 0xC8A4, 0xD639, 0xC8A5, 0xD63C, 0xC8A6, 0xD640, + 0xC8A7, 0xD645, 0xC8A8, 0xD648, 0xC8A9, 0xD649, 0xC8AA, 0xD64B, 0xC8AB, 0xD64D, 0xC8AC, 0xD651, 0xC8AD, 0xD654, 0xC8AE, 0xD655, + 0xC8AF, 0xD658, 0xC8B0, 0xD65C, 0xC8B1, 0xD667, 0xC8B2, 0xD669, 0xC8B3, 0xD670, 0xC8B4, 0xD671, 0xC8B5, 0xD674, 0xC8B6, 0xD683, + 0xC8B7, 0xD685, 0xC8B8, 0xD68C, 0xC8B9, 0xD68D, 0xC8BA, 0xD690, 0xC8BB, 0xD694, 0xC8BC, 0xD69D, 0xC8BD, 0xD69F, 0xC8BE, 0xD6A1, + 0xC8BF, 0xD6A8, 0xC8C0, 0xD6AC, 0xC8C1, 0xD6B0, 0xC8C2, 0xD6B9, 0xC8C3, 0xD6BB, 0xC8C4, 0xD6C4, 0xC8C5, 0xD6C5, 0xC8C6, 0xD6C8, + 0xC8C7, 0xD6CC, 0xC8C8, 0xD6D1, 0xC8C9, 0xD6D4, 0xC8CA, 0xD6D7, 0xC8CB, 0xD6D9, 0xC8CC, 0xD6E0, 0xC8CD, 0xD6E4, 0xC8CE, 0xD6E8, + 0xC8CF, 0xD6F0, 0xC8D0, 0xD6F5, 0xC8D1, 0xD6FC, 0xC8D2, 0xD6FD, 0xC8D3, 0xD700, 0xC8D4, 0xD704, 0xC8D5, 0xD711, 0xC8D6, 0xD718, + 0xC8D7, 0xD719, 0xC8D8, 0xD71C, 0xC8D9, 0xD720, 0xC8DA, 0xD728, 0xC8DB, 0xD729, 0xC8DC, 0xD72B, 0xC8DD, 0xD72D, 0xC8DE, 0xD734, + 0xC8DF, 0xD735, 0xC8E0, 0xD738, 0xC8E1, 0xD73C, 0xC8E2, 0xD744, 0xC8E3, 0xD747, 0xC8E4, 0xD749, 0xC8E5, 0xD750, 0xC8E6, 0xD751, + 0xC8E7, 0xD754, 0xC8E8, 0xD756, 0xC8E9, 0xD757, 0xC8EA, 0xD758, 0xC8EB, 0xD759, 0xC8EC, 0xD760, 0xC8ED, 0xD761, 0xC8EE, 0xD763, + 0xC8EF, 0xD765, 0xC8F0, 0xD769, 0xC8F1, 0xD76C, 0xC8F2, 0xD770, 0xC8F3, 0xD774, 0xC8F4, 0xD77C, 0xC8F5, 0xD77D, 0xC8F6, 0xD781, + 0xC8F7, 0xD788, 0xC8F8, 0xD789, 0xC8F9, 0xD78C, 0xC8FA, 0xD790, 0xC8FB, 0xD798, 0xC8FC, 0xD799, 0xC8FD, 0xD79B, 0xC8FE, 0xD79D, + 0xCAA1, 0x4F3D, 0xCAA2, 0x4F73, 0xCAA3, 0x5047, 0xCAA4, 0x50F9, 0xCAA5, 0x52A0, 0xCAA6, 0x53EF, 0xCAA7, 0x5475, 0xCAA8, 0x54E5, + 0xCAA9, 0x5609, 0xCAAA, 0x5AC1, 0xCAAB, 0x5BB6, 0xCAAC, 0x6687, 0xCAAD, 0x67B6, 0xCAAE, 0x67B7, 0xCAAF, 0x67EF, 0xCAB0, 0x6B4C, + 0xCAB1, 0x73C2, 0xCAB2, 0x75C2, 0xCAB3, 0x7A3C, 0xCAB4, 0x82DB, 0xCAB5, 0x8304, 0xCAB6, 0x8857, 0xCAB7, 0x8888, 0xCAB8, 0x8A36, + 0xCAB9, 0x8CC8, 0xCABA, 0x8DCF, 0xCABB, 0x8EFB, 0xCABC, 0x8FE6, 0xCABD, 0x99D5, 0xCABE, 0x523B, 0xCABF, 0x5374, 0xCAC0, 0x5404, + 0xCAC1, 0x606A, 0xCAC2, 0x6164, 0xCAC3, 0x6BBC, 0xCAC4, 0x73CF, 0xCAC5, 0x811A, 0xCAC6, 0x89BA, 0xCAC7, 0x89D2, 0xCAC8, 0x95A3, + 0xCAC9, 0x4F83, 0xCACA, 0x520A, 0xCACB, 0x58BE, 0xCACC, 0x5978, 0xCACD, 0x59E6, 0xCACE, 0x5E72, 0xCACF, 0x5E79, 0xCAD0, 0x61C7, + 0xCAD1, 0x63C0, 0xCAD2, 0x6746, 0xCAD3, 0x67EC, 0xCAD4, 0x687F, 0xCAD5, 0x6F97, 0xCAD6, 0x764E, 0xCAD7, 0x770B, 0xCAD8, 0x78F5, + 0xCAD9, 0x7A08, 0xCADA, 0x7AFF, 0xCADB, 0x7C21, 0xCADC, 0x809D, 0xCADD, 0x826E, 0xCADE, 0x8271, 0xCADF, 0x8AEB, 0xCAE0, 0x9593, + 0xCAE1, 0x4E6B, 0xCAE2, 0x559D, 0xCAE3, 0x66F7, 0xCAE4, 0x6E34, 0xCAE5, 0x78A3, 0xCAE6, 0x7AED, 0xCAE7, 0x845B, 0xCAE8, 0x8910, + 0xCAE9, 0x874E, 0xCAEA, 0x97A8, 0xCAEB, 0x52D8, 0xCAEC, 0x574E, 0xCAED, 0x582A, 0xCAEE, 0x5D4C, 0xCAEF, 0x611F, 0xCAF0, 0x61BE, + 0xCAF1, 0x6221, 0xCAF2, 0x6562, 0xCAF3, 0x67D1, 0xCAF4, 0x6A44, 0xCAF5, 0x6E1B, 0xCAF6, 0x7518, 0xCAF7, 0x75B3, 0xCAF8, 0x76E3, + 0xCAF9, 0x77B0, 0xCAFA, 0x7D3A, 0xCAFB, 0x90AF, 0xCAFC, 0x9451, 0xCAFD, 0x9452, 0xCAFE, 0x9F95, 0xCBA1, 0x5323, 0xCBA2, 0x5CAC, + 0xCBA3, 0x7532, 0xCBA4, 0x80DB, 0xCBA5, 0x9240, 0xCBA6, 0x9598, 0xCBA7, 0x525B, 0xCBA8, 0x5808, 0xCBA9, 0x59DC, 0xCBAA, 0x5CA1, + 0xCBAB, 0x5D17, 0xCBAC, 0x5EB7, 0xCBAD, 0x5F3A, 0xCBAE, 0x5F4A, 0xCBAF, 0x6177, 0xCBB0, 0x6C5F, 0xCBB1, 0x757A, 0xCBB2, 0x7586, + 0xCBB3, 0x7CE0, 0xCBB4, 0x7D73, 0xCBB5, 0x7DB1, 0xCBB6, 0x7F8C, 0xCBB7, 0x8154, 0xCBB8, 0x8221, 0xCBB9, 0x8591, 0xCBBA, 0x8941, + 0xCBBB, 0x8B1B, 0xCBBC, 0x92FC, 0xCBBD, 0x964D, 0xCBBE, 0x9C47, 0xCBBF, 0x4ECB, 0xCBC0, 0x4EF7, 0xCBC1, 0x500B, 0xCBC2, 0x51F1, + 0xCBC3, 0x584F, 0xCBC4, 0x6137, 0xCBC5, 0x613E, 0xCBC6, 0x6168, 0xCBC7, 0x6539, 0xCBC8, 0x69EA, 0xCBC9, 0x6F11, 0xCBCA, 0x75A5, + 0xCBCB, 0x7686, 0xCBCC, 0x76D6, 0xCBCD, 0x7B87, 0xCBCE, 0x82A5, 0xCBCF, 0x84CB, 0xCBD0, 0xF900, 0xCBD1, 0x93A7, 0xCBD2, 0x958B, + 0xCBD3, 0x5580, 0xCBD4, 0x5BA2, 0xCBD5, 0x5751, 0xCBD6, 0xF901, 0xCBD7, 0x7CB3, 0xCBD8, 0x7FB9, 0xCBD9, 0x91B5, 0xCBDA, 0x5028, + 0xCBDB, 0x53BB, 0xCBDC, 0x5C45, 0xCBDD, 0x5DE8, 0xCBDE, 0x62D2, 0xCBDF, 0x636E, 0xCBE0, 0x64DA, 0xCBE1, 0x64E7, 0xCBE2, 0x6E20, + 0xCBE3, 0x70AC, 0xCBE4, 0x795B, 0xCBE5, 0x8DDD, 0xCBE6, 0x8E1E, 0xCBE7, 0xF902, 0xCBE8, 0x907D, 0xCBE9, 0x9245, 0xCBEA, 0x92F8, + 0xCBEB, 0x4E7E, 0xCBEC, 0x4EF6, 0xCBED, 0x5065, 0xCBEE, 0x5DFE, 0xCBEF, 0x5EFA, 0xCBF0, 0x6106, 0xCBF1, 0x6957, 0xCBF2, 0x8171, + 0xCBF3, 0x8654, 0xCBF4, 0x8E47, 0xCBF5, 0x9375, 0xCBF6, 0x9A2B, 0xCBF7, 0x4E5E, 0xCBF8, 0x5091, 0xCBF9, 0x6770, 0xCBFA, 0x6840, + 0xCBFB, 0x5109, 0xCBFC, 0x528D, 0xCBFD, 0x5292, 0xCBFE, 0x6AA2, 0xCCA1, 0x77BC, 0xCCA2, 0x9210, 0xCCA3, 0x9ED4, 0xCCA4, 0x52AB, + 0xCCA5, 0x602F, 0xCCA6, 0x8FF2, 0xCCA7, 0x5048, 0xCCA8, 0x61A9, 0xCCA9, 0x63ED, 0xCCAA, 0x64CA, 0xCCAB, 0x683C, 0xCCAC, 0x6A84, + 0xCCAD, 0x6FC0, 0xCCAE, 0x8188, 0xCCAF, 0x89A1, 0xCCB0, 0x9694, 0xCCB1, 0x5805, 0xCCB2, 0x727D, 0xCCB3, 0x72AC, 0xCCB4, 0x7504, + 0xCCB5, 0x7D79, 0xCCB6, 0x7E6D, 0xCCB7, 0x80A9, 0xCCB8, 0x898B, 0xCCB9, 0x8B74, 0xCCBA, 0x9063, 0xCCBB, 0x9D51, 0xCCBC, 0x6289, + 0xCCBD, 0x6C7A, 0xCCBE, 0x6F54, 0xCCBF, 0x7D50, 0xCCC0, 0x7F3A, 0xCCC1, 0x8A23, 0xCCC2, 0x517C, 0xCCC3, 0x614A, 0xCCC4, 0x7B9D, + 0xCCC5, 0x8B19, 0xCCC6, 0x9257, 0xCCC7, 0x938C, 0xCCC8, 0x4EAC, 0xCCC9, 0x4FD3, 0xCCCA, 0x501E, 0xCCCB, 0x50BE, 0xCCCC, 0x5106, + 0xCCCD, 0x52C1, 0xCCCE, 0x52CD, 0xCCCF, 0x537F, 0xCCD0, 0x5770, 0xCCD1, 0x5883, 0xCCD2, 0x5E9A, 0xCCD3, 0x5F91, 0xCCD4, 0x6176, + 0xCCD5, 0x61AC, 0xCCD6, 0x64CE, 0xCCD7, 0x656C, 0xCCD8, 0x666F, 0xCCD9, 0x66BB, 0xCCDA, 0x66F4, 0xCCDB, 0x6897, 0xCCDC, 0x6D87, + 0xCCDD, 0x7085, 0xCCDE, 0x70F1, 0xCCDF, 0x749F, 0xCCE0, 0x74A5, 0xCCE1, 0x74CA, 0xCCE2, 0x75D9, 0xCCE3, 0x786C, 0xCCE4, 0x78EC, + 0xCCE5, 0x7ADF, 0xCCE6, 0x7AF6, 0xCCE7, 0x7D45, 0xCCE8, 0x7D93, 0xCCE9, 0x8015, 0xCCEA, 0x803F, 0xCCEB, 0x811B, 0xCCEC, 0x8396, + 0xCCED, 0x8B66, 0xCCEE, 0x8F15, 0xCCEF, 0x9015, 0xCCF0, 0x93E1, 0xCCF1, 0x9803, 0xCCF2, 0x9838, 0xCCF3, 0x9A5A, 0xCCF4, 0x9BE8, + 0xCCF5, 0x4FC2, 0xCCF6, 0x5553, 0xCCF7, 0x583A, 0xCCF8, 0x5951, 0xCCF9, 0x5B63, 0xCCFA, 0x5C46, 0xCCFB, 0x60B8, 0xCCFC, 0x6212, + 0xCCFD, 0x6842, 0xCCFE, 0x68B0, 0xCDA1, 0x68E8, 0xCDA2, 0x6EAA, 0xCDA3, 0x754C, 0xCDA4, 0x7678, 0xCDA5, 0x78CE, 0xCDA6, 0x7A3D, + 0xCDA7, 0x7CFB, 0xCDA8, 0x7E6B, 0xCDA9, 0x7E7C, 0xCDAA, 0x8A08, 0xCDAB, 0x8AA1, 0xCDAC, 0x8C3F, 0xCDAD, 0x968E, 0xCDAE, 0x9DC4, + 0xCDAF, 0x53E4, 0xCDB0, 0x53E9, 0xCDB1, 0x544A, 0xCDB2, 0x5471, 0xCDB3, 0x56FA, 0xCDB4, 0x59D1, 0xCDB5, 0x5B64, 0xCDB6, 0x5C3B, + 0xCDB7, 0x5EAB, 0xCDB8, 0x62F7, 0xCDB9, 0x6537, 0xCDBA, 0x6545, 0xCDBB, 0x6572, 0xCDBC, 0x66A0, 0xCDBD, 0x67AF, 0xCDBE, 0x69C1, + 0xCDBF, 0x6CBD, 0xCDC0, 0x75FC, 0xCDC1, 0x7690, 0xCDC2, 0x777E, 0xCDC3, 0x7A3F, 0xCDC4, 0x7F94, 0xCDC5, 0x8003, 0xCDC6, 0x80A1, + 0xCDC7, 0x818F, 0xCDC8, 0x82E6, 0xCDC9, 0x82FD, 0xCDCA, 0x83F0, 0xCDCB, 0x85C1, 0xCDCC, 0x8831, 0xCDCD, 0x88B4, 0xCDCE, 0x8AA5, + 0xCDCF, 0xF903, 0xCDD0, 0x8F9C, 0xCDD1, 0x932E, 0xCDD2, 0x96C7, 0xCDD3, 0x9867, 0xCDD4, 0x9AD8, 0xCDD5, 0x9F13, 0xCDD6, 0x54ED, + 0xCDD7, 0x659B, 0xCDD8, 0x66F2, 0xCDD9, 0x688F, 0xCDDA, 0x7A40, 0xCDDB, 0x8C37, 0xCDDC, 0x9D60, 0xCDDD, 0x56F0, 0xCDDE, 0x5764, + 0xCDDF, 0x5D11, 0xCDE0, 0x6606, 0xCDE1, 0x68B1, 0xCDE2, 0x68CD, 0xCDE3, 0x6EFE, 0xCDE4, 0x7428, 0xCDE5, 0x889E, 0xCDE6, 0x9BE4, + 0xCDE7, 0x6C68, 0xCDE8, 0xF904, 0xCDE9, 0x9AA8, 0xCDEA, 0x4F9B, 0xCDEB, 0x516C, 0xCDEC, 0x5171, 0xCDED, 0x529F, 0xCDEE, 0x5B54, + 0xCDEF, 0x5DE5, 0xCDF0, 0x6050, 0xCDF1, 0x606D, 0xCDF2, 0x62F1, 0xCDF3, 0x63A7, 0xCDF4, 0x653B, 0xCDF5, 0x73D9, 0xCDF6, 0x7A7A, + 0xCDF7, 0x86A3, 0xCDF8, 0x8CA2, 0xCDF9, 0x978F, 0xCDFA, 0x4E32, 0xCDFB, 0x5BE1, 0xCDFC, 0x6208, 0xCDFD, 0x679C, 0xCDFE, 0x74DC, + 0xCEA1, 0x79D1, 0xCEA2, 0x83D3, 0xCEA3, 0x8A87, 0xCEA4, 0x8AB2, 0xCEA5, 0x8DE8, 0xCEA6, 0x904E, 0xCEA7, 0x934B, 0xCEA8, 0x9846, + 0xCEA9, 0x5ED3, 0xCEAA, 0x69E8, 0xCEAB, 0x85FF, 0xCEAC, 0x90ED, 0xCEAD, 0xF905, 0xCEAE, 0x51A0, 0xCEAF, 0x5B98, 0xCEB0, 0x5BEC, + 0xCEB1, 0x6163, 0xCEB2, 0x68FA, 0xCEB3, 0x6B3E, 0xCEB4, 0x704C, 0xCEB5, 0x742F, 0xCEB6, 0x74D8, 0xCEB7, 0x7BA1, 0xCEB8, 0x7F50, + 0xCEB9, 0x83C5, 0xCEBA, 0x89C0, 0xCEBB, 0x8CAB, 0xCEBC, 0x95DC, 0xCEBD, 0x9928, 0xCEBE, 0x522E, 0xCEBF, 0x605D, 0xCEC0, 0x62EC, + 0xCEC1, 0x9002, 0xCEC2, 0x4F8A, 0xCEC3, 0x5149, 0xCEC4, 0x5321, 0xCEC5, 0x58D9, 0xCEC6, 0x5EE3, 0xCEC7, 0x66E0, 0xCEC8, 0x6D38, + 0xCEC9, 0x709A, 0xCECA, 0x72C2, 0xCECB, 0x73D6, 0xCECC, 0x7B50, 0xCECD, 0x80F1, 0xCECE, 0x945B, 0xCECF, 0x5366, 0xCED0, 0x639B, + 0xCED1, 0x7F6B, 0xCED2, 0x4E56, 0xCED3, 0x5080, 0xCED4, 0x584A, 0xCED5, 0x58DE, 0xCED6, 0x602A, 0xCED7, 0x6127, 0xCED8, 0x62D0, + 0xCED9, 0x69D0, 0xCEDA, 0x9B41, 0xCEDB, 0x5B8F, 0xCEDC, 0x7D18, 0xCEDD, 0x80B1, 0xCEDE, 0x8F5F, 0xCEDF, 0x4EA4, 0xCEE0, 0x50D1, + 0xCEE1, 0x54AC, 0xCEE2, 0x55AC, 0xCEE3, 0x5B0C, 0xCEE4, 0x5DA0, 0xCEE5, 0x5DE7, 0xCEE6, 0x652A, 0xCEE7, 0x654E, 0xCEE8, 0x6821, + 0xCEE9, 0x6A4B, 0xCEEA, 0x72E1, 0xCEEB, 0x768E, 0xCEEC, 0x77EF, 0xCEED, 0x7D5E, 0xCEEE, 0x7FF9, 0xCEEF, 0x81A0, 0xCEF0, 0x854E, + 0xCEF1, 0x86DF, 0xCEF2, 0x8F03, 0xCEF3, 0x8F4E, 0xCEF4, 0x90CA, 0xCEF5, 0x9903, 0xCEF6, 0x9A55, 0xCEF7, 0x9BAB, 0xCEF8, 0x4E18, + 0xCEF9, 0x4E45, 0xCEFA, 0x4E5D, 0xCEFB, 0x4EC7, 0xCEFC, 0x4FF1, 0xCEFD, 0x5177, 0xCEFE, 0x52FE, 0xCFA1, 0x5340, 0xCFA2, 0x53E3, + 0xCFA3, 0x53E5, 0xCFA4, 0x548E, 0xCFA5, 0x5614, 0xCFA6, 0x5775, 0xCFA7, 0x57A2, 0xCFA8, 0x5BC7, 0xCFA9, 0x5D87, 0xCFAA, 0x5ED0, + 0xCFAB, 0x61FC, 0xCFAC, 0x62D8, 0xCFAD, 0x6551, 0xCFAE, 0x67B8, 0xCFAF, 0x67E9, 0xCFB0, 0x69CB, 0xCFB1, 0x6B50, 0xCFB2, 0x6BC6, + 0xCFB3, 0x6BEC, 0xCFB4, 0x6C42, 0xCFB5, 0x6E9D, 0xCFB6, 0x7078, 0xCFB7, 0x72D7, 0xCFB8, 0x7396, 0xCFB9, 0x7403, 0xCFBA, 0x77BF, + 0xCFBB, 0x77E9, 0xCFBC, 0x7A76, 0xCFBD, 0x7D7F, 0xCFBE, 0x8009, 0xCFBF, 0x81FC, 0xCFC0, 0x8205, 0xCFC1, 0x820A, 0xCFC2, 0x82DF, + 0xCFC3, 0x8862, 0xCFC4, 0x8B33, 0xCFC5, 0x8CFC, 0xCFC6, 0x8EC0, 0xCFC7, 0x9011, 0xCFC8, 0x90B1, 0xCFC9, 0x9264, 0xCFCA, 0x92B6, + 0xCFCB, 0x99D2, 0xCFCC, 0x9A45, 0xCFCD, 0x9CE9, 0xCFCE, 0x9DD7, 0xCFCF, 0x9F9C, 0xCFD0, 0x570B, 0xCFD1, 0x5C40, 0xCFD2, 0x83CA, + 0xCFD3, 0x97A0, 0xCFD4, 0x97AB, 0xCFD5, 0x9EB4, 0xCFD6, 0x541B, 0xCFD7, 0x7A98, 0xCFD8, 0x7FA4, 0xCFD9, 0x88D9, 0xCFDA, 0x8ECD, + 0xCFDB, 0x90E1, 0xCFDC, 0x5800, 0xCFDD, 0x5C48, 0xCFDE, 0x6398, 0xCFDF, 0x7A9F, 0xCFE0, 0x5BAE, 0xCFE1, 0x5F13, 0xCFE2, 0x7A79, + 0xCFE3, 0x7AAE, 0xCFE4, 0x828E, 0xCFE5, 0x8EAC, 0xCFE6, 0x5026, 0xCFE7, 0x5238, 0xCFE8, 0x52F8, 0xCFE9, 0x5377, 0xCFEA, 0x5708, + 0xCFEB, 0x62F3, 0xCFEC, 0x6372, 0xCFED, 0x6B0A, 0xCFEE, 0x6DC3, 0xCFEF, 0x7737, 0xCFF0, 0x53A5, 0xCFF1, 0x7357, 0xCFF2, 0x8568, + 0xCFF3, 0x8E76, 0xCFF4, 0x95D5, 0xCFF5, 0x673A, 0xCFF6, 0x6AC3, 0xCFF7, 0x6F70, 0xCFF8, 0x8A6D, 0xCFF9, 0x8ECC, 0xCFFA, 0x994B, + 0xCFFB, 0xF906, 0xCFFC, 0x6677, 0xCFFD, 0x6B78, 0xCFFE, 0x8CB4, 0xD0A1, 0x9B3C, 0xD0A2, 0xF907, 0xD0A3, 0x53EB, 0xD0A4, 0x572D, + 0xD0A5, 0x594E, 0xD0A6, 0x63C6, 0xD0A7, 0x69FB, 0xD0A8, 0x73EA, 0xD0A9, 0x7845, 0xD0AA, 0x7ABA, 0xD0AB, 0x7AC5, 0xD0AC, 0x7CFE, + 0xD0AD, 0x8475, 0xD0AE, 0x898F, 0xD0AF, 0x8D73, 0xD0B0, 0x9035, 0xD0B1, 0x95A8, 0xD0B2, 0x52FB, 0xD0B3, 0x5747, 0xD0B4, 0x7547, + 0xD0B5, 0x7B60, 0xD0B6, 0x83CC, 0xD0B7, 0x921E, 0xD0B8, 0xF908, 0xD0B9, 0x6A58, 0xD0BA, 0x514B, 0xD0BB, 0x524B, 0xD0BC, 0x5287, + 0xD0BD, 0x621F, 0xD0BE, 0x68D8, 0xD0BF, 0x6975, 0xD0C0, 0x9699, 0xD0C1, 0x50C5, 0xD0C2, 0x52A4, 0xD0C3, 0x52E4, 0xD0C4, 0x61C3, + 0xD0C5, 0x65A4, 0xD0C6, 0x6839, 0xD0C7, 0x69FF, 0xD0C8, 0x747E, 0xD0C9, 0x7B4B, 0xD0CA, 0x82B9, 0xD0CB, 0x83EB, 0xD0CC, 0x89B2, + 0xD0CD, 0x8B39, 0xD0CE, 0x8FD1, 0xD0CF, 0x9949, 0xD0D0, 0xF909, 0xD0D1, 0x4ECA, 0xD0D2, 0x5997, 0xD0D3, 0x64D2, 0xD0D4, 0x6611, + 0xD0D5, 0x6A8E, 0xD0D6, 0x7434, 0xD0D7, 0x7981, 0xD0D8, 0x79BD, 0xD0D9, 0x82A9, 0xD0DA, 0x887E, 0xD0DB, 0x887F, 0xD0DC, 0x895F, + 0xD0DD, 0xF90A, 0xD0DE, 0x9326, 0xD0DF, 0x4F0B, 0xD0E0, 0x53CA, 0xD0E1, 0x6025, 0xD0E2, 0x6271, 0xD0E3, 0x6C72, 0xD0E4, 0x7D1A, + 0xD0E5, 0x7D66, 0xD0E6, 0x4E98, 0xD0E7, 0x5162, 0xD0E8, 0x77DC, 0xD0E9, 0x80AF, 0xD0EA, 0x4F01, 0xD0EB, 0x4F0E, 0xD0EC, 0x5176, + 0xD0ED, 0x5180, 0xD0EE, 0x55DC, 0xD0EF, 0x5668, 0xD0F0, 0x573B, 0xD0F1, 0x57FA, 0xD0F2, 0x57FC, 0xD0F3, 0x5914, 0xD0F4, 0x5947, + 0xD0F5, 0x5993, 0xD0F6, 0x5BC4, 0xD0F7, 0x5C90, 0xD0F8, 0x5D0E, 0xD0F9, 0x5DF1, 0xD0FA, 0x5E7E, 0xD0FB, 0x5FCC, 0xD0FC, 0x6280, + 0xD0FD, 0x65D7, 0xD0FE, 0x65E3, 0xD1A1, 0x671E, 0xD1A2, 0x671F, 0xD1A3, 0x675E, 0xD1A4, 0x68CB, 0xD1A5, 0x68C4, 0xD1A6, 0x6A5F, + 0xD1A7, 0x6B3A, 0xD1A8, 0x6C23, 0xD1A9, 0x6C7D, 0xD1AA, 0x6C82, 0xD1AB, 0x6DC7, 0xD1AC, 0x7398, 0xD1AD, 0x7426, 0xD1AE, 0x742A, + 0xD1AF, 0x7482, 0xD1B0, 0x74A3, 0xD1B1, 0x7578, 0xD1B2, 0x757F, 0xD1B3, 0x7881, 0xD1B4, 0x78EF, 0xD1B5, 0x7941, 0xD1B6, 0x7947, + 0xD1B7, 0x7948, 0xD1B8, 0x797A, 0xD1B9, 0x7B95, 0xD1BA, 0x7D00, 0xD1BB, 0x7DBA, 0xD1BC, 0x7F88, 0xD1BD, 0x8006, 0xD1BE, 0x802D, + 0xD1BF, 0x808C, 0xD1C0, 0x8A18, 0xD1C1, 0x8B4F, 0xD1C2, 0x8C48, 0xD1C3, 0x8D77, 0xD1C4, 0x9321, 0xD1C5, 0x9324, 0xD1C6, 0x98E2, + 0xD1C7, 0x9951, 0xD1C8, 0x9A0E, 0xD1C9, 0x9A0F, 0xD1CA, 0x9A65, 0xD1CB, 0x9E92, 0xD1CC, 0x7DCA, 0xD1CD, 0x4F76, 0xD1CE, 0x5409, + 0xD1CF, 0x62EE, 0xD1D0, 0x6854, 0xD1D1, 0x91D1, 0xD1D2, 0x55AB, 0xD1D3, 0x513A, 0xD1D4, 0xF90B, 0xD1D5, 0xF90C, 0xD1D6, 0x5A1C, + 0xD1D7, 0x61E6, 0xD1D8, 0xF90D, 0xD1D9, 0x62CF, 0xD1DA, 0x62FF, 0xD1DB, 0xF90E, 0xD1DC, 0xF90F, 0xD1DD, 0xF910, 0xD1DE, 0xF911, + 0xD1DF, 0xF912, 0xD1E0, 0xF913, 0xD1E1, 0x90A3, 0xD1E2, 0xF914, 0xD1E3, 0xF915, 0xD1E4, 0xF916, 0xD1E5, 0xF917, 0xD1E6, 0xF918, + 0xD1E7, 0x8AFE, 0xD1E8, 0xF919, 0xD1E9, 0xF91A, 0xD1EA, 0xF91B, 0xD1EB, 0xF91C, 0xD1EC, 0x6696, 0xD1ED, 0xF91D, 0xD1EE, 0x7156, + 0xD1EF, 0xF91E, 0xD1F0, 0xF91F, 0xD1F1, 0x96E3, 0xD1F2, 0xF920, 0xD1F3, 0x634F, 0xD1F4, 0x637A, 0xD1F5, 0x5357, 0xD1F6, 0xF921, + 0xD1F7, 0x678F, 0xD1F8, 0x6960, 0xD1F9, 0x6E73, 0xD1FA, 0xF922, 0xD1FB, 0x7537, 0xD1FC, 0xF923, 0xD1FD, 0xF924, 0xD1FE, 0xF925, + 0xD2A1, 0x7D0D, 0xD2A2, 0xF926, 0xD2A3, 0xF927, 0xD2A4, 0x8872, 0xD2A5, 0x56CA, 0xD2A6, 0x5A18, 0xD2A7, 0xF928, 0xD2A8, 0xF929, + 0xD2A9, 0xF92A, 0xD2AA, 0xF92B, 0xD2AB, 0xF92C, 0xD2AC, 0x4E43, 0xD2AD, 0xF92D, 0xD2AE, 0x5167, 0xD2AF, 0x5948, 0xD2B0, 0x67F0, + 0xD2B1, 0x8010, 0xD2B2, 0xF92E, 0xD2B3, 0x5973, 0xD2B4, 0x5E74, 0xD2B5, 0x649A, 0xD2B6, 0x79CA, 0xD2B7, 0x5FF5, 0xD2B8, 0x606C, + 0xD2B9, 0x62C8, 0xD2BA, 0x637B, 0xD2BB, 0x5BE7, 0xD2BC, 0x5BD7, 0xD2BD, 0x52AA, 0xD2BE, 0xF92F, 0xD2BF, 0x5974, 0xD2C0, 0x5F29, + 0xD2C1, 0x6012, 0xD2C2, 0xF930, 0xD2C3, 0xF931, 0xD2C4, 0xF932, 0xD2C5, 0x7459, 0xD2C6, 0xF933, 0xD2C7, 0xF934, 0xD2C8, 0xF935, + 0xD2C9, 0xF936, 0xD2CA, 0xF937, 0xD2CB, 0xF938, 0xD2CC, 0x99D1, 0xD2CD, 0xF939, 0xD2CE, 0xF93A, 0xD2CF, 0xF93B, 0xD2D0, 0xF93C, + 0xD2D1, 0xF93D, 0xD2D2, 0xF93E, 0xD2D3, 0xF93F, 0xD2D4, 0xF940, 0xD2D5, 0xF941, 0xD2D6, 0xF942, 0xD2D7, 0xF943, 0xD2D8, 0x6FC3, + 0xD2D9, 0xF944, 0xD2DA, 0xF945, 0xD2DB, 0x81BF, 0xD2DC, 0x8FB2, 0xD2DD, 0x60F1, 0xD2DE, 0xF946, 0xD2DF, 0xF947, 0xD2E0, 0x8166, + 0xD2E1, 0xF948, 0xD2E2, 0xF949, 0xD2E3, 0x5C3F, 0xD2E4, 0xF94A, 0xD2E5, 0xF94B, 0xD2E6, 0xF94C, 0xD2E7, 0xF94D, 0xD2E8, 0xF94E, + 0xD2E9, 0xF94F, 0xD2EA, 0xF950, 0xD2EB, 0xF951, 0xD2EC, 0x5AE9, 0xD2ED, 0x8A25, 0xD2EE, 0x677B, 0xD2EF, 0x7D10, 0xD2F0, 0xF952, + 0xD2F1, 0xF953, 0xD2F2, 0xF954, 0xD2F3, 0xF955, 0xD2F4, 0xF956, 0xD2F5, 0xF957, 0xD2F6, 0x80FD, 0xD2F7, 0xF958, 0xD2F8, 0xF959, + 0xD2F9, 0x5C3C, 0xD2FA, 0x6CE5, 0xD2FB, 0x533F, 0xD2FC, 0x6EBA, 0xD2FD, 0x591A, 0xD2FE, 0x8336, 0xD3A1, 0x4E39, 0xD3A2, 0x4EB6, + 0xD3A3, 0x4F46, 0xD3A4, 0x55AE, 0xD3A5, 0x5718, 0xD3A6, 0x58C7, 0xD3A7, 0x5F56, 0xD3A8, 0x65B7, 0xD3A9, 0x65E6, 0xD3AA, 0x6A80, + 0xD3AB, 0x6BB5, 0xD3AC, 0x6E4D, 0xD3AD, 0x77ED, 0xD3AE, 0x7AEF, 0xD3AF, 0x7C1E, 0xD3B0, 0x7DDE, 0xD3B1, 0x86CB, 0xD3B2, 0x8892, + 0xD3B3, 0x9132, 0xD3B4, 0x935B, 0xD3B5, 0x64BB, 0xD3B6, 0x6FBE, 0xD3B7, 0x737A, 0xD3B8, 0x75B8, 0xD3B9, 0x9054, 0xD3BA, 0x5556, + 0xD3BB, 0x574D, 0xD3BC, 0x61BA, 0xD3BD, 0x64D4, 0xD3BE, 0x66C7, 0xD3BF, 0x6DE1, 0xD3C0, 0x6E5B, 0xD3C1, 0x6F6D, 0xD3C2, 0x6FB9, + 0xD3C3, 0x75F0, 0xD3C4, 0x8043, 0xD3C5, 0x81BD, 0xD3C6, 0x8541, 0xD3C7, 0x8983, 0xD3C8, 0x8AC7, 0xD3C9, 0x8B5A, 0xD3CA, 0x931F, + 0xD3CB, 0x6C93, 0xD3CC, 0x7553, 0xD3CD, 0x7B54, 0xD3CE, 0x8E0F, 0xD3CF, 0x905D, 0xD3D0, 0x5510, 0xD3D1, 0x5802, 0xD3D2, 0x5858, + 0xD3D3, 0x5E62, 0xD3D4, 0x6207, 0xD3D5, 0x649E, 0xD3D6, 0x68E0, 0xD3D7, 0x7576, 0xD3D8, 0x7CD6, 0xD3D9, 0x87B3, 0xD3DA, 0x9EE8, + 0xD3DB, 0x4EE3, 0xD3DC, 0x5788, 0xD3DD, 0x576E, 0xD3DE, 0x5927, 0xD3DF, 0x5C0D, 0xD3E0, 0x5CB1, 0xD3E1, 0x5E36, 0xD3E2, 0x5F85, + 0xD3E3, 0x6234, 0xD3E4, 0x64E1, 0xD3E5, 0x73B3, 0xD3E6, 0x81FA, 0xD3E7, 0x888B, 0xD3E8, 0x8CB8, 0xD3E9, 0x968A, 0xD3EA, 0x9EDB, + 0xD3EB, 0x5B85, 0xD3EC, 0x5FB7, 0xD3ED, 0x60B3, 0xD3EE, 0x5012, 0xD3EF, 0x5200, 0xD3F0, 0x5230, 0xD3F1, 0x5716, 0xD3F2, 0x5835, + 0xD3F3, 0x5857, 0xD3F4, 0x5C0E, 0xD3F5, 0x5C60, 0xD3F6, 0x5CF6, 0xD3F7, 0x5D8B, 0xD3F8, 0x5EA6, 0xD3F9, 0x5F92, 0xD3FA, 0x60BC, + 0xD3FB, 0x6311, 0xD3FC, 0x6389, 0xD3FD, 0x6417, 0xD3FE, 0x6843, 0xD4A1, 0x68F9, 0xD4A2, 0x6AC2, 0xD4A3, 0x6DD8, 0xD4A4, 0x6E21, + 0xD4A5, 0x6ED4, 0xD4A6, 0x6FE4, 0xD4A7, 0x71FE, 0xD4A8, 0x76DC, 0xD4A9, 0x7779, 0xD4AA, 0x79B1, 0xD4AB, 0x7A3B, 0xD4AC, 0x8404, + 0xD4AD, 0x89A9, 0xD4AE, 0x8CED, 0xD4AF, 0x8DF3, 0xD4B0, 0x8E48, 0xD4B1, 0x9003, 0xD4B2, 0x9014, 0xD4B3, 0x9053, 0xD4B4, 0x90FD, + 0xD4B5, 0x934D, 0xD4B6, 0x9676, 0xD4B7, 0x97DC, 0xD4B8, 0x6BD2, 0xD4B9, 0x7006, 0xD4BA, 0x7258, 0xD4BB, 0x72A2, 0xD4BC, 0x7368, + 0xD4BD, 0x7763, 0xD4BE, 0x79BF, 0xD4BF, 0x7BE4, 0xD4C0, 0x7E9B, 0xD4C1, 0x8B80, 0xD4C2, 0x58A9, 0xD4C3, 0x60C7, 0xD4C4, 0x6566, + 0xD4C5, 0x65FD, 0xD4C6, 0x66BE, 0xD4C7, 0x6C8C, 0xD4C8, 0x711E, 0xD4C9, 0x71C9, 0xD4CA, 0x8C5A, 0xD4CB, 0x9813, 0xD4CC, 0x4E6D, + 0xD4CD, 0x7A81, 0xD4CE, 0x4EDD, 0xD4CF, 0x51AC, 0xD4D0, 0x51CD, 0xD4D1, 0x52D5, 0xD4D2, 0x540C, 0xD4D3, 0x61A7, 0xD4D4, 0x6771, + 0xD4D5, 0x6850, 0xD4D6, 0x68DF, 0xD4D7, 0x6D1E, 0xD4D8, 0x6F7C, 0xD4D9, 0x75BC, 0xD4DA, 0x77B3, 0xD4DB, 0x7AE5, 0xD4DC, 0x80F4, + 0xD4DD, 0x8463, 0xD4DE, 0x9285, 0xD4DF, 0x515C, 0xD4E0, 0x6597, 0xD4E1, 0x675C, 0xD4E2, 0x6793, 0xD4E3, 0x75D8, 0xD4E4, 0x7AC7, + 0xD4E5, 0x8373, 0xD4E6, 0xF95A, 0xD4E7, 0x8C46, 0xD4E8, 0x9017, 0xD4E9, 0x982D, 0xD4EA, 0x5C6F, 0xD4EB, 0x81C0, 0xD4EC, 0x829A, + 0xD4ED, 0x9041, 0xD4EE, 0x906F, 0xD4EF, 0x920D, 0xD4F0, 0x5F97, 0xD4F1, 0x5D9D, 0xD4F2, 0x6A59, 0xD4F3, 0x71C8, 0xD4F4, 0x767B, + 0xD4F5, 0x7B49, 0xD4F6, 0x85E4, 0xD4F7, 0x8B04, 0xD4F8, 0x9127, 0xD4F9, 0x9A30, 0xD4FA, 0x5587, 0xD4FB, 0x61F6, 0xD4FC, 0xF95B, + 0xD4FD, 0x7669, 0xD4FE, 0x7F85, 0xD5A1, 0x863F, 0xD5A2, 0x87BA, 0xD5A3, 0x88F8, 0xD5A4, 0x908F, 0xD5A5, 0xF95C, 0xD5A6, 0x6D1B, + 0xD5A7, 0x70D9, 0xD5A8, 0x73DE, 0xD5A9, 0x7D61, 0xD5AA, 0x843D, 0xD5AB, 0xF95D, 0xD5AC, 0x916A, 0xD5AD, 0x99F1, 0xD5AE, 0xF95E, + 0xD5AF, 0x4E82, 0xD5B0, 0x5375, 0xD5B1, 0x6B04, 0xD5B2, 0x6B12, 0xD5B3, 0x703E, 0xD5B4, 0x721B, 0xD5B5, 0x862D, 0xD5B6, 0x9E1E, + 0xD5B7, 0x524C, 0xD5B8, 0x8FA3, 0xD5B9, 0x5D50, 0xD5BA, 0x64E5, 0xD5BB, 0x652C, 0xD5BC, 0x6B16, 0xD5BD, 0x6FEB, 0xD5BE, 0x7C43, + 0xD5BF, 0x7E9C, 0xD5C0, 0x85CD, 0xD5C1, 0x8964, 0xD5C2, 0x89BD, 0xD5C3, 0x62C9, 0xD5C4, 0x81D8, 0xD5C5, 0x881F, 0xD5C6, 0x5ECA, + 0xD5C7, 0x6717, 0xD5C8, 0x6D6A, 0xD5C9, 0x72FC, 0xD5CA, 0x7405, 0xD5CB, 0x746F, 0xD5CC, 0x8782, 0xD5CD, 0x90DE, 0xD5CE, 0x4F86, + 0xD5CF, 0x5D0D, 0xD5D0, 0x5FA0, 0xD5D1, 0x840A, 0xD5D2, 0x51B7, 0xD5D3, 0x63A0, 0xD5D4, 0x7565, 0xD5D5, 0x4EAE, 0xD5D6, 0x5006, + 0xD5D7, 0x5169, 0xD5D8, 0x51C9, 0xD5D9, 0x6881, 0xD5DA, 0x6A11, 0xD5DB, 0x7CAE, 0xD5DC, 0x7CB1, 0xD5DD, 0x7CE7, 0xD5DE, 0x826F, + 0xD5DF, 0x8AD2, 0xD5E0, 0x8F1B, 0xD5E1, 0x91CF, 0xD5E2, 0x4FB6, 0xD5E3, 0x5137, 0xD5E4, 0x52F5, 0xD5E5, 0x5442, 0xD5E6, 0x5EEC, + 0xD5E7, 0x616E, 0xD5E8, 0x623E, 0xD5E9, 0x65C5, 0xD5EA, 0x6ADA, 0xD5EB, 0x6FFE, 0xD5EC, 0x792A, 0xD5ED, 0x85DC, 0xD5EE, 0x8823, + 0xD5EF, 0x95AD, 0xD5F0, 0x9A62, 0xD5F1, 0x9A6A, 0xD5F2, 0x9E97, 0xD5F3, 0x9ECE, 0xD5F4, 0x529B, 0xD5F5, 0x66C6, 0xD5F6, 0x6B77, + 0xD5F7, 0x701D, 0xD5F8, 0x792B, 0xD5F9, 0x8F62, 0xD5FA, 0x9742, 0xD5FB, 0x6190, 0xD5FC, 0x6200, 0xD5FD, 0x6523, 0xD5FE, 0x6F23, + 0xD6A1, 0x7149, 0xD6A2, 0x7489, 0xD6A3, 0x7DF4, 0xD6A4, 0x806F, 0xD6A5, 0x84EE, 0xD6A6, 0x8F26, 0xD6A7, 0x9023, 0xD6A8, 0x934A, + 0xD6A9, 0x51BD, 0xD6AA, 0x5217, 0xD6AB, 0x52A3, 0xD6AC, 0x6D0C, 0xD6AD, 0x70C8, 0xD6AE, 0x88C2, 0xD6AF, 0x5EC9, 0xD6B0, 0x6582, + 0xD6B1, 0x6BAE, 0xD6B2, 0x6FC2, 0xD6B3, 0x7C3E, 0xD6B4, 0x7375, 0xD6B5, 0x4EE4, 0xD6B6, 0x4F36, 0xD6B7, 0x56F9, 0xD6B8, 0xF95F, + 0xD6B9, 0x5CBA, 0xD6BA, 0x5DBA, 0xD6BB, 0x601C, 0xD6BC, 0x73B2, 0xD6BD, 0x7B2D, 0xD6BE, 0x7F9A, 0xD6BF, 0x7FCE, 0xD6C0, 0x8046, + 0xD6C1, 0x901E, 0xD6C2, 0x9234, 0xD6C3, 0x96F6, 0xD6C4, 0x9748, 0xD6C5, 0x9818, 0xD6C6, 0x9F61, 0xD6C7, 0x4F8B, 0xD6C8, 0x6FA7, + 0xD6C9, 0x79AE, 0xD6CA, 0x91B4, 0xD6CB, 0x96B7, 0xD6CC, 0x52DE, 0xD6CD, 0xF960, 0xD6CE, 0x6488, 0xD6CF, 0x64C4, 0xD6D0, 0x6AD3, + 0xD6D1, 0x6F5E, 0xD6D2, 0x7018, 0xD6D3, 0x7210, 0xD6D4, 0x76E7, 0xD6D5, 0x8001, 0xD6D6, 0x8606, 0xD6D7, 0x865C, 0xD6D8, 0x8DEF, + 0xD6D9, 0x8F05, 0xD6DA, 0x9732, 0xD6DB, 0x9B6F, 0xD6DC, 0x9DFA, 0xD6DD, 0x9E75, 0xD6DE, 0x788C, 0xD6DF, 0x797F, 0xD6E0, 0x7DA0, + 0xD6E1, 0x83C9, 0xD6E2, 0x9304, 0xD6E3, 0x9E7F, 0xD6E4, 0x9E93, 0xD6E5, 0x8AD6, 0xD6E6, 0x58DF, 0xD6E7, 0x5F04, 0xD6E8, 0x6727, + 0xD6E9, 0x7027, 0xD6EA, 0x74CF, 0xD6EB, 0x7C60, 0xD6EC, 0x807E, 0xD6ED, 0x5121, 0xD6EE, 0x7028, 0xD6EF, 0x7262, 0xD6F0, 0x78CA, + 0xD6F1, 0x8CC2, 0xD6F2, 0x8CDA, 0xD6F3, 0x8CF4, 0xD6F4, 0x96F7, 0xD6F5, 0x4E86, 0xD6F6, 0x50DA, 0xD6F7, 0x5BEE, 0xD6F8, 0x5ED6, + 0xD6F9, 0x6599, 0xD6FA, 0x71CE, 0xD6FB, 0x7642, 0xD6FC, 0x77AD, 0xD6FD, 0x804A, 0xD6FE, 0x84FC, 0xD7A1, 0x907C, 0xD7A2, 0x9B27, + 0xD7A3, 0x9F8D, 0xD7A4, 0x58D8, 0xD7A5, 0x5A41, 0xD7A6, 0x5C62, 0xD7A7, 0x6A13, 0xD7A8, 0x6DDA, 0xD7A9, 0x6F0F, 0xD7AA, 0x763B, + 0xD7AB, 0x7D2F, 0xD7AC, 0x7E37, 0xD7AD, 0x851E, 0xD7AE, 0x8938, 0xD7AF, 0x93E4, 0xD7B0, 0x964B, 0xD7B1, 0x5289, 0xD7B2, 0x65D2, + 0xD7B3, 0x67F3, 0xD7B4, 0x69B4, 0xD7B5, 0x6D41, 0xD7B6, 0x6E9C, 0xD7B7, 0x700F, 0xD7B8, 0x7409, 0xD7B9, 0x7460, 0xD7BA, 0x7559, + 0xD7BB, 0x7624, 0xD7BC, 0x786B, 0xD7BD, 0x8B2C, 0xD7BE, 0x985E, 0xD7BF, 0x516D, 0xD7C0, 0x622E, 0xD7C1, 0x9678, 0xD7C2, 0x4F96, + 0xD7C3, 0x502B, 0xD7C4, 0x5D19, 0xD7C5, 0x6DEA, 0xD7C6, 0x7DB8, 0xD7C7, 0x8F2A, 0xD7C8, 0x5F8B, 0xD7C9, 0x6144, 0xD7CA, 0x6817, + 0xD7CB, 0xF961, 0xD7CC, 0x9686, 0xD7CD, 0x52D2, 0xD7CE, 0x808B, 0xD7CF, 0x51DC, 0xD7D0, 0x51CC, 0xD7D1, 0x695E, 0xD7D2, 0x7A1C, + 0xD7D3, 0x7DBE, 0xD7D4, 0x83F1, 0xD7D5, 0x9675, 0xD7D6, 0x4FDA, 0xD7D7, 0x5229, 0xD7D8, 0x5398, 0xD7D9, 0x540F, 0xD7DA, 0x550E, + 0xD7DB, 0x5C65, 0xD7DC, 0x60A7, 0xD7DD, 0x674E, 0xD7DE, 0x68A8, 0xD7DF, 0x6D6C, 0xD7E0, 0x7281, 0xD7E1, 0x72F8, 0xD7E2, 0x7406, + 0xD7E3, 0x7483, 0xD7E4, 0xF962, 0xD7E5, 0x75E2, 0xD7E6, 0x7C6C, 0xD7E7, 0x7F79, 0xD7E8, 0x7FB8, 0xD7E9, 0x8389, 0xD7EA, 0x88CF, + 0xD7EB, 0x88E1, 0xD7EC, 0x91CC, 0xD7ED, 0x91D0, 0xD7EE, 0x96E2, 0xD7EF, 0x9BC9, 0xD7F0, 0x541D, 0xD7F1, 0x6F7E, 0xD7F2, 0x71D0, + 0xD7F3, 0x7498, 0xD7F4, 0x85FA, 0xD7F5, 0x8EAA, 0xD7F6, 0x96A3, 0xD7F7, 0x9C57, 0xD7F8, 0x9E9F, 0xD7F9, 0x6797, 0xD7FA, 0x6DCB, + 0xD7FB, 0x7433, 0xD7FC, 0x81E8, 0xD7FD, 0x9716, 0xD7FE, 0x782C, 0xD8A1, 0x7ACB, 0xD8A2, 0x7B20, 0xD8A3, 0x7C92, 0xD8A4, 0x6469, + 0xD8A5, 0x746A, 0xD8A6, 0x75F2, 0xD8A7, 0x78BC, 0xD8A8, 0x78E8, 0xD8A9, 0x99AC, 0xD8AA, 0x9B54, 0xD8AB, 0x9EBB, 0xD8AC, 0x5BDE, + 0xD8AD, 0x5E55, 0xD8AE, 0x6F20, 0xD8AF, 0x819C, 0xD8B0, 0x83AB, 0xD8B1, 0x9088, 0xD8B2, 0x4E07, 0xD8B3, 0x534D, 0xD8B4, 0x5A29, + 0xD8B5, 0x5DD2, 0xD8B6, 0x5F4E, 0xD8B7, 0x6162, 0xD8B8, 0x633D, 0xD8B9, 0x6669, 0xD8BA, 0x66FC, 0xD8BB, 0x6EFF, 0xD8BC, 0x6F2B, + 0xD8BD, 0x7063, 0xD8BE, 0x779E, 0xD8BF, 0x842C, 0xD8C0, 0x8513, 0xD8C1, 0x883B, 0xD8C2, 0x8F13, 0xD8C3, 0x9945, 0xD8C4, 0x9C3B, + 0xD8C5, 0x551C, 0xD8C6, 0x62B9, 0xD8C7, 0x672B, 0xD8C8, 0x6CAB, 0xD8C9, 0x8309, 0xD8CA, 0x896A, 0xD8CB, 0x977A, 0xD8CC, 0x4EA1, + 0xD8CD, 0x5984, 0xD8CE, 0x5FD8, 0xD8CF, 0x5FD9, 0xD8D0, 0x671B, 0xD8D1, 0x7DB2, 0xD8D2, 0x7F54, 0xD8D3, 0x8292, 0xD8D4, 0x832B, + 0xD8D5, 0x83BD, 0xD8D6, 0x8F1E, 0xD8D7, 0x9099, 0xD8D8, 0x57CB, 0xD8D9, 0x59B9, 0xD8DA, 0x5A92, 0xD8DB, 0x5BD0, 0xD8DC, 0x6627, + 0xD8DD, 0x679A, 0xD8DE, 0x6885, 0xD8DF, 0x6BCF, 0xD8E0, 0x7164, 0xD8E1, 0x7F75, 0xD8E2, 0x8CB7, 0xD8E3, 0x8CE3, 0xD8E4, 0x9081, + 0xD8E5, 0x9B45, 0xD8E6, 0x8108, 0xD8E7, 0x8C8A, 0xD8E8, 0x964C, 0xD8E9, 0x9A40, 0xD8EA, 0x9EA5, 0xD8EB, 0x5B5F, 0xD8EC, 0x6C13, + 0xD8ED, 0x731B, 0xD8EE, 0x76F2, 0xD8EF, 0x76DF, 0xD8F0, 0x840C, 0xD8F1, 0x51AA, 0xD8F2, 0x8993, 0xD8F3, 0x514D, 0xD8F4, 0x5195, + 0xD8F5, 0x52C9, 0xD8F6, 0x68C9, 0xD8F7, 0x6C94, 0xD8F8, 0x7704, 0xD8F9, 0x7720, 0xD8FA, 0x7DBF, 0xD8FB, 0x7DEC, 0xD8FC, 0x9762, + 0xD8FD, 0x9EB5, 0xD8FE, 0x6EC5, 0xD9A1, 0x8511, 0xD9A2, 0x51A5, 0xD9A3, 0x540D, 0xD9A4, 0x547D, 0xD9A5, 0x660E, 0xD9A6, 0x669D, + 0xD9A7, 0x6927, 0xD9A8, 0x6E9F, 0xD9A9, 0x76BF, 0xD9AA, 0x7791, 0xD9AB, 0x8317, 0xD9AC, 0x84C2, 0xD9AD, 0x879F, 0xD9AE, 0x9169, + 0xD9AF, 0x9298, 0xD9B0, 0x9CF4, 0xD9B1, 0x8882, 0xD9B2, 0x4FAE, 0xD9B3, 0x5192, 0xD9B4, 0x52DF, 0xD9B5, 0x59C6, 0xD9B6, 0x5E3D, + 0xD9B7, 0x6155, 0xD9B8, 0x6478, 0xD9B9, 0x6479, 0xD9BA, 0x66AE, 0xD9BB, 0x67D0, 0xD9BC, 0x6A21, 0xD9BD, 0x6BCD, 0xD9BE, 0x6BDB, + 0xD9BF, 0x725F, 0xD9C0, 0x7261, 0xD9C1, 0x7441, 0xD9C2, 0x7738, 0xD9C3, 0x77DB, 0xD9C4, 0x8017, 0xD9C5, 0x82BC, 0xD9C6, 0x8305, + 0xD9C7, 0x8B00, 0xD9C8, 0x8B28, 0xD9C9, 0x8C8C, 0xD9CA, 0x6728, 0xD9CB, 0x6C90, 0xD9CC, 0x7267, 0xD9CD, 0x76EE, 0xD9CE, 0x7766, + 0xD9CF, 0x7A46, 0xD9D0, 0x9DA9, 0xD9D1, 0x6B7F, 0xD9D2, 0x6C92, 0xD9D3, 0x5922, 0xD9D4, 0x6726, 0xD9D5, 0x8499, 0xD9D6, 0x536F, + 0xD9D7, 0x5893, 0xD9D8, 0x5999, 0xD9D9, 0x5EDF, 0xD9DA, 0x63CF, 0xD9DB, 0x6634, 0xD9DC, 0x6773, 0xD9DD, 0x6E3A, 0xD9DE, 0x732B, + 0xD9DF, 0x7AD7, 0xD9E0, 0x82D7, 0xD9E1, 0x9328, 0xD9E2, 0x52D9, 0xD9E3, 0x5DEB, 0xD9E4, 0x61AE, 0xD9E5, 0x61CB, 0xD9E6, 0x620A, + 0xD9E7, 0x62C7, 0xD9E8, 0x64AB, 0xD9E9, 0x65E0, 0xD9EA, 0x6959, 0xD9EB, 0x6B66, 0xD9EC, 0x6BCB, 0xD9ED, 0x7121, 0xD9EE, 0x73F7, + 0xD9EF, 0x755D, 0xD9F0, 0x7E46, 0xD9F1, 0x821E, 0xD9F2, 0x8302, 0xD9F3, 0x856A, 0xD9F4, 0x8AA3, 0xD9F5, 0x8CBF, 0xD9F6, 0x9727, + 0xD9F7, 0x9D61, 0xD9F8, 0x58A8, 0xD9F9, 0x9ED8, 0xD9FA, 0x5011, 0xD9FB, 0x520E, 0xD9FC, 0x543B, 0xD9FD, 0x554F, 0xD9FE, 0x6587, + 0xDAA1, 0x6C76, 0xDAA2, 0x7D0A, 0xDAA3, 0x7D0B, 0xDAA4, 0x805E, 0xDAA5, 0x868A, 0xDAA6, 0x9580, 0xDAA7, 0x96EF, 0xDAA8, 0x52FF, + 0xDAA9, 0x6C95, 0xDAAA, 0x7269, 0xDAAB, 0x5473, 0xDAAC, 0x5A9A, 0xDAAD, 0x5C3E, 0xDAAE, 0x5D4B, 0xDAAF, 0x5F4C, 0xDAB0, 0x5FAE, + 0xDAB1, 0x672A, 0xDAB2, 0x68B6, 0xDAB3, 0x6963, 0xDAB4, 0x6E3C, 0xDAB5, 0x6E44, 0xDAB6, 0x7709, 0xDAB7, 0x7C73, 0xDAB8, 0x7F8E, + 0xDAB9, 0x8587, 0xDABA, 0x8B0E, 0xDABB, 0x8FF7, 0xDABC, 0x9761, 0xDABD, 0x9EF4, 0xDABE, 0x5CB7, 0xDABF, 0x60B6, 0xDAC0, 0x610D, + 0xDAC1, 0x61AB, 0xDAC2, 0x654F, 0xDAC3, 0x65FB, 0xDAC4, 0x65FC, 0xDAC5, 0x6C11, 0xDAC6, 0x6CEF, 0xDAC7, 0x739F, 0xDAC8, 0x73C9, + 0xDAC9, 0x7DE1, 0xDACA, 0x9594, 0xDACB, 0x5BC6, 0xDACC, 0x871C, 0xDACD, 0x8B10, 0xDACE, 0x525D, 0xDACF, 0x535A, 0xDAD0, 0x62CD, + 0xDAD1, 0x640F, 0xDAD2, 0x64B2, 0xDAD3, 0x6734, 0xDAD4, 0x6A38, 0xDAD5, 0x6CCA, 0xDAD6, 0x73C0, 0xDAD7, 0x749E, 0xDAD8, 0x7B94, + 0xDAD9, 0x7C95, 0xDADA, 0x7E1B, 0xDADB, 0x818A, 0xDADC, 0x8236, 0xDADD, 0x8584, 0xDADE, 0x8FEB, 0xDADF, 0x96F9, 0xDAE0, 0x99C1, + 0xDAE1, 0x4F34, 0xDAE2, 0x534A, 0xDAE3, 0x53CD, 0xDAE4, 0x53DB, 0xDAE5, 0x62CC, 0xDAE6, 0x642C, 0xDAE7, 0x6500, 0xDAE8, 0x6591, + 0xDAE9, 0x69C3, 0xDAEA, 0x6CEE, 0xDAEB, 0x6F58, 0xDAEC, 0x73ED, 0xDAED, 0x7554, 0xDAEE, 0x7622, 0xDAEF, 0x76E4, 0xDAF0, 0x76FC, + 0xDAF1, 0x78D0, 0xDAF2, 0x78FB, 0xDAF3, 0x792C, 0xDAF4, 0x7D46, 0xDAF5, 0x822C, 0xDAF6, 0x87E0, 0xDAF7, 0x8FD4, 0xDAF8, 0x9812, + 0xDAF9, 0x98EF, 0xDAFA, 0x52C3, 0xDAFB, 0x62D4, 0xDAFC, 0x64A5, 0xDAFD, 0x6E24, 0xDAFE, 0x6F51, 0xDBA1, 0x767C, 0xDBA2, 0x8DCB, + 0xDBA3, 0x91B1, 0xDBA4, 0x9262, 0xDBA5, 0x9AEE, 0xDBA6, 0x9B43, 0xDBA7, 0x5023, 0xDBA8, 0x508D, 0xDBA9, 0x574A, 0xDBAA, 0x59A8, + 0xDBAB, 0x5C28, 0xDBAC, 0x5E47, 0xDBAD, 0x5F77, 0xDBAE, 0x623F, 0xDBAF, 0x653E, 0xDBB0, 0x65B9, 0xDBB1, 0x65C1, 0xDBB2, 0x6609, + 0xDBB3, 0x678B, 0xDBB4, 0x699C, 0xDBB5, 0x6EC2, 0xDBB6, 0x78C5, 0xDBB7, 0x7D21, 0xDBB8, 0x80AA, 0xDBB9, 0x8180, 0xDBBA, 0x822B, + 0xDBBB, 0x82B3, 0xDBBC, 0x84A1, 0xDBBD, 0x868C, 0xDBBE, 0x8A2A, 0xDBBF, 0x8B17, 0xDBC0, 0x90A6, 0xDBC1, 0x9632, 0xDBC2, 0x9F90, + 0xDBC3, 0x500D, 0xDBC4, 0x4FF3, 0xDBC5, 0xF963, 0xDBC6, 0x57F9, 0xDBC7, 0x5F98, 0xDBC8, 0x62DC, 0xDBC9, 0x6392, 0xDBCA, 0x676F, + 0xDBCB, 0x6E43, 0xDBCC, 0x7119, 0xDBCD, 0x76C3, 0xDBCE, 0x80CC, 0xDBCF, 0x80DA, 0xDBD0, 0x88F4, 0xDBD1, 0x88F5, 0xDBD2, 0x8919, + 0xDBD3, 0x8CE0, 0xDBD4, 0x8F29, 0xDBD5, 0x914D, 0xDBD6, 0x966A, 0xDBD7, 0x4F2F, 0xDBD8, 0x4F70, 0xDBD9, 0x5E1B, 0xDBDA, 0x67CF, + 0xDBDB, 0x6822, 0xDBDC, 0x767D, 0xDBDD, 0x767E, 0xDBDE, 0x9B44, 0xDBDF, 0x5E61, 0xDBE0, 0x6A0A, 0xDBE1, 0x7169, 0xDBE2, 0x71D4, + 0xDBE3, 0x756A, 0xDBE4, 0xF964, 0xDBE5, 0x7E41, 0xDBE6, 0x8543, 0xDBE7, 0x85E9, 0xDBE8, 0x98DC, 0xDBE9, 0x4F10, 0xDBEA, 0x7B4F, + 0xDBEB, 0x7F70, 0xDBEC, 0x95A5, 0xDBED, 0x51E1, 0xDBEE, 0x5E06, 0xDBEF, 0x68B5, 0xDBF0, 0x6C3E, 0xDBF1, 0x6C4E, 0xDBF2, 0x6CDB, + 0xDBF3, 0x72AF, 0xDBF4, 0x7BC4, 0xDBF5, 0x8303, 0xDBF6, 0x6CD5, 0xDBF7, 0x743A, 0xDBF8, 0x50FB, 0xDBF9, 0x5288, 0xDBFA, 0x58C1, + 0xDBFB, 0x64D8, 0xDBFC, 0x6A97, 0xDBFD, 0x74A7, 0xDBFE, 0x7656, 0xDCA1, 0x78A7, 0xDCA2, 0x8617, 0xDCA3, 0x95E2, 0xDCA4, 0x9739, + 0xDCA5, 0xF965, 0xDCA6, 0x535E, 0xDCA7, 0x5F01, 0xDCA8, 0x8B8A, 0xDCA9, 0x8FA8, 0xDCAA, 0x8FAF, 0xDCAB, 0x908A, 0xDCAC, 0x5225, + 0xDCAD, 0x77A5, 0xDCAE, 0x9C49, 0xDCAF, 0x9F08, 0xDCB0, 0x4E19, 0xDCB1, 0x5002, 0xDCB2, 0x5175, 0xDCB3, 0x5C5B, 0xDCB4, 0x5E77, + 0xDCB5, 0x661E, 0xDCB6, 0x663A, 0xDCB7, 0x67C4, 0xDCB8, 0x68C5, 0xDCB9, 0x70B3, 0xDCBA, 0x7501, 0xDCBB, 0x75C5, 0xDCBC, 0x79C9, + 0xDCBD, 0x7ADD, 0xDCBE, 0x8F27, 0xDCBF, 0x9920, 0xDCC0, 0x9A08, 0xDCC1, 0x4FDD, 0xDCC2, 0x5821, 0xDCC3, 0x5831, 0xDCC4, 0x5BF6, + 0xDCC5, 0x666E, 0xDCC6, 0x6B65, 0xDCC7, 0x6D11, 0xDCC8, 0x6E7A, 0xDCC9, 0x6F7D, 0xDCCA, 0x73E4, 0xDCCB, 0x752B, 0xDCCC, 0x83E9, + 0xDCCD, 0x88DC, 0xDCCE, 0x8913, 0xDCCF, 0x8B5C, 0xDCD0, 0x8F14, 0xDCD1, 0x4F0F, 0xDCD2, 0x50D5, 0xDCD3, 0x5310, 0xDCD4, 0x535C, + 0xDCD5, 0x5B93, 0xDCD6, 0x5FA9, 0xDCD7, 0x670D, 0xDCD8, 0x798F, 0xDCD9, 0x8179, 0xDCDA, 0x832F, 0xDCDB, 0x8514, 0xDCDC, 0x8907, + 0xDCDD, 0x8986, 0xDCDE, 0x8F39, 0xDCDF, 0x8F3B, 0xDCE0, 0x99A5, 0xDCE1, 0x9C12, 0xDCE2, 0x672C, 0xDCE3, 0x4E76, 0xDCE4, 0x4FF8, + 0xDCE5, 0x5949, 0xDCE6, 0x5C01, 0xDCE7, 0x5CEF, 0xDCE8, 0x5CF0, 0xDCE9, 0x6367, 0xDCEA, 0x68D2, 0xDCEB, 0x70FD, 0xDCEC, 0x71A2, + 0xDCED, 0x742B, 0xDCEE, 0x7E2B, 0xDCEF, 0x84EC, 0xDCF0, 0x8702, 0xDCF1, 0x9022, 0xDCF2, 0x92D2, 0xDCF3, 0x9CF3, 0xDCF4, 0x4E0D, + 0xDCF5, 0x4ED8, 0xDCF6, 0x4FEF, 0xDCF7, 0x5085, 0xDCF8, 0x5256, 0xDCF9, 0x526F, 0xDCFA, 0x5426, 0xDCFB, 0x5490, 0xDCFC, 0x57E0, + 0xDCFD, 0x592B, 0xDCFE, 0x5A66, 0xDDA1, 0x5B5A, 0xDDA2, 0x5B75, 0xDDA3, 0x5BCC, 0xDDA4, 0x5E9C, 0xDDA5, 0xF966, 0xDDA6, 0x6276, + 0xDDA7, 0x6577, 0xDDA8, 0x65A7, 0xDDA9, 0x6D6E, 0xDDAA, 0x6EA5, 0xDDAB, 0x7236, 0xDDAC, 0x7B26, 0xDDAD, 0x7C3F, 0xDDAE, 0x7F36, + 0xDDAF, 0x8150, 0xDDB0, 0x8151, 0xDDB1, 0x819A, 0xDDB2, 0x8240, 0xDDB3, 0x8299, 0xDDB4, 0x83A9, 0xDDB5, 0x8A03, 0xDDB6, 0x8CA0, + 0xDDB7, 0x8CE6, 0xDDB8, 0x8CFB, 0xDDB9, 0x8D74, 0xDDBA, 0x8DBA, 0xDDBB, 0x90E8, 0xDDBC, 0x91DC, 0xDDBD, 0x961C, 0xDDBE, 0x9644, + 0xDDBF, 0x99D9, 0xDDC0, 0x9CE7, 0xDDC1, 0x5317, 0xDDC2, 0x5206, 0xDDC3, 0x5429, 0xDDC4, 0x5674, 0xDDC5, 0x58B3, 0xDDC6, 0x5954, + 0xDDC7, 0x596E, 0xDDC8, 0x5FFF, 0xDDC9, 0x61A4, 0xDDCA, 0x626E, 0xDDCB, 0x6610, 0xDDCC, 0x6C7E, 0xDDCD, 0x711A, 0xDDCE, 0x76C6, + 0xDDCF, 0x7C89, 0xDDD0, 0x7CDE, 0xDDD1, 0x7D1B, 0xDDD2, 0x82AC, 0xDDD3, 0x8CC1, 0xDDD4, 0x96F0, 0xDDD5, 0xF967, 0xDDD6, 0x4F5B, + 0xDDD7, 0x5F17, 0xDDD8, 0x5F7F, 0xDDD9, 0x62C2, 0xDDDA, 0x5D29, 0xDDDB, 0x670B, 0xDDDC, 0x68DA, 0xDDDD, 0x787C, 0xDDDE, 0x7E43, + 0xDDDF, 0x9D6C, 0xDDE0, 0x4E15, 0xDDE1, 0x5099, 0xDDE2, 0x5315, 0xDDE3, 0x532A, 0xDDE4, 0x5351, 0xDDE5, 0x5983, 0xDDE6, 0x5A62, + 0xDDE7, 0x5E87, 0xDDE8, 0x60B2, 0xDDE9, 0x618A, 0xDDEA, 0x6249, 0xDDEB, 0x6279, 0xDDEC, 0x6590, 0xDDED, 0x6787, 0xDDEE, 0x69A7, + 0xDDEF, 0x6BD4, 0xDDF0, 0x6BD6, 0xDDF1, 0x6BD7, 0xDDF2, 0x6BD8, 0xDDF3, 0x6CB8, 0xDDF4, 0xF968, 0xDDF5, 0x7435, 0xDDF6, 0x75FA, + 0xDDF7, 0x7812, 0xDDF8, 0x7891, 0xDDF9, 0x79D5, 0xDDFA, 0x79D8, 0xDDFB, 0x7C83, 0xDDFC, 0x7DCB, 0xDDFD, 0x7FE1, 0xDDFE, 0x80A5, + 0xDEA1, 0x813E, 0xDEA2, 0x81C2, 0xDEA3, 0x83F2, 0xDEA4, 0x871A, 0xDEA5, 0x88E8, 0xDEA6, 0x8AB9, 0xDEA7, 0x8B6C, 0xDEA8, 0x8CBB, + 0xDEA9, 0x9119, 0xDEAA, 0x975E, 0xDEAB, 0x98DB, 0xDEAC, 0x9F3B, 0xDEAD, 0x56AC, 0xDEAE, 0x5B2A, 0xDEAF, 0x5F6C, 0xDEB0, 0x658C, + 0xDEB1, 0x6AB3, 0xDEB2, 0x6BAF, 0xDEB3, 0x6D5C, 0xDEB4, 0x6FF1, 0xDEB5, 0x7015, 0xDEB6, 0x725D, 0xDEB7, 0x73AD, 0xDEB8, 0x8CA7, + 0xDEB9, 0x8CD3, 0xDEBA, 0x983B, 0xDEBB, 0x6191, 0xDEBC, 0x6C37, 0xDEBD, 0x8058, 0xDEBE, 0x9A01, 0xDEBF, 0x4E4D, 0xDEC0, 0x4E8B, + 0xDEC1, 0x4E9B, 0xDEC2, 0x4ED5, 0xDEC3, 0x4F3A, 0xDEC4, 0x4F3C, 0xDEC5, 0x4F7F, 0xDEC6, 0x4FDF, 0xDEC7, 0x50FF, 0xDEC8, 0x53F2, + 0xDEC9, 0x53F8, 0xDECA, 0x5506, 0xDECB, 0x55E3, 0xDECC, 0x56DB, 0xDECD, 0x58EB, 0xDECE, 0x5962, 0xDECF, 0x5A11, 0xDED0, 0x5BEB, + 0xDED1, 0x5BFA, 0xDED2, 0x5C04, 0xDED3, 0x5DF3, 0xDED4, 0x5E2B, 0xDED5, 0x5F99, 0xDED6, 0x601D, 0xDED7, 0x6368, 0xDED8, 0x659C, + 0xDED9, 0x65AF, 0xDEDA, 0x67F6, 0xDEDB, 0x67FB, 0xDEDC, 0x68AD, 0xDEDD, 0x6B7B, 0xDEDE, 0x6C99, 0xDEDF, 0x6CD7, 0xDEE0, 0x6E23, + 0xDEE1, 0x7009, 0xDEE2, 0x7345, 0xDEE3, 0x7802, 0xDEE4, 0x793E, 0xDEE5, 0x7940, 0xDEE6, 0x7960, 0xDEE7, 0x79C1, 0xDEE8, 0x7BE9, + 0xDEE9, 0x7D17, 0xDEEA, 0x7D72, 0xDEEB, 0x8086, 0xDEEC, 0x820D, 0xDEED, 0x838E, 0xDEEE, 0x84D1, 0xDEEF, 0x86C7, 0xDEF0, 0x88DF, + 0xDEF1, 0x8A50, 0xDEF2, 0x8A5E, 0xDEF3, 0x8B1D, 0xDEF4, 0x8CDC, 0xDEF5, 0x8D66, 0xDEF6, 0x8FAD, 0xDEF7, 0x90AA, 0xDEF8, 0x98FC, + 0xDEF9, 0x99DF, 0xDEFA, 0x9E9D, 0xDEFB, 0x524A, 0xDEFC, 0xF969, 0xDEFD, 0x6714, 0xDEFE, 0xF96A, 0xDFA1, 0x5098, 0xDFA2, 0x522A, + 0xDFA3, 0x5C71, 0xDFA4, 0x6563, 0xDFA5, 0x6C55, 0xDFA6, 0x73CA, 0xDFA7, 0x7523, 0xDFA8, 0x759D, 0xDFA9, 0x7B97, 0xDFAA, 0x849C, + 0xDFAB, 0x9178, 0xDFAC, 0x9730, 0xDFAD, 0x4E77, 0xDFAE, 0x6492, 0xDFAF, 0x6BBA, 0xDFB0, 0x715E, 0xDFB1, 0x85A9, 0xDFB2, 0x4E09, + 0xDFB3, 0xF96B, 0xDFB4, 0x6749, 0xDFB5, 0x68EE, 0xDFB6, 0x6E17, 0xDFB7, 0x829F, 0xDFB8, 0x8518, 0xDFB9, 0x886B, 0xDFBA, 0x63F7, + 0xDFBB, 0x6F81, 0xDFBC, 0x9212, 0xDFBD, 0x98AF, 0xDFBE, 0x4E0A, 0xDFBF, 0x50B7, 0xDFC0, 0x50CF, 0xDFC1, 0x511F, 0xDFC2, 0x5546, + 0xDFC3, 0x55AA, 0xDFC4, 0x5617, 0xDFC5, 0x5B40, 0xDFC6, 0x5C19, 0xDFC7, 0x5CE0, 0xDFC8, 0x5E38, 0xDFC9, 0x5E8A, 0xDFCA, 0x5EA0, + 0xDFCB, 0x5EC2, 0xDFCC, 0x60F3, 0xDFCD, 0x6851, 0xDFCE, 0x6A61, 0xDFCF, 0x6E58, 0xDFD0, 0x723D, 0xDFD1, 0x7240, 0xDFD2, 0x72C0, + 0xDFD3, 0x76F8, 0xDFD4, 0x7965, 0xDFD5, 0x7BB1, 0xDFD6, 0x7FD4, 0xDFD7, 0x88F3, 0xDFD8, 0x89F4, 0xDFD9, 0x8A73, 0xDFDA, 0x8C61, + 0xDFDB, 0x8CDE, 0xDFDC, 0x971C, 0xDFDD, 0x585E, 0xDFDE, 0x74BD, 0xDFDF, 0x8CFD, 0xDFE0, 0x55C7, 0xDFE1, 0xF96C, 0xDFE2, 0x7A61, + 0xDFE3, 0x7D22, 0xDFE4, 0x8272, 0xDFE5, 0x7272, 0xDFE6, 0x751F, 0xDFE7, 0x7525, 0xDFE8, 0xF96D, 0xDFE9, 0x7B19, 0xDFEA, 0x5885, + 0xDFEB, 0x58FB, 0xDFEC, 0x5DBC, 0xDFED, 0x5E8F, 0xDFEE, 0x5EB6, 0xDFEF, 0x5F90, 0xDFF0, 0x6055, 0xDFF1, 0x6292, 0xDFF2, 0x637F, + 0xDFF3, 0x654D, 0xDFF4, 0x6691, 0xDFF5, 0x66D9, 0xDFF6, 0x66F8, 0xDFF7, 0x6816, 0xDFF8, 0x68F2, 0xDFF9, 0x7280, 0xDFFA, 0x745E, + 0xDFFB, 0x7B6E, 0xDFFC, 0x7D6E, 0xDFFD, 0x7DD6, 0xDFFE, 0x7F72, 0xE0A1, 0x80E5, 0xE0A2, 0x8212, 0xE0A3, 0x85AF, 0xE0A4, 0x897F, + 0xE0A5, 0x8A93, 0xE0A6, 0x901D, 0xE0A7, 0x92E4, 0xE0A8, 0x9ECD, 0xE0A9, 0x9F20, 0xE0AA, 0x5915, 0xE0AB, 0x596D, 0xE0AC, 0x5E2D, + 0xE0AD, 0x60DC, 0xE0AE, 0x6614, 0xE0AF, 0x6673, 0xE0B0, 0x6790, 0xE0B1, 0x6C50, 0xE0B2, 0x6DC5, 0xE0B3, 0x6F5F, 0xE0B4, 0x77F3, + 0xE0B5, 0x78A9, 0xE0B6, 0x84C6, 0xE0B7, 0x91CB, 0xE0B8, 0x932B, 0xE0B9, 0x4ED9, 0xE0BA, 0x50CA, 0xE0BB, 0x5148, 0xE0BC, 0x5584, + 0xE0BD, 0x5B0B, 0xE0BE, 0x5BA3, 0xE0BF, 0x6247, 0xE0C0, 0x657E, 0xE0C1, 0x65CB, 0xE0C2, 0x6E32, 0xE0C3, 0x717D, 0xE0C4, 0x7401, + 0xE0C5, 0x7444, 0xE0C6, 0x7487, 0xE0C7, 0x74BF, 0xE0C8, 0x766C, 0xE0C9, 0x79AA, 0xE0CA, 0x7DDA, 0xE0CB, 0x7E55, 0xE0CC, 0x7FA8, + 0xE0CD, 0x817A, 0xE0CE, 0x81B3, 0xE0CF, 0x8239, 0xE0D0, 0x861A, 0xE0D1, 0x87EC, 0xE0D2, 0x8A75, 0xE0D3, 0x8DE3, 0xE0D4, 0x9078, + 0xE0D5, 0x9291, 0xE0D6, 0x9425, 0xE0D7, 0x994D, 0xE0D8, 0x9BAE, 0xE0D9, 0x5368, 0xE0DA, 0x5C51, 0xE0DB, 0x6954, 0xE0DC, 0x6CC4, + 0xE0DD, 0x6D29, 0xE0DE, 0x6E2B, 0xE0DF, 0x820C, 0xE0E0, 0x859B, 0xE0E1, 0x893B, 0xE0E2, 0x8A2D, 0xE0E3, 0x8AAA, 0xE0E4, 0x96EA, + 0xE0E5, 0x9F67, 0xE0E6, 0x5261, 0xE0E7, 0x66B9, 0xE0E8, 0x6BB2, 0xE0E9, 0x7E96, 0xE0EA, 0x87FE, 0xE0EB, 0x8D0D, 0xE0EC, 0x9583, + 0xE0ED, 0x965D, 0xE0EE, 0x651D, 0xE0EF, 0x6D89, 0xE0F0, 0x71EE, 0xE0F1, 0xF96E, 0xE0F2, 0x57CE, 0xE0F3, 0x59D3, 0xE0F4, 0x5BAC, + 0xE0F5, 0x6027, 0xE0F6, 0x60FA, 0xE0F7, 0x6210, 0xE0F8, 0x661F, 0xE0F9, 0x665F, 0xE0FA, 0x7329, 0xE0FB, 0x73F9, 0xE0FC, 0x76DB, + 0xE0FD, 0x7701, 0xE0FE, 0x7B6C, 0xE1A1, 0x8056, 0xE1A2, 0x8072, 0xE1A3, 0x8165, 0xE1A4, 0x8AA0, 0xE1A5, 0x9192, 0xE1A6, 0x4E16, + 0xE1A7, 0x52E2, 0xE1A8, 0x6B72, 0xE1A9, 0x6D17, 0xE1AA, 0x7A05, 0xE1AB, 0x7B39, 0xE1AC, 0x7D30, 0xE1AD, 0xF96F, 0xE1AE, 0x8CB0, + 0xE1AF, 0x53EC, 0xE1B0, 0x562F, 0xE1B1, 0x5851, 0xE1B2, 0x5BB5, 0xE1B3, 0x5C0F, 0xE1B4, 0x5C11, 0xE1B5, 0x5DE2, 0xE1B6, 0x6240, + 0xE1B7, 0x6383, 0xE1B8, 0x6414, 0xE1B9, 0x662D, 0xE1BA, 0x68B3, 0xE1BB, 0x6CBC, 0xE1BC, 0x6D88, 0xE1BD, 0x6EAF, 0xE1BE, 0x701F, + 0xE1BF, 0x70A4, 0xE1C0, 0x71D2, 0xE1C1, 0x7526, 0xE1C2, 0x758F, 0xE1C3, 0x758E, 0xE1C4, 0x7619, 0xE1C5, 0x7B11, 0xE1C6, 0x7BE0, + 0xE1C7, 0x7C2B, 0xE1C8, 0x7D20, 0xE1C9, 0x7D39, 0xE1CA, 0x852C, 0xE1CB, 0x856D, 0xE1CC, 0x8607, 0xE1CD, 0x8A34, 0xE1CE, 0x900D, + 0xE1CF, 0x9061, 0xE1D0, 0x90B5, 0xE1D1, 0x92B7, 0xE1D2, 0x97F6, 0xE1D3, 0x9A37, 0xE1D4, 0x4FD7, 0xE1D5, 0x5C6C, 0xE1D6, 0x675F, + 0xE1D7, 0x6D91, 0xE1D8, 0x7C9F, 0xE1D9, 0x7E8C, 0xE1DA, 0x8B16, 0xE1DB, 0x8D16, 0xE1DC, 0x901F, 0xE1DD, 0x5B6B, 0xE1DE, 0x5DFD, + 0xE1DF, 0x640D, 0xE1E0, 0x84C0, 0xE1E1, 0x905C, 0xE1E2, 0x98E1, 0xE1E3, 0x7387, 0xE1E4, 0x5B8B, 0xE1E5, 0x609A, 0xE1E6, 0x677E, + 0xE1E7, 0x6DDE, 0xE1E8, 0x8A1F, 0xE1E9, 0x8AA6, 0xE1EA, 0x9001, 0xE1EB, 0x980C, 0xE1EC, 0x5237, 0xE1ED, 0xF970, 0xE1EE, 0x7051, + 0xE1EF, 0x788E, 0xE1F0, 0x9396, 0xE1F1, 0x8870, 0xE1F2, 0x91D7, 0xE1F3, 0x4FEE, 0xE1F4, 0x53D7, 0xE1F5, 0x55FD, 0xE1F6, 0x56DA, + 0xE1F7, 0x5782, 0xE1F8, 0x58FD, 0xE1F9, 0x5AC2, 0xE1FA, 0x5B88, 0xE1FB, 0x5CAB, 0xE1FC, 0x5CC0, 0xE1FD, 0x5E25, 0xE1FE, 0x6101, + 0xE2A1, 0x620D, 0xE2A2, 0x624B, 0xE2A3, 0x6388, 0xE2A4, 0x641C, 0xE2A5, 0x6536, 0xE2A6, 0x6578, 0xE2A7, 0x6A39, 0xE2A8, 0x6B8A, + 0xE2A9, 0x6C34, 0xE2AA, 0x6D19, 0xE2AB, 0x6F31, 0xE2AC, 0x71E7, 0xE2AD, 0x72E9, 0xE2AE, 0x7378, 0xE2AF, 0x7407, 0xE2B0, 0x74B2, + 0xE2B1, 0x7626, 0xE2B2, 0x7761, 0xE2B3, 0x79C0, 0xE2B4, 0x7A57, 0xE2B5, 0x7AEA, 0xE2B6, 0x7CB9, 0xE2B7, 0x7D8F, 0xE2B8, 0x7DAC, + 0xE2B9, 0x7E61, 0xE2BA, 0x7F9E, 0xE2BB, 0x8129, 0xE2BC, 0x8331, 0xE2BD, 0x8490, 0xE2BE, 0x84DA, 0xE2BF, 0x85EA, 0xE2C0, 0x8896, + 0xE2C1, 0x8AB0, 0xE2C2, 0x8B90, 0xE2C3, 0x8F38, 0xE2C4, 0x9042, 0xE2C5, 0x9083, 0xE2C6, 0x916C, 0xE2C7, 0x9296, 0xE2C8, 0x92B9, + 0xE2C9, 0x968B, 0xE2CA, 0x96A7, 0xE2CB, 0x96A8, 0xE2CC, 0x96D6, 0xE2CD, 0x9700, 0xE2CE, 0x9808, 0xE2CF, 0x9996, 0xE2D0, 0x9AD3, + 0xE2D1, 0x9B1A, 0xE2D2, 0x53D4, 0xE2D3, 0x587E, 0xE2D4, 0x5919, 0xE2D5, 0x5B70, 0xE2D6, 0x5BBF, 0xE2D7, 0x6DD1, 0xE2D8, 0x6F5A, + 0xE2D9, 0x719F, 0xE2DA, 0x7421, 0xE2DB, 0x74B9, 0xE2DC, 0x8085, 0xE2DD, 0x83FD, 0xE2DE, 0x5DE1, 0xE2DF, 0x5F87, 0xE2E0, 0x5FAA, + 0xE2E1, 0x6042, 0xE2E2, 0x65EC, 0xE2E3, 0x6812, 0xE2E4, 0x696F, 0xE2E5, 0x6A53, 0xE2E6, 0x6B89, 0xE2E7, 0x6D35, 0xE2E8, 0x6DF3, + 0xE2E9, 0x73E3, 0xE2EA, 0x76FE, 0xE2EB, 0x77AC, 0xE2EC, 0x7B4D, 0xE2ED, 0x7D14, 0xE2EE, 0x8123, 0xE2EF, 0x821C, 0xE2F0, 0x8340, + 0xE2F1, 0x84F4, 0xE2F2, 0x8563, 0xE2F3, 0x8A62, 0xE2F4, 0x8AC4, 0xE2F5, 0x9187, 0xE2F6, 0x931E, 0xE2F7, 0x9806, 0xE2F8, 0x99B4, + 0xE2F9, 0x620C, 0xE2FA, 0x8853, 0xE2FB, 0x8FF0, 0xE2FC, 0x9265, 0xE2FD, 0x5D07, 0xE2FE, 0x5D27, 0xE3A1, 0x5D69, 0xE3A2, 0x745F, + 0xE3A3, 0x819D, 0xE3A4, 0x8768, 0xE3A5, 0x6FD5, 0xE3A6, 0x62FE, 0xE3A7, 0x7FD2, 0xE3A8, 0x8936, 0xE3A9, 0x8972, 0xE3AA, 0x4E1E, + 0xE3AB, 0x4E58, 0xE3AC, 0x50E7, 0xE3AD, 0x52DD, 0xE3AE, 0x5347, 0xE3AF, 0x627F, 0xE3B0, 0x6607, 0xE3B1, 0x7E69, 0xE3B2, 0x8805, + 0xE3B3, 0x965E, 0xE3B4, 0x4F8D, 0xE3B5, 0x5319, 0xE3B6, 0x5636, 0xE3B7, 0x59CB, 0xE3B8, 0x5AA4, 0xE3B9, 0x5C38, 0xE3BA, 0x5C4E, + 0xE3BB, 0x5C4D, 0xE3BC, 0x5E02, 0xE3BD, 0x5F11, 0xE3BE, 0x6043, 0xE3BF, 0x65BD, 0xE3C0, 0x662F, 0xE3C1, 0x6642, 0xE3C2, 0x67BE, + 0xE3C3, 0x67F4, 0xE3C4, 0x731C, 0xE3C5, 0x77E2, 0xE3C6, 0x793A, 0xE3C7, 0x7FC5, 0xE3C8, 0x8494, 0xE3C9, 0x84CD, 0xE3CA, 0x8996, + 0xE3CB, 0x8A66, 0xE3CC, 0x8A69, 0xE3CD, 0x8AE1, 0xE3CE, 0x8C55, 0xE3CF, 0x8C7A, 0xE3D0, 0x57F4, 0xE3D1, 0x5BD4, 0xE3D2, 0x5F0F, + 0xE3D3, 0x606F, 0xE3D4, 0x62ED, 0xE3D5, 0x690D, 0xE3D6, 0x6B96, 0xE3D7, 0x6E5C, 0xE3D8, 0x7184, 0xE3D9, 0x7BD2, 0xE3DA, 0x8755, + 0xE3DB, 0x8B58, 0xE3DC, 0x8EFE, 0xE3DD, 0x98DF, 0xE3DE, 0x98FE, 0xE3DF, 0x4F38, 0xE3E0, 0x4F81, 0xE3E1, 0x4FE1, 0xE3E2, 0x547B, + 0xE3E3, 0x5A20, 0xE3E4, 0x5BB8, 0xE3E5, 0x613C, 0xE3E6, 0x65B0, 0xE3E7, 0x6668, 0xE3E8, 0x71FC, 0xE3E9, 0x7533, 0xE3EA, 0x795E, + 0xE3EB, 0x7D33, 0xE3EC, 0x814E, 0xE3ED, 0x81E3, 0xE3EE, 0x8398, 0xE3EF, 0x85AA, 0xE3F0, 0x85CE, 0xE3F1, 0x8703, 0xE3F2, 0x8A0A, + 0xE3F3, 0x8EAB, 0xE3F4, 0x8F9B, 0xE3F5, 0xF971, 0xE3F6, 0x8FC5, 0xE3F7, 0x5931, 0xE3F8, 0x5BA4, 0xE3F9, 0x5BE6, 0xE3FA, 0x6089, + 0xE3FB, 0x5BE9, 0xE3FC, 0x5C0B, 0xE3FD, 0x5FC3, 0xE3FE, 0x6C81, 0xE4A1, 0xF972, 0xE4A2, 0x6DF1, 0xE4A3, 0x700B, 0xE4A4, 0x751A, + 0xE4A5, 0x82AF, 0xE4A6, 0x8AF6, 0xE4A7, 0x4EC0, 0xE4A8, 0x5341, 0xE4A9, 0xF973, 0xE4AA, 0x96D9, 0xE4AB, 0x6C0F, 0xE4AC, 0x4E9E, + 0xE4AD, 0x4FC4, 0xE4AE, 0x5152, 0xE4AF, 0x555E, 0xE4B0, 0x5A25, 0xE4B1, 0x5CE8, 0xE4B2, 0x6211, 0xE4B3, 0x7259, 0xE4B4, 0x82BD, + 0xE4B5, 0x83AA, 0xE4B6, 0x86FE, 0xE4B7, 0x8859, 0xE4B8, 0x8A1D, 0xE4B9, 0x963F, 0xE4BA, 0x96C5, 0xE4BB, 0x9913, 0xE4BC, 0x9D09, + 0xE4BD, 0x9D5D, 0xE4BE, 0x580A, 0xE4BF, 0x5CB3, 0xE4C0, 0x5DBD, 0xE4C1, 0x5E44, 0xE4C2, 0x60E1, 0xE4C3, 0x6115, 0xE4C4, 0x63E1, + 0xE4C5, 0x6A02, 0xE4C6, 0x6E25, 0xE4C7, 0x9102, 0xE4C8, 0x9354, 0xE4C9, 0x984E, 0xE4CA, 0x9C10, 0xE4CB, 0x9F77, 0xE4CC, 0x5B89, + 0xE4CD, 0x5CB8, 0xE4CE, 0x6309, 0xE4CF, 0x664F, 0xE4D0, 0x6848, 0xE4D1, 0x773C, 0xE4D2, 0x96C1, 0xE4D3, 0x978D, 0xE4D4, 0x9854, + 0xE4D5, 0x9B9F, 0xE4D6, 0x65A1, 0xE4D7, 0x8B01, 0xE4D8, 0x8ECB, 0xE4D9, 0x95BC, 0xE4DA, 0x5535, 0xE4DB, 0x5CA9, 0xE4DC, 0x5DD6, + 0xE4DD, 0x5EB5, 0xE4DE, 0x6697, 0xE4DF, 0x764C, 0xE4E0, 0x83F4, 0xE4E1, 0x95C7, 0xE4E2, 0x58D3, 0xE4E3, 0x62BC, 0xE4E4, 0x72CE, + 0xE4E5, 0x9D28, 0xE4E6, 0x4EF0, 0xE4E7, 0x592E, 0xE4E8, 0x600F, 0xE4E9, 0x663B, 0xE4EA, 0x6B83, 0xE4EB, 0x79E7, 0xE4EC, 0x9D26, + 0xE4ED, 0x5393, 0xE4EE, 0x54C0, 0xE4EF, 0x57C3, 0xE4F0, 0x5D16, 0xE4F1, 0x611B, 0xE4F2, 0x66D6, 0xE4F3, 0x6DAF, 0xE4F4, 0x788D, + 0xE4F5, 0x827E, 0xE4F6, 0x9698, 0xE4F7, 0x9744, 0xE4F8, 0x5384, 0xE4F9, 0x627C, 0xE4FA, 0x6396, 0xE4FB, 0x6DB2, 0xE4FC, 0x7E0A, + 0xE4FD, 0x814B, 0xE4FE, 0x984D, 0xE5A1, 0x6AFB, 0xE5A2, 0x7F4C, 0xE5A3, 0x9DAF, 0xE5A4, 0x9E1A, 0xE5A5, 0x4E5F, 0xE5A6, 0x503B, + 0xE5A7, 0x51B6, 0xE5A8, 0x591C, 0xE5A9, 0x60F9, 0xE5AA, 0x63F6, 0xE5AB, 0x6930, 0xE5AC, 0x723A, 0xE5AD, 0x8036, 0xE5AE, 0xF974, + 0xE5AF, 0x91CE, 0xE5B0, 0x5F31, 0xE5B1, 0xF975, 0xE5B2, 0xF976, 0xE5B3, 0x7D04, 0xE5B4, 0x82E5, 0xE5B5, 0x846F, 0xE5B6, 0x84BB, + 0xE5B7, 0x85E5, 0xE5B8, 0x8E8D, 0xE5B9, 0xF977, 0xE5BA, 0x4F6F, 0xE5BB, 0xF978, 0xE5BC, 0xF979, 0xE5BD, 0x58E4, 0xE5BE, 0x5B43, + 0xE5BF, 0x6059, 0xE5C0, 0x63DA, 0xE5C1, 0x6518, 0xE5C2, 0x656D, 0xE5C3, 0x6698, 0xE5C4, 0xF97A, 0xE5C5, 0x694A, 0xE5C6, 0x6A23, + 0xE5C7, 0x6D0B, 0xE5C8, 0x7001, 0xE5C9, 0x716C, 0xE5CA, 0x75D2, 0xE5CB, 0x760D, 0xE5CC, 0x79B3, 0xE5CD, 0x7A70, 0xE5CE, 0xF97B, + 0xE5CF, 0x7F8A, 0xE5D0, 0xF97C, 0xE5D1, 0x8944, 0xE5D2, 0xF97D, 0xE5D3, 0x8B93, 0xE5D4, 0x91C0, 0xE5D5, 0x967D, 0xE5D6, 0xF97E, + 0xE5D7, 0x990A, 0xE5D8, 0x5704, 0xE5D9, 0x5FA1, 0xE5DA, 0x65BC, 0xE5DB, 0x6F01, 0xE5DC, 0x7600, 0xE5DD, 0x79A6, 0xE5DE, 0x8A9E, + 0xE5DF, 0x99AD, 0xE5E0, 0x9B5A, 0xE5E1, 0x9F6C, 0xE5E2, 0x5104, 0xE5E3, 0x61B6, 0xE5E4, 0x6291, 0xE5E5, 0x6A8D, 0xE5E6, 0x81C6, + 0xE5E7, 0x5043, 0xE5E8, 0x5830, 0xE5E9, 0x5F66, 0xE5EA, 0x7109, 0xE5EB, 0x8A00, 0xE5EC, 0x8AFA, 0xE5ED, 0x5B7C, 0xE5EE, 0x8616, + 0xE5EF, 0x4FFA, 0xE5F0, 0x513C, 0xE5F1, 0x56B4, 0xE5F2, 0x5944, 0xE5F3, 0x63A9, 0xE5F4, 0x6DF9, 0xE5F5, 0x5DAA, 0xE5F6, 0x696D, + 0xE5F7, 0x5186, 0xE5F8, 0x4E88, 0xE5F9, 0x4F59, 0xE5FA, 0xF97F, 0xE5FB, 0xF980, 0xE5FC, 0xF981, 0xE5FD, 0x5982, 0xE5FE, 0xF982, + 0xE6A1, 0xF983, 0xE6A2, 0x6B5F, 0xE6A3, 0x6C5D, 0xE6A4, 0xF984, 0xE6A5, 0x74B5, 0xE6A6, 0x7916, 0xE6A7, 0xF985, 0xE6A8, 0x8207, + 0xE6A9, 0x8245, 0xE6AA, 0x8339, 0xE6AB, 0x8F3F, 0xE6AC, 0x8F5D, 0xE6AD, 0xF986, 0xE6AE, 0x9918, 0xE6AF, 0xF987, 0xE6B0, 0xF988, + 0xE6B1, 0xF989, 0xE6B2, 0x4EA6, 0xE6B3, 0xF98A, 0xE6B4, 0x57DF, 0xE6B5, 0x5F79, 0xE6B6, 0x6613, 0xE6B7, 0xF98B, 0xE6B8, 0xF98C, + 0xE6B9, 0x75AB, 0xE6BA, 0x7E79, 0xE6BB, 0x8B6F, 0xE6BC, 0xF98D, 0xE6BD, 0x9006, 0xE6BE, 0x9A5B, 0xE6BF, 0x56A5, 0xE6C0, 0x5827, + 0xE6C1, 0x59F8, 0xE6C2, 0x5A1F, 0xE6C3, 0x5BB4, 0xE6C4, 0xF98E, 0xE6C5, 0x5EF6, 0xE6C6, 0xF98F, 0xE6C7, 0xF990, 0xE6C8, 0x6350, + 0xE6C9, 0x633B, 0xE6CA, 0xF991, 0xE6CB, 0x693D, 0xE6CC, 0x6C87, 0xE6CD, 0x6CBF, 0xE6CE, 0x6D8E, 0xE6CF, 0x6D93, 0xE6D0, 0x6DF5, + 0xE6D1, 0x6F14, 0xE6D2, 0xF992, 0xE6D3, 0x70DF, 0xE6D4, 0x7136, 0xE6D5, 0x7159, 0xE6D6, 0xF993, 0xE6D7, 0x71C3, 0xE6D8, 0x71D5, + 0xE6D9, 0xF994, 0xE6DA, 0x784F, 0xE6DB, 0x786F, 0xE6DC, 0xF995, 0xE6DD, 0x7B75, 0xE6DE, 0x7DE3, 0xE6DF, 0xF996, 0xE6E0, 0x7E2F, + 0xE6E1, 0xF997, 0xE6E2, 0x884D, 0xE6E3, 0x8EDF, 0xE6E4, 0xF998, 0xE6E5, 0xF999, 0xE6E6, 0xF99A, 0xE6E7, 0x925B, 0xE6E8, 0xF99B, + 0xE6E9, 0x9CF6, 0xE6EA, 0xF99C, 0xE6EB, 0xF99D, 0xE6EC, 0xF99E, 0xE6ED, 0x6085, 0xE6EE, 0x6D85, 0xE6EF, 0xF99F, 0xE6F0, 0x71B1, + 0xE6F1, 0xF9A0, 0xE6F2, 0xF9A1, 0xE6F3, 0x95B1, 0xE6F4, 0x53AD, 0xE6F5, 0xF9A2, 0xE6F6, 0xF9A3, 0xE6F7, 0xF9A4, 0xE6F8, 0x67D3, + 0xE6F9, 0xF9A5, 0xE6FA, 0x708E, 0xE6FB, 0x7130, 0xE6FC, 0x7430, 0xE6FD, 0x8276, 0xE6FE, 0x82D2, 0xE7A1, 0xF9A6, 0xE7A2, 0x95BB, + 0xE7A3, 0x9AE5, 0xE7A4, 0x9E7D, 0xE7A5, 0x66C4, 0xE7A6, 0xF9A7, 0xE7A7, 0x71C1, 0xE7A8, 0x8449, 0xE7A9, 0xF9A8, 0xE7AA, 0xF9A9, + 0xE7AB, 0x584B, 0xE7AC, 0xF9AA, 0xE7AD, 0xF9AB, 0xE7AE, 0x5DB8, 0xE7AF, 0x5F71, 0xE7B0, 0xF9AC, 0xE7B1, 0x6620, 0xE7B2, 0x668E, + 0xE7B3, 0x6979, 0xE7B4, 0x69AE, 0xE7B5, 0x6C38, 0xE7B6, 0x6CF3, 0xE7B7, 0x6E36, 0xE7B8, 0x6F41, 0xE7B9, 0x6FDA, 0xE7BA, 0x701B, + 0xE7BB, 0x702F, 0xE7BC, 0x7150, 0xE7BD, 0x71DF, 0xE7BE, 0x7370, 0xE7BF, 0xF9AD, 0xE7C0, 0x745B, 0xE7C1, 0xF9AE, 0xE7C2, 0x74D4, + 0xE7C3, 0x76C8, 0xE7C4, 0x7A4E, 0xE7C5, 0x7E93, 0xE7C6, 0xF9AF, 0xE7C7, 0xF9B0, 0xE7C8, 0x82F1, 0xE7C9, 0x8A60, 0xE7CA, 0x8FCE, + 0xE7CB, 0xF9B1, 0xE7CC, 0x9348, 0xE7CD, 0xF9B2, 0xE7CE, 0x9719, 0xE7CF, 0xF9B3, 0xE7D0, 0xF9B4, 0xE7D1, 0x4E42, 0xE7D2, 0x502A, + 0xE7D3, 0xF9B5, 0xE7D4, 0x5208, 0xE7D5, 0x53E1, 0xE7D6, 0x66F3, 0xE7D7, 0x6C6D, 0xE7D8, 0x6FCA, 0xE7D9, 0x730A, 0xE7DA, 0x777F, + 0xE7DB, 0x7A62, 0xE7DC, 0x82AE, 0xE7DD, 0x85DD, 0xE7DE, 0x8602, 0xE7DF, 0xF9B6, 0xE7E0, 0x88D4, 0xE7E1, 0x8A63, 0xE7E2, 0x8B7D, + 0xE7E3, 0x8C6B, 0xE7E4, 0xF9B7, 0xE7E5, 0x92B3, 0xE7E6, 0xF9B8, 0xE7E7, 0x9713, 0xE7E8, 0x9810, 0xE7E9, 0x4E94, 0xE7EA, 0x4F0D, + 0xE7EB, 0x4FC9, 0xE7EC, 0x50B2, 0xE7ED, 0x5348, 0xE7EE, 0x543E, 0xE7EF, 0x5433, 0xE7F0, 0x55DA, 0xE7F1, 0x5862, 0xE7F2, 0x58BA, + 0xE7F3, 0x5967, 0xE7F4, 0x5A1B, 0xE7F5, 0x5BE4, 0xE7F6, 0x609F, 0xE7F7, 0xF9B9, 0xE7F8, 0x61CA, 0xE7F9, 0x6556, 0xE7FA, 0x65FF, + 0xE7FB, 0x6664, 0xE7FC, 0x68A7, 0xE7FD, 0x6C5A, 0xE7FE, 0x6FB3, 0xE8A1, 0x70CF, 0xE8A2, 0x71AC, 0xE8A3, 0x7352, 0xE8A4, 0x7B7D, + 0xE8A5, 0x8708, 0xE8A6, 0x8AA4, 0xE8A7, 0x9C32, 0xE8A8, 0x9F07, 0xE8A9, 0x5C4B, 0xE8AA, 0x6C83, 0xE8AB, 0x7344, 0xE8AC, 0x7389, + 0xE8AD, 0x923A, 0xE8AE, 0x6EAB, 0xE8AF, 0x7465, 0xE8B0, 0x761F, 0xE8B1, 0x7A69, 0xE8B2, 0x7E15, 0xE8B3, 0x860A, 0xE8B4, 0x5140, + 0xE8B5, 0x58C5, 0xE8B6, 0x64C1, 0xE8B7, 0x74EE, 0xE8B8, 0x7515, 0xE8B9, 0x7670, 0xE8BA, 0x7FC1, 0xE8BB, 0x9095, 0xE8BC, 0x96CD, + 0xE8BD, 0x9954, 0xE8BE, 0x6E26, 0xE8BF, 0x74E6, 0xE8C0, 0x7AA9, 0xE8C1, 0x7AAA, 0xE8C2, 0x81E5, 0xE8C3, 0x86D9, 0xE8C4, 0x8778, + 0xE8C5, 0x8A1B, 0xE8C6, 0x5A49, 0xE8C7, 0x5B8C, 0xE8C8, 0x5B9B, 0xE8C9, 0x68A1, 0xE8CA, 0x6900, 0xE8CB, 0x6D63, 0xE8CC, 0x73A9, + 0xE8CD, 0x7413, 0xE8CE, 0x742C, 0xE8CF, 0x7897, 0xE8D0, 0x7DE9, 0xE8D1, 0x7FEB, 0xE8D2, 0x8118, 0xE8D3, 0x8155, 0xE8D4, 0x839E, + 0xE8D5, 0x8C4C, 0xE8D6, 0x962E, 0xE8D7, 0x9811, 0xE8D8, 0x66F0, 0xE8D9, 0x5F80, 0xE8DA, 0x65FA, 0xE8DB, 0x6789, 0xE8DC, 0x6C6A, + 0xE8DD, 0x738B, 0xE8DE, 0x502D, 0xE8DF, 0x5A03, 0xE8E0, 0x6B6A, 0xE8E1, 0x77EE, 0xE8E2, 0x5916, 0xE8E3, 0x5D6C, 0xE8E4, 0x5DCD, + 0xE8E5, 0x7325, 0xE8E6, 0x754F, 0xE8E7, 0xF9BA, 0xE8E8, 0xF9BB, 0xE8E9, 0x50E5, 0xE8EA, 0x51F9, 0xE8EB, 0x582F, 0xE8EC, 0x592D, + 0xE8ED, 0x5996, 0xE8EE, 0x59DA, 0xE8EF, 0x5BE5, 0xE8F0, 0xF9BC, 0xE8F1, 0xF9BD, 0xE8F2, 0x5DA2, 0xE8F3, 0x62D7, 0xE8F4, 0x6416, + 0xE8F5, 0x6493, 0xE8F6, 0x64FE, 0xE8F7, 0xF9BE, 0xE8F8, 0x66DC, 0xE8F9, 0xF9BF, 0xE8FA, 0x6A48, 0xE8FB, 0xF9C0, 0xE8FC, 0x71FF, + 0xE8FD, 0x7464, 0xE8FE, 0xF9C1, 0xE9A1, 0x7A88, 0xE9A2, 0x7AAF, 0xE9A3, 0x7E47, 0xE9A4, 0x7E5E, 0xE9A5, 0x8000, 0xE9A6, 0x8170, + 0xE9A7, 0xF9C2, 0xE9A8, 0x87EF, 0xE9A9, 0x8981, 0xE9AA, 0x8B20, 0xE9AB, 0x9059, 0xE9AC, 0xF9C3, 0xE9AD, 0x9080, 0xE9AE, 0x9952, + 0xE9AF, 0x617E, 0xE9B0, 0x6B32, 0xE9B1, 0x6D74, 0xE9B2, 0x7E1F, 0xE9B3, 0x8925, 0xE9B4, 0x8FB1, 0xE9B5, 0x4FD1, 0xE9B6, 0x50AD, + 0xE9B7, 0x5197, 0xE9B8, 0x52C7, 0xE9B9, 0x57C7, 0xE9BA, 0x5889, 0xE9BB, 0x5BB9, 0xE9BC, 0x5EB8, 0xE9BD, 0x6142, 0xE9BE, 0x6995, + 0xE9BF, 0x6D8C, 0xE9C0, 0x6E67, 0xE9C1, 0x6EB6, 0xE9C2, 0x7194, 0xE9C3, 0x7462, 0xE9C4, 0x7528, 0xE9C5, 0x752C, 0xE9C6, 0x8073, + 0xE9C7, 0x8338, 0xE9C8, 0x84C9, 0xE9C9, 0x8E0A, 0xE9CA, 0x9394, 0xE9CB, 0x93DE, 0xE9CC, 0xF9C4, 0xE9CD, 0x4E8E, 0xE9CE, 0x4F51, + 0xE9CF, 0x5076, 0xE9D0, 0x512A, 0xE9D1, 0x53C8, 0xE9D2, 0x53CB, 0xE9D3, 0x53F3, 0xE9D4, 0x5B87, 0xE9D5, 0x5BD3, 0xE9D6, 0x5C24, + 0xE9D7, 0x611A, 0xE9D8, 0x6182, 0xE9D9, 0x65F4, 0xE9DA, 0x725B, 0xE9DB, 0x7397, 0xE9DC, 0x7440, 0xE9DD, 0x76C2, 0xE9DE, 0x7950, + 0xE9DF, 0x7991, 0xE9E0, 0x79B9, 0xE9E1, 0x7D06, 0xE9E2, 0x7FBD, 0xE9E3, 0x828B, 0xE9E4, 0x85D5, 0xE9E5, 0x865E, 0xE9E6, 0x8FC2, + 0xE9E7, 0x9047, 0xE9E8, 0x90F5, 0xE9E9, 0x91EA, 0xE9EA, 0x9685, 0xE9EB, 0x96E8, 0xE9EC, 0x96E9, 0xE9ED, 0x52D6, 0xE9EE, 0x5F67, + 0xE9EF, 0x65ED, 0xE9F0, 0x6631, 0xE9F1, 0x682F, 0xE9F2, 0x715C, 0xE9F3, 0x7A36, 0xE9F4, 0x90C1, 0xE9F5, 0x980A, 0xE9F6, 0x4E91, + 0xE9F7, 0xF9C5, 0xE9F8, 0x6A52, 0xE9F9, 0x6B9E, 0xE9FA, 0x6F90, 0xE9FB, 0x7189, 0xE9FC, 0x8018, 0xE9FD, 0x82B8, 0xE9FE, 0x8553, + 0xEAA1, 0x904B, 0xEAA2, 0x9695, 0xEAA3, 0x96F2, 0xEAA4, 0x97FB, 0xEAA5, 0x851A, 0xEAA6, 0x9B31, 0xEAA7, 0x4E90, 0xEAA8, 0x718A, + 0xEAA9, 0x96C4, 0xEAAA, 0x5143, 0xEAAB, 0x539F, 0xEAAC, 0x54E1, 0xEAAD, 0x5713, 0xEAAE, 0x5712, 0xEAAF, 0x57A3, 0xEAB0, 0x5A9B, + 0xEAB1, 0x5AC4, 0xEAB2, 0x5BC3, 0xEAB3, 0x6028, 0xEAB4, 0x613F, 0xEAB5, 0x63F4, 0xEAB6, 0x6C85, 0xEAB7, 0x6D39, 0xEAB8, 0x6E72, + 0xEAB9, 0x6E90, 0xEABA, 0x7230, 0xEABB, 0x733F, 0xEABC, 0x7457, 0xEABD, 0x82D1, 0xEABE, 0x8881, 0xEABF, 0x8F45, 0xEAC0, 0x9060, + 0xEAC1, 0xF9C6, 0xEAC2, 0x9662, 0xEAC3, 0x9858, 0xEAC4, 0x9D1B, 0xEAC5, 0x6708, 0xEAC6, 0x8D8A, 0xEAC7, 0x925E, 0xEAC8, 0x4F4D, + 0xEAC9, 0x5049, 0xEACA, 0x50DE, 0xEACB, 0x5371, 0xEACC, 0x570D, 0xEACD, 0x59D4, 0xEACE, 0x5A01, 0xEACF, 0x5C09, 0xEAD0, 0x6170, + 0xEAD1, 0x6690, 0xEAD2, 0x6E2D, 0xEAD3, 0x7232, 0xEAD4, 0x744B, 0xEAD5, 0x7DEF, 0xEAD6, 0x80C3, 0xEAD7, 0x840E, 0xEAD8, 0x8466, + 0xEAD9, 0x853F, 0xEADA, 0x875F, 0xEADB, 0x885B, 0xEADC, 0x8918, 0xEADD, 0x8B02, 0xEADE, 0x9055, 0xEADF, 0x97CB, 0xEAE0, 0x9B4F, + 0xEAE1, 0x4E73, 0xEAE2, 0x4F91, 0xEAE3, 0x5112, 0xEAE4, 0x516A, 0xEAE5, 0xF9C7, 0xEAE6, 0x552F, 0xEAE7, 0x55A9, 0xEAE8, 0x5B7A, + 0xEAE9, 0x5BA5, 0xEAEA, 0x5E7C, 0xEAEB, 0x5E7D, 0xEAEC, 0x5EBE, 0xEAED, 0x60A0, 0xEAEE, 0x60DF, 0xEAEF, 0x6108, 0xEAF0, 0x6109, + 0xEAF1, 0x63C4, 0xEAF2, 0x6538, 0xEAF3, 0x6709, 0xEAF4, 0xF9C8, 0xEAF5, 0x67D4, 0xEAF6, 0x67DA, 0xEAF7, 0xF9C9, 0xEAF8, 0x6961, + 0xEAF9, 0x6962, 0xEAFA, 0x6CB9, 0xEAFB, 0x6D27, 0xEAFC, 0xF9CA, 0xEAFD, 0x6E38, 0xEAFE, 0xF9CB, 0xEBA1, 0x6FE1, 0xEBA2, 0x7336, + 0xEBA3, 0x7337, 0xEBA4, 0xF9CC, 0xEBA5, 0x745C, 0xEBA6, 0x7531, 0xEBA7, 0xF9CD, 0xEBA8, 0x7652, 0xEBA9, 0xF9CE, 0xEBAA, 0xF9CF, + 0xEBAB, 0x7DAD, 0xEBAC, 0x81FE, 0xEBAD, 0x8438, 0xEBAE, 0x88D5, 0xEBAF, 0x8A98, 0xEBB0, 0x8ADB, 0xEBB1, 0x8AED, 0xEBB2, 0x8E30, + 0xEBB3, 0x8E42, 0xEBB4, 0x904A, 0xEBB5, 0x903E, 0xEBB6, 0x907A, 0xEBB7, 0x9149, 0xEBB8, 0x91C9, 0xEBB9, 0x936E, 0xEBBA, 0xF9D0, + 0xEBBB, 0xF9D1, 0xEBBC, 0x5809, 0xEBBD, 0xF9D2, 0xEBBE, 0x6BD3, 0xEBBF, 0x8089, 0xEBC0, 0x80B2, 0xEBC1, 0xF9D3, 0xEBC2, 0xF9D4, + 0xEBC3, 0x5141, 0xEBC4, 0x596B, 0xEBC5, 0x5C39, 0xEBC6, 0xF9D5, 0xEBC7, 0xF9D6, 0xEBC8, 0x6F64, 0xEBC9, 0x73A7, 0xEBCA, 0x80E4, + 0xEBCB, 0x8D07, 0xEBCC, 0xF9D7, 0xEBCD, 0x9217, 0xEBCE, 0x958F, 0xEBCF, 0xF9D8, 0xEBD0, 0xF9D9, 0xEBD1, 0xF9DA, 0xEBD2, 0xF9DB, + 0xEBD3, 0x807F, 0xEBD4, 0x620E, 0xEBD5, 0x701C, 0xEBD6, 0x7D68, 0xEBD7, 0x878D, 0xEBD8, 0xF9DC, 0xEBD9, 0x57A0, 0xEBDA, 0x6069, + 0xEBDB, 0x6147, 0xEBDC, 0x6BB7, 0xEBDD, 0x8ABE, 0xEBDE, 0x9280, 0xEBDF, 0x96B1, 0xEBE0, 0x4E59, 0xEBE1, 0x541F, 0xEBE2, 0x6DEB, + 0xEBE3, 0x852D, 0xEBE4, 0x9670, 0xEBE5, 0x97F3, 0xEBE6, 0x98EE, 0xEBE7, 0x63D6, 0xEBE8, 0x6CE3, 0xEBE9, 0x9091, 0xEBEA, 0x51DD, + 0xEBEB, 0x61C9, 0xEBEC, 0x81BA, 0xEBED, 0x9DF9, 0xEBEE, 0x4F9D, 0xEBEF, 0x501A, 0xEBF0, 0x5100, 0xEBF1, 0x5B9C, 0xEBF2, 0x610F, + 0xEBF3, 0x61FF, 0xEBF4, 0x64EC, 0xEBF5, 0x6905, 0xEBF6, 0x6BC5, 0xEBF7, 0x7591, 0xEBF8, 0x77E3, 0xEBF9, 0x7FA9, 0xEBFA, 0x8264, + 0xEBFB, 0x858F, 0xEBFC, 0x87FB, 0xEBFD, 0x8863, 0xEBFE, 0x8ABC, 0xECA1, 0x8B70, 0xECA2, 0x91AB, 0xECA3, 0x4E8C, 0xECA4, 0x4EE5, + 0xECA5, 0x4F0A, 0xECA6, 0xF9DD, 0xECA7, 0xF9DE, 0xECA8, 0x5937, 0xECA9, 0x59E8, 0xECAA, 0xF9DF, 0xECAB, 0x5DF2, 0xECAC, 0x5F1B, + 0xECAD, 0x5F5B, 0xECAE, 0x6021, 0xECAF, 0xF9E0, 0xECB0, 0xF9E1, 0xECB1, 0xF9E2, 0xECB2, 0xF9E3, 0xECB3, 0x723E, 0xECB4, 0x73E5, + 0xECB5, 0xF9E4, 0xECB6, 0x7570, 0xECB7, 0x75CD, 0xECB8, 0xF9E5, 0xECB9, 0x79FB, 0xECBA, 0xF9E6, 0xECBB, 0x800C, 0xECBC, 0x8033, + 0xECBD, 0x8084, 0xECBE, 0x82E1, 0xECBF, 0x8351, 0xECC0, 0xF9E7, 0xECC1, 0xF9E8, 0xECC2, 0x8CBD, 0xECC3, 0x8CB3, 0xECC4, 0x9087, + 0xECC5, 0xF9E9, 0xECC6, 0xF9EA, 0xECC7, 0x98F4, 0xECC8, 0x990C, 0xECC9, 0xF9EB, 0xECCA, 0xF9EC, 0xECCB, 0x7037, 0xECCC, 0x76CA, + 0xECCD, 0x7FCA, 0xECCE, 0x7FCC, 0xECCF, 0x7FFC, 0xECD0, 0x8B1A, 0xECD1, 0x4EBA, 0xECD2, 0x4EC1, 0xECD3, 0x5203, 0xECD4, 0x5370, + 0xECD5, 0xF9ED, 0xECD6, 0x54BD, 0xECD7, 0x56E0, 0xECD8, 0x59FB, 0xECD9, 0x5BC5, 0xECDA, 0x5F15, 0xECDB, 0x5FCD, 0xECDC, 0x6E6E, + 0xECDD, 0xF9EE, 0xECDE, 0xF9EF, 0xECDF, 0x7D6A, 0xECE0, 0x8335, 0xECE1, 0xF9F0, 0xECE2, 0x8693, 0xECE3, 0x8A8D, 0xECE4, 0xF9F1, + 0xECE5, 0x976D, 0xECE6, 0x9777, 0xECE7, 0xF9F2, 0xECE8, 0xF9F3, 0xECE9, 0x4E00, 0xECEA, 0x4F5A, 0xECEB, 0x4F7E, 0xECEC, 0x58F9, + 0xECED, 0x65E5, 0xECEE, 0x6EA2, 0xECEF, 0x9038, 0xECF0, 0x93B0, 0xECF1, 0x99B9, 0xECF2, 0x4EFB, 0xECF3, 0x58EC, 0xECF4, 0x598A, + 0xECF5, 0x59D9, 0xECF6, 0x6041, 0xECF7, 0xF9F4, 0xECF8, 0xF9F5, 0xECF9, 0x7A14, 0xECFA, 0xF9F6, 0xECFB, 0x834F, 0xECFC, 0x8CC3, + 0xECFD, 0x5165, 0xECFE, 0x5344, 0xEDA1, 0xF9F7, 0xEDA2, 0xF9F8, 0xEDA3, 0xF9F9, 0xEDA4, 0x4ECD, 0xEDA5, 0x5269, 0xEDA6, 0x5B55, + 0xEDA7, 0x82BF, 0xEDA8, 0x4ED4, 0xEDA9, 0x523A, 0xEDAA, 0x54A8, 0xEDAB, 0x59C9, 0xEDAC, 0x59FF, 0xEDAD, 0x5B50, 0xEDAE, 0x5B57, + 0xEDAF, 0x5B5C, 0xEDB0, 0x6063, 0xEDB1, 0x6148, 0xEDB2, 0x6ECB, 0xEDB3, 0x7099, 0xEDB4, 0x716E, 0xEDB5, 0x7386, 0xEDB6, 0x74F7, + 0xEDB7, 0x75B5, 0xEDB8, 0x78C1, 0xEDB9, 0x7D2B, 0xEDBA, 0x8005, 0xEDBB, 0x81EA, 0xEDBC, 0x8328, 0xEDBD, 0x8517, 0xEDBE, 0x85C9, + 0xEDBF, 0x8AEE, 0xEDC0, 0x8CC7, 0xEDC1, 0x96CC, 0xEDC2, 0x4F5C, 0xEDC3, 0x52FA, 0xEDC4, 0x56BC, 0xEDC5, 0x65AB, 0xEDC6, 0x6628, + 0xEDC7, 0x707C, 0xEDC8, 0x70B8, 0xEDC9, 0x7235, 0xEDCA, 0x7DBD, 0xEDCB, 0x828D, 0xEDCC, 0x914C, 0xEDCD, 0x96C0, 0xEDCE, 0x9D72, + 0xEDCF, 0x5B71, 0xEDD0, 0x68E7, 0xEDD1, 0x6B98, 0xEDD2, 0x6F7A, 0xEDD3, 0x76DE, 0xEDD4, 0x5C91, 0xEDD5, 0x66AB, 0xEDD6, 0x6F5B, + 0xEDD7, 0x7BB4, 0xEDD8, 0x7C2A, 0xEDD9, 0x8836, 0xEDDA, 0x96DC, 0xEDDB, 0x4E08, 0xEDDC, 0x4ED7, 0xEDDD, 0x5320, 0xEDDE, 0x5834, + 0xEDDF, 0x58BB, 0xEDE0, 0x58EF, 0xEDE1, 0x596C, 0xEDE2, 0x5C07, 0xEDE3, 0x5E33, 0xEDE4, 0x5E84, 0xEDE5, 0x5F35, 0xEDE6, 0x638C, + 0xEDE7, 0x66B2, 0xEDE8, 0x6756, 0xEDE9, 0x6A1F, 0xEDEA, 0x6AA3, 0xEDEB, 0x6B0C, 0xEDEC, 0x6F3F, 0xEDED, 0x7246, 0xEDEE, 0xF9FA, + 0xEDEF, 0x7350, 0xEDF0, 0x748B, 0xEDF1, 0x7AE0, 0xEDF2, 0x7CA7, 0xEDF3, 0x8178, 0xEDF4, 0x81DF, 0xEDF5, 0x81E7, 0xEDF6, 0x838A, + 0xEDF7, 0x846C, 0xEDF8, 0x8523, 0xEDF9, 0x8594, 0xEDFA, 0x85CF, 0xEDFB, 0x88DD, 0xEDFC, 0x8D13, 0xEDFD, 0x91AC, 0xEDFE, 0x9577, + 0xEEA1, 0x969C, 0xEEA2, 0x518D, 0xEEA3, 0x54C9, 0xEEA4, 0x5728, 0xEEA5, 0x5BB0, 0xEEA6, 0x624D, 0xEEA7, 0x6750, 0xEEA8, 0x683D, + 0xEEA9, 0x6893, 0xEEAA, 0x6E3D, 0xEEAB, 0x6ED3, 0xEEAC, 0x707D, 0xEEAD, 0x7E21, 0xEEAE, 0x88C1, 0xEEAF, 0x8CA1, 0xEEB0, 0x8F09, + 0xEEB1, 0x9F4B, 0xEEB2, 0x9F4E, 0xEEB3, 0x722D, 0xEEB4, 0x7B8F, 0xEEB5, 0x8ACD, 0xEEB6, 0x931A, 0xEEB7, 0x4F47, 0xEEB8, 0x4F4E, + 0xEEB9, 0x5132, 0xEEBA, 0x5480, 0xEEBB, 0x59D0, 0xEEBC, 0x5E95, 0xEEBD, 0x62B5, 0xEEBE, 0x6775, 0xEEBF, 0x696E, 0xEEC0, 0x6A17, + 0xEEC1, 0x6CAE, 0xEEC2, 0x6E1A, 0xEEC3, 0x72D9, 0xEEC4, 0x732A, 0xEEC5, 0x75BD, 0xEEC6, 0x7BB8, 0xEEC7, 0x7D35, 0xEEC8, 0x82E7, + 0xEEC9, 0x83F9, 0xEECA, 0x8457, 0xEECB, 0x85F7, 0xEECC, 0x8A5B, 0xEECD, 0x8CAF, 0xEECE, 0x8E87, 0xEECF, 0x9019, 0xEED0, 0x90B8, + 0xEED1, 0x96CE, 0xEED2, 0x9F5F, 0xEED3, 0x52E3, 0xEED4, 0x540A, 0xEED5, 0x5AE1, 0xEED6, 0x5BC2, 0xEED7, 0x6458, 0xEED8, 0x6575, + 0xEED9, 0x6EF4, 0xEEDA, 0x72C4, 0xEEDB, 0xF9FB, 0xEEDC, 0x7684, 0xEEDD, 0x7A4D, 0xEEDE, 0x7B1B, 0xEEDF, 0x7C4D, 0xEEE0, 0x7E3E, + 0xEEE1, 0x7FDF, 0xEEE2, 0x837B, 0xEEE3, 0x8B2B, 0xEEE4, 0x8CCA, 0xEEE5, 0x8D64, 0xEEE6, 0x8DE1, 0xEEE7, 0x8E5F, 0xEEE8, 0x8FEA, + 0xEEE9, 0x8FF9, 0xEEEA, 0x9069, 0xEEEB, 0x93D1, 0xEEEC, 0x4F43, 0xEEED, 0x4F7A, 0xEEEE, 0x50B3, 0xEEEF, 0x5168, 0xEEF0, 0x5178, + 0xEEF1, 0x524D, 0xEEF2, 0x526A, 0xEEF3, 0x5861, 0xEEF4, 0x587C, 0xEEF5, 0x5960, 0xEEF6, 0x5C08, 0xEEF7, 0x5C55, 0xEEF8, 0x5EDB, + 0xEEF9, 0x609B, 0xEEFA, 0x6230, 0xEEFB, 0x6813, 0xEEFC, 0x6BBF, 0xEEFD, 0x6C08, 0xEEFE, 0x6FB1, 0xEFA1, 0x714E, 0xEFA2, 0x7420, + 0xEFA3, 0x7530, 0xEFA4, 0x7538, 0xEFA5, 0x7551, 0xEFA6, 0x7672, 0xEFA7, 0x7B4C, 0xEFA8, 0x7B8B, 0xEFA9, 0x7BAD, 0xEFAA, 0x7BC6, + 0xEFAB, 0x7E8F, 0xEFAC, 0x8A6E, 0xEFAD, 0x8F3E, 0xEFAE, 0x8F49, 0xEFAF, 0x923F, 0xEFB0, 0x9293, 0xEFB1, 0x9322, 0xEFB2, 0x942B, + 0xEFB3, 0x96FB, 0xEFB4, 0x985A, 0xEFB5, 0x986B, 0xEFB6, 0x991E, 0xEFB7, 0x5207, 0xEFB8, 0x622A, 0xEFB9, 0x6298, 0xEFBA, 0x6D59, + 0xEFBB, 0x7664, 0xEFBC, 0x7ACA, 0xEFBD, 0x7BC0, 0xEFBE, 0x7D76, 0xEFBF, 0x5360, 0xEFC0, 0x5CBE, 0xEFC1, 0x5E97, 0xEFC2, 0x6F38, + 0xEFC3, 0x70B9, 0xEFC4, 0x7C98, 0xEFC5, 0x9711, 0xEFC6, 0x9B8E, 0xEFC7, 0x9EDE, 0xEFC8, 0x63A5, 0xEFC9, 0x647A, 0xEFCA, 0x8776, + 0xEFCB, 0x4E01, 0xEFCC, 0x4E95, 0xEFCD, 0x4EAD, 0xEFCE, 0x505C, 0xEFCF, 0x5075, 0xEFD0, 0x5448, 0xEFD1, 0x59C3, 0xEFD2, 0x5B9A, + 0xEFD3, 0x5E40, 0xEFD4, 0x5EAD, 0xEFD5, 0x5EF7, 0xEFD6, 0x5F81, 0xEFD7, 0x60C5, 0xEFD8, 0x633A, 0xEFD9, 0x653F, 0xEFDA, 0x6574, + 0xEFDB, 0x65CC, 0xEFDC, 0x6676, 0xEFDD, 0x6678, 0xEFDE, 0x67FE, 0xEFDF, 0x6968, 0xEFE0, 0x6A89, 0xEFE1, 0x6B63, 0xEFE2, 0x6C40, + 0xEFE3, 0x6DC0, 0xEFE4, 0x6DE8, 0xEFE5, 0x6E1F, 0xEFE6, 0x6E5E, 0xEFE7, 0x701E, 0xEFE8, 0x70A1, 0xEFE9, 0x738E, 0xEFEA, 0x73FD, + 0xEFEB, 0x753A, 0xEFEC, 0x775B, 0xEFED, 0x7887, 0xEFEE, 0x798E, 0xEFEF, 0x7A0B, 0xEFF0, 0x7A7D, 0xEFF1, 0x7CBE, 0xEFF2, 0x7D8E, + 0xEFF3, 0x8247, 0xEFF4, 0x8A02, 0xEFF5, 0x8AEA, 0xEFF6, 0x8C9E, 0xEFF7, 0x912D, 0xEFF8, 0x914A, 0xEFF9, 0x91D8, 0xEFFA, 0x9266, + 0xEFFB, 0x92CC, 0xEFFC, 0x9320, 0xEFFD, 0x9706, 0xEFFE, 0x9756, 0xF0A1, 0x975C, 0xF0A2, 0x9802, 0xF0A3, 0x9F0E, 0xF0A4, 0x5236, + 0xF0A5, 0x5291, 0xF0A6, 0x557C, 0xF0A7, 0x5824, 0xF0A8, 0x5E1D, 0xF0A9, 0x5F1F, 0xF0AA, 0x608C, 0xF0AB, 0x63D0, 0xF0AC, 0x68AF, + 0xF0AD, 0x6FDF, 0xF0AE, 0x796D, 0xF0AF, 0x7B2C, 0xF0B0, 0x81CD, 0xF0B1, 0x85BA, 0xF0B2, 0x88FD, 0xF0B3, 0x8AF8, 0xF0B4, 0x8E44, + 0xF0B5, 0x918D, 0xF0B6, 0x9664, 0xF0B7, 0x969B, 0xF0B8, 0x973D, 0xF0B9, 0x984C, 0xF0BA, 0x9F4A, 0xF0BB, 0x4FCE, 0xF0BC, 0x5146, + 0xF0BD, 0x51CB, 0xF0BE, 0x52A9, 0xF0BF, 0x5632, 0xF0C0, 0x5F14, 0xF0C1, 0x5F6B, 0xF0C2, 0x63AA, 0xF0C3, 0x64CD, 0xF0C4, 0x65E9, + 0xF0C5, 0x6641, 0xF0C6, 0x66FA, 0xF0C7, 0x66F9, 0xF0C8, 0x671D, 0xF0C9, 0x689D, 0xF0CA, 0x68D7, 0xF0CB, 0x69FD, 0xF0CC, 0x6F15, + 0xF0CD, 0x6F6E, 0xF0CE, 0x7167, 0xF0CF, 0x71E5, 0xF0D0, 0x722A, 0xF0D1, 0x74AA, 0xF0D2, 0x773A, 0xF0D3, 0x7956, 0xF0D4, 0x795A, + 0xF0D5, 0x79DF, 0xF0D6, 0x7A20, 0xF0D7, 0x7A95, 0xF0D8, 0x7C97, 0xF0D9, 0x7CDF, 0xF0DA, 0x7D44, 0xF0DB, 0x7E70, 0xF0DC, 0x8087, + 0xF0DD, 0x85FB, 0xF0DE, 0x86A4, 0xF0DF, 0x8A54, 0xF0E0, 0x8ABF, 0xF0E1, 0x8D99, 0xF0E2, 0x8E81, 0xF0E3, 0x9020, 0xF0E4, 0x906D, + 0xF0E5, 0x91E3, 0xF0E6, 0x963B, 0xF0E7, 0x96D5, 0xF0E8, 0x9CE5, 0xF0E9, 0x65CF, 0xF0EA, 0x7C07, 0xF0EB, 0x8DB3, 0xF0EC, 0x93C3, + 0xF0ED, 0x5B58, 0xF0EE, 0x5C0A, 0xF0EF, 0x5352, 0xF0F0, 0x62D9, 0xF0F1, 0x731D, 0xF0F2, 0x5027, 0xF0F3, 0x5B97, 0xF0F4, 0x5F9E, + 0xF0F5, 0x60B0, 0xF0F6, 0x616B, 0xF0F7, 0x68D5, 0xF0F8, 0x6DD9, 0xF0F9, 0x742E, 0xF0FA, 0x7A2E, 0xF0FB, 0x7D42, 0xF0FC, 0x7D9C, + 0xF0FD, 0x7E31, 0xF0FE, 0x816B, 0xF1A1, 0x8E2A, 0xF1A2, 0x8E35, 0xF1A3, 0x937E, 0xF1A4, 0x9418, 0xF1A5, 0x4F50, 0xF1A6, 0x5750, + 0xF1A7, 0x5DE6, 0xF1A8, 0x5EA7, 0xF1A9, 0x632B, 0xF1AA, 0x7F6A, 0xF1AB, 0x4E3B, 0xF1AC, 0x4F4F, 0xF1AD, 0x4F8F, 0xF1AE, 0x505A, + 0xF1AF, 0x59DD, 0xF1B0, 0x80C4, 0xF1B1, 0x546A, 0xF1B2, 0x5468, 0xF1B3, 0x55FE, 0xF1B4, 0x594F, 0xF1B5, 0x5B99, 0xF1B6, 0x5DDE, + 0xF1B7, 0x5EDA, 0xF1B8, 0x665D, 0xF1B9, 0x6731, 0xF1BA, 0x67F1, 0xF1BB, 0x682A, 0xF1BC, 0x6CE8, 0xF1BD, 0x6D32, 0xF1BE, 0x6E4A, + 0xF1BF, 0x6F8D, 0xF1C0, 0x70B7, 0xF1C1, 0x73E0, 0xF1C2, 0x7587, 0xF1C3, 0x7C4C, 0xF1C4, 0x7D02, 0xF1C5, 0x7D2C, 0xF1C6, 0x7DA2, + 0xF1C7, 0x821F, 0xF1C8, 0x86DB, 0xF1C9, 0x8A3B, 0xF1CA, 0x8A85, 0xF1CB, 0x8D70, 0xF1CC, 0x8E8A, 0xF1CD, 0x8F33, 0xF1CE, 0x9031, + 0xF1CF, 0x914E, 0xF1D0, 0x9152, 0xF1D1, 0x9444, 0xF1D2, 0x99D0, 0xF1D3, 0x7AF9, 0xF1D4, 0x7CA5, 0xF1D5, 0x4FCA, 0xF1D6, 0x5101, + 0xF1D7, 0x51C6, 0xF1D8, 0x57C8, 0xF1D9, 0x5BEF, 0xF1DA, 0x5CFB, 0xF1DB, 0x6659, 0xF1DC, 0x6A3D, 0xF1DD, 0x6D5A, 0xF1DE, 0x6E96, + 0xF1DF, 0x6FEC, 0xF1E0, 0x710C, 0xF1E1, 0x756F, 0xF1E2, 0x7AE3, 0xF1E3, 0x8822, 0xF1E4, 0x9021, 0xF1E5, 0x9075, 0xF1E6, 0x96CB, + 0xF1E7, 0x99FF, 0xF1E8, 0x8301, 0xF1E9, 0x4E2D, 0xF1EA, 0x4EF2, 0xF1EB, 0x8846, 0xF1EC, 0x91CD, 0xF1ED, 0x537D, 0xF1EE, 0x6ADB, + 0xF1EF, 0x696B, 0xF1F0, 0x6C41, 0xF1F1, 0x847A, 0xF1F2, 0x589E, 0xF1F3, 0x618E, 0xF1F4, 0x66FE, 0xF1F5, 0x62EF, 0xF1F6, 0x70DD, + 0xF1F7, 0x7511, 0xF1F8, 0x75C7, 0xF1F9, 0x7E52, 0xF1FA, 0x84B8, 0xF1FB, 0x8B49, 0xF1FC, 0x8D08, 0xF1FD, 0x4E4B, 0xF1FE, 0x53EA, + 0xF2A1, 0x54AB, 0xF2A2, 0x5730, 0xF2A3, 0x5740, 0xF2A4, 0x5FD7, 0xF2A5, 0x6301, 0xF2A6, 0x6307, 0xF2A7, 0x646F, 0xF2A8, 0x652F, + 0xF2A9, 0x65E8, 0xF2AA, 0x667A, 0xF2AB, 0x679D, 0xF2AC, 0x67B3, 0xF2AD, 0x6B62, 0xF2AE, 0x6C60, 0xF2AF, 0x6C9A, 0xF2B0, 0x6F2C, + 0xF2B1, 0x77E5, 0xF2B2, 0x7825, 0xF2B3, 0x7949, 0xF2B4, 0x7957, 0xF2B5, 0x7D19, 0xF2B6, 0x80A2, 0xF2B7, 0x8102, 0xF2B8, 0x81F3, + 0xF2B9, 0x829D, 0xF2BA, 0x82B7, 0xF2BB, 0x8718, 0xF2BC, 0x8A8C, 0xF2BD, 0xF9FC, 0xF2BE, 0x8D04, 0xF2BF, 0x8DBE, 0xF2C0, 0x9072, + 0xF2C1, 0x76F4, 0xF2C2, 0x7A19, 0xF2C3, 0x7A37, 0xF2C4, 0x7E54, 0xF2C5, 0x8077, 0xF2C6, 0x5507, 0xF2C7, 0x55D4, 0xF2C8, 0x5875, + 0xF2C9, 0x632F, 0xF2CA, 0x6422, 0xF2CB, 0x6649, 0xF2CC, 0x664B, 0xF2CD, 0x686D, 0xF2CE, 0x699B, 0xF2CF, 0x6B84, 0xF2D0, 0x6D25, + 0xF2D1, 0x6EB1, 0xF2D2, 0x73CD, 0xF2D3, 0x7468, 0xF2D4, 0x74A1, 0xF2D5, 0x755B, 0xF2D6, 0x75B9, 0xF2D7, 0x76E1, 0xF2D8, 0x771E, + 0xF2D9, 0x778B, 0xF2DA, 0x79E6, 0xF2DB, 0x7E09, 0xF2DC, 0x7E1D, 0xF2DD, 0x81FB, 0xF2DE, 0x852F, 0xF2DF, 0x8897, 0xF2E0, 0x8A3A, + 0xF2E1, 0x8CD1, 0xF2E2, 0x8EEB, 0xF2E3, 0x8FB0, 0xF2E4, 0x9032, 0xF2E5, 0x93AD, 0xF2E6, 0x9663, 0xF2E7, 0x9673, 0xF2E8, 0x9707, + 0xF2E9, 0x4F84, 0xF2EA, 0x53F1, 0xF2EB, 0x59EA, 0xF2EC, 0x5AC9, 0xF2ED, 0x5E19, 0xF2EE, 0x684E, 0xF2EF, 0x74C6, 0xF2F0, 0x75BE, + 0xF2F1, 0x79E9, 0xF2F2, 0x7A92, 0xF2F3, 0x81A3, 0xF2F4, 0x86ED, 0xF2F5, 0x8CEA, 0xF2F6, 0x8DCC, 0xF2F7, 0x8FED, 0xF2F8, 0x659F, + 0xF2F9, 0x6715, 0xF2FA, 0xF9FD, 0xF2FB, 0x57F7, 0xF2FC, 0x6F57, 0xF2FD, 0x7DDD, 0xF2FE, 0x8F2F, 0xF3A1, 0x93F6, 0xF3A2, 0x96C6, + 0xF3A3, 0x5FB5, 0xF3A4, 0x61F2, 0xF3A5, 0x6F84, 0xF3A6, 0x4E14, 0xF3A7, 0x4F98, 0xF3A8, 0x501F, 0xF3A9, 0x53C9, 0xF3AA, 0x55DF, + 0xF3AB, 0x5D6F, 0xF3AC, 0x5DEE, 0xF3AD, 0x6B21, 0xF3AE, 0x6B64, 0xF3AF, 0x78CB, 0xF3B0, 0x7B9A, 0xF3B1, 0xF9FE, 0xF3B2, 0x8E49, + 0xF3B3, 0x8ECA, 0xF3B4, 0x906E, 0xF3B5, 0x6349, 0xF3B6, 0x643E, 0xF3B7, 0x7740, 0xF3B8, 0x7A84, 0xF3B9, 0x932F, 0xF3BA, 0x947F, + 0xF3BB, 0x9F6A, 0xF3BC, 0x64B0, 0xF3BD, 0x6FAF, 0xF3BE, 0x71E6, 0xF3BF, 0x74A8, 0xF3C0, 0x74DA, 0xF3C1, 0x7AC4, 0xF3C2, 0x7C12, + 0xF3C3, 0x7E82, 0xF3C4, 0x7CB2, 0xF3C5, 0x7E98, 0xF3C6, 0x8B9A, 0xF3C7, 0x8D0A, 0xF3C8, 0x947D, 0xF3C9, 0x9910, 0xF3CA, 0x994C, + 0xF3CB, 0x5239, 0xF3CC, 0x5BDF, 0xF3CD, 0x64E6, 0xF3CE, 0x672D, 0xF3CF, 0x7D2E, 0xF3D0, 0x50ED, 0xF3D1, 0x53C3, 0xF3D2, 0x5879, + 0xF3D3, 0x6158, 0xF3D4, 0x6159, 0xF3D5, 0x61FA, 0xF3D6, 0x65AC, 0xF3D7, 0x7AD9, 0xF3D8, 0x8B92, 0xF3D9, 0x8B96, 0xF3DA, 0x5009, + 0xF3DB, 0x5021, 0xF3DC, 0x5275, 0xF3DD, 0x5531, 0xF3DE, 0x5A3C, 0xF3DF, 0x5EE0, 0xF3E0, 0x5F70, 0xF3E1, 0x6134, 0xF3E2, 0x655E, + 0xF3E3, 0x660C, 0xF3E4, 0x6636, 0xF3E5, 0x66A2, 0xF3E6, 0x69CD, 0xF3E7, 0x6EC4, 0xF3E8, 0x6F32, 0xF3E9, 0x7316, 0xF3EA, 0x7621, + 0xF3EB, 0x7A93, 0xF3EC, 0x8139, 0xF3ED, 0x8259, 0xF3EE, 0x83D6, 0xF3EF, 0x84BC, 0xF3F0, 0x50B5, 0xF3F1, 0x57F0, 0xF3F2, 0x5BC0, + 0xF3F3, 0x5BE8, 0xF3F4, 0x5F69, 0xF3F5, 0x63A1, 0xF3F6, 0x7826, 0xF3F7, 0x7DB5, 0xF3F8, 0x83DC, 0xF3F9, 0x8521, 0xF3FA, 0x91C7, + 0xF3FB, 0x91F5, 0xF3FC, 0x518A, 0xF3FD, 0x67F5, 0xF3FE, 0x7B56, 0xF4A1, 0x8CAC, 0xF4A2, 0x51C4, 0xF4A3, 0x59BB, 0xF4A4, 0x60BD, + 0xF4A5, 0x8655, 0xF4A6, 0x501C, 0xF4A7, 0xF9FF, 0xF4A8, 0x5254, 0xF4A9, 0x5C3A, 0xF4AA, 0x617D, 0xF4AB, 0x621A, 0xF4AC, 0x62D3, + 0xF4AD, 0x64F2, 0xF4AE, 0x65A5, 0xF4AF, 0x6ECC, 0xF4B0, 0x7620, 0xF4B1, 0x810A, 0xF4B2, 0x8E60, 0xF4B3, 0x965F, 0xF4B4, 0x96BB, + 0xF4B5, 0x4EDF, 0xF4B6, 0x5343, 0xF4B7, 0x5598, 0xF4B8, 0x5929, 0xF4B9, 0x5DDD, 0xF4BA, 0x64C5, 0xF4BB, 0x6CC9, 0xF4BC, 0x6DFA, + 0xF4BD, 0x7394, 0xF4BE, 0x7A7F, 0xF4BF, 0x821B, 0xF4C0, 0x85A6, 0xF4C1, 0x8CE4, 0xF4C2, 0x8E10, 0xF4C3, 0x9077, 0xF4C4, 0x91E7, + 0xF4C5, 0x95E1, 0xF4C6, 0x9621, 0xF4C7, 0x97C6, 0xF4C8, 0x51F8, 0xF4C9, 0x54F2, 0xF4CA, 0x5586, 0xF4CB, 0x5FB9, 0xF4CC, 0x64A4, + 0xF4CD, 0x6F88, 0xF4CE, 0x7DB4, 0xF4CF, 0x8F1F, 0xF4D0, 0x8F4D, 0xF4D1, 0x9435, 0xF4D2, 0x50C9, 0xF4D3, 0x5C16, 0xF4D4, 0x6CBE, + 0xF4D5, 0x6DFB, 0xF4D6, 0x751B, 0xF4D7, 0x77BB, 0xF4D8, 0x7C3D, 0xF4D9, 0x7C64, 0xF4DA, 0x8A79, 0xF4DB, 0x8AC2, 0xF4DC, 0x581E, + 0xF4DD, 0x59BE, 0xF4DE, 0x5E16, 0xF4DF, 0x6377, 0xF4E0, 0x7252, 0xF4E1, 0x758A, 0xF4E2, 0x776B, 0xF4E3, 0x8ADC, 0xF4E4, 0x8CBC, + 0xF4E5, 0x8F12, 0xF4E6, 0x5EF3, 0xF4E7, 0x6674, 0xF4E8, 0x6DF8, 0xF4E9, 0x807D, 0xF4EA, 0x83C1, 0xF4EB, 0x8ACB, 0xF4EC, 0x9751, + 0xF4ED, 0x9BD6, 0xF4EE, 0xFA00, 0xF4EF, 0x5243, 0xF4F0, 0x66FF, 0xF4F1, 0x6D95, 0xF4F2, 0x6EEF, 0xF4F3, 0x7DE0, 0xF4F4, 0x8AE6, + 0xF4F5, 0x902E, 0xF4F6, 0x905E, 0xF4F7, 0x9AD4, 0xF4F8, 0x521D, 0xF4F9, 0x527F, 0xF4FA, 0x54E8, 0xF4FB, 0x6194, 0xF4FC, 0x6284, + 0xF4FD, 0x62DB, 0xF4FE, 0x68A2, 0xF5A1, 0x6912, 0xF5A2, 0x695A, 0xF5A3, 0x6A35, 0xF5A4, 0x7092, 0xF5A5, 0x7126, 0xF5A6, 0x785D, + 0xF5A7, 0x7901, 0xF5A8, 0x790E, 0xF5A9, 0x79D2, 0xF5AA, 0x7A0D, 0xF5AB, 0x8096, 0xF5AC, 0x8278, 0xF5AD, 0x82D5, 0xF5AE, 0x8349, + 0xF5AF, 0x8549, 0xF5B0, 0x8C82, 0xF5B1, 0x8D85, 0xF5B2, 0x9162, 0xF5B3, 0x918B, 0xF5B4, 0x91AE, 0xF5B5, 0x4FC3, 0xF5B6, 0x56D1, + 0xF5B7, 0x71ED, 0xF5B8, 0x77D7, 0xF5B9, 0x8700, 0xF5BA, 0x89F8, 0xF5BB, 0x5BF8, 0xF5BC, 0x5FD6, 0xF5BD, 0x6751, 0xF5BE, 0x90A8, + 0xF5BF, 0x53E2, 0xF5C0, 0x585A, 0xF5C1, 0x5BF5, 0xF5C2, 0x60A4, 0xF5C3, 0x6181, 0xF5C4, 0x6460, 0xF5C5, 0x7E3D, 0xF5C6, 0x8070, + 0xF5C7, 0x8525, 0xF5C8, 0x9283, 0xF5C9, 0x64AE, 0xF5CA, 0x50AC, 0xF5CB, 0x5D14, 0xF5CC, 0x6700, 0xF5CD, 0x589C, 0xF5CE, 0x62BD, + 0xF5CF, 0x63A8, 0xF5D0, 0x690E, 0xF5D1, 0x6978, 0xF5D2, 0x6A1E, 0xF5D3, 0x6E6B, 0xF5D4, 0x76BA, 0xF5D5, 0x79CB, 0xF5D6, 0x82BB, + 0xF5D7, 0x8429, 0xF5D8, 0x8ACF, 0xF5D9, 0x8DA8, 0xF5DA, 0x8FFD, 0xF5DB, 0x9112, 0xF5DC, 0x914B, 0xF5DD, 0x919C, 0xF5DE, 0x9310, + 0xF5DF, 0x9318, 0xF5E0, 0x939A, 0xF5E1, 0x96DB, 0xF5E2, 0x9A36, 0xF5E3, 0x9C0D, 0xF5E4, 0x4E11, 0xF5E5, 0x755C, 0xF5E6, 0x795D, + 0xF5E7, 0x7AFA, 0xF5E8, 0x7B51, 0xF5E9, 0x7BC9, 0xF5EA, 0x7E2E, 0xF5EB, 0x84C4, 0xF5EC, 0x8E59, 0xF5ED, 0x8E74, 0xF5EE, 0x8EF8, + 0xF5EF, 0x9010, 0xF5F0, 0x6625, 0xF5F1, 0x693F, 0xF5F2, 0x7443, 0xF5F3, 0x51FA, 0xF5F4, 0x672E, 0xF5F5, 0x9EDC, 0xF5F6, 0x5145, + 0xF5F7, 0x5FE0, 0xF5F8, 0x6C96, 0xF5F9, 0x87F2, 0xF5FA, 0x885D, 0xF5FB, 0x8877, 0xF5FC, 0x60B4, 0xF5FD, 0x81B5, 0xF5FE, 0x8403, + 0xF6A1, 0x8D05, 0xF6A2, 0x53D6, 0xF6A3, 0x5439, 0xF6A4, 0x5634, 0xF6A5, 0x5A36, 0xF6A6, 0x5C31, 0xF6A7, 0x708A, 0xF6A8, 0x7FE0, + 0xF6A9, 0x805A, 0xF6AA, 0x8106, 0xF6AB, 0x81ED, 0xF6AC, 0x8DA3, 0xF6AD, 0x9189, 0xF6AE, 0x9A5F, 0xF6AF, 0x9DF2, 0xF6B0, 0x5074, + 0xF6B1, 0x4EC4, 0xF6B2, 0x53A0, 0xF6B3, 0x60FB, 0xF6B4, 0x6E2C, 0xF6B5, 0x5C64, 0xF6B6, 0x4F88, 0xF6B7, 0x5024, 0xF6B8, 0x55E4, + 0xF6B9, 0x5CD9, 0xF6BA, 0x5E5F, 0xF6BB, 0x6065, 0xF6BC, 0x6894, 0xF6BD, 0x6CBB, 0xF6BE, 0x6DC4, 0xF6BF, 0x71BE, 0xF6C0, 0x75D4, + 0xF6C1, 0x75F4, 0xF6C2, 0x7661, 0xF6C3, 0x7A1A, 0xF6C4, 0x7A49, 0xF6C5, 0x7DC7, 0xF6C6, 0x7DFB, 0xF6C7, 0x7F6E, 0xF6C8, 0x81F4, + 0xF6C9, 0x86A9, 0xF6CA, 0x8F1C, 0xF6CB, 0x96C9, 0xF6CC, 0x99B3, 0xF6CD, 0x9F52, 0xF6CE, 0x5247, 0xF6CF, 0x52C5, 0xF6D0, 0x98ED, + 0xF6D1, 0x89AA, 0xF6D2, 0x4E03, 0xF6D3, 0x67D2, 0xF6D4, 0x6F06, 0xF6D5, 0x4FB5, 0xF6D6, 0x5BE2, 0xF6D7, 0x6795, 0xF6D8, 0x6C88, + 0xF6D9, 0x6D78, 0xF6DA, 0x741B, 0xF6DB, 0x7827, 0xF6DC, 0x91DD, 0xF6DD, 0x937C, 0xF6DE, 0x87C4, 0xF6DF, 0x79E4, 0xF6E0, 0x7A31, + 0xF6E1, 0x5FEB, 0xF6E2, 0x4ED6, 0xF6E3, 0x54A4, 0xF6E4, 0x553E, 0xF6E5, 0x58AE, 0xF6E6, 0x59A5, 0xF6E7, 0x60F0, 0xF6E8, 0x6253, + 0xF6E9, 0x62D6, 0xF6EA, 0x6736, 0xF6EB, 0x6955, 0xF6EC, 0x8235, 0xF6ED, 0x9640, 0xF6EE, 0x99B1, 0xF6EF, 0x99DD, 0xF6F0, 0x502C, + 0xF6F1, 0x5353, 0xF6F2, 0x5544, 0xF6F3, 0x577C, 0xF6F4, 0xFA01, 0xF6F5, 0x6258, 0xF6F6, 0xFA02, 0xF6F7, 0x64E2, 0xF6F8, 0x666B, + 0xF6F9, 0x67DD, 0xF6FA, 0x6FC1, 0xF6FB, 0x6FEF, 0xF6FC, 0x7422, 0xF6FD, 0x7438, 0xF6FE, 0x8A17, 0xF7A1, 0x9438, 0xF7A2, 0x5451, + 0xF7A3, 0x5606, 0xF7A4, 0x5766, 0xF7A5, 0x5F48, 0xF7A6, 0x619A, 0xF7A7, 0x6B4E, 0xF7A8, 0x7058, 0xF7A9, 0x70AD, 0xF7AA, 0x7DBB, + 0xF7AB, 0x8A95, 0xF7AC, 0x596A, 0xF7AD, 0x812B, 0xF7AE, 0x63A2, 0xF7AF, 0x7708, 0xF7B0, 0x803D, 0xF7B1, 0x8CAA, 0xF7B2, 0x5854, + 0xF7B3, 0x642D, 0xF7B4, 0x69BB, 0xF7B5, 0x5B95, 0xF7B6, 0x5E11, 0xF7B7, 0x6E6F, 0xF7B8, 0xFA03, 0xF7B9, 0x8569, 0xF7BA, 0x514C, + 0xF7BB, 0x53F0, 0xF7BC, 0x592A, 0xF7BD, 0x6020, 0xF7BE, 0x614B, 0xF7BF, 0x6B86, 0xF7C0, 0x6C70, 0xF7C1, 0x6CF0, 0xF7C2, 0x7B1E, + 0xF7C3, 0x80CE, 0xF7C4, 0x82D4, 0xF7C5, 0x8DC6, 0xF7C6, 0x90B0, 0xF7C7, 0x98B1, 0xF7C8, 0xFA04, 0xF7C9, 0x64C7, 0xF7CA, 0x6FA4, + 0xF7CB, 0x6491, 0xF7CC, 0x6504, 0xF7CD, 0x514E, 0xF7CE, 0x5410, 0xF7CF, 0x571F, 0xF7D0, 0x8A0E, 0xF7D1, 0x615F, 0xF7D2, 0x6876, + 0xF7D3, 0xFA05, 0xF7D4, 0x75DB, 0xF7D5, 0x7B52, 0xF7D6, 0x7D71, 0xF7D7, 0x901A, 0xF7D8, 0x5806, 0xF7D9, 0x69CC, 0xF7DA, 0x817F, + 0xF7DB, 0x892A, 0xF7DC, 0x9000, 0xF7DD, 0x9839, 0xF7DE, 0x5078, 0xF7DF, 0x5957, 0xF7E0, 0x59AC, 0xF7E1, 0x6295, 0xF7E2, 0x900F, + 0xF7E3, 0x9B2A, 0xF7E4, 0x615D, 0xF7E5, 0x7279, 0xF7E6, 0x95D6, 0xF7E7, 0x5761, 0xF7E8, 0x5A46, 0xF7E9, 0x5DF4, 0xF7EA, 0x628A, + 0xF7EB, 0x64AD, 0xF7EC, 0x64FA, 0xF7ED, 0x6777, 0xF7EE, 0x6CE2, 0xF7EF, 0x6D3E, 0xF7F0, 0x722C, 0xF7F1, 0x7436, 0xF7F2, 0x7834, + 0xF7F3, 0x7F77, 0xF7F4, 0x82AD, 0xF7F5, 0x8DDB, 0xF7F6, 0x9817, 0xF7F7, 0x5224, 0xF7F8, 0x5742, 0xF7F9, 0x677F, 0xF7FA, 0x7248, + 0xF7FB, 0x74E3, 0xF7FC, 0x8CA9, 0xF7FD, 0x8FA6, 0xF7FE, 0x9211, 0xF8A1, 0x962A, 0xF8A2, 0x516B, 0xF8A3, 0x53ED, 0xF8A4, 0x634C, + 0xF8A5, 0x4F69, 0xF8A6, 0x5504, 0xF8A7, 0x6096, 0xF8A8, 0x6557, 0xF8A9, 0x6C9B, 0xF8AA, 0x6D7F, 0xF8AB, 0x724C, 0xF8AC, 0x72FD, + 0xF8AD, 0x7A17, 0xF8AE, 0x8987, 0xF8AF, 0x8C9D, 0xF8B0, 0x5F6D, 0xF8B1, 0x6F8E, 0xF8B2, 0x70F9, 0xF8B3, 0x81A8, 0xF8B4, 0x610E, + 0xF8B5, 0x4FBF, 0xF8B6, 0x504F, 0xF8B7, 0x6241, 0xF8B8, 0x7247, 0xF8B9, 0x7BC7, 0xF8BA, 0x7DE8, 0xF8BB, 0x7FE9, 0xF8BC, 0x904D, + 0xF8BD, 0x97AD, 0xF8BE, 0x9A19, 0xF8BF, 0x8CB6, 0xF8C0, 0x576A, 0xF8C1, 0x5E73, 0xF8C2, 0x67B0, 0xF8C3, 0x840D, 0xF8C4, 0x8A55, + 0xF8C5, 0x5420, 0xF8C6, 0x5B16, 0xF8C7, 0x5E63, 0xF8C8, 0x5EE2, 0xF8C9, 0x5F0A, 0xF8CA, 0x6583, 0xF8CB, 0x80BA, 0xF8CC, 0x853D, + 0xF8CD, 0x9589, 0xF8CE, 0x965B, 0xF8CF, 0x4F48, 0xF8D0, 0x5305, 0xF8D1, 0x530D, 0xF8D2, 0x530F, 0xF8D3, 0x5486, 0xF8D4, 0x54FA, + 0xF8D5, 0x5703, 0xF8D6, 0x5E03, 0xF8D7, 0x6016, 0xF8D8, 0x629B, 0xF8D9, 0x62B1, 0xF8DA, 0x6355, 0xF8DB, 0xFA06, 0xF8DC, 0x6CE1, + 0xF8DD, 0x6D66, 0xF8DE, 0x75B1, 0xF8DF, 0x7832, 0xF8E0, 0x80DE, 0xF8E1, 0x812F, 0xF8E2, 0x82DE, 0xF8E3, 0x8461, 0xF8E4, 0x84B2, + 0xF8E5, 0x888D, 0xF8E6, 0x8912, 0xF8E7, 0x900B, 0xF8E8, 0x92EA, 0xF8E9, 0x98FD, 0xF8EA, 0x9B91, 0xF8EB, 0x5E45, 0xF8EC, 0x66B4, + 0xF8ED, 0x66DD, 0xF8EE, 0x7011, 0xF8EF, 0x7206, 0xF8F0, 0xFA07, 0xF8F1, 0x4FF5, 0xF8F2, 0x527D, 0xF8F3, 0x5F6A, 0xF8F4, 0x6153, + 0xF8F5, 0x6753, 0xF8F6, 0x6A19, 0xF8F7, 0x6F02, 0xF8F8, 0x74E2, 0xF8F9, 0x7968, 0xF8FA, 0x8868, 0xF8FB, 0x8C79, 0xF8FC, 0x98C7, + 0xF8FD, 0x98C4, 0xF8FE, 0x9A43, 0xF9A1, 0x54C1, 0xF9A2, 0x7A1F, 0xF9A3, 0x6953, 0xF9A4, 0x8AF7, 0xF9A5, 0x8C4A, 0xF9A6, 0x98A8, + 0xF9A7, 0x99AE, 0xF9A8, 0x5F7C, 0xF9A9, 0x62AB, 0xF9AA, 0x75B2, 0xF9AB, 0x76AE, 0xF9AC, 0x88AB, 0xF9AD, 0x907F, 0xF9AE, 0x9642, + 0xF9AF, 0x5339, 0xF9B0, 0x5F3C, 0xF9B1, 0x5FC5, 0xF9B2, 0x6CCC, 0xF9B3, 0x73CC, 0xF9B4, 0x7562, 0xF9B5, 0x758B, 0xF9B6, 0x7B46, + 0xF9B7, 0x82FE, 0xF9B8, 0x999D, 0xF9B9, 0x4E4F, 0xF9BA, 0x903C, 0xF9BB, 0x4E0B, 0xF9BC, 0x4F55, 0xF9BD, 0x53A6, 0xF9BE, 0x590F, + 0xF9BF, 0x5EC8, 0xF9C0, 0x6630, 0xF9C1, 0x6CB3, 0xF9C2, 0x7455, 0xF9C3, 0x8377, 0xF9C4, 0x8766, 0xF9C5, 0x8CC0, 0xF9C6, 0x9050, + 0xF9C7, 0x971E, 0xF9C8, 0x9C15, 0xF9C9, 0x58D1, 0xF9CA, 0x5B78, 0xF9CB, 0x8650, 0xF9CC, 0x8B14, 0xF9CD, 0x9DB4, 0xF9CE, 0x5BD2, + 0xF9CF, 0x6068, 0xF9D0, 0x608D, 0xF9D1, 0x65F1, 0xF9D2, 0x6C57, 0xF9D3, 0x6F22, 0xF9D4, 0x6FA3, 0xF9D5, 0x701A, 0xF9D6, 0x7F55, + 0xF9D7, 0x7FF0, 0xF9D8, 0x9591, 0xF9D9, 0x9592, 0xF9DA, 0x9650, 0xF9DB, 0x97D3, 0xF9DC, 0x5272, 0xF9DD, 0x8F44, 0xF9DE, 0x51FD, + 0xF9DF, 0x542B, 0xF9E0, 0x54B8, 0xF9E1, 0x5563, 0xF9E2, 0x558A, 0xF9E3, 0x6ABB, 0xF9E4, 0x6DB5, 0xF9E5, 0x7DD8, 0xF9E6, 0x8266, + 0xF9E7, 0x929C, 0xF9E8, 0x9677, 0xF9E9, 0x9E79, 0xF9EA, 0x5408, 0xF9EB, 0x54C8, 0xF9EC, 0x76D2, 0xF9ED, 0x86E4, 0xF9EE, 0x95A4, + 0xF9EF, 0x95D4, 0xF9F0, 0x965C, 0xF9F1, 0x4EA2, 0xF9F2, 0x4F09, 0xF9F3, 0x59EE, 0xF9F4, 0x5AE6, 0xF9F5, 0x5DF7, 0xF9F6, 0x6052, + 0xF9F7, 0x6297, 0xF9F8, 0x676D, 0xF9F9, 0x6841, 0xF9FA, 0x6C86, 0xF9FB, 0x6E2F, 0xF9FC, 0x7F38, 0xF9FD, 0x809B, 0xF9FE, 0x822A, + 0xFAA1, 0xFA08, 0xFAA2, 0xFA09, 0xFAA3, 0x9805, 0xFAA4, 0x4EA5, 0xFAA5, 0x5055, 0xFAA6, 0x54B3, 0xFAA7, 0x5793, 0xFAA8, 0x595A, + 0xFAA9, 0x5B69, 0xFAAA, 0x5BB3, 0xFAAB, 0x61C8, 0xFAAC, 0x6977, 0xFAAD, 0x6D77, 0xFAAE, 0x7023, 0xFAAF, 0x87F9, 0xFAB0, 0x89E3, + 0xFAB1, 0x8A72, 0xFAB2, 0x8AE7, 0xFAB3, 0x9082, 0xFAB4, 0x99ED, 0xFAB5, 0x9AB8, 0xFAB6, 0x52BE, 0xFAB7, 0x6838, 0xFAB8, 0x5016, + 0xFAB9, 0x5E78, 0xFABA, 0x674F, 0xFABB, 0x8347, 0xFABC, 0x884C, 0xFABD, 0x4EAB, 0xFABE, 0x5411, 0xFABF, 0x56AE, 0xFAC0, 0x73E6, + 0xFAC1, 0x9115, 0xFAC2, 0x97FF, 0xFAC3, 0x9909, 0xFAC4, 0x9957, 0xFAC5, 0x9999, 0xFAC6, 0x5653, 0xFAC7, 0x589F, 0xFAC8, 0x865B, + 0xFAC9, 0x8A31, 0xFACA, 0x61B2, 0xFACB, 0x6AF6, 0xFACC, 0x737B, 0xFACD, 0x8ED2, 0xFACE, 0x6B47, 0xFACF, 0x96AA, 0xFAD0, 0x9A57, + 0xFAD1, 0x5955, 0xFAD2, 0x7200, 0xFAD3, 0x8D6B, 0xFAD4, 0x9769, 0xFAD5, 0x4FD4, 0xFAD6, 0x5CF4, 0xFAD7, 0x5F26, 0xFAD8, 0x61F8, + 0xFAD9, 0x665B, 0xFADA, 0x6CEB, 0xFADB, 0x70AB, 0xFADC, 0x7384, 0xFADD, 0x73B9, 0xFADE, 0x73FE, 0xFADF, 0x7729, 0xFAE0, 0x774D, + 0xFAE1, 0x7D43, 0xFAE2, 0x7D62, 0xFAE3, 0x7E23, 0xFAE4, 0x8237, 0xFAE5, 0x8852, 0xFAE6, 0xFA0A, 0xFAE7, 0x8CE2, 0xFAE8, 0x9249, + 0xFAE9, 0x986F, 0xFAEA, 0x5B51, 0xFAEB, 0x7A74, 0xFAEC, 0x8840, 0xFAED, 0x9801, 0xFAEE, 0x5ACC, 0xFAEF, 0x4FE0, 0xFAF0, 0x5354, + 0xFAF1, 0x593E, 0xFAF2, 0x5CFD, 0xFAF3, 0x633E, 0xFAF4, 0x6D79, 0xFAF5, 0x72F9, 0xFAF6, 0x8105, 0xFAF7, 0x8107, 0xFAF8, 0x83A2, + 0xFAF9, 0x92CF, 0xFAFA, 0x9830, 0xFAFB, 0x4EA8, 0xFAFC, 0x5144, 0xFAFD, 0x5211, 0xFAFE, 0x578B, 0xFBA1, 0x5F62, 0xFBA2, 0x6CC2, + 0xFBA3, 0x6ECE, 0xFBA4, 0x7005, 0xFBA5, 0x7050, 0xFBA6, 0x70AF, 0xFBA7, 0x7192, 0xFBA8, 0x73E9, 0xFBA9, 0x7469, 0xFBAA, 0x834A, + 0xFBAB, 0x87A2, 0xFBAC, 0x8861, 0xFBAD, 0x9008, 0xFBAE, 0x90A2, 0xFBAF, 0x93A3, 0xFBB0, 0x99A8, 0xFBB1, 0x516E, 0xFBB2, 0x5F57, + 0xFBB3, 0x60E0, 0xFBB4, 0x6167, 0xFBB5, 0x66B3, 0xFBB6, 0x8559, 0xFBB7, 0x8E4A, 0xFBB8, 0x91AF, 0xFBB9, 0x978B, 0xFBBA, 0x4E4E, + 0xFBBB, 0x4E92, 0xFBBC, 0x547C, 0xFBBD, 0x58D5, 0xFBBE, 0x58FA, 0xFBBF, 0x597D, 0xFBC0, 0x5CB5, 0xFBC1, 0x5F27, 0xFBC2, 0x6236, + 0xFBC3, 0x6248, 0xFBC4, 0x660A, 0xFBC5, 0x6667, 0xFBC6, 0x6BEB, 0xFBC7, 0x6D69, 0xFBC8, 0x6DCF, 0xFBC9, 0x6E56, 0xFBCA, 0x6EF8, + 0xFBCB, 0x6F94, 0xFBCC, 0x6FE0, 0xFBCD, 0x6FE9, 0xFBCE, 0x705D, 0xFBCF, 0x72D0, 0xFBD0, 0x7425, 0xFBD1, 0x745A, 0xFBD2, 0x74E0, + 0xFBD3, 0x7693, 0xFBD4, 0x795C, 0xFBD5, 0x7CCA, 0xFBD6, 0x7E1E, 0xFBD7, 0x80E1, 0xFBD8, 0x82A6, 0xFBD9, 0x846B, 0xFBDA, 0x84BF, + 0xFBDB, 0x864E, 0xFBDC, 0x865F, 0xFBDD, 0x8774, 0xFBDE, 0x8B77, 0xFBDF, 0x8C6A, 0xFBE0, 0x93AC, 0xFBE1, 0x9800, 0xFBE2, 0x9865, + 0xFBE3, 0x60D1, 0xFBE4, 0x6216, 0xFBE5, 0x9177, 0xFBE6, 0x5A5A, 0xFBE7, 0x660F, 0xFBE8, 0x6DF7, 0xFBE9, 0x6E3E, 0xFBEA, 0x743F, + 0xFBEB, 0x9B42, 0xFBEC, 0x5FFD, 0xFBED, 0x60DA, 0xFBEE, 0x7B0F, 0xFBEF, 0x54C4, 0xFBF0, 0x5F18, 0xFBF1, 0x6C5E, 0xFBF2, 0x6CD3, + 0xFBF3, 0x6D2A, 0xFBF4, 0x70D8, 0xFBF5, 0x7D05, 0xFBF6, 0x8679, 0xFBF7, 0x8A0C, 0xFBF8, 0x9D3B, 0xFBF9, 0x5316, 0xFBFA, 0x548C, + 0xFBFB, 0x5B05, 0xFBFC, 0x6A3A, 0xFBFD, 0x706B, 0xFBFE, 0x7575, 0xFCA1, 0x798D, 0xFCA2, 0x79BE, 0xFCA3, 0x82B1, 0xFCA4, 0x83EF, + 0xFCA5, 0x8A71, 0xFCA6, 0x8B41, 0xFCA7, 0x8CA8, 0xFCA8, 0x9774, 0xFCA9, 0xFA0B, 0xFCAA, 0x64F4, 0xFCAB, 0x652B, 0xFCAC, 0x78BA, + 0xFCAD, 0x78BB, 0xFCAE, 0x7A6B, 0xFCAF, 0x4E38, 0xFCB0, 0x559A, 0xFCB1, 0x5950, 0xFCB2, 0x5BA6, 0xFCB3, 0x5E7B, 0xFCB4, 0x60A3, + 0xFCB5, 0x63DB, 0xFCB6, 0x6B61, 0xFCB7, 0x6665, 0xFCB8, 0x6853, 0xFCB9, 0x6E19, 0xFCBA, 0x7165, 0xFCBB, 0x74B0, 0xFCBC, 0x7D08, + 0xFCBD, 0x9084, 0xFCBE, 0x9A69, 0xFCBF, 0x9C25, 0xFCC0, 0x6D3B, 0xFCC1, 0x6ED1, 0xFCC2, 0x733E, 0xFCC3, 0x8C41, 0xFCC4, 0x95CA, + 0xFCC5, 0x51F0, 0xFCC6, 0x5E4C, 0xFCC7, 0x5FA8, 0xFCC8, 0x604D, 0xFCC9, 0x60F6, 0xFCCA, 0x6130, 0xFCCB, 0x614C, 0xFCCC, 0x6643, + 0xFCCD, 0x6644, 0xFCCE, 0x69A5, 0xFCCF, 0x6CC1, 0xFCD0, 0x6E5F, 0xFCD1, 0x6EC9, 0xFCD2, 0x6F62, 0xFCD3, 0x714C, 0xFCD4, 0x749C, + 0xFCD5, 0x7687, 0xFCD6, 0x7BC1, 0xFCD7, 0x7C27, 0xFCD8, 0x8352, 0xFCD9, 0x8757, 0xFCDA, 0x9051, 0xFCDB, 0x968D, 0xFCDC, 0x9EC3, + 0xFCDD, 0x532F, 0xFCDE, 0x56DE, 0xFCDF, 0x5EFB, 0xFCE0, 0x5F8A, 0xFCE1, 0x6062, 0xFCE2, 0x6094, 0xFCE3, 0x61F7, 0xFCE4, 0x6666, + 0xFCE5, 0x6703, 0xFCE6, 0x6A9C, 0xFCE7, 0x6DEE, 0xFCE8, 0x6FAE, 0xFCE9, 0x7070, 0xFCEA, 0x736A, 0xFCEB, 0x7E6A, 0xFCEC, 0x81BE, + 0xFCED, 0x8334, 0xFCEE, 0x86D4, 0xFCEF, 0x8AA8, 0xFCF0, 0x8CC4, 0xFCF1, 0x5283, 0xFCF2, 0x7372, 0xFCF3, 0x5B96, 0xFCF4, 0x6A6B, + 0xFCF5, 0x9404, 0xFCF6, 0x54EE, 0xFCF7, 0x5686, 0xFCF8, 0x5B5D, 0xFCF9, 0x6548, 0xFCFA, 0x6585, 0xFCFB, 0x66C9, 0xFCFC, 0x689F, + 0xFCFD, 0x6D8D, 0xFCFE, 0x6DC6, 0xFDA1, 0x723B, 0xFDA2, 0x80B4, 0xFDA3, 0x9175, 0xFDA4, 0x9A4D, 0xFDA5, 0x4FAF, 0xFDA6, 0x5019, + 0xFDA7, 0x539A, 0xFDA8, 0x540E, 0xFDA9, 0x543C, 0xFDAA, 0x5589, 0xFDAB, 0x55C5, 0xFDAC, 0x5E3F, 0xFDAD, 0x5F8C, 0xFDAE, 0x673D, + 0xFDAF, 0x7166, 0xFDB0, 0x73DD, 0xFDB1, 0x9005, 0xFDB2, 0x52DB, 0xFDB3, 0x52F3, 0xFDB4, 0x5864, 0xFDB5, 0x58CE, 0xFDB6, 0x7104, + 0xFDB7, 0x718F, 0xFDB8, 0x71FB, 0xFDB9, 0x85B0, 0xFDBA, 0x8A13, 0xFDBB, 0x6688, 0xFDBC, 0x85A8, 0xFDBD, 0x55A7, 0xFDBE, 0x6684, + 0xFDBF, 0x714A, 0xFDC0, 0x8431, 0xFDC1, 0x5349, 0xFDC2, 0x5599, 0xFDC3, 0x6BC1, 0xFDC4, 0x5F59, 0xFDC5, 0x5FBD, 0xFDC6, 0x63EE, + 0xFDC7, 0x6689, 0xFDC8, 0x7147, 0xFDC9, 0x8AF1, 0xFDCA, 0x8F1D, 0xFDCB, 0x9EBE, 0xFDCC, 0x4F11, 0xFDCD, 0x643A, 0xFDCE, 0x70CB, + 0xFDCF, 0x7566, 0xFDD0, 0x8667, 0xFDD1, 0x6064, 0xFDD2, 0x8B4E, 0xFDD3, 0x9DF8, 0xFDD4, 0x5147, 0xFDD5, 0x51F6, 0xFDD6, 0x5308, + 0xFDD7, 0x6D36, 0xFDD8, 0x80F8, 0xFDD9, 0x9ED1, 0xFDDA, 0x6615, 0xFDDB, 0x6B23, 0xFDDC, 0x7098, 0xFDDD, 0x75D5, 0xFDDE, 0x5403, + 0xFDDF, 0x5C79, 0xFDE0, 0x7D07, 0xFDE1, 0x8A16, 0xFDE2, 0x6B20, 0xFDE3, 0x6B3D, 0xFDE4, 0x6B46, 0xFDE5, 0x5438, 0xFDE6, 0x6070, + 0xFDE7, 0x6D3D, 0xFDE8, 0x7FD5, 0xFDE9, 0x8208, 0xFDEA, 0x50D6, 0xFDEB, 0x51DE, 0xFDEC, 0x559C, 0xFDED, 0x566B, 0xFDEE, 0x56CD, + 0xFDEF, 0x59EC, 0xFDF0, 0x5B09, 0xFDF1, 0x5E0C, 0xFDF2, 0x6199, 0xFDF3, 0x6198, 0xFDF4, 0x6231, 0xFDF5, 0x665E, 0xFDF6, 0x66E6, + 0xFDF7, 0x7199, 0xFDF8, 0x71B9, 0xFDF9, 0x71BA, 0xFDFA, 0x72A7, 0xFDFB, 0x79A7, 0xFDFC, 0x7A00, 0xFDFD, 0x7FB2, 0xFDFE, 0x8A70, + 0, 0 +}; +#endif + +#if FF_CODE_PAGE == 950 || FF_CODE_PAGE == 0 /* Traditional Chinese */ +static const WCHAR uni2oem950[] = { /* Unicode --> Big5 pairs */ + 0x00A7, 0xA1B1, 0x00AF, 0xA1C2, 0x00B0, 0xA258, 0x00B1, 0xA1D3, 0x00B7, 0xA150, 0x00D7, 0xA1D1, 0x00F7, 0xA1D2, 0x02C7, 0xA3BE, + 0x02C9, 0xA3BC, 0x02CA, 0xA3BD, 0x02CB, 0xA3BF, 0x02CD, 0xA1C5, 0x02D9, 0xA3BB, 0x0391, 0xA344, 0x0392, 0xA345, 0x0393, 0xA346, + 0x0394, 0xA347, 0x0395, 0xA348, 0x0396, 0xA349, 0x0397, 0xA34A, 0x0398, 0xA34B, 0x0399, 0xA34C, 0x039A, 0xA34D, 0x039B, 0xA34E, + 0x039C, 0xA34F, 0x039D, 0xA350, 0x039E, 0xA351, 0x039F, 0xA352, 0x03A0, 0xA353, 0x03A1, 0xA354, 0x03A3, 0xA355, 0x03A4, 0xA356, + 0x03A5, 0xA357, 0x03A6, 0xA358, 0x03A7, 0xA359, 0x03A8, 0xA35A, 0x03A9, 0xA35B, 0x03B1, 0xA35C, 0x03B2, 0xA35D, 0x03B3, 0xA35E, + 0x03B4, 0xA35F, 0x03B5, 0xA360, 0x03B6, 0xA361, 0x03B7, 0xA362, 0x03B8, 0xA363, 0x03B9, 0xA364, 0x03BA, 0xA365, 0x03BB, 0xA366, + 0x03BC, 0xA367, 0x03BD, 0xA368, 0x03BE, 0xA369, 0x03BF, 0xA36A, 0x03C0, 0xA36B, 0x03C1, 0xA36C, 0x03C3, 0xA36D, 0x03C4, 0xA36E, + 0x03C5, 0xA36F, 0x03C6, 0xA370, 0x03C7, 0xA371, 0x03C8, 0xA372, 0x03C9, 0xA373, 0x2013, 0xA156, 0x2014, 0xA158, 0x2018, 0xA1A5, + 0x2019, 0xA1A6, 0x201C, 0xA1A7, 0x201D, 0xA1A8, 0x2025, 0xA14C, 0x2026, 0xA14B, 0x2027, 0xA145, 0x2032, 0xA1AC, 0x2035, 0xA1AB, + 0x203B, 0xA1B0, 0x20AC, 0xA3E1, 0x2103, 0xA24A, 0x2105, 0xA1C1, 0x2109, 0xA24B, 0x2160, 0xA2B9, 0x2161, 0xA2BA, 0x2162, 0xA2BB, + 0x2163, 0xA2BC, 0x2164, 0xA2BD, 0x2165, 0xA2BE, 0x2166, 0xA2BF, 0x2167, 0xA2C0, 0x2168, 0xA2C1, 0x2169, 0xA2C2, 0x2190, 0xA1F6, + 0x2191, 0xA1F4, 0x2192, 0xA1F7, 0x2193, 0xA1F5, 0x2196, 0xA1F8, 0x2197, 0xA1F9, 0x2198, 0xA1FB, 0x2199, 0xA1FA, 0x2215, 0xA241, + 0x221A, 0xA1D4, 0x221E, 0xA1DB, 0x221F, 0xA1E8, 0x2220, 0xA1E7, 0x2223, 0xA1FD, 0x2225, 0xA1FC, 0x2229, 0xA1E4, 0x222A, 0xA1E5, + 0x222B, 0xA1EC, 0x222E, 0xA1ED, 0x2234, 0xA1EF, 0x2235, 0xA1EE, 0x2252, 0xA1DC, 0x2260, 0xA1DA, 0x2261, 0xA1DD, 0x2266, 0xA1D8, + 0x2267, 0xA1D9, 0x2295, 0xA1F2, 0x2299, 0xA1F3, 0x22A5, 0xA1E6, 0x22BF, 0xA1E9, 0x2500, 0xA277, 0x2502, 0xA278, 0x250C, 0xA27A, + 0x2510, 0xA27B, 0x2514, 0xA27C, 0x2518, 0xA27D, 0x251C, 0xA275, 0x2524, 0xA274, 0x252C, 0xA273, 0x2534, 0xA272, 0x253C, 0xA271, + 0x2550, 0xA2A4, 0x2550, 0xF9F9, 0x2551, 0xF9F8, 0x2552, 0xF9E6, 0x2553, 0xF9EF, 0x2554, 0xF9DD, 0x2555, 0xF9E8, 0x2556, 0xF9F1, + 0x2557, 0xF9DF, 0x2558, 0xF9EC, 0x2559, 0xF9F5, 0x255A, 0xF9E3, 0x255B, 0xF9EE, 0x255C, 0xF9F7, 0x255D, 0xF9E5, 0x255E, 0xA2A5, + 0x255E, 0xF9E9, 0x255F, 0xF9F2, 0x2560, 0xF9E0, 0x2561, 0xA2A7, 0x2561, 0xF9EB, 0x2562, 0xF9F4, 0x2563, 0xF9E2, 0x2564, 0xF9E7, + 0x2565, 0xF9F0, 0x2566, 0xF9DE, 0x2567, 0xF9ED, 0x2568, 0xF9F6, 0x2569, 0xF9E4, 0x256A, 0xA2A6, 0x256A, 0xF9EA, 0x256B, 0xF9F3, + 0x256C, 0xF9E1, 0x256D, 0xA27E, 0x256D, 0xF9FA, 0x256E, 0xA2A1, 0x256E, 0xF9FB, 0x256F, 0xA2A3, 0x256F, 0xF9FD, 0x2570, 0xA2A2, + 0x2570, 0xF9FC, 0x2571, 0xA2AC, 0x2572, 0xA2AD, 0x2573, 0xA2AE, 0x2574, 0xA15A, 0x2581, 0xA262, 0x2582, 0xA263, 0x2583, 0xA264, + 0x2584, 0xA265, 0x2585, 0xA266, 0x2586, 0xA267, 0x2587, 0xA268, 0x2588, 0xA269, 0x2589, 0xA270, 0x258A, 0xA26F, 0x258B, 0xA26E, + 0x258C, 0xA26D, 0x258D, 0xA26C, 0x258E, 0xA26B, 0x258F, 0xA26A, 0x2593, 0xF9FE, 0x2594, 0xA276, 0x2595, 0xA279, 0x25A0, 0xA1BD, + 0x25A1, 0xA1BC, 0x25B2, 0xA1B6, 0x25B3, 0xA1B5, 0x25BC, 0xA1BF, 0x25BD, 0xA1BE, 0x25C6, 0xA1BB, 0x25C7, 0xA1BA, 0x25CB, 0xA1B3, + 0x25CE, 0xA1B7, 0x25CF, 0xA1B4, 0x25E2, 0xA2A8, 0x25E3, 0xA2A9, 0x25E4, 0xA2AB, 0x25E5, 0xA2AA, 0x2605, 0xA1B9, 0x2606, 0xA1B8, + 0x2640, 0xA1F0, 0x2642, 0xA1F1, 0x3000, 0xA140, 0x3001, 0xA142, 0x3002, 0xA143, 0x3003, 0xA1B2, 0x3008, 0xA171, 0x3009, 0xA172, + 0x300A, 0xA16D, 0x300B, 0xA16E, 0x300C, 0xA175, 0x300D, 0xA176, 0x300E, 0xA179, 0x300F, 0xA17A, 0x3010, 0xA169, 0x3011, 0xA16A, + 0x3012, 0xA245, 0x3014, 0xA165, 0x3015, 0xA166, 0x301D, 0xA1A9, 0x301E, 0xA1AA, 0x3021, 0xA2C3, 0x3022, 0xA2C4, 0x3023, 0xA2C5, + 0x3024, 0xA2C6, 0x3025, 0xA2C7, 0x3026, 0xA2C8, 0x3027, 0xA2C9, 0x3028, 0xA2CA, 0x3029, 0xA2CB, 0x3105, 0xA374, 0x3106, 0xA375, + 0x3107, 0xA376, 0x3108, 0xA377, 0x3109, 0xA378, 0x310A, 0xA379, 0x310B, 0xA37A, 0x310C, 0xA37B, 0x310D, 0xA37C, 0x310E, 0xA37D, + 0x310F, 0xA37E, 0x3110, 0xA3A1, 0x3111, 0xA3A2, 0x3112, 0xA3A3, 0x3113, 0xA3A4, 0x3114, 0xA3A5, 0x3115, 0xA3A6, 0x3116, 0xA3A7, + 0x3117, 0xA3A8, 0x3118, 0xA3A9, 0x3119, 0xA3AA, 0x311A, 0xA3AB, 0x311B, 0xA3AC, 0x311C, 0xA3AD, 0x311D, 0xA3AE, 0x311E, 0xA3AF, + 0x311F, 0xA3B0, 0x3120, 0xA3B1, 0x3121, 0xA3B2, 0x3122, 0xA3B3, 0x3123, 0xA3B4, 0x3124, 0xA3B5, 0x3125, 0xA3B6, 0x3126, 0xA3B7, + 0x3127, 0xA3B8, 0x3128, 0xA3B9, 0x3129, 0xA3BA, 0x32A3, 0xA1C0, 0x338E, 0xA255, 0x338F, 0xA256, 0x339C, 0xA250, 0x339D, 0xA251, + 0x339E, 0xA252, 0x33A1, 0xA254, 0x33C4, 0xA257, 0x33CE, 0xA253, 0x33D1, 0xA1EB, 0x33D2, 0xA1EA, 0x33D5, 0xA24F, 0x4E00, 0xA440, + 0x4E01, 0xA442, 0x4E03, 0xA443, 0x4E07, 0xC945, 0x4E08, 0xA456, 0x4E09, 0xA454, 0x4E0A, 0xA457, 0x4E0B, 0xA455, 0x4E0C, 0xC946, + 0x4E0D, 0xA4A3, 0x4E0E, 0xC94F, 0x4E0F, 0xC94D, 0x4E10, 0xA4A2, 0x4E11, 0xA4A1, 0x4E14, 0xA542, 0x4E15, 0xA541, 0x4E16, 0xA540, + 0x4E18, 0xA543, 0x4E19, 0xA4FE, 0x4E1E, 0xA5E0, 0x4E1F, 0xA5E1, 0x4E26, 0xA8C3, 0x4E2B, 0xA458, 0x4E2D, 0xA4A4, 0x4E2E, 0xC950, + 0x4E30, 0xA4A5, 0x4E31, 0xC963, 0x4E32, 0xA6EA, 0x4E33, 0xCBB1, 0x4E38, 0xA459, 0x4E39, 0xA4A6, 0x4E3B, 0xA544, 0x4E3C, 0xC964, + 0x4E42, 0xC940, 0x4E43, 0xA444, 0x4E45, 0xA45B, 0x4E47, 0xC947, 0x4E48, 0xA45C, 0x4E4B, 0xA4A7, 0x4E4D, 0xA545, 0x4E4E, 0xA547, + 0x4E4F, 0xA546, 0x4E52, 0xA5E2, 0x4E53, 0xA5E3, 0x4E56, 0xA8C4, 0x4E58, 0xADBC, 0x4E59, 0xA441, 0x4E5C, 0xC941, 0x4E5D, 0xA445, + 0x4E5E, 0xA45E, 0x4E5F, 0xA45D, 0x4E69, 0xA5E4, 0x4E73, 0xA8C5, 0x4E7E, 0xB0AE, 0x4E7F, 0xD44B, 0x4E82, 0xB6C3, 0x4E83, 0xDCB1, + 0x4E84, 0xDCB2, 0x4E86, 0xA446, 0x4E88, 0xA4A9, 0x4E8B, 0xA8C6, 0x4E8C, 0xA447, 0x4E8D, 0xC948, 0x4E8E, 0xA45F, 0x4E91, 0xA4AA, + 0x4E92, 0xA4AC, 0x4E93, 0xC951, 0x4E94, 0xA4AD, 0x4E95, 0xA4AB, 0x4E99, 0xA5E5, 0x4E9B, 0xA8C7, 0x4E9E, 0xA8C8, 0x4E9F, 0xAB45, + 0x4EA1, 0xA460, 0x4EA2, 0xA4AE, 0x4EA4, 0xA5E6, 0x4EA5, 0xA5E8, 0x4EA6, 0xA5E7, 0x4EA8, 0xA6EB, 0x4EAB, 0xA8C9, 0x4EAC, 0xA8CA, + 0x4EAD, 0xAB46, 0x4EAE, 0xAB47, 0x4EB3, 0xADBD, 0x4EB6, 0xDCB3, 0x4EB9, 0xF6D6, 0x4EBA, 0xA448, 0x4EC0, 0xA4B0, 0x4EC1, 0xA4AF, + 0x4EC2, 0xC952, 0x4EC3, 0xA4B1, 0x4EC4, 0xA4B7, 0x4EC6, 0xA4B2, 0x4EC7, 0xA4B3, 0x4EC8, 0xC954, 0x4EC9, 0xC953, 0x4ECA, 0xA4B5, + 0x4ECB, 0xA4B6, 0x4ECD, 0xA4B4, 0x4ED4, 0xA54A, 0x4ED5, 0xA54B, 0x4ED6, 0xA54C, 0x4ED7, 0xA54D, 0x4ED8, 0xA549, 0x4ED9, 0xA550, + 0x4EDA, 0xC96A, 0x4EDC, 0xC966, 0x4EDD, 0xC969, 0x4EDE, 0xA551, 0x4EDF, 0xA561, 0x4EE1, 0xC968, 0x4EE3, 0xA54E, 0x4EE4, 0xA54F, + 0x4EE5, 0xA548, 0x4EE8, 0xC965, 0x4EE9, 0xC967, 0x4EF0, 0xA5F5, 0x4EF1, 0xC9B0, 0x4EF2, 0xA5F2, 0x4EF3, 0xA5F6, 0x4EF4, 0xC9BA, + 0x4EF5, 0xC9AE, 0x4EF6, 0xA5F3, 0x4EF7, 0xC9B2, 0x4EFB, 0xA5F4, 0x4EFD, 0xA5F7, 0x4EFF, 0xA5E9, 0x4F00, 0xC9B1, 0x4F01, 0xA5F8, + 0x4F02, 0xC9B5, 0x4F04, 0xC9B9, 0x4F05, 0xC9B6, 0x4F08, 0xC9B3, 0x4F09, 0xA5EA, 0x4F0A, 0xA5EC, 0x4F0B, 0xA5F9, 0x4F0D, 0xA5EE, + 0x4F0E, 0xC9AB, 0x4F0F, 0xA5F1, 0x4F10, 0xA5EF, 0x4F11, 0xA5F0, 0x4F12, 0xC9BB, 0x4F13, 0xC9B8, 0x4F14, 0xC9AF, 0x4F15, 0xA5ED, + 0x4F18, 0xC9AC, 0x4F19, 0xA5EB, 0x4F1D, 0xC9B4, 0x4F22, 0xC9B7, 0x4F2C, 0xC9AD, 0x4F2D, 0xCA66, 0x4F2F, 0xA742, 0x4F30, 0xA6F4, + 0x4F33, 0xCA67, 0x4F34, 0xA6F1, 0x4F36, 0xA744, 0x4F38, 0xA6F9, 0x4F3A, 0xA6F8, 0x4F3B, 0xCA5B, 0x4F3C, 0xA6FC, 0x4F3D, 0xA6F7, + 0x4F3E, 0xCA60, 0x4F3F, 0xCA68, 0x4F41, 0xCA64, 0x4F43, 0xA6FA, 0x4F46, 0xA6FD, 0x4F47, 0xA6EE, 0x4F48, 0xA747, 0x4F49, 0xCA5D, + 0x4F4C, 0xCBBD, 0x4F4D, 0xA6EC, 0x4F4E, 0xA743, 0x4F4F, 0xA6ED, 0x4F50, 0xA6F5, 0x4F51, 0xA6F6, 0x4F52, 0xCA62, 0x4F53, 0xCA5E, + 0x4F54, 0xA6FB, 0x4F55, 0xA6F3, 0x4F56, 0xCA5A, 0x4F57, 0xA6EF, 0x4F58, 0xCA65, 0x4F59, 0xA745, 0x4F5A, 0xA748, 0x4F5B, 0xA6F2, + 0x4F5C, 0xA740, 0x4F5D, 0xA746, 0x4F5E, 0xA6F0, 0x4F5F, 0xCA63, 0x4F60, 0xA741, 0x4F61, 0xCA69, 0x4F62, 0xCA5C, 0x4F63, 0xA6FE, + 0x4F64, 0xCA5F, 0x4F67, 0xCA61, 0x4F69, 0xA8D8, 0x4F6A, 0xCBBF, 0x4F6B, 0xCBCB, 0x4F6C, 0xA8D0, 0x4F6E, 0xCBCC, 0x4F6F, 0xA8CB, + 0x4F70, 0xA8D5, 0x4F73, 0xA8CE, 0x4F74, 0xCBB9, 0x4F75, 0xA8D6, 0x4F76, 0xCBB8, 0x4F77, 0xCBBC, 0x4F78, 0xCBC3, 0x4F79, 0xCBC1, + 0x4F7A, 0xA8DE, 0x4F7B, 0xA8D9, 0x4F7C, 0xCBB3, 0x4F7D, 0xCBB5, 0x4F7E, 0xA8DB, 0x4F7F, 0xA8CF, 0x4F80, 0xCBB6, 0x4F81, 0xCBC2, + 0x4F82, 0xCBC9, 0x4F83, 0xA8D4, 0x4F84, 0xCBBB, 0x4F85, 0xCBB4, 0x4F86, 0xA8D3, 0x4F87, 0xCBB7, 0x4F88, 0xA8D7, 0x4F89, 0xCBBA, + 0x4F8B, 0xA8D2, 0x4F8D, 0xA8CD, 0x4F8F, 0xA8DC, 0x4F90, 0xCBC4, 0x4F91, 0xA8DD, 0x4F92, 0xCBC8, 0x4F94, 0xCBC6, 0x4F95, 0xCBCA, + 0x4F96, 0xA8DA, 0x4F97, 0xCBBE, 0x4F98, 0xCBB2, 0x4F9A, 0xCBC0, 0x4F9B, 0xA8D1, 0x4F9C, 0xCBC5, 0x4F9D, 0xA8CC, 0x4F9E, 0xCBC7, + 0x4FAE, 0xAB56, 0x4FAF, 0xAB4A, 0x4FB2, 0xCDE0, 0x4FB3, 0xCDE8, 0x4FB5, 0xAB49, 0x4FB6, 0xAB51, 0x4FB7, 0xAB5D, 0x4FB9, 0xCDEE, + 0x4FBA, 0xCDEC, 0x4FBB, 0xCDE7, 0x4FBF, 0xAB4B, 0x4FC0, 0xCDED, 0x4FC1, 0xCDE3, 0x4FC2, 0xAB59, 0x4FC3, 0xAB50, 0x4FC4, 0xAB58, + 0x4FC5, 0xCDDE, 0x4FC7, 0xCDEA, 0x4FC9, 0xCDE1, 0x4FCA, 0xAB54, 0x4FCB, 0xCDE2, 0x4FCD, 0xCDDD, 0x4FCE, 0xAB5B, 0x4FCF, 0xAB4E, + 0x4FD0, 0xAB57, 0x4FD1, 0xAB4D, 0x4FD3, 0xCDDF, 0x4FD4, 0xCDE4, 0x4FD6, 0xCDEB, 0x4FD7, 0xAB55, 0x4FD8, 0xAB52, 0x4FD9, 0xCDE6, + 0x4FDA, 0xAB5A, 0x4FDB, 0xCDE9, 0x4FDC, 0xCDE5, 0x4FDD, 0xAB4F, 0x4FDE, 0xAB5C, 0x4FDF, 0xAB53, 0x4FE0, 0xAB4C, 0x4FE1, 0xAB48, + 0x4FEC, 0xCDEF, 0x4FEE, 0xADD7, 0x4FEF, 0xADC1, 0x4FF1, 0xADD1, 0x4FF3, 0xADD6, 0x4FF4, 0xD0D0, 0x4FF5, 0xD0CF, 0x4FF6, 0xD0D4, + 0x4FF7, 0xD0D5, 0x4FF8, 0xADC4, 0x4FFA, 0xADCD, 0x4FFE, 0xADDA, 0x5000, 0xADCE, 0x5005, 0xD0C9, 0x5006, 0xADC7, 0x5007, 0xD0CA, + 0x5009, 0xADDC, 0x500B, 0xADD3, 0x500C, 0xADBE, 0x500D, 0xADBF, 0x500E, 0xD0DD, 0x500F, 0xB0BF, 0x5011, 0xADCC, 0x5012, 0xADCB, + 0x5013, 0xD0CB, 0x5014, 0xADCF, 0x5015, 0xD45B, 0x5016, 0xADC6, 0x5017, 0xD0D6, 0x5018, 0xADD5, 0x5019, 0xADD4, 0x501A, 0xADCA, + 0x501B, 0xD0CE, 0x501C, 0xD0D7, 0x501E, 0xD0C8, 0x501F, 0xADC9, 0x5020, 0xD0D8, 0x5021, 0xADD2, 0x5022, 0xD0CC, 0x5023, 0xADC0, + 0x5025, 0xADC3, 0x5026, 0xADC2, 0x5027, 0xD0D9, 0x5028, 0xADD0, 0x5029, 0xADC5, 0x502A, 0xADD9, 0x502B, 0xADDB, 0x502C, 0xD0D3, + 0x502D, 0xADD8, 0x502F, 0xD0DB, 0x5030, 0xD0CD, 0x5031, 0xD0DC, 0x5033, 0xD0D1, 0x5035, 0xD0DA, 0x5037, 0xD0D2, 0x503C, 0xADC8, + 0x5040, 0xD463, 0x5041, 0xD457, 0x5043, 0xB0B3, 0x5045, 0xD45C, 0x5046, 0xD462, 0x5047, 0xB0B2, 0x5048, 0xD455, 0x5049, 0xB0B6, + 0x504A, 0xD459, 0x504B, 0xD452, 0x504C, 0xB0B4, 0x504D, 0xD456, 0x504E, 0xB0B9, 0x504F, 0xB0BE, 0x5051, 0xD467, 0x5053, 0xD451, + 0x5055, 0xB0BA, 0x5057, 0xD466, 0x505A, 0xB0B5, 0x505B, 0xD458, 0x505C, 0xB0B1, 0x505D, 0xD453, 0x505E, 0xD44F, 0x505F, 0xD45D, + 0x5060, 0xD450, 0x5061, 0xD44E, 0x5062, 0xD45A, 0x5063, 0xD460, 0x5064, 0xD461, 0x5065, 0xB0B7, 0x5068, 0xD85B, 0x5069, 0xD45E, + 0x506A, 0xD44D, 0x506B, 0xD45F, 0x506D, 0xB0C1, 0x506E, 0xD464, 0x506F, 0xB0C0, 0x5070, 0xD44C, 0x5072, 0xD454, 0x5073, 0xD465, + 0x5074, 0xB0BC, 0x5075, 0xB0BB, 0x5076, 0xB0B8, 0x5077, 0xB0BD, 0x507A, 0xB0AF, 0x507D, 0xB0B0, 0x5080, 0xB3C8, 0x5082, 0xD85E, + 0x5083, 0xD857, 0x5085, 0xB3C5, 0x5087, 0xD85F, 0x508B, 0xD855, 0x508C, 0xD858, 0x508D, 0xB3C4, 0x508E, 0xD859, 0x5091, 0xB3C7, + 0x5092, 0xD85D, 0x5094, 0xD853, 0x5095, 0xD852, 0x5096, 0xB3C9, 0x5098, 0xB3CA, 0x5099, 0xB3C6, 0x509A, 0xB3CB, 0x509B, 0xD851, + 0x509C, 0xD85C, 0x509D, 0xD85A, 0x509E, 0xD854, 0x50A2, 0xB3C3, 0x50A3, 0xD856, 0x50AC, 0xB6CA, 0x50AD, 0xB6C4, 0x50AE, 0xDCB7, + 0x50AF, 0xB6CD, 0x50B0, 0xDCBD, 0x50B1, 0xDCC0, 0x50B2, 0xB6C6, 0x50B3, 0xB6C7, 0x50B4, 0xDCBA, 0x50B5, 0xB6C5, 0x50B6, 0xDCC3, + 0x50B7, 0xB6CB, 0x50B8, 0xDCC4, 0x50BA, 0xDCBF, 0x50BB, 0xB6CC, 0x50BD, 0xDCB4, 0x50BE, 0xB6C9, 0x50BF, 0xDCB5, 0x50C1, 0xDCBE, + 0x50C2, 0xDCBC, 0x50C4, 0xDCB8, 0x50C5, 0xB6C8, 0x50C6, 0xDCB6, 0x50C7, 0xB6CE, 0x50C8, 0xDCBB, 0x50C9, 0xDCC2, 0x50CA, 0xDCB9, + 0x50CB, 0xDCC1, 0x50CE, 0xB9B6, 0x50CF, 0xB9B3, 0x50D1, 0xB9B4, 0x50D3, 0xE0F9, 0x50D4, 0xE0F1, 0x50D5, 0xB9B2, 0x50D6, 0xB9AF, + 0x50D7, 0xE0F2, 0x50DA, 0xB9B1, 0x50DB, 0xE0F5, 0x50DD, 0xE0F7, 0x50E0, 0xE0FE, 0x50E3, 0xE0FD, 0x50E4, 0xE0F8, 0x50E5, 0xB9AE, + 0x50E6, 0xE0F0, 0x50E7, 0xB9AC, 0x50E8, 0xE0F3, 0x50E9, 0xB9B7, 0x50EA, 0xE0F6, 0x50EC, 0xE0FA, 0x50ED, 0xB9B0, 0x50EE, 0xB9AD, + 0x50EF, 0xE0FC, 0x50F0, 0xE0FB, 0x50F1, 0xB9B5, 0x50F3, 0xE0F4, 0x50F5, 0xBBF8, 0x50F6, 0xE4EC, 0x50F8, 0xE4E9, 0x50F9, 0xBBF9, + 0x50FB, 0xBBF7, 0x50FD, 0xE4F0, 0x50FE, 0xE4ED, 0x50FF, 0xE4E6, 0x5100, 0xBBF6, 0x5102, 0xBBFA, 0x5103, 0xE4E7, 0x5104, 0xBBF5, + 0x5105, 0xBBFD, 0x5106, 0xE4EA, 0x5107, 0xE4EB, 0x5108, 0xBBFB, 0x5109, 0xBBFC, 0x510A, 0xE4F1, 0x510B, 0xE4EE, 0x510C, 0xE4EF, + 0x5110, 0xBEAA, 0x5111, 0xE8F8, 0x5112, 0xBEA7, 0x5113, 0xE8F5, 0x5114, 0xBEA9, 0x5115, 0xBEAB, 0x5117, 0xE8F6, 0x5118, 0xBEA8, + 0x511A, 0xE8F7, 0x511C, 0xE8F4, 0x511F, 0xC076, 0x5120, 0xECBD, 0x5121, 0xC077, 0x5122, 0xECBB, 0x5124, 0xECBC, 0x5125, 0xECBA, + 0x5126, 0xECB9, 0x5129, 0xECBE, 0x512A, 0xC075, 0x512D, 0xEFB8, 0x512E, 0xEFB9, 0x5130, 0xE4E8, 0x5131, 0xEFB7, 0x5132, 0xC078, + 0x5133, 0xC35F, 0x5134, 0xF1EB, 0x5135, 0xF1EC, 0x5137, 0xC4D7, 0x5138, 0xC4D8, 0x5139, 0xF5C1, 0x513A, 0xF5C0, 0x513B, 0xC56C, + 0x513C, 0xC56B, 0x513D, 0xF7D0, 0x513F, 0xA449, 0x5140, 0xA461, 0x5141, 0xA4B9, 0x5143, 0xA4B8, 0x5144, 0xA553, 0x5145, 0xA552, + 0x5146, 0xA5FC, 0x5147, 0xA5FB, 0x5148, 0xA5FD, 0x5149, 0xA5FA, 0x514B, 0xA74A, 0x514C, 0xA749, 0x514D, 0xA74B, 0x5152, 0xA8E0, + 0x5154, 0xA8DF, 0x5155, 0xA8E1, 0x5157, 0xAB5E, 0x5159, 0xA259, 0x515A, 0xD0DE, 0x515B, 0xA25A, 0x515C, 0xB0C2, 0x515D, 0xA25C, + 0x515E, 0xA25B, 0x515F, 0xD860, 0x5161, 0xA25D, 0x5162, 0xB9B8, 0x5163, 0xA25E, 0x5165, 0xA44A, 0x5167, 0xA4BA, 0x5168, 0xA5FE, + 0x5169, 0xA8E2, 0x516B, 0xA44B, 0x516C, 0xA4BD, 0x516D, 0xA4BB, 0x516E, 0xA4BC, 0x5171, 0xA640, 0x5175, 0xA74C, 0x5176, 0xA8E4, + 0x5177, 0xA8E3, 0x5178, 0xA8E5, 0x517C, 0xADDD, 0x5180, 0xBEAC, 0x5187, 0xC94E, 0x5189, 0xA554, 0x518A, 0xA555, 0x518D, 0xA641, + 0x518F, 0xCA6A, 0x5191, 0xAB60, 0x5192, 0xAB5F, 0x5193, 0xD0E0, 0x5194, 0xD0DF, 0x5195, 0xB0C3, 0x5197, 0xA4BE, 0x5198, 0xC955, + 0x519E, 0xCBCD, 0x51A0, 0xAB61, 0x51A2, 0xADE0, 0x51A4, 0xADDE, 0x51A5, 0xADDF, 0x51AA, 0xBEAD, 0x51AC, 0xA556, 0x51B0, 0xA642, + 0x51B1, 0xC9BC, 0x51B6, 0xA74D, 0x51B7, 0xA74E, 0x51B9, 0xCA6B, 0x51BC, 0xCBCE, 0x51BD, 0xA8E6, 0x51BE, 0xCBCF, 0x51C4, 0xD0E2, + 0x51C5, 0xD0E3, 0x51C6, 0xADE3, 0x51C8, 0xD0E4, 0x51CA, 0xD0E1, 0x51CB, 0xADE4, 0x51CC, 0xADE2, 0x51CD, 0xADE1, 0x51CE, 0xD0E5, + 0x51D0, 0xD468, 0x51D4, 0xD861, 0x51D7, 0xDCC5, 0x51D8, 0xE140, 0x51DC, 0xBBFE, 0x51DD, 0xBEAE, 0x51DE, 0xE8F9, 0x51E0, 0xA44C, + 0x51E1, 0xA45A, 0x51F0, 0xB0C4, 0x51F1, 0xB3CD, 0x51F3, 0xB9B9, 0x51F5, 0xC942, 0x51F6, 0xA4BF, 0x51F8, 0xA559, 0x51F9, 0xA557, + 0x51FA, 0xA558, 0x51FD, 0xA8E7, 0x5200, 0xA44D, 0x5201, 0xA44E, 0x5203, 0xA462, 0x5206, 0xA4C0, 0x5207, 0xA4C1, 0x5208, 0xA4C2, + 0x5209, 0xC9BE, 0x520A, 0xA55A, 0x520C, 0xC96B, 0x520E, 0xA646, 0x5210, 0xC9BF, 0x5211, 0xA644, 0x5212, 0xA645, 0x5213, 0xC9BD, + 0x5216, 0xA647, 0x5217, 0xA643, 0x521C, 0xCA6C, 0x521D, 0xAAEC, 0x521E, 0xCA6D, 0x5221, 0xCA6E, 0x5224, 0xA750, 0x5225, 0xA74F, + 0x5228, 0xA753, 0x5229, 0xA751, 0x522A, 0xA752, 0x522E, 0xA8ED, 0x5230, 0xA8EC, 0x5231, 0xCBD4, 0x5232, 0xCBD1, 0x5233, 0xCBD2, + 0x5235, 0xCBD0, 0x5236, 0xA8EE, 0x5237, 0xA8EA, 0x5238, 0xA8E9, 0x523A, 0xA8EB, 0x523B, 0xA8E8, 0x5241, 0xA8EF, 0x5243, 0xAB63, + 0x5244, 0xCDF0, 0x5246, 0xCBD3, 0x5247, 0xAB68, 0x5249, 0xCDF1, 0x524A, 0xAB64, 0x524B, 0xAB67, 0x524C, 0xAB66, 0x524D, 0xAB65, + 0x524E, 0xAB62, 0x5252, 0xD0E8, 0x5254, 0xADE7, 0x5255, 0xD0EB, 0x5256, 0xADE5, 0x525A, 0xD0E7, 0x525B, 0xADE8, 0x525C, 0xADE6, + 0x525D, 0xADE9, 0x525E, 0xD0E9, 0x525F, 0xD0EA, 0x5261, 0xD0E6, 0x5262, 0xD0EC, 0x5269, 0xB3D1, 0x526A, 0xB0C5, 0x526B, 0xD469, + 0x526C, 0xD46B, 0x526D, 0xD46A, 0x526E, 0xD46C, 0x526F, 0xB0C6, 0x5272, 0xB3CE, 0x5274, 0xB3CF, 0x5275, 0xB3D0, 0x5277, 0xB6D0, + 0x5278, 0xDCC7, 0x527A, 0xDCC6, 0x527B, 0xDCC8, 0x527C, 0xDCC9, 0x527D, 0xB6D1, 0x527F, 0xB6CF, 0x5280, 0xE141, 0x5281, 0xE142, + 0x5282, 0xB9BB, 0x5283, 0xB9BA, 0x5284, 0xE35A, 0x5287, 0xBC40, 0x5288, 0xBC41, 0x5289, 0xBC42, 0x528A, 0xBC44, 0x528B, 0xE4F2, + 0x528C, 0xE4F3, 0x528D, 0xBC43, 0x5291, 0xBEAF, 0x5293, 0xBEB0, 0x5296, 0xF1ED, 0x5297, 0xF5C3, 0x5298, 0xF5C2, 0x5299, 0xF7D1, + 0x529B, 0xA44F, 0x529F, 0xA55C, 0x52A0, 0xA55B, 0x52A3, 0xA648, 0x52A6, 0xC9C0, 0x52A9, 0xA755, 0x52AA, 0xA756, 0x52AB, 0xA754, + 0x52AC, 0xA757, 0x52AD, 0xCA6F, 0x52AE, 0xCA70, 0x52BB, 0xA8F1, 0x52BC, 0xCBD5, 0x52BE, 0xA8F0, 0x52C0, 0xCDF2, 0x52C1, 0xAB6C, + 0x52C2, 0xCDF3, 0x52C3, 0xAB6B, 0x52C7, 0xAB69, 0x52C9, 0xAB6A, 0x52CD, 0xD0ED, 0x52D2, 0xB0C7, 0x52D3, 0xD46E, 0x52D5, 0xB0CA, + 0x52D6, 0xD46D, 0x52D7, 0xB1E5, 0x52D8, 0xB0C9, 0x52D9, 0xB0C8, 0x52DB, 0xB3D4, 0x52DD, 0xB3D3, 0x52DE, 0xB3D2, 0x52DF, 0xB6D2, + 0x52E2, 0xB6D5, 0x52E3, 0xB6D6, 0x52E4, 0xB6D4, 0x52E6, 0xB6D3, 0x52E9, 0xE143, 0x52EB, 0xE144, 0x52EF, 0xE4F5, 0x52F0, 0xBC45, + 0x52F1, 0xE4F4, 0x52F3, 0xBEB1, 0x52F4, 0xECBF, 0x52F5, 0xC079, 0x52F7, 0xF1EE, 0x52F8, 0xC455, 0x52FA, 0xA463, 0x52FB, 0xA4C3, + 0x52FC, 0xC956, 0x52FE, 0xA4C4, 0x52FF, 0xA4C5, 0x5305, 0xA55D, 0x5306, 0xA55E, 0x5308, 0xA649, 0x5309, 0xCA71, 0x530A, 0xCBD6, + 0x530B, 0xCBD7, 0x530D, 0xAB6D, 0x530E, 0xD0EE, 0x530F, 0xB0CC, 0x5310, 0xB0CB, 0x5311, 0xD863, 0x5312, 0xD862, 0x5315, 0xA450, + 0x5316, 0xA4C6, 0x5317, 0xA55F, 0x5319, 0xB0CD, 0x531A, 0xC943, 0x531C, 0xC96C, 0x531D, 0xA560, 0x531F, 0xC9C2, 0x5320, 0xA64B, + 0x5321, 0xA64A, 0x5322, 0xC9C1, 0x5323, 0xA758, 0x532A, 0xADEA, 0x532D, 0xD46F, 0x532F, 0xB6D7, 0x5330, 0xE145, 0x5331, 0xB9BC, + 0x5334, 0xE8FA, 0x5337, 0xF3FD, 0x5339, 0xA4C7, 0x533C, 0xCBD8, 0x533D, 0xCDF4, 0x533E, 0xB0D0, 0x533F, 0xB0CE, 0x5340, 0xB0CF, + 0x5341, 0xA2CC, 0x5341, 0xA451, 0x5343, 0xA464, 0x5344, 0xA2CD, 0x5345, 0xA2CE, 0x5345, 0xA4CA, 0x5347, 0xA4C9, 0x5348, 0xA4C8, + 0x5349, 0xA563, 0x534A, 0xA562, 0x534C, 0xC96D, 0x534D, 0xC9C3, 0x5351, 0xA8F5, 0x5352, 0xA8F2, 0x5353, 0xA8F4, 0x5354, 0xA8F3, + 0x5357, 0xAB6E, 0x535A, 0xB3D5, 0x535C, 0xA452, 0x535E, 0xA4CB, 0x5360, 0xA565, 0x5361, 0xA564, 0x5363, 0xCA72, 0x5366, 0xA8F6, + 0x536C, 0xC957, 0x536E, 0xA567, 0x536F, 0xA566, 0x5370, 0xA64C, 0x5371, 0xA64D, 0x5372, 0xCA73, 0x5373, 0xA759, 0x5375, 0xA75A, + 0x5377, 0xA8F7, 0x5378, 0xA8F8, 0x5379, 0xA8F9, 0x537B, 0xAB6F, 0x537C, 0xCDF5, 0x537F, 0xADEB, 0x5382, 0xC944, 0x5384, 0xA4CC, + 0x538A, 0xC9C4, 0x538E, 0xCA74, 0x538F, 0xCA75, 0x5392, 0xCBD9, 0x5394, 0xCBDA, 0x5396, 0xCDF7, 0x5397, 0xCDF6, 0x5398, 0xCDF9, + 0x5399, 0xCDF8, 0x539A, 0xAB70, 0x539C, 0xD470, 0x539D, 0xADED, 0x539E, 0xD0EF, 0x539F, 0xADEC, 0x53A4, 0xD864, 0x53A5, 0xB3D6, + 0x53A7, 0xD865, 0x53AC, 0xE146, 0x53AD, 0xB9BD, 0x53B2, 0xBC46, 0x53B4, 0xF1EF, 0x53B9, 0xC958, 0x53BB, 0xA568, 0x53C3, 0xB0D1, + 0x53C8, 0xA453, 0x53C9, 0xA465, 0x53CA, 0xA4CE, 0x53CB, 0xA4CD, 0x53CD, 0xA4CF, 0x53D4, 0xA8FB, 0x53D6, 0xA8FA, 0x53D7, 0xA8FC, + 0x53DB, 0xAB71, 0x53DF, 0xADEE, 0x53E1, 0xE8FB, 0x53E2, 0xC24F, 0x53E3, 0xA466, 0x53E4, 0xA56A, 0x53E5, 0xA579, 0x53E6, 0xA574, + 0x53E8, 0xA56F, 0x53E9, 0xA56E, 0x53EA, 0xA575, 0x53EB, 0xA573, 0x53EC, 0xA56C, 0x53ED, 0xA57A, 0x53EE, 0xA56D, 0x53EF, 0xA569, + 0x53F0, 0xA578, 0x53F1, 0xA577, 0x53F2, 0xA576, 0x53F3, 0xA56B, 0x53F5, 0xA572, 0x53F8, 0xA571, 0x53FB, 0xA57B, 0x53FC, 0xA570, + 0x5401, 0xA653, 0x5403, 0xA659, 0x5404, 0xA655, 0x5406, 0xA65B, 0x5407, 0xC9C5, 0x5408, 0xA658, 0x5409, 0xA64E, 0x540A, 0xA651, + 0x540B, 0xA654, 0x540C, 0xA650, 0x540D, 0xA657, 0x540E, 0xA65A, 0x540F, 0xA64F, 0x5410, 0xA652, 0x5411, 0xA656, 0x5412, 0xA65C, + 0x5418, 0xCA7E, 0x5419, 0xCA7B, 0x541B, 0xA767, 0x541C, 0xCA7C, 0x541D, 0xA75B, 0x541E, 0xA75D, 0x541F, 0xA775, 0x5420, 0xA770, + 0x5424, 0xCAA5, 0x5425, 0xCA7D, 0x5426, 0xA75F, 0x5427, 0xA761, 0x5428, 0xCAA4, 0x5429, 0xA768, 0x542A, 0xCA78, 0x542B, 0xA774, + 0x542C, 0xA776, 0x542D, 0xA75C, 0x542E, 0xA76D, 0x5430, 0xCA76, 0x5431, 0xA773, 0x5433, 0xA764, 0x5435, 0xA76E, 0x5436, 0xA76F, + 0x5437, 0xCA77, 0x5438, 0xA76C, 0x5439, 0xA76A, 0x543B, 0xA76B, 0x543C, 0xA771, 0x543D, 0xCAA1, 0x543E, 0xA75E, 0x5440, 0xA772, + 0x5441, 0xCAA3, 0x5442, 0xA766, 0x5443, 0xA763, 0x5445, 0xCA7A, 0x5446, 0xA762, 0x5447, 0xCAA6, 0x5448, 0xA765, 0x544A, 0xA769, + 0x544E, 0xA760, 0x544F, 0xCAA2, 0x5454, 0xCA79, 0x5460, 0xCBEB, 0x5461, 0xCBEA, 0x5462, 0xA94F, 0x5463, 0xCBED, 0x5464, 0xCBEF, + 0x5465, 0xCBE4, 0x5466, 0xCBE7, 0x5467, 0xCBEE, 0x5468, 0xA950, 0x546B, 0xCBE1, 0x546C, 0xCBE5, 0x546F, 0xCBE9, 0x5470, 0xCE49, + 0x5471, 0xA94B, 0x5472, 0xCE4D, 0x5473, 0xA8FD, 0x5474, 0xCBE6, 0x5475, 0xA8FE, 0x5476, 0xA94C, 0x5477, 0xA945, 0x5478, 0xA941, + 0x547A, 0xCBE2, 0x547B, 0xA944, 0x547C, 0xA949, 0x547D, 0xA952, 0x547E, 0xCBE3, 0x547F, 0xCBDC, 0x5480, 0xA943, 0x5481, 0xCBDD, + 0x5482, 0xCBDF, 0x5484, 0xA946, 0x5486, 0xA948, 0x5487, 0xCBDB, 0x5488, 0xCBE0, 0x548B, 0xA951, 0x548C, 0xA94D, 0x548D, 0xCBE8, + 0x548E, 0xA953, 0x5490, 0xA94A, 0x5491, 0xCBDE, 0x5492, 0xA947, 0x5495, 0xA942, 0x5496, 0xA940, 0x5498, 0xCBEC, 0x549A, 0xA94E, + 0x54A0, 0xCE48, 0x54A1, 0xCDFB, 0x54A2, 0xCE4B, 0x54A5, 0xCDFD, 0x54A6, 0xAB78, 0x54A7, 0xABA8, 0x54A8, 0xAB74, 0x54A9, 0xABA7, + 0x54AA, 0xAB7D, 0x54AB, 0xABA4, 0x54AC, 0xAB72, 0x54AD, 0xCDFC, 0x54AE, 0xCE43, 0x54AF, 0xABA3, 0x54B0, 0xCE4F, 0x54B1, 0xABA5, + 0x54B3, 0xAB79, 0x54B6, 0xCE45, 0x54B7, 0xCE42, 0x54B8, 0xAB77, 0x54BA, 0xCDFA, 0x54BB, 0xABA6, 0x54BC, 0xCE4A, 0x54BD, 0xAB7C, + 0x54BE, 0xCE4C, 0x54BF, 0xABA9, 0x54C0, 0xAB73, 0x54C1, 0xAB7E, 0x54C2, 0xAB7B, 0x54C3, 0xCE40, 0x54C4, 0xABA1, 0x54C5, 0xCE46, + 0x54C6, 0xCE47, 0x54C7, 0xAB7A, 0x54C8, 0xABA2, 0x54C9, 0xAB76, 0x54CE, 0xAB75, 0x54CF, 0xCDFE, 0x54D6, 0xCE44, 0x54DE, 0xCE4E, + 0x54E0, 0xD144, 0x54E1, 0xADFB, 0x54E2, 0xD0F1, 0x54E4, 0xD0F6, 0x54E5, 0xADF4, 0x54E6, 0xAE40, 0x54E7, 0xD0F4, 0x54E8, 0xADEF, + 0x54E9, 0xADF9, 0x54EA, 0xADFE, 0x54EB, 0xD0FB, 0x54ED, 0xADFA, 0x54EE, 0xADFD, 0x54F1, 0xD0FE, 0x54F2, 0xADF5, 0x54F3, 0xD0F5, + 0x54F7, 0xD142, 0x54F8, 0xD143, 0x54FA, 0xADF7, 0x54FB, 0xD141, 0x54FC, 0xADF3, 0x54FD, 0xAE43, 0x54FF, 0xD0F8, 0x5501, 0xADF1, + 0x5503, 0xD146, 0x5504, 0xD0F9, 0x5505, 0xD0FD, 0x5506, 0xADF6, 0x5507, 0xAE42, 0x5508, 0xD0FA, 0x5509, 0xADFC, 0x550A, 0xD140, + 0x550B, 0xD147, 0x550C, 0xD4A1, 0x550E, 0xD145, 0x550F, 0xAE44, 0x5510, 0xADF0, 0x5511, 0xD0FC, 0x5512, 0xD0F3, 0x5514, 0xADF8, + 0x5517, 0xD0F2, 0x551A, 0xD0F7, 0x5526, 0xD0F0, 0x5527, 0xAE41, 0x552A, 0xD477, 0x552C, 0xB0E4, 0x552D, 0xD4A7, 0x552E, 0xB0E2, + 0x552F, 0xB0DF, 0x5530, 0xD47C, 0x5531, 0xB0DB, 0x5532, 0xD4A2, 0x5533, 0xB0E6, 0x5534, 0xD476, 0x5535, 0xD47B, 0x5536, 0xD47A, + 0x5537, 0xADF2, 0x5538, 0xB0E1, 0x5539, 0xD4A5, 0x553B, 0xD4A8, 0x553C, 0xD473, 0x553E, 0xB3E8, 0x5540, 0xD4A9, 0x5541, 0xB0E7, + 0x5543, 0xB0D9, 0x5544, 0xB0D6, 0x5545, 0xD47E, 0x5546, 0xB0D3, 0x5548, 0xD4A6, 0x554A, 0xB0DA, 0x554B, 0xD4AA, 0x554D, 0xD474, + 0x554E, 0xD4A4, 0x554F, 0xB0DD, 0x5550, 0xD475, 0x5551, 0xD478, 0x5552, 0xD47D, 0x5555, 0xB0DE, 0x5556, 0xB0DC, 0x5557, 0xB0E8, + 0x555C, 0xB0E3, 0x555E, 0xB0D7, 0x555F, 0xB1D2, 0x5561, 0xB0D8, 0x5562, 0xD479, 0x5563, 0xB0E5, 0x5564, 0xB0E0, 0x5565, 0xD4A3, + 0x5566, 0xB0D5, 0x556A, 0xB0D4, 0x5575, 0xD471, 0x5576, 0xD472, 0x5577, 0xD86A, 0x557B, 0xB3D7, 0x557C, 0xB3DA, 0x557D, 0xD875, + 0x557E, 0xB3EE, 0x557F, 0xD878, 0x5580, 0xB3D8, 0x5581, 0xD871, 0x5582, 0xB3DE, 0x5583, 0xB3E4, 0x5584, 0xB5BD, 0x5587, 0xB3E2, + 0x5588, 0xD86E, 0x5589, 0xB3EF, 0x558A, 0xB3DB, 0x558B, 0xB3E3, 0x558C, 0xD876, 0x558D, 0xDCD7, 0x558E, 0xD87B, 0x558F, 0xD86F, + 0x5591, 0xD866, 0x5592, 0xD873, 0x5593, 0xD86D, 0x5594, 0xB3E1, 0x5595, 0xD879, 0x5598, 0xB3DD, 0x5599, 0xB3F1, 0x559A, 0xB3EA, + 0x559C, 0xB3DF, 0x559D, 0xB3DC, 0x559F, 0xB3E7, 0x55A1, 0xD87A, 0x55A2, 0xD86C, 0x55A3, 0xD872, 0x55A4, 0xD874, 0x55A5, 0xD868, + 0x55A6, 0xD877, 0x55A7, 0xB3D9, 0x55A8, 0xD867, 0x55AA, 0xB3E0, 0x55AB, 0xB3F0, 0x55AC, 0xB3EC, 0x55AD, 0xD869, 0x55AE, 0xB3E6, + 0x55B1, 0xB3ED, 0x55B2, 0xB3E9, 0x55B3, 0xB3E5, 0x55B5, 0xD870, 0x55BB, 0xB3EB, 0x55BF, 0xDCD5, 0x55C0, 0xDCD1, 0x55C2, 0xDCE0, + 0x55C3, 0xDCCA, 0x55C4, 0xDCD3, 0x55C5, 0xB6E5, 0x55C6, 0xB6E6, 0x55C7, 0xB6DE, 0x55C8, 0xDCDC, 0x55C9, 0xB6E8, 0x55CA, 0xDCCF, + 0x55CB, 0xDCCE, 0x55CC, 0xDCCC, 0x55CD, 0xDCDE, 0x55CE, 0xB6DC, 0x55CF, 0xDCD8, 0x55D0, 0xDCCD, 0x55D1, 0xB6DF, 0x55D2, 0xDCD6, + 0x55D3, 0xB6DA, 0x55D4, 0xDCD2, 0x55D5, 0xDCD9, 0x55D6, 0xDCDB, 0x55D9, 0xDCDF, 0x55DA, 0xB6E3, 0x55DB, 0xDCCB, 0x55DC, 0xB6DD, + 0x55DD, 0xDCD0, 0x55DF, 0xB6D8, 0x55E1, 0xB6E4, 0x55E2, 0xDCDA, 0x55E3, 0xB6E0, 0x55E4, 0xB6E1, 0x55E5, 0xB6E7, 0x55E6, 0xB6DB, + 0x55E7, 0xA25F, 0x55E8, 0xB6D9, 0x55E9, 0xDCD4, 0x55EF, 0xB6E2, 0x55F2, 0xDCDD, 0x55F6, 0xB9CD, 0x55F7, 0xB9C8, 0x55F9, 0xE155, + 0x55FA, 0xE151, 0x55FC, 0xE14B, 0x55FD, 0xB9C2, 0x55FE, 0xB9BE, 0x55FF, 0xE154, 0x5600, 0xB9BF, 0x5601, 0xE14E, 0x5602, 0xE150, + 0x5604, 0xE153, 0x5606, 0xB9C4, 0x5608, 0xB9CB, 0x5609, 0xB9C5, 0x560C, 0xE149, 0x560D, 0xB9C6, 0x560E, 0xB9C7, 0x560F, 0xE14C, + 0x5610, 0xB9CC, 0x5612, 0xE14A, 0x5613, 0xE14F, 0x5614, 0xB9C3, 0x5615, 0xE148, 0x5616, 0xB9C9, 0x5617, 0xB9C1, 0x561B, 0xB9C0, + 0x561C, 0xE14D, 0x561D, 0xE152, 0x561F, 0xB9CA, 0x5627, 0xE147, 0x5629, 0xBC4D, 0x562A, 0xE547, 0x562C, 0xE544, 0x562E, 0xBC47, + 0x562F, 0xBC53, 0x5630, 0xBC54, 0x5632, 0xBC4A, 0x5633, 0xE542, 0x5634, 0xBC4C, 0x5635, 0xE4F9, 0x5636, 0xBC52, 0x5638, 0xE546, + 0x5639, 0xBC49, 0x563A, 0xE548, 0x563B, 0xBC48, 0x563D, 0xE543, 0x563E, 0xE545, 0x563F, 0xBC4B, 0x5640, 0xE541, 0x5641, 0xE4FA, + 0x5642, 0xE4F7, 0x5645, 0xD86B, 0x5646, 0xE4FD, 0x5648, 0xE4F6, 0x5649, 0xE4FC, 0x564A, 0xE4FB, 0x564C, 0xE4F8, 0x564E, 0xBC4F, + 0x5653, 0xBC4E, 0x5657, 0xBC50, 0x5658, 0xE4FE, 0x5659, 0xBEB2, 0x565A, 0xE540, 0x565E, 0xE945, 0x5660, 0xE8FD, 0x5662, 0xBEBE, + 0x5663, 0xE942, 0x5664, 0xBEB6, 0x5665, 0xBEBA, 0x5666, 0xE941, 0x5668, 0xBEB9, 0x5669, 0xBEB5, 0x566A, 0xBEB8, 0x566B, 0xBEB3, + 0x566C, 0xBEBD, 0x566D, 0xE943, 0x566E, 0xE8FE, 0x566F, 0xBEBC, 0x5670, 0xE8FC, 0x5671, 0xBEBB, 0x5672, 0xE944, 0x5673, 0xE940, + 0x5674, 0xBC51, 0x5676, 0xBEBF, 0x5677, 0xE946, 0x5678, 0xBEB7, 0x5679, 0xBEB4, 0x567E, 0xECC6, 0x567F, 0xECC8, 0x5680, 0xC07B, + 0x5681, 0xECC9, 0x5682, 0xECC7, 0x5683, 0xECC5, 0x5684, 0xECC4, 0x5685, 0xC07D, 0x5686, 0xECC3, 0x5687, 0xC07E, 0x568C, 0xECC1, + 0x568D, 0xECC2, 0x568E, 0xC07A, 0x568F, 0xC0A1, 0x5690, 0xC07C, 0x5693, 0xECC0, 0x5695, 0xC250, 0x5697, 0xEFBC, 0x5698, 0xEFBA, + 0x5699, 0xEFBF, 0x569A, 0xEFBD, 0x569C, 0xEFBB, 0x569D, 0xEFBE, 0x56A5, 0xC360, 0x56A6, 0xF1F2, 0x56A7, 0xF1F3, 0x56A8, 0xC456, + 0x56AA, 0xF1F4, 0x56AB, 0xF1F0, 0x56AC, 0xF1F5, 0x56AD, 0xF1F1, 0x56AE, 0xC251, 0x56B2, 0xF3FE, 0x56B3, 0xF441, 0x56B4, 0xC459, + 0x56B5, 0xF440, 0x56B6, 0xC458, 0x56B7, 0xC457, 0x56BC, 0xC45A, 0x56BD, 0xF5C5, 0x56BE, 0xF5C6, 0x56C0, 0xC4DA, 0x56C1, 0xC4D9, + 0x56C2, 0xC4DB, 0x56C3, 0xF5C4, 0x56C5, 0xF6D8, 0x56C6, 0xF6D7, 0x56C8, 0xC56D, 0x56C9, 0xC56F, 0x56CA, 0xC56E, 0x56CB, 0xF6D9, + 0x56CC, 0xC5C8, 0x56CD, 0xF8A6, 0x56D1, 0xC5F1, 0x56D3, 0xF8A5, 0x56D4, 0xF8EE, 0x56D7, 0xC949, 0x56DA, 0xA57D, 0x56DB, 0xA57C, + 0x56DD, 0xA65F, 0x56DE, 0xA65E, 0x56DF, 0xC9C7, 0x56E0, 0xA65D, 0x56E1, 0xC9C6, 0x56E4, 0xA779, 0x56E5, 0xCAA9, 0x56E7, 0xCAA8, + 0x56EA, 0xA777, 0x56EB, 0xA77A, 0x56EE, 0xCAA7, 0x56F0, 0xA778, 0x56F7, 0xCBF0, 0x56F9, 0xCBF1, 0x56FA, 0xA954, 0x56FF, 0xABAA, + 0x5701, 0xD148, 0x5702, 0xD149, 0x5703, 0xAE45, 0x5704, 0xAE46, 0x5707, 0xD4AC, 0x5708, 0xB0E9, 0x5709, 0xB0EB, 0x570A, 0xD4AB, + 0x570B, 0xB0EA, 0x570C, 0xD87C, 0x570D, 0xB3F2, 0x5712, 0xB6E9, 0x5713, 0xB6EA, 0x5714, 0xDCE1, 0x5716, 0xB9CF, 0x5718, 0xB9CE, + 0x571A, 0xE549, 0x571B, 0xE948, 0x571C, 0xE947, 0x571E, 0xF96B, 0x571F, 0xA467, 0x5720, 0xC959, 0x5722, 0xC96E, 0x5723, 0xC96F, + 0x5728, 0xA662, 0x5729, 0xA666, 0x572A, 0xC9C9, 0x572C, 0xA664, 0x572D, 0xA663, 0x572E, 0xC9C8, 0x572F, 0xA665, 0x5730, 0xA661, + 0x5733, 0xA660, 0x5734, 0xC9CA, 0x573B, 0xA7A6, 0x573E, 0xA7A3, 0x5740, 0xA77D, 0x5741, 0xCAAA, 0x5745, 0xCAAB, 0x5747, 0xA7A1, + 0x5749, 0xCAAD, 0x574A, 0xA77B, 0x574B, 0xCAAE, 0x574C, 0xCAAC, 0x574D, 0xA77E, 0x574E, 0xA7A2, 0x574F, 0xA7A5, 0x5750, 0xA7A4, + 0x5751, 0xA77C, 0x5752, 0xCAAF, 0x5761, 0xA959, 0x5762, 0xCBFE, 0x5764, 0xA95B, 0x5766, 0xA95A, 0x5768, 0xCC40, 0x5769, 0xA958, + 0x576A, 0xA957, 0x576B, 0xCBF5, 0x576D, 0xCBF4, 0x576F, 0xCBF2, 0x5770, 0xCBF7, 0x5771, 0xCBF6, 0x5772, 0xCBF3, 0x5773, 0xCBFC, + 0x5774, 0xCBFD, 0x5775, 0xCBFA, 0x5776, 0xCBF8, 0x5777, 0xA956, 0x577B, 0xCBFB, 0x577C, 0xA95C, 0x577D, 0xCC41, 0x5780, 0xCBF9, + 0x5782, 0xABAB, 0x5783, 0xA955, 0x578B, 0xABAC, 0x578C, 0xCE54, 0x578F, 0xCE5A, 0x5793, 0xABB2, 0x5794, 0xCE58, 0x5795, 0xCE5E, + 0x5797, 0xCE55, 0x5798, 0xCE59, 0x5799, 0xCE5B, 0x579A, 0xCE5D, 0x579B, 0xCE57, 0x579D, 0xCE56, 0x579E, 0xCE51, 0x579F, 0xCE52, + 0x57A0, 0xABAD, 0x57A2, 0xABAF, 0x57A3, 0xABAE, 0x57A4, 0xCE53, 0x57A5, 0xCE5C, 0x57AE, 0xABB1, 0x57B5, 0xCE50, 0x57B6, 0xD153, + 0x57B8, 0xD152, 0x57B9, 0xD157, 0x57BA, 0xD14E, 0x57BC, 0xD151, 0x57BD, 0xD150, 0x57BF, 0xD154, 0x57C1, 0xD158, 0x57C2, 0xAE47, + 0x57C3, 0xAE4A, 0x57C6, 0xD14F, 0x57C7, 0xD155, 0x57CB, 0xAE49, 0x57CC, 0xD14A, 0x57CE, 0xABB0, 0x57CF, 0xD4BA, 0x57D0, 0xD156, + 0x57D2, 0xD14D, 0x57D4, 0xAE48, 0x57D5, 0xD14C, 0x57DC, 0xD4B1, 0x57DF, 0xB0EC, 0x57E0, 0xB0F0, 0x57E1, 0xD4C1, 0x57E2, 0xD4AF, + 0x57E3, 0xD4BD, 0x57E4, 0xB0F1, 0x57E5, 0xD4BF, 0x57E7, 0xD4C5, 0x57E9, 0xD4C9, 0x57EC, 0xD4C0, 0x57ED, 0xD4B4, 0x57EE, 0xD4BC, + 0x57F0, 0xD4CA, 0x57F1, 0xD4C8, 0x57F2, 0xD4BE, 0x57F3, 0xD4B9, 0x57F4, 0xD4B2, 0x57F5, 0xD8A6, 0x57F6, 0xD4B0, 0x57F7, 0xB0F5, + 0x57F8, 0xD4B7, 0x57F9, 0xB0F6, 0x57FA, 0xB0F2, 0x57FB, 0xD4AD, 0x57FC, 0xD4C3, 0x57FD, 0xD4B5, 0x5800, 0xD4B3, 0x5801, 0xD4C6, + 0x5802, 0xB0F3, 0x5804, 0xD4CC, 0x5805, 0xB0ED, 0x5806, 0xB0EF, 0x5807, 0xD4BB, 0x5808, 0xD4B6, 0x5809, 0xAE4B, 0x580A, 0xB0EE, + 0x580B, 0xD4B8, 0x580C, 0xD4C7, 0x580D, 0xD4CB, 0x580E, 0xD4C2, 0x5810, 0xD4C4, 0x5814, 0xD4AE, 0x5819, 0xD8A1, 0x581B, 0xD8AA, + 0x581C, 0xD8A9, 0x581D, 0xB3FA, 0x581E, 0xD8A2, 0x5820, 0xB3FB, 0x5821, 0xB3F9, 0x5823, 0xD8A4, 0x5824, 0xB3F6, 0x5825, 0xD8A8, + 0x5827, 0xD8A3, 0x5828, 0xD8A5, 0x5829, 0xD87D, 0x582A, 0xB3F4, 0x582C, 0xD8B2, 0x582D, 0xD8B1, 0x582E, 0xD8AE, 0x582F, 0xB3F3, + 0x5830, 0xB3F7, 0x5831, 0xB3F8, 0x5832, 0xD14B, 0x5833, 0xD8AB, 0x5834, 0xB3F5, 0x5835, 0xB0F4, 0x5836, 0xD8AD, 0x5837, 0xD87E, + 0x5838, 0xD8B0, 0x5839, 0xD8AF, 0x583B, 0xD8B3, 0x583D, 0xDCEF, 0x583F, 0xD8AC, 0x5848, 0xD8A7, 0x5849, 0xDCE7, 0x584A, 0xB6F4, + 0x584B, 0xB6F7, 0x584C, 0xB6F2, 0x584D, 0xDCE6, 0x584E, 0xDCEA, 0x584F, 0xDCE5, 0x5851, 0xB6EC, 0x5852, 0xB6F6, 0x5853, 0xDCE2, + 0x5854, 0xB6F0, 0x5855, 0xDCE9, 0x5857, 0xB6EE, 0x5858, 0xB6ED, 0x5859, 0xDCEC, 0x585A, 0xB6EF, 0x585B, 0xDCEE, 0x585D, 0xDCEB, + 0x585E, 0xB6EB, 0x5862, 0xB6F5, 0x5863, 0xDCF0, 0x5864, 0xDCE4, 0x5865, 0xDCED, 0x5868, 0xDCE3, 0x586B, 0xB6F1, 0x586D, 0xB6F3, + 0x586F, 0xDCE8, 0x5871, 0xDCF1, 0x5874, 0xE15D, 0x5875, 0xB9D0, 0x5876, 0xE163, 0x5879, 0xB9D5, 0x587A, 0xE15F, 0x587B, 0xE166, + 0x587C, 0xE157, 0x587D, 0xB9D7, 0x587E, 0xB9D1, 0x587F, 0xE15C, 0x5880, 0xBC55, 0x5881, 0xE15B, 0x5882, 0xE164, 0x5883, 0xB9D2, + 0x5885, 0xB9D6, 0x5886, 0xE15A, 0x5887, 0xE160, 0x5888, 0xE165, 0x5889, 0xE156, 0x588A, 0xB9D4, 0x588B, 0xE15E, 0x588E, 0xE162, + 0x588F, 0xE168, 0x5890, 0xE158, 0x5891, 0xE161, 0x5893, 0xB9D3, 0x5894, 0xE167, 0x5898, 0xE159, 0x589C, 0xBC59, 0x589D, 0xE54B, + 0x589E, 0xBC57, 0x589F, 0xBC56, 0x58A0, 0xE54D, 0x58A1, 0xE552, 0x58A3, 0xE54E, 0x58A5, 0xE551, 0x58A6, 0xBC5C, 0x58A8, 0xBEA5, + 0x58A9, 0xBC5B, 0x58AB, 0xE54A, 0x58AC, 0xE550, 0x58AE, 0xBC5A, 0x58AF, 0xE54F, 0x58B1, 0xE54C, 0x58B3, 0xBC58, 0x58BA, 0xE94D, + 0x58BB, 0xF9D9, 0x58BC, 0xE94F, 0x58BD, 0xE94A, 0x58BE, 0xBEC1, 0x58BF, 0xE94C, 0x58C1, 0xBEC0, 0x58C2, 0xE94E, 0x58C5, 0xBEC3, + 0x58C6, 0xE950, 0x58C7, 0xBEC2, 0x58C8, 0xE949, 0x58C9, 0xE94B, 0x58CE, 0xC0A5, 0x58CF, 0xECCC, 0x58D1, 0xC0A4, 0x58D2, 0xECCD, + 0x58D3, 0xC0A3, 0x58D4, 0xECCB, 0x58D5, 0xC0A2, 0x58D6, 0xECCA, 0x58D8, 0xC253, 0x58D9, 0xC252, 0x58DA, 0xF1F6, 0x58DB, 0xF1F8, + 0x58DD, 0xF1F7, 0x58DE, 0xC361, 0x58DF, 0xC362, 0x58E2, 0xC363, 0x58E3, 0xF442, 0x58E4, 0xC45B, 0x58E7, 0xF7D3, 0x58E8, 0xF7D2, + 0x58E9, 0xC5F2, 0x58EB, 0xA468, 0x58EC, 0xA4D0, 0x58EF, 0xA7A7, 0x58F4, 0xCE5F, 0x58F9, 0xB3FC, 0x58FA, 0xB3FD, 0x58FC, 0xDCF2, + 0x58FD, 0xB9D8, 0x58FE, 0xE169, 0x58FF, 0xE553, 0x5903, 0xC95A, 0x5906, 0xCAB0, 0x590C, 0xCC42, 0x590D, 0xCE60, 0x590E, 0xD159, + 0x590F, 0xAE4C, 0x5912, 0xF1F9, 0x5914, 0xC4DC, 0x5915, 0xA469, 0x5916, 0xA57E, 0x5917, 0xC970, 0x5919, 0xA667, 0x591A, 0xA668, + 0x591C, 0xA95D, 0x5920, 0xB0F7, 0x5922, 0xB9DA, 0x5924, 0xB9DB, 0x5925, 0xB9D9, 0x5927, 0xA46A, 0x5929, 0xA4D1, 0x592A, 0xA4D3, + 0x592B, 0xA4D2, 0x592C, 0xC95B, 0x592D, 0xA4D4, 0x592E, 0xA5A1, 0x592F, 0xC971, 0x5931, 0xA5A2, 0x5937, 0xA669, 0x5938, 0xA66A, + 0x593C, 0xC9CB, 0x593E, 0xA7A8, 0x5940, 0xCAB1, 0x5944, 0xA961, 0x5945, 0xCC43, 0x5947, 0xA95F, 0x5948, 0xA960, 0x5949, 0xA95E, + 0x594A, 0xD15A, 0x594E, 0xABB6, 0x594F, 0xABB5, 0x5950, 0xABB7, 0x5951, 0xABB4, 0x5953, 0xCE61, 0x5954, 0xA962, 0x5955, 0xABB3, + 0x5957, 0xAE4D, 0x5958, 0xAE4E, 0x595A, 0xAE4F, 0x595C, 0xD4CD, 0x5960, 0xB3FE, 0x5961, 0xD8B4, 0x5962, 0xB0F8, 0x5967, 0xB6F8, + 0x5969, 0xB9DD, 0x596A, 0xB9DC, 0x596B, 0xE16A, 0x596D, 0xBC5D, 0x596E, 0xBEC4, 0x5970, 0xEFC0, 0x5971, 0xF6DA, 0x5972, 0xF7D4, + 0x5973, 0xA46B, 0x5974, 0xA5A3, 0x5976, 0xA5A4, 0x5977, 0xC9D1, 0x5978, 0xA66C, 0x5979, 0xA66F, 0x597B, 0xC9CF, 0x597C, 0xC9CD, + 0x597D, 0xA66E, 0x597E, 0xC9D0, 0x597F, 0xC9D2, 0x5980, 0xC9CC, 0x5981, 0xA671, 0x5982, 0xA670, 0x5983, 0xA66D, 0x5984, 0xA66B, + 0x5985, 0xC9CE, 0x598A, 0xA7B3, 0x598D, 0xA7B0, 0x598E, 0xCAB6, 0x598F, 0xCAB9, 0x5990, 0xCAB8, 0x5992, 0xA7AA, 0x5993, 0xA7B2, + 0x5996, 0xA7AF, 0x5997, 0xCAB5, 0x5998, 0xCAB3, 0x5999, 0xA7AE, 0x599D, 0xA7A9, 0x599E, 0xA7AC, 0x59A0, 0xCAB4, 0x59A1, 0xCABB, + 0x59A2, 0xCAB7, 0x59A3, 0xA7AD, 0x59A4, 0xA7B1, 0x59A5, 0xA7B4, 0x59A6, 0xCAB2, 0x59A7, 0xCABA, 0x59A8, 0xA7AB, 0x59AE, 0xA967, + 0x59AF, 0xA96F, 0x59B1, 0xCC4F, 0x59B2, 0xCC48, 0x59B3, 0xA970, 0x59B4, 0xCC53, 0x59B5, 0xCC44, 0x59B6, 0xCC4B, 0x59B9, 0xA966, + 0x59BA, 0xCC45, 0x59BB, 0xA964, 0x59BC, 0xCC4C, 0x59BD, 0xCC50, 0x59BE, 0xA963, 0x59C0, 0xCC51, 0x59C1, 0xCC4A, 0x59C3, 0xCC4D, + 0x59C5, 0xA972, 0x59C6, 0xA969, 0x59C7, 0xCC54, 0x59C8, 0xCC52, 0x59CA, 0xA96E, 0x59CB, 0xA96C, 0x59CC, 0xCC49, 0x59CD, 0xA96B, + 0x59CE, 0xCC47, 0x59CF, 0xCC46, 0x59D0, 0xA96A, 0x59D1, 0xA968, 0x59D2, 0xA971, 0x59D3, 0xA96D, 0x59D4, 0xA965, 0x59D6, 0xCC4E, + 0x59D8, 0xABB9, 0x59DA, 0xABC0, 0x59DB, 0xCE6F, 0x59DC, 0xABB8, 0x59DD, 0xCE67, 0x59DE, 0xCE63, 0x59E0, 0xCE73, 0x59E1, 0xCE62, + 0x59E3, 0xABBB, 0x59E4, 0xCE6C, 0x59E5, 0xABBE, 0x59E6, 0xABC1, 0x59E8, 0xABBC, 0x59E9, 0xCE70, 0x59EA, 0xABBF, 0x59EC, 0xAE56, + 0x59ED, 0xCE76, 0x59EE, 0xCE64, 0x59F1, 0xCE66, 0x59F2, 0xCE6D, 0x59F3, 0xCE71, 0x59F4, 0xCE75, 0x59F5, 0xCE72, 0x59F6, 0xCE6B, + 0x59F7, 0xCE6E, 0x59FA, 0xCE68, 0x59FB, 0xABC3, 0x59FC, 0xCE6A, 0x59FD, 0xCE69, 0x59FE, 0xCE74, 0x59FF, 0xABBA, 0x5A00, 0xCE65, + 0x5A01, 0xABC2, 0x5A03, 0xABBD, 0x5A09, 0xAE5C, 0x5A0A, 0xD162, 0x5A0C, 0xAE5B, 0x5A0F, 0xD160, 0x5A11, 0xAE50, 0x5A13, 0xAE55, + 0x5A15, 0xD15F, 0x5A16, 0xD15C, 0x5A17, 0xD161, 0x5A18, 0xAE51, 0x5A19, 0xD15B, 0x5A1B, 0xAE54, 0x5A1C, 0xAE52, 0x5A1E, 0xD163, + 0x5A1F, 0xAE53, 0x5A20, 0xAE57, 0x5A23, 0xAE58, 0x5A25, 0xAE5A, 0x5A29, 0xAE59, 0x5A2D, 0xD15D, 0x5A2E, 0xD15E, 0x5A33, 0xD164, + 0x5A35, 0xD4D4, 0x5A36, 0xB0F9, 0x5A37, 0xD8C2, 0x5A38, 0xD4D3, 0x5A39, 0xD4E6, 0x5A3C, 0xB140, 0x5A3E, 0xD4E4, 0x5A40, 0xB0FE, + 0x5A41, 0xB0FA, 0x5A42, 0xD4ED, 0x5A43, 0xD4DD, 0x5A44, 0xD4E0, 0x5A46, 0xB143, 0x5A47, 0xD4EA, 0x5A48, 0xD4E2, 0x5A49, 0xB0FB, + 0x5A4A, 0xB144, 0x5A4C, 0xD4E7, 0x5A4D, 0xD4E5, 0x5A50, 0xD4D6, 0x5A51, 0xD4EB, 0x5A52, 0xD4DF, 0x5A53, 0xD4DA, 0x5A55, 0xD4D0, + 0x5A56, 0xD4EC, 0x5A57, 0xD4DC, 0x5A58, 0xD4CF, 0x5A5A, 0xB142, 0x5A5B, 0xD4E1, 0x5A5C, 0xD4EE, 0x5A5D, 0xD4DE, 0x5A5E, 0xD4D2, + 0x5A5F, 0xD4D7, 0x5A60, 0xD4CE, 0x5A62, 0xB141, 0x5A64, 0xD4DB, 0x5A65, 0xD4D8, 0x5A66, 0xB0FC, 0x5A67, 0xD4D1, 0x5A69, 0xD4E9, + 0x5A6A, 0xB0FD, 0x5A6C, 0xD4D9, 0x5A6D, 0xD4D5, 0x5A70, 0xD4E8, 0x5A77, 0xB440, 0x5A78, 0xD8BB, 0x5A7A, 0xD8B8, 0x5A7B, 0xD8C9, + 0x5A7C, 0xD8BD, 0x5A7D, 0xD8CA, 0x5A7F, 0xB442, 0x5A83, 0xD8C6, 0x5A84, 0xD8C3, 0x5A8A, 0xD8C4, 0x5A8B, 0xD8C7, 0x5A8C, 0xD8CB, + 0x5A8E, 0xD4E3, 0x5A8F, 0xD8CD, 0x5A90, 0xDD47, 0x5A92, 0xB443, 0x5A93, 0xD8CE, 0x5A94, 0xD8B6, 0x5A95, 0xD8C0, 0x5A97, 0xD8C5, + 0x5A9A, 0xB441, 0x5A9B, 0xB444, 0x5A9C, 0xD8CC, 0x5A9D, 0xD8CF, 0x5A9E, 0xD8BA, 0x5A9F, 0xD8B7, 0x5AA2, 0xD8B9, 0x5AA5, 0xD8BE, + 0x5AA6, 0xD8BC, 0x5AA7, 0xB445, 0x5AA9, 0xD8C8, 0x5AAC, 0xD8BF, 0x5AAE, 0xD8C1, 0x5AAF, 0xD8B5, 0x5AB0, 0xDCFA, 0x5AB1, 0xDCF8, + 0x5AB2, 0xB742, 0x5AB3, 0xB740, 0x5AB4, 0xDD43, 0x5AB5, 0xDCF9, 0x5AB6, 0xDD44, 0x5AB7, 0xDD40, 0x5AB8, 0xDCF7, 0x5AB9, 0xDD46, + 0x5ABA, 0xDCF6, 0x5ABB, 0xDCFD, 0x5ABC, 0xB6FE, 0x5ABD, 0xB6FD, 0x5ABE, 0xB6FC, 0x5ABF, 0xDCFB, 0x5AC0, 0xDD41, 0x5AC1, 0xB6F9, + 0x5AC2, 0xB741, 0x5AC4, 0xDCF4, 0x5AC6, 0xDCFE, 0x5AC7, 0xDCF3, 0x5AC8, 0xDCFC, 0x5AC9, 0xB6FA, 0x5ACA, 0xDD42, 0x5ACB, 0xDCF5, + 0x5ACC, 0xB6FB, 0x5ACD, 0xDD45, 0x5AD5, 0xE16E, 0x5AD6, 0xB9E2, 0x5AD7, 0xB9E1, 0x5AD8, 0xB9E3, 0x5AD9, 0xE17A, 0x5ADA, 0xE170, + 0x5ADB, 0xE176, 0x5ADC, 0xE16B, 0x5ADD, 0xE179, 0x5ADE, 0xE178, 0x5ADF, 0xE17C, 0x5AE0, 0xE175, 0x5AE1, 0xB9DE, 0x5AE2, 0xE174, + 0x5AE3, 0xB9E4, 0x5AE5, 0xE16D, 0x5AE6, 0xB9DF, 0x5AE8, 0xE17B, 0x5AE9, 0xB9E0, 0x5AEA, 0xE16F, 0x5AEB, 0xE172, 0x5AEC, 0xE177, + 0x5AED, 0xE171, 0x5AEE, 0xE16C, 0x5AF3, 0xE173, 0x5AF4, 0xE555, 0x5AF5, 0xBC61, 0x5AF6, 0xE558, 0x5AF7, 0xE557, 0x5AF8, 0xE55A, + 0x5AF9, 0xE55C, 0x5AFA, 0xF9DC, 0x5AFB, 0xBC5F, 0x5AFD, 0xE556, 0x5AFF, 0xE554, 0x5B01, 0xE55D, 0x5B02, 0xE55B, 0x5B03, 0xE559, + 0x5B05, 0xE55F, 0x5B07, 0xE55E, 0x5B08, 0xBC63, 0x5B09, 0xBC5E, 0x5B0B, 0xBC60, 0x5B0C, 0xBC62, 0x5B0F, 0xE560, 0x5B10, 0xE957, + 0x5B13, 0xE956, 0x5B14, 0xE955, 0x5B16, 0xE958, 0x5B17, 0xE951, 0x5B19, 0xE952, 0x5B1A, 0xE95A, 0x5B1B, 0xE953, 0x5B1D, 0xBEC5, + 0x5B1E, 0xE95C, 0x5B20, 0xE95B, 0x5B21, 0xE954, 0x5B23, 0xECD1, 0x5B24, 0xC0A8, 0x5B25, 0xECCF, 0x5B26, 0xECD4, 0x5B27, 0xECD3, + 0x5B28, 0xE959, 0x5B2A, 0xC0A7, 0x5B2C, 0xECD2, 0x5B2D, 0xECCE, 0x5B2E, 0xECD6, 0x5B2F, 0xECD5, 0x5B30, 0xC0A6, 0x5B32, 0xECD0, + 0x5B34, 0xBEC6, 0x5B38, 0xC254, 0x5B3C, 0xEFC1, 0x5B3D, 0xF1FA, 0x5B3E, 0xF1FB, 0x5B3F, 0xF1FC, 0x5B40, 0xC45C, 0x5B43, 0xC45D, + 0x5B45, 0xF443, 0x5B47, 0xF5C8, 0x5B48, 0xF5C7, 0x5B4B, 0xF6DB, 0x5B4C, 0xF6DC, 0x5B4D, 0xF7D5, 0x5B4E, 0xF8A7, 0x5B50, 0xA46C, + 0x5B51, 0xA46D, 0x5B53, 0xA46E, 0x5B54, 0xA4D5, 0x5B55, 0xA5A5, 0x5B56, 0xC9D3, 0x5B57, 0xA672, 0x5B58, 0xA673, 0x5B5A, 0xA7B7, + 0x5B5B, 0xA7B8, 0x5B5C, 0xA7B6, 0x5B5D, 0xA7B5, 0x5B5F, 0xA973, 0x5B62, 0xCC55, 0x5B63, 0xA975, 0x5B64, 0xA974, 0x5B65, 0xCC56, + 0x5B69, 0xABC4, 0x5B6B, 0xAE5D, 0x5B6C, 0xD165, 0x5B6E, 0xD4F0, 0x5B70, 0xB145, 0x5B71, 0xB447, 0x5B72, 0xD4EF, 0x5B73, 0xB446, + 0x5B75, 0xB9E5, 0x5B77, 0xE17D, 0x5B78, 0xBEC7, 0x5B7A, 0xC0A9, 0x5B7B, 0xECD7, 0x5B7D, 0xC45E, 0x5B7F, 0xC570, 0x5B81, 0xC972, + 0x5B83, 0xA5A6, 0x5B84, 0xC973, 0x5B85, 0xA676, 0x5B87, 0xA674, 0x5B88, 0xA675, 0x5B89, 0xA677, 0x5B8B, 0xA7BA, 0x5B8C, 0xA7B9, + 0x5B8E, 0xCABC, 0x5B8F, 0xA7BB, 0x5B92, 0xCABD, 0x5B93, 0xCC57, 0x5B95, 0xCC58, 0x5B97, 0xA976, 0x5B98, 0xA978, 0x5B99, 0xA97A, + 0x5B9A, 0xA977, 0x5B9B, 0xA97B, 0x5B9C, 0xA979, 0x5BA2, 0xABC8, 0x5BA3, 0xABC5, 0x5BA4, 0xABC7, 0x5BA5, 0xABC9, 0x5BA6, 0xABC6, + 0x5BA7, 0xD166, 0x5BA8, 0xCE77, 0x5BAC, 0xD168, 0x5BAD, 0xD167, 0x5BAE, 0xAE63, 0x5BB0, 0xAE5F, 0x5BB3, 0xAE60, 0x5BB4, 0xAE62, + 0x5BB5, 0xAE64, 0x5BB6, 0xAE61, 0x5BB8, 0xAE66, 0x5BB9, 0xAE65, 0x5BBF, 0xB14A, 0x5BC0, 0xD4F2, 0x5BC1, 0xD4F1, 0x5BC2, 0xB149, + 0x5BC4, 0xB148, 0x5BC5, 0xB147, 0x5BC6, 0xB14B, 0x5BC7, 0xB146, 0x5BCA, 0xD8D5, 0x5BCB, 0xD8D2, 0x5BCC, 0xB449, 0x5BCD, 0xD8D1, + 0x5BCE, 0xD8D6, 0x5BD0, 0xB44B, 0x5BD1, 0xD8D4, 0x5BD2, 0xB448, 0x5BD3, 0xB44A, 0x5BD4, 0xD8D3, 0x5BD6, 0xDD48, 0x5BD8, 0xDD49, + 0x5BD9, 0xDD4A, 0x5BDE, 0xB9E6, 0x5BDF, 0xB9EE, 0x5BE0, 0xE17E, 0x5BE1, 0xB9E8, 0x5BE2, 0xB9EC, 0x5BE3, 0xE1A1, 0x5BE4, 0xB9ED, + 0x5BE5, 0xB9E9, 0x5BE6, 0xB9EA, 0x5BE7, 0xB9E7, 0x5BE8, 0xB9EB, 0x5BE9, 0xBC66, 0x5BEA, 0xD8D0, 0x5BEB, 0xBC67, 0x5BEC, 0xBC65, + 0x5BEE, 0xBC64, 0x5BEF, 0xE95D, 0x5BF0, 0xBEC8, 0x5BF1, 0xECD8, 0x5BF2, 0xECD9, 0x5BF5, 0xC364, 0x5BF6, 0xC45F, 0x5BF8, 0xA46F, + 0x5BFA, 0xA678, 0x5C01, 0xABCA, 0x5C03, 0xD169, 0x5C04, 0xAE67, 0x5C07, 0xB14E, 0x5C08, 0xB14D, 0x5C09, 0xB14C, 0x5C0A, 0xB44C, + 0x5C0B, 0xB44D, 0x5C0C, 0xD8D7, 0x5C0D, 0xB9EF, 0x5C0E, 0xBEC9, 0x5C0F, 0xA470, 0x5C10, 0xC95C, 0x5C11, 0xA4D6, 0x5C12, 0xC974, + 0x5C15, 0xC9D4, 0x5C16, 0xA679, 0x5C1A, 0xA97C, 0x5C1F, 0xDD4B, 0x5C22, 0xA471, 0x5C24, 0xA4D7, 0x5C25, 0xC9D5, 0x5C28, 0xCABE, + 0x5C2A, 0xCABF, 0x5C2C, 0xA7BC, 0x5C30, 0xD8D8, 0x5C31, 0xB44E, 0x5C33, 0xDD4C, 0x5C37, 0xC0AA, 0x5C38, 0xA472, 0x5C39, 0xA4A8, + 0x5C3A, 0xA4D8, 0x5C3B, 0xC975, 0x5C3C, 0xA5A7, 0x5C3E, 0xA7C0, 0x5C3F, 0xA7BF, 0x5C40, 0xA7BD, 0x5C41, 0xA7BE, 0x5C44, 0xCC59, + 0x5C45, 0xA97E, 0x5C46, 0xA9A1, 0x5C47, 0xCC5A, 0x5C48, 0xA97D, 0x5C4B, 0xABCE, 0x5C4C, 0xCE78, 0x5C4D, 0xABCD, 0x5C4E, 0xABCB, + 0x5C4F, 0xABCC, 0x5C50, 0xAE6A, 0x5C51, 0xAE68, 0x5C54, 0xD16B, 0x5C55, 0xAE69, 0x5C56, 0xD16A, 0x5C58, 0xAE5E, 0x5C59, 0xD4F3, + 0x5C5C, 0xB150, 0x5C5D, 0xB151, 0x5C60, 0xB14F, 0x5C62, 0xB9F0, 0x5C63, 0xE1A2, 0x5C64, 0xBC68, 0x5C65, 0xBC69, 0x5C67, 0xE561, + 0x5C68, 0xC0AB, 0x5C69, 0xEFC2, 0x5C6A, 0xEFC3, 0x5C6C, 0xC4DD, 0x5C6D, 0xF8A8, 0x5C6E, 0xC94B, 0x5C6F, 0xA4D9, 0x5C71, 0xA473, + 0x5C73, 0xC977, 0x5C74, 0xC976, 0x5C79, 0xA67A, 0x5C7A, 0xC9D7, 0x5C7B, 0xC9D8, 0x5C7C, 0xC9D6, 0x5C7E, 0xC9D9, 0x5C86, 0xCAC7, + 0x5C88, 0xCAC2, 0x5C89, 0xCAC4, 0x5C8A, 0xCAC6, 0x5C8B, 0xCAC3, 0x5C8C, 0xA7C4, 0x5C8D, 0xCAC0, 0x5C8F, 0xCAC1, 0x5C90, 0xA7C1, + 0x5C91, 0xA7C2, 0x5C92, 0xCAC5, 0x5C93, 0xCAC8, 0x5C94, 0xA7C3, 0x5C95, 0xCAC9, 0x5C9D, 0xCC68, 0x5C9F, 0xCC62, 0x5CA0, 0xCC5D, + 0x5CA1, 0xA9A3, 0x5CA2, 0xCC65, 0x5CA3, 0xCC63, 0x5CA4, 0xCC5C, 0x5CA5, 0xCC69, 0x5CA6, 0xCC6C, 0x5CA7, 0xCC67, 0x5CA8, 0xCC60, + 0x5CA9, 0xA9A5, 0x5CAA, 0xCC66, 0x5CAB, 0xA9A6, 0x5CAC, 0xCC61, 0x5CAD, 0xCC64, 0x5CAE, 0xCC5B, 0x5CAF, 0xCC5F, 0x5CB0, 0xCC6B, + 0x5CB1, 0xA9A7, 0x5CB3, 0xA9A8, 0x5CB5, 0xCC5E, 0x5CB6, 0xCC6A, 0x5CB7, 0xA9A2, 0x5CB8, 0xA9A4, 0x5CC6, 0xCEAB, 0x5CC7, 0xCEA4, + 0x5CC8, 0xCEAA, 0x5CC9, 0xCEA3, 0x5CCA, 0xCEA5, 0x5CCB, 0xCE7D, 0x5CCC, 0xCE7B, 0x5CCE, 0xCEAC, 0x5CCF, 0xCEA9, 0x5CD0, 0xCE79, + 0x5CD2, 0xABD0, 0x5CD3, 0xCEA7, 0x5CD4, 0xCEA8, 0x5CD6, 0xCEA6, 0x5CD7, 0xCE7C, 0x5CD8, 0xCE7A, 0x5CD9, 0xABCF, 0x5CDA, 0xCEA2, + 0x5CDB, 0xCE7E, 0x5CDE, 0xCEA1, 0x5CDF, 0xCEAD, 0x5CE8, 0xAE6F, 0x5CEA, 0xAE6E, 0x5CEC, 0xD16C, 0x5CED, 0xAE6B, 0x5CEE, 0xD16E, + 0x5CF0, 0xAE70, 0x5CF1, 0xD16F, 0x5CF4, 0xAE73, 0x5CF6, 0xAE71, 0x5CF7, 0xD170, 0x5CF8, 0xCEAE, 0x5CF9, 0xD172, 0x5CFB, 0xAE6D, + 0x5CFD, 0xAE6C, 0x5CFF, 0xD16D, 0x5D00, 0xD171, 0x5D01, 0xAE72, 0x5D06, 0xB153, 0x5D07, 0xB152, 0x5D0B, 0xD4F5, 0x5D0C, 0xD4F9, + 0x5D0D, 0xD4FB, 0x5D0E, 0xB154, 0x5D0F, 0xD4FE, 0x5D11, 0xB158, 0x5D12, 0xD541, 0x5D14, 0xB15A, 0x5D16, 0xB156, 0x5D17, 0xB15E, + 0x5D19, 0xB15B, 0x5D1A, 0xD4F7, 0x5D1B, 0xB155, 0x5D1D, 0xD4F6, 0x5D1E, 0xD4F4, 0x5D1F, 0xD543, 0x5D20, 0xD4F8, 0x5D22, 0xB157, + 0x5D23, 0xD542, 0x5D24, 0xB15C, 0x5D25, 0xD4FD, 0x5D26, 0xD4FC, 0x5D27, 0xB15D, 0x5D28, 0xD4FA, 0x5D29, 0xB159, 0x5D2E, 0xD544, + 0x5D30, 0xD540, 0x5D31, 0xD8E7, 0x5D32, 0xD8EE, 0x5D33, 0xD8E3, 0x5D34, 0xB451, 0x5D35, 0xD8DF, 0x5D36, 0xD8EF, 0x5D37, 0xD8D9, + 0x5D38, 0xD8EC, 0x5D39, 0xD8EA, 0x5D3A, 0xD8E4, 0x5D3C, 0xD8ED, 0x5D3D, 0xD8E6, 0x5D3F, 0xD8DE, 0x5D40, 0xD8F0, 0x5D41, 0xD8DC, + 0x5D42, 0xD8E9, 0x5D43, 0xD8DA, 0x5D45, 0xD8F1, 0x5D47, 0xB452, 0x5D49, 0xD8EB, 0x5D4A, 0xDD4F, 0x5D4B, 0xD8DD, 0x5D4C, 0xB44F, + 0x5D4E, 0xD8E1, 0x5D50, 0xB450, 0x5D51, 0xD8E0, 0x5D52, 0xD8E5, 0x5D55, 0xD8E2, 0x5D59, 0xD8E8, 0x5D5E, 0xDD53, 0x5D62, 0xDD56, + 0x5D63, 0xDD4E, 0x5D65, 0xDD50, 0x5D67, 0xDD55, 0x5D68, 0xDD54, 0x5D69, 0xB743, 0x5D6B, 0xD8DB, 0x5D6C, 0xDD52, 0x5D6F, 0xB744, + 0x5D71, 0xDD4D, 0x5D72, 0xDD51, 0x5D77, 0xE1A9, 0x5D79, 0xE1B0, 0x5D7A, 0xE1A7, 0x5D7C, 0xE1AE, 0x5D7D, 0xE1A5, 0x5D7E, 0xE1AD, + 0x5D7F, 0xE1B1, 0x5D80, 0xE1A4, 0x5D81, 0xE1A8, 0x5D82, 0xE1A3, 0x5D84, 0xB9F1, 0x5D86, 0xE1A6, 0x5D87, 0xB9F2, 0x5D88, 0xE1AC, + 0x5D89, 0xE1AB, 0x5D8A, 0xE1AA, 0x5D8D, 0xE1AF, 0x5D92, 0xE565, 0x5D93, 0xE567, 0x5D94, 0xBC6B, 0x5D95, 0xE568, 0x5D97, 0xE563, + 0x5D99, 0xE562, 0x5D9A, 0xE56C, 0x5D9C, 0xE56A, 0x5D9D, 0xBC6A, 0x5D9E, 0xE56D, 0x5D9F, 0xE564, 0x5DA0, 0xE569, 0x5DA1, 0xE56B, + 0x5DA2, 0xE566, 0x5DA7, 0xE961, 0x5DA8, 0xE966, 0x5DA9, 0xE960, 0x5DAA, 0xE965, 0x5DAC, 0xE95E, 0x5DAD, 0xE968, 0x5DAE, 0xE964, + 0x5DAF, 0xE969, 0x5DB0, 0xE963, 0x5DB1, 0xE95F, 0x5DB2, 0xE967, 0x5DB4, 0xE96A, 0x5DB5, 0xE962, 0x5DB7, 0xECDA, 0x5DB8, 0xC0AF, + 0x5DBA, 0xC0AD, 0x5DBC, 0xC0AC, 0x5DBD, 0xC0AE, 0x5DC0, 0xEFC4, 0x5DC2, 0xF172, 0x5DC3, 0xF1FD, 0x5DC6, 0xF444, 0x5DC7, 0xF445, + 0x5DC9, 0xC460, 0x5DCB, 0xF5C9, 0x5DCD, 0xC4DE, 0x5DCF, 0xF5CA, 0x5DD1, 0xF6DE, 0x5DD2, 0xC572, 0x5DD4, 0xC571, 0x5DD5, 0xF6DD, + 0x5DD6, 0xC5C9, 0x5DD8, 0xF7D6, 0x5DDD, 0xA474, 0x5DDE, 0xA67B, 0x5DDF, 0xC9DA, 0x5DE0, 0xCACA, 0x5DE1, 0xA8B5, 0x5DE2, 0xB15F, + 0x5DE5, 0xA475, 0x5DE6, 0xA5AA, 0x5DE7, 0xA5A9, 0x5DE8, 0xA5A8, 0x5DEB, 0xA7C5, 0x5DEE, 0xAE74, 0x5DF0, 0xDD57, 0x5DF1, 0xA476, + 0x5DF2, 0xA477, 0x5DF3, 0xA478, 0x5DF4, 0xA4DA, 0x5DF7, 0xABD1, 0x5DF9, 0xCEAF, 0x5DFD, 0xB453, 0x5DFE, 0xA479, 0x5DFF, 0xC95D, + 0x5E02, 0xA5AB, 0x5E03, 0xA5AC, 0x5E04, 0xC978, 0x5E06, 0xA67C, 0x5E0A, 0xCACB, 0x5E0C, 0xA7C6, 0x5E0E, 0xCACC, 0x5E11, 0xA9AE, + 0x5E14, 0xCC6E, 0x5E15, 0xA9AC, 0x5E16, 0xA9AB, 0x5E17, 0xCC6D, 0x5E18, 0xA9A9, 0x5E19, 0xCC6F, 0x5E1A, 0xA9AA, 0x5E1B, 0xA9AD, + 0x5E1D, 0xABD2, 0x5E1F, 0xABD4, 0x5E20, 0xCEB3, 0x5E21, 0xCEB0, 0x5E22, 0xCEB1, 0x5E23, 0xCEB2, 0x5E24, 0xCEB4, 0x5E25, 0xABD3, + 0x5E28, 0xD174, 0x5E29, 0xD173, 0x5E2B, 0xAE76, 0x5E2D, 0xAE75, 0x5E33, 0xB162, 0x5E34, 0xD546, 0x5E36, 0xB161, 0x5E37, 0xB163, + 0x5E38, 0xB160, 0x5E3D, 0xB455, 0x5E3E, 0xD545, 0x5E40, 0xB456, 0x5E41, 0xD8F3, 0x5E43, 0xB457, 0x5E44, 0xD8F2, 0x5E45, 0xB454, + 0x5E4A, 0xDD5A, 0x5E4B, 0xDD5C, 0x5E4C, 0xB745, 0x5E4D, 0xDD5B, 0x5E4E, 0xDD59, 0x5E4F, 0xDD58, 0x5E53, 0xE1B4, 0x5E54, 0xB9F7, + 0x5E55, 0xB9F5, 0x5E57, 0xB9F6, 0x5E58, 0xE1B2, 0x5E59, 0xE1B3, 0x5E5B, 0xB9F3, 0x5E5C, 0xE571, 0x5E5D, 0xE56F, 0x5E5F, 0xBC6D, + 0x5E60, 0xE570, 0x5E61, 0xBC6E, 0x5E62, 0xBC6C, 0x5E63, 0xB9F4, 0x5E66, 0xE96D, 0x5E67, 0xE96B, 0x5E68, 0xE96C, 0x5E69, 0xE56E, + 0x5E6A, 0xECDC, 0x5E6B, 0xC0B0, 0x5E6C, 0xECDB, 0x5E6D, 0xEFC5, 0x5E6E, 0xEFC6, 0x5E6F, 0xE96E, 0x5E70, 0xF1FE, 0x5E72, 0xA47A, + 0x5E73, 0xA5AD, 0x5E74, 0xA67E, 0x5E75, 0xC9DB, 0x5E76, 0xA67D, 0x5E78, 0xA9AF, 0x5E79, 0xB746, 0x5E7B, 0xA4DB, 0x5E7C, 0xA5AE, + 0x5E7D, 0xABD5, 0x5E7E, 0xB458, 0x5E80, 0xC979, 0x5E82, 0xC97A, 0x5E84, 0xC9DC, 0x5E87, 0xA7C8, 0x5E88, 0xCAD0, 0x5E89, 0xCACE, + 0x5E8A, 0xA7C9, 0x5E8B, 0xCACD, 0x5E8C, 0xCACF, 0x5E8D, 0xCAD1, 0x5E8F, 0xA7C7, 0x5E95, 0xA9B3, 0x5E96, 0xA9B4, 0x5E97, 0xA9B1, + 0x5E9A, 0xA9B0, 0x5E9B, 0xCEB8, 0x5E9C, 0xA9B2, 0x5EA0, 0xABD6, 0x5EA2, 0xCEB7, 0x5EA3, 0xCEB9, 0x5EA4, 0xCEB6, 0x5EA5, 0xCEBA, + 0x5EA6, 0xABD7, 0x5EA7, 0xAE79, 0x5EA8, 0xD175, 0x5EAA, 0xD177, 0x5EAB, 0xAE77, 0x5EAC, 0xD178, 0x5EAD, 0xAE78, 0x5EAE, 0xD176, + 0x5EB0, 0xCEB5, 0x5EB1, 0xD547, 0x5EB2, 0xD54A, 0x5EB3, 0xD54B, 0x5EB4, 0xD548, 0x5EB5, 0xB167, 0x5EB6, 0xB166, 0x5EB7, 0xB164, + 0x5EB8, 0xB165, 0x5EB9, 0xD549, 0x5EBE, 0xB168, 0x5EC1, 0xB45A, 0x5EC2, 0xB45B, 0x5EC4, 0xB45C, 0x5EC5, 0xDD5D, 0x5EC6, 0xDD5F, + 0x5EC7, 0xDD61, 0x5EC8, 0xB748, 0x5EC9, 0xB747, 0x5ECA, 0xB459, 0x5ECB, 0xDD60, 0x5ECC, 0xDD5E, 0x5ECE, 0xE1B8, 0x5ED1, 0xE1B6, + 0x5ED2, 0xE1BC, 0x5ED3, 0xB9F8, 0x5ED4, 0xE1BD, 0x5ED5, 0xE1BA, 0x5ED6, 0xB9F9, 0x5ED7, 0xE1B7, 0x5ED8, 0xE1B5, 0x5ED9, 0xE1BB, + 0x5EDA, 0xBC70, 0x5EDB, 0xE573, 0x5EDC, 0xE1B9, 0x5EDD, 0xBC72, 0x5EDE, 0xE574, 0x5EDF, 0xBC71, 0x5EE0, 0xBC74, 0x5EE1, 0xE575, + 0x5EE2, 0xBC6F, 0x5EE3, 0xBC73, 0x5EE5, 0xE973, 0x5EE6, 0xE971, 0x5EE7, 0xE970, 0x5EE8, 0xE972, 0x5EE9, 0xE96F, 0x5EEC, 0xC366, + 0x5EEE, 0xF446, 0x5EEF, 0xF447, 0x5EF1, 0xF5CB, 0x5EF2, 0xF6DF, 0x5EF3, 0xC655, 0x5EF6, 0xA9B5, 0x5EF7, 0xA7CA, 0x5EFA, 0xABD8, + 0x5EFE, 0xA47B, 0x5EFF, 0xA4DC, 0x5F01, 0xA5AF, 0x5F02, 0xC9DD, 0x5F04, 0xA7CB, 0x5F05, 0xCAD2, 0x5F07, 0xCEBB, 0x5F08, 0xABD9, + 0x5F0A, 0xB9FA, 0x5F0B, 0xA47C, 0x5F0F, 0xA6A1, 0x5F12, 0xB749, 0x5F13, 0xA47D, 0x5F14, 0xA4DD, 0x5F15, 0xA4DE, 0x5F17, 0xA5B1, + 0x5F18, 0xA5B0, 0x5F1A, 0xC9DE, 0x5F1B, 0xA6A2, 0x5F1D, 0xCAD3, 0x5F1F, 0xA7CC, 0x5F22, 0xCC71, 0x5F23, 0xCC72, 0x5F24, 0xCC73, + 0x5F26, 0xA9B6, 0x5F27, 0xA9B7, 0x5F28, 0xCC70, 0x5F29, 0xA9B8, 0x5F2D, 0xABDA, 0x5F2E, 0xCEBC, 0x5F30, 0xD17A, 0x5F31, 0xAE7A, + 0x5F33, 0xD179, 0x5F35, 0xB169, 0x5F36, 0xD54C, 0x5F37, 0xB16A, 0x5F38, 0xD54D, 0x5F3C, 0xB45D, 0x5F40, 0xDD62, 0x5F43, 0xE1BF, + 0x5F44, 0xE1BE, 0x5F46, 0xB9FB, 0x5F48, 0xBC75, 0x5F49, 0xE576, 0x5F4A, 0xBECA, 0x5F4B, 0xE974, 0x5F4C, 0xC0B1, 0x5F4E, 0xC573, + 0x5F4F, 0xF7D8, 0x5F54, 0xCC74, 0x5F56, 0xCEBD, 0x5F57, 0xB16B, 0x5F58, 0xD8F4, 0x5F59, 0xB74A, 0x5F5D, 0xC255, 0x5F62, 0xA7CE, + 0x5F64, 0xA7CD, 0x5F65, 0xABDB, 0x5F67, 0xD17B, 0x5F69, 0xB16D, 0x5F6A, 0xB343, 0x5F6B, 0xB16E, 0x5F6C, 0xB16C, 0x5F6D, 0xB45E, + 0x5F6F, 0xE1C0, 0x5F70, 0xB9FC, 0x5F71, 0xBC76, 0x5F73, 0xC94C, 0x5F74, 0xC9DF, 0x5F76, 0xCAD5, 0x5F77, 0xA7CF, 0x5F78, 0xCAD4, + 0x5F79, 0xA7D0, 0x5F7C, 0xA9BC, 0x5F7D, 0xCC77, 0x5F7E, 0xCC76, 0x5F7F, 0xA9BB, 0x5F80, 0xA9B9, 0x5F81, 0xA9BA, 0x5F82, 0xCC75, + 0x5F85, 0xABDD, 0x5F86, 0xCEBE, 0x5F87, 0xABE0, 0x5F88, 0xABDC, 0x5F89, 0xABE2, 0x5F8A, 0xABDE, 0x5F8B, 0xABDF, 0x5F8C, 0xABE1, + 0x5F90, 0xAE7D, 0x5F91, 0xAE7C, 0x5F92, 0xAE7B, 0x5F96, 0xD54F, 0x5F97, 0xB16F, 0x5F98, 0xB172, 0x5F99, 0xB170, 0x5F9B, 0xD54E, + 0x5F9C, 0xB175, 0x5F9E, 0xB171, 0x5F9F, 0xD550, 0x5FA0, 0xB174, 0x5FA1, 0xB173, 0x5FA5, 0xD8F6, 0x5FA6, 0xD8F5, 0x5FA8, 0xB461, + 0x5FA9, 0xB45F, 0x5FAA, 0xB460, 0x5FAB, 0xD8F7, 0x5FAC, 0xB74B, 0x5FAD, 0xDD64, 0x5FAE, 0xB74C, 0x5FAF, 0xDD63, 0x5FB2, 0xE577, + 0x5FB5, 0xBC78, 0x5FB6, 0xE1C1, 0x5FB7, 0xBC77, 0x5FB9, 0xB9FD, 0x5FBB, 0xECDE, 0x5FBC, 0xE975, 0x5FBD, 0xC0B2, 0x5FBE, 0xECDD, + 0x5FBF, 0xF240, 0x5FC0, 0xF448, 0x5FC1, 0xF449, 0x5FC3, 0xA4DF, 0x5FC5, 0xA5B2, 0x5FC9, 0xC97B, 0x5FCC, 0xA7D2, 0x5FCD, 0xA7D4, + 0x5FCF, 0xC9E2, 0x5FD0, 0xCAD8, 0x5FD1, 0xCAD7, 0x5FD2, 0xCAD6, 0x5FD4, 0xC9E1, 0x5FD5, 0xC9E0, 0x5FD6, 0xA6A4, 0x5FD7, 0xA7D3, + 0x5FD8, 0xA7D1, 0x5FD9, 0xA6A3, 0x5FDD, 0xA9BD, 0x5FDE, 0xCC78, 0x5FE0, 0xA9BE, 0x5FE1, 0xCADD, 0x5FE3, 0xCADF, 0x5FE4, 0xCADE, + 0x5FE5, 0xCC79, 0x5FE8, 0xCADA, 0x5FEA, 0xA7D8, 0x5FEB, 0xA7D6, 0x5FED, 0xCAD9, 0x5FEE, 0xCADB, 0x5FEF, 0xCAE1, 0x5FF1, 0xA7D5, + 0x5FF3, 0xCADC, 0x5FF4, 0xCAE5, 0x5FF5, 0xA9C0, 0x5FF7, 0xCAE2, 0x5FF8, 0xA7D7, 0x5FFA, 0xCAE0, 0x5FFB, 0xCAE3, 0x5FFD, 0xA9BF, + 0x5FFF, 0xA9C1, 0x6000, 0xCAE4, 0x6009, 0xCCAF, 0x600A, 0xCCA2, 0x600B, 0xCC7E, 0x600C, 0xCCAE, 0x600D, 0xCCA9, 0x600E, 0xABE7, + 0x600F, 0xA9C2, 0x6010, 0xCCAA, 0x6011, 0xCCAD, 0x6012, 0xABE3, 0x6013, 0xCCAC, 0x6014, 0xA9C3, 0x6015, 0xA9C8, 0x6016, 0xA9C6, + 0x6017, 0xCCA3, 0x6019, 0xCC7C, 0x601A, 0xCCA5, 0x601B, 0xA9CD, 0x601C, 0xCCB0, 0x601D, 0xABE4, 0x601E, 0xCCA6, 0x6020, 0xABE5, + 0x6021, 0xA9C9, 0x6022, 0xCCA8, 0x6024, 0xCECD, 0x6025, 0xABE6, 0x6026, 0xCC7B, 0x6027, 0xA9CA, 0x6028, 0xABE8, 0x6029, 0xA9CB, + 0x602A, 0xA9C7, 0x602B, 0xA9CC, 0x602C, 0xCCA7, 0x602D, 0xCC7A, 0x602E, 0xCCAB, 0x602F, 0xA9C4, 0x6032, 0xCC7D, 0x6033, 0xCCA4, + 0x6034, 0xCCA1, 0x6035, 0xA9C5, 0x6037, 0xCEBF, 0x6039, 0xCEC0, 0x6040, 0xCECA, 0x6041, 0xD1A1, 0x6042, 0xCECB, 0x6043, 0xABEE, + 0x6044, 0xCECE, 0x6045, 0xCEC4, 0x6046, 0xABED, 0x6047, 0xCEC6, 0x6049, 0xCEC7, 0x604C, 0xCEC9, 0x604D, 0xABE9, 0x6050, 0xAEA3, + 0x6052, 0xF9DA, 0x6053, 0xCEC5, 0x6054, 0xCEC1, 0x6055, 0xAEA4, 0x6058, 0xCECF, 0x6059, 0xAE7E, 0x605A, 0xD17D, 0x605B, 0xCEC8, + 0x605D, 0xD17C, 0x605E, 0xCEC3, 0x605F, 0xCECC, 0x6062, 0xABEC, 0x6063, 0xAEA1, 0x6064, 0xABF2, 0x6065, 0xAEA2, 0x6066, 0xCED0, + 0x6067, 0xD17E, 0x6068, 0xABEB, 0x6069, 0xAEA6, 0x606A, 0xABF1, 0x606B, 0xABF0, 0x606C, 0xABEF, 0x606D, 0xAEA5, 0x606E, 0xCED1, + 0x606F, 0xAEA7, 0x6070, 0xABEA, 0x6072, 0xCEC2, 0x607F, 0xB176, 0x6080, 0xD1A4, 0x6081, 0xD1A6, 0x6083, 0xD1A8, 0x6084, 0xAEA8, + 0x6085, 0xAEAE, 0x6086, 0xD553, 0x6087, 0xD1AC, 0x6088, 0xD1A3, 0x6089, 0xB178, 0x608A, 0xD551, 0x608C, 0xAEAD, 0x608D, 0xAEAB, + 0x608E, 0xD1AE, 0x6090, 0xD552, 0x6092, 0xD1A5, 0x6094, 0xAEAC, 0x6095, 0xD1A9, 0x6096, 0xAEAF, 0x6097, 0xD1AB, 0x609A, 0xAEAA, + 0x609B, 0xD1AA, 0x609C, 0xD1AD, 0x609D, 0xD1A7, 0x609F, 0xAEA9, 0x60A0, 0xB179, 0x60A2, 0xD1A2, 0x60A3, 0xB177, 0x60A8, 0xB17A, + 0x60B0, 0xD555, 0x60B1, 0xD55E, 0x60B2, 0xB464, 0x60B4, 0xB17C, 0x60B5, 0xB1A3, 0x60B6, 0xB465, 0x60B7, 0xD560, 0x60B8, 0xB1AA, + 0x60B9, 0xD8F9, 0x60BA, 0xD556, 0x60BB, 0xB1A2, 0x60BC, 0xB1A5, 0x60BD, 0xB17E, 0x60BE, 0xD554, 0x60BF, 0xD562, 0x60C0, 0xD565, + 0x60C1, 0xD949, 0x60C3, 0xD563, 0x60C4, 0xD8FD, 0x60C5, 0xB1A1, 0x60C6, 0xB1A8, 0x60C7, 0xB1AC, 0x60C8, 0xD55D, 0x60C9, 0xD8F8, + 0x60CA, 0xD561, 0x60CB, 0xB17B, 0x60CC, 0xD8FA, 0x60CD, 0xD564, 0x60CE, 0xD8FC, 0x60CF, 0xD559, 0x60D1, 0xB462, 0x60D3, 0xD557, + 0x60D4, 0xD558, 0x60D5, 0xB1A7, 0x60D8, 0xB1A6, 0x60D9, 0xD55B, 0x60DA, 0xB1AB, 0x60DB, 0xD55F, 0x60DC, 0xB1A4, 0x60DD, 0xD55C, + 0x60DF, 0xB1A9, 0x60E0, 0xB466, 0x60E1, 0xB463, 0x60E2, 0xD8FB, 0x60E4, 0xD55A, 0x60E6, 0xB17D, 0x60F0, 0xB46B, 0x60F1, 0xB46F, + 0x60F2, 0xD940, 0x60F3, 0xB751, 0x60F4, 0xB46D, 0x60F5, 0xD944, 0x60F6, 0xB471, 0x60F7, 0xDD65, 0x60F8, 0xD946, 0x60F9, 0xB753, + 0x60FA, 0xB469, 0x60FB, 0xB46C, 0x60FC, 0xD947, 0x60FE, 0xD948, 0x60FF, 0xD94E, 0x6100, 0xB473, 0x6101, 0xB754, 0x6103, 0xD94A, + 0x6104, 0xD94F, 0x6105, 0xD943, 0x6106, 0xB75E, 0x6108, 0xB755, 0x6109, 0xB472, 0x610A, 0xD941, 0x610B, 0xD950, 0x610D, 0xB75D, + 0x610E, 0xB470, 0x610F, 0xB74E, 0x6110, 0xD94D, 0x6112, 0xB474, 0x6113, 0xD945, 0x6114, 0xD8FE, 0x6115, 0xB46A, 0x6116, 0xD942, + 0x6118, 0xD94B, 0x611A, 0xB74D, 0x611B, 0xB752, 0x611C, 0xB467, 0x611D, 0xD94C, 0x611F, 0xB750, 0x6123, 0xB468, 0x6127, 0xB75C, + 0x6128, 0xE1C3, 0x6129, 0xDD70, 0x612B, 0xDD68, 0x612C, 0xE1C2, 0x612E, 0xDD6C, 0x612F, 0xDD6E, 0x6132, 0xDD6B, 0x6134, 0xB75B, + 0x6136, 0xDD6A, 0x6137, 0xB75F, 0x613B, 0xE1D2, 0x613E, 0xB75A, 0x613F, 0xBA40, 0x6140, 0xDD71, 0x6141, 0xE1C4, 0x6144, 0xB758, + 0x6145, 0xDD69, 0x6146, 0xDD6D, 0x6147, 0xB9FE, 0x6148, 0xB74F, 0x6149, 0xDD66, 0x614A, 0xDD67, 0x614B, 0xBA41, 0x614C, 0xB757, + 0x614D, 0xB759, 0x614E, 0xB756, 0x614F, 0xDD6F, 0x6152, 0xE1C8, 0x6153, 0xE1C9, 0x6154, 0xE1CE, 0x6155, 0xBC7D, 0x6156, 0xE1D5, + 0x6158, 0xBA47, 0x615A, 0xBA46, 0x615B, 0xE1D0, 0x615D, 0xBC7C, 0x615E, 0xE1C5, 0x615F, 0xBA45, 0x6161, 0xE1D4, 0x6162, 0xBA43, + 0x6163, 0xBA44, 0x6165, 0xE1D1, 0x6166, 0xE5AA, 0x6167, 0xBC7A, 0x6168, 0xB46E, 0x616A, 0xE1D3, 0x616B, 0xBCA3, 0x616C, 0xE1CB, + 0x616E, 0xBC7B, 0x6170, 0xBCA2, 0x6171, 0xE1C6, 0x6172, 0xE1CA, 0x6173, 0xE1C7, 0x6174, 0xE1CD, 0x6175, 0xBA48, 0x6176, 0xBC79, + 0x6177, 0xBA42, 0x6179, 0xE57A, 0x617A, 0xE1CF, 0x617C, 0xBCA1, 0x617E, 0xBCA4, 0x6180, 0xE1CC, 0x6182, 0xBC7E, 0x6183, 0xE579, + 0x6189, 0xE57E, 0x618A, 0xBECE, 0x618B, 0xE578, 0x618C, 0xE9A3, 0x618D, 0xE5A9, 0x618E, 0xBCA8, 0x6190, 0xBCA6, 0x6191, 0xBECC, + 0x6192, 0xE5A6, 0x6193, 0xE5A2, 0x6194, 0xBCAC, 0x6196, 0xE978, 0x619A, 0xBCAA, 0x619B, 0xE5A1, 0x619D, 0xE976, 0x619F, 0xE5A5, + 0x61A1, 0xE5A8, 0x61A2, 0xE57D, 0x61A4, 0xBCAB, 0x61A7, 0xBCA5, 0x61A8, 0xE977, 0x61A9, 0xBECD, 0x61AA, 0xE5A7, 0x61AB, 0xBCA7, + 0x61AC, 0xBCA9, 0x61AD, 0xE5A4, 0x61AE, 0xBCAD, 0x61AF, 0xE5A3, 0x61B0, 0xE57C, 0x61B1, 0xE57B, 0x61B2, 0xBECB, 0x61B3, 0xE5AB, + 0x61B4, 0xE97A, 0x61B5, 0xECE0, 0x61B6, 0xBED0, 0x61B8, 0xE9A2, 0x61BA, 0xE97E, 0x61BC, 0xECE1, 0x61BE, 0xBED1, 0x61BF, 0xE9A1, + 0x61C1, 0xE97C, 0x61C2, 0xC0B4, 0x61C3, 0xECDF, 0x61C5, 0xE979, 0x61C6, 0xE97B, 0x61C7, 0xC0B5, 0x61C8, 0xBED3, 0x61C9, 0xC0B3, + 0x61CA, 0xBED2, 0x61CB, 0xC0B7, 0x61CC, 0xE97D, 0x61CD, 0xBECF, 0x61D6, 0xEFCF, 0x61D8, 0xEFC7, 0x61DE, 0xECE7, 0x61DF, 0xEFC8, + 0x61E0, 0xECE3, 0x61E3, 0xC256, 0x61E4, 0xECE5, 0x61E5, 0xECE4, 0x61E6, 0xC0B6, 0x61E7, 0xECE2, 0x61E8, 0xECE6, 0x61E9, 0xEFD0, + 0x61EA, 0xEFCC, 0x61EB, 0xEFCE, 0x61ED, 0xEFC9, 0x61EE, 0xEFCA, 0x61F0, 0xEFCD, 0x61F1, 0xEFCB, 0x61F2, 0xC367, 0x61F5, 0xC36A, + 0x61F6, 0xC369, 0x61F7, 0xC368, 0x61F8, 0xC461, 0x61F9, 0xF44A, 0x61FA, 0xC462, 0x61FB, 0xF241, 0x61FC, 0xC4DF, 0x61FD, 0xF5CC, + 0x61FE, 0xC4E0, 0x61FF, 0xC574, 0x6200, 0xC5CA, 0x6201, 0xF7D9, 0x6203, 0xF7DA, 0x6204, 0xF7DB, 0x6207, 0xF9BA, 0x6208, 0xA4E0, + 0x6209, 0xC97C, 0x620A, 0xA5B3, 0x620C, 0xA6A6, 0x620D, 0xA6A7, 0x620E, 0xA6A5, 0x6210, 0xA6A8, 0x6211, 0xA7DA, 0x6212, 0xA7D9, + 0x6214, 0xCCB1, 0x6215, 0xA9CF, 0x6216, 0xA9CE, 0x6219, 0xD1AF, 0x621A, 0xB1AD, 0x621B, 0xB1AE, 0x621F, 0xB475, 0x6220, 0xDD72, + 0x6221, 0xB760, 0x6222, 0xB761, 0x6223, 0xDD74, 0x6224, 0xDD76, 0x6225, 0xDD75, 0x6227, 0xE1D7, 0x6229, 0xE1D6, 0x622A, 0xBA49, + 0x622B, 0xE1D8, 0x622D, 0xE5AC, 0x622E, 0xBCAE, 0x6230, 0xBED4, 0x6232, 0xC0B8, 0x6233, 0xC257, 0x6234, 0xC0B9, 0x6236, 0xA4E1, + 0x623A, 0xCAE6, 0x623D, 0xCCB2, 0x623E, 0xA9D1, 0x623F, 0xA9D0, 0x6240, 0xA9D2, 0x6241, 0xABF3, 0x6242, 0xCED2, 0x6243, 0xCED3, + 0x6246, 0xD1B0, 0x6247, 0xAEB0, 0x6248, 0xB1AF, 0x6249, 0xB476, 0x624A, 0xD951, 0x624B, 0xA4E2, 0x624D, 0xA47E, 0x624E, 0xA4E3, + 0x6250, 0xC97D, 0x6251, 0xA5B7, 0x6252, 0xA5B6, 0x6253, 0xA5B4, 0x6254, 0xA5B5, 0x6258, 0xA6AB, 0x6259, 0xC9E9, 0x625A, 0xC9EB, + 0x625B, 0xA6AA, 0x625C, 0xC9E3, 0x625E, 0xC9E4, 0x6260, 0xC9EA, 0x6261, 0xC9E6, 0x6262, 0xC9E8, 0x6263, 0xA6A9, 0x6264, 0xC9E5, + 0x6265, 0xC9EC, 0x6266, 0xC9E7, 0x626D, 0xA7E1, 0x626E, 0xA7EA, 0x626F, 0xA7E8, 0x6270, 0xCAF0, 0x6271, 0xCAED, 0x6272, 0xCAF5, + 0x6273, 0xA7E6, 0x6274, 0xCAF6, 0x6276, 0xA7DF, 0x6277, 0xCAF3, 0x6279, 0xA7E5, 0x627A, 0xCAEF, 0x627B, 0xCAEE, 0x627C, 0xA7E3, + 0x627D, 0xCAF4, 0x627E, 0xA7E4, 0x627F, 0xA9D3, 0x6280, 0xA7DE, 0x6281, 0xCAF1, 0x6283, 0xCAE7, 0x6284, 0xA7DB, 0x6286, 0xA7EE, + 0x6287, 0xCAEC, 0x6288, 0xCAF2, 0x6289, 0xA7E0, 0x628A, 0xA7E2, 0x628C, 0xCAE8, 0x628E, 0xCAE9, 0x628F, 0xCAEA, 0x6291, 0xA7ED, + 0x6292, 0xA7E7, 0x6293, 0xA7EC, 0x6294, 0xCAEB, 0x6295, 0xA7EB, 0x6296, 0xA7DD, 0x6297, 0xA7DC, 0x6298, 0xA7E9, 0x62A8, 0xA9E1, + 0x62A9, 0xCCBE, 0x62AA, 0xCCB7, 0x62AB, 0xA9DC, 0x62AC, 0xA9EF, 0x62AD, 0xCCB3, 0x62AE, 0xCCBA, 0x62AF, 0xCCBC, 0x62B0, 0xCCBF, + 0x62B1, 0xA9EA, 0x62B3, 0xCCBB, 0x62B4, 0xCCB4, 0x62B5, 0xA9E8, 0x62B6, 0xCCB8, 0x62B8, 0xCCC0, 0x62B9, 0xA9D9, 0x62BB, 0xCCBD, + 0x62BC, 0xA9E3, 0x62BD, 0xA9E2, 0x62BE, 0xCCB6, 0x62BF, 0xA9D7, 0x62C2, 0xA9D8, 0x62C4, 0xA9D6, 0x62C6, 0xA9EE, 0x62C7, 0xA9E6, + 0x62C8, 0xA9E0, 0x62C9, 0xA9D4, 0x62CA, 0xCCB9, 0x62CB, 0xA9DF, 0x62CC, 0xA9D5, 0x62CD, 0xA9E7, 0x62CE, 0xA9F0, 0x62CF, 0xCED4, + 0x62D0, 0xA9E4, 0x62D1, 0xCCB5, 0x62D2, 0xA9DA, 0x62D3, 0xA9DD, 0x62D4, 0xA9DE, 0x62D6, 0xA9EC, 0x62D7, 0xA9ED, 0x62D8, 0xA9EB, + 0x62D9, 0xA9E5, 0x62DA, 0xA9E9, 0x62DB, 0xA9DB, 0x62DC, 0xABF4, 0x62EB, 0xCEDA, 0x62EC, 0xAC41, 0x62ED, 0xABF8, 0x62EE, 0xABFA, + 0x62EF, 0xAC40, 0x62F0, 0xCEE6, 0x62F1, 0xABFD, 0x62F2, 0xD1B1, 0x62F3, 0xAEB1, 0x62F4, 0xAC43, 0x62F5, 0xCED7, 0x62F6, 0xCEDF, + 0x62F7, 0xABFE, 0x62F8, 0xCEDE, 0x62F9, 0xCEDB, 0x62FA, 0xCEE3, 0x62FB, 0xCEE5, 0x62FC, 0xABF7, 0x62FD, 0xABFB, 0x62FE, 0xAC42, + 0x62FF, 0xAEB3, 0x6300, 0xCEE0, 0x6301, 0xABF9, 0x6302, 0xAC45, 0x6303, 0xCED9, 0x6307, 0xABFC, 0x6308, 0xAEB2, 0x6309, 0xABF6, + 0x630B, 0xCED6, 0x630C, 0xCEDD, 0x630D, 0xCED5, 0x630E, 0xCED8, 0x630F, 0xCEDC, 0x6310, 0xD1B2, 0x6311, 0xAC44, 0x6313, 0xCEE1, + 0x6314, 0xCEE2, 0x6315, 0xCEE4, 0x6316, 0xABF5, 0x6328, 0xAEC1, 0x6329, 0xD1BE, 0x632A, 0xAEBF, 0x632B, 0xAEC0, 0x632C, 0xD1B4, + 0x632D, 0xD1C4, 0x632F, 0xAEB6, 0x6332, 0xD566, 0x6333, 0xD1C6, 0x6334, 0xD1C0, 0x6336, 0xD1B7, 0x6338, 0xD1C9, 0x6339, 0xD1BA, + 0x633A, 0xAEBC, 0x633B, 0xD57D, 0x633C, 0xD1BD, 0x633D, 0xAEBE, 0x633E, 0xAEB5, 0x6340, 0xD1CB, 0x6341, 0xD1BF, 0x6342, 0xAEB8, + 0x6343, 0xD1B8, 0x6344, 0xD1B5, 0x6345, 0xD1B6, 0x6346, 0xAEB9, 0x6347, 0xD1C5, 0x6348, 0xD1CC, 0x6349, 0xAEBB, 0x634A, 0xD1BC, + 0x634B, 0xD1BB, 0x634C, 0xAEC3, 0x634D, 0xAEC2, 0x634E, 0xAEB4, 0x634F, 0xAEBA, 0x6350, 0xAEBD, 0x6351, 0xD1C8, 0x6354, 0xD1C2, + 0x6355, 0xAEB7, 0x6356, 0xD1B3, 0x6357, 0xD1CA, 0x6358, 0xD1C1, 0x6359, 0xD1C3, 0x635A, 0xD1C7, 0x6365, 0xD567, 0x6367, 0xB1B7, + 0x6368, 0xB1CB, 0x6369, 0xB1CA, 0x636B, 0xB1BF, 0x636D, 0xD579, 0x636E, 0xD575, 0x636F, 0xD572, 0x6370, 0xD5A6, 0x6371, 0xB1BA, + 0x6372, 0xB1B2, 0x6375, 0xD577, 0x6376, 0xB4A8, 0x6377, 0xB1B6, 0x6378, 0xD5A1, 0x637A, 0xB1CC, 0x637B, 0xB1C9, 0x637C, 0xD57B, + 0x637D, 0xD56A, 0x6380, 0xB1C8, 0x6381, 0xD5A3, 0x6382, 0xD569, 0x6383, 0xB1BD, 0x6384, 0xB1C1, 0x6385, 0xD5A2, 0x6387, 0xD573, + 0x6388, 0xB1C2, 0x6389, 0xB1BC, 0x638A, 0xD568, 0x638C, 0xB478, 0x638D, 0xD5A5, 0x638E, 0xD571, 0x638F, 0xB1C7, 0x6390, 0xD574, + 0x6391, 0xD5A4, 0x6392, 0xB1C6, 0x6394, 0xD952, 0x6396, 0xB1B3, 0x6397, 0xD56F, 0x6398, 0xB1B8, 0x6399, 0xB1C3, 0x639B, 0xB1BE, + 0x639C, 0xD578, 0x639D, 0xD56E, 0x639E, 0xD56C, 0x639F, 0xD57E, 0x63A0, 0xB1B0, 0x63A1, 0xB1C4, 0x63A2, 0xB1B4, 0x63A3, 0xB477, + 0x63A4, 0xD57C, 0x63A5, 0xB1B5, 0x63A7, 0xB1B1, 0x63A8, 0xB1C0, 0x63A9, 0xB1BB, 0x63AA, 0xB1B9, 0x63AB, 0xD570, 0x63AC, 0xB1C5, + 0x63AD, 0xD56D, 0x63AE, 0xD57A, 0x63AF, 0xD576, 0x63B0, 0xD954, 0x63B1, 0xD953, 0x63BD, 0xD56B, 0x63BE, 0xD964, 0x63C0, 0xB47A, + 0x63C2, 0xD96A, 0x63C3, 0xD959, 0x63C4, 0xD967, 0x63C5, 0xDD77, 0x63C6, 0xB47D, 0x63C7, 0xD96B, 0x63C8, 0xD96E, 0x63C9, 0xB47C, + 0x63CA, 0xD95C, 0x63CB, 0xD96D, 0x63CC, 0xD96C, 0x63CD, 0xB47E, 0x63CE, 0xD955, 0x63CF, 0xB479, 0x63D0, 0xB4A3, 0x63D2, 0xB4A1, + 0x63D3, 0xD969, 0x63D5, 0xD95F, 0x63D6, 0xB4A5, 0x63D7, 0xD970, 0x63D8, 0xD968, 0x63D9, 0xD971, 0x63DA, 0xB4AD, 0x63DB, 0xB4AB, + 0x63DC, 0xD966, 0x63DD, 0xD965, 0x63DF, 0xD963, 0x63E0, 0xD95D, 0x63E1, 0xB4A4, 0x63E3, 0xB4A2, 0x63E4, 0xD1B9, 0x63E5, 0xD956, + 0x63E7, 0xDDB7, 0x63E8, 0xD957, 0x63E9, 0xB47B, 0x63EA, 0xB4AA, 0x63EB, 0xDD79, 0x63ED, 0xB4A6, 0x63EE, 0xB4A7, 0x63EF, 0xD958, + 0x63F0, 0xD96F, 0x63F1, 0xDD78, 0x63F2, 0xD960, 0x63F3, 0xD95B, 0x63F4, 0xB4A9, 0x63F5, 0xD961, 0x63F6, 0xD95E, 0x63F9, 0xB4AE, + 0x6406, 0xB770, 0x6409, 0xDD7C, 0x640A, 0xDDB1, 0x640B, 0xDDB6, 0x640C, 0xDDAA, 0x640D, 0xB76C, 0x640E, 0xDDBB, 0x640F, 0xB769, + 0x6410, 0xDD7A, 0x6412, 0xDD7B, 0x6413, 0xB762, 0x6414, 0xB76B, 0x6415, 0xDDA4, 0x6416, 0xB76E, 0x6417, 0xB76F, 0x6418, 0xDDA5, + 0x641A, 0xDDB2, 0x641B, 0xDDB8, 0x641C, 0xB76A, 0x641E, 0xB764, 0x641F, 0xDDA3, 0x6420, 0xDD7D, 0x6421, 0xDDBA, 0x6422, 0xDDA8, + 0x6423, 0xDDA9, 0x6424, 0xDD7E, 0x6425, 0xDDB4, 0x6426, 0xDDAB, 0x6427, 0xDDB5, 0x6428, 0xDDAD, 0x642A, 0xB765, 0x642B, 0xE1D9, + 0x642C, 0xB768, 0x642D, 0xB766, 0x642E, 0xDDB9, 0x642F, 0xDDB0, 0x6430, 0xDDAC, 0x6433, 0xDDA1, 0x6434, 0xBA53, 0x6435, 0xDDAF, + 0x6436, 0xB76D, 0x6437, 0xDDA7, 0x6439, 0xDDA6, 0x643D, 0xB767, 0x643E, 0xB763, 0x643F, 0xE1EE, 0x6440, 0xDDB3, 0x6441, 0xDDAE, + 0x6443, 0xDDA2, 0x644B, 0xE1E9, 0x644D, 0xE1DA, 0x644E, 0xE1E5, 0x6450, 0xE1EC, 0x6451, 0xBA51, 0x6452, 0xB4AC, 0x6453, 0xE1EA, + 0x6454, 0xBA4C, 0x6458, 0xBA4B, 0x6459, 0xE1F1, 0x645B, 0xE1DB, 0x645C, 0xE1E8, 0x645D, 0xE1DC, 0x645E, 0xE1E7, 0x645F, 0xBA4F, + 0x6460, 0xE1EB, 0x6461, 0xD962, 0x6465, 0xE1F2, 0x6466, 0xE1E3, 0x6467, 0xBA52, 0x6468, 0xE5BA, 0x6469, 0xBCAF, 0x646B, 0xE1F0, + 0x646C, 0xE1EF, 0x646D, 0xBA54, 0x646E, 0xE5AD, 0x646F, 0xBCB0, 0x6470, 0xE5AE, 0x6472, 0xE1DF, 0x6473, 0xE1E0, 0x6474, 0xE1DD, + 0x6475, 0xE1E2, 0x6476, 0xE1DE, 0x6477, 0xE1F3, 0x6478, 0xBA4E, 0x6479, 0xBCB1, 0x647A, 0xBA50, 0x647B, 0xBA55, 0x647D, 0xE1E1, + 0x647F, 0xE1ED, 0x6482, 0xE1E6, 0x6485, 0xE5B1, 0x6487, 0xBA4A, 0x6488, 0xBCB4, 0x6489, 0xE9AA, 0x648A, 0xE5B6, 0x648B, 0xE5B5, + 0x648C, 0xE5B7, 0x648F, 0xE5B4, 0x6490, 0xBCB5, 0x6492, 0xBCBB, 0x6493, 0xBCB8, 0x6495, 0xBCB9, 0x6496, 0xE5AF, 0x6497, 0xE5B2, + 0x6498, 0xE5BC, 0x6499, 0xBCC1, 0x649A, 0xBCBF, 0x649C, 0xE5B3, 0x649D, 0xD95A, 0x649E, 0xBCB2, 0x649F, 0xE5B9, 0x64A0, 0xE5B0, + 0x64A2, 0xBCC2, 0x64A3, 0xE5B8, 0x64A4, 0xBA4D, 0x64A5, 0xBCB7, 0x64A6, 0xE1E4, 0x64A9, 0xBCBA, 0x64AB, 0xBCBE, 0x64AC, 0xBCC0, + 0x64AD, 0xBCBD, 0x64AE, 0xBCBC, 0x64B0, 0xBCB6, 0x64B1, 0xE5BB, 0x64B2, 0xBCB3, 0x64B3, 0xBCC3, 0x64BB, 0xBED8, 0x64BC, 0xBED9, + 0x64BD, 0xE9A9, 0x64BE, 0xBEE2, 0x64BF, 0xBEDF, 0x64C1, 0xBED6, 0x64C2, 0xBEDD, 0x64C3, 0xE9AB, 0x64C4, 0xBEDB, 0x64C5, 0xBED5, + 0x64C7, 0xBEDC, 0x64C9, 0xE9A8, 0x64CA, 0xC0BB, 0x64CB, 0xBED7, 0x64CD, 0xBEDE, 0x64CE, 0xC0BA, 0x64CF, 0xE9A7, 0x64D0, 0xE9A6, + 0x64D2, 0xBEE0, 0x64D4, 0xBEE1, 0x64D6, 0xE9A5, 0x64D7, 0xE9A4, 0x64D8, 0xC0BC, 0x64D9, 0xE9AE, 0x64DA, 0xBEDA, 0x64DB, 0xE9AC, + 0x64E0, 0xC0BD, 0x64E2, 0xC0C2, 0x64E3, 0xECEA, 0x64E4, 0xECEC, 0x64E6, 0xC0BF, 0x64E8, 0xECED, 0x64E9, 0xECE9, 0x64EB, 0xECEB, + 0x64EC, 0xC0C0, 0x64ED, 0xC0C3, 0x64EF, 0xECE8, 0x64F0, 0xC0BE, 0x64F1, 0xC0C1, 0x64F2, 0xC259, 0x64F3, 0xE9AD, 0x64F4, 0xC258, + 0x64F7, 0xC25E, 0x64F8, 0xEFD4, 0x64FA, 0xC25C, 0x64FB, 0xC25D, 0x64FC, 0xEFD7, 0x64FD, 0xEFD3, 0x64FE, 0xC25A, 0x64FF, 0xEFD1, + 0x6500, 0xC36B, 0x6501, 0xEFD5, 0x6503, 0xEFD6, 0x6504, 0xEFD2, 0x6506, 0xC25B, 0x6507, 0xF242, 0x6509, 0xF245, 0x650C, 0xF246, + 0x650D, 0xF244, 0x650E, 0xF247, 0x650F, 0xC36C, 0x6510, 0xF243, 0x6513, 0xF44E, 0x6514, 0xC464, 0x6515, 0xF44D, 0x6516, 0xF44C, + 0x6517, 0xF44B, 0x6518, 0xC463, 0x6519, 0xC465, 0x651B, 0xF5CD, 0x651C, 0xC4E2, 0x651D, 0xC4E1, 0x6520, 0xF6E1, 0x6521, 0xF6E0, + 0x6522, 0xF6E3, 0x6523, 0xC5CB, 0x6524, 0xC575, 0x6525, 0xF7DD, 0x6526, 0xF6E2, 0x6529, 0xF7DC, 0x652A, 0xC5CD, 0x652B, 0xC5CC, + 0x652C, 0xC5F3, 0x652D, 0xF8A9, 0x652E, 0xF8EF, 0x652F, 0xA4E4, 0x6532, 0xD972, 0x6533, 0xE9AF, 0x6536, 0xA6AC, 0x6537, 0xCAF7, + 0x6538, 0xA7F1, 0x6539, 0xA7EF, 0x653B, 0xA7F0, 0x653D, 0xCCC1, 0x653E, 0xA9F1, 0x653F, 0xAC46, 0x6541, 0xCEE7, 0x6543, 0xCEE8, + 0x6545, 0xAC47, 0x6546, 0xD1CE, 0x6548, 0xAEC4, 0x6549, 0xAEC5, 0x654A, 0xD1CD, 0x654F, 0xB1D3, 0x6551, 0xB1CF, 0x6553, 0xD5A7, + 0x6554, 0xB1D6, 0x6555, 0xB1D5, 0x6556, 0xB1CE, 0x6557, 0xB1D1, 0x6558, 0xB1D4, 0x6559, 0xB1D0, 0x655C, 0xD976, 0x655D, 0xB1CD, + 0x655E, 0xB4AF, 0x6562, 0xB4B1, 0x6563, 0xB4B2, 0x6564, 0xD975, 0x6565, 0xD978, 0x6566, 0xB4B0, 0x6567, 0xD973, 0x6568, 0xD977, + 0x656A, 0xD974, 0x656C, 0xB771, 0x656F, 0xDDBC, 0x6572, 0xBA56, 0x6573, 0xE1F4, 0x6574, 0xBEE3, 0x6575, 0xBCC4, 0x6576, 0xE5BD, + 0x6577, 0xBCC5, 0x6578, 0xBCC6, 0x6579, 0xE5BF, 0x657A, 0xE5BE, 0x657B, 0xE5C0, 0x657C, 0xE9B1, 0x657F, 0xE9B0, 0x6580, 0xECEF, + 0x6581, 0xECEE, 0x6582, 0xC0C4, 0x6583, 0xC0C5, 0x6584, 0xF248, 0x6587, 0xA4E5, 0x658C, 0xD979, 0x6590, 0xB4B4, 0x6591, 0xB4B3, + 0x6592, 0xDDBD, 0x6594, 0xEFD8, 0x6595, 0xC4E3, 0x6596, 0xF7DE, 0x6597, 0xA4E6, 0x6599, 0xAEC6, 0x659B, 0xB1D8, 0x659C, 0xB1D7, + 0x659D, 0xD97A, 0x659E, 0xD97B, 0x659F, 0xB772, 0x65A0, 0xE1F5, 0x65A1, 0xBA57, 0x65A2, 0xE9B2, 0x65A4, 0xA4E7, 0x65A5, 0xA5B8, + 0x65A7, 0xA9F2, 0x65A8, 0xCCC2, 0x65AA, 0xCEE9, 0x65AB, 0xAC48, 0x65AC, 0xB1D9, 0x65AE, 0xD97C, 0x65AF, 0xB4B5, 0x65B0, 0xB773, + 0x65B2, 0xE5C1, 0x65B3, 0xE5C2, 0x65B6, 0xECF0, 0x65B7, 0xC25F, 0x65B8, 0xF8F0, 0x65B9, 0xA4E8, 0x65BB, 0xCCC3, 0x65BC, 0xA9F3, + 0x65BD, 0xAC49, 0x65BF, 0xCEEA, 0x65C1, 0xAEC7, 0x65C2, 0xD1D2, 0x65C3, 0xD1D0, 0x65C4, 0xD1D1, 0x65C5, 0xAEC8, 0x65C6, 0xD1CF, + 0x65CB, 0xB1DB, 0x65CC, 0xB1DC, 0x65CD, 0xD5A8, 0x65CE, 0xB1DD, 0x65CF, 0xB1DA, 0x65D0, 0xD97D, 0x65D2, 0xD97E, 0x65D3, 0xDDBE, + 0x65D6, 0xBA59, 0x65D7, 0xBA58, 0x65DA, 0xECF1, 0x65DB, 0xEFD9, 0x65DD, 0xF24A, 0x65DE, 0xF249, 0x65DF, 0xF44F, 0x65E1, 0xC95E, + 0x65E2, 0xAC4A, 0x65E5, 0xA4E9, 0x65E6, 0xA5B9, 0x65E8, 0xA6AE, 0x65E9, 0xA6AD, 0x65EC, 0xA6AF, 0x65ED, 0xA6B0, 0x65EE, 0xC9EE, + 0x65EF, 0xC9ED, 0x65F0, 0xCAF8, 0x65F1, 0xA7F2, 0x65F2, 0xCAFB, 0x65F3, 0xCAFA, 0x65F4, 0xCAF9, 0x65F5, 0xCAFC, 0x65FA, 0xA9F4, + 0x65FB, 0xCCC9, 0x65FC, 0xCCC5, 0x65FD, 0xCCCE, 0x6600, 0xA9FB, 0x6602, 0xA9F9, 0x6603, 0xCCCA, 0x6604, 0xCCC6, 0x6605, 0xCCCD, + 0x6606, 0xA9F8, 0x6607, 0xAA40, 0x6608, 0xCCC8, 0x6609, 0xCCC4, 0x660A, 0xA9FE, 0x660B, 0xCCCB, 0x660C, 0xA9F7, 0x660D, 0xCCCC, + 0x660E, 0xA9FA, 0x660F, 0xA9FC, 0x6610, 0xCCD0, 0x6611, 0xCCCF, 0x6612, 0xCCC7, 0x6613, 0xA9F6, 0x6614, 0xA9F5, 0x6615, 0xA9FD, + 0x661C, 0xCEEF, 0x661D, 0xCEF5, 0x661F, 0xAC50, 0x6620, 0xAC4D, 0x6621, 0xCEEC, 0x6622, 0xCEF1, 0x6624, 0xAC53, 0x6625, 0xAC4B, + 0x6626, 0xCEF0, 0x6627, 0xAC4E, 0x6628, 0xAC51, 0x662B, 0xCEF3, 0x662D, 0xAC4C, 0x662E, 0xCEF8, 0x662F, 0xAC4F, 0x6631, 0xAC52, + 0x6632, 0xCEED, 0x6633, 0xCEF2, 0x6634, 0xCEF6, 0x6635, 0xCEEE, 0x6636, 0xCEEB, 0x6639, 0xCEF7, 0x663A, 0xCEF4, 0x6641, 0xAED0, + 0x6642, 0xAEC9, 0x6643, 0xAECC, 0x6645, 0xAECF, 0x6647, 0xD1D5, 0x6649, 0xAECA, 0x664A, 0xD1D3, 0x664C, 0xAECE, 0x664F, 0xAECB, + 0x6651, 0xD1D6, 0x6652, 0xAECD, 0x6659, 0xD5AC, 0x665A, 0xB1DF, 0x665B, 0xD5AB, 0x665C, 0xD5AD, 0x665D, 0xB1DE, 0x665E, 0xB1E3, + 0x665F, 0xD1D4, 0x6661, 0xD5AA, 0x6662, 0xD5AE, 0x6664, 0xB1E0, 0x6665, 0xD5A9, 0x6666, 0xB1E2, 0x6668, 0xB1E1, 0x666A, 0xD9A7, + 0x666C, 0xD9A2, 0x666E, 0xB4B6, 0x666F, 0xB4BA, 0x6670, 0xB4B7, 0x6671, 0xD9A5, 0x6672, 0xD9A8, 0x6674, 0xB4B8, 0x6676, 0xB4B9, + 0x6677, 0xB4BE, 0x6678, 0xDDC7, 0x6679, 0xD9A6, 0x667A, 0xB4BC, 0x667B, 0xD9A3, 0x667C, 0xD9A1, 0x667E, 0xB4BD, 0x6680, 0xD9A4, + 0x6684, 0xB779, 0x6686, 0xDDBF, 0x6687, 0xB776, 0x6688, 0xB777, 0x6689, 0xB775, 0x668A, 0xDDC4, 0x668B, 0xDDC3, 0x668C, 0xDDC0, + 0x668D, 0xB77B, 0x6690, 0xDDC2, 0x6691, 0xB4BB, 0x6694, 0xDDC6, 0x6695, 0xDDC1, 0x6696, 0xB778, 0x6697, 0xB774, 0x6698, 0xB77A, + 0x6699, 0xDDC5, 0x669D, 0xBA5C, 0x669F, 0xE1F8, 0x66A0, 0xE1F7, 0x66A1, 0xE1F6, 0x66A2, 0xBA5A, 0x66A8, 0xBA5B, 0x66A9, 0xE5C5, + 0x66AA, 0xE5C8, 0x66AB, 0xBCC8, 0x66AE, 0xBCC7, 0x66AF, 0xE5C9, 0x66B0, 0xE5C4, 0x66B1, 0xBCCA, 0x66B2, 0xE5C6, 0x66B4, 0xBCC9, + 0x66B5, 0xE5C3, 0x66B7, 0xE5C7, 0x66B8, 0xBEE9, 0x66B9, 0xBEE6, 0x66BA, 0xE9BB, 0x66BB, 0xE9BA, 0x66BD, 0xE9B9, 0x66BE, 0xE9B4, + 0x66C0, 0xE9B5, 0x66C4, 0xBEE7, 0x66C6, 0xBEE4, 0x66C7, 0xBEE8, 0x66C8, 0xE9B3, 0x66C9, 0xBEE5, 0x66CA, 0xE9B6, 0x66CB, 0xE9B7, + 0x66CC, 0xE9BC, 0x66CF, 0xE9B8, 0x66D2, 0xECF2, 0x66D6, 0xC0C7, 0x66D8, 0xEFDC, 0x66D9, 0xC0C6, 0x66DA, 0xEFDA, 0x66DB, 0xEFDB, + 0x66DC, 0xC260, 0x66DD, 0xC36E, 0x66DE, 0xF24B, 0x66E0, 0xC36D, 0x66E3, 0xF451, 0x66E4, 0xF452, 0x66E6, 0xC466, 0x66E8, 0xF450, + 0x66E9, 0xC4E4, 0x66EB, 0xF7DF, 0x66EC, 0xC5CE, 0x66ED, 0xF8AA, 0x66EE, 0xF8AB, 0x66F0, 0xA4EA, 0x66F2, 0xA6B1, 0x66F3, 0xA6B2, + 0x66F4, 0xA7F3, 0x66F6, 0xCCD1, 0x66F7, 0xAC54, 0x66F8, 0xAED1, 0x66F9, 0xB1E4, 0x66FC, 0xB0D2, 0x66FE, 0xB4BF, 0x66FF, 0xB4C0, + 0x6700, 0xB3CC, 0x6701, 0xD9A9, 0x6703, 0xB77C, 0x6704, 0xE1FA, 0x6705, 0xE1F9, 0x6708, 0xA4EB, 0x6709, 0xA6B3, 0x670A, 0xCCD2, + 0x670B, 0xAA42, 0x670D, 0xAA41, 0x670F, 0xCEF9, 0x6710, 0xCEFA, 0x6712, 0xD1D7, 0x6713, 0xD1D8, 0x6714, 0xAED2, 0x6715, 0xAED3, + 0x6717, 0xAED4, 0x6718, 0xD5AF, 0x671B, 0xB1E6, 0x671D, 0xB4C2, 0x671F, 0xB4C1, 0x6720, 0xDDC8, 0x6721, 0xDF7A, 0x6722, 0xE1FB, + 0x6723, 0xE9BD, 0x6726, 0xC261, 0x6727, 0xC467, 0x6728, 0xA4EC, 0x672A, 0xA5BC, 0x672B, 0xA5BD, 0x672C, 0xA5BB, 0x672D, 0xA5BE, + 0x672E, 0xA5BA, 0x6731, 0xA6B6, 0x6733, 0xC9F6, 0x6734, 0xA6B5, 0x6735, 0xA6B7, 0x6738, 0xC9F1, 0x6739, 0xC9F0, 0x673A, 0xC9F3, + 0x673B, 0xC9F2, 0x673C, 0xC9F5, 0x673D, 0xA6B4, 0x673E, 0xC9EF, 0x673F, 0xC9F4, 0x6745, 0xCAFD, 0x6746, 0xA7FD, 0x6747, 0xCAFE, + 0x6748, 0xCB43, 0x6749, 0xA7FC, 0x674B, 0xCB47, 0x674C, 0xCB42, 0x674D, 0xCB45, 0x674E, 0xA7F5, 0x674F, 0xA7F6, 0x6750, 0xA7F7, + 0x6751, 0xA7F8, 0x6753, 0xA840, 0x6755, 0xCB41, 0x6756, 0xA7FA, 0x6757, 0xA841, 0x6759, 0xCB40, 0x675A, 0xCB46, 0x675C, 0xA7F9, + 0x675D, 0xCB44, 0x675E, 0xA7FB, 0x675F, 0xA7F4, 0x6760, 0xA7FE, 0x676A, 0xAA57, 0x676C, 0xCCD4, 0x676D, 0xAA43, 0x676F, 0xAA4D, + 0x6770, 0xAA4E, 0x6771, 0xAA46, 0x6772, 0xAA58, 0x6773, 0xAA48, 0x6774, 0xCCDC, 0x6775, 0xAA53, 0x6776, 0xCCD7, 0x6777, 0xAA49, + 0x6778, 0xCCE6, 0x6779, 0xCCE7, 0x677A, 0xCCDF, 0x677B, 0xCCD8, 0x677C, 0xAA56, 0x677D, 0xCCE4, 0x677E, 0xAA51, 0x677F, 0xAA4F, + 0x6781, 0xCCE5, 0x6783, 0xCCE3, 0x6784, 0xCCDB, 0x6785, 0xCCD3, 0x6786, 0xCCDA, 0x6787, 0xAA4A, 0x6789, 0xAA50, 0x678B, 0xAA44, + 0x678C, 0xCCDE, 0x678D, 0xCCDD, 0x678E, 0xCCD5, 0x6790, 0xAA52, 0x6791, 0xCCE1, 0x6792, 0xCCD6, 0x6793, 0xAA55, 0x6794, 0xCCE8, + 0x6795, 0xAA45, 0x6797, 0xAA4C, 0x6798, 0xCCD9, 0x6799, 0xCCE2, 0x679A, 0xAA54, 0x679C, 0xAA47, 0x679D, 0xAA4B, 0x679F, 0xCCE0, + 0x67AE, 0xCF5B, 0x67AF, 0xAC5C, 0x67B0, 0xAC69, 0x67B2, 0xCF56, 0x67B3, 0xCF4C, 0x67B4, 0xAC62, 0x67B5, 0xCF4A, 0x67B6, 0xAC5B, + 0x67B7, 0xCF45, 0x67B8, 0xAC65, 0x67B9, 0xCF52, 0x67BA, 0xCEFE, 0x67BB, 0xCF41, 0x67C0, 0xCF44, 0x67C1, 0xCEFB, 0x67C2, 0xCF51, + 0x67C3, 0xCF61, 0x67C4, 0xAC60, 0x67C5, 0xCF46, 0x67C6, 0xCF58, 0x67C8, 0xCEFD, 0x67C9, 0xCF5F, 0x67CA, 0xCF60, 0x67CB, 0xCF63, + 0x67CC, 0xCF5A, 0x67CD, 0xCF4B, 0x67CE, 0xCF53, 0x67CF, 0xAC66, 0x67D0, 0xAC59, 0x67D1, 0xAC61, 0x67D2, 0xAC6D, 0x67D3, 0xAC56, + 0x67D4, 0xAC58, 0x67D8, 0xCF43, 0x67D9, 0xAC6A, 0x67DA, 0xAC63, 0x67DB, 0xCF5D, 0x67DC, 0xCF40, 0x67DD, 0xAC6C, 0x67DE, 0xAC67, + 0x67DF, 0xCF49, 0x67E2, 0xAC6B, 0x67E3, 0xCF50, 0x67E4, 0xCF48, 0x67E5, 0xAC64, 0x67E6, 0xCF5C, 0x67E7, 0xCF54, 0x67E9, 0xAC5E, + 0x67EA, 0xCF62, 0x67EB, 0xCF47, 0x67EC, 0xAC5A, 0x67ED, 0xCF59, 0x67EE, 0xCF4F, 0x67EF, 0xAC5F, 0x67F0, 0xCF55, 0x67F1, 0xAC57, + 0x67F2, 0xCEFC, 0x67F3, 0xAC68, 0x67F4, 0xAEE3, 0x67F5, 0xAC5D, 0x67F6, 0xCF4E, 0x67F7, 0xCF4D, 0x67F8, 0xCF42, 0x67FA, 0xCF5E, + 0x67FC, 0xCF57, 0x67FF, 0xAC55, 0x6812, 0xD1EC, 0x6813, 0xAEEA, 0x6814, 0xD1ED, 0x6816, 0xD1E1, 0x6817, 0xAEDF, 0x6818, 0xAEEB, + 0x681A, 0xD1DA, 0x681C, 0xD1E3, 0x681D, 0xD1EB, 0x681F, 0xD1D9, 0x6820, 0xD1F4, 0x6821, 0xAED5, 0x6825, 0xD1F3, 0x6826, 0xD1EE, + 0x6828, 0xD1EF, 0x6829, 0xAEDD, 0x682A, 0xAEE8, 0x682B, 0xD1E5, 0x682D, 0xD1E6, 0x682E, 0xD1F0, 0x682F, 0xD1E7, 0x6831, 0xD1E2, + 0x6832, 0xD1DC, 0x6833, 0xD1DD, 0x6834, 0xD1EA, 0x6835, 0xD1E4, 0x6838, 0xAED6, 0x6839, 0xAEDA, 0x683A, 0xD1F2, 0x683B, 0xD1DE, + 0x683C, 0xAEE6, 0x683D, 0xAEE2, 0x6840, 0xAEE5, 0x6841, 0xAEEC, 0x6842, 0xAEDB, 0x6843, 0xAEE7, 0x6844, 0xD1E9, 0x6845, 0xAEE9, + 0x6846, 0xAED8, 0x6848, 0xAED7, 0x6849, 0xD1DB, 0x684B, 0xD1DF, 0x684C, 0xAEE0, 0x684D, 0xD1F1, 0x684E, 0xD1E8, 0x684F, 0xD1E0, + 0x6850, 0xAEE4, 0x6851, 0xAEE1, 0x6853, 0xAED9, 0x6854, 0xAEDC, 0x686B, 0xD5C4, 0x686D, 0xD5B4, 0x686E, 0xD5B5, 0x686F, 0xD5B9, + 0x6871, 0xD5C8, 0x6872, 0xD5C5, 0x6874, 0xD5BE, 0x6875, 0xD5BD, 0x6876, 0xB1ED, 0x6877, 0xD5C1, 0x6878, 0xD5D0, 0x6879, 0xD5B0, + 0x687B, 0xD5D1, 0x687C, 0xD5C3, 0x687D, 0xD5D5, 0x687E, 0xD5C9, 0x687F, 0xB1EC, 0x6880, 0xD5C7, 0x6881, 0xB1E7, 0x6882, 0xB1FC, + 0x6883, 0xB1F2, 0x6885, 0xB1F6, 0x6886, 0xB1F5, 0x6887, 0xD5B1, 0x6889, 0xD5CE, 0x688A, 0xD5D4, 0x688B, 0xD5CC, 0x688C, 0xD5D3, + 0x688F, 0xD5C0, 0x6890, 0xD5B2, 0x6891, 0xD5D2, 0x6892, 0xD5C2, 0x6893, 0xB1EA, 0x6894, 0xB1F7, 0x6896, 0xD5CB, 0x6897, 0xB1F0, + 0x689B, 0xD5CA, 0x689C, 0xD5B3, 0x689D, 0xB1F8, 0x689F, 0xB1FA, 0x68A0, 0xD5CD, 0x68A1, 0xB1FB, 0x68A2, 0xB1E9, 0x68A3, 0xD5BA, + 0x68A4, 0xD5CF, 0x68A7, 0xB1EF, 0x68A8, 0xB1F9, 0x68A9, 0xD5BC, 0x68AA, 0xD5C6, 0x68AB, 0xD5B7, 0x68AC, 0xD5BB, 0x68AD, 0xB1F4, + 0x68AE, 0xD5B6, 0x68AF, 0xB1E8, 0x68B0, 0xB1F1, 0x68B1, 0xB1EE, 0x68B2, 0xD5BF, 0x68B3, 0xAEDE, 0x68B4, 0xD9C0, 0x68B5, 0xB1EB, + 0x68C4, 0xB1F3, 0x68C6, 0xD9C3, 0x68C7, 0xD9D9, 0x68C8, 0xD9CE, 0x68C9, 0xB4D6, 0x68CB, 0xB4D1, 0x68CC, 0xD9BD, 0x68CD, 0xB4D2, + 0x68CE, 0xD9CD, 0x68D0, 0xD9C6, 0x68D1, 0xD9D3, 0x68D2, 0xB4CE, 0x68D3, 0xD9AB, 0x68D4, 0xD9D5, 0x68D5, 0xB4C4, 0x68D6, 0xD9B3, + 0x68D7, 0xB4C7, 0x68D8, 0xB4C6, 0x68DA, 0xB4D7, 0x68DC, 0xD9AD, 0x68DD, 0xD9CF, 0x68DE, 0xD9D0, 0x68DF, 0xB4C9, 0x68E0, 0xB4C5, + 0x68E1, 0xD9BB, 0x68E3, 0xB4D0, 0x68E4, 0xD9B6, 0x68E6, 0xD9D1, 0x68E7, 0xB4CC, 0x68E8, 0xD9C9, 0x68E9, 0xD9D6, 0x68EA, 0xD9B0, + 0x68EB, 0xD9B5, 0x68EC, 0xD9AF, 0x68EE, 0xB4CB, 0x68EF, 0xD9C2, 0x68F0, 0xDDDE, 0x68F1, 0xD9B1, 0x68F2, 0xB4CF, 0x68F3, 0xD9BA, + 0x68F4, 0xD9D2, 0x68F5, 0xB4CA, 0x68F6, 0xD9B7, 0x68F7, 0xD9B4, 0x68F8, 0xD9C5, 0x68F9, 0xB4CD, 0x68FA, 0xB4C3, 0x68FB, 0xB4D9, + 0x68FC, 0xD9C8, 0x68FD, 0xD9C7, 0x6904, 0xD9AC, 0x6905, 0xB4C8, 0x6906, 0xD9D4, 0x6907, 0xD9BC, 0x6908, 0xD9BE, 0x690A, 0xD9CB, + 0x690B, 0xD9CA, 0x690C, 0xD9AA, 0x690D, 0xB4D3, 0x690E, 0xB4D5, 0x690F, 0xD9B2, 0x6910, 0xD9B9, 0x6911, 0xD9C1, 0x6912, 0xB4D4, + 0x6913, 0xD9B8, 0x6914, 0xD9C4, 0x6915, 0xD9D7, 0x6917, 0xD9CC, 0x6925, 0xD9D8, 0x692A, 0xD9AE, 0x692F, 0xDDF2, 0x6930, 0xB7A6, + 0x6932, 0xDDF0, 0x6933, 0xDDDB, 0x6934, 0xDDE0, 0x6935, 0xDDD9, 0x6937, 0xDDEC, 0x6938, 0xDDCB, 0x6939, 0xDDD2, 0x693B, 0xDDEA, + 0x693C, 0xDDF4, 0x693D, 0xDDDC, 0x693F, 0xDDCF, 0x6940, 0xDDE2, 0x6941, 0xDDE7, 0x6942, 0xDDD3, 0x6944, 0xDDE4, 0x6945, 0xDDD0, + 0x6948, 0xDDD7, 0x6949, 0xDDD8, 0x694A, 0xB7A8, 0x694B, 0xDDEB, 0x694C, 0xDDE9, 0x694E, 0xDDCC, 0x694F, 0xDDEE, 0x6951, 0xDDEF, + 0x6952, 0xDDF1, 0x6953, 0xB7AC, 0x6954, 0xB7A4, 0x6956, 0xD5B8, 0x6957, 0xDDD4, 0x6958, 0xDDE6, 0x6959, 0xDDD5, 0x695A, 0xB7A1, + 0x695B, 0xB7B1, 0x695C, 0xDDED, 0x695D, 0xB7AF, 0x695E, 0xB7AB, 0x695F, 0xDDCA, 0x6960, 0xB7A3, 0x6962, 0xDDCD, 0x6963, 0xB7B0, + 0x6965, 0xDDDD, 0x6966, 0xDDC9, 0x6968, 0xB7A9, 0x6969, 0xDDE1, 0x696A, 0xDDD1, 0x696B, 0xB7AA, 0x696C, 0xDDDA, 0x696D, 0xB77E, + 0x696E, 0xB4D8, 0x696F, 0xDDE3, 0x6970, 0xD9BF, 0x6971, 0xDDCE, 0x6974, 0xDDE8, 0x6975, 0xB7A5, 0x6976, 0xDDE5, 0x6977, 0xB7A2, + 0x6978, 0xDDDF, 0x6979, 0xB7AD, 0x697A, 0xDDD6, 0x697B, 0xDDF3, 0x6982, 0xB7A7, 0x6983, 0xDEC6, 0x6986, 0xB7AE, 0x698D, 0xE24A, + 0x698E, 0xE248, 0x6990, 0xE25E, 0x6991, 0xE246, 0x6993, 0xE258, 0x6994, 0xB77D, 0x6995, 0xBA5F, 0x6996, 0xE242, 0x6997, 0xE25D, + 0x6999, 0xE247, 0x699A, 0xE255, 0x699B, 0xBA64, 0x699C, 0xBA5D, 0x699E, 0xE25B, 0x69A0, 0xE240, 0x69A1, 0xE25A, 0x69A3, 0xBA6F, + 0x69A4, 0xE251, 0x69A5, 0xE261, 0x69A6, 0xBA6D, 0x69A7, 0xE249, 0x69A8, 0xBA5E, 0x69A9, 0xE24B, 0x69AA, 0xE259, 0x69AB, 0xBA67, + 0x69AC, 0xE244, 0x69AD, 0xBA6B, 0x69AE, 0xBA61, 0x69AF, 0xE24D, 0x69B0, 0xE243, 0x69B1, 0xE1FC, 0x69B3, 0xE257, 0x69B4, 0xBA68, + 0x69B5, 0xE260, 0x69B6, 0xE1FD, 0x69B7, 0xBA65, 0x69B9, 0xE253, 0x69BB, 0xBA66, 0x69BC, 0xE245, 0x69BD, 0xE250, 0x69BE, 0xE24C, + 0x69BF, 0xE24E, 0x69C1, 0xBA60, 0x69C2, 0xE25F, 0x69C3, 0xBA6E, 0x69C4, 0xE24F, 0x69C6, 0xE262, 0x69C9, 0xE1FE, 0x69CA, 0xE254, + 0x69CB, 0xBA63, 0x69CC, 0xBA6C, 0x69CD, 0xBA6A, 0x69CE, 0xE241, 0x69CF, 0xE256, 0x69D0, 0xBA69, 0x69D3, 0xBA62, 0x69D4, 0xE252, + 0x69D9, 0xE25C, 0x69E2, 0xE5D5, 0x69E4, 0xE5D1, 0x69E5, 0xE5CD, 0x69E6, 0xE5E1, 0x69E7, 0xE5DE, 0x69E8, 0xBCCD, 0x69EB, 0xE5E5, + 0x69EC, 0xE5D4, 0x69ED, 0xBCD8, 0x69EE, 0xE5DB, 0x69F1, 0xE5D0, 0x69F2, 0xE5DA, 0x69F3, 0xBCD5, 0x69F4, 0xE5EE, 0x69F6, 0xE5EB, + 0x69F7, 0xE5DD, 0x69F8, 0xE5CE, 0x69FB, 0xE5E2, 0x69FC, 0xE5E4, 0x69FD, 0xBCD1, 0x69FE, 0xE5D8, 0x69FF, 0xE5D3, 0x6A00, 0xE5CA, + 0x6A01, 0xBCCE, 0x6A02, 0xBCD6, 0x6A04, 0xE5E7, 0x6A05, 0xBCD7, 0x6A06, 0xE5CB, 0x6A07, 0xE5ED, 0x6A08, 0xE5E0, 0x6A09, 0xE5E6, + 0x6A0A, 0xBCD4, 0x6A0D, 0xE5E3, 0x6A0F, 0xE5EA, 0x6A11, 0xBCD9, 0x6A13, 0xBCD3, 0x6A14, 0xE5DC, 0x6A15, 0xE5CF, 0x6A16, 0xE5EF, + 0x6A17, 0xE5CC, 0x6A18, 0xE5E8, 0x6A19, 0xBCD0, 0x6A1B, 0xE5D6, 0x6A1D, 0xE5D7, 0x6A1E, 0xBCCF, 0x6A1F, 0xBCCC, 0x6A20, 0xE5D2, + 0x6A21, 0xBCD2, 0x6A23, 0xBCCB, 0x6A25, 0xE5E9, 0x6A26, 0xE5EC, 0x6A27, 0xE5D9, 0x6A28, 0xE9CA, 0x6A32, 0xE9C2, 0x6A34, 0xE9BE, + 0x6A35, 0xBEF6, 0x6A38, 0xBEEB, 0x6A39, 0xBEF0, 0x6A3A, 0xBEEC, 0x6A3B, 0xE9CC, 0x6A3C, 0xE9D7, 0x6A3D, 0xBEEA, 0x6A3E, 0xE9C4, + 0x6A3F, 0xE9CD, 0x6A40, 0xE5DF, 0x6A41, 0xE9CE, 0x6A44, 0xBEF1, 0x6A46, 0xE9DD, 0x6A47, 0xBEF5, 0x6A48, 0xBEF8, 0x6A49, 0xE9C0, + 0x6A4B, 0xBEF4, 0x6A4D, 0xE9DB, 0x6A4E, 0xE9DC, 0x6A4F, 0xE9D2, 0x6A50, 0xE9D1, 0x6A51, 0xE9C9, 0x6A54, 0xE9D3, 0x6A55, 0xE9DA, + 0x6A56, 0xE9D9, 0x6A58, 0xBEEF, 0x6A59, 0xBEED, 0x6A5A, 0xE9CB, 0x6A5B, 0xE9C8, 0x6A5D, 0xE9C5, 0x6A5E, 0xE9D8, 0x6A5F, 0xBEF7, + 0x6A60, 0xE9D6, 0x6A61, 0xBEF3, 0x6A62, 0xBEF2, 0x6A64, 0xE9D0, 0x6A66, 0xE9BF, 0x6A67, 0xE9C1, 0x6A68, 0xE9C3, 0x6A69, 0xE9D5, + 0x6A6A, 0xE9CF, 0x6A6B, 0xBEEE, 0x6A6D, 0xE9C6, 0x6A6F, 0xE9D4, 0x6A76, 0xE9C7, 0x6A7E, 0xC0CF, 0x6A7F, 0xED45, 0x6A80, 0xC0C8, + 0x6A81, 0xECF5, 0x6A83, 0xED41, 0x6A84, 0xC0CA, 0x6A85, 0xED48, 0x6A87, 0xECFC, 0x6A89, 0xECF7, 0x6A8C, 0xED49, 0x6A8D, 0xECF3, + 0x6A8E, 0xECFE, 0x6A90, 0xC0D1, 0x6A91, 0xED44, 0x6A92, 0xED4A, 0x6A93, 0xECFD, 0x6A94, 0xC0C9, 0x6A95, 0xED40, 0x6A96, 0xECF4, + 0x6A97, 0xC0D0, 0x6A9A, 0xED47, 0x6A9B, 0xECF9, 0x6A9C, 0xC0CC, 0x6A9E, 0xECFB, 0x6A9F, 0xECF8, 0x6AA0, 0xC0D2, 0x6AA1, 0xECFA, + 0x6AA2, 0xC0CB, 0x6AA3, 0xC0CE, 0x6AA4, 0xED43, 0x6AA5, 0xECF6, 0x6AA6, 0xED46, 0x6AA8, 0xED42, 0x6AAC, 0xC263, 0x6AAD, 0xEFE7, + 0x6AAE, 0xC268, 0x6AAF, 0xC269, 0x6AB3, 0xC262, 0x6AB4, 0xEFE6, 0x6AB6, 0xEFE3, 0x6AB7, 0xEFE4, 0x6AB8, 0xC266, 0x6AB9, 0xEFDE, + 0x6ABA, 0xEFE2, 0x6ABB, 0xC265, 0x6ABD, 0xEFDF, 0x6AC2, 0xC267, 0x6AC3, 0xC264, 0x6AC5, 0xEFDD, 0x6AC6, 0xEFE1, 0x6AC7, 0xEFE5, + 0x6ACB, 0xF251, 0x6ACC, 0xF24E, 0x6ACD, 0xF257, 0x6ACF, 0xF256, 0x6AD0, 0xF254, 0x6AD1, 0xF24F, 0x6AD3, 0xC372, 0x6AD9, 0xF250, + 0x6ADA, 0xC371, 0x6ADB, 0xC0CD, 0x6ADC, 0xF253, 0x6ADD, 0xC370, 0x6ADE, 0xF258, 0x6ADF, 0xF252, 0x6AE0, 0xF24D, 0x6AE1, 0xEFE0, + 0x6AE5, 0xC36F, 0x6AE7, 0xF24C, 0x6AE8, 0xF456, 0x6AEA, 0xF455, 0x6AEB, 0xF255, 0x6AEC, 0xC468, 0x6AEE, 0xF459, 0x6AEF, 0xF45A, + 0x6AF0, 0xF454, 0x6AF1, 0xF458, 0x6AF3, 0xF453, 0x6AF8, 0xF5D1, 0x6AF9, 0xF457, 0x6AFA, 0xC4E7, 0x6AFB, 0xC4E5, 0x6AFC, 0xF5CF, + 0x6B00, 0xF5D2, 0x6B02, 0xF5CE, 0x6B03, 0xF5D0, 0x6B04, 0xC4E6, 0x6B08, 0xF6E5, 0x6B09, 0xF6E6, 0x6B0A, 0xC576, 0x6B0B, 0xF6E4, + 0x6B0F, 0xF7E2, 0x6B10, 0xC5CF, 0x6B11, 0xF7E0, 0x6B12, 0xF7E1, 0x6B13, 0xF8AC, 0x6B16, 0xC656, 0x6B17, 0xF8F3, 0x6B18, 0xF8F1, + 0x6B19, 0xF8F2, 0x6B1A, 0xF8F4, 0x6B1E, 0xF9BB, 0x6B20, 0xA4ED, 0x6B21, 0xA6B8, 0x6B23, 0xAA59, 0x6B25, 0xCCE9, 0x6B28, 0xCF64, + 0x6B2C, 0xD1F5, 0x6B2D, 0xD1F7, 0x6B2F, 0xD1F6, 0x6B31, 0xD1F8, 0x6B32, 0xB1FD, 0x6B33, 0xD5D7, 0x6B34, 0xD1F9, 0x6B36, 0xD5D6, + 0x6B37, 0xD5D8, 0x6B38, 0xD5D9, 0x6B39, 0xD9DA, 0x6B3A, 0xB4DB, 0x6B3B, 0xD9DB, 0x6B3C, 0xD9DD, 0x6B3D, 0xB4DC, 0x6B3E, 0xB4DA, + 0x6B3F, 0xD9DC, 0x6B41, 0xDDFA, 0x6B42, 0xDDF8, 0x6B43, 0xDDF7, 0x6B45, 0xDDF6, 0x6B46, 0xDDF5, 0x6B47, 0xB7B2, 0x6B48, 0xDDF9, + 0x6B49, 0xBA70, 0x6B4A, 0xE263, 0x6B4B, 0xE265, 0x6B4C, 0xBA71, 0x6B4D, 0xE264, 0x6B4E, 0xBCDB, 0x6B50, 0xBCDA, 0x6B51, 0xE5F0, + 0x6B54, 0xE9DF, 0x6B55, 0xE9DE, 0x6B56, 0xE9E0, 0x6B59, 0xBEF9, 0x6B5B, 0xED4B, 0x6B5C, 0xC0D3, 0x6B5E, 0xEFE8, 0x6B5F, 0xC26A, + 0x6B60, 0xF259, 0x6B61, 0xC577, 0x6B62, 0xA4EE, 0x6B63, 0xA5BF, 0x6B64, 0xA6B9, 0x6B65, 0xA842, 0x6B66, 0xAA5A, 0x6B67, 0xAA5B, + 0x6B6A, 0xAC6E, 0x6B6D, 0xD1FA, 0x6B72, 0xB7B3, 0x6B76, 0xE6D1, 0x6B77, 0xBEFA, 0x6B78, 0xC26B, 0x6B79, 0xA4EF, 0x6B7B, 0xA6BA, + 0x6B7E, 0xCCEB, 0x6B7F, 0xAA5C, 0x6B80, 0xCCEA, 0x6B82, 0xCF65, 0x6B83, 0xAC6F, 0x6B84, 0xCF66, 0x6B86, 0xAC70, 0x6B88, 0xD1FC, + 0x6B89, 0xAEEE, 0x6B8A, 0xAEED, 0x6B8C, 0xD5DE, 0x6B8D, 0xD5DC, 0x6B8E, 0xD5DD, 0x6B8F, 0xD5DB, 0x6B91, 0xD5DA, 0x6B94, 0xD9DE, + 0x6B95, 0xD9E1, 0x6B96, 0xB4DE, 0x6B97, 0xD9DF, 0x6B98, 0xB4DD, 0x6B99, 0xD9E0, 0x6B9B, 0xDDFB, 0x6B9E, 0xE266, 0x6B9F, 0xE267, + 0x6BA0, 0xE268, 0x6BA2, 0xE5F3, 0x6BA3, 0xE5F2, 0x6BA4, 0xBCDC, 0x6BA5, 0xE5F1, 0x6BA6, 0xE5F4, 0x6BA7, 0xE9E1, 0x6BAA, 0xE9E2, + 0x6BAB, 0xE9E3, 0x6BAD, 0xED4C, 0x6BAE, 0xC0D4, 0x6BAF, 0xC26C, 0x6BB0, 0xF25A, 0x6BB2, 0xC4E8, 0x6BB3, 0xC95F, 0x6BB5, 0xAC71, + 0x6BB6, 0xCF67, 0x6BB7, 0xAEEF, 0x6BBA, 0xB1FE, 0x6BBC, 0xB4DF, 0x6BBD, 0xD9E2, 0x6BBF, 0xB7B5, 0x6BC0, 0xB7B4, 0x6BC3, 0xE269, + 0x6BC4, 0xE26A, 0x6BC5, 0xBCDD, 0x6BC6, 0xBCDE, 0x6BC7, 0xE9E5, 0x6BC8, 0xE9E4, 0x6BC9, 0xEFE9, 0x6BCA, 0xF7E3, 0x6BCB, 0xA4F0, + 0x6BCC, 0xC960, 0x6BCD, 0xA5C0, 0x6BCF, 0xA843, 0x6BD0, 0xCB48, 0x6BD2, 0xAC72, 0x6BD3, 0xB7B6, 0x6BD4, 0xA4F1, 0x6BD6, 0xCF68, + 0x6BD7, 0xAC73, 0x6BD8, 0xCF69, 0x6BDA, 0xC0D5, 0x6BDB, 0xA4F2, 0x6BDE, 0xCCEC, 0x6BE0, 0xCF6A, 0x6BE2, 0xD242, 0x6BE3, 0xD241, + 0x6BE4, 0xD1FE, 0x6BE6, 0xD1FD, 0x6BE7, 0xD243, 0x6BE8, 0xD240, 0x6BEB, 0xB240, 0x6BEC, 0xB241, 0x6BEF, 0xB4E0, 0x6BF0, 0xD9E3, + 0x6BF2, 0xD9E4, 0x6BF3, 0xD9E5, 0x6BF7, 0xDE41, 0x6BF8, 0xDE42, 0x6BF9, 0xDE40, 0x6BFB, 0xDDFD, 0x6BFC, 0xDDFE, 0x6BFD, 0xB7B7, + 0x6BFE, 0xE26B, 0x6BFF, 0xE5F7, 0x6C00, 0xE5F6, 0x6C01, 0xE5F5, 0x6C02, 0xE5F8, 0x6C03, 0xE9E7, 0x6C04, 0xE9E6, 0x6C05, 0xBEFB, + 0x6C06, 0xE9E8, 0x6C08, 0xC0D6, 0x6C09, 0xED4D, 0x6C0B, 0xEFEA, 0x6C0C, 0xF25B, 0x6C0D, 0xF6E7, 0x6C0F, 0xA4F3, 0x6C10, 0xA5C2, + 0x6C11, 0xA5C1, 0x6C13, 0xAA5D, 0x6C14, 0xC961, 0x6C15, 0xC97E, 0x6C16, 0xA6BB, 0x6C18, 0xC9F7, 0x6C19, 0xCB49, 0x6C1A, 0xCB4A, + 0x6C1B, 0xAA5E, 0x6C1D, 0xCCED, 0x6C1F, 0xAC74, 0x6C20, 0xCF6B, 0x6C21, 0xCF6C, 0x6C23, 0xAEF0, 0x6C24, 0xAEF4, 0x6C25, 0xD244, + 0x6C26, 0xAEF3, 0x6C27, 0xAEF1, 0x6C28, 0xAEF2, 0x6C2A, 0xD5DF, 0x6C2B, 0xB242, 0x6C2C, 0xB4E3, 0x6C2E, 0xB4E1, 0x6C2F, 0xB4E2, + 0x6C30, 0xD9E6, 0x6C33, 0xBA72, 0x6C34, 0xA4F4, 0x6C36, 0xC9A1, 0x6C38, 0xA5C3, 0x6C3B, 0xC9A4, 0x6C3E, 0xA5C6, 0x6C3F, 0xC9A3, + 0x6C40, 0xA5C5, 0x6C41, 0xA5C4, 0x6C42, 0xA844, 0x6C43, 0xC9A2, 0x6C46, 0xC9F8, 0x6C4A, 0xC9FC, 0x6C4B, 0xC9FE, 0x6C4C, 0xCA40, + 0x6C4D, 0xA6C5, 0x6C4E, 0xA6C6, 0x6C4F, 0xC9FB, 0x6C50, 0xA6C1, 0x6C52, 0xC9F9, 0x6C54, 0xC9FD, 0x6C55, 0xA6C2, 0x6C57, 0xA6BD, + 0x6C59, 0xA6BE, 0x6C5B, 0xA6C4, 0x6C5C, 0xC9FA, 0x6C5D, 0xA6BC, 0x6C5E, 0xA845, 0x6C5F, 0xA6BF, 0x6C60, 0xA6C0, 0x6C61, 0xA6C3, + 0x6C65, 0xCB5B, 0x6C66, 0xCB59, 0x6C67, 0xCB4C, 0x6C68, 0xA851, 0x6C69, 0xCB53, 0x6C6A, 0xA84C, 0x6C6B, 0xCB4D, 0x6C6D, 0xCB55, + 0x6C6F, 0xCB52, 0x6C70, 0xA84F, 0x6C71, 0xCB51, 0x6C72, 0xA856, 0x6C73, 0xCB5A, 0x6C74, 0xA858, 0x6C76, 0xA85A, 0x6C78, 0xCB4B, + 0x6C7A, 0xA84D, 0x6C7B, 0xCB5C, 0x6C7D, 0xA854, 0x6C7E, 0xA857, 0x6C80, 0xCD45, 0x6C81, 0xA847, 0x6C82, 0xA85E, 0x6C83, 0xA855, + 0x6C84, 0xCB4E, 0x6C85, 0xA84A, 0x6C86, 0xA859, 0x6C87, 0xCB56, 0x6C88, 0xA848, 0x6C89, 0xA849, 0x6C8A, 0xCD43, 0x6C8B, 0xCB4F, + 0x6C8C, 0xA850, 0x6C8D, 0xA85B, 0x6C8E, 0xCB5D, 0x6C8F, 0xCB50, 0x6C90, 0xA84E, 0x6C92, 0xA853, 0x6C93, 0xCCEE, 0x6C94, 0xA85C, + 0x6C95, 0xCB57, 0x6C96, 0xA852, 0x6C98, 0xA85D, 0x6C99, 0xA846, 0x6C9A, 0xCB54, 0x6C9B, 0xA84B, 0x6C9C, 0xCB58, 0x6C9D, 0xCD44, + 0x6CAB, 0xAA6A, 0x6CAC, 0xAA7A, 0x6CAD, 0xCCF5, 0x6CAE, 0xAA71, 0x6CB0, 0xCD4B, 0x6CB1, 0xAA62, 0x6CB3, 0xAA65, 0x6CB4, 0xCD42, + 0x6CB6, 0xCCF3, 0x6CB7, 0xCCF7, 0x6CB8, 0xAA6D, 0x6CB9, 0xAA6F, 0x6CBA, 0xCCFA, 0x6CBB, 0xAA76, 0x6CBC, 0xAA68, 0x6CBD, 0xAA66, + 0x6CBE, 0xAA67, 0x6CBF, 0xAA75, 0x6CC0, 0xCD47, 0x6CC1, 0xAA70, 0x6CC2, 0xCCF9, 0x6CC3, 0xCCFB, 0x6CC4, 0xAA6E, 0x6CC5, 0xAA73, + 0x6CC6, 0xCCFC, 0x6CC7, 0xCD4A, 0x6CC9, 0xAC75, 0x6CCA, 0xAA79, 0x6CCC, 0xAA63, 0x6CCD, 0xCD49, 0x6CCF, 0xCD4D, 0x6CD0, 0xCCF8, + 0x6CD1, 0xCD4F, 0x6CD2, 0xCD40, 0x6CD3, 0xAA6C, 0x6CD4, 0xCCF4, 0x6CD5, 0xAA6B, 0x6CD6, 0xAA7D, 0x6CD7, 0xAA72, 0x6CD9, 0xCCF2, + 0x6CDA, 0xCF75, 0x6CDB, 0xAA78, 0x6CDC, 0xAA7C, 0x6CDD, 0xCD41, 0x6CDE, 0xCD46, 0x6CE0, 0xAA7E, 0x6CE1, 0xAA77, 0x6CE2, 0xAA69, + 0x6CE3, 0xAA5F, 0x6CE5, 0xAA64, 0x6CE7, 0xCCF6, 0x6CE8, 0xAA60, 0x6CE9, 0xCD4E, 0x6CEB, 0xCCF0, 0x6CEC, 0xCCEF, 0x6CED, 0xCCFD, + 0x6CEE, 0xCCF1, 0x6CEF, 0xAA7B, 0x6CF0, 0xAEF5, 0x6CF1, 0xAA74, 0x6CF2, 0xCCFE, 0x6CF3, 0xAA61, 0x6CF5, 0xACA6, 0x6CF9, 0xCD4C, + 0x6D00, 0xCF7C, 0x6D01, 0xCFA1, 0x6D03, 0xCFA4, 0x6D04, 0xCF77, 0x6D07, 0xCFA7, 0x6D08, 0xCFAA, 0x6D09, 0xCFAC, 0x6D0A, 0xCF74, + 0x6D0B, 0xAC76, 0x6D0C, 0xAC7B, 0x6D0D, 0xD249, 0x6D0E, 0xACAD, 0x6D0F, 0xCFA5, 0x6D10, 0xCFAD, 0x6D11, 0xCF7B, 0x6D12, 0xCF73, + 0x6D16, 0xD264, 0x6D17, 0xAC7E, 0x6D18, 0xCFA2, 0x6D19, 0xCF78, 0x6D1A, 0xCF7A, 0x6D1B, 0xACA5, 0x6D1D, 0xCF7D, 0x6D1E, 0xAC7D, + 0x6D1F, 0xCF70, 0x6D20, 0xCFA8, 0x6D22, 0xCFAB, 0x6D25, 0xAC7A, 0x6D27, 0xACA8, 0x6D28, 0xCF6D, 0x6D29, 0xACAA, 0x6D2A, 0xAC78, + 0x6D2B, 0xACAE, 0x6D2C, 0xCFA9, 0x6D2D, 0xCF6F, 0x6D2E, 0xACAB, 0x6D2F, 0xD25E, 0x6D30, 0xCD48, 0x6D31, 0xAC7C, 0x6D32, 0xAC77, + 0x6D33, 0xCF76, 0x6D34, 0xCF6E, 0x6D35, 0xACAC, 0x6D36, 0xACA4, 0x6D37, 0xCFA3, 0x6D38, 0xACA9, 0x6D39, 0xACA7, 0x6D3A, 0xCF79, + 0x6D3B, 0xACA1, 0x6D3C, 0xCF71, 0x6D3D, 0xACA2, 0x6D3E, 0xACA3, 0x6D3F, 0xCF72, 0x6D40, 0xCFA6, 0x6D41, 0xAC79, 0x6D42, 0xCF7E, + 0x6D58, 0xD24C, 0x6D59, 0xAEFD, 0x6D5A, 0xAF43, 0x6D5E, 0xD255, 0x6D5F, 0xD25B, 0x6D60, 0xD257, 0x6D61, 0xD24A, 0x6D62, 0xD24D, + 0x6D63, 0xD246, 0x6D64, 0xD247, 0x6D65, 0xAF4A, 0x6D66, 0xAEFA, 0x6D67, 0xD256, 0x6D68, 0xD25F, 0x6D69, 0xAF45, 0x6D6A, 0xAEF6, + 0x6D6C, 0xAF40, 0x6D6D, 0xD24E, 0x6D6E, 0xAF42, 0x6D6F, 0xD24F, 0x6D70, 0xD259, 0x6D74, 0xAF44, 0x6D75, 0xD268, 0x6D76, 0xD248, + 0x6D77, 0xAEFC, 0x6D78, 0xAEFB, 0x6D79, 0xAF48, 0x6D7A, 0xD245, 0x6D7B, 0xD266, 0x6D7C, 0xD25A, 0x6D7D, 0xD267, 0x6D7E, 0xD261, + 0x6D7F, 0xD253, 0x6D80, 0xD262, 0x6D82, 0xD25C, 0x6D83, 0xD265, 0x6D84, 0xD263, 0x6D85, 0xAF49, 0x6D86, 0xD254, 0x6D87, 0xAEF9, + 0x6D88, 0xAEF8, 0x6D89, 0xAF41, 0x6D8A, 0xAF47, 0x6D8B, 0xD260, 0x6D8C, 0xAF46, 0x6D8D, 0xD251, 0x6D8E, 0xB243, 0x6D90, 0xD269, + 0x6D91, 0xD250, 0x6D92, 0xD24B, 0x6D93, 0xAEFE, 0x6D94, 0xAF4B, 0x6D95, 0xAEF7, 0x6D97, 0xD258, 0x6D98, 0xD25D, 0x6DAA, 0xB265, + 0x6DAB, 0xD5E1, 0x6DAC, 0xD5E5, 0x6DAE, 0xB252, 0x6DAF, 0xB250, 0x6DB2, 0xB247, 0x6DB3, 0xD5E3, 0x6DB4, 0xD5E2, 0x6DB5, 0xB25B, + 0x6DB7, 0xD5E8, 0x6DB8, 0xB255, 0x6DBA, 0xD5FA, 0x6DBB, 0xD647, 0x6DBC, 0xB244, 0x6DBD, 0xD5F7, 0x6DBE, 0xD5F0, 0x6DBF, 0xB267, + 0x6DC0, 0xD5E0, 0x6DC2, 0xD5FC, 0x6DC4, 0xB264, 0x6DC5, 0xB258, 0x6DC6, 0xB263, 0x6DC7, 0xB24E, 0x6DC8, 0xD5EC, 0x6DC9, 0xD5FE, + 0x6DCA, 0xD5F6, 0x6DCB, 0xB24F, 0x6DCC, 0xB249, 0x6DCD, 0xD645, 0x6DCF, 0xD5FD, 0x6DD0, 0xD640, 0x6DD1, 0xB251, 0x6DD2, 0xB259, + 0x6DD3, 0xD642, 0x6DD4, 0xD5EA, 0x6DD5, 0xD5FB, 0x6DD6, 0xD5EF, 0x6DD7, 0xD644, 0x6DD8, 0xB25E, 0x6DD9, 0xB246, 0x6DDA, 0xB25C, + 0x6DDB, 0xD5F4, 0x6DDC, 0xD5F2, 0x6DDD, 0xD5F3, 0x6DDE, 0xB253, 0x6DDF, 0xD5EE, 0x6DE0, 0xD5ED, 0x6DE1, 0xB248, 0x6DE2, 0xD5E7, + 0x6DE3, 0xD646, 0x6DE4, 0xB24A, 0x6DE5, 0xD5F1, 0x6DE6, 0xB268, 0x6DE8, 0xB262, 0x6DE9, 0xD5E6, 0x6DEA, 0xB25F, 0x6DEB, 0xB25D, + 0x6DEC, 0xB266, 0x6DED, 0xD5F8, 0x6DEE, 0xB261, 0x6DEF, 0xD252, 0x6DF0, 0xD5F9, 0x6DF1, 0xB260, 0x6DF2, 0xD641, 0x6DF3, 0xB245, + 0x6DF4, 0xD5F5, 0x6DF5, 0xB257, 0x6DF6, 0xD5E9, 0x6DF7, 0xB256, 0x6DF9, 0xB254, 0x6DFA, 0xB24C, 0x6DFB, 0xB24B, 0x6DFC, 0xD9E7, + 0x6DFD, 0xD643, 0x6E00, 0xD5EB, 0x6E03, 0xD9FC, 0x6E05, 0xB24D, 0x6E19, 0xB541, 0x6E1A, 0xB25A, 0x6E1B, 0xB4EE, 0x6E1C, 0xD9F6, + 0x6E1D, 0xB4FC, 0x6E1F, 0xD9EA, 0x6E20, 0xB4EB, 0x6E21, 0xB4E7, 0x6E22, 0xDA49, 0x6E23, 0xB4ED, 0x6E24, 0xB4F1, 0x6E25, 0xB4EC, + 0x6E26, 0xB4F5, 0x6E27, 0xDA4D, 0x6E28, 0xDA44, 0x6E2B, 0xD9F1, 0x6E2C, 0xB4FA, 0x6E2D, 0xB4F4, 0x6E2E, 0xD9FD, 0x6E2F, 0xB4E4, + 0x6E30, 0xDA4A, 0x6E31, 0xDA43, 0x6E32, 0xB4E8, 0x6E33, 0xD9F7, 0x6E34, 0xB4F7, 0x6E35, 0xDA55, 0x6E36, 0xDA56, 0x6E38, 0xB4E5, + 0x6E39, 0xDA48, 0x6E3A, 0xB4F9, 0x6E3B, 0xD9FB, 0x6E3C, 0xD9ED, 0x6E3D, 0xD9EE, 0x6E3E, 0xB4FD, 0x6E3F, 0xD9F2, 0x6E40, 0xD9F9, + 0x6E41, 0xD9F3, 0x6E43, 0xB4FB, 0x6E44, 0xB544, 0x6E45, 0xD9EF, 0x6E46, 0xD9E8, 0x6E47, 0xD9E9, 0x6E49, 0xD9EB, 0x6E4A, 0xB4EA, + 0x6E4B, 0xD9F8, 0x6E4D, 0xB4F8, 0x6E4E, 0xB542, 0x6E51, 0xD9FA, 0x6E52, 0xDA53, 0x6E53, 0xDA4B, 0x6E54, 0xB4E6, 0x6E55, 0xDA51, + 0x6E56, 0xB4F2, 0x6E58, 0xB4F0, 0x6E5A, 0xDA57, 0x6E5B, 0xB4EF, 0x6E5C, 0xDA41, 0x6E5D, 0xD9F4, 0x6E5E, 0xD9FE, 0x6E5F, 0xB547, + 0x6E60, 0xDA45, 0x6E61, 0xDA42, 0x6E62, 0xD9F0, 0x6E63, 0xB543, 0x6E64, 0xDA4F, 0x6E65, 0xDA4C, 0x6E66, 0xDA54, 0x6E67, 0xB4E9, + 0x6E68, 0xDA40, 0x6E69, 0xB546, 0x6E6B, 0xDA47, 0x6E6E, 0xB4F3, 0x6E6F, 0xB4F6, 0x6E71, 0xDA46, 0x6E72, 0xB545, 0x6E73, 0xD9F5, + 0x6E74, 0xD5E4, 0x6E77, 0xDA50, 0x6E78, 0xDA4E, 0x6E79, 0xDA52, 0x6E88, 0xD9EC, 0x6E89, 0xB540, 0x6E8D, 0xDE61, 0x6E8E, 0xDE60, + 0x6E8F, 0xDE46, 0x6E90, 0xB7BD, 0x6E92, 0xDE5F, 0x6E93, 0xDE49, 0x6E94, 0xDE4A, 0x6E96, 0xB7C7, 0x6E97, 0xDE68, 0x6E98, 0xB7C2, + 0x6E99, 0xDE5E, 0x6E9B, 0xDE43, 0x6E9C, 0xB7C8, 0x6E9D, 0xB7BE, 0x6E9E, 0xDE52, 0x6E9F, 0xDE48, 0x6EA0, 0xDE4B, 0x6EA1, 0xDE63, + 0x6EA2, 0xB7B8, 0x6EA3, 0xDE6A, 0x6EA4, 0xDE62, 0x6EA5, 0xB7C1, 0x6EA6, 0xDE57, 0x6EA7, 0xB7CC, 0x6EAA, 0xB7CB, 0x6EAB, 0xB7C5, + 0x6EAE, 0xDE69, 0x6EAF, 0xB7B9, 0x6EB0, 0xDE55, 0x6EB1, 0xDE4C, 0x6EB2, 0xDE59, 0x6EB3, 0xDE65, 0x6EB4, 0xB7CD, 0x6EB6, 0xB7BB, + 0x6EB7, 0xDE54, 0x6EB9, 0xDE4D, 0x6EBA, 0xB7C4, 0x6EBC, 0xB7C3, 0x6EBD, 0xDE50, 0x6EBE, 0xDE5A, 0x6EBF, 0xDE64, 0x6EC0, 0xDE47, + 0x6EC1, 0xDE51, 0x6EC2, 0xB7BC, 0x6EC3, 0xDE5B, 0x6EC4, 0xB7C9, 0x6EC5, 0xB7C0, 0x6EC6, 0xDE4E, 0x6EC7, 0xB7BF, 0x6EC8, 0xDE45, + 0x6EC9, 0xDE53, 0x6ECA, 0xDE67, 0x6ECB, 0xB4FE, 0x6ECC, 0xBAB0, 0x6ECD, 0xDE56, 0x6ECE, 0xE26C, 0x6ECF, 0xDE58, 0x6ED0, 0xDE66, + 0x6ED1, 0xB7C6, 0x6ED2, 0xDE4F, 0x6ED3, 0xB7BA, 0x6ED4, 0xB7CA, 0x6ED5, 0xBCF0, 0x6ED6, 0xDE44, 0x6ED8, 0xDE5D, 0x6EDC, 0xDE5C, + 0x6EEB, 0xE2AA, 0x6EEC, 0xBAAD, 0x6EED, 0xE27D, 0x6EEE, 0xE2A4, 0x6EEF, 0xBAA2, 0x6EF1, 0xE26E, 0x6EF2, 0xBAAF, 0x6EF4, 0xBA77, + 0x6EF5, 0xE26D, 0x6EF6, 0xE2B0, 0x6EF7, 0xBAB1, 0x6EF8, 0xE271, 0x6EF9, 0xE2A3, 0x6EFB, 0xE273, 0x6EFC, 0xE2B3, 0x6EFD, 0xE2AF, + 0x6EFE, 0xBA75, 0x6EFF, 0xBAA1, 0x6F00, 0xE653, 0x6F01, 0xBAAE, 0x6F02, 0xBA7D, 0x6F03, 0xE26F, 0x6F05, 0xE2AE, 0x6F06, 0xBAA3, + 0x6F07, 0xE2AB, 0x6F08, 0xE2B8, 0x6F09, 0xE275, 0x6F0A, 0xE27E, 0x6F0D, 0xE2B6, 0x6F0E, 0xE2AC, 0x6F0F, 0xBA7C, 0x6F12, 0xE27C, + 0x6F13, 0xBA76, 0x6F14, 0xBA74, 0x6F15, 0xBAA8, 0x6F18, 0xE27A, 0x6F19, 0xE277, 0x6F1A, 0xE278, 0x6F1C, 0xE2B2, 0x6F1E, 0xE2B7, + 0x6F1F, 0xE2B5, 0x6F20, 0xBA7A, 0x6F21, 0xE2B9, 0x6F22, 0xBA7E, 0x6F23, 0xBAA7, 0x6F25, 0xE270, 0x6F26, 0xE5FA, 0x6F27, 0xE279, + 0x6F29, 0xBA78, 0x6F2A, 0xBAAC, 0x6F2B, 0xBAA9, 0x6F2C, 0xBA7B, 0x6F2D, 0xE2A5, 0x6F2E, 0xE274, 0x6F2F, 0xBAAA, 0x6F30, 0xE2A7, + 0x6F31, 0xBAA4, 0x6F32, 0xBAA6, 0x6F33, 0xBA73, 0x6F35, 0xE2A9, 0x6F36, 0xE2A1, 0x6F37, 0xE272, 0x6F38, 0xBAA5, 0x6F39, 0xE2B1, + 0x6F3A, 0xE2B4, 0x6F3B, 0xE27B, 0x6F3C, 0xE2A8, 0x6F3E, 0xBA79, 0x6F3F, 0xBCDF, 0x6F40, 0xE2A6, 0x6F41, 0xE5F9, 0x6F43, 0xE2AD, + 0x6F4E, 0xE276, 0x6F4F, 0xE644, 0x6F50, 0xE64E, 0x6F51, 0xBCE2, 0x6F52, 0xE64D, 0x6F53, 0xE659, 0x6F54, 0xBCE4, 0x6F55, 0xE64B, + 0x6F57, 0xE64F, 0x6F58, 0xBCEF, 0x6F5A, 0xE646, 0x6F5B, 0xBCE7, 0x6F5D, 0xE652, 0x6F5E, 0xE9F0, 0x6F5F, 0xBCF3, 0x6F60, 0xBCF2, + 0x6F61, 0xE654, 0x6F62, 0xE643, 0x6F63, 0xE65E, 0x6F64, 0xBCED, 0x6F66, 0xBCE3, 0x6F67, 0xE657, 0x6F69, 0xE65B, 0x6F6A, 0xE660, + 0x6F6B, 0xE655, 0x6F6C, 0xE649, 0x6F6D, 0xBCE6, 0x6F6E, 0xBCE9, 0x6F6F, 0xBCF1, 0x6F70, 0xBCEC, 0x6F72, 0xE64C, 0x6F73, 0xE2A2, + 0x6F76, 0xE648, 0x6F77, 0xE65F, 0x6F78, 0xBCE8, 0x6F7A, 0xBCEB, 0x6F7B, 0xE661, 0x6F7C, 0xBCE0, 0x6F7D, 0xE656, 0x6F7E, 0xE5FB, + 0x6F7F, 0xE65C, 0x6F80, 0xC0DF, 0x6F82, 0xE64A, 0x6F84, 0xBCE1, 0x6F85, 0xE645, 0x6F86, 0xBCE5, 0x6F87, 0xE5FC, 0x6F88, 0xBAAB, + 0x6F89, 0xE641, 0x6F8B, 0xE65A, 0x6F8C, 0xE642, 0x6F8D, 0xE640, 0x6F8E, 0xBCEA, 0x6F90, 0xE658, 0x6F92, 0xE5FE, 0x6F93, 0xE651, + 0x6F94, 0xE650, 0x6F95, 0xE65D, 0x6F96, 0xE647, 0x6F97, 0xBCEE, 0x6F9E, 0xE9F3, 0x6FA0, 0xBF49, 0x6FA1, 0xBEFE, 0x6FA2, 0xEA40, + 0x6FA3, 0xE9EB, 0x6FA4, 0xBF41, 0x6FA5, 0xE9F7, 0x6FA6, 0xBF48, 0x6FA7, 0xBF43, 0x6FA8, 0xE9F5, 0x6FA9, 0xED4F, 0x6FAA, 0xE9FB, + 0x6FAB, 0xEA42, 0x6FAC, 0xE9FA, 0x6FAD, 0xE9E9, 0x6FAE, 0xE9F8, 0x6FAF, 0xEA44, 0x6FB0, 0xEA46, 0x6FB1, 0xBEFD, 0x6FB2, 0xEA45, + 0x6FB3, 0xBF44, 0x6FB4, 0xBF4A, 0x6FB6, 0xBF47, 0x6FB8, 0xE9FE, 0x6FB9, 0xBF46, 0x6FBA, 0xE9F9, 0x6FBC, 0xE9ED, 0x6FBD, 0xE9F2, + 0x6FBF, 0xE9FD, 0x6FC0, 0xBF45, 0x6FC1, 0xBF42, 0x6FC2, 0xBEFC, 0x6FC3, 0xBF40, 0x6FC4, 0xE9F1, 0x6FC6, 0xE5FD, 0x6FC7, 0xE9EC, + 0x6FC8, 0xE9EF, 0x6FC9, 0xEA41, 0x6FCA, 0xE9F4, 0x6FCB, 0xE9EA, 0x6FCC, 0xED4E, 0x6FCD, 0xEA43, 0x6FCE, 0xE9EE, 0x6FCF, 0xE9FC, + 0x6FD4, 0xED51, 0x6FD5, 0xC0E3, 0x6FD8, 0xC0D7, 0x6FDB, 0xC0DB, 0x6FDC, 0xED53, 0x6FDD, 0xED59, 0x6FDE, 0xED57, 0x6FDF, 0xC0D9, + 0x6FE0, 0xC0DA, 0x6FE1, 0xC0E1, 0x6FE2, 0xED5A, 0x6FE3, 0xED52, 0x6FE4, 0xC0DC, 0x6FE6, 0xED56, 0x6FE7, 0xED55, 0x6FE8, 0xED5B, + 0x6FE9, 0xC0E2, 0x6FEB, 0xC0DD, 0x6FEC, 0xC0E0, 0x6FED, 0xED54, 0x6FEE, 0xC0E4, 0x6FEF, 0xC0DE, 0x6FF0, 0xC0E5, 0x6FF1, 0xC0D8, + 0x6FF2, 0xED58, 0x6FF4, 0xED50, 0x6FF7, 0xEFF7, 0x6FFA, 0xC271, 0x6FFB, 0xEFF4, 0x6FFC, 0xEFF6, 0x6FFE, 0xC26F, 0x6FFF, 0xEFF2, + 0x7000, 0xEFF3, 0x7001, 0xEFEE, 0x7004, 0xE9F6, 0x7005, 0xEFEF, 0x7006, 0xC270, 0x7007, 0xEFEB, 0x7009, 0xC26D, 0x700A, 0xEFF8, + 0x700B, 0xC26E, 0x700C, 0xEFEC, 0x700D, 0xEFED, 0x700E, 0xEFF1, 0x700F, 0xC273, 0x7011, 0xC272, 0x7014, 0xEFF0, 0x7015, 0xC378, + 0x7016, 0xF25F, 0x7017, 0xF265, 0x7018, 0xC379, 0x7019, 0xF25C, 0x701A, 0xC376, 0x701B, 0xC373, 0x701C, 0xF267, 0x701D, 0xC377, + 0x701F, 0xC374, 0x7020, 0xF25E, 0x7021, 0xF261, 0x7022, 0xF262, 0x7023, 0xF263, 0x7024, 0xF266, 0x7026, 0xEFF5, 0x7027, 0xF25D, + 0x7028, 0xC375, 0x7029, 0xF264, 0x702A, 0xF268, 0x702B, 0xF260, 0x702F, 0xF45D, 0x7030, 0xC46A, 0x7031, 0xF460, 0x7032, 0xC46B, + 0x7033, 0xF468, 0x7034, 0xF45F, 0x7035, 0xF45C, 0x7037, 0xF45E, 0x7038, 0xF462, 0x7039, 0xF465, 0x703A, 0xF464, 0x703B, 0xF467, + 0x703C, 0xF45B, 0x703E, 0xC469, 0x703F, 0xF463, 0x7040, 0xF466, 0x7041, 0xF469, 0x7042, 0xF461, 0x7043, 0xF5D3, 0x7044, 0xF5D4, + 0x7045, 0xF5D8, 0x7046, 0xF5D9, 0x7048, 0xF5D6, 0x7049, 0xF5D7, 0x704A, 0xF5D5, 0x704C, 0xC4E9, 0x7051, 0xC578, 0x7052, 0xF6EB, + 0x7055, 0xF6E8, 0x7056, 0xF6E9, 0x7057, 0xF6EA, 0x7058, 0xC579, 0x705A, 0xF7E5, 0x705B, 0xF7E4, 0x705D, 0xF8AF, 0x705E, 0xC5F4, + 0x705F, 0xF8AD, 0x7060, 0xF8B0, 0x7061, 0xF8AE, 0x7062, 0xF8F5, 0x7063, 0xC657, 0x7064, 0xC665, 0x7065, 0xF9A3, 0x7066, 0xF96C, + 0x7068, 0xF9A2, 0x7069, 0xF9D0, 0x706A, 0xF9D1, 0x706B, 0xA4F5, 0x7070, 0xA6C7, 0x7071, 0xCA41, 0x7074, 0xCB5E, 0x7076, 0xA85F, + 0x7078, 0xA862, 0x707A, 0xCB5F, 0x707C, 0xA860, 0x707D, 0xA861, 0x7082, 0xCD58, 0x7083, 0xCD5A, 0x7084, 0xCD55, 0x7085, 0xCD52, + 0x7086, 0xCD54, 0x708A, 0xAAA4, 0x708E, 0xAAA2, 0x7091, 0xCD56, 0x7092, 0xAAA3, 0x7093, 0xCD53, 0x7094, 0xCD50, 0x7095, 0xAAA1, + 0x7096, 0xCD57, 0x7098, 0xCD51, 0x7099, 0xAAA5, 0x709A, 0xCD59, 0x709F, 0xCFAF, 0x70A1, 0xCFB3, 0x70A4, 0xACB7, 0x70A9, 0xCFB6, + 0x70AB, 0xACAF, 0x70AC, 0xACB2, 0x70AD, 0xACB4, 0x70AE, 0xACB6, 0x70AF, 0xACB3, 0x70B0, 0xCFB2, 0x70B1, 0xCFB1, 0x70B3, 0xACB1, + 0x70B4, 0xCFB4, 0x70B5, 0xCFB5, 0x70B7, 0xCFAE, 0x70B8, 0xACB5, 0x70BA, 0xACB0, 0x70BE, 0xCFB0, 0x70C5, 0xD277, 0x70C6, 0xD278, + 0x70C7, 0xD279, 0x70C8, 0xAF50, 0x70CA, 0xAF4C, 0x70CB, 0xD26E, 0x70CD, 0xD276, 0x70CE, 0xD27B, 0x70CF, 0xAF51, 0x70D1, 0xD26C, + 0x70D2, 0xD272, 0x70D3, 0xD26B, 0x70D4, 0xD275, 0x70D7, 0xD271, 0x70D8, 0xAF4D, 0x70D9, 0xAF4F, 0x70DA, 0xD27A, 0x70DC, 0xD26A, + 0x70DD, 0xD26D, 0x70DE, 0xD273, 0x70E0, 0xD274, 0x70E1, 0xD27C, 0x70E2, 0xD270, 0x70E4, 0xAF4E, 0x70EF, 0xB26D, 0x70F0, 0xD64E, + 0x70F3, 0xD650, 0x70F4, 0xD64C, 0x70F6, 0xD658, 0x70F7, 0xD64A, 0x70F8, 0xD657, 0x70F9, 0xB269, 0x70FA, 0xD648, 0x70FB, 0xDA5B, + 0x70FC, 0xD652, 0x70FD, 0xB26C, 0x70FF, 0xD653, 0x7100, 0xD656, 0x7102, 0xD65A, 0x7104, 0xD64F, 0x7106, 0xD654, 0x7109, 0xB26A, + 0x710A, 0xB26B, 0x710B, 0xD659, 0x710C, 0xD64D, 0x710D, 0xD649, 0x710E, 0xD65B, 0x7110, 0xD651, 0x7113, 0xD655, 0x7117, 0xD64B, + 0x7119, 0xB548, 0x711A, 0xB549, 0x711B, 0xDA65, 0x711C, 0xB54F, 0x711E, 0xDA59, 0x711F, 0xDA62, 0x7120, 0xDA58, 0x7121, 0xB54C, + 0x7122, 0xDA60, 0x7123, 0xDA5E, 0x7125, 0xDA5F, 0x7126, 0xB54A, 0x7128, 0xDA63, 0x712E, 0xDA5C, 0x712F, 0xDA5A, 0x7130, 0xB54B, + 0x7131, 0xDA5D, 0x7132, 0xDA61, 0x7136, 0xB54D, 0x713A, 0xDA64, 0x7141, 0xDE70, 0x7142, 0xDE77, 0x7143, 0xDE79, 0x7144, 0xDEA1, + 0x7146, 0xB7DA, 0x7147, 0xDE6B, 0x7149, 0xB7D2, 0x714B, 0xDE7A, 0x714C, 0xB7D7, 0x714D, 0xDEA2, 0x714E, 0xB7CE, 0x7150, 0xDE7D, + 0x7152, 0xDE6D, 0x7153, 0xDE7E, 0x7154, 0xDE6C, 0x7156, 0xB7DC, 0x7158, 0xDE78, 0x7159, 0xB7CF, 0x715A, 0xDEA3, 0x715C, 0xB7D4, + 0x715D, 0xDE71, 0x715E, 0xB7D9, 0x715F, 0xDE7C, 0x7160, 0xDE6F, 0x7161, 0xDE76, 0x7162, 0xDE72, 0x7163, 0xDE6E, 0x7164, 0xB7D1, + 0x7165, 0xB7D8, 0x7166, 0xB7D6, 0x7167, 0xB7D3, 0x7168, 0xB7DB, 0x7169, 0xB7D0, 0x716A, 0xDE75, 0x716C, 0xB7D5, 0x716E, 0xB54E, + 0x7170, 0xDE7B, 0x7172, 0xDE73, 0x7178, 0xDE74, 0x717B, 0xE2C1, 0x717D, 0xBAB4, 0x7180, 0xE2BD, 0x7181, 0xE2C3, 0x7182, 0xE2BF, + 0x7184, 0xBAB6, 0x7185, 0xE2BE, 0x7186, 0xE2C2, 0x7187, 0xE2BA, 0x7189, 0xE2BC, 0x718A, 0xBAB5, 0x718F, 0xE2C0, 0x7190, 0xE2BB, + 0x7192, 0xBAB7, 0x7194, 0xBAB2, 0x7197, 0xE2C4, 0x7199, 0xBAB3, 0x719A, 0xE667, 0x719B, 0xE664, 0x719C, 0xE670, 0x719D, 0xE66A, + 0x719E, 0xE66C, 0x719F, 0xBCF4, 0x71A0, 0xE666, 0x71A1, 0xE66E, 0x71A4, 0xE66D, 0x71A5, 0xE66B, 0x71A7, 0xE671, 0x71A8, 0xBCF7, + 0x71A9, 0xE668, 0x71AA, 0xE66F, 0x71AC, 0xBCF5, 0x71AF, 0xE663, 0x71B0, 0xE665, 0x71B1, 0xBCF6, 0x71B2, 0xE662, 0x71B3, 0xE672, + 0x71B5, 0xE669, 0x71B8, 0xEA4A, 0x71B9, 0xBF51, 0x71BC, 0xEA55, 0x71BD, 0xEA53, 0x71BE, 0xBF4B, 0x71BF, 0xEA49, 0x71C0, 0xEA4C, + 0x71C1, 0xEA4D, 0x71C2, 0xEA48, 0x71C3, 0xBF55, 0x71C4, 0xBF56, 0x71C5, 0xEA47, 0x71C6, 0xEA56, 0x71C7, 0xEA51, 0x71C8, 0xBF4F, + 0x71C9, 0xBF4C, 0x71CA, 0xEA50, 0x71CB, 0xEA4E, 0x71CE, 0xBF52, 0x71CF, 0xEA52, 0x71D0, 0xBF4D, 0x71D2, 0xBF4E, 0x71D4, 0xEA4F, + 0x71D5, 0xBF50, 0x71D6, 0xEA4B, 0x71D8, 0xEA54, 0x71D9, 0xBF53, 0x71DA, 0xEA57, 0x71DB, 0xEA58, 0x71DC, 0xBF54, 0x71DF, 0xC0E7, + 0x71E0, 0xC0EE, 0x71E1, 0xED5C, 0x71E2, 0xED62, 0x71E4, 0xED60, 0x71E5, 0xC0EA, 0x71E6, 0xC0E9, 0x71E7, 0xC0E6, 0x71E8, 0xED5E, + 0x71EC, 0xC0EC, 0x71ED, 0xC0EB, 0x71EE, 0xC0E8, 0x71F0, 0xED61, 0x71F1, 0xED5D, 0x71F2, 0xED5F, 0x71F4, 0xC0ED, 0x71F8, 0xC277, + 0x71F9, 0xEFFB, 0x71FB, 0xC274, 0x71FC, 0xC275, 0x71FD, 0xEFFD, 0x71FE, 0xC276, 0x71FF, 0xEFFA, 0x7201, 0xEFF9, 0x7202, 0xF26C, + 0x7203, 0xEFFC, 0x7205, 0xF26D, 0x7206, 0xC37A, 0x7207, 0xF26B, 0x720A, 0xF26A, 0x720C, 0xF269, 0x720D, 0xC37B, 0x7210, 0xC46C, + 0x7213, 0xF46A, 0x7214, 0xF46B, 0x7219, 0xF5DC, 0x721A, 0xF5DB, 0x721B, 0xC4EA, 0x721D, 0xF5DA, 0x721E, 0xF6EC, 0x721F, 0xF6ED, + 0x7222, 0xF7E6, 0x7223, 0xF8B1, 0x7226, 0xF8F6, 0x7227, 0xF9BC, 0x7228, 0xC679, 0x7229, 0xF9C6, 0x722A, 0xA4F6, 0x722C, 0xAAA6, + 0x722D, 0xAAA7, 0x7230, 0xACB8, 0x7235, 0xC0EF, 0x7236, 0xA4F7, 0x7238, 0xAAA8, 0x7239, 0xAF52, 0x723A, 0xB7DD, 0x723B, 0xA4F8, + 0x723D, 0xB26E, 0x723E, 0xBAB8, 0x723F, 0xC962, 0x7241, 0xCFB7, 0x7242, 0xD27D, 0x7244, 0xE2C5, 0x7246, 0xC0F0, 0x7247, 0xA4F9, + 0x7248, 0xAAA9, 0x7249, 0xCFB8, 0x724A, 0xCFB9, 0x724B, 0xDA66, 0x724C, 0xB550, 0x724F, 0xDEA4, 0x7252, 0xB7DE, 0x7253, 0xE2C6, + 0x7256, 0xBCF8, 0x7258, 0xC37C, 0x7259, 0xA4FA, 0x725A, 0xDA67, 0x725B, 0xA4FB, 0x725D, 0xA6C9, 0x725E, 0xCA42, 0x725F, 0xA6C8, + 0x7260, 0xA865, 0x7261, 0xA864, 0x7262, 0xA863, 0x7263, 0xCB60, 0x7267, 0xAAAA, 0x7269, 0xAAAB, 0x726A, 0xCD5B, 0x726C, 0xCFBA, + 0x726E, 0xCFBD, 0x726F, 0xACBA, 0x7270, 0xCFBB, 0x7272, 0xACB9, 0x7273, 0xCFBC, 0x7274, 0xACBB, 0x7276, 0xD2A2, 0x7277, 0xD2A1, + 0x7278, 0xD27E, 0x7279, 0xAF53, 0x727B, 0xD65D, 0x727C, 0xD65E, 0x727D, 0xB26F, 0x727E, 0xD65C, 0x727F, 0xD65F, 0x7280, 0xB552, + 0x7281, 0xB270, 0x7284, 0xB551, 0x7285, 0xDA6B, 0x7286, 0xDA6A, 0x7288, 0xDA68, 0x7289, 0xDA69, 0x728B, 0xDA6C, 0x728C, 0xDEA6, + 0x728D, 0xDEA5, 0x728E, 0xDEA9, 0x7290, 0xDEA8, 0x7291, 0xDEA7, 0x7292, 0xBAB9, 0x7293, 0xE2C9, 0x7295, 0xE2C8, 0x7296, 0xBABA, + 0x7297, 0xE2C7, 0x7298, 0xE673, 0x729A, 0xE674, 0x729B, 0xBCF9, 0x729D, 0xEA59, 0x729E, 0xEA5A, 0x72A1, 0xF272, 0x72A2, 0xC37D, + 0x72A3, 0xF271, 0x72A4, 0xF270, 0x72A5, 0xF26E, 0x72A6, 0xF26F, 0x72A7, 0xC4EB, 0x72A8, 0xF46C, 0x72A9, 0xF6EE, 0x72AA, 0xF8F7, + 0x72AC, 0xA4FC, 0x72AE, 0xC9A5, 0x72AF, 0xA5C7, 0x72B0, 0xC9A6, 0x72B4, 0xCA43, 0x72B5, 0xCA44, 0x72BA, 0xCB66, 0x72BD, 0xCB62, + 0x72BF, 0xCB61, 0x72C0, 0xAAAC, 0x72C1, 0xCB65, 0x72C2, 0xA867, 0x72C3, 0xCB63, 0x72C4, 0xA866, 0x72C5, 0xCB67, 0x72C6, 0xCB64, + 0x72C9, 0xCD5F, 0x72CA, 0xCFBE, 0x72CB, 0xCD5D, 0x72CC, 0xCD64, 0x72CE, 0xAAAD, 0x72D0, 0xAAB0, 0x72D1, 0xCD65, 0x72D2, 0xCD61, + 0x72D4, 0xCD62, 0x72D6, 0xCD5C, 0x72D7, 0xAAAF, 0x72D8, 0xCD5E, 0x72D9, 0xAAAE, 0x72DA, 0xCD63, 0x72DC, 0xCD60, 0x72DF, 0xCFC2, + 0x72E0, 0xACBD, 0x72E1, 0xACBE, 0x72E3, 0xCFC5, 0x72E4, 0xCFBF, 0x72E6, 0xCFC4, 0x72E8, 0xCFC0, 0x72E9, 0xACBC, 0x72EA, 0xCFC3, + 0x72EB, 0xCFC1, 0x72F3, 0xD2A8, 0x72F4, 0xD2A5, 0x72F6, 0xD2A7, 0x72F7, 0xAF58, 0x72F8, 0xAF57, 0x72F9, 0xAF55, 0x72FA, 0xD2A4, + 0x72FB, 0xD2A9, 0x72FC, 0xAF54, 0x72FD, 0xAF56, 0x72FE, 0xD2A6, 0x72FF, 0xD667, 0x7300, 0xD2A3, 0x7301, 0xD2AA, 0x7307, 0xD662, + 0x7308, 0xD666, 0x730A, 0xD665, 0x730B, 0xDA6E, 0x730C, 0xDA79, 0x730F, 0xD668, 0x7311, 0xD663, 0x7312, 0xDA6D, 0x7313, 0xB274, + 0x7316, 0xB273, 0x7317, 0xD661, 0x7318, 0xD664, 0x7319, 0xB275, 0x731B, 0xB272, 0x731C, 0xB271, 0x731D, 0xD660, 0x731E, 0xD669, + 0x7322, 0xDA70, 0x7323, 0xDA77, 0x7325, 0xB554, 0x7326, 0xDA76, 0x7327, 0xDA73, 0x7329, 0xB556, 0x732D, 0xDA75, 0x7330, 0xDA6F, + 0x7331, 0xDA71, 0x7332, 0xDA74, 0x7333, 0xDA72, 0x7334, 0xB555, 0x7335, 0xDA78, 0x7336, 0xB553, 0x7337, 0xB7DF, 0x733A, 0xDEAD, + 0x733B, 0xDEAC, 0x733C, 0xDEAA, 0x733E, 0xB7E2, 0x733F, 0xB7E1, 0x7340, 0xDEAE, 0x7342, 0xDEAB, 0x7343, 0xE2CA, 0x7344, 0xBABB, + 0x7345, 0xB7E0, 0x7349, 0xDEB0, 0x734A, 0xDEAF, 0x734C, 0xE2CD, 0x734D, 0xE2CB, 0x734E, 0xBCFA, 0x7350, 0xBABC, 0x7351, 0xE2CC, + 0x7352, 0xE676, 0x7357, 0xBCFB, 0x7358, 0xE675, 0x7359, 0xE67E, 0x735A, 0xE67D, 0x735B, 0xE67B, 0x735D, 0xE67A, 0x735E, 0xE677, + 0x735F, 0xE678, 0x7360, 0xE679, 0x7361, 0xE67C, 0x7362, 0xE6A1, 0x7365, 0xEA5F, 0x7366, 0xEA5C, 0x7367, 0xEA5D, 0x7368, 0xBF57, + 0x7369, 0xEA5B, 0x736A, 0xEA61, 0x736B, 0xEA60, 0x736C, 0xEA5E, 0x736E, 0xED64, 0x736F, 0xED65, 0x7370, 0xC0F1, 0x7372, 0xC0F2, + 0x7373, 0xED63, 0x7375, 0xC279, 0x7376, 0xEFFE, 0x7377, 0xC278, 0x7378, 0xC37E, 0x737A, 0xC3A1, 0x737B, 0xC46D, 0x737C, 0xF46E, + 0x737D, 0xF46D, 0x737E, 0xF5DD, 0x737F, 0xF6EF, 0x7380, 0xC57A, 0x7381, 0xF7E8, 0x7382, 0xF7E7, 0x7383, 0xF7E9, 0x7384, 0xA5C8, + 0x7385, 0xCFC6, 0x7386, 0xAF59, 0x7387, 0xB276, 0x7388, 0xD66A, 0x7389, 0xA5C9, 0x738A, 0xC9A7, 0x738B, 0xA4FD, 0x738E, 0xCA45, + 0x7392, 0xCB6C, 0x7393, 0xCB6A, 0x7394, 0xCB6B, 0x7395, 0xCB68, 0x7396, 0xA868, 0x7397, 0xCB69, 0x739D, 0xCD6D, 0x739F, 0xAAB3, + 0x73A0, 0xCD6B, 0x73A1, 0xCD67, 0x73A2, 0xCD6A, 0x73A4, 0xCD66, 0x73A5, 0xAAB5, 0x73A6, 0xCD69, 0x73A8, 0xAAB2, 0x73A9, 0xAAB1, + 0x73AB, 0xAAB4, 0x73AC, 0xCD6C, 0x73AD, 0xCD68, 0x73B2, 0xACC2, 0x73B3, 0xACC5, 0x73B4, 0xCFCE, 0x73B5, 0xCFCD, 0x73B6, 0xCFCC, + 0x73B7, 0xACBF, 0x73B8, 0xCFD5, 0x73B9, 0xCFCB, 0x73BB, 0xACC1, 0x73BC, 0xD2AF, 0x73BE, 0xCFD2, 0x73BF, 0xCFD0, 0x73C0, 0xACC4, + 0x73C2, 0xCFC8, 0x73C3, 0xCFD3, 0x73C5, 0xCFCA, 0x73C6, 0xCFD4, 0x73C7, 0xCFD1, 0x73C8, 0xCFC9, 0x73CA, 0xACC0, 0x73CB, 0xCFD6, + 0x73CC, 0xCFC7, 0x73CD, 0xACC3, 0x73D2, 0xD2B4, 0x73D3, 0xD2AB, 0x73D4, 0xD2B6, 0x73D6, 0xD2AE, 0x73D7, 0xD2B9, 0x73D8, 0xD2BA, + 0x73D9, 0xD2AC, 0x73DA, 0xD2B8, 0x73DB, 0xD2B5, 0x73DC, 0xD2B3, 0x73DD, 0xD2B7, 0x73DE, 0xAF5F, 0x73E0, 0xAF5D, 0x73E3, 0xD2B1, + 0x73E5, 0xD2AD, 0x73E7, 0xD2B0, 0x73E8, 0xD2BB, 0x73E9, 0xD2B2, 0x73EA, 0xAF5E, 0x73EB, 0xCFCF, 0x73ED, 0xAF5A, 0x73EE, 0xAF5C, + 0x73F4, 0xD678, 0x73F5, 0xD66D, 0x73F6, 0xD66B, 0x73F8, 0xD66C, 0x73FA, 0xD673, 0x73FC, 0xD674, 0x73FD, 0xD670, 0x73FE, 0xB27B, + 0x73FF, 0xD675, 0x7400, 0xD672, 0x7401, 0xD66F, 0x7403, 0xB279, 0x7404, 0xD66E, 0x7405, 0xB277, 0x7406, 0xB27A, 0x7407, 0xD671, + 0x7408, 0xD679, 0x7409, 0xAF5B, 0x740A, 0xB278, 0x740B, 0xD677, 0x740C, 0xD676, 0x740D, 0xB27C, 0x7416, 0xDA7E, 0x741A, 0xDAA1, + 0x741B, 0xB560, 0x741D, 0xDAA7, 0x7420, 0xDAA9, 0x7421, 0xDAA2, 0x7422, 0xB55A, 0x7423, 0xDAA6, 0x7424, 0xDAA5, 0x7425, 0xB55B, + 0x7426, 0xB561, 0x7428, 0xB562, 0x7429, 0xDAA8, 0x742A, 0xB558, 0x742B, 0xDA7D, 0x742C, 0xDA7B, 0x742D, 0xDAA3, 0x742E, 0xDA7A, + 0x742F, 0xB55F, 0x7430, 0xDA7C, 0x7431, 0xDAA4, 0x7432, 0xDAAA, 0x7433, 0xB559, 0x7434, 0xB55E, 0x7435, 0xB55C, 0x7436, 0xB55D, + 0x743A, 0xB557, 0x743F, 0xB7E9, 0x7440, 0xDEB7, 0x7441, 0xB7E8, 0x7442, 0xDEBB, 0x7444, 0xDEB1, 0x7446, 0xDEBC, 0x744A, 0xDEB2, + 0x744B, 0xDEB3, 0x744D, 0xDEBD, 0x744E, 0xDEBA, 0x744F, 0xDEB8, 0x7450, 0xDEB9, 0x7451, 0xDEB5, 0x7452, 0xDEB4, 0x7454, 0xDEBE, + 0x7455, 0xB7E5, 0x7457, 0xDEB6, 0x7459, 0xB7EA, 0x745A, 0xB7E4, 0x745B, 0xB7EB, 0x745C, 0xB7EC, 0x745E, 0xB7E7, 0x745F, 0xB7E6, + 0x7462, 0xE2CE, 0x7463, 0xBABE, 0x7464, 0xBABD, 0x7467, 0xE2D3, 0x7469, 0xBCFC, 0x746A, 0xBABF, 0x746D, 0xBAC1, 0x746E, 0xE2D4, + 0x746F, 0xB7E3, 0x7470, 0xBAC0, 0x7471, 0xE2D0, 0x7472, 0xE2D2, 0x7473, 0xE2CF, 0x7475, 0xE2D1, 0x7479, 0xE6AB, 0x747C, 0xE6AA, + 0x747D, 0xE6A7, 0x747E, 0xBD40, 0x747F, 0xEA62, 0x7480, 0xBD41, 0x7481, 0xE6A6, 0x7483, 0xBCFE, 0x7485, 0xE6A8, 0x7486, 0xE6A5, + 0x7487, 0xE6A2, 0x7488, 0xE6A9, 0x7489, 0xE6A3, 0x748A, 0xE6A4, 0x748B, 0xBCFD, 0x7490, 0xED69, 0x7492, 0xEA66, 0x7494, 0xEA65, + 0x7495, 0xEA67, 0x7497, 0xED66, 0x7498, 0xBF5A, 0x749A, 0xEA63, 0x749C, 0xBF58, 0x749E, 0xBF5C, 0x749F, 0xBF5B, 0x74A0, 0xEA64, + 0x74A1, 0xEA68, 0x74A3, 0xBF59, 0x74A5, 0xED6D, 0x74A6, 0xC0F5, 0x74A7, 0xC27A, 0x74A8, 0xC0F6, 0x74A9, 0xC0F3, 0x74AA, 0xED6A, + 0x74AB, 0xED68, 0x74AD, 0xED6B, 0x74AF, 0xED6E, 0x74B0, 0xC0F4, 0x74B1, 0xED6C, 0x74B2, 0xED67, 0x74B5, 0xF042, 0x74B6, 0xF045, + 0x74B7, 0xF275, 0x74B8, 0xF040, 0x74BA, 0xF46F, 0x74BB, 0xF046, 0x74BD, 0xC3A2, 0x74BE, 0xF044, 0x74BF, 0xC27B, 0x74C0, 0xF041, + 0x74C1, 0xF043, 0x74C2, 0xF047, 0x74C3, 0xF276, 0x74C5, 0xF274, 0x74CA, 0xC3A3, 0x74CB, 0xF273, 0x74CF, 0xC46E, 0x74D4, 0xC4ED, + 0x74D5, 0xF6F1, 0x74D6, 0xC4EC, 0x74D7, 0xF6F3, 0x74D8, 0xF6F0, 0x74D9, 0xF6F2, 0x74DA, 0xC5D0, 0x74DB, 0xF8B2, 0x74DC, 0xA5CA, + 0x74DD, 0xCD6E, 0x74DE, 0xD2BC, 0x74DF, 0xD2BD, 0x74E0, 0xB27D, 0x74E1, 0xDEBF, 0x74E2, 0xBF5D, 0x74E3, 0xC3A4, 0x74E4, 0xC57B, + 0x74E5, 0xF8B3, 0x74E6, 0xA5CB, 0x74E8, 0xCD6F, 0x74E9, 0xA260, 0x74EC, 0xCFD7, 0x74EE, 0xCFD8, 0x74F4, 0xD2BE, 0x74F5, 0xD2BF, + 0x74F6, 0xB27E, 0x74F7, 0xB2A1, 0x74FB, 0xDAAB, 0x74FD, 0xDEC2, 0x74FE, 0xDEC1, 0x74FF, 0xDEC0, 0x7500, 0xE2D5, 0x7502, 0xE2D6, + 0x7503, 0xE2D7, 0x7504, 0xBAC2, 0x7507, 0xE6AD, 0x7508, 0xE6AC, 0x750B, 0xEA69, 0x750C, 0xBF5E, 0x750D, 0xBF5F, 0x750F, 0xED72, + 0x7510, 0xED6F, 0x7511, 0xED70, 0x7512, 0xED71, 0x7513, 0xF049, 0x7514, 0xF048, 0x7515, 0xC27C, 0x7516, 0xF277, 0x7517, 0xF5DE, + 0x7518, 0xA5CC, 0x751A, 0xACC6, 0x751C, 0xB2A2, 0x751D, 0xDEC3, 0x751F, 0xA5CD, 0x7521, 0xD2C0, 0x7522, 0xB2A3, 0x7525, 0xB563, + 0x7526, 0xB564, 0x7528, 0xA5CE, 0x7529, 0xA5CF, 0x752A, 0xCA46, 0x752B, 0xA86A, 0x752C, 0xA869, 0x752D, 0xACC7, 0x752E, 0xCFD9, + 0x752F, 0xDAAC, 0x7530, 0xA5D0, 0x7531, 0xA5D1, 0x7532, 0xA5D2, 0x7533, 0xA5D3, 0x7537, 0xA86B, 0x7538, 0xA86C, 0x7539, 0xCB6E, + 0x753A, 0xCB6D, 0x753D, 0xAAB6, 0x753E, 0xCD72, 0x753F, 0xCD70, 0x7540, 0xCD71, 0x7547, 0xCFDA, 0x7548, 0xCFDB, 0x754B, 0xACCB, + 0x754C, 0xACC9, 0x754E, 0xACCA, 0x754F, 0xACC8, 0x7554, 0xAF60, 0x7559, 0xAF64, 0x755A, 0xAF63, 0x755B, 0xD2C1, 0x755C, 0xAF62, + 0x755D, 0xAF61, 0x755F, 0xD2C2, 0x7562, 0xB2A6, 0x7563, 0xD67B, 0x7564, 0xD67A, 0x7565, 0xB2A4, 0x7566, 0xB2A5, 0x756A, 0xB566, + 0x756B, 0xB565, 0x756C, 0xDAAE, 0x756F, 0xDAAD, 0x7570, 0xB2A7, 0x7576, 0xB7ED, 0x7577, 0xDEC5, 0x7578, 0xB7EE, 0x7579, 0xDEC4, + 0x757D, 0xE2D8, 0x757E, 0xE6AE, 0x757F, 0xBD42, 0x7580, 0xEA6A, 0x7584, 0xED73, 0x7586, 0xC3A6, 0x7587, 0xC3A5, 0x758A, 0xC57C, + 0x758B, 0xA5D4, 0x758C, 0xCD73, 0x758F, 0xB2A8, 0x7590, 0xE2D9, 0x7591, 0xBAC3, 0x7594, 0xCB6F, 0x7595, 0xCB70, 0x7598, 0xCD74, + 0x7599, 0xAAB8, 0x759A, 0xAAB9, 0x759D, 0xAAB7, 0x75A2, 0xACCF, 0x75A3, 0xACD0, 0x75A4, 0xACCD, 0x75A5, 0xACCE, 0x75A7, 0xCFDC, + 0x75AA, 0xCFDD, 0x75AB, 0xACCC, 0x75B0, 0xD2C3, 0x75B2, 0xAF68, 0x75B3, 0xAF69, 0x75B5, 0xB2AB, 0x75B6, 0xD2C9, 0x75B8, 0xAF6E, + 0x75B9, 0xAF6C, 0x75BA, 0xD2CA, 0x75BB, 0xD2C5, 0x75BC, 0xAF6B, 0x75BD, 0xAF6A, 0x75BE, 0xAF65, 0x75BF, 0xD2C8, 0x75C0, 0xD2C7, + 0x75C1, 0xD2C4, 0x75C2, 0xAF6D, 0x75C4, 0xD2C6, 0x75C5, 0xAF66, 0x75C7, 0xAF67, 0x75CA, 0xB2AC, 0x75CB, 0xD6A1, 0x75CC, 0xD6A2, + 0x75CD, 0xB2AD, 0x75CE, 0xD67C, 0x75CF, 0xD67E, 0x75D0, 0xD6A4, 0x75D1, 0xD6A3, 0x75D2, 0xD67D, 0x75D4, 0xB2A9, 0x75D5, 0xB2AA, + 0x75D7, 0xDAB6, 0x75D8, 0xB56B, 0x75D9, 0xB56A, 0x75DA, 0xDAB0, 0x75DB, 0xB568, 0x75DD, 0xDAB3, 0x75DE, 0xB56C, 0x75DF, 0xDAB4, + 0x75E0, 0xB56D, 0x75E1, 0xDAB1, 0x75E2, 0xB567, 0x75E3, 0xB569, 0x75E4, 0xDAB5, 0x75E6, 0xDAB2, 0x75E7, 0xDAAF, 0x75ED, 0xDED2, + 0x75EF, 0xDEC7, 0x75F0, 0xB7F0, 0x75F1, 0xB7F3, 0x75F2, 0xB7F2, 0x75F3, 0xB7F7, 0x75F4, 0xB7F6, 0x75F5, 0xDED3, 0x75F6, 0xDED1, + 0x75F7, 0xDECA, 0x75F8, 0xDECE, 0x75F9, 0xDECD, 0x75FA, 0xB7F4, 0x75FB, 0xDED0, 0x75FC, 0xDECC, 0x75FD, 0xDED4, 0x75FE, 0xDECB, + 0x75FF, 0xB7F5, 0x7600, 0xB7EF, 0x7601, 0xB7F1, 0x7603, 0xDEC9, 0x7608, 0xE2DB, 0x7609, 0xBAC7, 0x760A, 0xE2DF, 0x760B, 0xBAC6, + 0x760C, 0xE2DC, 0x760D, 0xBAC5, 0x760F, 0xDEC8, 0x7610, 0xDECF, 0x7611, 0xE2DE, 0x7613, 0xBAC8, 0x7614, 0xE2E0, 0x7615, 0xE2DD, + 0x7616, 0xE2DA, 0x7619, 0xE6B1, 0x761A, 0xE6B5, 0x761B, 0xE6B7, 0x761C, 0xE6B3, 0x761D, 0xE6B2, 0x761E, 0xE6B0, 0x761F, 0xBD45, + 0x7620, 0xBD43, 0x7621, 0xBD48, 0x7622, 0xBD49, 0x7623, 0xE6B4, 0x7624, 0xBD46, 0x7625, 0xE6AF, 0x7626, 0xBD47, 0x7627, 0xBAC4, + 0x7628, 0xE6B6, 0x7629, 0xBD44, 0x762D, 0xEA6C, 0x762F, 0xEA6B, 0x7630, 0xEA73, 0x7631, 0xEA6D, 0x7632, 0xEA72, 0x7633, 0xEA6F, + 0x7634, 0xBF60, 0x7635, 0xEA71, 0x7638, 0xBF61, 0x763A, 0xBF62, 0x763C, 0xEA70, 0x763D, 0xEA6E, 0x7642, 0xC0F8, 0x7643, 0xED74, + 0x7646, 0xC0F7, 0x7647, 0xED77, 0x7648, 0xED75, 0x7649, 0xED76, 0x764C, 0xC0F9, 0x7650, 0xF04D, 0x7652, 0xC2A1, 0x7653, 0xF04E, + 0x7656, 0xC27D, 0x7657, 0xF04F, 0x7658, 0xC27E, 0x7659, 0xF04C, 0x765A, 0xF050, 0x765C, 0xF04A, 0x765F, 0xC3A7, 0x7660, 0xF278, + 0x7661, 0xC3A8, 0x7662, 0xC46F, 0x7664, 0xF04B, 0x7665, 0xC470, 0x7669, 0xC4EE, 0x766A, 0xF5DF, 0x766C, 0xC57E, 0x766D, 0xF6F4, + 0x766E, 0xC57D, 0x7670, 0xF7EA, 0x7671, 0xC5F5, 0x7672, 0xC5F6, 0x7675, 0xF9CC, 0x7678, 0xACD1, 0x7679, 0xCFDE, 0x767B, 0xB56E, + 0x767C, 0xB56F, 0x767D, 0xA5D5, 0x767E, 0xA6CA, 0x767F, 0xCA47, 0x7681, 0xCB71, 0x7682, 0xA86D, 0x7684, 0xAABA, 0x7686, 0xACD2, + 0x7687, 0xACD3, 0x7688, 0xACD4, 0x7689, 0xD6A6, 0x768A, 0xD2CB, 0x768B, 0xAF6F, 0x768E, 0xB2AE, 0x768F, 0xD6A5, 0x7692, 0xDAB8, + 0x7693, 0xB571, 0x7695, 0xDAB7, 0x7696, 0xB570, 0x7699, 0xDED5, 0x769A, 0xBD4A, 0x769B, 0xE6BB, 0x769C, 0xE6B8, 0x769D, 0xE6B9, + 0x769E, 0xE6BA, 0x76A4, 0xED78, 0x76A6, 0xF051, 0x76AA, 0xF471, 0x76AB, 0xF470, 0x76AD, 0xF6F5, 0x76AE, 0xA5D6, 0x76AF, 0xCD75, + 0x76B0, 0xAF70, 0x76B4, 0xB572, 0x76B5, 0xDED6, 0x76B8, 0xE2E1, 0x76BA, 0xBD4B, 0x76BB, 0xEA74, 0x76BD, 0xF052, 0x76BE, 0xF472, + 0x76BF, 0xA5D7, 0x76C2, 0xAABB, 0x76C3, 0xACD7, 0x76C4, 0xCFDF, 0x76C5, 0xACD8, 0x76C6, 0xACD6, 0x76C8, 0xACD5, 0x76C9, 0xD2CC, + 0x76CA, 0xAF71, 0x76CD, 0xAF72, 0x76CE, 0xAF73, 0x76D2, 0xB2B0, 0x76D3, 0xD6A7, 0x76D4, 0xB2AF, 0x76DA, 0xDAB9, 0x76DB, 0xB2B1, + 0x76DC, 0xB573, 0x76DD, 0xDED7, 0x76DE, 0xB7F8, 0x76DF, 0xB7F9, 0x76E1, 0xBAC9, 0x76E3, 0xBACA, 0x76E4, 0xBD4C, 0x76E5, 0xBF64, + 0x76E6, 0xEA75, 0x76E7, 0xBF63, 0x76E9, 0xED79, 0x76EA, 0xC0FA, 0x76EC, 0xF053, 0x76ED, 0xF473, 0x76EE, 0xA5D8, 0x76EF, 0xA86E, + 0x76F0, 0xCD78, 0x76F1, 0xCD77, 0x76F2, 0xAABC, 0x76F3, 0xCD76, 0x76F4, 0xAABD, 0x76F5, 0xCD79, 0x76F7, 0xCFE5, 0x76F8, 0xACDB, + 0x76F9, 0xACDA, 0x76FA, 0xCFE7, 0x76FB, 0xCFE6, 0x76FC, 0xACDF, 0x76FE, 0xACDE, 0x7701, 0xACD9, 0x7703, 0xCFE1, 0x7704, 0xCFE2, + 0x7705, 0xCFE3, 0x7707, 0xACE0, 0x7708, 0xCFE0, 0x7709, 0xACDC, 0x770A, 0xCFE4, 0x770B, 0xACDD, 0x7710, 0xD2CF, 0x7711, 0xD2D3, + 0x7712, 0xD2D1, 0x7713, 0xD2D0, 0x7715, 0xD2D4, 0x7719, 0xD2D5, 0x771A, 0xD2D6, 0x771B, 0xD2CE, 0x771D, 0xD2CD, 0x771F, 0xAF75, + 0x7720, 0xAF76, 0x7722, 0xD2D7, 0x7723, 0xD2D2, 0x7725, 0xD6B0, 0x7727, 0xD2D8, 0x7728, 0xAF77, 0x7729, 0xAF74, 0x772D, 0xD6AA, + 0x772F, 0xD6A9, 0x7731, 0xD6AB, 0x7732, 0xD6AC, 0x7733, 0xD6AE, 0x7734, 0xD6AD, 0x7735, 0xD6B2, 0x7736, 0xB2B5, 0x7737, 0xB2B2, + 0x7738, 0xB2B6, 0x7739, 0xD6A8, 0x773A, 0xB2B7, 0x773B, 0xD6B1, 0x773C, 0xB2B4, 0x773D, 0xD6AF, 0x773E, 0xB2B3, 0x7744, 0xDABC, + 0x7745, 0xDABE, 0x7746, 0xDABA, 0x7747, 0xDABB, 0x774A, 0xDABF, 0x774B, 0xDAC1, 0x774C, 0xDAC2, 0x774D, 0xDABD, 0x774E, 0xDAC0, + 0x774F, 0xB574, 0x7752, 0xDEDB, 0x7754, 0xDEE0, 0x7755, 0xDED8, 0x7756, 0xDEDC, 0x7759, 0xDEE1, 0x775A, 0xDEDD, 0x775B, 0xB7FA, + 0x775C, 0xB843, 0x775E, 0xB7FD, 0x775F, 0xDED9, 0x7760, 0xDEDA, 0x7761, 0xBACE, 0x7762, 0xB846, 0x7763, 0xB7FE, 0x7765, 0xB844, + 0x7766, 0xB7FC, 0x7767, 0xDEDF, 0x7768, 0xB845, 0x7769, 0xDEDE, 0x776A, 0xB841, 0x776B, 0xB7FB, 0x776C, 0xB842, 0x776D, 0xDEE2, + 0x776E, 0xE2E6, 0x776F, 0xE2E8, 0x7779, 0xB840, 0x777C, 0xE2E3, 0x777D, 0xBACC, 0x777E, 0xE2E9, 0x777F, 0xBACD, 0x7780, 0xE2E7, + 0x7781, 0xE2E2, 0x7782, 0xE2E5, 0x7783, 0xE2EA, 0x7784, 0xBACB, 0x7785, 0xE2E4, 0x7787, 0xBD4E, 0x7788, 0xE6BF, 0x7789, 0xE6BE, + 0x778B, 0xBD51, 0x778C, 0xBD4F, 0x778D, 0xE6BC, 0x778E, 0xBD4D, 0x778F, 0xE6BD, 0x7791, 0xBD50, 0x7795, 0xEA7D, 0x7797, 0xEAA1, + 0x7799, 0xEA7E, 0x779A, 0xEA76, 0x779B, 0xEA7A, 0x779C, 0xEA79, 0x779D, 0xEA77, 0x779E, 0xBF66, 0x779F, 0xBF67, 0x77A0, 0xBF65, + 0x77A1, 0xEA78, 0x77A2, 0xEA7B, 0x77A3, 0xEA7C, 0x77A5, 0xBF68, 0x77A7, 0xC140, 0x77A8, 0xEDA3, 0x77AA, 0xC0FC, 0x77AB, 0xED7B, + 0x77AC, 0xC0FE, 0x77AD, 0xC141, 0x77B0, 0xC0FD, 0x77B1, 0xEDA2, 0x77B2, 0xED7C, 0x77B3, 0xC0FB, 0x77B4, 0xEDA1, 0x77B5, 0xED7A, + 0x77B6, 0xED7E, 0x77B7, 0xED7D, 0x77BA, 0xF055, 0x77BB, 0xC2A4, 0x77BC, 0xC2A5, 0x77BD, 0xC2A2, 0x77BF, 0xC2A3, 0x77C2, 0xF054, + 0x77C4, 0xF27B, 0x77C7, 0xC3A9, 0x77C9, 0xF279, 0x77CA, 0xF27A, 0x77CC, 0xF474, 0x77CD, 0xF477, 0x77CE, 0xF475, 0x77CF, 0xF476, + 0x77D0, 0xF5E0, 0x77D3, 0xC4EF, 0x77D4, 0xF7EB, 0x77D5, 0xF8B4, 0x77D7, 0xC5F7, 0x77D8, 0xF8F8, 0x77D9, 0xF8F9, 0x77DA, 0xC666, + 0x77DB, 0xA5D9, 0x77DC, 0xACE1, 0x77DE, 0xDAC3, 0x77E0, 0xDEE3, 0x77E2, 0xA5DA, 0x77E3, 0xA86F, 0x77E5, 0xAABE, 0x77E7, 0xCFE8, + 0x77E8, 0xCFE9, 0x77E9, 0xAF78, 0x77EC, 0xDAC4, 0x77ED, 0xB575, 0x77EE, 0xB847, 0x77EF, 0xC142, 0x77F0, 0xEDA4, 0x77F1, 0xF27C, + 0x77F2, 0xF478, 0x77F3, 0xA5DB, 0x77F7, 0xCDA1, 0x77F8, 0xCD7A, 0x77F9, 0xCD7C, 0x77FA, 0xCD7E, 0x77FB, 0xCD7D, 0x77FC, 0xCD7B, + 0x77FD, 0xAABF, 0x7802, 0xACE2, 0x7803, 0xCFF2, 0x7805, 0xCFED, 0x7806, 0xCFEA, 0x7809, 0xCFF1, 0x780C, 0xACE4, 0x780D, 0xACE5, + 0x780E, 0xCFF0, 0x780F, 0xCFEF, 0x7810, 0xCFEE, 0x7811, 0xCFEB, 0x7812, 0xCFEC, 0x7813, 0xCFF3, 0x7814, 0xACE3, 0x781D, 0xAF7C, + 0x781F, 0xAFA4, 0x7820, 0xAFA3, 0x7821, 0xD2E1, 0x7822, 0xD2DB, 0x7823, 0xD2D9, 0x7825, 0xAFA1, 0x7826, 0xD6B9, 0x7827, 0xAF7A, + 0x7828, 0xD2DE, 0x7829, 0xD2E2, 0x782A, 0xD2E4, 0x782B, 0xD2E0, 0x782C, 0xD2DA, 0x782D, 0xAFA2, 0x782E, 0xD2DF, 0x782F, 0xD2DD, + 0x7830, 0xAF79, 0x7831, 0xD2E5, 0x7832, 0xAFA5, 0x7833, 0xD2E3, 0x7834, 0xAF7D, 0x7835, 0xD2DC, 0x7837, 0xAF7E, 0x7838, 0xAF7B, + 0x7843, 0xB2B9, 0x7845, 0xD6BA, 0x7848, 0xD6B3, 0x7849, 0xD6B5, 0x784A, 0xD6B7, 0x784C, 0xD6B8, 0x784D, 0xD6B6, 0x784E, 0xB2BA, + 0x7850, 0xD6BB, 0x7852, 0xD6B4, 0x785C, 0xDAC8, 0x785D, 0xB576, 0x785E, 0xDAD0, 0x7860, 0xDAC5, 0x7862, 0xDAD1, 0x7864, 0xDAC6, + 0x7865, 0xDAC7, 0x7868, 0xDACF, 0x7869, 0xDACE, 0x786A, 0xDACB, 0x786B, 0xB2B8, 0x786C, 0xB577, 0x786D, 0xDAC9, 0x786E, 0xDACC, + 0x786F, 0xB578, 0x7870, 0xDACD, 0x7871, 0xDACA, 0x7879, 0xDEEE, 0x787B, 0xDEF2, 0x787C, 0xB84E, 0x787E, 0xE2F0, 0x787F, 0xB851, + 0x7880, 0xDEF0, 0x7881, 0xF9D6, 0x7883, 0xDEED, 0x7884, 0xDEE8, 0x7885, 0xDEEA, 0x7886, 0xDEEB, 0x7887, 0xDEE4, 0x7889, 0xB84D, + 0x788C, 0xB84C, 0x788E, 0xB848, 0x788F, 0xDEE7, 0x7891, 0xB84F, 0x7893, 0xB850, 0x7894, 0xDEE6, 0x7895, 0xDEE9, 0x7896, 0xDEF1, + 0x7897, 0xB84A, 0x7898, 0xB84B, 0x7899, 0xDEEF, 0x789A, 0xDEE5, 0x789E, 0xE2F2, 0x789F, 0xBAD0, 0x78A0, 0xE2F4, 0x78A1, 0xDEEC, + 0x78A2, 0xE2F6, 0x78A3, 0xBAD4, 0x78A4, 0xE2F7, 0x78A5, 0xE2F3, 0x78A7, 0xBAD1, 0x78A8, 0xE2EF, 0x78A9, 0xBAD3, 0x78AA, 0xE2EC, + 0x78AB, 0xE2F1, 0x78AC, 0xE2F5, 0x78AD, 0xE2EE, 0x78B0, 0xB849, 0x78B2, 0xE2EB, 0x78B3, 0xBAD2, 0x78B4, 0xE2ED, 0x78BA, 0xBD54, + 0x78BB, 0xE6C1, 0x78BC, 0xBD58, 0x78BE, 0xBD56, 0x78C1, 0xBACF, 0x78C3, 0xE6C8, 0x78C4, 0xE6C9, 0x78C5, 0xBD53, 0x78C8, 0xE6C7, + 0x78C9, 0xE6CA, 0x78CA, 0xBD55, 0x78CB, 0xBD52, 0x78CC, 0xE6C3, 0x78CD, 0xE6C0, 0x78CE, 0xE6C5, 0x78CF, 0xE6C2, 0x78D0, 0xBD59, + 0x78D1, 0xE6C4, 0x78D4, 0xE6C6, 0x78D5, 0xBD57, 0x78DA, 0xBF6A, 0x78DB, 0xEAA8, 0x78DD, 0xEAA2, 0x78DE, 0xEAA6, 0x78DF, 0xEAAC, + 0x78E0, 0xEAAD, 0x78E1, 0xEAA9, 0x78E2, 0xEAAA, 0x78E3, 0xEAA7, 0x78E5, 0xEAA4, 0x78E7, 0xBF6C, 0x78E8, 0xBF69, 0x78E9, 0xEAA3, + 0x78EA, 0xEAA5, 0x78EC, 0xBF6B, 0x78ED, 0xEAAB, 0x78EF, 0xC146, 0x78F2, 0xEDAA, 0x78F3, 0xEDA5, 0x78F4, 0xC145, 0x78F7, 0xC143, + 0x78F9, 0xEDAC, 0x78FA, 0xC144, 0x78FB, 0xEDA8, 0x78FC, 0xEDA9, 0x78FD, 0xEDA6, 0x78FE, 0xEDAD, 0x78FF, 0xF056, 0x7901, 0xC147, + 0x7902, 0xEDA7, 0x7904, 0xEDAE, 0x7905, 0xEDAB, 0x7909, 0xF05A, 0x790C, 0xF057, 0x790E, 0xC2A6, 0x7910, 0xF05B, 0x7911, 0xF05D, + 0x7912, 0xF05C, 0x7913, 0xF058, 0x7914, 0xF059, 0x7917, 0xF2A3, 0x7919, 0xC3AA, 0x791B, 0xF27E, 0x791C, 0xF2A2, 0x791D, 0xF27D, + 0x791E, 0xF2A4, 0x7921, 0xF2A1, 0x7923, 0xF47A, 0x7924, 0xF47D, 0x7925, 0xF479, 0x7926, 0xC471, 0x7927, 0xF47B, 0x7928, 0xF47C, + 0x7929, 0xF47E, 0x792A, 0xC472, 0x792B, 0xC474, 0x792C, 0xC473, 0x792D, 0xF5E1, 0x792F, 0xF5E3, 0x7931, 0xF5E2, 0x7935, 0xF6F6, + 0x7938, 0xF8B5, 0x7939, 0xF8FA, 0x793A, 0xA5DC, 0x793D, 0xCB72, 0x793E, 0xAAC0, 0x793F, 0xCDA3, 0x7940, 0xAAC1, 0x7941, 0xAAC2, + 0x7942, 0xCDA2, 0x7944, 0xCFF8, 0x7945, 0xCFF7, 0x7946, 0xACE6, 0x7947, 0xACE9, 0x7948, 0xACE8, 0x7949, 0xACE7, 0x794A, 0xCFF4, + 0x794B, 0xCFF6, 0x794C, 0xCFF5, 0x794F, 0xD2E8, 0x7950, 0xAFA7, 0x7951, 0xD2EC, 0x7952, 0xD2EB, 0x7953, 0xD2EA, 0x7954, 0xD2E6, + 0x7955, 0xAFA6, 0x7956, 0xAFAA, 0x7957, 0xAFAD, 0x795A, 0xAFAE, 0x795B, 0xD2E7, 0x795C, 0xD2E9, 0x795D, 0xAFAC, 0x795E, 0xAFAB, + 0x795F, 0xAFA9, 0x7960, 0xAFA8, 0x7961, 0xD6C2, 0x7963, 0xD6C0, 0x7964, 0xD6BC, 0x7965, 0xB2BB, 0x7967, 0xD6BD, 0x7968, 0xB2BC, + 0x7969, 0xD6BE, 0x796A, 0xD6BF, 0x796B, 0xD6C1, 0x796D, 0xB2BD, 0x7970, 0xDAD5, 0x7972, 0xDAD4, 0x7973, 0xDAD3, 0x7974, 0xDAD2, + 0x7979, 0xDEF6, 0x797A, 0xB852, 0x797C, 0xDEF3, 0x797D, 0xDEF5, 0x797F, 0xB853, 0x7981, 0xB854, 0x7982, 0xDEF4, 0x7988, 0xE341, + 0x798A, 0xE2F9, 0x798B, 0xE2FA, 0x798D, 0xBAD7, 0x798E, 0xBAD5, 0x798F, 0xBAD6, 0x7990, 0xE343, 0x7992, 0xE342, 0x7993, 0xE2FE, + 0x7994, 0xE2FD, 0x7995, 0xE2FC, 0x7996, 0xE2FB, 0x7997, 0xE340, 0x7998, 0xE2F8, 0x799A, 0xE6CB, 0x799B, 0xE6D0, 0x799C, 0xE6CE, + 0x79A0, 0xE6CD, 0x79A1, 0xE6CC, 0x79A2, 0xE6CF, 0x79A4, 0xEAAE, 0x79A6, 0xBF6D, 0x79A7, 0xC148, 0x79A8, 0xEDB0, 0x79AA, 0xC149, + 0x79AB, 0xEDAF, 0x79AC, 0xF05F, 0x79AD, 0xF05E, 0x79AE, 0xC2A7, 0x79B0, 0xF2A5, 0x79B1, 0xC3AB, 0x79B2, 0xF4A1, 0x79B3, 0xC5A1, + 0x79B4, 0xF6F7, 0x79B6, 0xF8B7, 0x79B7, 0xF8B6, 0x79B8, 0xC9A8, 0x79B9, 0xACEA, 0x79BA, 0xACEB, 0x79BB, 0xD6C3, 0x79BD, 0xB856, + 0x79BE, 0xA5DD, 0x79BF, 0xA872, 0x79C0, 0xA871, 0x79C1, 0xA870, 0x79C5, 0xCDA4, 0x79C8, 0xAAC4, 0x79C9, 0xAAC3, 0x79CB, 0xACEE, + 0x79CD, 0xCFFA, 0x79CE, 0xCFFD, 0x79CF, 0xCFFB, 0x79D1, 0xACEC, 0x79D2, 0xACED, 0x79D5, 0xCFF9, 0x79D6, 0xCFFC, 0x79D8, 0xAFB5, + 0x79DC, 0xD2F3, 0x79DD, 0xD2F5, 0x79DE, 0xD2F4, 0x79DF, 0xAFB2, 0x79E0, 0xD2EF, 0x79E3, 0xAFB0, 0x79E4, 0xAFAF, 0x79E6, 0xAFB3, + 0x79E7, 0xAFB1, 0x79E9, 0xAFB4, 0x79EA, 0xD2F2, 0x79EB, 0xD2ED, 0x79EC, 0xD2EE, 0x79ED, 0xD2F1, 0x79EE, 0xD2F0, 0x79F6, 0xD6C6, + 0x79F7, 0xD6C7, 0x79F8, 0xD6C5, 0x79FA, 0xD6C4, 0x79FB, 0xB2BE, 0x7A00, 0xB57D, 0x7A02, 0xDAD6, 0x7A03, 0xDAD8, 0x7A04, 0xDADA, + 0x7A05, 0xB57C, 0x7A08, 0xB57A, 0x7A0A, 0xDAD7, 0x7A0B, 0xB57B, 0x7A0C, 0xDAD9, 0x7A0D, 0xB579, 0x7A10, 0xDF41, 0x7A11, 0xDEF7, + 0x7A12, 0xDEFA, 0x7A13, 0xDEFE, 0x7A14, 0xB85A, 0x7A15, 0xDEFC, 0x7A17, 0xDEFB, 0x7A18, 0xDEF8, 0x7A19, 0xDEF9, 0x7A1A, 0xB858, + 0x7A1B, 0xDF40, 0x7A1C, 0xB857, 0x7A1E, 0xB85C, 0x7A1F, 0xB85B, 0x7A20, 0xB859, 0x7A22, 0xDEFD, 0x7A26, 0xE349, 0x7A28, 0xE348, + 0x7A2B, 0xE344, 0x7A2E, 0xBAD8, 0x7A2F, 0xE347, 0x7A30, 0xE346, 0x7A31, 0xBAD9, 0x7A37, 0xBD5E, 0x7A39, 0xE6D2, 0x7A3B, 0xBD5F, + 0x7A3C, 0xBD5B, 0x7A3D, 0xBD5D, 0x7A3F, 0xBD5A, 0x7A40, 0xBD5C, 0x7A44, 0xEAAF, 0x7A46, 0xBF70, 0x7A47, 0xEAB1, 0x7A48, 0xEAB0, + 0x7A4A, 0xE345, 0x7A4B, 0xBF72, 0x7A4C, 0xBF71, 0x7A4D, 0xBF6E, 0x7A4E, 0xBF6F, 0x7A54, 0xEDB5, 0x7A56, 0xEDB3, 0x7A57, 0xC14A, + 0x7A58, 0xEDB4, 0x7A5A, 0xEDB6, 0x7A5B, 0xEDB2, 0x7A5C, 0xEDB1, 0x7A5F, 0xF060, 0x7A60, 0xC2AA, 0x7A61, 0xC2A8, 0x7A62, 0xC2A9, + 0x7A67, 0xF2A6, 0x7A68, 0xF2A7, 0x7A69, 0xC3AD, 0x7A6B, 0xC3AC, 0x7A6C, 0xF4A3, 0x7A6D, 0xF4A4, 0x7A6E, 0xF4A2, 0x7A70, 0xF6F8, + 0x7A71, 0xF6F9, 0x7A74, 0xA5DE, 0x7A75, 0xCA48, 0x7A76, 0xA873, 0x7A78, 0xCDA5, 0x7A79, 0xAAC6, 0x7A7A, 0xAAC5, 0x7A7B, 0xCDA6, + 0x7A7E, 0xD040, 0x7A7F, 0xACEF, 0x7A80, 0xCFFE, 0x7A81, 0xACF0, 0x7A84, 0xAFB6, 0x7A85, 0xD2F8, 0x7A86, 0xD2F6, 0x7A87, 0xD2FC, + 0x7A88, 0xAFB7, 0x7A89, 0xD2F7, 0x7A8A, 0xD2FB, 0x7A8B, 0xD2F9, 0x7A8C, 0xD2FA, 0x7A8F, 0xD6C8, 0x7A90, 0xD6CA, 0x7A92, 0xB2BF, + 0x7A94, 0xD6C9, 0x7A95, 0xB2C0, 0x7A96, 0xB5A2, 0x7A97, 0xB5A1, 0x7A98, 0xB57E, 0x7A99, 0xDADB, 0x7A9E, 0xDF44, 0x7A9F, 0xB85D, + 0x7AA0, 0xB85E, 0x7AA2, 0xDF43, 0x7AA3, 0xDF42, 0x7AA8, 0xE34A, 0x7AA9, 0xBADB, 0x7AAA, 0xBADA, 0x7AAB, 0xE34B, 0x7AAC, 0xE34C, + 0x7AAE, 0xBD61, 0x7AAF, 0xBD60, 0x7AB1, 0xEAB5, 0x7AB2, 0xE6D3, 0x7AB3, 0xE6D5, 0x7AB4, 0xE6D4, 0x7AB5, 0xEAB4, 0x7AB6, 0xEAB2, + 0x7AB7, 0xEAB6, 0x7AB8, 0xEAB3, 0x7ABA, 0xBF73, 0x7ABE, 0xEDB7, 0x7ABF, 0xC14B, 0x7AC0, 0xEDB8, 0x7AC1, 0xEDB9, 0x7AC4, 0xC2AB, + 0x7AC5, 0xC2AC, 0x7AC7, 0xC475, 0x7ACA, 0xC5D1, 0x7ACB, 0xA5DF, 0x7AD1, 0xD041, 0x7AD8, 0xD2FD, 0x7AD9, 0xAFB8, 0x7ADF, 0xB3BA, + 0x7AE0, 0xB3B9, 0x7AE3, 0xB5A4, 0x7AE4, 0xDADD, 0x7AE5, 0xB5A3, 0x7AE6, 0xDADC, 0x7AEB, 0xDF45, 0x7AED, 0xBADC, 0x7AEE, 0xE34D, + 0x7AEF, 0xBADD, 0x7AF6, 0xC476, 0x7AF7, 0xF4A5, 0x7AF9, 0xA6CB, 0x7AFA, 0xAAC7, 0x7AFB, 0xCDA7, 0x7AFD, 0xACF2, 0x7AFF, 0xACF1, + 0x7B00, 0xD042, 0x7B01, 0xD043, 0x7B04, 0xD340, 0x7B05, 0xD342, 0x7B06, 0xAFB9, 0x7B08, 0xD344, 0x7B09, 0xD347, 0x7B0A, 0xD345, + 0x7B0E, 0xD346, 0x7B0F, 0xD343, 0x7B10, 0xD2FE, 0x7B11, 0xAFBA, 0x7B12, 0xD348, 0x7B13, 0xD341, 0x7B18, 0xD6D3, 0x7B19, 0xB2C6, + 0x7B1A, 0xD6DC, 0x7B1B, 0xB2C3, 0x7B1D, 0xD6D5, 0x7B1E, 0xB2C7, 0x7B20, 0xB2C1, 0x7B22, 0xD6D0, 0x7B23, 0xD6DD, 0x7B24, 0xD6D1, + 0x7B25, 0xD6CE, 0x7B26, 0xB2C5, 0x7B28, 0xB2C2, 0x7B2A, 0xD6D4, 0x7B2B, 0xD6D7, 0x7B2C, 0xB2C4, 0x7B2D, 0xD6D8, 0x7B2E, 0xB2C8, + 0x7B2F, 0xD6D9, 0x7B30, 0xD6CF, 0x7B31, 0xD6D6, 0x7B32, 0xD6DA, 0x7B33, 0xD6D2, 0x7B34, 0xD6CD, 0x7B35, 0xD6CB, 0x7B38, 0xD6DB, + 0x7B3B, 0xDADF, 0x7B40, 0xDAE4, 0x7B44, 0xDAE0, 0x7B45, 0xDAE6, 0x7B46, 0xB5A7, 0x7B47, 0xD6CC, 0x7B48, 0xDAE1, 0x7B49, 0xB5A5, + 0x7B4A, 0xDADE, 0x7B4B, 0xB5AC, 0x7B4C, 0xDAE2, 0x7B4D, 0xB5AB, 0x7B4E, 0xDAE3, 0x7B4F, 0xB5AD, 0x7B50, 0xB5A8, 0x7B51, 0xB5AE, + 0x7B52, 0xB5A9, 0x7B54, 0xB5AA, 0x7B56, 0xB5A6, 0x7B58, 0xDAE5, 0x7B60, 0xB861, 0x7B61, 0xDF50, 0x7B63, 0xDF53, 0x7B64, 0xDF47, + 0x7B65, 0xDF4C, 0x7B66, 0xDF46, 0x7B67, 0xB863, 0x7B69, 0xDF4A, 0x7B6D, 0xDF48, 0x7B6E, 0xB862, 0x7B70, 0xDF4F, 0x7B71, 0xDF4E, + 0x7B72, 0xDF4B, 0x7B73, 0xDF4D, 0x7B74, 0xDF49, 0x7B75, 0xBAE1, 0x7B76, 0xDF52, 0x7B77, 0xB85F, 0x7B78, 0xDF51, 0x7B82, 0xE35D, + 0x7B84, 0xBAE8, 0x7B85, 0xE358, 0x7B87, 0xBAE7, 0x7B88, 0xE34E, 0x7B8A, 0xE350, 0x7B8B, 0xBAE0, 0x7B8C, 0xE355, 0x7B8D, 0xE354, + 0x7B8E, 0xE357, 0x7B8F, 0xBAE5, 0x7B90, 0xE352, 0x7B91, 0xE351, 0x7B94, 0xBAE4, 0x7B95, 0xBADF, 0x7B96, 0xE353, 0x7B97, 0xBAE2, + 0x7B98, 0xE359, 0x7B99, 0xE35B, 0x7B9B, 0xE356, 0x7B9C, 0xE34F, 0x7B9D, 0xBAE3, 0x7BA0, 0xBD69, 0x7BA1, 0xBADE, 0x7BA4, 0xE35C, + 0x7BAC, 0xE6D9, 0x7BAD, 0xBD62, 0x7BAF, 0xE6DB, 0x7BB1, 0xBD63, 0x7BB4, 0xBD65, 0x7BB5, 0xE6DE, 0x7BB7, 0xE6D6, 0x7BB8, 0xBAE6, + 0x7BB9, 0xE6DC, 0x7BBE, 0xE6D8, 0x7BC0, 0xB860, 0x7BC1, 0xBD68, 0x7BC4, 0xBD64, 0x7BC6, 0xBD66, 0x7BC7, 0xBD67, 0x7BC9, 0xBF76, + 0x7BCA, 0xE6DD, 0x7BCB, 0xE6D7, 0x7BCC, 0xBD6A, 0x7BCE, 0xE6DA, 0x7BD4, 0xEAC0, 0x7BD5, 0xEABB, 0x7BD8, 0xEAC5, 0x7BD9, 0xBF74, + 0x7BDA, 0xEABD, 0x7BDB, 0xBF78, 0x7BDC, 0xEAC3, 0x7BDD, 0xEABA, 0x7BDE, 0xEAB7, 0x7BDF, 0xEAC6, 0x7BE0, 0xC151, 0x7BE1, 0xBF79, + 0x7BE2, 0xEAC2, 0x7BE3, 0xEAB8, 0x7BE4, 0xBF77, 0x7BE5, 0xEABC, 0x7BE6, 0xBF7B, 0x7BE7, 0xEAB9, 0x7BE8, 0xEABE, 0x7BE9, 0xBF7A, + 0x7BEA, 0xEAC1, 0x7BEB, 0xEAC4, 0x7BF0, 0xEDCB, 0x7BF1, 0xEDCC, 0x7BF2, 0xEDBC, 0x7BF3, 0xEDC3, 0x7BF4, 0xEDC1, 0x7BF7, 0xC14F, + 0x7BF8, 0xEDC8, 0x7BF9, 0xEABF, 0x7BFB, 0xEDBF, 0x7BFD, 0xEDC9, 0x7BFE, 0xC14E, 0x7BFF, 0xEDBE, 0x7C00, 0xEDBD, 0x7C01, 0xEDC7, + 0x7C02, 0xEDC4, 0x7C03, 0xEDC6, 0x7C05, 0xEDBA, 0x7C06, 0xEDCA, 0x7C07, 0xC14C, 0x7C09, 0xEDC5, 0x7C0A, 0xEDCE, 0x7C0B, 0xEDC2, + 0x7C0C, 0xC150, 0x7C0D, 0xC14D, 0x7C0E, 0xEDC0, 0x7C0F, 0xEDBB, 0x7C10, 0xEDCD, 0x7C11, 0xBF75, 0x7C19, 0xF063, 0x7C1C, 0xF061, + 0x7C1D, 0xF067, 0x7C1E, 0xC2B0, 0x7C1F, 0xF065, 0x7C20, 0xF064, 0x7C21, 0xC2B2, 0x7C22, 0xF06A, 0x7C23, 0xC2B1, 0x7C25, 0xF06B, + 0x7C26, 0xF068, 0x7C27, 0xC2AE, 0x7C28, 0xF069, 0x7C29, 0xF062, 0x7C2A, 0xC2AF, 0x7C2B, 0xC2AD, 0x7C2C, 0xF2AB, 0x7C2D, 0xF066, + 0x7C30, 0xF06C, 0x7C33, 0xF2A8, 0x7C37, 0xC3B2, 0x7C38, 0xC3B0, 0x7C39, 0xF2AA, 0x7C3B, 0xF2AC, 0x7C3C, 0xF2A9, 0x7C3D, 0xC3B1, + 0x7C3E, 0xC3AE, 0x7C3F, 0xC3AF, 0x7C40, 0xC3B3, 0x7C43, 0xC478, 0x7C45, 0xF4AA, 0x7C47, 0xF4A9, 0x7C48, 0xF4A7, 0x7C49, 0xF4A6, + 0x7C4A, 0xF4A8, 0x7C4C, 0xC477, 0x7C4D, 0xC479, 0x7C50, 0xC4F0, 0x7C53, 0xF5E5, 0x7C54, 0xF5E4, 0x7C57, 0xF6FA, 0x7C59, 0xF6FC, + 0x7C5A, 0xF6FE, 0x7C5B, 0xF6FD, 0x7C5C, 0xF6FB, 0x7C5F, 0xC5A3, 0x7C60, 0xC5A2, 0x7C63, 0xC5D3, 0x7C64, 0xC5D2, 0x7C65, 0xC5D4, + 0x7C66, 0xF7ED, 0x7C67, 0xF7EC, 0x7C69, 0xF8FB, 0x7C6A, 0xF8B8, 0x7C6B, 0xF8FC, 0x7C6C, 0xC658, 0x7C6E, 0xC659, 0x7C6F, 0xF96D, + 0x7C72, 0xC67E, 0x7C73, 0xA6CC, 0x7C75, 0xCDA8, 0x7C78, 0xD045, 0x7C79, 0xD046, 0x7C7A, 0xD044, 0x7C7D, 0xACF3, 0x7C7F, 0xD047, + 0x7C80, 0xD048, 0x7C81, 0xD049, 0x7C84, 0xD349, 0x7C85, 0xD34F, 0x7C88, 0xD34D, 0x7C89, 0xAFBB, 0x7C8A, 0xD34B, 0x7C8C, 0xD34C, + 0x7C8D, 0xD34E, 0x7C91, 0xD34A, 0x7C92, 0xB2C9, 0x7C94, 0xD6DE, 0x7C95, 0xB2CB, 0x7C96, 0xD6E0, 0x7C97, 0xB2CA, 0x7C98, 0xD6DF, + 0x7C9E, 0xDAE8, 0x7C9F, 0xB5AF, 0x7CA1, 0xDAEA, 0x7CA2, 0xDAE7, 0x7CA3, 0xD6E1, 0x7CA5, 0xB5B0, 0x7CA7, 0xF9DB, 0x7CA8, 0xDAE9, + 0x7CAF, 0xDF56, 0x7CB1, 0xB864, 0x7CB2, 0xDF54, 0x7CB3, 0xB865, 0x7CB4, 0xDF55, 0x7CB5, 0xB866, 0x7CB9, 0xBAE9, 0x7CBA, 0xE361, + 0x7CBB, 0xE35E, 0x7CBC, 0xE360, 0x7CBD, 0xBAEA, 0x7CBE, 0xBAEB, 0x7CBF, 0xE35F, 0x7CC5, 0xE6DF, 0x7CC8, 0xE6E0, 0x7CCA, 0xBD6B, + 0x7CCB, 0xE6E2, 0x7CCC, 0xE6E1, 0x7CCE, 0xA261, 0x7CD0, 0xEACA, 0x7CD1, 0xEACB, 0x7CD2, 0xEAC7, 0x7CD4, 0xEAC8, 0x7CD5, 0xBF7C, + 0x7CD6, 0xBF7D, 0x7CD7, 0xEAC9, 0x7CD9, 0xC157, 0x7CDC, 0xC153, 0x7CDD, 0xC158, 0x7CDE, 0xC154, 0x7CDF, 0xC156, 0x7CE0, 0xC152, + 0x7CE2, 0xC155, 0x7CE7, 0xC2B3, 0x7CE8, 0xEDCF, 0x7CEA, 0xF2AE, 0x7CEC, 0xF2AD, 0x7CEE, 0xF4AB, 0x7CEF, 0xC47A, 0x7CF0, 0xC47B, + 0x7CF1, 0xF741, 0x7CF2, 0xF5E6, 0x7CF4, 0xF740, 0x7CF6, 0xF8FD, 0x7CF7, 0xF9A4, 0x7CF8, 0xA6CD, 0x7CFB, 0xA874, 0x7CFD, 0xCDA9, + 0x7CFE, 0xAAC8, 0x7D00, 0xACF6, 0x7D01, 0xD04C, 0x7D02, 0xACF4, 0x7D03, 0xD04A, 0x7D04, 0xACF9, 0x7D05, 0xACF5, 0x7D06, 0xACFA, + 0x7D07, 0xACF8, 0x7D08, 0xD04B, 0x7D09, 0xACF7, 0x7D0A, 0xAFBF, 0x7D0B, 0xAFBE, 0x7D0C, 0xD35A, 0x7D0D, 0xAFC7, 0x7D0E, 0xD353, + 0x7D0F, 0xD359, 0x7D10, 0xAFC3, 0x7D11, 0xD352, 0x7D12, 0xD358, 0x7D13, 0xD356, 0x7D14, 0xAFC2, 0x7D15, 0xAFC4, 0x7D16, 0xD355, + 0x7D17, 0xAFBD, 0x7D18, 0xD354, 0x7D19, 0xAFC8, 0x7D1A, 0xAFC5, 0x7D1B, 0xAFC9, 0x7D1C, 0xAFC6, 0x7D1D, 0xD351, 0x7D1E, 0xD350, + 0x7D1F, 0xD357, 0x7D20, 0xAFC0, 0x7D21, 0xAFBC, 0x7D22, 0xAFC1, 0x7D28, 0xD6F0, 0x7D29, 0xD6E9, 0x7D2B, 0xB5B5, 0x7D2C, 0xD6E8, + 0x7D2E, 0xB2CF, 0x7D2F, 0xB2D6, 0x7D30, 0xB2D3, 0x7D31, 0xB2D9, 0x7D32, 0xB2D8, 0x7D33, 0xB2D4, 0x7D35, 0xD6E2, 0x7D36, 0xD6E5, + 0x7D38, 0xD6E4, 0x7D39, 0xB2D0, 0x7D3A, 0xD6E6, 0x7D3B, 0xD6EF, 0x7D3C, 0xB2D1, 0x7D3D, 0xD6E3, 0x7D3E, 0xD6EC, 0x7D3F, 0xD6ED, + 0x7D40, 0xB2D2, 0x7D41, 0xD6EA, 0x7D42, 0xB2D7, 0x7D43, 0xB2CD, 0x7D44, 0xB2D5, 0x7D45, 0xD6E7, 0x7D46, 0xB2CC, 0x7D47, 0xD6EB, + 0x7D4A, 0xD6EE, 0x7D4E, 0xDAFB, 0x7D4F, 0xDAF2, 0x7D50, 0xB5B2, 0x7D51, 0xDAF9, 0x7D52, 0xDAF6, 0x7D53, 0xDAEE, 0x7D54, 0xDAF7, + 0x7D55, 0xB5B4, 0x7D56, 0xDAEF, 0x7D58, 0xDAEB, 0x7D5B, 0xB86C, 0x7D5C, 0xDAF4, 0x7D5E, 0xB5B1, 0x7D5F, 0xDAFA, 0x7D61, 0xB5B8, + 0x7D62, 0xB5BA, 0x7D63, 0xDAED, 0x7D66, 0xB5B9, 0x7D67, 0xDAF0, 0x7D68, 0xB5B3, 0x7D69, 0xDAF8, 0x7D6A, 0xDAF1, 0x7D6B, 0xDAF5, + 0x7D6D, 0xDAF3, 0x7D6E, 0xB5B6, 0x7D6F, 0xDAEC, 0x7D70, 0xB5BB, 0x7D71, 0xB2CE, 0x7D72, 0xB5B7, 0x7D73, 0xB5BC, 0x7D79, 0xB868, + 0x7D7A, 0xDF5D, 0x7D7B, 0xDF5F, 0x7D7C, 0xDF61, 0x7D7D, 0xDF65, 0x7D7F, 0xDF5B, 0x7D80, 0xDF59, 0x7D81, 0xB86A, 0x7D83, 0xDF60, + 0x7D84, 0xDF64, 0x7D85, 0xDF5C, 0x7D86, 0xDF58, 0x7D88, 0xDF57, 0x7D8C, 0xDF62, 0x7D8D, 0xDF5A, 0x7D8E, 0xDF5E, 0x7D8F, 0xB86B, + 0x7D91, 0xB869, 0x7D92, 0xDF66, 0x7D93, 0xB867, 0x7D94, 0xDF63, 0x7D96, 0xE372, 0x7D9C, 0xBAEE, 0x7D9D, 0xE36A, 0x7D9E, 0xBD78, + 0x7D9F, 0xE374, 0x7DA0, 0xBAF1, 0x7DA1, 0xE378, 0x7DA2, 0xBAF7, 0x7DA3, 0xE365, 0x7DA6, 0xE375, 0x7DA7, 0xE362, 0x7DA9, 0xE377, + 0x7DAA, 0xE366, 0x7DAC, 0xBAFE, 0x7DAD, 0xBAFB, 0x7DAE, 0xE376, 0x7DAF, 0xE370, 0x7DB0, 0xBAED, 0x7DB1, 0xBAF5, 0x7DB2, 0xBAF4, + 0x7DB4, 0xBAF3, 0x7DB5, 0xBAF9, 0x7DB7, 0xE363, 0x7DB8, 0xBAFA, 0x7DB9, 0xE371, 0x7DBA, 0xBAF6, 0x7DBB, 0xBAEC, 0x7DBC, 0xE373, + 0x7DBD, 0xBAEF, 0x7DBE, 0xBAF0, 0x7DBF, 0xBAF8, 0x7DC0, 0xE368, 0x7DC1, 0xE367, 0x7DC2, 0xE364, 0x7DC4, 0xE36C, 0x7DC5, 0xE369, + 0x7DC6, 0xE36D, 0x7DC7, 0xBAFD, 0x7DC9, 0xE379, 0x7DCA, 0xBAF2, 0x7DCB, 0xE36E, 0x7DCC, 0xE36F, 0x7DCE, 0xE36B, 0x7DD2, 0xBAFC, + 0x7DD7, 0xE6E7, 0x7DD8, 0xBD70, 0x7DD9, 0xBD79, 0x7DDA, 0xBD75, 0x7DDB, 0xE6E4, 0x7DDD, 0xBD72, 0x7DDE, 0xBD76, 0x7DDF, 0xE6F0, + 0x7DE0, 0xBD6C, 0x7DE1, 0xE6E8, 0x7DE3, 0xBD74, 0x7DE6, 0xE6EB, 0x7DE7, 0xE6E6, 0x7DE8, 0xBD73, 0x7DE9, 0xBD77, 0x7DEA, 0xE6E5, + 0x7DEC, 0xBD71, 0x7DEE, 0xE6EF, 0x7DEF, 0xBD6E, 0x7DF0, 0xE6EE, 0x7DF1, 0xE6ED, 0x7DF2, 0xBD7A, 0x7DF3, 0xE572, 0x7DF4, 0xBD6D, + 0x7DF6, 0xE6EC, 0x7DF7, 0xE6E3, 0x7DF9, 0xBD7B, 0x7DFA, 0xE6EA, 0x7DFB, 0xBD6F, 0x7E03, 0xE6E9, 0x7E08, 0xBFA2, 0x7E09, 0xBFA7, + 0x7E0A, 0xBF7E, 0x7E0B, 0xEAD8, 0x7E0C, 0xEACF, 0x7E0D, 0xEADB, 0x7E0E, 0xEAD3, 0x7E0F, 0xEAD9, 0x7E10, 0xBFA8, 0x7E11, 0xBFA1, + 0x7E12, 0xEACC, 0x7E13, 0xEAD2, 0x7E14, 0xEADC, 0x7E15, 0xEAD5, 0x7E16, 0xEADA, 0x7E17, 0xEACE, 0x7E1A, 0xEAD6, 0x7E1B, 0xBFA3, + 0x7E1C, 0xEAD4, 0x7E1D, 0xBFA6, 0x7E1E, 0xBFA5, 0x7E1F, 0xEAD0, 0x7E20, 0xEAD1, 0x7E21, 0xEACD, 0x7E22, 0xEAD7, 0x7E23, 0xBFA4, + 0x7E24, 0xEADE, 0x7E25, 0xEADD, 0x7E29, 0xEDDA, 0x7E2A, 0xEDD6, 0x7E2B, 0xC15F, 0x7E2D, 0xEDD0, 0x7E2E, 0xC159, 0x7E2F, 0xC169, + 0x7E30, 0xEDDC, 0x7E31, 0xC161, 0x7E32, 0xC15D, 0x7E33, 0xEDD3, 0x7E34, 0xC164, 0x7E35, 0xC167, 0x7E36, 0xEDDE, 0x7E37, 0xC15C, + 0x7E38, 0xEDD5, 0x7E39, 0xC165, 0x7E3A, 0xEDE0, 0x7E3B, 0xEDDD, 0x7E3C, 0xEDD1, 0x7E3D, 0xC160, 0x7E3E, 0xC15A, 0x7E3F, 0xC168, + 0x7E40, 0xEDD8, 0x7E41, 0xC163, 0x7E42, 0xEDD2, 0x7E43, 0xC15E, 0x7E44, 0xEDDF, 0x7E45, 0xC162, 0x7E46, 0xC15B, 0x7E47, 0xEDD9, + 0x7E48, 0xC166, 0x7E49, 0xEDD7, 0x7E4C, 0xEDDB, 0x7E50, 0xF06E, 0x7E51, 0xF074, 0x7E52, 0xC2B9, 0x7E53, 0xF077, 0x7E54, 0xC2B4, + 0x7E55, 0xC2B5, 0x7E56, 0xF06F, 0x7E57, 0xF076, 0x7E58, 0xF071, 0x7E59, 0xC2BA, 0x7E5A, 0xC2B7, 0x7E5C, 0xF06D, 0x7E5E, 0xC2B6, + 0x7E5F, 0xF073, 0x7E60, 0xF075, 0x7E61, 0xC2B8, 0x7E62, 0xF072, 0x7E63, 0xF070, 0x7E68, 0xF2B8, 0x7E69, 0xC3B7, 0x7E6A, 0xC3B8, + 0x7E6B, 0xC3B4, 0x7E6D, 0xC3B5, 0x7E6F, 0xF2B4, 0x7E70, 0xF2B2, 0x7E72, 0xF2B6, 0x7E73, 0xC3BA, 0x7E74, 0xF2B7, 0x7E75, 0xF2B0, + 0x7E76, 0xF2AF, 0x7E77, 0xF2B3, 0x7E78, 0xF2B1, 0x7E79, 0xC3B6, 0x7E7A, 0xF2B5, 0x7E7B, 0xF4AC, 0x7E7C, 0xC47E, 0x7E7D, 0xC47D, + 0x7E7E, 0xF4AD, 0x7E80, 0xF4AF, 0x7E81, 0xF4AE, 0x7E82, 0xC4A1, 0x7E86, 0xF5EB, 0x7E87, 0xF5E8, 0x7E88, 0xF5E9, 0x7E8A, 0xF5E7, + 0x7E8B, 0xF5EA, 0x7E8C, 0xC4F2, 0x7E8D, 0xF5EC, 0x7E8F, 0xC4F1, 0x7E91, 0xF742, 0x7E93, 0xC5D5, 0x7E94, 0xC5D7, 0x7E95, 0xF7EE, + 0x7E96, 0xC5D6, 0x7E97, 0xF8B9, 0x7E98, 0xF940, 0x7E99, 0xF942, 0x7E9A, 0xF8FE, 0x7E9B, 0xF941, 0x7E9C, 0xC66C, 0x7F36, 0xA6CE, + 0x7F38, 0xACFB, 0x7F39, 0xD26F, 0x7F3A, 0xAFCA, 0x7F3D, 0xB2DA, 0x7F3E, 0xDAFC, 0x7F3F, 0xDAFD, 0x7F43, 0xEADF, 0x7F44, 0xC16A, + 0x7F45, 0xEDE1, 0x7F48, 0xC2BB, 0x7F4A, 0xF2BA, 0x7F4B, 0xF2B9, 0x7F4C, 0xC4A2, 0x7F4D, 0xF5ED, 0x7F4F, 0xF743, 0x7F50, 0xC5F8, + 0x7F51, 0xCA49, 0x7F54, 0xAAC9, 0x7F55, 0xA875, 0x7F58, 0xD04D, 0x7F5B, 0xD360, 0x7F5C, 0xD35B, 0x7F5D, 0xD35F, 0x7F5E, 0xD35D, + 0x7F5F, 0xAFCB, 0x7F60, 0xD35E, 0x7F61, 0xD35C, 0x7F63, 0xD6F1, 0x7F65, 0xDAFE, 0x7F66, 0xDB40, 0x7F67, 0xDF69, 0x7F68, 0xDF6A, + 0x7F69, 0xB86E, 0x7F6A, 0xB86F, 0x7F6B, 0xDF68, 0x7F6C, 0xDF6B, 0x7F6D, 0xDF67, 0x7F6E, 0xB86D, 0x7F70, 0xBB40, 0x7F72, 0xB870, + 0x7F73, 0xE37A, 0x7F75, 0xBD7C, 0x7F76, 0xE6F1, 0x7F77, 0xBD7D, 0x7F79, 0xBFA9, 0x7F7A, 0xEAE2, 0x7F7B, 0xEAE0, 0x7F7C, 0xEAE1, + 0x7F7D, 0xEDE4, 0x7F7E, 0xEDE3, 0x7F7F, 0xEDE2, 0x7F83, 0xF2BB, 0x7F85, 0xC3B9, 0x7F86, 0xF2BC, 0x7F87, 0xF744, 0x7F88, 0xC5F9, + 0x7F89, 0xF8BA, 0x7F8A, 0xA6CF, 0x7F8B, 0xAACB, 0x7F8C, 0xAACA, 0x7F8D, 0xD04F, 0x7F8E, 0xACFC, 0x7F91, 0xD04E, 0x7F92, 0xD362, + 0x7F94, 0xAFCC, 0x7F95, 0xD6F2, 0x7F96, 0xD361, 0x7F9A, 0xB2DC, 0x7F9B, 0xD6F5, 0x7F9C, 0xD6F3, 0x7F9D, 0xD6F4, 0x7F9E, 0xB2DB, + 0x7FA0, 0xDB42, 0x7FA1, 0xDB43, 0x7FA2, 0xDB41, 0x7FA4, 0xB873, 0x7FA5, 0xDF6D, 0x7FA6, 0xDF6C, 0x7FA7, 0xDF6E, 0x7FA8, 0xB872, + 0x7FA9, 0xB871, 0x7FAC, 0xE6F2, 0x7FAD, 0xE6F4, 0x7FAF, 0xBD7E, 0x7FB0, 0xE6F3, 0x7FB1, 0xEAE3, 0x7FB2, 0xBFAA, 0x7FB3, 0xF079, + 0x7FB5, 0xF078, 0x7FB6, 0xC3BB, 0x7FB7, 0xF2BD, 0x7FB8, 0xC3BD, 0x7FB9, 0xC3BC, 0x7FBA, 0xF4B0, 0x7FBB, 0xF5EE, 0x7FBC, 0xC4F3, + 0x7FBD, 0xA6D0, 0x7FBE, 0xD050, 0x7FBF, 0xACFD, 0x7FC0, 0xD365, 0x7FC1, 0xAFCE, 0x7FC2, 0xD364, 0x7FC3, 0xD363, 0x7FC5, 0xAFCD, + 0x7FC7, 0xD6FB, 0x7FC9, 0xD6FD, 0x7FCA, 0xD6F6, 0x7FCB, 0xD6F7, 0x7FCC, 0xB2DD, 0x7FCD, 0xD6F8, 0x7FCE, 0xB2DE, 0x7FCF, 0xD6FC, + 0x7FD0, 0xD6F9, 0x7FD1, 0xD6FA, 0x7FD2, 0xB2DF, 0x7FD4, 0xB5BE, 0x7FD5, 0xB5BF, 0x7FD7, 0xDB44, 0x7FDB, 0xDF6F, 0x7FDC, 0xDF70, + 0x7FDE, 0xE37E, 0x7FDF, 0xBB43, 0x7FE0, 0xBB41, 0x7FE1, 0xBB42, 0x7FE2, 0xE37B, 0x7FE3, 0xE37C, 0x7FE5, 0xE37D, 0x7FE6, 0xE6F9, + 0x7FE8, 0xE6FA, 0x7FE9, 0xBDA1, 0x7FEA, 0xE6F7, 0x7FEB, 0xE6F6, 0x7FEC, 0xE6F8, 0x7FED, 0xE6F5, 0x7FEE, 0xBFAD, 0x7FEF, 0xEAE4, + 0x7FF0, 0xBFAB, 0x7FF1, 0xBFAC, 0x7FF2, 0xEDE6, 0x7FF3, 0xC16B, 0x7FF4, 0xEDE5, 0x7FF5, 0xEFA8, 0x7FF7, 0xF07A, 0x7FF8, 0xF07B, + 0x7FF9, 0xC2BC, 0x7FFB, 0xC2BD, 0x7FFC, 0xC16C, 0x7FFD, 0xF2BE, 0x7FFE, 0xF2BF, 0x7FFF, 0xF4B1, 0x8000, 0xC4A3, 0x8001, 0xA6D1, + 0x8003, 0xA6D2, 0x8004, 0xACFE, 0x8005, 0xAACC, 0x8006, 0xAFCF, 0x8007, 0xD051, 0x800B, 0xB5C0, 0x800C, 0xA6D3, 0x800D, 0xAD41, + 0x800E, 0xD052, 0x800F, 0xD053, 0x8010, 0xAD40, 0x8011, 0xAD42, 0x8012, 0xA6D4, 0x8014, 0xD054, 0x8015, 0xAFD1, 0x8016, 0xD366, + 0x8017, 0xAFD3, 0x8018, 0xAFD0, 0x8019, 0xAFD2, 0x801B, 0xD741, 0x801C, 0xB2E0, 0x801E, 0xD740, 0x801F, 0xD6FE, 0x8021, 0xDF71, + 0x8024, 0xE3A1, 0x8026, 0xBDA2, 0x8028, 0xBFAE, 0x8029, 0xEAE6, 0x802A, 0xEAE5, 0x802C, 0xEDE7, 0x8030, 0xF5EF, 0x8033, 0xA6D5, + 0x8034, 0xCB73, 0x8035, 0xCDAA, 0x8036, 0xAD43, 0x8037, 0xD055, 0x8039, 0xD368, 0x803D, 0xAFD4, 0x803E, 0xD367, 0x803F, 0xAFD5, + 0x8043, 0xD743, 0x8046, 0xB2E2, 0x8047, 0xD742, 0x8048, 0xD744, 0x804A, 0xB2E1, 0x804F, 0xDB46, 0x8050, 0xDB47, 0x8051, 0xDB45, + 0x8052, 0xB5C1, 0x8056, 0xB874, 0x8058, 0xB875, 0x805A, 0xBB45, 0x805C, 0xE3A3, 0x805D, 0xE3A2, 0x805E, 0xBB44, 0x8064, 0xE6FB, + 0x8067, 0xE6FC, 0x806C, 0xEAE7, 0x806F, 0xC170, 0x8070, 0xC16F, 0x8071, 0xC16D, 0x8072, 0xC16E, 0x8073, 0xC171, 0x8075, 0xF07C, + 0x8076, 0xC2BF, 0x8077, 0xC2BE, 0x8078, 0xF2C0, 0x8079, 0xF4B2, 0x807D, 0xC5A5, 0x807E, 0xC5A4, 0x807F, 0xA6D6, 0x8082, 0xD1FB, + 0x8084, 0xB877, 0x8085, 0xB5C2, 0x8086, 0xB876, 0x8087, 0xBB46, 0x8089, 0xA6D7, 0x808A, 0xC9A9, 0x808B, 0xA6D8, 0x808C, 0xA6D9, + 0x808F, 0xCDAB, 0x8090, 0xCB76, 0x8092, 0xCB77, 0x8093, 0xA877, 0x8095, 0xCB74, 0x8096, 0xA876, 0x8098, 0xA879, 0x8099, 0xCB75, + 0x809A, 0xA87B, 0x809B, 0xA87A, 0x809C, 0xCB78, 0x809D, 0xA878, 0x80A1, 0xAAD1, 0x80A2, 0xAACF, 0x80A3, 0xCDAD, 0x80A5, 0xAACE, + 0x80A9, 0xAAD3, 0x80AA, 0xAAD5, 0x80AB, 0xAAD2, 0x80AD, 0xCDB0, 0x80AE, 0xCDAC, 0x80AF, 0xAAD6, 0x80B1, 0xAAD0, 0x80B2, 0xA87C, + 0x80B4, 0xAAD4, 0x80B5, 0xCDAF, 0x80B8, 0xCDAE, 0x80BA, 0xAACD, 0x80C2, 0xD05B, 0x80C3, 0xAD47, 0x80C4, 0xAD48, 0x80C5, 0xD05D, + 0x80C7, 0xD057, 0x80C8, 0xD05A, 0x80C9, 0xD063, 0x80CA, 0xD061, 0x80CC, 0xAD49, 0x80CD, 0xD067, 0x80CE, 0xAD4C, 0x80CF, 0xD064, + 0x80D0, 0xD05C, 0x80D1, 0xD059, 0x80D4, 0xDB49, 0x80D5, 0xD062, 0x80D6, 0xAD44, 0x80D7, 0xD065, 0x80D8, 0xD056, 0x80D9, 0xD05F, + 0x80DA, 0xAD46, 0x80DB, 0xAD4B, 0x80DC, 0xD060, 0x80DD, 0xAD4F, 0x80DE, 0xAD4D, 0x80E0, 0xD058, 0x80E1, 0xAD4A, 0x80E3, 0xD05E, + 0x80E4, 0xAD4E, 0x80E5, 0xAD45, 0x80E6, 0xD066, 0x80ED, 0xAFDA, 0x80EF, 0xAFE3, 0x80F0, 0xAFD8, 0x80F1, 0xAFD6, 0x80F2, 0xD36A, + 0x80F3, 0xAFDE, 0x80F4, 0xAFDB, 0x80F5, 0xD36C, 0x80F8, 0xAFDD, 0x80F9, 0xD36B, 0x80FA, 0xD369, 0x80FB, 0xD36E, 0x80FC, 0xAFE2, + 0x80FD, 0xAFE0, 0x80FE, 0xDB48, 0x8100, 0xD36F, 0x8101, 0xD36D, 0x8102, 0xAFD7, 0x8105, 0xAFD9, 0x8106, 0xAFDC, 0x8108, 0xAFDF, + 0x810A, 0xAFE1, 0x8115, 0xD74E, 0x8116, 0xB2E4, 0x8118, 0xD745, 0x8119, 0xD747, 0x811B, 0xD748, 0x811D, 0xD750, 0x811E, 0xD74C, + 0x811F, 0xD74A, 0x8121, 0xD74D, 0x8122, 0xD751, 0x8123, 0xB2E5, 0x8124, 0xB2E9, 0x8125, 0xD746, 0x8127, 0xD74F, 0x8129, 0xB2E7, + 0x812B, 0xB2E6, 0x812C, 0xD74B, 0x812D, 0xD749, 0x812F, 0xB2E3, 0x8130, 0xB2E8, 0x8139, 0xB5C8, 0x813A, 0xDB51, 0x813D, 0xDB4F, + 0x813E, 0xB5CA, 0x8143, 0xDB4A, 0x8144, 0xDFA1, 0x8146, 0xB5C9, 0x8147, 0xDB4E, 0x814A, 0xDB4B, 0x814B, 0xB5C5, 0x814C, 0xB5CB, + 0x814D, 0xDB50, 0x814E, 0xB5C7, 0x814F, 0xDB4D, 0x8150, 0xBB47, 0x8151, 0xB5C6, 0x8152, 0xDB4C, 0x8153, 0xB5CC, 0x8154, 0xB5C4, + 0x8155, 0xB5C3, 0x815B, 0xDF77, 0x815C, 0xDF75, 0x815E, 0xDF7B, 0x8160, 0xDF73, 0x8161, 0xDFA2, 0x8162, 0xDF78, 0x8164, 0xDF72, + 0x8165, 0xB87B, 0x8166, 0xB8A3, 0x8167, 0xDF7D, 0x8169, 0xDF76, 0x816B, 0xB87E, 0x816E, 0xB87C, 0x816F, 0xDF7E, 0x8170, 0xB879, + 0x8171, 0xB878, 0x8172, 0xDF79, 0x8173, 0xB87D, 0x8174, 0xB5CD, 0x8176, 0xDF7C, 0x8177, 0xDF74, 0x8178, 0xB87A, 0x8179, 0xB8A1, + 0x817A, 0xB8A2, 0x817F, 0xBB4C, 0x8180, 0xBB48, 0x8182, 0xBB4D, 0x8183, 0xE3A6, 0x8186, 0xE3A5, 0x8187, 0xE3A7, 0x8188, 0xBB4A, + 0x8189, 0xE3A4, 0x818A, 0xBB4B, 0x818B, 0xE3AA, 0x818C, 0xE3A9, 0x818D, 0xE3A8, 0x818F, 0xBB49, 0x8195, 0xE741, 0x8197, 0xE744, + 0x8198, 0xBDA8, 0x8199, 0xE743, 0x819A, 0xBDA7, 0x819B, 0xBDA3, 0x819C, 0xBDA4, 0x819D, 0xBDA5, 0x819E, 0xE740, 0x819F, 0xE6FE, + 0x81A0, 0xBDA6, 0x81A2, 0xE742, 0x81A3, 0xE6FD, 0x81A6, 0xEAE9, 0x81A7, 0xEAF3, 0x81A8, 0xBFB1, 0x81A9, 0xBFB0, 0x81AB, 0xEAED, + 0x81AC, 0xEAEF, 0x81AE, 0xEAEA, 0x81B0, 0xEAEE, 0x81B1, 0xEAE8, 0x81B2, 0xEAF1, 0x81B3, 0xBFAF, 0x81B4, 0xEAF0, 0x81B5, 0xEAEC, + 0x81B7, 0xEAF2, 0x81B9, 0xEAEB, 0x81BA, 0xC174, 0x81BB, 0xEDE8, 0x81BC, 0xEDEE, 0x81BD, 0xC178, 0x81BE, 0xC17A, 0x81BF, 0xC177, + 0x81C0, 0xC176, 0x81C2, 0xC175, 0x81C3, 0xC173, 0x81C4, 0xEDE9, 0x81C5, 0xEDEC, 0x81C6, 0xC172, 0x81C7, 0xEDED, 0x81C9, 0xC179, + 0x81CA, 0xEDEB, 0x81CC, 0xEDEA, 0x81CD, 0xC2C0, 0x81CF, 0xC2C1, 0x81D0, 0xF0A1, 0x81D1, 0xF07D, 0x81D2, 0xF07E, 0x81D5, 0xF2C2, + 0x81D7, 0xF2C1, 0x81D8, 0xC3BE, 0x81D9, 0xF4B4, 0x81DA, 0xC4A4, 0x81DB, 0xF4B3, 0x81DD, 0xF5F0, 0x81DE, 0xF745, 0x81DF, 0xC5A6, + 0x81E0, 0xF943, 0x81E1, 0xF944, 0x81E2, 0xC5D8, 0x81E3, 0xA6DA, 0x81E5, 0xAAD7, 0x81E6, 0xDB52, 0x81E7, 0xBB4E, 0x81E8, 0xC17B, + 0x81E9, 0xEDEF, 0x81EA, 0xA6DB, 0x81EC, 0xAFE5, 0x81ED, 0xAFE4, 0x81EE, 0xDB53, 0x81F2, 0xEAF4, 0x81F3, 0xA6DC, 0x81F4, 0xAD50, + 0x81F7, 0xDB54, 0x81F8, 0xDB55, 0x81F9, 0xDB56, 0x81FA, 0xBB4F, 0x81FB, 0xBFB2, 0x81FC, 0xA6DD, 0x81FE, 0xAAD8, 0x81FF, 0xD068, + 0x8200, 0xAFE6, 0x8201, 0xD370, 0x8202, 0xB2EA, 0x8204, 0xDB57, 0x8205, 0xB8A4, 0x8207, 0xBB50, 0x8208, 0xBFB3, 0x8209, 0xC17C, + 0x820A, 0xC2C2, 0x820B, 0xF4B5, 0x820C, 0xA6DE, 0x820D, 0xAAD9, 0x8210, 0xAFE7, 0x8211, 0xD752, 0x8212, 0xB5CE, 0x8214, 0xBB51, + 0x8215, 0xE3AB, 0x8216, 0xE745, 0x821B, 0xA6DF, 0x821C, 0xB5CF, 0x821D, 0xDFA3, 0x821E, 0xBB52, 0x821F, 0xA6E0, 0x8220, 0xCDB1, + 0x8221, 0xD069, 0x8222, 0xAD51, 0x8225, 0xD372, 0x8228, 0xAFEA, 0x822A, 0xAFE8, 0x822B, 0xAFE9, 0x822C, 0xAFEB, 0x822F, 0xD371, + 0x8232, 0xD757, 0x8233, 0xD754, 0x8234, 0xD756, 0x8235, 0xB2EB, 0x8236, 0xB2ED, 0x8237, 0xB2EC, 0x8238, 0xD753, 0x8239, 0xB2EE, + 0x823A, 0xD755, 0x823C, 0xDB58, 0x823D, 0xDB59, 0x823F, 0xDB5A, 0x8240, 0xDFA6, 0x8242, 0xDFA7, 0x8244, 0xDFA5, 0x8245, 0xDFA8, + 0x8247, 0xB8A5, 0x8249, 0xDFA4, 0x824B, 0xBB53, 0x824E, 0xE74A, 0x824F, 0xE746, 0x8250, 0xE749, 0x8251, 0xE74B, 0x8252, 0xE748, + 0x8253, 0xE747, 0x8255, 0xEAF5, 0x8256, 0xEAF6, 0x8257, 0xEAF7, 0x8258, 0xBFB4, 0x8259, 0xBFB5, 0x825A, 0xEDF1, 0x825B, 0xEDF0, + 0x825C, 0xEDF2, 0x825E, 0xF0A3, 0x825F, 0xF0A2, 0x8261, 0xF2C4, 0x8263, 0xF2C5, 0x8264, 0xF2C3, 0x8266, 0xC4A5, 0x8268, 0xF4B6, + 0x8269, 0xF4B7, 0x826B, 0xF746, 0x826C, 0xF7EF, 0x826D, 0xF8BB, 0x826E, 0xA6E1, 0x826F, 0xA87D, 0x8271, 0xC17D, 0x8272, 0xA6E2, + 0x8274, 0xD758, 0x8275, 0xDB5B, 0x8277, 0xC641, 0x8278, 0xCA4A, 0x827C, 0xCA4B, 0x827D, 0xCA4D, 0x827E, 0xA6E3, 0x827F, 0xCA4E, + 0x8280, 0xCA4C, 0x8283, 0xCBA2, 0x8284, 0xCBA3, 0x8285, 0xCB7B, 0x828A, 0xCBA1, 0x828B, 0xA8A1, 0x828D, 0xA8A2, 0x828E, 0xCB7C, + 0x828F, 0xCB7A, 0x8290, 0xCB79, 0x8291, 0xCB7D, 0x8292, 0xA87E, 0x8293, 0xCB7E, 0x8294, 0xD06A, 0x8298, 0xCDB6, 0x8299, 0xAADC, + 0x829A, 0xCDB5, 0x829B, 0xCDB7, 0x829D, 0xAADB, 0x829E, 0xCDBC, 0x829F, 0xAADF, 0x82A0, 0xCDB2, 0x82A1, 0xCDC0, 0x82A2, 0xCDC6, + 0x82A3, 0xAAE6, 0x82A4, 0xCDC3, 0x82A5, 0xAAE3, 0x82A7, 0xCDB9, 0x82A8, 0xCDBF, 0x82A9, 0xCDC1, 0x82AB, 0xCDB4, 0x82AC, 0xAAE2, + 0x82AD, 0xAADD, 0x82AE, 0xCDBA, 0x82AF, 0xAAE4, 0x82B0, 0xAAE7, 0x82B1, 0xAAE1, 0x82B3, 0xAADA, 0x82B4, 0xCDBE, 0x82B5, 0xCDB8, + 0x82B6, 0xCDC5, 0x82B7, 0xAAE9, 0x82B8, 0xAAE5, 0x82B9, 0xAAE0, 0x82BA, 0xCDBD, 0x82BB, 0xAFEC, 0x82BC, 0xCDBB, 0x82BD, 0xAADE, + 0x82BE, 0xAAE8, 0x82C0, 0xCDB3, 0x82C2, 0xCDC2, 0x82C3, 0xCDC4, 0x82D1, 0xAD62, 0x82D2, 0xAD5C, 0x82D3, 0xAD64, 0x82D4, 0xAD61, + 0x82D5, 0xD071, 0x82D6, 0xD074, 0x82D7, 0xAD5D, 0x82D9, 0xD06B, 0x82DB, 0xAD56, 0x82DC, 0xAD60, 0x82DE, 0xAD63, 0x82DF, 0xAD65, + 0x82E0, 0xD0A2, 0x82E1, 0xD077, 0x82E3, 0xAD55, 0x82E4, 0xD0A1, 0x82E5, 0xAD59, 0x82E6, 0xAD57, 0x82E7, 0xAD52, 0x82E8, 0xD06F, + 0x82EA, 0xD07E, 0x82EB, 0xD073, 0x82EC, 0xD076, 0x82ED, 0xD0A5, 0x82EF, 0xAD66, 0x82F0, 0xD07D, 0x82F1, 0xAD5E, 0x82F2, 0xD078, + 0x82F3, 0xD0A4, 0x82F4, 0xD075, 0x82F5, 0xD079, 0x82F6, 0xD07C, 0x82F9, 0xD06D, 0x82FA, 0xD0A3, 0x82FB, 0xD07B, 0x82FE, 0xD06C, + 0x8300, 0xD070, 0x8301, 0xAD5F, 0x8302, 0xAD5A, 0x8303, 0xAD53, 0x8304, 0xAD58, 0x8305, 0xAD54, 0x8306, 0xAD67, 0x8307, 0xD06E, + 0x8308, 0xD3A5, 0x8309, 0xAD5B, 0x830C, 0xD07A, 0x830D, 0xCE41, 0x8316, 0xD3A8, 0x8317, 0xAFFA, 0x8319, 0xD376, 0x831B, 0xD3A3, + 0x831C, 0xD37D, 0x831E, 0xD3B2, 0x8320, 0xD3AA, 0x8322, 0xD37E, 0x8324, 0xD3A9, 0x8325, 0xD378, 0x8326, 0xD37C, 0x8327, 0xD3B5, + 0x8328, 0xAFFD, 0x8329, 0xD3AD, 0x832A, 0xD3A4, 0x832B, 0xAFED, 0x832C, 0xD3B3, 0x832D, 0xD374, 0x832F, 0xD3AC, 0x8331, 0xAFFC, + 0x8332, 0xAFF7, 0x8333, 0xD373, 0x8334, 0xAFF5, 0x8335, 0xAFF4, 0x8336, 0xAFF9, 0x8337, 0xD3AB, 0x8338, 0xAFF1, 0x8339, 0xAFF8, + 0x833A, 0xD072, 0x833B, 0xDB5C, 0x833C, 0xD3A6, 0x833F, 0xD37A, 0x8340, 0xAFFB, 0x8341, 0xD37B, 0x8342, 0xD3A1, 0x8343, 0xAFFE, + 0x8344, 0xD375, 0x8345, 0xD3AF, 0x8347, 0xD3AE, 0x8348, 0xD3B6, 0x8349, 0xAFF3, 0x834A, 0xAFF0, 0x834B, 0xD3B4, 0x834C, 0xD3B0, + 0x834D, 0xD3A7, 0x834E, 0xD3A2, 0x834F, 0xAFF6, 0x8350, 0xAFF2, 0x8351, 0xD377, 0x8352, 0xAFEE, 0x8353, 0xD3B1, 0x8354, 0xAFEF, + 0x8356, 0xD379, 0x8373, 0xD75E, 0x8374, 0xD760, 0x8375, 0xD765, 0x8376, 0xD779, 0x8377, 0xB2FC, 0x8378, 0xB2F2, 0x837A, 0xD75D, + 0x837B, 0xB2FD, 0x837C, 0xB2FE, 0x837D, 0xD768, 0x837E, 0xD76F, 0x837F, 0xD775, 0x8381, 0xD762, 0x8383, 0xD769, 0x8386, 0xB340, + 0x8387, 0xD777, 0x8388, 0xD772, 0x8389, 0xB2FA, 0x838A, 0xB2F8, 0x838B, 0xD76E, 0x838C, 0xD76A, 0x838D, 0xD75C, 0x838E, 0xB2EF, + 0x838F, 0xD761, 0x8390, 0xD759, 0x8392, 0xB2F7, 0x8393, 0xB2F9, 0x8394, 0xD766, 0x8395, 0xD763, 0x8396, 0xB2F4, 0x8397, 0xD773, + 0x8398, 0xB2F1, 0x8399, 0xD764, 0x839A, 0xD77A, 0x839B, 0xD76C, 0x839D, 0xD76B, 0x839E, 0xB2F0, 0x83A0, 0xB2FB, 0x83A2, 0xB2F3, + 0x83A3, 0xD75A, 0x83A4, 0xD75F, 0x83A5, 0xD770, 0x83A6, 0xD776, 0x83A7, 0xB341, 0x83A8, 0xD75B, 0x83A9, 0xD767, 0x83AA, 0xD76D, + 0x83AB, 0xB2F6, 0x83AE, 0xD778, 0x83AF, 0xD771, 0x83B0, 0xD774, 0x83BD, 0xB2F5, 0x83BF, 0xDB6C, 0x83C0, 0xDB60, 0x83C1, 0xB5D7, + 0x83C2, 0xDB7D, 0x83C3, 0xDBA7, 0x83C4, 0xDBAA, 0x83C5, 0xB5D5, 0x83C6, 0xDB68, 0x83C7, 0xDBA3, 0x83C8, 0xDB69, 0x83C9, 0xDB77, + 0x83CA, 0xB5E2, 0x83CB, 0xDB73, 0x83CC, 0xB5DF, 0x83CE, 0xDB74, 0x83CF, 0xDB5D, 0x83D1, 0xDBA4, 0x83D4, 0xB5E8, 0x83D5, 0xDBA1, + 0x83D6, 0xDB75, 0x83D7, 0xDBAC, 0x83D8, 0xDB70, 0x83D9, 0xDFC8, 0x83DB, 0xDBAF, 0x83DC, 0xB5E6, 0x83DD, 0xDB6E, 0x83DE, 0xDB7A, + 0x83DF, 0xB5E9, 0x83E0, 0xB5D4, 0x83E1, 0xDB72, 0x83E2, 0xDBAD, 0x83E3, 0xDB6B, 0x83E4, 0xDB64, 0x83E5, 0xDB6F, 0x83E7, 0xDB63, + 0x83E8, 0xDB61, 0x83E9, 0xB5D0, 0x83EA, 0xDBA5, 0x83EB, 0xDB6A, 0x83EC, 0xDBA8, 0x83EE, 0xDBA9, 0x83EF, 0xB5D8, 0x83F0, 0xB5DD, + 0x83F1, 0xB5D9, 0x83F2, 0xB5E1, 0x83F3, 0xDB7E, 0x83F4, 0xB5DA, 0x83F5, 0xDB76, 0x83F6, 0xDB66, 0x83F8, 0xB5D2, 0x83F9, 0xDB5E, + 0x83FA, 0xDBA2, 0x83FB, 0xDBAB, 0x83FC, 0xDB65, 0x83FD, 0xB5E0, 0x83FE, 0xDBB0, 0x83FF, 0xDB71, 0x8401, 0xDB6D, 0x8403, 0xB5D1, + 0x8404, 0xB5E5, 0x8406, 0xDB7C, 0x8407, 0xB5E7, 0x8409, 0xDB78, 0x840A, 0xB5DC, 0x840B, 0xB5D6, 0x840C, 0xB5DE, 0x840D, 0xB5D3, + 0x840E, 0xB5E4, 0x840F, 0xDB79, 0x8410, 0xDB67, 0x8411, 0xDB7B, 0x8412, 0xDB62, 0x8413, 0xDBA6, 0x841B, 0xDBAE, 0x8423, 0xDB5F, + 0x8429, 0xDFC7, 0x842B, 0xDFDD, 0x842C, 0xB855, 0x842D, 0xDFCC, 0x842F, 0xDFCA, 0x8430, 0xDFB5, 0x8431, 0xB8A9, 0x8432, 0xDFC5, + 0x8433, 0xDFD9, 0x8434, 0xDFC1, 0x8435, 0xB8B1, 0x8436, 0xDFD8, 0x8437, 0xDFBF, 0x8438, 0xB5E3, 0x8439, 0xDFCF, 0x843A, 0xDFC0, + 0x843B, 0xDFD6, 0x843C, 0xB8B0, 0x843D, 0xB8A8, 0x843F, 0xDFAA, 0x8440, 0xDFB2, 0x8442, 0xDFCB, 0x8443, 0xDFC3, 0x8444, 0xDFDC, + 0x8445, 0xDFC6, 0x8446, 0xB8B6, 0x8447, 0xDFD7, 0x8449, 0xB8AD, 0x844B, 0xDFC9, 0x844C, 0xDFD1, 0x844D, 0xDFB6, 0x844E, 0xDFD0, + 0x8450, 0xDFE1, 0x8451, 0xDFB1, 0x8452, 0xDFD2, 0x8454, 0xDFDF, 0x8456, 0xDFAB, 0x8457, 0xB5DB, 0x8459, 0xDFB9, 0x845A, 0xDFB8, + 0x845B, 0xB8AF, 0x845D, 0xDFBC, 0x845E, 0xDFBE, 0x845F, 0xDFCD, 0x8460, 0xDFDE, 0x8461, 0xB8B2, 0x8463, 0xB8B3, 0x8465, 0xDFB0, + 0x8466, 0xB8AB, 0x8467, 0xDFB4, 0x8468, 0xDFDA, 0x8469, 0xB8B4, 0x846B, 0xB8AC, 0x846C, 0xB8AE, 0x846D, 0xB8B5, 0x846E, 0xDFE0, + 0x846F, 0xDFD3, 0x8470, 0xDFCE, 0x8473, 0xDFBB, 0x8474, 0xDFBA, 0x8475, 0xB8AA, 0x8476, 0xDFAC, 0x8477, 0xB8A7, 0x8478, 0xDFC4, + 0x8479, 0xDFAD, 0x847A, 0xDFC2, 0x847D, 0xDFB7, 0x847E, 0xDFDB, 0x8482, 0xB8A6, 0x8486, 0xDFB3, 0x848D, 0xDFAF, 0x848E, 0xDFD5, + 0x848F, 0xDFAE, 0x8490, 0xBB60, 0x8491, 0xE3D3, 0x8494, 0xE3C2, 0x8497, 0xE3AC, 0x8498, 0xE3CA, 0x8499, 0xBB58, 0x849A, 0xE3BB, + 0x849B, 0xE3C5, 0x849C, 0xBB5B, 0x849D, 0xE3BE, 0x849E, 0xBB59, 0x849F, 0xE3AF, 0x84A0, 0xE3CD, 0x84A1, 0xE3AE, 0x84A2, 0xE3C1, + 0x84A4, 0xE3AD, 0x84A7, 0xE3BF, 0x84A8, 0xE3C8, 0x84A9, 0xE3C6, 0x84AA, 0xE3BA, 0x84AB, 0xE3B5, 0x84AC, 0xE3B3, 0x84AE, 0xE3B4, + 0x84AF, 0xE3C7, 0x84B0, 0xE3D2, 0x84B1, 0xE3BC, 0x84B2, 0xBB5A, 0x84B4, 0xE3B7, 0x84B6, 0xE3CB, 0x84B8, 0xBB5D, 0x84B9, 0xE3B6, + 0x84BA, 0xE3B0, 0x84BB, 0xE3C0, 0x84BC, 0xBB61, 0x84BF, 0xBB55, 0x84C0, 0xBB5E, 0x84C1, 0xE3B8, 0x84C2, 0xE3B2, 0x84C4, 0xBB57, + 0x84C5, 0xDFD4, 0x84C6, 0xBB56, 0x84C7, 0xE3C3, 0x84C9, 0xBB54, 0x84CA, 0xBB63, 0x84CB, 0xBB5C, 0x84CC, 0xE3C4, 0x84CD, 0xE3B9, + 0x84CE, 0xE3B1, 0x84CF, 0xE3CC, 0x84D0, 0xE3BD, 0x84D1, 0xBB62, 0x84D2, 0xE3D0, 0x84D3, 0xBB5F, 0x84D4, 0xE3CF, 0x84D6, 0xE3C9, + 0x84D7, 0xE3CE, 0x84DB, 0xE3D1, 0x84E7, 0xE773, 0x84E8, 0xE774, 0x84E9, 0xE767, 0x84EA, 0xE766, 0x84EB, 0xE762, 0x84EC, 0xBDB4, + 0x84EE, 0xBDAC, 0x84EF, 0xE776, 0x84F0, 0xE775, 0x84F1, 0xDFA9, 0x84F2, 0xE75F, 0x84F3, 0xE763, 0x84F4, 0xE75D, 0x84F6, 0xE770, + 0x84F7, 0xE761, 0x84F9, 0xE777, 0x84FA, 0xE75A, 0x84FB, 0xE758, 0x84FC, 0xE764, 0x84FD, 0xE76E, 0x84FE, 0xE769, 0x84FF, 0xBDB6, + 0x8500, 0xE74F, 0x8502, 0xE76D, 0x8506, 0xBDB7, 0x8507, 0xDFBD, 0x8508, 0xE75B, 0x8509, 0xE752, 0x850A, 0xE755, 0x850B, 0xE77B, + 0x850C, 0xE75C, 0x850D, 0xE753, 0x850E, 0xE751, 0x850F, 0xE74E, 0x8511, 0xBDB0, 0x8512, 0xE765, 0x8513, 0xBDAF, 0x8514, 0xBDB3, + 0x8515, 0xE760, 0x8516, 0xE768, 0x8517, 0xBDA9, 0x8518, 0xE778, 0x8519, 0xE77C, 0x851A, 0xBDAB, 0x851C, 0xE757, 0x851D, 0xE76B, + 0x851E, 0xE76F, 0x851F, 0xE754, 0x8520, 0xE779, 0x8521, 0xBDB2, 0x8523, 0xBDB1, 0x8524, 0xE74C, 0x8525, 0xBDB5, 0x8526, 0xE772, + 0x8527, 0xE756, 0x8528, 0xE76A, 0x8529, 0xE750, 0x852A, 0xE75E, 0x852B, 0xE759, 0x852C, 0xBDAD, 0x852D, 0xBDAE, 0x852E, 0xE76C, + 0x852F, 0xE77D, 0x8530, 0xE77A, 0x8531, 0xE771, 0x853B, 0xE74D, 0x853D, 0xBDAA, 0x853E, 0xEB49, 0x8540, 0xEB40, 0x8541, 0xEB43, + 0x8543, 0xBFBB, 0x8544, 0xEB45, 0x8545, 0xEAF9, 0x8546, 0xEB41, 0x8547, 0xEB47, 0x8548, 0xBFB8, 0x8549, 0xBFBC, 0x854A, 0xBFB6, + 0x854D, 0xEAFB, 0x854E, 0xEB4C, 0x8551, 0xEB46, 0x8553, 0xEAFC, 0x8554, 0xEB55, 0x8555, 0xEB4F, 0x8556, 0xEAF8, 0x8557, 0xEE46, + 0x8558, 0xEAFE, 0x8559, 0xBFB7, 0x855B, 0xEB4A, 0x855D, 0xEB54, 0x855E, 0xBFBF, 0x8560, 0xEB51, 0x8561, 0xEAFD, 0x8562, 0xEB44, + 0x8563, 0xEB48, 0x8564, 0xEB42, 0x8565, 0xEB56, 0x8566, 0xEB53, 0x8567, 0xEB50, 0x8568, 0xBFB9, 0x8569, 0xBFBA, 0x856A, 0xBFBE, + 0x856B, 0xEAFA, 0x856C, 0xEB57, 0x856D, 0xBFBD, 0x856E, 0xEB4D, 0x8571, 0xEB4B, 0x8575, 0xEB4E, 0x8576, 0xEE53, 0x8577, 0xEE40, + 0x8578, 0xEE45, 0x8579, 0xEE52, 0x857A, 0xEE44, 0x857B, 0xEDFB, 0x857C, 0xEE41, 0x857E, 0xC1A2, 0x8580, 0xEDF4, 0x8581, 0xEE4D, + 0x8582, 0xEE4F, 0x8583, 0xEDF3, 0x8584, 0xC1A1, 0x8585, 0xEE51, 0x8586, 0xEE49, 0x8587, 0xC1A8, 0x8588, 0xEE50, 0x8589, 0xEE42, + 0x858A, 0xC1AA, 0x858B, 0xEDF9, 0x858C, 0xEB52, 0x858D, 0xEE4A, 0x858E, 0xEE47, 0x858F, 0xEDF5, 0x8590, 0xEE55, 0x8591, 0xC1A4, + 0x8594, 0xC1A5, 0x8595, 0xEDF7, 0x8596, 0xEE48, 0x8598, 0xEE54, 0x8599, 0xEE4B, 0x859A, 0xEDFD, 0x859B, 0xC1A7, 0x859C, 0xC1A3, + 0x859D, 0xEE4C, 0x859E, 0xEDFE, 0x859F, 0xEE56, 0x85A0, 0xEDF8, 0x85A1, 0xEE43, 0x85A2, 0xEE4E, 0x85A3, 0xEDFA, 0x85A4, 0xEDFC, + 0x85A6, 0xC2CB, 0x85A7, 0xEDF6, 0x85A8, 0xC1A9, 0x85A9, 0xC2C4, 0x85AA, 0xC17E, 0x85AF, 0xC1A6, 0x85B0, 0xC2C8, 0x85B1, 0xF0B3, + 0x85B3, 0xF0A9, 0x85B4, 0xF0A4, 0x85B5, 0xF0AA, 0x85B6, 0xF0B4, 0x85B7, 0xF0B8, 0x85B8, 0xF0B7, 0x85B9, 0xC2CA, 0x85BA, 0xC2C9, + 0x85BD, 0xF0AB, 0x85BE, 0xF0B9, 0x85BF, 0xF0AE, 0x85C0, 0xF0A6, 0x85C2, 0xF0A8, 0x85C3, 0xF0A7, 0x85C4, 0xF0AD, 0x85C5, 0xF0B2, + 0x85C6, 0xF0A5, 0x85C7, 0xF0AC, 0x85C8, 0xF0B1, 0x85C9, 0xC2C7, 0x85CB, 0xF0AF, 0x85CD, 0xC2C5, 0x85CE, 0xF0B0, 0x85CF, 0xC2C3, + 0x85D0, 0xC2C6, 0x85D1, 0xF2D5, 0x85D2, 0xF0B5, 0x85D5, 0xC3C2, 0x85D7, 0xF2CD, 0x85D8, 0xF2D1, 0x85D9, 0xF2C9, 0x85DA, 0xF2CC, + 0x85DC, 0xF2D4, 0x85DD, 0xC3C0, 0x85DE, 0xF2D9, 0x85DF, 0xF2D2, 0x85E1, 0xF2CA, 0x85E2, 0xF2DA, 0x85E3, 0xF2D3, 0x85E4, 0xC3C3, + 0x85E5, 0xC3C4, 0x85E6, 0xF2D7, 0x85E8, 0xF2CB, 0x85E9, 0xC3BF, 0x85EA, 0xC3C1, 0x85EB, 0xF2C6, 0x85EC, 0xF2CE, 0x85ED, 0xF2C8, + 0x85EF, 0xF2D8, 0x85F0, 0xF2D6, 0x85F1, 0xF2C7, 0x85F2, 0xF2CF, 0x85F6, 0xF4BE, 0x85F7, 0xC3C5, 0x85F8, 0xF2D0, 0x85F9, 0xC4A7, + 0x85FA, 0xC4A9, 0x85FB, 0xC4A6, 0x85FD, 0xF4C3, 0x85FE, 0xF4BB, 0x85FF, 0xF4B9, 0x8600, 0xF4BD, 0x8601, 0xF4BA, 0x8604, 0xF4BF, + 0x8605, 0xF4C1, 0x8606, 0xC4AA, 0x8607, 0xC4AC, 0x8609, 0xF4C0, 0x860A, 0xC4AD, 0x860B, 0xC4AB, 0x860C, 0xF4C2, 0x8611, 0xC4A8, + 0x8617, 0xC4F4, 0x8618, 0xF5F1, 0x8619, 0xF5F7, 0x861A, 0xC4F6, 0x861B, 0xF4BC, 0x861C, 0xF5F6, 0x861E, 0xF5FD, 0x861F, 0xF5F4, + 0x8620, 0xF5FB, 0x8621, 0xF5FA, 0x8622, 0xF4B8, 0x8623, 0xF5F5, 0x8624, 0xF0B6, 0x8625, 0xF5FE, 0x8626, 0xF5F3, 0x8627, 0xF5F8, + 0x8629, 0xF5FC, 0x862A, 0xF5F2, 0x862C, 0xF74A, 0x862D, 0xC4F5, 0x862E, 0xF5F9, 0x8631, 0xF7F4, 0x8632, 0xF74B, 0x8633, 0xF749, + 0x8634, 0xF747, 0x8635, 0xF748, 0x8636, 0xF74C, 0x8638, 0xC5D9, 0x8639, 0xF7F2, 0x863A, 0xF7F0, 0x863B, 0xF7F5, 0x863C, 0xF7F3, + 0x863E, 0xF7F6, 0x863F, 0xC5DA, 0x8640, 0xF7F1, 0x8643, 0xF8BC, 0x8646, 0xF945, 0x8647, 0xF946, 0x8648, 0xF947, 0x864B, 0xF9C7, + 0x864C, 0xF9BD, 0x864D, 0xCA4F, 0x864E, 0xAAEA, 0x8650, 0xAD68, 0x8652, 0xD3B8, 0x8653, 0xD3B7, 0x8654, 0xB040, 0x8655, 0xB342, + 0x8656, 0xD77C, 0x8659, 0xD77B, 0x865B, 0xB5EA, 0x865C, 0xB8B8, 0x865E, 0xB8B7, 0x865F, 0xB8B9, 0x8661, 0xE3D4, 0x8662, 0xE77E, + 0x8663, 0xEB58, 0x8664, 0xEB5A, 0x8665, 0xEB59, 0x8667, 0xC1AB, 0x8668, 0xEE57, 0x8669, 0xF0BA, 0x866A, 0xF9A5, 0x866B, 0xA6E4, + 0x866D, 0xCDC9, 0x866E, 0xCDCA, 0x866F, 0xCDC8, 0x8670, 0xCDC7, 0x8671, 0xAAEB, 0x8673, 0xD0A9, 0x8674, 0xD0A7, 0x8677, 0xD0A6, + 0x8679, 0xAD69, 0x867A, 0xAD6B, 0x867B, 0xAD6A, 0x867C, 0xD0A8, 0x8685, 0xD3C4, 0x8686, 0xD3C1, 0x8687, 0xD3BF, 0x868A, 0xB041, + 0x868B, 0xD3C2, 0x868C, 0xB046, 0x868D, 0xD3BC, 0x868E, 0xD3CB, 0x8690, 0xD3CD, 0x8691, 0xD3BD, 0x8693, 0xB043, 0x8694, 0xD3CE, + 0x8695, 0xD3C9, 0x8696, 0xD3BB, 0x8697, 0xD3C0, 0x8698, 0xD3CA, 0x8699, 0xD3C6, 0x869A, 0xD3C3, 0x869C, 0xB048, 0x869D, 0xD3CC, + 0x869E, 0xD3BE, 0x86A1, 0xD3C7, 0x86A2, 0xD3B9, 0x86A3, 0xB047, 0x86A4, 0xB044, 0x86A5, 0xD3C5, 0x86A7, 0xD3C8, 0x86A8, 0xD3BA, + 0x86A9, 0xB045, 0x86AA, 0xB042, 0x86AF, 0xB34C, 0x86B0, 0xD7A5, 0x86B1, 0xB34B, 0x86B3, 0xD7A8, 0x86B4, 0xD7AB, 0x86B5, 0xB348, + 0x86B6, 0xB346, 0x86B7, 0xD77E, 0x86B8, 0xD7A9, 0x86B9, 0xD7A7, 0x86BA, 0xD7A4, 0x86BB, 0xD7AC, 0x86BC, 0xD7AD, 0x86BD, 0xD7AF, + 0x86BE, 0xD7B0, 0x86BF, 0xD77D, 0x86C0, 0xB345, 0x86C1, 0xD7A2, 0x86C2, 0xD7A1, 0x86C3, 0xD7AE, 0x86C4, 0xB347, 0x86C5, 0xD7A3, + 0x86C6, 0xB349, 0x86C7, 0xB344, 0x86C8, 0xD7A6, 0x86C9, 0xB34D, 0x86CB, 0xB34A, 0x86CC, 0xD7AA, 0x86D0, 0xB5F1, 0x86D1, 0xDBBF, + 0x86D3, 0xDBB4, 0x86D4, 0xB5EE, 0x86D6, 0xDFE7, 0x86D7, 0xDBBD, 0x86D8, 0xDBB1, 0x86D9, 0xB5EC, 0x86DA, 0xDBB6, 0x86DB, 0xB5EF, + 0x86DC, 0xDBBA, 0x86DD, 0xDBB8, 0x86DE, 0xB5F2, 0x86DF, 0xB5EB, 0x86E2, 0xDBB2, 0x86E3, 0xDBB5, 0x86E4, 0xB5F0, 0x86E6, 0xDBB3, + 0x86E8, 0xDBBE, 0x86E9, 0xDBBC, 0x86EA, 0xDBB7, 0x86EB, 0xDBB9, 0x86EC, 0xDBBB, 0x86ED, 0xB5ED, 0x86F5, 0xDFE8, 0x86F6, 0xDFEE, + 0x86F7, 0xDFE4, 0x86F8, 0xDFEA, 0x86F9, 0xB8BA, 0x86FA, 0xDFE6, 0x86FB, 0xB8C0, 0x86FE, 0xB8BF, 0x8700, 0xB8BE, 0x8701, 0xDFED, + 0x8702, 0xB8C1, 0x8703, 0xB8C2, 0x8704, 0xDFE3, 0x8705, 0xDFF0, 0x8706, 0xB8C3, 0x8707, 0xB8BD, 0x8708, 0xB8BC, 0x8709, 0xDFEC, + 0x870A, 0xB8C4, 0x870B, 0xDFE2, 0x870C, 0xDFE5, 0x870D, 0xDFEF, 0x870E, 0xDFEB, 0x8711, 0xE3F4, 0x8712, 0xE3E9, 0x8713, 0xB8BB, + 0x8718, 0xBB6A, 0x8719, 0xE3DD, 0x871A, 0xE3F2, 0x871B, 0xE3DE, 0x871C, 0xBB65, 0x871E, 0xE3DB, 0x8720, 0xE3E4, 0x8721, 0xE3DC, + 0x8722, 0xBB67, 0x8723, 0xE3D6, 0x8724, 0xE3F1, 0x8725, 0xBB68, 0x8726, 0xE3EE, 0x8727, 0xE3EF, 0x8728, 0xE3D7, 0x8729, 0xBB6D, + 0x872A, 0xE3E6, 0x872C, 0xE3E0, 0x872D, 0xE3E7, 0x872E, 0xE3DA, 0x8730, 0xE3F3, 0x8731, 0xE3EB, 0x8732, 0xE3E5, 0x8733, 0xE3D5, + 0x8734, 0xBB69, 0x8735, 0xE3EC, 0x8737, 0xBB6C, 0x8738, 0xE3F0, 0x873A, 0xE3EA, 0x873B, 0xBB66, 0x873C, 0xE3E8, 0x873E, 0xE3E2, + 0x873F, 0xBB64, 0x8740, 0xE3D9, 0x8741, 0xE3E1, 0x8742, 0xE3ED, 0x8743, 0xE3DF, 0x8746, 0xE3E3, 0x874C, 0xBDC1, 0x874D, 0xDFE9, + 0x874E, 0xE7B2, 0x874F, 0xE7BB, 0x8750, 0xE7B1, 0x8751, 0xE7AD, 0x8752, 0xE7AA, 0x8753, 0xBDC2, 0x8754, 0xE7A8, 0x8755, 0xBB6B, + 0x8756, 0xE7A1, 0x8757, 0xBDC0, 0x8758, 0xE7A7, 0x8759, 0xBDBF, 0x875A, 0xE7AC, 0x875B, 0xE7A9, 0x875C, 0xE7B9, 0x875D, 0xE7B4, + 0x875E, 0xE7AE, 0x875F, 0xE7B3, 0x8760, 0xBDBB, 0x8761, 0xE7AB, 0x8762, 0xE7BE, 0x8763, 0xE7A2, 0x8764, 0xE7A3, 0x8765, 0xE7BA, + 0x8766, 0xBDBC, 0x8767, 0xE7BF, 0x8768, 0xBDBE, 0x8769, 0xE7C0, 0x876A, 0xE7B0, 0x876B, 0xE3D8, 0x876C, 0xE7B6, 0x876D, 0xE7AF, + 0x876E, 0xE7B8, 0x876F, 0xE7B5, 0x8773, 0xE7A6, 0x8774, 0xBDB9, 0x8775, 0xE7BD, 0x8776, 0xBDBA, 0x8777, 0xE7A4, 0x8778, 0xBDBD, + 0x8779, 0xEB64, 0x877A, 0xE7B7, 0x877B, 0xE7BC, 0x8781, 0xEB61, 0x8782, 0xBDB8, 0x8783, 0xBFC0, 0x8784, 0xEB6B, 0x8785, 0xEB67, + 0x8787, 0xEB65, 0x8788, 0xEB60, 0x8789, 0xEB6F, 0x878D, 0xBFC4, 0x878F, 0xEB5C, 0x8790, 0xEB68, 0x8791, 0xEB69, 0x8792, 0xEB5F, + 0x8793, 0xEB5E, 0x8794, 0xEB6C, 0x8796, 0xEB62, 0x8797, 0xEB5D, 0x8798, 0xEB63, 0x879A, 0xEB6E, 0x879B, 0xEB5B, 0x879C, 0xEB6D, + 0x879D, 0xEB6A, 0x879E, 0xBFC2, 0x879F, 0xBFC1, 0x87A2, 0xBFC3, 0x87A3, 0xEB66, 0x87A4, 0xF0CB, 0x87AA, 0xEE59, 0x87AB, 0xC1B1, + 0x87AC, 0xEE5D, 0x87AD, 0xEE5A, 0x87AE, 0xEE61, 0x87AF, 0xEE67, 0x87B0, 0xEE5C, 0x87B2, 0xEE70, 0x87B3, 0xC1AE, 0x87B4, 0xEE6A, + 0x87B5, 0xEE5F, 0x87B6, 0xEE6B, 0x87B7, 0xEE66, 0x87B8, 0xEE6D, 0x87B9, 0xEE5E, 0x87BA, 0xC1B3, 0x87BB, 0xC1B2, 0x87BC, 0xEE60, + 0x87BD, 0xEE6E, 0x87BE, 0xEE58, 0x87BF, 0xEE6C, 0x87C0, 0xC1AC, 0x87C2, 0xEE64, 0x87C3, 0xEE63, 0x87C4, 0xEE68, 0x87C5, 0xEE5B, + 0x87C6, 0xC1B0, 0x87C8, 0xC1B4, 0x87C9, 0xEE62, 0x87CA, 0xEE69, 0x87CB, 0xC1B5, 0x87CC, 0xEE65, 0x87D1, 0xC1AD, 0x87D2, 0xC1AF, + 0x87D3, 0xF0C7, 0x87D4, 0xF0C5, 0x87D7, 0xF0CC, 0x87D8, 0xF0C9, 0x87D9, 0xF0CD, 0x87DB, 0xF0BE, 0x87DC, 0xF0C6, 0x87DD, 0xF0D1, + 0x87DE, 0xEE6F, 0x87DF, 0xF0C2, 0x87E0, 0xC2CF, 0x87E1, 0xE7A5, 0x87E2, 0xF0BD, 0x87E3, 0xF0CA, 0x87E4, 0xF0C4, 0x87E5, 0xF0C1, + 0x87E6, 0xF0BC, 0x87E7, 0xF0BB, 0x87E8, 0xF0D0, 0x87EA, 0xF0C0, 0x87EB, 0xF0BF, 0x87EC, 0xC2CD, 0x87ED, 0xF0C8, 0x87EF, 0xC2CC, + 0x87F2, 0xC2CE, 0x87F3, 0xF0C3, 0x87F4, 0xF0CF, 0x87F6, 0xF2DE, 0x87F7, 0xF2DF, 0x87F9, 0xC3C9, 0x87FA, 0xF2DC, 0x87FB, 0xC3C6, + 0x87FC, 0xF2E4, 0x87FE, 0xC3CA, 0x87FF, 0xF2E6, 0x8800, 0xF2DB, 0x8801, 0xF0CE, 0x8802, 0xF2E8, 0x8803, 0xF2DD, 0x8805, 0xC3C7, + 0x8806, 0xF2E3, 0x8808, 0xF2E5, 0x8809, 0xF2E0, 0x880A, 0xF2E7, 0x880B, 0xF2E2, 0x880C, 0xF2E1, 0x880D, 0xC3C8, 0x8810, 0xF4C5, + 0x8811, 0xF4C6, 0x8813, 0xF4C8, 0x8814, 0xC4AE, 0x8815, 0xC4AF, 0x8816, 0xF4C9, 0x8817, 0xF4C7, 0x8819, 0xF4C4, 0x881B, 0xF642, + 0x881C, 0xF645, 0x881D, 0xF641, 0x881F, 0xC4FA, 0x8820, 0xF643, 0x8821, 0xC4F9, 0x8822, 0xC4F8, 0x8823, 0xC4F7, 0x8824, 0xF644, + 0x8825, 0xF751, 0x8826, 0xF74F, 0x8828, 0xF74E, 0x8829, 0xF640, 0x882A, 0xF750, 0x882B, 0xF646, 0x882C, 0xF74D, 0x882E, 0xF7F9, + 0x882F, 0xF7D7, 0x8830, 0xF7F7, 0x8831, 0xC5DB, 0x8832, 0xF7F8, 0x8833, 0xF7FA, 0x8835, 0xF8BF, 0x8836, 0xC5FA, 0x8837, 0xF8BE, + 0x8838, 0xF8BD, 0x8839, 0xC5FB, 0x883B, 0xC65A, 0x883C, 0xF96E, 0x883D, 0xF9A7, 0x883E, 0xF9A6, 0x883F, 0xF9A8, 0x8840, 0xA6E5, + 0x8841, 0xD0AA, 0x8843, 0xD3CF, 0x8844, 0xD3D0, 0x8848, 0xDBC0, 0x884A, 0xF647, 0x884B, 0xF8C0, 0x884C, 0xA6E6, 0x884D, 0xAD6C, + 0x884E, 0xD0AB, 0x8852, 0xD7B1, 0x8853, 0xB34E, 0x8855, 0xDBC2, 0x8856, 0xDBC1, 0x8857, 0xB5F3, 0x8859, 0xB8C5, 0x885A, 0xE7C1, + 0x885B, 0xBDC3, 0x885D, 0xBDC4, 0x8861, 0xBFC5, 0x8862, 0xC5FC, 0x8863, 0xA6E7, 0x8867, 0xD0AC, 0x8868, 0xAAED, 0x8869, 0xD0AE, + 0x886A, 0xD0AD, 0x886B, 0xAD6D, 0x886D, 0xD3D1, 0x886F, 0xD3D8, 0x8870, 0xB049, 0x8871, 0xD3D6, 0x8872, 0xD3D4, 0x8874, 0xD3DB, + 0x8875, 0xD3D2, 0x8876, 0xD3D3, 0x8877, 0xB04A, 0x8879, 0xB04E, 0x887C, 0xD3DC, 0x887D, 0xB04D, 0x887E, 0xD3DA, 0x887F, 0xD3D7, + 0x8880, 0xD3D5, 0x8881, 0xB04B, 0x8882, 0xB04C, 0x8883, 0xD3D9, 0x8888, 0xB350, 0x8889, 0xD7B2, 0x888B, 0xB355, 0x888C, 0xD7C2, + 0x888D, 0xB354, 0x888E, 0xD7C4, 0x8891, 0xD7B8, 0x8892, 0xB352, 0x8893, 0xD7C3, 0x8895, 0xD7B3, 0x8896, 0xB353, 0x8897, 0xD7BF, + 0x8898, 0xD7BB, 0x8899, 0xD7BD, 0x889A, 0xD7B7, 0x889B, 0xD7BE, 0x889E, 0xB34F, 0x889F, 0xD7BA, 0x88A1, 0xD7B9, 0x88A2, 0xD7B5, + 0x88A4, 0xD7C0, 0x88A7, 0xD7BC, 0x88A8, 0xD7B4, 0x88AA, 0xD7B6, 0x88AB, 0xB351, 0x88AC, 0xD7C1, 0x88B1, 0xB5F6, 0x88B2, 0xDBCD, + 0x88B6, 0xDBC9, 0x88B7, 0xDBCB, 0x88B8, 0xDBC6, 0x88B9, 0xDBC5, 0x88BA, 0xDBC3, 0x88BC, 0xDBCA, 0x88BD, 0xDBCC, 0x88BE, 0xDBC8, + 0x88C0, 0xDBC7, 0x88C1, 0xB5F4, 0x88C2, 0xB5F5, 0x88C9, 0xDBCF, 0x88CA, 0xB8CD, 0x88CB, 0xDFF2, 0x88CC, 0xDFF8, 0x88CD, 0xDFF3, + 0x88CE, 0xDFF4, 0x88CF, 0xF9D8, 0x88D0, 0xDFF9, 0x88D2, 0xB8CF, 0x88D4, 0xB8C7, 0x88D5, 0xB8CE, 0x88D6, 0xDFF1, 0x88D7, 0xDBC4, + 0x88D8, 0xB8CA, 0x88D9, 0xB8C8, 0x88DA, 0xDFF7, 0x88DB, 0xDFF6, 0x88DC, 0xB8C9, 0x88DD, 0xB8CB, 0x88DE, 0xDFF5, 0x88DF, 0xB8C6, + 0x88E1, 0xB8CC, 0x88E7, 0xE3F6, 0x88E8, 0xBB74, 0x88EB, 0xE442, 0x88EC, 0xE441, 0x88EE, 0xE3FB, 0x88EF, 0xBB76, 0x88F0, 0xE440, + 0x88F1, 0xE3F7, 0x88F2, 0xE3F8, 0x88F3, 0xBB6E, 0x88F4, 0xBB70, 0x88F6, 0xE3FD, 0x88F7, 0xE3F5, 0x88F8, 0xBB72, 0x88F9, 0xBB71, + 0x88FA, 0xE3F9, 0x88FB, 0xE3FE, 0x88FC, 0xE3FC, 0x88FD, 0xBB73, 0x88FE, 0xE3FA, 0x8901, 0xDBCE, 0x8902, 0xBB6F, 0x8905, 0xE7C2, + 0x8906, 0xE7C9, 0x8907, 0xBDC6, 0x8909, 0xE7CD, 0x890A, 0xBDCA, 0x890B, 0xE7C5, 0x890C, 0xE7C3, 0x890E, 0xE7CC, 0x8910, 0xBDC5, + 0x8911, 0xE7CB, 0x8912, 0xBDC7, 0x8913, 0xBDC8, 0x8914, 0xE7C4, 0x8915, 0xBDC9, 0x8916, 0xE7CA, 0x8917, 0xE7C6, 0x8918, 0xE7C7, + 0x8919, 0xE7C8, 0x891A, 0xBB75, 0x891E, 0xEB70, 0x891F, 0xEB7C, 0x8921, 0xBFCA, 0x8922, 0xEB77, 0x8923, 0xEB79, 0x8925, 0xBFC8, + 0x8926, 0xEB71, 0x8927, 0xEB75, 0x8929, 0xEB78, 0x892A, 0xBFC6, 0x892B, 0xBFC9, 0x892C, 0xEB7B, 0x892D, 0xEB73, 0x892E, 0xEB74, + 0x892F, 0xEB7A, 0x8930, 0xEB72, 0x8931, 0xEB76, 0x8932, 0xBFC7, 0x8933, 0xEE72, 0x8935, 0xEE71, 0x8936, 0xC1B7, 0x8937, 0xEE77, + 0x8938, 0xC1B9, 0x893B, 0xC1B6, 0x893C, 0xEE73, 0x893D, 0xC1BA, 0x893E, 0xEE74, 0x8941, 0xEE75, 0x8942, 0xEE78, 0x8944, 0xC1B8, + 0x8946, 0xF0D6, 0x8949, 0xF0D9, 0x894B, 0xF0D3, 0x894C, 0xF0D5, 0x894F, 0xF0D4, 0x8950, 0xF0D7, 0x8951, 0xF0D8, 0x8952, 0xEE76, + 0x8953, 0xF0D2, 0x8956, 0xC3CD, 0x8957, 0xF2EC, 0x8958, 0xF2EF, 0x8959, 0xF2F1, 0x895A, 0xF2EA, 0x895B, 0xF2EB, 0x895C, 0xF2EE, + 0x895D, 0xF2F0, 0x895E, 0xC3CE, 0x895F, 0xC3CC, 0x8960, 0xC3CB, 0x8961, 0xF2ED, 0x8962, 0xF2E9, 0x8963, 0xF4CA, 0x8964, 0xC4B0, + 0x8966, 0xF4CB, 0x8969, 0xF649, 0x896A, 0xC4FB, 0x896B, 0xF64B, 0x896C, 0xC4FC, 0x896D, 0xF648, 0x896E, 0xF64A, 0x896F, 0xC5A8, + 0x8971, 0xF752, 0x8972, 0xC5A7, 0x8973, 0xF7FD, 0x8974, 0xF7FC, 0x8976, 0xF7FB, 0x8979, 0xF948, 0x897A, 0xF949, 0x897B, 0xF94B, + 0x897C, 0xF94A, 0x897E, 0xCA50, 0x897F, 0xA6E8, 0x8981, 0xAD6E, 0x8982, 0xD7C5, 0x8983, 0xB5F7, 0x8985, 0xDFFA, 0x8986, 0xC2D0, + 0x8988, 0xF2F2, 0x898B, 0xA8A3, 0x898F, 0xB357, 0x8993, 0xB356, 0x8995, 0xDBD0, 0x8996, 0xB5F8, 0x8997, 0xDBD2, 0x8998, 0xDBD1, + 0x899B, 0xDFFB, 0x899C, 0xB8D0, 0x899D, 0xE443, 0x899E, 0xE446, 0x899F, 0xE445, 0x89A1, 0xE444, 0x89A2, 0xE7CE, 0x89A3, 0xE7D0, + 0x89A4, 0xE7CF, 0x89A6, 0xBFCC, 0x89AA, 0xBFCB, 0x89AC, 0xC1BB, 0x89AD, 0xEE79, 0x89AE, 0xEE7B, 0x89AF, 0xEE7A, 0x89B2, 0xC2D1, + 0x89B6, 0xF2F4, 0x89B7, 0xF2F3, 0x89B9, 0xF4CC, 0x89BA, 0xC4B1, 0x89BD, 0xC4FD, 0x89BE, 0xF754, 0x89BF, 0xF753, 0x89C0, 0xC65B, + 0x89D2, 0xA8A4, 0x89D3, 0xD0AF, 0x89D4, 0xAD6F, 0x89D5, 0xD7C8, 0x89D6, 0xD7C6, 0x89D9, 0xD7C7, 0x89DA, 0xDBD4, 0x89DB, 0xDBD5, + 0x89DC, 0xE043, 0x89DD, 0xDBD3, 0x89DF, 0xDFFC, 0x89E0, 0xE041, 0x89E1, 0xE040, 0x89E2, 0xE042, 0x89E3, 0xB8D1, 0x89E4, 0xDFFE, + 0x89E5, 0xDFFD, 0x89E6, 0xE044, 0x89E8, 0xE449, 0x89E9, 0xE447, 0x89EB, 0xE448, 0x89EC, 0xE7D3, 0x89ED, 0xE7D1, 0x89F0, 0xE7D2, + 0x89F1, 0xEB7D, 0x89F2, 0xEE7C, 0x89F3, 0xEE7D, 0x89F4, 0xC2D2, 0x89F6, 0xF2F5, 0x89F7, 0xF4CD, 0x89F8, 0xC4B2, 0x89FA, 0xF64C, + 0x89FB, 0xF755, 0x89FC, 0xC5A9, 0x89FE, 0xF7FE, 0x89FF, 0xF94C, 0x8A00, 0xA8A5, 0x8A02, 0xAD71, 0x8A03, 0xAD72, 0x8A04, 0xD0B0, + 0x8A07, 0xD0B1, 0x8A08, 0xAD70, 0x8A0A, 0xB054, 0x8A0C, 0xB052, 0x8A0E, 0xB051, 0x8A0F, 0xB058, 0x8A10, 0xB050, 0x8A11, 0xB059, + 0x8A12, 0xD3DD, 0x8A13, 0xB056, 0x8A15, 0xB053, 0x8A16, 0xB057, 0x8A17, 0xB055, 0x8A18, 0xB04F, 0x8A1B, 0xB35F, 0x8A1D, 0xB359, + 0x8A1E, 0xD7CC, 0x8A1F, 0xB35E, 0x8A22, 0xB360, 0x8A23, 0xB35A, 0x8A25, 0xB35B, 0x8A27, 0xD7CA, 0x8A2A, 0xB358, 0x8A2C, 0xD7CB, + 0x8A2D, 0xB35D, 0x8A30, 0xD7C9, 0x8A31, 0xB35C, 0x8A34, 0xB644, 0x8A36, 0xB646, 0x8A39, 0xDBD8, 0x8A3A, 0xB645, 0x8A3B, 0xB5F9, + 0x8A3C, 0xB5FD, 0x8A3E, 0xB8E4, 0x8A3F, 0xE049, 0x8A40, 0xDBDA, 0x8A41, 0xB5FE, 0x8A44, 0xDBDD, 0x8A45, 0xDBDE, 0x8A46, 0xB643, + 0x8A48, 0xDBE0, 0x8A4A, 0xDBE2, 0x8A4C, 0xDBE3, 0x8A4D, 0xDBD7, 0x8A4E, 0xDBD6, 0x8A4F, 0xDBE4, 0x8A50, 0xB642, 0x8A51, 0xDBE1, + 0x8A52, 0xDBDF, 0x8A54, 0xB640, 0x8A55, 0xB5FB, 0x8A56, 0xB647, 0x8A57, 0xDBDB, 0x8A58, 0xDBDC, 0x8A59, 0xDBD9, 0x8A5B, 0xB641, + 0x8A5E, 0xB5FC, 0x8A60, 0xB5FA, 0x8A61, 0xE048, 0x8A62, 0xB8DF, 0x8A63, 0xB8DA, 0x8A66, 0xB8D5, 0x8A68, 0xB8E5, 0x8A69, 0xB8D6, + 0x8A6B, 0xB8D2, 0x8A6C, 0xB8E1, 0x8A6D, 0xB8DE, 0x8A6E, 0xB8E0, 0x8A70, 0xB8D7, 0x8A71, 0xB8DC, 0x8A72, 0xB8D3, 0x8A73, 0xB8D4, + 0x8A74, 0xE050, 0x8A75, 0xE04D, 0x8A76, 0xE045, 0x8A77, 0xE04A, 0x8A79, 0xB8E2, 0x8A7A, 0xE051, 0x8A7B, 0xB8E3, 0x8A7C, 0xB8D9, + 0x8A7F, 0xE047, 0x8A81, 0xE04F, 0x8A82, 0xE04B, 0x8A83, 0xE04E, 0x8A84, 0xE04C, 0x8A85, 0xB8DD, 0x8A86, 0xE046, 0x8A87, 0xB8D8, + 0x8A8B, 0xE44C, 0x8A8C, 0xBB78, 0x8A8D, 0xBB7B, 0x8A8F, 0xE44E, 0x8A91, 0xBBA5, 0x8A92, 0xE44D, 0x8A93, 0xBB7D, 0x8A95, 0xBDCF, + 0x8A96, 0xE44F, 0x8A98, 0xBBA4, 0x8A99, 0xE44B, 0x8A9A, 0xBBA6, 0x8A9E, 0xBB79, 0x8AA0, 0xB8DB, 0x8AA1, 0xBB7C, 0x8AA3, 0xBB7A, + 0x8AA4, 0xBB7E, 0x8AA5, 0xBBA2, 0x8AA6, 0xBB77, 0x8AA7, 0xBBA7, 0x8AA8, 0xBBA3, 0x8AAA, 0xBBA1, 0x8AAB, 0xE44A, 0x8AB0, 0xBDD6, + 0x8AB2, 0xBDD2, 0x8AB6, 0xBDD9, 0x8AB8, 0xE7D6, 0x8AB9, 0xBDDA, 0x8ABA, 0xE7E2, 0x8ABB, 0xE7DB, 0x8ABC, 0xBDCB, 0x8ABD, 0xE7E3, + 0x8ABE, 0xE7DD, 0x8ABF, 0xBDD5, 0x8AC0, 0xE7DE, 0x8AC2, 0xBDD4, 0x8AC3, 0xE7E1, 0x8AC4, 0xBDCE, 0x8AC5, 0xE7DF, 0x8AC6, 0xE7D5, + 0x8AC7, 0xBDCD, 0x8AC8, 0xEBAA, 0x8AC9, 0xBDD3, 0x8ACB, 0xBDD0, 0x8ACD, 0xBDD8, 0x8ACF, 0xE7D4, 0x8AD1, 0xE7D8, 0x8AD2, 0xBDCC, + 0x8AD3, 0xE7D7, 0x8AD4, 0xE7D9, 0x8AD5, 0xE7DA, 0x8AD6, 0xBDD7, 0x8AD7, 0xE7DC, 0x8AD8, 0xE7E0, 0x8AD9, 0xE7E4, 0x8ADB, 0xBDDB, + 0x8ADC, 0xBFD2, 0x8ADD, 0xEBA5, 0x8ADE, 0xEBAB, 0x8ADF, 0xEBA8, 0x8AE0, 0xEB7E, 0x8AE1, 0xEBAC, 0x8AE2, 0xEBA1, 0x8AE4, 0xEBA7, + 0x8AE6, 0xBFCD, 0x8AE7, 0xBFD3, 0x8AE8, 0xEBAD, 0x8AEB, 0xBFCF, 0x8AED, 0xBFD9, 0x8AEE, 0xBFD4, 0x8AEF, 0xEBAF, 0x8AF0, 0xEBA9, + 0x8AF1, 0xBFD0, 0x8AF2, 0xEBA2, 0x8AF3, 0xBFDA, 0x8AF4, 0xEBA3, 0x8AF5, 0xEBA4, 0x8AF6, 0xBFDB, 0x8AF7, 0xBFD8, 0x8AF8, 0xBDD1, + 0x8AFA, 0xBFCE, 0x8AFB, 0xEBB0, 0x8AFC, 0xBFDC, 0x8AFE, 0xBFD5, 0x8AFF, 0xEBAE, 0x8B00, 0xBFD1, 0x8B01, 0xBFD6, 0x8B02, 0xBFD7, + 0x8B04, 0xC1C3, 0x8B05, 0xEEA4, 0x8B06, 0xEEAD, 0x8B07, 0xEEAA, 0x8B08, 0xEEAC, 0x8B0A, 0xC1C0, 0x8B0B, 0xEEA5, 0x8B0D, 0xEEAB, + 0x8B0E, 0xC1BC, 0x8B0F, 0xEEA7, 0x8B10, 0xC1C4, 0x8B11, 0xEEA3, 0x8B12, 0xEEA8, 0x8B13, 0xEEAF, 0x8B14, 0xEBA6, 0x8B15, 0xEEA9, + 0x8B16, 0xEEA2, 0x8B17, 0xC1BD, 0x8B18, 0xEEA1, 0x8B19, 0xC1BE, 0x8B1A, 0xEEB0, 0x8B1B, 0xC1BF, 0x8B1C, 0xEEAE, 0x8B1D, 0xC1C2, + 0x8B1E, 0xEE7E, 0x8B20, 0xC1C1, 0x8B22, 0xEEA6, 0x8B23, 0xF0DC, 0x8B24, 0xF0EA, 0x8B25, 0xF0E5, 0x8B26, 0xF0E7, 0x8B27, 0xF0DB, + 0x8B28, 0xC2D3, 0x8B2A, 0xF0DA, 0x8B2B, 0xC2D6, 0x8B2C, 0xC2D5, 0x8B2E, 0xF0E9, 0x8B2F, 0xF0E1, 0x8B30, 0xF0DE, 0x8B31, 0xF0E4, + 0x8B33, 0xF0DD, 0x8B35, 0xF0DF, 0x8B36, 0xF0E8, 0x8B37, 0xF0E6, 0x8B39, 0xC2D4, 0x8B3A, 0xF0ED, 0x8B3B, 0xF0EB, 0x8B3C, 0xF0E2, + 0x8B3D, 0xF0EC, 0x8B3E, 0xF0E3, 0x8B40, 0xF2F9, 0x8B41, 0xC3CF, 0x8B42, 0xF341, 0x8B45, 0xF64F, 0x8B46, 0xC3D6, 0x8B47, 0xF0E0, + 0x8B48, 0xF2F7, 0x8B49, 0xC3D2, 0x8B4A, 0xF2F8, 0x8B4B, 0xF2FD, 0x8B4E, 0xC3D4, 0x8B4F, 0xC3D5, 0x8B50, 0xF2F6, 0x8B51, 0xF340, + 0x8B52, 0xF342, 0x8B53, 0xF2FA, 0x8B54, 0xF2FC, 0x8B55, 0xF2FE, 0x8B56, 0xF2FB, 0x8B57, 0xF343, 0x8B58, 0xC3D1, 0x8B59, 0xC3D7, + 0x8B5A, 0xC3D3, 0x8B5C, 0xC3D0, 0x8B5D, 0xF4D0, 0x8B5F, 0xC4B7, 0x8B60, 0xF4CE, 0x8B63, 0xF4D2, 0x8B65, 0xF4D3, 0x8B66, 0xC4B5, + 0x8B67, 0xF4D4, 0x8B68, 0xF4D1, 0x8B6A, 0xF4CF, 0x8B6B, 0xC4B8, 0x8B6C, 0xC4B4, 0x8B6D, 0xF4D5, 0x8B6F, 0xC4B6, 0x8B70, 0xC4B3, + 0x8B74, 0xC4FE, 0x8B77, 0xC540, 0x8B78, 0xF64E, 0x8B79, 0xF64D, 0x8B7A, 0xF650, 0x8B7B, 0xF651, 0x8B7D, 0xC541, 0x8B7E, 0xF756, + 0x8B7F, 0xF75B, 0x8B80, 0xC5AA, 0x8B82, 0xF758, 0x8B84, 0xF757, 0x8B85, 0xF75A, 0x8B86, 0xF759, 0x8B88, 0xF843, 0x8B8A, 0xC5DC, + 0x8B8B, 0xF842, 0x8B8C, 0xF840, 0x8B8E, 0xF841, 0x8B92, 0xC5FE, 0x8B93, 0xC5FD, 0x8B94, 0xF8C1, 0x8B95, 0xF8C2, 0x8B96, 0xC640, + 0x8B98, 0xF94D, 0x8B99, 0xF94E, 0x8B9A, 0xC667, 0x8B9C, 0xC66D, 0x8B9E, 0xF9A9, 0x8B9F, 0xF9C8, 0x8C37, 0xA8A6, 0x8C39, 0xD7CD, + 0x8C3B, 0xD7CE, 0x8C3C, 0xE052, 0x8C3D, 0xE450, 0x8C3E, 0xE7E5, 0x8C3F, 0xC1C6, 0x8C41, 0xC1C5, 0x8C42, 0xF0EE, 0x8C43, 0xF344, + 0x8C45, 0xF844, 0x8C46, 0xA8A7, 0x8C47, 0xD3DE, 0x8C48, 0xB05A, 0x8C49, 0xB361, 0x8C4A, 0xE054, 0x8C4B, 0xE053, 0x8C4C, 0xBDDC, + 0x8C4D, 0xE7E6, 0x8C4E, 0xBDDD, 0x8C4F, 0xEEB1, 0x8C50, 0xC2D7, 0x8C54, 0xC676, 0x8C55, 0xA8A8, 0x8C56, 0xCDCB, 0x8C57, 0xD3DF, + 0x8C5A, 0xB362, 0x8C5C, 0xD7CF, 0x8C5D, 0xD7D0, 0x8C5F, 0xDBE5, 0x8C61, 0xB648, 0x8C62, 0xB8E6, 0x8C64, 0xE056, 0x8C65, 0xE055, + 0x8C66, 0xE057, 0x8C68, 0xE451, 0x8C69, 0xE452, 0x8C6A, 0xBBA8, 0x8C6B, 0xBFDD, 0x8C6C, 0xBDDE, 0x8C6D, 0xBFDE, 0x8C6F, 0xEEB5, + 0x8C70, 0xEEB2, 0x8C71, 0xEEB4, 0x8C72, 0xEEB3, 0x8C73, 0xC1C7, 0x8C75, 0xF0EF, 0x8C76, 0xF346, 0x8C77, 0xF345, 0x8C78, 0xCBA4, + 0x8C79, 0xB05C, 0x8C7A, 0xB05B, 0x8C7B, 0xD3E0, 0x8C7D, 0xD7D1, 0x8C80, 0xDBE7, 0x8C81, 0xDBE6, 0x8C82, 0xB649, 0x8C84, 0xE059, + 0x8C85, 0xE05A, 0x8C86, 0xE058, 0x8C89, 0xB8E8, 0x8C8A, 0xB8E7, 0x8C8C, 0xBBAA, 0x8C8D, 0xBBA9, 0x8C8F, 0xE7E7, 0x8C90, 0xEBB3, + 0x8C91, 0xEBB1, 0x8C92, 0xEBB2, 0x8C93, 0xBFDF, 0x8C94, 0xEEB7, 0x8C95, 0xEEB6, 0x8C97, 0xF0F2, 0x8C98, 0xF0F1, 0x8C99, 0xF0F0, + 0x8C9A, 0xF347, 0x8C9C, 0xF9AA, 0x8C9D, 0xA8A9, 0x8C9E, 0xAD73, 0x8CA0, 0xAD74, 0x8CA1, 0xB05D, 0x8CA2, 0xB05E, 0x8CA3, 0xD3E2, + 0x8CA4, 0xD3E1, 0x8CA5, 0xD7D2, 0x8CA7, 0xB368, 0x8CA8, 0xB366, 0x8CA9, 0xB363, 0x8CAA, 0xB367, 0x8CAB, 0xB365, 0x8CAC, 0xB364, + 0x8CAF, 0xB64A, 0x8CB0, 0xDBEA, 0x8CB2, 0xB8ED, 0x8CB3, 0xB64C, 0x8CB4, 0xB651, 0x8CB5, 0xDBEC, 0x8CB6, 0xB653, 0x8CB7, 0xB652, + 0x8CB8, 0xB655, 0x8CB9, 0xDBEB, 0x8CBA, 0xDBE8, 0x8CBB, 0xB64F, 0x8CBC, 0xB64B, 0x8CBD, 0xB64D, 0x8CBE, 0xDBE9, 0x8CBF, 0xB654, + 0x8CC0, 0xB650, 0x8CC1, 0xB64E, 0x8CC2, 0xB8EF, 0x8CC3, 0xB8EE, 0x8CC4, 0xB8EC, 0x8CC5, 0xB8F0, 0x8CC7, 0xB8EA, 0x8CC8, 0xB8EB, + 0x8CCA, 0xB8E9, 0x8CCC, 0xE05B, 0x8CCF, 0xE454, 0x8CD1, 0xBBAC, 0x8CD2, 0xBBAD, 0x8CD3, 0xBBAB, 0x8CD5, 0xE453, 0x8CD7, 0xE455, + 0x8CD9, 0xE7EA, 0x8CDA, 0xE7EC, 0x8CDC, 0xBDE7, 0x8CDD, 0xE7ED, 0x8CDE, 0xBDE0, 0x8CDF, 0xE7E9, 0x8CE0, 0xBDDF, 0x8CE1, 0xBDE9, + 0x8CE2, 0xBDE5, 0x8CE3, 0xBDE6, 0x8CE4, 0xBDE2, 0x8CE5, 0xE7E8, 0x8CE6, 0xBDE1, 0x8CE7, 0xE7EE, 0x8CE8, 0xE7EB, 0x8CEA, 0xBDE8, + 0x8CEC, 0xBDE3, 0x8CED, 0xBDE4, 0x8CEE, 0xEBB5, 0x8CF0, 0xEBB7, 0x8CF1, 0xEBB6, 0x8CF3, 0xEBB8, 0x8CF4, 0xBFE0, 0x8CF5, 0xEBB4, + 0x8CF8, 0xC1CB, 0x8CF9, 0xEEB8, 0x8CFA, 0xC1C8, 0x8CFB, 0xC1CC, 0x8CFC, 0xC1CA, 0x8CFD, 0xC1C9, 0x8CFE, 0xF0F3, 0x8D00, 0xF0F6, + 0x8D02, 0xF0F5, 0x8D04, 0xF0F4, 0x8D05, 0xC2D8, 0x8D06, 0xF348, 0x8D07, 0xF349, 0x8D08, 0xC3D8, 0x8D09, 0xF34A, 0x8D0A, 0xC3D9, + 0x8D0D, 0xC4BA, 0x8D0F, 0xC4B9, 0x8D10, 0xF652, 0x8D13, 0xC542, 0x8D14, 0xF653, 0x8D15, 0xF75C, 0x8D16, 0xC5AB, 0x8D17, 0xC5AC, + 0x8D19, 0xF845, 0x8D1B, 0xC642, 0x8D64, 0xA8AA, 0x8D66, 0xB36A, 0x8D67, 0xB369, 0x8D68, 0xE05C, 0x8D69, 0xE05D, 0x8D6B, 0xBBAE, + 0x8D6C, 0xEBB9, 0x8D6D, 0xBDEA, 0x8D6E, 0xEBBA, 0x8D6F, 0xEEB9, 0x8D70, 0xA8AB, 0x8D72, 0xD0B2, 0x8D73, 0xAD76, 0x8D74, 0xAD75, + 0x8D76, 0xD3E3, 0x8D77, 0xB05F, 0x8D78, 0xD3E4, 0x8D79, 0xD7D5, 0x8D7B, 0xD7D4, 0x8D7D, 0xD7D3, 0x8D80, 0xDBEE, 0x8D81, 0xB658, + 0x8D84, 0xDBED, 0x8D85, 0xB657, 0x8D89, 0xDBEF, 0x8D8A, 0xB656, 0x8D8C, 0xE05F, 0x8D8D, 0xE062, 0x8D8E, 0xE060, 0x8D8F, 0xE061, + 0x8D90, 0xE065, 0x8D91, 0xE05E, 0x8D92, 0xE066, 0x8D93, 0xE063, 0x8D94, 0xE064, 0x8D95, 0xBBB0, 0x8D96, 0xE456, 0x8D99, 0xBBAF, + 0x8D9B, 0xE7F2, 0x8D9C, 0xE7F0, 0x8D9F, 0xBDEB, 0x8DA0, 0xE7EF, 0x8DA1, 0xE7F1, 0x8DA3, 0xBDEC, 0x8DA5, 0xEBBB, 0x8DA7, 0xEBBC, + 0x8DA8, 0xC1CD, 0x8DAA, 0xF34C, 0x8DAB, 0xF34E, 0x8DAC, 0xF34B, 0x8DAD, 0xF34D, 0x8DAE, 0xF4D6, 0x8DAF, 0xF654, 0x8DB2, 0xF96F, + 0x8DB3, 0xA8AC, 0x8DB4, 0xAD77, 0x8DB5, 0xD3E5, 0x8DB6, 0xD3E7, 0x8DB7, 0xD3E6, 0x8DB9, 0xD7D8, 0x8DBA, 0xB36C, 0x8DBC, 0xD7D6, + 0x8DBE, 0xB36B, 0x8DBF, 0xD7D9, 0x8DC1, 0xD7DA, 0x8DC2, 0xD7D7, 0x8DC5, 0xDBFB, 0x8DC6, 0xB660, 0x8DC7, 0xDBF3, 0x8DC8, 0xDBF9, + 0x8DCB, 0xB65B, 0x8DCC, 0xB65E, 0x8DCD, 0xDBF2, 0x8DCE, 0xB659, 0x8DCF, 0xDBF6, 0x8DD0, 0xE06C, 0x8DD1, 0xB65D, 0x8DD3, 0xDBF1, + 0x8DD5, 0xDBF7, 0x8DD6, 0xDBF4, 0x8DD7, 0xDBFA, 0x8DD8, 0xDBF0, 0x8DD9, 0xDBF8, 0x8DDA, 0xB65C, 0x8DDB, 0xB65F, 0x8DDC, 0xDBF5, + 0x8DDD, 0xB65A, 0x8DDF, 0xB8F2, 0x8DE0, 0xE068, 0x8DE1, 0xB8F1, 0x8DE2, 0xE06F, 0x8DE3, 0xE06E, 0x8DE4, 0xB8F8, 0x8DE6, 0xB8F9, + 0x8DE7, 0xE070, 0x8DE8, 0xB8F3, 0x8DE9, 0xE06D, 0x8DEA, 0xB8F7, 0x8DEB, 0xE072, 0x8DEC, 0xE069, 0x8DEE, 0xE06B, 0x8DEF, 0xB8F4, + 0x8DF0, 0xE067, 0x8DF1, 0xE06A, 0x8DF2, 0xE071, 0x8DF3, 0xB8F5, 0x8DF4, 0xE073, 0x8DFA, 0xB8F6, 0x8DFC, 0xBBB1, 0x8DFD, 0xE45B, + 0x8DFE, 0xE461, 0x8DFF, 0xE459, 0x8E00, 0xE462, 0x8E02, 0xE458, 0x8E03, 0xE45D, 0x8E04, 0xE463, 0x8E05, 0xE460, 0x8E06, 0xE45F, + 0x8E07, 0xE45E, 0x8E09, 0xE457, 0x8E0A, 0xE45C, 0x8E0D, 0xE45A, 0x8E0F, 0xBDF1, 0x8E10, 0xBDEE, 0x8E11, 0xE7FB, 0x8E12, 0xE841, + 0x8E13, 0xE843, 0x8E14, 0xE840, 0x8E15, 0xE7F8, 0x8E16, 0xE7FA, 0x8E17, 0xE845, 0x8E18, 0xE842, 0x8E19, 0xE7FC, 0x8E1A, 0xE846, + 0x8E1B, 0xE7F9, 0x8E1C, 0xE844, 0x8E1D, 0xBDEF, 0x8E1E, 0xBDF5, 0x8E1F, 0xBDF3, 0x8E20, 0xE7F3, 0x8E21, 0xBDF4, 0x8E22, 0xBDF0, + 0x8E23, 0xE7F4, 0x8E24, 0xE7F6, 0x8E25, 0xE7F5, 0x8E26, 0xE7FD, 0x8E27, 0xE7FE, 0x8E29, 0xBDF2, 0x8E2B, 0xBDED, 0x8E2E, 0xE7F7, + 0x8E30, 0xEBC6, 0x8E31, 0xBFE2, 0x8E33, 0xEBBD, 0x8E34, 0xBFE3, 0x8E35, 0xBFE6, 0x8E36, 0xEBC2, 0x8E38, 0xEBBF, 0x8E39, 0xBFE5, + 0x8E3C, 0xEBC3, 0x8E3D, 0xEBC4, 0x8E3E, 0xEBBE, 0x8E3F, 0xEBC7, 0x8E40, 0xEBC0, 0x8E41, 0xEBC5, 0x8E42, 0xBFE4, 0x8E44, 0xBFE1, + 0x8E45, 0xEBC1, 0x8E47, 0xEEBF, 0x8E48, 0xC1D0, 0x8E49, 0xC1CE, 0x8E4A, 0xC1D1, 0x8E4B, 0xC1CF, 0x8E4C, 0xEEBE, 0x8E4D, 0xEEBB, + 0x8E4E, 0xEEBA, 0x8E50, 0xEEBD, 0x8E53, 0xEEBC, 0x8E54, 0xF145, 0x8E55, 0xC2DE, 0x8E56, 0xF0FB, 0x8E57, 0xF0FA, 0x8E59, 0xC2D9, + 0x8E5A, 0xF141, 0x8E5B, 0xF140, 0x8E5C, 0xF0F7, 0x8E5D, 0xF143, 0x8E5E, 0xF0FC, 0x8E5F, 0xC2DD, 0x8E60, 0xF0F9, 0x8E61, 0xF142, + 0x8E62, 0xF0F8, 0x8E63, 0xC2DA, 0x8E64, 0xC2DC, 0x8E65, 0xF0FD, 0x8E66, 0xC2DB, 0x8E67, 0xF0FE, 0x8E69, 0xF144, 0x8E6A, 0xF352, + 0x8E6C, 0xC3DE, 0x8E6D, 0xF34F, 0x8E6F, 0xF353, 0x8E72, 0xC3DB, 0x8E73, 0xF351, 0x8E74, 0xC3E0, 0x8E76, 0xC3DD, 0x8E78, 0xF350, + 0x8E7A, 0xC3DF, 0x8E7B, 0xF354, 0x8E7C, 0xC3DA, 0x8E81, 0xC4BC, 0x8E82, 0xC4BE, 0x8E84, 0xF4D9, 0x8E85, 0xC4BD, 0x8E86, 0xF4D7, + 0x8E87, 0xC3DC, 0x8E88, 0xF4D8, 0x8E89, 0xC4BB, 0x8E8A, 0xC543, 0x8E8B, 0xC545, 0x8E8C, 0xF656, 0x8E8D, 0xC544, 0x8E8E, 0xF655, + 0x8E90, 0xF761, 0x8E91, 0xC5AD, 0x8E92, 0xF760, 0x8E93, 0xC5AE, 0x8E94, 0xF75E, 0x8E95, 0xF75D, 0x8E96, 0xF762, 0x8E97, 0xF763, + 0x8E98, 0xF846, 0x8E9A, 0xF75F, 0x8E9D, 0xF8C6, 0x8E9E, 0xF8C3, 0x8E9F, 0xF8C4, 0x8EA0, 0xF8C5, 0x8EA1, 0xC65C, 0x8EA3, 0xF951, + 0x8EA4, 0xF950, 0x8EA5, 0xF94F, 0x8EA6, 0xF970, 0x8EA8, 0xF9BE, 0x8EA9, 0xF9AB, 0x8EAA, 0xC66E, 0x8EAB, 0xA8AD, 0x8EAC, 0xB060, + 0x8EB2, 0xB8FA, 0x8EBA, 0xBDF6, 0x8EBD, 0xEBC8, 0x8EC0, 0xC2DF, 0x8EC2, 0xF355, 0x8EC9, 0xF9AC, 0x8ECA, 0xA8AE, 0x8ECB, 0xAAEE, + 0x8ECC, 0xAD79, 0x8ECD, 0xAD78, 0x8ECF, 0xB063, 0x8ED1, 0xD3E8, 0x8ED2, 0xB061, 0x8ED3, 0xD3E9, 0x8ED4, 0xB062, 0x8ED7, 0xD7DF, + 0x8ED8, 0xD7DB, 0x8EDB, 0xB36D, 0x8EDC, 0xD7DE, 0x8EDD, 0xD7DD, 0x8EDE, 0xD7DC, 0x8EDF, 0xB36E, 0x8EE0, 0xD7E0, 0x8EE1, 0xD7E1, + 0x8EE5, 0xDC43, 0x8EE6, 0xDC41, 0x8EE7, 0xDC45, 0x8EE8, 0xDC46, 0x8EE9, 0xDC4C, 0x8EEB, 0xDC48, 0x8EEC, 0xDC4A, 0x8EEE, 0xDC42, + 0x8EEF, 0xDBFC, 0x8EF1, 0xDC49, 0x8EF4, 0xDC4B, 0x8EF5, 0xDC44, 0x8EF6, 0xDC47, 0x8EF7, 0xDBFD, 0x8EF8, 0xB662, 0x8EF9, 0xDC40, + 0x8EFA, 0xDBFE, 0x8EFB, 0xB661, 0x8EFC, 0xB663, 0x8EFE, 0xB8FD, 0x8EFF, 0xE075, 0x8F00, 0xE077, 0x8F01, 0xE076, 0x8F02, 0xE07B, + 0x8F03, 0xB8FB, 0x8F05, 0xE078, 0x8F06, 0xE074, 0x8F07, 0xE079, 0x8F08, 0xE07A, 0x8F09, 0xB8FC, 0x8F0A, 0xB8FE, 0x8F0B, 0xE07C, + 0x8F0D, 0xE467, 0x8F0E, 0xE466, 0x8F10, 0xE464, 0x8F11, 0xE465, 0x8F12, 0xBBB3, 0x8F13, 0xBBB5, 0x8F14, 0xBBB2, 0x8F15, 0xBBB4, + 0x8F16, 0xE84D, 0x8F17, 0xE84E, 0x8F18, 0xE849, 0x8F1A, 0xE84A, 0x8F1B, 0xBDF8, 0x8F1C, 0xBDFD, 0x8F1D, 0xBDF7, 0x8F1E, 0xBDFE, + 0x8F1F, 0xBDF9, 0x8F20, 0xE84B, 0x8F23, 0xE84C, 0x8F24, 0xE848, 0x8F25, 0xBE40, 0x8F26, 0xBDFB, 0x8F29, 0xBDFA, 0x8F2A, 0xBDFC, + 0x8F2C, 0xE847, 0x8F2E, 0xEBCA, 0x8F2F, 0xBFE8, 0x8F32, 0xEBCC, 0x8F33, 0xBFEA, 0x8F34, 0xEBCF, 0x8F35, 0xEBCB, 0x8F36, 0xEBC9, + 0x8F37, 0xEBCE, 0x8F38, 0xBFE9, 0x8F39, 0xEBCD, 0x8F3B, 0xBFE7, 0x8F3E, 0xC1D3, 0x8F3F, 0xC1D6, 0x8F40, 0xEEC1, 0x8F42, 0xC1D4, + 0x8F43, 0xEEC0, 0x8F44, 0xC1D2, 0x8F45, 0xC1D5, 0x8F46, 0xF146, 0x8F47, 0xF147, 0x8F48, 0xF148, 0x8F49, 0xC2E0, 0x8F4B, 0xF149, + 0x8F4D, 0xC2E1, 0x8F4E, 0xC3E2, 0x8F4F, 0xF358, 0x8F50, 0xF359, 0x8F51, 0xF357, 0x8F52, 0xF356, 0x8F53, 0xF35A, 0x8F54, 0xC3E1, + 0x8F55, 0xF4DD, 0x8F56, 0xF4DB, 0x8F57, 0xF4DC, 0x8F58, 0xF4DE, 0x8F59, 0xF4DA, 0x8F5A, 0xF4DF, 0x8F5B, 0xF658, 0x8F5D, 0xF659, + 0x8F5E, 0xF657, 0x8F5F, 0xC546, 0x8F60, 0xF764, 0x8F61, 0xC5AF, 0x8F62, 0xF765, 0x8F63, 0xF848, 0x8F64, 0xF847, 0x8F9B, 0xA8AF, + 0x8F9C, 0xB664, 0x8F9F, 0xB940, 0x8FA3, 0xBBB6, 0x8FA6, 0xBFEC, 0x8FA8, 0xBFEB, 0x8FAD, 0xC3E3, 0x8FAE, 0xC47C, 0x8FAF, 0xC547, + 0x8FB0, 0xA8B0, 0x8FB1, 0xB064, 0x8FB2, 0xB941, 0x8FB4, 0xF35B, 0x8FBF, 0xCBA6, 0x8FC2, 0xA8B1, 0x8FC4, 0xA8B4, 0x8FC5, 0xA8B3, + 0x8FC6, 0xA8B2, 0x8FC9, 0xCBA5, 0x8FCB, 0xCDCD, 0x8FCD, 0xCDCF, 0x8FCE, 0xAAEF, 0x8FD1, 0xAAF1, 0x8FD2, 0xCDCC, 0x8FD3, 0xCDCE, + 0x8FD4, 0xAAF0, 0x8FD5, 0xCDD1, 0x8FD6, 0xCDD0, 0x8FD7, 0xCDD2, 0x8FE0, 0xD0B6, 0x8FE1, 0xD0B4, 0x8FE2, 0xAD7C, 0x8FE3, 0xD0B3, + 0x8FE4, 0xADA3, 0x8FE5, 0xAD7E, 0x8FE6, 0xAD7B, 0x8FE8, 0xADA4, 0x8FEA, 0xAD7D, 0x8FEB, 0xADA2, 0x8FED, 0xADA1, 0x8FEE, 0xD0B5, + 0x8FF0, 0xAD7A, 0x8FF4, 0xB06A, 0x8FF5, 0xD3EB, 0x8FF6, 0xD3F1, 0x8FF7, 0xB067, 0x8FF8, 0xB06E, 0x8FFA, 0xB069, 0x8FFB, 0xD3EE, + 0x8FFC, 0xD3F0, 0x8FFD, 0xB06C, 0x8FFE, 0xD3EA, 0x8FFF, 0xD3ED, 0x9000, 0xB068, 0x9001, 0xB065, 0x9002, 0xD3EC, 0x9003, 0xB06B, + 0x9004, 0xD3EF, 0x9005, 0xB06D, 0x9006, 0xB066, 0x900B, 0xD7E3, 0x900C, 0xD7E6, 0x900D, 0xB370, 0x900F, 0xB37A, 0x9010, 0xB376, + 0x9011, 0xD7E4, 0x9014, 0xB37E, 0x9015, 0xB377, 0x9016, 0xB37C, 0x9017, 0xB372, 0x9019, 0xB36F, 0x901A, 0xB371, 0x901B, 0xB37D, + 0x901C, 0xD7E5, 0x901D, 0xB375, 0x901E, 0xB378, 0x901F, 0xB374, 0x9020, 0xB379, 0x9021, 0xD7E7, 0x9022, 0xB37B, 0x9023, 0xB373, + 0x9024, 0xD7E2, 0x902D, 0xDC4D, 0x902E, 0xB665, 0x902F, 0xDC4F, 0x9031, 0xB667, 0x9032, 0xB669, 0x9034, 0xDC4E, 0x9035, 0xB666, + 0x9036, 0xB66A, 0x9038, 0xB668, 0x903C, 0xB947, 0x903D, 0xE0A3, 0x903E, 0xB94F, 0x903F, 0xE07E, 0x9041, 0xB950, 0x9042, 0xB945, + 0x9044, 0xE0A1, 0x9047, 0xB94A, 0x9049, 0xE0A2, 0x904A, 0xB943, 0x904B, 0xB942, 0x904D, 0xB94D, 0x904E, 0xB94C, 0x904F, 0xB94B, + 0x9050, 0xB949, 0x9051, 0xB94E, 0x9052, 0xE07D, 0x9053, 0xB944, 0x9054, 0xB946, 0x9055, 0xB948, 0x9058, 0xBBB8, 0x9059, 0xBBBB, + 0x905B, 0xBBBF, 0x905C, 0xBBB9, 0x905D, 0xBBBE, 0x905E, 0xBBBC, 0x9060, 0xBBB7, 0x9062, 0xBBBD, 0x9063, 0xBBBA, 0x9067, 0xE852, + 0x9068, 0xBE43, 0x9069, 0xBE41, 0x906B, 0xE853, 0x906D, 0xBE44, 0x906E, 0xBE42, 0x906F, 0xE851, 0x9070, 0xE850, 0x9072, 0xBFF0, + 0x9073, 0xE84F, 0x9074, 0xBFEE, 0x9075, 0xBFED, 0x9076, 0xEBD0, 0x9077, 0xBE45, 0x9078, 0xBFEF, 0x9079, 0xEBD1, 0x907A, 0xBFF2, + 0x907B, 0xEBD2, 0x907C, 0xBFF1, 0x907D, 0xC1D8, 0x907E, 0xEEC3, 0x907F, 0xC1D7, 0x9080, 0xC1DC, 0x9081, 0xC1DA, 0x9082, 0xC1DB, + 0x9083, 0xC2E3, 0x9084, 0xC1D9, 0x9085, 0xEEC2, 0x9086, 0xEBD3, 0x9087, 0xC2E2, 0x9088, 0xC2E4, 0x908A, 0xC3E4, 0x908B, 0xC3E5, + 0x908D, 0xF4E0, 0x908F, 0xC5DE, 0x9090, 0xC5DD, 0x9091, 0xA8B6, 0x9094, 0xCA55, 0x9095, 0xB06F, 0x9097, 0xCA52, 0x9098, 0xCA53, + 0x9099, 0xCA51, 0x909B, 0xCA54, 0x909E, 0xCBAA, 0x909F, 0xCBA7, 0x90A0, 0xCBAC, 0x90A1, 0xCBA8, 0x90A2, 0xA8B7, 0x90A3, 0xA8BA, + 0x90A5, 0xCBA9, 0x90A6, 0xA8B9, 0x90A7, 0xCBAB, 0x90AA, 0xA8B8, 0x90AF, 0xCDD5, 0x90B0, 0xCDD7, 0x90B1, 0xAAF4, 0x90B2, 0xCDD3, + 0x90B3, 0xCDD6, 0x90B4, 0xCDD4, 0x90B5, 0xAAF2, 0x90B6, 0xAAF5, 0x90B8, 0xAAF3, 0x90BD, 0xD0B8, 0x90BE, 0xD0BC, 0x90BF, 0xD0B9, + 0x90C1, 0xADA7, 0x90C3, 0xADA8, 0x90C5, 0xD0BB, 0x90C7, 0xD0BD, 0x90C8, 0xD0BF, 0x90CA, 0xADA5, 0x90CB, 0xD0BE, 0x90CE, 0xADA6, + 0x90D4, 0xD7EE, 0x90D5, 0xD0BA, 0x90D6, 0xD3F2, 0x90D7, 0xD3FB, 0x90D8, 0xD3F9, 0x90D9, 0xD3F4, 0x90DA, 0xD3F5, 0x90DB, 0xD3FA, + 0x90DC, 0xD3FC, 0x90DD, 0xB071, 0x90DF, 0xD3F7, 0x90E0, 0xD3F3, 0x90E1, 0xB070, 0x90E2, 0xB072, 0x90E3, 0xD3F6, 0x90E4, 0xD3FD, + 0x90E5, 0xD3F8, 0x90E8, 0xB3A1, 0x90E9, 0xD7F1, 0x90EA, 0xD7E9, 0x90EB, 0xD7EF, 0x90EC, 0xD7F0, 0x90ED, 0xB3A2, 0x90EF, 0xD7E8, + 0x90F0, 0xD7EA, 0x90F1, 0xD0B7, 0x90F2, 0xD7EC, 0x90F3, 0xD7ED, 0x90F4, 0xD7EB, 0x90F5, 0xB66C, 0x90F9, 0xDC56, 0x90FA, 0xEBD4, + 0x90FB, 0xDC57, 0x90FC, 0xDC54, 0x90FD, 0xB3A3, 0x90FE, 0xB66E, 0x90FF, 0xDC53, 0x9100, 0xDC59, 0x9101, 0xDC58, 0x9102, 0xB66B, + 0x9103, 0xDC5C, 0x9104, 0xDC52, 0x9105, 0xDC5B, 0x9106, 0xDC50, 0x9107, 0xDC5A, 0x9108, 0xDC55, 0x9109, 0xB66D, 0x910B, 0xE0AA, + 0x910D, 0xE0A5, 0x910E, 0xE0AB, 0x910F, 0xE0A6, 0x9110, 0xE0A4, 0x9111, 0xE0A7, 0x9112, 0xB951, 0x9114, 0xE0A9, 0x9116, 0xE0A8, + 0x9117, 0xB952, 0x9118, 0xBBC1, 0x9119, 0xBBC0, 0x911A, 0xE46E, 0x911B, 0xE471, 0x911C, 0xE469, 0x911D, 0xE46D, 0x911E, 0xBBC2, + 0x911F, 0xE46C, 0x9120, 0xE46A, 0x9121, 0xE470, 0x9122, 0xE46B, 0x9123, 0xE468, 0x9124, 0xE46F, 0x9126, 0xE859, 0x9127, 0xBE48, + 0x9128, 0xF14A, 0x9129, 0xE856, 0x912A, 0xE857, 0x912B, 0xE855, 0x912C, 0xDC51, 0x912D, 0xBE47, 0x912E, 0xE85A, 0x912F, 0xE854, + 0x9130, 0xBE46, 0x9131, 0xBE49, 0x9132, 0xE858, 0x9133, 0xEBD5, 0x9134, 0xBFF3, 0x9135, 0xEBD6, 0x9136, 0xEBD7, 0x9138, 0xEEC4, + 0x9139, 0xC1DD, 0x913A, 0xF14B, 0x913B, 0xF14C, 0x913E, 0xF14D, 0x913F, 0xF35D, 0x9140, 0xF35C, 0x9141, 0xF4E2, 0x9143, 0xF4E1, + 0x9144, 0xF65B, 0x9145, 0xF65C, 0x9146, 0xF65A, 0x9147, 0xF766, 0x9148, 0xC5B0, 0x9149, 0xA8BB, 0x914A, 0xADAA, 0x914B, 0xADA9, + 0x914C, 0xB075, 0x914D, 0xB074, 0x914E, 0xD440, 0x914F, 0xD441, 0x9150, 0xD3FE, 0x9152, 0xB073, 0x9153, 0xD7F5, 0x9155, 0xD7F6, + 0x9156, 0xD7F2, 0x9157, 0xB3A4, 0x9158, 0xD7F3, 0x915A, 0xD7F4, 0x915F, 0xDC5F, 0x9160, 0xDC61, 0x9161, 0xDC5D, 0x9162, 0xDC60, + 0x9163, 0xB66F, 0x9164, 0xDC5E, 0x9165, 0xB670, 0x9168, 0xDD73, 0x9169, 0xB955, 0x916A, 0xB954, 0x916C, 0xB953, 0x916E, 0xE0AC, + 0x916F, 0xE0AD, 0x9172, 0xE473, 0x9173, 0xE475, 0x9174, 0xBBC6, 0x9175, 0xBBC3, 0x9177, 0xBBC5, 0x9178, 0xBBC4, 0x9179, 0xE474, + 0x917A, 0xE472, 0x9180, 0xE861, 0x9181, 0xE85E, 0x9182, 0xE85F, 0x9183, 0xBE4D, 0x9184, 0xE860, 0x9185, 0xE85B, 0x9186, 0xE85C, + 0x9187, 0xBE4A, 0x9189, 0xBE4B, 0x918A, 0xE85D, 0x918B, 0xBE4C, 0x918D, 0xEBDB, 0x918F, 0xEBDC, 0x9190, 0xEBD9, 0x9191, 0xEBDA, + 0x9192, 0xBFF4, 0x9193, 0xEBD8, 0x9199, 0xEEC8, 0x919A, 0xEEC5, 0x919B, 0xEEC7, 0x919C, 0xC1E0, 0x919D, 0xEECB, 0x919E, 0xC1DF, + 0x919F, 0xEEC9, 0x91A0, 0xEECC, 0x91A1, 0xEECA, 0x91A2, 0xEEC6, 0x91A3, 0xC1DE, 0x91A5, 0xF14F, 0x91A7, 0xF150, 0x91A8, 0xF14E, + 0x91AA, 0xF152, 0x91AB, 0xC2E5, 0x91AC, 0xC2E6, 0x91AD, 0xF35F, 0x91AE, 0xC3E7, 0x91AF, 0xF151, 0x91B0, 0xF35E, 0x91B1, 0xC3E6, + 0x91B2, 0xF4E5, 0x91B3, 0xF4E6, 0x91B4, 0xC4BF, 0x91B5, 0xF4E4, 0x91B7, 0xF4E3, 0x91B9, 0xF65D, 0x91BA, 0xC548, 0x91BC, 0xF849, + 0x91BD, 0xF8C8, 0x91BE, 0xF8C7, 0x91C0, 0xC643, 0x91C1, 0xC65D, 0x91C2, 0xF8C9, 0x91C3, 0xF971, 0x91C5, 0xC66F, 0x91C6, 0xA8BC, + 0x91C7, 0xAAF6, 0x91C9, 0xB956, 0x91CB, 0xC4C0, 0x91CC, 0xA8BD, 0x91CD, 0xADAB, 0x91CE, 0xB3A5, 0x91CF, 0xB671, 0x91D0, 0xC2E7, + 0x91D1, 0xAAF7, 0x91D3, 0xD0C1, 0x91D4, 0xD0C0, 0x91D5, 0xD442, 0x91D7, 0xB078, 0x91D8, 0xB076, 0x91D9, 0xB07A, 0x91DA, 0xD444, + 0x91DC, 0xB079, 0x91DD, 0xB077, 0x91E2, 0xD443, 0x91E3, 0xB3A8, 0x91E4, 0xD7FC, 0x91E6, 0xB3A7, 0x91E7, 0xB3A9, 0x91E8, 0xD842, + 0x91E9, 0xB3AB, 0x91EA, 0xD7FE, 0x91EB, 0xD840, 0x91EC, 0xD7F7, 0x91ED, 0xB3AA, 0x91EE, 0xD843, 0x91F1, 0xD7F9, 0x91F3, 0xD7FA, + 0x91F4, 0xD7F8, 0x91F5, 0xB3A6, 0x91F7, 0xD841, 0x91F8, 0xD7FB, 0x91F9, 0xD7FD, 0x91FD, 0xDC6D, 0x91FF, 0xDC6C, 0x9200, 0xDC6A, + 0x9201, 0xDC62, 0x9202, 0xDC71, 0x9203, 0xDC65, 0x9204, 0xDC6F, 0x9205, 0xDC76, 0x9206, 0xDC6E, 0x9207, 0xB679, 0x9209, 0xB675, + 0x920A, 0xDC63, 0x920C, 0xDC69, 0x920D, 0xB677, 0x920F, 0xDC68, 0x9210, 0xB678, 0x9211, 0xB67A, 0x9212, 0xDC6B, 0x9214, 0xB672, + 0x9215, 0xB673, 0x9216, 0xDC77, 0x9217, 0xDC75, 0x9219, 0xDC74, 0x921A, 0xDC66, 0x921C, 0xDC72, 0x921E, 0xB676, 0x9223, 0xB674, + 0x9224, 0xDC73, 0x9225, 0xDC64, 0x9226, 0xDC67, 0x9227, 0xDC70, 0x922D, 0xE4BA, 0x922E, 0xE0B7, 0x9230, 0xE0B0, 0x9231, 0xE0C3, + 0x9232, 0xE0CC, 0x9233, 0xE0B3, 0x9234, 0xB961, 0x9236, 0xE0C0, 0x9237, 0xB957, 0x9238, 0xB959, 0x9239, 0xB965, 0x923A, 0xE0B1, + 0x923D, 0xB95A, 0x923E, 0xB95C, 0x923F, 0xB966, 0x9240, 0xB95B, 0x9245, 0xB964, 0x9246, 0xE0B9, 0x9248, 0xE0AE, 0x9249, 0xB962, + 0x924A, 0xE0B8, 0x924B, 0xB95E, 0x924C, 0xE0CA, 0x924D, 0xB963, 0x924E, 0xE0C8, 0x924F, 0xE0BC, 0x9250, 0xE0C6, 0x9251, 0xB960, + 0x9252, 0xE0AF, 0x9253, 0xE0C9, 0x9254, 0xE0C4, 0x9256, 0xE0CB, 0x9257, 0xB958, 0x925A, 0xB967, 0x925B, 0xB95D, 0x925E, 0xE0B5, + 0x9260, 0xE0BD, 0x9261, 0xE0C1, 0x9263, 0xE0C5, 0x9264, 0xB95F, 0x9265, 0xE0B4, 0x9266, 0xE0B2, 0x9267, 0xE0BE, 0x926C, 0xE0BB, + 0x926D, 0xE0BA, 0x926F, 0xE0BF, 0x9270, 0xE0C2, 0x9272, 0xE0C7, 0x9276, 0xE478, 0x9278, 0xBBC7, 0x9279, 0xE4A4, 0x927A, 0xE47A, + 0x927B, 0xBBCC, 0x927C, 0xBBD0, 0x927D, 0xE4AD, 0x927E, 0xE4B5, 0x927F, 0xE4A6, 0x9280, 0xBBC8, 0x9282, 0xE4AA, 0x9283, 0xE0B6, + 0x9285, 0xBBC9, 0x9286, 0xE4B1, 0x9287, 0xE4B6, 0x9288, 0xE4AE, 0x928A, 0xE4B0, 0x928B, 0xE4B9, 0x928C, 0xE4B2, 0x928D, 0xE47E, + 0x928E, 0xE4A9, 0x9291, 0xBBD1, 0x9293, 0xBBCD, 0x9294, 0xE47C, 0x9295, 0xE4AB, 0x9296, 0xBBCB, 0x9297, 0xE4A5, 0x9298, 0xBBCA, + 0x9299, 0xE4B3, 0x929A, 0xE4A2, 0x929B, 0xE479, 0x929C, 0xBBCE, 0x929D, 0xE4B8, 0x92A0, 0xE47B, 0x92A1, 0xE4AF, 0x92A2, 0xE4AC, + 0x92A3, 0xE4A7, 0x92A4, 0xE477, 0x92A5, 0xE476, 0x92A6, 0xE4A1, 0x92A7, 0xE4B4, 0x92A8, 0xBBCF, 0x92A9, 0xE4B7, 0x92AA, 0xE47D, + 0x92AB, 0xE4A3, 0x92AC, 0xBE52, 0x92B2, 0xBE5A, 0x92B3, 0xBE55, 0x92B4, 0xE8A4, 0x92B5, 0xE8A1, 0x92B6, 0xE867, 0x92B7, 0xBE50, + 0x92B9, 0xF9D7, 0x92BB, 0xBE4F, 0x92BC, 0xBE56, 0x92C0, 0xE865, 0x92C1, 0xBE54, 0x92C2, 0xE871, 0x92C3, 0xE863, 0x92C4, 0xE864, + 0x92C5, 0xBE4E, 0x92C6, 0xE8A3, 0x92C7, 0xBE58, 0x92C8, 0xE874, 0x92C9, 0xE879, 0x92CA, 0xE873, 0x92CB, 0xEBEE, 0x92CC, 0xE86F, + 0x92CD, 0xE877, 0x92CE, 0xE875, 0x92CF, 0xE868, 0x92D0, 0xE862, 0x92D1, 0xE87D, 0x92D2, 0xBE57, 0x92D3, 0xE87E, 0x92D5, 0xE878, + 0x92D7, 0xE86D, 0x92D8, 0xE86B, 0x92D9, 0xE866, 0x92DD, 0xE86E, 0x92DE, 0xE87B, 0x92DF, 0xE86A, 0x92E0, 0xE87A, 0x92E1, 0xE8A2, + 0x92E4, 0xBE53, 0x92E6, 0xE876, 0x92E7, 0xE87C, 0x92E8, 0xE872, 0x92E9, 0xE86C, 0x92EA, 0xBE51, 0x92EE, 0xE4A8, 0x92EF, 0xE870, + 0x92F0, 0xBE59, 0x92F1, 0xE869, 0x92F7, 0xEBF4, 0x92F8, 0xBFF7, 0x92F9, 0xEBF3, 0x92FA, 0xEBF0, 0x92FB, 0xEC44, 0x92FC, 0xBFFB, + 0x92FE, 0xEC41, 0x92FF, 0xEBF8, 0x9300, 0xEC43, 0x9301, 0xEBE9, 0x9302, 0xEBF6, 0x9304, 0xBFFD, 0x9306, 0xEBE1, 0x9308, 0xEBDF, + 0x9309, 0xEC42, 0x930B, 0xEC40, 0x930C, 0xEBFE, 0x930D, 0xEBED, 0x930E, 0xEBEC, 0x930F, 0xEBE2, 0x9310, 0xC040, 0x9312, 0xEBE8, + 0x9313, 0xEBF2, 0x9314, 0xEBFD, 0x9315, 0xC043, 0x9316, 0xEC45, 0x9318, 0xC1E8, 0x9319, 0xC045, 0x931A, 0xBFFE, 0x931B, 0xEBE6, + 0x931D, 0xEBEF, 0x931E, 0xEBDE, 0x931F, 0xEBE0, 0x9320, 0xBFF5, 0x9321, 0xC042, 0x9322, 0xBFFA, 0x9323, 0xEBE7, 0x9324, 0xEBF7, + 0x9325, 0xEBF1, 0x9326, 0xC041, 0x9327, 0xEBDD, 0x9328, 0xC1E3, 0x9329, 0xEBF9, 0x932A, 0xEBFC, 0x932B, 0xBFFC, 0x932D, 0xEBEB, + 0x932E, 0xC044, 0x932F, 0xBFF9, 0x9333, 0xBFF8, 0x9334, 0xEBF5, 0x9335, 0xEBFB, 0x9336, 0xBFF6, 0x9338, 0xEBE4, 0x9339, 0xEBFA, + 0x933C, 0xEBE5, 0x9346, 0xEBEA, 0x9347, 0xEED2, 0x9349, 0xEED7, 0x934A, 0xC1E5, 0x934B, 0xC1E7, 0x934C, 0xEEDD, 0x934D, 0xC1E1, + 0x934E, 0xEEEC, 0x934F, 0xEEE3, 0x9350, 0xEED8, 0x9351, 0xEED9, 0x9352, 0xEEE2, 0x9354, 0xC1EE, 0x9355, 0xEEE1, 0x9356, 0xEED1, + 0x9357, 0xEEE0, 0x9358, 0xEED4, 0x9359, 0xEEED, 0x935A, 0xC1ED, 0x935B, 0xC1EB, 0x935C, 0xEED5, 0x935E, 0xEEE8, 0x9360, 0xEEDA, + 0x9361, 0xEEE7, 0x9363, 0xEEE9, 0x9364, 0xEED0, 0x9365, 0xC1E6, 0x9367, 0xEEEA, 0x936A, 0xEEDE, 0x936C, 0xC1EA, 0x936D, 0xEEDB, + 0x9370, 0xC1EC, 0x9371, 0xEEE4, 0x9375, 0xC1E4, 0x9376, 0xEED6, 0x9377, 0xEEE5, 0x9379, 0xEEDF, 0x937A, 0xEBE3, 0x937B, 0xEEE6, + 0x937C, 0xEED3, 0x937E, 0xC1E9, 0x9380, 0xEEEB, 0x9382, 0xC1E2, 0x9383, 0xEECE, 0x9388, 0xF160, 0x9389, 0xF159, 0x938A, 0xC2E9, + 0x938C, 0xF154, 0x938D, 0xF163, 0x938E, 0xF15B, 0x938F, 0xEEDC, 0x9391, 0xF165, 0x9392, 0xF155, 0x9394, 0xC2E8, 0x9395, 0xF15F, + 0x9396, 0xC2EA, 0x9397, 0xC2F2, 0x9398, 0xC2F0, 0x9399, 0xF161, 0x939A, 0xC2F1, 0x939B, 0xF157, 0x939D, 0xF158, 0x939E, 0xF15D, + 0x939F, 0xF162, 0x93A1, 0xEECD, 0x93A2, 0xC2EB, 0x93A3, 0xF16A, 0x93A4, 0xF167, 0x93A5, 0xF16B, 0x93A6, 0xF15E, 0x93A7, 0xF15A, + 0x93A8, 0xF168, 0x93A9, 0xF36A, 0x93AA, 0xF15C, 0x93AC, 0xC2EE, 0x93AE, 0xC2ED, 0x93AF, 0xEECF, 0x93B0, 0xC2EF, 0x93B1, 0xF164, + 0x93B2, 0xF166, 0x93B3, 0xC2EC, 0x93B4, 0xF169, 0x93B5, 0xF153, 0x93B7, 0xF156, 0x93C0, 0xF373, 0x93C2, 0xF363, 0x93C3, 0xC3EB, + 0x93C4, 0xF371, 0x93C7, 0xF361, 0x93C8, 0xC3EC, 0x93CA, 0xF36C, 0x93CC, 0xF368, 0x93CD, 0xC3F1, 0x93CE, 0xF372, 0x93CF, 0xF362, + 0x93D0, 0xF365, 0x93D1, 0xC3E9, 0x93D2, 0xF374, 0x93D4, 0xF36D, 0x93D5, 0xF370, 0x93D6, 0xC3EF, 0x93D7, 0xC3F4, 0x93D8, 0xC3F2, + 0x93D9, 0xF369, 0x93DA, 0xF364, 0x93DC, 0xC3ED, 0x93DD, 0xC3EE, 0x93DE, 0xF360, 0x93DF, 0xC3EA, 0x93E1, 0xC3E8, 0x93E2, 0xC3F0, + 0x93E3, 0xF36F, 0x93E4, 0xC3F3, 0x93E6, 0xF36B, 0x93E7, 0xF375, 0x93E8, 0xC3F5, 0x93EC, 0xF367, 0x93EE, 0xF36E, 0x93F5, 0xF4F3, + 0x93F6, 0xF542, 0x93F7, 0xF4F5, 0x93F8, 0xF4FC, 0x93F9, 0xF366, 0x93FA, 0xF4FA, 0x93FB, 0xF4E9, 0x93FC, 0xF540, 0x93FD, 0xC4C3, + 0x93FE, 0xF4ED, 0x93FF, 0xF4FE, 0x9400, 0xF4F4, 0x9403, 0xC4C2, 0x9406, 0xF544, 0x9407, 0xF4F6, 0x9409, 0xF4FB, 0x940A, 0xF4FD, + 0x940B, 0xF4E7, 0x940C, 0xF541, 0x940D, 0xF4F2, 0x940E, 0xF4F7, 0x940F, 0xF4EB, 0x9410, 0xF4EF, 0x9411, 0xF543, 0x9412, 0xF4F9, + 0x9413, 0xF4E8, 0x9414, 0xF4EC, 0x9415, 0xF4EE, 0x9416, 0xF4F8, 0x9418, 0xC4C1, 0x9419, 0xF4F1, 0x9420, 0xF4EA, 0x9428, 0xF4F0, + 0x9429, 0xF661, 0x942A, 0xF666, 0x942B, 0xC54F, 0x942C, 0xF668, 0x942E, 0xC549, 0x9430, 0xF664, 0x9431, 0xF66A, 0x9432, 0xC54E, + 0x9433, 0xC54A, 0x9435, 0xC54B, 0x9436, 0xF660, 0x9437, 0xF667, 0x9438, 0xC54D, 0x9439, 0xF665, 0x943A, 0xC54C, 0x943B, 0xF65F, + 0x943C, 0xF663, 0x943D, 0xF662, 0x943F, 0xF65E, 0x9440, 0xF669, 0x9444, 0xC5B1, 0x9445, 0xF76D, 0x9446, 0xF770, 0x9447, 0xF76C, + 0x9448, 0xF76E, 0x9449, 0xF76F, 0x944A, 0xF769, 0x944B, 0xF76A, 0x944C, 0xF767, 0x944F, 0xF76B, 0x9450, 0xF768, 0x9451, 0xC5B2, + 0x9452, 0xC5B3, 0x9455, 0xF84B, 0x9457, 0xF84D, 0x945D, 0xF84C, 0x945E, 0xF84E, 0x9460, 0xC5E0, 0x9462, 0xF84A, 0x9463, 0xC5DF, + 0x9464, 0xC5E1, 0x9468, 0xF8CB, 0x9469, 0xF8CC, 0x946A, 0xC644, 0x946B, 0xF8CA, 0x946D, 0xF953, 0x946E, 0xF952, 0x946F, 0xF954, + 0x9470, 0xC65F, 0x9471, 0xF955, 0x9472, 0xC65E, 0x9473, 0xF956, 0x9474, 0xF972, 0x9475, 0xF975, 0x9476, 0xF974, 0x9477, 0xC668, + 0x9478, 0xF973, 0x947C, 0xC672, 0x947D, 0xC670, 0x947E, 0xC671, 0x947F, 0xC677, 0x9480, 0xF9C0, 0x9481, 0xF9C1, 0x9482, 0xF9BF, + 0x9483, 0xF9C9, 0x9577, 0xAAF8, 0x957A, 0xD844, 0x957B, 0xDC78, 0x957C, 0xE8A5, 0x957D, 0xF376, 0x9580, 0xAAF9, 0x9582, 0xADAC, + 0x9583, 0xB07B, 0x9586, 0xD845, 0x9588, 0xD846, 0x9589, 0xB3AC, 0x958B, 0xB67D, 0x958C, 0xDC7A, 0x958D, 0xDC79, 0x958E, 0xB6A3, + 0x958F, 0xB67C, 0x9590, 0xDC7B, 0x9591, 0xB67E, 0x9592, 0xB6A2, 0x9593, 0xB6A1, 0x9594, 0xB67B, 0x9598, 0xB968, 0x959B, 0xE0D0, + 0x959C, 0xE0CE, 0x959E, 0xE0CF, 0x959F, 0xE0CD, 0x95A1, 0xBBD2, 0x95A3, 0xBBD5, 0x95A4, 0xBBD7, 0x95A5, 0xBBD6, 0x95A8, 0xBBD3, + 0x95A9, 0xBBD4, 0x95AB, 0xE8A7, 0x95AC, 0xE8A6, 0x95AD, 0xBE5B, 0x95AE, 0xE8A8, 0x95B0, 0xE8A9, 0x95B1, 0xBE5C, 0x95B5, 0xEC4D, + 0x95B6, 0xEC4B, 0x95B7, 0xEEF3, 0x95B9, 0xEC49, 0x95BA, 0xEC4A, 0x95BB, 0xC046, 0x95BC, 0xEC46, 0x95BD, 0xEC4E, 0x95BE, 0xEC48, + 0x95BF, 0xEC4C, 0x95C0, 0xEEEF, 0x95C3, 0xEEF1, 0x95C5, 0xEEF2, 0x95C6, 0xC1F3, 0x95C7, 0xEEEE, 0x95C8, 0xC1F2, 0x95C9, 0xEEF0, + 0x95CA, 0xC1EF, 0x95CB, 0xC1F0, 0x95CC, 0xC1F1, 0x95CD, 0xEC47, 0x95D0, 0xC2F5, 0x95D1, 0xF16E, 0x95D2, 0xF16C, 0x95D3, 0xF16D, + 0x95D4, 0xC2F3, 0x95D5, 0xC2F6, 0x95D6, 0xC2F4, 0x95DA, 0xF377, 0x95DB, 0xF378, 0x95DC, 0xC3F6, 0x95DE, 0xF545, 0x95DF, 0xF547, + 0x95E0, 0xF546, 0x95E1, 0xC4C4, 0x95E2, 0xC550, 0x95E3, 0xF66D, 0x95E4, 0xF66C, 0x95E5, 0xF66B, 0x961C, 0xAAFA, 0x961E, 0xC9AA, + 0x9620, 0xCA58, 0x9621, 0xA6E9, 0x9622, 0xCA56, 0x9623, 0xCA59, 0x9624, 0xCA57, 0x9628, 0xCBAE, 0x962A, 0xA8C1, 0x962C, 0xA8C2, + 0x962D, 0xCBB0, 0x962E, 0xA8BF, 0x962F, 0xCBAF, 0x9630, 0xCBAD, 0x9631, 0xA8C0, 0x9632, 0xA8BE, 0x9639, 0xCDD8, 0x963A, 0xCDDB, + 0x963B, 0xAAFD, 0x963C, 0xCDDA, 0x963D, 0xCDD9, 0x963F, 0xAAFC, 0x9640, 0xAAFB, 0x9642, 0xAB40, 0x9643, 0xCDDC, 0x9644, 0xAAFE, + 0x964A, 0xD0C6, 0x964B, 0xADAE, 0x964C, 0xADAF, 0x964D, 0xADB0, 0x964E, 0xD0C7, 0x964F, 0xD0C3, 0x9650, 0xADAD, 0x9651, 0xD0C4, + 0x9653, 0xD0C5, 0x9654, 0xD0C2, 0x9658, 0xB0A4, 0x965B, 0xB0A1, 0x965C, 0xD445, 0x965D, 0xB0A2, 0x965E, 0xB0A5, 0x965F, 0xD446, + 0x9661, 0xB07E, 0x9662, 0xB07C, 0x9663, 0xB07D, 0x9664, 0xB0A3, 0x966A, 0xB3AD, 0x966B, 0xD849, 0x966C, 0xB3B5, 0x966D, 0xD848, + 0x966F, 0xD84B, 0x9670, 0xB3B1, 0x9671, 0xD84A, 0x9672, 0xB6AB, 0x9673, 0xB3AF, 0x9674, 0xB3B2, 0x9675, 0xB3AE, 0x9676, 0xB3B3, + 0x9677, 0xB3B4, 0x9678, 0xB3B0, 0x967C, 0xD847, 0x967D, 0xB6A7, 0x967E, 0xDC7D, 0x9680, 0xDCA3, 0x9683, 0xDCA2, 0x9684, 0xB6AC, + 0x9685, 0xB6A8, 0x9686, 0xB6A9, 0x9687, 0xDC7C, 0x9688, 0xDC7E, 0x9689, 0xDCA1, 0x968A, 0xB6A4, 0x968B, 0xB6A6, 0x968D, 0xB6AA, + 0x968E, 0xB6A5, 0x9691, 0xE0D3, 0x9692, 0xE0D1, 0x9693, 0xE0D2, 0x9694, 0xB96A, 0x9695, 0xB96B, 0x9697, 0xE0D4, 0x9698, 0xB969, + 0x9699, 0xBBD8, 0x969B, 0xBBDA, 0x969C, 0xBBD9, 0x969E, 0xE4BB, 0x96A1, 0xE4BC, 0x96A2, 0xE8AB, 0x96A4, 0xE8AA, 0x96A7, 0xC047, + 0x96A8, 0xC048, 0x96A9, 0xEC4F, 0x96AA, 0xC049, 0x96AC, 0xEEF6, 0x96AE, 0xEEF4, 0x96B0, 0xEEF5, 0x96B1, 0xC1F4, 0x96B3, 0xF16F, + 0x96B4, 0xC3F7, 0x96B8, 0xC1F5, 0x96B9, 0xAB41, 0x96BB, 0xB0A6, 0x96BC, 0xD447, 0x96BF, 0xD84C, 0x96C0, 0xB3B6, 0x96C1, 0xB6AD, + 0x96C2, 0xDCA4, 0x96C3, 0xDCA6, 0x96C4, 0xB6AF, 0x96C5, 0xB6AE, 0x96C6, 0xB6B0, 0x96C7, 0xB6B1, 0x96C8, 0xDCA5, 0x96C9, 0xB96E, + 0x96CA, 0xB96F, 0x96CB, 0xB96D, 0x96CC, 0xBBDB, 0x96CD, 0xB96C, 0x96CE, 0xE0D5, 0x96D2, 0xBBDC, 0x96D3, 0xE8AC, 0x96D4, 0xEC50, + 0x96D5, 0xC04A, 0x96D6, 0xC1F6, 0x96D7, 0xF170, 0x96D8, 0xF174, 0x96D9, 0xC2F9, 0x96DA, 0xF171, 0x96DB, 0xC2FA, 0x96DC, 0xC2F8, + 0x96DD, 0xF175, 0x96DE, 0xC2FB, 0x96DF, 0xF173, 0x96E1, 0xF379, 0x96E2, 0xC2F7, 0x96E3, 0xC3F8, 0x96E5, 0xF8CD, 0x96E8, 0xAB42, + 0x96E9, 0xB3B8, 0x96EA, 0xB3B7, 0x96EF, 0xB6B2, 0x96F0, 0xDCA8, 0x96F1, 0xDCA7, 0x96F2, 0xB6B3, 0x96F5, 0xE0D9, 0x96F6, 0xB973, + 0x96F7, 0xB970, 0x96F8, 0xE0D8, 0x96F9, 0xB972, 0x96FA, 0xE0D6, 0x96FB, 0xB971, 0x96FD, 0xE0D7, 0x96FF, 0xE4BD, 0x9700, 0xBBDD, + 0x9702, 0xE8AF, 0x9704, 0xBE5D, 0x9705, 0xE8AD, 0x9706, 0xBE5E, 0x9707, 0xBE5F, 0x9708, 0xE8AE, 0x9709, 0xBE60, 0x970B, 0xEC51, + 0x970D, 0xC04E, 0x970E, 0xC04B, 0x970F, 0xC050, 0x9710, 0xEC53, 0x9711, 0xC04C, 0x9712, 0xEC52, 0x9713, 0xC04F, 0x9716, 0xC04D, + 0x9718, 0xEEF9, 0x9719, 0xEEFB, 0x971C, 0xC1F7, 0x971D, 0xEEFA, 0x971E, 0xC1F8, 0x971F, 0xEEF8, 0x9720, 0xEEF7, 0x9722, 0xF177, + 0x9723, 0xF176, 0x9724, 0xC2FC, 0x9725, 0xF178, 0x9726, 0xF37E, 0x9727, 0xC3FA, 0x9728, 0xF37D, 0x9729, 0xF37A, 0x972A, 0xC3F9, + 0x972B, 0xF37B, 0x972C, 0xF37C, 0x972E, 0xF548, 0x972F, 0xF549, 0x9730, 0xC4C5, 0x9732, 0xC553, 0x9735, 0xF66E, 0x9738, 0xC551, + 0x9739, 0xC552, 0x973A, 0xF66F, 0x973D, 0xC5B4, 0x973E, 0xC5B5, 0x973F, 0xF771, 0x9742, 0xC645, 0x9743, 0xF8CF, 0x9744, 0xC647, + 0x9746, 0xF8CE, 0x9747, 0xF8D0, 0x9748, 0xC646, 0x9749, 0xF957, 0x974B, 0xF9AD, 0x9752, 0xAB43, 0x9756, 0xB974, 0x9758, 0xE4BE, + 0x975A, 0xE8B0, 0x975B, 0xC051, 0x975C, 0xC052, 0x975E, 0xAB44, 0x9760, 0xBE61, 0x9761, 0xC3FB, 0x9762, 0xADB1, 0x9766, 0xC053, + 0x9768, 0xC5E2, 0x9769, 0xADB2, 0x976A, 0xD84D, 0x976C, 0xDCA9, 0x976E, 0xDCAB, 0x9770, 0xDCAA, 0x9772, 0xE0DD, 0x9773, 0xE0DA, + 0x9774, 0xB975, 0x9776, 0xB976, 0x9777, 0xE0DB, 0x9778, 0xE0DC, 0x977A, 0xE4C0, 0x977B, 0xE4C5, 0x977C, 0xBBDE, 0x977D, 0xE4BF, + 0x977E, 0xE4C1, 0x977F, 0xE4C8, 0x9780, 0xE4C3, 0x9781, 0xE4C7, 0x9782, 0xE4C4, 0x9783, 0xE4C2, 0x9784, 0xE4C6, 0x9785, 0xBBDF, + 0x9788, 0xE8B3, 0x978A, 0xE8B1, 0x978B, 0xBE63, 0x978D, 0xBE62, 0x978E, 0xE8B2, 0x978F, 0xBE64, 0x9794, 0xEC56, 0x9797, 0xEC55, + 0x9798, 0xC054, 0x9799, 0xEC54, 0x979A, 0xEEFC, 0x979C, 0xEEFE, 0x979D, 0xEF41, 0x979E, 0xEF40, 0x97A0, 0xC1F9, 0x97A1, 0xEEFD, + 0x97A2, 0xF1A1, 0x97A3, 0xC2FD, 0x97A4, 0xF17D, 0x97A5, 0xF1A2, 0x97A6, 0xC2FE, 0x97A8, 0xF17B, 0x97AA, 0xF17E, 0x97AB, 0xF17C, + 0x97AC, 0xF179, 0x97AD, 0xC340, 0x97AE, 0xF17A, 0x97B3, 0xF3A1, 0x97B6, 0xF3A3, 0x97B7, 0xF3A2, 0x97B9, 0xF54A, 0x97BB, 0xF54B, + 0x97BF, 0xF670, 0x97C1, 0xC5B7, 0x97C3, 0xC5B6, 0x97C4, 0xF84F, 0x97C5, 0xF850, 0x97C6, 0xC648, 0x97C7, 0xF8D1, 0x97C9, 0xC669, + 0x97CB, 0xADB3, 0x97CC, 0xB6B4, 0x97CD, 0xE4CA, 0x97CE, 0xE4C9, 0x97CF, 0xE8B5, 0x97D0, 0xE8B4, 0x97D3, 0xC1FA, 0x97D4, 0xEF43, + 0x97D5, 0xEF42, 0x97D6, 0xF1A5, 0x97D7, 0xF1A3, 0x97D8, 0xF1A6, 0x97D9, 0xF1A4, 0x97DC, 0xC3FC, 0x97DD, 0xF3A4, 0x97DE, 0xF3A5, + 0x97DF, 0xF3A6, 0x97E1, 0xF671, 0x97E3, 0xF772, 0x97E5, 0xF8D2, 0x97ED, 0xADB4, 0x97F0, 0xEC57, 0x97F1, 0xEF44, 0x97F3, 0xADB5, + 0x97F6, 0xBBE0, 0x97F8, 0xEC58, 0x97F9, 0xC341, 0x97FA, 0xF1A7, 0x97FB, 0xC3FD, 0x97FD, 0xF54C, 0x97FE, 0xF54D, 0x97FF, 0xC554, + 0x9800, 0xF851, 0x9801, 0xADB6, 0x9802, 0xB3BB, 0x9803, 0xB3BC, 0x9804, 0xD84E, 0x9805, 0xB6B5, 0x9806, 0xB6B6, 0x9807, 0xDCAC, + 0x9808, 0xB6B7, 0x980A, 0xB97A, 0x980C, 0xB97C, 0x980D, 0xE0DF, 0x980E, 0xE0E0, 0x980F, 0xE0DE, 0x9810, 0xB977, 0x9811, 0xB978, + 0x9812, 0xB97B, 0x9813, 0xB979, 0x9816, 0xE4CB, 0x9817, 0xBBE1, 0x9818, 0xBBE2, 0x981B, 0xE8BC, 0x981C, 0xBE67, 0x981D, 0xE8B7, + 0x981E, 0xE8B6, 0x9820, 0xE8BB, 0x9821, 0xBE65, 0x9824, 0xC05B, 0x9826, 0xE8B8, 0x9827, 0xE8BD, 0x9828, 0xE8BA, 0x9829, 0xE8B9, + 0x982B, 0xBE66, 0x982D, 0xC059, 0x982F, 0xEC5A, 0x9830, 0xC055, 0x9832, 0xEC5B, 0x9835, 0xEC59, 0x9837, 0xC058, 0x9838, 0xC056, + 0x9839, 0xC05A, 0x983B, 0xC057, 0x9841, 0xEF45, 0x9843, 0xEF4A, 0x9844, 0xEF46, 0x9845, 0xEF49, 0x9846, 0xC1FB, 0x9848, 0xEDD4, + 0x9849, 0xEF48, 0x984A, 0xEF47, 0x984C, 0xC344, 0x984D, 0xC342, 0x984E, 0xC345, 0x984F, 0xC343, 0x9850, 0xF1A8, 0x9851, 0xF1A9, + 0x9852, 0xF1AA, 0x9853, 0xC346, 0x9857, 0xF3AA, 0x9858, 0xC440, 0x9859, 0xF3A8, 0x985B, 0xC441, 0x985C, 0xF3A7, 0x985D, 0xF3A9, + 0x985E, 0xC3FE, 0x985F, 0xF551, 0x9860, 0xF54E, 0x9862, 0xF54F, 0x9863, 0xF550, 0x9864, 0xF672, 0x9865, 0xC556, 0x9867, 0xC555, + 0x9869, 0xF774, 0x986A, 0xF773, 0x986B, 0xC5B8, 0x986F, 0xC5E3, 0x9870, 0xC649, 0x9871, 0xC660, 0x9872, 0xF958, 0x9873, 0xF9AE, + 0x9874, 0xF9AF, 0x98A8, 0xADB7, 0x98A9, 0xDCAD, 0x98AC, 0xE0E1, 0x98AD, 0xE4CC, 0x98AE, 0xE4CD, 0x98AF, 0xBBE3, 0x98B1, 0xBBE4, + 0x98B2, 0xE8BE, 0x98B3, 0xBE68, 0x98B6, 0xC1FC, 0x98B8, 0xF1AB, 0x98BA, 0xC347, 0x98BB, 0xF3AD, 0x98BC, 0xC442, 0x98BD, 0xF3AC, + 0x98BE, 0xF3AE, 0x98BF, 0xF3AB, 0x98C0, 0xF675, 0x98C1, 0xF552, 0x98C2, 0xF553, 0x98C4, 0xC4C6, 0x98C6, 0xF674, 0x98C9, 0xF673, + 0x98CB, 0xF775, 0x98CC, 0xF9B0, 0x98DB, 0xADB8, 0x98DF, 0xADB9, 0x98E2, 0xB0A7, 0x98E3, 0xD448, 0x98E5, 0xD84F, 0x98E7, 0xB6B8, + 0x98E9, 0xB6BB, 0x98EA, 0xB6B9, 0x98EB, 0xDCAE, 0x98ED, 0xB6BD, 0x98EF, 0xB6BA, 0x98F2, 0xB6BC, 0x98F4, 0xB97E, 0x98F6, 0xE0E2, + 0x98F9, 0xE0E3, 0x98FA, 0xE8C0, 0x98FC, 0xB97D, 0x98FD, 0xB9A1, 0x98FE, 0xB9A2, 0x9900, 0xE4CF, 0x9902, 0xE4CE, 0x9903, 0xBBE5, + 0x9905, 0xBBE6, 0x9907, 0xE4D0, 0x9908, 0xE8BF, 0x9909, 0xBBE8, 0x990A, 0xBE69, 0x990C, 0xBBE7, 0x9910, 0xC05C, 0x9911, 0xE8C1, + 0x9912, 0xBE6B, 0x9913, 0xBE6A, 0x9914, 0xE8C2, 0x9915, 0xE8C5, 0x9916, 0xE8C3, 0x9917, 0xE8C4, 0x9918, 0xBE6C, 0x991A, 0xC061, + 0x991B, 0xC05F, 0x991E, 0xC05E, 0x991F, 0xEC5D, 0x9921, 0xC060, 0x9924, 0xEC5C, 0x9925, 0xEF4B, 0x9927, 0xEC5E, 0x9928, 0xC05D, + 0x9929, 0xEC5F, 0x992A, 0xEF4E, 0x992B, 0xEF4C, 0x992C, 0xEF4D, 0x992D, 0xEF52, 0x992E, 0xC34B, 0x992F, 0xEF51, 0x9930, 0xEF54, + 0x9931, 0xEF53, 0x9932, 0xEF50, 0x9933, 0xEF4F, 0x9935, 0xC1FD, 0x993A, 0xF1AE, 0x993C, 0xF1AD, 0x993D, 0xC34A, 0x993E, 0xC348, + 0x993F, 0xC349, 0x9941, 0xF1AC, 0x9943, 0xF3B1, 0x9945, 0xC443, 0x9947, 0xF3B0, 0x9948, 0xF3AF, 0x9949, 0xC444, 0x994B, 0xF558, + 0x994C, 0xF557, 0x994E, 0xF555, 0x9950, 0xF554, 0x9951, 0xC4C8, 0x9952, 0xC4C7, 0x9953, 0xF559, 0x9954, 0xF776, 0x9955, 0xC5B9, + 0x9956, 0xF677, 0x9957, 0xC557, 0x9958, 0xF676, 0x9959, 0xF556, 0x995B, 0xF777, 0x995C, 0xC5E4, 0x995E, 0xC661, 0x995F, 0xF959, + 0x9961, 0xF9B1, 0x9996, 0xADBA, 0x9997, 0xD850, 0x9998, 0xEF55, 0x9999, 0xADBB, 0x999C, 0xE4D2, 0x999D, 0xE4D1, 0x999E, 0xEC60, + 0x99A1, 0xEF57, 0x99A3, 0xEF56, 0x99A5, 0xC34C, 0x99A6, 0xF3B2, 0x99A7, 0xF3B3, 0x99A8, 0xC4C9, 0x99AB, 0xF9B2, 0x99AC, 0xB0A8, + 0x99AD, 0xB6BF, 0x99AE, 0xB6BE, 0x99AF, 0xE0E4, 0x99B0, 0xE0E6, 0x99B1, 0xB9A4, 0x99B2, 0xE0E5, 0x99B3, 0xB9A3, 0x99B4, 0xB9A5, + 0x99B5, 0xE0E7, 0x99B9, 0xE4D4, 0x99BA, 0xE4D6, 0x99BB, 0xE4D5, 0x99BD, 0xE4D8, 0x99C1, 0xBBE9, 0x99C2, 0xE4D7, 0x99C3, 0xE4D3, + 0x99C7, 0xE4D9, 0x99C9, 0xE8CC, 0x99CB, 0xE8CF, 0x99CC, 0xE8D1, 0x99CD, 0xE8C7, 0x99CE, 0xE8CB, 0x99CF, 0xE8C8, 0x99D0, 0xBE6E, + 0x99D1, 0xBE71, 0x99D2, 0xBE73, 0x99D3, 0xE8C9, 0x99D4, 0xE8CA, 0x99D5, 0xBE72, 0x99D6, 0xE8CD, 0x99D7, 0xE8D0, 0x99D8, 0xE8CE, + 0x99D9, 0xBE74, 0x99DB, 0xBE70, 0x99DC, 0xE8C6, 0x99DD, 0xBE6D, 0x99DF, 0xBE6F, 0x99E2, 0xC063, 0x99E3, 0xEC66, 0x99E4, 0xEC64, + 0x99E5, 0xEC63, 0x99E7, 0xEC69, 0x99E9, 0xEC68, 0x99EA, 0xEC67, 0x99EC, 0xEC62, 0x99ED, 0xC062, 0x99EE, 0xEC61, 0x99F0, 0xEC65, + 0x99F1, 0xC064, 0x99F4, 0xEF5A, 0x99F6, 0xEF5E, 0x99F7, 0xEF5B, 0x99F8, 0xEF5D, 0x99F9, 0xEF5C, 0x99FA, 0xEF59, 0x99FB, 0xEF5F, + 0x99FC, 0xEF62, 0x99FD, 0xEF60, 0x99FE, 0xEF61, 0x99FF, 0xC240, 0x9A01, 0xC1FE, 0x9A02, 0xEF58, 0x9A03, 0xEF63, 0x9A04, 0xF1B3, + 0x9A05, 0xF1B6, 0x9A06, 0xF1B8, 0x9A07, 0xF1B7, 0x9A09, 0xF1B1, 0x9A0A, 0xF1B5, 0x9A0B, 0xF1B0, 0x9A0D, 0xF1B2, 0x9A0E, 0xC34D, + 0x9A0F, 0xF1AF, 0x9A11, 0xF1B4, 0x9A14, 0xF3C0, 0x9A15, 0xF3B5, 0x9A16, 0xC445, 0x9A19, 0xC446, 0x9A1A, 0xF3B4, 0x9A1B, 0xF3B9, + 0x9A1C, 0xF3BF, 0x9A1D, 0xF3B7, 0x9A1E, 0xF3BE, 0x9A20, 0xF3BB, 0x9A22, 0xF3BA, 0x9A23, 0xF3BD, 0x9A24, 0xF3B8, 0x9A25, 0xF3B6, + 0x9A27, 0xF3BC, 0x9A29, 0xF560, 0x9A2A, 0xF55E, 0x9A2B, 0xC4CA, 0x9A2C, 0xF55D, 0x9A2D, 0xF563, 0x9A2E, 0xF561, 0x9A30, 0xC4CB, + 0x9A31, 0xF55C, 0x9A32, 0xF55A, 0x9A34, 0xF55B, 0x9A35, 0xC4CD, 0x9A36, 0xF55F, 0x9A37, 0xC4CC, 0x9A38, 0xF562, 0x9A39, 0xF678, + 0x9A3A, 0xF67E, 0x9A3D, 0xF679, 0x9A3E, 0xC55B, 0x9A3F, 0xF6A1, 0x9A40, 0xC55A, 0x9A41, 0xF67D, 0x9A42, 0xF67C, 0x9A43, 0xC559, + 0x9A44, 0xF67B, 0x9A45, 0xC558, 0x9A46, 0xF67A, 0x9A48, 0xF77D, 0x9A49, 0xF7A1, 0x9A4A, 0xF77E, 0x9A4C, 0xF77B, 0x9A4D, 0xC5BB, + 0x9A4E, 0xF778, 0x9A4F, 0xF77C, 0x9A50, 0xF7A3, 0x9A52, 0xF7A2, 0x9A53, 0xF779, 0x9A54, 0xF77A, 0x9A55, 0xC5BA, 0x9A56, 0xF852, + 0x9A57, 0xC5E7, 0x9A59, 0xF853, 0x9A5A, 0xC5E5, 0x9A5B, 0xC5E6, 0x9A5E, 0xF8D3, 0x9A5F, 0xC64A, 0x9A60, 0xF976, 0x9A62, 0xC66A, + 0x9A64, 0xF9B3, 0x9A65, 0xC66B, 0x9A66, 0xF9B4, 0x9A67, 0xF9B5, 0x9A68, 0xF9C3, 0x9A69, 0xF9C2, 0x9A6A, 0xC67A, 0x9A6B, 0xF9CD, + 0x9AA8, 0xB0A9, 0x9AAB, 0xE0E9, 0x9AAD, 0xE0E8, 0x9AAF, 0xBBEA, 0x9AB0, 0xBBEB, 0x9AB1, 0xE4DA, 0x9AB3, 0xE8D2, 0x9AB4, 0xEC6C, + 0x9AB7, 0xBE75, 0x9AB8, 0xC065, 0x9AB9, 0xEC6A, 0x9ABB, 0xEC6D, 0x9ABC, 0xC066, 0x9ABE, 0xEF64, 0x9ABF, 0xEC6B, 0x9AC0, 0xF1B9, + 0x9AC1, 0xC34E, 0x9AC2, 0xF3C1, 0x9AC6, 0xF566, 0x9AC7, 0xF564, 0x9ACA, 0xF565, 0x9ACD, 0xF6A2, 0x9ACF, 0xC55C, 0x9AD0, 0xF7A4, + 0x9AD1, 0xC5EA, 0x9AD2, 0xC5BC, 0x9AD3, 0xC5E8, 0x9AD4, 0xC5E9, 0x9AD5, 0xF8D4, 0x9AD6, 0xC662, 0x9AD8, 0xB0AA, 0x9ADC, 0xF1BA, + 0x9ADF, 0xD449, 0x9AE1, 0xB9A6, 0x9AE3, 0xE4DB, 0x9AE6, 0xBBEC, 0x9AE7, 0xE4DC, 0x9AEB, 0xE8D4, 0x9AEC, 0xE8D3, 0x9AED, 0xC068, + 0x9AEE, 0xBE76, 0x9AEF, 0xBE77, 0x9AF1, 0xE8D7, 0x9AF2, 0xE8D6, 0x9AF3, 0xE8D5, 0x9AF6, 0xEC6E, 0x9AF7, 0xEC71, 0x9AF9, 0xEC70, + 0x9AFA, 0xEC6F, 0x9AFB, 0xC067, 0x9AFC, 0xEF68, 0x9AFD, 0xEF66, 0x9AFE, 0xEF65, 0x9B01, 0xEF67, 0x9B03, 0xC34F, 0x9B04, 0xF1BC, + 0x9B05, 0xF1BD, 0x9B06, 0xC350, 0x9B08, 0xF1BB, 0x9B0A, 0xF3C3, 0x9B0B, 0xF3C2, 0x9B0C, 0xF3C5, 0x9B0D, 0xC447, 0x9B0E, 0xF3C4, + 0x9B10, 0xF567, 0x9B11, 0xF569, 0x9B12, 0xF568, 0x9B15, 0xF6A3, 0x9B16, 0xF6A6, 0x9B17, 0xF6A4, 0x9B18, 0xF6A5, 0x9B19, 0xF7A5, + 0x9B1A, 0xC5BD, 0x9B1E, 0xF854, 0x9B1F, 0xF855, 0x9B20, 0xF856, 0x9B22, 0xC64B, 0x9B23, 0xC663, 0x9B24, 0xF9B6, 0x9B25, 0xB0AB, + 0x9B27, 0xBE78, 0x9B28, 0xC069, 0x9B29, 0xF1BE, 0x9B2B, 0xF7A6, 0x9B2E, 0xF9C4, 0x9B2F, 0xD44A, 0x9B31, 0xC67B, 0x9B32, 0xB0AC, + 0x9B33, 0xEC72, 0x9B35, 0xF1BF, 0x9B37, 0xF3C6, 0x9B3A, 0xF6A7, 0x9B3B, 0xF7A7, 0x9B3C, 0xB0AD, 0x9B3E, 0xE4DD, 0x9B3F, 0xE4DE, + 0x9B41, 0xBBED, 0x9B42, 0xBBEE, 0x9B43, 0xE8D9, 0x9B44, 0xBE7A, 0x9B45, 0xBE79, 0x9B46, 0xE8D8, 0x9B48, 0xEF69, 0x9B4A, 0xF1C0, + 0x9B4B, 0xF1C2, 0x9B4C, 0xF1C1, 0x9B4D, 0xC353, 0x9B4E, 0xC352, 0x9B4F, 0xC351, 0x9B51, 0xC55E, 0x9B52, 0xF6A8, 0x9B54, 0xC55D, + 0x9B55, 0xF7A9, 0x9B56, 0xF7A8, 0x9B58, 0xC64C, 0x9B59, 0xF8D5, 0x9B5A, 0xB3BD, 0x9B5B, 0xE0EA, 0x9B5F, 0xE4E1, 0x9B60, 0xE4DF, + 0x9B61, 0xE4E0, 0x9B64, 0xE8E2, 0x9B66, 0xE8DD, 0x9B67, 0xE8DA, 0x9B68, 0xE8E1, 0x9B6C, 0xE8E3, 0x9B6F, 0xBE7C, 0x9B70, 0xE8E0, + 0x9B71, 0xE8DC, 0x9B74, 0xE8DB, 0x9B75, 0xE8DF, 0x9B76, 0xE8DE, 0x9B77, 0xBE7B, 0x9B7A, 0xEC7D, 0x9B7B, 0xEC78, 0x9B7C, 0xEC76, + 0x9B7D, 0xECA1, 0x9B7E, 0xEC77, 0x9B80, 0xEC73, 0x9B82, 0xEC79, 0x9B85, 0xEC74, 0x9B86, 0xEF72, 0x9B87, 0xEC75, 0x9B88, 0xECA2, + 0x9B90, 0xEC7C, 0x9B91, 0xC06A, 0x9B92, 0xEC7B, 0x9B93, 0xEC7A, 0x9B95, 0xEC7E, 0x9B9A, 0xEF6A, 0x9B9B, 0xEF6D, 0x9B9E, 0xEF6C, + 0x9BA0, 0xEF74, 0x9BA1, 0xEF6F, 0x9BA2, 0xEF73, 0x9BA4, 0xEF71, 0x9BA5, 0xEF70, 0x9BA6, 0xEF6E, 0x9BA8, 0xEF6B, 0x9BAA, 0xC243, + 0x9BAB, 0xC242, 0x9BAD, 0xC244, 0x9BAE, 0xC241, 0x9BAF, 0xEF75, 0x9BB5, 0xF1C8, 0x9BB6, 0xF1CB, 0x9BB8, 0xF1C9, 0x9BB9, 0xF1CD, + 0x9BBD, 0xF1CE, 0x9BBF, 0xF1C6, 0x9BC0, 0xC358, 0x9BC1, 0xF1C7, 0x9BC3, 0xF1C5, 0x9BC4, 0xF1CC, 0x9BC6, 0xF1C4, 0x9BC7, 0xF1C3, + 0x9BC8, 0xC357, 0x9BC9, 0xC355, 0x9BCA, 0xC354, 0x9BD3, 0xF1CA, 0x9BD4, 0xF3CF, 0x9BD5, 0xF3D5, 0x9BD6, 0xC44A, 0x9BD7, 0xF3D0, + 0x9BD9, 0xF3D3, 0x9BDA, 0xF3D7, 0x9BDB, 0xC44B, 0x9BDC, 0xF3D2, 0x9BDE, 0xF3CA, 0x9BE0, 0xF3C9, 0x9BE1, 0xF3D6, 0x9BE2, 0xF3CD, + 0x9BE4, 0xF3CB, 0x9BE5, 0xF3D4, 0x9BE6, 0xF3CC, 0x9BE7, 0xC449, 0x9BE8, 0xC448, 0x9BEA, 0xF3C7, 0x9BEB, 0xF3C8, 0x9BEC, 0xF3D1, + 0x9BF0, 0xF3CE, 0x9BF7, 0xF56C, 0x9BF8, 0xF56F, 0x9BFD, 0xC356, 0x9C05, 0xF56D, 0x9C06, 0xF573, 0x9C07, 0xF571, 0x9C08, 0xF56B, + 0x9C09, 0xF576, 0x9C0B, 0xF56A, 0x9C0D, 0xC4CF, 0x9C0E, 0xF572, 0x9C12, 0xF56E, 0x9C13, 0xC4CE, 0x9C14, 0xF575, 0x9C17, 0xF574, + 0x9C1C, 0xF6AB, 0x9C1D, 0xF6AA, 0x9C21, 0xF6B1, 0x9C23, 0xF6AD, 0x9C24, 0xF6B0, 0x9C25, 0xC560, 0x9C28, 0xF6AE, 0x9C29, 0xF6AF, + 0x9C2B, 0xF6A9, 0x9C2C, 0xF6AC, 0x9C2D, 0xC55F, 0x9C31, 0xC5BF, 0x9C32, 0xF7B4, 0x9C33, 0xF7AF, 0x9C34, 0xF7B3, 0x9C36, 0xF7B6, + 0x9C37, 0xF7B2, 0x9C39, 0xF7AE, 0x9C3B, 0xC5C1, 0x9C3C, 0xF7B1, 0x9C3D, 0xF7B5, 0x9C3E, 0xC5C0, 0x9C3F, 0xF7AC, 0x9C40, 0xF570, + 0x9C41, 0xF7B0, 0x9C44, 0xF7AD, 0x9C46, 0xF7AA, 0x9C48, 0xF7AB, 0x9C49, 0xC5BE, 0x9C4A, 0xF85A, 0x9C4B, 0xF85C, 0x9C4C, 0xF85F, + 0x9C4D, 0xF85B, 0x9C4E, 0xF860, 0x9C50, 0xF859, 0x9C52, 0xF857, 0x9C54, 0xC5EB, 0x9C55, 0xF85D, 0x9C56, 0xC5ED, 0x9C57, 0xC5EC, + 0x9C58, 0xF858, 0x9C59, 0xF85E, 0x9C5E, 0xF8DA, 0x9C5F, 0xC64D, 0x9C60, 0xF8DB, 0x9C62, 0xF8D9, 0x9C63, 0xF8D6, 0x9C66, 0xF8D8, + 0x9C67, 0xF8D7, 0x9C68, 0xF95A, 0x9C6D, 0xF95C, 0x9C6E, 0xF95B, 0x9C71, 0xF979, 0x9C73, 0xF978, 0x9C74, 0xF977, 0x9C75, 0xF97A, + 0x9C77, 0xC673, 0x9C78, 0xC674, 0x9C79, 0xF9CA, 0x9C7A, 0xF9CE, 0x9CE5, 0xB3BE, 0x9CE6, 0xDCAF, 0x9CE7, 0xE0ED, 0x9CE9, 0xB9A7, + 0x9CEA, 0xE0EB, 0x9CED, 0xE0EC, 0x9CF1, 0xE4E2, 0x9CF2, 0xE4E3, 0x9CF3, 0xBBF1, 0x9CF4, 0xBBEF, 0x9CF5, 0xE4E4, 0x9CF6, 0xBBF0, + 0x9CF7, 0xE8E8, 0x9CF9, 0xE8EB, 0x9CFA, 0xE8E5, 0x9CFB, 0xE8EC, 0x9CFC, 0xE8E4, 0x9CFD, 0xE8E6, 0x9CFF, 0xE8E7, 0x9D00, 0xE8EA, + 0x9D03, 0xBEA1, 0x9D04, 0xE8EF, 0x9D05, 0xE8EE, 0x9D06, 0xBE7D, 0x9D07, 0xE8E9, 0x9D08, 0xE8ED, 0x9D09, 0xBE7E, 0x9D10, 0xECAC, + 0x9D12, 0xC06F, 0x9D14, 0xECA7, 0x9D15, 0xC06B, 0x9D17, 0xECA4, 0x9D18, 0xECAA, 0x9D19, 0xECAD, 0x9D1B, 0xC070, 0x9D1D, 0xECA9, + 0x9D1E, 0xECA6, 0x9D1F, 0xECAE, 0x9D20, 0xECA5, 0x9D22, 0xECAB, 0x9D23, 0xC06C, 0x9D25, 0xECA3, 0x9D26, 0xC06D, 0x9D28, 0xC06E, + 0x9D29, 0xECA8, 0x9D2D, 0xEFA9, 0x9D2E, 0xEF7A, 0x9D2F, 0xEF7B, 0x9D30, 0xEF7E, 0x9D31, 0xEF7C, 0x9D33, 0xEF76, 0x9D36, 0xEF79, + 0x9D37, 0xEFA5, 0x9D38, 0xEF7D, 0x9D3B, 0xC245, 0x9D3D, 0xEFA7, 0x9D3E, 0xEFA4, 0x9D3F, 0xC246, 0x9D40, 0xEFA6, 0x9D41, 0xEF77, + 0x9D42, 0xEFA2, 0x9D43, 0xEFA3, 0x9D45, 0xEFA1, 0x9D4A, 0xF1D2, 0x9D4B, 0xF1D4, 0x9D4C, 0xF1D7, 0x9D4F, 0xF1D1, 0x9D51, 0xC359, + 0x9D52, 0xF1D9, 0x9D53, 0xF1D0, 0x9D54, 0xF1DA, 0x9D56, 0xF1D6, 0x9D57, 0xF1D8, 0x9D58, 0xF1DC, 0x9D59, 0xF1D5, 0x9D5A, 0xF1DD, + 0x9D5B, 0xF1D3, 0x9D5C, 0xF1CF, 0x9D5D, 0xC35A, 0x9D5F, 0xF1DB, 0x9D60, 0xC35B, 0x9D61, 0xC44D, 0x9D67, 0xEF78, 0x9D68, 0xF3F1, + 0x9D69, 0xF3E8, 0x9D6A, 0xC44F, 0x9D6B, 0xF3E4, 0x9D6C, 0xC450, 0x9D6F, 0xF3ED, 0x9D70, 0xF3E7, 0x9D71, 0xF3DD, 0x9D72, 0xC44E, + 0x9D73, 0xF3EA, 0x9D74, 0xF3E5, 0x9D75, 0xF3E6, 0x9D77, 0xF3D8, 0x9D78, 0xF3DF, 0x9D79, 0xF3EE, 0x9D7B, 0xF3EB, 0x9D7D, 0xF3E3, + 0x9D7F, 0xF3EF, 0x9D80, 0xF3DE, 0x9D81, 0xF3D9, 0x9D82, 0xF3EC, 0x9D84, 0xF3DB, 0x9D85, 0xF3E9, 0x9D86, 0xF3E0, 0x9D87, 0xF3F0, + 0x9D88, 0xF3DC, 0x9D89, 0xC44C, 0x9D8A, 0xF3DA, 0x9D8B, 0xF3E1, 0x9D8C, 0xF3E2, 0x9D90, 0xF57D, 0x9D92, 0xF57B, 0x9D94, 0xF5A2, + 0x9D96, 0xF5AE, 0x9D97, 0xF5A5, 0x9D98, 0xF57C, 0x9D99, 0xF578, 0x9D9A, 0xF5A7, 0x9D9B, 0xF57E, 0x9D9C, 0xF5A3, 0x9D9D, 0xF57A, + 0x9D9E, 0xF5AA, 0x9D9F, 0xF577, 0x9DA0, 0xF5A1, 0x9DA1, 0xF5A6, 0x9DA2, 0xF5A8, 0x9DA3, 0xF5AB, 0x9DA4, 0xF579, 0x9DA6, 0xF5AF, + 0x9DA7, 0xF5B0, 0x9DA8, 0xF5A9, 0x9DA9, 0xF5AD, 0x9DAA, 0xF5A4, 0x9DAC, 0xF6C1, 0x9DAD, 0xF6C4, 0x9DAF, 0xC561, 0x9DB1, 0xF6C3, + 0x9DB2, 0xF6C8, 0x9DB3, 0xF6C6, 0x9DB4, 0xC562, 0x9DB5, 0xF6BD, 0x9DB6, 0xF6B3, 0x9DB7, 0xF6B2, 0x9DB8, 0xC564, 0x9DB9, 0xF6BF, + 0x9DBA, 0xF6C0, 0x9DBB, 0xF6BC, 0x9DBC, 0xF6B4, 0x9DBE, 0xF6B9, 0x9DBF, 0xF5AC, 0x9DC1, 0xF6B5, 0x9DC2, 0xC563, 0x9DC3, 0xF6BB, + 0x9DC5, 0xF6BA, 0x9DC7, 0xF6B6, 0x9DC8, 0xF6C2, 0x9DCA, 0xF6B7, 0x9DCB, 0xF7BB, 0x9DCC, 0xF6C5, 0x9DCD, 0xF6C7, 0x9DCE, 0xF6BE, + 0x9DCF, 0xF6B8, 0x9DD0, 0xF7BC, 0x9DD1, 0xF7BE, 0x9DD2, 0xF7B8, 0x9DD3, 0xC5C2, 0x9DD5, 0xF7C5, 0x9DD6, 0xF7C3, 0x9DD7, 0xC5C3, + 0x9DD8, 0xF7C2, 0x9DD9, 0xF7C1, 0x9DDA, 0xF7BA, 0x9DDB, 0xF7B7, 0x9DDC, 0xF7BD, 0x9DDD, 0xF7C6, 0x9DDE, 0xF7B9, 0x9DDF, 0xF7BF, + 0x9DE1, 0xF869, 0x9DE2, 0xF86E, 0x9DE3, 0xF864, 0x9DE4, 0xF867, 0x9DE5, 0xC5EE, 0x9DE6, 0xF86B, 0x9DE8, 0xF872, 0x9DE9, 0xF7C0, + 0x9DEB, 0xF865, 0x9DEC, 0xF86F, 0x9DED, 0xF873, 0x9DEE, 0xF86A, 0x9DEF, 0xF863, 0x9DF0, 0xF86D, 0x9DF2, 0xF86C, 0x9DF3, 0xF871, + 0x9DF4, 0xF870, 0x9DF5, 0xF7C4, 0x9DF6, 0xF868, 0x9DF7, 0xF862, 0x9DF8, 0xF866, 0x9DF9, 0xC64E, 0x9DFA, 0xC64F, 0x9DFB, 0xF861, + 0x9DFD, 0xF8E6, 0x9DFE, 0xF8DD, 0x9DFF, 0xF8E5, 0x9E00, 0xF8E2, 0x9E01, 0xF8E3, 0x9E02, 0xF8DC, 0x9E03, 0xF8DF, 0x9E04, 0xF8E7, + 0x9E05, 0xF8E1, 0x9E06, 0xF8E0, 0x9E07, 0xF8DE, 0x9E09, 0xF8E4, 0x9E0B, 0xF95D, 0x9E0D, 0xF95E, 0x9E0F, 0xF960, 0x9E10, 0xF95F, + 0x9E11, 0xF962, 0x9E12, 0xF961, 0x9E13, 0xF97C, 0x9E14, 0xF97B, 0x9E15, 0xF9B7, 0x9E17, 0xF9B8, 0x9E19, 0xF9C5, 0x9E1A, 0xC678, + 0x9E1B, 0xC67C, 0x9E1D, 0xF9CF, 0x9E1E, 0xC67D, 0x9E75, 0xB3BF, 0x9E79, 0xC4D0, 0x9E7A, 0xF6C9, 0x9E7C, 0xC650, 0x9E7D, 0xC651, + 0x9E7F, 0xB3C0, 0x9E80, 0xE0EE, 0x9E82, 0xB9A8, 0x9E83, 0xE8F0, 0x9E86, 0xECB0, 0x9E87, 0xECB1, 0x9E88, 0xECAF, 0x9E89, 0xEFAB, + 0x9E8A, 0xEFAA, 0x9E8B, 0xC247, 0x9E8C, 0xF1DF, 0x9E8D, 0xEFAC, 0x9E8E, 0xF1DE, 0x9E91, 0xF3F3, 0x9E92, 0xC451, 0x9E93, 0xC453, + 0x9E94, 0xF3F2, 0x9E97, 0xC452, 0x9E99, 0xF5B1, 0x9E9A, 0xF5B3, 0x9E9B, 0xF5B2, 0x9E9C, 0xF6CA, 0x9E9D, 0xC565, 0x9E9F, 0xC5EF, + 0x9EA0, 0xF8E8, 0x9EA1, 0xF963, 0x9EA4, 0xF9D2, 0x9EA5, 0xB3C1, 0x9EA7, 0xE4E5, 0x9EA9, 0xBEA2, 0x9EAD, 0xECB3, 0x9EAE, 0xECB2, + 0x9EB0, 0xEFAD, 0x9EB4, 0xC454, 0x9EB5, 0xC4D1, 0x9EB6, 0xF7C7, 0x9EB7, 0xF9CB, 0x9EBB, 0xB3C2, 0x9EBC, 0xBBF2, 0x9EBE, 0xBEA3, + 0x9EC0, 0xF3F4, 0x9EC2, 0xF874, 0x9EC3, 0xB6C0, 0x9EC8, 0xEFAE, 0x9ECC, 0xC664, 0x9ECD, 0xB6C1, 0x9ECE, 0xBEA4, 0x9ECF, 0xC248, + 0x9ED0, 0xF875, 0x9ED1, 0xB6C2, 0x9ED3, 0xE8F1, 0x9ED4, 0xC072, 0x9ED5, 0xECB4, 0x9ED6, 0xECB5, 0x9ED8, 0xC071, 0x9EDA, 0xEFAF, + 0x9EDB, 0xC24C, 0x9EDC, 0xC24A, 0x9EDD, 0xC24B, 0x9EDE, 0xC249, 0x9EDF, 0xF1E0, 0x9EE0, 0xC35C, 0x9EE4, 0xF5B5, 0x9EE5, 0xF5B4, + 0x9EE6, 0xF5B7, 0x9EE7, 0xF5B6, 0x9EE8, 0xC4D2, 0x9EEB, 0xF6CB, 0x9EED, 0xF6CD, 0x9EEE, 0xF6CC, 0x9EEF, 0xC566, 0x9EF0, 0xF7C8, + 0x9EF2, 0xF876, 0x9EF3, 0xF877, 0x9EF4, 0xC5F0, 0x9EF5, 0xF964, 0x9EF6, 0xF97D, 0x9EF7, 0xC675, 0x9EF9, 0xDCB0, 0x9EFA, 0xECB6, + 0x9EFB, 0xEFB0, 0x9EFC, 0xF3F5, 0x9EFD, 0xE0EF, 0x9EFF, 0xEFB1, 0x9F00, 0xF1E2, 0x9F01, 0xF1E1, 0x9F06, 0xF878, 0x9F07, 0xC652, + 0x9F09, 0xF965, 0x9F0A, 0xF97E, 0x9F0E, 0xB9A9, 0x9F0F, 0xE8F2, 0x9F10, 0xE8F3, 0x9F12, 0xECB7, 0x9F13, 0xB9AA, 0x9F15, 0xC35D, + 0x9F16, 0xF1E3, 0x9F18, 0xF6CF, 0x9F19, 0xC567, 0x9F1A, 0xF6D0, 0x9F1B, 0xF6CE, 0x9F1C, 0xF879, 0x9F1E, 0xF8E9, 0x9F20, 0xB9AB, + 0x9F22, 0xEFB4, 0x9F23, 0xEFB3, 0x9F24, 0xEFB2, 0x9F25, 0xF1E4, 0x9F28, 0xF1E8, 0x9F29, 0xF1E7, 0x9F2A, 0xF1E6, 0x9F2B, 0xF1E5, + 0x9F2C, 0xC35E, 0x9F2D, 0xF3F6, 0x9F2E, 0xF5B9, 0x9F2F, 0xC4D3, 0x9F30, 0xF5B8, 0x9F31, 0xF6D1, 0x9F32, 0xF7CB, 0x9F33, 0xF7CA, + 0x9F34, 0xC5C4, 0x9F35, 0xF7C9, 0x9F36, 0xF87C, 0x9F37, 0xF87B, 0x9F38, 0xF87A, 0x9F3B, 0xBBF3, 0x9F3D, 0xECB8, 0x9F3E, 0xC24D, + 0x9F40, 0xF3F7, 0x9F41, 0xF3F8, 0x9F42, 0xF7CC, 0x9F43, 0xF87D, 0x9F46, 0xF8EA, 0x9F47, 0xF966, 0x9F48, 0xF9B9, 0x9F49, 0xF9D4, + 0x9F4A, 0xBBF4, 0x9F4B, 0xC24E, 0x9F4C, 0xF1E9, 0x9F4D, 0xF3F9, 0x9F4E, 0xF6D2, 0x9F4F, 0xF87E, 0x9F52, 0xBEA6, 0x9F54, 0xEFB5, + 0x9F55, 0xF1EA, 0x9F56, 0xF3FA, 0x9F57, 0xF3FB, 0x9F58, 0xF3FC, 0x9F59, 0xF5BE, 0x9F5B, 0xF5BA, 0x9F5C, 0xC568, 0x9F5D, 0xF5BD, + 0x9F5E, 0xF5BC, 0x9F5F, 0xC4D4, 0x9F60, 0xF5BB, 0x9F61, 0xC4D6, 0x9F63, 0xC4D5, 0x9F64, 0xF6D4, 0x9F65, 0xF6D3, 0x9F66, 0xC569, + 0x9F67, 0xC56A, 0x9F6A, 0xC5C6, 0x9F6B, 0xF7CD, 0x9F6C, 0xC5C5, 0x9F6E, 0xF8A3, 0x9F6F, 0xF8A4, 0x9F70, 0xF8A2, 0x9F71, 0xF8A1, + 0x9F72, 0xC654, 0x9F74, 0xF8EB, 0x9F75, 0xF8EC, 0x9F76, 0xF8ED, 0x9F77, 0xC653, 0x9F78, 0xF967, 0x9F79, 0xF96A, 0x9F7A, 0xF969, + 0x9F7B, 0xF968, 0x9F7E, 0xF9D3, 0x9F8D, 0xC073, 0x9F90, 0xC365, 0x9F91, 0xF5BF, 0x9F92, 0xF6D5, 0x9F94, 0xC5C7, 0x9F95, 0xF7CE, + 0x9F98, 0xF9D5, 0x9F9C, 0xC074, 0x9FA0, 0xEFB6, 0x9FA2, 0xF7CF, 0x9FA4, 0xF9A1, 0xFA0C, 0xC94A, 0xFA0D, 0xDDFC, 0xFE30, 0xA14A, + 0xFE31, 0xA157, 0xFE33, 0xA159, 0xFE34, 0xA15B, 0xFE35, 0xA15F, 0xFE36, 0xA160, 0xFE37, 0xA163, 0xFE38, 0xA164, 0xFE39, 0xA167, + 0xFE3A, 0xA168, 0xFE3B, 0xA16B, 0xFE3C, 0xA16C, 0xFE3D, 0xA16F, 0xFE3E, 0xA170, 0xFE3F, 0xA173, 0xFE40, 0xA174, 0xFE41, 0xA177, + 0xFE42, 0xA178, 0xFE43, 0xA17B, 0xFE44, 0xA17C, 0xFE49, 0xA1C6, 0xFE4A, 0xA1C7, 0xFE4B, 0xA1CA, 0xFE4C, 0xA1CB, 0xFE4D, 0xA1C8, + 0xFE4E, 0xA1C9, 0xFE4F, 0xA15C, 0xFE50, 0xA14D, 0xFE51, 0xA14E, 0xFE52, 0xA14F, 0xFE54, 0xA151, 0xFE55, 0xA152, 0xFE56, 0xA153, + 0xFE57, 0xA154, 0xFE59, 0xA17D, 0xFE5A, 0xA17E, 0xFE5B, 0xA1A1, 0xFE5C, 0xA1A2, 0xFE5D, 0xA1A3, 0xFE5E, 0xA1A4, 0xFE5F, 0xA1CC, + 0xFE60, 0xA1CD, 0xFE61, 0xA1CE, 0xFE62, 0xA1DE, 0xFE63, 0xA1DF, 0xFE64, 0xA1E0, 0xFE65, 0xA1E1, 0xFE66, 0xA1E2, 0xFE68, 0xA242, + 0xFE69, 0xA24C, 0xFE6A, 0xA24D, 0xFE6B, 0xA24E, 0xFF01, 0xA149, 0xFF03, 0xA1AD, 0xFF04, 0xA243, 0xFF05, 0xA248, 0xFF06, 0xA1AE, + 0xFF08, 0xA15D, 0xFF09, 0xA15E, 0xFF0A, 0xA1AF, 0xFF0B, 0xA1CF, 0xFF0C, 0xA141, 0xFF0D, 0xA1D0, 0xFF0E, 0xA144, 0xFF0F, 0xA1FE, + 0xFF10, 0xA2AF, 0xFF11, 0xA2B0, 0xFF12, 0xA2B1, 0xFF13, 0xA2B2, 0xFF14, 0xA2B3, 0xFF15, 0xA2B4, 0xFF16, 0xA2B5, 0xFF17, 0xA2B6, + 0xFF18, 0xA2B7, 0xFF19, 0xA2B8, 0xFF1A, 0xA147, 0xFF1B, 0xA146, 0xFF1C, 0xA1D5, 0xFF1D, 0xA1D7, 0xFF1E, 0xA1D6, 0xFF1F, 0xA148, + 0xFF20, 0xA249, 0xFF21, 0xA2CF, 0xFF22, 0xA2D0, 0xFF23, 0xA2D1, 0xFF24, 0xA2D2, 0xFF25, 0xA2D3, 0xFF26, 0xA2D4, 0xFF27, 0xA2D5, + 0xFF28, 0xA2D6, 0xFF29, 0xA2D7, 0xFF2A, 0xA2D8, 0xFF2B, 0xA2D9, 0xFF2C, 0xA2DA, 0xFF2D, 0xA2DB, 0xFF2E, 0xA2DC, 0xFF2F, 0xA2DD, + 0xFF30, 0xA2DE, 0xFF31, 0xA2DF, 0xFF32, 0xA2E0, 0xFF33, 0xA2E1, 0xFF34, 0xA2E2, 0xFF35, 0xA2E3, 0xFF36, 0xA2E4, 0xFF37, 0xA2E5, + 0xFF38, 0xA2E6, 0xFF39, 0xA2E7, 0xFF3A, 0xA2E8, 0xFF3C, 0xA240, 0xFF3F, 0xA1C4, 0xFF41, 0xA2E9, 0xFF42, 0xA2EA, 0xFF43, 0xA2EB, + 0xFF44, 0xA2EC, 0xFF45, 0xA2ED, 0xFF46, 0xA2EE, 0xFF47, 0xA2EF, 0xFF48, 0xA2F0, 0xFF49, 0xA2F1, 0xFF4A, 0xA2F2, 0xFF4B, 0xA2F3, + 0xFF4C, 0xA2F4, 0xFF4D, 0xA2F5, 0xFF4E, 0xA2F6, 0xFF4F, 0xA2F7, 0xFF50, 0xA2F8, 0xFF51, 0xA2F9, 0xFF52, 0xA2FA, 0xFF53, 0xA2FB, + 0xFF54, 0xA2FC, 0xFF55, 0xA2FD, 0xFF56, 0xA2FE, 0xFF57, 0xA340, 0xFF58, 0xA341, 0xFF59, 0xA342, 0xFF5A, 0xA343, 0xFF5B, 0xA161, + 0xFF5C, 0xA155, 0xFF5D, 0xA162, 0xFF5E, 0xA1E3, 0xFFE0, 0xA246, 0xFFE1, 0xA247, 0xFFE3, 0xA1C3, 0xFFE5, 0xA244, 0, 0 +}; + +static const WCHAR oem2uni950[] = { /* Big5 --> Unicode pairs */ + 0xA140, 0x3000, 0xA141, 0xFF0C, 0xA142, 0x3001, 0xA143, 0x3002, 0xA144, 0xFF0E, 0xA145, 0x2027, 0xA146, 0xFF1B, 0xA147, 0xFF1A, + 0xA148, 0xFF1F, 0xA149, 0xFF01, 0xA14A, 0xFE30, 0xA14B, 0x2026, 0xA14C, 0x2025, 0xA14D, 0xFE50, 0xA14E, 0xFE51, 0xA14F, 0xFE52, + 0xA150, 0x00B7, 0xA151, 0xFE54, 0xA152, 0xFE55, 0xA153, 0xFE56, 0xA154, 0xFE57, 0xA155, 0xFF5C, 0xA156, 0x2013, 0xA157, 0xFE31, + 0xA158, 0x2014, 0xA159, 0xFE33, 0xA15A, 0x2574, 0xA15B, 0xFE34, 0xA15C, 0xFE4F, 0xA15D, 0xFF08, 0xA15E, 0xFF09, 0xA15F, 0xFE35, + 0xA160, 0xFE36, 0xA161, 0xFF5B, 0xA162, 0xFF5D, 0xA163, 0xFE37, 0xA164, 0xFE38, 0xA165, 0x3014, 0xA166, 0x3015, 0xA167, 0xFE39, + 0xA168, 0xFE3A, 0xA169, 0x3010, 0xA16A, 0x3011, 0xA16B, 0xFE3B, 0xA16C, 0xFE3C, 0xA16D, 0x300A, 0xA16E, 0x300B, 0xA16F, 0xFE3D, + 0xA170, 0xFE3E, 0xA171, 0x3008, 0xA172, 0x3009, 0xA173, 0xFE3F, 0xA174, 0xFE40, 0xA175, 0x300C, 0xA176, 0x300D, 0xA177, 0xFE41, + 0xA178, 0xFE42, 0xA179, 0x300E, 0xA17A, 0x300F, 0xA17B, 0xFE43, 0xA17C, 0xFE44, 0xA17D, 0xFE59, 0xA17E, 0xFE5A, 0xA1A1, 0xFE5B, + 0xA1A2, 0xFE5C, 0xA1A3, 0xFE5D, 0xA1A4, 0xFE5E, 0xA1A5, 0x2018, 0xA1A6, 0x2019, 0xA1A7, 0x201C, 0xA1A8, 0x201D, 0xA1A9, 0x301D, + 0xA1AA, 0x301E, 0xA1AB, 0x2035, 0xA1AC, 0x2032, 0xA1AD, 0xFF03, 0xA1AE, 0xFF06, 0xA1AF, 0xFF0A, 0xA1B0, 0x203B, 0xA1B1, 0x00A7, + 0xA1B2, 0x3003, 0xA1B3, 0x25CB, 0xA1B4, 0x25CF, 0xA1B5, 0x25B3, 0xA1B6, 0x25B2, 0xA1B7, 0x25CE, 0xA1B8, 0x2606, 0xA1B9, 0x2605, + 0xA1BA, 0x25C7, 0xA1BB, 0x25C6, 0xA1BC, 0x25A1, 0xA1BD, 0x25A0, 0xA1BE, 0x25BD, 0xA1BF, 0x25BC, 0xA1C0, 0x32A3, 0xA1C1, 0x2105, + 0xA1C2, 0x00AF, 0xA1C3, 0xFFE3, 0xA1C4, 0xFF3F, 0xA1C5, 0x02CD, 0xA1C6, 0xFE49, 0xA1C7, 0xFE4A, 0xA1C8, 0xFE4D, 0xA1C9, 0xFE4E, + 0xA1CA, 0xFE4B, 0xA1CB, 0xFE4C, 0xA1CC, 0xFE5F, 0xA1CD, 0xFE60, 0xA1CE, 0xFE61, 0xA1CF, 0xFF0B, 0xA1D0, 0xFF0D, 0xA1D1, 0x00D7, + 0xA1D2, 0x00F7, 0xA1D3, 0x00B1, 0xA1D4, 0x221A, 0xA1D5, 0xFF1C, 0xA1D6, 0xFF1E, 0xA1D7, 0xFF1D, 0xA1D8, 0x2266, 0xA1D9, 0x2267, + 0xA1DA, 0x2260, 0xA1DB, 0x221E, 0xA1DC, 0x2252, 0xA1DD, 0x2261, 0xA1DE, 0xFE62, 0xA1DF, 0xFE63, 0xA1E0, 0xFE64, 0xA1E1, 0xFE65, + 0xA1E2, 0xFE66, 0xA1E3, 0xFF5E, 0xA1E4, 0x2229, 0xA1E5, 0x222A, 0xA1E6, 0x22A5, 0xA1E7, 0x2220, 0xA1E8, 0x221F, 0xA1E9, 0x22BF, + 0xA1EA, 0x33D2, 0xA1EB, 0x33D1, 0xA1EC, 0x222B, 0xA1ED, 0x222E, 0xA1EE, 0x2235, 0xA1EF, 0x2234, 0xA1F0, 0x2640, 0xA1F1, 0x2642, + 0xA1F2, 0x2295, 0xA1F3, 0x2299, 0xA1F4, 0x2191, 0xA1F5, 0x2193, 0xA1F6, 0x2190, 0xA1F7, 0x2192, 0xA1F8, 0x2196, 0xA1F9, 0x2197, + 0xA1FA, 0x2199, 0xA1FB, 0x2198, 0xA1FC, 0x2225, 0xA1FD, 0x2223, 0xA1FE, 0xFF0F, 0xA240, 0xFF3C, 0xA241, 0x2215, 0xA242, 0xFE68, + 0xA243, 0xFF04, 0xA244, 0xFFE5, 0xA245, 0x3012, 0xA246, 0xFFE0, 0xA247, 0xFFE1, 0xA248, 0xFF05, 0xA249, 0xFF20, 0xA24A, 0x2103, + 0xA24B, 0x2109, 0xA24C, 0xFE69, 0xA24D, 0xFE6A, 0xA24E, 0xFE6B, 0xA24F, 0x33D5, 0xA250, 0x339C, 0xA251, 0x339D, 0xA252, 0x339E, + 0xA253, 0x33CE, 0xA254, 0x33A1, 0xA255, 0x338E, 0xA256, 0x338F, 0xA257, 0x33C4, 0xA258, 0x00B0, 0xA259, 0x5159, 0xA25A, 0x515B, + 0xA25B, 0x515E, 0xA25C, 0x515D, 0xA25D, 0x5161, 0xA25E, 0x5163, 0xA25F, 0x55E7, 0xA260, 0x74E9, 0xA261, 0x7CCE, 0xA262, 0x2581, + 0xA263, 0x2582, 0xA264, 0x2583, 0xA265, 0x2584, 0xA266, 0x2585, 0xA267, 0x2586, 0xA268, 0x2587, 0xA269, 0x2588, 0xA26A, 0x258F, + 0xA26B, 0x258E, 0xA26C, 0x258D, 0xA26D, 0x258C, 0xA26E, 0x258B, 0xA26F, 0x258A, 0xA270, 0x2589, 0xA271, 0x253C, 0xA272, 0x2534, + 0xA273, 0x252C, 0xA274, 0x2524, 0xA275, 0x251C, 0xA276, 0x2594, 0xA277, 0x2500, 0xA278, 0x2502, 0xA279, 0x2595, 0xA27A, 0x250C, + 0xA27B, 0x2510, 0xA27C, 0x2514, 0xA27D, 0x2518, 0xA27E, 0x256D, 0xA2A1, 0x256E, 0xA2A2, 0x2570, 0xA2A3, 0x256F, 0xA2A4, 0x2550, + 0xA2A5, 0x255E, 0xA2A6, 0x256A, 0xA2A7, 0x2561, 0xA2A8, 0x25E2, 0xA2A9, 0x25E3, 0xA2AA, 0x25E5, 0xA2AB, 0x25E4, 0xA2AC, 0x2571, + 0xA2AD, 0x2572, 0xA2AE, 0x2573, 0xA2AF, 0xFF10, 0xA2B0, 0xFF11, 0xA2B1, 0xFF12, 0xA2B2, 0xFF13, 0xA2B3, 0xFF14, 0xA2B4, 0xFF15, + 0xA2B5, 0xFF16, 0xA2B6, 0xFF17, 0xA2B7, 0xFF18, 0xA2B8, 0xFF19, 0xA2B9, 0x2160, 0xA2BA, 0x2161, 0xA2BB, 0x2162, 0xA2BC, 0x2163, + 0xA2BD, 0x2164, 0xA2BE, 0x2165, 0xA2BF, 0x2166, 0xA2C0, 0x2167, 0xA2C1, 0x2168, 0xA2C2, 0x2169, 0xA2C3, 0x3021, 0xA2C4, 0x3022, + 0xA2C5, 0x3023, 0xA2C6, 0x3024, 0xA2C7, 0x3025, 0xA2C8, 0x3026, 0xA2C9, 0x3027, 0xA2CA, 0x3028, 0xA2CB, 0x3029, 0xA2CC, 0x5341, + 0xA2CD, 0x5344, 0xA2CE, 0x5345, 0xA2CF, 0xFF21, 0xA2D0, 0xFF22, 0xA2D1, 0xFF23, 0xA2D2, 0xFF24, 0xA2D3, 0xFF25, 0xA2D4, 0xFF26, + 0xA2D5, 0xFF27, 0xA2D6, 0xFF28, 0xA2D7, 0xFF29, 0xA2D8, 0xFF2A, 0xA2D9, 0xFF2B, 0xA2DA, 0xFF2C, 0xA2DB, 0xFF2D, 0xA2DC, 0xFF2E, + 0xA2DD, 0xFF2F, 0xA2DE, 0xFF30, 0xA2DF, 0xFF31, 0xA2E0, 0xFF32, 0xA2E1, 0xFF33, 0xA2E2, 0xFF34, 0xA2E3, 0xFF35, 0xA2E4, 0xFF36, + 0xA2E5, 0xFF37, 0xA2E6, 0xFF38, 0xA2E7, 0xFF39, 0xA2E8, 0xFF3A, 0xA2E9, 0xFF41, 0xA2EA, 0xFF42, 0xA2EB, 0xFF43, 0xA2EC, 0xFF44, + 0xA2ED, 0xFF45, 0xA2EE, 0xFF46, 0xA2EF, 0xFF47, 0xA2F0, 0xFF48, 0xA2F1, 0xFF49, 0xA2F2, 0xFF4A, 0xA2F3, 0xFF4B, 0xA2F4, 0xFF4C, + 0xA2F5, 0xFF4D, 0xA2F6, 0xFF4E, 0xA2F7, 0xFF4F, 0xA2F8, 0xFF50, 0xA2F9, 0xFF51, 0xA2FA, 0xFF52, 0xA2FB, 0xFF53, 0xA2FC, 0xFF54, + 0xA2FD, 0xFF55, 0xA2FE, 0xFF56, 0xA340, 0xFF57, 0xA341, 0xFF58, 0xA342, 0xFF59, 0xA343, 0xFF5A, 0xA344, 0x0391, 0xA345, 0x0392, + 0xA346, 0x0393, 0xA347, 0x0394, 0xA348, 0x0395, 0xA349, 0x0396, 0xA34A, 0x0397, 0xA34B, 0x0398, 0xA34C, 0x0399, 0xA34D, 0x039A, + 0xA34E, 0x039B, 0xA34F, 0x039C, 0xA350, 0x039D, 0xA351, 0x039E, 0xA352, 0x039F, 0xA353, 0x03A0, 0xA354, 0x03A1, 0xA355, 0x03A3, + 0xA356, 0x03A4, 0xA357, 0x03A5, 0xA358, 0x03A6, 0xA359, 0x03A7, 0xA35A, 0x03A8, 0xA35B, 0x03A9, 0xA35C, 0x03B1, 0xA35D, 0x03B2, + 0xA35E, 0x03B3, 0xA35F, 0x03B4, 0xA360, 0x03B5, 0xA361, 0x03B6, 0xA362, 0x03B7, 0xA363, 0x03B8, 0xA364, 0x03B9, 0xA365, 0x03BA, + 0xA366, 0x03BB, 0xA367, 0x03BC, 0xA368, 0x03BD, 0xA369, 0x03BE, 0xA36A, 0x03BF, 0xA36B, 0x03C0, 0xA36C, 0x03C1, 0xA36D, 0x03C3, + 0xA36E, 0x03C4, 0xA36F, 0x03C5, 0xA370, 0x03C6, 0xA371, 0x03C7, 0xA372, 0x03C8, 0xA373, 0x03C9, 0xA374, 0x3105, 0xA375, 0x3106, + 0xA376, 0x3107, 0xA377, 0x3108, 0xA378, 0x3109, 0xA379, 0x310A, 0xA37A, 0x310B, 0xA37B, 0x310C, 0xA37C, 0x310D, 0xA37D, 0x310E, + 0xA37E, 0x310F, 0xA3A1, 0x3110, 0xA3A2, 0x3111, 0xA3A3, 0x3112, 0xA3A4, 0x3113, 0xA3A5, 0x3114, 0xA3A6, 0x3115, 0xA3A7, 0x3116, + 0xA3A8, 0x3117, 0xA3A9, 0x3118, 0xA3AA, 0x3119, 0xA3AB, 0x311A, 0xA3AC, 0x311B, 0xA3AD, 0x311C, 0xA3AE, 0x311D, 0xA3AF, 0x311E, + 0xA3B0, 0x311F, 0xA3B1, 0x3120, 0xA3B2, 0x3121, 0xA3B3, 0x3122, 0xA3B4, 0x3123, 0xA3B5, 0x3124, 0xA3B6, 0x3125, 0xA3B7, 0x3126, + 0xA3B8, 0x3127, 0xA3B9, 0x3128, 0xA3BA, 0x3129, 0xA3BB, 0x02D9, 0xA3BC, 0x02C9, 0xA3BD, 0x02CA, 0xA3BE, 0x02C7, 0xA3BF, 0x02CB, + 0xA3E1, 0x20AC, 0xA440, 0x4E00, 0xA441, 0x4E59, 0xA442, 0x4E01, 0xA443, 0x4E03, 0xA444, 0x4E43, 0xA445, 0x4E5D, 0xA446, 0x4E86, + 0xA447, 0x4E8C, 0xA448, 0x4EBA, 0xA449, 0x513F, 0xA44A, 0x5165, 0xA44B, 0x516B, 0xA44C, 0x51E0, 0xA44D, 0x5200, 0xA44E, 0x5201, + 0xA44F, 0x529B, 0xA450, 0x5315, 0xA451, 0x5341, 0xA452, 0x535C, 0xA453, 0x53C8, 0xA454, 0x4E09, 0xA455, 0x4E0B, 0xA456, 0x4E08, + 0xA457, 0x4E0A, 0xA458, 0x4E2B, 0xA459, 0x4E38, 0xA45A, 0x51E1, 0xA45B, 0x4E45, 0xA45C, 0x4E48, 0xA45D, 0x4E5F, 0xA45E, 0x4E5E, + 0xA45F, 0x4E8E, 0xA460, 0x4EA1, 0xA461, 0x5140, 0xA462, 0x5203, 0xA463, 0x52FA, 0xA464, 0x5343, 0xA465, 0x53C9, 0xA466, 0x53E3, + 0xA467, 0x571F, 0xA468, 0x58EB, 0xA469, 0x5915, 0xA46A, 0x5927, 0xA46B, 0x5973, 0xA46C, 0x5B50, 0xA46D, 0x5B51, 0xA46E, 0x5B53, + 0xA46F, 0x5BF8, 0xA470, 0x5C0F, 0xA471, 0x5C22, 0xA472, 0x5C38, 0xA473, 0x5C71, 0xA474, 0x5DDD, 0xA475, 0x5DE5, 0xA476, 0x5DF1, + 0xA477, 0x5DF2, 0xA478, 0x5DF3, 0xA479, 0x5DFE, 0xA47A, 0x5E72, 0xA47B, 0x5EFE, 0xA47C, 0x5F0B, 0xA47D, 0x5F13, 0xA47E, 0x624D, + 0xA4A1, 0x4E11, 0xA4A2, 0x4E10, 0xA4A3, 0x4E0D, 0xA4A4, 0x4E2D, 0xA4A5, 0x4E30, 0xA4A6, 0x4E39, 0xA4A7, 0x4E4B, 0xA4A8, 0x5C39, + 0xA4A9, 0x4E88, 0xA4AA, 0x4E91, 0xA4AB, 0x4E95, 0xA4AC, 0x4E92, 0xA4AD, 0x4E94, 0xA4AE, 0x4EA2, 0xA4AF, 0x4EC1, 0xA4B0, 0x4EC0, + 0xA4B1, 0x4EC3, 0xA4B2, 0x4EC6, 0xA4B3, 0x4EC7, 0xA4B4, 0x4ECD, 0xA4B5, 0x4ECA, 0xA4B6, 0x4ECB, 0xA4B7, 0x4EC4, 0xA4B8, 0x5143, + 0xA4B9, 0x5141, 0xA4BA, 0x5167, 0xA4BB, 0x516D, 0xA4BC, 0x516E, 0xA4BD, 0x516C, 0xA4BE, 0x5197, 0xA4BF, 0x51F6, 0xA4C0, 0x5206, + 0xA4C1, 0x5207, 0xA4C2, 0x5208, 0xA4C3, 0x52FB, 0xA4C4, 0x52FE, 0xA4C5, 0x52FF, 0xA4C6, 0x5316, 0xA4C7, 0x5339, 0xA4C8, 0x5348, + 0xA4C9, 0x5347, 0xA4CA, 0x5345, 0xA4CB, 0x535E, 0xA4CC, 0x5384, 0xA4CD, 0x53CB, 0xA4CE, 0x53CA, 0xA4CF, 0x53CD, 0xA4D0, 0x58EC, + 0xA4D1, 0x5929, 0xA4D2, 0x592B, 0xA4D3, 0x592A, 0xA4D4, 0x592D, 0xA4D5, 0x5B54, 0xA4D6, 0x5C11, 0xA4D7, 0x5C24, 0xA4D8, 0x5C3A, + 0xA4D9, 0x5C6F, 0xA4DA, 0x5DF4, 0xA4DB, 0x5E7B, 0xA4DC, 0x5EFF, 0xA4DD, 0x5F14, 0xA4DE, 0x5F15, 0xA4DF, 0x5FC3, 0xA4E0, 0x6208, + 0xA4E1, 0x6236, 0xA4E2, 0x624B, 0xA4E3, 0x624E, 0xA4E4, 0x652F, 0xA4E5, 0x6587, 0xA4E6, 0x6597, 0xA4E7, 0x65A4, 0xA4E8, 0x65B9, + 0xA4E9, 0x65E5, 0xA4EA, 0x66F0, 0xA4EB, 0x6708, 0xA4EC, 0x6728, 0xA4ED, 0x6B20, 0xA4EE, 0x6B62, 0xA4EF, 0x6B79, 0xA4F0, 0x6BCB, + 0xA4F1, 0x6BD4, 0xA4F2, 0x6BDB, 0xA4F3, 0x6C0F, 0xA4F4, 0x6C34, 0xA4F5, 0x706B, 0xA4F6, 0x722A, 0xA4F7, 0x7236, 0xA4F8, 0x723B, + 0xA4F9, 0x7247, 0xA4FA, 0x7259, 0xA4FB, 0x725B, 0xA4FC, 0x72AC, 0xA4FD, 0x738B, 0xA4FE, 0x4E19, 0xA540, 0x4E16, 0xA541, 0x4E15, + 0xA542, 0x4E14, 0xA543, 0x4E18, 0xA544, 0x4E3B, 0xA545, 0x4E4D, 0xA546, 0x4E4F, 0xA547, 0x4E4E, 0xA548, 0x4EE5, 0xA549, 0x4ED8, + 0xA54A, 0x4ED4, 0xA54B, 0x4ED5, 0xA54C, 0x4ED6, 0xA54D, 0x4ED7, 0xA54E, 0x4EE3, 0xA54F, 0x4EE4, 0xA550, 0x4ED9, 0xA551, 0x4EDE, + 0xA552, 0x5145, 0xA553, 0x5144, 0xA554, 0x5189, 0xA555, 0x518A, 0xA556, 0x51AC, 0xA557, 0x51F9, 0xA558, 0x51FA, 0xA559, 0x51F8, + 0xA55A, 0x520A, 0xA55B, 0x52A0, 0xA55C, 0x529F, 0xA55D, 0x5305, 0xA55E, 0x5306, 0xA55F, 0x5317, 0xA560, 0x531D, 0xA561, 0x4EDF, + 0xA562, 0x534A, 0xA563, 0x5349, 0xA564, 0x5361, 0xA565, 0x5360, 0xA566, 0x536F, 0xA567, 0x536E, 0xA568, 0x53BB, 0xA569, 0x53EF, + 0xA56A, 0x53E4, 0xA56B, 0x53F3, 0xA56C, 0x53EC, 0xA56D, 0x53EE, 0xA56E, 0x53E9, 0xA56F, 0x53E8, 0xA570, 0x53FC, 0xA571, 0x53F8, + 0xA572, 0x53F5, 0xA573, 0x53EB, 0xA574, 0x53E6, 0xA575, 0x53EA, 0xA576, 0x53F2, 0xA577, 0x53F1, 0xA578, 0x53F0, 0xA579, 0x53E5, + 0xA57A, 0x53ED, 0xA57B, 0x53FB, 0xA57C, 0x56DB, 0xA57D, 0x56DA, 0xA57E, 0x5916, 0xA5A1, 0x592E, 0xA5A2, 0x5931, 0xA5A3, 0x5974, + 0xA5A4, 0x5976, 0xA5A5, 0x5B55, 0xA5A6, 0x5B83, 0xA5A7, 0x5C3C, 0xA5A8, 0x5DE8, 0xA5A9, 0x5DE7, 0xA5AA, 0x5DE6, 0xA5AB, 0x5E02, + 0xA5AC, 0x5E03, 0xA5AD, 0x5E73, 0xA5AE, 0x5E7C, 0xA5AF, 0x5F01, 0xA5B0, 0x5F18, 0xA5B1, 0x5F17, 0xA5B2, 0x5FC5, 0xA5B3, 0x620A, + 0xA5B4, 0x6253, 0xA5B5, 0x6254, 0xA5B6, 0x6252, 0xA5B7, 0x6251, 0xA5B8, 0x65A5, 0xA5B9, 0x65E6, 0xA5BA, 0x672E, 0xA5BB, 0x672C, + 0xA5BC, 0x672A, 0xA5BD, 0x672B, 0xA5BE, 0x672D, 0xA5BF, 0x6B63, 0xA5C0, 0x6BCD, 0xA5C1, 0x6C11, 0xA5C2, 0x6C10, 0xA5C3, 0x6C38, + 0xA5C4, 0x6C41, 0xA5C5, 0x6C40, 0xA5C6, 0x6C3E, 0xA5C7, 0x72AF, 0xA5C8, 0x7384, 0xA5C9, 0x7389, 0xA5CA, 0x74DC, 0xA5CB, 0x74E6, + 0xA5CC, 0x7518, 0xA5CD, 0x751F, 0xA5CE, 0x7528, 0xA5CF, 0x7529, 0xA5D0, 0x7530, 0xA5D1, 0x7531, 0xA5D2, 0x7532, 0xA5D3, 0x7533, + 0xA5D4, 0x758B, 0xA5D5, 0x767D, 0xA5D6, 0x76AE, 0xA5D7, 0x76BF, 0xA5D8, 0x76EE, 0xA5D9, 0x77DB, 0xA5DA, 0x77E2, 0xA5DB, 0x77F3, + 0xA5DC, 0x793A, 0xA5DD, 0x79BE, 0xA5DE, 0x7A74, 0xA5DF, 0x7ACB, 0xA5E0, 0x4E1E, 0xA5E1, 0x4E1F, 0xA5E2, 0x4E52, 0xA5E3, 0x4E53, + 0xA5E4, 0x4E69, 0xA5E5, 0x4E99, 0xA5E6, 0x4EA4, 0xA5E7, 0x4EA6, 0xA5E8, 0x4EA5, 0xA5E9, 0x4EFF, 0xA5EA, 0x4F09, 0xA5EB, 0x4F19, + 0xA5EC, 0x4F0A, 0xA5ED, 0x4F15, 0xA5EE, 0x4F0D, 0xA5EF, 0x4F10, 0xA5F0, 0x4F11, 0xA5F1, 0x4F0F, 0xA5F2, 0x4EF2, 0xA5F3, 0x4EF6, + 0xA5F4, 0x4EFB, 0xA5F5, 0x4EF0, 0xA5F6, 0x4EF3, 0xA5F7, 0x4EFD, 0xA5F8, 0x4F01, 0xA5F9, 0x4F0B, 0xA5FA, 0x5149, 0xA5FB, 0x5147, + 0xA5FC, 0x5146, 0xA5FD, 0x5148, 0xA5FE, 0x5168, 0xA640, 0x5171, 0xA641, 0x518D, 0xA642, 0x51B0, 0xA643, 0x5217, 0xA644, 0x5211, + 0xA645, 0x5212, 0xA646, 0x520E, 0xA647, 0x5216, 0xA648, 0x52A3, 0xA649, 0x5308, 0xA64A, 0x5321, 0xA64B, 0x5320, 0xA64C, 0x5370, + 0xA64D, 0x5371, 0xA64E, 0x5409, 0xA64F, 0x540F, 0xA650, 0x540C, 0xA651, 0x540A, 0xA652, 0x5410, 0xA653, 0x5401, 0xA654, 0x540B, + 0xA655, 0x5404, 0xA656, 0x5411, 0xA657, 0x540D, 0xA658, 0x5408, 0xA659, 0x5403, 0xA65A, 0x540E, 0xA65B, 0x5406, 0xA65C, 0x5412, + 0xA65D, 0x56E0, 0xA65E, 0x56DE, 0xA65F, 0x56DD, 0xA660, 0x5733, 0xA661, 0x5730, 0xA662, 0x5728, 0xA663, 0x572D, 0xA664, 0x572C, + 0xA665, 0x572F, 0xA666, 0x5729, 0xA667, 0x5919, 0xA668, 0x591A, 0xA669, 0x5937, 0xA66A, 0x5938, 0xA66B, 0x5984, 0xA66C, 0x5978, + 0xA66D, 0x5983, 0xA66E, 0x597D, 0xA66F, 0x5979, 0xA670, 0x5982, 0xA671, 0x5981, 0xA672, 0x5B57, 0xA673, 0x5B58, 0xA674, 0x5B87, + 0xA675, 0x5B88, 0xA676, 0x5B85, 0xA677, 0x5B89, 0xA678, 0x5BFA, 0xA679, 0x5C16, 0xA67A, 0x5C79, 0xA67B, 0x5DDE, 0xA67C, 0x5E06, + 0xA67D, 0x5E76, 0xA67E, 0x5E74, 0xA6A1, 0x5F0F, 0xA6A2, 0x5F1B, 0xA6A3, 0x5FD9, 0xA6A4, 0x5FD6, 0xA6A5, 0x620E, 0xA6A6, 0x620C, + 0xA6A7, 0x620D, 0xA6A8, 0x6210, 0xA6A9, 0x6263, 0xA6AA, 0x625B, 0xA6AB, 0x6258, 0xA6AC, 0x6536, 0xA6AD, 0x65E9, 0xA6AE, 0x65E8, + 0xA6AF, 0x65EC, 0xA6B0, 0x65ED, 0xA6B1, 0x66F2, 0xA6B2, 0x66F3, 0xA6B3, 0x6709, 0xA6B4, 0x673D, 0xA6B5, 0x6734, 0xA6B6, 0x6731, + 0xA6B7, 0x6735, 0xA6B8, 0x6B21, 0xA6B9, 0x6B64, 0xA6BA, 0x6B7B, 0xA6BB, 0x6C16, 0xA6BC, 0x6C5D, 0xA6BD, 0x6C57, 0xA6BE, 0x6C59, + 0xA6BF, 0x6C5F, 0xA6C0, 0x6C60, 0xA6C1, 0x6C50, 0xA6C2, 0x6C55, 0xA6C3, 0x6C61, 0xA6C4, 0x6C5B, 0xA6C5, 0x6C4D, 0xA6C6, 0x6C4E, + 0xA6C7, 0x7070, 0xA6C8, 0x725F, 0xA6C9, 0x725D, 0xA6CA, 0x767E, 0xA6CB, 0x7AF9, 0xA6CC, 0x7C73, 0xA6CD, 0x7CF8, 0xA6CE, 0x7F36, + 0xA6CF, 0x7F8A, 0xA6D0, 0x7FBD, 0xA6D1, 0x8001, 0xA6D2, 0x8003, 0xA6D3, 0x800C, 0xA6D4, 0x8012, 0xA6D5, 0x8033, 0xA6D6, 0x807F, + 0xA6D7, 0x8089, 0xA6D8, 0x808B, 0xA6D9, 0x808C, 0xA6DA, 0x81E3, 0xA6DB, 0x81EA, 0xA6DC, 0x81F3, 0xA6DD, 0x81FC, 0xA6DE, 0x820C, + 0xA6DF, 0x821B, 0xA6E0, 0x821F, 0xA6E1, 0x826E, 0xA6E2, 0x8272, 0xA6E3, 0x827E, 0xA6E4, 0x866B, 0xA6E5, 0x8840, 0xA6E6, 0x884C, + 0xA6E7, 0x8863, 0xA6E8, 0x897F, 0xA6E9, 0x9621, 0xA6EA, 0x4E32, 0xA6EB, 0x4EA8, 0xA6EC, 0x4F4D, 0xA6ED, 0x4F4F, 0xA6EE, 0x4F47, + 0xA6EF, 0x4F57, 0xA6F0, 0x4F5E, 0xA6F1, 0x4F34, 0xA6F2, 0x4F5B, 0xA6F3, 0x4F55, 0xA6F4, 0x4F30, 0xA6F5, 0x4F50, 0xA6F6, 0x4F51, + 0xA6F7, 0x4F3D, 0xA6F8, 0x4F3A, 0xA6F9, 0x4F38, 0xA6FA, 0x4F43, 0xA6FB, 0x4F54, 0xA6FC, 0x4F3C, 0xA6FD, 0x4F46, 0xA6FE, 0x4F63, + 0xA740, 0x4F5C, 0xA741, 0x4F60, 0xA742, 0x4F2F, 0xA743, 0x4F4E, 0xA744, 0x4F36, 0xA745, 0x4F59, 0xA746, 0x4F5D, 0xA747, 0x4F48, + 0xA748, 0x4F5A, 0xA749, 0x514C, 0xA74A, 0x514B, 0xA74B, 0x514D, 0xA74C, 0x5175, 0xA74D, 0x51B6, 0xA74E, 0x51B7, 0xA74F, 0x5225, + 0xA750, 0x5224, 0xA751, 0x5229, 0xA752, 0x522A, 0xA753, 0x5228, 0xA754, 0x52AB, 0xA755, 0x52A9, 0xA756, 0x52AA, 0xA757, 0x52AC, + 0xA758, 0x5323, 0xA759, 0x5373, 0xA75A, 0x5375, 0xA75B, 0x541D, 0xA75C, 0x542D, 0xA75D, 0x541E, 0xA75E, 0x543E, 0xA75F, 0x5426, + 0xA760, 0x544E, 0xA761, 0x5427, 0xA762, 0x5446, 0xA763, 0x5443, 0xA764, 0x5433, 0xA765, 0x5448, 0xA766, 0x5442, 0xA767, 0x541B, + 0xA768, 0x5429, 0xA769, 0x544A, 0xA76A, 0x5439, 0xA76B, 0x543B, 0xA76C, 0x5438, 0xA76D, 0x542E, 0xA76E, 0x5435, 0xA76F, 0x5436, + 0xA770, 0x5420, 0xA771, 0x543C, 0xA772, 0x5440, 0xA773, 0x5431, 0xA774, 0x542B, 0xA775, 0x541F, 0xA776, 0x542C, 0xA777, 0x56EA, + 0xA778, 0x56F0, 0xA779, 0x56E4, 0xA77A, 0x56EB, 0xA77B, 0x574A, 0xA77C, 0x5751, 0xA77D, 0x5740, 0xA77E, 0x574D, 0xA7A1, 0x5747, + 0xA7A2, 0x574E, 0xA7A3, 0x573E, 0xA7A4, 0x5750, 0xA7A5, 0x574F, 0xA7A6, 0x573B, 0xA7A7, 0x58EF, 0xA7A8, 0x593E, 0xA7A9, 0x599D, + 0xA7AA, 0x5992, 0xA7AB, 0x59A8, 0xA7AC, 0x599E, 0xA7AD, 0x59A3, 0xA7AE, 0x5999, 0xA7AF, 0x5996, 0xA7B0, 0x598D, 0xA7B1, 0x59A4, + 0xA7B2, 0x5993, 0xA7B3, 0x598A, 0xA7B4, 0x59A5, 0xA7B5, 0x5B5D, 0xA7B6, 0x5B5C, 0xA7B7, 0x5B5A, 0xA7B8, 0x5B5B, 0xA7B9, 0x5B8C, + 0xA7BA, 0x5B8B, 0xA7BB, 0x5B8F, 0xA7BC, 0x5C2C, 0xA7BD, 0x5C40, 0xA7BE, 0x5C41, 0xA7BF, 0x5C3F, 0xA7C0, 0x5C3E, 0xA7C1, 0x5C90, + 0xA7C2, 0x5C91, 0xA7C3, 0x5C94, 0xA7C4, 0x5C8C, 0xA7C5, 0x5DEB, 0xA7C6, 0x5E0C, 0xA7C7, 0x5E8F, 0xA7C8, 0x5E87, 0xA7C9, 0x5E8A, + 0xA7CA, 0x5EF7, 0xA7CB, 0x5F04, 0xA7CC, 0x5F1F, 0xA7CD, 0x5F64, 0xA7CE, 0x5F62, 0xA7CF, 0x5F77, 0xA7D0, 0x5F79, 0xA7D1, 0x5FD8, + 0xA7D2, 0x5FCC, 0xA7D3, 0x5FD7, 0xA7D4, 0x5FCD, 0xA7D5, 0x5FF1, 0xA7D6, 0x5FEB, 0xA7D7, 0x5FF8, 0xA7D8, 0x5FEA, 0xA7D9, 0x6212, + 0xA7DA, 0x6211, 0xA7DB, 0x6284, 0xA7DC, 0x6297, 0xA7DD, 0x6296, 0xA7DE, 0x6280, 0xA7DF, 0x6276, 0xA7E0, 0x6289, 0xA7E1, 0x626D, + 0xA7E2, 0x628A, 0xA7E3, 0x627C, 0xA7E4, 0x627E, 0xA7E5, 0x6279, 0xA7E6, 0x6273, 0xA7E7, 0x6292, 0xA7E8, 0x626F, 0xA7E9, 0x6298, + 0xA7EA, 0x626E, 0xA7EB, 0x6295, 0xA7EC, 0x6293, 0xA7ED, 0x6291, 0xA7EE, 0x6286, 0xA7EF, 0x6539, 0xA7F0, 0x653B, 0xA7F1, 0x6538, + 0xA7F2, 0x65F1, 0xA7F3, 0x66F4, 0xA7F4, 0x675F, 0xA7F5, 0x674E, 0xA7F6, 0x674F, 0xA7F7, 0x6750, 0xA7F8, 0x6751, 0xA7F9, 0x675C, + 0xA7FA, 0x6756, 0xA7FB, 0x675E, 0xA7FC, 0x6749, 0xA7FD, 0x6746, 0xA7FE, 0x6760, 0xA840, 0x6753, 0xA841, 0x6757, 0xA842, 0x6B65, + 0xA843, 0x6BCF, 0xA844, 0x6C42, 0xA845, 0x6C5E, 0xA846, 0x6C99, 0xA847, 0x6C81, 0xA848, 0x6C88, 0xA849, 0x6C89, 0xA84A, 0x6C85, + 0xA84B, 0x6C9B, 0xA84C, 0x6C6A, 0xA84D, 0x6C7A, 0xA84E, 0x6C90, 0xA84F, 0x6C70, 0xA850, 0x6C8C, 0xA851, 0x6C68, 0xA852, 0x6C96, + 0xA853, 0x6C92, 0xA854, 0x6C7D, 0xA855, 0x6C83, 0xA856, 0x6C72, 0xA857, 0x6C7E, 0xA858, 0x6C74, 0xA859, 0x6C86, 0xA85A, 0x6C76, + 0xA85B, 0x6C8D, 0xA85C, 0x6C94, 0xA85D, 0x6C98, 0xA85E, 0x6C82, 0xA85F, 0x7076, 0xA860, 0x707C, 0xA861, 0x707D, 0xA862, 0x7078, + 0xA863, 0x7262, 0xA864, 0x7261, 0xA865, 0x7260, 0xA866, 0x72C4, 0xA867, 0x72C2, 0xA868, 0x7396, 0xA869, 0x752C, 0xA86A, 0x752B, + 0xA86B, 0x7537, 0xA86C, 0x7538, 0xA86D, 0x7682, 0xA86E, 0x76EF, 0xA86F, 0x77E3, 0xA870, 0x79C1, 0xA871, 0x79C0, 0xA872, 0x79BF, + 0xA873, 0x7A76, 0xA874, 0x7CFB, 0xA875, 0x7F55, 0xA876, 0x8096, 0xA877, 0x8093, 0xA878, 0x809D, 0xA879, 0x8098, 0xA87A, 0x809B, + 0xA87B, 0x809A, 0xA87C, 0x80B2, 0xA87D, 0x826F, 0xA87E, 0x8292, 0xA8A1, 0x828B, 0xA8A2, 0x828D, 0xA8A3, 0x898B, 0xA8A4, 0x89D2, + 0xA8A5, 0x8A00, 0xA8A6, 0x8C37, 0xA8A7, 0x8C46, 0xA8A8, 0x8C55, 0xA8A9, 0x8C9D, 0xA8AA, 0x8D64, 0xA8AB, 0x8D70, 0xA8AC, 0x8DB3, + 0xA8AD, 0x8EAB, 0xA8AE, 0x8ECA, 0xA8AF, 0x8F9B, 0xA8B0, 0x8FB0, 0xA8B1, 0x8FC2, 0xA8B2, 0x8FC6, 0xA8B3, 0x8FC5, 0xA8B4, 0x8FC4, + 0xA8B5, 0x5DE1, 0xA8B6, 0x9091, 0xA8B7, 0x90A2, 0xA8B8, 0x90AA, 0xA8B9, 0x90A6, 0xA8BA, 0x90A3, 0xA8BB, 0x9149, 0xA8BC, 0x91C6, + 0xA8BD, 0x91CC, 0xA8BE, 0x9632, 0xA8BF, 0x962E, 0xA8C0, 0x9631, 0xA8C1, 0x962A, 0xA8C2, 0x962C, 0xA8C3, 0x4E26, 0xA8C4, 0x4E56, + 0xA8C5, 0x4E73, 0xA8C6, 0x4E8B, 0xA8C7, 0x4E9B, 0xA8C8, 0x4E9E, 0xA8C9, 0x4EAB, 0xA8CA, 0x4EAC, 0xA8CB, 0x4F6F, 0xA8CC, 0x4F9D, + 0xA8CD, 0x4F8D, 0xA8CE, 0x4F73, 0xA8CF, 0x4F7F, 0xA8D0, 0x4F6C, 0xA8D1, 0x4F9B, 0xA8D2, 0x4F8B, 0xA8D3, 0x4F86, 0xA8D4, 0x4F83, + 0xA8D5, 0x4F70, 0xA8D6, 0x4F75, 0xA8D7, 0x4F88, 0xA8D8, 0x4F69, 0xA8D9, 0x4F7B, 0xA8DA, 0x4F96, 0xA8DB, 0x4F7E, 0xA8DC, 0x4F8F, + 0xA8DD, 0x4F91, 0xA8DE, 0x4F7A, 0xA8DF, 0x5154, 0xA8E0, 0x5152, 0xA8E1, 0x5155, 0xA8E2, 0x5169, 0xA8E3, 0x5177, 0xA8E4, 0x5176, + 0xA8E5, 0x5178, 0xA8E6, 0x51BD, 0xA8E7, 0x51FD, 0xA8E8, 0x523B, 0xA8E9, 0x5238, 0xA8EA, 0x5237, 0xA8EB, 0x523A, 0xA8EC, 0x5230, + 0xA8ED, 0x522E, 0xA8EE, 0x5236, 0xA8EF, 0x5241, 0xA8F0, 0x52BE, 0xA8F1, 0x52BB, 0xA8F2, 0x5352, 0xA8F3, 0x5354, 0xA8F4, 0x5353, + 0xA8F5, 0x5351, 0xA8F6, 0x5366, 0xA8F7, 0x5377, 0xA8F8, 0x5378, 0xA8F9, 0x5379, 0xA8FA, 0x53D6, 0xA8FB, 0x53D4, 0xA8FC, 0x53D7, + 0xA8FD, 0x5473, 0xA8FE, 0x5475, 0xA940, 0x5496, 0xA941, 0x5478, 0xA942, 0x5495, 0xA943, 0x5480, 0xA944, 0x547B, 0xA945, 0x5477, + 0xA946, 0x5484, 0xA947, 0x5492, 0xA948, 0x5486, 0xA949, 0x547C, 0xA94A, 0x5490, 0xA94B, 0x5471, 0xA94C, 0x5476, 0xA94D, 0x548C, + 0xA94E, 0x549A, 0xA94F, 0x5462, 0xA950, 0x5468, 0xA951, 0x548B, 0xA952, 0x547D, 0xA953, 0x548E, 0xA954, 0x56FA, 0xA955, 0x5783, + 0xA956, 0x5777, 0xA957, 0x576A, 0xA958, 0x5769, 0xA959, 0x5761, 0xA95A, 0x5766, 0xA95B, 0x5764, 0xA95C, 0x577C, 0xA95D, 0x591C, + 0xA95E, 0x5949, 0xA95F, 0x5947, 0xA960, 0x5948, 0xA961, 0x5944, 0xA962, 0x5954, 0xA963, 0x59BE, 0xA964, 0x59BB, 0xA965, 0x59D4, + 0xA966, 0x59B9, 0xA967, 0x59AE, 0xA968, 0x59D1, 0xA969, 0x59C6, 0xA96A, 0x59D0, 0xA96B, 0x59CD, 0xA96C, 0x59CB, 0xA96D, 0x59D3, + 0xA96E, 0x59CA, 0xA96F, 0x59AF, 0xA970, 0x59B3, 0xA971, 0x59D2, 0xA972, 0x59C5, 0xA973, 0x5B5F, 0xA974, 0x5B64, 0xA975, 0x5B63, + 0xA976, 0x5B97, 0xA977, 0x5B9A, 0xA978, 0x5B98, 0xA979, 0x5B9C, 0xA97A, 0x5B99, 0xA97B, 0x5B9B, 0xA97C, 0x5C1A, 0xA97D, 0x5C48, + 0xA97E, 0x5C45, 0xA9A1, 0x5C46, 0xA9A2, 0x5CB7, 0xA9A3, 0x5CA1, 0xA9A4, 0x5CB8, 0xA9A5, 0x5CA9, 0xA9A6, 0x5CAB, 0xA9A7, 0x5CB1, + 0xA9A8, 0x5CB3, 0xA9A9, 0x5E18, 0xA9AA, 0x5E1A, 0xA9AB, 0x5E16, 0xA9AC, 0x5E15, 0xA9AD, 0x5E1B, 0xA9AE, 0x5E11, 0xA9AF, 0x5E78, + 0xA9B0, 0x5E9A, 0xA9B1, 0x5E97, 0xA9B2, 0x5E9C, 0xA9B3, 0x5E95, 0xA9B4, 0x5E96, 0xA9B5, 0x5EF6, 0xA9B6, 0x5F26, 0xA9B7, 0x5F27, + 0xA9B8, 0x5F29, 0xA9B9, 0x5F80, 0xA9BA, 0x5F81, 0xA9BB, 0x5F7F, 0xA9BC, 0x5F7C, 0xA9BD, 0x5FDD, 0xA9BE, 0x5FE0, 0xA9BF, 0x5FFD, + 0xA9C0, 0x5FF5, 0xA9C1, 0x5FFF, 0xA9C2, 0x600F, 0xA9C3, 0x6014, 0xA9C4, 0x602F, 0xA9C5, 0x6035, 0xA9C6, 0x6016, 0xA9C7, 0x602A, + 0xA9C8, 0x6015, 0xA9C9, 0x6021, 0xA9CA, 0x6027, 0xA9CB, 0x6029, 0xA9CC, 0x602B, 0xA9CD, 0x601B, 0xA9CE, 0x6216, 0xA9CF, 0x6215, + 0xA9D0, 0x623F, 0xA9D1, 0x623E, 0xA9D2, 0x6240, 0xA9D3, 0x627F, 0xA9D4, 0x62C9, 0xA9D5, 0x62CC, 0xA9D6, 0x62C4, 0xA9D7, 0x62BF, + 0xA9D8, 0x62C2, 0xA9D9, 0x62B9, 0xA9DA, 0x62D2, 0xA9DB, 0x62DB, 0xA9DC, 0x62AB, 0xA9DD, 0x62D3, 0xA9DE, 0x62D4, 0xA9DF, 0x62CB, + 0xA9E0, 0x62C8, 0xA9E1, 0x62A8, 0xA9E2, 0x62BD, 0xA9E3, 0x62BC, 0xA9E4, 0x62D0, 0xA9E5, 0x62D9, 0xA9E6, 0x62C7, 0xA9E7, 0x62CD, + 0xA9E8, 0x62B5, 0xA9E9, 0x62DA, 0xA9EA, 0x62B1, 0xA9EB, 0x62D8, 0xA9EC, 0x62D6, 0xA9ED, 0x62D7, 0xA9EE, 0x62C6, 0xA9EF, 0x62AC, + 0xA9F0, 0x62CE, 0xA9F1, 0x653E, 0xA9F2, 0x65A7, 0xA9F3, 0x65BC, 0xA9F4, 0x65FA, 0xA9F5, 0x6614, 0xA9F6, 0x6613, 0xA9F7, 0x660C, + 0xA9F8, 0x6606, 0xA9F9, 0x6602, 0xA9FA, 0x660E, 0xA9FB, 0x6600, 0xA9FC, 0x660F, 0xA9FD, 0x6615, 0xA9FE, 0x660A, 0xAA40, 0x6607, + 0xAA41, 0x670D, 0xAA42, 0x670B, 0xAA43, 0x676D, 0xAA44, 0x678B, 0xAA45, 0x6795, 0xAA46, 0x6771, 0xAA47, 0x679C, 0xAA48, 0x6773, + 0xAA49, 0x6777, 0xAA4A, 0x6787, 0xAA4B, 0x679D, 0xAA4C, 0x6797, 0xAA4D, 0x676F, 0xAA4E, 0x6770, 0xAA4F, 0x677F, 0xAA50, 0x6789, + 0xAA51, 0x677E, 0xAA52, 0x6790, 0xAA53, 0x6775, 0xAA54, 0x679A, 0xAA55, 0x6793, 0xAA56, 0x677C, 0xAA57, 0x676A, 0xAA58, 0x6772, + 0xAA59, 0x6B23, 0xAA5A, 0x6B66, 0xAA5B, 0x6B67, 0xAA5C, 0x6B7F, 0xAA5D, 0x6C13, 0xAA5E, 0x6C1B, 0xAA5F, 0x6CE3, 0xAA60, 0x6CE8, + 0xAA61, 0x6CF3, 0xAA62, 0x6CB1, 0xAA63, 0x6CCC, 0xAA64, 0x6CE5, 0xAA65, 0x6CB3, 0xAA66, 0x6CBD, 0xAA67, 0x6CBE, 0xAA68, 0x6CBC, + 0xAA69, 0x6CE2, 0xAA6A, 0x6CAB, 0xAA6B, 0x6CD5, 0xAA6C, 0x6CD3, 0xAA6D, 0x6CB8, 0xAA6E, 0x6CC4, 0xAA6F, 0x6CB9, 0xAA70, 0x6CC1, + 0xAA71, 0x6CAE, 0xAA72, 0x6CD7, 0xAA73, 0x6CC5, 0xAA74, 0x6CF1, 0xAA75, 0x6CBF, 0xAA76, 0x6CBB, 0xAA77, 0x6CE1, 0xAA78, 0x6CDB, + 0xAA79, 0x6CCA, 0xAA7A, 0x6CAC, 0xAA7B, 0x6CEF, 0xAA7C, 0x6CDC, 0xAA7D, 0x6CD6, 0xAA7E, 0x6CE0, 0xAAA1, 0x7095, 0xAAA2, 0x708E, + 0xAAA3, 0x7092, 0xAAA4, 0x708A, 0xAAA5, 0x7099, 0xAAA6, 0x722C, 0xAAA7, 0x722D, 0xAAA8, 0x7238, 0xAAA9, 0x7248, 0xAAAA, 0x7267, + 0xAAAB, 0x7269, 0xAAAC, 0x72C0, 0xAAAD, 0x72CE, 0xAAAE, 0x72D9, 0xAAAF, 0x72D7, 0xAAB0, 0x72D0, 0xAAB1, 0x73A9, 0xAAB2, 0x73A8, + 0xAAB3, 0x739F, 0xAAB4, 0x73AB, 0xAAB5, 0x73A5, 0xAAB6, 0x753D, 0xAAB7, 0x759D, 0xAAB8, 0x7599, 0xAAB9, 0x759A, 0xAABA, 0x7684, + 0xAABB, 0x76C2, 0xAABC, 0x76F2, 0xAABD, 0x76F4, 0xAABE, 0x77E5, 0xAABF, 0x77FD, 0xAAC0, 0x793E, 0xAAC1, 0x7940, 0xAAC2, 0x7941, + 0xAAC3, 0x79C9, 0xAAC4, 0x79C8, 0xAAC5, 0x7A7A, 0xAAC6, 0x7A79, 0xAAC7, 0x7AFA, 0xAAC8, 0x7CFE, 0xAAC9, 0x7F54, 0xAACA, 0x7F8C, + 0xAACB, 0x7F8B, 0xAACC, 0x8005, 0xAACD, 0x80BA, 0xAACE, 0x80A5, 0xAACF, 0x80A2, 0xAAD0, 0x80B1, 0xAAD1, 0x80A1, 0xAAD2, 0x80AB, + 0xAAD3, 0x80A9, 0xAAD4, 0x80B4, 0xAAD5, 0x80AA, 0xAAD6, 0x80AF, 0xAAD7, 0x81E5, 0xAAD8, 0x81FE, 0xAAD9, 0x820D, 0xAADA, 0x82B3, + 0xAADB, 0x829D, 0xAADC, 0x8299, 0xAADD, 0x82AD, 0xAADE, 0x82BD, 0xAADF, 0x829F, 0xAAE0, 0x82B9, 0xAAE1, 0x82B1, 0xAAE2, 0x82AC, + 0xAAE3, 0x82A5, 0xAAE4, 0x82AF, 0xAAE5, 0x82B8, 0xAAE6, 0x82A3, 0xAAE7, 0x82B0, 0xAAE8, 0x82BE, 0xAAE9, 0x82B7, 0xAAEA, 0x864E, + 0xAAEB, 0x8671, 0xAAEC, 0x521D, 0xAAED, 0x8868, 0xAAEE, 0x8ECB, 0xAAEF, 0x8FCE, 0xAAF0, 0x8FD4, 0xAAF1, 0x8FD1, 0xAAF2, 0x90B5, + 0xAAF3, 0x90B8, 0xAAF4, 0x90B1, 0xAAF5, 0x90B6, 0xAAF6, 0x91C7, 0xAAF7, 0x91D1, 0xAAF8, 0x9577, 0xAAF9, 0x9580, 0xAAFA, 0x961C, + 0xAAFB, 0x9640, 0xAAFC, 0x963F, 0xAAFD, 0x963B, 0xAAFE, 0x9644, 0xAB40, 0x9642, 0xAB41, 0x96B9, 0xAB42, 0x96E8, 0xAB43, 0x9752, + 0xAB44, 0x975E, 0xAB45, 0x4E9F, 0xAB46, 0x4EAD, 0xAB47, 0x4EAE, 0xAB48, 0x4FE1, 0xAB49, 0x4FB5, 0xAB4A, 0x4FAF, 0xAB4B, 0x4FBF, + 0xAB4C, 0x4FE0, 0xAB4D, 0x4FD1, 0xAB4E, 0x4FCF, 0xAB4F, 0x4FDD, 0xAB50, 0x4FC3, 0xAB51, 0x4FB6, 0xAB52, 0x4FD8, 0xAB53, 0x4FDF, + 0xAB54, 0x4FCA, 0xAB55, 0x4FD7, 0xAB56, 0x4FAE, 0xAB57, 0x4FD0, 0xAB58, 0x4FC4, 0xAB59, 0x4FC2, 0xAB5A, 0x4FDA, 0xAB5B, 0x4FCE, + 0xAB5C, 0x4FDE, 0xAB5D, 0x4FB7, 0xAB5E, 0x5157, 0xAB5F, 0x5192, 0xAB60, 0x5191, 0xAB61, 0x51A0, 0xAB62, 0x524E, 0xAB63, 0x5243, + 0xAB64, 0x524A, 0xAB65, 0x524D, 0xAB66, 0x524C, 0xAB67, 0x524B, 0xAB68, 0x5247, 0xAB69, 0x52C7, 0xAB6A, 0x52C9, 0xAB6B, 0x52C3, + 0xAB6C, 0x52C1, 0xAB6D, 0x530D, 0xAB6E, 0x5357, 0xAB6F, 0x537B, 0xAB70, 0x539A, 0xAB71, 0x53DB, 0xAB72, 0x54AC, 0xAB73, 0x54C0, + 0xAB74, 0x54A8, 0xAB75, 0x54CE, 0xAB76, 0x54C9, 0xAB77, 0x54B8, 0xAB78, 0x54A6, 0xAB79, 0x54B3, 0xAB7A, 0x54C7, 0xAB7B, 0x54C2, + 0xAB7C, 0x54BD, 0xAB7D, 0x54AA, 0xAB7E, 0x54C1, 0xABA1, 0x54C4, 0xABA2, 0x54C8, 0xABA3, 0x54AF, 0xABA4, 0x54AB, 0xABA5, 0x54B1, + 0xABA6, 0x54BB, 0xABA7, 0x54A9, 0xABA8, 0x54A7, 0xABA9, 0x54BF, 0xABAA, 0x56FF, 0xABAB, 0x5782, 0xABAC, 0x578B, 0xABAD, 0x57A0, + 0xABAE, 0x57A3, 0xABAF, 0x57A2, 0xABB0, 0x57CE, 0xABB1, 0x57AE, 0xABB2, 0x5793, 0xABB3, 0x5955, 0xABB4, 0x5951, 0xABB5, 0x594F, + 0xABB6, 0x594E, 0xABB7, 0x5950, 0xABB8, 0x59DC, 0xABB9, 0x59D8, 0xABBA, 0x59FF, 0xABBB, 0x59E3, 0xABBC, 0x59E8, 0xABBD, 0x5A03, + 0xABBE, 0x59E5, 0xABBF, 0x59EA, 0xABC0, 0x59DA, 0xABC1, 0x59E6, 0xABC2, 0x5A01, 0xABC3, 0x59FB, 0xABC4, 0x5B69, 0xABC5, 0x5BA3, + 0xABC6, 0x5BA6, 0xABC7, 0x5BA4, 0xABC8, 0x5BA2, 0xABC9, 0x5BA5, 0xABCA, 0x5C01, 0xABCB, 0x5C4E, 0xABCC, 0x5C4F, 0xABCD, 0x5C4D, + 0xABCE, 0x5C4B, 0xABCF, 0x5CD9, 0xABD0, 0x5CD2, 0xABD1, 0x5DF7, 0xABD2, 0x5E1D, 0xABD3, 0x5E25, 0xABD4, 0x5E1F, 0xABD5, 0x5E7D, + 0xABD6, 0x5EA0, 0xABD7, 0x5EA6, 0xABD8, 0x5EFA, 0xABD9, 0x5F08, 0xABDA, 0x5F2D, 0xABDB, 0x5F65, 0xABDC, 0x5F88, 0xABDD, 0x5F85, + 0xABDE, 0x5F8A, 0xABDF, 0x5F8B, 0xABE0, 0x5F87, 0xABE1, 0x5F8C, 0xABE2, 0x5F89, 0xABE3, 0x6012, 0xABE4, 0x601D, 0xABE5, 0x6020, + 0xABE6, 0x6025, 0xABE7, 0x600E, 0xABE8, 0x6028, 0xABE9, 0x604D, 0xABEA, 0x6070, 0xABEB, 0x6068, 0xABEC, 0x6062, 0xABED, 0x6046, + 0xABEE, 0x6043, 0xABEF, 0x606C, 0xABF0, 0x606B, 0xABF1, 0x606A, 0xABF2, 0x6064, 0xABF3, 0x6241, 0xABF4, 0x62DC, 0xABF5, 0x6316, + 0xABF6, 0x6309, 0xABF7, 0x62FC, 0xABF8, 0x62ED, 0xABF9, 0x6301, 0xABFA, 0x62EE, 0xABFB, 0x62FD, 0xABFC, 0x6307, 0xABFD, 0x62F1, + 0xABFE, 0x62F7, 0xAC40, 0x62EF, 0xAC41, 0x62EC, 0xAC42, 0x62FE, 0xAC43, 0x62F4, 0xAC44, 0x6311, 0xAC45, 0x6302, 0xAC46, 0x653F, + 0xAC47, 0x6545, 0xAC48, 0x65AB, 0xAC49, 0x65BD, 0xAC4A, 0x65E2, 0xAC4B, 0x6625, 0xAC4C, 0x662D, 0xAC4D, 0x6620, 0xAC4E, 0x6627, + 0xAC4F, 0x662F, 0xAC50, 0x661F, 0xAC51, 0x6628, 0xAC52, 0x6631, 0xAC53, 0x6624, 0xAC54, 0x66F7, 0xAC55, 0x67FF, 0xAC56, 0x67D3, + 0xAC57, 0x67F1, 0xAC58, 0x67D4, 0xAC59, 0x67D0, 0xAC5A, 0x67EC, 0xAC5B, 0x67B6, 0xAC5C, 0x67AF, 0xAC5D, 0x67F5, 0xAC5E, 0x67E9, + 0xAC5F, 0x67EF, 0xAC60, 0x67C4, 0xAC61, 0x67D1, 0xAC62, 0x67B4, 0xAC63, 0x67DA, 0xAC64, 0x67E5, 0xAC65, 0x67B8, 0xAC66, 0x67CF, + 0xAC67, 0x67DE, 0xAC68, 0x67F3, 0xAC69, 0x67B0, 0xAC6A, 0x67D9, 0xAC6B, 0x67E2, 0xAC6C, 0x67DD, 0xAC6D, 0x67D2, 0xAC6E, 0x6B6A, + 0xAC6F, 0x6B83, 0xAC70, 0x6B86, 0xAC71, 0x6BB5, 0xAC72, 0x6BD2, 0xAC73, 0x6BD7, 0xAC74, 0x6C1F, 0xAC75, 0x6CC9, 0xAC76, 0x6D0B, + 0xAC77, 0x6D32, 0xAC78, 0x6D2A, 0xAC79, 0x6D41, 0xAC7A, 0x6D25, 0xAC7B, 0x6D0C, 0xAC7C, 0x6D31, 0xAC7D, 0x6D1E, 0xAC7E, 0x6D17, + 0xACA1, 0x6D3B, 0xACA2, 0x6D3D, 0xACA3, 0x6D3E, 0xACA4, 0x6D36, 0xACA5, 0x6D1B, 0xACA6, 0x6CF5, 0xACA7, 0x6D39, 0xACA8, 0x6D27, + 0xACA9, 0x6D38, 0xACAA, 0x6D29, 0xACAB, 0x6D2E, 0xACAC, 0x6D35, 0xACAD, 0x6D0E, 0xACAE, 0x6D2B, 0xACAF, 0x70AB, 0xACB0, 0x70BA, + 0xACB1, 0x70B3, 0xACB2, 0x70AC, 0xACB3, 0x70AF, 0xACB4, 0x70AD, 0xACB5, 0x70B8, 0xACB6, 0x70AE, 0xACB7, 0x70A4, 0xACB8, 0x7230, + 0xACB9, 0x7272, 0xACBA, 0x726F, 0xACBB, 0x7274, 0xACBC, 0x72E9, 0xACBD, 0x72E0, 0xACBE, 0x72E1, 0xACBF, 0x73B7, 0xACC0, 0x73CA, + 0xACC1, 0x73BB, 0xACC2, 0x73B2, 0xACC3, 0x73CD, 0xACC4, 0x73C0, 0xACC5, 0x73B3, 0xACC6, 0x751A, 0xACC7, 0x752D, 0xACC8, 0x754F, + 0xACC9, 0x754C, 0xACCA, 0x754E, 0xACCB, 0x754B, 0xACCC, 0x75AB, 0xACCD, 0x75A4, 0xACCE, 0x75A5, 0xACCF, 0x75A2, 0xACD0, 0x75A3, + 0xACD1, 0x7678, 0xACD2, 0x7686, 0xACD3, 0x7687, 0xACD4, 0x7688, 0xACD5, 0x76C8, 0xACD6, 0x76C6, 0xACD7, 0x76C3, 0xACD8, 0x76C5, + 0xACD9, 0x7701, 0xACDA, 0x76F9, 0xACDB, 0x76F8, 0xACDC, 0x7709, 0xACDD, 0x770B, 0xACDE, 0x76FE, 0xACDF, 0x76FC, 0xACE0, 0x7707, + 0xACE1, 0x77DC, 0xACE2, 0x7802, 0xACE3, 0x7814, 0xACE4, 0x780C, 0xACE5, 0x780D, 0xACE6, 0x7946, 0xACE7, 0x7949, 0xACE8, 0x7948, + 0xACE9, 0x7947, 0xACEA, 0x79B9, 0xACEB, 0x79BA, 0xACEC, 0x79D1, 0xACED, 0x79D2, 0xACEE, 0x79CB, 0xACEF, 0x7A7F, 0xACF0, 0x7A81, + 0xACF1, 0x7AFF, 0xACF2, 0x7AFD, 0xACF3, 0x7C7D, 0xACF4, 0x7D02, 0xACF5, 0x7D05, 0xACF6, 0x7D00, 0xACF7, 0x7D09, 0xACF8, 0x7D07, + 0xACF9, 0x7D04, 0xACFA, 0x7D06, 0xACFB, 0x7F38, 0xACFC, 0x7F8E, 0xACFD, 0x7FBF, 0xACFE, 0x8004, 0xAD40, 0x8010, 0xAD41, 0x800D, + 0xAD42, 0x8011, 0xAD43, 0x8036, 0xAD44, 0x80D6, 0xAD45, 0x80E5, 0xAD46, 0x80DA, 0xAD47, 0x80C3, 0xAD48, 0x80C4, 0xAD49, 0x80CC, + 0xAD4A, 0x80E1, 0xAD4B, 0x80DB, 0xAD4C, 0x80CE, 0xAD4D, 0x80DE, 0xAD4E, 0x80E4, 0xAD4F, 0x80DD, 0xAD50, 0x81F4, 0xAD51, 0x8222, + 0xAD52, 0x82E7, 0xAD53, 0x8303, 0xAD54, 0x8305, 0xAD55, 0x82E3, 0xAD56, 0x82DB, 0xAD57, 0x82E6, 0xAD58, 0x8304, 0xAD59, 0x82E5, + 0xAD5A, 0x8302, 0xAD5B, 0x8309, 0xAD5C, 0x82D2, 0xAD5D, 0x82D7, 0xAD5E, 0x82F1, 0xAD5F, 0x8301, 0xAD60, 0x82DC, 0xAD61, 0x82D4, + 0xAD62, 0x82D1, 0xAD63, 0x82DE, 0xAD64, 0x82D3, 0xAD65, 0x82DF, 0xAD66, 0x82EF, 0xAD67, 0x8306, 0xAD68, 0x8650, 0xAD69, 0x8679, + 0xAD6A, 0x867B, 0xAD6B, 0x867A, 0xAD6C, 0x884D, 0xAD6D, 0x886B, 0xAD6E, 0x8981, 0xAD6F, 0x89D4, 0xAD70, 0x8A08, 0xAD71, 0x8A02, + 0xAD72, 0x8A03, 0xAD73, 0x8C9E, 0xAD74, 0x8CA0, 0xAD75, 0x8D74, 0xAD76, 0x8D73, 0xAD77, 0x8DB4, 0xAD78, 0x8ECD, 0xAD79, 0x8ECC, + 0xAD7A, 0x8FF0, 0xAD7B, 0x8FE6, 0xAD7C, 0x8FE2, 0xAD7D, 0x8FEA, 0xAD7E, 0x8FE5, 0xADA1, 0x8FED, 0xADA2, 0x8FEB, 0xADA3, 0x8FE4, + 0xADA4, 0x8FE8, 0xADA5, 0x90CA, 0xADA6, 0x90CE, 0xADA7, 0x90C1, 0xADA8, 0x90C3, 0xADA9, 0x914B, 0xADAA, 0x914A, 0xADAB, 0x91CD, + 0xADAC, 0x9582, 0xADAD, 0x9650, 0xADAE, 0x964B, 0xADAF, 0x964C, 0xADB0, 0x964D, 0xADB1, 0x9762, 0xADB2, 0x9769, 0xADB3, 0x97CB, + 0xADB4, 0x97ED, 0xADB5, 0x97F3, 0xADB6, 0x9801, 0xADB7, 0x98A8, 0xADB8, 0x98DB, 0xADB9, 0x98DF, 0xADBA, 0x9996, 0xADBB, 0x9999, + 0xADBC, 0x4E58, 0xADBD, 0x4EB3, 0xADBE, 0x500C, 0xADBF, 0x500D, 0xADC0, 0x5023, 0xADC1, 0x4FEF, 0xADC2, 0x5026, 0xADC3, 0x5025, + 0xADC4, 0x4FF8, 0xADC5, 0x5029, 0xADC6, 0x5016, 0xADC7, 0x5006, 0xADC8, 0x503C, 0xADC9, 0x501F, 0xADCA, 0x501A, 0xADCB, 0x5012, + 0xADCC, 0x5011, 0xADCD, 0x4FFA, 0xADCE, 0x5000, 0xADCF, 0x5014, 0xADD0, 0x5028, 0xADD1, 0x4FF1, 0xADD2, 0x5021, 0xADD3, 0x500B, + 0xADD4, 0x5019, 0xADD5, 0x5018, 0xADD6, 0x4FF3, 0xADD7, 0x4FEE, 0xADD8, 0x502D, 0xADD9, 0x502A, 0xADDA, 0x4FFE, 0xADDB, 0x502B, + 0xADDC, 0x5009, 0xADDD, 0x517C, 0xADDE, 0x51A4, 0xADDF, 0x51A5, 0xADE0, 0x51A2, 0xADE1, 0x51CD, 0xADE2, 0x51CC, 0xADE3, 0x51C6, + 0xADE4, 0x51CB, 0xADE5, 0x5256, 0xADE6, 0x525C, 0xADE7, 0x5254, 0xADE8, 0x525B, 0xADE9, 0x525D, 0xADEA, 0x532A, 0xADEB, 0x537F, + 0xADEC, 0x539F, 0xADED, 0x539D, 0xADEE, 0x53DF, 0xADEF, 0x54E8, 0xADF0, 0x5510, 0xADF1, 0x5501, 0xADF2, 0x5537, 0xADF3, 0x54FC, + 0xADF4, 0x54E5, 0xADF5, 0x54F2, 0xADF6, 0x5506, 0xADF7, 0x54FA, 0xADF8, 0x5514, 0xADF9, 0x54E9, 0xADFA, 0x54ED, 0xADFB, 0x54E1, + 0xADFC, 0x5509, 0xADFD, 0x54EE, 0xADFE, 0x54EA, 0xAE40, 0x54E6, 0xAE41, 0x5527, 0xAE42, 0x5507, 0xAE43, 0x54FD, 0xAE44, 0x550F, + 0xAE45, 0x5703, 0xAE46, 0x5704, 0xAE47, 0x57C2, 0xAE48, 0x57D4, 0xAE49, 0x57CB, 0xAE4A, 0x57C3, 0xAE4B, 0x5809, 0xAE4C, 0x590F, + 0xAE4D, 0x5957, 0xAE4E, 0x5958, 0xAE4F, 0x595A, 0xAE50, 0x5A11, 0xAE51, 0x5A18, 0xAE52, 0x5A1C, 0xAE53, 0x5A1F, 0xAE54, 0x5A1B, + 0xAE55, 0x5A13, 0xAE56, 0x59EC, 0xAE57, 0x5A20, 0xAE58, 0x5A23, 0xAE59, 0x5A29, 0xAE5A, 0x5A25, 0xAE5B, 0x5A0C, 0xAE5C, 0x5A09, + 0xAE5D, 0x5B6B, 0xAE5E, 0x5C58, 0xAE5F, 0x5BB0, 0xAE60, 0x5BB3, 0xAE61, 0x5BB6, 0xAE62, 0x5BB4, 0xAE63, 0x5BAE, 0xAE64, 0x5BB5, + 0xAE65, 0x5BB9, 0xAE66, 0x5BB8, 0xAE67, 0x5C04, 0xAE68, 0x5C51, 0xAE69, 0x5C55, 0xAE6A, 0x5C50, 0xAE6B, 0x5CED, 0xAE6C, 0x5CFD, + 0xAE6D, 0x5CFB, 0xAE6E, 0x5CEA, 0xAE6F, 0x5CE8, 0xAE70, 0x5CF0, 0xAE71, 0x5CF6, 0xAE72, 0x5D01, 0xAE73, 0x5CF4, 0xAE74, 0x5DEE, + 0xAE75, 0x5E2D, 0xAE76, 0x5E2B, 0xAE77, 0x5EAB, 0xAE78, 0x5EAD, 0xAE79, 0x5EA7, 0xAE7A, 0x5F31, 0xAE7B, 0x5F92, 0xAE7C, 0x5F91, + 0xAE7D, 0x5F90, 0xAE7E, 0x6059, 0xAEA1, 0x6063, 0xAEA2, 0x6065, 0xAEA3, 0x6050, 0xAEA4, 0x6055, 0xAEA5, 0x606D, 0xAEA6, 0x6069, + 0xAEA7, 0x606F, 0xAEA8, 0x6084, 0xAEA9, 0x609F, 0xAEAA, 0x609A, 0xAEAB, 0x608D, 0xAEAC, 0x6094, 0xAEAD, 0x608C, 0xAEAE, 0x6085, + 0xAEAF, 0x6096, 0xAEB0, 0x6247, 0xAEB1, 0x62F3, 0xAEB2, 0x6308, 0xAEB3, 0x62FF, 0xAEB4, 0x634E, 0xAEB5, 0x633E, 0xAEB6, 0x632F, + 0xAEB7, 0x6355, 0xAEB8, 0x6342, 0xAEB9, 0x6346, 0xAEBA, 0x634F, 0xAEBB, 0x6349, 0xAEBC, 0x633A, 0xAEBD, 0x6350, 0xAEBE, 0x633D, + 0xAEBF, 0x632A, 0xAEC0, 0x632B, 0xAEC1, 0x6328, 0xAEC2, 0x634D, 0xAEC3, 0x634C, 0xAEC4, 0x6548, 0xAEC5, 0x6549, 0xAEC6, 0x6599, + 0xAEC7, 0x65C1, 0xAEC8, 0x65C5, 0xAEC9, 0x6642, 0xAECA, 0x6649, 0xAECB, 0x664F, 0xAECC, 0x6643, 0xAECD, 0x6652, 0xAECE, 0x664C, + 0xAECF, 0x6645, 0xAED0, 0x6641, 0xAED1, 0x66F8, 0xAED2, 0x6714, 0xAED3, 0x6715, 0xAED4, 0x6717, 0xAED5, 0x6821, 0xAED6, 0x6838, + 0xAED7, 0x6848, 0xAED8, 0x6846, 0xAED9, 0x6853, 0xAEDA, 0x6839, 0xAEDB, 0x6842, 0xAEDC, 0x6854, 0xAEDD, 0x6829, 0xAEDE, 0x68B3, + 0xAEDF, 0x6817, 0xAEE0, 0x684C, 0xAEE1, 0x6851, 0xAEE2, 0x683D, 0xAEE3, 0x67F4, 0xAEE4, 0x6850, 0xAEE5, 0x6840, 0xAEE6, 0x683C, + 0xAEE7, 0x6843, 0xAEE8, 0x682A, 0xAEE9, 0x6845, 0xAEEA, 0x6813, 0xAEEB, 0x6818, 0xAEEC, 0x6841, 0xAEED, 0x6B8A, 0xAEEE, 0x6B89, + 0xAEEF, 0x6BB7, 0xAEF0, 0x6C23, 0xAEF1, 0x6C27, 0xAEF2, 0x6C28, 0xAEF3, 0x6C26, 0xAEF4, 0x6C24, 0xAEF5, 0x6CF0, 0xAEF6, 0x6D6A, + 0xAEF7, 0x6D95, 0xAEF8, 0x6D88, 0xAEF9, 0x6D87, 0xAEFA, 0x6D66, 0xAEFB, 0x6D78, 0xAEFC, 0x6D77, 0xAEFD, 0x6D59, 0xAEFE, 0x6D93, + 0xAF40, 0x6D6C, 0xAF41, 0x6D89, 0xAF42, 0x6D6E, 0xAF43, 0x6D5A, 0xAF44, 0x6D74, 0xAF45, 0x6D69, 0xAF46, 0x6D8C, 0xAF47, 0x6D8A, + 0xAF48, 0x6D79, 0xAF49, 0x6D85, 0xAF4A, 0x6D65, 0xAF4B, 0x6D94, 0xAF4C, 0x70CA, 0xAF4D, 0x70D8, 0xAF4E, 0x70E4, 0xAF4F, 0x70D9, + 0xAF50, 0x70C8, 0xAF51, 0x70CF, 0xAF52, 0x7239, 0xAF53, 0x7279, 0xAF54, 0x72FC, 0xAF55, 0x72F9, 0xAF56, 0x72FD, 0xAF57, 0x72F8, + 0xAF58, 0x72F7, 0xAF59, 0x7386, 0xAF5A, 0x73ED, 0xAF5B, 0x7409, 0xAF5C, 0x73EE, 0xAF5D, 0x73E0, 0xAF5E, 0x73EA, 0xAF5F, 0x73DE, + 0xAF60, 0x7554, 0xAF61, 0x755D, 0xAF62, 0x755C, 0xAF63, 0x755A, 0xAF64, 0x7559, 0xAF65, 0x75BE, 0xAF66, 0x75C5, 0xAF67, 0x75C7, + 0xAF68, 0x75B2, 0xAF69, 0x75B3, 0xAF6A, 0x75BD, 0xAF6B, 0x75BC, 0xAF6C, 0x75B9, 0xAF6D, 0x75C2, 0xAF6E, 0x75B8, 0xAF6F, 0x768B, + 0xAF70, 0x76B0, 0xAF71, 0x76CA, 0xAF72, 0x76CD, 0xAF73, 0x76CE, 0xAF74, 0x7729, 0xAF75, 0x771F, 0xAF76, 0x7720, 0xAF77, 0x7728, + 0xAF78, 0x77E9, 0xAF79, 0x7830, 0xAF7A, 0x7827, 0xAF7B, 0x7838, 0xAF7C, 0x781D, 0xAF7D, 0x7834, 0xAF7E, 0x7837, 0xAFA1, 0x7825, + 0xAFA2, 0x782D, 0xAFA3, 0x7820, 0xAFA4, 0x781F, 0xAFA5, 0x7832, 0xAFA6, 0x7955, 0xAFA7, 0x7950, 0xAFA8, 0x7960, 0xAFA9, 0x795F, + 0xAFAA, 0x7956, 0xAFAB, 0x795E, 0xAFAC, 0x795D, 0xAFAD, 0x7957, 0xAFAE, 0x795A, 0xAFAF, 0x79E4, 0xAFB0, 0x79E3, 0xAFB1, 0x79E7, + 0xAFB2, 0x79DF, 0xAFB3, 0x79E6, 0xAFB4, 0x79E9, 0xAFB5, 0x79D8, 0xAFB6, 0x7A84, 0xAFB7, 0x7A88, 0xAFB8, 0x7AD9, 0xAFB9, 0x7B06, + 0xAFBA, 0x7B11, 0xAFBB, 0x7C89, 0xAFBC, 0x7D21, 0xAFBD, 0x7D17, 0xAFBE, 0x7D0B, 0xAFBF, 0x7D0A, 0xAFC0, 0x7D20, 0xAFC1, 0x7D22, + 0xAFC2, 0x7D14, 0xAFC3, 0x7D10, 0xAFC4, 0x7D15, 0xAFC5, 0x7D1A, 0xAFC6, 0x7D1C, 0xAFC7, 0x7D0D, 0xAFC8, 0x7D19, 0xAFC9, 0x7D1B, + 0xAFCA, 0x7F3A, 0xAFCB, 0x7F5F, 0xAFCC, 0x7F94, 0xAFCD, 0x7FC5, 0xAFCE, 0x7FC1, 0xAFCF, 0x8006, 0xAFD0, 0x8018, 0xAFD1, 0x8015, + 0xAFD2, 0x8019, 0xAFD3, 0x8017, 0xAFD4, 0x803D, 0xAFD5, 0x803F, 0xAFD6, 0x80F1, 0xAFD7, 0x8102, 0xAFD8, 0x80F0, 0xAFD9, 0x8105, + 0xAFDA, 0x80ED, 0xAFDB, 0x80F4, 0xAFDC, 0x8106, 0xAFDD, 0x80F8, 0xAFDE, 0x80F3, 0xAFDF, 0x8108, 0xAFE0, 0x80FD, 0xAFE1, 0x810A, + 0xAFE2, 0x80FC, 0xAFE3, 0x80EF, 0xAFE4, 0x81ED, 0xAFE5, 0x81EC, 0xAFE6, 0x8200, 0xAFE7, 0x8210, 0xAFE8, 0x822A, 0xAFE9, 0x822B, + 0xAFEA, 0x8228, 0xAFEB, 0x822C, 0xAFEC, 0x82BB, 0xAFED, 0x832B, 0xAFEE, 0x8352, 0xAFEF, 0x8354, 0xAFF0, 0x834A, 0xAFF1, 0x8338, + 0xAFF2, 0x8350, 0xAFF3, 0x8349, 0xAFF4, 0x8335, 0xAFF5, 0x8334, 0xAFF6, 0x834F, 0xAFF7, 0x8332, 0xAFF8, 0x8339, 0xAFF9, 0x8336, + 0xAFFA, 0x8317, 0xAFFB, 0x8340, 0xAFFC, 0x8331, 0xAFFD, 0x8328, 0xAFFE, 0x8343, 0xB040, 0x8654, 0xB041, 0x868A, 0xB042, 0x86AA, + 0xB043, 0x8693, 0xB044, 0x86A4, 0xB045, 0x86A9, 0xB046, 0x868C, 0xB047, 0x86A3, 0xB048, 0x869C, 0xB049, 0x8870, 0xB04A, 0x8877, + 0xB04B, 0x8881, 0xB04C, 0x8882, 0xB04D, 0x887D, 0xB04E, 0x8879, 0xB04F, 0x8A18, 0xB050, 0x8A10, 0xB051, 0x8A0E, 0xB052, 0x8A0C, + 0xB053, 0x8A15, 0xB054, 0x8A0A, 0xB055, 0x8A17, 0xB056, 0x8A13, 0xB057, 0x8A16, 0xB058, 0x8A0F, 0xB059, 0x8A11, 0xB05A, 0x8C48, + 0xB05B, 0x8C7A, 0xB05C, 0x8C79, 0xB05D, 0x8CA1, 0xB05E, 0x8CA2, 0xB05F, 0x8D77, 0xB060, 0x8EAC, 0xB061, 0x8ED2, 0xB062, 0x8ED4, + 0xB063, 0x8ECF, 0xB064, 0x8FB1, 0xB065, 0x9001, 0xB066, 0x9006, 0xB067, 0x8FF7, 0xB068, 0x9000, 0xB069, 0x8FFA, 0xB06A, 0x8FF4, + 0xB06B, 0x9003, 0xB06C, 0x8FFD, 0xB06D, 0x9005, 0xB06E, 0x8FF8, 0xB06F, 0x9095, 0xB070, 0x90E1, 0xB071, 0x90DD, 0xB072, 0x90E2, + 0xB073, 0x9152, 0xB074, 0x914D, 0xB075, 0x914C, 0xB076, 0x91D8, 0xB077, 0x91DD, 0xB078, 0x91D7, 0xB079, 0x91DC, 0xB07A, 0x91D9, + 0xB07B, 0x9583, 0xB07C, 0x9662, 0xB07D, 0x9663, 0xB07E, 0x9661, 0xB0A1, 0x965B, 0xB0A2, 0x965D, 0xB0A3, 0x9664, 0xB0A4, 0x9658, + 0xB0A5, 0x965E, 0xB0A6, 0x96BB, 0xB0A7, 0x98E2, 0xB0A8, 0x99AC, 0xB0A9, 0x9AA8, 0xB0AA, 0x9AD8, 0xB0AB, 0x9B25, 0xB0AC, 0x9B32, + 0xB0AD, 0x9B3C, 0xB0AE, 0x4E7E, 0xB0AF, 0x507A, 0xB0B0, 0x507D, 0xB0B1, 0x505C, 0xB0B2, 0x5047, 0xB0B3, 0x5043, 0xB0B4, 0x504C, + 0xB0B5, 0x505A, 0xB0B6, 0x5049, 0xB0B7, 0x5065, 0xB0B8, 0x5076, 0xB0B9, 0x504E, 0xB0BA, 0x5055, 0xB0BB, 0x5075, 0xB0BC, 0x5074, + 0xB0BD, 0x5077, 0xB0BE, 0x504F, 0xB0BF, 0x500F, 0xB0C0, 0x506F, 0xB0C1, 0x506D, 0xB0C2, 0x515C, 0xB0C3, 0x5195, 0xB0C4, 0x51F0, + 0xB0C5, 0x526A, 0xB0C6, 0x526F, 0xB0C7, 0x52D2, 0xB0C8, 0x52D9, 0xB0C9, 0x52D8, 0xB0CA, 0x52D5, 0xB0CB, 0x5310, 0xB0CC, 0x530F, + 0xB0CD, 0x5319, 0xB0CE, 0x533F, 0xB0CF, 0x5340, 0xB0D0, 0x533E, 0xB0D1, 0x53C3, 0xB0D2, 0x66FC, 0xB0D3, 0x5546, 0xB0D4, 0x556A, + 0xB0D5, 0x5566, 0xB0D6, 0x5544, 0xB0D7, 0x555E, 0xB0D8, 0x5561, 0xB0D9, 0x5543, 0xB0DA, 0x554A, 0xB0DB, 0x5531, 0xB0DC, 0x5556, + 0xB0DD, 0x554F, 0xB0DE, 0x5555, 0xB0DF, 0x552F, 0xB0E0, 0x5564, 0xB0E1, 0x5538, 0xB0E2, 0x552E, 0xB0E3, 0x555C, 0xB0E4, 0x552C, + 0xB0E5, 0x5563, 0xB0E6, 0x5533, 0xB0E7, 0x5541, 0xB0E8, 0x5557, 0xB0E9, 0x5708, 0xB0EA, 0x570B, 0xB0EB, 0x5709, 0xB0EC, 0x57DF, + 0xB0ED, 0x5805, 0xB0EE, 0x580A, 0xB0EF, 0x5806, 0xB0F0, 0x57E0, 0xB0F1, 0x57E4, 0xB0F2, 0x57FA, 0xB0F3, 0x5802, 0xB0F4, 0x5835, + 0xB0F5, 0x57F7, 0xB0F6, 0x57F9, 0xB0F7, 0x5920, 0xB0F8, 0x5962, 0xB0F9, 0x5A36, 0xB0FA, 0x5A41, 0xB0FB, 0x5A49, 0xB0FC, 0x5A66, + 0xB0FD, 0x5A6A, 0xB0FE, 0x5A40, 0xB140, 0x5A3C, 0xB141, 0x5A62, 0xB142, 0x5A5A, 0xB143, 0x5A46, 0xB144, 0x5A4A, 0xB145, 0x5B70, + 0xB146, 0x5BC7, 0xB147, 0x5BC5, 0xB148, 0x5BC4, 0xB149, 0x5BC2, 0xB14A, 0x5BBF, 0xB14B, 0x5BC6, 0xB14C, 0x5C09, 0xB14D, 0x5C08, + 0xB14E, 0x5C07, 0xB14F, 0x5C60, 0xB150, 0x5C5C, 0xB151, 0x5C5D, 0xB152, 0x5D07, 0xB153, 0x5D06, 0xB154, 0x5D0E, 0xB155, 0x5D1B, + 0xB156, 0x5D16, 0xB157, 0x5D22, 0xB158, 0x5D11, 0xB159, 0x5D29, 0xB15A, 0x5D14, 0xB15B, 0x5D19, 0xB15C, 0x5D24, 0xB15D, 0x5D27, + 0xB15E, 0x5D17, 0xB15F, 0x5DE2, 0xB160, 0x5E38, 0xB161, 0x5E36, 0xB162, 0x5E33, 0xB163, 0x5E37, 0xB164, 0x5EB7, 0xB165, 0x5EB8, + 0xB166, 0x5EB6, 0xB167, 0x5EB5, 0xB168, 0x5EBE, 0xB169, 0x5F35, 0xB16A, 0x5F37, 0xB16B, 0x5F57, 0xB16C, 0x5F6C, 0xB16D, 0x5F69, + 0xB16E, 0x5F6B, 0xB16F, 0x5F97, 0xB170, 0x5F99, 0xB171, 0x5F9E, 0xB172, 0x5F98, 0xB173, 0x5FA1, 0xB174, 0x5FA0, 0xB175, 0x5F9C, + 0xB176, 0x607F, 0xB177, 0x60A3, 0xB178, 0x6089, 0xB179, 0x60A0, 0xB17A, 0x60A8, 0xB17B, 0x60CB, 0xB17C, 0x60B4, 0xB17D, 0x60E6, + 0xB17E, 0x60BD, 0xB1A1, 0x60C5, 0xB1A2, 0x60BB, 0xB1A3, 0x60B5, 0xB1A4, 0x60DC, 0xB1A5, 0x60BC, 0xB1A6, 0x60D8, 0xB1A7, 0x60D5, + 0xB1A8, 0x60C6, 0xB1A9, 0x60DF, 0xB1AA, 0x60B8, 0xB1AB, 0x60DA, 0xB1AC, 0x60C7, 0xB1AD, 0x621A, 0xB1AE, 0x621B, 0xB1AF, 0x6248, + 0xB1B0, 0x63A0, 0xB1B1, 0x63A7, 0xB1B2, 0x6372, 0xB1B3, 0x6396, 0xB1B4, 0x63A2, 0xB1B5, 0x63A5, 0xB1B6, 0x6377, 0xB1B7, 0x6367, + 0xB1B8, 0x6398, 0xB1B9, 0x63AA, 0xB1BA, 0x6371, 0xB1BB, 0x63A9, 0xB1BC, 0x6389, 0xB1BD, 0x6383, 0xB1BE, 0x639B, 0xB1BF, 0x636B, + 0xB1C0, 0x63A8, 0xB1C1, 0x6384, 0xB1C2, 0x6388, 0xB1C3, 0x6399, 0xB1C4, 0x63A1, 0xB1C5, 0x63AC, 0xB1C6, 0x6392, 0xB1C7, 0x638F, + 0xB1C8, 0x6380, 0xB1C9, 0x637B, 0xB1CA, 0x6369, 0xB1CB, 0x6368, 0xB1CC, 0x637A, 0xB1CD, 0x655D, 0xB1CE, 0x6556, 0xB1CF, 0x6551, + 0xB1D0, 0x6559, 0xB1D1, 0x6557, 0xB1D2, 0x555F, 0xB1D3, 0x654F, 0xB1D4, 0x6558, 0xB1D5, 0x6555, 0xB1D6, 0x6554, 0xB1D7, 0x659C, + 0xB1D8, 0x659B, 0xB1D9, 0x65AC, 0xB1DA, 0x65CF, 0xB1DB, 0x65CB, 0xB1DC, 0x65CC, 0xB1DD, 0x65CE, 0xB1DE, 0x665D, 0xB1DF, 0x665A, + 0xB1E0, 0x6664, 0xB1E1, 0x6668, 0xB1E2, 0x6666, 0xB1E3, 0x665E, 0xB1E4, 0x66F9, 0xB1E5, 0x52D7, 0xB1E6, 0x671B, 0xB1E7, 0x6881, + 0xB1E8, 0x68AF, 0xB1E9, 0x68A2, 0xB1EA, 0x6893, 0xB1EB, 0x68B5, 0xB1EC, 0x687F, 0xB1ED, 0x6876, 0xB1EE, 0x68B1, 0xB1EF, 0x68A7, + 0xB1F0, 0x6897, 0xB1F1, 0x68B0, 0xB1F2, 0x6883, 0xB1F3, 0x68C4, 0xB1F4, 0x68AD, 0xB1F5, 0x6886, 0xB1F6, 0x6885, 0xB1F7, 0x6894, + 0xB1F8, 0x689D, 0xB1F9, 0x68A8, 0xB1FA, 0x689F, 0xB1FB, 0x68A1, 0xB1FC, 0x6882, 0xB1FD, 0x6B32, 0xB1FE, 0x6BBA, 0xB240, 0x6BEB, + 0xB241, 0x6BEC, 0xB242, 0x6C2B, 0xB243, 0x6D8E, 0xB244, 0x6DBC, 0xB245, 0x6DF3, 0xB246, 0x6DD9, 0xB247, 0x6DB2, 0xB248, 0x6DE1, + 0xB249, 0x6DCC, 0xB24A, 0x6DE4, 0xB24B, 0x6DFB, 0xB24C, 0x6DFA, 0xB24D, 0x6E05, 0xB24E, 0x6DC7, 0xB24F, 0x6DCB, 0xB250, 0x6DAF, + 0xB251, 0x6DD1, 0xB252, 0x6DAE, 0xB253, 0x6DDE, 0xB254, 0x6DF9, 0xB255, 0x6DB8, 0xB256, 0x6DF7, 0xB257, 0x6DF5, 0xB258, 0x6DC5, + 0xB259, 0x6DD2, 0xB25A, 0x6E1A, 0xB25B, 0x6DB5, 0xB25C, 0x6DDA, 0xB25D, 0x6DEB, 0xB25E, 0x6DD8, 0xB25F, 0x6DEA, 0xB260, 0x6DF1, + 0xB261, 0x6DEE, 0xB262, 0x6DE8, 0xB263, 0x6DC6, 0xB264, 0x6DC4, 0xB265, 0x6DAA, 0xB266, 0x6DEC, 0xB267, 0x6DBF, 0xB268, 0x6DE6, + 0xB269, 0x70F9, 0xB26A, 0x7109, 0xB26B, 0x710A, 0xB26C, 0x70FD, 0xB26D, 0x70EF, 0xB26E, 0x723D, 0xB26F, 0x727D, 0xB270, 0x7281, + 0xB271, 0x731C, 0xB272, 0x731B, 0xB273, 0x7316, 0xB274, 0x7313, 0xB275, 0x7319, 0xB276, 0x7387, 0xB277, 0x7405, 0xB278, 0x740A, + 0xB279, 0x7403, 0xB27A, 0x7406, 0xB27B, 0x73FE, 0xB27C, 0x740D, 0xB27D, 0x74E0, 0xB27E, 0x74F6, 0xB2A1, 0x74F7, 0xB2A2, 0x751C, + 0xB2A3, 0x7522, 0xB2A4, 0x7565, 0xB2A5, 0x7566, 0xB2A6, 0x7562, 0xB2A7, 0x7570, 0xB2A8, 0x758F, 0xB2A9, 0x75D4, 0xB2AA, 0x75D5, + 0xB2AB, 0x75B5, 0xB2AC, 0x75CA, 0xB2AD, 0x75CD, 0xB2AE, 0x768E, 0xB2AF, 0x76D4, 0xB2B0, 0x76D2, 0xB2B1, 0x76DB, 0xB2B2, 0x7737, + 0xB2B3, 0x773E, 0xB2B4, 0x773C, 0xB2B5, 0x7736, 0xB2B6, 0x7738, 0xB2B7, 0x773A, 0xB2B8, 0x786B, 0xB2B9, 0x7843, 0xB2BA, 0x784E, + 0xB2BB, 0x7965, 0xB2BC, 0x7968, 0xB2BD, 0x796D, 0xB2BE, 0x79FB, 0xB2BF, 0x7A92, 0xB2C0, 0x7A95, 0xB2C1, 0x7B20, 0xB2C2, 0x7B28, + 0xB2C3, 0x7B1B, 0xB2C4, 0x7B2C, 0xB2C5, 0x7B26, 0xB2C6, 0x7B19, 0xB2C7, 0x7B1E, 0xB2C8, 0x7B2E, 0xB2C9, 0x7C92, 0xB2CA, 0x7C97, + 0xB2CB, 0x7C95, 0xB2CC, 0x7D46, 0xB2CD, 0x7D43, 0xB2CE, 0x7D71, 0xB2CF, 0x7D2E, 0xB2D0, 0x7D39, 0xB2D1, 0x7D3C, 0xB2D2, 0x7D40, + 0xB2D3, 0x7D30, 0xB2D4, 0x7D33, 0xB2D5, 0x7D44, 0xB2D6, 0x7D2F, 0xB2D7, 0x7D42, 0xB2D8, 0x7D32, 0xB2D9, 0x7D31, 0xB2DA, 0x7F3D, + 0xB2DB, 0x7F9E, 0xB2DC, 0x7F9A, 0xB2DD, 0x7FCC, 0xB2DE, 0x7FCE, 0xB2DF, 0x7FD2, 0xB2E0, 0x801C, 0xB2E1, 0x804A, 0xB2E2, 0x8046, + 0xB2E3, 0x812F, 0xB2E4, 0x8116, 0xB2E5, 0x8123, 0xB2E6, 0x812B, 0xB2E7, 0x8129, 0xB2E8, 0x8130, 0xB2E9, 0x8124, 0xB2EA, 0x8202, + 0xB2EB, 0x8235, 0xB2EC, 0x8237, 0xB2ED, 0x8236, 0xB2EE, 0x8239, 0xB2EF, 0x838E, 0xB2F0, 0x839E, 0xB2F1, 0x8398, 0xB2F2, 0x8378, + 0xB2F3, 0x83A2, 0xB2F4, 0x8396, 0xB2F5, 0x83BD, 0xB2F6, 0x83AB, 0xB2F7, 0x8392, 0xB2F8, 0x838A, 0xB2F9, 0x8393, 0xB2FA, 0x8389, + 0xB2FB, 0x83A0, 0xB2FC, 0x8377, 0xB2FD, 0x837B, 0xB2FE, 0x837C, 0xB340, 0x8386, 0xB341, 0x83A7, 0xB342, 0x8655, 0xB343, 0x5F6A, + 0xB344, 0x86C7, 0xB345, 0x86C0, 0xB346, 0x86B6, 0xB347, 0x86C4, 0xB348, 0x86B5, 0xB349, 0x86C6, 0xB34A, 0x86CB, 0xB34B, 0x86B1, + 0xB34C, 0x86AF, 0xB34D, 0x86C9, 0xB34E, 0x8853, 0xB34F, 0x889E, 0xB350, 0x8888, 0xB351, 0x88AB, 0xB352, 0x8892, 0xB353, 0x8896, + 0xB354, 0x888D, 0xB355, 0x888B, 0xB356, 0x8993, 0xB357, 0x898F, 0xB358, 0x8A2A, 0xB359, 0x8A1D, 0xB35A, 0x8A23, 0xB35B, 0x8A25, + 0xB35C, 0x8A31, 0xB35D, 0x8A2D, 0xB35E, 0x8A1F, 0xB35F, 0x8A1B, 0xB360, 0x8A22, 0xB361, 0x8C49, 0xB362, 0x8C5A, 0xB363, 0x8CA9, + 0xB364, 0x8CAC, 0xB365, 0x8CAB, 0xB366, 0x8CA8, 0xB367, 0x8CAA, 0xB368, 0x8CA7, 0xB369, 0x8D67, 0xB36A, 0x8D66, 0xB36B, 0x8DBE, + 0xB36C, 0x8DBA, 0xB36D, 0x8EDB, 0xB36E, 0x8EDF, 0xB36F, 0x9019, 0xB370, 0x900D, 0xB371, 0x901A, 0xB372, 0x9017, 0xB373, 0x9023, + 0xB374, 0x901F, 0xB375, 0x901D, 0xB376, 0x9010, 0xB377, 0x9015, 0xB378, 0x901E, 0xB379, 0x9020, 0xB37A, 0x900F, 0xB37B, 0x9022, + 0xB37C, 0x9016, 0xB37D, 0x901B, 0xB37E, 0x9014, 0xB3A1, 0x90E8, 0xB3A2, 0x90ED, 0xB3A3, 0x90FD, 0xB3A4, 0x9157, 0xB3A5, 0x91CE, + 0xB3A6, 0x91F5, 0xB3A7, 0x91E6, 0xB3A8, 0x91E3, 0xB3A9, 0x91E7, 0xB3AA, 0x91ED, 0xB3AB, 0x91E9, 0xB3AC, 0x9589, 0xB3AD, 0x966A, + 0xB3AE, 0x9675, 0xB3AF, 0x9673, 0xB3B0, 0x9678, 0xB3B1, 0x9670, 0xB3B2, 0x9674, 0xB3B3, 0x9676, 0xB3B4, 0x9677, 0xB3B5, 0x966C, + 0xB3B6, 0x96C0, 0xB3B7, 0x96EA, 0xB3B8, 0x96E9, 0xB3B9, 0x7AE0, 0xB3BA, 0x7ADF, 0xB3BB, 0x9802, 0xB3BC, 0x9803, 0xB3BD, 0x9B5A, + 0xB3BE, 0x9CE5, 0xB3BF, 0x9E75, 0xB3C0, 0x9E7F, 0xB3C1, 0x9EA5, 0xB3C2, 0x9EBB, 0xB3C3, 0x50A2, 0xB3C4, 0x508D, 0xB3C5, 0x5085, + 0xB3C6, 0x5099, 0xB3C7, 0x5091, 0xB3C8, 0x5080, 0xB3C9, 0x5096, 0xB3CA, 0x5098, 0xB3CB, 0x509A, 0xB3CC, 0x6700, 0xB3CD, 0x51F1, + 0xB3CE, 0x5272, 0xB3CF, 0x5274, 0xB3D0, 0x5275, 0xB3D1, 0x5269, 0xB3D2, 0x52DE, 0xB3D3, 0x52DD, 0xB3D4, 0x52DB, 0xB3D5, 0x535A, + 0xB3D6, 0x53A5, 0xB3D7, 0x557B, 0xB3D8, 0x5580, 0xB3D9, 0x55A7, 0xB3DA, 0x557C, 0xB3DB, 0x558A, 0xB3DC, 0x559D, 0xB3DD, 0x5598, + 0xB3DE, 0x5582, 0xB3DF, 0x559C, 0xB3E0, 0x55AA, 0xB3E1, 0x5594, 0xB3E2, 0x5587, 0xB3E3, 0x558B, 0xB3E4, 0x5583, 0xB3E5, 0x55B3, + 0xB3E6, 0x55AE, 0xB3E7, 0x559F, 0xB3E8, 0x553E, 0xB3E9, 0x55B2, 0xB3EA, 0x559A, 0xB3EB, 0x55BB, 0xB3EC, 0x55AC, 0xB3ED, 0x55B1, + 0xB3EE, 0x557E, 0xB3EF, 0x5589, 0xB3F0, 0x55AB, 0xB3F1, 0x5599, 0xB3F2, 0x570D, 0xB3F3, 0x582F, 0xB3F4, 0x582A, 0xB3F5, 0x5834, + 0xB3F6, 0x5824, 0xB3F7, 0x5830, 0xB3F8, 0x5831, 0xB3F9, 0x5821, 0xB3FA, 0x581D, 0xB3FB, 0x5820, 0xB3FC, 0x58F9, 0xB3FD, 0x58FA, + 0xB3FE, 0x5960, 0xB440, 0x5A77, 0xB441, 0x5A9A, 0xB442, 0x5A7F, 0xB443, 0x5A92, 0xB444, 0x5A9B, 0xB445, 0x5AA7, 0xB446, 0x5B73, + 0xB447, 0x5B71, 0xB448, 0x5BD2, 0xB449, 0x5BCC, 0xB44A, 0x5BD3, 0xB44B, 0x5BD0, 0xB44C, 0x5C0A, 0xB44D, 0x5C0B, 0xB44E, 0x5C31, + 0xB44F, 0x5D4C, 0xB450, 0x5D50, 0xB451, 0x5D34, 0xB452, 0x5D47, 0xB453, 0x5DFD, 0xB454, 0x5E45, 0xB455, 0x5E3D, 0xB456, 0x5E40, + 0xB457, 0x5E43, 0xB458, 0x5E7E, 0xB459, 0x5ECA, 0xB45A, 0x5EC1, 0xB45B, 0x5EC2, 0xB45C, 0x5EC4, 0xB45D, 0x5F3C, 0xB45E, 0x5F6D, + 0xB45F, 0x5FA9, 0xB460, 0x5FAA, 0xB461, 0x5FA8, 0xB462, 0x60D1, 0xB463, 0x60E1, 0xB464, 0x60B2, 0xB465, 0x60B6, 0xB466, 0x60E0, + 0xB467, 0x611C, 0xB468, 0x6123, 0xB469, 0x60FA, 0xB46A, 0x6115, 0xB46B, 0x60F0, 0xB46C, 0x60FB, 0xB46D, 0x60F4, 0xB46E, 0x6168, + 0xB46F, 0x60F1, 0xB470, 0x610E, 0xB471, 0x60F6, 0xB472, 0x6109, 0xB473, 0x6100, 0xB474, 0x6112, 0xB475, 0x621F, 0xB476, 0x6249, + 0xB477, 0x63A3, 0xB478, 0x638C, 0xB479, 0x63CF, 0xB47A, 0x63C0, 0xB47B, 0x63E9, 0xB47C, 0x63C9, 0xB47D, 0x63C6, 0xB47E, 0x63CD, + 0xB4A1, 0x63D2, 0xB4A2, 0x63E3, 0xB4A3, 0x63D0, 0xB4A4, 0x63E1, 0xB4A5, 0x63D6, 0xB4A6, 0x63ED, 0xB4A7, 0x63EE, 0xB4A8, 0x6376, + 0xB4A9, 0x63F4, 0xB4AA, 0x63EA, 0xB4AB, 0x63DB, 0xB4AC, 0x6452, 0xB4AD, 0x63DA, 0xB4AE, 0x63F9, 0xB4AF, 0x655E, 0xB4B0, 0x6566, + 0xB4B1, 0x6562, 0xB4B2, 0x6563, 0xB4B3, 0x6591, 0xB4B4, 0x6590, 0xB4B5, 0x65AF, 0xB4B6, 0x666E, 0xB4B7, 0x6670, 0xB4B8, 0x6674, + 0xB4B9, 0x6676, 0xB4BA, 0x666F, 0xB4BB, 0x6691, 0xB4BC, 0x667A, 0xB4BD, 0x667E, 0xB4BE, 0x6677, 0xB4BF, 0x66FE, 0xB4C0, 0x66FF, + 0xB4C1, 0x671F, 0xB4C2, 0x671D, 0xB4C3, 0x68FA, 0xB4C4, 0x68D5, 0xB4C5, 0x68E0, 0xB4C6, 0x68D8, 0xB4C7, 0x68D7, 0xB4C8, 0x6905, + 0xB4C9, 0x68DF, 0xB4CA, 0x68F5, 0xB4CB, 0x68EE, 0xB4CC, 0x68E7, 0xB4CD, 0x68F9, 0xB4CE, 0x68D2, 0xB4CF, 0x68F2, 0xB4D0, 0x68E3, + 0xB4D1, 0x68CB, 0xB4D2, 0x68CD, 0xB4D3, 0x690D, 0xB4D4, 0x6912, 0xB4D5, 0x690E, 0xB4D6, 0x68C9, 0xB4D7, 0x68DA, 0xB4D8, 0x696E, + 0xB4D9, 0x68FB, 0xB4DA, 0x6B3E, 0xB4DB, 0x6B3A, 0xB4DC, 0x6B3D, 0xB4DD, 0x6B98, 0xB4DE, 0x6B96, 0xB4DF, 0x6BBC, 0xB4E0, 0x6BEF, + 0xB4E1, 0x6C2E, 0xB4E2, 0x6C2F, 0xB4E3, 0x6C2C, 0xB4E4, 0x6E2F, 0xB4E5, 0x6E38, 0xB4E6, 0x6E54, 0xB4E7, 0x6E21, 0xB4E8, 0x6E32, + 0xB4E9, 0x6E67, 0xB4EA, 0x6E4A, 0xB4EB, 0x6E20, 0xB4EC, 0x6E25, 0xB4ED, 0x6E23, 0xB4EE, 0x6E1B, 0xB4EF, 0x6E5B, 0xB4F0, 0x6E58, + 0xB4F1, 0x6E24, 0xB4F2, 0x6E56, 0xB4F3, 0x6E6E, 0xB4F4, 0x6E2D, 0xB4F5, 0x6E26, 0xB4F6, 0x6E6F, 0xB4F7, 0x6E34, 0xB4F8, 0x6E4D, + 0xB4F9, 0x6E3A, 0xB4FA, 0x6E2C, 0xB4FB, 0x6E43, 0xB4FC, 0x6E1D, 0xB4FD, 0x6E3E, 0xB4FE, 0x6ECB, 0xB540, 0x6E89, 0xB541, 0x6E19, + 0xB542, 0x6E4E, 0xB543, 0x6E63, 0xB544, 0x6E44, 0xB545, 0x6E72, 0xB546, 0x6E69, 0xB547, 0x6E5F, 0xB548, 0x7119, 0xB549, 0x711A, + 0xB54A, 0x7126, 0xB54B, 0x7130, 0xB54C, 0x7121, 0xB54D, 0x7136, 0xB54E, 0x716E, 0xB54F, 0x711C, 0xB550, 0x724C, 0xB551, 0x7284, + 0xB552, 0x7280, 0xB553, 0x7336, 0xB554, 0x7325, 0xB555, 0x7334, 0xB556, 0x7329, 0xB557, 0x743A, 0xB558, 0x742A, 0xB559, 0x7433, + 0xB55A, 0x7422, 0xB55B, 0x7425, 0xB55C, 0x7435, 0xB55D, 0x7436, 0xB55E, 0x7434, 0xB55F, 0x742F, 0xB560, 0x741B, 0xB561, 0x7426, + 0xB562, 0x7428, 0xB563, 0x7525, 0xB564, 0x7526, 0xB565, 0x756B, 0xB566, 0x756A, 0xB567, 0x75E2, 0xB568, 0x75DB, 0xB569, 0x75E3, + 0xB56A, 0x75D9, 0xB56B, 0x75D8, 0xB56C, 0x75DE, 0xB56D, 0x75E0, 0xB56E, 0x767B, 0xB56F, 0x767C, 0xB570, 0x7696, 0xB571, 0x7693, + 0xB572, 0x76B4, 0xB573, 0x76DC, 0xB574, 0x774F, 0xB575, 0x77ED, 0xB576, 0x785D, 0xB577, 0x786C, 0xB578, 0x786F, 0xB579, 0x7A0D, + 0xB57A, 0x7A08, 0xB57B, 0x7A0B, 0xB57C, 0x7A05, 0xB57D, 0x7A00, 0xB57E, 0x7A98, 0xB5A1, 0x7A97, 0xB5A2, 0x7A96, 0xB5A3, 0x7AE5, + 0xB5A4, 0x7AE3, 0xB5A5, 0x7B49, 0xB5A6, 0x7B56, 0xB5A7, 0x7B46, 0xB5A8, 0x7B50, 0xB5A9, 0x7B52, 0xB5AA, 0x7B54, 0xB5AB, 0x7B4D, + 0xB5AC, 0x7B4B, 0xB5AD, 0x7B4F, 0xB5AE, 0x7B51, 0xB5AF, 0x7C9F, 0xB5B0, 0x7CA5, 0xB5B1, 0x7D5E, 0xB5B2, 0x7D50, 0xB5B3, 0x7D68, + 0xB5B4, 0x7D55, 0xB5B5, 0x7D2B, 0xB5B6, 0x7D6E, 0xB5B7, 0x7D72, 0xB5B8, 0x7D61, 0xB5B9, 0x7D66, 0xB5BA, 0x7D62, 0xB5BB, 0x7D70, + 0xB5BC, 0x7D73, 0xB5BD, 0x5584, 0xB5BE, 0x7FD4, 0xB5BF, 0x7FD5, 0xB5C0, 0x800B, 0xB5C1, 0x8052, 0xB5C2, 0x8085, 0xB5C3, 0x8155, + 0xB5C4, 0x8154, 0xB5C5, 0x814B, 0xB5C6, 0x8151, 0xB5C7, 0x814E, 0xB5C8, 0x8139, 0xB5C9, 0x8146, 0xB5CA, 0x813E, 0xB5CB, 0x814C, + 0xB5CC, 0x8153, 0xB5CD, 0x8174, 0xB5CE, 0x8212, 0xB5CF, 0x821C, 0xB5D0, 0x83E9, 0xB5D1, 0x8403, 0xB5D2, 0x83F8, 0xB5D3, 0x840D, + 0xB5D4, 0x83E0, 0xB5D5, 0x83C5, 0xB5D6, 0x840B, 0xB5D7, 0x83C1, 0xB5D8, 0x83EF, 0xB5D9, 0x83F1, 0xB5DA, 0x83F4, 0xB5DB, 0x8457, + 0xB5DC, 0x840A, 0xB5DD, 0x83F0, 0xB5DE, 0x840C, 0xB5DF, 0x83CC, 0xB5E0, 0x83FD, 0xB5E1, 0x83F2, 0xB5E2, 0x83CA, 0xB5E3, 0x8438, + 0xB5E4, 0x840E, 0xB5E5, 0x8404, 0xB5E6, 0x83DC, 0xB5E7, 0x8407, 0xB5E8, 0x83D4, 0xB5E9, 0x83DF, 0xB5EA, 0x865B, 0xB5EB, 0x86DF, + 0xB5EC, 0x86D9, 0xB5ED, 0x86ED, 0xB5EE, 0x86D4, 0xB5EF, 0x86DB, 0xB5F0, 0x86E4, 0xB5F1, 0x86D0, 0xB5F2, 0x86DE, 0xB5F3, 0x8857, + 0xB5F4, 0x88C1, 0xB5F5, 0x88C2, 0xB5F6, 0x88B1, 0xB5F7, 0x8983, 0xB5F8, 0x8996, 0xB5F9, 0x8A3B, 0xB5FA, 0x8A60, 0xB5FB, 0x8A55, + 0xB5FC, 0x8A5E, 0xB5FD, 0x8A3C, 0xB5FE, 0x8A41, 0xB640, 0x8A54, 0xB641, 0x8A5B, 0xB642, 0x8A50, 0xB643, 0x8A46, 0xB644, 0x8A34, + 0xB645, 0x8A3A, 0xB646, 0x8A36, 0xB647, 0x8A56, 0xB648, 0x8C61, 0xB649, 0x8C82, 0xB64A, 0x8CAF, 0xB64B, 0x8CBC, 0xB64C, 0x8CB3, + 0xB64D, 0x8CBD, 0xB64E, 0x8CC1, 0xB64F, 0x8CBB, 0xB650, 0x8CC0, 0xB651, 0x8CB4, 0xB652, 0x8CB7, 0xB653, 0x8CB6, 0xB654, 0x8CBF, + 0xB655, 0x8CB8, 0xB656, 0x8D8A, 0xB657, 0x8D85, 0xB658, 0x8D81, 0xB659, 0x8DCE, 0xB65A, 0x8DDD, 0xB65B, 0x8DCB, 0xB65C, 0x8DDA, + 0xB65D, 0x8DD1, 0xB65E, 0x8DCC, 0xB65F, 0x8DDB, 0xB660, 0x8DC6, 0xB661, 0x8EFB, 0xB662, 0x8EF8, 0xB663, 0x8EFC, 0xB664, 0x8F9C, + 0xB665, 0x902E, 0xB666, 0x9035, 0xB667, 0x9031, 0xB668, 0x9038, 0xB669, 0x9032, 0xB66A, 0x9036, 0xB66B, 0x9102, 0xB66C, 0x90F5, + 0xB66D, 0x9109, 0xB66E, 0x90FE, 0xB66F, 0x9163, 0xB670, 0x9165, 0xB671, 0x91CF, 0xB672, 0x9214, 0xB673, 0x9215, 0xB674, 0x9223, + 0xB675, 0x9209, 0xB676, 0x921E, 0xB677, 0x920D, 0xB678, 0x9210, 0xB679, 0x9207, 0xB67A, 0x9211, 0xB67B, 0x9594, 0xB67C, 0x958F, + 0xB67D, 0x958B, 0xB67E, 0x9591, 0xB6A1, 0x9593, 0xB6A2, 0x9592, 0xB6A3, 0x958E, 0xB6A4, 0x968A, 0xB6A5, 0x968E, 0xB6A6, 0x968B, + 0xB6A7, 0x967D, 0xB6A8, 0x9685, 0xB6A9, 0x9686, 0xB6AA, 0x968D, 0xB6AB, 0x9672, 0xB6AC, 0x9684, 0xB6AD, 0x96C1, 0xB6AE, 0x96C5, + 0xB6AF, 0x96C4, 0xB6B0, 0x96C6, 0xB6B1, 0x96C7, 0xB6B2, 0x96EF, 0xB6B3, 0x96F2, 0xB6B4, 0x97CC, 0xB6B5, 0x9805, 0xB6B6, 0x9806, + 0xB6B7, 0x9808, 0xB6B8, 0x98E7, 0xB6B9, 0x98EA, 0xB6BA, 0x98EF, 0xB6BB, 0x98E9, 0xB6BC, 0x98F2, 0xB6BD, 0x98ED, 0xB6BE, 0x99AE, + 0xB6BF, 0x99AD, 0xB6C0, 0x9EC3, 0xB6C1, 0x9ECD, 0xB6C2, 0x9ED1, 0xB6C3, 0x4E82, 0xB6C4, 0x50AD, 0xB6C5, 0x50B5, 0xB6C6, 0x50B2, + 0xB6C7, 0x50B3, 0xB6C8, 0x50C5, 0xB6C9, 0x50BE, 0xB6CA, 0x50AC, 0xB6CB, 0x50B7, 0xB6CC, 0x50BB, 0xB6CD, 0x50AF, 0xB6CE, 0x50C7, + 0xB6CF, 0x527F, 0xB6D0, 0x5277, 0xB6D1, 0x527D, 0xB6D2, 0x52DF, 0xB6D3, 0x52E6, 0xB6D4, 0x52E4, 0xB6D5, 0x52E2, 0xB6D6, 0x52E3, + 0xB6D7, 0x532F, 0xB6D8, 0x55DF, 0xB6D9, 0x55E8, 0xB6DA, 0x55D3, 0xB6DB, 0x55E6, 0xB6DC, 0x55CE, 0xB6DD, 0x55DC, 0xB6DE, 0x55C7, + 0xB6DF, 0x55D1, 0xB6E0, 0x55E3, 0xB6E1, 0x55E4, 0xB6E2, 0x55EF, 0xB6E3, 0x55DA, 0xB6E4, 0x55E1, 0xB6E5, 0x55C5, 0xB6E6, 0x55C6, + 0xB6E7, 0x55E5, 0xB6E8, 0x55C9, 0xB6E9, 0x5712, 0xB6EA, 0x5713, 0xB6EB, 0x585E, 0xB6EC, 0x5851, 0xB6ED, 0x5858, 0xB6EE, 0x5857, + 0xB6EF, 0x585A, 0xB6F0, 0x5854, 0xB6F1, 0x586B, 0xB6F2, 0x584C, 0xB6F3, 0x586D, 0xB6F4, 0x584A, 0xB6F5, 0x5862, 0xB6F6, 0x5852, + 0xB6F7, 0x584B, 0xB6F8, 0x5967, 0xB6F9, 0x5AC1, 0xB6FA, 0x5AC9, 0xB6FB, 0x5ACC, 0xB6FC, 0x5ABE, 0xB6FD, 0x5ABD, 0xB6FE, 0x5ABC, + 0xB740, 0x5AB3, 0xB741, 0x5AC2, 0xB742, 0x5AB2, 0xB743, 0x5D69, 0xB744, 0x5D6F, 0xB745, 0x5E4C, 0xB746, 0x5E79, 0xB747, 0x5EC9, + 0xB748, 0x5EC8, 0xB749, 0x5F12, 0xB74A, 0x5F59, 0xB74B, 0x5FAC, 0xB74C, 0x5FAE, 0xB74D, 0x611A, 0xB74E, 0x610F, 0xB74F, 0x6148, + 0xB750, 0x611F, 0xB751, 0x60F3, 0xB752, 0x611B, 0xB753, 0x60F9, 0xB754, 0x6101, 0xB755, 0x6108, 0xB756, 0x614E, 0xB757, 0x614C, + 0xB758, 0x6144, 0xB759, 0x614D, 0xB75A, 0x613E, 0xB75B, 0x6134, 0xB75C, 0x6127, 0xB75D, 0x610D, 0xB75E, 0x6106, 0xB75F, 0x6137, + 0xB760, 0x6221, 0xB761, 0x6222, 0xB762, 0x6413, 0xB763, 0x643E, 0xB764, 0x641E, 0xB765, 0x642A, 0xB766, 0x642D, 0xB767, 0x643D, + 0xB768, 0x642C, 0xB769, 0x640F, 0xB76A, 0x641C, 0xB76B, 0x6414, 0xB76C, 0x640D, 0xB76D, 0x6436, 0xB76E, 0x6416, 0xB76F, 0x6417, + 0xB770, 0x6406, 0xB771, 0x656C, 0xB772, 0x659F, 0xB773, 0x65B0, 0xB774, 0x6697, 0xB775, 0x6689, 0xB776, 0x6687, 0xB777, 0x6688, + 0xB778, 0x6696, 0xB779, 0x6684, 0xB77A, 0x6698, 0xB77B, 0x668D, 0xB77C, 0x6703, 0xB77D, 0x6994, 0xB77E, 0x696D, 0xB7A1, 0x695A, + 0xB7A2, 0x6977, 0xB7A3, 0x6960, 0xB7A4, 0x6954, 0xB7A5, 0x6975, 0xB7A6, 0x6930, 0xB7A7, 0x6982, 0xB7A8, 0x694A, 0xB7A9, 0x6968, + 0xB7AA, 0x696B, 0xB7AB, 0x695E, 0xB7AC, 0x6953, 0xB7AD, 0x6979, 0xB7AE, 0x6986, 0xB7AF, 0x695D, 0xB7B0, 0x6963, 0xB7B1, 0x695B, + 0xB7B2, 0x6B47, 0xB7B3, 0x6B72, 0xB7B4, 0x6BC0, 0xB7B5, 0x6BBF, 0xB7B6, 0x6BD3, 0xB7B7, 0x6BFD, 0xB7B8, 0x6EA2, 0xB7B9, 0x6EAF, + 0xB7BA, 0x6ED3, 0xB7BB, 0x6EB6, 0xB7BC, 0x6EC2, 0xB7BD, 0x6E90, 0xB7BE, 0x6E9D, 0xB7BF, 0x6EC7, 0xB7C0, 0x6EC5, 0xB7C1, 0x6EA5, + 0xB7C2, 0x6E98, 0xB7C3, 0x6EBC, 0xB7C4, 0x6EBA, 0xB7C5, 0x6EAB, 0xB7C6, 0x6ED1, 0xB7C7, 0x6E96, 0xB7C8, 0x6E9C, 0xB7C9, 0x6EC4, + 0xB7CA, 0x6ED4, 0xB7CB, 0x6EAA, 0xB7CC, 0x6EA7, 0xB7CD, 0x6EB4, 0xB7CE, 0x714E, 0xB7CF, 0x7159, 0xB7D0, 0x7169, 0xB7D1, 0x7164, + 0xB7D2, 0x7149, 0xB7D3, 0x7167, 0xB7D4, 0x715C, 0xB7D5, 0x716C, 0xB7D6, 0x7166, 0xB7D7, 0x714C, 0xB7D8, 0x7165, 0xB7D9, 0x715E, + 0xB7DA, 0x7146, 0xB7DB, 0x7168, 0xB7DC, 0x7156, 0xB7DD, 0x723A, 0xB7DE, 0x7252, 0xB7DF, 0x7337, 0xB7E0, 0x7345, 0xB7E1, 0x733F, + 0xB7E2, 0x733E, 0xB7E3, 0x746F, 0xB7E4, 0x745A, 0xB7E5, 0x7455, 0xB7E6, 0x745F, 0xB7E7, 0x745E, 0xB7E8, 0x7441, 0xB7E9, 0x743F, + 0xB7EA, 0x7459, 0xB7EB, 0x745B, 0xB7EC, 0x745C, 0xB7ED, 0x7576, 0xB7EE, 0x7578, 0xB7EF, 0x7600, 0xB7F0, 0x75F0, 0xB7F1, 0x7601, + 0xB7F2, 0x75F2, 0xB7F3, 0x75F1, 0xB7F4, 0x75FA, 0xB7F5, 0x75FF, 0xB7F6, 0x75F4, 0xB7F7, 0x75F3, 0xB7F8, 0x76DE, 0xB7F9, 0x76DF, + 0xB7FA, 0x775B, 0xB7FB, 0x776B, 0xB7FC, 0x7766, 0xB7FD, 0x775E, 0xB7FE, 0x7763, 0xB840, 0x7779, 0xB841, 0x776A, 0xB842, 0x776C, + 0xB843, 0x775C, 0xB844, 0x7765, 0xB845, 0x7768, 0xB846, 0x7762, 0xB847, 0x77EE, 0xB848, 0x788E, 0xB849, 0x78B0, 0xB84A, 0x7897, + 0xB84B, 0x7898, 0xB84C, 0x788C, 0xB84D, 0x7889, 0xB84E, 0x787C, 0xB84F, 0x7891, 0xB850, 0x7893, 0xB851, 0x787F, 0xB852, 0x797A, + 0xB853, 0x797F, 0xB854, 0x7981, 0xB855, 0x842C, 0xB856, 0x79BD, 0xB857, 0x7A1C, 0xB858, 0x7A1A, 0xB859, 0x7A20, 0xB85A, 0x7A14, + 0xB85B, 0x7A1F, 0xB85C, 0x7A1E, 0xB85D, 0x7A9F, 0xB85E, 0x7AA0, 0xB85F, 0x7B77, 0xB860, 0x7BC0, 0xB861, 0x7B60, 0xB862, 0x7B6E, + 0xB863, 0x7B67, 0xB864, 0x7CB1, 0xB865, 0x7CB3, 0xB866, 0x7CB5, 0xB867, 0x7D93, 0xB868, 0x7D79, 0xB869, 0x7D91, 0xB86A, 0x7D81, + 0xB86B, 0x7D8F, 0xB86C, 0x7D5B, 0xB86D, 0x7F6E, 0xB86E, 0x7F69, 0xB86F, 0x7F6A, 0xB870, 0x7F72, 0xB871, 0x7FA9, 0xB872, 0x7FA8, + 0xB873, 0x7FA4, 0xB874, 0x8056, 0xB875, 0x8058, 0xB876, 0x8086, 0xB877, 0x8084, 0xB878, 0x8171, 0xB879, 0x8170, 0xB87A, 0x8178, + 0xB87B, 0x8165, 0xB87C, 0x816E, 0xB87D, 0x8173, 0xB87E, 0x816B, 0xB8A1, 0x8179, 0xB8A2, 0x817A, 0xB8A3, 0x8166, 0xB8A4, 0x8205, + 0xB8A5, 0x8247, 0xB8A6, 0x8482, 0xB8A7, 0x8477, 0xB8A8, 0x843D, 0xB8A9, 0x8431, 0xB8AA, 0x8475, 0xB8AB, 0x8466, 0xB8AC, 0x846B, + 0xB8AD, 0x8449, 0xB8AE, 0x846C, 0xB8AF, 0x845B, 0xB8B0, 0x843C, 0xB8B1, 0x8435, 0xB8B2, 0x8461, 0xB8B3, 0x8463, 0xB8B4, 0x8469, + 0xB8B5, 0x846D, 0xB8B6, 0x8446, 0xB8B7, 0x865E, 0xB8B8, 0x865C, 0xB8B9, 0x865F, 0xB8BA, 0x86F9, 0xB8BB, 0x8713, 0xB8BC, 0x8708, + 0xB8BD, 0x8707, 0xB8BE, 0x8700, 0xB8BF, 0x86FE, 0xB8C0, 0x86FB, 0xB8C1, 0x8702, 0xB8C2, 0x8703, 0xB8C3, 0x8706, 0xB8C4, 0x870A, + 0xB8C5, 0x8859, 0xB8C6, 0x88DF, 0xB8C7, 0x88D4, 0xB8C8, 0x88D9, 0xB8C9, 0x88DC, 0xB8CA, 0x88D8, 0xB8CB, 0x88DD, 0xB8CC, 0x88E1, + 0xB8CD, 0x88CA, 0xB8CE, 0x88D5, 0xB8CF, 0x88D2, 0xB8D0, 0x899C, 0xB8D1, 0x89E3, 0xB8D2, 0x8A6B, 0xB8D3, 0x8A72, 0xB8D4, 0x8A73, + 0xB8D5, 0x8A66, 0xB8D6, 0x8A69, 0xB8D7, 0x8A70, 0xB8D8, 0x8A87, 0xB8D9, 0x8A7C, 0xB8DA, 0x8A63, 0xB8DB, 0x8AA0, 0xB8DC, 0x8A71, + 0xB8DD, 0x8A85, 0xB8DE, 0x8A6D, 0xB8DF, 0x8A62, 0xB8E0, 0x8A6E, 0xB8E1, 0x8A6C, 0xB8E2, 0x8A79, 0xB8E3, 0x8A7B, 0xB8E4, 0x8A3E, + 0xB8E5, 0x8A68, 0xB8E6, 0x8C62, 0xB8E7, 0x8C8A, 0xB8E8, 0x8C89, 0xB8E9, 0x8CCA, 0xB8EA, 0x8CC7, 0xB8EB, 0x8CC8, 0xB8EC, 0x8CC4, + 0xB8ED, 0x8CB2, 0xB8EE, 0x8CC3, 0xB8EF, 0x8CC2, 0xB8F0, 0x8CC5, 0xB8F1, 0x8DE1, 0xB8F2, 0x8DDF, 0xB8F3, 0x8DE8, 0xB8F4, 0x8DEF, + 0xB8F5, 0x8DF3, 0xB8F6, 0x8DFA, 0xB8F7, 0x8DEA, 0xB8F8, 0x8DE4, 0xB8F9, 0x8DE6, 0xB8FA, 0x8EB2, 0xB8FB, 0x8F03, 0xB8FC, 0x8F09, + 0xB8FD, 0x8EFE, 0xB8FE, 0x8F0A, 0xB940, 0x8F9F, 0xB941, 0x8FB2, 0xB942, 0x904B, 0xB943, 0x904A, 0xB944, 0x9053, 0xB945, 0x9042, + 0xB946, 0x9054, 0xB947, 0x903C, 0xB948, 0x9055, 0xB949, 0x9050, 0xB94A, 0x9047, 0xB94B, 0x904F, 0xB94C, 0x904E, 0xB94D, 0x904D, + 0xB94E, 0x9051, 0xB94F, 0x903E, 0xB950, 0x9041, 0xB951, 0x9112, 0xB952, 0x9117, 0xB953, 0x916C, 0xB954, 0x916A, 0xB955, 0x9169, + 0xB956, 0x91C9, 0xB957, 0x9237, 0xB958, 0x9257, 0xB959, 0x9238, 0xB95A, 0x923D, 0xB95B, 0x9240, 0xB95C, 0x923E, 0xB95D, 0x925B, + 0xB95E, 0x924B, 0xB95F, 0x9264, 0xB960, 0x9251, 0xB961, 0x9234, 0xB962, 0x9249, 0xB963, 0x924D, 0xB964, 0x9245, 0xB965, 0x9239, + 0xB966, 0x923F, 0xB967, 0x925A, 0xB968, 0x9598, 0xB969, 0x9698, 0xB96A, 0x9694, 0xB96B, 0x9695, 0xB96C, 0x96CD, 0xB96D, 0x96CB, + 0xB96E, 0x96C9, 0xB96F, 0x96CA, 0xB970, 0x96F7, 0xB971, 0x96FB, 0xB972, 0x96F9, 0xB973, 0x96F6, 0xB974, 0x9756, 0xB975, 0x9774, + 0xB976, 0x9776, 0xB977, 0x9810, 0xB978, 0x9811, 0xB979, 0x9813, 0xB97A, 0x980A, 0xB97B, 0x9812, 0xB97C, 0x980C, 0xB97D, 0x98FC, + 0xB97E, 0x98F4, 0xB9A1, 0x98FD, 0xB9A2, 0x98FE, 0xB9A3, 0x99B3, 0xB9A4, 0x99B1, 0xB9A5, 0x99B4, 0xB9A6, 0x9AE1, 0xB9A7, 0x9CE9, + 0xB9A8, 0x9E82, 0xB9A9, 0x9F0E, 0xB9AA, 0x9F13, 0xB9AB, 0x9F20, 0xB9AC, 0x50E7, 0xB9AD, 0x50EE, 0xB9AE, 0x50E5, 0xB9AF, 0x50D6, + 0xB9B0, 0x50ED, 0xB9B1, 0x50DA, 0xB9B2, 0x50D5, 0xB9B3, 0x50CF, 0xB9B4, 0x50D1, 0xB9B5, 0x50F1, 0xB9B6, 0x50CE, 0xB9B7, 0x50E9, + 0xB9B8, 0x5162, 0xB9B9, 0x51F3, 0xB9BA, 0x5283, 0xB9BB, 0x5282, 0xB9BC, 0x5331, 0xB9BD, 0x53AD, 0xB9BE, 0x55FE, 0xB9BF, 0x5600, + 0xB9C0, 0x561B, 0xB9C1, 0x5617, 0xB9C2, 0x55FD, 0xB9C3, 0x5614, 0xB9C4, 0x5606, 0xB9C5, 0x5609, 0xB9C6, 0x560D, 0xB9C7, 0x560E, + 0xB9C8, 0x55F7, 0xB9C9, 0x5616, 0xB9CA, 0x561F, 0xB9CB, 0x5608, 0xB9CC, 0x5610, 0xB9CD, 0x55F6, 0xB9CE, 0x5718, 0xB9CF, 0x5716, + 0xB9D0, 0x5875, 0xB9D1, 0x587E, 0xB9D2, 0x5883, 0xB9D3, 0x5893, 0xB9D4, 0x588A, 0xB9D5, 0x5879, 0xB9D6, 0x5885, 0xB9D7, 0x587D, + 0xB9D8, 0x58FD, 0xB9D9, 0x5925, 0xB9DA, 0x5922, 0xB9DB, 0x5924, 0xB9DC, 0x596A, 0xB9DD, 0x5969, 0xB9DE, 0x5AE1, 0xB9DF, 0x5AE6, + 0xB9E0, 0x5AE9, 0xB9E1, 0x5AD7, 0xB9E2, 0x5AD6, 0xB9E3, 0x5AD8, 0xB9E4, 0x5AE3, 0xB9E5, 0x5B75, 0xB9E6, 0x5BDE, 0xB9E7, 0x5BE7, + 0xB9E8, 0x5BE1, 0xB9E9, 0x5BE5, 0xB9EA, 0x5BE6, 0xB9EB, 0x5BE8, 0xB9EC, 0x5BE2, 0xB9ED, 0x5BE4, 0xB9EE, 0x5BDF, 0xB9EF, 0x5C0D, + 0xB9F0, 0x5C62, 0xB9F1, 0x5D84, 0xB9F2, 0x5D87, 0xB9F3, 0x5E5B, 0xB9F4, 0x5E63, 0xB9F5, 0x5E55, 0xB9F6, 0x5E57, 0xB9F7, 0x5E54, + 0xB9F8, 0x5ED3, 0xB9F9, 0x5ED6, 0xB9FA, 0x5F0A, 0xB9FB, 0x5F46, 0xB9FC, 0x5F70, 0xB9FD, 0x5FB9, 0xB9FE, 0x6147, 0xBA40, 0x613F, + 0xBA41, 0x614B, 0xBA42, 0x6177, 0xBA43, 0x6162, 0xBA44, 0x6163, 0xBA45, 0x615F, 0xBA46, 0x615A, 0xBA47, 0x6158, 0xBA48, 0x6175, + 0xBA49, 0x622A, 0xBA4A, 0x6487, 0xBA4B, 0x6458, 0xBA4C, 0x6454, 0xBA4D, 0x64A4, 0xBA4E, 0x6478, 0xBA4F, 0x645F, 0xBA50, 0x647A, + 0xBA51, 0x6451, 0xBA52, 0x6467, 0xBA53, 0x6434, 0xBA54, 0x646D, 0xBA55, 0x647B, 0xBA56, 0x6572, 0xBA57, 0x65A1, 0xBA58, 0x65D7, + 0xBA59, 0x65D6, 0xBA5A, 0x66A2, 0xBA5B, 0x66A8, 0xBA5C, 0x669D, 0xBA5D, 0x699C, 0xBA5E, 0x69A8, 0xBA5F, 0x6995, 0xBA60, 0x69C1, + 0xBA61, 0x69AE, 0xBA62, 0x69D3, 0xBA63, 0x69CB, 0xBA64, 0x699B, 0xBA65, 0x69B7, 0xBA66, 0x69BB, 0xBA67, 0x69AB, 0xBA68, 0x69B4, + 0xBA69, 0x69D0, 0xBA6A, 0x69CD, 0xBA6B, 0x69AD, 0xBA6C, 0x69CC, 0xBA6D, 0x69A6, 0xBA6E, 0x69C3, 0xBA6F, 0x69A3, 0xBA70, 0x6B49, + 0xBA71, 0x6B4C, 0xBA72, 0x6C33, 0xBA73, 0x6F33, 0xBA74, 0x6F14, 0xBA75, 0x6EFE, 0xBA76, 0x6F13, 0xBA77, 0x6EF4, 0xBA78, 0x6F29, + 0xBA79, 0x6F3E, 0xBA7A, 0x6F20, 0xBA7B, 0x6F2C, 0xBA7C, 0x6F0F, 0xBA7D, 0x6F02, 0xBA7E, 0x6F22, 0xBAA1, 0x6EFF, 0xBAA2, 0x6EEF, + 0xBAA3, 0x6F06, 0xBAA4, 0x6F31, 0xBAA5, 0x6F38, 0xBAA6, 0x6F32, 0xBAA7, 0x6F23, 0xBAA8, 0x6F15, 0xBAA9, 0x6F2B, 0xBAAA, 0x6F2F, + 0xBAAB, 0x6F88, 0xBAAC, 0x6F2A, 0xBAAD, 0x6EEC, 0xBAAE, 0x6F01, 0xBAAF, 0x6EF2, 0xBAB0, 0x6ECC, 0xBAB1, 0x6EF7, 0xBAB2, 0x7194, + 0xBAB3, 0x7199, 0xBAB4, 0x717D, 0xBAB5, 0x718A, 0xBAB6, 0x7184, 0xBAB7, 0x7192, 0xBAB8, 0x723E, 0xBAB9, 0x7292, 0xBABA, 0x7296, + 0xBABB, 0x7344, 0xBABC, 0x7350, 0xBABD, 0x7464, 0xBABE, 0x7463, 0xBABF, 0x746A, 0xBAC0, 0x7470, 0xBAC1, 0x746D, 0xBAC2, 0x7504, + 0xBAC3, 0x7591, 0xBAC4, 0x7627, 0xBAC5, 0x760D, 0xBAC6, 0x760B, 0xBAC7, 0x7609, 0xBAC8, 0x7613, 0xBAC9, 0x76E1, 0xBACA, 0x76E3, + 0xBACB, 0x7784, 0xBACC, 0x777D, 0xBACD, 0x777F, 0xBACE, 0x7761, 0xBACF, 0x78C1, 0xBAD0, 0x789F, 0xBAD1, 0x78A7, 0xBAD2, 0x78B3, + 0xBAD3, 0x78A9, 0xBAD4, 0x78A3, 0xBAD5, 0x798E, 0xBAD6, 0x798F, 0xBAD7, 0x798D, 0xBAD8, 0x7A2E, 0xBAD9, 0x7A31, 0xBADA, 0x7AAA, + 0xBADB, 0x7AA9, 0xBADC, 0x7AED, 0xBADD, 0x7AEF, 0xBADE, 0x7BA1, 0xBADF, 0x7B95, 0xBAE0, 0x7B8B, 0xBAE1, 0x7B75, 0xBAE2, 0x7B97, + 0xBAE3, 0x7B9D, 0xBAE4, 0x7B94, 0xBAE5, 0x7B8F, 0xBAE6, 0x7BB8, 0xBAE7, 0x7B87, 0xBAE8, 0x7B84, 0xBAE9, 0x7CB9, 0xBAEA, 0x7CBD, + 0xBAEB, 0x7CBE, 0xBAEC, 0x7DBB, 0xBAED, 0x7DB0, 0xBAEE, 0x7D9C, 0xBAEF, 0x7DBD, 0xBAF0, 0x7DBE, 0xBAF1, 0x7DA0, 0xBAF2, 0x7DCA, + 0xBAF3, 0x7DB4, 0xBAF4, 0x7DB2, 0xBAF5, 0x7DB1, 0xBAF6, 0x7DBA, 0xBAF7, 0x7DA2, 0xBAF8, 0x7DBF, 0xBAF9, 0x7DB5, 0xBAFA, 0x7DB8, + 0xBAFB, 0x7DAD, 0xBAFC, 0x7DD2, 0xBAFD, 0x7DC7, 0xBAFE, 0x7DAC, 0xBB40, 0x7F70, 0xBB41, 0x7FE0, 0xBB42, 0x7FE1, 0xBB43, 0x7FDF, + 0xBB44, 0x805E, 0xBB45, 0x805A, 0xBB46, 0x8087, 0xBB47, 0x8150, 0xBB48, 0x8180, 0xBB49, 0x818F, 0xBB4A, 0x8188, 0xBB4B, 0x818A, + 0xBB4C, 0x817F, 0xBB4D, 0x8182, 0xBB4E, 0x81E7, 0xBB4F, 0x81FA, 0xBB50, 0x8207, 0xBB51, 0x8214, 0xBB52, 0x821E, 0xBB53, 0x824B, + 0xBB54, 0x84C9, 0xBB55, 0x84BF, 0xBB56, 0x84C6, 0xBB57, 0x84C4, 0xBB58, 0x8499, 0xBB59, 0x849E, 0xBB5A, 0x84B2, 0xBB5B, 0x849C, + 0xBB5C, 0x84CB, 0xBB5D, 0x84B8, 0xBB5E, 0x84C0, 0xBB5F, 0x84D3, 0xBB60, 0x8490, 0xBB61, 0x84BC, 0xBB62, 0x84D1, 0xBB63, 0x84CA, + 0xBB64, 0x873F, 0xBB65, 0x871C, 0xBB66, 0x873B, 0xBB67, 0x8722, 0xBB68, 0x8725, 0xBB69, 0x8734, 0xBB6A, 0x8718, 0xBB6B, 0x8755, + 0xBB6C, 0x8737, 0xBB6D, 0x8729, 0xBB6E, 0x88F3, 0xBB6F, 0x8902, 0xBB70, 0x88F4, 0xBB71, 0x88F9, 0xBB72, 0x88F8, 0xBB73, 0x88FD, + 0xBB74, 0x88E8, 0xBB75, 0x891A, 0xBB76, 0x88EF, 0xBB77, 0x8AA6, 0xBB78, 0x8A8C, 0xBB79, 0x8A9E, 0xBB7A, 0x8AA3, 0xBB7B, 0x8A8D, + 0xBB7C, 0x8AA1, 0xBB7D, 0x8A93, 0xBB7E, 0x8AA4, 0xBBA1, 0x8AAA, 0xBBA2, 0x8AA5, 0xBBA3, 0x8AA8, 0xBBA4, 0x8A98, 0xBBA5, 0x8A91, + 0xBBA6, 0x8A9A, 0xBBA7, 0x8AA7, 0xBBA8, 0x8C6A, 0xBBA9, 0x8C8D, 0xBBAA, 0x8C8C, 0xBBAB, 0x8CD3, 0xBBAC, 0x8CD1, 0xBBAD, 0x8CD2, + 0xBBAE, 0x8D6B, 0xBBAF, 0x8D99, 0xBBB0, 0x8D95, 0xBBB1, 0x8DFC, 0xBBB2, 0x8F14, 0xBBB3, 0x8F12, 0xBBB4, 0x8F15, 0xBBB5, 0x8F13, + 0xBBB6, 0x8FA3, 0xBBB7, 0x9060, 0xBBB8, 0x9058, 0xBBB9, 0x905C, 0xBBBA, 0x9063, 0xBBBB, 0x9059, 0xBBBC, 0x905E, 0xBBBD, 0x9062, + 0xBBBE, 0x905D, 0xBBBF, 0x905B, 0xBBC0, 0x9119, 0xBBC1, 0x9118, 0xBBC2, 0x911E, 0xBBC3, 0x9175, 0xBBC4, 0x9178, 0xBBC5, 0x9177, + 0xBBC6, 0x9174, 0xBBC7, 0x9278, 0xBBC8, 0x9280, 0xBBC9, 0x9285, 0xBBCA, 0x9298, 0xBBCB, 0x9296, 0xBBCC, 0x927B, 0xBBCD, 0x9293, + 0xBBCE, 0x929C, 0xBBCF, 0x92A8, 0xBBD0, 0x927C, 0xBBD1, 0x9291, 0xBBD2, 0x95A1, 0xBBD3, 0x95A8, 0xBBD4, 0x95A9, 0xBBD5, 0x95A3, + 0xBBD6, 0x95A5, 0xBBD7, 0x95A4, 0xBBD8, 0x9699, 0xBBD9, 0x969C, 0xBBDA, 0x969B, 0xBBDB, 0x96CC, 0xBBDC, 0x96D2, 0xBBDD, 0x9700, + 0xBBDE, 0x977C, 0xBBDF, 0x9785, 0xBBE0, 0x97F6, 0xBBE1, 0x9817, 0xBBE2, 0x9818, 0xBBE3, 0x98AF, 0xBBE4, 0x98B1, 0xBBE5, 0x9903, + 0xBBE6, 0x9905, 0xBBE7, 0x990C, 0xBBE8, 0x9909, 0xBBE9, 0x99C1, 0xBBEA, 0x9AAF, 0xBBEB, 0x9AB0, 0xBBEC, 0x9AE6, 0xBBED, 0x9B41, + 0xBBEE, 0x9B42, 0xBBEF, 0x9CF4, 0xBBF0, 0x9CF6, 0xBBF1, 0x9CF3, 0xBBF2, 0x9EBC, 0xBBF3, 0x9F3B, 0xBBF4, 0x9F4A, 0xBBF5, 0x5104, + 0xBBF6, 0x5100, 0xBBF7, 0x50FB, 0xBBF8, 0x50F5, 0xBBF9, 0x50F9, 0xBBFA, 0x5102, 0xBBFB, 0x5108, 0xBBFC, 0x5109, 0xBBFD, 0x5105, + 0xBBFE, 0x51DC, 0xBC40, 0x5287, 0xBC41, 0x5288, 0xBC42, 0x5289, 0xBC43, 0x528D, 0xBC44, 0x528A, 0xBC45, 0x52F0, 0xBC46, 0x53B2, + 0xBC47, 0x562E, 0xBC48, 0x563B, 0xBC49, 0x5639, 0xBC4A, 0x5632, 0xBC4B, 0x563F, 0xBC4C, 0x5634, 0xBC4D, 0x5629, 0xBC4E, 0x5653, + 0xBC4F, 0x564E, 0xBC50, 0x5657, 0xBC51, 0x5674, 0xBC52, 0x5636, 0xBC53, 0x562F, 0xBC54, 0x5630, 0xBC55, 0x5880, 0xBC56, 0x589F, + 0xBC57, 0x589E, 0xBC58, 0x58B3, 0xBC59, 0x589C, 0xBC5A, 0x58AE, 0xBC5B, 0x58A9, 0xBC5C, 0x58A6, 0xBC5D, 0x596D, 0xBC5E, 0x5B09, + 0xBC5F, 0x5AFB, 0xBC60, 0x5B0B, 0xBC61, 0x5AF5, 0xBC62, 0x5B0C, 0xBC63, 0x5B08, 0xBC64, 0x5BEE, 0xBC65, 0x5BEC, 0xBC66, 0x5BE9, + 0xBC67, 0x5BEB, 0xBC68, 0x5C64, 0xBC69, 0x5C65, 0xBC6A, 0x5D9D, 0xBC6B, 0x5D94, 0xBC6C, 0x5E62, 0xBC6D, 0x5E5F, 0xBC6E, 0x5E61, + 0xBC6F, 0x5EE2, 0xBC70, 0x5EDA, 0xBC71, 0x5EDF, 0xBC72, 0x5EDD, 0xBC73, 0x5EE3, 0xBC74, 0x5EE0, 0xBC75, 0x5F48, 0xBC76, 0x5F71, + 0xBC77, 0x5FB7, 0xBC78, 0x5FB5, 0xBC79, 0x6176, 0xBC7A, 0x6167, 0xBC7B, 0x616E, 0xBC7C, 0x615D, 0xBC7D, 0x6155, 0xBC7E, 0x6182, + 0xBCA1, 0x617C, 0xBCA2, 0x6170, 0xBCA3, 0x616B, 0xBCA4, 0x617E, 0xBCA5, 0x61A7, 0xBCA6, 0x6190, 0xBCA7, 0x61AB, 0xBCA8, 0x618E, + 0xBCA9, 0x61AC, 0xBCAA, 0x619A, 0xBCAB, 0x61A4, 0xBCAC, 0x6194, 0xBCAD, 0x61AE, 0xBCAE, 0x622E, 0xBCAF, 0x6469, 0xBCB0, 0x646F, + 0xBCB1, 0x6479, 0xBCB2, 0x649E, 0xBCB3, 0x64B2, 0xBCB4, 0x6488, 0xBCB5, 0x6490, 0xBCB6, 0x64B0, 0xBCB7, 0x64A5, 0xBCB8, 0x6493, + 0xBCB9, 0x6495, 0xBCBA, 0x64A9, 0xBCBB, 0x6492, 0xBCBC, 0x64AE, 0xBCBD, 0x64AD, 0xBCBE, 0x64AB, 0xBCBF, 0x649A, 0xBCC0, 0x64AC, + 0xBCC1, 0x6499, 0xBCC2, 0x64A2, 0xBCC3, 0x64B3, 0xBCC4, 0x6575, 0xBCC5, 0x6577, 0xBCC6, 0x6578, 0xBCC7, 0x66AE, 0xBCC8, 0x66AB, + 0xBCC9, 0x66B4, 0xBCCA, 0x66B1, 0xBCCB, 0x6A23, 0xBCCC, 0x6A1F, 0xBCCD, 0x69E8, 0xBCCE, 0x6A01, 0xBCCF, 0x6A1E, 0xBCD0, 0x6A19, + 0xBCD1, 0x69FD, 0xBCD2, 0x6A21, 0xBCD3, 0x6A13, 0xBCD4, 0x6A0A, 0xBCD5, 0x69F3, 0xBCD6, 0x6A02, 0xBCD7, 0x6A05, 0xBCD8, 0x69ED, + 0xBCD9, 0x6A11, 0xBCDA, 0x6B50, 0xBCDB, 0x6B4E, 0xBCDC, 0x6BA4, 0xBCDD, 0x6BC5, 0xBCDE, 0x6BC6, 0xBCDF, 0x6F3F, 0xBCE0, 0x6F7C, + 0xBCE1, 0x6F84, 0xBCE2, 0x6F51, 0xBCE3, 0x6F66, 0xBCE4, 0x6F54, 0xBCE5, 0x6F86, 0xBCE6, 0x6F6D, 0xBCE7, 0x6F5B, 0xBCE8, 0x6F78, + 0xBCE9, 0x6F6E, 0xBCEA, 0x6F8E, 0xBCEB, 0x6F7A, 0xBCEC, 0x6F70, 0xBCED, 0x6F64, 0xBCEE, 0x6F97, 0xBCEF, 0x6F58, 0xBCF0, 0x6ED5, + 0xBCF1, 0x6F6F, 0xBCF2, 0x6F60, 0xBCF3, 0x6F5F, 0xBCF4, 0x719F, 0xBCF5, 0x71AC, 0xBCF6, 0x71B1, 0xBCF7, 0x71A8, 0xBCF8, 0x7256, + 0xBCF9, 0x729B, 0xBCFA, 0x734E, 0xBCFB, 0x7357, 0xBCFC, 0x7469, 0xBCFD, 0x748B, 0xBCFE, 0x7483, 0xBD40, 0x747E, 0xBD41, 0x7480, + 0xBD42, 0x757F, 0xBD43, 0x7620, 0xBD44, 0x7629, 0xBD45, 0x761F, 0xBD46, 0x7624, 0xBD47, 0x7626, 0xBD48, 0x7621, 0xBD49, 0x7622, + 0xBD4A, 0x769A, 0xBD4B, 0x76BA, 0xBD4C, 0x76E4, 0xBD4D, 0x778E, 0xBD4E, 0x7787, 0xBD4F, 0x778C, 0xBD50, 0x7791, 0xBD51, 0x778B, + 0xBD52, 0x78CB, 0xBD53, 0x78C5, 0xBD54, 0x78BA, 0xBD55, 0x78CA, 0xBD56, 0x78BE, 0xBD57, 0x78D5, 0xBD58, 0x78BC, 0xBD59, 0x78D0, + 0xBD5A, 0x7A3F, 0xBD5B, 0x7A3C, 0xBD5C, 0x7A40, 0xBD5D, 0x7A3D, 0xBD5E, 0x7A37, 0xBD5F, 0x7A3B, 0xBD60, 0x7AAF, 0xBD61, 0x7AAE, + 0xBD62, 0x7BAD, 0xBD63, 0x7BB1, 0xBD64, 0x7BC4, 0xBD65, 0x7BB4, 0xBD66, 0x7BC6, 0xBD67, 0x7BC7, 0xBD68, 0x7BC1, 0xBD69, 0x7BA0, + 0xBD6A, 0x7BCC, 0xBD6B, 0x7CCA, 0xBD6C, 0x7DE0, 0xBD6D, 0x7DF4, 0xBD6E, 0x7DEF, 0xBD6F, 0x7DFB, 0xBD70, 0x7DD8, 0xBD71, 0x7DEC, + 0xBD72, 0x7DDD, 0xBD73, 0x7DE8, 0xBD74, 0x7DE3, 0xBD75, 0x7DDA, 0xBD76, 0x7DDE, 0xBD77, 0x7DE9, 0xBD78, 0x7D9E, 0xBD79, 0x7DD9, + 0xBD7A, 0x7DF2, 0xBD7B, 0x7DF9, 0xBD7C, 0x7F75, 0xBD7D, 0x7F77, 0xBD7E, 0x7FAF, 0xBDA1, 0x7FE9, 0xBDA2, 0x8026, 0xBDA3, 0x819B, + 0xBDA4, 0x819C, 0xBDA5, 0x819D, 0xBDA6, 0x81A0, 0xBDA7, 0x819A, 0xBDA8, 0x8198, 0xBDA9, 0x8517, 0xBDAA, 0x853D, 0xBDAB, 0x851A, + 0xBDAC, 0x84EE, 0xBDAD, 0x852C, 0xBDAE, 0x852D, 0xBDAF, 0x8513, 0xBDB0, 0x8511, 0xBDB1, 0x8523, 0xBDB2, 0x8521, 0xBDB3, 0x8514, + 0xBDB4, 0x84EC, 0xBDB5, 0x8525, 0xBDB6, 0x84FF, 0xBDB7, 0x8506, 0xBDB8, 0x8782, 0xBDB9, 0x8774, 0xBDBA, 0x8776, 0xBDBB, 0x8760, + 0xBDBC, 0x8766, 0xBDBD, 0x8778, 0xBDBE, 0x8768, 0xBDBF, 0x8759, 0xBDC0, 0x8757, 0xBDC1, 0x874C, 0xBDC2, 0x8753, 0xBDC3, 0x885B, + 0xBDC4, 0x885D, 0xBDC5, 0x8910, 0xBDC6, 0x8907, 0xBDC7, 0x8912, 0xBDC8, 0x8913, 0xBDC9, 0x8915, 0xBDCA, 0x890A, 0xBDCB, 0x8ABC, + 0xBDCC, 0x8AD2, 0xBDCD, 0x8AC7, 0xBDCE, 0x8AC4, 0xBDCF, 0x8A95, 0xBDD0, 0x8ACB, 0xBDD1, 0x8AF8, 0xBDD2, 0x8AB2, 0xBDD3, 0x8AC9, + 0xBDD4, 0x8AC2, 0xBDD5, 0x8ABF, 0xBDD6, 0x8AB0, 0xBDD7, 0x8AD6, 0xBDD8, 0x8ACD, 0xBDD9, 0x8AB6, 0xBDDA, 0x8AB9, 0xBDDB, 0x8ADB, + 0xBDDC, 0x8C4C, 0xBDDD, 0x8C4E, 0xBDDE, 0x8C6C, 0xBDDF, 0x8CE0, 0xBDE0, 0x8CDE, 0xBDE1, 0x8CE6, 0xBDE2, 0x8CE4, 0xBDE3, 0x8CEC, + 0xBDE4, 0x8CED, 0xBDE5, 0x8CE2, 0xBDE6, 0x8CE3, 0xBDE7, 0x8CDC, 0xBDE8, 0x8CEA, 0xBDE9, 0x8CE1, 0xBDEA, 0x8D6D, 0xBDEB, 0x8D9F, + 0xBDEC, 0x8DA3, 0xBDED, 0x8E2B, 0xBDEE, 0x8E10, 0xBDEF, 0x8E1D, 0xBDF0, 0x8E22, 0xBDF1, 0x8E0F, 0xBDF2, 0x8E29, 0xBDF3, 0x8E1F, + 0xBDF4, 0x8E21, 0xBDF5, 0x8E1E, 0xBDF6, 0x8EBA, 0xBDF7, 0x8F1D, 0xBDF8, 0x8F1B, 0xBDF9, 0x8F1F, 0xBDFA, 0x8F29, 0xBDFB, 0x8F26, + 0xBDFC, 0x8F2A, 0xBDFD, 0x8F1C, 0xBDFE, 0x8F1E, 0xBE40, 0x8F25, 0xBE41, 0x9069, 0xBE42, 0x906E, 0xBE43, 0x9068, 0xBE44, 0x906D, + 0xBE45, 0x9077, 0xBE46, 0x9130, 0xBE47, 0x912D, 0xBE48, 0x9127, 0xBE49, 0x9131, 0xBE4A, 0x9187, 0xBE4B, 0x9189, 0xBE4C, 0x918B, + 0xBE4D, 0x9183, 0xBE4E, 0x92C5, 0xBE4F, 0x92BB, 0xBE50, 0x92B7, 0xBE51, 0x92EA, 0xBE52, 0x92AC, 0xBE53, 0x92E4, 0xBE54, 0x92C1, + 0xBE55, 0x92B3, 0xBE56, 0x92BC, 0xBE57, 0x92D2, 0xBE58, 0x92C7, 0xBE59, 0x92F0, 0xBE5A, 0x92B2, 0xBE5B, 0x95AD, 0xBE5C, 0x95B1, + 0xBE5D, 0x9704, 0xBE5E, 0x9706, 0xBE5F, 0x9707, 0xBE60, 0x9709, 0xBE61, 0x9760, 0xBE62, 0x978D, 0xBE63, 0x978B, 0xBE64, 0x978F, + 0xBE65, 0x9821, 0xBE66, 0x982B, 0xBE67, 0x981C, 0xBE68, 0x98B3, 0xBE69, 0x990A, 0xBE6A, 0x9913, 0xBE6B, 0x9912, 0xBE6C, 0x9918, + 0xBE6D, 0x99DD, 0xBE6E, 0x99D0, 0xBE6F, 0x99DF, 0xBE70, 0x99DB, 0xBE71, 0x99D1, 0xBE72, 0x99D5, 0xBE73, 0x99D2, 0xBE74, 0x99D9, + 0xBE75, 0x9AB7, 0xBE76, 0x9AEE, 0xBE77, 0x9AEF, 0xBE78, 0x9B27, 0xBE79, 0x9B45, 0xBE7A, 0x9B44, 0xBE7B, 0x9B77, 0xBE7C, 0x9B6F, + 0xBE7D, 0x9D06, 0xBE7E, 0x9D09, 0xBEA1, 0x9D03, 0xBEA2, 0x9EA9, 0xBEA3, 0x9EBE, 0xBEA4, 0x9ECE, 0xBEA5, 0x58A8, 0xBEA6, 0x9F52, + 0xBEA7, 0x5112, 0xBEA8, 0x5118, 0xBEA9, 0x5114, 0xBEAA, 0x5110, 0xBEAB, 0x5115, 0xBEAC, 0x5180, 0xBEAD, 0x51AA, 0xBEAE, 0x51DD, + 0xBEAF, 0x5291, 0xBEB0, 0x5293, 0xBEB1, 0x52F3, 0xBEB2, 0x5659, 0xBEB3, 0x566B, 0xBEB4, 0x5679, 0xBEB5, 0x5669, 0xBEB6, 0x5664, + 0xBEB7, 0x5678, 0xBEB8, 0x566A, 0xBEB9, 0x5668, 0xBEBA, 0x5665, 0xBEBB, 0x5671, 0xBEBC, 0x566F, 0xBEBD, 0x566C, 0xBEBE, 0x5662, + 0xBEBF, 0x5676, 0xBEC0, 0x58C1, 0xBEC1, 0x58BE, 0xBEC2, 0x58C7, 0xBEC3, 0x58C5, 0xBEC4, 0x596E, 0xBEC5, 0x5B1D, 0xBEC6, 0x5B34, + 0xBEC7, 0x5B78, 0xBEC8, 0x5BF0, 0xBEC9, 0x5C0E, 0xBECA, 0x5F4A, 0xBECB, 0x61B2, 0xBECC, 0x6191, 0xBECD, 0x61A9, 0xBECE, 0x618A, + 0xBECF, 0x61CD, 0xBED0, 0x61B6, 0xBED1, 0x61BE, 0xBED2, 0x61CA, 0xBED3, 0x61C8, 0xBED4, 0x6230, 0xBED5, 0x64C5, 0xBED6, 0x64C1, + 0xBED7, 0x64CB, 0xBED8, 0x64BB, 0xBED9, 0x64BC, 0xBEDA, 0x64DA, 0xBEDB, 0x64C4, 0xBEDC, 0x64C7, 0xBEDD, 0x64C2, 0xBEDE, 0x64CD, + 0xBEDF, 0x64BF, 0xBEE0, 0x64D2, 0xBEE1, 0x64D4, 0xBEE2, 0x64BE, 0xBEE3, 0x6574, 0xBEE4, 0x66C6, 0xBEE5, 0x66C9, 0xBEE6, 0x66B9, + 0xBEE7, 0x66C4, 0xBEE8, 0x66C7, 0xBEE9, 0x66B8, 0xBEEA, 0x6A3D, 0xBEEB, 0x6A38, 0xBEEC, 0x6A3A, 0xBEED, 0x6A59, 0xBEEE, 0x6A6B, + 0xBEEF, 0x6A58, 0xBEF0, 0x6A39, 0xBEF1, 0x6A44, 0xBEF2, 0x6A62, 0xBEF3, 0x6A61, 0xBEF4, 0x6A4B, 0xBEF5, 0x6A47, 0xBEF6, 0x6A35, + 0xBEF7, 0x6A5F, 0xBEF8, 0x6A48, 0xBEF9, 0x6B59, 0xBEFA, 0x6B77, 0xBEFB, 0x6C05, 0xBEFC, 0x6FC2, 0xBEFD, 0x6FB1, 0xBEFE, 0x6FA1, + 0xBF40, 0x6FC3, 0xBF41, 0x6FA4, 0xBF42, 0x6FC1, 0xBF43, 0x6FA7, 0xBF44, 0x6FB3, 0xBF45, 0x6FC0, 0xBF46, 0x6FB9, 0xBF47, 0x6FB6, + 0xBF48, 0x6FA6, 0xBF49, 0x6FA0, 0xBF4A, 0x6FB4, 0xBF4B, 0x71BE, 0xBF4C, 0x71C9, 0xBF4D, 0x71D0, 0xBF4E, 0x71D2, 0xBF4F, 0x71C8, + 0xBF50, 0x71D5, 0xBF51, 0x71B9, 0xBF52, 0x71CE, 0xBF53, 0x71D9, 0xBF54, 0x71DC, 0xBF55, 0x71C3, 0xBF56, 0x71C4, 0xBF57, 0x7368, + 0xBF58, 0x749C, 0xBF59, 0x74A3, 0xBF5A, 0x7498, 0xBF5B, 0x749F, 0xBF5C, 0x749E, 0xBF5D, 0x74E2, 0xBF5E, 0x750C, 0xBF5F, 0x750D, + 0xBF60, 0x7634, 0xBF61, 0x7638, 0xBF62, 0x763A, 0xBF63, 0x76E7, 0xBF64, 0x76E5, 0xBF65, 0x77A0, 0xBF66, 0x779E, 0xBF67, 0x779F, + 0xBF68, 0x77A5, 0xBF69, 0x78E8, 0xBF6A, 0x78DA, 0xBF6B, 0x78EC, 0xBF6C, 0x78E7, 0xBF6D, 0x79A6, 0xBF6E, 0x7A4D, 0xBF6F, 0x7A4E, + 0xBF70, 0x7A46, 0xBF71, 0x7A4C, 0xBF72, 0x7A4B, 0xBF73, 0x7ABA, 0xBF74, 0x7BD9, 0xBF75, 0x7C11, 0xBF76, 0x7BC9, 0xBF77, 0x7BE4, + 0xBF78, 0x7BDB, 0xBF79, 0x7BE1, 0xBF7A, 0x7BE9, 0xBF7B, 0x7BE6, 0xBF7C, 0x7CD5, 0xBF7D, 0x7CD6, 0xBF7E, 0x7E0A, 0xBFA1, 0x7E11, + 0xBFA2, 0x7E08, 0xBFA3, 0x7E1B, 0xBFA4, 0x7E23, 0xBFA5, 0x7E1E, 0xBFA6, 0x7E1D, 0xBFA7, 0x7E09, 0xBFA8, 0x7E10, 0xBFA9, 0x7F79, + 0xBFAA, 0x7FB2, 0xBFAB, 0x7FF0, 0xBFAC, 0x7FF1, 0xBFAD, 0x7FEE, 0xBFAE, 0x8028, 0xBFAF, 0x81B3, 0xBFB0, 0x81A9, 0xBFB1, 0x81A8, + 0xBFB2, 0x81FB, 0xBFB3, 0x8208, 0xBFB4, 0x8258, 0xBFB5, 0x8259, 0xBFB6, 0x854A, 0xBFB7, 0x8559, 0xBFB8, 0x8548, 0xBFB9, 0x8568, + 0xBFBA, 0x8569, 0xBFBB, 0x8543, 0xBFBC, 0x8549, 0xBFBD, 0x856D, 0xBFBE, 0x856A, 0xBFBF, 0x855E, 0xBFC0, 0x8783, 0xBFC1, 0x879F, + 0xBFC2, 0x879E, 0xBFC3, 0x87A2, 0xBFC4, 0x878D, 0xBFC5, 0x8861, 0xBFC6, 0x892A, 0xBFC7, 0x8932, 0xBFC8, 0x8925, 0xBFC9, 0x892B, + 0xBFCA, 0x8921, 0xBFCB, 0x89AA, 0xBFCC, 0x89A6, 0xBFCD, 0x8AE6, 0xBFCE, 0x8AFA, 0xBFCF, 0x8AEB, 0xBFD0, 0x8AF1, 0xBFD1, 0x8B00, + 0xBFD2, 0x8ADC, 0xBFD3, 0x8AE7, 0xBFD4, 0x8AEE, 0xBFD5, 0x8AFE, 0xBFD6, 0x8B01, 0xBFD7, 0x8B02, 0xBFD8, 0x8AF7, 0xBFD9, 0x8AED, + 0xBFDA, 0x8AF3, 0xBFDB, 0x8AF6, 0xBFDC, 0x8AFC, 0xBFDD, 0x8C6B, 0xBFDE, 0x8C6D, 0xBFDF, 0x8C93, 0xBFE0, 0x8CF4, 0xBFE1, 0x8E44, + 0xBFE2, 0x8E31, 0xBFE3, 0x8E34, 0xBFE4, 0x8E42, 0xBFE5, 0x8E39, 0xBFE6, 0x8E35, 0xBFE7, 0x8F3B, 0xBFE8, 0x8F2F, 0xBFE9, 0x8F38, + 0xBFEA, 0x8F33, 0xBFEB, 0x8FA8, 0xBFEC, 0x8FA6, 0xBFED, 0x9075, 0xBFEE, 0x9074, 0xBFEF, 0x9078, 0xBFF0, 0x9072, 0xBFF1, 0x907C, + 0xBFF2, 0x907A, 0xBFF3, 0x9134, 0xBFF4, 0x9192, 0xBFF5, 0x9320, 0xBFF6, 0x9336, 0xBFF7, 0x92F8, 0xBFF8, 0x9333, 0xBFF9, 0x932F, + 0xBFFA, 0x9322, 0xBFFB, 0x92FC, 0xBFFC, 0x932B, 0xBFFD, 0x9304, 0xBFFE, 0x931A, 0xC040, 0x9310, 0xC041, 0x9326, 0xC042, 0x9321, + 0xC043, 0x9315, 0xC044, 0x932E, 0xC045, 0x9319, 0xC046, 0x95BB, 0xC047, 0x96A7, 0xC048, 0x96A8, 0xC049, 0x96AA, 0xC04A, 0x96D5, + 0xC04B, 0x970E, 0xC04C, 0x9711, 0xC04D, 0x9716, 0xC04E, 0x970D, 0xC04F, 0x9713, 0xC050, 0x970F, 0xC051, 0x975B, 0xC052, 0x975C, + 0xC053, 0x9766, 0xC054, 0x9798, 0xC055, 0x9830, 0xC056, 0x9838, 0xC057, 0x983B, 0xC058, 0x9837, 0xC059, 0x982D, 0xC05A, 0x9839, + 0xC05B, 0x9824, 0xC05C, 0x9910, 0xC05D, 0x9928, 0xC05E, 0x991E, 0xC05F, 0x991B, 0xC060, 0x9921, 0xC061, 0x991A, 0xC062, 0x99ED, + 0xC063, 0x99E2, 0xC064, 0x99F1, 0xC065, 0x9AB8, 0xC066, 0x9ABC, 0xC067, 0x9AFB, 0xC068, 0x9AED, 0xC069, 0x9B28, 0xC06A, 0x9B91, + 0xC06B, 0x9D15, 0xC06C, 0x9D23, 0xC06D, 0x9D26, 0xC06E, 0x9D28, 0xC06F, 0x9D12, 0xC070, 0x9D1B, 0xC071, 0x9ED8, 0xC072, 0x9ED4, + 0xC073, 0x9F8D, 0xC074, 0x9F9C, 0xC075, 0x512A, 0xC076, 0x511F, 0xC077, 0x5121, 0xC078, 0x5132, 0xC079, 0x52F5, 0xC07A, 0x568E, + 0xC07B, 0x5680, 0xC07C, 0x5690, 0xC07D, 0x5685, 0xC07E, 0x5687, 0xC0A1, 0x568F, 0xC0A2, 0x58D5, 0xC0A3, 0x58D3, 0xC0A4, 0x58D1, + 0xC0A5, 0x58CE, 0xC0A6, 0x5B30, 0xC0A7, 0x5B2A, 0xC0A8, 0x5B24, 0xC0A9, 0x5B7A, 0xC0AA, 0x5C37, 0xC0AB, 0x5C68, 0xC0AC, 0x5DBC, + 0xC0AD, 0x5DBA, 0xC0AE, 0x5DBD, 0xC0AF, 0x5DB8, 0xC0B0, 0x5E6B, 0xC0B1, 0x5F4C, 0xC0B2, 0x5FBD, 0xC0B3, 0x61C9, 0xC0B4, 0x61C2, + 0xC0B5, 0x61C7, 0xC0B6, 0x61E6, 0xC0B7, 0x61CB, 0xC0B8, 0x6232, 0xC0B9, 0x6234, 0xC0BA, 0x64CE, 0xC0BB, 0x64CA, 0xC0BC, 0x64D8, + 0xC0BD, 0x64E0, 0xC0BE, 0x64F0, 0xC0BF, 0x64E6, 0xC0C0, 0x64EC, 0xC0C1, 0x64F1, 0xC0C2, 0x64E2, 0xC0C3, 0x64ED, 0xC0C4, 0x6582, + 0xC0C5, 0x6583, 0xC0C6, 0x66D9, 0xC0C7, 0x66D6, 0xC0C8, 0x6A80, 0xC0C9, 0x6A94, 0xC0CA, 0x6A84, 0xC0CB, 0x6AA2, 0xC0CC, 0x6A9C, + 0xC0CD, 0x6ADB, 0xC0CE, 0x6AA3, 0xC0CF, 0x6A7E, 0xC0D0, 0x6A97, 0xC0D1, 0x6A90, 0xC0D2, 0x6AA0, 0xC0D3, 0x6B5C, 0xC0D4, 0x6BAE, + 0xC0D5, 0x6BDA, 0xC0D6, 0x6C08, 0xC0D7, 0x6FD8, 0xC0D8, 0x6FF1, 0xC0D9, 0x6FDF, 0xC0DA, 0x6FE0, 0xC0DB, 0x6FDB, 0xC0DC, 0x6FE4, + 0xC0DD, 0x6FEB, 0xC0DE, 0x6FEF, 0xC0DF, 0x6F80, 0xC0E0, 0x6FEC, 0xC0E1, 0x6FE1, 0xC0E2, 0x6FE9, 0xC0E3, 0x6FD5, 0xC0E4, 0x6FEE, + 0xC0E5, 0x6FF0, 0xC0E6, 0x71E7, 0xC0E7, 0x71DF, 0xC0E8, 0x71EE, 0xC0E9, 0x71E6, 0xC0EA, 0x71E5, 0xC0EB, 0x71ED, 0xC0EC, 0x71EC, + 0xC0ED, 0x71F4, 0xC0EE, 0x71E0, 0xC0EF, 0x7235, 0xC0F0, 0x7246, 0xC0F1, 0x7370, 0xC0F2, 0x7372, 0xC0F3, 0x74A9, 0xC0F4, 0x74B0, + 0xC0F5, 0x74A6, 0xC0F6, 0x74A8, 0xC0F7, 0x7646, 0xC0F8, 0x7642, 0xC0F9, 0x764C, 0xC0FA, 0x76EA, 0xC0FB, 0x77B3, 0xC0FC, 0x77AA, + 0xC0FD, 0x77B0, 0xC0FE, 0x77AC, 0xC140, 0x77A7, 0xC141, 0x77AD, 0xC142, 0x77EF, 0xC143, 0x78F7, 0xC144, 0x78FA, 0xC145, 0x78F4, + 0xC146, 0x78EF, 0xC147, 0x7901, 0xC148, 0x79A7, 0xC149, 0x79AA, 0xC14A, 0x7A57, 0xC14B, 0x7ABF, 0xC14C, 0x7C07, 0xC14D, 0x7C0D, + 0xC14E, 0x7BFE, 0xC14F, 0x7BF7, 0xC150, 0x7C0C, 0xC151, 0x7BE0, 0xC152, 0x7CE0, 0xC153, 0x7CDC, 0xC154, 0x7CDE, 0xC155, 0x7CE2, + 0xC156, 0x7CDF, 0xC157, 0x7CD9, 0xC158, 0x7CDD, 0xC159, 0x7E2E, 0xC15A, 0x7E3E, 0xC15B, 0x7E46, 0xC15C, 0x7E37, 0xC15D, 0x7E32, + 0xC15E, 0x7E43, 0xC15F, 0x7E2B, 0xC160, 0x7E3D, 0xC161, 0x7E31, 0xC162, 0x7E45, 0xC163, 0x7E41, 0xC164, 0x7E34, 0xC165, 0x7E39, + 0xC166, 0x7E48, 0xC167, 0x7E35, 0xC168, 0x7E3F, 0xC169, 0x7E2F, 0xC16A, 0x7F44, 0xC16B, 0x7FF3, 0xC16C, 0x7FFC, 0xC16D, 0x8071, + 0xC16E, 0x8072, 0xC16F, 0x8070, 0xC170, 0x806F, 0xC171, 0x8073, 0xC172, 0x81C6, 0xC173, 0x81C3, 0xC174, 0x81BA, 0xC175, 0x81C2, + 0xC176, 0x81C0, 0xC177, 0x81BF, 0xC178, 0x81BD, 0xC179, 0x81C9, 0xC17A, 0x81BE, 0xC17B, 0x81E8, 0xC17C, 0x8209, 0xC17D, 0x8271, + 0xC17E, 0x85AA, 0xC1A1, 0x8584, 0xC1A2, 0x857E, 0xC1A3, 0x859C, 0xC1A4, 0x8591, 0xC1A5, 0x8594, 0xC1A6, 0x85AF, 0xC1A7, 0x859B, + 0xC1A8, 0x8587, 0xC1A9, 0x85A8, 0xC1AA, 0x858A, 0xC1AB, 0x8667, 0xC1AC, 0x87C0, 0xC1AD, 0x87D1, 0xC1AE, 0x87B3, 0xC1AF, 0x87D2, + 0xC1B0, 0x87C6, 0xC1B1, 0x87AB, 0xC1B2, 0x87BB, 0xC1B3, 0x87BA, 0xC1B4, 0x87C8, 0xC1B5, 0x87CB, 0xC1B6, 0x893B, 0xC1B7, 0x8936, + 0xC1B8, 0x8944, 0xC1B9, 0x8938, 0xC1BA, 0x893D, 0xC1BB, 0x89AC, 0xC1BC, 0x8B0E, 0xC1BD, 0x8B17, 0xC1BE, 0x8B19, 0xC1BF, 0x8B1B, + 0xC1C0, 0x8B0A, 0xC1C1, 0x8B20, 0xC1C2, 0x8B1D, 0xC1C3, 0x8B04, 0xC1C4, 0x8B10, 0xC1C5, 0x8C41, 0xC1C6, 0x8C3F, 0xC1C7, 0x8C73, + 0xC1C8, 0x8CFA, 0xC1C9, 0x8CFD, 0xC1CA, 0x8CFC, 0xC1CB, 0x8CF8, 0xC1CC, 0x8CFB, 0xC1CD, 0x8DA8, 0xC1CE, 0x8E49, 0xC1CF, 0x8E4B, + 0xC1D0, 0x8E48, 0xC1D1, 0x8E4A, 0xC1D2, 0x8F44, 0xC1D3, 0x8F3E, 0xC1D4, 0x8F42, 0xC1D5, 0x8F45, 0xC1D6, 0x8F3F, 0xC1D7, 0x907F, + 0xC1D8, 0x907D, 0xC1D9, 0x9084, 0xC1DA, 0x9081, 0xC1DB, 0x9082, 0xC1DC, 0x9080, 0xC1DD, 0x9139, 0xC1DE, 0x91A3, 0xC1DF, 0x919E, + 0xC1E0, 0x919C, 0xC1E1, 0x934D, 0xC1E2, 0x9382, 0xC1E3, 0x9328, 0xC1E4, 0x9375, 0xC1E5, 0x934A, 0xC1E6, 0x9365, 0xC1E7, 0x934B, + 0xC1E8, 0x9318, 0xC1E9, 0x937E, 0xC1EA, 0x936C, 0xC1EB, 0x935B, 0xC1EC, 0x9370, 0xC1ED, 0x935A, 0xC1EE, 0x9354, 0xC1EF, 0x95CA, + 0xC1F0, 0x95CB, 0xC1F1, 0x95CC, 0xC1F2, 0x95C8, 0xC1F3, 0x95C6, 0xC1F4, 0x96B1, 0xC1F5, 0x96B8, 0xC1F6, 0x96D6, 0xC1F7, 0x971C, + 0xC1F8, 0x971E, 0xC1F9, 0x97A0, 0xC1FA, 0x97D3, 0xC1FB, 0x9846, 0xC1FC, 0x98B6, 0xC1FD, 0x9935, 0xC1FE, 0x9A01, 0xC240, 0x99FF, + 0xC241, 0x9BAE, 0xC242, 0x9BAB, 0xC243, 0x9BAA, 0xC244, 0x9BAD, 0xC245, 0x9D3B, 0xC246, 0x9D3F, 0xC247, 0x9E8B, 0xC248, 0x9ECF, + 0xC249, 0x9EDE, 0xC24A, 0x9EDC, 0xC24B, 0x9EDD, 0xC24C, 0x9EDB, 0xC24D, 0x9F3E, 0xC24E, 0x9F4B, 0xC24F, 0x53E2, 0xC250, 0x5695, + 0xC251, 0x56AE, 0xC252, 0x58D9, 0xC253, 0x58D8, 0xC254, 0x5B38, 0xC255, 0x5F5D, 0xC256, 0x61E3, 0xC257, 0x6233, 0xC258, 0x64F4, + 0xC259, 0x64F2, 0xC25A, 0x64FE, 0xC25B, 0x6506, 0xC25C, 0x64FA, 0xC25D, 0x64FB, 0xC25E, 0x64F7, 0xC25F, 0x65B7, 0xC260, 0x66DC, + 0xC261, 0x6726, 0xC262, 0x6AB3, 0xC263, 0x6AAC, 0xC264, 0x6AC3, 0xC265, 0x6ABB, 0xC266, 0x6AB8, 0xC267, 0x6AC2, 0xC268, 0x6AAE, + 0xC269, 0x6AAF, 0xC26A, 0x6B5F, 0xC26B, 0x6B78, 0xC26C, 0x6BAF, 0xC26D, 0x7009, 0xC26E, 0x700B, 0xC26F, 0x6FFE, 0xC270, 0x7006, + 0xC271, 0x6FFA, 0xC272, 0x7011, 0xC273, 0x700F, 0xC274, 0x71FB, 0xC275, 0x71FC, 0xC276, 0x71FE, 0xC277, 0x71F8, 0xC278, 0x7377, + 0xC279, 0x7375, 0xC27A, 0x74A7, 0xC27B, 0x74BF, 0xC27C, 0x7515, 0xC27D, 0x7656, 0xC27E, 0x7658, 0xC2A1, 0x7652, 0xC2A2, 0x77BD, + 0xC2A3, 0x77BF, 0xC2A4, 0x77BB, 0xC2A5, 0x77BC, 0xC2A6, 0x790E, 0xC2A7, 0x79AE, 0xC2A8, 0x7A61, 0xC2A9, 0x7A62, 0xC2AA, 0x7A60, + 0xC2AB, 0x7AC4, 0xC2AC, 0x7AC5, 0xC2AD, 0x7C2B, 0xC2AE, 0x7C27, 0xC2AF, 0x7C2A, 0xC2B0, 0x7C1E, 0xC2B1, 0x7C23, 0xC2B2, 0x7C21, + 0xC2B3, 0x7CE7, 0xC2B4, 0x7E54, 0xC2B5, 0x7E55, 0xC2B6, 0x7E5E, 0xC2B7, 0x7E5A, 0xC2B8, 0x7E61, 0xC2B9, 0x7E52, 0xC2BA, 0x7E59, + 0xC2BB, 0x7F48, 0xC2BC, 0x7FF9, 0xC2BD, 0x7FFB, 0xC2BE, 0x8077, 0xC2BF, 0x8076, 0xC2C0, 0x81CD, 0xC2C1, 0x81CF, 0xC2C2, 0x820A, + 0xC2C3, 0x85CF, 0xC2C4, 0x85A9, 0xC2C5, 0x85CD, 0xC2C6, 0x85D0, 0xC2C7, 0x85C9, 0xC2C8, 0x85B0, 0xC2C9, 0x85BA, 0xC2CA, 0x85B9, + 0xC2CB, 0x85A6, 0xC2CC, 0x87EF, 0xC2CD, 0x87EC, 0xC2CE, 0x87F2, 0xC2CF, 0x87E0, 0xC2D0, 0x8986, 0xC2D1, 0x89B2, 0xC2D2, 0x89F4, + 0xC2D3, 0x8B28, 0xC2D4, 0x8B39, 0xC2D5, 0x8B2C, 0xC2D6, 0x8B2B, 0xC2D7, 0x8C50, 0xC2D8, 0x8D05, 0xC2D9, 0x8E59, 0xC2DA, 0x8E63, + 0xC2DB, 0x8E66, 0xC2DC, 0x8E64, 0xC2DD, 0x8E5F, 0xC2DE, 0x8E55, 0xC2DF, 0x8EC0, 0xC2E0, 0x8F49, 0xC2E1, 0x8F4D, 0xC2E2, 0x9087, + 0xC2E3, 0x9083, 0xC2E4, 0x9088, 0xC2E5, 0x91AB, 0xC2E6, 0x91AC, 0xC2E7, 0x91D0, 0xC2E8, 0x9394, 0xC2E9, 0x938A, 0xC2EA, 0x9396, + 0xC2EB, 0x93A2, 0xC2EC, 0x93B3, 0xC2ED, 0x93AE, 0xC2EE, 0x93AC, 0xC2EF, 0x93B0, 0xC2F0, 0x9398, 0xC2F1, 0x939A, 0xC2F2, 0x9397, + 0xC2F3, 0x95D4, 0xC2F4, 0x95D6, 0xC2F5, 0x95D0, 0xC2F6, 0x95D5, 0xC2F7, 0x96E2, 0xC2F8, 0x96DC, 0xC2F9, 0x96D9, 0xC2FA, 0x96DB, + 0xC2FB, 0x96DE, 0xC2FC, 0x9724, 0xC2FD, 0x97A3, 0xC2FE, 0x97A6, 0xC340, 0x97AD, 0xC341, 0x97F9, 0xC342, 0x984D, 0xC343, 0x984F, + 0xC344, 0x984C, 0xC345, 0x984E, 0xC346, 0x9853, 0xC347, 0x98BA, 0xC348, 0x993E, 0xC349, 0x993F, 0xC34A, 0x993D, 0xC34B, 0x992E, + 0xC34C, 0x99A5, 0xC34D, 0x9A0E, 0xC34E, 0x9AC1, 0xC34F, 0x9B03, 0xC350, 0x9B06, 0xC351, 0x9B4F, 0xC352, 0x9B4E, 0xC353, 0x9B4D, + 0xC354, 0x9BCA, 0xC355, 0x9BC9, 0xC356, 0x9BFD, 0xC357, 0x9BC8, 0xC358, 0x9BC0, 0xC359, 0x9D51, 0xC35A, 0x9D5D, 0xC35B, 0x9D60, + 0xC35C, 0x9EE0, 0xC35D, 0x9F15, 0xC35E, 0x9F2C, 0xC35F, 0x5133, 0xC360, 0x56A5, 0xC361, 0x58DE, 0xC362, 0x58DF, 0xC363, 0x58E2, + 0xC364, 0x5BF5, 0xC365, 0x9F90, 0xC366, 0x5EEC, 0xC367, 0x61F2, 0xC368, 0x61F7, 0xC369, 0x61F6, 0xC36A, 0x61F5, 0xC36B, 0x6500, + 0xC36C, 0x650F, 0xC36D, 0x66E0, 0xC36E, 0x66DD, 0xC36F, 0x6AE5, 0xC370, 0x6ADD, 0xC371, 0x6ADA, 0xC372, 0x6AD3, 0xC373, 0x701B, + 0xC374, 0x701F, 0xC375, 0x7028, 0xC376, 0x701A, 0xC377, 0x701D, 0xC378, 0x7015, 0xC379, 0x7018, 0xC37A, 0x7206, 0xC37B, 0x720D, + 0xC37C, 0x7258, 0xC37D, 0x72A2, 0xC37E, 0x7378, 0xC3A1, 0x737A, 0xC3A2, 0x74BD, 0xC3A3, 0x74CA, 0xC3A4, 0x74E3, 0xC3A5, 0x7587, + 0xC3A6, 0x7586, 0xC3A7, 0x765F, 0xC3A8, 0x7661, 0xC3A9, 0x77C7, 0xC3AA, 0x7919, 0xC3AB, 0x79B1, 0xC3AC, 0x7A6B, 0xC3AD, 0x7A69, + 0xC3AE, 0x7C3E, 0xC3AF, 0x7C3F, 0xC3B0, 0x7C38, 0xC3B1, 0x7C3D, 0xC3B2, 0x7C37, 0xC3B3, 0x7C40, 0xC3B4, 0x7E6B, 0xC3B5, 0x7E6D, + 0xC3B6, 0x7E79, 0xC3B7, 0x7E69, 0xC3B8, 0x7E6A, 0xC3B9, 0x7F85, 0xC3BA, 0x7E73, 0xC3BB, 0x7FB6, 0xC3BC, 0x7FB9, 0xC3BD, 0x7FB8, + 0xC3BE, 0x81D8, 0xC3BF, 0x85E9, 0xC3C0, 0x85DD, 0xC3C1, 0x85EA, 0xC3C2, 0x85D5, 0xC3C3, 0x85E4, 0xC3C4, 0x85E5, 0xC3C5, 0x85F7, + 0xC3C6, 0x87FB, 0xC3C7, 0x8805, 0xC3C8, 0x880D, 0xC3C9, 0x87F9, 0xC3CA, 0x87FE, 0xC3CB, 0x8960, 0xC3CC, 0x895F, 0xC3CD, 0x8956, + 0xC3CE, 0x895E, 0xC3CF, 0x8B41, 0xC3D0, 0x8B5C, 0xC3D1, 0x8B58, 0xC3D2, 0x8B49, 0xC3D3, 0x8B5A, 0xC3D4, 0x8B4E, 0xC3D5, 0x8B4F, + 0xC3D6, 0x8B46, 0xC3D7, 0x8B59, 0xC3D8, 0x8D08, 0xC3D9, 0x8D0A, 0xC3DA, 0x8E7C, 0xC3DB, 0x8E72, 0xC3DC, 0x8E87, 0xC3DD, 0x8E76, + 0xC3DE, 0x8E6C, 0xC3DF, 0x8E7A, 0xC3E0, 0x8E74, 0xC3E1, 0x8F54, 0xC3E2, 0x8F4E, 0xC3E3, 0x8FAD, 0xC3E4, 0x908A, 0xC3E5, 0x908B, + 0xC3E6, 0x91B1, 0xC3E7, 0x91AE, 0xC3E8, 0x93E1, 0xC3E9, 0x93D1, 0xC3EA, 0x93DF, 0xC3EB, 0x93C3, 0xC3EC, 0x93C8, 0xC3ED, 0x93DC, + 0xC3EE, 0x93DD, 0xC3EF, 0x93D6, 0xC3F0, 0x93E2, 0xC3F1, 0x93CD, 0xC3F2, 0x93D8, 0xC3F3, 0x93E4, 0xC3F4, 0x93D7, 0xC3F5, 0x93E8, + 0xC3F6, 0x95DC, 0xC3F7, 0x96B4, 0xC3F8, 0x96E3, 0xC3F9, 0x972A, 0xC3FA, 0x9727, 0xC3FB, 0x9761, 0xC3FC, 0x97DC, 0xC3FD, 0x97FB, + 0xC3FE, 0x985E, 0xC440, 0x9858, 0xC441, 0x985B, 0xC442, 0x98BC, 0xC443, 0x9945, 0xC444, 0x9949, 0xC445, 0x9A16, 0xC446, 0x9A19, + 0xC447, 0x9B0D, 0xC448, 0x9BE8, 0xC449, 0x9BE7, 0xC44A, 0x9BD6, 0xC44B, 0x9BDB, 0xC44C, 0x9D89, 0xC44D, 0x9D61, 0xC44E, 0x9D72, + 0xC44F, 0x9D6A, 0xC450, 0x9D6C, 0xC451, 0x9E92, 0xC452, 0x9E97, 0xC453, 0x9E93, 0xC454, 0x9EB4, 0xC455, 0x52F8, 0xC456, 0x56A8, + 0xC457, 0x56B7, 0xC458, 0x56B6, 0xC459, 0x56B4, 0xC45A, 0x56BC, 0xC45B, 0x58E4, 0xC45C, 0x5B40, 0xC45D, 0x5B43, 0xC45E, 0x5B7D, + 0xC45F, 0x5BF6, 0xC460, 0x5DC9, 0xC461, 0x61F8, 0xC462, 0x61FA, 0xC463, 0x6518, 0xC464, 0x6514, 0xC465, 0x6519, 0xC466, 0x66E6, + 0xC467, 0x6727, 0xC468, 0x6AEC, 0xC469, 0x703E, 0xC46A, 0x7030, 0xC46B, 0x7032, 0xC46C, 0x7210, 0xC46D, 0x737B, 0xC46E, 0x74CF, + 0xC46F, 0x7662, 0xC470, 0x7665, 0xC471, 0x7926, 0xC472, 0x792A, 0xC473, 0x792C, 0xC474, 0x792B, 0xC475, 0x7AC7, 0xC476, 0x7AF6, + 0xC477, 0x7C4C, 0xC478, 0x7C43, 0xC479, 0x7C4D, 0xC47A, 0x7CEF, 0xC47B, 0x7CF0, 0xC47C, 0x8FAE, 0xC47D, 0x7E7D, 0xC47E, 0x7E7C, + 0xC4A1, 0x7E82, 0xC4A2, 0x7F4C, 0xC4A3, 0x8000, 0xC4A4, 0x81DA, 0xC4A5, 0x8266, 0xC4A6, 0x85FB, 0xC4A7, 0x85F9, 0xC4A8, 0x8611, + 0xC4A9, 0x85FA, 0xC4AA, 0x8606, 0xC4AB, 0x860B, 0xC4AC, 0x8607, 0xC4AD, 0x860A, 0xC4AE, 0x8814, 0xC4AF, 0x8815, 0xC4B0, 0x8964, + 0xC4B1, 0x89BA, 0xC4B2, 0x89F8, 0xC4B3, 0x8B70, 0xC4B4, 0x8B6C, 0xC4B5, 0x8B66, 0xC4B6, 0x8B6F, 0xC4B7, 0x8B5F, 0xC4B8, 0x8B6B, + 0xC4B9, 0x8D0F, 0xC4BA, 0x8D0D, 0xC4BB, 0x8E89, 0xC4BC, 0x8E81, 0xC4BD, 0x8E85, 0xC4BE, 0x8E82, 0xC4BF, 0x91B4, 0xC4C0, 0x91CB, + 0xC4C1, 0x9418, 0xC4C2, 0x9403, 0xC4C3, 0x93FD, 0xC4C4, 0x95E1, 0xC4C5, 0x9730, 0xC4C6, 0x98C4, 0xC4C7, 0x9952, 0xC4C8, 0x9951, + 0xC4C9, 0x99A8, 0xC4CA, 0x9A2B, 0xC4CB, 0x9A30, 0xC4CC, 0x9A37, 0xC4CD, 0x9A35, 0xC4CE, 0x9C13, 0xC4CF, 0x9C0D, 0xC4D0, 0x9E79, + 0xC4D1, 0x9EB5, 0xC4D2, 0x9EE8, 0xC4D3, 0x9F2F, 0xC4D4, 0x9F5F, 0xC4D5, 0x9F63, 0xC4D6, 0x9F61, 0xC4D7, 0x5137, 0xC4D8, 0x5138, + 0xC4D9, 0x56C1, 0xC4DA, 0x56C0, 0xC4DB, 0x56C2, 0xC4DC, 0x5914, 0xC4DD, 0x5C6C, 0xC4DE, 0x5DCD, 0xC4DF, 0x61FC, 0xC4E0, 0x61FE, + 0xC4E1, 0x651D, 0xC4E2, 0x651C, 0xC4E3, 0x6595, 0xC4E4, 0x66E9, 0xC4E5, 0x6AFB, 0xC4E6, 0x6B04, 0xC4E7, 0x6AFA, 0xC4E8, 0x6BB2, + 0xC4E9, 0x704C, 0xC4EA, 0x721B, 0xC4EB, 0x72A7, 0xC4EC, 0x74D6, 0xC4ED, 0x74D4, 0xC4EE, 0x7669, 0xC4EF, 0x77D3, 0xC4F0, 0x7C50, + 0xC4F1, 0x7E8F, 0xC4F2, 0x7E8C, 0xC4F3, 0x7FBC, 0xC4F4, 0x8617, 0xC4F5, 0x862D, 0xC4F6, 0x861A, 0xC4F7, 0x8823, 0xC4F8, 0x8822, + 0xC4F9, 0x8821, 0xC4FA, 0x881F, 0xC4FB, 0x896A, 0xC4FC, 0x896C, 0xC4FD, 0x89BD, 0xC4FE, 0x8B74, 0xC540, 0x8B77, 0xC541, 0x8B7D, + 0xC542, 0x8D13, 0xC543, 0x8E8A, 0xC544, 0x8E8D, 0xC545, 0x8E8B, 0xC546, 0x8F5F, 0xC547, 0x8FAF, 0xC548, 0x91BA, 0xC549, 0x942E, + 0xC54A, 0x9433, 0xC54B, 0x9435, 0xC54C, 0x943A, 0xC54D, 0x9438, 0xC54E, 0x9432, 0xC54F, 0x942B, 0xC550, 0x95E2, 0xC551, 0x9738, + 0xC552, 0x9739, 0xC553, 0x9732, 0xC554, 0x97FF, 0xC555, 0x9867, 0xC556, 0x9865, 0xC557, 0x9957, 0xC558, 0x9A45, 0xC559, 0x9A43, + 0xC55A, 0x9A40, 0xC55B, 0x9A3E, 0xC55C, 0x9ACF, 0xC55D, 0x9B54, 0xC55E, 0x9B51, 0xC55F, 0x9C2D, 0xC560, 0x9C25, 0xC561, 0x9DAF, + 0xC562, 0x9DB4, 0xC563, 0x9DC2, 0xC564, 0x9DB8, 0xC565, 0x9E9D, 0xC566, 0x9EEF, 0xC567, 0x9F19, 0xC568, 0x9F5C, 0xC569, 0x9F66, + 0xC56A, 0x9F67, 0xC56B, 0x513C, 0xC56C, 0x513B, 0xC56D, 0x56C8, 0xC56E, 0x56CA, 0xC56F, 0x56C9, 0xC570, 0x5B7F, 0xC571, 0x5DD4, + 0xC572, 0x5DD2, 0xC573, 0x5F4E, 0xC574, 0x61FF, 0xC575, 0x6524, 0xC576, 0x6B0A, 0xC577, 0x6B61, 0xC578, 0x7051, 0xC579, 0x7058, + 0xC57A, 0x7380, 0xC57B, 0x74E4, 0xC57C, 0x758A, 0xC57D, 0x766E, 0xC57E, 0x766C, 0xC5A1, 0x79B3, 0xC5A2, 0x7C60, 0xC5A3, 0x7C5F, + 0xC5A4, 0x807E, 0xC5A5, 0x807D, 0xC5A6, 0x81DF, 0xC5A7, 0x8972, 0xC5A8, 0x896F, 0xC5A9, 0x89FC, 0xC5AA, 0x8B80, 0xC5AB, 0x8D16, + 0xC5AC, 0x8D17, 0xC5AD, 0x8E91, 0xC5AE, 0x8E93, 0xC5AF, 0x8F61, 0xC5B0, 0x9148, 0xC5B1, 0x9444, 0xC5B2, 0x9451, 0xC5B3, 0x9452, + 0xC5B4, 0x973D, 0xC5B5, 0x973E, 0xC5B6, 0x97C3, 0xC5B7, 0x97C1, 0xC5B8, 0x986B, 0xC5B9, 0x9955, 0xC5BA, 0x9A55, 0xC5BB, 0x9A4D, + 0xC5BC, 0x9AD2, 0xC5BD, 0x9B1A, 0xC5BE, 0x9C49, 0xC5BF, 0x9C31, 0xC5C0, 0x9C3E, 0xC5C1, 0x9C3B, 0xC5C2, 0x9DD3, 0xC5C3, 0x9DD7, + 0xC5C4, 0x9F34, 0xC5C5, 0x9F6C, 0xC5C6, 0x9F6A, 0xC5C7, 0x9F94, 0xC5C8, 0x56CC, 0xC5C9, 0x5DD6, 0xC5CA, 0x6200, 0xC5CB, 0x6523, + 0xC5CC, 0x652B, 0xC5CD, 0x652A, 0xC5CE, 0x66EC, 0xC5CF, 0x6B10, 0xC5D0, 0x74DA, 0xC5D1, 0x7ACA, 0xC5D2, 0x7C64, 0xC5D3, 0x7C63, + 0xC5D4, 0x7C65, 0xC5D5, 0x7E93, 0xC5D6, 0x7E96, 0xC5D7, 0x7E94, 0xC5D8, 0x81E2, 0xC5D9, 0x8638, 0xC5DA, 0x863F, 0xC5DB, 0x8831, + 0xC5DC, 0x8B8A, 0xC5DD, 0x9090, 0xC5DE, 0x908F, 0xC5DF, 0x9463, 0xC5E0, 0x9460, 0xC5E1, 0x9464, 0xC5E2, 0x9768, 0xC5E3, 0x986F, + 0xC5E4, 0x995C, 0xC5E5, 0x9A5A, 0xC5E6, 0x9A5B, 0xC5E7, 0x9A57, 0xC5E8, 0x9AD3, 0xC5E9, 0x9AD4, 0xC5EA, 0x9AD1, 0xC5EB, 0x9C54, + 0xC5EC, 0x9C57, 0xC5ED, 0x9C56, 0xC5EE, 0x9DE5, 0xC5EF, 0x9E9F, 0xC5F0, 0x9EF4, 0xC5F1, 0x56D1, 0xC5F2, 0x58E9, 0xC5F3, 0x652C, + 0xC5F4, 0x705E, 0xC5F5, 0x7671, 0xC5F6, 0x7672, 0xC5F7, 0x77D7, 0xC5F8, 0x7F50, 0xC5F9, 0x7F88, 0xC5FA, 0x8836, 0xC5FB, 0x8839, + 0xC5FC, 0x8862, 0xC5FD, 0x8B93, 0xC5FE, 0x8B92, 0xC640, 0x8B96, 0xC641, 0x8277, 0xC642, 0x8D1B, 0xC643, 0x91C0, 0xC644, 0x946A, + 0xC645, 0x9742, 0xC646, 0x9748, 0xC647, 0x9744, 0xC648, 0x97C6, 0xC649, 0x9870, 0xC64A, 0x9A5F, 0xC64B, 0x9B22, 0xC64C, 0x9B58, + 0xC64D, 0x9C5F, 0xC64E, 0x9DF9, 0xC64F, 0x9DFA, 0xC650, 0x9E7C, 0xC651, 0x9E7D, 0xC652, 0x9F07, 0xC653, 0x9F77, 0xC654, 0x9F72, + 0xC655, 0x5EF3, 0xC656, 0x6B16, 0xC657, 0x7063, 0xC658, 0x7C6C, 0xC659, 0x7C6E, 0xC65A, 0x883B, 0xC65B, 0x89C0, 0xC65C, 0x8EA1, + 0xC65D, 0x91C1, 0xC65E, 0x9472, 0xC65F, 0x9470, 0xC660, 0x9871, 0xC661, 0x995E, 0xC662, 0x9AD6, 0xC663, 0x9B23, 0xC664, 0x9ECC, + 0xC665, 0x7064, 0xC666, 0x77DA, 0xC667, 0x8B9A, 0xC668, 0x9477, 0xC669, 0x97C9, 0xC66A, 0x9A62, 0xC66B, 0x9A65, 0xC66C, 0x7E9C, + 0xC66D, 0x8B9C, 0xC66E, 0x8EAA, 0xC66F, 0x91C5, 0xC670, 0x947D, 0xC671, 0x947E, 0xC672, 0x947C, 0xC673, 0x9C77, 0xC674, 0x9C78, + 0xC675, 0x9EF7, 0xC676, 0x8C54, 0xC677, 0x947F, 0xC678, 0x9E1A, 0xC679, 0x7228, 0xC67A, 0x9A6A, 0xC67B, 0x9B31, 0xC67C, 0x9E1B, + 0xC67D, 0x9E1E, 0xC67E, 0x7C72, 0xC940, 0x4E42, 0xC941, 0x4E5C, 0xC942, 0x51F5, 0xC943, 0x531A, 0xC944, 0x5382, 0xC945, 0x4E07, + 0xC946, 0x4E0C, 0xC947, 0x4E47, 0xC948, 0x4E8D, 0xC949, 0x56D7, 0xC94A, 0xFA0C, 0xC94B, 0x5C6E, 0xC94C, 0x5F73, 0xC94D, 0x4E0F, + 0xC94E, 0x5187, 0xC94F, 0x4E0E, 0xC950, 0x4E2E, 0xC951, 0x4E93, 0xC952, 0x4EC2, 0xC953, 0x4EC9, 0xC954, 0x4EC8, 0xC955, 0x5198, + 0xC956, 0x52FC, 0xC957, 0x536C, 0xC958, 0x53B9, 0xC959, 0x5720, 0xC95A, 0x5903, 0xC95B, 0x592C, 0xC95C, 0x5C10, 0xC95D, 0x5DFF, + 0xC95E, 0x65E1, 0xC95F, 0x6BB3, 0xC960, 0x6BCC, 0xC961, 0x6C14, 0xC962, 0x723F, 0xC963, 0x4E31, 0xC964, 0x4E3C, 0xC965, 0x4EE8, + 0xC966, 0x4EDC, 0xC967, 0x4EE9, 0xC968, 0x4EE1, 0xC969, 0x4EDD, 0xC96A, 0x4EDA, 0xC96B, 0x520C, 0xC96C, 0x531C, 0xC96D, 0x534C, + 0xC96E, 0x5722, 0xC96F, 0x5723, 0xC970, 0x5917, 0xC971, 0x592F, 0xC972, 0x5B81, 0xC973, 0x5B84, 0xC974, 0x5C12, 0xC975, 0x5C3B, + 0xC976, 0x5C74, 0xC977, 0x5C73, 0xC978, 0x5E04, 0xC979, 0x5E80, 0xC97A, 0x5E82, 0xC97B, 0x5FC9, 0xC97C, 0x6209, 0xC97D, 0x6250, + 0xC97E, 0x6C15, 0xC9A1, 0x6C36, 0xC9A2, 0x6C43, 0xC9A3, 0x6C3F, 0xC9A4, 0x6C3B, 0xC9A5, 0x72AE, 0xC9A6, 0x72B0, 0xC9A7, 0x738A, + 0xC9A8, 0x79B8, 0xC9A9, 0x808A, 0xC9AA, 0x961E, 0xC9AB, 0x4F0E, 0xC9AC, 0x4F18, 0xC9AD, 0x4F2C, 0xC9AE, 0x4EF5, 0xC9AF, 0x4F14, + 0xC9B0, 0x4EF1, 0xC9B1, 0x4F00, 0xC9B2, 0x4EF7, 0xC9B3, 0x4F08, 0xC9B4, 0x4F1D, 0xC9B5, 0x4F02, 0xC9B6, 0x4F05, 0xC9B7, 0x4F22, + 0xC9B8, 0x4F13, 0xC9B9, 0x4F04, 0xC9BA, 0x4EF4, 0xC9BB, 0x4F12, 0xC9BC, 0x51B1, 0xC9BD, 0x5213, 0xC9BE, 0x5209, 0xC9BF, 0x5210, + 0xC9C0, 0x52A6, 0xC9C1, 0x5322, 0xC9C2, 0x531F, 0xC9C3, 0x534D, 0xC9C4, 0x538A, 0xC9C5, 0x5407, 0xC9C6, 0x56E1, 0xC9C7, 0x56DF, + 0xC9C8, 0x572E, 0xC9C9, 0x572A, 0xC9CA, 0x5734, 0xC9CB, 0x593C, 0xC9CC, 0x5980, 0xC9CD, 0x597C, 0xC9CE, 0x5985, 0xC9CF, 0x597B, + 0xC9D0, 0x597E, 0xC9D1, 0x5977, 0xC9D2, 0x597F, 0xC9D3, 0x5B56, 0xC9D4, 0x5C15, 0xC9D5, 0x5C25, 0xC9D6, 0x5C7C, 0xC9D7, 0x5C7A, + 0xC9D8, 0x5C7B, 0xC9D9, 0x5C7E, 0xC9DA, 0x5DDF, 0xC9DB, 0x5E75, 0xC9DC, 0x5E84, 0xC9DD, 0x5F02, 0xC9DE, 0x5F1A, 0xC9DF, 0x5F74, + 0xC9E0, 0x5FD5, 0xC9E1, 0x5FD4, 0xC9E2, 0x5FCF, 0xC9E3, 0x625C, 0xC9E4, 0x625E, 0xC9E5, 0x6264, 0xC9E6, 0x6261, 0xC9E7, 0x6266, + 0xC9E8, 0x6262, 0xC9E9, 0x6259, 0xC9EA, 0x6260, 0xC9EB, 0x625A, 0xC9EC, 0x6265, 0xC9ED, 0x65EF, 0xC9EE, 0x65EE, 0xC9EF, 0x673E, + 0xC9F0, 0x6739, 0xC9F1, 0x6738, 0xC9F2, 0x673B, 0xC9F3, 0x673A, 0xC9F4, 0x673F, 0xC9F5, 0x673C, 0xC9F6, 0x6733, 0xC9F7, 0x6C18, + 0xC9F8, 0x6C46, 0xC9F9, 0x6C52, 0xC9FA, 0x6C5C, 0xC9FB, 0x6C4F, 0xC9FC, 0x6C4A, 0xC9FD, 0x6C54, 0xC9FE, 0x6C4B, 0xCA40, 0x6C4C, + 0xCA41, 0x7071, 0xCA42, 0x725E, 0xCA43, 0x72B4, 0xCA44, 0x72B5, 0xCA45, 0x738E, 0xCA46, 0x752A, 0xCA47, 0x767F, 0xCA48, 0x7A75, + 0xCA49, 0x7F51, 0xCA4A, 0x8278, 0xCA4B, 0x827C, 0xCA4C, 0x8280, 0xCA4D, 0x827D, 0xCA4E, 0x827F, 0xCA4F, 0x864D, 0xCA50, 0x897E, + 0xCA51, 0x9099, 0xCA52, 0x9097, 0xCA53, 0x9098, 0xCA54, 0x909B, 0xCA55, 0x9094, 0xCA56, 0x9622, 0xCA57, 0x9624, 0xCA58, 0x9620, + 0xCA59, 0x9623, 0xCA5A, 0x4F56, 0xCA5B, 0x4F3B, 0xCA5C, 0x4F62, 0xCA5D, 0x4F49, 0xCA5E, 0x4F53, 0xCA5F, 0x4F64, 0xCA60, 0x4F3E, + 0xCA61, 0x4F67, 0xCA62, 0x4F52, 0xCA63, 0x4F5F, 0xCA64, 0x4F41, 0xCA65, 0x4F58, 0xCA66, 0x4F2D, 0xCA67, 0x4F33, 0xCA68, 0x4F3F, + 0xCA69, 0x4F61, 0xCA6A, 0x518F, 0xCA6B, 0x51B9, 0xCA6C, 0x521C, 0xCA6D, 0x521E, 0xCA6E, 0x5221, 0xCA6F, 0x52AD, 0xCA70, 0x52AE, + 0xCA71, 0x5309, 0xCA72, 0x5363, 0xCA73, 0x5372, 0xCA74, 0x538E, 0xCA75, 0x538F, 0xCA76, 0x5430, 0xCA77, 0x5437, 0xCA78, 0x542A, + 0xCA79, 0x5454, 0xCA7A, 0x5445, 0xCA7B, 0x5419, 0xCA7C, 0x541C, 0xCA7D, 0x5425, 0xCA7E, 0x5418, 0xCAA1, 0x543D, 0xCAA2, 0x544F, + 0xCAA3, 0x5441, 0xCAA4, 0x5428, 0xCAA5, 0x5424, 0xCAA6, 0x5447, 0xCAA7, 0x56EE, 0xCAA8, 0x56E7, 0xCAA9, 0x56E5, 0xCAAA, 0x5741, + 0xCAAB, 0x5745, 0xCAAC, 0x574C, 0xCAAD, 0x5749, 0xCAAE, 0x574B, 0xCAAF, 0x5752, 0xCAB0, 0x5906, 0xCAB1, 0x5940, 0xCAB2, 0x59A6, + 0xCAB3, 0x5998, 0xCAB4, 0x59A0, 0xCAB5, 0x5997, 0xCAB6, 0x598E, 0xCAB7, 0x59A2, 0xCAB8, 0x5990, 0xCAB9, 0x598F, 0xCABA, 0x59A7, + 0xCABB, 0x59A1, 0xCABC, 0x5B8E, 0xCABD, 0x5B92, 0xCABE, 0x5C28, 0xCABF, 0x5C2A, 0xCAC0, 0x5C8D, 0xCAC1, 0x5C8F, 0xCAC2, 0x5C88, + 0xCAC3, 0x5C8B, 0xCAC4, 0x5C89, 0xCAC5, 0x5C92, 0xCAC6, 0x5C8A, 0xCAC7, 0x5C86, 0xCAC8, 0x5C93, 0xCAC9, 0x5C95, 0xCACA, 0x5DE0, + 0xCACB, 0x5E0A, 0xCACC, 0x5E0E, 0xCACD, 0x5E8B, 0xCACE, 0x5E89, 0xCACF, 0x5E8C, 0xCAD0, 0x5E88, 0xCAD1, 0x5E8D, 0xCAD2, 0x5F05, + 0xCAD3, 0x5F1D, 0xCAD4, 0x5F78, 0xCAD5, 0x5F76, 0xCAD6, 0x5FD2, 0xCAD7, 0x5FD1, 0xCAD8, 0x5FD0, 0xCAD9, 0x5FED, 0xCADA, 0x5FE8, + 0xCADB, 0x5FEE, 0xCADC, 0x5FF3, 0xCADD, 0x5FE1, 0xCADE, 0x5FE4, 0xCADF, 0x5FE3, 0xCAE0, 0x5FFA, 0xCAE1, 0x5FEF, 0xCAE2, 0x5FF7, + 0xCAE3, 0x5FFB, 0xCAE4, 0x6000, 0xCAE5, 0x5FF4, 0xCAE6, 0x623A, 0xCAE7, 0x6283, 0xCAE8, 0x628C, 0xCAE9, 0x628E, 0xCAEA, 0x628F, + 0xCAEB, 0x6294, 0xCAEC, 0x6287, 0xCAED, 0x6271, 0xCAEE, 0x627B, 0xCAEF, 0x627A, 0xCAF0, 0x6270, 0xCAF1, 0x6281, 0xCAF2, 0x6288, + 0xCAF3, 0x6277, 0xCAF4, 0x627D, 0xCAF5, 0x6272, 0xCAF6, 0x6274, 0xCAF7, 0x6537, 0xCAF8, 0x65F0, 0xCAF9, 0x65F4, 0xCAFA, 0x65F3, + 0xCAFB, 0x65F2, 0xCAFC, 0x65F5, 0xCAFD, 0x6745, 0xCAFE, 0x6747, 0xCB40, 0x6759, 0xCB41, 0x6755, 0xCB42, 0x674C, 0xCB43, 0x6748, + 0xCB44, 0x675D, 0xCB45, 0x674D, 0xCB46, 0x675A, 0xCB47, 0x674B, 0xCB48, 0x6BD0, 0xCB49, 0x6C19, 0xCB4A, 0x6C1A, 0xCB4B, 0x6C78, + 0xCB4C, 0x6C67, 0xCB4D, 0x6C6B, 0xCB4E, 0x6C84, 0xCB4F, 0x6C8B, 0xCB50, 0x6C8F, 0xCB51, 0x6C71, 0xCB52, 0x6C6F, 0xCB53, 0x6C69, + 0xCB54, 0x6C9A, 0xCB55, 0x6C6D, 0xCB56, 0x6C87, 0xCB57, 0x6C95, 0xCB58, 0x6C9C, 0xCB59, 0x6C66, 0xCB5A, 0x6C73, 0xCB5B, 0x6C65, + 0xCB5C, 0x6C7B, 0xCB5D, 0x6C8E, 0xCB5E, 0x7074, 0xCB5F, 0x707A, 0xCB60, 0x7263, 0xCB61, 0x72BF, 0xCB62, 0x72BD, 0xCB63, 0x72C3, + 0xCB64, 0x72C6, 0xCB65, 0x72C1, 0xCB66, 0x72BA, 0xCB67, 0x72C5, 0xCB68, 0x7395, 0xCB69, 0x7397, 0xCB6A, 0x7393, 0xCB6B, 0x7394, + 0xCB6C, 0x7392, 0xCB6D, 0x753A, 0xCB6E, 0x7539, 0xCB6F, 0x7594, 0xCB70, 0x7595, 0xCB71, 0x7681, 0xCB72, 0x793D, 0xCB73, 0x8034, + 0xCB74, 0x8095, 0xCB75, 0x8099, 0xCB76, 0x8090, 0xCB77, 0x8092, 0xCB78, 0x809C, 0xCB79, 0x8290, 0xCB7A, 0x828F, 0xCB7B, 0x8285, + 0xCB7C, 0x828E, 0xCB7D, 0x8291, 0xCB7E, 0x8293, 0xCBA1, 0x828A, 0xCBA2, 0x8283, 0xCBA3, 0x8284, 0xCBA4, 0x8C78, 0xCBA5, 0x8FC9, + 0xCBA6, 0x8FBF, 0xCBA7, 0x909F, 0xCBA8, 0x90A1, 0xCBA9, 0x90A5, 0xCBAA, 0x909E, 0xCBAB, 0x90A7, 0xCBAC, 0x90A0, 0xCBAD, 0x9630, + 0xCBAE, 0x9628, 0xCBAF, 0x962F, 0xCBB0, 0x962D, 0xCBB1, 0x4E33, 0xCBB2, 0x4F98, 0xCBB3, 0x4F7C, 0xCBB4, 0x4F85, 0xCBB5, 0x4F7D, + 0xCBB6, 0x4F80, 0xCBB7, 0x4F87, 0xCBB8, 0x4F76, 0xCBB9, 0x4F74, 0xCBBA, 0x4F89, 0xCBBB, 0x4F84, 0xCBBC, 0x4F77, 0xCBBD, 0x4F4C, + 0xCBBE, 0x4F97, 0xCBBF, 0x4F6A, 0xCBC0, 0x4F9A, 0xCBC1, 0x4F79, 0xCBC2, 0x4F81, 0xCBC3, 0x4F78, 0xCBC4, 0x4F90, 0xCBC5, 0x4F9C, + 0xCBC6, 0x4F94, 0xCBC7, 0x4F9E, 0xCBC8, 0x4F92, 0xCBC9, 0x4F82, 0xCBCA, 0x4F95, 0xCBCB, 0x4F6B, 0xCBCC, 0x4F6E, 0xCBCD, 0x519E, + 0xCBCE, 0x51BC, 0xCBCF, 0x51BE, 0xCBD0, 0x5235, 0xCBD1, 0x5232, 0xCBD2, 0x5233, 0xCBD3, 0x5246, 0xCBD4, 0x5231, 0xCBD5, 0x52BC, + 0xCBD6, 0x530A, 0xCBD7, 0x530B, 0xCBD8, 0x533C, 0xCBD9, 0x5392, 0xCBDA, 0x5394, 0xCBDB, 0x5487, 0xCBDC, 0x547F, 0xCBDD, 0x5481, + 0xCBDE, 0x5491, 0xCBDF, 0x5482, 0xCBE0, 0x5488, 0xCBE1, 0x546B, 0xCBE2, 0x547A, 0xCBE3, 0x547E, 0xCBE4, 0x5465, 0xCBE5, 0x546C, + 0xCBE6, 0x5474, 0xCBE7, 0x5466, 0xCBE8, 0x548D, 0xCBE9, 0x546F, 0xCBEA, 0x5461, 0xCBEB, 0x5460, 0xCBEC, 0x5498, 0xCBED, 0x5463, + 0xCBEE, 0x5467, 0xCBEF, 0x5464, 0xCBF0, 0x56F7, 0xCBF1, 0x56F9, 0xCBF2, 0x576F, 0xCBF3, 0x5772, 0xCBF4, 0x576D, 0xCBF5, 0x576B, + 0xCBF6, 0x5771, 0xCBF7, 0x5770, 0xCBF8, 0x5776, 0xCBF9, 0x5780, 0xCBFA, 0x5775, 0xCBFB, 0x577B, 0xCBFC, 0x5773, 0xCBFD, 0x5774, + 0xCBFE, 0x5762, 0xCC40, 0x5768, 0xCC41, 0x577D, 0xCC42, 0x590C, 0xCC43, 0x5945, 0xCC44, 0x59B5, 0xCC45, 0x59BA, 0xCC46, 0x59CF, + 0xCC47, 0x59CE, 0xCC48, 0x59B2, 0xCC49, 0x59CC, 0xCC4A, 0x59C1, 0xCC4B, 0x59B6, 0xCC4C, 0x59BC, 0xCC4D, 0x59C3, 0xCC4E, 0x59D6, + 0xCC4F, 0x59B1, 0xCC50, 0x59BD, 0xCC51, 0x59C0, 0xCC52, 0x59C8, 0xCC53, 0x59B4, 0xCC54, 0x59C7, 0xCC55, 0x5B62, 0xCC56, 0x5B65, + 0xCC57, 0x5B93, 0xCC58, 0x5B95, 0xCC59, 0x5C44, 0xCC5A, 0x5C47, 0xCC5B, 0x5CAE, 0xCC5C, 0x5CA4, 0xCC5D, 0x5CA0, 0xCC5E, 0x5CB5, + 0xCC5F, 0x5CAF, 0xCC60, 0x5CA8, 0xCC61, 0x5CAC, 0xCC62, 0x5C9F, 0xCC63, 0x5CA3, 0xCC64, 0x5CAD, 0xCC65, 0x5CA2, 0xCC66, 0x5CAA, + 0xCC67, 0x5CA7, 0xCC68, 0x5C9D, 0xCC69, 0x5CA5, 0xCC6A, 0x5CB6, 0xCC6B, 0x5CB0, 0xCC6C, 0x5CA6, 0xCC6D, 0x5E17, 0xCC6E, 0x5E14, + 0xCC6F, 0x5E19, 0xCC70, 0x5F28, 0xCC71, 0x5F22, 0xCC72, 0x5F23, 0xCC73, 0x5F24, 0xCC74, 0x5F54, 0xCC75, 0x5F82, 0xCC76, 0x5F7E, + 0xCC77, 0x5F7D, 0xCC78, 0x5FDE, 0xCC79, 0x5FE5, 0xCC7A, 0x602D, 0xCC7B, 0x6026, 0xCC7C, 0x6019, 0xCC7D, 0x6032, 0xCC7E, 0x600B, + 0xCCA1, 0x6034, 0xCCA2, 0x600A, 0xCCA3, 0x6017, 0xCCA4, 0x6033, 0xCCA5, 0x601A, 0xCCA6, 0x601E, 0xCCA7, 0x602C, 0xCCA8, 0x6022, + 0xCCA9, 0x600D, 0xCCAA, 0x6010, 0xCCAB, 0x602E, 0xCCAC, 0x6013, 0xCCAD, 0x6011, 0xCCAE, 0x600C, 0xCCAF, 0x6009, 0xCCB0, 0x601C, + 0xCCB1, 0x6214, 0xCCB2, 0x623D, 0xCCB3, 0x62AD, 0xCCB4, 0x62B4, 0xCCB5, 0x62D1, 0xCCB6, 0x62BE, 0xCCB7, 0x62AA, 0xCCB8, 0x62B6, + 0xCCB9, 0x62CA, 0xCCBA, 0x62AE, 0xCCBB, 0x62B3, 0xCCBC, 0x62AF, 0xCCBD, 0x62BB, 0xCCBE, 0x62A9, 0xCCBF, 0x62B0, 0xCCC0, 0x62B8, + 0xCCC1, 0x653D, 0xCCC2, 0x65A8, 0xCCC3, 0x65BB, 0xCCC4, 0x6609, 0xCCC5, 0x65FC, 0xCCC6, 0x6604, 0xCCC7, 0x6612, 0xCCC8, 0x6608, + 0xCCC9, 0x65FB, 0xCCCA, 0x6603, 0xCCCB, 0x660B, 0xCCCC, 0x660D, 0xCCCD, 0x6605, 0xCCCE, 0x65FD, 0xCCCF, 0x6611, 0xCCD0, 0x6610, + 0xCCD1, 0x66F6, 0xCCD2, 0x670A, 0xCCD3, 0x6785, 0xCCD4, 0x676C, 0xCCD5, 0x678E, 0xCCD6, 0x6792, 0xCCD7, 0x6776, 0xCCD8, 0x677B, + 0xCCD9, 0x6798, 0xCCDA, 0x6786, 0xCCDB, 0x6784, 0xCCDC, 0x6774, 0xCCDD, 0x678D, 0xCCDE, 0x678C, 0xCCDF, 0x677A, 0xCCE0, 0x679F, + 0xCCE1, 0x6791, 0xCCE2, 0x6799, 0xCCE3, 0x6783, 0xCCE4, 0x677D, 0xCCE5, 0x6781, 0xCCE6, 0x6778, 0xCCE7, 0x6779, 0xCCE8, 0x6794, + 0xCCE9, 0x6B25, 0xCCEA, 0x6B80, 0xCCEB, 0x6B7E, 0xCCEC, 0x6BDE, 0xCCED, 0x6C1D, 0xCCEE, 0x6C93, 0xCCEF, 0x6CEC, 0xCCF0, 0x6CEB, + 0xCCF1, 0x6CEE, 0xCCF2, 0x6CD9, 0xCCF3, 0x6CB6, 0xCCF4, 0x6CD4, 0xCCF5, 0x6CAD, 0xCCF6, 0x6CE7, 0xCCF7, 0x6CB7, 0xCCF8, 0x6CD0, + 0xCCF9, 0x6CC2, 0xCCFA, 0x6CBA, 0xCCFB, 0x6CC3, 0xCCFC, 0x6CC6, 0xCCFD, 0x6CED, 0xCCFE, 0x6CF2, 0xCD40, 0x6CD2, 0xCD41, 0x6CDD, + 0xCD42, 0x6CB4, 0xCD43, 0x6C8A, 0xCD44, 0x6C9D, 0xCD45, 0x6C80, 0xCD46, 0x6CDE, 0xCD47, 0x6CC0, 0xCD48, 0x6D30, 0xCD49, 0x6CCD, + 0xCD4A, 0x6CC7, 0xCD4B, 0x6CB0, 0xCD4C, 0x6CF9, 0xCD4D, 0x6CCF, 0xCD4E, 0x6CE9, 0xCD4F, 0x6CD1, 0xCD50, 0x7094, 0xCD51, 0x7098, + 0xCD52, 0x7085, 0xCD53, 0x7093, 0xCD54, 0x7086, 0xCD55, 0x7084, 0xCD56, 0x7091, 0xCD57, 0x7096, 0xCD58, 0x7082, 0xCD59, 0x709A, + 0xCD5A, 0x7083, 0xCD5B, 0x726A, 0xCD5C, 0x72D6, 0xCD5D, 0x72CB, 0xCD5E, 0x72D8, 0xCD5F, 0x72C9, 0xCD60, 0x72DC, 0xCD61, 0x72D2, + 0xCD62, 0x72D4, 0xCD63, 0x72DA, 0xCD64, 0x72CC, 0xCD65, 0x72D1, 0xCD66, 0x73A4, 0xCD67, 0x73A1, 0xCD68, 0x73AD, 0xCD69, 0x73A6, + 0xCD6A, 0x73A2, 0xCD6B, 0x73A0, 0xCD6C, 0x73AC, 0xCD6D, 0x739D, 0xCD6E, 0x74DD, 0xCD6F, 0x74E8, 0xCD70, 0x753F, 0xCD71, 0x7540, + 0xCD72, 0x753E, 0xCD73, 0x758C, 0xCD74, 0x7598, 0xCD75, 0x76AF, 0xCD76, 0x76F3, 0xCD77, 0x76F1, 0xCD78, 0x76F0, 0xCD79, 0x76F5, + 0xCD7A, 0x77F8, 0xCD7B, 0x77FC, 0xCD7C, 0x77F9, 0xCD7D, 0x77FB, 0xCD7E, 0x77FA, 0xCDA1, 0x77F7, 0xCDA2, 0x7942, 0xCDA3, 0x793F, + 0xCDA4, 0x79C5, 0xCDA5, 0x7A78, 0xCDA6, 0x7A7B, 0xCDA7, 0x7AFB, 0xCDA8, 0x7C75, 0xCDA9, 0x7CFD, 0xCDAA, 0x8035, 0xCDAB, 0x808F, + 0xCDAC, 0x80AE, 0xCDAD, 0x80A3, 0xCDAE, 0x80B8, 0xCDAF, 0x80B5, 0xCDB0, 0x80AD, 0xCDB1, 0x8220, 0xCDB2, 0x82A0, 0xCDB3, 0x82C0, + 0xCDB4, 0x82AB, 0xCDB5, 0x829A, 0xCDB6, 0x8298, 0xCDB7, 0x829B, 0xCDB8, 0x82B5, 0xCDB9, 0x82A7, 0xCDBA, 0x82AE, 0xCDBB, 0x82BC, + 0xCDBC, 0x829E, 0xCDBD, 0x82BA, 0xCDBE, 0x82B4, 0xCDBF, 0x82A8, 0xCDC0, 0x82A1, 0xCDC1, 0x82A9, 0xCDC2, 0x82C2, 0xCDC3, 0x82A4, + 0xCDC4, 0x82C3, 0xCDC5, 0x82B6, 0xCDC6, 0x82A2, 0xCDC7, 0x8670, 0xCDC8, 0x866F, 0xCDC9, 0x866D, 0xCDCA, 0x866E, 0xCDCB, 0x8C56, + 0xCDCC, 0x8FD2, 0xCDCD, 0x8FCB, 0xCDCE, 0x8FD3, 0xCDCF, 0x8FCD, 0xCDD0, 0x8FD6, 0xCDD1, 0x8FD5, 0xCDD2, 0x8FD7, 0xCDD3, 0x90B2, + 0xCDD4, 0x90B4, 0xCDD5, 0x90AF, 0xCDD6, 0x90B3, 0xCDD7, 0x90B0, 0xCDD8, 0x9639, 0xCDD9, 0x963D, 0xCDDA, 0x963C, 0xCDDB, 0x963A, + 0xCDDC, 0x9643, 0xCDDD, 0x4FCD, 0xCDDE, 0x4FC5, 0xCDDF, 0x4FD3, 0xCDE0, 0x4FB2, 0xCDE1, 0x4FC9, 0xCDE2, 0x4FCB, 0xCDE3, 0x4FC1, + 0xCDE4, 0x4FD4, 0xCDE5, 0x4FDC, 0xCDE6, 0x4FD9, 0xCDE7, 0x4FBB, 0xCDE8, 0x4FB3, 0xCDE9, 0x4FDB, 0xCDEA, 0x4FC7, 0xCDEB, 0x4FD6, + 0xCDEC, 0x4FBA, 0xCDED, 0x4FC0, 0xCDEE, 0x4FB9, 0xCDEF, 0x4FEC, 0xCDF0, 0x5244, 0xCDF1, 0x5249, 0xCDF2, 0x52C0, 0xCDF3, 0x52C2, + 0xCDF4, 0x533D, 0xCDF5, 0x537C, 0xCDF6, 0x5397, 0xCDF7, 0x5396, 0xCDF8, 0x5399, 0xCDF9, 0x5398, 0xCDFA, 0x54BA, 0xCDFB, 0x54A1, + 0xCDFC, 0x54AD, 0xCDFD, 0x54A5, 0xCDFE, 0x54CF, 0xCE40, 0x54C3, 0xCE41, 0x830D, 0xCE42, 0x54B7, 0xCE43, 0x54AE, 0xCE44, 0x54D6, + 0xCE45, 0x54B6, 0xCE46, 0x54C5, 0xCE47, 0x54C6, 0xCE48, 0x54A0, 0xCE49, 0x5470, 0xCE4A, 0x54BC, 0xCE4B, 0x54A2, 0xCE4C, 0x54BE, + 0xCE4D, 0x5472, 0xCE4E, 0x54DE, 0xCE4F, 0x54B0, 0xCE50, 0x57B5, 0xCE51, 0x579E, 0xCE52, 0x579F, 0xCE53, 0x57A4, 0xCE54, 0x578C, + 0xCE55, 0x5797, 0xCE56, 0x579D, 0xCE57, 0x579B, 0xCE58, 0x5794, 0xCE59, 0x5798, 0xCE5A, 0x578F, 0xCE5B, 0x5799, 0xCE5C, 0x57A5, + 0xCE5D, 0x579A, 0xCE5E, 0x5795, 0xCE5F, 0x58F4, 0xCE60, 0x590D, 0xCE61, 0x5953, 0xCE62, 0x59E1, 0xCE63, 0x59DE, 0xCE64, 0x59EE, + 0xCE65, 0x5A00, 0xCE66, 0x59F1, 0xCE67, 0x59DD, 0xCE68, 0x59FA, 0xCE69, 0x59FD, 0xCE6A, 0x59FC, 0xCE6B, 0x59F6, 0xCE6C, 0x59E4, + 0xCE6D, 0x59F2, 0xCE6E, 0x59F7, 0xCE6F, 0x59DB, 0xCE70, 0x59E9, 0xCE71, 0x59F3, 0xCE72, 0x59F5, 0xCE73, 0x59E0, 0xCE74, 0x59FE, + 0xCE75, 0x59F4, 0xCE76, 0x59ED, 0xCE77, 0x5BA8, 0xCE78, 0x5C4C, 0xCE79, 0x5CD0, 0xCE7A, 0x5CD8, 0xCE7B, 0x5CCC, 0xCE7C, 0x5CD7, + 0xCE7D, 0x5CCB, 0xCE7E, 0x5CDB, 0xCEA1, 0x5CDE, 0xCEA2, 0x5CDA, 0xCEA3, 0x5CC9, 0xCEA4, 0x5CC7, 0xCEA5, 0x5CCA, 0xCEA6, 0x5CD6, + 0xCEA7, 0x5CD3, 0xCEA8, 0x5CD4, 0xCEA9, 0x5CCF, 0xCEAA, 0x5CC8, 0xCEAB, 0x5CC6, 0xCEAC, 0x5CCE, 0xCEAD, 0x5CDF, 0xCEAE, 0x5CF8, + 0xCEAF, 0x5DF9, 0xCEB0, 0x5E21, 0xCEB1, 0x5E22, 0xCEB2, 0x5E23, 0xCEB3, 0x5E20, 0xCEB4, 0x5E24, 0xCEB5, 0x5EB0, 0xCEB6, 0x5EA4, + 0xCEB7, 0x5EA2, 0xCEB8, 0x5E9B, 0xCEB9, 0x5EA3, 0xCEBA, 0x5EA5, 0xCEBB, 0x5F07, 0xCEBC, 0x5F2E, 0xCEBD, 0x5F56, 0xCEBE, 0x5F86, + 0xCEBF, 0x6037, 0xCEC0, 0x6039, 0xCEC1, 0x6054, 0xCEC2, 0x6072, 0xCEC3, 0x605E, 0xCEC4, 0x6045, 0xCEC5, 0x6053, 0xCEC6, 0x6047, + 0xCEC7, 0x6049, 0xCEC8, 0x605B, 0xCEC9, 0x604C, 0xCECA, 0x6040, 0xCECB, 0x6042, 0xCECC, 0x605F, 0xCECD, 0x6024, 0xCECE, 0x6044, + 0xCECF, 0x6058, 0xCED0, 0x6066, 0xCED1, 0x606E, 0xCED2, 0x6242, 0xCED3, 0x6243, 0xCED4, 0x62CF, 0xCED5, 0x630D, 0xCED6, 0x630B, + 0xCED7, 0x62F5, 0xCED8, 0x630E, 0xCED9, 0x6303, 0xCEDA, 0x62EB, 0xCEDB, 0x62F9, 0xCEDC, 0x630F, 0xCEDD, 0x630C, 0xCEDE, 0x62F8, + 0xCEDF, 0x62F6, 0xCEE0, 0x6300, 0xCEE1, 0x6313, 0xCEE2, 0x6314, 0xCEE3, 0x62FA, 0xCEE4, 0x6315, 0xCEE5, 0x62FB, 0xCEE6, 0x62F0, + 0xCEE7, 0x6541, 0xCEE8, 0x6543, 0xCEE9, 0x65AA, 0xCEEA, 0x65BF, 0xCEEB, 0x6636, 0xCEEC, 0x6621, 0xCEED, 0x6632, 0xCEEE, 0x6635, + 0xCEEF, 0x661C, 0xCEF0, 0x6626, 0xCEF1, 0x6622, 0xCEF2, 0x6633, 0xCEF3, 0x662B, 0xCEF4, 0x663A, 0xCEF5, 0x661D, 0xCEF6, 0x6634, + 0xCEF7, 0x6639, 0xCEF8, 0x662E, 0xCEF9, 0x670F, 0xCEFA, 0x6710, 0xCEFB, 0x67C1, 0xCEFC, 0x67F2, 0xCEFD, 0x67C8, 0xCEFE, 0x67BA, + 0xCF40, 0x67DC, 0xCF41, 0x67BB, 0xCF42, 0x67F8, 0xCF43, 0x67D8, 0xCF44, 0x67C0, 0xCF45, 0x67B7, 0xCF46, 0x67C5, 0xCF47, 0x67EB, + 0xCF48, 0x67E4, 0xCF49, 0x67DF, 0xCF4A, 0x67B5, 0xCF4B, 0x67CD, 0xCF4C, 0x67B3, 0xCF4D, 0x67F7, 0xCF4E, 0x67F6, 0xCF4F, 0x67EE, + 0xCF50, 0x67E3, 0xCF51, 0x67C2, 0xCF52, 0x67B9, 0xCF53, 0x67CE, 0xCF54, 0x67E7, 0xCF55, 0x67F0, 0xCF56, 0x67B2, 0xCF57, 0x67FC, + 0xCF58, 0x67C6, 0xCF59, 0x67ED, 0xCF5A, 0x67CC, 0xCF5B, 0x67AE, 0xCF5C, 0x67E6, 0xCF5D, 0x67DB, 0xCF5E, 0x67FA, 0xCF5F, 0x67C9, + 0xCF60, 0x67CA, 0xCF61, 0x67C3, 0xCF62, 0x67EA, 0xCF63, 0x67CB, 0xCF64, 0x6B28, 0xCF65, 0x6B82, 0xCF66, 0x6B84, 0xCF67, 0x6BB6, + 0xCF68, 0x6BD6, 0xCF69, 0x6BD8, 0xCF6A, 0x6BE0, 0xCF6B, 0x6C20, 0xCF6C, 0x6C21, 0xCF6D, 0x6D28, 0xCF6E, 0x6D34, 0xCF6F, 0x6D2D, + 0xCF70, 0x6D1F, 0xCF71, 0x6D3C, 0xCF72, 0x6D3F, 0xCF73, 0x6D12, 0xCF74, 0x6D0A, 0xCF75, 0x6CDA, 0xCF76, 0x6D33, 0xCF77, 0x6D04, + 0xCF78, 0x6D19, 0xCF79, 0x6D3A, 0xCF7A, 0x6D1A, 0xCF7B, 0x6D11, 0xCF7C, 0x6D00, 0xCF7D, 0x6D1D, 0xCF7E, 0x6D42, 0xCFA1, 0x6D01, + 0xCFA2, 0x6D18, 0xCFA3, 0x6D37, 0xCFA4, 0x6D03, 0xCFA5, 0x6D0F, 0xCFA6, 0x6D40, 0xCFA7, 0x6D07, 0xCFA8, 0x6D20, 0xCFA9, 0x6D2C, + 0xCFAA, 0x6D08, 0xCFAB, 0x6D22, 0xCFAC, 0x6D09, 0xCFAD, 0x6D10, 0xCFAE, 0x70B7, 0xCFAF, 0x709F, 0xCFB0, 0x70BE, 0xCFB1, 0x70B1, + 0xCFB2, 0x70B0, 0xCFB3, 0x70A1, 0xCFB4, 0x70B4, 0xCFB5, 0x70B5, 0xCFB6, 0x70A9, 0xCFB7, 0x7241, 0xCFB8, 0x7249, 0xCFB9, 0x724A, + 0xCFBA, 0x726C, 0xCFBB, 0x7270, 0xCFBC, 0x7273, 0xCFBD, 0x726E, 0xCFBE, 0x72CA, 0xCFBF, 0x72E4, 0xCFC0, 0x72E8, 0xCFC1, 0x72EB, + 0xCFC2, 0x72DF, 0xCFC3, 0x72EA, 0xCFC4, 0x72E6, 0xCFC5, 0x72E3, 0xCFC6, 0x7385, 0xCFC7, 0x73CC, 0xCFC8, 0x73C2, 0xCFC9, 0x73C8, + 0xCFCA, 0x73C5, 0xCFCB, 0x73B9, 0xCFCC, 0x73B6, 0xCFCD, 0x73B5, 0xCFCE, 0x73B4, 0xCFCF, 0x73EB, 0xCFD0, 0x73BF, 0xCFD1, 0x73C7, + 0xCFD2, 0x73BE, 0xCFD3, 0x73C3, 0xCFD4, 0x73C6, 0xCFD5, 0x73B8, 0xCFD6, 0x73CB, 0xCFD7, 0x74EC, 0xCFD8, 0x74EE, 0xCFD9, 0x752E, + 0xCFDA, 0x7547, 0xCFDB, 0x7548, 0xCFDC, 0x75A7, 0xCFDD, 0x75AA, 0xCFDE, 0x7679, 0xCFDF, 0x76C4, 0xCFE0, 0x7708, 0xCFE1, 0x7703, + 0xCFE2, 0x7704, 0xCFE3, 0x7705, 0xCFE4, 0x770A, 0xCFE5, 0x76F7, 0xCFE6, 0x76FB, 0xCFE7, 0x76FA, 0xCFE8, 0x77E7, 0xCFE9, 0x77E8, + 0xCFEA, 0x7806, 0xCFEB, 0x7811, 0xCFEC, 0x7812, 0xCFED, 0x7805, 0xCFEE, 0x7810, 0xCFEF, 0x780F, 0xCFF0, 0x780E, 0xCFF1, 0x7809, + 0xCFF2, 0x7803, 0xCFF3, 0x7813, 0xCFF4, 0x794A, 0xCFF5, 0x794C, 0xCFF6, 0x794B, 0xCFF7, 0x7945, 0xCFF8, 0x7944, 0xCFF9, 0x79D5, + 0xCFFA, 0x79CD, 0xCFFB, 0x79CF, 0xCFFC, 0x79D6, 0xCFFD, 0x79CE, 0xCFFE, 0x7A80, 0xD040, 0x7A7E, 0xD041, 0x7AD1, 0xD042, 0x7B00, + 0xD043, 0x7B01, 0xD044, 0x7C7A, 0xD045, 0x7C78, 0xD046, 0x7C79, 0xD047, 0x7C7F, 0xD048, 0x7C80, 0xD049, 0x7C81, 0xD04A, 0x7D03, + 0xD04B, 0x7D08, 0xD04C, 0x7D01, 0xD04D, 0x7F58, 0xD04E, 0x7F91, 0xD04F, 0x7F8D, 0xD050, 0x7FBE, 0xD051, 0x8007, 0xD052, 0x800E, + 0xD053, 0x800F, 0xD054, 0x8014, 0xD055, 0x8037, 0xD056, 0x80D8, 0xD057, 0x80C7, 0xD058, 0x80E0, 0xD059, 0x80D1, 0xD05A, 0x80C8, + 0xD05B, 0x80C2, 0xD05C, 0x80D0, 0xD05D, 0x80C5, 0xD05E, 0x80E3, 0xD05F, 0x80D9, 0xD060, 0x80DC, 0xD061, 0x80CA, 0xD062, 0x80D5, + 0xD063, 0x80C9, 0xD064, 0x80CF, 0xD065, 0x80D7, 0xD066, 0x80E6, 0xD067, 0x80CD, 0xD068, 0x81FF, 0xD069, 0x8221, 0xD06A, 0x8294, + 0xD06B, 0x82D9, 0xD06C, 0x82FE, 0xD06D, 0x82F9, 0xD06E, 0x8307, 0xD06F, 0x82E8, 0xD070, 0x8300, 0xD071, 0x82D5, 0xD072, 0x833A, + 0xD073, 0x82EB, 0xD074, 0x82D6, 0xD075, 0x82F4, 0xD076, 0x82EC, 0xD077, 0x82E1, 0xD078, 0x82F2, 0xD079, 0x82F5, 0xD07A, 0x830C, + 0xD07B, 0x82FB, 0xD07C, 0x82F6, 0xD07D, 0x82F0, 0xD07E, 0x82EA, 0xD0A1, 0x82E4, 0xD0A2, 0x82E0, 0xD0A3, 0x82FA, 0xD0A4, 0x82F3, + 0xD0A5, 0x82ED, 0xD0A6, 0x8677, 0xD0A7, 0x8674, 0xD0A8, 0x867C, 0xD0A9, 0x8673, 0xD0AA, 0x8841, 0xD0AB, 0x884E, 0xD0AC, 0x8867, + 0xD0AD, 0x886A, 0xD0AE, 0x8869, 0xD0AF, 0x89D3, 0xD0B0, 0x8A04, 0xD0B1, 0x8A07, 0xD0B2, 0x8D72, 0xD0B3, 0x8FE3, 0xD0B4, 0x8FE1, + 0xD0B5, 0x8FEE, 0xD0B6, 0x8FE0, 0xD0B7, 0x90F1, 0xD0B8, 0x90BD, 0xD0B9, 0x90BF, 0xD0BA, 0x90D5, 0xD0BB, 0x90C5, 0xD0BC, 0x90BE, + 0xD0BD, 0x90C7, 0xD0BE, 0x90CB, 0xD0BF, 0x90C8, 0xD0C0, 0x91D4, 0xD0C1, 0x91D3, 0xD0C2, 0x9654, 0xD0C3, 0x964F, 0xD0C4, 0x9651, + 0xD0C5, 0x9653, 0xD0C6, 0x964A, 0xD0C7, 0x964E, 0xD0C8, 0x501E, 0xD0C9, 0x5005, 0xD0CA, 0x5007, 0xD0CB, 0x5013, 0xD0CC, 0x5022, + 0xD0CD, 0x5030, 0xD0CE, 0x501B, 0xD0CF, 0x4FF5, 0xD0D0, 0x4FF4, 0xD0D1, 0x5033, 0xD0D2, 0x5037, 0xD0D3, 0x502C, 0xD0D4, 0x4FF6, + 0xD0D5, 0x4FF7, 0xD0D6, 0x5017, 0xD0D7, 0x501C, 0xD0D8, 0x5020, 0xD0D9, 0x5027, 0xD0DA, 0x5035, 0xD0DB, 0x502F, 0xD0DC, 0x5031, + 0xD0DD, 0x500E, 0xD0DE, 0x515A, 0xD0DF, 0x5194, 0xD0E0, 0x5193, 0xD0E1, 0x51CA, 0xD0E2, 0x51C4, 0xD0E3, 0x51C5, 0xD0E4, 0x51C8, + 0xD0E5, 0x51CE, 0xD0E6, 0x5261, 0xD0E7, 0x525A, 0xD0E8, 0x5252, 0xD0E9, 0x525E, 0xD0EA, 0x525F, 0xD0EB, 0x5255, 0xD0EC, 0x5262, + 0xD0ED, 0x52CD, 0xD0EE, 0x530E, 0xD0EF, 0x539E, 0xD0F0, 0x5526, 0xD0F1, 0x54E2, 0xD0F2, 0x5517, 0xD0F3, 0x5512, 0xD0F4, 0x54E7, + 0xD0F5, 0x54F3, 0xD0F6, 0x54E4, 0xD0F7, 0x551A, 0xD0F8, 0x54FF, 0xD0F9, 0x5504, 0xD0FA, 0x5508, 0xD0FB, 0x54EB, 0xD0FC, 0x5511, + 0xD0FD, 0x5505, 0xD0FE, 0x54F1, 0xD140, 0x550A, 0xD141, 0x54FB, 0xD142, 0x54F7, 0xD143, 0x54F8, 0xD144, 0x54E0, 0xD145, 0x550E, + 0xD146, 0x5503, 0xD147, 0x550B, 0xD148, 0x5701, 0xD149, 0x5702, 0xD14A, 0x57CC, 0xD14B, 0x5832, 0xD14C, 0x57D5, 0xD14D, 0x57D2, + 0xD14E, 0x57BA, 0xD14F, 0x57C6, 0xD150, 0x57BD, 0xD151, 0x57BC, 0xD152, 0x57B8, 0xD153, 0x57B6, 0xD154, 0x57BF, 0xD155, 0x57C7, + 0xD156, 0x57D0, 0xD157, 0x57B9, 0xD158, 0x57C1, 0xD159, 0x590E, 0xD15A, 0x594A, 0xD15B, 0x5A19, 0xD15C, 0x5A16, 0xD15D, 0x5A2D, + 0xD15E, 0x5A2E, 0xD15F, 0x5A15, 0xD160, 0x5A0F, 0xD161, 0x5A17, 0xD162, 0x5A0A, 0xD163, 0x5A1E, 0xD164, 0x5A33, 0xD165, 0x5B6C, + 0xD166, 0x5BA7, 0xD167, 0x5BAD, 0xD168, 0x5BAC, 0xD169, 0x5C03, 0xD16A, 0x5C56, 0xD16B, 0x5C54, 0xD16C, 0x5CEC, 0xD16D, 0x5CFF, + 0xD16E, 0x5CEE, 0xD16F, 0x5CF1, 0xD170, 0x5CF7, 0xD171, 0x5D00, 0xD172, 0x5CF9, 0xD173, 0x5E29, 0xD174, 0x5E28, 0xD175, 0x5EA8, + 0xD176, 0x5EAE, 0xD177, 0x5EAA, 0xD178, 0x5EAC, 0xD179, 0x5F33, 0xD17A, 0x5F30, 0xD17B, 0x5F67, 0xD17C, 0x605D, 0xD17D, 0x605A, + 0xD17E, 0x6067, 0xD1A1, 0x6041, 0xD1A2, 0x60A2, 0xD1A3, 0x6088, 0xD1A4, 0x6080, 0xD1A5, 0x6092, 0xD1A6, 0x6081, 0xD1A7, 0x609D, + 0xD1A8, 0x6083, 0xD1A9, 0x6095, 0xD1AA, 0x609B, 0xD1AB, 0x6097, 0xD1AC, 0x6087, 0xD1AD, 0x609C, 0xD1AE, 0x608E, 0xD1AF, 0x6219, + 0xD1B0, 0x6246, 0xD1B1, 0x62F2, 0xD1B2, 0x6310, 0xD1B3, 0x6356, 0xD1B4, 0x632C, 0xD1B5, 0x6344, 0xD1B6, 0x6345, 0xD1B7, 0x6336, + 0xD1B8, 0x6343, 0xD1B9, 0x63E4, 0xD1BA, 0x6339, 0xD1BB, 0x634B, 0xD1BC, 0x634A, 0xD1BD, 0x633C, 0xD1BE, 0x6329, 0xD1BF, 0x6341, + 0xD1C0, 0x6334, 0xD1C1, 0x6358, 0xD1C2, 0x6354, 0xD1C3, 0x6359, 0xD1C4, 0x632D, 0xD1C5, 0x6347, 0xD1C6, 0x6333, 0xD1C7, 0x635A, + 0xD1C8, 0x6351, 0xD1C9, 0x6338, 0xD1CA, 0x6357, 0xD1CB, 0x6340, 0xD1CC, 0x6348, 0xD1CD, 0x654A, 0xD1CE, 0x6546, 0xD1CF, 0x65C6, + 0xD1D0, 0x65C3, 0xD1D1, 0x65C4, 0xD1D2, 0x65C2, 0xD1D3, 0x664A, 0xD1D4, 0x665F, 0xD1D5, 0x6647, 0xD1D6, 0x6651, 0xD1D7, 0x6712, + 0xD1D8, 0x6713, 0xD1D9, 0x681F, 0xD1DA, 0x681A, 0xD1DB, 0x6849, 0xD1DC, 0x6832, 0xD1DD, 0x6833, 0xD1DE, 0x683B, 0xD1DF, 0x684B, + 0xD1E0, 0x684F, 0xD1E1, 0x6816, 0xD1E2, 0x6831, 0xD1E3, 0x681C, 0xD1E4, 0x6835, 0xD1E5, 0x682B, 0xD1E6, 0x682D, 0xD1E7, 0x682F, + 0xD1E8, 0x684E, 0xD1E9, 0x6844, 0xD1EA, 0x6834, 0xD1EB, 0x681D, 0xD1EC, 0x6812, 0xD1ED, 0x6814, 0xD1EE, 0x6826, 0xD1EF, 0x6828, + 0xD1F0, 0x682E, 0xD1F1, 0x684D, 0xD1F2, 0x683A, 0xD1F3, 0x6825, 0xD1F4, 0x6820, 0xD1F5, 0x6B2C, 0xD1F6, 0x6B2F, 0xD1F7, 0x6B2D, + 0xD1F8, 0x6B31, 0xD1F9, 0x6B34, 0xD1FA, 0x6B6D, 0xD1FB, 0x8082, 0xD1FC, 0x6B88, 0xD1FD, 0x6BE6, 0xD1FE, 0x6BE4, 0xD240, 0x6BE8, + 0xD241, 0x6BE3, 0xD242, 0x6BE2, 0xD243, 0x6BE7, 0xD244, 0x6C25, 0xD245, 0x6D7A, 0xD246, 0x6D63, 0xD247, 0x6D64, 0xD248, 0x6D76, + 0xD249, 0x6D0D, 0xD24A, 0x6D61, 0xD24B, 0x6D92, 0xD24C, 0x6D58, 0xD24D, 0x6D62, 0xD24E, 0x6D6D, 0xD24F, 0x6D6F, 0xD250, 0x6D91, + 0xD251, 0x6D8D, 0xD252, 0x6DEF, 0xD253, 0x6D7F, 0xD254, 0x6D86, 0xD255, 0x6D5E, 0xD256, 0x6D67, 0xD257, 0x6D60, 0xD258, 0x6D97, + 0xD259, 0x6D70, 0xD25A, 0x6D7C, 0xD25B, 0x6D5F, 0xD25C, 0x6D82, 0xD25D, 0x6D98, 0xD25E, 0x6D2F, 0xD25F, 0x6D68, 0xD260, 0x6D8B, + 0xD261, 0x6D7E, 0xD262, 0x6D80, 0xD263, 0x6D84, 0xD264, 0x6D16, 0xD265, 0x6D83, 0xD266, 0x6D7B, 0xD267, 0x6D7D, 0xD268, 0x6D75, + 0xD269, 0x6D90, 0xD26A, 0x70DC, 0xD26B, 0x70D3, 0xD26C, 0x70D1, 0xD26D, 0x70DD, 0xD26E, 0x70CB, 0xD26F, 0x7F39, 0xD270, 0x70E2, + 0xD271, 0x70D7, 0xD272, 0x70D2, 0xD273, 0x70DE, 0xD274, 0x70E0, 0xD275, 0x70D4, 0xD276, 0x70CD, 0xD277, 0x70C5, 0xD278, 0x70C6, + 0xD279, 0x70C7, 0xD27A, 0x70DA, 0xD27B, 0x70CE, 0xD27C, 0x70E1, 0xD27D, 0x7242, 0xD27E, 0x7278, 0xD2A1, 0x7277, 0xD2A2, 0x7276, + 0xD2A3, 0x7300, 0xD2A4, 0x72FA, 0xD2A5, 0x72F4, 0xD2A6, 0x72FE, 0xD2A7, 0x72F6, 0xD2A8, 0x72F3, 0xD2A9, 0x72FB, 0xD2AA, 0x7301, + 0xD2AB, 0x73D3, 0xD2AC, 0x73D9, 0xD2AD, 0x73E5, 0xD2AE, 0x73D6, 0xD2AF, 0x73BC, 0xD2B0, 0x73E7, 0xD2B1, 0x73E3, 0xD2B2, 0x73E9, + 0xD2B3, 0x73DC, 0xD2B4, 0x73D2, 0xD2B5, 0x73DB, 0xD2B6, 0x73D4, 0xD2B7, 0x73DD, 0xD2B8, 0x73DA, 0xD2B9, 0x73D7, 0xD2BA, 0x73D8, + 0xD2BB, 0x73E8, 0xD2BC, 0x74DE, 0xD2BD, 0x74DF, 0xD2BE, 0x74F4, 0xD2BF, 0x74F5, 0xD2C0, 0x7521, 0xD2C1, 0x755B, 0xD2C2, 0x755F, + 0xD2C3, 0x75B0, 0xD2C4, 0x75C1, 0xD2C5, 0x75BB, 0xD2C6, 0x75C4, 0xD2C7, 0x75C0, 0xD2C8, 0x75BF, 0xD2C9, 0x75B6, 0xD2CA, 0x75BA, + 0xD2CB, 0x768A, 0xD2CC, 0x76C9, 0xD2CD, 0x771D, 0xD2CE, 0x771B, 0xD2CF, 0x7710, 0xD2D0, 0x7713, 0xD2D1, 0x7712, 0xD2D2, 0x7723, + 0xD2D3, 0x7711, 0xD2D4, 0x7715, 0xD2D5, 0x7719, 0xD2D6, 0x771A, 0xD2D7, 0x7722, 0xD2D8, 0x7727, 0xD2D9, 0x7823, 0xD2DA, 0x782C, + 0xD2DB, 0x7822, 0xD2DC, 0x7835, 0xD2DD, 0x782F, 0xD2DE, 0x7828, 0xD2DF, 0x782E, 0xD2E0, 0x782B, 0xD2E1, 0x7821, 0xD2E2, 0x7829, + 0xD2E3, 0x7833, 0xD2E4, 0x782A, 0xD2E5, 0x7831, 0xD2E6, 0x7954, 0xD2E7, 0x795B, 0xD2E8, 0x794F, 0xD2E9, 0x795C, 0xD2EA, 0x7953, + 0xD2EB, 0x7952, 0xD2EC, 0x7951, 0xD2ED, 0x79EB, 0xD2EE, 0x79EC, 0xD2EF, 0x79E0, 0xD2F0, 0x79EE, 0xD2F1, 0x79ED, 0xD2F2, 0x79EA, + 0xD2F3, 0x79DC, 0xD2F4, 0x79DE, 0xD2F5, 0x79DD, 0xD2F6, 0x7A86, 0xD2F7, 0x7A89, 0xD2F8, 0x7A85, 0xD2F9, 0x7A8B, 0xD2FA, 0x7A8C, + 0xD2FB, 0x7A8A, 0xD2FC, 0x7A87, 0xD2FD, 0x7AD8, 0xD2FE, 0x7B10, 0xD340, 0x7B04, 0xD341, 0x7B13, 0xD342, 0x7B05, 0xD343, 0x7B0F, + 0xD344, 0x7B08, 0xD345, 0x7B0A, 0xD346, 0x7B0E, 0xD347, 0x7B09, 0xD348, 0x7B12, 0xD349, 0x7C84, 0xD34A, 0x7C91, 0xD34B, 0x7C8A, + 0xD34C, 0x7C8C, 0xD34D, 0x7C88, 0xD34E, 0x7C8D, 0xD34F, 0x7C85, 0xD350, 0x7D1E, 0xD351, 0x7D1D, 0xD352, 0x7D11, 0xD353, 0x7D0E, + 0xD354, 0x7D18, 0xD355, 0x7D16, 0xD356, 0x7D13, 0xD357, 0x7D1F, 0xD358, 0x7D12, 0xD359, 0x7D0F, 0xD35A, 0x7D0C, 0xD35B, 0x7F5C, + 0xD35C, 0x7F61, 0xD35D, 0x7F5E, 0xD35E, 0x7F60, 0xD35F, 0x7F5D, 0xD360, 0x7F5B, 0xD361, 0x7F96, 0xD362, 0x7F92, 0xD363, 0x7FC3, + 0xD364, 0x7FC2, 0xD365, 0x7FC0, 0xD366, 0x8016, 0xD367, 0x803E, 0xD368, 0x8039, 0xD369, 0x80FA, 0xD36A, 0x80F2, 0xD36B, 0x80F9, + 0xD36C, 0x80F5, 0xD36D, 0x8101, 0xD36E, 0x80FB, 0xD36F, 0x8100, 0xD370, 0x8201, 0xD371, 0x822F, 0xD372, 0x8225, 0xD373, 0x8333, + 0xD374, 0x832D, 0xD375, 0x8344, 0xD376, 0x8319, 0xD377, 0x8351, 0xD378, 0x8325, 0xD379, 0x8356, 0xD37A, 0x833F, 0xD37B, 0x8341, + 0xD37C, 0x8326, 0xD37D, 0x831C, 0xD37E, 0x8322, 0xD3A1, 0x8342, 0xD3A2, 0x834E, 0xD3A3, 0x831B, 0xD3A4, 0x832A, 0xD3A5, 0x8308, + 0xD3A6, 0x833C, 0xD3A7, 0x834D, 0xD3A8, 0x8316, 0xD3A9, 0x8324, 0xD3AA, 0x8320, 0xD3AB, 0x8337, 0xD3AC, 0x832F, 0xD3AD, 0x8329, + 0xD3AE, 0x8347, 0xD3AF, 0x8345, 0xD3B0, 0x834C, 0xD3B1, 0x8353, 0xD3B2, 0x831E, 0xD3B3, 0x832C, 0xD3B4, 0x834B, 0xD3B5, 0x8327, + 0xD3B6, 0x8348, 0xD3B7, 0x8653, 0xD3B8, 0x8652, 0xD3B9, 0x86A2, 0xD3BA, 0x86A8, 0xD3BB, 0x8696, 0xD3BC, 0x868D, 0xD3BD, 0x8691, + 0xD3BE, 0x869E, 0xD3BF, 0x8687, 0xD3C0, 0x8697, 0xD3C1, 0x8686, 0xD3C2, 0x868B, 0xD3C3, 0x869A, 0xD3C4, 0x8685, 0xD3C5, 0x86A5, + 0xD3C6, 0x8699, 0xD3C7, 0x86A1, 0xD3C8, 0x86A7, 0xD3C9, 0x8695, 0xD3CA, 0x8698, 0xD3CB, 0x868E, 0xD3CC, 0x869D, 0xD3CD, 0x8690, + 0xD3CE, 0x8694, 0xD3CF, 0x8843, 0xD3D0, 0x8844, 0xD3D1, 0x886D, 0xD3D2, 0x8875, 0xD3D3, 0x8876, 0xD3D4, 0x8872, 0xD3D5, 0x8880, + 0xD3D6, 0x8871, 0xD3D7, 0x887F, 0xD3D8, 0x886F, 0xD3D9, 0x8883, 0xD3DA, 0x887E, 0xD3DB, 0x8874, 0xD3DC, 0x887C, 0xD3DD, 0x8A12, + 0xD3DE, 0x8C47, 0xD3DF, 0x8C57, 0xD3E0, 0x8C7B, 0xD3E1, 0x8CA4, 0xD3E2, 0x8CA3, 0xD3E3, 0x8D76, 0xD3E4, 0x8D78, 0xD3E5, 0x8DB5, + 0xD3E6, 0x8DB7, 0xD3E7, 0x8DB6, 0xD3E8, 0x8ED1, 0xD3E9, 0x8ED3, 0xD3EA, 0x8FFE, 0xD3EB, 0x8FF5, 0xD3EC, 0x9002, 0xD3ED, 0x8FFF, + 0xD3EE, 0x8FFB, 0xD3EF, 0x9004, 0xD3F0, 0x8FFC, 0xD3F1, 0x8FF6, 0xD3F2, 0x90D6, 0xD3F3, 0x90E0, 0xD3F4, 0x90D9, 0xD3F5, 0x90DA, + 0xD3F6, 0x90E3, 0xD3F7, 0x90DF, 0xD3F8, 0x90E5, 0xD3F9, 0x90D8, 0xD3FA, 0x90DB, 0xD3FB, 0x90D7, 0xD3FC, 0x90DC, 0xD3FD, 0x90E4, + 0xD3FE, 0x9150, 0xD440, 0x914E, 0xD441, 0x914F, 0xD442, 0x91D5, 0xD443, 0x91E2, 0xD444, 0x91DA, 0xD445, 0x965C, 0xD446, 0x965F, + 0xD447, 0x96BC, 0xD448, 0x98E3, 0xD449, 0x9ADF, 0xD44A, 0x9B2F, 0xD44B, 0x4E7F, 0xD44C, 0x5070, 0xD44D, 0x506A, 0xD44E, 0x5061, + 0xD44F, 0x505E, 0xD450, 0x5060, 0xD451, 0x5053, 0xD452, 0x504B, 0xD453, 0x505D, 0xD454, 0x5072, 0xD455, 0x5048, 0xD456, 0x504D, + 0xD457, 0x5041, 0xD458, 0x505B, 0xD459, 0x504A, 0xD45A, 0x5062, 0xD45B, 0x5015, 0xD45C, 0x5045, 0xD45D, 0x505F, 0xD45E, 0x5069, + 0xD45F, 0x506B, 0xD460, 0x5063, 0xD461, 0x5064, 0xD462, 0x5046, 0xD463, 0x5040, 0xD464, 0x506E, 0xD465, 0x5073, 0xD466, 0x5057, + 0xD467, 0x5051, 0xD468, 0x51D0, 0xD469, 0x526B, 0xD46A, 0x526D, 0xD46B, 0x526C, 0xD46C, 0x526E, 0xD46D, 0x52D6, 0xD46E, 0x52D3, + 0xD46F, 0x532D, 0xD470, 0x539C, 0xD471, 0x5575, 0xD472, 0x5576, 0xD473, 0x553C, 0xD474, 0x554D, 0xD475, 0x5550, 0xD476, 0x5534, + 0xD477, 0x552A, 0xD478, 0x5551, 0xD479, 0x5562, 0xD47A, 0x5536, 0xD47B, 0x5535, 0xD47C, 0x5530, 0xD47D, 0x5552, 0xD47E, 0x5545, + 0xD4A1, 0x550C, 0xD4A2, 0x5532, 0xD4A3, 0x5565, 0xD4A4, 0x554E, 0xD4A5, 0x5539, 0xD4A6, 0x5548, 0xD4A7, 0x552D, 0xD4A8, 0x553B, + 0xD4A9, 0x5540, 0xD4AA, 0x554B, 0xD4AB, 0x570A, 0xD4AC, 0x5707, 0xD4AD, 0x57FB, 0xD4AE, 0x5814, 0xD4AF, 0x57E2, 0xD4B0, 0x57F6, + 0xD4B1, 0x57DC, 0xD4B2, 0x57F4, 0xD4B3, 0x5800, 0xD4B4, 0x57ED, 0xD4B5, 0x57FD, 0xD4B6, 0x5808, 0xD4B7, 0x57F8, 0xD4B8, 0x580B, + 0xD4B9, 0x57F3, 0xD4BA, 0x57CF, 0xD4BB, 0x5807, 0xD4BC, 0x57EE, 0xD4BD, 0x57E3, 0xD4BE, 0x57F2, 0xD4BF, 0x57E5, 0xD4C0, 0x57EC, + 0xD4C1, 0x57E1, 0xD4C2, 0x580E, 0xD4C3, 0x57FC, 0xD4C4, 0x5810, 0xD4C5, 0x57E7, 0xD4C6, 0x5801, 0xD4C7, 0x580C, 0xD4C8, 0x57F1, + 0xD4C9, 0x57E9, 0xD4CA, 0x57F0, 0xD4CB, 0x580D, 0xD4CC, 0x5804, 0xD4CD, 0x595C, 0xD4CE, 0x5A60, 0xD4CF, 0x5A58, 0xD4D0, 0x5A55, + 0xD4D1, 0x5A67, 0xD4D2, 0x5A5E, 0xD4D3, 0x5A38, 0xD4D4, 0x5A35, 0xD4D5, 0x5A6D, 0xD4D6, 0x5A50, 0xD4D7, 0x5A5F, 0xD4D8, 0x5A65, + 0xD4D9, 0x5A6C, 0xD4DA, 0x5A53, 0xD4DB, 0x5A64, 0xD4DC, 0x5A57, 0xD4DD, 0x5A43, 0xD4DE, 0x5A5D, 0xD4DF, 0x5A52, 0xD4E0, 0x5A44, + 0xD4E1, 0x5A5B, 0xD4E2, 0x5A48, 0xD4E3, 0x5A8E, 0xD4E4, 0x5A3E, 0xD4E5, 0x5A4D, 0xD4E6, 0x5A39, 0xD4E7, 0x5A4C, 0xD4E8, 0x5A70, + 0xD4E9, 0x5A69, 0xD4EA, 0x5A47, 0xD4EB, 0x5A51, 0xD4EC, 0x5A56, 0xD4ED, 0x5A42, 0xD4EE, 0x5A5C, 0xD4EF, 0x5B72, 0xD4F0, 0x5B6E, + 0xD4F1, 0x5BC1, 0xD4F2, 0x5BC0, 0xD4F3, 0x5C59, 0xD4F4, 0x5D1E, 0xD4F5, 0x5D0B, 0xD4F6, 0x5D1D, 0xD4F7, 0x5D1A, 0xD4F8, 0x5D20, + 0xD4F9, 0x5D0C, 0xD4FA, 0x5D28, 0xD4FB, 0x5D0D, 0xD4FC, 0x5D26, 0xD4FD, 0x5D25, 0xD4FE, 0x5D0F, 0xD540, 0x5D30, 0xD541, 0x5D12, + 0xD542, 0x5D23, 0xD543, 0x5D1F, 0xD544, 0x5D2E, 0xD545, 0x5E3E, 0xD546, 0x5E34, 0xD547, 0x5EB1, 0xD548, 0x5EB4, 0xD549, 0x5EB9, + 0xD54A, 0x5EB2, 0xD54B, 0x5EB3, 0xD54C, 0x5F36, 0xD54D, 0x5F38, 0xD54E, 0x5F9B, 0xD54F, 0x5F96, 0xD550, 0x5F9F, 0xD551, 0x608A, + 0xD552, 0x6090, 0xD553, 0x6086, 0xD554, 0x60BE, 0xD555, 0x60B0, 0xD556, 0x60BA, 0xD557, 0x60D3, 0xD558, 0x60D4, 0xD559, 0x60CF, + 0xD55A, 0x60E4, 0xD55B, 0x60D9, 0xD55C, 0x60DD, 0xD55D, 0x60C8, 0xD55E, 0x60B1, 0xD55F, 0x60DB, 0xD560, 0x60B7, 0xD561, 0x60CA, + 0xD562, 0x60BF, 0xD563, 0x60C3, 0xD564, 0x60CD, 0xD565, 0x60C0, 0xD566, 0x6332, 0xD567, 0x6365, 0xD568, 0x638A, 0xD569, 0x6382, + 0xD56A, 0x637D, 0xD56B, 0x63BD, 0xD56C, 0x639E, 0xD56D, 0x63AD, 0xD56E, 0x639D, 0xD56F, 0x6397, 0xD570, 0x63AB, 0xD571, 0x638E, + 0xD572, 0x636F, 0xD573, 0x6387, 0xD574, 0x6390, 0xD575, 0x636E, 0xD576, 0x63AF, 0xD577, 0x6375, 0xD578, 0x639C, 0xD579, 0x636D, + 0xD57A, 0x63AE, 0xD57B, 0x637C, 0xD57C, 0x63A4, 0xD57D, 0x633B, 0xD57E, 0x639F, 0xD5A1, 0x6378, 0xD5A2, 0x6385, 0xD5A3, 0x6381, + 0xD5A4, 0x6391, 0xD5A5, 0x638D, 0xD5A6, 0x6370, 0xD5A7, 0x6553, 0xD5A8, 0x65CD, 0xD5A9, 0x6665, 0xD5AA, 0x6661, 0xD5AB, 0x665B, + 0xD5AC, 0x6659, 0xD5AD, 0x665C, 0xD5AE, 0x6662, 0xD5AF, 0x6718, 0xD5B0, 0x6879, 0xD5B1, 0x6887, 0xD5B2, 0x6890, 0xD5B3, 0x689C, + 0xD5B4, 0x686D, 0xD5B5, 0x686E, 0xD5B6, 0x68AE, 0xD5B7, 0x68AB, 0xD5B8, 0x6956, 0xD5B9, 0x686F, 0xD5BA, 0x68A3, 0xD5BB, 0x68AC, + 0xD5BC, 0x68A9, 0xD5BD, 0x6875, 0xD5BE, 0x6874, 0xD5BF, 0x68B2, 0xD5C0, 0x688F, 0xD5C1, 0x6877, 0xD5C2, 0x6892, 0xD5C3, 0x687C, + 0xD5C4, 0x686B, 0xD5C5, 0x6872, 0xD5C6, 0x68AA, 0xD5C7, 0x6880, 0xD5C8, 0x6871, 0xD5C9, 0x687E, 0xD5CA, 0x689B, 0xD5CB, 0x6896, + 0xD5CC, 0x688B, 0xD5CD, 0x68A0, 0xD5CE, 0x6889, 0xD5CF, 0x68A4, 0xD5D0, 0x6878, 0xD5D1, 0x687B, 0xD5D2, 0x6891, 0xD5D3, 0x688C, + 0xD5D4, 0x688A, 0xD5D5, 0x687D, 0xD5D6, 0x6B36, 0xD5D7, 0x6B33, 0xD5D8, 0x6B37, 0xD5D9, 0x6B38, 0xD5DA, 0x6B91, 0xD5DB, 0x6B8F, + 0xD5DC, 0x6B8D, 0xD5DD, 0x6B8E, 0xD5DE, 0x6B8C, 0xD5DF, 0x6C2A, 0xD5E0, 0x6DC0, 0xD5E1, 0x6DAB, 0xD5E2, 0x6DB4, 0xD5E3, 0x6DB3, + 0xD5E4, 0x6E74, 0xD5E5, 0x6DAC, 0xD5E6, 0x6DE9, 0xD5E7, 0x6DE2, 0xD5E8, 0x6DB7, 0xD5E9, 0x6DF6, 0xD5EA, 0x6DD4, 0xD5EB, 0x6E00, + 0xD5EC, 0x6DC8, 0xD5ED, 0x6DE0, 0xD5EE, 0x6DDF, 0xD5EF, 0x6DD6, 0xD5F0, 0x6DBE, 0xD5F1, 0x6DE5, 0xD5F2, 0x6DDC, 0xD5F3, 0x6DDD, + 0xD5F4, 0x6DDB, 0xD5F5, 0x6DF4, 0xD5F6, 0x6DCA, 0xD5F7, 0x6DBD, 0xD5F8, 0x6DED, 0xD5F9, 0x6DF0, 0xD5FA, 0x6DBA, 0xD5FB, 0x6DD5, + 0xD5FC, 0x6DC2, 0xD5FD, 0x6DCF, 0xD5FE, 0x6DC9, 0xD640, 0x6DD0, 0xD641, 0x6DF2, 0xD642, 0x6DD3, 0xD643, 0x6DFD, 0xD644, 0x6DD7, + 0xD645, 0x6DCD, 0xD646, 0x6DE3, 0xD647, 0x6DBB, 0xD648, 0x70FA, 0xD649, 0x710D, 0xD64A, 0x70F7, 0xD64B, 0x7117, 0xD64C, 0x70F4, + 0xD64D, 0x710C, 0xD64E, 0x70F0, 0xD64F, 0x7104, 0xD650, 0x70F3, 0xD651, 0x7110, 0xD652, 0x70FC, 0xD653, 0x70FF, 0xD654, 0x7106, + 0xD655, 0x7113, 0xD656, 0x7100, 0xD657, 0x70F8, 0xD658, 0x70F6, 0xD659, 0x710B, 0xD65A, 0x7102, 0xD65B, 0x710E, 0xD65C, 0x727E, + 0xD65D, 0x727B, 0xD65E, 0x727C, 0xD65F, 0x727F, 0xD660, 0x731D, 0xD661, 0x7317, 0xD662, 0x7307, 0xD663, 0x7311, 0xD664, 0x7318, + 0xD665, 0x730A, 0xD666, 0x7308, 0xD667, 0x72FF, 0xD668, 0x730F, 0xD669, 0x731E, 0xD66A, 0x7388, 0xD66B, 0x73F6, 0xD66C, 0x73F8, + 0xD66D, 0x73F5, 0xD66E, 0x7404, 0xD66F, 0x7401, 0xD670, 0x73FD, 0xD671, 0x7407, 0xD672, 0x7400, 0xD673, 0x73FA, 0xD674, 0x73FC, + 0xD675, 0x73FF, 0xD676, 0x740C, 0xD677, 0x740B, 0xD678, 0x73F4, 0xD679, 0x7408, 0xD67A, 0x7564, 0xD67B, 0x7563, 0xD67C, 0x75CE, + 0xD67D, 0x75D2, 0xD67E, 0x75CF, 0xD6A1, 0x75CB, 0xD6A2, 0x75CC, 0xD6A3, 0x75D1, 0xD6A4, 0x75D0, 0xD6A5, 0x768F, 0xD6A6, 0x7689, + 0xD6A7, 0x76D3, 0xD6A8, 0x7739, 0xD6A9, 0x772F, 0xD6AA, 0x772D, 0xD6AB, 0x7731, 0xD6AC, 0x7732, 0xD6AD, 0x7734, 0xD6AE, 0x7733, + 0xD6AF, 0x773D, 0xD6B0, 0x7725, 0xD6B1, 0x773B, 0xD6B2, 0x7735, 0xD6B3, 0x7848, 0xD6B4, 0x7852, 0xD6B5, 0x7849, 0xD6B6, 0x784D, + 0xD6B7, 0x784A, 0xD6B8, 0x784C, 0xD6B9, 0x7826, 0xD6BA, 0x7845, 0xD6BB, 0x7850, 0xD6BC, 0x7964, 0xD6BD, 0x7967, 0xD6BE, 0x7969, + 0xD6BF, 0x796A, 0xD6C0, 0x7963, 0xD6C1, 0x796B, 0xD6C2, 0x7961, 0xD6C3, 0x79BB, 0xD6C4, 0x79FA, 0xD6C5, 0x79F8, 0xD6C6, 0x79F6, + 0xD6C7, 0x79F7, 0xD6C8, 0x7A8F, 0xD6C9, 0x7A94, 0xD6CA, 0x7A90, 0xD6CB, 0x7B35, 0xD6CC, 0x7B47, 0xD6CD, 0x7B34, 0xD6CE, 0x7B25, + 0xD6CF, 0x7B30, 0xD6D0, 0x7B22, 0xD6D1, 0x7B24, 0xD6D2, 0x7B33, 0xD6D3, 0x7B18, 0xD6D4, 0x7B2A, 0xD6D5, 0x7B1D, 0xD6D6, 0x7B31, + 0xD6D7, 0x7B2B, 0xD6D8, 0x7B2D, 0xD6D9, 0x7B2F, 0xD6DA, 0x7B32, 0xD6DB, 0x7B38, 0xD6DC, 0x7B1A, 0xD6DD, 0x7B23, 0xD6DE, 0x7C94, + 0xD6DF, 0x7C98, 0xD6E0, 0x7C96, 0xD6E1, 0x7CA3, 0xD6E2, 0x7D35, 0xD6E3, 0x7D3D, 0xD6E4, 0x7D38, 0xD6E5, 0x7D36, 0xD6E6, 0x7D3A, + 0xD6E7, 0x7D45, 0xD6E8, 0x7D2C, 0xD6E9, 0x7D29, 0xD6EA, 0x7D41, 0xD6EB, 0x7D47, 0xD6EC, 0x7D3E, 0xD6ED, 0x7D3F, 0xD6EE, 0x7D4A, + 0xD6EF, 0x7D3B, 0xD6F0, 0x7D28, 0xD6F1, 0x7F63, 0xD6F2, 0x7F95, 0xD6F3, 0x7F9C, 0xD6F4, 0x7F9D, 0xD6F5, 0x7F9B, 0xD6F6, 0x7FCA, + 0xD6F7, 0x7FCB, 0xD6F8, 0x7FCD, 0xD6F9, 0x7FD0, 0xD6FA, 0x7FD1, 0xD6FB, 0x7FC7, 0xD6FC, 0x7FCF, 0xD6FD, 0x7FC9, 0xD6FE, 0x801F, + 0xD740, 0x801E, 0xD741, 0x801B, 0xD742, 0x8047, 0xD743, 0x8043, 0xD744, 0x8048, 0xD745, 0x8118, 0xD746, 0x8125, 0xD747, 0x8119, + 0xD748, 0x811B, 0xD749, 0x812D, 0xD74A, 0x811F, 0xD74B, 0x812C, 0xD74C, 0x811E, 0xD74D, 0x8121, 0xD74E, 0x8115, 0xD74F, 0x8127, + 0xD750, 0x811D, 0xD751, 0x8122, 0xD752, 0x8211, 0xD753, 0x8238, 0xD754, 0x8233, 0xD755, 0x823A, 0xD756, 0x8234, 0xD757, 0x8232, + 0xD758, 0x8274, 0xD759, 0x8390, 0xD75A, 0x83A3, 0xD75B, 0x83A8, 0xD75C, 0x838D, 0xD75D, 0x837A, 0xD75E, 0x8373, 0xD75F, 0x83A4, + 0xD760, 0x8374, 0xD761, 0x838F, 0xD762, 0x8381, 0xD763, 0x8395, 0xD764, 0x8399, 0xD765, 0x8375, 0xD766, 0x8394, 0xD767, 0x83A9, + 0xD768, 0x837D, 0xD769, 0x8383, 0xD76A, 0x838C, 0xD76B, 0x839D, 0xD76C, 0x839B, 0xD76D, 0x83AA, 0xD76E, 0x838B, 0xD76F, 0x837E, + 0xD770, 0x83A5, 0xD771, 0x83AF, 0xD772, 0x8388, 0xD773, 0x8397, 0xD774, 0x83B0, 0xD775, 0x837F, 0xD776, 0x83A6, 0xD777, 0x8387, + 0xD778, 0x83AE, 0xD779, 0x8376, 0xD77A, 0x839A, 0xD77B, 0x8659, 0xD77C, 0x8656, 0xD77D, 0x86BF, 0xD77E, 0x86B7, 0xD7A1, 0x86C2, + 0xD7A2, 0x86C1, 0xD7A3, 0x86C5, 0xD7A4, 0x86BA, 0xD7A5, 0x86B0, 0xD7A6, 0x86C8, 0xD7A7, 0x86B9, 0xD7A8, 0x86B3, 0xD7A9, 0x86B8, + 0xD7AA, 0x86CC, 0xD7AB, 0x86B4, 0xD7AC, 0x86BB, 0xD7AD, 0x86BC, 0xD7AE, 0x86C3, 0xD7AF, 0x86BD, 0xD7B0, 0x86BE, 0xD7B1, 0x8852, + 0xD7B2, 0x8889, 0xD7B3, 0x8895, 0xD7B4, 0x88A8, 0xD7B5, 0x88A2, 0xD7B6, 0x88AA, 0xD7B7, 0x889A, 0xD7B8, 0x8891, 0xD7B9, 0x88A1, + 0xD7BA, 0x889F, 0xD7BB, 0x8898, 0xD7BC, 0x88A7, 0xD7BD, 0x8899, 0xD7BE, 0x889B, 0xD7BF, 0x8897, 0xD7C0, 0x88A4, 0xD7C1, 0x88AC, + 0xD7C2, 0x888C, 0xD7C3, 0x8893, 0xD7C4, 0x888E, 0xD7C5, 0x8982, 0xD7C6, 0x89D6, 0xD7C7, 0x89D9, 0xD7C8, 0x89D5, 0xD7C9, 0x8A30, + 0xD7CA, 0x8A27, 0xD7CB, 0x8A2C, 0xD7CC, 0x8A1E, 0xD7CD, 0x8C39, 0xD7CE, 0x8C3B, 0xD7CF, 0x8C5C, 0xD7D0, 0x8C5D, 0xD7D1, 0x8C7D, + 0xD7D2, 0x8CA5, 0xD7D3, 0x8D7D, 0xD7D4, 0x8D7B, 0xD7D5, 0x8D79, 0xD7D6, 0x8DBC, 0xD7D7, 0x8DC2, 0xD7D8, 0x8DB9, 0xD7D9, 0x8DBF, + 0xD7DA, 0x8DC1, 0xD7DB, 0x8ED8, 0xD7DC, 0x8EDE, 0xD7DD, 0x8EDD, 0xD7DE, 0x8EDC, 0xD7DF, 0x8ED7, 0xD7E0, 0x8EE0, 0xD7E1, 0x8EE1, + 0xD7E2, 0x9024, 0xD7E3, 0x900B, 0xD7E4, 0x9011, 0xD7E5, 0x901C, 0xD7E6, 0x900C, 0xD7E7, 0x9021, 0xD7E8, 0x90EF, 0xD7E9, 0x90EA, + 0xD7EA, 0x90F0, 0xD7EB, 0x90F4, 0xD7EC, 0x90F2, 0xD7ED, 0x90F3, 0xD7EE, 0x90D4, 0xD7EF, 0x90EB, 0xD7F0, 0x90EC, 0xD7F1, 0x90E9, + 0xD7F2, 0x9156, 0xD7F3, 0x9158, 0xD7F4, 0x915A, 0xD7F5, 0x9153, 0xD7F6, 0x9155, 0xD7F7, 0x91EC, 0xD7F8, 0x91F4, 0xD7F9, 0x91F1, + 0xD7FA, 0x91F3, 0xD7FB, 0x91F8, 0xD7FC, 0x91E4, 0xD7FD, 0x91F9, 0xD7FE, 0x91EA, 0xD840, 0x91EB, 0xD841, 0x91F7, 0xD842, 0x91E8, + 0xD843, 0x91EE, 0xD844, 0x957A, 0xD845, 0x9586, 0xD846, 0x9588, 0xD847, 0x967C, 0xD848, 0x966D, 0xD849, 0x966B, 0xD84A, 0x9671, + 0xD84B, 0x966F, 0xD84C, 0x96BF, 0xD84D, 0x976A, 0xD84E, 0x9804, 0xD84F, 0x98E5, 0xD850, 0x9997, 0xD851, 0x509B, 0xD852, 0x5095, + 0xD853, 0x5094, 0xD854, 0x509E, 0xD855, 0x508B, 0xD856, 0x50A3, 0xD857, 0x5083, 0xD858, 0x508C, 0xD859, 0x508E, 0xD85A, 0x509D, + 0xD85B, 0x5068, 0xD85C, 0x509C, 0xD85D, 0x5092, 0xD85E, 0x5082, 0xD85F, 0x5087, 0xD860, 0x515F, 0xD861, 0x51D4, 0xD862, 0x5312, + 0xD863, 0x5311, 0xD864, 0x53A4, 0xD865, 0x53A7, 0xD866, 0x5591, 0xD867, 0x55A8, 0xD868, 0x55A5, 0xD869, 0x55AD, 0xD86A, 0x5577, + 0xD86B, 0x5645, 0xD86C, 0x55A2, 0xD86D, 0x5593, 0xD86E, 0x5588, 0xD86F, 0x558F, 0xD870, 0x55B5, 0xD871, 0x5581, 0xD872, 0x55A3, + 0xD873, 0x5592, 0xD874, 0x55A4, 0xD875, 0x557D, 0xD876, 0x558C, 0xD877, 0x55A6, 0xD878, 0x557F, 0xD879, 0x5595, 0xD87A, 0x55A1, + 0xD87B, 0x558E, 0xD87C, 0x570C, 0xD87D, 0x5829, 0xD87E, 0x5837, 0xD8A1, 0x5819, 0xD8A2, 0x581E, 0xD8A3, 0x5827, 0xD8A4, 0x5823, + 0xD8A5, 0x5828, 0xD8A6, 0x57F5, 0xD8A7, 0x5848, 0xD8A8, 0x5825, 0xD8A9, 0x581C, 0xD8AA, 0x581B, 0xD8AB, 0x5833, 0xD8AC, 0x583F, + 0xD8AD, 0x5836, 0xD8AE, 0x582E, 0xD8AF, 0x5839, 0xD8B0, 0x5838, 0xD8B1, 0x582D, 0xD8B2, 0x582C, 0xD8B3, 0x583B, 0xD8B4, 0x5961, + 0xD8B5, 0x5AAF, 0xD8B6, 0x5A94, 0xD8B7, 0x5A9F, 0xD8B8, 0x5A7A, 0xD8B9, 0x5AA2, 0xD8BA, 0x5A9E, 0xD8BB, 0x5A78, 0xD8BC, 0x5AA6, + 0xD8BD, 0x5A7C, 0xD8BE, 0x5AA5, 0xD8BF, 0x5AAC, 0xD8C0, 0x5A95, 0xD8C1, 0x5AAE, 0xD8C2, 0x5A37, 0xD8C3, 0x5A84, 0xD8C4, 0x5A8A, + 0xD8C5, 0x5A97, 0xD8C6, 0x5A83, 0xD8C7, 0x5A8B, 0xD8C8, 0x5AA9, 0xD8C9, 0x5A7B, 0xD8CA, 0x5A7D, 0xD8CB, 0x5A8C, 0xD8CC, 0x5A9C, + 0xD8CD, 0x5A8F, 0xD8CE, 0x5A93, 0xD8CF, 0x5A9D, 0xD8D0, 0x5BEA, 0xD8D1, 0x5BCD, 0xD8D2, 0x5BCB, 0xD8D3, 0x5BD4, 0xD8D4, 0x5BD1, + 0xD8D5, 0x5BCA, 0xD8D6, 0x5BCE, 0xD8D7, 0x5C0C, 0xD8D8, 0x5C30, 0xD8D9, 0x5D37, 0xD8DA, 0x5D43, 0xD8DB, 0x5D6B, 0xD8DC, 0x5D41, + 0xD8DD, 0x5D4B, 0xD8DE, 0x5D3F, 0xD8DF, 0x5D35, 0xD8E0, 0x5D51, 0xD8E1, 0x5D4E, 0xD8E2, 0x5D55, 0xD8E3, 0x5D33, 0xD8E4, 0x5D3A, + 0xD8E5, 0x5D52, 0xD8E6, 0x5D3D, 0xD8E7, 0x5D31, 0xD8E8, 0x5D59, 0xD8E9, 0x5D42, 0xD8EA, 0x5D39, 0xD8EB, 0x5D49, 0xD8EC, 0x5D38, + 0xD8ED, 0x5D3C, 0xD8EE, 0x5D32, 0xD8EF, 0x5D36, 0xD8F0, 0x5D40, 0xD8F1, 0x5D45, 0xD8F2, 0x5E44, 0xD8F3, 0x5E41, 0xD8F4, 0x5F58, + 0xD8F5, 0x5FA6, 0xD8F6, 0x5FA5, 0xD8F7, 0x5FAB, 0xD8F8, 0x60C9, 0xD8F9, 0x60B9, 0xD8FA, 0x60CC, 0xD8FB, 0x60E2, 0xD8FC, 0x60CE, + 0xD8FD, 0x60C4, 0xD8FE, 0x6114, 0xD940, 0x60F2, 0xD941, 0x610A, 0xD942, 0x6116, 0xD943, 0x6105, 0xD944, 0x60F5, 0xD945, 0x6113, + 0xD946, 0x60F8, 0xD947, 0x60FC, 0xD948, 0x60FE, 0xD949, 0x60C1, 0xD94A, 0x6103, 0xD94B, 0x6118, 0xD94C, 0x611D, 0xD94D, 0x6110, + 0xD94E, 0x60FF, 0xD94F, 0x6104, 0xD950, 0x610B, 0xD951, 0x624A, 0xD952, 0x6394, 0xD953, 0x63B1, 0xD954, 0x63B0, 0xD955, 0x63CE, + 0xD956, 0x63E5, 0xD957, 0x63E8, 0xD958, 0x63EF, 0xD959, 0x63C3, 0xD95A, 0x649D, 0xD95B, 0x63F3, 0xD95C, 0x63CA, 0xD95D, 0x63E0, + 0xD95E, 0x63F6, 0xD95F, 0x63D5, 0xD960, 0x63F2, 0xD961, 0x63F5, 0xD962, 0x6461, 0xD963, 0x63DF, 0xD964, 0x63BE, 0xD965, 0x63DD, + 0xD966, 0x63DC, 0xD967, 0x63C4, 0xD968, 0x63D8, 0xD969, 0x63D3, 0xD96A, 0x63C2, 0xD96B, 0x63C7, 0xD96C, 0x63CC, 0xD96D, 0x63CB, + 0xD96E, 0x63C8, 0xD96F, 0x63F0, 0xD970, 0x63D7, 0xD971, 0x63D9, 0xD972, 0x6532, 0xD973, 0x6567, 0xD974, 0x656A, 0xD975, 0x6564, + 0xD976, 0x655C, 0xD977, 0x6568, 0xD978, 0x6565, 0xD979, 0x658C, 0xD97A, 0x659D, 0xD97B, 0x659E, 0xD97C, 0x65AE, 0xD97D, 0x65D0, + 0xD97E, 0x65D2, 0xD9A1, 0x667C, 0xD9A2, 0x666C, 0xD9A3, 0x667B, 0xD9A4, 0x6680, 0xD9A5, 0x6671, 0xD9A6, 0x6679, 0xD9A7, 0x666A, + 0xD9A8, 0x6672, 0xD9A9, 0x6701, 0xD9AA, 0x690C, 0xD9AB, 0x68D3, 0xD9AC, 0x6904, 0xD9AD, 0x68DC, 0xD9AE, 0x692A, 0xD9AF, 0x68EC, + 0xD9B0, 0x68EA, 0xD9B1, 0x68F1, 0xD9B2, 0x690F, 0xD9B3, 0x68D6, 0xD9B4, 0x68F7, 0xD9B5, 0x68EB, 0xD9B6, 0x68E4, 0xD9B7, 0x68F6, + 0xD9B8, 0x6913, 0xD9B9, 0x6910, 0xD9BA, 0x68F3, 0xD9BB, 0x68E1, 0xD9BC, 0x6907, 0xD9BD, 0x68CC, 0xD9BE, 0x6908, 0xD9BF, 0x6970, + 0xD9C0, 0x68B4, 0xD9C1, 0x6911, 0xD9C2, 0x68EF, 0xD9C3, 0x68C6, 0xD9C4, 0x6914, 0xD9C5, 0x68F8, 0xD9C6, 0x68D0, 0xD9C7, 0x68FD, + 0xD9C8, 0x68FC, 0xD9C9, 0x68E8, 0xD9CA, 0x690B, 0xD9CB, 0x690A, 0xD9CC, 0x6917, 0xD9CD, 0x68CE, 0xD9CE, 0x68C8, 0xD9CF, 0x68DD, + 0xD9D0, 0x68DE, 0xD9D1, 0x68E6, 0xD9D2, 0x68F4, 0xD9D3, 0x68D1, 0xD9D4, 0x6906, 0xD9D5, 0x68D4, 0xD9D6, 0x68E9, 0xD9D7, 0x6915, + 0xD9D8, 0x6925, 0xD9D9, 0x68C7, 0xD9DA, 0x6B39, 0xD9DB, 0x6B3B, 0xD9DC, 0x6B3F, 0xD9DD, 0x6B3C, 0xD9DE, 0x6B94, 0xD9DF, 0x6B97, + 0xD9E0, 0x6B99, 0xD9E1, 0x6B95, 0xD9E2, 0x6BBD, 0xD9E3, 0x6BF0, 0xD9E4, 0x6BF2, 0xD9E5, 0x6BF3, 0xD9E6, 0x6C30, 0xD9E7, 0x6DFC, + 0xD9E8, 0x6E46, 0xD9E9, 0x6E47, 0xD9EA, 0x6E1F, 0xD9EB, 0x6E49, 0xD9EC, 0x6E88, 0xD9ED, 0x6E3C, 0xD9EE, 0x6E3D, 0xD9EF, 0x6E45, + 0xD9F0, 0x6E62, 0xD9F1, 0x6E2B, 0xD9F2, 0x6E3F, 0xD9F3, 0x6E41, 0xD9F4, 0x6E5D, 0xD9F5, 0x6E73, 0xD9F6, 0x6E1C, 0xD9F7, 0x6E33, + 0xD9F8, 0x6E4B, 0xD9F9, 0x6E40, 0xD9FA, 0x6E51, 0xD9FB, 0x6E3B, 0xD9FC, 0x6E03, 0xD9FD, 0x6E2E, 0xD9FE, 0x6E5E, 0xDA40, 0x6E68, + 0xDA41, 0x6E5C, 0xDA42, 0x6E61, 0xDA43, 0x6E31, 0xDA44, 0x6E28, 0xDA45, 0x6E60, 0xDA46, 0x6E71, 0xDA47, 0x6E6B, 0xDA48, 0x6E39, + 0xDA49, 0x6E22, 0xDA4A, 0x6E30, 0xDA4B, 0x6E53, 0xDA4C, 0x6E65, 0xDA4D, 0x6E27, 0xDA4E, 0x6E78, 0xDA4F, 0x6E64, 0xDA50, 0x6E77, + 0xDA51, 0x6E55, 0xDA52, 0x6E79, 0xDA53, 0x6E52, 0xDA54, 0x6E66, 0xDA55, 0x6E35, 0xDA56, 0x6E36, 0xDA57, 0x6E5A, 0xDA58, 0x7120, + 0xDA59, 0x711E, 0xDA5A, 0x712F, 0xDA5B, 0x70FB, 0xDA5C, 0x712E, 0xDA5D, 0x7131, 0xDA5E, 0x7123, 0xDA5F, 0x7125, 0xDA60, 0x7122, + 0xDA61, 0x7132, 0xDA62, 0x711F, 0xDA63, 0x7128, 0xDA64, 0x713A, 0xDA65, 0x711B, 0xDA66, 0x724B, 0xDA67, 0x725A, 0xDA68, 0x7288, + 0xDA69, 0x7289, 0xDA6A, 0x7286, 0xDA6B, 0x7285, 0xDA6C, 0x728B, 0xDA6D, 0x7312, 0xDA6E, 0x730B, 0xDA6F, 0x7330, 0xDA70, 0x7322, + 0xDA71, 0x7331, 0xDA72, 0x7333, 0xDA73, 0x7327, 0xDA74, 0x7332, 0xDA75, 0x732D, 0xDA76, 0x7326, 0xDA77, 0x7323, 0xDA78, 0x7335, + 0xDA79, 0x730C, 0xDA7A, 0x742E, 0xDA7B, 0x742C, 0xDA7C, 0x7430, 0xDA7D, 0x742B, 0xDA7E, 0x7416, 0xDAA1, 0x741A, 0xDAA2, 0x7421, + 0xDAA3, 0x742D, 0xDAA4, 0x7431, 0xDAA5, 0x7424, 0xDAA6, 0x7423, 0xDAA7, 0x741D, 0xDAA8, 0x7429, 0xDAA9, 0x7420, 0xDAAA, 0x7432, + 0xDAAB, 0x74FB, 0xDAAC, 0x752F, 0xDAAD, 0x756F, 0xDAAE, 0x756C, 0xDAAF, 0x75E7, 0xDAB0, 0x75DA, 0xDAB1, 0x75E1, 0xDAB2, 0x75E6, + 0xDAB3, 0x75DD, 0xDAB4, 0x75DF, 0xDAB5, 0x75E4, 0xDAB6, 0x75D7, 0xDAB7, 0x7695, 0xDAB8, 0x7692, 0xDAB9, 0x76DA, 0xDABA, 0x7746, + 0xDABB, 0x7747, 0xDABC, 0x7744, 0xDABD, 0x774D, 0xDABE, 0x7745, 0xDABF, 0x774A, 0xDAC0, 0x774E, 0xDAC1, 0x774B, 0xDAC2, 0x774C, + 0xDAC3, 0x77DE, 0xDAC4, 0x77EC, 0xDAC5, 0x7860, 0xDAC6, 0x7864, 0xDAC7, 0x7865, 0xDAC8, 0x785C, 0xDAC9, 0x786D, 0xDACA, 0x7871, + 0xDACB, 0x786A, 0xDACC, 0x786E, 0xDACD, 0x7870, 0xDACE, 0x7869, 0xDACF, 0x7868, 0xDAD0, 0x785E, 0xDAD1, 0x7862, 0xDAD2, 0x7974, + 0xDAD3, 0x7973, 0xDAD4, 0x7972, 0xDAD5, 0x7970, 0xDAD6, 0x7A02, 0xDAD7, 0x7A0A, 0xDAD8, 0x7A03, 0xDAD9, 0x7A0C, 0xDADA, 0x7A04, + 0xDADB, 0x7A99, 0xDADC, 0x7AE6, 0xDADD, 0x7AE4, 0xDADE, 0x7B4A, 0xDADF, 0x7B3B, 0xDAE0, 0x7B44, 0xDAE1, 0x7B48, 0xDAE2, 0x7B4C, + 0xDAE3, 0x7B4E, 0xDAE4, 0x7B40, 0xDAE5, 0x7B58, 0xDAE6, 0x7B45, 0xDAE7, 0x7CA2, 0xDAE8, 0x7C9E, 0xDAE9, 0x7CA8, 0xDAEA, 0x7CA1, + 0xDAEB, 0x7D58, 0xDAEC, 0x7D6F, 0xDAED, 0x7D63, 0xDAEE, 0x7D53, 0xDAEF, 0x7D56, 0xDAF0, 0x7D67, 0xDAF1, 0x7D6A, 0xDAF2, 0x7D4F, + 0xDAF3, 0x7D6D, 0xDAF4, 0x7D5C, 0xDAF5, 0x7D6B, 0xDAF6, 0x7D52, 0xDAF7, 0x7D54, 0xDAF8, 0x7D69, 0xDAF9, 0x7D51, 0xDAFA, 0x7D5F, + 0xDAFB, 0x7D4E, 0xDAFC, 0x7F3E, 0xDAFD, 0x7F3F, 0xDAFE, 0x7F65, 0xDB40, 0x7F66, 0xDB41, 0x7FA2, 0xDB42, 0x7FA0, 0xDB43, 0x7FA1, + 0xDB44, 0x7FD7, 0xDB45, 0x8051, 0xDB46, 0x804F, 0xDB47, 0x8050, 0xDB48, 0x80FE, 0xDB49, 0x80D4, 0xDB4A, 0x8143, 0xDB4B, 0x814A, + 0xDB4C, 0x8152, 0xDB4D, 0x814F, 0xDB4E, 0x8147, 0xDB4F, 0x813D, 0xDB50, 0x814D, 0xDB51, 0x813A, 0xDB52, 0x81E6, 0xDB53, 0x81EE, + 0xDB54, 0x81F7, 0xDB55, 0x81F8, 0xDB56, 0x81F9, 0xDB57, 0x8204, 0xDB58, 0x823C, 0xDB59, 0x823D, 0xDB5A, 0x823F, 0xDB5B, 0x8275, + 0xDB5C, 0x833B, 0xDB5D, 0x83CF, 0xDB5E, 0x83F9, 0xDB5F, 0x8423, 0xDB60, 0x83C0, 0xDB61, 0x83E8, 0xDB62, 0x8412, 0xDB63, 0x83E7, + 0xDB64, 0x83E4, 0xDB65, 0x83FC, 0xDB66, 0x83F6, 0xDB67, 0x8410, 0xDB68, 0x83C6, 0xDB69, 0x83C8, 0xDB6A, 0x83EB, 0xDB6B, 0x83E3, + 0xDB6C, 0x83BF, 0xDB6D, 0x8401, 0xDB6E, 0x83DD, 0xDB6F, 0x83E5, 0xDB70, 0x83D8, 0xDB71, 0x83FF, 0xDB72, 0x83E1, 0xDB73, 0x83CB, + 0xDB74, 0x83CE, 0xDB75, 0x83D6, 0xDB76, 0x83F5, 0xDB77, 0x83C9, 0xDB78, 0x8409, 0xDB79, 0x840F, 0xDB7A, 0x83DE, 0xDB7B, 0x8411, + 0xDB7C, 0x8406, 0xDB7D, 0x83C2, 0xDB7E, 0x83F3, 0xDBA1, 0x83D5, 0xDBA2, 0x83FA, 0xDBA3, 0x83C7, 0xDBA4, 0x83D1, 0xDBA5, 0x83EA, + 0xDBA6, 0x8413, 0xDBA7, 0x83C3, 0xDBA8, 0x83EC, 0xDBA9, 0x83EE, 0xDBAA, 0x83C4, 0xDBAB, 0x83FB, 0xDBAC, 0x83D7, 0xDBAD, 0x83E2, + 0xDBAE, 0x841B, 0xDBAF, 0x83DB, 0xDBB0, 0x83FE, 0xDBB1, 0x86D8, 0xDBB2, 0x86E2, 0xDBB3, 0x86E6, 0xDBB4, 0x86D3, 0xDBB5, 0x86E3, + 0xDBB6, 0x86DA, 0xDBB7, 0x86EA, 0xDBB8, 0x86DD, 0xDBB9, 0x86EB, 0xDBBA, 0x86DC, 0xDBBB, 0x86EC, 0xDBBC, 0x86E9, 0xDBBD, 0x86D7, + 0xDBBE, 0x86E8, 0xDBBF, 0x86D1, 0xDBC0, 0x8848, 0xDBC1, 0x8856, 0xDBC2, 0x8855, 0xDBC3, 0x88BA, 0xDBC4, 0x88D7, 0xDBC5, 0x88B9, + 0xDBC6, 0x88B8, 0xDBC7, 0x88C0, 0xDBC8, 0x88BE, 0xDBC9, 0x88B6, 0xDBCA, 0x88BC, 0xDBCB, 0x88B7, 0xDBCC, 0x88BD, 0xDBCD, 0x88B2, + 0xDBCE, 0x8901, 0xDBCF, 0x88C9, 0xDBD0, 0x8995, 0xDBD1, 0x8998, 0xDBD2, 0x8997, 0xDBD3, 0x89DD, 0xDBD4, 0x89DA, 0xDBD5, 0x89DB, + 0xDBD6, 0x8A4E, 0xDBD7, 0x8A4D, 0xDBD8, 0x8A39, 0xDBD9, 0x8A59, 0xDBDA, 0x8A40, 0xDBDB, 0x8A57, 0xDBDC, 0x8A58, 0xDBDD, 0x8A44, + 0xDBDE, 0x8A45, 0xDBDF, 0x8A52, 0xDBE0, 0x8A48, 0xDBE1, 0x8A51, 0xDBE2, 0x8A4A, 0xDBE3, 0x8A4C, 0xDBE4, 0x8A4F, 0xDBE5, 0x8C5F, + 0xDBE6, 0x8C81, 0xDBE7, 0x8C80, 0xDBE8, 0x8CBA, 0xDBE9, 0x8CBE, 0xDBEA, 0x8CB0, 0xDBEB, 0x8CB9, 0xDBEC, 0x8CB5, 0xDBED, 0x8D84, + 0xDBEE, 0x8D80, 0xDBEF, 0x8D89, 0xDBF0, 0x8DD8, 0xDBF1, 0x8DD3, 0xDBF2, 0x8DCD, 0xDBF3, 0x8DC7, 0xDBF4, 0x8DD6, 0xDBF5, 0x8DDC, + 0xDBF6, 0x8DCF, 0xDBF7, 0x8DD5, 0xDBF8, 0x8DD9, 0xDBF9, 0x8DC8, 0xDBFA, 0x8DD7, 0xDBFB, 0x8DC5, 0xDBFC, 0x8EEF, 0xDBFD, 0x8EF7, + 0xDBFE, 0x8EFA, 0xDC40, 0x8EF9, 0xDC41, 0x8EE6, 0xDC42, 0x8EEE, 0xDC43, 0x8EE5, 0xDC44, 0x8EF5, 0xDC45, 0x8EE7, 0xDC46, 0x8EE8, + 0xDC47, 0x8EF6, 0xDC48, 0x8EEB, 0xDC49, 0x8EF1, 0xDC4A, 0x8EEC, 0xDC4B, 0x8EF4, 0xDC4C, 0x8EE9, 0xDC4D, 0x902D, 0xDC4E, 0x9034, + 0xDC4F, 0x902F, 0xDC50, 0x9106, 0xDC51, 0x912C, 0xDC52, 0x9104, 0xDC53, 0x90FF, 0xDC54, 0x90FC, 0xDC55, 0x9108, 0xDC56, 0x90F9, + 0xDC57, 0x90FB, 0xDC58, 0x9101, 0xDC59, 0x9100, 0xDC5A, 0x9107, 0xDC5B, 0x9105, 0xDC5C, 0x9103, 0xDC5D, 0x9161, 0xDC5E, 0x9164, + 0xDC5F, 0x915F, 0xDC60, 0x9162, 0xDC61, 0x9160, 0xDC62, 0x9201, 0xDC63, 0x920A, 0xDC64, 0x9225, 0xDC65, 0x9203, 0xDC66, 0x921A, + 0xDC67, 0x9226, 0xDC68, 0x920F, 0xDC69, 0x920C, 0xDC6A, 0x9200, 0xDC6B, 0x9212, 0xDC6C, 0x91FF, 0xDC6D, 0x91FD, 0xDC6E, 0x9206, + 0xDC6F, 0x9204, 0xDC70, 0x9227, 0xDC71, 0x9202, 0xDC72, 0x921C, 0xDC73, 0x9224, 0xDC74, 0x9219, 0xDC75, 0x9217, 0xDC76, 0x9205, + 0xDC77, 0x9216, 0xDC78, 0x957B, 0xDC79, 0x958D, 0xDC7A, 0x958C, 0xDC7B, 0x9590, 0xDC7C, 0x9687, 0xDC7D, 0x967E, 0xDC7E, 0x9688, + 0xDCA1, 0x9689, 0xDCA2, 0x9683, 0xDCA3, 0x9680, 0xDCA4, 0x96C2, 0xDCA5, 0x96C8, 0xDCA6, 0x96C3, 0xDCA7, 0x96F1, 0xDCA8, 0x96F0, + 0xDCA9, 0x976C, 0xDCAA, 0x9770, 0xDCAB, 0x976E, 0xDCAC, 0x9807, 0xDCAD, 0x98A9, 0xDCAE, 0x98EB, 0xDCAF, 0x9CE6, 0xDCB0, 0x9EF9, + 0xDCB1, 0x4E83, 0xDCB2, 0x4E84, 0xDCB3, 0x4EB6, 0xDCB4, 0x50BD, 0xDCB5, 0x50BF, 0xDCB6, 0x50C6, 0xDCB7, 0x50AE, 0xDCB8, 0x50C4, + 0xDCB9, 0x50CA, 0xDCBA, 0x50B4, 0xDCBB, 0x50C8, 0xDCBC, 0x50C2, 0xDCBD, 0x50B0, 0xDCBE, 0x50C1, 0xDCBF, 0x50BA, 0xDCC0, 0x50B1, + 0xDCC1, 0x50CB, 0xDCC2, 0x50C9, 0xDCC3, 0x50B6, 0xDCC4, 0x50B8, 0xDCC5, 0x51D7, 0xDCC6, 0x527A, 0xDCC7, 0x5278, 0xDCC8, 0x527B, + 0xDCC9, 0x527C, 0xDCCA, 0x55C3, 0xDCCB, 0x55DB, 0xDCCC, 0x55CC, 0xDCCD, 0x55D0, 0xDCCE, 0x55CB, 0xDCCF, 0x55CA, 0xDCD0, 0x55DD, + 0xDCD1, 0x55C0, 0xDCD2, 0x55D4, 0xDCD3, 0x55C4, 0xDCD4, 0x55E9, 0xDCD5, 0x55BF, 0xDCD6, 0x55D2, 0xDCD7, 0x558D, 0xDCD8, 0x55CF, + 0xDCD9, 0x55D5, 0xDCDA, 0x55E2, 0xDCDB, 0x55D6, 0xDCDC, 0x55C8, 0xDCDD, 0x55F2, 0xDCDE, 0x55CD, 0xDCDF, 0x55D9, 0xDCE0, 0x55C2, + 0xDCE1, 0x5714, 0xDCE2, 0x5853, 0xDCE3, 0x5868, 0xDCE4, 0x5864, 0xDCE5, 0x584F, 0xDCE6, 0x584D, 0xDCE7, 0x5849, 0xDCE8, 0x586F, + 0xDCE9, 0x5855, 0xDCEA, 0x584E, 0xDCEB, 0x585D, 0xDCEC, 0x5859, 0xDCED, 0x5865, 0xDCEE, 0x585B, 0xDCEF, 0x583D, 0xDCF0, 0x5863, + 0xDCF1, 0x5871, 0xDCF2, 0x58FC, 0xDCF3, 0x5AC7, 0xDCF4, 0x5AC4, 0xDCF5, 0x5ACB, 0xDCF6, 0x5ABA, 0xDCF7, 0x5AB8, 0xDCF8, 0x5AB1, + 0xDCF9, 0x5AB5, 0xDCFA, 0x5AB0, 0xDCFB, 0x5ABF, 0xDCFC, 0x5AC8, 0xDCFD, 0x5ABB, 0xDCFE, 0x5AC6, 0xDD40, 0x5AB7, 0xDD41, 0x5AC0, + 0xDD42, 0x5ACA, 0xDD43, 0x5AB4, 0xDD44, 0x5AB6, 0xDD45, 0x5ACD, 0xDD46, 0x5AB9, 0xDD47, 0x5A90, 0xDD48, 0x5BD6, 0xDD49, 0x5BD8, + 0xDD4A, 0x5BD9, 0xDD4B, 0x5C1F, 0xDD4C, 0x5C33, 0xDD4D, 0x5D71, 0xDD4E, 0x5D63, 0xDD4F, 0x5D4A, 0xDD50, 0x5D65, 0xDD51, 0x5D72, + 0xDD52, 0x5D6C, 0xDD53, 0x5D5E, 0xDD54, 0x5D68, 0xDD55, 0x5D67, 0xDD56, 0x5D62, 0xDD57, 0x5DF0, 0xDD58, 0x5E4F, 0xDD59, 0x5E4E, + 0xDD5A, 0x5E4A, 0xDD5B, 0x5E4D, 0xDD5C, 0x5E4B, 0xDD5D, 0x5EC5, 0xDD5E, 0x5ECC, 0xDD5F, 0x5EC6, 0xDD60, 0x5ECB, 0xDD61, 0x5EC7, + 0xDD62, 0x5F40, 0xDD63, 0x5FAF, 0xDD64, 0x5FAD, 0xDD65, 0x60F7, 0xDD66, 0x6149, 0xDD67, 0x614A, 0xDD68, 0x612B, 0xDD69, 0x6145, + 0xDD6A, 0x6136, 0xDD6B, 0x6132, 0xDD6C, 0x612E, 0xDD6D, 0x6146, 0xDD6E, 0x612F, 0xDD6F, 0x614F, 0xDD70, 0x6129, 0xDD71, 0x6140, + 0xDD72, 0x6220, 0xDD73, 0x9168, 0xDD74, 0x6223, 0xDD75, 0x6225, 0xDD76, 0x6224, 0xDD77, 0x63C5, 0xDD78, 0x63F1, 0xDD79, 0x63EB, + 0xDD7A, 0x6410, 0xDD7B, 0x6412, 0xDD7C, 0x6409, 0xDD7D, 0x6420, 0xDD7E, 0x6424, 0xDDA1, 0x6433, 0xDDA2, 0x6443, 0xDDA3, 0x641F, + 0xDDA4, 0x6415, 0xDDA5, 0x6418, 0xDDA6, 0x6439, 0xDDA7, 0x6437, 0xDDA8, 0x6422, 0xDDA9, 0x6423, 0xDDAA, 0x640C, 0xDDAB, 0x6426, + 0xDDAC, 0x6430, 0xDDAD, 0x6428, 0xDDAE, 0x6441, 0xDDAF, 0x6435, 0xDDB0, 0x642F, 0xDDB1, 0x640A, 0xDDB2, 0x641A, 0xDDB3, 0x6440, + 0xDDB4, 0x6425, 0xDDB5, 0x6427, 0xDDB6, 0x640B, 0xDDB7, 0x63E7, 0xDDB8, 0x641B, 0xDDB9, 0x642E, 0xDDBA, 0x6421, 0xDDBB, 0x640E, + 0xDDBC, 0x656F, 0xDDBD, 0x6592, 0xDDBE, 0x65D3, 0xDDBF, 0x6686, 0xDDC0, 0x668C, 0xDDC1, 0x6695, 0xDDC2, 0x6690, 0xDDC3, 0x668B, + 0xDDC4, 0x668A, 0xDDC5, 0x6699, 0xDDC6, 0x6694, 0xDDC7, 0x6678, 0xDDC8, 0x6720, 0xDDC9, 0x6966, 0xDDCA, 0x695F, 0xDDCB, 0x6938, + 0xDDCC, 0x694E, 0xDDCD, 0x6962, 0xDDCE, 0x6971, 0xDDCF, 0x693F, 0xDDD0, 0x6945, 0xDDD1, 0x696A, 0xDDD2, 0x6939, 0xDDD3, 0x6942, + 0xDDD4, 0x6957, 0xDDD5, 0x6959, 0xDDD6, 0x697A, 0xDDD7, 0x6948, 0xDDD8, 0x6949, 0xDDD9, 0x6935, 0xDDDA, 0x696C, 0xDDDB, 0x6933, + 0xDDDC, 0x693D, 0xDDDD, 0x6965, 0xDDDE, 0x68F0, 0xDDDF, 0x6978, 0xDDE0, 0x6934, 0xDDE1, 0x6969, 0xDDE2, 0x6940, 0xDDE3, 0x696F, + 0xDDE4, 0x6944, 0xDDE5, 0x6976, 0xDDE6, 0x6958, 0xDDE7, 0x6941, 0xDDE8, 0x6974, 0xDDE9, 0x694C, 0xDDEA, 0x693B, 0xDDEB, 0x694B, + 0xDDEC, 0x6937, 0xDDED, 0x695C, 0xDDEE, 0x694F, 0xDDEF, 0x6951, 0xDDF0, 0x6932, 0xDDF1, 0x6952, 0xDDF2, 0x692F, 0xDDF3, 0x697B, + 0xDDF4, 0x693C, 0xDDF5, 0x6B46, 0xDDF6, 0x6B45, 0xDDF7, 0x6B43, 0xDDF8, 0x6B42, 0xDDF9, 0x6B48, 0xDDFA, 0x6B41, 0xDDFB, 0x6B9B, + 0xDDFC, 0xFA0D, 0xDDFD, 0x6BFB, 0xDDFE, 0x6BFC, 0xDE40, 0x6BF9, 0xDE41, 0x6BF7, 0xDE42, 0x6BF8, 0xDE43, 0x6E9B, 0xDE44, 0x6ED6, + 0xDE45, 0x6EC8, 0xDE46, 0x6E8F, 0xDE47, 0x6EC0, 0xDE48, 0x6E9F, 0xDE49, 0x6E93, 0xDE4A, 0x6E94, 0xDE4B, 0x6EA0, 0xDE4C, 0x6EB1, + 0xDE4D, 0x6EB9, 0xDE4E, 0x6EC6, 0xDE4F, 0x6ED2, 0xDE50, 0x6EBD, 0xDE51, 0x6EC1, 0xDE52, 0x6E9E, 0xDE53, 0x6EC9, 0xDE54, 0x6EB7, + 0xDE55, 0x6EB0, 0xDE56, 0x6ECD, 0xDE57, 0x6EA6, 0xDE58, 0x6ECF, 0xDE59, 0x6EB2, 0xDE5A, 0x6EBE, 0xDE5B, 0x6EC3, 0xDE5C, 0x6EDC, + 0xDE5D, 0x6ED8, 0xDE5E, 0x6E99, 0xDE5F, 0x6E92, 0xDE60, 0x6E8E, 0xDE61, 0x6E8D, 0xDE62, 0x6EA4, 0xDE63, 0x6EA1, 0xDE64, 0x6EBF, + 0xDE65, 0x6EB3, 0xDE66, 0x6ED0, 0xDE67, 0x6ECA, 0xDE68, 0x6E97, 0xDE69, 0x6EAE, 0xDE6A, 0x6EA3, 0xDE6B, 0x7147, 0xDE6C, 0x7154, + 0xDE6D, 0x7152, 0xDE6E, 0x7163, 0xDE6F, 0x7160, 0xDE70, 0x7141, 0xDE71, 0x715D, 0xDE72, 0x7162, 0xDE73, 0x7172, 0xDE74, 0x7178, + 0xDE75, 0x716A, 0xDE76, 0x7161, 0xDE77, 0x7142, 0xDE78, 0x7158, 0xDE79, 0x7143, 0xDE7A, 0x714B, 0xDE7B, 0x7170, 0xDE7C, 0x715F, + 0xDE7D, 0x7150, 0xDE7E, 0x7153, 0xDEA1, 0x7144, 0xDEA2, 0x714D, 0xDEA3, 0x715A, 0xDEA4, 0x724F, 0xDEA5, 0x728D, 0xDEA6, 0x728C, + 0xDEA7, 0x7291, 0xDEA8, 0x7290, 0xDEA9, 0x728E, 0xDEAA, 0x733C, 0xDEAB, 0x7342, 0xDEAC, 0x733B, 0xDEAD, 0x733A, 0xDEAE, 0x7340, + 0xDEAF, 0x734A, 0xDEB0, 0x7349, 0xDEB1, 0x7444, 0xDEB2, 0x744A, 0xDEB3, 0x744B, 0xDEB4, 0x7452, 0xDEB5, 0x7451, 0xDEB6, 0x7457, + 0xDEB7, 0x7440, 0xDEB8, 0x744F, 0xDEB9, 0x7450, 0xDEBA, 0x744E, 0xDEBB, 0x7442, 0xDEBC, 0x7446, 0xDEBD, 0x744D, 0xDEBE, 0x7454, + 0xDEBF, 0x74E1, 0xDEC0, 0x74FF, 0xDEC1, 0x74FE, 0xDEC2, 0x74FD, 0xDEC3, 0x751D, 0xDEC4, 0x7579, 0xDEC5, 0x7577, 0xDEC6, 0x6983, + 0xDEC7, 0x75EF, 0xDEC8, 0x760F, 0xDEC9, 0x7603, 0xDECA, 0x75F7, 0xDECB, 0x75FE, 0xDECC, 0x75FC, 0xDECD, 0x75F9, 0xDECE, 0x75F8, + 0xDECF, 0x7610, 0xDED0, 0x75FB, 0xDED1, 0x75F6, 0xDED2, 0x75ED, 0xDED3, 0x75F5, 0xDED4, 0x75FD, 0xDED5, 0x7699, 0xDED6, 0x76B5, + 0xDED7, 0x76DD, 0xDED8, 0x7755, 0xDED9, 0x775F, 0xDEDA, 0x7760, 0xDEDB, 0x7752, 0xDEDC, 0x7756, 0xDEDD, 0x775A, 0xDEDE, 0x7769, + 0xDEDF, 0x7767, 0xDEE0, 0x7754, 0xDEE1, 0x7759, 0xDEE2, 0x776D, 0xDEE3, 0x77E0, 0xDEE4, 0x7887, 0xDEE5, 0x789A, 0xDEE6, 0x7894, + 0xDEE7, 0x788F, 0xDEE8, 0x7884, 0xDEE9, 0x7895, 0xDEEA, 0x7885, 0xDEEB, 0x7886, 0xDEEC, 0x78A1, 0xDEED, 0x7883, 0xDEEE, 0x7879, + 0xDEEF, 0x7899, 0xDEF0, 0x7880, 0xDEF1, 0x7896, 0xDEF2, 0x787B, 0xDEF3, 0x797C, 0xDEF4, 0x7982, 0xDEF5, 0x797D, 0xDEF6, 0x7979, + 0xDEF7, 0x7A11, 0xDEF8, 0x7A18, 0xDEF9, 0x7A19, 0xDEFA, 0x7A12, 0xDEFB, 0x7A17, 0xDEFC, 0x7A15, 0xDEFD, 0x7A22, 0xDEFE, 0x7A13, + 0xDF40, 0x7A1B, 0xDF41, 0x7A10, 0xDF42, 0x7AA3, 0xDF43, 0x7AA2, 0xDF44, 0x7A9E, 0xDF45, 0x7AEB, 0xDF46, 0x7B66, 0xDF47, 0x7B64, + 0xDF48, 0x7B6D, 0xDF49, 0x7B74, 0xDF4A, 0x7B69, 0xDF4B, 0x7B72, 0xDF4C, 0x7B65, 0xDF4D, 0x7B73, 0xDF4E, 0x7B71, 0xDF4F, 0x7B70, + 0xDF50, 0x7B61, 0xDF51, 0x7B78, 0xDF52, 0x7B76, 0xDF53, 0x7B63, 0xDF54, 0x7CB2, 0xDF55, 0x7CB4, 0xDF56, 0x7CAF, 0xDF57, 0x7D88, + 0xDF58, 0x7D86, 0xDF59, 0x7D80, 0xDF5A, 0x7D8D, 0xDF5B, 0x7D7F, 0xDF5C, 0x7D85, 0xDF5D, 0x7D7A, 0xDF5E, 0x7D8E, 0xDF5F, 0x7D7B, + 0xDF60, 0x7D83, 0xDF61, 0x7D7C, 0xDF62, 0x7D8C, 0xDF63, 0x7D94, 0xDF64, 0x7D84, 0xDF65, 0x7D7D, 0xDF66, 0x7D92, 0xDF67, 0x7F6D, + 0xDF68, 0x7F6B, 0xDF69, 0x7F67, 0xDF6A, 0x7F68, 0xDF6B, 0x7F6C, 0xDF6C, 0x7FA6, 0xDF6D, 0x7FA5, 0xDF6E, 0x7FA7, 0xDF6F, 0x7FDB, + 0xDF70, 0x7FDC, 0xDF71, 0x8021, 0xDF72, 0x8164, 0xDF73, 0x8160, 0xDF74, 0x8177, 0xDF75, 0x815C, 0xDF76, 0x8169, 0xDF77, 0x815B, + 0xDF78, 0x8162, 0xDF79, 0x8172, 0xDF7A, 0x6721, 0xDF7B, 0x815E, 0xDF7C, 0x8176, 0xDF7D, 0x8167, 0xDF7E, 0x816F, 0xDFA1, 0x8144, + 0xDFA2, 0x8161, 0xDFA3, 0x821D, 0xDFA4, 0x8249, 0xDFA5, 0x8244, 0xDFA6, 0x8240, 0xDFA7, 0x8242, 0xDFA8, 0x8245, 0xDFA9, 0x84F1, + 0xDFAA, 0x843F, 0xDFAB, 0x8456, 0xDFAC, 0x8476, 0xDFAD, 0x8479, 0xDFAE, 0x848F, 0xDFAF, 0x848D, 0xDFB0, 0x8465, 0xDFB1, 0x8451, + 0xDFB2, 0x8440, 0xDFB3, 0x8486, 0xDFB4, 0x8467, 0xDFB5, 0x8430, 0xDFB6, 0x844D, 0xDFB7, 0x847D, 0xDFB8, 0x845A, 0xDFB9, 0x8459, + 0xDFBA, 0x8474, 0xDFBB, 0x8473, 0xDFBC, 0x845D, 0xDFBD, 0x8507, 0xDFBE, 0x845E, 0xDFBF, 0x8437, 0xDFC0, 0x843A, 0xDFC1, 0x8434, + 0xDFC2, 0x847A, 0xDFC3, 0x8443, 0xDFC4, 0x8478, 0xDFC5, 0x8432, 0xDFC6, 0x8445, 0xDFC7, 0x8429, 0xDFC8, 0x83D9, 0xDFC9, 0x844B, + 0xDFCA, 0x842F, 0xDFCB, 0x8442, 0xDFCC, 0x842D, 0xDFCD, 0x845F, 0xDFCE, 0x8470, 0xDFCF, 0x8439, 0xDFD0, 0x844E, 0xDFD1, 0x844C, + 0xDFD2, 0x8452, 0xDFD3, 0x846F, 0xDFD4, 0x84C5, 0xDFD5, 0x848E, 0xDFD6, 0x843B, 0xDFD7, 0x8447, 0xDFD8, 0x8436, 0xDFD9, 0x8433, + 0xDFDA, 0x8468, 0xDFDB, 0x847E, 0xDFDC, 0x8444, 0xDFDD, 0x842B, 0xDFDE, 0x8460, 0xDFDF, 0x8454, 0xDFE0, 0x846E, 0xDFE1, 0x8450, + 0xDFE2, 0x870B, 0xDFE3, 0x8704, 0xDFE4, 0x86F7, 0xDFE5, 0x870C, 0xDFE6, 0x86FA, 0xDFE7, 0x86D6, 0xDFE8, 0x86F5, 0xDFE9, 0x874D, + 0xDFEA, 0x86F8, 0xDFEB, 0x870E, 0xDFEC, 0x8709, 0xDFED, 0x8701, 0xDFEE, 0x86F6, 0xDFEF, 0x870D, 0xDFF0, 0x8705, 0xDFF1, 0x88D6, + 0xDFF2, 0x88CB, 0xDFF3, 0x88CD, 0xDFF4, 0x88CE, 0xDFF5, 0x88DE, 0xDFF6, 0x88DB, 0xDFF7, 0x88DA, 0xDFF8, 0x88CC, 0xDFF9, 0x88D0, + 0xDFFA, 0x8985, 0xDFFB, 0x899B, 0xDFFC, 0x89DF, 0xDFFD, 0x89E5, 0xDFFE, 0x89E4, 0xE040, 0x89E1, 0xE041, 0x89E0, 0xE042, 0x89E2, + 0xE043, 0x89DC, 0xE044, 0x89E6, 0xE045, 0x8A76, 0xE046, 0x8A86, 0xE047, 0x8A7F, 0xE048, 0x8A61, 0xE049, 0x8A3F, 0xE04A, 0x8A77, + 0xE04B, 0x8A82, 0xE04C, 0x8A84, 0xE04D, 0x8A75, 0xE04E, 0x8A83, 0xE04F, 0x8A81, 0xE050, 0x8A74, 0xE051, 0x8A7A, 0xE052, 0x8C3C, + 0xE053, 0x8C4B, 0xE054, 0x8C4A, 0xE055, 0x8C65, 0xE056, 0x8C64, 0xE057, 0x8C66, 0xE058, 0x8C86, 0xE059, 0x8C84, 0xE05A, 0x8C85, + 0xE05B, 0x8CCC, 0xE05C, 0x8D68, 0xE05D, 0x8D69, 0xE05E, 0x8D91, 0xE05F, 0x8D8C, 0xE060, 0x8D8E, 0xE061, 0x8D8F, 0xE062, 0x8D8D, + 0xE063, 0x8D93, 0xE064, 0x8D94, 0xE065, 0x8D90, 0xE066, 0x8D92, 0xE067, 0x8DF0, 0xE068, 0x8DE0, 0xE069, 0x8DEC, 0xE06A, 0x8DF1, + 0xE06B, 0x8DEE, 0xE06C, 0x8DD0, 0xE06D, 0x8DE9, 0xE06E, 0x8DE3, 0xE06F, 0x8DE2, 0xE070, 0x8DE7, 0xE071, 0x8DF2, 0xE072, 0x8DEB, + 0xE073, 0x8DF4, 0xE074, 0x8F06, 0xE075, 0x8EFF, 0xE076, 0x8F01, 0xE077, 0x8F00, 0xE078, 0x8F05, 0xE079, 0x8F07, 0xE07A, 0x8F08, + 0xE07B, 0x8F02, 0xE07C, 0x8F0B, 0xE07D, 0x9052, 0xE07E, 0x903F, 0xE0A1, 0x9044, 0xE0A2, 0x9049, 0xE0A3, 0x903D, 0xE0A4, 0x9110, + 0xE0A5, 0x910D, 0xE0A6, 0x910F, 0xE0A7, 0x9111, 0xE0A8, 0x9116, 0xE0A9, 0x9114, 0xE0AA, 0x910B, 0xE0AB, 0x910E, 0xE0AC, 0x916E, + 0xE0AD, 0x916F, 0xE0AE, 0x9248, 0xE0AF, 0x9252, 0xE0B0, 0x9230, 0xE0B1, 0x923A, 0xE0B2, 0x9266, 0xE0B3, 0x9233, 0xE0B4, 0x9265, + 0xE0B5, 0x925E, 0xE0B6, 0x9283, 0xE0B7, 0x922E, 0xE0B8, 0x924A, 0xE0B9, 0x9246, 0xE0BA, 0x926D, 0xE0BB, 0x926C, 0xE0BC, 0x924F, + 0xE0BD, 0x9260, 0xE0BE, 0x9267, 0xE0BF, 0x926F, 0xE0C0, 0x9236, 0xE0C1, 0x9261, 0xE0C2, 0x9270, 0xE0C3, 0x9231, 0xE0C4, 0x9254, + 0xE0C5, 0x9263, 0xE0C6, 0x9250, 0xE0C7, 0x9272, 0xE0C8, 0x924E, 0xE0C9, 0x9253, 0xE0CA, 0x924C, 0xE0CB, 0x9256, 0xE0CC, 0x9232, + 0xE0CD, 0x959F, 0xE0CE, 0x959C, 0xE0CF, 0x959E, 0xE0D0, 0x959B, 0xE0D1, 0x9692, 0xE0D2, 0x9693, 0xE0D3, 0x9691, 0xE0D4, 0x9697, + 0xE0D5, 0x96CE, 0xE0D6, 0x96FA, 0xE0D7, 0x96FD, 0xE0D8, 0x96F8, 0xE0D9, 0x96F5, 0xE0DA, 0x9773, 0xE0DB, 0x9777, 0xE0DC, 0x9778, + 0xE0DD, 0x9772, 0xE0DE, 0x980F, 0xE0DF, 0x980D, 0xE0E0, 0x980E, 0xE0E1, 0x98AC, 0xE0E2, 0x98F6, 0xE0E3, 0x98F9, 0xE0E4, 0x99AF, + 0xE0E5, 0x99B2, 0xE0E6, 0x99B0, 0xE0E7, 0x99B5, 0xE0E8, 0x9AAD, 0xE0E9, 0x9AAB, 0xE0EA, 0x9B5B, 0xE0EB, 0x9CEA, 0xE0EC, 0x9CED, + 0xE0ED, 0x9CE7, 0xE0EE, 0x9E80, 0xE0EF, 0x9EFD, 0xE0F0, 0x50E6, 0xE0F1, 0x50D4, 0xE0F2, 0x50D7, 0xE0F3, 0x50E8, 0xE0F4, 0x50F3, + 0xE0F5, 0x50DB, 0xE0F6, 0x50EA, 0xE0F7, 0x50DD, 0xE0F8, 0x50E4, 0xE0F9, 0x50D3, 0xE0FA, 0x50EC, 0xE0FB, 0x50F0, 0xE0FC, 0x50EF, + 0xE0FD, 0x50E3, 0xE0FE, 0x50E0, 0xE140, 0x51D8, 0xE141, 0x5280, 0xE142, 0x5281, 0xE143, 0x52E9, 0xE144, 0x52EB, 0xE145, 0x5330, + 0xE146, 0x53AC, 0xE147, 0x5627, 0xE148, 0x5615, 0xE149, 0x560C, 0xE14A, 0x5612, 0xE14B, 0x55FC, 0xE14C, 0x560F, 0xE14D, 0x561C, + 0xE14E, 0x5601, 0xE14F, 0x5613, 0xE150, 0x5602, 0xE151, 0x55FA, 0xE152, 0x561D, 0xE153, 0x5604, 0xE154, 0x55FF, 0xE155, 0x55F9, + 0xE156, 0x5889, 0xE157, 0x587C, 0xE158, 0x5890, 0xE159, 0x5898, 0xE15A, 0x5886, 0xE15B, 0x5881, 0xE15C, 0x587F, 0xE15D, 0x5874, + 0xE15E, 0x588B, 0xE15F, 0x587A, 0xE160, 0x5887, 0xE161, 0x5891, 0xE162, 0x588E, 0xE163, 0x5876, 0xE164, 0x5882, 0xE165, 0x5888, + 0xE166, 0x587B, 0xE167, 0x5894, 0xE168, 0x588F, 0xE169, 0x58FE, 0xE16A, 0x596B, 0xE16B, 0x5ADC, 0xE16C, 0x5AEE, 0xE16D, 0x5AE5, + 0xE16E, 0x5AD5, 0xE16F, 0x5AEA, 0xE170, 0x5ADA, 0xE171, 0x5AED, 0xE172, 0x5AEB, 0xE173, 0x5AF3, 0xE174, 0x5AE2, 0xE175, 0x5AE0, + 0xE176, 0x5ADB, 0xE177, 0x5AEC, 0xE178, 0x5ADE, 0xE179, 0x5ADD, 0xE17A, 0x5AD9, 0xE17B, 0x5AE8, 0xE17C, 0x5ADF, 0xE17D, 0x5B77, + 0xE17E, 0x5BE0, 0xE1A1, 0x5BE3, 0xE1A2, 0x5C63, 0xE1A3, 0x5D82, 0xE1A4, 0x5D80, 0xE1A5, 0x5D7D, 0xE1A6, 0x5D86, 0xE1A7, 0x5D7A, + 0xE1A8, 0x5D81, 0xE1A9, 0x5D77, 0xE1AA, 0x5D8A, 0xE1AB, 0x5D89, 0xE1AC, 0x5D88, 0xE1AD, 0x5D7E, 0xE1AE, 0x5D7C, 0xE1AF, 0x5D8D, + 0xE1B0, 0x5D79, 0xE1B1, 0x5D7F, 0xE1B2, 0x5E58, 0xE1B3, 0x5E59, 0xE1B4, 0x5E53, 0xE1B5, 0x5ED8, 0xE1B6, 0x5ED1, 0xE1B7, 0x5ED7, + 0xE1B8, 0x5ECE, 0xE1B9, 0x5EDC, 0xE1BA, 0x5ED5, 0xE1BB, 0x5ED9, 0xE1BC, 0x5ED2, 0xE1BD, 0x5ED4, 0xE1BE, 0x5F44, 0xE1BF, 0x5F43, + 0xE1C0, 0x5F6F, 0xE1C1, 0x5FB6, 0xE1C2, 0x612C, 0xE1C3, 0x6128, 0xE1C4, 0x6141, 0xE1C5, 0x615E, 0xE1C6, 0x6171, 0xE1C7, 0x6173, + 0xE1C8, 0x6152, 0xE1C9, 0x6153, 0xE1CA, 0x6172, 0xE1CB, 0x616C, 0xE1CC, 0x6180, 0xE1CD, 0x6174, 0xE1CE, 0x6154, 0xE1CF, 0x617A, + 0xE1D0, 0x615B, 0xE1D1, 0x6165, 0xE1D2, 0x613B, 0xE1D3, 0x616A, 0xE1D4, 0x6161, 0xE1D5, 0x6156, 0xE1D6, 0x6229, 0xE1D7, 0x6227, + 0xE1D8, 0x622B, 0xE1D9, 0x642B, 0xE1DA, 0x644D, 0xE1DB, 0x645B, 0xE1DC, 0x645D, 0xE1DD, 0x6474, 0xE1DE, 0x6476, 0xE1DF, 0x6472, + 0xE1E0, 0x6473, 0xE1E1, 0x647D, 0xE1E2, 0x6475, 0xE1E3, 0x6466, 0xE1E4, 0x64A6, 0xE1E5, 0x644E, 0xE1E6, 0x6482, 0xE1E7, 0x645E, + 0xE1E8, 0x645C, 0xE1E9, 0x644B, 0xE1EA, 0x6453, 0xE1EB, 0x6460, 0xE1EC, 0x6450, 0xE1ED, 0x647F, 0xE1EE, 0x643F, 0xE1EF, 0x646C, + 0xE1F0, 0x646B, 0xE1F1, 0x6459, 0xE1F2, 0x6465, 0xE1F3, 0x6477, 0xE1F4, 0x6573, 0xE1F5, 0x65A0, 0xE1F6, 0x66A1, 0xE1F7, 0x66A0, + 0xE1F8, 0x669F, 0xE1F9, 0x6705, 0xE1FA, 0x6704, 0xE1FB, 0x6722, 0xE1FC, 0x69B1, 0xE1FD, 0x69B6, 0xE1FE, 0x69C9, 0xE240, 0x69A0, + 0xE241, 0x69CE, 0xE242, 0x6996, 0xE243, 0x69B0, 0xE244, 0x69AC, 0xE245, 0x69BC, 0xE246, 0x6991, 0xE247, 0x6999, 0xE248, 0x698E, + 0xE249, 0x69A7, 0xE24A, 0x698D, 0xE24B, 0x69A9, 0xE24C, 0x69BE, 0xE24D, 0x69AF, 0xE24E, 0x69BF, 0xE24F, 0x69C4, 0xE250, 0x69BD, + 0xE251, 0x69A4, 0xE252, 0x69D4, 0xE253, 0x69B9, 0xE254, 0x69CA, 0xE255, 0x699A, 0xE256, 0x69CF, 0xE257, 0x69B3, 0xE258, 0x6993, + 0xE259, 0x69AA, 0xE25A, 0x69A1, 0xE25B, 0x699E, 0xE25C, 0x69D9, 0xE25D, 0x6997, 0xE25E, 0x6990, 0xE25F, 0x69C2, 0xE260, 0x69B5, + 0xE261, 0x69A5, 0xE262, 0x69C6, 0xE263, 0x6B4A, 0xE264, 0x6B4D, 0xE265, 0x6B4B, 0xE266, 0x6B9E, 0xE267, 0x6B9F, 0xE268, 0x6BA0, + 0xE269, 0x6BC3, 0xE26A, 0x6BC4, 0xE26B, 0x6BFE, 0xE26C, 0x6ECE, 0xE26D, 0x6EF5, 0xE26E, 0x6EF1, 0xE26F, 0x6F03, 0xE270, 0x6F25, + 0xE271, 0x6EF8, 0xE272, 0x6F37, 0xE273, 0x6EFB, 0xE274, 0x6F2E, 0xE275, 0x6F09, 0xE276, 0x6F4E, 0xE277, 0x6F19, 0xE278, 0x6F1A, + 0xE279, 0x6F27, 0xE27A, 0x6F18, 0xE27B, 0x6F3B, 0xE27C, 0x6F12, 0xE27D, 0x6EED, 0xE27E, 0x6F0A, 0xE2A1, 0x6F36, 0xE2A2, 0x6F73, + 0xE2A3, 0x6EF9, 0xE2A4, 0x6EEE, 0xE2A5, 0x6F2D, 0xE2A6, 0x6F40, 0xE2A7, 0x6F30, 0xE2A8, 0x6F3C, 0xE2A9, 0x6F35, 0xE2AA, 0x6EEB, + 0xE2AB, 0x6F07, 0xE2AC, 0x6F0E, 0xE2AD, 0x6F43, 0xE2AE, 0x6F05, 0xE2AF, 0x6EFD, 0xE2B0, 0x6EF6, 0xE2B1, 0x6F39, 0xE2B2, 0x6F1C, + 0xE2B3, 0x6EFC, 0xE2B4, 0x6F3A, 0xE2B5, 0x6F1F, 0xE2B6, 0x6F0D, 0xE2B7, 0x6F1E, 0xE2B8, 0x6F08, 0xE2B9, 0x6F21, 0xE2BA, 0x7187, + 0xE2BB, 0x7190, 0xE2BC, 0x7189, 0xE2BD, 0x7180, 0xE2BE, 0x7185, 0xE2BF, 0x7182, 0xE2C0, 0x718F, 0xE2C1, 0x717B, 0xE2C2, 0x7186, + 0xE2C3, 0x7181, 0xE2C4, 0x7197, 0xE2C5, 0x7244, 0xE2C6, 0x7253, 0xE2C7, 0x7297, 0xE2C8, 0x7295, 0xE2C9, 0x7293, 0xE2CA, 0x7343, + 0xE2CB, 0x734D, 0xE2CC, 0x7351, 0xE2CD, 0x734C, 0xE2CE, 0x7462, 0xE2CF, 0x7473, 0xE2D0, 0x7471, 0xE2D1, 0x7475, 0xE2D2, 0x7472, + 0xE2D3, 0x7467, 0xE2D4, 0x746E, 0xE2D5, 0x7500, 0xE2D6, 0x7502, 0xE2D7, 0x7503, 0xE2D8, 0x757D, 0xE2D9, 0x7590, 0xE2DA, 0x7616, + 0xE2DB, 0x7608, 0xE2DC, 0x760C, 0xE2DD, 0x7615, 0xE2DE, 0x7611, 0xE2DF, 0x760A, 0xE2E0, 0x7614, 0xE2E1, 0x76B8, 0xE2E2, 0x7781, + 0xE2E3, 0x777C, 0xE2E4, 0x7785, 0xE2E5, 0x7782, 0xE2E6, 0x776E, 0xE2E7, 0x7780, 0xE2E8, 0x776F, 0xE2E9, 0x777E, 0xE2EA, 0x7783, + 0xE2EB, 0x78B2, 0xE2EC, 0x78AA, 0xE2ED, 0x78B4, 0xE2EE, 0x78AD, 0xE2EF, 0x78A8, 0xE2F0, 0x787E, 0xE2F1, 0x78AB, 0xE2F2, 0x789E, + 0xE2F3, 0x78A5, 0xE2F4, 0x78A0, 0xE2F5, 0x78AC, 0xE2F6, 0x78A2, 0xE2F7, 0x78A4, 0xE2F8, 0x7998, 0xE2F9, 0x798A, 0xE2FA, 0x798B, + 0xE2FB, 0x7996, 0xE2FC, 0x7995, 0xE2FD, 0x7994, 0xE2FE, 0x7993, 0xE340, 0x7997, 0xE341, 0x7988, 0xE342, 0x7992, 0xE343, 0x7990, + 0xE344, 0x7A2B, 0xE345, 0x7A4A, 0xE346, 0x7A30, 0xE347, 0x7A2F, 0xE348, 0x7A28, 0xE349, 0x7A26, 0xE34A, 0x7AA8, 0xE34B, 0x7AAB, + 0xE34C, 0x7AAC, 0xE34D, 0x7AEE, 0xE34E, 0x7B88, 0xE34F, 0x7B9C, 0xE350, 0x7B8A, 0xE351, 0x7B91, 0xE352, 0x7B90, 0xE353, 0x7B96, + 0xE354, 0x7B8D, 0xE355, 0x7B8C, 0xE356, 0x7B9B, 0xE357, 0x7B8E, 0xE358, 0x7B85, 0xE359, 0x7B98, 0xE35A, 0x5284, 0xE35B, 0x7B99, + 0xE35C, 0x7BA4, 0xE35D, 0x7B82, 0xE35E, 0x7CBB, 0xE35F, 0x7CBF, 0xE360, 0x7CBC, 0xE361, 0x7CBA, 0xE362, 0x7DA7, 0xE363, 0x7DB7, + 0xE364, 0x7DC2, 0xE365, 0x7DA3, 0xE366, 0x7DAA, 0xE367, 0x7DC1, 0xE368, 0x7DC0, 0xE369, 0x7DC5, 0xE36A, 0x7D9D, 0xE36B, 0x7DCE, + 0xE36C, 0x7DC4, 0xE36D, 0x7DC6, 0xE36E, 0x7DCB, 0xE36F, 0x7DCC, 0xE370, 0x7DAF, 0xE371, 0x7DB9, 0xE372, 0x7D96, 0xE373, 0x7DBC, + 0xE374, 0x7D9F, 0xE375, 0x7DA6, 0xE376, 0x7DAE, 0xE377, 0x7DA9, 0xE378, 0x7DA1, 0xE379, 0x7DC9, 0xE37A, 0x7F73, 0xE37B, 0x7FE2, + 0xE37C, 0x7FE3, 0xE37D, 0x7FE5, 0xE37E, 0x7FDE, 0xE3A1, 0x8024, 0xE3A2, 0x805D, 0xE3A3, 0x805C, 0xE3A4, 0x8189, 0xE3A5, 0x8186, + 0xE3A6, 0x8183, 0xE3A7, 0x8187, 0xE3A8, 0x818D, 0xE3A9, 0x818C, 0xE3AA, 0x818B, 0xE3AB, 0x8215, 0xE3AC, 0x8497, 0xE3AD, 0x84A4, + 0xE3AE, 0x84A1, 0xE3AF, 0x849F, 0xE3B0, 0x84BA, 0xE3B1, 0x84CE, 0xE3B2, 0x84C2, 0xE3B3, 0x84AC, 0xE3B4, 0x84AE, 0xE3B5, 0x84AB, + 0xE3B6, 0x84B9, 0xE3B7, 0x84B4, 0xE3B8, 0x84C1, 0xE3B9, 0x84CD, 0xE3BA, 0x84AA, 0xE3BB, 0x849A, 0xE3BC, 0x84B1, 0xE3BD, 0x84D0, + 0xE3BE, 0x849D, 0xE3BF, 0x84A7, 0xE3C0, 0x84BB, 0xE3C1, 0x84A2, 0xE3C2, 0x8494, 0xE3C3, 0x84C7, 0xE3C4, 0x84CC, 0xE3C5, 0x849B, + 0xE3C6, 0x84A9, 0xE3C7, 0x84AF, 0xE3C8, 0x84A8, 0xE3C9, 0x84D6, 0xE3CA, 0x8498, 0xE3CB, 0x84B6, 0xE3CC, 0x84CF, 0xE3CD, 0x84A0, + 0xE3CE, 0x84D7, 0xE3CF, 0x84D4, 0xE3D0, 0x84D2, 0xE3D1, 0x84DB, 0xE3D2, 0x84B0, 0xE3D3, 0x8491, 0xE3D4, 0x8661, 0xE3D5, 0x8733, + 0xE3D6, 0x8723, 0xE3D7, 0x8728, 0xE3D8, 0x876B, 0xE3D9, 0x8740, 0xE3DA, 0x872E, 0xE3DB, 0x871E, 0xE3DC, 0x8721, 0xE3DD, 0x8719, + 0xE3DE, 0x871B, 0xE3DF, 0x8743, 0xE3E0, 0x872C, 0xE3E1, 0x8741, 0xE3E2, 0x873E, 0xE3E3, 0x8746, 0xE3E4, 0x8720, 0xE3E5, 0x8732, + 0xE3E6, 0x872A, 0xE3E7, 0x872D, 0xE3E8, 0x873C, 0xE3E9, 0x8712, 0xE3EA, 0x873A, 0xE3EB, 0x8731, 0xE3EC, 0x8735, 0xE3ED, 0x8742, + 0xE3EE, 0x8726, 0xE3EF, 0x8727, 0xE3F0, 0x8738, 0xE3F1, 0x8724, 0xE3F2, 0x871A, 0xE3F3, 0x8730, 0xE3F4, 0x8711, 0xE3F5, 0x88F7, + 0xE3F6, 0x88E7, 0xE3F7, 0x88F1, 0xE3F8, 0x88F2, 0xE3F9, 0x88FA, 0xE3FA, 0x88FE, 0xE3FB, 0x88EE, 0xE3FC, 0x88FC, 0xE3FD, 0x88F6, + 0xE3FE, 0x88FB, 0xE440, 0x88F0, 0xE441, 0x88EC, 0xE442, 0x88EB, 0xE443, 0x899D, 0xE444, 0x89A1, 0xE445, 0x899F, 0xE446, 0x899E, + 0xE447, 0x89E9, 0xE448, 0x89EB, 0xE449, 0x89E8, 0xE44A, 0x8AAB, 0xE44B, 0x8A99, 0xE44C, 0x8A8B, 0xE44D, 0x8A92, 0xE44E, 0x8A8F, + 0xE44F, 0x8A96, 0xE450, 0x8C3D, 0xE451, 0x8C68, 0xE452, 0x8C69, 0xE453, 0x8CD5, 0xE454, 0x8CCF, 0xE455, 0x8CD7, 0xE456, 0x8D96, + 0xE457, 0x8E09, 0xE458, 0x8E02, 0xE459, 0x8DFF, 0xE45A, 0x8E0D, 0xE45B, 0x8DFD, 0xE45C, 0x8E0A, 0xE45D, 0x8E03, 0xE45E, 0x8E07, + 0xE45F, 0x8E06, 0xE460, 0x8E05, 0xE461, 0x8DFE, 0xE462, 0x8E00, 0xE463, 0x8E04, 0xE464, 0x8F10, 0xE465, 0x8F11, 0xE466, 0x8F0E, + 0xE467, 0x8F0D, 0xE468, 0x9123, 0xE469, 0x911C, 0xE46A, 0x9120, 0xE46B, 0x9122, 0xE46C, 0x911F, 0xE46D, 0x911D, 0xE46E, 0x911A, + 0xE46F, 0x9124, 0xE470, 0x9121, 0xE471, 0x911B, 0xE472, 0x917A, 0xE473, 0x9172, 0xE474, 0x9179, 0xE475, 0x9173, 0xE476, 0x92A5, + 0xE477, 0x92A4, 0xE478, 0x9276, 0xE479, 0x929B, 0xE47A, 0x927A, 0xE47B, 0x92A0, 0xE47C, 0x9294, 0xE47D, 0x92AA, 0xE47E, 0x928D, + 0xE4A1, 0x92A6, 0xE4A2, 0x929A, 0xE4A3, 0x92AB, 0xE4A4, 0x9279, 0xE4A5, 0x9297, 0xE4A6, 0x927F, 0xE4A7, 0x92A3, 0xE4A8, 0x92EE, + 0xE4A9, 0x928E, 0xE4AA, 0x9282, 0xE4AB, 0x9295, 0xE4AC, 0x92A2, 0xE4AD, 0x927D, 0xE4AE, 0x9288, 0xE4AF, 0x92A1, 0xE4B0, 0x928A, + 0xE4B1, 0x9286, 0xE4B2, 0x928C, 0xE4B3, 0x9299, 0xE4B4, 0x92A7, 0xE4B5, 0x927E, 0xE4B6, 0x9287, 0xE4B7, 0x92A9, 0xE4B8, 0x929D, + 0xE4B9, 0x928B, 0xE4BA, 0x922D, 0xE4BB, 0x969E, 0xE4BC, 0x96A1, 0xE4BD, 0x96FF, 0xE4BE, 0x9758, 0xE4BF, 0x977D, 0xE4C0, 0x977A, + 0xE4C1, 0x977E, 0xE4C2, 0x9783, 0xE4C3, 0x9780, 0xE4C4, 0x9782, 0xE4C5, 0x977B, 0xE4C6, 0x9784, 0xE4C7, 0x9781, 0xE4C8, 0x977F, + 0xE4C9, 0x97CE, 0xE4CA, 0x97CD, 0xE4CB, 0x9816, 0xE4CC, 0x98AD, 0xE4CD, 0x98AE, 0xE4CE, 0x9902, 0xE4CF, 0x9900, 0xE4D0, 0x9907, + 0xE4D1, 0x999D, 0xE4D2, 0x999C, 0xE4D3, 0x99C3, 0xE4D4, 0x99B9, 0xE4D5, 0x99BB, 0xE4D6, 0x99BA, 0xE4D7, 0x99C2, 0xE4D8, 0x99BD, + 0xE4D9, 0x99C7, 0xE4DA, 0x9AB1, 0xE4DB, 0x9AE3, 0xE4DC, 0x9AE7, 0xE4DD, 0x9B3E, 0xE4DE, 0x9B3F, 0xE4DF, 0x9B60, 0xE4E0, 0x9B61, + 0xE4E1, 0x9B5F, 0xE4E2, 0x9CF1, 0xE4E3, 0x9CF2, 0xE4E4, 0x9CF5, 0xE4E5, 0x9EA7, 0xE4E6, 0x50FF, 0xE4E7, 0x5103, 0xE4E8, 0x5130, + 0xE4E9, 0x50F8, 0xE4EA, 0x5106, 0xE4EB, 0x5107, 0xE4EC, 0x50F6, 0xE4ED, 0x50FE, 0xE4EE, 0x510B, 0xE4EF, 0x510C, 0xE4F0, 0x50FD, + 0xE4F1, 0x510A, 0xE4F2, 0x528B, 0xE4F3, 0x528C, 0xE4F4, 0x52F1, 0xE4F5, 0x52EF, 0xE4F6, 0x5648, 0xE4F7, 0x5642, 0xE4F8, 0x564C, + 0xE4F9, 0x5635, 0xE4FA, 0x5641, 0xE4FB, 0x564A, 0xE4FC, 0x5649, 0xE4FD, 0x5646, 0xE4FE, 0x5658, 0xE540, 0x565A, 0xE541, 0x5640, + 0xE542, 0x5633, 0xE543, 0x563D, 0xE544, 0x562C, 0xE545, 0x563E, 0xE546, 0x5638, 0xE547, 0x562A, 0xE548, 0x563A, 0xE549, 0x571A, + 0xE54A, 0x58AB, 0xE54B, 0x589D, 0xE54C, 0x58B1, 0xE54D, 0x58A0, 0xE54E, 0x58A3, 0xE54F, 0x58AF, 0xE550, 0x58AC, 0xE551, 0x58A5, + 0xE552, 0x58A1, 0xE553, 0x58FF, 0xE554, 0x5AFF, 0xE555, 0x5AF4, 0xE556, 0x5AFD, 0xE557, 0x5AF7, 0xE558, 0x5AF6, 0xE559, 0x5B03, + 0xE55A, 0x5AF8, 0xE55B, 0x5B02, 0xE55C, 0x5AF9, 0xE55D, 0x5B01, 0xE55E, 0x5B07, 0xE55F, 0x5B05, 0xE560, 0x5B0F, 0xE561, 0x5C67, + 0xE562, 0x5D99, 0xE563, 0x5D97, 0xE564, 0x5D9F, 0xE565, 0x5D92, 0xE566, 0x5DA2, 0xE567, 0x5D93, 0xE568, 0x5D95, 0xE569, 0x5DA0, + 0xE56A, 0x5D9C, 0xE56B, 0x5DA1, 0xE56C, 0x5D9A, 0xE56D, 0x5D9E, 0xE56E, 0x5E69, 0xE56F, 0x5E5D, 0xE570, 0x5E60, 0xE571, 0x5E5C, + 0xE572, 0x7DF3, 0xE573, 0x5EDB, 0xE574, 0x5EDE, 0xE575, 0x5EE1, 0xE576, 0x5F49, 0xE577, 0x5FB2, 0xE578, 0x618B, 0xE579, 0x6183, + 0xE57A, 0x6179, 0xE57B, 0x61B1, 0xE57C, 0x61B0, 0xE57D, 0x61A2, 0xE57E, 0x6189, 0xE5A1, 0x619B, 0xE5A2, 0x6193, 0xE5A3, 0x61AF, + 0xE5A4, 0x61AD, 0xE5A5, 0x619F, 0xE5A6, 0x6192, 0xE5A7, 0x61AA, 0xE5A8, 0x61A1, 0xE5A9, 0x618D, 0xE5AA, 0x6166, 0xE5AB, 0x61B3, + 0xE5AC, 0x622D, 0xE5AD, 0x646E, 0xE5AE, 0x6470, 0xE5AF, 0x6496, 0xE5B0, 0x64A0, 0xE5B1, 0x6485, 0xE5B2, 0x6497, 0xE5B3, 0x649C, + 0xE5B4, 0x648F, 0xE5B5, 0x648B, 0xE5B6, 0x648A, 0xE5B7, 0x648C, 0xE5B8, 0x64A3, 0xE5B9, 0x649F, 0xE5BA, 0x6468, 0xE5BB, 0x64B1, + 0xE5BC, 0x6498, 0xE5BD, 0x6576, 0xE5BE, 0x657A, 0xE5BF, 0x6579, 0xE5C0, 0x657B, 0xE5C1, 0x65B2, 0xE5C2, 0x65B3, 0xE5C3, 0x66B5, + 0xE5C4, 0x66B0, 0xE5C5, 0x66A9, 0xE5C6, 0x66B2, 0xE5C7, 0x66B7, 0xE5C8, 0x66AA, 0xE5C9, 0x66AF, 0xE5CA, 0x6A00, 0xE5CB, 0x6A06, + 0xE5CC, 0x6A17, 0xE5CD, 0x69E5, 0xE5CE, 0x69F8, 0xE5CF, 0x6A15, 0xE5D0, 0x69F1, 0xE5D1, 0x69E4, 0xE5D2, 0x6A20, 0xE5D3, 0x69FF, + 0xE5D4, 0x69EC, 0xE5D5, 0x69E2, 0xE5D6, 0x6A1B, 0xE5D7, 0x6A1D, 0xE5D8, 0x69FE, 0xE5D9, 0x6A27, 0xE5DA, 0x69F2, 0xE5DB, 0x69EE, + 0xE5DC, 0x6A14, 0xE5DD, 0x69F7, 0xE5DE, 0x69E7, 0xE5DF, 0x6A40, 0xE5E0, 0x6A08, 0xE5E1, 0x69E6, 0xE5E2, 0x69FB, 0xE5E3, 0x6A0D, + 0xE5E4, 0x69FC, 0xE5E5, 0x69EB, 0xE5E6, 0x6A09, 0xE5E7, 0x6A04, 0xE5E8, 0x6A18, 0xE5E9, 0x6A25, 0xE5EA, 0x6A0F, 0xE5EB, 0x69F6, + 0xE5EC, 0x6A26, 0xE5ED, 0x6A07, 0xE5EE, 0x69F4, 0xE5EF, 0x6A16, 0xE5F0, 0x6B51, 0xE5F1, 0x6BA5, 0xE5F2, 0x6BA3, 0xE5F3, 0x6BA2, + 0xE5F4, 0x6BA6, 0xE5F5, 0x6C01, 0xE5F6, 0x6C00, 0xE5F7, 0x6BFF, 0xE5F8, 0x6C02, 0xE5F9, 0x6F41, 0xE5FA, 0x6F26, 0xE5FB, 0x6F7E, + 0xE5FC, 0x6F87, 0xE5FD, 0x6FC6, 0xE5FE, 0x6F92, 0xE640, 0x6F8D, 0xE641, 0x6F89, 0xE642, 0x6F8C, 0xE643, 0x6F62, 0xE644, 0x6F4F, + 0xE645, 0x6F85, 0xE646, 0x6F5A, 0xE647, 0x6F96, 0xE648, 0x6F76, 0xE649, 0x6F6C, 0xE64A, 0x6F82, 0xE64B, 0x6F55, 0xE64C, 0x6F72, + 0xE64D, 0x6F52, 0xE64E, 0x6F50, 0xE64F, 0x6F57, 0xE650, 0x6F94, 0xE651, 0x6F93, 0xE652, 0x6F5D, 0xE653, 0x6F00, 0xE654, 0x6F61, + 0xE655, 0x6F6B, 0xE656, 0x6F7D, 0xE657, 0x6F67, 0xE658, 0x6F90, 0xE659, 0x6F53, 0xE65A, 0x6F8B, 0xE65B, 0x6F69, 0xE65C, 0x6F7F, + 0xE65D, 0x6F95, 0xE65E, 0x6F63, 0xE65F, 0x6F77, 0xE660, 0x6F6A, 0xE661, 0x6F7B, 0xE662, 0x71B2, 0xE663, 0x71AF, 0xE664, 0x719B, + 0xE665, 0x71B0, 0xE666, 0x71A0, 0xE667, 0x719A, 0xE668, 0x71A9, 0xE669, 0x71B5, 0xE66A, 0x719D, 0xE66B, 0x71A5, 0xE66C, 0x719E, + 0xE66D, 0x71A4, 0xE66E, 0x71A1, 0xE66F, 0x71AA, 0xE670, 0x719C, 0xE671, 0x71A7, 0xE672, 0x71B3, 0xE673, 0x7298, 0xE674, 0x729A, + 0xE675, 0x7358, 0xE676, 0x7352, 0xE677, 0x735E, 0xE678, 0x735F, 0xE679, 0x7360, 0xE67A, 0x735D, 0xE67B, 0x735B, 0xE67C, 0x7361, + 0xE67D, 0x735A, 0xE67E, 0x7359, 0xE6A1, 0x7362, 0xE6A2, 0x7487, 0xE6A3, 0x7489, 0xE6A4, 0x748A, 0xE6A5, 0x7486, 0xE6A6, 0x7481, + 0xE6A7, 0x747D, 0xE6A8, 0x7485, 0xE6A9, 0x7488, 0xE6AA, 0x747C, 0xE6AB, 0x7479, 0xE6AC, 0x7508, 0xE6AD, 0x7507, 0xE6AE, 0x757E, + 0xE6AF, 0x7625, 0xE6B0, 0x761E, 0xE6B1, 0x7619, 0xE6B2, 0x761D, 0xE6B3, 0x761C, 0xE6B4, 0x7623, 0xE6B5, 0x761A, 0xE6B6, 0x7628, + 0xE6B7, 0x761B, 0xE6B8, 0x769C, 0xE6B9, 0x769D, 0xE6BA, 0x769E, 0xE6BB, 0x769B, 0xE6BC, 0x778D, 0xE6BD, 0x778F, 0xE6BE, 0x7789, + 0xE6BF, 0x7788, 0xE6C0, 0x78CD, 0xE6C1, 0x78BB, 0xE6C2, 0x78CF, 0xE6C3, 0x78CC, 0xE6C4, 0x78D1, 0xE6C5, 0x78CE, 0xE6C6, 0x78D4, + 0xE6C7, 0x78C8, 0xE6C8, 0x78C3, 0xE6C9, 0x78C4, 0xE6CA, 0x78C9, 0xE6CB, 0x799A, 0xE6CC, 0x79A1, 0xE6CD, 0x79A0, 0xE6CE, 0x799C, + 0xE6CF, 0x79A2, 0xE6D0, 0x799B, 0xE6D1, 0x6B76, 0xE6D2, 0x7A39, 0xE6D3, 0x7AB2, 0xE6D4, 0x7AB4, 0xE6D5, 0x7AB3, 0xE6D6, 0x7BB7, + 0xE6D7, 0x7BCB, 0xE6D8, 0x7BBE, 0xE6D9, 0x7BAC, 0xE6DA, 0x7BCE, 0xE6DB, 0x7BAF, 0xE6DC, 0x7BB9, 0xE6DD, 0x7BCA, 0xE6DE, 0x7BB5, + 0xE6DF, 0x7CC5, 0xE6E0, 0x7CC8, 0xE6E1, 0x7CCC, 0xE6E2, 0x7CCB, 0xE6E3, 0x7DF7, 0xE6E4, 0x7DDB, 0xE6E5, 0x7DEA, 0xE6E6, 0x7DE7, + 0xE6E7, 0x7DD7, 0xE6E8, 0x7DE1, 0xE6E9, 0x7E03, 0xE6EA, 0x7DFA, 0xE6EB, 0x7DE6, 0xE6EC, 0x7DF6, 0xE6ED, 0x7DF1, 0xE6EE, 0x7DF0, + 0xE6EF, 0x7DEE, 0xE6F0, 0x7DDF, 0xE6F1, 0x7F76, 0xE6F2, 0x7FAC, 0xE6F3, 0x7FB0, 0xE6F4, 0x7FAD, 0xE6F5, 0x7FED, 0xE6F6, 0x7FEB, + 0xE6F7, 0x7FEA, 0xE6F8, 0x7FEC, 0xE6F9, 0x7FE6, 0xE6FA, 0x7FE8, 0xE6FB, 0x8064, 0xE6FC, 0x8067, 0xE6FD, 0x81A3, 0xE6FE, 0x819F, + 0xE740, 0x819E, 0xE741, 0x8195, 0xE742, 0x81A2, 0xE743, 0x8199, 0xE744, 0x8197, 0xE745, 0x8216, 0xE746, 0x824F, 0xE747, 0x8253, + 0xE748, 0x8252, 0xE749, 0x8250, 0xE74A, 0x824E, 0xE74B, 0x8251, 0xE74C, 0x8524, 0xE74D, 0x853B, 0xE74E, 0x850F, 0xE74F, 0x8500, + 0xE750, 0x8529, 0xE751, 0x850E, 0xE752, 0x8509, 0xE753, 0x850D, 0xE754, 0x851F, 0xE755, 0x850A, 0xE756, 0x8527, 0xE757, 0x851C, + 0xE758, 0x84FB, 0xE759, 0x852B, 0xE75A, 0x84FA, 0xE75B, 0x8508, 0xE75C, 0x850C, 0xE75D, 0x84F4, 0xE75E, 0x852A, 0xE75F, 0x84F2, + 0xE760, 0x8515, 0xE761, 0x84F7, 0xE762, 0x84EB, 0xE763, 0x84F3, 0xE764, 0x84FC, 0xE765, 0x8512, 0xE766, 0x84EA, 0xE767, 0x84E9, + 0xE768, 0x8516, 0xE769, 0x84FE, 0xE76A, 0x8528, 0xE76B, 0x851D, 0xE76C, 0x852E, 0xE76D, 0x8502, 0xE76E, 0x84FD, 0xE76F, 0x851E, + 0xE770, 0x84F6, 0xE771, 0x8531, 0xE772, 0x8526, 0xE773, 0x84E7, 0xE774, 0x84E8, 0xE775, 0x84F0, 0xE776, 0x84EF, 0xE777, 0x84F9, + 0xE778, 0x8518, 0xE779, 0x8520, 0xE77A, 0x8530, 0xE77B, 0x850B, 0xE77C, 0x8519, 0xE77D, 0x852F, 0xE77E, 0x8662, 0xE7A1, 0x8756, + 0xE7A2, 0x8763, 0xE7A3, 0x8764, 0xE7A4, 0x8777, 0xE7A5, 0x87E1, 0xE7A6, 0x8773, 0xE7A7, 0x8758, 0xE7A8, 0x8754, 0xE7A9, 0x875B, + 0xE7AA, 0x8752, 0xE7AB, 0x8761, 0xE7AC, 0x875A, 0xE7AD, 0x8751, 0xE7AE, 0x875E, 0xE7AF, 0x876D, 0xE7B0, 0x876A, 0xE7B1, 0x8750, + 0xE7B2, 0x874E, 0xE7B3, 0x875F, 0xE7B4, 0x875D, 0xE7B5, 0x876F, 0xE7B6, 0x876C, 0xE7B7, 0x877A, 0xE7B8, 0x876E, 0xE7B9, 0x875C, + 0xE7BA, 0x8765, 0xE7BB, 0x874F, 0xE7BC, 0x877B, 0xE7BD, 0x8775, 0xE7BE, 0x8762, 0xE7BF, 0x8767, 0xE7C0, 0x8769, 0xE7C1, 0x885A, + 0xE7C2, 0x8905, 0xE7C3, 0x890C, 0xE7C4, 0x8914, 0xE7C5, 0x890B, 0xE7C6, 0x8917, 0xE7C7, 0x8918, 0xE7C8, 0x8919, 0xE7C9, 0x8906, + 0xE7CA, 0x8916, 0xE7CB, 0x8911, 0xE7CC, 0x890E, 0xE7CD, 0x8909, 0xE7CE, 0x89A2, 0xE7CF, 0x89A4, 0xE7D0, 0x89A3, 0xE7D1, 0x89ED, + 0xE7D2, 0x89F0, 0xE7D3, 0x89EC, 0xE7D4, 0x8ACF, 0xE7D5, 0x8AC6, 0xE7D6, 0x8AB8, 0xE7D7, 0x8AD3, 0xE7D8, 0x8AD1, 0xE7D9, 0x8AD4, + 0xE7DA, 0x8AD5, 0xE7DB, 0x8ABB, 0xE7DC, 0x8AD7, 0xE7DD, 0x8ABE, 0xE7DE, 0x8AC0, 0xE7DF, 0x8AC5, 0xE7E0, 0x8AD8, 0xE7E1, 0x8AC3, + 0xE7E2, 0x8ABA, 0xE7E3, 0x8ABD, 0xE7E4, 0x8AD9, 0xE7E5, 0x8C3E, 0xE7E6, 0x8C4D, 0xE7E7, 0x8C8F, 0xE7E8, 0x8CE5, 0xE7E9, 0x8CDF, + 0xE7EA, 0x8CD9, 0xE7EB, 0x8CE8, 0xE7EC, 0x8CDA, 0xE7ED, 0x8CDD, 0xE7EE, 0x8CE7, 0xE7EF, 0x8DA0, 0xE7F0, 0x8D9C, 0xE7F1, 0x8DA1, + 0xE7F2, 0x8D9B, 0xE7F3, 0x8E20, 0xE7F4, 0x8E23, 0xE7F5, 0x8E25, 0xE7F6, 0x8E24, 0xE7F7, 0x8E2E, 0xE7F8, 0x8E15, 0xE7F9, 0x8E1B, + 0xE7FA, 0x8E16, 0xE7FB, 0x8E11, 0xE7FC, 0x8E19, 0xE7FD, 0x8E26, 0xE7FE, 0x8E27, 0xE840, 0x8E14, 0xE841, 0x8E12, 0xE842, 0x8E18, + 0xE843, 0x8E13, 0xE844, 0x8E1C, 0xE845, 0x8E17, 0xE846, 0x8E1A, 0xE847, 0x8F2C, 0xE848, 0x8F24, 0xE849, 0x8F18, 0xE84A, 0x8F1A, + 0xE84B, 0x8F20, 0xE84C, 0x8F23, 0xE84D, 0x8F16, 0xE84E, 0x8F17, 0xE84F, 0x9073, 0xE850, 0x9070, 0xE851, 0x906F, 0xE852, 0x9067, + 0xE853, 0x906B, 0xE854, 0x912F, 0xE855, 0x912B, 0xE856, 0x9129, 0xE857, 0x912A, 0xE858, 0x9132, 0xE859, 0x9126, 0xE85A, 0x912E, + 0xE85B, 0x9185, 0xE85C, 0x9186, 0xE85D, 0x918A, 0xE85E, 0x9181, 0xE85F, 0x9182, 0xE860, 0x9184, 0xE861, 0x9180, 0xE862, 0x92D0, + 0xE863, 0x92C3, 0xE864, 0x92C4, 0xE865, 0x92C0, 0xE866, 0x92D9, 0xE867, 0x92B6, 0xE868, 0x92CF, 0xE869, 0x92F1, 0xE86A, 0x92DF, + 0xE86B, 0x92D8, 0xE86C, 0x92E9, 0xE86D, 0x92D7, 0xE86E, 0x92DD, 0xE86F, 0x92CC, 0xE870, 0x92EF, 0xE871, 0x92C2, 0xE872, 0x92E8, + 0xE873, 0x92CA, 0xE874, 0x92C8, 0xE875, 0x92CE, 0xE876, 0x92E6, 0xE877, 0x92CD, 0xE878, 0x92D5, 0xE879, 0x92C9, 0xE87A, 0x92E0, + 0xE87B, 0x92DE, 0xE87C, 0x92E7, 0xE87D, 0x92D1, 0xE87E, 0x92D3, 0xE8A1, 0x92B5, 0xE8A2, 0x92E1, 0xE8A3, 0x92C6, 0xE8A4, 0x92B4, + 0xE8A5, 0x957C, 0xE8A6, 0x95AC, 0xE8A7, 0x95AB, 0xE8A8, 0x95AE, 0xE8A9, 0x95B0, 0xE8AA, 0x96A4, 0xE8AB, 0x96A2, 0xE8AC, 0x96D3, + 0xE8AD, 0x9705, 0xE8AE, 0x9708, 0xE8AF, 0x9702, 0xE8B0, 0x975A, 0xE8B1, 0x978A, 0xE8B2, 0x978E, 0xE8B3, 0x9788, 0xE8B4, 0x97D0, + 0xE8B5, 0x97CF, 0xE8B6, 0x981E, 0xE8B7, 0x981D, 0xE8B8, 0x9826, 0xE8B9, 0x9829, 0xE8BA, 0x9828, 0xE8BB, 0x9820, 0xE8BC, 0x981B, + 0xE8BD, 0x9827, 0xE8BE, 0x98B2, 0xE8BF, 0x9908, 0xE8C0, 0x98FA, 0xE8C1, 0x9911, 0xE8C2, 0x9914, 0xE8C3, 0x9916, 0xE8C4, 0x9917, + 0xE8C5, 0x9915, 0xE8C6, 0x99DC, 0xE8C7, 0x99CD, 0xE8C8, 0x99CF, 0xE8C9, 0x99D3, 0xE8CA, 0x99D4, 0xE8CB, 0x99CE, 0xE8CC, 0x99C9, + 0xE8CD, 0x99D6, 0xE8CE, 0x99D8, 0xE8CF, 0x99CB, 0xE8D0, 0x99D7, 0xE8D1, 0x99CC, 0xE8D2, 0x9AB3, 0xE8D3, 0x9AEC, 0xE8D4, 0x9AEB, + 0xE8D5, 0x9AF3, 0xE8D6, 0x9AF2, 0xE8D7, 0x9AF1, 0xE8D8, 0x9B46, 0xE8D9, 0x9B43, 0xE8DA, 0x9B67, 0xE8DB, 0x9B74, 0xE8DC, 0x9B71, + 0xE8DD, 0x9B66, 0xE8DE, 0x9B76, 0xE8DF, 0x9B75, 0xE8E0, 0x9B70, 0xE8E1, 0x9B68, 0xE8E2, 0x9B64, 0xE8E3, 0x9B6C, 0xE8E4, 0x9CFC, + 0xE8E5, 0x9CFA, 0xE8E6, 0x9CFD, 0xE8E7, 0x9CFF, 0xE8E8, 0x9CF7, 0xE8E9, 0x9D07, 0xE8EA, 0x9D00, 0xE8EB, 0x9CF9, 0xE8EC, 0x9CFB, + 0xE8ED, 0x9D08, 0xE8EE, 0x9D05, 0xE8EF, 0x9D04, 0xE8F0, 0x9E83, 0xE8F1, 0x9ED3, 0xE8F2, 0x9F0F, 0xE8F3, 0x9F10, 0xE8F4, 0x511C, + 0xE8F5, 0x5113, 0xE8F6, 0x5117, 0xE8F7, 0x511A, 0xE8F8, 0x5111, 0xE8F9, 0x51DE, 0xE8FA, 0x5334, 0xE8FB, 0x53E1, 0xE8FC, 0x5670, + 0xE8FD, 0x5660, 0xE8FE, 0x566E, 0xE940, 0x5673, 0xE941, 0x5666, 0xE942, 0x5663, 0xE943, 0x566D, 0xE944, 0x5672, 0xE945, 0x565E, + 0xE946, 0x5677, 0xE947, 0x571C, 0xE948, 0x571B, 0xE949, 0x58C8, 0xE94A, 0x58BD, 0xE94B, 0x58C9, 0xE94C, 0x58BF, 0xE94D, 0x58BA, + 0xE94E, 0x58C2, 0xE94F, 0x58BC, 0xE950, 0x58C6, 0xE951, 0x5B17, 0xE952, 0x5B19, 0xE953, 0x5B1B, 0xE954, 0x5B21, 0xE955, 0x5B14, + 0xE956, 0x5B13, 0xE957, 0x5B10, 0xE958, 0x5B16, 0xE959, 0x5B28, 0xE95A, 0x5B1A, 0xE95B, 0x5B20, 0xE95C, 0x5B1E, 0xE95D, 0x5BEF, + 0xE95E, 0x5DAC, 0xE95F, 0x5DB1, 0xE960, 0x5DA9, 0xE961, 0x5DA7, 0xE962, 0x5DB5, 0xE963, 0x5DB0, 0xE964, 0x5DAE, 0xE965, 0x5DAA, + 0xE966, 0x5DA8, 0xE967, 0x5DB2, 0xE968, 0x5DAD, 0xE969, 0x5DAF, 0xE96A, 0x5DB4, 0xE96B, 0x5E67, 0xE96C, 0x5E68, 0xE96D, 0x5E66, + 0xE96E, 0x5E6F, 0xE96F, 0x5EE9, 0xE970, 0x5EE7, 0xE971, 0x5EE6, 0xE972, 0x5EE8, 0xE973, 0x5EE5, 0xE974, 0x5F4B, 0xE975, 0x5FBC, + 0xE976, 0x619D, 0xE977, 0x61A8, 0xE978, 0x6196, 0xE979, 0x61C5, 0xE97A, 0x61B4, 0xE97B, 0x61C6, 0xE97C, 0x61C1, 0xE97D, 0x61CC, + 0xE97E, 0x61BA, 0xE9A1, 0x61BF, 0xE9A2, 0x61B8, 0xE9A3, 0x618C, 0xE9A4, 0x64D7, 0xE9A5, 0x64D6, 0xE9A6, 0x64D0, 0xE9A7, 0x64CF, + 0xE9A8, 0x64C9, 0xE9A9, 0x64BD, 0xE9AA, 0x6489, 0xE9AB, 0x64C3, 0xE9AC, 0x64DB, 0xE9AD, 0x64F3, 0xE9AE, 0x64D9, 0xE9AF, 0x6533, + 0xE9B0, 0x657F, 0xE9B1, 0x657C, 0xE9B2, 0x65A2, 0xE9B3, 0x66C8, 0xE9B4, 0x66BE, 0xE9B5, 0x66C0, 0xE9B6, 0x66CA, 0xE9B7, 0x66CB, + 0xE9B8, 0x66CF, 0xE9B9, 0x66BD, 0xE9BA, 0x66BB, 0xE9BB, 0x66BA, 0xE9BC, 0x66CC, 0xE9BD, 0x6723, 0xE9BE, 0x6A34, 0xE9BF, 0x6A66, + 0xE9C0, 0x6A49, 0xE9C1, 0x6A67, 0xE9C2, 0x6A32, 0xE9C3, 0x6A68, 0xE9C4, 0x6A3E, 0xE9C5, 0x6A5D, 0xE9C6, 0x6A6D, 0xE9C7, 0x6A76, + 0xE9C8, 0x6A5B, 0xE9C9, 0x6A51, 0xE9CA, 0x6A28, 0xE9CB, 0x6A5A, 0xE9CC, 0x6A3B, 0xE9CD, 0x6A3F, 0xE9CE, 0x6A41, 0xE9CF, 0x6A6A, + 0xE9D0, 0x6A64, 0xE9D1, 0x6A50, 0xE9D2, 0x6A4F, 0xE9D3, 0x6A54, 0xE9D4, 0x6A6F, 0xE9D5, 0x6A69, 0xE9D6, 0x6A60, 0xE9D7, 0x6A3C, + 0xE9D8, 0x6A5E, 0xE9D9, 0x6A56, 0xE9DA, 0x6A55, 0xE9DB, 0x6A4D, 0xE9DC, 0x6A4E, 0xE9DD, 0x6A46, 0xE9DE, 0x6B55, 0xE9DF, 0x6B54, + 0xE9E0, 0x6B56, 0xE9E1, 0x6BA7, 0xE9E2, 0x6BAA, 0xE9E3, 0x6BAB, 0xE9E4, 0x6BC8, 0xE9E5, 0x6BC7, 0xE9E6, 0x6C04, 0xE9E7, 0x6C03, + 0xE9E8, 0x6C06, 0xE9E9, 0x6FAD, 0xE9EA, 0x6FCB, 0xE9EB, 0x6FA3, 0xE9EC, 0x6FC7, 0xE9ED, 0x6FBC, 0xE9EE, 0x6FCE, 0xE9EF, 0x6FC8, + 0xE9F0, 0x6F5E, 0xE9F1, 0x6FC4, 0xE9F2, 0x6FBD, 0xE9F3, 0x6F9E, 0xE9F4, 0x6FCA, 0xE9F5, 0x6FA8, 0xE9F6, 0x7004, 0xE9F7, 0x6FA5, + 0xE9F8, 0x6FAE, 0xE9F9, 0x6FBA, 0xE9FA, 0x6FAC, 0xE9FB, 0x6FAA, 0xE9FC, 0x6FCF, 0xE9FD, 0x6FBF, 0xE9FE, 0x6FB8, 0xEA40, 0x6FA2, + 0xEA41, 0x6FC9, 0xEA42, 0x6FAB, 0xEA43, 0x6FCD, 0xEA44, 0x6FAF, 0xEA45, 0x6FB2, 0xEA46, 0x6FB0, 0xEA47, 0x71C5, 0xEA48, 0x71C2, + 0xEA49, 0x71BF, 0xEA4A, 0x71B8, 0xEA4B, 0x71D6, 0xEA4C, 0x71C0, 0xEA4D, 0x71C1, 0xEA4E, 0x71CB, 0xEA4F, 0x71D4, 0xEA50, 0x71CA, + 0xEA51, 0x71C7, 0xEA52, 0x71CF, 0xEA53, 0x71BD, 0xEA54, 0x71D8, 0xEA55, 0x71BC, 0xEA56, 0x71C6, 0xEA57, 0x71DA, 0xEA58, 0x71DB, + 0xEA59, 0x729D, 0xEA5A, 0x729E, 0xEA5B, 0x7369, 0xEA5C, 0x7366, 0xEA5D, 0x7367, 0xEA5E, 0x736C, 0xEA5F, 0x7365, 0xEA60, 0x736B, + 0xEA61, 0x736A, 0xEA62, 0x747F, 0xEA63, 0x749A, 0xEA64, 0x74A0, 0xEA65, 0x7494, 0xEA66, 0x7492, 0xEA67, 0x7495, 0xEA68, 0x74A1, + 0xEA69, 0x750B, 0xEA6A, 0x7580, 0xEA6B, 0x762F, 0xEA6C, 0x762D, 0xEA6D, 0x7631, 0xEA6E, 0x763D, 0xEA6F, 0x7633, 0xEA70, 0x763C, + 0xEA71, 0x7635, 0xEA72, 0x7632, 0xEA73, 0x7630, 0xEA74, 0x76BB, 0xEA75, 0x76E6, 0xEA76, 0x779A, 0xEA77, 0x779D, 0xEA78, 0x77A1, + 0xEA79, 0x779C, 0xEA7A, 0x779B, 0xEA7B, 0x77A2, 0xEA7C, 0x77A3, 0xEA7D, 0x7795, 0xEA7E, 0x7799, 0xEAA1, 0x7797, 0xEAA2, 0x78DD, + 0xEAA3, 0x78E9, 0xEAA4, 0x78E5, 0xEAA5, 0x78EA, 0xEAA6, 0x78DE, 0xEAA7, 0x78E3, 0xEAA8, 0x78DB, 0xEAA9, 0x78E1, 0xEAAA, 0x78E2, + 0xEAAB, 0x78ED, 0xEAAC, 0x78DF, 0xEAAD, 0x78E0, 0xEAAE, 0x79A4, 0xEAAF, 0x7A44, 0xEAB0, 0x7A48, 0xEAB1, 0x7A47, 0xEAB2, 0x7AB6, + 0xEAB3, 0x7AB8, 0xEAB4, 0x7AB5, 0xEAB5, 0x7AB1, 0xEAB6, 0x7AB7, 0xEAB7, 0x7BDE, 0xEAB8, 0x7BE3, 0xEAB9, 0x7BE7, 0xEABA, 0x7BDD, + 0xEABB, 0x7BD5, 0xEABC, 0x7BE5, 0xEABD, 0x7BDA, 0xEABE, 0x7BE8, 0xEABF, 0x7BF9, 0xEAC0, 0x7BD4, 0xEAC1, 0x7BEA, 0xEAC2, 0x7BE2, + 0xEAC3, 0x7BDC, 0xEAC4, 0x7BEB, 0xEAC5, 0x7BD8, 0xEAC6, 0x7BDF, 0xEAC7, 0x7CD2, 0xEAC8, 0x7CD4, 0xEAC9, 0x7CD7, 0xEACA, 0x7CD0, + 0xEACB, 0x7CD1, 0xEACC, 0x7E12, 0xEACD, 0x7E21, 0xEACE, 0x7E17, 0xEACF, 0x7E0C, 0xEAD0, 0x7E1F, 0xEAD1, 0x7E20, 0xEAD2, 0x7E13, + 0xEAD3, 0x7E0E, 0xEAD4, 0x7E1C, 0xEAD5, 0x7E15, 0xEAD6, 0x7E1A, 0xEAD7, 0x7E22, 0xEAD8, 0x7E0B, 0xEAD9, 0x7E0F, 0xEADA, 0x7E16, + 0xEADB, 0x7E0D, 0xEADC, 0x7E14, 0xEADD, 0x7E25, 0xEADE, 0x7E24, 0xEADF, 0x7F43, 0xEAE0, 0x7F7B, 0xEAE1, 0x7F7C, 0xEAE2, 0x7F7A, + 0xEAE3, 0x7FB1, 0xEAE4, 0x7FEF, 0xEAE5, 0x802A, 0xEAE6, 0x8029, 0xEAE7, 0x806C, 0xEAE8, 0x81B1, 0xEAE9, 0x81A6, 0xEAEA, 0x81AE, + 0xEAEB, 0x81B9, 0xEAEC, 0x81B5, 0xEAED, 0x81AB, 0xEAEE, 0x81B0, 0xEAEF, 0x81AC, 0xEAF0, 0x81B4, 0xEAF1, 0x81B2, 0xEAF2, 0x81B7, + 0xEAF3, 0x81A7, 0xEAF4, 0x81F2, 0xEAF5, 0x8255, 0xEAF6, 0x8256, 0xEAF7, 0x8257, 0xEAF8, 0x8556, 0xEAF9, 0x8545, 0xEAFA, 0x856B, + 0xEAFB, 0x854D, 0xEAFC, 0x8553, 0xEAFD, 0x8561, 0xEAFE, 0x8558, 0xEB40, 0x8540, 0xEB41, 0x8546, 0xEB42, 0x8564, 0xEB43, 0x8541, + 0xEB44, 0x8562, 0xEB45, 0x8544, 0xEB46, 0x8551, 0xEB47, 0x8547, 0xEB48, 0x8563, 0xEB49, 0x853E, 0xEB4A, 0x855B, 0xEB4B, 0x8571, + 0xEB4C, 0x854E, 0xEB4D, 0x856E, 0xEB4E, 0x8575, 0xEB4F, 0x8555, 0xEB50, 0x8567, 0xEB51, 0x8560, 0xEB52, 0x858C, 0xEB53, 0x8566, + 0xEB54, 0x855D, 0xEB55, 0x8554, 0xEB56, 0x8565, 0xEB57, 0x856C, 0xEB58, 0x8663, 0xEB59, 0x8665, 0xEB5A, 0x8664, 0xEB5B, 0x879B, + 0xEB5C, 0x878F, 0xEB5D, 0x8797, 0xEB5E, 0x8793, 0xEB5F, 0x8792, 0xEB60, 0x8788, 0xEB61, 0x8781, 0xEB62, 0x8796, 0xEB63, 0x8798, + 0xEB64, 0x8779, 0xEB65, 0x8787, 0xEB66, 0x87A3, 0xEB67, 0x8785, 0xEB68, 0x8790, 0xEB69, 0x8791, 0xEB6A, 0x879D, 0xEB6B, 0x8784, + 0xEB6C, 0x8794, 0xEB6D, 0x879C, 0xEB6E, 0x879A, 0xEB6F, 0x8789, 0xEB70, 0x891E, 0xEB71, 0x8926, 0xEB72, 0x8930, 0xEB73, 0x892D, + 0xEB74, 0x892E, 0xEB75, 0x8927, 0xEB76, 0x8931, 0xEB77, 0x8922, 0xEB78, 0x8929, 0xEB79, 0x8923, 0xEB7A, 0x892F, 0xEB7B, 0x892C, + 0xEB7C, 0x891F, 0xEB7D, 0x89F1, 0xEB7E, 0x8AE0, 0xEBA1, 0x8AE2, 0xEBA2, 0x8AF2, 0xEBA3, 0x8AF4, 0xEBA4, 0x8AF5, 0xEBA5, 0x8ADD, + 0xEBA6, 0x8B14, 0xEBA7, 0x8AE4, 0xEBA8, 0x8ADF, 0xEBA9, 0x8AF0, 0xEBAA, 0x8AC8, 0xEBAB, 0x8ADE, 0xEBAC, 0x8AE1, 0xEBAD, 0x8AE8, + 0xEBAE, 0x8AFF, 0xEBAF, 0x8AEF, 0xEBB0, 0x8AFB, 0xEBB1, 0x8C91, 0xEBB2, 0x8C92, 0xEBB3, 0x8C90, 0xEBB4, 0x8CF5, 0xEBB5, 0x8CEE, + 0xEBB6, 0x8CF1, 0xEBB7, 0x8CF0, 0xEBB8, 0x8CF3, 0xEBB9, 0x8D6C, 0xEBBA, 0x8D6E, 0xEBBB, 0x8DA5, 0xEBBC, 0x8DA7, 0xEBBD, 0x8E33, + 0xEBBE, 0x8E3E, 0xEBBF, 0x8E38, 0xEBC0, 0x8E40, 0xEBC1, 0x8E45, 0xEBC2, 0x8E36, 0xEBC3, 0x8E3C, 0xEBC4, 0x8E3D, 0xEBC5, 0x8E41, + 0xEBC6, 0x8E30, 0xEBC7, 0x8E3F, 0xEBC8, 0x8EBD, 0xEBC9, 0x8F36, 0xEBCA, 0x8F2E, 0xEBCB, 0x8F35, 0xEBCC, 0x8F32, 0xEBCD, 0x8F39, + 0xEBCE, 0x8F37, 0xEBCF, 0x8F34, 0xEBD0, 0x9076, 0xEBD1, 0x9079, 0xEBD2, 0x907B, 0xEBD3, 0x9086, 0xEBD4, 0x90FA, 0xEBD5, 0x9133, + 0xEBD6, 0x9135, 0xEBD7, 0x9136, 0xEBD8, 0x9193, 0xEBD9, 0x9190, 0xEBDA, 0x9191, 0xEBDB, 0x918D, 0xEBDC, 0x918F, 0xEBDD, 0x9327, + 0xEBDE, 0x931E, 0xEBDF, 0x9308, 0xEBE0, 0x931F, 0xEBE1, 0x9306, 0xEBE2, 0x930F, 0xEBE3, 0x937A, 0xEBE4, 0x9338, 0xEBE5, 0x933C, + 0xEBE6, 0x931B, 0xEBE7, 0x9323, 0xEBE8, 0x9312, 0xEBE9, 0x9301, 0xEBEA, 0x9346, 0xEBEB, 0x932D, 0xEBEC, 0x930E, 0xEBED, 0x930D, + 0xEBEE, 0x92CB, 0xEBEF, 0x931D, 0xEBF0, 0x92FA, 0xEBF1, 0x9325, 0xEBF2, 0x9313, 0xEBF3, 0x92F9, 0xEBF4, 0x92F7, 0xEBF5, 0x9334, + 0xEBF6, 0x9302, 0xEBF7, 0x9324, 0xEBF8, 0x92FF, 0xEBF9, 0x9329, 0xEBFA, 0x9339, 0xEBFB, 0x9335, 0xEBFC, 0x932A, 0xEBFD, 0x9314, + 0xEBFE, 0x930C, 0xEC40, 0x930B, 0xEC41, 0x92FE, 0xEC42, 0x9309, 0xEC43, 0x9300, 0xEC44, 0x92FB, 0xEC45, 0x9316, 0xEC46, 0x95BC, + 0xEC47, 0x95CD, 0xEC48, 0x95BE, 0xEC49, 0x95B9, 0xEC4A, 0x95BA, 0xEC4B, 0x95B6, 0xEC4C, 0x95BF, 0xEC4D, 0x95B5, 0xEC4E, 0x95BD, + 0xEC4F, 0x96A9, 0xEC50, 0x96D4, 0xEC51, 0x970B, 0xEC52, 0x9712, 0xEC53, 0x9710, 0xEC54, 0x9799, 0xEC55, 0x9797, 0xEC56, 0x9794, + 0xEC57, 0x97F0, 0xEC58, 0x97F8, 0xEC59, 0x9835, 0xEC5A, 0x982F, 0xEC5B, 0x9832, 0xEC5C, 0x9924, 0xEC5D, 0x991F, 0xEC5E, 0x9927, + 0xEC5F, 0x9929, 0xEC60, 0x999E, 0xEC61, 0x99EE, 0xEC62, 0x99EC, 0xEC63, 0x99E5, 0xEC64, 0x99E4, 0xEC65, 0x99F0, 0xEC66, 0x99E3, + 0xEC67, 0x99EA, 0xEC68, 0x99E9, 0xEC69, 0x99E7, 0xEC6A, 0x9AB9, 0xEC6B, 0x9ABF, 0xEC6C, 0x9AB4, 0xEC6D, 0x9ABB, 0xEC6E, 0x9AF6, + 0xEC6F, 0x9AFA, 0xEC70, 0x9AF9, 0xEC71, 0x9AF7, 0xEC72, 0x9B33, 0xEC73, 0x9B80, 0xEC74, 0x9B85, 0xEC75, 0x9B87, 0xEC76, 0x9B7C, + 0xEC77, 0x9B7E, 0xEC78, 0x9B7B, 0xEC79, 0x9B82, 0xEC7A, 0x9B93, 0xEC7B, 0x9B92, 0xEC7C, 0x9B90, 0xEC7D, 0x9B7A, 0xEC7E, 0x9B95, + 0xECA1, 0x9B7D, 0xECA2, 0x9B88, 0xECA3, 0x9D25, 0xECA4, 0x9D17, 0xECA5, 0x9D20, 0xECA6, 0x9D1E, 0xECA7, 0x9D14, 0xECA8, 0x9D29, + 0xECA9, 0x9D1D, 0xECAA, 0x9D18, 0xECAB, 0x9D22, 0xECAC, 0x9D10, 0xECAD, 0x9D19, 0xECAE, 0x9D1F, 0xECAF, 0x9E88, 0xECB0, 0x9E86, + 0xECB1, 0x9E87, 0xECB2, 0x9EAE, 0xECB3, 0x9EAD, 0xECB4, 0x9ED5, 0xECB5, 0x9ED6, 0xECB6, 0x9EFA, 0xECB7, 0x9F12, 0xECB8, 0x9F3D, + 0xECB9, 0x5126, 0xECBA, 0x5125, 0xECBB, 0x5122, 0xECBC, 0x5124, 0xECBD, 0x5120, 0xECBE, 0x5129, 0xECBF, 0x52F4, 0xECC0, 0x5693, + 0xECC1, 0x568C, 0xECC2, 0x568D, 0xECC3, 0x5686, 0xECC4, 0x5684, 0xECC5, 0x5683, 0xECC6, 0x567E, 0xECC7, 0x5682, 0xECC8, 0x567F, + 0xECC9, 0x5681, 0xECCA, 0x58D6, 0xECCB, 0x58D4, 0xECCC, 0x58CF, 0xECCD, 0x58D2, 0xECCE, 0x5B2D, 0xECCF, 0x5B25, 0xECD0, 0x5B32, + 0xECD1, 0x5B23, 0xECD2, 0x5B2C, 0xECD3, 0x5B27, 0xECD4, 0x5B26, 0xECD5, 0x5B2F, 0xECD6, 0x5B2E, 0xECD7, 0x5B7B, 0xECD8, 0x5BF1, + 0xECD9, 0x5BF2, 0xECDA, 0x5DB7, 0xECDB, 0x5E6C, 0xECDC, 0x5E6A, 0xECDD, 0x5FBE, 0xECDE, 0x5FBB, 0xECDF, 0x61C3, 0xECE0, 0x61B5, + 0xECE1, 0x61BC, 0xECE2, 0x61E7, 0xECE3, 0x61E0, 0xECE4, 0x61E5, 0xECE5, 0x61E4, 0xECE6, 0x61E8, 0xECE7, 0x61DE, 0xECE8, 0x64EF, + 0xECE9, 0x64E9, 0xECEA, 0x64E3, 0xECEB, 0x64EB, 0xECEC, 0x64E4, 0xECED, 0x64E8, 0xECEE, 0x6581, 0xECEF, 0x6580, 0xECF0, 0x65B6, + 0xECF1, 0x65DA, 0xECF2, 0x66D2, 0xECF3, 0x6A8D, 0xECF4, 0x6A96, 0xECF5, 0x6A81, 0xECF6, 0x6AA5, 0xECF7, 0x6A89, 0xECF8, 0x6A9F, + 0xECF9, 0x6A9B, 0xECFA, 0x6AA1, 0xECFB, 0x6A9E, 0xECFC, 0x6A87, 0xECFD, 0x6A93, 0xECFE, 0x6A8E, 0xED40, 0x6A95, 0xED41, 0x6A83, + 0xED42, 0x6AA8, 0xED43, 0x6AA4, 0xED44, 0x6A91, 0xED45, 0x6A7F, 0xED46, 0x6AA6, 0xED47, 0x6A9A, 0xED48, 0x6A85, 0xED49, 0x6A8C, + 0xED4A, 0x6A92, 0xED4B, 0x6B5B, 0xED4C, 0x6BAD, 0xED4D, 0x6C09, 0xED4E, 0x6FCC, 0xED4F, 0x6FA9, 0xED50, 0x6FF4, 0xED51, 0x6FD4, + 0xED52, 0x6FE3, 0xED53, 0x6FDC, 0xED54, 0x6FED, 0xED55, 0x6FE7, 0xED56, 0x6FE6, 0xED57, 0x6FDE, 0xED58, 0x6FF2, 0xED59, 0x6FDD, + 0xED5A, 0x6FE2, 0xED5B, 0x6FE8, 0xED5C, 0x71E1, 0xED5D, 0x71F1, 0xED5E, 0x71E8, 0xED5F, 0x71F2, 0xED60, 0x71E4, 0xED61, 0x71F0, + 0xED62, 0x71E2, 0xED63, 0x7373, 0xED64, 0x736E, 0xED65, 0x736F, 0xED66, 0x7497, 0xED67, 0x74B2, 0xED68, 0x74AB, 0xED69, 0x7490, + 0xED6A, 0x74AA, 0xED6B, 0x74AD, 0xED6C, 0x74B1, 0xED6D, 0x74A5, 0xED6E, 0x74AF, 0xED6F, 0x7510, 0xED70, 0x7511, 0xED71, 0x7512, + 0xED72, 0x750F, 0xED73, 0x7584, 0xED74, 0x7643, 0xED75, 0x7648, 0xED76, 0x7649, 0xED77, 0x7647, 0xED78, 0x76A4, 0xED79, 0x76E9, + 0xED7A, 0x77B5, 0xED7B, 0x77AB, 0xED7C, 0x77B2, 0xED7D, 0x77B7, 0xED7E, 0x77B6, 0xEDA1, 0x77B4, 0xEDA2, 0x77B1, 0xEDA3, 0x77A8, + 0xEDA4, 0x77F0, 0xEDA5, 0x78F3, 0xEDA6, 0x78FD, 0xEDA7, 0x7902, 0xEDA8, 0x78FB, 0xEDA9, 0x78FC, 0xEDAA, 0x78F2, 0xEDAB, 0x7905, + 0xEDAC, 0x78F9, 0xEDAD, 0x78FE, 0xEDAE, 0x7904, 0xEDAF, 0x79AB, 0xEDB0, 0x79A8, 0xEDB1, 0x7A5C, 0xEDB2, 0x7A5B, 0xEDB3, 0x7A56, + 0xEDB4, 0x7A58, 0xEDB5, 0x7A54, 0xEDB6, 0x7A5A, 0xEDB7, 0x7ABE, 0xEDB8, 0x7AC0, 0xEDB9, 0x7AC1, 0xEDBA, 0x7C05, 0xEDBB, 0x7C0F, + 0xEDBC, 0x7BF2, 0xEDBD, 0x7C00, 0xEDBE, 0x7BFF, 0xEDBF, 0x7BFB, 0xEDC0, 0x7C0E, 0xEDC1, 0x7BF4, 0xEDC2, 0x7C0B, 0xEDC3, 0x7BF3, + 0xEDC4, 0x7C02, 0xEDC5, 0x7C09, 0xEDC6, 0x7C03, 0xEDC7, 0x7C01, 0xEDC8, 0x7BF8, 0xEDC9, 0x7BFD, 0xEDCA, 0x7C06, 0xEDCB, 0x7BF0, + 0xEDCC, 0x7BF1, 0xEDCD, 0x7C10, 0xEDCE, 0x7C0A, 0xEDCF, 0x7CE8, 0xEDD0, 0x7E2D, 0xEDD1, 0x7E3C, 0xEDD2, 0x7E42, 0xEDD3, 0x7E33, + 0xEDD4, 0x9848, 0xEDD5, 0x7E38, 0xEDD6, 0x7E2A, 0xEDD7, 0x7E49, 0xEDD8, 0x7E40, 0xEDD9, 0x7E47, 0xEDDA, 0x7E29, 0xEDDB, 0x7E4C, + 0xEDDC, 0x7E30, 0xEDDD, 0x7E3B, 0xEDDE, 0x7E36, 0xEDDF, 0x7E44, 0xEDE0, 0x7E3A, 0xEDE1, 0x7F45, 0xEDE2, 0x7F7F, 0xEDE3, 0x7F7E, + 0xEDE4, 0x7F7D, 0xEDE5, 0x7FF4, 0xEDE6, 0x7FF2, 0xEDE7, 0x802C, 0xEDE8, 0x81BB, 0xEDE9, 0x81C4, 0xEDEA, 0x81CC, 0xEDEB, 0x81CA, + 0xEDEC, 0x81C5, 0xEDED, 0x81C7, 0xEDEE, 0x81BC, 0xEDEF, 0x81E9, 0xEDF0, 0x825B, 0xEDF1, 0x825A, 0xEDF2, 0x825C, 0xEDF3, 0x8583, + 0xEDF4, 0x8580, 0xEDF5, 0x858F, 0xEDF6, 0x85A7, 0xEDF7, 0x8595, 0xEDF8, 0x85A0, 0xEDF9, 0x858B, 0xEDFA, 0x85A3, 0xEDFB, 0x857B, + 0xEDFC, 0x85A4, 0xEDFD, 0x859A, 0xEDFE, 0x859E, 0xEE40, 0x8577, 0xEE41, 0x857C, 0xEE42, 0x8589, 0xEE43, 0x85A1, 0xEE44, 0x857A, + 0xEE45, 0x8578, 0xEE46, 0x8557, 0xEE47, 0x858E, 0xEE48, 0x8596, 0xEE49, 0x8586, 0xEE4A, 0x858D, 0xEE4B, 0x8599, 0xEE4C, 0x859D, + 0xEE4D, 0x8581, 0xEE4E, 0x85A2, 0xEE4F, 0x8582, 0xEE50, 0x8588, 0xEE51, 0x8585, 0xEE52, 0x8579, 0xEE53, 0x8576, 0xEE54, 0x8598, + 0xEE55, 0x8590, 0xEE56, 0x859F, 0xEE57, 0x8668, 0xEE58, 0x87BE, 0xEE59, 0x87AA, 0xEE5A, 0x87AD, 0xEE5B, 0x87C5, 0xEE5C, 0x87B0, + 0xEE5D, 0x87AC, 0xEE5E, 0x87B9, 0xEE5F, 0x87B5, 0xEE60, 0x87BC, 0xEE61, 0x87AE, 0xEE62, 0x87C9, 0xEE63, 0x87C3, 0xEE64, 0x87C2, + 0xEE65, 0x87CC, 0xEE66, 0x87B7, 0xEE67, 0x87AF, 0xEE68, 0x87C4, 0xEE69, 0x87CA, 0xEE6A, 0x87B4, 0xEE6B, 0x87B6, 0xEE6C, 0x87BF, + 0xEE6D, 0x87B8, 0xEE6E, 0x87BD, 0xEE6F, 0x87DE, 0xEE70, 0x87B2, 0xEE71, 0x8935, 0xEE72, 0x8933, 0xEE73, 0x893C, 0xEE74, 0x893E, + 0xEE75, 0x8941, 0xEE76, 0x8952, 0xEE77, 0x8937, 0xEE78, 0x8942, 0xEE79, 0x89AD, 0xEE7A, 0x89AF, 0xEE7B, 0x89AE, 0xEE7C, 0x89F2, + 0xEE7D, 0x89F3, 0xEE7E, 0x8B1E, 0xEEA1, 0x8B18, 0xEEA2, 0x8B16, 0xEEA3, 0x8B11, 0xEEA4, 0x8B05, 0xEEA5, 0x8B0B, 0xEEA6, 0x8B22, + 0xEEA7, 0x8B0F, 0xEEA8, 0x8B12, 0xEEA9, 0x8B15, 0xEEAA, 0x8B07, 0xEEAB, 0x8B0D, 0xEEAC, 0x8B08, 0xEEAD, 0x8B06, 0xEEAE, 0x8B1C, + 0xEEAF, 0x8B13, 0xEEB0, 0x8B1A, 0xEEB1, 0x8C4F, 0xEEB2, 0x8C70, 0xEEB3, 0x8C72, 0xEEB4, 0x8C71, 0xEEB5, 0x8C6F, 0xEEB6, 0x8C95, + 0xEEB7, 0x8C94, 0xEEB8, 0x8CF9, 0xEEB9, 0x8D6F, 0xEEBA, 0x8E4E, 0xEEBB, 0x8E4D, 0xEEBC, 0x8E53, 0xEEBD, 0x8E50, 0xEEBE, 0x8E4C, + 0xEEBF, 0x8E47, 0xEEC0, 0x8F43, 0xEEC1, 0x8F40, 0xEEC2, 0x9085, 0xEEC3, 0x907E, 0xEEC4, 0x9138, 0xEEC5, 0x919A, 0xEEC6, 0x91A2, + 0xEEC7, 0x919B, 0xEEC8, 0x9199, 0xEEC9, 0x919F, 0xEECA, 0x91A1, 0xEECB, 0x919D, 0xEECC, 0x91A0, 0xEECD, 0x93A1, 0xEECE, 0x9383, + 0xEECF, 0x93AF, 0xEED0, 0x9364, 0xEED1, 0x9356, 0xEED2, 0x9347, 0xEED3, 0x937C, 0xEED4, 0x9358, 0xEED5, 0x935C, 0xEED6, 0x9376, + 0xEED7, 0x9349, 0xEED8, 0x9350, 0xEED9, 0x9351, 0xEEDA, 0x9360, 0xEEDB, 0x936D, 0xEEDC, 0x938F, 0xEEDD, 0x934C, 0xEEDE, 0x936A, + 0xEEDF, 0x9379, 0xEEE0, 0x9357, 0xEEE1, 0x9355, 0xEEE2, 0x9352, 0xEEE3, 0x934F, 0xEEE4, 0x9371, 0xEEE5, 0x9377, 0xEEE6, 0x937B, + 0xEEE7, 0x9361, 0xEEE8, 0x935E, 0xEEE9, 0x9363, 0xEEEA, 0x9367, 0xEEEB, 0x9380, 0xEEEC, 0x934E, 0xEEED, 0x9359, 0xEEEE, 0x95C7, + 0xEEEF, 0x95C0, 0xEEF0, 0x95C9, 0xEEF1, 0x95C3, 0xEEF2, 0x95C5, 0xEEF3, 0x95B7, 0xEEF4, 0x96AE, 0xEEF5, 0x96B0, 0xEEF6, 0x96AC, + 0xEEF7, 0x9720, 0xEEF8, 0x971F, 0xEEF9, 0x9718, 0xEEFA, 0x971D, 0xEEFB, 0x9719, 0xEEFC, 0x979A, 0xEEFD, 0x97A1, 0xEEFE, 0x979C, + 0xEF40, 0x979E, 0xEF41, 0x979D, 0xEF42, 0x97D5, 0xEF43, 0x97D4, 0xEF44, 0x97F1, 0xEF45, 0x9841, 0xEF46, 0x9844, 0xEF47, 0x984A, + 0xEF48, 0x9849, 0xEF49, 0x9845, 0xEF4A, 0x9843, 0xEF4B, 0x9925, 0xEF4C, 0x992B, 0xEF4D, 0x992C, 0xEF4E, 0x992A, 0xEF4F, 0x9933, + 0xEF50, 0x9932, 0xEF51, 0x992F, 0xEF52, 0x992D, 0xEF53, 0x9931, 0xEF54, 0x9930, 0xEF55, 0x9998, 0xEF56, 0x99A3, 0xEF57, 0x99A1, + 0xEF58, 0x9A02, 0xEF59, 0x99FA, 0xEF5A, 0x99F4, 0xEF5B, 0x99F7, 0xEF5C, 0x99F9, 0xEF5D, 0x99F8, 0xEF5E, 0x99F6, 0xEF5F, 0x99FB, + 0xEF60, 0x99FD, 0xEF61, 0x99FE, 0xEF62, 0x99FC, 0xEF63, 0x9A03, 0xEF64, 0x9ABE, 0xEF65, 0x9AFE, 0xEF66, 0x9AFD, 0xEF67, 0x9B01, + 0xEF68, 0x9AFC, 0xEF69, 0x9B48, 0xEF6A, 0x9B9A, 0xEF6B, 0x9BA8, 0xEF6C, 0x9B9E, 0xEF6D, 0x9B9B, 0xEF6E, 0x9BA6, 0xEF6F, 0x9BA1, + 0xEF70, 0x9BA5, 0xEF71, 0x9BA4, 0xEF72, 0x9B86, 0xEF73, 0x9BA2, 0xEF74, 0x9BA0, 0xEF75, 0x9BAF, 0xEF76, 0x9D33, 0xEF77, 0x9D41, + 0xEF78, 0x9D67, 0xEF79, 0x9D36, 0xEF7A, 0x9D2E, 0xEF7B, 0x9D2F, 0xEF7C, 0x9D31, 0xEF7D, 0x9D38, 0xEF7E, 0x9D30, 0xEFA1, 0x9D45, + 0xEFA2, 0x9D42, 0xEFA3, 0x9D43, 0xEFA4, 0x9D3E, 0xEFA5, 0x9D37, 0xEFA6, 0x9D40, 0xEFA7, 0x9D3D, 0xEFA8, 0x7FF5, 0xEFA9, 0x9D2D, + 0xEFAA, 0x9E8A, 0xEFAB, 0x9E89, 0xEFAC, 0x9E8D, 0xEFAD, 0x9EB0, 0xEFAE, 0x9EC8, 0xEFAF, 0x9EDA, 0xEFB0, 0x9EFB, 0xEFB1, 0x9EFF, + 0xEFB2, 0x9F24, 0xEFB3, 0x9F23, 0xEFB4, 0x9F22, 0xEFB5, 0x9F54, 0xEFB6, 0x9FA0, 0xEFB7, 0x5131, 0xEFB8, 0x512D, 0xEFB9, 0x512E, + 0xEFBA, 0x5698, 0xEFBB, 0x569C, 0xEFBC, 0x5697, 0xEFBD, 0x569A, 0xEFBE, 0x569D, 0xEFBF, 0x5699, 0xEFC0, 0x5970, 0xEFC1, 0x5B3C, + 0xEFC2, 0x5C69, 0xEFC3, 0x5C6A, 0xEFC4, 0x5DC0, 0xEFC5, 0x5E6D, 0xEFC6, 0x5E6E, 0xEFC7, 0x61D8, 0xEFC8, 0x61DF, 0xEFC9, 0x61ED, + 0xEFCA, 0x61EE, 0xEFCB, 0x61F1, 0xEFCC, 0x61EA, 0xEFCD, 0x61F0, 0xEFCE, 0x61EB, 0xEFCF, 0x61D6, 0xEFD0, 0x61E9, 0xEFD1, 0x64FF, + 0xEFD2, 0x6504, 0xEFD3, 0x64FD, 0xEFD4, 0x64F8, 0xEFD5, 0x6501, 0xEFD6, 0x6503, 0xEFD7, 0x64FC, 0xEFD8, 0x6594, 0xEFD9, 0x65DB, + 0xEFDA, 0x66DA, 0xEFDB, 0x66DB, 0xEFDC, 0x66D8, 0xEFDD, 0x6AC5, 0xEFDE, 0x6AB9, 0xEFDF, 0x6ABD, 0xEFE0, 0x6AE1, 0xEFE1, 0x6AC6, + 0xEFE2, 0x6ABA, 0xEFE3, 0x6AB6, 0xEFE4, 0x6AB7, 0xEFE5, 0x6AC7, 0xEFE6, 0x6AB4, 0xEFE7, 0x6AAD, 0xEFE8, 0x6B5E, 0xEFE9, 0x6BC9, + 0xEFEA, 0x6C0B, 0xEFEB, 0x7007, 0xEFEC, 0x700C, 0xEFED, 0x700D, 0xEFEE, 0x7001, 0xEFEF, 0x7005, 0xEFF0, 0x7014, 0xEFF1, 0x700E, + 0xEFF2, 0x6FFF, 0xEFF3, 0x7000, 0xEFF4, 0x6FFB, 0xEFF5, 0x7026, 0xEFF6, 0x6FFC, 0xEFF7, 0x6FF7, 0xEFF8, 0x700A, 0xEFF9, 0x7201, + 0xEFFA, 0x71FF, 0xEFFB, 0x71F9, 0xEFFC, 0x7203, 0xEFFD, 0x71FD, 0xEFFE, 0x7376, 0xF040, 0x74B8, 0xF041, 0x74C0, 0xF042, 0x74B5, + 0xF043, 0x74C1, 0xF044, 0x74BE, 0xF045, 0x74B6, 0xF046, 0x74BB, 0xF047, 0x74C2, 0xF048, 0x7514, 0xF049, 0x7513, 0xF04A, 0x765C, + 0xF04B, 0x7664, 0xF04C, 0x7659, 0xF04D, 0x7650, 0xF04E, 0x7653, 0xF04F, 0x7657, 0xF050, 0x765A, 0xF051, 0x76A6, 0xF052, 0x76BD, + 0xF053, 0x76EC, 0xF054, 0x77C2, 0xF055, 0x77BA, 0xF056, 0x78FF, 0xF057, 0x790C, 0xF058, 0x7913, 0xF059, 0x7914, 0xF05A, 0x7909, + 0xF05B, 0x7910, 0xF05C, 0x7912, 0xF05D, 0x7911, 0xF05E, 0x79AD, 0xF05F, 0x79AC, 0xF060, 0x7A5F, 0xF061, 0x7C1C, 0xF062, 0x7C29, + 0xF063, 0x7C19, 0xF064, 0x7C20, 0xF065, 0x7C1F, 0xF066, 0x7C2D, 0xF067, 0x7C1D, 0xF068, 0x7C26, 0xF069, 0x7C28, 0xF06A, 0x7C22, + 0xF06B, 0x7C25, 0xF06C, 0x7C30, 0xF06D, 0x7E5C, 0xF06E, 0x7E50, 0xF06F, 0x7E56, 0xF070, 0x7E63, 0xF071, 0x7E58, 0xF072, 0x7E62, + 0xF073, 0x7E5F, 0xF074, 0x7E51, 0xF075, 0x7E60, 0xF076, 0x7E57, 0xF077, 0x7E53, 0xF078, 0x7FB5, 0xF079, 0x7FB3, 0xF07A, 0x7FF7, + 0xF07B, 0x7FF8, 0xF07C, 0x8075, 0xF07D, 0x81D1, 0xF07E, 0x81D2, 0xF0A1, 0x81D0, 0xF0A2, 0x825F, 0xF0A3, 0x825E, 0xF0A4, 0x85B4, + 0xF0A5, 0x85C6, 0xF0A6, 0x85C0, 0xF0A7, 0x85C3, 0xF0A8, 0x85C2, 0xF0A9, 0x85B3, 0xF0AA, 0x85B5, 0xF0AB, 0x85BD, 0xF0AC, 0x85C7, + 0xF0AD, 0x85C4, 0xF0AE, 0x85BF, 0xF0AF, 0x85CB, 0xF0B0, 0x85CE, 0xF0B1, 0x85C8, 0xF0B2, 0x85C5, 0xF0B3, 0x85B1, 0xF0B4, 0x85B6, + 0xF0B5, 0x85D2, 0xF0B6, 0x8624, 0xF0B7, 0x85B8, 0xF0B8, 0x85B7, 0xF0B9, 0x85BE, 0xF0BA, 0x8669, 0xF0BB, 0x87E7, 0xF0BC, 0x87E6, + 0xF0BD, 0x87E2, 0xF0BE, 0x87DB, 0xF0BF, 0x87EB, 0xF0C0, 0x87EA, 0xF0C1, 0x87E5, 0xF0C2, 0x87DF, 0xF0C3, 0x87F3, 0xF0C4, 0x87E4, + 0xF0C5, 0x87D4, 0xF0C6, 0x87DC, 0xF0C7, 0x87D3, 0xF0C8, 0x87ED, 0xF0C9, 0x87D8, 0xF0CA, 0x87E3, 0xF0CB, 0x87A4, 0xF0CC, 0x87D7, + 0xF0CD, 0x87D9, 0xF0CE, 0x8801, 0xF0CF, 0x87F4, 0xF0D0, 0x87E8, 0xF0D1, 0x87DD, 0xF0D2, 0x8953, 0xF0D3, 0x894B, 0xF0D4, 0x894F, + 0xF0D5, 0x894C, 0xF0D6, 0x8946, 0xF0D7, 0x8950, 0xF0D8, 0x8951, 0xF0D9, 0x8949, 0xF0DA, 0x8B2A, 0xF0DB, 0x8B27, 0xF0DC, 0x8B23, + 0xF0DD, 0x8B33, 0xF0DE, 0x8B30, 0xF0DF, 0x8B35, 0xF0E0, 0x8B47, 0xF0E1, 0x8B2F, 0xF0E2, 0x8B3C, 0xF0E3, 0x8B3E, 0xF0E4, 0x8B31, + 0xF0E5, 0x8B25, 0xF0E6, 0x8B37, 0xF0E7, 0x8B26, 0xF0E8, 0x8B36, 0xF0E9, 0x8B2E, 0xF0EA, 0x8B24, 0xF0EB, 0x8B3B, 0xF0EC, 0x8B3D, + 0xF0ED, 0x8B3A, 0xF0EE, 0x8C42, 0xF0EF, 0x8C75, 0xF0F0, 0x8C99, 0xF0F1, 0x8C98, 0xF0F2, 0x8C97, 0xF0F3, 0x8CFE, 0xF0F4, 0x8D04, + 0xF0F5, 0x8D02, 0xF0F6, 0x8D00, 0xF0F7, 0x8E5C, 0xF0F8, 0x8E62, 0xF0F9, 0x8E60, 0xF0FA, 0x8E57, 0xF0FB, 0x8E56, 0xF0FC, 0x8E5E, + 0xF0FD, 0x8E65, 0xF0FE, 0x8E67, 0xF140, 0x8E5B, 0xF141, 0x8E5A, 0xF142, 0x8E61, 0xF143, 0x8E5D, 0xF144, 0x8E69, 0xF145, 0x8E54, + 0xF146, 0x8F46, 0xF147, 0x8F47, 0xF148, 0x8F48, 0xF149, 0x8F4B, 0xF14A, 0x9128, 0xF14B, 0x913A, 0xF14C, 0x913B, 0xF14D, 0x913E, + 0xF14E, 0x91A8, 0xF14F, 0x91A5, 0xF150, 0x91A7, 0xF151, 0x91AF, 0xF152, 0x91AA, 0xF153, 0x93B5, 0xF154, 0x938C, 0xF155, 0x9392, + 0xF156, 0x93B7, 0xF157, 0x939B, 0xF158, 0x939D, 0xF159, 0x9389, 0xF15A, 0x93A7, 0xF15B, 0x938E, 0xF15C, 0x93AA, 0xF15D, 0x939E, + 0xF15E, 0x93A6, 0xF15F, 0x9395, 0xF160, 0x9388, 0xF161, 0x9399, 0xF162, 0x939F, 0xF163, 0x938D, 0xF164, 0x93B1, 0xF165, 0x9391, + 0xF166, 0x93B2, 0xF167, 0x93A4, 0xF168, 0x93A8, 0xF169, 0x93B4, 0xF16A, 0x93A3, 0xF16B, 0x93A5, 0xF16C, 0x95D2, 0xF16D, 0x95D3, + 0xF16E, 0x95D1, 0xF16F, 0x96B3, 0xF170, 0x96D7, 0xF171, 0x96DA, 0xF172, 0x5DC2, 0xF173, 0x96DF, 0xF174, 0x96D8, 0xF175, 0x96DD, + 0xF176, 0x9723, 0xF177, 0x9722, 0xF178, 0x9725, 0xF179, 0x97AC, 0xF17A, 0x97AE, 0xF17B, 0x97A8, 0xF17C, 0x97AB, 0xF17D, 0x97A4, + 0xF17E, 0x97AA, 0xF1A1, 0x97A2, 0xF1A2, 0x97A5, 0xF1A3, 0x97D7, 0xF1A4, 0x97D9, 0xF1A5, 0x97D6, 0xF1A6, 0x97D8, 0xF1A7, 0x97FA, + 0xF1A8, 0x9850, 0xF1A9, 0x9851, 0xF1AA, 0x9852, 0xF1AB, 0x98B8, 0xF1AC, 0x9941, 0xF1AD, 0x993C, 0xF1AE, 0x993A, 0xF1AF, 0x9A0F, + 0xF1B0, 0x9A0B, 0xF1B1, 0x9A09, 0xF1B2, 0x9A0D, 0xF1B3, 0x9A04, 0xF1B4, 0x9A11, 0xF1B5, 0x9A0A, 0xF1B6, 0x9A05, 0xF1B7, 0x9A07, + 0xF1B8, 0x9A06, 0xF1B9, 0x9AC0, 0xF1BA, 0x9ADC, 0xF1BB, 0x9B08, 0xF1BC, 0x9B04, 0xF1BD, 0x9B05, 0xF1BE, 0x9B29, 0xF1BF, 0x9B35, + 0xF1C0, 0x9B4A, 0xF1C1, 0x9B4C, 0xF1C2, 0x9B4B, 0xF1C3, 0x9BC7, 0xF1C4, 0x9BC6, 0xF1C5, 0x9BC3, 0xF1C6, 0x9BBF, 0xF1C7, 0x9BC1, + 0xF1C8, 0x9BB5, 0xF1C9, 0x9BB8, 0xF1CA, 0x9BD3, 0xF1CB, 0x9BB6, 0xF1CC, 0x9BC4, 0xF1CD, 0x9BB9, 0xF1CE, 0x9BBD, 0xF1CF, 0x9D5C, + 0xF1D0, 0x9D53, 0xF1D1, 0x9D4F, 0xF1D2, 0x9D4A, 0xF1D3, 0x9D5B, 0xF1D4, 0x9D4B, 0xF1D5, 0x9D59, 0xF1D6, 0x9D56, 0xF1D7, 0x9D4C, + 0xF1D8, 0x9D57, 0xF1D9, 0x9D52, 0xF1DA, 0x9D54, 0xF1DB, 0x9D5F, 0xF1DC, 0x9D58, 0xF1DD, 0x9D5A, 0xF1DE, 0x9E8E, 0xF1DF, 0x9E8C, + 0xF1E0, 0x9EDF, 0xF1E1, 0x9F01, 0xF1E2, 0x9F00, 0xF1E3, 0x9F16, 0xF1E4, 0x9F25, 0xF1E5, 0x9F2B, 0xF1E6, 0x9F2A, 0xF1E7, 0x9F29, + 0xF1E8, 0x9F28, 0xF1E9, 0x9F4C, 0xF1EA, 0x9F55, 0xF1EB, 0x5134, 0xF1EC, 0x5135, 0xF1ED, 0x5296, 0xF1EE, 0x52F7, 0xF1EF, 0x53B4, + 0xF1F0, 0x56AB, 0xF1F1, 0x56AD, 0xF1F2, 0x56A6, 0xF1F3, 0x56A7, 0xF1F4, 0x56AA, 0xF1F5, 0x56AC, 0xF1F6, 0x58DA, 0xF1F7, 0x58DD, + 0xF1F8, 0x58DB, 0xF1F9, 0x5912, 0xF1FA, 0x5B3D, 0xF1FB, 0x5B3E, 0xF1FC, 0x5B3F, 0xF1FD, 0x5DC3, 0xF1FE, 0x5E70, 0xF240, 0x5FBF, + 0xF241, 0x61FB, 0xF242, 0x6507, 0xF243, 0x6510, 0xF244, 0x650D, 0xF245, 0x6509, 0xF246, 0x650C, 0xF247, 0x650E, 0xF248, 0x6584, + 0xF249, 0x65DE, 0xF24A, 0x65DD, 0xF24B, 0x66DE, 0xF24C, 0x6AE7, 0xF24D, 0x6AE0, 0xF24E, 0x6ACC, 0xF24F, 0x6AD1, 0xF250, 0x6AD9, + 0xF251, 0x6ACB, 0xF252, 0x6ADF, 0xF253, 0x6ADC, 0xF254, 0x6AD0, 0xF255, 0x6AEB, 0xF256, 0x6ACF, 0xF257, 0x6ACD, 0xF258, 0x6ADE, + 0xF259, 0x6B60, 0xF25A, 0x6BB0, 0xF25B, 0x6C0C, 0xF25C, 0x7019, 0xF25D, 0x7027, 0xF25E, 0x7020, 0xF25F, 0x7016, 0xF260, 0x702B, + 0xF261, 0x7021, 0xF262, 0x7022, 0xF263, 0x7023, 0xF264, 0x7029, 0xF265, 0x7017, 0xF266, 0x7024, 0xF267, 0x701C, 0xF268, 0x702A, + 0xF269, 0x720C, 0xF26A, 0x720A, 0xF26B, 0x7207, 0xF26C, 0x7202, 0xF26D, 0x7205, 0xF26E, 0x72A5, 0xF26F, 0x72A6, 0xF270, 0x72A4, + 0xF271, 0x72A3, 0xF272, 0x72A1, 0xF273, 0x74CB, 0xF274, 0x74C5, 0xF275, 0x74B7, 0xF276, 0x74C3, 0xF277, 0x7516, 0xF278, 0x7660, + 0xF279, 0x77C9, 0xF27A, 0x77CA, 0xF27B, 0x77C4, 0xF27C, 0x77F1, 0xF27D, 0x791D, 0xF27E, 0x791B, 0xF2A1, 0x7921, 0xF2A2, 0x791C, + 0xF2A3, 0x7917, 0xF2A4, 0x791E, 0xF2A5, 0x79B0, 0xF2A6, 0x7A67, 0xF2A7, 0x7A68, 0xF2A8, 0x7C33, 0xF2A9, 0x7C3C, 0xF2AA, 0x7C39, + 0xF2AB, 0x7C2C, 0xF2AC, 0x7C3B, 0xF2AD, 0x7CEC, 0xF2AE, 0x7CEA, 0xF2AF, 0x7E76, 0xF2B0, 0x7E75, 0xF2B1, 0x7E78, 0xF2B2, 0x7E70, + 0xF2B3, 0x7E77, 0xF2B4, 0x7E6F, 0xF2B5, 0x7E7A, 0xF2B6, 0x7E72, 0xF2B7, 0x7E74, 0xF2B8, 0x7E68, 0xF2B9, 0x7F4B, 0xF2BA, 0x7F4A, + 0xF2BB, 0x7F83, 0xF2BC, 0x7F86, 0xF2BD, 0x7FB7, 0xF2BE, 0x7FFD, 0xF2BF, 0x7FFE, 0xF2C0, 0x8078, 0xF2C1, 0x81D7, 0xF2C2, 0x81D5, + 0xF2C3, 0x8264, 0xF2C4, 0x8261, 0xF2C5, 0x8263, 0xF2C6, 0x85EB, 0xF2C7, 0x85F1, 0xF2C8, 0x85ED, 0xF2C9, 0x85D9, 0xF2CA, 0x85E1, + 0xF2CB, 0x85E8, 0xF2CC, 0x85DA, 0xF2CD, 0x85D7, 0xF2CE, 0x85EC, 0xF2CF, 0x85F2, 0xF2D0, 0x85F8, 0xF2D1, 0x85D8, 0xF2D2, 0x85DF, + 0xF2D3, 0x85E3, 0xF2D4, 0x85DC, 0xF2D5, 0x85D1, 0xF2D6, 0x85F0, 0xF2D7, 0x85E6, 0xF2D8, 0x85EF, 0xF2D9, 0x85DE, 0xF2DA, 0x85E2, + 0xF2DB, 0x8800, 0xF2DC, 0x87FA, 0xF2DD, 0x8803, 0xF2DE, 0x87F6, 0xF2DF, 0x87F7, 0xF2E0, 0x8809, 0xF2E1, 0x880C, 0xF2E2, 0x880B, + 0xF2E3, 0x8806, 0xF2E4, 0x87FC, 0xF2E5, 0x8808, 0xF2E6, 0x87FF, 0xF2E7, 0x880A, 0xF2E8, 0x8802, 0xF2E9, 0x8962, 0xF2EA, 0x895A, + 0xF2EB, 0x895B, 0xF2EC, 0x8957, 0xF2ED, 0x8961, 0xF2EE, 0x895C, 0xF2EF, 0x8958, 0xF2F0, 0x895D, 0xF2F1, 0x8959, 0xF2F2, 0x8988, + 0xF2F3, 0x89B7, 0xF2F4, 0x89B6, 0xF2F5, 0x89F6, 0xF2F6, 0x8B50, 0xF2F7, 0x8B48, 0xF2F8, 0x8B4A, 0xF2F9, 0x8B40, 0xF2FA, 0x8B53, + 0xF2FB, 0x8B56, 0xF2FC, 0x8B54, 0xF2FD, 0x8B4B, 0xF2FE, 0x8B55, 0xF340, 0x8B51, 0xF341, 0x8B42, 0xF342, 0x8B52, 0xF343, 0x8B57, + 0xF344, 0x8C43, 0xF345, 0x8C77, 0xF346, 0x8C76, 0xF347, 0x8C9A, 0xF348, 0x8D06, 0xF349, 0x8D07, 0xF34A, 0x8D09, 0xF34B, 0x8DAC, + 0xF34C, 0x8DAA, 0xF34D, 0x8DAD, 0xF34E, 0x8DAB, 0xF34F, 0x8E6D, 0xF350, 0x8E78, 0xF351, 0x8E73, 0xF352, 0x8E6A, 0xF353, 0x8E6F, + 0xF354, 0x8E7B, 0xF355, 0x8EC2, 0xF356, 0x8F52, 0xF357, 0x8F51, 0xF358, 0x8F4F, 0xF359, 0x8F50, 0xF35A, 0x8F53, 0xF35B, 0x8FB4, + 0xF35C, 0x9140, 0xF35D, 0x913F, 0xF35E, 0x91B0, 0xF35F, 0x91AD, 0xF360, 0x93DE, 0xF361, 0x93C7, 0xF362, 0x93CF, 0xF363, 0x93C2, + 0xF364, 0x93DA, 0xF365, 0x93D0, 0xF366, 0x93F9, 0xF367, 0x93EC, 0xF368, 0x93CC, 0xF369, 0x93D9, 0xF36A, 0x93A9, 0xF36B, 0x93E6, + 0xF36C, 0x93CA, 0xF36D, 0x93D4, 0xF36E, 0x93EE, 0xF36F, 0x93E3, 0xF370, 0x93D5, 0xF371, 0x93C4, 0xF372, 0x93CE, 0xF373, 0x93C0, + 0xF374, 0x93D2, 0xF375, 0x93E7, 0xF376, 0x957D, 0xF377, 0x95DA, 0xF378, 0x95DB, 0xF379, 0x96E1, 0xF37A, 0x9729, 0xF37B, 0x972B, + 0xF37C, 0x972C, 0xF37D, 0x9728, 0xF37E, 0x9726, 0xF3A1, 0x97B3, 0xF3A2, 0x97B7, 0xF3A3, 0x97B6, 0xF3A4, 0x97DD, 0xF3A5, 0x97DE, + 0xF3A6, 0x97DF, 0xF3A7, 0x985C, 0xF3A8, 0x9859, 0xF3A9, 0x985D, 0xF3AA, 0x9857, 0xF3AB, 0x98BF, 0xF3AC, 0x98BD, 0xF3AD, 0x98BB, + 0xF3AE, 0x98BE, 0xF3AF, 0x9948, 0xF3B0, 0x9947, 0xF3B1, 0x9943, 0xF3B2, 0x99A6, 0xF3B3, 0x99A7, 0xF3B4, 0x9A1A, 0xF3B5, 0x9A15, + 0xF3B6, 0x9A25, 0xF3B7, 0x9A1D, 0xF3B8, 0x9A24, 0xF3B9, 0x9A1B, 0xF3BA, 0x9A22, 0xF3BB, 0x9A20, 0xF3BC, 0x9A27, 0xF3BD, 0x9A23, + 0xF3BE, 0x9A1E, 0xF3BF, 0x9A1C, 0xF3C0, 0x9A14, 0xF3C1, 0x9AC2, 0xF3C2, 0x9B0B, 0xF3C3, 0x9B0A, 0xF3C4, 0x9B0E, 0xF3C5, 0x9B0C, + 0xF3C6, 0x9B37, 0xF3C7, 0x9BEA, 0xF3C8, 0x9BEB, 0xF3C9, 0x9BE0, 0xF3CA, 0x9BDE, 0xF3CB, 0x9BE4, 0xF3CC, 0x9BE6, 0xF3CD, 0x9BE2, + 0xF3CE, 0x9BF0, 0xF3CF, 0x9BD4, 0xF3D0, 0x9BD7, 0xF3D1, 0x9BEC, 0xF3D2, 0x9BDC, 0xF3D3, 0x9BD9, 0xF3D4, 0x9BE5, 0xF3D5, 0x9BD5, + 0xF3D6, 0x9BE1, 0xF3D7, 0x9BDA, 0xF3D8, 0x9D77, 0xF3D9, 0x9D81, 0xF3DA, 0x9D8A, 0xF3DB, 0x9D84, 0xF3DC, 0x9D88, 0xF3DD, 0x9D71, + 0xF3DE, 0x9D80, 0xF3DF, 0x9D78, 0xF3E0, 0x9D86, 0xF3E1, 0x9D8B, 0xF3E2, 0x9D8C, 0xF3E3, 0x9D7D, 0xF3E4, 0x9D6B, 0xF3E5, 0x9D74, + 0xF3E6, 0x9D75, 0xF3E7, 0x9D70, 0xF3E8, 0x9D69, 0xF3E9, 0x9D85, 0xF3EA, 0x9D73, 0xF3EB, 0x9D7B, 0xF3EC, 0x9D82, 0xF3ED, 0x9D6F, + 0xF3EE, 0x9D79, 0xF3EF, 0x9D7F, 0xF3F0, 0x9D87, 0xF3F1, 0x9D68, 0xF3F2, 0x9E94, 0xF3F3, 0x9E91, 0xF3F4, 0x9EC0, 0xF3F5, 0x9EFC, + 0xF3F6, 0x9F2D, 0xF3F7, 0x9F40, 0xF3F8, 0x9F41, 0xF3F9, 0x9F4D, 0xF3FA, 0x9F56, 0xF3FB, 0x9F57, 0xF3FC, 0x9F58, 0xF3FD, 0x5337, + 0xF3FE, 0x56B2, 0xF440, 0x56B5, 0xF441, 0x56B3, 0xF442, 0x58E3, 0xF443, 0x5B45, 0xF444, 0x5DC6, 0xF445, 0x5DC7, 0xF446, 0x5EEE, + 0xF447, 0x5EEF, 0xF448, 0x5FC0, 0xF449, 0x5FC1, 0xF44A, 0x61F9, 0xF44B, 0x6517, 0xF44C, 0x6516, 0xF44D, 0x6515, 0xF44E, 0x6513, + 0xF44F, 0x65DF, 0xF450, 0x66E8, 0xF451, 0x66E3, 0xF452, 0x66E4, 0xF453, 0x6AF3, 0xF454, 0x6AF0, 0xF455, 0x6AEA, 0xF456, 0x6AE8, + 0xF457, 0x6AF9, 0xF458, 0x6AF1, 0xF459, 0x6AEE, 0xF45A, 0x6AEF, 0xF45B, 0x703C, 0xF45C, 0x7035, 0xF45D, 0x702F, 0xF45E, 0x7037, + 0xF45F, 0x7034, 0xF460, 0x7031, 0xF461, 0x7042, 0xF462, 0x7038, 0xF463, 0x703F, 0xF464, 0x703A, 0xF465, 0x7039, 0xF466, 0x7040, + 0xF467, 0x703B, 0xF468, 0x7033, 0xF469, 0x7041, 0xF46A, 0x7213, 0xF46B, 0x7214, 0xF46C, 0x72A8, 0xF46D, 0x737D, 0xF46E, 0x737C, + 0xF46F, 0x74BA, 0xF470, 0x76AB, 0xF471, 0x76AA, 0xF472, 0x76BE, 0xF473, 0x76ED, 0xF474, 0x77CC, 0xF475, 0x77CE, 0xF476, 0x77CF, + 0xF477, 0x77CD, 0xF478, 0x77F2, 0xF479, 0x7925, 0xF47A, 0x7923, 0xF47B, 0x7927, 0xF47C, 0x7928, 0xF47D, 0x7924, 0xF47E, 0x7929, + 0xF4A1, 0x79B2, 0xF4A2, 0x7A6E, 0xF4A3, 0x7A6C, 0xF4A4, 0x7A6D, 0xF4A5, 0x7AF7, 0xF4A6, 0x7C49, 0xF4A7, 0x7C48, 0xF4A8, 0x7C4A, + 0xF4A9, 0x7C47, 0xF4AA, 0x7C45, 0xF4AB, 0x7CEE, 0xF4AC, 0x7E7B, 0xF4AD, 0x7E7E, 0xF4AE, 0x7E81, 0xF4AF, 0x7E80, 0xF4B0, 0x7FBA, + 0xF4B1, 0x7FFF, 0xF4B2, 0x8079, 0xF4B3, 0x81DB, 0xF4B4, 0x81D9, 0xF4B5, 0x820B, 0xF4B6, 0x8268, 0xF4B7, 0x8269, 0xF4B8, 0x8622, + 0xF4B9, 0x85FF, 0xF4BA, 0x8601, 0xF4BB, 0x85FE, 0xF4BC, 0x861B, 0xF4BD, 0x8600, 0xF4BE, 0x85F6, 0xF4BF, 0x8604, 0xF4C0, 0x8609, + 0xF4C1, 0x8605, 0xF4C2, 0x860C, 0xF4C3, 0x85FD, 0xF4C4, 0x8819, 0xF4C5, 0x8810, 0xF4C6, 0x8811, 0xF4C7, 0x8817, 0xF4C8, 0x8813, + 0xF4C9, 0x8816, 0xF4CA, 0x8963, 0xF4CB, 0x8966, 0xF4CC, 0x89B9, 0xF4CD, 0x89F7, 0xF4CE, 0x8B60, 0xF4CF, 0x8B6A, 0xF4D0, 0x8B5D, + 0xF4D1, 0x8B68, 0xF4D2, 0x8B63, 0xF4D3, 0x8B65, 0xF4D4, 0x8B67, 0xF4D5, 0x8B6D, 0xF4D6, 0x8DAE, 0xF4D7, 0x8E86, 0xF4D8, 0x8E88, + 0xF4D9, 0x8E84, 0xF4DA, 0x8F59, 0xF4DB, 0x8F56, 0xF4DC, 0x8F57, 0xF4DD, 0x8F55, 0xF4DE, 0x8F58, 0xF4DF, 0x8F5A, 0xF4E0, 0x908D, + 0xF4E1, 0x9143, 0xF4E2, 0x9141, 0xF4E3, 0x91B7, 0xF4E4, 0x91B5, 0xF4E5, 0x91B2, 0xF4E6, 0x91B3, 0xF4E7, 0x940B, 0xF4E8, 0x9413, + 0xF4E9, 0x93FB, 0xF4EA, 0x9420, 0xF4EB, 0x940F, 0xF4EC, 0x9414, 0xF4ED, 0x93FE, 0xF4EE, 0x9415, 0xF4EF, 0x9410, 0xF4F0, 0x9428, + 0xF4F1, 0x9419, 0xF4F2, 0x940D, 0xF4F3, 0x93F5, 0xF4F4, 0x9400, 0xF4F5, 0x93F7, 0xF4F6, 0x9407, 0xF4F7, 0x940E, 0xF4F8, 0x9416, + 0xF4F9, 0x9412, 0xF4FA, 0x93FA, 0xF4FB, 0x9409, 0xF4FC, 0x93F8, 0xF4FD, 0x940A, 0xF4FE, 0x93FF, 0xF540, 0x93FC, 0xF541, 0x940C, + 0xF542, 0x93F6, 0xF543, 0x9411, 0xF544, 0x9406, 0xF545, 0x95DE, 0xF546, 0x95E0, 0xF547, 0x95DF, 0xF548, 0x972E, 0xF549, 0x972F, + 0xF54A, 0x97B9, 0xF54B, 0x97BB, 0xF54C, 0x97FD, 0xF54D, 0x97FE, 0xF54E, 0x9860, 0xF54F, 0x9862, 0xF550, 0x9863, 0xF551, 0x985F, + 0xF552, 0x98C1, 0xF553, 0x98C2, 0xF554, 0x9950, 0xF555, 0x994E, 0xF556, 0x9959, 0xF557, 0x994C, 0xF558, 0x994B, 0xF559, 0x9953, + 0xF55A, 0x9A32, 0xF55B, 0x9A34, 0xF55C, 0x9A31, 0xF55D, 0x9A2C, 0xF55E, 0x9A2A, 0xF55F, 0x9A36, 0xF560, 0x9A29, 0xF561, 0x9A2E, + 0xF562, 0x9A38, 0xF563, 0x9A2D, 0xF564, 0x9AC7, 0xF565, 0x9ACA, 0xF566, 0x9AC6, 0xF567, 0x9B10, 0xF568, 0x9B12, 0xF569, 0x9B11, + 0xF56A, 0x9C0B, 0xF56B, 0x9C08, 0xF56C, 0x9BF7, 0xF56D, 0x9C05, 0xF56E, 0x9C12, 0xF56F, 0x9BF8, 0xF570, 0x9C40, 0xF571, 0x9C07, + 0xF572, 0x9C0E, 0xF573, 0x9C06, 0xF574, 0x9C17, 0xF575, 0x9C14, 0xF576, 0x9C09, 0xF577, 0x9D9F, 0xF578, 0x9D99, 0xF579, 0x9DA4, + 0xF57A, 0x9D9D, 0xF57B, 0x9D92, 0xF57C, 0x9D98, 0xF57D, 0x9D90, 0xF57E, 0x9D9B, 0xF5A1, 0x9DA0, 0xF5A2, 0x9D94, 0xF5A3, 0x9D9C, + 0xF5A4, 0x9DAA, 0xF5A5, 0x9D97, 0xF5A6, 0x9DA1, 0xF5A7, 0x9D9A, 0xF5A8, 0x9DA2, 0xF5A9, 0x9DA8, 0xF5AA, 0x9D9E, 0xF5AB, 0x9DA3, + 0xF5AC, 0x9DBF, 0xF5AD, 0x9DA9, 0xF5AE, 0x9D96, 0xF5AF, 0x9DA6, 0xF5B0, 0x9DA7, 0xF5B1, 0x9E99, 0xF5B2, 0x9E9B, 0xF5B3, 0x9E9A, + 0xF5B4, 0x9EE5, 0xF5B5, 0x9EE4, 0xF5B6, 0x9EE7, 0xF5B7, 0x9EE6, 0xF5B8, 0x9F30, 0xF5B9, 0x9F2E, 0xF5BA, 0x9F5B, 0xF5BB, 0x9F60, + 0xF5BC, 0x9F5E, 0xF5BD, 0x9F5D, 0xF5BE, 0x9F59, 0xF5BF, 0x9F91, 0xF5C0, 0x513A, 0xF5C1, 0x5139, 0xF5C2, 0x5298, 0xF5C3, 0x5297, + 0xF5C4, 0x56C3, 0xF5C5, 0x56BD, 0xF5C6, 0x56BE, 0xF5C7, 0x5B48, 0xF5C8, 0x5B47, 0xF5C9, 0x5DCB, 0xF5CA, 0x5DCF, 0xF5CB, 0x5EF1, + 0xF5CC, 0x61FD, 0xF5CD, 0x651B, 0xF5CE, 0x6B02, 0xF5CF, 0x6AFC, 0xF5D0, 0x6B03, 0xF5D1, 0x6AF8, 0xF5D2, 0x6B00, 0xF5D3, 0x7043, + 0xF5D4, 0x7044, 0xF5D5, 0x704A, 0xF5D6, 0x7048, 0xF5D7, 0x7049, 0xF5D8, 0x7045, 0xF5D9, 0x7046, 0xF5DA, 0x721D, 0xF5DB, 0x721A, + 0xF5DC, 0x7219, 0xF5DD, 0x737E, 0xF5DE, 0x7517, 0xF5DF, 0x766A, 0xF5E0, 0x77D0, 0xF5E1, 0x792D, 0xF5E2, 0x7931, 0xF5E3, 0x792F, + 0xF5E4, 0x7C54, 0xF5E5, 0x7C53, 0xF5E6, 0x7CF2, 0xF5E7, 0x7E8A, 0xF5E8, 0x7E87, 0xF5E9, 0x7E88, 0xF5EA, 0x7E8B, 0xF5EB, 0x7E86, + 0xF5EC, 0x7E8D, 0xF5ED, 0x7F4D, 0xF5EE, 0x7FBB, 0xF5EF, 0x8030, 0xF5F0, 0x81DD, 0xF5F1, 0x8618, 0xF5F2, 0x862A, 0xF5F3, 0x8626, + 0xF5F4, 0x861F, 0xF5F5, 0x8623, 0xF5F6, 0x861C, 0xF5F7, 0x8619, 0xF5F8, 0x8627, 0xF5F9, 0x862E, 0xF5FA, 0x8621, 0xF5FB, 0x8620, + 0xF5FC, 0x8629, 0xF5FD, 0x861E, 0xF5FE, 0x8625, 0xF640, 0x8829, 0xF641, 0x881D, 0xF642, 0x881B, 0xF643, 0x8820, 0xF644, 0x8824, + 0xF645, 0x881C, 0xF646, 0x882B, 0xF647, 0x884A, 0xF648, 0x896D, 0xF649, 0x8969, 0xF64A, 0x896E, 0xF64B, 0x896B, 0xF64C, 0x89FA, + 0xF64D, 0x8B79, 0xF64E, 0x8B78, 0xF64F, 0x8B45, 0xF650, 0x8B7A, 0xF651, 0x8B7B, 0xF652, 0x8D10, 0xF653, 0x8D14, 0xF654, 0x8DAF, + 0xF655, 0x8E8E, 0xF656, 0x8E8C, 0xF657, 0x8F5E, 0xF658, 0x8F5B, 0xF659, 0x8F5D, 0xF65A, 0x9146, 0xF65B, 0x9144, 0xF65C, 0x9145, + 0xF65D, 0x91B9, 0xF65E, 0x943F, 0xF65F, 0x943B, 0xF660, 0x9436, 0xF661, 0x9429, 0xF662, 0x943D, 0xF663, 0x943C, 0xF664, 0x9430, + 0xF665, 0x9439, 0xF666, 0x942A, 0xF667, 0x9437, 0xF668, 0x942C, 0xF669, 0x9440, 0xF66A, 0x9431, 0xF66B, 0x95E5, 0xF66C, 0x95E4, + 0xF66D, 0x95E3, 0xF66E, 0x9735, 0xF66F, 0x973A, 0xF670, 0x97BF, 0xF671, 0x97E1, 0xF672, 0x9864, 0xF673, 0x98C9, 0xF674, 0x98C6, + 0xF675, 0x98C0, 0xF676, 0x9958, 0xF677, 0x9956, 0xF678, 0x9A39, 0xF679, 0x9A3D, 0xF67A, 0x9A46, 0xF67B, 0x9A44, 0xF67C, 0x9A42, + 0xF67D, 0x9A41, 0xF67E, 0x9A3A, 0xF6A1, 0x9A3F, 0xF6A2, 0x9ACD, 0xF6A3, 0x9B15, 0xF6A4, 0x9B17, 0xF6A5, 0x9B18, 0xF6A6, 0x9B16, + 0xF6A7, 0x9B3A, 0xF6A8, 0x9B52, 0xF6A9, 0x9C2B, 0xF6AA, 0x9C1D, 0xF6AB, 0x9C1C, 0xF6AC, 0x9C2C, 0xF6AD, 0x9C23, 0xF6AE, 0x9C28, + 0xF6AF, 0x9C29, 0xF6B0, 0x9C24, 0xF6B1, 0x9C21, 0xF6B2, 0x9DB7, 0xF6B3, 0x9DB6, 0xF6B4, 0x9DBC, 0xF6B5, 0x9DC1, 0xF6B6, 0x9DC7, + 0xF6B7, 0x9DCA, 0xF6B8, 0x9DCF, 0xF6B9, 0x9DBE, 0xF6BA, 0x9DC5, 0xF6BB, 0x9DC3, 0xF6BC, 0x9DBB, 0xF6BD, 0x9DB5, 0xF6BE, 0x9DCE, + 0xF6BF, 0x9DB9, 0xF6C0, 0x9DBA, 0xF6C1, 0x9DAC, 0xF6C2, 0x9DC8, 0xF6C3, 0x9DB1, 0xF6C4, 0x9DAD, 0xF6C5, 0x9DCC, 0xF6C6, 0x9DB3, + 0xF6C7, 0x9DCD, 0xF6C8, 0x9DB2, 0xF6C9, 0x9E7A, 0xF6CA, 0x9E9C, 0xF6CB, 0x9EEB, 0xF6CC, 0x9EEE, 0xF6CD, 0x9EED, 0xF6CE, 0x9F1B, + 0xF6CF, 0x9F18, 0xF6D0, 0x9F1A, 0xF6D1, 0x9F31, 0xF6D2, 0x9F4E, 0xF6D3, 0x9F65, 0xF6D4, 0x9F64, 0xF6D5, 0x9F92, 0xF6D6, 0x4EB9, + 0xF6D7, 0x56C6, 0xF6D8, 0x56C5, 0xF6D9, 0x56CB, 0xF6DA, 0x5971, 0xF6DB, 0x5B4B, 0xF6DC, 0x5B4C, 0xF6DD, 0x5DD5, 0xF6DE, 0x5DD1, + 0xF6DF, 0x5EF2, 0xF6E0, 0x6521, 0xF6E1, 0x6520, 0xF6E2, 0x6526, 0xF6E3, 0x6522, 0xF6E4, 0x6B0B, 0xF6E5, 0x6B08, 0xF6E6, 0x6B09, + 0xF6E7, 0x6C0D, 0xF6E8, 0x7055, 0xF6E9, 0x7056, 0xF6EA, 0x7057, 0xF6EB, 0x7052, 0xF6EC, 0x721E, 0xF6ED, 0x721F, 0xF6EE, 0x72A9, + 0xF6EF, 0x737F, 0xF6F0, 0x74D8, 0xF6F1, 0x74D5, 0xF6F2, 0x74D9, 0xF6F3, 0x74D7, 0xF6F4, 0x766D, 0xF6F5, 0x76AD, 0xF6F6, 0x7935, + 0xF6F7, 0x79B4, 0xF6F8, 0x7A70, 0xF6F9, 0x7A71, 0xF6FA, 0x7C57, 0xF6FB, 0x7C5C, 0xF6FC, 0x7C59, 0xF6FD, 0x7C5B, 0xF6FE, 0x7C5A, + 0xF740, 0x7CF4, 0xF741, 0x7CF1, 0xF742, 0x7E91, 0xF743, 0x7F4F, 0xF744, 0x7F87, 0xF745, 0x81DE, 0xF746, 0x826B, 0xF747, 0x8634, + 0xF748, 0x8635, 0xF749, 0x8633, 0xF74A, 0x862C, 0xF74B, 0x8632, 0xF74C, 0x8636, 0xF74D, 0x882C, 0xF74E, 0x8828, 0xF74F, 0x8826, + 0xF750, 0x882A, 0xF751, 0x8825, 0xF752, 0x8971, 0xF753, 0x89BF, 0xF754, 0x89BE, 0xF755, 0x89FB, 0xF756, 0x8B7E, 0xF757, 0x8B84, + 0xF758, 0x8B82, 0xF759, 0x8B86, 0xF75A, 0x8B85, 0xF75B, 0x8B7F, 0xF75C, 0x8D15, 0xF75D, 0x8E95, 0xF75E, 0x8E94, 0xF75F, 0x8E9A, + 0xF760, 0x8E92, 0xF761, 0x8E90, 0xF762, 0x8E96, 0xF763, 0x8E97, 0xF764, 0x8F60, 0xF765, 0x8F62, 0xF766, 0x9147, 0xF767, 0x944C, + 0xF768, 0x9450, 0xF769, 0x944A, 0xF76A, 0x944B, 0xF76B, 0x944F, 0xF76C, 0x9447, 0xF76D, 0x9445, 0xF76E, 0x9448, 0xF76F, 0x9449, + 0xF770, 0x9446, 0xF771, 0x973F, 0xF772, 0x97E3, 0xF773, 0x986A, 0xF774, 0x9869, 0xF775, 0x98CB, 0xF776, 0x9954, 0xF777, 0x995B, + 0xF778, 0x9A4E, 0xF779, 0x9A53, 0xF77A, 0x9A54, 0xF77B, 0x9A4C, 0xF77C, 0x9A4F, 0xF77D, 0x9A48, 0xF77E, 0x9A4A, 0xF7A1, 0x9A49, + 0xF7A2, 0x9A52, 0xF7A3, 0x9A50, 0xF7A4, 0x9AD0, 0xF7A5, 0x9B19, 0xF7A6, 0x9B2B, 0xF7A7, 0x9B3B, 0xF7A8, 0x9B56, 0xF7A9, 0x9B55, + 0xF7AA, 0x9C46, 0xF7AB, 0x9C48, 0xF7AC, 0x9C3F, 0xF7AD, 0x9C44, 0xF7AE, 0x9C39, 0xF7AF, 0x9C33, 0xF7B0, 0x9C41, 0xF7B1, 0x9C3C, + 0xF7B2, 0x9C37, 0xF7B3, 0x9C34, 0xF7B4, 0x9C32, 0xF7B5, 0x9C3D, 0xF7B6, 0x9C36, 0xF7B7, 0x9DDB, 0xF7B8, 0x9DD2, 0xF7B9, 0x9DDE, + 0xF7BA, 0x9DDA, 0xF7BB, 0x9DCB, 0xF7BC, 0x9DD0, 0xF7BD, 0x9DDC, 0xF7BE, 0x9DD1, 0xF7BF, 0x9DDF, 0xF7C0, 0x9DE9, 0xF7C1, 0x9DD9, + 0xF7C2, 0x9DD8, 0xF7C3, 0x9DD6, 0xF7C4, 0x9DF5, 0xF7C5, 0x9DD5, 0xF7C6, 0x9DDD, 0xF7C7, 0x9EB6, 0xF7C8, 0x9EF0, 0xF7C9, 0x9F35, + 0xF7CA, 0x9F33, 0xF7CB, 0x9F32, 0xF7CC, 0x9F42, 0xF7CD, 0x9F6B, 0xF7CE, 0x9F95, 0xF7CF, 0x9FA2, 0xF7D0, 0x513D, 0xF7D1, 0x5299, + 0xF7D2, 0x58E8, 0xF7D3, 0x58E7, 0xF7D4, 0x5972, 0xF7D5, 0x5B4D, 0xF7D6, 0x5DD8, 0xF7D7, 0x882F, 0xF7D8, 0x5F4F, 0xF7D9, 0x6201, + 0xF7DA, 0x6203, 0xF7DB, 0x6204, 0xF7DC, 0x6529, 0xF7DD, 0x6525, 0xF7DE, 0x6596, 0xF7DF, 0x66EB, 0xF7E0, 0x6B11, 0xF7E1, 0x6B12, + 0xF7E2, 0x6B0F, 0xF7E3, 0x6BCA, 0xF7E4, 0x705B, 0xF7E5, 0x705A, 0xF7E6, 0x7222, 0xF7E7, 0x7382, 0xF7E8, 0x7381, 0xF7E9, 0x7383, + 0xF7EA, 0x7670, 0xF7EB, 0x77D4, 0xF7EC, 0x7C67, 0xF7ED, 0x7C66, 0xF7EE, 0x7E95, 0xF7EF, 0x826C, 0xF7F0, 0x863A, 0xF7F1, 0x8640, + 0xF7F2, 0x8639, 0xF7F3, 0x863C, 0xF7F4, 0x8631, 0xF7F5, 0x863B, 0xF7F6, 0x863E, 0xF7F7, 0x8830, 0xF7F8, 0x8832, 0xF7F9, 0x882E, + 0xF7FA, 0x8833, 0xF7FB, 0x8976, 0xF7FC, 0x8974, 0xF7FD, 0x8973, 0xF7FE, 0x89FE, 0xF840, 0x8B8C, 0xF841, 0x8B8E, 0xF842, 0x8B8B, + 0xF843, 0x8B88, 0xF844, 0x8C45, 0xF845, 0x8D19, 0xF846, 0x8E98, 0xF847, 0x8F64, 0xF848, 0x8F63, 0xF849, 0x91BC, 0xF84A, 0x9462, + 0xF84B, 0x9455, 0xF84C, 0x945D, 0xF84D, 0x9457, 0xF84E, 0x945E, 0xF84F, 0x97C4, 0xF850, 0x97C5, 0xF851, 0x9800, 0xF852, 0x9A56, + 0xF853, 0x9A59, 0xF854, 0x9B1E, 0xF855, 0x9B1F, 0xF856, 0x9B20, 0xF857, 0x9C52, 0xF858, 0x9C58, 0xF859, 0x9C50, 0xF85A, 0x9C4A, + 0xF85B, 0x9C4D, 0xF85C, 0x9C4B, 0xF85D, 0x9C55, 0xF85E, 0x9C59, 0xF85F, 0x9C4C, 0xF860, 0x9C4E, 0xF861, 0x9DFB, 0xF862, 0x9DF7, + 0xF863, 0x9DEF, 0xF864, 0x9DE3, 0xF865, 0x9DEB, 0xF866, 0x9DF8, 0xF867, 0x9DE4, 0xF868, 0x9DF6, 0xF869, 0x9DE1, 0xF86A, 0x9DEE, + 0xF86B, 0x9DE6, 0xF86C, 0x9DF2, 0xF86D, 0x9DF0, 0xF86E, 0x9DE2, 0xF86F, 0x9DEC, 0xF870, 0x9DF4, 0xF871, 0x9DF3, 0xF872, 0x9DE8, + 0xF873, 0x9DED, 0xF874, 0x9EC2, 0xF875, 0x9ED0, 0xF876, 0x9EF2, 0xF877, 0x9EF3, 0xF878, 0x9F06, 0xF879, 0x9F1C, 0xF87A, 0x9F38, + 0xF87B, 0x9F37, 0xF87C, 0x9F36, 0xF87D, 0x9F43, 0xF87E, 0x9F4F, 0xF8A1, 0x9F71, 0xF8A2, 0x9F70, 0xF8A3, 0x9F6E, 0xF8A4, 0x9F6F, + 0xF8A5, 0x56D3, 0xF8A6, 0x56CD, 0xF8A7, 0x5B4E, 0xF8A8, 0x5C6D, 0xF8A9, 0x652D, 0xF8AA, 0x66ED, 0xF8AB, 0x66EE, 0xF8AC, 0x6B13, + 0xF8AD, 0x705F, 0xF8AE, 0x7061, 0xF8AF, 0x705D, 0xF8B0, 0x7060, 0xF8B1, 0x7223, 0xF8B2, 0x74DB, 0xF8B3, 0x74E5, 0xF8B4, 0x77D5, + 0xF8B5, 0x7938, 0xF8B6, 0x79B7, 0xF8B7, 0x79B6, 0xF8B8, 0x7C6A, 0xF8B9, 0x7E97, 0xF8BA, 0x7F89, 0xF8BB, 0x826D, 0xF8BC, 0x8643, + 0xF8BD, 0x8838, 0xF8BE, 0x8837, 0xF8BF, 0x8835, 0xF8C0, 0x884B, 0xF8C1, 0x8B94, 0xF8C2, 0x8B95, 0xF8C3, 0x8E9E, 0xF8C4, 0x8E9F, + 0xF8C5, 0x8EA0, 0xF8C6, 0x8E9D, 0xF8C7, 0x91BE, 0xF8C8, 0x91BD, 0xF8C9, 0x91C2, 0xF8CA, 0x946B, 0xF8CB, 0x9468, 0xF8CC, 0x9469, + 0xF8CD, 0x96E5, 0xF8CE, 0x9746, 0xF8CF, 0x9743, 0xF8D0, 0x9747, 0xF8D1, 0x97C7, 0xF8D2, 0x97E5, 0xF8D3, 0x9A5E, 0xF8D4, 0x9AD5, + 0xF8D5, 0x9B59, 0xF8D6, 0x9C63, 0xF8D7, 0x9C67, 0xF8D8, 0x9C66, 0xF8D9, 0x9C62, 0xF8DA, 0x9C5E, 0xF8DB, 0x9C60, 0xF8DC, 0x9E02, + 0xF8DD, 0x9DFE, 0xF8DE, 0x9E07, 0xF8DF, 0x9E03, 0xF8E0, 0x9E06, 0xF8E1, 0x9E05, 0xF8E2, 0x9E00, 0xF8E3, 0x9E01, 0xF8E4, 0x9E09, + 0xF8E5, 0x9DFF, 0xF8E6, 0x9DFD, 0xF8E7, 0x9E04, 0xF8E8, 0x9EA0, 0xF8E9, 0x9F1E, 0xF8EA, 0x9F46, 0xF8EB, 0x9F74, 0xF8EC, 0x9F75, + 0xF8ED, 0x9F76, 0xF8EE, 0x56D4, 0xF8EF, 0x652E, 0xF8F0, 0x65B8, 0xF8F1, 0x6B18, 0xF8F2, 0x6B19, 0xF8F3, 0x6B17, 0xF8F4, 0x6B1A, + 0xF8F5, 0x7062, 0xF8F6, 0x7226, 0xF8F7, 0x72AA, 0xF8F8, 0x77D8, 0xF8F9, 0x77D9, 0xF8FA, 0x7939, 0xF8FB, 0x7C69, 0xF8FC, 0x7C6B, + 0xF8FD, 0x7CF6, 0xF8FE, 0x7E9A, 0xF940, 0x7E98, 0xF941, 0x7E9B, 0xF942, 0x7E99, 0xF943, 0x81E0, 0xF944, 0x81E1, 0xF945, 0x8646, + 0xF946, 0x8647, 0xF947, 0x8648, 0xF948, 0x8979, 0xF949, 0x897A, 0xF94A, 0x897C, 0xF94B, 0x897B, 0xF94C, 0x89FF, 0xF94D, 0x8B98, + 0xF94E, 0x8B99, 0xF94F, 0x8EA5, 0xF950, 0x8EA4, 0xF951, 0x8EA3, 0xF952, 0x946E, 0xF953, 0x946D, 0xF954, 0x946F, 0xF955, 0x9471, + 0xF956, 0x9473, 0xF957, 0x9749, 0xF958, 0x9872, 0xF959, 0x995F, 0xF95A, 0x9C68, 0xF95B, 0x9C6E, 0xF95C, 0x9C6D, 0xF95D, 0x9E0B, + 0xF95E, 0x9E0D, 0xF95F, 0x9E10, 0xF960, 0x9E0F, 0xF961, 0x9E12, 0xF962, 0x9E11, 0xF963, 0x9EA1, 0xF964, 0x9EF5, 0xF965, 0x9F09, + 0xF966, 0x9F47, 0xF967, 0x9F78, 0xF968, 0x9F7B, 0xF969, 0x9F7A, 0xF96A, 0x9F79, 0xF96B, 0x571E, 0xF96C, 0x7066, 0xF96D, 0x7C6F, + 0xF96E, 0x883C, 0xF96F, 0x8DB2, 0xF970, 0x8EA6, 0xF971, 0x91C3, 0xF972, 0x9474, 0xF973, 0x9478, 0xF974, 0x9476, 0xF975, 0x9475, + 0xF976, 0x9A60, 0xF977, 0x9C74, 0xF978, 0x9C73, 0xF979, 0x9C71, 0xF97A, 0x9C75, 0xF97B, 0x9E14, 0xF97C, 0x9E13, 0xF97D, 0x9EF6, + 0xF97E, 0x9F0A, 0xF9A1, 0x9FA4, 0xF9A2, 0x7068, 0xF9A3, 0x7065, 0xF9A4, 0x7CF7, 0xF9A5, 0x866A, 0xF9A6, 0x883E, 0xF9A7, 0x883D, + 0xF9A8, 0x883F, 0xF9A9, 0x8B9E, 0xF9AA, 0x8C9C, 0xF9AB, 0x8EA9, 0xF9AC, 0x8EC9, 0xF9AD, 0x974B, 0xF9AE, 0x9873, 0xF9AF, 0x9874, + 0xF9B0, 0x98CC, 0xF9B1, 0x9961, 0xF9B2, 0x99AB, 0xF9B3, 0x9A64, 0xF9B4, 0x9A66, 0xF9B5, 0x9A67, 0xF9B6, 0x9B24, 0xF9B7, 0x9E15, + 0xF9B8, 0x9E17, 0xF9B9, 0x9F48, 0xF9BA, 0x6207, 0xF9BB, 0x6B1E, 0xF9BC, 0x7227, 0xF9BD, 0x864C, 0xF9BE, 0x8EA8, 0xF9BF, 0x9482, + 0xF9C0, 0x9480, 0xF9C1, 0x9481, 0xF9C2, 0x9A69, 0xF9C3, 0x9A68, 0xF9C4, 0x9B2E, 0xF9C5, 0x9E19, 0xF9C6, 0x7229, 0xF9C7, 0x864B, + 0xF9C8, 0x8B9F, 0xF9C9, 0x9483, 0xF9CA, 0x9C79, 0xF9CB, 0x9EB7, 0xF9CC, 0x7675, 0xF9CD, 0x9A6B, 0xF9CE, 0x9C7A, 0xF9CF, 0x9E1D, + 0xF9D0, 0x7069, 0xF9D1, 0x706A, 0xF9D2, 0x9EA4, 0xF9D3, 0x9F7E, 0xF9D4, 0x9F49, 0xF9D5, 0x9F98, 0xF9D6, 0x7881, 0xF9D7, 0x92B9, + 0xF9D8, 0x88CF, 0xF9D9, 0x58BB, 0xF9DA, 0x6052, 0xF9DB, 0x7CA7, 0xF9DC, 0x5AFA, 0xF9DD, 0x2554, 0xF9DE, 0x2566, 0xF9DF, 0x2557, + 0xF9E0, 0x2560, 0xF9E1, 0x256C, 0xF9E2, 0x2563, 0xF9E3, 0x255A, 0xF9E4, 0x2569, 0xF9E5, 0x255D, 0xF9E6, 0x2552, 0xF9E7, 0x2564, + 0xF9E8, 0x2555, 0xF9E9, 0x255E, 0xF9EA, 0x256A, 0xF9EB, 0x2561, 0xF9EC, 0x2558, 0xF9ED, 0x2567, 0xF9EE, 0x255B, 0xF9EF, 0x2553, + 0xF9F0, 0x2565, 0xF9F1, 0x2556, 0xF9F2, 0x255F, 0xF9F3, 0x256B, 0xF9F4, 0x2562, 0xF9F5, 0x2559, 0xF9F6, 0x2568, 0xF9F7, 0x255C, + 0xF9F8, 0x2551, 0xF9F9, 0x2550, 0xF9FA, 0x256D, 0xF9FB, 0x256E, 0xF9FC, 0x2570, 0xF9FD, 0x256F, 0xF9FE, 0x2593, 0, 0 +}; +#endif + +#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 in UTF-16, 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 in UTF-16, 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 in UTF-16, 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/exosphere/mariko_fatal/source/fs/fatal_fs_api.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fs/fatal_fs_api.cpp new file mode 100644 index 00000000..fe3edf6e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fs/fatal_fs_api.cpp @@ -0,0 +1,259 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../fatfs/ff.h" +#include "fatal_fs_api.hpp" + +namespace ams::fs { + + namespace { + + constexpr size_t MaxFiles = 8; + + constinit bool g_is_sd_mounted = false; + + FATFS g_sd_fs; + + constinit FIL g_files[MaxFiles] = {}; + constinit bool g_files_opened[MaxFiles] = {}; + constinit int g_open_modes[MaxFiles] = {}; + + Result TranslateFatFsError(FRESULT res) { + switch (res) { + case FR_OK: + R_SUCCEED(); + case FR_DISK_ERR: + R_THROW(fs::ResultMmcAccessFailed()); + case FR_INT_ERR: + R_THROW(fs::ResultPreconditionViolation()); + case FR_NOT_READY: + R_THROW(fs::ResultMmcAccessFailed()); + case FR_NO_FILE: + R_THROW(fs::ResultPathNotFound()); + case FR_NO_PATH: + R_THROW(fs::ResultPathNotFound()); + case FR_INVALID_NAME: + R_THROW(fs::ResultInvalidPath()); + case FR_DENIED: + R_THROW(fs::ResultPermissionDenied()); + case FR_EXIST: + R_THROW(fs::ResultPathAlreadyExists()); + case FR_INVALID_OBJECT: + R_THROW(fs::ResultInvalidArgument()); + case FR_WRITE_PROTECTED: + R_THROW(fs::ResultWriteNotPermitted()); + case FR_INVALID_DRIVE: + R_THROW(fs::ResultInvalidMountName()); + case FR_NOT_ENABLED: + R_THROW(fs::ResultInvalidMountName()); /* BAD/TODO */ + case FR_NO_FILESYSTEM: + R_THROW(fs::ResultInvalidMountName()); /* BAD/TODO */ + case FR_TIMEOUT: + R_THROW(fs::ResultTargetLocked()); /* BAD/TODO */ + case FR_LOCKED: + R_THROW(fs::ResultTargetLocked()); + case FR_NOT_ENOUGH_CORE: + R_THROW(fs::ResultPreconditionViolation()); /* BAD/TODO */ + case FR_TOO_MANY_OPEN_FILES: + R_THROW(fs::ResultPreconditionViolation()); /* BAD/TODO */ + case FR_INVALID_PARAMETER: + R_THROW(fs::ResultInvalidArgument()); + default: + R_THROW(fs::ResultInternal()); + } + } + + int TranslateToFatFsMode(int mode) { + int fmode = FA_OPEN_EXISTING; + if ((mode & OpenMode_Read) != 0) { + fmode |= FA_READ; + } + if ((mode & OpenMode_Write) != 0) { + fmode |= FA_WRITE; + } + if ((mode & OpenMode_AllowAppend) != 0) { + fmode |= FA_OPEN_APPEND; + } + return fmode; + } + + FIL *GetInternalFile(FileHandle handle) { + return static_cast<FIL *>(handle._handle); + } + + ALWAYS_INLINE size_t GetFileIndex(FIL *fp) { + const size_t file_index = (fp - g_files); + AMS_ASSERT(file_index < MaxFiles); + return file_index; + } + + } + + bool MountSdCard() { + AMS_ASSERT(!g_is_sd_mounted); + g_is_sd_mounted = f_mount(std::addressof(g_sd_fs), "sdmc", 1) == FR_OK; + return g_is_sd_mounted; + } + + void UnmountSdCard() { + AMS_ASSERT(g_is_sd_mounted); + f_unmount("sdmc"); + g_is_sd_mounted = false; + } + + Result CreateFile(const char *path, s64 size) { + /* Create the file. */ + FIL fp; + R_TRY(TranslateFatFsError(f_open(std::addressof(fp), path, FA_CREATE_NEW | FA_READ | FA_WRITE))); + + /* Ensure that we close the file when we're done with it. */ + ON_SCOPE_EXIT { f_close(std::addressof(fp)); }; + + /* Expand the file. */ + R_TRY(TranslateFatFsError(f_expand(std::addressof(fp), size, 1))); + + R_SUCCEED(); + } + + Result CreateDirectory(const char *path) { + R_RETURN(TranslateFatFsError(f_mkdir(path))); + } + + Result OpenFile(FileHandle *out_file, const char *path, int mode) { + /* Find a free file. */ + for (size_t i = 0; i < MaxFiles; ++i) { + if (!g_files_opened[i]) { + /* Open the file. */ + FIL *fp = std::addressof(g_files[i]); + R_TRY(TranslateFatFsError(f_open(fp, path, TranslateToFatFsMode(mode)))); + + /* Set the output. */ + out_file->_handle = fp; + g_files_opened[i] = true; + g_open_modes[i] = mode; + R_SUCCEED(); + } + } + R_THROW(fs::ResultOpenCountLimit()); + } + + Result ReadFile(FileHandle handle, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) { + /* Option is unused. */ + AMS_UNUSED(option); + + /* Seek to the offset we're reading at. */ + R_TRY(TranslateFatFsError(f_lseek(GetInternalFile(handle), offset))); + + /* Read the data. */ + u32 br; + R_TRY(TranslateFatFsError(f_read(GetInternalFile(handle), buffer, size, std::addressof(br)))); + + /* Check that we read the correct amount. */ + R_UNLESS(br == size, fs::ResultOutOfRange()); + + R_SUCCEED(); + } + + Result ReadFile(FileHandle handle, s64 offset, void *buffer, size_t size) { + R_RETURN(ReadFile(handle, offset, buffer, size, fs::ReadOption::None)); + } + + Result ReadFile(size_t *out, FileHandle handle, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) { + /* Option is unused. */ + AMS_UNUSED(option); + + /* Seek to the offset we're reading at. */ + R_TRY(TranslateFatFsError(f_lseek(GetInternalFile(handle), offset))); + + /* Read the data. */ + u32 br; + R_TRY(TranslateFatFsError(f_read(GetInternalFile(handle), buffer, size, std::addressof(br)))); + + /* Set the output size. */ + *out = br; + + R_SUCCEED(); + } + + Result ReadFile(size_t *out, FileHandle handle, s64 offset, void *buffer, size_t size) { + R_RETURN(ReadFile(out, handle, offset, buffer, size, fs::ReadOption::None)); + } + + Result GetFileSize(s64 *out, FileHandle handle) { + FIL *fp = GetInternalFile(handle); + *out = f_size(fp); + R_SUCCEED(); + } + + Result FlushFile(FileHandle handle) { + R_RETURN(TranslateFatFsError(f_sync(GetInternalFile(handle)))); + } + + Result WriteFile(FileHandle handle, s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) { + /* Seek to the offset we're writing at. */ + R_TRY(TranslateFatFsError(f_lseek(GetInternalFile(handle), offset))); + + /* Write the data. */ + u32 bw; + R_TRY(TranslateFatFsError(f_write(GetInternalFile(handle), buffer, size, std::addressof(bw)))); + + /* Check that we wrote the correct amount. */ + R_UNLESS(bw == size, fs::ResultOutOfRange()); + + /* If we should, flush the file. */ + if (option.HasFlushFlag()) { + R_TRY(FlushFile(handle)); + } + + R_SUCCEED(); + } + + Result SetFileSize(FileHandle handle, s64 size) { + FIL *fp = GetInternalFile(handle); + + /* Check if we have nothing to do. */ + const size_t fsize = f_size(fp); + R_SUCCEED_IF(static_cast<FSIZE_t>(size) == fsize); + + /* NOTE/TODO: This may not preserve file data. Do this in a way that does? */ + + /* Truncate the file. */ + R_TRY(TranslateFatFsError(f_truncate(fp))); + + /* Expand the file. */ + R_TRY(TranslateFatFsError(f_expand(fp, size, 1))); + + /* Ensure the file is synchronized. */ + R_TRY(FlushFile(handle)); + + /* Check that our expansion succeeded. */ + AMS_ASSERT(f_size(fp) == static_cast<FSIZE_t>(size)); + + R_SUCCEED(); + } + + int GetFileOpenMode(FileHandle handle) { + return g_open_modes[GetFileIndex(GetInternalFile(handle))]; + } + + void CloseFile(FileHandle handle) { + const size_t index = GetFileIndex(GetInternalFile(handle)); + f_close(std::addressof(g_files[index])); + g_open_modes[index] = 0; + g_files_opened[index] = false; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fs/fatal_fs_api.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fs/fatal_fs_api.hpp new file mode 100644 index 00000000..509ecbb0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/mariko_fatal/source/fs/fatal_fs_api.hpp @@ -0,0 +1,94 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::fs { + + enum OpenMode { + OpenMode_Read = (1 << 0), + OpenMode_Write = (1 << 1), + OpenMode_AllowAppend = (1 << 2), + + OpenMode_ReadWrite = (OpenMode_Read | OpenMode_Write), + OpenMode_All = (OpenMode_ReadWrite | OpenMode_AllowAppend), + }; + + struct ReadOption { + u32 _value; + + static const ReadOption None; + }; + + inline constexpr const ReadOption ReadOption::None = {0}; + + inline constexpr bool operator==(const ReadOption &lhs, const ReadOption &rhs) { + return lhs._value == rhs._value; + } + + inline constexpr bool operator!=(const ReadOption &lhs, const ReadOption &rhs) { + return !(lhs == rhs); + } + + static_assert(util::is_pod<ReadOption>::value && sizeof(ReadOption) == sizeof(u32)); + + struct WriteOption { + u32 _value; + + constexpr inline bool HasFlushFlag() const { + return _value & 1; + } + + static const WriteOption None; + static const WriteOption Flush; + }; + + inline constexpr const WriteOption WriteOption::None = {0}; + inline constexpr const WriteOption WriteOption::Flush = {1}; + + inline constexpr bool operator==(const WriteOption &lhs, const WriteOption &rhs) { + return lhs._value == rhs._value; + } + + inline constexpr bool operator!=(const WriteOption &lhs, const WriteOption &rhs) { + return !(lhs == rhs); + } + + static_assert(util::is_pod<WriteOption>::value && sizeof(WriteOption) == sizeof(u32)); + + struct FileHandle { + void *_handle; + }; + + bool MountSdCard(); + void UnmountSdCard(); + + Result CreateFile(const char *path, s64 size); + Result CreateDirectory(const char *path); + Result OpenFile(FileHandle *out_file, const char *path, int mode); + + Result ReadFile(FileHandle handle, s64 offset, void *buffer, size_t size, const fs::ReadOption &option); + Result ReadFile(FileHandle handle, s64 offset, void *buffer, size_t size); + Result ReadFile(size_t *out, FileHandle handle, s64 offset, void *buffer, size_t size, const fs::ReadOption &option); + Result ReadFile(size_t *out, FileHandle handle, s64 offset, void *buffer, size_t size); + Result GetFileSize(s64 *out, FileHandle handle); + Result FlushFile(FileHandle handle); + Result WriteFile(FileHandle handle, s64 offset, const void *buffer, size_t size, const fs::WriteOption &option); + Result SetFileSize(FileHandle handle, s64 size); + int GetFileOpenMode(FileHandle handle); + void CloseFile(FileHandle handle); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/Makefile b/Source/Atmosphere-MTC-Unlock/exosphere/program/Makefile new file mode 100644 index 00000000..e704021f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/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)/program.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)/program.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/program/program.ld b/Source/Atmosphere-MTC-Unlock/exosphere/program/program.ld new file mode 100644 index 00000000..2cbd831e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/program.ld @@ -0,0 +1,307 @@ +OUTPUT_ARCH(aarch64) +ENTRY(_start) + +MEMORY +{ + NULL : ORIGIN = 0, LENGTH = 4K + unused_region : ORIGIN = 0x1000, LENGTH = 4K + iram_boot_code : ORIGIN = 0x040032000, LENGTH = 4K + iram_boot_keys : ORIGIN = 0x040033000, LENGTH = 4K + tzram : ORIGIN = 0x07C010000, LENGTH = 64K + + /* Warmboot code follows the vectors in memory. */ + /* However, we can't know for sure how big warmboot is, so we'll just say it's 2K. */ + warmboot_text : ORIGIN = ORIGIN(tzram) + 10K, LENGTH = 2K + + main : ORIGIN = 0x1F00C0000, LENGTH = 48K + debug_code : ORIGIN = 0x1F0150000, LENGTH = 16K + tzram_boot : ORIGIN = 0x1F01C0800, LENGTH = 6K + + glob : ORIGIN = 0x040032000, LENGTH = 128K +} + +SECTIONS +{ + .metadata : + { + . = ALIGN(8); + KEEP (*(.metadata .metadata.*)) + . = ALIGN(8); + } >unused_region AT>glob + + PROVIDE(__glob_start__ = ORIGIN(glob)); + . = __glob_start__; + + __bootcode_start__ = ABSOLUTE(.); + + .crt0 : + { + KEEP (*(.crt0 .crt0.*)) + KEEP (secmon_crt0_cpp.o(.text*)) + KEEP (secmon_make_page_table.o(.text*)) + *(.crt0.rodata*) + secmon_crt0_cpp.o(.rodata*) + secmon_make_page_table.o(.rodata*) + *(.crt0.data*) + secmon_crt0_cpp.o(.data*) + secmon_make_page_table.o(.data*) + . = ALIGN(8); + } >iram_boot_code AT>glob + + .preinit_array ALIGN(8) : + { + PROVIDE (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE (__preinit_array_end = .); + } >iram_boot_code AT>glob + + .init_array ALIGN(8) : + { + PROVIDE (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + PROVIDE (__init_array_end = .); + } >iram_boot_code AT>glob + + .fini_array ALIGN(8) : + { + PROVIDE (__fini_array_start = .); + KEEP (*(.fini_array)) + KEEP (*(SORT(.fini_array.*))) + PROVIDE (__fini_array_end = .); + } >iram_boot_code AT>glob + + .ctors ALIGN(8) : + { + KEEP (*crtbegin.o(.ctors)) /* MUST be first -- GCC requires it */ + KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + } >iram_boot_code AT>glob + + .dtors ALIGN(8) : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + } >iram_boot_code AT>glob + + .boot_code.fill : { + FILL(0x00000000); + . = ORIGIN(iram_boot_code) + 0xFFF; + BYTE(0x00); + } >iram_boot_code AT>glob + + .boot_code_volatile_keys : { + KEEP (*(.volatile_keys .volatile_keys.*)) + } >iram_boot_keys AT>glob + + .boot_keys.fill : { + FILL(0x00000000); + . = ORIGIN(iram_boot_keys) + 0xFFF; + BYTE(0x00); + } >iram_boot_keys AT>glob + + .debug_code : { + KEEP (*(.text._ZN3ams3log6PrintfEPKcz .text._ZN3ams3log7VPrintfEPKcSt9__va_list .text._ZN3ams3log4DumpEPKvm)) + KEEP (*(.text._ZN3ams4util10TVSNPrintfEPcmPKcSt9__va_list .text._ZN3ams4util12_GLOBAL__N_114TVSNPrintfImplEPcmPKcSt9__va_list .text._ZZN3ams4util12_GLOBAL__N_114TVSNPrintfImplEPcmPKcSt9__va_listENKUlbmE3_clEbm)) + KEEP (*(.text._ZN3ams4util12_GLOBAL__N_1L14TVSNPrintfImplEPcmPKcSt9__va_list .text._ZZN3ams4util12_GLOBAL__N_1L14TVSNPrintfImplEPcmPKcSt9__va_listENKUlbmE_clEbm)) + KEEP(secmon_exception_handler.o(.text*)) + secmon_exception_handler.o(.rodata*) + secmon_exception_handler.o(.data*) + } >debug_code AT>glob + + .debug_code.bss_fill : + { + FILL(0x00000000); + secmon_exception_handler.o(.bss* COMMON) + . = ORIGIN(debug_code) + LENGTH(debug_code) - 1; + BYTE(0x00); + } >debug_code AT>glob + + __bootcode_end__ = ABSOLUTE(.) - ORIGIN(debug_code) + 0x40034000; + + __program_start__ = __bootcode_end__; + + .tzram_boot_code : + { + KEEP(secmon_main.o(.text*)) + KEEP(secmon_boot_functions.o(.text*)) + KEEP(secmon_boot_cache.o(.text*)) + KEEP(secmon_boot_config.o(.text*)) + KEEP(secmon_boot_setup.o(.text*)) + KEEP(secmon_boot_rsa.o(.text*)) + KEEP(secmon_package2.o(.text*)) + secmon_main.o(.rodata*) + secmon_boot_functions.o(.rodata*) + secmon_boot_cache.o(.rodata*) + secmon_boot_config.o(.rodata*) + secmon_boot_setup.o(.rodata*) + secmon_boot_rsa.o(.rodata*) + secmon_package2.o(.rodata*) + secmon_main.o(.data*) + secmon_boot_functions.o(.data*) + secmon_boot_cache.o(.data*) + secmon_boot_config.o(.data*) + secmon_boot_setup.o(.data*) + secmon_boot_rsa.o(.data*) + secmon_package2.o(.data*) + . = ALIGN(8); + } >tzram_boot AT>glob + + .tzram_boot_code.bss_fill : + { + FILL(0x00000000); + __boot_bss_start__ = ABSOLUTE(.); + secmon_main.o(.bss* COMMON) + secmon_boot_functions.o(.bss* COMMON) + secmon_boot_cache.o(.bss* COMMON) + secmon_boot_config.o(.bss* COMMON) + secmon_boot_setup.o(.bss* COMMON) + secmon_boot_rsa.o(.bss* COMMON) + secmon_package2.o(.bss* COMMON) + __boot_bss_end__ = ABSOLUTE(.); + . = ORIGIN(tzram_boot) + LENGTH(tzram_boot) - 1; + BYTE(0x00); + } > tzram_boot AT>glob + + .vectors : + { + KEEP (*(.vectors*)) + . = ALIGN(0x100); + } >main AT>glob + + .warmboot : + { + KEEP (*(.warmboot.text.start)) /* Should be first */ + KEEP (*(.warmboot.text*)) + KEEP(secmon_setup_warm.o(.text*)) + KEEP(*(.text._ZN3ams4tsec4LockEv)) + KEEP (*(.warmboot.rodata*)) + KEEP(secmon_setup_warm.o(.rodata*)) + KEEP (*(.warmboot.data*)) + KEEP(secmon_setup_warm.o(.data*)) + } >warmboot_text AT>glob + + .text ORIGIN(main) + SIZEOF(.vectors) + SIZEOF(.warmboot) : + { + *(.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); + } >main AT>glob + + .init : + { + KEEP( *(.init) ) + . = ALIGN(8); + } >main AT>glob + + .plt : + { + *(.plt) + *(.iplt) + . = ALIGN(8); + } >main AT>glob + + .fini : + { + KEEP( *(.fini) ) + . = ALIGN(8); + } >main AT>glob + + + /* =========== RODATA section =========== */ + . = ALIGN(8); + __rodata_start = . ; + + .rodata : + { + *(.rodata .rodata.* .gnu.linkonce.r.*) + . = ALIGN(8); + } >main AT>glob + + .eh_frame_hdr : { __eh_frame_hdr_start = .; *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) __eh_frame_hdr_end = .; } >main AT>glob + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } >main AT>glob + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } >main AT>glob + .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } >main AT>glob + + .hash : { *(.hash) } >main AT>glob + + /* =========== DATA section =========== */ + . = ALIGN(8); + __data_start = . ; + + .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } >main AT>glob + .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } >main AT>glob + .gnu_extab : ONLY_IF_RW { *(.gnu_extab*) } >main AT>glob + .exception_ranges : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) } >main AT>glob + + __got_start__ = .; + + .got : { *(.got) *(.igot) } >main AT>glob + .got.plt : { *(.got.plt) *(.igot.plt) } >main AT>glob + + __got_end__ = .; + + .data ALIGN(8) : + { + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + } >main AT>glob + + .bss ALIGN(8) (NOLOAD) : + { + __bss_start__ = ABSOLUTE(.); + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + . = ALIGN(16); + __bss_end__ = ABSOLUTE(.); + } >main AT>glob + + __program_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/program/program.mk b/Source/Atmosphere-MTC-Unlock/exosphere/program/program.mk new file mode 100644 index 00000000..ef542d1c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/program.mk @@ -0,0 +1,162 @@ +#--------------------------------------------------------------------------------- +# 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)) \ + $(CURRENT_DIRECTORY)/sc7fw/$(ATMOSPHERE_BOOT_OUT_DIR) \ + $(CURRENT_DIRECTORY)/rebootstub/$(ATMOSPHERE_BOOT_OUT_DIR) + +CFILES := $(call FIND_SOURCE_FILES,$(SOURCES),c) +CPPFILES := $(call FIND_SOURCE_FILES,$(SOURCES),cpp) +SFILES := $(call FIND_SOURCE_FILES,$(SOURCES),s) +BINFILES := sc7fw.bin rebootstub.bin + +#--------------------------------------------------------------------------------- +# 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_boot_lib check_sc7fw check_rebootstub + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/$(ATMOSPHERE_LIBRARY_DIR)/libexosphere.a $(CURRENT_DIRECTORY)/sc7fw/$(ATMOSPHERE_BOOT_OUT_DIR)/sc7fw.bin $(CURRENT_DIRECTORY)/rebootstub/$(ATMOSPHERE_BOOT_OUT_DIR)/rebootstub.bin + @$(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." + + +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 + +$(CURRENT_DIRECTORY)/sc7fw/$(ATMOSPHERE_BOOT_OUT_DIR)/sc7fw.bin: check_sc7fw + +$(CURRENT_DIRECTORY)/rebootstub/$(ATMOSPHERE_BOOT_OUT_DIR)/rebootstub.bin: check_rebootstub + +ifeq ($(ATMOSPHERE_CHECKED_SC7FW),1) +check_sc7fw: +else +check_sc7fw: $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/$(ATMOSPHERE_BOOT_LIBRARY_DIR)/libexosphere.a + @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/sc7fw -f $(CURRENT_DIRECTORY)/sc7fw/sc7fw.mk ATMOSPHERE_CPU="$(strip $(ATMOSPHERE_BOOT_CPU))" ATMOSPHERE_CHECKED_LIBEXOSPHERE=1 ATMOSPHERE_CHECKED_BOOT_LIBEXOSPHERE=1 +endif + +ifeq ($(ATMOSPHERE_CHECKED_REBOOTSTUB),1) +check_rebootstub: +else +check_rebootstub: $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/$(ATMOSPHERE_BOOT_LIBRARY_DIR)/libexosphere.a + @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/rebootstub -f $(CURRENT_DIRECTORY)/rebootstub/rebootstub.mk ATMOSPHERE_CPU="$(strip $(ATMOSPHERE_BOOT_CPU))" ATMOSPHERE_CHECKED_LIBEXOSPHERE=1 ATMOSPHERE_CHECKED_BOOT_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 + +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 + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/rebootstub -f $(CURRENT_DIRECTORY)/rebootstub/rebootstub.mk clean ATMOSPHERE_CPU="$(strip $(ATMOSPHERE_BOOT_CPU))" + @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/sc7fw -f $(CURRENT_DIRECTORY)/sc7fw/sc7fw.mk clean ATMOSPHERE_CPU="$(strip $(ATMOSPHERE_BOOT_CPU))" + @rm -fr $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_OUT_DIR) + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- + +$(OUTPUT).lz4 : $(OUTPUT).bin + $(SILENTCMD)$(PYTHON) $(CURRENT_DIRECTORY)/split_program.py $(OUTPUT).bin $(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR) + @echo built ... $(notdir $@) + +$(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_crt0_cpp.o secmon_make_page_table.o : CFLAGS += -fno-builtin + +sc7fw.bin.o: sc7fw.bin + @echo $(notdir $<) + @$(bin2o) + +rebootstub.bin.o: rebootstub.bin + @echo $(notdir $<) + @$(bin2o) + +%.elf: + @echo linking $(notdir $@) + $(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + @$(NM) -CSn $@ > $(notdir $*.lst) + +$(OFILES_SRC) : $(OFILES_BIN) + +#--------------------------------------------------------------------------------- +# 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/program/program.specs b/Source/Atmosphere-MTC-Unlock/exosphere/program/program.specs new file mode 100644 index 00000000..a1f8ae65 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/program.specs @@ -0,0 +1,4 @@ +%rename link old_link + +*link: +%(old_link) -T %:getenv(ATMOSPHERE_TOPDIR /program.ld) --gc-sections --nmagic \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/rebootstub/Makefile b/Source/Atmosphere-MTC-Unlock/exosphere/program/rebootstub/Makefile new file mode 100644 index 00000000..dab7f522 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/rebootstub/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)/rebootstub.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)/rebootstub.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, arm7tdmi,)) + +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/program/rebootstub/rebootstub.ld b/Source/Atmosphere-MTC-Unlock/exosphere/program/rebootstub/rebootstub.ld new file mode 100644 index 00000000..c37c7555 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/rebootstub/rebootstub.ld @@ -0,0 +1,183 @@ +OUTPUT_ARCH(arm) +ENTRY(reset) + +MEMORY +{ + NULL : ORIGIN = 0, LENGTH = 4K + rebootstub : ORIGIN = 0x4003F000, LENGTH = 4K +} + + +SECTIONS +{ + /* =========== CODE section =========== */ + PROVIDE(__start__ = ORIGIN(rebootstub)); + . = __start__; + __code_start = . ; + + .vectors : + { + KEEP (*(.vectors .vectors.*)) + . = ALIGN(8); + } >rebootstub + + .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); + } >rebootstub + + .init : + { + KEEP( *(.init) ) + . = ALIGN(8); + } >rebootstub + + .plt : + { + *(.plt) + *(.iplt) + . = ALIGN(8); + } >rebootstub + + .fini : + { + KEEP( *(.fini) ) + . = ALIGN(8); + } >rebootstub + + + /* =========== RODATA section =========== */ + . = ALIGN(8); + __rodata_start = . ; + + .rodata : + { + *(.rodata .rodata.* .gnu.linkonce.r.*) + . = ALIGN(8); + } >rebootstub + + .eh_frame_hdr : { __eh_frame_hdr_start = .; *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) __eh_frame_hdr_end = .; } >rebootstub + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } >rebootstub + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } >rebootstub + .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } >rebootstub + + .hash : { *(.hash) } >rebootstub + + /* =========== DATA section =========== */ + . = ALIGN(8); + __data_start = . ; + + .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } >rebootstub + .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } >rebootstub + .gnu_extab : ONLY_IF_RW { *(.gnu_extab*) } >rebootstub + .exception_ranges : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) } >rebootstub + + .preinit_array ALIGN(8) : + { + PROVIDE (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE (__preinit_array_end = .); + } >rebootstub + + .init_array ALIGN(8) : + { + PROVIDE (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + PROVIDE (__init_array_end = .); + } >rebootstub + + .fini_array ALIGN(8) : + { + PROVIDE (__fini_array_start = .); + KEEP (*(.fini_array)) + KEEP (*(SORT(.fini_array.*))) + PROVIDE (__fini_array_end = .); + } >rebootstub + + .ctors ALIGN(8) : + { + KEEP (*crtbegin.o(.ctors)) /* MUST be first -- GCC requires it */ + KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + } >rebootstub + + .dtors ALIGN(8) : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + } >rebootstub + + __got_start__ = .; + + .got : { *(.got) *(.igot) } >rebootstub + .got.plt : { *(.got.plt) *(.igot.plt) } >rebootstub + + __got_end__ = .; + + .data ALIGN(8) : + { + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + } >rebootstub + + __bss_start__ = .; + .bss ALIGN(8) : + { + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + . = ALIGN(16); + } >rebootstub + __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/program/rebootstub/rebootstub.mk b/Source/Atmosphere-MTC-Unlock/exosphere/program/rebootstub/rebootstub.mk new file mode 100644 index 00000000..7ab59a95 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/rebootstub/rebootstub.mk @@ -0,0 +1,113 @@ +#--------------------------------------------------------------------------------- +# 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) + +#--------------------------------------------------------------------------------- +# 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/program/rebootstub/rebootstub.specs b/Source/Atmosphere-MTC-Unlock/exosphere/program/rebootstub/rebootstub.specs new file mode 100644 index 00000000..e480720e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/rebootstub/rebootstub.specs @@ -0,0 +1,4 @@ +%rename link old_link + +*link: +%(old_link) -T %:getenv(ATMOSPHERE_TOPDIR /rebootstub.ld) --gc-sections --nmagic \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/rebootstub/source/rebootstub_exception_vectors.s b/Source/Atmosphere-MTC-Unlock/exosphere/program/rebootstub/source/rebootstub_exception_vectors.s new file mode 100644 index 00000000..3fa58479 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/rebootstub/source/rebootstub_exception_vectors.s @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +.section .vectors, "ax", %progbits +.align 3 +.global reset +reset: + b _ZN3ams10rebootstub4MainEv + +.global _ZN3ams10rebootstub10RebootTypeE +_ZN3ams10rebootstub10RebootTypeE: +.word 0x00000001 \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/rebootstub/source/rebootstub_main.s b/Source/Atmosphere-MTC-Unlock/exosphere/program/rebootstub/source/rebootstub_main.s new file mode 100644 index 00000000..a26b4217 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/rebootstub/source/rebootstub_main.s @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +.section .text._ZN3ams10rebootstub4MainEv, "ax", %progbits +.align 3 +.global _ZN3ams10rebootstub4MainEv +_ZN3ams10rebootstub4MainEv: + /* Get the reboot type. */ + ldr r0, =_ZN3ams10rebootstub10RebootTypeE + ldr r0, [r0] + + /* If the reboot type is power off, perform a power off. */ + cmp r0, #0 + beq _ZN3ams10rebootstub8PowerOffEv + + /* Otherwise, clear all registers jump to the reboot payload in iram. */ + ldr r0, =0x52425430 /* RBT0 */ + mov r1, #0 + mov r2, #0 + mov r3, #0 + mov r4, #0 + mov r5, #0 + mov r5, #0 + mov r6, #0 + mov r7, #0 + mov r8, #0 + mov r9, #0 + mov r10, #0 + mov r11, #0 + mov r12, #0 + mov r9, #0 + mov lr, #0 + ldr sp, =0x40010000 + ldr pc, =0x40010000 + + /* Infinite loop. */ + 1: b 1b \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/rebootstub/source/rebootstub_power_off.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/rebootstub/source/rebootstub_power_off.cpp new file mode 100644 index 00000000..e71c3627 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/rebootstub/source/rebootstub_power_off.cpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::rebootstub { + + NORETURN void Halt() { + while (true) { + reg::Write(secmon::MemoryRegionPhysicalDeviceFlowController.GetAddress() + FLOW_CTLR_HALT_COP_EVENTS, FLOW_REG_BITS_ENUM(HALT_COP_EVENTS_MODE, FLOW_MODE_STOP), + FLOW_REG_BITS_ENUM(HALT_COP_EVENTS_JTAG, ENABLED)); + } + + __builtin_unreachable(); + } + + NORETURN void PowerOff() { + /* Ensure that i2c5 is usable. */ + clkrst::EnableI2c5Clock(); + + /* Initialize i2c5. */ + i2c::Initialize(i2c::Port_5); + + /* Stop rtc alarms. */ + rtc::StopAlarm(); + + /* Perform a pmic power off. */ + pmic::PowerOff(); + + /* Halt the bpmp. */ + Halt(); + + /* This can never be reached. */ + __builtin_unreachable(); + } + +} + +namespace ams::diag { + + NORETURN void AbortImpl() { + /* Halt the bpmp. */ + rebootstub::Halt(); + + /* This can never be reached. */ + __builtin_unreachable(); + } + + #include <exosphere/diag/diag_detailed_assertion_impl.inc> + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/Makefile b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/Makefile new file mode 100644 index 00000000..76641d72 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/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)/sc7fw.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)/sc7fw.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, arm7tdmi,)) + +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/program/sc7fw/sc7fw.ld b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/sc7fw.ld new file mode 100644 index 00000000..794edc3d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/sc7fw.ld @@ -0,0 +1,183 @@ +OUTPUT_ARCH(arm) +ENTRY(reset) + +MEMORY +{ + NULL : ORIGIN = 0, LENGTH = 4K + sc7fw : ORIGIN = 0x40003000, LENGTH = 4K +} + + +SECTIONS +{ + /* =========== CODE section =========== */ + PROVIDE(__start__ = ORIGIN(sc7fw)); + . = __start__; + __code_start = . ; + + .vectors : + { + KEEP (*(.vectors .vectors.*)) + . = ALIGN(8); + } >sc7fw + + .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); + } >sc7fw + + .init : + { + KEEP( *(.init) ) + . = ALIGN(8); + } >sc7fw + + .plt : + { + *(.plt) + *(.iplt) + . = ALIGN(8); + } >sc7fw + + .fini : + { + KEEP( *(.fini) ) + . = ALIGN(8); + } >sc7fw + + + /* =========== RODATA section =========== */ + . = ALIGN(8); + __rodata_start = . ; + + .rodata : + { + *(.rodata .rodata.* .gnu.linkonce.r.*) + . = ALIGN(8); + } >sc7fw + + .eh_frame_hdr : { __eh_frame_hdr_start = .; *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) __eh_frame_hdr_end = .; } >sc7fw + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } >sc7fw + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } >sc7fw + .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } >sc7fw + + .hash : { *(.hash) } >sc7fw + + /* =========== DATA section =========== */ + . = ALIGN(8); + __data_start = . ; + + .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } >sc7fw + .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } >sc7fw + .gnu_extab : ONLY_IF_RW { *(.gnu_extab*) } >sc7fw + .exception_ranges : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) } >sc7fw + + .preinit_array ALIGN(8) : + { + PROVIDE (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE (__preinit_array_end = .); + } >sc7fw + + .init_array ALIGN(8) : + { + PROVIDE (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + PROVIDE (__init_array_end = .); + } >sc7fw + + .fini_array ALIGN(8) : + { + PROVIDE (__fini_array_start = .); + KEEP (*(.fini_array)) + KEEP (*(SORT(.fini_array.*))) + PROVIDE (__fini_array_end = .); + } >sc7fw + + .ctors ALIGN(8) : + { + KEEP (*crtbegin.o(.ctors)) /* MUST be first -- GCC requires it */ + KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + } >sc7fw + + .dtors ALIGN(8) : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + } >sc7fw + + __got_start__ = .; + + .got : { *(.got) *(.igot) } >sc7fw + .got.plt : { *(.got.plt) *(.igot.plt) } >sc7fw + + __got_end__ = .; + + .data ALIGN(8) : + { + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + } >sc7fw + + __bss_start__ = .; + .bss ALIGN(8) : + { + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + . = ALIGN(16); + } >sc7fw + __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/program/sc7fw/sc7fw.mk b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/sc7fw.mk new file mode 100644 index 00000000..7ab59a95 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/sc7fw.mk @@ -0,0 +1,113 @@ +#--------------------------------------------------------------------------------- +# 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) + +#--------------------------------------------------------------------------------- +# 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/program/sc7fw/sc7fw.specs b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/sc7fw.specs new file mode 100644 index 00000000..8b876c97 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/sc7fw.specs @@ -0,0 +1,4 @@ +%rename link old_link + +*link: +%(old_link) -T %:getenv(ATMOSPHERE_TOPDIR /sc7fw.ld) --gc-sections --nmagic \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_dram.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_dram.cpp new file mode 100644 index 00000000..2a09e377 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_dram.cpp @@ -0,0 +1,182 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "sc7fw_util.hpp" +#include "sc7fw_dram.hpp" + +namespace ams::sc7fw { + + namespace { + + constexpr inline const uintptr_t PMC = secmon::MemoryRegionPhysicalDevicePmc.GetAddress(); + + void UpdateEmcTiming() { + /* Enable timing update. */ + reg::Write(EMC_ADDRESS(EMC_TIMING_CONTROL), EMC_REG_BITS_ENUM(TIMING_CONTROL_TIMING_UPDATE, ENABLED)); + + /* Wait for the timing update to complete. */ + while (!reg::HasValue(EMC_ADDRESS(EMC_EMC_STATUS), EMC_REG_BITS_ENUM(EMC_STATUS_TIMING_UPDATE_STALLED, DONE))) { + /* ... */ + } + } + + void RequestAllPadsPowerDown(uintptr_t addr, uintptr_t expected) { + constexpr u32 DpdAllRequestValue = reg::Encode(PMC_REG_BITS_ENUM(IO_DPD_REQ_CODE, DPD_ON)) | 0x0FFFFFFF; + const auto RequestAddress = addr; + const auto StatusAddress = addr + 4; + + /* Request all pads enter power down. */ + reg::Write(PMC + RequestAddress, DpdAllRequestValue); + + /* Wait until the status reflects our expectation (and all pads are shut down). */ + while (reg::Read(PMC + StatusAddress) != expected) { /* ... */ } + + /* Wait a little while to allow the power down status to propagate. */ + SpinLoop(0x20); + }; + + } + + void SaveEmcFsp() { + /* We require that the RAM is LPDDR4. */ + AMS_ABORT_UNLESS(reg::HasValue(EMC_ADDRESS(EMC_FBIO_CFG5), EMC_REG_BITS_ENUM(FBIO_CFG5_DRAM_TYPE, LPDDR4))); + + /* Read the frequency set points from MRW3. */ + constexpr u32 FspShift = 6; + constexpr u32 FspBits = 2; + constexpr u32 FspMask = ((1u << FspBits) - 1) << FspShift; + static_assert(FspMask == 0x000000C0); + const u32 fsp = (reg::Read(EMC_ADDRESS(EMC_MRW3)) & FspMask) >> FspShift; + + /* Write the fsp to PMC_SCRATCH18, where it will be restored to MRW3 by brom. */ + reg::ReadWrite(PMC + APBDEV_PMC_SCRATCH18, REG_BITS_VALUE(FspShift, FspBits, fsp)); + + /* Write the fsp twice to PMC_SCRATCH12, where it will be restored to MRW12 by brom. */ + reg::ReadWrite(PMC + APBDEV_PMC_SCRATCH12, REG_BITS_VALUE(FspShift, FspBits, fsp), REG_BITS_VALUE(FspShift + 8, FspBits, fsp)); + + /* Write the fsp twice to PMC_SCRATCH13, where it will be restored to MRW13 by brom. */ + reg::ReadWrite(PMC + APBDEV_PMC_SCRATCH13, REG_BITS_VALUE(FspShift, FspBits, fsp), REG_BITS_VALUE(FspShift + 8, FspBits, fsp)); + } + + void EnableSdramSelfRefresh() { + /* We require that the RAM is dual-channel. */ + AMS_ABORT_UNLESS(reg::HasValue(EMC_ADDRESS(EMC_FBIO_CFG7), EMC_REG_BITS_ENUM(FBIO_CFG7_CH1_ENABLE, ENABLE))); + + /* Disable RAM's ability to dynamically self-refresh, and to opportunistically perform powerdown. */ + reg::Write(EMC_ADDRESS(EMC_CFG), EMC_REG_BITS_ENUM(CFG_DYN_SELF_REF, DISABLED), + EMC_REG_BITS_ENUM(CFG_DRAM_ACPD, NO_POWERDOWN)); + + /* Update the EMC timing. */ + UpdateEmcTiming(); + + /* Wait five microseconds. */ + util::WaitMicroSeconds(5); + + /* Disable ZQ calibration. */ + reg::Write(EMC_ADDRESS(EMC_ZCAL_INTERVAL), 0); + + /* Disable automatic calibration. */ + reg::Write(EMC_ADDRESS(EMC_AUTO_CAL_CONFIG), EMC_REG_BITS_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_MEASURE_STALL, ENABLE), + EMC_REG_BITS_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_UPDATE_STALL, ENABLE), + EMC_REG_BITS_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_START, DISABLE)); + + /* Get whether digital delay locked loops are enabled. */ + const bool has_dll = reg::HasValue(EMC_ADDRESS(EMC_CFG_DIG_DLL), EMC_REG_BITS_ENUM(CFG_DIG_DLL_CFG_DLL_EN, ENABLED)); + if (has_dll) { + /* If they are, disable them. */ + reg::ReadWrite(EMC_ADDRESS(EMC_CFG_DIG_DLL), EMC_REG_BITS_ENUM(CFG_DIG_DLL_CFG_DLL_EN, DISABLED)); + } + + /* Update the EMC timing. */ + UpdateEmcTiming(); + + /* If dll was enabled, wait until both EMC0 and EMC1 have dll disabled. */ + if (has_dll) { + while (!reg::HasValue(EMC0_ADDRESS(EMC_CFG_DIG_DLL), EMC_REG_BITS_ENUM(CFG_DIG_DLL_CFG_DLL_EN, DISABLED))) { /* ... */ } + while (!reg::HasValue(EMC1_ADDRESS(EMC_CFG_DIG_DLL), EMC_REG_BITS_ENUM(CFG_DIG_DLL_CFG_DLL_EN, DISABLED))) { /* ... */ } + } + + /* Stall all reads and writes. */ + reg::Write(EMC_ADDRESS(EMC_REQ_CTRL), EMC_REG_BITS_VALUE(REQ_CTRL_STALL_ALL_READS, 1), + EMC_REG_BITS_VALUE(REQ_CTRL_STALL_ALL_WRITES, 1)); + + /* Wait until both EMC0 and EMC1 have no outstanding transactions. */ + while (!reg::HasValue(EMC0_ADDRESS(EMC_EMC_STATUS), EMC_REG_BITS_ENUM(EMC_STATUS_NO_OUTSTANDING_TRANSACTIONS, COMPLETED))) { /* ... */ } + while (!reg::HasValue(EMC1_ADDRESS(EMC_EMC_STATUS), EMC_REG_BITS_ENUM(EMC_STATUS_NO_OUTSTANDING_TRANSACTIONS, COMPLETED))) { /* ... */ } + + /* Enable self-refresh. */ + reg::Write(EMC_ADDRESS(EMC_SELF_REF), EMC_REG_BITS_ENUM(SELF_REF_SREF_DEV_SELECTN, BOTH), + EMC_REG_BITS_ENUM(SELF_REF_SELF_REF_CMD, ENABLED)); + + /* Wait until both EMC and EMC1 are in self-refresh. */ + const auto desired = reg::HasValue(EMC_ADDRESS(EMC_ADR_CFG), EMC_REG_BITS_ENUM(ADR_CFG_EMEM_NUMDEV, N2)) ? EMC_REG_BITS_ENUM(EMC_STATUS_DRAM_IN_SELF_REFRESH, BOTH_ENABLED) + : EMC_REG_BITS_ENUM(EMC_STATUS_DRAM_DEV0_IN_SELF_REFRESH, ENABLED); + + /* NOTE: Nintendo's sc7 entry firmware has a bug here. */ + /* Instead of waiting for both EMCs to report self-refresh, they just read the EMC_STATUS for each EMC. */ + /* This is incorrect, per documentation. */ + while (!reg::HasValue(EMC0_ADDRESS(EMC_EMC_STATUS), desired)) { /* ... */ } + while (!reg::HasValue(EMC1_ADDRESS(EMC_EMC_STATUS), desired)) { /* ... */ } + } + + void EnableEmcAllSegmentsRefresh() { + constexpr int MR17_PASR_Segment = 17; + + /* Write zeros to MR17_PASR_Segment to enable refresh for all segments for dev0. */ + reg::Write(EMC_ADDRESS(EMC_MRW), EMC_REG_BITS_ENUM (MRW_DEV_SELECTN, DEV0), + EMC_REG_BITS_ENUM (MRW_CNT, EXT1), + EMC_REG_BITS_VALUE(MRW_MA, MR17_PASR_Segment), + EMC_REG_BITS_VALUE(MRW_OP, 0)); + + /* If dev1 exists, do the same for dev1. */ + if (reg::HasValue(EMC_ADDRESS(EMC_ADR_CFG), EMC_REG_BITS_ENUM(ADR_CFG_EMEM_NUMDEV, N2))) { + reg::Write(EMC_ADDRESS(EMC_MRW), EMC_REG_BITS_ENUM (MRW_DEV_SELECTN, DEV1), + EMC_REG_BITS_ENUM (MRW_CNT, EXT1), + EMC_REG_BITS_VALUE(MRW_MA, MR17_PASR_Segment), + EMC_REG_BITS_VALUE(MRW_OP, 0)); + } + } + + void EnableDdrDeepPowerDown() { + /* Read and decode the parameters Nintendo stores in EMC_PMC_SCRATCH3. */ + const u32 scratch3 = reg::Read(EMC_ADDRESS(EMC_PMC_SCRATCH3)); + const bool weak_bias = (scratch3 & reg::EncodeMask(EMC_REG_BITS_MASK(PMC_SCRATCH3_WEAK_BIAS))) == reg::EncodeValue(EMC_REG_BITS_ENUM(PMC_SCRATCH3_WEAK_BIAS, ENABLED)); + const u32 ddr_cntrl = (scratch3 & reg::EncodeMask(EMC_REG_BITS_MASK(PMC_SCRATCH3_DDR_CNTRL))); + + /* Write the decoded value to PMC_DDR_CNTRL. */ + reg::Write(PMC + APBDEV_PMC_DDR_CNTRL, ddr_cntrl); + + /* If weak bias is enabled, set all VTT_E_WB bits in APBDEV_PMC_WEAK_BIAS. */ + if (weak_bias) { + constexpr u32 WeakBiasVttEWbAll = 0x7FFF0000; + reg::Write(PMC + APBDEV_PMC_WEAK_BIAS, WeakBiasVttEWbAll); + } + + /* Request that DPD3 pads power down. */ + constexpr u32 EristaDpd3Mask = 0x0FFFFFFF; + constexpr u32 MarikoDpd3Mask = 0x0FFF9FFF; + if (fuse::GetSocType() == fuse::SocType_Erista) { + RequestAllPadsPowerDown(APBDEV_PMC_IO_DPD3_REQ, EristaDpd3Mask); + } else { + RequestAllPadsPowerDown(APBDEV_PMC_IO_DPD3_REQ, MarikoDpd3Mask); + } + + /* Request that DPD4 pads power down. */ + constexpr u32 Dpd4Mask = 0x0FFF1FFF; + RequestAllPadsPowerDown(APBDEV_PMC_IO_DPD4_REQ, Dpd4Mask); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_dram.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_dram.hpp new file mode 100644 index 00000000..59940d1a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_dram.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::sc7fw { + + void SaveEmcFsp(); + void EnableSdramSelfRefresh(); + void EnableEmcAllSegmentsRefresh(); + void EnableDdrDeepPowerDown(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_exception_vectors.s b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_exception_vectors.s new file mode 100644 index 00000000..49428925 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_exception_vectors.s @@ -0,0 +1,22 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +.section .vectors, "ax", %progbits +.align 3 +.global reset +reset: + b _start + b _ZN3ams5sc7fw16ExceptionHandlerEv diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_main.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_main.cpp new file mode 100644 index 00000000..99c88354 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_main.cpp @@ -0,0 +1,120 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "sc7fw_util.hpp" +#include "sc7fw_dram.hpp" + +namespace ams::sc7fw { + + namespace { + + constexpr inline const uintptr_t PMC = secmon::MemoryRegionPhysicalDevicePmc.GetAddress(); + + void DisableCrail() { + /* Wait for CRAIL to be off. */ + while (!reg::HasValue(PMC + APBDEV_PMC_PWRGATE_STATUS, PMC_REG_BITS_ENUM(PWRGATE_STATUS_CRAIL, OFF))) { /* ... */ } + + /* Set CRAIL to be clamped. */ + reg::ReadWrite(PMC + APBDEV_PMC_SET_SW_CLAMP, PMC_REG_BITS_VALUE(SET_SW_CLAMP_CRAIL, 1)); + + /* Wait for CRAIL to be clamped. */ + while (!reg::HasValue(PMC + APBDEV_PMC_CLAMP_STATUS, PMC_REG_BITS_ENUM(CLAMP_STATUS_CRAIL, ENABLE))) { /* ... */ } + + /* Spin loop for a short while, to allow time for the clamp to take effect. */ + sc7fw::SpinLoop(10); + + /* Initialize i2c-5. */ + i2c::Initialize(i2c::Port_5); + + /* Disable the voltage to CPU. */ + pmic::DisableVddCpu(fuse::GetRegulator()); + + /* Wait 700 microseconds to ensure voltage is disabled. */ + util::WaitMicroSeconds(700); + } + + void DisableAllInterrupts() { + /* Disable all interrupts for bpmp in all interrupt controllers. */ + reg::Write(PRI_ICTLR(ICTLR_COP_IER_CLR), ~0u); + reg::Write(SEC_ICTLR(ICTLR_COP_IER_CLR), ~0u); + reg::Write(TRI_ICTLR(ICTLR_COP_IER_CLR), ~0u); + reg::Write(QUAD_ICTLR(ICTLR_COP_IER_CLR), ~0u); + reg::Write(PENTA_ICTLR(ICTLR_COP_IER_CLR), ~0u); + reg::Write(HEXA_ICTLR(ICTLR_COP_IER_CLR), ~0u); + } + + void EnterSc7() { + /* Disable read buffering and write buffering in the BPMP cache. */ + reg::ReadWrite(AVP_CACHE_ADDR(AVP_CACHE_CONFIG), AVP_CACHE_REG_BITS_ENUM(CONFIG_DISABLE_WB, TRUE), + AVP_CACHE_REG_BITS_ENUM(CONFIG_DISABLE_RB, TRUE)); + + /* Ensure the CPU Rail is turned off. */ + DisableCrail(); + + /* Disable all interrupts. */ + DisableAllInterrupts(); + + /* Save the EMC FSP */ + SaveEmcFsp(); + + /* Enable self-refresh for DRAM */ + EnableSdramSelfRefresh(); + + /* Enable refresh for all EMC devices. */ + EnableEmcAllSegmentsRefresh(); + + /* Enable deep power-down for ddr. */ + EnableDdrDeepPowerDown(); + + /* Enable pad sampling during deep sleep. */ + reg::ReadWrite(PMC + APBDEV_PMC_DPD_SAMPLE, PMC_REG_BITS_ENUM(DPD_SAMPLE_ON, ENABLE)); + reg::Read(PMC + APBDEV_PMC_DPD_SAMPLE); + + /* Wait a while for pad sampling to be enabled. */ + sc7fw::SpinLoop(0x128); + + /* Enter deep sleep. */ + reg::ReadWrite(PMC + APBDEV_PMC_DPD_ENABLE, PMC_REG_BITS_ENUM(DPD_ENABLE_ON, ENABLE)); + + /* Wait forever until we're asleep. */ + AMS_INFINITE_LOOP(); + } + + } + + void Main() { + EnterSc7(); + } + + NORETURN void ExceptionHandler() { + /* Write enable to MAIN_RESET. */ + reg::Write(PMC + APBDEV_PMC_CNTRL, PMC_REG_BITS_ENUM(CNTRL_MAIN_RESET, ENABLE)); + + /* Wait forever until we're reset. */ + AMS_INFINITE_LOOP(); + } + +} + +namespace ams::diag { + + NORETURN void AbortImpl() { + sc7fw::ExceptionHandler(); + } + + #include <exosphere/diag/diag_detailed_assertion_impl.inc> + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_start.s b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_start.s new file mode 100644 index 00000000..9812cc52 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_start.s @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +.section .text.start, "ax", %progbits +.align 3 +.global _start +_start: + /* Set CPSR_cf and CPSR_cf. */ + msr cpsr_f, #0xC0 + msr cpsr_cf, #0xD3 + + /* Set the stack pointer. */ + ldr sp, =0x40005000 + + /* Set our link register to the exception handler. */ + ldr lr, =_ZN3ams5sc7fw16ExceptionHandlerEv + + /* Invoke main. */ + bl _ZN3ams5sc7fw4MainEv + + /* Infinite loop. */ + 1: b 1b \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_util.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_util.hpp new file mode 100644 index 00000000..21d9146b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_util.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::sc7fw { + + void SpinLoop(unsigned int num); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_util_asm.s b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_util_asm.s new file mode 100644 index 00000000..97d4168b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/sc7fw/source/sc7fw_util_asm.s @@ -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 <http://www.gnu.org/licenses/>. + */ + +.section .text._ZN3ams5sc7fw8SpinLoopEj, "ax", %progbits +.global _ZN3ams5sc7fw8SpinLoopEj +.thumb_func +.syntax unified +_ZN3ams5sc7fw8SpinLoopEj: + 1: + /* Subtract one from the count. */ + subs r0, r0, #1 + + /* If we aren't at zero, continue looping. */ + bgt 1b + + /* Return. */ + bx lr \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot.hpp new file mode 100644 index 00000000..9445a198 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::secmon::boot { + + void MakePageTable(); + void UnmapPhysicalIdentityMapping(); + void UnmapDram(); + void LoadMarikoProgram(); + + void InitializeColdBoot(); + + bool VerifySignature(void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *msg, size_t msg_size); + bool VerifyHash(const void *hash, uintptr_t msg, size_t msg_size); + + bool VerifyBootConfigSignature(pkg1::BootConfig &bc, const void *mod, size_t mod_size); + bool VerifyBootConfigEcid(const pkg1::BootConfig &bc); + + void CalculatePackage2Hash(se::Sha256Hash *dst, const pkg2::Package2Meta &meta, uintptr_t package2_start); + + bool VerifyPackage2Signature(pkg2::Package2Header &header, const void *mod, size_t mod_size); + void DecryptPackage2(void *dst, size_t dst_size, const void *src, size_t src_size, const void *key, size_t key_size, const void *iv, size_t iv_size, u8 key_generation); + + bool VerifyPackage2Meta(const pkg2::Package2Meta &meta); + bool VerifyPackage2Version(const pkg2::Package2Meta &meta); + bool VerifyPackage2Payloads(const pkg2::Package2Meta &meta, uintptr_t payload_address); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_cache.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_cache.cpp new file mode 100644 index 00000000..21ec0877 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_cache.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_boot_cache.hpp" + +namespace ams::secmon::boot { + + #include "../secmon_cache_impl.inc" + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_cache.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_cache.hpp new file mode 100644 index 00000000..12f66846 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_cache.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::secmon::boot { + + #include "../secmon_cache.inc" + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_config.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_config.cpp new file mode 100644 index 00000000..75f427f2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_config.cpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_boot.hpp" + +namespace ams::secmon::boot { + + bool VerifyBootConfigSignature(pkg1::BootConfig &bc, const void *mod, size_t mod_size) { + return VerifySignature(std::addressof(bc.signature), sizeof(bc.signature), mod, mod_size, std::addressof(bc.signed_data), sizeof(bc.signed_data)); + } + + bool VerifyBootConfigEcid(const pkg1::BootConfig &bc) { + /* Get the ecid. */ + br::BootEcid ecid; + fuse::GetEcid(std::addressof(ecid)); + + /* Verify it matches. */ + return crypto::IsSameBytes(std::addressof(ecid), bc.signed_data.ecid, sizeof(ecid)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_functions.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_functions.cpp new file mode 100644 index 00000000..d391e032 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_functions.cpp @@ -0,0 +1,203 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../secmon_error.hpp" +#include "secmon_boot.hpp" +#include "secmon_boot_cache.hpp" +#include "secmon_boot_functions.hpp" + +namespace ams::secmon::boot { + + namespace { + + constexpr inline uintptr_t SYSCTR0 = MemoryRegionVirtualDeviceSysCtr0.GetAddress(); + + NOINLINE void DecryptPayload(uintptr_t dst, uintptr_t src, size_t size, const void *iv, size_t iv_size, u8 key_generation) { + secmon::boot::DecryptPackage2(reinterpret_cast<void *>(dst), size, reinterpret_cast<void *>(src), size, secmon::boot::GetPackage2AesKey(), crypto::AesEncryptor128::KeySize, iv, iv_size, key_generation); + } + + u32 GetChipId() { + constexpr u32 ChipIdErista = 0x210; + constexpr u32 ChipIdMariko = 0x214; + + switch (GetSocType()) { + case fuse::SocType_Erista: return ChipIdErista; + case fuse::SocType_Mariko: return ChipIdMariko; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + } + + void CheckVerifyResult(bool verify_result, pkg1::ErrorInfo error_info, const char *message) { + if (!verify_result) { + secmon::SetError(error_info); + AMS_ABORT(message); + } + } + + void ClearIramBootCode() { + /* Clear the boot code image from where it was loaded in IRAM. */ + util::ClearMemory(MemoryRegionPhysicalIramBootCodeCode.GetPointer(), MemoryRegionPhysicalIramBootCodeCode.GetSize()); + } + + void ClearIramBootKeys() { + /* Clear the boot keys from where they were loaded in IRAM. */ + util::ClearMemory(MemoryRegionPhysicalIramBootCodeKeys.GetPointer(), MemoryRegionPhysicalIramBootCodeKeys.GetSize()); + } + + void ClearIramDebugCode() { + /* Clear the boot code image from where it was loaded in IRAM. */ + util::ClearMemory(MemoryRegionPhysicalDebugCode.GetPointer(), MemoryRegionPhysicalDebugCode.GetSize()); + } + + void WaitForNxBootloader(const pkg1::SecureMonitorParameters ¶ms, pkg1::BootloaderState state) { + /* Check NX Bootloader's state once per microsecond until it's advanced enough. */ + while (params.bootloader_state < state) { + util::WaitMicroSeconds(1); + } + } + + void LoadBootConfig(const void *src) { + pkg1::BootConfig * const dst = secmon::impl::GetBootConfigStorage(); + + if (pkg1::IsProduction()) { + std::memset(dst, 0, sizeof(*dst)); + } else { + hw::FlushDataCache(src, sizeof(*dst)); + hw::DataSynchronizationBarrierInnerShareable(); + std::memcpy(dst, src, sizeof(*dst)); + } + } + + void VerifyOrClearBootConfig() { + /* On production hardware, the boot config is already cleared. */ + if (pkg1::IsProduction()) { + return; + } + + pkg1::BootConfig * const bc = secmon::impl::GetBootConfigStorage(); + + /* Determine if the bc is valid for the device. */ + bool valid_for_device = false; + { + const bool valid_signature = secmon::boot::VerifyBootConfigSignature(*bc, secmon::boot::GetBootConfigRsaModulus(), se::RsaSize); + if (valid_signature) { + valid_for_device = secmon::boot::VerifyBootConfigEcid(*bc); + } + } + + /* If the boot config is not valid for the device, clear its signed data. */ + if (!valid_for_device) { + util::ClearMemory(std::addressof(bc->signed_data), sizeof(bc->signed_data)); + } + } + + void EnableTsc(u64 initial_tsc_value) { + /* Write the initial value to the CNTCV registers. */ + const u32 lo = static_cast<u32>(initial_tsc_value >> 0); + const u32 hi = static_cast<u32>(initial_tsc_value >> 32); + + reg::Write(SYSCTR0 + SYSCTR0_CNTCV0, lo); + reg::Write(SYSCTR0 + SYSCTR0_CNTCV1, hi); + + /* Configure the system counter control register. */ + reg::Write(SYSCTR0 + SYSCTR0_CNTCR, SYSCTR0_REG_BITS_ENUM(CNTCR_HDBG, ENABLE), + SYSCTR0_REG_BITS_ENUM(CNTCR_EN, ENABLE)); + } + + void WriteGpuCarveoutMagicNumbers() { + /* Define the magic numbers. */ + constexpr u32 GpuMagicNumber = 0xC0EDBBCC; + constexpr u32 SkuInfo = 0x83; + constexpr u32 HdcpMicroCodeVersion = 0x2; + + /* Get our pointers. */ + u32 *gpu_magic = MemoryRegionDramGpuCarveout.GetEndPointer<u32>() - (0x004 / sizeof(*gpu_magic)); + u32 *tsec_magic = MemoryRegionDramGpuCarveout.GetEndPointer<u32>() - (0x100 / sizeof(*tsec_magic)); + + /* Write the gpu magic number. */ + gpu_magic[0] = GpuMagicNumber; + + /* Write the tsec magic numbers. */ + tsec_magic[0] = SkuInfo; + tsec_magic[1] = HdcpMicroCodeVersion; + tsec_magic[2] = GetChipId(); + + /* Flush the magic numbers. */ + hw::FlushDataCache(gpu_magic, 1 * sizeof(u32)); + hw::FlushDataCache(tsec_magic, 3 * sizeof(u32)); + hw::DataSynchronizationBarrierInnerShareable(); + } + + void UpdateBootConfigForPackage2Header(const pkg2::Package2Header &header) { + /* Check for all-zeroes signature. */ + const bool is_unsigned = header.signature[0] == 0 && crypto::IsSameBytes(header.signature, header.signature + 1, sizeof(header.signature) - 1); + secmon::impl::GetBootConfigStorage()->signed_data.SetPackage2SignatureVerificationDisabled(is_unsigned); + + /* Check for valid magic. */ + const bool is_decrypted = crypto::IsSameBytes(header.meta.magic, pkg2::Package2Meta::Magic::String, sizeof(header.meta.magic)); + secmon::impl::GetBootConfigStorage()->signed_data.SetPackage2EncryptionDisabled(is_decrypted); + } + + void VerifyPackage2HeaderSignature(pkg2::Package2Header &header, bool verify) { + const u8 * const mod = secmon::boot::GetPackage2RsaModulus(pkg1::IsProductionForPublicKey()); + const size_t mod_size = se::RsaSize; + if (verify) { + CheckVerifyResult(secmon::boot::VerifyPackage2Signature(header, mod, mod_size), pkg1::ErrorInfo_InvalidPackage2Signature, "pkg2 sign FAIL"); + } + } + + void DecryptPackage2Header(pkg2::Package2Meta *dst, const pkg2::Package2Meta &src, bool encrypted) { + if (encrypted) { + constexpr int IvSize = 0x10; + + /* Decrypt the header. */ + DecryptPackage2(dst, sizeof(*dst), std::addressof(src), sizeof(src), secmon::boot::GetPackage2AesKey(), crypto::AesEncryptor128::KeySize, std::addressof(src), IvSize, src.GetKeyGeneration()); + + /* Copy back the iv, which encodes encrypted metadata. */ + std::memcpy(dst, std::addressof(src), IvSize); + } else { + std::memcpy(dst, std::addressof(src), sizeof(*dst)); + } + } + + void VerifyPackage2Header(const pkg2::Package2Meta &meta) { + /* Validate the metadata. */ + CheckVerifyResult(VerifyPackage2Meta(meta), pkg1::ErrorInfo_InvalidPackage2Meta, "pkg2 meta FAIL"); + + /* Validate the version. */ + CheckVerifyResult(VerifyPackage2Version(meta), pkg1::ErrorInfo_InvalidPackage2Version, "pkg2 version FAIL"); + } + + void DecryptAndLoadPackage2Payloads(uintptr_t dst, const pkg2::Package2Meta &meta, uintptr_t src, bool encrypted) { + /* Get the key generation for crypto. */ + const u8 key_generation = meta.GetKeyGeneration(); + /* Decrypt or load each payload in order. */ + for (int i = 0; i < pkg2::PayloadCount; ++i) { + AMS_SECMON_LOG("pkg2 payload[%d]: %09lx -> %09lx size=%08x\n", i, src, dst + meta.payload_offsets[i], meta.payload_sizes[i]); + + if (encrypted) { + DecryptPayload(dst + meta.payload_offsets[i], src, meta.payload_sizes[i], meta.payload_ivs[i], sizeof(meta.payload_ivs[i]), key_generation); + } else { + std::memcpy(reinterpret_cast<void *>(dst + meta.payload_offsets[i]), reinterpret_cast<void *>(src), meta.payload_sizes[i]); + } + + src += meta.payload_sizes[i]; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_functions.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_functions.hpp new file mode 100644 index 00000000..368d532c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_functions.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::secmon::boot { + + void ClearIramBootCode(); + void ClearIramBootKeys(); + void ClearIramDebugCode(); + + void WaitForNxBootloader(const pkg1::SecureMonitorParameters ¶ms, pkg1::BootloaderState state); + + void LoadBootConfig(const void *src); + void VerifyOrClearBootConfig(); + + void EnableTsc(u64 initial_tsc_value); + + void WriteGpuCarveoutMagicNumbers(); + + void UpdateBootConfigForPackage2Header(const pkg2::Package2Header &header); + void VerifyPackage2HeaderSignature(pkg2::Package2Header &header, bool verify); + void DecryptPackage2Header(pkg2::Package2Meta *dst, const pkg2::Package2Meta &src, bool encrypted); + + void VerifyPackage2Header(const pkg2::Package2Meta &meta); + + void DecryptAndLoadPackage2Payloads(uintptr_t dst, const pkg2::Package2Meta &meta, uintptr_t src, bool encrypted); + + void CheckVerifyResult(bool verify_result, pkg1::ErrorInfo error_info, const char *message); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_key_data.s b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_key_data.s new file mode 100644 index 00000000..5ff3809d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_key_data.s @@ -0,0 +1,192 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + + +.section .volatile_keys._ZN3ams6secmon4boot15VolatileKeyDataE, "aw", %progbits +.global _ZN3ams6secmon4boot15VolatileKeyDataE +_ZN3ams6secmon4boot15VolatileKeyDataE: +/* BootConfig Rsa Modulus. */ +.byte 0xB5, 0x96, 0x87, 0x31, 0x39, 0xAA, 0xBB, 0x3C, 0x28, 0xF3, 0xF0, 0x65, 0xF1, 0x50, 0x70, 0x64 +.byte 0xE6, 0x6C, 0x97, 0x50, 0xCD, 0xA6, 0xEE, 0xEA, 0xC3, 0x8F, 0xE6, 0xB5, 0x81, 0x54, 0x65, 0x33 +.byte 0x1B, 0x88, 0x4B, 0xCE, 0x9F, 0x53, 0xDF, 0xE4, 0xF6, 0xAD, 0xC3, 0x78, 0xD7, 0x3C, 0xD1, 0xDB +.byte 0x27, 0x21, 0xA0, 0x24, 0x30, 0x2D, 0x98, 0x41, 0xA8, 0xDF, 0x50, 0x7D, 0xAB, 0xCE, 0x00, 0xD9 +.byte 0xCB, 0xAC, 0x8F, 0x37, 0xF5, 0x53, 0xE4, 0x97, 0x1F, 0x13, 0x3C, 0x19, 0xFF, 0x05, 0xA7, 0x3B +.byte 0xF6, 0xF4, 0x01, 0xDE, 0xF0, 0xC3, 0x77, 0x7B, 0x83, 0xBA, 0xAF, 0x99, 0x30, 0x94, 0x87, 0x25 +.byte 0x4E, 0x54, 0x42, 0x3F, 0xAC, 0x27, 0xF9, 0xCC, 0x87, 0xDD, 0xAE, 0xF2, 0x54, 0xF3, 0x97, 0x49 +.byte 0xF4, 0xB0, 0xF8, 0x6D, 0xDA, 0x60, 0xE0, 0xFD, 0x57, 0xAE, 0x55, 0xA9, 0x76, 0xEA, 0x80, 0x24 +.byte 0xA0, 0x04, 0x7D, 0xBE, 0xD1, 0x81, 0xD3, 0x0C, 0x95, 0xCF, 0xB7, 0xE0, 0x2D, 0x21, 0x21, 0xFF +.byte 0x97, 0x1E, 0xB3, 0xD7, 0x9F, 0xBB, 0x33, 0x0C, 0x23, 0xC5, 0x88, 0x4A, 0x33, 0xB9, 0xC9, 0x4E +.byte 0x1E, 0x65, 0x51, 0x45, 0xDE, 0xF9, 0x64, 0x7C, 0xF0, 0xBF, 0x11, 0xB4, 0x93, 0x8D, 0x5D, 0xC6 +.byte 0xAB, 0x37, 0x9E, 0xE9, 0x39, 0xC1, 0xC8, 0xDB, 0xB9, 0xFE, 0x45, 0xCE, 0x7B, 0xDD, 0x72, 0xD9 +.byte 0x6F, 0x68, 0x13, 0xC0, 0x4B, 0xBA, 0x00, 0xF4, 0x1E, 0x89, 0x71, 0x91, 0x26, 0xA6, 0x46, 0x12 +.byte 0xDF, 0x29, 0x6B, 0xC2, 0x5A, 0x53, 0xAF, 0xB9, 0x5B, 0xFD, 0x13, 0x9F, 0xD1, 0x8A, 0x7C, 0xB5 +.byte 0x04, 0xFD, 0x69, 0xEA, 0x23, 0xB4, 0x6D, 0x16, 0x21, 0x98, 0x54, 0xB4, 0xDF, 0xE6, 0xAB, 0x93 +.byte 0x36, 0xB6, 0xD2, 0x43, 0xCF, 0x2B, 0x98, 0x1D, 0x45, 0xC9, 0xBB, 0x20, 0x42, 0xB1, 0x9D, 0x1D + +/* Package2 Development Rsa Modulus. */ +.byte 0xB3, 0x65, 0x54, 0xFB, 0x0A, 0xB0, 0x1E, 0x85, 0xA7, 0xF6, 0xCF, 0x91, 0x8E, 0xBA, 0x96, 0x99 +.byte 0x0D, 0x8B, 0x91, 0x69, 0x2A, 0xEE, 0x01, 0x20, 0x4F, 0x34, 0x5C, 0x2C, 0x4F, 0x4E, 0x37, 0xC7 +.byte 0xF1, 0x0B, 0xD4, 0xCD, 0xA1, 0x7F, 0x93, 0xF1, 0x33, 0x59, 0xCE, 0xB1, 0xE9, 0xDD, 0x26, 0xE6 +.byte 0xF3, 0xBB, 0x77, 0x87, 0x46, 0x7A, 0xD6, 0x4E, 0x47, 0x4A, 0xD1, 0x41, 0xB7, 0x79, 0x4A, 0x38 +.byte 0x06, 0x6E, 0xCF, 0x61, 0x8F, 0xCD, 0xC1, 0x40, 0x0B, 0xFA, 0x26, 0xDC, 0xC0, 0x34, 0x51, 0x83 +.byte 0xD9, 0x3B, 0x11, 0x54, 0x3B, 0x96, 0x27, 0x32, 0x9A, 0x95, 0xBE, 0x1E, 0x68, 0x11, 0x50, 0xA0 +.byte 0x6B, 0x10, 0xA8, 0x83, 0x8B, 0xF5, 0xFC, 0xBC, 0x90, 0x84, 0x7A, 0x5A, 0x5C, 0x43, 0x52, 0xE6 +.byte 0xC8, 0x26, 0xE9, 0xFE, 0x06, 0xA0, 0x8B, 0x53, 0x0F, 0xAF, 0x1E, 0xC4, 0x1C, 0x0B, 0xCF, 0x50 +.byte 0x1A, 0xA4, 0xF3, 0x5C, 0xFB, 0xF0, 0x97, 0xE4, 0xDE, 0x32, 0x0A, 0x9F, 0xE3, 0x5A, 0xAA, 0xB7 +.byte 0x44, 0x7F, 0x5C, 0x33, 0x60, 0xB9, 0x0F, 0x22, 0x2D, 0x33, 0x2A, 0xE9, 0x69, 0x79, 0x31, 0x42 +.byte 0x8F, 0xE4, 0x3A, 0x13, 0x8B, 0xE7, 0x26, 0xBD, 0x08, 0x87, 0x6C, 0xA6, 0xF2, 0x73, 0xF6, 0x8E +.byte 0xA7, 0xF2, 0xFE, 0xFB, 0x6C, 0x28, 0x66, 0x0D, 0xBD, 0xD7, 0xEB, 0x42, 0xA8, 0x78, 0xE6, 0xB8 +.byte 0x6B, 0xAE, 0xC7, 0xA9, 0xE2, 0x40, 0x6E, 0x89, 0x20, 0x82, 0x25, 0x8E, 0x3C, 0x6A, 0x60, 0xD7 +.byte 0xF3, 0x56, 0x8E, 0xEC, 0x8D, 0x51, 0x8A, 0x63, 0x3C, 0x04, 0x78, 0x23, 0x0E, 0x90, 0x0C, 0xB4 +.byte 0xE7, 0x86, 0x3B, 0x4F, 0x8E, 0x13, 0x09, 0x47, 0x32, 0x0E, 0x04, 0xB8, 0x4D, 0x5B, 0xB0, 0x46 +.byte 0x71, 0xB0, 0x5C, 0xF4, 0xAD, 0x63, 0x4F, 0xC5, 0xE2, 0xAC, 0x1E, 0xC4, 0x33, 0x96, 0x09, 0x7B + +/* Package2 Production Rsa Modulus. */ +.byte 0x8D, 0x13, 0xA7, 0x77, 0x6A, 0xE5, 0xDC, 0xC0, 0x3B, 0x25, 0xD0, 0x58, 0xE4, 0x20, 0x69, 0x59 +.byte 0x55, 0x4B, 0xAB, 0x70, 0x40, 0x08, 0x28, 0x07, 0xA8, 0xA7, 0xFD, 0x0F, 0x31, 0x2E, 0x11, 0xFE +.byte 0x47, 0xA0, 0xF9, 0x9D, 0xDF, 0x80, 0xDB, 0x86, 0x5A, 0x27, 0x89, 0xCD, 0x97, 0x6C, 0x85, 0xC5 +.byte 0x6C, 0x39, 0x7F, 0x41, 0xF2, 0xFF, 0x24, 0x20, 0xC3, 0x95, 0xA6, 0xF7, 0x9D, 0x4A, 0x45, 0x74 +.byte 0x8B, 0x5D, 0x28, 0x8A, 0xC6, 0x99, 0x35, 0x68, 0x85, 0xA5, 0x64, 0x32, 0x80, 0x9F, 0xD3, 0x48 +.byte 0x39, 0xA2, 0x1D, 0x24, 0x67, 0x69, 0xDF, 0x75, 0xAC, 0x12, 0xB5, 0xBD, 0xC3, 0x29, 0x90, 0xBE +.byte 0x37, 0xE4, 0xA0, 0x80, 0x9A, 0xBE, 0x36, 0xBF, 0x1F, 0x2C, 0xAB, 0x2B, 0xAD, 0xF5, 0x97, 0x32 +.byte 0x9A, 0x42, 0x9D, 0x09, 0x8B, 0x08, 0xF0, 0x63, 0x47, 0xA3, 0xE9, 0x1B, 0x36, 0xD8, 0x2D, 0x8A +.byte 0xD7, 0xE1, 0x54, 0x11, 0x95, 0xE4, 0x45, 0x88, 0x69, 0x8A, 0x2B, 0x35, 0xCE, 0xD0, 0xA5, 0x0B +.byte 0xD5, 0x5D, 0xAC, 0xDB, 0xAF, 0x11, 0x4D, 0xCA, 0xB8, 0x1E, 0xE7, 0x01, 0x9E, 0xF4, 0x46, 0xA3 +.byte 0x8A, 0x94, 0x6D, 0x76, 0xBD, 0x8A, 0xC8, 0x3B, 0xD2, 0x31, 0x58, 0x0C, 0x79, 0xA8, 0x26, 0xE9 +.byte 0xD1, 0x79, 0x9C, 0xCB, 0xD4, 0x2B, 0x6A, 0x4F, 0xC6, 0xCC, 0xCF, 0x90, 0xA7, 0xB9, 0x98, 0x47 +.byte 0xFD, 0xFA, 0x4C, 0x6C, 0x6F, 0x81, 0x87, 0x3B, 0xCA, 0xB8, 0x50, 0xF6, 0x3E, 0x39, 0x5D, 0x4D +.byte 0x97, 0x3F, 0x0F, 0x35, 0x39, 0x53, 0xFB, 0xFA, 0xCD, 0xAB, 0xA8, 0x7A, 0x62, 0x9A, 0x3F, 0xF2 +.byte 0x09, 0x27, 0x96, 0x3F, 0x07, 0x9A, 0x91, 0xF7, 0x16, 0xBF, 0xC6, 0x3A, 0x82, 0x5A, 0x4B, 0xCF +.byte 0x49, 0x50, 0x95, 0x8C, 0x55, 0x80, 0x7E, 0x39, 0xB1, 0x48, 0x05, 0x1E, 0x21, 0xC7, 0x24, 0x4F + +/* Package2 Aes Key Source. */ +.byte 0xFB, 0x8B, 0x6A, 0x9C, 0x79, 0x00, 0xC8, 0x49, 0xEF, 0xD2, 0x4D, 0x85, 0x4D, 0x30, 0xA0, 0xC7 + +/* Master Key Source. */ +.byte 0xD8, 0xA2, 0x41, 0x0A, 0xC6, 0xC5, 0x90, 0x01, 0xC6, 0x1D, 0x6A, 0x26, 0x7C, 0x51, 0x3F, 0x3C + +/* Device Master Key Source Kek Source. */ +.byte 0x0C, 0x91, 0x09, 0xDB, 0x93, 0x93, 0x07, 0x81, 0x07, 0x3C, 0xC4, 0x16, 0x22, 0x7C, 0x6C, 0x28 + +/* NOTE: These are just latest-master-kek encrypted with KEK. */ +/* We can get away with only including latest because exosphere supports newer-than-expected master key in engine. */ +/* TODO: Update on next change of keys. */ +/* Mariko Development Master Kek Source. */ +.byte 0x8C, 0x2E, 0xC1, 0x1C, 0xA0, 0x28, 0x35, 0xFC, 0x9A, 0x9F, 0x1D, 0x9B, 0x4E, 0xDF, 0x1E, 0x03 + +/* Mariko Production Master Kek Source. */ +.byte 0x1A, 0x31, 0x62, 0x87, 0xA8, 0x09, 0xCA, 0xF8, 0x69, 0x15, 0x45, 0xC2, 0x6B, 0xAA, 0x5A, 0x8A + +/* Development Master Key Vectors. */ +.byte 0x46, 0x22, 0xB4, 0x51, 0x9A, 0x7E, 0xA7, 0x7F, 0x62, 0xA1, 0x1F, 0x8F, 0xC5, 0x3A, 0xDB, 0xFE /* Zeroes encrypted with Master Key 00. */ +.byte 0x39, 0x33, 0xF9, 0x31, 0xBA, 0xE4, 0xA7, 0x21, 0x2C, 0xDD, 0xB7, 0xD8, 0xB4, 0x4E, 0x37, 0x23 /* Master key 00 encrypted with Master key 01. */ +.byte 0x97, 0x29, 0xB0, 0x32, 0x43, 0x14, 0x8C, 0xA6, 0x85, 0xE9, 0x5A, 0x94, 0x99, 0x39, 0xAC, 0x5D /* Master key 01 encrypted with Master key 02. */ +.byte 0x2C, 0xCA, 0x9C, 0x31, 0x1E, 0x07, 0xB0, 0x02, 0x97, 0x0A, 0xD8, 0x03, 0xA2, 0x76, 0x3F, 0xA3 /* Master key 02 encrypted with Master key 03. */ +.byte 0x9B, 0x84, 0x76, 0x14, 0x72, 0x94, 0x52, 0xCB, 0x54, 0x92, 0x9B, 0xC4, 0x8C, 0x5B, 0x0F, 0xBA /* Master key 03 encrypted with Master key 04. */ +.byte 0x78, 0xD5, 0xF1, 0x20, 0x3D, 0x16, 0xE9, 0x30, 0x32, 0x27, 0x34, 0x6F, 0xCF, 0xE0, 0x27, 0xDC /* Master key 04 encrypted with Master key 05. */ +.byte 0x6F, 0xD2, 0x84, 0x1D, 0x05, 0xEC, 0x40, 0x94, 0x5F, 0x18, 0xB3, 0x81, 0x09, 0x98, 0x8D, 0x4E /* Master key 05 encrypted with Master key 06. */ +.byte 0x37, 0xAF, 0xAB, 0x35, 0x79, 0x09, 0xD9, 0x48, 0x29, 0xD2, 0xDB, 0xA5, 0xA5, 0xF5, 0x30, 0x19 /* Master key 06 encrypted with Master key 07. */ +.byte 0xEC, 0xE1, 0x46, 0x89, 0x37, 0xFD, 0xD2, 0x15, 0x8C, 0x3F, 0x24, 0x82, 0xEF, 0x49, 0x68, 0x04 /* Master key 07 encrypted with Master key 08. */ +.byte 0x43, 0x3D, 0xC5, 0x3B, 0xEF, 0x91, 0x02, 0x21, 0x61, 0x54, 0x63, 0x8A, 0x35, 0xE7, 0xCA, 0xEE /* Master key 08 encrypted with Master key 09. */ +.byte 0x6C, 0x2E, 0xCD, 0xB3, 0x34, 0x61, 0x77, 0xF5, 0xF9, 0xB1, 0xDD, 0x61, 0x98, 0x19, 0x3E, 0xD4 /* Master key 09 encrypted with Master key 0A. */ +.byte 0x21, 0x88, 0x6B, 0x10, 0x9E, 0x83, 0xD6, 0x52, 0xAB, 0x08, 0xDB, 0x6D, 0x39, 0xFF, 0x1C, 0x9C /* Master key 0A encrypted with Master key 0B. */ +.byte 0x8A, 0xCE, 0xC4, 0x7F, 0xBE, 0x08, 0x61, 0x88, 0xD3, 0x73, 0x64, 0x51, 0xE2, 0xB6, 0x53, 0x15 /* Master key 0B encrypted with Master key 0C. */ +.byte 0x08, 0xE0, 0xF4, 0xBE, 0xAA, 0x6E, 0x5A, 0xC3, 0xA6, 0xBC, 0xFE, 0xB9, 0xE2, 0xA3, 0x24, 0x12 /* Master key 0C encrypted with Master key 0D. */ +.byte 0xD6, 0x80, 0x98, 0xC0, 0xFA, 0xC7, 0x13, 0xCB, 0x93, 0xD2, 0x0B, 0x82, 0x4C, 0xA1, 0x7B, 0x8D /* Master key 0D encrypted with Master key 0E. */ +.byte 0x78, 0x66, 0x19, 0xBD, 0x86, 0xE7, 0xC1, 0x09, 0x9B, 0x6F, 0x92, 0xB2, 0x58, 0x7D, 0xCF, 0x26 /* Master key 0E encrypted with Master key 0F. */ +.byte 0x39, 0x1E, 0x7E, 0xF8, 0x7E, 0x73, 0xEA, 0x6F, 0xAF, 0x00, 0x3A, 0xB4, 0xAA, 0xB8, 0xB7, 0x59 /* Master key 0F encrypted with Master key 10. */ +.byte 0x0C, 0x75, 0x39, 0x15, 0x53, 0xEA, 0x81, 0x11, 0xA3, 0xE0, 0xDC, 0x3D, 0x0E, 0x76, 0xC6, 0xB8 /* Master key 10 encrypted with Master key 11. */ +.byte 0x90, 0x64, 0xF9, 0x08, 0x29, 0x88, 0xD4, 0xDC, 0x73, 0xA4, 0xA1, 0x13, 0x9E, 0x59, 0x85, 0xA0 /* Master key 11 encrypted with Master key 12. */ +.byte 0x94, 0x46, 0x3B, 0xFA, 0x7D, 0xB9, 0xE2, 0x94, 0xC2, 0x9D, 0xB9, 0xA4, 0xB2, 0x56, 0xCA, 0xFE /* Master key 12 encrypted with Master key 13. */ + +/* Production Master Key Vectors. */ +.byte 0x0C, 0xF0, 0x59, 0xAC, 0x85, 0xF6, 0x26, 0x65, 0xE1, 0xE9, 0x19, 0x55, 0xE6, 0xF2, 0x67, 0x3D /* Zeroes encrypted with Master Key 00. */ +.byte 0x29, 0x4C, 0x04, 0xC8, 0xEB, 0x10, 0xED, 0x9D, 0x51, 0x64, 0x97, 0xFB, 0xF3, 0x4D, 0x50, 0xDD /* Master key 00 encrypted with Master key 01. */ +.byte 0xDE, 0xCF, 0xEB, 0xEB, 0x10, 0xAE, 0x74, 0xD8, 0xAD, 0x7C, 0xF4, 0x9E, 0x62, 0xE0, 0xE8, 0x72 /* Master key 01 encrypted with Master key 02. */ +.byte 0x0A, 0x0D, 0xDF, 0x34, 0x22, 0x06, 0x6C, 0xA4, 0xE6, 0xB1, 0xEC, 0x71, 0x85, 0xCA, 0x4E, 0x07 /* Master key 02 encrypted with Master key 03. */ +.byte 0x6E, 0x7D, 0x2D, 0xC3, 0x0F, 0x59, 0xC8, 0xFA, 0x87, 0xA8, 0x2E, 0xD5, 0x89, 0x5E, 0xF3, 0xE9 /* Master key 03 encrypted with Master key 04. */ +.byte 0xEB, 0xF5, 0x6F, 0x83, 0x61, 0x9E, 0xF8, 0xFA, 0xE0, 0x87, 0xD7, 0xA1, 0x4E, 0x25, 0x36, 0xEE /* Master key 04 encrypted with Master key 05. */ +.byte 0x1E, 0x1E, 0x22, 0xC0, 0x5A, 0x33, 0x3C, 0xB9, 0x0B, 0xA9, 0x03, 0x04, 0xBA, 0xDB, 0x07, 0x57 /* Master key 05 encrypted with Master key 06. */ +.byte 0xA4, 0xD4, 0x52, 0x6F, 0xD1, 0xE4, 0x36, 0xAA, 0x9F, 0xCB, 0x61, 0x27, 0x1C, 0x67, 0x65, 0x1F /* Master key 06 encrypted with Master key 07. */ +.byte 0xEA, 0x60, 0xB3, 0xEA, 0xCE, 0x8F, 0x24, 0x46, 0x7D, 0x33, 0x9C, 0xD1, 0xBC, 0x24, 0x98, 0x29 /* Master key 07 encrypted with Master key 08. */ +.byte 0x4D, 0xD9, 0x98, 0x42, 0x45, 0x0D, 0xB1, 0x3C, 0x52, 0x0C, 0x9A, 0x44, 0xBB, 0xAD, 0xAF, 0x80 /* Master key 08 encrypted with Master key 09. */ +.byte 0xB8, 0x96, 0x9E, 0x4A, 0x00, 0x0D, 0xD6, 0x28, 0xB3, 0xD1, 0xDB, 0x68, 0x5F, 0xFB, 0xE1, 0x2A /* Master key 09 encrypted with Master key 0A. */ +.byte 0xC1, 0x8D, 0x16, 0xBB, 0x2A, 0xE4, 0x1D, 0xD4, 0xC2, 0xC1, 0xB6, 0x40, 0x94, 0x35, 0x63, 0x98 /* Master key 0A encrypted with Master key 0B. */ +.byte 0xA3, 0x24, 0x65, 0x75, 0xEA, 0xCC, 0x6E, 0x8D, 0xFB, 0x5A, 0x16, 0x50, 0x74, 0xD2, 0x15, 0x06 /* Master key 0B encrypted with Master key 0C. */ +.byte 0x83, 0x67, 0xAF, 0x01, 0xCF, 0x93, 0xA1, 0xAB, 0x80, 0x45, 0xF7, 0x3F, 0x72, 0xFD, 0x3B, 0x38 /* Master key 0C encrypted with Master key 0D. */ +.byte 0xB1, 0x81, 0xA6, 0x0D, 0x72, 0xC7, 0xEE, 0x15, 0x21, 0xF3, 0xC0, 0xB5, 0x6B, 0x61, 0x6D, 0xE7 /* Master key 0D encrypted with Master key 0E. */ +.byte 0xAF, 0x11, 0x4C, 0x67, 0x17, 0x7A, 0x52, 0x43, 0xF7, 0x70, 0x2F, 0xC7, 0xEF, 0x81, 0x72, 0x16 /* Master key 0E encrypted with Master key 0F. */ +.byte 0x25, 0x12, 0x8B, 0xCB, 0xB5, 0x46, 0xA1, 0xF8, 0xE0, 0x52, 0x15, 0xB7, 0x0B, 0x57, 0x00, 0xBD /* Master key 0F encrypted with Master key 10. */ +.byte 0x58, 0x15, 0xD2, 0xF6, 0x8A, 0xE8, 0x19, 0xAB, 0xFB, 0x2D, 0x52, 0x9D, 0xE7, 0x55, 0xF3, 0x93 /* Master key 10 encrypted with Master key 11. */ +.byte 0x4A, 0x01, 0x3B, 0xC7, 0x44, 0x6E, 0x45, 0xBD, 0xE6, 0x5E, 0x2B, 0xEC, 0x07, 0x37, 0x52, 0x86 /* Master key 11 encrypted with Master key 12. */ +.byte 0x97, 0xE4, 0x11, 0xAB, 0x22, 0x72, 0x1A, 0x1F, 0x70, 0x5C, 0x00, 0xB3, 0x96, 0x30, 0x05, 0x28 /* Master key 12 encrypted with Master key 13. */ + +/* Device Master Key Source Sources. */ +.byte 0x8B, 0x4E, 0x1C, 0x22, 0x42, 0x07, 0xC8, 0x73, 0x56, 0x94, 0x08, 0x8B, 0xCC, 0x47, 0x0F, 0x5D /* 4.0.0 Device Master Key Source Source. */ +.byte 0x6C, 0xEF, 0xC6, 0x27, 0x8B, 0xEC, 0x8A, 0x91, 0x99, 0xAB, 0x24, 0xAC, 0x4F, 0x1C, 0x8F, 0x1C /* 5.0.0 Device Master Key Source Source. */ +.byte 0x70, 0x08, 0x1B, 0x97, 0x44, 0x64, 0xF8, 0x91, 0x54, 0x9D, 0xC6, 0x84, 0x8F, 0x1A, 0xB2, 0xE4 /* 6.0.0 Device Master Key Source Source. */ +.byte 0x8E, 0x09, 0x1F, 0x7A, 0xBB, 0xCA, 0x6A, 0xFB, 0xB8, 0x9B, 0xD5, 0xC1, 0x25, 0x9C, 0xA9, 0x17 /* 6.2.0 Device Master Key Source Source. */ +.byte 0x8F, 0x77, 0x5A, 0x96, 0xB0, 0x94, 0xFD, 0x8D, 0x28, 0xE4, 0x19, 0xC8, 0x16, 0x1C, 0xDB, 0x3D /* 7.0.0 Device Master Key Source Source. */ +.byte 0x67, 0x62, 0xD4, 0x8E, 0x55, 0xCF, 0xFF, 0x41, 0x31, 0x15, 0x3B, 0x24, 0x0C, 0x7C, 0x07, 0xAE /* 8.1.0 Device Master Key Source Source. */ +.byte 0x4A, 0xC3, 0x4E, 0x14, 0x8B, 0x96, 0x4A, 0xD5, 0xD4, 0x99, 0x73, 0xC4, 0x45, 0xAB, 0x8B, 0x49 /* 9.0.0 Device Master Key Source Source. */ +.byte 0x14, 0xB8, 0x74, 0x12, 0xCB, 0xBD, 0x0B, 0x8F, 0x20, 0xFB, 0x30, 0xDA, 0x27, 0xE4, 0x58, 0x94 /* 9.1.0 Device Master Key Source Source. */ +.byte 0xAA, 0xFD, 0xBC, 0xBB, 0x25, 0xC3, 0xA4, 0xEF, 0xE3, 0xEE, 0x58, 0x53, 0xB7, 0xF8, 0xDD, 0xD6 /* 12.1.0 Device Master Key Source Source. */ +.byte 0xE4, 0xF3, 0x45, 0x6F, 0x18, 0xA1, 0x89, 0xF8, 0xDA, 0x4C, 0x64, 0x75, 0x68, 0xE6, 0xBD, 0x4F /* 13.0.0 Device Master Key Source Source. */ +.byte 0x5B, 0x94, 0x63, 0xF7, 0xAD, 0x96, 0x1B, 0xA6, 0x23, 0x30, 0x06, 0x4D, 0x01, 0xE4, 0xCE, 0x1D /* 14.0.0 Device Master Key Source Source. */ +.byte 0x5E, 0xC9, 0xC5, 0x0A, 0xD0, 0x5F, 0x8B, 0x7B, 0xA7, 0x39, 0xEA, 0xBC, 0x60, 0x0F, 0x74, 0xE6 /* 15.0.0 Device Master Key Source Source. */ +.byte 0xEA, 0x90, 0x6E, 0xA8, 0xAE, 0x92, 0x99, 0x64, 0x36, 0xC1, 0xF3, 0x1C, 0xC6, 0x32, 0x83, 0x8C /* 16.0.0 Device Master Key Source Source. */ +.byte 0xDA, 0xB9, 0xD6, 0x77, 0x52, 0x2D, 0x1F, 0x78, 0x73, 0xC9, 0x98, 0x5B, 0x06, 0xFE, 0xA0, 0x52 /* 17.0.0 Device Master Key Source Source. */ +.byte 0x14, 0xF5, 0xA5, 0xD0, 0x73, 0x6D, 0x44, 0x80, 0x5F, 0x31, 0x5A, 0x8F, 0x1E, 0xD4, 0x0D, 0x63 /* 18.0.0 Device Master Key Source Source. */ +.byte 0x07, 0x38, 0x9A, 0xEC, 0x9C, 0xBD, 0x50, 0x4A, 0x4C, 0x1F, 0x04, 0xDA, 0x40, 0x68, 0x29, 0xE3 /* 19.0.0 Device Master Key Source Source. */ +.byte 0xA3, 0x6B, 0x0A, 0xB5, 0x6F, 0x57, 0x4C, 0x5E, 0x00, 0xFD, 0x56, 0x21, 0xF5, 0x06, 0x6B, 0xD1 /* 20.0.0 Device Master Key Source Source. */ + +/* Development Device Master Kek Sources. */ +.byte 0xD6, 0xBD, 0x9F, 0xC6, 0x18, 0x09, 0xE1, 0x96, 0x20, 0x39, 0x60, 0xD2, 0x89, 0x83, 0x31, 0x34 /* 4.0.0 Device Master Kek Source. */ +.byte 0x59, 0x2D, 0x20, 0x69, 0x33, 0xB5, 0x17, 0xBA, 0xCF, 0xB1, 0x4E, 0xFD, 0xE4, 0xC2, 0x7B, 0xA8 /* 5.0.0 Device Master Kek Source. */ +.byte 0xF6, 0xD8, 0x59, 0x63, 0x8F, 0x47, 0xCB, 0x4A, 0xD8, 0x74, 0x05, 0x7F, 0x88, 0x92, 0x33, 0xA5 /* 6.0.0 Device Master Kek Source. */ +.byte 0x20, 0xAB, 0xF2, 0x0F, 0x05, 0xE3, 0xDE, 0x2E, 0xA1, 0xFB, 0x37, 0x5E, 0x8B, 0x22, 0x1A, 0x38 /* 6.2.0 Device Master Kek Source. */ +.byte 0x60, 0xAE, 0x56, 0x68, 0x11, 0xE2, 0x0C, 0x99, 0xDE, 0x05, 0xAE, 0x68, 0x78, 0x85, 0x04, 0xAE /* 7.0.0 Device Master Kek Source. */ +.byte 0x94, 0xD6, 0xA8, 0xC0, 0x95, 0xAF, 0xD0, 0xA6, 0x27, 0x53, 0x5E, 0xE5, 0x8E, 0x70, 0x1F, 0x87 /* 8.1.0 Device Master Kek Source. */ +.byte 0x61, 0x6A, 0x88, 0x21, 0xA3, 0x52, 0xB0, 0x19, 0x16, 0x25, 0xA4, 0xE3, 0x4C, 0x54, 0x02, 0x0F /* 9.0.0 Device Master Kek Source. */ +.byte 0x9D, 0xB1, 0xAE, 0xCB, 0xF6, 0xF6, 0xE3, 0xFE, 0xAB, 0x6F, 0xCB, 0xAF, 0x38, 0x03, 0xFC, 0x7B /* 9.1.0 Device Master Kek Source. */ +.byte 0xC4, 0xBB, 0xF3, 0x9F, 0xA3, 0xAA, 0x00, 0x99, 0x7C, 0x97, 0xAD, 0x91, 0x8F, 0xE8, 0x45, 0xCB /* 12.1.0 Device Master Kek Source. */ +.byte 0x20, 0x20, 0xAA, 0xFB, 0x89, 0xC2, 0xF0, 0x70, 0xB5, 0xE0, 0xA3, 0x11, 0x8A, 0x29, 0x8D, 0x0F /* 13.0.0 Device Master Kek Source. */ +.byte 0xCE, 0x14, 0x74, 0x66, 0x98, 0xA8, 0x6D, 0x7D, 0xBD, 0x54, 0x91, 0x68, 0x5F, 0x1D, 0x0E, 0xEA /* 14.0.0 Device Master Kek Source. */ +.byte 0xAE, 0x05, 0x48, 0x65, 0xAB, 0x17, 0x9D, 0x3D, 0x51, 0xB7, 0x56, 0xBD, 0x9B, 0x0B, 0x5B, 0x6E /* 15.0.0 Device Master Kek Source. */ +.byte 0xFF, 0xF6, 0x4B, 0x0F, 0xFF, 0x0D, 0xC0, 0x4F, 0x56, 0x8A, 0x40, 0x74, 0x67, 0xC5, 0xFE, 0x9F /* 16.0.0 Device Master Kek Source. */ +.byte 0x4E, 0xCE, 0x7B, 0x2A, 0xEA, 0x2E, 0x3D, 0x16, 0xD5, 0x2A, 0xDE, 0xF6, 0xF8, 0x6A, 0x7D, 0x43 /* 17.0.0 Device Master Kek Source. */ +.byte 0x3B, 0x00, 0x89, 0xD7, 0xA9, 0x9E, 0xB7, 0x70, 0x86, 0x00, 0xC3, 0x49, 0x52, 0x8C, 0xA4, 0xAF /* 18.0.0 Device Master Kek Source. */ +.byte 0xAE, 0x78, 0x36, 0xB6, 0x91, 0xEB, 0xAF, 0x9C, 0x18, 0xF1, 0xC0, 0xD5, 0x8A, 0x0C, 0x7C, 0xA1 /* 19.0.0 Device Master Kek Source. */ +.byte 0x09, 0x12, 0x4F, 0x26, 0x90, 0xB9, 0xA6, 0xF5, 0xA5, 0x18, 0x74, 0xB6, 0x8D, 0x80, 0x59, 0x3D /* 20.0.0 Device Master Kek Source. */ + +/* Production Device Master Kek Sources. */ +.byte 0x88, 0x62, 0x34, 0x6E, 0xFA, 0xF7, 0xD8, 0x3F, 0xE1, 0x30, 0x39, 0x50, 0xF0, 0xB7, 0x5D, 0x5D /* 4.0.0 Device Master Kek Source. */ +.byte 0x06, 0x1E, 0x7B, 0xE9, 0x6D, 0x47, 0x8C, 0x77, 0xC5, 0xC8, 0xE7, 0x94, 0x9A, 0xA8, 0x5F, 0x2E /* 5.0.0 Device Master Kek Source. */ +.byte 0x99, 0xFA, 0x98, 0xBD, 0x15, 0x1C, 0x72, 0xFD, 0x7D, 0x9A, 0xD5, 0x41, 0x00, 0xFD, 0xB2, 0xEF /* 6.0.0 Device Master Kek Source. */ +.byte 0x81, 0x3C, 0x6C, 0xBF, 0x5D, 0x21, 0xDE, 0x77, 0x20, 0xD9, 0x6C, 0xE3, 0x22, 0x06, 0xAE, 0xBB /* 6.2.0 Device Master Kek Source. */ +.byte 0x86, 0x61, 0xB0, 0x16, 0xFA, 0x7A, 0x9A, 0xEA, 0xF6, 0xF5, 0xBE, 0x1A, 0x13, 0x5B, 0x6D, 0x9E /* 7.0.0 Device Master Kek Source. */ +.byte 0xA6, 0x81, 0x71, 0xE7, 0xB5, 0x23, 0x74, 0xB0, 0x39, 0x8C, 0xB7, 0xFF, 0xA0, 0x62, 0x9F, 0x8D /* 8.1.0 Device Master Kek Source. */ +.byte 0x03, 0xE7, 0xEB, 0x43, 0x1B, 0xCF, 0x5F, 0xB5, 0xED, 0xDC, 0x97, 0xAE, 0x21, 0x8D, 0x19, 0xED /* 9.0.0 Device Master Kek Source. */ +.byte 0xCE, 0xFE, 0x41, 0x0F, 0x46, 0x9A, 0x30, 0xD6, 0xF2, 0xE9, 0x0C, 0x6B, 0xB7, 0x15, 0x91, 0x36 /* 9.1.0 Device Master Kek Source. */ +.byte 0xC2, 0x65, 0x34, 0x6E, 0xC7, 0xC6, 0x5D, 0x97, 0x3E, 0x34, 0x5C, 0x6B, 0xB3, 0x7E, 0xC6, 0xE3 /* 12.1.0 Device Master Kek Source. */ +.byte 0x77, 0x52, 0x92, 0xF0, 0xAA, 0xE3, 0xFB, 0xE0, 0x60, 0x16, 0xB3, 0x78, 0x68, 0x53, 0xF7, 0xA8 /* 13.0.0 Device Master Kek Source. */ +.byte 0x67, 0xD5, 0xD6, 0x0C, 0x08, 0xF5, 0xA3, 0x11, 0xBD, 0x6D, 0x5A, 0xEB, 0x96, 0x24, 0xB0, 0xD2 /* 14.0.0 Device Master Kek Source. */ +.byte 0x7C, 0x30, 0xED, 0x8B, 0x39, 0x25, 0x2C, 0x08, 0x8F, 0x48, 0xDC, 0x28, 0xE6, 0x1A, 0x6B, 0x49 /* 15.0.0 Device Master Kek Source. */ +.byte 0xF0, 0xF3, 0xFF, 0x52, 0x75, 0x2F, 0xBA, 0x4D, 0x09, 0x72, 0x30, 0x89, 0xA9, 0xDF, 0xFE, 0x1F /* 16.0.0 Device Master Kek Source. */ +.byte 0x21, 0xD6, 0x35, 0xF1, 0x0F, 0x7A, 0xF0, 0x5D, 0xDF, 0x79, 0x1C, 0x7A, 0xE4, 0x32, 0x82, 0x9E /* 17.0.0 Device Master Kek Source. */ +.byte 0xE7, 0x85, 0x8C, 0xA2, 0xF4, 0x49, 0xCB, 0x07, 0xD1, 0x8E, 0x48, 0x1B, 0xE8, 0x1E, 0x28, 0x3B /* 18.0.0 Device Master Kek Source. */ +.byte 0x9B, 0xA5, 0xFD, 0x74, 0x7F, 0xCD, 0x23, 0xD1, 0xD9, 0xBD, 0x6C, 0x51, 0x72, 0x5F, 0x3D, 0x1F /* 19.0.0 Device Master Kek Source. */ +.byte 0xDA, 0xFB, 0x61, 0x39, 0x48, 0x2D, 0xC2, 0x7E, 0x0D, 0x8E, 0x8F, 0x98, 0x57, 0x20, 0xB8, 0x15 /* 20.0.0 Device Master Kek Source. */ \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_rsa.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_rsa.cpp new file mode 100644 index 00000000..447fb81d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_rsa.cpp @@ -0,0 +1,159 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_boot.hpp" + +namespace ams::secmon::boot { + + namespace { + + constinit const u8 RsaPublicKeyExponent[] = { + 0x00, 0x01, 0x00, 0x01, + }; + + constexpr inline u8 TailMagic = 0xBC; + + bool VerifyRsaPssSha256(const u8 *sig, const void *msg, size_t msg_size) { + /* Define constants. */ + constexpr int EmBits = 2047; + constexpr int EmLen = util::DivideUp(EmBits, BITSIZEOF(u8)); + constexpr int SaltLen = 0x20; + constexpr int HashLen = se::Sha256HashSize; + + /* Define a work buffer. */ + u8 work[EmLen]; + ON_SCOPE_EXIT { util::ClearMemory(work, sizeof(work)); }; + + /* Calculate the message hash, first flushing cache to ensure SE sees correct data. */ + se::Sha256Hash msg_hash; + hw::FlushDataCache(msg, msg_size); + hw::DataSynchronizationBarrierInnerShareable(); + se::CalculateSha256(std::addressof(msg_hash), msg, msg_size); + + /* Verify the tail magic. */ + bool is_valid = sig[EmLen - 1] == TailMagic; + + /* Determine extents of masked db and h. */ + const u8 *masked_db = std::addressof(sig[0]); + const u8 *h = std::addressof(sig[EmLen - HashLen - 1]); + + /* Verify the extra bits are zero. */ + is_valid &= (masked_db[0] >> (BITSIZEOF(u8) - (BITSIZEOF(u8) * EmLen - EmBits))) == 0; + + /* Calculate the db mask. */ + { + constexpr int MaskLen = EmLen - HashLen - 1; + constexpr int HashIters = util::DivideUp(MaskLen, HashLen); + + u8 mgf1_buf[sizeof(u32) + HashLen]; + + std::memcpy(std::addressof(mgf1_buf[0]), h, HashLen); + std::memset(std::addressof(mgf1_buf[HashLen]), 0, sizeof(u32)); + + for (int i = 0; i < HashIters; ++i) { + /* Set the counter for this iteration. */ + mgf1_buf[sizeof(mgf1_buf) - 1] = i; + + /* Calculate the sha256 to the appropriate place in the work buffer. */ + auto *mgf1_dst = reinterpret_cast<se::Sha256Hash *>(std::addressof(work[HashLen * i])); + + hw::FlushDataCache(mgf1_buf, sizeof(mgf1_buf)); + hw::DataSynchronizationBarrierInnerShareable(); + se::CalculateSha256(mgf1_dst, mgf1_buf, sizeof(mgf1_buf)); + } + } + + /* Decrypt masked db using the mask we just generated. */ + for (int i = 0; i < EmLen - HashLen - 1; ++i) { + work[i] ^= masked_db[i]; + } + + /* Mask out the top bits. */ + u8 *db = work; + db[0] &= 0xFF >> (BITSIZEOF(u8) * EmLen - EmBits); + + /* Verify that DB is of the form 0000...0001 */ + constexpr int DbLen = EmLen - HashLen - 1; + int salt_ofs = 0; + { + int looking_for_one = 1; + int invalid_db_padding = 0; + int is_zero; + int is_one; + for (size_t i = 0; i < DbLen; /* ... */) { + is_zero = (db[i] == 0); + is_one = (db[i] == 1); + salt_ofs += (looking_for_one & is_one) * (static_cast<s32>(++i)); + looking_for_one &= ~is_one; + invalid_db_padding |= (looking_for_one & ~is_zero); + } + + is_valid &= (invalid_db_padding == 0); + } + + /* Verify salt. */ + is_valid &= (DbLen - salt_ofs) == SaltLen; + + /* Setup the message to verify. */ + const u8 *salt = std::addressof(db[DbLen - SaltLen]); + u8 verif_msg[8 + HashLen + SaltLen]; + ON_SCOPE_EXIT { util::ClearMemory(verif_msg, sizeof(verif_msg)); }; + + util::ClearMemory(std::addressof(verif_msg[0]), 8); + std::memcpy(std::addressof(verif_msg[8]), std::addressof(msg_hash), HashLen); + std::memcpy(std::addressof(verif_msg[8 + HashLen]), salt, SaltLen); + + /* Verify the final hash. */ + return VerifyHash(h, reinterpret_cast<uintptr_t>(std::addressof(verif_msg[0])), sizeof(verif_msg)); + } + + bool VerifyRsaPssSha256(int slot, void *sig, size_t sig_size, const void *msg, size_t msg_size) { + /* Exponentiate the signature, using the signature as the destination buffer. */ + se::ModularExponentiate(sig, sig_size, slot, sig, sig_size); + + /* Verify the pss padding. */ + return VerifyRsaPssSha256(static_cast<const u8 *>(sig), msg, msg_size); + } + + } + + bool VerifySignature(void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *msg, size_t msg_size) { + /* Load the public key into a temporary keyslot. */ + const int slot = pkg1::RsaKeySlot_Temporary; + se::SetRsaKey(slot, mod, mod_size, RsaPublicKeyExponent, util::size(RsaPublicKeyExponent)); + + return VerifyRsaPssSha256(slot, sig, sig_size, msg, msg_size); + } + + bool VerifyHash(const void *hash, uintptr_t msg, size_t msg_size) { + /* Zero-sized messages are always valid. */ + if (msg_size == 0) { + return true; + } + + /* Ensure that the SE sees correct data for the message. */ + hw::FlushDataCache(reinterpret_cast<void *>(msg), msg_size); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Calculate the hash. */ + se::Sha256Hash calc_hash; + se::CalculateSha256(std::addressof(calc_hash), reinterpret_cast<void *>(msg), msg_size); + + /* Verify the result. */ + return crypto::IsSameBytes(std::addressof(calc_hash), hash, sizeof(calc_hash)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_setup.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_setup.cpp new file mode 100644 index 00000000..c794b3ab --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_boot_setup.cpp @@ -0,0 +1,417 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_boot.hpp" +#include "secmon_boot_cache.hpp" +#include "../secmon_setup.hpp" +#include "../secmon_key_storage.hpp" + +namespace ams::secmon::boot { + + namespace { + + void ValidateSystemCounters() { + const uintptr_t sysctr0 = MemoryRegionVirtualDeviceSysCtr0.GetAddress(); + + /* Validate the system counter frequency is as expected. */ + AMS_ABORT_UNLESS(reg::Read(sysctr0 + SYSCTR0_CNTFID0) == 19'200'000u); + + /* Validate the system counters are as expected. */ + AMS_ABORT_UNLESS(reg::Read(sysctr0 + SYSCTR0_COUNTERID( 0)) == 0); + AMS_ABORT_UNLESS(reg::Read(sysctr0 + SYSCTR0_COUNTERID( 1)) == 0); + AMS_ABORT_UNLESS(reg::Read(sysctr0 + SYSCTR0_COUNTERID( 2)) == 0); + AMS_ABORT_UNLESS(reg::Read(sysctr0 + SYSCTR0_COUNTERID( 3)) == 0); + AMS_ABORT_UNLESS(reg::Read(sysctr0 + SYSCTR0_COUNTERID( 4)) == 0); + AMS_ABORT_UNLESS(reg::Read(sysctr0 + SYSCTR0_COUNTERID( 5)) == 0); + AMS_ABORT_UNLESS(reg::Read(sysctr0 + SYSCTR0_COUNTERID( 6)) == 0); + AMS_ABORT_UNLESS(reg::Read(sysctr0 + SYSCTR0_COUNTERID( 7)) == 0); + AMS_ABORT_UNLESS(reg::Read(sysctr0 + SYSCTR0_COUNTERID( 8)) == 0); + AMS_ABORT_UNLESS(reg::Read(sysctr0 + SYSCTR0_COUNTERID( 9)) == 0); + AMS_ABORT_UNLESS(reg::Read(sysctr0 + SYSCTR0_COUNTERID(10)) == 0); + AMS_ABORT_UNLESS(reg::Read(sysctr0 + SYSCTR0_COUNTERID(11)) == 0); + } + + void SetupPmcRegisters() { + const auto pmc = MemoryRegionVirtualDevicePmc.GetAddress(); + + /* Set the physical address of the warmboot binary to scratch 1. */ + if (GetSocType() == fuse::SocType_Mariko) { + reg::Write(pmc + APBDEV_PMC_SECURE_SCRATCH119, static_cast<u32>(MemoryRegionPhysicalDramSecureDataStoreWarmbootFirmware.GetAddress())); + } else /* if (GetSocType() == fuse::SocType_Erista) */ { + reg::Write(pmc + APBDEV_PMC_SCRATCH1, static_cast<u32>(MemoryRegionPhysicalDramSecureDataStoreWarmbootFirmware.GetAddress())); + } + + + /* Configure logging by setting bits 18-19 of scratch 20. */ + reg::ReadWrite(pmc + APBDEV_PMC_SCRATCH20, REG_BITS_VALUE(18, 2, 0)); + + /* Clear the wdt reset flag. */ + reg::ReadWrite(pmc + APBDEV_PMC_SCRATCH190, REG_BITS_VALUE(0, 1, 0)); + + /* Configure warmboot to set Set FUSE_PRIVATEKEYDISABLE to KEY_INVISIBLE. */ + reg::ReadWrite(pmc + APBDEV_PMC_SECURE_SCRATCH21, REG_BITS_VALUE(4, 1, 1)); + + /* NOTE: Here, Nintendo writes the warmboot key. */ + /* However, we rely on the bootloader (e.g. fusee/hekate) having already done this. */ + /* reg::Write(pmc + APBDEV_PMC_SECURE_SCRATCH32, ...); */ + } + + /* This function derives the master kek and device keys using the tsec root key. */ + void DeriveMasterKekAndDeviceKeyErista(bool is_prod) { + /* NOTE: Exosphere does not use this in practice, and expects the bootloader to set up keys already. */ + /* NOTE: This function is currently not implemented. If implemented, it will only be a reference implementation. */ + if constexpr (false) { + /* TODO: Consider implementing this as a reference. */ + } + + AMS_UNUSED(is_prod); + } + + void DeriveMasterKekAndDeviceKeyMariko(bool is_prod) { + /* Clear all keyslots other than KEK and SBK in SE1. */ + for (int i = 0; i < pkg1::AesKeySlot_Count; ++i) { + if (i != pkg1::AesKeySlot_MarikoKek && i != pkg1::AesKeySlot_SecureBoot) { + se::ClearAesKeySlot(i); + } + } + + /* Clear all keyslots in SE2. */ + for (int i = 0; i < pkg1::AesKeySlot_Count; ++i) { + se::ClearAesKeySlot2(i); + } + + /* Derive the master kek. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_MasterKek, pkg1::AesKeySlot_MarikoKek, GetMarikoMasterKekSource(is_prod), se::AesBlockSize); + + /* Derive the device master key source kek. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_DeviceMasterKeySourceKekMariko, pkg1::AesKeySlot_SecureBoot, GetDeviceMasterKeySourceKekSource(), se::AesBlockSize); + + /* Clear the KEK, now that we're done using it. */ + se::ClearAesKeySlot(pkg1::AesKeySlot_MarikoKek); + } + + void DeriveMasterKekAndDeviceKey(bool is_prod) { + if (GetSocType() == fuse::SocType_Mariko) { + DeriveMasterKekAndDeviceKeyMariko(is_prod); + } else /* if (GetSocType() == fuse::SocType_Erista) */ { + DeriveMasterKekAndDeviceKeyErista(is_prod); + } + } + + void DeriveMasterKey() { + if (GetSocType() == fuse::SocType_Mariko) { + se::SetEncryptedAesKey128(pkg1::AesKeySlot_Master, pkg1::AesKeySlot_MasterKek, GetMasterKeySource(), se::AesBlockSize); + } else /* if (GetSocType() == fuse::SocType_Erista) */ { + /* Nothing to do here; erista bootloader will have derived master key already. */ + } + } + + void SetupRandomKey(int slot, se::KeySlotLockFlags flags) { + /* Create an aligned buffer to hold the key. */ + constexpr size_t KeySize = se::AesBlockSize; + util::AlignedBuffer<hw::DataCacheLineSize, KeySize> key; + + /* Ensure data is consistent before triggering the SE. */ + hw::FlushDataCache(key, KeySize); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Generate random bytes into the key. */ + se::GenerateRandomBytes(key, KeySize); + + /* Ensure that the CPU sees consistent data. */ + hw::DataSynchronizationBarrierInnerShareable(); + hw::FlushDataCache(key, KeySize); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Use the random bytes as a key source. */ + se::SetEncryptedAesKey128(slot, pkg1::AesKeySlot_DeviceMaster, key, KeySize); + + /* Lock the keyslot. */ + se::LockAesKeySlot(slot, flags); + } + + bool TestKeyGeneration(int generation, bool is_prod) { + /* Decrypt the vector chain from generation to start. */ + int slot = pkg1::AesKeySlot_Master; + for (int i = generation; i > 0; --i) { + se::SetEncryptedAesKey128(pkg1::AesKeySlot_Temporary, slot, GetMasterKeyVector(is_prod, i), se::AesBlockSize); + slot = pkg1::AesKeySlot_Temporary; + } + + /* Decrypt the final vector. */ + u8 test_vector[se::AesBlockSize]; + se::DecryptAes128(test_vector, se::AesBlockSize, slot, GetMasterKeyVector(is_prod, 0), se::AesBlockSize); + + constexpr u8 ZeroBlock[se::AesBlockSize] = {}; + return crypto::IsSameBytes(ZeroBlock, test_vector, se::AesBlockSize); + } + + int DetermineKeyGeneration(bool is_prod) { + /* Test each generation in order. */ + for (int generation = 0; generation < pkg1::KeyGeneration_Count; ++generation) { + if (TestKeyGeneration(generation, is_prod)) { + return generation; + } + } + + /* We must have found a correct key generation. */ + AMS_ABORT(); + } + + void DeriveAllMasterKeys(bool is_prod, u8 * const work_block) { + /* Determine the generation. */ + const int generation = DetermineKeyGeneration(is_prod); + AMS_SECMON_LOG("KeyGen: %02X\n", static_cast<unsigned int>(generation)); + + /* Set the global generation. */ + ::ams::secmon::impl::SetKeyGeneration(generation); + + /* Derive all old keys. */ + int slot = pkg1::AesKeySlot_Master; + for (int i = generation; i > 0; --i) { + /* Decrypt the old master key. */ + se::DecryptAes128(work_block, se::AesBlockSize, slot, GetMasterKeyVector(is_prod, i), se::AesBlockSize); + + /* Set the old master key. */ + SetMasterKey(i - 1, work_block, se::AesBlockSize); + + /* Set the old master key into a temporary keyslot. */ + se::SetAesKey(pkg1::AesKeySlot_Temporary, work_block, se::AesBlockSize); + + /* Perform the next decryption with the older master key. */ + slot = pkg1::AesKeySlot_Temporary; + } + } + + void DeriveAllDeviceMasterKeys(bool is_prod, u8 * const work_block) { + /* Get the current key generation. */ + const int current_generation = secmon::GetKeyGeneration(); + + /* Get the kek slot. */ + const int kek_slot = GetSocType() == fuse::SocType_Mariko ? pkg1::AesKeySlot_DeviceMasterKeySourceKekMariko : pkg1::AesKeySlot_DeviceMasterKeySourceKekErista; + + /* Iterate for all generations. */ + for (int i = 0; i < pkg1::OldDeviceMasterKeyCount; ++i) { + const int generation = pkg1::KeyGeneration_4_0_0 + i; + + /* Load the first master key into the temporary keyslot keyslot. */ + LoadMasterKey(pkg1::AesKeySlot_Temporary, pkg1::KeyGeneration_1_0_0); + + /* Decrypt the device master kek for the generation. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_Temporary, pkg1::AesKeySlot_Temporary, GetDeviceMasterKekSource(is_prod, i), se::AesBlockSize); + + /* Decrypt the device master key source into the work block. */ + se::DecryptAes128(work_block, se::AesBlockSize, kek_slot, GetDeviceMasterKeySourceSource(i), se::AesBlockSize); + + /* If we're decrypting the current device master key, decrypt into the keyslot. */ + if (generation == current_generation) { + se::SetEncryptedAesKey128(pkg1::AesKeySlot_DeviceMaster, pkg1::AesKeySlot_Temporary, work_block, se::AesBlockSize); + } else { + /* Otherwise, decrypt the work block into itself and set the old device master key. */ + se::DecryptAes128(work_block, se::AesBlockSize, pkg1::AesKeySlot_Temporary, work_block, se::AesBlockSize); + + /* Set the device master key. */ + SetDeviceMasterKey(generation, work_block, se::AesBlockSize); + } + } + + /* Clear and lock the Device Master Key Source Kek. */ + se::ClearAesKeySlot(pkg1::AesKeySlot_DeviceMasterKeySourceKekMariko); + se::LockAesKeySlot(pkg1::AesKeySlot_DeviceMasterKeySourceKekMariko, se::KeySlotLockFlags_AllLockKek); + } + + void DeriveAllKeys(bool is_prod) { + /* Get the ephemeral work block. */ + u8 * const work_block = se::GetEphemeralWorkBlock(); + ON_SCOPE_EXIT { util::ClearMemory(work_block, se::AesBlockSize); }; + + /* Lock the master key as a key. */ + se::LockAesKeySlot(pkg1::AesKeySlot_Master, se::KeySlotLockFlags_AllLockKey); + + /* Setup a random key to protect the old master and device master keys. */ + SetupRandomKey(pkg1::AesKeySlot_RandomForKeyStorageWrap, se::KeySlotLockFlags_AllLockKey); + + /* Derive the master keys. */ + DeriveAllMasterKeys(is_prod, work_block); + + /* Lock the master key as a kek. */ + se::LockAesKeySlot(pkg1::AesKeySlot_Master, se::KeySlotLockFlags_AllLockKek); + + /* Derive the device master keys. */ + DeriveAllDeviceMasterKeys(is_prod, work_block); + + /* Lock the device master key as a kek. */ + se::LockAesKeySlot(pkg1::AesKeySlot_DeviceMaster, se::KeySlotLockFlags_AllLockKek); + + /* Setup a random key to protect user keys. */ + SetupRandomKey(pkg1::AesKeySlot_RandomForUserWrap, se::KeySlotLockFlags_AllLockKek); + } + + void InitializeKeys() { + /* Read lock all aes keys. */ + for (int i = 0; i < se::AesKeySlotCount; ++i) { + se::LockAesKeySlot(i, se::KeySlotLockFlags_AllReadLock); + } + + /* Lock the secure monitor aes keys to be secmon only and non-readable. */ + for (int i = pkg1::AesKeySlot_SecmonStart; i < pkg1::AesKeySlot_SecmonEnd; ++i) { + se::LockAesKeySlot(i, se::KeySlotLockFlags_KeyUse | se::KeySlotLockFlags_PerKey); + } + + /* Lock the unused keyslots entirely. */ + static_assert(pkg1::AesKeySlot_UserEnd <= pkg1::AesKeySlot_SecmonStart); + for (int i = pkg1::AesKeySlot_UserEnd; i < pkg1::AesKeySlot_SecmonStart; ++i) { + se::LockAesKeySlot(i, se::KeySlotLockFlags_AllLockKek); + } + + /* Read lock all rsa keys. */ + for (int i = 0; i < se::RsaKeySlotCount; ++i) { + se::LockRsaKeySlot(i, se::KeySlotLockFlags_KeyUse | se::KeySlotLockFlags_PerKey | se::KeySlotLockFlags_KeyRead); + } + + /* Initialize the rng. */ + se::InitializeRandom(); + + /* Determine whether we're production. */ + const bool is_prod = IsProduction(); + + /* Derive the master kek and device key. */ + /* NOTE: This is a no-op on erista, because fusee will have set up keys. */ + DeriveMasterKekAndDeviceKey(is_prod); + + /* Lock the device key as only usable as a kek. */ + se::LockAesKeySlot(pkg1::AesKeySlot_Device, se::KeySlotLockFlags_AllLockKek); + + /* Derive the master key. */ + DeriveMasterKey(); + + /* Derive all other keys. */ + DeriveAllKeys(is_prod); + } + + } + + namespace { + + using namespace ams::mmu; + + constexpr void UnmapPhysicalIdentityMappingImpl(u64 *l1, u64 *l2, u64 *l3) { + /* Invalidate the L3 entries for the tzram and iram boot code regions. */ + InvalidateL3Entries(l3, MemoryRegionPhysicalTzram.GetAddress(), MemoryRegionPhysicalTzram.GetSize()); + InvalidateL3Entries(l3, MemoryRegionPhysicalIramBootCode.GetAddress(), MemoryRegionPhysicalIramBootCode.GetSize()); + + /* Unmap the L2 entries corresponding to those L3 entries. */ + InvalidateL2Entries(l2, MemoryRegionPhysicalIramL2.GetAddress(), MemoryRegionPhysicalIramL2.GetSize()); + InvalidateL2Entries(l2, MemoryRegionPhysicalTzramL2.GetAddress(), MemoryRegionPhysicalTzramL2.GetSize()); + + /* Unmap the L1 entry corresponding to to those L2 entries. */ + InvalidateL1Entries(l1, MemoryRegionPhysical.GetAddress(), MemoryRegionPhysical.GetSize()); + } + + constexpr void UnmapDramImpl(u64 *l1, u64 *l2, u64 *l3) { + /* Unmap the L1 entry corresponding to to the Dram entries. */ + AMS_UNUSED(l2, l3); + InvalidateL1Entries(l1, MemoryRegionDram.GetAddress(), MemoryRegionDram.GetSize()); + } + + constexpr void UnmapMarikoProgramImpl(u64 *l1, u64 *l2, u64 *l3) { + /* Unmap the L1 entry corresponding to to the Dram entries. */ + AMS_UNUSED(l1, l2); + InvalidateL3Entries(l3, MemoryRegionVirtualTzramMarikoProgram.GetAddress(), MemoryRegionVirtualTzramMarikoProgram.GetSize()); + } + + } + + void InitializeColdBoot() { + /* Ensure that the system counters are valid. */ + ValidateSystemCounters(); + + /* Set the security engine to Tzram Secure. */ + se::SetTzramSecure(); + + /* Set the security engine to Per Key Secure. */ + se::SetPerKeySecure(); + + /* Set the security engine to Context Save Secure. */ + se::SetContextSaveSecure(); + + /* Setup the PMC registers. */ + SetupPmcRegisters(); + + /* Lockout the scratch that we've just written. */ + /* pmc::LockSecureRegisters(1); */ + + /* Generate a random srk. */ + se::GenerateSrk(); + + /* Initialize the SE keyslots. */ + InitializeKeys(); + + /* Save a test vector for the SE keyslots. */ + SaveSecurityEngineAesKeySlotTestVector(); + } + + void UnmapPhysicalIdentityMapping() { + /* Get the tables. */ + u64 * const l1 = MemoryRegionVirtualTzramL1PageTable.GetPointer<u64>(); + u64 * const l2_l3 = MemoryRegionVirtualTzramL2L3PageTable.GetPointer<u64>(); + + /* Unmap. */ + UnmapPhysicalIdentityMappingImpl(l1, l2_l3, l2_l3); + + /* Ensure the mappings are consistent. */ + secmon::boot::EnsureMappingConsistency(); + } + + void UnmapDram() { + /* Get the tables. */ + u64 * const l1 = MemoryRegionVirtualTzramL1PageTable.GetPointer<u64>(); + u64 * const l2_l3 = MemoryRegionVirtualTzramL2L3PageTable.GetPointer<u64>(); + + /* Unmap. */ + UnmapDramImpl(l1, l2_l3, l2_l3); + + /* Ensure the mappings are consistent. */ + secmon::boot::EnsureMappingConsistency(); + } + + void LoadMarikoProgram() { + void * const mariko_program_dst = MemoryRegionVirtualTzramMarikoProgram.GetPointer<void>(); + void * const mariko_program_src = MemoryRegionPhysicalMarikoProgramImage.GetPointer<void>(); + const size_t mariko_program_size = MemoryRegionVirtualTzramMarikoProgram.GetSize(); + + if (fuse::GetSocType() == fuse::SocType_Mariko) { + /* On Mariko, we want to load the mariko program image into mariko tzram. */ + std::memcpy(mariko_program_dst, mariko_program_src, mariko_program_size); + hw::FlushDataCache(mariko_program_dst, mariko_program_size); + } else { + /* On Erista, we don't have mariko-only-tzram, so unmap it. */ + u64 * const l1 = MemoryRegionVirtualTzramL1PageTable.GetPointer<u64>(); + u64 * const l2_l3 = MemoryRegionVirtualTzramL2L3PageTable.GetPointer<u64>(); + + UnmapMarikoProgramImpl(l1, l2_l3, l2_l3); + } + + /* Clear the Mariko program image from DRAM. */ + util::ClearMemory(mariko_program_src, mariko_program_size); + hw::FlushDataCache(mariko_program_src, mariko_program_size); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Ensure the mappings are consistent. */ + secmon::boot::EnsureMappingConsistency(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_crt0.s b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_crt0.s new file mode 100644 index 00000000..a9b9e929 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_crt0.s @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +.section .crt0.text.start, "ax", %progbits +.align 6 +.global _start +_start: + /* mask all interrupts */ + msr daifset, #0xF + + /* Set the stack pointer to a temporary location. */ + ldr x20, =0x7C010800 + mov sp, x20 + + /* Initialize all memory to expected state. */ + ldr x0, =__bss_start__ + ldr x1, =__bss_end__ + ldr x2, =__boot_bss_start__ + ldr x3, =__boot_bss_end__ + bl _ZN3ams6secmon4boot10InitializeEmmmm + + /* Jump to the first bit of virtual code. */ + ldr x16, =_ZN3ams6secmon5StartEv + br x16 diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_crt0_cpp.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_crt0_cpp.cpp new file mode 100644 index 00000000..a8b92031 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_crt0_cpp.cpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_boot.hpp" +#include "../secmon_setup.hpp" + +extern "C" void __libc_init_array(); + +namespace ams::secmon::boot { + + void Initialize(uintptr_t bss_start, size_t bss_end, uintptr_t boot_bss_start, uintptr_t boot_bss_end) { + /* Set our start time. */ + auto &secmon_params = *MemoryRegionPhysicalDeviceBootloaderParams.GetPointer<pkg1::SecureMonitorParameters>(); + secmon_params.secmon_start_time = *reinterpret_cast<volatile u32 *>(MemoryRegionPhysicalDeviceTimer.GetAddress() + 0x10); + + /* Setup DMA controllers. */ + SetupSocDmaControllers(); + + /* Make the page table. */ + MakePageTable(); + + /* Setup memory controllers the MMU. */ + SetupCpuMemoryControllersEnableMmu(); + + /* Clear bss. */ + std::memset(reinterpret_cast<void *>(bss_start), 0, bss_end - bss_start); + std::memset(reinterpret_cast<void *>(boot_bss_start), 0, boot_bss_end - boot_bss_start); + + /* Call init array. */ + __libc_init_array(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_main.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_main.cpp new file mode 100644 index 00000000..fd38a7c8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_main.cpp @@ -0,0 +1,205 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_boot.hpp" +#include "secmon_boot_functions.hpp" +#include "../smc/secmon_random_cache.hpp" +#include "../smc/secmon_smc_handler.hpp" +#include "../secmon_cache.hpp" +#include "../secmon_cpu_context.hpp" +#include "../secmon_misc.hpp" +#include "../secmon_setup.hpp" + +namespace ams::secmon { + + namespace { + + constexpr inline const uintptr_t Package2LoadAddress = MemoryRegionDramPackage2Payloads.GetAddress(); + + } + + 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()); + + /* Get the secure monitor parameters. */ + auto &secmon_params = *reinterpret_cast<pkg1::SecureMonitorParameters *>(MemoryRegionVirtualDeviceBootloaderParams.GetAddress()); + + /* Perform initialization. */ + { + /* Perform initial setup. */ + /* This checks the security engine's validity, and configures common interrupts in the GIC. */ + /* This also initializes the global configuration context. */ + secmon::Setup1(); + AMS_SECMON_LOG("%s\n", "Boot begin."); + + /* Save the boot info. */ + secmon::SaveBootInfo(secmon_params); + + /* Perform cold-boot specific init. */ + secmon::boot::InitializeColdBoot(); + + /* Setup the SoC security measures. */ + secmon::SetupSocSecurity(); + + /* Setup the Cpu core context. */ + secmon::SetupCpuCoreContext(); + + /* Clear the crt0 code that was present in iram. */ + secmon::boot::ClearIramBootCode(); + + /* Clear the debug code from iram, if we're not in debug config. */ + #if !defined(AMS_BUILD_FOR_DEBUGGING) && !defined(AMS_BUILD_FOR_AUDITING) + secmon::boot::ClearIramDebugCode(); + #endif + + /* Alert the bootloader that we're initialized. */ + secmon_params.secmon_state = pkg1::SecureMonitorState_Initialized; + hw::FlushDataCache(std::addressof(secmon_params.secmon_state), sizeof(secmon_params.secmon_state)); + hw::DataSynchronizationBarrierInnerShareable(); + } + + /* Wait for NX Bootloader to finish loading the BootConfig. */ + secmon::boot::WaitForNxBootloader(secmon_params, pkg1::BootloaderState_LoadedBootConfig); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Load the bootconfig. */ + secmon::boot::LoadBootConfig(MemoryRegionPhysicalIramBootConfig.GetPointer()); + + /* Verify or clear the boot config. */ + secmon::boot::VerifyOrClearBootConfig(); + + /* Get the boot config. */ + const auto &bc = secmon::GetBootConfig(); + + /* Set the tsc value by the boot config. */ + { + constexpr u64 TscMask = (static_cast<u64>(1) << 55) - 1; + + secmon::boot::EnableTsc(bc.data.GetInitialTscValue() & TscMask); + } + + /* Wait for NX Bootloader to initialize DRAM. */ + secmon::boot::WaitForNxBootloader(secmon_params, pkg1::BootloaderState_InitializedDram); + + /* Secure the PMC and MC. */ + secmon::SetupPmcAndMcSecure(); + + /* Copy warmboot to dram. */ + { + /* Define warmboot extents. */ + const void * const src = MemoryRegionPhysicalIramWarmbootBin.GetPointer(); + void * const dst = MemoryRegionVirtualDramSecureDataStoreWarmbootFirmware.GetPointer(); + const size_t size = MemoryRegionPhysicalIramWarmbootBin.GetSize(); + + /* Ensure we copy the correct data. */ + hw::FlushDataCache(src, size); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Copy warmboot.bin to its secure dram location. */ + std::memcpy(dst, src, size); + } + + /* Load the mariko program image. */ + secmon::boot::LoadMarikoProgram(); + + /* Setup the GPU carveout's magic numbers. */ + secmon::boot::WriteGpuCarveoutMagicNumbers(); + + /* Wait for NX bootloader to load Package2. */ + secmon::boot::WaitForNxBootloader(secmon_params, pkg1::BootloaderState_LoadedPackage2); + + /* Parse and decrypt the package2 header. */ + pkg2::Package2Meta &pkg2_meta = secmon::boot::GetEphemeralPackage2Meta(); + const uintptr_t pkg2_payloads_start = MemoryRegionDramPackage2.GetAddress() + sizeof(pkg2::Package2Header); + { + /* Read the encrypred header. */ + pkg2::Package2Header encrypted_header; + + const auto *dram_header = MemoryRegionDramPackage2.GetPointer<pkg2::Package2Header>(); + hw::FlushDataCache(dram_header, sizeof(*dram_header)); + hw::DataSynchronizationBarrierInnerShareable(); + + std::memcpy(std::addressof(encrypted_header), dram_header, sizeof(encrypted_header)); + + /* Atmosphere extension: support plaintext package2, identified by all-zeroes signature and decrypted header. */ + secmon::boot::UpdateBootConfigForPackage2Header(encrypted_header); + + /* Verify the package2 header's signature. */ + secmon::boot::VerifyPackage2HeaderSignature(encrypted_header, !bc.signed_data.IsPackage2SignatureVerificationDisabled()); + + /* Decrypt the package2 header. */ + secmon::boot::DecryptPackage2Header(std::addressof(pkg2_meta), encrypted_header.meta, !bc.signed_data.IsPackage2EncryptionDisabled()); + } + + /* Verify the package2 header. */ + secmon::boot::VerifyPackage2Header(pkg2_meta); + + /* Save the package2 hash if in recovery boot. */ + if (secmon::IsRecoveryBoot()) { + se::Sha256Hash hash; + secmon::boot::CalculatePackage2Hash(std::addressof(hash), pkg2_meta, MemoryRegionDramPackage2.GetAddress()); + secmon::SetPackage2Hash(hash); + } + + /* Verify the package2 payloads. */ + secmon::boot::CheckVerifyResult(secmon::boot::VerifyPackage2Payloads(pkg2_meta, pkg2_payloads_start), pkg1::ErrorInfo_InvalidPackage2Payload, "pkg2 payload FAIL"); + + /* Decrypt/Move the package2 payloads to the right places. */ + secmon::boot::DecryptAndLoadPackage2Payloads(Package2LoadAddress, pkg2_meta, pkg2_payloads_start, !bc.signed_data.IsPackage2EncryptionDisabled()); + + /* Ensure that the CPU sees correct package2 data. */ + secmon::FlushEntireDataCache(); + secmon::EnsureInstructionConsistency(); + + /* Set the core's entrypoint and argument. */ + secmon::SetEntryContext(0, Package2LoadAddress + pkg2_meta.entrypoint, 0); + + /* Clear the boot keys from iram. */ + secmon::boot::ClearIramBootKeys(); + + /* Unmap the identity mapping. */ + secmon::boot::UnmapPhysicalIdentityMapping(); + + /* Unmap DRAM. */ + secmon::boot::UnmapDram(); + + /* Wait for NX bootloader to be done. */ + secmon::boot::WaitForNxBootloader(secmon_params, pkg1::BootloaderState_Done); + + /* Perform final initialization. */ + secmon::SetupSocProtections(); + secmon::SetupCpuSErrorDebug(); + + /* Configure the smc handler tables to reflect the current target firmware. */ + secmon::smc::ConfigureSmcHandlersForTargetFirmware(); + + AMS_SECMON_LOG("%s\n", "Boot end."); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_make_page_table.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_make_page_table.cpp new file mode 100644 index 00000000..eaf2f2e9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_make_page_table.cpp @@ -0,0 +1,169 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../secmon_setup.hpp" +#include "secmon_boot.hpp" + +namespace ams::secmon::boot { + + namespace { + + using namespace ams::mmu; + + constexpr inline PageTableMappingAttribute MappingAttributesEl3SecureRoCode = AddMappingAttributeIndex(PageTableMappingAttributes_El3SecureRoCode, MemoryAttributeIndexNormal); + constexpr inline PageTableMappingAttribute MappingAttributesEl3SecureRwCode = AddMappingAttributeIndex(PageTableMappingAttributes_El3SecureRwCode, MemoryAttributeIndexNormal); + constexpr inline PageTableMappingAttribute MappingAttributesEl3SecureRoData = AddMappingAttributeIndex(PageTableMappingAttributes_El3SecureRoData, MemoryAttributeIndexNormal); + constexpr inline PageTableMappingAttribute MappingAttributesEl3SecureRwData = AddMappingAttributeIndex(PageTableMappingAttributes_El3SecureRwData, MemoryAttributeIndexNormal); + constexpr inline PageTableMappingAttribute MappingAttributesEl3NonSecureRwData = AddMappingAttributeIndex(PageTableMappingAttributes_El3NonSecureRwData, MemoryAttributeIndexNormal); + + constexpr inline PageTableMappingAttribute MappingAttributesEl3SecureDevice = AddMappingAttributeIndex(PageTableMappingAttributes_El3SecureRwData, MemoryAttributeIndexDevice); + constexpr inline PageTableMappingAttribute MappingAttributesEl3NonSecureDevice = AddMappingAttributeIndex(PageTableMappingAttributes_El3NonSecureRwData, MemoryAttributeIndexDevice); + + + constexpr void ClearMemory(volatile u64 *start, size_t size) { + volatile u64 * const end = start + (size / sizeof(u64)); + + for (volatile u64 *cur = start; cur < end; ++cur) { + *cur = 0; + } + } + + constexpr void MakePageTablesImpl(u64 *l1, u64 *l2, u64 *l3) { + /* Setup the L1 table. */ + { + ClearMemory(l1, MemoryRegionPhysicalTzramL1PageTable.GetSize()); + + /* Create an L1 table entry for the physical region. */ + SetL1TableEntry(l1, MemoryRegionPhysical.GetAddress(), MemoryRegionPhysicalTzramL2L3PageTable.GetAddress(), PageTableTableAttributes_El3SecureCode); + static_assert(GetL1EntryIndex(MemoryRegionPhysical.GetAddress()) == 1); + + /* Create an L1 mapping entry for dram. */ + SetL1BlockEntry(l1, MemoryRegionDram.GetAddress(), MemoryRegionDram.GetAddress(), MemoryRegionDram.GetSize(), MappingAttributesEl3NonSecureRwData); + + /* Create an L1 table entry for the virtual region. */ + SetL1TableEntry(l1, MemoryRegionVirtual.GetAddress(), MemoryRegionPhysicalTzramL2L3PageTable.GetAddress(), PageTableTableAttributes_El3SecureCode); + } + + /* Setup the L2 table. */ + { + ClearMemory(l2, MemoryRegionPhysicalTzramL2L3PageTable.GetSize()); + + /* Create an L2 table entry for the virtual region. */ + SetL2TableEntry(l2, MemoryRegionVirtualL2.GetAddress(), MemoryRegionPhysicalTzramL2L3PageTable.GetAddress(), PageTableTableAttributes_El3SecureCode); + + /* Create an L2 table entry for the physical iram region. */ + SetL2TableEntry(l2, MemoryRegionPhysicalIramL2.GetAddress(), MemoryRegionPhysicalTzramL2L3PageTable.GetAddress(), PageTableTableAttributes_El3SecureCode); + + /* Create an L2 table entry for the physical tzram region. */ + SetL2TableEntry(l2, MemoryRegionPhysicalTzramL2.GetAddress(), MemoryRegionPhysicalTzramL2L3PageTable.GetAddress(), PageTableTableAttributes_El3SecureCode); + } + + /* Setup the L3 table. */ + { + /* L2 and L3 share a page table. */ + if (l2 != l3) { + ClearMemory(l3, MemoryRegionPhysicalTzramL2L3PageTable.GetSize()); + } + + /* Identity-map TZRAM as rwx. */ + SetL3BlockEntry(l3, MemoryRegionPhysicalTzram.GetAddress(), MemoryRegionPhysicalTzram.GetAddress(), MemoryRegionPhysicalTzram.GetSize(), MappingAttributesEl3SecureRwCode); + + /* Identity-map IRAM boot code as rwx. */ + SetL3BlockEntry(l3, MemoryRegionPhysicalIramBootCode.GetAddress(), MemoryRegionPhysicalIramBootCode.GetAddress(), MemoryRegionPhysicalIramBootCode.GetSize(), MappingAttributesEl3SecureRwCode); + + #if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING) + { + /* Map the debug code region as rwx. */ + SetL3BlockEntry(l3, MemoryRegionVirtualDebugCode.GetAddress(), MemoryRegionPhysicalDebugCode.GetAddress(), MemoryRegionPhysicalDebugCode.GetSize(), MappingAttributesEl3SecureRwCode); + + + /* Map the DRAM debug code store region as rw. */ + SetL3BlockEntry(l3, MemoryRegionVirtualDramDebugDataStore.GetAddress(), MemoryRegionPhysicalDramDebugDataStore.GetAddress(), MemoryRegionPhysicalDramDebugDataStore.GetSize(), MappingAttributesEl3NonSecureRwData); + } + #endif + + /* Map all devices. */ + { + #define MAP_DEVICE_REGION(_NAME_, _PREV_, _ADDRESS_, _SIZE_, _SECURE_) \ + SetL3BlockEntry(l3, MemoryRegionVirtualDevice##_NAME_.GetAddress(), MemoryRegionPhysicalDevice##_NAME_.GetAddress(), MemoryRegionVirtualDevice##_NAME_.GetSize(), _SECURE_ ? MappingAttributesEl3SecureDevice : MappingAttributesEl3NonSecureDevice); + + AMS_SECMON_FOREACH_DEVICE_REGION(MAP_DEVICE_REGION); + + #undef MAP_DEVICE_REGION + } + + /* Map the IRAM SC7 work region. */ + SetL3BlockEntry(l3, MemoryRegionVirtualIramSc7Work.GetAddress(), MemoryRegionPhysicalIramSc7Work.GetAddress(), MemoryRegionVirtualIramSc7Work.GetSize(), MappingAttributesEl3NonSecureDevice); + + /* Map the IRAM SC7 firmware region. */ + SetL3BlockEntry(l3, MemoryRegionVirtualIramSc7Firmware.GetAddress(), MemoryRegionPhysicalIramSc7Firmware.GetAddress(), MemoryRegionVirtualIramSc7Firmware.GetSize(), MappingAttributesEl3NonSecureDevice); + + /* Map the Debug region. */ + /* NOTE: This region is reserved for debug. By default it will be the last 0x8000 bytes of IRAM, but this is subject to change. */ + /* If you are doing development work for exosphere, feel free to locally change this to whatever is useful. */ + SetL3BlockEntry(l3, MemoryRegionVirtualDebug.GetAddress(), MemoryRegionPhysicalIram.GetEndAddress() - 0x8000, 0x8000, MappingAttributesEl3SecureDevice); + + /* Map the TZRAM ro alias region. */ + SetL3BlockEntry(l3, MemoryRegionVirtualTzramReadOnlyAlias.GetAddress(), MemoryRegionPhysicalTzramReadOnlyAlias.GetAddress(), MemoryRegionVirtualTzramReadOnlyAlias.GetSize(), MappingAttributesEl3SecureRoData); + + /* Map the DRAM secure data store region. */ + SetL3BlockEntry(l3, MemoryRegionVirtualDramSecureDataStore.GetAddress(), MemoryRegionPhysicalDramSecureDataStore.GetAddress(), MemoryRegionVirtualDramSecureDataStore.GetSize(), MappingAttributesEl3NonSecureDevice); + + /* Map the program region as rwx. */ + SetL3BlockEntry(l3, MemoryRegionVirtualTzramProgram.GetAddress(), MemoryRegionPhysicalTzramProgram.GetAddress(), MemoryRegionVirtualTzramProgram.GetSize(), MappingAttributesEl3SecureRwCode); + + /* Map the mariko program region as rwx. */ + SetL3BlockEntry(l3, MemoryRegionVirtualTzramMarikoProgram.GetAddress(), MemoryRegionPhysicalTzramMarikoProgram.GetAddress(), MemoryRegionPhysicalTzramMarikoProgram.GetSize(), MappingAttributesEl3SecureRwCode); + + /* Map the mariko program region as rwx. */ + SetL3BlockEntry(l3, MemoryRegionVirtualTzramMarikoProgramStack.GetAddress(), MemoryRegionPhysicalTzramMarikoProgramStack.GetAddress(), MemoryRegionPhysicalTzramMarikoProgramStack.GetSize(), MappingAttributesEl3SecureRwData); + + /* Map the boot code region. */ + SetL3BlockEntry(l3, MemoryRegionVirtualTzramBootCode.GetAddress(), MemoryRegionPhysicalTzramBootCode.GetAddress(), MemoryRegionVirtualTzramBootCode.GetSize(), MappingAttributesEl3SecureRwCode); + + /* Map the volatile data regions regions. */ + SetL3BlockEntry(l3, MemoryRegionVirtualTzramVolatileData.GetAddress(), MemoryRegionPhysicalTzramVolatileData.GetAddress(), MemoryRegionVirtualTzramVolatileData.GetSize(), MappingAttributesEl3SecureRwData); + SetL3BlockEntry(l3, MemoryRegionVirtualTzramVolatileStack.GetAddress(), MemoryRegionPhysicalTzramVolatileStack.GetAddress(), MemoryRegionVirtualTzramVolatileStack.GetSize(), MappingAttributesEl3SecureRwData); + + /* Map the configuration data. */ + SetL3BlockEntry(l3, MemoryRegionVirtualTzramConfigurationData.GetAddress(), MemoryRegionPhysicalTzramConfigurationData.GetAddress(), MemoryRegionVirtualTzramConfigurationData.GetSize(), MappingAttributesEl3SecureRwData); + + /* Map the page tables. */ + SetL3BlockEntry(l3, util::AlignDown(MemoryRegionVirtualTzramL1PageTable.GetAddress(), PageSize), util::AlignDown(MemoryRegionPhysicalTzramL1PageTable.GetAddress(), PageSize), PageSize, MappingAttributesEl3SecureRwData); + SetL3BlockEntry(l3, MemoryRegionVirtualTzramL2L3PageTable.GetAddress(), MemoryRegionPhysicalTzramL2L3PageTable.GetAddress(), MemoryRegionVirtualTzramL2L3PageTable.GetSize(), MappingAttributesEl3SecureRwData); + } + } + + constexpr bool ValidateTzramPageTables() { + u64 l1_table[MemoryRegionPhysicalTzramL1PageTable.GetSize() / sizeof(u64)] = {}; + u64 l2_l3_table[MemoryRegionPhysicalTzramL2L3PageTable.GetSize() / sizeof(u64)] = {}; + + MakePageTablesImpl(l1_table, l2_l3_table, l2_l3_table); + + return true; + } + + static_assert(ValidateTzramPageTables()); + + } + + void MakePageTable() { + u64 * const l1 = MemoryRegionPhysicalTzramL1PageTable.GetPointer<u64>(); + u64 * const l2_l3 = MemoryRegionPhysicalTzramL2L3PageTable.GetPointer<u64>(); + MakePageTablesImpl(l1, l2_l3, l2_l3); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_package2.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_package2.cpp new file mode 100644 index 00000000..88c8f118 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_package2.cpp @@ -0,0 +1,170 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../secmon_error.hpp" +#include "../secmon_key_storage.hpp" +#include "secmon_boot.hpp" + +namespace ams::secmon::boot { + + void CalculatePackage2Hash(se::Sha256Hash *dst, const pkg2::Package2Meta &meta, uintptr_t package2_start) { + /* Determine the region to hash. */ + const void *data = reinterpret_cast<const void *>(package2_start); + const size_t size = meta.GetSize(); + + /* Flush to ensure the SE sees the correct data. */ + hw::FlushDataCache(data, size); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Calculate the hash. */ + se::CalculateSha256(dst, data, size); + } + + bool VerifyPackage2Signature(pkg2::Package2Header &header, const void *mod, size_t mod_size) { + return VerifySignature(header.signature, sizeof(header.signature), mod, mod_size, std::addressof(header.meta), sizeof(header.meta)); + } + + int PrepareMasterKey(int key_generation) { + if (key_generation == GetKeyGeneration()) { + return pkg1::AesKeySlot_Master; + } + + constexpr int Slot = pkg1::AesKeySlot_Temporary; + LoadMasterKey(Slot, key_generation); + + return Slot; + } + + void PreparePackage2Key(int pkg2_slot, int key_generation, const void *key, size_t key_size) { + /* Get keyslot for the desired master key. */ + const int master_slot = PrepareMasterKey(key_generation); + + /* Load the package2 key into the desired keyslot. */ + se::SetEncryptedAesKey128(pkg2_slot, master_slot, key, key_size); + } + + void DecryptPackage2(void *dst, size_t dst_size, const void *src, size_t src_size, const void *key, size_t key_size, const void *iv, size_t iv_size, u8 key_generation) { + /* Ensure that the SE sees consistent data. */ + hw::FlushDataCache(key, key_size); + hw::FlushDataCache(src, src_size); + hw::FlushDataCache(dst, dst_size); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Load the package2 key into the temporary keyslot. */ + PreparePackage2Key(pkg1::AesKeySlot_Temporary, key_generation, key, key_size); + + /* Decrypt the data. */ + se::ComputeAes128Ctr(dst, dst_size, pkg1::AesKeySlot_Temporary, src, src_size, iv, iv_size); + + /* Clear the keyslot we just used. */ + se::ClearAesKeySlot(pkg1::AesKeySlot_Temporary); + + /* Ensure that the cpu sees consistent data. */ + hw::DataSynchronizationBarrierInnerShareable(); + hw::FlushDataCache(dst, dst_size); + hw::DataSynchronizationBarrierInnerShareable(); + } + + bool VerifyPackage2Meta(const pkg2::Package2Meta &meta) { + /* Get the obfuscated metadata. */ + const size_t size = meta.GetSize(); + const u8 key_generation = meta.GetKeyGeneration(); + + /* Check that size is big enough for the header. */ + if (size <= sizeof(pkg2::Package2Header)) { + return false; + } + + /* Check that the size isn't larger than what we allow. */ + if (size > pkg2::Package2SizeMax) { + return false; + } + + /* Check that the key generation is one that we can use. */ + static_assert(pkg1::KeyGeneration_Count == 20); + if (key_generation >= pkg1::KeyGeneration_Count) { + return false; + } + + /* Check the magic number. */ + if (!crypto::IsSameBytes(meta.magic, pkg2::Package2Meta::Magic::String, sizeof(meta.magic))) { + return false; + } + + /* Check the payload alignments. */ + if ((meta.entrypoint % pkg2::PayloadAlignment) != 0) { + return false; + } + + for (int i = 0; i < pkg2::PayloadCount; ++i) { + if ((meta.payload_sizes[i] % pkg2::PayloadAlignment) != 0) { + return false; + } + } + + /* Check that the sizes sum to the total. */ + if (size != sizeof(pkg2::Package2Header) + meta.payload_sizes[0] + meta.payload_sizes[1] + meta.payload_sizes[2]) { + return false; + } + + /* Check that the payloads do not overflow. */ + for (int i = 0; i < pkg2::PayloadCount; ++i) { + if (meta.payload_offsets[i] > meta.payload_offsets[i] + meta.payload_sizes[i]) { + return false; + } + } + + /* Verify that no payloads overlap. */ + for (int i = 0; i < pkg2::PayloadCount - 1; ++i) { + for (int j = i + 1; j < pkg2::PayloadCount; ++j) { + if (util::HasOverlap(meta.payload_offsets[i], meta.payload_sizes[i], meta.payload_offsets[j], meta.payload_sizes[j])) { + return false; + } + } + } + + /* Check whether any payload contains the entrypoint. */ + for (int i = 0; i < pkg2::PayloadCount; ++i) { + if (util::Contains(meta.payload_offsets[i], meta.payload_sizes[i], meta.entrypoint)) { + return true; + } + } + + /* No payload contains the entrypoint, so we're not valid. */ + return false; + } + + bool VerifyPackage2Version(const pkg2::Package2Meta &meta) { + return meta.bootloader_version <= pkg2::CurrentBootloaderVersion && meta.package2_version >= pkg2::MinimumValidDataVersion; + } + + bool VerifyPackage2Payloads(const pkg2::Package2Meta &meta, uintptr_t payload_address) { + /* Verify hashes match for all payloads. */ + for (int i = 0; i < pkg2::PayloadCount; ++i) { + /* Allow all-zero bytes to match any payload. */ + if (!(meta.payload_hashes[i][0] == 0 && std::memcmp(meta.payload_hashes[i] + 0, meta.payload_hashes[i] + 1, sizeof(meta.payload_hashes[i]) - 1) == 0)) { + if (!VerifyHash(meta.payload_hashes[i], payload_address, meta.payload_sizes[i])) { + return false; + } + } + + payload_address += meta.payload_sizes[i]; + } + + return true; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_size_data.s b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_size_data.s new file mode 100644 index 00000000..eb38daaf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/boot/secmon_size_data.s @@ -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 <http://www.gnu.org/licenses/>. + */ + +.section .metadata.sizes, "ax", %progbits +.align 6 +.global __metadata__sizes +__metadata__sizes: + .quad 0xAAAAAAAAAAAAAAAA, 0xBBBBBBBBBBBBBBBB + .quad __glob_start__ + .quad __bootcode_start__ + .quad __bootcode_end__ + .quad __program_start__ + .quad 0xCCCCCCCCCCCCCCCC, 0xDDDDDDDDDDDDDDDD diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_cache.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_cache.cpp new file mode 100644 index 00000000..5a60866e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_cache.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_cache.hpp" + +namespace ams::secmon { + + #include "secmon_cache_impl.inc" + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_cache.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_cache.hpp new file mode 100644 index 00000000..ce1de43a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_cache.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::secmon { + + #include "secmon_cache.inc" + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_cache.inc b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_cache.inc new file mode 100644 index 00000000..dbb34735 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_cache.inc @@ -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 <http://www.gnu.org/licenses/>. + */ + +void FlushEntireDataCache(); +void FlushEntireDataCacheLocal(); +void InvalidateEntireDataCache(); + +void EnsureMappingConsistency(); +void EnsureMappingConsistency(uintptr_t address); +void EnsureInstructionConsistency(); diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_cache_impl.inc b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_cache_impl.inc new file mode 100644 index 00000000..e28bd9ca --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_cache_impl.inc @@ -0,0 +1,172 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +namespace { + + ALWAYS_INLINE int FloorLog2(int v) { + return BITSIZEOF(u32) - (hw::CountLeadingZeros(static_cast<u32>(v)) + 1); + } + + ALWAYS_INLINE int CeilLog2(int v) { + const int log = FloorLog2(v); + return ((1 << log) == v) ? log : log + 1; + } + + void FlushDataCacheTo(int loc) { + for (int level = 0; level < loc; ++level) { + /* Set the selection register. */ + { + util::BitPack32 csselr = {}; + csselr.Set<hw::CsselrEl1::InD>(0); + csselr.Set<hw::CsselrEl1::Level>(level); + HW_CPU_SET_CSSELR_EL1(csselr); + } + + /* Ensure that reordering doesn't occur around this operation. */ + hw::InstructionSynchronizationBarrier(); + + /* Get ccsidr. */ + util::BitPack32 ccsidr; + HW_CPU_GET_CCSIDR_EL1(ccsidr); + + /* Get cache size id info. */ + const int num_sets = ccsidr.Get<hw::CcsidrEl1::NumSets>() + 1; + const int num_ways = ccsidr.Get<hw::CcsidrEl1::Associativity>() + 1; + const int line_size = ccsidr.Get<hw::CcsidrEl1::LineSize>() + 4; + + const int way_shift = 32 - FloorLog2(num_ways); + const int set_shift = line_size; + + for (int way = 0; way <= num_ways; way++) { + for (int set = 0; set <= num_sets; set++) { + const u64 value = (static_cast<u64>(way) << way_shift) | (static_cast<u64>(set) << set_shift) | (static_cast<u64>(level) << 1); + __asm__ __volatile__("dc cisw, %[value]" :: [value]"r"(value) : "memory"); + } + } + } + } + + void FlushDataCacheFrom(int loc) { + for (int level = loc - 1; level >= 0; --level) { + /* Set the selection register. */ + { + util::BitPack32 csselr = {}; + csselr.Set<hw::CsselrEl1::InD>(0); + csselr.Set<hw::CsselrEl1::Level>(level); + HW_CPU_SET_CSSELR_EL1(csselr); + } + + /* Ensure that reordering doesn't occur around this operation. */ + hw::InstructionSynchronizationBarrier(); + + /* Get ccsidr. */ + util::BitPack32 ccsidr; + HW_CPU_GET_CCSIDR_EL1(ccsidr); + + /* Get cache size id info. */ + const int num_sets = ccsidr.Get<hw::CcsidrEl1::NumSets>() + 1; + const int num_ways = ccsidr.Get<hw::CcsidrEl1::Associativity>() + 1; + const int line_size = ccsidr.Get<hw::CcsidrEl1::LineSize>() + 4; + + const int way_shift = 32 - FloorLog2(num_ways); + const int set_shift = line_size; + + for (int way = 0; way <= num_ways; way++) { + for (int set = 0; set <= num_sets; set++) { + const u64 value = (static_cast<u64>(way) << way_shift) | (static_cast<u64>(set) << set_shift) | (static_cast<u64>(level) << 1); + __asm__ __volatile__("dc cisw, %[value]" :: [value]"r"(value) : "memory"); + } + } + } + } + + void InvalidateDataCacheTo(int loc) { + for (int level = 0; level < loc; ++level) { + /* Set the selection register. */ + { + util::BitPack32 csselr = {}; + csselr.Set<hw::CsselrEl1::InD>(0); + csselr.Set<hw::CsselrEl1::Level>(level); + HW_CPU_SET_CSSELR_EL1(csselr); + } + + /* Ensure that reordering doesn't occur around this operation. */ + hw::InstructionSynchronizationBarrier(); + + /* Get ccsidr. */ + util::BitPack32 ccsidr; + HW_CPU_GET_CCSIDR_EL1(ccsidr); + + /* Get cache size id info. */ + const int num_sets = ccsidr.Get<hw::CcsidrEl1::NumSets>() + 1; + const int num_ways = ccsidr.Get<hw::CcsidrEl1::Associativity>() + 1; + const int line_size = ccsidr.Get<hw::CcsidrEl1::LineSize>() + 4; + + const int way_shift = 32 - FloorLog2(num_ways); + const int set_shift = line_size; + + for (int way = 0; way <= num_ways; way++) { + for (int set = 0; set <= num_sets; set++) { + const u64 value = (static_cast<u64>(way) << way_shift) | (static_cast<u64>(set) << set_shift) | (static_cast<u64>(level) << 1); + __asm__ __volatile__("dc isw, %[value]" :: [value]"r"(value) : "memory"); + } + } + } + } + +} + +void FlushEntireDataCache() { + util::BitPack32 clidr; + HW_CPU_GET_CLIDR_EL1(clidr); + FlushDataCacheTo(clidr.Get<hw::ClidrEl1::Loc>()); +} + +void FlushEntireDataCacheLocal() { + util::BitPack32 clidr; + HW_CPU_GET_CLIDR_EL1(clidr); + FlushDataCacheFrom(clidr.Get<hw::ClidrEl1::Louis>()); +} + +void InvalidateEntireDataCache() { + util::BitPack32 clidr; + HW_CPU_GET_CLIDR_EL1(clidr); + InvalidateDataCacheTo(clidr.Get<hw::ClidrEl1::Loc>()); +} + +void EnsureMappingConsistency() { + ::ams::hw::DataSynchronizationBarrierInnerShareable(); + ::ams::hw::InvalidateEntireTlb(); + ::ams::hw::DataSynchronizationBarrierInnerShareable(); + + ::ams::hw::InstructionSynchronizationBarrier(); +} + +void EnsureMappingConsistency(uintptr_t address) { + ::ams::hw::DataSynchronizationBarrierInnerShareable(); + ::ams::hw::InvalidateTlb(address); + ::ams::hw::DataSynchronizationBarrierInnerShareable(); + + ::ams::hw::InstructionSynchronizationBarrier(); +} + +void EnsureInstructionConsistency() { + ::ams::hw::DataSynchronizationBarrierInnerShareable(); + ::ams::hw::InvalidateEntireInstructionCache(); + ::ams::hw::DataSynchronizationBarrierInnerShareable(); + + ::ams::hw::InstructionSynchronizationBarrier(); +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_cpu_context.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_cpu_context.cpp new file mode 100644 index 00000000..b39dde89 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_cpu_context.cpp @@ -0,0 +1,142 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_cpu_context.hpp" +#include "secmon_error.hpp" + +namespace ams::secmon { + + namespace { + + struct DebugRegisters { + u32 osdttrx_el1; + u32 osdtrtx_el1; + u32 mdscr_el1; + u32 oseccr_el1; + u32 mdccint_el1; + u32 dbgclaimclr_el1; + u32 dbgvcr32_el2; + u32 sder32_el3; + u32 mdcr_el2; + u32 mdcr_el3; + u32 spsr_el3; + }; + + struct CoreContext { + EntryContext entry_context; + bool is_on; + bool is_reset_expected; + bool is_debug_registers_saved; + DebugRegisters debug_registers; + }; + + void SaveDebugRegisters(DebugRegisters &dr) { + /* Set the OS lock; this will be unlocked by entry code. */ + HW_CPU_SET_OSLAR_EL1(1); + + /* Save general debug registers. */ + HW_CPU_GET_OSDTRRX_EL1 (dr.osdttrx_el1); + HW_CPU_GET_OSDTRTX_EL1 (dr.osdtrtx_el1); + HW_CPU_GET_MDSCR_EL1 (dr.mdscr_el1); + HW_CPU_GET_OSECCR_EL1 (dr.oseccr_el1); + HW_CPU_GET_MDCCINT_EL1 (dr.mdccint_el1); + HW_CPU_GET_DBGCLAIMCLR_EL1(dr.dbgclaimclr_el1); + HW_CPU_GET_DBGVCR32_EL2 (dr.dbgvcr32_el2); + HW_CPU_GET_SDER32_EL3 (dr.sder32_el3); + HW_CPU_GET_MDCR_EL2 (dr.mdcr_el2); + HW_CPU_GET_MDCR_EL3 (dr.mdcr_el3); + HW_CPU_GET_SPSR_EL3 (dr.spsr_el3); + } + + void RestoreDebugRegisters(const DebugRegisters &dr) { + /* Restore general debug registers. */ + HW_CPU_SET_OSDTRRX_EL1 (dr.osdttrx_el1); + HW_CPU_SET_OSDTRTX_EL1 (dr.osdtrtx_el1); + HW_CPU_SET_MDSCR_EL1 (dr.mdscr_el1); + HW_CPU_SET_OSECCR_EL1 (dr.oseccr_el1); + HW_CPU_SET_MDCCINT_EL1 (dr.mdccint_el1); + HW_CPU_SET_DBGCLAIMCLR_EL1(dr.dbgclaimclr_el1); + HW_CPU_SET_DBGVCR32_EL2 (dr.dbgvcr32_el2); + HW_CPU_SET_SDER32_EL3 (dr.sder32_el3); + HW_CPU_SET_MDCR_EL2 (dr.mdcr_el2); + HW_CPU_SET_MDCR_EL3 (dr.mdcr_el3); + HW_CPU_SET_SPSR_EL3 (dr.spsr_el3); + } + + constinit CoreContext g_core_contexts[NumCores] = {}; + + } + + bool IsCoreOn(int core) { + return g_core_contexts[core].is_on; + } + + void SetCoreOff() { + g_core_contexts[hw::GetCurrentCoreId()].is_on = false; + } + + bool IsResetExpected() { + return g_core_contexts[hw::GetCurrentCoreId()].is_reset_expected; + } + + void SetResetExpected(int core, bool expected) { + g_core_contexts[core].is_reset_expected = expected; + } + + void SetResetExpected(bool expected) { + SetResetExpected(hw::GetCurrentCoreId(), expected); + } + + void SetEntryContext(int core, uintptr_t address, uintptr_t arg) { + g_core_contexts[core].entry_context.pc = address; + g_core_contexts[core].entry_context.x0 = arg; + } + + void GetEntryContext(EntryContext *out) { + auto &ctx = g_core_contexts[hw::GetCurrentCoreId()]; + + const auto pc = ctx.entry_context.pc; + const auto x0 = ctx.entry_context.x0; + + if (pc == 0 || ctx.is_on) { + SetError(pkg1::ErrorInfo_InvalidCoreContext); + AMS_ABORT("Invalid core context"); + } + + ctx.entry_context = {}; + ctx.is_on = true; + + out->pc = pc; + out->x0 = x0; + } + + void SaveDebugRegisters() { + auto &ctx = g_core_contexts[hw::GetCurrentCoreId()]; + + SaveDebugRegisters(ctx.debug_registers); + ctx.is_debug_registers_saved = true; + } + + void RestoreDebugRegisters() { + auto &ctx = g_core_contexts[hw::GetCurrentCoreId()]; + + if (ctx.is_debug_registers_saved) { + RestoreDebugRegisters(ctx.debug_registers); + ctx.is_debug_registers_saved = false; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_cpu_context.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_cpu_context.hpp new file mode 100644 index 00000000..8aedd1fc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_cpu_context.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::secmon { + + constexpr inline int NumCores = 4; + + struct EntryContext { + u64 x0; + u64 pc; + }; + + bool IsCoreOn(int core); + void SetCoreOff(); + + bool IsResetExpected(); + void SetResetExpected(int core, bool expected); + void SetResetExpected(bool expected); + + void SetEntryContext(int core, uintptr_t address, uintptr_t arg); + void GetEntryContext(EntryContext *out); + + void SaveDebugRegisters(); + void RestoreDebugRegisters(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_error.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_error.cpp new file mode 100644 index 00000000..a8d17bc2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_error.cpp @@ -0,0 +1,136 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_error.hpp" + +namespace ams { + + namespace { + + constexpr bool SaveSystemStateForDebug = false; + + } +} + +namespace ams::diag { + + namespace { + + ALWAYS_INLINE void SaveSystemStateForDebugAbort() { + *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x00) = 0xDDDDDDDD; + + u64 temp_reg; + __asm__ __volatile__("mov %0, lr" : "=r"(temp_reg) :: "memory"); + *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x10) = static_cast<u32>(temp_reg >> 0); + *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x14) = static_cast<u32>(temp_reg >> 32); + + + __asm__ __volatile__("mov %0, sp" : "=r"(temp_reg) :: "memory"); + for (int i = 0; i < 0x100; i += 4) { + *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x20 + i) = *(volatile u32 *)(temp_reg + i); + } + *(volatile u32 *)(secmon::MemoryRegionVirtualDevicePmc.GetAddress() + 0x50) = 0x02; + *(volatile u32 *)(secmon::MemoryRegionVirtualDevicePmc.GetAddress() + 0x00) = 0x10; + + util::WaitMicroSeconds(1000); + } + + } + + NORETURN void AbortImpl() { + /* Perform any necessary (typically none) debugging. */ + if constexpr (SaveSystemStateForDebug) { + SaveSystemStateForDebugAbort(); + } + + secmon::SetError(pkg1::ErrorInfo_UnknownAbort); + secmon::ErrorReboot(); + } + + #include <exosphere/diag/diag_detailed_assertion_impl.inc> + +} + +namespace ams::secmon { + + namespace { + + constexpr inline uintptr_t PMC = MemoryRegionVirtualDevicePmc.GetAddress(); + + ALWAYS_INLINE void SaveSystemStateForDebugErrorReboot() { + u64 temp_reg; + *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x00) = 0x5A5A5A5A; + + __asm__ __volatile__("mrs %0, esr_el3" : "=r"(temp_reg) :: "memory"); + *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x08) = static_cast<u32>(temp_reg >> 0); + *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x0C) = static_cast<u32>(temp_reg >> 32); + + __asm__ __volatile__("mrs %0, elr_el3" : "=r"(temp_reg) :: "memory"); + *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x10) = static_cast<u32>(temp_reg >> 0); + *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x14) = static_cast<u32>(temp_reg >> 32); + + __asm__ __volatile__("mrs %0, far_el3" : "=r"(temp_reg) :: "memory"); + *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x18) = static_cast<u32>(temp_reg >> 0); + *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x1C) = static_cast<u32>(temp_reg >> 32); + + __asm__ __volatile__("mov %0, lr" : "=r"(temp_reg) :: "memory"); + *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x20) = static_cast<u32>(temp_reg >> 0); + *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x24) = static_cast<u32>(temp_reg >> 32); + + __asm__ __volatile__("mov %0, sp" : "=r"(temp_reg) :: "memory"); + *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x30) = static_cast<u32>(temp_reg >> 0); + *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x34) = static_cast<u32>(temp_reg >> 32); + + for (int i = 0; i < 0x100; i += 4) { + *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x40 + i) = *(volatile u32 *)(temp_reg + i); + } + *(volatile u32 *)(secmon::MemoryRegionVirtualDevicePmc.GetAddress() + 0x50) = 0x02; + *(volatile u32 *)(secmon::MemoryRegionVirtualDevicePmc.GetAddress() + 0x00) = 0x10; + + util::WaitMicroSeconds(1000); + } + + } + + void SetError(pkg1::ErrorInfo info) { + const uintptr_t address = secmon::MemoryRegionVirtualDevicePmc.GetAddress() + PKG1_SECURE_MONITOR_PMC_ERROR_SCRATCH; + + if (reg::Read(address) == pkg1::ErrorInfo_None) { + reg::Write(address, info); + } + } + + NORETURN void ErrorReboot() { + /* Perform any necessary (typically none) debugging. */ + if constexpr (SaveSystemStateForDebug) { + SaveSystemStateForDebugErrorReboot(); + } + + /* Lockout the security engine. */ + se::Lockout(); + + /* Lockout fuses. */ + fuse::Lockout(); + + /* Disable crypto operations after reboot. */ + reg::Write(PMC + APBDEV_PMC_CRYPTO_OP, 0); + + while (true) { + wdt::Reboot(); + } + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_error.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_error.hpp new file mode 100644 index 00000000..99d5d3bd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_error.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::secmon { + + void SetError(pkg1::ErrorInfo info); + NORETURN void ErrorReboot(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_exception_handler.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_exception_handler.cpp new file mode 100644 index 00000000..e1af2105 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_exception_handler.cpp @@ -0,0 +1,99 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_error.hpp" + +namespace ams::secmon { + + namespace { + + constexpr inline uintptr_t PMC = MemoryRegionVirtualDevicePmc.GetAddress(); + + constinit util::Atomic<bool> g_is_locked = false; + + } + + void ExceptionHandlerImpl(uintptr_t lr, uintptr_t sp) { + /* On release config, we won't actually use the passed parameters. */ + AMS_UNUSED(lr, sp); + + /* Ensure that previous logs have been flushed. */ + AMS_LOG_FLUSH(); + + /* Get system registers. */ + uintptr_t far_el1, far_el3, elr_el3; + util::BitPack32 esr_el3; + + HW_CPU_GET_FAR_EL1(far_el1); + HW_CPU_GET_FAR_EL3(far_el3); + HW_CPU_GET_ELR_EL3(elr_el3); + HW_CPU_GET_ESR_EL3(esr_el3); + + /* Print some whitespace before the exception handler. */ + AMS_LOG("\n\n"); + AMS_SECMON_LOG("ExceptionHandler\n"); + AMS_SECMON_LOG("----------------\n"); + AMS_SECMON_LOG("esr: 0x%08X\n", esr_el3.value); + AMS_SECMON_LOG(" Exception Class: 0x%02X\n", esr_el3.Get<hw::EsrEl3::Ec>()); + AMS_SECMON_LOG(" Instruction Length: %d\n", esr_el3.Get<hw::EsrEl3::Il>() ? 32 : 16); + AMS_SECMON_LOG(" Instruction Specific Syndrome: 0x%07X\n", esr_el3.Get<hw::EsrEl3::Iss>()); + + AMS_SECMON_LOG("far_el1: 0x%016lX\n", far_el1); + AMS_SECMON_LOG("far_el3: 0x%016lX\n", far_el3); + AMS_SECMON_LOG("elr_el3: 0x%016lX\n", elr_el3); + + AMS_SECMON_LOG("lr: 0x%016lX\n", lr); + AMS_SECMON_LOG("sp: 0x%016lX\n", sp); + + AMS_DUMP(reinterpret_cast<void *>(sp), util::AlignUp(sp, mmu::PageSize) - sp); + + AMS_LOG_FLUSH(); + } + + NORETURN void ExceptionHandler() { + /* Get link register and stack pointer. */ + u64 lr, sp; + { + __asm__ __volatile__("mov %0, lr" : "=r"(lr) :: "memory"); + __asm__ __volatile__("mov %0, sp" : "=r"(sp) :: "memory"); + } + + /* Acquire exclusive access to exception handling logic. */ + if (!g_is_locked.Exchange(true)) { + /* Invoke the exception handler impl. */ + ExceptionHandlerImpl(lr, sp); + + /* Lockout the security engine. */ + se::Lockout(); + + /* Lockout fuses. */ + fuse::Lockout(); + + /* Disable crypto operations after reboot. */ + reg::Write(PMC + APBDEV_PMC_CRYPTO_OP, 0); + + /* Perform an error reboot. */ + secmon::SetError(pkg1::ErrorInfo_UnknownAbort); + secmon::ErrorReboot(); + } else { + /* Wait forever while the first core prints the exception and reboots. */ + while (true) { + util::WaitMicroSeconds(1000); + } + } + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_exception_vectors.s b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_exception_vectors.s new file mode 100644 index 00000000..073c8077 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_exception_vectors.s @@ -0,0 +1,366 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* Some macros taken from https://github.com/ARM-software/arm-trusted-firmware/blob/master/include/common/aarch64/asm_macros.S */ +/* + * Copyright (c) 2013-2017, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/* + * Declare the exception vector table, enforcing it is aligned on a + * 2KB boundary, as required by the ARMv8 architecture. + * Use zero bytes as the fill value to be stored in the padding bytes + * so that it inserts illegal AArch64 instructions. This increases + * security, robustness and potentially facilitates debugging. + */ +.macro vector_base label, section_name=.vectors +.section \section_name, "ax" +.align 11, 0 +\label: +.endm + +/* + * Create an entry in the exception vector table, enforcing it is + * aligned on a 128-byte boundary, as required by the ARMv8 architecture. + * Use zero bytes as the fill value to be stored in the padding bytes + * so that it inserts illegal AArch64 instructions. This increases + * security, robustness and potentially facilitates debugging. + */ +.macro vector_entry label, section_name=.vectors +.cfi_sections .debug_frame +.section \section_name, "ax" +.align 7, 0 +.type \label, %function +.func \label +.cfi_startproc +\label: +.endm + +/* + * This macro verifies that the given vector doesnt exceed the + * architectural limit of 32 instructions. This is meant to be placed + * immediately after the last instruction in the vector. It takes the + * vector entry as the parameter + */ +.macro check_vector_size since + .endfunc + .cfi_endproc + .if (. - \since) > (32 * 4) + .error "Vector exceeds 32 instructions" + .endif +.endm + +/* Actual Vectors for Secure Monitor. */ +.global _ZN3ams6secmon16ExceptionVectorsEv +vector_base _ZN3ams6secmon16ExceptionVectorsEv + +/* Current EL, SP0 */ +vector_entry synch_sp0 + /* Branch to the exception handler. */ + b _ZN3ams6secmon26UnexpectedExceptionHandlerEv + .endfunc + .cfi_endproc +_ZN3ams6secmon26UnexpectedExceptionHandlerEv: + #if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING) + /* Jump to the debug exception handler. */ + ldr x16, =_ZN3ams6secmon16ExceptionHandlerEv + br x16 + #else + /* Load the ErrorInfo scratch. */ + ldr x0, =0x1F004AC40 + + /* Write ErrorInfo_UnknownAbort to it. */ + ldr w1, =0x07F00010 + str w1, [x0] + + /* Perform an error reboot. */ + b _ZN3ams6secmon11ErrorRebootEv + #endif + +vector_entry irq_sp0 + /* An unexpected exception was taken. */ + b _ZN3ams6secmon26UnexpectedExceptionHandlerEv + check_vector_size irq_sp0 + +vector_entry fiq_sp0 + /* An unexpected exception was taken. */ + b _ZN3ams6secmon26UnexpectedExceptionHandlerEv + check_vector_size fiq_sp0 + +vector_entry serror_sp0 + /* An unexpected exception was taken. */ + b _ZN3ams6secmon26UnexpectedExceptionHandlerEv + check_vector_size serror_sp0 + +/* Current EL, SPx */ +vector_entry synch_spx + /* An unexpected exception was taken. */ + b _ZN3ams6secmon26UnexpectedExceptionHandlerEv + check_vector_size synch_spx + +vector_entry irq_spx + /* An unexpected exception was taken. */ + b _ZN3ams6secmon26UnexpectedExceptionHandlerEv + check_vector_size irq_spx + +vector_entry fiq_spx + /* An unexpected exception was taken. */ + b _ZN3ams6secmon26UnexpectedExceptionHandlerEv + check_vector_size fiq_spx + +vector_entry serror_spx + /* An unexpected exception was taken. */ + b _ZN3ams6secmon26UnexpectedExceptionHandlerEv + check_vector_size serror_spx + +/* Lower EL, A64 */ +vector_entry synch_a64 + /* Check whether the exception is an SMC. If it's not, take the unexpected handler. */ + stp x29, x30, [sp, #-0x10]! + mrs x30, esr_el3 + lsr w29, w30, #26 + cmp w29, #0x17 + ldp x29, x30, [sp], #0x10 + b.ne _ZN3ams6secmon26UnexpectedExceptionHandlerEv + + /* Save x29 and x30. */ + stp x29, x30, [sp, #-0x10]! + + /* Get the current core. */ + mrs x29, mpidr_el1 + and x29, x29, #3 + + /* If we're not on core 3, take the core0-2 handler. */ + cmp x29, #3 + b.ne _ZN3ams6secmon25HandleSmcExceptionCore012Ev + + /* Handle the smc. */ + bl _ZN3ams6secmon18HandleSmcExceptionEv + + /* Return. */ + ldp x29, x30, [sp], #0x10 + eret + check_vector_size synch_a64 + +vector_entry irq_a64 + /* An unexpected exception was taken. */ + b _ZN3ams6secmon26UnexpectedExceptionHandlerEv + check_vector_size irq_a64 + +vector_entry fiq_a64 + /* Save x18, x26-x30. */ + stp x29, x30, [sp, #-0x10]! + stp x28, x18, [sp, #-0x10]! + stp x26, x27, [sp, #-0x10]! + + /* Set x18 to the global data region. */ + ldr x18, =0x1F01FA000 + + /* Get the current core. */ + mrs x29, mpidr_el1 + and x29, x29, #3 + + /* If we're not on core 3, take the core0-2 handler. */ + cmp x29, #3 + b.ne _ZN3ams6secmon25HandleFiqExceptionCore012Ev + + /* Handle the fiq exception. */ + bl _ZN3ams6secmon18HandleFiqExceptionEv + + /* Restore registers. */ + ldp x26, x27, [sp], #0x10 + ldp x28, x18, [sp], #0x10 + ldp x29, x30, [sp], #0x10 + + /* Return. */ + eret + check_vector_size fiq_a64 + +vector_entry serror_a64 + /* An unexpected exception was taken. */ + b _ZN3ams6secmon26UnexpectedExceptionHandlerEv + .endfunc + .cfi_endproc +_ZN3ams6secmon25HandleSmcExceptionCore012Ev: + /* Acquire exclusive access to the common smc stack. */ + stp x4, x5, [sp, #-0x10]! + stp x2, x3, [sp, #-0x10]! + stp x0, x1, [sp, #-0x10]! + bl _ZN3ams6secmon25AcquireCommonSmcStackLockEv + ldp x0, x1, [sp], #0x10 + ldp x2, x3, [sp], #0x10 + ldp x4, x5, [sp], #0x10 + + /* Pivot to use the common smc stack. */ + mov x30, sp + ldr x29, =0x1F01F6E80 + mov sp, x29 + stp x29, x30, [sp, #-0x10]! + + /* Handle the SMC. */ + bl _ZN3ams6secmon18HandleSmcExceptionEv + + /* Restore our core-specific stack. */ + ldp x29, x30, [sp], #0x10 + mov sp, x30 + + /* Release our exclusive access to the common smc stack. */ + stp x0, x1, [sp, #-0x10]! + bl _ZN3ams6secmon25ReleaseCommonSmcStackLockEv + ldp x0, x1, [sp], #0x10 + + /* Return. */ + ldp x29, x30, [sp], #0x10 + eret + +/* Lower EL, A32 */ +vector_entry synch_a32 + /* An unexpected exception was taken. */ + b _ZN3ams6secmon26UnexpectedExceptionHandlerEv + check_vector_size synch_a32 + +vector_entry irq_a32 + /* An unexpected exception was taken. */ + b _ZN3ams6secmon26UnexpectedExceptionHandlerEv + .endfunc + .cfi_endproc +_ZN3ams6secmon18HandleSmcExceptionEv: + /* Save registers. */ + stp x29, x30, [sp, #-0x10]! + stp x18, x19, [sp, #-0x10]! + stp x16, x17, [sp, #-0x10]! + stp x14, x15, [sp, #-0x10]! + stp x12, x13, [sp, #-0x10]! + stp x10, x11, [sp, #-0x10]! + stp x8, x9, [sp, #-0x10]! + stp x6, x7, [sp, #-0x10]! + stp x4, x5, [sp, #-0x10]! + stp x2, x3, [sp, #-0x10]! + stp x0, x1, [sp, #-0x10]! + + /* Set x18 to the global data region. */ + ldr x18, =0x1F01FA000 + + /* Get esr. */ + mrs x0, esr_el3 + and x0, x0, #0xFFFF + + /* Get the function arguments. */ + mov x1, sp + + /* Invoke the smc handler. */ + bl _ZN3ams6secmon3smc9HandleSmcEiRNS1_12SmcArgumentsE + + /* Restore registers. */ + ldp x0, x1, [sp], #0x10 + ldp x2, x3, [sp], #0x10 + ldp x4, x5, [sp], #0x10 + ldp x6, x7, [sp], #0x10 + ldp x8, x9, [sp], #0x10 + ldp x10, x11, [sp], #0x10 + ldp x12, x13, [sp], #0x10 + ldp x14, x15, [sp], #0x10 + ldp x16, x17, [sp], #0x10 + ldp x18, x19, [sp], #0x10 + ldp x29, x30, [sp], #0x10 + + ret +vector_entry fiq_a32 + /* Handle fiq from a32 the same as fiq from a64. */ + b fiq_a64 + .endfunc + .cfi_endproc +_ZN3ams6secmon18HandleFiqExceptionEv: + /* Save registers. */ + stp x29, x30, [sp, #-0x10]! + stp x24, x25, [sp, #-0x10]! + stp x22, x23, [sp, #-0x10]! + stp x20, x21, [sp, #-0x10]! + stp x18, x19, [sp, #-0x10]! + stp x16, x17, [sp, #-0x10]! + stp x14, x15, [sp, #-0x10]! + stp x12, x13, [sp, #-0x10]! + stp x10, x11, [sp, #-0x10]! + stp x8, x9, [sp, #-0x10]! + stp x6, x7, [sp, #-0x10]! + stp x4, x5, [sp, #-0x10]! + stp x2, x3, [sp, #-0x10]! + stp x0, x1, [sp, #-0x10]! + + /* Invoke the interrupt handler. */ + bl _ZN3ams6secmon15HandleInterruptEv + + /* Restore registers. */ + ldp x0, x1, [sp], #0x10 + ldp x2, x3, [sp], #0x10 + ldp x4, x5, [sp], #0x10 + ldp x6, x7, [sp], #0x10 + ldp x8, x9, [sp], #0x10 + ldp x10, x11, [sp], #0x10 + ldp x12, x13, [sp], #0x10 + ldp x14, x15, [sp], #0x10 + ldp x16, x17, [sp], #0x10 + ldp x18, x19, [sp], #0x10 + ldp x20, x21, [sp], #0x10 + ldp x22, x23, [sp], #0x10 + ldp x24, x25, [sp], #0x10 + ldp x29, x30, [sp], #0x10 + + ret + +vector_entry serror_a32 + /* An unexpected exception was taken. */ + b _ZN3ams6secmon26UnexpectedExceptionHandlerEv + .endfunc + .cfi_endproc +_ZN3ams6secmon25HandleFiqExceptionCore012Ev: + /* Acquire exclusive access to the common smc stack. */ + stp x4, x5, [sp, #-0x10]! + stp x2, x3, [sp, #-0x10]! + stp x0, x1, [sp, #-0x10]! + bl _ZN3ams6secmon25AcquireCommonSmcStackLockEv + ldp x0, x1, [sp], #0x10 + ldp x2, x3, [sp], #0x10 + ldp x4, x5, [sp], #0x10 + + /* Pivot to use the common smc stack. */ + mov x30, sp + ldr x29, =0x1F01F6E80 + mov sp, x29 + stp x29, x30, [sp, #-0x10]! + + /* Handle the fiq exception. */ + bl _ZN3ams6secmon18HandleFiqExceptionEv + + /* Restore our core-specific stack. */ + ldp x29, x30, [sp], #0x10 + mov sp, x30 + + /* Release our exclusive access to the common smc stack. */ + stp x0, x1, [sp, #-0x10]! + bl _ZN3ams6secmon25ReleaseCommonSmcStackLockEv + ldp x0, x1, [sp], #0x10 + + /* Return. */ + ldp x26, x27, [sp], #0x10 + ldp x28, x18, [sp], #0x10 + ldp x29, x30, [sp], #0x10 + eret + + /* Instantiate the literal pool for the exception vectors. */ + .ltorg diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_interrupt_handler.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_interrupt_handler.cpp new file mode 100644 index 00000000..a7e7163a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_interrupt_handler.cpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_interrupt_handler.hpp" +#include "secmon_error.hpp" + +namespace ams::secmon { + + namespace { + + constexpr inline int InterruptHandlersMax = 4; + + constinit InterruptHandler g_handlers[InterruptHandlersMax] = {}; + constinit int g_interrupt_ids[InterruptHandlersMax] = {}; + constinit u8 g_interrupt_core_masks[InterruptHandlersMax] = {}; + + } + + void SetInterruptHandler(int interrupt_id, u8 core_mask, InterruptHandler handler) { + for (int i = 0; i < InterruptHandlersMax; ++i) { + if (g_interrupt_ids[i] == 0) { + g_interrupt_ids[i] = interrupt_id; + g_handlers[i] = handler; + g_interrupt_core_masks[i] = core_mask; + return; + } + } + + AMS_ABORT("Failed to register interrupt handler"); + } + + void HandleInterrupt() { + /* Get the interrupt id. */ + const int interrupt_id = gic::GetInterruptRequestId(); + if (interrupt_id >= gic::InterruptCount) { + /* Invalid interrupt number, just return. */ + return; + } + + /* Check each handler. */ + for (int i = 0; i < InterruptHandlersMax; ++i) { + if (g_interrupt_ids[i] == interrupt_id) { + /* Validate that we can invoke the handler. */ + AMS_ABORT_UNLESS((g_interrupt_core_masks[i] & (1u << hw::GetCurrentCoreId())) != 0); + + /* Invoke the handler. */ + g_handlers[i](); + gic::SetEndOfInterrupt(interrupt_id); + return; + } + } + + AMS_ABORT("Failed to find interrupt handler."); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_interrupt_handler.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_interrupt_handler.hpp new file mode 100644 index 00000000..4a302261 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_interrupt_handler.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::secmon { + + using InterruptHandler = void (*)(); + + void SetInterruptHandler(int interrupt_id, u8 core_mask, InterruptHandler handler); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_key_storage.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_key_storage.cpp new file mode 100644 index 00000000..91c85e50 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_key_storage.cpp @@ -0,0 +1,109 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_key_storage.hpp" + +namespace ams::secmon { + + namespace { + + constexpr const u8 RsaPublicKey[] = { 0x00, 0x01, 0x00, 0x01 }; + + constinit u8 g_rsa_moduli[ImportRsaKey_Count][se::RsaSize] = {}; + constinit bool g_rsa_modulus_committed[ImportRsaKey_Count] = {}; + + ALWAYS_INLINE u8 *GetRsaKeyModulus(ImportRsaKey which) { + return g_rsa_moduli[which]; + } + + ALWAYS_INLINE u8 *GetRsaKeyPrivateExponent(ImportRsaKey which) { + return ::ams::secmon::impl::GetRsaPrivateExponentStorage(static_cast<int>(which)); + } + + ALWAYS_INLINE bool IsRsaKeyProvisional(ImportRsaKey which) { + return g_rsa_modulus_committed[which] == false; + } + + void ClearRsaKeyModulus(ImportRsaKey which) { + g_rsa_modulus_committed[which] = false; + std::memset(g_rsa_moduli[which], 0, se::RsaSize); + } + + ALWAYS_INLINE u8 *GetMasterKeyStorage(int index) { + return ::ams::secmon::impl::GetMasterKeyStorage(index); + } + + ALWAYS_INLINE u8 *GetDeviceMasterKeyStorage(int index) { + return ::ams::secmon::impl::GetDeviceMasterKeyStorage(index); + } + + } + + void ImportRsaKeyExponent(ImportRsaKey which, const void *src, size_t size) { + /* If we import an exponent, the modulus is not committed. */ + ClearRsaKeyModulus(which); + + /* Copy the exponent. */ + std::memcpy(GetRsaKeyPrivateExponent(which), src, size); + } + + void ImportRsaKeyModulusProvisionally(ImportRsaKey which, const void *src, size_t size) { + std::memcpy(GetRsaKeyModulus(which), src, std::min(static_cast<int>(size), se::RsaSize)); + } + + void CommitRsaKeyModulus(ImportRsaKey which) { + g_rsa_modulus_committed[which] = true; + } + + bool LoadRsaKey(int slot, ImportRsaKey which) { + /* If the key is still provisional, we can't load it. */ + if (IsRsaKeyProvisional(which)) { + return false; + } + + se::SetRsaKey(slot, GetRsaKeyModulus(which), se::RsaSize, GetRsaKeyPrivateExponent(which), se::RsaSize); + return true; + } + + void LoadProvisionalRsaKey(int slot, ImportRsaKey which) { + se::SetRsaKey(slot, GetRsaKeyModulus(which), se::RsaSize, GetRsaKeyPrivateExponent(which), se::RsaSize); + } + + void LoadProvisionalRsaPublicKey(int slot, ImportRsaKey which) { + se::SetRsaKey(slot, GetRsaKeyModulus(which), se::RsaSize, RsaPublicKey, sizeof(RsaPublicKey)); + } + + void SetMasterKey(int generation, const void *src, size_t size) { + const int index = generation - pkg1::KeyGeneration_Min; + se::EncryptAes128(GetMasterKeyStorage(index), se::AesBlockSize, pkg1::AesKeySlot_RandomForKeyStorageWrap, src, size); + } + + void LoadMasterKey(int slot, int generation) { + const int index = std::max(0, generation - pkg1::KeyGeneration_Min); + se::SetEncryptedAesKey128(slot, pkg1::AesKeySlot_RandomForKeyStorageWrap, GetMasterKeyStorage(index), se::AesBlockSize); + } + + void SetDeviceMasterKey(int generation, const void *src, size_t size) { + const int index = generation - pkg1::KeyGeneration_4_0_0; + se::EncryptAes128(GetDeviceMasterKeyStorage(index), se::AesBlockSize, pkg1::AesKeySlot_RandomForKeyStorageWrap, src, size); + } + + void LoadDeviceMasterKey(int slot, int generation) { + const int index = std::max(0, generation - pkg1::KeyGeneration_4_0_0); + se::SetEncryptedAesKey128(slot, pkg1::AesKeySlot_RandomForKeyStorageWrap, GetDeviceMasterKeyStorage(index), se::AesBlockSize); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_key_storage.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_key_storage.hpp new file mode 100644 index 00000000..22da712a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_key_storage.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::secmon { + + /* NOTE: Lotus and EsDrmCert are switched here versus official enum, */ + /* however, this considerably simplifies logic. */ + enum ImportRsaKey { + ImportRsaKey_Lotus = 0, + ImportRsaKey_EsDrmCert = 1, + ImportRsaKey_Ssl = 2, + ImportRsaKey_EsClientCert = 3, + + ImportRsaKey_Count = 4, + }; + static_assert(util::size(secmon::ConfigurationContext{}.rsa_private_exponents) == ImportRsaKey_Count); + + void ImportRsaKeyExponent(ImportRsaKey which, const void *src, size_t size); + void ImportRsaKeyModulusProvisionally(ImportRsaKey which, const void *src, size_t size); + void CommitRsaKeyModulus(ImportRsaKey which); + + bool LoadRsaKey(int slot, ImportRsaKey which); + void LoadProvisionalRsaKey(int slot, ImportRsaKey which); + void LoadProvisionalRsaPublicKey(int slot, ImportRsaKey which); + + void SetMasterKey(int generation, const void *src, size_t size); + void LoadMasterKey(int slot, int generation); + + void SetDeviceMasterKey(int generation, const void *src, size_t size); + void LoadDeviceMasterKey(int slot, int generation); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_map.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_map.cpp new file mode 100644 index 00000000..caca8cdb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_map.cpp @@ -0,0 +1,322 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_cache.hpp" +#include "secmon_setup.hpp" +#include "secmon_spinlock.hpp" +#include "secmon_map.hpp" +#include "smc/secmon_smc_info.hpp" + +namespace ams::secmon { + + namespace { + + constexpr inline const uintptr_t BootCodeAddress = MemoryRegionVirtualTzramBootCode.GetAddress(); + constexpr inline const size_t BootCodeSize = MemoryRegionVirtualTzramBootCode.GetSize(); + + constinit uintptr_t g_smc_user_page_physical_address = 0; + constinit uintptr_t g_ams_iram_page_physical_address = 0; + constinit uintptr_t g_ams_user_page_physical_address = 0; + + constinit SpinLockType g_ams_iram_page_spin_lock = {}; + constinit SpinLockType g_ams_user_page_spin_lock = {}; + + using namespace ams::mmu; + + constexpr inline PageTableMappingAttribute MappingAttributesEl3NonSecureRwData = AddMappingAttributeIndex(PageTableMappingAttributes_El3NonSecureRwData, MemoryAttributeIndexNormal); + constexpr inline PageTableMappingAttribute MappingAttributesEl3NonSecureDevice = AddMappingAttributeIndex(PageTableMappingAttributes_El3NonSecureRwData, MemoryAttributeIndexDevice); + + constexpr void UnmapBootCodeImpl(u64 *l1, u64 *l2, u64 *l3, uintptr_t boot_code, size_t boot_code_size) { + /* Unmap the L3 entries corresponding to the boot code. */ + AMS_UNUSED(l1, l2); + InvalidateL3Entries(l3, boot_code, boot_code_size); + } + + constexpr void UnmapTzramImpl(u64 *l1, u64 *l2, u64 *l3) { + /* Unmap the L3 entries corresponding to tzram. */ + InvalidateL3Entries(l3, MemoryRegionPhysicalTzram.GetAddress(), MemoryRegionPhysicalTzram.GetSize()); + + /* Unmap the L2 entries corresponding to those L3 entries. */ + InvalidateL2Entries(l2, MemoryRegionPhysicalTzramL2.GetAddress(), MemoryRegionPhysicalTzramL2.GetSize()); + + /* Unmap the L1 entry corresponding to to those L2 entries. */ + InvalidateL1Entries(l1, MemoryRegionPhysical.GetAddress(), MemoryRegionPhysical.GetSize()); + } + + constexpr void MapSmcUserPageImpl(u64 *l3, uintptr_t address) { + /* Set the L3 entry. */ + SetL3BlockEntry(l3, MemoryRegionVirtualSmcUserPage.GetAddress(), address, MemoryRegionVirtualSmcUserPage.GetSize(), MappingAttributesEl3NonSecureRwData); + } + + constexpr void UnmapSmcUserPageImpl(u64 *l3) { + /* Unmap the L3 entry. */ + InvalidateL3Entries(l3, MemoryRegionVirtualSmcUserPage.GetAddress(), MemoryRegionVirtualSmcUserPage.GetSize()); + } + + constexpr void MapAtmosphereIramPageImpl(u64 *l3, uintptr_t address) { + /* Set the L3 entry. */ + SetL3BlockEntry(l3, MemoryRegionVirtualAtmosphereIramPage.GetAddress(), address, MemoryRegionVirtualAtmosphereIramPage.GetSize(), MappingAttributesEl3NonSecureDevice); + } + + constexpr void UnmapAtmosphereIramPageImpl(u64 *l3) { + /* Unmap the L3 entry. */ + InvalidateL3Entries(l3, MemoryRegionVirtualAtmosphereIramPage.GetAddress(), MemoryRegionVirtualAtmosphereIramPage.GetSize()); + } + + constexpr void MapAtmosphereUserPageImpl(u64 *l3, uintptr_t address) { + /* Set the L3 entry. */ + SetL3BlockEntry(l3, MemoryRegionVirtualAtmosphereUserPage.GetAddress(), address, MemoryRegionVirtualAtmosphereUserPage.GetSize(), MappingAttributesEl3NonSecureRwData); + } + + constexpr void UnmapAtmosphereUserPageImpl(u64 *l3) { + /* Unmap the L3 entry. */ + InvalidateL3Entries(l3, MemoryRegionVirtualAtmosphereUserPage.GetAddress(), MemoryRegionVirtualAtmosphereUserPage.GetSize()); + } + + constexpr void MapDramForMarikoProgramImpl(u64 *l1, u64 *l2, u64 *l3) { + /* Map the L1 entry corresponding to the mariko program dram entry. */ + AMS_UNUSED(l2, l3); + SetL1BlockEntry(l1, MemoryRegionDramForMarikoProgram.GetAddress(), MemoryRegionDramForMarikoProgram.GetAddress(), MemoryRegionDramForMarikoProgram.GetSize(), MappingAttributesEl3NonSecureRwData); + } + + void ClearLow(uintptr_t address, size_t size) { + /* Clear the low part. */ + util::ClearMemory(reinterpret_cast<void *>(address), size / 2); + } + + void ClearHigh(uintptr_t address, size_t size) { + /* Clear the high part. */ + util::ClearMemory(reinterpret_cast<void *>(address + size / 2), size / 2); + } + + } + + void ClearBootCodeHigh() { + ClearHigh(BootCodeAddress, BootCodeSize); + } + + void UnmapBootCode() { + /* Get the tables. */ + u64 * const l1 = MemoryRegionVirtualTzramL1PageTable.GetPointer<u64>(); + u64 * const l2_l3 = MemoryRegionVirtualTzramL2L3PageTable.GetPointer<u64>(); + + /* Clear the low boot code region; high was already cleared by a previous call. */ + ClearLow(BootCodeAddress, BootCodeSize); + + /* Unmap. */ + UnmapBootCodeImpl(l1, l2_l3, l2_l3, BootCodeAddress, BootCodeSize); + + /* Ensure the mappings are consistent. */ + secmon::EnsureMappingConsistency(); + } + + size_t GetPhysicalMemorySize() { + switch (smc::GetPhysicalMemorySize()) { + case pkg1::MemorySize_4GB: return 4_GB; + case pkg1::MemorySize_6GB: return 6_GB; + case pkg1::MemorySize_8GB: return 8_GB; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + bool IsPhysicalMemoryAddress(uintptr_t address) { + return (address - MemoryRegionDram.GetAddress()) < GetPhysicalMemorySize(); + } + + void UnmapTzram() { + /* Get the tables. */ + u64 * const l1 = MemoryRegionVirtualTzramL1PageTable.GetPointer<u64>(); + u64 * const l2_l3 = MemoryRegionVirtualTzramL2L3PageTable.GetPointer<u64>(); + + /* Unmap. */ + UnmapTzramImpl(l1, l2_l3, l2_l3); + + /* Ensure the mappings are consistent. */ + secmon::EnsureMappingConsistency(); + } + + uintptr_t MapSmcUserPage(uintptr_t address) { + if (g_smc_user_page_physical_address == 0) { + if (!IsPhysicalMemoryAddress(address)) { + return 0; + } + + if (!util::IsAligned(address, 4_KB)) { + return 0; + } + + g_smc_user_page_physical_address = address; + + u64 * const l2_l3 = MemoryRegionVirtualTzramL2L3PageTable.GetPointer<u64>(); + + MapSmcUserPageImpl(l2_l3, address); + + /* Ensure the mappings are consistent. */ + secmon::EnsureMappingConsistency(MemoryRegionVirtualSmcUserPage.GetAddress()); + } else { + AMS_ABORT_UNLESS(address == g_smc_user_page_physical_address); + } + + return MemoryRegionVirtualSmcUserPage.GetAddress(); + } + + void UnmapSmcUserPage() { + if (g_smc_user_page_physical_address == 0) { + return; + } + + /* Ensure that the page is no longer in cache. */ + hw::FlushDataCache(MemoryRegionVirtualSmcUserPage.GetPointer<void>(), MemoryRegionVirtualSmcUserPage.GetSize()); + hw::DataSynchronizationBarrierInnerShareable(); + + u64 * const l2_l3 = MemoryRegionVirtualTzramL2L3PageTable.GetPointer<u64>(); + + UnmapSmcUserPageImpl(l2_l3); + + /* Ensure the mappings are consistent. */ + secmon::EnsureMappingConsistency(MemoryRegionVirtualSmcUserPage.GetAddress()); + + g_smc_user_page_physical_address = 0; + } + + uintptr_t MapAtmosphereIramPage(uintptr_t address) { + /* Acquire the ams iram spinlock. */ + AcquireSpinLock(g_ams_iram_page_spin_lock); + auto lock_guard = SCOPE_GUARD { ReleaseSpinLock(g_ams_iram_page_spin_lock); }; + + /* Validate that the page is an IRAM page. */ + if (!MemoryRegionPhysicalIram.Contains(address, 1)) { + return 0; + } + + /* Validate that the page isn't a secure monitor debug page. */ + if (MemoryRegionPhysicalIramSecureMonitorDebug.Contains(address, 1)) { + return 0; + } + + /* Validate that the page is aligned. */ + if (!util::IsAligned(address, 4_KB)) { + return 0; + } + + /* Map the page. */ + g_ams_iram_page_physical_address = address; + + u64 * const l2_l3 = MemoryRegionVirtualTzramL2L3PageTable.GetPointer<u64>(); + + MapAtmosphereIramPageImpl(l2_l3, address); + + /* Ensure the mappings are consistent. */ + secmon::EnsureMappingConsistency(MemoryRegionVirtualAtmosphereIramPage.GetAddress()); + + /* Hold the lock. */ + lock_guard.Cancel(); + + return MemoryRegionVirtualAtmosphereIramPage.GetAddress(); + } + + void UnmapAtmosphereIramPage() { + /* Can't unmap if nothing's unmapped. */ + if (g_ams_iram_page_physical_address == 0) { + return; + } + + /* Ensure that the page is no longer in cache. */ + hw::FlushDataCache(MemoryRegionVirtualAtmosphereIramPage.GetPointer<void>(), MemoryRegionVirtualAtmosphereIramPage.GetSize()); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Unmap the page. */ + u64 * const l2_l3 = MemoryRegionVirtualTzramL2L3PageTable.GetPointer<u64>(); + + UnmapAtmosphereIramPageImpl(l2_l3); + + /* Ensure the mappings are consistent. */ + secmon::EnsureMappingConsistency(MemoryRegionVirtualAtmosphereIramPage.GetAddress()); + + /* Release the page. */ + g_ams_iram_page_physical_address = 0; + + ReleaseSpinLock(g_ams_iram_page_spin_lock); + } + + uintptr_t MapAtmosphereUserPage(uintptr_t address) { + /* Acquire the ams user spinlock. */ + AcquireSpinLock(g_ams_user_page_spin_lock); + auto lock_guard = SCOPE_GUARD { ReleaseSpinLock(g_ams_user_page_spin_lock); }; + + /* Validate that the page is a dram page. */ + if (!IsPhysicalMemoryAddress(address)) { + return 0; + } + + /* Validate that the page is aligned. */ + if (!util::IsAligned(address, 4_KB)) { + return 0; + } + + /* Map the page. */ + g_ams_user_page_physical_address = address; + + u64 * const l2_l3 = MemoryRegionVirtualTzramL2L3PageTable.GetPointer<u64>(); + + MapAtmosphereUserPageImpl(l2_l3, address); + + /* Ensure the mappings are consistent. */ + secmon::EnsureMappingConsistency(MemoryRegionVirtualAtmosphereUserPage.GetAddress()); + + /* Hold the lock. */ + lock_guard.Cancel(); + + return MemoryRegionVirtualAtmosphereUserPage.GetAddress(); + } + + void UnmapAtmosphereUserPage() { + /* Can't unmap if nothing's unmapped. */ + if (g_ams_user_page_physical_address == 0) { + return; + } + + /* Ensure that the page is no longer in cache. */ + hw::FlushDataCache(MemoryRegionVirtualAtmosphereUserPage.GetPointer<void>(), MemoryRegionVirtualAtmosphereUserPage.GetSize()); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Unmap the page. */ + u64 * const l2_l3 = MemoryRegionVirtualTzramL2L3PageTable.GetPointer<u64>(); + + UnmapAtmosphereUserPageImpl(l2_l3); + + /* Ensure the mappings are consistent. */ + secmon::EnsureMappingConsistency(MemoryRegionVirtualAtmosphereUserPage.GetAddress()); + + /* Release the page. */ + g_ams_user_page_physical_address = 0; + + ReleaseSpinLock(g_ams_user_page_spin_lock); + } + + void MapDramForMarikoProgram() { + /* Get the tables. */ + u64 * const l1 = MemoryRegionVirtualTzramL1PageTable.GetPointer<u64>(); + u64 * const l2_l3 = MemoryRegionVirtualTzramL2L3PageTable.GetPointer<u64>(); + + /* Map. */ + MapDramForMarikoProgramImpl(l1, l2_l3, l2_l3); + + /* Ensure the mappings are consistent. */ + secmon::EnsureMappingConsistency(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_map.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_map.hpp new file mode 100644 index 00000000..4ab65c6d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_map.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::secmon { + + bool IsPhysicalMemoryAddress(uintptr_t address); + size_t GetPhysicalMemorySize(); + + void UnmapTzram(); + + uintptr_t MapSmcUserPage(uintptr_t address); + void UnmapSmcUserPage(); + + uintptr_t MapAtmosphereIramPage(uintptr_t address); + void UnmapAtmosphereIramPage(); + + uintptr_t MapAtmosphereUserPage(uintptr_t address); + void UnmapAtmosphereUserPage(); + + void MapDramForMarikoProgram(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_mariko_fatal_error.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_mariko_fatal_error.cpp new file mode 100644 index 00000000..4be7523f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_mariko_fatal_error.cpp @@ -0,0 +1,89 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_cpu_context.hpp" +#include "secmon_map.hpp" +#include "secmon_page_mapper.hpp" +#include "secmon_mariko_fatal_error.hpp" +#include "smc/secmon_smc_power_management.hpp" + +namespace ams::secmon { + + namespace { + + constinit u8 g_fatal_error_mask = 0; + + } + + void HandleMarikoFatalErrorInterrupt() { + /* This interrupt handler doesn't return, so mark that we're at end of interrupt. */ + gic::SetEndOfInterrupt(MarikoFatalErrorInterruptId); + + /* Get the current core id. */ + const auto core_id = hw::GetCurrentCoreId(); + + /* Set that we received the fatal on the current core. */ + g_fatal_error_mask |= (1u << core_id); + hw::FlushDataCache(std::addressof(g_fatal_error_mask), sizeof(g_fatal_error_mask)); + hw::DataSynchronizationBarrier(); + + /* If not all cores have received the fatal, we need to trigger the interrupt on other cores. */ + if (g_fatal_error_mask != (1u << NumCores) - 1) { + /* Configure and send the interrupt to the next core. */ + const auto next_core = __builtin_ctz(~g_fatal_error_mask); + gic::SetSpiTargetCpu(MarikoFatalErrorInterruptId, (1u << next_core)); + gic::SetPending(MarikoFatalErrorInterruptId); + } + + /* If current core is not 3, kill ourselves. */ + if (core_id != NumCores - 1) { + smc::PowerOffCpu(); + } else { + /* Wait for all cores to kill themselves. */ + while (g_fatal_error_mask != (1u << NumCores) - 1) { + util::WaitMicroSeconds(100); + } + } + + /* Copy the fatal error context to mariko tzram. */ + { + /* Map the iram page. */ + constexpr uintptr_t FatalErrorPhysicalAddress = MemoryRegionPhysicalIramFatalErrorContext.GetAddress(); + AtmosphereIramPageMapper mapper(FatalErrorPhysicalAddress); + if (mapper.Map()) { + /* Copy the fatal error context. */ + void *dst = MemoryRegionVirtualTzramMarikoProgramFatalErrorContext.GetPointer<void>(); + const void *src = mapper.GetPointerTo(FatalErrorPhysicalAddress, sizeof(ams::impl::FatalErrorContext)); + std::memcpy(dst, src, sizeof(ams::impl::FatalErrorContext)); + } + } + + /* Map Dram for the mariko program. */ + MapDramForMarikoProgram(); + + AMS_SECMON_LOG("%s\n", "Jumping to Mariko Fatal."); + AMS_LOG_FLUSH(); + + /* Jump to the mariko fatal program. */ + reinterpret_cast<void (*)()>(secmon::MemoryRegionVirtualTzramMarikoProgram.GetAddress())(); + + /* The mariko fatal program never returns. */ + __builtin_unreachable(); + + AMS_INFINITE_LOOP(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_mariko_fatal_error.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_mariko_fatal_error.hpp new file mode 100644 index 00000000..2106e8d5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_mariko_fatal_error.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::secmon { + + constexpr inline int MarikoFatalErrorInterruptId = 198; + + NORETURN void HandleMarikoFatalErrorInterrupt(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_misc.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_misc.cpp new file mode 100644 index 00000000..13f18d16 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_misc.cpp @@ -0,0 +1,66 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_misc.hpp" + +namespace ams::secmon { + + namespace { + + constinit pkg1::BctParameters g_bct_params = {}; + constinit se::Sha256Hash g_package2_hash = {}; + + constinit u32 g_deprecated_boot_reason_value = {}; + constinit u8 g_deprecated_boot_reason_state = {}; + + } + + void SaveBootInfo(const pkg1::SecureMonitorParameters &secmon_params) { + /* Save the BCT parameters. */ + g_bct_params = secmon_params.bct_params; + + /* Save the deprecated boot reason. */ + g_deprecated_boot_reason_value = secmon_params.deprecated_boot_reason_value; + g_deprecated_boot_reason_state = secmon_params.deprecated_boot_reason_state; + } + + bool IsRecoveryBoot() { + return (g_bct_params.bootloader_attributes & pkg1::BootloaderAttribute_RecoveryBoot) != 0; + } + + u32 GetRestrictedSmcMask() { + return (g_bct_params.bootloader_attributes & pkg1::BootloaderAttribute_RestrictedSmcMask) >> pkg1::BootloaderAttribute_RestrictedSmcShift; + } + + bool IsJtagEnabled() { + util::BitPack32 dbg_auth; + HW_CPU_GET_DBGAUTHSTATUS_EL1(dbg_auth); + return dbg_auth.Get<hw::DbgAuthStatusEl1::Nsid>() == 3; + } + + void GetPackage2Hash(se::Sha256Hash *out) { + *out = g_package2_hash; + } + + void SetPackage2Hash(const se::Sha256Hash &hash) { + g_package2_hash = hash; + } + + u32 GetDeprecatedBootReason() { + return (static_cast<u32>(g_deprecated_boot_reason_state) << 24) | (g_deprecated_boot_reason_value & 0x00FFFFFF); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_misc.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_misc.hpp new file mode 100644 index 00000000..4004b7d2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_misc.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::secmon { + + void SaveBootInfo(const pkg1::SecureMonitorParameters &secmon_params); + + bool IsRecoveryBoot(); + + u32 GetRestrictedSmcMask(); + + bool IsJtagEnabled(); + + void GetPackage2Hash(se::Sha256Hash *out); + void SetPackage2Hash(const se::Sha256Hash &hash); + + u32 GetDeprecatedBootReason(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_page_mapper.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_page_mapper.cpp new file mode 100644 index 00000000..01416631 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_page_mapper.cpp @@ -0,0 +1,80 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_map.hpp" +#include "secmon_page_mapper.hpp" + +namespace ams::secmon { + + namespace impl { + + void *PageMapperImpl::GetPointerTo(uintptr_t phys, size_t size) const { + /* Ensure we stay within the page. */ + if (util::AlignDown(phys, 4_KB) != m_physical_address) { + return nullptr; + } + if (size != 0) { + if (util::AlignDown(phys + size - 1, 4_KB) != m_physical_address) { + return nullptr; + } + } + + return reinterpret_cast<void *>(phys + (m_virtual_address - m_physical_address)); + } + + bool PageMapperImpl::CopyToMapping(uintptr_t dst_phys, const void *src, size_t size) const { + void * const dst = this->GetPointerTo(dst_phys, size); + if (dst == nullptr) { + return false; + } + + std::memcpy(dst, src, size); + return true; + } + + bool PageMapperImpl::CopyFromMapping(void *dst, uintptr_t src_phys, size_t size) const { + const void * const src = this->GetPointerTo(src_phys, size); + if (src == nullptr) { + return false; + } + + std::memcpy(dst, src, size); + return true; + } + + } + + bool UserPageMapper::Map() { + return this->MapImpl<MapSmcUserPage>(); + } + + bool AtmosphereIramPageMapper::Map() { + return this->MapImpl<MapAtmosphereIramPage>(); + } + + bool AtmosphereUserPageMapper::Map() { + return this->MapImpl<MapAtmosphereUserPage>(); + } + + AtmosphereIramPageMapper::~AtmosphereIramPageMapper() { + this->UnmapImpl<UnmapAtmosphereIramPage>(); + } + + AtmosphereUserPageMapper::~AtmosphereUserPageMapper() { + this->UnmapImpl<UnmapAtmosphereUserPage>(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_page_mapper.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_page_mapper.hpp new file mode 100644 index 00000000..7373fa5e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_page_mapper.hpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::secmon { + + namespace impl { + + class PageMapperImpl { + private: + uintptr_t m_physical_address; + uintptr_t m_virtual_address; + public: + constexpr PageMapperImpl(uintptr_t phys) : m_physical_address(util::AlignDown(phys, 4_KB)), m_virtual_address() { /* ... */ } + + void *GetPointerTo(uintptr_t phys, size_t size) const; + + bool CopyToMapping(uintptr_t dst_phys, const void *src, size_t size) const; + bool CopyFromMapping(void *dst, uintptr_t src_phys, size_t size) const; + + ALWAYS_INLINE bool CopyToUser(uintptr_t dst_phys, const void *src, size_t size) const { return CopyToMapping(dst_phys, src, size); } + ALWAYS_INLINE bool CopyFromUser(void *dst, uintptr_t src_phys, size_t size) const { return CopyFromMapping(dst, src_phys, size); } + + template<auto F> + bool MapImpl() { + m_virtual_address = F(m_physical_address); + return m_virtual_address != 0; + } + + template<auto F> + void UnmapImpl() { + F(); + m_virtual_address = 0; + } + }; + + } + + class UserPageMapper : public impl::PageMapperImpl { + public: + constexpr UserPageMapper(uintptr_t phys) : PageMapperImpl(phys) { /* ... */ } + + bool Map(); + }; + + class AtmosphereIramPageMapper : public impl::PageMapperImpl { + public: + constexpr AtmosphereIramPageMapper(uintptr_t phys) : PageMapperImpl(phys) { /* ... */ } + ~AtmosphereIramPageMapper(); + + bool Map(); + }; + + class AtmosphereUserPageMapper : public impl::PageMapperImpl { + public: + constexpr AtmosphereUserPageMapper(uintptr_t phys) : PageMapperImpl(phys) { /* ... */ } + ~AtmosphereUserPageMapper(); + + bool Map(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_setup.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_setup.cpp new file mode 100644 index 00000000..6479fa43 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_setup.cpp @@ -0,0 +1,1289 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_setup.hpp" +#include "secmon_error.hpp" +#include "secmon_map.hpp" +#include "secmon_cpu_context.hpp" +#include "secmon_mariko_fatal_error.hpp" +#include "secmon_interrupt_handler.hpp" +#include "secmon_misc.hpp" +#include "smc/secmon_random_cache.hpp" +#include "smc/secmon_smc_power_management.hpp" +#include "smc/secmon_smc_se_lock.hpp" + +namespace ams::secmon { + + namespace { + + constexpr inline const uintptr_t TIMER = secmon::MemoryRegionVirtualDeviceTimer.GetAddress(); + constexpr inline const uintptr_t SYSTEM = secmon::MemoryRegionVirtualDeviceSystem.GetAddress(); + constexpr inline const uintptr_t APB_MISC = secmon::MemoryRegionVirtualDeviceApbMisc.GetAddress(); + constexpr inline const uintptr_t FLOW_CTLR = secmon::MemoryRegionVirtualDeviceFlowController.GetAddress(); + constexpr inline const uintptr_t PMC = secmon::MemoryRegionVirtualDevicePmc.GetAddress(); + constexpr inline const uintptr_t MC = secmon::MemoryRegionVirtualDeviceMemoryController.GetAddress(); + constexpr inline const uintptr_t EVP = secmon::MemoryRegionVirtualDeviceExceptionVectors.GetAddress(); + constexpr inline const uintptr_t CLK_RST = secmon::MemoryRegionVirtualDeviceClkRst.GetAddress(); + + alignas(8) constinit u8 g_se_aes_key_slot_test_vector[se::AesBlockSize] = {}; + + struct Carveout { + uintptr_t address; + size_t size; + }; + + constinit Carveout g_kernel_carveouts[KernelCarveoutCount] = { + { secmon::MemoryRegionDramDefaultKernelCarveout.GetAddress(), secmon::MemoryRegionDramDefaultKernelCarveout.GetSize(), }, + { 0, 0, }, + }; + + constinit bool g_is_cold_boot = true; + + constinit se::StickyBits ExpectedSeStickyBits = { + .se_security = (1 << 0), /* SE_HARD_SETTING */ + .tzram_security = 0, + .crypto_security_perkey = (1 << pkg1::AesKeySlot_UserEnd) - 1, + .crypto_keytable_access = { + (0 << 7) | (1 << 6) | (1 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (1 << 1) | (0 << 0), /* 0: User keyslot. KEY. KEYUSE, UIVUPDATE, OIVUPDATE, KEYUPDATE enabled. UIVREAD, OIVREAD, KEYREAD disabled. */ + (0 << 7) | (1 << 6) | (1 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (1 << 1) | (0 << 0), /* 1: User keyslot. KEY. KEYUSE, UIVUPDATE, OIVUPDATE, KEYUPDATE enabled. UIVREAD, OIVREAD, KEYREAD disabled. */ + (0 << 7) | (1 << 6) | (1 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (1 << 1) | (0 << 0), /* 2: User keyslot. KEY. KEYUSE, UIVUPDATE, OIVUPDATE, KEYUPDATE enabled. UIVREAD, OIVREAD, KEYREAD disabled. */ + (0 << 7) | (1 << 6) | (1 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (1 << 1) | (0 << 0), /* 3: User keyslot. KEY. KEYUSE, UIVUPDATE, OIVUPDATE, KEYUPDATE enabled. UIVREAD, OIVREAD, KEYREAD disabled. */ + (0 << 7) | (1 << 6) | (1 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (1 << 1) | (0 << 0), /* 4: User keyslot. KEY. KEYUSE, UIVUPDATE, OIVUPDATE, KEYUPDATE enabled. UIVREAD, OIVREAD, KEYREAD disabled. */ + (0 << 7) | (1 << 6) | (1 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (1 << 1) | (0 << 0), /* 5: User keyslot. KEY. KEYUSE, UIVUPDATE, OIVUPDATE, KEYUPDATE enabled. UIVREAD, OIVREAD, KEYREAD disabled. */ + (1 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | (0 << 0), /* 6: Unused keyslot. KEK. KEYUSE, UIVUPDATE, UIVREAD, OIVUPDATE, OIVREAD, KEYUPDATE, KEYREAD disabled. */ + (1 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | (0 << 0), /* 7: Unused keyslot. KEK. KEYUSE, UIVUPDATE, UIVREAD, OIVUPDATE, OIVREAD, KEYUPDATE, KEYREAD disabled. */ + (0 << 7) | (0 << 6) | (1 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (1 << 1) | (0 << 0), /* 8: Temp keyslot. KEY. UIVUPDATE, OIVUPDATE, KEYUPDATE enabled. KEYUSE, UIVREAD, OIVREAD, KEYREAD disabled. */ + (0 << 7) | (0 << 6) | (1 << 5) | (0 << 4) | (1 << 3) | (0 << 2) | (1 << 1) | (0 << 0), /* 9: SmcTemp keyslot. KEY. UIVUPDATE, OIVUPDATE, KEYUPDATE enabled. KEYUSE, UIVREAD, OIVREAD, KEYREAD disabled. */ + (1 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | (0 << 0), /* 10: Wrap1 keyslot. KEK. KEYUSE, UIVUPDATE, UIVREAD, OIVUPDATE, OIVREAD, KEYUPDATE, KEYREAD disabled. */ + (0 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | (0 << 0), /* 11: Wrap2 keyslot. KEY. KEYUSE, UIVUPDATE, UIVREAD, OIVUPDATE, OIVREAD, KEYUPDATE, KEYREAD disabled. */ + (1 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | (0 << 0), /* 12: DMaster keyslot. KEK. KEYUSE, UIVUPDATE, UIVREAD, OIVUPDATE, OIVREAD, KEYUPDATE, KEYREAD disabled. */ + (1 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | (0 << 0), /* 13: Master keyslot. KEK. KEYUSE, UIVUPDATE, UIVREAD, OIVUPDATE, OIVREAD, KEYUPDATE, KEYREAD disabled. */ + (1 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | (0 << 0), /* 14: Unused keyslot. KEK. KEYUSE, UIVUPDATE, UIVREAD, OIVUPDATE, OIVREAD, KEYUPDATE, KEYREAD disabled. */ + (1 << 7) | (0 << 6) | (0 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (0 << 1) | (0 << 0), /* 13: Device keyslot. KEK. KEYUSE, UIVUPDATE, UIVREAD, OIVUPDATE, OIVREAD, KEYUPDATE, KEYREAD disabled. */ + }, + .rsa_security_perkey = 0, + .rsa_keytable_access = { + (0 << 2) | (1 << 1) | (0 << 0), /* KEYUSE/KEYREAD disabled, KEYUPDATE enabled. */ + (0 << 2) | (1 << 1) | (0 << 0), /* KEYUSE/KEYREAD disabled, KEYUPDATE enabled. */ + }, + }; + + void InitializeConfigurationContext() { + /* Get the global context. */ + auto &ctx = ::ams::secmon::impl::GetConfigurationContext(); + + /* Clear the context to zero. */ + std::memset(std::addressof(ctx), 0, sizeof(ctx)); + + /* If the storage context is valid, we want to copy it to the global context. */ + if (const auto &storage_ctx = *MemoryRegionPhysicalDramMonitorConfiguration.GetPointer<const SecureMonitorStorageConfiguration>(); storage_ctx.IsValid()) { + ctx.secmon_cfg.CopyFrom(storage_ctx); + ctx.emummc_cfg = storage_ctx.emummc_cfg; + } else { + /* If we don't have a valid storage context, we can just use the default one. */ + ctx.secmon_cfg = DefaultSecureMonitorConfiguration; + } + + /* Cache the fuse info for quick access. */ + ctx.secmon_cfg.SetFuseInfo(); + } + + void GenerateSecurityEngineAesKeySlotTestVector(void *dst, size_t size) { + /* Clear the output. */ + AMS_ABORT_UNLESS(size == se::AesBlockSize); + std::memset(dst, 0, se::AesBlockSize); + + /* Ensure output is seen as cleared by the se. */ + hw::FlushDataCache(dst, se::AesBlockSize); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Declare a block. */ + alignas(8) u8 empty_block[se::AesBlockSize]; + + /* Iteratively transform an empty block. */ + #define TRANSFORM_WITH_KEY(key) \ + __builtin_memset(empty_block, 0, sizeof(empty_block)); \ + se::SetEncryptedAesKey256(pkg1::AesKeySlot_Temporary, key, empty_block, sizeof(empty_block)); \ + se::DecryptAes128(dst, se::AesBlockSize, pkg1::AesKeySlot_Temporary, dst, se::AesBlockSize) + + TRANSFORM_WITH_KEY(pkg1::AesKeySlot_RandomForUserWrap); + TRANSFORM_WITH_KEY(pkg1::AesKeySlot_RandomForKeyStorageWrap); + TRANSFORM_WITH_KEY(pkg1::AesKeySlot_Master); + TRANSFORM_WITH_KEY(pkg1::AesKeySlot_DeviceMaster); + TRANSFORM_WITH_KEY(pkg1::AesKeySlot_Device); + + TRANSFORM_WITH_KEY(pkg1::AesKeySlot_RandomForUserWrap); + TRANSFORM_WITH_KEY(pkg1::AesKeySlot_RandomForKeyStorageWrap); + TRANSFORM_WITH_KEY(pkg1::AesKeySlot_Master); + TRANSFORM_WITH_KEY(pkg1::AesKeySlot_DeviceMaster); + TRANSFORM_WITH_KEY(pkg1::AesKeySlot_Device); + + /* Ensure output is seen correctly by the cpu. */ + hw::FlushDataCache(dst, se::AesBlockSize); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Clear the temporary key slot. */ + se::ClearAesKeySlot(pkg1::AesKeySlot_Temporary); + } + + void VerifySecurityEngineStickyBits() { + /* On mariko, an extra sticky bit is set. */ + if (GetSocType() == fuse::SocType_Mariko) { + ExpectedSeStickyBits.se_security |= (1 << 5); + } else /* if (GetSocType() == fuse::SocType_Erista) */ { + /* Erista does not support DST_KEYTABLE_ONLY, and so all keys will have the bit clear. */ + for (size_t i = 0; i < util::size(ExpectedSeStickyBits.crypto_keytable_access); ++i) { + ExpectedSeStickyBits.crypto_keytable_access[i] &= ~(1 << 7); + } + } + + if (!se::ValidateStickyBits(ExpectedSeStickyBits)) { + SetError(pkg1::ErrorInfo_InvalidSecurityEngineStickyBits); + AMS_ABORT("Invalid sticky bits"); + } + } + + void VerifySecurityEngineAesKeySlotTestVector() { + alignas(8) u8 test_vector[se::AesBlockSize]; + GenerateSecurityEngineAesKeySlotTestVector(test_vector, sizeof(test_vector)); + + AMS_ABORT_UNLESS(crypto::IsSameBytes(g_se_aes_key_slot_test_vector, test_vector, se::AesBlockSize)); + } + + void ClearAesKeySlots() { + /* Clear all non-secure monitor keys. */ + for (int i = 0; i < pkg1::AesKeySlot_SecmonStart; ++i) { + se::ClearAesKeySlot(i); + } + + /* Clear the secure-monitor temporary keys. */ + se::ClearAesKeySlot(pkg1::AesKeySlot_Temporary); + se::ClearAesKeySlot(pkg1::AesKeySlot_Smc); + } + + void ClearRsaKeySlots() { + /* Clear all rsa keyslots. */ + for (int i = 0; i < se::RsaKeySlotCount; ++i) { + se::ClearRsaKeySlot(i); + } + } + + void SetupKernelCarveouts() { + #define MC_ENABLE_CLIENT_ACCESS(INDEX, WHICH) MC_REG_BITS_ENUM(CLIENT_ACCESS##INDEX##_##WHICH, ENABLE) + + constexpr u32 ClientAccess0 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(0, PTCR), + MC_ENABLE_CLIENT_ACCESS(0, DISPLAY0A), + MC_ENABLE_CLIENT_ACCESS(0, DISPLAY0AB), + MC_ENABLE_CLIENT_ACCESS(0, DISPLAY0B), + MC_ENABLE_CLIENT_ACCESS(0, DISPLAY0BB), + MC_ENABLE_CLIENT_ACCESS(0, DISPLAY0C), + MC_ENABLE_CLIENT_ACCESS(0, DISPLAY0CB), + MC_ENABLE_CLIENT_ACCESS(0, AFIR), + MC_ENABLE_CLIENT_ACCESS(0, DISPLAYHC), + MC_ENABLE_CLIENT_ACCESS(0, DISPLAYHCB), + MC_ENABLE_CLIENT_ACCESS(0, HDAR), + MC_ENABLE_CLIENT_ACCESS(0, HOST1XDMAR), + MC_ENABLE_CLIENT_ACCESS(0, HOST1XR), + MC_ENABLE_CLIENT_ACCESS(0, NVENCSRD), + MC_ENABLE_CLIENT_ACCESS(0, PPCSAHBDMAR), + MC_ENABLE_CLIENT_ACCESS(0, PPCSAHBSLVR)); + + constexpr u32 ClientAccess1 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(1, MPCORER), + MC_ENABLE_CLIENT_ACCESS(1, NVENCSWR), + MC_ENABLE_CLIENT_ACCESS(1, AFIW), + MC_ENABLE_CLIENT_ACCESS(1, HDAW), + MC_ENABLE_CLIENT_ACCESS(1, HOST1XW), + MC_ENABLE_CLIENT_ACCESS(1, MPCOREW), + MC_ENABLE_CLIENT_ACCESS(1, PPCSAHBDMAW), + MC_ENABLE_CLIENT_ACCESS(1, PPCSAHBSLVW)); + + constexpr u32 ClientAccess2 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(2, XUSB_HOSTR), + MC_ENABLE_CLIENT_ACCESS(2, XUSB_HOSTW), + MC_ENABLE_CLIENT_ACCESS(2, XUSB_DEVR), + MC_ENABLE_CLIENT_ACCESS(2, XUSB_DEVW)); + + constexpr u32 ClientAccess2_100 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(2, XUSB_HOSTR), + MC_ENABLE_CLIENT_ACCESS(2, XUSB_HOSTW), + MC_ENABLE_CLIENT_ACCESS(2, XUSB_DEVR), + MC_ENABLE_CLIENT_ACCESS(2, XUSB_DEVW), + MC_ENABLE_CLIENT_ACCESS(2, TSECSRD), + MC_ENABLE_CLIENT_ACCESS(2, TSECSWR)); + + constexpr u32 ClientAccess3 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(3, SDMMCRA), + MC_ENABLE_CLIENT_ACCESS(3, SDMMCRAA), + MC_ENABLE_CLIENT_ACCESS(3, SDMMCRAB), + MC_ENABLE_CLIENT_ACCESS(3, SDMMCWA), + MC_ENABLE_CLIENT_ACCESS(3, SDMMCWAA), + MC_ENABLE_CLIENT_ACCESS(3, SDMMCWAB), + MC_ENABLE_CLIENT_ACCESS(3, VICSRD), + MC_ENABLE_CLIENT_ACCESS(3, VICSWR), + MC_ENABLE_CLIENT_ACCESS(3, DISPLAYD), + MC_ENABLE_CLIENT_ACCESS(3, APER), + MC_ENABLE_CLIENT_ACCESS(3, APEW), + MC_ENABLE_CLIENT_ACCESS(3, NVJPGSRD), + MC_ENABLE_CLIENT_ACCESS(3, NVJPGSWR)); + + constexpr u32 ClientAccess3_100 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(3, SDMMCRA), + MC_ENABLE_CLIENT_ACCESS(3, SDMMCRAA), + MC_ENABLE_CLIENT_ACCESS(3, SDMMCRAB), + MC_ENABLE_CLIENT_ACCESS(3, SDMMCWA), + MC_ENABLE_CLIENT_ACCESS(3, SDMMCWAA), + MC_ENABLE_CLIENT_ACCESS(3, SDMMCWAB), + MC_ENABLE_CLIENT_ACCESS(3, VICSRD), + MC_ENABLE_CLIENT_ACCESS(3, VICSWR), + MC_ENABLE_CLIENT_ACCESS(3, DISPLAYD), + MC_ENABLE_CLIENT_ACCESS(3, NVDECSRD), + MC_ENABLE_CLIENT_ACCESS(3, NVDECSWR), + MC_ENABLE_CLIENT_ACCESS(3, APER), + MC_ENABLE_CLIENT_ACCESS(3, APEW), + MC_ENABLE_CLIENT_ACCESS(3, NVJPGSRD), + MC_ENABLE_CLIENT_ACCESS(3, NVJPGSWR)); + + constexpr u32 ClientAccess4 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(4, SESRD), + MC_ENABLE_CLIENT_ACCESS(4, SESWR)); + + constexpr u32 ClientAccess4_800 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(4, SESRD), + MC_ENABLE_CLIENT_ACCESS(4, SESWR), + MC_ENABLE_CLIENT_ACCESS(4, TSECRDB), + MC_ENABLE_CLIENT_ACCESS(4, TSECWRB)); + + + constexpr u32 ClientAccess4_100 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(4, SESRD), + MC_ENABLE_CLIENT_ACCESS(4, SESWR)); + + #undef MC_ENABLE_CLIENT_ACCESS + + constexpr u32 ForceInternalAccess0 = reg::Encode(MC_REG_BITS_ENUM(CLIENT_ACCESS0_AVPCARM7R, ENABLE)); + constexpr u32 ForceInternalAccess0_100 = 0; + + constexpr u32 ForceInternalAccess1 = reg::Encode(MC_REG_BITS_ENUM(CLIENT_ACCESS1_AVPCARM7W, ENABLE)); + constexpr u32 ForceInternalAccess1_100 = 0; + + constexpr u32 CarveoutConfig = reg::Encode(MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_IS_WPR, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_FORCE_APERTURE_ID_MATCH, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ALLOW_APERTURE_ID_MISMATCH, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_RD_EN, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_WR_EN, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_SEND_CFG_TO_GPU, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS), + MC_REG_BITS_VALUE(SECURITY_CARVEOUT_CFG0_APERTURE_ID, 0), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL3, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL2, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL1, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL0, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL3, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL2, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL1, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL0, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ADDRESS_TYPE, ANY_ADDRESS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_LOCK_MODE, LOCKED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_PROTECT_MODE, TZ_SECURE)); + + constexpr u32 CarveoutConfig_100 = reg::Encode(MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_IS_WPR, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_FORCE_APERTURE_ID_MATCH, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ALLOW_APERTURE_ID_MISMATCH, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_RD_EN, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_WR_EN, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_SEND_CFG_TO_GPU, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS), + MC_REG_BITS_VALUE(SECURITY_CARVEOUT_CFG0_APERTURE_ID, 0), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL3, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL2, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL1, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL0, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL3, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL2, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL1, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL0, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ADDRESS_TYPE, ANY_ADDRESS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_LOCK_MODE, LOCKED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_PROTECT_MODE, TZ_SECURE)); + + const u32 target_fw = GetTargetFirmware(); + u32 client_access_2; + u32 client_access_3; + u32 client_access_4; + u32 carveout_config; + if (target_fw >= TargetFirmware_8_1_0) { + client_access_2 = ClientAccess2; + client_access_3 = ClientAccess3; + client_access_4 = ClientAccess4; + carveout_config = CarveoutConfig; + } else if (target_fw >= TargetFirmware_8_0_0) { + client_access_2 = ClientAccess2; + client_access_3 = ClientAccess3; + client_access_4 = ClientAccess4_800; + carveout_config = CarveoutConfig; + } else { + client_access_2 = ClientAccess2_100; + client_access_3 = ClientAccess3_100; + client_access_4 = ClientAccess4_100; + carveout_config = CarveoutConfig_100; + } + + /* Configure carveout 4. */ + reg::Write(MC + MC_SECURITY_CARVEOUT4_BOM, g_kernel_carveouts[0].address >> 0); + reg::Write(MC + MC_SECURITY_CARVEOUT4_BOM_HI, g_kernel_carveouts[0].address >> 32); + reg::Write(MC + MC_SECURITY_CARVEOUT4_SIZE_128KB, g_kernel_carveouts[0].size / 128_KB); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_ACCESS0, ClientAccess0); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_ACCESS1, ClientAccess1); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_ACCESS2, client_access_2); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_ACCESS3, client_access_3); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_ACCESS4, client_access_4); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS0, (target_fw >= TargetFirmware_4_0_0) ? ForceInternalAccess0 : ForceInternalAccess0_100); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS1, (target_fw >= TargetFirmware_4_0_0) ? ForceInternalAccess1 : ForceInternalAccess1_100); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS2, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS3, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS4, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CFG0, carveout_config); + + /* Configure carveout 5. */ + reg::Write(MC + MC_SECURITY_CARVEOUT5_BOM, g_kernel_carveouts[1].address >> 0); + reg::Write(MC + MC_SECURITY_CARVEOUT5_BOM_HI, g_kernel_carveouts[1].address >> 32); + reg::Write(MC + MC_SECURITY_CARVEOUT5_SIZE_128KB, g_kernel_carveouts[1].size / 128_KB); + reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_ACCESS0, ClientAccess0); + reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_ACCESS1, ClientAccess1); + reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_ACCESS2, client_access_2); + reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_ACCESS3, client_access_3); + reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_ACCESS4, client_access_4); + reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS0, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS1, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS2, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS3, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS4, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT5_CFG0, carveout_config); + } + + void ConfigureSlaveSecurity() { + u32 reg0, reg1, reg2; + if (GetTargetFirmware() > TargetFirmware_1_0_0) { + reg0 = reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(0, SATA_AUX, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(0, DTV, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(0, QSPI, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(0, SE, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(0, SATA, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(0, LA, ENABLE)); + + reg1 = reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(1, SPI1, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(1, SPI2, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(1, SPI3, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(1, SPI5, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(1, SPI6, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(1, I2C6, ENABLE)); + + reg2 = reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(2, SDMMC3, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(2, DDS, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(2, DP2, ENABLE)); + + const auto hw_type = GetHardwareType(); + + /* Switch Lite can't use HDMI, so set CEC to secure on hoag. */ + if (hw_type == fuse::HardwareType_Hoag) { + reg0 |= reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(0, CEC, ENABLE)); + } + + /* Icosa, Iowa, and Aula all set I2C4 to be secure. */ + if (hw_type == fuse::HardwareType_Icosa && hw_type == fuse::HardwareType_Iowa && hw_type == fuse::HardwareType_Aula) { + reg1 |= reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(1, I2C4, ENABLE)); + + } + + /* Hoag additionally sets UART_B to secure. */ + if (hw_type == fuse::HardwareType_Hoag) { + reg1 |= reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(1, UART_B, ENABLE)); + } + + /* Copper and Calcio lack a lot of hardware, so set the corresponding registers to secure for them. */ + if (hw_type == fuse::HardwareType_Calcio || hw_type == fuse::HardwareType_Copper) { + reg1 |= reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(1, UART_B, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(1, UART_C, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(1, SPI4, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(1, I2C2, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(1, I2C3, ENABLE)); + + /* Copper/Calcio have no gamecard reader, and thus set SDMMC2 as secure. */ + reg2 |= reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(2, SDMMC2, ENABLE)); + } + + /* Mariko hardware types (not Icosa or Copper) additionally set mariko-only mmio (SE2, PKA1, FEK) as secure. */ + if (hw_type != fuse::HardwareType_Icosa && hw_type != fuse::HardwareType_Copper) { + reg2 |= reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(2, SE2, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(2, PKA1, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(2, FEK, ENABLE)); + } + } else { + reg0 = reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(0, SATA_AUX, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(0, DTV, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(0, QSPI, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(0, SATA, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(0, LA, ENABLE)); + + reg1 = reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(1, SPI1, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(1, SPI2, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(1, SPI3, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(1, SPI5, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(1, SPI6, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(1, I2C6, ENABLE)); + + reg2 = reg::Encode(SLAVE_SECURITY_REG_BITS_ENUM(2, DDS, ENABLE), + REG_BITS_VALUE(5, 1, 1), /* Note: Bit 5 is not documented in TRM. */ + REG_BITS_VALUE(4, 1, 1)); /* Note: Bit 4 is not documented in TRM. */ + } + + reg::Write(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG0_0, reg0); + reg::Write(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG1_0, reg1); + reg::Write(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG2_0, reg2); + } + + void SetupSecureRegisters() { + /* Configure timers 5-8 and watchdog timers 0-3 as secure. */ + reg::Write(TIMER + TIMER_SHARED_TIMER_SECURE_CFG, TIMER_REG_BITS_ENUM(SHARED_TIMER_SECURE_CFG_TMR5, ENABLE), + TIMER_REG_BITS_ENUM(SHARED_TIMER_SECURE_CFG_TMR6, ENABLE), + TIMER_REG_BITS_ENUM(SHARED_TIMER_SECURE_CFG_TMR7, ENABLE), + TIMER_REG_BITS_ENUM(SHARED_TIMER_SECURE_CFG_TMR8, ENABLE), + TIMER_REG_BITS_ENUM(SHARED_TIMER_SECURE_CFG_WDT0, ENABLE), + TIMER_REG_BITS_ENUM(SHARED_TIMER_SECURE_CFG_WDT1, ENABLE), + TIMER_REG_BITS_ENUM(SHARED_TIMER_SECURE_CFG_WDT2, ENABLE), + TIMER_REG_BITS_ENUM(SHARED_TIMER_SECURE_CFG_WDT3, ENABLE)); + + /* Lock cluster switching, to prevent usage of the A53 cores. */ + reg::Write(FLOW_CTLR + FLOW_CTLR_BPMP_CLUSTER_CONTROL, FLOW_REG_BITS_ENUM(BPMP_CLUSTER_CONTROL_ACTIVE_CLUSTER_LOCK, ENABLE), + FLOW_REG_BITS_ENUM(BPMP_CLUSTER_CONTROL_CLUSTER_SWITCH_ENABLE, DISABLE), + FLOW_REG_BITS_ENUM(BPMP_CLUSTER_CONTROL_ACTIVE_CLUSTER, FAST)); + + /* Enable flow controller debug qualifier for legacy FIQs. */ + reg::Write(FLOW_CTLR + FLOW_CTLR_FLOW_DBG_QUAL, FLOW_REG_BITS_ENUM(FLOW_DBG_QUAL_FIQ2CCPLEX_ENABLE, ENABLE)); + + /* Configure the PMC to disable deep power-down. */ + reg::Write(PMC + APBDEV_PMC_DPD_ENABLE, PMC_REG_BITS_ENUM(DPD_ENABLE_TSC_MULT_EN, DISABLE), + PMC_REG_BITS_ENUM(DPD_ENABLE_ON, DISABLE)); + + /* Configure the video protect region. */ + reg::Write(MC + MC_VIDEO_PROTECT_GPU_OVERRIDE_0, 1); + reg::Write(MC + MC_VIDEO_PROTECT_GPU_OVERRIDE_1, 0); + reg::Write(MC + MC_VIDEO_PROTECT_BOM, 0); + reg::Write(MC + MC_VIDEO_PROTECT_SIZE_MB, 0); + reg::Write(MC + MC_VIDEO_PROTECT_REG_CTRL, MC_REG_BITS_ENUM(VIDEO_PROTECT_REG_CTRL_VIDEO_PROTECT_ALLOW_TZ_WRITE, DISABLED), + MC_REG_BITS_ENUM(VIDEO_PROTECT_REG_CTRL_VIDEO_PROTECT_WRITE_ACCESS, DISABLED)); + + /* Configure the SEC carveout. */ + reg::Write(MC + MC_SEC_CARVEOUT_BOM, 0); + reg::Write(MC + MC_SEC_CARVEOUT_SIZE_MB, 0); + reg::Write(MC + MC_SEC_CARVEOUT_REG_CTRL, MC_REG_BITS_ENUM(SEC_CARVEOUT_REG_CTRL_SEC_CARVEOUT_WRITE_ACCESS, DISABLED)); + + /* Configure the MTS carveout. */ + reg::Write(MC + MC_MTS_CARVEOUT_BOM, 0); + reg::Write(MC + MC_MTS_CARVEOUT_SIZE_MB, 0); + reg::Write(MC + MC_MTS_CARVEOUT_ADR_HI, 0); + reg::Write(MC + MC_MTS_CARVEOUT_REG_CTRL, MC_REG_BITS_ENUM(MTS_CARVEOUT_REG_CTRL_MTS_CARVEOUT_WRITE_ACCESS, DISABLED)); + + /* Configure the security carveout. */ + reg::Write(MC + MC_SECURITY_CFG0, MC_REG_BITS_VALUE(SECURITY_CFG0_SECURITY_BOM, 0)); + reg::Write(MC + MC_SECURITY_CFG1, MC_REG_BITS_VALUE(SECURITY_CFG1_SECURITY_SIZE, 0)); + reg::Write(MC + MC_SECURITY_CFG3, MC_REG_BITS_VALUE(SECURITY_CFG3_SECURITY_BOM_HI, 3)); + + /* Configure security carveout 1. */ + reg::Write(MC + MC_SECURITY_CARVEOUT1_BOM, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT1_BOM_HI, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT1_SIZE_128KB, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_ACCESS0, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_ACCESS1, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_ACCESS2, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_ACCESS3, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_ACCESS4, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS0, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS1, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS2, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS3, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS4, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT1_CFG0, MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_IS_WPR, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_FORCE_APERTURE_ID_MATCH, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ALLOW_APERTURE_ID_MISMATCH, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_RD_EN, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_WR_EN, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_SEND_CFG_TO_GPU, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS), + MC_REG_BITS_VALUE(SECURITY_CARVEOUT_CFG0_APERTURE_ID, 0), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL3, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL2, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL1, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL0, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL3, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL2, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL1, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL0, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ADDRESS_TYPE, UNTRANSLATED_ONLY), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_LOCK_MODE, LOCKED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_PROTECT_MODE, LOCKBIT_SECURE)); + + /* Security carveout 2 will be configured later by SetupGpuCarveout, after magic values are written to configure gpu/tsec. */ + + /* Configure carveout 3. */ + reg::Write(MC + MC_SECURITY_CARVEOUT3_BOM, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT3_BOM_HI, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT3_SIZE_128KB, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_ACCESS0, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_ACCESS1, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_ACCESS2, MC_REG_BITS_ENUM (CLIENT_ACCESS2_GPUSRD, ENABLE), + MC_REG_BITS_ENUM (CLIENT_ACCESS2_GPUSWR, ENABLE)); + reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_ACCESS3, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_ACCESS4, MC_REG_BITS_ENUM (CLIENT_ACCESS4_GPUSRD2, ENABLE), + MC_REG_BITS_ENUM (CLIENT_ACCESS4_GPUSWR2, ENABLE)); + reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS0, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS1, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS2, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS3, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS4, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT3_CFG0, MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_IS_WPR, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_FORCE_APERTURE_ID_MATCH, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ALLOW_APERTURE_ID_MISMATCH, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_RD_EN, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_WR_EN, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_SEND_CFG_TO_GPU, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS), + MC_REG_BITS_VALUE(SECURITY_CARVEOUT_CFG0_APERTURE_ID, 3), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL3, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL2, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL1, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL0, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL3, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL2, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL1, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL0, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ADDRESS_TYPE, UNTRANSLATED_ONLY), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_LOCK_MODE, LOCKED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_PROTECT_MODE, LOCKBIT_SECURE)); + + /* If we're cold-booting and on 1.0.0, alter the default carveout size. */ + if (g_is_cold_boot && GetTargetFirmware() <= TargetFirmware_1_0_0) { + g_kernel_carveouts[0].size = 200 * 128_KB; + } + + /* NOTE: Here Nintendo configures the two kernel carveouts; we will do this later, to allow fusee to continue using AVP_CACHE. */ + /* SetupKernelCarveouts(); */ + + /* Configure slave register security. */ + ConfigureSlaveSecurity(); + } + + void SetupSmmu() { + /* Turn on SMMU translation for all devices. */ + reg::Write(MC + MC_SMMU_TRANSLATION_ENABLE_0, ~0u); + reg::Write(MC + MC_SMMU_TRANSLATION_ENABLE_1, ~0u); + reg::Write(MC + MC_SMMU_TRANSLATION_ENABLE_2, ~0u); + reg::Write(MC + MC_SMMU_TRANSLATION_ENABLE_3, ~0u); + reg::Write(MC + MC_SMMU_TRANSLATION_ENABLE_4, ~0u); + + /* On modern firmware, configure ASIDs 1-3 as secure, and all others as non-secure. */ + if (GetTargetFirmware() >= TargetFirmware_4_0_0) { + reg::Write(MC + MC_SMMU_ASID_SECURITY, MC_REG_BITS_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_1, SECURE), + MC_REG_BITS_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_2, SECURE), + MC_REG_BITS_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_3, SECURE)); + } else { + /* Legacy firmware accesses the MC directly, though, and so correspondingly we must allow ASIDs to be edited by non-secure world. */ + reg::Write(MC + MC_SMMU_ASID_SECURITY, 0); + } + + reg::Write(MC + MC_SMMU_ASID_SECURITY_1, 0); + reg::Write(MC + MC_SMMU_ASID_SECURITY_2, 0); + reg::Write(MC + MC_SMMU_ASID_SECURITY_3, 0); + reg::Write(MC + MC_SMMU_ASID_SECURITY_4, 0); + reg::Write(MC + MC_SMMU_ASID_SECURITY_5, 0); + reg::Write(MC + MC_SMMU_ASID_SECURITY_6, 0); + reg::Write(MC + MC_SMMU_ASID_SECURITY_7, 0); + + /* Initialize the PTB registers to zero .*/ + reg::Write(MC + MC_SMMU_PTB_ASID, 0); + reg::Write(MC + MC_SMMU_PTB_DATA, 0); + + /* Configure the TLB and PTC, then read TLB_CONFIG to ensure configuration takes. */ + reg::Write(MC + MC_SMMU_TLB_CONFIG, MC_REG_BITS_ENUM (SMMU_TLB_CONFIG_TLB_HIT_UNDER_MISS, ENABLE), + MC_REG_BITS_ENUM (SMMU_TLB_CONFIG_TLB_ROUND_ROBIN_ARBITRATION, ENABLE), + MC_REG_BITS_VALUE(SMMU_TLB_CONFIG_TLB_ACTIVE_LINES, 0x30)); + + reg::Write(MC + MC_SMMU_PTC_CONFIG, MC_REG_BITS_ENUM (SMMU_PTC_CONFIG_PTC_CACHE_ENABLE, ENABLE), + MC_REG_BITS_VALUE(SMMU_PTC_CONFIG_PTC_REQ_LIMIT, 8), + MC_REG_BITS_VALUE(SMMU_PTC_CONFIG_PTC_INDEX_MAP, 0x3F)); + + reg::Read (MC + MC_SMMU_TLB_CONFIG); + + /* Flush the entire page table cache, and read TLB_CONFIG to ensure the flush takes. */ + reg::Write(MC + MC_SMMU_PTC_FLUSH_0, 0); + reg::Read (MC + MC_SMMU_TLB_CONFIG); + + /* Flush the entire translation lookaside buffer, and read TLB_CONFIG to ensure the flush takes. */ + reg::Write(MC + MC_SMMU_TLB_FLUSH, 0); + reg::Read (MC + MC_SMMU_TLB_CONFIG); + + /* Enable the SMMU, and read TLB_CONFIG to ensure the enable takes. */ + reg::Write(MC + MC_SMMU_CONFIG, MC_REG_BITS_ENUM (SMMU_CONFIG_SMMU_ENABLE, ENABLE)); + reg::Read (MC + MC_SMMU_TLB_CONFIG); + } + + void SetupSecureEl2AndEl1SystemRegisters() { + /* Setup actlr_el2 and actlr_el3. */ + { + util::BitPack32 actlr = {}; + + actlr.Set<hw::ActlrCortexA57::Cpuactlr>(1); /* Enable access to cpuactlr from lower EL. */ + actlr.Set<hw::ActlrCortexA57::Cpuectlr>(1); /* Enable access to cpuectlr from lower EL. */ + actlr.Set<hw::ActlrCortexA57::L2ctlr>(1); /* Enable access to l2ctlr from lower EL. */ + actlr.Set<hw::ActlrCortexA57::L2actlr>(1); /* Enable access to l2actlr from lower EL. */ + actlr.Set<hw::ActlrCortexA57::L2ectlr>(1); /* Enable access to l2ectlr from lower EL. */ + + HW_CPU_SET_ACTLR_EL3(actlr); + HW_CPU_SET_ACTLR_EL2(actlr); + } + + /* Setup hcr_el2. */ + { + util::BitPack64 hcr = {}; + + hcr.Set<hw::HcrEl2::Rw>(1); /* EL1 is aarch64 mode. */ + + HW_CPU_SET_HCR_EL2(hcr); + } + + /* Configure all domain access permissions as manager. */ + HW_CPU_SET_DACR32_EL2(~0u); + + /* Setup sctlr_el1. */ + { + util::BitPack64 sctlr = { hw::SctlrEl1::Res1 }; + + sctlr.Set<hw::SctlrEl1::M>(0); /* Globally disable the MMU. */ + sctlr.Set<hw::SctlrEl1::A>(0); /* Disable alignment fault checking. */ + sctlr.Set<hw::SctlrEl1::C>(0); /* Globally disable the data and unified caches. */ + sctlr.Set<hw::SctlrEl1::Sa>(1); /* Enable stack alignment checking. */ + sctlr.Set<hw::SctlrEl1::Sa0>(1); /* Enable el0 stack alignment checking. */ + sctlr.Set<hw::SctlrEl1::Cp15BEn>(1); /* Enable cp15 barrier operations. */ + sctlr.Set<hw::SctlrEl1::Thee>(0); /* Disable ThumbEE. */ + sctlr.Set<hw::SctlrEl1::Itd>(0); /* Enable itd instructions. */ + sctlr.Set<hw::SctlrEl1::Sed>(0); /* Enable setend instruction. */ + sctlr.Set<hw::SctlrEl1::Uma>(0); /* Disable el0 interrupt mask access. */ + sctlr.Set<hw::SctlrEl1::I>(0); /* Globally disable the instruction cache. */ + sctlr.Set<hw::SctlrEl1::Dze>(0); /* Disable el0 access to dc zva instruction. */ + sctlr.Set<hw::SctlrEl1::Ntwi>(1); /* wfi instructions in el0 trap. */ + sctlr.Set<hw::SctlrEl1::Ntwe>(1); /* wfe instructions in el0 trap. */ + sctlr.Set<hw::SctlrEl1::Wxn>(0); /* Do not force writable pages to be ExecuteNever. */ + sctlr.Set<hw::SctlrEl1::E0e>(0); /* Data accesses in el0 are little endian. */ + sctlr.Set<hw::SctlrEl1::Ee>(0); /* Exceptions should be little endian. */ + sctlr.Set<hw::SctlrEl1::Uci>(0); /* Disable el0 access to dc cvau, dc civac, dc cvac, ic ivau. */ + + HW_CPU_SET_SCTLR_EL1(sctlr); + } + + /* Setup sctlr_el2. */ + { + util::BitPack64 sctlr = { hw::SctlrEl2::Res1 }; + + sctlr.Set<hw::SctlrEl2::M>(0); /* Globally disable the MMU. */ + sctlr.Set<hw::SctlrEl2::A>(0); /* Disable alignment fault checking. */ + sctlr.Set<hw::SctlrEl2::C>(0); /* Globally disable the data and unified caches. */ + sctlr.Set<hw::SctlrEl2::Sa>(1); /* Enable stack alignment checking. */ + sctlr.Set<hw::SctlrEl2::I>(0); /* Globally disable the instruction cache. */ + sctlr.Set<hw::SctlrEl2::Wxn>(0); /* Do not force writable pages to be ExecuteNever. */ + sctlr.Set<hw::SctlrEl2::Ee>(0); /* Exceptions should be little endian. */ + + HW_CPU_SET_SCTLR_EL2(sctlr); + } + + /* Ensure instruction consistency. */ + hw::InstructionSynchronizationBarrier(); + } + + void SetupNonSecureSystemRegisters(u32 tsc_frequency) { + /* Set cntfrq_el0. */ + HW_CPU_SET_CNTFRQ_EL0(tsc_frequency); + + /* Set cnthctl_el2. */ + { + util::BitPack32 cnthctl = {}; + + cnthctl.Set<hw::CnthctlEl2::El1PctEn>(1); /* Do not trap accesses to cntpct_el0. */ + cnthctl.Set<hw::CnthctlEl2::El1PcEn>(1); /* Do not trap accesses to cntp_ctl_el0, cntp_cval_el0, and cntp_tval_el0. */ + cnthctl.Set<hw::CnthctlEl2::EvntEn>(0); /* Disable the event stream. */ + cnthctl.Set<hw::CnthctlEl2::EvntDir>(0); /* Trigger events on 0 -> 1 transition. */ + cnthctl.Set<hw::CnthctlEl2::EvntI>(0); /* Select bit0 of cntpct_el0 as the event stream trigger. */ + + HW_CPU_SET_CNTHCTL_EL2(cnthctl); + } + + /* Ensure instruction consistency. */ + hw::InstructionSynchronizationBarrier(); + } + + void SetupGpuCarveout() { + /* Configure carveout 2. */ + reg::Write(MC + MC_SECURITY_CARVEOUT2_BOM, static_cast<u32>(MemoryRegionDramGpuCarveout.GetAddress() >> 0)); + reg::Write(MC + MC_SECURITY_CARVEOUT2_BOM_HI, static_cast<u32>(MemoryRegionDramGpuCarveout.GetAddress() >> BITSIZEOF(u32))); + reg::Write(MC + MC_SECURITY_CARVEOUT2_SIZE_128KB, MemoryRegionDramGpuCarveout.GetSize() / 128_KB); + reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_ACCESS0, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_ACCESS1, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_ACCESS2, MC_REG_BITS_ENUM (CLIENT_ACCESS2_GPUSRD, ENABLE), + MC_REG_BITS_ENUM (CLIENT_ACCESS2_GPUSWR, ENABLE), + MC_REG_BITS_ENUM (CLIENT_ACCESS2_TSECSRD, ENABLE)); + reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_ACCESS3, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_ACCESS4, MC_REG_BITS_ENUM (CLIENT_ACCESS4_GPUSRD2, ENABLE), + MC_REG_BITS_ENUM (CLIENT_ACCESS4_GPUSWR2, ENABLE)); + reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS0, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS1, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS2, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS3, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS4, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT2_CFG0, MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_IS_WPR, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_FORCE_APERTURE_ID_MATCH, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ALLOW_APERTURE_ID_MISMATCH, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_RD_EN, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_WR_EN, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_SEND_CFG_TO_GPU, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS), + MC_REG_BITS_VALUE(SECURITY_CARVEOUT_CFG0_APERTURE_ID, 2), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL3, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL2, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL1, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL0, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL3, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL2, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL1, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL0, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ADDRESS_TYPE, UNTRANSLATED_ONLY), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_LOCK_MODE, LOCKED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_PROTECT_MODE, LOCKBIT_SECURE)); + } + + void DisableArc() { + /* Configure IRAM top/bottom to point to memory ends (disabling redirection). */ + reg::Write(MC + MC_IRAM_BOM, MC_REG_BITS_VALUE(IRAM_BOM_IRAM_BOM, (~0u))); + reg::Write(MC + MC_IRAM_TOM, MC_REG_BITS_VALUE(IRAM_TOM_IRAM_TOM, ( 0u))); + + /* Lock the IRAM aperture. */ + reg::ReadWrite(MC + MC_IRAM_REG_CTRL, MC_REG_BITS_ENUM(IRAM_REG_CTRL_IRAM_CFG_WRITE_ACCESS, DISABLED)); + + /* Disable the ARC clock gate override. */ + reg::ReadWrite(CLK_RST + CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRD, CLK_RST_REG_BITS_ENUM(LVL2_CLK_GATE_OVRD_ARC_CLK_OVR_ON, OFF)); + + /* Read IRAM REG CTRL to make sure our writes take. */ + reg::Read(MC + MC_IRAM_REG_CTRL); + } + + void DisableUntranslatedDeviceMemoryAccess() { + /* If we can (mariko only), disable GMMU accesses that bypass the SMMU. */ + /* Additionally, force all untranslated acccesses to hit one of the carveouts. */ + if (GetSocType() == fuse::SocType_Mariko) { + reg::Write(MC + MC_UNTRANSLATED_REGION_CHECK, MC_REG_BITS_ENUM(UNTRANSLATED_REGION_CHECK_UNTRANSLATED_REGION_CHECK_ACCESS, DISABLED), + MC_REG_BITS_ENUM(UNTRANSLATED_REGION_CHECK_REQUIRE_UNTRANSLATED_CLIENTS_HIT_CARVEOUT, ENABLED), + MC_REG_BITS_ENUM(UNTRANSLATED_REGION_CHECK_REQUIRE_UNTRANSLATED_GPU_HIT_CARVEOUT, ENABLED)); + } + } + + void FinalizeCarveoutSecureScratchRegisters() { + /* Define carveout scratch values. */ + constexpr uintptr_t WarmbootCarveoutAddress = MemoryRegionDram.GetAddress(); + constexpr size_t WarmbootCarveoutSize = 128_KB; + + #define MC_ENABLE_CLIENT_ACCESS(INDEX, WHICH) MC_REG_BITS_ENUM(CLIENT_ACCESS##INDEX##_##WHICH, ENABLE) + + constexpr u32 WarmbootCarveoutClientAccess0 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(0, AVPCARM7R), + MC_ENABLE_CLIENT_ACCESS(0, PPCSAHBSLVR)); + + constexpr u32 WarmbootCarveoutClientAccess1 = reg::Encode(MC_ENABLE_CLIENT_ACCESS(1, AVPCARM7W)); + + #undef MC_ENABLE_CLIENT_ACCESS + + constexpr u32 WarmbootCarveoutForceInternalAccess0 = reg::Encode(MC_REG_BITS_ENUM(CLIENT_ACCESS0_AVPCARM7R, ENABLE), + MC_REG_BITS_ENUM(CLIENT_ACCESS0_PPCSAHBSLVR, ENABLE)); + + constexpr u32 WarmbootCarveoutForceInternalAccess1 = reg::Encode(MC_REG_BITS_ENUM(CLIENT_ACCESS1_AVPCARM7W, ENABLE)); + + constexpr u32 WarmbootCarveoutConfig = reg::Encode(MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_IS_WPR, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_FORCE_APERTURE_ID_MATCH, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ALLOW_APERTURE_ID_MISMATCH, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_RD_EN, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_WR_EN, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_SEND_CFG_TO_GPU, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS), + MC_REG_BITS_VALUE(SECURITY_CARVEOUT_CFG0_APERTURE_ID, 0), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL3, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL2, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL1, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL0, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL3, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL2, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL1, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL0, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ADDRESS_TYPE, ANY_ADDRESS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_LOCK_MODE, UNLOCKED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_PROTECT_MODE, LOCKBIT_SECURE)); + + /* Save the carveout values into secure scratch. */ + + /* Save MC_SECURITY_CARVEOUT4_BOM. */ + reg::ReadWrite(PMC + APBDEV_PMC_SECURE_SCRATCH51, REG_BITS_VALUE( 0, 15, WarmbootCarveoutAddress >> 17)); + + /* Save MC_SECURITY_CARVEOUT4_BOM_HI. */ + reg::ReadWrite(PMC + APBDEV_PMC_SECURE_SCRATCH16, REG_BITS_VALUE(30, 2, WarmbootCarveoutAddress >> 32)); + + /* Save MC_SECURITY_CARVEOUT4_SIZE_128KB. */ + reg::ReadWrite(PMC + APBDEV_PMC_SECURE_SCRATCH55, REG_BITS_VALUE(12, 12, WarmbootCarveoutSize / 128_KB)); + + /* Save MC_SECURITY_CARVEOUT4_CLIENT_ACCESS. */ + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH74, WarmbootCarveoutClientAccess0); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH75, WarmbootCarveoutClientAccess1); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH76, 0); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH77, 0); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH78, 0); + + /* Save MC_SECURITY_CARVEOUT4_FORCE_INTERNAL_ACCESS. */ + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH99, WarmbootCarveoutForceInternalAccess0); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH100, WarmbootCarveoutForceInternalAccess1); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH101, 0); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH102, 0); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH103, 0); + + /* Save MC_SECURITY_CARVEOUT4_CFG0. */ + reg::ReadWrite(PMC + APBDEV_PMC_SECURE_SCRATCH39, REG_BITS_VALUE(0, 27, WarmbootCarveoutConfig)); + } + + void EnableBpmpSmmu() { + /* Define the ASID contents. */ + constexpr int BpmpAsid = 1; + constexpr uintptr_t BpmpAsidPde = MemoryRegionPhysicalDeviceSecurityEngine.GetAddress(); + + /* Configure the ASID. */ + reg::Write(MC + MC_SMMU_PTB_ASID, MC_REG_BITS_VALUE(SMMU_PTB_ASID_CURRENT_ASID, BpmpAsid)); + + reg::Write(MC + MC_SMMU_PTB_DATA, MC_REG_BITS_VALUE(SMMU_PTB_DATA_ASID_PDE_BASE, BpmpAsidPde / 4_KB), + MC_REG_BITS_ENUM (SMMU_PTB_DATA_ASID_NONSECURE, DISABLE), + MC_REG_BITS_ENUM (SMMU_PTB_DATA_ASID_WRITABLE, DISABLE), + MC_REG_BITS_ENUM (SMMU_PTB_DATA_ASID_READABLE, DISABLE)); + + /* Configure the BPMP and PPCS1 to use the asid. */ + reg::Write(MC + MC_SMMU_AVPC_ASID, MC_REG_BITS_ENUM(SMMU_AVPC_ASID_AVPC_SMMU_ENABLE, ENABLE), MC_REG_BITS_VALUE(SMMU_AVPC_ASID_AVPC_ASID, BpmpAsid)); + reg::Write(MC + MC_SMMU_PPCS1_ASID, MC_REG_BITS_ENUM(SMMU_PPCS1_ASID_PPCS1_SMMU_ENABLE, ENABLE), MC_REG_BITS_VALUE(SMMU_PPCS1_ASID_PPCS1_ASID, BpmpAsid)); + + /* Flush the entire page table cache, and read TLB_CONFIG to ensure the flush takes. */ + reg::Write(MC + MC_SMMU_PTC_FLUSH_0, 0); + reg::Read (MC + MC_SMMU_TLB_CONFIG); + + /* Flush the entire translation lookaside buffer, and read TLB_CONFIG to ensure the flush takes. */ + reg::Write(MC + MC_SMMU_TLB_FLUSH, 0); + reg::Read (MC + MC_SMMU_TLB_CONFIG); + } + + void ValidateResetExpected() { + /* We're coming out of reset, so check that we expected to come out of reset. */ + if (!IsResetExpected()) { + secmon::SetError(pkg1::ErrorInfo_UnexpectedReset); + AMS_ABORT("unexpected reset"); + } + SetResetExpected(false); + } + + void ActmonInterruptHandler() { + SetError(pkg1::ErrorInfo_ActivityMonitorInterrupt); + AMS_ABORT("actmon observed bpmp wakeup"); + } + + void ExitChargerHiZMode() { + /* Setup I2c-1. */ + pinmux::SetupI2c1(); + clkrst::EnableI2c1Clock(); + + /* Initialize I2c-1. */ + i2c::Initialize(i2c::Port_1); + + /* Exit Hi-Z mode. */ + charger::ExitHiZMode(); + + /* Disable clock to I2c-1. */ + clkrst::DisableI2c1Clock(); + } + + bool IsExitLp0() { + return reg::Read(MC + MC_SECURITY_CFG3) == 0; + } + + void SetupLogForBoot() { + log::Initialize(secmon::GetLogPort(), secmon::GetLogBaudRate(), secmon::GetLogFlags()); + log::SendText("OHAYO\n", 6); + log::Flush(); + } + + void LogExitLp0() { + /* NOTE: Nintendo only does this on dev, but we will always do it. */ + if (true /* !pkg1::IsProduction() */) { + SetupLogForBoot(); + } + } + + void SetupForLp0Exit() { + /* Exit HiZ mode in charger, if we need to. */ + const auto target_fw = GetTargetFirmware(); + const bool force_exit_hiz_mode = (target_fw < TargetFirmware_4_0_0) || (target_fw < TargetFirmware_8_0_0 && fuse::GetHardwareType() == fuse::HardwareType_Icosa); + if (force_exit_hiz_mode || smc::IsChargerHiZModeEnabled()) { + ExitChargerHiZMode(); + } + + /* Refill the random cache, which is volatile and thus wiped on warmboot. */ + smc::FillRandomCache(); + + /* Unlock the security engine. */ + secmon::smc::UnlockSecurityEngine(); + } + + } + + void Setup1() { + /* Load the global configuration context. */ + InitializeConfigurationContext(); + + /* Initialize uart for logging. */ + SetupLogForBoot(); + + /* Initialize the security engine. */ + se::Initialize(); + + /* Initialize the gic. */ + gic::InitializeCommon(); + } + + void Setup1ForWarmboot() { + /* Initialize the security engine. */ + se::Initialize(); + + /* Initialize the gic. */ + gic::InitializeCommon(); + } + + void SaveSecurityEngineAesKeySlotTestVector() { + GenerateSecurityEngineAesKeySlotTestVector(g_se_aes_key_slot_test_vector, sizeof(g_se_aes_key_slot_test_vector)); + } + + void SetupSocSecurity() { + /* Set the fuse visibility. */ + clkrst::SetFuseVisibility(true); + + /* Set fuses as only secure-writable. */ + fuse::SetWriteSecureOnly(); + + /* Lockout the fuses. */ + fuse::Lockout(); + + /* Set the security engine to secure mode. */ + se::SetSecure(true); + + /* Verify the security engine's sticky bits. */ + VerifySecurityEngineStickyBits(); + + /* Verify the security engine's Aes slots contain correct contents. */ + VerifySecurityEngineAesKeySlotTestVector(); + + /* Clear aes keyslots. */ + ClearAesKeySlots(); + + /* Clear rsa keyslots. */ + ClearRsaKeySlots(); + + /* Overwrite keys that we want to be random with random contents. */ + se::InitializeRandom(); + se::ConfigureAutomaticContextSave(); + se::SetRandomKey(pkg1::AesKeySlot_Temporary); + se::GenerateSrk(); + se::SetRandomKey(pkg1::AesKeySlot_TzramSaveKek); + + /* Initialize pmc secure scratch. */ + if (GetSocType() == fuse::SocType_Erista) { + pmc::InitializeRandomScratch(); + } + pmc::LockSecureRegister(pmc::SecureRegister_Srk); + + /* Setup secure registers. */ + SetupSecureRegisters(); + + /* Setup the smmu. */ + SetupSmmu(); + + /* Clear the cpu reset vector. */ + reg::Write(EVP + EVP_CPU_RESET_VECTOR, 0); + + /* Configure the SB registers to our start address. */ + constexpr u32 ResetVectorLow = static_cast<u32>((PhysicalTzramProgramResetVector >> 0)); + constexpr u32 ResetVectorHigh = static_cast<u32>((PhysicalTzramProgramResetVector >> BITSIZEOF(u32))); + + /* Write our reset vector to the secure boot registers. */ + reg::Write(secmon::MemoryRegionVirtualDeviceSystem.GetAddress() + SB_AA64_RESET_LOW, ResetVectorLow | 1); + reg::Write(secmon::MemoryRegionVirtualDeviceSystem.GetAddress() + SB_AA64_RESET_HIGH, ResetVectorHigh); + + /* Disable non-secure writes to the reset vector. */ + reg::Write(secmon::MemoryRegionVirtualDeviceSystem.GetAddress() + SB_CSR, SB_REG_BITS_ENUM(CSR_NS_RST_VEC_WR_DIS, DISABLE)); + + /* Read back SB_CSR to make sure our non-secure write disable takes. */ + reg::Read(secmon::MemoryRegionVirtualDeviceSystem.GetAddress() + SB_CSR); + + /* Write our reset vector to scratch registers used by warmboot, and lock those scratch registers. */ + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH34, ResetVectorLow); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH35, ResetVectorHigh); + pmc::LockSecureRegister(pmc::SecureRegister_ResetVector); + + /* Setup the security engine interrupt. */ + constexpr int SecurityEngineInterruptId = 90; + constexpr u8 SecurityEngineInterruptCoreMask = (1 << 3); + gic::SetPriority (SecurityEngineInterruptId, gic::HighestPriority); + gic::SetInterruptGroup(SecurityEngineInterruptId, 0); + gic::SetEnable (SecurityEngineInterruptId, true); + gic::SetSpiTargetCpu (SecurityEngineInterruptId, SecurityEngineInterruptCoreMask); + gic::SetSpiMode (SecurityEngineInterruptId, gic::InterruptMode_Level); + + /* Setup the activity monitor interrupt. */ + constexpr int ActivityMonitorInterruptId = 77; + constexpr u8 ActivityMonitorInterruptCoreMask = (1 << 3); + gic::SetPriority (ActivityMonitorInterruptId, gic::HighestPriority); + gic::SetInterruptGroup(ActivityMonitorInterruptId, 0); + gic::SetEnable (ActivityMonitorInterruptId, true); + gic::SetSpiTargetCpu (ActivityMonitorInterruptId, ActivityMonitorInterruptCoreMask); + gic::SetSpiMode (ActivityMonitorInterruptId, gic::InterruptMode_Level); + + /* Setup the mariko fatal error interrupt. */ + constexpr u8 MarikoFatalInterruptCoreMask = 0b1111; + gic::SetPriority (MarikoFatalErrorInterruptId, gic::HighestPriority); + gic::SetInterruptGroup(MarikoFatalErrorInterruptId, 0); + gic::SetEnable (MarikoFatalErrorInterruptId, true); + gic::SetSpiTargetCpu (MarikoFatalErrorInterruptId, 0); + gic::SetSpiMode (MarikoFatalErrorInterruptId, gic::InterruptMode_Level); + + /* If we're coldboot, perform one-time setup. */ + if (g_is_cold_boot) { + /* Register all interrupt handlers. */ + SetInterruptHandler(SecurityEngineInterruptId, SecurityEngineInterruptCoreMask, se::HandleInterrupt); + SetInterruptHandler(ActivityMonitorInterruptId, ActivityMonitorInterruptCoreMask, actmon::HandleInterrupt); + SetInterruptHandler(MarikoFatalErrorInterruptId, MarikoFatalInterruptCoreMask, secmon::HandleMarikoFatalErrorInterrupt); + + /* We're expecting the other cores to come out of reset. */ + for (int i = 1; i < NumCores; ++i) { + SetResetExpected(i, true); + } + + /* We only coldboot once. */ + g_is_cold_boot = false; + } + } + + void SetupSocSecurityWarmboot() { + /* Check that we're allowed to continue. */ + ValidateResetExpected(); + + /* Unmap the tzram identity mapping. */ + UnmapTzram(); + + /* If we're exiting LP0, there's a little more work for us to do. */ + if (IsExitLp0()) { + /* Log that we're exiting LP0. */ + LogExitLp0(); + + /* Perform initial setup. */ + Setup1ForWarmboot(); + + /* Generate a random srk. */ + se::GenerateSrk(); + + /* Setup the Soc security. */ + SetupSocSecurity(); + + /* Set the PMC and MC as secure-only. */ + SetupPmcAndMcSecure(); + + /* Perform Lp0-exit specific init. */ + SetupForLp0Exit(); + + /* Setup the Soc protections. */ + SetupSocProtections(); + } + + /* Perform remaining CPU initialization. */ + SetupCpuCoreContext(); + SetupCpuSErrorDebug(); + } + + void SetupSocProtections() { + /* Setup the GPU carveout. */ + SetupGpuCarveout(); + + /* Configure the two kernel carveouts. */ + SetupKernelCarveouts(); + + /* Disable the ARC. */ + DisableArc(); + + /* Disable untranslated memory accesses by devices. */ + DisableUntranslatedDeviceMemoryAccess(); + + /* Further protections aren't applied on <= 1.0.0. */ + if (GetTargetFirmware() <= TargetFirmware_1_0_0) { + return; + } + + /* Finalize and lock the carveout scratch registers. */ + FinalizeCarveoutSecureScratchRegisters(); + pmc::LockSecureRegister(pmc::SecureRegister_Carveout); + + /* Clear all the BPMP exception vectors to a fixed value. */ + constexpr u32 BpmpExceptionVector = 0x7D000000; + reg::Write(EVP + EVP_COP_RESET_VECTOR, BpmpExceptionVector); + reg::Write(EVP + EVP_COP_UNDEF_VECTOR, BpmpExceptionVector); + reg::Write(EVP + EVP_COP_SWI_VECTOR, BpmpExceptionVector); + reg::Write(EVP + EVP_COP_PREFETCH_ABORT_VECTOR, BpmpExceptionVector); + reg::Write(EVP + EVP_COP_DATA_ABORT_VECTOR, BpmpExceptionVector); + reg::Write(EVP + EVP_COP_RSVD_VECTOR, BpmpExceptionVector); + reg::Write(EVP + EVP_COP_IRQ_VECTOR, BpmpExceptionVector); + reg::Write(EVP + EVP_COP_FIQ_VECTOR, BpmpExceptionVector); + + /* Disable arbitration for the bpmp. */ + reg::ReadWrite(SYSTEM + AHB_ARBITRATION_DISABLE, AHB_REG_BITS_ENUM(ARBITRATION_DISABLE_COP, DISABLE)); + + /* Turn on the SMMU for the BPMP. */ + EnableBpmpSmmu(); + + /* Wait until the flow controller reports that the BPMP is halted. */ + while (!reg::HasValue(FLOW_CTLR + FLOW_CTLR_HALT_COP_EVENTS, FLOW_REG_BITS_ENUM(HALT_COP_EVENTS_MODE, FLOW_MODE_STOP))) { + util::WaitMicroSeconds(1); + } + + /* Enable clock to the activity monitor. */ + clkrst::EnableActmonClock(); + + /* If JTAG is disabled, disable JTAG. */ + if (!secmon::IsJtagEnabled()) { + reg::Write(FLOW_CTLR + FLOW_CTLR_HALT_COP_EVENTS, FLOW_REG_BITS_ENUM(HALT_COP_EVENTS_MODE, FLOW_MODE_STOP), + FLOW_REG_BITS_ENUM(HALT_COP_EVENTS_JTAG, DISABLED)); + + /* Turn on the activity monitor to prevent booting up the bpmp. */ + actmon::StartMonitoringBpmp(ActmonInterruptHandler); + } + } + + void SetupPmcAndMcSecure() { + const auto target_fw = GetTargetFirmware(); + + if (target_fw >= TargetFirmware_2_0_0) { + /* Set the PMC secure. */ + reg::ReadWrite(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG0_0, SLAVE_SECURITY_REG_BITS_ENUM(0, PMC, ENABLE)); + } + + if (target_fw >= TargetFirmware_4_0_0) { + /* Set the MC secure. */ + reg::ReadWrite(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG1_0, SLAVE_SECURITY_REG_BITS_ENUM(1, MC0, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(1, MC1, ENABLE), + SLAVE_SECURITY_REG_BITS_ENUM(1, MCB, ENABLE)); + } + } + + void SetupCpuCoreContext() { + /* Get the tsc frequency. */ + const u32 tsc_frequency = reg::Read(MemoryRegionVirtualDeviceSysCtr0.GetAddress() + SYSCTR0_CNTFID0); + + /* Setup the secure EL2/EL1 system registers. */ + SetupSecureEl2AndEl1SystemRegisters(); + + /* Setup the non-secure system registers. */ + SetupNonSecureSystemRegisters(tsc_frequency); + + /* Reset the cpu flow controller registers. */ + flow::ResetCpuRegisters(hw::GetCurrentCoreId()); + + /* Initialize the core unique gic registers. */ + gic::InitializeCoreUnique(); + + /* Configure cpu fiq. */ + constexpr int FiqInterruptId = 28; + gic::SetPriority (FiqInterruptId, gic::HighestPriority); + gic::SetInterruptGroup(FiqInterruptId, 0); + gic::SetEnable (FiqInterruptId, true); + + /* Restore the cpu's debug registers. */ + RestoreDebugRegisters(); + } + + void SetupCpuSErrorDebug() { + /* Get whether we should enable SError debug. */ + const auto &bc_data = secmon::GetBootConfig().data; + const bool enabled = bc_data.IsDevelopmentFunctionEnabled() && bc_data.IsSErrorDebugEnabled(); + + /* Get and set scr_el3. */ + { + util::BitPack32 scr; + HW_CPU_GET_SCR_EL3(scr); + + scr.Set<hw::ScrEl3::Ea>(enabled ? 0 : 1); + HW_CPU_SET_SCR_EL3(scr); + } + + /* Prevent reordering instructions around this call. */ + hw::InstructionSynchronizationBarrier(); + } + + void SetKernelCarveoutRegion(int index, uintptr_t address, size_t size) { + /* Configure the carveout. */ + auto &carveout = g_kernel_carveouts[index]; + carveout.address = address; + carveout.size = size; + + SetupKernelCarveouts(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_setup.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_setup.hpp new file mode 100644 index 00000000..1bba3e31 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_setup.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::secmon { + + constexpr inline u64 MemoryAttributeIndexNormal = 0; + constexpr inline u64 MemoryAttributeIndexDevice = 1; + + constexpr inline int KernelCarveoutCount = 2; + + constexpr size_t CarveoutSizeMax = 512_MB - 128_KB; + + void SetupCpuMemoryControllersEnableMmu(); + void SetupCpuCoreContext(); + void SetupCpuSErrorDebug(); + + void SetupSocDmaControllers(); + void SetupSocSecurity(); + void SetupSocProtections(); + + void SetupPmcAndMcSecure(); + + void Setup1(); + + void SaveSecurityEngineAesKeySlotTestVector(); + + void SetKernelCarveoutRegion(int index, uintptr_t address, size_t size); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_setup_warm.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_setup_warm.cpp new file mode 100644 index 00000000..baa8cbb3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_setup_warm.cpp @@ -0,0 +1,302 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_setup.hpp" + +namespace ams::secmon { + + namespace setup { + + #include "secmon_cache_impl.inc" + + } + + namespace { + + constexpr inline uintptr_t MC = MemoryRegionPhysicalDeviceMemoryController.GetAddress(); + + using namespace ams::mmu; + + constexpr inline PageTableMappingAttribute MappingAttributesEl3SecureRwCode = AddMappingAttributeIndex(PageTableMappingAttributes_El3SecureRwCode, MemoryAttributeIndexNormal); + + void SetupCpuCommonControllers() { + /* Set cpuactlr_el1. */ + { + util::BitPack64 cpuactlr = {}; + cpuactlr.Set<hw::CpuactlrEl1CortexA57::NonCacheableStreamingEnhancement>(1); + cpuactlr.Set<hw::CpuactlrEl1CortexA57::DisableLoadPassDmb>(1); + HW_CPU_SET_CPUACTLR_EL1(cpuactlr); + } + + /* Set cpuectlr_el1. */ + { + util::BitPack64 cpuectlr = {}; + cpuectlr.Set<hw::CpuectlrEl1CortexA57::Smpen>(1); + cpuectlr.Set<hw::CpuectlrEl1CortexA57::L2LoadStoreDataPrefetchDistance>(3); + cpuectlr.Set<hw::CpuectlrEl1CortexA57::L2InstructionFetchPrefetchDistance>(3); + HW_CPU_SET_CPUECTLR_EL1(cpuectlr); + } + + /* Prevent instruction reordering. */ + hw::InstructionSynchronizationBarrier(); + } + + void SetupCpuEl3Controllers() { + /* Set scr_el3. */ + { + util::BitPack32 scr = {}; + scr.Set<hw::ScrEl3::Ns >(1); /* Set EL0/EL1 as Non-Secure. */ + scr.Set<hw::ScrEl3::Irq >(0); /* IRQs are taken in IRQ mode. */ + scr.Set<hw::ScrEl3::Fiq >(1); /* FIQs are taken in Monitor mode. */ + scr.Set<hw::ScrEl3::Ea >(1); /* External aborts are taken in Monitor mode. */ + scr.Set<hw::ScrEl3::Fw >(1); /* CPSR.F is non-secure writable. */ + scr.Set<hw::ScrEl3::Aw >(1); /* CPSR.A is non-secure writable. */ + scr.Set<hw::ScrEl3::Net >(0); /* This bit is not implemented. */ + scr.Set<hw::ScrEl3::Smd >(0); /* Secure Monitor Call is allowed. */ + scr.Set<hw::ScrEl3::Hce >(0); /* Hypervisor Calls are disabled. */ /* TODO: Enable for thermosphere? */ + scr.Set<hw::ScrEl3::Sif >(1); /* Secure mode cannot fetch instructions from non-secure memory. */ + scr.Set<hw::ScrEl3::RwCortexA53>(1); /* Reserved bit. N probably sets it because on Cortex A53, this sets kernel as aarch64. */ + scr.Set<hw::ScrEl3::StCortexA53>(0); /* Reserved bit. On Cortex A53, this sets secure registers to EL3 only. */ + scr.Set<hw::ScrEl3::Twi >(0); /* WFI is not trapped. */ + scr.Set<hw::ScrEl3::Twe >(0); /* WFE is not trapped. */ + + HW_CPU_SET_SCR_EL3(scr); + } + + /* Set ttbr0_el3. */ + { + constexpr u64 ttbr0 = MemoryRegionPhysicalTzramL1PageTable.GetAddress(); + HW_CPU_SET_TTBR0_EL3(ttbr0); + } + + /* Set tcr_el3. */ + { + util::BitPack32 tcr = { hw::TcrEl3::Res1 }; + tcr.Set<hw::TcrEl3::T0sz >(31); /* Configure TTBR0 addressed size to be 64 GiB */ + tcr.Set<hw::TcrEl3::Irgn0>(1); /* Configure PTE walks as inner write-back write-allocate cacheable */ + tcr.Set<hw::TcrEl3::Orgn0>(1); /* Configure PTE walks as outer write-back write-allocate cacheable */ + tcr.Set<hw::TcrEl3::Sh0 >(3); /* Configure PTE walks as inner shareable */ + tcr.Set<hw::TcrEl3::Tg0 >(0); /* Set TTBR0_EL3 granule as 4 KiB */ + tcr.Set<hw::TcrEl3::Ps >(1); /* Set the physical addrss size as 36-bit (64 GiB) */ + tcr.Set<hw::TcrEl3::Tbi >(0); /* Top byte is not ignored in addrss calculations */ + + HW_CPU_SET_TCR_EL3(tcr); + } + + /* Clear cptr_el3. */ + { + util::BitPack32 cptr = {}; + + cptr.Set<hw::CptrEl3::Tfp >(0); /* FP/SIMD instructions don't trap. */ + cptr.Set<hw::CptrEl3::Tta >(0); /* Reserved bit (no trace functionality present). */ + cptr.Set<hw::CptrEl3::Tcpac>(0); /* Access to cpacr_El1 does not trap. */ + + HW_CPU_SET_CPTR_EL3(cptr); + } + + /* Set mair_el3. */ + { + u64 mair = (MemoryRegionAttributes_Normal << (MemoryRegionAttributeWidth * MemoryAttributeIndexNormal)) | + (MemoryRegionAttributes_Device << (MemoryRegionAttributeWidth * MemoryAttributeIndexDevice)); + HW_CPU_SET_MAIR_EL3(mair); + } + + /* Set vectors. */ + { + constexpr u64 vectors = MemoryRegionVirtualTzramProgramExceptionVectors.GetAddress(); + HW_CPU_SET_VBAR_EL3(vectors); + } + + /* Prevent instruction re-ordering around this point. */ + hw::InstructionSynchronizationBarrier(); + } + + void EnableMmu() { + /* Create sctlr value. */ + util::BitPack64 sctlr = { hw::SctlrEl3::Res1 }; + sctlr.Set<hw::SctlrEl3::M>(1); /* Globally enable the MMU. */ + sctlr.Set<hw::SctlrEl3::A>(0); /* Disable alignment fault checking. */ + sctlr.Set<hw::SctlrEl3::C>(1); /* Globally enable the data and unified caches. */ + sctlr.Set<hw::SctlrEl3::Sa>(0); /* Disable stack alignment checking. */ + sctlr.Set<hw::SctlrEl3::I>(1); /* Globally enable the instruction cache. */ + sctlr.Set<hw::SctlrEl3::Wxn>(0); /* Do not force writable pages to be ExecuteNever. */ + sctlr.Set<hw::SctlrEl3::Ee>(0); /* Exceptions should be little endian. */ + + /* Ensure all writes are done before turning on the mmu. */ + hw::DataSynchronizationBarrierInnerShareable(); + + /* Invalidate the entire tlb. */ + hw::InvalidateEntireTlb(); + + /* Ensure instruction consistency. */ + hw::DataSynchronizationBarrierInnerShareable(); + hw::InstructionSynchronizationBarrier(); + + /* Set sctlr_el3. */ + HW_CPU_SET_SCTLR_EL3(sctlr); + hw::InstructionSynchronizationBarrier(); + } + + bool IsExitLp0() { + return reg::Read(MC + MC_SECURITY_CFG3) == 0; + } + + constexpr void AddPhysicalTzramIdentityMappingImpl(u64 *l1, u64 *l2, u64 *l3) { + /* Define extents. */ + const uintptr_t start_address = MemoryRegionPhysicalTzram.GetAddress(); + const size_t size = MemoryRegionPhysicalTzram.GetSize(); + const uintptr_t end_address = start_address + size; + + /* Flush cache for the L3 page table entries. */ + { + const uintptr_t start = GetL3EntryIndex(start_address); + const uintptr_t end = GetL3EntryIndex(end_address); + for (uintptr_t i = start; i < end; i += hw::DataCacheLineSize / sizeof(*l3)) { + if (!std::is_constant_evaluated()) { hw::FlushDataCacheLine(l3 + i); } + } + } + + /* Flush cache for the L2 page table entry. */ + if (!std::is_constant_evaluated()) { hw::FlushDataCacheLine(l2 + GetL2EntryIndex(start_address)); } + + /* Flush cache for the L1 page table entry. */ + if (!std::is_constant_evaluated()) { hw::FlushDataCacheLine(l1 + GetL1EntryIndex(start_address)); } + + /* Add the L3 mappings. */ + SetL3BlockEntry(l3, start_address, start_address, size, MappingAttributesEl3SecureRwCode); + + /* Add the L2 entry for the physical tzram region. */ + SetL2TableEntry(l2, MemoryRegionPhysicalTzramL2.GetAddress(), MemoryRegionPhysicalTzramL2L3PageTable.GetAddress(), PageTableTableAttributes_El3SecureCode); + + /* Add the L1 entry for the physical region. */ + SetL1TableEntry(l1, MemoryRegionPhysical.GetAddress(), MemoryRegionPhysicalTzramL2L3PageTable.GetAddress(), PageTableTableAttributes_El3SecureCode); + static_assert(GetL1EntryIndex(MemoryRegionPhysical.GetAddress()) == 1); + + /* Invalidate the data cache for the L3 page table entries. */ + { + const uintptr_t start = GetL3EntryIndex(start_address); + const uintptr_t end = GetL3EntryIndex(end_address); + for (uintptr_t i = start; i < end; i += hw::DataCacheLineSize / sizeof(*l3)) { + if (!std::is_constant_evaluated()) { hw::InvalidateDataCacheLine(l3 + i); } + } + } + + /* Flush cache for the L2 page table entry. */ + if (!std::is_constant_evaluated()) { hw::InvalidateDataCacheLine(l2 + GetL2EntryIndex(start_address)); } + + /* Flush cache for the L1 page table entry. */ + if (!std::is_constant_evaluated()) { hw::InvalidateDataCacheLine(l1 + GetL1EntryIndex(start_address)); } + } + + void RestoreDebugCode() { + #if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING) + { + const u64 *src = MemoryRegionPhysicalDramDebugDataStore.GetPointer<u64>(); + volatile u64 *dst = MemoryRegionPhysicalDebugCode.GetPointer<u64>(); + for (size_t i = 0; i < MemoryRegionPhysicalDramDebugDataStore.GetSize() / sizeof(u64); ++i) { + dst[i] = src[i]; + } + } + #endif + } + + void AddPhysicalTzramIdentityMapping() { + /* Get page table extents. */ + u64 * const l1 = MemoryRegionPhysicalTzramL1PageTable.GetPointer<u64>(); + u64 * const l2_l3 = MemoryRegionPhysicalTzramL2L3PageTable.GetPointer<u64>(); + + /* Add the mapping. */ + AddPhysicalTzramIdentityMappingImpl(l1, l2_l3, l2_l3); + + /* Ensure that mappings are consistent. */ + setup::EnsureMappingConsistency(); + } + + } + + void SetupCpuMemoryControllersEnableMmu() { + SetupCpuCommonControllers(); + SetupCpuEl3Controllers(); + EnableMmu(); + } + + void SetupSocDmaControllers() { + /* Ensure that our caches are managed. */ + setup::InvalidateEntireDataCache(); + setup::EnsureInstructionConsistency(); + + /* Lock tsec. */ + tsec::Lock(); + + /* Enable SWID[0] for all bits. */ + reg::Write(AHB_ARBC(AHB_MASTER_SWID), ~0u); + + /* Clear SWID1 for all bits. */ + reg::Write(AHB_ARBC(AHB_MASTER_SWID_1), 0u); + + /* Set MSELECT config to set WRAP_TO_INCR_SLAVE0(APC) | WRAP_TO_INCR_SLAVE1(PCIe) | WRAP_TO_INCR_SLAVE2(GPU) */ + /* and clear ERR_RESP_EN_SLAVE1(PCIe) | ERR_RESP_EN_SLAVE2(GPU) */ + { + reg::ReadWrite(MSELECT(MSELECT_CONFIG), MSELECT_REG_BITS_ENUM(CONFIG_ERR_RESP_EN_SLAVE1, DISABLE), + MSELECT_REG_BITS_ENUM(CONFIG_ERR_RESP_EN_SLAVE2, DISABLE), + MSELECT_REG_BITS_ENUM(CONFIG_WRAP_TO_INCR_SLAVE0, ENABLE), + MSELECT_REG_BITS_ENUM(CONFIG_WRAP_TO_INCR_SLAVE1, ENABLE), + MSELECT_REG_BITS_ENUM(CONFIG_WRAP_TO_INCR_SLAVE2, ENABLE)); + } + + /* Disable USB, USB2, AHB-DMA from arbitration. */ + { + reg::ReadWrite(AHB_ARBC(AHB_ARBITRATION_DISABLE), AHB_REG_BITS_ENUM(ARBITRATION_DISABLE_AHBDMA, DISABLE), + AHB_REG_BITS_ENUM(ARBITRATION_DISABLE_USB, DISABLE), + AHB_REG_BITS_ENUM(ARBITRATION_DISABLE_USB2, DISABLE)); + } + + /* Select high priority group with priority 7. */ + { + u32 priority_ctrl = {}; + priority_ctrl |= (7u << 29); /* Set group 7. */ + priority_ctrl |= (1u << 0); /* Set high priority. */ + reg::Write(AHB_ARBC(AHB_ARBITRATION_PRIORITY_CTRL), priority_ctrl); + } + + /* Prevent splitting AHB writes to TZRAM. */ + { + reg::Write(AHB_ARBC(AHB_GIZMO_TZRAM), (1u << 7)); + } + + /* NOTE: This is Mariko only in Nintendo's firmware. */ + /* Still, it seems to have no adverse effects on Erista... */ + /* TODO: Find a way to get access to SocType this early (fuse driver isn't alive yet), only write on mariko? */ + { + reg::ReadWrite(AHB_ARBC(AHB_AHB_SPARE_REG), AHB_REG_BITS_VALUE(AHB_SPARE_REG_AHB_SPARE_REG, 0xE0000)); + } + } + + void SetupSocDmaControllersCpuMemoryControllersEnableMmuWarmboot() { + /* If this is being called from lp0 exit, we want to setup the soc dma controllers. */ + if (IsExitLp0()) { + RestoreDebugCode(); + SetupSocDmaControllers(); + } + + /* Add a physical TZRAM identity map. */ + AddPhysicalTzramIdentityMapping(); + + /* Initialize cpu memory controllers and the MMU. */ + SetupCpuMemoryControllersEnableMmu(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_spinlock.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_spinlock.hpp new file mode 100644 index 00000000..ba1364da --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_spinlock.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::secmon { + + using SpinLockType = u32; + + void AcquireSpinLock(SpinLockType &lock); + void ReleaseSpinLock(SpinLockType &lock); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_spinlock.s b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_spinlock.s new file mode 100644 index 00000000..ba79ff51 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_spinlock.s @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +.section .text._ZN3ams6secmon15AcquireSpinLockERj, "ax", %progbits +.align 4 +.global _ZN3ams6secmon15AcquireSpinLockERj +_ZN3ams6secmon15AcquireSpinLockERj: + /* Prepare to try to take the spinlock. */ + mov w1, #1 + sevl + prfm pstl1keep, [x0] + +1: /* Repeatedly try to take the lock. */ + wfe + ldaxr w2, [x0] + cbnz w2, 1b + stxr w2, w1, [x0] + cbnz w2, 1b + + /* Return. */ + ret + +.section .text._ZN3ams6secmon15ReleaseSpinLockERj, "ax", %progbits +.align 4 +.global _ZN3ams6secmon15ReleaseSpinLockERj +_ZN3ams6secmon15ReleaseSpinLockERj: + /* Release the spinlock. */ + stlr wzr, [x0] + + /* Return. */ + ret diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_stack_warm.s b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_stack_warm.s new file mode 100644 index 00000000..64069185 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_stack_warm.s @@ -0,0 +1,21 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +.section .warmboot.data._ZN3ams6secmon23CommonWarmbootStackLockE, "aw", %progbits +.global _ZN3ams6secmon23CommonWarmbootStackLockE +_ZN3ams6secmon23CommonWarmbootStackLockE: + /* Define storage for the global common warmboot stack bakery lock. */ + .word 0 diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_start_virtual.s b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_start_virtual.s new file mode 100644 index 00000000..f3d2709a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_start_virtual.s @@ -0,0 +1,212 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +.section .text._ZN3ams6secmon5StartEv, "ax", %progbits +.align 4 +.global _ZN3ams6secmon5StartEv +_ZN3ams6secmon5StartEv: + /* Set SPSEL 1 stack pointer to the core 0 exception stack address. */ + msr spsel, #1 + ldr x20, =0x1F01F6F00 + mov sp, x20 + + /* Set SPSEL 0 stack pointer to a temporary location in volatile memory. */ + msr spsel, #0 + ldr x20, =0x1F01C0800 + mov sp, x20 + + /* Setup X18 to point to the global context. */ + ldr x18, =0x1F01FA000 + + /* Invoke main. */ + bl _ZN3ams6secmon4MainEv + + /* Clear boot code high. */ + bl _ZN3ams6secmon17ClearBootCodeHighEv + + /* Set the stack pointer to the core 3 exception stack address. */ + ldr x20, =0x1F01F9000 + mov sp, x20 + + /* Unmap the boot code region (and clear the low part). */ + bl _ZN3ams6secmon13UnmapBootCodeEv + + /* Initialize the random cache. */ + /* NOTE: Nintendo does this much earlier, but we reuse volatile space. */ + bl _ZN3ams6secmon3smc15FillRandomCacheEv + + /* Jump to lower exception level. */ + b _ZN3ams6secmon25JumpToLowerExceptionLevelEv + +.section .text._ZN3ams6secmon20StartWarmbootVirtualEv, "ax", %progbits +.align 4 +.global _ZN3ams6secmon20StartWarmbootVirtualEv +_ZN3ams6secmon20StartWarmbootVirtualEv: + /* Set the stack pointer to the shared warmboot stack address. */ + ldr x20, =0x1F01F67C0 + mov sp, x20 + + /* Setup X18 to point to the global context. */ + ldr x18, =0x1F01FA000 + + /* Perform final warmboot setup. */ + bl _ZN3ams6secmon24SetupSocSecurityWarmbootEv + + /* Jump to lower exception level. */ + b _ZN3ams6secmon25JumpToLowerExceptionLevelEv + +.section .text._ZN3ams6secmon25JumpToLowerExceptionLevelEv, "ax", %progbits +.align 4 +.global _ZN3ams6secmon25JumpToLowerExceptionLevelEv +_ZN3ams6secmon25JumpToLowerExceptionLevelEv: + /* Get the EntryContext. */ + sub sp, sp, #0x10 + mov x0, sp + bl _ZN3ams6secmon15GetEntryContextEPNS0_12EntryContextE + + /* Load the entrypoint and argument from the context. */ + ldr x19, [sp, #0x00] + ldr x0, [sp, #0x08] + + /* Set the exception return address. */ + msr elr_el3, x0 + + /* Get the core exception stack. */ + bl _ZN3ams6secmon28GetCoreExceptionStackVirtualEv + mov sp, x0 + + /* Release our exclusive access to the common warmboot stack. */ + bl _ZN3ams6secmon26ReleaseCommonWarmbootStackEv + + /* Configure SPSR_EL3. */ + mov x0, #0x3C5 + msr spsr_el3, x0 + + /* Set x0 to the entry argument. */ + mov x0, x19 + + /* Ensure instruction reordering doesn't happen around this point. */ + isb + + /* Return to lower level. */ + eret + + /* Infinite loop, though we should never get here. */ + 1: b 1b + +.section .text._ZN3ams6secmon28GetCoreExceptionStackVirtualEv, "ax", %progbits +.align 4 +.global _ZN3ams6secmon28GetCoreExceptionStackVirtualEv +_ZN3ams6secmon28GetCoreExceptionStackVirtualEv: + /* Get the current core id. */ + mrs x0, mpidr_el1 + and x0, x0, #3 + + /* Jump to the appropriate core's stack handler. */ + cmp x0, #3 + b.eq 3f + + cmp x0, #2 + b.eq 2f + + cmp x0, #1 + b.eq 1f + + /* cmp x0, #0 */ + /* b.eq 0f */ + + 0: + ldr x0, =0x1F01F6F00 + ret + 1: + ldr x0, =0x1F01F6F80 + ret + 2: + ldr x0, =0x1F01F7000 + ret + 3: + ldr x0, =0x1F01F9000 + ret + +.section .text._ZN3ams6secmon25AcquireCommonSmcStackLockEv, "ax", %progbits +.align 4 +.global _ZN3ams6secmon25AcquireCommonSmcStackLockEv +_ZN3ams6secmon25AcquireCommonSmcStackLockEv: + /* Get the address of the lock. */ + ldr x0, =_ZN3ams6secmon18CommonSmcStackLockE + + /* Take the lock. */ + b _ZN3ams6secmon15AcquireSpinLockERj + +.section .text._ZN3ams6secmon25ReleaseCommonSmcStackLockEv, "ax", %progbits +.align 4 +.global _ZN3ams6secmon25ReleaseCommonSmcStackLockEv +_ZN3ams6secmon25ReleaseCommonSmcStackLockEv: + /* Get the address of the lock. */ + ldr x0, =_ZN3ams6secmon18CommonSmcStackLockE + + /* Release the lock. */ + b _ZN3ams6secmon15ReleaseSpinLockERj + +.section .text._ZN3ams6secmon26ReleaseCommonWarmbootStackEv, "ax", %progbits +.align 4 +.global _ZN3ams6secmon26ReleaseCommonWarmbootStackEv +_ZN3ams6secmon26ReleaseCommonWarmbootStackEv: + /* Get the virtual address of the lock. */ + ldr x0, =_ZN3ams6secmon23CommonWarmbootStackLockE + ldr x1, =(0x1F00C0000 - 0x07C012000) + add x1, x1, x0 + + /* Get the bakery value for our core. */ + mrs x0, mpidr_el1 + and x0, x0, #3 + ldrb w2, [x1, x0] + + /* Clear our ticket number. */ + and w2, w2, #(~0x7F) + strb w2, [x1, x0] + + /* Flush the cache. */ + dc civac, x1 + + /* Synchronize data for all cores. */ + dsb sy + + /* Send an event. */ + sev + + /* Return. */ + ret + +.section .text._ZN3ams6secmon19PivotStackAndInvokeEPvPFvvE, "ax", %progbits +.align 4 +.global _ZN3ams6secmon19PivotStackAndInvokeEPvPFvvE +_ZN3ams6secmon19PivotStackAndInvokeEPvPFvvE: + /* Pivot to use the provided stack pointer. */ + mov sp, x0 + + /* Release our lock on the common smc stack. */ + mov x19, x1 + bl _ZN3ams6secmon25ReleaseCommonSmcStackLockEv + + /* Invoke the function with the new stack. */ + br x19 + +.section .data._ZN3ams6secmon18CommonSmcStackLockE, "aw", %progbits +.global _ZN3ams6secmon18CommonSmcStackLockE +_ZN3ams6secmon18CommonSmcStackLockE: + /* Define storage for the global common smc stack spinlock. */ + .word 0 diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_start_warm.s b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_start_warm.s new file mode 100644 index 00000000..90c03728 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_start_warm.s @@ -0,0 +1,212 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* 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 .warmboot.text.start, "ax", %progbits +.align 4 +.global _start_warm +_start_warm: + /* mask all interrupts */ + msr daifset, #0xF + + /* Fixup hardware erratum */ + ERRATUM_INVALIDATE_BTB_AT_BOOT + + /* Acquire exclusive access to the common warmboot stack. */ + bl _ZN3ams6secmon26AcquireCommonWarmbootStackEv + + /* Set the stack pointer to the common warmboot stack address. */ + msr spsel, #1 + ldr x20, =0x7C0107C0 + mov sp, x20 + + /* Perform warmboot setup. */ + bl _ZN3ams6secmon59SetupSocDmaControllersCpuMemoryControllersEnableMmuWarmbootEv + + /* Jump to the newly-mapped virtual address. */ + b _ZN3ams6secmon20StartWarmbootVirtualEv + + +/* void ams::secmon::AcquireCommonWarmbootStack() { */ +/* NOTE: This implements critical section enter via https://en.wikipedia.org/wiki/Lamport%27s_bakery_algorithm */ +/* This algorithm is used because the MMU is not awake yet, so exclusive load/store instructions are not usable. */ +/* NOTE: Nintendo attempted to implement this algorithm themselves, but did not really understand how it works. */ +/* They use the same ticket number for all cores; this can lead to starvation and other problems. */ +.section .warmboot.text._ZN3ams6secmon26AcquireCommonWarmbootStackEv, "ax", %progbits +.align 4 +.global _ZN3ams6secmon26AcquireCommonWarmbootStackEv +_ZN3ams6secmon26AcquireCommonWarmbootStackEv: + /* BakeryLock *lock = std::addressof(secmon::CommonWarmBootStackLock); */ + ldr x0, =_ZN3ams6secmon23CommonWarmbootStackLockE + + /* const u32 id = GetCurrentCoreId(); */ + mrs x8, mpidr_el1 + and x8, x8, #3 + + /* lock->customers[id].is_entering = true; */ + ldrb w2, [x0, x8] + orr w2, w2, #~0x7F + strb w2, [x0, x8] + + /* const u8 ticket_0 = lock->customers[0].ticket_number; */ + ldrb w4, [x0, #0] + and w4, w4, #0x7F + + /* const u8 ticket_1 = lock->customers[1].ticket_number; */ + ldrb w5, [x0, #1] + and w5, w5, #0x7F + + /* const u8 ticket_2 = lock->customers[2].ticket_number; */ + ldrb w6, [x0, #2] + and w6, w6, #0x7F + + /* const u8 ticket_3 = lock->customers[3].ticket_number; */ + ldrb w7, [x0, #3] + and w7, w7, #0x7F + + /* u8 biggest_ticket = std::max(std::max(ticket_0, ticket_1), std::max(ticket_2, ticket_3)) */ + cmp w4, w5 + csel w2, w4, w5, hi + cmp w6, w7 + csel w3, w6, w7, hi + cmp w2, w3 + csel w2, w2, w3, hi + + /* NOTE: The biggest a ticket can ever be is 4, so the general increment is safe and 7-bit increment is not needed. */ + /* lock->customers[id] = { .is_entering = false, .ticket_number = ++biggest_ticket }; */ + add w2, w2, #1 + strb w2, [x0, x8] + + /* Ensure instructions aren't reordered around this point. */ + /* hw::DataSynchronizationBarrier(); */ + dsb sy + + /* hw::SendEvent(); */ + sev + + /* for (unsigned int i = 0; i < 4; ++i) { */ + mov w3, #0 +1: + /* hw::SendEventLocal(); */ + sevl + + /* do { */ +2: + /* hw::WaitForEvent(); */ + wfe + /* while (lock->customers[i].is_entering); */ + ldrb w4, [x0, x3] + tbnz w4, #7, 2b + + /* u8 their_ticket; */ + + /* hw::SendEventLocal(); */ + sevl + + /* do { */ +2: + /* hw::WaitForEvent(); */ + wfe + /* their_ticket = lock->customers[i].ticket_number; */ + ldrb w4, [x0, x3] + ands w4, w4, #0x7F + /* if (their_ticket == 0) { break; } */ + b.eq 3f + /* while ((their_ticket > my_ticket) || (their_ticket == my_ticket && id > i)); */ + cmp w2, w4 + b.hi 2b + ccmp w8, w3, #0, eq + b.hi 2b + + /* } */ +3: + add w3, w3, #1 + cmp w3, #4 + b.ne 1b + + /* hw::DataMemoryBarrier(); */ + dmb sy + + ret diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_user_power_management.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_user_power_management.cpp new file mode 100644 index 00000000..6f81af02 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_user_power_management.cpp @@ -0,0 +1,128 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_cpu_context.hpp" +#include "secmon_page_mapper.hpp" +#include "secmon_mariko_fatal_error.hpp" +#include "secmon_user_power_management.hpp" + +#include "rebootstub_bin.h" + +namespace ams::secmon { + + namespace { + + constexpr inline const uintptr_t PMC = MemoryRegionVirtualDevicePmc.GetAddress(); + + constexpr inline const u32 RebootStubPhysicalAddress = MemoryRegionPhysicalIramRebootStub.GetAddress(); + + enum RebootStubAction { + RebootStubAction_ShutDown = 0, + RebootStubAction_JumpToPayload = 1, + }; + + NORETURN void PerformPmcReboot() { + /* Write MAIN_RST. */ + reg::Write(PMC + APBDEV_PMC_CNTRL, 0x10); + + while (true) { + /* ... */ + } + } + + void LoadRebootStub(u32 action) { + /* Configure the bootrom to boot to warmboot payload. */ + reg::Write(PMC + APBDEV_PMC_SCRATCH0, 0x1); + + /* Patch the bootrom to perform an SVC immediately after the second spare write. */ + reg::Write(PMC + APBDEV_PMC_SCRATCH45, 0x2E38DFFF); + reg::Write(PMC + APBDEV_PMC_SCRATCH46, 0x6001DC28); + + /* Patch the bootrom to jump to the reboot stub we'll prepare in iram on SVC. */ + reg::Write(PMC + APBDEV_PMC_SCRATCH33, RebootStubPhysicalAddress); + reg::Write(PMC + APBDEV_PMC_SCRATCH40, 0x6000F208); + + { + /* Map the iram page. */ + AtmosphereIramPageMapper mapper(RebootStubPhysicalAddress); + AMS_ABORT_UNLESS(mapper.Map()); + + /* Copy the reboot stub. */ + AMS_ABORT_UNLESS(mapper.CopyToMapping(RebootStubPhysicalAddress, rebootstub_bin, rebootstub_bin_size)); + + /* Set the reboot type. */ + AMS_ABORT_UNLESS(mapper.CopyToMapping(RebootStubPhysicalAddress + 4, std::addressof(action), sizeof(action))); + } + } + + } + + void PerformUserRebootByPmic() { + /* Ensure that i2c-5 is usable for communicating with the pmic. */ + clkrst::EnableI2c5Clock(); + i2c::Initialize(i2c::Port_5); + + /* Reboot. */ + pmic::ShutdownSystem(true); + } + + void PerformUserRebootToRcm() { + /* Configure the bootrom to boot to rcm. */ + reg::Write(PMC + APBDEV_PMC_SCRATCH0, 0x2); + + /* Reboot. */ + PerformPmcReboot(); + } + + void PerformUserRebootToPayload() { + /* Load our reboot stub to iram. */ + LoadRebootStub(RebootStubAction_JumpToPayload); + + /* Reboot. */ + PerformPmcReboot(); + } + + void PerformUserRebootToFatalError() { + if (fuse::GetSocType() == fuse::SocType_Erista) { + /* On Erista, we reboot to fatal error by jumping to fusee primary's handler. */ + return PerformUserRebootToPayload(); + } else /* if (fuse::GetSocType() == fuse::SocType_Mariko) */ { + /* Call the fatal error handler. */ + HandleMarikoFatalErrorInterrupt(); + + /* We should never get to this point. */ + AMS_ABORT("Returned from Mariko Fatal handler?\n"); + } + } + + void PerformUserShutDown() { + if (fuse::GetSocType() == fuse::SocType_Mariko) { + /* Ensure that i2c-5 is usable for communicating with the pmic. */ + clkrst::EnableI2c5Clock(); + i2c::Initialize(i2c::Port_5); + + /* On Mariko shutdown via pmic. */ + pmic::ShutdownSystem(false); + } else /* if (fuse::GetSocType() == fuse::SocType_Erista) */ { + /* Load our reboot stub to iram. */ + LoadRebootStub(RebootStubAction_ShutDown); + + /* Reboot. */ + PerformPmcReboot(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_user_power_management.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_user_power_management.hpp new file mode 100644 index 00000000..678ae316 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/secmon_user_power_management.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::secmon { + + enum UserRebootType { + UserRebootType_None = 0, + UserRebootType_ToRcm = 1, + UserRebootType_ToPayload = 2, + UserRebootType_ToFatalError = 3, + UserRebootType_ByPmic = 4, + }; + + void PerformUserRebootToRcm(); + void PerformUserRebootToPayload(); + void PerformUserRebootToFatalError(); + void PerformUserRebootByPmic(); + void PerformUserShutDown(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_define_access_table.inc b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_define_access_table.inc new file mode 100644 index 00000000..504dc071 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_define_access_table.inc @@ -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 <http://www.gnu.org/licenses/>. + */ + +using __ACCESS_TABLE_NAME__ = AccessTable<__ACCESS_TABLE_ADDRESS__, [] { + /* Declare a table. */ + std::array<u8, 0x80> table = {}; + + /* Declare a helper. */ + auto SetRegisterAllowed = [&](uintptr_t reg) { SetRegisterTableAllowed(table, reg); }; + + /* Populate the table. */ + #include __ACCESS_TABLE_INC__ + + return table; +}()>; + +static_assert(__ACCESS_TABLE_NAME__::Address >= __ACCESS_TABLE_ADDRESS__); diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_define_emc_access_table.inc b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_define_emc_access_table.inc new file mode 100644 index 00000000..161f77c6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_define_emc_access_table.inc @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#define __ACCESS_TABLE_NAME__ EmcAccessTable +#define __ACCESS_TABLE_ADDRESS__ MemoryRegionPhysicalDeviceExternalMemoryController.GetAddress() +#define __ACCESS_TABLE_INC__ "secmon_emc_access_table_data.inc" + +#include "secmon_define_access_table.inc" + +#undef __ACCESS_TABLE_INC__ +#undef __ACCESS_TABLE_ADDRESS__ +#undef __ACCESS_TABLE_NAME__ diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_define_mc01_access_table.inc b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_define_mc01_access_table.inc new file mode 100644 index 00000000..2f708b58 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_define_mc01_access_table.inc @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#define __ACCESS_TABLE_NAME__ Mc01AccessTable +#define __ACCESS_TABLE_ADDRESS__ 0 +#define __ACCESS_TABLE_INC__ "secmon_mc01_access_table_data.inc" + +#include "secmon_define_access_table.inc" + +#undef __ACCESS_TABLE_INC__ +#undef __ACCESS_TABLE_ADDRESS__ +#undef __ACCESS_TABLE_NAME__ diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_define_mc_access_table.inc b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_define_mc_access_table.inc new file mode 100644 index 00000000..3c0a96b8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_define_mc_access_table.inc @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#define __ACCESS_TABLE_NAME__ McAccessTable +#define __ACCESS_TABLE_ADDRESS__ MemoryRegionPhysicalDeviceMemoryController.GetAddress() +#define __ACCESS_TABLE_INC__ "secmon_mc_access_table_data.inc" + +#include "secmon_define_access_table.inc" + +#undef __ACCESS_TABLE_INC__ +#undef __ACCESS_TABLE_ADDRESS__ +#undef __ACCESS_TABLE_NAME__ diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_define_pmc_access_table.inc b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_define_pmc_access_table.inc new file mode 100644 index 00000000..c945a124 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_define_pmc_access_table.inc @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#define __ACCESS_TABLE_NAME__ PmcAccessTable +#define __ACCESS_TABLE_ADDRESS__ MemoryRegionPhysicalDevicePmc.GetAddress() +#define __ACCESS_TABLE_INC__ "secmon_pmc_access_table_data.inc" + +#include "secmon_define_access_table.inc" + +#undef __ACCESS_TABLE_INC__ +#undef __ACCESS_TABLE_ADDRESS__ +#undef __ACCESS_TABLE_NAME__ diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_emc_access_table_data.inc b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_emc_access_table_data.inc new file mode 100644 index 00000000..6089047e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_emc_access_table_data.inc @@ -0,0 +1,967 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +SetRegisterAllowed(0x0); +SetRegisterAllowed(0x4); +SetRegisterAllowed(0x8); +SetRegisterAllowed(0xC); +SetRegisterAllowed(0x10); +SetRegisterAllowed(0x14); +SetRegisterAllowed(0x18); +SetRegisterAllowed(0x1C); +SetRegisterAllowed(0x20); +SetRegisterAllowed(0x24); +SetRegisterAllowed(0x28); +SetRegisterAllowed(0x2C); +SetRegisterAllowed(0x30); +SetRegisterAllowed(0x34); +SetRegisterAllowed(0x38); +SetRegisterAllowed(0x3C); +SetRegisterAllowed(0x40); +SetRegisterAllowed(0x44); +SetRegisterAllowed(0x48); +SetRegisterAllowed(0x4C); +SetRegisterAllowed(0x50); +SetRegisterAllowed(0x54); +SetRegisterAllowed(0x58); +SetRegisterAllowed(0x5C); +SetRegisterAllowed(0x60); +SetRegisterAllowed(0x64); +SetRegisterAllowed(0x68); +SetRegisterAllowed(0x6C); +SetRegisterAllowed(0x70); +SetRegisterAllowed(0x74); +SetRegisterAllowed(0x78); +SetRegisterAllowed(0x7C); +SetRegisterAllowed(0x80); +SetRegisterAllowed(0x84); +SetRegisterAllowed(0x88); +SetRegisterAllowed(0x8C); +SetRegisterAllowed(0x90); +SetRegisterAllowed(0x94); +SetRegisterAllowed(0x98); +SetRegisterAllowed(0x9C); +SetRegisterAllowed(0xA0); +SetRegisterAllowed(0xA4); +SetRegisterAllowed(0xA8); +SetRegisterAllowed(0xAC); +SetRegisterAllowed(0xB0); +SetRegisterAllowed(0xB4); +SetRegisterAllowed(0xB8); +SetRegisterAllowed(0xBC); +SetRegisterAllowed(0xC0); +SetRegisterAllowed(0xC4); +SetRegisterAllowed(0xC8); +SetRegisterAllowed(0xCC); +SetRegisterAllowed(0xD0); +SetRegisterAllowed(0xD4); +SetRegisterAllowed(0xD8); +SetRegisterAllowed(0xDC); +SetRegisterAllowed(0xE0); +SetRegisterAllowed(0xE4); +SetRegisterAllowed(0xE8); +SetRegisterAllowed(0xEC); +SetRegisterAllowed(0xF0); +SetRegisterAllowed(0xF4); +SetRegisterAllowed(0xF8); +SetRegisterAllowed(0xFC); +SetRegisterAllowed(0x100); +SetRegisterAllowed(0x104); +SetRegisterAllowed(0x108); +SetRegisterAllowed(0x10C); +SetRegisterAllowed(0x110); +SetRegisterAllowed(0x114); +SetRegisterAllowed(0x118); +SetRegisterAllowed(0x11C); +SetRegisterAllowed(0x120); +SetRegisterAllowed(0x124); +SetRegisterAllowed(0x128); +SetRegisterAllowed(0x12C); +SetRegisterAllowed(0x130); +SetRegisterAllowed(0x134); +SetRegisterAllowed(0x138); +SetRegisterAllowed(0x13C); +SetRegisterAllowed(0x140); +SetRegisterAllowed(0x144); +SetRegisterAllowed(0x148); +SetRegisterAllowed(0x14C); +SetRegisterAllowed(0x150); +SetRegisterAllowed(0x154); +SetRegisterAllowed(0x158); +SetRegisterAllowed(0x15C); +SetRegisterAllowed(0x160); +SetRegisterAllowed(0x164); +SetRegisterAllowed(0x168); +SetRegisterAllowed(0x16C); +SetRegisterAllowed(0x170); +SetRegisterAllowed(0x174); +SetRegisterAllowed(0x178); +SetRegisterAllowed(0x17C); +SetRegisterAllowed(0x180); +SetRegisterAllowed(0x184); +SetRegisterAllowed(0x188); +SetRegisterAllowed(0x18C); +SetRegisterAllowed(0x190); +SetRegisterAllowed(0x194); +SetRegisterAllowed(0x198); +SetRegisterAllowed(0x19C); +SetRegisterAllowed(0x1A0); +SetRegisterAllowed(0x1A4); +SetRegisterAllowed(0x1A8); +SetRegisterAllowed(0x1AC); +SetRegisterAllowed(0x1B0); +SetRegisterAllowed(0x1B4); +SetRegisterAllowed(0x1B8); +SetRegisterAllowed(0x1BC); +SetRegisterAllowed(0x1C0); +SetRegisterAllowed(0x1C4); +SetRegisterAllowed(0x1C8); +SetRegisterAllowed(0x1CC); +SetRegisterAllowed(0x1D0); +SetRegisterAllowed(0x1D4); +SetRegisterAllowed(0x1D8); +SetRegisterAllowed(0x1DC); +SetRegisterAllowed(0x1E0); +SetRegisterAllowed(0x1E4); +SetRegisterAllowed(0x1E8); +SetRegisterAllowed(0x1EC); +SetRegisterAllowed(0x1F0); +SetRegisterAllowed(0x1F4); +SetRegisterAllowed(0x1F8); +SetRegisterAllowed(0x1FC); +SetRegisterAllowed(0x200); +SetRegisterAllowed(0x204); +SetRegisterAllowed(0x208); +SetRegisterAllowed(0x20C); +SetRegisterAllowed(0x210); +SetRegisterAllowed(0x214); +SetRegisterAllowed(0x218); +SetRegisterAllowed(0x21C); +SetRegisterAllowed(0x220); +SetRegisterAllowed(0x224); +SetRegisterAllowed(0x228); +SetRegisterAllowed(0x22C); +SetRegisterAllowed(0x230); +SetRegisterAllowed(0x234); +SetRegisterAllowed(0x238); +SetRegisterAllowed(0x23C); +SetRegisterAllowed(0x240); +SetRegisterAllowed(0x244); +SetRegisterAllowed(0x248); +SetRegisterAllowed(0x24C); +SetRegisterAllowed(0x250); +SetRegisterAllowed(0x254); +SetRegisterAllowed(0x258); +SetRegisterAllowed(0x25C); +SetRegisterAllowed(0x260); +SetRegisterAllowed(0x264); +SetRegisterAllowed(0x268); +SetRegisterAllowed(0x26C); +SetRegisterAllowed(0x270); +SetRegisterAllowed(0x274); +SetRegisterAllowed(0x278); +SetRegisterAllowed(0x27C); +SetRegisterAllowed(0x280); +SetRegisterAllowed(0x284); +SetRegisterAllowed(0x288); +SetRegisterAllowed(0x28C); +SetRegisterAllowed(0x290); +SetRegisterAllowed(0x294); +SetRegisterAllowed(0x298); +SetRegisterAllowed(0x29C); +SetRegisterAllowed(0x2A0); +SetRegisterAllowed(0x2A4); +SetRegisterAllowed(0x2A8); +SetRegisterAllowed(0x2AC); +SetRegisterAllowed(0x2B0); +SetRegisterAllowed(0x2B4); +SetRegisterAllowed(0x2B8); +SetRegisterAllowed(0x2BC); +SetRegisterAllowed(0x2C0); +SetRegisterAllowed(0x2C4); +SetRegisterAllowed(0x2C8); +SetRegisterAllowed(0x2CC); +SetRegisterAllowed(0x2D0); +SetRegisterAllowed(0x2D4); +SetRegisterAllowed(0x2D8); +SetRegisterAllowed(0x2DC); +SetRegisterAllowed(0x2E0); +SetRegisterAllowed(0x2E4); +SetRegisterAllowed(0x2E8); +SetRegisterAllowed(0x2EC); +SetRegisterAllowed(0x2F0); +SetRegisterAllowed(0x2F4); +SetRegisterAllowed(0x2F8); +SetRegisterAllowed(0x2FC); +SetRegisterAllowed(0x300); +SetRegisterAllowed(0x304); +SetRegisterAllowed(0x308); +SetRegisterAllowed(0x30C); +SetRegisterAllowed(0x310); +SetRegisterAllowed(0x314); +SetRegisterAllowed(0x318); +SetRegisterAllowed(0x31C); +SetRegisterAllowed(0x320); +SetRegisterAllowed(0x324); +SetRegisterAllowed(0x328); +SetRegisterAllowed(0x32C); +SetRegisterAllowed(0x330); +SetRegisterAllowed(0x334); +SetRegisterAllowed(0x338); +SetRegisterAllowed(0x33C); +SetRegisterAllowed(0x340); +SetRegisterAllowed(0x344); +SetRegisterAllowed(0x348); +SetRegisterAllowed(0x34C); +SetRegisterAllowed(0x350); +SetRegisterAllowed(0x354); +SetRegisterAllowed(0x358); +SetRegisterAllowed(0x35C); +SetRegisterAllowed(0x360); +SetRegisterAllowed(0x364); +SetRegisterAllowed(0x368); +SetRegisterAllowed(0x36C); +SetRegisterAllowed(0x370); +SetRegisterAllowed(0x374); +SetRegisterAllowed(0x378); +SetRegisterAllowed(0x37C); +SetRegisterAllowed(0x380); +SetRegisterAllowed(0x384); +SetRegisterAllowed(0x388); +SetRegisterAllowed(0x38C); +SetRegisterAllowed(0x390); +SetRegisterAllowed(0x394); +SetRegisterAllowed(0x398); +SetRegisterAllowed(0x39C); +SetRegisterAllowed(0x3A0); +SetRegisterAllowed(0x3A4); +SetRegisterAllowed(0x3A8); +SetRegisterAllowed(0x3AC); +SetRegisterAllowed(0x3B0); +SetRegisterAllowed(0x3B4); +SetRegisterAllowed(0x3B8); +SetRegisterAllowed(0x3BC); +SetRegisterAllowed(0x3C0); +SetRegisterAllowed(0x3C4); +SetRegisterAllowed(0x3C8); +SetRegisterAllowed(0x3CC); +SetRegisterAllowed(0x3D0); +SetRegisterAllowed(0x3D4); +SetRegisterAllowed(0x3D8); +SetRegisterAllowed(0x3DC); +SetRegisterAllowed(0x3E0); +SetRegisterAllowed(0x3E4); +SetRegisterAllowed(0x3E8); +SetRegisterAllowed(0x3EC); +SetRegisterAllowed(0x3F0); +SetRegisterAllowed(0x3F4); +SetRegisterAllowed(0x3F8); +SetRegisterAllowed(0x3FC); +SetRegisterAllowed(0x400); +SetRegisterAllowed(0x404); +SetRegisterAllowed(0x408); +SetRegisterAllowed(0x40C); +SetRegisterAllowed(0x410); +SetRegisterAllowed(0x414); +SetRegisterAllowed(0x418); +SetRegisterAllowed(0x41C); +SetRegisterAllowed(0x420); +SetRegisterAllowed(0x424); +SetRegisterAllowed(0x428); +SetRegisterAllowed(0x42C); +SetRegisterAllowed(0x430); +SetRegisterAllowed(0x434); +SetRegisterAllowed(0x438); +SetRegisterAllowed(0x43C); +SetRegisterAllowed(0x440); +SetRegisterAllowed(0x444); +SetRegisterAllowed(0x448); +SetRegisterAllowed(0x44C); +SetRegisterAllowed(0x450); +SetRegisterAllowed(0x454); +SetRegisterAllowed(0x458); +SetRegisterAllowed(0x45C); +SetRegisterAllowed(0x460); +SetRegisterAllowed(0x464); +SetRegisterAllowed(0x468); +SetRegisterAllowed(0x46C); +SetRegisterAllowed(0x470); +SetRegisterAllowed(0x474); +SetRegisterAllowed(0x478); +SetRegisterAllowed(0x47C); +SetRegisterAllowed(0x480); +SetRegisterAllowed(0x484); +SetRegisterAllowed(0x488); +SetRegisterAllowed(0x48C); +SetRegisterAllowed(0x490); +SetRegisterAllowed(0x494); +SetRegisterAllowed(0x498); +SetRegisterAllowed(0x49C); +SetRegisterAllowed(0x4A0); +SetRegisterAllowed(0x4A4); +SetRegisterAllowed(0x4A8); +SetRegisterAllowed(0x4AC); +SetRegisterAllowed(0x4B0); +SetRegisterAllowed(0x4B4); +SetRegisterAllowed(0x4B8); +SetRegisterAllowed(0x4BC); +SetRegisterAllowed(0x4C0); +SetRegisterAllowed(0x4C4); +SetRegisterAllowed(0x4C8); +SetRegisterAllowed(0x4CC); +SetRegisterAllowed(0x4D0); +SetRegisterAllowed(0x4D4); +SetRegisterAllowed(0x4D8); +SetRegisterAllowed(0x4DC); +SetRegisterAllowed(0x4E0); +SetRegisterAllowed(0x4E4); +SetRegisterAllowed(0x4E8); +SetRegisterAllowed(0x4EC); +SetRegisterAllowed(0x4F0); +SetRegisterAllowed(0x4F4); +SetRegisterAllowed(0x4F8); +SetRegisterAllowed(0x4FC); +SetRegisterAllowed(0x500); +SetRegisterAllowed(0x504); +SetRegisterAllowed(0x508); +SetRegisterAllowed(0x50C); +SetRegisterAllowed(0x510); +SetRegisterAllowed(0x514); +SetRegisterAllowed(0x518); +SetRegisterAllowed(0x51C); +SetRegisterAllowed(0x520); +SetRegisterAllowed(0x524); +SetRegisterAllowed(0x528); +SetRegisterAllowed(0x52C); +SetRegisterAllowed(0x530); +SetRegisterAllowed(0x534); +SetRegisterAllowed(0x538); +SetRegisterAllowed(0x53C); +SetRegisterAllowed(0x540); +SetRegisterAllowed(0x544); +SetRegisterAllowed(0x548); +SetRegisterAllowed(0x54C); +SetRegisterAllowed(0x550); +SetRegisterAllowed(0x554); +SetRegisterAllowed(0x558); +SetRegisterAllowed(0x55C); +SetRegisterAllowed(0x560); +SetRegisterAllowed(0x564); +SetRegisterAllowed(0x568); +SetRegisterAllowed(0x56C); +SetRegisterAllowed(0x570); +SetRegisterAllowed(0x574); +SetRegisterAllowed(0x578); +SetRegisterAllowed(0x57C); +SetRegisterAllowed(0x580); +SetRegisterAllowed(0x584); +SetRegisterAllowed(0x588); +SetRegisterAllowed(0x58C); +SetRegisterAllowed(0x590); +SetRegisterAllowed(0x594); +SetRegisterAllowed(0x598); +SetRegisterAllowed(0x59C); +SetRegisterAllowed(0x5A0); +SetRegisterAllowed(0x5A4); +SetRegisterAllowed(0x5A8); +SetRegisterAllowed(0x5AC); +SetRegisterAllowed(0x5B0); +SetRegisterAllowed(0x5B4); +SetRegisterAllowed(0x5B8); +SetRegisterAllowed(0x5BC); +SetRegisterAllowed(0x5C0); +SetRegisterAllowed(0x5C4); +SetRegisterAllowed(0x5C8); +SetRegisterAllowed(0x5CC); +SetRegisterAllowed(0x5D0); +SetRegisterAllowed(0x5D4); +SetRegisterAllowed(0x5D8); +SetRegisterAllowed(0x5DC); +SetRegisterAllowed(0x5E0); +SetRegisterAllowed(0x5E4); +SetRegisterAllowed(0x5E8); +SetRegisterAllowed(0x5EC); +SetRegisterAllowed(0x5F0); +SetRegisterAllowed(0x5F4); +SetRegisterAllowed(0x5F8); +SetRegisterAllowed(0x5FC); +SetRegisterAllowed(0x600); +SetRegisterAllowed(0x604); +SetRegisterAllowed(0x608); +SetRegisterAllowed(0x60C); +SetRegisterAllowed(0x610); +SetRegisterAllowed(0x614); +SetRegisterAllowed(0x618); +SetRegisterAllowed(0x61C); +SetRegisterAllowed(0x620); +SetRegisterAllowed(0x624); +SetRegisterAllowed(0x628); +SetRegisterAllowed(0x62C); +SetRegisterAllowed(0x630); +SetRegisterAllowed(0x634); +SetRegisterAllowed(0x638); +SetRegisterAllowed(0x63C); +SetRegisterAllowed(0x640); +SetRegisterAllowed(0x644); +SetRegisterAllowed(0x648); +SetRegisterAllowed(0x64C); +SetRegisterAllowed(0x650); +SetRegisterAllowed(0x654); +SetRegisterAllowed(0x658); +SetRegisterAllowed(0x65C); +SetRegisterAllowed(0x660); +SetRegisterAllowed(0x664); +SetRegisterAllowed(0x668); +SetRegisterAllowed(0x66C); +SetRegisterAllowed(0x670); +SetRegisterAllowed(0x674); +SetRegisterAllowed(0x678); +SetRegisterAllowed(0x67C); +SetRegisterAllowed(0x680); +SetRegisterAllowed(0x684); +SetRegisterAllowed(0x688); +SetRegisterAllowed(0x68C); +SetRegisterAllowed(0x690); +SetRegisterAllowed(0x694); +SetRegisterAllowed(0x698); +SetRegisterAllowed(0x69C); +SetRegisterAllowed(0x6A0); +SetRegisterAllowed(0x6A4); +SetRegisterAllowed(0x6A8); +SetRegisterAllowed(0x6AC); +SetRegisterAllowed(0x6B0); +SetRegisterAllowed(0x6B4); +SetRegisterAllowed(0x6B8); +SetRegisterAllowed(0x6BC); +SetRegisterAllowed(0x6C0); +SetRegisterAllowed(0x6C4); +SetRegisterAllowed(0x6C8); +SetRegisterAllowed(0x6CC); +SetRegisterAllowed(0x6D0); +SetRegisterAllowed(0x6D4); +SetRegisterAllowed(0x6D8); +SetRegisterAllowed(0x6DC); +SetRegisterAllowed(0x6E0); +SetRegisterAllowed(0x6E4); +SetRegisterAllowed(0x6E8); +SetRegisterAllowed(0x6EC); +SetRegisterAllowed(0x6F0); +SetRegisterAllowed(0x6F4); +SetRegisterAllowed(0x6F8); +SetRegisterAllowed(0x6FC); +SetRegisterAllowed(0x700); +SetRegisterAllowed(0x704); +SetRegisterAllowed(0x708); +SetRegisterAllowed(0x70C); +SetRegisterAllowed(0x710); +SetRegisterAllowed(0x714); +SetRegisterAllowed(0x718); +SetRegisterAllowed(0x71C); +SetRegisterAllowed(0x720); +SetRegisterAllowed(0x724); +SetRegisterAllowed(0x728); +SetRegisterAllowed(0x72C); +SetRegisterAllowed(0x730); +SetRegisterAllowed(0x734); +SetRegisterAllowed(0x738); +SetRegisterAllowed(0x73C); +SetRegisterAllowed(0x740); +SetRegisterAllowed(0x744); +SetRegisterAllowed(0x748); +SetRegisterAllowed(0x74C); +SetRegisterAllowed(0x750); +SetRegisterAllowed(0x754); +SetRegisterAllowed(0x758); +SetRegisterAllowed(0x75C); +SetRegisterAllowed(0x760); +SetRegisterAllowed(0x764); +SetRegisterAllowed(0x768); +SetRegisterAllowed(0x76C); +SetRegisterAllowed(0x770); +SetRegisterAllowed(0x774); +SetRegisterAllowed(0x778); +SetRegisterAllowed(0x77C); +SetRegisterAllowed(0x780); +SetRegisterAllowed(0x784); +SetRegisterAllowed(0x788); +SetRegisterAllowed(0x78C); +SetRegisterAllowed(0x790); +SetRegisterAllowed(0x794); +SetRegisterAllowed(0x798); +SetRegisterAllowed(0x79C); +SetRegisterAllowed(0x7A0); +SetRegisterAllowed(0x7A4); +SetRegisterAllowed(0x7A8); +SetRegisterAllowed(0x7AC); +SetRegisterAllowed(0x7B0); +SetRegisterAllowed(0x7B4); +SetRegisterAllowed(0x7B8); +SetRegisterAllowed(0x7BC); +SetRegisterAllowed(0x7C0); +SetRegisterAllowed(0x7C4); +SetRegisterAllowed(0x7C8); +SetRegisterAllowed(0x7CC); +SetRegisterAllowed(0x7D0); +SetRegisterAllowed(0x7D4); +SetRegisterAllowed(0x7D8); +SetRegisterAllowed(0x7DC); +SetRegisterAllowed(0x7E0); +SetRegisterAllowed(0x7E4); +SetRegisterAllowed(0x7E8); +SetRegisterAllowed(0x7EC); +SetRegisterAllowed(0x7F0); +SetRegisterAllowed(0x7F4); +SetRegisterAllowed(0x7F8); +SetRegisterAllowed(0x7FC); +SetRegisterAllowed(0x800); +SetRegisterAllowed(0x804); +SetRegisterAllowed(0x808); +SetRegisterAllowed(0x80C); +SetRegisterAllowed(0x810); +SetRegisterAllowed(0x814); +SetRegisterAllowed(0x818); +SetRegisterAllowed(0x81C); +SetRegisterAllowed(0x820); +SetRegisterAllowed(0x824); +SetRegisterAllowed(0x828); +SetRegisterAllowed(0x82C); +SetRegisterAllowed(0x830); +SetRegisterAllowed(0x834); +SetRegisterAllowed(0x838); +SetRegisterAllowed(0x83C); +SetRegisterAllowed(0x840); +SetRegisterAllowed(0x844); +SetRegisterAllowed(0x848); +SetRegisterAllowed(0x84C); +SetRegisterAllowed(0x850); +SetRegisterAllowed(0x854); +SetRegisterAllowed(0x858); +SetRegisterAllowed(0x85C); +SetRegisterAllowed(0x860); +SetRegisterAllowed(0x864); +SetRegisterAllowed(0x868); +SetRegisterAllowed(0x86C); +SetRegisterAllowed(0x870); +SetRegisterAllowed(0x874); +SetRegisterAllowed(0x878); +SetRegisterAllowed(0x87C); +SetRegisterAllowed(0x880); +SetRegisterAllowed(0x884); +SetRegisterAllowed(0x888); +SetRegisterAllowed(0x88C); +SetRegisterAllowed(0x890); +SetRegisterAllowed(0x894); +SetRegisterAllowed(0x898); +SetRegisterAllowed(0x89C); +SetRegisterAllowed(0x8A0); +SetRegisterAllowed(0x8A4); +SetRegisterAllowed(0x8A8); +SetRegisterAllowed(0x8AC); +SetRegisterAllowed(0x8B0); +SetRegisterAllowed(0x8B4); +SetRegisterAllowed(0x8B8); +SetRegisterAllowed(0x8BC); +SetRegisterAllowed(0x8C0); +SetRegisterAllowed(0x8C4); +SetRegisterAllowed(0x8C8); +SetRegisterAllowed(0x8CC); +SetRegisterAllowed(0x8D0); +SetRegisterAllowed(0x8D4); +SetRegisterAllowed(0x8D8); +SetRegisterAllowed(0x8DC); +SetRegisterAllowed(0x8E0); +SetRegisterAllowed(0x8E4); +SetRegisterAllowed(0x8E8); +SetRegisterAllowed(0x8EC); +SetRegisterAllowed(0x8F0); +SetRegisterAllowed(0x8F4); +SetRegisterAllowed(0x8F8); +SetRegisterAllowed(0x8FC); +SetRegisterAllowed(0x900); +SetRegisterAllowed(0x904); +SetRegisterAllowed(0x908); +SetRegisterAllowed(0x90C); +SetRegisterAllowed(0x910); +SetRegisterAllowed(0x914); +SetRegisterAllowed(0x918); +SetRegisterAllowed(0x91C); +SetRegisterAllowed(0x920); +SetRegisterAllowed(0x924); +SetRegisterAllowed(0x928); +SetRegisterAllowed(0x92C); +SetRegisterAllowed(0x930); +SetRegisterAllowed(0x934); +SetRegisterAllowed(0x938); +SetRegisterAllowed(0x93C); +SetRegisterAllowed(0x940); +SetRegisterAllowed(0x944); +SetRegisterAllowed(0x948); +SetRegisterAllowed(0x94C); +SetRegisterAllowed(0x950); +SetRegisterAllowed(0x954); +SetRegisterAllowed(0x958); +SetRegisterAllowed(0x95C); +SetRegisterAllowed(0x960); +SetRegisterAllowed(0x964); +SetRegisterAllowed(0x968); +SetRegisterAllowed(0x96C); +SetRegisterAllowed(0x970); +SetRegisterAllowed(0x974); +SetRegisterAllowed(0x978); +SetRegisterAllowed(0x97C); +SetRegisterAllowed(0x980); +SetRegisterAllowed(0x984); +SetRegisterAllowed(0x988); +SetRegisterAllowed(0x98C); +SetRegisterAllowed(0x990); +SetRegisterAllowed(0x994); +SetRegisterAllowed(0x998); +SetRegisterAllowed(0x99C); +SetRegisterAllowed(0x9A0); +SetRegisterAllowed(0x9A4); +SetRegisterAllowed(0x9A8); +SetRegisterAllowed(0x9AC); +SetRegisterAllowed(0x9B0); +SetRegisterAllowed(0x9B4); +SetRegisterAllowed(0x9B8); +SetRegisterAllowed(0x9BC); +SetRegisterAllowed(0x9C0); +SetRegisterAllowed(0x9C4); +SetRegisterAllowed(0x9C8); +SetRegisterAllowed(0x9CC); +SetRegisterAllowed(0x9D0); +SetRegisterAllowed(0x9D4); +SetRegisterAllowed(0x9D8); +SetRegisterAllowed(0x9DC); +SetRegisterAllowed(0x9E0); +SetRegisterAllowed(0x9E4); +SetRegisterAllowed(0x9E8); +SetRegisterAllowed(0x9EC); +SetRegisterAllowed(0x9F0); +SetRegisterAllowed(0x9F4); +SetRegisterAllowed(0x9F8); +SetRegisterAllowed(0x9FC); +SetRegisterAllowed(0xA00); +SetRegisterAllowed(0xA04); +SetRegisterAllowed(0xA08); +SetRegisterAllowed(0xA0C); +SetRegisterAllowed(0xA10); +SetRegisterAllowed(0xA14); +SetRegisterAllowed(0xA18); +SetRegisterAllowed(0xA1C); +SetRegisterAllowed(0xA20); +SetRegisterAllowed(0xA24); +SetRegisterAllowed(0xA28); +SetRegisterAllowed(0xA2C); +SetRegisterAllowed(0xA30); +SetRegisterAllowed(0xA34); +SetRegisterAllowed(0xA38); +SetRegisterAllowed(0xA3C); +SetRegisterAllowed(0xA40); +SetRegisterAllowed(0xA44); +SetRegisterAllowed(0xA48); +SetRegisterAllowed(0xA4C); +SetRegisterAllowed(0xA50); +SetRegisterAllowed(0xA54); +SetRegisterAllowed(0xA58); +SetRegisterAllowed(0xA5C); +SetRegisterAllowed(0xA60); +SetRegisterAllowed(0xA64); +SetRegisterAllowed(0xA68); +SetRegisterAllowed(0xA6C); +SetRegisterAllowed(0xA70); +SetRegisterAllowed(0xA74); +SetRegisterAllowed(0xA78); +SetRegisterAllowed(0xA7C); +SetRegisterAllowed(0xA80); +SetRegisterAllowed(0xA84); +SetRegisterAllowed(0xA88); +SetRegisterAllowed(0xA8C); +SetRegisterAllowed(0xA90); +SetRegisterAllowed(0xA94); +SetRegisterAllowed(0xA98); +SetRegisterAllowed(0xA9C); +SetRegisterAllowed(0xAA0); +SetRegisterAllowed(0xAA4); +SetRegisterAllowed(0xAA8); +SetRegisterAllowed(0xAAC); +SetRegisterAllowed(0xAB0); +SetRegisterAllowed(0xAB4); +SetRegisterAllowed(0xAB8); +SetRegisterAllowed(0xABC); +SetRegisterAllowed(0xAC0); +SetRegisterAllowed(0xAC4); +SetRegisterAllowed(0xAC8); +SetRegisterAllowed(0xACC); +SetRegisterAllowed(0xAD0); +SetRegisterAllowed(0xAD4); +SetRegisterAllowed(0xAD8); +SetRegisterAllowed(0xADC); +SetRegisterAllowed(0xAE0); +SetRegisterAllowed(0xAE4); +SetRegisterAllowed(0xAE8); +SetRegisterAllowed(0xAEC); +SetRegisterAllowed(0xAF0); +SetRegisterAllowed(0xAF4); +SetRegisterAllowed(0xAF8); +SetRegisterAllowed(0xAFC); +SetRegisterAllowed(0xB00); +SetRegisterAllowed(0xB04); +SetRegisterAllowed(0xB08); +SetRegisterAllowed(0xB0C); +SetRegisterAllowed(0xB10); +SetRegisterAllowed(0xB14); +SetRegisterAllowed(0xB18); +SetRegisterAllowed(0xB1C); +SetRegisterAllowed(0xB20); +SetRegisterAllowed(0xB24); +SetRegisterAllowed(0xB28); +SetRegisterAllowed(0xB2C); +SetRegisterAllowed(0xB30); +SetRegisterAllowed(0xB34); +SetRegisterAllowed(0xB38); +SetRegisterAllowed(0xB3C); +SetRegisterAllowed(0xB40); +SetRegisterAllowed(0xB44); +SetRegisterAllowed(0xB48); +SetRegisterAllowed(0xB4C); +SetRegisterAllowed(0xB50); +SetRegisterAllowed(0xB54); +SetRegisterAllowed(0xB58); +SetRegisterAllowed(0xB5C); +SetRegisterAllowed(0xB60); +SetRegisterAllowed(0xB64); +SetRegisterAllowed(0xB68); +SetRegisterAllowed(0xB6C); +SetRegisterAllowed(0xB70); +SetRegisterAllowed(0xB74); +SetRegisterAllowed(0xB78); +SetRegisterAllowed(0xB7C); +SetRegisterAllowed(0xB80); +SetRegisterAllowed(0xB84); +SetRegisterAllowed(0xB88); +SetRegisterAllowed(0xB8C); +SetRegisterAllowed(0xB90); +SetRegisterAllowed(0xB94); +SetRegisterAllowed(0xB98); +SetRegisterAllowed(0xB9C); +SetRegisterAllowed(0xBA0); +SetRegisterAllowed(0xBA4); +SetRegisterAllowed(0xBA8); +SetRegisterAllowed(0xBAC); +SetRegisterAllowed(0xBB0); +SetRegisterAllowed(0xBB4); +SetRegisterAllowed(0xBB8); +SetRegisterAllowed(0xBBC); +SetRegisterAllowed(0xBC0); +SetRegisterAllowed(0xBC4); +SetRegisterAllowed(0xBC8); +SetRegisterAllowed(0xBCC); +SetRegisterAllowed(0xBD0); +SetRegisterAllowed(0xBD4); +SetRegisterAllowed(0xBD8); +SetRegisterAllowed(0xBDC); +SetRegisterAllowed(0xBE0); +SetRegisterAllowed(0xBE4); +SetRegisterAllowed(0xBE8); +SetRegisterAllowed(0xBEC); +SetRegisterAllowed(0xBF0); +SetRegisterAllowed(0xBF4); +SetRegisterAllowed(0xBF8); +SetRegisterAllowed(0xBFC); +SetRegisterAllowed(0xC00); +SetRegisterAllowed(0xC04); +SetRegisterAllowed(0xC08); +SetRegisterAllowed(0xC0C); +SetRegisterAllowed(0xC10); +SetRegisterAllowed(0xC14); +SetRegisterAllowed(0xC18); +SetRegisterAllowed(0xC1C); +SetRegisterAllowed(0xC20); +SetRegisterAllowed(0xC24); +SetRegisterAllowed(0xC28); +SetRegisterAllowed(0xC2C); +SetRegisterAllowed(0xC30); +SetRegisterAllowed(0xC34); +SetRegisterAllowed(0xC38); +SetRegisterAllowed(0xC3C); +SetRegisterAllowed(0xC40); +SetRegisterAllowed(0xC44); +SetRegisterAllowed(0xC48); +SetRegisterAllowed(0xC4C); +SetRegisterAllowed(0xC50); +SetRegisterAllowed(0xC54); +SetRegisterAllowed(0xC58); +SetRegisterAllowed(0xC5C); +SetRegisterAllowed(0xC60); +SetRegisterAllowed(0xC64); +SetRegisterAllowed(0xC68); +SetRegisterAllowed(0xC6C); +SetRegisterAllowed(0xC70); +SetRegisterAllowed(0xC74); +SetRegisterAllowed(0xC78); +SetRegisterAllowed(0xC7C); +SetRegisterAllowed(0xC80); +SetRegisterAllowed(0xC84); +SetRegisterAllowed(0xC88); +SetRegisterAllowed(0xC8C); +SetRegisterAllowed(0xC90); +SetRegisterAllowed(0xC94); +SetRegisterAllowed(0xC98); +SetRegisterAllowed(0xC9C); +SetRegisterAllowed(0xCA0); +SetRegisterAllowed(0xCA4); +SetRegisterAllowed(0xCA8); +SetRegisterAllowed(0xCAC); +SetRegisterAllowed(0xCB0); +SetRegisterAllowed(0xCB4); +SetRegisterAllowed(0xCB8); +SetRegisterAllowed(0xCBC); +SetRegisterAllowed(0xCC0); +SetRegisterAllowed(0xCC4); +SetRegisterAllowed(0xCC8); +SetRegisterAllowed(0xCCC); +SetRegisterAllowed(0xCD0); +SetRegisterAllowed(0xCD4); +SetRegisterAllowed(0xCD8); +SetRegisterAllowed(0xCDC); +SetRegisterAllowed(0xCE0); +SetRegisterAllowed(0xCE4); +SetRegisterAllowed(0xCE8); +SetRegisterAllowed(0xCEC); +SetRegisterAllowed(0xCF0); +SetRegisterAllowed(0xCF4); +SetRegisterAllowed(0xCF8); +SetRegisterAllowed(0xCFC); +SetRegisterAllowed(0xD00); +SetRegisterAllowed(0xD04); +SetRegisterAllowed(0xD08); +SetRegisterAllowed(0xD0C); +SetRegisterAllowed(0xD10); +SetRegisterAllowed(0xD14); +SetRegisterAllowed(0xD18); +SetRegisterAllowed(0xD1C); +SetRegisterAllowed(0xD20); +SetRegisterAllowed(0xD24); +SetRegisterAllowed(0xD28); +SetRegisterAllowed(0xD2C); +SetRegisterAllowed(0xD30); +SetRegisterAllowed(0xD34); +SetRegisterAllowed(0xD38); +SetRegisterAllowed(0xD3C); +SetRegisterAllowed(0xD40); +SetRegisterAllowed(0xD44); +SetRegisterAllowed(0xD48); +SetRegisterAllowed(0xD4C); +SetRegisterAllowed(0xD50); +SetRegisterAllowed(0xD54); +SetRegisterAllowed(0xD58); +SetRegisterAllowed(0xD5C); +SetRegisterAllowed(0xD60); +SetRegisterAllowed(0xD64); +SetRegisterAllowed(0xD68); +SetRegisterAllowed(0xD6C); +SetRegisterAllowed(0xD70); +SetRegisterAllowed(0xD74); +SetRegisterAllowed(0xD78); +SetRegisterAllowed(0xD7C); +SetRegisterAllowed(0xD80); +SetRegisterAllowed(0xD84); +SetRegisterAllowed(0xD88); +SetRegisterAllowed(0xD8C); +SetRegisterAllowed(0xD90); +SetRegisterAllowed(0xD94); +SetRegisterAllowed(0xD98); +SetRegisterAllowed(0xD9C); +SetRegisterAllowed(0xDA0); +SetRegisterAllowed(0xDA4); +SetRegisterAllowed(0xDA8); +SetRegisterAllowed(0xDAC); +SetRegisterAllowed(0xDB0); +SetRegisterAllowed(0xDB4); +SetRegisterAllowed(0xDB8); +SetRegisterAllowed(0xDBC); +SetRegisterAllowed(0xDC0); +SetRegisterAllowed(0xDC4); +SetRegisterAllowed(0xDC8); +SetRegisterAllowed(0xDCC); +SetRegisterAllowed(0xDD0); +SetRegisterAllowed(0xDD4); +SetRegisterAllowed(0xDD8); +SetRegisterAllowed(0xDDC); +SetRegisterAllowed(0xDE0); +SetRegisterAllowed(0xDE4); +SetRegisterAllowed(0xDE8); +SetRegisterAllowed(0xDEC); +SetRegisterAllowed(0xDF0); +SetRegisterAllowed(0xDF4); +SetRegisterAllowed(0xDF8); +SetRegisterAllowed(0xDFC); +SetRegisterAllowed(0xE00); +SetRegisterAllowed(0xE04); +SetRegisterAllowed(0xE08); +SetRegisterAllowed(0xE0C); +SetRegisterAllowed(0xE10); +SetRegisterAllowed(0xE14); +SetRegisterAllowed(0xE18); +SetRegisterAllowed(0xE1C); +SetRegisterAllowed(0xE20); +SetRegisterAllowed(0xE24); +SetRegisterAllowed(0xE28); +SetRegisterAllowed(0xE2C); +SetRegisterAllowed(0xE30); +SetRegisterAllowed(0xE34); +SetRegisterAllowed(0xE38); +SetRegisterAllowed(0xE3C); +SetRegisterAllowed(0xE40); +SetRegisterAllowed(0xE44); +SetRegisterAllowed(0xE48); +SetRegisterAllowed(0xE4C); +SetRegisterAllowed(0xE50); +SetRegisterAllowed(0xE54); +SetRegisterAllowed(0xE58); +SetRegisterAllowed(0xE5C); +SetRegisterAllowed(0xE60); +SetRegisterAllowed(0xE64); +SetRegisterAllowed(0xE68); +SetRegisterAllowed(0xE6C); +SetRegisterAllowed(0xE70); +SetRegisterAllowed(0xE74); +SetRegisterAllowed(0xE78); +SetRegisterAllowed(0xE7C); +SetRegisterAllowed(0xE80); +SetRegisterAllowed(0xE84); +SetRegisterAllowed(0xE88); +SetRegisterAllowed(0xE8C); +SetRegisterAllowed(0xE90); +SetRegisterAllowed(0xE94); +SetRegisterAllowed(0xE98); +SetRegisterAllowed(0xE9C); +SetRegisterAllowed(0xEA0); +SetRegisterAllowed(0xEA4); +SetRegisterAllowed(0xEA8); +SetRegisterAllowed(0xEAC); +SetRegisterAllowed(0xEB0); +SetRegisterAllowed(0xEB4); +SetRegisterAllowed(0xEB8); +SetRegisterAllowed(0xEBC); +SetRegisterAllowed(0xEC0); +SetRegisterAllowed(0xEC4); +SetRegisterAllowed(0xEC8); +SetRegisterAllowed(0xECC); +SetRegisterAllowed(0xED0); +SetRegisterAllowed(0xED4); +SetRegisterAllowed(0xED8); diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_mc01_access_table_data.inc b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_mc01_access_table_data.inc new file mode 100644 index 00000000..49f3e5cb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_mc01_access_table_data.inc @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +SetRegisterAllowed(MC_STAT_CONTROL); /* 0x100 */ +SetRegisterAllowed(MC_STAT_EMC_CLOCK_LIMIT); /* 0x108 */ +SetRegisterAllowed(MC_STAT_EMC_CLOCK_LIMIT_MSBS); /* 0x10C */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET0_ADR_LIMIT_LO); /* 0x118 */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET0_ADR_LIMIT_HI); /* 0x11C */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET0_SPARE); /* 0x124 */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET0_CLIENT_0); /* 0x128 */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET0_CLIENT_1); /* 0x12C */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET0_CLIENT_2); /* 0x130 */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET0_CLIENT_3); /* 0x134 */ +SetRegisterAllowed(MC_STAT_EMC_SET0_COUNT); /* 0x138 */ +SetRegisterAllowed(MC_STAT_EMC_SET0_COUNT_MSBS); /* 0x13C */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET1_ADR_LIMIT_LO); /* 0x158 */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET1_ADR_LIMIT_HI); /* 0x15C */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET1_SPARE); /* 0x164 */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET1_CLIENT_0); /* 0x168 */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET1_CLIENT_1); /* 0x16C */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET1_CLIENT_2); /* 0x170 */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET1_CLIENT_3); /* 0x174 */ +SetRegisterAllowed(MC_STAT_EMC_SET1_COUNT); /* 0x178 */ +SetRegisterAllowed(MC_STAT_EMC_SET1_COUNT_MSBS); /* 0x17C */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET0_ADR_LIMIT_UPPER); /* 0xA20 */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET1_ADR_LIMIT_UPPER); /* 0xA24 */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET0_CLIENT_4); /* 0xB88 */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET1_CLIENT_4); /* 0xB8C */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET0_CLIENT_5); /* 0xBC4 */ +SetRegisterAllowed(MC_STAT_EMC_FILTER_SET1_CLIENT_5); /* 0xBC8 */ \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_mc_access_table_data.inc b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_mc_access_table_data.inc new file mode 100644 index 00000000..9eba3f57 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_mc_access_table_data.inc @@ -0,0 +1,195 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +SetRegisterAllowed(MC_INTSTATUS); /* 0x000 */ +SetRegisterAllowed(MC_INTMASK); /* 0x004 */ +SetRegisterAllowed(MC_ERR_STATUS); /* 0x008 */ +SetRegisterAllowed(MC_ERR_ADR); /* 0x00C */ +SetRegisterAllowed(MC_SMMU_CONFIG); /* 0x010 */ +SetRegisterAllowed(MC_SMMU_PTB_ASID); /* 0x01C */ +SetRegisterAllowed(MC_SMMU_PTB_DATA); /* 0x020 */ +SetRegisterAllowed(MC_SMMU_TLB_FLUSH); /* 0x030 */ +SetRegisterAllowed(MC_SMMU_PTC_FLUSH_0); /* 0x034 */ +SetRegisterAllowed(MC_EMEM_CFG); /* 0x050 */ +SetRegisterAllowed(MC_EMEM_ADR_CFG); /* 0x054 */ +SetRegisterAllowed(MC_EMEM_ARB_CFG); /* 0x090 */ +SetRegisterAllowed(MC_EMEM_ARB_OUTSTANDING_REQ); /* 0x094 */ +SetRegisterAllowed(MC_EMEM_ARB_TIMING_RCD); /* 0x098 */ +SetRegisterAllowed(MC_EMEM_ARB_TIMING_RP); /* 0x09C */ +SetRegisterAllowed(MC_EMEM_ARB_TIMING_RC); /* 0x0A0 */ +SetRegisterAllowed(MC_EMEM_ARB_TIMING_RAS); /* 0x0A4 */ +SetRegisterAllowed(MC_EMEM_ARB_TIMING_FAW); /* 0x0A8 */ +SetRegisterAllowed(MC_EMEM_ARB_TIMING_RRD); /* 0x0AC */ +SetRegisterAllowed(MC_EMEM_ARB_TIMING_RAP2PRE); /* 0x0B0 */ +SetRegisterAllowed(MC_EMEM_ARB_TIMING_WAP2PRE); /* 0x0B4 */ +SetRegisterAllowed(MC_EMEM_ARB_TIMING_R2R); /* 0x0B8 */ +SetRegisterAllowed(MC_EMEM_ARB_TIMING_W2W); /* 0x0BC */ +SetRegisterAllowed(MC_EMEM_ARB_TIMING_R2W); /* 0x0C0 */ +SetRegisterAllowed(MC_EMEM_ARB_TIMING_W2R); /* 0x0C4 */ +SetRegisterAllowed(MC_EMEM_ARB_MISC2); /* 0x0C8 */ +SetRegisterAllowed(MC_EMEM_ARB_DA_TURNS); /* 0x0D0 */ +SetRegisterAllowed(MC_EMEM_ARB_DA_COVERS); /* 0x0D4 */ +SetRegisterAllowed(MC_EMEM_ARB_MISC0); /* 0x0D8 */ +SetRegisterAllowed(MC_EMEM_ARB_MISC1); /* 0x0DC */ +SetRegisterAllowed(MC_EMEM_ARB_RING1_THROTTLE); /* 0x0E0 */ +SetRegisterAllowed(MC_CLIENT_HOTRESET_CTRL); /* 0x200 */ +SetRegisterAllowed(MC_CLIENT_HOTRESET_STATUS); /* 0x204 */ +SetRegisterAllowed(MC_SMMU_AFI_ASID); /* 0x238 */ +SetRegisterAllowed(MC_SMMU_DC_ASID); /* 0x240 */ +SetRegisterAllowed(MC_SMMU_DCB_ASID); /* 0x244 */ +SetRegisterAllowed(MC_SMMU_HC_ASID); /* 0x250 */ +SetRegisterAllowed(MC_SMMU_HDA_ASID); /* 0x254 */ +SetRegisterAllowed(MC_SMMU_ISP2_ASID); /* 0x258 */ +SetRegisterAllowed(MC_SMMU_MSENC_NVENC_ASID); /* 0x264 */ +SetRegisterAllowed(MC_SMMU_NV_ASID); /* 0x268 */ +SetRegisterAllowed(MC_SMMU_NV2_ASID); /* 0x26C */ +SetRegisterAllowed(MC_SMMU_PPCS_ASID); /* 0x270 */ +SetRegisterAllowed(MC_SMMU_SATA_ASID); /* 0x274 */ +SetRegisterAllowed(MC_SMMU_VI_ASID); /* 0x280 */ +SetRegisterAllowed(MC_SMMU_VIC_ASID); /* 0x284 */ +SetRegisterAllowed(MC_SMMU_XUSB_HOST_ASID); /* 0x288 */ +SetRegisterAllowed(MC_SMMU_XUSB_DEV_ASID); /* 0x28C */ +SetRegisterAllowed(MC_SMMU_TSEC_ASID); /* 0x294 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_AVPC_0); /* 0x2E4 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_DC_0); /* 0x2E8 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_DC_1); /* 0x2EC */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_DCB_0); /* 0x2F4 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_DCB_1); /* 0x2F8 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_HC_0); /* 0x310 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_HC_1); /* 0x314 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_MPCORE_0); /* 0x320 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_NVENC_0); /* 0x328 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_PPCS_0); /* 0x344 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_PPCS_1); /* 0x348 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_ISP2_0); /* 0x370 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_ISP2_1); /* 0x374 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_XUSB_0); /* 0x37C */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_XUSB_1); /* 0x380 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_TSEC_0); /* 0x390 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_VIC_0); /* 0x394 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_VI2_0); /* 0x398 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_GPU_0); /* 0x3AC */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_SDMMCA_0); /* 0x3B8 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_SDMMCAA_0); /* 0x3BC */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_SDMMC_0); /* 0x3C0 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_SDMMCAB_0); /* 0x3C4 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_NVDEC_0); /* 0x3D8 */ +SetRegisterAllowed(MC_LATENCY_ALLOWANCE_GPU2_0); /* 0x3E8 */ +SetRegisterAllowed(MC_DIS_PTSA_RATE); /* 0x41C */ +SetRegisterAllowed(MC_DIS_PTSA_MIN); /* 0x420 */ +SetRegisterAllowed(MC_DIS_PTSA_MAX); /* 0x424 */ +SetRegisterAllowed(MC_DISB_PTSA_RATE); /* 0x428 */ +SetRegisterAllowed(MC_DISB_PTSA_MIN); /* 0x42C */ +SetRegisterAllowed(MC_DISB_PTSA_MAX); /* 0x430 */ +SetRegisterAllowed(MC_VE_PTSA_RATE); /* 0x434 */ +SetRegisterAllowed(MC_VE_PTSA_MIN); /* 0x438 */ +SetRegisterAllowed(MC_VE_PTSA_MAX); /* 0x43C */ +SetRegisterAllowed(MC_MLL_MPCORER_PTSA_RATE); /* 0x44C */ +SetRegisterAllowed(MC_RING1_PTSA_RATE); /* 0x47C */ +SetRegisterAllowed(MC_RING1_PTSA_MIN); /* 0x480 */ +SetRegisterAllowed(MC_RING1_PTSA_MAX); /* 0x484 */ +SetRegisterAllowed(MC_PCX_PTSA_RATE); /* 0x4AC */ +SetRegisterAllowed(MC_PCX_PTSA_MIN); /* 0x4B0 */ +SetRegisterAllowed(MC_PCX_PTSA_MAX); /* 0x4B4 */ +SetRegisterAllowed(MC_MSE_PTSA_RATE); /* 0x4C4 */ +SetRegisterAllowed(MC_MSE_PTSA_MIN); /* 0x4C8 */ +SetRegisterAllowed(MC_MSE_PTSA_MAX); /* 0x4CC */ +SetRegisterAllowed(MC_AHB_PTSA_RATE); /* 0x4DC */ +SetRegisterAllowed(MC_AHB_PTSA_MIN); /* 0x4E0 */ +SetRegisterAllowed(MC_AHB_PTSA_MAX); /* 0x4E4 */ +SetRegisterAllowed(MC_APB_PTSA_RATE); /* 0x4E8 */ +SetRegisterAllowed(MC_APB_PTSA_MIN); /* 0x4EC */ +SetRegisterAllowed(MC_APB_PTSA_MAX); /* 0x4F0 */ +SetRegisterAllowed(MC_FTOP_PTSA_RATE); /* 0x50C */ +SetRegisterAllowed(MC_HOST_PTSA_RATE); /* 0x518 */ +SetRegisterAllowed(MC_HOST_PTSA_MIN); /* 0x51C */ +SetRegisterAllowed(MC_HOST_PTSA_MAX); /* 0x520 */ +SetRegisterAllowed(MC_USBX_PTSA_RATE); /* 0x524 */ +SetRegisterAllowed(MC_USBX_PTSA_MIN); /* 0x528 */ +SetRegisterAllowed(MC_USBX_PTSA_MAX); /* 0x52C */ +SetRegisterAllowed(MC_USBD_PTSA_RATE); /* 0x530 */ +SetRegisterAllowed(MC_USBD_PTSA_MIN); /* 0x534 */ +SetRegisterAllowed(MC_USBD_PTSA_MAX); /* 0x538 */ +SetRegisterAllowed(MC_GK_PTSA_RATE); /* 0x53C */ +SetRegisterAllowed(MC_GK_PTSA_MIN); /* 0x540 */ +SetRegisterAllowed(MC_GK_PTSA_MAX); /* 0x544 */ +SetRegisterAllowed(MC_AUD_PTSA_RATE); /* 0x548 */ +SetRegisterAllowed(MC_AUD_PTSA_MIN); /* 0x54C */ +SetRegisterAllowed(MC_AUD_PTSA_MAX); /* 0x550 */ +SetRegisterAllowed(MC_VICPC_PTSA_RATE); /* 0x554 */ +SetRegisterAllowed(MC_VICPC_PTSA_MIN); /* 0x558 */ +SetRegisterAllowed(MC_VICPC_PTSA_MAX); /* 0x55C */ +SetRegisterAllowed(MC_JPG_PTSA_RATE); /* 0x584 */ +SetRegisterAllowed(MC_JPG_PTSA_MIN); /* 0x588 */ +SetRegisterAllowed(MC_JPG_PTSA_MAX); /* 0x58C */ +SetRegisterAllowed(MC_GK2_PTSA_RATE); /* 0x610 */ +SetRegisterAllowed(MC_GK2_PTSA_MIN); /* 0x614 */ +SetRegisterAllowed(MC_GK2_PTSA_MAX); /* 0x618 */ +SetRegisterAllowed(MC_SDM_PTSA_RATE); /* 0x61C */ +SetRegisterAllowed(MC_SDM_PTSA_MIN); /* 0x620 */ +SetRegisterAllowed(MC_SDM_PTSA_MAX); /* 0x624 */ +SetRegisterAllowed(MC_HDAPC_PTSA_RATE); /* 0x628 */ +SetRegisterAllowed(MC_HDAPC_PTSA_MIN); /* 0x62C */ +SetRegisterAllowed(MC_HDAPC_PTSA_MAX); /* 0x630 */ +SetRegisterAllowed(MC_SEC_CARVEOUT_BOM); /* 0x670 */ +SetRegisterAllowed(MC_SEC_CARVEOUT_SIZE_MB); /* 0x674 */ +SetRegisterAllowed(MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0A); /* 0x690 */ +SetRegisterAllowed(MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0AB); /* 0x694 */ +SetRegisterAllowed(MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0B); /* 0x698 */ +SetRegisterAllowed(MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0BB); /* 0x69C */ +SetRegisterAllowed(MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0C); /* 0x6A0 */ +SetRegisterAllowed(MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0CB); /* 0x6A4 */ +SetRegisterAllowed(MC_EMEM_ARB_TIMING_RFCPB); /* 0x6C0 */ +SetRegisterAllowed(MC_EMEM_ARB_TIMING_CCDMW); /* 0x6C4 */ +SetRegisterAllowed(MC_EMEM_ARB_REFPB_HP_CTRL); /* 0x6F0 */ +SetRegisterAllowed(MC_EMEM_ARB_REFPB_BANK_CTRL); /* 0x6F4 */ +SetRegisterAllowed(MC_PTSA_GRANT_DECREMENT); /* 0x960 */ +SetRegisterAllowed(MC_CLIENT_HOTRESET_CTRL_1); /* 0x970 */ +SetRegisterAllowed(MC_CLIENT_HOTRESET_STATUS_1); /* 0x974 */ +SetRegisterAllowed(MC_SMMU_PTC_FLUSH_1); /* 0x9B8 */ +SetRegisterAllowed(MC_SMMU_DC1_ASID); /* 0xA88 */ +SetRegisterAllowed(MC_SMMU_SDMMC1A_ASID); /* 0xA94 */ +SetRegisterAllowed(MC_SMMU_SDMMC2A_ASID); /* 0xA98 */ +SetRegisterAllowed(MC_SMMU_SDMMC3A_ASID); /* 0xA9C */ +SetRegisterAllowed(MC_SMMU_SDMMC4A_ASID); /* 0xAA0 */ +SetRegisterAllowed(MC_SMMU_ISP2B_ASID); /* 0xAA4 */ +SetRegisterAllowed(MC_SMMU_GPU_ASID); /* 0xAA8 */ +SetRegisterAllowed(MC_SMMU_GPUB_ASID); /* 0xAAC */ +SetRegisterAllowed(MC_SMMU_PPCS2_ASID); /* 0xAB0 */ +SetRegisterAllowed(MC_SMMU_NVDEC_ASID); /* 0xAB4 */ +SetRegisterAllowed(MC_SMMU_APE_ASID); /* 0xAB8 */ +SetRegisterAllowed(MC_SMMU_SE_ASID); /* 0xABC */ +SetRegisterAllowed(MC_SMMU_NVJPG_ASID); /* 0xAC0 */ +SetRegisterAllowed(MC_SMMU_HC1_ASID); /* 0xAC4 */ +SetRegisterAllowed(MC_SMMU_SE1_ASID); /* 0xAC8 */ +SetRegisterAllowed(MC_SMMU_AXIAP_ASID); /* 0xACC */ +SetRegisterAllowed(MC_SMMU_ETR_ASID); /* 0xAD0 */ +SetRegisterAllowed(MC_SMMU_TSECB_ASID); /* 0xAD4 */ +SetRegisterAllowed(MC_SMMU_TSEC1_ASID); /* 0xAD8 */ +SetRegisterAllowed(MC_SMMU_TSECB1_ASID); /* 0xADC */ +SetRegisterAllowed(MC_SMMU_NVDEC1_ASID); /* 0xAE0 */ +SetRegisterAllowed(MC_EMEM_ARB_DHYST_CTRL); /* 0xBCC */ +SetRegisterAllowed(MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_0); /* 0xBD0 */ +SetRegisterAllowed(MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_1); /* 0xBD4 */ +SetRegisterAllowed(MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_2); /* 0xBD8 */ +SetRegisterAllowed(MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_3); /* 0xBDC */ +SetRegisterAllowed(MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_4); /* 0xBE0 */ +SetRegisterAllowed(MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_5); /* 0xBE4 */ +SetRegisterAllowed(MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_6); /* 0xBE8 */ +SetRegisterAllowed(MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_7); /* 0xBEC */ +SetRegisterAllowed(MC_ERR_GENERALIZED_CARVEOUT_STATUS); /* 0xC00 */ +SetRegisterAllowed(MC_SECURITY_CARVEOUT2_BOM); /* 0xC5C */ +SetRegisterAllowed(MC_SECURITY_CARVEOUT3_BOM); /* 0xCAC */ \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_pmc_access_table_data.inc b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_pmc_access_table_data.inc new file mode 100644 index 00000000..73471d6f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_pmc_access_table_data.inc @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +SetRegisterAllowed(APBDEV_PMC_CNTRL); /* 0x000 */ +SetRegisterAllowed(APBDEV_PMC_WAKE_MASK); /* 0x00C */ +SetRegisterAllowed(APBDEV_PMC_WAKE_LVL); /* 0x010 */ +SetRegisterAllowed(APBDEV_PMC_WAKE_STATUS); /* 0x014 */ +SetRegisterAllowed(APBDEV_PMC_DPD_PADS_ORIDE); /* 0x01C */ +SetRegisterAllowed(APBDEV_PMC_DPD_SAMPLE); /* 0x020 */ +SetRegisterAllowed(APBDEV_PMC_CLAMP_STATUS); /* 0x02C */ +SetRegisterAllowed(APBDEV_PMC_PWRGATE_TOGGLE); /* 0x030 */ +SetRegisterAllowed(APBDEV_PMC_REMOVE_CLAMPING_CMD ); /* 0x034 */ +SetRegisterAllowed(APBDEV_PMC_PWRGATE_STATUS); /* 0x038 */ +SetRegisterAllowed(APBDEV_PMC_PWRGOOD_TIMER); /* 0x03C */ +SetRegisterAllowed(APBDEV_PMC_BLINK_TIMER); /* 0x040 */ +SetRegisterAllowed(APBDEV_PMC_NO_IOPOWER); /* 0x044 */ +SetRegisterAllowed(APBDEV_PMC_PWR_DET); /* 0x048 */ +SetRegisterAllowed(APBDEV_PMC_AUTO_WAKE_LVL_MASK); /* 0x0DC */ +SetRegisterAllowed(APBDEV_PMC_WAKE_DELAY); /* 0x0E0 */ +SetRegisterAllowed(APBDEV_PMC_PWR_DET_VAL); /* 0x0E4 */ +SetRegisterAllowed(APBDEV_PMC_WAKE2_MASK); /* 0x160 */ +SetRegisterAllowed(APBDEV_PMC_WAKE2_LVL); /* 0x164 */ +SetRegisterAllowed(APBDEV_PMC_WAKE2_STATUS); /* 0x168 */ +SetRegisterAllowed(APBDEV_PMC_AUTO_WAKE2_LVL_MASK ); /* 0x170 */ +SetRegisterAllowed(APBDEV_PMC_CLK_OUT_CNTRL); /* 0x1A8 */ +SetRegisterAllowed(APBDEV_PMC_IO_DPD_REQ); /* 0x1B8 */ +SetRegisterAllowed(APBDEV_PMC_IO_DPD_STATUS); /* 0x1BC */ +SetRegisterAllowed(APBDEV_PMC_IO_DPD2_REQ); /* 0x1C0 */ +SetRegisterAllowed(APBDEV_PMC_IO_DPD2_STATUS); /* 0x1C4 */ +SetRegisterAllowed(APBDEV_PMC_SEL_DPD_TIM); /* 0x1C8 */ +SetRegisterAllowed(APBDEV_PMC_TSC_MULT); /* 0x2B4 */ +SetRegisterAllowed(APBDEV_PMC_GPU_RG_CNTRL); /* 0x2D4 */ +SetRegisterAllowed(APBDEV_PMC_CNTRL2); /* 0x440 */ +SetRegisterAllowed(APBDEV_PMC_WAKE_DEBOUNCE_EN); /* 0x4D8 */ \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_random_cache.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_random_cache.cpp new file mode 100644 index 00000000..710d30a9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_random_cache.cpp @@ -0,0 +1,96 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "secmon_smc_common.hpp" +#include "secmon_random_cache.hpp" + +namespace ams::secmon::smc { + + namespace { + + constinit int g_random_offset_low = 0; + constinit int g_random_offset_high = 0; + + void FillRandomCache(int offset, int size) { + /* Get the cache. */ + u8 * const random_cache_loc = GetRandomBytesCache() + offset; + + /* Flush the region we're about to fill to ensure consistency with the SE. */ + hw::FlushDataCache(random_cache_loc, size); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Generate random bytes. */ + se::GenerateRandomBytes(random_cache_loc, size); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Flush to ensure the CPU sees consistent data for the region. */ + hw::FlushDataCache(random_cache_loc, size); + hw::DataSynchronizationBarrierInnerShareable(); + } + + } + + void FillRandomCache() { + /* Fill the cache. */ + FillRandomCache(0, GetRandomBytesCacheSize()); + + /* Set the extents. */ + g_random_offset_low = 0; + g_random_offset_high = GetRandomBytesCacheSize() - 1; + } + + void RefillRandomCache() { + /* Check that we need to do any refilling. */ + if (const int used_start = (g_random_offset_high + 1) % GetRandomBytesCacheSize(); used_start != g_random_offset_low) { + if (used_start < g_random_offset_low) { + /* The region we need to fill is after used_start but before g_random_offset_low. */ + const auto size = g_random_offset_low - used_start; + FillRandomCache(used_start, size); + g_random_offset_high += size; + } else { + /* We need to fill the space from high to the end and from low to start. */ + const int high_size = GetRandomBytesCacheSize() - used_start; + if (high_size > 0) { + FillRandomCache(used_start, high_size); + g_random_offset_high += high_size; + } + + const int low_size = g_random_offset_low; + if (low_size > 0) { + FillRandomCache(0, low_size); + g_random_offset_high += low_size; + } + + } + + g_random_offset_high %= GetRandomBytesCacheSize(); + } + } + + void GetRandomFromCache(void *dst, size_t size) { + /* Copy out the requested size. */ + std::memcpy(dst, GetRandomBytesCache() + g_random_offset_low, size); + + /* Advance. */ + g_random_offset_low += size; + + /* Ensure that at all times g_random_offset_low is not within 0x38 bytes of the end of the pool. */ + if (g_random_offset_low + MaxRandomBytes >= GetRandomBytesCacheSize()) { + g_random_offset_low = 0; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_random_cache.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_random_cache.hpp new file mode 100644 index 00000000..e45e1dc1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_random_cache.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> +#include "secmon_smc_common.hpp" + +namespace ams::secmon::smc { + + constexpr inline size_t MaxRandomBytes = sizeof(SmcArguments) - sizeof(SmcArguments{}.r[0]); + + void FillRandomCache(); + void RefillRandomCache(); + void GetRandomFromCache(void *dst, size_t size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_aes.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_aes.cpp new file mode 100644 index 00000000..84999fd7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_aes.cpp @@ -0,0 +1,865 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../secmon_error.hpp" +#include "../secmon_key_storage.hpp" +#include "../secmon_misc.hpp" +#include "../secmon_page_mapper.hpp" +#include "secmon_smc_aes.hpp" +#include "secmon_smc_device_unique_data.hpp" +#include "secmon_smc_se_lock.hpp" + +namespace ams::secmon::smc { + + namespace { + + constexpr inline auto AesKeySize = se::AesBlockSize; + constexpr inline size_t CmacSizeMax = 1_KB; + constexpr inline size_t DeviceUniqueDataSizeMin = 0x130; + constexpr inline size_t DeviceUniqueDataSizeMax = 0x240; + + enum SealKey { + SealKey_LoadAesKey = 0, + SealKey_DecryptDeviceUniqueData = 1, + SealKey_ImportLotusKey = 2, + SealKey_ImportEsDeviceKey = 3, + SealKey_ReencryptDeviceUniqueData = 4, + SealKey_ImportSslKey = 5, + SealKey_ImportEsClientCertKey = 6, + + SealKey_Count, + }; + + enum KeyType { + KeyType_Default = 0, + KeyType_NormalOnly = 1, + KeyType_RecoveryOnly = 2, + KeyType_NormalAndRecovery = 3, + + KeyType_Count, + }; + + enum CipherMode { + CipherMode_CbcEncryption = 0, + CipherMode_CbcDecryption = 1, + CipherMode_Ctr = 2, + CipherMode_Cmac = 3, + }; + + enum SpecificAesKey { + SpecificAesKey_CalibrationEncryption0 = 0, + SpecificAesKey_CalibrationEncryption1 = 1, + + SpecificAesKey_Count, + }; + + enum DeviceUniqueData { + DeviceUniqueData_DecryptDeviceUniqueData = 0, + DeviceUniqueData_ImportLotusKey = 1, + DeviceUniqueData_ImportEsDeviceKey = 2, + DeviceUniqueData_ImportSslKey = 3, + DeviceUniqueData_ImportEsClientCertKey = 4, + + DeviceUniqueData_Count, + }; + + /* Ensure that our "subtract one" simplification is valid for the cases we care about. */ + static_assert(DeviceUniqueData_ImportLotusKey - 1 == ImportRsaKey_Lotus); + static_assert(DeviceUniqueData_ImportEsDeviceKey - 1 == ImportRsaKey_EsDrmCert); + static_assert(DeviceUniqueData_ImportSslKey - 1 == ImportRsaKey_Ssl); + static_assert(DeviceUniqueData_ImportEsClientCertKey - 1 == ImportRsaKey_EsClientCert); + + constexpr ImportRsaKey ConvertToImportRsaKey(DeviceUniqueData data) { + /* Not necessary, but if this is invoked at compile-time this will force a compile-time error. */ + AMS_ASSUME(data != DeviceUniqueData_DecryptDeviceUniqueData); + AMS_ASSUME(data < DeviceUniqueData_Count); + + return static_cast<ImportRsaKey>(static_cast<int>(data) - 1); + } + + enum SecureData { + SecureData_Calibration = 0, + SecureData_SafeMode = 1, + SecureData_UserSystemProperEncryption = 2, + SecureData_UserSystem = 3, + + SecureData_Count, + }; + + struct GenerateAesKekOption { + using IsDeviceUnique = util::BitPack32::Field<0, 1, bool>; + using KeyTypeIndex = util::BitPack32::Field<1, 4, KeyType>; + using SealKeyIndex = util::BitPack32::Field<5, 3, SealKey>; + using Reserved = util::BitPack32::Field<8, 24, u32>; + }; + + struct ComputeAesOption { + using KeySlot = util::BitPack32::Field<0, 3, int>; + using CipherModeIndex = util::BitPack32::Field<4, 2, CipherMode>; + }; + + struct DecryptDeviceUniqueDataOption { + using DeviceUniqueDataIndex = util::BitPack32::Field<0, 3, DeviceUniqueData>; + using Reserved = util::BitPack32::Field<3, 29, u32>; + + /* Legacy. */ + using EnforceDeviceUnique = util::BitPack32::Field<0, 1, bool>; + }; + + constexpr const u8 SealKeySources[SealKey_Count][AesKeySize] = { + [SealKey_LoadAesKey] = { 0xF4, 0x0C, 0x16, 0x26, 0x0D, 0x46, 0x3B, 0xE0, 0x8C, 0x6A, 0x56, 0xE5, 0x82, 0xD4, 0x1B, 0xF6 }, + [SealKey_DecryptDeviceUniqueData] = { 0x7F, 0x54, 0x2C, 0x98, 0x1E, 0x54, 0x18, 0x3B, 0xBA, 0x63, 0xBD, 0x4C, 0x13, 0x5B, 0xF1, 0x06 }, + [SealKey_ImportLotusKey] = { 0xC7, 0x3F, 0x73, 0x60, 0xB7, 0xB9, 0x9D, 0x74, 0x0A, 0xF8, 0x35, 0x60, 0x1A, 0x18, 0x74, 0x63 }, + [SealKey_ImportEsDeviceKey] = { 0x0E, 0xE0, 0xC4, 0x33, 0x82, 0x66, 0xE8, 0x08, 0x39, 0x13, 0x41, 0x7D, 0x04, 0x64, 0x2B, 0x6D }, + [SealKey_ReencryptDeviceUniqueData] = { 0xE1, 0xA8, 0xAA, 0x6A, 0x2D, 0x9C, 0xDE, 0x43, 0x0C, 0xDE, 0xC6, 0x17, 0xF6, 0xC7, 0xF1, 0xDE }, + [SealKey_ImportSslKey] = { 0x74, 0x20, 0xF6, 0x46, 0x77, 0xB0, 0x59, 0x2C, 0xE8, 0x1B, 0x58, 0x64, 0x47, 0x41, 0x37, 0xD9 }, + [SealKey_ImportEsClientCertKey] = { 0xAA, 0x19, 0x0F, 0xFA, 0x4C, 0x30, 0x3B, 0x2E, 0xE6, 0xD8, 0x9A, 0xCF, 0xE5, 0x3F, 0xB3, 0x4B }, + }; + + constexpr const u8 KeyTypeSources[KeyType_Count][AesKeySize] = { + [KeyType_Default] = { 0x4D, 0x87, 0x09, 0x86, 0xC4, 0x5D, 0x20, 0x72, 0x2F, 0xBA, 0x10, 0x53, 0xDA, 0x92, 0xE8, 0xA9 }, + [KeyType_NormalOnly] = { 0x25, 0x03, 0x31, 0xFB, 0x25, 0x26, 0x0B, 0x79, 0x8C, 0x80, 0xD2, 0x69, 0x98, 0xE2, 0x22, 0x77 }, + [KeyType_RecoveryOnly] = { 0x76, 0x14, 0x1D, 0x34, 0x93, 0x2D, 0xE1, 0x84, 0x24, 0x7B, 0x66, 0x65, 0x55, 0x04, 0x65, 0x81 }, + [KeyType_NormalAndRecovery] = { 0xAF, 0x3D, 0xB7, 0xF3, 0x08, 0xA2, 0xD8, 0xA2, 0x08, 0xCA, 0x18, 0xA8, 0x69, 0x46, 0xC9, 0x0B }, + }; + + constexpr const u8 SealKeyMasks[SealKey_Count][AesKeySize] = { + [SealKey_LoadAesKey] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + [SealKey_DecryptDeviceUniqueData] = { 0xA2, 0xAB, 0xBF, 0x9C, 0x92, 0x2F, 0xBB, 0xE3, 0x78, 0x79, 0x9B, 0xC0, 0xCC, 0xEA, 0xA5, 0x74 }, + [SealKey_ImportLotusKey] = { 0x57, 0xE2, 0xD9, 0x45, 0xE4, 0x92, 0xF4, 0xFD, 0xC3, 0xF9, 0x86, 0x38, 0x89, 0x78, 0x9F, 0x3C }, + [SealKey_ImportEsDeviceKey] = { 0xE5, 0x4D, 0x9A, 0x02, 0xF0, 0x4F, 0x5F, 0xA8, 0xAD, 0x76, 0x0A, 0xF6, 0x32, 0x95, 0x59, 0xBB }, + [SealKey_ReencryptDeviceUniqueData] = { 0x59, 0xD9, 0x31, 0xF4, 0xA7, 0x97, 0xB8, 0x14, 0x40, 0xD6, 0xA2, 0x60, 0x2B, 0xED, 0x15, 0x31 }, + [SealKey_ImportSslKey] = { 0xFD, 0x6A, 0x25, 0xE5, 0xD8, 0x38, 0x7F, 0x91, 0x49, 0xDA, 0xF8, 0x59, 0xA8, 0x28, 0xE6, 0x75 }, + [SealKey_ImportEsClientCertKey] = { 0x89, 0x96, 0x43, 0x9A, 0x7C, 0xD5, 0x59, 0x55, 0x24, 0xD5, 0x24, 0x18, 0xAB, 0x6C, 0x04, 0x61 }, + }; + + constexpr const SealKey DeviceUniqueDataToSealKey[DeviceUniqueData_Count] = { + [DeviceUniqueData_DecryptDeviceUniqueData] = SealKey_DecryptDeviceUniqueData, + [DeviceUniqueData_ImportLotusKey] = SealKey_ImportLotusKey, + [DeviceUniqueData_ImportEsDeviceKey] = SealKey_ImportEsDeviceKey, + [DeviceUniqueData_ImportSslKey] = SealKey_ImportSslKey, + [DeviceUniqueData_ImportEsClientCertKey] = SealKey_ImportEsClientCertKey, + }; + + constexpr const u8 CalibrationKeySource[AesKeySize] = { + 0xE2, 0xD6, 0xB8, 0x7A, 0x11, 0x9C, 0xB8, 0x80, 0xE8, 0x22, 0x88, 0x8A, 0x46, 0xFB, 0xA1, 0x95 + }; + + constexpr const u8 EsCommonKeySources[EsCommonKeyType_Count][AesKeySize] = { + [EsCommonKeyType_TitleKey] = { 0x1E, 0xDC, 0x7B, 0x3B, 0x60, 0xE6, 0xB4, 0xD8, 0x78, 0xB8, 0x17, 0x15, 0x98, 0x5E, 0x62, 0x9B }, + [EsCommonKeyType_ArchiveKey] = { 0x3B, 0x78, 0xF2, 0x61, 0x0F, 0x9D, 0x5A, 0xE2, 0x7B, 0x4E, 0x45, 0xAF, 0xCB, 0x0B, 0x67, 0x4D }, + [EsCommonKeyType_Unknown2] = { 0x42, 0x64, 0x0B, 0xE3, 0x5F, 0xC6, 0xBE, 0x47, 0xC7, 0xB4, 0x84, 0xC5, 0xEB, 0x63, 0xAA, 0x02 }, + }; + + constexpr const u8 EsSealKeySource[AesKeySize] = { + 0xCB, 0xB7, 0x6E, 0x38, 0xA1, 0xCB, 0x77, 0x0F, 0xB2, 0xA5, 0xB2, 0x9D, 0xD8, 0x56, 0x9F, 0x76 + }; + + constexpr const u8 SecureDataSource[AesKeySize] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + constexpr const u8 SecureDataCounters[][AesKeySize] = { + [SecureData_Calibration] = { 0x3C, 0xD5, 0x92, 0xEC, 0x68, 0x31, 0x4A, 0x06, 0xD4, 0x1B, 0x0C, 0xD9, 0xF6, 0x2E, 0xD9, 0xE9 }, + [SecureData_SafeMode] = { 0x50, 0x81, 0xCF, 0x77, 0x18, 0x11, 0xD7, 0x0D, 0x13, 0x29, 0x60, 0xED, 0x4B, 0x21, 0x3E, 0xFC }, + [SecureData_UserSystemProperEncryption] = { 0x98, 0xCB, 0x4C, 0xEB, 0x15, 0xF1, 0x4A, 0x5A, 0x7A, 0x86, 0xB6, 0xF1, 0x94, 0x66, 0xF4, 0x9D }, + }; + + constexpr const u8 SecureDataTweaks[][AesKeySize] = { + [SecureData_Calibration] = { 0xAC, 0xCA, 0x9A, 0xCA, 0xFF, 0x2E, 0xB9, 0x22, 0xCC, 0x1F, 0x4F, 0xAD, 0xDD, 0x77, 0x21, 0x1E }, + [SecureData_SafeMode] = { 0x6E, 0xF8, 0x2A, 0x1A, 0xE0, 0x4F, 0xC3, 0x20, 0x08, 0x7B, 0xBA, 0x50, 0xC0, 0xCD, 0x7B, 0x39 }, + [SecureData_UserSystemProperEncryption] = { 0x6D, 0x02, 0x56, 0x2D, 0xF4, 0x3D, 0x0A, 0x15, 0xB1, 0x34, 0x5C, 0xC2, 0x84, 0x4C, 0xD4, 0x28 }, + }; + + constexpr const u8 *GetSecureDataCounter(SecureData which) { + switch (which) { + case SecureData_Calibration: + return SecureDataCounters[SecureData_Calibration]; + case SecureData_SafeMode: + return SecureDataCounters[SecureData_SafeMode]; + case SecureData_UserSystem: + case SecureData_UserSystemProperEncryption: + return SecureDataCounters[SecureData_UserSystemProperEncryption]; + default: + return nullptr; + } + } + + constexpr const u8 *GetSecureDataTweak(SecureData which) { + switch (which) { + case SecureData_Calibration: + return SecureDataTweaks[SecureData_Calibration]; + case SecureData_SafeMode: + return SecureDataTweaks[SecureData_SafeMode]; + case SecureData_UserSystem: + case SecureData_UserSystemProperEncryption: + return SecureDataTweaks[SecureData_UserSystemProperEncryption]; + default: + return nullptr; + } + } + + constexpr uintptr_t LinkedListAddressMinimum = secmon::MemoryRegionDram.GetAddress(); + constexpr size_t LinkedListAddressRangeSize = 4_MB - 2_KB; + constexpr uintptr_t LinkedListAddressMaximum = LinkedListAddressMinimum + LinkedListAddressRangeSize; + + constexpr size_t LinkedListSize = 12; + + constexpr bool IsValidLinkedListAddress(uintptr_t address) { + return LinkedListAddressMinimum <= address && address <= (LinkedListAddressMaximum - LinkedListSize); + } + + constinit bool g_is_compute_aes_completed = false; + + void SecurityEngineDoneHandler() { + /* Check that the compute succeeded. */ + se::ValidateAesOperationResult(); + + /* End the asynchronous operation. */ + g_is_compute_aes_completed = true; + EndAsyncOperation(); + } + + SmcResult GetComputeAesResult(void *dst, size_t size) { + /* Arguments are unused. */ + AMS_UNUSED(dst); + AMS_UNUSED(size); + + /* Check that the operation is completed. */ + SMC_R_UNLESS(g_is_compute_aes_completed, Busy); + + /* Unlock the security engine and succeed. */ + UnlockSecurityEngine(); + return SmcResult::Success; + } + + int PrepareMasterKey(int generation) { + if (generation == GetKeyGeneration()) { + return pkg1::AesKeySlot_Master; + } + + constexpr int Slot = pkg1::AesKeySlot_Smc; + LoadMasterKey(Slot, generation); + + return Slot; + } + + int PrepareDeviceMasterKey(int generation) { + if (generation == pkg1::KeyGeneration_1_0_0 && GetSocType() == fuse::SocType_Erista) { + return pkg1::AesKeySlot_Device; + } + if (generation == GetKeyGeneration()) { + return pkg1::AesKeySlot_DeviceMaster; + } + + constexpr int Slot = pkg1::AesKeySlot_Smc; + LoadDeviceMasterKey(Slot, generation); + + return Slot; + } + + void GetSecureDataImpl(u8 *dst, SecureData which, bool tweak) { + /* Compute the appropriate AES-CTR. */ + { + /* Ensure that the SE sees consistent data. */ + hw::FlushDataCache(dst, AesKeySize); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Perform the appropriate AES operation. */ + se::ComputeAes128Ctr(dst, AesKeySize, pkg1::AesKeySlot_Device, SecureDataSource, AesKeySize, GetSecureDataCounter(which), AesKeySize); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Ensure the CPU sees consistent data. */ + hw::FlushDataCache(dst, AesKeySize); + hw::DataSynchronizationBarrierInnerShareable(); + } + + /* Tweak, if we should. */ + if (tweak) { + const u8 * const tweak = GetSecureDataTweak(which); + + for (size_t i = 0; i < AesKeySize; ++i) { + dst[i] ^= tweak[i]; + } + } + } + + SmcResult GenerateAesKekImpl(SmcArguments &args) { + /* Decode arguments. */ + u8 kek_source[AesKeySize]; + std::memcpy(kek_source, std::addressof(args.r[1]), AesKeySize); + + const int generation = std::max<int>(static_cast<int>(args.r[3]) - 1, pkg1::KeyGeneration_1_0_0); + + const util::BitPack32 option = { static_cast<u32>(args.r[4]) }; + const bool is_device_unique = option.Get<GenerateAesKekOption::IsDeviceUnique>(); + const auto key_type = option.Get<GenerateAesKekOption::KeyTypeIndex>(); + const auto seal_key = option.Get<GenerateAesKekOption::SealKeyIndex>(); + const u32 reserved = option.Get<GenerateAesKekOption::Reserved>(); + + /* Validate arguments. */ + SMC_R_UNLESS(reserved == 0, InvalidArgument); + + if (is_device_unique) { + SMC_R_UNLESS(pkg1::IsValidDeviceUniqueKeyGeneration(generation), InvalidArgument); + } else { + SMC_R_UNLESS(pkg1::IsValidKeyGeneration(generation), InvalidArgument); + SMC_R_UNLESS(generation <= GetKeyGeneration(), InvalidArgument); + } + + SMC_R_UNLESS(0 <= key_type && key_type < KeyType_Count, InvalidArgument); + SMC_R_UNLESS(0 <= seal_key && seal_key < SealKey_Count, InvalidArgument); + + switch (key_type) { + case KeyType_NormalOnly: SMC_R_UNLESS(!IsRecoveryBoot(), InvalidArgument); break; + case KeyType_RecoveryOnly: SMC_R_UNLESS( IsRecoveryBoot(), InvalidArgument); break; + default: break; + } + + /* Declare temporary data storage. */ + u8 static_source[AesKeySize]; + u8 generated_key[AesKeySize]; + u8 access_key[AesKeySize]; + + /* Derive the static source. */ + for (size_t i = 0; i < sizeof(static_source); ++i) { + static_source[i] = KeyTypeSources[key_type][i] ^ SealKeyMasks[seal_key][i]; + } + + /* Get the seal key source. */ + const u8 * const seal_key_source = SealKeySources[seal_key]; + + /* Get the key slot. */ + const int slot = is_device_unique ? PrepareDeviceMasterKey(generation) : PrepareMasterKey(generation); + + /* Derive a static generation kek. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_Smc, slot, static_source, sizeof(static_source)); + + /* Decrypt the input with the static generation kek. */ + se::DecryptAes128(generated_key, sizeof(generated_key), pkg1::AesKeySlot_Smc, kek_source, sizeof(kek_source)); + + /* Generate the seal key. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_Smc, pkg1::AesKeySlot_RandomForUserWrap, seal_key_source, AesKeySize); + + /* Seal the generated key. */ + se::EncryptAes128(access_key, sizeof(access_key), pkg1::AesKeySlot_Smc, generated_key, sizeof(generated_key)); + + /* Copy the access key out. */ + std::memcpy(std::addressof(args.r[1]), access_key, sizeof(access_key)); + return SmcResult::Success; + } + + SmcResult LoadAesKeyImpl(SmcArguments &args) { + /* Decode arguments. */ + const int slot = args.r[1]; + + u8 access_key[AesKeySize]; + std::memcpy(access_key, std::addressof(args.r[2]), sizeof(access_key)); + + u8 key_source[AesKeySize]; + std::memcpy(key_source, std::addressof(args.r[4]), sizeof(key_source)); + + /* Validate arguments. */ + SMC_R_UNLESS(pkg1::IsUserAesKeySlot(slot), InvalidArgument); + + /* Get the seal key source. */ + constexpr const u8 * const SealKeySource = SealKeySources[SealKey_LoadAesKey]; + + /* Derive the seal key. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_Smc, pkg1::AesKeySlot_RandomForUserWrap, SealKeySource, AesKeySize); + + /* Unseal the access key. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_Smc, pkg1::AesKeySlot_Smc, access_key, sizeof(access_key)); + + /* Derive the key. */ + se::SetEncryptedAesKey128(slot, pkg1::AesKeySlot_Smc, key_source, sizeof(key_source)); + + return SmcResult::Success; + } + + SmcResult ComputeAesImpl(SmcArguments &args) { + /* Decode arguments. */ + u8 iv[se::AesBlockSize]; + + const util::BitPack32 option = { static_cast<u32>(args.r[1]) }; + std::memcpy(iv, std::addressof(args.r[2]), sizeof(iv)); + const u32 input_address = args.r[4]; + const u32 output_address = args.r[5]; + const u32 size = args.r[6]; + + const int slot = option.Get<ComputeAesOption::KeySlot>(); + const auto cipher_mode = option.Get<ComputeAesOption::CipherModeIndex>(); + + /* Validate arguments. */ + SMC_R_UNLESS(pkg1::IsUserAesKeySlot(slot), InvalidArgument); + SMC_R_UNLESS(util::IsAligned(size, se::AesBlockSize), InvalidArgument); + SMC_R_UNLESS(IsValidLinkedListAddress(input_address), InvalidArgument); + SMC_R_UNLESS(IsValidLinkedListAddress(output_address), InvalidArgument); + + /* We're starting an aes operation, so reset the completion status. */ + g_is_compute_aes_completed = false; + + /* Dispatch the correct aes operation asynchronously. */ + switch (cipher_mode) { + case CipherMode_CbcEncryption: se::EncryptAes128CbcAsync(output_address, slot, input_address, size, iv, sizeof(iv), SecurityEngineDoneHandler); break; + case CipherMode_CbcDecryption: se::DecryptAes128CbcAsync(output_address, slot, input_address, size, iv, sizeof(iv), SecurityEngineDoneHandler); break; + case CipherMode_Ctr: se::ComputeAes128CtrAsync(output_address, slot, input_address, size, iv, sizeof(iv), SecurityEngineDoneHandler); break; + case CipherMode_Cmac: + return SmcResult::NotSupported; + default: + return SmcResult::InvalidArgument; + } + + return SmcResult::Success; + } + + SmcResult GenerateSpecificAesKeyImpl(SmcArguments &args) { + /* Decode arguments. */ + u8 key_source[AesKeySize]; + std::memcpy(key_source, std::addressof(args.r[1]), sizeof(key_source)); + + const int generation = GetTargetFirmware() >= TargetFirmware_4_0_0 ? std::max<int>(static_cast<int>(args.r[3]) - 1, pkg1::KeyGeneration_1_0_0) : pkg1::KeyGeneration_1_0_0; + const auto which = static_cast<SpecificAesKey>(args.r[4]); + + /* Validate arguments. */ + SMC_R_UNLESS(pkg1::IsValidKeyGeneration(generation), InvalidArgument); + SMC_R_UNLESS(which < SpecificAesKey_Count, InvalidArgument); + + /* Generate the specific aes key. */ + u8 output_key[AesKeySize]; + if (fuse::GetPatchVersion() >= fuse::PatchVersion_Odnx02A2) { + const int slot = PrepareDeviceMasterKey(generation); + se::SetEncryptedAesKey128(pkg1::AesKeySlot_Smc, slot, CalibrationKeySource, sizeof(CalibrationKeySource)); + se::DecryptAes128(output_key, sizeof(output_key), pkg1::AesKeySlot_Smc, key_source, sizeof(key_source)); + } else { + GetSecureDataImpl(output_key, SecureData_Calibration, which == SpecificAesKey_CalibrationEncryption1); + } + + /* Copy the key to output. */ + std::memcpy(std::addressof(args.r[1]), output_key, sizeof(output_key)); + return SmcResult::Success; + } + + SmcResult ComputeCmacImpl(SmcArguments &args) { + /* Decode arguments. */ + const int slot = args.r[1]; + const uintptr_t data_address = args.r[2]; + const size_t data_size = args.r[3]; + + /* Declare buffer for user data. */ + alignas(8) u8 user_data[CmacSizeMax]; + + /* Validate arguments. */ + SMC_R_UNLESS(pkg1::IsUserAesKeySlot(slot), InvalidArgument); + SMC_R_UNLESS(data_size <= sizeof(user_data), InvalidArgument); + + /* Map the user data, and copy to stack. */ + { + UserPageMapper mapper(data_address); + SMC_R_UNLESS(mapper.Map(), InvalidArgument); + SMC_R_UNLESS(mapper.CopyFromUser(user_data, data_address, data_size), InvalidArgument); + } + + /* Ensure the SE sees consistent data. */ + hw::FlushDataCache(user_data, data_size); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Compute the mac. */ + { + u8 mac[se::AesBlockSize]; + se::ComputeAes128Cmac(mac, sizeof(mac), slot, user_data, data_size); + + std::memcpy(std::addressof(args.r[1]), mac, sizeof(mac)); + } + + return SmcResult::Success; + } + + SmcResult LoadPreparedAesKeyImpl(SmcArguments &args) { + /* Decode arguments. */ + u8 access_key[AesKeySize]; + + const int slot = args.r[1]; + std::memcpy(access_key, std::addressof(args.r[2]), sizeof(access_key)); + + /* Validate arguments. */ + SMC_R_UNLESS(pkg1::IsUserAesKeySlot(slot), InvalidArgument); + + /* Derive the seal key. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_Smc, pkg1::AesKeySlot_RandomForUserWrap, EsSealKeySource, sizeof(EsSealKeySource)); + + /* Unseal the key. */ + se::SetEncryptedAesKey128(slot, pkg1::AesKeySlot_Smc, access_key, sizeof(access_key)); + + return SmcResult::Success; + } + + SmcResult PrepareEsCommonTitleKeyImpl(SmcArguments &args) { + /* Declare variables. */ + u8 key_source[se::AesBlockSize]; + u8 key[se::AesBlockSize]; + u8 access_key[se::AesBlockSize]; + + /* Decode arguments. */ + std::memcpy(key_source, std::addressof(args.r[1]), sizeof(key_source)); + const int generation = GetTargetFirmware() >= TargetFirmware_3_0_0 ? std::max<int>(pkg1::KeyGeneration_1_0_0, static_cast<int>(args.r[3]) - 1) : pkg1::KeyGeneration_1_0_0; + + /* Validate arguments. */ + SMC_R_UNLESS(pkg1::IsValidKeyGeneration(generation), InvalidArgument); + SMC_R_UNLESS(generation <= GetKeyGeneration(), InvalidArgument); + + /* Derive the key. */ + DecryptWithEsCommonKey(key, sizeof(key), key_source, sizeof(key_source), EsCommonKeyType_TitleKey, generation); + + /* Prepare the aes key. */ + PrepareEsAesKey(access_key, sizeof(access_key), key, sizeof(key)); + + /* Copy the access key to output. */ + std::memcpy(std::addressof(args.r[1]), access_key, sizeof(access_key)); + + return SmcResult::Success; + } + + constexpr size_t GetDiscountedMinimumDeviceUniqueDataSize(bool enforce_device_unique) { + if (enforce_device_unique) { + return 0; + } else { + return DeviceUniqueDataTotalMetaSize - DeviceUniqueDataIvSize; + } + } + + SmcResult ValidateDeviceUniqueDataSize(DeviceUniqueData mode, size_t data_size, bool enforce_device_unique) { + /* Determine the discounted size towards the minimum. */ + const size_t discounted_size = GetDiscountedMinimumDeviceUniqueDataSize(enforce_device_unique); + SMC_R_UNLESS(enforce_device_unique || fuse::GetPatchVersion() < fuse::PatchVersion_Odnx02A2, InvalidArgument); + + switch (mode) { + case DeviceUniqueData_DecryptDeviceUniqueData: + { + SMC_R_UNLESS(DeviceUniqueDataTotalMetaSize - discounted_size < data_size && data_size <= DeviceUniqueDataSizeMax, InvalidArgument); + } + break; + case DeviceUniqueData_ImportLotusKey: + case DeviceUniqueData_ImportEsDeviceKey: + case DeviceUniqueData_ImportSslKey: + case DeviceUniqueData_ImportEsClientCertKey: + { + SMC_R_UNLESS(DeviceUniqueDataSizeMin - discounted_size <= data_size && data_size <= DeviceUniqueDataSizeMax, InvalidArgument); + } + break; + default: + return SmcResult::InvalidArgument; + } + + return SmcResult::Success; + } + + SmcResult DecryptDeviceUniqueDataImpl(const u8 *access_key, const u8 *key_source, const DeviceUniqueData mode, const uintptr_t data_address, const size_t data_size, bool enforce_device_unique) { + /* Validate arguments. */ + SMC_R_TRY(ValidateDeviceUniqueDataSize(mode, data_size, enforce_device_unique)); + + /* Decrypt the device unique data. */ + alignas(8) u8 work_buffer[DeviceUniqueDataSizeMax]; + ON_SCOPE_EXIT { crypto::ClearMemory(work_buffer, sizeof(work_buffer)); }; + { + /* Map and copy in the encrypted data. */ + UserPageMapper mapper(data_address); + SMC_R_UNLESS(mapper.Map(), InvalidArgument); + SMC_R_UNLESS(mapper.CopyFromUser(work_buffer, data_address, data_size), InvalidArgument); + + /* Determine the seal key to use. */ + const auto seal_key_type = DeviceUniqueDataToSealKey[mode]; + const u8 * const seal_key_source = SealKeySources[seal_key_type]; + + /* Decrypt the data. */ + if (!DecryptDeviceUniqueData(work_buffer, data_size, nullptr, seal_key_source, se::AesBlockSize, access_key, se::AesBlockSize, key_source, se::AesBlockSize, work_buffer, data_size, enforce_device_unique)) { + return SmcResult::InvalidArgument; + } + + /* Either output the key, or import it. */ + switch (mode) { + case DeviceUniqueData_DecryptDeviceUniqueData: + { + SMC_R_UNLESS(mapper.CopyToUser(data_address, work_buffer, data_size), InvalidArgument); + } + break; + case DeviceUniqueData_ImportLotusKey: + case DeviceUniqueData_ImportSslKey: + ImportRsaKeyExponent(ConvertToImportRsaKey(mode), work_buffer, se::RsaSize); + break; + case DeviceUniqueData_ImportEsDeviceKey: + case DeviceUniqueData_ImportEsClientCertKey: + ImportRsaKeyExponent(ConvertToImportRsaKey(mode), work_buffer, se::RsaSize); + ImportRsaKeyModulusProvisionally(ConvertToImportRsaKey(mode), work_buffer + se::RsaSize, se::RsaSize); + CommitRsaKeyModulus(ConvertToImportRsaKey(mode)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + return SmcResult::Success; + } + + SmcResult DecryptDeviceUniqueDataImpl(SmcArguments &args) { + /* Decode arguments. */ + u8 access_key[se::AesBlockSize]; + u8 key_source[se::AesBlockSize]; + + std::memcpy(access_key, std::addressof(args.r[1]), sizeof(access_key)); + const util::BitPack32 option = { static_cast<u32>(args.r[3]) }; + const uintptr_t data_address = args.r[4]; + const size_t data_size = args.r[5]; + std::memcpy(key_source, std::addressof(args.r[6]), sizeof(key_source)); + + const auto mode = GetTargetFirmware() >= TargetFirmware_5_0_0 ? option.Get<DecryptDeviceUniqueDataOption::DeviceUniqueDataIndex>() : DeviceUniqueData_DecryptDeviceUniqueData; + const auto reserved = option.Get<DecryptDeviceUniqueDataOption::Reserved>(); + + const bool enforce_device_unique = GetTargetFirmware() >= TargetFirmware_5_0_0 ? true : option.Get<DecryptDeviceUniqueDataOption::EnforceDeviceUnique>(); + + /* Validate arguments. */ + SMC_R_UNLESS(reserved == 0, InvalidArgument); + + /* Decrypt the device unique data. */ + return DecryptDeviceUniqueDataImpl(access_key, key_source, mode, data_address, data_size, enforce_device_unique); + } + + SmcResult DecryptAndImportEsDeviceKeyImpl(SmcArguments &args) { + /* Decode arguments. */ + u8 access_key[se::AesBlockSize]; + u8 key_source[se::AesBlockSize]; + + std::memcpy(access_key, std::addressof(args.r[1]), sizeof(access_key)); + const util::BitPack32 option = { static_cast<u32>(args.r[3]) }; + const uintptr_t data_address = args.r[4]; + const size_t data_size = args.r[5]; + std::memcpy(key_source, std::addressof(args.r[6]), sizeof(key_source)); + + const auto mode = DeviceUniqueData_ImportEsDeviceKey; + const auto reserved = option.Get<DecryptDeviceUniqueDataOption::Reserved>(); + + const bool enforce_device_unique = option.Get<DecryptDeviceUniqueDataOption::EnforceDeviceUnique>(); + + /* Validate arguments. */ + SMC_R_UNLESS(reserved == 0, InvalidArgument); + + /* Ensure that the key is exactly the correct size. */ + if (enforce_device_unique) { + SMC_R_UNLESS(data_size == util::AlignUp(2 * se::RsaSize + sizeof(u32), se::AesBlockSize) + DeviceUniqueDataTotalMetaSize, InvalidArgument); + } else { + SMC_R_UNLESS(data_size == util::AlignUp(2 * se::RsaSize + sizeof(u32), se::AesBlockSize) + DeviceUniqueDataIvSize, InvalidArgument); + } + + /* Decrypt the device unique data. */ + return DecryptDeviceUniqueDataImpl(access_key, key_source, mode, data_address, data_size, enforce_device_unique); + } + + SmcResult DecryptAndImportLotusKeyImpl(SmcArguments &args) { + /* Decode arguments. */ + u8 access_key[se::AesBlockSize]; + u8 key_source[se::AesBlockSize]; + + std::memcpy(access_key, std::addressof(args.r[1]), sizeof(access_key)); + const util::BitPack32 option = { static_cast<u32>(args.r[3]) }; + const uintptr_t data_address = args.r[4]; + const size_t data_size = args.r[5]; + std::memcpy(key_source, std::addressof(args.r[6]), sizeof(key_source)); + + const auto mode = DeviceUniqueData_ImportLotusKey; + const auto reserved = option.Get<DecryptDeviceUniqueDataOption::Reserved>(); + + const bool enforce_device_unique = option.Get<DecryptDeviceUniqueDataOption::EnforceDeviceUnique>(); + + /* Validate arguments. */ + SMC_R_UNLESS(reserved == 0, InvalidArgument); + + /* Ensure that the key is exactly the correct size. */ + if (enforce_device_unique) { + SMC_R_UNLESS(data_size == se::RsaSize + DeviceUniqueDataTotalMetaSize, InvalidArgument); + } else { + SMC_R_UNLESS(data_size == se::RsaSize + DeviceUniqueDataIvSize, InvalidArgument); + } + + /* Decrypt the device unique data. */ + return DecryptDeviceUniqueDataImpl(access_key, key_source, mode, data_address, data_size, enforce_device_unique); + } + + SmcResult ReencryptDeviceUniqueDataImpl(SmcArguments &args) { + /* Decode arguments. */ + u8 access_key_dec[se::AesBlockSize]; + u8 access_key_enc[se::AesBlockSize]; + u8 key_source_dec[se::AesBlockSize]; + u8 key_source_enc[se::AesBlockSize]; + + const uintptr_t access_key_dec_address = args.r[1]; + const uintptr_t access_key_enc_address = args.r[2]; + const util::BitPack32 option = { static_cast<u32>(args.r[3]) }; + const uintptr_t data_address = args.r[4]; + const size_t data_size = args.r[5]; + const uintptr_t key_source_dec_address = args.r[6]; + const uintptr_t key_source_enc_address = args.r[7]; + + const auto mode = option.Get<DecryptDeviceUniqueDataOption::DeviceUniqueDataIndex>(); + const auto reserved = option.Get<DecryptDeviceUniqueDataOption::Reserved>(); + + const bool enforce_device_unique = true; + + /* Validate arguments. */ + SMC_R_UNLESS(reserved == 0, InvalidArgument); + SMC_R_TRY(ValidateDeviceUniqueDataSize(mode, data_size, enforce_device_unique)); + + /* Decrypt the device unique data. */ + alignas(8) u8 work_buffer[DeviceUniqueDataSizeMax]; + ON_SCOPE_EXIT { crypto::ClearMemory(work_buffer, sizeof(work_buffer)); }; + { + /* Map and copy in the encrypted data. */ + UserPageMapper mapper(data_address); + SMC_R_UNLESS(mapper.Map(), InvalidArgument); + SMC_R_UNLESS(mapper.CopyFromUser(work_buffer, data_address, data_size), InvalidArgument); + SMC_R_UNLESS(mapper.CopyFromUser(access_key_dec, access_key_dec_address, sizeof(access_key_dec)), InvalidArgument); + SMC_R_UNLESS(mapper.CopyFromUser(access_key_enc, access_key_enc_address, sizeof(access_key_enc)), InvalidArgument); + SMC_R_UNLESS(mapper.CopyFromUser(key_source_dec, key_source_dec_address, sizeof(key_source_dec)), InvalidArgument); + SMC_R_UNLESS(mapper.CopyFromUser(key_source_enc, key_source_enc_address, sizeof(key_source_enc)), InvalidArgument); + + /* Decrypt the data. */ + u8 device_id_high; + { + /* Determine the seal key to use. */ + const u8 * const seal_key_source = SealKeySources[SealKey_ReencryptDeviceUniqueData]; + + if (!DecryptDeviceUniqueData(work_buffer, data_size, std::addressof(device_id_high), seal_key_source, se::AesBlockSize, access_key_dec, sizeof(access_key_dec), key_source_dec, sizeof(key_source_dec), work_buffer, data_size, enforce_device_unique)) { + return SmcResult::InvalidArgument; + } + } + + /* Reencrypt the data. */ + { + /* Determine the seal key to use. */ + const auto seal_key_type = DeviceUniqueDataToSealKey[mode]; + const u8 * const seal_key_source = SealKeySources[seal_key_type]; + + /* Encrypt the data. */ + EncryptDeviceUniqueData(work_buffer, data_size, seal_key_source, se::AesBlockSize, access_key_enc, sizeof(access_key_enc), key_source_enc, sizeof(key_source_enc), work_buffer, data_size - DeviceUniqueDataTotalMetaSize, device_id_high); + } + + /* Copy the reencrypted data back to user. */ + SMC_R_UNLESS(mapper.CopyToUser(data_address, work_buffer, data_size), InvalidArgument); + } + + return SmcResult::Success; + } + + SmcResult GetSecureDataImpl(SmcArguments &args) { + /* Decode arguments. */ + const auto which = static_cast<SecureData>(args.r[1]); + + /* Validate arguments/conditions. */ + SMC_R_UNLESS(fuse::GetPatchVersion() < fuse::PatchVersion_Odnx02A2, NotSupported); + SMC_R_UNLESS(which < SecureData_Count, NotSupported); + + /* Use a temporary buffer. */ + u8 secure_data[AesKeySize]; + GetSecureDataImpl(secure_data, which, false); + + /* Copy out. */ + std::memcpy(std::addressof(args.r[1]), secure_data, sizeof(secure_data)); + return SmcResult::Success; + } + + } + + /* Aes functionality. */ + SmcResult SmcGenerateAesKek(SmcArguments &args) { + return LockSecurityEngineAndInvoke(args, GenerateAesKekImpl); + } + + SmcResult SmcLoadAesKey(SmcArguments &args) { + return LockSecurityEngineAndInvoke(args, LoadAesKeyImpl); + } + + SmcResult SmcComputeAes(SmcArguments &args) { + return LockSecurityEngineAndInvokeAsync(args, ComputeAesImpl, GetComputeAesResult); + } + + SmcResult SmcGenerateSpecificAesKey(SmcArguments &args) { + return LockSecurityEngineAndInvoke(args, GenerateSpecificAesKeyImpl); + } + + SmcResult SmcComputeCmac(SmcArguments &args) { + return LockSecurityEngineAndInvoke(args, ComputeCmacImpl); + } + + SmcResult SmcLoadPreparedAesKey(SmcArguments &args) { + return LockSecurityEngineAndInvoke(args, LoadPreparedAesKeyImpl); + } + + SmcResult SmcPrepareEsCommonTitleKey(SmcArguments &args) { + return LockSecurityEngineAndInvoke(args, PrepareEsCommonTitleKeyImpl); + } + + /* Device unique data functionality. */ + SmcResult SmcDecryptDeviceUniqueData(SmcArguments &args) { + return LockSecurityEngineAndInvoke(args, DecryptDeviceUniqueDataImpl); + } + + SmcResult SmcReencryptDeviceUniqueData(SmcArguments &args) { + return LockSecurityEngineAndInvoke(args, ReencryptDeviceUniqueDataImpl); + } + + /* Legacy APIs. */ + SmcResult SmcDecryptAndImportEsDeviceKey(SmcArguments &args) { + return LockSecurityEngineAndInvoke(args, DecryptAndImportEsDeviceKeyImpl); + } + + SmcResult SmcDecryptAndImportLotusKey(SmcArguments &args) { + return LockSecurityEngineAndInvoke(args, DecryptAndImportLotusKeyImpl); + } + + /* Es encryption utilities. */ + void DecryptWithEsCommonKey(void *dst, size_t dst_size, const void *src, size_t src_size, EsCommonKeyType type, int generation) { + /* Validate pre-conditions. */ + AMS_ABORT_UNLESS(dst_size == AesKeySize); + AMS_ABORT_UNLESS(src_size == AesKeySize); + AMS_ABORT_UNLESS(0 <= type && type < EsCommonKeyType_Count); + + /* Prepare the master key for the generation. */ + const int slot = PrepareMasterKey(generation); + + /* Derive the es common key. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_Smc, slot, EsCommonKeySources[type], AesKeySize); + + /* Decrypt the input using the common key. */ + se::DecryptAes128(dst, dst_size, pkg1::AesKeySlot_Smc, src, src_size); + } + + void PrepareEsAesKey(void *dst, size_t dst_size, const void *src, size_t src_size) { + /* Validate pre-conditions. */ + AMS_ABORT_UNLESS(dst_size == AesKeySize); + AMS_ABORT_UNLESS(src_size == AesKeySize); + + /* Derive the seal key. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_Smc, pkg1::AesKeySlot_RandomForUserWrap, EsSealKeySource, sizeof(EsSealKeySource)); + + /* Seal the key. */ + se::EncryptAes128(dst, dst_size, pkg1::AesKeySlot_Smc, src, src_size); + } + + /* 'Tis the last rose of summer, / Left blooming alone; */ + /* Oh! who would inhabit / This bleak world alone? */ + SmcResult SmcGetSecureData(SmcArguments &args) { + return LockSecurityEngineAndInvoke(args, GetSecureDataImpl); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_aes.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_aes.hpp new file mode 100644 index 00000000..ce0d8390 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_aes.hpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> +#include "secmon_smc_common.hpp" + +namespace ams::secmon::smc { + + enum EsCommonKeyType { + EsCommonKeyType_TitleKey = 0, + EsCommonKeyType_ArchiveKey = 1, + EsCommonKeyType_Unknown2 = 2, + + EsCommonKeyType_Count, + }; + + /* General Aes functionality. */ + SmcResult SmcGenerateAesKek(SmcArguments &args); + SmcResult SmcLoadAesKey(SmcArguments &args); + SmcResult SmcComputeAes(SmcArguments &args); + SmcResult SmcGenerateSpecificAesKey(SmcArguments &args); + SmcResult SmcComputeCmac(SmcArguments &args); + SmcResult SmcLoadPreparedAesKey(SmcArguments &args); + SmcResult SmcPrepareEsCommonTitleKey(SmcArguments &args); + + /* Device unique data functionality. */ + SmcResult SmcDecryptDeviceUniqueData(SmcArguments &args); + SmcResult SmcReencryptDeviceUniqueData(SmcArguments &args); + + /* Legacy device unique data functionality. */ + SmcResult SmcDecryptAndImportEsDeviceKey(SmcArguments &args); + SmcResult SmcDecryptAndImportLotusKey(SmcArguments &args); + + /* Es encryption utilities. */ + void DecryptWithEsCommonKey(void *dst, size_t dst_size, const void *src, size_t src_size, EsCommonKeyType type, int generation); + void PrepareEsAesKey(void *dst, size_t dst_size, const void *src, size_t src_size); + + /* The last rose of summer. */ + SmcResult SmcGetSecureData(SmcArguments &args); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_carveout.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_carveout.cpp new file mode 100644 index 00000000..d9c0a26d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_carveout.cpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../secmon_error.hpp" +#include "../secmon_setup.hpp" +#include "secmon_smc_carveout.hpp" + +namespace ams::secmon::smc { + + SmcResult SmcSetKernelCarveoutRegion(SmcArguments &args) { + /* Decode arguments. */ + const int index = args.r[1]; + const uintptr_t address = args.r[2]; + const size_t size = args.r[3]; + + /* Validate arguments. */ + SMC_R_UNLESS(0 <= index && index < KernelCarveoutCount, InvalidArgument); + SMC_R_UNLESS(util::IsAligned(address, 128_KB), InvalidArgument); + SMC_R_UNLESS(util::IsAligned(size, 128_KB), InvalidArgument); + SMC_R_UNLESS(size <= CarveoutSizeMax, InvalidArgument); + + /* Set the carveout. */ + SetKernelCarveoutRegion(index, address, size); + + return SmcResult::Success; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_carveout.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_carveout.hpp new file mode 100644 index 00000000..fcfe038d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_carveout.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> +#include "secmon_smc_common.hpp" + +namespace ams::secmon::smc { + + SmcResult SmcSetKernelCarveoutRegion(SmcArguments &args); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_common.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_common.hpp new file mode 100644 index 00000000..fcf6b4c6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_common.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::secmon::smc { + + enum class SmcResult : u32 { + Success = 0, + NotSupported = 1, + InvalidArgument = 2, + Busy = 3, + NoAsyncOperation = 4, + InvalidAsyncOperation = 5, + NotPermitted = 6, + NotInitialized = 7, + + PsciSuccess = 0, + PsciNotSupported = static_cast<u32>(-1), + PsciInvalidParameters = static_cast<u32>(-2), + PsciDenied = static_cast<u32>(-3), + PsciAlreadyOn = static_cast<u32>(-4), + }; + + #define SMC_R_SUCCEEEDED(res) (res == SmcResult::Success) + #define SMC_R_FAILED(res) (res != SmcResult::Success) + + #define SMC_R_TRY(res_expr) ({ const auto _tmp_r_try_rc = (res_expr); if (SMC_R_FAILED(_tmp_r_try_rc)) { return _tmp_r_try_rc; } }) + #define SMC_R_UNLESS(cond, RES) ({ if (!(cond)) { return SmcResult::RES; }}) + + struct SmcArguments { + u64 r[8]; + }; + + constexpr inline int SecurityEngineUserInterruptId = 44; + constexpr inline u64 InvalidAsyncKey = 0; + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_cpu_asm.s b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_cpu_asm.s new file mode 100644 index 00000000..a52359b0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_cpu_asm.s @@ -0,0 +1,94 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* 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 + +.section .text._ZN3ams6secmon3smc19PivotStackAndInvokeEPvPFvvE, "ax", %progbits +.align 4 +.global _ZN3ams6secmon3smc19PivotStackAndInvokeEPvPFvvE +_ZN3ams6secmon3smc19PivotStackAndInvokeEPvPFvvE: + /* Pivot to use the provided stack pointer. */ + mov sp, x0 + + /* Release our lock on the common smc stack. */ + mov x19, x1 + bl _ZN3ams6secmon25ReleaseCommonSmcStackLockEv + + /* Invoke the function with the new stack. */ + br x19 + +.section .text._ZN3ams6secmon3smc16FinalizePowerOffEv, "ax", %progbits +.align 4 +.global _ZN3ams6secmon3smc16FinalizePowerOffEv +_ZN3ams6secmon3smc16FinalizePowerOffEv: + /* Disable all caches by clearing sctlr_el1.C. */ + mrs x0, sctlr_el1 + and x0, x0, #~(1 << 2) + msr sctlr_el1, x0 + isb + + /* Disable all caches by clearing sctlr_el3.C. */ + mrs x0, sctlr_el3 + and x0, x0, #~(1 << 2) + msr sctlr_el3, x0 + isb + + /* Disable prefetching of page table walking descriptors. */ + mrs x0, cpuectlr_el1 + orr x0, x0, #(1 << 38) + + /* Disable prefetching of instructions. */ + and x0, x0, #~(3 << 35) + + /* Disable prefetching of data. */ + and x0, x0, #~(3 << 32) + msr cpuectlr_el1, x0 + isb + + /* Ensure that all data prefetching prior to our configuration change completes. */ + dsb sy + + /* Flush the entire data cache (local). */ + bl _ZN3ams6secmon25FlushEntireDataCacheLocalEv + + /* Disable receiving instruction cache/TLB maintenance operations. */ + mrs x0, cpuectlr_el1 + and x0, x0, #~(1 << 6) + msr cpuectlr_el1, x0 + + /* Configure the gic to not send interrupts to the current core. */ + ldr x1, =0x1F0043000 + mov w0, #0x1E0 /* Set FIQBypDisGrp1, IRQBypDisGrp1, reserved bits 7/8. */ + str w0, [x1] + + /* Lock the OS Double Lock. */ + mrs x0, osdlr_el1 + orr x0, x0, #(1 << 0) + msr osdlr_el1, x0 + + /* Ensure that our configuration takes. */ + isb + dsb sy + + /* Wait for interrupts, infinitely. */ +0: + wfi + b 0b + + + diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_device_unique_data.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_device_unique_data.cpp new file mode 100644 index 00000000..b487e530 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_device_unique_data.cpp @@ -0,0 +1,206 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../secmon_error.hpp" +#include "secmon_smc_device_unique_data.hpp" + +namespace ams::secmon::smc { + + namespace { + + void GenerateIv(void *dst, size_t dst_size) { + /* Flush the region we're about to fill to ensure consistency with the SE. */ + hw::FlushDataCache(dst, dst_size); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Generate random bytes. */ + se::GenerateRandomBytes(dst, dst_size); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Flush to ensure the CPU sees consistent data for the region. */ + hw::FlushDataCache(dst, dst_size); + hw::DataSynchronizationBarrierInnerShareable(); + } + + void PrepareDeviceUniqueDataKey(const void *seal_key_source, size_t seal_key_source_size, const void *access_key, size_t access_key_size, const void *key_source, size_t key_source_size) { + /* Derive the seal key. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_Smc, pkg1::AesKeySlot_RandomForUserWrap, seal_key_source, seal_key_source_size); + + /* Derive the device unique data kek. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_Smc, pkg1::AesKeySlot_Smc, access_key, access_key_size); + + /* Derive the actual device unique data key. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_Smc, pkg1::AesKeySlot_Smc, key_source, key_source_size); + } + + void ComputeAes128Ctr(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, const void *iv, size_t iv_size) { + /* Ensure that the SE sees consistent data. */ + hw::FlushDataCache(src, src_size); + hw::FlushDataCache(dst, dst_size); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Use the security engine to transform the data. */ + se::ComputeAes128Ctr(dst, dst_size, slot, src, src_size, iv, iv_size); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Ensure the CPU sees consistent data. */ + hw::FlushDataCache(dst, dst_size); + hw::DataSynchronizationBarrierInnerShareable(); + } + + void ComputeGmac(void *dst, size_t dst_size, const void *data, size_t data_size, const void *iv, size_t iv_size) { + /* Declare keyslot (as encryptor will need to take it by pointer/reference). */ + constexpr int Slot = pkg1::AesKeySlot_Smc; + + /* Calculate the mac. */ + crypto::Aes128GcmEncryptor gcm; + gcm.Initialize(std::addressof(Slot), sizeof(Slot), iv, iv_size); + gcm.UpdateAad(data, data_size); + gcm.GetMac(dst, dst_size); + } + + constexpr u64 GetDeviceIdLow(u64 device_id) { + /* Mask out the top byte. */ + constexpr u64 ByteMask = (static_cast<u64>(1) << BITSIZEOF(u8)) - 1; + constexpr u64 LowMask = ~(ByteMask << (BITSIZEOF(u64) - BITSIZEOF(u8))); + return device_id & LowMask; + } + + constexpr u8 GetDeviceIdHigh(u64 device_id) { + /* Get the top byte. */ + return static_cast<u8>(device_id >> (BITSIZEOF(u64) - BITSIZEOF(u8))); + } + + constexpr u64 EncodeDeviceId(u8 device_id_high, u64 device_id_low) { + return (static_cast<u64>(device_id_high) << (BITSIZEOF(u64) - BITSIZEOF(u8))) | device_id_low; + } + + } + + bool DecryptDeviceUniqueData(void *dst, size_t dst_size, u8 *out_device_id_high, const void *seal_key_source, size_t seal_key_source_size, const void *access_key, size_t access_key_size, const void *key_source, size_t key_source_size, const void *src, size_t src_size, bool enforce_device_unique) { + /* Determine how much decrypted data there will be. */ + const size_t enc_size = src_size - (enforce_device_unique ? DeviceUniqueDataOuterMetaSize : DeviceUniqueDataIvSize); + const size_t dec_size = enc_size - DeviceUniqueDataInnerMetaSize; + + /* Ensure that our sizes are allowed. */ + AMS_ABORT_UNLESS(src_size > (enforce_device_unique ? DeviceUniqueDataTotalMetaSize : DeviceUniqueDataIvSize)); + AMS_ABORT_UNLESS(dst_size >= enc_size); + + /* Determine the extents of the data. */ + const u8 * const iv = static_cast<const u8 *>(src); + const u8 * const enc = iv + DeviceUniqueDataIvSize; + const u8 * const mac = enc + enc_size; + + /* Decrypt the data. */ + { + /* Declare temporaries. */ + u8 temp_iv[DeviceUniqueDataIvSize]; + u8 calc_mac[DeviceUniqueDataMacSize]; + ON_SCOPE_EXIT { crypto::ClearMemory(temp_iv, sizeof(temp_iv)); crypto::ClearMemory(calc_mac, sizeof(calc_mac)); }; + + /* Prepare the key used to decrypt the data. */ + PrepareDeviceUniqueDataKey(seal_key_source, seal_key_source_size, access_key, access_key_size, key_source, key_source_size); + + /* Copy the iv to stack. */ + std::memcpy(temp_iv, iv, sizeof(temp_iv)); + + /* Decrypt the data. */ + ComputeAes128Ctr(dst, dst_size, pkg1::AesKeySlot_Smc, enc, enc_size, temp_iv, DeviceUniqueDataIvSize); + + /* If we're not enforcing device unique, there's no mac/device id. */ + if (!enforce_device_unique) { + return true; + } + + /* Compute the gmac. */ + ComputeGmac(calc_mac, DeviceUniqueDataMacSize, dst, enc_size, temp_iv, DeviceUniqueDataIvSize); + + /* Validate the gmac. */ + if (!crypto::IsSameBytes(mac, calc_mac, sizeof(calc_mac))) { + return false; + } + } + + /* Validate device id, output device id if needed. */ + { + /* Locate the device id in the decryption output. */ + const u8 * const padding = static_cast<const u8 *>(dst) + dec_size; + const u8 * const device_id = padding + DeviceUniqueDataPaddingSize; + + /* Load the big endian device id. */ + const u64 device_id_val = util::LoadBigEndian(static_cast<const u64 *>(static_cast<const void *>(device_id))); + + /* Validate that the device id low matches the value in fuses. */ + if (GetDeviceIdLow(device_id_val) != fuse::GetDeviceId()) { + return false; + } + + /* Set the output device id high, if needed. */ + if (out_device_id_high != nullptr) { + *out_device_id_high = GetDeviceIdHigh(device_id_val); + } + } + + return true; + } + + void EncryptDeviceUniqueData(void *dst, size_t dst_size, const void *seal_key_source, size_t seal_key_source_size, const void *access_key, size_t access_key_size, const void *key_source, size_t key_source_size, const void *src, size_t src_size, u8 device_id_high) { + /* Determine metadata locations. */ + u8 * const dst_iv = static_cast<u8 *>(dst); + u8 * const dst_data = dst_iv + DeviceUniqueDataIvSize; + u8 * const dst_pad = dst_data + src_size; + u8 * const dst_did = dst_pad + DeviceUniqueDataPaddingSize; + u8 * const dst_mac = dst_did + DeviceUniqueDataDeviceIdSize; + + /* Verify that our sizes are okay. */ + const size_t enc_size = src_size + DeviceUniqueDataInnerMetaSize; + const size_t res_size = src_size + DeviceUniqueDataTotalMetaSize; + AMS_ABORT_UNLESS(res_size <= dst_size); + + /* Layout the image as expected. */ + { + /* Generate a random iv. */ + util::AlignedBuffer<hw::DataCacheLineSize, DeviceUniqueDataIvSize> iv; + GenerateIv(iv, DeviceUniqueDataIvSize); + + /* Move the data to the output image. */ + std::memmove(dst_data, src, src_size); + + /* Copy the iv. */ + std::memcpy(dst_iv, iv, DeviceUniqueDataIvSize); + + /* Clear the padding. */ + std::memset(dst_pad, 0, DeviceUniqueDataPaddingSize); + + /* Store the device id. */ + util::StoreBigEndian(reinterpret_cast<u64 *>(dst_did), EncodeDeviceId(device_id_high, fuse::GetDeviceId())); + } + + /* Encrypt and mac. */ + { + + /* Prepare the key used to encrypt the data. */ + PrepareDeviceUniqueDataKey(seal_key_source, seal_key_source_size, access_key, access_key_size, key_source, key_source_size); + + /* Compute the gmac. */ + ComputeGmac(dst_mac, DeviceUniqueDataMacSize, dst_data, enc_size, dst_iv, DeviceUniqueDataIvSize); + + /* Encrypt the data. */ + ComputeAes128Ctr(dst_data, enc_size, pkg1::AesKeySlot_Smc, dst_data, enc_size, dst_iv, DeviceUniqueDataIvSize); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_device_unique_data.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_device_unique_data.hpp new file mode 100644 index 00000000..8ff5703b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_device_unique_data.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> +#include "secmon_smc_common.hpp" + +namespace ams::secmon::smc { + + constexpr inline size_t DeviceUniqueDataIvSize = se::AesBlockSize; + constexpr inline size_t DeviceUniqueDataMacSize = se::AesBlockSize; + constexpr inline size_t DeviceUniqueDataDeviceIdSize = sizeof(u64); + constexpr inline size_t DeviceUniqueDataPaddingSize = se::AesBlockSize - DeviceUniqueDataDeviceIdSize; + + constexpr inline size_t DeviceUniqueDataOuterMetaSize = DeviceUniqueDataIvSize + DeviceUniqueDataMacSize; + constexpr inline size_t DeviceUniqueDataInnerMetaSize = DeviceUniqueDataPaddingSize + DeviceUniqueDataDeviceIdSize; + constexpr inline size_t DeviceUniqueDataTotalMetaSize = DeviceUniqueDataOuterMetaSize + DeviceUniqueDataInnerMetaSize; + + bool DecryptDeviceUniqueData(void *dst, size_t dst_size, u8 *out_device_id_high, const void *seal_key_source, size_t seal_key_source_size, const void *access_key, size_t access_key_size, const void *key_source, size_t key_source_size, const void *src, size_t src_size, bool enforce_device_unique); + void EncryptDeviceUniqueData(void *dst, size_t dst_size, const void *seal_key_source, size_t seal_key_source_size, const void *access_key, size_t access_key_size, const void *key_source, size_t key_source_size, const void *src, size_t src_size, u8 device_id_high); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_error.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_error.cpp new file mode 100644 index 00000000..d40f68ff --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_error.cpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../secmon_error.hpp" +#include "secmon_smc_error.hpp" + +namespace ams::secmon::smc { + + SmcResult SmcShowError(SmcArguments &args) { + /* Decode arguments. */ + const u32 r = ((args.r[1] >> 8) & 0xF); + const u32 g = ((args.r[1] >> 4) & 0xF); + const u32 b = ((args.r[1] >> 0) & 0xF); + + const u32 rgb = (b << 8) | (g << 4) | (r << 0); + + /* Set the error info. */ + SetError(pkg1::MakeKernelPanicResetInfo(rgb)); + + /* Reboot. */ + ErrorReboot(); + + /* This point will never be reached. */ + __builtin_unreachable(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_error.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_error.hpp new file mode 100644 index 00000000..1d85a4a3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_error.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> +#include "secmon_smc_common.hpp" + +namespace ams::secmon::smc { + + SmcResult SmcShowError(SmcArguments &args); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_handler.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_handler.cpp new file mode 100644 index 00000000..8eaf40cd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_handler.cpp @@ -0,0 +1,267 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../secmon_error.hpp" +#include "../secmon_misc.hpp" +#include "secmon_smc_common.hpp" +#include "secmon_smc_handler.hpp" +#include "secmon_smc_aes.hpp" +#include "secmon_smc_carveout.hpp" +#include "secmon_smc_device_unique_data.hpp" +#include "secmon_smc_error.hpp" +#include "secmon_smc_info.hpp" +#include "secmon_smc_memory_access.hpp" +#include "secmon_smc_power_management.hpp" +#include "secmon_smc_random.hpp" +#include "secmon_smc_register_access.hpp" +#include "secmon_smc_result.hpp" +#include "secmon_smc_rsa.hpp" + +namespace ams::secmon::smc { + + namespace { + + struct HandlerInfo { + u32 function_id; + u32 restriction_mask; + SmcHandler handler; + }; + + struct HandlerTable { + const HandlerInfo *entries; + size_t count; + }; + + enum HandlerType : int { + HandlerType_User = 0, + HandlerType_Kern = 1, + HandlerType_Count = 2, + }; + + enum Restriction { + Restriction_None = (0 << 0), + Restriction_Normal = (1 << 0), + Restriction_DeviceUniqueDataNotAllowed = (1 << 1), + Restriction_SafeModeNotAllowed = (1 << 2), + }; + + enum SmcCallRange { + SmcCallRange_ArmArch = 0, + SmcCallRange_Cpu = 1, + SmcCallRange_Sip = 2, + SmcCallRange_Oem = 3, + SmcCallRange_Standard = 4, + + SmcCallRange_TrustedApp = 0x30, + }; + + enum SmcArgumentType { + ArgumentType_Integer = 0, + ArgumentType_Pointer = 1, + }; + + enum SmcConvention { + Convention_Smc32 = 0, + Convention_Smc64 = 1, + }; + + enum SmcCallType { + SmcCallType_YieldingCall = 0, + SmcCallType_FastCall = 1, + }; + + struct SmcFunctionId { + using FunctionId = util::BitPack64::Field< 0, 8, u32>; + using ArgumentType0 = util::BitPack64::Field< 8, 1, SmcArgumentType>; + using ArgumentType1 = util::BitPack64::Field< 9, 1, SmcArgumentType>; + using ArgumentType2 = util::BitPack64::Field<10, 1, SmcArgumentType>; + using ArgumentType3 = util::BitPack64::Field<11, 1, SmcArgumentType>; + using ArgumentType4 = util::BitPack64::Field<12, 1, SmcArgumentType>; + using ArgumentType5 = util::BitPack64::Field<13, 1, SmcArgumentType>; + using ArgumentType6 = util::BitPack64::Field<14, 1, SmcArgumentType>; + using ArgumentType7 = util::BitPack64::Field<15, 1, SmcArgumentType>; + using Reserved = util::BitPack64::Field<16, 8, u32>; + using CallRange = util::BitPack64::Field<24, 6, SmcCallRange>; + using Convention = util::BitPack64::Field<30, 1, SmcConvention>; + using CallType = util::BitPack64::Field<31, 1, SmcCallType>; + using Reserved2 = util::BitPack64::Field<32, 32, u32>; + }; + + constinit HandlerInfo g_user_handlers[] = { + { 0x00000000, Restriction_SafeModeNotAllowed, nullptr }, + { 0xC3000401, Restriction_SafeModeNotAllowed, SmcSetConfig }, + { 0xC3000002, Restriction_Normal, SmcGetConfigUser }, + { 0xC3000003, Restriction_Normal, SmcGetResult }, + { 0xC3000404, Restriction_Normal, SmcGetResultData }, + { 0xC3000E05, Restriction_SafeModeNotAllowed, SmcModularExponentiate }, + { 0xC3000006, Restriction_Normal, SmcGenerateRandomBytes }, + { 0xC3000007, Restriction_Normal, SmcGenerateAesKek }, + { 0xC3000008, Restriction_Normal, SmcLoadAesKey }, + { 0xC3000009, Restriction_Normal, SmcComputeAes }, + { 0xC300000A, Restriction_Normal, SmcGenerateSpecificAesKey }, + { 0xC300040B, Restriction_Normal, SmcComputeCmac }, + { 0xC300D60C, Restriction_Normal, SmcReencryptDeviceUniqueData }, + { 0xC300100D, Restriction_DeviceUniqueDataNotAllowed, SmcDecryptDeviceUniqueData }, + { 0xC300000E, Restriction_SafeModeNotAllowed, nullptr }, + { 0xC300060F, Restriction_DeviceUniqueDataNotAllowed, SmcModularExponentiateByStorageKey }, + { 0xC3000610, Restriction_SafeModeNotAllowed, SmcPrepareEsDeviceUniqueKey }, + { 0xC3000011, Restriction_SafeModeNotAllowed, SmcLoadPreparedAesKey }, + { 0xC3000012, Restriction_SafeModeNotAllowed, SmcPrepareEsCommonTitleKey } + }; + + /* Deprecated handlerss. */ + constexpr inline const HandlerInfo DecryptAndImportEsDeviceKeyHandlerInfo = { + 0xC300100C, Restriction_Normal, SmcDecryptAndImportEsDeviceKey + }; + + constexpr inline const HandlerInfo DecryptAndImportLotusKeyHandlerInfo = { + 0xC300100E, Restriction_SafeModeNotAllowed, SmcDecryptAndImportLotusKey + }; + + constinit HandlerInfo g_kern_handlers[] = { + { 0x00000000, Restriction_SafeModeNotAllowed, nullptr }, + { 0xC4000001, Restriction_SafeModeNotAllowed, SmcSuspendCpu }, + { 0x84000002, Restriction_SafeModeNotAllowed, SmcPowerOffCpu }, + { 0xC4000003, Restriction_SafeModeNotAllowed, SmcPowerOnCpu }, + { 0xC3000004, Restriction_Normal, SmcGetConfigKern }, + { 0xC3000005, Restriction_Normal, SmcGenerateRandomBytesNonBlocking }, + { 0xC3000006, Restriction_Normal, SmcShowError }, + { 0xC3000007, Restriction_Normal, SmcSetKernelCarveoutRegion }, + { 0xC3000008, Restriction_Normal, SmcReadWriteRegister }, + + /* NOTE: Atmosphere extension for mesosphere. This ID is subject to change at any time. */ + { 0xC3000409, Restriction_Normal, SmcSetConfig }, + }; + + constinit HandlerInfo g_ams_handlers[] = { + { 0x00000000, Restriction_SafeModeNotAllowed, nullptr }, + { 0xF0000201, Restriction_None, SmcIramCopy }, + { 0xF0000002, Restriction_None, SmcReadWriteRegister }, + { 0xF0000003, Restriction_None, SmcWriteAddress }, + { 0xF0000404, Restriction_None, SmcGetEmummcConfig }, + }; + + constexpr const HandlerInfo GetSecureDataHandlerInfo = { + 0x67891234, Restriction_None, SmcGetSecureData + }; + + constinit HandlerTable g_handler_tables[] = { + { g_user_handlers, util::size(g_user_handlers) }, + { g_kern_handlers, util::size(g_kern_handlers) }, + }; + + constinit HandlerTable g_ams_handler_table = { + g_ams_handlers, util::size(g_ams_handlers) + }; + + NORETURN void InvalidSmcError(u64 id) { + SetError(pkg1::ErrorInfo_UnknownSmc); + AMS_ABORT("Invalid SMC: %lx", id); + } + + const HandlerTable &GetHandlerTable(HandlerType type, u64 id) { + /* Ensure we have a valid handler type. */ + if (AMS_UNLIKELY(!(0 <= type && type < HandlerType_Count))) { + InvalidSmcError(id); + } + + /* Provide support for legacy SmcGetSecureData. */ + if (id == GetSecureDataHandlerInfo.function_id) { + return g_handler_tables[HandlerType_User]; + } + + /* Check if we're a user SMC. */ + if (type == HandlerType_User) { + /* Nintendo uses OEM SMCs. */ + /* We will assign Atmosphere extension SMCs the TrustedApplication range. */ + if (util::BitPack64{id}.Get<SmcFunctionId::CallRange>() == SmcCallRange_TrustedApp) { + return g_ams_handler_table; + } + + /* If we're not performing an atmosphere extension smc, require that we're being invoked by spl on core 3. */ + if (AMS_UNLIKELY(hw::GetCurrentCoreId() != 3)) { + InvalidSmcError(id); + } + } + + return g_handler_tables[type]; + } + + const HandlerInfo &GetHandlerInfo(const HandlerTable &table, u64 id) { + /* Provide support for legacy SmcGetSecureData. */ + if (id == GetSecureDataHandlerInfo.function_id) { + return GetSecureDataHandlerInfo; + } + + /* Get and check the index. */ + const auto index = util::BitPack64{id}.Get<SmcFunctionId::FunctionId>(); + if (AMS_UNLIKELY(index >= table.count)) { + InvalidSmcError(id); + } + + /* Get and check the handler info. */ + const auto &handler_info = table.entries[index]; + + /* Check that the handler isn't null. */ + if (AMS_UNLIKELY(handler_info.handler == nullptr)) { + InvalidSmcError(id); + } + + /* Check that the handler's id matches. */ + if (AMS_UNLIKELY(handler_info.function_id != id)) { + InvalidSmcError(id); + } + + return handler_info; + } + + bool IsHandlerRestricted(const HandlerInfo &info) { + return (info.restriction_mask & secmon::GetRestrictedSmcMask()) != 0; + } + + SmcResult InvokeSmcHandler(const HandlerInfo &info, SmcArguments &args) { + /* Check if the smc is restricted. */ + if (GetTargetFirmware() >= TargetFirmware_8_0_0 && AMS_UNLIKELY(IsHandlerRestricted(info))) { + return SmcResult::NotPermitted; + } + + /* Invoke the smc. */ + return info.handler(args); + } + + } + + void ConfigureSmcHandlersForTargetFirmware() { + const auto target_fw = GetTargetFirmware(); + + if (target_fw < TargetFirmware_5_0_0) { + g_user_handlers[DecryptAndImportEsDeviceKeyHandlerInfo.function_id & 0xFF] = DecryptAndImportEsDeviceKeyHandlerInfo; + g_user_handlers[DecryptAndImportLotusKeyHandlerInfo.function_id & 0xFF] = DecryptAndImportLotusKeyHandlerInfo; + } + } + + void HandleSmc(int type, SmcArguments &args) { + /* Get the table. */ + const auto &table = GetHandlerTable(static_cast<HandlerType>(type), args.r[0]); + + /* Get the handler info. */ + const auto &info = GetHandlerInfo(table, args.r[0]); + + /* Set the invocation result. */ + args.r[0] = static_cast<u64>(InvokeSmcHandler(info, args)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_handler.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_handler.hpp new file mode 100644 index 00000000..9ccec791 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_handler.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> +#include "secmon_smc_common.hpp" + +namespace ams::secmon::smc { + + using SmcHandler = SmcResult (*)(SmcArguments &args); + + void ConfigureSmcHandlersForTargetFirmware(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_info.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_info.cpp new file mode 100644 index 00000000..11f4a693 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_info.cpp @@ -0,0 +1,479 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../secmon_error.hpp" +#include "../secmon_map.hpp" +#include "../secmon_misc.hpp" +#include "../secmon_page_mapper.hpp" +#include "../secmon_user_power_management.hpp" +#include "secmon_smc_info.hpp" +#include "secmon_smc_power_management.hpp" + +namespace ams::secmon::smc { + + namespace { + + struct KernelConfiguration { + /* Secure Monitor view. */ + using Flags1 = util::BitPack32::Field< 0, 8>; + using Flags0 = util::BitPack32::Field< 8, 8>; + using PhysicalMemorySize = util::BitPack32::Field<16, 2>; + + /* Kernel view, from libmesosphere. */ + using DebugFillMemory = util::BitPack32::Field<0, 1, bool>; + using EnableUserExceptionHandlers = util::BitPack32::Field<DebugFillMemory::Next, 1, bool>; + using EnableUserPmuAccess = util::BitPack32::Field<EnableUserExceptionHandlers::Next, 1, bool>; + using IncreaseThreadResourceLimit = util::BitPack32::Field<EnableUserPmuAccess::Next, 1, bool>; + using DisableDynamicResourceLimits = util::BitPack32::Field<IncreaseThreadResourceLimit::Next, 1, bool>; + using Reserved5 = util::BitPack32::Field<DisableDynamicResourceLimits::Next, 3, u32>; + using UseSecureMonitorPanicCall = util::BitPack32::Field<Reserved5::Next, 1, bool>; + using Reserved9 = util::BitPack32::Field<UseSecureMonitorPanicCall::Next, 7, u32>; + using MemorySize = util::BitPack32::Field<Reserved9::Next, 2, u32>; /* smc::MemorySize = pkg1::MemorySize */ + }; + + constexpr const pkg1::MemorySize DramIdToMemorySize[fuse::DramId_Count] = { + [fuse::DramId_IcosaSamsung4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_IcosaHynix4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_IcosaMicron4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_IowaHynix1y4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_IcosaSamsung6GB] = pkg1::MemorySize_6GB, + [fuse::DramId_HoagHynix1y4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_AulaHynix1y4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_Deprecated7] = pkg1::MemorySize_4GB, + [fuse::DramId_IowaSansung4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_IowaSamsung8GB] = pkg1::MemorySize_8GB, + [fuse::DramId_IowaHynix4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_IowaMicron4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_HoagSamsung4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_HoagSamsung8GB] = pkg1::MemorySize_8GB, + [fuse::DramId_HoagHynix4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_HoagMicron4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_Deprecated16] = pkg1::MemorySize_4GB, + [fuse::DramId_IowaSamsung1y4GBX] = pkg1::MemorySize_4GB, + [fuse::DramId_IowaSamsung1y8GBX] = pkg1::MemorySize_8GB, + [fuse::DramId_HoagSamsung1y4GBX] = pkg1::MemorySize_4GB, + [fuse::DramId_IowaSamsung1z4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_HoagSamsung1z4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_AulaSamsung1z4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_HoagSamsung1y8GBX] = pkg1::MemorySize_8GB, + [fuse::DramId_AulaSamsung1y4GBX] = pkg1::MemorySize_4GB, + [fuse::DramId_IowaMicron1y4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_HoagMicron1y4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_AulaMicron1y4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_AulaSamsung1y8GBX] = pkg1::MemorySize_8GB, + [fuse::DramId_IowaX1X2Samsung4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_HoagX1X2Samsung4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_AulaX1X2Samsung4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_IowaSamsung4GBY] = pkg1::MemorySize_4GB, + [fuse::DramId_HoagSamsung4GBY] = pkg1::MemorySize_4GB, + [fuse::DramId_AulaSamsung4GBY] = pkg1::MemorySize_4GB, + }; + + constexpr const pkg1::MemoryMode MemoryModes[] = { + pkg1::MemoryMode_Auto, + + pkg1::MemoryMode_4GB, + pkg1::MemoryMode_4GBAppletDev, + pkg1::MemoryMode_4GBSystemDev, + + pkg1::MemoryMode_6GB, + pkg1::MemoryMode_6GBAppletDev, + + pkg1::MemoryMode_8GB, + }; + + constexpr bool IsValidMemoryMode(pkg1::MemoryMode mode) { + for (const auto known_mode : MemoryModes) { + if (mode == known_mode) { + return true; + } + } + return false; + } + + pkg1::MemoryMode SanitizeMemoryMode(pkg1::MemoryMode mode) { + if (IsValidMemoryMode(mode)) { + return mode; + } + return pkg1::MemoryMode_Auto; + } + + pkg1::MemorySize GetAvailableMemorySize(pkg1::MemorySize size) { + return std::min(GetPhysicalMemorySize(), size); + } + + pkg1::MemoryMode GetMemoryMode(pkg1::MemoryMode mode) { + /* Sanitize the mode. */ + mode = SanitizeMemoryMode(mode); + + /* If the mode is auto, construct the memory mode. */ + if (mode == pkg1::MemoryMode_Auto) { + return pkg1::MakeMemoryMode(GetPhysicalMemorySize(), pkg1::MemoryArrange_Normal); + } else { + const auto mode_size = GetMemorySize(mode); + const auto mode_arrange = GetMemoryArrange(mode); + const auto size = GetAvailableMemorySize(mode_size); + const auto arrange = (size == mode_size) ? mode_arrange : pkg1::MemoryArrange_Normal; + return pkg1::MakeMemoryMode(size, arrange); + } + } + + u32 GetMemoryMode() { + /* Unless development function is enabled, we're 4 GB. */ + u32 memory_mode = pkg1::MemoryMode_4GB; + + if (const auto &bcd = GetBootConfig().data; bcd.IsDevelopmentFunctionEnabled()) { + memory_mode = GetMemoryMode(bcd.GetMemoryMode()); + } + + return memory_mode; + } + + u32 GetKernelConfiguration() { + pkg1::MemorySize memory_size = pkg1::MemorySize_4GB; + util::BitPack32 value = {}; + + if (const auto &bcd = GetBootConfig().data; bcd.IsDevelopmentFunctionEnabled()) { + memory_size = GetMemorySize(GetMemoryMode(bcd.GetMemoryMode())); + + value.Set<KernelConfiguration::Flags1>(bcd.GetKernelFlags1()); + value.Set<KernelConfiguration::Flags0>(bcd.GetKernelFlags0()); + } + + value.Set<KernelConfiguration::PhysicalMemorySize>(memory_size); + + /* Exosphere extensions. */ + const auto &sc = GetSecmonConfiguration(); + + if (!sc.DisableUserModeExceptionHandlers()) { + value.Set<KernelConfiguration::EnableUserExceptionHandlers>(true); + } + + if (sc.EnableUserModePerformanceCounterAccess()) { + value.Set<KernelConfiguration::EnableUserPmuAccess>(true); + } + + return value.value; + } + + constinit u64 g_payload_address = 0; + constinit bool g_set_true_target_firmware = false; + + SmcResult GetConfig(SmcArguments &args, bool kern) { + switch (static_cast<ConfigItem>(args.r[1])) { + case ConfigItem::DisableProgramVerification: + args.r[1] = GetBootConfig().signed_data.IsProgramVerificationDisabled(); + break; + case ConfigItem::DramId: + args.r[1] = fuse::GetDramId(); + break; + case ConfigItem::SecurityEngineInterruptNumber: + args.r[1] = SecurityEngineUserInterruptId; + break; + case ConfigItem::FuseVersion: + args.r[1] = fuse::GetExpectedFuseVersion(GetTargetFirmware()); + break; + case ConfigItem::HardwareType: + args.r[1] = fuse::GetHardwareType(); + break; + case ConfigItem::HardwareState: + args.r[1] = fuse::GetHardwareState(); + break; + case ConfigItem::IsRecoveryBoot: + args.r[1] = IsRecoveryBoot(); + break; + case ConfigItem::DeviceId: + args.r[1] = fuse::GetDeviceId(); + break; + case ConfigItem::BootReason: + { + /* This was removed in firmware 4.0.0. */ + if (GetTargetFirmware() >= TargetFirmware_4_0_0) { + return SmcResult::InvalidArgument; + } + + args.r[1] = GetDeprecatedBootReason(); + } + break; + case ConfigItem::MemoryMode: + args.r[1] = GetMemoryMode(); + break; + case ConfigItem::IsDevelopmentFunctionEnabled: + args.r[1] = GetSecmonConfiguration().IsDevelopmentFunctionEnabled(kern) || GetBootConfig().data.IsDevelopmentFunctionEnabled(); + break; + case ConfigItem::KernelConfiguration: + args.r[1] = GetKernelConfiguration(); + break; + case ConfigItem::IsChargerHiZModeEnabled: + args.r[1] = IsChargerHiZModeEnabled(); + break; + case ConfigItem::RetailInteractiveDisplayState: + args.r[1] = fuse::GetRetailInteractiveDisplayState(); + break; + case ConfigItem::RegulatorType: + args.r[1] = fuse::GetRegulator(); + break; + case ConfigItem::DeviceUniqueKeyGeneration: + args.r[1] = fuse::GetDeviceUniqueKeyGeneration(); + break; + case ConfigItem::Package2Hash: + { + /* Only allow getting the package2 hash in recovery boot. */ + if (!IsRecoveryBoot()) { + return SmcResult::InvalidArgument; + } + + /* Get the hash. */ + se::Sha256Hash tmp_hash; + GetPackage2Hash(std::addressof(tmp_hash)); + + /* Copy it out. */ + static_assert(sizeof(args) - sizeof(args.r[0]) >= sizeof(tmp_hash)); + std::memcpy(std::addressof(args.r[1]), std::addressof(tmp_hash), sizeof(tmp_hash)); + } + break; + case ConfigItem::ExosphereApiVersion: + /* Get information about the current exosphere version. */ + if (kern || g_set_true_target_firmware) { + args.r[1] = (static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MAJOR & 0xFF) << 56) | + (static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MINOR & 0xFF) << 48) | + (static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MICRO & 0xFF) << 40) | + (static_cast<u64>(GetKeyGeneration()) << 32) | + (static_cast<u64>(GetTargetFirmware()) << 0); + } else { + return SmcResult::NotInitialized; + } + break; + case ConfigItem::ExosphereNeedsReboot: + /* We are executing, so we aren't in the process of rebooting. */ + args.r[1] = 0; + break; + case ConfigItem::ExosphereNeedsShutdown: + /* We are executing, so we aren't in the process of shutting down. */ + args.r[1] = 0; + break; + case ConfigItem::ExosphereGitCommitHash: + /* Get information about the current exosphere git commit hash. */ + args.r[1] = ATMOSPHERE_GIT_HASH; + break; + case ConfigItem::ExosphereHasRcmBugPatch: + /* Get information about whether this unit has the RCM bug patched. */ + args.r[1] = fuse::HasRcmVulnerabilityPatch(); + break; + case ConfigItem::ExosphereBlankProdInfo: + /* Get whether this unit should simulate a "blanked" PRODINFO. */ + args.r[1] = GetSecmonConfiguration().ShouldUseBlankCalibrationBinary(); + break; + case ConfigItem::ExosphereAllowCalWrites: + /* Get whether this unit should allow writing to the calibration partition. */ + args.r[1] = (GetEmummcConfiguration().IsEmummcActive() || GetSecmonConfiguration().AllowWritingToCalibrationBinarySysmmc()); + break; + case ConfigItem::ExosphereEmummcType: + /* Get what kind of emummc this unit has active. */ + /* NOTE: This may return values other than 1 in the future. */ + args.r[1] = (GetEmummcConfiguration().IsEmummcActive() ? 1 : 0); + break; + case ConfigItem::ExospherePayloadAddress: + /* Gets the physical address of the reboot payload buffer, if one exists. */ + if (g_payload_address != 0) { + args.r[1] = g_payload_address; + } else { + return SmcResult::NotInitialized; + } + break; + case ConfigItem::ExosphereLogConfiguration: + /* Get the log configuration. */ + args.r[1] = (static_cast<u64>(static_cast<u8>(secmon::GetLogPort())) << 32) | static_cast<u64>(secmon::GetLogBaudRate()); + break; + case ConfigItem::ExosphereForceEnableUsb30: + /* Get whether usb 3.0 should be force-enabled. */ + args.r[1] = GetSecmonConfiguration().IsUsb30ForceEnabled(); + break; + case ConfigItem::ExosphereSupportedHosVersion: + /* Get information about the supported hos version. */ + args.r[1] = (static_cast<u64>(ATMOSPHERE_SUPPORTED_HOS_VERSION_MAJOR & 0xFF) << 24) | + (static_cast<u64>(ATMOSPHERE_SUPPORTED_HOS_VERSION_MINOR & 0xFF) << 16) | + (static_cast<u64>(ATMOSPHERE_SUPPORTED_HOS_VERSION_MICRO & 0xFF) << 8); + break; + case ConfigItem::ExosphereApproximateApiVersion: + /* Get information about the current exosphere version. */ + if (!g_set_true_target_firmware) { + args.r[1] = (static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MAJOR & 0xFF) << 56) | + (static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MINOR & 0xFF) << 48) | + (static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MICRO & 0xFF) << 40) | + (static_cast<u64>(GetKeyGeneration()) << 32) | + (static_cast<u64>(GetTargetFirmware()) << 0); + } else { + return SmcResult::Busy; + } + break; + default: + return SmcResult::InvalidArgument; + } + + return SmcResult::Success; + } + + SmcResult SetConfig(SmcArguments &args) { + const auto soc_type = GetSocType(); + + switch (static_cast<ConfigItem>(args.r[1])) { + case ConfigItem::IsChargerHiZModeEnabled: + /* Configure the HiZ mode. */ + SetChargerHiZModeEnabled(static_cast<bool>(args.r[3])); + break; + case ConfigItem::ExosphereApiVersion: + if (!g_set_true_target_firmware) { + ::ams::secmon::impl::SetTargetFirmware(static_cast<ams::TargetFirmware>(args.r[3] & 0xFFFFFFFF)); + g_set_true_target_firmware = true; + } else { + return SmcResult::Busy; + } + break; + case ConfigItem::ExosphereNeedsReboot: + if (soc_type == fuse::SocType_Erista) { + switch (static_cast<UserRebootType>(args.r[3])) { + case UserRebootType_None: + break; + case UserRebootType_ToRcm: + PerformUserRebootToRcm(); + break; + case UserRebootType_ToPayload: + PerformUserRebootToPayload(); + break; + case UserRebootType_ToFatalError: + PerformUserRebootToFatalError(); + break; + case UserRebootType_ByPmic: + PerformUserRebootByPmic(); + break; + default: + return SmcResult::InvalidArgument; + } + } else /* if (soc_type == fuse::SocType_Mariko) */ { + switch (static_cast<UserRebootType>(args.r[3])) { + case UserRebootType_ToFatalError: + PerformUserRebootToFatalError(); + break; + case UserRebootType_ByPmic: + PerformUserRebootByPmic(); + break; + default: + return SmcResult::InvalidArgument; + } + } + break; + case ConfigItem::ExosphereNeedsShutdown: + if (args.r[3] != 0) { + PerformUserShutDown(); + } + break; + case ConfigItem::ExospherePayloadAddress: + if (g_payload_address == 0) { + if (secmon::IsPhysicalMemoryAddress(args.r[2])) { + g_payload_address = args.r[2]; + } else { + return SmcResult::InvalidArgument; + } + } else { + return SmcResult::Busy; + } + break; + default: + return SmcResult::InvalidArgument; + } + + return SmcResult::Success; + } + + } + + SmcResult SmcGetConfigUser(SmcArguments &args) { + return GetConfig(args, false); + } + + SmcResult SmcGetConfigKern(SmcArguments &args) { + return GetConfig(args, true); + } + + SmcResult SmcSetConfig(SmcArguments &args) { + return SetConfig(args); + } + + /* This is an atmosphere extension smc. */ + SmcResult SmcGetEmummcConfig(SmcArguments &args) { + /* Decode arguments. */ + const auto mmc = static_cast<EmummcMmc>(args.r[1]); + const uintptr_t user_address = args.r[2]; + const uintptr_t user_offset = user_address % 4_KB; + + /* Validate arguments. */ + /* NOTE: In the future, configuration for non-NAND storage may be implemented. */ + SMC_R_UNLESS(mmc == EmummcMmc_Nand, NotSupported); + SMC_R_UNLESS(user_offset + 2 * sizeof(EmummcFilePath) <= 4_KB, InvalidArgument); + + /* Get the emummc config. */ + const auto &cfg = GetEmummcConfiguration(); + static_assert(sizeof(cfg.file_cfg) == sizeof(EmummcFilePath)); + static_assert(sizeof(cfg.emu_dir_path) == sizeof(EmummcFilePath)); + + /* Clear the output. */ + constexpr size_t InlineOutputSize = sizeof(args) - sizeof(args.r[0]); + u8 * const inline_output = static_cast<u8 *>(static_cast<void *>(std::addressof(args.r[1]))); + std::memset(inline_output, 0, InlineOutputSize); + + /* Copy out the configuration. */ + { + /* Map the user output page. */ + AtmosphereUserPageMapper mapper(user_address); + SMC_R_UNLESS(mapper.Map(), InvalidArgument); + + /* Copy the base configuration. */ + static_assert(sizeof(cfg.base_cfg) <= InlineOutputSize); + std::memcpy(inline_output, std::addressof(cfg.base_cfg), sizeof(cfg.base_cfg)); + + /* Copy out type-specific data. */ + switch (cfg.base_cfg.type) { + case EmummcType_None: + /* No additional configuration needs to be copied. */ + break; + case EmummcType_Partition: + /* Copy the partition config. */ + static_assert(sizeof(cfg.base_cfg) + sizeof(cfg.partition_cfg) <= InlineOutputSize); + std::memcpy(inline_output + sizeof(cfg.base_cfg), std::addressof(cfg.partition_cfg), sizeof(cfg.partition_cfg)); + break; + case EmummcType_File: + /* Copy the file config. */ + SMC_R_UNLESS(mapper.CopyToUser(user_address, std::addressof(cfg.file_cfg), sizeof(cfg.file_cfg)), InvalidArgument); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Copy the redirection directory path to the user page. */ + SMC_R_UNLESS(mapper.CopyToUser(user_address + sizeof(EmummcFilePath), std::addressof(cfg.emu_dir_path), sizeof(cfg.emu_dir_path)), InvalidArgument); + } + + return SmcResult::Success; + } + + /* For exosphere's usage. */ + pkg1::MemorySize GetPhysicalMemorySize() { + const auto dram_id = fuse::GetDramId(); + AMS_ABORT_UNLESS(dram_id < fuse::DramId_Count); + return DramIdToMemorySize[dram_id]; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_info.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_info.hpp new file mode 100644 index 00000000..605bbf33 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_info.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> +#include "secmon_smc_common.hpp" + +namespace ams::secmon::smc { + + enum class ConfigItem : u32 { + /* Standard config items. */ + DisableProgramVerification = 1, + DramId = 2, + SecurityEngineInterruptNumber = 3, + FuseVersion = 4, + HardwareType = 5, + HardwareState = 6, + IsRecoveryBoot = 7, + DeviceId = 8, + BootReason = 9, + MemoryMode = 10, + IsDevelopmentFunctionEnabled = 11, + KernelConfiguration = 12, + IsChargerHiZModeEnabled = 13, + RetailInteractiveDisplayState = 14, + RegulatorType = 15, + DeviceUniqueKeyGeneration = 16, + Package2Hash = 17, + + /* Extension config items for exosphere. */ + ExosphereApiVersion = 65000, + ExosphereNeedsReboot = 65001, + ExosphereNeedsShutdown = 65002, + ExosphereGitCommitHash = 65003, + ExosphereHasRcmBugPatch = 65004, + ExosphereBlankProdInfo = 65005, + ExosphereAllowCalWrites = 65006, + ExosphereEmummcType = 65007, + ExospherePayloadAddress = 65008, + ExosphereLogConfiguration = 65009, + ExosphereForceEnableUsb30 = 65010, + ExosphereSupportedHosVersion = 65011, + ExosphereApproximateApiVersion = 65012, + }; + + SmcResult SmcGetConfigUser(SmcArguments &args); + SmcResult SmcGetConfigKern(SmcArguments &args); + SmcResult SmcSetConfig(SmcArguments &args); + + /* This is an atmosphere extension smc. */ + SmcResult SmcGetEmummcConfig(SmcArguments &args); + + /* For other parts of exosphere. */ + pkg1::MemorySize GetPhysicalMemorySize(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_memory_access.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_memory_access.cpp new file mode 100644 index 00000000..50b8ae8f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_memory_access.cpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../secmon_error.hpp" +#include "../secmon_page_mapper.hpp" +#include "secmon_smc_memory_access.hpp" + +namespace ams::secmon::smc { + + namespace { + + enum IramCopyType { + IramCopyType_FromIramToDram = 0, + IramCopyType_FromDramToIram = 1, + IramCopyType_Count, + }; + + struct IramCopyOption { + using CopyType = util::BitPack32::Field<0, 1, IramCopyType>; + }; + + } + + /* This is an atmosphere extension smc. */ + SmcResult SmcIramCopy(SmcArguments &args) { + /* Decode arguments. */ + const uintptr_t dram_address = args.r[1]; + const uintptr_t iram_address = args.r[2]; + const size_t size = args.r[3]; + const util::BitPack32 option = { static_cast<u32>(args.r[4]) }; + + const auto copy_type = option.Get<IramCopyOption::CopyType>(); + + /* Validate arguments. */ + SMC_R_UNLESS(copy_type < IramCopyType_Count, InvalidArgument); + + { + /* Map the pages. */ + AtmosphereUserPageMapper dram_mapper(dram_address); + AtmosphereIramPageMapper iram_mapper(iram_address); + SMC_R_UNLESS(dram_mapper.Map(), InvalidArgument); + SMC_R_UNLESS(iram_mapper.Map(), InvalidArgument); + + /* Get the ranges we're copying. */ + const void * const src = (copy_type == IramCopyType_FromIramToDram) ? iram_mapper.GetPointerTo(iram_address, size) : dram_mapper.GetPointerTo(dram_address, size); + void * const dst = (copy_type == IramCopyType_FromIramToDram) ? dram_mapper.GetPointerTo(dram_address, size) : iram_mapper.GetPointerTo(iram_address, size); + SMC_R_UNLESS(src != nullptr, InvalidArgument); + SMC_R_UNLESS(dst != nullptr, InvalidArgument); + + /* Copy the data. */ + std::memcpy(dst, src, size); + } + + return SmcResult::Success; + } + + SmcResult SmcWriteAddress(SmcArguments &args) { + /* NOTE: This smc was deprecated in Atmosphère 0.13.0. */ + AMS_UNUSED(args); + return SmcResult::NotSupported; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_memory_access.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_memory_access.hpp new file mode 100644 index 00000000..ecced92b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_memory_access.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> +#include "secmon_smc_common.hpp" + +namespace ams::secmon::smc { + + /* This is an atmosphere extension smc. */ + SmcResult SmcIramCopy(SmcArguments &args); + SmcResult SmcWriteAddress(SmcArguments &args); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_power_management.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_power_management.cpp new file mode 100644 index 00000000..7747bb09 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_power_management.cpp @@ -0,0 +1,567 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../secmon_cache.hpp" +#include "../secmon_cpu_context.hpp" +#include "../secmon_error.hpp" +#include "../secmon_misc.hpp" +#include "secmon_smc_power_management.hpp" +#include "secmon_smc_se_lock.hpp" + +#include "sc7fw_bin.h" + +namespace ams::secmon { + + /* Declare assembly functionality. */ + void *GetCoreExceptionStackVirtual(); + +} + +namespace ams::secmon::smc { + + /* Declare assembly power-management functionality. */ + void PivotStackAndInvoke(void *stack, void (*function)()); + void FinalizePowerOff(); + + namespace { + + constexpr inline const uintptr_t PMC = MemoryRegionVirtualDevicePmc.GetAddress(); + constexpr inline const uintptr_t APB_MISC = MemoryRegionVirtualDeviceApbMisc.GetAddress(); + constexpr inline const uintptr_t GPIO = MemoryRegionVirtualDeviceGpio.GetAddress(); + constexpr inline const uintptr_t CLK_RST = MemoryRegionVirtualDeviceClkRst.GetAddress(); + constexpr inline const uintptr_t EVP = secmon::MemoryRegionVirtualDeviceExceptionVectors.GetAddress(); + constexpr inline const uintptr_t FLOW_CTLR = MemoryRegionVirtualDeviceFlowController.GetAddress(); + constexpr inline const uintptr_t AHB_ARBC = MemoryRegionVirtualDeviceSystem.GetAddress(); + + constexpr inline uintptr_t CommonSmcStackTop = MemoryRegionVirtualTzramVolatileData.GetEndAddress() - (0x80 * (NumCores - 1)); + + enum PowerStateType { + PowerStateType_StandBy = 0, + PowerStateType_PowerDown = 1, + }; + + enum PowerStateId { + PowerStateId_Sc7 = 27, + }; + + /* http://infocenter.arm.com/help/topic/com.arm.doc.den0022d/Power_State_Coordination_Interface_PDD_v1_1_DEN0022D.pdf Page 46 */ + struct SuspendCpuPowerState { + using StateId = util::BitPack32::Field< 0, 16, PowerStateId>; + using StateType = util::BitPack32::Field<16, 1, PowerStateType>; + using PowerLevel = util::BitPack32::Field<24, 2, u32>; + }; + + constinit bool g_charger_hi_z_mode_enabled = false; + + constinit const reg::BitsMask CpuPowerGateStatusMasks[NumCores] = { + PMC_REG_BITS_MASK(PWRGATE_STATUS_CE0), + PMC_REG_BITS_MASK(PWRGATE_STATUS_CE1), + PMC_REG_BITS_MASK(PWRGATE_STATUS_CE2), + PMC_REG_BITS_MASK(PWRGATE_STATUS_CE3), + }; + + constinit const APBDEV_PMC_PWRGATE_TOGGLE_PARTID CpuPowerGateTogglePartitionIds[NumCores] = { + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE0, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE1, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE2, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE3, + }; + + bool IsCpuPoweredOn(const reg::BitsMask mask) { + return reg::HasValue(PMC + APBDEV_PMC_PWRGATE_STATUS, REG_BITS_VALUE_FROM_MASK(mask, APBDEV_PMC_PWRGATE_STATUS_STATUS_ON)); + } + + void PowerOnCpu(const reg::BitsMask mask, u32 toggle_partid) { + /* If the cpu is already on, we have nothing to do. */ + if (IsCpuPoweredOn(mask)) { + return; + } + + /* Wait until nothing is being powergated. */ + int timeout = 5000; + while (true) { + if (reg::HasValue(PMC + APBDEV_PMC_PWRGATE_TOGGLE, PMC_REG_BITS_ENUM(PWRGATE_TOGGLE_START, DISABLE))) { + break; + } + + util::WaitMicroSeconds(1); + if ((--timeout) < 0) { + /* NOTE: Nintendo doesn't do any error handling here... */ + return; + } + } + + /* Toggle on the cpu partition. */ + reg::Write(PMC + APBDEV_PMC_PWRGATE_TOGGLE, PMC_REG_BITS_ENUM (PWRGATE_TOGGLE_START, ENABLE), + PMC_REG_BITS_VALUE(PWRGATE_TOGGLE_PARTID, toggle_partid)); + + /* Wait up to 5000 us for the powergate to complete. */ + timeout = 5000; + while (true) { + if (IsCpuPoweredOn(mask)) { + break; + } + + util::WaitMicroSeconds(1); + if ((--timeout) < 0) { + /* NOTE: Nintendo doesn't do any error handling here... */ + return; + } + } + } + + void ResetCpu(int which_core) { + reg::Write(CLK_RST + CLK_RST_CONTROLLER_RST_CPUG_CMPLX_SET, REG_BITS_VALUE(which_core + 0x00, 1, 1), /* CPURESETn */ + REG_BITS_VALUE(which_core + 0x10, 1, 1)); /* CORERESETn */ + } + + void StartCpu(int which_core) { + reg::Write(CLK_RST + CLK_RST_CONTROLLER_RST_CPUG_CMPLX_CLR, REG_BITS_VALUE(which_core + 0x00, 1, 1), /* CPURESETn */ + REG_BITS_VALUE(which_core + 0x10, 1, 1)); /* CORERESETn */ + } + + void PowerOffCpuImpl() { + /* Get the current core id. */ + const auto core_id = hw::GetCurrentCoreId(); + + /* Configure the flow controller to prepare for shutting down the current core. */ + flow::SetCpuCsr(core_id, FLOW_CTLR_CPUN_CSR_ENABLE_EXT_POWERGATE_CPU_ONLY); + flow::SetHaltCpuEvents(core_id, false); + flow::SetCc4Ctrl(core_id, 0); + + /* Save the core's context for restoration on next power-on. */ + SaveDebugRegisters(); + SetCoreOff(); + + /* Ensure there are no pending memory transactions prior to our power-down. */ + FlushEntireDataCache(); + + /* Finalize our powerdown and wait for an interrupt. */ + FinalizePowerOff(); + } + + void ValidateSocStateForSuspend() { + /* Validate that all other cores are off. */ + AMS_ABORT_UNLESS(reg::HasValue(PMC + APBDEV_PMC_PWRGATE_STATUS, PMC_REG_BITS_VALUE(PWRGATE_STATUS_CE123, 0))); + + /* Validate that the bpmp is appropriately halted. */ + const bool jtag = IsJtagEnabled(); + AMS_ABORT_UNLESS(reg::Read(FLOW_CTLR + FLOW_CTLR_HALT_COP_EVENTS) == reg::Encode(FLOW_REG_BITS_ENUM (HALT_COP_EVENTS_MODE, FLOW_MODE_STOP), + FLOW_REG_BITS_ENUM_SEL(HALT_COP_EVENTS_JTAG, jtag, ENABLED, DISABLED))); + + /* Further validations aren't guaranteed on < 6.0.0. */ + if (GetTargetFirmware() < TargetFirmware_6_0_0) { + return; + } + + /* Validate that USB2, APB-DMA, AHB-DMA are held in reset. */ + AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_RST_DEVICES_H, CLK_RST_REG_BITS_ENUM(RST_DEVICES_H_SWR_USB2_RST, ENABLE), + CLK_RST_REG_BITS_ENUM(RST_DEVICES_H_SWR_APBDMA_RST, ENABLE), + CLK_RST_REG_BITS_ENUM(RST_DEVICES_H_SWR_AHBDMA_RST, ENABLE))); + + /* Validate that USBD is held in reset. */ + AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_REG_BITS_ENUM(RST_DEVICES_L_SWR_USBD_RST, ENABLE))); + + /* Validate that AHB-DMA, USB, USB2, COP are not allowed to arbitrate on the AHB. */ + AMS_ABORT_UNLESS(reg::HasValue(AHB_ARBC + AHB_ARBITRATION_DISABLE, AHB_REG_BITS_ENUM(ARBITRATION_DISABLE_COP, DISABLE), + AHB_REG_BITS_ENUM(ARBITRATION_DISABLE_AHBDMA, DISABLE), + AHB_REG_BITS_ENUM(ARBITRATION_DISABLE_USB, DISABLE), + AHB_REG_BITS_ENUM(ARBITRATION_DISABLE_USB2, DISABLE))); + + /* Validate that the GPIO controller has clock enabled. */ + AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_L_CLK_ENB_GPIO, ENABLE))); + + /* Validate that both FUSE and KFUSE have clock enabled. */ + AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_CLK_OUT_ENB_H, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_H_CLK_ENB_FUSE, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_H_CLK_ENB_KFUSE, ENABLE))); + + /* Validate that all of IRAM has clock enabled. */ + AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_CLK_OUT_ENB_U, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_U_CLK_ENB_IRAMA, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_U_CLK_ENB_IRAMB, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_U_CLK_ENB_IRAMC, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_U_CLK_ENB_IRAMD, ENABLE))); + + /* Validate that ACTMON has clock enabled. */ + AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_CLK_OUT_ENB_V, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_V_CLK_ENB_ACTMON, ENABLE))); + + /* Validate that ENTROPY has clock enabled. */ + AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_CLK_OUT_ENB_W, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_W_CLK_ENB_ENTROPY, ENABLE))); + } + + void GenerateCryptographicallyRandomBytes(void * const dst, int size) { + /* Flush the region we're about to fill to ensure consistency with the SE. */ + hw::FlushDataCache(dst, size); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Generate random bytes. */ + se::GenerateRandomBytes(dst, size); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Flush to ensure the CPU sees consistent data for the region. */ + hw::FlushDataCache(dst, size); + hw::DataSynchronizationBarrierInnerShareable(); + } + + void SaveSecureContextForErista() { + /* Generate a random key source. */ + util::AlignedBuffer<hw::DataCacheLineSize, se::AesBlockSize> key_source; + GenerateCryptographicallyRandomBytes(key_source, se::AesBlockSize); + + const u32 * const key_source_32 = reinterpret_cast<const u32 *>(static_cast<u8 *>(key_source)); + + /* Ensure that the key source registers are not locked. */ + AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_KeySourceReadWrite) != pmc::LockState::Locked); + + /* Write the key source, lock writes to the key source, and verify that the key source is write-locked. */ + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH24, key_source_32[0]); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH25, key_source_32[1]); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH26, key_source_32[2]); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH27, key_source_32[3]); + pmc::LockSecureRegister(pmc::SecureRegister_KeySourceWrite); + AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_KeySourceWrite) == pmc::LockState::Locked); + + /* Verify the key source is correct in registers, and read-lock the key source registers. */ + AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH24) == key_source_32[0]); + AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH25) == key_source_32[1]); + AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH26) == key_source_32[2]); + AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH27) == key_source_32[3]); + pmc::LockSecureRegister(pmc::SecureRegister_KeySourceRead); + + /* Ensure that the key source registers are locked. */ + AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_KeySourceReadWrite) == pmc::LockState::Locked); + + /* Generate a random kek into keyslot 2. */ + se::SetRandomKey(pkg1::AesKeySlot_TzramSaveKek); + + /* Verify that the se is in a validate state, context save, and validate again. */ + { + se::ValidateErrStatus(); + ON_SCOPE_EXIT { se::ValidateErrStatus(); }; + + { + /* Transition to non-secure mode for the duration of the context save operation. */ + se::SetSecure(false); + ON_SCOPE_EXIT { se::SetSecure(true); }; + + /* Get a pointer to the context storage. */ + se::Context * const context = MemoryRegionVirtualDramSecureDataStoreSecurityEngineState.GetPointer<se::Context>(); + static_assert(MemoryRegionVirtualDramSecureDataStoreSecurityEngineState.GetSize() == sizeof(*context)); + + /* Save the context. */ + se::SaveContext(context); + + /* Ensure that the cpu sees consistent data. */ + hw::FlushDataCache(context, sizeof(*context)); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Write the context pointer to pmc scratch, so that the bootrom will restore it on wake. */ + reg::Write(PMC + APBDEV_PMC_SCRATCH43, MemoryRegionPhysicalDramSecureDataStoreSecurityEngineState.GetAddress()); + } + } + + /* Clear keyslot 3, and then derive the save key. */ + se::ClearAesKeySlot(pkg1::AesKeySlot_TzramSaveKey); + se::SetEncryptedAesKey256(pkg1::AesKeySlot_TzramSaveKey, pkg1::AesKeySlot_TzramSaveKek, key_source, se::AesBlockSize); + + /* Declare a temporary block to be used as both iv and mac. */ + u32 temp_block[se::AesBlockSize / sizeof(u32)] = {}; + + /* Ensure that the SE sees consistent data for tzram. */ + const void * const tzram_save_src = MemoryRegionVirtualTzramReadOnlyAlias.GetPointer<u8>() + MemoryRegionVirtualTzramVolatileData.GetSize() + MemoryRegionVirtualTzramVolatileStack.GetSize(); + void * const tzram_save_dst = MemoryRegionVirtualIramSc7Work.GetPointer<void>(); + constexpr size_t TzramSaveSize = MemoryRegionVirtualDramSecureDataStoreTzram.GetSize(); + + hw::FlushDataCache(tzram_save_src, TzramSaveSize); + hw::FlushDataCache(tzram_save_dst, TzramSaveSize); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Encrypt tzram using our random key. */ + se::EncryptAes256Cbc(tzram_save_dst, TzramSaveSize, pkg1::AesKeySlot_TzramSaveKey, tzram_save_src, TzramSaveSize, temp_block, se::AesBlockSize); + hw::FlushDataCache(tzram_save_dst, TzramSaveSize); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Copy the data from work space to the secure storage destination. */ + void * const tzram_store_dst = MemoryRegionVirtualDramSecureDataStoreTzram.GetPointer<void>(); + std::memcpy(tzram_store_dst, tzram_save_dst, TzramSaveSize); + hw::FlushDataCache(tzram_store_dst, TzramSaveSize); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Compute cmac of tzram into our temporary block. */ + se::ComputeAes256Cmac(temp_block, se::AesBlockSize, pkg1::AesKeySlot_TzramSaveKey, tzram_save_src, TzramSaveSize); + + /* Ensure that the cmac registers are not locked. */ + AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_CmacReadWrite) != pmc::LockState::Locked); + + /* Write the cmac, lock writes to the cmac, and verify that the cmac is write-locked. */ + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH112, temp_block[0]); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH113, temp_block[1]); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH114, temp_block[2]); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH115, temp_block[3]); + pmc::LockSecureRegister(pmc::SecureRegister_CmacWrite); + AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_CmacWrite) == pmc::LockState::Locked); + + /* Verify the key source is correct in registers, and read-lock the key source registers. */ + AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH112) == temp_block[0]); + AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH113) == temp_block[1]); + AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH114) == temp_block[2]); + AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH115) == temp_block[3]); + pmc::LockSecureRegister(pmc::SecureRegister_CmacRead); + + /* Ensure that the key source registers are locked. */ + AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_CmacReadWrite) == pmc::LockState::Locked); + } + + void SaveSecureContextForMariko() { + /* Save security engine context to TZRAM SE carveout (inaccessible to cpu). */ + se::SaveContextAutomatic(); + + /* Save TZRAM to shadow-TZRAM in always-on power domain. */ + se::SaveTzramAutomatic(); + } + + void SaveSecureContext() { + /* Save the appropriate secure context. */ + const auto soc_type = GetSocType(); + if (soc_type == fuse::SocType_Erista) { + SaveSecureContextForErista(); + } else /* if (soc_type == fuse::SocType_Mariko) */ { + SaveSecureContextForMariko(); + } + + /* Save the debug code. */ + #if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING) + { + const void * const debug_code_src = MemoryRegionVirtualDebugCode.GetPointer<void>(); + void * const debug_code_dst = MemoryRegionVirtualDramDebugDataStore.GetPointer<void>(); + std::memcpy(debug_code_dst, debug_code_src, MemoryRegionVirtualDebugCode.GetSize()); + hw::FlushDataCache(debug_code_dst, MemoryRegionVirtualDebugCode.GetSize()); + hw::DataSynchronizationBarrierInnerShareable(); + } + #endif + } + + void LoadAndStartSc7BpmpFirmware() { + /* Set BPMP reset. */ + reg::Write(CLK_RST + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SET_SET_COP_RST, ENABLE)); + + /* Set the PMC as insecure, so that the BPMP firmware can access it. */ + reg::ReadWrite(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG0_0, SLAVE_SECURITY_REG_BITS_ENUM(0, PMC, DISABLE)); + + /* Set the exception vectors for the bpmp. RESET should point to RESET, all others should point to generic exception/panic. */ + constexpr const u32 Sc7FirmwareResetVector = static_cast<u32>(MemoryRegionPhysicalIramSc7Firmware.GetAddress() + 0x0); + constexpr const u32 Sc7FirmwarePanicVector = static_cast<u32>(MemoryRegionPhysicalIramSc7Firmware.GetAddress() + 0x4); + + reg::Write(EVP + EVP_COP_RESET_VECTOR, Sc7FirmwareResetVector); + reg::Write(EVP + EVP_COP_UNDEF_VECTOR, Sc7FirmwarePanicVector); + reg::Write(EVP + EVP_COP_SWI_VECTOR, Sc7FirmwarePanicVector); + reg::Write(EVP + EVP_COP_PREFETCH_ABORT_VECTOR, Sc7FirmwarePanicVector); + reg::Write(EVP + EVP_COP_DATA_ABORT_VECTOR, Sc7FirmwarePanicVector); + reg::Write(EVP + EVP_COP_RSVD_VECTOR, Sc7FirmwarePanicVector); + reg::Write(EVP + EVP_COP_IRQ_VECTOR, Sc7FirmwarePanicVector); + reg::Write(EVP + EVP_COP_FIQ_VECTOR, Sc7FirmwarePanicVector); + + /* Disable activity monitor bpmp monitoring, so that we don't panic upon bpmp wake. */ + actmon::StopMonitoringBpmp(); + + /* Load the bpmp firmware. */ + void * const sc7fw_load_address = MemoryRegionVirtualIramSc7Firmware.GetPointer<void>(); + std::memcpy(sc7fw_load_address, sc7fw_bin, sc7fw_bin_size); + hw::FlushDataCache(sc7fw_load_address, sc7fw_bin_size); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Ensure that the bpmp firmware was loaded. */ + AMS_ABORT_UNLESS(crypto::IsSameBytes(sc7fw_load_address, sc7fw_bin, sc7fw_bin_size)); + + /* Clear BPMP reset. */ + reg::Write(CLK_RST + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_CLR_CLR_COP_RST, ENABLE)); + + /* Start the bpmp. */ + reg::Write(FLOW_CTLR + FLOW_CTLR_HALT_COP_EVENTS, FLOW_REG_BITS_ENUM(HALT_COP_EVENTS_MODE, FLOW_MODE_NONE)); + } + + void SaveSecureContextAndSuspend() { + /* Ensure there are no pending memory transactions before we continue */ + FlushEntireDataCache(); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Save all secure context (security engine state + tzram). */ + SaveSecureContext(); + + /* Load and start the sc7 firmware on the bpmp. */ + if (GetTargetFirmware() >= TargetFirmware_2_0_0) { + LoadAndStartSc7BpmpFirmware(); + } + + /* Log our suspension. */ + /* NOTE: Nintendo only does this on dev, but we will always do it. */ + if (true /* !pkg1::IsProduction() */) { + log::Initialize(secmon::GetLogPort(), secmon::GetLogBaudRate(), secmon::GetLogFlags()); + log::SendText("OYASUMI\n", 8); + log::Flush(); + } + + /* If we're on erista, configure the bootrom to allow our custom warmboot firmware. */ + if (GetSocType() == fuse::SocType_Erista) { + reg::Write(PMC + APBDEV_PMC_SCRATCH31, 0x2202E012); + reg::Write(PMC + APBDEV_PMC_SCRATCH32, 0x6001DC28); + } + + /* Finalize our powerdown and wait for an interrupt. */ + FinalizePowerOff(); + } + + SmcResult SuspendCpuImpl(SmcArguments &args) { + /* Decode arguments. */ + const util::BitPack32 power_state = { static_cast<u32>(args.r[1]) }; + const uintptr_t entry_point = args.r[2]; + const uintptr_t context_id = args.r[3]; + + const auto state_type = power_state.Get<SuspendCpuPowerState::StateType>(); + const auto state_id = power_state.Get<SuspendCpuPowerState::StateId>(); + + const auto core_id = hw::GetCurrentCoreId(); + + /* Validate arguments. */ + SMC_R_UNLESS(state_type == PowerStateType_PowerDown, PsciDenied); + SMC_R_UNLESS(state_id == PowerStateId_Sc7, PsciDenied); + + /* Orchestrate charger transition to Hi-Z mode if needed. */ + if (IsChargerHiZModeEnabled()) { + /* Ensure we can do comms over i2c-1. */ + clkrst::EnableI2c1Clock(); + + /* If the charger isn't in hi-z mode, perform a transition. */ + if (!charger::IsHiZMode()) { + charger::EnterHiZMode(); + + /* Wait up to 50ms for the transition to complete. */ + const auto start_time = util::GetMicroSeconds(); + auto current_time = start_time; + while ((current_time - start_time) <= 50'000) { + if (auto intr_status = reg::Read(GPIO + 0x634); (intr_status & 1) == 0) { + /* Wait 256 us to ensure the transition completes. */ + util::WaitMicroSeconds(256); + break; + } + current_time = util::GetMicroSeconds(); + } + } + + /* Disable i2c-1, since we're done communicating over it. */ + clkrst::DisableI2c1Clock(); + } + + /* Enable wake event detection. */ + pmc::EnableWakeEventDetection(); + + /* Ensure that i2c-5 is usable for communicating with the pmic. */ + clkrst::EnableI2c5Clock(); + i2c::Initialize(i2c::Port_5); + + /* Orchestrate sleep entry with the pmic. */ + pmic::EnableSleep(); + + /* Ensure that the soc is in a state valid for us to suspend. */ + if (GetTargetFirmware() >= TargetFirmware_2_0_0) { + ValidateSocStateForSuspend(); + } + + /* Configure the pmc for sc7 entry. */ + pmc::ConfigureForSc7Entry(); + + /* Configure the flow controller for sc7 entry. */ + flow::SetCc4Ctrl(core_id, 0); + flow::SetHaltCpuEvents(core_id, false); + flow::ClearL2FlushControl(); + flow::SetCpuCsr(core_id, FLOW_CTLR_CPUN_CSR_ENABLE_EXT_POWERGATE_CPU_TURNOFF_CPURAIL); + + /* Save the entry context. */ + SetEntryContext(core_id, entry_point, context_id); + + /* Configure the cpu context for reset. */ + SaveDebugRegisters(); + SetCoreOff(); + SetResetExpected(true); + + /* Switch to use the common smc stack (all other cores are off), and perform suspension. */ + PivotStackAndInvoke(reinterpret_cast<void *>(CommonSmcStackTop), SaveSecureContextAndSuspend); + + /* This code will never be reached. */ + __builtin_unreachable(); + } + + } + + void PowerOffCpu() { + /* Get the current core id. */ + const auto core_id = hw::GetCurrentCoreId(); + + /* Note that we're expecting a reset for the current core. */ + SetResetExpected(true); + + /* If we're on the final core, shut down directly. Otherwise, invoke with special stack. */ + if (core_id == NumCores - 1) { + PowerOffCpuImpl(); + } else { + PivotStackAndInvoke(GetCoreExceptionStackVirtual(), PowerOffCpuImpl); + } + + /* This code will never be reached. */ + __builtin_unreachable(); + } + + SmcResult SmcPowerOffCpu(SmcArguments &args) { + AMS_UNUSED(args); + + PowerOffCpu(); + } + + SmcResult SmcPowerOnCpu(SmcArguments &args) { + /* Get and validate the core to power on. */ + const int which_core = args.r[1]; + SMC_R_UNLESS(0 <= which_core && which_core < NumCores, PsciInvalidParameters); + + /* Ensure the core isn't already on. */ + SMC_R_UNLESS(!IsCoreOn(which_core), PsciAlreadyOn); + + /* Save the entry context. */ + SetEntryContext(which_core, args.r[2], args.r[3]); + + /* Reset the cpu. */ + ResetCpu(which_core); + + /* Turn on the core. */ + PowerOnCpu(CpuPowerGateStatusMasks[which_core], CpuPowerGateTogglePartitionIds[which_core]); + + /* Start the core. */ + StartCpu(which_core); + + return SmcResult::PsciSuccess; + } + + SmcResult SmcSuspendCpu(SmcArguments &args) { + return LockSecurityEngineAndInvoke(args, SuspendCpuImpl); + } + + bool IsChargerHiZModeEnabled() { + return g_charger_hi_z_mode_enabled; + } + + void SetChargerHiZModeEnabled(bool en) { + g_charger_hi_z_mode_enabled = en; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_power_management.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_power_management.hpp new file mode 100644 index 00000000..5f78ea95 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_power_management.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> +#include "secmon_smc_common.hpp" + +namespace ams::secmon::smc { + + NORETURN void PowerOffCpu(); + + SmcResult SmcPowerOffCpu(SmcArguments &args); + SmcResult SmcPowerOnCpu(SmcArguments &args); + + SmcResult SmcSuspendCpu(SmcArguments &args); + + bool IsChargerHiZModeEnabled(); + void SetChargerHiZModeEnabled(bool en); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_random.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_random.cpp new file mode 100644 index 00000000..a1e8aeed --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_random.cpp @@ -0,0 +1,77 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../secmon_error.hpp" +#include "secmon_smc_random.hpp" +#include "secmon_random_cache.hpp" +#include "secmon_smc_se_lock.hpp" + +namespace ams::secmon::smc { + + namespace { + + SmcResult GenerateRandomBytesImpl(SmcArguments &args) { + /* Validate the input size. */ + const size_t size = args.r[1]; + SMC_R_UNLESS(size <= MaxRandomBytes, InvalidArgument); + + /* Create a buffer that the se can generate bytes into. */ + util::AlignedBuffer<hw::DataCacheLineSize, MaxRandomBytes> buffer; + hw::FlushDataCache(buffer, size); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Generate random bytes into the buffer. */ + se::GenerateRandomBytes(buffer, size); + + /* Ensure that the cpu sees consistent data. */ + hw::DataSynchronizationBarrierInnerShareable(); + hw::FlushDataCache(buffer, size); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Copy the bytes to output. */ + std::memcpy(std::addressof(args.r[1]), buffer, size); + return SmcResult::Success; + } + + } + + SmcResult SmcGenerateRandomBytes(SmcArguments &args) { + return LockSecurityEngineAndInvoke(args, GenerateRandomBytesImpl); + } + + SmcResult SmcGenerateRandomBytesNonBlocking(SmcArguments &args) { + /* Try to lock the security engine, so that we can call the standard impl. */ + if (TryLockSecurityEngine()) { + /* Ensure we unlock the security engine when done. */ + ON_SCOPE_EXIT { UnlockSecurityEngine(); }; + + /* Take advantage of our lock to refill lthe random cache. */ + ON_SCOPE_EXIT { RefillRandomCache(); }; + + /* If we lock it successfully, we can just call the blocking impl. */ + return GenerateRandomBytesImpl(args); + } else { + /* Otherwise, we'll retrieve some bytes from the cache. */ + const size_t size = args.r[1]; + SMC_R_UNLESS(size <= MaxRandomBytes, InvalidArgument); + + /* Get random bytes from the cache. */ + GetRandomFromCache(std::addressof(args.r[1]), size); + return SmcResult::Success; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_random.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_random.hpp new file mode 100644 index 00000000..64684244 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_random.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> +#include "secmon_smc_common.hpp" + +namespace ams::secmon::smc { + + SmcResult SmcGenerateRandomBytes(SmcArguments &args); + SmcResult SmcGenerateRandomBytesNonBlocking(SmcArguments &args); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_register_access.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_register_access.cpp new file mode 100644 index 00000000..eae2933c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_register_access.cpp @@ -0,0 +1,196 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../secmon_error.hpp" +#include "secmon_smc_register_access.hpp" + +namespace ams::secmon::smc { + + namespace { + + template<size_t N> + constexpr void SetRegisterTableAllowed(std::array<u8, N> &arr, uintptr_t reg) { + /* All registers should be four byte aligned. */ + AMS_ASSUME(reg % sizeof(u32) == 0); + + /* Reduce the register to an index. */ + reg /= sizeof(u32); + + /* Get the index and mask. */ + const auto index = reg / BITSIZEOF(u8); + const auto mask = (1u << (reg % BITSIZEOF(u8))); + + /* Check that the permission bit isn't already set. */ + AMS_ASSUME((arr[index] & mask) == 0); + + /* Set the permission bit. */ + arr[index] |= mask; + + /* Ensure that indices are set in sorted order. */ + for (auto i = (reg % BITSIZEOF(u8)) + 1; i < 8; ++i) { + AMS_ASSUME((arr[index] & (1u << i)) == 0); + } + + for (auto i = index + 1; i < arr.size(); ++i) { + AMS_ASSUME(arr[i] == 0); + } + } + + template<size_t N> + consteval std::pair<size_t, size_t> GetReducedAccessTableInfo(const std::array<u8, N> &arr) { + for (int last = arr.size() - 1; last >= 0; --last) { + if (arr[last] != 0) { + const int end = last + 1; + for (int start = 0; start < end; ++start) { + if (arr[start] != 0) { + return std::make_pair(static_cast<size_t>(start), static_cast<size_t>(end)); + } + } + return std::make_pair(static_cast<size_t>(0), static_cast<size_t>(end)); + } + } + + /* All empty perm table is disallowed. */ + AMS_ASSUME(false); + } + + + template<u32 _Address, auto RawTable> + struct AccessTable { + static constexpr inline auto ReducedAccessTableInfo = GetReducedAccessTableInfo(RawTable); + static constexpr inline size_t ReducedAccessTableSize = ReducedAccessTableInfo.second - ReducedAccessTableInfo.first; + static constexpr inline auto ReducedAccessTable = []() -> std::array<u8, ReducedAccessTableSize> { + std::array<u8, ReducedAccessTableSize> reduced = {}; + + for (size_t i = ReducedAccessTableInfo.first; i < ReducedAccessTableInfo.second; ++i) { + reduced[i - ReducedAccessTableInfo.first] = RawTable[i]; + } + + return reduced; + }(); + + static constexpr u32 Address = _Address + (ReducedAccessTableInfo.first * sizeof(u32) * BITSIZEOF(u8)); + static constexpr u32 Size = static_cast<u32>(ReducedAccessTableSize * sizeof(u32) * BITSIZEOF(u8)); + + static_assert(Size <= 0x1000); + }; + + struct AccessTableEntry { + const u8 * const table; + uintptr_t virtual_address; + u32 address; + u32 size; + }; + + /* Include the access tables. */ + #include "secmon_define_pmc_access_table.inc" + #include "secmon_define_mc_access_table.inc" + #include "secmon_define_emc_access_table.inc" + #include "secmon_define_mc01_access_table.inc" + + constexpr const AccessTableEntry AccessTables[] = { + { PmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDevicePmc.GetAddress(), PmcAccessTable::Address, PmcAccessTable::Size, }, + { McAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceMemoryController.GetAddress(), McAccessTable::Address, McAccessTable::Size, }, + { EmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceExternalMemoryController.GetAddress(), EmcAccessTable::Address, EmcAccessTable::Size, }, + { Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController0.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController0.GetAddress(), Mc01AccessTable::Size, }, + { Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController1.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController1.GetAddress(), Mc01AccessTable::Size, }, + }; + + constexpr bool IsAccessAllowed(const AccessTableEntry &entry, uintptr_t address) { + /* Check if the address is within range. */ + if (!(entry.address <= address && address < entry.address + entry.size)) { + return false; + } + + /* Get the offset. */ + const auto offset = address - entry.address; + + /* Convert it to an index. */ + const auto reg_index = offset / sizeof(u32); + + /* Get the bit fields. */ + const auto index = reg_index / BITSIZEOF(u8); + const auto mask = (1u << (reg_index % BITSIZEOF(u8))); + + /* Validate that we're not going out of bounds. */ + if (index >= entry.size / sizeof(u32)) { + return false; + } + + return (entry.table[index] & mask) != 0; + } + + constexpr const AccessTableEntry *GetAccessTableEntry(uintptr_t address) { + for (const auto &entry : AccessTables) { + if (IsAccessAllowed(entry, address)) { + return std::addressof(entry); + } + } + + return nullptr; + } + + } + + SmcResult SmcReadWriteRegister(SmcArguments &args) { + /* Get the arguments. */ + const uintptr_t address = args.r[1]; + const u32 mask = args.r[2]; + const u32 value = args.r[3]; + + /* Validate that the address is aligned. */ + SMC_R_UNLESS(util::IsAligned(address, alignof(u32)), InvalidArgument); + + /* Find the access table. */ + const AccessTableEntry * const entry = GetAccessTableEntry(address); + + /* Translate our entry into an address to access. */ + uintptr_t virtual_address = 0; + if (entry != nullptr) { + /* Get the address to read or write. */ + virtual_address = entry->virtual_address + (address - entry->address); + } else { + /* For no clearly discernable reason, SmcReadWriteRegister returns success despite not doing the read/write */ + /* when accessing the SMMU controls for the BPMP and for APB-DMA. */ + /* This is "probably" to fuck with hackers who got access to the SMC and are trying to get control of the */ + /* BPMP to exploit jamais vu, deja vu, or other related DMA/wake-from-sleep vulnerabilities. */ + constexpr uintptr_t MC = MemoryRegionPhysicalDeviceMemoryController.GetAddress(); + SMC_R_UNLESS((address == (MC + MC_SMMU_AVPC_ASID) || address == (MC + MC_SMMU_PPCS1_ASID)), InvalidArgument); + + /* For backwards compatibility, we'll allow access to these devices on 1.0.0. */ + if (GetTargetFirmware() < TargetFirmware_2_0_0) { + virtual_address = MemoryRegionVirtualDeviceMemoryController.GetAddress() + (address - MC); + } + } + + /* Perform the read or write, if we should. */ + if (virtual_address != 0) { + u32 out = 0; + + if (mask != ~static_cast<u32>(0)) { + out = reg::Read(virtual_address); + } + if (mask != static_cast<u32>(0)) { + reg::Write(virtual_address, (out & ~mask) | (value & mask)); + } + + args.r[1] = out; + } + + return SmcResult::Success; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_register_access.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_register_access.hpp new file mode 100644 index 00000000..c252eeb5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_register_access.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> +#include "secmon_smc_common.hpp" + +namespace ams::secmon::smc { + + SmcResult SmcReadWriteRegister(SmcArguments &args); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_result.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_result.cpp new file mode 100644 index 00000000..bf2ea771 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_result.cpp @@ -0,0 +1,106 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../secmon_error.hpp" +#include "../secmon_page_mapper.hpp" +#include "secmon_smc_result.hpp" + +namespace ams::secmon::smc { + + namespace { + + constinit u64 g_async_key = InvalidAsyncKey; + constinit GetResultHandler g_async_handler = nullptr; + + u64 GenerateRandomU64() { + /* NOTE: This is one of the only places where Nintendo does not do data flushing. */ + /* to ensure coherency when doing random byte generation. */ + /* It is not clear why it is necessary elsewhere but not here. */ + /* TODO: Figure out why. */ + u64 v; + se::GenerateRandomBytes(std::addressof(v), sizeof(v)); + return v; + } + + } + + u64 BeginAsyncOperation(GetResultHandler handler) { + /* Only allow one async operation at a time. */ + if (g_async_key != InvalidAsyncKey) { + return InvalidAsyncKey; + } + + /* Generate a random async key. */ + g_async_key = GenerateRandomU64(); + g_async_handler = handler; + + return g_async_key; + } + + void CancelAsyncOperation(u64 async_key) { + if (async_key == g_async_key) { + g_async_key = InvalidAsyncKey; + } + } + + void EndAsyncOperation() { + gic::SetPending(SecurityEngineUserInterruptId); + } + + SmcResult SmcGetResult(SmcArguments &args) { + /* Decode arguments. */ + const u64 async_key = args.r[1]; + + /* Validate arguments. */ + SMC_R_UNLESS(g_async_key != InvalidAsyncKey, NoAsyncOperation); + SMC_R_UNLESS(g_async_key == async_key, InvalidAsyncOperation); + + /* Call the handler. */ + args.r[1] = static_cast<u64>(g_async_handler(nullptr, 0)); + g_async_key = InvalidAsyncKey; + + return SmcResult::Success; + } + + SmcResult SmcGetResultData(SmcArguments &args) { + /* Decode arguments. */ + const u64 async_key = args.r[1]; + const uintptr_t user_phys_addr = args.r[2]; + const size_t user_size = args.r[3]; + + /* Allocate a work buffer on the stack. */ + alignas(8) u8 work_buffer[1_KB]; + + /* Validate arguments. */ + SMC_R_UNLESS(g_async_key != InvalidAsyncKey, NoAsyncOperation); + SMC_R_UNLESS(g_async_key == async_key, InvalidAsyncOperation); + SMC_R_UNLESS(user_size <= sizeof(work_buffer), InvalidArgument); + + /* Call the handler. */ + args.r[1] = static_cast<u64>(g_async_handler(work_buffer, user_size)); + g_async_key = InvalidAsyncKey; + + /* Map the user buffer. */ + { + UserPageMapper mapper(user_phys_addr); + SMC_R_UNLESS(mapper.Map(), InvalidArgument); + SMC_R_UNLESS(mapper.CopyToUser(user_phys_addr, work_buffer, user_size), InvalidArgument); + } + + return SmcResult::Success; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_result.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_result.hpp new file mode 100644 index 00000000..3726b514 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_result.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> +#include "secmon_smc_common.hpp" + +namespace ams::secmon::smc { + + using GetResultHandler = SmcResult (*)(void *dst, size_t dst_size); + + u64 BeginAsyncOperation(GetResultHandler handler); + void CancelAsyncOperation(u64 async_key); + void EndAsyncOperation(); + + SmcResult SmcGetResult(SmcArguments &args); + SmcResult SmcGetResultData(SmcArguments &args); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_rsa.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_rsa.cpp new file mode 100644 index 00000000..920a65be --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_rsa.cpp @@ -0,0 +1,368 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../secmon_error.hpp" +#include "../secmon_key_storage.hpp" +#include "../secmon_page_mapper.hpp" +#include "secmon_smc_aes.hpp" +#include "secmon_smc_rsa.hpp" +#include "secmon_smc_se_lock.hpp" + +namespace ams::secmon::smc { + + namespace { + + struct ModularExponentiateByStorageKeyOption { + using Mode = util::BitPack32::Field<0, 2, u32>; + using Reserved = util::BitPack32::Field<2, 30, u32>; + }; + + struct PrepareEsDeviceUniqueKeyOption { + using KeyGeneration = util::BitPack32::Field<0, 6, int>; + using Type = util::BitPack32::Field<6, 2, EsCommonKeyType>; + using Reserved = util::BitPack32::Field<8, 24, u32>; + }; + + constexpr const u8 ModularExponentiateByStorageKeyTable[] = { + static_cast<u8>(ImportRsaKey_Lotus), + static_cast<u8>(ImportRsaKey_Ssl), + static_cast<u8>(ImportRsaKey_EsClientCert), + }; + constexpr size_t ModularExponentiateByStorageKeyTableSize = util::size(ModularExponentiateByStorageKeyTable); + + consteval u32 GetModeForImportRsaKey(ImportRsaKey import_key) { + for (size_t i = 0; i < ModularExponentiateByStorageKeyTableSize; ++i) { + if (static_cast<ImportRsaKey>(ModularExponentiateByStorageKeyTable[i]) == import_key) { + return i; + } + } + + AMS_ASSUME(false); + } + + class PrepareEsDeviceUniqueKeyAsyncArguments { + private: + int m_generation; + EsCommonKeyType m_type; + u8 m_label_digest[crypto::Sha256Generator::HashSize]; + public: + void Set(int gen, EsCommonKeyType t, const u8 ld[crypto::Sha256Generator::HashSize]) { + m_generation = gen; + m_type = t; + std::memcpy(m_label_digest, ld, sizeof(m_label_digest)); + } + + int GetKeyGeneration() const { return m_generation; } + EsCommonKeyType GetCommonKeyType() const { return m_type; } + void GetLabelDigest(u8 dst[crypto::Sha256Generator::HashSize]) const { std::memcpy(dst, m_label_digest, sizeof(m_label_digest)); } + }; + + class ModularExponentiateByStorageKeyAsyncArguments { + private: + u8 m_msg[se::RsaSize]; + public: + void Set(const void *m, size_t m_size) { + AMS_UNUSED(m_size); + std::memcpy(m_msg, m, sizeof(m_msg)); + } + + const u8 *GetMessage() const { return m_msg; } + }; + + constinit SmcResult g_exp_mod_result = SmcResult::Success; + + constinit bool g_test_exp_mod_public = false; + constinit int g_test_exp_mod_slot = pkg1::RsaKeySlot_Temporary; + constinit ImportRsaKey g_test_exp_mod_key = {}; + + constinit union { + ModularExponentiateByStorageKeyAsyncArguments modular_exponentiate_by_storage_key; + PrepareEsDeviceUniqueKeyAsyncArguments prepare_es_device_unique_key; + } g_async_arguments; + + ALWAYS_INLINE ModularExponentiateByStorageKeyAsyncArguments &GetModularExponentiateByStorageKeyAsyncArguments() { + return g_async_arguments.modular_exponentiate_by_storage_key; + } + + ALWAYS_INLINE PrepareEsDeviceUniqueKeyAsyncArguments &GetPrepareEsDeviceUniqueKeyAsyncArguments() { + return g_async_arguments.prepare_es_device_unique_key; + } + + void SecurityEngineDoneHandler() { + /* End the asynchronous operation. */ + g_exp_mod_result = SmcResult::Success; + EndAsyncOperation(); + } + + void TestRsaPublicKey(ImportRsaKey which, int slot, const void *mod, size_t mod_size, se::DoneHandler handler) { + /* Declare a buffer for our test message. */ + u8 msg[se::RsaSize]; + std::memset(msg, 'D', sizeof(msg)); + + /* Provisionally import the modulus. */ + ImportRsaKeyModulusProvisionally(which, mod, mod_size); + + /* Load the provisional public key into the slot. */ + LoadProvisionalRsaPublicKey(slot, which); + + /* Perform the test exponentiation. */ + se::ModularExponentiateAsync(slot, msg, sizeof(msg), handler); + } + + void TestRsaPrivateKey(ImportRsaKey which, int slot, se::DoneHandler handler) { + /* Get the result of the public key test. */ + u8 msg[se::RsaSize]; + se::GetRsaResult(msg, sizeof(msg)); + + /* Load the provisional private key into the slot. */ + LoadProvisionalRsaKey(slot, which); + + /* Perform the test exponentiation. */ + se::ModularExponentiateAsync(slot, msg, sizeof(msg), handler); + } + + void VerifyTestRsaKeyResult(ImportRsaKey which) { + /* Get the result of the test. */ + u8 msg[se::RsaSize]; + se::GetRsaResult(msg, sizeof(msg)); + + /* Validate the result. */ + const bool is_valid = (msg[0] == 'D') & (crypto::IsSameBytes(msg, msg + 1, sizeof(msg) - 1)); + + /* If the test passes, the key is no longer provisional. */ + if (is_valid) { + CommitRsaKeyModulus(which); + } + } + + void TestRsaKeyDoneHandler() { + if (g_test_exp_mod_public) { + /* If we're testing the public key, we still have another exponentiation to do to test the private key. */ + g_test_exp_mod_public = false; + + /* Test the private key. */ + TestRsaPrivateKey(g_test_exp_mod_key, g_test_exp_mod_slot, TestRsaKeyDoneHandler); + } else { + /* We're testing the private key, so validate the result. */ + VerifyTestRsaKeyResult(g_test_exp_mod_key); + + /* If the test passed, we can proceed to perform the intended exponentiation. */ + if (LoadRsaKey(g_test_exp_mod_slot, g_test_exp_mod_key)) { + se::ModularExponentiateAsync(pkg1::RsaKeySlot_Temporary, GetModularExponentiateByStorageKeyAsyncArguments().GetMessage(), se::RsaSize, SecurityEngineDoneHandler); + } else { + /* The test failed, so end the asynchronous operation. */ + g_exp_mod_result = SmcResult::InvalidArgument; + EndAsyncOperation(); + } + } + } + + SmcResult ModularExponentiateImpl(SmcArguments &args) { + /* Decode arguments. */ + const uintptr_t msg_address = args.r[1]; + const uintptr_t exp_address = args.r[2]; + const uintptr_t mod_address = args.r[3]; + const size_t exp_size = args.r[4]; + + /* Validate arguments. */ + SMC_R_UNLESS(util::IsAligned(exp_size, sizeof(u32)), InvalidArgument); + SMC_R_UNLESS(exp_size <= se::RsaSize, InvalidArgument); + + /* Copy the message and modulus from the user. */ + alignas(8) u8 msg[se::RsaSize]; + alignas(8) u8 exp[se::RsaSize]; + alignas(8) u8 mod[se::RsaSize]; + { + UserPageMapper mapper(msg_address); + SMC_R_UNLESS(mapper.Map(), InvalidArgument); + SMC_R_UNLESS(mapper.CopyFromUser(msg, msg_address, sizeof(msg)), InvalidArgument); + SMC_R_UNLESS(mapper.CopyFromUser(exp, exp_address, exp_size), InvalidArgument); + SMC_R_UNLESS(mapper.CopyFromUser(mod, mod_address, sizeof(mod)), InvalidArgument); + } + + /* We're performing an operation, so set the result to busy. */ + g_exp_mod_result = SmcResult::Busy; + + /* Load the key into the temporary keyslot. */ + se::SetRsaKey(pkg1::RsaKeySlot_Temporary, mod, sizeof(mod), exp, exp_size); + + /* Begin the asynchronous exponentiation. */ + se::ModularExponentiateAsync(pkg1::RsaKeySlot_Temporary, msg, sizeof(msg), SecurityEngineDoneHandler); + + return SmcResult::Success; + } + + SmcResult ModularExponentiateByStorageKeyImpl(SmcArguments &args) { + /* Decode arguments. */ + const uintptr_t msg_address = args.r[1]; + const uintptr_t mod_address = args.r[2]; + const util::BitPack32 option = { static_cast<u32>(args.r[3]) }; + + const auto mode = GetTargetFirmware() >= TargetFirmware_5_0_0 ? option.Get<ModularExponentiateByStorageKeyOption::Mode>() : GetModeForImportRsaKey(ImportRsaKey_Lotus); + const auto reserved = option.Get<PrepareEsDeviceUniqueKeyOption::Reserved>(); + + /* Validate arguments. */ + SMC_R_UNLESS(reserved == 0, InvalidArgument); + SMC_R_UNLESS(mode < ModularExponentiateByStorageKeyTableSize, InvalidArgument); + + /* Convert the mode to an import key. */ + const auto import_key = static_cast<ImportRsaKey>(ModularExponentiateByStorageKeyTable[mode]); + + /* Copy the message and modulus from the user. */ + alignas(8) u8 msg[se::RsaSize]; + alignas(8) u8 mod[se::RsaSize]; + { + UserPageMapper mapper(msg_address); + SMC_R_UNLESS(mapper.Map(), InvalidArgument); + SMC_R_UNLESS(mapper.CopyFromUser(msg, msg_address, sizeof(msg)), InvalidArgument); + SMC_R_UNLESS(mapper.CopyFromUser(mod, mod_address, sizeof(mod)), InvalidArgument); + } + + /* We're performing an operation, so set the result to busy. */ + g_exp_mod_result = SmcResult::Busy; + + /* In the ideal case, the key pair is already verified. If it is, we can use it directly. */ + if (LoadRsaKey(pkg1::RsaKeySlot_Temporary, import_key)) { + se::ModularExponentiateAsync(pkg1::RsaKeySlot_Temporary, msg, sizeof(msg), SecurityEngineDoneHandler); + } else { + /* Set the async arguments. */ + GetModularExponentiateByStorageKeyAsyncArguments().Set(msg, sizeof(msg)); + + /* Test the rsa key. */ + g_test_exp_mod_slot = pkg1::RsaKeySlot_Temporary; + g_test_exp_mod_key = import_key; + g_test_exp_mod_public = true; + + TestRsaPublicKey(import_key, pkg1::RsaKeySlot_Temporary, mod, sizeof(mod), TestRsaKeyDoneHandler); + } + + return SmcResult::Success; + } + + SmcResult PrepareEsDeviceUniqueKeyImpl(SmcArguments &args) { + /* Decode arguments. */ + u8 label_digest[crypto::Sha256Generator::HashSize]; + + const uintptr_t msg_address = args.r[1]; + const uintptr_t mod_address = args.r[2]; + std::memcpy(label_digest, std::addressof(args.r[3]), sizeof(label_digest)); + const util::BitPack32 option = { static_cast<u32>(args.r[7]) }; + + const auto generation = GetTargetFirmware() >= TargetFirmware_3_0_0 ? std::max<int>(pkg1::KeyGeneration_1_0_0, option.Get<PrepareEsDeviceUniqueKeyOption::KeyGeneration>() - 1) : pkg1::KeyGeneration_1_0_0; + const auto type = option.Get<PrepareEsDeviceUniqueKeyOption::Type>(); + const auto reserved = option.Get<PrepareEsDeviceUniqueKeyOption::Reserved>(); + + /* Validate arguments. */ + SMC_R_UNLESS(reserved == 0, InvalidArgument); + SMC_R_UNLESS(pkg1::IsValidKeyGeneration(generation), InvalidArgument); + SMC_R_UNLESS(generation <= GetKeyGeneration(), InvalidArgument); + SMC_R_UNLESS(type < EsCommonKeyType_Count, InvalidArgument); + + /* Copy the message and modulus from the user. */ + alignas(8) u8 msg[se::RsaSize]; + alignas(8) u8 mod[se::RsaSize]; + { + UserPageMapper mapper(msg_address); + SMC_R_UNLESS(mapper.Map(), InvalidArgument); + SMC_R_UNLESS(mapper.CopyFromUser(msg, msg_address, sizeof(msg)), InvalidArgument); + SMC_R_UNLESS(mapper.CopyFromUser(mod, mod_address, sizeof(mod)), InvalidArgument); + } + + /* We're performing an operation, so set the result to busy. */ + g_exp_mod_result = SmcResult::Busy; + + /* Set the async arguments. */ + GetPrepareEsDeviceUniqueKeyAsyncArguments().Set(generation, type, label_digest); + + /* Load the es drm key into the security engine. */ + SMC_R_UNLESS(LoadRsaKey(pkg1::RsaKeySlot_Temporary, ImportRsaKey_EsDrmCert), NotInitialized); + + /* Trigger the asynchronous modular exponentiation. */ + se::ModularExponentiateAsync(pkg1::RsaKeySlot_Temporary, msg, sizeof(msg), SecurityEngineDoneHandler); + + return SmcResult::Success; + } + + SmcResult GetModularExponentiateResult(void *dst, size_t dst_size) { + /* Validate state. */ + SMC_R_TRY(g_exp_mod_result); + SMC_R_UNLESS(dst_size == se::RsaSize, InvalidArgument); + + /* We want to relinquish our security engine lock at the end of scope. */ + ON_SCOPE_EXIT { UnlockSecurityEngine(); }; + + /* Get the result of the exponentiation. */ + se::GetRsaResult(dst, se::RsaSize); + + return SmcResult::Success; + } + + SmcResult GetPrepareEsDeviceUniqueKeyResult(void *dst, size_t dst_size) { + /* Declare variables. */ + u8 key_source[se::AesBlockSize]; + u8 key[se::AesBlockSize]; + u8 access_key[se::AesBlockSize]; + + /* Validate state. */ + SMC_R_TRY(g_exp_mod_result); + SMC_R_UNLESS(dst_size == sizeof(access_key), InvalidArgument); + + /* We want to relinquish our security engine lock at the end of scope. */ + ON_SCOPE_EXIT { UnlockSecurityEngine(); }; + + /* Get the async args. */ + const auto &async_args = GetPrepareEsDeviceUniqueKeyAsyncArguments(); + + /* Get the exponentiation output. */ + alignas(8) u8 msg[se::RsaSize]; + se::GetRsaResult(msg, sizeof(msg)); + + /* Decode the key. */ + { + /* Get the label digest. */ + u8 label_digest[crypto::Sha256Generator::HashSize]; + async_args.GetLabelDigest(label_digest); + + /* Decode the key source. */ + const size_t key_source_size = se::DecodeRsaOaepSha256(key_source, sizeof(key_source), msg, sizeof(msg), label_digest, sizeof(label_digest)); + SMC_R_UNLESS(key_source_size == sizeof(key_source), InvalidArgument); + } + + /* Decrypt the key. */ + DecryptWithEsCommonKey(key, sizeof(key), key_source, sizeof(key_source), async_args.GetCommonKeyType(), async_args.GetKeyGeneration()); + PrepareEsAesKey(access_key, sizeof(access_key), key, sizeof(key)); + + /* Copy the access key to output. */ + std::memcpy(dst, access_key, sizeof(access_key)); + + return SmcResult::Success; + } + + } + + SmcResult SmcModularExponentiate(SmcArguments &args) { + return LockSecurityEngineAndInvokeAsync(args, ModularExponentiateImpl, GetModularExponentiateResult); + } + + SmcResult SmcModularExponentiateByStorageKey(SmcArguments &args) { + return LockSecurityEngineAndInvokeAsync(args, ModularExponentiateByStorageKeyImpl, GetModularExponentiateResult); + } + + SmcResult SmcPrepareEsDeviceUniqueKey(SmcArguments &args) { + return LockSecurityEngineAndInvokeAsync(args, PrepareEsDeviceUniqueKeyImpl, GetPrepareEsDeviceUniqueKeyResult); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_rsa.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_rsa.hpp new file mode 100644 index 00000000..36491e48 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_rsa.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> +#include "secmon_smc_common.hpp" + +namespace ams::secmon::smc { + + SmcResult SmcModularExponentiate(SmcArguments &args); + SmcResult SmcModularExponentiateByStorageKey(SmcArguments &args); + + SmcResult SmcPrepareEsDeviceUniqueKey(SmcArguments &args); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_se_lock.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_se_lock.cpp new file mode 100644 index 00000000..1dc5866a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_se_lock.cpp @@ -0,0 +1,82 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../secmon_error.hpp" +#include "secmon_smc_se_lock.hpp" + +namespace ams::secmon::smc { + + namespace { + + constinit util::Atomic<bool> g_is_locked = false; + + ALWAYS_INLINE bool TryLockSecurityEngineImpl() { + bool value = false; + return g_is_locked.CompareExchangeStrong(value, true); + } + + ALWAYS_INLINE void UnlockSecurityEngineImpl() { + g_is_locked = false; + } + + ALWAYS_INLINE bool IsSecurityEngineLockedImpl() { + return g_is_locked.Load(); + } + + } + + bool TryLockSecurityEngine() { + return TryLockSecurityEngineImpl(); + } + + void UnlockSecurityEngine() { + return UnlockSecurityEngineImpl(); + } + + bool IsSecurityEngineLocked() { + return IsSecurityEngineLockedImpl(); + } + + SmcResult LockSecurityEngineAndInvoke(SmcArguments &args, SmcHandler impl) { + /* Try to lock the security engine. */ + SMC_R_UNLESS(TryLockSecurityEngineImpl(), Busy); + ON_SCOPE_EXIT { UnlockSecurityEngineImpl(); }; + + return impl(args); + } + + SmcResult LockSecurityEngineAndInvokeAsync(SmcArguments &args, SmcHandler impl, GetResultHandler result_handler) { + /* Try to lock the security engine. */ + SMC_R_UNLESS(TryLockSecurityEngineImpl(), Busy); + auto se_guard = SCOPE_GUARD { UnlockSecurityEngineImpl(); }; + + /* Try to start an async operation. */ + const u64 async_key = BeginAsyncOperation(result_handler); + SMC_R_UNLESS(async_key != InvalidAsyncKey, Busy); + auto async_guard = SCOPE_GUARD { CancelAsyncOperation(async_key); }; + + /* Try to invoke the operation. */ + SMC_R_TRY(impl(args)); + + /* We succeeded! Cancel our guards, and return the async key to our caller. */ + async_guard.Cancel(); + se_guard.Cancel(); + + args.r[1] = async_key; + return SmcResult::Success; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_se_lock.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_se_lock.hpp new file mode 100644 index 00000000..7a52d2dd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/source/smc/secmon_smc_se_lock.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> +#include "secmon_smc_common.hpp" +#include "secmon_smc_handler.hpp" +#include "secmon_smc_result.hpp" + +namespace ams::secmon::smc { + + bool TryLockSecurityEngine(); + void UnlockSecurityEngine(); + bool IsSecurityEngineLocked(); + + SmcResult LockSecurityEngineAndInvoke(SmcArguments &args, SmcHandler impl); + SmcResult LockSecurityEngineAndInvokeAsync(SmcArguments &args, SmcHandler impl, GetResultHandler result_handler); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/program/split_program.py b/Source/Atmosphere-MTC-Unlock/exosphere/program/split_program.py new file mode 100644 index 00000000..ec4e74c3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/program/split_program.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +import sys, lz4 +from struct import unpack as up + +def lz4_compress(data): + try: + import lz4.block as block + except ImportError: + block = lz4.LZ4_compress + return block.compress(data, 'high_compression', store_size=False) + +def split_binary(data): + A, B, START, BOOT_CODE_START, BOOT_CODE_END, PROGRAM_START, C, D = up('<QQQQQQQQ', data[:0x40]) + assert A == 0xAAAAAAAAAAAAAAAA + assert B == 0xBBBBBBBBBBBBBBBB + assert C == 0xCCCCCCCCCCCCCCCC + assert D == 0xDDDDDDDDDDDDDDDD + data = data[0x40:] + + #print ('%X %X %X %X' % (START, BOOT_CODE_START, BOOT_CODE_END, PROGRAM_START)) + boot_code = data[BOOT_CODE_START - START:BOOT_CODE_END - BOOT_CODE_START] + program = data[PROGRAM_START - START:] + return [('boot_code.lz4', lz4_compress(boot_code)), ('program.lz4', lz4_compress(program))] + +def main(argc, argv): + if argc != 3: + print('Usage: %s in outdir' % argv[0]) + return 1 + with open(argv[1], 'rb') as f: + data = f.read() + assert len(data) >= 0x40 + for (fn, fdata) in split_binary(data): + with open('%s/%s' % (argv[2], fn), 'wb') as f: + f.write(fdata) + return 0 + +if __name__ == '__main__': + sys.exit(main(len(sys.argv), sys.argv)) diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/sdmmc_test/Makefile b/Source/Atmosphere-MTC-Unlock/exosphere/sdmmc_test/Makefile new file mode 100644 index 00000000..4bfed891 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/sdmmc_test/Makefile @@ -0,0 +1,108 @@ +#--------------------------------------------------------------------------------- +# Define the atmosphere board and cpu +#--------------------------------------------------------------------------------- +export ATMOSPHERE_BOARD := nx-hac-001 +export ATMOSPHERE_CPU := arm7tdmi + +#--------------------------------------------------------------------------------- +# pull in common atmosphere configuration +#--------------------------------------------------------------------------------- +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../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 ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) +export DEPSDIR := $(CURDIR)/$(BUILD) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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) + +#--------------------------------------------------------------------------------- +# 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)/lib -L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) check_libexo + +$(BUILD): check_libexo + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +check_libexo: + @$(MAKE) --no-print-directory -C ../../libraries/libexosphere arm + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(OUTPUT).bin $(OUTPUT).elf *.lz4 + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT).bin + +$(OUTPUT).bin : $(OUTPUT).elf + $(OBJCOPY) -S -O binary --set-section-flags .bss=alloc,load,contents $< $@ + @echo built ... $(notdir $@) + +$(OUTPUT).elf : $(OFILES) ../../../libraries/libexosphere/$(ATMOSPHERE_LIBRARY_DIR)/libexosphere.a + +%.elf: + @echo linking $(notdir $@) + $(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + @$(NM) -CSn $@ > $(notdir $*.lst) + +$(OFILES_SRC) : $(HFILES_BIN) + +#--------------------------------------------------------------------------------- +# 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/sdmmc_test/sdmmc_test.ld b/Source/Atmosphere-MTC-Unlock/exosphere/sdmmc_test/sdmmc_test.ld new file mode 100644 index 00000000..181d3333 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/sdmmc_test/sdmmc_test.ld @@ -0,0 +1,194 @@ +OUTPUT_ARCH(arm) +ENTRY(_ZN3ams10sdmmc_test5StartEv) + +MEMORY +{ + NULL : ORIGIN = 0, LENGTH = 4K + test_fw : ORIGIN = 0x40010000, LENGTH = 32K +} + + +SECTIONS +{ + /* =========== CODE section =========== */ + PROVIDE(__start__ = ORIGIN(test_fw)); + . = __start__; + __code_start = . ; + + .crt0 : + { + KEEP (*(.crt0 .crt0.*)) + . = ALIGN(8); + } >test_fw + + .vectors : + { + KEEP (*(.vectors .vectors.*)) + . = ALIGN(8); + } >test_fw + + .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); + } >test_fw + + .init : + { + KEEP( *(.init) ) + . = ALIGN(8); + } >test_fw + + .plt : + { + *(.plt) + *(.iplt) + . = ALIGN(8); + } >test_fw + + .fini : + { + KEEP( *(.fini) ) + . = ALIGN(8); + } >test_fw + + + /* =========== RODATA section =========== */ + . = ALIGN(8); + __rodata_start = . ; + + .rodata : + { + *(.rodata .rodata.* .gnu.linkonce.r.*) + . = ALIGN(8); + } >test_fw + + .eh_frame_hdr : { __eh_frame_hdr_start = .; *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) __eh_frame_hdr_end = .; } >test_fw + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } >test_fw + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } >test_fw + .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } >test_fw + + .hash : { *(.hash) } >test_fw + + /* =========== DATA section =========== */ + . = ALIGN(8); + __data_start = . ; + + .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } >test_fw + .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } >test_fw + .gnu_extab : ONLY_IF_RW { *(.gnu_extab*) } >test_fw + .exception_ranges : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) } >test_fw + + .preinit_array ALIGN(8) : + { + PROVIDE (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE (__preinit_array_end = .); + } >test_fw + + .init_array ALIGN(8) : + { + PROVIDE (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + PROVIDE (__init_array_end = .); + } >test_fw + + .fini_array ALIGN(8) : + { + PROVIDE (__fini_array_start = .); + KEEP (*(.fini_array)) + KEEP (*(SORT(.fini_array.*))) + PROVIDE (__fini_array_end = .); + } >test_fw + + .ctors ALIGN(8) : + { + KEEP (*crtbegin.o(.ctors)) /* MUST be first -- GCC requires it */ + KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + } >test_fw + + .dtors ALIGN(8) : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + } >test_fw + + __got_start__ = .; + + .got : { *(.got) *(.igot) } >test_fw + .got.plt : { *(.got.plt) *(.igot.plt) } >test_fw + + __got_end__ = .; + + .data ALIGN(8) : + { + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + } >test_fw + + __bss_start__ = .; + .bss ALIGN(8) : + { + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + . = ALIGN(16); + } >test_fw + __bss_end__ = .; + + __end__ = ABSOLUTE(.) ; + + __total_size__ = (__end__ - __start__); + + __stack_top__ = 0x40031000; + __stack_bottom__ = 0x40030000; + + /* ================== + ==== 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/sdmmc_test/sdmmc_test.specs b/Source/Atmosphere-MTC-Unlock/exosphere/sdmmc_test/sdmmc_test.specs new file mode 100644 index 00000000..fdf28d6e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/sdmmc_test/sdmmc_test.specs @@ -0,0 +1,4 @@ +%rename link old_link + +*link: +%(old_link) -T %:getenv(TOPDIR /sdmmc_test.ld) --gc-sections --nmagic \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/sdmmc_test/source/sdmmc_test_main.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/sdmmc_test/source/sdmmc_test_main.cpp new file mode 100644 index 00000000..b865e3a2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/sdmmc_test/source/sdmmc_test_main.cpp @@ -0,0 +1,146 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::sdmmc_test { + + namespace { + + constexpr inline const uintptr_t PMC = secmon::MemoryRegionPhysicalDevicePmc.GetAddress(); + + constexpr inline auto Port = sdmmc::Port_SdCard0; + alignas(8) constinit u8 g_sd_work_buffer[sdmmc::SdCardWorkBufferSize]; + + constexpr inline u32 SectorIndex = 0; + constexpr inline u32 SectorCount = 2; + + NORETURN void PmcMainReboot() { + /* Write enable to MAIN_RESET. */ + reg::Write(PMC + APBDEV_PMC_CNTRL, PMC_REG_BITS_ENUM(CNTRL_MAIN_RESET, ENABLE)); + + /* Wait forever until we're reset. */ + AMS_INFINITE_LOOP(); + } + + void CheckResult(const Result result) { + volatile u32 * const DEBUG = reinterpret_cast<volatile u32 *>(0x4003C000); + if (R_FAILED(result)) { + DEBUG[1] = result.GetValue(); + PmcMainReboot(); + } + } + + } + + void Main() { + /* Perform butchered hwinit. */ + /* TODO: replace with simpler, non-C logic. */ + /* nx_hwinit(); */ + + /* Clear output buffer for debug. */ + std::memset((void *)0x40038000, 0xAA, 0x400); + + /* Normally, these pins get configured by boot sysmodule during initial pinmux config. */ + /* However, they're required to access the SD card. */ + { + const uintptr_t apb_misc = dd::QueryIoMapping(0x70000000, 0x4000); + + reg::ReadWrite(apb_misc + PINMUX_AUX_SDMMC1_CLK, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_DOWN), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_CLK_PM, SDMMC1)); + + reg::ReadWrite(apb_misc + PINMUX_AUX_SDMMC1_CMD, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_UP), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_CMD_PM, SDMMC1)); + + reg::ReadWrite(apb_misc + PINMUX_AUX_SDMMC1_DAT3, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_UP), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_DAT3_PM, SDMMC1)); + + reg::ReadWrite(apb_misc + PINMUX_AUX_SDMMC1_DAT2, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_UP), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_DAT2_PM, SDMMC1)); + + reg::ReadWrite(apb_misc + PINMUX_AUX_SDMMC1_DAT1, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_UP), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_DAT1_PM, SDMMC1)); + + reg::ReadWrite(apb_misc + PINMUX_AUX_SDMMC1_DAT0, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_UP), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_DAT0_PM, SDMMC1)); + + reg::ReadWrite(apb_misc + PINMUX_AUX_DMIC3_CLK, PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_DAT0_PM, RSVD2)); + } + + /* Debug signaler. */ + volatile u32 * const DEBUG = reinterpret_cast<volatile u32 *>(0x4003C000); + DEBUG[0] = 0; + DEBUG[1] = 0xAAAAAAAA; + + /* Initialize sdmmc library. */ + sdmmc::Initialize(Port); + DEBUG[0] = 1; + + sdmmc::SetSdCardWorkBuffer(Port, g_sd_work_buffer, sizeof(g_sd_work_buffer)); + DEBUG[0] = 2; + + Result result = sdmmc::Activate(Port); + DEBUG[0] = 3; + CheckResult(result); + + /* Read the first two sectors from disk. */ + void * const sector_dst = reinterpret_cast<void *>(0x40038000); + result = sdmmc::Read(sector_dst, SectorCount * sdmmc::SectorSize, Port, SectorIndex, SectorCount); + DEBUG[0] = 4; + CheckResult(result); + + /* Get the connection status. */ + sdmmc::SpeedMode speed_mode; + sdmmc::BusWidth bus_width; + result = sdmmc::CheckSdCardConnection(std::addressof(speed_mode), std::addressof(bus_width), Port); + + /* Save status for debug. */ + DEBUG[0] = 5; + DEBUG[1] = result.GetValue(); + DEBUG[2] = static_cast<u32>(speed_mode); + DEBUG[3] = static_cast<u32>(bus_width); + + /* Perform a reboot. */ + PmcMainReboot(); + } + + NORETURN void ExceptionHandler() { + PmcMainReboot(); + } + +} + +namespace ams::diag { + + void AbortImpl() { + sdmmc_test::ExceptionHandler(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/sdmmc_test/source/sdmmc_test_start.s b/Source/Atmosphere-MTC-Unlock/exosphere/sdmmc_test/source/sdmmc_test_start.s new file mode 100644 index 00000000..8cf40b4c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/sdmmc_test/source/sdmmc_test_start.s @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +.section .crt0.text._ZN3ams10sdmmc_test5StartEv, "ax", %progbits +.align 3 +.global _ZN3ams10sdmmc_test5StartEv +_ZN3ams10sdmmc_test5StartEv: + /* Switch to system mode, mask all interrupts, clear all flags */ + msr cpsr_cxsf, #0xDF + + /* Set the stack pointer. */ + ldr sp, =__stack_top__ + + /* Set our link register to the exception handler. */ + ldr lr, =_ZN3ams10sdmmc_test16ExceptionHandlerEv + + /* Call init array functions. */ + bl __libc_init_array + + /* Invoke main. */ + b _ZN3ams10sdmmc_test4MainEv + + /* Infinite loop. */ + 2: b 2b \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/Makefile b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/Makefile new file mode 100644 index 00000000..94881dc4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/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)/warmboot.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)/warmboot.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, arm7tdmi,)) + +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/warmboot/source/warmboot_bootrom_workaround.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_bootrom_workaround.cpp new file mode 100644 index 00000000..806e3315 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_bootrom_workaround.cpp @@ -0,0 +1,99 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "warmboot_clkrst.hpp" + +namespace ams::warmboot { + + namespace { + + constexpr inline const uintptr_t CLKRST = secmon::MemoryRegionPhysicalDeviceClkRst.GetAddress(); + constexpr inline const uintptr_t EMC = EMC_ADDRESS(0); + + } + + void ApplyMbistWorkaround() { + /* Clear all LVL2 clock gate overrides to zero. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRA, 0); + reg::Write(CLKRST + CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRB, 0); + reg::Write(CLKRST + CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRC, 0); + reg::Write(CLKRST + CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRD, 0); + reg::Write(CLKRST + CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRE, 0); + + /* Clear clock enable for all but a select few devices. */ + auto devices_to_clear_l = reg::Read(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_L); + reg::ClearBits(static_cast<volatile u32 &>(devices_to_clear_l), CLK_RST_REG_BITS_MASK(CLK_ENB_L_CLK_ENB_RTC ), + CLK_RST_REG_BITS_MASK(CLK_ENB_L_CLK_ENB_TMR ), + CLK_RST_REG_BITS_MASK(CLK_ENB_L_CLK_ENB_GPIO ), + CLK_RST_REG_BITS_MASK(CLK_ENB_L_CLK_ENB_CACHE2)); + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_L_CLR, devices_to_clear_l); + + auto devices_to_clear_h = reg::Read(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_H); + reg::ClearBits(static_cast<volatile u32 &>(devices_to_clear_h), CLK_RST_REG_BITS_MASK(CLK_ENB_H_CLK_ENB_MEM ), + CLK_RST_REG_BITS_MASK(CLK_ENB_H_CLK_ENB_PMC ), + CLK_RST_REG_BITS_MASK(CLK_ENB_H_CLK_ENB_FUSE), + CLK_RST_REG_BITS_MASK(CLK_ENB_H_CLK_ENB_EMC )); + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_H_CLR, devices_to_clear_h); + + auto devices_to_clear_u = reg::Read(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_U); + reg::ClearBits(static_cast<volatile u32 &>(devices_to_clear_u), CLK_RST_REG_BITS_MASK(CLK_ENB_U_CLK_ENB_CSITE), + CLK_RST_REG_BITS_MASK(CLK_ENB_U_CLK_ENB_IRAMA), + CLK_RST_REG_BITS_MASK(CLK_ENB_U_CLK_ENB_IRAMB), + CLK_RST_REG_BITS_MASK(CLK_ENB_U_CLK_ENB_IRAMC), + CLK_RST_REG_BITS_MASK(CLK_ENB_U_CLK_ENB_IRAMD), + CLK_RST_REG_BITS_MASK(CLK_ENB_U_CLK_ENB_CRAM2)); + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_U_CLR, devices_to_clear_u); + + auto devices_to_clear_v = reg::Read(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_V); + reg::ClearBits(static_cast<volatile u32 &>(devices_to_clear_v), CLK_RST_REG_BITS_MASK(CLK_ENB_V_CLK_ENB_MSELECT ), + CLK_RST_REG_BITS_MASK(CLK_ENB_V_CLK_ENB_SPDIF_DOUBLER), + CLK_RST_REG_BITS_MASK(CLK_ENB_V_CLK_ENB_TZRAM ), + CLK_RST_REG_BITS_MASK(CLK_ENB_V_CLK_ENB_SE )); + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_V_CLR, devices_to_clear_v); + + auto devices_to_clear_w = reg::Read(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_W); + reg::ClearBits(static_cast<volatile u32 &>(devices_to_clear_w), CLK_RST_REG_BITS_MASK(CLK_ENB_W_CLK_ENB_PCIERX0), + CLK_RST_REG_BITS_MASK(CLK_ENB_W_CLK_ENB_PCIERX1), + CLK_RST_REG_BITS_MASK(CLK_ENB_W_CLK_ENB_PCIERX2), + CLK_RST_REG_BITS_MASK(CLK_ENB_W_CLK_ENB_PCIERX3), + CLK_RST_REG_BITS_MASK(CLK_ENB_W_CLK_ENB_PCIERX4), + CLK_RST_REG_BITS_MASK(CLK_ENB_W_CLK_ENB_PCIERX5), + CLK_RST_REG_BITS_MASK(CLK_ENB_W_CLK_ENB_ENTROPY)); + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_W_CLR, devices_to_clear_w); + + auto devices_to_clear_x = reg::Read(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_X); + reg::ClearBits(static_cast<volatile u32 &>(devices_to_clear_x), CLK_RST_REG_BITS_MASK(CLK_ENB_X_CLK_ENB_MC_CAPA ), + CLK_RST_REG_BITS_MASK(CLK_ENB_X_CLK_ENB_MC_CBPA ), + CLK_RST_REG_BITS_MASK(CLK_ENB_X_CLK_ENB_MC_CPU ), + CLK_RST_REG_BITS_MASK(CLK_ENB_X_CLK_ENB_MC_BBC ), + CLK_RST_REG_BITS_MASK(CLK_ENB_X_CLK_ENB_EMC_DLL ), + CLK_RST_REG_BITS_MASK(CLK_ENB_X_CLK_ENB_GPU ), + CLK_RST_REG_BITS_MASK(CLK_ENB_X_CLK_ENB_DBGAPB ), + CLK_RST_REG_BITS_MASK(CLK_ENB_X_CLK_ENB_PLLG_REF)); + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_X_CLR, devices_to_clear_x); + + auto devices_to_clear_y = reg::Read(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_Y); + reg::ClearBits(static_cast<volatile u32 &>(devices_to_clear_y), CLK_RST_REG_BITS_MASK(CLK_ENB_Y_CLK_ENB_MC_CCPA), + CLK_RST_REG_BITS_MASK(CLK_ENB_Y_CLK_ENB_MC_CDPA)); + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_Y_CLR, devices_to_clear_y); + + /* If CH1 is enabled, enable clock to MC1. */ + if (reg::HasValue(EMC + EMC_FBIO_CFG7, EMC_REG_BITS_ENUM(FBIO_CFG7_CH1_ENABLE, ENABLE))) { + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_W_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_W_CLK_ENB_MC1, ENABLE)); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_bootrom_workaround.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_bootrom_workaround.hpp new file mode 100644 index 00000000..6672a6ff --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_bootrom_workaround.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::warmboot { + + void ApplyMbistWorkaround(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_clkrst.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_clkrst.cpp new file mode 100644 index 00000000..062d941a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_clkrst.cpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "warmboot_clkrst.hpp" + +namespace ams::warmboot { + + namespace { + + constexpr inline const uintptr_t CLKRST = secmon::MemoryRegionPhysicalDeviceClkRst.GetAddress(); + constexpr inline const uintptr_t PMC = secmon::MemoryRegionPhysicalDevicePmc.GetAddress(); + constexpr inline const uintptr_t TIMER = secmon::MemoryRegionPhysicalDeviceTimer.GetAddress(); + + } + + void ConfigureOscillators() { + /* Enable the crystal oscillator, and copy the drive strength from pmc. */ + const auto xofs = reg::GetValue(PMC + APBDEV_PMC_OSC_EDPD_OVER, PMC_REG_BITS_MASK(OSC_EDPD_OVER_XOFS)); + + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_OSC_CTRL, CLK_RST_REG_BITS_ENUM (OSC_CTRL_XOE, ENABLE), + CLK_RST_REG_BITS_VALUE(OSC_CTRL_XOFS, xofs)); + + /* Configure CLK_M_DIVISOR to 2. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_SPARE_REG0, CLK_RST_REG_BITS_ENUM(SPARE_REG0_CLK_M_DIVISOR, CLK_M_DIVISOR2)); + reg::Read(CLKRST + CLK_RST_CONTROLLER_SPARE_REG0); + + /* Restore TIMERUS config to 19.2 MHz. */ + reg::Write(TIMER + TIMERUS_USEC_CFG, TIMER_REG_BITS_VALUE(USEC_CFG_USEC_DIVIDEND, 5 - 1), + TIMER_REG_BITS_VALUE(USEC_CFG_USEC_DIVISOR, 96 - 1)); + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_clkrst.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_clkrst.hpp new file mode 100644 index 00000000..99dcad3a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_clkrst.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::warmboot { + + void ConfigureOscillators(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_cpu_cluster.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_cpu_cluster.cpp new file mode 100644 index 00000000..71a6d405 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_cpu_cluster.cpp @@ -0,0 +1,219 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "warmboot_clkrst.hpp" +#include "warmboot_util.hpp" + +namespace ams::warmboot { + + namespace { + + constexpr inline const uintptr_t APB_MISC = secmon::MemoryRegionPhysicalDeviceApbMisc.GetAddress(); + constexpr inline const uintptr_t CLKRST = secmon::MemoryRegionPhysicalDeviceClkRst.GetAddress(); + constexpr inline const uintptr_t FLOW_CTLR = secmon::MemoryRegionPhysicalDeviceFlowController.GetAddress(); + constexpr inline const uintptr_t GPIO = secmon::MemoryRegionPhysicalDeviceGpio.GetAddress(); + constexpr inline const uintptr_t MSELECT = MSELECT(0); + constexpr inline const uintptr_t PMC = secmon::MemoryRegionPhysicalDevicePmc.GetAddress(); + constexpr inline const uintptr_t SYSTEM = secmon::MemoryRegionPhysicalDeviceSystem.GetAddress(); + + ALWAYS_INLINE void EnableClusterPartition(const reg::BitsValue value, APBDEV_PMC_PWRGATE_TOGGLE_PARTID part_id) { + /* Toggle the partitions if necessary. */ + if (!reg::HasValue(PMC + APBDEV_PMC_PWRGATE_STATUS, value)) { + reg::Write(PMC + APBDEV_PMC_PWRGATE_TOGGLE, PMC_REG_BITS_ENUM (PWRGATE_TOGGLE_START, ENABLE), + PMC_REG_BITS_VALUE(PWRGATE_TOGGLE_PARTID, part_id)); + } + + /* Wait for the toggle to complete. */ + while (!reg::HasValue(PMC + APBDEV_PMC_PWRGATE_STATUS, value)) { /* ... */ } + + /* Remove clamping. */ + reg::Write(PMC + APBDEV_PMC_REMOVE_CLAMPING_CMD, value); + + /* Wait for clamping to be removed. */ + while (reg::HasValue(PMC + APBDEV_PMC_CLAMP_STATUS, value)) { /* ... */ } + } + + } + + void InitializeCpuCluster() { + /* Set CoreSight reset. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_RST_DEV_U_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_U_SET_SET_CSITE_RST, ENABLE)); + + /* Restore PROD setting to CPU_SOFTRST_CTRL2 by clearing CAR2PMC_CPU_ACK_WIDTH. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_CPU_SOFTRST_CTRL2, CLK_RST_REG_BITS_VALUE(CPU_SOFTRST_CTRL2_CAR2PMC_CPU_ACK_WIDTH, 0)); + + /* Restore the CPU reset vector from PMC scratch. */ + reg::Write(SYSTEM + SB_AA64_RESET_LOW, reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH34) | 1); + reg::Write(SYSTEM + SB_AA64_RESET_HIGH, reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH35)); + + /* Configure SUPER_CCLKG_DIVIDER. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_SUPER_CCLKG_DIVIDER, CLK_RST_REG_BITS_ENUM (SUPER_CCLKG_DIVIDER_SUPER_CDIV_ENB, ENABLE), + CLK_RST_REG_BITS_ENUM (SUPER_CCLK_DIVIDER_SUPER_CDIV_DIS_FROM_COP_FIQ, NO_IMPACT), + CLK_RST_REG_BITS_ENUM (SUPER_CCLK_DIVIDER_SUPER_CDIV_DIS_FROM_CPU_FIQ, NO_IMPACT), + CLK_RST_REG_BITS_ENUM (SUPER_CCLK_DIVIDER_SUPER_CDIV_DIS_FROM_COP_IRQ, NO_IMPACT), + CLK_RST_REG_BITS_ENUM (SUPER_CCLK_DIVIDER_SUPER_CDIV_DIS_FROM_CPU_IRQ, NO_IMPACT), + CLK_RST_REG_BITS_VALUE(SUPER_CCLK_DIVIDER_SUPER_CDIV_DIVIDEND, 0), + CLK_RST_REG_BITS_VALUE(SUPER_CCLK_DIVIDER_SUPER_CDIV_DIVISOR, 0)); + + /* Configure SUPER_CCLKLP_DIVIDER. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_SUPER_CCLKLP_DIVIDER, CLK_RST_REG_BITS_ENUM (SUPER_CCLKLP_DIVIDER_SUPER_CDIV_ENB, ENABLE), + CLK_RST_REG_BITS_ENUM (SUPER_CCLK_DIVIDER_SUPER_CDIV_DIS_FROM_COP_FIQ, NO_IMPACT), + CLK_RST_REG_BITS_ENUM (SUPER_CCLK_DIVIDER_SUPER_CDIV_DIS_FROM_CPU_FIQ, NO_IMPACT), + CLK_RST_REG_BITS_ENUM (SUPER_CCLK_DIVIDER_SUPER_CDIV_DIS_FROM_COP_IRQ, NO_IMPACT), + CLK_RST_REG_BITS_ENUM (SUPER_CCLK_DIVIDER_SUPER_CDIV_DIS_FROM_CPU_IRQ, NO_IMPACT), + CLK_RST_REG_BITS_VALUE(SUPER_CCLK_DIVIDER_SUPER_CDIV_DIVIDEND, 0), + CLK_RST_REG_BITS_VALUE(SUPER_CCLK_DIVIDER_SUPER_CDIV_DIVISOR, 0)); + + /* Enable CoreSight clock. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_U_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_U_SET_SET_CLK_ENB_CSITE, ENABLE)); + + /* Clear CoreSight reset. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_RST_DEV_U_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_U_CLR_CLR_CSITE_RST, ENABLE)); + + /* Select MSELECT clock source as PLLP_OUT0 with divider of 4. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_MSELECT, CLK_RST_REG_BITS_ENUM (CLK_SOURCE_MSELECT_MSELECT_CLK_SRC, PLLP_OUT0), + CLK_RST_REG_BITS_VALUE(CLK_SOURCE_MSELECT_MSELECT_CLK_DIVISOR, 6)); + + /* Enable clock to MSELECT. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_V_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_V_SET_SET_CLK_ENB_MSELECT, ENABLE)); + + /* Wait two microseconds, then take MSELECT out of reset. */ + util::WaitMicroSeconds(2); + reg::Write(CLKRST + CLK_RST_CONTROLLER_RST_DEV_V_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_V_CLR_CLR_MSELECT_RST, ENABLE)); + + /* Workaround bug by disabling MSELECT error mechanism and enabling WRAP type conversion. */ + reg::ReadWrite(MSELECT + MSELECT_CONFIG, MSELECT_REG_BITS_ENUM(CONFIG_ERR_RESP_EN_SLAVE1, DISABLE), + MSELECT_REG_BITS_ENUM(CONFIG_ERR_RESP_EN_SLAVE2, DISABLE), + MSELECT_REG_BITS_ENUM(CONFIG_WRAP_TO_INCR_SLAVE0, ENABLE), + MSELECT_REG_BITS_ENUM(CONFIG_WRAP_TO_INCR_SLAVE1, ENABLE), + MSELECT_REG_BITS_ENUM(CONFIG_WRAP_TO_INCR_SLAVE2, ENABLE)); + + /* Disable PLLX. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_PLLX_BASE, CLK_RST_REG_BITS_ENUM(PLLX_BASE_PLLX_ENABLE, DISABLE)); + + /* Clear bit 0 of PMC Scratch 190. */ + reg::ReadWrite(PMC + APBDEV_PMC_SCRATCH190, REG_BITS_VALUE(0, 1, 0)); + + /* Clear PMC_DPD_SAMPLE, and wait 10 us for clear to take effect. */ + reg::Write(PMC + APBDEV_PMC_DPD_SAMPLE, 0); + util::WaitMicroSeconds(10); + + /* Configure UART2_RTS low (GPIO controller 2 G). */ + reg::ReadWrite(GPIO + 0x108, REG_BITS_VALUE(2, 1, 1)); /* GPIO_CNF */ + reg::ReadWrite(GPIO + 0x118, REG_BITS_VALUE(2, 1, 1)); /* GPIO_OE */ + reg::ReadWrite(GPIO + 0x128, REG_BITS_VALUE(2, 1, 0)); /* GPIO_OUT */ + + /* Tristate CLDVFS PWN. */ + reg::Write(APB_MISC + PINMUX_AUX_DVFS_PWM, PINMUX_REG_BITS_ENUM(AUX_TRISTATE, TRISTATE), + PINMUX_REG_BITS_ENUM(AUX_DVFS_PWM_PM, CLDVFS)); + reg::Read(APB_MISC + PINMUX_AUX_DVFS_PWM); + + /* Restore PWR_I2C E_INPUT. */ + reg::Write(APB_MISC + PINMUX_AUX_PWR_I2C_SCL, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE)); + reg::Write(APB_MISC + PINMUX_AUX_PWR_I2C_SDA, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE)); + + /* Enable CLDVFS clock. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_W_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_W_SET_SET_CLK_ENB_DVFS, ENABLE)); + + /* Set CLDVFS clock source/divider. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_DVFS_REF, CLK_RST_REG_BITS_ENUM (CLK_SOURCE_DVFS_REF_DVFS_REF_CLK_SRC, PLLP_OUT0), + CLK_RST_REG_BITS_VALUE(CLK_SOURCE_DVFS_REF_DVFS_REF_DIVISOR, 14)); + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_DVFS_SOC, CLK_RST_REG_BITS_ENUM (CLK_SOURCE_DVFS_SOC_DVFS_SOC_CLK_SRC, PLLP_OUT0), + CLK_RST_REG_BITS_VALUE(CLK_SOURCE_DVFS_SOC_DVFS_SOC_DIVISOR, 14)); + + /* Enable PWR_I2C controller (I2C5). */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_H_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_H_SET_SET_CLK_ENB_I2C5, ENABLE)); + reg::Write(CLKRST + CLK_RST_CONTROLLER_RST_DEV_H_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_H_SET_SET_I2C5_RST, ENABLE)); + util::WaitMicroSeconds(5); + + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_I2C5, CLK_RST_REG_BITS_ENUM (CLK_SOURCE_I2C5_I2C5_CLK_SRC, PLLP_OUT0), + CLK_RST_REG_BITS_VALUE(CLK_SOURCE_I2C5_I2C5_CLK_DIVISOR, 4)); + + reg::Write(CLKRST + CLK_RST_CONTROLLER_RST_DEV_H_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_H_CLR_CLR_I2C5_RST, ENABLE)); + + /* Set the EN bit in pmic regulator. */ + pmic::SetEnBit(fuse::GetRegulator()); + + /* Wait 2ms. */ + util::WaitMicroSeconds(2'000); + + /* Enable power to the CRAIL partition. */ + EnableClusterPartition(PMC_REG_BITS_ENUM(PWRGATE_STATUS_CRAIL, ON), APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CRAIL); + + /* Remove software clamp to CRAIL. */ + reg::Write(PMC + APBDEV_PMC_SET_SW_CLAMP, 0); + reg::Write(PMC + APBDEV_PMC_REMOVE_CLAMPING_CMD, PMC_REG_BITS_ENUM(REMOVE_CLAMPING_COMMAND_CRAIL, ENABLE)); + while (reg::HasValue(PMC + APBDEV_PMC_CLAMP_STATUS, PMC_REG_BITS_ENUM(CLAMP_STATUS_CRAIL, ENABLE))) { /* ... */ } + + /* Spinloop 8 times, to add a little delay. */ + SpinLoop(8); + + /* Disable PWR_I2C controller (I2C5). */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_RST_DEV_H_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_H_SET_SET_I2C5_RST, ENABLE)); + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_H_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_H_CLR_CLR_CLK_ENB_I2C5, ENABLE)); + + /* Disable CLDVFS clock. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_W_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_W_CLR_CLR_CLK_ENB_DVFS, ENABLE)); + + /* Perform fast cluster RAM repair if needed. */ + if (!reg::HasValue(FLOW_CTLR + FLOW_CTLR_BPMP_CLUSTER_CONTROL, FLOW_REG_BITS_ENUM(BPMP_CLUSTER_CONTROL_ACTIVE_CLUSTER, SLOW))) { + reg::Write(FLOW_CTLR + FLOW_CTLR_RAM_REPAIR, FLOW_REG_BITS_ENUM(RAM_REPAIR_REQ, ENABLE)); + + while (!reg::HasValue(FLOW_CTLR + FLOW_CTLR_RAM_REPAIR, FLOW_REG_BITS_ENUM(RAM_REPAIR_STS, DONE))) { + /* ... */ + } + } + + /* Enable power to the non-cpu partition. */ + EnableClusterPartition(PMC_REG_BITS_ENUM(PWRGATE_STATUS_C0NC, ON), APBDEV_PMC_PWRGATE_TOGGLE_PARTID_C0NC); + + /* Enable clock to PLLP_OUT_CPU. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_Y_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_Y_SET_SET_CLK_ENB_PLLP_OUT_CPU, ENABLE)); + util::WaitMicroSeconds(2); + + /* Enable clock to the cpu complex. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_L_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_SET_SET_CLK_ENB_CPU, ENABLE)); + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_V_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_V_SET_SET_CLK_ENB_CPUG, ENABLE)); + util::WaitMicroSeconds(10); + + /* Select cpu complex clock source. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_CCLKG_BURST_POLICY, CLK_RST_REG_BITS_ENUM(CCLK_BURST_POLICY_CWAKEUP_IDLE_SOURCE, PLLP_OUT0), + CLK_RST_REG_BITS_ENUM(CCLK_BURST_POLICY_CWAKEUP_RUN_SOURCE, PLLP_OUT0), + CLK_RST_REG_BITS_ENUM(CCLK_BURST_POLICY_CWAKEUP_IRQ_SOURCE, PLLP_OUT0), + CLK_RST_REG_BITS_ENUM(CCLK_BURST_POLICY_CWAKEUP_FIQ_SOURCE, PLLP_OUT0), + CLK_RST_REG_BITS_ENUM(CCLK_BURST_POLICY_CPU_STATE, RUN)); + + reg::Write(CLKRST + CLK_RST_CONTROLLER_CCLKLP_BURST_POLICY, CLK_RST_REG_BITS_ENUM(CCLK_BURST_POLICY_CWAKEUP_IDLE_SOURCE, PLLP_OUT0), + CLK_RST_REG_BITS_ENUM(CCLK_BURST_POLICY_CWAKEUP_RUN_SOURCE, PLLP_OUT0), + CLK_RST_REG_BITS_ENUM(CCLK_BURST_POLICY_CWAKEUP_IRQ_SOURCE, PLLP_OUT0), + CLK_RST_REG_BITS_ENUM(CCLK_BURST_POLICY_CWAKEUP_FIQ_SOURCE, PLLP_OUT0), + CLK_RST_REG_BITS_ENUM(CCLK_BURST_POLICY_CPU_STATE, RUN)); + util::WaitMicroSeconds(10); + + /* Wake non-cpu out of reset. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_RST_CPUG_CMPLX_CLR, CLK_RST_REG_BITS_ENUM(RST_CPUG_CMPLX_CLR_CLR_NONCPURESET, ENABLE)); + } + + void PowerOnCpu() { + /* Enable power to the CE0 partition. */ + EnableClusterPartition(PMC_REG_BITS_ENUM(PWRGATE_STATUS_CE0, ON), APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE0); + + /* Clear CPU reset. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_RST_CPUG_CMPLX_CLR, CLK_RST_REG_BITS_ENUM(RST_CPUG_CMPLX_CLR_CLR_CPURESET0, ENABLE), + CLK_RST_REG_BITS_ENUM(RST_CPUG_CMPLX_CLR_CLR_CORERESET0, ENABLE)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_cpu_cluster.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_cpu_cluster.hpp new file mode 100644 index 00000000..952436a9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_cpu_cluster.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::warmboot { + + void InitializeCpuCluster(); + void PowerOnCpu(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_dram.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_dram.cpp new file mode 100644 index 00000000..4184f964 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_dram.cpp @@ -0,0 +1,130 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "warmboot_dram.hpp" + +namespace ams::warmboot { + + namespace { + + constexpr inline const uintptr_t APB_MISC = secmon::MemoryRegionPhysicalDeviceApbMisc.GetAddress(); + constexpr inline const uintptr_t EMC = EMC_ADDRESS(0); + constexpr inline const uintptr_t EMC0 = EMC0_ADDRESS(0); + constexpr inline const uintptr_t EMC1 = EMC1_ADDRESS(0); + constexpr inline const uintptr_t MC = secmon::MemoryRegionPhysicalDeviceMemoryController.GetAddress(); + constexpr inline const uintptr_t MC0 = secmon::MemoryRegionPhysicalDeviceMemoryController0.GetAddress(); + constexpr inline const uintptr_t MC1 = secmon::MemoryRegionPhysicalDeviceMemoryController1.GetAddress(); + + } + + void RestrictBpmpAccessToMainMemory() { + /* Bpmp memory access is restricted by forcing internal access to an invalid carveout. */ + constexpr u32 ForceInternalAccess0 = reg::Encode(MC_REG_BITS_ENUM(CLIENT_ACCESS0_AVPCARM7R, ENABLE)); + constexpr u32 ForceInternalAccess1 = reg::Encode(MC_REG_BITS_ENUM(CLIENT_ACCESS1_AVPCARM7W, ENABLE)); + + constexpr u32 CarveoutConfig = reg::Encode(MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_IS_WPR, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_FORCE_APERTURE_ID_MATCH, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ALLOW_APERTURE_ID_MISMATCH, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_RD_EN, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_WR_EN, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_SEND_CFG_TO_GPU, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL3, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL2, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL1, ENABLE_CHECKS), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL0, ENABLE_CHECKS), + MC_REG_BITS_VALUE(SECURITY_CARVEOUT_CFG0_APERTURE_ID, 0), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL3, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL2, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL1, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL0, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL3, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL2, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL1, DISABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL0, ENABLED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_ADDRESS_TYPE, UNTRANSLATED_ONLY), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_LOCK_MODE, LOCKED), + MC_REG_BITS_ENUM (SECURITY_CARVEOUT_CFG0_PROTECT_MODE, TZ_SECURE)); + + /* Specify a 128KB carveout at NULL with no clients allowed access, and bpmp forced to access. */ + reg::Write(MC + MC_SECURITY_CARVEOUT4_BOM, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT4_BOM_HI, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT4_SIZE_128KB, 1); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_ACCESS0, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_ACCESS1, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_ACCESS2, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_ACCESS3, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_ACCESS4, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS0, ForceInternalAccess0); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS1, ForceInternalAccess1); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS2, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS3, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS4, 0); + reg::Write(MC + MC_SECURITY_CARVEOUT4_CFG0, CarveoutConfig); + } + + void RestoreRamSvop() { + reg::ReadWrite(APB_MISC + APB_MISC_GP_ASDBGREG, APB_MISC_REG_BITS_VALUE(GP_ASDBGREG_CFG2TMC_RAM_SVOP_PDP, 2)); + } + + void ConfigureEmcPmacroTraining() { + /* Disable writes to BYTE0-7. */ + reg::Write(EMC + EMC_PMACRO_CFG_PM_GLOBAL_0, EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE0, ENABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE1, ENABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE2, ENABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE3, ENABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE4, ENABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE5, ENABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE6, ENABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE7, ENABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_CMD0, DISABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_CMD1, DISABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_CMD2, DISABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_CMD3, DISABLE)); + + /* Set E_WRPTR on Channel 0. */ + reg::Write(EMC + EMC_PMACRO_TRAINING_CTRL_0, EMC_REG_BITS_ENUM(PMACRO_TRAINING_CTRL_0_CH0_TRAINING_ENABLED, DISABLED), + EMC_REG_BITS_ENUM(PMACRO_TRAINING_CTRL_0_CH0_TRAINING_TRAIN_QPOP, DISABLED), + EMC_REG_BITS_ENUM(PMACRO_TRAINING_CTRL_0_CH0_TRAINING_RX_E_DIRECT_ZI, DISABLED), + EMC_REG_BITS_ENUM(PMACRO_TRAINING_CTRL_0_CH0_TRAINING_E_WRPTR, ENABLED), + EMC_REG_BITS_ENUM(PMACRO_TRAINING_CTRL_0_CH0_TRAINING_DRV_DQS, DISABLED)); + + /* Set E_WRPTR on Channel 1. */ + reg::Write(EMC + EMC_PMACRO_TRAINING_CTRL_1, EMC_REG_BITS_ENUM(PMACRO_TRAINING_CTRL_1_CH1_TRAINING_ENABLED, DISABLED), + EMC_REG_BITS_ENUM(PMACRO_TRAINING_CTRL_1_CH1_TRAINING_TRAIN_QPOP, DISABLED), + EMC_REG_BITS_ENUM(PMACRO_TRAINING_CTRL_1_CH1_TRAINING_RX_E_DIRECT_ZI, DISABLED), + EMC_REG_BITS_ENUM(PMACRO_TRAINING_CTRL_1_CH1_TRAINING_E_WRPTR, ENABLED), + EMC_REG_BITS_ENUM(PMACRO_TRAINING_CTRL_1_CH1_TRAINING_DRV_DQS, DISABLED)); + + /* Re-enable writes to BYTE0-7. */ + reg::Write(EMC + EMC_PMACRO_CFG_PM_GLOBAL_0, EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE0, DISABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE1, DISABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE2, DISABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE3, DISABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE4, DISABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE5, DISABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE6, DISABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE7, DISABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_CMD0, DISABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_CMD1, DISABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_CMD2, DISABLE), + EMC_REG_BITS_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_CMD3, DISABLE)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_dram.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_dram.hpp new file mode 100644 index 00000000..d19adf32 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_dram.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::warmboot { + + void RestrictBpmpAccessToMainMemory(); + void RestoreRamSvop(); + void ConfigureEmcPmacroTraining(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_exception_vectors.s b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_exception_vectors.s new file mode 100644 index 00000000..2c280a3e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_exception_vectors.s @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +.section .vectors, "ax", %progbits +.align 3 +.global warmboot_header +warmboot_header: + /* TODO: If Mariko warmboothax ever happens, generate a mariko header? */ + /* Warmboot header. */ + .word __total_size__ + .rept 3 + .word 0x00000000 + .endr + + /* RSA modulus. */ + .rept 0x40 + .word 0xFFFFFFFF + .endr + + /* Padding */ + .rept 4 + .word 0x00000000 + .endr + + /* RSA signature */ + .rept 0x40 + .word 0xFFFFFFFF + .endr + + /* Padding */ + .rept 4 + .word 0x00000000 + .endr + + /* Firmware metadata. */ + .word __total_size__ + .word _reset + .word _reset + .word __executable_size__ + +.global _reset +_reset: + b _ZN3ams8warmboot5StartEv + +.global _metadata +_metadata: + .ascii "WBT0" /* Magic number */ + .word 0x00000000 /* Target firmware. */ + .word 0x00000000 /* Reserved */ + .word 0x00000000 /* Reserved */ \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_main.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_main.cpp new file mode 100644 index 00000000..d9527a9c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_main.cpp @@ -0,0 +1,107 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "warmboot_bootrom_workaround.hpp" +#include "warmboot_clkrst.hpp" +#include "warmboot_cpu_cluster.hpp" +#include "warmboot_dram.hpp" +#include "warmboot_main.hpp" +#include "warmboot_misc.hpp" +#include "warmboot_secure_monitor.hpp" + +namespace ams::warmboot { + + namespace { + + constexpr inline const uintptr_t PMC = secmon::MemoryRegionPhysicalDevicePmc.GetAddress(); + constexpr inline const uintptr_t FLOW_CTLR = secmon::MemoryRegionPhysicalDeviceFlowController.GetAddress(); + + constexpr inline const uintptr_t ExpectedMetadataAddress = 0x40010244; + + } + + void Main(const Metadata *metadata) { + /* Ensure that we're running under vaguely sane conditions. */ + AMS_ABORT_UNLESS(metadata->magic == Metadata::Magic); + + /* Restrict the bpmp's access to dram. */ + if (metadata->target_firmware >= TargetFirmware_4_0_0) { + RestrictBpmpAccessToMainMemory(); + } + + /* Configure rtck-daisychaining/jtag. */ + ConfigureMiscSystemDebug(); + + /* NOTE: Here, Nintendo checks that the number of burnt anti-downgrade fuses is valid. */ + + /* NOTE: Here, Nintendo validates that APBDEV_PMC_SECURE_SCRATCH32 contains the correct magic number for the current warmboot firmware revision. */ + + /* Validate that we're executing at the correct address. */ + AMS_ABORT_UNLESS(reinterpret_cast<uintptr_t>(metadata) == ExpectedMetadataAddress); + + /* Validate that we're executing on the bpmp. */ + AMS_ABORT_UNLESS(reg::Read(PG_UP(PG_UP_TAG)) == PG_UP_TAG_PID_COP); + + /* Configure fuse bypass. */ + fuse::ConfigureFuseBypass(); + + /* Configure system oscillators. */ + ConfigureOscillators(); + + /* Restore DRAM configuration. */ + RestoreRamSvop(); + ConfigureEmcPmacroTraining(); + + /* If on Erista, work around the bootrom mbist issue. */ + if (fuse::GetSocType() == fuse::SocType_Erista) { + ApplyMbistWorkaround(); + } + + /* Initialize the cpu cluster. */ + InitializeCpuCluster(); + + /* Restore the secure monitor to tzram. */ + RestoreSecureMonitorToTzram(metadata->target_firmware); + + /* Power on the cpu. */ + PowerOnCpu(); + + /* Halt ourselves. */ + while (true) { + reg::Write(secmon::MemoryRegionPhysicalDeviceFlowController.GetAddress() + FLOW_CTLR_HALT_COP_EVENTS, FLOW_REG_BITS_ENUM(HALT_COP_EVENTS_MODE, FLOW_MODE_STOP), + FLOW_REG_BITS_ENUM(HALT_COP_EVENTS_JTAG, ENABLED)); + } + } + + NORETURN void ExceptionHandler() { + /* Write enable to MAIN_RESET. */ + reg::Write(PMC + APBDEV_PMC_CNTRL, PMC_REG_BITS_ENUM(CNTRL_MAIN_RESET, ENABLE)); + + /* Wait forever until we're reset. */ + AMS_INFINITE_LOOP(); + } + +} + +namespace ams::diag { + + NORETURN void AbortImpl() { + warmboot::ExceptionHandler(); + } + + #include <exosphere/diag/diag_detailed_assertion_impl.inc> + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_main.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_main.hpp new file mode 100644 index 00000000..dd67c429 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_main.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::warmboot { + + struct Metadata { + static constexpr u32 Magic = util::FourCC<'W','B','T','0'>::Code; + + u32 magic; + ams::TargetFirmware target_firmware; + u32 reserved[2]; + }; + static_assert(util::is_pod<Metadata>::value); + static_assert(sizeof(Metadata) == 0x10); + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_misc.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_misc.cpp new file mode 100644 index 00000000..16c48710 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_misc.cpp @@ -0,0 +1,56 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "warmboot_misc.hpp" + +namespace ams::warmboot { + + namespace { + + constexpr inline const uintptr_t APB_MISC = secmon::MemoryRegionPhysicalDeviceApbMisc.GetAddress(); + constexpr inline const uintptr_t PMC = secmon::MemoryRegionPhysicalDevicePmc.GetAddress(); + constexpr inline const uintptr_t SYSTEM = secmon::MemoryRegionPhysicalDeviceSystem.GetAddress(); + + } + + void ConfigureMiscSystemDebug() { + /* Enable RTCK daisy-chaining. */ + reg::Write(APB_MISC + APB_MISC_PP_CONFIG_CTL, APB_MISC_REG_BITS_ENUM(PP_CONFIG_CTL_TBE, ENABLE)); + + /* If we're in production mode, perform JTAG configuration. */ + /* NOTE: While this is what NVidia's code does, this is almost certainly a logic error. */ + /* They intend to configure JTAG only when *not* in production mode. */ + /* However, here we will do what they do, and not what they intend. */ + const bool production_mode = fuse::IsOdmProductionMode(); + if (production_mode) { + const bool jtag_sts = reg::HasValue(PMC + APBDEV_PMC_STICKY_BITS, PMC_REG_BITS_ENUM(STICKY_BITS_JTAG_STS, ENABLE)); + + reg::ReadWrite(SYSTEM + SB_PFCFG, SB_REG_BITS_ENUM_SEL(PFCFG_DBGEN, jtag_sts, ENABLE, DISABLE), + SB_REG_BITS_ENUM_SEL(PFCFG_SPNIDEN, jtag_sts, ENABLE, DISABLE), + SB_REG_BITS_ENUM (PFCFG_NIDEN, ENABLE), + SB_REG_BITS_ENUM (PFCFG_SPIDEN, DISABLE)); + + reg::ReadWrite(APB_MISC + APB_MISC_PP_CONFIG_CTL, APB_MISC_REG_BITS_ENUM_SEL(PP_CONFIG_CTL_JTAG, jtag_sts, ENABLE, DISABLE)); + } + + /* Configure HDA codec disable. */ + reg::ReadWrite(PMC + APBDEV_PMC_STICKY_BITS, PMC_REG_BITS_ENUM_SEL(STICKY_BITS_HDA_LPBK_DIS, production_mode, ENABLE, DISABLE)); + + /* Set E_INPUT in PINMUX_AUX_GPIO_PA6 (needed by the XUSB and SATA controllers). */ + reg::ReadWrite(APB_MISC + PINMUX_AUX_GPIO_PA6, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_misc.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_misc.hpp new file mode 100644 index 00000000..f78b7710 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_misc.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::warmboot { + + void ConfigureMiscSystemDebug(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_secure_monitor.cpp b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_secure_monitor.cpp new file mode 100644 index 00000000..51308362 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_secure_monitor.cpp @@ -0,0 +1,130 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "warmboot_secure_monitor.hpp" + +namespace ams::warmboot { + + namespace { + + constexpr inline const uintptr_t PMC = secmon::MemoryRegionPhysicalDevicePmc.GetAddress(); + + constexpr inline const pkg1::AesKeySlot SavedAesKeySlots[] = { + pkg1::AesKeySlot_TzramSaveKek, + pkg1::AesKeySlot_RandomForUserWrap, + pkg1::AesKeySlot_RandomForKeyStorageWrap, + pkg1::AesKeySlot_DeviceMaster, + pkg1::AesKeySlot_Master, + pkg1::AesKeySlot_Device, + }; + + constexpr ALWAYS_INLINE bool IsSavedAesKeySlot(int slot) { + for (const auto SavedSlot : SavedAesKeySlots) { + if (slot == SavedSlot) { + return true; + } + } + + return false; + } + + void ClearUnsavedSecurityEngineKeySlots() { + /* Clear unsaved aes keys and all ivs. */ + for (int slot = 0; slot < se::AesKeySlotCount; ++slot) { + if (!IsSavedAesKeySlot(slot)) { + se::ClearAesKeySlot(slot); + } + se::ClearAesKeyIv(slot); + } + + /* Clear all rsa keys. */ + for (int slot = 0; slot < se::RsaKeySlotCount; ++slot) { + se::ClearRsaKeySlot(slot); + } + } + + void RestoreEncryptedTzram(void * const tzram_dst, const void * const tzram_src, size_t tzram_size) { + /* Derive the save key from the save kek. */ + const u32 key_source[se::AesBlockSize / sizeof(u32)] = { reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH24), reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH25), reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH26), reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH27)}; + se::ClearAesKeySlot(pkg1::AesKeySlot_TzramSaveKey); + se::SetEncryptedAesKey256(pkg1::AesKeySlot_TzramSaveKey, pkg1::AesKeySlot_TzramSaveKek, key_source, sizeof(key_source)); + + /* Decrypt tzram. */ + const u8 tzram_iv[se::AesBlockSize] = {}; + se::DecryptAes256Cbc(tzram_dst, tzram_size, pkg1::AesKeySlot_TzramSaveKey, tzram_src, tzram_size, tzram_iv, sizeof(tzram_iv)); + + /* Calculate the cmac of decrypted tzram. */ + u8 tzram_mac[se::AesBlockSize] = {}; + se::ComputeAes256Cmac(tzram_mac, sizeof(tzram_mac), pkg1::AesKeySlot_TzramSaveKey, tzram_dst, tzram_size); + + /* Get the expected mac from pmc scratch. */ + const u32 expected_mac[sizeof(tzram_mac) / sizeof(u32)] = { reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH112), reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH113), reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH114), reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH115)}; + + /* Validate that the calculated mac is correct. */ + AMS_ABORT_UNLESS(crypto::IsSameBytes(tzram_mac, expected_mac, sizeof(tzram_mac))); + } + + void RestoreSecureMonitorToTzramErista(const TargetFirmware target_fw) { + /* Clear all unsaved security engine keyslots. */ + ClearUnsavedSecurityEngineKeySlots(); + + /* Restore encrypted tzram contents. */ + void * const tzram_src = secmon::MemoryRegionPhysicalDramSecureDataStoreTzram.GetPointer<void>(); + void * const tzram_dst = secmon::MemoryRegionPhysicalTzramNonVolatile.GetPointer<void>(); + const size_t tzram_size = secmon::MemoryRegionPhysicalTzramNonVolatile.GetSize(); + RestoreEncryptedTzram(tzram_dst, tzram_src, tzram_size); + + /* Clear the tzram kek registers. */ + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH24, 0); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH25, 0); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH26, 0); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH27, 0); + + /* Clear the tzram cmac registers. */ + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH112, 0); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH113, 0); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH114, 0); + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH115, 0); + + /* Clear the keydata used to protect tzram. */ + se::ClearAesKeySlot(pkg1::AesKeySlot_TzramSaveKek); + se::ClearAesKeySlot(pkg1::AesKeySlot_TzramSaveKey); + + /* Clear the encrypted copy of tzram in dram. */ + /* NOTE: This does not actually clear the encrypted copy, because BPMP access to main memory has been restricted. */ + /* Nintendo seems to not realize this, though, so we'll do the same. */ + std::memset(tzram_src, 0, tzram_size); + + /* Set Tzram to secure-world only. */ + se::SetTzramSecure(); + } + + } + + void RestoreSecureMonitorToTzram(const TargetFirmware target_fw) { + /* If erista, perform restoration procedure. */ + if (fuse::GetSocType() == fuse::SocType_Erista) { + RestoreSecureMonitorToTzramErista(target_fw); + } + + /* Lock secure scratch. */ + pmc::LockSecureRegister(static_cast<pmc::SecureRegister>(pmc::SecureRegister_DramParameters | pmc::SecureRegister_Other)); + + /* Lockout fuses. */ + fuse::Lockout(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_secure_monitor.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_secure_monitor.hpp new file mode 100644 index 00000000..f0dda360 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_secure_monitor.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::warmboot { + + void RestoreSecureMonitorToTzram(const TargetFirmware target_fw); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_start.s b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_start.s new file mode 100644 index 00000000..aafdf7af --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_start.s @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +.section .text._ZN3ams8warmboot5StartEv, "ax", %progbits +.align 3 +.global _ZN3ams8warmboot5StartEv +_ZN3ams8warmboot5StartEv: + /* Set CPSR_cf and CPSR_cf. */ + msr cpsr_f, #0xC0 + msr cpsr_cf, #0xD3 + + /* Set the stack pointer. */ + ldr sp, =__stack_top__ + + /* Set our link register to the exception handler. */ + ldr lr, =_ZN3ams8warmboot16ExceptionHandlerEv + + /* Invoke main. */ + ldr r0, =_metadata + b _ZN3ams8warmboot4MainEPKNS0_8MetadataE + + /* Infinite loop. */ + 1: b 1b \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_util.hpp b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_util.hpp new file mode 100644 index 00000000..63759817 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_util.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::warmboot { + + void SpinLoop(unsigned int num); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_util_asm.s b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_util_asm.s new file mode 100644 index 00000000..650ac414 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/source/warmboot_util_asm.s @@ -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 <http://www.gnu.org/licenses/>. + */ + +.section .text._ZN3ams8warmboot8SpinLoopEj, "ax", %progbits +.global _ZN3ams8warmboot8SpinLoopEj +.thumb_func +.syntax unified +_ZN3ams8warmboot8SpinLoopEj: + 1: + /* Subtract one from the count. */ + subs r0, r0, #1 + + /* If we aren't at zero, continue looping. */ + bgt 1b + + /* Return. */ + bx lr \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/warmboot.ld b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/warmboot.ld new file mode 100644 index 00000000..0e3d4482 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/warmboot.ld @@ -0,0 +1,189 @@ +OUTPUT_ARCH(arm) +ENTRY(_reset) + +MEMORY +{ + NULL : ORIGIN = 0, LENGTH = 4K + lp0fw : ORIGIN = 0x40010000, LENGTH = 16K +} + + +SECTIONS +{ + /* =========== CODE section =========== */ + PROVIDE(__start__ = ORIGIN(lp0fw)); + . = __start__; + __code_start = . ; + + .vectors : + { + KEEP (*(.vectors .vectors.*)) + . = ALIGN(8); + } >lp0fw + + .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); + } >lp0fw + + .init : + { + KEEP( *(.init) ) + . = ALIGN(8); + } >lp0fw + + .plt : + { + *(.plt) + *(.iplt) + . = ALIGN(8); + } >lp0fw + + .fini : + { + KEEP( *(.fini) ) + . = ALIGN(8); + } >lp0fw + + + /* =========== RODATA section =========== */ + . = ALIGN(8); + __rodata_start = . ; + + .rodata : + { + *(.rodata .rodata.* .gnu.linkonce.r.*) + . = ALIGN(8); + } >lp0fw + + .eh_frame_hdr : { __eh_frame_hdr_start = .; *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) __eh_frame_hdr_end = .; } >lp0fw + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } >lp0fw + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } >lp0fw + .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } >lp0fw + + .hash : { *(.hash) } >lp0fw + + /* =========== DATA section =========== */ + . = ALIGN(8); + __data_start = . ; + + .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } >lp0fw + .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } >lp0fw + .gnu_extab : ONLY_IF_RW { *(.gnu_extab*) } >lp0fw + .exception_ranges : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) } >lp0fw + + .preinit_array ALIGN(8) : + { + PROVIDE (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE (__preinit_array_end = .); + } >lp0fw + + .init_array ALIGN(8) : + { + PROVIDE (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + PROVIDE (__init_array_end = .); + } >lp0fw + + .fini_array ALIGN(8) : + { + PROVIDE (__fini_array_start = .); + KEEP (*(.fini_array)) + KEEP (*(SORT(.fini_array.*))) + PROVIDE (__fini_array_end = .); + } >lp0fw + + .ctors ALIGN(8) : + { + KEEP (*crtbegin.o(.ctors)) /* MUST be first -- GCC requires it */ + KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + } >lp0fw + + .dtors ALIGN(8) : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + } >lp0fw + + __got_start__ = .; + + .got : { *(.got) *(.igot) } >lp0fw + .got.plt : { *(.got.plt) *(.igot.plt) } >lp0fw + + __got_end__ = .; + + .data ALIGN(8) : + { + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + } >lp0fw + + __bss_start__ = .; + .bss ALIGN(8) : + { + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + . = ALIGN(16); + } >lp0fw + __bss_end__ = .; + + __end__ = ABSOLUTE(.) ; + + __total_size__ = (__end__ - __start__); + __executable_size__ = (__end__ - _reset); + + __stack_top__ = 0x40013000; + __stack_bottom__ = 0x40012000; + + /* ================== + ==== 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/warmboot/warmboot.mk b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/warmboot.mk new file mode 100644 index 00000000..104130b7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/warmboot.mk @@ -0,0 +1,113 @@ +#--------------------------------------------------------------------------------- +# 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) + +#--------------------------------------------------------------------------------- +# 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/warmboot/warmboot.specs b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/warmboot.specs new file mode 100644 index 00000000..d496f567 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/exosphere/warmboot/warmboot.specs @@ -0,0 +1,4 @@ +%rename link old_link + +*link: +%(old_link) -T %:getenv(ATMOSPHERE_TOPDIR /warmboot.ld) --gc-sections --nmagic \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/Makefile b/Source/Atmosphere-MTC-Unlock/fusee/Makefile new file mode 100644 index 00000000..120342c8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/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)/fusee.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)/fusee.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, arm7tdmi,)) + +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/fusee/build_package3.py b/Source/Atmosphere-MTC-Unlock/fusee/build_package3.py new file mode 100644 index 00000000..d4d8bed1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/build_package3.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python +import sys, lz4, hashlib, os +from struct import unpack as up, pack as pk + +def lz4_compress(data): + try: + import lz4.block as block + except ImportError: + block = lz4.LZ4_compress + return block.compress(data, 'high_compression', store_size=False) + +def read_file(fn): + with open(fn, 'rb') as f: + return f.read() + +def pad(data, size): + assert len(data) <= size + return (data + b'\x00' * size)[:size] + +def get_overlay(program, i): + return program[0x2B000 + 0x14000 * i:0x2B000 + 0x14000 * (i+1)] + +KIP_NAMES = [b'Loader', b'NCM', b'ProcessManager', b'sm', b'boot', b'spl', b'ams_mitm'] + +def get_kips(ams_dir, build_out_dir): + emummc = read_file(os.path.join(ams_dir, 'emummc/emummc_unpacked.kip')) + loader = read_file(os.path.join(ams_dir, 'stratosphere/loader/%s/loader.kip' % build_out_dir)) + ncm = read_file(os.path.join(ams_dir, 'stratosphere/ncm/%s/ncm.kip' % build_out_dir)) + pm = read_file(os.path.join(ams_dir, 'stratosphere/pm/%s/pm.kip' % build_out_dir)) + sm = read_file(os.path.join(ams_dir, 'stratosphere/sm/%s/sm.kip' % build_out_dir)) + boot = read_file(os.path.join(ams_dir, 'stratosphere/boot/%s/boot.kip' % build_out_dir)) + spl = read_file(os.path.join(ams_dir, 'stratosphere/spl/%s/spl.kip' % build_out_dir)) + ams_mitm = read_file(os.path.join(ams_dir, 'stratosphere/ams_mitm/%s/ams_mitm.kip' % build_out_dir)) + return (emummc, { + b'Loader' : loader, + b'NCM' : ncm, + b'ProcessManager' : pm, + b'sm' : sm, + b'boot' : boot, + b'spl' : spl, + b'ams_mitm' : ams_mitm, + }) + +def write_kip_meta(f, kip, ofs): + # Write program id + f.write(kip[0x10:0x18]) + # Write offset, size + f.write(pk('<II', ofs - 0x100000, len(kip))) + # Write hash + f.write(hashlib.sha256(kip).digest()) + +def write_header(f, all_kips, wb_size, tk_size, xf_size, ex_size, ms_size, fs_size, rb_size, git_revision, major, minor, micro, relstep, s_major, s_minor, s_micro, s_relstep): + # Unpack kips + emummc, kips = all_kips + # Write magic as PK31 magic. + f.write(b'PK31') + # Write metadata offset = 0x10 + f.write(pk('<I', 0x20)) + # Write flags + f.write(pk('<I', 0x00000000)) + # Write meso_size + f.write(pk('<I', ms_size)) + # Write num_kips + f.write(pk('<I', len(KIP_NAMES))) + # Write reserved1 + f.write(b'\xCC' * 0xC) + # Write legacy magic + f.write(b'FSS0') + # Write total size + f.write(pk('<I', 0x800000)) + # Write reserved2 + f.write(pk('<I', 0xCCCCCCCC)) + # Write content_header_offset + f.write(pk('<I', 0x40)) + # Write num_content_headers; + f.write(pk('<I', 8 + len(KIP_NAMES))) + # Write supported_hos_version; + f.write(pk('<BBBB', s_relstep, s_micro, s_minor, s_major)) + # Write release_version; + f.write(pk('<BBBB', relstep, micro, minor, major)) + # Write git_revision; + f.write(pk('<I', git_revision)) + # Write content metas + f.write(pk('<IIBBBBI16s', 0x000800, wb_size, 2, 0, 0, 0, 0xCCCCCCCC, b'warmboot')) + f.write(pk('<IIBBBBI16s', 0x002000, tk_size, 12, 0, 0, 0, 0xCCCCCCCC, b'tsec_keygen')) + f.write(pk('<IIBBBBI16s', 0x004000, xf_size, 11, 0, 0, 0, 0xCCCCCCCC, b'exosphere_fatal')) + f.write(pk('<IIBBBBI16s', 0x048000, ex_size, 1, 0, 0, 0, 0xCCCCCCCC, b'exosphere')) + f.write(pk('<IIBBBBI16s', 0x056000, ms_size, 10, 0, 0, 0, 0xCCCCCCCC, b'mesosphere')) + f.write(pk('<IIBBBBI16s', 0x7C0000, fs_size, 0, 0, 0, 0, 0xCCCCCCCC, b'fusee')) + f.write(pk('<IIBBBBI16s', 0x7E0000, rb_size, 3, 0, 0, 0, 0xCCCCCCCC, b'rebootstub')) + f.write(pk('<IIBBBBI16s', 0x100000, len(emummc), 8, 0, 0, 0, 0xCCCCCCCC, b'emummc')) + ofs = (0x100000 + len(emummc) + 0xF) & ~0xF + for kip_name in KIP_NAMES: + kip_data = kips[kip_name] + f.write(pk('<IIBBBBI16s', ofs, len(kip_data), 6, 0, 0, 0, 0xCCCCCCCC, kip_name)) + ofs += len(kip_data) + ofs += 0xF + ofs &= ~0xF + # Pad to kip metas. + f.write(b'\xCC' * (0x400 - 0x40 - (0x20 * (8 + len(KIP_NAMES))))) + # Write emummc_meta. */ + write_kip_meta(f, emummc, 0x100000) + # Write kip metas + ofs = (0x100000 + len(emummc) + 0xF) & ~0xF + for kip_name in KIP_NAMES: + kip_data = kips[kip_name] + write_kip_meta(f, kip_data, ofs) + ofs += len(kip_data) + ofs += 0xF + ofs &= ~0xF + # Pad to end of header + f.write(b'\xCC' * (0x800 - (0x400 + (1 + len(KIP_NAMES)) * 0x30))) + +def write_kips(f, all_kips): + # Unpack kips + emummc, kips = all_kips + # Write emummc + f.write(emummc) + # Write kips + tot = len(emummc) + if (tot & 0xF): + f.write(b'\xCC' * (0x10 - (tot & 0xF))) + tot += 0xF + tot &= ~0xF + for kip_name in KIP_NAMES: + kip_data = kips[kip_name] + f.write(kip_data) + tot += len(kip_data) + if (tot & 0xF): + f.write(b'\xCC' * (0x10 - (tot & 0xF))) + tot += 0xF + tot &= ~0xF + # Pad to 3 MB + f.write(b'\xCC' * (0x300000 - tot)) + +def main(argc, argv): + if argc != 13: + print('Usage: %s ams_dir build_out_dir build_boot_out_dir revision major minor micro relstep s_major s_minor s_micro s_relstep' % argv[0]) + return 1 + # Parse arguments + ams_dir = argv[1] + build_out_dir = argv[2] + build_boot_out_dir = argv[3] + revision = int(argv[4][:8], 16) + major = int(argv[5]) + minor = int(argv[6]) + micro = int(argv[7]) + relstep = int(argv[8]) + s_major = int(argv[9]) + s_minor = int(argv[10]) + s_micro = int(argv[11]) + s_relstep = int(argv[12]) + # Read/parse fusee + fusee_program = read_file(os.path.join(ams_dir, 'fusee/program/%s/program.bin' % build_boot_out_dir)) + fusee_bin = read_file(os.path.join(ams_dir, 'fusee/%s/fusee.bin' % build_boot_out_dir)) + erista_mtc = get_overlay(fusee_program, 1) + mariko_mtc = get_overlay(fusee_program, 2) + erista_hsh = hashlib.sha256(erista_mtc[:-4]).digest()[:4] + mariko_hsh = hashlib.sha256(mariko_mtc[:-4]).digest()[:4] + # Read other files + exosphere = read_file(os.path.join(ams_dir, 'exosphere/%s/exosphere.bin' % build_out_dir)) + warmboot = read_file(os.path.join(ams_dir, 'exosphere/warmboot/%s/warmboot.bin' % build_boot_out_dir)) + mariko_fatal = read_file(os.path.join(ams_dir, 'exosphere/mariko_fatal/%s/mariko_fatal.bin' % build_out_dir)) + rebootstub = read_file(os.path.join(ams_dir, 'exosphere/program/rebootstub/%s/rebootstub.bin' % build_boot_out_dir)) + mesosphere = read_file(os.path.join(ams_dir, 'mesosphere/%s/mesosphere.bin' % build_out_dir)) + all_kips = get_kips(ams_dir, build_out_dir) + tsec_keygen = read_file(os.path.join(ams_dir, 'fusee/program/tsec_keygen/tsec_keygen.bin')) + splash_bin = read_file(os.path.join(ams_dir, 'img/splash.bin')) + assert len(splash_bin) == 0x3C0000 + with open(os.path.join(ams_dir, 'fusee/%s/package3' % build_boot_out_dir), 'wb') as f: + # Write header + write_header(f, all_kips, len(warmboot), len(tsec_keygen), len(mariko_fatal), len(exosphere), len(mesosphere), len(fusee_bin), len(rebootstub), revision, major, minor, micro, relstep, s_major, s_minor, s_micro, s_relstep) + # Write warmboot + f.write(pad(warmboot, 0x1800)) + # Write TSEC Keygen + f.write(pad(tsec_keygen, 0x2000)) + # Write Mariko Fatal + f.write(pad(mariko_fatal, 0x1C000)) + # Write Erista MTC + f.write(erista_mtc[:-4] + erista_hsh) + # Write Mariko MTC + f.write(mariko_mtc[:-4] + mariko_hsh) + # Write exosphere + f.write(pad(exosphere, 0xE000)) + # Write mesosphere + f.write(pad(mesosphere, 0xAA000)) + # Write kips + write_kips(f, all_kips) + # Write Splash Screen + f.write(splash_bin) + # Write fusee + f.write(pad(fusee_bin, 0x20000)) + # Write rebootstub + f.write(pad(rebootstub, 0x1000)) + # Pad to 8 MB + f.write(b'\xCC' * (0x800000 - 0x7E1000)) + + + return 0 + +if __name__ == '__main__': + sys.exit(main(len(sys.argv), sys.argv)) diff --git a/Source/Atmosphere-MTC-Unlock/fusee/fusee.mk b/Source/Atmosphere-MTC-Unlock/fusee/fusee.mk new file mode 100644 index 00000000..5f100a14 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/fusee.mk @@ -0,0 +1,48 @@ +#--------------------------------------------------------------------------------- +# 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)/fusee.bin + +$(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)/fusee.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)/fusee.bin + @echo "Built fusee.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.bin: check_program + @$(SILENTCMD)echo "Checked program." + +$(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/$(ATMOSPHERE_LIBRARY_DIR)/libexosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +check_loader_stub: $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/$(ATMOSPHERE_LIBRARY_DIR)/libexosphere.a $(CURRENT_DIRECTORY)/program/$(ATMOSPHERE_OUT_DIR)/program.bin + @$(SILENTCMD)echo "Checking loader stub..." + @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/loader_stub -f $(CURRENT_DIRECTORY)/loader_stub/loader_stub.mk ATMOSPHERE_CHECKED_LIBEXOSPHERE=1 ATMOSPHERE_CHECKED_FUSEE_PROGRAM=1 + +check_program: $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/$(ATMOSPHERE_LIBRARY_DIR)/libexosphere.a + @$(SILENTCMD)echo "Checking program..." + @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/program -f $(CURRENT_DIRECTORY)/program/program.mk ATMOSPHERE_CHECKED_LIBEXOSPHERE=1 + +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 + +$(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 + @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_loader_stub check_program \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/Makefile b/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/Makefile new file mode 100644 index 00000000..af41919d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/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, arm7tdmi,)) + +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/fusee/loader_stub/loader_stub.ld b/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/loader_stub.ld new file mode 100644 index 00000000..0db3c298 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/loader_stub.ld @@ -0,0 +1,126 @@ +OUTPUT_ARCH(arm) +ENTRY(_ZN3ams6nxboot6loader5StartEv) + +MEMORY +{ + NULL : ORIGIN = 0, LENGTH = 4K + data : ORIGIN = 0x40010000, LENGTH = 0x28000 + loader_stub : ORIGIN = 0x4003D000, LENGTH = 4K +} + + +SECTIONS +{ + /* =========== CODE section =========== */ + + . = ORIGIN(data); + __data_start__ = . ; + + .crt0 : + { + KEEP(*(.crt0 .crt0.*)) + . = ALIGN(8); + } >data + + /* =========== RODATA section =========== */ + . = ALIGN(8); + + .rodata : + { + *(.rodata .rodata.* .gnu.linkonce.r.*) + . = ALIGN(8); + } >data + + .eh_frame_hdr : { __eh_frame_hdr_start = .; *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) __eh_frame_hdr_end = .; } >data + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } >data + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } >data + .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } >data + + .hash : { *(.hash) } >data + + /* =========== DATA section =========== */ + . = ALIGN(8); + + .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 + + .data ALIGN(8) : + { + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + } >data + + __bss_start__ = .; + .bss (NOLOAD) : + { + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + . = ALIGN(32); + } >data :NONE + __bss_end__ = .; + + __data_end__ = ABSOLUTE(.); + + + .loader_stub : + { + . = ALIGN(32); + PROVIDE (__loader_stub_start__ = ABSOLUTE(.)); + PROVIDE (__loader_stub_lma__ = LOADADDR(.loader_stub)); + fusee_loader_main.o(.text*) + fusee_loader_uncompress.o(.text*) + fusee_loader_error.o(.text*) + *(.text.memcpy) + fusee_loader_main.o(.rodata*) + fusee_loader_uncompress.o(.rodata*) + fusee_loader_error.o(.rodata*) + fusee_loader_main.o(.data*) + fusee_loader_uncompress.o(.data*) + fusee_loader_error.o(.data*) + . = ALIGN(32); + PROVIDE (__loader_stub_end__ = ABSOLUTE(.)); + } >loader_stub AT>data + + /* ================== + ==== 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/fusee/loader_stub/loader_stub.mk b/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/loader_stub.mk new file mode 100644 index 00000000..7cc7f7a3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/loader_stub.mk @@ -0,0 +1,127 @@ +#--------------------------------------------------------------------------------- +# 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)) $(CURRENT_DIRECTORY)/../program/$(ATMOSPHERE_OUT_DIR) + +CFILES := $(call FIND_SOURCE_FILES,$(SOURCES),c) +CPPFILES := $(call FIND_SOURCE_FILES,$(SOURCES),cpp) +SFILES := $(call FIND_SOURCE_FILES,$(SOURCES),s) +BINFILES := program.lz4 + +#--------------------------------------------------------------------------------- +# 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 $(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_fusee_program + @$(SILENTCMD)echo "Checked fusee program." + +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_FUSEE_PROGRAM),1) +check_fusee_program: +else +check_fusee_program: check_lib + @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/../program -f $(CURRENT_DIRECTORY)/../program/program.mk ATMOSPHERE_CHECKED_LIBEXOSPHERE=1 +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) + +program.lz4.o: program.lz4 + @echo $(notdir $<) + @$(bin2o) + +$(OFILES_SRC) : $(OFILES_BIN) + +#--------------------------------------------------------------------------------- +# 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/fusee/loader_stub/loader_stub.specs b/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/loader_stub.specs new file mode 100644 index 00000000..7326f34d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/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/fusee/loader_stub/source/fusee_loader_error.cpp b/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/source/fusee_loader_error.cpp new file mode 100644 index 00000000..690a54b3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/source/fusee_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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_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::nxboot::loader::ErrorStop(); + } + + 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::nxboot::loader::ErrorStop(); + } + + NORETURN void AbortImpl() { + ams::nxboot::loader::ErrorStop(); + } + +} + +namespace ams::nxboot::loader { + + NORETURN void ErrorStop() { + /* Halt ourselves. */ + while (true) { + reg::Write(secmon::MemoryRegionPhysicalDeviceFlowController.GetAddress() + FLOW_CTLR_HALT_COP_EVENTS, FLOW_REG_BITS_ENUM(HALT_COP_EVENTS_MODE, FLOW_MODE_STOP), + FLOW_REG_BITS_ENUM(HALT_COP_EVENTS_JTAG, ENABLED)); + } + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/source/fusee_loader_error.hpp b/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/source/fusee_loader_error.hpp new file mode 100644 index 00000000..7f69ed09 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/source/fusee_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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#pragma once + +namespace ams::nxboot::loader { + + NORETURN void ErrorStop(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/source/fusee_loader_main.cpp b/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/source/fusee_loader_main.cpp new file mode 100644 index 00000000..75d41caa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/source/fusee_loader_main.cpp @@ -0,0 +1,56 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_loader_uncompress.hpp" +#include "program_lz4.h" + +namespace ams::nxboot::loader { + + namespace { + + constexpr uintptr_t ProgramImageBase = 0x40001000; + constexpr uintptr_t ProgramImageEnd = 0x4003D000; + constexpr size_t ProgramImageSizeMax = ProgramImageEnd - ProgramImageBase; + + void CopyBackwards(void *dst, const void *src, size_t size) { + u8 *dst_8 = static_cast<u8 *>(dst) + size; + const u8 *src_8 = static_cast<const u8 *>(src) + size; + + for (size_t i = 0; i < size; ++i) { + *(--dst_8) = *(--src_8); + } + } + + } + + NORETURN void UncompressAndExecute(const void *program, size_t program_size) { + /* Relocate the compressed binary to a place where we can safely decompress it. */ + void *relocated_program = reinterpret_cast<void *>(ProgramImageEnd - program_size); + if (relocated_program != program) { + CopyBackwards(relocated_program, program, program_size); + } + + /* Uncompress the program image. */ + Uncompress(reinterpret_cast<void *>(ProgramImageBase), ProgramImageSizeMax, relocated_program, program_size); + + /* Jump to the boot image. */ + reinterpret_cast<void (*)()>(ProgramImageBase)(); + + /* We will never reach this point. */ + __builtin_unreachable(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/source/fusee_loader_start.s b/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/source/fusee_loader_start.s new file mode 100644 index 00000000..d4a725e9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/source/fusee_loader_start.s @@ -0,0 +1,85 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + + + +.section .crt0._ZN3ams6nxboot6loader5StartEv, "ax", %progbits +.arm +.align 5 +.global _ZN3ams6nxboot6loader5StartEv +.type _ZN3ams6nxboot6loader5StartEv, %function +_ZN3ams6nxboot6loader5StartEv: + /* Switch to system mode, mask all interrupts, clear all flags */ + msr cpsr_cxsf, #0xDF + + /* Relocate main program. */ + ldr r0, =_ZN3ams6nxboot6loader5StartEv + adr r1, _ZN3ams6nxboot6loader5StartEv + cmp r0, r1 + beq 3f + + /* Relocate first 0x100. */ + mov r4, #0x100 + 0: + ldmia r1!, {r5-r12} + stmia r0!, {r5-r12} + subs r4, #0x20 + bne 0b + + /* Jump, continue relocating. */ + ldr r3, =1f + bx r3 + + 1: + ldr r4, =__loader_stub_lma__ + ldr r3, =__loader_stub_end__ + add r4, r4, r3 + ldr r3, =__loader_stub_start__ + sub r4, r4, r3 + sub r4, r4, r0 + 2: + ldmia r1!, {r5-r12} + stmia r0!, {r5-r12} + subs r4, #0x20 + bne 2b + + /* Relocate loader stub. */ + 3: + ldr r2, =__loader_stub_lma__ + + ldr r3, =__loader_stub_start__ + ldr r4, =__loader_stub_end__ + sub r4, r4, r3 + 4: + ldmia r2!, {r5-r12} + stmia r3!, {r5-r12} + subs r4, #0x20 + bne 4b + + /* Set the stack pointer */ + ldr sp, =0x40001000 + mov fp, #0 + + /* Generate arguments. */ + ldr r3, =program_lz4 + ldr r4, =program_lz4_end + sub r4, r4, r3 + mov r0, r3 + mov r1, r4 + + /* Jump to the loader stub. */ + ldr r3, =_ZN3ams6nxboot6loader20UncompressAndExecuteEPKvj + bx r3 \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/source/fusee_loader_uncompress.cpp b/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/source/fusee_loader_uncompress.cpp new file mode 100644 index 00000000..94ed4ff7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/source/fusee_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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_loader_uncompress.hpp" + +namespace ams::nxboot::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<const u8 *>(src)), m_src_size(src_size), m_src_offset(0), m_dst(static_cast<u8 *>(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 and execute a decompressor. */ + Lz4Uncompressor(dst, dst_size, src, src_size).Uncompress(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/source/fusee_loader_uncompress.hpp b/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/source/fusee_loader_uncompress.hpp new file mode 100644 index 00000000..8afe05a4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/loader_stub/source/fusee_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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> +#pragma once + +namespace ams::nxboot::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/fusee/program/Makefile b/Source/Atmosphere-MTC-Unlock/fusee/program/Makefile new file mode 100644 index 00000000..7e5fde26 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/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)/program.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)/program.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, arm7tdmi,)) + +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/fusee/program/lz4_compress.py b/Source/Atmosphere-MTC-Unlock/fusee/program/lz4_compress.py new file mode 100644 index 00000000..c242f35d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/lz4_compress.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +import sys, lz4, hashlib +from struct import unpack as up, pack as pk + +def lz4_compress(data): + try: + import lz4.block as block + except ImportError: + block = lz4.LZ4_compress + return block.compress(data, 'high_compression', store_size=False) + +def read_file(fn): + with open(fn, 'rb') as f: + return f.read() + +def get_overlay(program, i): + return program[0x2B000 + 0x14000 * i:0x2B000 + 0x14000 * (i+1)] + +def main(argc, argv): + if argc != 3: + print('Usage: %s in out' % argv[0]) + return 1 + data = read_file(argv[1]) + erista_mtc = get_overlay(data, 1) + mariko_mtc = get_overlay(data, 2) + erista_hsh = hashlib.sha256(erista_mtc[:-4]).digest()[:4] + mariko_hsh = hashlib.sha256(mariko_mtc[:-4]).digest()[:4] + fusee_program = lz4_compress(data[:0x2B000 - 8] + erista_hsh + mariko_hsh + get_overlay(data, 0)[:0x11000]) + with open(argv[2], 'wb') as f: + f.write(fusee_program) + + return 0 + +if __name__ == '__main__': + sys.exit(main(len(sys.argv), sys.argv)) diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/program.ld b/Source/Atmosphere-MTC-Unlock/fusee/program/program.ld new file mode 100644 index 00000000..ce165ea9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/program.ld @@ -0,0 +1,195 @@ +OUTPUT_ARCH(arm) +ENTRY(_ZN3ams6nxboot5StartEv) + +SECTIONS +{ + /* =========== CODE section =========== */ + + PROVIDE(__start__ = ORIGIN(main)); + . = __start__; + __main_start__ = . ; + + .crt0 : + { + FILL(0x00000000) + KEEP (*(.crt0 .crt0.*)) + . = ORIGIN(main) + 0xC0 - 1; + BYTE(00); + } >main AT>glob + + .text : + { + FILL(0x00000000) + KEEP(*(.text._ZN3ams4util15GetMicroSecondsEv)) + KEEP(*(.text._ZN3ams4util16WaitMicroSecondsEi)) + KEEP(*(.text.memcpy)) + KEEP(*(.text.memset)) + KEEP(*(.text.memcmp)) + KEEP(*(.text._ZN3ams6nxboot14ShowFatalErrorEPKcz)) + KEEP(*(.text._ZN3ams6nxboot10UncompressEPvjPKvj)) + KEEP(*(.text._ZN3ams6nxboot12RebootToSelfEv)) + _*.o(SORT(.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(16); + . = . + 15; + BYTE(0x00); + } >main AT>glob + + .init : + { + KEEP( *(.init) ) + } >main AT>glob + + .plt : + { + *(.plt) + *(.iplt) + } >main AT>glob + + .fini : + { + KEEP( *(.fini) ) + } >main AT>glob + + + /* =========== RODATA section =========== */ + __rodata_start = . ; + + .rodata : + { + FILL(0x00000000) + *(.rodata .rodata.* .gnu.linkonce.r.*) + . = ALIGN(16); + . = . + 15; + BYTE(0x00); + } >main AT>glob + + .eh_frame_hdr : { __eh_frame_hdr_start = .; *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) __eh_frame_hdr_end = .; } >main AT>glob + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } >main AT>glob + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } >main AT>glob + .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } >main AT>glob + + .hash : { *(.hash) } >main AT>glob + + /* =========== DATA section =========== */ + __data_start = . ; + + .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } >main AT>glob + .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } >main AT>glob + .gnu_extab : ONLY_IF_RW { *(.gnu_extab*) } >main AT>glob + .exception_ranges : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) } >main AT>glob + + .preinit_array : + { + PROVIDE (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE (__preinit_array_end = .); + } >main AT>glob + + .init_array : + { + PROVIDE (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + PROVIDE (__init_array_end = .); + } >main AT>glob + + .fini_array : + { + PROVIDE (__fini_array_start = .); + KEEP (*(.fini_array)) + KEEP (*(SORT(.fini_array.*))) + PROVIDE (__fini_array_end = .); + } >main AT>glob + + .ctors : + { + KEEP (*crtbegin.o(.ctors)) /* MUST be first -- GCC requires it */ + KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + } >main AT>glob + + .dtors : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + } >main AT>glob + + __got_start__ = .; + + .got : { *(.got) *(.igot) } >main + .got.plt : { *(.got.plt) *(.igot.plt) } >main + + __got_end__ = .; + + .data : + { + FILL(0x00000000) + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + . = ALIGN(16); + . = . + 15; + BYTE(0x00); + } >main AT>glob + + __bss_start__ = .; + .main.fill : + { + FILL(0x00000000) + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + . = ORIGIN(main) + LENGTH(main) - 1; + BYTE(0x00); + } >main AT>glob + + __bss_end__ = .; + __main_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/fusee/program/program.mk b/Source/Atmosphere-MTC-Unlock/fusee/program/program.mk new file mode 100644 index 00000000..9c9f85ff --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/program.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 + +#--------------------------------------------------------------------------------- +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).lz4 : $(OUTPUT).bin + $(SILENTCMD)$(PYTHON) $(CURRENT_DIRECTORY)/lz4_compress.py $(OUTPUT).bin $(OUTPUT).lz4 + @echo built ... $(notdir $@) + +$(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) + +#--------------------------------------------------------------------------------- +# 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/fusee/program/program.specs b/Source/Atmosphere-MTC-Unlock/fusee/program/program.specs new file mode 100644 index 00000000..1a9458f9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/program.specs @@ -0,0 +1,4 @@ +%rename link old_link + +*link: +%(old_link) -T %:getenv(ATMOSPHERE_TOPDIR /program_ovl.ld) -T %:getenv(ATMOSPHERE_TOPDIR /program.ld) --gc-sections --nmagic \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/program_ovl.ld b/Source/Atmosphere-MTC-Unlock/fusee/program/program_ovl.ld new file mode 100644 index 00000000..db7fcb3f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/program_ovl.ld @@ -0,0 +1,47 @@ +MEMORY +{ + NULL : ORIGIN = 0, LENGTH = 4K + main : ORIGIN = 0x40001000, LENGTH = 0x2B000 + ovl : ORIGIN = 0x4002C000, LENGTH = 0x14000 + + glob : ORIGIN = ORIGIN(main), LENGTH = LENGTH(main) + LENGTH(ovl) * 3 +} + +SECTIONS { + OVERLAY : NOCROSSREFS { + .ovl_sein { + fusee_secure_initialize.o(.text*); + fusee_sdram.o(.text*); + fusee_secure_initialize.o(.rodata*); + fusee_sdram.o(.rodata*); + fusee_secure_initialize.o(.data*); + fusee_sdram.o(.data*); + fusee_secure_initialize.o(.bss*); + fusee_sdram.o(.bss*); + FILL(0x00000000) + . = ORIGIN(ovl) + LENGTH(ovl) - 1; + BYTE(0x00); + } + .ovl_mtc_erista { + KEEP(*(.text._ZN3ams6nxboot22DoMemoryTrainingEristaEiPv)) + fusee_mtc_erista.o(.text*); + fusee_mtc_erista.o(SORT(.rodata*)); + fusee_mtc_erista.o(SORT(.data*)); + fusee_mtc_erista.o(SORT(.bss*)); + FILL(0x00000000) + . = ORIGIN(ovl) + LENGTH(ovl) - 1; + BYTE(0x00); + } + .ovl_mtc_mariko { + KEEP(*(.text._ZN3ams6nxboot22DoMemoryTrainingMarikoEPbiPv)) + fusee_mtc_mariko.o(.text*); + fusee_mtc_mariko.o(SORT(.rodata*)); + fusee_mtc_mariko.o(SORT(.data*)); + fusee_mtc_mariko.o(SORT(.bss*)); + FILL(0x00000000) + . = ORIGIN(ovl) + LENGTH(ovl) - 1; + BYTE(0x00); + } + } >ovl AT>glob +} +INSERT AFTER .main.fill \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/diskio.c b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/diskio.c new file mode 100644 index 00000000..a91dd722 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/diskio.c @@ -0,0 +1,106 @@ +/*-----------------------------------------------------------------------*/ +/* 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 <stdbool.h> +#include <string.h> +#include "ff.h" /* Obtains integer types */ +#include "diskio.h" /* Declarations of disk functions */ +#include "ffconf.h" + +#include "diskio_cpp.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 DRIVE_SD: + return diskio_read_sd_card(buff, count * 512, sector, count) ? RES_OK : RES_ERROR; + case DRIVE_SYS: + return diskio_read_system(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 DRIVE_SD: + return diskio_write_sd_card(sector, count, buff, count * 512) ? RES_OK : RES_ERROR; + case DRIVE_SYS: + return diskio_write_system(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/fusee/program/source/fatfs/diskio.h b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/diskio.h new file mode 100644 index 00000000..9b2bbb30 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/diskio.h @@ -0,0 +1,83 @@ +/*-----------------------------------------------------------------------/ +/ 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; + +/* Disk drives. */ +typedef enum { + DRIVE_SD, + DRIVE_SYS, +} DDRIVE; + + +/*---------------------------------------*/ +/* 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/fusee/program/source/fatfs/diskio_cpp.h b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/diskio_cpp.h new file mode 100644 index 00000000..6c48d7d1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/diskio_cpp.h @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +bool diskio_read_sd_card(void *dst, size_t size, size_t sector_index, size_t sector_count); +bool diskio_write_sd_card(size_t sector_index, size_t sector_count, const void *src, size_t size); + +bool diskio_read_system(void *dst, size_t size, size_t sector_index, size_t sector_count); +bool diskio_write_system(size_t sector_index, size_t sector_count, const void *src, size_t size); + +#ifdef __cplusplus +} +#endif diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/ff.c b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/ff.c new file mode 100644 index 00000000..3e8d435a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/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 <stdarg.h> +/*-----------------------------------------------------------------------*/ +/* 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/fusee/program/source/fatfs/ff.h b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/ff.h new file mode 100644 index 00000000..edf3f80d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/ff.h @@ -0,0 +1,427 @@ +/*----------------------------------------------------------------------------/ +/ FatFs - Generic FAT Filesystem module R0.14 / +/-----------------------------------------------------------------------------/ +/ +/ Copyright (C) 2019, ChaN, all right reserved. +/ +/ FatFs module is an open source software. Redistribution and use of FatFs in +/ source and binary forms, with or without modification, are permitted provided +/ that the following condition is met: + +/ 1. Redistributions of source code must retain the above copyright notice, +/ this condition and the following disclaimer. +/ +/ This software is provided by the copyright holder and contributors "AS IS" +/ and any warranties related to this software are DISCLAIMED. +/ The copyright owner or contributors be NOT LIABLE for any damages caused +/ by use of this software. +/ +/----------------------------------------------------------------------------*/ + + +#ifndef FF_DEFINED +#define FF_DEFINED 86606 /* Revision ID */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ffconf.h" /* FatFs configuration options */ + +#if FF_DEFINED != FFCONF_DEF +#error Wrong configuration file (ffconf.h). +#endif + + +/* Integer types used for FatFs API */ + +#if defined(_WIN32) /* Main development platform */ +#define FF_INTDEF 2 +#include <windows.h> +typedef unsigned __int64 QWORD; +#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus) /* C99 or later */ +#define FF_INTDEF 2 +#include <stdint.h> +typedef unsigned int UINT; /* int must be 16-bit or 32-bit */ +typedef unsigned char BYTE; /* char must be 8-bit */ +typedef uint16_t WORD; /* 16-bit unsigned integer */ +typedef uint32_t DWORD; /* 32-bit unsigned integer */ +typedef uint64_t QWORD; /* 64-bit unsigned integer */ +typedef WORD WCHAR; /* UTF-16 character type */ +#else /* Earlier than C99 */ +#define FF_INTDEF 1 +typedef unsigned int UINT; /* int must be 16-bit or 32-bit */ +typedef unsigned char BYTE; /* char must be 8-bit */ +typedef unsigned short WORD; /* 16-bit unsigned integer */ +typedef unsigned long DWORD; /* 32-bit unsigned integer */ +typedef WORD WCHAR; /* UTF-16 character type */ +#endif + + +/* Definitions of volume management */ + +#if FF_MULTI_PARTITION /* Multiple partition configuration */ +typedef struct { + BYTE pd; /* Physical drive number */ + BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */ +} PARTITION; +extern PARTITION VolToPart[]; /* Volume - Partition mapping table */ +#endif + +#if FF_STR_VOLUME_ID +#ifndef FF_VOLUME_STRS +extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */ +#endif +#endif + + + +/* Type of path name strings on FatFs API */ + +#ifndef _INC_TCHAR +#define _INC_TCHAR + +#if FF_USE_LFN && FF_LFN_UNICODE == 1 /* Unicode in UTF-16 encoding */ +typedef WCHAR TCHAR; +#define _T(x) L ## x +#define _TEXT(x) L ## x +#elif FF_USE_LFN && FF_LFN_UNICODE == 2 /* Unicode in UTF-8 encoding */ +typedef char TCHAR; +#define _T(x) u8 ## x +#define _TEXT(x) u8 ## x +#elif FF_USE_LFN && FF_LFN_UNICODE == 3 /* Unicode in UTF-32 encoding */ +typedef DWORD TCHAR; +#define _T(x) U ## x +#define _TEXT(x) U ## x +#elif FF_USE_LFN && (FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3) +#error Wrong FF_LFN_UNICODE setting +#else /* ANSI/OEM code in SBCS/DBCS */ +typedef char TCHAR; +#define _T(x) x +#define _TEXT(x) x +#endif + +#endif + + + +/* Type of file size and LBA variables */ + +#if FF_FS_EXFAT +#if FF_INTDEF != 2 +#error exFAT feature wants C99 or later +#endif +typedef QWORD FSIZE_t; +#if FF_LBA64 +typedef QWORD LBA_t; +#else +typedef DWORD LBA_t; +#endif +#else +#if FF_LBA64 +#error exFAT needs to be enabled when enable 64-bit LBA +#endif +typedef DWORD FSIZE_t; +typedef DWORD LBA_t; +#endif + + + +/* Filesystem object structure (FATFS) */ + +typedef struct { + BYTE fs_type; /* Filesystem type (0:not mounted) */ + BYTE pdrv; /* Associated physical drive */ + BYTE n_fats; /* Number of FATs (1 or 2) */ + BYTE wflag; /* win[] flag (b0:dirty) */ + BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */ + WORD id; /* Volume mount ID */ + WORD n_rootdir; /* Number of root directory entries (FAT12/16) */ + WORD csize; /* Cluster size [sectors] */ +#if FF_MAX_SS != FF_MIN_SS + WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */ +#endif +#if FF_USE_LFN + WCHAR* lfnbuf; /* LFN working buffer */ +#endif +#if FF_FS_EXFAT + BYTE* dirbuf; /* Directory entry block scratchpad buffer for exFAT */ +#endif +#if FF_FS_REENTRANT + FF_SYNC_t sobj; /* Identifier of sync object */ +#endif +#if !FF_FS_READONLY + DWORD last_clst; /* Last allocated cluster */ + DWORD free_clst; /* Number of free clusters */ +#endif +#if FF_FS_RPATH + DWORD cdir; /* Current directory start cluster (0:root) */ +#if FF_FS_EXFAT + DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */ + DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */ + DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */ +#endif +#endif + DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */ + DWORD fsize; /* Size of an FAT [sectors] */ + LBA_t volbase; /* Volume base sector */ + LBA_t fatbase; /* FAT base sector */ + LBA_t dirbase; /* Root directory base sector/cluster */ + LBA_t database; /* Data base sector */ +#if FF_FS_EXFAT + LBA_t bitbase; /* Allocation bitmap base sector */ +#endif + LBA_t winsect; /* Current sector appearing in the win[] */ + BYTE pad[4]; + BYTE win[FF_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */ +} FATFS; + + + +/* Object ID and allocation information (FFOBJID) */ + +typedef struct { + FATFS* fs; /* Pointer to the hosting volume of this object */ + WORD id; /* Hosting volume mount ID */ + BYTE attr; /* Object attribute */ + BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */ + DWORD sclust; /* Object data start cluster (0:no cluster or root directory) */ + FSIZE_t objsize; /* Object size (valid when sclust != 0) */ +#if FF_FS_EXFAT + DWORD n_cont; /* Size of first fragment - 1 (valid when stat == 3) */ + DWORD n_frag; /* Size of last fragment needs to be written to FAT (valid when not zero) */ + DWORD c_scl; /* Containing directory start cluster (valid when sclust != 0) */ + DWORD c_size; /* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */ + DWORD c_ofs; /* Offset in the containing directory (valid when file object and sclust != 0) */ +#endif +#if FF_FS_LOCK + UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */ +#endif +} FFOBJID; + + + +/* File object structure (FIL) */ + +typedef struct { + FFOBJID obj; /* Object identifier (must be the 1st member to detect invalid object pointer) */ + BYTE flag; /* File status flags */ + BYTE err; /* Abort flag (error code) */ + FSIZE_t fptr; /* File read/write pointer (Zeroed on file open) */ + DWORD clust; /* Current cluster of fpter (invalid when fptr is 0) */ + LBA_t sect; /* Sector number appearing in buf[] (0:invalid) */ +#if !FF_FS_READONLY + LBA_t dir_sect; /* Sector number containing the directory entry (not used at exFAT) */ + BYTE* dir_ptr; /* Pointer to the directory entry in the win[] (not used at exFAT) */ +#endif +#if FF_USE_FASTSEEK + DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */ +#endif +#if !FF_FS_TINY + BYTE buf[FF_MAX_SS]; /* File private data read/write window */ +#endif +} FIL; + + + +/* Directory object structure (DIR) */ + +typedef struct { + FFOBJID obj; /* Object identifier */ + DWORD dptr; /* Current read/write offset */ + DWORD clust; /* Current cluster */ + LBA_t sect; /* Current sector (0:Read operation has terminated) */ + BYTE* dir; /* Pointer to the directory item in the win[] */ + BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */ +#if FF_USE_LFN + DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */ +#endif +#if FF_USE_FIND + const TCHAR* pat; /* Pointer to the name matching pattern */ +#endif +} DIR; + + + +/* File information structure (FILINFO) */ + +typedef struct { + FSIZE_t fsize; /* File size */ + WORD fdate; /* Modified date */ + WORD ftime; /* Modified time */ + BYTE fattrib; /* File attribute */ +#if FF_USE_LFN + TCHAR altname[FF_SFN_BUF + 1];/* Altenative file name */ + TCHAR fname[FF_LFN_BUF + 1]; /* Primary file name */ +#else + TCHAR fname[12 + 1]; /* File name */ +#endif +} FILINFO; + + + +/* Format parameter structure (MKFS_PARM) */ + +typedef struct { + BYTE fmt; /* Format option (FM_FAT, FM_FAT32, FM_EXFAT and FM_SFD) */ + BYTE n_fat; /* Number of FATs */ + UINT align; /* Data area alignment (sector) */ + UINT n_root; /* Number of root directory entries */ + DWORD au_size; /* Cluster size (byte) */ +} MKFS_PARM; + + + +/* File function return code (FRESULT) */ + +typedef enum { + FR_OK = 0, /* (0) Succeeded */ + FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */ + FR_INT_ERR, /* (2) Assertion failed */ + FR_NOT_READY, /* (3) The physical drive cannot work */ + FR_NO_FILE, /* (4) Could not find the file */ + FR_NO_PATH, /* (5) Could not find the path */ + FR_INVALID_NAME, /* (6) The path name format is invalid */ + FR_DENIED, /* (7) Access denied due to prohibited access or directory full */ + FR_EXIST, /* (8) Access denied due to prohibited access */ + FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */ + FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */ + FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */ + FR_NOT_ENABLED, /* (12) The volume has no work area */ + FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */ + FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */ + FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */ + FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */ + FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */ + FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */ + FR_INVALID_PARAMETER /* (19) Given parameter is invalid */ +} FRESULT; + + + +/*--------------------------------------------------------------*/ +/* FatFs module application interface */ + +FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */ +FRESULT f_close (FIL* fp); /* Close an open file object */ +FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */ +FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */ +FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */ +FRESULT f_truncate (FIL* fp); /* Truncate the file */ +FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */ +FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */ +FRESULT f_closedir (DIR* dp); /* Close an open directory */ +FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */ +FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */ +FRESULT f_findnext (DIR* dp, FILINFO* fno); /* Find next file */ +FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */ +FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */ +FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */ +FRESULT f_stat (const TCHAR* path, FILINFO* fno); /* Get file status */ +FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */ +FRESULT f_utime (const TCHAR* path, const FILINFO* fno); /* Change timestamp of a file/dir */ +FRESULT f_chdir (const TCHAR* path); /* Change current directory */ +FRESULT f_chdrive (const TCHAR* path); /* Change current drive */ +FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */ +FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */ +FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */ +FRESULT f_setlabel (const TCHAR* label); /* Set volume label */ +FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */ +FRESULT f_expand (FIL* fp, FSIZE_t fsz, BYTE opt); /* Allocate a contiguous block to the file */ +FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */ +FRESULT f_mkfs (const TCHAR* path, const MKFS_PARM* opt, void* work, UINT len); /* Create a FAT volume */ +FRESULT f_fdisk (BYTE pdrv, const LBA_t ptbl[], void* work); /* Divide a physical drive into some partitions */ +FRESULT f_setcp (WORD cp); /* Set current code page */ +int f_putc (TCHAR c, FIL* fp); /* Put a character to the file */ +int f_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */ +int f_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */ +TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */ + +#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize)) +#define f_error(fp) ((fp)->err) +#define f_tell(fp) ((fp)->fptr) +#define f_size(fp) ((fp)->obj.objsize) +#define f_rewind(fp) f_lseek((fp), 0) +#define f_rewinddir(dp) f_readdir((dp), 0) +#define f_rmdir(path) f_unlink(path) +#define f_unmount(path) f_mount(0, path, 0) + +#ifndef EOF +#define EOF (-1) +#endif + + + + +/*--------------------------------------------------------------*/ +/* Additional user defined functions */ + +/* RTC function */ +#if !FF_FS_READONLY && !FF_FS_NORTC +DWORD get_fattime (void); +#endif + +/* LFN support functions */ +#if FF_USE_LFN >= 1 /* Code conversion (defined in unicode.c) */ +WCHAR ff_oem2uni (WCHAR oem, WORD cp); /* OEM code to Unicode conversion */ +WCHAR ff_uni2oem (DWORD uni, WORD cp); /* Unicode to OEM code conversion */ +DWORD ff_wtoupper (DWORD uni); /* Unicode upper-case conversion */ +#endif +#if FF_USE_LFN == 3 /* Dynamic memory allocation */ +void* ff_memalloc (UINT msize); /* Allocate memory block */ +void ff_memfree (void* mblock); /* Free memory block */ +#endif + +/* Sync functions */ +#if FF_FS_REENTRANT +int ff_cre_syncobj (BYTE vol, FF_SYNC_t* sobj); /* Create a sync object */ +int ff_req_grant (FF_SYNC_t sobj); /* Lock sync object */ +void ff_rel_grant (FF_SYNC_t sobj); /* Unlock sync object */ +int ff_del_syncobj (FF_SYNC_t sobj); /* Delete a sync object */ +#endif + + + + +/*--------------------------------------------------------------*/ +/* Flags and offset address */ + + +/* File access mode and open method flags (3rd argument of f_open) */ +#define FA_READ 0x01 +#define FA_WRITE 0x02 +#define FA_OPEN_EXISTING 0x00 +#define FA_CREATE_NEW 0x04 +#define FA_CREATE_ALWAYS 0x08 +#define FA_OPEN_ALWAYS 0x10 +#define FA_OPEN_APPEND 0x30 + +/* Fast seek controls (2nd argument of f_lseek) */ +#define CREATE_LINKMAP ((FSIZE_t)0 - 1) + +/* Format options (2nd argument of f_mkfs) */ +#define FM_FAT 0x01 +#define FM_FAT32 0x02 +#define FM_EXFAT 0x04 +#define FM_ANY 0x07 +#define FM_SFD 0x08 + +/* Filesystem type (FATFS.fs_type) */ +#define FS_FAT12 1 +#define FS_FAT16 2 +#define FS_FAT32 3 +#define FS_EXFAT 4 + +/* File attribute bits for directory entry (FILINFO.fattrib) */ +#define AM_RDO 0x01 /* Read only */ +#define AM_HID 0x02 /* Hidden */ +#define AM_SYS 0x04 /* System */ +#define AM_DIR 0x10 /* Directory */ +#define AM_ARC 0x20 /* Archive */ + + +#ifdef __cplusplus +} +#endif + +#endif /* FF_DEFINED */ diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/ffconf.h b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/ffconf.h new file mode 100644 index 00000000..5be0a26c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/ffconf.h @@ -0,0 +1,298 @@ +/*---------------------------------------------------------------------------/ +/ FatFs Functional Configurations +/---------------------------------------------------------------------------*/ + +#define FFCONF_DEF 86606 /* 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 0 +/* 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_USE_FASTSEEK 0 +/* This option switches fast seek function. (0:Disable or 1:Enable) */ + + +#define FF_USE_EXPAND 1 +/* 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 1 +#define FF_MAX_LFN 255 +/* The FF_USE_LFN switches the support for LFN (long file name). +/ +/ 0: Disable LFN. FF_MAX_LFN has no effect. +/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe. +/ 2: Enable LFN with dynamic working buffer on the STACK. +/ 3: Enable LFN with dynamic working buffer on the HEAP. +/ +/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function +/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and +/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled. +/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can +/ be in range of 12 to 255. It is recommended to be set it 255 to fully support LFN +/ specification. +/ When use stack for the working buffer, take care on stack overflow. When use heap +/ memory for the working buffer, memory management functions, ff_memalloc() and +/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */ + + +#define FF_LFN_UNICODE 2 +/* 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 3 +/* 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 2 +/* Number of volumes (logical drives) to be used. (1-10) */ + + +#define FF_STR_VOLUME_ID 1 +#define FF_VOLUME_STRS "sdmc","sys" +/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings. +/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive +/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each +/ logical drives. Number of items must not be less than FF_VOLUMES. Valid +/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are +/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is +/ not defined, a user defined volume string table needs to be defined as: +/ +/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",... +*/ + + +#define FF_MULTI_PARTITION 0 +/* This option switches support for multiple volumes on the physical drive. +/ By default (0), each logical drive number is bound to the same physical drive +/ number and only an FAT volume found on the physical drive will be mounted. +/ When this function is enabled (1), each logical drive number can be bound to +/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk() +/ funciton will be available. */ + + +#define FF_MIN_SS 512 +#define FF_MAX_SS 512 +/* This set of options configures the range of sector size to be supported. (512, +/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and +/ harddisk. But a larger value may be required for on-board flash memory and some +/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured +/ for variable sector size mode and disk_ioctl() function needs to implement +/ GET_SECTOR_SIZE command. */ + + +#define FF_LBA64 0 +/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable) +/ To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */ + + +#define FF_MIN_GPT 0x100000000 +/* Minimum number of sectors to switch GPT format to create partition in f_mkfs and +/ f_fdisk function. 0x100000000 max. This option has no effect when FF_LBA64 == 0. */ + + +#define FF_USE_TRIM 0 +/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable) +/ To enable Trim function, also CTRL_TRIM command should be implemented to the +/ disk_ioctl() function. */ + + + +/*---------------------------------------------------------------------------/ +/ System Configurations +/---------------------------------------------------------------------------*/ + +#define FF_FS_TINY 0 +/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny) +/ At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes. +/ Instead of private sector buffer eliminated from the file object, common sector +/ buffer in the filesystem object (FATFS) is used for the file data transfer. */ + + +#define FF_FS_EXFAT 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 2019 +/* The option FF_FS_NORTC switches timestamp functiton. If the system does not have +/ any RTC function or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable +/ the timestamp function. Every object modified by FatFs will have a fixed timestamp +/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time. +/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be +/ added to the project to read current time form real-time clock. FF_NORTC_MON, +/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect. +/ These options have no effect in read-only configuration (FF_FS_READONLY = 1). */ + + +#define FF_FS_NOFSINFO 0 +/* If you need to know correct free space on the FAT32 volume, set bit 0 of this +/ option, and f_getfree() function at first time after volume mount will force +/ a full FAT scan. Bit 1 controls the use of last allocated cluster number. +/ +/ bit0=0: Use free cluster count in the FSINFO if available. +/ bit0=1: Do not trust free cluster count in the FSINFO. +/ bit1=0: Use last allocated cluster number in the FSINFO if available. +/ bit1=1: Do not trust last allocated cluster number in the FSINFO. +*/ + + +#define FF_FS_LOCK 0 +/* The option FF_FS_LOCK switches file lock function to control duplicated file open +/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY +/ is 1. +/ +/ 0: Disable file lock function. To avoid volume corruption, application program +/ should avoid illegal open, remove and rename to the open objects. +/ >0: Enable file lock function. The value defines how many files/sub-directories +/ can be opened simultaneously under file lock control. Note that the file +/ lock control is independent of re-entrancy. */ + + +/* #include <somertos.h> // O/S definitions */ +#define FF_FS_REENTRANT 0 +#define FF_FS_TIMEOUT 1000 +#define FF_SYNC_t HANDLE +/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs +/ module itself. Note that regardless of this option, file access to different +/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs() +/ and f_fdisk() function, are always not re-entrant. Only file/directory access +/ to the same volume is under control of this function. +/ +/ 0: Disable re-entrancy. FF_FS_TIMEOUT and FF_SYNC_t have no effect. +/ 1: Enable re-entrancy. Also user provided synchronization handlers, +/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj() +/ function, must be added to the project. Samples are available in +/ option/syscall.c. +/ +/ The FF_FS_TIMEOUT defines timeout period in unit of time tick. +/ The FF_SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*, +/ SemaphoreHandle_t and etc. A header file for O/S definitions needs to be +/ included somewhere in the scope of ff.h. */ + + + +/*--- End of configuration options ---*/ diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/ffsystem.c b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/ffsystem.c new file mode 100644 index 00000000..8d38eddf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/ffsystem.c @@ -0,0 +1,170 @@ +/*------------------------------------------------------------------------*/ +/* Sample Code of OS Dependent Functions for FatFs */ +/* (C)ChaN, 2018 */ +/*------------------------------------------------------------------------*/ + + +#include "ff.h" + + +#if FF_USE_LFN == 3 /* Dynamic memory allocation */ + +/*------------------------------------------------------------------------*/ +/* Allocate a memory block */ +/*------------------------------------------------------------------------*/ + +void* ff_memalloc ( /* Returns pointer to the allocated memory block (null if not enough core) */ + UINT msize /* Number of bytes to allocate */ +) +{ + return malloc(msize); /* Allocate a new memory block with POSIX API */ +} + + +/*------------------------------------------------------------------------*/ +/* Free a memory block */ +/*------------------------------------------------------------------------*/ + +void ff_memfree ( + void* mblock /* Pointer to the memory block to free (nothing to do if null) */ +) +{ + free(mblock); /* Free the memory block with POSIX API */ +} + +#endif + + + +#if FF_FS_REENTRANT /* Mutal exclusion */ + +/*------------------------------------------------------------------------*/ +/* Create a Synchronization Object */ +/*------------------------------------------------------------------------*/ +/* This function is called in f_mount() function to create a new +/ synchronization object for the volume, such as semaphore and mutex. +/ When a 0 is returned, the f_mount() function fails with FR_INT_ERR. +*/ + +//const osMutexDef_t Mutex[FF_VOLUMES]; /* Table of CMSIS-RTOS mutex */ + + +int ff_cre_syncobj ( /* 1:Function succeeded, 0:Could not create the sync object */ + BYTE vol, /* Corresponding volume (logical drive number) */ + FF_SYNC_t* sobj /* Pointer to return the created sync object */ +) +{ + /* Win32 */ + *sobj = CreateMutex(NULL, FALSE, NULL); + return (int)(*sobj != INVALID_HANDLE_VALUE); + + /* uITRON */ +// T_CSEM csem = {TA_TPRI,1,1}; +// *sobj = acre_sem(&csem); +// return (int)(*sobj > 0); + + /* uC/OS-II */ +// OS_ERR err; +// *sobj = OSMutexCreate(0, &err); +// return (int)(err == OS_NO_ERR); + + /* FreeRTOS */ +// *sobj = xSemaphoreCreateMutex(); +// return (int)(*sobj != NULL); + + /* CMSIS-RTOS */ +// *sobj = osMutexCreate(&Mutex[vol]); +// return (int)(*sobj != NULL); +} + + +/*------------------------------------------------------------------------*/ +/* Delete a Synchronization Object */ +/*------------------------------------------------------------------------*/ +/* This function is called in f_mount() function to delete a synchronization +/ object that created with ff_cre_syncobj() function. When a 0 is returned, +/ the f_mount() function fails with FR_INT_ERR. +*/ + +int ff_del_syncobj ( /* 1:Function succeeded, 0:Could not delete due to an error */ + FF_SYNC_t sobj /* Sync object tied to the logical drive to be deleted */ +) +{ + /* Win32 */ + return (int)CloseHandle(sobj); + + /* uITRON */ +// return (int)(del_sem(sobj) == E_OK); + + /* uC/OS-II */ +// OS_ERR err; +// OSMutexDel(sobj, OS_DEL_ALWAYS, &err); +// return (int)(err == OS_NO_ERR); + + /* FreeRTOS */ +// vSemaphoreDelete(sobj); +// return 1; + + /* CMSIS-RTOS */ +// return (int)(osMutexDelete(sobj) == osOK); +} + + +/*------------------------------------------------------------------------*/ +/* Request Grant to Access the Volume */ +/*------------------------------------------------------------------------*/ +/* This function is called on entering file functions to lock the volume. +/ When a 0 is returned, the file function fails with FR_TIMEOUT. +*/ + +int ff_req_grant ( /* 1:Got a grant to access the volume, 0:Could not get a grant */ + FF_SYNC_t sobj /* Sync object to wait */ +) +{ + /* Win32 */ + return (int)(WaitForSingleObject(sobj, FF_FS_TIMEOUT) == WAIT_OBJECT_0); + + /* uITRON */ +// return (int)(wai_sem(sobj) == E_OK); + + /* uC/OS-II */ +// OS_ERR err; +// OSMutexPend(sobj, FF_FS_TIMEOUT, &err)); +// return (int)(err == OS_NO_ERR); + + /* FreeRTOS */ +// return (int)(xSemaphoreTake(sobj, FF_FS_TIMEOUT) == pdTRUE); + + /* CMSIS-RTOS */ +// return (int)(osMutexWait(sobj, FF_FS_TIMEOUT) == osOK); +} + + +/*------------------------------------------------------------------------*/ +/* Release Grant to Access the Volume */ +/*------------------------------------------------------------------------*/ +/* This function is called on leaving file functions to unlock the volume. +*/ + +void ff_rel_grant ( + FF_SYNC_t sobj /* Sync object to be signaled */ +) +{ + /* Win32 */ + ReleaseMutex(sobj); + + /* uITRON */ +// sig_sem(sobj); + + /* uC/OS-II */ +// OSMutexPost(sobj); + + /* FreeRTOS */ +// xSemaphoreGive(sobj); + + /* CMSIS-RTOS */ +// osMutexRelease(sobj); +} + +#endif + diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/ffunicode.c b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/ffunicode.c new file mode 100644 index 00000000..e8b36dbe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/ffunicode.c @@ -0,0 +1,15593 @@ +/*------------------------------------------------------------------------*/ +/* Unicode handling functions for FatFs R0.13+ */ +/*------------------------------------------------------------------------*/ +/* 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) 2014, 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 if non-LFN configuration */ + +#define MERGE2(a, b) a ## b +#define CVTBL(tbl, cp) MERGE2(tbl, cp) + + +/*------------------------------------------------------------------------*/ +/* Code Conversion Tables */ +/*------------------------------------------------------------------------*/ + +#if FF_CODE_PAGE == 932 || FF_CODE_PAGE == 0 /* Japanese */ +static const WCHAR uni2oem932[] = { /* Unicode --> Shift_JIS pairs */ + 0x00A7, 0x8198, 0x00A8, 0x814E, 0x00B0, 0x818B, 0x00B1, 0x817D, 0x00B4, 0x814C, 0x00B6, 0x81F7, 0x00D7, 0x817E, 0x00F7, 0x8180, + 0x0391, 0x839F, 0x0392, 0x83A0, 0x0393, 0x83A1, 0x0394, 0x83A2, 0x0395, 0x83A3, 0x0396, 0x83A4, 0x0397, 0x83A5, 0x0398, 0x83A6, + 0x0399, 0x83A7, 0x039A, 0x83A8, 0x039B, 0x83A9, 0x039C, 0x83AA, 0x039D, 0x83AB, 0x039E, 0x83AC, 0x039F, 0x83AD, 0x03A0, 0x83AE, + 0x03A1, 0x83AF, 0x03A3, 0x83B0, 0x03A4, 0x83B1, 0x03A5, 0x83B2, 0x03A6, 0x83B3, 0x03A7, 0x83B4, 0x03A8, 0x83B5, 0x03A9, 0x83B6, + 0x03B1, 0x83BF, 0x03B2, 0x83C0, 0x03B3, 0x83C1, 0x03B4, 0x83C2, 0x03B5, 0x83C3, 0x03B6, 0x83C4, 0x03B7, 0x83C5, 0x03B8, 0x83C6, + 0x03B9, 0x83C7, 0x03BA, 0x83C8, 0x03BB, 0x83C9, 0x03BC, 0x83CA, 0x03BD, 0x83CB, 0x03BE, 0x83CC, 0x03BF, 0x83CD, 0x03C0, 0x83CE, + 0x03C1, 0x83CF, 0x03C3, 0x83D0, 0x03C4, 0x83D1, 0x03C5, 0x83D2, 0x03C6, 0x83D3, 0x03C7, 0x83D4, 0x03C8, 0x83D5, 0x03C9, 0x83D6, + 0x0401, 0x8446, 0x0410, 0x8440, 0x0411, 0x8441, 0x0412, 0x8442, 0x0413, 0x8443, 0x0414, 0x8444, 0x0415, 0x8445, 0x0416, 0x8447, + 0x0417, 0x8448, 0x0418, 0x8449, 0x0419, 0x844A, 0x041A, 0x844B, 0x041B, 0x844C, 0x041C, 0x844D, 0x041D, 0x844E, 0x041E, 0x844F, + 0x041F, 0x8450, 0x0420, 0x8451, 0x0421, 0x8452, 0x0422, 0x8453, 0x0423, 0x8454, 0x0424, 0x8455, 0x0425, 0x8456, 0x0426, 0x8457, + 0x0427, 0x8458, 0x0428, 0x8459, 0x0429, 0x845A, 0x042A, 0x845B, 0x042B, 0x845C, 0x042C, 0x845D, 0x042D, 0x845E, 0x042E, 0x845F, + 0x042F, 0x8460, 0x0430, 0x8470, 0x0431, 0x8471, 0x0432, 0x8472, 0x0433, 0x8473, 0x0434, 0x8474, 0x0435, 0x8475, 0x0436, 0x8477, + 0x0437, 0x8478, 0x0438, 0x8479, 0x0439, 0x847A, 0x043A, 0x847B, 0x043B, 0x847C, 0x043C, 0x847D, 0x043D, 0x847E, 0x043E, 0x8480, + 0x043F, 0x8481, 0x0440, 0x8482, 0x0441, 0x8483, 0x0442, 0x8484, 0x0443, 0x8485, 0x0444, 0x8486, 0x0445, 0x8487, 0x0446, 0x8488, + 0x0447, 0x8489, 0x0448, 0x848A, 0x0449, 0x848B, 0x044A, 0x848C, 0x044B, 0x848D, 0x044C, 0x848E, 0x044D, 0x848F, 0x044E, 0x8490, + 0x044F, 0x8491, 0x0451, 0x8476, 0x2010, 0x815D, 0x2015, 0x815C, 0x2018, 0x8165, 0x2019, 0x8166, 0x201C, 0x8167, 0x201D, 0x8168, + 0x2020, 0x81F5, 0x2021, 0x81F6, 0x2025, 0x8164, 0x2026, 0x8163, 0x2030, 0x81F1, 0x2032, 0x818C, 0x2033, 0x818D, 0x203B, 0x81A6, + 0x2103, 0x818E, 0x2116, 0x8782, 0x2121, 0x8784, 0x212B, 0x81F0, 0x2160, 0x8754, 0x2161, 0x8755, 0x2162, 0x8756, 0x2163, 0x8757, + 0x2164, 0x8758, 0x2165, 0x8759, 0x2166, 0x875A, 0x2167, 0x875B, 0x2168, 0x875C, 0x2169, 0x875D, 0x2170, 0xFA40, 0x2171, 0xFA41, + 0x2172, 0xFA42, 0x2173, 0xFA43, 0x2174, 0xFA44, 0x2175, 0xFA45, 0x2176, 0xFA46, 0x2177, 0xFA47, 0x2178, 0xFA48, 0x2179, 0xFA49, + 0x2190, 0x81A9, 0x2191, 0x81AA, 0x2192, 0x81A8, 0x2193, 0x81AB, 0x21D2, 0x81CB, 0x21D4, 0x81CC, 0x2200, 0x81CD, 0x2202, 0x81DD, + 0x2203, 0x81CE, 0x2207, 0x81DE, 0x2208, 0x81B8, 0x220B, 0x81B9, 0x2211, 0x8794, 0x221A, 0x81E3, 0x221D, 0x81E5, 0x221E, 0x8187, + 0x221F, 0x8798, 0x2220, 0x81DA, 0x2225, 0x8161, 0x2227, 0x81C8, 0x2228, 0x81C9, 0x2229, 0x81BF, 0x222A, 0x81BE, 0x222B, 0x81E7, + 0x222C, 0x81E8, 0x222E, 0x8793, 0x2234, 0x8188, 0x2235, 0x81E6, 0x223D, 0x81E4, 0x2252, 0x81E0, 0x2260, 0x8182, 0x2261, 0x81DF, + 0x2266, 0x8185, 0x2267, 0x8186, 0x226A, 0x81E1, 0x226B, 0x81E2, 0x2282, 0x81BC, 0x2283, 0x81BD, 0x2286, 0x81BA, 0x2287, 0x81BB, + 0x22A5, 0x81DB, 0x22BF, 0x8799, 0x2312, 0x81DC, 0x2460, 0x8740, 0x2461, 0x8741, 0x2462, 0x8742, 0x2463, 0x8743, 0x2464, 0x8744, + 0x2465, 0x8745, 0x2466, 0x8746, 0x2467, 0x8747, 0x2468, 0x8748, 0x2469, 0x8749, 0x246A, 0x874A, 0x246B, 0x874B, 0x246C, 0x874C, + 0x246D, 0x874D, 0x246E, 0x874E, 0x246F, 0x874F, 0x2470, 0x8750, 0x2471, 0x8751, 0x2472, 0x8752, 0x2473, 0x8753, 0x2500, 0x849F, + 0x2501, 0x84AA, 0x2502, 0x84A0, 0x2503, 0x84AB, 0x250C, 0x84A1, 0x250F, 0x84AC, 0x2510, 0x84A2, 0x2513, 0x84AD, 0x2514, 0x84A4, + 0x2517, 0x84AF, 0x2518, 0x84A3, 0x251B, 0x84AE, 0x251C, 0x84A5, 0x251D, 0x84BA, 0x2520, 0x84B5, 0x2523, 0x84B0, 0x2524, 0x84A7, + 0x2525, 0x84BC, 0x2528, 0x84B7, 0x252B, 0x84B2, 0x252C, 0x84A6, 0x252F, 0x84B6, 0x2530, 0x84BB, 0x2533, 0x84B1, 0x2534, 0x84A8, + 0x2537, 0x84B8, 0x2538, 0x84BD, 0x253B, 0x84B3, 0x253C, 0x84A9, 0x253F, 0x84B9, 0x2542, 0x84BE, 0x254B, 0x84B4, 0x25A0, 0x81A1, + 0x25A1, 0x81A0, 0x25B2, 0x81A3, 0x25B3, 0x81A2, 0x25BC, 0x81A5, 0x25BD, 0x81A4, 0x25C6, 0x819F, 0x25C7, 0x819E, 0x25CB, 0x819B, + 0x25CE, 0x819D, 0x25CF, 0x819C, 0x25EF, 0x81FC, 0x2605, 0x819A, 0x2606, 0x8199, 0x2640, 0x818A, 0x2642, 0x8189, 0x266A, 0x81F4, + 0x266D, 0x81F3, 0x266F, 0x81F2, 0x3000, 0x8140, 0x3001, 0x8141, 0x3002, 0x8142, 0x3003, 0x8156, 0x3005, 0x8158, 0x3006, 0x8159, + 0x3007, 0x815A, 0x3008, 0x8171, 0x3009, 0x8172, 0x300A, 0x8173, 0x300B, 0x8174, 0x300C, 0x8175, 0x300D, 0x8176, 0x300E, 0x8177, + 0x300F, 0x8178, 0x3010, 0x8179, 0x3011, 0x817A, 0x3012, 0x81A7, 0x3013, 0x81AC, 0x3014, 0x816B, 0x3015, 0x816C, 0x301D, 0x8780, + 0x301F, 0x8781, 0x3041, 0x829F, 0x3042, 0x82A0, 0x3043, 0x82A1, 0x3044, 0x82A2, 0x3045, 0x82A3, 0x3046, 0x82A4, 0x3047, 0x82A5, + 0x3048, 0x82A6, 0x3049, 0x82A7, 0x304A, 0x82A8, 0x304B, 0x82A9, 0x304C, 0x82AA, 0x304D, 0x82AB, 0x304E, 0x82AC, 0x304F, 0x82AD, + 0x3050, 0x82AE, 0x3051, 0x82AF, 0x3052, 0x82B0, 0x3053, 0x82B1, 0x3054, 0x82B2, 0x3055, 0x82B3, 0x3056, 0x82B4, 0x3057, 0x82B5, + 0x3058, 0x82B6, 0x3059, 0x82B7, 0x305A, 0x82B8, 0x305B, 0x82B9, 0x305C, 0x82BA, 0x305D, 0x82BB, 0x305E, 0x82BC, 0x305F, 0x82BD, + 0x3060, 0x82BE, 0x3061, 0x82BF, 0x3062, 0x82C0, 0x3063, 0x82C1, 0x3064, 0x82C2, 0x3065, 0x82C3, 0x3066, 0x82C4, 0x3067, 0x82C5, + 0x3068, 0x82C6, 0x3069, 0x82C7, 0x306A, 0x82C8, 0x306B, 0x82C9, 0x306C, 0x82CA, 0x306D, 0x82CB, 0x306E, 0x82CC, 0x306F, 0x82CD, + 0x3070, 0x82CE, 0x3071, 0x82CF, 0x3072, 0x82D0, 0x3073, 0x82D1, 0x3074, 0x82D2, 0x3075, 0x82D3, 0x3076, 0x82D4, 0x3077, 0x82D5, + 0x3078, 0x82D6, 0x3079, 0x82D7, 0x307A, 0x82D8, 0x307B, 0x82D9, 0x307C, 0x82DA, 0x307D, 0x82DB, 0x307E, 0x82DC, 0x307F, 0x82DD, + 0x3080, 0x82DE, 0x3081, 0x82DF, 0x3082, 0x82E0, 0x3083, 0x82E1, 0x3084, 0x82E2, 0x3085, 0x82E3, 0x3086, 0x82E4, 0x3087, 0x82E5, + 0x3088, 0x82E6, 0x3089, 0x82E7, 0x308A, 0x82E8, 0x308B, 0x82E9, 0x308C, 0x82EA, 0x308D, 0x82EB, 0x308E, 0x82EC, 0x308F, 0x82ED, + 0x3090, 0x82EE, 0x3091, 0x82EF, 0x3092, 0x82F0, 0x3093, 0x82F1, 0x309B, 0x814A, 0x309C, 0x814B, 0x309D, 0x8154, 0x309E, 0x8155, + 0x30A1, 0x8340, 0x30A2, 0x8341, 0x30A3, 0x8342, 0x30A4, 0x8343, 0x30A5, 0x8344, 0x30A6, 0x8345, 0x30A7, 0x8346, 0x30A8, 0x8347, + 0x30A9, 0x8348, 0x30AA, 0x8349, 0x30AB, 0x834A, 0x30AC, 0x834B, 0x30AD, 0x834C, 0x30AE, 0x834D, 0x30AF, 0x834E, 0x30B0, 0x834F, + 0x30B1, 0x8350, 0x30B2, 0x8351, 0x30B3, 0x8352, 0x30B4, 0x8353, 0x30B5, 0x8354, 0x30B6, 0x8355, 0x30B7, 0x8356, 0x30B8, 0x8357, + 0x30B9, 0x8358, 0x30BA, 0x8359, 0x30BB, 0x835A, 0x30BC, 0x835B, 0x30BD, 0x835C, 0x30BE, 0x835D, 0x30BF, 0x835E, 0x30C0, 0x835F, + 0x30C1, 0x8360, 0x30C2, 0x8361, 0x30C3, 0x8362, 0x30C4, 0x8363, 0x30C5, 0x8364, 0x30C6, 0x8365, 0x30C7, 0x8366, 0x30C8, 0x8367, + 0x30C9, 0x8368, 0x30CA, 0x8369, 0x30CB, 0x836A, 0x30CC, 0x836B, 0x30CD, 0x836C, 0x30CE, 0x836D, 0x30CF, 0x836E, 0x30D0, 0x836F, + 0x30D1, 0x8370, 0x30D2, 0x8371, 0x30D3, 0x8372, 0x30D4, 0x8373, 0x30D5, 0x8374, 0x30D6, 0x8375, 0x30D7, 0x8376, 0x30D8, 0x8377, + 0x30D9, 0x8378, 0x30DA, 0x8379, 0x30DB, 0x837A, 0x30DC, 0x837B, 0x30DD, 0x837C, 0x30DE, 0x837D, 0x30DF, 0x837E, 0x30E0, 0x8380, + 0x30E1, 0x8381, 0x30E2, 0x8382, 0x30E3, 0x8383, 0x30E4, 0x8384, 0x30E5, 0x8385, 0x30E6, 0x8386, 0x30E7, 0x8387, 0x30E8, 0x8388, + 0x30E9, 0x8389, 0x30EA, 0x838A, 0x30EB, 0x838B, 0x30EC, 0x838C, 0x30ED, 0x838D, 0x30EE, 0x838E, 0x30EF, 0x838F, 0x30F0, 0x8390, + 0x30F1, 0x8391, 0x30F2, 0x8392, 0x30F3, 0x8393, 0x30F4, 0x8394, 0x30F5, 0x8395, 0x30F6, 0x8396, 0x30FB, 0x8145, 0x30FC, 0x815B, + 0x30FD, 0x8152, 0x30FE, 0x8153, 0x3231, 0x878A, 0x3232, 0x878B, 0x3239, 0x878C, 0x32A4, 0x8785, 0x32A5, 0x8786, 0x32A6, 0x8787, + 0x32A7, 0x8788, 0x32A8, 0x8789, 0x3303, 0x8765, 0x330D, 0x8769, 0x3314, 0x8760, 0x3318, 0x8763, 0x3322, 0x8761, 0x3323, 0x876B, + 0x3326, 0x876A, 0x3327, 0x8764, 0x332B, 0x876C, 0x3336, 0x8766, 0x333B, 0x876E, 0x3349, 0x875F, 0x334A, 0x876D, 0x334D, 0x8762, + 0x3351, 0x8767, 0x3357, 0x8768, 0x337B, 0x877E, 0x337C, 0x878F, 0x337D, 0x878E, 0x337E, 0x878D, 0x338E, 0x8772, 0x338F, 0x8773, + 0x339C, 0x876F, 0x339D, 0x8770, 0x339E, 0x8771, 0x33A1, 0x8775, 0x33C4, 0x8774, 0x33CD, 0x8783, 0x4E00, 0x88EA, 0x4E01, 0x929A, + 0x4E03, 0x8EB5, 0x4E07, 0x969C, 0x4E08, 0x8FE4, 0x4E09, 0x8E4F, 0x4E0A, 0x8FE3, 0x4E0B, 0x89BA, 0x4E0D, 0x9573, 0x4E0E, 0x975E, + 0x4E10, 0x98A0, 0x4E11, 0x894E, 0x4E14, 0x8A8E, 0x4E15, 0x98A1, 0x4E16, 0x90A2, 0x4E17, 0x99C0, 0x4E18, 0x8B75, 0x4E19, 0x95B8, + 0x4E1E, 0x8FE5, 0x4E21, 0x97BC, 0x4E26, 0x95C0, 0x4E28, 0xFA68, 0x4E2A, 0x98A2, 0x4E2D, 0x9286, 0x4E31, 0x98A3, 0x4E32, 0x8BF8, + 0x4E36, 0x98A4, 0x4E38, 0x8ADB, 0x4E39, 0x924F, 0x4E3B, 0x8EE5, 0x4E3C, 0x98A5, 0x4E3F, 0x98A6, 0x4E42, 0x98A7, 0x4E43, 0x9454, + 0x4E45, 0x8B76, 0x4E4B, 0x9456, 0x4E4D, 0x93E1, 0x4E4E, 0x8CC1, 0x4E4F, 0x9652, 0x4E55, 0xE568, 0x4E56, 0x98A8, 0x4E57, 0x8FE6, + 0x4E58, 0x98A9, 0x4E59, 0x89B3, 0x4E5D, 0x8BE3, 0x4E5E, 0x8CEE, 0x4E5F, 0x96E7, 0x4E62, 0x9BA4, 0x4E71, 0x9790, 0x4E73, 0x93FB, + 0x4E7E, 0x8AA3, 0x4E80, 0x8B54, 0x4E82, 0x98AA, 0x4E85, 0x98AB, 0x4E86, 0x97B9, 0x4E88, 0x975C, 0x4E89, 0x9188, 0x4E8A, 0x98AD, + 0x4E8B, 0x8E96, 0x4E8C, 0x93F1, 0x4E8E, 0x98B0, 0x4E91, 0x895D, 0x4E92, 0x8CDD, 0x4E94, 0x8CDC, 0x4E95, 0x88E4, 0x4E98, 0x986A, + 0x4E99, 0x9869, 0x4E9B, 0x8DB1, 0x4E9C, 0x889F, 0x4E9E, 0x98B1, 0x4E9F, 0x98B2, 0x4EA0, 0x98B3, 0x4EA1, 0x9653, 0x4EA2, 0x98B4, + 0x4EA4, 0x8CF0, 0x4EA5, 0x88E5, 0x4EA6, 0x9692, 0x4EA8, 0x8B9C, 0x4EAB, 0x8B9D, 0x4EAC, 0x8B9E, 0x4EAD, 0x92E0, 0x4EAE, 0x97BA, + 0x4EB0, 0x98B5, 0x4EB3, 0x98B6, 0x4EB6, 0x98B7, 0x4EBA, 0x906C, 0x4EC0, 0x8F59, 0x4EC1, 0x906D, 0x4EC2, 0x98BC, 0x4EC4, 0x98BA, + 0x4EC6, 0x98BB, 0x4EC7, 0x8B77, 0x4ECA, 0x8DA1, 0x4ECB, 0x89EE, 0x4ECD, 0x98B9, 0x4ECE, 0x98B8, 0x4ECF, 0x95A7, 0x4ED4, 0x8E65, + 0x4ED5, 0x8E64, 0x4ED6, 0x91BC, 0x4ED7, 0x98BD, 0x4ED8, 0x9574, 0x4ED9, 0x90E5, 0x4EDD, 0x8157, 0x4EDE, 0x98BE, 0x4EDF, 0x98C0, + 0x4EE1, 0xFA69, 0x4EE3, 0x91E3, 0x4EE4, 0x97DF, 0x4EE5, 0x88C8, 0x4EED, 0x98BF, 0x4EEE, 0x89BC, 0x4EF0, 0x8BC2, 0x4EF2, 0x9287, + 0x4EF6, 0x8C8F, 0x4EF7, 0x98C1, 0x4EFB, 0x9443, 0x4EFC, 0xFA6A, 0x4F00, 0xFA6B, 0x4F01, 0x8AE9, 0x4F03, 0xFA6C, 0x4F09, 0x98C2, + 0x4F0A, 0x88C9, 0x4F0D, 0x8CDE, 0x4F0E, 0x8AEA, 0x4F0F, 0x959A, 0x4F10, 0x94B0, 0x4F11, 0x8B78, 0x4F1A, 0x89EF, 0x4F1C, 0x98E5, + 0x4F1D, 0x9360, 0x4F2F, 0x948C, 0x4F30, 0x98C4, 0x4F34, 0x94BA, 0x4F36, 0x97E0, 0x4F38, 0x904C, 0x4F39, 0xFA6D, 0x4F3A, 0x8E66, + 0x4F3C, 0x8E97, 0x4F3D, 0x89BE, 0x4F43, 0x92CF, 0x4F46, 0x9241, 0x4F47, 0x98C8, 0x4F4D, 0x88CA, 0x4F4E, 0x92E1, 0x4F4F, 0x8F5A, + 0x4F50, 0x8DB2, 0x4F51, 0x9743, 0x4F53, 0x91CC, 0x4F55, 0x89BD, 0x4F56, 0xFA6E, 0x4F57, 0x98C7, 0x4F59, 0x975D, 0x4F5A, 0x98C3, + 0x4F5B, 0x98C5, 0x4F5C, 0x8DEC, 0x4F5D, 0x98C6, 0x4F5E, 0x9B43, 0x4F69, 0x98CE, 0x4F6F, 0x98D1, 0x4F70, 0x98CF, 0x4F73, 0x89C0, + 0x4F75, 0x95B9, 0x4F76, 0x98C9, 0x4F7B, 0x98CD, 0x4F7C, 0x8CF1, 0x4F7F, 0x8E67, 0x4F83, 0x8AA4, 0x4F86, 0x98D2, 0x4F88, 0x98CA, + 0x4F8A, 0xFA70, 0x4F8B, 0x97E1, 0x4F8D, 0x8E98, 0x4F8F, 0x98CB, 0x4F91, 0x98D0, 0x4F92, 0xFA6F, 0x4F94, 0xFA72, 0x4F96, 0x98D3, + 0x4F98, 0x98CC, 0x4F9A, 0xFA71, 0x4F9B, 0x8B9F, 0x4F9D, 0x88CB, 0x4FA0, 0x8BA0, 0x4FA1, 0x89BF, 0x4FAB, 0x9B44, 0x4FAD, 0x9699, + 0x4FAE, 0x958E, 0x4FAF, 0x8CF2, 0x4FB5, 0x904E, 0x4FB6, 0x97B5, 0x4FBF, 0x95D6, 0x4FC2, 0x8C57, 0x4FC3, 0x91A3, 0x4FC4, 0x89E2, + 0x4FC9, 0xFA61, 0x4FCA, 0x8F72, 0x4FCD, 0xFA73, 0x4FCE, 0x98D7, 0x4FD0, 0x98DC, 0x4FD1, 0x98DA, 0x4FD4, 0x98D5, 0x4FD7, 0x91AD, + 0x4FD8, 0x98D8, 0x4FDA, 0x98DB, 0x4FDB, 0x98D9, 0x4FDD, 0x95DB, 0x4FDF, 0x98D6, 0x4FE1, 0x904D, 0x4FE3, 0x9693, 0x4FE4, 0x98DD, + 0x4FE5, 0x98DE, 0x4FEE, 0x8F43, 0x4FEF, 0x98EB, 0x4FF3, 0x946F, 0x4FF5, 0x9555, 0x4FF6, 0x98E6, 0x4FF8, 0x95EE, 0x4FFA, 0x89B4, + 0x4FFE, 0x98EA, 0x4FFF, 0xFA76, 0x5005, 0x98E4, 0x5006, 0x98ED, 0x5009, 0x9171, 0x500B, 0x8CC2, 0x500D, 0x947B, 0x500F, 0xE0C5, + 0x5011, 0x98EC, 0x5012, 0x937C, 0x5014, 0x98E1, 0x5016, 0x8CF4, 0x5019, 0x8CF3, 0x501A, 0x98DF, 0x501E, 0xFA77, 0x501F, 0x8ED8, + 0x5021, 0x98E7, 0x5022, 0xFA75, 0x5023, 0x95ED, 0x5024, 0x926C, 0x5025, 0x98E3, 0x5026, 0x8C91, 0x5028, 0x98E0, 0x5029, 0x98E8, + 0x502A, 0x98E2, 0x502B, 0x97CF, 0x502C, 0x98E9, 0x502D, 0x9860, 0x5036, 0x8BE4, 0x5039, 0x8C90, 0x5040, 0xFA74, 0x5042, 0xFA7A, + 0x5043, 0x98EE, 0x5046, 0xFA78, 0x5047, 0x98EF, 0x5048, 0x98F3, 0x5049, 0x88CC, 0x504F, 0x95CE, 0x5050, 0x98F2, 0x5055, 0x98F1, + 0x5056, 0x98F5, 0x505A, 0x98F4, 0x505C, 0x92E2, 0x5065, 0x8C92, 0x506C, 0x98F6, 0x5070, 0xFA79, 0x5072, 0x8EC3, 0x5074, 0x91A4, + 0x5075, 0x92E3, 0x5076, 0x8BF4, 0x5078, 0x98F7, 0x507D, 0x8B55, 0x5080, 0x98F8, 0x5085, 0x98FA, 0x508D, 0x9654, 0x5091, 0x8C86, + 0x5094, 0xFA7B, 0x5098, 0x8E50, 0x5099, 0x94F5, 0x509A, 0x98F9, 0x50AC, 0x8DC3, 0x50AD, 0x9762, 0x50B2, 0x98FC, 0x50B3, 0x9942, + 0x50B4, 0x98FB, 0x50B5, 0x8DC2, 0x50B7, 0x8F9D, 0x50BE, 0x8C58, 0x50C2, 0x9943, 0x50C5, 0x8BCD, 0x50C9, 0x9940, 0x50CA, 0x9941, + 0x50CD, 0x93AD, 0x50CF, 0x919C, 0x50D1, 0x8BA1, 0x50D5, 0x966C, 0x50D6, 0x9944, 0x50D8, 0xFA7D, 0x50DA, 0x97BB, 0x50DE, 0x9945, + 0x50E3, 0x9948, 0x50E5, 0x9946, 0x50E7, 0x916D, 0x50ED, 0x9947, 0x50EE, 0x9949, 0x50F4, 0xFA7C, 0x50F5, 0x994B, 0x50F9, 0x994A, + 0x50FB, 0x95C6, 0x5100, 0x8B56, 0x5101, 0x994D, 0x5102, 0x994E, 0x5104, 0x89AD, 0x5109, 0x994C, 0x5112, 0x8EF2, 0x5114, 0x9951, + 0x5115, 0x9950, 0x5116, 0x994F, 0x5118, 0x98D4, 0x511A, 0x9952, 0x511F, 0x8F9E, 0x5121, 0x9953, 0x512A, 0x9744, 0x5132, 0x96D7, + 0x5137, 0x9955, 0x513A, 0x9954, 0x513B, 0x9957, 0x513C, 0x9956, 0x513F, 0x9958, 0x5140, 0x9959, 0x5141, 0x88F2, 0x5143, 0x8CB3, + 0x5144, 0x8C5A, 0x5145, 0x8F5B, 0x5146, 0x929B, 0x5147, 0x8BA2, 0x5148, 0x90E6, 0x5149, 0x8CF5, 0x514A, 0xFA7E, 0x514B, 0x8D8E, + 0x514C, 0x995B, 0x514D, 0x96C6, 0x514E, 0x9365, 0x5150, 0x8E99, 0x5152, 0x995A, 0x5154, 0x995C, 0x515A, 0x937D, 0x515C, 0x8A95, + 0x5162, 0x995D, 0x5164, 0xFA80, 0x5165, 0x93FC, 0x5168, 0x9153, 0x5169, 0x995F, 0x516A, 0x9960, 0x516B, 0x94AA, 0x516C, 0x8CF6, + 0x516D, 0x985A, 0x516E, 0x9961, 0x5171, 0x8BA4, 0x5175, 0x95BA, 0x5176, 0x91B4, 0x5177, 0x8BEF, 0x5178, 0x9354, 0x517C, 0x8C93, + 0x5180, 0x9962, 0x5182, 0x9963, 0x5185, 0x93E0, 0x5186, 0x897E, 0x5189, 0x9966, 0x518A, 0x8DFB, 0x518C, 0x9965, 0x518D, 0x8DC4, + 0x518F, 0x9967, 0x5190, 0xE3EC, 0x5191, 0x9968, 0x5192, 0x9660, 0x5193, 0x9969, 0x5195, 0x996A, 0x5196, 0x996B, 0x5197, 0x8FE7, + 0x5199, 0x8ECA, 0x519D, 0xFA81, 0x51A0, 0x8AA5, 0x51A2, 0x996E, 0x51A4, 0x996C, 0x51A5, 0x96BB, 0x51A6, 0x996D, 0x51A8, 0x9579, + 0x51A9, 0x996F, 0x51AA, 0x9970, 0x51AB, 0x9971, 0x51AC, 0x937E, 0x51B0, 0x9975, 0x51B1, 0x9973, 0x51B2, 0x9974, 0x51B3, 0x9972, + 0x51B4, 0x8DE1, 0x51B5, 0x9976, 0x51B6, 0x96E8, 0x51B7, 0x97E2, 0x51BD, 0x9977, 0x51BE, 0xFA82, 0x51C4, 0x90A6, 0x51C5, 0x9978, + 0x51C6, 0x8F79, 0x51C9, 0x9979, 0x51CB, 0x929C, 0x51CC, 0x97BD, 0x51CD, 0x9380, 0x51D6, 0x99C3, 0x51DB, 0x997A, 0x51DC, 0xEAA3, + 0x51DD, 0x8BC3, 0x51E0, 0x997B, 0x51E1, 0x967D, 0x51E6, 0x8F88, 0x51E7, 0x91FA, 0x51E9, 0x997D, 0x51EA, 0x93E2, 0x51EC, 0xFA83, + 0x51ED, 0x997E, 0x51F0, 0x9980, 0x51F1, 0x8A4D, 0x51F5, 0x9981, 0x51F6, 0x8BA5, 0x51F8, 0x93CA, 0x51F9, 0x899A, 0x51FA, 0x8F6F, + 0x51FD, 0x949F, 0x51FE, 0x9982, 0x5200, 0x9381, 0x5203, 0x906E, 0x5204, 0x9983, 0x5206, 0x95AA, 0x5207, 0x90D8, 0x5208, 0x8AA0, + 0x520A, 0x8AA7, 0x520B, 0x9984, 0x520E, 0x9986, 0x5211, 0x8C59, 0x5214, 0x9985, 0x5215, 0xFA84, 0x5217, 0x97F1, 0x521D, 0x8F89, + 0x5224, 0x94BB, 0x5225, 0x95CA, 0x5227, 0x9987, 0x5229, 0x9798, 0x522A, 0x9988, 0x522E, 0x9989, 0x5230, 0x939E, 0x5233, 0x998A, + 0x5236, 0x90A7, 0x5237, 0x8DFC, 0x5238, 0x8C94, 0x5239, 0x998B, 0x523A, 0x8E68, 0x523B, 0x8D8F, 0x5243, 0x92E4, 0x5244, 0x998D, + 0x5247, 0x91A5, 0x524A, 0x8DED, 0x524B, 0x998E, 0x524C, 0x998F, 0x524D, 0x914F, 0x524F, 0x998C, 0x5254, 0x9991, 0x5256, 0x9655, + 0x525B, 0x8D84, 0x525E, 0x9990, 0x5263, 0x8C95, 0x5264, 0x8DDC, 0x5265, 0x948D, 0x5269, 0x9994, 0x526A, 0x9992, 0x526F, 0x959B, + 0x5270, 0x8FE8, 0x5271, 0x999B, 0x5272, 0x8A84, 0x5273, 0x9995, 0x5274, 0x9993, 0x5275, 0x916E, 0x527D, 0x9997, 0x527F, 0x9996, + 0x5283, 0x8A63, 0x5287, 0x8C80, 0x5288, 0x999C, 0x5289, 0x97AB, 0x528D, 0x9998, 0x5291, 0x999D, 0x5292, 0x999A, 0x5294, 0x9999, + 0x529B, 0x97CD, 0x529C, 0xFA85, 0x529F, 0x8CF7, 0x52A0, 0x89C1, 0x52A3, 0x97F2, 0x52A6, 0xFA86, 0x52A9, 0x8F95, 0x52AA, 0x9377, + 0x52AB, 0x8D85, 0x52AC, 0x99A0, 0x52AD, 0x99A1, 0x52AF, 0xFB77, 0x52B1, 0x97E3, 0x52B4, 0x984A, 0x52B5, 0x99A3, 0x52B9, 0x8CF8, + 0x52BC, 0x99A2, 0x52BE, 0x8A4E, 0x52C0, 0xFA87, 0x52C1, 0x99A4, 0x52C3, 0x9675, 0x52C5, 0x92BA, 0x52C7, 0x9745, 0x52C9, 0x95D7, + 0x52CD, 0x99A5, 0x52D2, 0xE8D3, 0x52D5, 0x93AE, 0x52D7, 0x99A6, 0x52D8, 0x8AA8, 0x52D9, 0x96B1, 0x52DB, 0xFA88, 0x52DD, 0x8F9F, + 0x52DE, 0x99A7, 0x52DF, 0x95E5, 0x52E0, 0x99AB, 0x52E2, 0x90A8, 0x52E3, 0x99A8, 0x52E4, 0x8BCE, 0x52E6, 0x99A9, 0x52E7, 0x8AA9, + 0x52F2, 0x8C4D, 0x52F3, 0x99AC, 0x52F5, 0x99AD, 0x52F8, 0x99AE, 0x52F9, 0x99AF, 0x52FA, 0x8ED9, 0x52FE, 0x8CF9, 0x52FF, 0x96DC, + 0x5300, 0xFA89, 0x5301, 0x96E6, 0x5302, 0x93F5, 0x5305, 0x95EF, 0x5306, 0x99B0, 0x5307, 0xFA8A, 0x5308, 0x99B1, 0x530D, 0x99B3, + 0x530F, 0x99B5, 0x5310, 0x99B4, 0x5315, 0x99B6, 0x5316, 0x89BB, 0x5317, 0x966B, 0x5319, 0x8DFA, 0x531A, 0x99B7, 0x531D, 0x9178, + 0x5320, 0x8FA0, 0x5321, 0x8BA7, 0x5323, 0x99B8, 0x5324, 0xFA8B, 0x532A, 0x94D9, 0x532F, 0x99B9, 0x5331, 0x99BA, 0x5333, 0x99BB, + 0x5338, 0x99BC, 0x5339, 0x9543, 0x533A, 0x8BE6, 0x533B, 0x88E3, 0x533F, 0x93BD, 0x5340, 0x99BD, 0x5341, 0x8F5C, 0x5343, 0x90E7, + 0x5345, 0x99BF, 0x5346, 0x99BE, 0x5347, 0x8FA1, 0x5348, 0x8CDF, 0x5349, 0x99C1, 0x534A, 0x94BC, 0x534D, 0x99C2, 0x5351, 0x94DA, + 0x5352, 0x91B2, 0x5353, 0x91EC, 0x5354, 0x8BA6, 0x5357, 0x93EC, 0x5358, 0x9250, 0x535A, 0x948E, 0x535C, 0x966D, 0x535E, 0x99C4, + 0x5360, 0x90E8, 0x5366, 0x8C54, 0x5369, 0x99C5, 0x536E, 0x99C6, 0x536F, 0x894B, 0x5370, 0x88F3, 0x5371, 0x8AEB, 0x5372, 0xFA8C, + 0x5373, 0x91A6, 0x5374, 0x8B70, 0x5375, 0x9791, 0x5377, 0x99C9, 0x5378, 0x89B5, 0x537B, 0x99C8, 0x537F, 0x8BA8, 0x5382, 0x99CA, + 0x5384, 0x96EF, 0x5393, 0xFA8D, 0x5396, 0x99CB, 0x5398, 0x97D0, 0x539A, 0x8CFA, 0x539F, 0x8CB4, 0x53A0, 0x99CC, 0x53A5, 0x99CE, + 0x53A6, 0x99CD, 0x53A8, 0x907E, 0x53A9, 0x8958, 0x53AD, 0x897D, 0x53AE, 0x99CF, 0x53B0, 0x99D0, 0x53B2, 0xFA8E, 0x53B3, 0x8CB5, + 0x53B6, 0x99D1, 0x53BB, 0x8B8E, 0x53C2, 0x8E51, 0x53C3, 0x99D2, 0x53C8, 0x9694, 0x53C9, 0x8DB3, 0x53CA, 0x8B79, 0x53CB, 0x9746, + 0x53CC, 0x916F, 0x53CD, 0x94BD, 0x53CE, 0x8EFB, 0x53D4, 0x8F66, 0x53D6, 0x8EE6, 0x53D7, 0x8EF3, 0x53D9, 0x8F96, 0x53DB, 0x94BE, + 0x53DD, 0xFA8F, 0x53DF, 0x99D5, 0x53E1, 0x8962, 0x53E2, 0x9170, 0x53E3, 0x8CFB, 0x53E4, 0x8CC3, 0x53E5, 0x8BE5, 0x53E8, 0x99D9, + 0x53E9, 0x9240, 0x53EA, 0x91FC, 0x53EB, 0x8BA9, 0x53EC, 0x8FA2, 0x53ED, 0x99DA, 0x53EE, 0x99D8, 0x53EF, 0x89C2, 0x53F0, 0x91E4, + 0x53F1, 0x8EB6, 0x53F2, 0x8E6A, 0x53F3, 0x8945, 0x53F6, 0x8A90, 0x53F7, 0x8D86, 0x53F8, 0x8E69, 0x53FA, 0x99DB, 0x5401, 0x99DC, + 0x5403, 0x8B68, 0x5404, 0x8A65, 0x5408, 0x8D87, 0x5409, 0x8B67, 0x540A, 0x92DD, 0x540B, 0x8944, 0x540C, 0x93AF, 0x540D, 0x96BC, + 0x540E, 0x8D40, 0x540F, 0x9799, 0x5410, 0x9366, 0x5411, 0x8CFC, 0x541B, 0x8C4E, 0x541D, 0x99E5, 0x541F, 0x8BE1, 0x5420, 0x9669, + 0x5426, 0x94DB, 0x5429, 0x99E4, 0x542B, 0x8ADC, 0x542C, 0x99DF, 0x542D, 0x99E0, 0x542E, 0x99E2, 0x5436, 0x99E3, 0x5438, 0x8B7A, + 0x5439, 0x9081, 0x543B, 0x95AB, 0x543C, 0x99E1, 0x543D, 0x99DD, 0x543E, 0x8CE1, 0x5440, 0x99DE, 0x5442, 0x9843, 0x5446, 0x95F0, + 0x5448, 0x92E6, 0x5449, 0x8CE0, 0x544A, 0x8D90, 0x544E, 0x99E6, 0x5451, 0x93DB, 0x545F, 0x99EA, 0x5468, 0x8EFC, 0x546A, 0x8EF4, + 0x5470, 0x99ED, 0x5471, 0x99EB, 0x5473, 0x96A1, 0x5475, 0x99E8, 0x5476, 0x99F1, 0x5477, 0x99EC, 0x547B, 0x99EF, 0x547C, 0x8CC4, + 0x547D, 0x96BD, 0x5480, 0x99F0, 0x5484, 0x99F2, 0x5486, 0x99F4, 0x548A, 0xFA92, 0x548B, 0x8DEE, 0x548C, 0x9861, 0x548E, 0x99E9, + 0x548F, 0x99E7, 0x5490, 0x99F3, 0x5492, 0x99EE, 0x549C, 0xFA91, 0x54A2, 0x99F6, 0x54A4, 0x9A42, 0x54A5, 0x99F8, 0x54A8, 0x99FC, + 0x54A9, 0xFA93, 0x54AB, 0x9A40, 0x54AC, 0x99F9, 0x54AF, 0x9A5D, 0x54B2, 0x8DE7, 0x54B3, 0x8A50, 0x54B8, 0x99F7, 0x54BC, 0x9A44, + 0x54BD, 0x88F4, 0x54BE, 0x9A43, 0x54C0, 0x88A3, 0x54C1, 0x9569, 0x54C2, 0x9A41, 0x54C4, 0x99FA, 0x54C7, 0x99F5, 0x54C8, 0x99FB, + 0x54C9, 0x8DC6, 0x54D8, 0x9A45, 0x54E1, 0x88F5, 0x54E2, 0x9A4E, 0x54E5, 0x9A46, 0x54E6, 0x9A47, 0x54E8, 0x8FA3, 0x54E9, 0x9689, + 0x54ED, 0x9A4C, 0x54EE, 0x9A4B, 0x54F2, 0x934E, 0x54FA, 0x9A4D, 0x54FD, 0x9A4A, 0x54FF, 0xFA94, 0x5504, 0x8953, 0x5506, 0x8DB4, + 0x5507, 0x904F, 0x550F, 0x9A48, 0x5510, 0x9382, 0x5514, 0x9A49, 0x5516, 0x88A0, 0x552E, 0x9A53, 0x552F, 0x9742, 0x5531, 0x8FA5, + 0x5533, 0x9A59, 0x5538, 0x9A58, 0x5539, 0x9A4F, 0x553E, 0x91C1, 0x5540, 0x9A50, 0x5544, 0x91ED, 0x5545, 0x9A55, 0x5546, 0x8FA4, + 0x554C, 0x9A52, 0x554F, 0x96E2, 0x5553, 0x8C5B, 0x5556, 0x9A56, 0x5557, 0x9A57, 0x555C, 0x9A54, 0x555D, 0x9A5A, 0x5563, 0x9A51, + 0x557B, 0x9A60, 0x557C, 0x9A65, 0x557E, 0x9A61, 0x5580, 0x9A5C, 0x5583, 0x9A66, 0x5584, 0x9150, 0x5586, 0xFA95, 0x5587, 0x9A68, + 0x5589, 0x8D41, 0x558A, 0x9A5E, 0x558B, 0x929D, 0x5598, 0x9A62, 0x5599, 0x9A5B, 0x559A, 0x8AAB, 0x559C, 0x8AEC, 0x559D, 0x8A85, + 0x559E, 0x9A63, 0x559F, 0x9A5F, 0x55A7, 0x8C96, 0x55A8, 0x9A69, 0x55A9, 0x9A67, 0x55AA, 0x9172, 0x55AB, 0x8B69, 0x55AC, 0x8BAA, + 0x55AE, 0x9A64, 0x55B0, 0x8BF2, 0x55B6, 0x8963, 0x55C4, 0x9A6D, 0x55C5, 0x9A6B, 0x55C7, 0x9AA5, 0x55D4, 0x9A70, 0x55DA, 0x9A6A, + 0x55DC, 0x9A6E, 0x55DF, 0x9A6C, 0x55E3, 0x8E6B, 0x55E4, 0x9A6F, 0x55F7, 0x9A72, 0x55F9, 0x9A77, 0x55FD, 0x9A75, 0x55FE, 0x9A74, + 0x5606, 0x9251, 0x5609, 0x89C3, 0x5614, 0x9A71, 0x5616, 0x9A73, 0x5617, 0x8FA6, 0x5618, 0x8952, 0x561B, 0x9A76, 0x5629, 0x89DC, + 0x562F, 0x9A82, 0x5631, 0x8FFA, 0x5632, 0x9A7D, 0x5634, 0x9A7B, 0x5636, 0x9A7C, 0x5638, 0x9A7E, 0x5642, 0x895C, 0x564C, 0x9158, + 0x564E, 0x9A78, 0x5650, 0x9A79, 0x565B, 0x8A9A, 0x5664, 0x9A81, 0x5668, 0x8AED, 0x566A, 0x9A84, 0x566B, 0x9A80, 0x566C, 0x9A83, + 0x5674, 0x95AC, 0x5678, 0x93D3, 0x567A, 0x94B6, 0x5680, 0x9A86, 0x5686, 0x9A85, 0x5687, 0x8A64, 0x568A, 0x9A87, 0x568F, 0x9A8A, + 0x5694, 0x9A89, 0x56A0, 0x9A88, 0x56A2, 0x9458, 0x56A5, 0x9A8B, 0x56AE, 0x9A8C, 0x56B4, 0x9A8E, 0x56B6, 0x9A8D, 0x56BC, 0x9A90, + 0x56C0, 0x9A93, 0x56C1, 0x9A91, 0x56C2, 0x9A8F, 0x56C3, 0x9A92, 0x56C8, 0x9A94, 0x56CE, 0x9A95, 0x56D1, 0x9A96, 0x56D3, 0x9A97, + 0x56D7, 0x9A98, 0x56D8, 0x9964, 0x56DA, 0x8EFA, 0x56DB, 0x8E6C, 0x56DE, 0x89F1, 0x56E0, 0x88F6, 0x56E3, 0x9263, 0x56EE, 0x9A99, + 0x56F0, 0x8DA2, 0x56F2, 0x88CD, 0x56F3, 0x907D, 0x56F9, 0x9A9A, 0x56FA, 0x8CC5, 0x56FD, 0x8D91, 0x56FF, 0x9A9C, 0x5700, 0x9A9B, + 0x5703, 0x95DE, 0x5704, 0x9A9D, 0x5708, 0x9A9F, 0x5709, 0x9A9E, 0x570B, 0x9AA0, 0x570D, 0x9AA1, 0x570F, 0x8C97, 0x5712, 0x8980, + 0x5713, 0x9AA2, 0x5716, 0x9AA4, 0x5718, 0x9AA3, 0x571C, 0x9AA6, 0x571F, 0x9379, 0x5726, 0x9AA7, 0x5727, 0x88B3, 0x5728, 0x8DDD, + 0x572D, 0x8C5C, 0x5730, 0x926E, 0x5737, 0x9AA8, 0x5738, 0x9AA9, 0x573B, 0x9AAB, 0x5740, 0x9AAC, 0x5742, 0x8DE2, 0x5747, 0x8BCF, + 0x574A, 0x9656, 0x574E, 0x9AAA, 0x574F, 0x9AAD, 0x5750, 0x8DBF, 0x5751, 0x8D42, 0x5759, 0xFA96, 0x5761, 0x9AB1, 0x5764, 0x8DA3, + 0x5765, 0xFA97, 0x5766, 0x9252, 0x5769, 0x9AAE, 0x576A, 0x92D8, 0x577F, 0x9AB2, 0x5782, 0x9082, 0x5788, 0x9AB0, 0x5789, 0x9AB3, + 0x578B, 0x8C5E, 0x5793, 0x9AB4, 0x57A0, 0x9AB5, 0x57A2, 0x8D43, 0x57A3, 0x8A5F, 0x57A4, 0x9AB7, 0x57AA, 0x9AB8, 0x57AC, 0xFA98, + 0x57B0, 0x9AB9, 0x57B3, 0x9AB6, 0x57C0, 0x9AAF, 0x57C3, 0x9ABA, 0x57C6, 0x9ABB, 0x57C7, 0xFA9A, 0x57C8, 0xFA99, 0x57CB, 0x9684, + 0x57CE, 0x8FE9, 0x57D2, 0x9ABD, 0x57D3, 0x9ABE, 0x57D4, 0x9ABC, 0x57D6, 0x9AC0, 0x57DC, 0x9457, 0x57DF, 0x88E6, 0x57E0, 0x9575, + 0x57E3, 0x9AC1, 0x57F4, 0x8FFB, 0x57F7, 0x8EB7, 0x57F9, 0x947C, 0x57FA, 0x8AEE, 0x57FC, 0x8DE9, 0x5800, 0x9678, 0x5802, 0x93B0, + 0x5805, 0x8C98, 0x5806, 0x91CD, 0x580A, 0x9ABF, 0x580B, 0x9AC2, 0x5815, 0x91C2, 0x5819, 0x9AC3, 0x581D, 0x9AC4, 0x5821, 0x9AC6, + 0x5824, 0x92E7, 0x582A, 0x8AAC, 0x582F, 0xEA9F, 0x5830, 0x8981, 0x5831, 0x95F1, 0x5834, 0x8FEA, 0x5835, 0x9367, 0x583A, 0x8DE4, + 0x583D, 0x9ACC, 0x5840, 0x95BB, 0x5841, 0x97DB, 0x584A, 0x89F2, 0x584B, 0x9AC8, 0x5851, 0x9159, 0x5852, 0x9ACB, 0x5854, 0x9383, + 0x5857, 0x9368, 0x5858, 0x9384, 0x5859, 0x94B7, 0x585A, 0x92CB, 0x585E, 0x8DC7, 0x5862, 0x9AC7, 0x5869, 0x8996, 0x586B, 0x9355, + 0x5870, 0x9AC9, 0x5872, 0x9AC5, 0x5875, 0x906F, 0x5879, 0x9ACD, 0x587E, 0x8F6D, 0x5883, 0x8BAB, 0x5885, 0x9ACE, 0x5893, 0x95E6, + 0x5897, 0x919D, 0x589C, 0x92C4, 0x589E, 0xFA9D, 0x589F, 0x9AD0, 0x58A8, 0x966E, 0x58AB, 0x9AD1, 0x58AE, 0x9AD6, 0x58B2, 0xFA9E, + 0x58B3, 0x95AD, 0x58B8, 0x9AD5, 0x58B9, 0x9ACF, 0x58BA, 0x9AD2, 0x58BB, 0x9AD4, 0x58BE, 0x8DA4, 0x58C1, 0x95C7, 0x58C5, 0x9AD7, + 0x58C7, 0x9264, 0x58CA, 0x89F3, 0x58CC, 0x8FEB, 0x58D1, 0x9AD9, 0x58D3, 0x9AD8, 0x58D5, 0x8D88, 0x58D7, 0x9ADA, 0x58D8, 0x9ADC, + 0x58D9, 0x9ADB, 0x58DC, 0x9ADE, 0x58DE, 0x9AD3, 0x58DF, 0x9AE0, 0x58E4, 0x9ADF, 0x58E5, 0x9ADD, 0x58EB, 0x8E6D, 0x58EC, 0x9070, + 0x58EE, 0x9173, 0x58EF, 0x9AE1, 0x58F0, 0x90BA, 0x58F1, 0x88EB, 0x58F2, 0x9484, 0x58F7, 0x92D9, 0x58F9, 0x9AE3, 0x58FA, 0x9AE2, + 0x58FB, 0x9AE4, 0x58FC, 0x9AE5, 0x58FD, 0x9AE6, 0x5902, 0x9AE7, 0x5909, 0x95CF, 0x590A, 0x9AE8, 0x590B, 0xFA9F, 0x590F, 0x89C4, + 0x5910, 0x9AE9, 0x5915, 0x975B, 0x5916, 0x8A4F, 0x5918, 0x99C7, 0x5919, 0x8F67, 0x591A, 0x91BD, 0x591B, 0x9AEA, 0x591C, 0x96E9, + 0x5922, 0x96B2, 0x5925, 0x9AEC, 0x5927, 0x91E5, 0x5929, 0x9356, 0x592A, 0x91BE, 0x592B, 0x9576, 0x592C, 0x9AED, 0x592D, 0x9AEE, + 0x592E, 0x899B, 0x5931, 0x8EB8, 0x5932, 0x9AEF, 0x5937, 0x88CE, 0x5938, 0x9AF0, 0x593E, 0x9AF1, 0x5944, 0x8982, 0x5947, 0x8AEF, + 0x5948, 0x93DE, 0x5949, 0x95F2, 0x594E, 0x9AF5, 0x594F, 0x9174, 0x5950, 0x9AF4, 0x5951, 0x8C5F, 0x5953, 0xFAA0, 0x5954, 0x967A, + 0x5955, 0x9AF3, 0x5957, 0x9385, 0x5958, 0x9AF7, 0x595A, 0x9AF6, 0x595B, 0xFAA1, 0x595D, 0xFAA2, 0x5960, 0x9AF9, 0x5962, 0x9AF8, + 0x5963, 0xFAA3, 0x5965, 0x899C, 0x5967, 0x9AFA, 0x5968, 0x8FA7, 0x5969, 0x9AFC, 0x596A, 0x9244, 0x596C, 0x9AFB, 0x596E, 0x95B1, + 0x5973, 0x8F97, 0x5974, 0x937A, 0x5978, 0x9B40, 0x597D, 0x8D44, 0x5981, 0x9B41, 0x5982, 0x9440, 0x5983, 0x94DC, 0x5984, 0x96CF, + 0x598A, 0x9444, 0x598D, 0x9B4A, 0x5993, 0x8B57, 0x5996, 0x9764, 0x5999, 0x96AD, 0x599B, 0x9BAA, 0x599D, 0x9B42, 0x59A3, 0x9B45, + 0x59A4, 0xFAA4, 0x59A5, 0x91C3, 0x59A8, 0x9657, 0x59AC, 0x9369, 0x59B2, 0x9B46, 0x59B9, 0x9685, 0x59BA, 0xFAA5, 0x59BB, 0x8DC8, + 0x59BE, 0x8FA8, 0x59C6, 0x9B47, 0x59C9, 0x8E6F, 0x59CB, 0x8E6E, 0x59D0, 0x88B7, 0x59D1, 0x8CC6, 0x59D3, 0x90A9, 0x59D4, 0x88CF, + 0x59D9, 0x9B4B, 0x59DA, 0x9B4C, 0x59DC, 0x9B49, 0x59E5, 0x8957, 0x59E6, 0x8AAD, 0x59E8, 0x9B48, 0x59EA, 0x96C3, 0x59EB, 0x9550, + 0x59F6, 0x88A6, 0x59FB, 0x88F7, 0x59FF, 0x8E70, 0x5A01, 0x88D0, 0x5A03, 0x88A1, 0x5A09, 0x9B51, 0x5A11, 0x9B4F, 0x5A18, 0x96BA, + 0x5A1A, 0x9B52, 0x5A1C, 0x9B50, 0x5A1F, 0x9B4E, 0x5A20, 0x9050, 0x5A25, 0x9B4D, 0x5A29, 0x95D8, 0x5A2F, 0x8CE2, 0x5A35, 0x9B56, + 0x5A36, 0x9B57, 0x5A3C, 0x8FA9, 0x5A40, 0x9B53, 0x5A41, 0x984B, 0x5A46, 0x946B, 0x5A49, 0x9B55, 0x5A5A, 0x8DA5, 0x5A62, 0x9B58, + 0x5A66, 0x9577, 0x5A6A, 0x9B59, 0x5A6C, 0x9B54, 0x5A7F, 0x96B9, 0x5A92, 0x947D, 0x5A9A, 0x9B5A, 0x5A9B, 0x9551, 0x5ABC, 0x9B5B, + 0x5ABD, 0x9B5F, 0x5ABE, 0x9B5C, 0x5AC1, 0x89C5, 0x5AC2, 0x9B5E, 0x5AC9, 0x8EB9, 0x5ACB, 0x9B5D, 0x5ACC, 0x8C99, 0x5AD0, 0x9B6B, + 0x5AD6, 0x9B64, 0x5AD7, 0x9B61, 0x5AE1, 0x9284, 0x5AE3, 0x9B60, 0x5AE6, 0x9B62, 0x5AE9, 0x9B63, 0x5AFA, 0x9B65, 0x5AFB, 0x9B66, + 0x5B09, 0x8AF0, 0x5B0B, 0x9B68, 0x5B0C, 0x9B67, 0x5B16, 0x9B69, 0x5B22, 0x8FEC, 0x5B2A, 0x9B6C, 0x5B2C, 0x92DA, 0x5B30, 0x8964, + 0x5B32, 0x9B6A, 0x5B36, 0x9B6D, 0x5B3E, 0x9B6E, 0x5B40, 0x9B71, 0x5B43, 0x9B6F, 0x5B45, 0x9B70, 0x5B50, 0x8E71, 0x5B51, 0x9B72, + 0x5B54, 0x8D45, 0x5B55, 0x9B73, 0x5B56, 0xFAA6, 0x5B57, 0x8E9A, 0x5B58, 0x91B6, 0x5B5A, 0x9B74, 0x5B5B, 0x9B75, 0x5B5C, 0x8E79, + 0x5B5D, 0x8D46, 0x5B5F, 0x96D0, 0x5B63, 0x8B47, 0x5B64, 0x8CC7, 0x5B65, 0x9B76, 0x5B66, 0x8A77, 0x5B69, 0x9B77, 0x5B6B, 0x91B7, + 0x5B70, 0x9B78, 0x5B71, 0x9BA1, 0x5B73, 0x9B79, 0x5B75, 0x9B7A, 0x5B78, 0x9B7B, 0x5B7A, 0x9B7D, 0x5B80, 0x9B7E, 0x5B83, 0x9B80, + 0x5B85, 0x91EE, 0x5B87, 0x8946, 0x5B88, 0x8EE7, 0x5B89, 0x88C0, 0x5B8B, 0x9176, 0x5B8C, 0x8AAE, 0x5B8D, 0x8EB3, 0x5B8F, 0x8D47, + 0x5B95, 0x9386, 0x5B97, 0x8F40, 0x5B98, 0x8AAF, 0x5B99, 0x9288, 0x5B9A, 0x92E8, 0x5B9B, 0x88B6, 0x5B9C, 0x8B58, 0x5B9D, 0x95F3, + 0x5B9F, 0x8EC0, 0x5BA2, 0x8B71, 0x5BA3, 0x90E9, 0x5BA4, 0x8EBA, 0x5BA5, 0x9747, 0x5BA6, 0x9B81, 0x5BAE, 0x8B7B, 0x5BB0, 0x8DC9, + 0x5BB3, 0x8A51, 0x5BB4, 0x8983, 0x5BB5, 0x8FAA, 0x5BB6, 0x89C6, 0x5BB8, 0x9B82, 0x5BB9, 0x9765, 0x5BBF, 0x8F68, 0x5BC0, 0xFAA7, + 0x5BC2, 0x8EE2, 0x5BC3, 0x9B83, 0x5BC4, 0x8AF1, 0x5BC5, 0x93D0, 0x5BC6, 0x96A7, 0x5BC7, 0x9B84, 0x5BC9, 0x9B85, 0x5BCC, 0x9578, + 0x5BD0, 0x9B87, 0x5BD2, 0x8AA6, 0x5BD3, 0x8BF5, 0x5BD4, 0x9B86, 0x5BD8, 0xFAA9, 0x5BDB, 0x8AB0, 0x5BDD, 0x9051, 0x5BDE, 0x9B8B, + 0x5BDF, 0x8E40, 0x5BE1, 0x89C7, 0x5BE2, 0x9B8A, 0x5BE4, 0x9B88, 0x5BE5, 0x9B8C, 0x5BE6, 0x9B89, 0x5BE7, 0x944A, 0x5BE8, 0x9ECB, + 0x5BE9, 0x9052, 0x5BEB, 0x9B8D, 0x5BEC, 0xFAAA, 0x5BEE, 0x97BE, 0x5BF0, 0x9B8E, 0x5BF3, 0x9B90, 0x5BF5, 0x929E, 0x5BF6, 0x9B8F, + 0x5BF8, 0x90A1, 0x5BFA, 0x8E9B, 0x5BFE, 0x91CE, 0x5BFF, 0x8EF5, 0x5C01, 0x9595, 0x5C02, 0x90EA, 0x5C04, 0x8ECB, 0x5C05, 0x9B91, + 0x5C06, 0x8FAB, 0x5C07, 0x9B92, 0x5C08, 0x9B93, 0x5C09, 0x88D1, 0x5C0A, 0x91B8, 0x5C0B, 0x9071, 0x5C0D, 0x9B94, 0x5C0E, 0x93B1, + 0x5C0F, 0x8FAC, 0x5C11, 0x8FAD, 0x5C13, 0x9B95, 0x5C16, 0x90EB, 0x5C1A, 0x8FAE, 0x5C1E, 0xFAAB, 0x5C20, 0x9B96, 0x5C22, 0x9B97, + 0x5C24, 0x96DE, 0x5C28, 0x9B98, 0x5C2D, 0x8BC4, 0x5C31, 0x8F41, 0x5C38, 0x9B99, 0x5C39, 0x9B9A, 0x5C3A, 0x8EDA, 0x5C3B, 0x904B, + 0x5C3C, 0x93F2, 0x5C3D, 0x9073, 0x5C3E, 0x94F6, 0x5C3F, 0x9441, 0x5C40, 0x8BC7, 0x5C41, 0x9B9B, 0x5C45, 0x8B8F, 0x5C46, 0x9B9C, + 0x5C48, 0x8BFC, 0x5C4A, 0x93CD, 0x5C4B, 0x89AE, 0x5C4D, 0x8E72, 0x5C4E, 0x9B9D, 0x5C4F, 0x9BA0, 0x5C50, 0x9B9F, 0x5C51, 0x8BFB, + 0x5C53, 0x9B9E, 0x5C55, 0x9357, 0x5C5E, 0x91AE, 0x5C60, 0x936A, 0x5C61, 0x8EC6, 0x5C64, 0x9177, 0x5C65, 0x979A, 0x5C6C, 0x9BA2, + 0x5C6E, 0x9BA3, 0x5C6F, 0x93D4, 0x5C71, 0x8E52, 0x5C76, 0x9BA5, 0x5C79, 0x9BA6, 0x5C8C, 0x9BA7, 0x5C90, 0x8AF2, 0x5C91, 0x9BA8, + 0x5C94, 0x9BA9, 0x5CA1, 0x89AA, 0x5CA6, 0xFAAC, 0x5CA8, 0x915A, 0x5CA9, 0x8AE2, 0x5CAB, 0x9BAB, 0x5CAC, 0x96A6, 0x5CB1, 0x91D0, + 0x5CB3, 0x8A78, 0x5CB6, 0x9BAD, 0x5CB7, 0x9BAF, 0x5CB8, 0x8ADD, 0x5CBA, 0xFAAD, 0x5CBB, 0x9BAC, 0x5CBC, 0x9BAE, 0x5CBE, 0x9BB1, + 0x5CC5, 0x9BB0, 0x5CC7, 0x9BB2, 0x5CD9, 0x9BB3, 0x5CE0, 0x93BB, 0x5CE1, 0x8BAC, 0x5CE8, 0x89E3, 0x5CE9, 0x9BB4, 0x5CEA, 0x9BB9, + 0x5CED, 0x9BB7, 0x5CEF, 0x95F5, 0x5CF0, 0x95F4, 0x5CF5, 0xFAAE, 0x5CF6, 0x9387, 0x5CFA, 0x9BB6, 0x5CFB, 0x8F73, 0x5CFD, 0x9BB5, + 0x5D07, 0x9092, 0x5D0B, 0x9BBA, 0x5D0E, 0x8DE8, 0x5D11, 0x9BC0, 0x5D14, 0x9BC1, 0x5D15, 0x9BBB, 0x5D16, 0x8A52, 0x5D17, 0x9BBC, + 0x5D18, 0x9BC5, 0x5D19, 0x9BC4, 0x5D1A, 0x9BC3, 0x5D1B, 0x9BBF, 0x5D1F, 0x9BBE, 0x5D22, 0x9BC2, 0x5D27, 0xFAAF, 0x5D29, 0x95F6, + 0x5D42, 0xFAB2, 0x5D4B, 0x9BC9, 0x5D4C, 0x9BC6, 0x5D4E, 0x9BC8, 0x5D50, 0x9792, 0x5D52, 0x9BC7, 0x5D53, 0xFAB0, 0x5D5C, 0x9BBD, + 0x5D69, 0x9093, 0x5D6C, 0x9BCA, 0x5D6D, 0xFAB3, 0x5D6F, 0x8DB5, 0x5D73, 0x9BCB, 0x5D76, 0x9BCC, 0x5D82, 0x9BCF, 0x5D84, 0x9BCE, + 0x5D87, 0x9BCD, 0x5D8B, 0x9388, 0x5D8C, 0x9BB8, 0x5D90, 0x9BD5, 0x5D9D, 0x9BD1, 0x5DA2, 0x9BD0, 0x5DAC, 0x9BD2, 0x5DAE, 0x9BD3, + 0x5DB7, 0x9BD6, 0x5DB8, 0xFAB4, 0x5DB9, 0xFAB5, 0x5DBA, 0x97E4, 0x5DBC, 0x9BD7, 0x5DBD, 0x9BD4, 0x5DC9, 0x9BD8, 0x5DCC, 0x8ADE, + 0x5DCD, 0x9BD9, 0x5DD0, 0xFAB6, 0x5DD2, 0x9BDB, 0x5DD3, 0x9BDA, 0x5DD6, 0x9BDC, 0x5DDB, 0x9BDD, 0x5DDD, 0x90EC, 0x5DDE, 0x8F42, + 0x5DE1, 0x8F84, 0x5DE3, 0x9183, 0x5DE5, 0x8D48, 0x5DE6, 0x8DB6, 0x5DE7, 0x8D49, 0x5DE8, 0x8B90, 0x5DEB, 0x9BDE, 0x5DEE, 0x8DB7, + 0x5DF1, 0x8CC8, 0x5DF2, 0x9BDF, 0x5DF3, 0x96A4, 0x5DF4, 0x9462, 0x5DF5, 0x9BE0, 0x5DF7, 0x8D4A, 0x5DFB, 0x8AAA, 0x5DFD, 0x9246, + 0x5DFE, 0x8BD0, 0x5E02, 0x8E73, 0x5E03, 0x957A, 0x5E06, 0x94BF, 0x5E0B, 0x9BE1, 0x5E0C, 0x8AF3, 0x5E11, 0x9BE4, 0x5E16, 0x929F, + 0x5E19, 0x9BE3, 0x5E1A, 0x9BE2, 0x5E1B, 0x9BE5, 0x5E1D, 0x92E9, 0x5E25, 0x9083, 0x5E2B, 0x8E74, 0x5E2D, 0x90C8, 0x5E2F, 0x91D1, + 0x5E30, 0x8B41, 0x5E33, 0x92A0, 0x5E36, 0x9BE6, 0x5E37, 0x9BE7, 0x5E38, 0x8FED, 0x5E3D, 0x9658, 0x5E40, 0x9BEA, 0x5E43, 0x9BE9, + 0x5E44, 0x9BE8, 0x5E45, 0x959D, 0x5E47, 0x9BF1, 0x5E4C, 0x9679, 0x5E4E, 0x9BEB, 0x5E54, 0x9BED, 0x5E55, 0x968B, 0x5E57, 0x9BEC, + 0x5E5F, 0x9BEE, 0x5E61, 0x94A6, 0x5E62, 0x9BEF, 0x5E63, 0x95BC, 0x5E64, 0x9BF0, 0x5E72, 0x8AB1, 0x5E73, 0x95BD, 0x5E74, 0x944E, + 0x5E75, 0x9BF2, 0x5E76, 0x9BF3, 0x5E78, 0x8D4B, 0x5E79, 0x8AB2, 0x5E7A, 0x9BF4, 0x5E7B, 0x8CB6, 0x5E7C, 0x9763, 0x5E7D, 0x9748, + 0x5E7E, 0x8AF4, 0x5E7F, 0x9BF6, 0x5E81, 0x92A1, 0x5E83, 0x8D4C, 0x5E84, 0x8FAF, 0x5E87, 0x94DD, 0x5E8A, 0x8FB0, 0x5E8F, 0x8F98, + 0x5E95, 0x92EA, 0x5E96, 0x95F7, 0x5E97, 0x9358, 0x5E9A, 0x8D4D, 0x5E9C, 0x957B, 0x5EA0, 0x9BF7, 0x5EA6, 0x9378, 0x5EA7, 0x8DC0, + 0x5EAB, 0x8CC9, 0x5EAD, 0x92EB, 0x5EB5, 0x88C1, 0x5EB6, 0x8F8E, 0x5EB7, 0x8D4E, 0x5EB8, 0x9766, 0x5EC1, 0x9BF8, 0x5EC2, 0x9BF9, + 0x5EC3, 0x9470, 0x5EC8, 0x9BFA, 0x5EC9, 0x97F5, 0x5ECA, 0x984C, 0x5ECF, 0x9BFC, 0x5ED0, 0x9BFB, 0x5ED3, 0x8A66, 0x5ED6, 0x9C40, + 0x5EDA, 0x9C43, 0x5EDB, 0x9C44, 0x5EDD, 0x9C42, 0x5EDF, 0x955F, 0x5EE0, 0x8FB1, 0x5EE1, 0x9C46, 0x5EE2, 0x9C45, 0x5EE3, 0x9C41, + 0x5EE8, 0x9C47, 0x5EE9, 0x9C48, 0x5EEC, 0x9C49, 0x5EF0, 0x9C4C, 0x5EF1, 0x9C4A, 0x5EF3, 0x9C4B, 0x5EF4, 0x9C4D, 0x5EF6, 0x8984, + 0x5EF7, 0x92EC, 0x5EF8, 0x9C4E, 0x5EFA, 0x8C9A, 0x5EFB, 0x89F4, 0x5EFC, 0x9455, 0x5EFE, 0x9C4F, 0x5EFF, 0x93F9, 0x5F01, 0x95D9, + 0x5F03, 0x9C50, 0x5F04, 0x984D, 0x5F09, 0x9C51, 0x5F0A, 0x95BE, 0x5F0B, 0x9C54, 0x5F0C, 0x989F, 0x5F0D, 0x98AF, 0x5F0F, 0x8EAE, + 0x5F10, 0x93F3, 0x5F11, 0x9C55, 0x5F13, 0x8B7C, 0x5F14, 0x92A2, 0x5F15, 0x88F8, 0x5F16, 0x9C56, 0x5F17, 0x95A4, 0x5F18, 0x8D4F, + 0x5F1B, 0x926F, 0x5F1F, 0x92ED, 0x5F21, 0xFAB7, 0x5F25, 0x96ED, 0x5F26, 0x8CB7, 0x5F27, 0x8CCA, 0x5F29, 0x9C57, 0x5F2D, 0x9C58, + 0x5F2F, 0x9C5E, 0x5F31, 0x8EE3, 0x5F34, 0xFAB8, 0x5F35, 0x92A3, 0x5F37, 0x8BAD, 0x5F38, 0x9C59, 0x5F3C, 0x954A, 0x5F3E, 0x9265, + 0x5F41, 0x9C5A, 0x5F45, 0xFA67, 0x5F48, 0x9C5B, 0x5F4A, 0x8BAE, 0x5F4C, 0x9C5C, 0x5F4E, 0x9C5D, 0x5F51, 0x9C5F, 0x5F53, 0x9396, + 0x5F56, 0x9C60, 0x5F57, 0x9C61, 0x5F59, 0x9C62, 0x5F5C, 0x9C53, 0x5F5D, 0x9C52, 0x5F61, 0x9C63, 0x5F62, 0x8C60, 0x5F66, 0x9546, + 0x5F67, 0xFAB9, 0x5F69, 0x8DCA, 0x5F6A, 0x9556, 0x5F6B, 0x92A4, 0x5F6C, 0x956A, 0x5F6D, 0x9C64, 0x5F70, 0x8FB2, 0x5F71, 0x8965, + 0x5F73, 0x9C65, 0x5F77, 0x9C66, 0x5F79, 0x96F0, 0x5F7C, 0x94DE, 0x5F7F, 0x9C69, 0x5F80, 0x899D, 0x5F81, 0x90AA, 0x5F82, 0x9C68, + 0x5F83, 0x9C67, 0x5F84, 0x8C61, 0x5F85, 0x91D2, 0x5F87, 0x9C6D, 0x5F88, 0x9C6B, 0x5F8A, 0x9C6A, 0x5F8B, 0x97A5, 0x5F8C, 0x8CE3, + 0x5F90, 0x8F99, 0x5F91, 0x9C6C, 0x5F92, 0x936B, 0x5F93, 0x8F5D, 0x5F97, 0x93BE, 0x5F98, 0x9C70, 0x5F99, 0x9C6F, 0x5F9E, 0x9C6E, + 0x5FA0, 0x9C71, 0x5FA1, 0x8CE4, 0x5FA8, 0x9C72, 0x5FA9, 0x959C, 0x5FAA, 0x8F7A, 0x5FAD, 0x9C73, 0x5FAE, 0x94F7, 0x5FB3, 0x93BF, + 0x5FB4, 0x92A5, 0x5FB7, 0xFABA, 0x5FB9, 0x934F, 0x5FBC, 0x9C74, 0x5FBD, 0x8B4A, 0x5FC3, 0x9053, 0x5FC5, 0x954B, 0x5FCC, 0x8AF5, + 0x5FCD, 0x9445, 0x5FD6, 0x9C75, 0x5FD7, 0x8E75, 0x5FD8, 0x9659, 0x5FD9, 0x965A, 0x5FDC, 0x899E, 0x5FDD, 0x9C7A, 0x5FDE, 0xFABB, + 0x5FE0, 0x9289, 0x5FE4, 0x9C77, 0x5FEB, 0x89F5, 0x5FF0, 0x9CAB, 0x5FF1, 0x9C79, 0x5FF5, 0x944F, 0x5FF8, 0x9C78, 0x5FFB, 0x9C76, + 0x5FFD, 0x8D9A, 0x5FFF, 0x9C7C, 0x600E, 0x9C83, 0x600F, 0x9C89, 0x6010, 0x9C81, 0x6012, 0x937B, 0x6015, 0x9C86, 0x6016, 0x957C, + 0x6019, 0x9C80, 0x601B, 0x9C85, 0x601C, 0x97E5, 0x601D, 0x8E76, 0x6020, 0x91D3, 0x6021, 0x9C7D, 0x6025, 0x8B7D, 0x6026, 0x9C88, + 0x6027, 0x90AB, 0x6028, 0x8985, 0x6029, 0x9C82, 0x602A, 0x89F6, 0x602B, 0x9C87, 0x602F, 0x8BAF, 0x6031, 0x9C84, 0x603A, 0x9C8A, + 0x6041, 0x9C8C, 0x6042, 0x9C96, 0x6043, 0x9C94, 0x6046, 0x9C91, 0x604A, 0x9C90, 0x604B, 0x97F6, 0x604D, 0x9C92, 0x6050, 0x8BB0, + 0x6052, 0x8D50, 0x6055, 0x8F9A, 0x6059, 0x9C99, 0x605A, 0x9C8B, 0x605D, 0xFABC, 0x605F, 0x9C8F, 0x6060, 0x9C7E, 0x6062, 0x89F8, + 0x6063, 0x9C93, 0x6064, 0x9C95, 0x6065, 0x9270, 0x6068, 0x8DA6, 0x6069, 0x89B6, 0x606A, 0x9C8D, 0x606B, 0x9C98, 0x606C, 0x9C97, + 0x606D, 0x8BB1, 0x606F, 0x91A7, 0x6070, 0x8A86, 0x6075, 0x8C62, 0x6077, 0x9C8E, 0x6081, 0x9C9A, 0x6083, 0x9C9D, 0x6084, 0x9C9F, + 0x6085, 0xFABD, 0x6089, 0x8EBB, 0x608A, 0xFABE, 0x608B, 0x9CA5, 0x608C, 0x92EE, 0x608D, 0x9C9B, 0x6092, 0x9CA3, 0x6094, 0x89F7, + 0x6096, 0x9CA1, 0x6097, 0x9CA2, 0x609A, 0x9C9E, 0x609B, 0x9CA0, 0x609F, 0x8CE5, 0x60A0, 0x9749, 0x60A3, 0x8AB3, 0x60A6, 0x8978, + 0x60A7, 0x9CA4, 0x60A9, 0x9459, 0x60AA, 0x88AB, 0x60B2, 0x94DF, 0x60B3, 0x9C7B, 0x60B4, 0x9CAA, 0x60B5, 0x9CAE, 0x60B6, 0x96E3, + 0x60B8, 0x9CA7, 0x60BC, 0x9389, 0x60BD, 0x9CAC, 0x60C5, 0x8FEE, 0x60C6, 0x9CAD, 0x60C7, 0x93D5, 0x60D1, 0x9866, 0x60D3, 0x9CA9, + 0x60D5, 0xFAC0, 0x60D8, 0x9CAF, 0x60DA, 0x8D9B, 0x60DC, 0x90C9, 0x60DE, 0xFABF, 0x60DF, 0x88D2, 0x60E0, 0x9CA8, 0x60E1, 0x9CA6, + 0x60E3, 0x9179, 0x60E7, 0x9C9C, 0x60E8, 0x8E53, 0x60F0, 0x91C4, 0x60F1, 0x9CBB, 0x60F2, 0xFAC2, 0x60F3, 0x917A, 0x60F4, 0x9CB6, + 0x60F6, 0x9CB3, 0x60F7, 0x9CB4, 0x60F9, 0x8EE4, 0x60FA, 0x9CB7, 0x60FB, 0x9CBA, 0x6100, 0x9CB5, 0x6101, 0x8F44, 0x6103, 0x9CB8, + 0x6106, 0x9CB2, 0x6108, 0x96FA, 0x6109, 0x96F9, 0x610D, 0x9CBC, 0x610E, 0x9CBD, 0x610F, 0x88D3, 0x6111, 0xFAC3, 0x6115, 0x9CB1, + 0x611A, 0x8BF0, 0x611B, 0x88A4, 0x611F, 0x8AB4, 0x6120, 0xFAC1, 0x6121, 0x9CB9, 0x6127, 0x9CC1, 0x6128, 0x9CC0, 0x612C, 0x9CC5, + 0x6130, 0xFAC5, 0x6134, 0x9CC6, 0x6137, 0xFAC4, 0x613C, 0x9CC4, 0x613D, 0x9CC7, 0x613E, 0x9CBF, 0x613F, 0x9CC3, 0x6142, 0x9CC8, + 0x6144, 0x9CC9, 0x6147, 0x9CBE, 0x6148, 0x8E9C, 0x614A, 0x9CC2, 0x614B, 0x91D4, 0x614C, 0x8D51, 0x614D, 0x9CB0, 0x614E, 0x9054, + 0x6153, 0x9CD6, 0x6155, 0x95E7, 0x6158, 0x9CCC, 0x6159, 0x9CCD, 0x615A, 0x9CCE, 0x615D, 0x9CD5, 0x615F, 0x9CD4, 0x6162, 0x969D, + 0x6163, 0x8AB5, 0x6165, 0x9CD2, 0x6167, 0x8C64, 0x6168, 0x8A53, 0x616B, 0x9CCF, 0x616E, 0x97B6, 0x616F, 0x9CD1, 0x6170, 0x88D4, + 0x6171, 0x9CD3, 0x6173, 0x9CCA, 0x6174, 0x9CD0, 0x6175, 0x9CD7, 0x6176, 0x8C63, 0x6177, 0x9CCB, 0x617E, 0x977C, 0x6182, 0x974A, + 0x6187, 0x9CDA, 0x618A, 0x9CDE, 0x618E, 0x919E, 0x6190, 0x97F7, 0x6191, 0x9CDF, 0x6194, 0x9CDC, 0x6196, 0x9CD9, 0x6198, 0xFAC6, + 0x6199, 0x9CD8, 0x619A, 0x9CDD, 0x61A4, 0x95AE, 0x61A7, 0x93B2, 0x61A9, 0x8C65, 0x61AB, 0x9CE0, 0x61AC, 0x9CDB, 0x61AE, 0x9CE1, + 0x61B2, 0x8C9B, 0x61B6, 0x89AF, 0x61BA, 0x9CE9, 0x61BE, 0x8AB6, 0x61C3, 0x9CE7, 0x61C6, 0x9CE8, 0x61C7, 0x8DA7, 0x61C8, 0x9CE6, + 0x61C9, 0x9CE4, 0x61CA, 0x9CE3, 0x61CB, 0x9CEA, 0x61CC, 0x9CE2, 0x61CD, 0x9CEC, 0x61D0, 0x89F9, 0x61E3, 0x9CEE, 0x61E6, 0x9CED, + 0x61F2, 0x92A6, 0x61F4, 0x9CF1, 0x61F6, 0x9CEF, 0x61F7, 0x9CE5, 0x61F8, 0x8C9C, 0x61FA, 0x9CF0, 0x61FC, 0x9CF4, 0x61FD, 0x9CF3, + 0x61FE, 0x9CF5, 0x61FF, 0x9CF2, 0x6200, 0x9CF6, 0x6208, 0x9CF7, 0x6209, 0x9CF8, 0x620A, 0x95E8, 0x620C, 0x9CFA, 0x620D, 0x9CF9, + 0x620E, 0x8F5E, 0x6210, 0x90AC, 0x6211, 0x89E4, 0x6212, 0x89FA, 0x6213, 0xFAC7, 0x6214, 0x9CFB, 0x6216, 0x88BD, 0x621A, 0x90CA, + 0x621B, 0x9CFC, 0x621D, 0xE6C1, 0x621E, 0x9D40, 0x621F, 0x8C81, 0x6221, 0x9D41, 0x6226, 0x90ED, 0x622A, 0x9D42, 0x622E, 0x9D43, + 0x622F, 0x8B59, 0x6230, 0x9D44, 0x6232, 0x9D45, 0x6233, 0x9D46, 0x6234, 0x91D5, 0x6238, 0x8CCB, 0x623B, 0x96DF, 0x623F, 0x965B, + 0x6240, 0x8F8A, 0x6241, 0x9D47, 0x6247, 0x90EE, 0x6248, 0xE7BB, 0x6249, 0x94E0, 0x624B, 0x8EE8, 0x624D, 0x8DCB, 0x624E, 0x9D48, + 0x6253, 0x91C5, 0x6255, 0x95A5, 0x6258, 0x91EF, 0x625B, 0x9D4B, 0x625E, 0x9D49, 0x6260, 0x9D4C, 0x6263, 0x9D4A, 0x6268, 0x9D4D, + 0x626E, 0x95AF, 0x6271, 0x88B5, 0x6276, 0x957D, 0x6279, 0x94E1, 0x627C, 0x9D4E, 0x627E, 0x9D51, 0x627F, 0x8FB3, 0x6280, 0x8B5A, + 0x6282, 0x9D4F, 0x6283, 0x9D56, 0x6284, 0x8FB4, 0x6289, 0x9D50, 0x628A, 0x9463, 0x6291, 0x977D, 0x6292, 0x9D52, 0x6293, 0x9D53, + 0x6294, 0x9D57, 0x6295, 0x938A, 0x6296, 0x9D54, 0x6297, 0x8D52, 0x6298, 0x90DC, 0x629B, 0x9D65, 0x629C, 0x94B2, 0x629E, 0x91F0, + 0x62A6, 0xFAC8, 0x62AB, 0x94E2, 0x62AC, 0x9DAB, 0x62B1, 0x95F8, 0x62B5, 0x92EF, 0x62B9, 0x9695, 0x62BB, 0x9D5A, 0x62BC, 0x899F, + 0x62BD, 0x928A, 0x62C2, 0x9D63, 0x62C5, 0x9253, 0x62C6, 0x9D5D, 0x62C7, 0x9D64, 0x62C8, 0x9D5F, 0x62C9, 0x9D66, 0x62CA, 0x9D62, + 0x62CC, 0x9D61, 0x62CD, 0x948F, 0x62CF, 0x9D5B, 0x62D0, 0x89FB, 0x62D1, 0x9D59, 0x62D2, 0x8B91, 0x62D3, 0x91F1, 0x62D4, 0x9D55, + 0x62D7, 0x9D58, 0x62D8, 0x8D53, 0x62D9, 0x90D9, 0x62DB, 0x8FB5, 0x62DC, 0x9D60, 0x62DD, 0x9471, 0x62E0, 0x8B92, 0x62E1, 0x8A67, + 0x62EC, 0x8A87, 0x62ED, 0x9040, 0x62EE, 0x9D68, 0x62EF, 0x9D6D, 0x62F1, 0x9D69, 0x62F3, 0x8C9D, 0x62F5, 0x9D6E, 0x62F6, 0x8E41, + 0x62F7, 0x8D89, 0x62FE, 0x8F45, 0x62FF, 0x9D5C, 0x6301, 0x8E9D, 0x6302, 0x9D6B, 0x6307, 0x8E77, 0x6308, 0x9D6C, 0x6309, 0x88C2, + 0x630C, 0x9D67, 0x6311, 0x92A7, 0x6319, 0x8B93, 0x631F, 0x8BB2, 0x6327, 0x9D6A, 0x6328, 0x88A5, 0x632B, 0x8DC1, 0x632F, 0x9055, + 0x633A, 0x92F0, 0x633D, 0x94D2, 0x633E, 0x9D70, 0x633F, 0x917D, 0x6349, 0x91A8, 0x634C, 0x8E4A, 0x634D, 0x9D71, 0x634F, 0x9D73, + 0x6350, 0x9D6F, 0x6355, 0x95DF, 0x6357, 0x92BB, 0x635C, 0x917B, 0x6367, 0x95F9, 0x6368, 0x8ECC, 0x6369, 0x9D80, 0x636B, 0x9D7E, + 0x636E, 0x9098, 0x6372, 0x8C9E, 0x6376, 0x9D78, 0x6377, 0x8FB7, 0x637A, 0x93E6, 0x637B, 0x9450, 0x6380, 0x9D76, 0x6383, 0x917C, + 0x6388, 0x8EF6, 0x6389, 0x9D7B, 0x638C, 0x8FB6, 0x638E, 0x9D75, 0x638F, 0x9D7A, 0x6392, 0x9472, 0x6396, 0x9D74, 0x6398, 0x8C40, + 0x639B, 0x8A7C, 0x639F, 0x9D7C, 0x63A0, 0x97A9, 0x63A1, 0x8DCC, 0x63A2, 0x9254, 0x63A3, 0x9D79, 0x63A5, 0x90DA, 0x63A7, 0x8D54, + 0x63A8, 0x9084, 0x63A9, 0x8986, 0x63AA, 0x915B, 0x63AB, 0x9D77, 0x63AC, 0x8B64, 0x63B2, 0x8C66, 0x63B4, 0x92CD, 0x63B5, 0x9D7D, + 0x63BB, 0x917E, 0x63BE, 0x9D81, 0x63C0, 0x9D83, 0x63C3, 0x91B5, 0x63C4, 0x9D89, 0x63C6, 0x9D84, 0x63C9, 0x9D86, 0x63CF, 0x9560, + 0x63D0, 0x92F1, 0x63D2, 0x9D87, 0x63D6, 0x974B, 0x63DA, 0x9767, 0x63DB, 0x8AB7, 0x63E1, 0x88AC, 0x63E3, 0x9D85, 0x63E9, 0x9D82, + 0x63EE, 0x8AF6, 0x63F4, 0x8987, 0x63F5, 0xFAC9, 0x63F6, 0x9D88, 0x63FA, 0x9768, 0x6406, 0x9D8C, 0x640D, 0x91B9, 0x640F, 0x9D93, + 0x6413, 0x9D8D, 0x6416, 0x9D8A, 0x6417, 0x9D91, 0x641C, 0x9D72, 0x6426, 0x9D8E, 0x6428, 0x9D92, 0x642C, 0x94C0, 0x642D, 0x938B, + 0x6434, 0x9D8B, 0x6436, 0x9D8F, 0x643A, 0x8C67, 0x643E, 0x8DEF, 0x6442, 0x90DB, 0x644E, 0x9D97, 0x6458, 0x9345, 0x6460, 0xFACA, + 0x6467, 0x9D94, 0x6469, 0x9680, 0x646F, 0x9D95, 0x6476, 0x9D96, 0x6478, 0x96CC, 0x647A, 0x90A0, 0x6483, 0x8C82, 0x6488, 0x9D9D, + 0x6492, 0x8E54, 0x6493, 0x9D9A, 0x6495, 0x9D99, 0x649A, 0x9451, 0x649D, 0xFACB, 0x649E, 0x93B3, 0x64A4, 0x9350, 0x64A5, 0x9D9B, + 0x64A9, 0x9D9C, 0x64AB, 0x958F, 0x64AD, 0x9464, 0x64AE, 0x8E42, 0x64B0, 0x90EF, 0x64B2, 0x966F, 0x64B9, 0x8A68, 0x64BB, 0x9DA3, + 0x64BC, 0x9D9E, 0x64C1, 0x9769, 0x64C2, 0x9DA5, 0x64C5, 0x9DA1, 0x64C7, 0x9DA2, 0x64CD, 0x9180, 0x64CE, 0xFACC, 0x64D2, 0x9DA0, + 0x64D4, 0x9D5E, 0x64D8, 0x9DA4, 0x64DA, 0x9D9F, 0x64E0, 0x9DA9, 0x64E1, 0x9DAA, 0x64E2, 0x9346, 0x64E3, 0x9DAC, 0x64E6, 0x8E43, + 0x64E7, 0x9DA7, 0x64EC, 0x8B5B, 0x64EF, 0x9DAD, 0x64F1, 0x9DA6, 0x64F2, 0x9DB1, 0x64F4, 0x9DB0, 0x64F6, 0x9DAF, 0x64FA, 0x9DB2, + 0x64FD, 0x9DB4, 0x64FE, 0x8FEF, 0x6500, 0x9DB3, 0x6505, 0x9DB7, 0x6518, 0x9DB5, 0x651C, 0x9DB6, 0x651D, 0x9D90, 0x6523, 0x9DB9, + 0x6524, 0x9DB8, 0x652A, 0x9D98, 0x652B, 0x9DBA, 0x652C, 0x9DAE, 0x652F, 0x8E78, 0x6534, 0x9DBB, 0x6535, 0x9DBC, 0x6536, 0x9DBE, + 0x6537, 0x9DBD, 0x6538, 0x9DBF, 0x6539, 0x89FC, 0x653B, 0x8D55, 0x653E, 0x95FA, 0x653F, 0x90AD, 0x6545, 0x8CCC, 0x6548, 0x9DC1, + 0x654D, 0x9DC4, 0x654E, 0xFACD, 0x654F, 0x9571, 0x6551, 0x8B7E, 0x6555, 0x9DC3, 0x6556, 0x9DC2, 0x6557, 0x9473, 0x6558, 0x9DC5, + 0x6559, 0x8BB3, 0x655D, 0x9DC7, 0x655E, 0x9DC6, 0x6562, 0x8AB8, 0x6563, 0x8E55, 0x6566, 0x93D6, 0x656C, 0x8C68, 0x6570, 0x9094, + 0x6572, 0x9DC8, 0x6574, 0x90AE, 0x6575, 0x9347, 0x6577, 0x957E, 0x6578, 0x9DC9, 0x6582, 0x9DCA, 0x6583, 0x9DCB, 0x6587, 0x95B6, + 0x6588, 0x9B7C, 0x6589, 0x90C4, 0x658C, 0x956B, 0x658E, 0x8DD6, 0x6590, 0x94E3, 0x6591, 0x94C1, 0x6597, 0x936C, 0x6599, 0x97BF, + 0x659B, 0x9DCD, 0x659C, 0x8ECE, 0x659F, 0x9DCE, 0x65A1, 0x88B4, 0x65A4, 0x8BD2, 0x65A5, 0x90CB, 0x65A7, 0x9580, 0x65AB, 0x9DCF, + 0x65AC, 0x8E61, 0x65AD, 0x9266, 0x65AF, 0x8E7A, 0x65B0, 0x9056, 0x65B7, 0x9DD0, 0x65B9, 0x95FB, 0x65BC, 0x8997, 0x65BD, 0x8E7B, + 0x65C1, 0x9DD3, 0x65C3, 0x9DD1, 0x65C4, 0x9DD4, 0x65C5, 0x97B7, 0x65C6, 0x9DD2, 0x65CB, 0x90F9, 0x65CC, 0x9DD5, 0x65CF, 0x91B0, + 0x65D2, 0x9DD6, 0x65D7, 0x8AF8, 0x65D9, 0x9DD8, 0x65DB, 0x9DD7, 0x65E0, 0x9DD9, 0x65E1, 0x9DDA, 0x65E2, 0x8AF9, 0x65E5, 0x93FA, + 0x65E6, 0x9255, 0x65E7, 0x8B8C, 0x65E8, 0x8E7C, 0x65E9, 0x9181, 0x65EC, 0x8F7B, 0x65ED, 0x88AE, 0x65F1, 0x9DDB, 0x65FA, 0x89A0, + 0x65FB, 0x9DDF, 0x6600, 0xFACE, 0x6602, 0x8D56, 0x6603, 0x9DDE, 0x6606, 0x8DA9, 0x6607, 0x8FB8, 0x6609, 0xFAD1, 0x660A, 0x9DDD, + 0x660C, 0x8FB9, 0x660E, 0x96BE, 0x660F, 0x8DA8, 0x6613, 0x88D5, 0x6614, 0x90CC, 0x6615, 0xFACF, 0x661C, 0x9DE4, 0x661E, 0xFAD3, + 0x661F, 0x90AF, 0x6620, 0x8966, 0x6624, 0xFAD4, 0x6625, 0x8F74, 0x6627, 0x9686, 0x6628, 0x8DF0, 0x662D, 0x8FBA, 0x662E, 0xFAD2, + 0x662F, 0x90A5, 0x6631, 0xFA63, 0x6634, 0x9DE3, 0x6635, 0x9DE1, 0x6636, 0x9DE2, 0x663B, 0xFAD0, 0x663C, 0x928B, 0x663F, 0x9E45, + 0x6641, 0x9DE8, 0x6642, 0x8E9E, 0x6643, 0x8D57, 0x6644, 0x9DE6, 0x6649, 0x9DE7, 0x664B, 0x9057, 0x664F, 0x9DE5, 0x6652, 0x8E4E, + 0x6657, 0xFAD6, 0x6659, 0xFAD7, 0x665D, 0x9DEA, 0x665E, 0x9DE9, 0x665F, 0x9DEE, 0x6662, 0x9DEF, 0x6664, 0x9DEB, 0x6665, 0xFAD5, + 0x6666, 0x8A41, 0x6667, 0x9DEC, 0x6668, 0x9DED, 0x6669, 0x94D3, 0x666E, 0x9581, 0x666F, 0x8C69, 0x6670, 0x9DF0, 0x6673, 0xFAD9, + 0x6674, 0x90B0, 0x6676, 0x8FBB, 0x667A, 0x9271, 0x6681, 0x8BC5, 0x6683, 0x9DF1, 0x6684, 0x9DF5, 0x6687, 0x89C9, 0x6688, 0x9DF2, + 0x6689, 0x9DF4, 0x668E, 0x9DF3, 0x6691, 0x8F8B, 0x6696, 0x9267, 0x6697, 0x88C3, 0x6698, 0x9DF6, 0x6699, 0xFADA, 0x669D, 0x9DF7, + 0x66A0, 0xFADB, 0x66A2, 0x92A8, 0x66A6, 0x97EF, 0x66AB, 0x8E62, 0x66AE, 0x95E9, 0x66B2, 0xFADC, 0x66B4, 0x965C, 0x66B8, 0x9E41, + 0x66B9, 0x9DF9, 0x66BC, 0x9DFC, 0x66BE, 0x9DFB, 0x66BF, 0xFADD, 0x66C1, 0x9DF8, 0x66C4, 0x9E40, 0x66C7, 0x93DC, 0x66C9, 0x9DFA, + 0x66D6, 0x9E42, 0x66D9, 0x8F8C, 0x66DA, 0x9E43, 0x66DC, 0x976A, 0x66DD, 0x9498, 0x66E0, 0x9E44, 0x66E6, 0x9E46, 0x66E9, 0x9E47, + 0x66F0, 0x9E48, 0x66F2, 0x8BC8, 0x66F3, 0x8967, 0x66F4, 0x8D58, 0x66F5, 0x9E49, 0x66F7, 0x9E4A, 0x66F8, 0x8F91, 0x66F9, 0x9182, + 0x66FA, 0xFADE, 0x66FB, 0xFA66, 0x66FC, 0x99D6, 0x66FD, 0x915D, 0x66FE, 0x915C, 0x66FF, 0x91D6, 0x6700, 0x8DC5, 0x6703, 0x98F0, + 0x6708, 0x8C8E, 0x6709, 0x974C, 0x670B, 0x95FC, 0x670D, 0x959E, 0x670E, 0xFADF, 0x670F, 0x9E4B, 0x6714, 0x8DF1, 0x6715, 0x92BD, + 0x6716, 0x9E4C, 0x6717, 0x984E, 0x671B, 0x965D, 0x671D, 0x92A9, 0x671E, 0x9E4D, 0x671F, 0x8AFA, 0x6726, 0x9E4E, 0x6727, 0x9E4F, + 0x6728, 0x96D8, 0x672A, 0x96A2, 0x672B, 0x9696, 0x672C, 0x967B, 0x672D, 0x8E44, 0x672E, 0x9E51, 0x6731, 0x8EE9, 0x6734, 0x9670, + 0x6736, 0x9E53, 0x6737, 0x9E56, 0x6738, 0x9E55, 0x673A, 0x8AF7, 0x673D, 0x8B80, 0x673F, 0x9E52, 0x6741, 0x9E54, 0x6746, 0x9E57, + 0x6749, 0x9099, 0x674E, 0x979B, 0x674F, 0x88C7, 0x6750, 0x8DDE, 0x6751, 0x91BA, 0x6753, 0x8EDB, 0x6756, 0x8FF1, 0x6759, 0x9E5A, + 0x675C, 0x936D, 0x675E, 0x9E58, 0x675F, 0x91A9, 0x6760, 0x9E59, 0x6761, 0x8FF0, 0x6762, 0x96DB, 0x6763, 0x9E5B, 0x6764, 0x9E5C, + 0x6765, 0x9788, 0x6766, 0xFAE1, 0x676A, 0x9E61, 0x676D, 0x8D59, 0x676F, 0x9474, 0x6770, 0x9E5E, 0x6771, 0x938C, 0x6772, 0x9DDC, + 0x6773, 0x9DE0, 0x6775, 0x8B6E, 0x6777, 0x9466, 0x677C, 0x9E60, 0x677E, 0x8FBC, 0x677F, 0x94C2, 0x6785, 0x9E66, 0x6787, 0x94F8, + 0x6789, 0x9E5D, 0x678B, 0x9E63, 0x678C, 0x9E62, 0x6790, 0x90CD, 0x6795, 0x968D, 0x6797, 0x97D1, 0x679A, 0x9687, 0x679C, 0x89CA, + 0x679D, 0x8E7D, 0x67A0, 0x9867, 0x67A1, 0x9E65, 0x67A2, 0x9095, 0x67A6, 0x9E64, 0x67A9, 0x9E5F, 0x67AF, 0x8CCD, 0x67B3, 0x9E6B, + 0x67B4, 0x9E69, 0x67B6, 0x89CB, 0x67B7, 0x9E67, 0x67B8, 0x9E6D, 0x67B9, 0x9E73, 0x67BB, 0xFAE2, 0x67C0, 0xFAE4, 0x67C1, 0x91C6, + 0x67C4, 0x95BF, 0x67C6, 0x9E75, 0x67CA, 0x9541, 0x67CE, 0x9E74, 0x67CF, 0x9490, 0x67D0, 0x965E, 0x67D1, 0x8AB9, 0x67D3, 0x90F5, + 0x67D4, 0x8F5F, 0x67D8, 0x92D1, 0x67DA, 0x974D, 0x67DD, 0x9E70, 0x67DE, 0x9E6F, 0x67E2, 0x9E71, 0x67E4, 0x9E6E, 0x67E7, 0x9E76, + 0x67E9, 0x9E6C, 0x67EC, 0x9E6A, 0x67EE, 0x9E72, 0x67EF, 0x9E68, 0x67F1, 0x928C, 0x67F3, 0x96F6, 0x67F4, 0x8EC4, 0x67F5, 0x8DF2, + 0x67FB, 0x8DB8, 0x67FE, 0x968F, 0x67FF, 0x8A60, 0x6801, 0xFAE5, 0x6802, 0x92CC, 0x6803, 0x93C8, 0x6804, 0x8968, 0x6813, 0x90F0, + 0x6816, 0x90B2, 0x6817, 0x8C49, 0x681E, 0x9E78, 0x6821, 0x8D5A, 0x6822, 0x8A9C, 0x6829, 0x9E7A, 0x682A, 0x8A94, 0x682B, 0x9E81, + 0x6832, 0x9E7D, 0x6834, 0x90F1, 0x6838, 0x8A6A, 0x6839, 0x8DAA, 0x683C, 0x8A69, 0x683D, 0x8DCD, 0x6840, 0x9E7B, 0x6841, 0x8C85, + 0x6842, 0x8C6A, 0x6843, 0x938D, 0x6844, 0xFAE6, 0x6846, 0x9E79, 0x6848, 0x88C4, 0x684D, 0x9E7C, 0x684E, 0x9E7E, 0x6850, 0x8BCB, + 0x6851, 0x8C4B, 0x6852, 0xFAE3, 0x6853, 0x8ABA, 0x6854, 0x8B6A, 0x6859, 0x9E82, 0x685C, 0x8DF7, 0x685D, 0x9691, 0x685F, 0x8E56, + 0x6863, 0x9E83, 0x6867, 0x954F, 0x6874, 0x9E8F, 0x6876, 0x89B1, 0x6877, 0x9E84, 0x687E, 0x9E95, 0x687F, 0x9E85, 0x6881, 0x97C0, + 0x6883, 0x9E8C, 0x6885, 0x947E, 0x688D, 0x9E94, 0x688F, 0x9E87, 0x6893, 0x88B2, 0x6894, 0x9E89, 0x6897, 0x8D5B, 0x689B, 0x9E8B, + 0x689D, 0x9E8A, 0x689F, 0x9E86, 0x68A0, 0x9E91, 0x68A2, 0x8FBD, 0x68A6, 0x9AEB, 0x68A7, 0x8CE6, 0x68A8, 0x979C, 0x68AD, 0x9E88, + 0x68AF, 0x92F2, 0x68B0, 0x8A42, 0x68B1, 0x8DAB, 0x68B3, 0x9E80, 0x68B5, 0x9E90, 0x68B6, 0x8A81, 0x68B9, 0x9E8E, 0x68BA, 0x9E92, + 0x68BC, 0x938E, 0x68C4, 0x8AFC, 0x68C6, 0x9EB0, 0x68C8, 0xFA64, 0x68C9, 0x96C7, 0x68CA, 0x9E97, 0x68CB, 0x8AFB, 0x68CD, 0x9E9E, + 0x68CF, 0xFAE7, 0x68D2, 0x965F, 0x68D4, 0x9E9F, 0x68D5, 0x9EA1, 0x68D7, 0x9EA5, 0x68D8, 0x9E99, 0x68DA, 0x9249, 0x68DF, 0x938F, + 0x68E0, 0x9EA9, 0x68E1, 0x9E9C, 0x68E3, 0x9EA6, 0x68E7, 0x9EA0, 0x68EE, 0x9058, 0x68EF, 0x9EAA, 0x68F2, 0x90B1, 0x68F9, 0x9EA8, + 0x68FA, 0x8ABB, 0x6900, 0x986F, 0x6901, 0x9E96, 0x6904, 0x9EA4, 0x6905, 0x88D6, 0x6908, 0x9E98, 0x690B, 0x96B8, 0x690C, 0x9E9D, + 0x690D, 0x9041, 0x690E, 0x92C5, 0x690F, 0x9E93, 0x6912, 0x9EA3, 0x6919, 0x909A, 0x691A, 0x9EAD, 0x691B, 0x8A91, 0x691C, 0x8C9F, + 0x6921, 0x9EAF, 0x6922, 0x9E9A, 0x6923, 0x9EAE, 0x6925, 0x9EA7, 0x6926, 0x9E9B, 0x6928, 0x9EAB, 0x692A, 0x9EAC, 0x6930, 0x9EBD, + 0x6934, 0x93CC, 0x6936, 0x9EA2, 0x6939, 0x9EB9, 0x693D, 0x9EBB, 0x693F, 0x92D6, 0x694A, 0x976B, 0x6953, 0x9596, 0x6954, 0x9EB6, + 0x6955, 0x91C8, 0x6959, 0x9EBC, 0x695A, 0x915E, 0x695C, 0x9EB3, 0x695D, 0x9EC0, 0x695E, 0x9EBF, 0x6960, 0x93ED, 0x6961, 0x9EBE, + 0x6962, 0x93E8, 0x6968, 0xFAE9, 0x696A, 0x9EC2, 0x696B, 0x9EB5, 0x696D, 0x8BC6, 0x696E, 0x9EB8, 0x696F, 0x8F7C, 0x6973, 0x9480, + 0x6974, 0x9EBA, 0x6975, 0x8BC9, 0x6977, 0x9EB2, 0x6978, 0x9EB4, 0x6979, 0x9EB1, 0x697C, 0x984F, 0x697D, 0x8A79, 0x697E, 0x9EB7, + 0x6981, 0x9EC1, 0x6982, 0x8A54, 0x698A, 0x8DE5, 0x698E, 0x897C, 0x6991, 0x9ED2, 0x6994, 0x9850, 0x6995, 0x9ED5, 0x6998, 0xFAEB, + 0x699B, 0x9059, 0x699C, 0x9ED4, 0x69A0, 0x9ED3, 0x69A7, 0x9ED0, 0x69AE, 0x9EC4, 0x69B1, 0x9EE1, 0x69B2, 0x9EC3, 0x69B4, 0x9ED6, + 0x69BB, 0x9ECE, 0x69BE, 0x9EC9, 0x69BF, 0x9EC6, 0x69C1, 0x9EC7, 0x69C3, 0x9ECF, 0x69C7, 0xEAA0, 0x69CA, 0x9ECC, 0x69CB, 0x8D5C, + 0x69CC, 0x92C6, 0x69CD, 0x9184, 0x69CE, 0x9ECA, 0x69D0, 0x9EC5, 0x69D3, 0x9EC8, 0x69D8, 0x976C, 0x69D9, 0x968A, 0x69DD, 0x9ECD, + 0x69DE, 0x9ED7, 0x69E2, 0xFAEC, 0x69E7, 0x9EDF, 0x69E8, 0x9ED8, 0x69EB, 0x9EE5, 0x69ED, 0x9EE3, 0x69F2, 0x9EDE, 0x69F9, 0x9EDD, + 0x69FB, 0x92CE, 0x69FD, 0x9185, 0x69FF, 0x9EDB, 0x6A02, 0x9ED9, 0x6A05, 0x9EE0, 0x6A0A, 0x9EE6, 0x6A0B, 0x94F3, 0x6A0C, 0x9EEC, + 0x6A12, 0x9EE7, 0x6A13, 0x9EEA, 0x6A14, 0x9EE4, 0x6A17, 0x9294, 0x6A19, 0x9557, 0x6A1B, 0x9EDA, 0x6A1E, 0x9EE2, 0x6A1F, 0x8FBE, + 0x6A21, 0x96CD, 0x6A22, 0x9EF6, 0x6A23, 0x9EE9, 0x6A29, 0x8CA0, 0x6A2A, 0x89A1, 0x6A2B, 0x8A7E, 0x6A2E, 0x9ED1, 0x6A30, 0xFAED, + 0x6A35, 0x8FBF, 0x6A36, 0x9EEE, 0x6A38, 0x9EF5, 0x6A39, 0x8EF7, 0x6A3A, 0x8A92, 0x6A3D, 0x924D, 0x6A44, 0x9EEB, 0x6A46, 0xFAEF, + 0x6A47, 0x9EF0, 0x6A48, 0x9EF4, 0x6A4B, 0x8BB4, 0x6A58, 0x8B6B, 0x6A59, 0x9EF2, 0x6A5F, 0x8B40, 0x6A61, 0x93C9, 0x6A62, 0x9EF1, + 0x6A66, 0x9EF3, 0x6A6B, 0xFAEE, 0x6A72, 0x9EED, 0x6A73, 0xFAF0, 0x6A78, 0x9EEF, 0x6A7E, 0xFAF1, 0x6A7F, 0x8A80, 0x6A80, 0x9268, + 0x6A84, 0x9EFA, 0x6A8D, 0x9EF8, 0x6A8E, 0x8CE7, 0x6A90, 0x9EF7, 0x6A97, 0x9F40, 0x6A9C, 0x9E77, 0x6AA0, 0x9EF9, 0x6AA2, 0x9EFB, + 0x6AA3, 0x9EFC, 0x6AAA, 0x9F4B, 0x6AAC, 0x9F47, 0x6AAE, 0x9E8D, 0x6AB3, 0x9F46, 0x6AB8, 0x9F45, 0x6ABB, 0x9F42, 0x6AC1, 0x9EE8, + 0x6AC2, 0x9F44, 0x6AC3, 0x9F43, 0x6AD1, 0x9F49, 0x6AD3, 0x9845, 0x6ADA, 0x9F4C, 0x6ADB, 0x8BF9, 0x6ADE, 0x9F48, 0x6ADF, 0x9F4A, + 0x6AE2, 0xFAF2, 0x6AE4, 0xFAF3, 0x6AE8, 0x94A5, 0x6AEA, 0x9F4D, 0x6AFA, 0x9F51, 0x6AFB, 0x9F4E, 0x6B04, 0x9793, 0x6B05, 0x9F4F, + 0x6B0A, 0x9EDC, 0x6B12, 0x9F52, 0x6B16, 0x9F53, 0x6B1D, 0x8954, 0x6B1F, 0x9F55, 0x6B20, 0x8C87, 0x6B21, 0x8E9F, 0x6B23, 0x8BD3, + 0x6B27, 0x89A2, 0x6B32, 0x977E, 0x6B37, 0x9F57, 0x6B38, 0x9F56, 0x6B39, 0x9F59, 0x6B3A, 0x8B5C, 0x6B3D, 0x8BD4, 0x6B3E, 0x8ABC, + 0x6B43, 0x9F5C, 0x6B47, 0x9F5B, 0x6B49, 0x9F5D, 0x6B4C, 0x89CC, 0x6B4E, 0x9256, 0x6B50, 0x9F5E, 0x6B53, 0x8ABD, 0x6B54, 0x9F60, + 0x6B59, 0x9F5F, 0x6B5B, 0x9F61, 0x6B5F, 0x9F62, 0x6B61, 0x9F63, 0x6B62, 0x8E7E, 0x6B63, 0x90B3, 0x6B64, 0x8D9F, 0x6B66, 0x9590, + 0x6B69, 0x95E0, 0x6B6A, 0x9863, 0x6B6F, 0x8E95, 0x6B73, 0x8DCE, 0x6B74, 0x97F0, 0x6B78, 0x9F64, 0x6B79, 0x9F65, 0x6B7B, 0x8E80, + 0x6B7F, 0x9F66, 0x6B80, 0x9F67, 0x6B83, 0x9F69, 0x6B84, 0x9F68, 0x6B86, 0x9677, 0x6B89, 0x8F7D, 0x6B8A, 0x8EEA, 0x6B8B, 0x8E63, + 0x6B8D, 0x9F6A, 0x6B95, 0x9F6C, 0x6B96, 0x9042, 0x6B98, 0x9F6B, 0x6B9E, 0x9F6D, 0x6BA4, 0x9F6E, 0x6BAA, 0x9F6F, 0x6BAB, 0x9F70, + 0x6BAF, 0x9F71, 0x6BB1, 0x9F73, 0x6BB2, 0x9F72, 0x6BB3, 0x9F74, 0x6BB4, 0x89A3, 0x6BB5, 0x9269, 0x6BB7, 0x9F75, 0x6BBA, 0x8E45, + 0x6BBB, 0x8A6B, 0x6BBC, 0x9F76, 0x6BBF, 0x9361, 0x6BC0, 0x9ACA, 0x6BC5, 0x8B42, 0x6BC6, 0x9F77, 0x6BCB, 0x9F78, 0x6BCD, 0x95EA, + 0x6BCE, 0x9688, 0x6BD2, 0x93C5, 0x6BD3, 0x9F79, 0x6BD4, 0x94E4, 0x6BD6, 0xFAF4, 0x6BD8, 0x94F9, 0x6BDB, 0x96D1, 0x6BDF, 0x9F7A, + 0x6BEB, 0x9F7C, 0x6BEC, 0x9F7B, 0x6BEF, 0x9F7E, 0x6BF3, 0x9F7D, 0x6C08, 0x9F81, 0x6C0F, 0x8E81, 0x6C11, 0x96AF, 0x6C13, 0x9F82, + 0x6C14, 0x9F83, 0x6C17, 0x8B43, 0x6C1B, 0x9F84, 0x6C23, 0x9F86, 0x6C24, 0x9F85, 0x6C34, 0x9085, 0x6C37, 0x9558, 0x6C38, 0x8969, + 0x6C3E, 0x94C3, 0x6C3F, 0xFAF5, 0x6C40, 0x92F3, 0x6C41, 0x8F60, 0x6C42, 0x8B81, 0x6C4E, 0x94C4, 0x6C50, 0x8EAC, 0x6C55, 0x9F88, + 0x6C57, 0x8ABE, 0x6C5A, 0x8998, 0x6C5C, 0xFAF6, 0x6C5D, 0x93F0, 0x6C5E, 0x9F87, 0x6C5F, 0x8D5D, 0x6C60, 0x9272, 0x6C62, 0x9F89, + 0x6C68, 0x9F91, 0x6C6A, 0x9F8A, 0x6C6F, 0xFAF8, 0x6C70, 0x91BF, 0x6C72, 0x8B82, 0x6C73, 0x9F92, 0x6C7A, 0x8C88, 0x6C7D, 0x8B44, + 0x6C7E, 0x9F90, 0x6C81, 0x9F8E, 0x6C82, 0x9F8B, 0x6C83, 0x9780, 0x6C86, 0xFAF7, 0x6C88, 0x92BE, 0x6C8C, 0x93D7, 0x6C8D, 0x9F8C, + 0x6C90, 0x9F94, 0x6C92, 0x9F93, 0x6C93, 0x8C42, 0x6C96, 0x89AB, 0x6C99, 0x8DB9, 0x6C9A, 0x9F8D, 0x6C9B, 0x9F8F, 0x6CA1, 0x9676, + 0x6CA2, 0x91F2, 0x6CAB, 0x9697, 0x6CAE, 0x9F9C, 0x6CB1, 0x9F9D, 0x6CB3, 0x89CD, 0x6CB8, 0x95A6, 0x6CB9, 0x96FB, 0x6CBA, 0x9F9F, + 0x6CBB, 0x8EA1, 0x6CBC, 0x8FC0, 0x6CBD, 0x9F98, 0x6CBE, 0x9F9E, 0x6CBF, 0x8988, 0x6CC1, 0x8BB5, 0x6CC4, 0x9F95, 0x6CC5, 0x9F9A, + 0x6CC9, 0x90F2, 0x6CCA, 0x9491, 0x6CCC, 0x94E5, 0x6CD3, 0x9F97, 0x6CD5, 0x9640, 0x6CD7, 0x9F99, 0x6CD9, 0x9FA2, 0x6CDA, 0xFAF9, + 0x6CDB, 0x9FA0, 0x6CDD, 0x9F9B, 0x6CE1, 0x9641, 0x6CE2, 0x9467, 0x6CE3, 0x8B83, 0x6CE5, 0x9344, 0x6CE8, 0x928D, 0x6CEA, 0x9FA3, + 0x6CEF, 0x9FA1, 0x6CF0, 0x91D7, 0x6CF1, 0x9F96, 0x6CF3, 0x896A, 0x6D04, 0xFAFA, 0x6D0B, 0x976D, 0x6D0C, 0x9FAE, 0x6D12, 0x9FAD, + 0x6D17, 0x90F4, 0x6D19, 0x9FAA, 0x6D1B, 0x978C, 0x6D1E, 0x93B4, 0x6D1F, 0x9FA4, 0x6D25, 0x92C3, 0x6D29, 0x896B, 0x6D2A, 0x8D5E, + 0x6D2B, 0x9FA7, 0x6D32, 0x8F46, 0x6D33, 0x9FAC, 0x6D35, 0x9FAB, 0x6D36, 0x9FA6, 0x6D38, 0x9FA9, 0x6D3B, 0x8A88, 0x6D3D, 0x9FA8, + 0x6D3E, 0x9468, 0x6D41, 0x97AC, 0x6D44, 0x8FF2, 0x6D45, 0x90F3, 0x6D59, 0x9FB4, 0x6D5A, 0x9FB2, 0x6D5C, 0x956C, 0x6D63, 0x9FAF, + 0x6D64, 0x9FB1, 0x6D66, 0x8959, 0x6D69, 0x8D5F, 0x6D6A, 0x9851, 0x6D6C, 0x8A5C, 0x6D6E, 0x9582, 0x6D6F, 0xFAFC, 0x6D74, 0x9781, + 0x6D77, 0x8A43, 0x6D78, 0x905A, 0x6D79, 0x9FB3, 0x6D85, 0x9FB8, 0x6D87, 0xFAFB, 0x6D88, 0x8FC1, 0x6D8C, 0x974F, 0x6D8E, 0x9FB5, + 0x6D93, 0x9FB0, 0x6D95, 0x9FB6, 0x6D96, 0xFB40, 0x6D99, 0x97DC, 0x6D9B, 0x9393, 0x6D9C, 0x93C0, 0x6DAC, 0xFB41, 0x6DAF, 0x8A55, + 0x6DB2, 0x8974, 0x6DB5, 0x9FBC, 0x6DB8, 0x9FBF, 0x6DBC, 0x97C1, 0x6DC0, 0x9784, 0x6DC5, 0x9FC6, 0x6DC6, 0x9FC0, 0x6DC7, 0x9FBD, + 0x6DCB, 0x97D2, 0x6DCC, 0x9FC3, 0x6DCF, 0xFB42, 0x6DD1, 0x8F69, 0x6DD2, 0x9FC5, 0x6DD5, 0x9FCA, 0x6DD8, 0x9391, 0x6DD9, 0x9FC8, + 0x6DDE, 0x9FC2, 0x6DE1, 0x9257, 0x6DE4, 0x9FC9, 0x6DE6, 0x9FBE, 0x6DE8, 0x9FC4, 0x6DEA, 0x9FCB, 0x6DEB, 0x88FA, 0x6DEC, 0x9FC1, + 0x6DEE, 0x9FCC, 0x6DF1, 0x905B, 0x6DF2, 0xFB44, 0x6DF3, 0x8F7E, 0x6DF5, 0x95A3, 0x6DF7, 0x8DAC, 0x6DF8, 0xFB43, 0x6DF9, 0x9FB9, + 0x6DFA, 0x9FC7, 0x6DFB, 0x9359, 0x6DFC, 0xFB45, 0x6E05, 0x90B4, 0x6E07, 0x8A89, 0x6E08, 0x8DCF, 0x6E09, 0x8FC2, 0x6E0A, 0x9FBB, + 0x6E0B, 0x8F61, 0x6E13, 0x8C6B, 0x6E15, 0x9FBA, 0x6E19, 0x9FD0, 0x6E1A, 0x8F8D, 0x6E1B, 0x8CB8, 0x6E1D, 0x9FDF, 0x6E1F, 0x9FD9, + 0x6E20, 0x8B94, 0x6E21, 0x936E, 0x6E23, 0x9FD4, 0x6E24, 0x9FDD, 0x6E25, 0x88AD, 0x6E26, 0x8951, 0x6E27, 0xFB48, 0x6E29, 0x89B7, + 0x6E2B, 0x9FD6, 0x6E2C, 0x91AA, 0x6E2D, 0x9FCD, 0x6E2E, 0x9FCF, 0x6E2F, 0x8D60, 0x6E38, 0x9FE0, 0x6E39, 0xFB46, 0x6E3A, 0x9FDB, + 0x6E3C, 0xFB49, 0x6E3E, 0x9FD3, 0x6E43, 0x9FDA, 0x6E4A, 0x96A9, 0x6E4D, 0x9FD8, 0x6E4E, 0x9FDC, 0x6E56, 0x8CCE, 0x6E58, 0x8FC3, + 0x6E5B, 0x9258, 0x6E5C, 0xFB47, 0x6E5F, 0x9FD2, 0x6E67, 0x974E, 0x6E6B, 0x9FD5, 0x6E6E, 0x9FCE, 0x6E6F, 0x9392, 0x6E72, 0x9FD1, + 0x6E76, 0x9FD7, 0x6E7E, 0x9870, 0x6E7F, 0x8EBC, 0x6E80, 0x969E, 0x6E82, 0x9FE1, 0x6E8C, 0x94AC, 0x6E8F, 0x9FED, 0x6E90, 0x8CB9, + 0x6E96, 0x8F80, 0x6E98, 0x9FE3, 0x6E9C, 0x97AD, 0x6E9D, 0x8D61, 0x6E9F, 0x9FF0, 0x6EA2, 0x88EC, 0x6EA5, 0x9FEE, 0x6EAA, 0x9FE2, + 0x6EAF, 0x9FE8, 0x6EB2, 0x9FEA, 0x6EB6, 0x976E, 0x6EB7, 0x9FE5, 0x6EBA, 0x934D, 0x6EBD, 0x9FE7, 0x6EBF, 0xFB4A, 0x6EC2, 0x9FEF, + 0x6EC4, 0x9FE9, 0x6EC5, 0x96C5, 0x6EC9, 0x9FE4, 0x6ECB, 0x8EA0, 0x6ECC, 0x9FFC, 0x6ED1, 0x8A8A, 0x6ED3, 0x9FE6, 0x6ED4, 0x9FEB, + 0x6ED5, 0x9FEC, 0x6EDD, 0x91EA, 0x6EDE, 0x91D8, 0x6EEC, 0x9FF4, 0x6EEF, 0x9FFA, 0x6EF2, 0x9FF8, 0x6EF4, 0x9348, 0x6EF7, 0xE042, + 0x6EF8, 0x9FF5, 0x6EFE, 0x9FF6, 0x6EFF, 0x9FDE, 0x6F01, 0x8B99, 0x6F02, 0x9559, 0x6F06, 0x8EBD, 0x6F09, 0x8D97, 0x6F0F, 0x9852, + 0x6F11, 0x9FF2, 0x6F13, 0xE041, 0x6F14, 0x8989, 0x6F15, 0x9186, 0x6F20, 0x9499, 0x6F22, 0x8ABF, 0x6F23, 0x97F8, 0x6F2B, 0x969F, + 0x6F2C, 0x92D0, 0x6F31, 0x9FF9, 0x6F32, 0x9FFB, 0x6F38, 0x9151, 0x6F3E, 0xE040, 0x6F3F, 0x9FF7, 0x6F41, 0x9FF1, 0x6F45, 0x8AC1, + 0x6F54, 0x8C89, 0x6F58, 0xE04E, 0x6F5B, 0xE049, 0x6F5C, 0x90F6, 0x6F5F, 0x8A83, 0x6F64, 0x8F81, 0x6F66, 0xE052, 0x6F6D, 0xE04B, + 0x6F6E, 0x92AA, 0x6F6F, 0xE048, 0x6F70, 0x92D7, 0x6F74, 0xE06B, 0x6F78, 0xE045, 0x6F7A, 0xE044, 0x6F7C, 0xE04D, 0x6F80, 0xE047, + 0x6F81, 0xE046, 0x6F82, 0xE04C, 0x6F84, 0x909F, 0x6F86, 0xE043, 0x6F88, 0xFB4B, 0x6F8E, 0xE04F, 0x6F91, 0xE050, 0x6F97, 0x8AC0, + 0x6FA1, 0xE055, 0x6FA3, 0xE054, 0x6FA4, 0xE056, 0x6FAA, 0xE059, 0x6FB1, 0x9362, 0x6FB3, 0xE053, 0x6FB5, 0xFB4C, 0x6FB9, 0xE057, + 0x6FC0, 0x8C83, 0x6FC1, 0x91F7, 0x6FC2, 0xE051, 0x6FC3, 0x945A, 0x6FC6, 0xE058, 0x6FD4, 0xE05D, 0x6FD5, 0xE05B, 0x6FD8, 0xE05E, + 0x6FDB, 0xE061, 0x6FDF, 0xE05A, 0x6FE0, 0x8D8A, 0x6FE1, 0x9447, 0x6FE4, 0x9FB7, 0x6FEB, 0x9794, 0x6FEC, 0xE05C, 0x6FEE, 0xE060, + 0x6FEF, 0x91F3, 0x6FF1, 0xE05F, 0x6FF3, 0xE04A, 0x6FF5, 0xFB4D, 0x6FF6, 0xE889, 0x6FFA, 0xE064, 0x6FFE, 0xE068, 0x7001, 0xE066, + 0x7005, 0xFB4E, 0x7007, 0xFB4F, 0x7009, 0xE062, 0x700B, 0xE063, 0x700F, 0xE067, 0x7011, 0xE065, 0x7015, 0x956D, 0x7018, 0xE06D, + 0x701A, 0xE06A, 0x701B, 0xE069, 0x701D, 0xE06C, 0x701E, 0x93D2, 0x701F, 0xE06E, 0x7026, 0x9295, 0x7027, 0x91EB, 0x7028, 0xFB50, + 0x702C, 0x90A3, 0x7030, 0xE06F, 0x7032, 0xE071, 0x703E, 0xE070, 0x704C, 0x9FF3, 0x7051, 0xE072, 0x7058, 0x93E5, 0x7063, 0xE073, + 0x706B, 0x89CE, 0x706F, 0x9394, 0x7070, 0x8A44, 0x7078, 0x8B84, 0x707C, 0x8EDC, 0x707D, 0x8DD0, 0x7085, 0xFB51, 0x7089, 0x9846, + 0x708A, 0x9086, 0x708E, 0x898A, 0x7092, 0xE075, 0x7099, 0xE074, 0x70AB, 0xFB52, 0x70AC, 0xE078, 0x70AD, 0x9259, 0x70AE, 0xE07B, + 0x70AF, 0xE076, 0x70B3, 0xE07A, 0x70B8, 0xE079, 0x70B9, 0x935F, 0x70BA, 0x88D7, 0x70BB, 0xFA62, 0x70C8, 0x97F3, 0x70CB, 0xE07D, + 0x70CF, 0x8947, 0x70D9, 0xE080, 0x70DD, 0xE07E, 0x70DF, 0xE07C, 0x70F1, 0xE077, 0x70F9, 0x9642, 0x70FD, 0xE082, 0x7104, 0xFB54, + 0x7109, 0xE081, 0x710F, 0xFB53, 0x7114, 0x898B, 0x7119, 0xE084, 0x711A, 0x95B0, 0x711C, 0xE083, 0x7121, 0x96B3, 0x7126, 0x8FC5, + 0x7136, 0x9152, 0x713C, 0x8FC4, 0x7146, 0xFB56, 0x7147, 0xFB57, 0x7149, 0x97F9, 0x714C, 0xE08A, 0x714E, 0x90F7, 0x7155, 0xE086, + 0x7156, 0xE08B, 0x7159, 0x898C, 0x715C, 0xFB55, 0x7162, 0xE089, 0x7164, 0x9481, 0x7165, 0xE085, 0x7166, 0xE088, 0x7167, 0x8FC6, + 0x7169, 0x94CF, 0x716C, 0xE08C, 0x716E, 0x8ECF, 0x717D, 0x90F8, 0x7184, 0xE08F, 0x7188, 0xE087, 0x718A, 0x8C46, 0x718F, 0xE08D, + 0x7194, 0x976F, 0x7195, 0xE090, 0x7199, 0xEAA4, 0x719F, 0x8F6E, 0x71A8, 0xE091, 0x71AC, 0xE092, 0x71B1, 0x944D, 0x71B9, 0xE094, + 0x71BE, 0xE095, 0x71C1, 0xFB59, 0x71C3, 0x9452, 0x71C8, 0x9395, 0x71C9, 0xE097, 0x71CE, 0xE099, 0x71D0, 0x97D3, 0x71D2, 0xE096, + 0x71D4, 0xE098, 0x71D5, 0x898D, 0x71D7, 0xE093, 0x71DF, 0x9A7A, 0x71E0, 0xE09A, 0x71E5, 0x9187, 0x71E6, 0x8E57, 0x71E7, 0xE09C, + 0x71EC, 0xE09B, 0x71ED, 0x9043, 0x71EE, 0x99D7, 0x71F5, 0xE09D, 0x71F9, 0xE09F, 0x71FB, 0xE08E, 0x71FC, 0xE09E, 0x71FE, 0xFB5A, + 0x71FF, 0xE0A0, 0x7206, 0x949A, 0x720D, 0xE0A1, 0x7210, 0xE0A2, 0x721B, 0xE0A3, 0x7228, 0xE0A4, 0x722A, 0x92DC, 0x722C, 0xE0A6, + 0x722D, 0xE0A5, 0x7230, 0xE0A7, 0x7232, 0xE0A8, 0x7235, 0x8EDD, 0x7236, 0x9583, 0x723A, 0x96EA, 0x723B, 0xE0A9, 0x723C, 0xE0AA, + 0x723D, 0x9175, 0x723E, 0x8EA2, 0x723F, 0xE0AB, 0x7240, 0xE0AC, 0x7246, 0xE0AD, 0x7247, 0x95D0, 0x7248, 0x94C5, 0x724B, 0xE0AE, + 0x724C, 0x9476, 0x7252, 0x92AB, 0x7258, 0xE0AF, 0x7259, 0x89E5, 0x725B, 0x8B8D, 0x725D, 0x96C4, 0x725F, 0x96B4, 0x7261, 0x89B2, + 0x7262, 0x9853, 0x7267, 0x9671, 0x7269, 0x95A8, 0x7272, 0x90B5, 0x7274, 0xE0B0, 0x7279, 0x93C1, 0x727D, 0x8CA1, 0x727E, 0xE0B1, + 0x7280, 0x8DD2, 0x7281, 0xE0B3, 0x7282, 0xE0B2, 0x7287, 0xE0B4, 0x7292, 0xE0B5, 0x7296, 0xE0B6, 0x72A0, 0x8B5D, 0x72A2, 0xE0B7, + 0x72A7, 0xE0B8, 0x72AC, 0x8CA2, 0x72AF, 0x94C6, 0x72B1, 0xFB5B, 0x72B2, 0xE0BA, 0x72B6, 0x8FF3, 0x72B9, 0xE0B9, 0x72BE, 0xFB5C, + 0x72C2, 0x8BB6, 0x72C3, 0xE0BB, 0x72C4, 0xE0BD, 0x72C6, 0xE0BC, 0x72CE, 0xE0BE, 0x72D0, 0x8CCF, 0x72D2, 0xE0BF, 0x72D7, 0x8BE7, + 0x72D9, 0x915F, 0x72DB, 0x8D9D, 0x72E0, 0xE0C1, 0x72E1, 0xE0C2, 0x72E2, 0xE0C0, 0x72E9, 0x8EEB, 0x72EC, 0x93C6, 0x72ED, 0x8BB7, + 0x72F7, 0xE0C4, 0x72F8, 0x924B, 0x72F9, 0xE0C3, 0x72FC, 0x9854, 0x72FD, 0x9482, 0x730A, 0xE0C7, 0x7316, 0xE0C9, 0x7317, 0xE0C6, + 0x731B, 0x96D2, 0x731C, 0xE0C8, 0x731D, 0xE0CA, 0x731F, 0x97C2, 0x7324, 0xFB5D, 0x7325, 0xE0CE, 0x7329, 0xE0CD, 0x732A, 0x9296, + 0x732B, 0x944C, 0x732E, 0x8CA3, 0x732F, 0xE0CC, 0x7334, 0xE0CB, 0x7336, 0x9750, 0x7337, 0x9751, 0x733E, 0xE0CF, 0x733F, 0x898E, + 0x7344, 0x8D96, 0x7345, 0x8E82, 0x734E, 0xE0D0, 0x734F, 0xE0D1, 0x7357, 0xE0D3, 0x7363, 0x8F62, 0x7368, 0xE0D5, 0x736A, 0xE0D4, + 0x7370, 0xE0D6, 0x7372, 0x8A6C, 0x7375, 0xE0D8, 0x7377, 0xFB5F, 0x7378, 0xE0D7, 0x737A, 0xE0DA, 0x737B, 0xE0D9, 0x7384, 0x8CBA, + 0x7387, 0x97A6, 0x7389, 0x8BCA, 0x738B, 0x89A4, 0x7396, 0x8BE8, 0x73A9, 0x8ADF, 0x73B2, 0x97E6, 0x73B3, 0xE0DC, 0x73BB, 0xE0DE, + 0x73BD, 0xFB60, 0x73C0, 0xE0DF, 0x73C2, 0x89CF, 0x73C8, 0xE0DB, 0x73C9, 0xFB61, 0x73CA, 0x8E58, 0x73CD, 0x92BF, 0x73CE, 0xE0DD, + 0x73D2, 0xFB64, 0x73D6, 0xFB62, 0x73DE, 0xE0E2, 0x73E0, 0x8EEC, 0x73E3, 0xFB63, 0x73E5, 0xE0E0, 0x73EA, 0x8C5D, 0x73ED, 0x94C7, + 0x73EE, 0xE0E1, 0x73F1, 0xE0FC, 0x73F5, 0xFB66, 0x73F8, 0xE0E7, 0x73FE, 0x8CBB, 0x7403, 0x8B85, 0x7405, 0xE0E4, 0x7406, 0x979D, + 0x7407, 0xFB65, 0x7409, 0x97AE, 0x7422, 0x91F4, 0x7425, 0xE0E6, 0x7426, 0xFB67, 0x7429, 0xFB69, 0x742A, 0xFB68, 0x742E, 0xFB6A, + 0x7432, 0xE0E8, 0x7433, 0x97D4, 0x7434, 0x8BD5, 0x7435, 0x94FA, 0x7436, 0x9469, 0x743A, 0xE0E9, 0x743F, 0xE0EB, 0x7441, 0xE0EE, + 0x7455, 0xE0EA, 0x7459, 0xE0ED, 0x745A, 0x8CE8, 0x745B, 0x896C, 0x745C, 0xE0EF, 0x745E, 0x9090, 0x745F, 0xE0EC, 0x7460, 0x97DA, + 0x7462, 0xFB6B, 0x7463, 0xE0F2, 0x7464, 0xEAA2, 0x7469, 0xE0F0, 0x746A, 0xE0F3, 0x746F, 0xE0E5, 0x7470, 0xE0F1, 0x7473, 0x8DBA, + 0x7476, 0xE0F4, 0x747E, 0xE0F5, 0x7483, 0x979E, 0x7489, 0xFB6C, 0x748B, 0xE0F6, 0x749E, 0xE0F7, 0x749F, 0xFB6D, 0x74A2, 0xE0E3, + 0x74A7, 0xE0F8, 0x74B0, 0x8AC2, 0x74BD, 0x8EA3, 0x74CA, 0xE0F9, 0x74CF, 0xE0FA, 0x74D4, 0xE0FB, 0x74DC, 0x895A, 0x74E0, 0xE140, + 0x74E2, 0x955A, 0x74E3, 0xE141, 0x74E6, 0x8AA2, 0x74E7, 0xE142, 0x74E9, 0xE143, 0x74EE, 0xE144, 0x74F0, 0xE146, 0x74F1, 0xE147, + 0x74F2, 0xE145, 0x74F6, 0x9572, 0x74F7, 0xE149, 0x74F8, 0xE148, 0x7501, 0xFB6E, 0x7503, 0xE14B, 0x7504, 0xE14A, 0x7505, 0xE14C, + 0x750C, 0xE14D, 0x750D, 0xE14F, 0x750E, 0xE14E, 0x7511, 0x8D99, 0x7513, 0xE151, 0x7515, 0xE150, 0x7518, 0x8AC3, 0x751A, 0x9072, + 0x751C, 0x935B, 0x751E, 0xE152, 0x751F, 0x90B6, 0x7523, 0x8E59, 0x7525, 0x8999, 0x7526, 0xE153, 0x7528, 0x9770, 0x752B, 0x95E1, + 0x752C, 0xE154, 0x752F, 0xFAA8, 0x7530, 0x9363, 0x7531, 0x9752, 0x7532, 0x8D62, 0x7533, 0x905C, 0x7537, 0x926A, 0x7538, 0x99B2, + 0x753A, 0x92AC, 0x753B, 0x89E6, 0x753C, 0xE155, 0x7544, 0xE156, 0x7546, 0xE15B, 0x7549, 0xE159, 0x754A, 0xE158, 0x754B, 0x9DC0, + 0x754C, 0x8A45, 0x754D, 0xE157, 0x754F, 0x88D8, 0x7551, 0x94A8, 0x7554, 0x94C8, 0x7559, 0x97AF, 0x755A, 0xE15C, 0x755B, 0xE15A, + 0x755C, 0x927B, 0x755D, 0x90A4, 0x7560, 0x94A9, 0x7562, 0x954C, 0x7564, 0xE15E, 0x7565, 0x97AA, 0x7566, 0x8C6C, 0x7567, 0xE15F, + 0x7569, 0xE15D, 0x756A, 0x94D4, 0x756B, 0xE160, 0x756D, 0xE161, 0x756F, 0xFB6F, 0x7570, 0x88D9, 0x7573, 0x8FF4, 0x7574, 0xE166, + 0x7576, 0xE163, 0x7577, 0x93EB, 0x7578, 0xE162, 0x757F, 0x8B45, 0x7582, 0xE169, 0x7586, 0xE164, 0x7587, 0xE165, 0x7589, 0xE168, + 0x758A, 0xE167, 0x758B, 0x9544, 0x758E, 0x9161, 0x758F, 0x9160, 0x7591, 0x8B5E, 0x7594, 0xE16A, 0x759A, 0xE16B, 0x759D, 0xE16C, + 0x75A3, 0xE16E, 0x75A5, 0xE16D, 0x75AB, 0x8975, 0x75B1, 0xE176, 0x75B2, 0x94E6, 0x75B3, 0xE170, 0x75B5, 0xE172, 0x75B8, 0xE174, + 0x75B9, 0x905D, 0x75BC, 0xE175, 0x75BD, 0xE173, 0x75BE, 0x8EBE, 0x75C2, 0xE16F, 0x75C3, 0xE171, 0x75C5, 0x9561, 0x75C7, 0x8FC7, + 0x75CA, 0xE178, 0x75CD, 0xE177, 0x75D2, 0xE179, 0x75D4, 0x8EA4, 0x75D5, 0x8DAD, 0x75D8, 0x9397, 0x75D9, 0xE17A, 0x75DB, 0x92C9, + 0x75DE, 0xE17C, 0x75E2, 0x979F, 0x75E3, 0xE17B, 0x75E9, 0x9189, 0x75F0, 0xE182, 0x75F2, 0xE184, 0x75F3, 0xE185, 0x75F4, 0x9273, + 0x75FA, 0xE183, 0x75FC, 0xE180, 0x75FE, 0xE17D, 0x75FF, 0xE17E, 0x7601, 0xE181, 0x7609, 0xE188, 0x760B, 0xE186, 0x760D, 0xE187, + 0x761F, 0xE189, 0x7620, 0xE18B, 0x7621, 0xE18C, 0x7622, 0xE18D, 0x7624, 0xE18E, 0x7627, 0xE18A, 0x7630, 0xE190, 0x7634, 0xE18F, + 0x763B, 0xE191, 0x7642, 0x97C3, 0x7646, 0xE194, 0x7647, 0xE192, 0x7648, 0xE193, 0x764C, 0x8AE0, 0x7652, 0x96FC, 0x7656, 0x95C8, + 0x7658, 0xE196, 0x765C, 0xE195, 0x7661, 0xE197, 0x7662, 0xE198, 0x7667, 0xE19C, 0x7668, 0xE199, 0x7669, 0xE19A, 0x766A, 0xE19B, + 0x766C, 0xE19D, 0x7670, 0xE19E, 0x7672, 0xE19F, 0x7676, 0xE1A0, 0x7678, 0xE1A1, 0x767A, 0x94AD, 0x767B, 0x936F, 0x767C, 0xE1A2, + 0x767D, 0x9492, 0x767E, 0x9553, 0x7680, 0xE1A3, 0x7682, 0xFB70, 0x7683, 0xE1A4, 0x7684, 0x9349, 0x7686, 0x8A46, 0x7687, 0x8D63, + 0x7688, 0xE1A5, 0x768B, 0xE1A6, 0x768E, 0xE1A7, 0x7690, 0x8E48, 0x7693, 0xE1A9, 0x7696, 0xE1A8, 0x7699, 0xE1AA, 0x769A, 0xE1AB, + 0x769B, 0xFB73, 0x769C, 0xFB71, 0x769E, 0xFB72, 0x76A6, 0xFB74, 0x76AE, 0x94E7, 0x76B0, 0xE1AC, 0x76B4, 0xE1AD, 0x76B7, 0xEA89, + 0x76B8, 0xE1AE, 0x76B9, 0xE1AF, 0x76BA, 0xE1B0, 0x76BF, 0x8E4D, 0x76C2, 0xE1B1, 0x76C3, 0x9475, 0x76C6, 0x967E, 0x76C8, 0x896D, + 0x76CA, 0x8976, 0x76CD, 0xE1B2, 0x76D2, 0xE1B4, 0x76D6, 0xE1B3, 0x76D7, 0x9390, 0x76DB, 0x90B7, 0x76DC, 0x9F58, 0x76DE, 0xE1B5, + 0x76DF, 0x96BF, 0x76E1, 0xE1B6, 0x76E3, 0x8AC4, 0x76E4, 0x94D5, 0x76E5, 0xE1B7, 0x76E7, 0xE1B8, 0x76EA, 0xE1B9, 0x76EE, 0x96DA, + 0x76F2, 0x96D3, 0x76F4, 0x92BC, 0x76F8, 0x918A, 0x76FB, 0xE1BB, 0x76FE, 0x8F82, 0x7701, 0x8FC8, 0x7704, 0xE1BE, 0x7707, 0xE1BD, + 0x7708, 0xE1BC, 0x7709, 0x94FB, 0x770B, 0x8AC5, 0x770C, 0x8CA7, 0x771B, 0xE1C4, 0x771E, 0xE1C1, 0x771F, 0x905E, 0x7720, 0x96B0, + 0x7724, 0xE1C0, 0x7725, 0xE1C2, 0x7726, 0xE1C3, 0x7729, 0xE1BF, 0x7737, 0xE1C5, 0x7738, 0xE1C6, 0x773A, 0x92AD, 0x773C, 0x8AE1, + 0x7740, 0x9285, 0x7746, 0xFB76, 0x7747, 0xE1C7, 0x775A, 0xE1C8, 0x775B, 0xE1CB, 0x7761, 0x9087, 0x7763, 0x93C2, 0x7765, 0xE1CC, + 0x7766, 0x9672, 0x7768, 0xE1C9, 0x776B, 0xE1CA, 0x7779, 0xE1CF, 0x777E, 0xE1CE, 0x777F, 0xE1CD, 0x778B, 0xE1D1, 0x778E, 0xE1D0, + 0x7791, 0xE1D2, 0x779E, 0xE1D4, 0x77A0, 0xE1D3, 0x77A5, 0x95CB, 0x77AC, 0x8F75, 0x77AD, 0x97C4, 0x77B0, 0xE1D5, 0x77B3, 0x93B5, + 0x77B6, 0xE1D6, 0x77B9, 0xE1D7, 0x77BB, 0xE1DB, 0x77BC, 0xE1D9, 0x77BD, 0xE1DA, 0x77BF, 0xE1D8, 0x77C7, 0xE1DC, 0x77CD, 0xE1DD, + 0x77D7, 0xE1DE, 0x77DA, 0xE1DF, 0x77DB, 0x96B5, 0x77DC, 0xE1E0, 0x77E2, 0x96EE, 0x77E3, 0xE1E1, 0x77E5, 0x926D, 0x77E7, 0x948A, + 0x77E9, 0x8BE9, 0x77ED, 0x925A, 0x77EE, 0xE1E2, 0x77EF, 0x8BB8, 0x77F3, 0x90CE, 0x77FC, 0xE1E3, 0x7802, 0x8DBB, 0x780C, 0xE1E4, + 0x7812, 0xE1E5, 0x7814, 0x8CA4, 0x7815, 0x8DD3, 0x7820, 0xE1E7, 0x7821, 0xFB78, 0x7825, 0x9375, 0x7826, 0x8DD4, 0x7827, 0x8B6D, + 0x7832, 0x9643, 0x7834, 0x946A, 0x783A, 0x9376, 0x783F, 0x8D7B, 0x7845, 0xE1E9, 0x784E, 0xFB79, 0x785D, 0x8FC9, 0x7864, 0xFB7A, + 0x786B, 0x97B0, 0x786C, 0x8D64, 0x786F, 0x8CA5, 0x7872, 0x94A1, 0x7874, 0xE1EB, 0x787A, 0xFB7B, 0x787C, 0xE1ED, 0x7881, 0x8CE9, + 0x7886, 0xE1EC, 0x7887, 0x92F4, 0x788C, 0xE1EF, 0x788D, 0x8A56, 0x788E, 0xE1EA, 0x7891, 0x94E8, 0x7893, 0x894F, 0x7895, 0x8DEA, + 0x7897, 0x9871, 0x789A, 0xE1EE, 0x78A3, 0xE1F0, 0x78A7, 0x95C9, 0x78A9, 0x90D7, 0x78AA, 0xE1F2, 0x78AF, 0xE1F3, 0x78B5, 0xE1F1, + 0x78BA, 0x8A6D, 0x78BC, 0xE1F9, 0x78BE, 0xE1F8, 0x78C1, 0x8EA5, 0x78C5, 0xE1FA, 0x78C6, 0xE1F5, 0x78CA, 0xE1FB, 0x78CB, 0xE1F6, + 0x78D0, 0x94D6, 0x78D1, 0xE1F4, 0x78D4, 0xE1F7, 0x78DA, 0xE241, 0x78E7, 0xE240, 0x78E8, 0x9681, 0x78EC, 0xE1FC, 0x78EF, 0x88E9, + 0x78F4, 0xE243, 0x78FD, 0xE242, 0x7901, 0x8FCA, 0x7907, 0xE244, 0x790E, 0x9162, 0x7911, 0xE246, 0x7912, 0xE245, 0x7919, 0xE247, + 0x7926, 0xE1E6, 0x792A, 0xE1E8, 0x792B, 0xE249, 0x792C, 0xE248, 0x7930, 0xFB7C, 0x793A, 0x8EA6, 0x793C, 0x97E7, 0x793E, 0x8ED0, + 0x7940, 0xE24A, 0x7941, 0x8C56, 0x7947, 0x8B5F, 0x7948, 0x8B46, 0x7949, 0x8E83, 0x7950, 0x9753, 0x7953, 0xE250, 0x7955, 0xE24F, + 0x7956, 0x9163, 0x7957, 0xE24C, 0x795A, 0xE24E, 0x795D, 0x8F6A, 0x795E, 0x905F, 0x795F, 0xE24D, 0x7960, 0xE24B, 0x7962, 0x9449, + 0x7965, 0x8FCB, 0x7968, 0x955B, 0x796D, 0x8DD5, 0x7977, 0x9398, 0x797A, 0xE251, 0x797F, 0xE252, 0x7980, 0xE268, 0x7981, 0x8BD6, + 0x7984, 0x985C, 0x7985, 0x9154, 0x798A, 0xE253, 0x798D, 0x89D0, 0x798E, 0x92F5, 0x798F, 0x959F, 0x7994, 0xFB81, 0x799B, 0xFB83, + 0x799D, 0xE254, 0x79A6, 0x8B9A, 0x79A7, 0xE255, 0x79AA, 0xE257, 0x79AE, 0xE258, 0x79B0, 0x9448, 0x79B3, 0xE259, 0x79B9, 0xE25A, + 0x79BA, 0xE25B, 0x79BD, 0x8BD7, 0x79BE, 0x89D1, 0x79BF, 0x93C3, 0x79C0, 0x8F47, 0x79C1, 0x8E84, 0x79C9, 0xE25C, 0x79CB, 0x8F48, + 0x79D1, 0x89C8, 0x79D2, 0x9562, 0x79D5, 0xE25D, 0x79D8, 0x94E9, 0x79DF, 0x9164, 0x79E1, 0xE260, 0x79E3, 0xE261, 0x79E4, 0x9489, + 0x79E6, 0x9060, 0x79E7, 0xE25E, 0x79E9, 0x9281, 0x79EC, 0xE25F, 0x79F0, 0x8FCC, 0x79FB, 0x88DA, 0x7A00, 0x8B48, 0x7A08, 0xE262, + 0x7A0B, 0x92F6, 0x7A0D, 0xE263, 0x7A0E, 0x90C5, 0x7A14, 0x96AB, 0x7A17, 0x9542, 0x7A18, 0xE264, 0x7A19, 0xE265, 0x7A1A, 0x9274, + 0x7A1C, 0x97C5, 0x7A1F, 0xE267, 0x7A20, 0xE266, 0x7A2E, 0x8EED, 0x7A31, 0xE269, 0x7A32, 0x88EE, 0x7A37, 0xE26C, 0x7A3B, 0xE26A, + 0x7A3C, 0x89D2, 0x7A3D, 0x8C6D, 0x7A3E, 0xE26B, 0x7A3F, 0x8D65, 0x7A40, 0x8D92, 0x7A42, 0x95E4, 0x7A43, 0xE26D, 0x7A46, 0x9673, + 0x7A49, 0xE26F, 0x7A4D, 0x90CF, 0x7A4E, 0x896E, 0x7A4F, 0x89B8, 0x7A50, 0x88AA, 0x7A57, 0xE26E, 0x7A61, 0xE270, 0x7A62, 0xE271, + 0x7A63, 0x8FF5, 0x7A69, 0xE272, 0x7A6B, 0x8A6E, 0x7A70, 0xE274, 0x7A74, 0x8C8A, 0x7A76, 0x8B86, 0x7A79, 0xE275, 0x7A7A, 0x8BF3, + 0x7A7D, 0xE276, 0x7A7F, 0x90FA, 0x7A81, 0x93CB, 0x7A83, 0x90DE, 0x7A84, 0x8DF3, 0x7A88, 0xE277, 0x7A92, 0x9282, 0x7A93, 0x918B, + 0x7A95, 0xE279, 0x7A96, 0xE27B, 0x7A97, 0xE278, 0x7A98, 0xE27A, 0x7A9F, 0x8C41, 0x7AA9, 0xE27C, 0x7AAA, 0x8C45, 0x7AAE, 0x8B87, + 0x7AAF, 0x9771, 0x7AB0, 0xE27E, 0x7AB6, 0xE280, 0x7ABA, 0x894D, 0x7ABF, 0xE283, 0x7AC3, 0x8A96, 0x7AC4, 0xE282, 0x7AC5, 0xE281, + 0x7AC7, 0xE285, 0x7AC8, 0xE27D, 0x7ACA, 0xE286, 0x7ACB, 0x97A7, 0x7ACD, 0xE287, 0x7ACF, 0xE288, 0x7AD1, 0xFB84, 0x7AD2, 0x9AF2, + 0x7AD3, 0xE28A, 0x7AD5, 0xE289, 0x7AD9, 0xE28B, 0x7ADA, 0xE28C, 0x7ADC, 0x97B3, 0x7ADD, 0xE28D, 0x7ADF, 0xE8ED, 0x7AE0, 0x8FCD, + 0x7AE1, 0xE28E, 0x7AE2, 0xE28F, 0x7AE3, 0x8F76, 0x7AE5, 0x93B6, 0x7AE6, 0xE290, 0x7AE7, 0xFB85, 0x7AEA, 0x9247, 0x7AEB, 0xFB87, + 0x7AED, 0xE291, 0x7AEF, 0x925B, 0x7AF0, 0xE292, 0x7AF6, 0x8BA3, 0x7AF8, 0x995E, 0x7AF9, 0x927C, 0x7AFA, 0x8EB1, 0x7AFF, 0x8AC6, + 0x7B02, 0xE293, 0x7B04, 0xE2A0, 0x7B06, 0xE296, 0x7B08, 0x8B88, 0x7B0A, 0xE295, 0x7B0B, 0xE2A2, 0x7B0F, 0xE294, 0x7B11, 0x8FCE, + 0x7B18, 0xE298, 0x7B19, 0xE299, 0x7B1B, 0x934A, 0x7B1E, 0xE29A, 0x7B20, 0x8A7D, 0x7B25, 0x9079, 0x7B26, 0x9584, 0x7B28, 0xE29C, + 0x7B2C, 0x91E6, 0x7B33, 0xE297, 0x7B35, 0xE29B, 0x7B36, 0xE29D, 0x7B39, 0x8DF9, 0x7B45, 0xE2A4, 0x7B46, 0x954D, 0x7B48, 0x94A4, + 0x7B49, 0x9399, 0x7B4B, 0x8BD8, 0x7B4C, 0xE2A3, 0x7B4D, 0xE2A1, 0x7B4F, 0x94B3, 0x7B50, 0xE29E, 0x7B51, 0x927D, 0x7B52, 0x939B, + 0x7B54, 0x939A, 0x7B56, 0x8DF4, 0x7B5D, 0xE2B6, 0x7B65, 0xE2A6, 0x7B67, 0xE2A8, 0x7B6C, 0xE2AB, 0x7B6E, 0xE2AC, 0x7B70, 0xE2A9, + 0x7B71, 0xE2AA, 0x7B74, 0xE2A7, 0x7B75, 0xE2A5, 0x7B7A, 0xE29F, 0x7B86, 0x95CD, 0x7B87, 0x89D3, 0x7B8B, 0xE2B3, 0x7B8D, 0xE2B0, + 0x7B8F, 0xE2B5, 0x7B92, 0xE2B4, 0x7B94, 0x9493, 0x7B95, 0x96A5, 0x7B97, 0x8E5A, 0x7B98, 0xE2AE, 0x7B99, 0xE2B7, 0x7B9A, 0xE2B2, + 0x7B9C, 0xE2B1, 0x7B9D, 0xE2AD, 0x7B9E, 0xFB88, 0x7B9F, 0xE2AF, 0x7BA1, 0x8AC7, 0x7BAA, 0x925C, 0x7BAD, 0x90FB, 0x7BB1, 0x94A0, + 0x7BB4, 0xE2BC, 0x7BB8, 0x94A2, 0x7BC0, 0x90DF, 0x7BC1, 0xE2B9, 0x7BC4, 0x94CD, 0x7BC6, 0xE2BD, 0x7BC7, 0x95D1, 0x7BC9, 0x927A, + 0x7BCB, 0xE2B8, 0x7BCC, 0xE2BA, 0x7BCF, 0xE2BB, 0x7BDD, 0xE2BE, 0x7BE0, 0x8EC2, 0x7BE4, 0x93C4, 0x7BE5, 0xE2C3, 0x7BE6, 0xE2C2, + 0x7BE9, 0xE2BF, 0x7BED, 0x9855, 0x7BF3, 0xE2C8, 0x7BF6, 0xE2CC, 0x7BF7, 0xE2C9, 0x7C00, 0xE2C5, 0x7C07, 0xE2C6, 0x7C0D, 0xE2CB, + 0x7C11, 0xE2C0, 0x7C12, 0x99D3, 0x7C13, 0xE2C7, 0x7C14, 0xE2C1, 0x7C17, 0xE2CA, 0x7C1F, 0xE2D0, 0x7C21, 0x8AC8, 0x7C23, 0xE2CD, + 0x7C27, 0xE2CE, 0x7C2A, 0xE2CF, 0x7C2B, 0xE2D2, 0x7C37, 0xE2D1, 0x7C38, 0x94F4, 0x7C3D, 0xE2D3, 0x7C3E, 0x97FA, 0x7C3F, 0x95EB, + 0x7C40, 0xE2D8, 0x7C43, 0xE2D5, 0x7C4C, 0xE2D4, 0x7C4D, 0x90D0, 0x7C4F, 0xE2D7, 0x7C50, 0xE2D9, 0x7C54, 0xE2D6, 0x7C56, 0xE2DD, + 0x7C58, 0xE2DA, 0x7C5F, 0xE2DB, 0x7C60, 0xE2C4, 0x7C64, 0xE2DC, 0x7C65, 0xE2DE, 0x7C6C, 0xE2DF, 0x7C73, 0x95C4, 0x7C75, 0xE2E0, + 0x7C7E, 0x96E0, 0x7C81, 0x8BCC, 0x7C82, 0x8C48, 0x7C83, 0xE2E1, 0x7C89, 0x95B2, 0x7C8B, 0x9088, 0x7C8D, 0x96AE, 0x7C90, 0xE2E2, + 0x7C92, 0x97B1, 0x7C95, 0x9494, 0x7C97, 0x9165, 0x7C98, 0x9453, 0x7C9B, 0x8F6C, 0x7C9F, 0x88BE, 0x7CA1, 0xE2E7, 0x7CA2, 0xE2E5, + 0x7CA4, 0xE2E3, 0x7CA5, 0x8A9F, 0x7CA7, 0x8FCF, 0x7CA8, 0xE2E8, 0x7CAB, 0xE2E6, 0x7CAD, 0xE2E4, 0x7CAE, 0xE2EC, 0x7CB1, 0xE2EB, + 0x7CB2, 0xE2EA, 0x7CB3, 0xE2E9, 0x7CB9, 0xE2ED, 0x7CBD, 0xE2EE, 0x7CBE, 0x90B8, 0x7CC0, 0xE2EF, 0x7CC2, 0xE2F1, 0x7CC5, 0xE2F0, + 0x7CCA, 0x8CD0, 0x7CCE, 0x9157, 0x7CD2, 0xE2F3, 0x7CD6, 0x939C, 0x7CD8, 0xE2F2, 0x7CDC, 0xE2F4, 0x7CDE, 0x95B3, 0x7CDF, 0x918C, + 0x7CE0, 0x8D66, 0x7CE2, 0xE2F5, 0x7CE7, 0x97C6, 0x7CEF, 0xE2F7, 0x7CF2, 0xE2F8, 0x7CF4, 0xE2F9, 0x7CF6, 0xE2FA, 0x7CF8, 0x8E85, + 0x7CFA, 0xE2FB, 0x7CFB, 0x8C6E, 0x7CFE, 0x8B8A, 0x7D00, 0x8B49, 0x7D02, 0xE340, 0x7D04, 0x96F1, 0x7D05, 0x8D67, 0x7D06, 0xE2FC, + 0x7D0A, 0xE343, 0x7D0B, 0x96E4, 0x7D0D, 0x945B, 0x7D10, 0x9552, 0x7D14, 0x8F83, 0x7D15, 0xE342, 0x7D17, 0x8ED1, 0x7D18, 0x8D68, + 0x7D19, 0x8E86, 0x7D1A, 0x8B89, 0x7D1B, 0x95B4, 0x7D1C, 0xE341, 0x7D20, 0x9166, 0x7D21, 0x9661, 0x7D22, 0x8DF5, 0x7D2B, 0x8E87, + 0x7D2C, 0x92DB, 0x7D2E, 0xE346, 0x7D2F, 0x97DD, 0x7D30, 0x8DD7, 0x7D32, 0xE347, 0x7D33, 0x9061, 0x7D35, 0xE349, 0x7D39, 0x8FD0, + 0x7D3A, 0x8DAE, 0x7D3F, 0xE348, 0x7D42, 0x8F49, 0x7D43, 0x8CBC, 0x7D44, 0x9167, 0x7D45, 0xE344, 0x7D46, 0xE34A, 0x7D48, 0xFB8A, + 0x7D4B, 0xE345, 0x7D4C, 0x8C6F, 0x7D4E, 0xE34D, 0x7D4F, 0xE351, 0x7D50, 0x8C8B, 0x7D56, 0xE34C, 0x7D5B, 0xE355, 0x7D5C, 0xFB8B, + 0x7D5E, 0x8D69, 0x7D61, 0x978D, 0x7D62, 0x88BA, 0x7D63, 0xE352, 0x7D66, 0x8B8B, 0x7D68, 0xE34F, 0x7D6E, 0xE350, 0x7D71, 0x939D, + 0x7D72, 0xE34E, 0x7D73, 0xE34B, 0x7D75, 0x8A47, 0x7D76, 0x90E2, 0x7D79, 0x8CA6, 0x7D7D, 0xE357, 0x7D89, 0xE354, 0x7D8F, 0xE356, + 0x7D93, 0xE353, 0x7D99, 0x8C70, 0x7D9A, 0x91B1, 0x7D9B, 0xE358, 0x7D9C, 0x918E, 0x7D9F, 0xE365, 0x7DA0, 0xFB8D, 0x7DA2, 0xE361, + 0x7DA3, 0xE35B, 0x7DAB, 0xE35F, 0x7DAC, 0x8EF8, 0x7DAD, 0x88DB, 0x7DAE, 0xE35A, 0x7DAF, 0xE362, 0x7DB0, 0xE366, 0x7DB1, 0x8D6A, + 0x7DB2, 0x96D4, 0x7DB4, 0x92D4, 0x7DB5, 0xE35C, 0x7DB7, 0xFB8C, 0x7DB8, 0xE364, 0x7DBA, 0xE359, 0x7DBB, 0x925D, 0x7DBD, 0xE35E, + 0x7DBE, 0x88BB, 0x7DBF, 0x96C8, 0x7DC7, 0xE35D, 0x7DCA, 0x8BD9, 0x7DCB, 0x94EA, 0x7DCF, 0x918D, 0x7DD1, 0x97CE, 0x7DD2, 0x8F8F, + 0x7DD5, 0xE38E, 0x7DD6, 0xFB8E, 0x7DD8, 0xE367, 0x7DDA, 0x90FC, 0x7DDC, 0xE363, 0x7DDD, 0xE368, 0x7DDE, 0xE36A, 0x7DE0, 0x92F7, + 0x7DE1, 0xE36D, 0x7DE4, 0xE369, 0x7DE8, 0x95D2, 0x7DE9, 0x8AC9, 0x7DEC, 0x96C9, 0x7DEF, 0x88DC, 0x7DF2, 0xE36C, 0x7DF4, 0x97FB, + 0x7DFB, 0xE36B, 0x7E01, 0x898F, 0x7E04, 0x93EA, 0x7E05, 0xE36E, 0x7E09, 0xE375, 0x7E0A, 0xE36F, 0x7E0B, 0xE376, 0x7E12, 0xE372, + 0x7E1B, 0x949B, 0x7E1E, 0x8EC8, 0x7E1F, 0xE374, 0x7E21, 0xE371, 0x7E22, 0xE377, 0x7E23, 0xE370, 0x7E26, 0x8F63, 0x7E2B, 0x9644, + 0x7E2E, 0x8F6B, 0x7E31, 0xE373, 0x7E32, 0xE380, 0x7E35, 0xE37B, 0x7E37, 0xE37E, 0x7E39, 0xE37C, 0x7E3A, 0xE381, 0x7E3B, 0xE37A, + 0x7E3D, 0xE360, 0x7E3E, 0x90D1, 0x7E41, 0x94C9, 0x7E43, 0xE37D, 0x7E46, 0xE378, 0x7E4A, 0x9140, 0x7E4B, 0x8C71, 0x7E4D, 0x8F4A, + 0x7E52, 0xFB8F, 0x7E54, 0x9044, 0x7E55, 0x9155, 0x7E56, 0xE384, 0x7E59, 0xE386, 0x7E5A, 0xE387, 0x7E5D, 0xE383, 0x7E5E, 0xE385, + 0x7E66, 0xE379, 0x7E67, 0xE382, 0x7E69, 0xE38A, 0x7E6A, 0xE389, 0x7E6D, 0x969A, 0x7E70, 0x8C4A, 0x7E79, 0xE388, 0x7E7B, 0xE38C, + 0x7E7C, 0xE38B, 0x7E7D, 0xE38F, 0x7E7F, 0xE391, 0x7E82, 0x8E5B, 0x7E83, 0xE38D, 0x7E88, 0xE392, 0x7E89, 0xE393, 0x7E8A, 0xFA5C, + 0x7E8C, 0xE394, 0x7E8E, 0xE39A, 0x7E8F, 0x935A, 0x7E90, 0xE396, 0x7E92, 0xE395, 0x7E93, 0xE397, 0x7E94, 0xE398, 0x7E96, 0xE399, + 0x7E9B, 0xE39B, 0x7E9C, 0xE39C, 0x7F36, 0x8ACA, 0x7F38, 0xE39D, 0x7F3A, 0xE39E, 0x7F45, 0xE39F, 0x7F47, 0xFB90, 0x7F4C, 0xE3A0, + 0x7F4D, 0xE3A1, 0x7F4E, 0xE3A2, 0x7F50, 0xE3A3, 0x7F51, 0xE3A4, 0x7F54, 0xE3A6, 0x7F55, 0xE3A5, 0x7F58, 0xE3A7, 0x7F5F, 0xE3A8, + 0x7F60, 0xE3A9, 0x7F67, 0xE3AC, 0x7F68, 0xE3AA, 0x7F69, 0xE3AB, 0x7F6A, 0x8DDF, 0x7F6B, 0x8C72, 0x7F6E, 0x9275, 0x7F70, 0x94B1, + 0x7F72, 0x8F90, 0x7F75, 0x946C, 0x7F77, 0x94EB, 0x7F78, 0xE3AD, 0x7F79, 0x9CEB, 0x7F82, 0xE3AE, 0x7F83, 0xE3B0, 0x7F85, 0x9785, + 0x7F86, 0xE3AF, 0x7F87, 0xE3B2, 0x7F88, 0xE3B1, 0x7F8A, 0x9772, 0x7F8C, 0xE3B3, 0x7F8E, 0x94FC, 0x7F94, 0xE3B4, 0x7F9A, 0xE3B7, + 0x7F9D, 0xE3B6, 0x7F9E, 0xE3B5, 0x7FA1, 0xFB91, 0x7FA3, 0xE3B8, 0x7FA4, 0x8C51, 0x7FA8, 0x9141, 0x7FA9, 0x8B60, 0x7FAE, 0xE3BC, + 0x7FAF, 0xE3B9, 0x7FB2, 0xE3BA, 0x7FB6, 0xE3BD, 0x7FB8, 0xE3BE, 0x7FB9, 0xE3BB, 0x7FBD, 0x8948, 0x7FC1, 0x89A5, 0x7FC5, 0xE3C0, + 0x7FC6, 0xE3C1, 0x7FCA, 0xE3C2, 0x7FCC, 0x9782, 0x7FD2, 0x8F4B, 0x7FD4, 0xE3C4, 0x7FD5, 0xE3C3, 0x7FE0, 0x9089, 0x7FE1, 0xE3C5, + 0x7FE6, 0xE3C6, 0x7FE9, 0xE3C7, 0x7FEB, 0x8AE3, 0x7FF0, 0x8ACB, 0x7FF3, 0xE3C8, 0x7FF9, 0xE3C9, 0x7FFB, 0x967C, 0x7FFC, 0x9783, + 0x8000, 0x9773, 0x8001, 0x9856, 0x8003, 0x8D6C, 0x8004, 0xE3CC, 0x8005, 0x8ED2, 0x8006, 0xE3CB, 0x800B, 0xE3CD, 0x800C, 0x8EA7, + 0x8010, 0x91CF, 0x8012, 0xE3CE, 0x8015, 0x8D6B, 0x8017, 0x96D5, 0x8018, 0xE3CF, 0x8019, 0xE3D0, 0x801C, 0xE3D1, 0x8021, 0xE3D2, + 0x8028, 0xE3D3, 0x8033, 0x8EA8, 0x8036, 0x96EB, 0x803B, 0xE3D5, 0x803D, 0x925E, 0x803F, 0xE3D4, 0x8046, 0xE3D7, 0x804A, 0xE3D6, + 0x8052, 0xE3D8, 0x8056, 0x90B9, 0x8058, 0xE3D9, 0x805A, 0xE3DA, 0x805E, 0x95B7, 0x805F, 0xE3DB, 0x8061, 0x918F, 0x8062, 0xE3DC, + 0x8068, 0xE3DD, 0x806F, 0x97FC, 0x8070, 0xE3E0, 0x8072, 0xE3DF, 0x8073, 0xE3DE, 0x8074, 0x92AE, 0x8076, 0xE3E1, 0x8077, 0x9045, + 0x8079, 0xE3E2, 0x807D, 0xE3E3, 0x807E, 0x9857, 0x807F, 0xE3E4, 0x8084, 0xE3E5, 0x8085, 0xE3E7, 0x8086, 0xE3E6, 0x8087, 0x94A3, + 0x8089, 0x93F7, 0x808B, 0x985D, 0x808C, 0x94A7, 0x8093, 0xE3E9, 0x8096, 0x8FD1, 0x8098, 0x9549, 0x809A, 0xE3EA, 0x809B, 0xE3E8, + 0x809D, 0x8ACC, 0x80A1, 0x8CD2, 0x80A2, 0x8E88, 0x80A5, 0x94EC, 0x80A9, 0x8CA8, 0x80AA, 0x9662, 0x80AC, 0xE3ED, 0x80AD, 0xE3EB, + 0x80AF, 0x8D6D, 0x80B1, 0x8D6E, 0x80B2, 0x88E7, 0x80B4, 0x8DE6, 0x80BA, 0x9478, 0x80C3, 0x88DD, 0x80C4, 0xE3F2, 0x80C6, 0x925F, + 0x80CC, 0x9477, 0x80CE, 0x91D9, 0x80D6, 0xE3F4, 0x80D9, 0xE3F0, 0x80DA, 0xE3F3, 0x80DB, 0xE3EE, 0x80DD, 0xE3F1, 0x80DE, 0x9645, + 0x80E1, 0x8CD3, 0x80E4, 0x88FB, 0x80E5, 0xE3EF, 0x80EF, 0xE3F6, 0x80F1, 0xE3F7, 0x80F4, 0x93B7, 0x80F8, 0x8BB9, 0x80FC, 0xE445, + 0x80FD, 0x945C, 0x8102, 0x8E89, 0x8105, 0x8BBA, 0x8106, 0x90C6, 0x8107, 0x9865, 0x8108, 0x96AC, 0x8109, 0xE3F5, 0x810A, 0x90D2, + 0x811A, 0x8B72, 0x811B, 0xE3F8, 0x8123, 0xE3FA, 0x8129, 0xE3F9, 0x812F, 0xE3FB, 0x8131, 0x9245, 0x8133, 0x945D, 0x8139, 0x92AF, + 0x813E, 0xE442, 0x8146, 0xE441, 0x814B, 0xE3FC, 0x814E, 0x9074, 0x8150, 0x9585, 0x8151, 0xE444, 0x8153, 0xE443, 0x8154, 0x8D6F, + 0x8155, 0x9872, 0x815F, 0xE454, 0x8165, 0xE448, 0x8166, 0xE449, 0x816B, 0x8EEE, 0x816E, 0xE447, 0x8170, 0x8D98, 0x8171, 0xE446, + 0x8174, 0xE44A, 0x8178, 0x92B0, 0x8179, 0x95A0, 0x817A, 0x9142, 0x817F, 0x91DA, 0x8180, 0xE44E, 0x8182, 0xE44F, 0x8183, 0xE44B, + 0x8188, 0xE44C, 0x818A, 0xE44D, 0x818F, 0x8D70, 0x8193, 0xE455, 0x8195, 0xE451, 0x819A, 0x9586, 0x819C, 0x968C, 0x819D, 0x9547, + 0x81A0, 0xE450, 0x81A3, 0xE453, 0x81A4, 0xE452, 0x81A8, 0x9663, 0x81A9, 0xE456, 0x81B0, 0xE457, 0x81B3, 0x9156, 0x81B5, 0xE458, + 0x81B8, 0xE45A, 0x81BA, 0xE45E, 0x81BD, 0xE45B, 0x81BE, 0xE459, 0x81BF, 0x945E, 0x81C0, 0xE45C, 0x81C2, 0xE45D, 0x81C6, 0x89B0, + 0x81C8, 0xE464, 0x81C9, 0xE45F, 0x81CD, 0xE460, 0x81D1, 0xE461, 0x81D3, 0x919F, 0x81D8, 0xE463, 0x81D9, 0xE462, 0x81DA, 0xE465, + 0x81DF, 0xE466, 0x81E0, 0xE467, 0x81E3, 0x9062, 0x81E5, 0x89E7, 0x81E7, 0xE468, 0x81E8, 0x97D5, 0x81EA, 0x8EA9, 0x81ED, 0x8F4C, + 0x81F3, 0x8E8A, 0x81F4, 0x9276, 0x81FA, 0xE469, 0x81FB, 0xE46A, 0x81FC, 0x8950, 0x81FE, 0xE46B, 0x8201, 0xE46C, 0x8202, 0xE46D, + 0x8205, 0xE46E, 0x8207, 0xE46F, 0x8208, 0x8BBB, 0x8209, 0x9DA8, 0x820A, 0xE470, 0x820C, 0x90E3, 0x820D, 0xE471, 0x820E, 0x8EC9, + 0x8210, 0xE472, 0x8212, 0x98AE, 0x8216, 0xE473, 0x8217, 0x95DC, 0x8218, 0x8ADA, 0x821B, 0x9143, 0x821C, 0x8F77, 0x821E, 0x9591, + 0x821F, 0x8F4D, 0x8229, 0xE474, 0x822A, 0x8D71, 0x822B, 0xE475, 0x822C, 0x94CA, 0x822E, 0xE484, 0x8233, 0xE477, 0x8235, 0x91C7, + 0x8236, 0x9495, 0x8237, 0x8CBD, 0x8238, 0xE476, 0x8239, 0x9144, 0x8240, 0xE478, 0x8247, 0x92F8, 0x8258, 0xE47A, 0x8259, 0xE479, + 0x825A, 0xE47C, 0x825D, 0xE47B, 0x825F, 0xE47D, 0x8262, 0xE480, 0x8264, 0xE47E, 0x8266, 0x8ACD, 0x8268, 0xE481, 0x826A, 0xE482, + 0x826B, 0xE483, 0x826E, 0x8DAF, 0x826F, 0x97C7, 0x8271, 0xE485, 0x8272, 0x9046, 0x8276, 0x8990, 0x8277, 0xE486, 0x8278, 0xE487, + 0x827E, 0xE488, 0x828B, 0x88F0, 0x828D, 0xE489, 0x8292, 0xE48A, 0x8299, 0x9587, 0x829D, 0x8EC5, 0x829F, 0xE48C, 0x82A5, 0x8A48, + 0x82A6, 0x88B0, 0x82AB, 0xE48B, 0x82AC, 0xE48E, 0x82AD, 0x946D, 0x82AF, 0x9063, 0x82B1, 0x89D4, 0x82B3, 0x9646, 0x82B8, 0x8C7C, + 0x82B9, 0x8BDA, 0x82BB, 0xE48D, 0x82BD, 0x89E8, 0x82C5, 0x8AA1, 0x82D1, 0x8991, 0x82D2, 0xE492, 0x82D3, 0x97E8, 0x82D4, 0x91DB, + 0x82D7, 0x9563, 0x82D9, 0xE49E, 0x82DB, 0x89D5, 0x82DC, 0xE49C, 0x82DE, 0xE49A, 0x82DF, 0xE491, 0x82E1, 0xE48F, 0x82E3, 0xE490, + 0x82E5, 0x8EE1, 0x82E6, 0x8BEA, 0x82E7, 0x9297, 0x82EB, 0x93CF, 0x82F1, 0x8970, 0x82F3, 0xE494, 0x82F4, 0xE493, 0x82F9, 0xE499, + 0x82FA, 0xE495, 0x82FB, 0xE498, 0x8301, 0xFB93, 0x8302, 0x96CE, 0x8303, 0xE497, 0x8304, 0x89D6, 0x8305, 0x8A9D, 0x8306, 0xE49B, + 0x8309, 0xE49D, 0x830E, 0x8C73, 0x8316, 0xE4A1, 0x8317, 0xE4AA, 0x8318, 0xE4AB, 0x831C, 0x88A9, 0x8323, 0xE4B2, 0x8328, 0x88EF, + 0x832B, 0xE4A9, 0x832F, 0xE4A8, 0x8331, 0xE4A3, 0x8332, 0xE4A2, 0x8334, 0xE4A0, 0x8335, 0xE49F, 0x8336, 0x9283, 0x8338, 0x91F9, + 0x8339, 0xE4A5, 0x8340, 0xE4A4, 0x8345, 0xE4A7, 0x8349, 0x9190, 0x834A, 0x8C74, 0x834F, 0x8960, 0x8350, 0xE4A6, 0x8352, 0x8D72, + 0x8358, 0x9191, 0x8362, 0xFB94, 0x8373, 0xE4B8, 0x8375, 0xE4B9, 0x8377, 0x89D7, 0x837B, 0x89AC, 0x837C, 0xE4B6, 0x837F, 0xFB95, + 0x8385, 0xE4AC, 0x8387, 0xE4B4, 0x8389, 0xE4BB, 0x838A, 0xE4B5, 0x838E, 0xE4B3, 0x8393, 0xE496, 0x8396, 0xE4B1, 0x839A, 0xE4AD, + 0x839E, 0x8ACE, 0x839F, 0xE4AF, 0x83A0, 0xE4BA, 0x83A2, 0xE4B0, 0x83A8, 0xE4BC, 0x83AA, 0xE4AE, 0x83AB, 0x949C, 0x83B1, 0x9789, + 0x83B5, 0xE4B7, 0x83BD, 0xE4CD, 0x83C1, 0xE4C5, 0x83C5, 0x909B, 0x83C7, 0xFB96, 0x83CA, 0x8B65, 0x83CC, 0x8BDB, 0x83CE, 0xE4C0, + 0x83D3, 0x89D9, 0x83D6, 0x8FD2, 0x83D8, 0xE4C3, 0x83DC, 0x8DD8, 0x83DF, 0x9370, 0x83E0, 0xE4C8, 0x83E9, 0x95EC, 0x83EB, 0xE4BF, + 0x83EF, 0x89D8, 0x83F0, 0x8CD4, 0x83F1, 0x9548, 0x83F2, 0xE4C9, 0x83F4, 0xE4BD, 0x83F6, 0xFB97, 0x83F7, 0xE4C6, 0x83FB, 0xE4D0, + 0x83FD, 0xE4C1, 0x8403, 0xE4C2, 0x8404, 0x93B8, 0x8407, 0xE4C7, 0x840B, 0xE4C4, 0x840C, 0x9647, 0x840D, 0xE4CA, 0x840E, 0x88DE, + 0x8413, 0xE4BE, 0x8420, 0xE4CC, 0x8422, 0xE4CB, 0x8429, 0x948B, 0x842A, 0xE4D2, 0x842C, 0xE4DD, 0x8431, 0x8A9E, 0x8435, 0xE4E0, + 0x8438, 0xE4CE, 0x843C, 0xE4D3, 0x843D, 0x978E, 0x8446, 0xE4DC, 0x8448, 0xFB98, 0x8449, 0x9774, 0x844E, 0x97A8, 0x8457, 0x9298, + 0x845B, 0x8A8B, 0x8461, 0x9592, 0x8462, 0xE4E2, 0x8463, 0x939F, 0x8466, 0x88AF, 0x8469, 0xE4DB, 0x846B, 0xE4D7, 0x846C, 0x9192, + 0x846D, 0xE4D1, 0x846E, 0xE4D9, 0x846F, 0xE4DE, 0x8471, 0x944B, 0x8475, 0x88A8, 0x8477, 0xE4D6, 0x8479, 0xE4DF, 0x847A, 0x9598, + 0x8482, 0xE4DA, 0x8484, 0xE4D5, 0x848B, 0x8FD3, 0x8490, 0x8F4E, 0x8494, 0x8EAA, 0x8499, 0x96D6, 0x849C, 0x9566, 0x849F, 0xE4E5, + 0x84A1, 0xE4EE, 0x84AD, 0xE4D8, 0x84B2, 0x8A97, 0x84B4, 0xFB99, 0x84B8, 0x8FF6, 0x84B9, 0xE4E3, 0x84BB, 0xE4E8, 0x84BC, 0x9193, + 0x84BF, 0xE4E4, 0x84C1, 0xE4EB, 0x84C4, 0x927E, 0x84C6, 0xE4EC, 0x84C9, 0x9775, 0x84CA, 0xE4E1, 0x84CB, 0x8A57, 0x84CD, 0xE4E7, + 0x84D0, 0xE4EA, 0x84D1, 0x96AA, 0x84D6, 0xE4ED, 0x84D9, 0xE4E6, 0x84DA, 0xE4E9, 0x84DC, 0xFA60, 0x84EC, 0x9648, 0x84EE, 0x9840, + 0x84F4, 0xE4F1, 0x84FC, 0xE4F8, 0x84FF, 0xE4F0, 0x8500, 0x8EC1, 0x8506, 0xE4CF, 0x8511, 0x95CC, 0x8513, 0x96A0, 0x8514, 0xE4F7, + 0x8515, 0xE4F6, 0x8517, 0xE4F2, 0x8518, 0xE4F3, 0x851A, 0x8955, 0x851F, 0xE4F5, 0x8521, 0xE4EF, 0x8526, 0x92D3, 0x852C, 0xE4F4, + 0x852D, 0x88FC, 0x8535, 0x91A0, 0x853D, 0x95C1, 0x8540, 0xE4F9, 0x8541, 0xE540, 0x8543, 0x94D7, 0x8548, 0xE4FC, 0x8549, 0x8FD4, + 0x854A, 0x8EC7, 0x854B, 0xE542, 0x854E, 0x8BBC, 0x8553, 0xFB9A, 0x8555, 0xE543, 0x8557, 0x9599, 0x8558, 0xE4FB, 0x8559, 0xFB9B, + 0x855A, 0xE4D4, 0x8563, 0xE4FA, 0x8568, 0x986E, 0x8569, 0x93A0, 0x856A, 0x9593, 0x856B, 0xFB9C, 0x856D, 0xE54A, 0x8577, 0xE550, + 0x857E, 0xE551, 0x8580, 0xE544, 0x8584, 0x9496, 0x8587, 0xE54E, 0x8588, 0xE546, 0x858A, 0xE548, 0x8590, 0xE552, 0x8591, 0xE547, + 0x8594, 0xE54B, 0x8597, 0x8992, 0x8599, 0x93E3, 0x859B, 0xE54C, 0x859C, 0xE54F, 0x85A4, 0xE545, 0x85A6, 0x9145, 0x85A8, 0xE549, + 0x85A9, 0x8E46, 0x85AA, 0x9064, 0x85AB, 0x8C4F, 0x85AC, 0x96F2, 0x85AE, 0x96F7, 0x85AF, 0x8F92, 0x85B0, 0xFB9E, 0x85B9, 0xE556, + 0x85BA, 0xE554, 0x85C1, 0x986D, 0x85C9, 0xE553, 0x85CD, 0x9795, 0x85CF, 0xE555, 0x85D0, 0xE557, 0x85D5, 0xE558, 0x85DC, 0xE55B, + 0x85DD, 0xE559, 0x85E4, 0x93A1, 0x85E5, 0xE55A, 0x85E9, 0x94CB, 0x85EA, 0xE54D, 0x85F7, 0x8F93, 0x85F9, 0xE55C, 0x85FA, 0xE561, + 0x85FB, 0x9194, 0x85FE, 0xE560, 0x8602, 0xE541, 0x8606, 0xE562, 0x8607, 0x9168, 0x860A, 0xE55D, 0x860B, 0xE55F, 0x8613, 0xE55E, + 0x8616, 0x9F50, 0x8617, 0x9F41, 0x861A, 0xE564, 0x8622, 0xE563, 0x862D, 0x9796, 0x862F, 0xE1BA, 0x8630, 0xE565, 0x863F, 0xE566, + 0x864D, 0xE567, 0x864E, 0x8CD5, 0x8650, 0x8B73, 0x8654, 0xE569, 0x8655, 0x997C, 0x865A, 0x8B95, 0x865C, 0x97B8, 0x865E, 0x8BF1, + 0x865F, 0xE56A, 0x8667, 0xE56B, 0x866B, 0x928E, 0x8671, 0xE56C, 0x8679, 0x93F8, 0x867B, 0x88B8, 0x868A, 0x89E1, 0x868B, 0xE571, + 0x868C, 0xE572, 0x8693, 0xE56D, 0x8695, 0x8E5C, 0x86A3, 0xE56E, 0x86A4, 0x9461, 0x86A9, 0xE56F, 0x86AA, 0xE570, 0x86AB, 0xE57A, + 0x86AF, 0xE574, 0x86B0, 0xE577, 0x86B6, 0xE573, 0x86C4, 0xE575, 0x86C6, 0xE576, 0x86C7, 0x8ED6, 0x86C9, 0xE578, 0x86CB, 0x9260, + 0x86CD, 0x8C75, 0x86CE, 0x8A61, 0x86D4, 0xE57B, 0x86D9, 0x8A5E, 0x86DB, 0xE581, 0x86DE, 0xE57C, 0x86DF, 0xE580, 0x86E4, 0x94B8, + 0x86E9, 0xE57D, 0x86EC, 0xE57E, 0x86ED, 0x9567, 0x86EE, 0x94D8, 0x86EF, 0xE582, 0x86F8, 0x91FB, 0x86F9, 0xE58C, 0x86FB, 0xE588, + 0x86FE, 0x89E9, 0x8700, 0xE586, 0x8702, 0x9649, 0x8703, 0xE587, 0x8706, 0xE584, 0x8708, 0xE585, 0x8709, 0xE58A, 0x870A, 0xE58D, + 0x870D, 0xE58B, 0x8711, 0xE589, 0x8712, 0xE583, 0x8718, 0x9277, 0x871A, 0xE594, 0x871C, 0x96A8, 0x8725, 0xE592, 0x8729, 0xE593, + 0x8734, 0xE58E, 0x8737, 0xE590, 0x873B, 0xE591, 0x873F, 0xE58F, 0x8749, 0x90E4, 0x874B, 0x9858, 0x874C, 0xE598, 0x874E, 0xE599, + 0x8753, 0xE59F, 0x8755, 0x9049, 0x8757, 0xE59B, 0x8759, 0xE59E, 0x875F, 0xE596, 0x8760, 0xE595, 0x8763, 0xE5A0, 0x8766, 0x89DA, + 0x8768, 0xE59C, 0x876A, 0xE5A1, 0x876E, 0xE59D, 0x8774, 0xE59A, 0x8776, 0x92B1, 0x8778, 0xE597, 0x877F, 0x9488, 0x8782, 0xE5A5, + 0x878D, 0x975A, 0x879F, 0xE5A4, 0x87A2, 0xE5A3, 0x87AB, 0xE5AC, 0x87AF, 0xE5A6, 0x87B3, 0xE5AE, 0x87BA, 0x9786, 0x87BB, 0xE5B1, + 0x87BD, 0xE5A8, 0x87C0, 0xE5A9, 0x87C4, 0xE5AD, 0x87C6, 0xE5B0, 0x87C7, 0xE5AF, 0x87CB, 0xE5A7, 0x87D0, 0xE5AA, 0x87D2, 0xE5BB, + 0x87E0, 0xE5B4, 0x87EF, 0xE5B2, 0x87F2, 0xE5B3, 0x87F6, 0xE5B8, 0x87F7, 0xE5B9, 0x87F9, 0x8A49, 0x87FB, 0x8B61, 0x87FE, 0xE5B7, + 0x8805, 0xE5A2, 0x8807, 0xFBA1, 0x880D, 0xE5B6, 0x880E, 0xE5BA, 0x880F, 0xE5B5, 0x8811, 0xE5BC, 0x8815, 0xE5BE, 0x8816, 0xE5BD, + 0x8821, 0xE5C0, 0x8822, 0xE5BF, 0x8823, 0xE579, 0x8827, 0xE5C4, 0x8831, 0xE5C1, 0x8836, 0xE5C2, 0x8839, 0xE5C3, 0x883B, 0xE5C5, + 0x8840, 0x8C8C, 0x8842, 0xE5C7, 0x8844, 0xE5C6, 0x8846, 0x8F4F, 0x884C, 0x8D73, 0x884D, 0x9FA5, 0x8852, 0xE5C8, 0x8853, 0x8F70, + 0x8857, 0x8A58, 0x8859, 0xE5C9, 0x885B, 0x8971, 0x885D, 0x8FD5, 0x885E, 0xE5CA, 0x8861, 0x8D74, 0x8862, 0xE5CB, 0x8863, 0x88DF, + 0x8868, 0x955C, 0x886B, 0xE5CC, 0x8870, 0x908A, 0x8872, 0xE5D3, 0x8875, 0xE5D0, 0x8877, 0x928F, 0x887D, 0xE5D1, 0x887E, 0xE5CE, + 0x887F, 0x8BDC, 0x8881, 0xE5CD, 0x8882, 0xE5D4, 0x8888, 0x8C55, 0x888B, 0x91DC, 0x888D, 0xE5DA, 0x8892, 0xE5D6, 0x8896, 0x91B3, + 0x8897, 0xE5D5, 0x8899, 0xE5D8, 0x889E, 0xE5CF, 0x88A2, 0xE5D9, 0x88A4, 0xE5DB, 0x88AB, 0x94ED, 0x88AE, 0xE5D7, 0x88B0, 0xE5DC, + 0x88B1, 0xE5DE, 0x88B4, 0x8CD1, 0x88B5, 0xE5D2, 0x88B7, 0x88BF, 0x88BF, 0xE5DD, 0x88C1, 0x8DD9, 0x88C2, 0x97F4, 0x88C3, 0xE5DF, + 0x88C4, 0xE5E0, 0x88C5, 0x9195, 0x88CF, 0x97A0, 0x88D4, 0xE5E1, 0x88D5, 0x9754, 0x88D8, 0xE5E2, 0x88D9, 0xE5E3, 0x88DC, 0x95E2, + 0x88DD, 0xE5E4, 0x88DF, 0x8DBE, 0x88E1, 0x97A1, 0x88E8, 0xE5E9, 0x88F2, 0xE5EA, 0x88F3, 0x8FD6, 0x88F4, 0xE5E8, 0x88F5, 0xFBA2, + 0x88F8, 0x9787, 0x88F9, 0xE5E5, 0x88FC, 0xE5E7, 0x88FD, 0x90BB, 0x88FE, 0x909E, 0x8902, 0xE5E6, 0x8904, 0xE5EB, 0x8907, 0x95A1, + 0x890A, 0xE5ED, 0x890C, 0xE5EC, 0x8910, 0x8A8C, 0x8912, 0x964A, 0x8913, 0xE5EE, 0x891C, 0xFA5D, 0x891D, 0xE5FA, 0x891E, 0xE5F0, + 0x8925, 0xE5F1, 0x892A, 0xE5F2, 0x892B, 0xE5F3, 0x8936, 0xE5F7, 0x8938, 0xE5F8, 0x893B, 0xE5F6, 0x8941, 0xE5F4, 0x8943, 0xE5EF, + 0x8944, 0xE5F5, 0x894C, 0xE5F9, 0x894D, 0xE8B5, 0x8956, 0x89A6, 0x895E, 0xE5FC, 0x895F, 0x8BDD, 0x8960, 0xE5FB, 0x8964, 0xE641, + 0x8966, 0xE640, 0x896A, 0xE643, 0x896D, 0xE642, 0x896F, 0xE644, 0x8972, 0x8F50, 0x8974, 0xE645, 0x8977, 0xE646, 0x897E, 0xE647, + 0x897F, 0x90BC, 0x8981, 0x9776, 0x8983, 0xE648, 0x8986, 0x95A2, 0x8987, 0x9465, 0x8988, 0xE649, 0x898A, 0xE64A, 0x898B, 0x8CA9, + 0x898F, 0x8B4B, 0x8993, 0xE64B, 0x8996, 0x8E8B, 0x8997, 0x9460, 0x8998, 0xE64C, 0x899A, 0x8A6F, 0x89A1, 0xE64D, 0x89A6, 0xE64F, + 0x89A7, 0x9797, 0x89A9, 0xE64E, 0x89AA, 0x9065, 0x89AC, 0xE650, 0x89AF, 0xE651, 0x89B2, 0xE652, 0x89B3, 0x8ACF, 0x89BA, 0xE653, + 0x89BD, 0xE654, 0x89BF, 0xE655, 0x89C0, 0xE656, 0x89D2, 0x8A70, 0x89DA, 0xE657, 0x89DC, 0xE658, 0x89DD, 0xE659, 0x89E3, 0x89F0, + 0x89E6, 0x9047, 0x89E7, 0xE65A, 0x89F4, 0xE65B, 0x89F8, 0xE65C, 0x8A00, 0x8CBE, 0x8A02, 0x92F9, 0x8A03, 0xE65D, 0x8A08, 0x8C76, + 0x8A0A, 0x9075, 0x8A0C, 0xE660, 0x8A0E, 0x93A2, 0x8A10, 0xE65F, 0x8A12, 0xFBA3, 0x8A13, 0x8C50, 0x8A16, 0xE65E, 0x8A17, 0x91F5, + 0x8A18, 0x8B4C, 0x8A1B, 0xE661, 0x8A1D, 0xE662, 0x8A1F, 0x8FD7, 0x8A23, 0x8C8D, 0x8A25, 0xE663, 0x8A2A, 0x964B, 0x8A2D, 0x90DD, + 0x8A31, 0x8B96, 0x8A33, 0x96F3, 0x8A34, 0x9169, 0x8A36, 0xE664, 0x8A37, 0xFBA4, 0x8A3A, 0x9066, 0x8A3B, 0x9290, 0x8A3C, 0x8FD8, + 0x8A41, 0xE665, 0x8A46, 0xE668, 0x8A48, 0xE669, 0x8A50, 0x8DBC, 0x8A51, 0x91C0, 0x8A52, 0xE667, 0x8A54, 0x8FD9, 0x8A55, 0x955D, + 0x8A5B, 0xE666, 0x8A5E, 0x8E8C, 0x8A60, 0x8972, 0x8A62, 0xE66D, 0x8A63, 0x8C77, 0x8A66, 0x8E8E, 0x8A69, 0x8E8D, 0x8A6B, 0x986C, + 0x8A6C, 0xE66C, 0x8A6D, 0xE66B, 0x8A6E, 0x9146, 0x8A70, 0x8B6C, 0x8A71, 0x9862, 0x8A72, 0x8A59, 0x8A73, 0x8FDA, 0x8A79, 0xFBA5, + 0x8A7C, 0xE66A, 0x8A82, 0xE66F, 0x8A84, 0xE670, 0x8A85, 0xE66E, 0x8A87, 0x8CD6, 0x8A89, 0x975F, 0x8A8C, 0x8E8F, 0x8A8D, 0x9446, + 0x8A91, 0xE673, 0x8A93, 0x90BE, 0x8A95, 0x9261, 0x8A98, 0x9755, 0x8A9A, 0xE676, 0x8A9E, 0x8CEA, 0x8AA0, 0x90BD, 0x8AA1, 0xE672, + 0x8AA3, 0xE677, 0x8AA4, 0x8CEB, 0x8AA5, 0xE674, 0x8AA6, 0xE675, 0x8AA7, 0xFBA6, 0x8AA8, 0xE671, 0x8AAC, 0x90E0, 0x8AAD, 0x93C7, + 0x8AB0, 0x924E, 0x8AB2, 0x89DB, 0x8AB9, 0x94EE, 0x8ABC, 0x8B62, 0x8ABE, 0xFBA7, 0x8ABF, 0x92B2, 0x8AC2, 0xE67A, 0x8AC4, 0xE678, + 0x8AC7, 0x926B, 0x8ACB, 0x90BF, 0x8ACC, 0x8AD0, 0x8ACD, 0xE679, 0x8ACF, 0x907A, 0x8AD2, 0x97C8, 0x8AD6, 0x985F, 0x8ADA, 0xE67B, + 0x8ADB, 0xE687, 0x8ADC, 0x92B3, 0x8ADE, 0xE686, 0x8ADF, 0xFBA8, 0x8AE0, 0xE683, 0x8AE1, 0xE68B, 0x8AE2, 0xE684, 0x8AE4, 0xE680, + 0x8AE6, 0x92FA, 0x8AE7, 0xE67E, 0x8AEB, 0xE67C, 0x8AED, 0x9740, 0x8AEE, 0x8E90, 0x8AF1, 0xE681, 0x8AF3, 0xE67D, 0x8AF6, 0xFBAA, + 0x8AF7, 0xE685, 0x8AF8, 0x8F94, 0x8AFA, 0x8CBF, 0x8AFE, 0x91F8, 0x8B00, 0x9664, 0x8B01, 0x8979, 0x8B02, 0x88E0, 0x8B04, 0x93A3, + 0x8B07, 0xE689, 0x8B0C, 0xE688, 0x8B0E, 0x93E4, 0x8B10, 0xE68D, 0x8B14, 0xE682, 0x8B16, 0xE68C, 0x8B17, 0xE68E, 0x8B19, 0x8CAA, + 0x8B1A, 0xE68A, 0x8B1B, 0x8D75, 0x8B1D, 0x8ED3, 0x8B20, 0xE68F, 0x8B21, 0x9777, 0x8B26, 0xE692, 0x8B28, 0xE695, 0x8B2B, 0xE693, + 0x8B2C, 0x9554, 0x8B33, 0xE690, 0x8B39, 0x8BDE, 0x8B3E, 0xE694, 0x8B41, 0xE696, 0x8B49, 0xE69A, 0x8B4C, 0xE697, 0x8B4E, 0xE699, + 0x8B4F, 0xE698, 0x8B53, 0xFBAB, 0x8B56, 0xE69B, 0x8B58, 0x8EAF, 0x8B5A, 0xE69D, 0x8B5B, 0xE69C, 0x8B5C, 0x9588, 0x8B5F, 0xE69F, + 0x8B66, 0x8C78, 0x8B6B, 0xE69E, 0x8B6C, 0xE6A0, 0x8B6F, 0xE6A1, 0x8B70, 0x8B63, 0x8B71, 0xE3BF, 0x8B72, 0x8FF7, 0x8B74, 0xE6A2, + 0x8B77, 0x8CEC, 0x8B7D, 0xE6A3, 0x8B7F, 0xFBAC, 0x8B80, 0xE6A4, 0x8B83, 0x8E5D, 0x8B8A, 0x9DCC, 0x8B8C, 0xE6A5, 0x8B8E, 0xE6A6, + 0x8B90, 0x8F51, 0x8B92, 0xE6A7, 0x8B93, 0xE6A8, 0x8B96, 0xE6A9, 0x8B99, 0xE6AA, 0x8B9A, 0xE6AB, 0x8C37, 0x924A, 0x8C3A, 0xE6AC, + 0x8C3F, 0xE6AE, 0x8C41, 0xE6AD, 0x8C46, 0x93A4, 0x8C48, 0xE6AF, 0x8C4A, 0x964C, 0x8C4C, 0xE6B0, 0x8C4E, 0xE6B1, 0x8C50, 0xE6B2, + 0x8C55, 0xE6B3, 0x8C5A, 0x93D8, 0x8C61, 0x8FDB, 0x8C62, 0xE6B4, 0x8C6A, 0x8D8B, 0x8C6B, 0x98AC, 0x8C6C, 0xE6B5, 0x8C78, 0xE6B6, + 0x8C79, 0x955E, 0x8C7A, 0xE6B7, 0x8C7C, 0xE6BF, 0x8C82, 0xE6B8, 0x8C85, 0xE6BA, 0x8C89, 0xE6B9, 0x8C8A, 0xE6BB, 0x8C8C, 0x9665, + 0x8C8D, 0xE6BC, 0x8C8E, 0xE6BD, 0x8C94, 0xE6BE, 0x8C98, 0xE6C0, 0x8C9D, 0x8A4C, 0x8C9E, 0x92E5, 0x8CA0, 0x9589, 0x8CA1, 0x8DE0, + 0x8CA2, 0x8D76, 0x8CA7, 0x956E, 0x8CA8, 0x89DD, 0x8CA9, 0x94CC, 0x8CAA, 0xE6C3, 0x8CAB, 0x8AD1, 0x8CAC, 0x90D3, 0x8CAD, 0xE6C2, + 0x8CAE, 0xE6C7, 0x8CAF, 0x9299, 0x8CB0, 0x96E1, 0x8CB2, 0xE6C5, 0x8CB3, 0xE6C6, 0x8CB4, 0x8B4D, 0x8CB6, 0xE6C8, 0x8CB7, 0x9483, + 0x8CB8, 0x91DD, 0x8CBB, 0x94EF, 0x8CBC, 0x935C, 0x8CBD, 0xE6C4, 0x8CBF, 0x9666, 0x8CC0, 0x89EA, 0x8CC1, 0xE6CA, 0x8CC2, 0x9847, + 0x8CC3, 0x92C0, 0x8CC4, 0x9864, 0x8CC7, 0x8E91, 0x8CC8, 0xE6C9, 0x8CCA, 0x91AF, 0x8CCD, 0xE6DA, 0x8CCE, 0x9147, 0x8CD1, 0x93F6, + 0x8CD3, 0x956F, 0x8CDA, 0xE6CD, 0x8CDB, 0x8E5E, 0x8CDC, 0x8E92, 0x8CDE, 0x8FDC, 0x8CE0, 0x9485, 0x8CE2, 0x8CAB, 0x8CE3, 0xE6CC, + 0x8CE4, 0xE6CB, 0x8CE6, 0x958A, 0x8CEA, 0x8EBF, 0x8CED, 0x9371, 0x8CF0, 0xFBAD, 0x8CF4, 0xFBAE, 0x8CFA, 0xE6CF, 0x8CFB, 0xE6D0, + 0x8CFC, 0x8D77, 0x8CFD, 0xE6CE, 0x8D04, 0xE6D1, 0x8D05, 0xE6D2, 0x8D07, 0xE6D4, 0x8D08, 0x91A1, 0x8D0A, 0xE6D3, 0x8D0B, 0x8AE4, + 0x8D0D, 0xE6D6, 0x8D0F, 0xE6D5, 0x8D10, 0xE6D7, 0x8D12, 0xFBAF, 0x8D13, 0xE6D9, 0x8D14, 0xE6DB, 0x8D16, 0xE6DC, 0x8D64, 0x90D4, + 0x8D66, 0x8ECD, 0x8D67, 0xE6DD, 0x8D6B, 0x8A71, 0x8D6D, 0xE6DE, 0x8D70, 0x9196, 0x8D71, 0xE6DF, 0x8D73, 0xE6E0, 0x8D74, 0x958B, + 0x8D76, 0xFBB0, 0x8D77, 0x8B4E, 0x8D81, 0xE6E1, 0x8D85, 0x92B4, 0x8D8A, 0x897A, 0x8D99, 0xE6E2, 0x8DA3, 0x8EEF, 0x8DA8, 0x9096, + 0x8DB3, 0x91AB, 0x8DBA, 0xE6E5, 0x8DBE, 0xE6E4, 0x8DC2, 0xE6E3, 0x8DCB, 0xE6EB, 0x8DCC, 0xE6E9, 0x8DCF, 0xE6E6, 0x8DD6, 0xE6E8, + 0x8DDA, 0xE6E7, 0x8DDB, 0xE6EA, 0x8DDD, 0x8B97, 0x8DDF, 0xE6EE, 0x8DE1, 0x90D5, 0x8DE3, 0xE6EF, 0x8DE8, 0x8CD7, 0x8DEA, 0xE6EC, + 0x8DEB, 0xE6ED, 0x8DEF, 0x9848, 0x8DF3, 0x92B5, 0x8DF5, 0x9148, 0x8DFC, 0xE6F0, 0x8DFF, 0xE6F3, 0x8E08, 0xE6F1, 0x8E09, 0xE6F2, + 0x8E0A, 0x9778, 0x8E0F, 0x93A5, 0x8E10, 0xE6F6, 0x8E1D, 0xE6F4, 0x8E1E, 0xE6F5, 0x8E1F, 0xE6F7, 0x8E2A, 0xE748, 0x8E30, 0xE6FA, + 0x8E34, 0xE6FB, 0x8E35, 0xE6F9, 0x8E42, 0xE6F8, 0x8E44, 0x92FB, 0x8E47, 0xE740, 0x8E48, 0xE744, 0x8E49, 0xE741, 0x8E4A, 0xE6FC, + 0x8E4C, 0xE742, 0x8E50, 0xE743, 0x8E55, 0xE74A, 0x8E59, 0xE745, 0x8E5F, 0x90D6, 0x8E60, 0xE747, 0x8E63, 0xE749, 0x8E64, 0xE746, + 0x8E72, 0xE74C, 0x8E74, 0x8F52, 0x8E76, 0xE74B, 0x8E7C, 0xE74D, 0x8E81, 0xE74E, 0x8E84, 0xE751, 0x8E85, 0xE750, 0x8E87, 0xE74F, + 0x8E8A, 0xE753, 0x8E8B, 0xE752, 0x8E8D, 0x96F4, 0x8E91, 0xE755, 0x8E93, 0xE754, 0x8E94, 0xE756, 0x8E99, 0xE757, 0x8EA1, 0xE759, + 0x8EAA, 0xE758, 0x8EAB, 0x9067, 0x8EAC, 0xE75A, 0x8EAF, 0x8BEB, 0x8EB0, 0xE75B, 0x8EB1, 0xE75D, 0x8EBE, 0xE75E, 0x8EC5, 0xE75F, + 0x8EC6, 0xE75C, 0x8EC8, 0xE760, 0x8ECA, 0x8ED4, 0x8ECB, 0xE761, 0x8ECC, 0x8B4F, 0x8ECD, 0x8C52, 0x8ECF, 0xFBB2, 0x8ED2, 0x8CAC, + 0x8EDB, 0xE762, 0x8EDF, 0x93EE, 0x8EE2, 0x935D, 0x8EE3, 0xE763, 0x8EEB, 0xE766, 0x8EF8, 0x8EB2, 0x8EFB, 0xE765, 0x8EFC, 0xE764, + 0x8EFD, 0x8C79, 0x8EFE, 0xE767, 0x8F03, 0x8A72, 0x8F05, 0xE769, 0x8F09, 0x8DDA, 0x8F0A, 0xE768, 0x8F0C, 0xE771, 0x8F12, 0xE76B, + 0x8F13, 0xE76D, 0x8F14, 0x95E3, 0x8F15, 0xE76A, 0x8F19, 0xE76C, 0x8F1B, 0xE770, 0x8F1C, 0xE76E, 0x8F1D, 0x8B50, 0x8F1F, 0xE76F, + 0x8F26, 0xE772, 0x8F29, 0x9479, 0x8F2A, 0x97D6, 0x8F2F, 0x8F53, 0x8F33, 0xE773, 0x8F38, 0x9741, 0x8F39, 0xE775, 0x8F3B, 0xE774, + 0x8F3E, 0xE778, 0x8F3F, 0x9760, 0x8F42, 0xE777, 0x8F44, 0x8A8D, 0x8F45, 0xE776, 0x8F46, 0xE77B, 0x8F49, 0xE77A, 0x8F4C, 0xE779, + 0x8F4D, 0x9351, 0x8F4E, 0xE77C, 0x8F57, 0xE77D, 0x8F5C, 0xE77E, 0x8F5F, 0x8D8C, 0x8F61, 0x8C44, 0x8F62, 0xE780, 0x8F63, 0xE781, + 0x8F64, 0xE782, 0x8F9B, 0x9068, 0x8F9C, 0xE783, 0x8F9E, 0x8EAB, 0x8F9F, 0xE784, 0x8FA3, 0xE785, 0x8FA7, 0x999F, 0x8FA8, 0x999E, + 0x8FAD, 0xE786, 0x8FAE, 0xE390, 0x8FAF, 0xE787, 0x8FB0, 0x9243, 0x8FB1, 0x904A, 0x8FB2, 0x945F, 0x8FB7, 0xE788, 0x8FBA, 0x95D3, + 0x8FBB, 0x92D2, 0x8FBC, 0x8D9E, 0x8FBF, 0x9248, 0x8FC2, 0x8949, 0x8FC4, 0x9698, 0x8FC5, 0x9076, 0x8FCE, 0x8C7D, 0x8FD1, 0x8BDF, + 0x8FD4, 0x95D4, 0x8FDA, 0xE789, 0x8FE2, 0xE78B, 0x8FE5, 0xE78A, 0x8FE6, 0x89DE, 0x8FE9, 0x93F4, 0x8FEA, 0xE78C, 0x8FEB, 0x9497, + 0x8FED, 0x9352, 0x8FEF, 0xE78D, 0x8FF0, 0x8F71, 0x8FF4, 0xE78F, 0x8FF7, 0x96C0, 0x8FF8, 0xE79E, 0x8FF9, 0xE791, 0x8FFA, 0xE792, + 0x8FFD, 0x92C7, 0x9000, 0x91DE, 0x9001, 0x9197, 0x9003, 0x93A6, 0x9005, 0xE790, 0x9006, 0x8B74, 0x900B, 0xE799, 0x900D, 0xE796, + 0x900E, 0xE7A3, 0x900F, 0x93A7, 0x9010, 0x9280, 0x9011, 0xE793, 0x9013, 0x92FC, 0x9014, 0x9372, 0x9015, 0xE794, 0x9016, 0xE798, + 0x9017, 0x9080, 0x9019, 0x9487, 0x901A, 0x92CA, 0x901D, 0x90C0, 0x901E, 0xE797, 0x901F, 0x91AC, 0x9020, 0x91A2, 0x9021, 0xE795, + 0x9022, 0x88A7, 0x9023, 0x9841, 0x9027, 0xE79A, 0x902E, 0x91DF, 0x9031, 0x8F54, 0x9032, 0x9069, 0x9035, 0xE79C, 0x9036, 0xE79B, + 0x9038, 0x88ED, 0x9039, 0xE79D, 0x903C, 0x954E, 0x903E, 0xE7A5, 0x9041, 0x93D9, 0x9042, 0x908B, 0x9045, 0x9278, 0x9047, 0x8BF6, + 0x9049, 0xE7A4, 0x904A, 0x9756, 0x904B, 0x895E, 0x904D, 0x95D5, 0x904E, 0x89DF, 0x904F, 0xE79F, 0x9050, 0xE7A0, 0x9051, 0xE7A1, + 0x9052, 0xE7A2, 0x9053, 0x93B9, 0x9054, 0x9242, 0x9055, 0x88E1, 0x9056, 0xE7A6, 0x9058, 0xE7A7, 0x9059, 0xEAA1, 0x905C, 0x91BB, + 0x905E, 0xE7A8, 0x9060, 0x8993, 0x9061, 0x916B, 0x9063, 0x8CAD, 0x9065, 0x9779, 0x9067, 0xFBB5, 0x9068, 0xE7A9, 0x9069, 0x934B, + 0x906D, 0x9198, 0x906E, 0x8ED5, 0x906F, 0xE7AA, 0x9072, 0xE7AD, 0x9075, 0x8F85, 0x9076, 0xE7AB, 0x9077, 0x914A, 0x9078, 0x9149, + 0x907A, 0x88E2, 0x907C, 0x97C9, 0x907D, 0xE7AF, 0x907F, 0x94F0, 0x9080, 0xE7B1, 0x9081, 0xE7B0, 0x9082, 0xE7AE, 0x9083, 0xE284, + 0x9084, 0x8AD2, 0x9087, 0xE78E, 0x9089, 0xE7B3, 0x908A, 0xE7B2, 0x908F, 0xE7B4, 0x9091, 0x9757, 0x90A3, 0x93DF, 0x90A6, 0x964D, + 0x90A8, 0xE7B5, 0x90AA, 0x8ED7, 0x90AF, 0xE7B6, 0x90B1, 0xE7B7, 0x90B5, 0xE7B8, 0x90B8, 0x9340, 0x90C1, 0x88E8, 0x90CA, 0x8D78, + 0x90CE, 0x9859, 0x90DB, 0xE7BC, 0x90DE, 0xFBB6, 0x90E1, 0x8C53, 0x90E2, 0xE7B9, 0x90E4, 0xE7BA, 0x90E8, 0x9594, 0x90ED, 0x8A73, + 0x90F5, 0x9758, 0x90F7, 0x8BBD, 0x90FD, 0x9373, 0x9102, 0xE7BD, 0x9112, 0xE7BE, 0x9115, 0xFBB8, 0x9119, 0xE7BF, 0x9127, 0xFBB9, + 0x912D, 0x9341, 0x9130, 0xE7C1, 0x9132, 0xE7C0, 0x9149, 0x93D1, 0x914A, 0xE7C2, 0x914B, 0x8F55, 0x914C, 0x8EDE, 0x914D, 0x947A, + 0x914E, 0x9291, 0x9152, 0x8EF0, 0x9154, 0x908C, 0x9156, 0xE7C3, 0x9158, 0xE7C4, 0x9162, 0x907C, 0x9163, 0xE7C5, 0x9165, 0xE7C6, + 0x9169, 0xE7C7, 0x916A, 0x978F, 0x916C, 0x8F56, 0x9172, 0xE7C9, 0x9173, 0xE7C8, 0x9175, 0x8D79, 0x9177, 0x8D93, 0x9178, 0x8E5F, + 0x9182, 0xE7CC, 0x9187, 0x8F86, 0x9189, 0xE7CB, 0x918B, 0xE7CA, 0x918D, 0x91E7, 0x9190, 0x8CED, 0x9192, 0x90C1, 0x9197, 0x94AE, + 0x919C, 0x8F58, 0x91A2, 0xE7CD, 0x91A4, 0x8FDD, 0x91AA, 0xE7D0, 0x91AB, 0xE7CE, 0x91AF, 0xE7CF, 0x91B4, 0xE7D2, 0x91B5, 0xE7D1, + 0x91B8, 0x8FF8, 0x91BA, 0xE7D3, 0x91C0, 0xE7D4, 0x91C1, 0xE7D5, 0x91C6, 0x94CE, 0x91C7, 0x8DD1, 0x91C8, 0x8EDF, 0x91C9, 0xE7D6, + 0x91CB, 0xE7D7, 0x91CC, 0x97A2, 0x91CD, 0x8F64, 0x91CE, 0x96EC, 0x91CF, 0x97CA, 0x91D0, 0xE7D8, 0x91D1, 0x8BE0, 0x91D6, 0xE7D9, + 0x91D7, 0xFBBB, 0x91D8, 0x9342, 0x91DA, 0xFBBA, 0x91DB, 0xE7DC, 0x91DC, 0x8A98, 0x91DD, 0x906A, 0x91DE, 0xFBBC, 0x91DF, 0xE7DA, + 0x91E1, 0xE7DB, 0x91E3, 0x92DE, 0x91E4, 0xFBBF, 0x91E5, 0xFBC0, 0x91E6, 0x9674, 0x91E7, 0x8BFA, 0x91ED, 0xFBBD, 0x91EE, 0xFBBE, + 0x91F5, 0xE7DE, 0x91F6, 0xE7DF, 0x91FC, 0xE7DD, 0x91FF, 0xE7E1, 0x9206, 0xFBC1, 0x920A, 0xFBC3, 0x920D, 0x93DD, 0x920E, 0x8A62, + 0x9210, 0xFBC2, 0x9211, 0xE7E5, 0x9214, 0xE7E2, 0x9215, 0xE7E4, 0x921E, 0xE7E0, 0x9229, 0xE86E, 0x922C, 0xE7E3, 0x9234, 0x97E9, + 0x9237, 0x8CD8, 0x9239, 0xFBCA, 0x923A, 0xFBC4, 0x923C, 0xFBC6, 0x923F, 0xE7ED, 0x9240, 0xFBC5, 0x9244, 0x9353, 0x9245, 0xE7E8, + 0x9248, 0xE7EB, 0x9249, 0xE7E9, 0x924B, 0xE7EE, 0x924E, 0xFBC7, 0x9250, 0xE7EF, 0x9251, 0xFBC9, 0x9257, 0xE7E7, 0x9259, 0xFBC8, + 0x925A, 0xE7F4, 0x925B, 0x8994, 0x925E, 0xE7E6, 0x9262, 0x94AB, 0x9264, 0xE7EA, 0x9266, 0x8FDE, 0x9267, 0xFBCB, 0x9271, 0x8D7A, + 0x9277, 0xFBCD, 0x9278, 0xFBCE, 0x927E, 0x9667, 0x9280, 0x8BE2, 0x9283, 0x8F65, 0x9285, 0x93BA, 0x9288, 0xFA5F, 0x9291, 0x914C, + 0x9293, 0xE7F2, 0x9295, 0xE7EC, 0x9296, 0xE7F1, 0x9298, 0x96C1, 0x929A, 0x92B6, 0x929B, 0xE7F3, 0x929C, 0xE7F0, 0x92A7, 0xFBCC, + 0x92AD, 0x914B, 0x92B7, 0xE7F7, 0x92B9, 0xE7F6, 0x92CF, 0xE7F5, 0x92D0, 0xFBD2, 0x92D2, 0x964E, 0x92D3, 0xFBD6, 0x92D5, 0xFBD4, + 0x92D7, 0xFBD0, 0x92D9, 0xFBD1, 0x92E0, 0xFBD5, 0x92E4, 0x8F9B, 0x92E7, 0xFBCF, 0x92E9, 0xE7F8, 0x92EA, 0x95DD, 0x92ED, 0x8973, + 0x92F2, 0x9565, 0x92F3, 0x9292, 0x92F8, 0x8B98, 0x92F9, 0xFA65, 0x92FA, 0xE7FA, 0x92FB, 0xFBD9, 0x92FC, 0x8D7C, 0x92FF, 0xFBDC, + 0x9302, 0xFBDE, 0x9306, 0x8E4B, 0x930F, 0xE7F9, 0x9310, 0x908D, 0x9318, 0x908E, 0x9319, 0xE840, 0x931A, 0xE842, 0x931D, 0xFBDD, + 0x931E, 0xFBDB, 0x9320, 0x8FF9, 0x9321, 0xFBD8, 0x9322, 0xE841, 0x9323, 0xE843, 0x9325, 0xFBD7, 0x9326, 0x8BD1, 0x9328, 0x9564, + 0x932B, 0x8EE0, 0x932C, 0x9842, 0x932E, 0xE7FC, 0x932F, 0x8DF6, 0x9332, 0x985E, 0x9335, 0xE845, 0x933A, 0xE844, 0x933B, 0xE846, + 0x9344, 0xE7FB, 0x9348, 0xFA5E, 0x934B, 0x93E7, 0x934D, 0x9374, 0x9354, 0x92D5, 0x9356, 0xE84B, 0x9357, 0xFBE0, 0x935B, 0x9262, + 0x935C, 0xE847, 0x9360, 0xE848, 0x936C, 0x8C4C, 0x936E, 0xE84A, 0x9370, 0xFBDF, 0x9375, 0x8CAE, 0x937C, 0xE849, 0x937E, 0x8FDF, + 0x938C, 0x8A99, 0x9394, 0xE84F, 0x9396, 0x8DBD, 0x9397, 0x9199, 0x939A, 0x92C8, 0x93A4, 0xFBE1, 0x93A7, 0x8A5A, 0x93AC, 0xE84D, + 0x93AD, 0xE84E, 0x93AE, 0x92C1, 0x93B0, 0xE84C, 0x93B9, 0xE850, 0x93C3, 0xE856, 0x93C6, 0xFBE2, 0x93C8, 0xE859, 0x93D0, 0xE858, + 0x93D1, 0x934C, 0x93D6, 0xE851, 0x93D7, 0xE852, 0x93D8, 0xE855, 0x93DD, 0xE857, 0x93DE, 0xFBE3, 0x93E1, 0x8BBE, 0x93E4, 0xE85A, + 0x93E5, 0xE854, 0x93E8, 0xE853, 0x93F8, 0xFBE4, 0x9403, 0xE85E, 0x9407, 0xE85F, 0x9410, 0xE860, 0x9413, 0xE85D, 0x9414, 0xE85C, + 0x9418, 0x8FE0, 0x9419, 0x93A8, 0x941A, 0xE85B, 0x9421, 0xE864, 0x942B, 0xE862, 0x9431, 0xFBE5, 0x9435, 0xE863, 0x9436, 0xE861, + 0x9438, 0x91F6, 0x943A, 0xE865, 0x9441, 0xE866, 0x9444, 0xE868, 0x9445, 0xFBE6, 0x9448, 0xFBE7, 0x9451, 0x8AD3, 0x9452, 0xE867, + 0x9453, 0x96F8, 0x945A, 0xE873, 0x945B, 0xE869, 0x945E, 0xE86C, 0x9460, 0xE86A, 0x9462, 0xE86B, 0x946A, 0xE86D, 0x9470, 0xE86F, + 0x9475, 0xE870, 0x9477, 0xE871, 0x947C, 0xE874, 0x947D, 0xE872, 0x947E, 0xE875, 0x947F, 0xE877, 0x9481, 0xE876, 0x9577, 0x92B7, + 0x9580, 0x96E5, 0x9582, 0xE878, 0x9583, 0x914D, 0x9587, 0xE879, 0x9589, 0x95C2, 0x958A, 0xE87A, 0x958B, 0x8A4A, 0x958F, 0x895B, + 0x9591, 0x8AD5, 0x9592, 0xFBE8, 0x9593, 0x8AD4, 0x9594, 0xE87B, 0x9596, 0xE87C, 0x9598, 0xE87D, 0x9599, 0xE87E, 0x95A0, 0xE880, + 0x95A2, 0x8AD6, 0x95A3, 0x8A74, 0x95A4, 0x8D7D, 0x95A5, 0x94B4, 0x95A7, 0xE882, 0x95A8, 0xE881, 0x95AD, 0xE883, 0x95B2, 0x897B, + 0x95B9, 0xE886, 0x95BB, 0xE885, 0x95BC, 0xE884, 0x95BE, 0xE887, 0x95C3, 0xE88A, 0x95C7, 0x88C5, 0x95CA, 0xE888, 0x95CC, 0xE88C, + 0x95CD, 0xE88B, 0x95D4, 0xE88E, 0x95D5, 0xE88D, 0x95D6, 0xE88F, 0x95D8, 0x93AC, 0x95DC, 0xE890, 0x95E1, 0xE891, 0x95E2, 0xE893, + 0x95E5, 0xE892, 0x961C, 0x958C, 0x9621, 0xE894, 0x9628, 0xE895, 0x962A, 0x8DE3, 0x962E, 0xE896, 0x962F, 0xE897, 0x9632, 0x9668, + 0x963B, 0x916A, 0x963F, 0x88A2, 0x9640, 0x91C9, 0x9642, 0xE898, 0x9644, 0x958D, 0x964B, 0xE89B, 0x964C, 0xE899, 0x964D, 0x8D7E, + 0x964F, 0xE89A, 0x9650, 0x8CC0, 0x965B, 0x95C3, 0x965C, 0xE89D, 0x965D, 0xE89F, 0x965E, 0xE89E, 0x965F, 0xE8A0, 0x9662, 0x8940, + 0x9663, 0x9077, 0x9664, 0x8F9C, 0x9665, 0x8AD7, 0x9666, 0xE8A1, 0x966A, 0x9486, 0x966C, 0xE8A3, 0x9670, 0x8941, 0x9672, 0xE8A2, + 0x9673, 0x92C2, 0x9675, 0x97CB, 0x9676, 0x93A9, 0x9677, 0xE89C, 0x9678, 0x97A4, 0x967A, 0x8CAF, 0x967D, 0x977A, 0x9685, 0x8BF7, + 0x9686, 0x97B2, 0x9688, 0x8C47, 0x968A, 0x91E0, 0x968B, 0xE440, 0x968D, 0xE8A4, 0x968E, 0x8A4B, 0x968F, 0x908F, 0x9694, 0x8A75, + 0x9695, 0xE8A6, 0x9697, 0xE8A7, 0x9698, 0xE8A5, 0x9699, 0x8C84, 0x969B, 0x8DDB, 0x969C, 0x8FE1, 0x969D, 0xFBEB, 0x96A0, 0x8942, + 0x96A3, 0x97D7, 0x96A7, 0xE8A9, 0x96A8, 0xE7AC, 0x96AA, 0xE8A8, 0x96AF, 0xFBEC, 0x96B0, 0xE8AC, 0x96B1, 0xE8AA, 0x96B2, 0xE8AB, + 0x96B4, 0xE8AD, 0x96B6, 0xE8AE, 0x96B7, 0x97EA, 0x96B8, 0xE8AF, 0x96B9, 0xE8B0, 0x96BB, 0x90C7, 0x96BC, 0x94B9, 0x96C0, 0x909D, + 0x96C1, 0x8AE5, 0x96C4, 0x9759, 0x96C5, 0x89EB, 0x96C6, 0x8F57, 0x96C7, 0x8CD9, 0x96C9, 0xE8B3, 0x96CB, 0xE8B2, 0x96CC, 0x8E93, + 0x96CD, 0xE8B4, 0x96CE, 0xE8B1, 0x96D1, 0x8E47, 0x96D5, 0xE8B8, 0x96D6, 0xE5AB, 0x96D9, 0x99D4, 0x96DB, 0x9097, 0x96DC, 0xE8B6, + 0x96E2, 0x97A3, 0x96E3, 0x93EF, 0x96E8, 0x894A, 0x96EA, 0x90E1, 0x96EB, 0x8EB4, 0x96F0, 0x95B5, 0x96F2, 0x895F, 0x96F6, 0x97EB, + 0x96F7, 0x978B, 0x96F9, 0xE8B9, 0x96FB, 0x9364, 0x9700, 0x8EF9, 0x9704, 0xE8BA, 0x9706, 0xE8BB, 0x9707, 0x906B, 0x9708, 0xE8BC, + 0x970A, 0x97EC, 0x970D, 0xE8B7, 0x970E, 0xE8BE, 0x970F, 0xE8C0, 0x9711, 0xE8BF, 0x9713, 0xE8BD, 0x9716, 0xE8C1, 0x9719, 0xE8C2, + 0x971C, 0x919A, 0x971E, 0x89E0, 0x9724, 0xE8C3, 0x9727, 0x96B6, 0x972A, 0xE8C4, 0x9730, 0xE8C5, 0x9732, 0x9849, 0x9733, 0xFBED, + 0x9738, 0x9E50, 0x9739, 0xE8C6, 0x973B, 0xFBEE, 0x973D, 0xE8C7, 0x973E, 0xE8C8, 0x9742, 0xE8CC, 0x9743, 0xFBEF, 0x9744, 0xE8C9, + 0x9746, 0xE8CA, 0x9748, 0xE8CB, 0x9749, 0xE8CD, 0x974D, 0xFBF0, 0x974F, 0xFBF1, 0x9751, 0xFBF2, 0x9752, 0x90C2, 0x9755, 0xFBF3, + 0x9756, 0x96F5, 0x9759, 0x90C3, 0x975C, 0xE8CE, 0x975E, 0x94F1, 0x9760, 0xE8CF, 0x9761, 0xEA72, 0x9762, 0x96CA, 0x9764, 0xE8D0, + 0x9766, 0xE8D1, 0x9768, 0xE8D2, 0x9769, 0x8A76, 0x976B, 0xE8D4, 0x976D, 0x9078, 0x9771, 0xE8D5, 0x9774, 0x8C43, 0x9779, 0xE8D6, + 0x977A, 0xE8DA, 0x977C, 0xE8D8, 0x9781, 0xE8D9, 0x9784, 0x8A93, 0x9785, 0xE8D7, 0x9786, 0xE8DB, 0x978B, 0xE8DC, 0x978D, 0x88C6, + 0x978F, 0xE8DD, 0x9790, 0xE8DE, 0x9798, 0x8FE2, 0x979C, 0xE8DF, 0x97A0, 0x8B66, 0x97A3, 0xE8E2, 0x97A6, 0xE8E1, 0x97A8, 0xE8E0, + 0x97AB, 0xE691, 0x97AD, 0x95DA, 0x97B3, 0xE8E3, 0x97B4, 0xE8E4, 0x97C3, 0xE8E5, 0x97C6, 0xE8E6, 0x97C8, 0xE8E7, 0x97CB, 0xE8E8, + 0x97D3, 0x8AD8, 0x97DC, 0xE8E9, 0x97ED, 0xE8EA, 0x97EE, 0x9442, 0x97F2, 0xE8EC, 0x97F3, 0x89B9, 0x97F5, 0xE8EF, 0x97F6, 0xE8EE, + 0x97FB, 0x8943, 0x97FF, 0x8BBF, 0x9801, 0x95C5, 0x9802, 0x92B8, 0x9803, 0x8DA0, 0x9805, 0x8D80, 0x9806, 0x8F87, 0x9808, 0x907B, + 0x980C, 0xE8F1, 0x980F, 0xE8F0, 0x9810, 0x9761, 0x9811, 0x8AE6, 0x9812, 0x94D0, 0x9813, 0x93DA, 0x9817, 0x909C, 0x9818, 0x97CC, + 0x981A, 0x8C7A, 0x9821, 0xE8F4, 0x9824, 0xE8F3, 0x982C, 0x966A, 0x982D, 0x93AA, 0x9834, 0x896F, 0x9837, 0xE8F5, 0x9838, 0xE8F2, + 0x983B, 0x9570, 0x983C, 0x978A, 0x983D, 0xE8F6, 0x9846, 0xE8F7, 0x984B, 0xE8F9, 0x984C, 0x91E8, 0x984D, 0x8A7A, 0x984E, 0x8A7B, + 0x984F, 0xE8F8, 0x9854, 0x8AE7, 0x9855, 0x8CB0, 0x9857, 0xFBF4, 0x9858, 0x8AE8, 0x985B, 0x935E, 0x985E, 0x97DE, 0x9865, 0xFBF5, + 0x9867, 0x8CDA, 0x986B, 0xE8FA, 0x986F, 0xE8FB, 0x9870, 0xE8FC, 0x9871, 0xE940, 0x9873, 0xE942, 0x9874, 0xE941, 0x98A8, 0x9597, + 0x98AA, 0xE943, 0x98AF, 0xE944, 0x98B1, 0xE945, 0x98B6, 0xE946, 0x98C3, 0xE948, 0x98C4, 0xE947, 0x98C6, 0xE949, 0x98DB, 0x94F2, + 0x98DC, 0xE3CA, 0x98DF, 0x9048, 0x98E2, 0x8B51, 0x98E9, 0xE94A, 0x98EB, 0xE94B, 0x98ED, 0x99AA, 0x98EE, 0x9F5A, 0x98EF, 0x94D1, + 0x98F2, 0x88F9, 0x98F4, 0x88B9, 0x98FC, 0x8E94, 0x98FD, 0x964F, 0x98FE, 0x8FFC, 0x9903, 0xE94C, 0x9905, 0x96DD, 0x9909, 0xE94D, + 0x990A, 0x977B, 0x990C, 0x8961, 0x9910, 0x8E60, 0x9912, 0xE94E, 0x9913, 0x89EC, 0x9914, 0xE94F, 0x9918, 0xE950, 0x991D, 0xE952, + 0x991E, 0xE953, 0x9920, 0xE955, 0x9921, 0xE951, 0x9924, 0xE954, 0x9927, 0xFBF8, 0x9928, 0x8AD9, 0x992C, 0xE956, 0x992E, 0xE957, + 0x993D, 0xE958, 0x993E, 0xE959, 0x9942, 0xE95A, 0x9945, 0xE95C, 0x9949, 0xE95B, 0x994B, 0xE95E, 0x994C, 0xE961, 0x9950, 0xE95D, + 0x9951, 0xE95F, 0x9952, 0xE960, 0x9955, 0xE962, 0x9957, 0x8BC0, 0x9996, 0x8EF1, 0x9997, 0xE963, 0x9998, 0xE964, 0x9999, 0x8D81, + 0x999E, 0xFBFA, 0x99A5, 0xE965, 0x99A8, 0x8A5D, 0x99AC, 0x946E, 0x99AD, 0xE966, 0x99AE, 0xE967, 0x99B3, 0x9279, 0x99B4, 0x93E9, + 0x99BC, 0xE968, 0x99C1, 0x949D, 0x99C4, 0x91CA, 0x99C5, 0x8977, 0x99C6, 0x8BEC, 0x99C8, 0x8BED, 0x99D0, 0x9293, 0x99D1, 0xE96D, + 0x99D2, 0x8BEE, 0x99D5, 0x89ED, 0x99D8, 0xE96C, 0x99DB, 0xE96A, 0x99DD, 0xE96B, 0x99DF, 0xE969, 0x99E2, 0xE977, 0x99ED, 0xE96E, + 0x99EE, 0xE96F, 0x99F1, 0xE970, 0x99F2, 0xE971, 0x99F8, 0xE973, 0x99FB, 0xE972, 0x99FF, 0x8F78, 0x9A01, 0xE974, 0x9A05, 0xE976, + 0x9A0E, 0x8B52, 0x9A0F, 0xE975, 0x9A12, 0x919B, 0x9A13, 0x8CB1, 0x9A19, 0xE978, 0x9A28, 0x91CB, 0x9A2B, 0xE979, 0x9A30, 0x93AB, + 0x9A37, 0xE97A, 0x9A3E, 0xE980, 0x9A40, 0xE97D, 0x9A42, 0xE97C, 0x9A43, 0xE97E, 0x9A45, 0xE97B, 0x9A4D, 0xE982, 0x9A4E, 0xFBFB, + 0x9A55, 0xE981, 0x9A57, 0xE984, 0x9A5A, 0x8BC1, 0x9A5B, 0xE983, 0x9A5F, 0xE985, 0x9A62, 0xE986, 0x9A64, 0xE988, 0x9A65, 0xE987, + 0x9A69, 0xE989, 0x9A6A, 0xE98B, 0x9A6B, 0xE98A, 0x9AA8, 0x8D9C, 0x9AAD, 0xE98C, 0x9AB0, 0xE98D, 0x9AB8, 0x8A5B, 0x9ABC, 0xE98E, + 0x9AC0, 0xE98F, 0x9AC4, 0x9091, 0x9ACF, 0xE990, 0x9AD1, 0xE991, 0x9AD3, 0xE992, 0x9AD4, 0xE993, 0x9AD8, 0x8D82, 0x9AD9, 0xFBFC, + 0x9ADC, 0xFC40, 0x9ADE, 0xE994, 0x9ADF, 0xE995, 0x9AE2, 0xE996, 0x9AE3, 0xE997, 0x9AE6, 0xE998, 0x9AEA, 0x94AF, 0x9AEB, 0xE99A, + 0x9AED, 0x9545, 0x9AEE, 0xE99B, 0x9AEF, 0xE999, 0x9AF1, 0xE99D, 0x9AF4, 0xE99C, 0x9AF7, 0xE99E, 0x9AFB, 0xE99F, 0x9B06, 0xE9A0, + 0x9B18, 0xE9A1, 0x9B1A, 0xE9A2, 0x9B1F, 0xE9A3, 0x9B22, 0xE9A4, 0x9B23, 0xE9A5, 0x9B25, 0xE9A6, 0x9B27, 0xE9A7, 0x9B28, 0xE9A8, + 0x9B29, 0xE9A9, 0x9B2A, 0xE9AA, 0x9B2E, 0xE9AB, 0x9B2F, 0xE9AC, 0x9B31, 0x9F54, 0x9B32, 0xE9AD, 0x9B3B, 0xE2F6, 0x9B3C, 0x8B53, + 0x9B41, 0x8A40, 0x9B42, 0x8DB0, 0x9B43, 0xE9AF, 0x9B44, 0xE9AE, 0x9B45, 0x96A3, 0x9B4D, 0xE9B1, 0x9B4E, 0xE9B2, 0x9B4F, 0xE9B0, + 0x9B51, 0xE9B3, 0x9B54, 0x9682, 0x9B58, 0xE9B4, 0x9B5A, 0x8B9B, 0x9B6F, 0x9844, 0x9B72, 0xFC42, 0x9B74, 0xE9B5, 0x9B75, 0xFC41, + 0x9B83, 0xE9B7, 0x9B8E, 0x88BC, 0x9B8F, 0xFC43, 0x9B91, 0xE9B8, 0x9B92, 0x95A9, 0x9B93, 0xE9B6, 0x9B96, 0xE9B9, 0x9B97, 0xE9BA, + 0x9B9F, 0xE9BB, 0x9BA0, 0xE9BC, 0x9BA8, 0xE9BD, 0x9BAA, 0x968E, 0x9BAB, 0x8E4C, 0x9BAD, 0x8DF8, 0x9BAE, 0x914E, 0x9BB1, 0xFC44, + 0x9BB4, 0xE9BE, 0x9BB9, 0xE9C1, 0x9BBB, 0xFC45, 0x9BC0, 0xE9BF, 0x9BC6, 0xE9C2, 0x9BC9, 0x8CEF, 0x9BCA, 0xE9C0, 0x9BCF, 0xE9C3, + 0x9BD1, 0xE9C4, 0x9BD2, 0xE9C5, 0x9BD4, 0xE9C9, 0x9BD6, 0x8E49, 0x9BDB, 0x91E2, 0x9BE1, 0xE9CA, 0x9BE2, 0xE9C7, 0x9BE3, 0xE9C6, + 0x9BE4, 0xE9C8, 0x9BE8, 0x8C7E, 0x9BF0, 0xE9CE, 0x9BF1, 0xE9CD, 0x9BF2, 0xE9CC, 0x9BF5, 0x88B1, 0x9C00, 0xFC46, 0x9C04, 0xE9D8, + 0x9C06, 0xE9D4, 0x9C08, 0xE9D5, 0x9C09, 0xE9D1, 0x9C0A, 0xE9D7, 0x9C0C, 0xE9D3, 0x9C0D, 0x8A82, 0x9C10, 0x986B, 0x9C12, 0xE9D6, + 0x9C13, 0xE9D2, 0x9C14, 0xE9D0, 0x9C15, 0xE9CF, 0x9C1B, 0xE9DA, 0x9C21, 0xE9DD, 0x9C24, 0xE9DC, 0x9C25, 0xE9DB, 0x9C2D, 0x9568, + 0x9C2E, 0xE9D9, 0x9C2F, 0x88F1, 0x9C30, 0xE9DE, 0x9C32, 0xE9E0, 0x9C39, 0x8A8F, 0x9C3A, 0xE9CB, 0x9C3B, 0x8956, 0x9C3E, 0xE9E2, + 0x9C46, 0xE9E1, 0x9C47, 0xE9DF, 0x9C48, 0x924C, 0x9C52, 0x9690, 0x9C57, 0x97D8, 0x9C5A, 0xE9E3, 0x9C60, 0xE9E4, 0x9C67, 0xE9E5, + 0x9C76, 0xE9E6, 0x9C78, 0xE9E7, 0x9CE5, 0x92B9, 0x9CE7, 0xE9E8, 0x9CE9, 0x94B5, 0x9CEB, 0xE9ED, 0x9CEC, 0xE9E9, 0x9CF0, 0xE9EA, + 0x9CF3, 0x9650, 0x9CF4, 0x96C2, 0x9CF6, 0x93CE, 0x9D03, 0xE9EE, 0x9D06, 0xE9EF, 0x9D07, 0x93BC, 0x9D08, 0xE9EC, 0x9D09, 0xE9EB, + 0x9D0E, 0x89A8, 0x9D12, 0xE9F7, 0x9D15, 0xE9F6, 0x9D1B, 0x8995, 0x9D1F, 0xE9F4, 0x9D23, 0xE9F3, 0x9D26, 0xE9F1, 0x9D28, 0x8A9B, + 0x9D2A, 0xE9F0, 0x9D2B, 0x8EB0, 0x9D2C, 0x89A7, 0x9D3B, 0x8D83, 0x9D3E, 0xE9FA, 0x9D3F, 0xE9F9, 0x9D41, 0xE9F8, 0x9D44, 0xE9F5, + 0x9D46, 0xE9FB, 0x9D48, 0xE9FC, 0x9D50, 0xEA44, 0x9D51, 0xEA43, 0x9D59, 0xEA45, 0x9D5C, 0x894C, 0x9D5D, 0xEA40, 0x9D5E, 0xEA41, + 0x9D60, 0x8D94, 0x9D61, 0x96B7, 0x9D64, 0xEA42, 0x9D6B, 0xFC48, 0x9D6C, 0x9651, 0x9D6F, 0xEA4A, 0x9D70, 0xFC47, 0x9D72, 0xEA46, + 0x9D7A, 0xEA4B, 0x9D87, 0xEA48, 0x9D89, 0xEA47, 0x9D8F, 0x8C7B, 0x9D9A, 0xEA4C, 0x9DA4, 0xEA4D, 0x9DA9, 0xEA4E, 0x9DAB, 0xEA49, + 0x9DAF, 0xE9F2, 0x9DB2, 0xEA4F, 0x9DB4, 0x92DF, 0x9DB8, 0xEA53, 0x9DBA, 0xEA54, 0x9DBB, 0xEA52, 0x9DC1, 0xEA51, 0x9DC2, 0xEA57, + 0x9DC4, 0xEA50, 0x9DC6, 0xEA55, 0x9DCF, 0xEA56, 0x9DD3, 0xEA59, 0x9DD9, 0xEA58, 0x9DE6, 0xEA5B, 0x9DED, 0xEA5C, 0x9DEF, 0xEA5D, + 0x9DF2, 0x9868, 0x9DF8, 0xEA5A, 0x9DF9, 0x91E9, 0x9DFA, 0x8DEB, 0x9DFD, 0xEA5E, 0x9E19, 0xFC4A, 0x9E1A, 0xEA5F, 0x9E1B, 0xEA60, + 0x9E1E, 0xEA61, 0x9E75, 0xEA62, 0x9E78, 0x8CB2, 0x9E79, 0xEA63, 0x9E7D, 0xEA64, 0x9E7F, 0x8EAD, 0x9E81, 0xEA65, 0x9E88, 0xEA66, + 0x9E8B, 0xEA67, 0x9E8C, 0xEA68, 0x9E91, 0xEA6B, 0x9E92, 0xEA69, 0x9E93, 0x985B, 0x9E95, 0xEA6A, 0x9E97, 0x97ED, 0x9E9D, 0xEA6C, + 0x9E9F, 0x97D9, 0x9EA5, 0xEA6D, 0x9EA6, 0x949E, 0x9EA9, 0xEA6E, 0x9EAA, 0xEA70, 0x9EAD, 0xEA71, 0x9EB8, 0xEA6F, 0x9EB9, 0x8D8D, + 0x9EBA, 0x96CB, 0x9EBB, 0x9683, 0x9EBC, 0x9BF5, 0x9EBE, 0x9F80, 0x9EBF, 0x969B, 0x9EC4, 0x89A9, 0x9ECC, 0xEA73, 0x9ECD, 0x8B6F, + 0x9ECE, 0xEA74, 0x9ECF, 0xEA75, 0x9ED0, 0xEA76, 0x9ED1, 0xFC4B, 0x9ED2, 0x8D95, 0x9ED4, 0xEA77, 0x9ED8, 0xE0D2, 0x9ED9, 0x96D9, + 0x9EDB, 0x91E1, 0x9EDC, 0xEA78, 0x9EDD, 0xEA7A, 0x9EDE, 0xEA79, 0x9EE0, 0xEA7B, 0x9EE5, 0xEA7C, 0x9EE8, 0xEA7D, 0x9EEF, 0xEA7E, + 0x9EF4, 0xEA80, 0x9EF6, 0xEA81, 0x9EF7, 0xEA82, 0x9EF9, 0xEA83, 0x9EFB, 0xEA84, 0x9EFC, 0xEA85, 0x9EFD, 0xEA86, 0x9F07, 0xEA87, + 0x9F08, 0xEA88, 0x9F0E, 0x9343, 0x9F13, 0x8CDB, 0x9F15, 0xEA8A, 0x9F20, 0x916C, 0x9F21, 0xEA8B, 0x9F2C, 0xEA8C, 0x9F3B, 0x9540, + 0x9F3E, 0xEA8D, 0x9F4A, 0xEA8E, 0x9F4B, 0xE256, 0x9F4E, 0xE6D8, 0x9F4F, 0xE8EB, 0x9F52, 0xEA8F, 0x9F54, 0xEA90, 0x9F5F, 0xEA92, + 0x9F60, 0xEA93, 0x9F61, 0xEA94, 0x9F62, 0x97EE, 0x9F63, 0xEA91, 0x9F66, 0xEA95, 0x9F67, 0xEA96, 0x9F6A, 0xEA98, 0x9F6C, 0xEA97, + 0x9F72, 0xEA9A, 0x9F76, 0xEA9B, 0x9F77, 0xEA99, 0x9F8D, 0x97B4, 0x9F95, 0xEA9C, 0x9F9C, 0xEA9D, 0x9F9D, 0xE273, 0x9FA0, 0xEA9E, + 0xF929, 0xFAE0, 0xF9DC, 0xFBE9, 0xFA0E, 0xFA90, 0xFA0F, 0xFA9B, 0xFA10, 0xFA9C, 0xFA11, 0xFAB1, 0xFA12, 0xFAD8, 0xFA13, 0xFAE8, + 0xFA14, 0xFAEA, 0xFA15, 0xFB58, 0xFA16, 0xFB5E, 0xFA17, 0xFB75, 0xFA18, 0xFB7D, 0xFA19, 0xFB7E, 0xFA1A, 0xFB80, 0xFA1B, 0xFB82, + 0xFA1C, 0xFB86, 0xFA1D, 0xFB89, 0xFA1E, 0xFB92, 0xFA1F, 0xFB9D, 0xFA20, 0xFB9F, 0xFA21, 0xFBA0, 0xFA22, 0xFBA9, 0xFA23, 0xFBB1, + 0xFA24, 0xFBB3, 0xFA25, 0xFBB4, 0xFA26, 0xFBB7, 0xFA27, 0xFBD3, 0xFA28, 0xFBDA, 0xFA29, 0xFBEA, 0xFA2A, 0xFBF6, 0xFA2B, 0xFBF7, + 0xFA2C, 0xFBF9, 0xFA2D, 0xFC49, 0xFF01, 0x8149, 0xFF02, 0xFA57, 0xFF03, 0x8194, 0xFF04, 0x8190, 0xFF05, 0x8193, 0xFF06, 0x8195, + 0xFF07, 0xFA56, 0xFF08, 0x8169, 0xFF09, 0x816A, 0xFF0A, 0x8196, 0xFF0B, 0x817B, 0xFF0C, 0x8143, 0xFF0D, 0x817C, 0xFF0E, 0x8144, + 0xFF0F, 0x815E, 0xFF10, 0x824F, 0xFF11, 0x8250, 0xFF12, 0x8251, 0xFF13, 0x8252, 0xFF14, 0x8253, 0xFF15, 0x8254, 0xFF16, 0x8255, + 0xFF17, 0x8256, 0xFF18, 0x8257, 0xFF19, 0x8258, 0xFF1A, 0x8146, 0xFF1B, 0x8147, 0xFF1C, 0x8183, 0xFF1D, 0x8181, 0xFF1E, 0x8184, + 0xFF1F, 0x8148, 0xFF20, 0x8197, 0xFF21, 0x8260, 0xFF22, 0x8261, 0xFF23, 0x8262, 0xFF24, 0x8263, 0xFF25, 0x8264, 0xFF26, 0x8265, + 0xFF27, 0x8266, 0xFF28, 0x8267, 0xFF29, 0x8268, 0xFF2A, 0x8269, 0xFF2B, 0x826A, 0xFF2C, 0x826B, 0xFF2D, 0x826C, 0xFF2E, 0x826D, + 0xFF2F, 0x826E, 0xFF30, 0x826F, 0xFF31, 0x8270, 0xFF32, 0x8271, 0xFF33, 0x8272, 0xFF34, 0x8273, 0xFF35, 0x8274, 0xFF36, 0x8275, + 0xFF37, 0x8276, 0xFF38, 0x8277, 0xFF39, 0x8278, 0xFF3A, 0x8279, 0xFF3B, 0x816D, 0xFF3C, 0x815F, 0xFF3D, 0x816E, 0xFF3E, 0x814F, + 0xFF3F, 0x8151, 0xFF40, 0x814D, 0xFF41, 0x8281, 0xFF42, 0x8282, 0xFF43, 0x8283, 0xFF44, 0x8284, 0xFF45, 0x8285, 0xFF46, 0x8286, + 0xFF47, 0x8287, 0xFF48, 0x8288, 0xFF49, 0x8289, 0xFF4A, 0x828A, 0xFF4B, 0x828B, 0xFF4C, 0x828C, 0xFF4D, 0x828D, 0xFF4E, 0x828E, + 0xFF4F, 0x828F, 0xFF50, 0x8290, 0xFF51, 0x8291, 0xFF52, 0x8292, 0xFF53, 0x8293, 0xFF54, 0x8294, 0xFF55, 0x8295, 0xFF56, 0x8296, + 0xFF57, 0x8297, 0xFF58, 0x8298, 0xFF59, 0x8299, 0xFF5A, 0x829A, 0xFF5B, 0x816F, 0xFF5C, 0x8162, 0xFF5D, 0x8170, 0xFF5E, 0x8160, + 0xFF61, 0x00A1, 0xFF62, 0x00A2, 0xFF63, 0x00A3, 0xFF64, 0x00A4, 0xFF65, 0x00A5, 0xFF66, 0x00A6, 0xFF67, 0x00A7, 0xFF68, 0x00A8, + 0xFF69, 0x00A9, 0xFF6A, 0x00AA, 0xFF6B, 0x00AB, 0xFF6C, 0x00AC, 0xFF6D, 0x00AD, 0xFF6E, 0x00AE, 0xFF6F, 0x00AF, 0xFF70, 0x00B0, + 0xFF71, 0x00B1, 0xFF72, 0x00B2, 0xFF73, 0x00B3, 0xFF74, 0x00B4, 0xFF75, 0x00B5, 0xFF76, 0x00B6, 0xFF77, 0x00B7, 0xFF78, 0x00B8, + 0xFF79, 0x00B9, 0xFF7A, 0x00BA, 0xFF7B, 0x00BB, 0xFF7C, 0x00BC, 0xFF7D, 0x00BD, 0xFF7E, 0x00BE, 0xFF7F, 0x00BF, 0xFF80, 0x00C0, + 0xFF81, 0x00C1, 0xFF82, 0x00C2, 0xFF83, 0x00C3, 0xFF84, 0x00C4, 0xFF85, 0x00C5, 0xFF86, 0x00C6, 0xFF87, 0x00C7, 0xFF88, 0x00C8, + 0xFF89, 0x00C9, 0xFF8A, 0x00CA, 0xFF8B, 0x00CB, 0xFF8C, 0x00CC, 0xFF8D, 0x00CD, 0xFF8E, 0x00CE, 0xFF8F, 0x00CF, 0xFF90, 0x00D0, + 0xFF91, 0x00D1, 0xFF92, 0x00D2, 0xFF93, 0x00D3, 0xFF94, 0x00D4, 0xFF95, 0x00D5, 0xFF96, 0x00D6, 0xFF97, 0x00D7, 0xFF98, 0x00D8, + 0xFF99, 0x00D9, 0xFF9A, 0x00DA, 0xFF9B, 0x00DB, 0xFF9C, 0x00DC, 0xFF9D, 0x00DD, 0xFF9E, 0x00DE, 0xFF9F, 0x00DF, 0xFFE0, 0x8191, + 0xFFE1, 0x8192, 0xFFE2, 0x81CA, 0xFFE3, 0x8150, 0xFFE4, 0xFA55, 0xFFE5, 0x818F, 0, 0 +}; + +static const WCHAR oem2uni932[] = { /* Shift_JIS --> Unicode pairs */ + 0x00A1, 0xFF61, 0x00A2, 0xFF62, 0x00A3, 0xFF63, 0x00A4, 0xFF64, 0x00A5, 0xFF65, 0x00A6, 0xFF66, 0x00A7, 0xFF67, 0x00A8, 0xFF68, + 0x00A9, 0xFF69, 0x00AA, 0xFF6A, 0x00AB, 0xFF6B, 0x00AC, 0xFF6C, 0x00AD, 0xFF6D, 0x00AE, 0xFF6E, 0x00AF, 0xFF6F, 0x00B0, 0xFF70, + 0x00B1, 0xFF71, 0x00B2, 0xFF72, 0x00B3, 0xFF73, 0x00B4, 0xFF74, 0x00B5, 0xFF75, 0x00B6, 0xFF76, 0x00B7, 0xFF77, 0x00B8, 0xFF78, + 0x00B9, 0xFF79, 0x00BA, 0xFF7A, 0x00BB, 0xFF7B, 0x00BC, 0xFF7C, 0x00BD, 0xFF7D, 0x00BE, 0xFF7E, 0x00BF, 0xFF7F, 0x00C0, 0xFF80, + 0x00C1, 0xFF81, 0x00C2, 0xFF82, 0x00C3, 0xFF83, 0x00C4, 0xFF84, 0x00C5, 0xFF85, 0x00C6, 0xFF86, 0x00C7, 0xFF87, 0x00C8, 0xFF88, + 0x00C9, 0xFF89, 0x00CA, 0xFF8A, 0x00CB, 0xFF8B, 0x00CC, 0xFF8C, 0x00CD, 0xFF8D, 0x00CE, 0xFF8E, 0x00CF, 0xFF8F, 0x00D0, 0xFF90, + 0x00D1, 0xFF91, 0x00D2, 0xFF92, 0x00D3, 0xFF93, 0x00D4, 0xFF94, 0x00D5, 0xFF95, 0x00D6, 0xFF96, 0x00D7, 0xFF97, 0x00D8, 0xFF98, + 0x00D9, 0xFF99, 0x00DA, 0xFF9A, 0x00DB, 0xFF9B, 0x00DC, 0xFF9C, 0x00DD, 0xFF9D, 0x00DE, 0xFF9E, 0x00DF, 0xFF9F, 0x8140, 0x3000, + 0x8141, 0x3001, 0x8142, 0x3002, 0x8143, 0xFF0C, 0x8144, 0xFF0E, 0x8145, 0x30FB, 0x8146, 0xFF1A, 0x8147, 0xFF1B, 0x8148, 0xFF1F, + 0x8149, 0xFF01, 0x814A, 0x309B, 0x814B, 0x309C, 0x814C, 0x00B4, 0x814D, 0xFF40, 0x814E, 0x00A8, 0x814F, 0xFF3E, 0x8150, 0xFFE3, + 0x8151, 0xFF3F, 0x8152, 0x30FD, 0x8153, 0x30FE, 0x8154, 0x309D, 0x8155, 0x309E, 0x8156, 0x3003, 0x8157, 0x4EDD, 0x8158, 0x3005, + 0x8159, 0x3006, 0x815A, 0x3007, 0x815B, 0x30FC, 0x815C, 0x2015, 0x815D, 0x2010, 0x815E, 0xFF0F, 0x815F, 0xFF3C, 0x8160, 0xFF5E, + 0x8161, 0x2225, 0x8162, 0xFF5C, 0x8163, 0x2026, 0x8164, 0x2025, 0x8165, 0x2018, 0x8166, 0x2019, 0x8167, 0x201C, 0x8168, 0x201D, + 0x8169, 0xFF08, 0x816A, 0xFF09, 0x816B, 0x3014, 0x816C, 0x3015, 0x816D, 0xFF3B, 0x816E, 0xFF3D, 0x816F, 0xFF5B, 0x8170, 0xFF5D, + 0x8171, 0x3008, 0x8172, 0x3009, 0x8173, 0x300A, 0x8174, 0x300B, 0x8175, 0x300C, 0x8176, 0x300D, 0x8177, 0x300E, 0x8178, 0x300F, + 0x8179, 0x3010, 0x817A, 0x3011, 0x817B, 0xFF0B, 0x817C, 0xFF0D, 0x817D, 0x00B1, 0x817E, 0x00D7, 0x8180, 0x00F7, 0x8181, 0xFF1D, + 0x8182, 0x2260, 0x8183, 0xFF1C, 0x8184, 0xFF1E, 0x8185, 0x2266, 0x8186, 0x2267, 0x8187, 0x221E, 0x8188, 0x2234, 0x8189, 0x2642, + 0x818A, 0x2640, 0x818B, 0x00B0, 0x818C, 0x2032, 0x818D, 0x2033, 0x818E, 0x2103, 0x818F, 0xFFE5, 0x8190, 0xFF04, 0x8191, 0xFFE0, + 0x8192, 0xFFE1, 0x8193, 0xFF05, 0x8194, 0xFF03, 0x8195, 0xFF06, 0x8196, 0xFF0A, 0x8197, 0xFF20, 0x8198, 0x00A7, 0x8199, 0x2606, + 0x819A, 0x2605, 0x819B, 0x25CB, 0x819C, 0x25CF, 0x819D, 0x25CE, 0x819E, 0x25C7, 0x819F, 0x25C6, 0x81A0, 0x25A1, 0x81A1, 0x25A0, + 0x81A2, 0x25B3, 0x81A3, 0x25B2, 0x81A4, 0x25BD, 0x81A5, 0x25BC, 0x81A6, 0x203B, 0x81A7, 0x3012, 0x81A8, 0x2192, 0x81A9, 0x2190, + 0x81AA, 0x2191, 0x81AB, 0x2193, 0x81AC, 0x3013, 0x81B8, 0x2208, 0x81B9, 0x220B, 0x81BA, 0x2286, 0x81BB, 0x2287, 0x81BC, 0x2282, + 0x81BD, 0x2283, 0x81BE, 0x222A, 0x81BF, 0x2229, 0x81C8, 0x2227, 0x81C9, 0x2228, 0x81CA, 0xFFE2, 0x81CB, 0x21D2, 0x81CC, 0x21D4, + 0x81CD, 0x2200, 0x81CE, 0x2203, 0x81DA, 0x2220, 0x81DB, 0x22A5, 0x81DC, 0x2312, 0x81DD, 0x2202, 0x81DE, 0x2207, 0x81DF, 0x2261, + 0x81E0, 0x2252, 0x81E1, 0x226A, 0x81E2, 0x226B, 0x81E3, 0x221A, 0x81E4, 0x223D, 0x81E5, 0x221D, 0x81E6, 0x2235, 0x81E7, 0x222B, + 0x81E8, 0x222C, 0x81F0, 0x212B, 0x81F1, 0x2030, 0x81F2, 0x266F, 0x81F3, 0x266D, 0x81F4, 0x266A, 0x81F5, 0x2020, 0x81F6, 0x2021, + 0x81F7, 0x00B6, 0x81FC, 0x25EF, 0x824F, 0xFF10, 0x8250, 0xFF11, 0x8251, 0xFF12, 0x8252, 0xFF13, 0x8253, 0xFF14, 0x8254, 0xFF15, + 0x8255, 0xFF16, 0x8256, 0xFF17, 0x8257, 0xFF18, 0x8258, 0xFF19, 0x8260, 0xFF21, 0x8261, 0xFF22, 0x8262, 0xFF23, 0x8263, 0xFF24, + 0x8264, 0xFF25, 0x8265, 0xFF26, 0x8266, 0xFF27, 0x8267, 0xFF28, 0x8268, 0xFF29, 0x8269, 0xFF2A, 0x826A, 0xFF2B, 0x826B, 0xFF2C, + 0x826C, 0xFF2D, 0x826D, 0xFF2E, 0x826E, 0xFF2F, 0x826F, 0xFF30, 0x8270, 0xFF31, 0x8271, 0xFF32, 0x8272, 0xFF33, 0x8273, 0xFF34, + 0x8274, 0xFF35, 0x8275, 0xFF36, 0x8276, 0xFF37, 0x8277, 0xFF38, 0x8278, 0xFF39, 0x8279, 0xFF3A, 0x8281, 0xFF41, 0x8282, 0xFF42, + 0x8283, 0xFF43, 0x8284, 0xFF44, 0x8285, 0xFF45, 0x8286, 0xFF46, 0x8287, 0xFF47, 0x8288, 0xFF48, 0x8289, 0xFF49, 0x828A, 0xFF4A, + 0x828B, 0xFF4B, 0x828C, 0xFF4C, 0x828D, 0xFF4D, 0x828E, 0xFF4E, 0x828F, 0xFF4F, 0x8290, 0xFF50, 0x8291, 0xFF51, 0x8292, 0xFF52, + 0x8293, 0xFF53, 0x8294, 0xFF54, 0x8295, 0xFF55, 0x8296, 0xFF56, 0x8297, 0xFF57, 0x8298, 0xFF58, 0x8299, 0xFF59, 0x829A, 0xFF5A, + 0x829F, 0x3041, 0x82A0, 0x3042, 0x82A1, 0x3043, 0x82A2, 0x3044, 0x82A3, 0x3045, 0x82A4, 0x3046, 0x82A5, 0x3047, 0x82A6, 0x3048, + 0x82A7, 0x3049, 0x82A8, 0x304A, 0x82A9, 0x304B, 0x82AA, 0x304C, 0x82AB, 0x304D, 0x82AC, 0x304E, 0x82AD, 0x304F, 0x82AE, 0x3050, + 0x82AF, 0x3051, 0x82B0, 0x3052, 0x82B1, 0x3053, 0x82B2, 0x3054, 0x82B3, 0x3055, 0x82B4, 0x3056, 0x82B5, 0x3057, 0x82B6, 0x3058, + 0x82B7, 0x3059, 0x82B8, 0x305A, 0x82B9, 0x305B, 0x82BA, 0x305C, 0x82BB, 0x305D, 0x82BC, 0x305E, 0x82BD, 0x305F, 0x82BE, 0x3060, + 0x82BF, 0x3061, 0x82C0, 0x3062, 0x82C1, 0x3063, 0x82C2, 0x3064, 0x82C3, 0x3065, 0x82C4, 0x3066, 0x82C5, 0x3067, 0x82C6, 0x3068, + 0x82C7, 0x3069, 0x82C8, 0x306A, 0x82C9, 0x306B, 0x82CA, 0x306C, 0x82CB, 0x306D, 0x82CC, 0x306E, 0x82CD, 0x306F, 0x82CE, 0x3070, + 0x82CF, 0x3071, 0x82D0, 0x3072, 0x82D1, 0x3073, 0x82D2, 0x3074, 0x82D3, 0x3075, 0x82D4, 0x3076, 0x82D5, 0x3077, 0x82D6, 0x3078, + 0x82D7, 0x3079, 0x82D8, 0x307A, 0x82D9, 0x307B, 0x82DA, 0x307C, 0x82DB, 0x307D, 0x82DC, 0x307E, 0x82DD, 0x307F, 0x82DE, 0x3080, + 0x82DF, 0x3081, 0x82E0, 0x3082, 0x82E1, 0x3083, 0x82E2, 0x3084, 0x82E3, 0x3085, 0x82E4, 0x3086, 0x82E5, 0x3087, 0x82E6, 0x3088, + 0x82E7, 0x3089, 0x82E8, 0x308A, 0x82E9, 0x308B, 0x82EA, 0x308C, 0x82EB, 0x308D, 0x82EC, 0x308E, 0x82ED, 0x308F, 0x82EE, 0x3090, + 0x82EF, 0x3091, 0x82F0, 0x3092, 0x82F1, 0x3093, 0x8340, 0x30A1, 0x8341, 0x30A2, 0x8342, 0x30A3, 0x8343, 0x30A4, 0x8344, 0x30A5, + 0x8345, 0x30A6, 0x8346, 0x30A7, 0x8347, 0x30A8, 0x8348, 0x30A9, 0x8349, 0x30AA, 0x834A, 0x30AB, 0x834B, 0x30AC, 0x834C, 0x30AD, + 0x834D, 0x30AE, 0x834E, 0x30AF, 0x834F, 0x30B0, 0x8350, 0x30B1, 0x8351, 0x30B2, 0x8352, 0x30B3, 0x8353, 0x30B4, 0x8354, 0x30B5, + 0x8355, 0x30B6, 0x8356, 0x30B7, 0x8357, 0x30B8, 0x8358, 0x30B9, 0x8359, 0x30BA, 0x835A, 0x30BB, 0x835B, 0x30BC, 0x835C, 0x30BD, + 0x835D, 0x30BE, 0x835E, 0x30BF, 0x835F, 0x30C0, 0x8360, 0x30C1, 0x8361, 0x30C2, 0x8362, 0x30C3, 0x8363, 0x30C4, 0x8364, 0x30C5, + 0x8365, 0x30C6, 0x8366, 0x30C7, 0x8367, 0x30C8, 0x8368, 0x30C9, 0x8369, 0x30CA, 0x836A, 0x30CB, 0x836B, 0x30CC, 0x836C, 0x30CD, + 0x836D, 0x30CE, 0x836E, 0x30CF, 0x836F, 0x30D0, 0x8370, 0x30D1, 0x8371, 0x30D2, 0x8372, 0x30D3, 0x8373, 0x30D4, 0x8374, 0x30D5, + 0x8375, 0x30D6, 0x8376, 0x30D7, 0x8377, 0x30D8, 0x8378, 0x30D9, 0x8379, 0x30DA, 0x837A, 0x30DB, 0x837B, 0x30DC, 0x837C, 0x30DD, + 0x837D, 0x30DE, 0x837E, 0x30DF, 0x8380, 0x30E0, 0x8381, 0x30E1, 0x8382, 0x30E2, 0x8383, 0x30E3, 0x8384, 0x30E4, 0x8385, 0x30E5, + 0x8386, 0x30E6, 0x8387, 0x30E7, 0x8388, 0x30E8, 0x8389, 0x30E9, 0x838A, 0x30EA, 0x838B, 0x30EB, 0x838C, 0x30EC, 0x838D, 0x30ED, + 0x838E, 0x30EE, 0x838F, 0x30EF, 0x8390, 0x30F0, 0x8391, 0x30F1, 0x8392, 0x30F2, 0x8393, 0x30F3, 0x8394, 0x30F4, 0x8395, 0x30F5, + 0x8396, 0x30F6, 0x839F, 0x0391, 0x83A0, 0x0392, 0x83A1, 0x0393, 0x83A2, 0x0394, 0x83A3, 0x0395, 0x83A4, 0x0396, 0x83A5, 0x0397, + 0x83A6, 0x0398, 0x83A7, 0x0399, 0x83A8, 0x039A, 0x83A9, 0x039B, 0x83AA, 0x039C, 0x83AB, 0x039D, 0x83AC, 0x039E, 0x83AD, 0x039F, + 0x83AE, 0x03A0, 0x83AF, 0x03A1, 0x83B0, 0x03A3, 0x83B1, 0x03A4, 0x83B2, 0x03A5, 0x83B3, 0x03A6, 0x83B4, 0x03A7, 0x83B5, 0x03A8, + 0x83B6, 0x03A9, 0x83BF, 0x03B1, 0x83C0, 0x03B2, 0x83C1, 0x03B3, 0x83C2, 0x03B4, 0x83C3, 0x03B5, 0x83C4, 0x03B6, 0x83C5, 0x03B7, + 0x83C6, 0x03B8, 0x83C7, 0x03B9, 0x83C8, 0x03BA, 0x83C9, 0x03BB, 0x83CA, 0x03BC, 0x83CB, 0x03BD, 0x83CC, 0x03BE, 0x83CD, 0x03BF, + 0x83CE, 0x03C0, 0x83CF, 0x03C1, 0x83D0, 0x03C3, 0x83D1, 0x03C4, 0x83D2, 0x03C5, 0x83D3, 0x03C6, 0x83D4, 0x03C7, 0x83D5, 0x03C8, + 0x83D6, 0x03C9, 0x8440, 0x0410, 0x8441, 0x0411, 0x8442, 0x0412, 0x8443, 0x0413, 0x8444, 0x0414, 0x8445, 0x0415, 0x8446, 0x0401, + 0x8447, 0x0416, 0x8448, 0x0417, 0x8449, 0x0418, 0x844A, 0x0419, 0x844B, 0x041A, 0x844C, 0x041B, 0x844D, 0x041C, 0x844E, 0x041D, + 0x844F, 0x041E, 0x8450, 0x041F, 0x8451, 0x0420, 0x8452, 0x0421, 0x8453, 0x0422, 0x8454, 0x0423, 0x8455, 0x0424, 0x8456, 0x0425, + 0x8457, 0x0426, 0x8458, 0x0427, 0x8459, 0x0428, 0x845A, 0x0429, 0x845B, 0x042A, 0x845C, 0x042B, 0x845D, 0x042C, 0x845E, 0x042D, + 0x845F, 0x042E, 0x8460, 0x042F, 0x8470, 0x0430, 0x8471, 0x0431, 0x8472, 0x0432, 0x8473, 0x0433, 0x8474, 0x0434, 0x8475, 0x0435, + 0x8476, 0x0451, 0x8477, 0x0436, 0x8478, 0x0437, 0x8479, 0x0438, 0x847A, 0x0439, 0x847B, 0x043A, 0x847C, 0x043B, 0x847D, 0x043C, + 0x847E, 0x043D, 0x8480, 0x043E, 0x8481, 0x043F, 0x8482, 0x0440, 0x8483, 0x0441, 0x8484, 0x0442, 0x8485, 0x0443, 0x8486, 0x0444, + 0x8487, 0x0445, 0x8488, 0x0446, 0x8489, 0x0447, 0x848A, 0x0448, 0x848B, 0x0449, 0x848C, 0x044A, 0x848D, 0x044B, 0x848E, 0x044C, + 0x848F, 0x044D, 0x8490, 0x044E, 0x8491, 0x044F, 0x849F, 0x2500, 0x84A0, 0x2502, 0x84A1, 0x250C, 0x84A2, 0x2510, 0x84A3, 0x2518, + 0x84A4, 0x2514, 0x84A5, 0x251C, 0x84A6, 0x252C, 0x84A7, 0x2524, 0x84A8, 0x2534, 0x84A9, 0x253C, 0x84AA, 0x2501, 0x84AB, 0x2503, + 0x84AC, 0x250F, 0x84AD, 0x2513, 0x84AE, 0x251B, 0x84AF, 0x2517, 0x84B0, 0x2523, 0x84B1, 0x2533, 0x84B2, 0x252B, 0x84B3, 0x253B, + 0x84B4, 0x254B, 0x84B5, 0x2520, 0x84B6, 0x252F, 0x84B7, 0x2528, 0x84B8, 0x2537, 0x84B9, 0x253F, 0x84BA, 0x251D, 0x84BB, 0x2530, + 0x84BC, 0x2525, 0x84BD, 0x2538, 0x84BE, 0x2542, 0x8740, 0x2460, 0x8741, 0x2461, 0x8742, 0x2462, 0x8743, 0x2463, 0x8744, 0x2464, + 0x8745, 0x2465, 0x8746, 0x2466, 0x8747, 0x2467, 0x8748, 0x2468, 0x8749, 0x2469, 0x874A, 0x246A, 0x874B, 0x246B, 0x874C, 0x246C, + 0x874D, 0x246D, 0x874E, 0x246E, 0x874F, 0x246F, 0x8750, 0x2470, 0x8751, 0x2471, 0x8752, 0x2472, 0x8753, 0x2473, 0x8754, 0x2160, + 0x8755, 0x2161, 0x8756, 0x2162, 0x8757, 0x2163, 0x8758, 0x2164, 0x8759, 0x2165, 0x875A, 0x2166, 0x875B, 0x2167, 0x875C, 0x2168, + 0x875D, 0x2169, 0x875F, 0x3349, 0x8760, 0x3314, 0x8761, 0x3322, 0x8762, 0x334D, 0x8763, 0x3318, 0x8764, 0x3327, 0x8765, 0x3303, + 0x8766, 0x3336, 0x8767, 0x3351, 0x8768, 0x3357, 0x8769, 0x330D, 0x876A, 0x3326, 0x876B, 0x3323, 0x876C, 0x332B, 0x876D, 0x334A, + 0x876E, 0x333B, 0x876F, 0x339C, 0x8770, 0x339D, 0x8771, 0x339E, 0x8772, 0x338E, 0x8773, 0x338F, 0x8774, 0x33C4, 0x8775, 0x33A1, + 0x877E, 0x337B, 0x8780, 0x301D, 0x8781, 0x301F, 0x8782, 0x2116, 0x8783, 0x33CD, 0x8784, 0x2121, 0x8785, 0x32A4, 0x8786, 0x32A5, + 0x8787, 0x32A6, 0x8788, 0x32A7, 0x8789, 0x32A8, 0x878A, 0x3231, 0x878B, 0x3232, 0x878C, 0x3239, 0x878D, 0x337E, 0x878E, 0x337D, + 0x878F, 0x337C, 0x8793, 0x222E, 0x8794, 0x2211, 0x8798, 0x221F, 0x8799, 0x22BF, 0x889F, 0x4E9C, 0x88A0, 0x5516, 0x88A1, 0x5A03, + 0x88A2, 0x963F, 0x88A3, 0x54C0, 0x88A4, 0x611B, 0x88A5, 0x6328, 0x88A6, 0x59F6, 0x88A7, 0x9022, 0x88A8, 0x8475, 0x88A9, 0x831C, + 0x88AA, 0x7A50, 0x88AB, 0x60AA, 0x88AC, 0x63E1, 0x88AD, 0x6E25, 0x88AE, 0x65ED, 0x88AF, 0x8466, 0x88B0, 0x82A6, 0x88B1, 0x9BF5, + 0x88B2, 0x6893, 0x88B3, 0x5727, 0x88B4, 0x65A1, 0x88B5, 0x6271, 0x88B6, 0x5B9B, 0x88B7, 0x59D0, 0x88B8, 0x867B, 0x88B9, 0x98F4, + 0x88BA, 0x7D62, 0x88BB, 0x7DBE, 0x88BC, 0x9B8E, 0x88BD, 0x6216, 0x88BE, 0x7C9F, 0x88BF, 0x88B7, 0x88C0, 0x5B89, 0x88C1, 0x5EB5, + 0x88C2, 0x6309, 0x88C3, 0x6697, 0x88C4, 0x6848, 0x88C5, 0x95C7, 0x88C6, 0x978D, 0x88C7, 0x674F, 0x88C8, 0x4EE5, 0x88C9, 0x4F0A, + 0x88CA, 0x4F4D, 0x88CB, 0x4F9D, 0x88CC, 0x5049, 0x88CD, 0x56F2, 0x88CE, 0x5937, 0x88CF, 0x59D4, 0x88D0, 0x5A01, 0x88D1, 0x5C09, + 0x88D2, 0x60DF, 0x88D3, 0x610F, 0x88D4, 0x6170, 0x88D5, 0x6613, 0x88D6, 0x6905, 0x88D7, 0x70BA, 0x88D8, 0x754F, 0x88D9, 0x7570, + 0x88DA, 0x79FB, 0x88DB, 0x7DAD, 0x88DC, 0x7DEF, 0x88DD, 0x80C3, 0x88DE, 0x840E, 0x88DF, 0x8863, 0x88E0, 0x8B02, 0x88E1, 0x9055, + 0x88E2, 0x907A, 0x88E3, 0x533B, 0x88E4, 0x4E95, 0x88E5, 0x4EA5, 0x88E6, 0x57DF, 0x88E7, 0x80B2, 0x88E8, 0x90C1, 0x88E9, 0x78EF, + 0x88EA, 0x4E00, 0x88EB, 0x58F1, 0x88EC, 0x6EA2, 0x88ED, 0x9038, 0x88EE, 0x7A32, 0x88EF, 0x8328, 0x88F0, 0x828B, 0x88F1, 0x9C2F, + 0x88F2, 0x5141, 0x88F3, 0x5370, 0x88F4, 0x54BD, 0x88F5, 0x54E1, 0x88F6, 0x56E0, 0x88F7, 0x59FB, 0x88F8, 0x5F15, 0x88F9, 0x98F2, + 0x88FA, 0x6DEB, 0x88FB, 0x80E4, 0x88FC, 0x852D, 0x8940, 0x9662, 0x8941, 0x9670, 0x8942, 0x96A0, 0x8943, 0x97FB, 0x8944, 0x540B, + 0x8945, 0x53F3, 0x8946, 0x5B87, 0x8947, 0x70CF, 0x8948, 0x7FBD, 0x8949, 0x8FC2, 0x894A, 0x96E8, 0x894B, 0x536F, 0x894C, 0x9D5C, + 0x894D, 0x7ABA, 0x894E, 0x4E11, 0x894F, 0x7893, 0x8950, 0x81FC, 0x8951, 0x6E26, 0x8952, 0x5618, 0x8953, 0x5504, 0x8954, 0x6B1D, + 0x8955, 0x851A, 0x8956, 0x9C3B, 0x8957, 0x59E5, 0x8958, 0x53A9, 0x8959, 0x6D66, 0x895A, 0x74DC, 0x895B, 0x958F, 0x895C, 0x5642, + 0x895D, 0x4E91, 0x895E, 0x904B, 0x895F, 0x96F2, 0x8960, 0x834F, 0x8961, 0x990C, 0x8962, 0x53E1, 0x8963, 0x55B6, 0x8964, 0x5B30, + 0x8965, 0x5F71, 0x8966, 0x6620, 0x8967, 0x66F3, 0x8968, 0x6804, 0x8969, 0x6C38, 0x896A, 0x6CF3, 0x896B, 0x6D29, 0x896C, 0x745B, + 0x896D, 0x76C8, 0x896E, 0x7A4E, 0x896F, 0x9834, 0x8970, 0x82F1, 0x8971, 0x885B, 0x8972, 0x8A60, 0x8973, 0x92ED, 0x8974, 0x6DB2, + 0x8975, 0x75AB, 0x8976, 0x76CA, 0x8977, 0x99C5, 0x8978, 0x60A6, 0x8979, 0x8B01, 0x897A, 0x8D8A, 0x897B, 0x95B2, 0x897C, 0x698E, + 0x897D, 0x53AD, 0x897E, 0x5186, 0x8980, 0x5712, 0x8981, 0x5830, 0x8982, 0x5944, 0x8983, 0x5BB4, 0x8984, 0x5EF6, 0x8985, 0x6028, + 0x8986, 0x63A9, 0x8987, 0x63F4, 0x8988, 0x6CBF, 0x8989, 0x6F14, 0x898A, 0x708E, 0x898B, 0x7114, 0x898C, 0x7159, 0x898D, 0x71D5, + 0x898E, 0x733F, 0x898F, 0x7E01, 0x8990, 0x8276, 0x8991, 0x82D1, 0x8992, 0x8597, 0x8993, 0x9060, 0x8994, 0x925B, 0x8995, 0x9D1B, + 0x8996, 0x5869, 0x8997, 0x65BC, 0x8998, 0x6C5A, 0x8999, 0x7525, 0x899A, 0x51F9, 0x899B, 0x592E, 0x899C, 0x5965, 0x899D, 0x5F80, + 0x899E, 0x5FDC, 0x899F, 0x62BC, 0x89A0, 0x65FA, 0x89A1, 0x6A2A, 0x89A2, 0x6B27, 0x89A3, 0x6BB4, 0x89A4, 0x738B, 0x89A5, 0x7FC1, + 0x89A6, 0x8956, 0x89A7, 0x9D2C, 0x89A8, 0x9D0E, 0x89A9, 0x9EC4, 0x89AA, 0x5CA1, 0x89AB, 0x6C96, 0x89AC, 0x837B, 0x89AD, 0x5104, + 0x89AE, 0x5C4B, 0x89AF, 0x61B6, 0x89B0, 0x81C6, 0x89B1, 0x6876, 0x89B2, 0x7261, 0x89B3, 0x4E59, 0x89B4, 0x4FFA, 0x89B5, 0x5378, + 0x89B6, 0x6069, 0x89B7, 0x6E29, 0x89B8, 0x7A4F, 0x89B9, 0x97F3, 0x89BA, 0x4E0B, 0x89BB, 0x5316, 0x89BC, 0x4EEE, 0x89BD, 0x4F55, + 0x89BE, 0x4F3D, 0x89BF, 0x4FA1, 0x89C0, 0x4F73, 0x89C1, 0x52A0, 0x89C2, 0x53EF, 0x89C3, 0x5609, 0x89C4, 0x590F, 0x89C5, 0x5AC1, + 0x89C6, 0x5BB6, 0x89C7, 0x5BE1, 0x89C8, 0x79D1, 0x89C9, 0x6687, 0x89CA, 0x679C, 0x89CB, 0x67B6, 0x89CC, 0x6B4C, 0x89CD, 0x6CB3, + 0x89CE, 0x706B, 0x89CF, 0x73C2, 0x89D0, 0x798D, 0x89D1, 0x79BE, 0x89D2, 0x7A3C, 0x89D3, 0x7B87, 0x89D4, 0x82B1, 0x89D5, 0x82DB, + 0x89D6, 0x8304, 0x89D7, 0x8377, 0x89D8, 0x83EF, 0x89D9, 0x83D3, 0x89DA, 0x8766, 0x89DB, 0x8AB2, 0x89DC, 0x5629, 0x89DD, 0x8CA8, + 0x89DE, 0x8FE6, 0x89DF, 0x904E, 0x89E0, 0x971E, 0x89E1, 0x868A, 0x89E2, 0x4FC4, 0x89E3, 0x5CE8, 0x89E4, 0x6211, 0x89E5, 0x7259, + 0x89E6, 0x753B, 0x89E7, 0x81E5, 0x89E8, 0x82BD, 0x89E9, 0x86FE, 0x89EA, 0x8CC0, 0x89EB, 0x96C5, 0x89EC, 0x9913, 0x89ED, 0x99D5, + 0x89EE, 0x4ECB, 0x89EF, 0x4F1A, 0x89F0, 0x89E3, 0x89F1, 0x56DE, 0x89F2, 0x584A, 0x89F3, 0x58CA, 0x89F4, 0x5EFB, 0x89F5, 0x5FEB, + 0x89F6, 0x602A, 0x89F7, 0x6094, 0x89F8, 0x6062, 0x89F9, 0x61D0, 0x89FA, 0x6212, 0x89FB, 0x62D0, 0x89FC, 0x6539, 0x8A40, 0x9B41, + 0x8A41, 0x6666, 0x8A42, 0x68B0, 0x8A43, 0x6D77, 0x8A44, 0x7070, 0x8A45, 0x754C, 0x8A46, 0x7686, 0x8A47, 0x7D75, 0x8A48, 0x82A5, + 0x8A49, 0x87F9, 0x8A4A, 0x958B, 0x8A4B, 0x968E, 0x8A4C, 0x8C9D, 0x8A4D, 0x51F1, 0x8A4E, 0x52BE, 0x8A4F, 0x5916, 0x8A50, 0x54B3, + 0x8A51, 0x5BB3, 0x8A52, 0x5D16, 0x8A53, 0x6168, 0x8A54, 0x6982, 0x8A55, 0x6DAF, 0x8A56, 0x788D, 0x8A57, 0x84CB, 0x8A58, 0x8857, + 0x8A59, 0x8A72, 0x8A5A, 0x93A7, 0x8A5B, 0x9AB8, 0x8A5C, 0x6D6C, 0x8A5D, 0x99A8, 0x8A5E, 0x86D9, 0x8A5F, 0x57A3, 0x8A60, 0x67FF, + 0x8A61, 0x86CE, 0x8A62, 0x920E, 0x8A63, 0x5283, 0x8A64, 0x5687, 0x8A65, 0x5404, 0x8A66, 0x5ED3, 0x8A67, 0x62E1, 0x8A68, 0x64B9, + 0x8A69, 0x683C, 0x8A6A, 0x6838, 0x8A6B, 0x6BBB, 0x8A6C, 0x7372, 0x8A6D, 0x78BA, 0x8A6E, 0x7A6B, 0x8A6F, 0x899A, 0x8A70, 0x89D2, + 0x8A71, 0x8D6B, 0x8A72, 0x8F03, 0x8A73, 0x90ED, 0x8A74, 0x95A3, 0x8A75, 0x9694, 0x8A76, 0x9769, 0x8A77, 0x5B66, 0x8A78, 0x5CB3, + 0x8A79, 0x697D, 0x8A7A, 0x984D, 0x8A7B, 0x984E, 0x8A7C, 0x639B, 0x8A7D, 0x7B20, 0x8A7E, 0x6A2B, 0x8A80, 0x6A7F, 0x8A81, 0x68B6, + 0x8A82, 0x9C0D, 0x8A83, 0x6F5F, 0x8A84, 0x5272, 0x8A85, 0x559D, 0x8A86, 0x6070, 0x8A87, 0x62EC, 0x8A88, 0x6D3B, 0x8A89, 0x6E07, + 0x8A8A, 0x6ED1, 0x8A8B, 0x845B, 0x8A8C, 0x8910, 0x8A8D, 0x8F44, 0x8A8E, 0x4E14, 0x8A8F, 0x9C39, 0x8A90, 0x53F6, 0x8A91, 0x691B, + 0x8A92, 0x6A3A, 0x8A93, 0x9784, 0x8A94, 0x682A, 0x8A95, 0x515C, 0x8A96, 0x7AC3, 0x8A97, 0x84B2, 0x8A98, 0x91DC, 0x8A99, 0x938C, + 0x8A9A, 0x565B, 0x8A9B, 0x9D28, 0x8A9C, 0x6822, 0x8A9D, 0x8305, 0x8A9E, 0x8431, 0x8A9F, 0x7CA5, 0x8AA0, 0x5208, 0x8AA1, 0x82C5, + 0x8AA2, 0x74E6, 0x8AA3, 0x4E7E, 0x8AA4, 0x4F83, 0x8AA5, 0x51A0, 0x8AA6, 0x5BD2, 0x8AA7, 0x520A, 0x8AA8, 0x52D8, 0x8AA9, 0x52E7, + 0x8AAA, 0x5DFB, 0x8AAB, 0x559A, 0x8AAC, 0x582A, 0x8AAD, 0x59E6, 0x8AAE, 0x5B8C, 0x8AAF, 0x5B98, 0x8AB0, 0x5BDB, 0x8AB1, 0x5E72, + 0x8AB2, 0x5E79, 0x8AB3, 0x60A3, 0x8AB4, 0x611F, 0x8AB5, 0x6163, 0x8AB6, 0x61BE, 0x8AB7, 0x63DB, 0x8AB8, 0x6562, 0x8AB9, 0x67D1, + 0x8ABA, 0x6853, 0x8ABB, 0x68FA, 0x8ABC, 0x6B3E, 0x8ABD, 0x6B53, 0x8ABE, 0x6C57, 0x8ABF, 0x6F22, 0x8AC0, 0x6F97, 0x8AC1, 0x6F45, + 0x8AC2, 0x74B0, 0x8AC3, 0x7518, 0x8AC4, 0x76E3, 0x8AC5, 0x770B, 0x8AC6, 0x7AFF, 0x8AC7, 0x7BA1, 0x8AC8, 0x7C21, 0x8AC9, 0x7DE9, + 0x8ACA, 0x7F36, 0x8ACB, 0x7FF0, 0x8ACC, 0x809D, 0x8ACD, 0x8266, 0x8ACE, 0x839E, 0x8ACF, 0x89B3, 0x8AD0, 0x8ACC, 0x8AD1, 0x8CAB, + 0x8AD2, 0x9084, 0x8AD3, 0x9451, 0x8AD4, 0x9593, 0x8AD5, 0x9591, 0x8AD6, 0x95A2, 0x8AD7, 0x9665, 0x8AD8, 0x97D3, 0x8AD9, 0x9928, + 0x8ADA, 0x8218, 0x8ADB, 0x4E38, 0x8ADC, 0x542B, 0x8ADD, 0x5CB8, 0x8ADE, 0x5DCC, 0x8ADF, 0x73A9, 0x8AE0, 0x764C, 0x8AE1, 0x773C, + 0x8AE2, 0x5CA9, 0x8AE3, 0x7FEB, 0x8AE4, 0x8D0B, 0x8AE5, 0x96C1, 0x8AE6, 0x9811, 0x8AE7, 0x9854, 0x8AE8, 0x9858, 0x8AE9, 0x4F01, + 0x8AEA, 0x4F0E, 0x8AEB, 0x5371, 0x8AEC, 0x559C, 0x8AED, 0x5668, 0x8AEE, 0x57FA, 0x8AEF, 0x5947, 0x8AF0, 0x5B09, 0x8AF1, 0x5BC4, + 0x8AF2, 0x5C90, 0x8AF3, 0x5E0C, 0x8AF4, 0x5E7E, 0x8AF5, 0x5FCC, 0x8AF6, 0x63EE, 0x8AF7, 0x673A, 0x8AF8, 0x65D7, 0x8AF9, 0x65E2, + 0x8AFA, 0x671F, 0x8AFB, 0x68CB, 0x8AFC, 0x68C4, 0x8B40, 0x6A5F, 0x8B41, 0x5E30, 0x8B42, 0x6BC5, 0x8B43, 0x6C17, 0x8B44, 0x6C7D, + 0x8B45, 0x757F, 0x8B46, 0x7948, 0x8B47, 0x5B63, 0x8B48, 0x7A00, 0x8B49, 0x7D00, 0x8B4A, 0x5FBD, 0x8B4B, 0x898F, 0x8B4C, 0x8A18, + 0x8B4D, 0x8CB4, 0x8B4E, 0x8D77, 0x8B4F, 0x8ECC, 0x8B50, 0x8F1D, 0x8B51, 0x98E2, 0x8B52, 0x9A0E, 0x8B53, 0x9B3C, 0x8B54, 0x4E80, + 0x8B55, 0x507D, 0x8B56, 0x5100, 0x8B57, 0x5993, 0x8B58, 0x5B9C, 0x8B59, 0x622F, 0x8B5A, 0x6280, 0x8B5B, 0x64EC, 0x8B5C, 0x6B3A, + 0x8B5D, 0x72A0, 0x8B5E, 0x7591, 0x8B5F, 0x7947, 0x8B60, 0x7FA9, 0x8B61, 0x87FB, 0x8B62, 0x8ABC, 0x8B63, 0x8B70, 0x8B64, 0x63AC, + 0x8B65, 0x83CA, 0x8B66, 0x97A0, 0x8B67, 0x5409, 0x8B68, 0x5403, 0x8B69, 0x55AB, 0x8B6A, 0x6854, 0x8B6B, 0x6A58, 0x8B6C, 0x8A70, + 0x8B6D, 0x7827, 0x8B6E, 0x6775, 0x8B6F, 0x9ECD, 0x8B70, 0x5374, 0x8B71, 0x5BA2, 0x8B72, 0x811A, 0x8B73, 0x8650, 0x8B74, 0x9006, + 0x8B75, 0x4E18, 0x8B76, 0x4E45, 0x8B77, 0x4EC7, 0x8B78, 0x4F11, 0x8B79, 0x53CA, 0x8B7A, 0x5438, 0x8B7B, 0x5BAE, 0x8B7C, 0x5F13, + 0x8B7D, 0x6025, 0x8B7E, 0x6551, 0x8B80, 0x673D, 0x8B81, 0x6C42, 0x8B82, 0x6C72, 0x8B83, 0x6CE3, 0x8B84, 0x7078, 0x8B85, 0x7403, + 0x8B86, 0x7A76, 0x8B87, 0x7AAE, 0x8B88, 0x7B08, 0x8B89, 0x7D1A, 0x8B8A, 0x7CFE, 0x8B8B, 0x7D66, 0x8B8C, 0x65E7, 0x8B8D, 0x725B, + 0x8B8E, 0x53BB, 0x8B8F, 0x5C45, 0x8B90, 0x5DE8, 0x8B91, 0x62D2, 0x8B92, 0x62E0, 0x8B93, 0x6319, 0x8B94, 0x6E20, 0x8B95, 0x865A, + 0x8B96, 0x8A31, 0x8B97, 0x8DDD, 0x8B98, 0x92F8, 0x8B99, 0x6F01, 0x8B9A, 0x79A6, 0x8B9B, 0x9B5A, 0x8B9C, 0x4EA8, 0x8B9D, 0x4EAB, + 0x8B9E, 0x4EAC, 0x8B9F, 0x4F9B, 0x8BA0, 0x4FA0, 0x8BA1, 0x50D1, 0x8BA2, 0x5147, 0x8BA3, 0x7AF6, 0x8BA4, 0x5171, 0x8BA5, 0x51F6, + 0x8BA6, 0x5354, 0x8BA7, 0x5321, 0x8BA8, 0x537F, 0x8BA9, 0x53EB, 0x8BAA, 0x55AC, 0x8BAB, 0x5883, 0x8BAC, 0x5CE1, 0x8BAD, 0x5F37, + 0x8BAE, 0x5F4A, 0x8BAF, 0x602F, 0x8BB0, 0x6050, 0x8BB1, 0x606D, 0x8BB2, 0x631F, 0x8BB3, 0x6559, 0x8BB4, 0x6A4B, 0x8BB5, 0x6CC1, + 0x8BB6, 0x72C2, 0x8BB7, 0x72ED, 0x8BB8, 0x77EF, 0x8BB9, 0x80F8, 0x8BBA, 0x8105, 0x8BBB, 0x8208, 0x8BBC, 0x854E, 0x8BBD, 0x90F7, + 0x8BBE, 0x93E1, 0x8BBF, 0x97FF, 0x8BC0, 0x9957, 0x8BC1, 0x9A5A, 0x8BC2, 0x4EF0, 0x8BC3, 0x51DD, 0x8BC4, 0x5C2D, 0x8BC5, 0x6681, + 0x8BC6, 0x696D, 0x8BC7, 0x5C40, 0x8BC8, 0x66F2, 0x8BC9, 0x6975, 0x8BCA, 0x7389, 0x8BCB, 0x6850, 0x8BCC, 0x7C81, 0x8BCD, 0x50C5, + 0x8BCE, 0x52E4, 0x8BCF, 0x5747, 0x8BD0, 0x5DFE, 0x8BD1, 0x9326, 0x8BD2, 0x65A4, 0x8BD3, 0x6B23, 0x8BD4, 0x6B3D, 0x8BD5, 0x7434, + 0x8BD6, 0x7981, 0x8BD7, 0x79BD, 0x8BD8, 0x7B4B, 0x8BD9, 0x7DCA, 0x8BDA, 0x82B9, 0x8BDB, 0x83CC, 0x8BDC, 0x887F, 0x8BDD, 0x895F, + 0x8BDE, 0x8B39, 0x8BDF, 0x8FD1, 0x8BE0, 0x91D1, 0x8BE1, 0x541F, 0x8BE2, 0x9280, 0x8BE3, 0x4E5D, 0x8BE4, 0x5036, 0x8BE5, 0x53E5, + 0x8BE6, 0x533A, 0x8BE7, 0x72D7, 0x8BE8, 0x7396, 0x8BE9, 0x77E9, 0x8BEA, 0x82E6, 0x8BEB, 0x8EAF, 0x8BEC, 0x99C6, 0x8BED, 0x99C8, + 0x8BEE, 0x99D2, 0x8BEF, 0x5177, 0x8BF0, 0x611A, 0x8BF1, 0x865E, 0x8BF2, 0x55B0, 0x8BF3, 0x7A7A, 0x8BF4, 0x5076, 0x8BF5, 0x5BD3, + 0x8BF6, 0x9047, 0x8BF7, 0x9685, 0x8BF8, 0x4E32, 0x8BF9, 0x6ADB, 0x8BFA, 0x91E7, 0x8BFB, 0x5C51, 0x8BFC, 0x5C48, 0x8C40, 0x6398, + 0x8C41, 0x7A9F, 0x8C42, 0x6C93, 0x8C43, 0x9774, 0x8C44, 0x8F61, 0x8C45, 0x7AAA, 0x8C46, 0x718A, 0x8C47, 0x9688, 0x8C48, 0x7C82, + 0x8C49, 0x6817, 0x8C4A, 0x7E70, 0x8C4B, 0x6851, 0x8C4C, 0x936C, 0x8C4D, 0x52F2, 0x8C4E, 0x541B, 0x8C4F, 0x85AB, 0x8C50, 0x8A13, + 0x8C51, 0x7FA4, 0x8C52, 0x8ECD, 0x8C53, 0x90E1, 0x8C54, 0x5366, 0x8C55, 0x8888, 0x8C56, 0x7941, 0x8C57, 0x4FC2, 0x8C58, 0x50BE, + 0x8C59, 0x5211, 0x8C5A, 0x5144, 0x8C5B, 0x5553, 0x8C5C, 0x572D, 0x8C5D, 0x73EA, 0x8C5E, 0x578B, 0x8C5F, 0x5951, 0x8C60, 0x5F62, + 0x8C61, 0x5F84, 0x8C62, 0x6075, 0x8C63, 0x6176, 0x8C64, 0x6167, 0x8C65, 0x61A9, 0x8C66, 0x63B2, 0x8C67, 0x643A, 0x8C68, 0x656C, + 0x8C69, 0x666F, 0x8C6A, 0x6842, 0x8C6B, 0x6E13, 0x8C6C, 0x7566, 0x8C6D, 0x7A3D, 0x8C6E, 0x7CFB, 0x8C6F, 0x7D4C, 0x8C70, 0x7D99, + 0x8C71, 0x7E4B, 0x8C72, 0x7F6B, 0x8C73, 0x830E, 0x8C74, 0x834A, 0x8C75, 0x86CD, 0x8C76, 0x8A08, 0x8C77, 0x8A63, 0x8C78, 0x8B66, + 0x8C79, 0x8EFD, 0x8C7A, 0x981A, 0x8C7B, 0x9D8F, 0x8C7C, 0x82B8, 0x8C7D, 0x8FCE, 0x8C7E, 0x9BE8, 0x8C80, 0x5287, 0x8C81, 0x621F, + 0x8C82, 0x6483, 0x8C83, 0x6FC0, 0x8C84, 0x9699, 0x8C85, 0x6841, 0x8C86, 0x5091, 0x8C87, 0x6B20, 0x8C88, 0x6C7A, 0x8C89, 0x6F54, + 0x8C8A, 0x7A74, 0x8C8B, 0x7D50, 0x8C8C, 0x8840, 0x8C8D, 0x8A23, 0x8C8E, 0x6708, 0x8C8F, 0x4EF6, 0x8C90, 0x5039, 0x8C91, 0x5026, + 0x8C92, 0x5065, 0x8C93, 0x517C, 0x8C94, 0x5238, 0x8C95, 0x5263, 0x8C96, 0x55A7, 0x8C97, 0x570F, 0x8C98, 0x5805, 0x8C99, 0x5ACC, + 0x8C9A, 0x5EFA, 0x8C9B, 0x61B2, 0x8C9C, 0x61F8, 0x8C9D, 0x62F3, 0x8C9E, 0x6372, 0x8C9F, 0x691C, 0x8CA0, 0x6A29, 0x8CA1, 0x727D, + 0x8CA2, 0x72AC, 0x8CA3, 0x732E, 0x8CA4, 0x7814, 0x8CA5, 0x786F, 0x8CA6, 0x7D79, 0x8CA7, 0x770C, 0x8CA8, 0x80A9, 0x8CA9, 0x898B, + 0x8CAA, 0x8B19, 0x8CAB, 0x8CE2, 0x8CAC, 0x8ED2, 0x8CAD, 0x9063, 0x8CAE, 0x9375, 0x8CAF, 0x967A, 0x8CB0, 0x9855, 0x8CB1, 0x9A13, + 0x8CB2, 0x9E78, 0x8CB3, 0x5143, 0x8CB4, 0x539F, 0x8CB5, 0x53B3, 0x8CB6, 0x5E7B, 0x8CB7, 0x5F26, 0x8CB8, 0x6E1B, 0x8CB9, 0x6E90, + 0x8CBA, 0x7384, 0x8CBB, 0x73FE, 0x8CBC, 0x7D43, 0x8CBD, 0x8237, 0x8CBE, 0x8A00, 0x8CBF, 0x8AFA, 0x8CC0, 0x9650, 0x8CC1, 0x4E4E, + 0x8CC2, 0x500B, 0x8CC3, 0x53E4, 0x8CC4, 0x547C, 0x8CC5, 0x56FA, 0x8CC6, 0x59D1, 0x8CC7, 0x5B64, 0x8CC8, 0x5DF1, 0x8CC9, 0x5EAB, + 0x8CCA, 0x5F27, 0x8CCB, 0x6238, 0x8CCC, 0x6545, 0x8CCD, 0x67AF, 0x8CCE, 0x6E56, 0x8CCF, 0x72D0, 0x8CD0, 0x7CCA, 0x8CD1, 0x88B4, + 0x8CD2, 0x80A1, 0x8CD3, 0x80E1, 0x8CD4, 0x83F0, 0x8CD5, 0x864E, 0x8CD6, 0x8A87, 0x8CD7, 0x8DE8, 0x8CD8, 0x9237, 0x8CD9, 0x96C7, + 0x8CDA, 0x9867, 0x8CDB, 0x9F13, 0x8CDC, 0x4E94, 0x8CDD, 0x4E92, 0x8CDE, 0x4F0D, 0x8CDF, 0x5348, 0x8CE0, 0x5449, 0x8CE1, 0x543E, + 0x8CE2, 0x5A2F, 0x8CE3, 0x5F8C, 0x8CE4, 0x5FA1, 0x8CE5, 0x609F, 0x8CE6, 0x68A7, 0x8CE7, 0x6A8E, 0x8CE8, 0x745A, 0x8CE9, 0x7881, + 0x8CEA, 0x8A9E, 0x8CEB, 0x8AA4, 0x8CEC, 0x8B77, 0x8CED, 0x9190, 0x8CEE, 0x4E5E, 0x8CEF, 0x9BC9, 0x8CF0, 0x4EA4, 0x8CF1, 0x4F7C, + 0x8CF2, 0x4FAF, 0x8CF3, 0x5019, 0x8CF4, 0x5016, 0x8CF5, 0x5149, 0x8CF6, 0x516C, 0x8CF7, 0x529F, 0x8CF8, 0x52B9, 0x8CF9, 0x52FE, + 0x8CFA, 0x539A, 0x8CFB, 0x53E3, 0x8CFC, 0x5411, 0x8D40, 0x540E, 0x8D41, 0x5589, 0x8D42, 0x5751, 0x8D43, 0x57A2, 0x8D44, 0x597D, + 0x8D45, 0x5B54, 0x8D46, 0x5B5D, 0x8D47, 0x5B8F, 0x8D48, 0x5DE5, 0x8D49, 0x5DE7, 0x8D4A, 0x5DF7, 0x8D4B, 0x5E78, 0x8D4C, 0x5E83, + 0x8D4D, 0x5E9A, 0x8D4E, 0x5EB7, 0x8D4F, 0x5F18, 0x8D50, 0x6052, 0x8D51, 0x614C, 0x8D52, 0x6297, 0x8D53, 0x62D8, 0x8D54, 0x63A7, + 0x8D55, 0x653B, 0x8D56, 0x6602, 0x8D57, 0x6643, 0x8D58, 0x66F4, 0x8D59, 0x676D, 0x8D5A, 0x6821, 0x8D5B, 0x6897, 0x8D5C, 0x69CB, + 0x8D5D, 0x6C5F, 0x8D5E, 0x6D2A, 0x8D5F, 0x6D69, 0x8D60, 0x6E2F, 0x8D61, 0x6E9D, 0x8D62, 0x7532, 0x8D63, 0x7687, 0x8D64, 0x786C, + 0x8D65, 0x7A3F, 0x8D66, 0x7CE0, 0x8D67, 0x7D05, 0x8D68, 0x7D18, 0x8D69, 0x7D5E, 0x8D6A, 0x7DB1, 0x8D6B, 0x8015, 0x8D6C, 0x8003, + 0x8D6D, 0x80AF, 0x8D6E, 0x80B1, 0x8D6F, 0x8154, 0x8D70, 0x818F, 0x8D71, 0x822A, 0x8D72, 0x8352, 0x8D73, 0x884C, 0x8D74, 0x8861, + 0x8D75, 0x8B1B, 0x8D76, 0x8CA2, 0x8D77, 0x8CFC, 0x8D78, 0x90CA, 0x8D79, 0x9175, 0x8D7A, 0x9271, 0x8D7B, 0x783F, 0x8D7C, 0x92FC, + 0x8D7D, 0x95A4, 0x8D7E, 0x964D, 0x8D80, 0x9805, 0x8D81, 0x9999, 0x8D82, 0x9AD8, 0x8D83, 0x9D3B, 0x8D84, 0x525B, 0x8D85, 0x52AB, + 0x8D86, 0x53F7, 0x8D87, 0x5408, 0x8D88, 0x58D5, 0x8D89, 0x62F7, 0x8D8A, 0x6FE0, 0x8D8B, 0x8C6A, 0x8D8C, 0x8F5F, 0x8D8D, 0x9EB9, + 0x8D8E, 0x514B, 0x8D8F, 0x523B, 0x8D90, 0x544A, 0x8D91, 0x56FD, 0x8D92, 0x7A40, 0x8D93, 0x9177, 0x8D94, 0x9D60, 0x8D95, 0x9ED2, + 0x8D96, 0x7344, 0x8D97, 0x6F09, 0x8D98, 0x8170, 0x8D99, 0x7511, 0x8D9A, 0x5FFD, 0x8D9B, 0x60DA, 0x8D9C, 0x9AA8, 0x8D9D, 0x72DB, + 0x8D9E, 0x8FBC, 0x8D9F, 0x6B64, 0x8DA0, 0x9803, 0x8DA1, 0x4ECA, 0x8DA2, 0x56F0, 0x8DA3, 0x5764, 0x8DA4, 0x58BE, 0x8DA5, 0x5A5A, + 0x8DA6, 0x6068, 0x8DA7, 0x61C7, 0x8DA8, 0x660F, 0x8DA9, 0x6606, 0x8DAA, 0x6839, 0x8DAB, 0x68B1, 0x8DAC, 0x6DF7, 0x8DAD, 0x75D5, + 0x8DAE, 0x7D3A, 0x8DAF, 0x826E, 0x8DB0, 0x9B42, 0x8DB1, 0x4E9B, 0x8DB2, 0x4F50, 0x8DB3, 0x53C9, 0x8DB4, 0x5506, 0x8DB5, 0x5D6F, + 0x8DB6, 0x5DE6, 0x8DB7, 0x5DEE, 0x8DB8, 0x67FB, 0x8DB9, 0x6C99, 0x8DBA, 0x7473, 0x8DBB, 0x7802, 0x8DBC, 0x8A50, 0x8DBD, 0x9396, + 0x8DBE, 0x88DF, 0x8DBF, 0x5750, 0x8DC0, 0x5EA7, 0x8DC1, 0x632B, 0x8DC2, 0x50B5, 0x8DC3, 0x50AC, 0x8DC4, 0x518D, 0x8DC5, 0x6700, + 0x8DC6, 0x54C9, 0x8DC7, 0x585E, 0x8DC8, 0x59BB, 0x8DC9, 0x5BB0, 0x8DCA, 0x5F69, 0x8DCB, 0x624D, 0x8DCC, 0x63A1, 0x8DCD, 0x683D, + 0x8DCE, 0x6B73, 0x8DCF, 0x6E08, 0x8DD0, 0x707D, 0x8DD1, 0x91C7, 0x8DD2, 0x7280, 0x8DD3, 0x7815, 0x8DD4, 0x7826, 0x8DD5, 0x796D, + 0x8DD6, 0x658E, 0x8DD7, 0x7D30, 0x8DD8, 0x83DC, 0x8DD9, 0x88C1, 0x8DDA, 0x8F09, 0x8DDB, 0x969B, 0x8DDC, 0x5264, 0x8DDD, 0x5728, + 0x8DDE, 0x6750, 0x8DDF, 0x7F6A, 0x8DE0, 0x8CA1, 0x8DE1, 0x51B4, 0x8DE2, 0x5742, 0x8DE3, 0x962A, 0x8DE4, 0x583A, 0x8DE5, 0x698A, + 0x8DE6, 0x80B4, 0x8DE7, 0x54B2, 0x8DE8, 0x5D0E, 0x8DE9, 0x57FC, 0x8DEA, 0x7895, 0x8DEB, 0x9DFA, 0x8DEC, 0x4F5C, 0x8DED, 0x524A, + 0x8DEE, 0x548B, 0x8DEF, 0x643E, 0x8DF0, 0x6628, 0x8DF1, 0x6714, 0x8DF2, 0x67F5, 0x8DF3, 0x7A84, 0x8DF4, 0x7B56, 0x8DF5, 0x7D22, + 0x8DF6, 0x932F, 0x8DF7, 0x685C, 0x8DF8, 0x9BAD, 0x8DF9, 0x7B39, 0x8DFA, 0x5319, 0x8DFB, 0x518A, 0x8DFC, 0x5237, 0x8E40, 0x5BDF, + 0x8E41, 0x62F6, 0x8E42, 0x64AE, 0x8E43, 0x64E6, 0x8E44, 0x672D, 0x8E45, 0x6BBA, 0x8E46, 0x85A9, 0x8E47, 0x96D1, 0x8E48, 0x7690, + 0x8E49, 0x9BD6, 0x8E4A, 0x634C, 0x8E4B, 0x9306, 0x8E4C, 0x9BAB, 0x8E4D, 0x76BF, 0x8E4E, 0x6652, 0x8E4F, 0x4E09, 0x8E50, 0x5098, + 0x8E51, 0x53C2, 0x8E52, 0x5C71, 0x8E53, 0x60E8, 0x8E54, 0x6492, 0x8E55, 0x6563, 0x8E56, 0x685F, 0x8E57, 0x71E6, 0x8E58, 0x73CA, + 0x8E59, 0x7523, 0x8E5A, 0x7B97, 0x8E5B, 0x7E82, 0x8E5C, 0x8695, 0x8E5D, 0x8B83, 0x8E5E, 0x8CDB, 0x8E5F, 0x9178, 0x8E60, 0x9910, + 0x8E61, 0x65AC, 0x8E62, 0x66AB, 0x8E63, 0x6B8B, 0x8E64, 0x4ED5, 0x8E65, 0x4ED4, 0x8E66, 0x4F3A, 0x8E67, 0x4F7F, 0x8E68, 0x523A, + 0x8E69, 0x53F8, 0x8E6A, 0x53F2, 0x8E6B, 0x55E3, 0x8E6C, 0x56DB, 0x8E6D, 0x58EB, 0x8E6E, 0x59CB, 0x8E6F, 0x59C9, 0x8E70, 0x59FF, + 0x8E71, 0x5B50, 0x8E72, 0x5C4D, 0x8E73, 0x5E02, 0x8E74, 0x5E2B, 0x8E75, 0x5FD7, 0x8E76, 0x601D, 0x8E77, 0x6307, 0x8E78, 0x652F, + 0x8E79, 0x5B5C, 0x8E7A, 0x65AF, 0x8E7B, 0x65BD, 0x8E7C, 0x65E8, 0x8E7D, 0x679D, 0x8E7E, 0x6B62, 0x8E80, 0x6B7B, 0x8E81, 0x6C0F, + 0x8E82, 0x7345, 0x8E83, 0x7949, 0x8E84, 0x79C1, 0x8E85, 0x7CF8, 0x8E86, 0x7D19, 0x8E87, 0x7D2B, 0x8E88, 0x80A2, 0x8E89, 0x8102, + 0x8E8A, 0x81F3, 0x8E8B, 0x8996, 0x8E8C, 0x8A5E, 0x8E8D, 0x8A69, 0x8E8E, 0x8A66, 0x8E8F, 0x8A8C, 0x8E90, 0x8AEE, 0x8E91, 0x8CC7, + 0x8E92, 0x8CDC, 0x8E93, 0x96CC, 0x8E94, 0x98FC, 0x8E95, 0x6B6F, 0x8E96, 0x4E8B, 0x8E97, 0x4F3C, 0x8E98, 0x4F8D, 0x8E99, 0x5150, + 0x8E9A, 0x5B57, 0x8E9B, 0x5BFA, 0x8E9C, 0x6148, 0x8E9D, 0x6301, 0x8E9E, 0x6642, 0x8E9F, 0x6B21, 0x8EA0, 0x6ECB, 0x8EA1, 0x6CBB, + 0x8EA2, 0x723E, 0x8EA3, 0x74BD, 0x8EA4, 0x75D4, 0x8EA5, 0x78C1, 0x8EA6, 0x793A, 0x8EA7, 0x800C, 0x8EA8, 0x8033, 0x8EA9, 0x81EA, + 0x8EAA, 0x8494, 0x8EAB, 0x8F9E, 0x8EAC, 0x6C50, 0x8EAD, 0x9E7F, 0x8EAE, 0x5F0F, 0x8EAF, 0x8B58, 0x8EB0, 0x9D2B, 0x8EB1, 0x7AFA, + 0x8EB2, 0x8EF8, 0x8EB3, 0x5B8D, 0x8EB4, 0x96EB, 0x8EB5, 0x4E03, 0x8EB6, 0x53F1, 0x8EB7, 0x57F7, 0x8EB8, 0x5931, 0x8EB9, 0x5AC9, + 0x8EBA, 0x5BA4, 0x8EBB, 0x6089, 0x8EBC, 0x6E7F, 0x8EBD, 0x6F06, 0x8EBE, 0x75BE, 0x8EBF, 0x8CEA, 0x8EC0, 0x5B9F, 0x8EC1, 0x8500, + 0x8EC2, 0x7BE0, 0x8EC3, 0x5072, 0x8EC4, 0x67F4, 0x8EC5, 0x829D, 0x8EC6, 0x5C61, 0x8EC7, 0x854A, 0x8EC8, 0x7E1E, 0x8EC9, 0x820E, + 0x8ECA, 0x5199, 0x8ECB, 0x5C04, 0x8ECC, 0x6368, 0x8ECD, 0x8D66, 0x8ECE, 0x659C, 0x8ECF, 0x716E, 0x8ED0, 0x793E, 0x8ED1, 0x7D17, + 0x8ED2, 0x8005, 0x8ED3, 0x8B1D, 0x8ED4, 0x8ECA, 0x8ED5, 0x906E, 0x8ED6, 0x86C7, 0x8ED7, 0x90AA, 0x8ED8, 0x501F, 0x8ED9, 0x52FA, + 0x8EDA, 0x5C3A, 0x8EDB, 0x6753, 0x8EDC, 0x707C, 0x8EDD, 0x7235, 0x8EDE, 0x914C, 0x8EDF, 0x91C8, 0x8EE0, 0x932B, 0x8EE1, 0x82E5, + 0x8EE2, 0x5BC2, 0x8EE3, 0x5F31, 0x8EE4, 0x60F9, 0x8EE5, 0x4E3B, 0x8EE6, 0x53D6, 0x8EE7, 0x5B88, 0x8EE8, 0x624B, 0x8EE9, 0x6731, + 0x8EEA, 0x6B8A, 0x8EEB, 0x72E9, 0x8EEC, 0x73E0, 0x8EED, 0x7A2E, 0x8EEE, 0x816B, 0x8EEF, 0x8DA3, 0x8EF0, 0x9152, 0x8EF1, 0x9996, + 0x8EF2, 0x5112, 0x8EF3, 0x53D7, 0x8EF4, 0x546A, 0x8EF5, 0x5BFF, 0x8EF6, 0x6388, 0x8EF7, 0x6A39, 0x8EF8, 0x7DAC, 0x8EF9, 0x9700, + 0x8EFA, 0x56DA, 0x8EFB, 0x53CE, 0x8EFC, 0x5468, 0x8F40, 0x5B97, 0x8F41, 0x5C31, 0x8F42, 0x5DDE, 0x8F43, 0x4FEE, 0x8F44, 0x6101, + 0x8F45, 0x62FE, 0x8F46, 0x6D32, 0x8F47, 0x79C0, 0x8F48, 0x79CB, 0x8F49, 0x7D42, 0x8F4A, 0x7E4D, 0x8F4B, 0x7FD2, 0x8F4C, 0x81ED, + 0x8F4D, 0x821F, 0x8F4E, 0x8490, 0x8F4F, 0x8846, 0x8F50, 0x8972, 0x8F51, 0x8B90, 0x8F52, 0x8E74, 0x8F53, 0x8F2F, 0x8F54, 0x9031, + 0x8F55, 0x914B, 0x8F56, 0x916C, 0x8F57, 0x96C6, 0x8F58, 0x919C, 0x8F59, 0x4EC0, 0x8F5A, 0x4F4F, 0x8F5B, 0x5145, 0x8F5C, 0x5341, + 0x8F5D, 0x5F93, 0x8F5E, 0x620E, 0x8F5F, 0x67D4, 0x8F60, 0x6C41, 0x8F61, 0x6E0B, 0x8F62, 0x7363, 0x8F63, 0x7E26, 0x8F64, 0x91CD, + 0x8F65, 0x9283, 0x8F66, 0x53D4, 0x8F67, 0x5919, 0x8F68, 0x5BBF, 0x8F69, 0x6DD1, 0x8F6A, 0x795D, 0x8F6B, 0x7E2E, 0x8F6C, 0x7C9B, + 0x8F6D, 0x587E, 0x8F6E, 0x719F, 0x8F6F, 0x51FA, 0x8F70, 0x8853, 0x8F71, 0x8FF0, 0x8F72, 0x4FCA, 0x8F73, 0x5CFB, 0x8F74, 0x6625, + 0x8F75, 0x77AC, 0x8F76, 0x7AE3, 0x8F77, 0x821C, 0x8F78, 0x99FF, 0x8F79, 0x51C6, 0x8F7A, 0x5FAA, 0x8F7B, 0x65EC, 0x8F7C, 0x696F, + 0x8F7D, 0x6B89, 0x8F7E, 0x6DF3, 0x8F80, 0x6E96, 0x8F81, 0x6F64, 0x8F82, 0x76FE, 0x8F83, 0x7D14, 0x8F84, 0x5DE1, 0x8F85, 0x9075, + 0x8F86, 0x9187, 0x8F87, 0x9806, 0x8F88, 0x51E6, 0x8F89, 0x521D, 0x8F8A, 0x6240, 0x8F8B, 0x6691, 0x8F8C, 0x66D9, 0x8F8D, 0x6E1A, + 0x8F8E, 0x5EB6, 0x8F8F, 0x7DD2, 0x8F90, 0x7F72, 0x8F91, 0x66F8, 0x8F92, 0x85AF, 0x8F93, 0x85F7, 0x8F94, 0x8AF8, 0x8F95, 0x52A9, + 0x8F96, 0x53D9, 0x8F97, 0x5973, 0x8F98, 0x5E8F, 0x8F99, 0x5F90, 0x8F9A, 0x6055, 0x8F9B, 0x92E4, 0x8F9C, 0x9664, 0x8F9D, 0x50B7, + 0x8F9E, 0x511F, 0x8F9F, 0x52DD, 0x8FA0, 0x5320, 0x8FA1, 0x5347, 0x8FA2, 0x53EC, 0x8FA3, 0x54E8, 0x8FA4, 0x5546, 0x8FA5, 0x5531, + 0x8FA6, 0x5617, 0x8FA7, 0x5968, 0x8FA8, 0x59BE, 0x8FA9, 0x5A3C, 0x8FAA, 0x5BB5, 0x8FAB, 0x5C06, 0x8FAC, 0x5C0F, 0x8FAD, 0x5C11, + 0x8FAE, 0x5C1A, 0x8FAF, 0x5E84, 0x8FB0, 0x5E8A, 0x8FB1, 0x5EE0, 0x8FB2, 0x5F70, 0x8FB3, 0x627F, 0x8FB4, 0x6284, 0x8FB5, 0x62DB, + 0x8FB6, 0x638C, 0x8FB7, 0x6377, 0x8FB8, 0x6607, 0x8FB9, 0x660C, 0x8FBA, 0x662D, 0x8FBB, 0x6676, 0x8FBC, 0x677E, 0x8FBD, 0x68A2, + 0x8FBE, 0x6A1F, 0x8FBF, 0x6A35, 0x8FC0, 0x6CBC, 0x8FC1, 0x6D88, 0x8FC2, 0x6E09, 0x8FC3, 0x6E58, 0x8FC4, 0x713C, 0x8FC5, 0x7126, + 0x8FC6, 0x7167, 0x8FC7, 0x75C7, 0x8FC8, 0x7701, 0x8FC9, 0x785D, 0x8FCA, 0x7901, 0x8FCB, 0x7965, 0x8FCC, 0x79F0, 0x8FCD, 0x7AE0, + 0x8FCE, 0x7B11, 0x8FCF, 0x7CA7, 0x8FD0, 0x7D39, 0x8FD1, 0x8096, 0x8FD2, 0x83D6, 0x8FD3, 0x848B, 0x8FD4, 0x8549, 0x8FD5, 0x885D, + 0x8FD6, 0x88F3, 0x8FD7, 0x8A1F, 0x8FD8, 0x8A3C, 0x8FD9, 0x8A54, 0x8FDA, 0x8A73, 0x8FDB, 0x8C61, 0x8FDC, 0x8CDE, 0x8FDD, 0x91A4, + 0x8FDE, 0x9266, 0x8FDF, 0x937E, 0x8FE0, 0x9418, 0x8FE1, 0x969C, 0x8FE2, 0x9798, 0x8FE3, 0x4E0A, 0x8FE4, 0x4E08, 0x8FE5, 0x4E1E, + 0x8FE6, 0x4E57, 0x8FE7, 0x5197, 0x8FE8, 0x5270, 0x8FE9, 0x57CE, 0x8FEA, 0x5834, 0x8FEB, 0x58CC, 0x8FEC, 0x5B22, 0x8FED, 0x5E38, + 0x8FEE, 0x60C5, 0x8FEF, 0x64FE, 0x8FF0, 0x6761, 0x8FF1, 0x6756, 0x8FF2, 0x6D44, 0x8FF3, 0x72B6, 0x8FF4, 0x7573, 0x8FF5, 0x7A63, + 0x8FF6, 0x84B8, 0x8FF7, 0x8B72, 0x8FF8, 0x91B8, 0x8FF9, 0x9320, 0x8FFA, 0x5631, 0x8FFB, 0x57F4, 0x8FFC, 0x98FE, 0x9040, 0x62ED, + 0x9041, 0x690D, 0x9042, 0x6B96, 0x9043, 0x71ED, 0x9044, 0x7E54, 0x9045, 0x8077, 0x9046, 0x8272, 0x9047, 0x89E6, 0x9048, 0x98DF, + 0x9049, 0x8755, 0x904A, 0x8FB1, 0x904B, 0x5C3B, 0x904C, 0x4F38, 0x904D, 0x4FE1, 0x904E, 0x4FB5, 0x904F, 0x5507, 0x9050, 0x5A20, + 0x9051, 0x5BDD, 0x9052, 0x5BE9, 0x9053, 0x5FC3, 0x9054, 0x614E, 0x9055, 0x632F, 0x9056, 0x65B0, 0x9057, 0x664B, 0x9058, 0x68EE, + 0x9059, 0x699B, 0x905A, 0x6D78, 0x905B, 0x6DF1, 0x905C, 0x7533, 0x905D, 0x75B9, 0x905E, 0x771F, 0x905F, 0x795E, 0x9060, 0x79E6, + 0x9061, 0x7D33, 0x9062, 0x81E3, 0x9063, 0x82AF, 0x9064, 0x85AA, 0x9065, 0x89AA, 0x9066, 0x8A3A, 0x9067, 0x8EAB, 0x9068, 0x8F9B, + 0x9069, 0x9032, 0x906A, 0x91DD, 0x906B, 0x9707, 0x906C, 0x4EBA, 0x906D, 0x4EC1, 0x906E, 0x5203, 0x906F, 0x5875, 0x9070, 0x58EC, + 0x9071, 0x5C0B, 0x9072, 0x751A, 0x9073, 0x5C3D, 0x9074, 0x814E, 0x9075, 0x8A0A, 0x9076, 0x8FC5, 0x9077, 0x9663, 0x9078, 0x976D, + 0x9079, 0x7B25, 0x907A, 0x8ACF, 0x907B, 0x9808, 0x907C, 0x9162, 0x907D, 0x56F3, 0x907E, 0x53A8, 0x9080, 0x9017, 0x9081, 0x5439, + 0x9082, 0x5782, 0x9083, 0x5E25, 0x9084, 0x63A8, 0x9085, 0x6C34, 0x9086, 0x708A, 0x9087, 0x7761, 0x9088, 0x7C8B, 0x9089, 0x7FE0, + 0x908A, 0x8870, 0x908B, 0x9042, 0x908C, 0x9154, 0x908D, 0x9310, 0x908E, 0x9318, 0x908F, 0x968F, 0x9090, 0x745E, 0x9091, 0x9AC4, + 0x9092, 0x5D07, 0x9093, 0x5D69, 0x9094, 0x6570, 0x9095, 0x67A2, 0x9096, 0x8DA8, 0x9097, 0x96DB, 0x9098, 0x636E, 0x9099, 0x6749, + 0x909A, 0x6919, 0x909B, 0x83C5, 0x909C, 0x9817, 0x909D, 0x96C0, 0x909E, 0x88FE, 0x909F, 0x6F84, 0x90A0, 0x647A, 0x90A1, 0x5BF8, + 0x90A2, 0x4E16, 0x90A3, 0x702C, 0x90A4, 0x755D, 0x90A5, 0x662F, 0x90A6, 0x51C4, 0x90A7, 0x5236, 0x90A8, 0x52E2, 0x90A9, 0x59D3, + 0x90AA, 0x5F81, 0x90AB, 0x6027, 0x90AC, 0x6210, 0x90AD, 0x653F, 0x90AE, 0x6574, 0x90AF, 0x661F, 0x90B0, 0x6674, 0x90B1, 0x68F2, + 0x90B2, 0x6816, 0x90B3, 0x6B63, 0x90B4, 0x6E05, 0x90B5, 0x7272, 0x90B6, 0x751F, 0x90B7, 0x76DB, 0x90B8, 0x7CBE, 0x90B9, 0x8056, + 0x90BA, 0x58F0, 0x90BB, 0x88FD, 0x90BC, 0x897F, 0x90BD, 0x8AA0, 0x90BE, 0x8A93, 0x90BF, 0x8ACB, 0x90C0, 0x901D, 0x90C1, 0x9192, + 0x90C2, 0x9752, 0x90C3, 0x9759, 0x90C4, 0x6589, 0x90C5, 0x7A0E, 0x90C6, 0x8106, 0x90C7, 0x96BB, 0x90C8, 0x5E2D, 0x90C9, 0x60DC, + 0x90CA, 0x621A, 0x90CB, 0x65A5, 0x90CC, 0x6614, 0x90CD, 0x6790, 0x90CE, 0x77F3, 0x90CF, 0x7A4D, 0x90D0, 0x7C4D, 0x90D1, 0x7E3E, + 0x90D2, 0x810A, 0x90D3, 0x8CAC, 0x90D4, 0x8D64, 0x90D5, 0x8DE1, 0x90D6, 0x8E5F, 0x90D7, 0x78A9, 0x90D8, 0x5207, 0x90D9, 0x62D9, + 0x90DA, 0x63A5, 0x90DB, 0x6442, 0x90DC, 0x6298, 0x90DD, 0x8A2D, 0x90DE, 0x7A83, 0x90DF, 0x7BC0, 0x90E0, 0x8AAC, 0x90E1, 0x96EA, + 0x90E2, 0x7D76, 0x90E3, 0x820C, 0x90E4, 0x8749, 0x90E5, 0x4ED9, 0x90E6, 0x5148, 0x90E7, 0x5343, 0x90E8, 0x5360, 0x90E9, 0x5BA3, + 0x90EA, 0x5C02, 0x90EB, 0x5C16, 0x90EC, 0x5DDD, 0x90ED, 0x6226, 0x90EE, 0x6247, 0x90EF, 0x64B0, 0x90F0, 0x6813, 0x90F1, 0x6834, + 0x90F2, 0x6CC9, 0x90F3, 0x6D45, 0x90F4, 0x6D17, 0x90F5, 0x67D3, 0x90F6, 0x6F5C, 0x90F7, 0x714E, 0x90F8, 0x717D, 0x90F9, 0x65CB, + 0x90FA, 0x7A7F, 0x90FB, 0x7BAD, 0x90FC, 0x7DDA, 0x9140, 0x7E4A, 0x9141, 0x7FA8, 0x9142, 0x817A, 0x9143, 0x821B, 0x9144, 0x8239, + 0x9145, 0x85A6, 0x9146, 0x8A6E, 0x9147, 0x8CCE, 0x9148, 0x8DF5, 0x9149, 0x9078, 0x914A, 0x9077, 0x914B, 0x92AD, 0x914C, 0x9291, + 0x914D, 0x9583, 0x914E, 0x9BAE, 0x914F, 0x524D, 0x9150, 0x5584, 0x9151, 0x6F38, 0x9152, 0x7136, 0x9153, 0x5168, 0x9154, 0x7985, + 0x9155, 0x7E55, 0x9156, 0x81B3, 0x9157, 0x7CCE, 0x9158, 0x564C, 0x9159, 0x5851, 0x915A, 0x5CA8, 0x915B, 0x63AA, 0x915C, 0x66FE, + 0x915D, 0x66FD, 0x915E, 0x695A, 0x915F, 0x72D9, 0x9160, 0x758F, 0x9161, 0x758E, 0x9162, 0x790E, 0x9163, 0x7956, 0x9164, 0x79DF, + 0x9165, 0x7C97, 0x9166, 0x7D20, 0x9167, 0x7D44, 0x9168, 0x8607, 0x9169, 0x8A34, 0x916A, 0x963B, 0x916B, 0x9061, 0x916C, 0x9F20, + 0x916D, 0x50E7, 0x916E, 0x5275, 0x916F, 0x53CC, 0x9170, 0x53E2, 0x9171, 0x5009, 0x9172, 0x55AA, 0x9173, 0x58EE, 0x9174, 0x594F, + 0x9175, 0x723D, 0x9176, 0x5B8B, 0x9177, 0x5C64, 0x9178, 0x531D, 0x9179, 0x60E3, 0x917A, 0x60F3, 0x917B, 0x635C, 0x917C, 0x6383, + 0x917D, 0x633F, 0x917E, 0x63BB, 0x9180, 0x64CD, 0x9181, 0x65E9, 0x9182, 0x66F9, 0x9183, 0x5DE3, 0x9184, 0x69CD, 0x9185, 0x69FD, + 0x9186, 0x6F15, 0x9187, 0x71E5, 0x9188, 0x4E89, 0x9189, 0x75E9, 0x918A, 0x76F8, 0x918B, 0x7A93, 0x918C, 0x7CDF, 0x918D, 0x7DCF, + 0x918E, 0x7D9C, 0x918F, 0x8061, 0x9190, 0x8349, 0x9191, 0x8358, 0x9192, 0x846C, 0x9193, 0x84BC, 0x9194, 0x85FB, 0x9195, 0x88C5, + 0x9196, 0x8D70, 0x9197, 0x9001, 0x9198, 0x906D, 0x9199, 0x9397, 0x919A, 0x971C, 0x919B, 0x9A12, 0x919C, 0x50CF, 0x919D, 0x5897, + 0x919E, 0x618E, 0x919F, 0x81D3, 0x91A0, 0x8535, 0x91A1, 0x8D08, 0x91A2, 0x9020, 0x91A3, 0x4FC3, 0x91A4, 0x5074, 0x91A5, 0x5247, + 0x91A6, 0x5373, 0x91A7, 0x606F, 0x91A8, 0x6349, 0x91A9, 0x675F, 0x91AA, 0x6E2C, 0x91AB, 0x8DB3, 0x91AC, 0x901F, 0x91AD, 0x4FD7, + 0x91AE, 0x5C5E, 0x91AF, 0x8CCA, 0x91B0, 0x65CF, 0x91B1, 0x7D9A, 0x91B2, 0x5352, 0x91B3, 0x8896, 0x91B4, 0x5176, 0x91B5, 0x63C3, + 0x91B6, 0x5B58, 0x91B7, 0x5B6B, 0x91B8, 0x5C0A, 0x91B9, 0x640D, 0x91BA, 0x6751, 0x91BB, 0x905C, 0x91BC, 0x4ED6, 0x91BD, 0x591A, + 0x91BE, 0x592A, 0x91BF, 0x6C70, 0x91C0, 0x8A51, 0x91C1, 0x553E, 0x91C2, 0x5815, 0x91C3, 0x59A5, 0x91C4, 0x60F0, 0x91C5, 0x6253, + 0x91C6, 0x67C1, 0x91C7, 0x8235, 0x91C8, 0x6955, 0x91C9, 0x9640, 0x91CA, 0x99C4, 0x91CB, 0x9A28, 0x91CC, 0x4F53, 0x91CD, 0x5806, + 0x91CE, 0x5BFE, 0x91CF, 0x8010, 0x91D0, 0x5CB1, 0x91D1, 0x5E2F, 0x91D2, 0x5F85, 0x91D3, 0x6020, 0x91D4, 0x614B, 0x91D5, 0x6234, + 0x91D6, 0x66FF, 0x91D7, 0x6CF0, 0x91D8, 0x6EDE, 0x91D9, 0x80CE, 0x91DA, 0x817F, 0x91DB, 0x82D4, 0x91DC, 0x888B, 0x91DD, 0x8CB8, + 0x91DE, 0x9000, 0x91DF, 0x902E, 0x91E0, 0x968A, 0x91E1, 0x9EDB, 0x91E2, 0x9BDB, 0x91E3, 0x4EE3, 0x91E4, 0x53F0, 0x91E5, 0x5927, + 0x91E6, 0x7B2C, 0x91E7, 0x918D, 0x91E8, 0x984C, 0x91E9, 0x9DF9, 0x91EA, 0x6EDD, 0x91EB, 0x7027, 0x91EC, 0x5353, 0x91ED, 0x5544, + 0x91EE, 0x5B85, 0x91EF, 0x6258, 0x91F0, 0x629E, 0x91F1, 0x62D3, 0x91F2, 0x6CA2, 0x91F3, 0x6FEF, 0x91F4, 0x7422, 0x91F5, 0x8A17, + 0x91F6, 0x9438, 0x91F7, 0x6FC1, 0x91F8, 0x8AFE, 0x91F9, 0x8338, 0x91FA, 0x51E7, 0x91FB, 0x86F8, 0x91FC, 0x53EA, 0x9240, 0x53E9, + 0x9241, 0x4F46, 0x9242, 0x9054, 0x9243, 0x8FB0, 0x9244, 0x596A, 0x9245, 0x8131, 0x9246, 0x5DFD, 0x9247, 0x7AEA, 0x9248, 0x8FBF, + 0x9249, 0x68DA, 0x924A, 0x8C37, 0x924B, 0x72F8, 0x924C, 0x9C48, 0x924D, 0x6A3D, 0x924E, 0x8AB0, 0x924F, 0x4E39, 0x9250, 0x5358, + 0x9251, 0x5606, 0x9252, 0x5766, 0x9253, 0x62C5, 0x9254, 0x63A2, 0x9255, 0x65E6, 0x9256, 0x6B4E, 0x9257, 0x6DE1, 0x9258, 0x6E5B, + 0x9259, 0x70AD, 0x925A, 0x77ED, 0x925B, 0x7AEF, 0x925C, 0x7BAA, 0x925D, 0x7DBB, 0x925E, 0x803D, 0x925F, 0x80C6, 0x9260, 0x86CB, + 0x9261, 0x8A95, 0x9262, 0x935B, 0x9263, 0x56E3, 0x9264, 0x58C7, 0x9265, 0x5F3E, 0x9266, 0x65AD, 0x9267, 0x6696, 0x9268, 0x6A80, + 0x9269, 0x6BB5, 0x926A, 0x7537, 0x926B, 0x8AC7, 0x926C, 0x5024, 0x926D, 0x77E5, 0x926E, 0x5730, 0x926F, 0x5F1B, 0x9270, 0x6065, + 0x9271, 0x667A, 0x9272, 0x6C60, 0x9273, 0x75F4, 0x9274, 0x7A1A, 0x9275, 0x7F6E, 0x9276, 0x81F4, 0x9277, 0x8718, 0x9278, 0x9045, + 0x9279, 0x99B3, 0x927A, 0x7BC9, 0x927B, 0x755C, 0x927C, 0x7AF9, 0x927D, 0x7B51, 0x927E, 0x84C4, 0x9280, 0x9010, 0x9281, 0x79E9, + 0x9282, 0x7A92, 0x9283, 0x8336, 0x9284, 0x5AE1, 0x9285, 0x7740, 0x9286, 0x4E2D, 0x9287, 0x4EF2, 0x9288, 0x5B99, 0x9289, 0x5FE0, + 0x928A, 0x62BD, 0x928B, 0x663C, 0x928C, 0x67F1, 0x928D, 0x6CE8, 0x928E, 0x866B, 0x928F, 0x8877, 0x9290, 0x8A3B, 0x9291, 0x914E, + 0x9292, 0x92F3, 0x9293, 0x99D0, 0x9294, 0x6A17, 0x9295, 0x7026, 0x9296, 0x732A, 0x9297, 0x82E7, 0x9298, 0x8457, 0x9299, 0x8CAF, + 0x929A, 0x4E01, 0x929B, 0x5146, 0x929C, 0x51CB, 0x929D, 0x558B, 0x929E, 0x5BF5, 0x929F, 0x5E16, 0x92A0, 0x5E33, 0x92A1, 0x5E81, + 0x92A2, 0x5F14, 0x92A3, 0x5F35, 0x92A4, 0x5F6B, 0x92A5, 0x5FB4, 0x92A6, 0x61F2, 0x92A7, 0x6311, 0x92A8, 0x66A2, 0x92A9, 0x671D, + 0x92AA, 0x6F6E, 0x92AB, 0x7252, 0x92AC, 0x753A, 0x92AD, 0x773A, 0x92AE, 0x8074, 0x92AF, 0x8139, 0x92B0, 0x8178, 0x92B1, 0x8776, + 0x92B2, 0x8ABF, 0x92B3, 0x8ADC, 0x92B4, 0x8D85, 0x92B5, 0x8DF3, 0x92B6, 0x929A, 0x92B7, 0x9577, 0x92B8, 0x9802, 0x92B9, 0x9CE5, + 0x92BA, 0x52C5, 0x92BB, 0x6357, 0x92BC, 0x76F4, 0x92BD, 0x6715, 0x92BE, 0x6C88, 0x92BF, 0x73CD, 0x92C0, 0x8CC3, 0x92C1, 0x93AE, + 0x92C2, 0x9673, 0x92C3, 0x6D25, 0x92C4, 0x589C, 0x92C5, 0x690E, 0x92C6, 0x69CC, 0x92C7, 0x8FFD, 0x92C8, 0x939A, 0x92C9, 0x75DB, + 0x92CA, 0x901A, 0x92CB, 0x585A, 0x92CC, 0x6802, 0x92CD, 0x63B4, 0x92CE, 0x69FB, 0x92CF, 0x4F43, 0x92D0, 0x6F2C, 0x92D1, 0x67D8, + 0x92D2, 0x8FBB, 0x92D3, 0x8526, 0x92D4, 0x7DB4, 0x92D5, 0x9354, 0x92D6, 0x693F, 0x92D7, 0x6F70, 0x92D8, 0x576A, 0x92D9, 0x58F7, + 0x92DA, 0x5B2C, 0x92DB, 0x7D2C, 0x92DC, 0x722A, 0x92DD, 0x540A, 0x92DE, 0x91E3, 0x92DF, 0x9DB4, 0x92E0, 0x4EAD, 0x92E1, 0x4F4E, + 0x92E2, 0x505C, 0x92E3, 0x5075, 0x92E4, 0x5243, 0x92E5, 0x8C9E, 0x92E6, 0x5448, 0x92E7, 0x5824, 0x92E8, 0x5B9A, 0x92E9, 0x5E1D, + 0x92EA, 0x5E95, 0x92EB, 0x5EAD, 0x92EC, 0x5EF7, 0x92ED, 0x5F1F, 0x92EE, 0x608C, 0x92EF, 0x62B5, 0x92F0, 0x633A, 0x92F1, 0x63D0, + 0x92F2, 0x68AF, 0x92F3, 0x6C40, 0x92F4, 0x7887, 0x92F5, 0x798E, 0x92F6, 0x7A0B, 0x92F7, 0x7DE0, 0x92F8, 0x8247, 0x92F9, 0x8A02, + 0x92FA, 0x8AE6, 0x92FB, 0x8E44, 0x92FC, 0x9013, 0x9340, 0x90B8, 0x9341, 0x912D, 0x9342, 0x91D8, 0x9343, 0x9F0E, 0x9344, 0x6CE5, + 0x9345, 0x6458, 0x9346, 0x64E2, 0x9347, 0x6575, 0x9348, 0x6EF4, 0x9349, 0x7684, 0x934A, 0x7B1B, 0x934B, 0x9069, 0x934C, 0x93D1, + 0x934D, 0x6EBA, 0x934E, 0x54F2, 0x934F, 0x5FB9, 0x9350, 0x64A4, 0x9351, 0x8F4D, 0x9352, 0x8FED, 0x9353, 0x9244, 0x9354, 0x5178, + 0x9355, 0x586B, 0x9356, 0x5929, 0x9357, 0x5C55, 0x9358, 0x5E97, 0x9359, 0x6DFB, 0x935A, 0x7E8F, 0x935B, 0x751C, 0x935C, 0x8CBC, + 0x935D, 0x8EE2, 0x935E, 0x985B, 0x935F, 0x70B9, 0x9360, 0x4F1D, 0x9361, 0x6BBF, 0x9362, 0x6FB1, 0x9363, 0x7530, 0x9364, 0x96FB, + 0x9365, 0x514E, 0x9366, 0x5410, 0x9367, 0x5835, 0x9368, 0x5857, 0x9369, 0x59AC, 0x936A, 0x5C60, 0x936B, 0x5F92, 0x936C, 0x6597, + 0x936D, 0x675C, 0x936E, 0x6E21, 0x936F, 0x767B, 0x9370, 0x83DF, 0x9371, 0x8CED, 0x9372, 0x9014, 0x9373, 0x90FD, 0x9374, 0x934D, + 0x9375, 0x7825, 0x9376, 0x783A, 0x9377, 0x52AA, 0x9378, 0x5EA6, 0x9379, 0x571F, 0x937A, 0x5974, 0x937B, 0x6012, 0x937C, 0x5012, + 0x937D, 0x515A, 0x937E, 0x51AC, 0x9380, 0x51CD, 0x9381, 0x5200, 0x9382, 0x5510, 0x9383, 0x5854, 0x9384, 0x5858, 0x9385, 0x5957, + 0x9386, 0x5B95, 0x9387, 0x5CF6, 0x9388, 0x5D8B, 0x9389, 0x60BC, 0x938A, 0x6295, 0x938B, 0x642D, 0x938C, 0x6771, 0x938D, 0x6843, + 0x938E, 0x68BC, 0x938F, 0x68DF, 0x9390, 0x76D7, 0x9391, 0x6DD8, 0x9392, 0x6E6F, 0x9393, 0x6D9B, 0x9394, 0x706F, 0x9395, 0x71C8, + 0x9396, 0x5F53, 0x9397, 0x75D8, 0x9398, 0x7977, 0x9399, 0x7B49, 0x939A, 0x7B54, 0x939B, 0x7B52, 0x939C, 0x7CD6, 0x939D, 0x7D71, + 0x939E, 0x5230, 0x939F, 0x8463, 0x93A0, 0x8569, 0x93A1, 0x85E4, 0x93A2, 0x8A0E, 0x93A3, 0x8B04, 0x93A4, 0x8C46, 0x93A5, 0x8E0F, + 0x93A6, 0x9003, 0x93A7, 0x900F, 0x93A8, 0x9419, 0x93A9, 0x9676, 0x93AA, 0x982D, 0x93AB, 0x9A30, 0x93AC, 0x95D8, 0x93AD, 0x50CD, + 0x93AE, 0x52D5, 0x93AF, 0x540C, 0x93B0, 0x5802, 0x93B1, 0x5C0E, 0x93B2, 0x61A7, 0x93B3, 0x649E, 0x93B4, 0x6D1E, 0x93B5, 0x77B3, + 0x93B6, 0x7AE5, 0x93B7, 0x80F4, 0x93B8, 0x8404, 0x93B9, 0x9053, 0x93BA, 0x9285, 0x93BB, 0x5CE0, 0x93BC, 0x9D07, 0x93BD, 0x533F, + 0x93BE, 0x5F97, 0x93BF, 0x5FB3, 0x93C0, 0x6D9C, 0x93C1, 0x7279, 0x93C2, 0x7763, 0x93C3, 0x79BF, 0x93C4, 0x7BE4, 0x93C5, 0x6BD2, + 0x93C6, 0x72EC, 0x93C7, 0x8AAD, 0x93C8, 0x6803, 0x93C9, 0x6A61, 0x93CA, 0x51F8, 0x93CB, 0x7A81, 0x93CC, 0x6934, 0x93CD, 0x5C4A, + 0x93CE, 0x9CF6, 0x93CF, 0x82EB, 0x93D0, 0x5BC5, 0x93D1, 0x9149, 0x93D2, 0x701E, 0x93D3, 0x5678, 0x93D4, 0x5C6F, 0x93D5, 0x60C7, + 0x93D6, 0x6566, 0x93D7, 0x6C8C, 0x93D8, 0x8C5A, 0x93D9, 0x9041, 0x93DA, 0x9813, 0x93DB, 0x5451, 0x93DC, 0x66C7, 0x93DD, 0x920D, + 0x93DE, 0x5948, 0x93DF, 0x90A3, 0x93E0, 0x5185, 0x93E1, 0x4E4D, 0x93E2, 0x51EA, 0x93E3, 0x8599, 0x93E4, 0x8B0E, 0x93E5, 0x7058, + 0x93E6, 0x637A, 0x93E7, 0x934B, 0x93E8, 0x6962, 0x93E9, 0x99B4, 0x93EA, 0x7E04, 0x93EB, 0x7577, 0x93EC, 0x5357, 0x93ED, 0x6960, + 0x93EE, 0x8EDF, 0x93EF, 0x96E3, 0x93F0, 0x6C5D, 0x93F1, 0x4E8C, 0x93F2, 0x5C3C, 0x93F3, 0x5F10, 0x93F4, 0x8FE9, 0x93F5, 0x5302, + 0x93F6, 0x8CD1, 0x93F7, 0x8089, 0x93F8, 0x8679, 0x93F9, 0x5EFF, 0x93FA, 0x65E5, 0x93FB, 0x4E73, 0x93FC, 0x5165, 0x9440, 0x5982, + 0x9441, 0x5C3F, 0x9442, 0x97EE, 0x9443, 0x4EFB, 0x9444, 0x598A, 0x9445, 0x5FCD, 0x9446, 0x8A8D, 0x9447, 0x6FE1, 0x9448, 0x79B0, + 0x9449, 0x7962, 0x944A, 0x5BE7, 0x944B, 0x8471, 0x944C, 0x732B, 0x944D, 0x71B1, 0x944E, 0x5E74, 0x944F, 0x5FF5, 0x9450, 0x637B, + 0x9451, 0x649A, 0x9452, 0x71C3, 0x9453, 0x7C98, 0x9454, 0x4E43, 0x9455, 0x5EFC, 0x9456, 0x4E4B, 0x9457, 0x57DC, 0x9458, 0x56A2, + 0x9459, 0x60A9, 0x945A, 0x6FC3, 0x945B, 0x7D0D, 0x945C, 0x80FD, 0x945D, 0x8133, 0x945E, 0x81BF, 0x945F, 0x8FB2, 0x9460, 0x8997, + 0x9461, 0x86A4, 0x9462, 0x5DF4, 0x9463, 0x628A, 0x9464, 0x64AD, 0x9465, 0x8987, 0x9466, 0x6777, 0x9467, 0x6CE2, 0x9468, 0x6D3E, + 0x9469, 0x7436, 0x946A, 0x7834, 0x946B, 0x5A46, 0x946C, 0x7F75, 0x946D, 0x82AD, 0x946E, 0x99AC, 0x946F, 0x4FF3, 0x9470, 0x5EC3, + 0x9471, 0x62DD, 0x9472, 0x6392, 0x9473, 0x6557, 0x9474, 0x676F, 0x9475, 0x76C3, 0x9476, 0x724C, 0x9477, 0x80CC, 0x9478, 0x80BA, + 0x9479, 0x8F29, 0x947A, 0x914D, 0x947B, 0x500D, 0x947C, 0x57F9, 0x947D, 0x5A92, 0x947E, 0x6885, 0x9480, 0x6973, 0x9481, 0x7164, + 0x9482, 0x72FD, 0x9483, 0x8CB7, 0x9484, 0x58F2, 0x9485, 0x8CE0, 0x9486, 0x966A, 0x9487, 0x9019, 0x9488, 0x877F, 0x9489, 0x79E4, + 0x948A, 0x77E7, 0x948B, 0x8429, 0x948C, 0x4F2F, 0x948D, 0x5265, 0x948E, 0x535A, 0x948F, 0x62CD, 0x9490, 0x67CF, 0x9491, 0x6CCA, + 0x9492, 0x767D, 0x9493, 0x7B94, 0x9494, 0x7C95, 0x9495, 0x8236, 0x9496, 0x8584, 0x9497, 0x8FEB, 0x9498, 0x66DD, 0x9499, 0x6F20, + 0x949A, 0x7206, 0x949B, 0x7E1B, 0x949C, 0x83AB, 0x949D, 0x99C1, 0x949E, 0x9EA6, 0x949F, 0x51FD, 0x94A0, 0x7BB1, 0x94A1, 0x7872, + 0x94A2, 0x7BB8, 0x94A3, 0x8087, 0x94A4, 0x7B48, 0x94A5, 0x6AE8, 0x94A6, 0x5E61, 0x94A7, 0x808C, 0x94A8, 0x7551, 0x94A9, 0x7560, + 0x94AA, 0x516B, 0x94AB, 0x9262, 0x94AC, 0x6E8C, 0x94AD, 0x767A, 0x94AE, 0x9197, 0x94AF, 0x9AEA, 0x94B0, 0x4F10, 0x94B1, 0x7F70, + 0x94B2, 0x629C, 0x94B3, 0x7B4F, 0x94B4, 0x95A5, 0x94B5, 0x9CE9, 0x94B6, 0x567A, 0x94B7, 0x5859, 0x94B8, 0x86E4, 0x94B9, 0x96BC, + 0x94BA, 0x4F34, 0x94BB, 0x5224, 0x94BC, 0x534A, 0x94BD, 0x53CD, 0x94BE, 0x53DB, 0x94BF, 0x5E06, 0x94C0, 0x642C, 0x94C1, 0x6591, + 0x94C2, 0x677F, 0x94C3, 0x6C3E, 0x94C4, 0x6C4E, 0x94C5, 0x7248, 0x94C6, 0x72AF, 0x94C7, 0x73ED, 0x94C8, 0x7554, 0x94C9, 0x7E41, + 0x94CA, 0x822C, 0x94CB, 0x85E9, 0x94CC, 0x8CA9, 0x94CD, 0x7BC4, 0x94CE, 0x91C6, 0x94CF, 0x7169, 0x94D0, 0x9812, 0x94D1, 0x98EF, + 0x94D2, 0x633D, 0x94D3, 0x6669, 0x94D4, 0x756A, 0x94D5, 0x76E4, 0x94D6, 0x78D0, 0x94D7, 0x8543, 0x94D8, 0x86EE, 0x94D9, 0x532A, + 0x94DA, 0x5351, 0x94DB, 0x5426, 0x94DC, 0x5983, 0x94DD, 0x5E87, 0x94DE, 0x5F7C, 0x94DF, 0x60B2, 0x94E0, 0x6249, 0x94E1, 0x6279, + 0x94E2, 0x62AB, 0x94E3, 0x6590, 0x94E4, 0x6BD4, 0x94E5, 0x6CCC, 0x94E6, 0x75B2, 0x94E7, 0x76AE, 0x94E8, 0x7891, 0x94E9, 0x79D8, + 0x94EA, 0x7DCB, 0x94EB, 0x7F77, 0x94EC, 0x80A5, 0x94ED, 0x88AB, 0x94EE, 0x8AB9, 0x94EF, 0x8CBB, 0x94F0, 0x907F, 0x94F1, 0x975E, + 0x94F2, 0x98DB, 0x94F3, 0x6A0B, 0x94F4, 0x7C38, 0x94F5, 0x5099, 0x94F6, 0x5C3E, 0x94F7, 0x5FAE, 0x94F8, 0x6787, 0x94F9, 0x6BD8, + 0x94FA, 0x7435, 0x94FB, 0x7709, 0x94FC, 0x7F8E, 0x9540, 0x9F3B, 0x9541, 0x67CA, 0x9542, 0x7A17, 0x9543, 0x5339, 0x9544, 0x758B, + 0x9545, 0x9AED, 0x9546, 0x5F66, 0x9547, 0x819D, 0x9548, 0x83F1, 0x9549, 0x8098, 0x954A, 0x5F3C, 0x954B, 0x5FC5, 0x954C, 0x7562, + 0x954D, 0x7B46, 0x954E, 0x903C, 0x954F, 0x6867, 0x9550, 0x59EB, 0x9551, 0x5A9B, 0x9552, 0x7D10, 0x9553, 0x767E, 0x9554, 0x8B2C, + 0x9555, 0x4FF5, 0x9556, 0x5F6A, 0x9557, 0x6A19, 0x9558, 0x6C37, 0x9559, 0x6F02, 0x955A, 0x74E2, 0x955B, 0x7968, 0x955C, 0x8868, + 0x955D, 0x8A55, 0x955E, 0x8C79, 0x955F, 0x5EDF, 0x9560, 0x63CF, 0x9561, 0x75C5, 0x9562, 0x79D2, 0x9563, 0x82D7, 0x9564, 0x9328, + 0x9565, 0x92F2, 0x9566, 0x849C, 0x9567, 0x86ED, 0x9568, 0x9C2D, 0x9569, 0x54C1, 0x956A, 0x5F6C, 0x956B, 0x658C, 0x956C, 0x6D5C, + 0x956D, 0x7015, 0x956E, 0x8CA7, 0x956F, 0x8CD3, 0x9570, 0x983B, 0x9571, 0x654F, 0x9572, 0x74F6, 0x9573, 0x4E0D, 0x9574, 0x4ED8, + 0x9575, 0x57E0, 0x9576, 0x592B, 0x9577, 0x5A66, 0x9578, 0x5BCC, 0x9579, 0x51A8, 0x957A, 0x5E03, 0x957B, 0x5E9C, 0x957C, 0x6016, + 0x957D, 0x6276, 0x957E, 0x6577, 0x9580, 0x65A7, 0x9581, 0x666E, 0x9582, 0x6D6E, 0x9583, 0x7236, 0x9584, 0x7B26, 0x9585, 0x8150, + 0x9586, 0x819A, 0x9587, 0x8299, 0x9588, 0x8B5C, 0x9589, 0x8CA0, 0x958A, 0x8CE6, 0x958B, 0x8D74, 0x958C, 0x961C, 0x958D, 0x9644, + 0x958E, 0x4FAE, 0x958F, 0x64AB, 0x9590, 0x6B66, 0x9591, 0x821E, 0x9592, 0x8461, 0x9593, 0x856A, 0x9594, 0x90E8, 0x9595, 0x5C01, + 0x9596, 0x6953, 0x9597, 0x98A8, 0x9598, 0x847A, 0x9599, 0x8557, 0x959A, 0x4F0F, 0x959B, 0x526F, 0x959C, 0x5FA9, 0x959D, 0x5E45, + 0x959E, 0x670D, 0x959F, 0x798F, 0x95A0, 0x8179, 0x95A1, 0x8907, 0x95A2, 0x8986, 0x95A3, 0x6DF5, 0x95A4, 0x5F17, 0x95A5, 0x6255, + 0x95A6, 0x6CB8, 0x95A7, 0x4ECF, 0x95A8, 0x7269, 0x95A9, 0x9B92, 0x95AA, 0x5206, 0x95AB, 0x543B, 0x95AC, 0x5674, 0x95AD, 0x58B3, + 0x95AE, 0x61A4, 0x95AF, 0x626E, 0x95B0, 0x711A, 0x95B1, 0x596E, 0x95B2, 0x7C89, 0x95B3, 0x7CDE, 0x95B4, 0x7D1B, 0x95B5, 0x96F0, + 0x95B6, 0x6587, 0x95B7, 0x805E, 0x95B8, 0x4E19, 0x95B9, 0x4F75, 0x95BA, 0x5175, 0x95BB, 0x5840, 0x95BC, 0x5E63, 0x95BD, 0x5E73, + 0x95BE, 0x5F0A, 0x95BF, 0x67C4, 0x95C0, 0x4E26, 0x95C1, 0x853D, 0x95C2, 0x9589, 0x95C3, 0x965B, 0x95C4, 0x7C73, 0x95C5, 0x9801, + 0x95C6, 0x50FB, 0x95C7, 0x58C1, 0x95C8, 0x7656, 0x95C9, 0x78A7, 0x95CA, 0x5225, 0x95CB, 0x77A5, 0x95CC, 0x8511, 0x95CD, 0x7B86, + 0x95CE, 0x504F, 0x95CF, 0x5909, 0x95D0, 0x7247, 0x95D1, 0x7BC7, 0x95D2, 0x7DE8, 0x95D3, 0x8FBA, 0x95D4, 0x8FD4, 0x95D5, 0x904D, + 0x95D6, 0x4FBF, 0x95D7, 0x52C9, 0x95D8, 0x5A29, 0x95D9, 0x5F01, 0x95DA, 0x97AD, 0x95DB, 0x4FDD, 0x95DC, 0x8217, 0x95DD, 0x92EA, + 0x95DE, 0x5703, 0x95DF, 0x6355, 0x95E0, 0x6B69, 0x95E1, 0x752B, 0x95E2, 0x88DC, 0x95E3, 0x8F14, 0x95E4, 0x7A42, 0x95E5, 0x52DF, + 0x95E6, 0x5893, 0x95E7, 0x6155, 0x95E8, 0x620A, 0x95E9, 0x66AE, 0x95EA, 0x6BCD, 0x95EB, 0x7C3F, 0x95EC, 0x83E9, 0x95ED, 0x5023, + 0x95EE, 0x4FF8, 0x95EF, 0x5305, 0x95F0, 0x5446, 0x95F1, 0x5831, 0x95F2, 0x5949, 0x95F3, 0x5B9D, 0x95F4, 0x5CF0, 0x95F5, 0x5CEF, + 0x95F6, 0x5D29, 0x95F7, 0x5E96, 0x95F8, 0x62B1, 0x95F9, 0x6367, 0x95FA, 0x653E, 0x95FB, 0x65B9, 0x95FC, 0x670B, 0x9640, 0x6CD5, + 0x9641, 0x6CE1, 0x9642, 0x70F9, 0x9643, 0x7832, 0x9644, 0x7E2B, 0x9645, 0x80DE, 0x9646, 0x82B3, 0x9647, 0x840C, 0x9648, 0x84EC, + 0x9649, 0x8702, 0x964A, 0x8912, 0x964B, 0x8A2A, 0x964C, 0x8C4A, 0x964D, 0x90A6, 0x964E, 0x92D2, 0x964F, 0x98FD, 0x9650, 0x9CF3, + 0x9651, 0x9D6C, 0x9652, 0x4E4F, 0x9653, 0x4EA1, 0x9654, 0x508D, 0x9655, 0x5256, 0x9656, 0x574A, 0x9657, 0x59A8, 0x9658, 0x5E3D, + 0x9659, 0x5FD8, 0x965A, 0x5FD9, 0x965B, 0x623F, 0x965C, 0x66B4, 0x965D, 0x671B, 0x965E, 0x67D0, 0x965F, 0x68D2, 0x9660, 0x5192, + 0x9661, 0x7D21, 0x9662, 0x80AA, 0x9663, 0x81A8, 0x9664, 0x8B00, 0x9665, 0x8C8C, 0x9666, 0x8CBF, 0x9667, 0x927E, 0x9668, 0x9632, + 0x9669, 0x5420, 0x966A, 0x982C, 0x966B, 0x5317, 0x966C, 0x50D5, 0x966D, 0x535C, 0x966E, 0x58A8, 0x966F, 0x64B2, 0x9670, 0x6734, + 0x9671, 0x7267, 0x9672, 0x7766, 0x9673, 0x7A46, 0x9674, 0x91E6, 0x9675, 0x52C3, 0x9676, 0x6CA1, 0x9677, 0x6B86, 0x9678, 0x5800, + 0x9679, 0x5E4C, 0x967A, 0x5954, 0x967B, 0x672C, 0x967C, 0x7FFB, 0x967D, 0x51E1, 0x967E, 0x76C6, 0x9680, 0x6469, 0x9681, 0x78E8, + 0x9682, 0x9B54, 0x9683, 0x9EBB, 0x9684, 0x57CB, 0x9685, 0x59B9, 0x9686, 0x6627, 0x9687, 0x679A, 0x9688, 0x6BCE, 0x9689, 0x54E9, + 0x968A, 0x69D9, 0x968B, 0x5E55, 0x968C, 0x819C, 0x968D, 0x6795, 0x968E, 0x9BAA, 0x968F, 0x67FE, 0x9690, 0x9C52, 0x9691, 0x685D, + 0x9692, 0x4EA6, 0x9693, 0x4FE3, 0x9694, 0x53C8, 0x9695, 0x62B9, 0x9696, 0x672B, 0x9697, 0x6CAB, 0x9698, 0x8FC4, 0x9699, 0x4FAD, + 0x969A, 0x7E6D, 0x969B, 0x9EBF, 0x969C, 0x4E07, 0x969D, 0x6162, 0x969E, 0x6E80, 0x969F, 0x6F2B, 0x96A0, 0x8513, 0x96A1, 0x5473, + 0x96A2, 0x672A, 0x96A3, 0x9B45, 0x96A4, 0x5DF3, 0x96A5, 0x7B95, 0x96A6, 0x5CAC, 0x96A7, 0x5BC6, 0x96A8, 0x871C, 0x96A9, 0x6E4A, + 0x96AA, 0x84D1, 0x96AB, 0x7A14, 0x96AC, 0x8108, 0x96AD, 0x5999, 0x96AE, 0x7C8D, 0x96AF, 0x6C11, 0x96B0, 0x7720, 0x96B1, 0x52D9, + 0x96B2, 0x5922, 0x96B3, 0x7121, 0x96B4, 0x725F, 0x96B5, 0x77DB, 0x96B6, 0x9727, 0x96B7, 0x9D61, 0x96B8, 0x690B, 0x96B9, 0x5A7F, + 0x96BA, 0x5A18, 0x96BB, 0x51A5, 0x96BC, 0x540D, 0x96BD, 0x547D, 0x96BE, 0x660E, 0x96BF, 0x76DF, 0x96C0, 0x8FF7, 0x96C1, 0x9298, + 0x96C2, 0x9CF4, 0x96C3, 0x59EA, 0x96C4, 0x725D, 0x96C5, 0x6EC5, 0x96C6, 0x514D, 0x96C7, 0x68C9, 0x96C8, 0x7DBF, 0x96C9, 0x7DEC, + 0x96CA, 0x9762, 0x96CB, 0x9EBA, 0x96CC, 0x6478, 0x96CD, 0x6A21, 0x96CE, 0x8302, 0x96CF, 0x5984, 0x96D0, 0x5B5F, 0x96D1, 0x6BDB, + 0x96D2, 0x731B, 0x96D3, 0x76F2, 0x96D4, 0x7DB2, 0x96D5, 0x8017, 0x96D6, 0x8499, 0x96D7, 0x5132, 0x96D8, 0x6728, 0x96D9, 0x9ED9, + 0x96DA, 0x76EE, 0x96DB, 0x6762, 0x96DC, 0x52FF, 0x96DD, 0x9905, 0x96DE, 0x5C24, 0x96DF, 0x623B, 0x96E0, 0x7C7E, 0x96E1, 0x8CB0, + 0x96E2, 0x554F, 0x96E3, 0x60B6, 0x96E4, 0x7D0B, 0x96E5, 0x9580, 0x96E6, 0x5301, 0x96E7, 0x4E5F, 0x96E8, 0x51B6, 0x96E9, 0x591C, + 0x96EA, 0x723A, 0x96EB, 0x8036, 0x96EC, 0x91CE, 0x96ED, 0x5F25, 0x96EE, 0x77E2, 0x96EF, 0x5384, 0x96F0, 0x5F79, 0x96F1, 0x7D04, + 0x96F2, 0x85AC, 0x96F3, 0x8A33, 0x96F4, 0x8E8D, 0x96F5, 0x9756, 0x96F6, 0x67F3, 0x96F7, 0x85AE, 0x96F8, 0x9453, 0x96F9, 0x6109, + 0x96FA, 0x6108, 0x96FB, 0x6CB9, 0x96FC, 0x7652, 0x9740, 0x8AED, 0x9741, 0x8F38, 0x9742, 0x552F, 0x9743, 0x4F51, 0x9744, 0x512A, + 0x9745, 0x52C7, 0x9746, 0x53CB, 0x9747, 0x5BA5, 0x9748, 0x5E7D, 0x9749, 0x60A0, 0x974A, 0x6182, 0x974B, 0x63D6, 0x974C, 0x6709, + 0x974D, 0x67DA, 0x974E, 0x6E67, 0x974F, 0x6D8C, 0x9750, 0x7336, 0x9751, 0x7337, 0x9752, 0x7531, 0x9753, 0x7950, 0x9754, 0x88D5, + 0x9755, 0x8A98, 0x9756, 0x904A, 0x9757, 0x9091, 0x9758, 0x90F5, 0x9759, 0x96C4, 0x975A, 0x878D, 0x975B, 0x5915, 0x975C, 0x4E88, + 0x975D, 0x4F59, 0x975E, 0x4E0E, 0x975F, 0x8A89, 0x9760, 0x8F3F, 0x9761, 0x9810, 0x9762, 0x50AD, 0x9763, 0x5E7C, 0x9764, 0x5996, + 0x9765, 0x5BB9, 0x9766, 0x5EB8, 0x9767, 0x63DA, 0x9768, 0x63FA, 0x9769, 0x64C1, 0x976A, 0x66DC, 0x976B, 0x694A, 0x976C, 0x69D8, + 0x976D, 0x6D0B, 0x976E, 0x6EB6, 0x976F, 0x7194, 0x9770, 0x7528, 0x9771, 0x7AAF, 0x9772, 0x7F8A, 0x9773, 0x8000, 0x9774, 0x8449, + 0x9775, 0x84C9, 0x9776, 0x8981, 0x9777, 0x8B21, 0x9778, 0x8E0A, 0x9779, 0x9065, 0x977A, 0x967D, 0x977B, 0x990A, 0x977C, 0x617E, + 0x977D, 0x6291, 0x977E, 0x6B32, 0x9780, 0x6C83, 0x9781, 0x6D74, 0x9782, 0x7FCC, 0x9783, 0x7FFC, 0x9784, 0x6DC0, 0x9785, 0x7F85, + 0x9786, 0x87BA, 0x9787, 0x88F8, 0x9788, 0x6765, 0x9789, 0x83B1, 0x978A, 0x983C, 0x978B, 0x96F7, 0x978C, 0x6D1B, 0x978D, 0x7D61, + 0x978E, 0x843D, 0x978F, 0x916A, 0x9790, 0x4E71, 0x9791, 0x5375, 0x9792, 0x5D50, 0x9793, 0x6B04, 0x9794, 0x6FEB, 0x9795, 0x85CD, + 0x9796, 0x862D, 0x9797, 0x89A7, 0x9798, 0x5229, 0x9799, 0x540F, 0x979A, 0x5C65, 0x979B, 0x674E, 0x979C, 0x68A8, 0x979D, 0x7406, + 0x979E, 0x7483, 0x979F, 0x75E2, 0x97A0, 0x88CF, 0x97A1, 0x88E1, 0x97A2, 0x91CC, 0x97A3, 0x96E2, 0x97A4, 0x9678, 0x97A5, 0x5F8B, + 0x97A6, 0x7387, 0x97A7, 0x7ACB, 0x97A8, 0x844E, 0x97A9, 0x63A0, 0x97AA, 0x7565, 0x97AB, 0x5289, 0x97AC, 0x6D41, 0x97AD, 0x6E9C, + 0x97AE, 0x7409, 0x97AF, 0x7559, 0x97B0, 0x786B, 0x97B1, 0x7C92, 0x97B2, 0x9686, 0x97B3, 0x7ADC, 0x97B4, 0x9F8D, 0x97B5, 0x4FB6, + 0x97B6, 0x616E, 0x97B7, 0x65C5, 0x97B8, 0x865C, 0x97B9, 0x4E86, 0x97BA, 0x4EAE, 0x97BB, 0x50DA, 0x97BC, 0x4E21, 0x97BD, 0x51CC, + 0x97BE, 0x5BEE, 0x97BF, 0x6599, 0x97C0, 0x6881, 0x97C1, 0x6DBC, 0x97C2, 0x731F, 0x97C3, 0x7642, 0x97C4, 0x77AD, 0x97C5, 0x7A1C, + 0x97C6, 0x7CE7, 0x97C7, 0x826F, 0x97C8, 0x8AD2, 0x97C9, 0x907C, 0x97CA, 0x91CF, 0x97CB, 0x9675, 0x97CC, 0x9818, 0x97CD, 0x529B, + 0x97CE, 0x7DD1, 0x97CF, 0x502B, 0x97D0, 0x5398, 0x97D1, 0x6797, 0x97D2, 0x6DCB, 0x97D3, 0x71D0, 0x97D4, 0x7433, 0x97D5, 0x81E8, + 0x97D6, 0x8F2A, 0x97D7, 0x96A3, 0x97D8, 0x9C57, 0x97D9, 0x9E9F, 0x97DA, 0x7460, 0x97DB, 0x5841, 0x97DC, 0x6D99, 0x97DD, 0x7D2F, + 0x97DE, 0x985E, 0x97DF, 0x4EE4, 0x97E0, 0x4F36, 0x97E1, 0x4F8B, 0x97E2, 0x51B7, 0x97E3, 0x52B1, 0x97E4, 0x5DBA, 0x97E5, 0x601C, + 0x97E6, 0x73B2, 0x97E7, 0x793C, 0x97E8, 0x82D3, 0x97E9, 0x9234, 0x97EA, 0x96B7, 0x97EB, 0x96F6, 0x97EC, 0x970A, 0x97ED, 0x9E97, + 0x97EE, 0x9F62, 0x97EF, 0x66A6, 0x97F0, 0x6B74, 0x97F1, 0x5217, 0x97F2, 0x52A3, 0x97F3, 0x70C8, 0x97F4, 0x88C2, 0x97F5, 0x5EC9, + 0x97F6, 0x604B, 0x97F7, 0x6190, 0x97F8, 0x6F23, 0x97F9, 0x7149, 0x97FA, 0x7C3E, 0x97FB, 0x7DF4, 0x97FC, 0x806F, 0x9840, 0x84EE, + 0x9841, 0x9023, 0x9842, 0x932C, 0x9843, 0x5442, 0x9844, 0x9B6F, 0x9845, 0x6AD3, 0x9846, 0x7089, 0x9847, 0x8CC2, 0x9848, 0x8DEF, + 0x9849, 0x9732, 0x984A, 0x52B4, 0x984B, 0x5A41, 0x984C, 0x5ECA, 0x984D, 0x5F04, 0x984E, 0x6717, 0x984F, 0x697C, 0x9850, 0x6994, + 0x9851, 0x6D6A, 0x9852, 0x6F0F, 0x9853, 0x7262, 0x9854, 0x72FC, 0x9855, 0x7BED, 0x9856, 0x8001, 0x9857, 0x807E, 0x9858, 0x874B, + 0x9859, 0x90CE, 0x985A, 0x516D, 0x985B, 0x9E93, 0x985C, 0x7984, 0x985D, 0x808B, 0x985E, 0x9332, 0x985F, 0x8AD6, 0x9860, 0x502D, + 0x9861, 0x548C, 0x9862, 0x8A71, 0x9863, 0x6B6A, 0x9864, 0x8CC4, 0x9865, 0x8107, 0x9866, 0x60D1, 0x9867, 0x67A0, 0x9868, 0x9DF2, + 0x9869, 0x4E99, 0x986A, 0x4E98, 0x986B, 0x9C10, 0x986C, 0x8A6B, 0x986D, 0x85C1, 0x986E, 0x8568, 0x986F, 0x6900, 0x9870, 0x6E7E, + 0x9871, 0x7897, 0x9872, 0x8155, 0x989F, 0x5F0C, 0x98A0, 0x4E10, 0x98A1, 0x4E15, 0x98A2, 0x4E2A, 0x98A3, 0x4E31, 0x98A4, 0x4E36, + 0x98A5, 0x4E3C, 0x98A6, 0x4E3F, 0x98A7, 0x4E42, 0x98A8, 0x4E56, 0x98A9, 0x4E58, 0x98AA, 0x4E82, 0x98AB, 0x4E85, 0x98AC, 0x8C6B, + 0x98AD, 0x4E8A, 0x98AE, 0x8212, 0x98AF, 0x5F0D, 0x98B0, 0x4E8E, 0x98B1, 0x4E9E, 0x98B2, 0x4E9F, 0x98B3, 0x4EA0, 0x98B4, 0x4EA2, + 0x98B5, 0x4EB0, 0x98B6, 0x4EB3, 0x98B7, 0x4EB6, 0x98B8, 0x4ECE, 0x98B9, 0x4ECD, 0x98BA, 0x4EC4, 0x98BB, 0x4EC6, 0x98BC, 0x4EC2, + 0x98BD, 0x4ED7, 0x98BE, 0x4EDE, 0x98BF, 0x4EED, 0x98C0, 0x4EDF, 0x98C1, 0x4EF7, 0x98C2, 0x4F09, 0x98C3, 0x4F5A, 0x98C4, 0x4F30, + 0x98C5, 0x4F5B, 0x98C6, 0x4F5D, 0x98C7, 0x4F57, 0x98C8, 0x4F47, 0x98C9, 0x4F76, 0x98CA, 0x4F88, 0x98CB, 0x4F8F, 0x98CC, 0x4F98, + 0x98CD, 0x4F7B, 0x98CE, 0x4F69, 0x98CF, 0x4F70, 0x98D0, 0x4F91, 0x98D1, 0x4F6F, 0x98D2, 0x4F86, 0x98D3, 0x4F96, 0x98D4, 0x5118, + 0x98D5, 0x4FD4, 0x98D6, 0x4FDF, 0x98D7, 0x4FCE, 0x98D8, 0x4FD8, 0x98D9, 0x4FDB, 0x98DA, 0x4FD1, 0x98DB, 0x4FDA, 0x98DC, 0x4FD0, + 0x98DD, 0x4FE4, 0x98DE, 0x4FE5, 0x98DF, 0x501A, 0x98E0, 0x5028, 0x98E1, 0x5014, 0x98E2, 0x502A, 0x98E3, 0x5025, 0x98E4, 0x5005, + 0x98E5, 0x4F1C, 0x98E6, 0x4FF6, 0x98E7, 0x5021, 0x98E8, 0x5029, 0x98E9, 0x502C, 0x98EA, 0x4FFE, 0x98EB, 0x4FEF, 0x98EC, 0x5011, + 0x98ED, 0x5006, 0x98EE, 0x5043, 0x98EF, 0x5047, 0x98F0, 0x6703, 0x98F1, 0x5055, 0x98F2, 0x5050, 0x98F3, 0x5048, 0x98F4, 0x505A, + 0x98F5, 0x5056, 0x98F6, 0x506C, 0x98F7, 0x5078, 0x98F8, 0x5080, 0x98F9, 0x509A, 0x98FA, 0x5085, 0x98FB, 0x50B4, 0x98FC, 0x50B2, + 0x9940, 0x50C9, 0x9941, 0x50CA, 0x9942, 0x50B3, 0x9943, 0x50C2, 0x9944, 0x50D6, 0x9945, 0x50DE, 0x9946, 0x50E5, 0x9947, 0x50ED, + 0x9948, 0x50E3, 0x9949, 0x50EE, 0x994A, 0x50F9, 0x994B, 0x50F5, 0x994C, 0x5109, 0x994D, 0x5101, 0x994E, 0x5102, 0x994F, 0x5116, + 0x9950, 0x5115, 0x9951, 0x5114, 0x9952, 0x511A, 0x9953, 0x5121, 0x9954, 0x513A, 0x9955, 0x5137, 0x9956, 0x513C, 0x9957, 0x513B, + 0x9958, 0x513F, 0x9959, 0x5140, 0x995A, 0x5152, 0x995B, 0x514C, 0x995C, 0x5154, 0x995D, 0x5162, 0x995E, 0x7AF8, 0x995F, 0x5169, + 0x9960, 0x516A, 0x9961, 0x516E, 0x9962, 0x5180, 0x9963, 0x5182, 0x9964, 0x56D8, 0x9965, 0x518C, 0x9966, 0x5189, 0x9967, 0x518F, + 0x9968, 0x5191, 0x9969, 0x5193, 0x996A, 0x5195, 0x996B, 0x5196, 0x996C, 0x51A4, 0x996D, 0x51A6, 0x996E, 0x51A2, 0x996F, 0x51A9, + 0x9970, 0x51AA, 0x9971, 0x51AB, 0x9972, 0x51B3, 0x9973, 0x51B1, 0x9974, 0x51B2, 0x9975, 0x51B0, 0x9976, 0x51B5, 0x9977, 0x51BD, + 0x9978, 0x51C5, 0x9979, 0x51C9, 0x997A, 0x51DB, 0x997B, 0x51E0, 0x997C, 0x8655, 0x997D, 0x51E9, 0x997E, 0x51ED, 0x9980, 0x51F0, + 0x9981, 0x51F5, 0x9982, 0x51FE, 0x9983, 0x5204, 0x9984, 0x520B, 0x9985, 0x5214, 0x9986, 0x520E, 0x9987, 0x5227, 0x9988, 0x522A, + 0x9989, 0x522E, 0x998A, 0x5233, 0x998B, 0x5239, 0x998C, 0x524F, 0x998D, 0x5244, 0x998E, 0x524B, 0x998F, 0x524C, 0x9990, 0x525E, + 0x9991, 0x5254, 0x9992, 0x526A, 0x9993, 0x5274, 0x9994, 0x5269, 0x9995, 0x5273, 0x9996, 0x527F, 0x9997, 0x527D, 0x9998, 0x528D, + 0x9999, 0x5294, 0x999A, 0x5292, 0x999B, 0x5271, 0x999C, 0x5288, 0x999D, 0x5291, 0x999E, 0x8FA8, 0x999F, 0x8FA7, 0x99A0, 0x52AC, + 0x99A1, 0x52AD, 0x99A2, 0x52BC, 0x99A3, 0x52B5, 0x99A4, 0x52C1, 0x99A5, 0x52CD, 0x99A6, 0x52D7, 0x99A7, 0x52DE, 0x99A8, 0x52E3, + 0x99A9, 0x52E6, 0x99AA, 0x98ED, 0x99AB, 0x52E0, 0x99AC, 0x52F3, 0x99AD, 0x52F5, 0x99AE, 0x52F8, 0x99AF, 0x52F9, 0x99B0, 0x5306, + 0x99B1, 0x5308, 0x99B2, 0x7538, 0x99B3, 0x530D, 0x99B4, 0x5310, 0x99B5, 0x530F, 0x99B6, 0x5315, 0x99B7, 0x531A, 0x99B8, 0x5323, + 0x99B9, 0x532F, 0x99BA, 0x5331, 0x99BB, 0x5333, 0x99BC, 0x5338, 0x99BD, 0x5340, 0x99BE, 0x5346, 0x99BF, 0x5345, 0x99C0, 0x4E17, + 0x99C1, 0x5349, 0x99C2, 0x534D, 0x99C3, 0x51D6, 0x99C4, 0x535E, 0x99C5, 0x5369, 0x99C6, 0x536E, 0x99C7, 0x5918, 0x99C8, 0x537B, + 0x99C9, 0x5377, 0x99CA, 0x5382, 0x99CB, 0x5396, 0x99CC, 0x53A0, 0x99CD, 0x53A6, 0x99CE, 0x53A5, 0x99CF, 0x53AE, 0x99D0, 0x53B0, + 0x99D1, 0x53B6, 0x99D2, 0x53C3, 0x99D3, 0x7C12, 0x99D4, 0x96D9, 0x99D5, 0x53DF, 0x99D6, 0x66FC, 0x99D7, 0x71EE, 0x99D8, 0x53EE, + 0x99D9, 0x53E8, 0x99DA, 0x53ED, 0x99DB, 0x53FA, 0x99DC, 0x5401, 0x99DD, 0x543D, 0x99DE, 0x5440, 0x99DF, 0x542C, 0x99E0, 0x542D, + 0x99E1, 0x543C, 0x99E2, 0x542E, 0x99E3, 0x5436, 0x99E4, 0x5429, 0x99E5, 0x541D, 0x99E6, 0x544E, 0x99E7, 0x548F, 0x99E8, 0x5475, + 0x99E9, 0x548E, 0x99EA, 0x545F, 0x99EB, 0x5471, 0x99EC, 0x5477, 0x99ED, 0x5470, 0x99EE, 0x5492, 0x99EF, 0x547B, 0x99F0, 0x5480, + 0x99F1, 0x5476, 0x99F2, 0x5484, 0x99F3, 0x5490, 0x99F4, 0x5486, 0x99F5, 0x54C7, 0x99F6, 0x54A2, 0x99F7, 0x54B8, 0x99F8, 0x54A5, + 0x99F9, 0x54AC, 0x99FA, 0x54C4, 0x99FB, 0x54C8, 0x99FC, 0x54A8, 0x9A40, 0x54AB, 0x9A41, 0x54C2, 0x9A42, 0x54A4, 0x9A43, 0x54BE, + 0x9A44, 0x54BC, 0x9A45, 0x54D8, 0x9A46, 0x54E5, 0x9A47, 0x54E6, 0x9A48, 0x550F, 0x9A49, 0x5514, 0x9A4A, 0x54FD, 0x9A4B, 0x54EE, + 0x9A4C, 0x54ED, 0x9A4D, 0x54FA, 0x9A4E, 0x54E2, 0x9A4F, 0x5539, 0x9A50, 0x5540, 0x9A51, 0x5563, 0x9A52, 0x554C, 0x9A53, 0x552E, + 0x9A54, 0x555C, 0x9A55, 0x5545, 0x9A56, 0x5556, 0x9A57, 0x5557, 0x9A58, 0x5538, 0x9A59, 0x5533, 0x9A5A, 0x555D, 0x9A5B, 0x5599, + 0x9A5C, 0x5580, 0x9A5D, 0x54AF, 0x9A5E, 0x558A, 0x9A5F, 0x559F, 0x9A60, 0x557B, 0x9A61, 0x557E, 0x9A62, 0x5598, 0x9A63, 0x559E, + 0x9A64, 0x55AE, 0x9A65, 0x557C, 0x9A66, 0x5583, 0x9A67, 0x55A9, 0x9A68, 0x5587, 0x9A69, 0x55A8, 0x9A6A, 0x55DA, 0x9A6B, 0x55C5, + 0x9A6C, 0x55DF, 0x9A6D, 0x55C4, 0x9A6E, 0x55DC, 0x9A6F, 0x55E4, 0x9A70, 0x55D4, 0x9A71, 0x5614, 0x9A72, 0x55F7, 0x9A73, 0x5616, + 0x9A74, 0x55FE, 0x9A75, 0x55FD, 0x9A76, 0x561B, 0x9A77, 0x55F9, 0x9A78, 0x564E, 0x9A79, 0x5650, 0x9A7A, 0x71DF, 0x9A7B, 0x5634, + 0x9A7C, 0x5636, 0x9A7D, 0x5632, 0x9A7E, 0x5638, 0x9A80, 0x566B, 0x9A81, 0x5664, 0x9A82, 0x562F, 0x9A83, 0x566C, 0x9A84, 0x566A, + 0x9A85, 0x5686, 0x9A86, 0x5680, 0x9A87, 0x568A, 0x9A88, 0x56A0, 0x9A89, 0x5694, 0x9A8A, 0x568F, 0x9A8B, 0x56A5, 0x9A8C, 0x56AE, + 0x9A8D, 0x56B6, 0x9A8E, 0x56B4, 0x9A8F, 0x56C2, 0x9A90, 0x56BC, 0x9A91, 0x56C1, 0x9A92, 0x56C3, 0x9A93, 0x56C0, 0x9A94, 0x56C8, + 0x9A95, 0x56CE, 0x9A96, 0x56D1, 0x9A97, 0x56D3, 0x9A98, 0x56D7, 0x9A99, 0x56EE, 0x9A9A, 0x56F9, 0x9A9B, 0x5700, 0x9A9C, 0x56FF, + 0x9A9D, 0x5704, 0x9A9E, 0x5709, 0x9A9F, 0x5708, 0x9AA0, 0x570B, 0x9AA1, 0x570D, 0x9AA2, 0x5713, 0x9AA3, 0x5718, 0x9AA4, 0x5716, + 0x9AA5, 0x55C7, 0x9AA6, 0x571C, 0x9AA7, 0x5726, 0x9AA8, 0x5737, 0x9AA9, 0x5738, 0x9AAA, 0x574E, 0x9AAB, 0x573B, 0x9AAC, 0x5740, + 0x9AAD, 0x574F, 0x9AAE, 0x5769, 0x9AAF, 0x57C0, 0x9AB0, 0x5788, 0x9AB1, 0x5761, 0x9AB2, 0x577F, 0x9AB3, 0x5789, 0x9AB4, 0x5793, + 0x9AB5, 0x57A0, 0x9AB6, 0x57B3, 0x9AB7, 0x57A4, 0x9AB8, 0x57AA, 0x9AB9, 0x57B0, 0x9ABA, 0x57C3, 0x9ABB, 0x57C6, 0x9ABC, 0x57D4, + 0x9ABD, 0x57D2, 0x9ABE, 0x57D3, 0x9ABF, 0x580A, 0x9AC0, 0x57D6, 0x9AC1, 0x57E3, 0x9AC2, 0x580B, 0x9AC3, 0x5819, 0x9AC4, 0x581D, + 0x9AC5, 0x5872, 0x9AC6, 0x5821, 0x9AC7, 0x5862, 0x9AC8, 0x584B, 0x9AC9, 0x5870, 0x9ACA, 0x6BC0, 0x9ACB, 0x5852, 0x9ACC, 0x583D, + 0x9ACD, 0x5879, 0x9ACE, 0x5885, 0x9ACF, 0x58B9, 0x9AD0, 0x589F, 0x9AD1, 0x58AB, 0x9AD2, 0x58BA, 0x9AD3, 0x58DE, 0x9AD4, 0x58BB, + 0x9AD5, 0x58B8, 0x9AD6, 0x58AE, 0x9AD7, 0x58C5, 0x9AD8, 0x58D3, 0x9AD9, 0x58D1, 0x9ADA, 0x58D7, 0x9ADB, 0x58D9, 0x9ADC, 0x58D8, + 0x9ADD, 0x58E5, 0x9ADE, 0x58DC, 0x9ADF, 0x58E4, 0x9AE0, 0x58DF, 0x9AE1, 0x58EF, 0x9AE2, 0x58FA, 0x9AE3, 0x58F9, 0x9AE4, 0x58FB, + 0x9AE5, 0x58FC, 0x9AE6, 0x58FD, 0x9AE7, 0x5902, 0x9AE8, 0x590A, 0x9AE9, 0x5910, 0x9AEA, 0x591B, 0x9AEB, 0x68A6, 0x9AEC, 0x5925, + 0x9AED, 0x592C, 0x9AEE, 0x592D, 0x9AEF, 0x5932, 0x9AF0, 0x5938, 0x9AF1, 0x593E, 0x9AF2, 0x7AD2, 0x9AF3, 0x5955, 0x9AF4, 0x5950, + 0x9AF5, 0x594E, 0x9AF6, 0x595A, 0x9AF7, 0x5958, 0x9AF8, 0x5962, 0x9AF9, 0x5960, 0x9AFA, 0x5967, 0x9AFB, 0x596C, 0x9AFC, 0x5969, + 0x9B40, 0x5978, 0x9B41, 0x5981, 0x9B42, 0x599D, 0x9B43, 0x4F5E, 0x9B44, 0x4FAB, 0x9B45, 0x59A3, 0x9B46, 0x59B2, 0x9B47, 0x59C6, + 0x9B48, 0x59E8, 0x9B49, 0x59DC, 0x9B4A, 0x598D, 0x9B4B, 0x59D9, 0x9B4C, 0x59DA, 0x9B4D, 0x5A25, 0x9B4E, 0x5A1F, 0x9B4F, 0x5A11, + 0x9B50, 0x5A1C, 0x9B51, 0x5A09, 0x9B52, 0x5A1A, 0x9B53, 0x5A40, 0x9B54, 0x5A6C, 0x9B55, 0x5A49, 0x9B56, 0x5A35, 0x9B57, 0x5A36, + 0x9B58, 0x5A62, 0x9B59, 0x5A6A, 0x9B5A, 0x5A9A, 0x9B5B, 0x5ABC, 0x9B5C, 0x5ABE, 0x9B5D, 0x5ACB, 0x9B5E, 0x5AC2, 0x9B5F, 0x5ABD, + 0x9B60, 0x5AE3, 0x9B61, 0x5AD7, 0x9B62, 0x5AE6, 0x9B63, 0x5AE9, 0x9B64, 0x5AD6, 0x9B65, 0x5AFA, 0x9B66, 0x5AFB, 0x9B67, 0x5B0C, + 0x9B68, 0x5B0B, 0x9B69, 0x5B16, 0x9B6A, 0x5B32, 0x9B6B, 0x5AD0, 0x9B6C, 0x5B2A, 0x9B6D, 0x5B36, 0x9B6E, 0x5B3E, 0x9B6F, 0x5B43, + 0x9B70, 0x5B45, 0x9B71, 0x5B40, 0x9B72, 0x5B51, 0x9B73, 0x5B55, 0x9B74, 0x5B5A, 0x9B75, 0x5B5B, 0x9B76, 0x5B65, 0x9B77, 0x5B69, + 0x9B78, 0x5B70, 0x9B79, 0x5B73, 0x9B7A, 0x5B75, 0x9B7B, 0x5B78, 0x9B7C, 0x6588, 0x9B7D, 0x5B7A, 0x9B7E, 0x5B80, 0x9B80, 0x5B83, + 0x9B81, 0x5BA6, 0x9B82, 0x5BB8, 0x9B83, 0x5BC3, 0x9B84, 0x5BC7, 0x9B85, 0x5BC9, 0x9B86, 0x5BD4, 0x9B87, 0x5BD0, 0x9B88, 0x5BE4, + 0x9B89, 0x5BE6, 0x9B8A, 0x5BE2, 0x9B8B, 0x5BDE, 0x9B8C, 0x5BE5, 0x9B8D, 0x5BEB, 0x9B8E, 0x5BF0, 0x9B8F, 0x5BF6, 0x9B90, 0x5BF3, + 0x9B91, 0x5C05, 0x9B92, 0x5C07, 0x9B93, 0x5C08, 0x9B94, 0x5C0D, 0x9B95, 0x5C13, 0x9B96, 0x5C20, 0x9B97, 0x5C22, 0x9B98, 0x5C28, + 0x9B99, 0x5C38, 0x9B9A, 0x5C39, 0x9B9B, 0x5C41, 0x9B9C, 0x5C46, 0x9B9D, 0x5C4E, 0x9B9E, 0x5C53, 0x9B9F, 0x5C50, 0x9BA0, 0x5C4F, + 0x9BA1, 0x5B71, 0x9BA2, 0x5C6C, 0x9BA3, 0x5C6E, 0x9BA4, 0x4E62, 0x9BA5, 0x5C76, 0x9BA6, 0x5C79, 0x9BA7, 0x5C8C, 0x9BA8, 0x5C91, + 0x9BA9, 0x5C94, 0x9BAA, 0x599B, 0x9BAB, 0x5CAB, 0x9BAC, 0x5CBB, 0x9BAD, 0x5CB6, 0x9BAE, 0x5CBC, 0x9BAF, 0x5CB7, 0x9BB0, 0x5CC5, + 0x9BB1, 0x5CBE, 0x9BB2, 0x5CC7, 0x9BB3, 0x5CD9, 0x9BB4, 0x5CE9, 0x9BB5, 0x5CFD, 0x9BB6, 0x5CFA, 0x9BB7, 0x5CED, 0x9BB8, 0x5D8C, + 0x9BB9, 0x5CEA, 0x9BBA, 0x5D0B, 0x9BBB, 0x5D15, 0x9BBC, 0x5D17, 0x9BBD, 0x5D5C, 0x9BBE, 0x5D1F, 0x9BBF, 0x5D1B, 0x9BC0, 0x5D11, + 0x9BC1, 0x5D14, 0x9BC2, 0x5D22, 0x9BC3, 0x5D1A, 0x9BC4, 0x5D19, 0x9BC5, 0x5D18, 0x9BC6, 0x5D4C, 0x9BC7, 0x5D52, 0x9BC8, 0x5D4E, + 0x9BC9, 0x5D4B, 0x9BCA, 0x5D6C, 0x9BCB, 0x5D73, 0x9BCC, 0x5D76, 0x9BCD, 0x5D87, 0x9BCE, 0x5D84, 0x9BCF, 0x5D82, 0x9BD0, 0x5DA2, + 0x9BD1, 0x5D9D, 0x9BD2, 0x5DAC, 0x9BD3, 0x5DAE, 0x9BD4, 0x5DBD, 0x9BD5, 0x5D90, 0x9BD6, 0x5DB7, 0x9BD7, 0x5DBC, 0x9BD8, 0x5DC9, + 0x9BD9, 0x5DCD, 0x9BDA, 0x5DD3, 0x9BDB, 0x5DD2, 0x9BDC, 0x5DD6, 0x9BDD, 0x5DDB, 0x9BDE, 0x5DEB, 0x9BDF, 0x5DF2, 0x9BE0, 0x5DF5, + 0x9BE1, 0x5E0B, 0x9BE2, 0x5E1A, 0x9BE3, 0x5E19, 0x9BE4, 0x5E11, 0x9BE5, 0x5E1B, 0x9BE6, 0x5E36, 0x9BE7, 0x5E37, 0x9BE8, 0x5E44, + 0x9BE9, 0x5E43, 0x9BEA, 0x5E40, 0x9BEB, 0x5E4E, 0x9BEC, 0x5E57, 0x9BED, 0x5E54, 0x9BEE, 0x5E5F, 0x9BEF, 0x5E62, 0x9BF0, 0x5E64, + 0x9BF1, 0x5E47, 0x9BF2, 0x5E75, 0x9BF3, 0x5E76, 0x9BF4, 0x5E7A, 0x9BF5, 0x9EBC, 0x9BF6, 0x5E7F, 0x9BF7, 0x5EA0, 0x9BF8, 0x5EC1, + 0x9BF9, 0x5EC2, 0x9BFA, 0x5EC8, 0x9BFB, 0x5ED0, 0x9BFC, 0x5ECF, 0x9C40, 0x5ED6, 0x9C41, 0x5EE3, 0x9C42, 0x5EDD, 0x9C43, 0x5EDA, + 0x9C44, 0x5EDB, 0x9C45, 0x5EE2, 0x9C46, 0x5EE1, 0x9C47, 0x5EE8, 0x9C48, 0x5EE9, 0x9C49, 0x5EEC, 0x9C4A, 0x5EF1, 0x9C4B, 0x5EF3, + 0x9C4C, 0x5EF0, 0x9C4D, 0x5EF4, 0x9C4E, 0x5EF8, 0x9C4F, 0x5EFE, 0x9C50, 0x5F03, 0x9C51, 0x5F09, 0x9C52, 0x5F5D, 0x9C53, 0x5F5C, + 0x9C54, 0x5F0B, 0x9C55, 0x5F11, 0x9C56, 0x5F16, 0x9C57, 0x5F29, 0x9C58, 0x5F2D, 0x9C59, 0x5F38, 0x9C5A, 0x5F41, 0x9C5B, 0x5F48, + 0x9C5C, 0x5F4C, 0x9C5D, 0x5F4E, 0x9C5E, 0x5F2F, 0x9C5F, 0x5F51, 0x9C60, 0x5F56, 0x9C61, 0x5F57, 0x9C62, 0x5F59, 0x9C63, 0x5F61, + 0x9C64, 0x5F6D, 0x9C65, 0x5F73, 0x9C66, 0x5F77, 0x9C67, 0x5F83, 0x9C68, 0x5F82, 0x9C69, 0x5F7F, 0x9C6A, 0x5F8A, 0x9C6B, 0x5F88, + 0x9C6C, 0x5F91, 0x9C6D, 0x5F87, 0x9C6E, 0x5F9E, 0x9C6F, 0x5F99, 0x9C70, 0x5F98, 0x9C71, 0x5FA0, 0x9C72, 0x5FA8, 0x9C73, 0x5FAD, + 0x9C74, 0x5FBC, 0x9C75, 0x5FD6, 0x9C76, 0x5FFB, 0x9C77, 0x5FE4, 0x9C78, 0x5FF8, 0x9C79, 0x5FF1, 0x9C7A, 0x5FDD, 0x9C7B, 0x60B3, + 0x9C7C, 0x5FFF, 0x9C7D, 0x6021, 0x9C7E, 0x6060, 0x9C80, 0x6019, 0x9C81, 0x6010, 0x9C82, 0x6029, 0x9C83, 0x600E, 0x9C84, 0x6031, + 0x9C85, 0x601B, 0x9C86, 0x6015, 0x9C87, 0x602B, 0x9C88, 0x6026, 0x9C89, 0x600F, 0x9C8A, 0x603A, 0x9C8B, 0x605A, 0x9C8C, 0x6041, + 0x9C8D, 0x606A, 0x9C8E, 0x6077, 0x9C8F, 0x605F, 0x9C90, 0x604A, 0x9C91, 0x6046, 0x9C92, 0x604D, 0x9C93, 0x6063, 0x9C94, 0x6043, + 0x9C95, 0x6064, 0x9C96, 0x6042, 0x9C97, 0x606C, 0x9C98, 0x606B, 0x9C99, 0x6059, 0x9C9A, 0x6081, 0x9C9B, 0x608D, 0x9C9C, 0x60E7, + 0x9C9D, 0x6083, 0x9C9E, 0x609A, 0x9C9F, 0x6084, 0x9CA0, 0x609B, 0x9CA1, 0x6096, 0x9CA2, 0x6097, 0x9CA3, 0x6092, 0x9CA4, 0x60A7, + 0x9CA5, 0x608B, 0x9CA6, 0x60E1, 0x9CA7, 0x60B8, 0x9CA8, 0x60E0, 0x9CA9, 0x60D3, 0x9CAA, 0x60B4, 0x9CAB, 0x5FF0, 0x9CAC, 0x60BD, + 0x9CAD, 0x60C6, 0x9CAE, 0x60B5, 0x9CAF, 0x60D8, 0x9CB0, 0x614D, 0x9CB1, 0x6115, 0x9CB2, 0x6106, 0x9CB3, 0x60F6, 0x9CB4, 0x60F7, + 0x9CB5, 0x6100, 0x9CB6, 0x60F4, 0x9CB7, 0x60FA, 0x9CB8, 0x6103, 0x9CB9, 0x6121, 0x9CBA, 0x60FB, 0x9CBB, 0x60F1, 0x9CBC, 0x610D, + 0x9CBD, 0x610E, 0x9CBE, 0x6147, 0x9CBF, 0x613E, 0x9CC0, 0x6128, 0x9CC1, 0x6127, 0x9CC2, 0x614A, 0x9CC3, 0x613F, 0x9CC4, 0x613C, + 0x9CC5, 0x612C, 0x9CC6, 0x6134, 0x9CC7, 0x613D, 0x9CC8, 0x6142, 0x9CC9, 0x6144, 0x9CCA, 0x6173, 0x9CCB, 0x6177, 0x9CCC, 0x6158, + 0x9CCD, 0x6159, 0x9CCE, 0x615A, 0x9CCF, 0x616B, 0x9CD0, 0x6174, 0x9CD1, 0x616F, 0x9CD2, 0x6165, 0x9CD3, 0x6171, 0x9CD4, 0x615F, + 0x9CD5, 0x615D, 0x9CD6, 0x6153, 0x9CD7, 0x6175, 0x9CD8, 0x6199, 0x9CD9, 0x6196, 0x9CDA, 0x6187, 0x9CDB, 0x61AC, 0x9CDC, 0x6194, + 0x9CDD, 0x619A, 0x9CDE, 0x618A, 0x9CDF, 0x6191, 0x9CE0, 0x61AB, 0x9CE1, 0x61AE, 0x9CE2, 0x61CC, 0x9CE3, 0x61CA, 0x9CE4, 0x61C9, + 0x9CE5, 0x61F7, 0x9CE6, 0x61C8, 0x9CE7, 0x61C3, 0x9CE8, 0x61C6, 0x9CE9, 0x61BA, 0x9CEA, 0x61CB, 0x9CEB, 0x7F79, 0x9CEC, 0x61CD, + 0x9CED, 0x61E6, 0x9CEE, 0x61E3, 0x9CEF, 0x61F6, 0x9CF0, 0x61FA, 0x9CF1, 0x61F4, 0x9CF2, 0x61FF, 0x9CF3, 0x61FD, 0x9CF4, 0x61FC, + 0x9CF5, 0x61FE, 0x9CF6, 0x6200, 0x9CF7, 0x6208, 0x9CF8, 0x6209, 0x9CF9, 0x620D, 0x9CFA, 0x620C, 0x9CFB, 0x6214, 0x9CFC, 0x621B, + 0x9D40, 0x621E, 0x9D41, 0x6221, 0x9D42, 0x622A, 0x9D43, 0x622E, 0x9D44, 0x6230, 0x9D45, 0x6232, 0x9D46, 0x6233, 0x9D47, 0x6241, + 0x9D48, 0x624E, 0x9D49, 0x625E, 0x9D4A, 0x6263, 0x9D4B, 0x625B, 0x9D4C, 0x6260, 0x9D4D, 0x6268, 0x9D4E, 0x627C, 0x9D4F, 0x6282, + 0x9D50, 0x6289, 0x9D51, 0x627E, 0x9D52, 0x6292, 0x9D53, 0x6293, 0x9D54, 0x6296, 0x9D55, 0x62D4, 0x9D56, 0x6283, 0x9D57, 0x6294, + 0x9D58, 0x62D7, 0x9D59, 0x62D1, 0x9D5A, 0x62BB, 0x9D5B, 0x62CF, 0x9D5C, 0x62FF, 0x9D5D, 0x62C6, 0x9D5E, 0x64D4, 0x9D5F, 0x62C8, + 0x9D60, 0x62DC, 0x9D61, 0x62CC, 0x9D62, 0x62CA, 0x9D63, 0x62C2, 0x9D64, 0x62C7, 0x9D65, 0x629B, 0x9D66, 0x62C9, 0x9D67, 0x630C, + 0x9D68, 0x62EE, 0x9D69, 0x62F1, 0x9D6A, 0x6327, 0x9D6B, 0x6302, 0x9D6C, 0x6308, 0x9D6D, 0x62EF, 0x9D6E, 0x62F5, 0x9D6F, 0x6350, + 0x9D70, 0x633E, 0x9D71, 0x634D, 0x9D72, 0x641C, 0x9D73, 0x634F, 0x9D74, 0x6396, 0x9D75, 0x638E, 0x9D76, 0x6380, 0x9D77, 0x63AB, + 0x9D78, 0x6376, 0x9D79, 0x63A3, 0x9D7A, 0x638F, 0x9D7B, 0x6389, 0x9D7C, 0x639F, 0x9D7D, 0x63B5, 0x9D7E, 0x636B, 0x9D80, 0x6369, + 0x9D81, 0x63BE, 0x9D82, 0x63E9, 0x9D83, 0x63C0, 0x9D84, 0x63C6, 0x9D85, 0x63E3, 0x9D86, 0x63C9, 0x9D87, 0x63D2, 0x9D88, 0x63F6, + 0x9D89, 0x63C4, 0x9D8A, 0x6416, 0x9D8B, 0x6434, 0x9D8C, 0x6406, 0x9D8D, 0x6413, 0x9D8E, 0x6426, 0x9D8F, 0x6436, 0x9D90, 0x651D, + 0x9D91, 0x6417, 0x9D92, 0x6428, 0x9D93, 0x640F, 0x9D94, 0x6467, 0x9D95, 0x646F, 0x9D96, 0x6476, 0x9D97, 0x644E, 0x9D98, 0x652A, + 0x9D99, 0x6495, 0x9D9A, 0x6493, 0x9D9B, 0x64A5, 0x9D9C, 0x64A9, 0x9D9D, 0x6488, 0x9D9E, 0x64BC, 0x9D9F, 0x64DA, 0x9DA0, 0x64D2, + 0x9DA1, 0x64C5, 0x9DA2, 0x64C7, 0x9DA3, 0x64BB, 0x9DA4, 0x64D8, 0x9DA5, 0x64C2, 0x9DA6, 0x64F1, 0x9DA7, 0x64E7, 0x9DA8, 0x8209, + 0x9DA9, 0x64E0, 0x9DAA, 0x64E1, 0x9DAB, 0x62AC, 0x9DAC, 0x64E3, 0x9DAD, 0x64EF, 0x9DAE, 0x652C, 0x9DAF, 0x64F6, 0x9DB0, 0x64F4, + 0x9DB1, 0x64F2, 0x9DB2, 0x64FA, 0x9DB3, 0x6500, 0x9DB4, 0x64FD, 0x9DB5, 0x6518, 0x9DB6, 0x651C, 0x9DB7, 0x6505, 0x9DB8, 0x6524, + 0x9DB9, 0x6523, 0x9DBA, 0x652B, 0x9DBB, 0x6534, 0x9DBC, 0x6535, 0x9DBD, 0x6537, 0x9DBE, 0x6536, 0x9DBF, 0x6538, 0x9DC0, 0x754B, + 0x9DC1, 0x6548, 0x9DC2, 0x6556, 0x9DC3, 0x6555, 0x9DC4, 0x654D, 0x9DC5, 0x6558, 0x9DC6, 0x655E, 0x9DC7, 0x655D, 0x9DC8, 0x6572, + 0x9DC9, 0x6578, 0x9DCA, 0x6582, 0x9DCB, 0x6583, 0x9DCC, 0x8B8A, 0x9DCD, 0x659B, 0x9DCE, 0x659F, 0x9DCF, 0x65AB, 0x9DD0, 0x65B7, + 0x9DD1, 0x65C3, 0x9DD2, 0x65C6, 0x9DD3, 0x65C1, 0x9DD4, 0x65C4, 0x9DD5, 0x65CC, 0x9DD6, 0x65D2, 0x9DD7, 0x65DB, 0x9DD8, 0x65D9, + 0x9DD9, 0x65E0, 0x9DDA, 0x65E1, 0x9DDB, 0x65F1, 0x9DDC, 0x6772, 0x9DDD, 0x660A, 0x9DDE, 0x6603, 0x9DDF, 0x65FB, 0x9DE0, 0x6773, + 0x9DE1, 0x6635, 0x9DE2, 0x6636, 0x9DE3, 0x6634, 0x9DE4, 0x661C, 0x9DE5, 0x664F, 0x9DE6, 0x6644, 0x9DE7, 0x6649, 0x9DE8, 0x6641, + 0x9DE9, 0x665E, 0x9DEA, 0x665D, 0x9DEB, 0x6664, 0x9DEC, 0x6667, 0x9DED, 0x6668, 0x9DEE, 0x665F, 0x9DEF, 0x6662, 0x9DF0, 0x6670, + 0x9DF1, 0x6683, 0x9DF2, 0x6688, 0x9DF3, 0x668E, 0x9DF4, 0x6689, 0x9DF5, 0x6684, 0x9DF6, 0x6698, 0x9DF7, 0x669D, 0x9DF8, 0x66C1, + 0x9DF9, 0x66B9, 0x9DFA, 0x66C9, 0x9DFB, 0x66BE, 0x9DFC, 0x66BC, 0x9E40, 0x66C4, 0x9E41, 0x66B8, 0x9E42, 0x66D6, 0x9E43, 0x66DA, + 0x9E44, 0x66E0, 0x9E45, 0x663F, 0x9E46, 0x66E6, 0x9E47, 0x66E9, 0x9E48, 0x66F0, 0x9E49, 0x66F5, 0x9E4A, 0x66F7, 0x9E4B, 0x670F, + 0x9E4C, 0x6716, 0x9E4D, 0x671E, 0x9E4E, 0x6726, 0x9E4F, 0x6727, 0x9E50, 0x9738, 0x9E51, 0x672E, 0x9E52, 0x673F, 0x9E53, 0x6736, + 0x9E54, 0x6741, 0x9E55, 0x6738, 0x9E56, 0x6737, 0x9E57, 0x6746, 0x9E58, 0x675E, 0x9E59, 0x6760, 0x9E5A, 0x6759, 0x9E5B, 0x6763, + 0x9E5C, 0x6764, 0x9E5D, 0x6789, 0x9E5E, 0x6770, 0x9E5F, 0x67A9, 0x9E60, 0x677C, 0x9E61, 0x676A, 0x9E62, 0x678C, 0x9E63, 0x678B, + 0x9E64, 0x67A6, 0x9E65, 0x67A1, 0x9E66, 0x6785, 0x9E67, 0x67B7, 0x9E68, 0x67EF, 0x9E69, 0x67B4, 0x9E6A, 0x67EC, 0x9E6B, 0x67B3, + 0x9E6C, 0x67E9, 0x9E6D, 0x67B8, 0x9E6E, 0x67E4, 0x9E6F, 0x67DE, 0x9E70, 0x67DD, 0x9E71, 0x67E2, 0x9E72, 0x67EE, 0x9E73, 0x67B9, + 0x9E74, 0x67CE, 0x9E75, 0x67C6, 0x9E76, 0x67E7, 0x9E77, 0x6A9C, 0x9E78, 0x681E, 0x9E79, 0x6846, 0x9E7A, 0x6829, 0x9E7B, 0x6840, + 0x9E7C, 0x684D, 0x9E7D, 0x6832, 0x9E7E, 0x684E, 0x9E80, 0x68B3, 0x9E81, 0x682B, 0x9E82, 0x6859, 0x9E83, 0x6863, 0x9E84, 0x6877, + 0x9E85, 0x687F, 0x9E86, 0x689F, 0x9E87, 0x688F, 0x9E88, 0x68AD, 0x9E89, 0x6894, 0x9E8A, 0x689D, 0x9E8B, 0x689B, 0x9E8C, 0x6883, + 0x9E8D, 0x6AAE, 0x9E8E, 0x68B9, 0x9E8F, 0x6874, 0x9E90, 0x68B5, 0x9E91, 0x68A0, 0x9E92, 0x68BA, 0x9E93, 0x690F, 0x9E94, 0x688D, + 0x9E95, 0x687E, 0x9E96, 0x6901, 0x9E97, 0x68CA, 0x9E98, 0x6908, 0x9E99, 0x68D8, 0x9E9A, 0x6922, 0x9E9B, 0x6926, 0x9E9C, 0x68E1, + 0x9E9D, 0x690C, 0x9E9E, 0x68CD, 0x9E9F, 0x68D4, 0x9EA0, 0x68E7, 0x9EA1, 0x68D5, 0x9EA2, 0x6936, 0x9EA3, 0x6912, 0x9EA4, 0x6904, + 0x9EA5, 0x68D7, 0x9EA6, 0x68E3, 0x9EA7, 0x6925, 0x9EA8, 0x68F9, 0x9EA9, 0x68E0, 0x9EAA, 0x68EF, 0x9EAB, 0x6928, 0x9EAC, 0x692A, + 0x9EAD, 0x691A, 0x9EAE, 0x6923, 0x9EAF, 0x6921, 0x9EB0, 0x68C6, 0x9EB1, 0x6979, 0x9EB2, 0x6977, 0x9EB3, 0x695C, 0x9EB4, 0x6978, + 0x9EB5, 0x696B, 0x9EB6, 0x6954, 0x9EB7, 0x697E, 0x9EB8, 0x696E, 0x9EB9, 0x6939, 0x9EBA, 0x6974, 0x9EBB, 0x693D, 0x9EBC, 0x6959, + 0x9EBD, 0x6930, 0x9EBE, 0x6961, 0x9EBF, 0x695E, 0x9EC0, 0x695D, 0x9EC1, 0x6981, 0x9EC2, 0x696A, 0x9EC3, 0x69B2, 0x9EC4, 0x69AE, + 0x9EC5, 0x69D0, 0x9EC6, 0x69BF, 0x9EC7, 0x69C1, 0x9EC8, 0x69D3, 0x9EC9, 0x69BE, 0x9ECA, 0x69CE, 0x9ECB, 0x5BE8, 0x9ECC, 0x69CA, + 0x9ECD, 0x69DD, 0x9ECE, 0x69BB, 0x9ECF, 0x69C3, 0x9ED0, 0x69A7, 0x9ED1, 0x6A2E, 0x9ED2, 0x6991, 0x9ED3, 0x69A0, 0x9ED4, 0x699C, + 0x9ED5, 0x6995, 0x9ED6, 0x69B4, 0x9ED7, 0x69DE, 0x9ED8, 0x69E8, 0x9ED9, 0x6A02, 0x9EDA, 0x6A1B, 0x9EDB, 0x69FF, 0x9EDC, 0x6B0A, + 0x9EDD, 0x69F9, 0x9EDE, 0x69F2, 0x9EDF, 0x69E7, 0x9EE0, 0x6A05, 0x9EE1, 0x69B1, 0x9EE2, 0x6A1E, 0x9EE3, 0x69ED, 0x9EE4, 0x6A14, + 0x9EE5, 0x69EB, 0x9EE6, 0x6A0A, 0x9EE7, 0x6A12, 0x9EE8, 0x6AC1, 0x9EE9, 0x6A23, 0x9EEA, 0x6A13, 0x9EEB, 0x6A44, 0x9EEC, 0x6A0C, + 0x9EED, 0x6A72, 0x9EEE, 0x6A36, 0x9EEF, 0x6A78, 0x9EF0, 0x6A47, 0x9EF1, 0x6A62, 0x9EF2, 0x6A59, 0x9EF3, 0x6A66, 0x9EF4, 0x6A48, + 0x9EF5, 0x6A38, 0x9EF6, 0x6A22, 0x9EF7, 0x6A90, 0x9EF8, 0x6A8D, 0x9EF9, 0x6AA0, 0x9EFA, 0x6A84, 0x9EFB, 0x6AA2, 0x9EFC, 0x6AA3, + 0x9F40, 0x6A97, 0x9F41, 0x8617, 0x9F42, 0x6ABB, 0x9F43, 0x6AC3, 0x9F44, 0x6AC2, 0x9F45, 0x6AB8, 0x9F46, 0x6AB3, 0x9F47, 0x6AAC, + 0x9F48, 0x6ADE, 0x9F49, 0x6AD1, 0x9F4A, 0x6ADF, 0x9F4B, 0x6AAA, 0x9F4C, 0x6ADA, 0x9F4D, 0x6AEA, 0x9F4E, 0x6AFB, 0x9F4F, 0x6B05, + 0x9F50, 0x8616, 0x9F51, 0x6AFA, 0x9F52, 0x6B12, 0x9F53, 0x6B16, 0x9F54, 0x9B31, 0x9F55, 0x6B1F, 0x9F56, 0x6B38, 0x9F57, 0x6B37, + 0x9F58, 0x76DC, 0x9F59, 0x6B39, 0x9F5A, 0x98EE, 0x9F5B, 0x6B47, 0x9F5C, 0x6B43, 0x9F5D, 0x6B49, 0x9F5E, 0x6B50, 0x9F5F, 0x6B59, + 0x9F60, 0x6B54, 0x9F61, 0x6B5B, 0x9F62, 0x6B5F, 0x9F63, 0x6B61, 0x9F64, 0x6B78, 0x9F65, 0x6B79, 0x9F66, 0x6B7F, 0x9F67, 0x6B80, + 0x9F68, 0x6B84, 0x9F69, 0x6B83, 0x9F6A, 0x6B8D, 0x9F6B, 0x6B98, 0x9F6C, 0x6B95, 0x9F6D, 0x6B9E, 0x9F6E, 0x6BA4, 0x9F6F, 0x6BAA, + 0x9F70, 0x6BAB, 0x9F71, 0x6BAF, 0x9F72, 0x6BB2, 0x9F73, 0x6BB1, 0x9F74, 0x6BB3, 0x9F75, 0x6BB7, 0x9F76, 0x6BBC, 0x9F77, 0x6BC6, + 0x9F78, 0x6BCB, 0x9F79, 0x6BD3, 0x9F7A, 0x6BDF, 0x9F7B, 0x6BEC, 0x9F7C, 0x6BEB, 0x9F7D, 0x6BF3, 0x9F7E, 0x6BEF, 0x9F80, 0x9EBE, + 0x9F81, 0x6C08, 0x9F82, 0x6C13, 0x9F83, 0x6C14, 0x9F84, 0x6C1B, 0x9F85, 0x6C24, 0x9F86, 0x6C23, 0x9F87, 0x6C5E, 0x9F88, 0x6C55, + 0x9F89, 0x6C62, 0x9F8A, 0x6C6A, 0x9F8B, 0x6C82, 0x9F8C, 0x6C8D, 0x9F8D, 0x6C9A, 0x9F8E, 0x6C81, 0x9F8F, 0x6C9B, 0x9F90, 0x6C7E, + 0x9F91, 0x6C68, 0x9F92, 0x6C73, 0x9F93, 0x6C92, 0x9F94, 0x6C90, 0x9F95, 0x6CC4, 0x9F96, 0x6CF1, 0x9F97, 0x6CD3, 0x9F98, 0x6CBD, + 0x9F99, 0x6CD7, 0x9F9A, 0x6CC5, 0x9F9B, 0x6CDD, 0x9F9C, 0x6CAE, 0x9F9D, 0x6CB1, 0x9F9E, 0x6CBE, 0x9F9F, 0x6CBA, 0x9FA0, 0x6CDB, + 0x9FA1, 0x6CEF, 0x9FA2, 0x6CD9, 0x9FA3, 0x6CEA, 0x9FA4, 0x6D1F, 0x9FA5, 0x884D, 0x9FA6, 0x6D36, 0x9FA7, 0x6D2B, 0x9FA8, 0x6D3D, + 0x9FA9, 0x6D38, 0x9FAA, 0x6D19, 0x9FAB, 0x6D35, 0x9FAC, 0x6D33, 0x9FAD, 0x6D12, 0x9FAE, 0x6D0C, 0x9FAF, 0x6D63, 0x9FB0, 0x6D93, + 0x9FB1, 0x6D64, 0x9FB2, 0x6D5A, 0x9FB3, 0x6D79, 0x9FB4, 0x6D59, 0x9FB5, 0x6D8E, 0x9FB6, 0x6D95, 0x9FB7, 0x6FE4, 0x9FB8, 0x6D85, + 0x9FB9, 0x6DF9, 0x9FBA, 0x6E15, 0x9FBB, 0x6E0A, 0x9FBC, 0x6DB5, 0x9FBD, 0x6DC7, 0x9FBE, 0x6DE6, 0x9FBF, 0x6DB8, 0x9FC0, 0x6DC6, + 0x9FC1, 0x6DEC, 0x9FC2, 0x6DDE, 0x9FC3, 0x6DCC, 0x9FC4, 0x6DE8, 0x9FC5, 0x6DD2, 0x9FC6, 0x6DC5, 0x9FC7, 0x6DFA, 0x9FC8, 0x6DD9, + 0x9FC9, 0x6DE4, 0x9FCA, 0x6DD5, 0x9FCB, 0x6DEA, 0x9FCC, 0x6DEE, 0x9FCD, 0x6E2D, 0x9FCE, 0x6E6E, 0x9FCF, 0x6E2E, 0x9FD0, 0x6E19, + 0x9FD1, 0x6E72, 0x9FD2, 0x6E5F, 0x9FD3, 0x6E3E, 0x9FD4, 0x6E23, 0x9FD5, 0x6E6B, 0x9FD6, 0x6E2B, 0x9FD7, 0x6E76, 0x9FD8, 0x6E4D, + 0x9FD9, 0x6E1F, 0x9FDA, 0x6E43, 0x9FDB, 0x6E3A, 0x9FDC, 0x6E4E, 0x9FDD, 0x6E24, 0x9FDE, 0x6EFF, 0x9FDF, 0x6E1D, 0x9FE0, 0x6E38, + 0x9FE1, 0x6E82, 0x9FE2, 0x6EAA, 0x9FE3, 0x6E98, 0x9FE4, 0x6EC9, 0x9FE5, 0x6EB7, 0x9FE6, 0x6ED3, 0x9FE7, 0x6EBD, 0x9FE8, 0x6EAF, + 0x9FE9, 0x6EC4, 0x9FEA, 0x6EB2, 0x9FEB, 0x6ED4, 0x9FEC, 0x6ED5, 0x9FED, 0x6E8F, 0x9FEE, 0x6EA5, 0x9FEF, 0x6EC2, 0x9FF0, 0x6E9F, + 0x9FF1, 0x6F41, 0x9FF2, 0x6F11, 0x9FF3, 0x704C, 0x9FF4, 0x6EEC, 0x9FF5, 0x6EF8, 0x9FF6, 0x6EFE, 0x9FF7, 0x6F3F, 0x9FF8, 0x6EF2, + 0x9FF9, 0x6F31, 0x9FFA, 0x6EEF, 0x9FFB, 0x6F32, 0x9FFC, 0x6ECC, 0xE040, 0x6F3E, 0xE041, 0x6F13, 0xE042, 0x6EF7, 0xE043, 0x6F86, + 0xE044, 0x6F7A, 0xE045, 0x6F78, 0xE046, 0x6F81, 0xE047, 0x6F80, 0xE048, 0x6F6F, 0xE049, 0x6F5B, 0xE04A, 0x6FF3, 0xE04B, 0x6F6D, + 0xE04C, 0x6F82, 0xE04D, 0x6F7C, 0xE04E, 0x6F58, 0xE04F, 0x6F8E, 0xE050, 0x6F91, 0xE051, 0x6FC2, 0xE052, 0x6F66, 0xE053, 0x6FB3, + 0xE054, 0x6FA3, 0xE055, 0x6FA1, 0xE056, 0x6FA4, 0xE057, 0x6FB9, 0xE058, 0x6FC6, 0xE059, 0x6FAA, 0xE05A, 0x6FDF, 0xE05B, 0x6FD5, + 0xE05C, 0x6FEC, 0xE05D, 0x6FD4, 0xE05E, 0x6FD8, 0xE05F, 0x6FF1, 0xE060, 0x6FEE, 0xE061, 0x6FDB, 0xE062, 0x7009, 0xE063, 0x700B, + 0xE064, 0x6FFA, 0xE065, 0x7011, 0xE066, 0x7001, 0xE067, 0x700F, 0xE068, 0x6FFE, 0xE069, 0x701B, 0xE06A, 0x701A, 0xE06B, 0x6F74, + 0xE06C, 0x701D, 0xE06D, 0x7018, 0xE06E, 0x701F, 0xE06F, 0x7030, 0xE070, 0x703E, 0xE071, 0x7032, 0xE072, 0x7051, 0xE073, 0x7063, + 0xE074, 0x7099, 0xE075, 0x7092, 0xE076, 0x70AF, 0xE077, 0x70F1, 0xE078, 0x70AC, 0xE079, 0x70B8, 0xE07A, 0x70B3, 0xE07B, 0x70AE, + 0xE07C, 0x70DF, 0xE07D, 0x70CB, 0xE07E, 0x70DD, 0xE080, 0x70D9, 0xE081, 0x7109, 0xE082, 0x70FD, 0xE083, 0x711C, 0xE084, 0x7119, + 0xE085, 0x7165, 0xE086, 0x7155, 0xE087, 0x7188, 0xE088, 0x7166, 0xE089, 0x7162, 0xE08A, 0x714C, 0xE08B, 0x7156, 0xE08C, 0x716C, + 0xE08D, 0x718F, 0xE08E, 0x71FB, 0xE08F, 0x7184, 0xE090, 0x7195, 0xE091, 0x71A8, 0xE092, 0x71AC, 0xE093, 0x71D7, 0xE094, 0x71B9, + 0xE095, 0x71BE, 0xE096, 0x71D2, 0xE097, 0x71C9, 0xE098, 0x71D4, 0xE099, 0x71CE, 0xE09A, 0x71E0, 0xE09B, 0x71EC, 0xE09C, 0x71E7, + 0xE09D, 0x71F5, 0xE09E, 0x71FC, 0xE09F, 0x71F9, 0xE0A0, 0x71FF, 0xE0A1, 0x720D, 0xE0A2, 0x7210, 0xE0A3, 0x721B, 0xE0A4, 0x7228, + 0xE0A5, 0x722D, 0xE0A6, 0x722C, 0xE0A7, 0x7230, 0xE0A8, 0x7232, 0xE0A9, 0x723B, 0xE0AA, 0x723C, 0xE0AB, 0x723F, 0xE0AC, 0x7240, + 0xE0AD, 0x7246, 0xE0AE, 0x724B, 0xE0AF, 0x7258, 0xE0B0, 0x7274, 0xE0B1, 0x727E, 0xE0B2, 0x7282, 0xE0B3, 0x7281, 0xE0B4, 0x7287, + 0xE0B5, 0x7292, 0xE0B6, 0x7296, 0xE0B7, 0x72A2, 0xE0B8, 0x72A7, 0xE0B9, 0x72B9, 0xE0BA, 0x72B2, 0xE0BB, 0x72C3, 0xE0BC, 0x72C6, + 0xE0BD, 0x72C4, 0xE0BE, 0x72CE, 0xE0BF, 0x72D2, 0xE0C0, 0x72E2, 0xE0C1, 0x72E0, 0xE0C2, 0x72E1, 0xE0C3, 0x72F9, 0xE0C4, 0x72F7, + 0xE0C5, 0x500F, 0xE0C6, 0x7317, 0xE0C7, 0x730A, 0xE0C8, 0x731C, 0xE0C9, 0x7316, 0xE0CA, 0x731D, 0xE0CB, 0x7334, 0xE0CC, 0x732F, + 0xE0CD, 0x7329, 0xE0CE, 0x7325, 0xE0CF, 0x733E, 0xE0D0, 0x734E, 0xE0D1, 0x734F, 0xE0D2, 0x9ED8, 0xE0D3, 0x7357, 0xE0D4, 0x736A, + 0xE0D5, 0x7368, 0xE0D6, 0x7370, 0xE0D7, 0x7378, 0xE0D8, 0x7375, 0xE0D9, 0x737B, 0xE0DA, 0x737A, 0xE0DB, 0x73C8, 0xE0DC, 0x73B3, + 0xE0DD, 0x73CE, 0xE0DE, 0x73BB, 0xE0DF, 0x73C0, 0xE0E0, 0x73E5, 0xE0E1, 0x73EE, 0xE0E2, 0x73DE, 0xE0E3, 0x74A2, 0xE0E4, 0x7405, + 0xE0E5, 0x746F, 0xE0E6, 0x7425, 0xE0E7, 0x73F8, 0xE0E8, 0x7432, 0xE0E9, 0x743A, 0xE0EA, 0x7455, 0xE0EB, 0x743F, 0xE0EC, 0x745F, + 0xE0ED, 0x7459, 0xE0EE, 0x7441, 0xE0EF, 0x745C, 0xE0F0, 0x7469, 0xE0F1, 0x7470, 0xE0F2, 0x7463, 0xE0F3, 0x746A, 0xE0F4, 0x7476, + 0xE0F5, 0x747E, 0xE0F6, 0x748B, 0xE0F7, 0x749E, 0xE0F8, 0x74A7, 0xE0F9, 0x74CA, 0xE0FA, 0x74CF, 0xE0FB, 0x74D4, 0xE0FC, 0x73F1, + 0xE140, 0x74E0, 0xE141, 0x74E3, 0xE142, 0x74E7, 0xE143, 0x74E9, 0xE144, 0x74EE, 0xE145, 0x74F2, 0xE146, 0x74F0, 0xE147, 0x74F1, + 0xE148, 0x74F8, 0xE149, 0x74F7, 0xE14A, 0x7504, 0xE14B, 0x7503, 0xE14C, 0x7505, 0xE14D, 0x750C, 0xE14E, 0x750E, 0xE14F, 0x750D, + 0xE150, 0x7515, 0xE151, 0x7513, 0xE152, 0x751E, 0xE153, 0x7526, 0xE154, 0x752C, 0xE155, 0x753C, 0xE156, 0x7544, 0xE157, 0x754D, + 0xE158, 0x754A, 0xE159, 0x7549, 0xE15A, 0x755B, 0xE15B, 0x7546, 0xE15C, 0x755A, 0xE15D, 0x7569, 0xE15E, 0x7564, 0xE15F, 0x7567, + 0xE160, 0x756B, 0xE161, 0x756D, 0xE162, 0x7578, 0xE163, 0x7576, 0xE164, 0x7586, 0xE165, 0x7587, 0xE166, 0x7574, 0xE167, 0x758A, + 0xE168, 0x7589, 0xE169, 0x7582, 0xE16A, 0x7594, 0xE16B, 0x759A, 0xE16C, 0x759D, 0xE16D, 0x75A5, 0xE16E, 0x75A3, 0xE16F, 0x75C2, + 0xE170, 0x75B3, 0xE171, 0x75C3, 0xE172, 0x75B5, 0xE173, 0x75BD, 0xE174, 0x75B8, 0xE175, 0x75BC, 0xE176, 0x75B1, 0xE177, 0x75CD, + 0xE178, 0x75CA, 0xE179, 0x75D2, 0xE17A, 0x75D9, 0xE17B, 0x75E3, 0xE17C, 0x75DE, 0xE17D, 0x75FE, 0xE17E, 0x75FF, 0xE180, 0x75FC, + 0xE181, 0x7601, 0xE182, 0x75F0, 0xE183, 0x75FA, 0xE184, 0x75F2, 0xE185, 0x75F3, 0xE186, 0x760B, 0xE187, 0x760D, 0xE188, 0x7609, + 0xE189, 0x761F, 0xE18A, 0x7627, 0xE18B, 0x7620, 0xE18C, 0x7621, 0xE18D, 0x7622, 0xE18E, 0x7624, 0xE18F, 0x7634, 0xE190, 0x7630, + 0xE191, 0x763B, 0xE192, 0x7647, 0xE193, 0x7648, 0xE194, 0x7646, 0xE195, 0x765C, 0xE196, 0x7658, 0xE197, 0x7661, 0xE198, 0x7662, + 0xE199, 0x7668, 0xE19A, 0x7669, 0xE19B, 0x766A, 0xE19C, 0x7667, 0xE19D, 0x766C, 0xE19E, 0x7670, 0xE19F, 0x7672, 0xE1A0, 0x7676, + 0xE1A1, 0x7678, 0xE1A2, 0x767C, 0xE1A3, 0x7680, 0xE1A4, 0x7683, 0xE1A5, 0x7688, 0xE1A6, 0x768B, 0xE1A7, 0x768E, 0xE1A8, 0x7696, + 0xE1A9, 0x7693, 0xE1AA, 0x7699, 0xE1AB, 0x769A, 0xE1AC, 0x76B0, 0xE1AD, 0x76B4, 0xE1AE, 0x76B8, 0xE1AF, 0x76B9, 0xE1B0, 0x76BA, + 0xE1B1, 0x76C2, 0xE1B2, 0x76CD, 0xE1B3, 0x76D6, 0xE1B4, 0x76D2, 0xE1B5, 0x76DE, 0xE1B6, 0x76E1, 0xE1B7, 0x76E5, 0xE1B8, 0x76E7, + 0xE1B9, 0x76EA, 0xE1BA, 0x862F, 0xE1BB, 0x76FB, 0xE1BC, 0x7708, 0xE1BD, 0x7707, 0xE1BE, 0x7704, 0xE1BF, 0x7729, 0xE1C0, 0x7724, + 0xE1C1, 0x771E, 0xE1C2, 0x7725, 0xE1C3, 0x7726, 0xE1C4, 0x771B, 0xE1C5, 0x7737, 0xE1C6, 0x7738, 0xE1C7, 0x7747, 0xE1C8, 0x775A, + 0xE1C9, 0x7768, 0xE1CA, 0x776B, 0xE1CB, 0x775B, 0xE1CC, 0x7765, 0xE1CD, 0x777F, 0xE1CE, 0x777E, 0xE1CF, 0x7779, 0xE1D0, 0x778E, + 0xE1D1, 0x778B, 0xE1D2, 0x7791, 0xE1D3, 0x77A0, 0xE1D4, 0x779E, 0xE1D5, 0x77B0, 0xE1D6, 0x77B6, 0xE1D7, 0x77B9, 0xE1D8, 0x77BF, + 0xE1D9, 0x77BC, 0xE1DA, 0x77BD, 0xE1DB, 0x77BB, 0xE1DC, 0x77C7, 0xE1DD, 0x77CD, 0xE1DE, 0x77D7, 0xE1DF, 0x77DA, 0xE1E0, 0x77DC, + 0xE1E1, 0x77E3, 0xE1E2, 0x77EE, 0xE1E3, 0x77FC, 0xE1E4, 0x780C, 0xE1E5, 0x7812, 0xE1E6, 0x7926, 0xE1E7, 0x7820, 0xE1E8, 0x792A, + 0xE1E9, 0x7845, 0xE1EA, 0x788E, 0xE1EB, 0x7874, 0xE1EC, 0x7886, 0xE1ED, 0x787C, 0xE1EE, 0x789A, 0xE1EF, 0x788C, 0xE1F0, 0x78A3, + 0xE1F1, 0x78B5, 0xE1F2, 0x78AA, 0xE1F3, 0x78AF, 0xE1F4, 0x78D1, 0xE1F5, 0x78C6, 0xE1F6, 0x78CB, 0xE1F7, 0x78D4, 0xE1F8, 0x78BE, + 0xE1F9, 0x78BC, 0xE1FA, 0x78C5, 0xE1FB, 0x78CA, 0xE1FC, 0x78EC, 0xE240, 0x78E7, 0xE241, 0x78DA, 0xE242, 0x78FD, 0xE243, 0x78F4, + 0xE244, 0x7907, 0xE245, 0x7912, 0xE246, 0x7911, 0xE247, 0x7919, 0xE248, 0x792C, 0xE249, 0x792B, 0xE24A, 0x7940, 0xE24B, 0x7960, + 0xE24C, 0x7957, 0xE24D, 0x795F, 0xE24E, 0x795A, 0xE24F, 0x7955, 0xE250, 0x7953, 0xE251, 0x797A, 0xE252, 0x797F, 0xE253, 0x798A, + 0xE254, 0x799D, 0xE255, 0x79A7, 0xE256, 0x9F4B, 0xE257, 0x79AA, 0xE258, 0x79AE, 0xE259, 0x79B3, 0xE25A, 0x79B9, 0xE25B, 0x79BA, + 0xE25C, 0x79C9, 0xE25D, 0x79D5, 0xE25E, 0x79E7, 0xE25F, 0x79EC, 0xE260, 0x79E1, 0xE261, 0x79E3, 0xE262, 0x7A08, 0xE263, 0x7A0D, + 0xE264, 0x7A18, 0xE265, 0x7A19, 0xE266, 0x7A20, 0xE267, 0x7A1F, 0xE268, 0x7980, 0xE269, 0x7A31, 0xE26A, 0x7A3B, 0xE26B, 0x7A3E, + 0xE26C, 0x7A37, 0xE26D, 0x7A43, 0xE26E, 0x7A57, 0xE26F, 0x7A49, 0xE270, 0x7A61, 0xE271, 0x7A62, 0xE272, 0x7A69, 0xE273, 0x9F9D, + 0xE274, 0x7A70, 0xE275, 0x7A79, 0xE276, 0x7A7D, 0xE277, 0x7A88, 0xE278, 0x7A97, 0xE279, 0x7A95, 0xE27A, 0x7A98, 0xE27B, 0x7A96, + 0xE27C, 0x7AA9, 0xE27D, 0x7AC8, 0xE27E, 0x7AB0, 0xE280, 0x7AB6, 0xE281, 0x7AC5, 0xE282, 0x7AC4, 0xE283, 0x7ABF, 0xE284, 0x9083, + 0xE285, 0x7AC7, 0xE286, 0x7ACA, 0xE287, 0x7ACD, 0xE288, 0x7ACF, 0xE289, 0x7AD5, 0xE28A, 0x7AD3, 0xE28B, 0x7AD9, 0xE28C, 0x7ADA, + 0xE28D, 0x7ADD, 0xE28E, 0x7AE1, 0xE28F, 0x7AE2, 0xE290, 0x7AE6, 0xE291, 0x7AED, 0xE292, 0x7AF0, 0xE293, 0x7B02, 0xE294, 0x7B0F, + 0xE295, 0x7B0A, 0xE296, 0x7B06, 0xE297, 0x7B33, 0xE298, 0x7B18, 0xE299, 0x7B19, 0xE29A, 0x7B1E, 0xE29B, 0x7B35, 0xE29C, 0x7B28, + 0xE29D, 0x7B36, 0xE29E, 0x7B50, 0xE29F, 0x7B7A, 0xE2A0, 0x7B04, 0xE2A1, 0x7B4D, 0xE2A2, 0x7B0B, 0xE2A3, 0x7B4C, 0xE2A4, 0x7B45, + 0xE2A5, 0x7B75, 0xE2A6, 0x7B65, 0xE2A7, 0x7B74, 0xE2A8, 0x7B67, 0xE2A9, 0x7B70, 0xE2AA, 0x7B71, 0xE2AB, 0x7B6C, 0xE2AC, 0x7B6E, + 0xE2AD, 0x7B9D, 0xE2AE, 0x7B98, 0xE2AF, 0x7B9F, 0xE2B0, 0x7B8D, 0xE2B1, 0x7B9C, 0xE2B2, 0x7B9A, 0xE2B3, 0x7B8B, 0xE2B4, 0x7B92, + 0xE2B5, 0x7B8F, 0xE2B6, 0x7B5D, 0xE2B7, 0x7B99, 0xE2B8, 0x7BCB, 0xE2B9, 0x7BC1, 0xE2BA, 0x7BCC, 0xE2BB, 0x7BCF, 0xE2BC, 0x7BB4, + 0xE2BD, 0x7BC6, 0xE2BE, 0x7BDD, 0xE2BF, 0x7BE9, 0xE2C0, 0x7C11, 0xE2C1, 0x7C14, 0xE2C2, 0x7BE6, 0xE2C3, 0x7BE5, 0xE2C4, 0x7C60, + 0xE2C5, 0x7C00, 0xE2C6, 0x7C07, 0xE2C7, 0x7C13, 0xE2C8, 0x7BF3, 0xE2C9, 0x7BF7, 0xE2CA, 0x7C17, 0xE2CB, 0x7C0D, 0xE2CC, 0x7BF6, + 0xE2CD, 0x7C23, 0xE2CE, 0x7C27, 0xE2CF, 0x7C2A, 0xE2D0, 0x7C1F, 0xE2D1, 0x7C37, 0xE2D2, 0x7C2B, 0xE2D3, 0x7C3D, 0xE2D4, 0x7C4C, + 0xE2D5, 0x7C43, 0xE2D6, 0x7C54, 0xE2D7, 0x7C4F, 0xE2D8, 0x7C40, 0xE2D9, 0x7C50, 0xE2DA, 0x7C58, 0xE2DB, 0x7C5F, 0xE2DC, 0x7C64, + 0xE2DD, 0x7C56, 0xE2DE, 0x7C65, 0xE2DF, 0x7C6C, 0xE2E0, 0x7C75, 0xE2E1, 0x7C83, 0xE2E2, 0x7C90, 0xE2E3, 0x7CA4, 0xE2E4, 0x7CAD, + 0xE2E5, 0x7CA2, 0xE2E6, 0x7CAB, 0xE2E7, 0x7CA1, 0xE2E8, 0x7CA8, 0xE2E9, 0x7CB3, 0xE2EA, 0x7CB2, 0xE2EB, 0x7CB1, 0xE2EC, 0x7CAE, + 0xE2ED, 0x7CB9, 0xE2EE, 0x7CBD, 0xE2EF, 0x7CC0, 0xE2F0, 0x7CC5, 0xE2F1, 0x7CC2, 0xE2F2, 0x7CD8, 0xE2F3, 0x7CD2, 0xE2F4, 0x7CDC, + 0xE2F5, 0x7CE2, 0xE2F6, 0x9B3B, 0xE2F7, 0x7CEF, 0xE2F8, 0x7CF2, 0xE2F9, 0x7CF4, 0xE2FA, 0x7CF6, 0xE2FB, 0x7CFA, 0xE2FC, 0x7D06, + 0xE340, 0x7D02, 0xE341, 0x7D1C, 0xE342, 0x7D15, 0xE343, 0x7D0A, 0xE344, 0x7D45, 0xE345, 0x7D4B, 0xE346, 0x7D2E, 0xE347, 0x7D32, + 0xE348, 0x7D3F, 0xE349, 0x7D35, 0xE34A, 0x7D46, 0xE34B, 0x7D73, 0xE34C, 0x7D56, 0xE34D, 0x7D4E, 0xE34E, 0x7D72, 0xE34F, 0x7D68, + 0xE350, 0x7D6E, 0xE351, 0x7D4F, 0xE352, 0x7D63, 0xE353, 0x7D93, 0xE354, 0x7D89, 0xE355, 0x7D5B, 0xE356, 0x7D8F, 0xE357, 0x7D7D, + 0xE358, 0x7D9B, 0xE359, 0x7DBA, 0xE35A, 0x7DAE, 0xE35B, 0x7DA3, 0xE35C, 0x7DB5, 0xE35D, 0x7DC7, 0xE35E, 0x7DBD, 0xE35F, 0x7DAB, + 0xE360, 0x7E3D, 0xE361, 0x7DA2, 0xE362, 0x7DAF, 0xE363, 0x7DDC, 0xE364, 0x7DB8, 0xE365, 0x7D9F, 0xE366, 0x7DB0, 0xE367, 0x7DD8, + 0xE368, 0x7DDD, 0xE369, 0x7DE4, 0xE36A, 0x7DDE, 0xE36B, 0x7DFB, 0xE36C, 0x7DF2, 0xE36D, 0x7DE1, 0xE36E, 0x7E05, 0xE36F, 0x7E0A, + 0xE370, 0x7E23, 0xE371, 0x7E21, 0xE372, 0x7E12, 0xE373, 0x7E31, 0xE374, 0x7E1F, 0xE375, 0x7E09, 0xE376, 0x7E0B, 0xE377, 0x7E22, + 0xE378, 0x7E46, 0xE379, 0x7E66, 0xE37A, 0x7E3B, 0xE37B, 0x7E35, 0xE37C, 0x7E39, 0xE37D, 0x7E43, 0xE37E, 0x7E37, 0xE380, 0x7E32, + 0xE381, 0x7E3A, 0xE382, 0x7E67, 0xE383, 0x7E5D, 0xE384, 0x7E56, 0xE385, 0x7E5E, 0xE386, 0x7E59, 0xE387, 0x7E5A, 0xE388, 0x7E79, + 0xE389, 0x7E6A, 0xE38A, 0x7E69, 0xE38B, 0x7E7C, 0xE38C, 0x7E7B, 0xE38D, 0x7E83, 0xE38E, 0x7DD5, 0xE38F, 0x7E7D, 0xE390, 0x8FAE, + 0xE391, 0x7E7F, 0xE392, 0x7E88, 0xE393, 0x7E89, 0xE394, 0x7E8C, 0xE395, 0x7E92, 0xE396, 0x7E90, 0xE397, 0x7E93, 0xE398, 0x7E94, + 0xE399, 0x7E96, 0xE39A, 0x7E8E, 0xE39B, 0x7E9B, 0xE39C, 0x7E9C, 0xE39D, 0x7F38, 0xE39E, 0x7F3A, 0xE39F, 0x7F45, 0xE3A0, 0x7F4C, + 0xE3A1, 0x7F4D, 0xE3A2, 0x7F4E, 0xE3A3, 0x7F50, 0xE3A4, 0x7F51, 0xE3A5, 0x7F55, 0xE3A6, 0x7F54, 0xE3A7, 0x7F58, 0xE3A8, 0x7F5F, + 0xE3A9, 0x7F60, 0xE3AA, 0x7F68, 0xE3AB, 0x7F69, 0xE3AC, 0x7F67, 0xE3AD, 0x7F78, 0xE3AE, 0x7F82, 0xE3AF, 0x7F86, 0xE3B0, 0x7F83, + 0xE3B1, 0x7F88, 0xE3B2, 0x7F87, 0xE3B3, 0x7F8C, 0xE3B4, 0x7F94, 0xE3B5, 0x7F9E, 0xE3B6, 0x7F9D, 0xE3B7, 0x7F9A, 0xE3B8, 0x7FA3, + 0xE3B9, 0x7FAF, 0xE3BA, 0x7FB2, 0xE3BB, 0x7FB9, 0xE3BC, 0x7FAE, 0xE3BD, 0x7FB6, 0xE3BE, 0x7FB8, 0xE3BF, 0x8B71, 0xE3C0, 0x7FC5, + 0xE3C1, 0x7FC6, 0xE3C2, 0x7FCA, 0xE3C3, 0x7FD5, 0xE3C4, 0x7FD4, 0xE3C5, 0x7FE1, 0xE3C6, 0x7FE6, 0xE3C7, 0x7FE9, 0xE3C8, 0x7FF3, + 0xE3C9, 0x7FF9, 0xE3CA, 0x98DC, 0xE3CB, 0x8006, 0xE3CC, 0x8004, 0xE3CD, 0x800B, 0xE3CE, 0x8012, 0xE3CF, 0x8018, 0xE3D0, 0x8019, + 0xE3D1, 0x801C, 0xE3D2, 0x8021, 0xE3D3, 0x8028, 0xE3D4, 0x803F, 0xE3D5, 0x803B, 0xE3D6, 0x804A, 0xE3D7, 0x8046, 0xE3D8, 0x8052, + 0xE3D9, 0x8058, 0xE3DA, 0x805A, 0xE3DB, 0x805F, 0xE3DC, 0x8062, 0xE3DD, 0x8068, 0xE3DE, 0x8073, 0xE3DF, 0x8072, 0xE3E0, 0x8070, + 0xE3E1, 0x8076, 0xE3E2, 0x8079, 0xE3E3, 0x807D, 0xE3E4, 0x807F, 0xE3E5, 0x8084, 0xE3E6, 0x8086, 0xE3E7, 0x8085, 0xE3E8, 0x809B, + 0xE3E9, 0x8093, 0xE3EA, 0x809A, 0xE3EB, 0x80AD, 0xE3EC, 0x5190, 0xE3ED, 0x80AC, 0xE3EE, 0x80DB, 0xE3EF, 0x80E5, 0xE3F0, 0x80D9, + 0xE3F1, 0x80DD, 0xE3F2, 0x80C4, 0xE3F3, 0x80DA, 0xE3F4, 0x80D6, 0xE3F5, 0x8109, 0xE3F6, 0x80EF, 0xE3F7, 0x80F1, 0xE3F8, 0x811B, + 0xE3F9, 0x8129, 0xE3FA, 0x8123, 0xE3FB, 0x812F, 0xE3FC, 0x814B, 0xE440, 0x968B, 0xE441, 0x8146, 0xE442, 0x813E, 0xE443, 0x8153, + 0xE444, 0x8151, 0xE445, 0x80FC, 0xE446, 0x8171, 0xE447, 0x816E, 0xE448, 0x8165, 0xE449, 0x8166, 0xE44A, 0x8174, 0xE44B, 0x8183, + 0xE44C, 0x8188, 0xE44D, 0x818A, 0xE44E, 0x8180, 0xE44F, 0x8182, 0xE450, 0x81A0, 0xE451, 0x8195, 0xE452, 0x81A4, 0xE453, 0x81A3, + 0xE454, 0x815F, 0xE455, 0x8193, 0xE456, 0x81A9, 0xE457, 0x81B0, 0xE458, 0x81B5, 0xE459, 0x81BE, 0xE45A, 0x81B8, 0xE45B, 0x81BD, + 0xE45C, 0x81C0, 0xE45D, 0x81C2, 0xE45E, 0x81BA, 0xE45F, 0x81C9, 0xE460, 0x81CD, 0xE461, 0x81D1, 0xE462, 0x81D9, 0xE463, 0x81D8, + 0xE464, 0x81C8, 0xE465, 0x81DA, 0xE466, 0x81DF, 0xE467, 0x81E0, 0xE468, 0x81E7, 0xE469, 0x81FA, 0xE46A, 0x81FB, 0xE46B, 0x81FE, + 0xE46C, 0x8201, 0xE46D, 0x8202, 0xE46E, 0x8205, 0xE46F, 0x8207, 0xE470, 0x820A, 0xE471, 0x820D, 0xE472, 0x8210, 0xE473, 0x8216, + 0xE474, 0x8229, 0xE475, 0x822B, 0xE476, 0x8238, 0xE477, 0x8233, 0xE478, 0x8240, 0xE479, 0x8259, 0xE47A, 0x8258, 0xE47B, 0x825D, + 0xE47C, 0x825A, 0xE47D, 0x825F, 0xE47E, 0x8264, 0xE480, 0x8262, 0xE481, 0x8268, 0xE482, 0x826A, 0xE483, 0x826B, 0xE484, 0x822E, + 0xE485, 0x8271, 0xE486, 0x8277, 0xE487, 0x8278, 0xE488, 0x827E, 0xE489, 0x828D, 0xE48A, 0x8292, 0xE48B, 0x82AB, 0xE48C, 0x829F, + 0xE48D, 0x82BB, 0xE48E, 0x82AC, 0xE48F, 0x82E1, 0xE490, 0x82E3, 0xE491, 0x82DF, 0xE492, 0x82D2, 0xE493, 0x82F4, 0xE494, 0x82F3, + 0xE495, 0x82FA, 0xE496, 0x8393, 0xE497, 0x8303, 0xE498, 0x82FB, 0xE499, 0x82F9, 0xE49A, 0x82DE, 0xE49B, 0x8306, 0xE49C, 0x82DC, + 0xE49D, 0x8309, 0xE49E, 0x82D9, 0xE49F, 0x8335, 0xE4A0, 0x8334, 0xE4A1, 0x8316, 0xE4A2, 0x8332, 0xE4A3, 0x8331, 0xE4A4, 0x8340, + 0xE4A5, 0x8339, 0xE4A6, 0x8350, 0xE4A7, 0x8345, 0xE4A8, 0x832F, 0xE4A9, 0x832B, 0xE4AA, 0x8317, 0xE4AB, 0x8318, 0xE4AC, 0x8385, + 0xE4AD, 0x839A, 0xE4AE, 0x83AA, 0xE4AF, 0x839F, 0xE4B0, 0x83A2, 0xE4B1, 0x8396, 0xE4B2, 0x8323, 0xE4B3, 0x838E, 0xE4B4, 0x8387, + 0xE4B5, 0x838A, 0xE4B6, 0x837C, 0xE4B7, 0x83B5, 0xE4B8, 0x8373, 0xE4B9, 0x8375, 0xE4BA, 0x83A0, 0xE4BB, 0x8389, 0xE4BC, 0x83A8, + 0xE4BD, 0x83F4, 0xE4BE, 0x8413, 0xE4BF, 0x83EB, 0xE4C0, 0x83CE, 0xE4C1, 0x83FD, 0xE4C2, 0x8403, 0xE4C3, 0x83D8, 0xE4C4, 0x840B, + 0xE4C5, 0x83C1, 0xE4C6, 0x83F7, 0xE4C7, 0x8407, 0xE4C8, 0x83E0, 0xE4C9, 0x83F2, 0xE4CA, 0x840D, 0xE4CB, 0x8422, 0xE4CC, 0x8420, + 0xE4CD, 0x83BD, 0xE4CE, 0x8438, 0xE4CF, 0x8506, 0xE4D0, 0x83FB, 0xE4D1, 0x846D, 0xE4D2, 0x842A, 0xE4D3, 0x843C, 0xE4D4, 0x855A, + 0xE4D5, 0x8484, 0xE4D6, 0x8477, 0xE4D7, 0x846B, 0xE4D8, 0x84AD, 0xE4D9, 0x846E, 0xE4DA, 0x8482, 0xE4DB, 0x8469, 0xE4DC, 0x8446, + 0xE4DD, 0x842C, 0xE4DE, 0x846F, 0xE4DF, 0x8479, 0xE4E0, 0x8435, 0xE4E1, 0x84CA, 0xE4E2, 0x8462, 0xE4E3, 0x84B9, 0xE4E4, 0x84BF, + 0xE4E5, 0x849F, 0xE4E6, 0x84D9, 0xE4E7, 0x84CD, 0xE4E8, 0x84BB, 0xE4E9, 0x84DA, 0xE4EA, 0x84D0, 0xE4EB, 0x84C1, 0xE4EC, 0x84C6, + 0xE4ED, 0x84D6, 0xE4EE, 0x84A1, 0xE4EF, 0x8521, 0xE4F0, 0x84FF, 0xE4F1, 0x84F4, 0xE4F2, 0x8517, 0xE4F3, 0x8518, 0xE4F4, 0x852C, + 0xE4F5, 0x851F, 0xE4F6, 0x8515, 0xE4F7, 0x8514, 0xE4F8, 0x84FC, 0xE4F9, 0x8540, 0xE4FA, 0x8563, 0xE4FB, 0x8558, 0xE4FC, 0x8548, + 0xE540, 0x8541, 0xE541, 0x8602, 0xE542, 0x854B, 0xE543, 0x8555, 0xE544, 0x8580, 0xE545, 0x85A4, 0xE546, 0x8588, 0xE547, 0x8591, + 0xE548, 0x858A, 0xE549, 0x85A8, 0xE54A, 0x856D, 0xE54B, 0x8594, 0xE54C, 0x859B, 0xE54D, 0x85EA, 0xE54E, 0x8587, 0xE54F, 0x859C, + 0xE550, 0x8577, 0xE551, 0x857E, 0xE552, 0x8590, 0xE553, 0x85C9, 0xE554, 0x85BA, 0xE555, 0x85CF, 0xE556, 0x85B9, 0xE557, 0x85D0, + 0xE558, 0x85D5, 0xE559, 0x85DD, 0xE55A, 0x85E5, 0xE55B, 0x85DC, 0xE55C, 0x85F9, 0xE55D, 0x860A, 0xE55E, 0x8613, 0xE55F, 0x860B, + 0xE560, 0x85FE, 0xE561, 0x85FA, 0xE562, 0x8606, 0xE563, 0x8622, 0xE564, 0x861A, 0xE565, 0x8630, 0xE566, 0x863F, 0xE567, 0x864D, + 0xE568, 0x4E55, 0xE569, 0x8654, 0xE56A, 0x865F, 0xE56B, 0x8667, 0xE56C, 0x8671, 0xE56D, 0x8693, 0xE56E, 0x86A3, 0xE56F, 0x86A9, + 0xE570, 0x86AA, 0xE571, 0x868B, 0xE572, 0x868C, 0xE573, 0x86B6, 0xE574, 0x86AF, 0xE575, 0x86C4, 0xE576, 0x86C6, 0xE577, 0x86B0, + 0xE578, 0x86C9, 0xE579, 0x8823, 0xE57A, 0x86AB, 0xE57B, 0x86D4, 0xE57C, 0x86DE, 0xE57D, 0x86E9, 0xE57E, 0x86EC, 0xE580, 0x86DF, + 0xE581, 0x86DB, 0xE582, 0x86EF, 0xE583, 0x8712, 0xE584, 0x8706, 0xE585, 0x8708, 0xE586, 0x8700, 0xE587, 0x8703, 0xE588, 0x86FB, + 0xE589, 0x8711, 0xE58A, 0x8709, 0xE58B, 0x870D, 0xE58C, 0x86F9, 0xE58D, 0x870A, 0xE58E, 0x8734, 0xE58F, 0x873F, 0xE590, 0x8737, + 0xE591, 0x873B, 0xE592, 0x8725, 0xE593, 0x8729, 0xE594, 0x871A, 0xE595, 0x8760, 0xE596, 0x875F, 0xE597, 0x8778, 0xE598, 0x874C, + 0xE599, 0x874E, 0xE59A, 0x8774, 0xE59B, 0x8757, 0xE59C, 0x8768, 0xE59D, 0x876E, 0xE59E, 0x8759, 0xE59F, 0x8753, 0xE5A0, 0x8763, + 0xE5A1, 0x876A, 0xE5A2, 0x8805, 0xE5A3, 0x87A2, 0xE5A4, 0x879F, 0xE5A5, 0x8782, 0xE5A6, 0x87AF, 0xE5A7, 0x87CB, 0xE5A8, 0x87BD, + 0xE5A9, 0x87C0, 0xE5AA, 0x87D0, 0xE5AB, 0x96D6, 0xE5AC, 0x87AB, 0xE5AD, 0x87C4, 0xE5AE, 0x87B3, 0xE5AF, 0x87C7, 0xE5B0, 0x87C6, + 0xE5B1, 0x87BB, 0xE5B2, 0x87EF, 0xE5B3, 0x87F2, 0xE5B4, 0x87E0, 0xE5B5, 0x880F, 0xE5B6, 0x880D, 0xE5B7, 0x87FE, 0xE5B8, 0x87F6, + 0xE5B9, 0x87F7, 0xE5BA, 0x880E, 0xE5BB, 0x87D2, 0xE5BC, 0x8811, 0xE5BD, 0x8816, 0xE5BE, 0x8815, 0xE5BF, 0x8822, 0xE5C0, 0x8821, + 0xE5C1, 0x8831, 0xE5C2, 0x8836, 0xE5C3, 0x8839, 0xE5C4, 0x8827, 0xE5C5, 0x883B, 0xE5C6, 0x8844, 0xE5C7, 0x8842, 0xE5C8, 0x8852, + 0xE5C9, 0x8859, 0xE5CA, 0x885E, 0xE5CB, 0x8862, 0xE5CC, 0x886B, 0xE5CD, 0x8881, 0xE5CE, 0x887E, 0xE5CF, 0x889E, 0xE5D0, 0x8875, + 0xE5D1, 0x887D, 0xE5D2, 0x88B5, 0xE5D3, 0x8872, 0xE5D4, 0x8882, 0xE5D5, 0x8897, 0xE5D6, 0x8892, 0xE5D7, 0x88AE, 0xE5D8, 0x8899, + 0xE5D9, 0x88A2, 0xE5DA, 0x888D, 0xE5DB, 0x88A4, 0xE5DC, 0x88B0, 0xE5DD, 0x88BF, 0xE5DE, 0x88B1, 0xE5DF, 0x88C3, 0xE5E0, 0x88C4, + 0xE5E1, 0x88D4, 0xE5E2, 0x88D8, 0xE5E3, 0x88D9, 0xE5E4, 0x88DD, 0xE5E5, 0x88F9, 0xE5E6, 0x8902, 0xE5E7, 0x88FC, 0xE5E8, 0x88F4, + 0xE5E9, 0x88E8, 0xE5EA, 0x88F2, 0xE5EB, 0x8904, 0xE5EC, 0x890C, 0xE5ED, 0x890A, 0xE5EE, 0x8913, 0xE5EF, 0x8943, 0xE5F0, 0x891E, + 0xE5F1, 0x8925, 0xE5F2, 0x892A, 0xE5F3, 0x892B, 0xE5F4, 0x8941, 0xE5F5, 0x8944, 0xE5F6, 0x893B, 0xE5F7, 0x8936, 0xE5F8, 0x8938, + 0xE5F9, 0x894C, 0xE5FA, 0x891D, 0xE5FB, 0x8960, 0xE5FC, 0x895E, 0xE640, 0x8966, 0xE641, 0x8964, 0xE642, 0x896D, 0xE643, 0x896A, + 0xE644, 0x896F, 0xE645, 0x8974, 0xE646, 0x8977, 0xE647, 0x897E, 0xE648, 0x8983, 0xE649, 0x8988, 0xE64A, 0x898A, 0xE64B, 0x8993, + 0xE64C, 0x8998, 0xE64D, 0x89A1, 0xE64E, 0x89A9, 0xE64F, 0x89A6, 0xE650, 0x89AC, 0xE651, 0x89AF, 0xE652, 0x89B2, 0xE653, 0x89BA, + 0xE654, 0x89BD, 0xE655, 0x89BF, 0xE656, 0x89C0, 0xE657, 0x89DA, 0xE658, 0x89DC, 0xE659, 0x89DD, 0xE65A, 0x89E7, 0xE65B, 0x89F4, + 0xE65C, 0x89F8, 0xE65D, 0x8A03, 0xE65E, 0x8A16, 0xE65F, 0x8A10, 0xE660, 0x8A0C, 0xE661, 0x8A1B, 0xE662, 0x8A1D, 0xE663, 0x8A25, + 0xE664, 0x8A36, 0xE665, 0x8A41, 0xE666, 0x8A5B, 0xE667, 0x8A52, 0xE668, 0x8A46, 0xE669, 0x8A48, 0xE66A, 0x8A7C, 0xE66B, 0x8A6D, + 0xE66C, 0x8A6C, 0xE66D, 0x8A62, 0xE66E, 0x8A85, 0xE66F, 0x8A82, 0xE670, 0x8A84, 0xE671, 0x8AA8, 0xE672, 0x8AA1, 0xE673, 0x8A91, + 0xE674, 0x8AA5, 0xE675, 0x8AA6, 0xE676, 0x8A9A, 0xE677, 0x8AA3, 0xE678, 0x8AC4, 0xE679, 0x8ACD, 0xE67A, 0x8AC2, 0xE67B, 0x8ADA, + 0xE67C, 0x8AEB, 0xE67D, 0x8AF3, 0xE67E, 0x8AE7, 0xE680, 0x8AE4, 0xE681, 0x8AF1, 0xE682, 0x8B14, 0xE683, 0x8AE0, 0xE684, 0x8AE2, + 0xE685, 0x8AF7, 0xE686, 0x8ADE, 0xE687, 0x8ADB, 0xE688, 0x8B0C, 0xE689, 0x8B07, 0xE68A, 0x8B1A, 0xE68B, 0x8AE1, 0xE68C, 0x8B16, + 0xE68D, 0x8B10, 0xE68E, 0x8B17, 0xE68F, 0x8B20, 0xE690, 0x8B33, 0xE691, 0x97AB, 0xE692, 0x8B26, 0xE693, 0x8B2B, 0xE694, 0x8B3E, + 0xE695, 0x8B28, 0xE696, 0x8B41, 0xE697, 0x8B4C, 0xE698, 0x8B4F, 0xE699, 0x8B4E, 0xE69A, 0x8B49, 0xE69B, 0x8B56, 0xE69C, 0x8B5B, + 0xE69D, 0x8B5A, 0xE69E, 0x8B6B, 0xE69F, 0x8B5F, 0xE6A0, 0x8B6C, 0xE6A1, 0x8B6F, 0xE6A2, 0x8B74, 0xE6A3, 0x8B7D, 0xE6A4, 0x8B80, + 0xE6A5, 0x8B8C, 0xE6A6, 0x8B8E, 0xE6A7, 0x8B92, 0xE6A8, 0x8B93, 0xE6A9, 0x8B96, 0xE6AA, 0x8B99, 0xE6AB, 0x8B9A, 0xE6AC, 0x8C3A, + 0xE6AD, 0x8C41, 0xE6AE, 0x8C3F, 0xE6AF, 0x8C48, 0xE6B0, 0x8C4C, 0xE6B1, 0x8C4E, 0xE6B2, 0x8C50, 0xE6B3, 0x8C55, 0xE6B4, 0x8C62, + 0xE6B5, 0x8C6C, 0xE6B6, 0x8C78, 0xE6B7, 0x8C7A, 0xE6B8, 0x8C82, 0xE6B9, 0x8C89, 0xE6BA, 0x8C85, 0xE6BB, 0x8C8A, 0xE6BC, 0x8C8D, + 0xE6BD, 0x8C8E, 0xE6BE, 0x8C94, 0xE6BF, 0x8C7C, 0xE6C0, 0x8C98, 0xE6C1, 0x621D, 0xE6C2, 0x8CAD, 0xE6C3, 0x8CAA, 0xE6C4, 0x8CBD, + 0xE6C5, 0x8CB2, 0xE6C6, 0x8CB3, 0xE6C7, 0x8CAE, 0xE6C8, 0x8CB6, 0xE6C9, 0x8CC8, 0xE6CA, 0x8CC1, 0xE6CB, 0x8CE4, 0xE6CC, 0x8CE3, + 0xE6CD, 0x8CDA, 0xE6CE, 0x8CFD, 0xE6CF, 0x8CFA, 0xE6D0, 0x8CFB, 0xE6D1, 0x8D04, 0xE6D2, 0x8D05, 0xE6D3, 0x8D0A, 0xE6D4, 0x8D07, + 0xE6D5, 0x8D0F, 0xE6D6, 0x8D0D, 0xE6D7, 0x8D10, 0xE6D8, 0x9F4E, 0xE6D9, 0x8D13, 0xE6DA, 0x8CCD, 0xE6DB, 0x8D14, 0xE6DC, 0x8D16, + 0xE6DD, 0x8D67, 0xE6DE, 0x8D6D, 0xE6DF, 0x8D71, 0xE6E0, 0x8D73, 0xE6E1, 0x8D81, 0xE6E2, 0x8D99, 0xE6E3, 0x8DC2, 0xE6E4, 0x8DBE, + 0xE6E5, 0x8DBA, 0xE6E6, 0x8DCF, 0xE6E7, 0x8DDA, 0xE6E8, 0x8DD6, 0xE6E9, 0x8DCC, 0xE6EA, 0x8DDB, 0xE6EB, 0x8DCB, 0xE6EC, 0x8DEA, + 0xE6ED, 0x8DEB, 0xE6EE, 0x8DDF, 0xE6EF, 0x8DE3, 0xE6F0, 0x8DFC, 0xE6F1, 0x8E08, 0xE6F2, 0x8E09, 0xE6F3, 0x8DFF, 0xE6F4, 0x8E1D, + 0xE6F5, 0x8E1E, 0xE6F6, 0x8E10, 0xE6F7, 0x8E1F, 0xE6F8, 0x8E42, 0xE6F9, 0x8E35, 0xE6FA, 0x8E30, 0xE6FB, 0x8E34, 0xE6FC, 0x8E4A, + 0xE740, 0x8E47, 0xE741, 0x8E49, 0xE742, 0x8E4C, 0xE743, 0x8E50, 0xE744, 0x8E48, 0xE745, 0x8E59, 0xE746, 0x8E64, 0xE747, 0x8E60, + 0xE748, 0x8E2A, 0xE749, 0x8E63, 0xE74A, 0x8E55, 0xE74B, 0x8E76, 0xE74C, 0x8E72, 0xE74D, 0x8E7C, 0xE74E, 0x8E81, 0xE74F, 0x8E87, + 0xE750, 0x8E85, 0xE751, 0x8E84, 0xE752, 0x8E8B, 0xE753, 0x8E8A, 0xE754, 0x8E93, 0xE755, 0x8E91, 0xE756, 0x8E94, 0xE757, 0x8E99, + 0xE758, 0x8EAA, 0xE759, 0x8EA1, 0xE75A, 0x8EAC, 0xE75B, 0x8EB0, 0xE75C, 0x8EC6, 0xE75D, 0x8EB1, 0xE75E, 0x8EBE, 0xE75F, 0x8EC5, + 0xE760, 0x8EC8, 0xE761, 0x8ECB, 0xE762, 0x8EDB, 0xE763, 0x8EE3, 0xE764, 0x8EFC, 0xE765, 0x8EFB, 0xE766, 0x8EEB, 0xE767, 0x8EFE, + 0xE768, 0x8F0A, 0xE769, 0x8F05, 0xE76A, 0x8F15, 0xE76B, 0x8F12, 0xE76C, 0x8F19, 0xE76D, 0x8F13, 0xE76E, 0x8F1C, 0xE76F, 0x8F1F, + 0xE770, 0x8F1B, 0xE771, 0x8F0C, 0xE772, 0x8F26, 0xE773, 0x8F33, 0xE774, 0x8F3B, 0xE775, 0x8F39, 0xE776, 0x8F45, 0xE777, 0x8F42, + 0xE778, 0x8F3E, 0xE779, 0x8F4C, 0xE77A, 0x8F49, 0xE77B, 0x8F46, 0xE77C, 0x8F4E, 0xE77D, 0x8F57, 0xE77E, 0x8F5C, 0xE780, 0x8F62, + 0xE781, 0x8F63, 0xE782, 0x8F64, 0xE783, 0x8F9C, 0xE784, 0x8F9F, 0xE785, 0x8FA3, 0xE786, 0x8FAD, 0xE787, 0x8FAF, 0xE788, 0x8FB7, + 0xE789, 0x8FDA, 0xE78A, 0x8FE5, 0xE78B, 0x8FE2, 0xE78C, 0x8FEA, 0xE78D, 0x8FEF, 0xE78E, 0x9087, 0xE78F, 0x8FF4, 0xE790, 0x9005, + 0xE791, 0x8FF9, 0xE792, 0x8FFA, 0xE793, 0x9011, 0xE794, 0x9015, 0xE795, 0x9021, 0xE796, 0x900D, 0xE797, 0x901E, 0xE798, 0x9016, + 0xE799, 0x900B, 0xE79A, 0x9027, 0xE79B, 0x9036, 0xE79C, 0x9035, 0xE79D, 0x9039, 0xE79E, 0x8FF8, 0xE79F, 0x904F, 0xE7A0, 0x9050, + 0xE7A1, 0x9051, 0xE7A2, 0x9052, 0xE7A3, 0x900E, 0xE7A4, 0x9049, 0xE7A5, 0x903E, 0xE7A6, 0x9056, 0xE7A7, 0x9058, 0xE7A8, 0x905E, + 0xE7A9, 0x9068, 0xE7AA, 0x906F, 0xE7AB, 0x9076, 0xE7AC, 0x96A8, 0xE7AD, 0x9072, 0xE7AE, 0x9082, 0xE7AF, 0x907D, 0xE7B0, 0x9081, + 0xE7B1, 0x9080, 0xE7B2, 0x908A, 0xE7B3, 0x9089, 0xE7B4, 0x908F, 0xE7B5, 0x90A8, 0xE7B6, 0x90AF, 0xE7B7, 0x90B1, 0xE7B8, 0x90B5, + 0xE7B9, 0x90E2, 0xE7BA, 0x90E4, 0xE7BB, 0x6248, 0xE7BC, 0x90DB, 0xE7BD, 0x9102, 0xE7BE, 0x9112, 0xE7BF, 0x9119, 0xE7C0, 0x9132, + 0xE7C1, 0x9130, 0xE7C2, 0x914A, 0xE7C3, 0x9156, 0xE7C4, 0x9158, 0xE7C5, 0x9163, 0xE7C6, 0x9165, 0xE7C7, 0x9169, 0xE7C8, 0x9173, + 0xE7C9, 0x9172, 0xE7CA, 0x918B, 0xE7CB, 0x9189, 0xE7CC, 0x9182, 0xE7CD, 0x91A2, 0xE7CE, 0x91AB, 0xE7CF, 0x91AF, 0xE7D0, 0x91AA, + 0xE7D1, 0x91B5, 0xE7D2, 0x91B4, 0xE7D3, 0x91BA, 0xE7D4, 0x91C0, 0xE7D5, 0x91C1, 0xE7D6, 0x91C9, 0xE7D7, 0x91CB, 0xE7D8, 0x91D0, + 0xE7D9, 0x91D6, 0xE7DA, 0x91DF, 0xE7DB, 0x91E1, 0xE7DC, 0x91DB, 0xE7DD, 0x91FC, 0xE7DE, 0x91F5, 0xE7DF, 0x91F6, 0xE7E0, 0x921E, + 0xE7E1, 0x91FF, 0xE7E2, 0x9214, 0xE7E3, 0x922C, 0xE7E4, 0x9215, 0xE7E5, 0x9211, 0xE7E6, 0x925E, 0xE7E7, 0x9257, 0xE7E8, 0x9245, + 0xE7E9, 0x9249, 0xE7EA, 0x9264, 0xE7EB, 0x9248, 0xE7EC, 0x9295, 0xE7ED, 0x923F, 0xE7EE, 0x924B, 0xE7EF, 0x9250, 0xE7F0, 0x929C, + 0xE7F1, 0x9296, 0xE7F2, 0x9293, 0xE7F3, 0x929B, 0xE7F4, 0x925A, 0xE7F5, 0x92CF, 0xE7F6, 0x92B9, 0xE7F7, 0x92B7, 0xE7F8, 0x92E9, + 0xE7F9, 0x930F, 0xE7FA, 0x92FA, 0xE7FB, 0x9344, 0xE7FC, 0x932E, 0xE840, 0x9319, 0xE841, 0x9322, 0xE842, 0x931A, 0xE843, 0x9323, + 0xE844, 0x933A, 0xE845, 0x9335, 0xE846, 0x933B, 0xE847, 0x935C, 0xE848, 0x9360, 0xE849, 0x937C, 0xE84A, 0x936E, 0xE84B, 0x9356, + 0xE84C, 0x93B0, 0xE84D, 0x93AC, 0xE84E, 0x93AD, 0xE84F, 0x9394, 0xE850, 0x93B9, 0xE851, 0x93D6, 0xE852, 0x93D7, 0xE853, 0x93E8, + 0xE854, 0x93E5, 0xE855, 0x93D8, 0xE856, 0x93C3, 0xE857, 0x93DD, 0xE858, 0x93D0, 0xE859, 0x93C8, 0xE85A, 0x93E4, 0xE85B, 0x941A, + 0xE85C, 0x9414, 0xE85D, 0x9413, 0xE85E, 0x9403, 0xE85F, 0x9407, 0xE860, 0x9410, 0xE861, 0x9436, 0xE862, 0x942B, 0xE863, 0x9435, + 0xE864, 0x9421, 0xE865, 0x943A, 0xE866, 0x9441, 0xE867, 0x9452, 0xE868, 0x9444, 0xE869, 0x945B, 0xE86A, 0x9460, 0xE86B, 0x9462, + 0xE86C, 0x945E, 0xE86D, 0x946A, 0xE86E, 0x9229, 0xE86F, 0x9470, 0xE870, 0x9475, 0xE871, 0x9477, 0xE872, 0x947D, 0xE873, 0x945A, + 0xE874, 0x947C, 0xE875, 0x947E, 0xE876, 0x9481, 0xE877, 0x947F, 0xE878, 0x9582, 0xE879, 0x9587, 0xE87A, 0x958A, 0xE87B, 0x9594, + 0xE87C, 0x9596, 0xE87D, 0x9598, 0xE87E, 0x9599, 0xE880, 0x95A0, 0xE881, 0x95A8, 0xE882, 0x95A7, 0xE883, 0x95AD, 0xE884, 0x95BC, + 0xE885, 0x95BB, 0xE886, 0x95B9, 0xE887, 0x95BE, 0xE888, 0x95CA, 0xE889, 0x6FF6, 0xE88A, 0x95C3, 0xE88B, 0x95CD, 0xE88C, 0x95CC, + 0xE88D, 0x95D5, 0xE88E, 0x95D4, 0xE88F, 0x95D6, 0xE890, 0x95DC, 0xE891, 0x95E1, 0xE892, 0x95E5, 0xE893, 0x95E2, 0xE894, 0x9621, + 0xE895, 0x9628, 0xE896, 0x962E, 0xE897, 0x962F, 0xE898, 0x9642, 0xE899, 0x964C, 0xE89A, 0x964F, 0xE89B, 0x964B, 0xE89C, 0x9677, + 0xE89D, 0x965C, 0xE89E, 0x965E, 0xE89F, 0x965D, 0xE8A0, 0x965F, 0xE8A1, 0x9666, 0xE8A2, 0x9672, 0xE8A3, 0x966C, 0xE8A4, 0x968D, + 0xE8A5, 0x9698, 0xE8A6, 0x9695, 0xE8A7, 0x9697, 0xE8A8, 0x96AA, 0xE8A9, 0x96A7, 0xE8AA, 0x96B1, 0xE8AB, 0x96B2, 0xE8AC, 0x96B0, + 0xE8AD, 0x96B4, 0xE8AE, 0x96B6, 0xE8AF, 0x96B8, 0xE8B0, 0x96B9, 0xE8B1, 0x96CE, 0xE8B2, 0x96CB, 0xE8B3, 0x96C9, 0xE8B4, 0x96CD, + 0xE8B5, 0x894D, 0xE8B6, 0x96DC, 0xE8B7, 0x970D, 0xE8B8, 0x96D5, 0xE8B9, 0x96F9, 0xE8BA, 0x9704, 0xE8BB, 0x9706, 0xE8BC, 0x9708, + 0xE8BD, 0x9713, 0xE8BE, 0x970E, 0xE8BF, 0x9711, 0xE8C0, 0x970F, 0xE8C1, 0x9716, 0xE8C2, 0x9719, 0xE8C3, 0x9724, 0xE8C4, 0x972A, + 0xE8C5, 0x9730, 0xE8C6, 0x9739, 0xE8C7, 0x973D, 0xE8C8, 0x973E, 0xE8C9, 0x9744, 0xE8CA, 0x9746, 0xE8CB, 0x9748, 0xE8CC, 0x9742, + 0xE8CD, 0x9749, 0xE8CE, 0x975C, 0xE8CF, 0x9760, 0xE8D0, 0x9764, 0xE8D1, 0x9766, 0xE8D2, 0x9768, 0xE8D3, 0x52D2, 0xE8D4, 0x976B, + 0xE8D5, 0x9771, 0xE8D6, 0x9779, 0xE8D7, 0x9785, 0xE8D8, 0x977C, 0xE8D9, 0x9781, 0xE8DA, 0x977A, 0xE8DB, 0x9786, 0xE8DC, 0x978B, + 0xE8DD, 0x978F, 0xE8DE, 0x9790, 0xE8DF, 0x979C, 0xE8E0, 0x97A8, 0xE8E1, 0x97A6, 0xE8E2, 0x97A3, 0xE8E3, 0x97B3, 0xE8E4, 0x97B4, + 0xE8E5, 0x97C3, 0xE8E6, 0x97C6, 0xE8E7, 0x97C8, 0xE8E8, 0x97CB, 0xE8E9, 0x97DC, 0xE8EA, 0x97ED, 0xE8EB, 0x9F4F, 0xE8EC, 0x97F2, + 0xE8ED, 0x7ADF, 0xE8EE, 0x97F6, 0xE8EF, 0x97F5, 0xE8F0, 0x980F, 0xE8F1, 0x980C, 0xE8F2, 0x9838, 0xE8F3, 0x9824, 0xE8F4, 0x9821, + 0xE8F5, 0x9837, 0xE8F6, 0x983D, 0xE8F7, 0x9846, 0xE8F8, 0x984F, 0xE8F9, 0x984B, 0xE8FA, 0x986B, 0xE8FB, 0x986F, 0xE8FC, 0x9870, + 0xE940, 0x9871, 0xE941, 0x9874, 0xE942, 0x9873, 0xE943, 0x98AA, 0xE944, 0x98AF, 0xE945, 0x98B1, 0xE946, 0x98B6, 0xE947, 0x98C4, + 0xE948, 0x98C3, 0xE949, 0x98C6, 0xE94A, 0x98E9, 0xE94B, 0x98EB, 0xE94C, 0x9903, 0xE94D, 0x9909, 0xE94E, 0x9912, 0xE94F, 0x9914, + 0xE950, 0x9918, 0xE951, 0x9921, 0xE952, 0x991D, 0xE953, 0x991E, 0xE954, 0x9924, 0xE955, 0x9920, 0xE956, 0x992C, 0xE957, 0x992E, + 0xE958, 0x993D, 0xE959, 0x993E, 0xE95A, 0x9942, 0xE95B, 0x9949, 0xE95C, 0x9945, 0xE95D, 0x9950, 0xE95E, 0x994B, 0xE95F, 0x9951, + 0xE960, 0x9952, 0xE961, 0x994C, 0xE962, 0x9955, 0xE963, 0x9997, 0xE964, 0x9998, 0xE965, 0x99A5, 0xE966, 0x99AD, 0xE967, 0x99AE, + 0xE968, 0x99BC, 0xE969, 0x99DF, 0xE96A, 0x99DB, 0xE96B, 0x99DD, 0xE96C, 0x99D8, 0xE96D, 0x99D1, 0xE96E, 0x99ED, 0xE96F, 0x99EE, + 0xE970, 0x99F1, 0xE971, 0x99F2, 0xE972, 0x99FB, 0xE973, 0x99F8, 0xE974, 0x9A01, 0xE975, 0x9A0F, 0xE976, 0x9A05, 0xE977, 0x99E2, + 0xE978, 0x9A19, 0xE979, 0x9A2B, 0xE97A, 0x9A37, 0xE97B, 0x9A45, 0xE97C, 0x9A42, 0xE97D, 0x9A40, 0xE97E, 0x9A43, 0xE980, 0x9A3E, + 0xE981, 0x9A55, 0xE982, 0x9A4D, 0xE983, 0x9A5B, 0xE984, 0x9A57, 0xE985, 0x9A5F, 0xE986, 0x9A62, 0xE987, 0x9A65, 0xE988, 0x9A64, + 0xE989, 0x9A69, 0xE98A, 0x9A6B, 0xE98B, 0x9A6A, 0xE98C, 0x9AAD, 0xE98D, 0x9AB0, 0xE98E, 0x9ABC, 0xE98F, 0x9AC0, 0xE990, 0x9ACF, + 0xE991, 0x9AD1, 0xE992, 0x9AD3, 0xE993, 0x9AD4, 0xE994, 0x9ADE, 0xE995, 0x9ADF, 0xE996, 0x9AE2, 0xE997, 0x9AE3, 0xE998, 0x9AE6, + 0xE999, 0x9AEF, 0xE99A, 0x9AEB, 0xE99B, 0x9AEE, 0xE99C, 0x9AF4, 0xE99D, 0x9AF1, 0xE99E, 0x9AF7, 0xE99F, 0x9AFB, 0xE9A0, 0x9B06, + 0xE9A1, 0x9B18, 0xE9A2, 0x9B1A, 0xE9A3, 0x9B1F, 0xE9A4, 0x9B22, 0xE9A5, 0x9B23, 0xE9A6, 0x9B25, 0xE9A7, 0x9B27, 0xE9A8, 0x9B28, + 0xE9A9, 0x9B29, 0xE9AA, 0x9B2A, 0xE9AB, 0x9B2E, 0xE9AC, 0x9B2F, 0xE9AD, 0x9B32, 0xE9AE, 0x9B44, 0xE9AF, 0x9B43, 0xE9B0, 0x9B4F, + 0xE9B1, 0x9B4D, 0xE9B2, 0x9B4E, 0xE9B3, 0x9B51, 0xE9B4, 0x9B58, 0xE9B5, 0x9B74, 0xE9B6, 0x9B93, 0xE9B7, 0x9B83, 0xE9B8, 0x9B91, + 0xE9B9, 0x9B96, 0xE9BA, 0x9B97, 0xE9BB, 0x9B9F, 0xE9BC, 0x9BA0, 0xE9BD, 0x9BA8, 0xE9BE, 0x9BB4, 0xE9BF, 0x9BC0, 0xE9C0, 0x9BCA, + 0xE9C1, 0x9BB9, 0xE9C2, 0x9BC6, 0xE9C3, 0x9BCF, 0xE9C4, 0x9BD1, 0xE9C5, 0x9BD2, 0xE9C6, 0x9BE3, 0xE9C7, 0x9BE2, 0xE9C8, 0x9BE4, + 0xE9C9, 0x9BD4, 0xE9CA, 0x9BE1, 0xE9CB, 0x9C3A, 0xE9CC, 0x9BF2, 0xE9CD, 0x9BF1, 0xE9CE, 0x9BF0, 0xE9CF, 0x9C15, 0xE9D0, 0x9C14, + 0xE9D1, 0x9C09, 0xE9D2, 0x9C13, 0xE9D3, 0x9C0C, 0xE9D4, 0x9C06, 0xE9D5, 0x9C08, 0xE9D6, 0x9C12, 0xE9D7, 0x9C0A, 0xE9D8, 0x9C04, + 0xE9D9, 0x9C2E, 0xE9DA, 0x9C1B, 0xE9DB, 0x9C25, 0xE9DC, 0x9C24, 0xE9DD, 0x9C21, 0xE9DE, 0x9C30, 0xE9DF, 0x9C47, 0xE9E0, 0x9C32, + 0xE9E1, 0x9C46, 0xE9E2, 0x9C3E, 0xE9E3, 0x9C5A, 0xE9E4, 0x9C60, 0xE9E5, 0x9C67, 0xE9E6, 0x9C76, 0xE9E7, 0x9C78, 0xE9E8, 0x9CE7, + 0xE9E9, 0x9CEC, 0xE9EA, 0x9CF0, 0xE9EB, 0x9D09, 0xE9EC, 0x9D08, 0xE9ED, 0x9CEB, 0xE9EE, 0x9D03, 0xE9EF, 0x9D06, 0xE9F0, 0x9D2A, + 0xE9F1, 0x9D26, 0xE9F2, 0x9DAF, 0xE9F3, 0x9D23, 0xE9F4, 0x9D1F, 0xE9F5, 0x9D44, 0xE9F6, 0x9D15, 0xE9F7, 0x9D12, 0xE9F8, 0x9D41, + 0xE9F9, 0x9D3F, 0xE9FA, 0x9D3E, 0xE9FB, 0x9D46, 0xE9FC, 0x9D48, 0xEA40, 0x9D5D, 0xEA41, 0x9D5E, 0xEA42, 0x9D64, 0xEA43, 0x9D51, + 0xEA44, 0x9D50, 0xEA45, 0x9D59, 0xEA46, 0x9D72, 0xEA47, 0x9D89, 0xEA48, 0x9D87, 0xEA49, 0x9DAB, 0xEA4A, 0x9D6F, 0xEA4B, 0x9D7A, + 0xEA4C, 0x9D9A, 0xEA4D, 0x9DA4, 0xEA4E, 0x9DA9, 0xEA4F, 0x9DB2, 0xEA50, 0x9DC4, 0xEA51, 0x9DC1, 0xEA52, 0x9DBB, 0xEA53, 0x9DB8, + 0xEA54, 0x9DBA, 0xEA55, 0x9DC6, 0xEA56, 0x9DCF, 0xEA57, 0x9DC2, 0xEA58, 0x9DD9, 0xEA59, 0x9DD3, 0xEA5A, 0x9DF8, 0xEA5B, 0x9DE6, + 0xEA5C, 0x9DED, 0xEA5D, 0x9DEF, 0xEA5E, 0x9DFD, 0xEA5F, 0x9E1A, 0xEA60, 0x9E1B, 0xEA61, 0x9E1E, 0xEA62, 0x9E75, 0xEA63, 0x9E79, + 0xEA64, 0x9E7D, 0xEA65, 0x9E81, 0xEA66, 0x9E88, 0xEA67, 0x9E8B, 0xEA68, 0x9E8C, 0xEA69, 0x9E92, 0xEA6A, 0x9E95, 0xEA6B, 0x9E91, + 0xEA6C, 0x9E9D, 0xEA6D, 0x9EA5, 0xEA6E, 0x9EA9, 0xEA6F, 0x9EB8, 0xEA70, 0x9EAA, 0xEA71, 0x9EAD, 0xEA72, 0x9761, 0xEA73, 0x9ECC, + 0xEA74, 0x9ECE, 0xEA75, 0x9ECF, 0xEA76, 0x9ED0, 0xEA77, 0x9ED4, 0xEA78, 0x9EDC, 0xEA79, 0x9EDE, 0xEA7A, 0x9EDD, 0xEA7B, 0x9EE0, + 0xEA7C, 0x9EE5, 0xEA7D, 0x9EE8, 0xEA7E, 0x9EEF, 0xEA80, 0x9EF4, 0xEA81, 0x9EF6, 0xEA82, 0x9EF7, 0xEA83, 0x9EF9, 0xEA84, 0x9EFB, + 0xEA85, 0x9EFC, 0xEA86, 0x9EFD, 0xEA87, 0x9F07, 0xEA88, 0x9F08, 0xEA89, 0x76B7, 0xEA8A, 0x9F15, 0xEA8B, 0x9F21, 0xEA8C, 0x9F2C, + 0xEA8D, 0x9F3E, 0xEA8E, 0x9F4A, 0xEA8F, 0x9F52, 0xEA90, 0x9F54, 0xEA91, 0x9F63, 0xEA92, 0x9F5F, 0xEA93, 0x9F60, 0xEA94, 0x9F61, + 0xEA95, 0x9F66, 0xEA96, 0x9F67, 0xEA97, 0x9F6C, 0xEA98, 0x9F6A, 0xEA99, 0x9F77, 0xEA9A, 0x9F72, 0xEA9B, 0x9F76, 0xEA9C, 0x9F95, + 0xEA9D, 0x9F9C, 0xEA9E, 0x9FA0, 0xEA9F, 0x582F, 0xEAA0, 0x69C7, 0xEAA1, 0x9059, 0xEAA2, 0x7464, 0xEAA3, 0x51DC, 0xEAA4, 0x7199, + 0xFA40, 0x2170, 0xFA41, 0x2171, 0xFA42, 0x2172, 0xFA43, 0x2173, 0xFA44, 0x2174, 0xFA45, 0x2175, 0xFA46, 0x2176, 0xFA47, 0x2177, + 0xFA48, 0x2178, 0xFA49, 0x2179, 0xFA55, 0xFFE4, 0xFA56, 0xFF07, 0xFA57, 0xFF02, 0xFA5C, 0x7E8A, 0xFA5D, 0x891C, 0xFA5E, 0x9348, + 0xFA5F, 0x9288, 0xFA60, 0x84DC, 0xFA61, 0x4FC9, 0xFA62, 0x70BB, 0xFA63, 0x6631, 0xFA64, 0x68C8, 0xFA65, 0x92F9, 0xFA66, 0x66FB, + 0xFA67, 0x5F45, 0xFA68, 0x4E28, 0xFA69, 0x4EE1, 0xFA6A, 0x4EFC, 0xFA6B, 0x4F00, 0xFA6C, 0x4F03, 0xFA6D, 0x4F39, 0xFA6E, 0x4F56, + 0xFA6F, 0x4F92, 0xFA70, 0x4F8A, 0xFA71, 0x4F9A, 0xFA72, 0x4F94, 0xFA73, 0x4FCD, 0xFA74, 0x5040, 0xFA75, 0x5022, 0xFA76, 0x4FFF, + 0xFA77, 0x501E, 0xFA78, 0x5046, 0xFA79, 0x5070, 0xFA7A, 0x5042, 0xFA7B, 0x5094, 0xFA7C, 0x50F4, 0xFA7D, 0x50D8, 0xFA7E, 0x514A, + 0xFA80, 0x5164, 0xFA81, 0x519D, 0xFA82, 0x51BE, 0xFA83, 0x51EC, 0xFA84, 0x5215, 0xFA85, 0x529C, 0xFA86, 0x52A6, 0xFA87, 0x52C0, + 0xFA88, 0x52DB, 0xFA89, 0x5300, 0xFA8A, 0x5307, 0xFA8B, 0x5324, 0xFA8C, 0x5372, 0xFA8D, 0x5393, 0xFA8E, 0x53B2, 0xFA8F, 0x53DD, + 0xFA90, 0xFA0E, 0xFA91, 0x549C, 0xFA92, 0x548A, 0xFA93, 0x54A9, 0xFA94, 0x54FF, 0xFA95, 0x5586, 0xFA96, 0x5759, 0xFA97, 0x5765, + 0xFA98, 0x57AC, 0xFA99, 0x57C8, 0xFA9A, 0x57C7, 0xFA9B, 0xFA0F, 0xFA9C, 0xFA10, 0xFA9D, 0x589E, 0xFA9E, 0x58B2, 0xFA9F, 0x590B, + 0xFAA0, 0x5953, 0xFAA1, 0x595B, 0xFAA2, 0x595D, 0xFAA3, 0x5963, 0xFAA4, 0x59A4, 0xFAA5, 0x59BA, 0xFAA6, 0x5B56, 0xFAA7, 0x5BC0, + 0xFAA8, 0x752F, 0xFAA9, 0x5BD8, 0xFAAA, 0x5BEC, 0xFAAB, 0x5C1E, 0xFAAC, 0x5CA6, 0xFAAD, 0x5CBA, 0xFAAE, 0x5CF5, 0xFAAF, 0x5D27, + 0xFAB0, 0x5D53, 0xFAB1, 0xFA11, 0xFAB2, 0x5D42, 0xFAB3, 0x5D6D, 0xFAB4, 0x5DB8, 0xFAB5, 0x5DB9, 0xFAB6, 0x5DD0, 0xFAB7, 0x5F21, + 0xFAB8, 0x5F34, 0xFAB9, 0x5F67, 0xFABA, 0x5FB7, 0xFABB, 0x5FDE, 0xFABC, 0x605D, 0xFABD, 0x6085, 0xFABE, 0x608A, 0xFABF, 0x60DE, + 0xFAC0, 0x60D5, 0xFAC1, 0x6120, 0xFAC2, 0x60F2, 0xFAC3, 0x6111, 0xFAC4, 0x6137, 0xFAC5, 0x6130, 0xFAC6, 0x6198, 0xFAC7, 0x6213, + 0xFAC8, 0x62A6, 0xFAC9, 0x63F5, 0xFACA, 0x6460, 0xFACB, 0x649D, 0xFACC, 0x64CE, 0xFACD, 0x654E, 0xFACE, 0x6600, 0xFACF, 0x6615, + 0xFAD0, 0x663B, 0xFAD1, 0x6609, 0xFAD2, 0x662E, 0xFAD3, 0x661E, 0xFAD4, 0x6624, 0xFAD5, 0x6665, 0xFAD6, 0x6657, 0xFAD7, 0x6659, + 0xFAD8, 0xFA12, 0xFAD9, 0x6673, 0xFADA, 0x6699, 0xFADB, 0x66A0, 0xFADC, 0x66B2, 0xFADD, 0x66BF, 0xFADE, 0x66FA, 0xFADF, 0x670E, + 0xFAE0, 0xF929, 0xFAE1, 0x6766, 0xFAE2, 0x67BB, 0xFAE3, 0x6852, 0xFAE4, 0x67C0, 0xFAE5, 0x6801, 0xFAE6, 0x6844, 0xFAE7, 0x68CF, + 0xFAE8, 0xFA13, 0xFAE9, 0x6968, 0xFAEA, 0xFA14, 0xFAEB, 0x6998, 0xFAEC, 0x69E2, 0xFAED, 0x6A30, 0xFAEE, 0x6A6B, 0xFAEF, 0x6A46, + 0xFAF0, 0x6A73, 0xFAF1, 0x6A7E, 0xFAF2, 0x6AE2, 0xFAF3, 0x6AE4, 0xFAF4, 0x6BD6, 0xFAF5, 0x6C3F, 0xFAF6, 0x6C5C, 0xFAF7, 0x6C86, + 0xFAF8, 0x6C6F, 0xFAF9, 0x6CDA, 0xFAFA, 0x6D04, 0xFAFB, 0x6D87, 0xFAFC, 0x6D6F, 0xFB40, 0x6D96, 0xFB41, 0x6DAC, 0xFB42, 0x6DCF, + 0xFB43, 0x6DF8, 0xFB44, 0x6DF2, 0xFB45, 0x6DFC, 0xFB46, 0x6E39, 0xFB47, 0x6E5C, 0xFB48, 0x6E27, 0xFB49, 0x6E3C, 0xFB4A, 0x6EBF, + 0xFB4B, 0x6F88, 0xFB4C, 0x6FB5, 0xFB4D, 0x6FF5, 0xFB4E, 0x7005, 0xFB4F, 0x7007, 0xFB50, 0x7028, 0xFB51, 0x7085, 0xFB52, 0x70AB, + 0xFB53, 0x710F, 0xFB54, 0x7104, 0xFB55, 0x715C, 0xFB56, 0x7146, 0xFB57, 0x7147, 0xFB58, 0xFA15, 0xFB59, 0x71C1, 0xFB5A, 0x71FE, + 0xFB5B, 0x72B1, 0xFB5C, 0x72BE, 0xFB5D, 0x7324, 0xFB5E, 0xFA16, 0xFB5F, 0x7377, 0xFB60, 0x73BD, 0xFB61, 0x73C9, 0xFB62, 0x73D6, + 0xFB63, 0x73E3, 0xFB64, 0x73D2, 0xFB65, 0x7407, 0xFB66, 0x73F5, 0xFB67, 0x7426, 0xFB68, 0x742A, 0xFB69, 0x7429, 0xFB6A, 0x742E, + 0xFB6B, 0x7462, 0xFB6C, 0x7489, 0xFB6D, 0x749F, 0xFB6E, 0x7501, 0xFB6F, 0x756F, 0xFB70, 0x7682, 0xFB71, 0x769C, 0xFB72, 0x769E, + 0xFB73, 0x769B, 0xFB74, 0x76A6, 0xFB75, 0xFA17, 0xFB76, 0x7746, 0xFB77, 0x52AF, 0xFB78, 0x7821, 0xFB79, 0x784E, 0xFB7A, 0x7864, + 0xFB7B, 0x787A, 0xFB7C, 0x7930, 0xFB7D, 0xFA18, 0xFB7E, 0xFA19, 0xFB80, 0xFA1A, 0xFB81, 0x7994, 0xFB82, 0xFA1B, 0xFB83, 0x799B, + 0xFB84, 0x7AD1, 0xFB85, 0x7AE7, 0xFB86, 0xFA1C, 0xFB87, 0x7AEB, 0xFB88, 0x7B9E, 0xFB89, 0xFA1D, 0xFB8A, 0x7D48, 0xFB8B, 0x7D5C, + 0xFB8C, 0x7DB7, 0xFB8D, 0x7DA0, 0xFB8E, 0x7DD6, 0xFB8F, 0x7E52, 0xFB90, 0x7F47, 0xFB91, 0x7FA1, 0xFB92, 0xFA1E, 0xFB93, 0x8301, + 0xFB94, 0x8362, 0xFB95, 0x837F, 0xFB96, 0x83C7, 0xFB97, 0x83F6, 0xFB98, 0x8448, 0xFB99, 0x84B4, 0xFB9A, 0x8553, 0xFB9B, 0x8559, + 0xFB9C, 0x856B, 0xFB9D, 0xFA1F, 0xFB9E, 0x85B0, 0xFB9F, 0xFA20, 0xFBA0, 0xFA21, 0xFBA1, 0x8807, 0xFBA2, 0x88F5, 0xFBA3, 0x8A12, + 0xFBA4, 0x8A37, 0xFBA5, 0x8A79, 0xFBA6, 0x8AA7, 0xFBA7, 0x8ABE, 0xFBA8, 0x8ADF, 0xFBA9, 0xFA22, 0xFBAA, 0x8AF6, 0xFBAB, 0x8B53, + 0xFBAC, 0x8B7F, 0xFBAD, 0x8CF0, 0xFBAE, 0x8CF4, 0xFBAF, 0x8D12, 0xFBB0, 0x8D76, 0xFBB1, 0xFA23, 0xFBB2, 0x8ECF, 0xFBB3, 0xFA24, + 0xFBB4, 0xFA25, 0xFBB5, 0x9067, 0xFBB6, 0x90DE, 0xFBB7, 0xFA26, 0xFBB8, 0x9115, 0xFBB9, 0x9127, 0xFBBA, 0x91DA, 0xFBBB, 0x91D7, + 0xFBBC, 0x91DE, 0xFBBD, 0x91ED, 0xFBBE, 0x91EE, 0xFBBF, 0x91E4, 0xFBC0, 0x91E5, 0xFBC1, 0x9206, 0xFBC2, 0x9210, 0xFBC3, 0x920A, + 0xFBC4, 0x923A, 0xFBC5, 0x9240, 0xFBC6, 0x923C, 0xFBC7, 0x924E, 0xFBC8, 0x9259, 0xFBC9, 0x9251, 0xFBCA, 0x9239, 0xFBCB, 0x9267, + 0xFBCC, 0x92A7, 0xFBCD, 0x9277, 0xFBCE, 0x9278, 0xFBCF, 0x92E7, 0xFBD0, 0x92D7, 0xFBD1, 0x92D9, 0xFBD2, 0x92D0, 0xFBD3, 0xFA27, + 0xFBD4, 0x92D5, 0xFBD5, 0x92E0, 0xFBD6, 0x92D3, 0xFBD7, 0x9325, 0xFBD8, 0x9321, 0xFBD9, 0x92FB, 0xFBDA, 0xFA28, 0xFBDB, 0x931E, + 0xFBDC, 0x92FF, 0xFBDD, 0x931D, 0xFBDE, 0x9302, 0xFBDF, 0x9370, 0xFBE0, 0x9357, 0xFBE1, 0x93A4, 0xFBE2, 0x93C6, 0xFBE3, 0x93DE, + 0xFBE4, 0x93F8, 0xFBE5, 0x9431, 0xFBE6, 0x9445, 0xFBE7, 0x9448, 0xFBE8, 0x9592, 0xFBE9, 0xF9DC, 0xFBEA, 0xFA29, 0xFBEB, 0x969D, + 0xFBEC, 0x96AF, 0xFBED, 0x9733, 0xFBEE, 0x973B, 0xFBEF, 0x9743, 0xFBF0, 0x974D, 0xFBF1, 0x974F, 0xFBF2, 0x9751, 0xFBF3, 0x9755, + 0xFBF4, 0x9857, 0xFBF5, 0x9865, 0xFBF6, 0xFA2A, 0xFBF7, 0xFA2B, 0xFBF8, 0x9927, 0xFBF9, 0xFA2C, 0xFBFA, 0x999E, 0xFBFB, 0x9A4E, + 0xFBFC, 0x9AD9, 0xFC40, 0x9ADC, 0xFC41, 0x9B75, 0xFC42, 0x9B72, 0xFC43, 0x9B8F, 0xFC44, 0x9BB1, 0xFC45, 0x9BBB, 0xFC46, 0x9C00, + 0xFC47, 0x9D70, 0xFC48, 0x9D6B, 0xFC49, 0xFA2D, 0xFC4A, 0x9E19, 0xFC4B, 0x9ED1, 0, 0 +}; +#endif + +#if FF_CODE_PAGE == 936 || FF_CODE_PAGE == 0 /* Simplified Chinese */ +static const WCHAR uni2oem936[] = { /* Unicode --> GBK pairs */ + 0x00A4, 0xA1E8, 0x00A7, 0xA1EC, 0x00A8, 0xA1A7, 0x00B0, 0xA1E3, 0x00B1, 0xA1C0, 0x00B7, 0xA1A4, 0x00D7, 0xA1C1, 0x00E0, 0xA8A4, + 0x00E1, 0xA8A2, 0x00E8, 0xA8A8, 0x00E9, 0xA8A6, 0x00EA, 0xA8BA, 0x00EC, 0xA8AC, 0x00ED, 0xA8AA, 0x00F2, 0xA8B0, 0x00F3, 0xA8AE, + 0x00F7, 0xA1C2, 0x00F9, 0xA8B4, 0x00FA, 0xA8B2, 0x00FC, 0xA8B9, 0x0101, 0xA8A1, 0x0113, 0xA8A5, 0x011B, 0xA8A7, 0x012B, 0xA8A9, + 0x0144, 0xA8BD, 0x0148, 0xA8BE, 0x014D, 0xA8AD, 0x016B, 0xA8B1, 0x01CE, 0xA8A3, 0x01D0, 0xA8AB, 0x01D2, 0xA8AF, 0x01D4, 0xA8B3, + 0x01D6, 0xA8B5, 0x01D8, 0xA8B6, 0x01DA, 0xA8B7, 0x01DC, 0xA8B8, 0x0251, 0xA8BB, 0x0261, 0xA8C0, 0x02C7, 0xA1A6, 0x02C9, 0xA1A5, + 0x02CA, 0xA840, 0x02CB, 0xA841, 0x02D9, 0xA842, 0x0391, 0xA6A1, 0x0392, 0xA6A2, 0x0393, 0xA6A3, 0x0394, 0xA6A4, 0x0395, 0xA6A5, + 0x0396, 0xA6A6, 0x0397, 0xA6A7, 0x0398, 0xA6A8, 0x0399, 0xA6A9, 0x039A, 0xA6AA, 0x039B, 0xA6AB, 0x039C, 0xA6AC, 0x039D, 0xA6AD, + 0x039E, 0xA6AE, 0x039F, 0xA6AF, 0x03A0, 0xA6B0, 0x03A1, 0xA6B1, 0x03A3, 0xA6B2, 0x03A4, 0xA6B3, 0x03A5, 0xA6B4, 0x03A6, 0xA6B5, + 0x03A7, 0xA6B6, 0x03A8, 0xA6B7, 0x03A9, 0xA6B8, 0x03B1, 0xA6C1, 0x03B2, 0xA6C2, 0x03B3, 0xA6C3, 0x03B4, 0xA6C4, 0x03B5, 0xA6C5, + 0x03B6, 0xA6C6, 0x03B7, 0xA6C7, 0x03B8, 0xA6C8, 0x03B9, 0xA6C9, 0x03BA, 0xA6CA, 0x03BB, 0xA6CB, 0x03BC, 0xA6CC, 0x03BD, 0xA6CD, + 0x03BE, 0xA6CE, 0x03BF, 0xA6CF, 0x03C0, 0xA6D0, 0x03C1, 0xA6D1, 0x03C3, 0xA6D2, 0x03C4, 0xA6D3, 0x03C5, 0xA6D4, 0x03C6, 0xA6D5, + 0x03C7, 0xA6D6, 0x03C8, 0xA6D7, 0x03C9, 0xA6D8, 0x0401, 0xA7A7, 0x0410, 0xA7A1, 0x0411, 0xA7A2, 0x0412, 0xA7A3, 0x0413, 0xA7A4, + 0x0414, 0xA7A5, 0x0415, 0xA7A6, 0x0416, 0xA7A8, 0x0417, 0xA7A9, 0x0418, 0xA7AA, 0x0419, 0xA7AB, 0x041A, 0xA7AC, 0x041B, 0xA7AD, + 0x041C, 0xA7AE, 0x041D, 0xA7AF, 0x041E, 0xA7B0, 0x041F, 0xA7B1, 0x0420, 0xA7B2, 0x0421, 0xA7B3, 0x0422, 0xA7B4, 0x0423, 0xA7B5, + 0x0424, 0xA7B6, 0x0425, 0xA7B7, 0x0426, 0xA7B8, 0x0427, 0xA7B9, 0x0428, 0xA7BA, 0x0429, 0xA7BB, 0x042A, 0xA7BC, 0x042B, 0xA7BD, + 0x042C, 0xA7BE, 0x042D, 0xA7BF, 0x042E, 0xA7C0, 0x042F, 0xA7C1, 0x0430, 0xA7D1, 0x0431, 0xA7D2, 0x0432, 0xA7D3, 0x0433, 0xA7D4, + 0x0434, 0xA7D5, 0x0435, 0xA7D6, 0x0436, 0xA7D8, 0x0437, 0xA7D9, 0x0438, 0xA7DA, 0x0439, 0xA7DB, 0x043A, 0xA7DC, 0x043B, 0xA7DD, + 0x043C, 0xA7DE, 0x043D, 0xA7DF, 0x043E, 0xA7E0, 0x043F, 0xA7E1, 0x0440, 0xA7E2, 0x0441, 0xA7E3, 0x0442, 0xA7E4, 0x0443, 0xA7E5, + 0x0444, 0xA7E6, 0x0445, 0xA7E7, 0x0446, 0xA7E8, 0x0447, 0xA7E9, 0x0448, 0xA7EA, 0x0449, 0xA7EB, 0x044A, 0xA7EC, 0x044B, 0xA7ED, + 0x044C, 0xA7EE, 0x044D, 0xA7EF, 0x044E, 0xA7F0, 0x044F, 0xA7F1, 0x0451, 0xA7D7, 0x2010, 0xA95C, 0x2013, 0xA843, 0x2014, 0xA1AA, + 0x2015, 0xA844, 0x2016, 0xA1AC, 0x2018, 0xA1AE, 0x2019, 0xA1AF, 0x201C, 0xA1B0, 0x201D, 0xA1B1, 0x2025, 0xA845, 0x2026, 0xA1AD, + 0x2030, 0xA1EB, 0x2032, 0xA1E4, 0x2033, 0xA1E5, 0x2035, 0xA846, 0x203B, 0xA1F9, 0x20AC, 0x0080, 0x2103, 0xA1E6, 0x2105, 0xA847, + 0x2109, 0xA848, 0x2116, 0xA1ED, 0x2121, 0xA959, 0x2160, 0xA2F1, 0x2161, 0xA2F2, 0x2162, 0xA2F3, 0x2163, 0xA2F4, 0x2164, 0xA2F5, + 0x2165, 0xA2F6, 0x2166, 0xA2F7, 0x2167, 0xA2F8, 0x2168, 0xA2F9, 0x2169, 0xA2FA, 0x216A, 0xA2FB, 0x216B, 0xA2FC, 0x2170, 0xA2A1, + 0x2171, 0xA2A2, 0x2172, 0xA2A3, 0x2173, 0xA2A4, 0x2174, 0xA2A5, 0x2175, 0xA2A6, 0x2176, 0xA2A7, 0x2177, 0xA2A8, 0x2178, 0xA2A9, + 0x2179, 0xA2AA, 0x2190, 0xA1FB, 0x2191, 0xA1FC, 0x2192, 0xA1FA, 0x2193, 0xA1FD, 0x2196, 0xA849, 0x2197, 0xA84A, 0x2198, 0xA84B, + 0x2199, 0xA84C, 0x2208, 0xA1CA, 0x220F, 0xA1C7, 0x2211, 0xA1C6, 0x2215, 0xA84D, 0x221A, 0xA1CC, 0x221D, 0xA1D8, 0x221E, 0xA1DE, + 0x221F, 0xA84E, 0x2220, 0xA1CF, 0x2223, 0xA84F, 0x2225, 0xA1CE, 0x2227, 0xA1C4, 0x2228, 0xA1C5, 0x2229, 0xA1C9, 0x222A, 0xA1C8, + 0x222B, 0xA1D2, 0x222E, 0xA1D3, 0x2234, 0xA1E0, 0x2235, 0xA1DF, 0x2236, 0xA1C3, 0x2237, 0xA1CB, 0x223D, 0xA1D7, 0x2248, 0xA1D6, + 0x224C, 0xA1D5, 0x2252, 0xA850, 0x2260, 0xA1D9, 0x2261, 0xA1D4, 0x2264, 0xA1DC, 0x2265, 0xA1DD, 0x2266, 0xA851, 0x2267, 0xA852, + 0x226E, 0xA1DA, 0x226F, 0xA1DB, 0x2295, 0xA892, 0x2299, 0xA1D1, 0x22A5, 0xA1CD, 0x22BF, 0xA853, 0x2312, 0xA1D0, 0x2460, 0xA2D9, + 0x2461, 0xA2DA, 0x2462, 0xA2DB, 0x2463, 0xA2DC, 0x2464, 0xA2DD, 0x2465, 0xA2DE, 0x2466, 0xA2DF, 0x2467, 0xA2E0, 0x2468, 0xA2E1, + 0x2469, 0xA2E2, 0x2474, 0xA2C5, 0x2475, 0xA2C6, 0x2476, 0xA2C7, 0x2477, 0xA2C8, 0x2478, 0xA2C9, 0x2479, 0xA2CA, 0x247A, 0xA2CB, + 0x247B, 0xA2CC, 0x247C, 0xA2CD, 0x247D, 0xA2CE, 0x247E, 0xA2CF, 0x247F, 0xA2D0, 0x2480, 0xA2D1, 0x2481, 0xA2D2, 0x2482, 0xA2D3, + 0x2483, 0xA2D4, 0x2484, 0xA2D5, 0x2485, 0xA2D6, 0x2486, 0xA2D7, 0x2487, 0xA2D8, 0x2488, 0xA2B1, 0x2489, 0xA2B2, 0x248A, 0xA2B3, + 0x248B, 0xA2B4, 0x248C, 0xA2B5, 0x248D, 0xA2B6, 0x248E, 0xA2B7, 0x248F, 0xA2B8, 0x2490, 0xA2B9, 0x2491, 0xA2BA, 0x2492, 0xA2BB, + 0x2493, 0xA2BC, 0x2494, 0xA2BD, 0x2495, 0xA2BE, 0x2496, 0xA2BF, 0x2497, 0xA2C0, 0x2498, 0xA2C1, 0x2499, 0xA2C2, 0x249A, 0xA2C3, + 0x249B, 0xA2C4, 0x2500, 0xA9A4, 0x2501, 0xA9A5, 0x2502, 0xA9A6, 0x2503, 0xA9A7, 0x2504, 0xA9A8, 0x2505, 0xA9A9, 0x2506, 0xA9AA, + 0x2507, 0xA9AB, 0x2508, 0xA9AC, 0x2509, 0xA9AD, 0x250A, 0xA9AE, 0x250B, 0xA9AF, 0x250C, 0xA9B0, 0x250D, 0xA9B1, 0x250E, 0xA9B2, + 0x250F, 0xA9B3, 0x2510, 0xA9B4, 0x2511, 0xA9B5, 0x2512, 0xA9B6, 0x2513, 0xA9B7, 0x2514, 0xA9B8, 0x2515, 0xA9B9, 0x2516, 0xA9BA, + 0x2517, 0xA9BB, 0x2518, 0xA9BC, 0x2519, 0xA9BD, 0x251A, 0xA9BE, 0x251B, 0xA9BF, 0x251C, 0xA9C0, 0x251D, 0xA9C1, 0x251E, 0xA9C2, + 0x251F, 0xA9C3, 0x2520, 0xA9C4, 0x2521, 0xA9C5, 0x2522, 0xA9C6, 0x2523, 0xA9C7, 0x2524, 0xA9C8, 0x2525, 0xA9C9, 0x2526, 0xA9CA, + 0x2527, 0xA9CB, 0x2528, 0xA9CC, 0x2529, 0xA9CD, 0x252A, 0xA9CE, 0x252B, 0xA9CF, 0x252C, 0xA9D0, 0x252D, 0xA9D1, 0x252E, 0xA9D2, + 0x252F, 0xA9D3, 0x2530, 0xA9D4, 0x2531, 0xA9D5, 0x2532, 0xA9D6, 0x2533, 0xA9D7, 0x2534, 0xA9D8, 0x2535, 0xA9D9, 0x2536, 0xA9DA, + 0x2537, 0xA9DB, 0x2538, 0xA9DC, 0x2539, 0xA9DD, 0x253A, 0xA9DE, 0x253B, 0xA9DF, 0x253C, 0xA9E0, 0x253D, 0xA9E1, 0x253E, 0xA9E2, + 0x253F, 0xA9E3, 0x2540, 0xA9E4, 0x2541, 0xA9E5, 0x2542, 0xA9E6, 0x2543, 0xA9E7, 0x2544, 0xA9E8, 0x2545, 0xA9E9, 0x2546, 0xA9EA, + 0x2547, 0xA9EB, 0x2548, 0xA9EC, 0x2549, 0xA9ED, 0x254A, 0xA9EE, 0x254B, 0xA9EF, 0x2550, 0xA854, 0x2551, 0xA855, 0x2552, 0xA856, + 0x2553, 0xA857, 0x2554, 0xA858, 0x2555, 0xA859, 0x2556, 0xA85A, 0x2557, 0xA85B, 0x2558, 0xA85C, 0x2559, 0xA85D, 0x255A, 0xA85E, + 0x255B, 0xA85F, 0x255C, 0xA860, 0x255D, 0xA861, 0x255E, 0xA862, 0x255F, 0xA863, 0x2560, 0xA864, 0x2561, 0xA865, 0x2562, 0xA866, + 0x2563, 0xA867, 0x2564, 0xA868, 0x2565, 0xA869, 0x2566, 0xA86A, 0x2567, 0xA86B, 0x2568, 0xA86C, 0x2569, 0xA86D, 0x256A, 0xA86E, + 0x256B, 0xA86F, 0x256C, 0xA870, 0x256D, 0xA871, 0x256E, 0xA872, 0x256F, 0xA873, 0x2570, 0xA874, 0x2571, 0xA875, 0x2572, 0xA876, + 0x2573, 0xA877, 0x2581, 0xA878, 0x2582, 0xA879, 0x2583, 0xA87A, 0x2584, 0xA87B, 0x2585, 0xA87C, 0x2586, 0xA87D, 0x2587, 0xA87E, + 0x2588, 0xA880, 0x2589, 0xA881, 0x258A, 0xA882, 0x258B, 0xA883, 0x258C, 0xA884, 0x258D, 0xA885, 0x258E, 0xA886, 0x258F, 0xA887, + 0x2593, 0xA888, 0x2594, 0xA889, 0x2595, 0xA88A, 0x25A0, 0xA1F6, 0x25A1, 0xA1F5, 0x25B2, 0xA1F8, 0x25B3, 0xA1F7, 0x25BC, 0xA88B, + 0x25BD, 0xA88C, 0x25C6, 0xA1F4, 0x25C7, 0xA1F3, 0x25CB, 0xA1F0, 0x25CE, 0xA1F2, 0x25CF, 0xA1F1, 0x25E2, 0xA88D, 0x25E3, 0xA88E, + 0x25E4, 0xA88F, 0x25E5, 0xA890, 0x2605, 0xA1EF, 0x2606, 0xA1EE, 0x2609, 0xA891, 0x2640, 0xA1E2, 0x2642, 0xA1E1, 0x3000, 0xA1A1, + 0x3001, 0xA1A2, 0x3002, 0xA1A3, 0x3003, 0xA1A8, 0x3005, 0xA1A9, 0x3006, 0xA965, 0x3007, 0xA996, 0x3008, 0xA1B4, 0x3009, 0xA1B5, + 0x300A, 0xA1B6, 0x300B, 0xA1B7, 0x300C, 0xA1B8, 0x300D, 0xA1B9, 0x300E, 0xA1BA, 0x300F, 0xA1BB, 0x3010, 0xA1BE, 0x3011, 0xA1BF, + 0x3012, 0xA893, 0x3013, 0xA1FE, 0x3014, 0xA1B2, 0x3015, 0xA1B3, 0x3016, 0xA1BC, 0x3017, 0xA1BD, 0x301D, 0xA894, 0x301E, 0xA895, + 0x3021, 0xA940, 0x3022, 0xA941, 0x3023, 0xA942, 0x3024, 0xA943, 0x3025, 0xA944, 0x3026, 0xA945, 0x3027, 0xA946, 0x3028, 0xA947, + 0x3029, 0xA948, 0x3041, 0xA4A1, 0x3042, 0xA4A2, 0x3043, 0xA4A3, 0x3044, 0xA4A4, 0x3045, 0xA4A5, 0x3046, 0xA4A6, 0x3047, 0xA4A7, + 0x3048, 0xA4A8, 0x3049, 0xA4A9, 0x304A, 0xA4AA, 0x304B, 0xA4AB, 0x304C, 0xA4AC, 0x304D, 0xA4AD, 0x304E, 0xA4AE, 0x304F, 0xA4AF, + 0x3050, 0xA4B0, 0x3051, 0xA4B1, 0x3052, 0xA4B2, 0x3053, 0xA4B3, 0x3054, 0xA4B4, 0x3055, 0xA4B5, 0x3056, 0xA4B6, 0x3057, 0xA4B7, + 0x3058, 0xA4B8, 0x3059, 0xA4B9, 0x305A, 0xA4BA, 0x305B, 0xA4BB, 0x305C, 0xA4BC, 0x305D, 0xA4BD, 0x305E, 0xA4BE, 0x305F, 0xA4BF, + 0x3060, 0xA4C0, 0x3061, 0xA4C1, 0x3062, 0xA4C2, 0x3063, 0xA4C3, 0x3064, 0xA4C4, 0x3065, 0xA4C5, 0x3066, 0xA4C6, 0x3067, 0xA4C7, + 0x3068, 0xA4C8, 0x3069, 0xA4C9, 0x306A, 0xA4CA, 0x306B, 0xA4CB, 0x306C, 0xA4CC, 0x306D, 0xA4CD, 0x306E, 0xA4CE, 0x306F, 0xA4CF, + 0x3070, 0xA4D0, 0x3071, 0xA4D1, 0x3072, 0xA4D2, 0x3073, 0xA4D3, 0x3074, 0xA4D4, 0x3075, 0xA4D5, 0x3076, 0xA4D6, 0x3077, 0xA4D7, + 0x3078, 0xA4D8, 0x3079, 0xA4D9, 0x307A, 0xA4DA, 0x307B, 0xA4DB, 0x307C, 0xA4DC, 0x307D, 0xA4DD, 0x307E, 0xA4DE, 0x307F, 0xA4DF, + 0x3080, 0xA4E0, 0x3081, 0xA4E1, 0x3082, 0xA4E2, 0x3083, 0xA4E3, 0x3084, 0xA4E4, 0x3085, 0xA4E5, 0x3086, 0xA4E6, 0x3087, 0xA4E7, + 0x3088, 0xA4E8, 0x3089, 0xA4E9, 0x308A, 0xA4EA, 0x308B, 0xA4EB, 0x308C, 0xA4EC, 0x308D, 0xA4ED, 0x308E, 0xA4EE, 0x308F, 0xA4EF, + 0x3090, 0xA4F0, 0x3091, 0xA4F1, 0x3092, 0xA4F2, 0x3093, 0xA4F3, 0x309B, 0xA961, 0x309C, 0xA962, 0x309D, 0xA966, 0x309E, 0xA967, + 0x30A1, 0xA5A1, 0x30A2, 0xA5A2, 0x30A3, 0xA5A3, 0x30A4, 0xA5A4, 0x30A5, 0xA5A5, 0x30A6, 0xA5A6, 0x30A7, 0xA5A7, 0x30A8, 0xA5A8, + 0x30A9, 0xA5A9, 0x30AA, 0xA5AA, 0x30AB, 0xA5AB, 0x30AC, 0xA5AC, 0x30AD, 0xA5AD, 0x30AE, 0xA5AE, 0x30AF, 0xA5AF, 0x30B0, 0xA5B0, + 0x30B1, 0xA5B1, 0x30B2, 0xA5B2, 0x30B3, 0xA5B3, 0x30B4, 0xA5B4, 0x30B5, 0xA5B5, 0x30B6, 0xA5B6, 0x30B7, 0xA5B7, 0x30B8, 0xA5B8, + 0x30B9, 0xA5B9, 0x30BA, 0xA5BA, 0x30BB, 0xA5BB, 0x30BC, 0xA5BC, 0x30BD, 0xA5BD, 0x30BE, 0xA5BE, 0x30BF, 0xA5BF, 0x30C0, 0xA5C0, + 0x30C1, 0xA5C1, 0x30C2, 0xA5C2, 0x30C3, 0xA5C3, 0x30C4, 0xA5C4, 0x30C5, 0xA5C5, 0x30C6, 0xA5C6, 0x30C7, 0xA5C7, 0x30C8, 0xA5C8, + 0x30C9, 0xA5C9, 0x30CA, 0xA5CA, 0x30CB, 0xA5CB, 0x30CC, 0xA5CC, 0x30CD, 0xA5CD, 0x30CE, 0xA5CE, 0x30CF, 0xA5CF, 0x30D0, 0xA5D0, + 0x30D1, 0xA5D1, 0x30D2, 0xA5D2, 0x30D3, 0xA5D3, 0x30D4, 0xA5D4, 0x30D5, 0xA5D5, 0x30D6, 0xA5D6, 0x30D7, 0xA5D7, 0x30D8, 0xA5D8, + 0x30D9, 0xA5D9, 0x30DA, 0xA5DA, 0x30DB, 0xA5DB, 0x30DC, 0xA5DC, 0x30DD, 0xA5DD, 0x30DE, 0xA5DE, 0x30DF, 0xA5DF, 0x30E0, 0xA5E0, + 0x30E1, 0xA5E1, 0x30E2, 0xA5E2, 0x30E3, 0xA5E3, 0x30E4, 0xA5E4, 0x30E5, 0xA5E5, 0x30E6, 0xA5E6, 0x30E7, 0xA5E7, 0x30E8, 0xA5E8, + 0x30E9, 0xA5E9, 0x30EA, 0xA5EA, 0x30EB, 0xA5EB, 0x30EC, 0xA5EC, 0x30ED, 0xA5ED, 0x30EE, 0xA5EE, 0x30EF, 0xA5EF, 0x30F0, 0xA5F0, + 0x30F1, 0xA5F1, 0x30F2, 0xA5F2, 0x30F3, 0xA5F3, 0x30F4, 0xA5F4, 0x30F5, 0xA5F5, 0x30F6, 0xA5F6, 0x30FC, 0xA960, 0x30FD, 0xA963, + 0x30FE, 0xA964, 0x3105, 0xA8C5, 0x3106, 0xA8C6, 0x3107, 0xA8C7, 0x3108, 0xA8C8, 0x3109, 0xA8C9, 0x310A, 0xA8CA, 0x310B, 0xA8CB, + 0x310C, 0xA8CC, 0x310D, 0xA8CD, 0x310E, 0xA8CE, 0x310F, 0xA8CF, 0x3110, 0xA8D0, 0x3111, 0xA8D1, 0x3112, 0xA8D2, 0x3113, 0xA8D3, + 0x3114, 0xA8D4, 0x3115, 0xA8D5, 0x3116, 0xA8D6, 0x3117, 0xA8D7, 0x3118, 0xA8D8, 0x3119, 0xA8D9, 0x311A, 0xA8DA, 0x311B, 0xA8DB, + 0x311C, 0xA8DC, 0x311D, 0xA8DD, 0x311E, 0xA8DE, 0x311F, 0xA8DF, 0x3120, 0xA8E0, 0x3121, 0xA8E1, 0x3122, 0xA8E2, 0x3123, 0xA8E3, + 0x3124, 0xA8E4, 0x3125, 0xA8E5, 0x3126, 0xA8E6, 0x3127, 0xA8E7, 0x3128, 0xA8E8, 0x3129, 0xA8E9, 0x3220, 0xA2E5, 0x3221, 0xA2E6, + 0x3222, 0xA2E7, 0x3223, 0xA2E8, 0x3224, 0xA2E9, 0x3225, 0xA2EA, 0x3226, 0xA2EB, 0x3227, 0xA2EC, 0x3228, 0xA2ED, 0x3229, 0xA2EE, + 0x3231, 0xA95A, 0x32A3, 0xA949, 0x338E, 0xA94A, 0x338F, 0xA94B, 0x339C, 0xA94C, 0x339D, 0xA94D, 0x339E, 0xA94E, 0x33A1, 0xA94F, + 0x33C4, 0xA950, 0x33CE, 0xA951, 0x33D1, 0xA952, 0x33D2, 0xA953, 0x33D5, 0xA954, 0x4E00, 0xD2BB, 0x4E01, 0xB6A1, 0x4E02, 0x8140, + 0x4E03, 0xC6DF, 0x4E04, 0x8141, 0x4E05, 0x8142, 0x4E06, 0x8143, 0x4E07, 0xCDF2, 0x4E08, 0xD5C9, 0x4E09, 0xC8FD, 0x4E0A, 0xC9CF, + 0x4E0B, 0xCFC2, 0x4E0C, 0xD8A2, 0x4E0D, 0xB2BB, 0x4E0E, 0xD3EB, 0x4E0F, 0x8144, 0x4E10, 0xD8A4, 0x4E11, 0xB3F3, 0x4E12, 0x8145, + 0x4E13, 0xD7A8, 0x4E14, 0xC7D2, 0x4E15, 0xD8A7, 0x4E16, 0xCAC0, 0x4E17, 0x8146, 0x4E18, 0xC7F0, 0x4E19, 0xB1FB, 0x4E1A, 0xD2B5, + 0x4E1B, 0xB4D4, 0x4E1C, 0xB6AB, 0x4E1D, 0xCBBF, 0x4E1E, 0xD8A9, 0x4E1F, 0x8147, 0x4E20, 0x8148, 0x4E21, 0x8149, 0x4E22, 0xB6AA, + 0x4E23, 0x814A, 0x4E24, 0xC1BD, 0x4E25, 0xD1CF, 0x4E26, 0x814B, 0x4E27, 0xC9A5, 0x4E28, 0xD8AD, 0x4E29, 0x814C, 0x4E2A, 0xB8F6, + 0x4E2B, 0xD1BE, 0x4E2C, 0xE3DC, 0x4E2D, 0xD6D0, 0x4E2E, 0x814D, 0x4E2F, 0x814E, 0x4E30, 0xB7E1, 0x4E31, 0x814F, 0x4E32, 0xB4AE, + 0x4E33, 0x8150, 0x4E34, 0xC1D9, 0x4E35, 0x8151, 0x4E36, 0xD8BC, 0x4E37, 0x8152, 0x4E38, 0xCDE8, 0x4E39, 0xB5A4, 0x4E3A, 0xCEAA, + 0x4E3B, 0xD6F7, 0x4E3C, 0x8153, 0x4E3D, 0xC0F6, 0x4E3E, 0xBED9, 0x4E3F, 0xD8AF, 0x4E40, 0x8154, 0x4E41, 0x8155, 0x4E42, 0x8156, + 0x4E43, 0xC4CB, 0x4E44, 0x8157, 0x4E45, 0xBEC3, 0x4E46, 0x8158, 0x4E47, 0xD8B1, 0x4E48, 0xC3B4, 0x4E49, 0xD2E5, 0x4E4A, 0x8159, + 0x4E4B, 0xD6AE, 0x4E4C, 0xCEDA, 0x4E4D, 0xD5A7, 0x4E4E, 0xBAF5, 0x4E4F, 0xB7A6, 0x4E50, 0xC0D6, 0x4E51, 0x815A, 0x4E52, 0xC6B9, + 0x4E53, 0xC5D2, 0x4E54, 0xC7C7, 0x4E55, 0x815B, 0x4E56, 0xB9D4, 0x4E57, 0x815C, 0x4E58, 0xB3CB, 0x4E59, 0xD2D2, 0x4E5A, 0x815D, + 0x4E5B, 0x815E, 0x4E5C, 0xD8BF, 0x4E5D, 0xBEC5, 0x4E5E, 0xC6F2, 0x4E5F, 0xD2B2, 0x4E60, 0xCFB0, 0x4E61, 0xCFE7, 0x4E62, 0x815F, + 0x4E63, 0x8160, 0x4E64, 0x8161, 0x4E65, 0x8162, 0x4E66, 0xCAE9, 0x4E67, 0x8163, 0x4E68, 0x8164, 0x4E69, 0xD8C0, 0x4E6A, 0x8165, + 0x4E6B, 0x8166, 0x4E6C, 0x8167, 0x4E6D, 0x8168, 0x4E6E, 0x8169, 0x4E6F, 0x816A, 0x4E70, 0xC2F2, 0x4E71, 0xC2D2, 0x4E72, 0x816B, + 0x4E73, 0xC8E9, 0x4E74, 0x816C, 0x4E75, 0x816D, 0x4E76, 0x816E, 0x4E77, 0x816F, 0x4E78, 0x8170, 0x4E79, 0x8171, 0x4E7A, 0x8172, + 0x4E7B, 0x8173, 0x4E7C, 0x8174, 0x4E7D, 0x8175, 0x4E7E, 0xC7AC, 0x4E7F, 0x8176, 0x4E80, 0x8177, 0x4E81, 0x8178, 0x4E82, 0x8179, + 0x4E83, 0x817A, 0x4E84, 0x817B, 0x4E85, 0x817C, 0x4E86, 0xC1CB, 0x4E87, 0x817D, 0x4E88, 0xD3E8, 0x4E89, 0xD5F9, 0x4E8A, 0x817E, + 0x4E8B, 0xCAC2, 0x4E8C, 0xB6FE, 0x4E8D, 0xD8A1, 0x4E8E, 0xD3DA, 0x4E8F, 0xBFF7, 0x4E90, 0x8180, 0x4E91, 0xD4C6, 0x4E92, 0xBBA5, + 0x4E93, 0xD8C1, 0x4E94, 0xCEE5, 0x4E95, 0xBEAE, 0x4E96, 0x8181, 0x4E97, 0x8182, 0x4E98, 0xD8A8, 0x4E99, 0x8183, 0x4E9A, 0xD1C7, + 0x4E9B, 0xD0A9, 0x4E9C, 0x8184, 0x4E9D, 0x8185, 0x4E9E, 0x8186, 0x4E9F, 0xD8BD, 0x4EA0, 0xD9EF, 0x4EA1, 0xCDF6, 0x4EA2, 0xBFBA, + 0x4EA3, 0x8187, 0x4EA4, 0xBDBB, 0x4EA5, 0xBAA5, 0x4EA6, 0xD2E0, 0x4EA7, 0xB2FA, 0x4EA8, 0xBAE0, 0x4EA9, 0xC4B6, 0x4EAA, 0x8188, + 0x4EAB, 0xCFED, 0x4EAC, 0xBEA9, 0x4EAD, 0xCDA4, 0x4EAE, 0xC1C1, 0x4EAF, 0x8189, 0x4EB0, 0x818A, 0x4EB1, 0x818B, 0x4EB2, 0xC7D7, + 0x4EB3, 0xD9F1, 0x4EB4, 0x818C, 0x4EB5, 0xD9F4, 0x4EB6, 0x818D, 0x4EB7, 0x818E, 0x4EB8, 0x818F, 0x4EB9, 0x8190, 0x4EBA, 0xC8CB, + 0x4EBB, 0xD8E9, 0x4EBC, 0x8191, 0x4EBD, 0x8192, 0x4EBE, 0x8193, 0x4EBF, 0xD2DA, 0x4EC0, 0xCAB2, 0x4EC1, 0xC8CA, 0x4EC2, 0xD8EC, + 0x4EC3, 0xD8EA, 0x4EC4, 0xD8C6, 0x4EC5, 0xBDF6, 0x4EC6, 0xC6CD, 0x4EC7, 0xB3F0, 0x4EC8, 0x8194, 0x4EC9, 0xD8EB, 0x4ECA, 0xBDF1, + 0x4ECB, 0xBDE9, 0x4ECC, 0x8195, 0x4ECD, 0xC8D4, 0x4ECE, 0xB4D3, 0x4ECF, 0x8196, 0x4ED0, 0x8197, 0x4ED1, 0xC2D8, 0x4ED2, 0x8198, + 0x4ED3, 0xB2D6, 0x4ED4, 0xD7D0, 0x4ED5, 0xCACB, 0x4ED6, 0xCBFB, 0x4ED7, 0xD5CC, 0x4ED8, 0xB8B6, 0x4ED9, 0xCFC9, 0x4EDA, 0x8199, + 0x4EDB, 0x819A, 0x4EDC, 0x819B, 0x4EDD, 0xD9DA, 0x4EDE, 0xD8F0, 0x4EDF, 0xC7AA, 0x4EE0, 0x819C, 0x4EE1, 0xD8EE, 0x4EE2, 0x819D, + 0x4EE3, 0xB4FA, 0x4EE4, 0xC1EE, 0x4EE5, 0xD2D4, 0x4EE6, 0x819E, 0x4EE7, 0x819F, 0x4EE8, 0xD8ED, 0x4EE9, 0x81A0, 0x4EEA, 0xD2C7, + 0x4EEB, 0xD8EF, 0x4EEC, 0xC3C7, 0x4EED, 0x81A1, 0x4EEE, 0x81A2, 0x4EEF, 0x81A3, 0x4EF0, 0xD1F6, 0x4EF1, 0x81A4, 0x4EF2, 0xD6D9, + 0x4EF3, 0xD8F2, 0x4EF4, 0x81A5, 0x4EF5, 0xD8F5, 0x4EF6, 0xBCFE, 0x4EF7, 0xBCDB, 0x4EF8, 0x81A6, 0x4EF9, 0x81A7, 0x4EFA, 0x81A8, + 0x4EFB, 0xC8CE, 0x4EFC, 0x81A9, 0x4EFD, 0xB7DD, 0x4EFE, 0x81AA, 0x4EFF, 0xB7C2, 0x4F00, 0x81AB, 0x4F01, 0xC6F3, 0x4F02, 0x81AC, + 0x4F03, 0x81AD, 0x4F04, 0x81AE, 0x4F05, 0x81AF, 0x4F06, 0x81B0, 0x4F07, 0x81B1, 0x4F08, 0x81B2, 0x4F09, 0xD8F8, 0x4F0A, 0xD2C1, + 0x4F0B, 0x81B3, 0x4F0C, 0x81B4, 0x4F0D, 0xCEE9, 0x4F0E, 0xBCBF, 0x4F0F, 0xB7FC, 0x4F10, 0xB7A5, 0x4F11, 0xD0DD, 0x4F12, 0x81B5, + 0x4F13, 0x81B6, 0x4F14, 0x81B7, 0x4F15, 0x81B8, 0x4F16, 0x81B9, 0x4F17, 0xD6DA, 0x4F18, 0xD3C5, 0x4F19, 0xBBEF, 0x4F1A, 0xBBE1, + 0x4F1B, 0xD8F1, 0x4F1C, 0x81BA, 0x4F1D, 0x81BB, 0x4F1E, 0xC9A1, 0x4F1F, 0xCEB0, 0x4F20, 0xB4AB, 0x4F21, 0x81BC, 0x4F22, 0xD8F3, + 0x4F23, 0x81BD, 0x4F24, 0xC9CB, 0x4F25, 0xD8F6, 0x4F26, 0xC2D7, 0x4F27, 0xD8F7, 0x4F28, 0x81BE, 0x4F29, 0x81BF, 0x4F2A, 0xCEB1, + 0x4F2B, 0xD8F9, 0x4F2C, 0x81C0, 0x4F2D, 0x81C1, 0x4F2E, 0x81C2, 0x4F2F, 0xB2AE, 0x4F30, 0xB9C0, 0x4F31, 0x81C3, 0x4F32, 0xD9A3, + 0x4F33, 0x81C4, 0x4F34, 0xB0E9, 0x4F35, 0x81C5, 0x4F36, 0xC1E6, 0x4F37, 0x81C6, 0x4F38, 0xC9EC, 0x4F39, 0x81C7, 0x4F3A, 0xCBC5, + 0x4F3B, 0x81C8, 0x4F3C, 0xCBC6, 0x4F3D, 0xD9A4, 0x4F3E, 0x81C9, 0x4F3F, 0x81CA, 0x4F40, 0x81CB, 0x4F41, 0x81CC, 0x4F42, 0x81CD, + 0x4F43, 0xB5E8, 0x4F44, 0x81CE, 0x4F45, 0x81CF, 0x4F46, 0xB5AB, 0x4F47, 0x81D0, 0x4F48, 0x81D1, 0x4F49, 0x81D2, 0x4F4A, 0x81D3, + 0x4F4B, 0x81D4, 0x4F4C, 0x81D5, 0x4F4D, 0xCEBB, 0x4F4E, 0xB5CD, 0x4F4F, 0xD7A1, 0x4F50, 0xD7F4, 0x4F51, 0xD3D3, 0x4F52, 0x81D6, + 0x4F53, 0xCCE5, 0x4F54, 0x81D7, 0x4F55, 0xBACE, 0x4F56, 0x81D8, 0x4F57, 0xD9A2, 0x4F58, 0xD9DC, 0x4F59, 0xD3E0, 0x4F5A, 0xD8FD, + 0x4F5B, 0xB7F0, 0x4F5C, 0xD7F7, 0x4F5D, 0xD8FE, 0x4F5E, 0xD8FA, 0x4F5F, 0xD9A1, 0x4F60, 0xC4E3, 0x4F61, 0x81D9, 0x4F62, 0x81DA, + 0x4F63, 0xD3B6, 0x4F64, 0xD8F4, 0x4F65, 0xD9DD, 0x4F66, 0x81DB, 0x4F67, 0xD8FB, 0x4F68, 0x81DC, 0x4F69, 0xC5E5, 0x4F6A, 0x81DD, + 0x4F6B, 0x81DE, 0x4F6C, 0xC0D0, 0x4F6D, 0x81DF, 0x4F6E, 0x81E0, 0x4F6F, 0xD1F0, 0x4F70, 0xB0DB, 0x4F71, 0x81E1, 0x4F72, 0x81E2, + 0x4F73, 0xBCD1, 0x4F74, 0xD9A6, 0x4F75, 0x81E3, 0x4F76, 0xD9A5, 0x4F77, 0x81E4, 0x4F78, 0x81E5, 0x4F79, 0x81E6, 0x4F7A, 0x81E7, + 0x4F7B, 0xD9AC, 0x4F7C, 0xD9AE, 0x4F7D, 0x81E8, 0x4F7E, 0xD9AB, 0x4F7F, 0xCAB9, 0x4F80, 0x81E9, 0x4F81, 0x81EA, 0x4F82, 0x81EB, + 0x4F83, 0xD9A9, 0x4F84, 0xD6B6, 0x4F85, 0x81EC, 0x4F86, 0x81ED, 0x4F87, 0x81EE, 0x4F88, 0xB3DE, 0x4F89, 0xD9A8, 0x4F8A, 0x81EF, + 0x4F8B, 0xC0FD, 0x4F8C, 0x81F0, 0x4F8D, 0xCACC, 0x4F8E, 0x81F1, 0x4F8F, 0xD9AA, 0x4F90, 0x81F2, 0x4F91, 0xD9A7, 0x4F92, 0x81F3, + 0x4F93, 0x81F4, 0x4F94, 0xD9B0, 0x4F95, 0x81F5, 0x4F96, 0x81F6, 0x4F97, 0xB6B1, 0x4F98, 0x81F7, 0x4F99, 0x81F8, 0x4F9A, 0x81F9, + 0x4F9B, 0xB9A9, 0x4F9C, 0x81FA, 0x4F9D, 0xD2C0, 0x4F9E, 0x81FB, 0x4F9F, 0x81FC, 0x4FA0, 0xCFC0, 0x4FA1, 0x81FD, 0x4FA2, 0x81FE, + 0x4FA3, 0xC2C2, 0x4FA4, 0x8240, 0x4FA5, 0xBDC4, 0x4FA6, 0xD5EC, 0x4FA7, 0xB2E0, 0x4FA8, 0xC7C8, 0x4FA9, 0xBFEB, 0x4FAA, 0xD9AD, + 0x4FAB, 0x8241, 0x4FAC, 0xD9AF, 0x4FAD, 0x8242, 0x4FAE, 0xCEEA, 0x4FAF, 0xBAEE, 0x4FB0, 0x8243, 0x4FB1, 0x8244, 0x4FB2, 0x8245, + 0x4FB3, 0x8246, 0x4FB4, 0x8247, 0x4FB5, 0xC7D6, 0x4FB6, 0x8248, 0x4FB7, 0x8249, 0x4FB8, 0x824A, 0x4FB9, 0x824B, 0x4FBA, 0x824C, + 0x4FBB, 0x824D, 0x4FBC, 0x824E, 0x4FBD, 0x824F, 0x4FBE, 0x8250, 0x4FBF, 0xB1E3, 0x4FC0, 0x8251, 0x4FC1, 0x8252, 0x4FC2, 0x8253, + 0x4FC3, 0xB4D9, 0x4FC4, 0xB6ED, 0x4FC5, 0xD9B4, 0x4FC6, 0x8254, 0x4FC7, 0x8255, 0x4FC8, 0x8256, 0x4FC9, 0x8257, 0x4FCA, 0xBFA1, + 0x4FCB, 0x8258, 0x4FCC, 0x8259, 0x4FCD, 0x825A, 0x4FCE, 0xD9DE, 0x4FCF, 0xC7CE, 0x4FD0, 0xC0FE, 0x4FD1, 0xD9B8, 0x4FD2, 0x825B, + 0x4FD3, 0x825C, 0x4FD4, 0x825D, 0x4FD5, 0x825E, 0x4FD6, 0x825F, 0x4FD7, 0xCBD7, 0x4FD8, 0xB7FD, 0x4FD9, 0x8260, 0x4FDA, 0xD9B5, + 0x4FDB, 0x8261, 0x4FDC, 0xD9B7, 0x4FDD, 0xB1A3, 0x4FDE, 0xD3E1, 0x4FDF, 0xD9B9, 0x4FE0, 0x8262, 0x4FE1, 0xD0C5, 0x4FE2, 0x8263, + 0x4FE3, 0xD9B6, 0x4FE4, 0x8264, 0x4FE5, 0x8265, 0x4FE6, 0xD9B1, 0x4FE7, 0x8266, 0x4FE8, 0xD9B2, 0x4FE9, 0xC1A9, 0x4FEA, 0xD9B3, + 0x4FEB, 0x8267, 0x4FEC, 0x8268, 0x4FED, 0xBCF3, 0x4FEE, 0xD0DE, 0x4FEF, 0xB8A9, 0x4FF0, 0x8269, 0x4FF1, 0xBEE3, 0x4FF2, 0x826A, + 0x4FF3, 0xD9BD, 0x4FF4, 0x826B, 0x4FF5, 0x826C, 0x4FF6, 0x826D, 0x4FF7, 0x826E, 0x4FF8, 0xD9BA, 0x4FF9, 0x826F, 0x4FFA, 0xB0B3, + 0x4FFB, 0x8270, 0x4FFC, 0x8271, 0x4FFD, 0x8272, 0x4FFE, 0xD9C2, 0x4FFF, 0x8273, 0x5000, 0x8274, 0x5001, 0x8275, 0x5002, 0x8276, + 0x5003, 0x8277, 0x5004, 0x8278, 0x5005, 0x8279, 0x5006, 0x827A, 0x5007, 0x827B, 0x5008, 0x827C, 0x5009, 0x827D, 0x500A, 0x827E, + 0x500B, 0x8280, 0x500C, 0xD9C4, 0x500D, 0xB1B6, 0x500E, 0x8281, 0x500F, 0xD9BF, 0x5010, 0x8282, 0x5011, 0x8283, 0x5012, 0xB5B9, + 0x5013, 0x8284, 0x5014, 0xBEF3, 0x5015, 0x8285, 0x5016, 0x8286, 0x5017, 0x8287, 0x5018, 0xCCC8, 0x5019, 0xBAF2, 0x501A, 0xD2D0, + 0x501B, 0x8288, 0x501C, 0xD9C3, 0x501D, 0x8289, 0x501E, 0x828A, 0x501F, 0xBDE8, 0x5020, 0x828B, 0x5021, 0xB3AB, 0x5022, 0x828C, + 0x5023, 0x828D, 0x5024, 0x828E, 0x5025, 0xD9C5, 0x5026, 0xBEEB, 0x5027, 0x828F, 0x5028, 0xD9C6, 0x5029, 0xD9BB, 0x502A, 0xC4DF, + 0x502B, 0x8290, 0x502C, 0xD9BE, 0x502D, 0xD9C1, 0x502E, 0xD9C0, 0x502F, 0x8291, 0x5030, 0x8292, 0x5031, 0x8293, 0x5032, 0x8294, + 0x5033, 0x8295, 0x5034, 0x8296, 0x5035, 0x8297, 0x5036, 0x8298, 0x5037, 0x8299, 0x5038, 0x829A, 0x5039, 0x829B, 0x503A, 0xD5AE, + 0x503B, 0x829C, 0x503C, 0xD6B5, 0x503D, 0x829D, 0x503E, 0xC7E3, 0x503F, 0x829E, 0x5040, 0x829F, 0x5041, 0x82A0, 0x5042, 0x82A1, + 0x5043, 0xD9C8, 0x5044, 0x82A2, 0x5045, 0x82A3, 0x5046, 0x82A4, 0x5047, 0xBCD9, 0x5048, 0xD9CA, 0x5049, 0x82A5, 0x504A, 0x82A6, + 0x504B, 0x82A7, 0x504C, 0xD9BC, 0x504D, 0x82A8, 0x504E, 0xD9CB, 0x504F, 0xC6AB, 0x5050, 0x82A9, 0x5051, 0x82AA, 0x5052, 0x82AB, + 0x5053, 0x82AC, 0x5054, 0x82AD, 0x5055, 0xD9C9, 0x5056, 0x82AE, 0x5057, 0x82AF, 0x5058, 0x82B0, 0x5059, 0x82B1, 0x505A, 0xD7F6, + 0x505B, 0x82B2, 0x505C, 0xCDA3, 0x505D, 0x82B3, 0x505E, 0x82B4, 0x505F, 0x82B5, 0x5060, 0x82B6, 0x5061, 0x82B7, 0x5062, 0x82B8, + 0x5063, 0x82B9, 0x5064, 0x82BA, 0x5065, 0xBDA1, 0x5066, 0x82BB, 0x5067, 0x82BC, 0x5068, 0x82BD, 0x5069, 0x82BE, 0x506A, 0x82BF, + 0x506B, 0x82C0, 0x506C, 0xD9CC, 0x506D, 0x82C1, 0x506E, 0x82C2, 0x506F, 0x82C3, 0x5070, 0x82C4, 0x5071, 0x82C5, 0x5072, 0x82C6, + 0x5073, 0x82C7, 0x5074, 0x82C8, 0x5075, 0x82C9, 0x5076, 0xC5BC, 0x5077, 0xCDB5, 0x5078, 0x82CA, 0x5079, 0x82CB, 0x507A, 0x82CC, + 0x507B, 0xD9CD, 0x507C, 0x82CD, 0x507D, 0x82CE, 0x507E, 0xD9C7, 0x507F, 0xB3A5, 0x5080, 0xBFFE, 0x5081, 0x82CF, 0x5082, 0x82D0, + 0x5083, 0x82D1, 0x5084, 0x82D2, 0x5085, 0xB8B5, 0x5086, 0x82D3, 0x5087, 0x82D4, 0x5088, 0xC0FC, 0x5089, 0x82D5, 0x508A, 0x82D6, + 0x508B, 0x82D7, 0x508C, 0x82D8, 0x508D, 0xB0F8, 0x508E, 0x82D9, 0x508F, 0x82DA, 0x5090, 0x82DB, 0x5091, 0x82DC, 0x5092, 0x82DD, + 0x5093, 0x82DE, 0x5094, 0x82DF, 0x5095, 0x82E0, 0x5096, 0x82E1, 0x5097, 0x82E2, 0x5098, 0x82E3, 0x5099, 0x82E4, 0x509A, 0x82E5, + 0x509B, 0x82E6, 0x509C, 0x82E7, 0x509D, 0x82E8, 0x509E, 0x82E9, 0x509F, 0x82EA, 0x50A0, 0x82EB, 0x50A1, 0x82EC, 0x50A2, 0x82ED, + 0x50A3, 0xB4F6, 0x50A4, 0x82EE, 0x50A5, 0xD9CE, 0x50A6, 0x82EF, 0x50A7, 0xD9CF, 0x50A8, 0xB4A2, 0x50A9, 0xD9D0, 0x50AA, 0x82F0, + 0x50AB, 0x82F1, 0x50AC, 0xB4DF, 0x50AD, 0x82F2, 0x50AE, 0x82F3, 0x50AF, 0x82F4, 0x50B0, 0x82F5, 0x50B1, 0x82F6, 0x50B2, 0xB0C1, + 0x50B3, 0x82F7, 0x50B4, 0x82F8, 0x50B5, 0x82F9, 0x50B6, 0x82FA, 0x50B7, 0x82FB, 0x50B8, 0x82FC, 0x50B9, 0x82FD, 0x50BA, 0xD9D1, + 0x50BB, 0xC9B5, 0x50BC, 0x82FE, 0x50BD, 0x8340, 0x50BE, 0x8341, 0x50BF, 0x8342, 0x50C0, 0x8343, 0x50C1, 0x8344, 0x50C2, 0x8345, + 0x50C3, 0x8346, 0x50C4, 0x8347, 0x50C5, 0x8348, 0x50C6, 0x8349, 0x50C7, 0x834A, 0x50C8, 0x834B, 0x50C9, 0x834C, 0x50CA, 0x834D, + 0x50CB, 0x834E, 0x50CC, 0x834F, 0x50CD, 0x8350, 0x50CE, 0x8351, 0x50CF, 0xCFF1, 0x50D0, 0x8352, 0x50D1, 0x8353, 0x50D2, 0x8354, + 0x50D3, 0x8355, 0x50D4, 0x8356, 0x50D5, 0x8357, 0x50D6, 0xD9D2, 0x50D7, 0x8358, 0x50D8, 0x8359, 0x50D9, 0x835A, 0x50DA, 0xC1C5, + 0x50DB, 0x835B, 0x50DC, 0x835C, 0x50DD, 0x835D, 0x50DE, 0x835E, 0x50DF, 0x835F, 0x50E0, 0x8360, 0x50E1, 0x8361, 0x50E2, 0x8362, + 0x50E3, 0x8363, 0x50E4, 0x8364, 0x50E5, 0x8365, 0x50E6, 0xD9D6, 0x50E7, 0xC9AE, 0x50E8, 0x8366, 0x50E9, 0x8367, 0x50EA, 0x8368, + 0x50EB, 0x8369, 0x50EC, 0xD9D5, 0x50ED, 0xD9D4, 0x50EE, 0xD9D7, 0x50EF, 0x836A, 0x50F0, 0x836B, 0x50F1, 0x836C, 0x50F2, 0x836D, + 0x50F3, 0xCBDB, 0x50F4, 0x836E, 0x50F5, 0xBDA9, 0x50F6, 0x836F, 0x50F7, 0x8370, 0x50F8, 0x8371, 0x50F9, 0x8372, 0x50FA, 0x8373, + 0x50FB, 0xC6A7, 0x50FC, 0x8374, 0x50FD, 0x8375, 0x50FE, 0x8376, 0x50FF, 0x8377, 0x5100, 0x8378, 0x5101, 0x8379, 0x5102, 0x837A, + 0x5103, 0x837B, 0x5104, 0x837C, 0x5105, 0x837D, 0x5106, 0xD9D3, 0x5107, 0xD9D8, 0x5108, 0x837E, 0x5109, 0x8380, 0x510A, 0x8381, + 0x510B, 0xD9D9, 0x510C, 0x8382, 0x510D, 0x8383, 0x510E, 0x8384, 0x510F, 0x8385, 0x5110, 0x8386, 0x5111, 0x8387, 0x5112, 0xC8E5, + 0x5113, 0x8388, 0x5114, 0x8389, 0x5115, 0x838A, 0x5116, 0x838B, 0x5117, 0x838C, 0x5118, 0x838D, 0x5119, 0x838E, 0x511A, 0x838F, + 0x511B, 0x8390, 0x511C, 0x8391, 0x511D, 0x8392, 0x511E, 0x8393, 0x511F, 0x8394, 0x5120, 0x8395, 0x5121, 0xC0DC, 0x5122, 0x8396, + 0x5123, 0x8397, 0x5124, 0x8398, 0x5125, 0x8399, 0x5126, 0x839A, 0x5127, 0x839B, 0x5128, 0x839C, 0x5129, 0x839D, 0x512A, 0x839E, + 0x512B, 0x839F, 0x512C, 0x83A0, 0x512D, 0x83A1, 0x512E, 0x83A2, 0x512F, 0x83A3, 0x5130, 0x83A4, 0x5131, 0x83A5, 0x5132, 0x83A6, + 0x5133, 0x83A7, 0x5134, 0x83A8, 0x5135, 0x83A9, 0x5136, 0x83AA, 0x5137, 0x83AB, 0x5138, 0x83AC, 0x5139, 0x83AD, 0x513A, 0x83AE, + 0x513B, 0x83AF, 0x513C, 0x83B0, 0x513D, 0x83B1, 0x513E, 0x83B2, 0x513F, 0xB6F9, 0x5140, 0xD8A3, 0x5141, 0xD4CA, 0x5142, 0x83B3, + 0x5143, 0xD4AA, 0x5144, 0xD0D6, 0x5145, 0xB3E4, 0x5146, 0xD5D7, 0x5147, 0x83B4, 0x5148, 0xCFC8, 0x5149, 0xB9E2, 0x514A, 0x83B5, + 0x514B, 0xBFCB, 0x514C, 0x83B6, 0x514D, 0xC3E2, 0x514E, 0x83B7, 0x514F, 0x83B8, 0x5150, 0x83B9, 0x5151, 0xB6D2, 0x5152, 0x83BA, + 0x5153, 0x83BB, 0x5154, 0xCDC3, 0x5155, 0xD9EE, 0x5156, 0xD9F0, 0x5157, 0x83BC, 0x5158, 0x83BD, 0x5159, 0x83BE, 0x515A, 0xB5B3, + 0x515B, 0x83BF, 0x515C, 0xB6B5, 0x515D, 0x83C0, 0x515E, 0x83C1, 0x515F, 0x83C2, 0x5160, 0x83C3, 0x5161, 0x83C4, 0x5162, 0xBEA4, + 0x5163, 0x83C5, 0x5164, 0x83C6, 0x5165, 0xC8EB, 0x5166, 0x83C7, 0x5167, 0x83C8, 0x5168, 0xC8AB, 0x5169, 0x83C9, 0x516A, 0x83CA, + 0x516B, 0xB0CB, 0x516C, 0xB9AB, 0x516D, 0xC1F9, 0x516E, 0xD9E2, 0x516F, 0x83CB, 0x5170, 0xC0BC, 0x5171, 0xB9B2, 0x5172, 0x83CC, + 0x5173, 0xB9D8, 0x5174, 0xD0CB, 0x5175, 0xB1F8, 0x5176, 0xC6E4, 0x5177, 0xBEDF, 0x5178, 0xB5E4, 0x5179, 0xD7C8, 0x517A, 0x83CD, + 0x517B, 0xD1F8, 0x517C, 0xBCE6, 0x517D, 0xCADE, 0x517E, 0x83CE, 0x517F, 0x83CF, 0x5180, 0xBCBD, 0x5181, 0xD9E6, 0x5182, 0xD8E7, + 0x5183, 0x83D0, 0x5184, 0x83D1, 0x5185, 0xC4DA, 0x5186, 0x83D2, 0x5187, 0x83D3, 0x5188, 0xB8D4, 0x5189, 0xC8BD, 0x518A, 0x83D4, + 0x518B, 0x83D5, 0x518C, 0xB2E1, 0x518D, 0xD4D9, 0x518E, 0x83D6, 0x518F, 0x83D7, 0x5190, 0x83D8, 0x5191, 0x83D9, 0x5192, 0xC3B0, + 0x5193, 0x83DA, 0x5194, 0x83DB, 0x5195, 0xC3E1, 0x5196, 0xDAA2, 0x5197, 0xC8DF, 0x5198, 0x83DC, 0x5199, 0xD0B4, 0x519A, 0x83DD, + 0x519B, 0xBEFC, 0x519C, 0xC5A9, 0x519D, 0x83DE, 0x519E, 0x83DF, 0x519F, 0x83E0, 0x51A0, 0xB9DA, 0x51A1, 0x83E1, 0x51A2, 0xDAA3, + 0x51A3, 0x83E2, 0x51A4, 0xD4A9, 0x51A5, 0xDAA4, 0x51A6, 0x83E3, 0x51A7, 0x83E4, 0x51A8, 0x83E5, 0x51A9, 0x83E6, 0x51AA, 0x83E7, + 0x51AB, 0xD9FB, 0x51AC, 0xB6AC, 0x51AD, 0x83E8, 0x51AE, 0x83E9, 0x51AF, 0xB7EB, 0x51B0, 0xB1F9, 0x51B1, 0xD9FC, 0x51B2, 0xB3E5, + 0x51B3, 0xBEF6, 0x51B4, 0x83EA, 0x51B5, 0xBFF6, 0x51B6, 0xD2B1, 0x51B7, 0xC0E4, 0x51B8, 0x83EB, 0x51B9, 0x83EC, 0x51BA, 0x83ED, + 0x51BB, 0xB6B3, 0x51BC, 0xD9FE, 0x51BD, 0xD9FD, 0x51BE, 0x83EE, 0x51BF, 0x83EF, 0x51C0, 0xBEBB, 0x51C1, 0x83F0, 0x51C2, 0x83F1, + 0x51C3, 0x83F2, 0x51C4, 0xC6E0, 0x51C5, 0x83F3, 0x51C6, 0xD7BC, 0x51C7, 0xDAA1, 0x51C8, 0x83F4, 0x51C9, 0xC1B9, 0x51CA, 0x83F5, + 0x51CB, 0xB5F2, 0x51CC, 0xC1E8, 0x51CD, 0x83F6, 0x51CE, 0x83F7, 0x51CF, 0xBCF5, 0x51D0, 0x83F8, 0x51D1, 0xB4D5, 0x51D2, 0x83F9, + 0x51D3, 0x83FA, 0x51D4, 0x83FB, 0x51D5, 0x83FC, 0x51D6, 0x83FD, 0x51D7, 0x83FE, 0x51D8, 0x8440, 0x51D9, 0x8441, 0x51DA, 0x8442, + 0x51DB, 0xC1DD, 0x51DC, 0x8443, 0x51DD, 0xC4FD, 0x51DE, 0x8444, 0x51DF, 0x8445, 0x51E0, 0xBCB8, 0x51E1, 0xB7B2, 0x51E2, 0x8446, + 0x51E3, 0x8447, 0x51E4, 0xB7EF, 0x51E5, 0x8448, 0x51E6, 0x8449, 0x51E7, 0x844A, 0x51E8, 0x844B, 0x51E9, 0x844C, 0x51EA, 0x844D, + 0x51EB, 0xD9EC, 0x51EC, 0x844E, 0x51ED, 0xC6BE, 0x51EE, 0x844F, 0x51EF, 0xBFAD, 0x51F0, 0xBBCB, 0x51F1, 0x8450, 0x51F2, 0x8451, + 0x51F3, 0xB5CA, 0x51F4, 0x8452, 0x51F5, 0xDBC9, 0x51F6, 0xD0D7, 0x51F7, 0x8453, 0x51F8, 0xCDB9, 0x51F9, 0xB0BC, 0x51FA, 0xB3F6, + 0x51FB, 0xBBF7, 0x51FC, 0xDBCA, 0x51FD, 0xBAAF, 0x51FE, 0x8454, 0x51FF, 0xD4E4, 0x5200, 0xB5B6, 0x5201, 0xB5F3, 0x5202, 0xD8D6, + 0x5203, 0xC8D0, 0x5204, 0x8455, 0x5205, 0x8456, 0x5206, 0xB7D6, 0x5207, 0xC7D0, 0x5208, 0xD8D7, 0x5209, 0x8457, 0x520A, 0xBFAF, + 0x520B, 0x8458, 0x520C, 0x8459, 0x520D, 0xDBBB, 0x520E, 0xD8D8, 0x520F, 0x845A, 0x5210, 0x845B, 0x5211, 0xD0CC, 0x5212, 0xBBAE, + 0x5213, 0x845C, 0x5214, 0x845D, 0x5215, 0x845E, 0x5216, 0xEBBE, 0x5217, 0xC1D0, 0x5218, 0xC1F5, 0x5219, 0xD4F2, 0x521A, 0xB8D5, + 0x521B, 0xB4B4, 0x521C, 0x845F, 0x521D, 0xB3F5, 0x521E, 0x8460, 0x521F, 0x8461, 0x5220, 0xC9BE, 0x5221, 0x8462, 0x5222, 0x8463, + 0x5223, 0x8464, 0x5224, 0xC5D0, 0x5225, 0x8465, 0x5226, 0x8466, 0x5227, 0x8467, 0x5228, 0xC5D9, 0x5229, 0xC0FB, 0x522A, 0x8468, + 0x522B, 0xB1F0, 0x522C, 0x8469, 0x522D, 0xD8D9, 0x522E, 0xB9CE, 0x522F, 0x846A, 0x5230, 0xB5BD, 0x5231, 0x846B, 0x5232, 0x846C, + 0x5233, 0xD8DA, 0x5234, 0x846D, 0x5235, 0x846E, 0x5236, 0xD6C6, 0x5237, 0xCBA2, 0x5238, 0xC8AF, 0x5239, 0xC9B2, 0x523A, 0xB4CC, + 0x523B, 0xBFCC, 0x523C, 0x846F, 0x523D, 0xB9F4, 0x523E, 0x8470, 0x523F, 0xD8DB, 0x5240, 0xD8DC, 0x5241, 0xB6E7, 0x5242, 0xBCC1, + 0x5243, 0xCCEA, 0x5244, 0x8471, 0x5245, 0x8472, 0x5246, 0x8473, 0x5247, 0x8474, 0x5248, 0x8475, 0x5249, 0x8476, 0x524A, 0xCFF7, + 0x524B, 0x8477, 0x524C, 0xD8DD, 0x524D, 0xC7B0, 0x524E, 0x8478, 0x524F, 0x8479, 0x5250, 0xB9D0, 0x5251, 0xBDA3, 0x5252, 0x847A, + 0x5253, 0x847B, 0x5254, 0xCCDE, 0x5255, 0x847C, 0x5256, 0xC6CA, 0x5257, 0x847D, 0x5258, 0x847E, 0x5259, 0x8480, 0x525A, 0x8481, + 0x525B, 0x8482, 0x525C, 0xD8E0, 0x525D, 0x8483, 0x525E, 0xD8DE, 0x525F, 0x8484, 0x5260, 0x8485, 0x5261, 0xD8DF, 0x5262, 0x8486, + 0x5263, 0x8487, 0x5264, 0x8488, 0x5265, 0xB0FE, 0x5266, 0x8489, 0x5267, 0xBEE7, 0x5268, 0x848A, 0x5269, 0xCAA3, 0x526A, 0xBCF4, + 0x526B, 0x848B, 0x526C, 0x848C, 0x526D, 0x848D, 0x526E, 0x848E, 0x526F, 0xB8B1, 0x5270, 0x848F, 0x5271, 0x8490, 0x5272, 0xB8EE, + 0x5273, 0x8491, 0x5274, 0x8492, 0x5275, 0x8493, 0x5276, 0x8494, 0x5277, 0x8495, 0x5278, 0x8496, 0x5279, 0x8497, 0x527A, 0x8498, + 0x527B, 0x8499, 0x527C, 0x849A, 0x527D, 0xD8E2, 0x527E, 0x849B, 0x527F, 0xBDCB, 0x5280, 0x849C, 0x5281, 0xD8E4, 0x5282, 0xD8E3, + 0x5283, 0x849D, 0x5284, 0x849E, 0x5285, 0x849F, 0x5286, 0x84A0, 0x5287, 0x84A1, 0x5288, 0xC5FC, 0x5289, 0x84A2, 0x528A, 0x84A3, + 0x528B, 0x84A4, 0x528C, 0x84A5, 0x528D, 0x84A6, 0x528E, 0x84A7, 0x528F, 0x84A8, 0x5290, 0xD8E5, 0x5291, 0x84A9, 0x5292, 0x84AA, + 0x5293, 0xD8E6, 0x5294, 0x84AB, 0x5295, 0x84AC, 0x5296, 0x84AD, 0x5297, 0x84AE, 0x5298, 0x84AF, 0x5299, 0x84B0, 0x529A, 0x84B1, + 0x529B, 0xC1A6, 0x529C, 0x84B2, 0x529D, 0xC8B0, 0x529E, 0xB0EC, 0x529F, 0xB9A6, 0x52A0, 0xBCD3, 0x52A1, 0xCEF1, 0x52A2, 0xDBBD, + 0x52A3, 0xC1D3, 0x52A4, 0x84B3, 0x52A5, 0x84B4, 0x52A6, 0x84B5, 0x52A7, 0x84B6, 0x52A8, 0xB6AF, 0x52A9, 0xD6FA, 0x52AA, 0xC5AC, + 0x52AB, 0xBDD9, 0x52AC, 0xDBBE, 0x52AD, 0xDBBF, 0x52AE, 0x84B7, 0x52AF, 0x84B8, 0x52B0, 0x84B9, 0x52B1, 0xC0F8, 0x52B2, 0xBEA2, + 0x52B3, 0xC0CD, 0x52B4, 0x84BA, 0x52B5, 0x84BB, 0x52B6, 0x84BC, 0x52B7, 0x84BD, 0x52B8, 0x84BE, 0x52B9, 0x84BF, 0x52BA, 0x84C0, + 0x52BB, 0x84C1, 0x52BC, 0x84C2, 0x52BD, 0x84C3, 0x52BE, 0xDBC0, 0x52BF, 0xCAC6, 0x52C0, 0x84C4, 0x52C1, 0x84C5, 0x52C2, 0x84C6, + 0x52C3, 0xB2AA, 0x52C4, 0x84C7, 0x52C5, 0x84C8, 0x52C6, 0x84C9, 0x52C7, 0xD3C2, 0x52C8, 0x84CA, 0x52C9, 0xC3E3, 0x52CA, 0x84CB, + 0x52CB, 0xD1AB, 0x52CC, 0x84CC, 0x52CD, 0x84CD, 0x52CE, 0x84CE, 0x52CF, 0x84CF, 0x52D0, 0xDBC2, 0x52D1, 0x84D0, 0x52D2, 0xC0D5, + 0x52D3, 0x84D1, 0x52D4, 0x84D2, 0x52D5, 0x84D3, 0x52D6, 0xDBC3, 0x52D7, 0x84D4, 0x52D8, 0xBFB1, 0x52D9, 0x84D5, 0x52DA, 0x84D6, + 0x52DB, 0x84D7, 0x52DC, 0x84D8, 0x52DD, 0x84D9, 0x52DE, 0x84DA, 0x52DF, 0xC4BC, 0x52E0, 0x84DB, 0x52E1, 0x84DC, 0x52E2, 0x84DD, + 0x52E3, 0x84DE, 0x52E4, 0xC7DA, 0x52E5, 0x84DF, 0x52E6, 0x84E0, 0x52E7, 0x84E1, 0x52E8, 0x84E2, 0x52E9, 0x84E3, 0x52EA, 0x84E4, + 0x52EB, 0x84E5, 0x52EC, 0x84E6, 0x52ED, 0x84E7, 0x52EE, 0x84E8, 0x52EF, 0x84E9, 0x52F0, 0xDBC4, 0x52F1, 0x84EA, 0x52F2, 0x84EB, + 0x52F3, 0x84EC, 0x52F4, 0x84ED, 0x52F5, 0x84EE, 0x52F6, 0x84EF, 0x52F7, 0x84F0, 0x52F8, 0x84F1, 0x52F9, 0xD9E8, 0x52FA, 0xC9D7, + 0x52FB, 0x84F2, 0x52FC, 0x84F3, 0x52FD, 0x84F4, 0x52FE, 0xB9B4, 0x52FF, 0xCEF0, 0x5300, 0xD4C8, 0x5301, 0x84F5, 0x5302, 0x84F6, + 0x5303, 0x84F7, 0x5304, 0x84F8, 0x5305, 0xB0FC, 0x5306, 0xB4D2, 0x5307, 0x84F9, 0x5308, 0xD0D9, 0x5309, 0x84FA, 0x530A, 0x84FB, + 0x530B, 0x84FC, 0x530C, 0x84FD, 0x530D, 0xD9E9, 0x530E, 0x84FE, 0x530F, 0xDECB, 0x5310, 0xD9EB, 0x5311, 0x8540, 0x5312, 0x8541, + 0x5313, 0x8542, 0x5314, 0x8543, 0x5315, 0xD8B0, 0x5316, 0xBBAF, 0x5317, 0xB1B1, 0x5318, 0x8544, 0x5319, 0xB3D7, 0x531A, 0xD8CE, + 0x531B, 0x8545, 0x531C, 0x8546, 0x531D, 0xD4D1, 0x531E, 0x8547, 0x531F, 0x8548, 0x5320, 0xBDB3, 0x5321, 0xBFEF, 0x5322, 0x8549, + 0x5323, 0xCFBB, 0x5324, 0x854A, 0x5325, 0x854B, 0x5326, 0xD8D0, 0x5327, 0x854C, 0x5328, 0x854D, 0x5329, 0x854E, 0x532A, 0xB7CB, + 0x532B, 0x854F, 0x532C, 0x8550, 0x532D, 0x8551, 0x532E, 0xD8D1, 0x532F, 0x8552, 0x5330, 0x8553, 0x5331, 0x8554, 0x5332, 0x8555, + 0x5333, 0x8556, 0x5334, 0x8557, 0x5335, 0x8558, 0x5336, 0x8559, 0x5337, 0x855A, 0x5338, 0x855B, 0x5339, 0xC6A5, 0x533A, 0xC7F8, + 0x533B, 0xD2BD, 0x533C, 0x855C, 0x533D, 0x855D, 0x533E, 0xD8D2, 0x533F, 0xC4E4, 0x5340, 0x855E, 0x5341, 0xCAAE, 0x5342, 0x855F, + 0x5343, 0xC7A7, 0x5344, 0x8560, 0x5345, 0xD8A6, 0x5346, 0x8561, 0x5347, 0xC9FD, 0x5348, 0xCEE7, 0x5349, 0xBBDC, 0x534A, 0xB0EB, + 0x534B, 0x8562, 0x534C, 0x8563, 0x534D, 0x8564, 0x534E, 0xBBAA, 0x534F, 0xD0AD, 0x5350, 0x8565, 0x5351, 0xB1B0, 0x5352, 0xD7E4, + 0x5353, 0xD7BF, 0x5354, 0x8566, 0x5355, 0xB5A5, 0x5356, 0xC2F4, 0x5357, 0xC4CF, 0x5358, 0x8567, 0x5359, 0x8568, 0x535A, 0xB2A9, + 0x535B, 0x8569, 0x535C, 0xB2B7, 0x535D, 0x856A, 0x535E, 0xB1E5, 0x535F, 0xDFB2, 0x5360, 0xD5BC, 0x5361, 0xBFA8, 0x5362, 0xC2AC, + 0x5363, 0xD8D5, 0x5364, 0xC2B1, 0x5365, 0x856B, 0x5366, 0xD8D4, 0x5367, 0xCED4, 0x5368, 0x856C, 0x5369, 0xDAE0, 0x536A, 0x856D, + 0x536B, 0xCEC0, 0x536C, 0x856E, 0x536D, 0x856F, 0x536E, 0xD8B4, 0x536F, 0xC3AE, 0x5370, 0xD3A1, 0x5371, 0xCEA3, 0x5372, 0x8570, + 0x5373, 0xBCB4, 0x5374, 0xC8B4, 0x5375, 0xC2D1, 0x5376, 0x8571, 0x5377, 0xBEED, 0x5378, 0xD0B6, 0x5379, 0x8572, 0x537A, 0xDAE1, + 0x537B, 0x8573, 0x537C, 0x8574, 0x537D, 0x8575, 0x537E, 0x8576, 0x537F, 0xC7E4, 0x5380, 0x8577, 0x5381, 0x8578, 0x5382, 0xB3A7, + 0x5383, 0x8579, 0x5384, 0xB6F2, 0x5385, 0xCCFC, 0x5386, 0xC0FA, 0x5387, 0x857A, 0x5388, 0x857B, 0x5389, 0xC0F7, 0x538A, 0x857C, + 0x538B, 0xD1B9, 0x538C, 0xD1E1, 0x538D, 0xD8C7, 0x538E, 0x857D, 0x538F, 0x857E, 0x5390, 0x8580, 0x5391, 0x8581, 0x5392, 0x8582, + 0x5393, 0x8583, 0x5394, 0x8584, 0x5395, 0xB2DE, 0x5396, 0x8585, 0x5397, 0x8586, 0x5398, 0xC0E5, 0x5399, 0x8587, 0x539A, 0xBAF1, + 0x539B, 0x8588, 0x539C, 0x8589, 0x539D, 0xD8C8, 0x539E, 0x858A, 0x539F, 0xD4AD, 0x53A0, 0x858B, 0x53A1, 0x858C, 0x53A2, 0xCFE1, + 0x53A3, 0xD8C9, 0x53A4, 0x858D, 0x53A5, 0xD8CA, 0x53A6, 0xCFC3, 0x53A7, 0x858E, 0x53A8, 0xB3F8, 0x53A9, 0xBEC7, 0x53AA, 0x858F, + 0x53AB, 0x8590, 0x53AC, 0x8591, 0x53AD, 0x8592, 0x53AE, 0xD8CB, 0x53AF, 0x8593, 0x53B0, 0x8594, 0x53B1, 0x8595, 0x53B2, 0x8596, + 0x53B3, 0x8597, 0x53B4, 0x8598, 0x53B5, 0x8599, 0x53B6, 0xDBCC, 0x53B7, 0x859A, 0x53B8, 0x859B, 0x53B9, 0x859C, 0x53BA, 0x859D, + 0x53BB, 0xC8A5, 0x53BC, 0x859E, 0x53BD, 0x859F, 0x53BE, 0x85A0, 0x53BF, 0xCFD8, 0x53C0, 0x85A1, 0x53C1, 0xC8FE, 0x53C2, 0xB2CE, + 0x53C3, 0x85A2, 0x53C4, 0x85A3, 0x53C5, 0x85A4, 0x53C6, 0x85A5, 0x53C7, 0x85A6, 0x53C8, 0xD3D6, 0x53C9, 0xB2E6, 0x53CA, 0xBCB0, + 0x53CB, 0xD3D1, 0x53CC, 0xCBAB, 0x53CD, 0xB7B4, 0x53CE, 0x85A7, 0x53CF, 0x85A8, 0x53D0, 0x85A9, 0x53D1, 0xB7A2, 0x53D2, 0x85AA, + 0x53D3, 0x85AB, 0x53D4, 0xCAE5, 0x53D5, 0x85AC, 0x53D6, 0xC8A1, 0x53D7, 0xCADC, 0x53D8, 0xB1E4, 0x53D9, 0xD0F0, 0x53DA, 0x85AD, + 0x53DB, 0xC5D1, 0x53DC, 0x85AE, 0x53DD, 0x85AF, 0x53DE, 0x85B0, 0x53DF, 0xDBC5, 0x53E0, 0xB5FE, 0x53E1, 0x85B1, 0x53E2, 0x85B2, + 0x53E3, 0xBFDA, 0x53E4, 0xB9C5, 0x53E5, 0xBEE4, 0x53E6, 0xC1ED, 0x53E7, 0x85B3, 0x53E8, 0xDFB6, 0x53E9, 0xDFB5, 0x53EA, 0xD6BB, + 0x53EB, 0xBDD0, 0x53EC, 0xD5D9, 0x53ED, 0xB0C8, 0x53EE, 0xB6A3, 0x53EF, 0xBFC9, 0x53F0, 0xCCA8, 0x53F1, 0xDFB3, 0x53F2, 0xCAB7, + 0x53F3, 0xD3D2, 0x53F4, 0x85B4, 0x53F5, 0xD8CF, 0x53F6, 0xD2B6, 0x53F7, 0xBAC5, 0x53F8, 0xCBBE, 0x53F9, 0xCCBE, 0x53FA, 0x85B5, + 0x53FB, 0xDFB7, 0x53FC, 0xB5F0, 0x53FD, 0xDFB4, 0x53FE, 0x85B6, 0x53FF, 0x85B7, 0x5400, 0x85B8, 0x5401, 0xD3F5, 0x5402, 0x85B9, + 0x5403, 0xB3D4, 0x5404, 0xB8F7, 0x5405, 0x85BA, 0x5406, 0xDFBA, 0x5407, 0x85BB, 0x5408, 0xBACF, 0x5409, 0xBCAA, 0x540A, 0xB5F5, + 0x540B, 0x85BC, 0x540C, 0xCDAC, 0x540D, 0xC3FB, 0x540E, 0xBAF3, 0x540F, 0xC0F4, 0x5410, 0xCDC2, 0x5411, 0xCFF2, 0x5412, 0xDFB8, + 0x5413, 0xCFC5, 0x5414, 0x85BD, 0x5415, 0xC2C0, 0x5416, 0xDFB9, 0x5417, 0xC2F0, 0x5418, 0x85BE, 0x5419, 0x85BF, 0x541A, 0x85C0, + 0x541B, 0xBEFD, 0x541C, 0x85C1, 0x541D, 0xC1DF, 0x541E, 0xCDCC, 0x541F, 0xD2F7, 0x5420, 0xB7CD, 0x5421, 0xDFC1, 0x5422, 0x85C2, + 0x5423, 0xDFC4, 0x5424, 0x85C3, 0x5425, 0x85C4, 0x5426, 0xB7F1, 0x5427, 0xB0C9, 0x5428, 0xB6D6, 0x5429, 0xB7D4, 0x542A, 0x85C5, + 0x542B, 0xBAAC, 0x542C, 0xCCFD, 0x542D, 0xBFD4, 0x542E, 0xCBB1, 0x542F, 0xC6F4, 0x5430, 0x85C6, 0x5431, 0xD6A8, 0x5432, 0xDFC5, + 0x5433, 0x85C7, 0x5434, 0xCEE2, 0x5435, 0xB3B3, 0x5436, 0x85C8, 0x5437, 0x85C9, 0x5438, 0xCEFC, 0x5439, 0xB4B5, 0x543A, 0x85CA, + 0x543B, 0xCEC7, 0x543C, 0xBAF0, 0x543D, 0x85CB, 0x543E, 0xCEE1, 0x543F, 0x85CC, 0x5440, 0xD1BD, 0x5441, 0x85CD, 0x5442, 0x85CE, + 0x5443, 0xDFC0, 0x5444, 0x85CF, 0x5445, 0x85D0, 0x5446, 0xB4F4, 0x5447, 0x85D1, 0x5448, 0xB3CA, 0x5449, 0x85D2, 0x544A, 0xB8E6, + 0x544B, 0xDFBB, 0x544C, 0x85D3, 0x544D, 0x85D4, 0x544E, 0x85D5, 0x544F, 0x85D6, 0x5450, 0xC4C5, 0x5451, 0x85D7, 0x5452, 0xDFBC, + 0x5453, 0xDFBD, 0x5454, 0xDFBE, 0x5455, 0xC5BB, 0x5456, 0xDFBF, 0x5457, 0xDFC2, 0x5458, 0xD4B1, 0x5459, 0xDFC3, 0x545A, 0x85D8, + 0x545B, 0xC7BA, 0x545C, 0xCED8, 0x545D, 0x85D9, 0x545E, 0x85DA, 0x545F, 0x85DB, 0x5460, 0x85DC, 0x5461, 0x85DD, 0x5462, 0xC4D8, + 0x5463, 0x85DE, 0x5464, 0xDFCA, 0x5465, 0x85DF, 0x5466, 0xDFCF, 0x5467, 0x85E0, 0x5468, 0xD6DC, 0x5469, 0x85E1, 0x546A, 0x85E2, + 0x546B, 0x85E3, 0x546C, 0x85E4, 0x546D, 0x85E5, 0x546E, 0x85E6, 0x546F, 0x85E7, 0x5470, 0x85E8, 0x5471, 0xDFC9, 0x5472, 0xDFDA, + 0x5473, 0xCEB6, 0x5474, 0x85E9, 0x5475, 0xBAC7, 0x5476, 0xDFCE, 0x5477, 0xDFC8, 0x5478, 0xC5DE, 0x5479, 0x85EA, 0x547A, 0x85EB, + 0x547B, 0xC9EB, 0x547C, 0xBAF4, 0x547D, 0xC3FC, 0x547E, 0x85EC, 0x547F, 0x85ED, 0x5480, 0xBED7, 0x5481, 0x85EE, 0x5482, 0xDFC6, + 0x5483, 0x85EF, 0x5484, 0xDFCD, 0x5485, 0x85F0, 0x5486, 0xC5D8, 0x5487, 0x85F1, 0x5488, 0x85F2, 0x5489, 0x85F3, 0x548A, 0x85F4, + 0x548B, 0xD5A6, 0x548C, 0xBACD, 0x548D, 0x85F5, 0x548E, 0xBECC, 0x548F, 0xD3BD, 0x5490, 0xB8C0, 0x5491, 0x85F6, 0x5492, 0xD6E4, + 0x5493, 0x85F7, 0x5494, 0xDFC7, 0x5495, 0xB9BE, 0x5496, 0xBFA7, 0x5497, 0x85F8, 0x5498, 0x85F9, 0x5499, 0xC1FC, 0x549A, 0xDFCB, + 0x549B, 0xDFCC, 0x549C, 0x85FA, 0x549D, 0xDFD0, 0x549E, 0x85FB, 0x549F, 0x85FC, 0x54A0, 0x85FD, 0x54A1, 0x85FE, 0x54A2, 0x8640, + 0x54A3, 0xDFDB, 0x54A4, 0xDFE5, 0x54A5, 0x8641, 0x54A6, 0xDFD7, 0x54A7, 0xDFD6, 0x54A8, 0xD7C9, 0x54A9, 0xDFE3, 0x54AA, 0xDFE4, + 0x54AB, 0xE5EB, 0x54AC, 0xD2A7, 0x54AD, 0xDFD2, 0x54AE, 0x8642, 0x54AF, 0xBFA9, 0x54B0, 0x8643, 0x54B1, 0xD4DB, 0x54B2, 0x8644, + 0x54B3, 0xBFC8, 0x54B4, 0xDFD4, 0x54B5, 0x8645, 0x54B6, 0x8646, 0x54B7, 0x8647, 0x54B8, 0xCFCC, 0x54B9, 0x8648, 0x54BA, 0x8649, + 0x54BB, 0xDFDD, 0x54BC, 0x864A, 0x54BD, 0xD1CA, 0x54BE, 0x864B, 0x54BF, 0xDFDE, 0x54C0, 0xB0A7, 0x54C1, 0xC6B7, 0x54C2, 0xDFD3, + 0x54C3, 0x864C, 0x54C4, 0xBAE5, 0x54C5, 0x864D, 0x54C6, 0xB6DF, 0x54C7, 0xCDDB, 0x54C8, 0xB9FE, 0x54C9, 0xD4D5, 0x54CA, 0x864E, + 0x54CB, 0x864F, 0x54CC, 0xDFDF, 0x54CD, 0xCFEC, 0x54CE, 0xB0A5, 0x54CF, 0xDFE7, 0x54D0, 0xDFD1, 0x54D1, 0xD1C6, 0x54D2, 0xDFD5, + 0x54D3, 0xDFD8, 0x54D4, 0xDFD9, 0x54D5, 0xDFDC, 0x54D6, 0x8650, 0x54D7, 0xBBA9, 0x54D8, 0x8651, 0x54D9, 0xDFE0, 0x54DA, 0xDFE1, + 0x54DB, 0x8652, 0x54DC, 0xDFE2, 0x54DD, 0xDFE6, 0x54DE, 0xDFE8, 0x54DF, 0xD3B4, 0x54E0, 0x8653, 0x54E1, 0x8654, 0x54E2, 0x8655, + 0x54E3, 0x8656, 0x54E4, 0x8657, 0x54E5, 0xB8E7, 0x54E6, 0xC5B6, 0x54E7, 0xDFEA, 0x54E8, 0xC9DA, 0x54E9, 0xC1A8, 0x54EA, 0xC4C4, + 0x54EB, 0x8658, 0x54EC, 0x8659, 0x54ED, 0xBFDE, 0x54EE, 0xCFF8, 0x54EF, 0x865A, 0x54F0, 0x865B, 0x54F1, 0x865C, 0x54F2, 0xD5DC, + 0x54F3, 0xDFEE, 0x54F4, 0x865D, 0x54F5, 0x865E, 0x54F6, 0x865F, 0x54F7, 0x8660, 0x54F8, 0x8661, 0x54F9, 0x8662, 0x54FA, 0xB2B8, + 0x54FB, 0x8663, 0x54FC, 0xBADF, 0x54FD, 0xDFEC, 0x54FE, 0x8664, 0x54FF, 0xDBC1, 0x5500, 0x8665, 0x5501, 0xD1E4, 0x5502, 0x8666, + 0x5503, 0x8667, 0x5504, 0x8668, 0x5505, 0x8669, 0x5506, 0xCBF4, 0x5507, 0xB4BD, 0x5508, 0x866A, 0x5509, 0xB0A6, 0x550A, 0x866B, + 0x550B, 0x866C, 0x550C, 0x866D, 0x550D, 0x866E, 0x550E, 0x866F, 0x550F, 0xDFF1, 0x5510, 0xCCC6, 0x5511, 0xDFF2, 0x5512, 0x8670, + 0x5513, 0x8671, 0x5514, 0xDFED, 0x5515, 0x8672, 0x5516, 0x8673, 0x5517, 0x8674, 0x5518, 0x8675, 0x5519, 0x8676, 0x551A, 0x8677, + 0x551B, 0xDFE9, 0x551C, 0x8678, 0x551D, 0x8679, 0x551E, 0x867A, 0x551F, 0x867B, 0x5520, 0xDFEB, 0x5521, 0x867C, 0x5522, 0xDFEF, + 0x5523, 0xDFF0, 0x5524, 0xBBBD, 0x5525, 0x867D, 0x5526, 0x867E, 0x5527, 0xDFF3, 0x5528, 0x8680, 0x5529, 0x8681, 0x552A, 0xDFF4, + 0x552B, 0x8682, 0x552C, 0xBBA3, 0x552D, 0x8683, 0x552E, 0xCADB, 0x552F, 0xCEA8, 0x5530, 0xE0A7, 0x5531, 0xB3AA, 0x5532, 0x8684, + 0x5533, 0xE0A6, 0x5534, 0x8685, 0x5535, 0x8686, 0x5536, 0x8687, 0x5537, 0xE0A1, 0x5538, 0x8688, 0x5539, 0x8689, 0x553A, 0x868A, + 0x553B, 0x868B, 0x553C, 0xDFFE, 0x553D, 0x868C, 0x553E, 0xCDD9, 0x553F, 0xDFFC, 0x5540, 0x868D, 0x5541, 0xDFFA, 0x5542, 0x868E, + 0x5543, 0xBFD0, 0x5544, 0xD7C4, 0x5545, 0x868F, 0x5546, 0xC9CC, 0x5547, 0x8690, 0x5548, 0x8691, 0x5549, 0xDFF8, 0x554A, 0xB0A1, + 0x554B, 0x8692, 0x554C, 0x8693, 0x554D, 0x8694, 0x554E, 0x8695, 0x554F, 0x8696, 0x5550, 0xDFFD, 0x5551, 0x8697, 0x5552, 0x8698, + 0x5553, 0x8699, 0x5554, 0x869A, 0x5555, 0xDFFB, 0x5556, 0xE0A2, 0x5557, 0x869B, 0x5558, 0x869C, 0x5559, 0x869D, 0x555A, 0x869E, + 0x555B, 0x869F, 0x555C, 0xE0A8, 0x555D, 0x86A0, 0x555E, 0x86A1, 0x555F, 0x86A2, 0x5560, 0x86A3, 0x5561, 0xB7C8, 0x5562, 0x86A4, + 0x5563, 0x86A5, 0x5564, 0xC6A1, 0x5565, 0xC9B6, 0x5566, 0xC0B2, 0x5567, 0xDFF5, 0x5568, 0x86A6, 0x5569, 0x86A7, 0x556A, 0xC5BE, + 0x556B, 0x86A8, 0x556C, 0xD8C4, 0x556D, 0xDFF9, 0x556E, 0xC4F6, 0x556F, 0x86A9, 0x5570, 0x86AA, 0x5571, 0x86AB, 0x5572, 0x86AC, + 0x5573, 0x86AD, 0x5574, 0x86AE, 0x5575, 0xE0A3, 0x5576, 0xE0A4, 0x5577, 0xE0A5, 0x5578, 0xD0A5, 0x5579, 0x86AF, 0x557A, 0x86B0, + 0x557B, 0xE0B4, 0x557C, 0xCCE4, 0x557D, 0x86B1, 0x557E, 0xE0B1, 0x557F, 0x86B2, 0x5580, 0xBFA6, 0x5581, 0xE0AF, 0x5582, 0xCEB9, + 0x5583, 0xE0AB, 0x5584, 0xC9C6, 0x5585, 0x86B3, 0x5586, 0x86B4, 0x5587, 0xC0AE, 0x5588, 0xE0AE, 0x5589, 0xBAED, 0x558A, 0xBAB0, + 0x558B, 0xE0A9, 0x558C, 0x86B5, 0x558D, 0x86B6, 0x558E, 0x86B7, 0x558F, 0xDFF6, 0x5590, 0x86B8, 0x5591, 0xE0B3, 0x5592, 0x86B9, + 0x5593, 0x86BA, 0x5594, 0xE0B8, 0x5595, 0x86BB, 0x5596, 0x86BC, 0x5597, 0x86BD, 0x5598, 0xB4AD, 0x5599, 0xE0B9, 0x559A, 0x86BE, + 0x559B, 0x86BF, 0x559C, 0xCFB2, 0x559D, 0xBAC8, 0x559E, 0x86C0, 0x559F, 0xE0B0, 0x55A0, 0x86C1, 0x55A1, 0x86C2, 0x55A2, 0x86C3, + 0x55A3, 0x86C4, 0x55A4, 0x86C5, 0x55A5, 0x86C6, 0x55A6, 0x86C7, 0x55A7, 0xD0FA, 0x55A8, 0x86C8, 0x55A9, 0x86C9, 0x55AA, 0x86CA, + 0x55AB, 0x86CB, 0x55AC, 0x86CC, 0x55AD, 0x86CD, 0x55AE, 0x86CE, 0x55AF, 0x86CF, 0x55B0, 0x86D0, 0x55B1, 0xE0AC, 0x55B2, 0x86D1, + 0x55B3, 0xD4FB, 0x55B4, 0x86D2, 0x55B5, 0xDFF7, 0x55B6, 0x86D3, 0x55B7, 0xC5E7, 0x55B8, 0x86D4, 0x55B9, 0xE0AD, 0x55BA, 0x86D5, + 0x55BB, 0xD3F7, 0x55BC, 0x86D6, 0x55BD, 0xE0B6, 0x55BE, 0xE0B7, 0x55BF, 0x86D7, 0x55C0, 0x86D8, 0x55C1, 0x86D9, 0x55C2, 0x86DA, + 0x55C3, 0x86DB, 0x55C4, 0xE0C4, 0x55C5, 0xD0E1, 0x55C6, 0x86DC, 0x55C7, 0x86DD, 0x55C8, 0x86DE, 0x55C9, 0xE0BC, 0x55CA, 0x86DF, + 0x55CB, 0x86E0, 0x55CC, 0xE0C9, 0x55CD, 0xE0CA, 0x55CE, 0x86E1, 0x55CF, 0x86E2, 0x55D0, 0x86E3, 0x55D1, 0xE0BE, 0x55D2, 0xE0AA, + 0x55D3, 0xC9A4, 0x55D4, 0xE0C1, 0x55D5, 0x86E4, 0x55D6, 0xE0B2, 0x55D7, 0x86E5, 0x55D8, 0x86E6, 0x55D9, 0x86E7, 0x55DA, 0x86E8, + 0x55DB, 0x86E9, 0x55DC, 0xCAC8, 0x55DD, 0xE0C3, 0x55DE, 0x86EA, 0x55DF, 0xE0B5, 0x55E0, 0x86EB, 0x55E1, 0xCECB, 0x55E2, 0x86EC, + 0x55E3, 0xCBC3, 0x55E4, 0xE0CD, 0x55E5, 0xE0C6, 0x55E6, 0xE0C2, 0x55E7, 0x86ED, 0x55E8, 0xE0CB, 0x55E9, 0x86EE, 0x55EA, 0xE0BA, + 0x55EB, 0xE0BF, 0x55EC, 0xE0C0, 0x55ED, 0x86EF, 0x55EE, 0x86F0, 0x55EF, 0xE0C5, 0x55F0, 0x86F1, 0x55F1, 0x86F2, 0x55F2, 0xE0C7, + 0x55F3, 0xE0C8, 0x55F4, 0x86F3, 0x55F5, 0xE0CC, 0x55F6, 0x86F4, 0x55F7, 0xE0BB, 0x55F8, 0x86F5, 0x55F9, 0x86F6, 0x55FA, 0x86F7, + 0x55FB, 0x86F8, 0x55FC, 0x86F9, 0x55FD, 0xCBD4, 0x55FE, 0xE0D5, 0x55FF, 0x86FA, 0x5600, 0xE0D6, 0x5601, 0xE0D2, 0x5602, 0x86FB, + 0x5603, 0x86FC, 0x5604, 0x86FD, 0x5605, 0x86FE, 0x5606, 0x8740, 0x5607, 0x8741, 0x5608, 0xE0D0, 0x5609, 0xBCCE, 0x560A, 0x8742, + 0x560B, 0x8743, 0x560C, 0xE0D1, 0x560D, 0x8744, 0x560E, 0xB8C2, 0x560F, 0xD8C5, 0x5610, 0x8745, 0x5611, 0x8746, 0x5612, 0x8747, + 0x5613, 0x8748, 0x5614, 0x8749, 0x5615, 0x874A, 0x5616, 0x874B, 0x5617, 0x874C, 0x5618, 0xD0EA, 0x5619, 0x874D, 0x561A, 0x874E, + 0x561B, 0xC2EF, 0x561C, 0x874F, 0x561D, 0x8750, 0x561E, 0xE0CF, 0x561F, 0xE0BD, 0x5620, 0x8751, 0x5621, 0x8752, 0x5622, 0x8753, + 0x5623, 0xE0D4, 0x5624, 0xE0D3, 0x5625, 0x8754, 0x5626, 0x8755, 0x5627, 0xE0D7, 0x5628, 0x8756, 0x5629, 0x8757, 0x562A, 0x8758, + 0x562B, 0x8759, 0x562C, 0xE0DC, 0x562D, 0xE0D8, 0x562E, 0x875A, 0x562F, 0x875B, 0x5630, 0x875C, 0x5631, 0xD6F6, 0x5632, 0xB3B0, + 0x5633, 0x875D, 0x5634, 0xD7EC, 0x5635, 0x875E, 0x5636, 0xCBBB, 0x5637, 0x875F, 0x5638, 0x8760, 0x5639, 0xE0DA, 0x563A, 0x8761, + 0x563B, 0xCEFB, 0x563C, 0x8762, 0x563D, 0x8763, 0x563E, 0x8764, 0x563F, 0xBAD9, 0x5640, 0x8765, 0x5641, 0x8766, 0x5642, 0x8767, + 0x5643, 0x8768, 0x5644, 0x8769, 0x5645, 0x876A, 0x5646, 0x876B, 0x5647, 0x876C, 0x5648, 0x876D, 0x5649, 0x876E, 0x564A, 0x876F, + 0x564B, 0x8770, 0x564C, 0xE0E1, 0x564D, 0xE0DD, 0x564E, 0xD2AD, 0x564F, 0x8771, 0x5650, 0x8772, 0x5651, 0x8773, 0x5652, 0x8774, + 0x5653, 0x8775, 0x5654, 0xE0E2, 0x5655, 0x8776, 0x5656, 0x8777, 0x5657, 0xE0DB, 0x5658, 0xE0D9, 0x5659, 0xE0DF, 0x565A, 0x8778, + 0x565B, 0x8779, 0x565C, 0xE0E0, 0x565D, 0x877A, 0x565E, 0x877B, 0x565F, 0x877C, 0x5660, 0x877D, 0x5661, 0x877E, 0x5662, 0xE0DE, + 0x5663, 0x8780, 0x5664, 0xE0E4, 0x5665, 0x8781, 0x5666, 0x8782, 0x5667, 0x8783, 0x5668, 0xC6F7, 0x5669, 0xD8AC, 0x566A, 0xD4EB, + 0x566B, 0xE0E6, 0x566C, 0xCAC9, 0x566D, 0x8784, 0x566E, 0x8785, 0x566F, 0x8786, 0x5670, 0x8787, 0x5671, 0xE0E5, 0x5672, 0x8788, + 0x5673, 0x8789, 0x5674, 0x878A, 0x5675, 0x878B, 0x5676, 0xB8C1, 0x5677, 0x878C, 0x5678, 0x878D, 0x5679, 0x878E, 0x567A, 0x878F, + 0x567B, 0xE0E7, 0x567C, 0xE0E8, 0x567D, 0x8790, 0x567E, 0x8791, 0x567F, 0x8792, 0x5680, 0x8793, 0x5681, 0x8794, 0x5682, 0x8795, + 0x5683, 0x8796, 0x5684, 0x8797, 0x5685, 0xE0E9, 0x5686, 0xE0E3, 0x5687, 0x8798, 0x5688, 0x8799, 0x5689, 0x879A, 0x568A, 0x879B, + 0x568B, 0x879C, 0x568C, 0x879D, 0x568D, 0x879E, 0x568E, 0xBABF, 0x568F, 0xCCE7, 0x5690, 0x879F, 0x5691, 0x87A0, 0x5692, 0x87A1, + 0x5693, 0xE0EA, 0x5694, 0x87A2, 0x5695, 0x87A3, 0x5696, 0x87A4, 0x5697, 0x87A5, 0x5698, 0x87A6, 0x5699, 0x87A7, 0x569A, 0x87A8, + 0x569B, 0x87A9, 0x569C, 0x87AA, 0x569D, 0x87AB, 0x569E, 0x87AC, 0x569F, 0x87AD, 0x56A0, 0x87AE, 0x56A1, 0x87AF, 0x56A2, 0x87B0, + 0x56A3, 0xCFF9, 0x56A4, 0x87B1, 0x56A5, 0x87B2, 0x56A6, 0x87B3, 0x56A7, 0x87B4, 0x56A8, 0x87B5, 0x56A9, 0x87B6, 0x56AA, 0x87B7, + 0x56AB, 0x87B8, 0x56AC, 0x87B9, 0x56AD, 0x87BA, 0x56AE, 0x87BB, 0x56AF, 0xE0EB, 0x56B0, 0x87BC, 0x56B1, 0x87BD, 0x56B2, 0x87BE, + 0x56B3, 0x87BF, 0x56B4, 0x87C0, 0x56B5, 0x87C1, 0x56B6, 0x87C2, 0x56B7, 0xC8C2, 0x56B8, 0x87C3, 0x56B9, 0x87C4, 0x56BA, 0x87C5, + 0x56BB, 0x87C6, 0x56BC, 0xBDC0, 0x56BD, 0x87C7, 0x56BE, 0x87C8, 0x56BF, 0x87C9, 0x56C0, 0x87CA, 0x56C1, 0x87CB, 0x56C2, 0x87CC, + 0x56C3, 0x87CD, 0x56C4, 0x87CE, 0x56C5, 0x87CF, 0x56C6, 0x87D0, 0x56C7, 0x87D1, 0x56C8, 0x87D2, 0x56C9, 0x87D3, 0x56CA, 0xC4D2, + 0x56CB, 0x87D4, 0x56CC, 0x87D5, 0x56CD, 0x87D6, 0x56CE, 0x87D7, 0x56CF, 0x87D8, 0x56D0, 0x87D9, 0x56D1, 0x87DA, 0x56D2, 0x87DB, + 0x56D3, 0x87DC, 0x56D4, 0xE0EC, 0x56D5, 0x87DD, 0x56D6, 0x87DE, 0x56D7, 0xE0ED, 0x56D8, 0x87DF, 0x56D9, 0x87E0, 0x56DA, 0xC7F4, + 0x56DB, 0xCBC4, 0x56DC, 0x87E1, 0x56DD, 0xE0EE, 0x56DE, 0xBBD8, 0x56DF, 0xD8B6, 0x56E0, 0xD2F2, 0x56E1, 0xE0EF, 0x56E2, 0xCDC5, + 0x56E3, 0x87E2, 0x56E4, 0xB6DA, 0x56E5, 0x87E3, 0x56E6, 0x87E4, 0x56E7, 0x87E5, 0x56E8, 0x87E6, 0x56E9, 0x87E7, 0x56EA, 0x87E8, + 0x56EB, 0xE0F1, 0x56EC, 0x87E9, 0x56ED, 0xD4B0, 0x56EE, 0x87EA, 0x56EF, 0x87EB, 0x56F0, 0xC0A7, 0x56F1, 0xB4D1, 0x56F2, 0x87EC, + 0x56F3, 0x87ED, 0x56F4, 0xCEA7, 0x56F5, 0xE0F0, 0x56F6, 0x87EE, 0x56F7, 0x87EF, 0x56F8, 0x87F0, 0x56F9, 0xE0F2, 0x56FA, 0xB9CC, + 0x56FB, 0x87F1, 0x56FC, 0x87F2, 0x56FD, 0xB9FA, 0x56FE, 0xCDBC, 0x56FF, 0xE0F3, 0x5700, 0x87F3, 0x5701, 0x87F4, 0x5702, 0x87F5, + 0x5703, 0xC6D4, 0x5704, 0xE0F4, 0x5705, 0x87F6, 0x5706, 0xD4B2, 0x5707, 0x87F7, 0x5708, 0xC8A6, 0x5709, 0xE0F6, 0x570A, 0xE0F5, + 0x570B, 0x87F8, 0x570C, 0x87F9, 0x570D, 0x87FA, 0x570E, 0x87FB, 0x570F, 0x87FC, 0x5710, 0x87FD, 0x5711, 0x87FE, 0x5712, 0x8840, + 0x5713, 0x8841, 0x5714, 0x8842, 0x5715, 0x8843, 0x5716, 0x8844, 0x5717, 0x8845, 0x5718, 0x8846, 0x5719, 0x8847, 0x571A, 0x8848, + 0x571B, 0x8849, 0x571C, 0xE0F7, 0x571D, 0x884A, 0x571E, 0x884B, 0x571F, 0xCDC1, 0x5720, 0x884C, 0x5721, 0x884D, 0x5722, 0x884E, + 0x5723, 0xCAA5, 0x5724, 0x884F, 0x5725, 0x8850, 0x5726, 0x8851, 0x5727, 0x8852, 0x5728, 0xD4DA, 0x5729, 0xDBD7, 0x572A, 0xDBD9, + 0x572B, 0x8853, 0x572C, 0xDBD8, 0x572D, 0xB9E7, 0x572E, 0xDBDC, 0x572F, 0xDBDD, 0x5730, 0xB5D8, 0x5731, 0x8854, 0x5732, 0x8855, + 0x5733, 0xDBDA, 0x5734, 0x8856, 0x5735, 0x8857, 0x5736, 0x8858, 0x5737, 0x8859, 0x5738, 0x885A, 0x5739, 0xDBDB, 0x573A, 0xB3A1, + 0x573B, 0xDBDF, 0x573C, 0x885B, 0x573D, 0x885C, 0x573E, 0xBBF8, 0x573F, 0x885D, 0x5740, 0xD6B7, 0x5741, 0x885E, 0x5742, 0xDBE0, + 0x5743, 0x885F, 0x5744, 0x8860, 0x5745, 0x8861, 0x5746, 0x8862, 0x5747, 0xBEF9, 0x5748, 0x8863, 0x5749, 0x8864, 0x574A, 0xB7BB, + 0x574B, 0x8865, 0x574C, 0xDBD0, 0x574D, 0xCCAE, 0x574E, 0xBFB2, 0x574F, 0xBBB5, 0x5750, 0xD7F8, 0x5751, 0xBFD3, 0x5752, 0x8866, + 0x5753, 0x8867, 0x5754, 0x8868, 0x5755, 0x8869, 0x5756, 0x886A, 0x5757, 0xBFE9, 0x5758, 0x886B, 0x5759, 0x886C, 0x575A, 0xBCE1, + 0x575B, 0xCCB3, 0x575C, 0xDBDE, 0x575D, 0xB0D3, 0x575E, 0xCEEB, 0x575F, 0xB7D8, 0x5760, 0xD7B9, 0x5761, 0xC6C2, 0x5762, 0x886D, + 0x5763, 0x886E, 0x5764, 0xC0A4, 0x5765, 0x886F, 0x5766, 0xCCB9, 0x5767, 0x8870, 0x5768, 0xDBE7, 0x5769, 0xDBE1, 0x576A, 0xC6BA, + 0x576B, 0xDBE3, 0x576C, 0x8871, 0x576D, 0xDBE8, 0x576E, 0x8872, 0x576F, 0xC5F7, 0x5770, 0x8873, 0x5771, 0x8874, 0x5772, 0x8875, + 0x5773, 0xDBEA, 0x5774, 0x8876, 0x5775, 0x8877, 0x5776, 0xDBE9, 0x5777, 0xBFC0, 0x5778, 0x8878, 0x5779, 0x8879, 0x577A, 0x887A, + 0x577B, 0xDBE6, 0x577C, 0xDBE5, 0x577D, 0x887B, 0x577E, 0x887C, 0x577F, 0x887D, 0x5780, 0x887E, 0x5781, 0x8880, 0x5782, 0xB4B9, + 0x5783, 0xC0AC, 0x5784, 0xC2A2, 0x5785, 0xDBE2, 0x5786, 0xDBE4, 0x5787, 0x8881, 0x5788, 0x8882, 0x5789, 0x8883, 0x578A, 0x8884, + 0x578B, 0xD0CD, 0x578C, 0xDBED, 0x578D, 0x8885, 0x578E, 0x8886, 0x578F, 0x8887, 0x5790, 0x8888, 0x5791, 0x8889, 0x5792, 0xC0DD, + 0x5793, 0xDBF2, 0x5794, 0x888A, 0x5795, 0x888B, 0x5796, 0x888C, 0x5797, 0x888D, 0x5798, 0x888E, 0x5799, 0x888F, 0x579A, 0x8890, + 0x579B, 0xB6E2, 0x579C, 0x8891, 0x579D, 0x8892, 0x579E, 0x8893, 0x579F, 0x8894, 0x57A0, 0xDBF3, 0x57A1, 0xDBD2, 0x57A2, 0xB9B8, + 0x57A3, 0xD4AB, 0x57A4, 0xDBEC, 0x57A5, 0x8895, 0x57A6, 0xBFD1, 0x57A7, 0xDBF0, 0x57A8, 0x8896, 0x57A9, 0xDBD1, 0x57AA, 0x8897, + 0x57AB, 0xB5E6, 0x57AC, 0x8898, 0x57AD, 0xDBEB, 0x57AE, 0xBFE5, 0x57AF, 0x8899, 0x57B0, 0x889A, 0x57B1, 0x889B, 0x57B2, 0xDBEE, + 0x57B3, 0x889C, 0x57B4, 0xDBF1, 0x57B5, 0x889D, 0x57B6, 0x889E, 0x57B7, 0x889F, 0x57B8, 0xDBF9, 0x57B9, 0x88A0, 0x57BA, 0x88A1, + 0x57BB, 0x88A2, 0x57BC, 0x88A3, 0x57BD, 0x88A4, 0x57BE, 0x88A5, 0x57BF, 0x88A6, 0x57C0, 0x88A7, 0x57C1, 0x88A8, 0x57C2, 0xB9A1, + 0x57C3, 0xB0A3, 0x57C4, 0x88A9, 0x57C5, 0x88AA, 0x57C6, 0x88AB, 0x57C7, 0x88AC, 0x57C8, 0x88AD, 0x57C9, 0x88AE, 0x57CA, 0x88AF, + 0x57CB, 0xC2F1, 0x57CC, 0x88B0, 0x57CD, 0x88B1, 0x57CE, 0xB3C7, 0x57CF, 0xDBEF, 0x57D0, 0x88B2, 0x57D1, 0x88B3, 0x57D2, 0xDBF8, + 0x57D3, 0x88B4, 0x57D4, 0xC6D2, 0x57D5, 0xDBF4, 0x57D6, 0x88B5, 0x57D7, 0x88B6, 0x57D8, 0xDBF5, 0x57D9, 0xDBF7, 0x57DA, 0xDBF6, + 0x57DB, 0x88B7, 0x57DC, 0x88B8, 0x57DD, 0xDBFE, 0x57DE, 0x88B9, 0x57DF, 0xD3F2, 0x57E0, 0xB2BA, 0x57E1, 0x88BA, 0x57E2, 0x88BB, + 0x57E3, 0x88BC, 0x57E4, 0xDBFD, 0x57E5, 0x88BD, 0x57E6, 0x88BE, 0x57E7, 0x88BF, 0x57E8, 0x88C0, 0x57E9, 0x88C1, 0x57EA, 0x88C2, + 0x57EB, 0x88C3, 0x57EC, 0x88C4, 0x57ED, 0xDCA4, 0x57EE, 0x88C5, 0x57EF, 0xDBFB, 0x57F0, 0x88C6, 0x57F1, 0x88C7, 0x57F2, 0x88C8, + 0x57F3, 0x88C9, 0x57F4, 0xDBFA, 0x57F5, 0x88CA, 0x57F6, 0x88CB, 0x57F7, 0x88CC, 0x57F8, 0xDBFC, 0x57F9, 0xC5E0, 0x57FA, 0xBBF9, + 0x57FB, 0x88CD, 0x57FC, 0x88CE, 0x57FD, 0xDCA3, 0x57FE, 0x88CF, 0x57FF, 0x88D0, 0x5800, 0xDCA5, 0x5801, 0x88D1, 0x5802, 0xCCC3, + 0x5803, 0x88D2, 0x5804, 0x88D3, 0x5805, 0x88D4, 0x5806, 0xB6D1, 0x5807, 0xDDC0, 0x5808, 0x88D5, 0x5809, 0x88D6, 0x580A, 0x88D7, + 0x580B, 0xDCA1, 0x580C, 0x88D8, 0x580D, 0xDCA2, 0x580E, 0x88D9, 0x580F, 0x88DA, 0x5810, 0x88DB, 0x5811, 0xC7B5, 0x5812, 0x88DC, + 0x5813, 0x88DD, 0x5814, 0x88DE, 0x5815, 0xB6E9, 0x5816, 0x88DF, 0x5817, 0x88E0, 0x5818, 0x88E1, 0x5819, 0xDCA7, 0x581A, 0x88E2, + 0x581B, 0x88E3, 0x581C, 0x88E4, 0x581D, 0x88E5, 0x581E, 0xDCA6, 0x581F, 0x88E6, 0x5820, 0xDCA9, 0x5821, 0xB1A4, 0x5822, 0x88E7, + 0x5823, 0x88E8, 0x5824, 0xB5CC, 0x5825, 0x88E9, 0x5826, 0x88EA, 0x5827, 0x88EB, 0x5828, 0x88EC, 0x5829, 0x88ED, 0x582A, 0xBFB0, + 0x582B, 0x88EE, 0x582C, 0x88EF, 0x582D, 0x88F0, 0x582E, 0x88F1, 0x582F, 0x88F2, 0x5830, 0xD1DF, 0x5831, 0x88F3, 0x5832, 0x88F4, + 0x5833, 0x88F5, 0x5834, 0x88F6, 0x5835, 0xB6C2, 0x5836, 0x88F7, 0x5837, 0x88F8, 0x5838, 0x88F9, 0x5839, 0x88FA, 0x583A, 0x88FB, + 0x583B, 0x88FC, 0x583C, 0x88FD, 0x583D, 0x88FE, 0x583E, 0x8940, 0x583F, 0x8941, 0x5840, 0x8942, 0x5841, 0x8943, 0x5842, 0x8944, + 0x5843, 0x8945, 0x5844, 0xDCA8, 0x5845, 0x8946, 0x5846, 0x8947, 0x5847, 0x8948, 0x5848, 0x8949, 0x5849, 0x894A, 0x584A, 0x894B, + 0x584B, 0x894C, 0x584C, 0xCBFA, 0x584D, 0xEBF3, 0x584E, 0x894D, 0x584F, 0x894E, 0x5850, 0x894F, 0x5851, 0xCBDC, 0x5852, 0x8950, + 0x5853, 0x8951, 0x5854, 0xCBFE, 0x5855, 0x8952, 0x5856, 0x8953, 0x5857, 0x8954, 0x5858, 0xCCC1, 0x5859, 0x8955, 0x585A, 0x8956, + 0x585B, 0x8957, 0x585C, 0x8958, 0x585D, 0x8959, 0x585E, 0xC8FB, 0x585F, 0x895A, 0x5860, 0x895B, 0x5861, 0x895C, 0x5862, 0x895D, + 0x5863, 0x895E, 0x5864, 0x895F, 0x5865, 0xDCAA, 0x5866, 0x8960, 0x5867, 0x8961, 0x5868, 0x8962, 0x5869, 0x8963, 0x586A, 0x8964, + 0x586B, 0xCCEE, 0x586C, 0xDCAB, 0x586D, 0x8965, 0x586E, 0x8966, 0x586F, 0x8967, 0x5870, 0x8968, 0x5871, 0x8969, 0x5872, 0x896A, + 0x5873, 0x896B, 0x5874, 0x896C, 0x5875, 0x896D, 0x5876, 0x896E, 0x5877, 0x896F, 0x5878, 0x8970, 0x5879, 0x8971, 0x587A, 0x8972, + 0x587B, 0x8973, 0x587C, 0x8974, 0x587D, 0x8975, 0x587E, 0xDBD3, 0x587F, 0x8976, 0x5880, 0xDCAF, 0x5881, 0xDCAC, 0x5882, 0x8977, + 0x5883, 0xBEB3, 0x5884, 0x8978, 0x5885, 0xCAFB, 0x5886, 0x8979, 0x5887, 0x897A, 0x5888, 0x897B, 0x5889, 0xDCAD, 0x588A, 0x897C, + 0x588B, 0x897D, 0x588C, 0x897E, 0x588D, 0x8980, 0x588E, 0x8981, 0x588F, 0x8982, 0x5890, 0x8983, 0x5891, 0x8984, 0x5892, 0xC9CA, + 0x5893, 0xC4B9, 0x5894, 0x8985, 0x5895, 0x8986, 0x5896, 0x8987, 0x5897, 0x8988, 0x5898, 0x8989, 0x5899, 0xC7BD, 0x589A, 0xDCAE, + 0x589B, 0x898A, 0x589C, 0x898B, 0x589D, 0x898C, 0x589E, 0xD4F6, 0x589F, 0xD0E6, 0x58A0, 0x898D, 0x58A1, 0x898E, 0x58A2, 0x898F, + 0x58A3, 0x8990, 0x58A4, 0x8991, 0x58A5, 0x8992, 0x58A6, 0x8993, 0x58A7, 0x8994, 0x58A8, 0xC4AB, 0x58A9, 0xB6D5, 0x58AA, 0x8995, + 0x58AB, 0x8996, 0x58AC, 0x8997, 0x58AD, 0x8998, 0x58AE, 0x8999, 0x58AF, 0x899A, 0x58B0, 0x899B, 0x58B1, 0x899C, 0x58B2, 0x899D, + 0x58B3, 0x899E, 0x58B4, 0x899F, 0x58B5, 0x89A0, 0x58B6, 0x89A1, 0x58B7, 0x89A2, 0x58B8, 0x89A3, 0x58B9, 0x89A4, 0x58BA, 0x89A5, + 0x58BB, 0x89A6, 0x58BC, 0xDBD4, 0x58BD, 0x89A7, 0x58BE, 0x89A8, 0x58BF, 0x89A9, 0x58C0, 0x89AA, 0x58C1, 0xB1DA, 0x58C2, 0x89AB, + 0x58C3, 0x89AC, 0x58C4, 0x89AD, 0x58C5, 0xDBD5, 0x58C6, 0x89AE, 0x58C7, 0x89AF, 0x58C8, 0x89B0, 0x58C9, 0x89B1, 0x58CA, 0x89B2, + 0x58CB, 0x89B3, 0x58CC, 0x89B4, 0x58CD, 0x89B5, 0x58CE, 0x89B6, 0x58CF, 0x89B7, 0x58D0, 0x89B8, 0x58D1, 0xDBD6, 0x58D2, 0x89B9, + 0x58D3, 0x89BA, 0x58D4, 0x89BB, 0x58D5, 0xBABE, 0x58D6, 0x89BC, 0x58D7, 0x89BD, 0x58D8, 0x89BE, 0x58D9, 0x89BF, 0x58DA, 0x89C0, + 0x58DB, 0x89C1, 0x58DC, 0x89C2, 0x58DD, 0x89C3, 0x58DE, 0x89C4, 0x58DF, 0x89C5, 0x58E0, 0x89C6, 0x58E1, 0x89C7, 0x58E2, 0x89C8, + 0x58E3, 0x89C9, 0x58E4, 0xC8C0, 0x58E5, 0x89CA, 0x58E6, 0x89CB, 0x58E7, 0x89CC, 0x58E8, 0x89CD, 0x58E9, 0x89CE, 0x58EA, 0x89CF, + 0x58EB, 0xCABF, 0x58EC, 0xC8C9, 0x58ED, 0x89D0, 0x58EE, 0xD7B3, 0x58EF, 0x89D1, 0x58F0, 0xC9F9, 0x58F1, 0x89D2, 0x58F2, 0x89D3, + 0x58F3, 0xBFC7, 0x58F4, 0x89D4, 0x58F5, 0x89D5, 0x58F6, 0xBAF8, 0x58F7, 0x89D6, 0x58F8, 0x89D7, 0x58F9, 0xD2BC, 0x58FA, 0x89D8, + 0x58FB, 0x89D9, 0x58FC, 0x89DA, 0x58FD, 0x89DB, 0x58FE, 0x89DC, 0x58FF, 0x89DD, 0x5900, 0x89DE, 0x5901, 0x89DF, 0x5902, 0xE2BA, + 0x5903, 0x89E0, 0x5904, 0xB4A6, 0x5905, 0x89E1, 0x5906, 0x89E2, 0x5907, 0xB1B8, 0x5908, 0x89E3, 0x5909, 0x89E4, 0x590A, 0x89E5, + 0x590B, 0x89E6, 0x590C, 0x89E7, 0x590D, 0xB8B4, 0x590E, 0x89E8, 0x590F, 0xCFC4, 0x5910, 0x89E9, 0x5911, 0x89EA, 0x5912, 0x89EB, + 0x5913, 0x89EC, 0x5914, 0xD9E7, 0x5915, 0xCFA6, 0x5916, 0xCDE2, 0x5917, 0x89ED, 0x5918, 0x89EE, 0x5919, 0xD9ED, 0x591A, 0xB6E0, + 0x591B, 0x89EF, 0x591C, 0xD2B9, 0x591D, 0x89F0, 0x591E, 0x89F1, 0x591F, 0xB9BB, 0x5920, 0x89F2, 0x5921, 0x89F3, 0x5922, 0x89F4, + 0x5923, 0x89F5, 0x5924, 0xE2B9, 0x5925, 0xE2B7, 0x5926, 0x89F6, 0x5927, 0xB4F3, 0x5928, 0x89F7, 0x5929, 0xCCEC, 0x592A, 0xCCAB, + 0x592B, 0xB7F2, 0x592C, 0x89F8, 0x592D, 0xD8B2, 0x592E, 0xD1EB, 0x592F, 0xBABB, 0x5930, 0x89F9, 0x5931, 0xCAA7, 0x5932, 0x89FA, + 0x5933, 0x89FB, 0x5934, 0xCDB7, 0x5935, 0x89FC, 0x5936, 0x89FD, 0x5937, 0xD2C4, 0x5938, 0xBFE4, 0x5939, 0xBCD0, 0x593A, 0xB6E1, + 0x593B, 0x89FE, 0x593C, 0xDEC5, 0x593D, 0x8A40, 0x593E, 0x8A41, 0x593F, 0x8A42, 0x5940, 0x8A43, 0x5941, 0xDEC6, 0x5942, 0xDBBC, + 0x5943, 0x8A44, 0x5944, 0xD1D9, 0x5945, 0x8A45, 0x5946, 0x8A46, 0x5947, 0xC6E6, 0x5948, 0xC4CE, 0x5949, 0xB7EE, 0x594A, 0x8A47, + 0x594B, 0xB7DC, 0x594C, 0x8A48, 0x594D, 0x8A49, 0x594E, 0xBFFC, 0x594F, 0xD7E0, 0x5950, 0x8A4A, 0x5951, 0xC6F5, 0x5952, 0x8A4B, + 0x5953, 0x8A4C, 0x5954, 0xB1BC, 0x5955, 0xDEC8, 0x5956, 0xBDB1, 0x5957, 0xCCD7, 0x5958, 0xDECA, 0x5959, 0x8A4D, 0x595A, 0xDEC9, + 0x595B, 0x8A4E, 0x595C, 0x8A4F, 0x595D, 0x8A50, 0x595E, 0x8A51, 0x595F, 0x8A52, 0x5960, 0xB5EC, 0x5961, 0x8A53, 0x5962, 0xC9DD, + 0x5963, 0x8A54, 0x5964, 0x8A55, 0x5965, 0xB0C2, 0x5966, 0x8A56, 0x5967, 0x8A57, 0x5968, 0x8A58, 0x5969, 0x8A59, 0x596A, 0x8A5A, + 0x596B, 0x8A5B, 0x596C, 0x8A5C, 0x596D, 0x8A5D, 0x596E, 0x8A5E, 0x596F, 0x8A5F, 0x5970, 0x8A60, 0x5971, 0x8A61, 0x5972, 0x8A62, + 0x5973, 0xC5AE, 0x5974, 0xC5AB, 0x5975, 0x8A63, 0x5976, 0xC4CC, 0x5977, 0x8A64, 0x5978, 0xBCE9, 0x5979, 0xCBFD, 0x597A, 0x8A65, + 0x597B, 0x8A66, 0x597C, 0x8A67, 0x597D, 0xBAC3, 0x597E, 0x8A68, 0x597F, 0x8A69, 0x5980, 0x8A6A, 0x5981, 0xE5F9, 0x5982, 0xC8E7, + 0x5983, 0xE5FA, 0x5984, 0xCDFD, 0x5985, 0x8A6B, 0x5986, 0xD7B1, 0x5987, 0xB8BE, 0x5988, 0xC2E8, 0x5989, 0x8A6C, 0x598A, 0xC8D1, + 0x598B, 0x8A6D, 0x598C, 0x8A6E, 0x598D, 0xE5FB, 0x598E, 0x8A6F, 0x598F, 0x8A70, 0x5990, 0x8A71, 0x5991, 0x8A72, 0x5992, 0xB6CA, + 0x5993, 0xBCCB, 0x5994, 0x8A73, 0x5995, 0x8A74, 0x5996, 0xD1FD, 0x5997, 0xE6A1, 0x5998, 0x8A75, 0x5999, 0xC3EE, 0x599A, 0x8A76, + 0x599B, 0x8A77, 0x599C, 0x8A78, 0x599D, 0x8A79, 0x599E, 0xE6A4, 0x599F, 0x8A7A, 0x59A0, 0x8A7B, 0x59A1, 0x8A7C, 0x59A2, 0x8A7D, + 0x59A3, 0xE5FE, 0x59A4, 0xE6A5, 0x59A5, 0xCDD7, 0x59A6, 0x8A7E, 0x59A7, 0x8A80, 0x59A8, 0xB7C1, 0x59A9, 0xE5FC, 0x59AA, 0xE5FD, + 0x59AB, 0xE6A3, 0x59AC, 0x8A81, 0x59AD, 0x8A82, 0x59AE, 0xC4DD, 0x59AF, 0xE6A8, 0x59B0, 0x8A83, 0x59B1, 0x8A84, 0x59B2, 0xE6A7, + 0x59B3, 0x8A85, 0x59B4, 0x8A86, 0x59B5, 0x8A87, 0x59B6, 0x8A88, 0x59B7, 0x8A89, 0x59B8, 0x8A8A, 0x59B9, 0xC3C3, 0x59BA, 0x8A8B, + 0x59BB, 0xC6DE, 0x59BC, 0x8A8C, 0x59BD, 0x8A8D, 0x59BE, 0xE6AA, 0x59BF, 0x8A8E, 0x59C0, 0x8A8F, 0x59C1, 0x8A90, 0x59C2, 0x8A91, + 0x59C3, 0x8A92, 0x59C4, 0x8A93, 0x59C5, 0x8A94, 0x59C6, 0xC4B7, 0x59C7, 0x8A95, 0x59C8, 0x8A96, 0x59C9, 0x8A97, 0x59CA, 0xE6A2, + 0x59CB, 0xCABC, 0x59CC, 0x8A98, 0x59CD, 0x8A99, 0x59CE, 0x8A9A, 0x59CF, 0x8A9B, 0x59D0, 0xBDE3, 0x59D1, 0xB9C3, 0x59D2, 0xE6A6, + 0x59D3, 0xD0D5, 0x59D4, 0xCEAF, 0x59D5, 0x8A9C, 0x59D6, 0x8A9D, 0x59D7, 0xE6A9, 0x59D8, 0xE6B0, 0x59D9, 0x8A9E, 0x59DA, 0xD2A6, + 0x59DB, 0x8A9F, 0x59DC, 0xBDAA, 0x59DD, 0xE6AD, 0x59DE, 0x8AA0, 0x59DF, 0x8AA1, 0x59E0, 0x8AA2, 0x59E1, 0x8AA3, 0x59E2, 0x8AA4, + 0x59E3, 0xE6AF, 0x59E4, 0x8AA5, 0x59E5, 0xC0D1, 0x59E6, 0x8AA6, 0x59E7, 0x8AA7, 0x59E8, 0xD2CC, 0x59E9, 0x8AA8, 0x59EA, 0x8AA9, + 0x59EB, 0x8AAA, 0x59EC, 0xBCA7, 0x59ED, 0x8AAB, 0x59EE, 0x8AAC, 0x59EF, 0x8AAD, 0x59F0, 0x8AAE, 0x59F1, 0x8AAF, 0x59F2, 0x8AB0, + 0x59F3, 0x8AB1, 0x59F4, 0x8AB2, 0x59F5, 0x8AB3, 0x59F6, 0x8AB4, 0x59F7, 0x8AB5, 0x59F8, 0x8AB6, 0x59F9, 0xE6B1, 0x59FA, 0x8AB7, + 0x59FB, 0xD2F6, 0x59FC, 0x8AB8, 0x59FD, 0x8AB9, 0x59FE, 0x8ABA, 0x59FF, 0xD7CB, 0x5A00, 0x8ABB, 0x5A01, 0xCDFE, 0x5A02, 0x8ABC, + 0x5A03, 0xCDDE, 0x5A04, 0xC2A6, 0x5A05, 0xE6AB, 0x5A06, 0xE6AC, 0x5A07, 0xBDBF, 0x5A08, 0xE6AE, 0x5A09, 0xE6B3, 0x5A0A, 0x8ABD, + 0x5A0B, 0x8ABE, 0x5A0C, 0xE6B2, 0x5A0D, 0x8ABF, 0x5A0E, 0x8AC0, 0x5A0F, 0x8AC1, 0x5A10, 0x8AC2, 0x5A11, 0xE6B6, 0x5A12, 0x8AC3, + 0x5A13, 0xE6B8, 0x5A14, 0x8AC4, 0x5A15, 0x8AC5, 0x5A16, 0x8AC6, 0x5A17, 0x8AC7, 0x5A18, 0xC4EF, 0x5A19, 0x8AC8, 0x5A1A, 0x8AC9, + 0x5A1B, 0x8ACA, 0x5A1C, 0xC4C8, 0x5A1D, 0x8ACB, 0x5A1E, 0x8ACC, 0x5A1F, 0xBEEA, 0x5A20, 0xC9EF, 0x5A21, 0x8ACD, 0x5A22, 0x8ACE, + 0x5A23, 0xE6B7, 0x5A24, 0x8ACF, 0x5A25, 0xB6F0, 0x5A26, 0x8AD0, 0x5A27, 0x8AD1, 0x5A28, 0x8AD2, 0x5A29, 0xC3E4, 0x5A2A, 0x8AD3, + 0x5A2B, 0x8AD4, 0x5A2C, 0x8AD5, 0x5A2D, 0x8AD6, 0x5A2E, 0x8AD7, 0x5A2F, 0x8AD8, 0x5A30, 0x8AD9, 0x5A31, 0xD3E9, 0x5A32, 0xE6B4, + 0x5A33, 0x8ADA, 0x5A34, 0xE6B5, 0x5A35, 0x8ADB, 0x5A36, 0xC8A2, 0x5A37, 0x8ADC, 0x5A38, 0x8ADD, 0x5A39, 0x8ADE, 0x5A3A, 0x8ADF, + 0x5A3B, 0x8AE0, 0x5A3C, 0xE6BD, 0x5A3D, 0x8AE1, 0x5A3E, 0x8AE2, 0x5A3F, 0x8AE3, 0x5A40, 0xE6B9, 0x5A41, 0x8AE4, 0x5A42, 0x8AE5, + 0x5A43, 0x8AE6, 0x5A44, 0x8AE7, 0x5A45, 0x8AE8, 0x5A46, 0xC6C5, 0x5A47, 0x8AE9, 0x5A48, 0x8AEA, 0x5A49, 0xCDF1, 0x5A4A, 0xE6BB, + 0x5A4B, 0x8AEB, 0x5A4C, 0x8AEC, 0x5A4D, 0x8AED, 0x5A4E, 0x8AEE, 0x5A4F, 0x8AEF, 0x5A50, 0x8AF0, 0x5A51, 0x8AF1, 0x5A52, 0x8AF2, + 0x5A53, 0x8AF3, 0x5A54, 0x8AF4, 0x5A55, 0xE6BC, 0x5A56, 0x8AF5, 0x5A57, 0x8AF6, 0x5A58, 0x8AF7, 0x5A59, 0x8AF8, 0x5A5A, 0xBBE9, + 0x5A5B, 0x8AF9, 0x5A5C, 0x8AFA, 0x5A5D, 0x8AFB, 0x5A5E, 0x8AFC, 0x5A5F, 0x8AFD, 0x5A60, 0x8AFE, 0x5A61, 0x8B40, 0x5A62, 0xE6BE, + 0x5A63, 0x8B41, 0x5A64, 0x8B42, 0x5A65, 0x8B43, 0x5A66, 0x8B44, 0x5A67, 0xE6BA, 0x5A68, 0x8B45, 0x5A69, 0x8B46, 0x5A6A, 0xC0B7, + 0x5A6B, 0x8B47, 0x5A6C, 0x8B48, 0x5A6D, 0x8B49, 0x5A6E, 0x8B4A, 0x5A6F, 0x8B4B, 0x5A70, 0x8B4C, 0x5A71, 0x8B4D, 0x5A72, 0x8B4E, + 0x5A73, 0x8B4F, 0x5A74, 0xD3A4, 0x5A75, 0xE6BF, 0x5A76, 0xC9F4, 0x5A77, 0xE6C3, 0x5A78, 0x8B50, 0x5A79, 0x8B51, 0x5A7A, 0xE6C4, + 0x5A7B, 0x8B52, 0x5A7C, 0x8B53, 0x5A7D, 0x8B54, 0x5A7E, 0x8B55, 0x5A7F, 0xD0F6, 0x5A80, 0x8B56, 0x5A81, 0x8B57, 0x5A82, 0x8B58, + 0x5A83, 0x8B59, 0x5A84, 0x8B5A, 0x5A85, 0x8B5B, 0x5A86, 0x8B5C, 0x5A87, 0x8B5D, 0x5A88, 0x8B5E, 0x5A89, 0x8B5F, 0x5A8A, 0x8B60, + 0x5A8B, 0x8B61, 0x5A8C, 0x8B62, 0x5A8D, 0x8B63, 0x5A8E, 0x8B64, 0x5A8F, 0x8B65, 0x5A90, 0x8B66, 0x5A91, 0x8B67, 0x5A92, 0xC3BD, + 0x5A93, 0x8B68, 0x5A94, 0x8B69, 0x5A95, 0x8B6A, 0x5A96, 0x8B6B, 0x5A97, 0x8B6C, 0x5A98, 0x8B6D, 0x5A99, 0x8B6E, 0x5A9A, 0xC3C4, + 0x5A9B, 0xE6C2, 0x5A9C, 0x8B6F, 0x5A9D, 0x8B70, 0x5A9E, 0x8B71, 0x5A9F, 0x8B72, 0x5AA0, 0x8B73, 0x5AA1, 0x8B74, 0x5AA2, 0x8B75, + 0x5AA3, 0x8B76, 0x5AA4, 0x8B77, 0x5AA5, 0x8B78, 0x5AA6, 0x8B79, 0x5AA7, 0x8B7A, 0x5AA8, 0x8B7B, 0x5AA9, 0x8B7C, 0x5AAA, 0xE6C1, + 0x5AAB, 0x8B7D, 0x5AAC, 0x8B7E, 0x5AAD, 0x8B80, 0x5AAE, 0x8B81, 0x5AAF, 0x8B82, 0x5AB0, 0x8B83, 0x5AB1, 0x8B84, 0x5AB2, 0xE6C7, + 0x5AB3, 0xCFB1, 0x5AB4, 0x8B85, 0x5AB5, 0xEBF4, 0x5AB6, 0x8B86, 0x5AB7, 0x8B87, 0x5AB8, 0xE6CA, 0x5AB9, 0x8B88, 0x5ABA, 0x8B89, + 0x5ABB, 0x8B8A, 0x5ABC, 0x8B8B, 0x5ABD, 0x8B8C, 0x5ABE, 0xE6C5, 0x5ABF, 0x8B8D, 0x5AC0, 0x8B8E, 0x5AC1, 0xBCDE, 0x5AC2, 0xC9A9, + 0x5AC3, 0x8B8F, 0x5AC4, 0x8B90, 0x5AC5, 0x8B91, 0x5AC6, 0x8B92, 0x5AC7, 0x8B93, 0x5AC8, 0x8B94, 0x5AC9, 0xBCB5, 0x5ACA, 0x8B95, + 0x5ACB, 0x8B96, 0x5ACC, 0xCFD3, 0x5ACD, 0x8B97, 0x5ACE, 0x8B98, 0x5ACF, 0x8B99, 0x5AD0, 0x8B9A, 0x5AD1, 0x8B9B, 0x5AD2, 0xE6C8, + 0x5AD3, 0x8B9C, 0x5AD4, 0xE6C9, 0x5AD5, 0x8B9D, 0x5AD6, 0xE6CE, 0x5AD7, 0x8B9E, 0x5AD8, 0xE6D0, 0x5AD9, 0x8B9F, 0x5ADA, 0x8BA0, + 0x5ADB, 0x8BA1, 0x5ADC, 0xE6D1, 0x5ADD, 0x8BA2, 0x5ADE, 0x8BA3, 0x5ADF, 0x8BA4, 0x5AE0, 0xE6CB, 0x5AE1, 0xB5D5, 0x5AE2, 0x8BA5, + 0x5AE3, 0xE6CC, 0x5AE4, 0x8BA6, 0x5AE5, 0x8BA7, 0x5AE6, 0xE6CF, 0x5AE7, 0x8BA8, 0x5AE8, 0x8BA9, 0x5AE9, 0xC4DB, 0x5AEA, 0x8BAA, + 0x5AEB, 0xE6C6, 0x5AEC, 0x8BAB, 0x5AED, 0x8BAC, 0x5AEE, 0x8BAD, 0x5AEF, 0x8BAE, 0x5AF0, 0x8BAF, 0x5AF1, 0xE6CD, 0x5AF2, 0x8BB0, + 0x5AF3, 0x8BB1, 0x5AF4, 0x8BB2, 0x5AF5, 0x8BB3, 0x5AF6, 0x8BB4, 0x5AF7, 0x8BB5, 0x5AF8, 0x8BB6, 0x5AF9, 0x8BB7, 0x5AFA, 0x8BB8, + 0x5AFB, 0x8BB9, 0x5AFC, 0x8BBA, 0x5AFD, 0x8BBB, 0x5AFE, 0x8BBC, 0x5AFF, 0x8BBD, 0x5B00, 0x8BBE, 0x5B01, 0x8BBF, 0x5B02, 0x8BC0, + 0x5B03, 0x8BC1, 0x5B04, 0x8BC2, 0x5B05, 0x8BC3, 0x5B06, 0x8BC4, 0x5B07, 0x8BC5, 0x5B08, 0x8BC6, 0x5B09, 0xE6D2, 0x5B0A, 0x8BC7, + 0x5B0B, 0x8BC8, 0x5B0C, 0x8BC9, 0x5B0D, 0x8BCA, 0x5B0E, 0x8BCB, 0x5B0F, 0x8BCC, 0x5B10, 0x8BCD, 0x5B11, 0x8BCE, 0x5B12, 0x8BCF, + 0x5B13, 0x8BD0, 0x5B14, 0x8BD1, 0x5B15, 0x8BD2, 0x5B16, 0xE6D4, 0x5B17, 0xE6D3, 0x5B18, 0x8BD3, 0x5B19, 0x8BD4, 0x5B1A, 0x8BD5, + 0x5B1B, 0x8BD6, 0x5B1C, 0x8BD7, 0x5B1D, 0x8BD8, 0x5B1E, 0x8BD9, 0x5B1F, 0x8BDA, 0x5B20, 0x8BDB, 0x5B21, 0x8BDC, 0x5B22, 0x8BDD, + 0x5B23, 0x8BDE, 0x5B24, 0x8BDF, 0x5B25, 0x8BE0, 0x5B26, 0x8BE1, 0x5B27, 0x8BE2, 0x5B28, 0x8BE3, 0x5B29, 0x8BE4, 0x5B2A, 0x8BE5, + 0x5B2B, 0x8BE6, 0x5B2C, 0x8BE7, 0x5B2D, 0x8BE8, 0x5B2E, 0x8BE9, 0x5B2F, 0x8BEA, 0x5B30, 0x8BEB, 0x5B31, 0x8BEC, 0x5B32, 0xE6D5, + 0x5B33, 0x8BED, 0x5B34, 0xD9F8, 0x5B35, 0x8BEE, 0x5B36, 0x8BEF, 0x5B37, 0xE6D6, 0x5B38, 0x8BF0, 0x5B39, 0x8BF1, 0x5B3A, 0x8BF2, + 0x5B3B, 0x8BF3, 0x5B3C, 0x8BF4, 0x5B3D, 0x8BF5, 0x5B3E, 0x8BF6, 0x5B3F, 0x8BF7, 0x5B40, 0xE6D7, 0x5B41, 0x8BF8, 0x5B42, 0x8BF9, + 0x5B43, 0x8BFA, 0x5B44, 0x8BFB, 0x5B45, 0x8BFC, 0x5B46, 0x8BFD, 0x5B47, 0x8BFE, 0x5B48, 0x8C40, 0x5B49, 0x8C41, 0x5B4A, 0x8C42, + 0x5B4B, 0x8C43, 0x5B4C, 0x8C44, 0x5B4D, 0x8C45, 0x5B4E, 0x8C46, 0x5B4F, 0x8C47, 0x5B50, 0xD7D3, 0x5B51, 0xE6DD, 0x5B52, 0x8C48, + 0x5B53, 0xE6DE, 0x5B54, 0xBFD7, 0x5B55, 0xD4D0, 0x5B56, 0x8C49, 0x5B57, 0xD7D6, 0x5B58, 0xB4E6, 0x5B59, 0xCBEF, 0x5B5A, 0xE6DA, + 0x5B5B, 0xD8C3, 0x5B5C, 0xD7CE, 0x5B5D, 0xD0A2, 0x5B5E, 0x8C4A, 0x5B5F, 0xC3CF, 0x5B60, 0x8C4B, 0x5B61, 0x8C4C, 0x5B62, 0xE6DF, + 0x5B63, 0xBCBE, 0x5B64, 0xB9C2, 0x5B65, 0xE6DB, 0x5B66, 0xD1A7, 0x5B67, 0x8C4D, 0x5B68, 0x8C4E, 0x5B69, 0xBAA2, 0x5B6A, 0xC2CF, + 0x5B6B, 0x8C4F, 0x5B6C, 0xD8AB, 0x5B6D, 0x8C50, 0x5B6E, 0x8C51, 0x5B6F, 0x8C52, 0x5B70, 0xCAEB, 0x5B71, 0xE5EE, 0x5B72, 0x8C53, + 0x5B73, 0xE6DC, 0x5B74, 0x8C54, 0x5B75, 0xB7F5, 0x5B76, 0x8C55, 0x5B77, 0x8C56, 0x5B78, 0x8C57, 0x5B79, 0x8C58, 0x5B7A, 0xC8E6, + 0x5B7B, 0x8C59, 0x5B7C, 0x8C5A, 0x5B7D, 0xC4F5, 0x5B7E, 0x8C5B, 0x5B7F, 0x8C5C, 0x5B80, 0xE5B2, 0x5B81, 0xC4FE, 0x5B82, 0x8C5D, + 0x5B83, 0xCBFC, 0x5B84, 0xE5B3, 0x5B85, 0xD5AC, 0x5B86, 0x8C5E, 0x5B87, 0xD3EE, 0x5B88, 0xCAD8, 0x5B89, 0xB0B2, 0x5B8A, 0x8C5F, + 0x5B8B, 0xCBCE, 0x5B8C, 0xCDEA, 0x5B8D, 0x8C60, 0x5B8E, 0x8C61, 0x5B8F, 0xBAEA, 0x5B90, 0x8C62, 0x5B91, 0x8C63, 0x5B92, 0x8C64, + 0x5B93, 0xE5B5, 0x5B94, 0x8C65, 0x5B95, 0xE5B4, 0x5B96, 0x8C66, 0x5B97, 0xD7DA, 0x5B98, 0xB9D9, 0x5B99, 0xD6E6, 0x5B9A, 0xB6A8, + 0x5B9B, 0xCDF0, 0x5B9C, 0xD2CB, 0x5B9D, 0xB1A6, 0x5B9E, 0xCAB5, 0x5B9F, 0x8C67, 0x5BA0, 0xB3E8, 0x5BA1, 0xC9F3, 0x5BA2, 0xBFCD, + 0x5BA3, 0xD0FB, 0x5BA4, 0xCAD2, 0x5BA5, 0xE5B6, 0x5BA6, 0xBBC2, 0x5BA7, 0x8C68, 0x5BA8, 0x8C69, 0x5BA9, 0x8C6A, 0x5BAA, 0xCFDC, + 0x5BAB, 0xB9AC, 0x5BAC, 0x8C6B, 0x5BAD, 0x8C6C, 0x5BAE, 0x8C6D, 0x5BAF, 0x8C6E, 0x5BB0, 0xD4D7, 0x5BB1, 0x8C6F, 0x5BB2, 0x8C70, + 0x5BB3, 0xBAA6, 0x5BB4, 0xD1E7, 0x5BB5, 0xCFFC, 0x5BB6, 0xBCD2, 0x5BB7, 0x8C71, 0x5BB8, 0xE5B7, 0x5BB9, 0xC8DD, 0x5BBA, 0x8C72, + 0x5BBB, 0x8C73, 0x5BBC, 0x8C74, 0x5BBD, 0xBFED, 0x5BBE, 0xB1F6, 0x5BBF, 0xCBDE, 0x5BC0, 0x8C75, 0x5BC1, 0x8C76, 0x5BC2, 0xBCC5, + 0x5BC3, 0x8C77, 0x5BC4, 0xBCC4, 0x5BC5, 0xD2FA, 0x5BC6, 0xC3DC, 0x5BC7, 0xBFDC, 0x5BC8, 0x8C78, 0x5BC9, 0x8C79, 0x5BCA, 0x8C7A, + 0x5BCB, 0x8C7B, 0x5BCC, 0xB8BB, 0x5BCD, 0x8C7C, 0x5BCE, 0x8C7D, 0x5BCF, 0x8C7E, 0x5BD0, 0xC3C2, 0x5BD1, 0x8C80, 0x5BD2, 0xBAAE, + 0x5BD3, 0xD4A2, 0x5BD4, 0x8C81, 0x5BD5, 0x8C82, 0x5BD6, 0x8C83, 0x5BD7, 0x8C84, 0x5BD8, 0x8C85, 0x5BD9, 0x8C86, 0x5BDA, 0x8C87, + 0x5BDB, 0x8C88, 0x5BDC, 0x8C89, 0x5BDD, 0xC7DE, 0x5BDE, 0xC4AF, 0x5BDF, 0xB2EC, 0x5BE0, 0x8C8A, 0x5BE1, 0xB9D1, 0x5BE2, 0x8C8B, + 0x5BE3, 0x8C8C, 0x5BE4, 0xE5BB, 0x5BE5, 0xC1C8, 0x5BE6, 0x8C8D, 0x5BE7, 0x8C8E, 0x5BE8, 0xD5AF, 0x5BE9, 0x8C8F, 0x5BEA, 0x8C90, + 0x5BEB, 0x8C91, 0x5BEC, 0x8C92, 0x5BED, 0x8C93, 0x5BEE, 0xE5BC, 0x5BEF, 0x8C94, 0x5BF0, 0xE5BE, 0x5BF1, 0x8C95, 0x5BF2, 0x8C96, + 0x5BF3, 0x8C97, 0x5BF4, 0x8C98, 0x5BF5, 0x8C99, 0x5BF6, 0x8C9A, 0x5BF7, 0x8C9B, 0x5BF8, 0xB4E7, 0x5BF9, 0xB6D4, 0x5BFA, 0xCBC2, + 0x5BFB, 0xD1B0, 0x5BFC, 0xB5BC, 0x5BFD, 0x8C9C, 0x5BFE, 0x8C9D, 0x5BFF, 0xCAD9, 0x5C00, 0x8C9E, 0x5C01, 0xB7E2, 0x5C02, 0x8C9F, + 0x5C03, 0x8CA0, 0x5C04, 0xC9E4, 0x5C05, 0x8CA1, 0x5C06, 0xBDAB, 0x5C07, 0x8CA2, 0x5C08, 0x8CA3, 0x5C09, 0xCEBE, 0x5C0A, 0xD7F0, + 0x5C0B, 0x8CA4, 0x5C0C, 0x8CA5, 0x5C0D, 0x8CA6, 0x5C0E, 0x8CA7, 0x5C0F, 0xD0A1, 0x5C10, 0x8CA8, 0x5C11, 0xC9D9, 0x5C12, 0x8CA9, + 0x5C13, 0x8CAA, 0x5C14, 0xB6FB, 0x5C15, 0xE6D8, 0x5C16, 0xBCE2, 0x5C17, 0x8CAB, 0x5C18, 0xB3BE, 0x5C19, 0x8CAC, 0x5C1A, 0xC9D0, + 0x5C1B, 0x8CAD, 0x5C1C, 0xE6D9, 0x5C1D, 0xB3A2, 0x5C1E, 0x8CAE, 0x5C1F, 0x8CAF, 0x5C20, 0x8CB0, 0x5C21, 0x8CB1, 0x5C22, 0xDECC, + 0x5C23, 0x8CB2, 0x5C24, 0xD3C8, 0x5C25, 0xDECD, 0x5C26, 0x8CB3, 0x5C27, 0xD2A2, 0x5C28, 0x8CB4, 0x5C29, 0x8CB5, 0x5C2A, 0x8CB6, + 0x5C2B, 0x8CB7, 0x5C2C, 0xDECE, 0x5C2D, 0x8CB8, 0x5C2E, 0x8CB9, 0x5C2F, 0x8CBA, 0x5C30, 0x8CBB, 0x5C31, 0xBECD, 0x5C32, 0x8CBC, + 0x5C33, 0x8CBD, 0x5C34, 0xDECF, 0x5C35, 0x8CBE, 0x5C36, 0x8CBF, 0x5C37, 0x8CC0, 0x5C38, 0xCAAC, 0x5C39, 0xD2FC, 0x5C3A, 0xB3DF, + 0x5C3B, 0xE5EA, 0x5C3C, 0xC4E1, 0x5C3D, 0xBEA1, 0x5C3E, 0xCEB2, 0x5C3F, 0xC4F2, 0x5C40, 0xBED6, 0x5C41, 0xC6A8, 0x5C42, 0xB2E3, + 0x5C43, 0x8CC1, 0x5C44, 0x8CC2, 0x5C45, 0xBED3, 0x5C46, 0x8CC3, 0x5C47, 0x8CC4, 0x5C48, 0xC7FC, 0x5C49, 0xCCEB, 0x5C4A, 0xBDEC, + 0x5C4B, 0xCEDD, 0x5C4C, 0x8CC5, 0x5C4D, 0x8CC6, 0x5C4E, 0xCABA, 0x5C4F, 0xC6C1, 0x5C50, 0xE5EC, 0x5C51, 0xD0BC, 0x5C52, 0x8CC7, + 0x5C53, 0x8CC8, 0x5C54, 0x8CC9, 0x5C55, 0xD5B9, 0x5C56, 0x8CCA, 0x5C57, 0x8CCB, 0x5C58, 0x8CCC, 0x5C59, 0xE5ED, 0x5C5A, 0x8CCD, + 0x5C5B, 0x8CCE, 0x5C5C, 0x8CCF, 0x5C5D, 0x8CD0, 0x5C5E, 0xCAF4, 0x5C5F, 0x8CD1, 0x5C60, 0xCDC0, 0x5C61, 0xC2C5, 0x5C62, 0x8CD2, + 0x5C63, 0xE5EF, 0x5C64, 0x8CD3, 0x5C65, 0xC2C4, 0x5C66, 0xE5F0, 0x5C67, 0x8CD4, 0x5C68, 0x8CD5, 0x5C69, 0x8CD6, 0x5C6A, 0x8CD7, + 0x5C6B, 0x8CD8, 0x5C6C, 0x8CD9, 0x5C6D, 0x8CDA, 0x5C6E, 0xE5F8, 0x5C6F, 0xCDCD, 0x5C70, 0x8CDB, 0x5C71, 0xC9BD, 0x5C72, 0x8CDC, + 0x5C73, 0x8CDD, 0x5C74, 0x8CDE, 0x5C75, 0x8CDF, 0x5C76, 0x8CE0, 0x5C77, 0x8CE1, 0x5C78, 0x8CE2, 0x5C79, 0xD2D9, 0x5C7A, 0xE1A8, + 0x5C7B, 0x8CE3, 0x5C7C, 0x8CE4, 0x5C7D, 0x8CE5, 0x5C7E, 0x8CE6, 0x5C7F, 0xD3EC, 0x5C80, 0x8CE7, 0x5C81, 0xCBEA, 0x5C82, 0xC6F1, + 0x5C83, 0x8CE8, 0x5C84, 0x8CE9, 0x5C85, 0x8CEA, 0x5C86, 0x8CEB, 0x5C87, 0x8CEC, 0x5C88, 0xE1AC, 0x5C89, 0x8CED, 0x5C8A, 0x8CEE, + 0x5C8B, 0x8CEF, 0x5C8C, 0xE1A7, 0x5C8D, 0xE1A9, 0x5C8E, 0x8CF0, 0x5C8F, 0x8CF1, 0x5C90, 0xE1AA, 0x5C91, 0xE1AF, 0x5C92, 0x8CF2, + 0x5C93, 0x8CF3, 0x5C94, 0xB2ED, 0x5C95, 0x8CF4, 0x5C96, 0xE1AB, 0x5C97, 0xB8DA, 0x5C98, 0xE1AD, 0x5C99, 0xE1AE, 0x5C9A, 0xE1B0, + 0x5C9B, 0xB5BA, 0x5C9C, 0xE1B1, 0x5C9D, 0x8CF5, 0x5C9E, 0x8CF6, 0x5C9F, 0x8CF7, 0x5CA0, 0x8CF8, 0x5CA1, 0x8CF9, 0x5CA2, 0xE1B3, + 0x5CA3, 0xE1B8, 0x5CA4, 0x8CFA, 0x5CA5, 0x8CFB, 0x5CA6, 0x8CFC, 0x5CA7, 0x8CFD, 0x5CA8, 0x8CFE, 0x5CA9, 0xD1D2, 0x5CAA, 0x8D40, + 0x5CAB, 0xE1B6, 0x5CAC, 0xE1B5, 0x5CAD, 0xC1EB, 0x5CAE, 0x8D41, 0x5CAF, 0x8D42, 0x5CB0, 0x8D43, 0x5CB1, 0xE1B7, 0x5CB2, 0x8D44, + 0x5CB3, 0xD4C0, 0x5CB4, 0x8D45, 0x5CB5, 0xE1B2, 0x5CB6, 0x8D46, 0x5CB7, 0xE1BA, 0x5CB8, 0xB0B6, 0x5CB9, 0x8D47, 0x5CBA, 0x8D48, + 0x5CBB, 0x8D49, 0x5CBC, 0x8D4A, 0x5CBD, 0xE1B4, 0x5CBE, 0x8D4B, 0x5CBF, 0xBFF9, 0x5CC0, 0x8D4C, 0x5CC1, 0xE1B9, 0x5CC2, 0x8D4D, + 0x5CC3, 0x8D4E, 0x5CC4, 0xE1BB, 0x5CC5, 0x8D4F, 0x5CC6, 0x8D50, 0x5CC7, 0x8D51, 0x5CC8, 0x8D52, 0x5CC9, 0x8D53, 0x5CCA, 0x8D54, + 0x5CCB, 0xE1BE, 0x5CCC, 0x8D55, 0x5CCD, 0x8D56, 0x5CCE, 0x8D57, 0x5CCF, 0x8D58, 0x5CD0, 0x8D59, 0x5CD1, 0x8D5A, 0x5CD2, 0xE1BC, + 0x5CD3, 0x8D5B, 0x5CD4, 0x8D5C, 0x5CD5, 0x8D5D, 0x5CD6, 0x8D5E, 0x5CD7, 0x8D5F, 0x5CD8, 0x8D60, 0x5CD9, 0xD6C5, 0x5CDA, 0x8D61, + 0x5CDB, 0x8D62, 0x5CDC, 0x8D63, 0x5CDD, 0x8D64, 0x5CDE, 0x8D65, 0x5CDF, 0x8D66, 0x5CE0, 0x8D67, 0x5CE1, 0xCFBF, 0x5CE2, 0x8D68, + 0x5CE3, 0x8D69, 0x5CE4, 0xE1BD, 0x5CE5, 0xE1BF, 0x5CE6, 0xC2CD, 0x5CE7, 0x8D6A, 0x5CE8, 0xB6EB, 0x5CE9, 0x8D6B, 0x5CEA, 0xD3F8, + 0x5CEB, 0x8D6C, 0x5CEC, 0x8D6D, 0x5CED, 0xC7CD, 0x5CEE, 0x8D6E, 0x5CEF, 0x8D6F, 0x5CF0, 0xB7E5, 0x5CF1, 0x8D70, 0x5CF2, 0x8D71, + 0x5CF3, 0x8D72, 0x5CF4, 0x8D73, 0x5CF5, 0x8D74, 0x5CF6, 0x8D75, 0x5CF7, 0x8D76, 0x5CF8, 0x8D77, 0x5CF9, 0x8D78, 0x5CFA, 0x8D79, + 0x5CFB, 0xBEFE, 0x5CFC, 0x8D7A, 0x5CFD, 0x8D7B, 0x5CFE, 0x8D7C, 0x5CFF, 0x8D7D, 0x5D00, 0x8D7E, 0x5D01, 0x8D80, 0x5D02, 0xE1C0, + 0x5D03, 0xE1C1, 0x5D04, 0x8D81, 0x5D05, 0x8D82, 0x5D06, 0xE1C7, 0x5D07, 0xB3E7, 0x5D08, 0x8D83, 0x5D09, 0x8D84, 0x5D0A, 0x8D85, + 0x5D0B, 0x8D86, 0x5D0C, 0x8D87, 0x5D0D, 0x8D88, 0x5D0E, 0xC6E9, 0x5D0F, 0x8D89, 0x5D10, 0x8D8A, 0x5D11, 0x8D8B, 0x5D12, 0x8D8C, + 0x5D13, 0x8D8D, 0x5D14, 0xB4DE, 0x5D15, 0x8D8E, 0x5D16, 0xD1C2, 0x5D17, 0x8D8F, 0x5D18, 0x8D90, 0x5D19, 0x8D91, 0x5D1A, 0x8D92, + 0x5D1B, 0xE1C8, 0x5D1C, 0x8D93, 0x5D1D, 0x8D94, 0x5D1E, 0xE1C6, 0x5D1F, 0x8D95, 0x5D20, 0x8D96, 0x5D21, 0x8D97, 0x5D22, 0x8D98, + 0x5D23, 0x8D99, 0x5D24, 0xE1C5, 0x5D25, 0x8D9A, 0x5D26, 0xE1C3, 0x5D27, 0xE1C2, 0x5D28, 0x8D9B, 0x5D29, 0xB1C0, 0x5D2A, 0x8D9C, + 0x5D2B, 0x8D9D, 0x5D2C, 0x8D9E, 0x5D2D, 0xD5B8, 0x5D2E, 0xE1C4, 0x5D2F, 0x8D9F, 0x5D30, 0x8DA0, 0x5D31, 0x8DA1, 0x5D32, 0x8DA2, + 0x5D33, 0x8DA3, 0x5D34, 0xE1CB, 0x5D35, 0x8DA4, 0x5D36, 0x8DA5, 0x5D37, 0x8DA6, 0x5D38, 0x8DA7, 0x5D39, 0x8DA8, 0x5D3A, 0x8DA9, + 0x5D3B, 0x8DAA, 0x5D3C, 0x8DAB, 0x5D3D, 0xE1CC, 0x5D3E, 0xE1CA, 0x5D3F, 0x8DAC, 0x5D40, 0x8DAD, 0x5D41, 0x8DAE, 0x5D42, 0x8DAF, + 0x5D43, 0x8DB0, 0x5D44, 0x8DB1, 0x5D45, 0x8DB2, 0x5D46, 0x8DB3, 0x5D47, 0xEFFA, 0x5D48, 0x8DB4, 0x5D49, 0x8DB5, 0x5D4A, 0xE1D3, + 0x5D4B, 0xE1D2, 0x5D4C, 0xC7B6, 0x5D4D, 0x8DB6, 0x5D4E, 0x8DB7, 0x5D4F, 0x8DB8, 0x5D50, 0x8DB9, 0x5D51, 0x8DBA, 0x5D52, 0x8DBB, + 0x5D53, 0x8DBC, 0x5D54, 0x8DBD, 0x5D55, 0x8DBE, 0x5D56, 0x8DBF, 0x5D57, 0x8DC0, 0x5D58, 0xE1C9, 0x5D59, 0x8DC1, 0x5D5A, 0x8DC2, + 0x5D5B, 0xE1CE, 0x5D5C, 0x8DC3, 0x5D5D, 0xE1D0, 0x5D5E, 0x8DC4, 0x5D5F, 0x8DC5, 0x5D60, 0x8DC6, 0x5D61, 0x8DC7, 0x5D62, 0x8DC8, + 0x5D63, 0x8DC9, 0x5D64, 0x8DCA, 0x5D65, 0x8DCB, 0x5D66, 0x8DCC, 0x5D67, 0x8DCD, 0x5D68, 0x8DCE, 0x5D69, 0xE1D4, 0x5D6A, 0x8DCF, + 0x5D6B, 0xE1D1, 0x5D6C, 0xE1CD, 0x5D6D, 0x8DD0, 0x5D6E, 0x8DD1, 0x5D6F, 0xE1CF, 0x5D70, 0x8DD2, 0x5D71, 0x8DD3, 0x5D72, 0x8DD4, + 0x5D73, 0x8DD5, 0x5D74, 0xE1D5, 0x5D75, 0x8DD6, 0x5D76, 0x8DD7, 0x5D77, 0x8DD8, 0x5D78, 0x8DD9, 0x5D79, 0x8DDA, 0x5D7A, 0x8DDB, + 0x5D7B, 0x8DDC, 0x5D7C, 0x8DDD, 0x5D7D, 0x8DDE, 0x5D7E, 0x8DDF, 0x5D7F, 0x8DE0, 0x5D80, 0x8DE1, 0x5D81, 0x8DE2, 0x5D82, 0xE1D6, + 0x5D83, 0x8DE3, 0x5D84, 0x8DE4, 0x5D85, 0x8DE5, 0x5D86, 0x8DE6, 0x5D87, 0x8DE7, 0x5D88, 0x8DE8, 0x5D89, 0x8DE9, 0x5D8A, 0x8DEA, + 0x5D8B, 0x8DEB, 0x5D8C, 0x8DEC, 0x5D8D, 0x8DED, 0x5D8E, 0x8DEE, 0x5D8F, 0x8DEF, 0x5D90, 0x8DF0, 0x5D91, 0x8DF1, 0x5D92, 0x8DF2, + 0x5D93, 0x8DF3, 0x5D94, 0x8DF4, 0x5D95, 0x8DF5, 0x5D96, 0x8DF6, 0x5D97, 0x8DF7, 0x5D98, 0x8DF8, 0x5D99, 0xE1D7, 0x5D9A, 0x8DF9, + 0x5D9B, 0x8DFA, 0x5D9C, 0x8DFB, 0x5D9D, 0xE1D8, 0x5D9E, 0x8DFC, 0x5D9F, 0x8DFD, 0x5DA0, 0x8DFE, 0x5DA1, 0x8E40, 0x5DA2, 0x8E41, + 0x5DA3, 0x8E42, 0x5DA4, 0x8E43, 0x5DA5, 0x8E44, 0x5DA6, 0x8E45, 0x5DA7, 0x8E46, 0x5DA8, 0x8E47, 0x5DA9, 0x8E48, 0x5DAA, 0x8E49, + 0x5DAB, 0x8E4A, 0x5DAC, 0x8E4B, 0x5DAD, 0x8E4C, 0x5DAE, 0x8E4D, 0x5DAF, 0x8E4E, 0x5DB0, 0x8E4F, 0x5DB1, 0x8E50, 0x5DB2, 0x8E51, + 0x5DB3, 0x8E52, 0x5DB4, 0x8E53, 0x5DB5, 0x8E54, 0x5DB6, 0x8E55, 0x5DB7, 0xE1DA, 0x5DB8, 0x8E56, 0x5DB9, 0x8E57, 0x5DBA, 0x8E58, + 0x5DBB, 0x8E59, 0x5DBC, 0x8E5A, 0x5DBD, 0x8E5B, 0x5DBE, 0x8E5C, 0x5DBF, 0x8E5D, 0x5DC0, 0x8E5E, 0x5DC1, 0x8E5F, 0x5DC2, 0x8E60, + 0x5DC3, 0x8E61, 0x5DC4, 0x8E62, 0x5DC5, 0xE1DB, 0x5DC6, 0x8E63, 0x5DC7, 0x8E64, 0x5DC8, 0x8E65, 0x5DC9, 0x8E66, 0x5DCA, 0x8E67, + 0x5DCB, 0x8E68, 0x5DCC, 0x8E69, 0x5DCD, 0xCEA1, 0x5DCE, 0x8E6A, 0x5DCF, 0x8E6B, 0x5DD0, 0x8E6C, 0x5DD1, 0x8E6D, 0x5DD2, 0x8E6E, + 0x5DD3, 0x8E6F, 0x5DD4, 0x8E70, 0x5DD5, 0x8E71, 0x5DD6, 0x8E72, 0x5DD7, 0x8E73, 0x5DD8, 0x8E74, 0x5DD9, 0x8E75, 0x5DDA, 0x8E76, + 0x5DDB, 0xE7DD, 0x5DDC, 0x8E77, 0x5DDD, 0xB4A8, 0x5DDE, 0xD6DD, 0x5DDF, 0x8E78, 0x5DE0, 0x8E79, 0x5DE1, 0xD1B2, 0x5DE2, 0xB3B2, + 0x5DE3, 0x8E7A, 0x5DE4, 0x8E7B, 0x5DE5, 0xB9A4, 0x5DE6, 0xD7F3, 0x5DE7, 0xC7C9, 0x5DE8, 0xBEDE, 0x5DE9, 0xB9AE, 0x5DEA, 0x8E7C, + 0x5DEB, 0xCED7, 0x5DEC, 0x8E7D, 0x5DED, 0x8E7E, 0x5DEE, 0xB2EE, 0x5DEF, 0xDBCF, 0x5DF0, 0x8E80, 0x5DF1, 0xBCBA, 0x5DF2, 0xD2D1, + 0x5DF3, 0xCBC8, 0x5DF4, 0xB0CD, 0x5DF5, 0x8E81, 0x5DF6, 0x8E82, 0x5DF7, 0xCFEF, 0x5DF8, 0x8E83, 0x5DF9, 0x8E84, 0x5DFA, 0x8E85, + 0x5DFB, 0x8E86, 0x5DFC, 0x8E87, 0x5DFD, 0xD9E3, 0x5DFE, 0xBDED, 0x5DFF, 0x8E88, 0x5E00, 0x8E89, 0x5E01, 0xB1D2, 0x5E02, 0xCAD0, + 0x5E03, 0xB2BC, 0x5E04, 0x8E8A, 0x5E05, 0xCBA7, 0x5E06, 0xB7AB, 0x5E07, 0x8E8B, 0x5E08, 0xCAA6, 0x5E09, 0x8E8C, 0x5E0A, 0x8E8D, + 0x5E0B, 0x8E8E, 0x5E0C, 0xCFA3, 0x5E0D, 0x8E8F, 0x5E0E, 0x8E90, 0x5E0F, 0xE0F8, 0x5E10, 0xD5CA, 0x5E11, 0xE0FB, 0x5E12, 0x8E91, + 0x5E13, 0x8E92, 0x5E14, 0xE0FA, 0x5E15, 0xC5C1, 0x5E16, 0xCCFB, 0x5E17, 0x8E93, 0x5E18, 0xC1B1, 0x5E19, 0xE0F9, 0x5E1A, 0xD6E3, + 0x5E1B, 0xB2AF, 0x5E1C, 0xD6C4, 0x5E1D, 0xB5DB, 0x5E1E, 0x8E94, 0x5E1F, 0x8E95, 0x5E20, 0x8E96, 0x5E21, 0x8E97, 0x5E22, 0x8E98, + 0x5E23, 0x8E99, 0x5E24, 0x8E9A, 0x5E25, 0x8E9B, 0x5E26, 0xB4F8, 0x5E27, 0xD6A1, 0x5E28, 0x8E9C, 0x5E29, 0x8E9D, 0x5E2A, 0x8E9E, + 0x5E2B, 0x8E9F, 0x5E2C, 0x8EA0, 0x5E2D, 0xCFAF, 0x5E2E, 0xB0EF, 0x5E2F, 0x8EA1, 0x5E30, 0x8EA2, 0x5E31, 0xE0FC, 0x5E32, 0x8EA3, + 0x5E33, 0x8EA4, 0x5E34, 0x8EA5, 0x5E35, 0x8EA6, 0x5E36, 0x8EA7, 0x5E37, 0xE1A1, 0x5E38, 0xB3A3, 0x5E39, 0x8EA8, 0x5E3A, 0x8EA9, + 0x5E3B, 0xE0FD, 0x5E3C, 0xE0FE, 0x5E3D, 0xC3B1, 0x5E3E, 0x8EAA, 0x5E3F, 0x8EAB, 0x5E40, 0x8EAC, 0x5E41, 0x8EAD, 0x5E42, 0xC3DD, + 0x5E43, 0x8EAE, 0x5E44, 0xE1A2, 0x5E45, 0xB7F9, 0x5E46, 0x8EAF, 0x5E47, 0x8EB0, 0x5E48, 0x8EB1, 0x5E49, 0x8EB2, 0x5E4A, 0x8EB3, + 0x5E4B, 0x8EB4, 0x5E4C, 0xBBCF, 0x5E4D, 0x8EB5, 0x5E4E, 0x8EB6, 0x5E4F, 0x8EB7, 0x5E50, 0x8EB8, 0x5E51, 0x8EB9, 0x5E52, 0x8EBA, + 0x5E53, 0x8EBB, 0x5E54, 0xE1A3, 0x5E55, 0xC4BB, 0x5E56, 0x8EBC, 0x5E57, 0x8EBD, 0x5E58, 0x8EBE, 0x5E59, 0x8EBF, 0x5E5A, 0x8EC0, + 0x5E5B, 0xE1A4, 0x5E5C, 0x8EC1, 0x5E5D, 0x8EC2, 0x5E5E, 0xE1A5, 0x5E5F, 0x8EC3, 0x5E60, 0x8EC4, 0x5E61, 0xE1A6, 0x5E62, 0xB4B1, + 0x5E63, 0x8EC5, 0x5E64, 0x8EC6, 0x5E65, 0x8EC7, 0x5E66, 0x8EC8, 0x5E67, 0x8EC9, 0x5E68, 0x8ECA, 0x5E69, 0x8ECB, 0x5E6A, 0x8ECC, + 0x5E6B, 0x8ECD, 0x5E6C, 0x8ECE, 0x5E6D, 0x8ECF, 0x5E6E, 0x8ED0, 0x5E6F, 0x8ED1, 0x5E70, 0x8ED2, 0x5E71, 0x8ED3, 0x5E72, 0xB8C9, + 0x5E73, 0xC6BD, 0x5E74, 0xC4EA, 0x5E75, 0x8ED4, 0x5E76, 0xB2A2, 0x5E77, 0x8ED5, 0x5E78, 0xD0D2, 0x5E79, 0x8ED6, 0x5E7A, 0xE7DB, + 0x5E7B, 0xBBC3, 0x5E7C, 0xD3D7, 0x5E7D, 0xD3C4, 0x5E7E, 0x8ED7, 0x5E7F, 0xB9E3, 0x5E80, 0xE2CF, 0x5E81, 0x8ED8, 0x5E82, 0x8ED9, + 0x5E83, 0x8EDA, 0x5E84, 0xD7AF, 0x5E85, 0x8EDB, 0x5E86, 0xC7EC, 0x5E87, 0xB1D3, 0x5E88, 0x8EDC, 0x5E89, 0x8EDD, 0x5E8A, 0xB4B2, + 0x5E8B, 0xE2D1, 0x5E8C, 0x8EDE, 0x5E8D, 0x8EDF, 0x5E8E, 0x8EE0, 0x5E8F, 0xD0F2, 0x5E90, 0xC2AE, 0x5E91, 0xE2D0, 0x5E92, 0x8EE1, + 0x5E93, 0xBFE2, 0x5E94, 0xD3A6, 0x5E95, 0xB5D7, 0x5E96, 0xE2D2, 0x5E97, 0xB5EA, 0x5E98, 0x8EE2, 0x5E99, 0xC3ED, 0x5E9A, 0xB8FD, + 0x5E9B, 0x8EE3, 0x5E9C, 0xB8AE, 0x5E9D, 0x8EE4, 0x5E9E, 0xC5D3, 0x5E9F, 0xB7CF, 0x5EA0, 0xE2D4, 0x5EA1, 0x8EE5, 0x5EA2, 0x8EE6, + 0x5EA3, 0x8EE7, 0x5EA4, 0x8EE8, 0x5EA5, 0xE2D3, 0x5EA6, 0xB6C8, 0x5EA7, 0xD7F9, 0x5EA8, 0x8EE9, 0x5EA9, 0x8EEA, 0x5EAA, 0x8EEB, + 0x5EAB, 0x8EEC, 0x5EAC, 0x8EED, 0x5EAD, 0xCDA5, 0x5EAE, 0x8EEE, 0x5EAF, 0x8EEF, 0x5EB0, 0x8EF0, 0x5EB1, 0x8EF1, 0x5EB2, 0x8EF2, + 0x5EB3, 0xE2D8, 0x5EB4, 0x8EF3, 0x5EB5, 0xE2D6, 0x5EB6, 0xCAFC, 0x5EB7, 0xBFB5, 0x5EB8, 0xD3B9, 0x5EB9, 0xE2D5, 0x5EBA, 0x8EF4, + 0x5EBB, 0x8EF5, 0x5EBC, 0x8EF6, 0x5EBD, 0x8EF7, 0x5EBE, 0xE2D7, 0x5EBF, 0x8EF8, 0x5EC0, 0x8EF9, 0x5EC1, 0x8EFA, 0x5EC2, 0x8EFB, + 0x5EC3, 0x8EFC, 0x5EC4, 0x8EFD, 0x5EC5, 0x8EFE, 0x5EC6, 0x8F40, 0x5EC7, 0x8F41, 0x5EC8, 0x8F42, 0x5EC9, 0xC1AE, 0x5ECA, 0xC0C8, + 0x5ECB, 0x8F43, 0x5ECC, 0x8F44, 0x5ECD, 0x8F45, 0x5ECE, 0x8F46, 0x5ECF, 0x8F47, 0x5ED0, 0x8F48, 0x5ED1, 0xE2DB, 0x5ED2, 0xE2DA, + 0x5ED3, 0xC0AA, 0x5ED4, 0x8F49, 0x5ED5, 0x8F4A, 0x5ED6, 0xC1CE, 0x5ED7, 0x8F4B, 0x5ED8, 0x8F4C, 0x5ED9, 0x8F4D, 0x5EDA, 0x8F4E, + 0x5EDB, 0xE2DC, 0x5EDC, 0x8F4F, 0x5EDD, 0x8F50, 0x5EDE, 0x8F51, 0x5EDF, 0x8F52, 0x5EE0, 0x8F53, 0x5EE1, 0x8F54, 0x5EE2, 0x8F55, + 0x5EE3, 0x8F56, 0x5EE4, 0x8F57, 0x5EE5, 0x8F58, 0x5EE6, 0x8F59, 0x5EE7, 0x8F5A, 0x5EE8, 0xE2DD, 0x5EE9, 0x8F5B, 0x5EEA, 0xE2DE, + 0x5EEB, 0x8F5C, 0x5EEC, 0x8F5D, 0x5EED, 0x8F5E, 0x5EEE, 0x8F5F, 0x5EEF, 0x8F60, 0x5EF0, 0x8F61, 0x5EF1, 0x8F62, 0x5EF2, 0x8F63, + 0x5EF3, 0x8F64, 0x5EF4, 0xDBC8, 0x5EF5, 0x8F65, 0x5EF6, 0xD1D3, 0x5EF7, 0xCDA2, 0x5EF8, 0x8F66, 0x5EF9, 0x8F67, 0x5EFA, 0xBDA8, + 0x5EFB, 0x8F68, 0x5EFC, 0x8F69, 0x5EFD, 0x8F6A, 0x5EFE, 0xDEC3, 0x5EFF, 0xD8A5, 0x5F00, 0xBFAA, 0x5F01, 0xDBCD, 0x5F02, 0xD2EC, + 0x5F03, 0xC6FA, 0x5F04, 0xC5AA, 0x5F05, 0x8F6B, 0x5F06, 0x8F6C, 0x5F07, 0x8F6D, 0x5F08, 0xDEC4, 0x5F09, 0x8F6E, 0x5F0A, 0xB1D7, + 0x5F0B, 0xDFAE, 0x5F0C, 0x8F6F, 0x5F0D, 0x8F70, 0x5F0E, 0x8F71, 0x5F0F, 0xCABD, 0x5F10, 0x8F72, 0x5F11, 0xDFB1, 0x5F12, 0x8F73, + 0x5F13, 0xB9AD, 0x5F14, 0x8F74, 0x5F15, 0xD2FD, 0x5F16, 0x8F75, 0x5F17, 0xB8A5, 0x5F18, 0xBAEB, 0x5F19, 0x8F76, 0x5F1A, 0x8F77, + 0x5F1B, 0xB3DA, 0x5F1C, 0x8F78, 0x5F1D, 0x8F79, 0x5F1E, 0x8F7A, 0x5F1F, 0xB5DC, 0x5F20, 0xD5C5, 0x5F21, 0x8F7B, 0x5F22, 0x8F7C, + 0x5F23, 0x8F7D, 0x5F24, 0x8F7E, 0x5F25, 0xC3D6, 0x5F26, 0xCFD2, 0x5F27, 0xBBA1, 0x5F28, 0x8F80, 0x5F29, 0xE5F3, 0x5F2A, 0xE5F2, + 0x5F2B, 0x8F81, 0x5F2C, 0x8F82, 0x5F2D, 0xE5F4, 0x5F2E, 0x8F83, 0x5F2F, 0xCDE4, 0x5F30, 0x8F84, 0x5F31, 0xC8F5, 0x5F32, 0x8F85, + 0x5F33, 0x8F86, 0x5F34, 0x8F87, 0x5F35, 0x8F88, 0x5F36, 0x8F89, 0x5F37, 0x8F8A, 0x5F38, 0x8F8B, 0x5F39, 0xB5AF, 0x5F3A, 0xC7BF, + 0x5F3B, 0x8F8C, 0x5F3C, 0xE5F6, 0x5F3D, 0x8F8D, 0x5F3E, 0x8F8E, 0x5F3F, 0x8F8F, 0x5F40, 0xECB0, 0x5F41, 0x8F90, 0x5F42, 0x8F91, + 0x5F43, 0x8F92, 0x5F44, 0x8F93, 0x5F45, 0x8F94, 0x5F46, 0x8F95, 0x5F47, 0x8F96, 0x5F48, 0x8F97, 0x5F49, 0x8F98, 0x5F4A, 0x8F99, + 0x5F4B, 0x8F9A, 0x5F4C, 0x8F9B, 0x5F4D, 0x8F9C, 0x5F4E, 0x8F9D, 0x5F4F, 0x8F9E, 0x5F50, 0xE5E6, 0x5F51, 0x8F9F, 0x5F52, 0xB9E9, + 0x5F53, 0xB5B1, 0x5F54, 0x8FA0, 0x5F55, 0xC2BC, 0x5F56, 0xE5E8, 0x5F57, 0xE5E7, 0x5F58, 0xE5E9, 0x5F59, 0x8FA1, 0x5F5A, 0x8FA2, + 0x5F5B, 0x8FA3, 0x5F5C, 0x8FA4, 0x5F5D, 0xD2CD, 0x5F5E, 0x8FA5, 0x5F5F, 0x8FA6, 0x5F60, 0x8FA7, 0x5F61, 0xE1EA, 0x5F62, 0xD0CE, + 0x5F63, 0x8FA8, 0x5F64, 0xCDAE, 0x5F65, 0x8FA9, 0x5F66, 0xD1E5, 0x5F67, 0x8FAA, 0x5F68, 0x8FAB, 0x5F69, 0xB2CA, 0x5F6A, 0xB1EB, + 0x5F6B, 0x8FAC, 0x5F6C, 0xB1F2, 0x5F6D, 0xC5ED, 0x5F6E, 0x8FAD, 0x5F6F, 0x8FAE, 0x5F70, 0xD5C3, 0x5F71, 0xD3B0, 0x5F72, 0x8FAF, + 0x5F73, 0xE1DC, 0x5F74, 0x8FB0, 0x5F75, 0x8FB1, 0x5F76, 0x8FB2, 0x5F77, 0xE1DD, 0x5F78, 0x8FB3, 0x5F79, 0xD2DB, 0x5F7A, 0x8FB4, + 0x5F7B, 0xB3B9, 0x5F7C, 0xB1CB, 0x5F7D, 0x8FB5, 0x5F7E, 0x8FB6, 0x5F7F, 0x8FB7, 0x5F80, 0xCDF9, 0x5F81, 0xD5F7, 0x5F82, 0xE1DE, + 0x5F83, 0x8FB8, 0x5F84, 0xBEB6, 0x5F85, 0xB4FD, 0x5F86, 0x8FB9, 0x5F87, 0xE1DF, 0x5F88, 0xBADC, 0x5F89, 0xE1E0, 0x5F8A, 0xBBB2, + 0x5F8B, 0xC2C9, 0x5F8C, 0xE1E1, 0x5F8D, 0x8FBA, 0x5F8E, 0x8FBB, 0x5F8F, 0x8FBC, 0x5F90, 0xD0EC, 0x5F91, 0x8FBD, 0x5F92, 0xCDBD, + 0x5F93, 0x8FBE, 0x5F94, 0x8FBF, 0x5F95, 0xE1E2, 0x5F96, 0x8FC0, 0x5F97, 0xB5C3, 0x5F98, 0xC5C7, 0x5F99, 0xE1E3, 0x5F9A, 0x8FC1, + 0x5F9B, 0x8FC2, 0x5F9C, 0xE1E4, 0x5F9D, 0x8FC3, 0x5F9E, 0x8FC4, 0x5F9F, 0x8FC5, 0x5FA0, 0x8FC6, 0x5FA1, 0xD3F9, 0x5FA2, 0x8FC7, + 0x5FA3, 0x8FC8, 0x5FA4, 0x8FC9, 0x5FA5, 0x8FCA, 0x5FA6, 0x8FCB, 0x5FA7, 0x8FCC, 0x5FA8, 0xE1E5, 0x5FA9, 0x8FCD, 0x5FAA, 0xD1AD, + 0x5FAB, 0x8FCE, 0x5FAC, 0x8FCF, 0x5FAD, 0xE1E6, 0x5FAE, 0xCEA2, 0x5FAF, 0x8FD0, 0x5FB0, 0x8FD1, 0x5FB1, 0x8FD2, 0x5FB2, 0x8FD3, + 0x5FB3, 0x8FD4, 0x5FB4, 0x8FD5, 0x5FB5, 0xE1E7, 0x5FB6, 0x8FD6, 0x5FB7, 0xB5C2, 0x5FB8, 0x8FD7, 0x5FB9, 0x8FD8, 0x5FBA, 0x8FD9, + 0x5FBB, 0x8FDA, 0x5FBC, 0xE1E8, 0x5FBD, 0xBBD5, 0x5FBE, 0x8FDB, 0x5FBF, 0x8FDC, 0x5FC0, 0x8FDD, 0x5FC1, 0x8FDE, 0x5FC2, 0x8FDF, + 0x5FC3, 0xD0C4, 0x5FC4, 0xE2E0, 0x5FC5, 0xB1D8, 0x5FC6, 0xD2E4, 0x5FC7, 0x8FE0, 0x5FC8, 0x8FE1, 0x5FC9, 0xE2E1, 0x5FCA, 0x8FE2, + 0x5FCB, 0x8FE3, 0x5FCC, 0xBCC9, 0x5FCD, 0xC8CC, 0x5FCE, 0x8FE4, 0x5FCF, 0xE2E3, 0x5FD0, 0xECFE, 0x5FD1, 0xECFD, 0x5FD2, 0xDFAF, + 0x5FD3, 0x8FE5, 0x5FD4, 0x8FE6, 0x5FD5, 0x8FE7, 0x5FD6, 0xE2E2, 0x5FD7, 0xD6BE, 0x5FD8, 0xCDFC, 0x5FD9, 0xC3A6, 0x5FDA, 0x8FE8, + 0x5FDB, 0x8FE9, 0x5FDC, 0x8FEA, 0x5FDD, 0xE3C3, 0x5FDE, 0x8FEB, 0x5FDF, 0x8FEC, 0x5FE0, 0xD6D2, 0x5FE1, 0xE2E7, 0x5FE2, 0x8FED, + 0x5FE3, 0x8FEE, 0x5FE4, 0xE2E8, 0x5FE5, 0x8FEF, 0x5FE6, 0x8FF0, 0x5FE7, 0xD3C7, 0x5FE8, 0x8FF1, 0x5FE9, 0x8FF2, 0x5FEA, 0xE2EC, + 0x5FEB, 0xBFEC, 0x5FEC, 0x8FF3, 0x5FED, 0xE2ED, 0x5FEE, 0xE2E5, 0x5FEF, 0x8FF4, 0x5FF0, 0x8FF5, 0x5FF1, 0xB3C0, 0x5FF2, 0x8FF6, + 0x5FF3, 0x8FF7, 0x5FF4, 0x8FF8, 0x5FF5, 0xC4EE, 0x5FF6, 0x8FF9, 0x5FF7, 0x8FFA, 0x5FF8, 0xE2EE, 0x5FF9, 0x8FFB, 0x5FFA, 0x8FFC, + 0x5FFB, 0xD0C3, 0x5FFC, 0x8FFD, 0x5FFD, 0xBAF6, 0x5FFE, 0xE2E9, 0x5FFF, 0xB7DE, 0x6000, 0xBBB3, 0x6001, 0xCCAC, 0x6002, 0xCBCB, + 0x6003, 0xE2E4, 0x6004, 0xE2E6, 0x6005, 0xE2EA, 0x6006, 0xE2EB, 0x6007, 0x8FFE, 0x6008, 0x9040, 0x6009, 0x9041, 0x600A, 0xE2F7, + 0x600B, 0x9042, 0x600C, 0x9043, 0x600D, 0xE2F4, 0x600E, 0xD4F5, 0x600F, 0xE2F3, 0x6010, 0x9044, 0x6011, 0x9045, 0x6012, 0xC5AD, + 0x6013, 0x9046, 0x6014, 0xD5FA, 0x6015, 0xC5C2, 0x6016, 0xB2C0, 0x6017, 0x9047, 0x6018, 0x9048, 0x6019, 0xE2EF, 0x601A, 0x9049, + 0x601B, 0xE2F2, 0x601C, 0xC1AF, 0x601D, 0xCBBC, 0x601E, 0x904A, 0x601F, 0x904B, 0x6020, 0xB5A1, 0x6021, 0xE2F9, 0x6022, 0x904C, + 0x6023, 0x904D, 0x6024, 0x904E, 0x6025, 0xBCB1, 0x6026, 0xE2F1, 0x6027, 0xD0D4, 0x6028, 0xD4B9, 0x6029, 0xE2F5, 0x602A, 0xB9D6, + 0x602B, 0xE2F6, 0x602C, 0x904F, 0x602D, 0x9050, 0x602E, 0x9051, 0x602F, 0xC7D3, 0x6030, 0x9052, 0x6031, 0x9053, 0x6032, 0x9054, + 0x6033, 0x9055, 0x6034, 0x9056, 0x6035, 0xE2F0, 0x6036, 0x9057, 0x6037, 0x9058, 0x6038, 0x9059, 0x6039, 0x905A, 0x603A, 0x905B, + 0x603B, 0xD7DC, 0x603C, 0xEDA1, 0x603D, 0x905C, 0x603E, 0x905D, 0x603F, 0xE2F8, 0x6040, 0x905E, 0x6041, 0xEDA5, 0x6042, 0xE2FE, + 0x6043, 0xCAD1, 0x6044, 0x905F, 0x6045, 0x9060, 0x6046, 0x9061, 0x6047, 0x9062, 0x6048, 0x9063, 0x6049, 0x9064, 0x604A, 0x9065, + 0x604B, 0xC1B5, 0x604C, 0x9066, 0x604D, 0xBBD0, 0x604E, 0x9067, 0x604F, 0x9068, 0x6050, 0xBFD6, 0x6051, 0x9069, 0x6052, 0xBAE3, + 0x6053, 0x906A, 0x6054, 0x906B, 0x6055, 0xCBA1, 0x6056, 0x906C, 0x6057, 0x906D, 0x6058, 0x906E, 0x6059, 0xEDA6, 0x605A, 0xEDA3, + 0x605B, 0x906F, 0x605C, 0x9070, 0x605D, 0xEDA2, 0x605E, 0x9071, 0x605F, 0x9072, 0x6060, 0x9073, 0x6061, 0x9074, 0x6062, 0xBBD6, + 0x6063, 0xEDA7, 0x6064, 0xD0F4, 0x6065, 0x9075, 0x6066, 0x9076, 0x6067, 0xEDA4, 0x6068, 0xBADE, 0x6069, 0xB6F7, 0x606A, 0xE3A1, + 0x606B, 0xB6B2, 0x606C, 0xCCF1, 0x606D, 0xB9A7, 0x606E, 0x9077, 0x606F, 0xCFA2, 0x6070, 0xC7A1, 0x6071, 0x9078, 0x6072, 0x9079, + 0x6073, 0xBFD2, 0x6074, 0x907A, 0x6075, 0x907B, 0x6076, 0xB6F1, 0x6077, 0x907C, 0x6078, 0xE2FA, 0x6079, 0xE2FB, 0x607A, 0xE2FD, + 0x607B, 0xE2FC, 0x607C, 0xC4D5, 0x607D, 0xE3A2, 0x607E, 0x907D, 0x607F, 0xD3C1, 0x6080, 0x907E, 0x6081, 0x9080, 0x6082, 0x9081, + 0x6083, 0xE3A7, 0x6084, 0xC7C4, 0x6085, 0x9082, 0x6086, 0x9083, 0x6087, 0x9084, 0x6088, 0x9085, 0x6089, 0xCFA4, 0x608A, 0x9086, + 0x608B, 0x9087, 0x608C, 0xE3A9, 0x608D, 0xBAB7, 0x608E, 0x9088, 0x608F, 0x9089, 0x6090, 0x908A, 0x6091, 0x908B, 0x6092, 0xE3A8, + 0x6093, 0x908C, 0x6094, 0xBBDA, 0x6095, 0x908D, 0x6096, 0xE3A3, 0x6097, 0x908E, 0x6098, 0x908F, 0x6099, 0x9090, 0x609A, 0xE3A4, + 0x609B, 0xE3AA, 0x609C, 0x9091, 0x609D, 0xE3A6, 0x609E, 0x9092, 0x609F, 0xCEF2, 0x60A0, 0xD3C6, 0x60A1, 0x9093, 0x60A2, 0x9094, + 0x60A3, 0xBBBC, 0x60A4, 0x9095, 0x60A5, 0x9096, 0x60A6, 0xD4C3, 0x60A7, 0x9097, 0x60A8, 0xC4FA, 0x60A9, 0x9098, 0x60AA, 0x9099, + 0x60AB, 0xEDA8, 0x60AC, 0xD0FC, 0x60AD, 0xE3A5, 0x60AE, 0x909A, 0x60AF, 0xC3F5, 0x60B0, 0x909B, 0x60B1, 0xE3AD, 0x60B2, 0xB1AF, + 0x60B3, 0x909C, 0x60B4, 0xE3B2, 0x60B5, 0x909D, 0x60B6, 0x909E, 0x60B7, 0x909F, 0x60B8, 0xBCC2, 0x60B9, 0x90A0, 0x60BA, 0x90A1, + 0x60BB, 0xE3AC, 0x60BC, 0xB5BF, 0x60BD, 0x90A2, 0x60BE, 0x90A3, 0x60BF, 0x90A4, 0x60C0, 0x90A5, 0x60C1, 0x90A6, 0x60C2, 0x90A7, + 0x60C3, 0x90A8, 0x60C4, 0x90A9, 0x60C5, 0xC7E9, 0x60C6, 0xE3B0, 0x60C7, 0x90AA, 0x60C8, 0x90AB, 0x60C9, 0x90AC, 0x60CA, 0xBEAA, + 0x60CB, 0xCDEF, 0x60CC, 0x90AD, 0x60CD, 0x90AE, 0x60CE, 0x90AF, 0x60CF, 0x90B0, 0x60D0, 0x90B1, 0x60D1, 0xBBF3, 0x60D2, 0x90B2, + 0x60D3, 0x90B3, 0x60D4, 0x90B4, 0x60D5, 0xCCE8, 0x60D6, 0x90B5, 0x60D7, 0x90B6, 0x60D8, 0xE3AF, 0x60D9, 0x90B7, 0x60DA, 0xE3B1, + 0x60DB, 0x90B8, 0x60DC, 0xCFA7, 0x60DD, 0xE3AE, 0x60DE, 0x90B9, 0x60DF, 0xCEA9, 0x60E0, 0xBBDD, 0x60E1, 0x90BA, 0x60E2, 0x90BB, + 0x60E3, 0x90BC, 0x60E4, 0x90BD, 0x60E5, 0x90BE, 0x60E6, 0xB5EB, 0x60E7, 0xBEE5, 0x60E8, 0xB2D2, 0x60E9, 0xB3CD, 0x60EA, 0x90BF, + 0x60EB, 0xB1B9, 0x60EC, 0xE3AB, 0x60ED, 0xB2D1, 0x60EE, 0xB5AC, 0x60EF, 0xB9DF, 0x60F0, 0xB6E8, 0x60F1, 0x90C0, 0x60F2, 0x90C1, + 0x60F3, 0xCFEB, 0x60F4, 0xE3B7, 0x60F5, 0x90C2, 0x60F6, 0xBBCC, 0x60F7, 0x90C3, 0x60F8, 0x90C4, 0x60F9, 0xC8C7, 0x60FA, 0xD0CA, + 0x60FB, 0x90C5, 0x60FC, 0x90C6, 0x60FD, 0x90C7, 0x60FE, 0x90C8, 0x60FF, 0x90C9, 0x6100, 0xE3B8, 0x6101, 0xB3EE, 0x6102, 0x90CA, + 0x6103, 0x90CB, 0x6104, 0x90CC, 0x6105, 0x90CD, 0x6106, 0xEDA9, 0x6107, 0x90CE, 0x6108, 0xD3FA, 0x6109, 0xD3E4, 0x610A, 0x90CF, + 0x610B, 0x90D0, 0x610C, 0x90D1, 0x610D, 0xEDAA, 0x610E, 0xE3B9, 0x610F, 0xD2E2, 0x6110, 0x90D2, 0x6111, 0x90D3, 0x6112, 0x90D4, + 0x6113, 0x90D5, 0x6114, 0x90D6, 0x6115, 0xE3B5, 0x6116, 0x90D7, 0x6117, 0x90D8, 0x6118, 0x90D9, 0x6119, 0x90DA, 0x611A, 0xD3DE, + 0x611B, 0x90DB, 0x611C, 0x90DC, 0x611D, 0x90DD, 0x611E, 0x90DE, 0x611F, 0xB8D0, 0x6120, 0xE3B3, 0x6121, 0x90DF, 0x6122, 0x90E0, + 0x6123, 0xE3B6, 0x6124, 0xB7DF, 0x6125, 0x90E1, 0x6126, 0xE3B4, 0x6127, 0xC0A2, 0x6128, 0x90E2, 0x6129, 0x90E3, 0x612A, 0x90E4, + 0x612B, 0xE3BA, 0x612C, 0x90E5, 0x612D, 0x90E6, 0x612E, 0x90E7, 0x612F, 0x90E8, 0x6130, 0x90E9, 0x6131, 0x90EA, 0x6132, 0x90EB, + 0x6133, 0x90EC, 0x6134, 0x90ED, 0x6135, 0x90EE, 0x6136, 0x90EF, 0x6137, 0x90F0, 0x6138, 0x90F1, 0x6139, 0x90F2, 0x613A, 0x90F3, + 0x613B, 0x90F4, 0x613C, 0x90F5, 0x613D, 0x90F6, 0x613E, 0x90F7, 0x613F, 0xD4B8, 0x6140, 0x90F8, 0x6141, 0x90F9, 0x6142, 0x90FA, + 0x6143, 0x90FB, 0x6144, 0x90FC, 0x6145, 0x90FD, 0x6146, 0x90FE, 0x6147, 0x9140, 0x6148, 0xB4C8, 0x6149, 0x9141, 0x614A, 0xE3BB, + 0x614B, 0x9142, 0x614C, 0xBBC5, 0x614D, 0x9143, 0x614E, 0xC9F7, 0x614F, 0x9144, 0x6150, 0x9145, 0x6151, 0xC9E5, 0x6152, 0x9146, + 0x6153, 0x9147, 0x6154, 0x9148, 0x6155, 0xC4BD, 0x6156, 0x9149, 0x6157, 0x914A, 0x6158, 0x914B, 0x6159, 0x914C, 0x615A, 0x914D, + 0x615B, 0x914E, 0x615C, 0x914F, 0x615D, 0xEDAB, 0x615E, 0x9150, 0x615F, 0x9151, 0x6160, 0x9152, 0x6161, 0x9153, 0x6162, 0xC2FD, + 0x6163, 0x9154, 0x6164, 0x9155, 0x6165, 0x9156, 0x6166, 0x9157, 0x6167, 0xBBDB, 0x6168, 0xBFAE, 0x6169, 0x9158, 0x616A, 0x9159, + 0x616B, 0x915A, 0x616C, 0x915B, 0x616D, 0x915C, 0x616E, 0x915D, 0x616F, 0x915E, 0x6170, 0xCEBF, 0x6171, 0x915F, 0x6172, 0x9160, + 0x6173, 0x9161, 0x6174, 0x9162, 0x6175, 0xE3BC, 0x6176, 0x9163, 0x6177, 0xBFB6, 0x6178, 0x9164, 0x6179, 0x9165, 0x617A, 0x9166, + 0x617B, 0x9167, 0x617C, 0x9168, 0x617D, 0x9169, 0x617E, 0x916A, 0x617F, 0x916B, 0x6180, 0x916C, 0x6181, 0x916D, 0x6182, 0x916E, + 0x6183, 0x916F, 0x6184, 0x9170, 0x6185, 0x9171, 0x6186, 0x9172, 0x6187, 0x9173, 0x6188, 0x9174, 0x6189, 0x9175, 0x618A, 0x9176, + 0x618B, 0xB1EF, 0x618C, 0x9177, 0x618D, 0x9178, 0x618E, 0xD4F7, 0x618F, 0x9179, 0x6190, 0x917A, 0x6191, 0x917B, 0x6192, 0x917C, + 0x6193, 0x917D, 0x6194, 0xE3BE, 0x6195, 0x917E, 0x6196, 0x9180, 0x6197, 0x9181, 0x6198, 0x9182, 0x6199, 0x9183, 0x619A, 0x9184, + 0x619B, 0x9185, 0x619C, 0x9186, 0x619D, 0xEDAD, 0x619E, 0x9187, 0x619F, 0x9188, 0x61A0, 0x9189, 0x61A1, 0x918A, 0x61A2, 0x918B, + 0x61A3, 0x918C, 0x61A4, 0x918D, 0x61A5, 0x918E, 0x61A6, 0x918F, 0x61A7, 0xE3BF, 0x61A8, 0xBAA9, 0x61A9, 0xEDAC, 0x61AA, 0x9190, + 0x61AB, 0x9191, 0x61AC, 0xE3BD, 0x61AD, 0x9192, 0x61AE, 0x9193, 0x61AF, 0x9194, 0x61B0, 0x9195, 0x61B1, 0x9196, 0x61B2, 0x9197, + 0x61B3, 0x9198, 0x61B4, 0x9199, 0x61B5, 0x919A, 0x61B6, 0x919B, 0x61B7, 0xE3C0, 0x61B8, 0x919C, 0x61B9, 0x919D, 0x61BA, 0x919E, + 0x61BB, 0x919F, 0x61BC, 0x91A0, 0x61BD, 0x91A1, 0x61BE, 0xBAB6, 0x61BF, 0x91A2, 0x61C0, 0x91A3, 0x61C1, 0x91A4, 0x61C2, 0xB6AE, + 0x61C3, 0x91A5, 0x61C4, 0x91A6, 0x61C5, 0x91A7, 0x61C6, 0x91A8, 0x61C7, 0x91A9, 0x61C8, 0xD0B8, 0x61C9, 0x91AA, 0x61CA, 0xB0C3, + 0x61CB, 0xEDAE, 0x61CC, 0x91AB, 0x61CD, 0x91AC, 0x61CE, 0x91AD, 0x61CF, 0x91AE, 0x61D0, 0x91AF, 0x61D1, 0xEDAF, 0x61D2, 0xC0C1, + 0x61D3, 0x91B0, 0x61D4, 0xE3C1, 0x61D5, 0x91B1, 0x61D6, 0x91B2, 0x61D7, 0x91B3, 0x61D8, 0x91B4, 0x61D9, 0x91B5, 0x61DA, 0x91B6, + 0x61DB, 0x91B7, 0x61DC, 0x91B8, 0x61DD, 0x91B9, 0x61DE, 0x91BA, 0x61DF, 0x91BB, 0x61E0, 0x91BC, 0x61E1, 0x91BD, 0x61E2, 0x91BE, + 0x61E3, 0x91BF, 0x61E4, 0x91C0, 0x61E5, 0x91C1, 0x61E6, 0xC5B3, 0x61E7, 0x91C2, 0x61E8, 0x91C3, 0x61E9, 0x91C4, 0x61EA, 0x91C5, + 0x61EB, 0x91C6, 0x61EC, 0x91C7, 0x61ED, 0x91C8, 0x61EE, 0x91C9, 0x61EF, 0x91CA, 0x61F0, 0x91CB, 0x61F1, 0x91CC, 0x61F2, 0x91CD, + 0x61F3, 0x91CE, 0x61F4, 0x91CF, 0x61F5, 0xE3C2, 0x61F6, 0x91D0, 0x61F7, 0x91D1, 0x61F8, 0x91D2, 0x61F9, 0x91D3, 0x61FA, 0x91D4, + 0x61FB, 0x91D5, 0x61FC, 0x91D6, 0x61FD, 0x91D7, 0x61FE, 0x91D8, 0x61FF, 0xDCB2, 0x6200, 0x91D9, 0x6201, 0x91DA, 0x6202, 0x91DB, + 0x6203, 0x91DC, 0x6204, 0x91DD, 0x6205, 0x91DE, 0x6206, 0xEDB0, 0x6207, 0x91DF, 0x6208, 0xB8EA, 0x6209, 0x91E0, 0x620A, 0xCEEC, + 0x620B, 0xEAA7, 0x620C, 0xD0E7, 0x620D, 0xCAF9, 0x620E, 0xC8D6, 0x620F, 0xCFB7, 0x6210, 0xB3C9, 0x6211, 0xCED2, 0x6212, 0xBDE4, + 0x6213, 0x91E1, 0x6214, 0x91E2, 0x6215, 0xE3DE, 0x6216, 0xBBF2, 0x6217, 0xEAA8, 0x6218, 0xD5BD, 0x6219, 0x91E3, 0x621A, 0xC6DD, + 0x621B, 0xEAA9, 0x621C, 0x91E4, 0x621D, 0x91E5, 0x621E, 0x91E6, 0x621F, 0xEAAA, 0x6220, 0x91E7, 0x6221, 0xEAAC, 0x6222, 0xEAAB, + 0x6223, 0x91E8, 0x6224, 0xEAAE, 0x6225, 0xEAAD, 0x6226, 0x91E9, 0x6227, 0x91EA, 0x6228, 0x91EB, 0x6229, 0x91EC, 0x622A, 0xBDD8, + 0x622B, 0x91ED, 0x622C, 0xEAAF, 0x622D, 0x91EE, 0x622E, 0xC2BE, 0x622F, 0x91EF, 0x6230, 0x91F0, 0x6231, 0x91F1, 0x6232, 0x91F2, + 0x6233, 0xB4C1, 0x6234, 0xB4F7, 0x6235, 0x91F3, 0x6236, 0x91F4, 0x6237, 0xBBA7, 0x6238, 0x91F5, 0x6239, 0x91F6, 0x623A, 0x91F7, + 0x623B, 0x91F8, 0x623C, 0x91F9, 0x623D, 0xECE6, 0x623E, 0xECE5, 0x623F, 0xB7BF, 0x6240, 0xCBF9, 0x6241, 0xB1E2, 0x6242, 0x91FA, + 0x6243, 0xECE7, 0x6244, 0x91FB, 0x6245, 0x91FC, 0x6246, 0x91FD, 0x6247, 0xC9C8, 0x6248, 0xECE8, 0x6249, 0xECE9, 0x624A, 0x91FE, + 0x624B, 0xCAD6, 0x624C, 0xDED0, 0x624D, 0xB2C5, 0x624E, 0xD4FA, 0x624F, 0x9240, 0x6250, 0x9241, 0x6251, 0xC6CB, 0x6252, 0xB0C7, + 0x6253, 0xB4F2, 0x6254, 0xC8D3, 0x6255, 0x9242, 0x6256, 0x9243, 0x6257, 0x9244, 0x6258, 0xCDD0, 0x6259, 0x9245, 0x625A, 0x9246, + 0x625B, 0xBFB8, 0x625C, 0x9247, 0x625D, 0x9248, 0x625E, 0x9249, 0x625F, 0x924A, 0x6260, 0x924B, 0x6261, 0x924C, 0x6262, 0x924D, + 0x6263, 0xBFDB, 0x6264, 0x924E, 0x6265, 0x924F, 0x6266, 0xC7A4, 0x6267, 0xD6B4, 0x6268, 0x9250, 0x6269, 0xC0A9, 0x626A, 0xDED1, + 0x626B, 0xC9A8, 0x626C, 0xD1EF, 0x626D, 0xC5A4, 0x626E, 0xB0E7, 0x626F, 0xB3B6, 0x6270, 0xC8C5, 0x6271, 0x9251, 0x6272, 0x9252, + 0x6273, 0xB0E2, 0x6274, 0x9253, 0x6275, 0x9254, 0x6276, 0xB7F6, 0x6277, 0x9255, 0x6278, 0x9256, 0x6279, 0xC5FA, 0x627A, 0x9257, + 0x627B, 0x9258, 0x627C, 0xB6F3, 0x627D, 0x9259, 0x627E, 0xD5D2, 0x627F, 0xB3D0, 0x6280, 0xBCBC, 0x6281, 0x925A, 0x6282, 0x925B, + 0x6283, 0x925C, 0x6284, 0xB3AD, 0x6285, 0x925D, 0x6286, 0x925E, 0x6287, 0x925F, 0x6288, 0x9260, 0x6289, 0xBEF1, 0x628A, 0xB0D1, + 0x628B, 0x9261, 0x628C, 0x9262, 0x628D, 0x9263, 0x628E, 0x9264, 0x628F, 0x9265, 0x6290, 0x9266, 0x6291, 0xD2D6, 0x6292, 0xCAE3, + 0x6293, 0xD7A5, 0x6294, 0x9267, 0x6295, 0xCDB6, 0x6296, 0xB6B6, 0x6297, 0xBFB9, 0x6298, 0xD5DB, 0x6299, 0x9268, 0x629A, 0xB8A7, + 0x629B, 0xC5D7, 0x629C, 0x9269, 0x629D, 0x926A, 0x629E, 0x926B, 0x629F, 0xDED2, 0x62A0, 0xBFD9, 0x62A1, 0xC2D5, 0x62A2, 0xC7C0, + 0x62A3, 0x926C, 0x62A4, 0xBBA4, 0x62A5, 0xB1A8, 0x62A6, 0x926D, 0x62A7, 0x926E, 0x62A8, 0xC5EA, 0x62A9, 0x926F, 0x62AA, 0x9270, + 0x62AB, 0xC5FB, 0x62AC, 0xCCA7, 0x62AD, 0x9271, 0x62AE, 0x9272, 0x62AF, 0x9273, 0x62B0, 0x9274, 0x62B1, 0xB1A7, 0x62B2, 0x9275, + 0x62B3, 0x9276, 0x62B4, 0x9277, 0x62B5, 0xB5D6, 0x62B6, 0x9278, 0x62B7, 0x9279, 0x62B8, 0x927A, 0x62B9, 0xC4A8, 0x62BA, 0x927B, + 0x62BB, 0xDED3, 0x62BC, 0xD1BA, 0x62BD, 0xB3E9, 0x62BE, 0x927C, 0x62BF, 0xC3F2, 0x62C0, 0x927D, 0x62C1, 0x927E, 0x62C2, 0xB7F7, + 0x62C3, 0x9280, 0x62C4, 0xD6F4, 0x62C5, 0xB5A3, 0x62C6, 0xB2F0, 0x62C7, 0xC4B4, 0x62C8, 0xC4E9, 0x62C9, 0xC0AD, 0x62CA, 0xDED4, + 0x62CB, 0x9281, 0x62CC, 0xB0E8, 0x62CD, 0xC5C4, 0x62CE, 0xC1E0, 0x62CF, 0x9282, 0x62D0, 0xB9D5, 0x62D1, 0x9283, 0x62D2, 0xBEDC, + 0x62D3, 0xCDD8, 0x62D4, 0xB0CE, 0x62D5, 0x9284, 0x62D6, 0xCDCF, 0x62D7, 0xDED6, 0x62D8, 0xBED0, 0x62D9, 0xD7BE, 0x62DA, 0xDED5, + 0x62DB, 0xD5D0, 0x62DC, 0xB0DD, 0x62DD, 0x9285, 0x62DE, 0x9286, 0x62DF, 0xC4E2, 0x62E0, 0x9287, 0x62E1, 0x9288, 0x62E2, 0xC2A3, + 0x62E3, 0xBCF0, 0x62E4, 0x9289, 0x62E5, 0xD3B5, 0x62E6, 0xC0B9, 0x62E7, 0xC5A1, 0x62E8, 0xB2A6, 0x62E9, 0xD4F1, 0x62EA, 0x928A, + 0x62EB, 0x928B, 0x62EC, 0xC0A8, 0x62ED, 0xCAC3, 0x62EE, 0xDED7, 0x62EF, 0xD5FC, 0x62F0, 0x928C, 0x62F1, 0xB9B0, 0x62F2, 0x928D, + 0x62F3, 0xC8AD, 0x62F4, 0xCBA9, 0x62F5, 0x928E, 0x62F6, 0xDED9, 0x62F7, 0xBFBD, 0x62F8, 0x928F, 0x62F9, 0x9290, 0x62FA, 0x9291, + 0x62FB, 0x9292, 0x62FC, 0xC6B4, 0x62FD, 0xD7A7, 0x62FE, 0xCAB0, 0x62FF, 0xC4C3, 0x6300, 0x9293, 0x6301, 0xB3D6, 0x6302, 0xB9D2, + 0x6303, 0x9294, 0x6304, 0x9295, 0x6305, 0x9296, 0x6306, 0x9297, 0x6307, 0xD6B8, 0x6308, 0xEAFC, 0x6309, 0xB0B4, 0x630A, 0x9298, + 0x630B, 0x9299, 0x630C, 0x929A, 0x630D, 0x929B, 0x630E, 0xBFE6, 0x630F, 0x929C, 0x6310, 0x929D, 0x6311, 0xCCF4, 0x6312, 0x929E, + 0x6313, 0x929F, 0x6314, 0x92A0, 0x6315, 0x92A1, 0x6316, 0xCDDA, 0x6317, 0x92A2, 0x6318, 0x92A3, 0x6319, 0x92A4, 0x631A, 0xD6BF, + 0x631B, 0xC2CE, 0x631C, 0x92A5, 0x631D, 0xCECE, 0x631E, 0xCCA2, 0x631F, 0xD0AE, 0x6320, 0xC4D3, 0x6321, 0xB5B2, 0x6322, 0xDED8, + 0x6323, 0xD5F5, 0x6324, 0xBCB7, 0x6325, 0xBBD3, 0x6326, 0x92A6, 0x6327, 0x92A7, 0x6328, 0xB0A4, 0x6329, 0x92A8, 0x632A, 0xC5B2, + 0x632B, 0xB4EC, 0x632C, 0x92A9, 0x632D, 0x92AA, 0x632E, 0x92AB, 0x632F, 0xD5F1, 0x6330, 0x92AC, 0x6331, 0x92AD, 0x6332, 0xEAFD, + 0x6333, 0x92AE, 0x6334, 0x92AF, 0x6335, 0x92B0, 0x6336, 0x92B1, 0x6337, 0x92B2, 0x6338, 0x92B3, 0x6339, 0xDEDA, 0x633A, 0xCDA6, + 0x633B, 0x92B4, 0x633C, 0x92B5, 0x633D, 0xCDEC, 0x633E, 0x92B6, 0x633F, 0x92B7, 0x6340, 0x92B8, 0x6341, 0x92B9, 0x6342, 0xCEE6, + 0x6343, 0xDEDC, 0x6344, 0x92BA, 0x6345, 0xCDB1, 0x6346, 0xC0A6, 0x6347, 0x92BB, 0x6348, 0x92BC, 0x6349, 0xD7BD, 0x634A, 0x92BD, + 0x634B, 0xDEDB, 0x634C, 0xB0C6, 0x634D, 0xBAB4, 0x634E, 0xC9D3, 0x634F, 0xC4F3, 0x6350, 0xBEE8, 0x6351, 0x92BE, 0x6352, 0x92BF, + 0x6353, 0x92C0, 0x6354, 0x92C1, 0x6355, 0xB2B6, 0x6356, 0x92C2, 0x6357, 0x92C3, 0x6358, 0x92C4, 0x6359, 0x92C5, 0x635A, 0x92C6, + 0x635B, 0x92C7, 0x635C, 0x92C8, 0x635D, 0x92C9, 0x635E, 0xC0CC, 0x635F, 0xCBF0, 0x6360, 0x92CA, 0x6361, 0xBCF1, 0x6362, 0xBBBB, + 0x6363, 0xB5B7, 0x6364, 0x92CB, 0x6365, 0x92CC, 0x6366, 0x92CD, 0x6367, 0xC5F5, 0x6368, 0x92CE, 0x6369, 0xDEE6, 0x636A, 0x92CF, + 0x636B, 0x92D0, 0x636C, 0x92D1, 0x636D, 0xDEE3, 0x636E, 0xBEDD, 0x636F, 0x92D2, 0x6370, 0x92D3, 0x6371, 0xDEDF, 0x6372, 0x92D4, + 0x6373, 0x92D5, 0x6374, 0x92D6, 0x6375, 0x92D7, 0x6376, 0xB4B7, 0x6377, 0xBDDD, 0x6378, 0x92D8, 0x6379, 0x92D9, 0x637A, 0xDEE0, + 0x637B, 0xC4ED, 0x637C, 0x92DA, 0x637D, 0x92DB, 0x637E, 0x92DC, 0x637F, 0x92DD, 0x6380, 0xCFC6, 0x6381, 0x92DE, 0x6382, 0xB5E0, + 0x6383, 0x92DF, 0x6384, 0x92E0, 0x6385, 0x92E1, 0x6386, 0x92E2, 0x6387, 0xB6DE, 0x6388, 0xCADA, 0x6389, 0xB5F4, 0x638A, 0xDEE5, + 0x638B, 0x92E3, 0x638C, 0xD5C6, 0x638D, 0x92E4, 0x638E, 0xDEE1, 0x638F, 0xCCCD, 0x6390, 0xC6FE, 0x6391, 0x92E5, 0x6392, 0xC5C5, + 0x6393, 0x92E6, 0x6394, 0x92E7, 0x6395, 0x92E8, 0x6396, 0xD2B4, 0x6397, 0x92E9, 0x6398, 0xBEF2, 0x6399, 0x92EA, 0x639A, 0x92EB, + 0x639B, 0x92EC, 0x639C, 0x92ED, 0x639D, 0x92EE, 0x639E, 0x92EF, 0x639F, 0x92F0, 0x63A0, 0xC2D3, 0x63A1, 0x92F1, 0x63A2, 0xCCBD, + 0x63A3, 0xB3B8, 0x63A4, 0x92F2, 0x63A5, 0xBDD3, 0x63A6, 0x92F3, 0x63A7, 0xBFD8, 0x63A8, 0xCDC6, 0x63A9, 0xD1DA, 0x63AA, 0xB4EB, + 0x63AB, 0x92F4, 0x63AC, 0xDEE4, 0x63AD, 0xDEDD, 0x63AE, 0xDEE7, 0x63AF, 0x92F5, 0x63B0, 0xEAFE, 0x63B1, 0x92F6, 0x63B2, 0x92F7, + 0x63B3, 0xC2B0, 0x63B4, 0xDEE2, 0x63B5, 0x92F8, 0x63B6, 0x92F9, 0x63B7, 0xD6C0, 0x63B8, 0xB5A7, 0x63B9, 0x92FA, 0x63BA, 0xB2F4, + 0x63BB, 0x92FB, 0x63BC, 0xDEE8, 0x63BD, 0x92FC, 0x63BE, 0xDEF2, 0x63BF, 0x92FD, 0x63C0, 0x92FE, 0x63C1, 0x9340, 0x63C2, 0x9341, + 0x63C3, 0x9342, 0x63C4, 0xDEED, 0x63C5, 0x9343, 0x63C6, 0xDEF1, 0x63C7, 0x9344, 0x63C8, 0x9345, 0x63C9, 0xC8E0, 0x63CA, 0x9346, + 0x63CB, 0x9347, 0x63CC, 0x9348, 0x63CD, 0xD7E1, 0x63CE, 0xDEEF, 0x63CF, 0xC3E8, 0x63D0, 0xCCE1, 0x63D1, 0x9349, 0x63D2, 0xB2E5, + 0x63D3, 0x934A, 0x63D4, 0x934B, 0x63D5, 0x934C, 0x63D6, 0xD2BE, 0x63D7, 0x934D, 0x63D8, 0x934E, 0x63D9, 0x934F, 0x63DA, 0x9350, + 0x63DB, 0x9351, 0x63DC, 0x9352, 0x63DD, 0x9353, 0x63DE, 0xDEEE, 0x63DF, 0x9354, 0x63E0, 0xDEEB, 0x63E1, 0xCED5, 0x63E2, 0x9355, + 0x63E3, 0xB4A7, 0x63E4, 0x9356, 0x63E5, 0x9357, 0x63E6, 0x9358, 0x63E7, 0x9359, 0x63E8, 0x935A, 0x63E9, 0xBFAB, 0x63EA, 0xBEBE, + 0x63EB, 0x935B, 0x63EC, 0x935C, 0x63ED, 0xBDD2, 0x63EE, 0x935D, 0x63EF, 0x935E, 0x63F0, 0x935F, 0x63F1, 0x9360, 0x63F2, 0xDEE9, + 0x63F3, 0x9361, 0x63F4, 0xD4AE, 0x63F5, 0x9362, 0x63F6, 0xDEDE, 0x63F7, 0x9363, 0x63F8, 0xDEEA, 0x63F9, 0x9364, 0x63FA, 0x9365, + 0x63FB, 0x9366, 0x63FC, 0x9367, 0x63FD, 0xC0BF, 0x63FE, 0x9368, 0x63FF, 0xDEEC, 0x6400, 0xB2F3, 0x6401, 0xB8E9, 0x6402, 0xC2A7, + 0x6403, 0x9369, 0x6404, 0x936A, 0x6405, 0xBDC1, 0x6406, 0x936B, 0x6407, 0x936C, 0x6408, 0x936D, 0x6409, 0x936E, 0x640A, 0x936F, + 0x640B, 0xDEF5, 0x640C, 0xDEF8, 0x640D, 0x9370, 0x640E, 0x9371, 0x640F, 0xB2AB, 0x6410, 0xB4A4, 0x6411, 0x9372, 0x6412, 0x9373, + 0x6413, 0xB4EA, 0x6414, 0xC9A6, 0x6415, 0x9374, 0x6416, 0x9375, 0x6417, 0x9376, 0x6418, 0x9377, 0x6419, 0x9378, 0x641A, 0x9379, + 0x641B, 0xDEF6, 0x641C, 0xCBD1, 0x641D, 0x937A, 0x641E, 0xB8E3, 0x641F, 0x937B, 0x6420, 0xDEF7, 0x6421, 0xDEFA, 0x6422, 0x937C, + 0x6423, 0x937D, 0x6424, 0x937E, 0x6425, 0x9380, 0x6426, 0xDEF9, 0x6427, 0x9381, 0x6428, 0x9382, 0x6429, 0x9383, 0x642A, 0xCCC2, + 0x642B, 0x9384, 0x642C, 0xB0E1, 0x642D, 0xB4EE, 0x642E, 0x9385, 0x642F, 0x9386, 0x6430, 0x9387, 0x6431, 0x9388, 0x6432, 0x9389, + 0x6433, 0x938A, 0x6434, 0xE5BA, 0x6435, 0x938B, 0x6436, 0x938C, 0x6437, 0x938D, 0x6438, 0x938E, 0x6439, 0x938F, 0x643A, 0xD0AF, + 0x643B, 0x9390, 0x643C, 0x9391, 0x643D, 0xB2EB, 0x643E, 0x9392, 0x643F, 0xEBA1, 0x6440, 0x9393, 0x6441, 0xDEF4, 0x6442, 0x9394, + 0x6443, 0x9395, 0x6444, 0xC9E3, 0x6445, 0xDEF3, 0x6446, 0xB0DA, 0x6447, 0xD2A1, 0x6448, 0xB1F7, 0x6449, 0x9396, 0x644A, 0xCCAF, + 0x644B, 0x9397, 0x644C, 0x9398, 0x644D, 0x9399, 0x644E, 0x939A, 0x644F, 0x939B, 0x6450, 0x939C, 0x6451, 0x939D, 0x6452, 0xDEF0, + 0x6453, 0x939E, 0x6454, 0xCBA4, 0x6455, 0x939F, 0x6456, 0x93A0, 0x6457, 0x93A1, 0x6458, 0xD5AA, 0x6459, 0x93A2, 0x645A, 0x93A3, + 0x645B, 0x93A4, 0x645C, 0x93A5, 0x645D, 0x93A6, 0x645E, 0xDEFB, 0x645F, 0x93A7, 0x6460, 0x93A8, 0x6461, 0x93A9, 0x6462, 0x93AA, + 0x6463, 0x93AB, 0x6464, 0x93AC, 0x6465, 0x93AD, 0x6466, 0x93AE, 0x6467, 0xB4DD, 0x6468, 0x93AF, 0x6469, 0xC4A6, 0x646A, 0x93B0, + 0x646B, 0x93B1, 0x646C, 0x93B2, 0x646D, 0xDEFD, 0x646E, 0x93B3, 0x646F, 0x93B4, 0x6470, 0x93B5, 0x6471, 0x93B6, 0x6472, 0x93B7, + 0x6473, 0x93B8, 0x6474, 0x93B9, 0x6475, 0x93BA, 0x6476, 0x93BB, 0x6477, 0x93BC, 0x6478, 0xC3FE, 0x6479, 0xC4A1, 0x647A, 0xDFA1, + 0x647B, 0x93BD, 0x647C, 0x93BE, 0x647D, 0x93BF, 0x647E, 0x93C0, 0x647F, 0x93C1, 0x6480, 0x93C2, 0x6481, 0x93C3, 0x6482, 0xC1CC, + 0x6483, 0x93C4, 0x6484, 0xDEFC, 0x6485, 0xBEEF, 0x6486, 0x93C5, 0x6487, 0xC6B2, 0x6488, 0x93C6, 0x6489, 0x93C7, 0x648A, 0x93C8, + 0x648B, 0x93C9, 0x648C, 0x93CA, 0x648D, 0x93CB, 0x648E, 0x93CC, 0x648F, 0x93CD, 0x6490, 0x93CE, 0x6491, 0xB3C5, 0x6492, 0xC8F6, + 0x6493, 0x93CF, 0x6494, 0x93D0, 0x6495, 0xCBBA, 0x6496, 0xDEFE, 0x6497, 0x93D1, 0x6498, 0x93D2, 0x6499, 0xDFA4, 0x649A, 0x93D3, + 0x649B, 0x93D4, 0x649C, 0x93D5, 0x649D, 0x93D6, 0x649E, 0xD7B2, 0x649F, 0x93D7, 0x64A0, 0x93D8, 0x64A1, 0x93D9, 0x64A2, 0x93DA, + 0x64A3, 0x93DB, 0x64A4, 0xB3B7, 0x64A5, 0x93DC, 0x64A6, 0x93DD, 0x64A7, 0x93DE, 0x64A8, 0x93DF, 0x64A9, 0xC1C3, 0x64AA, 0x93E0, + 0x64AB, 0x93E1, 0x64AC, 0xC7CB, 0x64AD, 0xB2A5, 0x64AE, 0xB4E9, 0x64AF, 0x93E2, 0x64B0, 0xD7AB, 0x64B1, 0x93E3, 0x64B2, 0x93E4, + 0x64B3, 0x93E5, 0x64B4, 0x93E6, 0x64B5, 0xC4EC, 0x64B6, 0x93E7, 0x64B7, 0xDFA2, 0x64B8, 0xDFA3, 0x64B9, 0x93E8, 0x64BA, 0xDFA5, + 0x64BB, 0x93E9, 0x64BC, 0xBAB3, 0x64BD, 0x93EA, 0x64BE, 0x93EB, 0x64BF, 0x93EC, 0x64C0, 0xDFA6, 0x64C1, 0x93ED, 0x64C2, 0xC0DE, + 0x64C3, 0x93EE, 0x64C4, 0x93EF, 0x64C5, 0xC9C3, 0x64C6, 0x93F0, 0x64C7, 0x93F1, 0x64C8, 0x93F2, 0x64C9, 0x93F3, 0x64CA, 0x93F4, + 0x64CB, 0x93F5, 0x64CC, 0x93F6, 0x64CD, 0xB2D9, 0x64CE, 0xC7E6, 0x64CF, 0x93F7, 0x64D0, 0xDFA7, 0x64D1, 0x93F8, 0x64D2, 0xC7DC, + 0x64D3, 0x93F9, 0x64D4, 0x93FA, 0x64D5, 0x93FB, 0x64D6, 0x93FC, 0x64D7, 0xDFA8, 0x64D8, 0xEBA2, 0x64D9, 0x93FD, 0x64DA, 0x93FE, + 0x64DB, 0x9440, 0x64DC, 0x9441, 0x64DD, 0x9442, 0x64DE, 0xCBD3, 0x64DF, 0x9443, 0x64E0, 0x9444, 0x64E1, 0x9445, 0x64E2, 0xDFAA, + 0x64E3, 0x9446, 0x64E4, 0xDFA9, 0x64E5, 0x9447, 0x64E6, 0xB2C1, 0x64E7, 0x9448, 0x64E8, 0x9449, 0x64E9, 0x944A, 0x64EA, 0x944B, + 0x64EB, 0x944C, 0x64EC, 0x944D, 0x64ED, 0x944E, 0x64EE, 0x944F, 0x64EF, 0x9450, 0x64F0, 0x9451, 0x64F1, 0x9452, 0x64F2, 0x9453, + 0x64F3, 0x9454, 0x64F4, 0x9455, 0x64F5, 0x9456, 0x64F6, 0x9457, 0x64F7, 0x9458, 0x64F8, 0x9459, 0x64F9, 0x945A, 0x64FA, 0x945B, + 0x64FB, 0x945C, 0x64FC, 0x945D, 0x64FD, 0x945E, 0x64FE, 0x945F, 0x64FF, 0x9460, 0x6500, 0xC5CA, 0x6501, 0x9461, 0x6502, 0x9462, + 0x6503, 0x9463, 0x6504, 0x9464, 0x6505, 0x9465, 0x6506, 0x9466, 0x6507, 0x9467, 0x6508, 0x9468, 0x6509, 0xDFAB, 0x650A, 0x9469, + 0x650B, 0x946A, 0x650C, 0x946B, 0x650D, 0x946C, 0x650E, 0x946D, 0x650F, 0x946E, 0x6510, 0x946F, 0x6511, 0x9470, 0x6512, 0xD4DC, + 0x6513, 0x9471, 0x6514, 0x9472, 0x6515, 0x9473, 0x6516, 0x9474, 0x6517, 0x9475, 0x6518, 0xC8C1, 0x6519, 0x9476, 0x651A, 0x9477, + 0x651B, 0x9478, 0x651C, 0x9479, 0x651D, 0x947A, 0x651E, 0x947B, 0x651F, 0x947C, 0x6520, 0x947D, 0x6521, 0x947E, 0x6522, 0x9480, + 0x6523, 0x9481, 0x6524, 0x9482, 0x6525, 0xDFAC, 0x6526, 0x9483, 0x6527, 0x9484, 0x6528, 0x9485, 0x6529, 0x9486, 0x652A, 0x9487, + 0x652B, 0xBEF0, 0x652C, 0x9488, 0x652D, 0x9489, 0x652E, 0xDFAD, 0x652F, 0xD6A7, 0x6530, 0x948A, 0x6531, 0x948B, 0x6532, 0x948C, + 0x6533, 0x948D, 0x6534, 0xEAB7, 0x6535, 0xEBB6, 0x6536, 0xCAD5, 0x6537, 0x948E, 0x6538, 0xD8FC, 0x6539, 0xB8C4, 0x653A, 0x948F, + 0x653B, 0xB9A5, 0x653C, 0x9490, 0x653D, 0x9491, 0x653E, 0xB7C5, 0x653F, 0xD5FE, 0x6540, 0x9492, 0x6541, 0x9493, 0x6542, 0x9494, + 0x6543, 0x9495, 0x6544, 0x9496, 0x6545, 0xB9CA, 0x6546, 0x9497, 0x6547, 0x9498, 0x6548, 0xD0A7, 0x6549, 0xF4CD, 0x654A, 0x9499, + 0x654B, 0x949A, 0x654C, 0xB5D0, 0x654D, 0x949B, 0x654E, 0x949C, 0x654F, 0xC3F4, 0x6550, 0x949D, 0x6551, 0xBEC8, 0x6552, 0x949E, + 0x6553, 0x949F, 0x6554, 0x94A0, 0x6555, 0xEBB7, 0x6556, 0xB0BD, 0x6557, 0x94A1, 0x6558, 0x94A2, 0x6559, 0xBDCC, 0x655A, 0x94A3, + 0x655B, 0xC1B2, 0x655C, 0x94A4, 0x655D, 0xB1D6, 0x655E, 0xB3A8, 0x655F, 0x94A5, 0x6560, 0x94A6, 0x6561, 0x94A7, 0x6562, 0xB8D2, + 0x6563, 0xC9A2, 0x6564, 0x94A8, 0x6565, 0x94A9, 0x6566, 0xB6D8, 0x6567, 0x94AA, 0x6568, 0x94AB, 0x6569, 0x94AC, 0x656A, 0x94AD, + 0x656B, 0xEBB8, 0x656C, 0xBEB4, 0x656D, 0x94AE, 0x656E, 0x94AF, 0x656F, 0x94B0, 0x6570, 0xCAFD, 0x6571, 0x94B1, 0x6572, 0xC7C3, + 0x6573, 0x94B2, 0x6574, 0xD5FB, 0x6575, 0x94B3, 0x6576, 0x94B4, 0x6577, 0xB7F3, 0x6578, 0x94B5, 0x6579, 0x94B6, 0x657A, 0x94B7, + 0x657B, 0x94B8, 0x657C, 0x94B9, 0x657D, 0x94BA, 0x657E, 0x94BB, 0x657F, 0x94BC, 0x6580, 0x94BD, 0x6581, 0x94BE, 0x6582, 0x94BF, + 0x6583, 0x94C0, 0x6584, 0x94C1, 0x6585, 0x94C2, 0x6586, 0x94C3, 0x6587, 0xCEC4, 0x6588, 0x94C4, 0x6589, 0x94C5, 0x658A, 0x94C6, + 0x658B, 0xD5AB, 0x658C, 0xB1F3, 0x658D, 0x94C7, 0x658E, 0x94C8, 0x658F, 0x94C9, 0x6590, 0xECB3, 0x6591, 0xB0DF, 0x6592, 0x94CA, + 0x6593, 0xECB5, 0x6594, 0x94CB, 0x6595, 0x94CC, 0x6596, 0x94CD, 0x6597, 0xB6B7, 0x6598, 0x94CE, 0x6599, 0xC1CF, 0x659A, 0x94CF, + 0x659B, 0xF5FA, 0x659C, 0xD0B1, 0x659D, 0x94D0, 0x659E, 0x94D1, 0x659F, 0xD5E5, 0x65A0, 0x94D2, 0x65A1, 0xCED3, 0x65A2, 0x94D3, + 0x65A3, 0x94D4, 0x65A4, 0xBDEF, 0x65A5, 0xB3E2, 0x65A6, 0x94D5, 0x65A7, 0xB8AB, 0x65A8, 0x94D6, 0x65A9, 0xD5B6, 0x65AA, 0x94D7, + 0x65AB, 0xEDBD, 0x65AC, 0x94D8, 0x65AD, 0xB6CF, 0x65AE, 0x94D9, 0x65AF, 0xCBB9, 0x65B0, 0xD0C2, 0x65B1, 0x94DA, 0x65B2, 0x94DB, + 0x65B3, 0x94DC, 0x65B4, 0x94DD, 0x65B5, 0x94DE, 0x65B6, 0x94DF, 0x65B7, 0x94E0, 0x65B8, 0x94E1, 0x65B9, 0xB7BD, 0x65BA, 0x94E2, + 0x65BB, 0x94E3, 0x65BC, 0xECB6, 0x65BD, 0xCAA9, 0x65BE, 0x94E4, 0x65BF, 0x94E5, 0x65C0, 0x94E6, 0x65C1, 0xC5D4, 0x65C2, 0x94E7, + 0x65C3, 0xECB9, 0x65C4, 0xECB8, 0x65C5, 0xC2C3, 0x65C6, 0xECB7, 0x65C7, 0x94E8, 0x65C8, 0x94E9, 0x65C9, 0x94EA, 0x65CA, 0x94EB, + 0x65CB, 0xD0FD, 0x65CC, 0xECBA, 0x65CD, 0x94EC, 0x65CE, 0xECBB, 0x65CF, 0xD7E5, 0x65D0, 0x94ED, 0x65D1, 0x94EE, 0x65D2, 0xECBC, + 0x65D3, 0x94EF, 0x65D4, 0x94F0, 0x65D5, 0x94F1, 0x65D6, 0xECBD, 0x65D7, 0xC6EC, 0x65D8, 0x94F2, 0x65D9, 0x94F3, 0x65DA, 0x94F4, + 0x65DB, 0x94F5, 0x65DC, 0x94F6, 0x65DD, 0x94F7, 0x65DE, 0x94F8, 0x65DF, 0x94F9, 0x65E0, 0xCEDE, 0x65E1, 0x94FA, 0x65E2, 0xBCC8, + 0x65E3, 0x94FB, 0x65E4, 0x94FC, 0x65E5, 0xC8D5, 0x65E6, 0xB5A9, 0x65E7, 0xBEC9, 0x65E8, 0xD6BC, 0x65E9, 0xD4E7, 0x65EA, 0x94FD, + 0x65EB, 0x94FE, 0x65EC, 0xD1AE, 0x65ED, 0xD0F1, 0x65EE, 0xEAB8, 0x65EF, 0xEAB9, 0x65F0, 0xEABA, 0x65F1, 0xBAB5, 0x65F2, 0x9540, + 0x65F3, 0x9541, 0x65F4, 0x9542, 0x65F5, 0x9543, 0x65F6, 0xCAB1, 0x65F7, 0xBFF5, 0x65F8, 0x9544, 0x65F9, 0x9545, 0x65FA, 0xCDFA, + 0x65FB, 0x9546, 0x65FC, 0x9547, 0x65FD, 0x9548, 0x65FE, 0x9549, 0x65FF, 0x954A, 0x6600, 0xEAC0, 0x6601, 0x954B, 0x6602, 0xB0BA, + 0x6603, 0xEABE, 0x6604, 0x954C, 0x6605, 0x954D, 0x6606, 0xC0A5, 0x6607, 0x954E, 0x6608, 0x954F, 0x6609, 0x9550, 0x660A, 0xEABB, + 0x660B, 0x9551, 0x660C, 0xB2FD, 0x660D, 0x9552, 0x660E, 0xC3F7, 0x660F, 0xBBE8, 0x6610, 0x9553, 0x6611, 0x9554, 0x6612, 0x9555, + 0x6613, 0xD2D7, 0x6614, 0xCEF4, 0x6615, 0xEABF, 0x6616, 0x9556, 0x6617, 0x9557, 0x6618, 0x9558, 0x6619, 0xEABC, 0x661A, 0x9559, + 0x661B, 0x955A, 0x661C, 0x955B, 0x661D, 0xEAC3, 0x661E, 0x955C, 0x661F, 0xD0C7, 0x6620, 0xD3B3, 0x6621, 0x955D, 0x6622, 0x955E, + 0x6623, 0x955F, 0x6624, 0x9560, 0x6625, 0xB4BA, 0x6626, 0x9561, 0x6627, 0xC3C1, 0x6628, 0xD7F2, 0x6629, 0x9562, 0x662A, 0x9563, + 0x662B, 0x9564, 0x662C, 0x9565, 0x662D, 0xD5D1, 0x662E, 0x9566, 0x662F, 0xCAC7, 0x6630, 0x9567, 0x6631, 0xEAC5, 0x6632, 0x9568, + 0x6633, 0x9569, 0x6634, 0xEAC4, 0x6635, 0xEAC7, 0x6636, 0xEAC6, 0x6637, 0x956A, 0x6638, 0x956B, 0x6639, 0x956C, 0x663A, 0x956D, + 0x663B, 0x956E, 0x663C, 0xD6E7, 0x663D, 0x956F, 0x663E, 0xCFD4, 0x663F, 0x9570, 0x6640, 0x9571, 0x6641, 0xEACB, 0x6642, 0x9572, + 0x6643, 0xBBCE, 0x6644, 0x9573, 0x6645, 0x9574, 0x6646, 0x9575, 0x6647, 0x9576, 0x6648, 0x9577, 0x6649, 0x9578, 0x664A, 0x9579, + 0x664B, 0xBDFA, 0x664C, 0xC9CE, 0x664D, 0x957A, 0x664E, 0x957B, 0x664F, 0xEACC, 0x6650, 0x957C, 0x6651, 0x957D, 0x6652, 0xC9B9, + 0x6653, 0xCFFE, 0x6654, 0xEACA, 0x6655, 0xD4CE, 0x6656, 0xEACD, 0x6657, 0xEACF, 0x6658, 0x957E, 0x6659, 0x9580, 0x665A, 0xCDED, + 0x665B, 0x9581, 0x665C, 0x9582, 0x665D, 0x9583, 0x665E, 0x9584, 0x665F, 0xEAC9, 0x6660, 0x9585, 0x6661, 0xEACE, 0x6662, 0x9586, + 0x6663, 0x9587, 0x6664, 0xCEEE, 0x6665, 0x9588, 0x6666, 0xBBDE, 0x6667, 0x9589, 0x6668, 0xB3BF, 0x6669, 0x958A, 0x666A, 0x958B, + 0x666B, 0x958C, 0x666C, 0x958D, 0x666D, 0x958E, 0x666E, 0xC6D5, 0x666F, 0xBEB0, 0x6670, 0xCEFA, 0x6671, 0x958F, 0x6672, 0x9590, + 0x6673, 0x9591, 0x6674, 0xC7E7, 0x6675, 0x9592, 0x6676, 0xBEA7, 0x6677, 0xEAD0, 0x6678, 0x9593, 0x6679, 0x9594, 0x667A, 0xD6C7, + 0x667B, 0x9595, 0x667C, 0x9596, 0x667D, 0x9597, 0x667E, 0xC1C0, 0x667F, 0x9598, 0x6680, 0x9599, 0x6681, 0x959A, 0x6682, 0xD4DD, + 0x6683, 0x959B, 0x6684, 0xEAD1, 0x6685, 0x959C, 0x6686, 0x959D, 0x6687, 0xCFBE, 0x6688, 0x959E, 0x6689, 0x959F, 0x668A, 0x95A0, + 0x668B, 0x95A1, 0x668C, 0xEAD2, 0x668D, 0x95A2, 0x668E, 0x95A3, 0x668F, 0x95A4, 0x6690, 0x95A5, 0x6691, 0xCAEE, 0x6692, 0x95A6, + 0x6693, 0x95A7, 0x6694, 0x95A8, 0x6695, 0x95A9, 0x6696, 0xC5AF, 0x6697, 0xB0B5, 0x6698, 0x95AA, 0x6699, 0x95AB, 0x669A, 0x95AC, + 0x669B, 0x95AD, 0x669C, 0x95AE, 0x669D, 0xEAD4, 0x669E, 0x95AF, 0x669F, 0x95B0, 0x66A0, 0x95B1, 0x66A1, 0x95B2, 0x66A2, 0x95B3, + 0x66A3, 0x95B4, 0x66A4, 0x95B5, 0x66A5, 0x95B6, 0x66A6, 0x95B7, 0x66A7, 0xEAD3, 0x66A8, 0xF4DF, 0x66A9, 0x95B8, 0x66AA, 0x95B9, + 0x66AB, 0x95BA, 0x66AC, 0x95BB, 0x66AD, 0x95BC, 0x66AE, 0xC4BA, 0x66AF, 0x95BD, 0x66B0, 0x95BE, 0x66B1, 0x95BF, 0x66B2, 0x95C0, + 0x66B3, 0x95C1, 0x66B4, 0xB1A9, 0x66B5, 0x95C2, 0x66B6, 0x95C3, 0x66B7, 0x95C4, 0x66B8, 0x95C5, 0x66B9, 0xE5DF, 0x66BA, 0x95C6, + 0x66BB, 0x95C7, 0x66BC, 0x95C8, 0x66BD, 0x95C9, 0x66BE, 0xEAD5, 0x66BF, 0x95CA, 0x66C0, 0x95CB, 0x66C1, 0x95CC, 0x66C2, 0x95CD, + 0x66C3, 0x95CE, 0x66C4, 0x95CF, 0x66C5, 0x95D0, 0x66C6, 0x95D1, 0x66C7, 0x95D2, 0x66C8, 0x95D3, 0x66C9, 0x95D4, 0x66CA, 0x95D5, + 0x66CB, 0x95D6, 0x66CC, 0x95D7, 0x66CD, 0x95D8, 0x66CE, 0x95D9, 0x66CF, 0x95DA, 0x66D0, 0x95DB, 0x66D1, 0x95DC, 0x66D2, 0x95DD, + 0x66D3, 0x95DE, 0x66D4, 0x95DF, 0x66D5, 0x95E0, 0x66D6, 0x95E1, 0x66D7, 0x95E2, 0x66D8, 0x95E3, 0x66D9, 0xCAEF, 0x66DA, 0x95E4, + 0x66DB, 0xEAD6, 0x66DC, 0xEAD7, 0x66DD, 0xC6D8, 0x66DE, 0x95E5, 0x66DF, 0x95E6, 0x66E0, 0x95E7, 0x66E1, 0x95E8, 0x66E2, 0x95E9, + 0x66E3, 0x95EA, 0x66E4, 0x95EB, 0x66E5, 0x95EC, 0x66E6, 0xEAD8, 0x66E7, 0x95ED, 0x66E8, 0x95EE, 0x66E9, 0xEAD9, 0x66EA, 0x95EF, + 0x66EB, 0x95F0, 0x66EC, 0x95F1, 0x66ED, 0x95F2, 0x66EE, 0x95F3, 0x66EF, 0x95F4, 0x66F0, 0xD4BB, 0x66F1, 0x95F5, 0x66F2, 0xC7FA, + 0x66F3, 0xD2B7, 0x66F4, 0xB8FC, 0x66F5, 0x95F6, 0x66F6, 0x95F7, 0x66F7, 0xEAC2, 0x66F8, 0x95F8, 0x66F9, 0xB2DC, 0x66FA, 0x95F9, + 0x66FB, 0x95FA, 0x66FC, 0xC2FC, 0x66FD, 0x95FB, 0x66FE, 0xD4F8, 0x66FF, 0xCCE6, 0x6700, 0xD7EE, 0x6701, 0x95FC, 0x6702, 0x95FD, + 0x6703, 0x95FE, 0x6704, 0x9640, 0x6705, 0x9641, 0x6706, 0x9642, 0x6707, 0x9643, 0x6708, 0xD4C2, 0x6709, 0xD3D0, 0x670A, 0xEBC3, + 0x670B, 0xC5F3, 0x670C, 0x9644, 0x670D, 0xB7FE, 0x670E, 0x9645, 0x670F, 0x9646, 0x6710, 0xEBD4, 0x6711, 0x9647, 0x6712, 0x9648, + 0x6713, 0x9649, 0x6714, 0xCBB7, 0x6715, 0xEBDE, 0x6716, 0x964A, 0x6717, 0xC0CA, 0x6718, 0x964B, 0x6719, 0x964C, 0x671A, 0x964D, + 0x671B, 0xCDFB, 0x671C, 0x964E, 0x671D, 0xB3AF, 0x671E, 0x964F, 0x671F, 0xC6DA, 0x6720, 0x9650, 0x6721, 0x9651, 0x6722, 0x9652, + 0x6723, 0x9653, 0x6724, 0x9654, 0x6725, 0x9655, 0x6726, 0xEBFC, 0x6727, 0x9656, 0x6728, 0xC4BE, 0x6729, 0x9657, 0x672A, 0xCEB4, + 0x672B, 0xC4A9, 0x672C, 0xB1BE, 0x672D, 0xD4FD, 0x672E, 0x9658, 0x672F, 0xCAF5, 0x6730, 0x9659, 0x6731, 0xD6EC, 0x6732, 0x965A, + 0x6733, 0x965B, 0x6734, 0xC6D3, 0x6735, 0xB6E4, 0x6736, 0x965C, 0x6737, 0x965D, 0x6738, 0x965E, 0x6739, 0x965F, 0x673A, 0xBBFA, + 0x673B, 0x9660, 0x673C, 0x9661, 0x673D, 0xD0E0, 0x673E, 0x9662, 0x673F, 0x9663, 0x6740, 0xC9B1, 0x6741, 0x9664, 0x6742, 0xD4D3, + 0x6743, 0xC8A8, 0x6744, 0x9665, 0x6745, 0x9666, 0x6746, 0xB8CB, 0x6747, 0x9667, 0x6748, 0xE8BE, 0x6749, 0xC9BC, 0x674A, 0x9668, + 0x674B, 0x9669, 0x674C, 0xE8BB, 0x674D, 0x966A, 0x674E, 0xC0EE, 0x674F, 0xD0D3, 0x6750, 0xB2C4, 0x6751, 0xB4E5, 0x6752, 0x966B, + 0x6753, 0xE8BC, 0x6754, 0x966C, 0x6755, 0x966D, 0x6756, 0xD5C8, 0x6757, 0x966E, 0x6758, 0x966F, 0x6759, 0x9670, 0x675A, 0x9671, + 0x675B, 0x9672, 0x675C, 0xB6C5, 0x675D, 0x9673, 0x675E, 0xE8BD, 0x675F, 0xCAF8, 0x6760, 0xB8DC, 0x6761, 0xCCF5, 0x6762, 0x9674, + 0x6763, 0x9675, 0x6764, 0x9676, 0x6765, 0xC0B4, 0x6766, 0x9677, 0x6767, 0x9678, 0x6768, 0xD1EE, 0x6769, 0xE8BF, 0x676A, 0xE8C2, + 0x676B, 0x9679, 0x676C, 0x967A, 0x676D, 0xBABC, 0x676E, 0x967B, 0x676F, 0xB1AD, 0x6770, 0xBDDC, 0x6771, 0x967C, 0x6772, 0xEABD, + 0x6773, 0xE8C3, 0x6774, 0x967D, 0x6775, 0xE8C6, 0x6776, 0x967E, 0x6777, 0xE8CB, 0x6778, 0x9680, 0x6779, 0x9681, 0x677A, 0x9682, + 0x677B, 0x9683, 0x677C, 0xE8CC, 0x677D, 0x9684, 0x677E, 0xCBC9, 0x677F, 0xB0E5, 0x6780, 0x9685, 0x6781, 0xBCAB, 0x6782, 0x9686, + 0x6783, 0x9687, 0x6784, 0xB9B9, 0x6785, 0x9688, 0x6786, 0x9689, 0x6787, 0xE8C1, 0x6788, 0x968A, 0x6789, 0xCDF7, 0x678A, 0x968B, + 0x678B, 0xE8CA, 0x678C, 0x968C, 0x678D, 0x968D, 0x678E, 0x968E, 0x678F, 0x968F, 0x6790, 0xCEF6, 0x6791, 0x9690, 0x6792, 0x9691, + 0x6793, 0x9692, 0x6794, 0x9693, 0x6795, 0xD5ED, 0x6796, 0x9694, 0x6797, 0xC1D6, 0x6798, 0xE8C4, 0x6799, 0x9695, 0x679A, 0xC3B6, + 0x679B, 0x9696, 0x679C, 0xB9FB, 0x679D, 0xD6A6, 0x679E, 0xE8C8, 0x679F, 0x9697, 0x67A0, 0x9698, 0x67A1, 0x9699, 0x67A2, 0xCAE0, + 0x67A3, 0xD4E6, 0x67A4, 0x969A, 0x67A5, 0xE8C0, 0x67A6, 0x969B, 0x67A7, 0xE8C5, 0x67A8, 0xE8C7, 0x67A9, 0x969C, 0x67AA, 0xC7B9, + 0x67AB, 0xB7E3, 0x67AC, 0x969D, 0x67AD, 0xE8C9, 0x67AE, 0x969E, 0x67AF, 0xBFDD, 0x67B0, 0xE8D2, 0x67B1, 0x969F, 0x67B2, 0x96A0, + 0x67B3, 0xE8D7, 0x67B4, 0x96A1, 0x67B5, 0xE8D5, 0x67B6, 0xBCDC, 0x67B7, 0xBCCF, 0x67B8, 0xE8DB, 0x67B9, 0x96A2, 0x67BA, 0x96A3, + 0x67BB, 0x96A4, 0x67BC, 0x96A5, 0x67BD, 0x96A6, 0x67BE, 0x96A7, 0x67BF, 0x96A8, 0x67C0, 0x96A9, 0x67C1, 0xE8DE, 0x67C2, 0x96AA, + 0x67C3, 0xE8DA, 0x67C4, 0xB1FA, 0x67C5, 0x96AB, 0x67C6, 0x96AC, 0x67C7, 0x96AD, 0x67C8, 0x96AE, 0x67C9, 0x96AF, 0x67CA, 0x96B0, + 0x67CB, 0x96B1, 0x67CC, 0x96B2, 0x67CD, 0x96B3, 0x67CE, 0x96B4, 0x67CF, 0xB0D8, 0x67D0, 0xC4B3, 0x67D1, 0xB8CC, 0x67D2, 0xC6E2, + 0x67D3, 0xC8BE, 0x67D4, 0xC8E1, 0x67D5, 0x96B5, 0x67D6, 0x96B6, 0x67D7, 0x96B7, 0x67D8, 0xE8CF, 0x67D9, 0xE8D4, 0x67DA, 0xE8D6, + 0x67DB, 0x96B8, 0x67DC, 0xB9F1, 0x67DD, 0xE8D8, 0x67DE, 0xD7F5, 0x67DF, 0x96B9, 0x67E0, 0xC4FB, 0x67E1, 0x96BA, 0x67E2, 0xE8DC, + 0x67E3, 0x96BB, 0x67E4, 0x96BC, 0x67E5, 0xB2E9, 0x67E6, 0x96BD, 0x67E7, 0x96BE, 0x67E8, 0x96BF, 0x67E9, 0xE8D1, 0x67EA, 0x96C0, + 0x67EB, 0x96C1, 0x67EC, 0xBCED, 0x67ED, 0x96C2, 0x67EE, 0x96C3, 0x67EF, 0xBFC2, 0x67F0, 0xE8CD, 0x67F1, 0xD6F9, 0x67F2, 0x96C4, + 0x67F3, 0xC1F8, 0x67F4, 0xB2F1, 0x67F5, 0x96C5, 0x67F6, 0x96C6, 0x67F7, 0x96C7, 0x67F8, 0x96C8, 0x67F9, 0x96C9, 0x67FA, 0x96CA, + 0x67FB, 0x96CB, 0x67FC, 0x96CC, 0x67FD, 0xE8DF, 0x67FE, 0x96CD, 0x67FF, 0xCAC1, 0x6800, 0xE8D9, 0x6801, 0x96CE, 0x6802, 0x96CF, + 0x6803, 0x96D0, 0x6804, 0x96D1, 0x6805, 0xD5A4, 0x6806, 0x96D2, 0x6807, 0xB1EA, 0x6808, 0xD5BB, 0x6809, 0xE8CE, 0x680A, 0xE8D0, + 0x680B, 0xB6B0, 0x680C, 0xE8D3, 0x680D, 0x96D3, 0x680E, 0xE8DD, 0x680F, 0xC0B8, 0x6810, 0x96D4, 0x6811, 0xCAF7, 0x6812, 0x96D5, + 0x6813, 0xCBA8, 0x6814, 0x96D6, 0x6815, 0x96D7, 0x6816, 0xC6DC, 0x6817, 0xC0F5, 0x6818, 0x96D8, 0x6819, 0x96D9, 0x681A, 0x96DA, + 0x681B, 0x96DB, 0x681C, 0x96DC, 0x681D, 0xE8E9, 0x681E, 0x96DD, 0x681F, 0x96DE, 0x6820, 0x96DF, 0x6821, 0xD0A3, 0x6822, 0x96E0, + 0x6823, 0x96E1, 0x6824, 0x96E2, 0x6825, 0x96E3, 0x6826, 0x96E4, 0x6827, 0x96E5, 0x6828, 0x96E6, 0x6829, 0xE8F2, 0x682A, 0xD6EA, + 0x682B, 0x96E7, 0x682C, 0x96E8, 0x682D, 0x96E9, 0x682E, 0x96EA, 0x682F, 0x96EB, 0x6830, 0x96EC, 0x6831, 0x96ED, 0x6832, 0xE8E0, + 0x6833, 0xE8E1, 0x6834, 0x96EE, 0x6835, 0x96EF, 0x6836, 0x96F0, 0x6837, 0xD1F9, 0x6838, 0xBACB, 0x6839, 0xB8F9, 0x683A, 0x96F1, + 0x683B, 0x96F2, 0x683C, 0xB8F1, 0x683D, 0xD4D4, 0x683E, 0xE8EF, 0x683F, 0x96F3, 0x6840, 0xE8EE, 0x6841, 0xE8EC, 0x6842, 0xB9F0, + 0x6843, 0xCCD2, 0x6844, 0xE8E6, 0x6845, 0xCEA6, 0x6846, 0xBFF2, 0x6847, 0x96F4, 0x6848, 0xB0B8, 0x6849, 0xE8F1, 0x684A, 0xE8F0, + 0x684B, 0x96F5, 0x684C, 0xD7C0, 0x684D, 0x96F6, 0x684E, 0xE8E4, 0x684F, 0x96F7, 0x6850, 0xCDA9, 0x6851, 0xC9A3, 0x6852, 0x96F8, + 0x6853, 0xBBB8, 0x6854, 0xBDDB, 0x6855, 0xE8EA, 0x6856, 0x96F9, 0x6857, 0x96FA, 0x6858, 0x96FB, 0x6859, 0x96FC, 0x685A, 0x96FD, + 0x685B, 0x96FE, 0x685C, 0x9740, 0x685D, 0x9741, 0x685E, 0x9742, 0x685F, 0x9743, 0x6860, 0xE8E2, 0x6861, 0xE8E3, 0x6862, 0xE8E5, + 0x6863, 0xB5B5, 0x6864, 0xE8E7, 0x6865, 0xC7C5, 0x6866, 0xE8EB, 0x6867, 0xE8ED, 0x6868, 0xBDB0, 0x6869, 0xD7AE, 0x686A, 0x9744, + 0x686B, 0xE8F8, 0x686C, 0x9745, 0x686D, 0x9746, 0x686E, 0x9747, 0x686F, 0x9748, 0x6870, 0x9749, 0x6871, 0x974A, 0x6872, 0x974B, + 0x6873, 0x974C, 0x6874, 0xE8F5, 0x6875, 0x974D, 0x6876, 0xCDB0, 0x6877, 0xE8F6, 0x6878, 0x974E, 0x6879, 0x974F, 0x687A, 0x9750, + 0x687B, 0x9751, 0x687C, 0x9752, 0x687D, 0x9753, 0x687E, 0x9754, 0x687F, 0x9755, 0x6880, 0x9756, 0x6881, 0xC1BA, 0x6882, 0x9757, + 0x6883, 0xE8E8, 0x6884, 0x9758, 0x6885, 0xC3B7, 0x6886, 0xB0F0, 0x6887, 0x9759, 0x6888, 0x975A, 0x6889, 0x975B, 0x688A, 0x975C, + 0x688B, 0x975D, 0x688C, 0x975E, 0x688D, 0x975F, 0x688E, 0x9760, 0x688F, 0xE8F4, 0x6890, 0x9761, 0x6891, 0x9762, 0x6892, 0x9763, + 0x6893, 0xE8F7, 0x6894, 0x9764, 0x6895, 0x9765, 0x6896, 0x9766, 0x6897, 0xB9A3, 0x6898, 0x9767, 0x6899, 0x9768, 0x689A, 0x9769, + 0x689B, 0x976A, 0x689C, 0x976B, 0x689D, 0x976C, 0x689E, 0x976D, 0x689F, 0x976E, 0x68A0, 0x976F, 0x68A1, 0x9770, 0x68A2, 0xC9D2, + 0x68A3, 0x9771, 0x68A4, 0x9772, 0x68A5, 0x9773, 0x68A6, 0xC3CE, 0x68A7, 0xCEE0, 0x68A8, 0xC0E6, 0x68A9, 0x9774, 0x68AA, 0x9775, + 0x68AB, 0x9776, 0x68AC, 0x9777, 0x68AD, 0xCBF3, 0x68AE, 0x9778, 0x68AF, 0xCCDD, 0x68B0, 0xD0B5, 0x68B1, 0x9779, 0x68B2, 0x977A, + 0x68B3, 0xCAE1, 0x68B4, 0x977B, 0x68B5, 0xE8F3, 0x68B6, 0x977C, 0x68B7, 0x977D, 0x68B8, 0x977E, 0x68B9, 0x9780, 0x68BA, 0x9781, + 0x68BB, 0x9782, 0x68BC, 0x9783, 0x68BD, 0x9784, 0x68BE, 0x9785, 0x68BF, 0x9786, 0x68C0, 0xBCEC, 0x68C1, 0x9787, 0x68C2, 0xE8F9, + 0x68C3, 0x9788, 0x68C4, 0x9789, 0x68C5, 0x978A, 0x68C6, 0x978B, 0x68C7, 0x978C, 0x68C8, 0x978D, 0x68C9, 0xC3DE, 0x68CA, 0x978E, + 0x68CB, 0xC6E5, 0x68CC, 0x978F, 0x68CD, 0xB9F7, 0x68CE, 0x9790, 0x68CF, 0x9791, 0x68D0, 0x9792, 0x68D1, 0x9793, 0x68D2, 0xB0F4, + 0x68D3, 0x9794, 0x68D4, 0x9795, 0x68D5, 0xD7D8, 0x68D6, 0x9796, 0x68D7, 0x9797, 0x68D8, 0xBCAC, 0x68D9, 0x9798, 0x68DA, 0xC5EF, + 0x68DB, 0x9799, 0x68DC, 0x979A, 0x68DD, 0x979B, 0x68DE, 0x979C, 0x68DF, 0x979D, 0x68E0, 0xCCC4, 0x68E1, 0x979E, 0x68E2, 0x979F, + 0x68E3, 0xE9A6, 0x68E4, 0x97A0, 0x68E5, 0x97A1, 0x68E6, 0x97A2, 0x68E7, 0x97A3, 0x68E8, 0x97A4, 0x68E9, 0x97A5, 0x68EA, 0x97A6, + 0x68EB, 0x97A7, 0x68EC, 0x97A8, 0x68ED, 0x97A9, 0x68EE, 0xC9AD, 0x68EF, 0x97AA, 0x68F0, 0xE9A2, 0x68F1, 0xC0E2, 0x68F2, 0x97AB, + 0x68F3, 0x97AC, 0x68F4, 0x97AD, 0x68F5, 0xBFC3, 0x68F6, 0x97AE, 0x68F7, 0x97AF, 0x68F8, 0x97B0, 0x68F9, 0xE8FE, 0x68FA, 0xB9D7, + 0x68FB, 0x97B1, 0x68FC, 0xE8FB, 0x68FD, 0x97B2, 0x68FE, 0x97B3, 0x68FF, 0x97B4, 0x6900, 0x97B5, 0x6901, 0xE9A4, 0x6902, 0x97B6, + 0x6903, 0x97B7, 0x6904, 0x97B8, 0x6905, 0xD2CE, 0x6906, 0x97B9, 0x6907, 0x97BA, 0x6908, 0x97BB, 0x6909, 0x97BC, 0x690A, 0x97BD, + 0x690B, 0xE9A3, 0x690C, 0x97BE, 0x690D, 0xD6B2, 0x690E, 0xD7B5, 0x690F, 0x97BF, 0x6910, 0xE9A7, 0x6911, 0x97C0, 0x6912, 0xBDB7, + 0x6913, 0x97C1, 0x6914, 0x97C2, 0x6915, 0x97C3, 0x6916, 0x97C4, 0x6917, 0x97C5, 0x6918, 0x97C6, 0x6919, 0x97C7, 0x691A, 0x97C8, + 0x691B, 0x97C9, 0x691C, 0x97CA, 0x691D, 0x97CB, 0x691E, 0x97CC, 0x691F, 0xE8FC, 0x6920, 0xE8FD, 0x6921, 0x97CD, 0x6922, 0x97CE, + 0x6923, 0x97CF, 0x6924, 0xE9A1, 0x6925, 0x97D0, 0x6926, 0x97D1, 0x6927, 0x97D2, 0x6928, 0x97D3, 0x6929, 0x97D4, 0x692A, 0x97D5, + 0x692B, 0x97D6, 0x692C, 0x97D7, 0x692D, 0xCDD6, 0x692E, 0x97D8, 0x692F, 0x97D9, 0x6930, 0xD2AC, 0x6931, 0x97DA, 0x6932, 0x97DB, + 0x6933, 0x97DC, 0x6934, 0xE9B2, 0x6935, 0x97DD, 0x6936, 0x97DE, 0x6937, 0x97DF, 0x6938, 0x97E0, 0x6939, 0xE9A9, 0x693A, 0x97E1, + 0x693B, 0x97E2, 0x693C, 0x97E3, 0x693D, 0xB4AA, 0x693E, 0x97E4, 0x693F, 0xB4BB, 0x6940, 0x97E5, 0x6941, 0x97E6, 0x6942, 0xE9AB, + 0x6943, 0x97E7, 0x6944, 0x97E8, 0x6945, 0x97E9, 0x6946, 0x97EA, 0x6947, 0x97EB, 0x6948, 0x97EC, 0x6949, 0x97ED, 0x694A, 0x97EE, + 0x694B, 0x97EF, 0x694C, 0x97F0, 0x694D, 0x97F1, 0x694E, 0x97F2, 0x694F, 0x97F3, 0x6950, 0x97F4, 0x6951, 0x97F5, 0x6952, 0x97F6, + 0x6953, 0x97F7, 0x6954, 0xD0A8, 0x6955, 0x97F8, 0x6956, 0x97F9, 0x6957, 0xE9A5, 0x6958, 0x97FA, 0x6959, 0x97FB, 0x695A, 0xB3FE, + 0x695B, 0x97FC, 0x695C, 0x97FD, 0x695D, 0xE9AC, 0x695E, 0xC0E3, 0x695F, 0x97FE, 0x6960, 0xE9AA, 0x6961, 0x9840, 0x6962, 0x9841, + 0x6963, 0xE9B9, 0x6964, 0x9842, 0x6965, 0x9843, 0x6966, 0xE9B8, 0x6967, 0x9844, 0x6968, 0x9845, 0x6969, 0x9846, 0x696A, 0x9847, + 0x696B, 0xE9AE, 0x696C, 0x9848, 0x696D, 0x9849, 0x696E, 0xE8FA, 0x696F, 0x984A, 0x6970, 0x984B, 0x6971, 0xE9A8, 0x6972, 0x984C, + 0x6973, 0x984D, 0x6974, 0x984E, 0x6975, 0x984F, 0x6976, 0x9850, 0x6977, 0xBFAC, 0x6978, 0xE9B1, 0x6979, 0xE9BA, 0x697A, 0x9851, + 0x697B, 0x9852, 0x697C, 0xC2A5, 0x697D, 0x9853, 0x697E, 0x9854, 0x697F, 0x9855, 0x6980, 0xE9AF, 0x6981, 0x9856, 0x6982, 0xB8C5, + 0x6983, 0x9857, 0x6984, 0xE9AD, 0x6985, 0x9858, 0x6986, 0xD3DC, 0x6987, 0xE9B4, 0x6988, 0xE9B5, 0x6989, 0xE9B7, 0x698A, 0x9859, + 0x698B, 0x985A, 0x698C, 0x985B, 0x698D, 0xE9C7, 0x698E, 0x985C, 0x698F, 0x985D, 0x6990, 0x985E, 0x6991, 0x985F, 0x6992, 0x9860, + 0x6993, 0x9861, 0x6994, 0xC0C6, 0x6995, 0xE9C5, 0x6996, 0x9862, 0x6997, 0x9863, 0x6998, 0xE9B0, 0x6999, 0x9864, 0x699A, 0x9865, + 0x699B, 0xE9BB, 0x699C, 0xB0F1, 0x699D, 0x9866, 0x699E, 0x9867, 0x699F, 0x9868, 0x69A0, 0x9869, 0x69A1, 0x986A, 0x69A2, 0x986B, + 0x69A3, 0x986C, 0x69A4, 0x986D, 0x69A5, 0x986E, 0x69A6, 0x986F, 0x69A7, 0xE9BC, 0x69A8, 0xD5A5, 0x69A9, 0x9870, 0x69AA, 0x9871, + 0x69AB, 0xE9BE, 0x69AC, 0x9872, 0x69AD, 0xE9BF, 0x69AE, 0x9873, 0x69AF, 0x9874, 0x69B0, 0x9875, 0x69B1, 0xE9C1, 0x69B2, 0x9876, + 0x69B3, 0x9877, 0x69B4, 0xC1F1, 0x69B5, 0x9878, 0x69B6, 0x9879, 0x69B7, 0xC8B6, 0x69B8, 0x987A, 0x69B9, 0x987B, 0x69BA, 0x987C, + 0x69BB, 0xE9BD, 0x69BC, 0x987D, 0x69BD, 0x987E, 0x69BE, 0x9880, 0x69BF, 0x9881, 0x69C0, 0x9882, 0x69C1, 0xE9C2, 0x69C2, 0x9883, + 0x69C3, 0x9884, 0x69C4, 0x9885, 0x69C5, 0x9886, 0x69C6, 0x9887, 0x69C7, 0x9888, 0x69C8, 0x9889, 0x69C9, 0x988A, 0x69CA, 0xE9C3, + 0x69CB, 0x988B, 0x69CC, 0xE9B3, 0x69CD, 0x988C, 0x69CE, 0xE9B6, 0x69CF, 0x988D, 0x69D0, 0xBBB1, 0x69D1, 0x988E, 0x69D2, 0x988F, + 0x69D3, 0x9890, 0x69D4, 0xE9C0, 0x69D5, 0x9891, 0x69D6, 0x9892, 0x69D7, 0x9893, 0x69D8, 0x9894, 0x69D9, 0x9895, 0x69DA, 0x9896, + 0x69DB, 0xBCF7, 0x69DC, 0x9897, 0x69DD, 0x9898, 0x69DE, 0x9899, 0x69DF, 0xE9C4, 0x69E0, 0xE9C6, 0x69E1, 0x989A, 0x69E2, 0x989B, + 0x69E3, 0x989C, 0x69E4, 0x989D, 0x69E5, 0x989E, 0x69E6, 0x989F, 0x69E7, 0x98A0, 0x69E8, 0x98A1, 0x69E9, 0x98A2, 0x69EA, 0x98A3, + 0x69EB, 0x98A4, 0x69EC, 0x98A5, 0x69ED, 0xE9CA, 0x69EE, 0x98A6, 0x69EF, 0x98A7, 0x69F0, 0x98A8, 0x69F1, 0x98A9, 0x69F2, 0xE9CE, + 0x69F3, 0x98AA, 0x69F4, 0x98AB, 0x69F5, 0x98AC, 0x69F6, 0x98AD, 0x69F7, 0x98AE, 0x69F8, 0x98AF, 0x69F9, 0x98B0, 0x69FA, 0x98B1, + 0x69FB, 0x98B2, 0x69FC, 0x98B3, 0x69FD, 0xB2DB, 0x69FE, 0x98B4, 0x69FF, 0xE9C8, 0x6A00, 0x98B5, 0x6A01, 0x98B6, 0x6A02, 0x98B7, + 0x6A03, 0x98B8, 0x6A04, 0x98B9, 0x6A05, 0x98BA, 0x6A06, 0x98BB, 0x6A07, 0x98BC, 0x6A08, 0x98BD, 0x6A09, 0x98BE, 0x6A0A, 0xB7AE, + 0x6A0B, 0x98BF, 0x6A0C, 0x98C0, 0x6A0D, 0x98C1, 0x6A0E, 0x98C2, 0x6A0F, 0x98C3, 0x6A10, 0x98C4, 0x6A11, 0x98C5, 0x6A12, 0x98C6, + 0x6A13, 0x98C7, 0x6A14, 0x98C8, 0x6A15, 0x98C9, 0x6A16, 0x98CA, 0x6A17, 0xE9CB, 0x6A18, 0xE9CC, 0x6A19, 0x98CB, 0x6A1A, 0x98CC, + 0x6A1B, 0x98CD, 0x6A1C, 0x98CE, 0x6A1D, 0x98CF, 0x6A1E, 0x98D0, 0x6A1F, 0xD5C1, 0x6A20, 0x98D1, 0x6A21, 0xC4A3, 0x6A22, 0x98D2, + 0x6A23, 0x98D3, 0x6A24, 0x98D4, 0x6A25, 0x98D5, 0x6A26, 0x98D6, 0x6A27, 0x98D7, 0x6A28, 0xE9D8, 0x6A29, 0x98D8, 0x6A2A, 0xBAE1, + 0x6A2B, 0x98D9, 0x6A2C, 0x98DA, 0x6A2D, 0x98DB, 0x6A2E, 0x98DC, 0x6A2F, 0xE9C9, 0x6A30, 0x98DD, 0x6A31, 0xD3A3, 0x6A32, 0x98DE, + 0x6A33, 0x98DF, 0x6A34, 0x98E0, 0x6A35, 0xE9D4, 0x6A36, 0x98E1, 0x6A37, 0x98E2, 0x6A38, 0x98E3, 0x6A39, 0x98E4, 0x6A3A, 0x98E5, + 0x6A3B, 0x98E6, 0x6A3C, 0x98E7, 0x6A3D, 0xE9D7, 0x6A3E, 0xE9D0, 0x6A3F, 0x98E8, 0x6A40, 0x98E9, 0x6A41, 0x98EA, 0x6A42, 0x98EB, + 0x6A43, 0x98EC, 0x6A44, 0xE9CF, 0x6A45, 0x98ED, 0x6A46, 0x98EE, 0x6A47, 0xC7C1, 0x6A48, 0x98EF, 0x6A49, 0x98F0, 0x6A4A, 0x98F1, + 0x6A4B, 0x98F2, 0x6A4C, 0x98F3, 0x6A4D, 0x98F4, 0x6A4E, 0x98F5, 0x6A4F, 0x98F6, 0x6A50, 0xE9D2, 0x6A51, 0x98F7, 0x6A52, 0x98F8, + 0x6A53, 0x98F9, 0x6A54, 0x98FA, 0x6A55, 0x98FB, 0x6A56, 0x98FC, 0x6A57, 0x98FD, 0x6A58, 0xE9D9, 0x6A59, 0xB3C8, 0x6A5A, 0x98FE, + 0x6A5B, 0xE9D3, 0x6A5C, 0x9940, 0x6A5D, 0x9941, 0x6A5E, 0x9942, 0x6A5F, 0x9943, 0x6A60, 0x9944, 0x6A61, 0xCFF0, 0x6A62, 0x9945, + 0x6A63, 0x9946, 0x6A64, 0x9947, 0x6A65, 0xE9CD, 0x6A66, 0x9948, 0x6A67, 0x9949, 0x6A68, 0x994A, 0x6A69, 0x994B, 0x6A6A, 0x994C, + 0x6A6B, 0x994D, 0x6A6C, 0x994E, 0x6A6D, 0x994F, 0x6A6E, 0x9950, 0x6A6F, 0x9951, 0x6A70, 0x9952, 0x6A71, 0xB3F7, 0x6A72, 0x9953, + 0x6A73, 0x9954, 0x6A74, 0x9955, 0x6A75, 0x9956, 0x6A76, 0x9957, 0x6A77, 0x9958, 0x6A78, 0x9959, 0x6A79, 0xE9D6, 0x6A7A, 0x995A, + 0x6A7B, 0x995B, 0x6A7C, 0xE9DA, 0x6A7D, 0x995C, 0x6A7E, 0x995D, 0x6A7F, 0x995E, 0x6A80, 0xCCB4, 0x6A81, 0x995F, 0x6A82, 0x9960, + 0x6A83, 0x9961, 0x6A84, 0xCFAD, 0x6A85, 0x9962, 0x6A86, 0x9963, 0x6A87, 0x9964, 0x6A88, 0x9965, 0x6A89, 0x9966, 0x6A8A, 0x9967, + 0x6A8B, 0x9968, 0x6A8C, 0x9969, 0x6A8D, 0x996A, 0x6A8E, 0xE9D5, 0x6A8F, 0x996B, 0x6A90, 0xE9DC, 0x6A91, 0xE9DB, 0x6A92, 0x996C, + 0x6A93, 0x996D, 0x6A94, 0x996E, 0x6A95, 0x996F, 0x6A96, 0x9970, 0x6A97, 0xE9DE, 0x6A98, 0x9971, 0x6A99, 0x9972, 0x6A9A, 0x9973, + 0x6A9B, 0x9974, 0x6A9C, 0x9975, 0x6A9D, 0x9976, 0x6A9E, 0x9977, 0x6A9F, 0x9978, 0x6AA0, 0xE9D1, 0x6AA1, 0x9979, 0x6AA2, 0x997A, + 0x6AA3, 0x997B, 0x6AA4, 0x997C, 0x6AA5, 0x997D, 0x6AA6, 0x997E, 0x6AA7, 0x9980, 0x6AA8, 0x9981, 0x6AA9, 0xE9DD, 0x6AAA, 0x9982, + 0x6AAB, 0xE9DF, 0x6AAC, 0xC3CA, 0x6AAD, 0x9983, 0x6AAE, 0x9984, 0x6AAF, 0x9985, 0x6AB0, 0x9986, 0x6AB1, 0x9987, 0x6AB2, 0x9988, + 0x6AB3, 0x9989, 0x6AB4, 0x998A, 0x6AB5, 0x998B, 0x6AB6, 0x998C, 0x6AB7, 0x998D, 0x6AB8, 0x998E, 0x6AB9, 0x998F, 0x6ABA, 0x9990, + 0x6ABB, 0x9991, 0x6ABC, 0x9992, 0x6ABD, 0x9993, 0x6ABE, 0x9994, 0x6ABF, 0x9995, 0x6AC0, 0x9996, 0x6AC1, 0x9997, 0x6AC2, 0x9998, + 0x6AC3, 0x9999, 0x6AC4, 0x999A, 0x6AC5, 0x999B, 0x6AC6, 0x999C, 0x6AC7, 0x999D, 0x6AC8, 0x999E, 0x6AC9, 0x999F, 0x6ACA, 0x99A0, + 0x6ACB, 0x99A1, 0x6ACC, 0x99A2, 0x6ACD, 0x99A3, 0x6ACE, 0x99A4, 0x6ACF, 0x99A5, 0x6AD0, 0x99A6, 0x6AD1, 0x99A7, 0x6AD2, 0x99A8, + 0x6AD3, 0x99A9, 0x6AD4, 0x99AA, 0x6AD5, 0x99AB, 0x6AD6, 0x99AC, 0x6AD7, 0x99AD, 0x6AD8, 0x99AE, 0x6AD9, 0x99AF, 0x6ADA, 0x99B0, + 0x6ADB, 0x99B1, 0x6ADC, 0x99B2, 0x6ADD, 0x99B3, 0x6ADE, 0x99B4, 0x6ADF, 0x99B5, 0x6AE0, 0x99B6, 0x6AE1, 0x99B7, 0x6AE2, 0x99B8, + 0x6AE3, 0x99B9, 0x6AE4, 0x99BA, 0x6AE5, 0x99BB, 0x6AE6, 0x99BC, 0x6AE7, 0x99BD, 0x6AE8, 0x99BE, 0x6AE9, 0x99BF, 0x6AEA, 0x99C0, + 0x6AEB, 0x99C1, 0x6AEC, 0x99C2, 0x6AED, 0x99C3, 0x6AEE, 0x99C4, 0x6AEF, 0x99C5, 0x6AF0, 0x99C6, 0x6AF1, 0x99C7, 0x6AF2, 0x99C8, + 0x6AF3, 0x99C9, 0x6AF4, 0x99CA, 0x6AF5, 0x99CB, 0x6AF6, 0x99CC, 0x6AF7, 0x99CD, 0x6AF8, 0x99CE, 0x6AF9, 0x99CF, 0x6AFA, 0x99D0, + 0x6AFB, 0x99D1, 0x6AFC, 0x99D2, 0x6AFD, 0x99D3, 0x6AFE, 0x99D4, 0x6AFF, 0x99D5, 0x6B00, 0x99D6, 0x6B01, 0x99D7, 0x6B02, 0x99D8, + 0x6B03, 0x99D9, 0x6B04, 0x99DA, 0x6B05, 0x99DB, 0x6B06, 0x99DC, 0x6B07, 0x99DD, 0x6B08, 0x99DE, 0x6B09, 0x99DF, 0x6B0A, 0x99E0, + 0x6B0B, 0x99E1, 0x6B0C, 0x99E2, 0x6B0D, 0x99E3, 0x6B0E, 0x99E4, 0x6B0F, 0x99E5, 0x6B10, 0x99E6, 0x6B11, 0x99E7, 0x6B12, 0x99E8, + 0x6B13, 0x99E9, 0x6B14, 0x99EA, 0x6B15, 0x99EB, 0x6B16, 0x99EC, 0x6B17, 0x99ED, 0x6B18, 0x99EE, 0x6B19, 0x99EF, 0x6B1A, 0x99F0, + 0x6B1B, 0x99F1, 0x6B1C, 0x99F2, 0x6B1D, 0x99F3, 0x6B1E, 0x99F4, 0x6B1F, 0x99F5, 0x6B20, 0xC7B7, 0x6B21, 0xB4CE, 0x6B22, 0xBBB6, + 0x6B23, 0xD0C0, 0x6B24, 0xECA3, 0x6B25, 0x99F6, 0x6B26, 0x99F7, 0x6B27, 0xC5B7, 0x6B28, 0x99F8, 0x6B29, 0x99F9, 0x6B2A, 0x99FA, + 0x6B2B, 0x99FB, 0x6B2C, 0x99FC, 0x6B2D, 0x99FD, 0x6B2E, 0x99FE, 0x6B2F, 0x9A40, 0x6B30, 0x9A41, 0x6B31, 0x9A42, 0x6B32, 0xD3FB, + 0x6B33, 0x9A43, 0x6B34, 0x9A44, 0x6B35, 0x9A45, 0x6B36, 0x9A46, 0x6B37, 0xECA4, 0x6B38, 0x9A47, 0x6B39, 0xECA5, 0x6B3A, 0xC6DB, + 0x6B3B, 0x9A48, 0x6B3C, 0x9A49, 0x6B3D, 0x9A4A, 0x6B3E, 0xBFEE, 0x6B3F, 0x9A4B, 0x6B40, 0x9A4C, 0x6B41, 0x9A4D, 0x6B42, 0x9A4E, + 0x6B43, 0xECA6, 0x6B44, 0x9A4F, 0x6B45, 0x9A50, 0x6B46, 0xECA7, 0x6B47, 0xD0AA, 0x6B48, 0x9A51, 0x6B49, 0xC7B8, 0x6B4A, 0x9A52, + 0x6B4B, 0x9A53, 0x6B4C, 0xB8E8, 0x6B4D, 0x9A54, 0x6B4E, 0x9A55, 0x6B4F, 0x9A56, 0x6B50, 0x9A57, 0x6B51, 0x9A58, 0x6B52, 0x9A59, + 0x6B53, 0x9A5A, 0x6B54, 0x9A5B, 0x6B55, 0x9A5C, 0x6B56, 0x9A5D, 0x6B57, 0x9A5E, 0x6B58, 0x9A5F, 0x6B59, 0xECA8, 0x6B5A, 0x9A60, + 0x6B5B, 0x9A61, 0x6B5C, 0x9A62, 0x6B5D, 0x9A63, 0x6B5E, 0x9A64, 0x6B5F, 0x9A65, 0x6B60, 0x9A66, 0x6B61, 0x9A67, 0x6B62, 0xD6B9, + 0x6B63, 0xD5FD, 0x6B64, 0xB4CB, 0x6B65, 0xB2BD, 0x6B66, 0xCEE4, 0x6B67, 0xC6E7, 0x6B68, 0x9A68, 0x6B69, 0x9A69, 0x6B6A, 0xCDE1, + 0x6B6B, 0x9A6A, 0x6B6C, 0x9A6B, 0x6B6D, 0x9A6C, 0x6B6E, 0x9A6D, 0x6B6F, 0x9A6E, 0x6B70, 0x9A6F, 0x6B71, 0x9A70, 0x6B72, 0x9A71, + 0x6B73, 0x9A72, 0x6B74, 0x9A73, 0x6B75, 0x9A74, 0x6B76, 0x9A75, 0x6B77, 0x9A76, 0x6B78, 0x9A77, 0x6B79, 0xB4F5, 0x6B7A, 0x9A78, + 0x6B7B, 0xCBC0, 0x6B7C, 0xBCDF, 0x6B7D, 0x9A79, 0x6B7E, 0x9A7A, 0x6B7F, 0x9A7B, 0x6B80, 0x9A7C, 0x6B81, 0xE9E2, 0x6B82, 0xE9E3, + 0x6B83, 0xD1EA, 0x6B84, 0xE9E5, 0x6B85, 0x9A7D, 0x6B86, 0xB4F9, 0x6B87, 0xE9E4, 0x6B88, 0x9A7E, 0x6B89, 0xD1B3, 0x6B8A, 0xCAE2, + 0x6B8B, 0xB2D0, 0x6B8C, 0x9A80, 0x6B8D, 0xE9E8, 0x6B8E, 0x9A81, 0x6B8F, 0x9A82, 0x6B90, 0x9A83, 0x6B91, 0x9A84, 0x6B92, 0xE9E6, + 0x6B93, 0xE9E7, 0x6B94, 0x9A85, 0x6B95, 0x9A86, 0x6B96, 0xD6B3, 0x6B97, 0x9A87, 0x6B98, 0x9A88, 0x6B99, 0x9A89, 0x6B9A, 0xE9E9, + 0x6B9B, 0xE9EA, 0x6B9C, 0x9A8A, 0x6B9D, 0x9A8B, 0x6B9E, 0x9A8C, 0x6B9F, 0x9A8D, 0x6BA0, 0x9A8E, 0x6BA1, 0xE9EB, 0x6BA2, 0x9A8F, + 0x6BA3, 0x9A90, 0x6BA4, 0x9A91, 0x6BA5, 0x9A92, 0x6BA6, 0x9A93, 0x6BA7, 0x9A94, 0x6BA8, 0x9A95, 0x6BA9, 0x9A96, 0x6BAA, 0xE9EC, + 0x6BAB, 0x9A97, 0x6BAC, 0x9A98, 0x6BAD, 0x9A99, 0x6BAE, 0x9A9A, 0x6BAF, 0x9A9B, 0x6BB0, 0x9A9C, 0x6BB1, 0x9A9D, 0x6BB2, 0x9A9E, + 0x6BB3, 0xECAF, 0x6BB4, 0xC5B9, 0x6BB5, 0xB6CE, 0x6BB6, 0x9A9F, 0x6BB7, 0xD2F3, 0x6BB8, 0x9AA0, 0x6BB9, 0x9AA1, 0x6BBA, 0x9AA2, + 0x6BBB, 0x9AA3, 0x6BBC, 0x9AA4, 0x6BBD, 0x9AA5, 0x6BBE, 0x9AA6, 0x6BBF, 0xB5EE, 0x6BC0, 0x9AA7, 0x6BC1, 0xBBD9, 0x6BC2, 0xECB1, + 0x6BC3, 0x9AA8, 0x6BC4, 0x9AA9, 0x6BC5, 0xD2E3, 0x6BC6, 0x9AAA, 0x6BC7, 0x9AAB, 0x6BC8, 0x9AAC, 0x6BC9, 0x9AAD, 0x6BCA, 0x9AAE, + 0x6BCB, 0xCEE3, 0x6BCC, 0x9AAF, 0x6BCD, 0xC4B8, 0x6BCE, 0x9AB0, 0x6BCF, 0xC3BF, 0x6BD0, 0x9AB1, 0x6BD1, 0x9AB2, 0x6BD2, 0xB6BE, + 0x6BD3, 0xD8B9, 0x6BD4, 0xB1C8, 0x6BD5, 0xB1CF, 0x6BD6, 0xB1D1, 0x6BD7, 0xC5FE, 0x6BD8, 0x9AB3, 0x6BD9, 0xB1D0, 0x6BDA, 0x9AB4, + 0x6BDB, 0xC3AB, 0x6BDC, 0x9AB5, 0x6BDD, 0x9AB6, 0x6BDE, 0x9AB7, 0x6BDF, 0x9AB8, 0x6BE0, 0x9AB9, 0x6BE1, 0xD5B1, 0x6BE2, 0x9ABA, + 0x6BE3, 0x9ABB, 0x6BE4, 0x9ABC, 0x6BE5, 0x9ABD, 0x6BE6, 0x9ABE, 0x6BE7, 0x9ABF, 0x6BE8, 0x9AC0, 0x6BE9, 0x9AC1, 0x6BEA, 0xEBA4, + 0x6BEB, 0xBAC1, 0x6BEC, 0x9AC2, 0x6BED, 0x9AC3, 0x6BEE, 0x9AC4, 0x6BEF, 0xCCBA, 0x6BF0, 0x9AC5, 0x6BF1, 0x9AC6, 0x6BF2, 0x9AC7, + 0x6BF3, 0xEBA5, 0x6BF4, 0x9AC8, 0x6BF5, 0xEBA7, 0x6BF6, 0x9AC9, 0x6BF7, 0x9ACA, 0x6BF8, 0x9ACB, 0x6BF9, 0xEBA8, 0x6BFA, 0x9ACC, + 0x6BFB, 0x9ACD, 0x6BFC, 0x9ACE, 0x6BFD, 0xEBA6, 0x6BFE, 0x9ACF, 0x6BFF, 0x9AD0, 0x6C00, 0x9AD1, 0x6C01, 0x9AD2, 0x6C02, 0x9AD3, + 0x6C03, 0x9AD4, 0x6C04, 0x9AD5, 0x6C05, 0xEBA9, 0x6C06, 0xEBAB, 0x6C07, 0xEBAA, 0x6C08, 0x9AD6, 0x6C09, 0x9AD7, 0x6C0A, 0x9AD8, + 0x6C0B, 0x9AD9, 0x6C0C, 0x9ADA, 0x6C0D, 0xEBAC, 0x6C0E, 0x9ADB, 0x6C0F, 0xCACF, 0x6C10, 0xD8B5, 0x6C11, 0xC3F1, 0x6C12, 0x9ADC, + 0x6C13, 0xC3A5, 0x6C14, 0xC6F8, 0x6C15, 0xEBAD, 0x6C16, 0xC4CA, 0x6C17, 0x9ADD, 0x6C18, 0xEBAE, 0x6C19, 0xEBAF, 0x6C1A, 0xEBB0, + 0x6C1B, 0xB7D5, 0x6C1C, 0x9ADE, 0x6C1D, 0x9ADF, 0x6C1E, 0x9AE0, 0x6C1F, 0xB7FA, 0x6C20, 0x9AE1, 0x6C21, 0xEBB1, 0x6C22, 0xC7E2, + 0x6C23, 0x9AE2, 0x6C24, 0xEBB3, 0x6C25, 0x9AE3, 0x6C26, 0xBAA4, 0x6C27, 0xD1F5, 0x6C28, 0xB0B1, 0x6C29, 0xEBB2, 0x6C2A, 0xEBB4, + 0x6C2B, 0x9AE4, 0x6C2C, 0x9AE5, 0x6C2D, 0x9AE6, 0x6C2E, 0xB5AA, 0x6C2F, 0xC2C8, 0x6C30, 0xC7E8, 0x6C31, 0x9AE7, 0x6C32, 0xEBB5, + 0x6C33, 0x9AE8, 0x6C34, 0xCBAE, 0x6C35, 0xE3DF, 0x6C36, 0x9AE9, 0x6C37, 0x9AEA, 0x6C38, 0xD3C0, 0x6C39, 0x9AEB, 0x6C3A, 0x9AEC, + 0x6C3B, 0x9AED, 0x6C3C, 0x9AEE, 0x6C3D, 0xD9DB, 0x6C3E, 0x9AEF, 0x6C3F, 0x9AF0, 0x6C40, 0xCDA1, 0x6C41, 0xD6AD, 0x6C42, 0xC7F3, + 0x6C43, 0x9AF1, 0x6C44, 0x9AF2, 0x6C45, 0x9AF3, 0x6C46, 0xD9E0, 0x6C47, 0xBBE3, 0x6C48, 0x9AF4, 0x6C49, 0xBABA, 0x6C4A, 0xE3E2, + 0x6C4B, 0x9AF5, 0x6C4C, 0x9AF6, 0x6C4D, 0x9AF7, 0x6C4E, 0x9AF8, 0x6C4F, 0x9AF9, 0x6C50, 0xCFAB, 0x6C51, 0x9AFA, 0x6C52, 0x9AFB, + 0x6C53, 0x9AFC, 0x6C54, 0xE3E0, 0x6C55, 0xC9C7, 0x6C56, 0x9AFD, 0x6C57, 0xBAB9, 0x6C58, 0x9AFE, 0x6C59, 0x9B40, 0x6C5A, 0x9B41, + 0x6C5B, 0xD1B4, 0x6C5C, 0xE3E1, 0x6C5D, 0xC8EA, 0x6C5E, 0xB9AF, 0x6C5F, 0xBDAD, 0x6C60, 0xB3D8, 0x6C61, 0xCEDB, 0x6C62, 0x9B42, + 0x6C63, 0x9B43, 0x6C64, 0xCCC0, 0x6C65, 0x9B44, 0x6C66, 0x9B45, 0x6C67, 0x9B46, 0x6C68, 0xE3E8, 0x6C69, 0xE3E9, 0x6C6A, 0xCDF4, + 0x6C6B, 0x9B47, 0x6C6C, 0x9B48, 0x6C6D, 0x9B49, 0x6C6E, 0x9B4A, 0x6C6F, 0x9B4B, 0x6C70, 0xCCAD, 0x6C71, 0x9B4C, 0x6C72, 0xBCB3, + 0x6C73, 0x9B4D, 0x6C74, 0xE3EA, 0x6C75, 0x9B4E, 0x6C76, 0xE3EB, 0x6C77, 0x9B4F, 0x6C78, 0x9B50, 0x6C79, 0xD0DA, 0x6C7A, 0x9B51, + 0x6C7B, 0x9B52, 0x6C7C, 0x9B53, 0x6C7D, 0xC6FB, 0x6C7E, 0xB7DA, 0x6C7F, 0x9B54, 0x6C80, 0x9B55, 0x6C81, 0xC7DF, 0x6C82, 0xD2CA, + 0x6C83, 0xCED6, 0x6C84, 0x9B56, 0x6C85, 0xE3E4, 0x6C86, 0xE3EC, 0x6C87, 0x9B57, 0x6C88, 0xC9F2, 0x6C89, 0xB3C1, 0x6C8A, 0x9B58, + 0x6C8B, 0x9B59, 0x6C8C, 0xE3E7, 0x6C8D, 0x9B5A, 0x6C8E, 0x9B5B, 0x6C8F, 0xC6E3, 0x6C90, 0xE3E5, 0x6C91, 0x9B5C, 0x6C92, 0x9B5D, + 0x6C93, 0xEDB3, 0x6C94, 0xE3E6, 0x6C95, 0x9B5E, 0x6C96, 0x9B5F, 0x6C97, 0x9B60, 0x6C98, 0x9B61, 0x6C99, 0xC9B3, 0x6C9A, 0x9B62, + 0x6C9B, 0xC5E6, 0x6C9C, 0x9B63, 0x6C9D, 0x9B64, 0x6C9E, 0x9B65, 0x6C9F, 0xB9B5, 0x6CA0, 0x9B66, 0x6CA1, 0xC3BB, 0x6CA2, 0x9B67, + 0x6CA3, 0xE3E3, 0x6CA4, 0xC5BD, 0x6CA5, 0xC1A4, 0x6CA6, 0xC2D9, 0x6CA7, 0xB2D7, 0x6CA8, 0x9B68, 0x6CA9, 0xE3ED, 0x6CAA, 0xBBA6, + 0x6CAB, 0xC4AD, 0x6CAC, 0x9B69, 0x6CAD, 0xE3F0, 0x6CAE, 0xBEDA, 0x6CAF, 0x9B6A, 0x6CB0, 0x9B6B, 0x6CB1, 0xE3FB, 0x6CB2, 0xE3F5, + 0x6CB3, 0xBAD3, 0x6CB4, 0x9B6C, 0x6CB5, 0x9B6D, 0x6CB6, 0x9B6E, 0x6CB7, 0x9B6F, 0x6CB8, 0xB7D0, 0x6CB9, 0xD3CD, 0x6CBA, 0x9B70, + 0x6CBB, 0xD6CE, 0x6CBC, 0xD5D3, 0x6CBD, 0xB9C1, 0x6CBE, 0xD5B4, 0x6CBF, 0xD1D8, 0x6CC0, 0x9B71, 0x6CC1, 0x9B72, 0x6CC2, 0x9B73, + 0x6CC3, 0x9B74, 0x6CC4, 0xD0B9, 0x6CC5, 0xC7F6, 0x6CC6, 0x9B75, 0x6CC7, 0x9B76, 0x6CC8, 0x9B77, 0x6CC9, 0xC8AA, 0x6CCA, 0xB2B4, + 0x6CCB, 0x9B78, 0x6CCC, 0xC3DA, 0x6CCD, 0x9B79, 0x6CCE, 0x9B7A, 0x6CCF, 0x9B7B, 0x6CD0, 0xE3EE, 0x6CD1, 0x9B7C, 0x6CD2, 0x9B7D, + 0x6CD3, 0xE3FC, 0x6CD4, 0xE3EF, 0x6CD5, 0xB7A8, 0x6CD6, 0xE3F7, 0x6CD7, 0xE3F4, 0x6CD8, 0x9B7E, 0x6CD9, 0x9B80, 0x6CDA, 0x9B81, + 0x6CDB, 0xB7BA, 0x6CDC, 0x9B82, 0x6CDD, 0x9B83, 0x6CDE, 0xC5A2, 0x6CDF, 0x9B84, 0x6CE0, 0xE3F6, 0x6CE1, 0xC5DD, 0x6CE2, 0xB2A8, + 0x6CE3, 0xC6FC, 0x6CE4, 0x9B85, 0x6CE5, 0xC4E0, 0x6CE6, 0x9B86, 0x6CE7, 0x9B87, 0x6CE8, 0xD7A2, 0x6CE9, 0x9B88, 0x6CEA, 0xC0E1, + 0x6CEB, 0xE3F9, 0x6CEC, 0x9B89, 0x6CED, 0x9B8A, 0x6CEE, 0xE3FA, 0x6CEF, 0xE3FD, 0x6CF0, 0xCCA9, 0x6CF1, 0xE3F3, 0x6CF2, 0x9B8B, + 0x6CF3, 0xD3BE, 0x6CF4, 0x9B8C, 0x6CF5, 0xB1C3, 0x6CF6, 0xEDB4, 0x6CF7, 0xE3F1, 0x6CF8, 0xE3F2, 0x6CF9, 0x9B8D, 0x6CFA, 0xE3F8, + 0x6CFB, 0xD0BA, 0x6CFC, 0xC6C3, 0x6CFD, 0xD4F3, 0x6CFE, 0xE3FE, 0x6CFF, 0x9B8E, 0x6D00, 0x9B8F, 0x6D01, 0xBDE0, 0x6D02, 0x9B90, + 0x6D03, 0x9B91, 0x6D04, 0xE4A7, 0x6D05, 0x9B92, 0x6D06, 0x9B93, 0x6D07, 0xE4A6, 0x6D08, 0x9B94, 0x6D09, 0x9B95, 0x6D0A, 0x9B96, + 0x6D0B, 0xD1F3, 0x6D0C, 0xE4A3, 0x6D0D, 0x9B97, 0x6D0E, 0xE4A9, 0x6D0F, 0x9B98, 0x6D10, 0x9B99, 0x6D11, 0x9B9A, 0x6D12, 0xC8F7, + 0x6D13, 0x9B9B, 0x6D14, 0x9B9C, 0x6D15, 0x9B9D, 0x6D16, 0x9B9E, 0x6D17, 0xCFB4, 0x6D18, 0x9B9F, 0x6D19, 0xE4A8, 0x6D1A, 0xE4AE, + 0x6D1B, 0xC2E5, 0x6D1C, 0x9BA0, 0x6D1D, 0x9BA1, 0x6D1E, 0xB6B4, 0x6D1F, 0x9BA2, 0x6D20, 0x9BA3, 0x6D21, 0x9BA4, 0x6D22, 0x9BA5, + 0x6D23, 0x9BA6, 0x6D24, 0x9BA7, 0x6D25, 0xBDF2, 0x6D26, 0x9BA8, 0x6D27, 0xE4A2, 0x6D28, 0x9BA9, 0x6D29, 0x9BAA, 0x6D2A, 0xBAE9, + 0x6D2B, 0xE4AA, 0x6D2C, 0x9BAB, 0x6D2D, 0x9BAC, 0x6D2E, 0xE4AC, 0x6D2F, 0x9BAD, 0x6D30, 0x9BAE, 0x6D31, 0xB6FD, 0x6D32, 0xD6DE, + 0x6D33, 0xE4B2, 0x6D34, 0x9BAF, 0x6D35, 0xE4AD, 0x6D36, 0x9BB0, 0x6D37, 0x9BB1, 0x6D38, 0x9BB2, 0x6D39, 0xE4A1, 0x6D3A, 0x9BB3, + 0x6D3B, 0xBBEE, 0x6D3C, 0xCDDD, 0x6D3D, 0xC7A2, 0x6D3E, 0xC5C9, 0x6D3F, 0x9BB4, 0x6D40, 0x9BB5, 0x6D41, 0xC1F7, 0x6D42, 0x9BB6, + 0x6D43, 0xE4A4, 0x6D44, 0x9BB7, 0x6D45, 0xC7B3, 0x6D46, 0xBDAC, 0x6D47, 0xBDBD, 0x6D48, 0xE4A5, 0x6D49, 0x9BB8, 0x6D4A, 0xD7C7, + 0x6D4B, 0xB2E2, 0x6D4C, 0x9BB9, 0x6D4D, 0xE4AB, 0x6D4E, 0xBCC3, 0x6D4F, 0xE4AF, 0x6D50, 0x9BBA, 0x6D51, 0xBBEB, 0x6D52, 0xE4B0, + 0x6D53, 0xC5A8, 0x6D54, 0xE4B1, 0x6D55, 0x9BBB, 0x6D56, 0x9BBC, 0x6D57, 0x9BBD, 0x6D58, 0x9BBE, 0x6D59, 0xD5E3, 0x6D5A, 0xBFA3, + 0x6D5B, 0x9BBF, 0x6D5C, 0xE4BA, 0x6D5D, 0x9BC0, 0x6D5E, 0xE4B7, 0x6D5F, 0x9BC1, 0x6D60, 0xE4BB, 0x6D61, 0x9BC2, 0x6D62, 0x9BC3, + 0x6D63, 0xE4BD, 0x6D64, 0x9BC4, 0x6D65, 0x9BC5, 0x6D66, 0xC6D6, 0x6D67, 0x9BC6, 0x6D68, 0x9BC7, 0x6D69, 0xBAC6, 0x6D6A, 0xC0CB, + 0x6D6B, 0x9BC8, 0x6D6C, 0x9BC9, 0x6D6D, 0x9BCA, 0x6D6E, 0xB8A1, 0x6D6F, 0xE4B4, 0x6D70, 0x9BCB, 0x6D71, 0x9BCC, 0x6D72, 0x9BCD, + 0x6D73, 0x9BCE, 0x6D74, 0xD4A1, 0x6D75, 0x9BCF, 0x6D76, 0x9BD0, 0x6D77, 0xBAA3, 0x6D78, 0xBDFE, 0x6D79, 0x9BD1, 0x6D7A, 0x9BD2, + 0x6D7B, 0x9BD3, 0x6D7C, 0xE4BC, 0x6D7D, 0x9BD4, 0x6D7E, 0x9BD5, 0x6D7F, 0x9BD6, 0x6D80, 0x9BD7, 0x6D81, 0x9BD8, 0x6D82, 0xCDBF, + 0x6D83, 0x9BD9, 0x6D84, 0x9BDA, 0x6D85, 0xC4F9, 0x6D86, 0x9BDB, 0x6D87, 0x9BDC, 0x6D88, 0xCFFB, 0x6D89, 0xC9E6, 0x6D8A, 0x9BDD, + 0x6D8B, 0x9BDE, 0x6D8C, 0xD3BF, 0x6D8D, 0x9BDF, 0x6D8E, 0xCFD1, 0x6D8F, 0x9BE0, 0x6D90, 0x9BE1, 0x6D91, 0xE4B3, 0x6D92, 0x9BE2, + 0x6D93, 0xE4B8, 0x6D94, 0xE4B9, 0x6D95, 0xCCE9, 0x6D96, 0x9BE3, 0x6D97, 0x9BE4, 0x6D98, 0x9BE5, 0x6D99, 0x9BE6, 0x6D9A, 0x9BE7, + 0x6D9B, 0xCCCE, 0x6D9C, 0x9BE8, 0x6D9D, 0xC0D4, 0x6D9E, 0xE4B5, 0x6D9F, 0xC1B0, 0x6DA0, 0xE4B6, 0x6DA1, 0xCED0, 0x6DA2, 0x9BE9, + 0x6DA3, 0xBBC1, 0x6DA4, 0xB5D3, 0x6DA5, 0x9BEA, 0x6DA6, 0xC8F3, 0x6DA7, 0xBDA7, 0x6DA8, 0xD5C7, 0x6DA9, 0xC9AC, 0x6DAA, 0xB8A2, + 0x6DAB, 0xE4CA, 0x6DAC, 0x9BEB, 0x6DAD, 0x9BEC, 0x6DAE, 0xE4CC, 0x6DAF, 0xD1C4, 0x6DB0, 0x9BED, 0x6DB1, 0x9BEE, 0x6DB2, 0xD2BA, + 0x6DB3, 0x9BEF, 0x6DB4, 0x9BF0, 0x6DB5, 0xBAAD, 0x6DB6, 0x9BF1, 0x6DB7, 0x9BF2, 0x6DB8, 0xBAD4, 0x6DB9, 0x9BF3, 0x6DBA, 0x9BF4, + 0x6DBB, 0x9BF5, 0x6DBC, 0x9BF6, 0x6DBD, 0x9BF7, 0x6DBE, 0x9BF8, 0x6DBF, 0xE4C3, 0x6DC0, 0xB5ED, 0x6DC1, 0x9BF9, 0x6DC2, 0x9BFA, + 0x6DC3, 0x9BFB, 0x6DC4, 0xD7CD, 0x6DC5, 0xE4C0, 0x6DC6, 0xCFFD, 0x6DC7, 0xE4BF, 0x6DC8, 0x9BFC, 0x6DC9, 0x9BFD, 0x6DCA, 0x9BFE, + 0x6DCB, 0xC1DC, 0x6DCC, 0xCCCA, 0x6DCD, 0x9C40, 0x6DCE, 0x9C41, 0x6DCF, 0x9C42, 0x6DD0, 0x9C43, 0x6DD1, 0xCAE7, 0x6DD2, 0x9C44, + 0x6DD3, 0x9C45, 0x6DD4, 0x9C46, 0x6DD5, 0x9C47, 0x6DD6, 0xC4D7, 0x6DD7, 0x9C48, 0x6DD8, 0xCCD4, 0x6DD9, 0xE4C8, 0x6DDA, 0x9C49, + 0x6DDB, 0x9C4A, 0x6DDC, 0x9C4B, 0x6DDD, 0xE4C7, 0x6DDE, 0xE4C1, 0x6DDF, 0x9C4C, 0x6DE0, 0xE4C4, 0x6DE1, 0xB5AD, 0x6DE2, 0x9C4D, + 0x6DE3, 0x9C4E, 0x6DE4, 0xD3D9, 0x6DE5, 0x9C4F, 0x6DE6, 0xE4C6, 0x6DE7, 0x9C50, 0x6DE8, 0x9C51, 0x6DE9, 0x9C52, 0x6DEA, 0x9C53, + 0x6DEB, 0xD2F9, 0x6DEC, 0xB4E3, 0x6DED, 0x9C54, 0x6DEE, 0xBBB4, 0x6DEF, 0x9C55, 0x6DF0, 0x9C56, 0x6DF1, 0xC9EE, 0x6DF2, 0x9C57, + 0x6DF3, 0xB4BE, 0x6DF4, 0x9C58, 0x6DF5, 0x9C59, 0x6DF6, 0x9C5A, 0x6DF7, 0xBBEC, 0x6DF8, 0x9C5B, 0x6DF9, 0xD1CD, 0x6DFA, 0x9C5C, + 0x6DFB, 0xCCED, 0x6DFC, 0xEDB5, 0x6DFD, 0x9C5D, 0x6DFE, 0x9C5E, 0x6DFF, 0x9C5F, 0x6E00, 0x9C60, 0x6E01, 0x9C61, 0x6E02, 0x9C62, + 0x6E03, 0x9C63, 0x6E04, 0x9C64, 0x6E05, 0xC7E5, 0x6E06, 0x9C65, 0x6E07, 0x9C66, 0x6E08, 0x9C67, 0x6E09, 0x9C68, 0x6E0A, 0xD4A8, + 0x6E0B, 0x9C69, 0x6E0C, 0xE4CB, 0x6E0D, 0xD7D5, 0x6E0E, 0xE4C2, 0x6E0F, 0x9C6A, 0x6E10, 0xBDA5, 0x6E11, 0xE4C5, 0x6E12, 0x9C6B, + 0x6E13, 0x9C6C, 0x6E14, 0xD3E6, 0x6E15, 0x9C6D, 0x6E16, 0xE4C9, 0x6E17, 0xC9F8, 0x6E18, 0x9C6E, 0x6E19, 0x9C6F, 0x6E1A, 0xE4BE, + 0x6E1B, 0x9C70, 0x6E1C, 0x9C71, 0x6E1D, 0xD3E5, 0x6E1E, 0x9C72, 0x6E1F, 0x9C73, 0x6E20, 0xC7FE, 0x6E21, 0xB6C9, 0x6E22, 0x9C74, + 0x6E23, 0xD4FC, 0x6E24, 0xB2B3, 0x6E25, 0xE4D7, 0x6E26, 0x9C75, 0x6E27, 0x9C76, 0x6E28, 0x9C77, 0x6E29, 0xCEC2, 0x6E2A, 0x9C78, + 0x6E2B, 0xE4CD, 0x6E2C, 0x9C79, 0x6E2D, 0xCEBC, 0x6E2E, 0x9C7A, 0x6E2F, 0xB8DB, 0x6E30, 0x9C7B, 0x6E31, 0x9C7C, 0x6E32, 0xE4D6, + 0x6E33, 0x9C7D, 0x6E34, 0xBFCA, 0x6E35, 0x9C7E, 0x6E36, 0x9C80, 0x6E37, 0x9C81, 0x6E38, 0xD3CE, 0x6E39, 0x9C82, 0x6E3A, 0xC3EC, + 0x6E3B, 0x9C83, 0x6E3C, 0x9C84, 0x6E3D, 0x9C85, 0x6E3E, 0x9C86, 0x6E3F, 0x9C87, 0x6E40, 0x9C88, 0x6E41, 0x9C89, 0x6E42, 0x9C8A, + 0x6E43, 0xC5C8, 0x6E44, 0xE4D8, 0x6E45, 0x9C8B, 0x6E46, 0x9C8C, 0x6E47, 0x9C8D, 0x6E48, 0x9C8E, 0x6E49, 0x9C8F, 0x6E4A, 0x9C90, + 0x6E4B, 0x9C91, 0x6E4C, 0x9C92, 0x6E4D, 0xCDC4, 0x6E4E, 0xE4CF, 0x6E4F, 0x9C93, 0x6E50, 0x9C94, 0x6E51, 0x9C95, 0x6E52, 0x9C96, + 0x6E53, 0xE4D4, 0x6E54, 0xE4D5, 0x6E55, 0x9C97, 0x6E56, 0xBAFE, 0x6E57, 0x9C98, 0x6E58, 0xCFE6, 0x6E59, 0x9C99, 0x6E5A, 0x9C9A, + 0x6E5B, 0xD5BF, 0x6E5C, 0x9C9B, 0x6E5D, 0x9C9C, 0x6E5E, 0x9C9D, 0x6E5F, 0xE4D2, 0x6E60, 0x9C9E, 0x6E61, 0x9C9F, 0x6E62, 0x9CA0, + 0x6E63, 0x9CA1, 0x6E64, 0x9CA2, 0x6E65, 0x9CA3, 0x6E66, 0x9CA4, 0x6E67, 0x9CA5, 0x6E68, 0x9CA6, 0x6E69, 0x9CA7, 0x6E6A, 0x9CA8, + 0x6E6B, 0xE4D0, 0x6E6C, 0x9CA9, 0x6E6D, 0x9CAA, 0x6E6E, 0xE4CE, 0x6E6F, 0x9CAB, 0x6E70, 0x9CAC, 0x6E71, 0x9CAD, 0x6E72, 0x9CAE, + 0x6E73, 0x9CAF, 0x6E74, 0x9CB0, 0x6E75, 0x9CB1, 0x6E76, 0x9CB2, 0x6E77, 0x9CB3, 0x6E78, 0x9CB4, 0x6E79, 0x9CB5, 0x6E7A, 0x9CB6, + 0x6E7B, 0x9CB7, 0x6E7C, 0x9CB8, 0x6E7D, 0x9CB9, 0x6E7E, 0xCDE5, 0x6E7F, 0xCAAA, 0x6E80, 0x9CBA, 0x6E81, 0x9CBB, 0x6E82, 0x9CBC, + 0x6E83, 0xC0A3, 0x6E84, 0x9CBD, 0x6E85, 0xBDA6, 0x6E86, 0xE4D3, 0x6E87, 0x9CBE, 0x6E88, 0x9CBF, 0x6E89, 0xB8C8, 0x6E8A, 0x9CC0, + 0x6E8B, 0x9CC1, 0x6E8C, 0x9CC2, 0x6E8D, 0x9CC3, 0x6E8E, 0x9CC4, 0x6E8F, 0xE4E7, 0x6E90, 0xD4B4, 0x6E91, 0x9CC5, 0x6E92, 0x9CC6, + 0x6E93, 0x9CC7, 0x6E94, 0x9CC8, 0x6E95, 0x9CC9, 0x6E96, 0x9CCA, 0x6E97, 0x9CCB, 0x6E98, 0xE4DB, 0x6E99, 0x9CCC, 0x6E9A, 0x9CCD, + 0x6E9B, 0x9CCE, 0x6E9C, 0xC1EF, 0x6E9D, 0x9CCF, 0x6E9E, 0x9CD0, 0x6E9F, 0xE4E9, 0x6EA0, 0x9CD1, 0x6EA1, 0x9CD2, 0x6EA2, 0xD2E7, + 0x6EA3, 0x9CD3, 0x6EA4, 0x9CD4, 0x6EA5, 0xE4DF, 0x6EA6, 0x9CD5, 0x6EA7, 0xE4E0, 0x6EA8, 0x9CD6, 0x6EA9, 0x9CD7, 0x6EAA, 0xCFAA, + 0x6EAB, 0x9CD8, 0x6EAC, 0x9CD9, 0x6EAD, 0x9CDA, 0x6EAE, 0x9CDB, 0x6EAF, 0xCBDD, 0x6EB0, 0x9CDC, 0x6EB1, 0xE4DA, 0x6EB2, 0xE4D1, + 0x6EB3, 0x9CDD, 0x6EB4, 0xE4E5, 0x6EB5, 0x9CDE, 0x6EB6, 0xC8DC, 0x6EB7, 0xE4E3, 0x6EB8, 0x9CDF, 0x6EB9, 0x9CE0, 0x6EBA, 0xC4E7, + 0x6EBB, 0xE4E2, 0x6EBC, 0x9CE1, 0x6EBD, 0xE4E1, 0x6EBE, 0x9CE2, 0x6EBF, 0x9CE3, 0x6EC0, 0x9CE4, 0x6EC1, 0xB3FC, 0x6EC2, 0xE4E8, + 0x6EC3, 0x9CE5, 0x6EC4, 0x9CE6, 0x6EC5, 0x9CE7, 0x6EC6, 0x9CE8, 0x6EC7, 0xB5E1, 0x6EC8, 0x9CE9, 0x6EC9, 0x9CEA, 0x6ECA, 0x9CEB, + 0x6ECB, 0xD7CC, 0x6ECC, 0x9CEC, 0x6ECD, 0x9CED, 0x6ECE, 0x9CEE, 0x6ECF, 0xE4E6, 0x6ED0, 0x9CEF, 0x6ED1, 0xBBAC, 0x6ED2, 0x9CF0, + 0x6ED3, 0xD7D2, 0x6ED4, 0xCCCF, 0x6ED5, 0xEBF8, 0x6ED6, 0x9CF1, 0x6ED7, 0xE4E4, 0x6ED8, 0x9CF2, 0x6ED9, 0x9CF3, 0x6EDA, 0xB9F6, + 0x6EDB, 0x9CF4, 0x6EDC, 0x9CF5, 0x6EDD, 0x9CF6, 0x6EDE, 0xD6CD, 0x6EDF, 0xE4D9, 0x6EE0, 0xE4DC, 0x6EE1, 0xC2FA, 0x6EE2, 0xE4DE, + 0x6EE3, 0x9CF7, 0x6EE4, 0xC2CB, 0x6EE5, 0xC0C4, 0x6EE6, 0xC2D0, 0x6EE7, 0x9CF8, 0x6EE8, 0xB1F5, 0x6EE9, 0xCCB2, 0x6EEA, 0x9CF9, + 0x6EEB, 0x9CFA, 0x6EEC, 0x9CFB, 0x6EED, 0x9CFC, 0x6EEE, 0x9CFD, 0x6EEF, 0x9CFE, 0x6EF0, 0x9D40, 0x6EF1, 0x9D41, 0x6EF2, 0x9D42, + 0x6EF3, 0x9D43, 0x6EF4, 0xB5CE, 0x6EF5, 0x9D44, 0x6EF6, 0x9D45, 0x6EF7, 0x9D46, 0x6EF8, 0x9D47, 0x6EF9, 0xE4EF, 0x6EFA, 0x9D48, + 0x6EFB, 0x9D49, 0x6EFC, 0x9D4A, 0x6EFD, 0x9D4B, 0x6EFE, 0x9D4C, 0x6EFF, 0x9D4D, 0x6F00, 0x9D4E, 0x6F01, 0x9D4F, 0x6F02, 0xC6AF, + 0x6F03, 0x9D50, 0x6F04, 0x9D51, 0x6F05, 0x9D52, 0x6F06, 0xC6E1, 0x6F07, 0x9D53, 0x6F08, 0x9D54, 0x6F09, 0xE4F5, 0x6F0A, 0x9D55, + 0x6F0B, 0x9D56, 0x6F0C, 0x9D57, 0x6F0D, 0x9D58, 0x6F0E, 0x9D59, 0x6F0F, 0xC2A9, 0x6F10, 0x9D5A, 0x6F11, 0x9D5B, 0x6F12, 0x9D5C, + 0x6F13, 0xC0EC, 0x6F14, 0xD1DD, 0x6F15, 0xE4EE, 0x6F16, 0x9D5D, 0x6F17, 0x9D5E, 0x6F18, 0x9D5F, 0x6F19, 0x9D60, 0x6F1A, 0x9D61, + 0x6F1B, 0x9D62, 0x6F1C, 0x9D63, 0x6F1D, 0x9D64, 0x6F1E, 0x9D65, 0x6F1F, 0x9D66, 0x6F20, 0xC4AE, 0x6F21, 0x9D67, 0x6F22, 0x9D68, + 0x6F23, 0x9D69, 0x6F24, 0xE4ED, 0x6F25, 0x9D6A, 0x6F26, 0x9D6B, 0x6F27, 0x9D6C, 0x6F28, 0x9D6D, 0x6F29, 0xE4F6, 0x6F2A, 0xE4F4, + 0x6F2B, 0xC2FE, 0x6F2C, 0x9D6E, 0x6F2D, 0xE4DD, 0x6F2E, 0x9D6F, 0x6F2F, 0xE4F0, 0x6F30, 0x9D70, 0x6F31, 0xCAFE, 0x6F32, 0x9D71, + 0x6F33, 0xD5C4, 0x6F34, 0x9D72, 0x6F35, 0x9D73, 0x6F36, 0xE4F1, 0x6F37, 0x9D74, 0x6F38, 0x9D75, 0x6F39, 0x9D76, 0x6F3A, 0x9D77, + 0x6F3B, 0x9D78, 0x6F3C, 0x9D79, 0x6F3D, 0x9D7A, 0x6F3E, 0xD1FA, 0x6F3F, 0x9D7B, 0x6F40, 0x9D7C, 0x6F41, 0x9D7D, 0x6F42, 0x9D7E, + 0x6F43, 0x9D80, 0x6F44, 0x9D81, 0x6F45, 0x9D82, 0x6F46, 0xE4EB, 0x6F47, 0xE4EC, 0x6F48, 0x9D83, 0x6F49, 0x9D84, 0x6F4A, 0x9D85, + 0x6F4B, 0xE4F2, 0x6F4C, 0x9D86, 0x6F4D, 0xCEAB, 0x6F4E, 0x9D87, 0x6F4F, 0x9D88, 0x6F50, 0x9D89, 0x6F51, 0x9D8A, 0x6F52, 0x9D8B, + 0x6F53, 0x9D8C, 0x6F54, 0x9D8D, 0x6F55, 0x9D8E, 0x6F56, 0x9D8F, 0x6F57, 0x9D90, 0x6F58, 0xC5CB, 0x6F59, 0x9D91, 0x6F5A, 0x9D92, + 0x6F5B, 0x9D93, 0x6F5C, 0xC7B1, 0x6F5D, 0x9D94, 0x6F5E, 0xC2BA, 0x6F5F, 0x9D95, 0x6F60, 0x9D96, 0x6F61, 0x9D97, 0x6F62, 0xE4EA, + 0x6F63, 0x9D98, 0x6F64, 0x9D99, 0x6F65, 0x9D9A, 0x6F66, 0xC1CA, 0x6F67, 0x9D9B, 0x6F68, 0x9D9C, 0x6F69, 0x9D9D, 0x6F6A, 0x9D9E, + 0x6F6B, 0x9D9F, 0x6F6C, 0x9DA0, 0x6F6D, 0xCCB6, 0x6F6E, 0xB3B1, 0x6F6F, 0x9DA1, 0x6F70, 0x9DA2, 0x6F71, 0x9DA3, 0x6F72, 0xE4FB, + 0x6F73, 0x9DA4, 0x6F74, 0xE4F3, 0x6F75, 0x9DA5, 0x6F76, 0x9DA6, 0x6F77, 0x9DA7, 0x6F78, 0xE4FA, 0x6F79, 0x9DA8, 0x6F7A, 0xE4FD, + 0x6F7B, 0x9DA9, 0x6F7C, 0xE4FC, 0x6F7D, 0x9DAA, 0x6F7E, 0x9DAB, 0x6F7F, 0x9DAC, 0x6F80, 0x9DAD, 0x6F81, 0x9DAE, 0x6F82, 0x9DAF, + 0x6F83, 0x9DB0, 0x6F84, 0xB3CE, 0x6F85, 0x9DB1, 0x6F86, 0x9DB2, 0x6F87, 0x9DB3, 0x6F88, 0xB3BA, 0x6F89, 0xE4F7, 0x6F8A, 0x9DB4, + 0x6F8B, 0x9DB5, 0x6F8C, 0xE4F9, 0x6F8D, 0xE4F8, 0x6F8E, 0xC5EC, 0x6F8F, 0x9DB6, 0x6F90, 0x9DB7, 0x6F91, 0x9DB8, 0x6F92, 0x9DB9, + 0x6F93, 0x9DBA, 0x6F94, 0x9DBB, 0x6F95, 0x9DBC, 0x6F96, 0x9DBD, 0x6F97, 0x9DBE, 0x6F98, 0x9DBF, 0x6F99, 0x9DC0, 0x6F9A, 0x9DC1, + 0x6F9B, 0x9DC2, 0x6F9C, 0xC0BD, 0x6F9D, 0x9DC3, 0x6F9E, 0x9DC4, 0x6F9F, 0x9DC5, 0x6FA0, 0x9DC6, 0x6FA1, 0xD4E8, 0x6FA2, 0x9DC7, + 0x6FA3, 0x9DC8, 0x6FA4, 0x9DC9, 0x6FA5, 0x9DCA, 0x6FA6, 0x9DCB, 0x6FA7, 0xE5A2, 0x6FA8, 0x9DCC, 0x6FA9, 0x9DCD, 0x6FAA, 0x9DCE, + 0x6FAB, 0x9DCF, 0x6FAC, 0x9DD0, 0x6FAD, 0x9DD1, 0x6FAE, 0x9DD2, 0x6FAF, 0x9DD3, 0x6FB0, 0x9DD4, 0x6FB1, 0x9DD5, 0x6FB2, 0x9DD6, + 0x6FB3, 0xB0C4, 0x6FB4, 0x9DD7, 0x6FB5, 0x9DD8, 0x6FB6, 0xE5A4, 0x6FB7, 0x9DD9, 0x6FB8, 0x9DDA, 0x6FB9, 0xE5A3, 0x6FBA, 0x9DDB, + 0x6FBB, 0x9DDC, 0x6FBC, 0x9DDD, 0x6FBD, 0x9DDE, 0x6FBE, 0x9DDF, 0x6FBF, 0x9DE0, 0x6FC0, 0xBCA4, 0x6FC1, 0x9DE1, 0x6FC2, 0xE5A5, + 0x6FC3, 0x9DE2, 0x6FC4, 0x9DE3, 0x6FC5, 0x9DE4, 0x6FC6, 0x9DE5, 0x6FC7, 0x9DE6, 0x6FC8, 0x9DE7, 0x6FC9, 0xE5A1, 0x6FCA, 0x9DE8, + 0x6FCB, 0x9DE9, 0x6FCC, 0x9DEA, 0x6FCD, 0x9DEB, 0x6FCE, 0x9DEC, 0x6FCF, 0x9DED, 0x6FD0, 0x9DEE, 0x6FD1, 0xE4FE, 0x6FD2, 0xB1F4, + 0x6FD3, 0x9DEF, 0x6FD4, 0x9DF0, 0x6FD5, 0x9DF1, 0x6FD6, 0x9DF2, 0x6FD7, 0x9DF3, 0x6FD8, 0x9DF4, 0x6FD9, 0x9DF5, 0x6FDA, 0x9DF6, + 0x6FDB, 0x9DF7, 0x6FDC, 0x9DF8, 0x6FDD, 0x9DF9, 0x6FDE, 0xE5A8, 0x6FDF, 0x9DFA, 0x6FE0, 0xE5A9, 0x6FE1, 0xE5A6, 0x6FE2, 0x9DFB, + 0x6FE3, 0x9DFC, 0x6FE4, 0x9DFD, 0x6FE5, 0x9DFE, 0x6FE6, 0x9E40, 0x6FE7, 0x9E41, 0x6FE8, 0x9E42, 0x6FE9, 0x9E43, 0x6FEA, 0x9E44, + 0x6FEB, 0x9E45, 0x6FEC, 0x9E46, 0x6FED, 0x9E47, 0x6FEE, 0xE5A7, 0x6FEF, 0xE5AA, 0x6FF0, 0x9E48, 0x6FF1, 0x9E49, 0x6FF2, 0x9E4A, + 0x6FF3, 0x9E4B, 0x6FF4, 0x9E4C, 0x6FF5, 0x9E4D, 0x6FF6, 0x9E4E, 0x6FF7, 0x9E4F, 0x6FF8, 0x9E50, 0x6FF9, 0x9E51, 0x6FFA, 0x9E52, + 0x6FFB, 0x9E53, 0x6FFC, 0x9E54, 0x6FFD, 0x9E55, 0x6FFE, 0x9E56, 0x6FFF, 0x9E57, 0x7000, 0x9E58, 0x7001, 0x9E59, 0x7002, 0x9E5A, + 0x7003, 0x9E5B, 0x7004, 0x9E5C, 0x7005, 0x9E5D, 0x7006, 0x9E5E, 0x7007, 0x9E5F, 0x7008, 0x9E60, 0x7009, 0x9E61, 0x700A, 0x9E62, + 0x700B, 0x9E63, 0x700C, 0x9E64, 0x700D, 0x9E65, 0x700E, 0x9E66, 0x700F, 0x9E67, 0x7010, 0x9E68, 0x7011, 0xC6D9, 0x7012, 0x9E69, + 0x7013, 0x9E6A, 0x7014, 0x9E6B, 0x7015, 0x9E6C, 0x7016, 0x9E6D, 0x7017, 0x9E6E, 0x7018, 0x9E6F, 0x7019, 0x9E70, 0x701A, 0xE5AB, + 0x701B, 0xE5AD, 0x701C, 0x9E71, 0x701D, 0x9E72, 0x701E, 0x9E73, 0x701F, 0x9E74, 0x7020, 0x9E75, 0x7021, 0x9E76, 0x7022, 0x9E77, + 0x7023, 0xE5AC, 0x7024, 0x9E78, 0x7025, 0x9E79, 0x7026, 0x9E7A, 0x7027, 0x9E7B, 0x7028, 0x9E7C, 0x7029, 0x9E7D, 0x702A, 0x9E7E, + 0x702B, 0x9E80, 0x702C, 0x9E81, 0x702D, 0x9E82, 0x702E, 0x9E83, 0x702F, 0x9E84, 0x7030, 0x9E85, 0x7031, 0x9E86, 0x7032, 0x9E87, + 0x7033, 0x9E88, 0x7034, 0x9E89, 0x7035, 0xE5AF, 0x7036, 0x9E8A, 0x7037, 0x9E8B, 0x7038, 0x9E8C, 0x7039, 0xE5AE, 0x703A, 0x9E8D, + 0x703B, 0x9E8E, 0x703C, 0x9E8F, 0x703D, 0x9E90, 0x703E, 0x9E91, 0x703F, 0x9E92, 0x7040, 0x9E93, 0x7041, 0x9E94, 0x7042, 0x9E95, + 0x7043, 0x9E96, 0x7044, 0x9E97, 0x7045, 0x9E98, 0x7046, 0x9E99, 0x7047, 0x9E9A, 0x7048, 0x9E9B, 0x7049, 0x9E9C, 0x704A, 0x9E9D, + 0x704B, 0x9E9E, 0x704C, 0xB9E0, 0x704D, 0x9E9F, 0x704E, 0x9EA0, 0x704F, 0xE5B0, 0x7050, 0x9EA1, 0x7051, 0x9EA2, 0x7052, 0x9EA3, + 0x7053, 0x9EA4, 0x7054, 0x9EA5, 0x7055, 0x9EA6, 0x7056, 0x9EA7, 0x7057, 0x9EA8, 0x7058, 0x9EA9, 0x7059, 0x9EAA, 0x705A, 0x9EAB, + 0x705B, 0x9EAC, 0x705C, 0x9EAD, 0x705D, 0x9EAE, 0x705E, 0xE5B1, 0x705F, 0x9EAF, 0x7060, 0x9EB0, 0x7061, 0x9EB1, 0x7062, 0x9EB2, + 0x7063, 0x9EB3, 0x7064, 0x9EB4, 0x7065, 0x9EB5, 0x7066, 0x9EB6, 0x7067, 0x9EB7, 0x7068, 0x9EB8, 0x7069, 0x9EB9, 0x706A, 0x9EBA, + 0x706B, 0xBBF0, 0x706C, 0xECE1, 0x706D, 0xC3F0, 0x706E, 0x9EBB, 0x706F, 0xB5C6, 0x7070, 0xBBD2, 0x7071, 0x9EBC, 0x7072, 0x9EBD, + 0x7073, 0x9EBE, 0x7074, 0x9EBF, 0x7075, 0xC1E9, 0x7076, 0xD4EE, 0x7077, 0x9EC0, 0x7078, 0xBEC4, 0x7079, 0x9EC1, 0x707A, 0x9EC2, + 0x707B, 0x9EC3, 0x707C, 0xD7C6, 0x707D, 0x9EC4, 0x707E, 0xD4D6, 0x707F, 0xB2D3, 0x7080, 0xECBE, 0x7081, 0x9EC5, 0x7082, 0x9EC6, + 0x7083, 0x9EC7, 0x7084, 0x9EC8, 0x7085, 0xEAC1, 0x7086, 0x9EC9, 0x7087, 0x9ECA, 0x7088, 0x9ECB, 0x7089, 0xC2AF, 0x708A, 0xB4B6, + 0x708B, 0x9ECC, 0x708C, 0x9ECD, 0x708D, 0x9ECE, 0x708E, 0xD1D7, 0x708F, 0x9ECF, 0x7090, 0x9ED0, 0x7091, 0x9ED1, 0x7092, 0xB3B4, + 0x7093, 0x9ED2, 0x7094, 0xC8B2, 0x7095, 0xBFBB, 0x7096, 0xECC0, 0x7097, 0x9ED3, 0x7098, 0x9ED4, 0x7099, 0xD6CB, 0x709A, 0x9ED5, + 0x709B, 0x9ED6, 0x709C, 0xECBF, 0x709D, 0xECC1, 0x709E, 0x9ED7, 0x709F, 0x9ED8, 0x70A0, 0x9ED9, 0x70A1, 0x9EDA, 0x70A2, 0x9EDB, + 0x70A3, 0x9EDC, 0x70A4, 0x9EDD, 0x70A5, 0x9EDE, 0x70A6, 0x9EDF, 0x70A7, 0x9EE0, 0x70A8, 0x9EE1, 0x70A9, 0x9EE2, 0x70AA, 0x9EE3, + 0x70AB, 0xECC5, 0x70AC, 0xBEE6, 0x70AD, 0xCCBF, 0x70AE, 0xC5DA, 0x70AF, 0xBEBC, 0x70B0, 0x9EE4, 0x70B1, 0xECC6, 0x70B2, 0x9EE5, + 0x70B3, 0xB1FE, 0x70B4, 0x9EE6, 0x70B5, 0x9EE7, 0x70B6, 0x9EE8, 0x70B7, 0xECC4, 0x70B8, 0xD5A8, 0x70B9, 0xB5E3, 0x70BA, 0x9EE9, + 0x70BB, 0xECC2, 0x70BC, 0xC1B6, 0x70BD, 0xB3E3, 0x70BE, 0x9EEA, 0x70BF, 0x9EEB, 0x70C0, 0xECC3, 0x70C1, 0xCBB8, 0x70C2, 0xC0C3, + 0x70C3, 0xCCFE, 0x70C4, 0x9EEC, 0x70C5, 0x9EED, 0x70C6, 0x9EEE, 0x70C7, 0x9EEF, 0x70C8, 0xC1D2, 0x70C9, 0x9EF0, 0x70CA, 0xECC8, + 0x70CB, 0x9EF1, 0x70CC, 0x9EF2, 0x70CD, 0x9EF3, 0x70CE, 0x9EF4, 0x70CF, 0x9EF5, 0x70D0, 0x9EF6, 0x70D1, 0x9EF7, 0x70D2, 0x9EF8, + 0x70D3, 0x9EF9, 0x70D4, 0x9EFA, 0x70D5, 0x9EFB, 0x70D6, 0x9EFC, 0x70D7, 0x9EFD, 0x70D8, 0xBAE6, 0x70D9, 0xC0D3, 0x70DA, 0x9EFE, + 0x70DB, 0xD6F2, 0x70DC, 0x9F40, 0x70DD, 0x9F41, 0x70DE, 0x9F42, 0x70DF, 0xD1CC, 0x70E0, 0x9F43, 0x70E1, 0x9F44, 0x70E2, 0x9F45, + 0x70E3, 0x9F46, 0x70E4, 0xBFBE, 0x70E5, 0x9F47, 0x70E6, 0xB7B3, 0x70E7, 0xC9D5, 0x70E8, 0xECC7, 0x70E9, 0xBBE2, 0x70EA, 0x9F48, + 0x70EB, 0xCCCC, 0x70EC, 0xBDFD, 0x70ED, 0xC8C8, 0x70EE, 0x9F49, 0x70EF, 0xCFA9, 0x70F0, 0x9F4A, 0x70F1, 0x9F4B, 0x70F2, 0x9F4C, + 0x70F3, 0x9F4D, 0x70F4, 0x9F4E, 0x70F5, 0x9F4F, 0x70F6, 0x9F50, 0x70F7, 0xCDE9, 0x70F8, 0x9F51, 0x70F9, 0xC5EB, 0x70FA, 0x9F52, + 0x70FB, 0x9F53, 0x70FC, 0x9F54, 0x70FD, 0xB7E9, 0x70FE, 0x9F55, 0x70FF, 0x9F56, 0x7100, 0x9F57, 0x7101, 0x9F58, 0x7102, 0x9F59, + 0x7103, 0x9F5A, 0x7104, 0x9F5B, 0x7105, 0x9F5C, 0x7106, 0x9F5D, 0x7107, 0x9F5E, 0x7108, 0x9F5F, 0x7109, 0xD1C9, 0x710A, 0xBAB8, + 0x710B, 0x9F60, 0x710C, 0x9F61, 0x710D, 0x9F62, 0x710E, 0x9F63, 0x710F, 0x9F64, 0x7110, 0xECC9, 0x7111, 0x9F65, 0x7112, 0x9F66, + 0x7113, 0xECCA, 0x7114, 0x9F67, 0x7115, 0xBBC0, 0x7116, 0xECCB, 0x7117, 0x9F68, 0x7118, 0xECE2, 0x7119, 0xB1BA, 0x711A, 0xB7D9, + 0x711B, 0x9F69, 0x711C, 0x9F6A, 0x711D, 0x9F6B, 0x711E, 0x9F6C, 0x711F, 0x9F6D, 0x7120, 0x9F6E, 0x7121, 0x9F6F, 0x7122, 0x9F70, + 0x7123, 0x9F71, 0x7124, 0x9F72, 0x7125, 0x9F73, 0x7126, 0xBDB9, 0x7127, 0x9F74, 0x7128, 0x9F75, 0x7129, 0x9F76, 0x712A, 0x9F77, + 0x712B, 0x9F78, 0x712C, 0x9F79, 0x712D, 0x9F7A, 0x712E, 0x9F7B, 0x712F, 0xECCC, 0x7130, 0xD1E6, 0x7131, 0xECCD, 0x7132, 0x9F7C, + 0x7133, 0x9F7D, 0x7134, 0x9F7E, 0x7135, 0x9F80, 0x7136, 0xC8BB, 0x7137, 0x9F81, 0x7138, 0x9F82, 0x7139, 0x9F83, 0x713A, 0x9F84, + 0x713B, 0x9F85, 0x713C, 0x9F86, 0x713D, 0x9F87, 0x713E, 0x9F88, 0x713F, 0x9F89, 0x7140, 0x9F8A, 0x7141, 0x9F8B, 0x7142, 0x9F8C, + 0x7143, 0x9F8D, 0x7144, 0x9F8E, 0x7145, 0xECD1, 0x7146, 0x9F8F, 0x7147, 0x9F90, 0x7148, 0x9F91, 0x7149, 0x9F92, 0x714A, 0xECD3, + 0x714B, 0x9F93, 0x714C, 0xBBCD, 0x714D, 0x9F94, 0x714E, 0xBCE5, 0x714F, 0x9F95, 0x7150, 0x9F96, 0x7151, 0x9F97, 0x7152, 0x9F98, + 0x7153, 0x9F99, 0x7154, 0x9F9A, 0x7155, 0x9F9B, 0x7156, 0x9F9C, 0x7157, 0x9F9D, 0x7158, 0x9F9E, 0x7159, 0x9F9F, 0x715A, 0x9FA0, + 0x715B, 0x9FA1, 0x715C, 0xECCF, 0x715D, 0x9FA2, 0x715E, 0xC9B7, 0x715F, 0x9FA3, 0x7160, 0x9FA4, 0x7161, 0x9FA5, 0x7162, 0x9FA6, + 0x7163, 0x9FA7, 0x7164, 0xC3BA, 0x7165, 0x9FA8, 0x7166, 0xECE3, 0x7167, 0xD5D5, 0x7168, 0xECD0, 0x7169, 0x9FA9, 0x716A, 0x9FAA, + 0x716B, 0x9FAB, 0x716C, 0x9FAC, 0x716D, 0x9FAD, 0x716E, 0xD6F3, 0x716F, 0x9FAE, 0x7170, 0x9FAF, 0x7171, 0x9FB0, 0x7172, 0xECD2, + 0x7173, 0xECCE, 0x7174, 0x9FB1, 0x7175, 0x9FB2, 0x7176, 0x9FB3, 0x7177, 0x9FB4, 0x7178, 0xECD4, 0x7179, 0x9FB5, 0x717A, 0xECD5, + 0x717B, 0x9FB6, 0x717C, 0x9FB7, 0x717D, 0xC9BF, 0x717E, 0x9FB8, 0x717F, 0x9FB9, 0x7180, 0x9FBA, 0x7181, 0x9FBB, 0x7182, 0x9FBC, + 0x7183, 0x9FBD, 0x7184, 0xCFA8, 0x7185, 0x9FBE, 0x7186, 0x9FBF, 0x7187, 0x9FC0, 0x7188, 0x9FC1, 0x7189, 0x9FC2, 0x718A, 0xD0DC, + 0x718B, 0x9FC3, 0x718C, 0x9FC4, 0x718D, 0x9FC5, 0x718E, 0x9FC6, 0x718F, 0xD1AC, 0x7190, 0x9FC7, 0x7191, 0x9FC8, 0x7192, 0x9FC9, + 0x7193, 0x9FCA, 0x7194, 0xC8DB, 0x7195, 0x9FCB, 0x7196, 0x9FCC, 0x7197, 0x9FCD, 0x7198, 0xECD6, 0x7199, 0xCEF5, 0x719A, 0x9FCE, + 0x719B, 0x9FCF, 0x719C, 0x9FD0, 0x719D, 0x9FD1, 0x719E, 0x9FD2, 0x719F, 0xCAEC, 0x71A0, 0xECDA, 0x71A1, 0x9FD3, 0x71A2, 0x9FD4, + 0x71A3, 0x9FD5, 0x71A4, 0x9FD6, 0x71A5, 0x9FD7, 0x71A6, 0x9FD8, 0x71A7, 0x9FD9, 0x71A8, 0xECD9, 0x71A9, 0x9FDA, 0x71AA, 0x9FDB, + 0x71AB, 0x9FDC, 0x71AC, 0xB0BE, 0x71AD, 0x9FDD, 0x71AE, 0x9FDE, 0x71AF, 0x9FDF, 0x71B0, 0x9FE0, 0x71B1, 0x9FE1, 0x71B2, 0x9FE2, + 0x71B3, 0xECD7, 0x71B4, 0x9FE3, 0x71B5, 0xECD8, 0x71B6, 0x9FE4, 0x71B7, 0x9FE5, 0x71B8, 0x9FE6, 0x71B9, 0xECE4, 0x71BA, 0x9FE7, + 0x71BB, 0x9FE8, 0x71BC, 0x9FE9, 0x71BD, 0x9FEA, 0x71BE, 0x9FEB, 0x71BF, 0x9FEC, 0x71C0, 0x9FED, 0x71C1, 0x9FEE, 0x71C2, 0x9FEF, + 0x71C3, 0xC8BC, 0x71C4, 0x9FF0, 0x71C5, 0x9FF1, 0x71C6, 0x9FF2, 0x71C7, 0x9FF3, 0x71C8, 0x9FF4, 0x71C9, 0x9FF5, 0x71CA, 0x9FF6, + 0x71CB, 0x9FF7, 0x71CC, 0x9FF8, 0x71CD, 0x9FF9, 0x71CE, 0xC1C7, 0x71CF, 0x9FFA, 0x71D0, 0x9FFB, 0x71D1, 0x9FFC, 0x71D2, 0x9FFD, + 0x71D3, 0x9FFE, 0x71D4, 0xECDC, 0x71D5, 0xD1E0, 0x71D6, 0xA040, 0x71D7, 0xA041, 0x71D8, 0xA042, 0x71D9, 0xA043, 0x71DA, 0xA044, + 0x71DB, 0xA045, 0x71DC, 0xA046, 0x71DD, 0xA047, 0x71DE, 0xA048, 0x71DF, 0xA049, 0x71E0, 0xECDB, 0x71E1, 0xA04A, 0x71E2, 0xA04B, + 0x71E3, 0xA04C, 0x71E4, 0xA04D, 0x71E5, 0xD4EF, 0x71E6, 0xA04E, 0x71E7, 0xECDD, 0x71E8, 0xA04F, 0x71E9, 0xA050, 0x71EA, 0xA051, + 0x71EB, 0xA052, 0x71EC, 0xA053, 0x71ED, 0xA054, 0x71EE, 0xDBC6, 0x71EF, 0xA055, 0x71F0, 0xA056, 0x71F1, 0xA057, 0x71F2, 0xA058, + 0x71F3, 0xA059, 0x71F4, 0xA05A, 0x71F5, 0xA05B, 0x71F6, 0xA05C, 0x71F7, 0xA05D, 0x71F8, 0xA05E, 0x71F9, 0xECDE, 0x71FA, 0xA05F, + 0x71FB, 0xA060, 0x71FC, 0xA061, 0x71FD, 0xA062, 0x71FE, 0xA063, 0x71FF, 0xA064, 0x7200, 0xA065, 0x7201, 0xA066, 0x7202, 0xA067, + 0x7203, 0xA068, 0x7204, 0xA069, 0x7205, 0xA06A, 0x7206, 0xB1AC, 0x7207, 0xA06B, 0x7208, 0xA06C, 0x7209, 0xA06D, 0x720A, 0xA06E, + 0x720B, 0xA06F, 0x720C, 0xA070, 0x720D, 0xA071, 0x720E, 0xA072, 0x720F, 0xA073, 0x7210, 0xA074, 0x7211, 0xA075, 0x7212, 0xA076, + 0x7213, 0xA077, 0x7214, 0xA078, 0x7215, 0xA079, 0x7216, 0xA07A, 0x7217, 0xA07B, 0x7218, 0xA07C, 0x7219, 0xA07D, 0x721A, 0xA07E, + 0x721B, 0xA080, 0x721C, 0xA081, 0x721D, 0xECDF, 0x721E, 0xA082, 0x721F, 0xA083, 0x7220, 0xA084, 0x7221, 0xA085, 0x7222, 0xA086, + 0x7223, 0xA087, 0x7224, 0xA088, 0x7225, 0xA089, 0x7226, 0xA08A, 0x7227, 0xA08B, 0x7228, 0xECE0, 0x7229, 0xA08C, 0x722A, 0xD7A6, + 0x722B, 0xA08D, 0x722C, 0xC5C0, 0x722D, 0xA08E, 0x722E, 0xA08F, 0x722F, 0xA090, 0x7230, 0xEBBC, 0x7231, 0xB0AE, 0x7232, 0xA091, + 0x7233, 0xA092, 0x7234, 0xA093, 0x7235, 0xBEF4, 0x7236, 0xB8B8, 0x7237, 0xD2AF, 0x7238, 0xB0D6, 0x7239, 0xB5F9, 0x723A, 0xA094, + 0x723B, 0xD8B3, 0x723C, 0xA095, 0x723D, 0xCBAC, 0x723E, 0xA096, 0x723F, 0xE3DD, 0x7240, 0xA097, 0x7241, 0xA098, 0x7242, 0xA099, + 0x7243, 0xA09A, 0x7244, 0xA09B, 0x7245, 0xA09C, 0x7246, 0xA09D, 0x7247, 0xC6AC, 0x7248, 0xB0E6, 0x7249, 0xA09E, 0x724A, 0xA09F, + 0x724B, 0xA0A0, 0x724C, 0xC5C6, 0x724D, 0xEBB9, 0x724E, 0xA0A1, 0x724F, 0xA0A2, 0x7250, 0xA0A3, 0x7251, 0xA0A4, 0x7252, 0xEBBA, + 0x7253, 0xA0A5, 0x7254, 0xA0A6, 0x7255, 0xA0A7, 0x7256, 0xEBBB, 0x7257, 0xA0A8, 0x7258, 0xA0A9, 0x7259, 0xD1C0, 0x725A, 0xA0AA, + 0x725B, 0xC5A3, 0x725C, 0xA0AB, 0x725D, 0xEAF2, 0x725E, 0xA0AC, 0x725F, 0xC4B2, 0x7260, 0xA0AD, 0x7261, 0xC4B5, 0x7262, 0xC0CE, + 0x7263, 0xA0AE, 0x7264, 0xA0AF, 0x7265, 0xA0B0, 0x7266, 0xEAF3, 0x7267, 0xC4C1, 0x7268, 0xA0B1, 0x7269, 0xCEEF, 0x726A, 0xA0B2, + 0x726B, 0xA0B3, 0x726C, 0xA0B4, 0x726D, 0xA0B5, 0x726E, 0xEAF0, 0x726F, 0xEAF4, 0x7270, 0xA0B6, 0x7271, 0xA0B7, 0x7272, 0xC9FC, + 0x7273, 0xA0B8, 0x7274, 0xA0B9, 0x7275, 0xC7A3, 0x7276, 0xA0BA, 0x7277, 0xA0BB, 0x7278, 0xA0BC, 0x7279, 0xCCD8, 0x727A, 0xCEFE, + 0x727B, 0xA0BD, 0x727C, 0xA0BE, 0x727D, 0xA0BF, 0x727E, 0xEAF5, 0x727F, 0xEAF6, 0x7280, 0xCFAC, 0x7281, 0xC0E7, 0x7282, 0xA0C0, + 0x7283, 0xA0C1, 0x7284, 0xEAF7, 0x7285, 0xA0C2, 0x7286, 0xA0C3, 0x7287, 0xA0C4, 0x7288, 0xA0C5, 0x7289, 0xA0C6, 0x728A, 0xB6BF, + 0x728B, 0xEAF8, 0x728C, 0xA0C7, 0x728D, 0xEAF9, 0x728E, 0xA0C8, 0x728F, 0xEAFA, 0x7290, 0xA0C9, 0x7291, 0xA0CA, 0x7292, 0xEAFB, + 0x7293, 0xA0CB, 0x7294, 0xA0CC, 0x7295, 0xA0CD, 0x7296, 0xA0CE, 0x7297, 0xA0CF, 0x7298, 0xA0D0, 0x7299, 0xA0D1, 0x729A, 0xA0D2, + 0x729B, 0xA0D3, 0x729C, 0xA0D4, 0x729D, 0xA0D5, 0x729E, 0xA0D6, 0x729F, 0xEAF1, 0x72A0, 0xA0D7, 0x72A1, 0xA0D8, 0x72A2, 0xA0D9, + 0x72A3, 0xA0DA, 0x72A4, 0xA0DB, 0x72A5, 0xA0DC, 0x72A6, 0xA0DD, 0x72A7, 0xA0DE, 0x72A8, 0xA0DF, 0x72A9, 0xA0E0, 0x72AA, 0xA0E1, + 0x72AB, 0xA0E2, 0x72AC, 0xC8AE, 0x72AD, 0xE1EB, 0x72AE, 0xA0E3, 0x72AF, 0xB7B8, 0x72B0, 0xE1EC, 0x72B1, 0xA0E4, 0x72B2, 0xA0E5, + 0x72B3, 0xA0E6, 0x72B4, 0xE1ED, 0x72B5, 0xA0E7, 0x72B6, 0xD7B4, 0x72B7, 0xE1EE, 0x72B8, 0xE1EF, 0x72B9, 0xD3CC, 0x72BA, 0xA0E8, + 0x72BB, 0xA0E9, 0x72BC, 0xA0EA, 0x72BD, 0xA0EB, 0x72BE, 0xA0EC, 0x72BF, 0xA0ED, 0x72C0, 0xA0EE, 0x72C1, 0xE1F1, 0x72C2, 0xBFF1, + 0x72C3, 0xE1F0, 0x72C4, 0xB5D2, 0x72C5, 0xA0EF, 0x72C6, 0xA0F0, 0x72C7, 0xA0F1, 0x72C8, 0xB1B7, 0x72C9, 0xA0F2, 0x72CA, 0xA0F3, + 0x72CB, 0xA0F4, 0x72CC, 0xA0F5, 0x72CD, 0xE1F3, 0x72CE, 0xE1F2, 0x72CF, 0xA0F6, 0x72D0, 0xBAFC, 0x72D1, 0xA0F7, 0x72D2, 0xE1F4, + 0x72D3, 0xA0F8, 0x72D4, 0xA0F9, 0x72D5, 0xA0FA, 0x72D6, 0xA0FB, 0x72D7, 0xB9B7, 0x72D8, 0xA0FC, 0x72D9, 0xBED1, 0x72DA, 0xA0FD, + 0x72DB, 0xA0FE, 0x72DC, 0xAA40, 0x72DD, 0xAA41, 0x72DE, 0xC4FC, 0x72DF, 0xAA42, 0x72E0, 0xBADD, 0x72E1, 0xBDC6, 0x72E2, 0xAA43, + 0x72E3, 0xAA44, 0x72E4, 0xAA45, 0x72E5, 0xAA46, 0x72E6, 0xAA47, 0x72E7, 0xAA48, 0x72E8, 0xE1F5, 0x72E9, 0xE1F7, 0x72EA, 0xAA49, + 0x72EB, 0xAA4A, 0x72EC, 0xB6C0, 0x72ED, 0xCFC1, 0x72EE, 0xCAA8, 0x72EF, 0xE1F6, 0x72F0, 0xD5F8, 0x72F1, 0xD3FC, 0x72F2, 0xE1F8, + 0x72F3, 0xE1FC, 0x72F4, 0xE1F9, 0x72F5, 0xAA4B, 0x72F6, 0xAA4C, 0x72F7, 0xE1FA, 0x72F8, 0xC0EA, 0x72F9, 0xAA4D, 0x72FA, 0xE1FE, + 0x72FB, 0xE2A1, 0x72FC, 0xC0C7, 0x72FD, 0xAA4E, 0x72FE, 0xAA4F, 0x72FF, 0xAA50, 0x7300, 0xAA51, 0x7301, 0xE1FB, 0x7302, 0xAA52, + 0x7303, 0xE1FD, 0x7304, 0xAA53, 0x7305, 0xAA54, 0x7306, 0xAA55, 0x7307, 0xAA56, 0x7308, 0xAA57, 0x7309, 0xAA58, 0x730A, 0xE2A5, + 0x730B, 0xAA59, 0x730C, 0xAA5A, 0x730D, 0xAA5B, 0x730E, 0xC1D4, 0x730F, 0xAA5C, 0x7310, 0xAA5D, 0x7311, 0xAA5E, 0x7312, 0xAA5F, + 0x7313, 0xE2A3, 0x7314, 0xAA60, 0x7315, 0xE2A8, 0x7316, 0xB2FE, 0x7317, 0xE2A2, 0x7318, 0xAA61, 0x7319, 0xAA62, 0x731A, 0xAA63, + 0x731B, 0xC3CD, 0x731C, 0xB2C2, 0x731D, 0xE2A7, 0x731E, 0xE2A6, 0x731F, 0xAA64, 0x7320, 0xAA65, 0x7321, 0xE2A4, 0x7322, 0xE2A9, + 0x7323, 0xAA66, 0x7324, 0xAA67, 0x7325, 0xE2AB, 0x7326, 0xAA68, 0x7327, 0xAA69, 0x7328, 0xAA6A, 0x7329, 0xD0C9, 0x732A, 0xD6ED, + 0x732B, 0xC3A8, 0x732C, 0xE2AC, 0x732D, 0xAA6B, 0x732E, 0xCFD7, 0x732F, 0xAA6C, 0x7330, 0xAA6D, 0x7331, 0xE2AE, 0x7332, 0xAA6E, + 0x7333, 0xAA6F, 0x7334, 0xBAEF, 0x7335, 0xAA70, 0x7336, 0xAA71, 0x7337, 0xE9E0, 0x7338, 0xE2AD, 0x7339, 0xE2AA, 0x733A, 0xAA72, + 0x733B, 0xAA73, 0x733C, 0xAA74, 0x733D, 0xAA75, 0x733E, 0xBBAB, 0x733F, 0xD4B3, 0x7340, 0xAA76, 0x7341, 0xAA77, 0x7342, 0xAA78, + 0x7343, 0xAA79, 0x7344, 0xAA7A, 0x7345, 0xAA7B, 0x7346, 0xAA7C, 0x7347, 0xAA7D, 0x7348, 0xAA7E, 0x7349, 0xAA80, 0x734A, 0xAA81, + 0x734B, 0xAA82, 0x734C, 0xAA83, 0x734D, 0xE2B0, 0x734E, 0xAA84, 0x734F, 0xAA85, 0x7350, 0xE2AF, 0x7351, 0xAA86, 0x7352, 0xE9E1, + 0x7353, 0xAA87, 0x7354, 0xAA88, 0x7355, 0xAA89, 0x7356, 0xAA8A, 0x7357, 0xE2B1, 0x7358, 0xAA8B, 0x7359, 0xAA8C, 0x735A, 0xAA8D, + 0x735B, 0xAA8E, 0x735C, 0xAA8F, 0x735D, 0xAA90, 0x735E, 0xAA91, 0x735F, 0xAA92, 0x7360, 0xE2B2, 0x7361, 0xAA93, 0x7362, 0xAA94, + 0x7363, 0xAA95, 0x7364, 0xAA96, 0x7365, 0xAA97, 0x7366, 0xAA98, 0x7367, 0xAA99, 0x7368, 0xAA9A, 0x7369, 0xAA9B, 0x736A, 0xAA9C, + 0x736B, 0xAA9D, 0x736C, 0xE2B3, 0x736D, 0xCCA1, 0x736E, 0xAA9E, 0x736F, 0xE2B4, 0x7370, 0xAA9F, 0x7371, 0xAAA0, 0x7372, 0xAB40, + 0x7373, 0xAB41, 0x7374, 0xAB42, 0x7375, 0xAB43, 0x7376, 0xAB44, 0x7377, 0xAB45, 0x7378, 0xAB46, 0x7379, 0xAB47, 0x737A, 0xAB48, + 0x737B, 0xAB49, 0x737C, 0xAB4A, 0x737D, 0xAB4B, 0x737E, 0xE2B5, 0x737F, 0xAB4C, 0x7380, 0xAB4D, 0x7381, 0xAB4E, 0x7382, 0xAB4F, + 0x7383, 0xAB50, 0x7384, 0xD0FE, 0x7385, 0xAB51, 0x7386, 0xAB52, 0x7387, 0xC2CA, 0x7388, 0xAB53, 0x7389, 0xD3F1, 0x738A, 0xAB54, + 0x738B, 0xCDF5, 0x738C, 0xAB55, 0x738D, 0xAB56, 0x738E, 0xE7E0, 0x738F, 0xAB57, 0x7390, 0xAB58, 0x7391, 0xE7E1, 0x7392, 0xAB59, + 0x7393, 0xAB5A, 0x7394, 0xAB5B, 0x7395, 0xAB5C, 0x7396, 0xBEC1, 0x7397, 0xAB5D, 0x7398, 0xAB5E, 0x7399, 0xAB5F, 0x739A, 0xAB60, + 0x739B, 0xC2EA, 0x739C, 0xAB61, 0x739D, 0xAB62, 0x739E, 0xAB63, 0x739F, 0xE7E4, 0x73A0, 0xAB64, 0x73A1, 0xAB65, 0x73A2, 0xE7E3, + 0x73A3, 0xAB66, 0x73A4, 0xAB67, 0x73A5, 0xAB68, 0x73A6, 0xAB69, 0x73A7, 0xAB6A, 0x73A8, 0xAB6B, 0x73A9, 0xCDE6, 0x73AA, 0xAB6C, + 0x73AB, 0xC3B5, 0x73AC, 0xAB6D, 0x73AD, 0xAB6E, 0x73AE, 0xE7E2, 0x73AF, 0xBBB7, 0x73B0, 0xCFD6, 0x73B1, 0xAB6F, 0x73B2, 0xC1E1, + 0x73B3, 0xE7E9, 0x73B4, 0xAB70, 0x73B5, 0xAB71, 0x73B6, 0xAB72, 0x73B7, 0xE7E8, 0x73B8, 0xAB73, 0x73B9, 0xAB74, 0x73BA, 0xE7F4, + 0x73BB, 0xB2A3, 0x73BC, 0xAB75, 0x73BD, 0xAB76, 0x73BE, 0xAB77, 0x73BF, 0xAB78, 0x73C0, 0xE7EA, 0x73C1, 0xAB79, 0x73C2, 0xE7E6, + 0x73C3, 0xAB7A, 0x73C4, 0xAB7B, 0x73C5, 0xAB7C, 0x73C6, 0xAB7D, 0x73C7, 0xAB7E, 0x73C8, 0xE7EC, 0x73C9, 0xE7EB, 0x73CA, 0xC9BA, + 0x73CB, 0xAB80, 0x73CC, 0xAB81, 0x73CD, 0xD5E4, 0x73CE, 0xAB82, 0x73CF, 0xE7E5, 0x73D0, 0xB7A9, 0x73D1, 0xE7E7, 0x73D2, 0xAB83, + 0x73D3, 0xAB84, 0x73D4, 0xAB85, 0x73D5, 0xAB86, 0x73D6, 0xAB87, 0x73D7, 0xAB88, 0x73D8, 0xAB89, 0x73D9, 0xE7EE, 0x73DA, 0xAB8A, + 0x73DB, 0xAB8B, 0x73DC, 0xAB8C, 0x73DD, 0xAB8D, 0x73DE, 0xE7F3, 0x73DF, 0xAB8E, 0x73E0, 0xD6E9, 0x73E1, 0xAB8F, 0x73E2, 0xAB90, + 0x73E3, 0xAB91, 0x73E4, 0xAB92, 0x73E5, 0xE7ED, 0x73E6, 0xAB93, 0x73E7, 0xE7F2, 0x73E8, 0xAB94, 0x73E9, 0xE7F1, 0x73EA, 0xAB95, + 0x73EB, 0xAB96, 0x73EC, 0xAB97, 0x73ED, 0xB0E0, 0x73EE, 0xAB98, 0x73EF, 0xAB99, 0x73F0, 0xAB9A, 0x73F1, 0xAB9B, 0x73F2, 0xE7F5, + 0x73F3, 0xAB9C, 0x73F4, 0xAB9D, 0x73F5, 0xAB9E, 0x73F6, 0xAB9F, 0x73F7, 0xABA0, 0x73F8, 0xAC40, 0x73F9, 0xAC41, 0x73FA, 0xAC42, + 0x73FB, 0xAC43, 0x73FC, 0xAC44, 0x73FD, 0xAC45, 0x73FE, 0xAC46, 0x73FF, 0xAC47, 0x7400, 0xAC48, 0x7401, 0xAC49, 0x7402, 0xAC4A, + 0x7403, 0xC7F2, 0x7404, 0xAC4B, 0x7405, 0xC0C5, 0x7406, 0xC0ED, 0x7407, 0xAC4C, 0x7408, 0xAC4D, 0x7409, 0xC1F0, 0x740A, 0xE7F0, + 0x740B, 0xAC4E, 0x740C, 0xAC4F, 0x740D, 0xAC50, 0x740E, 0xAC51, 0x740F, 0xE7F6, 0x7410, 0xCBF6, 0x7411, 0xAC52, 0x7412, 0xAC53, + 0x7413, 0xAC54, 0x7414, 0xAC55, 0x7415, 0xAC56, 0x7416, 0xAC57, 0x7417, 0xAC58, 0x7418, 0xAC59, 0x7419, 0xAC5A, 0x741A, 0xE8A2, + 0x741B, 0xE8A1, 0x741C, 0xAC5B, 0x741D, 0xAC5C, 0x741E, 0xAC5D, 0x741F, 0xAC5E, 0x7420, 0xAC5F, 0x7421, 0xAC60, 0x7422, 0xD7C1, + 0x7423, 0xAC61, 0x7424, 0xAC62, 0x7425, 0xE7FA, 0x7426, 0xE7F9, 0x7427, 0xAC63, 0x7428, 0xE7FB, 0x7429, 0xAC64, 0x742A, 0xE7F7, + 0x742B, 0xAC65, 0x742C, 0xE7FE, 0x742D, 0xAC66, 0x742E, 0xE7FD, 0x742F, 0xAC67, 0x7430, 0xE7FC, 0x7431, 0xAC68, 0x7432, 0xAC69, + 0x7433, 0xC1D5, 0x7434, 0xC7D9, 0x7435, 0xC5FD, 0x7436, 0xC5C3, 0x7437, 0xAC6A, 0x7438, 0xAC6B, 0x7439, 0xAC6C, 0x743A, 0xAC6D, + 0x743B, 0xAC6E, 0x743C, 0xC7ED, 0x743D, 0xAC6F, 0x743E, 0xAC70, 0x743F, 0xAC71, 0x7440, 0xAC72, 0x7441, 0xE8A3, 0x7442, 0xAC73, + 0x7443, 0xAC74, 0x7444, 0xAC75, 0x7445, 0xAC76, 0x7446, 0xAC77, 0x7447, 0xAC78, 0x7448, 0xAC79, 0x7449, 0xAC7A, 0x744A, 0xAC7B, + 0x744B, 0xAC7C, 0x744C, 0xAC7D, 0x744D, 0xAC7E, 0x744E, 0xAC80, 0x744F, 0xAC81, 0x7450, 0xAC82, 0x7451, 0xAC83, 0x7452, 0xAC84, + 0x7453, 0xAC85, 0x7454, 0xAC86, 0x7455, 0xE8A6, 0x7456, 0xAC87, 0x7457, 0xE8A5, 0x7458, 0xAC88, 0x7459, 0xE8A7, 0x745A, 0xBAF7, + 0x745B, 0xE7F8, 0x745C, 0xE8A4, 0x745D, 0xAC89, 0x745E, 0xC8F0, 0x745F, 0xC9AA, 0x7460, 0xAC8A, 0x7461, 0xAC8B, 0x7462, 0xAC8C, + 0x7463, 0xAC8D, 0x7464, 0xAC8E, 0x7465, 0xAC8F, 0x7466, 0xAC90, 0x7467, 0xAC91, 0x7468, 0xAC92, 0x7469, 0xAC93, 0x746A, 0xAC94, + 0x746B, 0xAC95, 0x746C, 0xAC96, 0x746D, 0xE8A9, 0x746E, 0xAC97, 0x746F, 0xAC98, 0x7470, 0xB9E5, 0x7471, 0xAC99, 0x7472, 0xAC9A, + 0x7473, 0xAC9B, 0x7474, 0xAC9C, 0x7475, 0xAC9D, 0x7476, 0xD1FE, 0x7477, 0xE8A8, 0x7478, 0xAC9E, 0x7479, 0xAC9F, 0x747A, 0xACA0, + 0x747B, 0xAD40, 0x747C, 0xAD41, 0x747D, 0xAD42, 0x747E, 0xE8AA, 0x747F, 0xAD43, 0x7480, 0xE8AD, 0x7481, 0xE8AE, 0x7482, 0xAD44, + 0x7483, 0xC1A7, 0x7484, 0xAD45, 0x7485, 0xAD46, 0x7486, 0xAD47, 0x7487, 0xE8AF, 0x7488, 0xAD48, 0x7489, 0xAD49, 0x748A, 0xAD4A, + 0x748B, 0xE8B0, 0x748C, 0xAD4B, 0x748D, 0xAD4C, 0x748E, 0xE8AC, 0x748F, 0xAD4D, 0x7490, 0xE8B4, 0x7491, 0xAD4E, 0x7492, 0xAD4F, + 0x7493, 0xAD50, 0x7494, 0xAD51, 0x7495, 0xAD52, 0x7496, 0xAD53, 0x7497, 0xAD54, 0x7498, 0xAD55, 0x7499, 0xAD56, 0x749A, 0xAD57, + 0x749B, 0xAD58, 0x749C, 0xE8AB, 0x749D, 0xAD59, 0x749E, 0xE8B1, 0x749F, 0xAD5A, 0x74A0, 0xAD5B, 0x74A1, 0xAD5C, 0x74A2, 0xAD5D, + 0x74A3, 0xAD5E, 0x74A4, 0xAD5F, 0x74A5, 0xAD60, 0x74A6, 0xAD61, 0x74A7, 0xE8B5, 0x74A8, 0xE8B2, 0x74A9, 0xE8B3, 0x74AA, 0xAD62, + 0x74AB, 0xAD63, 0x74AC, 0xAD64, 0x74AD, 0xAD65, 0x74AE, 0xAD66, 0x74AF, 0xAD67, 0x74B0, 0xAD68, 0x74B1, 0xAD69, 0x74B2, 0xAD6A, + 0x74B3, 0xAD6B, 0x74B4, 0xAD6C, 0x74B5, 0xAD6D, 0x74B6, 0xAD6E, 0x74B7, 0xAD6F, 0x74B8, 0xAD70, 0x74B9, 0xAD71, 0x74BA, 0xE8B7, + 0x74BB, 0xAD72, 0x74BC, 0xAD73, 0x74BD, 0xAD74, 0x74BE, 0xAD75, 0x74BF, 0xAD76, 0x74C0, 0xAD77, 0x74C1, 0xAD78, 0x74C2, 0xAD79, + 0x74C3, 0xAD7A, 0x74C4, 0xAD7B, 0x74C5, 0xAD7C, 0x74C6, 0xAD7D, 0x74C7, 0xAD7E, 0x74C8, 0xAD80, 0x74C9, 0xAD81, 0x74CA, 0xAD82, + 0x74CB, 0xAD83, 0x74CC, 0xAD84, 0x74CD, 0xAD85, 0x74CE, 0xAD86, 0x74CF, 0xAD87, 0x74D0, 0xAD88, 0x74D1, 0xAD89, 0x74D2, 0xE8B6, + 0x74D3, 0xAD8A, 0x74D4, 0xAD8B, 0x74D5, 0xAD8C, 0x74D6, 0xAD8D, 0x74D7, 0xAD8E, 0x74D8, 0xAD8F, 0x74D9, 0xAD90, 0x74DA, 0xAD91, + 0x74DB, 0xAD92, 0x74DC, 0xB9CF, 0x74DD, 0xAD93, 0x74DE, 0xF0AC, 0x74DF, 0xAD94, 0x74E0, 0xF0AD, 0x74E1, 0xAD95, 0x74E2, 0xC6B0, + 0x74E3, 0xB0EA, 0x74E4, 0xC8BF, 0x74E5, 0xAD96, 0x74E6, 0xCDDF, 0x74E7, 0xAD97, 0x74E8, 0xAD98, 0x74E9, 0xAD99, 0x74EA, 0xAD9A, + 0x74EB, 0xAD9B, 0x74EC, 0xAD9C, 0x74ED, 0xAD9D, 0x74EE, 0xCECD, 0x74EF, 0xEAB1, 0x74F0, 0xAD9E, 0x74F1, 0xAD9F, 0x74F2, 0xADA0, + 0x74F3, 0xAE40, 0x74F4, 0xEAB2, 0x74F5, 0xAE41, 0x74F6, 0xC6BF, 0x74F7, 0xB4C9, 0x74F8, 0xAE42, 0x74F9, 0xAE43, 0x74FA, 0xAE44, + 0x74FB, 0xAE45, 0x74FC, 0xAE46, 0x74FD, 0xAE47, 0x74FE, 0xAE48, 0x74FF, 0xEAB3, 0x7500, 0xAE49, 0x7501, 0xAE4A, 0x7502, 0xAE4B, + 0x7503, 0xAE4C, 0x7504, 0xD5E7, 0x7505, 0xAE4D, 0x7506, 0xAE4E, 0x7507, 0xAE4F, 0x7508, 0xAE50, 0x7509, 0xAE51, 0x750A, 0xAE52, + 0x750B, 0xAE53, 0x750C, 0xAE54, 0x750D, 0xDDF9, 0x750E, 0xAE55, 0x750F, 0xEAB4, 0x7510, 0xAE56, 0x7511, 0xEAB5, 0x7512, 0xAE57, + 0x7513, 0xEAB6, 0x7514, 0xAE58, 0x7515, 0xAE59, 0x7516, 0xAE5A, 0x7517, 0xAE5B, 0x7518, 0xB8CA, 0x7519, 0xDFB0, 0x751A, 0xC9F5, + 0x751B, 0xAE5C, 0x751C, 0xCCF0, 0x751D, 0xAE5D, 0x751E, 0xAE5E, 0x751F, 0xC9FA, 0x7520, 0xAE5F, 0x7521, 0xAE60, 0x7522, 0xAE61, + 0x7523, 0xAE62, 0x7524, 0xAE63, 0x7525, 0xC9FB, 0x7526, 0xAE64, 0x7527, 0xAE65, 0x7528, 0xD3C3, 0x7529, 0xCBA6, 0x752A, 0xAE66, + 0x752B, 0xB8A6, 0x752C, 0xF0AE, 0x752D, 0xB1C2, 0x752E, 0xAE67, 0x752F, 0xE5B8, 0x7530, 0xCCEF, 0x7531, 0xD3C9, 0x7532, 0xBCD7, + 0x7533, 0xC9EA, 0x7534, 0xAE68, 0x7535, 0xB5E7, 0x7536, 0xAE69, 0x7537, 0xC4D0, 0x7538, 0xB5E9, 0x7539, 0xAE6A, 0x753A, 0xEEAE, + 0x753B, 0xBBAD, 0x753C, 0xAE6B, 0x753D, 0xAE6C, 0x753E, 0xE7DE, 0x753F, 0xAE6D, 0x7540, 0xEEAF, 0x7541, 0xAE6E, 0x7542, 0xAE6F, + 0x7543, 0xAE70, 0x7544, 0xAE71, 0x7545, 0xB3A9, 0x7546, 0xAE72, 0x7547, 0xAE73, 0x7548, 0xEEB2, 0x7549, 0xAE74, 0x754A, 0xAE75, + 0x754B, 0xEEB1, 0x754C, 0xBDE7, 0x754D, 0xAE76, 0x754E, 0xEEB0, 0x754F, 0xCEB7, 0x7550, 0xAE77, 0x7551, 0xAE78, 0x7552, 0xAE79, + 0x7553, 0xAE7A, 0x7554, 0xC5CF, 0x7555, 0xAE7B, 0x7556, 0xAE7C, 0x7557, 0xAE7D, 0x7558, 0xAE7E, 0x7559, 0xC1F4, 0x755A, 0xDBCE, + 0x755B, 0xEEB3, 0x755C, 0xD0F3, 0x755D, 0xAE80, 0x755E, 0xAE81, 0x755F, 0xAE82, 0x7560, 0xAE83, 0x7561, 0xAE84, 0x7562, 0xAE85, + 0x7563, 0xAE86, 0x7564, 0xAE87, 0x7565, 0xC2D4, 0x7566, 0xC6E8, 0x7567, 0xAE88, 0x7568, 0xAE89, 0x7569, 0xAE8A, 0x756A, 0xB7AC, + 0x756B, 0xAE8B, 0x756C, 0xAE8C, 0x756D, 0xAE8D, 0x756E, 0xAE8E, 0x756F, 0xAE8F, 0x7570, 0xAE90, 0x7571, 0xAE91, 0x7572, 0xEEB4, + 0x7573, 0xAE92, 0x7574, 0xB3EB, 0x7575, 0xAE93, 0x7576, 0xAE94, 0x7577, 0xAE95, 0x7578, 0xBBFB, 0x7579, 0xEEB5, 0x757A, 0xAE96, + 0x757B, 0xAE97, 0x757C, 0xAE98, 0x757D, 0xAE99, 0x757E, 0xAE9A, 0x757F, 0xE7DC, 0x7580, 0xAE9B, 0x7581, 0xAE9C, 0x7582, 0xAE9D, + 0x7583, 0xEEB6, 0x7584, 0xAE9E, 0x7585, 0xAE9F, 0x7586, 0xBDAE, 0x7587, 0xAEA0, 0x7588, 0xAF40, 0x7589, 0xAF41, 0x758A, 0xAF42, + 0x758B, 0xF1E2, 0x758C, 0xAF43, 0x758D, 0xAF44, 0x758E, 0xAF45, 0x758F, 0xCAE8, 0x7590, 0xAF46, 0x7591, 0xD2C9, 0x7592, 0xF0DA, + 0x7593, 0xAF47, 0x7594, 0xF0DB, 0x7595, 0xAF48, 0x7596, 0xF0DC, 0x7597, 0xC1C6, 0x7598, 0xAF49, 0x7599, 0xB8ED, 0x759A, 0xBECE, + 0x759B, 0xAF4A, 0x759C, 0xAF4B, 0x759D, 0xF0DE, 0x759E, 0xAF4C, 0x759F, 0xC5B1, 0x75A0, 0xF0DD, 0x75A1, 0xD1F1, 0x75A2, 0xAF4D, + 0x75A3, 0xF0E0, 0x75A4, 0xB0CC, 0x75A5, 0xBDEA, 0x75A6, 0xAF4E, 0x75A7, 0xAF4F, 0x75A8, 0xAF50, 0x75A9, 0xAF51, 0x75AA, 0xAF52, + 0x75AB, 0xD2DF, 0x75AC, 0xF0DF, 0x75AD, 0xAF53, 0x75AE, 0xB4AF, 0x75AF, 0xB7E8, 0x75B0, 0xF0E6, 0x75B1, 0xF0E5, 0x75B2, 0xC6A3, + 0x75B3, 0xF0E1, 0x75B4, 0xF0E2, 0x75B5, 0xB4C3, 0x75B6, 0xAF54, 0x75B7, 0xAF55, 0x75B8, 0xF0E3, 0x75B9, 0xD5EE, 0x75BA, 0xAF56, + 0x75BB, 0xAF57, 0x75BC, 0xCCDB, 0x75BD, 0xBED2, 0x75BE, 0xBCB2, 0x75BF, 0xAF58, 0x75C0, 0xAF59, 0x75C1, 0xAF5A, 0x75C2, 0xF0E8, + 0x75C3, 0xF0E7, 0x75C4, 0xF0E4, 0x75C5, 0xB2A1, 0x75C6, 0xAF5B, 0x75C7, 0xD6A2, 0x75C8, 0xD3B8, 0x75C9, 0xBEB7, 0x75CA, 0xC8AC, + 0x75CB, 0xAF5C, 0x75CC, 0xAF5D, 0x75CD, 0xF0EA, 0x75CE, 0xAF5E, 0x75CF, 0xAF5F, 0x75D0, 0xAF60, 0x75D1, 0xAF61, 0x75D2, 0xD1F7, + 0x75D3, 0xAF62, 0x75D4, 0xD6CC, 0x75D5, 0xBADB, 0x75D6, 0xF0E9, 0x75D7, 0xAF63, 0x75D8, 0xB6BB, 0x75D9, 0xAF64, 0x75DA, 0xAF65, + 0x75DB, 0xCDB4, 0x75DC, 0xAF66, 0x75DD, 0xAF67, 0x75DE, 0xC6A6, 0x75DF, 0xAF68, 0x75E0, 0xAF69, 0x75E1, 0xAF6A, 0x75E2, 0xC1A1, + 0x75E3, 0xF0EB, 0x75E4, 0xF0EE, 0x75E5, 0xAF6B, 0x75E6, 0xF0ED, 0x75E7, 0xF0F0, 0x75E8, 0xF0EC, 0x75E9, 0xAF6C, 0x75EA, 0xBBBE, + 0x75EB, 0xF0EF, 0x75EC, 0xAF6D, 0x75ED, 0xAF6E, 0x75EE, 0xAF6F, 0x75EF, 0xAF70, 0x75F0, 0xCCB5, 0x75F1, 0xF0F2, 0x75F2, 0xAF71, + 0x75F3, 0xAF72, 0x75F4, 0xB3D5, 0x75F5, 0xAF73, 0x75F6, 0xAF74, 0x75F7, 0xAF75, 0x75F8, 0xAF76, 0x75F9, 0xB1D4, 0x75FA, 0xAF77, + 0x75FB, 0xAF78, 0x75FC, 0xF0F3, 0x75FD, 0xAF79, 0x75FE, 0xAF7A, 0x75FF, 0xF0F4, 0x7600, 0xF0F6, 0x7601, 0xB4E1, 0x7602, 0xAF7B, + 0x7603, 0xF0F1, 0x7604, 0xAF7C, 0x7605, 0xF0F7, 0x7606, 0xAF7D, 0x7607, 0xAF7E, 0x7608, 0xAF80, 0x7609, 0xAF81, 0x760A, 0xF0FA, + 0x760B, 0xAF82, 0x760C, 0xF0F8, 0x760D, 0xAF83, 0x760E, 0xAF84, 0x760F, 0xAF85, 0x7610, 0xF0F5, 0x7611, 0xAF86, 0x7612, 0xAF87, + 0x7613, 0xAF88, 0x7614, 0xAF89, 0x7615, 0xF0FD, 0x7616, 0xAF8A, 0x7617, 0xF0F9, 0x7618, 0xF0FC, 0x7619, 0xF0FE, 0x761A, 0xAF8B, + 0x761B, 0xF1A1, 0x761C, 0xAF8C, 0x761D, 0xAF8D, 0x761E, 0xAF8E, 0x761F, 0xCEC1, 0x7620, 0xF1A4, 0x7621, 0xAF8F, 0x7622, 0xF1A3, + 0x7623, 0xAF90, 0x7624, 0xC1F6, 0x7625, 0xF0FB, 0x7626, 0xCADD, 0x7627, 0xAF91, 0x7628, 0xAF92, 0x7629, 0xB4F1, 0x762A, 0xB1F1, + 0x762B, 0xCCB1, 0x762C, 0xAF93, 0x762D, 0xF1A6, 0x762E, 0xAF94, 0x762F, 0xAF95, 0x7630, 0xF1A7, 0x7631, 0xAF96, 0x7632, 0xAF97, + 0x7633, 0xF1AC, 0x7634, 0xD5CE, 0x7635, 0xF1A9, 0x7636, 0xAF98, 0x7637, 0xAF99, 0x7638, 0xC8B3, 0x7639, 0xAF9A, 0x763A, 0xAF9B, + 0x763B, 0xAF9C, 0x763C, 0xF1A2, 0x763D, 0xAF9D, 0x763E, 0xF1AB, 0x763F, 0xF1A8, 0x7640, 0xF1A5, 0x7641, 0xAF9E, 0x7642, 0xAF9F, + 0x7643, 0xF1AA, 0x7644, 0xAFA0, 0x7645, 0xB040, 0x7646, 0xB041, 0x7647, 0xB042, 0x7648, 0xB043, 0x7649, 0xB044, 0x764A, 0xB045, + 0x764B, 0xB046, 0x764C, 0xB0A9, 0x764D, 0xF1AD, 0x764E, 0xB047, 0x764F, 0xB048, 0x7650, 0xB049, 0x7651, 0xB04A, 0x7652, 0xB04B, + 0x7653, 0xB04C, 0x7654, 0xF1AF, 0x7655, 0xB04D, 0x7656, 0xF1B1, 0x7657, 0xB04E, 0x7658, 0xB04F, 0x7659, 0xB050, 0x765A, 0xB051, + 0x765B, 0xB052, 0x765C, 0xF1B0, 0x765D, 0xB053, 0x765E, 0xF1AE, 0x765F, 0xB054, 0x7660, 0xB055, 0x7661, 0xB056, 0x7662, 0xB057, + 0x7663, 0xD1A2, 0x7664, 0xB058, 0x7665, 0xB059, 0x7666, 0xB05A, 0x7667, 0xB05B, 0x7668, 0xB05C, 0x7669, 0xB05D, 0x766A, 0xB05E, + 0x766B, 0xF1B2, 0x766C, 0xB05F, 0x766D, 0xB060, 0x766E, 0xB061, 0x766F, 0xF1B3, 0x7670, 0xB062, 0x7671, 0xB063, 0x7672, 0xB064, + 0x7673, 0xB065, 0x7674, 0xB066, 0x7675, 0xB067, 0x7676, 0xB068, 0x7677, 0xB069, 0x7678, 0xB9EF, 0x7679, 0xB06A, 0x767A, 0xB06B, + 0x767B, 0xB5C7, 0x767C, 0xB06C, 0x767D, 0xB0D7, 0x767E, 0xB0D9, 0x767F, 0xB06D, 0x7680, 0xB06E, 0x7681, 0xB06F, 0x7682, 0xD4ED, + 0x7683, 0xB070, 0x7684, 0xB5C4, 0x7685, 0xB071, 0x7686, 0xBDD4, 0x7687, 0xBBCA, 0x7688, 0xF0A7, 0x7689, 0xB072, 0x768A, 0xB073, + 0x768B, 0xB8DE, 0x768C, 0xB074, 0x768D, 0xB075, 0x768E, 0xF0A8, 0x768F, 0xB076, 0x7690, 0xB077, 0x7691, 0xB0A8, 0x7692, 0xB078, + 0x7693, 0xF0A9, 0x7694, 0xB079, 0x7695, 0xB07A, 0x7696, 0xCDEE, 0x7697, 0xB07B, 0x7698, 0xB07C, 0x7699, 0xF0AA, 0x769A, 0xB07D, + 0x769B, 0xB07E, 0x769C, 0xB080, 0x769D, 0xB081, 0x769E, 0xB082, 0x769F, 0xB083, 0x76A0, 0xB084, 0x76A1, 0xB085, 0x76A2, 0xB086, + 0x76A3, 0xB087, 0x76A4, 0xF0AB, 0x76A5, 0xB088, 0x76A6, 0xB089, 0x76A7, 0xB08A, 0x76A8, 0xB08B, 0x76A9, 0xB08C, 0x76AA, 0xB08D, + 0x76AB, 0xB08E, 0x76AC, 0xB08F, 0x76AD, 0xB090, 0x76AE, 0xC6A4, 0x76AF, 0xB091, 0x76B0, 0xB092, 0x76B1, 0xD6E5, 0x76B2, 0xF1E4, + 0x76B3, 0xB093, 0x76B4, 0xF1E5, 0x76B5, 0xB094, 0x76B6, 0xB095, 0x76B7, 0xB096, 0x76B8, 0xB097, 0x76B9, 0xB098, 0x76BA, 0xB099, + 0x76BB, 0xB09A, 0x76BC, 0xB09B, 0x76BD, 0xB09C, 0x76BE, 0xB09D, 0x76BF, 0xC3F3, 0x76C0, 0xB09E, 0x76C1, 0xB09F, 0x76C2, 0xD3DB, + 0x76C3, 0xB0A0, 0x76C4, 0xB140, 0x76C5, 0xD6D1, 0x76C6, 0xC5E8, 0x76C7, 0xB141, 0x76C8, 0xD3AF, 0x76C9, 0xB142, 0x76CA, 0xD2E6, + 0x76CB, 0xB143, 0x76CC, 0xB144, 0x76CD, 0xEEC1, 0x76CE, 0xB0BB, 0x76CF, 0xD5B5, 0x76D0, 0xD1CE, 0x76D1, 0xBCE0, 0x76D2, 0xBAD0, + 0x76D3, 0xB145, 0x76D4, 0xBFF8, 0x76D5, 0xB146, 0x76D6, 0xB8C7, 0x76D7, 0xB5C1, 0x76D8, 0xC5CC, 0x76D9, 0xB147, 0x76DA, 0xB148, + 0x76DB, 0xCAA2, 0x76DC, 0xB149, 0x76DD, 0xB14A, 0x76DE, 0xB14B, 0x76DF, 0xC3CB, 0x76E0, 0xB14C, 0x76E1, 0xB14D, 0x76E2, 0xB14E, + 0x76E3, 0xB14F, 0x76E4, 0xB150, 0x76E5, 0xEEC2, 0x76E6, 0xB151, 0x76E7, 0xB152, 0x76E8, 0xB153, 0x76E9, 0xB154, 0x76EA, 0xB155, + 0x76EB, 0xB156, 0x76EC, 0xB157, 0x76ED, 0xB158, 0x76EE, 0xC4BF, 0x76EF, 0xB6A2, 0x76F0, 0xB159, 0x76F1, 0xEDEC, 0x76F2, 0xC3A4, + 0x76F3, 0xB15A, 0x76F4, 0xD6B1, 0x76F5, 0xB15B, 0x76F6, 0xB15C, 0x76F7, 0xB15D, 0x76F8, 0xCFE0, 0x76F9, 0xEDEF, 0x76FA, 0xB15E, + 0x76FB, 0xB15F, 0x76FC, 0xC5CE, 0x76FD, 0xB160, 0x76FE, 0xB6DC, 0x76FF, 0xB161, 0x7700, 0xB162, 0x7701, 0xCAA1, 0x7702, 0xB163, + 0x7703, 0xB164, 0x7704, 0xEDED, 0x7705, 0xB165, 0x7706, 0xB166, 0x7707, 0xEDF0, 0x7708, 0xEDF1, 0x7709, 0xC3BC, 0x770A, 0xB167, + 0x770B, 0xBFB4, 0x770C, 0xB168, 0x770D, 0xEDEE, 0x770E, 0xB169, 0x770F, 0xB16A, 0x7710, 0xB16B, 0x7711, 0xB16C, 0x7712, 0xB16D, + 0x7713, 0xB16E, 0x7714, 0xB16F, 0x7715, 0xB170, 0x7716, 0xB171, 0x7717, 0xB172, 0x7718, 0xB173, 0x7719, 0xEDF4, 0x771A, 0xEDF2, + 0x771B, 0xB174, 0x771C, 0xB175, 0x771D, 0xB176, 0x771E, 0xB177, 0x771F, 0xD5E6, 0x7720, 0xC3DF, 0x7721, 0xB178, 0x7722, 0xEDF3, + 0x7723, 0xB179, 0x7724, 0xB17A, 0x7725, 0xB17B, 0x7726, 0xEDF6, 0x7727, 0xB17C, 0x7728, 0xD5A3, 0x7729, 0xD1A3, 0x772A, 0xB17D, + 0x772B, 0xB17E, 0x772C, 0xB180, 0x772D, 0xEDF5, 0x772E, 0xB181, 0x772F, 0xC3D0, 0x7730, 0xB182, 0x7731, 0xB183, 0x7732, 0xB184, + 0x7733, 0xB185, 0x7734, 0xB186, 0x7735, 0xEDF7, 0x7736, 0xBFF4, 0x7737, 0xBEEC, 0x7738, 0xEDF8, 0x7739, 0xB187, 0x773A, 0xCCF7, + 0x773B, 0xB188, 0x773C, 0xD1DB, 0x773D, 0xB189, 0x773E, 0xB18A, 0x773F, 0xB18B, 0x7740, 0xD7C5, 0x7741, 0xD5F6, 0x7742, 0xB18C, + 0x7743, 0xEDFC, 0x7744, 0xB18D, 0x7745, 0xB18E, 0x7746, 0xB18F, 0x7747, 0xEDFB, 0x7748, 0xB190, 0x7749, 0xB191, 0x774A, 0xB192, + 0x774B, 0xB193, 0x774C, 0xB194, 0x774D, 0xB195, 0x774E, 0xB196, 0x774F, 0xB197, 0x7750, 0xEDF9, 0x7751, 0xEDFA, 0x7752, 0xB198, + 0x7753, 0xB199, 0x7754, 0xB19A, 0x7755, 0xB19B, 0x7756, 0xB19C, 0x7757, 0xB19D, 0x7758, 0xB19E, 0x7759, 0xB19F, 0x775A, 0xEDFD, + 0x775B, 0xBEA6, 0x775C, 0xB1A0, 0x775D, 0xB240, 0x775E, 0xB241, 0x775F, 0xB242, 0x7760, 0xB243, 0x7761, 0xCBAF, 0x7762, 0xEEA1, + 0x7763, 0xB6BD, 0x7764, 0xB244, 0x7765, 0xEEA2, 0x7766, 0xC4C0, 0x7767, 0xB245, 0x7768, 0xEDFE, 0x7769, 0xB246, 0x776A, 0xB247, + 0x776B, 0xBDDE, 0x776C, 0xB2C7, 0x776D, 0xB248, 0x776E, 0xB249, 0x776F, 0xB24A, 0x7770, 0xB24B, 0x7771, 0xB24C, 0x7772, 0xB24D, + 0x7773, 0xB24E, 0x7774, 0xB24F, 0x7775, 0xB250, 0x7776, 0xB251, 0x7777, 0xB252, 0x7778, 0xB253, 0x7779, 0xB6C3, 0x777A, 0xB254, + 0x777B, 0xB255, 0x777C, 0xB256, 0x777D, 0xEEA5, 0x777E, 0xD8BA, 0x777F, 0xEEA3, 0x7780, 0xEEA6, 0x7781, 0xB257, 0x7782, 0xB258, + 0x7783, 0xB259, 0x7784, 0xC3E9, 0x7785, 0xB3F2, 0x7786, 0xB25A, 0x7787, 0xB25B, 0x7788, 0xB25C, 0x7789, 0xB25D, 0x778A, 0xB25E, + 0x778B, 0xB25F, 0x778C, 0xEEA7, 0x778D, 0xEEA4, 0x778E, 0xCFB9, 0x778F, 0xB260, 0x7790, 0xB261, 0x7791, 0xEEA8, 0x7792, 0xC2F7, + 0x7793, 0xB262, 0x7794, 0xB263, 0x7795, 0xB264, 0x7796, 0xB265, 0x7797, 0xB266, 0x7798, 0xB267, 0x7799, 0xB268, 0x779A, 0xB269, + 0x779B, 0xB26A, 0x779C, 0xB26B, 0x779D, 0xB26C, 0x779E, 0xB26D, 0x779F, 0xEEA9, 0x77A0, 0xEEAA, 0x77A1, 0xB26E, 0x77A2, 0xDEAB, + 0x77A3, 0xB26F, 0x77A4, 0xB270, 0x77A5, 0xC6B3, 0x77A6, 0xB271, 0x77A7, 0xC7C6, 0x77A8, 0xB272, 0x77A9, 0xD6F5, 0x77AA, 0xB5C9, + 0x77AB, 0xB273, 0x77AC, 0xCBB2, 0x77AD, 0xB274, 0x77AE, 0xB275, 0x77AF, 0xB276, 0x77B0, 0xEEAB, 0x77B1, 0xB277, 0x77B2, 0xB278, + 0x77B3, 0xCDAB, 0x77B4, 0xB279, 0x77B5, 0xEEAC, 0x77B6, 0xB27A, 0x77B7, 0xB27B, 0x77B8, 0xB27C, 0x77B9, 0xB27D, 0x77BA, 0xB27E, + 0x77BB, 0xD5B0, 0x77BC, 0xB280, 0x77BD, 0xEEAD, 0x77BE, 0xB281, 0x77BF, 0xF6C4, 0x77C0, 0xB282, 0x77C1, 0xB283, 0x77C2, 0xB284, + 0x77C3, 0xB285, 0x77C4, 0xB286, 0x77C5, 0xB287, 0x77C6, 0xB288, 0x77C7, 0xB289, 0x77C8, 0xB28A, 0x77C9, 0xB28B, 0x77CA, 0xB28C, + 0x77CB, 0xB28D, 0x77CC, 0xB28E, 0x77CD, 0xDBC7, 0x77CE, 0xB28F, 0x77CF, 0xB290, 0x77D0, 0xB291, 0x77D1, 0xB292, 0x77D2, 0xB293, + 0x77D3, 0xB294, 0x77D4, 0xB295, 0x77D5, 0xB296, 0x77D6, 0xB297, 0x77D7, 0xB4A3, 0x77D8, 0xB298, 0x77D9, 0xB299, 0x77DA, 0xB29A, + 0x77DB, 0xC3AC, 0x77DC, 0xF1E6, 0x77DD, 0xB29B, 0x77DE, 0xB29C, 0x77DF, 0xB29D, 0x77E0, 0xB29E, 0x77E1, 0xB29F, 0x77E2, 0xCAB8, + 0x77E3, 0xD2D3, 0x77E4, 0xB2A0, 0x77E5, 0xD6AA, 0x77E6, 0xB340, 0x77E7, 0xEFF2, 0x77E8, 0xB341, 0x77E9, 0xBED8, 0x77EA, 0xB342, + 0x77EB, 0xBDC3, 0x77EC, 0xEFF3, 0x77ED, 0xB6CC, 0x77EE, 0xB0AB, 0x77EF, 0xB343, 0x77F0, 0xB344, 0x77F1, 0xB345, 0x77F2, 0xB346, + 0x77F3, 0xCAAF, 0x77F4, 0xB347, 0x77F5, 0xB348, 0x77F6, 0xEDB6, 0x77F7, 0xB349, 0x77F8, 0xEDB7, 0x77F9, 0xB34A, 0x77FA, 0xB34B, + 0x77FB, 0xB34C, 0x77FC, 0xB34D, 0x77FD, 0xCEF9, 0x77FE, 0xB7AF, 0x77FF, 0xBFF3, 0x7800, 0xEDB8, 0x7801, 0xC2EB, 0x7802, 0xC9B0, + 0x7803, 0xB34E, 0x7804, 0xB34F, 0x7805, 0xB350, 0x7806, 0xB351, 0x7807, 0xB352, 0x7808, 0xB353, 0x7809, 0xEDB9, 0x780A, 0xB354, + 0x780B, 0xB355, 0x780C, 0xC6F6, 0x780D, 0xBFB3, 0x780E, 0xB356, 0x780F, 0xB357, 0x7810, 0xB358, 0x7811, 0xEDBC, 0x7812, 0xC5F8, + 0x7813, 0xB359, 0x7814, 0xD1D0, 0x7815, 0xB35A, 0x7816, 0xD7A9, 0x7817, 0xEDBA, 0x7818, 0xEDBB, 0x7819, 0xB35B, 0x781A, 0xD1E2, + 0x781B, 0xB35C, 0x781C, 0xEDBF, 0x781D, 0xEDC0, 0x781E, 0xB35D, 0x781F, 0xEDC4, 0x7820, 0xB35E, 0x7821, 0xB35F, 0x7822, 0xB360, + 0x7823, 0xEDC8, 0x7824, 0xB361, 0x7825, 0xEDC6, 0x7826, 0xEDCE, 0x7827, 0xD5E8, 0x7828, 0xB362, 0x7829, 0xEDC9, 0x782A, 0xB363, + 0x782B, 0xB364, 0x782C, 0xEDC7, 0x782D, 0xEDBE, 0x782E, 0xB365, 0x782F, 0xB366, 0x7830, 0xC5E9, 0x7831, 0xB367, 0x7832, 0xB368, + 0x7833, 0xB369, 0x7834, 0xC6C6, 0x7835, 0xB36A, 0x7836, 0xB36B, 0x7837, 0xC9E9, 0x7838, 0xD4D2, 0x7839, 0xEDC1, 0x783A, 0xEDC2, + 0x783B, 0xEDC3, 0x783C, 0xEDC5, 0x783D, 0xB36C, 0x783E, 0xC0F9, 0x783F, 0xB36D, 0x7840, 0xB4A1, 0x7841, 0xB36E, 0x7842, 0xB36F, + 0x7843, 0xB370, 0x7844, 0xB371, 0x7845, 0xB9E8, 0x7846, 0xB372, 0x7847, 0xEDD0, 0x7848, 0xB373, 0x7849, 0xB374, 0x784A, 0xB375, + 0x784B, 0xB376, 0x784C, 0xEDD1, 0x784D, 0xB377, 0x784E, 0xEDCA, 0x784F, 0xB378, 0x7850, 0xEDCF, 0x7851, 0xB379, 0x7852, 0xCEF8, + 0x7853, 0xB37A, 0x7854, 0xB37B, 0x7855, 0xCBB6, 0x7856, 0xEDCC, 0x7857, 0xEDCD, 0x7858, 0xB37C, 0x7859, 0xB37D, 0x785A, 0xB37E, + 0x785B, 0xB380, 0x785C, 0xB381, 0x785D, 0xCFF5, 0x785E, 0xB382, 0x785F, 0xB383, 0x7860, 0xB384, 0x7861, 0xB385, 0x7862, 0xB386, + 0x7863, 0xB387, 0x7864, 0xB388, 0x7865, 0xB389, 0x7866, 0xB38A, 0x7867, 0xB38B, 0x7868, 0xB38C, 0x7869, 0xB38D, 0x786A, 0xEDD2, + 0x786B, 0xC1F2, 0x786C, 0xD3B2, 0x786D, 0xEDCB, 0x786E, 0xC8B7, 0x786F, 0xB38E, 0x7870, 0xB38F, 0x7871, 0xB390, 0x7872, 0xB391, + 0x7873, 0xB392, 0x7874, 0xB393, 0x7875, 0xB394, 0x7876, 0xB395, 0x7877, 0xBCEF, 0x7878, 0xB396, 0x7879, 0xB397, 0x787A, 0xB398, + 0x787B, 0xB399, 0x787C, 0xC5F0, 0x787D, 0xB39A, 0x787E, 0xB39B, 0x787F, 0xB39C, 0x7880, 0xB39D, 0x7881, 0xB39E, 0x7882, 0xB39F, + 0x7883, 0xB3A0, 0x7884, 0xB440, 0x7885, 0xB441, 0x7886, 0xB442, 0x7887, 0xEDD6, 0x7888, 0xB443, 0x7889, 0xB5EF, 0x788A, 0xB444, + 0x788B, 0xB445, 0x788C, 0xC2B5, 0x788D, 0xB0AD, 0x788E, 0xCBE9, 0x788F, 0xB446, 0x7890, 0xB447, 0x7891, 0xB1AE, 0x7892, 0xB448, + 0x7893, 0xEDD4, 0x7894, 0xB449, 0x7895, 0xB44A, 0x7896, 0xB44B, 0x7897, 0xCDEB, 0x7898, 0xB5E2, 0x7899, 0xB44C, 0x789A, 0xEDD5, + 0x789B, 0xEDD3, 0x789C, 0xEDD7, 0x789D, 0xB44D, 0x789E, 0xB44E, 0x789F, 0xB5FA, 0x78A0, 0xB44F, 0x78A1, 0xEDD8, 0x78A2, 0xB450, + 0x78A3, 0xEDD9, 0x78A4, 0xB451, 0x78A5, 0xEDDC, 0x78A6, 0xB452, 0x78A7, 0xB1CC, 0x78A8, 0xB453, 0x78A9, 0xB454, 0x78AA, 0xB455, + 0x78AB, 0xB456, 0x78AC, 0xB457, 0x78AD, 0xB458, 0x78AE, 0xB459, 0x78AF, 0xB45A, 0x78B0, 0xC5F6, 0x78B1, 0xBCEE, 0x78B2, 0xEDDA, + 0x78B3, 0xCCBC, 0x78B4, 0xB2EA, 0x78B5, 0xB45B, 0x78B6, 0xB45C, 0x78B7, 0xB45D, 0x78B8, 0xB45E, 0x78B9, 0xEDDB, 0x78BA, 0xB45F, + 0x78BB, 0xB460, 0x78BC, 0xB461, 0x78BD, 0xB462, 0x78BE, 0xC4EB, 0x78BF, 0xB463, 0x78C0, 0xB464, 0x78C1, 0xB4C5, 0x78C2, 0xB465, + 0x78C3, 0xB466, 0x78C4, 0xB467, 0x78C5, 0xB0F5, 0x78C6, 0xB468, 0x78C7, 0xB469, 0x78C8, 0xB46A, 0x78C9, 0xEDDF, 0x78CA, 0xC0DA, + 0x78CB, 0xB4E8, 0x78CC, 0xB46B, 0x78CD, 0xB46C, 0x78CE, 0xB46D, 0x78CF, 0xB46E, 0x78D0, 0xC5CD, 0x78D1, 0xB46F, 0x78D2, 0xB470, + 0x78D3, 0xB471, 0x78D4, 0xEDDD, 0x78D5, 0xBFC4, 0x78D6, 0xB472, 0x78D7, 0xB473, 0x78D8, 0xB474, 0x78D9, 0xEDDE, 0x78DA, 0xB475, + 0x78DB, 0xB476, 0x78DC, 0xB477, 0x78DD, 0xB478, 0x78DE, 0xB479, 0x78DF, 0xB47A, 0x78E0, 0xB47B, 0x78E1, 0xB47C, 0x78E2, 0xB47D, + 0x78E3, 0xB47E, 0x78E4, 0xB480, 0x78E5, 0xB481, 0x78E6, 0xB482, 0x78E7, 0xB483, 0x78E8, 0xC4A5, 0x78E9, 0xB484, 0x78EA, 0xB485, + 0x78EB, 0xB486, 0x78EC, 0xEDE0, 0x78ED, 0xB487, 0x78EE, 0xB488, 0x78EF, 0xB489, 0x78F0, 0xB48A, 0x78F1, 0xB48B, 0x78F2, 0xEDE1, + 0x78F3, 0xB48C, 0x78F4, 0xEDE3, 0x78F5, 0xB48D, 0x78F6, 0xB48E, 0x78F7, 0xC1D7, 0x78F8, 0xB48F, 0x78F9, 0xB490, 0x78FA, 0xBBC7, + 0x78FB, 0xB491, 0x78FC, 0xB492, 0x78FD, 0xB493, 0x78FE, 0xB494, 0x78FF, 0xB495, 0x7900, 0xB496, 0x7901, 0xBDB8, 0x7902, 0xB497, + 0x7903, 0xB498, 0x7904, 0xB499, 0x7905, 0xEDE2, 0x7906, 0xB49A, 0x7907, 0xB49B, 0x7908, 0xB49C, 0x7909, 0xB49D, 0x790A, 0xB49E, + 0x790B, 0xB49F, 0x790C, 0xB4A0, 0x790D, 0xB540, 0x790E, 0xB541, 0x790F, 0xB542, 0x7910, 0xB543, 0x7911, 0xB544, 0x7912, 0xB545, + 0x7913, 0xEDE4, 0x7914, 0xB546, 0x7915, 0xB547, 0x7916, 0xB548, 0x7917, 0xB549, 0x7918, 0xB54A, 0x7919, 0xB54B, 0x791A, 0xB54C, + 0x791B, 0xB54D, 0x791C, 0xB54E, 0x791D, 0xB54F, 0x791E, 0xEDE6, 0x791F, 0xB550, 0x7920, 0xB551, 0x7921, 0xB552, 0x7922, 0xB553, + 0x7923, 0xB554, 0x7924, 0xEDE5, 0x7925, 0xB555, 0x7926, 0xB556, 0x7927, 0xB557, 0x7928, 0xB558, 0x7929, 0xB559, 0x792A, 0xB55A, + 0x792B, 0xB55B, 0x792C, 0xB55C, 0x792D, 0xB55D, 0x792E, 0xB55E, 0x792F, 0xB55F, 0x7930, 0xB560, 0x7931, 0xB561, 0x7932, 0xB562, + 0x7933, 0xB563, 0x7934, 0xEDE7, 0x7935, 0xB564, 0x7936, 0xB565, 0x7937, 0xB566, 0x7938, 0xB567, 0x7939, 0xB568, 0x793A, 0xCABE, + 0x793B, 0xECEA, 0x793C, 0xC0F1, 0x793D, 0xB569, 0x793E, 0xC9E7, 0x793F, 0xB56A, 0x7940, 0xECEB, 0x7941, 0xC6EE, 0x7942, 0xB56B, + 0x7943, 0xB56C, 0x7944, 0xB56D, 0x7945, 0xB56E, 0x7946, 0xECEC, 0x7947, 0xB56F, 0x7948, 0xC6ED, 0x7949, 0xECED, 0x794A, 0xB570, + 0x794B, 0xB571, 0x794C, 0xB572, 0x794D, 0xB573, 0x794E, 0xB574, 0x794F, 0xB575, 0x7950, 0xB576, 0x7951, 0xB577, 0x7952, 0xB578, + 0x7953, 0xECF0, 0x7954, 0xB579, 0x7955, 0xB57A, 0x7956, 0xD7E6, 0x7957, 0xECF3, 0x7958, 0xB57B, 0x7959, 0xB57C, 0x795A, 0xECF1, + 0x795B, 0xECEE, 0x795C, 0xECEF, 0x795D, 0xD7A3, 0x795E, 0xC9F1, 0x795F, 0xCBEE, 0x7960, 0xECF4, 0x7961, 0xB57D, 0x7962, 0xECF2, + 0x7963, 0xB57E, 0x7964, 0xB580, 0x7965, 0xCFE9, 0x7966, 0xB581, 0x7967, 0xECF6, 0x7968, 0xC6B1, 0x7969, 0xB582, 0x796A, 0xB583, + 0x796B, 0xB584, 0x796C, 0xB585, 0x796D, 0xBCC0, 0x796E, 0xB586, 0x796F, 0xECF5, 0x7970, 0xB587, 0x7971, 0xB588, 0x7972, 0xB589, + 0x7973, 0xB58A, 0x7974, 0xB58B, 0x7975, 0xB58C, 0x7976, 0xB58D, 0x7977, 0xB5BB, 0x7978, 0xBBF6, 0x7979, 0xB58E, 0x797A, 0xECF7, + 0x797B, 0xB58F, 0x797C, 0xB590, 0x797D, 0xB591, 0x797E, 0xB592, 0x797F, 0xB593, 0x7980, 0xD9F7, 0x7981, 0xBDFB, 0x7982, 0xB594, + 0x7983, 0xB595, 0x7984, 0xC2BB, 0x7985, 0xECF8, 0x7986, 0xB596, 0x7987, 0xB597, 0x7988, 0xB598, 0x7989, 0xB599, 0x798A, 0xECF9, + 0x798B, 0xB59A, 0x798C, 0xB59B, 0x798D, 0xB59C, 0x798E, 0xB59D, 0x798F, 0xB8A3, 0x7990, 0xB59E, 0x7991, 0xB59F, 0x7992, 0xB5A0, + 0x7993, 0xB640, 0x7994, 0xB641, 0x7995, 0xB642, 0x7996, 0xB643, 0x7997, 0xB644, 0x7998, 0xB645, 0x7999, 0xB646, 0x799A, 0xECFA, + 0x799B, 0xB647, 0x799C, 0xB648, 0x799D, 0xB649, 0x799E, 0xB64A, 0x799F, 0xB64B, 0x79A0, 0xB64C, 0x79A1, 0xB64D, 0x79A2, 0xB64E, + 0x79A3, 0xB64F, 0x79A4, 0xB650, 0x79A5, 0xB651, 0x79A6, 0xB652, 0x79A7, 0xECFB, 0x79A8, 0xB653, 0x79A9, 0xB654, 0x79AA, 0xB655, + 0x79AB, 0xB656, 0x79AC, 0xB657, 0x79AD, 0xB658, 0x79AE, 0xB659, 0x79AF, 0xB65A, 0x79B0, 0xB65B, 0x79B1, 0xB65C, 0x79B2, 0xB65D, + 0x79B3, 0xECFC, 0x79B4, 0xB65E, 0x79B5, 0xB65F, 0x79B6, 0xB660, 0x79B7, 0xB661, 0x79B8, 0xB662, 0x79B9, 0xD3ED, 0x79BA, 0xD8AE, + 0x79BB, 0xC0EB, 0x79BC, 0xB663, 0x79BD, 0xC7DD, 0x79BE, 0xBACC, 0x79BF, 0xB664, 0x79C0, 0xD0E3, 0x79C1, 0xCBBD, 0x79C2, 0xB665, + 0x79C3, 0xCDBA, 0x79C4, 0xB666, 0x79C5, 0xB667, 0x79C6, 0xB8D1, 0x79C7, 0xB668, 0x79C8, 0xB669, 0x79C9, 0xB1FC, 0x79CA, 0xB66A, + 0x79CB, 0xC7EF, 0x79CC, 0xB66B, 0x79CD, 0xD6D6, 0x79CE, 0xB66C, 0x79CF, 0xB66D, 0x79D0, 0xB66E, 0x79D1, 0xBFC6, 0x79D2, 0xC3EB, + 0x79D3, 0xB66F, 0x79D4, 0xB670, 0x79D5, 0xEFF5, 0x79D6, 0xB671, 0x79D7, 0xB672, 0x79D8, 0xC3D8, 0x79D9, 0xB673, 0x79DA, 0xB674, + 0x79DB, 0xB675, 0x79DC, 0xB676, 0x79DD, 0xB677, 0x79DE, 0xB678, 0x79DF, 0xD7E2, 0x79E0, 0xB679, 0x79E1, 0xB67A, 0x79E2, 0xB67B, + 0x79E3, 0xEFF7, 0x79E4, 0xB3D3, 0x79E5, 0xB67C, 0x79E6, 0xC7D8, 0x79E7, 0xD1ED, 0x79E8, 0xB67D, 0x79E9, 0xD6C8, 0x79EA, 0xB67E, + 0x79EB, 0xEFF8, 0x79EC, 0xB680, 0x79ED, 0xEFF6, 0x79EE, 0xB681, 0x79EF, 0xBBFD, 0x79F0, 0xB3C6, 0x79F1, 0xB682, 0x79F2, 0xB683, + 0x79F3, 0xB684, 0x79F4, 0xB685, 0x79F5, 0xB686, 0x79F6, 0xB687, 0x79F7, 0xB688, 0x79F8, 0xBDD5, 0x79F9, 0xB689, 0x79FA, 0xB68A, + 0x79FB, 0xD2C6, 0x79FC, 0xB68B, 0x79FD, 0xBBE0, 0x79FE, 0xB68C, 0x79FF, 0xB68D, 0x7A00, 0xCFA1, 0x7A01, 0xB68E, 0x7A02, 0xEFFC, + 0x7A03, 0xEFFB, 0x7A04, 0xB68F, 0x7A05, 0xB690, 0x7A06, 0xEFF9, 0x7A07, 0xB691, 0x7A08, 0xB692, 0x7A09, 0xB693, 0x7A0A, 0xB694, + 0x7A0B, 0xB3CC, 0x7A0C, 0xB695, 0x7A0D, 0xC9D4, 0x7A0E, 0xCBB0, 0x7A0F, 0xB696, 0x7A10, 0xB697, 0x7A11, 0xB698, 0x7A12, 0xB699, + 0x7A13, 0xB69A, 0x7A14, 0xEFFE, 0x7A15, 0xB69B, 0x7A16, 0xB69C, 0x7A17, 0xB0DE, 0x7A18, 0xB69D, 0x7A19, 0xB69E, 0x7A1A, 0xD6C9, + 0x7A1B, 0xB69F, 0x7A1C, 0xB6A0, 0x7A1D, 0xB740, 0x7A1E, 0xEFFD, 0x7A1F, 0xB741, 0x7A20, 0xB3ED, 0x7A21, 0xB742, 0x7A22, 0xB743, + 0x7A23, 0xF6D5, 0x7A24, 0xB744, 0x7A25, 0xB745, 0x7A26, 0xB746, 0x7A27, 0xB747, 0x7A28, 0xB748, 0x7A29, 0xB749, 0x7A2A, 0xB74A, + 0x7A2B, 0xB74B, 0x7A2C, 0xB74C, 0x7A2D, 0xB74D, 0x7A2E, 0xB74E, 0x7A2F, 0xB74F, 0x7A30, 0xB750, 0x7A31, 0xB751, 0x7A32, 0xB752, + 0x7A33, 0xCEC8, 0x7A34, 0xB753, 0x7A35, 0xB754, 0x7A36, 0xB755, 0x7A37, 0xF0A2, 0x7A38, 0xB756, 0x7A39, 0xF0A1, 0x7A3A, 0xB757, + 0x7A3B, 0xB5BE, 0x7A3C, 0xBCDA, 0x7A3D, 0xBBFC, 0x7A3E, 0xB758, 0x7A3F, 0xB8E5, 0x7A40, 0xB759, 0x7A41, 0xB75A, 0x7A42, 0xB75B, + 0x7A43, 0xB75C, 0x7A44, 0xB75D, 0x7A45, 0xB75E, 0x7A46, 0xC4C2, 0x7A47, 0xB75F, 0x7A48, 0xB760, 0x7A49, 0xB761, 0x7A4A, 0xB762, + 0x7A4B, 0xB763, 0x7A4C, 0xB764, 0x7A4D, 0xB765, 0x7A4E, 0xB766, 0x7A4F, 0xB767, 0x7A50, 0xB768, 0x7A51, 0xF0A3, 0x7A52, 0xB769, + 0x7A53, 0xB76A, 0x7A54, 0xB76B, 0x7A55, 0xB76C, 0x7A56, 0xB76D, 0x7A57, 0xCBEB, 0x7A58, 0xB76E, 0x7A59, 0xB76F, 0x7A5A, 0xB770, + 0x7A5B, 0xB771, 0x7A5C, 0xB772, 0x7A5D, 0xB773, 0x7A5E, 0xB774, 0x7A5F, 0xB775, 0x7A60, 0xB776, 0x7A61, 0xB777, 0x7A62, 0xB778, + 0x7A63, 0xB779, 0x7A64, 0xB77A, 0x7A65, 0xB77B, 0x7A66, 0xB77C, 0x7A67, 0xB77D, 0x7A68, 0xB77E, 0x7A69, 0xB780, 0x7A6A, 0xB781, + 0x7A6B, 0xB782, 0x7A6C, 0xB783, 0x7A6D, 0xB784, 0x7A6E, 0xB785, 0x7A6F, 0xB786, 0x7A70, 0xF0A6, 0x7A71, 0xB787, 0x7A72, 0xB788, + 0x7A73, 0xB789, 0x7A74, 0xD1A8, 0x7A75, 0xB78A, 0x7A76, 0xBEBF, 0x7A77, 0xC7EE, 0x7A78, 0xF1B6, 0x7A79, 0xF1B7, 0x7A7A, 0xBFD5, + 0x7A7B, 0xB78B, 0x7A7C, 0xB78C, 0x7A7D, 0xB78D, 0x7A7E, 0xB78E, 0x7A7F, 0xB4A9, 0x7A80, 0xF1B8, 0x7A81, 0xCDBB, 0x7A82, 0xB78F, + 0x7A83, 0xC7D4, 0x7A84, 0xD5AD, 0x7A85, 0xB790, 0x7A86, 0xF1B9, 0x7A87, 0xB791, 0x7A88, 0xF1BA, 0x7A89, 0xB792, 0x7A8A, 0xB793, + 0x7A8B, 0xB794, 0x7A8C, 0xB795, 0x7A8D, 0xC7CF, 0x7A8E, 0xB796, 0x7A8F, 0xB797, 0x7A90, 0xB798, 0x7A91, 0xD2A4, 0x7A92, 0xD6CF, + 0x7A93, 0xB799, 0x7A94, 0xB79A, 0x7A95, 0xF1BB, 0x7A96, 0xBDD1, 0x7A97, 0xB4B0, 0x7A98, 0xBEBD, 0x7A99, 0xB79B, 0x7A9A, 0xB79C, + 0x7A9B, 0xB79D, 0x7A9C, 0xB4DC, 0x7A9D, 0xCED1, 0x7A9E, 0xB79E, 0x7A9F, 0xBFDF, 0x7AA0, 0xF1BD, 0x7AA1, 0xB79F, 0x7AA2, 0xB7A0, + 0x7AA3, 0xB840, 0x7AA4, 0xB841, 0x7AA5, 0xBFFA, 0x7AA6, 0xF1BC, 0x7AA7, 0xB842, 0x7AA8, 0xF1BF, 0x7AA9, 0xB843, 0x7AAA, 0xB844, + 0x7AAB, 0xB845, 0x7AAC, 0xF1BE, 0x7AAD, 0xF1C0, 0x7AAE, 0xB846, 0x7AAF, 0xB847, 0x7AB0, 0xB848, 0x7AB1, 0xB849, 0x7AB2, 0xB84A, + 0x7AB3, 0xF1C1, 0x7AB4, 0xB84B, 0x7AB5, 0xB84C, 0x7AB6, 0xB84D, 0x7AB7, 0xB84E, 0x7AB8, 0xB84F, 0x7AB9, 0xB850, 0x7ABA, 0xB851, + 0x7ABB, 0xB852, 0x7ABC, 0xB853, 0x7ABD, 0xB854, 0x7ABE, 0xB855, 0x7ABF, 0xC1FE, 0x7AC0, 0xB856, 0x7AC1, 0xB857, 0x7AC2, 0xB858, + 0x7AC3, 0xB859, 0x7AC4, 0xB85A, 0x7AC5, 0xB85B, 0x7AC6, 0xB85C, 0x7AC7, 0xB85D, 0x7AC8, 0xB85E, 0x7AC9, 0xB85F, 0x7ACA, 0xB860, + 0x7ACB, 0xC1A2, 0x7ACC, 0xB861, 0x7ACD, 0xB862, 0x7ACE, 0xB863, 0x7ACF, 0xB864, 0x7AD0, 0xB865, 0x7AD1, 0xB866, 0x7AD2, 0xB867, + 0x7AD3, 0xB868, 0x7AD4, 0xB869, 0x7AD5, 0xB86A, 0x7AD6, 0xCAFA, 0x7AD7, 0xB86B, 0x7AD8, 0xB86C, 0x7AD9, 0xD5BE, 0x7ADA, 0xB86D, + 0x7ADB, 0xB86E, 0x7ADC, 0xB86F, 0x7ADD, 0xB870, 0x7ADE, 0xBEBA, 0x7ADF, 0xBEB9, 0x7AE0, 0xD5C2, 0x7AE1, 0xB871, 0x7AE2, 0xB872, + 0x7AE3, 0xBFA2, 0x7AE4, 0xB873, 0x7AE5, 0xCDAF, 0x7AE6, 0xF1B5, 0x7AE7, 0xB874, 0x7AE8, 0xB875, 0x7AE9, 0xB876, 0x7AEA, 0xB877, + 0x7AEB, 0xB878, 0x7AEC, 0xB879, 0x7AED, 0xBDDF, 0x7AEE, 0xB87A, 0x7AEF, 0xB6CB, 0x7AF0, 0xB87B, 0x7AF1, 0xB87C, 0x7AF2, 0xB87D, + 0x7AF3, 0xB87E, 0x7AF4, 0xB880, 0x7AF5, 0xB881, 0x7AF6, 0xB882, 0x7AF7, 0xB883, 0x7AF8, 0xB884, 0x7AF9, 0xD6F1, 0x7AFA, 0xF3C3, + 0x7AFB, 0xB885, 0x7AFC, 0xB886, 0x7AFD, 0xF3C4, 0x7AFE, 0xB887, 0x7AFF, 0xB8CD, 0x7B00, 0xB888, 0x7B01, 0xB889, 0x7B02, 0xB88A, + 0x7B03, 0xF3C6, 0x7B04, 0xF3C7, 0x7B05, 0xB88B, 0x7B06, 0xB0CA, 0x7B07, 0xB88C, 0x7B08, 0xF3C5, 0x7B09, 0xB88D, 0x7B0A, 0xF3C9, + 0x7B0B, 0xCBF1, 0x7B0C, 0xB88E, 0x7B0D, 0xB88F, 0x7B0E, 0xB890, 0x7B0F, 0xF3CB, 0x7B10, 0xB891, 0x7B11, 0xD0A6, 0x7B12, 0xB892, + 0x7B13, 0xB893, 0x7B14, 0xB1CA, 0x7B15, 0xF3C8, 0x7B16, 0xB894, 0x7B17, 0xB895, 0x7B18, 0xB896, 0x7B19, 0xF3CF, 0x7B1A, 0xB897, + 0x7B1B, 0xB5D1, 0x7B1C, 0xB898, 0x7B1D, 0xB899, 0x7B1E, 0xF3D7, 0x7B1F, 0xB89A, 0x7B20, 0xF3D2, 0x7B21, 0xB89B, 0x7B22, 0xB89C, + 0x7B23, 0xB89D, 0x7B24, 0xF3D4, 0x7B25, 0xF3D3, 0x7B26, 0xB7FB, 0x7B27, 0xB89E, 0x7B28, 0xB1BF, 0x7B29, 0xB89F, 0x7B2A, 0xF3CE, + 0x7B2B, 0xF3CA, 0x7B2C, 0xB5DA, 0x7B2D, 0xB8A0, 0x7B2E, 0xF3D0, 0x7B2F, 0xB940, 0x7B30, 0xB941, 0x7B31, 0xF3D1, 0x7B32, 0xB942, + 0x7B33, 0xF3D5, 0x7B34, 0xB943, 0x7B35, 0xB944, 0x7B36, 0xB945, 0x7B37, 0xB946, 0x7B38, 0xF3CD, 0x7B39, 0xB947, 0x7B3A, 0xBCE3, + 0x7B3B, 0xB948, 0x7B3C, 0xC1FD, 0x7B3D, 0xB949, 0x7B3E, 0xF3D6, 0x7B3F, 0xB94A, 0x7B40, 0xB94B, 0x7B41, 0xB94C, 0x7B42, 0xB94D, + 0x7B43, 0xB94E, 0x7B44, 0xB94F, 0x7B45, 0xF3DA, 0x7B46, 0xB950, 0x7B47, 0xF3CC, 0x7B48, 0xB951, 0x7B49, 0xB5C8, 0x7B4A, 0xB952, + 0x7B4B, 0xBDEE, 0x7B4C, 0xF3DC, 0x7B4D, 0xB953, 0x7B4E, 0xB954, 0x7B4F, 0xB7A4, 0x7B50, 0xBFF0, 0x7B51, 0xD6FE, 0x7B52, 0xCDB2, + 0x7B53, 0xB955, 0x7B54, 0xB4F0, 0x7B55, 0xB956, 0x7B56, 0xB2DF, 0x7B57, 0xB957, 0x7B58, 0xF3D8, 0x7B59, 0xB958, 0x7B5A, 0xF3D9, + 0x7B5B, 0xC9B8, 0x7B5C, 0xB959, 0x7B5D, 0xF3DD, 0x7B5E, 0xB95A, 0x7B5F, 0xB95B, 0x7B60, 0xF3DE, 0x7B61, 0xB95C, 0x7B62, 0xF3E1, + 0x7B63, 0xB95D, 0x7B64, 0xB95E, 0x7B65, 0xB95F, 0x7B66, 0xB960, 0x7B67, 0xB961, 0x7B68, 0xB962, 0x7B69, 0xB963, 0x7B6A, 0xB964, + 0x7B6B, 0xB965, 0x7B6C, 0xB966, 0x7B6D, 0xB967, 0x7B6E, 0xF3DF, 0x7B6F, 0xB968, 0x7B70, 0xB969, 0x7B71, 0xF3E3, 0x7B72, 0xF3E2, + 0x7B73, 0xB96A, 0x7B74, 0xB96B, 0x7B75, 0xF3DB, 0x7B76, 0xB96C, 0x7B77, 0xBFEA, 0x7B78, 0xB96D, 0x7B79, 0xB3EF, 0x7B7A, 0xB96E, + 0x7B7B, 0xF3E0, 0x7B7C, 0xB96F, 0x7B7D, 0xB970, 0x7B7E, 0xC7A9, 0x7B7F, 0xB971, 0x7B80, 0xBCF2, 0x7B81, 0xB972, 0x7B82, 0xB973, + 0x7B83, 0xB974, 0x7B84, 0xB975, 0x7B85, 0xF3EB, 0x7B86, 0xB976, 0x7B87, 0xB977, 0x7B88, 0xB978, 0x7B89, 0xB979, 0x7B8A, 0xB97A, + 0x7B8B, 0xB97B, 0x7B8C, 0xB97C, 0x7B8D, 0xB9BF, 0x7B8E, 0xB97D, 0x7B8F, 0xB97E, 0x7B90, 0xF3E4, 0x7B91, 0xB980, 0x7B92, 0xB981, + 0x7B93, 0xB982, 0x7B94, 0xB2AD, 0x7B95, 0xBBFE, 0x7B96, 0xB983, 0x7B97, 0xCBE3, 0x7B98, 0xB984, 0x7B99, 0xB985, 0x7B9A, 0xB986, + 0x7B9B, 0xB987, 0x7B9C, 0xF3ED, 0x7B9D, 0xF3E9, 0x7B9E, 0xB988, 0x7B9F, 0xB989, 0x7BA0, 0xB98A, 0x7BA1, 0xB9DC, 0x7BA2, 0xF3EE, + 0x7BA3, 0xB98B, 0x7BA4, 0xB98C, 0x7BA5, 0xB98D, 0x7BA6, 0xF3E5, 0x7BA7, 0xF3E6, 0x7BA8, 0xF3EA, 0x7BA9, 0xC2E1, 0x7BAA, 0xF3EC, + 0x7BAB, 0xF3EF, 0x7BAC, 0xF3E8, 0x7BAD, 0xBCFD, 0x7BAE, 0xB98E, 0x7BAF, 0xB98F, 0x7BB0, 0xB990, 0x7BB1, 0xCFE4, 0x7BB2, 0xB991, + 0x7BB3, 0xB992, 0x7BB4, 0xF3F0, 0x7BB5, 0xB993, 0x7BB6, 0xB994, 0x7BB7, 0xB995, 0x7BB8, 0xF3E7, 0x7BB9, 0xB996, 0x7BBA, 0xB997, + 0x7BBB, 0xB998, 0x7BBC, 0xB999, 0x7BBD, 0xB99A, 0x7BBE, 0xB99B, 0x7BBF, 0xB99C, 0x7BC0, 0xB99D, 0x7BC1, 0xF3F2, 0x7BC2, 0xB99E, + 0x7BC3, 0xB99F, 0x7BC4, 0xB9A0, 0x7BC5, 0xBA40, 0x7BC6, 0xD7AD, 0x7BC7, 0xC6AA, 0x7BC8, 0xBA41, 0x7BC9, 0xBA42, 0x7BCA, 0xBA43, + 0x7BCB, 0xBA44, 0x7BCC, 0xF3F3, 0x7BCD, 0xBA45, 0x7BCE, 0xBA46, 0x7BCF, 0xBA47, 0x7BD0, 0xBA48, 0x7BD1, 0xF3F1, 0x7BD2, 0xBA49, + 0x7BD3, 0xC2A8, 0x7BD4, 0xBA4A, 0x7BD5, 0xBA4B, 0x7BD6, 0xBA4C, 0x7BD7, 0xBA4D, 0x7BD8, 0xBA4E, 0x7BD9, 0xB8DD, 0x7BDA, 0xF3F5, + 0x7BDB, 0xBA4F, 0x7BDC, 0xBA50, 0x7BDD, 0xF3F4, 0x7BDE, 0xBA51, 0x7BDF, 0xBA52, 0x7BE0, 0xBA53, 0x7BE1, 0xB4DB, 0x7BE2, 0xBA54, + 0x7BE3, 0xBA55, 0x7BE4, 0xBA56, 0x7BE5, 0xF3F6, 0x7BE6, 0xF3F7, 0x7BE7, 0xBA57, 0x7BE8, 0xBA58, 0x7BE9, 0xBA59, 0x7BEA, 0xF3F8, + 0x7BEB, 0xBA5A, 0x7BEC, 0xBA5B, 0x7BED, 0xBA5C, 0x7BEE, 0xC0BA, 0x7BEF, 0xBA5D, 0x7BF0, 0xBA5E, 0x7BF1, 0xC0E9, 0x7BF2, 0xBA5F, + 0x7BF3, 0xBA60, 0x7BF4, 0xBA61, 0x7BF5, 0xBA62, 0x7BF6, 0xBA63, 0x7BF7, 0xC5F1, 0x7BF8, 0xBA64, 0x7BF9, 0xBA65, 0x7BFA, 0xBA66, + 0x7BFB, 0xBA67, 0x7BFC, 0xF3FB, 0x7BFD, 0xBA68, 0x7BFE, 0xF3FA, 0x7BFF, 0xBA69, 0x7C00, 0xBA6A, 0x7C01, 0xBA6B, 0x7C02, 0xBA6C, + 0x7C03, 0xBA6D, 0x7C04, 0xBA6E, 0x7C05, 0xBA6F, 0x7C06, 0xBA70, 0x7C07, 0xB4D8, 0x7C08, 0xBA71, 0x7C09, 0xBA72, 0x7C0A, 0xBA73, + 0x7C0B, 0xF3FE, 0x7C0C, 0xF3F9, 0x7C0D, 0xBA74, 0x7C0E, 0xBA75, 0x7C0F, 0xF3FC, 0x7C10, 0xBA76, 0x7C11, 0xBA77, 0x7C12, 0xBA78, + 0x7C13, 0xBA79, 0x7C14, 0xBA7A, 0x7C15, 0xBA7B, 0x7C16, 0xF3FD, 0x7C17, 0xBA7C, 0x7C18, 0xBA7D, 0x7C19, 0xBA7E, 0x7C1A, 0xBA80, + 0x7C1B, 0xBA81, 0x7C1C, 0xBA82, 0x7C1D, 0xBA83, 0x7C1E, 0xBA84, 0x7C1F, 0xF4A1, 0x7C20, 0xBA85, 0x7C21, 0xBA86, 0x7C22, 0xBA87, + 0x7C23, 0xBA88, 0x7C24, 0xBA89, 0x7C25, 0xBA8A, 0x7C26, 0xF4A3, 0x7C27, 0xBBC9, 0x7C28, 0xBA8B, 0x7C29, 0xBA8C, 0x7C2A, 0xF4A2, + 0x7C2B, 0xBA8D, 0x7C2C, 0xBA8E, 0x7C2D, 0xBA8F, 0x7C2E, 0xBA90, 0x7C2F, 0xBA91, 0x7C30, 0xBA92, 0x7C31, 0xBA93, 0x7C32, 0xBA94, + 0x7C33, 0xBA95, 0x7C34, 0xBA96, 0x7C35, 0xBA97, 0x7C36, 0xBA98, 0x7C37, 0xBA99, 0x7C38, 0xF4A4, 0x7C39, 0xBA9A, 0x7C3A, 0xBA9B, + 0x7C3B, 0xBA9C, 0x7C3C, 0xBA9D, 0x7C3D, 0xBA9E, 0x7C3E, 0xBA9F, 0x7C3F, 0xB2BE, 0x7C40, 0xF4A6, 0x7C41, 0xF4A5, 0x7C42, 0xBAA0, + 0x7C43, 0xBB40, 0x7C44, 0xBB41, 0x7C45, 0xBB42, 0x7C46, 0xBB43, 0x7C47, 0xBB44, 0x7C48, 0xBB45, 0x7C49, 0xBB46, 0x7C4A, 0xBB47, + 0x7C4B, 0xBB48, 0x7C4C, 0xBB49, 0x7C4D, 0xBCAE, 0x7C4E, 0xBB4A, 0x7C4F, 0xBB4B, 0x7C50, 0xBB4C, 0x7C51, 0xBB4D, 0x7C52, 0xBB4E, + 0x7C53, 0xBB4F, 0x7C54, 0xBB50, 0x7C55, 0xBB51, 0x7C56, 0xBB52, 0x7C57, 0xBB53, 0x7C58, 0xBB54, 0x7C59, 0xBB55, 0x7C5A, 0xBB56, + 0x7C5B, 0xBB57, 0x7C5C, 0xBB58, 0x7C5D, 0xBB59, 0x7C5E, 0xBB5A, 0x7C5F, 0xBB5B, 0x7C60, 0xBB5C, 0x7C61, 0xBB5D, 0x7C62, 0xBB5E, + 0x7C63, 0xBB5F, 0x7C64, 0xBB60, 0x7C65, 0xBB61, 0x7C66, 0xBB62, 0x7C67, 0xBB63, 0x7C68, 0xBB64, 0x7C69, 0xBB65, 0x7C6A, 0xBB66, + 0x7C6B, 0xBB67, 0x7C6C, 0xBB68, 0x7C6D, 0xBB69, 0x7C6E, 0xBB6A, 0x7C6F, 0xBB6B, 0x7C70, 0xBB6C, 0x7C71, 0xBB6D, 0x7C72, 0xBB6E, + 0x7C73, 0xC3D7, 0x7C74, 0xD9E1, 0x7C75, 0xBB6F, 0x7C76, 0xBB70, 0x7C77, 0xBB71, 0x7C78, 0xBB72, 0x7C79, 0xBB73, 0x7C7A, 0xBB74, + 0x7C7B, 0xC0E0, 0x7C7C, 0xF4CC, 0x7C7D, 0xD7D1, 0x7C7E, 0xBB75, 0x7C7F, 0xBB76, 0x7C80, 0xBB77, 0x7C81, 0xBB78, 0x7C82, 0xBB79, + 0x7C83, 0xBB7A, 0x7C84, 0xBB7B, 0x7C85, 0xBB7C, 0x7C86, 0xBB7D, 0x7C87, 0xBB7E, 0x7C88, 0xBB80, 0x7C89, 0xB7DB, 0x7C8A, 0xBB81, + 0x7C8B, 0xBB82, 0x7C8C, 0xBB83, 0x7C8D, 0xBB84, 0x7C8E, 0xBB85, 0x7C8F, 0xBB86, 0x7C90, 0xBB87, 0x7C91, 0xF4CE, 0x7C92, 0xC1A3, + 0x7C93, 0xBB88, 0x7C94, 0xBB89, 0x7C95, 0xC6C9, 0x7C96, 0xBB8A, 0x7C97, 0xB4D6, 0x7C98, 0xD5B3, 0x7C99, 0xBB8B, 0x7C9A, 0xBB8C, + 0x7C9B, 0xBB8D, 0x7C9C, 0xF4D0, 0x7C9D, 0xF4CF, 0x7C9E, 0xF4D1, 0x7C9F, 0xCBDA, 0x7CA0, 0xBB8E, 0x7CA1, 0xBB8F, 0x7CA2, 0xF4D2, + 0x7CA3, 0xBB90, 0x7CA4, 0xD4C1, 0x7CA5, 0xD6E0, 0x7CA6, 0xBB91, 0x7CA7, 0xBB92, 0x7CA8, 0xBB93, 0x7CA9, 0xBB94, 0x7CAA, 0xB7E0, + 0x7CAB, 0xBB95, 0x7CAC, 0xBB96, 0x7CAD, 0xBB97, 0x7CAE, 0xC1B8, 0x7CAF, 0xBB98, 0x7CB0, 0xBB99, 0x7CB1, 0xC1BB, 0x7CB2, 0xF4D3, + 0x7CB3, 0xBEAC, 0x7CB4, 0xBB9A, 0x7CB5, 0xBB9B, 0x7CB6, 0xBB9C, 0x7CB7, 0xBB9D, 0x7CB8, 0xBB9E, 0x7CB9, 0xB4E2, 0x7CBA, 0xBB9F, + 0x7CBB, 0xBBA0, 0x7CBC, 0xF4D4, 0x7CBD, 0xF4D5, 0x7CBE, 0xBEAB, 0x7CBF, 0xBC40, 0x7CC0, 0xBC41, 0x7CC1, 0xF4D6, 0x7CC2, 0xBC42, + 0x7CC3, 0xBC43, 0x7CC4, 0xBC44, 0x7CC5, 0xF4DB, 0x7CC6, 0xBC45, 0x7CC7, 0xF4D7, 0x7CC8, 0xF4DA, 0x7CC9, 0xBC46, 0x7CCA, 0xBAFD, + 0x7CCB, 0xBC47, 0x7CCC, 0xF4D8, 0x7CCD, 0xF4D9, 0x7CCE, 0xBC48, 0x7CCF, 0xBC49, 0x7CD0, 0xBC4A, 0x7CD1, 0xBC4B, 0x7CD2, 0xBC4C, + 0x7CD3, 0xBC4D, 0x7CD4, 0xBC4E, 0x7CD5, 0xB8E2, 0x7CD6, 0xCCC7, 0x7CD7, 0xF4DC, 0x7CD8, 0xBC4F, 0x7CD9, 0xB2DA, 0x7CDA, 0xBC50, + 0x7CDB, 0xBC51, 0x7CDC, 0xC3D3, 0x7CDD, 0xBC52, 0x7CDE, 0xBC53, 0x7CDF, 0xD4E3, 0x7CE0, 0xBFB7, 0x7CE1, 0xBC54, 0x7CE2, 0xBC55, + 0x7CE3, 0xBC56, 0x7CE4, 0xBC57, 0x7CE5, 0xBC58, 0x7CE6, 0xBC59, 0x7CE7, 0xBC5A, 0x7CE8, 0xF4DD, 0x7CE9, 0xBC5B, 0x7CEA, 0xBC5C, + 0x7CEB, 0xBC5D, 0x7CEC, 0xBC5E, 0x7CED, 0xBC5F, 0x7CEE, 0xBC60, 0x7CEF, 0xC5B4, 0x7CF0, 0xBC61, 0x7CF1, 0xBC62, 0x7CF2, 0xBC63, + 0x7CF3, 0xBC64, 0x7CF4, 0xBC65, 0x7CF5, 0xBC66, 0x7CF6, 0xBC67, 0x7CF7, 0xBC68, 0x7CF8, 0xF4E9, 0x7CF9, 0xBC69, 0x7CFA, 0xBC6A, + 0x7CFB, 0xCFB5, 0x7CFC, 0xBC6B, 0x7CFD, 0xBC6C, 0x7CFE, 0xBC6D, 0x7CFF, 0xBC6E, 0x7D00, 0xBC6F, 0x7D01, 0xBC70, 0x7D02, 0xBC71, + 0x7D03, 0xBC72, 0x7D04, 0xBC73, 0x7D05, 0xBC74, 0x7D06, 0xBC75, 0x7D07, 0xBC76, 0x7D08, 0xBC77, 0x7D09, 0xBC78, 0x7D0A, 0xCEC9, + 0x7D0B, 0xBC79, 0x7D0C, 0xBC7A, 0x7D0D, 0xBC7B, 0x7D0E, 0xBC7C, 0x7D0F, 0xBC7D, 0x7D10, 0xBC7E, 0x7D11, 0xBC80, 0x7D12, 0xBC81, + 0x7D13, 0xBC82, 0x7D14, 0xBC83, 0x7D15, 0xBC84, 0x7D16, 0xBC85, 0x7D17, 0xBC86, 0x7D18, 0xBC87, 0x7D19, 0xBC88, 0x7D1A, 0xBC89, + 0x7D1B, 0xBC8A, 0x7D1C, 0xBC8B, 0x7D1D, 0xBC8C, 0x7D1E, 0xBC8D, 0x7D1F, 0xBC8E, 0x7D20, 0xCBD8, 0x7D21, 0xBC8F, 0x7D22, 0xCBF7, + 0x7D23, 0xBC90, 0x7D24, 0xBC91, 0x7D25, 0xBC92, 0x7D26, 0xBC93, 0x7D27, 0xBDF4, 0x7D28, 0xBC94, 0x7D29, 0xBC95, 0x7D2A, 0xBC96, + 0x7D2B, 0xD7CF, 0x7D2C, 0xBC97, 0x7D2D, 0xBC98, 0x7D2E, 0xBC99, 0x7D2F, 0xC0DB, 0x7D30, 0xBC9A, 0x7D31, 0xBC9B, 0x7D32, 0xBC9C, + 0x7D33, 0xBC9D, 0x7D34, 0xBC9E, 0x7D35, 0xBC9F, 0x7D36, 0xBCA0, 0x7D37, 0xBD40, 0x7D38, 0xBD41, 0x7D39, 0xBD42, 0x7D3A, 0xBD43, + 0x7D3B, 0xBD44, 0x7D3C, 0xBD45, 0x7D3D, 0xBD46, 0x7D3E, 0xBD47, 0x7D3F, 0xBD48, 0x7D40, 0xBD49, 0x7D41, 0xBD4A, 0x7D42, 0xBD4B, + 0x7D43, 0xBD4C, 0x7D44, 0xBD4D, 0x7D45, 0xBD4E, 0x7D46, 0xBD4F, 0x7D47, 0xBD50, 0x7D48, 0xBD51, 0x7D49, 0xBD52, 0x7D4A, 0xBD53, + 0x7D4B, 0xBD54, 0x7D4C, 0xBD55, 0x7D4D, 0xBD56, 0x7D4E, 0xBD57, 0x7D4F, 0xBD58, 0x7D50, 0xBD59, 0x7D51, 0xBD5A, 0x7D52, 0xBD5B, + 0x7D53, 0xBD5C, 0x7D54, 0xBD5D, 0x7D55, 0xBD5E, 0x7D56, 0xBD5F, 0x7D57, 0xBD60, 0x7D58, 0xBD61, 0x7D59, 0xBD62, 0x7D5A, 0xBD63, + 0x7D5B, 0xBD64, 0x7D5C, 0xBD65, 0x7D5D, 0xBD66, 0x7D5E, 0xBD67, 0x7D5F, 0xBD68, 0x7D60, 0xBD69, 0x7D61, 0xBD6A, 0x7D62, 0xBD6B, + 0x7D63, 0xBD6C, 0x7D64, 0xBD6D, 0x7D65, 0xBD6E, 0x7D66, 0xBD6F, 0x7D67, 0xBD70, 0x7D68, 0xBD71, 0x7D69, 0xBD72, 0x7D6A, 0xBD73, + 0x7D6B, 0xBD74, 0x7D6C, 0xBD75, 0x7D6D, 0xBD76, 0x7D6E, 0xD0F5, 0x7D6F, 0xBD77, 0x7D70, 0xBD78, 0x7D71, 0xBD79, 0x7D72, 0xBD7A, + 0x7D73, 0xBD7B, 0x7D74, 0xBD7C, 0x7D75, 0xBD7D, 0x7D76, 0xBD7E, 0x7D77, 0xF4EA, 0x7D78, 0xBD80, 0x7D79, 0xBD81, 0x7D7A, 0xBD82, + 0x7D7B, 0xBD83, 0x7D7C, 0xBD84, 0x7D7D, 0xBD85, 0x7D7E, 0xBD86, 0x7D7F, 0xBD87, 0x7D80, 0xBD88, 0x7D81, 0xBD89, 0x7D82, 0xBD8A, + 0x7D83, 0xBD8B, 0x7D84, 0xBD8C, 0x7D85, 0xBD8D, 0x7D86, 0xBD8E, 0x7D87, 0xBD8F, 0x7D88, 0xBD90, 0x7D89, 0xBD91, 0x7D8A, 0xBD92, + 0x7D8B, 0xBD93, 0x7D8C, 0xBD94, 0x7D8D, 0xBD95, 0x7D8E, 0xBD96, 0x7D8F, 0xBD97, 0x7D90, 0xBD98, 0x7D91, 0xBD99, 0x7D92, 0xBD9A, + 0x7D93, 0xBD9B, 0x7D94, 0xBD9C, 0x7D95, 0xBD9D, 0x7D96, 0xBD9E, 0x7D97, 0xBD9F, 0x7D98, 0xBDA0, 0x7D99, 0xBE40, 0x7D9A, 0xBE41, + 0x7D9B, 0xBE42, 0x7D9C, 0xBE43, 0x7D9D, 0xBE44, 0x7D9E, 0xBE45, 0x7D9F, 0xBE46, 0x7DA0, 0xBE47, 0x7DA1, 0xBE48, 0x7DA2, 0xBE49, + 0x7DA3, 0xBE4A, 0x7DA4, 0xBE4B, 0x7DA5, 0xBE4C, 0x7DA6, 0xF4EB, 0x7DA7, 0xBE4D, 0x7DA8, 0xBE4E, 0x7DA9, 0xBE4F, 0x7DAA, 0xBE50, + 0x7DAB, 0xBE51, 0x7DAC, 0xBE52, 0x7DAD, 0xBE53, 0x7DAE, 0xF4EC, 0x7DAF, 0xBE54, 0x7DB0, 0xBE55, 0x7DB1, 0xBE56, 0x7DB2, 0xBE57, + 0x7DB3, 0xBE58, 0x7DB4, 0xBE59, 0x7DB5, 0xBE5A, 0x7DB6, 0xBE5B, 0x7DB7, 0xBE5C, 0x7DB8, 0xBE5D, 0x7DB9, 0xBE5E, 0x7DBA, 0xBE5F, + 0x7DBB, 0xBE60, 0x7DBC, 0xBE61, 0x7DBD, 0xBE62, 0x7DBE, 0xBE63, 0x7DBF, 0xBE64, 0x7DC0, 0xBE65, 0x7DC1, 0xBE66, 0x7DC2, 0xBE67, + 0x7DC3, 0xBE68, 0x7DC4, 0xBE69, 0x7DC5, 0xBE6A, 0x7DC6, 0xBE6B, 0x7DC7, 0xBE6C, 0x7DC8, 0xBE6D, 0x7DC9, 0xBE6E, 0x7DCA, 0xBE6F, + 0x7DCB, 0xBE70, 0x7DCC, 0xBE71, 0x7DCD, 0xBE72, 0x7DCE, 0xBE73, 0x7DCF, 0xBE74, 0x7DD0, 0xBE75, 0x7DD1, 0xBE76, 0x7DD2, 0xBE77, + 0x7DD3, 0xBE78, 0x7DD4, 0xBE79, 0x7DD5, 0xBE7A, 0x7DD6, 0xBE7B, 0x7DD7, 0xBE7C, 0x7DD8, 0xBE7D, 0x7DD9, 0xBE7E, 0x7DDA, 0xBE80, + 0x7DDB, 0xBE81, 0x7DDC, 0xBE82, 0x7DDD, 0xBE83, 0x7DDE, 0xBE84, 0x7DDF, 0xBE85, 0x7DE0, 0xBE86, 0x7DE1, 0xBE87, 0x7DE2, 0xBE88, + 0x7DE3, 0xBE89, 0x7DE4, 0xBE8A, 0x7DE5, 0xBE8B, 0x7DE6, 0xBE8C, 0x7DE7, 0xBE8D, 0x7DE8, 0xBE8E, 0x7DE9, 0xBE8F, 0x7DEA, 0xBE90, + 0x7DEB, 0xBE91, 0x7DEC, 0xBE92, 0x7DED, 0xBE93, 0x7DEE, 0xBE94, 0x7DEF, 0xBE95, 0x7DF0, 0xBE96, 0x7DF1, 0xBE97, 0x7DF2, 0xBE98, + 0x7DF3, 0xBE99, 0x7DF4, 0xBE9A, 0x7DF5, 0xBE9B, 0x7DF6, 0xBE9C, 0x7DF7, 0xBE9D, 0x7DF8, 0xBE9E, 0x7DF9, 0xBE9F, 0x7DFA, 0xBEA0, + 0x7DFB, 0xBF40, 0x7DFC, 0xBF41, 0x7DFD, 0xBF42, 0x7DFE, 0xBF43, 0x7DFF, 0xBF44, 0x7E00, 0xBF45, 0x7E01, 0xBF46, 0x7E02, 0xBF47, + 0x7E03, 0xBF48, 0x7E04, 0xBF49, 0x7E05, 0xBF4A, 0x7E06, 0xBF4B, 0x7E07, 0xBF4C, 0x7E08, 0xBF4D, 0x7E09, 0xBF4E, 0x7E0A, 0xBF4F, + 0x7E0B, 0xBF50, 0x7E0C, 0xBF51, 0x7E0D, 0xBF52, 0x7E0E, 0xBF53, 0x7E0F, 0xBF54, 0x7E10, 0xBF55, 0x7E11, 0xBF56, 0x7E12, 0xBF57, + 0x7E13, 0xBF58, 0x7E14, 0xBF59, 0x7E15, 0xBF5A, 0x7E16, 0xBF5B, 0x7E17, 0xBF5C, 0x7E18, 0xBF5D, 0x7E19, 0xBF5E, 0x7E1A, 0xBF5F, + 0x7E1B, 0xBF60, 0x7E1C, 0xBF61, 0x7E1D, 0xBF62, 0x7E1E, 0xBF63, 0x7E1F, 0xBF64, 0x7E20, 0xBF65, 0x7E21, 0xBF66, 0x7E22, 0xBF67, + 0x7E23, 0xBF68, 0x7E24, 0xBF69, 0x7E25, 0xBF6A, 0x7E26, 0xBF6B, 0x7E27, 0xBF6C, 0x7E28, 0xBF6D, 0x7E29, 0xBF6E, 0x7E2A, 0xBF6F, + 0x7E2B, 0xBF70, 0x7E2C, 0xBF71, 0x7E2D, 0xBF72, 0x7E2E, 0xBF73, 0x7E2F, 0xBF74, 0x7E30, 0xBF75, 0x7E31, 0xBF76, 0x7E32, 0xBF77, + 0x7E33, 0xBF78, 0x7E34, 0xBF79, 0x7E35, 0xBF7A, 0x7E36, 0xBF7B, 0x7E37, 0xBF7C, 0x7E38, 0xBF7D, 0x7E39, 0xBF7E, 0x7E3A, 0xBF80, + 0x7E3B, 0xF7E3, 0x7E3C, 0xBF81, 0x7E3D, 0xBF82, 0x7E3E, 0xBF83, 0x7E3F, 0xBF84, 0x7E40, 0xBF85, 0x7E41, 0xB7B1, 0x7E42, 0xBF86, + 0x7E43, 0xBF87, 0x7E44, 0xBF88, 0x7E45, 0xBF89, 0x7E46, 0xBF8A, 0x7E47, 0xF4ED, 0x7E48, 0xBF8B, 0x7E49, 0xBF8C, 0x7E4A, 0xBF8D, + 0x7E4B, 0xBF8E, 0x7E4C, 0xBF8F, 0x7E4D, 0xBF90, 0x7E4E, 0xBF91, 0x7E4F, 0xBF92, 0x7E50, 0xBF93, 0x7E51, 0xBF94, 0x7E52, 0xBF95, + 0x7E53, 0xBF96, 0x7E54, 0xBF97, 0x7E55, 0xBF98, 0x7E56, 0xBF99, 0x7E57, 0xBF9A, 0x7E58, 0xBF9B, 0x7E59, 0xBF9C, 0x7E5A, 0xBF9D, + 0x7E5B, 0xBF9E, 0x7E5C, 0xBF9F, 0x7E5D, 0xBFA0, 0x7E5E, 0xC040, 0x7E5F, 0xC041, 0x7E60, 0xC042, 0x7E61, 0xC043, 0x7E62, 0xC044, + 0x7E63, 0xC045, 0x7E64, 0xC046, 0x7E65, 0xC047, 0x7E66, 0xC048, 0x7E67, 0xC049, 0x7E68, 0xC04A, 0x7E69, 0xC04B, 0x7E6A, 0xC04C, + 0x7E6B, 0xC04D, 0x7E6C, 0xC04E, 0x7E6D, 0xC04F, 0x7E6E, 0xC050, 0x7E6F, 0xC051, 0x7E70, 0xC052, 0x7E71, 0xC053, 0x7E72, 0xC054, + 0x7E73, 0xC055, 0x7E74, 0xC056, 0x7E75, 0xC057, 0x7E76, 0xC058, 0x7E77, 0xC059, 0x7E78, 0xC05A, 0x7E79, 0xC05B, 0x7E7A, 0xC05C, + 0x7E7B, 0xC05D, 0x7E7C, 0xC05E, 0x7E7D, 0xC05F, 0x7E7E, 0xC060, 0x7E7F, 0xC061, 0x7E80, 0xC062, 0x7E81, 0xC063, 0x7E82, 0xD7EB, + 0x7E83, 0xC064, 0x7E84, 0xC065, 0x7E85, 0xC066, 0x7E86, 0xC067, 0x7E87, 0xC068, 0x7E88, 0xC069, 0x7E89, 0xC06A, 0x7E8A, 0xC06B, + 0x7E8B, 0xC06C, 0x7E8C, 0xC06D, 0x7E8D, 0xC06E, 0x7E8E, 0xC06F, 0x7E8F, 0xC070, 0x7E90, 0xC071, 0x7E91, 0xC072, 0x7E92, 0xC073, + 0x7E93, 0xC074, 0x7E94, 0xC075, 0x7E95, 0xC076, 0x7E96, 0xC077, 0x7E97, 0xC078, 0x7E98, 0xC079, 0x7E99, 0xC07A, 0x7E9A, 0xC07B, + 0x7E9B, 0xF4EE, 0x7E9C, 0xC07C, 0x7E9D, 0xC07D, 0x7E9E, 0xC07E, 0x7E9F, 0xE6F9, 0x7EA0, 0xBEC0, 0x7EA1, 0xE6FA, 0x7EA2, 0xBAEC, + 0x7EA3, 0xE6FB, 0x7EA4, 0xCFCB, 0x7EA5, 0xE6FC, 0x7EA6, 0xD4BC, 0x7EA7, 0xBCB6, 0x7EA8, 0xE6FD, 0x7EA9, 0xE6FE, 0x7EAA, 0xBCCD, + 0x7EAB, 0xC8D2, 0x7EAC, 0xCEB3, 0x7EAD, 0xE7A1, 0x7EAE, 0xC080, 0x7EAF, 0xB4BF, 0x7EB0, 0xE7A2, 0x7EB1, 0xC9B4, 0x7EB2, 0xB8D9, + 0x7EB3, 0xC4C9, 0x7EB4, 0xC081, 0x7EB5, 0xD7DD, 0x7EB6, 0xC2DA, 0x7EB7, 0xB7D7, 0x7EB8, 0xD6BD, 0x7EB9, 0xCEC6, 0x7EBA, 0xB7C4, + 0x7EBB, 0xC082, 0x7EBC, 0xC083, 0x7EBD, 0xC5A6, 0x7EBE, 0xE7A3, 0x7EBF, 0xCFDF, 0x7EC0, 0xE7A4, 0x7EC1, 0xE7A5, 0x7EC2, 0xE7A6, + 0x7EC3, 0xC1B7, 0x7EC4, 0xD7E9, 0x7EC5, 0xC9F0, 0x7EC6, 0xCFB8, 0x7EC7, 0xD6AF, 0x7EC8, 0xD6D5, 0x7EC9, 0xE7A7, 0x7ECA, 0xB0ED, + 0x7ECB, 0xE7A8, 0x7ECC, 0xE7A9, 0x7ECD, 0xC9DC, 0x7ECE, 0xD2EF, 0x7ECF, 0xBEAD, 0x7ED0, 0xE7AA, 0x7ED1, 0xB0F3, 0x7ED2, 0xC8DE, + 0x7ED3, 0xBDE1, 0x7ED4, 0xE7AB, 0x7ED5, 0xC8C6, 0x7ED6, 0xC084, 0x7ED7, 0xE7AC, 0x7ED8, 0xBBE6, 0x7ED9, 0xB8F8, 0x7EDA, 0xD1A4, + 0x7EDB, 0xE7AD, 0x7EDC, 0xC2E7, 0x7EDD, 0xBEF8, 0x7EDE, 0xBDCA, 0x7EDF, 0xCDB3, 0x7EE0, 0xE7AE, 0x7EE1, 0xE7AF, 0x7EE2, 0xBEEE, + 0x7EE3, 0xD0E5, 0x7EE4, 0xC085, 0x7EE5, 0xCBE7, 0x7EE6, 0xCCD0, 0x7EE7, 0xBCCC, 0x7EE8, 0xE7B0, 0x7EE9, 0xBCA8, 0x7EEA, 0xD0F7, + 0x7EEB, 0xE7B1, 0x7EEC, 0xC086, 0x7EED, 0xD0F8, 0x7EEE, 0xE7B2, 0x7EEF, 0xE7B3, 0x7EF0, 0xB4C2, 0x7EF1, 0xE7B4, 0x7EF2, 0xE7B5, + 0x7EF3, 0xC9FE, 0x7EF4, 0xCEAC, 0x7EF5, 0xC3E0, 0x7EF6, 0xE7B7, 0x7EF7, 0xB1C1, 0x7EF8, 0xB3F1, 0x7EF9, 0xC087, 0x7EFA, 0xE7B8, + 0x7EFB, 0xE7B9, 0x7EFC, 0xD7DB, 0x7EFD, 0xD5C0, 0x7EFE, 0xE7BA, 0x7EFF, 0xC2CC, 0x7F00, 0xD7BA, 0x7F01, 0xE7BB, 0x7F02, 0xE7BC, + 0x7F03, 0xE7BD, 0x7F04, 0xBCEA, 0x7F05, 0xC3E5, 0x7F06, 0xC0C2, 0x7F07, 0xE7BE, 0x7F08, 0xE7BF, 0x7F09, 0xBCA9, 0x7F0A, 0xC088, + 0x7F0B, 0xE7C0, 0x7F0C, 0xE7C1, 0x7F0D, 0xE7B6, 0x7F0E, 0xB6D0, 0x7F0F, 0xE7C2, 0x7F10, 0xC089, 0x7F11, 0xE7C3, 0x7F12, 0xE7C4, + 0x7F13, 0xBBBA, 0x7F14, 0xB5DE, 0x7F15, 0xC2C6, 0x7F16, 0xB1E0, 0x7F17, 0xE7C5, 0x7F18, 0xD4B5, 0x7F19, 0xE7C6, 0x7F1A, 0xB8BF, + 0x7F1B, 0xE7C8, 0x7F1C, 0xE7C7, 0x7F1D, 0xB7EC, 0x7F1E, 0xC08A, 0x7F1F, 0xE7C9, 0x7F20, 0xB2F8, 0x7F21, 0xE7CA, 0x7F22, 0xE7CB, + 0x7F23, 0xE7CC, 0x7F24, 0xE7CD, 0x7F25, 0xE7CE, 0x7F26, 0xE7CF, 0x7F27, 0xE7D0, 0x7F28, 0xD3A7, 0x7F29, 0xCBF5, 0x7F2A, 0xE7D1, + 0x7F2B, 0xE7D2, 0x7F2C, 0xE7D3, 0x7F2D, 0xE7D4, 0x7F2E, 0xC9C9, 0x7F2F, 0xE7D5, 0x7F30, 0xE7D6, 0x7F31, 0xE7D7, 0x7F32, 0xE7D8, + 0x7F33, 0xE7D9, 0x7F34, 0xBDC9, 0x7F35, 0xE7DA, 0x7F36, 0xF3BE, 0x7F37, 0xC08B, 0x7F38, 0xB8D7, 0x7F39, 0xC08C, 0x7F3A, 0xC8B1, + 0x7F3B, 0xC08D, 0x7F3C, 0xC08E, 0x7F3D, 0xC08F, 0x7F3E, 0xC090, 0x7F3F, 0xC091, 0x7F40, 0xC092, 0x7F41, 0xC093, 0x7F42, 0xF3BF, + 0x7F43, 0xC094, 0x7F44, 0xF3C0, 0x7F45, 0xF3C1, 0x7F46, 0xC095, 0x7F47, 0xC096, 0x7F48, 0xC097, 0x7F49, 0xC098, 0x7F4A, 0xC099, + 0x7F4B, 0xC09A, 0x7F4C, 0xC09B, 0x7F4D, 0xC09C, 0x7F4E, 0xC09D, 0x7F4F, 0xC09E, 0x7F50, 0xB9DE, 0x7F51, 0xCDF8, 0x7F52, 0xC09F, + 0x7F53, 0xC0A0, 0x7F54, 0xD8E8, 0x7F55, 0xBAB1, 0x7F56, 0xC140, 0x7F57, 0xC2DE, 0x7F58, 0xEEB7, 0x7F59, 0xC141, 0x7F5A, 0xB7A3, + 0x7F5B, 0xC142, 0x7F5C, 0xC143, 0x7F5D, 0xC144, 0x7F5E, 0xC145, 0x7F5F, 0xEEB9, 0x7F60, 0xC146, 0x7F61, 0xEEB8, 0x7F62, 0xB0D5, + 0x7F63, 0xC147, 0x7F64, 0xC148, 0x7F65, 0xC149, 0x7F66, 0xC14A, 0x7F67, 0xC14B, 0x7F68, 0xEEBB, 0x7F69, 0xD5D6, 0x7F6A, 0xD7EF, + 0x7F6B, 0xC14C, 0x7F6C, 0xC14D, 0x7F6D, 0xC14E, 0x7F6E, 0xD6C3, 0x7F6F, 0xC14F, 0x7F70, 0xC150, 0x7F71, 0xEEBD, 0x7F72, 0xCAF0, + 0x7F73, 0xC151, 0x7F74, 0xEEBC, 0x7F75, 0xC152, 0x7F76, 0xC153, 0x7F77, 0xC154, 0x7F78, 0xC155, 0x7F79, 0xEEBE, 0x7F7A, 0xC156, + 0x7F7B, 0xC157, 0x7F7C, 0xC158, 0x7F7D, 0xC159, 0x7F7E, 0xEEC0, 0x7F7F, 0xC15A, 0x7F80, 0xC15B, 0x7F81, 0xEEBF, 0x7F82, 0xC15C, + 0x7F83, 0xC15D, 0x7F84, 0xC15E, 0x7F85, 0xC15F, 0x7F86, 0xC160, 0x7F87, 0xC161, 0x7F88, 0xC162, 0x7F89, 0xC163, 0x7F8A, 0xD1F2, + 0x7F8B, 0xC164, 0x7F8C, 0xC7BC, 0x7F8D, 0xC165, 0x7F8E, 0xC3C0, 0x7F8F, 0xC166, 0x7F90, 0xC167, 0x7F91, 0xC168, 0x7F92, 0xC169, + 0x7F93, 0xC16A, 0x7F94, 0xB8E1, 0x7F95, 0xC16B, 0x7F96, 0xC16C, 0x7F97, 0xC16D, 0x7F98, 0xC16E, 0x7F99, 0xC16F, 0x7F9A, 0xC1E7, + 0x7F9B, 0xC170, 0x7F9C, 0xC171, 0x7F9D, 0xF4C6, 0x7F9E, 0xD0DF, 0x7F9F, 0xF4C7, 0x7FA0, 0xC172, 0x7FA1, 0xCFDB, 0x7FA2, 0xC173, + 0x7FA3, 0xC174, 0x7FA4, 0xC8BA, 0x7FA5, 0xC175, 0x7FA6, 0xC176, 0x7FA7, 0xF4C8, 0x7FA8, 0xC177, 0x7FA9, 0xC178, 0x7FAA, 0xC179, + 0x7FAB, 0xC17A, 0x7FAC, 0xC17B, 0x7FAD, 0xC17C, 0x7FAE, 0xC17D, 0x7FAF, 0xF4C9, 0x7FB0, 0xF4CA, 0x7FB1, 0xC17E, 0x7FB2, 0xF4CB, + 0x7FB3, 0xC180, 0x7FB4, 0xC181, 0x7FB5, 0xC182, 0x7FB6, 0xC183, 0x7FB7, 0xC184, 0x7FB8, 0xD9FA, 0x7FB9, 0xB8FE, 0x7FBA, 0xC185, + 0x7FBB, 0xC186, 0x7FBC, 0xE5F1, 0x7FBD, 0xD3F0, 0x7FBE, 0xC187, 0x7FBF, 0xF4E0, 0x7FC0, 0xC188, 0x7FC1, 0xCECC, 0x7FC2, 0xC189, + 0x7FC3, 0xC18A, 0x7FC4, 0xC18B, 0x7FC5, 0xB3E1, 0x7FC6, 0xC18C, 0x7FC7, 0xC18D, 0x7FC8, 0xC18E, 0x7FC9, 0xC18F, 0x7FCA, 0xF1B4, + 0x7FCB, 0xC190, 0x7FCC, 0xD2EE, 0x7FCD, 0xC191, 0x7FCE, 0xF4E1, 0x7FCF, 0xC192, 0x7FD0, 0xC193, 0x7FD1, 0xC194, 0x7FD2, 0xC195, + 0x7FD3, 0xC196, 0x7FD4, 0xCFE8, 0x7FD5, 0xF4E2, 0x7FD6, 0xC197, 0x7FD7, 0xC198, 0x7FD8, 0xC7CC, 0x7FD9, 0xC199, 0x7FDA, 0xC19A, + 0x7FDB, 0xC19B, 0x7FDC, 0xC19C, 0x7FDD, 0xC19D, 0x7FDE, 0xC19E, 0x7FDF, 0xB5D4, 0x7FE0, 0xB4E4, 0x7FE1, 0xF4E4, 0x7FE2, 0xC19F, + 0x7FE3, 0xC1A0, 0x7FE4, 0xC240, 0x7FE5, 0xF4E3, 0x7FE6, 0xF4E5, 0x7FE7, 0xC241, 0x7FE8, 0xC242, 0x7FE9, 0xF4E6, 0x7FEA, 0xC243, + 0x7FEB, 0xC244, 0x7FEC, 0xC245, 0x7FED, 0xC246, 0x7FEE, 0xF4E7, 0x7FEF, 0xC247, 0x7FF0, 0xBAB2, 0x7FF1, 0xB0BF, 0x7FF2, 0xC248, + 0x7FF3, 0xF4E8, 0x7FF4, 0xC249, 0x7FF5, 0xC24A, 0x7FF6, 0xC24B, 0x7FF7, 0xC24C, 0x7FF8, 0xC24D, 0x7FF9, 0xC24E, 0x7FFA, 0xC24F, + 0x7FFB, 0xB7AD, 0x7FFC, 0xD2ED, 0x7FFD, 0xC250, 0x7FFE, 0xC251, 0x7FFF, 0xC252, 0x8000, 0xD2AB, 0x8001, 0xC0CF, 0x8002, 0xC253, + 0x8003, 0xBFBC, 0x8004, 0xEBA3, 0x8005, 0xD5DF, 0x8006, 0xEAC8, 0x8007, 0xC254, 0x8008, 0xC255, 0x8009, 0xC256, 0x800A, 0xC257, + 0x800B, 0xF1F3, 0x800C, 0xB6F8, 0x800D, 0xCBA3, 0x800E, 0xC258, 0x800F, 0xC259, 0x8010, 0xC4CD, 0x8011, 0xC25A, 0x8012, 0xF1E7, + 0x8013, 0xC25B, 0x8014, 0xF1E8, 0x8015, 0xB8FB, 0x8016, 0xF1E9, 0x8017, 0xBAC4, 0x8018, 0xD4C5, 0x8019, 0xB0D2, 0x801A, 0xC25C, + 0x801B, 0xC25D, 0x801C, 0xF1EA, 0x801D, 0xC25E, 0x801E, 0xC25F, 0x801F, 0xC260, 0x8020, 0xF1EB, 0x8021, 0xC261, 0x8022, 0xF1EC, + 0x8023, 0xC262, 0x8024, 0xC263, 0x8025, 0xF1ED, 0x8026, 0xF1EE, 0x8027, 0xF1EF, 0x8028, 0xF1F1, 0x8029, 0xF1F0, 0x802A, 0xC5D5, + 0x802B, 0xC264, 0x802C, 0xC265, 0x802D, 0xC266, 0x802E, 0xC267, 0x802F, 0xC268, 0x8030, 0xC269, 0x8031, 0xF1F2, 0x8032, 0xC26A, + 0x8033, 0xB6FA, 0x8034, 0xC26B, 0x8035, 0xF1F4, 0x8036, 0xD2AE, 0x8037, 0xDEC7, 0x8038, 0xCBCA, 0x8039, 0xC26C, 0x803A, 0xC26D, + 0x803B, 0xB3DC, 0x803C, 0xC26E, 0x803D, 0xB5A2, 0x803E, 0xC26F, 0x803F, 0xB9A2, 0x8040, 0xC270, 0x8041, 0xC271, 0x8042, 0xC4F4, + 0x8043, 0xF1F5, 0x8044, 0xC272, 0x8045, 0xC273, 0x8046, 0xF1F6, 0x8047, 0xC274, 0x8048, 0xC275, 0x8049, 0xC276, 0x804A, 0xC1C4, + 0x804B, 0xC1FB, 0x804C, 0xD6B0, 0x804D, 0xF1F7, 0x804E, 0xC277, 0x804F, 0xC278, 0x8050, 0xC279, 0x8051, 0xC27A, 0x8052, 0xF1F8, + 0x8053, 0xC27B, 0x8054, 0xC1AA, 0x8055, 0xC27C, 0x8056, 0xC27D, 0x8057, 0xC27E, 0x8058, 0xC6B8, 0x8059, 0xC280, 0x805A, 0xBEDB, + 0x805B, 0xC281, 0x805C, 0xC282, 0x805D, 0xC283, 0x805E, 0xC284, 0x805F, 0xC285, 0x8060, 0xC286, 0x8061, 0xC287, 0x8062, 0xC288, + 0x8063, 0xC289, 0x8064, 0xC28A, 0x8065, 0xC28B, 0x8066, 0xC28C, 0x8067, 0xC28D, 0x8068, 0xC28E, 0x8069, 0xF1F9, 0x806A, 0xB4CF, + 0x806B, 0xC28F, 0x806C, 0xC290, 0x806D, 0xC291, 0x806E, 0xC292, 0x806F, 0xC293, 0x8070, 0xC294, 0x8071, 0xF1FA, 0x8072, 0xC295, + 0x8073, 0xC296, 0x8074, 0xC297, 0x8075, 0xC298, 0x8076, 0xC299, 0x8077, 0xC29A, 0x8078, 0xC29B, 0x8079, 0xC29C, 0x807A, 0xC29D, + 0x807B, 0xC29E, 0x807C, 0xC29F, 0x807D, 0xC2A0, 0x807E, 0xC340, 0x807F, 0xEDB2, 0x8080, 0xEDB1, 0x8081, 0xC341, 0x8082, 0xC342, + 0x8083, 0xCBE0, 0x8084, 0xD2DE, 0x8085, 0xC343, 0x8086, 0xCBC1, 0x8087, 0xD5D8, 0x8088, 0xC344, 0x8089, 0xC8E2, 0x808A, 0xC345, + 0x808B, 0xC0DF, 0x808C, 0xBCA1, 0x808D, 0xC346, 0x808E, 0xC347, 0x808F, 0xC348, 0x8090, 0xC349, 0x8091, 0xC34A, 0x8092, 0xC34B, + 0x8093, 0xEBC1, 0x8094, 0xC34C, 0x8095, 0xC34D, 0x8096, 0xD0A4, 0x8097, 0xC34E, 0x8098, 0xD6E2, 0x8099, 0xC34F, 0x809A, 0xB6C7, + 0x809B, 0xB8D8, 0x809C, 0xEBC0, 0x809D, 0xB8CE, 0x809E, 0xC350, 0x809F, 0xEBBF, 0x80A0, 0xB3A6, 0x80A1, 0xB9C9, 0x80A2, 0xD6AB, + 0x80A3, 0xC351, 0x80A4, 0xB7F4, 0x80A5, 0xB7CA, 0x80A6, 0xC352, 0x80A7, 0xC353, 0x80A8, 0xC354, 0x80A9, 0xBCE7, 0x80AA, 0xB7BE, + 0x80AB, 0xEBC6, 0x80AC, 0xC355, 0x80AD, 0xEBC7, 0x80AE, 0xB0B9, 0x80AF, 0xBFCF, 0x80B0, 0xC356, 0x80B1, 0xEBC5, 0x80B2, 0xD3FD, + 0x80B3, 0xC357, 0x80B4, 0xEBC8, 0x80B5, 0xC358, 0x80B6, 0xC359, 0x80B7, 0xEBC9, 0x80B8, 0xC35A, 0x80B9, 0xC35B, 0x80BA, 0xB7CE, + 0x80BB, 0xC35C, 0x80BC, 0xEBC2, 0x80BD, 0xEBC4, 0x80BE, 0xC9F6, 0x80BF, 0xD6D7, 0x80C0, 0xD5CD, 0x80C1, 0xD0B2, 0x80C2, 0xEBCF, + 0x80C3, 0xCEB8, 0x80C4, 0xEBD0, 0x80C5, 0xC35D, 0x80C6, 0xB5A8, 0x80C7, 0xC35E, 0x80C8, 0xC35F, 0x80C9, 0xC360, 0x80CA, 0xC361, + 0x80CB, 0xC362, 0x80CC, 0xB1B3, 0x80CD, 0xEBD2, 0x80CE, 0xCCA5, 0x80CF, 0xC363, 0x80D0, 0xC364, 0x80D1, 0xC365, 0x80D2, 0xC366, + 0x80D3, 0xC367, 0x80D4, 0xC368, 0x80D5, 0xC369, 0x80D6, 0xC5D6, 0x80D7, 0xEBD3, 0x80D8, 0xC36A, 0x80D9, 0xEBD1, 0x80DA, 0xC5DF, + 0x80DB, 0xEBCE, 0x80DC, 0xCAA4, 0x80DD, 0xEBD5, 0x80DE, 0xB0FB, 0x80DF, 0xC36B, 0x80E0, 0xC36C, 0x80E1, 0xBAFA, 0x80E2, 0xC36D, + 0x80E3, 0xC36E, 0x80E4, 0xD8B7, 0x80E5, 0xF1E3, 0x80E6, 0xC36F, 0x80E7, 0xEBCA, 0x80E8, 0xEBCB, 0x80E9, 0xEBCC, 0x80EA, 0xEBCD, + 0x80EB, 0xEBD6, 0x80EC, 0xE6C0, 0x80ED, 0xEBD9, 0x80EE, 0xC370, 0x80EF, 0xBFE8, 0x80F0, 0xD2C8, 0x80F1, 0xEBD7, 0x80F2, 0xEBDC, + 0x80F3, 0xB8EC, 0x80F4, 0xEBD8, 0x80F5, 0xC371, 0x80F6, 0xBDBA, 0x80F7, 0xC372, 0x80F8, 0xD0D8, 0x80F9, 0xC373, 0x80FA, 0xB0B7, + 0x80FB, 0xC374, 0x80FC, 0xEBDD, 0x80FD, 0xC4DC, 0x80FE, 0xC375, 0x80FF, 0xC376, 0x8100, 0xC377, 0x8101, 0xC378, 0x8102, 0xD6AC, + 0x8103, 0xC379, 0x8104, 0xC37A, 0x8105, 0xC37B, 0x8106, 0xB4E0, 0x8107, 0xC37C, 0x8108, 0xC37D, 0x8109, 0xC2F6, 0x810A, 0xBCB9, + 0x810B, 0xC37E, 0x810C, 0xC380, 0x810D, 0xEBDA, 0x810E, 0xEBDB, 0x810F, 0xD4E0, 0x8110, 0xC6EA, 0x8111, 0xC4D4, 0x8112, 0xEBDF, + 0x8113, 0xC5A7, 0x8114, 0xD9F5, 0x8115, 0xC381, 0x8116, 0xB2B1, 0x8117, 0xC382, 0x8118, 0xEBE4, 0x8119, 0xC383, 0x811A, 0xBDC5, + 0x811B, 0xC384, 0x811C, 0xC385, 0x811D, 0xC386, 0x811E, 0xEBE2, 0x811F, 0xC387, 0x8120, 0xC388, 0x8121, 0xC389, 0x8122, 0xC38A, + 0x8123, 0xC38B, 0x8124, 0xC38C, 0x8125, 0xC38D, 0x8126, 0xC38E, 0x8127, 0xC38F, 0x8128, 0xC390, 0x8129, 0xC391, 0x812A, 0xC392, + 0x812B, 0xC393, 0x812C, 0xEBE3, 0x812D, 0xC394, 0x812E, 0xC395, 0x812F, 0xB8AC, 0x8130, 0xC396, 0x8131, 0xCDD1, 0x8132, 0xEBE5, + 0x8133, 0xC397, 0x8134, 0xC398, 0x8135, 0xC399, 0x8136, 0xEBE1, 0x8137, 0xC39A, 0x8138, 0xC1B3, 0x8139, 0xC39B, 0x813A, 0xC39C, + 0x813B, 0xC39D, 0x813C, 0xC39E, 0x813D, 0xC39F, 0x813E, 0xC6A2, 0x813F, 0xC3A0, 0x8140, 0xC440, 0x8141, 0xC441, 0x8142, 0xC442, + 0x8143, 0xC443, 0x8144, 0xC444, 0x8145, 0xC445, 0x8146, 0xCCF3, 0x8147, 0xC446, 0x8148, 0xEBE6, 0x8149, 0xC447, 0x814A, 0xC0B0, + 0x814B, 0xD2B8, 0x814C, 0xEBE7, 0x814D, 0xC448, 0x814E, 0xC449, 0x814F, 0xC44A, 0x8150, 0xB8AF, 0x8151, 0xB8AD, 0x8152, 0xC44B, + 0x8153, 0xEBE8, 0x8154, 0xC7BB, 0x8155, 0xCDF3, 0x8156, 0xC44C, 0x8157, 0xC44D, 0x8158, 0xC44E, 0x8159, 0xEBEA, 0x815A, 0xEBEB, + 0x815B, 0xC44F, 0x815C, 0xC450, 0x815D, 0xC451, 0x815E, 0xC452, 0x815F, 0xC453, 0x8160, 0xEBED, 0x8161, 0xC454, 0x8162, 0xC455, + 0x8163, 0xC456, 0x8164, 0xC457, 0x8165, 0xD0C8, 0x8166, 0xC458, 0x8167, 0xEBF2, 0x8168, 0xC459, 0x8169, 0xEBEE, 0x816A, 0xC45A, + 0x816B, 0xC45B, 0x816C, 0xC45C, 0x816D, 0xEBF1, 0x816E, 0xC8F9, 0x816F, 0xC45D, 0x8170, 0xD1FC, 0x8171, 0xEBEC, 0x8172, 0xC45E, + 0x8173, 0xC45F, 0x8174, 0xEBE9, 0x8175, 0xC460, 0x8176, 0xC461, 0x8177, 0xC462, 0x8178, 0xC463, 0x8179, 0xB8B9, 0x817A, 0xCFD9, + 0x817B, 0xC4E5, 0x817C, 0xEBEF, 0x817D, 0xEBF0, 0x817E, 0xCCDA, 0x817F, 0xCDC8, 0x8180, 0xB0F2, 0x8181, 0xC464, 0x8182, 0xEBF6, + 0x8183, 0xC465, 0x8184, 0xC466, 0x8185, 0xC467, 0x8186, 0xC468, 0x8187, 0xC469, 0x8188, 0xEBF5, 0x8189, 0xC46A, 0x818A, 0xB2B2, + 0x818B, 0xC46B, 0x818C, 0xC46C, 0x818D, 0xC46D, 0x818E, 0xC46E, 0x818F, 0xB8E0, 0x8190, 0xC46F, 0x8191, 0xEBF7, 0x8192, 0xC470, + 0x8193, 0xC471, 0x8194, 0xC472, 0x8195, 0xC473, 0x8196, 0xC474, 0x8197, 0xC475, 0x8198, 0xB1EC, 0x8199, 0xC476, 0x819A, 0xC477, + 0x819B, 0xCCC5, 0x819C, 0xC4A4, 0x819D, 0xCFA5, 0x819E, 0xC478, 0x819F, 0xC479, 0x81A0, 0xC47A, 0x81A1, 0xC47B, 0x81A2, 0xC47C, + 0x81A3, 0xEBF9, 0x81A4, 0xC47D, 0x81A5, 0xC47E, 0x81A6, 0xECA2, 0x81A7, 0xC480, 0x81A8, 0xC5F2, 0x81A9, 0xC481, 0x81AA, 0xEBFA, + 0x81AB, 0xC482, 0x81AC, 0xC483, 0x81AD, 0xC484, 0x81AE, 0xC485, 0x81AF, 0xC486, 0x81B0, 0xC487, 0x81B1, 0xC488, 0x81B2, 0xC489, + 0x81B3, 0xC9C5, 0x81B4, 0xC48A, 0x81B5, 0xC48B, 0x81B6, 0xC48C, 0x81B7, 0xC48D, 0x81B8, 0xC48E, 0x81B9, 0xC48F, 0x81BA, 0xE2DF, + 0x81BB, 0xEBFE, 0x81BC, 0xC490, 0x81BD, 0xC491, 0x81BE, 0xC492, 0x81BF, 0xC493, 0x81C0, 0xCDCE, 0x81C1, 0xECA1, 0x81C2, 0xB1DB, + 0x81C3, 0xD3B7, 0x81C4, 0xC494, 0x81C5, 0xC495, 0x81C6, 0xD2DC, 0x81C7, 0xC496, 0x81C8, 0xC497, 0x81C9, 0xC498, 0x81CA, 0xEBFD, + 0x81CB, 0xC499, 0x81CC, 0xEBFB, 0x81CD, 0xC49A, 0x81CE, 0xC49B, 0x81CF, 0xC49C, 0x81D0, 0xC49D, 0x81D1, 0xC49E, 0x81D2, 0xC49F, + 0x81D3, 0xC4A0, 0x81D4, 0xC540, 0x81D5, 0xC541, 0x81D6, 0xC542, 0x81D7, 0xC543, 0x81D8, 0xC544, 0x81D9, 0xC545, 0x81DA, 0xC546, + 0x81DB, 0xC547, 0x81DC, 0xC548, 0x81DD, 0xC549, 0x81DE, 0xC54A, 0x81DF, 0xC54B, 0x81E0, 0xC54C, 0x81E1, 0xC54D, 0x81E2, 0xC54E, + 0x81E3, 0xB3BC, 0x81E4, 0xC54F, 0x81E5, 0xC550, 0x81E6, 0xC551, 0x81E7, 0xEAB0, 0x81E8, 0xC552, 0x81E9, 0xC553, 0x81EA, 0xD7D4, + 0x81EB, 0xC554, 0x81EC, 0xF4AB, 0x81ED, 0xB3F4, 0x81EE, 0xC555, 0x81EF, 0xC556, 0x81F0, 0xC557, 0x81F1, 0xC558, 0x81F2, 0xC559, + 0x81F3, 0xD6C1, 0x81F4, 0xD6C2, 0x81F5, 0xC55A, 0x81F6, 0xC55B, 0x81F7, 0xC55C, 0x81F8, 0xC55D, 0x81F9, 0xC55E, 0x81FA, 0xC55F, + 0x81FB, 0xD5E9, 0x81FC, 0xBECA, 0x81FD, 0xC560, 0x81FE, 0xF4A7, 0x81FF, 0xC561, 0x8200, 0xD2A8, 0x8201, 0xF4A8, 0x8202, 0xF4A9, + 0x8203, 0xC562, 0x8204, 0xF4AA, 0x8205, 0xBECB, 0x8206, 0xD3DF, 0x8207, 0xC563, 0x8208, 0xC564, 0x8209, 0xC565, 0x820A, 0xC566, + 0x820B, 0xC567, 0x820C, 0xC9E0, 0x820D, 0xC9E1, 0x820E, 0xC568, 0x820F, 0xC569, 0x8210, 0xF3C2, 0x8211, 0xC56A, 0x8212, 0xCAE6, + 0x8213, 0xC56B, 0x8214, 0xCCF2, 0x8215, 0xC56C, 0x8216, 0xC56D, 0x8217, 0xC56E, 0x8218, 0xC56F, 0x8219, 0xC570, 0x821A, 0xC571, + 0x821B, 0xE2B6, 0x821C, 0xCBB4, 0x821D, 0xC572, 0x821E, 0xCEE8, 0x821F, 0xD6DB, 0x8220, 0xC573, 0x8221, 0xF4AD, 0x8222, 0xF4AE, + 0x8223, 0xF4AF, 0x8224, 0xC574, 0x8225, 0xC575, 0x8226, 0xC576, 0x8227, 0xC577, 0x8228, 0xF4B2, 0x8229, 0xC578, 0x822A, 0xBABD, + 0x822B, 0xF4B3, 0x822C, 0xB0E3, 0x822D, 0xF4B0, 0x822E, 0xC579, 0x822F, 0xF4B1, 0x8230, 0xBDA2, 0x8231, 0xB2D5, 0x8232, 0xC57A, + 0x8233, 0xF4B6, 0x8234, 0xF4B7, 0x8235, 0xB6E6, 0x8236, 0xB2B0, 0x8237, 0xCFCF, 0x8238, 0xF4B4, 0x8239, 0xB4AC, 0x823A, 0xC57B, + 0x823B, 0xF4B5, 0x823C, 0xC57C, 0x823D, 0xC57D, 0x823E, 0xF4B8, 0x823F, 0xC57E, 0x8240, 0xC580, 0x8241, 0xC581, 0x8242, 0xC582, + 0x8243, 0xC583, 0x8244, 0xF4B9, 0x8245, 0xC584, 0x8246, 0xC585, 0x8247, 0xCDA7, 0x8248, 0xC586, 0x8249, 0xF4BA, 0x824A, 0xC587, + 0x824B, 0xF4BB, 0x824C, 0xC588, 0x824D, 0xC589, 0x824E, 0xC58A, 0x824F, 0xF4BC, 0x8250, 0xC58B, 0x8251, 0xC58C, 0x8252, 0xC58D, + 0x8253, 0xC58E, 0x8254, 0xC58F, 0x8255, 0xC590, 0x8256, 0xC591, 0x8257, 0xC592, 0x8258, 0xCBD2, 0x8259, 0xC593, 0x825A, 0xF4BD, + 0x825B, 0xC594, 0x825C, 0xC595, 0x825D, 0xC596, 0x825E, 0xC597, 0x825F, 0xF4BE, 0x8260, 0xC598, 0x8261, 0xC599, 0x8262, 0xC59A, + 0x8263, 0xC59B, 0x8264, 0xC59C, 0x8265, 0xC59D, 0x8266, 0xC59E, 0x8267, 0xC59F, 0x8268, 0xF4BF, 0x8269, 0xC5A0, 0x826A, 0xC640, + 0x826B, 0xC641, 0x826C, 0xC642, 0x826D, 0xC643, 0x826E, 0xF4DE, 0x826F, 0xC1BC, 0x8270, 0xBCE8, 0x8271, 0xC644, 0x8272, 0xC9AB, + 0x8273, 0xD1DE, 0x8274, 0xE5F5, 0x8275, 0xC645, 0x8276, 0xC646, 0x8277, 0xC647, 0x8278, 0xC648, 0x8279, 0xDCB3, 0x827A, 0xD2D5, + 0x827B, 0xC649, 0x827C, 0xC64A, 0x827D, 0xDCB4, 0x827E, 0xB0AC, 0x827F, 0xDCB5, 0x8280, 0xC64B, 0x8281, 0xC64C, 0x8282, 0xBDDA, + 0x8283, 0xC64D, 0x8284, 0xDCB9, 0x8285, 0xC64E, 0x8286, 0xC64F, 0x8287, 0xC650, 0x8288, 0xD8C2, 0x8289, 0xC651, 0x828A, 0xDCB7, + 0x828B, 0xD3F3, 0x828C, 0xC652, 0x828D, 0xC9D6, 0x828E, 0xDCBA, 0x828F, 0xDCB6, 0x8290, 0xC653, 0x8291, 0xDCBB, 0x8292, 0xC3A2, + 0x8293, 0xC654, 0x8294, 0xC655, 0x8295, 0xC656, 0x8296, 0xC657, 0x8297, 0xDCBC, 0x8298, 0xDCC5, 0x8299, 0xDCBD, 0x829A, 0xC658, + 0x829B, 0xC659, 0x829C, 0xCEDF, 0x829D, 0xD6A5, 0x829E, 0xC65A, 0x829F, 0xDCCF, 0x82A0, 0xC65B, 0x82A1, 0xDCCD, 0x82A2, 0xC65C, + 0x82A3, 0xC65D, 0x82A4, 0xDCD2, 0x82A5, 0xBDE6, 0x82A6, 0xC2AB, 0x82A7, 0xC65E, 0x82A8, 0xDCB8, 0x82A9, 0xDCCB, 0x82AA, 0xDCCE, + 0x82AB, 0xDCBE, 0x82AC, 0xB7D2, 0x82AD, 0xB0C5, 0x82AE, 0xDCC7, 0x82AF, 0xD0BE, 0x82B0, 0xDCC1, 0x82B1, 0xBBA8, 0x82B2, 0xC65F, + 0x82B3, 0xB7BC, 0x82B4, 0xDCCC, 0x82B5, 0xC660, 0x82B6, 0xC661, 0x82B7, 0xDCC6, 0x82B8, 0xDCBF, 0x82B9, 0xC7DB, 0x82BA, 0xC662, + 0x82BB, 0xC663, 0x82BC, 0xC664, 0x82BD, 0xD1BF, 0x82BE, 0xDCC0, 0x82BF, 0xC665, 0x82C0, 0xC666, 0x82C1, 0xDCCA, 0x82C2, 0xC667, + 0x82C3, 0xC668, 0x82C4, 0xDCD0, 0x82C5, 0xC669, 0x82C6, 0xC66A, 0x82C7, 0xCEAD, 0x82C8, 0xDCC2, 0x82C9, 0xC66B, 0x82CA, 0xDCC3, + 0x82CB, 0xDCC8, 0x82CC, 0xDCC9, 0x82CD, 0xB2D4, 0x82CE, 0xDCD1, 0x82CF, 0xCBD5, 0x82D0, 0xC66C, 0x82D1, 0xD4B7, 0x82D2, 0xDCDB, + 0x82D3, 0xDCDF, 0x82D4, 0xCCA6, 0x82D5, 0xDCE6, 0x82D6, 0xC66D, 0x82D7, 0xC3E7, 0x82D8, 0xDCDC, 0x82D9, 0xC66E, 0x82DA, 0xC66F, + 0x82DB, 0xBFC1, 0x82DC, 0xDCD9, 0x82DD, 0xC670, 0x82DE, 0xB0FA, 0x82DF, 0xB9B6, 0x82E0, 0xDCE5, 0x82E1, 0xDCD3, 0x82E2, 0xC671, + 0x82E3, 0xDCC4, 0x82E4, 0xDCD6, 0x82E5, 0xC8F4, 0x82E6, 0xBFE0, 0x82E7, 0xC672, 0x82E8, 0xC673, 0x82E9, 0xC674, 0x82EA, 0xC675, + 0x82EB, 0xC9BB, 0x82EC, 0xC676, 0x82ED, 0xC677, 0x82EE, 0xC678, 0x82EF, 0xB1BD, 0x82F0, 0xC679, 0x82F1, 0xD3A2, 0x82F2, 0xC67A, + 0x82F3, 0xC67B, 0x82F4, 0xDCDA, 0x82F5, 0xC67C, 0x82F6, 0xC67D, 0x82F7, 0xDCD5, 0x82F8, 0xC67E, 0x82F9, 0xC6BB, 0x82FA, 0xC680, + 0x82FB, 0xDCDE, 0x82FC, 0xC681, 0x82FD, 0xC682, 0x82FE, 0xC683, 0x82FF, 0xC684, 0x8300, 0xC685, 0x8301, 0xD7C2, 0x8302, 0xC3AF, + 0x8303, 0xB7B6, 0x8304, 0xC7D1, 0x8305, 0xC3A9, 0x8306, 0xDCE2, 0x8307, 0xDCD8, 0x8308, 0xDCEB, 0x8309, 0xDCD4, 0x830A, 0xC686, + 0x830B, 0xC687, 0x830C, 0xDCDD, 0x830D, 0xC688, 0x830E, 0xBEA5, 0x830F, 0xDCD7, 0x8310, 0xC689, 0x8311, 0xDCE0, 0x8312, 0xC68A, + 0x8313, 0xC68B, 0x8314, 0xDCE3, 0x8315, 0xDCE4, 0x8316, 0xC68C, 0x8317, 0xDCF8, 0x8318, 0xC68D, 0x8319, 0xC68E, 0x831A, 0xDCE1, + 0x831B, 0xDDA2, 0x831C, 0xDCE7, 0x831D, 0xC68F, 0x831E, 0xC690, 0x831F, 0xC691, 0x8320, 0xC692, 0x8321, 0xC693, 0x8322, 0xC694, + 0x8323, 0xC695, 0x8324, 0xC696, 0x8325, 0xC697, 0x8326, 0xC698, 0x8327, 0xBCEB, 0x8328, 0xB4C4, 0x8329, 0xC699, 0x832A, 0xC69A, + 0x832B, 0xC3A3, 0x832C, 0xB2E7, 0x832D, 0xDCFA, 0x832E, 0xC69B, 0x832F, 0xDCF2, 0x8330, 0xC69C, 0x8331, 0xDCEF, 0x8332, 0xC69D, + 0x8333, 0xDCFC, 0x8334, 0xDCEE, 0x8335, 0xD2F0, 0x8336, 0xB2E8, 0x8337, 0xC69E, 0x8338, 0xC8D7, 0x8339, 0xC8E3, 0x833A, 0xDCFB, + 0x833B, 0xC69F, 0x833C, 0xDCED, 0x833D, 0xC6A0, 0x833E, 0xC740, 0x833F, 0xC741, 0x8340, 0xDCF7, 0x8341, 0xC742, 0x8342, 0xC743, + 0x8343, 0xDCF5, 0x8344, 0xC744, 0x8345, 0xC745, 0x8346, 0xBEA3, 0x8347, 0xDCF4, 0x8348, 0xC746, 0x8349, 0xB2DD, 0x834A, 0xC747, + 0x834B, 0xC748, 0x834C, 0xC749, 0x834D, 0xC74A, 0x834E, 0xC74B, 0x834F, 0xDCF3, 0x8350, 0xBCF6, 0x8351, 0xDCE8, 0x8352, 0xBBC4, + 0x8353, 0xC74C, 0x8354, 0xC0F3, 0x8355, 0xC74D, 0x8356, 0xC74E, 0x8357, 0xC74F, 0x8358, 0xC750, 0x8359, 0xC751, 0x835A, 0xBCD4, + 0x835B, 0xDCE9, 0x835C, 0xDCEA, 0x835D, 0xC752, 0x835E, 0xDCF1, 0x835F, 0xDCF6, 0x8360, 0xDCF9, 0x8361, 0xB5B4, 0x8362, 0xC753, + 0x8363, 0xC8D9, 0x8364, 0xBBE7, 0x8365, 0xDCFE, 0x8366, 0xDCFD, 0x8367, 0xD3AB, 0x8368, 0xDDA1, 0x8369, 0xDDA3, 0x836A, 0xDDA5, + 0x836B, 0xD2F1, 0x836C, 0xDDA4, 0x836D, 0xDDA6, 0x836E, 0xDDA7, 0x836F, 0xD2A9, 0x8370, 0xC754, 0x8371, 0xC755, 0x8372, 0xC756, + 0x8373, 0xC757, 0x8374, 0xC758, 0x8375, 0xC759, 0x8376, 0xC75A, 0x8377, 0xBAC9, 0x8378, 0xDDA9, 0x8379, 0xC75B, 0x837A, 0xC75C, + 0x837B, 0xDDB6, 0x837C, 0xDDB1, 0x837D, 0xDDB4, 0x837E, 0xC75D, 0x837F, 0xC75E, 0x8380, 0xC75F, 0x8381, 0xC760, 0x8382, 0xC761, + 0x8383, 0xC762, 0x8384, 0xC763, 0x8385, 0xDDB0, 0x8386, 0xC6CE, 0x8387, 0xC764, 0x8388, 0xC765, 0x8389, 0xC0F2, 0x838A, 0xC766, + 0x838B, 0xC767, 0x838C, 0xC768, 0x838D, 0xC769, 0x838E, 0xC9AF, 0x838F, 0xC76A, 0x8390, 0xC76B, 0x8391, 0xC76C, 0x8392, 0xDCEC, + 0x8393, 0xDDAE, 0x8394, 0xC76D, 0x8395, 0xC76E, 0x8396, 0xC76F, 0x8397, 0xC770, 0x8398, 0xDDB7, 0x8399, 0xC771, 0x839A, 0xC772, + 0x839B, 0xDCF0, 0x839C, 0xDDAF, 0x839D, 0xC773, 0x839E, 0xDDB8, 0x839F, 0xC774, 0x83A0, 0xDDAC, 0x83A1, 0xC775, 0x83A2, 0xC776, + 0x83A3, 0xC777, 0x83A4, 0xC778, 0x83A5, 0xC779, 0x83A6, 0xC77A, 0x83A7, 0xC77B, 0x83A8, 0xDDB9, 0x83A9, 0xDDB3, 0x83AA, 0xDDAD, + 0x83AB, 0xC4AA, 0x83AC, 0xC77C, 0x83AD, 0xC77D, 0x83AE, 0xC77E, 0x83AF, 0xC780, 0x83B0, 0xDDA8, 0x83B1, 0xC0B3, 0x83B2, 0xC1AB, + 0x83B3, 0xDDAA, 0x83B4, 0xDDAB, 0x83B5, 0xC781, 0x83B6, 0xDDB2, 0x83B7, 0xBBF1, 0x83B8, 0xDDB5, 0x83B9, 0xD3A8, 0x83BA, 0xDDBA, + 0x83BB, 0xC782, 0x83BC, 0xDDBB, 0x83BD, 0xC3A7, 0x83BE, 0xC783, 0x83BF, 0xC784, 0x83C0, 0xDDD2, 0x83C1, 0xDDBC, 0x83C2, 0xC785, + 0x83C3, 0xC786, 0x83C4, 0xC787, 0x83C5, 0xDDD1, 0x83C6, 0xC788, 0x83C7, 0xB9BD, 0x83C8, 0xC789, 0x83C9, 0xC78A, 0x83CA, 0xBED5, + 0x83CB, 0xC78B, 0x83CC, 0xBEFA, 0x83CD, 0xC78C, 0x83CE, 0xC78D, 0x83CF, 0xBACA, 0x83D0, 0xC78E, 0x83D1, 0xC78F, 0x83D2, 0xC790, + 0x83D3, 0xC791, 0x83D4, 0xDDCA, 0x83D5, 0xC792, 0x83D6, 0xDDC5, 0x83D7, 0xC793, 0x83D8, 0xDDBF, 0x83D9, 0xC794, 0x83DA, 0xC795, + 0x83DB, 0xC796, 0x83DC, 0xB2CB, 0x83DD, 0xDDC3, 0x83DE, 0xC797, 0x83DF, 0xDDCB, 0x83E0, 0xB2A4, 0x83E1, 0xDDD5, 0x83E2, 0xC798, + 0x83E3, 0xC799, 0x83E4, 0xC79A, 0x83E5, 0xDDBE, 0x83E6, 0xC79B, 0x83E7, 0xC79C, 0x83E8, 0xC79D, 0x83E9, 0xC6D0, 0x83EA, 0xDDD0, + 0x83EB, 0xC79E, 0x83EC, 0xC79F, 0x83ED, 0xC7A0, 0x83EE, 0xC840, 0x83EF, 0xC841, 0x83F0, 0xDDD4, 0x83F1, 0xC1E2, 0x83F2, 0xB7C6, + 0x83F3, 0xC842, 0x83F4, 0xC843, 0x83F5, 0xC844, 0x83F6, 0xC845, 0x83F7, 0xC846, 0x83F8, 0xDDCE, 0x83F9, 0xDDCF, 0x83FA, 0xC847, + 0x83FB, 0xC848, 0x83FC, 0xC849, 0x83FD, 0xDDC4, 0x83FE, 0xC84A, 0x83FF, 0xC84B, 0x8400, 0xC84C, 0x8401, 0xDDBD, 0x8402, 0xC84D, + 0x8403, 0xDDCD, 0x8404, 0xCCD1, 0x8405, 0xC84E, 0x8406, 0xDDC9, 0x8407, 0xC84F, 0x8408, 0xC850, 0x8409, 0xC851, 0x840A, 0xC852, + 0x840B, 0xDDC2, 0x840C, 0xC3C8, 0x840D, 0xC6BC, 0x840E, 0xCEAE, 0x840F, 0xDDCC, 0x8410, 0xC853, 0x8411, 0xDDC8, 0x8412, 0xC854, + 0x8413, 0xC855, 0x8414, 0xC856, 0x8415, 0xC857, 0x8416, 0xC858, 0x8417, 0xC859, 0x8418, 0xDDC1, 0x8419, 0xC85A, 0x841A, 0xC85B, + 0x841B, 0xC85C, 0x841C, 0xDDC6, 0x841D, 0xC2DC, 0x841E, 0xC85D, 0x841F, 0xC85E, 0x8420, 0xC85F, 0x8421, 0xC860, 0x8422, 0xC861, + 0x8423, 0xC862, 0x8424, 0xD3A9, 0x8425, 0xD3AA, 0x8426, 0xDDD3, 0x8427, 0xCFF4, 0x8428, 0xC8F8, 0x8429, 0xC863, 0x842A, 0xC864, + 0x842B, 0xC865, 0x842C, 0xC866, 0x842D, 0xC867, 0x842E, 0xC868, 0x842F, 0xC869, 0x8430, 0xC86A, 0x8431, 0xDDE6, 0x8432, 0xC86B, + 0x8433, 0xC86C, 0x8434, 0xC86D, 0x8435, 0xC86E, 0x8436, 0xC86F, 0x8437, 0xC870, 0x8438, 0xDDC7, 0x8439, 0xC871, 0x843A, 0xC872, + 0x843B, 0xC873, 0x843C, 0xDDE0, 0x843D, 0xC2E4, 0x843E, 0xC874, 0x843F, 0xC875, 0x8440, 0xC876, 0x8441, 0xC877, 0x8442, 0xC878, + 0x8443, 0xC879, 0x8444, 0xC87A, 0x8445, 0xC87B, 0x8446, 0xDDE1, 0x8447, 0xC87C, 0x8448, 0xC87D, 0x8449, 0xC87E, 0x844A, 0xC880, + 0x844B, 0xC881, 0x844C, 0xC882, 0x844D, 0xC883, 0x844E, 0xC884, 0x844F, 0xC885, 0x8450, 0xC886, 0x8451, 0xDDD7, 0x8452, 0xC887, + 0x8453, 0xC888, 0x8454, 0xC889, 0x8455, 0xC88A, 0x8456, 0xC88B, 0x8457, 0xD6F8, 0x8458, 0xC88C, 0x8459, 0xDDD9, 0x845A, 0xDDD8, + 0x845B, 0xB8F0, 0x845C, 0xDDD6, 0x845D, 0xC88D, 0x845E, 0xC88E, 0x845F, 0xC88F, 0x8460, 0xC890, 0x8461, 0xC6CF, 0x8462, 0xC891, + 0x8463, 0xB6AD, 0x8464, 0xC892, 0x8465, 0xC893, 0x8466, 0xC894, 0x8467, 0xC895, 0x8468, 0xC896, 0x8469, 0xDDE2, 0x846A, 0xC897, + 0x846B, 0xBAF9, 0x846C, 0xD4E1, 0x846D, 0xDDE7, 0x846E, 0xC898, 0x846F, 0xC899, 0x8470, 0xC89A, 0x8471, 0xB4D0, 0x8472, 0xC89B, + 0x8473, 0xDDDA, 0x8474, 0xC89C, 0x8475, 0xBFFB, 0x8476, 0xDDE3, 0x8477, 0xC89D, 0x8478, 0xDDDF, 0x8479, 0xC89E, 0x847A, 0xDDDD, + 0x847B, 0xC89F, 0x847C, 0xC8A0, 0x847D, 0xC940, 0x847E, 0xC941, 0x847F, 0xC942, 0x8480, 0xC943, 0x8481, 0xC944, 0x8482, 0xB5D9, + 0x8483, 0xC945, 0x8484, 0xC946, 0x8485, 0xC947, 0x8486, 0xC948, 0x8487, 0xDDDB, 0x8488, 0xDDDC, 0x8489, 0xDDDE, 0x848A, 0xC949, + 0x848B, 0xBDAF, 0x848C, 0xDDE4, 0x848D, 0xC94A, 0x848E, 0xDDE5, 0x848F, 0xC94B, 0x8490, 0xC94C, 0x8491, 0xC94D, 0x8492, 0xC94E, + 0x8493, 0xC94F, 0x8494, 0xC950, 0x8495, 0xC951, 0x8496, 0xC952, 0x8497, 0xDDF5, 0x8498, 0xC953, 0x8499, 0xC3C9, 0x849A, 0xC954, + 0x849B, 0xC955, 0x849C, 0xCBE2, 0x849D, 0xC956, 0x849E, 0xC957, 0x849F, 0xC958, 0x84A0, 0xC959, 0x84A1, 0xDDF2, 0x84A2, 0xC95A, + 0x84A3, 0xC95B, 0x84A4, 0xC95C, 0x84A5, 0xC95D, 0x84A6, 0xC95E, 0x84A7, 0xC95F, 0x84A8, 0xC960, 0x84A9, 0xC961, 0x84AA, 0xC962, + 0x84AB, 0xC963, 0x84AC, 0xC964, 0x84AD, 0xC965, 0x84AE, 0xC966, 0x84AF, 0xD8E1, 0x84B0, 0xC967, 0x84B1, 0xC968, 0x84B2, 0xC6D1, + 0x84B3, 0xC969, 0x84B4, 0xDDF4, 0x84B5, 0xC96A, 0x84B6, 0xC96B, 0x84B7, 0xC96C, 0x84B8, 0xD5F4, 0x84B9, 0xDDF3, 0x84BA, 0xDDF0, + 0x84BB, 0xC96D, 0x84BC, 0xC96E, 0x84BD, 0xDDEC, 0x84BE, 0xC96F, 0x84BF, 0xDDEF, 0x84C0, 0xC970, 0x84C1, 0xDDE8, 0x84C2, 0xC971, + 0x84C3, 0xC972, 0x84C4, 0xD0EE, 0x84C5, 0xC973, 0x84C6, 0xC974, 0x84C7, 0xC975, 0x84C8, 0xC976, 0x84C9, 0xC8D8, 0x84CA, 0xDDEE, + 0x84CB, 0xC977, 0x84CC, 0xC978, 0x84CD, 0xDDE9, 0x84CE, 0xC979, 0x84CF, 0xC97A, 0x84D0, 0xDDEA, 0x84D1, 0xCBF2, 0x84D2, 0xC97B, + 0x84D3, 0xDDED, 0x84D4, 0xC97C, 0x84D5, 0xC97D, 0x84D6, 0xB1CD, 0x84D7, 0xC97E, 0x84D8, 0xC980, 0x84D9, 0xC981, 0x84DA, 0xC982, + 0x84DB, 0xC983, 0x84DC, 0xC984, 0x84DD, 0xC0B6, 0x84DE, 0xC985, 0x84DF, 0xBCBB, 0x84E0, 0xDDF1, 0x84E1, 0xC986, 0x84E2, 0xC987, + 0x84E3, 0xDDF7, 0x84E4, 0xC988, 0x84E5, 0xDDF6, 0x84E6, 0xDDEB, 0x84E7, 0xC989, 0x84E8, 0xC98A, 0x84E9, 0xC98B, 0x84EA, 0xC98C, + 0x84EB, 0xC98D, 0x84EC, 0xC5EE, 0x84ED, 0xC98E, 0x84EE, 0xC98F, 0x84EF, 0xC990, 0x84F0, 0xDDFB, 0x84F1, 0xC991, 0x84F2, 0xC992, + 0x84F3, 0xC993, 0x84F4, 0xC994, 0x84F5, 0xC995, 0x84F6, 0xC996, 0x84F7, 0xC997, 0x84F8, 0xC998, 0x84F9, 0xC999, 0x84FA, 0xC99A, + 0x84FB, 0xC99B, 0x84FC, 0xDEA4, 0x84FD, 0xC99C, 0x84FE, 0xC99D, 0x84FF, 0xDEA3, 0x8500, 0xC99E, 0x8501, 0xC99F, 0x8502, 0xC9A0, + 0x8503, 0xCA40, 0x8504, 0xCA41, 0x8505, 0xCA42, 0x8506, 0xCA43, 0x8507, 0xCA44, 0x8508, 0xCA45, 0x8509, 0xCA46, 0x850A, 0xCA47, + 0x850B, 0xCA48, 0x850C, 0xDDF8, 0x850D, 0xCA49, 0x850E, 0xCA4A, 0x850F, 0xCA4B, 0x8510, 0xCA4C, 0x8511, 0xC3EF, 0x8512, 0xCA4D, + 0x8513, 0xC2FB, 0x8514, 0xCA4E, 0x8515, 0xCA4F, 0x8516, 0xCA50, 0x8517, 0xD5E1, 0x8518, 0xCA51, 0x8519, 0xCA52, 0x851A, 0xCEB5, + 0x851B, 0xCA53, 0x851C, 0xCA54, 0x851D, 0xCA55, 0x851E, 0xCA56, 0x851F, 0xDDFD, 0x8520, 0xCA57, 0x8521, 0xB2CC, 0x8522, 0xCA58, + 0x8523, 0xCA59, 0x8524, 0xCA5A, 0x8525, 0xCA5B, 0x8526, 0xCA5C, 0x8527, 0xCA5D, 0x8528, 0xCA5E, 0x8529, 0xCA5F, 0x852A, 0xCA60, + 0x852B, 0xC4E8, 0x852C, 0xCADF, 0x852D, 0xCA61, 0x852E, 0xCA62, 0x852F, 0xCA63, 0x8530, 0xCA64, 0x8531, 0xCA65, 0x8532, 0xCA66, + 0x8533, 0xCA67, 0x8534, 0xCA68, 0x8535, 0xCA69, 0x8536, 0xCA6A, 0x8537, 0xC7BE, 0x8538, 0xDDFA, 0x8539, 0xDDFC, 0x853A, 0xDDFE, + 0x853B, 0xDEA2, 0x853C, 0xB0AA, 0x853D, 0xB1CE, 0x853E, 0xCA6B, 0x853F, 0xCA6C, 0x8540, 0xCA6D, 0x8541, 0xCA6E, 0x8542, 0xCA6F, + 0x8543, 0xDEAC, 0x8544, 0xCA70, 0x8545, 0xCA71, 0x8546, 0xCA72, 0x8547, 0xCA73, 0x8548, 0xDEA6, 0x8549, 0xBDB6, 0x854A, 0xC8EF, + 0x854B, 0xCA74, 0x854C, 0xCA75, 0x854D, 0xCA76, 0x854E, 0xCA77, 0x854F, 0xCA78, 0x8550, 0xCA79, 0x8551, 0xCA7A, 0x8552, 0xCA7B, + 0x8553, 0xCA7C, 0x8554, 0xCA7D, 0x8555, 0xCA7E, 0x8556, 0xDEA1, 0x8557, 0xCA80, 0x8558, 0xCA81, 0x8559, 0xDEA5, 0x855A, 0xCA82, + 0x855B, 0xCA83, 0x855C, 0xCA84, 0x855D, 0xCA85, 0x855E, 0xDEA9, 0x855F, 0xCA86, 0x8560, 0xCA87, 0x8561, 0xCA88, 0x8562, 0xCA89, + 0x8563, 0xCA8A, 0x8564, 0xDEA8, 0x8565, 0xCA8B, 0x8566, 0xCA8C, 0x8567, 0xCA8D, 0x8568, 0xDEA7, 0x8569, 0xCA8E, 0x856A, 0xCA8F, + 0x856B, 0xCA90, 0x856C, 0xCA91, 0x856D, 0xCA92, 0x856E, 0xCA93, 0x856F, 0xCA94, 0x8570, 0xCA95, 0x8571, 0xCA96, 0x8572, 0xDEAD, + 0x8573, 0xCA97, 0x8574, 0xD4CC, 0x8575, 0xCA98, 0x8576, 0xCA99, 0x8577, 0xCA9A, 0x8578, 0xCA9B, 0x8579, 0xDEB3, 0x857A, 0xDEAA, + 0x857B, 0xDEAE, 0x857C, 0xCA9C, 0x857D, 0xCA9D, 0x857E, 0xC0D9, 0x857F, 0xCA9E, 0x8580, 0xCA9F, 0x8581, 0xCAA0, 0x8582, 0xCB40, + 0x8583, 0xCB41, 0x8584, 0xB1A1, 0x8585, 0xDEB6, 0x8586, 0xCB42, 0x8587, 0xDEB1, 0x8588, 0xCB43, 0x8589, 0xCB44, 0x858A, 0xCB45, + 0x858B, 0xCB46, 0x858C, 0xCB47, 0x858D, 0xCB48, 0x858E, 0xCB49, 0x858F, 0xDEB2, 0x8590, 0xCB4A, 0x8591, 0xCB4B, 0x8592, 0xCB4C, + 0x8593, 0xCB4D, 0x8594, 0xCB4E, 0x8595, 0xCB4F, 0x8596, 0xCB50, 0x8597, 0xCB51, 0x8598, 0xCB52, 0x8599, 0xCB53, 0x859A, 0xCB54, + 0x859B, 0xD1A6, 0x859C, 0xDEB5, 0x859D, 0xCB55, 0x859E, 0xCB56, 0x859F, 0xCB57, 0x85A0, 0xCB58, 0x85A1, 0xCB59, 0x85A2, 0xCB5A, + 0x85A3, 0xCB5B, 0x85A4, 0xDEAF, 0x85A5, 0xCB5C, 0x85A6, 0xCB5D, 0x85A7, 0xCB5E, 0x85A8, 0xDEB0, 0x85A9, 0xCB5F, 0x85AA, 0xD0BD, + 0x85AB, 0xCB60, 0x85AC, 0xCB61, 0x85AD, 0xCB62, 0x85AE, 0xDEB4, 0x85AF, 0xCAED, 0x85B0, 0xDEB9, 0x85B1, 0xCB63, 0x85B2, 0xCB64, + 0x85B3, 0xCB65, 0x85B4, 0xCB66, 0x85B5, 0xCB67, 0x85B6, 0xCB68, 0x85B7, 0xDEB8, 0x85B8, 0xCB69, 0x85B9, 0xDEB7, 0x85BA, 0xCB6A, + 0x85BB, 0xCB6B, 0x85BC, 0xCB6C, 0x85BD, 0xCB6D, 0x85BE, 0xCB6E, 0x85BF, 0xCB6F, 0x85C0, 0xCB70, 0x85C1, 0xDEBB, 0x85C2, 0xCB71, + 0x85C3, 0xCB72, 0x85C4, 0xCB73, 0x85C5, 0xCB74, 0x85C6, 0xCB75, 0x85C7, 0xCB76, 0x85C8, 0xCB77, 0x85C9, 0xBDE5, 0x85CA, 0xCB78, + 0x85CB, 0xCB79, 0x85CC, 0xCB7A, 0x85CD, 0xCB7B, 0x85CE, 0xCB7C, 0x85CF, 0xB2D8, 0x85D0, 0xC3EA, 0x85D1, 0xCB7D, 0x85D2, 0xCB7E, + 0x85D3, 0xDEBA, 0x85D4, 0xCB80, 0x85D5, 0xC5BA, 0x85D6, 0xCB81, 0x85D7, 0xCB82, 0x85D8, 0xCB83, 0x85D9, 0xCB84, 0x85DA, 0xCB85, + 0x85DB, 0xCB86, 0x85DC, 0xDEBC, 0x85DD, 0xCB87, 0x85DE, 0xCB88, 0x85DF, 0xCB89, 0x85E0, 0xCB8A, 0x85E1, 0xCB8B, 0x85E2, 0xCB8C, + 0x85E3, 0xCB8D, 0x85E4, 0xCCD9, 0x85E5, 0xCB8E, 0x85E6, 0xCB8F, 0x85E7, 0xCB90, 0x85E8, 0xCB91, 0x85E9, 0xB7AA, 0x85EA, 0xCB92, + 0x85EB, 0xCB93, 0x85EC, 0xCB94, 0x85ED, 0xCB95, 0x85EE, 0xCB96, 0x85EF, 0xCB97, 0x85F0, 0xCB98, 0x85F1, 0xCB99, 0x85F2, 0xCB9A, + 0x85F3, 0xCB9B, 0x85F4, 0xCB9C, 0x85F5, 0xCB9D, 0x85F6, 0xCB9E, 0x85F7, 0xCB9F, 0x85F8, 0xCBA0, 0x85F9, 0xCC40, 0x85FA, 0xCC41, + 0x85FB, 0xD4E5, 0x85FC, 0xCC42, 0x85FD, 0xCC43, 0x85FE, 0xCC44, 0x85FF, 0xDEBD, 0x8600, 0xCC45, 0x8601, 0xCC46, 0x8602, 0xCC47, + 0x8603, 0xCC48, 0x8604, 0xCC49, 0x8605, 0xDEBF, 0x8606, 0xCC4A, 0x8607, 0xCC4B, 0x8608, 0xCC4C, 0x8609, 0xCC4D, 0x860A, 0xCC4E, + 0x860B, 0xCC4F, 0x860C, 0xCC50, 0x860D, 0xCC51, 0x860E, 0xCC52, 0x860F, 0xCC53, 0x8610, 0xCC54, 0x8611, 0xC4A2, 0x8612, 0xCC55, + 0x8613, 0xCC56, 0x8614, 0xCC57, 0x8615, 0xCC58, 0x8616, 0xDEC1, 0x8617, 0xCC59, 0x8618, 0xCC5A, 0x8619, 0xCC5B, 0x861A, 0xCC5C, + 0x861B, 0xCC5D, 0x861C, 0xCC5E, 0x861D, 0xCC5F, 0x861E, 0xCC60, 0x861F, 0xCC61, 0x8620, 0xCC62, 0x8621, 0xCC63, 0x8622, 0xCC64, + 0x8623, 0xCC65, 0x8624, 0xCC66, 0x8625, 0xCC67, 0x8626, 0xCC68, 0x8627, 0xDEBE, 0x8628, 0xCC69, 0x8629, 0xDEC0, 0x862A, 0xCC6A, + 0x862B, 0xCC6B, 0x862C, 0xCC6C, 0x862D, 0xCC6D, 0x862E, 0xCC6E, 0x862F, 0xCC6F, 0x8630, 0xCC70, 0x8631, 0xCC71, 0x8632, 0xCC72, + 0x8633, 0xCC73, 0x8634, 0xCC74, 0x8635, 0xCC75, 0x8636, 0xCC76, 0x8637, 0xCC77, 0x8638, 0xD5BA, 0x8639, 0xCC78, 0x863A, 0xCC79, + 0x863B, 0xCC7A, 0x863C, 0xDEC2, 0x863D, 0xCC7B, 0x863E, 0xCC7C, 0x863F, 0xCC7D, 0x8640, 0xCC7E, 0x8641, 0xCC80, 0x8642, 0xCC81, + 0x8643, 0xCC82, 0x8644, 0xCC83, 0x8645, 0xCC84, 0x8646, 0xCC85, 0x8647, 0xCC86, 0x8648, 0xCC87, 0x8649, 0xCC88, 0x864A, 0xCC89, + 0x864B, 0xCC8A, 0x864C, 0xCC8B, 0x864D, 0xF2AE, 0x864E, 0xBBA2, 0x864F, 0xC2B2, 0x8650, 0xC5B0, 0x8651, 0xC2C7, 0x8652, 0xCC8C, + 0x8653, 0xCC8D, 0x8654, 0xF2AF, 0x8655, 0xCC8E, 0x8656, 0xCC8F, 0x8657, 0xCC90, 0x8658, 0xCC91, 0x8659, 0xCC92, 0x865A, 0xD0E9, + 0x865B, 0xCC93, 0x865C, 0xCC94, 0x865D, 0xCC95, 0x865E, 0xD3DD, 0x865F, 0xCC96, 0x8660, 0xCC97, 0x8661, 0xCC98, 0x8662, 0xEBBD, + 0x8663, 0xCC99, 0x8664, 0xCC9A, 0x8665, 0xCC9B, 0x8666, 0xCC9C, 0x8667, 0xCC9D, 0x8668, 0xCC9E, 0x8669, 0xCC9F, 0x866A, 0xCCA0, + 0x866B, 0xB3E6, 0x866C, 0xF2B0, 0x866D, 0xCD40, 0x866E, 0xF2B1, 0x866F, 0xCD41, 0x8670, 0xCD42, 0x8671, 0xCAAD, 0x8672, 0xCD43, + 0x8673, 0xCD44, 0x8674, 0xCD45, 0x8675, 0xCD46, 0x8676, 0xCD47, 0x8677, 0xCD48, 0x8678, 0xCD49, 0x8679, 0xBAE7, 0x867A, 0xF2B3, + 0x867B, 0xF2B5, 0x867C, 0xF2B4, 0x867D, 0xCBE4, 0x867E, 0xCFBA, 0x867F, 0xF2B2, 0x8680, 0xCAB4, 0x8681, 0xD2CF, 0x8682, 0xC2EC, + 0x8683, 0xCD4A, 0x8684, 0xCD4B, 0x8685, 0xCD4C, 0x8686, 0xCD4D, 0x8687, 0xCD4E, 0x8688, 0xCD4F, 0x8689, 0xCD50, 0x868A, 0xCEC3, + 0x868B, 0xF2B8, 0x868C, 0xB0F6, 0x868D, 0xF2B7, 0x868E, 0xCD51, 0x868F, 0xCD52, 0x8690, 0xCD53, 0x8691, 0xCD54, 0x8692, 0xCD55, + 0x8693, 0xF2BE, 0x8694, 0xCD56, 0x8695, 0xB2CF, 0x8696, 0xCD57, 0x8697, 0xCD58, 0x8698, 0xCD59, 0x8699, 0xCD5A, 0x869A, 0xCD5B, + 0x869B, 0xCD5C, 0x869C, 0xD1C1, 0x869D, 0xF2BA, 0x869E, 0xCD5D, 0x869F, 0xCD5E, 0x86A0, 0xCD5F, 0x86A1, 0xCD60, 0x86A2, 0xCD61, + 0x86A3, 0xF2BC, 0x86A4, 0xD4E9, 0x86A5, 0xCD62, 0x86A6, 0xCD63, 0x86A7, 0xF2BB, 0x86A8, 0xF2B6, 0x86A9, 0xF2BF, 0x86AA, 0xF2BD, + 0x86AB, 0xCD64, 0x86AC, 0xF2B9, 0x86AD, 0xCD65, 0x86AE, 0xCD66, 0x86AF, 0xF2C7, 0x86B0, 0xF2C4, 0x86B1, 0xF2C6, 0x86B2, 0xCD67, + 0x86B3, 0xCD68, 0x86B4, 0xF2CA, 0x86B5, 0xF2C2, 0x86B6, 0xF2C0, 0x86B7, 0xCD69, 0x86B8, 0xCD6A, 0x86B9, 0xCD6B, 0x86BA, 0xF2C5, + 0x86BB, 0xCD6C, 0x86BC, 0xCD6D, 0x86BD, 0xCD6E, 0x86BE, 0xCD6F, 0x86BF, 0xCD70, 0x86C0, 0xD6FB, 0x86C1, 0xCD71, 0x86C2, 0xCD72, + 0x86C3, 0xCD73, 0x86C4, 0xF2C1, 0x86C5, 0xCD74, 0x86C6, 0xC7F9, 0x86C7, 0xC9DF, 0x86C8, 0xCD75, 0x86C9, 0xF2C8, 0x86CA, 0xB9C6, + 0x86CB, 0xB5B0, 0x86CC, 0xCD76, 0x86CD, 0xCD77, 0x86CE, 0xF2C3, 0x86CF, 0xF2C9, 0x86D0, 0xF2D0, 0x86D1, 0xF2D6, 0x86D2, 0xCD78, + 0x86D3, 0xCD79, 0x86D4, 0xBBD7, 0x86D5, 0xCD7A, 0x86D6, 0xCD7B, 0x86D7, 0xCD7C, 0x86D8, 0xF2D5, 0x86D9, 0xCDDC, 0x86DA, 0xCD7D, + 0x86DB, 0xD6EB, 0x86DC, 0xCD7E, 0x86DD, 0xCD80, 0x86DE, 0xF2D2, 0x86DF, 0xF2D4, 0x86E0, 0xCD81, 0x86E1, 0xCD82, 0x86E2, 0xCD83, + 0x86E3, 0xCD84, 0x86E4, 0xB8F2, 0x86E5, 0xCD85, 0x86E6, 0xCD86, 0x86E7, 0xCD87, 0x86E8, 0xCD88, 0x86E9, 0xF2CB, 0x86EA, 0xCD89, + 0x86EB, 0xCD8A, 0x86EC, 0xCD8B, 0x86ED, 0xF2CE, 0x86EE, 0xC2F9, 0x86EF, 0xCD8C, 0x86F0, 0xD5DD, 0x86F1, 0xF2CC, 0x86F2, 0xF2CD, + 0x86F3, 0xF2CF, 0x86F4, 0xF2D3, 0x86F5, 0xCD8D, 0x86F6, 0xCD8E, 0x86F7, 0xCD8F, 0x86F8, 0xF2D9, 0x86F9, 0xD3BC, 0x86FA, 0xCD90, + 0x86FB, 0xCD91, 0x86FC, 0xCD92, 0x86FD, 0xCD93, 0x86FE, 0xB6EA, 0x86FF, 0xCD94, 0x8700, 0xCAF1, 0x8701, 0xCD95, 0x8702, 0xB7E4, + 0x8703, 0xF2D7, 0x8704, 0xCD96, 0x8705, 0xCD97, 0x8706, 0xCD98, 0x8707, 0xF2D8, 0x8708, 0xF2DA, 0x8709, 0xF2DD, 0x870A, 0xF2DB, + 0x870B, 0xCD99, 0x870C, 0xCD9A, 0x870D, 0xF2DC, 0x870E, 0xCD9B, 0x870F, 0xCD9C, 0x8710, 0xCD9D, 0x8711, 0xCD9E, 0x8712, 0xD1D1, + 0x8713, 0xF2D1, 0x8714, 0xCD9F, 0x8715, 0xCDC9, 0x8716, 0xCDA0, 0x8717, 0xCECF, 0x8718, 0xD6A9, 0x8719, 0xCE40, 0x871A, 0xF2E3, + 0x871B, 0xCE41, 0x871C, 0xC3DB, 0x871D, 0xCE42, 0x871E, 0xF2E0, 0x871F, 0xCE43, 0x8720, 0xCE44, 0x8721, 0xC0AF, 0x8722, 0xF2EC, + 0x8723, 0xF2DE, 0x8724, 0xCE45, 0x8725, 0xF2E1, 0x8726, 0xCE46, 0x8727, 0xCE47, 0x8728, 0xCE48, 0x8729, 0xF2E8, 0x872A, 0xCE49, + 0x872B, 0xCE4A, 0x872C, 0xCE4B, 0x872D, 0xCE4C, 0x872E, 0xF2E2, 0x872F, 0xCE4D, 0x8730, 0xCE4E, 0x8731, 0xF2E7, 0x8732, 0xCE4F, + 0x8733, 0xCE50, 0x8734, 0xF2E6, 0x8735, 0xCE51, 0x8736, 0xCE52, 0x8737, 0xF2E9, 0x8738, 0xCE53, 0x8739, 0xCE54, 0x873A, 0xCE55, + 0x873B, 0xF2DF, 0x873C, 0xCE56, 0x873D, 0xCE57, 0x873E, 0xF2E4, 0x873F, 0xF2EA, 0x8740, 0xCE58, 0x8741, 0xCE59, 0x8742, 0xCE5A, + 0x8743, 0xCE5B, 0x8744, 0xCE5C, 0x8745, 0xCE5D, 0x8746, 0xCE5E, 0x8747, 0xD3AC, 0x8748, 0xF2E5, 0x8749, 0xB2F5, 0x874A, 0xCE5F, + 0x874B, 0xCE60, 0x874C, 0xF2F2, 0x874D, 0xCE61, 0x874E, 0xD0AB, 0x874F, 0xCE62, 0x8750, 0xCE63, 0x8751, 0xCE64, 0x8752, 0xCE65, + 0x8753, 0xF2F5, 0x8754, 0xCE66, 0x8755, 0xCE67, 0x8756, 0xCE68, 0x8757, 0xBBC8, 0x8758, 0xCE69, 0x8759, 0xF2F9, 0x875A, 0xCE6A, + 0x875B, 0xCE6B, 0x875C, 0xCE6C, 0x875D, 0xCE6D, 0x875E, 0xCE6E, 0x875F, 0xCE6F, 0x8760, 0xF2F0, 0x8761, 0xCE70, 0x8762, 0xCE71, + 0x8763, 0xF2F6, 0x8764, 0xF2F8, 0x8765, 0xF2FA, 0x8766, 0xCE72, 0x8767, 0xCE73, 0x8768, 0xCE74, 0x8769, 0xCE75, 0x876A, 0xCE76, + 0x876B, 0xCE77, 0x876C, 0xCE78, 0x876D, 0xCE79, 0x876E, 0xF2F3, 0x876F, 0xCE7A, 0x8770, 0xF2F1, 0x8771, 0xCE7B, 0x8772, 0xCE7C, + 0x8773, 0xCE7D, 0x8774, 0xBAFB, 0x8775, 0xCE7E, 0x8776, 0xB5FB, 0x8777, 0xCE80, 0x8778, 0xCE81, 0x8779, 0xCE82, 0x877A, 0xCE83, + 0x877B, 0xF2EF, 0x877C, 0xF2F7, 0x877D, 0xF2ED, 0x877E, 0xF2EE, 0x877F, 0xCE84, 0x8780, 0xCE85, 0x8781, 0xCE86, 0x8782, 0xF2EB, + 0x8783, 0xF3A6, 0x8784, 0xCE87, 0x8785, 0xF3A3, 0x8786, 0xCE88, 0x8787, 0xCE89, 0x8788, 0xF3A2, 0x8789, 0xCE8A, 0x878A, 0xCE8B, + 0x878B, 0xF2F4, 0x878C, 0xCE8C, 0x878D, 0xC8DA, 0x878E, 0xCE8D, 0x878F, 0xCE8E, 0x8790, 0xCE8F, 0x8791, 0xCE90, 0x8792, 0xCE91, + 0x8793, 0xF2FB, 0x8794, 0xCE92, 0x8795, 0xCE93, 0x8796, 0xCE94, 0x8797, 0xF3A5, 0x8798, 0xCE95, 0x8799, 0xCE96, 0x879A, 0xCE97, + 0x879B, 0xCE98, 0x879C, 0xCE99, 0x879D, 0xCE9A, 0x879E, 0xCE9B, 0x879F, 0xC3F8, 0x87A0, 0xCE9C, 0x87A1, 0xCE9D, 0x87A2, 0xCE9E, + 0x87A3, 0xCE9F, 0x87A4, 0xCEA0, 0x87A5, 0xCF40, 0x87A6, 0xCF41, 0x87A7, 0xCF42, 0x87A8, 0xF2FD, 0x87A9, 0xCF43, 0x87AA, 0xCF44, + 0x87AB, 0xF3A7, 0x87AC, 0xF3A9, 0x87AD, 0xF3A4, 0x87AE, 0xCF45, 0x87AF, 0xF2FC, 0x87B0, 0xCF46, 0x87B1, 0xCF47, 0x87B2, 0xCF48, + 0x87B3, 0xF3AB, 0x87B4, 0xCF49, 0x87B5, 0xF3AA, 0x87B6, 0xCF4A, 0x87B7, 0xCF4B, 0x87B8, 0xCF4C, 0x87B9, 0xCF4D, 0x87BA, 0xC2DD, + 0x87BB, 0xCF4E, 0x87BC, 0xCF4F, 0x87BD, 0xF3AE, 0x87BE, 0xCF50, 0x87BF, 0xCF51, 0x87C0, 0xF3B0, 0x87C1, 0xCF52, 0x87C2, 0xCF53, + 0x87C3, 0xCF54, 0x87C4, 0xCF55, 0x87C5, 0xCF56, 0x87C6, 0xF3A1, 0x87C7, 0xCF57, 0x87C8, 0xCF58, 0x87C9, 0xCF59, 0x87CA, 0xF3B1, + 0x87CB, 0xF3AC, 0x87CC, 0xCF5A, 0x87CD, 0xCF5B, 0x87CE, 0xCF5C, 0x87CF, 0xCF5D, 0x87D0, 0xCF5E, 0x87D1, 0xF3AF, 0x87D2, 0xF2FE, + 0x87D3, 0xF3AD, 0x87D4, 0xCF5F, 0x87D5, 0xCF60, 0x87D6, 0xCF61, 0x87D7, 0xCF62, 0x87D8, 0xCF63, 0x87D9, 0xCF64, 0x87DA, 0xCF65, + 0x87DB, 0xF3B2, 0x87DC, 0xCF66, 0x87DD, 0xCF67, 0x87DE, 0xCF68, 0x87DF, 0xCF69, 0x87E0, 0xF3B4, 0x87E1, 0xCF6A, 0x87E2, 0xCF6B, + 0x87E3, 0xCF6C, 0x87E4, 0xCF6D, 0x87E5, 0xF3A8, 0x87E6, 0xCF6E, 0x87E7, 0xCF6F, 0x87E8, 0xCF70, 0x87E9, 0xCF71, 0x87EA, 0xF3B3, + 0x87EB, 0xCF72, 0x87EC, 0xCF73, 0x87ED, 0xCF74, 0x87EE, 0xF3B5, 0x87EF, 0xCF75, 0x87F0, 0xCF76, 0x87F1, 0xCF77, 0x87F2, 0xCF78, + 0x87F3, 0xCF79, 0x87F4, 0xCF7A, 0x87F5, 0xCF7B, 0x87F6, 0xCF7C, 0x87F7, 0xCF7D, 0x87F8, 0xCF7E, 0x87F9, 0xD0B7, 0x87FA, 0xCF80, + 0x87FB, 0xCF81, 0x87FC, 0xCF82, 0x87FD, 0xCF83, 0x87FE, 0xF3B8, 0x87FF, 0xCF84, 0x8800, 0xCF85, 0x8801, 0xCF86, 0x8802, 0xCF87, + 0x8803, 0xD9F9, 0x8804, 0xCF88, 0x8805, 0xCF89, 0x8806, 0xCF8A, 0x8807, 0xCF8B, 0x8808, 0xCF8C, 0x8809, 0xCF8D, 0x880A, 0xF3B9, + 0x880B, 0xCF8E, 0x880C, 0xCF8F, 0x880D, 0xCF90, 0x880E, 0xCF91, 0x880F, 0xCF92, 0x8810, 0xCF93, 0x8811, 0xCF94, 0x8812, 0xCF95, + 0x8813, 0xF3B7, 0x8814, 0xCF96, 0x8815, 0xC8E4, 0x8816, 0xF3B6, 0x8817, 0xCF97, 0x8818, 0xCF98, 0x8819, 0xCF99, 0x881A, 0xCF9A, + 0x881B, 0xF3BA, 0x881C, 0xCF9B, 0x881D, 0xCF9C, 0x881E, 0xCF9D, 0x881F, 0xCF9E, 0x8820, 0xCF9F, 0x8821, 0xF3BB, 0x8822, 0xB4C0, + 0x8823, 0xCFA0, 0x8824, 0xD040, 0x8825, 0xD041, 0x8826, 0xD042, 0x8827, 0xD043, 0x8828, 0xD044, 0x8829, 0xD045, 0x882A, 0xD046, + 0x882B, 0xD047, 0x882C, 0xD048, 0x882D, 0xD049, 0x882E, 0xD04A, 0x882F, 0xD04B, 0x8830, 0xD04C, 0x8831, 0xD04D, 0x8832, 0xEEC3, + 0x8833, 0xD04E, 0x8834, 0xD04F, 0x8835, 0xD050, 0x8836, 0xD051, 0x8837, 0xD052, 0x8838, 0xD053, 0x8839, 0xF3BC, 0x883A, 0xD054, + 0x883B, 0xD055, 0x883C, 0xF3BD, 0x883D, 0xD056, 0x883E, 0xD057, 0x883F, 0xD058, 0x8840, 0xD1AA, 0x8841, 0xD059, 0x8842, 0xD05A, + 0x8843, 0xD05B, 0x8844, 0xF4AC, 0x8845, 0xD0C6, 0x8846, 0xD05C, 0x8847, 0xD05D, 0x8848, 0xD05E, 0x8849, 0xD05F, 0x884A, 0xD060, + 0x884B, 0xD061, 0x884C, 0xD0D0, 0x884D, 0xD1DC, 0x884E, 0xD062, 0x884F, 0xD063, 0x8850, 0xD064, 0x8851, 0xD065, 0x8852, 0xD066, + 0x8853, 0xD067, 0x8854, 0xCFCE, 0x8855, 0xD068, 0x8856, 0xD069, 0x8857, 0xBDD6, 0x8858, 0xD06A, 0x8859, 0xD1C3, 0x885A, 0xD06B, + 0x885B, 0xD06C, 0x885C, 0xD06D, 0x885D, 0xD06E, 0x885E, 0xD06F, 0x885F, 0xD070, 0x8860, 0xD071, 0x8861, 0xBAE2, 0x8862, 0xE1E9, + 0x8863, 0xD2C2, 0x8864, 0xF1C2, 0x8865, 0xB2B9, 0x8866, 0xD072, 0x8867, 0xD073, 0x8868, 0xB1ED, 0x8869, 0xF1C3, 0x886A, 0xD074, + 0x886B, 0xC9C0, 0x886C, 0xB3C4, 0x886D, 0xD075, 0x886E, 0xD9F2, 0x886F, 0xD076, 0x8870, 0xCBA5, 0x8871, 0xD077, 0x8872, 0xF1C4, + 0x8873, 0xD078, 0x8874, 0xD079, 0x8875, 0xD07A, 0x8876, 0xD07B, 0x8877, 0xD6D4, 0x8878, 0xD07C, 0x8879, 0xD07D, 0x887A, 0xD07E, + 0x887B, 0xD080, 0x887C, 0xD081, 0x887D, 0xF1C5, 0x887E, 0xF4C0, 0x887F, 0xF1C6, 0x8880, 0xD082, 0x8881, 0xD4AC, 0x8882, 0xF1C7, + 0x8883, 0xD083, 0x8884, 0xB0C0, 0x8885, 0xF4C1, 0x8886, 0xD084, 0x8887, 0xD085, 0x8888, 0xF4C2, 0x8889, 0xD086, 0x888A, 0xD087, + 0x888B, 0xB4FC, 0x888C, 0xD088, 0x888D, 0xC5DB, 0x888E, 0xD089, 0x888F, 0xD08A, 0x8890, 0xD08B, 0x8891, 0xD08C, 0x8892, 0xCCBB, + 0x8893, 0xD08D, 0x8894, 0xD08E, 0x8895, 0xD08F, 0x8896, 0xD0E4, 0x8897, 0xD090, 0x8898, 0xD091, 0x8899, 0xD092, 0x889A, 0xD093, + 0x889B, 0xD094, 0x889C, 0xCDE0, 0x889D, 0xD095, 0x889E, 0xD096, 0x889F, 0xD097, 0x88A0, 0xD098, 0x88A1, 0xD099, 0x88A2, 0xF1C8, + 0x88A3, 0xD09A, 0x88A4, 0xD9F3, 0x88A5, 0xD09B, 0x88A6, 0xD09C, 0x88A7, 0xD09D, 0x88A8, 0xD09E, 0x88A9, 0xD09F, 0x88AA, 0xD0A0, + 0x88AB, 0xB1BB, 0x88AC, 0xD140, 0x88AD, 0xCFAE, 0x88AE, 0xD141, 0x88AF, 0xD142, 0x88B0, 0xD143, 0x88B1, 0xB8A4, 0x88B2, 0xD144, + 0x88B3, 0xD145, 0x88B4, 0xD146, 0x88B5, 0xD147, 0x88B6, 0xD148, 0x88B7, 0xF1CA, 0x88B8, 0xD149, 0x88B9, 0xD14A, 0x88BA, 0xD14B, + 0x88BB, 0xD14C, 0x88BC, 0xF1CB, 0x88BD, 0xD14D, 0x88BE, 0xD14E, 0x88BF, 0xD14F, 0x88C0, 0xD150, 0x88C1, 0xB2C3, 0x88C2, 0xC1D1, + 0x88C3, 0xD151, 0x88C4, 0xD152, 0x88C5, 0xD7B0, 0x88C6, 0xF1C9, 0x88C7, 0xD153, 0x88C8, 0xD154, 0x88C9, 0xF1CC, 0x88CA, 0xD155, + 0x88CB, 0xD156, 0x88CC, 0xD157, 0x88CD, 0xD158, 0x88CE, 0xF1CE, 0x88CF, 0xD159, 0x88D0, 0xD15A, 0x88D1, 0xD15B, 0x88D2, 0xD9F6, + 0x88D3, 0xD15C, 0x88D4, 0xD2E1, 0x88D5, 0xD4A3, 0x88D6, 0xD15D, 0x88D7, 0xD15E, 0x88D8, 0xF4C3, 0x88D9, 0xC8B9, 0x88DA, 0xD15F, + 0x88DB, 0xD160, 0x88DC, 0xD161, 0x88DD, 0xD162, 0x88DE, 0xD163, 0x88DF, 0xF4C4, 0x88E0, 0xD164, 0x88E1, 0xD165, 0x88E2, 0xF1CD, + 0x88E3, 0xF1CF, 0x88E4, 0xBFE3, 0x88E5, 0xF1D0, 0x88E6, 0xD166, 0x88E7, 0xD167, 0x88E8, 0xF1D4, 0x88E9, 0xD168, 0x88EA, 0xD169, + 0x88EB, 0xD16A, 0x88EC, 0xD16B, 0x88ED, 0xD16C, 0x88EE, 0xD16D, 0x88EF, 0xD16E, 0x88F0, 0xF1D6, 0x88F1, 0xF1D1, 0x88F2, 0xD16F, + 0x88F3, 0xC9D1, 0x88F4, 0xC5E1, 0x88F5, 0xD170, 0x88F6, 0xD171, 0x88F7, 0xD172, 0x88F8, 0xC2E3, 0x88F9, 0xB9FC, 0x88FA, 0xD173, + 0x88FB, 0xD174, 0x88FC, 0xF1D3, 0x88FD, 0xD175, 0x88FE, 0xF1D5, 0x88FF, 0xD176, 0x8900, 0xD177, 0x8901, 0xD178, 0x8902, 0xB9D3, + 0x8903, 0xD179, 0x8904, 0xD17A, 0x8905, 0xD17B, 0x8906, 0xD17C, 0x8907, 0xD17D, 0x8908, 0xD17E, 0x8909, 0xD180, 0x890A, 0xF1DB, + 0x890B, 0xD181, 0x890C, 0xD182, 0x890D, 0xD183, 0x890E, 0xD184, 0x890F, 0xD185, 0x8910, 0xBAD6, 0x8911, 0xD186, 0x8912, 0xB0FD, + 0x8913, 0xF1D9, 0x8914, 0xD187, 0x8915, 0xD188, 0x8916, 0xD189, 0x8917, 0xD18A, 0x8918, 0xD18B, 0x8919, 0xF1D8, 0x891A, 0xF1D2, + 0x891B, 0xF1DA, 0x891C, 0xD18C, 0x891D, 0xD18D, 0x891E, 0xD18E, 0x891F, 0xD18F, 0x8920, 0xD190, 0x8921, 0xF1D7, 0x8922, 0xD191, + 0x8923, 0xD192, 0x8924, 0xD193, 0x8925, 0xC8EC, 0x8926, 0xD194, 0x8927, 0xD195, 0x8928, 0xD196, 0x8929, 0xD197, 0x892A, 0xCDCA, + 0x892B, 0xF1DD, 0x892C, 0xD198, 0x892D, 0xD199, 0x892E, 0xD19A, 0x892F, 0xD19B, 0x8930, 0xE5BD, 0x8931, 0xD19C, 0x8932, 0xD19D, + 0x8933, 0xD19E, 0x8934, 0xF1DC, 0x8935, 0xD19F, 0x8936, 0xF1DE, 0x8937, 0xD1A0, 0x8938, 0xD240, 0x8939, 0xD241, 0x893A, 0xD242, + 0x893B, 0xD243, 0x893C, 0xD244, 0x893D, 0xD245, 0x893E, 0xD246, 0x893F, 0xD247, 0x8940, 0xD248, 0x8941, 0xF1DF, 0x8942, 0xD249, + 0x8943, 0xD24A, 0x8944, 0xCFE5, 0x8945, 0xD24B, 0x8946, 0xD24C, 0x8947, 0xD24D, 0x8948, 0xD24E, 0x8949, 0xD24F, 0x894A, 0xD250, + 0x894B, 0xD251, 0x894C, 0xD252, 0x894D, 0xD253, 0x894E, 0xD254, 0x894F, 0xD255, 0x8950, 0xD256, 0x8951, 0xD257, 0x8952, 0xD258, + 0x8953, 0xD259, 0x8954, 0xD25A, 0x8955, 0xD25B, 0x8956, 0xD25C, 0x8957, 0xD25D, 0x8958, 0xD25E, 0x8959, 0xD25F, 0x895A, 0xD260, + 0x895B, 0xD261, 0x895C, 0xD262, 0x895D, 0xD263, 0x895E, 0xF4C5, 0x895F, 0xBDF3, 0x8960, 0xD264, 0x8961, 0xD265, 0x8962, 0xD266, + 0x8963, 0xD267, 0x8964, 0xD268, 0x8965, 0xD269, 0x8966, 0xF1E0, 0x8967, 0xD26A, 0x8968, 0xD26B, 0x8969, 0xD26C, 0x896A, 0xD26D, + 0x896B, 0xD26E, 0x896C, 0xD26F, 0x896D, 0xD270, 0x896E, 0xD271, 0x896F, 0xD272, 0x8970, 0xD273, 0x8971, 0xD274, 0x8972, 0xD275, + 0x8973, 0xD276, 0x8974, 0xD277, 0x8975, 0xD278, 0x8976, 0xD279, 0x8977, 0xD27A, 0x8978, 0xD27B, 0x8979, 0xD27C, 0x897A, 0xD27D, + 0x897B, 0xF1E1, 0x897C, 0xD27E, 0x897D, 0xD280, 0x897E, 0xD281, 0x897F, 0xCEF7, 0x8980, 0xD282, 0x8981, 0xD2AA, 0x8982, 0xD283, + 0x8983, 0xF1FB, 0x8984, 0xD284, 0x8985, 0xD285, 0x8986, 0xB8B2, 0x8987, 0xD286, 0x8988, 0xD287, 0x8989, 0xD288, 0x898A, 0xD289, + 0x898B, 0xD28A, 0x898C, 0xD28B, 0x898D, 0xD28C, 0x898E, 0xD28D, 0x898F, 0xD28E, 0x8990, 0xD28F, 0x8991, 0xD290, 0x8992, 0xD291, + 0x8993, 0xD292, 0x8994, 0xD293, 0x8995, 0xD294, 0x8996, 0xD295, 0x8997, 0xD296, 0x8998, 0xD297, 0x8999, 0xD298, 0x899A, 0xD299, + 0x899B, 0xD29A, 0x899C, 0xD29B, 0x899D, 0xD29C, 0x899E, 0xD29D, 0x899F, 0xD29E, 0x89A0, 0xD29F, 0x89A1, 0xD2A0, 0x89A2, 0xD340, + 0x89A3, 0xD341, 0x89A4, 0xD342, 0x89A5, 0xD343, 0x89A6, 0xD344, 0x89A7, 0xD345, 0x89A8, 0xD346, 0x89A9, 0xD347, 0x89AA, 0xD348, + 0x89AB, 0xD349, 0x89AC, 0xD34A, 0x89AD, 0xD34B, 0x89AE, 0xD34C, 0x89AF, 0xD34D, 0x89B0, 0xD34E, 0x89B1, 0xD34F, 0x89B2, 0xD350, + 0x89B3, 0xD351, 0x89B4, 0xD352, 0x89B5, 0xD353, 0x89B6, 0xD354, 0x89B7, 0xD355, 0x89B8, 0xD356, 0x89B9, 0xD357, 0x89BA, 0xD358, + 0x89BB, 0xD359, 0x89BC, 0xD35A, 0x89BD, 0xD35B, 0x89BE, 0xD35C, 0x89BF, 0xD35D, 0x89C0, 0xD35E, 0x89C1, 0xBCFB, 0x89C2, 0xB9DB, + 0x89C3, 0xD35F, 0x89C4, 0xB9E6, 0x89C5, 0xC3D9, 0x89C6, 0xCAD3, 0x89C7, 0xEAE8, 0x89C8, 0xC0C0, 0x89C9, 0xBEF5, 0x89CA, 0xEAE9, + 0x89CB, 0xEAEA, 0x89CC, 0xEAEB, 0x89CD, 0xD360, 0x89CE, 0xEAEC, 0x89CF, 0xEAED, 0x89D0, 0xEAEE, 0x89D1, 0xEAEF, 0x89D2, 0xBDC7, + 0x89D3, 0xD361, 0x89D4, 0xD362, 0x89D5, 0xD363, 0x89D6, 0xF5FB, 0x89D7, 0xD364, 0x89D8, 0xD365, 0x89D9, 0xD366, 0x89DA, 0xF5FD, + 0x89DB, 0xD367, 0x89DC, 0xF5FE, 0x89DD, 0xD368, 0x89DE, 0xF5FC, 0x89DF, 0xD369, 0x89E0, 0xD36A, 0x89E1, 0xD36B, 0x89E2, 0xD36C, + 0x89E3, 0xBDE2, 0x89E4, 0xD36D, 0x89E5, 0xF6A1, 0x89E6, 0xB4A5, 0x89E7, 0xD36E, 0x89E8, 0xD36F, 0x89E9, 0xD370, 0x89EA, 0xD371, + 0x89EB, 0xF6A2, 0x89EC, 0xD372, 0x89ED, 0xD373, 0x89EE, 0xD374, 0x89EF, 0xF6A3, 0x89F0, 0xD375, 0x89F1, 0xD376, 0x89F2, 0xD377, + 0x89F3, 0xECB2, 0x89F4, 0xD378, 0x89F5, 0xD379, 0x89F6, 0xD37A, 0x89F7, 0xD37B, 0x89F8, 0xD37C, 0x89F9, 0xD37D, 0x89FA, 0xD37E, + 0x89FB, 0xD380, 0x89FC, 0xD381, 0x89FD, 0xD382, 0x89FE, 0xD383, 0x89FF, 0xD384, 0x8A00, 0xD1D4, 0x8A01, 0xD385, 0x8A02, 0xD386, + 0x8A03, 0xD387, 0x8A04, 0xD388, 0x8A05, 0xD389, 0x8A06, 0xD38A, 0x8A07, 0xD9EA, 0x8A08, 0xD38B, 0x8A09, 0xD38C, 0x8A0A, 0xD38D, + 0x8A0B, 0xD38E, 0x8A0C, 0xD38F, 0x8A0D, 0xD390, 0x8A0E, 0xD391, 0x8A0F, 0xD392, 0x8A10, 0xD393, 0x8A11, 0xD394, 0x8A12, 0xD395, + 0x8A13, 0xD396, 0x8A14, 0xD397, 0x8A15, 0xD398, 0x8A16, 0xD399, 0x8A17, 0xD39A, 0x8A18, 0xD39B, 0x8A19, 0xD39C, 0x8A1A, 0xD39D, + 0x8A1B, 0xD39E, 0x8A1C, 0xD39F, 0x8A1D, 0xD3A0, 0x8A1E, 0xD440, 0x8A1F, 0xD441, 0x8A20, 0xD442, 0x8A21, 0xD443, 0x8A22, 0xD444, + 0x8A23, 0xD445, 0x8A24, 0xD446, 0x8A25, 0xD447, 0x8A26, 0xD448, 0x8A27, 0xD449, 0x8A28, 0xD44A, 0x8A29, 0xD44B, 0x8A2A, 0xD44C, + 0x8A2B, 0xD44D, 0x8A2C, 0xD44E, 0x8A2D, 0xD44F, 0x8A2E, 0xD450, 0x8A2F, 0xD451, 0x8A30, 0xD452, 0x8A31, 0xD453, 0x8A32, 0xD454, + 0x8A33, 0xD455, 0x8A34, 0xD456, 0x8A35, 0xD457, 0x8A36, 0xD458, 0x8A37, 0xD459, 0x8A38, 0xD45A, 0x8A39, 0xD45B, 0x8A3A, 0xD45C, + 0x8A3B, 0xD45D, 0x8A3C, 0xD45E, 0x8A3D, 0xD45F, 0x8A3E, 0xF6A4, 0x8A3F, 0xD460, 0x8A40, 0xD461, 0x8A41, 0xD462, 0x8A42, 0xD463, + 0x8A43, 0xD464, 0x8A44, 0xD465, 0x8A45, 0xD466, 0x8A46, 0xD467, 0x8A47, 0xD468, 0x8A48, 0xEEBA, 0x8A49, 0xD469, 0x8A4A, 0xD46A, + 0x8A4B, 0xD46B, 0x8A4C, 0xD46C, 0x8A4D, 0xD46D, 0x8A4E, 0xD46E, 0x8A4F, 0xD46F, 0x8A50, 0xD470, 0x8A51, 0xD471, 0x8A52, 0xD472, + 0x8A53, 0xD473, 0x8A54, 0xD474, 0x8A55, 0xD475, 0x8A56, 0xD476, 0x8A57, 0xD477, 0x8A58, 0xD478, 0x8A59, 0xD479, 0x8A5A, 0xD47A, + 0x8A5B, 0xD47B, 0x8A5C, 0xD47C, 0x8A5D, 0xD47D, 0x8A5E, 0xD47E, 0x8A5F, 0xD480, 0x8A60, 0xD481, 0x8A61, 0xD482, 0x8A62, 0xD483, + 0x8A63, 0xD484, 0x8A64, 0xD485, 0x8A65, 0xD486, 0x8A66, 0xD487, 0x8A67, 0xD488, 0x8A68, 0xD489, 0x8A69, 0xD48A, 0x8A6A, 0xD48B, + 0x8A6B, 0xD48C, 0x8A6C, 0xD48D, 0x8A6D, 0xD48E, 0x8A6E, 0xD48F, 0x8A6F, 0xD490, 0x8A70, 0xD491, 0x8A71, 0xD492, 0x8A72, 0xD493, + 0x8A73, 0xD494, 0x8A74, 0xD495, 0x8A75, 0xD496, 0x8A76, 0xD497, 0x8A77, 0xD498, 0x8A78, 0xD499, 0x8A79, 0xD5B2, 0x8A7A, 0xD49A, + 0x8A7B, 0xD49B, 0x8A7C, 0xD49C, 0x8A7D, 0xD49D, 0x8A7E, 0xD49E, 0x8A7F, 0xD49F, 0x8A80, 0xD4A0, 0x8A81, 0xD540, 0x8A82, 0xD541, + 0x8A83, 0xD542, 0x8A84, 0xD543, 0x8A85, 0xD544, 0x8A86, 0xD545, 0x8A87, 0xD546, 0x8A88, 0xD547, 0x8A89, 0xD3FE, 0x8A8A, 0xCCDC, + 0x8A8B, 0xD548, 0x8A8C, 0xD549, 0x8A8D, 0xD54A, 0x8A8E, 0xD54B, 0x8A8F, 0xD54C, 0x8A90, 0xD54D, 0x8A91, 0xD54E, 0x8A92, 0xD54F, + 0x8A93, 0xCAC4, 0x8A94, 0xD550, 0x8A95, 0xD551, 0x8A96, 0xD552, 0x8A97, 0xD553, 0x8A98, 0xD554, 0x8A99, 0xD555, 0x8A9A, 0xD556, + 0x8A9B, 0xD557, 0x8A9C, 0xD558, 0x8A9D, 0xD559, 0x8A9E, 0xD55A, 0x8A9F, 0xD55B, 0x8AA0, 0xD55C, 0x8AA1, 0xD55D, 0x8AA2, 0xD55E, + 0x8AA3, 0xD55F, 0x8AA4, 0xD560, 0x8AA5, 0xD561, 0x8AA6, 0xD562, 0x8AA7, 0xD563, 0x8AA8, 0xD564, 0x8AA9, 0xD565, 0x8AAA, 0xD566, + 0x8AAB, 0xD567, 0x8AAC, 0xD568, 0x8AAD, 0xD569, 0x8AAE, 0xD56A, 0x8AAF, 0xD56B, 0x8AB0, 0xD56C, 0x8AB1, 0xD56D, 0x8AB2, 0xD56E, + 0x8AB3, 0xD56F, 0x8AB4, 0xD570, 0x8AB5, 0xD571, 0x8AB6, 0xD572, 0x8AB7, 0xD573, 0x8AB8, 0xD574, 0x8AB9, 0xD575, 0x8ABA, 0xD576, + 0x8ABB, 0xD577, 0x8ABC, 0xD578, 0x8ABD, 0xD579, 0x8ABE, 0xD57A, 0x8ABF, 0xD57B, 0x8AC0, 0xD57C, 0x8AC1, 0xD57D, 0x8AC2, 0xD57E, + 0x8AC3, 0xD580, 0x8AC4, 0xD581, 0x8AC5, 0xD582, 0x8AC6, 0xD583, 0x8AC7, 0xD584, 0x8AC8, 0xD585, 0x8AC9, 0xD586, 0x8ACA, 0xD587, + 0x8ACB, 0xD588, 0x8ACC, 0xD589, 0x8ACD, 0xD58A, 0x8ACE, 0xD58B, 0x8ACF, 0xD58C, 0x8AD0, 0xD58D, 0x8AD1, 0xD58E, 0x8AD2, 0xD58F, + 0x8AD3, 0xD590, 0x8AD4, 0xD591, 0x8AD5, 0xD592, 0x8AD6, 0xD593, 0x8AD7, 0xD594, 0x8AD8, 0xD595, 0x8AD9, 0xD596, 0x8ADA, 0xD597, + 0x8ADB, 0xD598, 0x8ADC, 0xD599, 0x8ADD, 0xD59A, 0x8ADE, 0xD59B, 0x8ADF, 0xD59C, 0x8AE0, 0xD59D, 0x8AE1, 0xD59E, 0x8AE2, 0xD59F, + 0x8AE3, 0xD5A0, 0x8AE4, 0xD640, 0x8AE5, 0xD641, 0x8AE6, 0xD642, 0x8AE7, 0xD643, 0x8AE8, 0xD644, 0x8AE9, 0xD645, 0x8AEA, 0xD646, + 0x8AEB, 0xD647, 0x8AEC, 0xD648, 0x8AED, 0xD649, 0x8AEE, 0xD64A, 0x8AEF, 0xD64B, 0x8AF0, 0xD64C, 0x8AF1, 0xD64D, 0x8AF2, 0xD64E, + 0x8AF3, 0xD64F, 0x8AF4, 0xD650, 0x8AF5, 0xD651, 0x8AF6, 0xD652, 0x8AF7, 0xD653, 0x8AF8, 0xD654, 0x8AF9, 0xD655, 0x8AFA, 0xD656, + 0x8AFB, 0xD657, 0x8AFC, 0xD658, 0x8AFD, 0xD659, 0x8AFE, 0xD65A, 0x8AFF, 0xD65B, 0x8B00, 0xD65C, 0x8B01, 0xD65D, 0x8B02, 0xD65E, + 0x8B03, 0xD65F, 0x8B04, 0xD660, 0x8B05, 0xD661, 0x8B06, 0xD662, 0x8B07, 0xE5C0, 0x8B08, 0xD663, 0x8B09, 0xD664, 0x8B0A, 0xD665, + 0x8B0B, 0xD666, 0x8B0C, 0xD667, 0x8B0D, 0xD668, 0x8B0E, 0xD669, 0x8B0F, 0xD66A, 0x8B10, 0xD66B, 0x8B11, 0xD66C, 0x8B12, 0xD66D, + 0x8B13, 0xD66E, 0x8B14, 0xD66F, 0x8B15, 0xD670, 0x8B16, 0xD671, 0x8B17, 0xD672, 0x8B18, 0xD673, 0x8B19, 0xD674, 0x8B1A, 0xD675, + 0x8B1B, 0xD676, 0x8B1C, 0xD677, 0x8B1D, 0xD678, 0x8B1E, 0xD679, 0x8B1F, 0xD67A, 0x8B20, 0xD67B, 0x8B21, 0xD67C, 0x8B22, 0xD67D, + 0x8B23, 0xD67E, 0x8B24, 0xD680, 0x8B25, 0xD681, 0x8B26, 0xF6A5, 0x8B27, 0xD682, 0x8B28, 0xD683, 0x8B29, 0xD684, 0x8B2A, 0xD685, + 0x8B2B, 0xD686, 0x8B2C, 0xD687, 0x8B2D, 0xD688, 0x8B2E, 0xD689, 0x8B2F, 0xD68A, 0x8B30, 0xD68B, 0x8B31, 0xD68C, 0x8B32, 0xD68D, + 0x8B33, 0xD68E, 0x8B34, 0xD68F, 0x8B35, 0xD690, 0x8B36, 0xD691, 0x8B37, 0xD692, 0x8B38, 0xD693, 0x8B39, 0xD694, 0x8B3A, 0xD695, + 0x8B3B, 0xD696, 0x8B3C, 0xD697, 0x8B3D, 0xD698, 0x8B3E, 0xD699, 0x8B3F, 0xD69A, 0x8B40, 0xD69B, 0x8B41, 0xD69C, 0x8B42, 0xD69D, + 0x8B43, 0xD69E, 0x8B44, 0xD69F, 0x8B45, 0xD6A0, 0x8B46, 0xD740, 0x8B47, 0xD741, 0x8B48, 0xD742, 0x8B49, 0xD743, 0x8B4A, 0xD744, + 0x8B4B, 0xD745, 0x8B4C, 0xD746, 0x8B4D, 0xD747, 0x8B4E, 0xD748, 0x8B4F, 0xD749, 0x8B50, 0xD74A, 0x8B51, 0xD74B, 0x8B52, 0xD74C, + 0x8B53, 0xD74D, 0x8B54, 0xD74E, 0x8B55, 0xD74F, 0x8B56, 0xD750, 0x8B57, 0xD751, 0x8B58, 0xD752, 0x8B59, 0xD753, 0x8B5A, 0xD754, + 0x8B5B, 0xD755, 0x8B5C, 0xD756, 0x8B5D, 0xD757, 0x8B5E, 0xD758, 0x8B5F, 0xD759, 0x8B60, 0xD75A, 0x8B61, 0xD75B, 0x8B62, 0xD75C, + 0x8B63, 0xD75D, 0x8B64, 0xD75E, 0x8B65, 0xD75F, 0x8B66, 0xBEAF, 0x8B67, 0xD760, 0x8B68, 0xD761, 0x8B69, 0xD762, 0x8B6A, 0xD763, + 0x8B6B, 0xD764, 0x8B6C, 0xC6A9, 0x8B6D, 0xD765, 0x8B6E, 0xD766, 0x8B6F, 0xD767, 0x8B70, 0xD768, 0x8B71, 0xD769, 0x8B72, 0xD76A, + 0x8B73, 0xD76B, 0x8B74, 0xD76C, 0x8B75, 0xD76D, 0x8B76, 0xD76E, 0x8B77, 0xD76F, 0x8B78, 0xD770, 0x8B79, 0xD771, 0x8B7A, 0xD772, + 0x8B7B, 0xD773, 0x8B7C, 0xD774, 0x8B7D, 0xD775, 0x8B7E, 0xD776, 0x8B7F, 0xD777, 0x8B80, 0xD778, 0x8B81, 0xD779, 0x8B82, 0xD77A, + 0x8B83, 0xD77B, 0x8B84, 0xD77C, 0x8B85, 0xD77D, 0x8B86, 0xD77E, 0x8B87, 0xD780, 0x8B88, 0xD781, 0x8B89, 0xD782, 0x8B8A, 0xD783, + 0x8B8B, 0xD784, 0x8B8C, 0xD785, 0x8B8D, 0xD786, 0x8B8E, 0xD787, 0x8B8F, 0xD788, 0x8B90, 0xD789, 0x8B91, 0xD78A, 0x8B92, 0xD78B, + 0x8B93, 0xD78C, 0x8B94, 0xD78D, 0x8B95, 0xD78E, 0x8B96, 0xD78F, 0x8B97, 0xD790, 0x8B98, 0xD791, 0x8B99, 0xD792, 0x8B9A, 0xD793, + 0x8B9B, 0xD794, 0x8B9C, 0xD795, 0x8B9D, 0xD796, 0x8B9E, 0xD797, 0x8B9F, 0xD798, 0x8BA0, 0xDAA5, 0x8BA1, 0xBCC6, 0x8BA2, 0xB6A9, + 0x8BA3, 0xB8BC, 0x8BA4, 0xC8CF, 0x8BA5, 0xBCA5, 0x8BA6, 0xDAA6, 0x8BA7, 0xDAA7, 0x8BA8, 0xCCD6, 0x8BA9, 0xC8C3, 0x8BAA, 0xDAA8, + 0x8BAB, 0xC6FD, 0x8BAC, 0xD799, 0x8BAD, 0xD1B5, 0x8BAE, 0xD2E9, 0x8BAF, 0xD1B6, 0x8BB0, 0xBCC7, 0x8BB1, 0xD79A, 0x8BB2, 0xBDB2, + 0x8BB3, 0xBBE4, 0x8BB4, 0xDAA9, 0x8BB5, 0xDAAA, 0x8BB6, 0xD1C8, 0x8BB7, 0xDAAB, 0x8BB8, 0xD0ED, 0x8BB9, 0xB6EF, 0x8BBA, 0xC2DB, + 0x8BBB, 0xD79B, 0x8BBC, 0xCBCF, 0x8BBD, 0xB7ED, 0x8BBE, 0xC9E8, 0x8BBF, 0xB7C3, 0x8BC0, 0xBEF7, 0x8BC1, 0xD6A4, 0x8BC2, 0xDAAC, + 0x8BC3, 0xDAAD, 0x8BC4, 0xC6C0, 0x8BC5, 0xD7E7, 0x8BC6, 0xCAB6, 0x8BC7, 0xD79C, 0x8BC8, 0xD5A9, 0x8BC9, 0xCBDF, 0x8BCA, 0xD5EF, + 0x8BCB, 0xDAAE, 0x8BCC, 0xD6DF, 0x8BCD, 0xB4CA, 0x8BCE, 0xDAB0, 0x8BCF, 0xDAAF, 0x8BD0, 0xD79D, 0x8BD1, 0xD2EB, 0x8BD2, 0xDAB1, + 0x8BD3, 0xDAB2, 0x8BD4, 0xDAB3, 0x8BD5, 0xCAD4, 0x8BD6, 0xDAB4, 0x8BD7, 0xCAAB, 0x8BD8, 0xDAB5, 0x8BD9, 0xDAB6, 0x8BDA, 0xB3CF, + 0x8BDB, 0xD6EF, 0x8BDC, 0xDAB7, 0x8BDD, 0xBBB0, 0x8BDE, 0xB5AE, 0x8BDF, 0xDAB8, 0x8BE0, 0xDAB9, 0x8BE1, 0xB9EE, 0x8BE2, 0xD1AF, + 0x8BE3, 0xD2E8, 0x8BE4, 0xDABA, 0x8BE5, 0xB8C3, 0x8BE6, 0xCFEA, 0x8BE7, 0xB2EF, 0x8BE8, 0xDABB, 0x8BE9, 0xDABC, 0x8BEA, 0xD79E, + 0x8BEB, 0xBDEB, 0x8BEC, 0xCEDC, 0x8BED, 0xD3EF, 0x8BEE, 0xDABD, 0x8BEF, 0xCEF3, 0x8BF0, 0xDABE, 0x8BF1, 0xD3D5, 0x8BF2, 0xBBE5, + 0x8BF3, 0xDABF, 0x8BF4, 0xCBB5, 0x8BF5, 0xCBD0, 0x8BF6, 0xDAC0, 0x8BF7, 0xC7EB, 0x8BF8, 0xD6EE, 0x8BF9, 0xDAC1, 0x8BFA, 0xC5B5, + 0x8BFB, 0xB6C1, 0x8BFC, 0xDAC2, 0x8BFD, 0xB7CC, 0x8BFE, 0xBFCE, 0x8BFF, 0xDAC3, 0x8C00, 0xDAC4, 0x8C01, 0xCBAD, 0x8C02, 0xDAC5, + 0x8C03, 0xB5F7, 0x8C04, 0xDAC6, 0x8C05, 0xC1C2, 0x8C06, 0xD7BB, 0x8C07, 0xDAC7, 0x8C08, 0xCCB8, 0x8C09, 0xD79F, 0x8C0A, 0xD2EA, + 0x8C0B, 0xC4B1, 0x8C0C, 0xDAC8, 0x8C0D, 0xB5FD, 0x8C0E, 0xBBD1, 0x8C0F, 0xDAC9, 0x8C10, 0xD0B3, 0x8C11, 0xDACA, 0x8C12, 0xDACB, + 0x8C13, 0xCEBD, 0x8C14, 0xDACC, 0x8C15, 0xDACD, 0x8C16, 0xDACE, 0x8C17, 0xB2F7, 0x8C18, 0xDAD1, 0x8C19, 0xDACF, 0x8C1A, 0xD1E8, + 0x8C1B, 0xDAD0, 0x8C1C, 0xC3D5, 0x8C1D, 0xDAD2, 0x8C1E, 0xD7A0, 0x8C1F, 0xDAD3, 0x8C20, 0xDAD4, 0x8C21, 0xDAD5, 0x8C22, 0xD0BB, + 0x8C23, 0xD2A5, 0x8C24, 0xB0F9, 0x8C25, 0xDAD6, 0x8C26, 0xC7AB, 0x8C27, 0xDAD7, 0x8C28, 0xBDF7, 0x8C29, 0xC3A1, 0x8C2A, 0xDAD8, + 0x8C2B, 0xDAD9, 0x8C2C, 0xC3FD, 0x8C2D, 0xCCB7, 0x8C2E, 0xDADA, 0x8C2F, 0xDADB, 0x8C30, 0xC0BE, 0x8C31, 0xC6D7, 0x8C32, 0xDADC, + 0x8C33, 0xDADD, 0x8C34, 0xC7B4, 0x8C35, 0xDADE, 0x8C36, 0xDADF, 0x8C37, 0xB9C8, 0x8C38, 0xD840, 0x8C39, 0xD841, 0x8C3A, 0xD842, + 0x8C3B, 0xD843, 0x8C3C, 0xD844, 0x8C3D, 0xD845, 0x8C3E, 0xD846, 0x8C3F, 0xD847, 0x8C40, 0xD848, 0x8C41, 0xBBED, 0x8C42, 0xD849, + 0x8C43, 0xD84A, 0x8C44, 0xD84B, 0x8C45, 0xD84C, 0x8C46, 0xB6B9, 0x8C47, 0xF4F8, 0x8C48, 0xD84D, 0x8C49, 0xF4F9, 0x8C4A, 0xD84E, + 0x8C4B, 0xD84F, 0x8C4C, 0xCDE3, 0x8C4D, 0xD850, 0x8C4E, 0xD851, 0x8C4F, 0xD852, 0x8C50, 0xD853, 0x8C51, 0xD854, 0x8C52, 0xD855, + 0x8C53, 0xD856, 0x8C54, 0xD857, 0x8C55, 0xF5B9, 0x8C56, 0xD858, 0x8C57, 0xD859, 0x8C58, 0xD85A, 0x8C59, 0xD85B, 0x8C5A, 0xEBE0, + 0x8C5B, 0xD85C, 0x8C5C, 0xD85D, 0x8C5D, 0xD85E, 0x8C5E, 0xD85F, 0x8C5F, 0xD860, 0x8C60, 0xD861, 0x8C61, 0xCFF3, 0x8C62, 0xBBBF, + 0x8C63, 0xD862, 0x8C64, 0xD863, 0x8C65, 0xD864, 0x8C66, 0xD865, 0x8C67, 0xD866, 0x8C68, 0xD867, 0x8C69, 0xD868, 0x8C6A, 0xBAC0, + 0x8C6B, 0xD4A5, 0x8C6C, 0xD869, 0x8C6D, 0xD86A, 0x8C6E, 0xD86B, 0x8C6F, 0xD86C, 0x8C70, 0xD86D, 0x8C71, 0xD86E, 0x8C72, 0xD86F, + 0x8C73, 0xE1D9, 0x8C74, 0xD870, 0x8C75, 0xD871, 0x8C76, 0xD872, 0x8C77, 0xD873, 0x8C78, 0xF5F4, 0x8C79, 0xB1AA, 0x8C7A, 0xB2F2, + 0x8C7B, 0xD874, 0x8C7C, 0xD875, 0x8C7D, 0xD876, 0x8C7E, 0xD877, 0x8C7F, 0xD878, 0x8C80, 0xD879, 0x8C81, 0xD87A, 0x8C82, 0xF5F5, + 0x8C83, 0xD87B, 0x8C84, 0xD87C, 0x8C85, 0xF5F7, 0x8C86, 0xD87D, 0x8C87, 0xD87E, 0x8C88, 0xD880, 0x8C89, 0xBAD1, 0x8C8A, 0xF5F6, + 0x8C8B, 0xD881, 0x8C8C, 0xC3B2, 0x8C8D, 0xD882, 0x8C8E, 0xD883, 0x8C8F, 0xD884, 0x8C90, 0xD885, 0x8C91, 0xD886, 0x8C92, 0xD887, + 0x8C93, 0xD888, 0x8C94, 0xF5F9, 0x8C95, 0xD889, 0x8C96, 0xD88A, 0x8C97, 0xD88B, 0x8C98, 0xF5F8, 0x8C99, 0xD88C, 0x8C9A, 0xD88D, + 0x8C9B, 0xD88E, 0x8C9C, 0xD88F, 0x8C9D, 0xD890, 0x8C9E, 0xD891, 0x8C9F, 0xD892, 0x8CA0, 0xD893, 0x8CA1, 0xD894, 0x8CA2, 0xD895, + 0x8CA3, 0xD896, 0x8CA4, 0xD897, 0x8CA5, 0xD898, 0x8CA6, 0xD899, 0x8CA7, 0xD89A, 0x8CA8, 0xD89B, 0x8CA9, 0xD89C, 0x8CAA, 0xD89D, + 0x8CAB, 0xD89E, 0x8CAC, 0xD89F, 0x8CAD, 0xD8A0, 0x8CAE, 0xD940, 0x8CAF, 0xD941, 0x8CB0, 0xD942, 0x8CB1, 0xD943, 0x8CB2, 0xD944, + 0x8CB3, 0xD945, 0x8CB4, 0xD946, 0x8CB5, 0xD947, 0x8CB6, 0xD948, 0x8CB7, 0xD949, 0x8CB8, 0xD94A, 0x8CB9, 0xD94B, 0x8CBA, 0xD94C, + 0x8CBB, 0xD94D, 0x8CBC, 0xD94E, 0x8CBD, 0xD94F, 0x8CBE, 0xD950, 0x8CBF, 0xD951, 0x8CC0, 0xD952, 0x8CC1, 0xD953, 0x8CC2, 0xD954, + 0x8CC3, 0xD955, 0x8CC4, 0xD956, 0x8CC5, 0xD957, 0x8CC6, 0xD958, 0x8CC7, 0xD959, 0x8CC8, 0xD95A, 0x8CC9, 0xD95B, 0x8CCA, 0xD95C, + 0x8CCB, 0xD95D, 0x8CCC, 0xD95E, 0x8CCD, 0xD95F, 0x8CCE, 0xD960, 0x8CCF, 0xD961, 0x8CD0, 0xD962, 0x8CD1, 0xD963, 0x8CD2, 0xD964, + 0x8CD3, 0xD965, 0x8CD4, 0xD966, 0x8CD5, 0xD967, 0x8CD6, 0xD968, 0x8CD7, 0xD969, 0x8CD8, 0xD96A, 0x8CD9, 0xD96B, 0x8CDA, 0xD96C, + 0x8CDB, 0xD96D, 0x8CDC, 0xD96E, 0x8CDD, 0xD96F, 0x8CDE, 0xD970, 0x8CDF, 0xD971, 0x8CE0, 0xD972, 0x8CE1, 0xD973, 0x8CE2, 0xD974, + 0x8CE3, 0xD975, 0x8CE4, 0xD976, 0x8CE5, 0xD977, 0x8CE6, 0xD978, 0x8CE7, 0xD979, 0x8CE8, 0xD97A, 0x8CE9, 0xD97B, 0x8CEA, 0xD97C, + 0x8CEB, 0xD97D, 0x8CEC, 0xD97E, 0x8CED, 0xD980, 0x8CEE, 0xD981, 0x8CEF, 0xD982, 0x8CF0, 0xD983, 0x8CF1, 0xD984, 0x8CF2, 0xD985, + 0x8CF3, 0xD986, 0x8CF4, 0xD987, 0x8CF5, 0xD988, 0x8CF6, 0xD989, 0x8CF7, 0xD98A, 0x8CF8, 0xD98B, 0x8CF9, 0xD98C, 0x8CFA, 0xD98D, + 0x8CFB, 0xD98E, 0x8CFC, 0xD98F, 0x8CFD, 0xD990, 0x8CFE, 0xD991, 0x8CFF, 0xD992, 0x8D00, 0xD993, 0x8D01, 0xD994, 0x8D02, 0xD995, + 0x8D03, 0xD996, 0x8D04, 0xD997, 0x8D05, 0xD998, 0x8D06, 0xD999, 0x8D07, 0xD99A, 0x8D08, 0xD99B, 0x8D09, 0xD99C, 0x8D0A, 0xD99D, + 0x8D0B, 0xD99E, 0x8D0C, 0xD99F, 0x8D0D, 0xD9A0, 0x8D0E, 0xDA40, 0x8D0F, 0xDA41, 0x8D10, 0xDA42, 0x8D11, 0xDA43, 0x8D12, 0xDA44, + 0x8D13, 0xDA45, 0x8D14, 0xDA46, 0x8D15, 0xDA47, 0x8D16, 0xDA48, 0x8D17, 0xDA49, 0x8D18, 0xDA4A, 0x8D19, 0xDA4B, 0x8D1A, 0xDA4C, + 0x8D1B, 0xDA4D, 0x8D1C, 0xDA4E, 0x8D1D, 0xB1B4, 0x8D1E, 0xD5EA, 0x8D1F, 0xB8BA, 0x8D20, 0xDA4F, 0x8D21, 0xB9B1, 0x8D22, 0xB2C6, + 0x8D23, 0xD4F0, 0x8D24, 0xCFCD, 0x8D25, 0xB0DC, 0x8D26, 0xD5CB, 0x8D27, 0xBBF5, 0x8D28, 0xD6CA, 0x8D29, 0xB7B7, 0x8D2A, 0xCCB0, + 0x8D2B, 0xC6B6, 0x8D2C, 0xB1E1, 0x8D2D, 0xB9BA, 0x8D2E, 0xD6FC, 0x8D2F, 0xB9E1, 0x8D30, 0xB7A1, 0x8D31, 0xBCFA, 0x8D32, 0xEADA, + 0x8D33, 0xEADB, 0x8D34, 0xCCF9, 0x8D35, 0xB9F3, 0x8D36, 0xEADC, 0x8D37, 0xB4FB, 0x8D38, 0xC3B3, 0x8D39, 0xB7D1, 0x8D3A, 0xBAD8, + 0x8D3B, 0xEADD, 0x8D3C, 0xD4F4, 0x8D3D, 0xEADE, 0x8D3E, 0xBCD6, 0x8D3F, 0xBBDF, 0x8D40, 0xEADF, 0x8D41, 0xC1DE, 0x8D42, 0xC2B8, + 0x8D43, 0xD4DF, 0x8D44, 0xD7CA, 0x8D45, 0xEAE0, 0x8D46, 0xEAE1, 0x8D47, 0xEAE4, 0x8D48, 0xEAE2, 0x8D49, 0xEAE3, 0x8D4A, 0xC9DE, + 0x8D4B, 0xB8B3, 0x8D4C, 0xB6C4, 0x8D4D, 0xEAE5, 0x8D4E, 0xCAEA, 0x8D4F, 0xC9CD, 0x8D50, 0xB4CD, 0x8D51, 0xDA50, 0x8D52, 0xDA51, + 0x8D53, 0xE2D9, 0x8D54, 0xC5E2, 0x8D55, 0xEAE6, 0x8D56, 0xC0B5, 0x8D57, 0xDA52, 0x8D58, 0xD7B8, 0x8D59, 0xEAE7, 0x8D5A, 0xD7AC, + 0x8D5B, 0xC8FC, 0x8D5C, 0xD8D3, 0x8D5D, 0xD8CD, 0x8D5E, 0xD4DE, 0x8D5F, 0xDA53, 0x8D60, 0xD4F9, 0x8D61, 0xC9C4, 0x8D62, 0xD3AE, + 0x8D63, 0xB8D3, 0x8D64, 0xB3E0, 0x8D65, 0xDA54, 0x8D66, 0xC9E2, 0x8D67, 0xF4F6, 0x8D68, 0xDA55, 0x8D69, 0xDA56, 0x8D6A, 0xDA57, + 0x8D6B, 0xBAD5, 0x8D6C, 0xDA58, 0x8D6D, 0xF4F7, 0x8D6E, 0xDA59, 0x8D6F, 0xDA5A, 0x8D70, 0xD7DF, 0x8D71, 0xDA5B, 0x8D72, 0xDA5C, + 0x8D73, 0xF4F1, 0x8D74, 0xB8B0, 0x8D75, 0xD5D4, 0x8D76, 0xB8CF, 0x8D77, 0xC6F0, 0x8D78, 0xDA5D, 0x8D79, 0xDA5E, 0x8D7A, 0xDA5F, + 0x8D7B, 0xDA60, 0x8D7C, 0xDA61, 0x8D7D, 0xDA62, 0x8D7E, 0xDA63, 0x8D7F, 0xDA64, 0x8D80, 0xDA65, 0x8D81, 0xB3C3, 0x8D82, 0xDA66, + 0x8D83, 0xDA67, 0x8D84, 0xF4F2, 0x8D85, 0xB3AC, 0x8D86, 0xDA68, 0x8D87, 0xDA69, 0x8D88, 0xDA6A, 0x8D89, 0xDA6B, 0x8D8A, 0xD4BD, + 0x8D8B, 0xC7F7, 0x8D8C, 0xDA6C, 0x8D8D, 0xDA6D, 0x8D8E, 0xDA6E, 0x8D8F, 0xDA6F, 0x8D90, 0xDA70, 0x8D91, 0xF4F4, 0x8D92, 0xDA71, + 0x8D93, 0xDA72, 0x8D94, 0xF4F3, 0x8D95, 0xDA73, 0x8D96, 0xDA74, 0x8D97, 0xDA75, 0x8D98, 0xDA76, 0x8D99, 0xDA77, 0x8D9A, 0xDA78, + 0x8D9B, 0xDA79, 0x8D9C, 0xDA7A, 0x8D9D, 0xDA7B, 0x8D9E, 0xDA7C, 0x8D9F, 0xCCCB, 0x8DA0, 0xDA7D, 0x8DA1, 0xDA7E, 0x8DA2, 0xDA80, + 0x8DA3, 0xC8A4, 0x8DA4, 0xDA81, 0x8DA5, 0xDA82, 0x8DA6, 0xDA83, 0x8DA7, 0xDA84, 0x8DA8, 0xDA85, 0x8DA9, 0xDA86, 0x8DAA, 0xDA87, + 0x8DAB, 0xDA88, 0x8DAC, 0xDA89, 0x8DAD, 0xDA8A, 0x8DAE, 0xDA8B, 0x8DAF, 0xDA8C, 0x8DB0, 0xDA8D, 0x8DB1, 0xF4F5, 0x8DB2, 0xDA8E, + 0x8DB3, 0xD7E3, 0x8DB4, 0xC5BF, 0x8DB5, 0xF5C0, 0x8DB6, 0xDA8F, 0x8DB7, 0xDA90, 0x8DB8, 0xF5BB, 0x8DB9, 0xDA91, 0x8DBA, 0xF5C3, + 0x8DBB, 0xDA92, 0x8DBC, 0xF5C2, 0x8DBD, 0xDA93, 0x8DBE, 0xD6BA, 0x8DBF, 0xF5C1, 0x8DC0, 0xDA94, 0x8DC1, 0xDA95, 0x8DC2, 0xDA96, + 0x8DC3, 0xD4BE, 0x8DC4, 0xF5C4, 0x8DC5, 0xDA97, 0x8DC6, 0xF5CC, 0x8DC7, 0xDA98, 0x8DC8, 0xDA99, 0x8DC9, 0xDA9A, 0x8DCA, 0xDA9B, + 0x8DCB, 0xB0CF, 0x8DCC, 0xB5F8, 0x8DCD, 0xDA9C, 0x8DCE, 0xF5C9, 0x8DCF, 0xF5CA, 0x8DD0, 0xDA9D, 0x8DD1, 0xC5DC, 0x8DD2, 0xDA9E, + 0x8DD3, 0xDA9F, 0x8DD4, 0xDAA0, 0x8DD5, 0xDB40, 0x8DD6, 0xF5C5, 0x8DD7, 0xF5C6, 0x8DD8, 0xDB41, 0x8DD9, 0xDB42, 0x8DDA, 0xF5C7, + 0x8DDB, 0xF5CB, 0x8DDC, 0xDB43, 0x8DDD, 0xBEE0, 0x8DDE, 0xF5C8, 0x8DDF, 0xB8FA, 0x8DE0, 0xDB44, 0x8DE1, 0xDB45, 0x8DE2, 0xDB46, + 0x8DE3, 0xF5D0, 0x8DE4, 0xF5D3, 0x8DE5, 0xDB47, 0x8DE6, 0xDB48, 0x8DE7, 0xDB49, 0x8DE8, 0xBFE7, 0x8DE9, 0xDB4A, 0x8DEA, 0xB9F2, + 0x8DEB, 0xF5BC, 0x8DEC, 0xF5CD, 0x8DED, 0xDB4B, 0x8DEE, 0xDB4C, 0x8DEF, 0xC2B7, 0x8DF0, 0xDB4D, 0x8DF1, 0xDB4E, 0x8DF2, 0xDB4F, + 0x8DF3, 0xCCF8, 0x8DF4, 0xDB50, 0x8DF5, 0xBCF9, 0x8DF6, 0xDB51, 0x8DF7, 0xF5CE, 0x8DF8, 0xF5CF, 0x8DF9, 0xF5D1, 0x8DFA, 0xB6E5, + 0x8DFB, 0xF5D2, 0x8DFC, 0xDB52, 0x8DFD, 0xF5D5, 0x8DFE, 0xDB53, 0x8DFF, 0xDB54, 0x8E00, 0xDB55, 0x8E01, 0xDB56, 0x8E02, 0xDB57, + 0x8E03, 0xDB58, 0x8E04, 0xDB59, 0x8E05, 0xF5BD, 0x8E06, 0xDB5A, 0x8E07, 0xDB5B, 0x8E08, 0xDB5C, 0x8E09, 0xF5D4, 0x8E0A, 0xD3BB, + 0x8E0B, 0xDB5D, 0x8E0C, 0xB3EC, 0x8E0D, 0xDB5E, 0x8E0E, 0xDB5F, 0x8E0F, 0xCCA4, 0x8E10, 0xDB60, 0x8E11, 0xDB61, 0x8E12, 0xDB62, + 0x8E13, 0xDB63, 0x8E14, 0xF5D6, 0x8E15, 0xDB64, 0x8E16, 0xDB65, 0x8E17, 0xDB66, 0x8E18, 0xDB67, 0x8E19, 0xDB68, 0x8E1A, 0xDB69, + 0x8E1B, 0xDB6A, 0x8E1C, 0xDB6B, 0x8E1D, 0xF5D7, 0x8E1E, 0xBEE1, 0x8E1F, 0xF5D8, 0x8E20, 0xDB6C, 0x8E21, 0xDB6D, 0x8E22, 0xCCDF, + 0x8E23, 0xF5DB, 0x8E24, 0xDB6E, 0x8E25, 0xDB6F, 0x8E26, 0xDB70, 0x8E27, 0xDB71, 0x8E28, 0xDB72, 0x8E29, 0xB2C8, 0x8E2A, 0xD7D9, + 0x8E2B, 0xDB73, 0x8E2C, 0xF5D9, 0x8E2D, 0xDB74, 0x8E2E, 0xF5DA, 0x8E2F, 0xF5DC, 0x8E30, 0xDB75, 0x8E31, 0xF5E2, 0x8E32, 0xDB76, + 0x8E33, 0xDB77, 0x8E34, 0xDB78, 0x8E35, 0xF5E0, 0x8E36, 0xDB79, 0x8E37, 0xDB7A, 0x8E38, 0xDB7B, 0x8E39, 0xF5DF, 0x8E3A, 0xF5DD, + 0x8E3B, 0xDB7C, 0x8E3C, 0xDB7D, 0x8E3D, 0xF5E1, 0x8E3E, 0xDB7E, 0x8E3F, 0xDB80, 0x8E40, 0xF5DE, 0x8E41, 0xF5E4, 0x8E42, 0xF5E5, + 0x8E43, 0xDB81, 0x8E44, 0xCCE3, 0x8E45, 0xDB82, 0x8E46, 0xDB83, 0x8E47, 0xE5BF, 0x8E48, 0xB5B8, 0x8E49, 0xF5E3, 0x8E4A, 0xF5E8, + 0x8E4B, 0xCCA3, 0x8E4C, 0xDB84, 0x8E4D, 0xDB85, 0x8E4E, 0xDB86, 0x8E4F, 0xDB87, 0x8E50, 0xDB88, 0x8E51, 0xF5E6, 0x8E52, 0xF5E7, + 0x8E53, 0xDB89, 0x8E54, 0xDB8A, 0x8E55, 0xDB8B, 0x8E56, 0xDB8C, 0x8E57, 0xDB8D, 0x8E58, 0xDB8E, 0x8E59, 0xF5BE, 0x8E5A, 0xDB8F, + 0x8E5B, 0xDB90, 0x8E5C, 0xDB91, 0x8E5D, 0xDB92, 0x8E5E, 0xDB93, 0x8E5F, 0xDB94, 0x8E60, 0xDB95, 0x8E61, 0xDB96, 0x8E62, 0xDB97, + 0x8E63, 0xDB98, 0x8E64, 0xDB99, 0x8E65, 0xDB9A, 0x8E66, 0xB1C4, 0x8E67, 0xDB9B, 0x8E68, 0xDB9C, 0x8E69, 0xF5BF, 0x8E6A, 0xDB9D, + 0x8E6B, 0xDB9E, 0x8E6C, 0xB5C5, 0x8E6D, 0xB2E4, 0x8E6E, 0xDB9F, 0x8E6F, 0xF5EC, 0x8E70, 0xF5E9, 0x8E71, 0xDBA0, 0x8E72, 0xB6D7, + 0x8E73, 0xDC40, 0x8E74, 0xF5ED, 0x8E75, 0xDC41, 0x8E76, 0xF5EA, 0x8E77, 0xDC42, 0x8E78, 0xDC43, 0x8E79, 0xDC44, 0x8E7A, 0xDC45, + 0x8E7B, 0xDC46, 0x8E7C, 0xF5EB, 0x8E7D, 0xDC47, 0x8E7E, 0xDC48, 0x8E7F, 0xB4DA, 0x8E80, 0xDC49, 0x8E81, 0xD4EA, 0x8E82, 0xDC4A, + 0x8E83, 0xDC4B, 0x8E84, 0xDC4C, 0x8E85, 0xF5EE, 0x8E86, 0xDC4D, 0x8E87, 0xB3F9, 0x8E88, 0xDC4E, 0x8E89, 0xDC4F, 0x8E8A, 0xDC50, + 0x8E8B, 0xDC51, 0x8E8C, 0xDC52, 0x8E8D, 0xDC53, 0x8E8E, 0xDC54, 0x8E8F, 0xF5EF, 0x8E90, 0xF5F1, 0x8E91, 0xDC55, 0x8E92, 0xDC56, + 0x8E93, 0xDC57, 0x8E94, 0xF5F0, 0x8E95, 0xDC58, 0x8E96, 0xDC59, 0x8E97, 0xDC5A, 0x8E98, 0xDC5B, 0x8E99, 0xDC5C, 0x8E9A, 0xDC5D, + 0x8E9B, 0xDC5E, 0x8E9C, 0xF5F2, 0x8E9D, 0xDC5F, 0x8E9E, 0xF5F3, 0x8E9F, 0xDC60, 0x8EA0, 0xDC61, 0x8EA1, 0xDC62, 0x8EA2, 0xDC63, + 0x8EA3, 0xDC64, 0x8EA4, 0xDC65, 0x8EA5, 0xDC66, 0x8EA6, 0xDC67, 0x8EA7, 0xDC68, 0x8EA8, 0xDC69, 0x8EA9, 0xDC6A, 0x8EAA, 0xDC6B, + 0x8EAB, 0xC9ED, 0x8EAC, 0xB9AA, 0x8EAD, 0xDC6C, 0x8EAE, 0xDC6D, 0x8EAF, 0xC7FB, 0x8EB0, 0xDC6E, 0x8EB1, 0xDC6F, 0x8EB2, 0xB6E3, + 0x8EB3, 0xDC70, 0x8EB4, 0xDC71, 0x8EB5, 0xDC72, 0x8EB6, 0xDC73, 0x8EB7, 0xDC74, 0x8EB8, 0xDC75, 0x8EB9, 0xDC76, 0x8EBA, 0xCCC9, + 0x8EBB, 0xDC77, 0x8EBC, 0xDC78, 0x8EBD, 0xDC79, 0x8EBE, 0xDC7A, 0x8EBF, 0xDC7B, 0x8EC0, 0xDC7C, 0x8EC1, 0xDC7D, 0x8EC2, 0xDC7E, + 0x8EC3, 0xDC80, 0x8EC4, 0xDC81, 0x8EC5, 0xDC82, 0x8EC6, 0xDC83, 0x8EC7, 0xDC84, 0x8EC8, 0xDC85, 0x8EC9, 0xDC86, 0x8ECA, 0xDC87, + 0x8ECB, 0xDC88, 0x8ECC, 0xDC89, 0x8ECD, 0xDC8A, 0x8ECE, 0xEAA6, 0x8ECF, 0xDC8B, 0x8ED0, 0xDC8C, 0x8ED1, 0xDC8D, 0x8ED2, 0xDC8E, + 0x8ED3, 0xDC8F, 0x8ED4, 0xDC90, 0x8ED5, 0xDC91, 0x8ED6, 0xDC92, 0x8ED7, 0xDC93, 0x8ED8, 0xDC94, 0x8ED9, 0xDC95, 0x8EDA, 0xDC96, + 0x8EDB, 0xDC97, 0x8EDC, 0xDC98, 0x8EDD, 0xDC99, 0x8EDE, 0xDC9A, 0x8EDF, 0xDC9B, 0x8EE0, 0xDC9C, 0x8EE1, 0xDC9D, 0x8EE2, 0xDC9E, + 0x8EE3, 0xDC9F, 0x8EE4, 0xDCA0, 0x8EE5, 0xDD40, 0x8EE6, 0xDD41, 0x8EE7, 0xDD42, 0x8EE8, 0xDD43, 0x8EE9, 0xDD44, 0x8EEA, 0xDD45, + 0x8EEB, 0xDD46, 0x8EEC, 0xDD47, 0x8EED, 0xDD48, 0x8EEE, 0xDD49, 0x8EEF, 0xDD4A, 0x8EF0, 0xDD4B, 0x8EF1, 0xDD4C, 0x8EF2, 0xDD4D, + 0x8EF3, 0xDD4E, 0x8EF4, 0xDD4F, 0x8EF5, 0xDD50, 0x8EF6, 0xDD51, 0x8EF7, 0xDD52, 0x8EF8, 0xDD53, 0x8EF9, 0xDD54, 0x8EFA, 0xDD55, + 0x8EFB, 0xDD56, 0x8EFC, 0xDD57, 0x8EFD, 0xDD58, 0x8EFE, 0xDD59, 0x8EFF, 0xDD5A, 0x8F00, 0xDD5B, 0x8F01, 0xDD5C, 0x8F02, 0xDD5D, + 0x8F03, 0xDD5E, 0x8F04, 0xDD5F, 0x8F05, 0xDD60, 0x8F06, 0xDD61, 0x8F07, 0xDD62, 0x8F08, 0xDD63, 0x8F09, 0xDD64, 0x8F0A, 0xDD65, + 0x8F0B, 0xDD66, 0x8F0C, 0xDD67, 0x8F0D, 0xDD68, 0x8F0E, 0xDD69, 0x8F0F, 0xDD6A, 0x8F10, 0xDD6B, 0x8F11, 0xDD6C, 0x8F12, 0xDD6D, + 0x8F13, 0xDD6E, 0x8F14, 0xDD6F, 0x8F15, 0xDD70, 0x8F16, 0xDD71, 0x8F17, 0xDD72, 0x8F18, 0xDD73, 0x8F19, 0xDD74, 0x8F1A, 0xDD75, + 0x8F1B, 0xDD76, 0x8F1C, 0xDD77, 0x8F1D, 0xDD78, 0x8F1E, 0xDD79, 0x8F1F, 0xDD7A, 0x8F20, 0xDD7B, 0x8F21, 0xDD7C, 0x8F22, 0xDD7D, + 0x8F23, 0xDD7E, 0x8F24, 0xDD80, 0x8F25, 0xDD81, 0x8F26, 0xDD82, 0x8F27, 0xDD83, 0x8F28, 0xDD84, 0x8F29, 0xDD85, 0x8F2A, 0xDD86, + 0x8F2B, 0xDD87, 0x8F2C, 0xDD88, 0x8F2D, 0xDD89, 0x8F2E, 0xDD8A, 0x8F2F, 0xDD8B, 0x8F30, 0xDD8C, 0x8F31, 0xDD8D, 0x8F32, 0xDD8E, + 0x8F33, 0xDD8F, 0x8F34, 0xDD90, 0x8F35, 0xDD91, 0x8F36, 0xDD92, 0x8F37, 0xDD93, 0x8F38, 0xDD94, 0x8F39, 0xDD95, 0x8F3A, 0xDD96, + 0x8F3B, 0xDD97, 0x8F3C, 0xDD98, 0x8F3D, 0xDD99, 0x8F3E, 0xDD9A, 0x8F3F, 0xDD9B, 0x8F40, 0xDD9C, 0x8F41, 0xDD9D, 0x8F42, 0xDD9E, + 0x8F43, 0xDD9F, 0x8F44, 0xDDA0, 0x8F45, 0xDE40, 0x8F46, 0xDE41, 0x8F47, 0xDE42, 0x8F48, 0xDE43, 0x8F49, 0xDE44, 0x8F4A, 0xDE45, + 0x8F4B, 0xDE46, 0x8F4C, 0xDE47, 0x8F4D, 0xDE48, 0x8F4E, 0xDE49, 0x8F4F, 0xDE4A, 0x8F50, 0xDE4B, 0x8F51, 0xDE4C, 0x8F52, 0xDE4D, + 0x8F53, 0xDE4E, 0x8F54, 0xDE4F, 0x8F55, 0xDE50, 0x8F56, 0xDE51, 0x8F57, 0xDE52, 0x8F58, 0xDE53, 0x8F59, 0xDE54, 0x8F5A, 0xDE55, + 0x8F5B, 0xDE56, 0x8F5C, 0xDE57, 0x8F5D, 0xDE58, 0x8F5E, 0xDE59, 0x8F5F, 0xDE5A, 0x8F60, 0xDE5B, 0x8F61, 0xDE5C, 0x8F62, 0xDE5D, + 0x8F63, 0xDE5E, 0x8F64, 0xDE5F, 0x8F65, 0xDE60, 0x8F66, 0xB3B5, 0x8F67, 0xD4FE, 0x8F68, 0xB9EC, 0x8F69, 0xD0F9, 0x8F6A, 0xDE61, + 0x8F6B, 0xE9ED, 0x8F6C, 0xD7AA, 0x8F6D, 0xE9EE, 0x8F6E, 0xC2D6, 0x8F6F, 0xC8ED, 0x8F70, 0xBAE4, 0x8F71, 0xE9EF, 0x8F72, 0xE9F0, + 0x8F73, 0xE9F1, 0x8F74, 0xD6E1, 0x8F75, 0xE9F2, 0x8F76, 0xE9F3, 0x8F77, 0xE9F5, 0x8F78, 0xE9F4, 0x8F79, 0xE9F6, 0x8F7A, 0xE9F7, + 0x8F7B, 0xC7E1, 0x8F7C, 0xE9F8, 0x8F7D, 0xD4D8, 0x8F7E, 0xE9F9, 0x8F7F, 0xBDCE, 0x8F80, 0xDE62, 0x8F81, 0xE9FA, 0x8F82, 0xE9FB, + 0x8F83, 0xBDCF, 0x8F84, 0xE9FC, 0x8F85, 0xB8A8, 0x8F86, 0xC1BE, 0x8F87, 0xE9FD, 0x8F88, 0xB1B2, 0x8F89, 0xBBD4, 0x8F8A, 0xB9F5, + 0x8F8B, 0xE9FE, 0x8F8C, 0xDE63, 0x8F8D, 0xEAA1, 0x8F8E, 0xEAA2, 0x8F8F, 0xEAA3, 0x8F90, 0xB7F8, 0x8F91, 0xBCAD, 0x8F92, 0xDE64, + 0x8F93, 0xCAE4, 0x8F94, 0xE0CE, 0x8F95, 0xD4AF, 0x8F96, 0xCFBD, 0x8F97, 0xD5B7, 0x8F98, 0xEAA4, 0x8F99, 0xD5DE, 0x8F9A, 0xEAA5, + 0x8F9B, 0xD0C1, 0x8F9C, 0xB9BC, 0x8F9D, 0xDE65, 0x8F9E, 0xB4C7, 0x8F9F, 0xB1D9, 0x8FA0, 0xDE66, 0x8FA1, 0xDE67, 0x8FA2, 0xDE68, + 0x8FA3, 0xC0B1, 0x8FA4, 0xDE69, 0x8FA5, 0xDE6A, 0x8FA6, 0xDE6B, 0x8FA7, 0xDE6C, 0x8FA8, 0xB1E6, 0x8FA9, 0xB1E7, 0x8FAA, 0xDE6D, + 0x8FAB, 0xB1E8, 0x8FAC, 0xDE6E, 0x8FAD, 0xDE6F, 0x8FAE, 0xDE70, 0x8FAF, 0xDE71, 0x8FB0, 0xB3BD, 0x8FB1, 0xC8E8, 0x8FB2, 0xDE72, + 0x8FB3, 0xDE73, 0x8FB4, 0xDE74, 0x8FB5, 0xDE75, 0x8FB6, 0xE5C1, 0x8FB7, 0xDE76, 0x8FB8, 0xDE77, 0x8FB9, 0xB1DF, 0x8FBA, 0xDE78, + 0x8FBB, 0xDE79, 0x8FBC, 0xDE7A, 0x8FBD, 0xC1C9, 0x8FBE, 0xB4EF, 0x8FBF, 0xDE7B, 0x8FC0, 0xDE7C, 0x8FC1, 0xC7A8, 0x8FC2, 0xD3D8, + 0x8FC3, 0xDE7D, 0x8FC4, 0xC6F9, 0x8FC5, 0xD1B8, 0x8FC6, 0xDE7E, 0x8FC7, 0xB9FD, 0x8FC8, 0xC2F5, 0x8FC9, 0xDE80, 0x8FCA, 0xDE81, + 0x8FCB, 0xDE82, 0x8FCC, 0xDE83, 0x8FCD, 0xDE84, 0x8FCE, 0xD3AD, 0x8FCF, 0xDE85, 0x8FD0, 0xD4CB, 0x8FD1, 0xBDFC, 0x8FD2, 0xDE86, + 0x8FD3, 0xE5C2, 0x8FD4, 0xB7B5, 0x8FD5, 0xE5C3, 0x8FD6, 0xDE87, 0x8FD7, 0xDE88, 0x8FD8, 0xBBB9, 0x8FD9, 0xD5E2, 0x8FDA, 0xDE89, + 0x8FDB, 0xBDF8, 0x8FDC, 0xD4B6, 0x8FDD, 0xCEA5, 0x8FDE, 0xC1AC, 0x8FDF, 0xB3D9, 0x8FE0, 0xDE8A, 0x8FE1, 0xDE8B, 0x8FE2, 0xCCF6, + 0x8FE3, 0xDE8C, 0x8FE4, 0xE5C6, 0x8FE5, 0xE5C4, 0x8FE6, 0xE5C8, 0x8FE7, 0xDE8D, 0x8FE8, 0xE5CA, 0x8FE9, 0xE5C7, 0x8FEA, 0xB5CF, + 0x8FEB, 0xC6C8, 0x8FEC, 0xDE8E, 0x8FED, 0xB5FC, 0x8FEE, 0xE5C5, 0x8FEF, 0xDE8F, 0x8FF0, 0xCAF6, 0x8FF1, 0xDE90, 0x8FF2, 0xDE91, + 0x8FF3, 0xE5C9, 0x8FF4, 0xDE92, 0x8FF5, 0xDE93, 0x8FF6, 0xDE94, 0x8FF7, 0xC3D4, 0x8FF8, 0xB1C5, 0x8FF9, 0xBCA3, 0x8FFA, 0xDE95, + 0x8FFB, 0xDE96, 0x8FFC, 0xDE97, 0x8FFD, 0xD7B7, 0x8FFE, 0xDE98, 0x8FFF, 0xDE99, 0x9000, 0xCDCB, 0x9001, 0xCBCD, 0x9002, 0xCACA, + 0x9003, 0xCCD3, 0x9004, 0xE5CC, 0x9005, 0xE5CB, 0x9006, 0xC4E6, 0x9007, 0xDE9A, 0x9008, 0xDE9B, 0x9009, 0xD1A1, 0x900A, 0xD1B7, + 0x900B, 0xE5CD, 0x900C, 0xDE9C, 0x900D, 0xE5D0, 0x900E, 0xDE9D, 0x900F, 0xCDB8, 0x9010, 0xD6F0, 0x9011, 0xE5CF, 0x9012, 0xB5DD, + 0x9013, 0xDE9E, 0x9014, 0xCDBE, 0x9015, 0xDE9F, 0x9016, 0xE5D1, 0x9017, 0xB6BA, 0x9018, 0xDEA0, 0x9019, 0xDF40, 0x901A, 0xCDA8, + 0x901B, 0xB9E4, 0x901C, 0xDF41, 0x901D, 0xCAC5, 0x901E, 0xB3D1, 0x901F, 0xCBD9, 0x9020, 0xD4EC, 0x9021, 0xE5D2, 0x9022, 0xB7EA, + 0x9023, 0xDF42, 0x9024, 0xDF43, 0x9025, 0xDF44, 0x9026, 0xE5CE, 0x9027, 0xDF45, 0x9028, 0xDF46, 0x9029, 0xDF47, 0x902A, 0xDF48, + 0x902B, 0xDF49, 0x902C, 0xDF4A, 0x902D, 0xE5D5, 0x902E, 0xB4FE, 0x902F, 0xE5D6, 0x9030, 0xDF4B, 0x9031, 0xDF4C, 0x9032, 0xDF4D, + 0x9033, 0xDF4E, 0x9034, 0xDF4F, 0x9035, 0xE5D3, 0x9036, 0xE5D4, 0x9037, 0xDF50, 0x9038, 0xD2DD, 0x9039, 0xDF51, 0x903A, 0xDF52, + 0x903B, 0xC2DF, 0x903C, 0xB1C6, 0x903D, 0xDF53, 0x903E, 0xD3E2, 0x903F, 0xDF54, 0x9040, 0xDF55, 0x9041, 0xB6DD, 0x9042, 0xCBEC, + 0x9043, 0xDF56, 0x9044, 0xE5D7, 0x9045, 0xDF57, 0x9046, 0xDF58, 0x9047, 0xD3F6, 0x9048, 0xDF59, 0x9049, 0xDF5A, 0x904A, 0xDF5B, + 0x904B, 0xDF5C, 0x904C, 0xDF5D, 0x904D, 0xB1E9, 0x904E, 0xDF5E, 0x904F, 0xB6F4, 0x9050, 0xE5DA, 0x9051, 0xE5D8, 0x9052, 0xE5D9, + 0x9053, 0xB5C0, 0x9054, 0xDF5F, 0x9055, 0xDF60, 0x9056, 0xDF61, 0x9057, 0xD2C5, 0x9058, 0xE5DC, 0x9059, 0xDF62, 0x905A, 0xDF63, + 0x905B, 0xE5DE, 0x905C, 0xDF64, 0x905D, 0xDF65, 0x905E, 0xDF66, 0x905F, 0xDF67, 0x9060, 0xDF68, 0x9061, 0xDF69, 0x9062, 0xE5DD, + 0x9063, 0xC7B2, 0x9064, 0xDF6A, 0x9065, 0xD2A3, 0x9066, 0xDF6B, 0x9067, 0xDF6C, 0x9068, 0xE5DB, 0x9069, 0xDF6D, 0x906A, 0xDF6E, + 0x906B, 0xDF6F, 0x906C, 0xDF70, 0x906D, 0xD4E2, 0x906E, 0xD5DA, 0x906F, 0xDF71, 0x9070, 0xDF72, 0x9071, 0xDF73, 0x9072, 0xDF74, + 0x9073, 0xDF75, 0x9074, 0xE5E0, 0x9075, 0xD7F1, 0x9076, 0xDF76, 0x9077, 0xDF77, 0x9078, 0xDF78, 0x9079, 0xDF79, 0x907A, 0xDF7A, + 0x907B, 0xDF7B, 0x907C, 0xDF7C, 0x907D, 0xE5E1, 0x907E, 0xDF7D, 0x907F, 0xB1DC, 0x9080, 0xD1FB, 0x9081, 0xDF7E, 0x9082, 0xE5E2, + 0x9083, 0xE5E4, 0x9084, 0xDF80, 0x9085, 0xDF81, 0x9086, 0xDF82, 0x9087, 0xDF83, 0x9088, 0xE5E3, 0x9089, 0xDF84, 0x908A, 0xDF85, + 0x908B, 0xE5E5, 0x908C, 0xDF86, 0x908D, 0xDF87, 0x908E, 0xDF88, 0x908F, 0xDF89, 0x9090, 0xDF8A, 0x9091, 0xD2D8, 0x9092, 0xDF8B, + 0x9093, 0xB5CB, 0x9094, 0xDF8C, 0x9095, 0xE7DF, 0x9096, 0xDF8D, 0x9097, 0xDAF5, 0x9098, 0xDF8E, 0x9099, 0xDAF8, 0x909A, 0xDF8F, + 0x909B, 0xDAF6, 0x909C, 0xDF90, 0x909D, 0xDAF7, 0x909E, 0xDF91, 0x909F, 0xDF92, 0x90A0, 0xDF93, 0x90A1, 0xDAFA, 0x90A2, 0xD0CF, + 0x90A3, 0xC4C7, 0x90A4, 0xDF94, 0x90A5, 0xDF95, 0x90A6, 0xB0EE, 0x90A7, 0xDF96, 0x90A8, 0xDF97, 0x90A9, 0xDF98, 0x90AA, 0xD0B0, + 0x90AB, 0xDF99, 0x90AC, 0xDAF9, 0x90AD, 0xDF9A, 0x90AE, 0xD3CA, 0x90AF, 0xBAAA, 0x90B0, 0xDBA2, 0x90B1, 0xC7F1, 0x90B2, 0xDF9B, + 0x90B3, 0xDAFC, 0x90B4, 0xDAFB, 0x90B5, 0xC9DB, 0x90B6, 0xDAFD, 0x90B7, 0xDF9C, 0x90B8, 0xDBA1, 0x90B9, 0xD7DE, 0x90BA, 0xDAFE, + 0x90BB, 0xC1DA, 0x90BC, 0xDF9D, 0x90BD, 0xDF9E, 0x90BE, 0xDBA5, 0x90BF, 0xDF9F, 0x90C0, 0xDFA0, 0x90C1, 0xD3F4, 0x90C2, 0xE040, + 0x90C3, 0xE041, 0x90C4, 0xDBA7, 0x90C5, 0xDBA4, 0x90C6, 0xE042, 0x90C7, 0xDBA8, 0x90C8, 0xE043, 0x90C9, 0xE044, 0x90CA, 0xBDBC, + 0x90CB, 0xE045, 0x90CC, 0xE046, 0x90CD, 0xE047, 0x90CE, 0xC0C9, 0x90CF, 0xDBA3, 0x90D0, 0xDBA6, 0x90D1, 0xD6A3, 0x90D2, 0xE048, + 0x90D3, 0xDBA9, 0x90D4, 0xE049, 0x90D5, 0xE04A, 0x90D6, 0xE04B, 0x90D7, 0xDBAD, 0x90D8, 0xE04C, 0x90D9, 0xE04D, 0x90DA, 0xE04E, + 0x90DB, 0xDBAE, 0x90DC, 0xDBAC, 0x90DD, 0xBAC2, 0x90DE, 0xE04F, 0x90DF, 0xE050, 0x90E0, 0xE051, 0x90E1, 0xBFA4, 0x90E2, 0xDBAB, + 0x90E3, 0xE052, 0x90E4, 0xE053, 0x90E5, 0xE054, 0x90E6, 0xDBAA, 0x90E7, 0xD4C7, 0x90E8, 0xB2BF, 0x90E9, 0xE055, 0x90EA, 0xE056, + 0x90EB, 0xDBAF, 0x90EC, 0xE057, 0x90ED, 0xB9F9, 0x90EE, 0xE058, 0x90EF, 0xDBB0, 0x90F0, 0xE059, 0x90F1, 0xE05A, 0x90F2, 0xE05B, + 0x90F3, 0xE05C, 0x90F4, 0xB3BB, 0x90F5, 0xE05D, 0x90F6, 0xE05E, 0x90F7, 0xE05F, 0x90F8, 0xB5A6, 0x90F9, 0xE060, 0x90FA, 0xE061, + 0x90FB, 0xE062, 0x90FC, 0xE063, 0x90FD, 0xB6BC, 0x90FE, 0xDBB1, 0x90FF, 0xE064, 0x9100, 0xE065, 0x9101, 0xE066, 0x9102, 0xB6F5, + 0x9103, 0xE067, 0x9104, 0xDBB2, 0x9105, 0xE068, 0x9106, 0xE069, 0x9107, 0xE06A, 0x9108, 0xE06B, 0x9109, 0xE06C, 0x910A, 0xE06D, + 0x910B, 0xE06E, 0x910C, 0xE06F, 0x910D, 0xE070, 0x910E, 0xE071, 0x910F, 0xE072, 0x9110, 0xE073, 0x9111, 0xE074, 0x9112, 0xE075, + 0x9113, 0xE076, 0x9114, 0xE077, 0x9115, 0xE078, 0x9116, 0xE079, 0x9117, 0xE07A, 0x9118, 0xE07B, 0x9119, 0xB1C9, 0x911A, 0xE07C, + 0x911B, 0xE07D, 0x911C, 0xE07E, 0x911D, 0xE080, 0x911E, 0xDBB4, 0x911F, 0xE081, 0x9120, 0xE082, 0x9121, 0xE083, 0x9122, 0xDBB3, + 0x9123, 0xDBB5, 0x9124, 0xE084, 0x9125, 0xE085, 0x9126, 0xE086, 0x9127, 0xE087, 0x9128, 0xE088, 0x9129, 0xE089, 0x912A, 0xE08A, + 0x912B, 0xE08B, 0x912C, 0xE08C, 0x912D, 0xE08D, 0x912E, 0xE08E, 0x912F, 0xDBB7, 0x9130, 0xE08F, 0x9131, 0xDBB6, 0x9132, 0xE090, + 0x9133, 0xE091, 0x9134, 0xE092, 0x9135, 0xE093, 0x9136, 0xE094, 0x9137, 0xE095, 0x9138, 0xE096, 0x9139, 0xDBB8, 0x913A, 0xE097, + 0x913B, 0xE098, 0x913C, 0xE099, 0x913D, 0xE09A, 0x913E, 0xE09B, 0x913F, 0xE09C, 0x9140, 0xE09D, 0x9141, 0xE09E, 0x9142, 0xE09F, + 0x9143, 0xDBB9, 0x9144, 0xE0A0, 0x9145, 0xE140, 0x9146, 0xDBBA, 0x9147, 0xE141, 0x9148, 0xE142, 0x9149, 0xD3CF, 0x914A, 0xF4FA, + 0x914B, 0xC7F5, 0x914C, 0xD7C3, 0x914D, 0xC5E4, 0x914E, 0xF4FC, 0x914F, 0xF4FD, 0x9150, 0xF4FB, 0x9151, 0xE143, 0x9152, 0xBEC6, + 0x9153, 0xE144, 0x9154, 0xE145, 0x9155, 0xE146, 0x9156, 0xE147, 0x9157, 0xD0EF, 0x9158, 0xE148, 0x9159, 0xE149, 0x915A, 0xB7D3, + 0x915B, 0xE14A, 0x915C, 0xE14B, 0x915D, 0xD4CD, 0x915E, 0xCCAA, 0x915F, 0xE14C, 0x9160, 0xE14D, 0x9161, 0xF5A2, 0x9162, 0xF5A1, + 0x9163, 0xBAA8, 0x9164, 0xF4FE, 0x9165, 0xCBD6, 0x9166, 0xE14E, 0x9167, 0xE14F, 0x9168, 0xE150, 0x9169, 0xF5A4, 0x916A, 0xC0D2, + 0x916B, 0xE151, 0x916C, 0xB3EA, 0x916D, 0xE152, 0x916E, 0xCDAA, 0x916F, 0xF5A5, 0x9170, 0xF5A3, 0x9171, 0xBDB4, 0x9172, 0xF5A8, + 0x9173, 0xE153, 0x9174, 0xF5A9, 0x9175, 0xBDCD, 0x9176, 0xC3B8, 0x9177, 0xBFE1, 0x9178, 0xCBE1, 0x9179, 0xF5AA, 0x917A, 0xE154, + 0x917B, 0xE155, 0x917C, 0xE156, 0x917D, 0xF5A6, 0x917E, 0xF5A7, 0x917F, 0xC4F0, 0x9180, 0xE157, 0x9181, 0xE158, 0x9182, 0xE159, + 0x9183, 0xE15A, 0x9184, 0xE15B, 0x9185, 0xF5AC, 0x9186, 0xE15C, 0x9187, 0xB4BC, 0x9188, 0xE15D, 0x9189, 0xD7ED, 0x918A, 0xE15E, + 0x918B, 0xB4D7, 0x918C, 0xF5AB, 0x918D, 0xF5AE, 0x918E, 0xE15F, 0x918F, 0xE160, 0x9190, 0xF5AD, 0x9191, 0xF5AF, 0x9192, 0xD0D1, + 0x9193, 0xE161, 0x9194, 0xE162, 0x9195, 0xE163, 0x9196, 0xE164, 0x9197, 0xE165, 0x9198, 0xE166, 0x9199, 0xE167, 0x919A, 0xC3D1, + 0x919B, 0xC8A9, 0x919C, 0xE168, 0x919D, 0xE169, 0x919E, 0xE16A, 0x919F, 0xE16B, 0x91A0, 0xE16C, 0x91A1, 0xE16D, 0x91A2, 0xF5B0, + 0x91A3, 0xF5B1, 0x91A4, 0xE16E, 0x91A5, 0xE16F, 0x91A6, 0xE170, 0x91A7, 0xE171, 0x91A8, 0xE172, 0x91A9, 0xE173, 0x91AA, 0xF5B2, + 0x91AB, 0xE174, 0x91AC, 0xE175, 0x91AD, 0xF5B3, 0x91AE, 0xF5B4, 0x91AF, 0xF5B5, 0x91B0, 0xE176, 0x91B1, 0xE177, 0x91B2, 0xE178, + 0x91B3, 0xE179, 0x91B4, 0xF5B7, 0x91B5, 0xF5B6, 0x91B6, 0xE17A, 0x91B7, 0xE17B, 0x91B8, 0xE17C, 0x91B9, 0xE17D, 0x91BA, 0xF5B8, + 0x91BB, 0xE17E, 0x91BC, 0xE180, 0x91BD, 0xE181, 0x91BE, 0xE182, 0x91BF, 0xE183, 0x91C0, 0xE184, 0x91C1, 0xE185, 0x91C2, 0xE186, + 0x91C3, 0xE187, 0x91C4, 0xE188, 0x91C5, 0xE189, 0x91C6, 0xE18A, 0x91C7, 0xB2C9, 0x91C8, 0xE18B, 0x91C9, 0xD3D4, 0x91CA, 0xCACD, + 0x91CB, 0xE18C, 0x91CC, 0xC0EF, 0x91CD, 0xD6D8, 0x91CE, 0xD2B0, 0x91CF, 0xC1BF, 0x91D0, 0xE18D, 0x91D1, 0xBDF0, 0x91D2, 0xE18E, + 0x91D3, 0xE18F, 0x91D4, 0xE190, 0x91D5, 0xE191, 0x91D6, 0xE192, 0x91D7, 0xE193, 0x91D8, 0xE194, 0x91D9, 0xE195, 0x91DA, 0xE196, + 0x91DB, 0xE197, 0x91DC, 0xB8AA, 0x91DD, 0xE198, 0x91DE, 0xE199, 0x91DF, 0xE19A, 0x91E0, 0xE19B, 0x91E1, 0xE19C, 0x91E2, 0xE19D, + 0x91E3, 0xE19E, 0x91E4, 0xE19F, 0x91E5, 0xE1A0, 0x91E6, 0xE240, 0x91E7, 0xE241, 0x91E8, 0xE242, 0x91E9, 0xE243, 0x91EA, 0xE244, + 0x91EB, 0xE245, 0x91EC, 0xE246, 0x91ED, 0xE247, 0x91EE, 0xE248, 0x91EF, 0xE249, 0x91F0, 0xE24A, 0x91F1, 0xE24B, 0x91F2, 0xE24C, + 0x91F3, 0xE24D, 0x91F4, 0xE24E, 0x91F5, 0xE24F, 0x91F6, 0xE250, 0x91F7, 0xE251, 0x91F8, 0xE252, 0x91F9, 0xE253, 0x91FA, 0xE254, + 0x91FB, 0xE255, 0x91FC, 0xE256, 0x91FD, 0xE257, 0x91FE, 0xE258, 0x91FF, 0xE259, 0x9200, 0xE25A, 0x9201, 0xE25B, 0x9202, 0xE25C, + 0x9203, 0xE25D, 0x9204, 0xE25E, 0x9205, 0xE25F, 0x9206, 0xE260, 0x9207, 0xE261, 0x9208, 0xE262, 0x9209, 0xE263, 0x920A, 0xE264, + 0x920B, 0xE265, 0x920C, 0xE266, 0x920D, 0xE267, 0x920E, 0xE268, 0x920F, 0xE269, 0x9210, 0xE26A, 0x9211, 0xE26B, 0x9212, 0xE26C, + 0x9213, 0xE26D, 0x9214, 0xE26E, 0x9215, 0xE26F, 0x9216, 0xE270, 0x9217, 0xE271, 0x9218, 0xE272, 0x9219, 0xE273, 0x921A, 0xE274, + 0x921B, 0xE275, 0x921C, 0xE276, 0x921D, 0xE277, 0x921E, 0xE278, 0x921F, 0xE279, 0x9220, 0xE27A, 0x9221, 0xE27B, 0x9222, 0xE27C, + 0x9223, 0xE27D, 0x9224, 0xE27E, 0x9225, 0xE280, 0x9226, 0xE281, 0x9227, 0xE282, 0x9228, 0xE283, 0x9229, 0xE284, 0x922A, 0xE285, + 0x922B, 0xE286, 0x922C, 0xE287, 0x922D, 0xE288, 0x922E, 0xE289, 0x922F, 0xE28A, 0x9230, 0xE28B, 0x9231, 0xE28C, 0x9232, 0xE28D, + 0x9233, 0xE28E, 0x9234, 0xE28F, 0x9235, 0xE290, 0x9236, 0xE291, 0x9237, 0xE292, 0x9238, 0xE293, 0x9239, 0xE294, 0x923A, 0xE295, + 0x923B, 0xE296, 0x923C, 0xE297, 0x923D, 0xE298, 0x923E, 0xE299, 0x923F, 0xE29A, 0x9240, 0xE29B, 0x9241, 0xE29C, 0x9242, 0xE29D, + 0x9243, 0xE29E, 0x9244, 0xE29F, 0x9245, 0xE2A0, 0x9246, 0xE340, 0x9247, 0xE341, 0x9248, 0xE342, 0x9249, 0xE343, 0x924A, 0xE344, + 0x924B, 0xE345, 0x924C, 0xE346, 0x924D, 0xE347, 0x924E, 0xE348, 0x924F, 0xE349, 0x9250, 0xE34A, 0x9251, 0xE34B, 0x9252, 0xE34C, + 0x9253, 0xE34D, 0x9254, 0xE34E, 0x9255, 0xE34F, 0x9256, 0xE350, 0x9257, 0xE351, 0x9258, 0xE352, 0x9259, 0xE353, 0x925A, 0xE354, + 0x925B, 0xE355, 0x925C, 0xE356, 0x925D, 0xE357, 0x925E, 0xE358, 0x925F, 0xE359, 0x9260, 0xE35A, 0x9261, 0xE35B, 0x9262, 0xE35C, + 0x9263, 0xE35D, 0x9264, 0xE35E, 0x9265, 0xE35F, 0x9266, 0xE360, 0x9267, 0xE361, 0x9268, 0xE362, 0x9269, 0xE363, 0x926A, 0xE364, + 0x926B, 0xE365, 0x926C, 0xE366, 0x926D, 0xE367, 0x926E, 0xE368, 0x926F, 0xE369, 0x9270, 0xE36A, 0x9271, 0xE36B, 0x9272, 0xE36C, + 0x9273, 0xE36D, 0x9274, 0xBCF8, 0x9275, 0xE36E, 0x9276, 0xE36F, 0x9277, 0xE370, 0x9278, 0xE371, 0x9279, 0xE372, 0x927A, 0xE373, + 0x927B, 0xE374, 0x927C, 0xE375, 0x927D, 0xE376, 0x927E, 0xE377, 0x927F, 0xE378, 0x9280, 0xE379, 0x9281, 0xE37A, 0x9282, 0xE37B, + 0x9283, 0xE37C, 0x9284, 0xE37D, 0x9285, 0xE37E, 0x9286, 0xE380, 0x9287, 0xE381, 0x9288, 0xE382, 0x9289, 0xE383, 0x928A, 0xE384, + 0x928B, 0xE385, 0x928C, 0xE386, 0x928D, 0xE387, 0x928E, 0xF6C6, 0x928F, 0xE388, 0x9290, 0xE389, 0x9291, 0xE38A, 0x9292, 0xE38B, + 0x9293, 0xE38C, 0x9294, 0xE38D, 0x9295, 0xE38E, 0x9296, 0xE38F, 0x9297, 0xE390, 0x9298, 0xE391, 0x9299, 0xE392, 0x929A, 0xE393, + 0x929B, 0xE394, 0x929C, 0xE395, 0x929D, 0xE396, 0x929E, 0xE397, 0x929F, 0xE398, 0x92A0, 0xE399, 0x92A1, 0xE39A, 0x92A2, 0xE39B, + 0x92A3, 0xE39C, 0x92A4, 0xE39D, 0x92A5, 0xE39E, 0x92A6, 0xE39F, 0x92A7, 0xE3A0, 0x92A8, 0xE440, 0x92A9, 0xE441, 0x92AA, 0xE442, + 0x92AB, 0xE443, 0x92AC, 0xE444, 0x92AD, 0xE445, 0x92AE, 0xF6C7, 0x92AF, 0xE446, 0x92B0, 0xE447, 0x92B1, 0xE448, 0x92B2, 0xE449, + 0x92B3, 0xE44A, 0x92B4, 0xE44B, 0x92B5, 0xE44C, 0x92B6, 0xE44D, 0x92B7, 0xE44E, 0x92B8, 0xE44F, 0x92B9, 0xE450, 0x92BA, 0xE451, + 0x92BB, 0xE452, 0x92BC, 0xE453, 0x92BD, 0xE454, 0x92BE, 0xE455, 0x92BF, 0xE456, 0x92C0, 0xE457, 0x92C1, 0xE458, 0x92C2, 0xE459, + 0x92C3, 0xE45A, 0x92C4, 0xE45B, 0x92C5, 0xE45C, 0x92C6, 0xE45D, 0x92C7, 0xE45E, 0x92C8, 0xF6C8, 0x92C9, 0xE45F, 0x92CA, 0xE460, + 0x92CB, 0xE461, 0x92CC, 0xE462, 0x92CD, 0xE463, 0x92CE, 0xE464, 0x92CF, 0xE465, 0x92D0, 0xE466, 0x92D1, 0xE467, 0x92D2, 0xE468, + 0x92D3, 0xE469, 0x92D4, 0xE46A, 0x92D5, 0xE46B, 0x92D6, 0xE46C, 0x92D7, 0xE46D, 0x92D8, 0xE46E, 0x92D9, 0xE46F, 0x92DA, 0xE470, + 0x92DB, 0xE471, 0x92DC, 0xE472, 0x92DD, 0xE473, 0x92DE, 0xE474, 0x92DF, 0xE475, 0x92E0, 0xE476, 0x92E1, 0xE477, 0x92E2, 0xE478, + 0x92E3, 0xE479, 0x92E4, 0xE47A, 0x92E5, 0xE47B, 0x92E6, 0xE47C, 0x92E7, 0xE47D, 0x92E8, 0xE47E, 0x92E9, 0xE480, 0x92EA, 0xE481, + 0x92EB, 0xE482, 0x92EC, 0xE483, 0x92ED, 0xE484, 0x92EE, 0xE485, 0x92EF, 0xE486, 0x92F0, 0xE487, 0x92F1, 0xE488, 0x92F2, 0xE489, + 0x92F3, 0xE48A, 0x92F4, 0xE48B, 0x92F5, 0xE48C, 0x92F6, 0xE48D, 0x92F7, 0xE48E, 0x92F8, 0xE48F, 0x92F9, 0xE490, 0x92FA, 0xE491, + 0x92FB, 0xE492, 0x92FC, 0xE493, 0x92FD, 0xE494, 0x92FE, 0xE495, 0x92FF, 0xE496, 0x9300, 0xE497, 0x9301, 0xE498, 0x9302, 0xE499, + 0x9303, 0xE49A, 0x9304, 0xE49B, 0x9305, 0xE49C, 0x9306, 0xE49D, 0x9307, 0xE49E, 0x9308, 0xE49F, 0x9309, 0xE4A0, 0x930A, 0xE540, + 0x930B, 0xE541, 0x930C, 0xE542, 0x930D, 0xE543, 0x930E, 0xE544, 0x930F, 0xE545, 0x9310, 0xE546, 0x9311, 0xE547, 0x9312, 0xE548, + 0x9313, 0xE549, 0x9314, 0xE54A, 0x9315, 0xE54B, 0x9316, 0xE54C, 0x9317, 0xE54D, 0x9318, 0xE54E, 0x9319, 0xE54F, 0x931A, 0xE550, + 0x931B, 0xE551, 0x931C, 0xE552, 0x931D, 0xE553, 0x931E, 0xE554, 0x931F, 0xE555, 0x9320, 0xE556, 0x9321, 0xE557, 0x9322, 0xE558, + 0x9323, 0xE559, 0x9324, 0xE55A, 0x9325, 0xE55B, 0x9326, 0xE55C, 0x9327, 0xE55D, 0x9328, 0xE55E, 0x9329, 0xE55F, 0x932A, 0xE560, + 0x932B, 0xE561, 0x932C, 0xE562, 0x932D, 0xE563, 0x932E, 0xE564, 0x932F, 0xE565, 0x9330, 0xE566, 0x9331, 0xE567, 0x9332, 0xE568, + 0x9333, 0xE569, 0x9334, 0xE56A, 0x9335, 0xE56B, 0x9336, 0xE56C, 0x9337, 0xE56D, 0x9338, 0xE56E, 0x9339, 0xE56F, 0x933A, 0xE570, + 0x933B, 0xE571, 0x933C, 0xE572, 0x933D, 0xE573, 0x933E, 0xF6C9, 0x933F, 0xE574, 0x9340, 0xE575, 0x9341, 0xE576, 0x9342, 0xE577, + 0x9343, 0xE578, 0x9344, 0xE579, 0x9345, 0xE57A, 0x9346, 0xE57B, 0x9347, 0xE57C, 0x9348, 0xE57D, 0x9349, 0xE57E, 0x934A, 0xE580, + 0x934B, 0xE581, 0x934C, 0xE582, 0x934D, 0xE583, 0x934E, 0xE584, 0x934F, 0xE585, 0x9350, 0xE586, 0x9351, 0xE587, 0x9352, 0xE588, + 0x9353, 0xE589, 0x9354, 0xE58A, 0x9355, 0xE58B, 0x9356, 0xE58C, 0x9357, 0xE58D, 0x9358, 0xE58E, 0x9359, 0xE58F, 0x935A, 0xE590, + 0x935B, 0xE591, 0x935C, 0xE592, 0x935D, 0xE593, 0x935E, 0xE594, 0x935F, 0xE595, 0x9360, 0xE596, 0x9361, 0xE597, 0x9362, 0xE598, + 0x9363, 0xE599, 0x9364, 0xE59A, 0x9365, 0xE59B, 0x9366, 0xE59C, 0x9367, 0xE59D, 0x9368, 0xE59E, 0x9369, 0xE59F, 0x936A, 0xF6CA, + 0x936B, 0xE5A0, 0x936C, 0xE640, 0x936D, 0xE641, 0x936E, 0xE642, 0x936F, 0xE643, 0x9370, 0xE644, 0x9371, 0xE645, 0x9372, 0xE646, + 0x9373, 0xE647, 0x9374, 0xE648, 0x9375, 0xE649, 0x9376, 0xE64A, 0x9377, 0xE64B, 0x9378, 0xE64C, 0x9379, 0xE64D, 0x937A, 0xE64E, + 0x937B, 0xE64F, 0x937C, 0xE650, 0x937D, 0xE651, 0x937E, 0xE652, 0x937F, 0xE653, 0x9380, 0xE654, 0x9381, 0xE655, 0x9382, 0xE656, + 0x9383, 0xE657, 0x9384, 0xE658, 0x9385, 0xE659, 0x9386, 0xE65A, 0x9387, 0xE65B, 0x9388, 0xE65C, 0x9389, 0xE65D, 0x938A, 0xE65E, + 0x938B, 0xE65F, 0x938C, 0xE660, 0x938D, 0xE661, 0x938E, 0xE662, 0x938F, 0xF6CC, 0x9390, 0xE663, 0x9391, 0xE664, 0x9392, 0xE665, + 0x9393, 0xE666, 0x9394, 0xE667, 0x9395, 0xE668, 0x9396, 0xE669, 0x9397, 0xE66A, 0x9398, 0xE66B, 0x9399, 0xE66C, 0x939A, 0xE66D, + 0x939B, 0xE66E, 0x939C, 0xE66F, 0x939D, 0xE670, 0x939E, 0xE671, 0x939F, 0xE672, 0x93A0, 0xE673, 0x93A1, 0xE674, 0x93A2, 0xE675, + 0x93A3, 0xE676, 0x93A4, 0xE677, 0x93A5, 0xE678, 0x93A6, 0xE679, 0x93A7, 0xE67A, 0x93A8, 0xE67B, 0x93A9, 0xE67C, 0x93AA, 0xE67D, + 0x93AB, 0xE67E, 0x93AC, 0xE680, 0x93AD, 0xE681, 0x93AE, 0xE682, 0x93AF, 0xE683, 0x93B0, 0xE684, 0x93B1, 0xE685, 0x93B2, 0xE686, + 0x93B3, 0xE687, 0x93B4, 0xE688, 0x93B5, 0xE689, 0x93B6, 0xE68A, 0x93B7, 0xE68B, 0x93B8, 0xE68C, 0x93B9, 0xE68D, 0x93BA, 0xE68E, + 0x93BB, 0xE68F, 0x93BC, 0xE690, 0x93BD, 0xE691, 0x93BE, 0xE692, 0x93BF, 0xE693, 0x93C0, 0xE694, 0x93C1, 0xE695, 0x93C2, 0xE696, + 0x93C3, 0xE697, 0x93C4, 0xE698, 0x93C5, 0xE699, 0x93C6, 0xE69A, 0x93C7, 0xE69B, 0x93C8, 0xE69C, 0x93C9, 0xE69D, 0x93CA, 0xF6CB, + 0x93CB, 0xE69E, 0x93CC, 0xE69F, 0x93CD, 0xE6A0, 0x93CE, 0xE740, 0x93CF, 0xE741, 0x93D0, 0xE742, 0x93D1, 0xE743, 0x93D2, 0xE744, + 0x93D3, 0xE745, 0x93D4, 0xE746, 0x93D5, 0xE747, 0x93D6, 0xF7E9, 0x93D7, 0xE748, 0x93D8, 0xE749, 0x93D9, 0xE74A, 0x93DA, 0xE74B, + 0x93DB, 0xE74C, 0x93DC, 0xE74D, 0x93DD, 0xE74E, 0x93DE, 0xE74F, 0x93DF, 0xE750, 0x93E0, 0xE751, 0x93E1, 0xE752, 0x93E2, 0xE753, + 0x93E3, 0xE754, 0x93E4, 0xE755, 0x93E5, 0xE756, 0x93E6, 0xE757, 0x93E7, 0xE758, 0x93E8, 0xE759, 0x93E9, 0xE75A, 0x93EA, 0xE75B, + 0x93EB, 0xE75C, 0x93EC, 0xE75D, 0x93ED, 0xE75E, 0x93EE, 0xE75F, 0x93EF, 0xE760, 0x93F0, 0xE761, 0x93F1, 0xE762, 0x93F2, 0xE763, + 0x93F3, 0xE764, 0x93F4, 0xE765, 0x93F5, 0xE766, 0x93F6, 0xE767, 0x93F7, 0xE768, 0x93F8, 0xE769, 0x93F9, 0xE76A, 0x93FA, 0xE76B, + 0x93FB, 0xE76C, 0x93FC, 0xE76D, 0x93FD, 0xE76E, 0x93FE, 0xE76F, 0x93FF, 0xE770, 0x9400, 0xE771, 0x9401, 0xE772, 0x9402, 0xE773, + 0x9403, 0xE774, 0x9404, 0xE775, 0x9405, 0xE776, 0x9406, 0xE777, 0x9407, 0xE778, 0x9408, 0xE779, 0x9409, 0xE77A, 0x940A, 0xE77B, + 0x940B, 0xE77C, 0x940C, 0xE77D, 0x940D, 0xE77E, 0x940E, 0xE780, 0x940F, 0xE781, 0x9410, 0xE782, 0x9411, 0xE783, 0x9412, 0xE784, + 0x9413, 0xE785, 0x9414, 0xE786, 0x9415, 0xE787, 0x9416, 0xE788, 0x9417, 0xE789, 0x9418, 0xE78A, 0x9419, 0xE78B, 0x941A, 0xE78C, + 0x941B, 0xE78D, 0x941C, 0xE78E, 0x941D, 0xE78F, 0x941E, 0xE790, 0x941F, 0xE791, 0x9420, 0xE792, 0x9421, 0xE793, 0x9422, 0xE794, + 0x9423, 0xE795, 0x9424, 0xE796, 0x9425, 0xE797, 0x9426, 0xE798, 0x9427, 0xE799, 0x9428, 0xE79A, 0x9429, 0xE79B, 0x942A, 0xE79C, + 0x942B, 0xE79D, 0x942C, 0xE79E, 0x942D, 0xE79F, 0x942E, 0xE7A0, 0x942F, 0xE840, 0x9430, 0xE841, 0x9431, 0xE842, 0x9432, 0xE843, + 0x9433, 0xE844, 0x9434, 0xE845, 0x9435, 0xE846, 0x9436, 0xE847, 0x9437, 0xE848, 0x9438, 0xE849, 0x9439, 0xE84A, 0x943A, 0xE84B, + 0x943B, 0xE84C, 0x943C, 0xE84D, 0x943D, 0xE84E, 0x943E, 0xF6CD, 0x943F, 0xE84F, 0x9440, 0xE850, 0x9441, 0xE851, 0x9442, 0xE852, + 0x9443, 0xE853, 0x9444, 0xE854, 0x9445, 0xE855, 0x9446, 0xE856, 0x9447, 0xE857, 0x9448, 0xE858, 0x9449, 0xE859, 0x944A, 0xE85A, + 0x944B, 0xE85B, 0x944C, 0xE85C, 0x944D, 0xE85D, 0x944E, 0xE85E, 0x944F, 0xE85F, 0x9450, 0xE860, 0x9451, 0xE861, 0x9452, 0xE862, + 0x9453, 0xE863, 0x9454, 0xE864, 0x9455, 0xE865, 0x9456, 0xE866, 0x9457, 0xE867, 0x9458, 0xE868, 0x9459, 0xE869, 0x945A, 0xE86A, + 0x945B, 0xE86B, 0x945C, 0xE86C, 0x945D, 0xE86D, 0x945E, 0xE86E, 0x945F, 0xE86F, 0x9460, 0xE870, 0x9461, 0xE871, 0x9462, 0xE872, + 0x9463, 0xE873, 0x9464, 0xE874, 0x9465, 0xE875, 0x9466, 0xE876, 0x9467, 0xE877, 0x9468, 0xE878, 0x9469, 0xE879, 0x946A, 0xE87A, + 0x946B, 0xF6CE, 0x946C, 0xE87B, 0x946D, 0xE87C, 0x946E, 0xE87D, 0x946F, 0xE87E, 0x9470, 0xE880, 0x9471, 0xE881, 0x9472, 0xE882, + 0x9473, 0xE883, 0x9474, 0xE884, 0x9475, 0xE885, 0x9476, 0xE886, 0x9477, 0xE887, 0x9478, 0xE888, 0x9479, 0xE889, 0x947A, 0xE88A, + 0x947B, 0xE88B, 0x947C, 0xE88C, 0x947D, 0xE88D, 0x947E, 0xE88E, 0x947F, 0xE88F, 0x9480, 0xE890, 0x9481, 0xE891, 0x9482, 0xE892, + 0x9483, 0xE893, 0x9484, 0xE894, 0x9485, 0xEEC4, 0x9486, 0xEEC5, 0x9487, 0xEEC6, 0x9488, 0xD5EB, 0x9489, 0xB6A4, 0x948A, 0xEEC8, + 0x948B, 0xEEC7, 0x948C, 0xEEC9, 0x948D, 0xEECA, 0x948E, 0xC7A5, 0x948F, 0xEECB, 0x9490, 0xEECC, 0x9491, 0xE895, 0x9492, 0xB7B0, + 0x9493, 0xB5F6, 0x9494, 0xEECD, 0x9495, 0xEECF, 0x9496, 0xE896, 0x9497, 0xEECE, 0x9498, 0xE897, 0x9499, 0xB8C6, 0x949A, 0xEED0, + 0x949B, 0xEED1, 0x949C, 0xEED2, 0x949D, 0xB6DB, 0x949E, 0xB3AE, 0x949F, 0xD6D3, 0x94A0, 0xC4C6, 0x94A1, 0xB1B5, 0x94A2, 0xB8D6, + 0x94A3, 0xEED3, 0x94A4, 0xEED4, 0x94A5, 0xD4BF, 0x94A6, 0xC7D5, 0x94A7, 0xBEFB, 0x94A8, 0xCED9, 0x94A9, 0xB9B3, 0x94AA, 0xEED6, + 0x94AB, 0xEED5, 0x94AC, 0xEED8, 0x94AD, 0xEED7, 0x94AE, 0xC5A5, 0x94AF, 0xEED9, 0x94B0, 0xEEDA, 0x94B1, 0xC7AE, 0x94B2, 0xEEDB, + 0x94B3, 0xC7AF, 0x94B4, 0xEEDC, 0x94B5, 0xB2A7, 0x94B6, 0xEEDD, 0x94B7, 0xEEDE, 0x94B8, 0xEEDF, 0x94B9, 0xEEE0, 0x94BA, 0xEEE1, + 0x94BB, 0xD7EA, 0x94BC, 0xEEE2, 0x94BD, 0xEEE3, 0x94BE, 0xBCD8, 0x94BF, 0xEEE4, 0x94C0, 0xD3CB, 0x94C1, 0xCCFA, 0x94C2, 0xB2AC, + 0x94C3, 0xC1E5, 0x94C4, 0xEEE5, 0x94C5, 0xC7A6, 0x94C6, 0xC3AD, 0x94C7, 0xE898, 0x94C8, 0xEEE6, 0x94C9, 0xEEE7, 0x94CA, 0xEEE8, + 0x94CB, 0xEEE9, 0x94CC, 0xEEEA, 0x94CD, 0xEEEB, 0x94CE, 0xEEEC, 0x94CF, 0xE899, 0x94D0, 0xEEED, 0x94D1, 0xEEEE, 0x94D2, 0xEEEF, + 0x94D3, 0xE89A, 0x94D4, 0xE89B, 0x94D5, 0xEEF0, 0x94D6, 0xEEF1, 0x94D7, 0xEEF2, 0x94D8, 0xEEF4, 0x94D9, 0xEEF3, 0x94DA, 0xE89C, + 0x94DB, 0xEEF5, 0x94DC, 0xCDAD, 0x94DD, 0xC2C1, 0x94DE, 0xEEF6, 0x94DF, 0xEEF7, 0x94E0, 0xEEF8, 0x94E1, 0xD5A1, 0x94E2, 0xEEF9, + 0x94E3, 0xCFB3, 0x94E4, 0xEEFA, 0x94E5, 0xEEFB, 0x94E6, 0xE89D, 0x94E7, 0xEEFC, 0x94E8, 0xEEFD, 0x94E9, 0xEFA1, 0x94EA, 0xEEFE, + 0x94EB, 0xEFA2, 0x94EC, 0xB8F5, 0x94ED, 0xC3FA, 0x94EE, 0xEFA3, 0x94EF, 0xEFA4, 0x94F0, 0xBDC2, 0x94F1, 0xD2BF, 0x94F2, 0xB2F9, + 0x94F3, 0xEFA5, 0x94F4, 0xEFA6, 0x94F5, 0xEFA7, 0x94F6, 0xD2F8, 0x94F7, 0xEFA8, 0x94F8, 0xD6FD, 0x94F9, 0xEFA9, 0x94FA, 0xC6CC, + 0x94FB, 0xE89E, 0x94FC, 0xEFAA, 0x94FD, 0xEFAB, 0x94FE, 0xC1B4, 0x94FF, 0xEFAC, 0x9500, 0xCFFA, 0x9501, 0xCBF8, 0x9502, 0xEFAE, + 0x9503, 0xEFAD, 0x9504, 0xB3FA, 0x9505, 0xB9F8, 0x9506, 0xEFAF, 0x9507, 0xEFB0, 0x9508, 0xD0E2, 0x9509, 0xEFB1, 0x950A, 0xEFB2, + 0x950B, 0xB7E6, 0x950C, 0xD0BF, 0x950D, 0xEFB3, 0x950E, 0xEFB4, 0x950F, 0xEFB5, 0x9510, 0xC8F1, 0x9511, 0xCCE0, 0x9512, 0xEFB6, + 0x9513, 0xEFB7, 0x9514, 0xEFB8, 0x9515, 0xEFB9, 0x9516, 0xEFBA, 0x9517, 0xD5E0, 0x9518, 0xEFBB, 0x9519, 0xB4ED, 0x951A, 0xC3AA, + 0x951B, 0xEFBC, 0x951C, 0xE89F, 0x951D, 0xEFBD, 0x951E, 0xEFBE, 0x951F, 0xEFBF, 0x9520, 0xE8A0, 0x9521, 0xCEFD, 0x9522, 0xEFC0, + 0x9523, 0xC2E0, 0x9524, 0xB4B8, 0x9525, 0xD7B6, 0x9526, 0xBDF5, 0x9527, 0xE940, 0x9528, 0xCFC7, 0x9529, 0xEFC3, 0x952A, 0xEFC1, + 0x952B, 0xEFC2, 0x952C, 0xEFC4, 0x952D, 0xB6A7, 0x952E, 0xBCFC, 0x952F, 0xBEE2, 0x9530, 0xC3CC, 0x9531, 0xEFC5, 0x9532, 0xEFC6, + 0x9533, 0xE941, 0x9534, 0xEFC7, 0x9535, 0xEFCF, 0x9536, 0xEFC8, 0x9537, 0xEFC9, 0x9538, 0xEFCA, 0x9539, 0xC7C2, 0x953A, 0xEFF1, + 0x953B, 0xB6CD, 0x953C, 0xEFCB, 0x953D, 0xE942, 0x953E, 0xEFCC, 0x953F, 0xEFCD, 0x9540, 0xB6C6, 0x9541, 0xC3BE, 0x9542, 0xEFCE, + 0x9543, 0xE943, 0x9544, 0xEFD0, 0x9545, 0xEFD1, 0x9546, 0xEFD2, 0x9547, 0xD5F2, 0x9548, 0xE944, 0x9549, 0xEFD3, 0x954A, 0xC4F7, + 0x954B, 0xE945, 0x954C, 0xEFD4, 0x954D, 0xC4F8, 0x954E, 0xEFD5, 0x954F, 0xEFD6, 0x9550, 0xB8E4, 0x9551, 0xB0F7, 0x9552, 0xEFD7, + 0x9553, 0xEFD8, 0x9554, 0xEFD9, 0x9555, 0xE946, 0x9556, 0xEFDA, 0x9557, 0xEFDB, 0x9558, 0xEFDC, 0x9559, 0xEFDD, 0x955A, 0xE947, + 0x955B, 0xEFDE, 0x955C, 0xBEB5, 0x955D, 0xEFE1, 0x955E, 0xEFDF, 0x955F, 0xEFE0, 0x9560, 0xE948, 0x9561, 0xEFE2, 0x9562, 0xEFE3, + 0x9563, 0xC1CD, 0x9564, 0xEFE4, 0x9565, 0xEFE5, 0x9566, 0xEFE6, 0x9567, 0xEFE7, 0x9568, 0xEFE8, 0x9569, 0xEFE9, 0x956A, 0xEFEA, + 0x956B, 0xEFEB, 0x956C, 0xEFEC, 0x956D, 0xC0D8, 0x956E, 0xE949, 0x956F, 0xEFED, 0x9570, 0xC1AD, 0x9571, 0xEFEE, 0x9572, 0xEFEF, + 0x9573, 0xEFF0, 0x9574, 0xE94A, 0x9575, 0xE94B, 0x9576, 0xCFE2, 0x9577, 0xE94C, 0x9578, 0xE94D, 0x9579, 0xE94E, 0x957A, 0xE94F, + 0x957B, 0xE950, 0x957C, 0xE951, 0x957D, 0xE952, 0x957E, 0xE953, 0x957F, 0xB3A4, 0x9580, 0xE954, 0x9581, 0xE955, 0x9582, 0xE956, + 0x9583, 0xE957, 0x9584, 0xE958, 0x9585, 0xE959, 0x9586, 0xE95A, 0x9587, 0xE95B, 0x9588, 0xE95C, 0x9589, 0xE95D, 0x958A, 0xE95E, + 0x958B, 0xE95F, 0x958C, 0xE960, 0x958D, 0xE961, 0x958E, 0xE962, 0x958F, 0xE963, 0x9590, 0xE964, 0x9591, 0xE965, 0x9592, 0xE966, + 0x9593, 0xE967, 0x9594, 0xE968, 0x9595, 0xE969, 0x9596, 0xE96A, 0x9597, 0xE96B, 0x9598, 0xE96C, 0x9599, 0xE96D, 0x959A, 0xE96E, + 0x959B, 0xE96F, 0x959C, 0xE970, 0x959D, 0xE971, 0x959E, 0xE972, 0x959F, 0xE973, 0x95A0, 0xE974, 0x95A1, 0xE975, 0x95A2, 0xE976, + 0x95A3, 0xE977, 0x95A4, 0xE978, 0x95A5, 0xE979, 0x95A6, 0xE97A, 0x95A7, 0xE97B, 0x95A8, 0xE97C, 0x95A9, 0xE97D, 0x95AA, 0xE97E, + 0x95AB, 0xE980, 0x95AC, 0xE981, 0x95AD, 0xE982, 0x95AE, 0xE983, 0x95AF, 0xE984, 0x95B0, 0xE985, 0x95B1, 0xE986, 0x95B2, 0xE987, + 0x95B3, 0xE988, 0x95B4, 0xE989, 0x95B5, 0xE98A, 0x95B6, 0xE98B, 0x95B7, 0xE98C, 0x95B8, 0xE98D, 0x95B9, 0xE98E, 0x95BA, 0xE98F, + 0x95BB, 0xE990, 0x95BC, 0xE991, 0x95BD, 0xE992, 0x95BE, 0xE993, 0x95BF, 0xE994, 0x95C0, 0xE995, 0x95C1, 0xE996, 0x95C2, 0xE997, + 0x95C3, 0xE998, 0x95C4, 0xE999, 0x95C5, 0xE99A, 0x95C6, 0xE99B, 0x95C7, 0xE99C, 0x95C8, 0xE99D, 0x95C9, 0xE99E, 0x95CA, 0xE99F, + 0x95CB, 0xE9A0, 0x95CC, 0xEA40, 0x95CD, 0xEA41, 0x95CE, 0xEA42, 0x95CF, 0xEA43, 0x95D0, 0xEA44, 0x95D1, 0xEA45, 0x95D2, 0xEA46, + 0x95D3, 0xEA47, 0x95D4, 0xEA48, 0x95D5, 0xEA49, 0x95D6, 0xEA4A, 0x95D7, 0xEA4B, 0x95D8, 0xEA4C, 0x95D9, 0xEA4D, 0x95DA, 0xEA4E, + 0x95DB, 0xEA4F, 0x95DC, 0xEA50, 0x95DD, 0xEA51, 0x95DE, 0xEA52, 0x95DF, 0xEA53, 0x95E0, 0xEA54, 0x95E1, 0xEA55, 0x95E2, 0xEA56, + 0x95E3, 0xEA57, 0x95E4, 0xEA58, 0x95E5, 0xEA59, 0x95E6, 0xEA5A, 0x95E7, 0xEA5B, 0x95E8, 0xC3C5, 0x95E9, 0xE3C5, 0x95EA, 0xC9C1, + 0x95EB, 0xE3C6, 0x95EC, 0xEA5C, 0x95ED, 0xB1D5, 0x95EE, 0xCECA, 0x95EF, 0xB4B3, 0x95F0, 0xC8F2, 0x95F1, 0xE3C7, 0x95F2, 0xCFD0, + 0x95F3, 0xE3C8, 0x95F4, 0xBCE4, 0x95F5, 0xE3C9, 0x95F6, 0xE3CA, 0x95F7, 0xC3C6, 0x95F8, 0xD5A2, 0x95F9, 0xC4D6, 0x95FA, 0xB9EB, + 0x95FB, 0xCEC5, 0x95FC, 0xE3CB, 0x95FD, 0xC3F6, 0x95FE, 0xE3CC, 0x95FF, 0xEA5D, 0x9600, 0xB7A7, 0x9601, 0xB8F3, 0x9602, 0xBAD2, + 0x9603, 0xE3CD, 0x9604, 0xE3CE, 0x9605, 0xD4C4, 0x9606, 0xE3CF, 0x9607, 0xEA5E, 0x9608, 0xE3D0, 0x9609, 0xD1CB, 0x960A, 0xE3D1, + 0x960B, 0xE3D2, 0x960C, 0xE3D3, 0x960D, 0xE3D4, 0x960E, 0xD1D6, 0x960F, 0xE3D5, 0x9610, 0xB2FB, 0x9611, 0xC0BB, 0x9612, 0xE3D6, + 0x9613, 0xEA5F, 0x9614, 0xC0AB, 0x9615, 0xE3D7, 0x9616, 0xE3D8, 0x9617, 0xE3D9, 0x9618, 0xEA60, 0x9619, 0xE3DA, 0x961A, 0xE3DB, + 0x961B, 0xEA61, 0x961C, 0xB8B7, 0x961D, 0xDAE2, 0x961E, 0xEA62, 0x961F, 0xB6D3, 0x9620, 0xEA63, 0x9621, 0xDAE4, 0x9622, 0xDAE3, + 0x9623, 0xEA64, 0x9624, 0xEA65, 0x9625, 0xEA66, 0x9626, 0xEA67, 0x9627, 0xEA68, 0x9628, 0xEA69, 0x9629, 0xEA6A, 0x962A, 0xDAE6, + 0x962B, 0xEA6B, 0x962C, 0xEA6C, 0x962D, 0xEA6D, 0x962E, 0xC8EE, 0x962F, 0xEA6E, 0x9630, 0xEA6F, 0x9631, 0xDAE5, 0x9632, 0xB7C0, + 0x9633, 0xD1F4, 0x9634, 0xD2F5, 0x9635, 0xD5F3, 0x9636, 0xBDD7, 0x9637, 0xEA70, 0x9638, 0xEA71, 0x9639, 0xEA72, 0x963A, 0xEA73, + 0x963B, 0xD7E8, 0x963C, 0xDAE8, 0x963D, 0xDAE7, 0x963E, 0xEA74, 0x963F, 0xB0A2, 0x9640, 0xCDD3, 0x9641, 0xEA75, 0x9642, 0xDAE9, + 0x9643, 0xEA76, 0x9644, 0xB8BD, 0x9645, 0xBCCA, 0x9646, 0xC2BD, 0x9647, 0xC2A4, 0x9648, 0xB3C2, 0x9649, 0xDAEA, 0x964A, 0xEA77, + 0x964B, 0xC2AA, 0x964C, 0xC4B0, 0x964D, 0xBDB5, 0x964E, 0xEA78, 0x964F, 0xEA79, 0x9650, 0xCFDE, 0x9651, 0xEA7A, 0x9652, 0xEA7B, + 0x9653, 0xEA7C, 0x9654, 0xDAEB, 0x9655, 0xC9C2, 0x9656, 0xEA7D, 0x9657, 0xEA7E, 0x9658, 0xEA80, 0x9659, 0xEA81, 0x965A, 0xEA82, + 0x965B, 0xB1DD, 0x965C, 0xEA83, 0x965D, 0xEA84, 0x965E, 0xEA85, 0x965F, 0xDAEC, 0x9660, 0xEA86, 0x9661, 0xB6B8, 0x9662, 0xD4BA, + 0x9663, 0xEA87, 0x9664, 0xB3FD, 0x9665, 0xEA88, 0x9666, 0xEA89, 0x9667, 0xDAED, 0x9668, 0xD4C9, 0x9669, 0xCFD5, 0x966A, 0xC5E3, + 0x966B, 0xEA8A, 0x966C, 0xDAEE, 0x966D, 0xEA8B, 0x966E, 0xEA8C, 0x966F, 0xEA8D, 0x9670, 0xEA8E, 0x9671, 0xEA8F, 0x9672, 0xDAEF, + 0x9673, 0xEA90, 0x9674, 0xDAF0, 0x9675, 0xC1EA, 0x9676, 0xCCD5, 0x9677, 0xCFDD, 0x9678, 0xEA91, 0x9679, 0xEA92, 0x967A, 0xEA93, + 0x967B, 0xEA94, 0x967C, 0xEA95, 0x967D, 0xEA96, 0x967E, 0xEA97, 0x967F, 0xEA98, 0x9680, 0xEA99, 0x9681, 0xEA9A, 0x9682, 0xEA9B, + 0x9683, 0xEA9C, 0x9684, 0xEA9D, 0x9685, 0xD3E7, 0x9686, 0xC2A1, 0x9687, 0xEA9E, 0x9688, 0xDAF1, 0x9689, 0xEA9F, 0x968A, 0xEAA0, + 0x968B, 0xCBE5, 0x968C, 0xEB40, 0x968D, 0xDAF2, 0x968E, 0xEB41, 0x968F, 0xCBE6, 0x9690, 0xD2FE, 0x9691, 0xEB42, 0x9692, 0xEB43, + 0x9693, 0xEB44, 0x9694, 0xB8F4, 0x9695, 0xEB45, 0x9696, 0xEB46, 0x9697, 0xDAF3, 0x9698, 0xB0AF, 0x9699, 0xCFB6, 0x969A, 0xEB47, + 0x969B, 0xEB48, 0x969C, 0xD5CF, 0x969D, 0xEB49, 0x969E, 0xEB4A, 0x969F, 0xEB4B, 0x96A0, 0xEB4C, 0x96A1, 0xEB4D, 0x96A2, 0xEB4E, + 0x96A3, 0xEB4F, 0x96A4, 0xEB50, 0x96A5, 0xEB51, 0x96A6, 0xEB52, 0x96A7, 0xCBED, 0x96A8, 0xEB53, 0x96A9, 0xEB54, 0x96AA, 0xEB55, + 0x96AB, 0xEB56, 0x96AC, 0xEB57, 0x96AD, 0xEB58, 0x96AE, 0xEB59, 0x96AF, 0xEB5A, 0x96B0, 0xDAF4, 0x96B1, 0xEB5B, 0x96B2, 0xEB5C, + 0x96B3, 0xE3C4, 0x96B4, 0xEB5D, 0x96B5, 0xEB5E, 0x96B6, 0xC1A5, 0x96B7, 0xEB5F, 0x96B8, 0xEB60, 0x96B9, 0xF6BF, 0x96BA, 0xEB61, + 0x96BB, 0xEB62, 0x96BC, 0xF6C0, 0x96BD, 0xF6C1, 0x96BE, 0xC4D1, 0x96BF, 0xEB63, 0x96C0, 0xC8B8, 0x96C1, 0xD1E3, 0x96C2, 0xEB64, + 0x96C3, 0xEB65, 0x96C4, 0xD0DB, 0x96C5, 0xD1C5, 0x96C6, 0xBCAF, 0x96C7, 0xB9CD, 0x96C8, 0xEB66, 0x96C9, 0xEFF4, 0x96CA, 0xEB67, + 0x96CB, 0xEB68, 0x96CC, 0xB4C6, 0x96CD, 0xD3BA, 0x96CE, 0xF6C2, 0x96CF, 0xB3FB, 0x96D0, 0xEB69, 0x96D1, 0xEB6A, 0x96D2, 0xF6C3, + 0x96D3, 0xEB6B, 0x96D4, 0xEB6C, 0x96D5, 0xB5F1, 0x96D6, 0xEB6D, 0x96D7, 0xEB6E, 0x96D8, 0xEB6F, 0x96D9, 0xEB70, 0x96DA, 0xEB71, + 0x96DB, 0xEB72, 0x96DC, 0xEB73, 0x96DD, 0xEB74, 0x96DE, 0xEB75, 0x96DF, 0xEB76, 0x96E0, 0xF6C5, 0x96E1, 0xEB77, 0x96E2, 0xEB78, + 0x96E3, 0xEB79, 0x96E4, 0xEB7A, 0x96E5, 0xEB7B, 0x96E6, 0xEB7C, 0x96E7, 0xEB7D, 0x96E8, 0xD3EA, 0x96E9, 0xF6A7, 0x96EA, 0xD1A9, + 0x96EB, 0xEB7E, 0x96EC, 0xEB80, 0x96ED, 0xEB81, 0x96EE, 0xEB82, 0x96EF, 0xF6A9, 0x96F0, 0xEB83, 0x96F1, 0xEB84, 0x96F2, 0xEB85, + 0x96F3, 0xF6A8, 0x96F4, 0xEB86, 0x96F5, 0xEB87, 0x96F6, 0xC1E3, 0x96F7, 0xC0D7, 0x96F8, 0xEB88, 0x96F9, 0xB1A2, 0x96FA, 0xEB89, + 0x96FB, 0xEB8A, 0x96FC, 0xEB8B, 0x96FD, 0xEB8C, 0x96FE, 0xCEED, 0x96FF, 0xEB8D, 0x9700, 0xD0E8, 0x9701, 0xF6AB, 0x9702, 0xEB8E, + 0x9703, 0xEB8F, 0x9704, 0xCFF6, 0x9705, 0xEB90, 0x9706, 0xF6AA, 0x9707, 0xD5F0, 0x9708, 0xF6AC, 0x9709, 0xC3B9, 0x970A, 0xEB91, + 0x970B, 0xEB92, 0x970C, 0xEB93, 0x970D, 0xBBF4, 0x970E, 0xF6AE, 0x970F, 0xF6AD, 0x9710, 0xEB94, 0x9711, 0xEB95, 0x9712, 0xEB96, + 0x9713, 0xC4DE, 0x9714, 0xEB97, 0x9715, 0xEB98, 0x9716, 0xC1D8, 0x9717, 0xEB99, 0x9718, 0xEB9A, 0x9719, 0xEB9B, 0x971A, 0xEB9C, + 0x971B, 0xEB9D, 0x971C, 0xCBAA, 0x971D, 0xEB9E, 0x971E, 0xCFBC, 0x971F, 0xEB9F, 0x9720, 0xEBA0, 0x9721, 0xEC40, 0x9722, 0xEC41, + 0x9723, 0xEC42, 0x9724, 0xEC43, 0x9725, 0xEC44, 0x9726, 0xEC45, 0x9727, 0xEC46, 0x9728, 0xEC47, 0x9729, 0xEC48, 0x972A, 0xF6AF, + 0x972B, 0xEC49, 0x972C, 0xEC4A, 0x972D, 0xF6B0, 0x972E, 0xEC4B, 0x972F, 0xEC4C, 0x9730, 0xF6B1, 0x9731, 0xEC4D, 0x9732, 0xC2B6, + 0x9733, 0xEC4E, 0x9734, 0xEC4F, 0x9735, 0xEC50, 0x9736, 0xEC51, 0x9737, 0xEC52, 0x9738, 0xB0D4, 0x9739, 0xC5F9, 0x973A, 0xEC53, + 0x973B, 0xEC54, 0x973C, 0xEC55, 0x973D, 0xEC56, 0x973E, 0xF6B2, 0x973F, 0xEC57, 0x9740, 0xEC58, 0x9741, 0xEC59, 0x9742, 0xEC5A, + 0x9743, 0xEC5B, 0x9744, 0xEC5C, 0x9745, 0xEC5D, 0x9746, 0xEC5E, 0x9747, 0xEC5F, 0x9748, 0xEC60, 0x9749, 0xEC61, 0x974A, 0xEC62, + 0x974B, 0xEC63, 0x974C, 0xEC64, 0x974D, 0xEC65, 0x974E, 0xEC66, 0x974F, 0xEC67, 0x9750, 0xEC68, 0x9751, 0xEC69, 0x9752, 0xC7E0, + 0x9753, 0xF6A6, 0x9754, 0xEC6A, 0x9755, 0xEC6B, 0x9756, 0xBEB8, 0x9757, 0xEC6C, 0x9758, 0xEC6D, 0x9759, 0xBEB2, 0x975A, 0xEC6E, + 0x975B, 0xB5E5, 0x975C, 0xEC6F, 0x975D, 0xEC70, 0x975E, 0xB7C7, 0x975F, 0xEC71, 0x9760, 0xBFBF, 0x9761, 0xC3D2, 0x9762, 0xC3E6, + 0x9763, 0xEC72, 0x9764, 0xEC73, 0x9765, 0xD8CC, 0x9766, 0xEC74, 0x9767, 0xEC75, 0x9768, 0xEC76, 0x9769, 0xB8EF, 0x976A, 0xEC77, + 0x976B, 0xEC78, 0x976C, 0xEC79, 0x976D, 0xEC7A, 0x976E, 0xEC7B, 0x976F, 0xEC7C, 0x9770, 0xEC7D, 0x9771, 0xEC7E, 0x9772, 0xEC80, + 0x9773, 0xBDF9, 0x9774, 0xD1A5, 0x9775, 0xEC81, 0x9776, 0xB0D0, 0x9777, 0xEC82, 0x9778, 0xEC83, 0x9779, 0xEC84, 0x977A, 0xEC85, + 0x977B, 0xEC86, 0x977C, 0xF7B0, 0x977D, 0xEC87, 0x977E, 0xEC88, 0x977F, 0xEC89, 0x9780, 0xEC8A, 0x9781, 0xEC8B, 0x9782, 0xEC8C, + 0x9783, 0xEC8D, 0x9784, 0xEC8E, 0x9785, 0xF7B1, 0x9786, 0xEC8F, 0x9787, 0xEC90, 0x9788, 0xEC91, 0x9789, 0xEC92, 0x978A, 0xEC93, + 0x978B, 0xD0AC, 0x978C, 0xEC94, 0x978D, 0xB0B0, 0x978E, 0xEC95, 0x978F, 0xEC96, 0x9790, 0xEC97, 0x9791, 0xF7B2, 0x9792, 0xF7B3, + 0x9793, 0xEC98, 0x9794, 0xF7B4, 0x9795, 0xEC99, 0x9796, 0xEC9A, 0x9797, 0xEC9B, 0x9798, 0xC7CA, 0x9799, 0xEC9C, 0x979A, 0xEC9D, + 0x979B, 0xEC9E, 0x979C, 0xEC9F, 0x979D, 0xECA0, 0x979E, 0xED40, 0x979F, 0xED41, 0x97A0, 0xBECF, 0x97A1, 0xED42, 0x97A2, 0xED43, + 0x97A3, 0xF7B7, 0x97A4, 0xED44, 0x97A5, 0xED45, 0x97A6, 0xED46, 0x97A7, 0xED47, 0x97A8, 0xED48, 0x97A9, 0xED49, 0x97AA, 0xED4A, + 0x97AB, 0xF7B6, 0x97AC, 0xED4B, 0x97AD, 0xB1DE, 0x97AE, 0xED4C, 0x97AF, 0xF7B5, 0x97B0, 0xED4D, 0x97B1, 0xED4E, 0x97B2, 0xF7B8, + 0x97B3, 0xED4F, 0x97B4, 0xF7B9, 0x97B5, 0xED50, 0x97B6, 0xED51, 0x97B7, 0xED52, 0x97B8, 0xED53, 0x97B9, 0xED54, 0x97BA, 0xED55, + 0x97BB, 0xED56, 0x97BC, 0xED57, 0x97BD, 0xED58, 0x97BE, 0xED59, 0x97BF, 0xED5A, 0x97C0, 0xED5B, 0x97C1, 0xED5C, 0x97C2, 0xED5D, + 0x97C3, 0xED5E, 0x97C4, 0xED5F, 0x97C5, 0xED60, 0x97C6, 0xED61, 0x97C7, 0xED62, 0x97C8, 0xED63, 0x97C9, 0xED64, 0x97CA, 0xED65, + 0x97CB, 0xED66, 0x97CC, 0xED67, 0x97CD, 0xED68, 0x97CE, 0xED69, 0x97CF, 0xED6A, 0x97D0, 0xED6B, 0x97D1, 0xED6C, 0x97D2, 0xED6D, + 0x97D3, 0xED6E, 0x97D4, 0xED6F, 0x97D5, 0xED70, 0x97D6, 0xED71, 0x97D7, 0xED72, 0x97D8, 0xED73, 0x97D9, 0xED74, 0x97DA, 0xED75, + 0x97DB, 0xED76, 0x97DC, 0xED77, 0x97DD, 0xED78, 0x97DE, 0xED79, 0x97DF, 0xED7A, 0x97E0, 0xED7B, 0x97E1, 0xED7C, 0x97E2, 0xED7D, + 0x97E3, 0xED7E, 0x97E4, 0xED80, 0x97E5, 0xED81, 0x97E6, 0xCEA4, 0x97E7, 0xC8CD, 0x97E8, 0xED82, 0x97E9, 0xBAAB, 0x97EA, 0xE8B8, + 0x97EB, 0xE8B9, 0x97EC, 0xE8BA, 0x97ED, 0xBEC2, 0x97EE, 0xED83, 0x97EF, 0xED84, 0x97F0, 0xED85, 0x97F1, 0xED86, 0x97F2, 0xED87, + 0x97F3, 0xD2F4, 0x97F4, 0xED88, 0x97F5, 0xD4CF, 0x97F6, 0xC9D8, 0x97F7, 0xED89, 0x97F8, 0xED8A, 0x97F9, 0xED8B, 0x97FA, 0xED8C, + 0x97FB, 0xED8D, 0x97FC, 0xED8E, 0x97FD, 0xED8F, 0x97FE, 0xED90, 0x97FF, 0xED91, 0x9800, 0xED92, 0x9801, 0xED93, 0x9802, 0xED94, + 0x9803, 0xED95, 0x9804, 0xED96, 0x9805, 0xED97, 0x9806, 0xED98, 0x9807, 0xED99, 0x9808, 0xED9A, 0x9809, 0xED9B, 0x980A, 0xED9C, + 0x980B, 0xED9D, 0x980C, 0xED9E, 0x980D, 0xED9F, 0x980E, 0xEDA0, 0x980F, 0xEE40, 0x9810, 0xEE41, 0x9811, 0xEE42, 0x9812, 0xEE43, + 0x9813, 0xEE44, 0x9814, 0xEE45, 0x9815, 0xEE46, 0x9816, 0xEE47, 0x9817, 0xEE48, 0x9818, 0xEE49, 0x9819, 0xEE4A, 0x981A, 0xEE4B, + 0x981B, 0xEE4C, 0x981C, 0xEE4D, 0x981D, 0xEE4E, 0x981E, 0xEE4F, 0x981F, 0xEE50, 0x9820, 0xEE51, 0x9821, 0xEE52, 0x9822, 0xEE53, + 0x9823, 0xEE54, 0x9824, 0xEE55, 0x9825, 0xEE56, 0x9826, 0xEE57, 0x9827, 0xEE58, 0x9828, 0xEE59, 0x9829, 0xEE5A, 0x982A, 0xEE5B, + 0x982B, 0xEE5C, 0x982C, 0xEE5D, 0x982D, 0xEE5E, 0x982E, 0xEE5F, 0x982F, 0xEE60, 0x9830, 0xEE61, 0x9831, 0xEE62, 0x9832, 0xEE63, + 0x9833, 0xEE64, 0x9834, 0xEE65, 0x9835, 0xEE66, 0x9836, 0xEE67, 0x9837, 0xEE68, 0x9838, 0xEE69, 0x9839, 0xEE6A, 0x983A, 0xEE6B, + 0x983B, 0xEE6C, 0x983C, 0xEE6D, 0x983D, 0xEE6E, 0x983E, 0xEE6F, 0x983F, 0xEE70, 0x9840, 0xEE71, 0x9841, 0xEE72, 0x9842, 0xEE73, + 0x9843, 0xEE74, 0x9844, 0xEE75, 0x9845, 0xEE76, 0x9846, 0xEE77, 0x9847, 0xEE78, 0x9848, 0xEE79, 0x9849, 0xEE7A, 0x984A, 0xEE7B, + 0x984B, 0xEE7C, 0x984C, 0xEE7D, 0x984D, 0xEE7E, 0x984E, 0xEE80, 0x984F, 0xEE81, 0x9850, 0xEE82, 0x9851, 0xEE83, 0x9852, 0xEE84, + 0x9853, 0xEE85, 0x9854, 0xEE86, 0x9855, 0xEE87, 0x9856, 0xEE88, 0x9857, 0xEE89, 0x9858, 0xEE8A, 0x9859, 0xEE8B, 0x985A, 0xEE8C, + 0x985B, 0xEE8D, 0x985C, 0xEE8E, 0x985D, 0xEE8F, 0x985E, 0xEE90, 0x985F, 0xEE91, 0x9860, 0xEE92, 0x9861, 0xEE93, 0x9862, 0xEE94, + 0x9863, 0xEE95, 0x9864, 0xEE96, 0x9865, 0xEE97, 0x9866, 0xEE98, 0x9867, 0xEE99, 0x9868, 0xEE9A, 0x9869, 0xEE9B, 0x986A, 0xEE9C, + 0x986B, 0xEE9D, 0x986C, 0xEE9E, 0x986D, 0xEE9F, 0x986E, 0xEEA0, 0x986F, 0xEF40, 0x9870, 0xEF41, 0x9871, 0xEF42, 0x9872, 0xEF43, + 0x9873, 0xEF44, 0x9874, 0xEF45, 0x9875, 0xD2B3, 0x9876, 0xB6A5, 0x9877, 0xC7EA, 0x9878, 0xF1FC, 0x9879, 0xCFEE, 0x987A, 0xCBB3, + 0x987B, 0xD0EB, 0x987C, 0xE7EF, 0x987D, 0xCDE7, 0x987E, 0xB9CB, 0x987F, 0xB6D9, 0x9880, 0xF1FD, 0x9881, 0xB0E4, 0x9882, 0xCBCC, + 0x9883, 0xF1FE, 0x9884, 0xD4A4, 0x9885, 0xC2AD, 0x9886, 0xC1EC, 0x9887, 0xC6C4, 0x9888, 0xBEB1, 0x9889, 0xF2A1, 0x988A, 0xBCD5, + 0x988B, 0xEF46, 0x988C, 0xF2A2, 0x988D, 0xF2A3, 0x988E, 0xEF47, 0x988F, 0xF2A4, 0x9890, 0xD2C3, 0x9891, 0xC6B5, 0x9892, 0xEF48, + 0x9893, 0xCDC7, 0x9894, 0xF2A5, 0x9895, 0xEF49, 0x9896, 0xD3B1, 0x9897, 0xBFC5, 0x9898, 0xCCE2, 0x9899, 0xEF4A, 0x989A, 0xF2A6, + 0x989B, 0xF2A7, 0x989C, 0xD1D5, 0x989D, 0xB6EE, 0x989E, 0xF2A8, 0x989F, 0xF2A9, 0x98A0, 0xB5DF, 0x98A1, 0xF2AA, 0x98A2, 0xF2AB, + 0x98A3, 0xEF4B, 0x98A4, 0xB2FC, 0x98A5, 0xF2AC, 0x98A6, 0xF2AD, 0x98A7, 0xC8A7, 0x98A8, 0xEF4C, 0x98A9, 0xEF4D, 0x98AA, 0xEF4E, + 0x98AB, 0xEF4F, 0x98AC, 0xEF50, 0x98AD, 0xEF51, 0x98AE, 0xEF52, 0x98AF, 0xEF53, 0x98B0, 0xEF54, 0x98B1, 0xEF55, 0x98B2, 0xEF56, + 0x98B3, 0xEF57, 0x98B4, 0xEF58, 0x98B5, 0xEF59, 0x98B6, 0xEF5A, 0x98B7, 0xEF5B, 0x98B8, 0xEF5C, 0x98B9, 0xEF5D, 0x98BA, 0xEF5E, + 0x98BB, 0xEF5F, 0x98BC, 0xEF60, 0x98BD, 0xEF61, 0x98BE, 0xEF62, 0x98BF, 0xEF63, 0x98C0, 0xEF64, 0x98C1, 0xEF65, 0x98C2, 0xEF66, + 0x98C3, 0xEF67, 0x98C4, 0xEF68, 0x98C5, 0xEF69, 0x98C6, 0xEF6A, 0x98C7, 0xEF6B, 0x98C8, 0xEF6C, 0x98C9, 0xEF6D, 0x98CA, 0xEF6E, + 0x98CB, 0xEF6F, 0x98CC, 0xEF70, 0x98CD, 0xEF71, 0x98CE, 0xB7E7, 0x98CF, 0xEF72, 0x98D0, 0xEF73, 0x98D1, 0xECA9, 0x98D2, 0xECAA, + 0x98D3, 0xECAB, 0x98D4, 0xEF74, 0x98D5, 0xECAC, 0x98D6, 0xEF75, 0x98D7, 0xEF76, 0x98D8, 0xC6AE, 0x98D9, 0xECAD, 0x98DA, 0xECAE, + 0x98DB, 0xEF77, 0x98DC, 0xEF78, 0x98DD, 0xEF79, 0x98DE, 0xB7C9, 0x98DF, 0xCAB3, 0x98E0, 0xEF7A, 0x98E1, 0xEF7B, 0x98E2, 0xEF7C, + 0x98E3, 0xEF7D, 0x98E4, 0xEF7E, 0x98E5, 0xEF80, 0x98E6, 0xEF81, 0x98E7, 0xE2B8, 0x98E8, 0xF7CF, 0x98E9, 0xEF82, 0x98EA, 0xEF83, + 0x98EB, 0xEF84, 0x98EC, 0xEF85, 0x98ED, 0xEF86, 0x98EE, 0xEF87, 0x98EF, 0xEF88, 0x98F0, 0xEF89, 0x98F1, 0xEF8A, 0x98F2, 0xEF8B, + 0x98F3, 0xEF8C, 0x98F4, 0xEF8D, 0x98F5, 0xEF8E, 0x98F6, 0xEF8F, 0x98F7, 0xEF90, 0x98F8, 0xEF91, 0x98F9, 0xEF92, 0x98FA, 0xEF93, + 0x98FB, 0xEF94, 0x98FC, 0xEF95, 0x98FD, 0xEF96, 0x98FE, 0xEF97, 0x98FF, 0xEF98, 0x9900, 0xEF99, 0x9901, 0xEF9A, 0x9902, 0xEF9B, + 0x9903, 0xEF9C, 0x9904, 0xEF9D, 0x9905, 0xEF9E, 0x9906, 0xEF9F, 0x9907, 0xEFA0, 0x9908, 0xF040, 0x9909, 0xF041, 0x990A, 0xF042, + 0x990B, 0xF043, 0x990C, 0xF044, 0x990D, 0xF7D0, 0x990E, 0xF045, 0x990F, 0xF046, 0x9910, 0xB2CD, 0x9911, 0xF047, 0x9912, 0xF048, + 0x9913, 0xF049, 0x9914, 0xF04A, 0x9915, 0xF04B, 0x9916, 0xF04C, 0x9917, 0xF04D, 0x9918, 0xF04E, 0x9919, 0xF04F, 0x991A, 0xF050, + 0x991B, 0xF051, 0x991C, 0xF052, 0x991D, 0xF053, 0x991E, 0xF054, 0x991F, 0xF055, 0x9920, 0xF056, 0x9921, 0xF057, 0x9922, 0xF058, + 0x9923, 0xF059, 0x9924, 0xF05A, 0x9925, 0xF05B, 0x9926, 0xF05C, 0x9927, 0xF05D, 0x9928, 0xF05E, 0x9929, 0xF05F, 0x992A, 0xF060, + 0x992B, 0xF061, 0x992C, 0xF062, 0x992D, 0xF063, 0x992E, 0xF7D1, 0x992F, 0xF064, 0x9930, 0xF065, 0x9931, 0xF066, 0x9932, 0xF067, + 0x9933, 0xF068, 0x9934, 0xF069, 0x9935, 0xF06A, 0x9936, 0xF06B, 0x9937, 0xF06C, 0x9938, 0xF06D, 0x9939, 0xF06E, 0x993A, 0xF06F, + 0x993B, 0xF070, 0x993C, 0xF071, 0x993D, 0xF072, 0x993E, 0xF073, 0x993F, 0xF074, 0x9940, 0xF075, 0x9941, 0xF076, 0x9942, 0xF077, + 0x9943, 0xF078, 0x9944, 0xF079, 0x9945, 0xF07A, 0x9946, 0xF07B, 0x9947, 0xF07C, 0x9948, 0xF07D, 0x9949, 0xF07E, 0x994A, 0xF080, + 0x994B, 0xF081, 0x994C, 0xF082, 0x994D, 0xF083, 0x994E, 0xF084, 0x994F, 0xF085, 0x9950, 0xF086, 0x9951, 0xF087, 0x9952, 0xF088, + 0x9953, 0xF089, 0x9954, 0xF7D3, 0x9955, 0xF7D2, 0x9956, 0xF08A, 0x9957, 0xF08B, 0x9958, 0xF08C, 0x9959, 0xF08D, 0x995A, 0xF08E, + 0x995B, 0xF08F, 0x995C, 0xF090, 0x995D, 0xF091, 0x995E, 0xF092, 0x995F, 0xF093, 0x9960, 0xF094, 0x9961, 0xF095, 0x9962, 0xF096, + 0x9963, 0xE2BB, 0x9964, 0xF097, 0x9965, 0xBCA2, 0x9966, 0xF098, 0x9967, 0xE2BC, 0x9968, 0xE2BD, 0x9969, 0xE2BE, 0x996A, 0xE2BF, + 0x996B, 0xE2C0, 0x996C, 0xE2C1, 0x996D, 0xB7B9, 0x996E, 0xD2FB, 0x996F, 0xBDA4, 0x9970, 0xCACE, 0x9971, 0xB1A5, 0x9972, 0xCBC7, + 0x9973, 0xF099, 0x9974, 0xE2C2, 0x9975, 0xB6FC, 0x9976, 0xC8C4, 0x9977, 0xE2C3, 0x9978, 0xF09A, 0x9979, 0xF09B, 0x997A, 0xBDC8, + 0x997B, 0xF09C, 0x997C, 0xB1FD, 0x997D, 0xE2C4, 0x997E, 0xF09D, 0x997F, 0xB6F6, 0x9980, 0xE2C5, 0x9981, 0xC4D9, 0x9982, 0xF09E, + 0x9983, 0xF09F, 0x9984, 0xE2C6, 0x9985, 0xCFDA, 0x9986, 0xB9DD, 0x9987, 0xE2C7, 0x9988, 0xC0A1, 0x9989, 0xF0A0, 0x998A, 0xE2C8, + 0x998B, 0xB2F6, 0x998C, 0xF140, 0x998D, 0xE2C9, 0x998E, 0xF141, 0x998F, 0xC1F3, 0x9990, 0xE2CA, 0x9991, 0xE2CB, 0x9992, 0xC2F8, + 0x9993, 0xE2CC, 0x9994, 0xE2CD, 0x9995, 0xE2CE, 0x9996, 0xCAD7, 0x9997, 0xD8B8, 0x9998, 0xD9E5, 0x9999, 0xCFE3, 0x999A, 0xF142, + 0x999B, 0xF143, 0x999C, 0xF144, 0x999D, 0xF145, 0x999E, 0xF146, 0x999F, 0xF147, 0x99A0, 0xF148, 0x99A1, 0xF149, 0x99A2, 0xF14A, + 0x99A3, 0xF14B, 0x99A4, 0xF14C, 0x99A5, 0xF0A5, 0x99A6, 0xF14D, 0x99A7, 0xF14E, 0x99A8, 0xDCB0, 0x99A9, 0xF14F, 0x99AA, 0xF150, + 0x99AB, 0xF151, 0x99AC, 0xF152, 0x99AD, 0xF153, 0x99AE, 0xF154, 0x99AF, 0xF155, 0x99B0, 0xF156, 0x99B1, 0xF157, 0x99B2, 0xF158, + 0x99B3, 0xF159, 0x99B4, 0xF15A, 0x99B5, 0xF15B, 0x99B6, 0xF15C, 0x99B7, 0xF15D, 0x99B8, 0xF15E, 0x99B9, 0xF15F, 0x99BA, 0xF160, + 0x99BB, 0xF161, 0x99BC, 0xF162, 0x99BD, 0xF163, 0x99BE, 0xF164, 0x99BF, 0xF165, 0x99C0, 0xF166, 0x99C1, 0xF167, 0x99C2, 0xF168, + 0x99C3, 0xF169, 0x99C4, 0xF16A, 0x99C5, 0xF16B, 0x99C6, 0xF16C, 0x99C7, 0xF16D, 0x99C8, 0xF16E, 0x99C9, 0xF16F, 0x99CA, 0xF170, + 0x99CB, 0xF171, 0x99CC, 0xF172, 0x99CD, 0xF173, 0x99CE, 0xF174, 0x99CF, 0xF175, 0x99D0, 0xF176, 0x99D1, 0xF177, 0x99D2, 0xF178, + 0x99D3, 0xF179, 0x99D4, 0xF17A, 0x99D5, 0xF17B, 0x99D6, 0xF17C, 0x99D7, 0xF17D, 0x99D8, 0xF17E, 0x99D9, 0xF180, 0x99DA, 0xF181, + 0x99DB, 0xF182, 0x99DC, 0xF183, 0x99DD, 0xF184, 0x99DE, 0xF185, 0x99DF, 0xF186, 0x99E0, 0xF187, 0x99E1, 0xF188, 0x99E2, 0xF189, + 0x99E3, 0xF18A, 0x99E4, 0xF18B, 0x99E5, 0xF18C, 0x99E6, 0xF18D, 0x99E7, 0xF18E, 0x99E8, 0xF18F, 0x99E9, 0xF190, 0x99EA, 0xF191, + 0x99EB, 0xF192, 0x99EC, 0xF193, 0x99ED, 0xF194, 0x99EE, 0xF195, 0x99EF, 0xF196, 0x99F0, 0xF197, 0x99F1, 0xF198, 0x99F2, 0xF199, + 0x99F3, 0xF19A, 0x99F4, 0xF19B, 0x99F5, 0xF19C, 0x99F6, 0xF19D, 0x99F7, 0xF19E, 0x99F8, 0xF19F, 0x99F9, 0xF1A0, 0x99FA, 0xF240, + 0x99FB, 0xF241, 0x99FC, 0xF242, 0x99FD, 0xF243, 0x99FE, 0xF244, 0x99FF, 0xF245, 0x9A00, 0xF246, 0x9A01, 0xF247, 0x9A02, 0xF248, + 0x9A03, 0xF249, 0x9A04, 0xF24A, 0x9A05, 0xF24B, 0x9A06, 0xF24C, 0x9A07, 0xF24D, 0x9A08, 0xF24E, 0x9A09, 0xF24F, 0x9A0A, 0xF250, + 0x9A0B, 0xF251, 0x9A0C, 0xF252, 0x9A0D, 0xF253, 0x9A0E, 0xF254, 0x9A0F, 0xF255, 0x9A10, 0xF256, 0x9A11, 0xF257, 0x9A12, 0xF258, + 0x9A13, 0xF259, 0x9A14, 0xF25A, 0x9A15, 0xF25B, 0x9A16, 0xF25C, 0x9A17, 0xF25D, 0x9A18, 0xF25E, 0x9A19, 0xF25F, 0x9A1A, 0xF260, + 0x9A1B, 0xF261, 0x9A1C, 0xF262, 0x9A1D, 0xF263, 0x9A1E, 0xF264, 0x9A1F, 0xF265, 0x9A20, 0xF266, 0x9A21, 0xF267, 0x9A22, 0xF268, + 0x9A23, 0xF269, 0x9A24, 0xF26A, 0x9A25, 0xF26B, 0x9A26, 0xF26C, 0x9A27, 0xF26D, 0x9A28, 0xF26E, 0x9A29, 0xF26F, 0x9A2A, 0xF270, + 0x9A2B, 0xF271, 0x9A2C, 0xF272, 0x9A2D, 0xF273, 0x9A2E, 0xF274, 0x9A2F, 0xF275, 0x9A30, 0xF276, 0x9A31, 0xF277, 0x9A32, 0xF278, + 0x9A33, 0xF279, 0x9A34, 0xF27A, 0x9A35, 0xF27B, 0x9A36, 0xF27C, 0x9A37, 0xF27D, 0x9A38, 0xF27E, 0x9A39, 0xF280, 0x9A3A, 0xF281, + 0x9A3B, 0xF282, 0x9A3C, 0xF283, 0x9A3D, 0xF284, 0x9A3E, 0xF285, 0x9A3F, 0xF286, 0x9A40, 0xF287, 0x9A41, 0xF288, 0x9A42, 0xF289, + 0x9A43, 0xF28A, 0x9A44, 0xF28B, 0x9A45, 0xF28C, 0x9A46, 0xF28D, 0x9A47, 0xF28E, 0x9A48, 0xF28F, 0x9A49, 0xF290, 0x9A4A, 0xF291, + 0x9A4B, 0xF292, 0x9A4C, 0xF293, 0x9A4D, 0xF294, 0x9A4E, 0xF295, 0x9A4F, 0xF296, 0x9A50, 0xF297, 0x9A51, 0xF298, 0x9A52, 0xF299, + 0x9A53, 0xF29A, 0x9A54, 0xF29B, 0x9A55, 0xF29C, 0x9A56, 0xF29D, 0x9A57, 0xF29E, 0x9A58, 0xF29F, 0x9A59, 0xF2A0, 0x9A5A, 0xF340, + 0x9A5B, 0xF341, 0x9A5C, 0xF342, 0x9A5D, 0xF343, 0x9A5E, 0xF344, 0x9A5F, 0xF345, 0x9A60, 0xF346, 0x9A61, 0xF347, 0x9A62, 0xF348, + 0x9A63, 0xF349, 0x9A64, 0xF34A, 0x9A65, 0xF34B, 0x9A66, 0xF34C, 0x9A67, 0xF34D, 0x9A68, 0xF34E, 0x9A69, 0xF34F, 0x9A6A, 0xF350, + 0x9A6B, 0xF351, 0x9A6C, 0xC2ED, 0x9A6D, 0xD4A6, 0x9A6E, 0xCDD4, 0x9A6F, 0xD1B1, 0x9A70, 0xB3DB, 0x9A71, 0xC7FD, 0x9A72, 0xF352, + 0x9A73, 0xB2B5, 0x9A74, 0xC2BF, 0x9A75, 0xE6E0, 0x9A76, 0xCABB, 0x9A77, 0xE6E1, 0x9A78, 0xE6E2, 0x9A79, 0xBED4, 0x9A7A, 0xE6E3, + 0x9A7B, 0xD7A4, 0x9A7C, 0xCDD5, 0x9A7D, 0xE6E5, 0x9A7E, 0xBCDD, 0x9A7F, 0xE6E4, 0x9A80, 0xE6E6, 0x9A81, 0xE6E7, 0x9A82, 0xC2EE, + 0x9A83, 0xF353, 0x9A84, 0xBDBE, 0x9A85, 0xE6E8, 0x9A86, 0xC2E6, 0x9A87, 0xBAA7, 0x9A88, 0xE6E9, 0x9A89, 0xF354, 0x9A8A, 0xE6EA, + 0x9A8B, 0xB3D2, 0x9A8C, 0xD1E9, 0x9A8D, 0xF355, 0x9A8E, 0xF356, 0x9A8F, 0xBFA5, 0x9A90, 0xE6EB, 0x9A91, 0xC6EF, 0x9A92, 0xE6EC, + 0x9A93, 0xE6ED, 0x9A94, 0xF357, 0x9A95, 0xF358, 0x9A96, 0xE6EE, 0x9A97, 0xC6AD, 0x9A98, 0xE6EF, 0x9A99, 0xF359, 0x9A9A, 0xC9A7, + 0x9A9B, 0xE6F0, 0x9A9C, 0xE6F1, 0x9A9D, 0xE6F2, 0x9A9E, 0xE5B9, 0x9A9F, 0xE6F3, 0x9AA0, 0xE6F4, 0x9AA1, 0xC2E2, 0x9AA2, 0xE6F5, + 0x9AA3, 0xE6F6, 0x9AA4, 0xD6E8, 0x9AA5, 0xE6F7, 0x9AA6, 0xF35A, 0x9AA7, 0xE6F8, 0x9AA8, 0xB9C7, 0x9AA9, 0xF35B, 0x9AAA, 0xF35C, + 0x9AAB, 0xF35D, 0x9AAC, 0xF35E, 0x9AAD, 0xF35F, 0x9AAE, 0xF360, 0x9AAF, 0xF361, 0x9AB0, 0xF7BB, 0x9AB1, 0xF7BA, 0x9AB2, 0xF362, + 0x9AB3, 0xF363, 0x9AB4, 0xF364, 0x9AB5, 0xF365, 0x9AB6, 0xF7BE, 0x9AB7, 0xF7BC, 0x9AB8, 0xBAA1, 0x9AB9, 0xF366, 0x9ABA, 0xF7BF, + 0x9ABB, 0xF367, 0x9ABC, 0xF7C0, 0x9ABD, 0xF368, 0x9ABE, 0xF369, 0x9ABF, 0xF36A, 0x9AC0, 0xF7C2, 0x9AC1, 0xF7C1, 0x9AC2, 0xF7C4, + 0x9AC3, 0xF36B, 0x9AC4, 0xF36C, 0x9AC5, 0xF7C3, 0x9AC6, 0xF36D, 0x9AC7, 0xF36E, 0x9AC8, 0xF36F, 0x9AC9, 0xF370, 0x9ACA, 0xF371, + 0x9ACB, 0xF7C5, 0x9ACC, 0xF7C6, 0x9ACD, 0xF372, 0x9ACE, 0xF373, 0x9ACF, 0xF374, 0x9AD0, 0xF375, 0x9AD1, 0xF7C7, 0x9AD2, 0xF376, + 0x9AD3, 0xCBE8, 0x9AD4, 0xF377, 0x9AD5, 0xF378, 0x9AD6, 0xF379, 0x9AD7, 0xF37A, 0x9AD8, 0xB8DF, 0x9AD9, 0xF37B, 0x9ADA, 0xF37C, + 0x9ADB, 0xF37D, 0x9ADC, 0xF37E, 0x9ADD, 0xF380, 0x9ADE, 0xF381, 0x9ADF, 0xF7D4, 0x9AE0, 0xF382, 0x9AE1, 0xF7D5, 0x9AE2, 0xF383, + 0x9AE3, 0xF384, 0x9AE4, 0xF385, 0x9AE5, 0xF386, 0x9AE6, 0xF7D6, 0x9AE7, 0xF387, 0x9AE8, 0xF388, 0x9AE9, 0xF389, 0x9AEA, 0xF38A, + 0x9AEB, 0xF7D8, 0x9AEC, 0xF38B, 0x9AED, 0xF7DA, 0x9AEE, 0xF38C, 0x9AEF, 0xF7D7, 0x9AF0, 0xF38D, 0x9AF1, 0xF38E, 0x9AF2, 0xF38F, + 0x9AF3, 0xF390, 0x9AF4, 0xF391, 0x9AF5, 0xF392, 0x9AF6, 0xF393, 0x9AF7, 0xF394, 0x9AF8, 0xF395, 0x9AF9, 0xF7DB, 0x9AFA, 0xF396, + 0x9AFB, 0xF7D9, 0x9AFC, 0xF397, 0x9AFD, 0xF398, 0x9AFE, 0xF399, 0x9AFF, 0xF39A, 0x9B00, 0xF39B, 0x9B01, 0xF39C, 0x9B02, 0xF39D, + 0x9B03, 0xD7D7, 0x9B04, 0xF39E, 0x9B05, 0xF39F, 0x9B06, 0xF3A0, 0x9B07, 0xF440, 0x9B08, 0xF7DC, 0x9B09, 0xF441, 0x9B0A, 0xF442, + 0x9B0B, 0xF443, 0x9B0C, 0xF444, 0x9B0D, 0xF445, 0x9B0E, 0xF446, 0x9B0F, 0xF7DD, 0x9B10, 0xF447, 0x9B11, 0xF448, 0x9B12, 0xF449, + 0x9B13, 0xF7DE, 0x9B14, 0xF44A, 0x9B15, 0xF44B, 0x9B16, 0xF44C, 0x9B17, 0xF44D, 0x9B18, 0xF44E, 0x9B19, 0xF44F, 0x9B1A, 0xF450, + 0x9B1B, 0xF451, 0x9B1C, 0xF452, 0x9B1D, 0xF453, 0x9B1E, 0xF454, 0x9B1F, 0xF7DF, 0x9B20, 0xF455, 0x9B21, 0xF456, 0x9B22, 0xF457, + 0x9B23, 0xF7E0, 0x9B24, 0xF458, 0x9B25, 0xF459, 0x9B26, 0xF45A, 0x9B27, 0xF45B, 0x9B28, 0xF45C, 0x9B29, 0xF45D, 0x9B2A, 0xF45E, + 0x9B2B, 0xF45F, 0x9B2C, 0xF460, 0x9B2D, 0xF461, 0x9B2E, 0xF462, 0x9B2F, 0xDBCB, 0x9B30, 0xF463, 0x9B31, 0xF464, 0x9B32, 0xD8AA, + 0x9B33, 0xF465, 0x9B34, 0xF466, 0x9B35, 0xF467, 0x9B36, 0xF468, 0x9B37, 0xF469, 0x9B38, 0xF46A, 0x9B39, 0xF46B, 0x9B3A, 0xF46C, + 0x9B3B, 0xE5F7, 0x9B3C, 0xB9ED, 0x9B3D, 0xF46D, 0x9B3E, 0xF46E, 0x9B3F, 0xF46F, 0x9B40, 0xF470, 0x9B41, 0xBFFD, 0x9B42, 0xBBEA, + 0x9B43, 0xF7C9, 0x9B44, 0xC6C7, 0x9B45, 0xF7C8, 0x9B46, 0xF471, 0x9B47, 0xF7CA, 0x9B48, 0xF7CC, 0x9B49, 0xF7CB, 0x9B4A, 0xF472, + 0x9B4B, 0xF473, 0x9B4C, 0xF474, 0x9B4D, 0xF7CD, 0x9B4E, 0xF475, 0x9B4F, 0xCEBA, 0x9B50, 0xF476, 0x9B51, 0xF7CE, 0x9B52, 0xF477, + 0x9B53, 0xF478, 0x9B54, 0xC4A7, 0x9B55, 0xF479, 0x9B56, 0xF47A, 0x9B57, 0xF47B, 0x9B58, 0xF47C, 0x9B59, 0xF47D, 0x9B5A, 0xF47E, + 0x9B5B, 0xF480, 0x9B5C, 0xF481, 0x9B5D, 0xF482, 0x9B5E, 0xF483, 0x9B5F, 0xF484, 0x9B60, 0xF485, 0x9B61, 0xF486, 0x9B62, 0xF487, + 0x9B63, 0xF488, 0x9B64, 0xF489, 0x9B65, 0xF48A, 0x9B66, 0xF48B, 0x9B67, 0xF48C, 0x9B68, 0xF48D, 0x9B69, 0xF48E, 0x9B6A, 0xF48F, + 0x9B6B, 0xF490, 0x9B6C, 0xF491, 0x9B6D, 0xF492, 0x9B6E, 0xF493, 0x9B6F, 0xF494, 0x9B70, 0xF495, 0x9B71, 0xF496, 0x9B72, 0xF497, + 0x9B73, 0xF498, 0x9B74, 0xF499, 0x9B75, 0xF49A, 0x9B76, 0xF49B, 0x9B77, 0xF49C, 0x9B78, 0xF49D, 0x9B79, 0xF49E, 0x9B7A, 0xF49F, + 0x9B7B, 0xF4A0, 0x9B7C, 0xF540, 0x9B7D, 0xF541, 0x9B7E, 0xF542, 0x9B7F, 0xF543, 0x9B80, 0xF544, 0x9B81, 0xF545, 0x9B82, 0xF546, + 0x9B83, 0xF547, 0x9B84, 0xF548, 0x9B85, 0xF549, 0x9B86, 0xF54A, 0x9B87, 0xF54B, 0x9B88, 0xF54C, 0x9B89, 0xF54D, 0x9B8A, 0xF54E, + 0x9B8B, 0xF54F, 0x9B8C, 0xF550, 0x9B8D, 0xF551, 0x9B8E, 0xF552, 0x9B8F, 0xF553, 0x9B90, 0xF554, 0x9B91, 0xF555, 0x9B92, 0xF556, + 0x9B93, 0xF557, 0x9B94, 0xF558, 0x9B95, 0xF559, 0x9B96, 0xF55A, 0x9B97, 0xF55B, 0x9B98, 0xF55C, 0x9B99, 0xF55D, 0x9B9A, 0xF55E, + 0x9B9B, 0xF55F, 0x9B9C, 0xF560, 0x9B9D, 0xF561, 0x9B9E, 0xF562, 0x9B9F, 0xF563, 0x9BA0, 0xF564, 0x9BA1, 0xF565, 0x9BA2, 0xF566, + 0x9BA3, 0xF567, 0x9BA4, 0xF568, 0x9BA5, 0xF569, 0x9BA6, 0xF56A, 0x9BA7, 0xF56B, 0x9BA8, 0xF56C, 0x9BA9, 0xF56D, 0x9BAA, 0xF56E, + 0x9BAB, 0xF56F, 0x9BAC, 0xF570, 0x9BAD, 0xF571, 0x9BAE, 0xF572, 0x9BAF, 0xF573, 0x9BB0, 0xF574, 0x9BB1, 0xF575, 0x9BB2, 0xF576, + 0x9BB3, 0xF577, 0x9BB4, 0xF578, 0x9BB5, 0xF579, 0x9BB6, 0xF57A, 0x9BB7, 0xF57B, 0x9BB8, 0xF57C, 0x9BB9, 0xF57D, 0x9BBA, 0xF57E, + 0x9BBB, 0xF580, 0x9BBC, 0xF581, 0x9BBD, 0xF582, 0x9BBE, 0xF583, 0x9BBF, 0xF584, 0x9BC0, 0xF585, 0x9BC1, 0xF586, 0x9BC2, 0xF587, + 0x9BC3, 0xF588, 0x9BC4, 0xF589, 0x9BC5, 0xF58A, 0x9BC6, 0xF58B, 0x9BC7, 0xF58C, 0x9BC8, 0xF58D, 0x9BC9, 0xF58E, 0x9BCA, 0xF58F, + 0x9BCB, 0xF590, 0x9BCC, 0xF591, 0x9BCD, 0xF592, 0x9BCE, 0xF593, 0x9BCF, 0xF594, 0x9BD0, 0xF595, 0x9BD1, 0xF596, 0x9BD2, 0xF597, + 0x9BD3, 0xF598, 0x9BD4, 0xF599, 0x9BD5, 0xF59A, 0x9BD6, 0xF59B, 0x9BD7, 0xF59C, 0x9BD8, 0xF59D, 0x9BD9, 0xF59E, 0x9BDA, 0xF59F, + 0x9BDB, 0xF5A0, 0x9BDC, 0xF640, 0x9BDD, 0xF641, 0x9BDE, 0xF642, 0x9BDF, 0xF643, 0x9BE0, 0xF644, 0x9BE1, 0xF645, 0x9BE2, 0xF646, + 0x9BE3, 0xF647, 0x9BE4, 0xF648, 0x9BE5, 0xF649, 0x9BE6, 0xF64A, 0x9BE7, 0xF64B, 0x9BE8, 0xF64C, 0x9BE9, 0xF64D, 0x9BEA, 0xF64E, + 0x9BEB, 0xF64F, 0x9BEC, 0xF650, 0x9BED, 0xF651, 0x9BEE, 0xF652, 0x9BEF, 0xF653, 0x9BF0, 0xF654, 0x9BF1, 0xF655, 0x9BF2, 0xF656, + 0x9BF3, 0xF657, 0x9BF4, 0xF658, 0x9BF5, 0xF659, 0x9BF6, 0xF65A, 0x9BF7, 0xF65B, 0x9BF8, 0xF65C, 0x9BF9, 0xF65D, 0x9BFA, 0xF65E, + 0x9BFB, 0xF65F, 0x9BFC, 0xF660, 0x9BFD, 0xF661, 0x9BFE, 0xF662, 0x9BFF, 0xF663, 0x9C00, 0xF664, 0x9C01, 0xF665, 0x9C02, 0xF666, + 0x9C03, 0xF667, 0x9C04, 0xF668, 0x9C05, 0xF669, 0x9C06, 0xF66A, 0x9C07, 0xF66B, 0x9C08, 0xF66C, 0x9C09, 0xF66D, 0x9C0A, 0xF66E, + 0x9C0B, 0xF66F, 0x9C0C, 0xF670, 0x9C0D, 0xF671, 0x9C0E, 0xF672, 0x9C0F, 0xF673, 0x9C10, 0xF674, 0x9C11, 0xF675, 0x9C12, 0xF676, + 0x9C13, 0xF677, 0x9C14, 0xF678, 0x9C15, 0xF679, 0x9C16, 0xF67A, 0x9C17, 0xF67B, 0x9C18, 0xF67C, 0x9C19, 0xF67D, 0x9C1A, 0xF67E, + 0x9C1B, 0xF680, 0x9C1C, 0xF681, 0x9C1D, 0xF682, 0x9C1E, 0xF683, 0x9C1F, 0xF684, 0x9C20, 0xF685, 0x9C21, 0xF686, 0x9C22, 0xF687, + 0x9C23, 0xF688, 0x9C24, 0xF689, 0x9C25, 0xF68A, 0x9C26, 0xF68B, 0x9C27, 0xF68C, 0x9C28, 0xF68D, 0x9C29, 0xF68E, 0x9C2A, 0xF68F, + 0x9C2B, 0xF690, 0x9C2C, 0xF691, 0x9C2D, 0xF692, 0x9C2E, 0xF693, 0x9C2F, 0xF694, 0x9C30, 0xF695, 0x9C31, 0xF696, 0x9C32, 0xF697, + 0x9C33, 0xF698, 0x9C34, 0xF699, 0x9C35, 0xF69A, 0x9C36, 0xF69B, 0x9C37, 0xF69C, 0x9C38, 0xF69D, 0x9C39, 0xF69E, 0x9C3A, 0xF69F, + 0x9C3B, 0xF6A0, 0x9C3C, 0xF740, 0x9C3D, 0xF741, 0x9C3E, 0xF742, 0x9C3F, 0xF743, 0x9C40, 0xF744, 0x9C41, 0xF745, 0x9C42, 0xF746, + 0x9C43, 0xF747, 0x9C44, 0xF748, 0x9C45, 0xF749, 0x9C46, 0xF74A, 0x9C47, 0xF74B, 0x9C48, 0xF74C, 0x9C49, 0xF74D, 0x9C4A, 0xF74E, + 0x9C4B, 0xF74F, 0x9C4C, 0xF750, 0x9C4D, 0xF751, 0x9C4E, 0xF752, 0x9C4F, 0xF753, 0x9C50, 0xF754, 0x9C51, 0xF755, 0x9C52, 0xF756, + 0x9C53, 0xF757, 0x9C54, 0xF758, 0x9C55, 0xF759, 0x9C56, 0xF75A, 0x9C57, 0xF75B, 0x9C58, 0xF75C, 0x9C59, 0xF75D, 0x9C5A, 0xF75E, + 0x9C5B, 0xF75F, 0x9C5C, 0xF760, 0x9C5D, 0xF761, 0x9C5E, 0xF762, 0x9C5F, 0xF763, 0x9C60, 0xF764, 0x9C61, 0xF765, 0x9C62, 0xF766, + 0x9C63, 0xF767, 0x9C64, 0xF768, 0x9C65, 0xF769, 0x9C66, 0xF76A, 0x9C67, 0xF76B, 0x9C68, 0xF76C, 0x9C69, 0xF76D, 0x9C6A, 0xF76E, + 0x9C6B, 0xF76F, 0x9C6C, 0xF770, 0x9C6D, 0xF771, 0x9C6E, 0xF772, 0x9C6F, 0xF773, 0x9C70, 0xF774, 0x9C71, 0xF775, 0x9C72, 0xF776, + 0x9C73, 0xF777, 0x9C74, 0xF778, 0x9C75, 0xF779, 0x9C76, 0xF77A, 0x9C77, 0xF77B, 0x9C78, 0xF77C, 0x9C79, 0xF77D, 0x9C7A, 0xF77E, + 0x9C7B, 0xF780, 0x9C7C, 0xD3E3, 0x9C7D, 0xF781, 0x9C7E, 0xF782, 0x9C7F, 0xF6CF, 0x9C80, 0xF783, 0x9C81, 0xC2B3, 0x9C82, 0xF6D0, + 0x9C83, 0xF784, 0x9C84, 0xF785, 0x9C85, 0xF6D1, 0x9C86, 0xF6D2, 0x9C87, 0xF6D3, 0x9C88, 0xF6D4, 0x9C89, 0xF786, 0x9C8A, 0xF787, + 0x9C8B, 0xF6D6, 0x9C8C, 0xF788, 0x9C8D, 0xB1AB, 0x9C8E, 0xF6D7, 0x9C8F, 0xF789, 0x9C90, 0xF6D8, 0x9C91, 0xF6D9, 0x9C92, 0xF6DA, + 0x9C93, 0xF78A, 0x9C94, 0xF6DB, 0x9C95, 0xF6DC, 0x9C96, 0xF78B, 0x9C97, 0xF78C, 0x9C98, 0xF78D, 0x9C99, 0xF78E, 0x9C9A, 0xF6DD, + 0x9C9B, 0xF6DE, 0x9C9C, 0xCFCA, 0x9C9D, 0xF78F, 0x9C9E, 0xF6DF, 0x9C9F, 0xF6E0, 0x9CA0, 0xF6E1, 0x9CA1, 0xF6E2, 0x9CA2, 0xF6E3, + 0x9CA3, 0xF6E4, 0x9CA4, 0xC0F0, 0x9CA5, 0xF6E5, 0x9CA6, 0xF6E6, 0x9CA7, 0xF6E7, 0x9CA8, 0xF6E8, 0x9CA9, 0xF6E9, 0x9CAA, 0xF790, + 0x9CAB, 0xF6EA, 0x9CAC, 0xF791, 0x9CAD, 0xF6EB, 0x9CAE, 0xF6EC, 0x9CAF, 0xF792, 0x9CB0, 0xF6ED, 0x9CB1, 0xF6EE, 0x9CB2, 0xF6EF, + 0x9CB3, 0xF6F0, 0x9CB4, 0xF6F1, 0x9CB5, 0xF6F2, 0x9CB6, 0xF6F3, 0x9CB7, 0xF6F4, 0x9CB8, 0xBEA8, 0x9CB9, 0xF793, 0x9CBA, 0xF6F5, + 0x9CBB, 0xF6F6, 0x9CBC, 0xF6F7, 0x9CBD, 0xF6F8, 0x9CBE, 0xF794, 0x9CBF, 0xF795, 0x9CC0, 0xF796, 0x9CC1, 0xF797, 0x9CC2, 0xF798, + 0x9CC3, 0xC8FA, 0x9CC4, 0xF6F9, 0x9CC5, 0xF6FA, 0x9CC6, 0xF6FB, 0x9CC7, 0xF6FC, 0x9CC8, 0xF799, 0x9CC9, 0xF79A, 0x9CCA, 0xF6FD, + 0x9CCB, 0xF6FE, 0x9CCC, 0xF7A1, 0x9CCD, 0xF7A2, 0x9CCE, 0xF7A3, 0x9CCF, 0xF7A4, 0x9CD0, 0xF7A5, 0x9CD1, 0xF79B, 0x9CD2, 0xF79C, + 0x9CD3, 0xF7A6, 0x9CD4, 0xF7A7, 0x9CD5, 0xF7A8, 0x9CD6, 0xB1EE, 0x9CD7, 0xF7A9, 0x9CD8, 0xF7AA, 0x9CD9, 0xF7AB, 0x9CDA, 0xF79D, + 0x9CDB, 0xF79E, 0x9CDC, 0xF7AC, 0x9CDD, 0xF7AD, 0x9CDE, 0xC1DB, 0x9CDF, 0xF7AE, 0x9CE0, 0xF79F, 0x9CE1, 0xF7A0, 0x9CE2, 0xF7AF, + 0x9CE3, 0xF840, 0x9CE4, 0xF841, 0x9CE5, 0xF842, 0x9CE6, 0xF843, 0x9CE7, 0xF844, 0x9CE8, 0xF845, 0x9CE9, 0xF846, 0x9CEA, 0xF847, + 0x9CEB, 0xF848, 0x9CEC, 0xF849, 0x9CED, 0xF84A, 0x9CEE, 0xF84B, 0x9CEF, 0xF84C, 0x9CF0, 0xF84D, 0x9CF1, 0xF84E, 0x9CF2, 0xF84F, + 0x9CF3, 0xF850, 0x9CF4, 0xF851, 0x9CF5, 0xF852, 0x9CF6, 0xF853, 0x9CF7, 0xF854, 0x9CF8, 0xF855, 0x9CF9, 0xF856, 0x9CFA, 0xF857, + 0x9CFB, 0xF858, 0x9CFC, 0xF859, 0x9CFD, 0xF85A, 0x9CFE, 0xF85B, 0x9CFF, 0xF85C, 0x9D00, 0xF85D, 0x9D01, 0xF85E, 0x9D02, 0xF85F, + 0x9D03, 0xF860, 0x9D04, 0xF861, 0x9D05, 0xF862, 0x9D06, 0xF863, 0x9D07, 0xF864, 0x9D08, 0xF865, 0x9D09, 0xF866, 0x9D0A, 0xF867, + 0x9D0B, 0xF868, 0x9D0C, 0xF869, 0x9D0D, 0xF86A, 0x9D0E, 0xF86B, 0x9D0F, 0xF86C, 0x9D10, 0xF86D, 0x9D11, 0xF86E, 0x9D12, 0xF86F, + 0x9D13, 0xF870, 0x9D14, 0xF871, 0x9D15, 0xF872, 0x9D16, 0xF873, 0x9D17, 0xF874, 0x9D18, 0xF875, 0x9D19, 0xF876, 0x9D1A, 0xF877, + 0x9D1B, 0xF878, 0x9D1C, 0xF879, 0x9D1D, 0xF87A, 0x9D1E, 0xF87B, 0x9D1F, 0xF87C, 0x9D20, 0xF87D, 0x9D21, 0xF87E, 0x9D22, 0xF880, + 0x9D23, 0xF881, 0x9D24, 0xF882, 0x9D25, 0xF883, 0x9D26, 0xF884, 0x9D27, 0xF885, 0x9D28, 0xF886, 0x9D29, 0xF887, 0x9D2A, 0xF888, + 0x9D2B, 0xF889, 0x9D2C, 0xF88A, 0x9D2D, 0xF88B, 0x9D2E, 0xF88C, 0x9D2F, 0xF88D, 0x9D30, 0xF88E, 0x9D31, 0xF88F, 0x9D32, 0xF890, + 0x9D33, 0xF891, 0x9D34, 0xF892, 0x9D35, 0xF893, 0x9D36, 0xF894, 0x9D37, 0xF895, 0x9D38, 0xF896, 0x9D39, 0xF897, 0x9D3A, 0xF898, + 0x9D3B, 0xF899, 0x9D3C, 0xF89A, 0x9D3D, 0xF89B, 0x9D3E, 0xF89C, 0x9D3F, 0xF89D, 0x9D40, 0xF89E, 0x9D41, 0xF89F, 0x9D42, 0xF8A0, + 0x9D43, 0xF940, 0x9D44, 0xF941, 0x9D45, 0xF942, 0x9D46, 0xF943, 0x9D47, 0xF944, 0x9D48, 0xF945, 0x9D49, 0xF946, 0x9D4A, 0xF947, + 0x9D4B, 0xF948, 0x9D4C, 0xF949, 0x9D4D, 0xF94A, 0x9D4E, 0xF94B, 0x9D4F, 0xF94C, 0x9D50, 0xF94D, 0x9D51, 0xF94E, 0x9D52, 0xF94F, + 0x9D53, 0xF950, 0x9D54, 0xF951, 0x9D55, 0xF952, 0x9D56, 0xF953, 0x9D57, 0xF954, 0x9D58, 0xF955, 0x9D59, 0xF956, 0x9D5A, 0xF957, + 0x9D5B, 0xF958, 0x9D5C, 0xF959, 0x9D5D, 0xF95A, 0x9D5E, 0xF95B, 0x9D5F, 0xF95C, 0x9D60, 0xF95D, 0x9D61, 0xF95E, 0x9D62, 0xF95F, + 0x9D63, 0xF960, 0x9D64, 0xF961, 0x9D65, 0xF962, 0x9D66, 0xF963, 0x9D67, 0xF964, 0x9D68, 0xF965, 0x9D69, 0xF966, 0x9D6A, 0xF967, + 0x9D6B, 0xF968, 0x9D6C, 0xF969, 0x9D6D, 0xF96A, 0x9D6E, 0xF96B, 0x9D6F, 0xF96C, 0x9D70, 0xF96D, 0x9D71, 0xF96E, 0x9D72, 0xF96F, + 0x9D73, 0xF970, 0x9D74, 0xF971, 0x9D75, 0xF972, 0x9D76, 0xF973, 0x9D77, 0xF974, 0x9D78, 0xF975, 0x9D79, 0xF976, 0x9D7A, 0xF977, + 0x9D7B, 0xF978, 0x9D7C, 0xF979, 0x9D7D, 0xF97A, 0x9D7E, 0xF97B, 0x9D7F, 0xF97C, 0x9D80, 0xF97D, 0x9D81, 0xF97E, 0x9D82, 0xF980, + 0x9D83, 0xF981, 0x9D84, 0xF982, 0x9D85, 0xF983, 0x9D86, 0xF984, 0x9D87, 0xF985, 0x9D88, 0xF986, 0x9D89, 0xF987, 0x9D8A, 0xF988, + 0x9D8B, 0xF989, 0x9D8C, 0xF98A, 0x9D8D, 0xF98B, 0x9D8E, 0xF98C, 0x9D8F, 0xF98D, 0x9D90, 0xF98E, 0x9D91, 0xF98F, 0x9D92, 0xF990, + 0x9D93, 0xF991, 0x9D94, 0xF992, 0x9D95, 0xF993, 0x9D96, 0xF994, 0x9D97, 0xF995, 0x9D98, 0xF996, 0x9D99, 0xF997, 0x9D9A, 0xF998, + 0x9D9B, 0xF999, 0x9D9C, 0xF99A, 0x9D9D, 0xF99B, 0x9D9E, 0xF99C, 0x9D9F, 0xF99D, 0x9DA0, 0xF99E, 0x9DA1, 0xF99F, 0x9DA2, 0xF9A0, + 0x9DA3, 0xFA40, 0x9DA4, 0xFA41, 0x9DA5, 0xFA42, 0x9DA6, 0xFA43, 0x9DA7, 0xFA44, 0x9DA8, 0xFA45, 0x9DA9, 0xFA46, 0x9DAA, 0xFA47, + 0x9DAB, 0xFA48, 0x9DAC, 0xFA49, 0x9DAD, 0xFA4A, 0x9DAE, 0xFA4B, 0x9DAF, 0xFA4C, 0x9DB0, 0xFA4D, 0x9DB1, 0xFA4E, 0x9DB2, 0xFA4F, + 0x9DB3, 0xFA50, 0x9DB4, 0xFA51, 0x9DB5, 0xFA52, 0x9DB6, 0xFA53, 0x9DB7, 0xFA54, 0x9DB8, 0xFA55, 0x9DB9, 0xFA56, 0x9DBA, 0xFA57, + 0x9DBB, 0xFA58, 0x9DBC, 0xFA59, 0x9DBD, 0xFA5A, 0x9DBE, 0xFA5B, 0x9DBF, 0xFA5C, 0x9DC0, 0xFA5D, 0x9DC1, 0xFA5E, 0x9DC2, 0xFA5F, + 0x9DC3, 0xFA60, 0x9DC4, 0xFA61, 0x9DC5, 0xFA62, 0x9DC6, 0xFA63, 0x9DC7, 0xFA64, 0x9DC8, 0xFA65, 0x9DC9, 0xFA66, 0x9DCA, 0xFA67, + 0x9DCB, 0xFA68, 0x9DCC, 0xFA69, 0x9DCD, 0xFA6A, 0x9DCE, 0xFA6B, 0x9DCF, 0xFA6C, 0x9DD0, 0xFA6D, 0x9DD1, 0xFA6E, 0x9DD2, 0xFA6F, + 0x9DD3, 0xFA70, 0x9DD4, 0xFA71, 0x9DD5, 0xFA72, 0x9DD6, 0xFA73, 0x9DD7, 0xFA74, 0x9DD8, 0xFA75, 0x9DD9, 0xFA76, 0x9DDA, 0xFA77, + 0x9DDB, 0xFA78, 0x9DDC, 0xFA79, 0x9DDD, 0xFA7A, 0x9DDE, 0xFA7B, 0x9DDF, 0xFA7C, 0x9DE0, 0xFA7D, 0x9DE1, 0xFA7E, 0x9DE2, 0xFA80, + 0x9DE3, 0xFA81, 0x9DE4, 0xFA82, 0x9DE5, 0xFA83, 0x9DE6, 0xFA84, 0x9DE7, 0xFA85, 0x9DE8, 0xFA86, 0x9DE9, 0xFA87, 0x9DEA, 0xFA88, + 0x9DEB, 0xFA89, 0x9DEC, 0xFA8A, 0x9DED, 0xFA8B, 0x9DEE, 0xFA8C, 0x9DEF, 0xFA8D, 0x9DF0, 0xFA8E, 0x9DF1, 0xFA8F, 0x9DF2, 0xFA90, + 0x9DF3, 0xFA91, 0x9DF4, 0xFA92, 0x9DF5, 0xFA93, 0x9DF6, 0xFA94, 0x9DF7, 0xFA95, 0x9DF8, 0xFA96, 0x9DF9, 0xFA97, 0x9DFA, 0xFA98, + 0x9DFB, 0xFA99, 0x9DFC, 0xFA9A, 0x9DFD, 0xFA9B, 0x9DFE, 0xFA9C, 0x9DFF, 0xFA9D, 0x9E00, 0xFA9E, 0x9E01, 0xFA9F, 0x9E02, 0xFAA0, + 0x9E03, 0xFB40, 0x9E04, 0xFB41, 0x9E05, 0xFB42, 0x9E06, 0xFB43, 0x9E07, 0xFB44, 0x9E08, 0xFB45, 0x9E09, 0xFB46, 0x9E0A, 0xFB47, + 0x9E0B, 0xFB48, 0x9E0C, 0xFB49, 0x9E0D, 0xFB4A, 0x9E0E, 0xFB4B, 0x9E0F, 0xFB4C, 0x9E10, 0xFB4D, 0x9E11, 0xFB4E, 0x9E12, 0xFB4F, + 0x9E13, 0xFB50, 0x9E14, 0xFB51, 0x9E15, 0xFB52, 0x9E16, 0xFB53, 0x9E17, 0xFB54, 0x9E18, 0xFB55, 0x9E19, 0xFB56, 0x9E1A, 0xFB57, + 0x9E1B, 0xFB58, 0x9E1C, 0xFB59, 0x9E1D, 0xFB5A, 0x9E1E, 0xFB5B, 0x9E1F, 0xC4F1, 0x9E20, 0xF0AF, 0x9E21, 0xBCA6, 0x9E22, 0xF0B0, + 0x9E23, 0xC3F9, 0x9E24, 0xFB5C, 0x9E25, 0xC5B8, 0x9E26, 0xD1BB, 0x9E27, 0xFB5D, 0x9E28, 0xF0B1, 0x9E29, 0xF0B2, 0x9E2A, 0xF0B3, + 0x9E2B, 0xF0B4, 0x9E2C, 0xF0B5, 0x9E2D, 0xD1BC, 0x9E2E, 0xFB5E, 0x9E2F, 0xD1EC, 0x9E30, 0xFB5F, 0x9E31, 0xF0B7, 0x9E32, 0xF0B6, + 0x9E33, 0xD4A7, 0x9E34, 0xFB60, 0x9E35, 0xCDD2, 0x9E36, 0xF0B8, 0x9E37, 0xF0BA, 0x9E38, 0xF0B9, 0x9E39, 0xF0BB, 0x9E3A, 0xF0BC, + 0x9E3B, 0xFB61, 0x9E3C, 0xFB62, 0x9E3D, 0xB8EB, 0x9E3E, 0xF0BD, 0x9E3F, 0xBAE8, 0x9E40, 0xFB63, 0x9E41, 0xF0BE, 0x9E42, 0xF0BF, + 0x9E43, 0xBEE9, 0x9E44, 0xF0C0, 0x9E45, 0xB6EC, 0x9E46, 0xF0C1, 0x9E47, 0xF0C2, 0x9E48, 0xF0C3, 0x9E49, 0xF0C4, 0x9E4A, 0xC8B5, + 0x9E4B, 0xF0C5, 0x9E4C, 0xF0C6, 0x9E4D, 0xFB64, 0x9E4E, 0xF0C7, 0x9E4F, 0xC5F4, 0x9E50, 0xFB65, 0x9E51, 0xF0C8, 0x9E52, 0xFB66, + 0x9E53, 0xFB67, 0x9E54, 0xFB68, 0x9E55, 0xF0C9, 0x9E56, 0xFB69, 0x9E57, 0xF0CA, 0x9E58, 0xF7BD, 0x9E59, 0xFB6A, 0x9E5A, 0xF0CB, + 0x9E5B, 0xF0CC, 0x9E5C, 0xF0CD, 0x9E5D, 0xFB6B, 0x9E5E, 0xF0CE, 0x9E5F, 0xFB6C, 0x9E60, 0xFB6D, 0x9E61, 0xFB6E, 0x9E62, 0xFB6F, + 0x9E63, 0xF0CF, 0x9E64, 0xBAD7, 0x9E65, 0xFB70, 0x9E66, 0xF0D0, 0x9E67, 0xF0D1, 0x9E68, 0xF0D2, 0x9E69, 0xF0D3, 0x9E6A, 0xF0D4, + 0x9E6B, 0xF0D5, 0x9E6C, 0xF0D6, 0x9E6D, 0xF0D8, 0x9E6E, 0xFB71, 0x9E6F, 0xFB72, 0x9E70, 0xD3A5, 0x9E71, 0xF0D7, 0x9E72, 0xFB73, + 0x9E73, 0xF0D9, 0x9E74, 0xFB74, 0x9E75, 0xFB75, 0x9E76, 0xFB76, 0x9E77, 0xFB77, 0x9E78, 0xFB78, 0x9E79, 0xFB79, 0x9E7A, 0xFB7A, + 0x9E7B, 0xFB7B, 0x9E7C, 0xFB7C, 0x9E7D, 0xFB7D, 0x9E7E, 0xF5BA, 0x9E7F, 0xC2B9, 0x9E80, 0xFB7E, 0x9E81, 0xFB80, 0x9E82, 0xF7E4, + 0x9E83, 0xFB81, 0x9E84, 0xFB82, 0x9E85, 0xFB83, 0x9E86, 0xFB84, 0x9E87, 0xF7E5, 0x9E88, 0xF7E6, 0x9E89, 0xFB85, 0x9E8A, 0xFB86, + 0x9E8B, 0xF7E7, 0x9E8C, 0xFB87, 0x9E8D, 0xFB88, 0x9E8E, 0xFB89, 0x9E8F, 0xFB8A, 0x9E90, 0xFB8B, 0x9E91, 0xFB8C, 0x9E92, 0xF7E8, + 0x9E93, 0xC2B4, 0x9E94, 0xFB8D, 0x9E95, 0xFB8E, 0x9E96, 0xFB8F, 0x9E97, 0xFB90, 0x9E98, 0xFB91, 0x9E99, 0xFB92, 0x9E9A, 0xFB93, + 0x9E9B, 0xFB94, 0x9E9C, 0xFB95, 0x9E9D, 0xF7EA, 0x9E9E, 0xFB96, 0x9E9F, 0xF7EB, 0x9EA0, 0xFB97, 0x9EA1, 0xFB98, 0x9EA2, 0xFB99, + 0x9EA3, 0xFB9A, 0x9EA4, 0xFB9B, 0x9EA5, 0xFB9C, 0x9EA6, 0xC2F3, 0x9EA7, 0xFB9D, 0x9EA8, 0xFB9E, 0x9EA9, 0xFB9F, 0x9EAA, 0xFBA0, + 0x9EAB, 0xFC40, 0x9EAC, 0xFC41, 0x9EAD, 0xFC42, 0x9EAE, 0xFC43, 0x9EAF, 0xFC44, 0x9EB0, 0xFC45, 0x9EB1, 0xFC46, 0x9EB2, 0xFC47, + 0x9EB3, 0xFC48, 0x9EB4, 0xF4F0, 0x9EB5, 0xFC49, 0x9EB6, 0xFC4A, 0x9EB7, 0xFC4B, 0x9EB8, 0xF4EF, 0x9EB9, 0xFC4C, 0x9EBA, 0xFC4D, + 0x9EBB, 0xC2E9, 0x9EBC, 0xFC4E, 0x9EBD, 0xF7E1, 0x9EBE, 0xF7E2, 0x9EBF, 0xFC4F, 0x9EC0, 0xFC50, 0x9EC1, 0xFC51, 0x9EC2, 0xFC52, + 0x9EC3, 0xFC53, 0x9EC4, 0xBBC6, 0x9EC5, 0xFC54, 0x9EC6, 0xFC55, 0x9EC7, 0xFC56, 0x9EC8, 0xFC57, 0x9EC9, 0xD9E4, 0x9ECA, 0xFC58, + 0x9ECB, 0xFC59, 0x9ECC, 0xFC5A, 0x9ECD, 0xCAF2, 0x9ECE, 0xC0E8, 0x9ECF, 0xF0A4, 0x9ED0, 0xFC5B, 0x9ED1, 0xBADA, 0x9ED2, 0xFC5C, + 0x9ED3, 0xFC5D, 0x9ED4, 0xC7AD, 0x9ED5, 0xFC5E, 0x9ED6, 0xFC5F, 0x9ED7, 0xFC60, 0x9ED8, 0xC4AC, 0x9ED9, 0xFC61, 0x9EDA, 0xFC62, + 0x9EDB, 0xF7EC, 0x9EDC, 0xF7ED, 0x9EDD, 0xF7EE, 0x9EDE, 0xFC63, 0x9EDF, 0xF7F0, 0x9EE0, 0xF7EF, 0x9EE1, 0xFC64, 0x9EE2, 0xF7F1, + 0x9EE3, 0xFC65, 0x9EE4, 0xFC66, 0x9EE5, 0xF7F4, 0x9EE6, 0xFC67, 0x9EE7, 0xF7F3, 0x9EE8, 0xFC68, 0x9EE9, 0xF7F2, 0x9EEA, 0xF7F5, + 0x9EEB, 0xFC69, 0x9EEC, 0xFC6A, 0x9EED, 0xFC6B, 0x9EEE, 0xFC6C, 0x9EEF, 0xF7F6, 0x9EF0, 0xFC6D, 0x9EF1, 0xFC6E, 0x9EF2, 0xFC6F, + 0x9EF3, 0xFC70, 0x9EF4, 0xFC71, 0x9EF5, 0xFC72, 0x9EF6, 0xFC73, 0x9EF7, 0xFC74, 0x9EF8, 0xFC75, 0x9EF9, 0xEDE9, 0x9EFA, 0xFC76, + 0x9EFB, 0xEDEA, 0x9EFC, 0xEDEB, 0x9EFD, 0xFC77, 0x9EFE, 0xF6BC, 0x9EFF, 0xFC78, 0x9F00, 0xFC79, 0x9F01, 0xFC7A, 0x9F02, 0xFC7B, + 0x9F03, 0xFC7C, 0x9F04, 0xFC7D, 0x9F05, 0xFC7E, 0x9F06, 0xFC80, 0x9F07, 0xFC81, 0x9F08, 0xFC82, 0x9F09, 0xFC83, 0x9F0A, 0xFC84, + 0x9F0B, 0xF6BD, 0x9F0C, 0xFC85, 0x9F0D, 0xF6BE, 0x9F0E, 0xB6A6, 0x9F0F, 0xFC86, 0x9F10, 0xD8BE, 0x9F11, 0xFC87, 0x9F12, 0xFC88, + 0x9F13, 0xB9C4, 0x9F14, 0xFC89, 0x9F15, 0xFC8A, 0x9F16, 0xFC8B, 0x9F17, 0xD8BB, 0x9F18, 0xFC8C, 0x9F19, 0xDCB1, 0x9F1A, 0xFC8D, + 0x9F1B, 0xFC8E, 0x9F1C, 0xFC8F, 0x9F1D, 0xFC90, 0x9F1E, 0xFC91, 0x9F1F, 0xFC92, 0x9F20, 0xCAF3, 0x9F21, 0xFC93, 0x9F22, 0xF7F7, + 0x9F23, 0xFC94, 0x9F24, 0xFC95, 0x9F25, 0xFC96, 0x9F26, 0xFC97, 0x9F27, 0xFC98, 0x9F28, 0xFC99, 0x9F29, 0xFC9A, 0x9F2A, 0xFC9B, + 0x9F2B, 0xFC9C, 0x9F2C, 0xF7F8, 0x9F2D, 0xFC9D, 0x9F2E, 0xFC9E, 0x9F2F, 0xF7F9, 0x9F30, 0xFC9F, 0x9F31, 0xFCA0, 0x9F32, 0xFD40, + 0x9F33, 0xFD41, 0x9F34, 0xFD42, 0x9F35, 0xFD43, 0x9F36, 0xFD44, 0x9F37, 0xF7FB, 0x9F38, 0xFD45, 0x9F39, 0xF7FA, 0x9F3A, 0xFD46, + 0x9F3B, 0xB1C7, 0x9F3C, 0xFD47, 0x9F3D, 0xF7FC, 0x9F3E, 0xF7FD, 0x9F3F, 0xFD48, 0x9F40, 0xFD49, 0x9F41, 0xFD4A, 0x9F42, 0xFD4B, + 0x9F43, 0xFD4C, 0x9F44, 0xF7FE, 0x9F45, 0xFD4D, 0x9F46, 0xFD4E, 0x9F47, 0xFD4F, 0x9F48, 0xFD50, 0x9F49, 0xFD51, 0x9F4A, 0xFD52, + 0x9F4B, 0xFD53, 0x9F4C, 0xFD54, 0x9F4D, 0xFD55, 0x9F4E, 0xFD56, 0x9F4F, 0xFD57, 0x9F50, 0xC6EB, 0x9F51, 0xECB4, 0x9F52, 0xFD58, + 0x9F53, 0xFD59, 0x9F54, 0xFD5A, 0x9F55, 0xFD5B, 0x9F56, 0xFD5C, 0x9F57, 0xFD5D, 0x9F58, 0xFD5E, 0x9F59, 0xFD5F, 0x9F5A, 0xFD60, + 0x9F5B, 0xFD61, 0x9F5C, 0xFD62, 0x9F5D, 0xFD63, 0x9F5E, 0xFD64, 0x9F5F, 0xFD65, 0x9F60, 0xFD66, 0x9F61, 0xFD67, 0x9F62, 0xFD68, + 0x9F63, 0xFD69, 0x9F64, 0xFD6A, 0x9F65, 0xFD6B, 0x9F66, 0xFD6C, 0x9F67, 0xFD6D, 0x9F68, 0xFD6E, 0x9F69, 0xFD6F, 0x9F6A, 0xFD70, + 0x9F6B, 0xFD71, 0x9F6C, 0xFD72, 0x9F6D, 0xFD73, 0x9F6E, 0xFD74, 0x9F6F, 0xFD75, 0x9F70, 0xFD76, 0x9F71, 0xFD77, 0x9F72, 0xFD78, + 0x9F73, 0xFD79, 0x9F74, 0xFD7A, 0x9F75, 0xFD7B, 0x9F76, 0xFD7C, 0x9F77, 0xFD7D, 0x9F78, 0xFD7E, 0x9F79, 0xFD80, 0x9F7A, 0xFD81, + 0x9F7B, 0xFD82, 0x9F7C, 0xFD83, 0x9F7D, 0xFD84, 0x9F7E, 0xFD85, 0x9F7F, 0xB3DD, 0x9F80, 0xF6B3, 0x9F81, 0xFD86, 0x9F82, 0xFD87, + 0x9F83, 0xF6B4, 0x9F84, 0xC1E4, 0x9F85, 0xF6B5, 0x9F86, 0xF6B6, 0x9F87, 0xF6B7, 0x9F88, 0xF6B8, 0x9F89, 0xF6B9, 0x9F8A, 0xF6BA, + 0x9F8B, 0xC8A3, 0x9F8C, 0xF6BB, 0x9F8D, 0xFD88, 0x9F8E, 0xFD89, 0x9F8F, 0xFD8A, 0x9F90, 0xFD8B, 0x9F91, 0xFD8C, 0x9F92, 0xFD8D, + 0x9F93, 0xFD8E, 0x9F94, 0xFD8F, 0x9F95, 0xFD90, 0x9F96, 0xFD91, 0x9F97, 0xFD92, 0x9F98, 0xFD93, 0x9F99, 0xC1FA, 0x9F9A, 0xB9A8, + 0x9F9B, 0xEDE8, 0x9F9C, 0xFD94, 0x9F9D, 0xFD95, 0x9F9E, 0xFD96, 0x9F9F, 0xB9EA, 0x9FA0, 0xD9DF, 0x9FA1, 0xFD97, 0x9FA2, 0xFD98, + 0x9FA3, 0xFD99, 0x9FA4, 0xFD9A, 0x9FA5, 0xFD9B, 0xF92C, 0xFD9C, 0xF979, 0xFD9D, 0xF995, 0xFD9E, 0xF9E7, 0xFD9F, 0xF9F1, 0xFDA0, + 0xFA0C, 0xFE40, 0xFA0D, 0xFE41, 0xFA0E, 0xFE42, 0xFA0F, 0xFE43, 0xFA11, 0xFE44, 0xFA13, 0xFE45, 0xFA14, 0xFE46, 0xFA18, 0xFE47, + 0xFA1F, 0xFE48, 0xFA20, 0xFE49, 0xFA21, 0xFE4A, 0xFA23, 0xFE4B, 0xFA24, 0xFE4C, 0xFA27, 0xFE4D, 0xFA28, 0xFE4E, 0xFA29, 0xFE4F, + 0xFE30, 0xA955, 0xFE31, 0xA6F2, 0xFE33, 0xA6F4, 0xFE34, 0xA6F5, 0xFE35, 0xA6E0, 0xFE36, 0xA6E1, 0xFE37, 0xA6F0, 0xFE38, 0xA6F1, + 0xFE39, 0xA6E2, 0xFE3A, 0xA6E3, 0xFE3B, 0xA6EE, 0xFE3C, 0xA6EF, 0xFE3D, 0xA6E6, 0xFE3E, 0xA6E7, 0xFE3F, 0xA6E4, 0xFE40, 0xA6E5, + 0xFE41, 0xA6E8, 0xFE42, 0xA6E9, 0xFE43, 0xA6EA, 0xFE44, 0xA6EB, 0xFE49, 0xA968, 0xFE4A, 0xA969, 0xFE4B, 0xA96A, 0xFE4C, 0xA96B, + 0xFE4D, 0xA96C, 0xFE4E, 0xA96D, 0xFE4F, 0xA96E, 0xFE50, 0xA96F, 0xFE51, 0xA970, 0xFE52, 0xA971, 0xFE54, 0xA972, 0xFE55, 0xA973, + 0xFE56, 0xA974, 0xFE57, 0xA975, 0xFE59, 0xA976, 0xFE5A, 0xA977, 0xFE5B, 0xA978, 0xFE5C, 0xA979, 0xFE5D, 0xA97A, 0xFE5E, 0xA97B, + 0xFE5F, 0xA97C, 0xFE60, 0xA97D, 0xFE61, 0xA97E, 0xFE62, 0xA980, 0xFE63, 0xA981, 0xFE64, 0xA982, 0xFE65, 0xA983, 0xFE66, 0xA984, + 0xFE68, 0xA985, 0xFE69, 0xA986, 0xFE6A, 0xA987, 0xFE6B, 0xA988, 0xFF01, 0xA3A1, 0xFF02, 0xA3A2, 0xFF03, 0xA3A3, 0xFF04, 0xA1E7, + 0xFF05, 0xA3A5, 0xFF06, 0xA3A6, 0xFF07, 0xA3A7, 0xFF08, 0xA3A8, 0xFF09, 0xA3A9, 0xFF0A, 0xA3AA, 0xFF0B, 0xA3AB, 0xFF0C, 0xA3AC, + 0xFF0D, 0xA3AD, 0xFF0E, 0xA3AE, 0xFF0F, 0xA3AF, 0xFF10, 0xA3B0, 0xFF11, 0xA3B1, 0xFF12, 0xA3B2, 0xFF13, 0xA3B3, 0xFF14, 0xA3B4, + 0xFF15, 0xA3B5, 0xFF16, 0xA3B6, 0xFF17, 0xA3B7, 0xFF18, 0xA3B8, 0xFF19, 0xA3B9, 0xFF1A, 0xA3BA, 0xFF1B, 0xA3BB, 0xFF1C, 0xA3BC, + 0xFF1D, 0xA3BD, 0xFF1E, 0xA3BE, 0xFF1F, 0xA3BF, 0xFF20, 0xA3C0, 0xFF21, 0xA3C1, 0xFF22, 0xA3C2, 0xFF23, 0xA3C3, 0xFF24, 0xA3C4, + 0xFF25, 0xA3C5, 0xFF26, 0xA3C6, 0xFF27, 0xA3C7, 0xFF28, 0xA3C8, 0xFF29, 0xA3C9, 0xFF2A, 0xA3CA, 0xFF2B, 0xA3CB, 0xFF2C, 0xA3CC, + 0xFF2D, 0xA3CD, 0xFF2E, 0xA3CE, 0xFF2F, 0xA3CF, 0xFF30, 0xA3D0, 0xFF31, 0xA3D1, 0xFF32, 0xA3D2, 0xFF33, 0xA3D3, 0xFF34, 0xA3D4, + 0xFF35, 0xA3D5, 0xFF36, 0xA3D6, 0xFF37, 0xA3D7, 0xFF38, 0xA3D8, 0xFF39, 0xA3D9, 0xFF3A, 0xA3DA, 0xFF3B, 0xA3DB, 0xFF3C, 0xA3DC, + 0xFF3D, 0xA3DD, 0xFF3E, 0xA3DE, 0xFF3F, 0xA3DF, 0xFF40, 0xA3E0, 0xFF41, 0xA3E1, 0xFF42, 0xA3E2, 0xFF43, 0xA3E3, 0xFF44, 0xA3E4, + 0xFF45, 0xA3E5, 0xFF46, 0xA3E6, 0xFF47, 0xA3E7, 0xFF48, 0xA3E8, 0xFF49, 0xA3E9, 0xFF4A, 0xA3EA, 0xFF4B, 0xA3EB, 0xFF4C, 0xA3EC, + 0xFF4D, 0xA3ED, 0xFF4E, 0xA3EE, 0xFF4F, 0xA3EF, 0xFF50, 0xA3F0, 0xFF51, 0xA3F1, 0xFF52, 0xA3F2, 0xFF53, 0xA3F3, 0xFF54, 0xA3F4, + 0xFF55, 0xA3F5, 0xFF56, 0xA3F6, 0xFF57, 0xA3F7, 0xFF58, 0xA3F8, 0xFF59, 0xA3F9, 0xFF5A, 0xA3FA, 0xFF5B, 0xA3FB, 0xFF5C, 0xA3FC, + 0xFF5D, 0xA3FD, 0xFF5E, 0xA1AB, 0xFFE0, 0xA1E9, 0xFFE1, 0xA1EA, 0xFFE2, 0xA956, 0xFFE3, 0xA3FE, 0xFFE4, 0xA957, 0xFFE5, 0xA3A4, + 0, 0 +}; + +static const WCHAR oem2uni936[] = { /* GBK --> Unicode pairs */ + 0x0080, 0x20AC, 0x8140, 0x4E02, 0x8141, 0x4E04, 0x8142, 0x4E05, 0x8143, 0x4E06, 0x8144, 0x4E0F, 0x8145, 0x4E12, 0x8146, 0x4E17, + 0x8147, 0x4E1F, 0x8148, 0x4E20, 0x8149, 0x4E21, 0x814A, 0x4E23, 0x814B, 0x4E26, 0x814C, 0x4E29, 0x814D, 0x4E2E, 0x814E, 0x4E2F, + 0x814F, 0x4E31, 0x8150, 0x4E33, 0x8151, 0x4E35, 0x8152, 0x4E37, 0x8153, 0x4E3C, 0x8154, 0x4E40, 0x8155, 0x4E41, 0x8156, 0x4E42, + 0x8157, 0x4E44, 0x8158, 0x4E46, 0x8159, 0x4E4A, 0x815A, 0x4E51, 0x815B, 0x4E55, 0x815C, 0x4E57, 0x815D, 0x4E5A, 0x815E, 0x4E5B, + 0x815F, 0x4E62, 0x8160, 0x4E63, 0x8161, 0x4E64, 0x8162, 0x4E65, 0x8163, 0x4E67, 0x8164, 0x4E68, 0x8165, 0x4E6A, 0x8166, 0x4E6B, + 0x8167, 0x4E6C, 0x8168, 0x4E6D, 0x8169, 0x4E6E, 0x816A, 0x4E6F, 0x816B, 0x4E72, 0x816C, 0x4E74, 0x816D, 0x4E75, 0x816E, 0x4E76, + 0x816F, 0x4E77, 0x8170, 0x4E78, 0x8171, 0x4E79, 0x8172, 0x4E7A, 0x8173, 0x4E7B, 0x8174, 0x4E7C, 0x8175, 0x4E7D, 0x8176, 0x4E7F, + 0x8177, 0x4E80, 0x8178, 0x4E81, 0x8179, 0x4E82, 0x817A, 0x4E83, 0x817B, 0x4E84, 0x817C, 0x4E85, 0x817D, 0x4E87, 0x817E, 0x4E8A, + 0x8180, 0x4E90, 0x8181, 0x4E96, 0x8182, 0x4E97, 0x8183, 0x4E99, 0x8184, 0x4E9C, 0x8185, 0x4E9D, 0x8186, 0x4E9E, 0x8187, 0x4EA3, + 0x8188, 0x4EAA, 0x8189, 0x4EAF, 0x818A, 0x4EB0, 0x818B, 0x4EB1, 0x818C, 0x4EB4, 0x818D, 0x4EB6, 0x818E, 0x4EB7, 0x818F, 0x4EB8, + 0x8190, 0x4EB9, 0x8191, 0x4EBC, 0x8192, 0x4EBD, 0x8193, 0x4EBE, 0x8194, 0x4EC8, 0x8195, 0x4ECC, 0x8196, 0x4ECF, 0x8197, 0x4ED0, + 0x8198, 0x4ED2, 0x8199, 0x4EDA, 0x819A, 0x4EDB, 0x819B, 0x4EDC, 0x819C, 0x4EE0, 0x819D, 0x4EE2, 0x819E, 0x4EE6, 0x819F, 0x4EE7, + 0x81A0, 0x4EE9, 0x81A1, 0x4EED, 0x81A2, 0x4EEE, 0x81A3, 0x4EEF, 0x81A4, 0x4EF1, 0x81A5, 0x4EF4, 0x81A6, 0x4EF8, 0x81A7, 0x4EF9, + 0x81A8, 0x4EFA, 0x81A9, 0x4EFC, 0x81AA, 0x4EFE, 0x81AB, 0x4F00, 0x81AC, 0x4F02, 0x81AD, 0x4F03, 0x81AE, 0x4F04, 0x81AF, 0x4F05, + 0x81B0, 0x4F06, 0x81B1, 0x4F07, 0x81B2, 0x4F08, 0x81B3, 0x4F0B, 0x81B4, 0x4F0C, 0x81B5, 0x4F12, 0x81B6, 0x4F13, 0x81B7, 0x4F14, + 0x81B8, 0x4F15, 0x81B9, 0x4F16, 0x81BA, 0x4F1C, 0x81BB, 0x4F1D, 0x81BC, 0x4F21, 0x81BD, 0x4F23, 0x81BE, 0x4F28, 0x81BF, 0x4F29, + 0x81C0, 0x4F2C, 0x81C1, 0x4F2D, 0x81C2, 0x4F2E, 0x81C3, 0x4F31, 0x81C4, 0x4F33, 0x81C5, 0x4F35, 0x81C6, 0x4F37, 0x81C7, 0x4F39, + 0x81C8, 0x4F3B, 0x81C9, 0x4F3E, 0x81CA, 0x4F3F, 0x81CB, 0x4F40, 0x81CC, 0x4F41, 0x81CD, 0x4F42, 0x81CE, 0x4F44, 0x81CF, 0x4F45, + 0x81D0, 0x4F47, 0x81D1, 0x4F48, 0x81D2, 0x4F49, 0x81D3, 0x4F4A, 0x81D4, 0x4F4B, 0x81D5, 0x4F4C, 0x81D6, 0x4F52, 0x81D7, 0x4F54, + 0x81D8, 0x4F56, 0x81D9, 0x4F61, 0x81DA, 0x4F62, 0x81DB, 0x4F66, 0x81DC, 0x4F68, 0x81DD, 0x4F6A, 0x81DE, 0x4F6B, 0x81DF, 0x4F6D, + 0x81E0, 0x4F6E, 0x81E1, 0x4F71, 0x81E2, 0x4F72, 0x81E3, 0x4F75, 0x81E4, 0x4F77, 0x81E5, 0x4F78, 0x81E6, 0x4F79, 0x81E7, 0x4F7A, + 0x81E8, 0x4F7D, 0x81E9, 0x4F80, 0x81EA, 0x4F81, 0x81EB, 0x4F82, 0x81EC, 0x4F85, 0x81ED, 0x4F86, 0x81EE, 0x4F87, 0x81EF, 0x4F8A, + 0x81F0, 0x4F8C, 0x81F1, 0x4F8E, 0x81F2, 0x4F90, 0x81F3, 0x4F92, 0x81F4, 0x4F93, 0x81F5, 0x4F95, 0x81F6, 0x4F96, 0x81F7, 0x4F98, + 0x81F8, 0x4F99, 0x81F9, 0x4F9A, 0x81FA, 0x4F9C, 0x81FB, 0x4F9E, 0x81FC, 0x4F9F, 0x81FD, 0x4FA1, 0x81FE, 0x4FA2, 0x8240, 0x4FA4, + 0x8241, 0x4FAB, 0x8242, 0x4FAD, 0x8243, 0x4FB0, 0x8244, 0x4FB1, 0x8245, 0x4FB2, 0x8246, 0x4FB3, 0x8247, 0x4FB4, 0x8248, 0x4FB6, + 0x8249, 0x4FB7, 0x824A, 0x4FB8, 0x824B, 0x4FB9, 0x824C, 0x4FBA, 0x824D, 0x4FBB, 0x824E, 0x4FBC, 0x824F, 0x4FBD, 0x8250, 0x4FBE, + 0x8251, 0x4FC0, 0x8252, 0x4FC1, 0x8253, 0x4FC2, 0x8254, 0x4FC6, 0x8255, 0x4FC7, 0x8256, 0x4FC8, 0x8257, 0x4FC9, 0x8258, 0x4FCB, + 0x8259, 0x4FCC, 0x825A, 0x4FCD, 0x825B, 0x4FD2, 0x825C, 0x4FD3, 0x825D, 0x4FD4, 0x825E, 0x4FD5, 0x825F, 0x4FD6, 0x8260, 0x4FD9, + 0x8261, 0x4FDB, 0x8262, 0x4FE0, 0x8263, 0x4FE2, 0x8264, 0x4FE4, 0x8265, 0x4FE5, 0x8266, 0x4FE7, 0x8267, 0x4FEB, 0x8268, 0x4FEC, + 0x8269, 0x4FF0, 0x826A, 0x4FF2, 0x826B, 0x4FF4, 0x826C, 0x4FF5, 0x826D, 0x4FF6, 0x826E, 0x4FF7, 0x826F, 0x4FF9, 0x8270, 0x4FFB, + 0x8271, 0x4FFC, 0x8272, 0x4FFD, 0x8273, 0x4FFF, 0x8274, 0x5000, 0x8275, 0x5001, 0x8276, 0x5002, 0x8277, 0x5003, 0x8278, 0x5004, + 0x8279, 0x5005, 0x827A, 0x5006, 0x827B, 0x5007, 0x827C, 0x5008, 0x827D, 0x5009, 0x827E, 0x500A, 0x8280, 0x500B, 0x8281, 0x500E, + 0x8282, 0x5010, 0x8283, 0x5011, 0x8284, 0x5013, 0x8285, 0x5015, 0x8286, 0x5016, 0x8287, 0x5017, 0x8288, 0x501B, 0x8289, 0x501D, + 0x828A, 0x501E, 0x828B, 0x5020, 0x828C, 0x5022, 0x828D, 0x5023, 0x828E, 0x5024, 0x828F, 0x5027, 0x8290, 0x502B, 0x8291, 0x502F, + 0x8292, 0x5030, 0x8293, 0x5031, 0x8294, 0x5032, 0x8295, 0x5033, 0x8296, 0x5034, 0x8297, 0x5035, 0x8298, 0x5036, 0x8299, 0x5037, + 0x829A, 0x5038, 0x829B, 0x5039, 0x829C, 0x503B, 0x829D, 0x503D, 0x829E, 0x503F, 0x829F, 0x5040, 0x82A0, 0x5041, 0x82A1, 0x5042, + 0x82A2, 0x5044, 0x82A3, 0x5045, 0x82A4, 0x5046, 0x82A5, 0x5049, 0x82A6, 0x504A, 0x82A7, 0x504B, 0x82A8, 0x504D, 0x82A9, 0x5050, + 0x82AA, 0x5051, 0x82AB, 0x5052, 0x82AC, 0x5053, 0x82AD, 0x5054, 0x82AE, 0x5056, 0x82AF, 0x5057, 0x82B0, 0x5058, 0x82B1, 0x5059, + 0x82B2, 0x505B, 0x82B3, 0x505D, 0x82B4, 0x505E, 0x82B5, 0x505F, 0x82B6, 0x5060, 0x82B7, 0x5061, 0x82B8, 0x5062, 0x82B9, 0x5063, + 0x82BA, 0x5064, 0x82BB, 0x5066, 0x82BC, 0x5067, 0x82BD, 0x5068, 0x82BE, 0x5069, 0x82BF, 0x506A, 0x82C0, 0x506B, 0x82C1, 0x506D, + 0x82C2, 0x506E, 0x82C3, 0x506F, 0x82C4, 0x5070, 0x82C5, 0x5071, 0x82C6, 0x5072, 0x82C7, 0x5073, 0x82C8, 0x5074, 0x82C9, 0x5075, + 0x82CA, 0x5078, 0x82CB, 0x5079, 0x82CC, 0x507A, 0x82CD, 0x507C, 0x82CE, 0x507D, 0x82CF, 0x5081, 0x82D0, 0x5082, 0x82D1, 0x5083, + 0x82D2, 0x5084, 0x82D3, 0x5086, 0x82D4, 0x5087, 0x82D5, 0x5089, 0x82D6, 0x508A, 0x82D7, 0x508B, 0x82D8, 0x508C, 0x82D9, 0x508E, + 0x82DA, 0x508F, 0x82DB, 0x5090, 0x82DC, 0x5091, 0x82DD, 0x5092, 0x82DE, 0x5093, 0x82DF, 0x5094, 0x82E0, 0x5095, 0x82E1, 0x5096, + 0x82E2, 0x5097, 0x82E3, 0x5098, 0x82E4, 0x5099, 0x82E5, 0x509A, 0x82E6, 0x509B, 0x82E7, 0x509C, 0x82E8, 0x509D, 0x82E9, 0x509E, + 0x82EA, 0x509F, 0x82EB, 0x50A0, 0x82EC, 0x50A1, 0x82ED, 0x50A2, 0x82EE, 0x50A4, 0x82EF, 0x50A6, 0x82F0, 0x50AA, 0x82F1, 0x50AB, + 0x82F2, 0x50AD, 0x82F3, 0x50AE, 0x82F4, 0x50AF, 0x82F5, 0x50B0, 0x82F6, 0x50B1, 0x82F7, 0x50B3, 0x82F8, 0x50B4, 0x82F9, 0x50B5, + 0x82FA, 0x50B6, 0x82FB, 0x50B7, 0x82FC, 0x50B8, 0x82FD, 0x50B9, 0x82FE, 0x50BC, 0x8340, 0x50BD, 0x8341, 0x50BE, 0x8342, 0x50BF, + 0x8343, 0x50C0, 0x8344, 0x50C1, 0x8345, 0x50C2, 0x8346, 0x50C3, 0x8347, 0x50C4, 0x8348, 0x50C5, 0x8349, 0x50C6, 0x834A, 0x50C7, + 0x834B, 0x50C8, 0x834C, 0x50C9, 0x834D, 0x50CA, 0x834E, 0x50CB, 0x834F, 0x50CC, 0x8350, 0x50CD, 0x8351, 0x50CE, 0x8352, 0x50D0, + 0x8353, 0x50D1, 0x8354, 0x50D2, 0x8355, 0x50D3, 0x8356, 0x50D4, 0x8357, 0x50D5, 0x8358, 0x50D7, 0x8359, 0x50D8, 0x835A, 0x50D9, + 0x835B, 0x50DB, 0x835C, 0x50DC, 0x835D, 0x50DD, 0x835E, 0x50DE, 0x835F, 0x50DF, 0x8360, 0x50E0, 0x8361, 0x50E1, 0x8362, 0x50E2, + 0x8363, 0x50E3, 0x8364, 0x50E4, 0x8365, 0x50E5, 0x8366, 0x50E8, 0x8367, 0x50E9, 0x8368, 0x50EA, 0x8369, 0x50EB, 0x836A, 0x50EF, + 0x836B, 0x50F0, 0x836C, 0x50F1, 0x836D, 0x50F2, 0x836E, 0x50F4, 0x836F, 0x50F6, 0x8370, 0x50F7, 0x8371, 0x50F8, 0x8372, 0x50F9, + 0x8373, 0x50FA, 0x8374, 0x50FC, 0x8375, 0x50FD, 0x8376, 0x50FE, 0x8377, 0x50FF, 0x8378, 0x5100, 0x8379, 0x5101, 0x837A, 0x5102, + 0x837B, 0x5103, 0x837C, 0x5104, 0x837D, 0x5105, 0x837E, 0x5108, 0x8380, 0x5109, 0x8381, 0x510A, 0x8382, 0x510C, 0x8383, 0x510D, + 0x8384, 0x510E, 0x8385, 0x510F, 0x8386, 0x5110, 0x8387, 0x5111, 0x8388, 0x5113, 0x8389, 0x5114, 0x838A, 0x5115, 0x838B, 0x5116, + 0x838C, 0x5117, 0x838D, 0x5118, 0x838E, 0x5119, 0x838F, 0x511A, 0x8390, 0x511B, 0x8391, 0x511C, 0x8392, 0x511D, 0x8393, 0x511E, + 0x8394, 0x511F, 0x8395, 0x5120, 0x8396, 0x5122, 0x8397, 0x5123, 0x8398, 0x5124, 0x8399, 0x5125, 0x839A, 0x5126, 0x839B, 0x5127, + 0x839C, 0x5128, 0x839D, 0x5129, 0x839E, 0x512A, 0x839F, 0x512B, 0x83A0, 0x512C, 0x83A1, 0x512D, 0x83A2, 0x512E, 0x83A3, 0x512F, + 0x83A4, 0x5130, 0x83A5, 0x5131, 0x83A6, 0x5132, 0x83A7, 0x5133, 0x83A8, 0x5134, 0x83A9, 0x5135, 0x83AA, 0x5136, 0x83AB, 0x5137, + 0x83AC, 0x5138, 0x83AD, 0x5139, 0x83AE, 0x513A, 0x83AF, 0x513B, 0x83B0, 0x513C, 0x83B1, 0x513D, 0x83B2, 0x513E, 0x83B3, 0x5142, + 0x83B4, 0x5147, 0x83B5, 0x514A, 0x83B6, 0x514C, 0x83B7, 0x514E, 0x83B8, 0x514F, 0x83B9, 0x5150, 0x83BA, 0x5152, 0x83BB, 0x5153, + 0x83BC, 0x5157, 0x83BD, 0x5158, 0x83BE, 0x5159, 0x83BF, 0x515B, 0x83C0, 0x515D, 0x83C1, 0x515E, 0x83C2, 0x515F, 0x83C3, 0x5160, + 0x83C4, 0x5161, 0x83C5, 0x5163, 0x83C6, 0x5164, 0x83C7, 0x5166, 0x83C8, 0x5167, 0x83C9, 0x5169, 0x83CA, 0x516A, 0x83CB, 0x516F, + 0x83CC, 0x5172, 0x83CD, 0x517A, 0x83CE, 0x517E, 0x83CF, 0x517F, 0x83D0, 0x5183, 0x83D1, 0x5184, 0x83D2, 0x5186, 0x83D3, 0x5187, + 0x83D4, 0x518A, 0x83D5, 0x518B, 0x83D6, 0x518E, 0x83D7, 0x518F, 0x83D8, 0x5190, 0x83D9, 0x5191, 0x83DA, 0x5193, 0x83DB, 0x5194, + 0x83DC, 0x5198, 0x83DD, 0x519A, 0x83DE, 0x519D, 0x83DF, 0x519E, 0x83E0, 0x519F, 0x83E1, 0x51A1, 0x83E2, 0x51A3, 0x83E3, 0x51A6, + 0x83E4, 0x51A7, 0x83E5, 0x51A8, 0x83E6, 0x51A9, 0x83E7, 0x51AA, 0x83E8, 0x51AD, 0x83E9, 0x51AE, 0x83EA, 0x51B4, 0x83EB, 0x51B8, + 0x83EC, 0x51B9, 0x83ED, 0x51BA, 0x83EE, 0x51BE, 0x83EF, 0x51BF, 0x83F0, 0x51C1, 0x83F1, 0x51C2, 0x83F2, 0x51C3, 0x83F3, 0x51C5, + 0x83F4, 0x51C8, 0x83F5, 0x51CA, 0x83F6, 0x51CD, 0x83F7, 0x51CE, 0x83F8, 0x51D0, 0x83F9, 0x51D2, 0x83FA, 0x51D3, 0x83FB, 0x51D4, + 0x83FC, 0x51D5, 0x83FD, 0x51D6, 0x83FE, 0x51D7, 0x8440, 0x51D8, 0x8441, 0x51D9, 0x8442, 0x51DA, 0x8443, 0x51DC, 0x8444, 0x51DE, + 0x8445, 0x51DF, 0x8446, 0x51E2, 0x8447, 0x51E3, 0x8448, 0x51E5, 0x8449, 0x51E6, 0x844A, 0x51E7, 0x844B, 0x51E8, 0x844C, 0x51E9, + 0x844D, 0x51EA, 0x844E, 0x51EC, 0x844F, 0x51EE, 0x8450, 0x51F1, 0x8451, 0x51F2, 0x8452, 0x51F4, 0x8453, 0x51F7, 0x8454, 0x51FE, + 0x8455, 0x5204, 0x8456, 0x5205, 0x8457, 0x5209, 0x8458, 0x520B, 0x8459, 0x520C, 0x845A, 0x520F, 0x845B, 0x5210, 0x845C, 0x5213, + 0x845D, 0x5214, 0x845E, 0x5215, 0x845F, 0x521C, 0x8460, 0x521E, 0x8461, 0x521F, 0x8462, 0x5221, 0x8463, 0x5222, 0x8464, 0x5223, + 0x8465, 0x5225, 0x8466, 0x5226, 0x8467, 0x5227, 0x8468, 0x522A, 0x8469, 0x522C, 0x846A, 0x522F, 0x846B, 0x5231, 0x846C, 0x5232, + 0x846D, 0x5234, 0x846E, 0x5235, 0x846F, 0x523C, 0x8470, 0x523E, 0x8471, 0x5244, 0x8472, 0x5245, 0x8473, 0x5246, 0x8474, 0x5247, + 0x8475, 0x5248, 0x8476, 0x5249, 0x8477, 0x524B, 0x8478, 0x524E, 0x8479, 0x524F, 0x847A, 0x5252, 0x847B, 0x5253, 0x847C, 0x5255, + 0x847D, 0x5257, 0x847E, 0x5258, 0x8480, 0x5259, 0x8481, 0x525A, 0x8482, 0x525B, 0x8483, 0x525D, 0x8484, 0x525F, 0x8485, 0x5260, + 0x8486, 0x5262, 0x8487, 0x5263, 0x8488, 0x5264, 0x8489, 0x5266, 0x848A, 0x5268, 0x848B, 0x526B, 0x848C, 0x526C, 0x848D, 0x526D, + 0x848E, 0x526E, 0x848F, 0x5270, 0x8490, 0x5271, 0x8491, 0x5273, 0x8492, 0x5274, 0x8493, 0x5275, 0x8494, 0x5276, 0x8495, 0x5277, + 0x8496, 0x5278, 0x8497, 0x5279, 0x8498, 0x527A, 0x8499, 0x527B, 0x849A, 0x527C, 0x849B, 0x527E, 0x849C, 0x5280, 0x849D, 0x5283, + 0x849E, 0x5284, 0x849F, 0x5285, 0x84A0, 0x5286, 0x84A1, 0x5287, 0x84A2, 0x5289, 0x84A3, 0x528A, 0x84A4, 0x528B, 0x84A5, 0x528C, + 0x84A6, 0x528D, 0x84A7, 0x528E, 0x84A8, 0x528F, 0x84A9, 0x5291, 0x84AA, 0x5292, 0x84AB, 0x5294, 0x84AC, 0x5295, 0x84AD, 0x5296, + 0x84AE, 0x5297, 0x84AF, 0x5298, 0x84B0, 0x5299, 0x84B1, 0x529A, 0x84B2, 0x529C, 0x84B3, 0x52A4, 0x84B4, 0x52A5, 0x84B5, 0x52A6, + 0x84B6, 0x52A7, 0x84B7, 0x52AE, 0x84B8, 0x52AF, 0x84B9, 0x52B0, 0x84BA, 0x52B4, 0x84BB, 0x52B5, 0x84BC, 0x52B6, 0x84BD, 0x52B7, + 0x84BE, 0x52B8, 0x84BF, 0x52B9, 0x84C0, 0x52BA, 0x84C1, 0x52BB, 0x84C2, 0x52BC, 0x84C3, 0x52BD, 0x84C4, 0x52C0, 0x84C5, 0x52C1, + 0x84C6, 0x52C2, 0x84C7, 0x52C4, 0x84C8, 0x52C5, 0x84C9, 0x52C6, 0x84CA, 0x52C8, 0x84CB, 0x52CA, 0x84CC, 0x52CC, 0x84CD, 0x52CD, + 0x84CE, 0x52CE, 0x84CF, 0x52CF, 0x84D0, 0x52D1, 0x84D1, 0x52D3, 0x84D2, 0x52D4, 0x84D3, 0x52D5, 0x84D4, 0x52D7, 0x84D5, 0x52D9, + 0x84D6, 0x52DA, 0x84D7, 0x52DB, 0x84D8, 0x52DC, 0x84D9, 0x52DD, 0x84DA, 0x52DE, 0x84DB, 0x52E0, 0x84DC, 0x52E1, 0x84DD, 0x52E2, + 0x84DE, 0x52E3, 0x84DF, 0x52E5, 0x84E0, 0x52E6, 0x84E1, 0x52E7, 0x84E2, 0x52E8, 0x84E3, 0x52E9, 0x84E4, 0x52EA, 0x84E5, 0x52EB, + 0x84E6, 0x52EC, 0x84E7, 0x52ED, 0x84E8, 0x52EE, 0x84E9, 0x52EF, 0x84EA, 0x52F1, 0x84EB, 0x52F2, 0x84EC, 0x52F3, 0x84ED, 0x52F4, + 0x84EE, 0x52F5, 0x84EF, 0x52F6, 0x84F0, 0x52F7, 0x84F1, 0x52F8, 0x84F2, 0x52FB, 0x84F3, 0x52FC, 0x84F4, 0x52FD, 0x84F5, 0x5301, + 0x84F6, 0x5302, 0x84F7, 0x5303, 0x84F8, 0x5304, 0x84F9, 0x5307, 0x84FA, 0x5309, 0x84FB, 0x530A, 0x84FC, 0x530B, 0x84FD, 0x530C, + 0x84FE, 0x530E, 0x8540, 0x5311, 0x8541, 0x5312, 0x8542, 0x5313, 0x8543, 0x5314, 0x8544, 0x5318, 0x8545, 0x531B, 0x8546, 0x531C, + 0x8547, 0x531E, 0x8548, 0x531F, 0x8549, 0x5322, 0x854A, 0x5324, 0x854B, 0x5325, 0x854C, 0x5327, 0x854D, 0x5328, 0x854E, 0x5329, + 0x854F, 0x532B, 0x8550, 0x532C, 0x8551, 0x532D, 0x8552, 0x532F, 0x8553, 0x5330, 0x8554, 0x5331, 0x8555, 0x5332, 0x8556, 0x5333, + 0x8557, 0x5334, 0x8558, 0x5335, 0x8559, 0x5336, 0x855A, 0x5337, 0x855B, 0x5338, 0x855C, 0x533C, 0x855D, 0x533D, 0x855E, 0x5340, + 0x855F, 0x5342, 0x8560, 0x5344, 0x8561, 0x5346, 0x8562, 0x534B, 0x8563, 0x534C, 0x8564, 0x534D, 0x8565, 0x5350, 0x8566, 0x5354, + 0x8567, 0x5358, 0x8568, 0x5359, 0x8569, 0x535B, 0x856A, 0x535D, 0x856B, 0x5365, 0x856C, 0x5368, 0x856D, 0x536A, 0x856E, 0x536C, + 0x856F, 0x536D, 0x8570, 0x5372, 0x8571, 0x5376, 0x8572, 0x5379, 0x8573, 0x537B, 0x8574, 0x537C, 0x8575, 0x537D, 0x8576, 0x537E, + 0x8577, 0x5380, 0x8578, 0x5381, 0x8579, 0x5383, 0x857A, 0x5387, 0x857B, 0x5388, 0x857C, 0x538A, 0x857D, 0x538E, 0x857E, 0x538F, + 0x8580, 0x5390, 0x8581, 0x5391, 0x8582, 0x5392, 0x8583, 0x5393, 0x8584, 0x5394, 0x8585, 0x5396, 0x8586, 0x5397, 0x8587, 0x5399, + 0x8588, 0x539B, 0x8589, 0x539C, 0x858A, 0x539E, 0x858B, 0x53A0, 0x858C, 0x53A1, 0x858D, 0x53A4, 0x858E, 0x53A7, 0x858F, 0x53AA, + 0x8590, 0x53AB, 0x8591, 0x53AC, 0x8592, 0x53AD, 0x8593, 0x53AF, 0x8594, 0x53B0, 0x8595, 0x53B1, 0x8596, 0x53B2, 0x8597, 0x53B3, + 0x8598, 0x53B4, 0x8599, 0x53B5, 0x859A, 0x53B7, 0x859B, 0x53B8, 0x859C, 0x53B9, 0x859D, 0x53BA, 0x859E, 0x53BC, 0x859F, 0x53BD, + 0x85A0, 0x53BE, 0x85A1, 0x53C0, 0x85A2, 0x53C3, 0x85A3, 0x53C4, 0x85A4, 0x53C5, 0x85A5, 0x53C6, 0x85A6, 0x53C7, 0x85A7, 0x53CE, + 0x85A8, 0x53CF, 0x85A9, 0x53D0, 0x85AA, 0x53D2, 0x85AB, 0x53D3, 0x85AC, 0x53D5, 0x85AD, 0x53DA, 0x85AE, 0x53DC, 0x85AF, 0x53DD, + 0x85B0, 0x53DE, 0x85B1, 0x53E1, 0x85B2, 0x53E2, 0x85B3, 0x53E7, 0x85B4, 0x53F4, 0x85B5, 0x53FA, 0x85B6, 0x53FE, 0x85B7, 0x53FF, + 0x85B8, 0x5400, 0x85B9, 0x5402, 0x85BA, 0x5405, 0x85BB, 0x5407, 0x85BC, 0x540B, 0x85BD, 0x5414, 0x85BE, 0x5418, 0x85BF, 0x5419, + 0x85C0, 0x541A, 0x85C1, 0x541C, 0x85C2, 0x5422, 0x85C3, 0x5424, 0x85C4, 0x5425, 0x85C5, 0x542A, 0x85C6, 0x5430, 0x85C7, 0x5433, + 0x85C8, 0x5436, 0x85C9, 0x5437, 0x85CA, 0x543A, 0x85CB, 0x543D, 0x85CC, 0x543F, 0x85CD, 0x5441, 0x85CE, 0x5442, 0x85CF, 0x5444, + 0x85D0, 0x5445, 0x85D1, 0x5447, 0x85D2, 0x5449, 0x85D3, 0x544C, 0x85D4, 0x544D, 0x85D5, 0x544E, 0x85D6, 0x544F, 0x85D7, 0x5451, + 0x85D8, 0x545A, 0x85D9, 0x545D, 0x85DA, 0x545E, 0x85DB, 0x545F, 0x85DC, 0x5460, 0x85DD, 0x5461, 0x85DE, 0x5463, 0x85DF, 0x5465, + 0x85E0, 0x5467, 0x85E1, 0x5469, 0x85E2, 0x546A, 0x85E3, 0x546B, 0x85E4, 0x546C, 0x85E5, 0x546D, 0x85E6, 0x546E, 0x85E7, 0x546F, + 0x85E8, 0x5470, 0x85E9, 0x5474, 0x85EA, 0x5479, 0x85EB, 0x547A, 0x85EC, 0x547E, 0x85ED, 0x547F, 0x85EE, 0x5481, 0x85EF, 0x5483, + 0x85F0, 0x5485, 0x85F1, 0x5487, 0x85F2, 0x5488, 0x85F3, 0x5489, 0x85F4, 0x548A, 0x85F5, 0x548D, 0x85F6, 0x5491, 0x85F7, 0x5493, + 0x85F8, 0x5497, 0x85F9, 0x5498, 0x85FA, 0x549C, 0x85FB, 0x549E, 0x85FC, 0x549F, 0x85FD, 0x54A0, 0x85FE, 0x54A1, 0x8640, 0x54A2, + 0x8641, 0x54A5, 0x8642, 0x54AE, 0x8643, 0x54B0, 0x8644, 0x54B2, 0x8645, 0x54B5, 0x8646, 0x54B6, 0x8647, 0x54B7, 0x8648, 0x54B9, + 0x8649, 0x54BA, 0x864A, 0x54BC, 0x864B, 0x54BE, 0x864C, 0x54C3, 0x864D, 0x54C5, 0x864E, 0x54CA, 0x864F, 0x54CB, 0x8650, 0x54D6, + 0x8651, 0x54D8, 0x8652, 0x54DB, 0x8653, 0x54E0, 0x8654, 0x54E1, 0x8655, 0x54E2, 0x8656, 0x54E3, 0x8657, 0x54E4, 0x8658, 0x54EB, + 0x8659, 0x54EC, 0x865A, 0x54EF, 0x865B, 0x54F0, 0x865C, 0x54F1, 0x865D, 0x54F4, 0x865E, 0x54F5, 0x865F, 0x54F6, 0x8660, 0x54F7, + 0x8661, 0x54F8, 0x8662, 0x54F9, 0x8663, 0x54FB, 0x8664, 0x54FE, 0x8665, 0x5500, 0x8666, 0x5502, 0x8667, 0x5503, 0x8668, 0x5504, + 0x8669, 0x5505, 0x866A, 0x5508, 0x866B, 0x550A, 0x866C, 0x550B, 0x866D, 0x550C, 0x866E, 0x550D, 0x866F, 0x550E, 0x8670, 0x5512, + 0x8671, 0x5513, 0x8672, 0x5515, 0x8673, 0x5516, 0x8674, 0x5517, 0x8675, 0x5518, 0x8676, 0x5519, 0x8677, 0x551A, 0x8678, 0x551C, + 0x8679, 0x551D, 0x867A, 0x551E, 0x867B, 0x551F, 0x867C, 0x5521, 0x867D, 0x5525, 0x867E, 0x5526, 0x8680, 0x5528, 0x8681, 0x5529, + 0x8682, 0x552B, 0x8683, 0x552D, 0x8684, 0x5532, 0x8685, 0x5534, 0x8686, 0x5535, 0x8687, 0x5536, 0x8688, 0x5538, 0x8689, 0x5539, + 0x868A, 0x553A, 0x868B, 0x553B, 0x868C, 0x553D, 0x868D, 0x5540, 0x868E, 0x5542, 0x868F, 0x5545, 0x8690, 0x5547, 0x8691, 0x5548, + 0x8692, 0x554B, 0x8693, 0x554C, 0x8694, 0x554D, 0x8695, 0x554E, 0x8696, 0x554F, 0x8697, 0x5551, 0x8698, 0x5552, 0x8699, 0x5553, + 0x869A, 0x5554, 0x869B, 0x5557, 0x869C, 0x5558, 0x869D, 0x5559, 0x869E, 0x555A, 0x869F, 0x555B, 0x86A0, 0x555D, 0x86A1, 0x555E, + 0x86A2, 0x555F, 0x86A3, 0x5560, 0x86A4, 0x5562, 0x86A5, 0x5563, 0x86A6, 0x5568, 0x86A7, 0x5569, 0x86A8, 0x556B, 0x86A9, 0x556F, + 0x86AA, 0x5570, 0x86AB, 0x5571, 0x86AC, 0x5572, 0x86AD, 0x5573, 0x86AE, 0x5574, 0x86AF, 0x5579, 0x86B0, 0x557A, 0x86B1, 0x557D, + 0x86B2, 0x557F, 0x86B3, 0x5585, 0x86B4, 0x5586, 0x86B5, 0x558C, 0x86B6, 0x558D, 0x86B7, 0x558E, 0x86B8, 0x5590, 0x86B9, 0x5592, + 0x86BA, 0x5593, 0x86BB, 0x5595, 0x86BC, 0x5596, 0x86BD, 0x5597, 0x86BE, 0x559A, 0x86BF, 0x559B, 0x86C0, 0x559E, 0x86C1, 0x55A0, + 0x86C2, 0x55A1, 0x86C3, 0x55A2, 0x86C4, 0x55A3, 0x86C5, 0x55A4, 0x86C6, 0x55A5, 0x86C7, 0x55A6, 0x86C8, 0x55A8, 0x86C9, 0x55A9, + 0x86CA, 0x55AA, 0x86CB, 0x55AB, 0x86CC, 0x55AC, 0x86CD, 0x55AD, 0x86CE, 0x55AE, 0x86CF, 0x55AF, 0x86D0, 0x55B0, 0x86D1, 0x55B2, + 0x86D2, 0x55B4, 0x86D3, 0x55B6, 0x86D4, 0x55B8, 0x86D5, 0x55BA, 0x86D6, 0x55BC, 0x86D7, 0x55BF, 0x86D8, 0x55C0, 0x86D9, 0x55C1, + 0x86DA, 0x55C2, 0x86DB, 0x55C3, 0x86DC, 0x55C6, 0x86DD, 0x55C7, 0x86DE, 0x55C8, 0x86DF, 0x55CA, 0x86E0, 0x55CB, 0x86E1, 0x55CE, + 0x86E2, 0x55CF, 0x86E3, 0x55D0, 0x86E4, 0x55D5, 0x86E5, 0x55D7, 0x86E6, 0x55D8, 0x86E7, 0x55D9, 0x86E8, 0x55DA, 0x86E9, 0x55DB, + 0x86EA, 0x55DE, 0x86EB, 0x55E0, 0x86EC, 0x55E2, 0x86ED, 0x55E7, 0x86EE, 0x55E9, 0x86EF, 0x55ED, 0x86F0, 0x55EE, 0x86F1, 0x55F0, + 0x86F2, 0x55F1, 0x86F3, 0x55F4, 0x86F4, 0x55F6, 0x86F5, 0x55F8, 0x86F6, 0x55F9, 0x86F7, 0x55FA, 0x86F8, 0x55FB, 0x86F9, 0x55FC, + 0x86FA, 0x55FF, 0x86FB, 0x5602, 0x86FC, 0x5603, 0x86FD, 0x5604, 0x86FE, 0x5605, 0x8740, 0x5606, 0x8741, 0x5607, 0x8742, 0x560A, + 0x8743, 0x560B, 0x8744, 0x560D, 0x8745, 0x5610, 0x8746, 0x5611, 0x8747, 0x5612, 0x8748, 0x5613, 0x8749, 0x5614, 0x874A, 0x5615, + 0x874B, 0x5616, 0x874C, 0x5617, 0x874D, 0x5619, 0x874E, 0x561A, 0x874F, 0x561C, 0x8750, 0x561D, 0x8751, 0x5620, 0x8752, 0x5621, + 0x8753, 0x5622, 0x8754, 0x5625, 0x8755, 0x5626, 0x8756, 0x5628, 0x8757, 0x5629, 0x8758, 0x562A, 0x8759, 0x562B, 0x875A, 0x562E, + 0x875B, 0x562F, 0x875C, 0x5630, 0x875D, 0x5633, 0x875E, 0x5635, 0x875F, 0x5637, 0x8760, 0x5638, 0x8761, 0x563A, 0x8762, 0x563C, + 0x8763, 0x563D, 0x8764, 0x563E, 0x8765, 0x5640, 0x8766, 0x5641, 0x8767, 0x5642, 0x8768, 0x5643, 0x8769, 0x5644, 0x876A, 0x5645, + 0x876B, 0x5646, 0x876C, 0x5647, 0x876D, 0x5648, 0x876E, 0x5649, 0x876F, 0x564A, 0x8770, 0x564B, 0x8771, 0x564F, 0x8772, 0x5650, + 0x8773, 0x5651, 0x8774, 0x5652, 0x8775, 0x5653, 0x8776, 0x5655, 0x8777, 0x5656, 0x8778, 0x565A, 0x8779, 0x565B, 0x877A, 0x565D, + 0x877B, 0x565E, 0x877C, 0x565F, 0x877D, 0x5660, 0x877E, 0x5661, 0x8780, 0x5663, 0x8781, 0x5665, 0x8782, 0x5666, 0x8783, 0x5667, + 0x8784, 0x566D, 0x8785, 0x566E, 0x8786, 0x566F, 0x8787, 0x5670, 0x8788, 0x5672, 0x8789, 0x5673, 0x878A, 0x5674, 0x878B, 0x5675, + 0x878C, 0x5677, 0x878D, 0x5678, 0x878E, 0x5679, 0x878F, 0x567A, 0x8790, 0x567D, 0x8791, 0x567E, 0x8792, 0x567F, 0x8793, 0x5680, + 0x8794, 0x5681, 0x8795, 0x5682, 0x8796, 0x5683, 0x8797, 0x5684, 0x8798, 0x5687, 0x8799, 0x5688, 0x879A, 0x5689, 0x879B, 0x568A, + 0x879C, 0x568B, 0x879D, 0x568C, 0x879E, 0x568D, 0x879F, 0x5690, 0x87A0, 0x5691, 0x87A1, 0x5692, 0x87A2, 0x5694, 0x87A3, 0x5695, + 0x87A4, 0x5696, 0x87A5, 0x5697, 0x87A6, 0x5698, 0x87A7, 0x5699, 0x87A8, 0x569A, 0x87A9, 0x569B, 0x87AA, 0x569C, 0x87AB, 0x569D, + 0x87AC, 0x569E, 0x87AD, 0x569F, 0x87AE, 0x56A0, 0x87AF, 0x56A1, 0x87B0, 0x56A2, 0x87B1, 0x56A4, 0x87B2, 0x56A5, 0x87B3, 0x56A6, + 0x87B4, 0x56A7, 0x87B5, 0x56A8, 0x87B6, 0x56A9, 0x87B7, 0x56AA, 0x87B8, 0x56AB, 0x87B9, 0x56AC, 0x87BA, 0x56AD, 0x87BB, 0x56AE, + 0x87BC, 0x56B0, 0x87BD, 0x56B1, 0x87BE, 0x56B2, 0x87BF, 0x56B3, 0x87C0, 0x56B4, 0x87C1, 0x56B5, 0x87C2, 0x56B6, 0x87C3, 0x56B8, + 0x87C4, 0x56B9, 0x87C5, 0x56BA, 0x87C6, 0x56BB, 0x87C7, 0x56BD, 0x87C8, 0x56BE, 0x87C9, 0x56BF, 0x87CA, 0x56C0, 0x87CB, 0x56C1, + 0x87CC, 0x56C2, 0x87CD, 0x56C3, 0x87CE, 0x56C4, 0x87CF, 0x56C5, 0x87D0, 0x56C6, 0x87D1, 0x56C7, 0x87D2, 0x56C8, 0x87D3, 0x56C9, + 0x87D4, 0x56CB, 0x87D5, 0x56CC, 0x87D6, 0x56CD, 0x87D7, 0x56CE, 0x87D8, 0x56CF, 0x87D9, 0x56D0, 0x87DA, 0x56D1, 0x87DB, 0x56D2, + 0x87DC, 0x56D3, 0x87DD, 0x56D5, 0x87DE, 0x56D6, 0x87DF, 0x56D8, 0x87E0, 0x56D9, 0x87E1, 0x56DC, 0x87E2, 0x56E3, 0x87E3, 0x56E5, + 0x87E4, 0x56E6, 0x87E5, 0x56E7, 0x87E6, 0x56E8, 0x87E7, 0x56E9, 0x87E8, 0x56EA, 0x87E9, 0x56EC, 0x87EA, 0x56EE, 0x87EB, 0x56EF, + 0x87EC, 0x56F2, 0x87ED, 0x56F3, 0x87EE, 0x56F6, 0x87EF, 0x56F7, 0x87F0, 0x56F8, 0x87F1, 0x56FB, 0x87F2, 0x56FC, 0x87F3, 0x5700, + 0x87F4, 0x5701, 0x87F5, 0x5702, 0x87F6, 0x5705, 0x87F7, 0x5707, 0x87F8, 0x570B, 0x87F9, 0x570C, 0x87FA, 0x570D, 0x87FB, 0x570E, + 0x87FC, 0x570F, 0x87FD, 0x5710, 0x87FE, 0x5711, 0x8840, 0x5712, 0x8841, 0x5713, 0x8842, 0x5714, 0x8843, 0x5715, 0x8844, 0x5716, + 0x8845, 0x5717, 0x8846, 0x5718, 0x8847, 0x5719, 0x8848, 0x571A, 0x8849, 0x571B, 0x884A, 0x571D, 0x884B, 0x571E, 0x884C, 0x5720, + 0x884D, 0x5721, 0x884E, 0x5722, 0x884F, 0x5724, 0x8850, 0x5725, 0x8851, 0x5726, 0x8852, 0x5727, 0x8853, 0x572B, 0x8854, 0x5731, + 0x8855, 0x5732, 0x8856, 0x5734, 0x8857, 0x5735, 0x8858, 0x5736, 0x8859, 0x5737, 0x885A, 0x5738, 0x885B, 0x573C, 0x885C, 0x573D, + 0x885D, 0x573F, 0x885E, 0x5741, 0x885F, 0x5743, 0x8860, 0x5744, 0x8861, 0x5745, 0x8862, 0x5746, 0x8863, 0x5748, 0x8864, 0x5749, + 0x8865, 0x574B, 0x8866, 0x5752, 0x8867, 0x5753, 0x8868, 0x5754, 0x8869, 0x5755, 0x886A, 0x5756, 0x886B, 0x5758, 0x886C, 0x5759, + 0x886D, 0x5762, 0x886E, 0x5763, 0x886F, 0x5765, 0x8870, 0x5767, 0x8871, 0x576C, 0x8872, 0x576E, 0x8873, 0x5770, 0x8874, 0x5771, + 0x8875, 0x5772, 0x8876, 0x5774, 0x8877, 0x5775, 0x8878, 0x5778, 0x8879, 0x5779, 0x887A, 0x577A, 0x887B, 0x577D, 0x887C, 0x577E, + 0x887D, 0x577F, 0x887E, 0x5780, 0x8880, 0x5781, 0x8881, 0x5787, 0x8882, 0x5788, 0x8883, 0x5789, 0x8884, 0x578A, 0x8885, 0x578D, + 0x8886, 0x578E, 0x8887, 0x578F, 0x8888, 0x5790, 0x8889, 0x5791, 0x888A, 0x5794, 0x888B, 0x5795, 0x888C, 0x5796, 0x888D, 0x5797, + 0x888E, 0x5798, 0x888F, 0x5799, 0x8890, 0x579A, 0x8891, 0x579C, 0x8892, 0x579D, 0x8893, 0x579E, 0x8894, 0x579F, 0x8895, 0x57A5, + 0x8896, 0x57A8, 0x8897, 0x57AA, 0x8898, 0x57AC, 0x8899, 0x57AF, 0x889A, 0x57B0, 0x889B, 0x57B1, 0x889C, 0x57B3, 0x889D, 0x57B5, + 0x889E, 0x57B6, 0x889F, 0x57B7, 0x88A0, 0x57B9, 0x88A1, 0x57BA, 0x88A2, 0x57BB, 0x88A3, 0x57BC, 0x88A4, 0x57BD, 0x88A5, 0x57BE, + 0x88A6, 0x57BF, 0x88A7, 0x57C0, 0x88A8, 0x57C1, 0x88A9, 0x57C4, 0x88AA, 0x57C5, 0x88AB, 0x57C6, 0x88AC, 0x57C7, 0x88AD, 0x57C8, + 0x88AE, 0x57C9, 0x88AF, 0x57CA, 0x88B0, 0x57CC, 0x88B1, 0x57CD, 0x88B2, 0x57D0, 0x88B3, 0x57D1, 0x88B4, 0x57D3, 0x88B5, 0x57D6, + 0x88B6, 0x57D7, 0x88B7, 0x57DB, 0x88B8, 0x57DC, 0x88B9, 0x57DE, 0x88BA, 0x57E1, 0x88BB, 0x57E2, 0x88BC, 0x57E3, 0x88BD, 0x57E5, + 0x88BE, 0x57E6, 0x88BF, 0x57E7, 0x88C0, 0x57E8, 0x88C1, 0x57E9, 0x88C2, 0x57EA, 0x88C3, 0x57EB, 0x88C4, 0x57EC, 0x88C5, 0x57EE, + 0x88C6, 0x57F0, 0x88C7, 0x57F1, 0x88C8, 0x57F2, 0x88C9, 0x57F3, 0x88CA, 0x57F5, 0x88CB, 0x57F6, 0x88CC, 0x57F7, 0x88CD, 0x57FB, + 0x88CE, 0x57FC, 0x88CF, 0x57FE, 0x88D0, 0x57FF, 0x88D1, 0x5801, 0x88D2, 0x5803, 0x88D3, 0x5804, 0x88D4, 0x5805, 0x88D5, 0x5808, + 0x88D6, 0x5809, 0x88D7, 0x580A, 0x88D8, 0x580C, 0x88D9, 0x580E, 0x88DA, 0x580F, 0x88DB, 0x5810, 0x88DC, 0x5812, 0x88DD, 0x5813, + 0x88DE, 0x5814, 0x88DF, 0x5816, 0x88E0, 0x5817, 0x88E1, 0x5818, 0x88E2, 0x581A, 0x88E3, 0x581B, 0x88E4, 0x581C, 0x88E5, 0x581D, + 0x88E6, 0x581F, 0x88E7, 0x5822, 0x88E8, 0x5823, 0x88E9, 0x5825, 0x88EA, 0x5826, 0x88EB, 0x5827, 0x88EC, 0x5828, 0x88ED, 0x5829, + 0x88EE, 0x582B, 0x88EF, 0x582C, 0x88F0, 0x582D, 0x88F1, 0x582E, 0x88F2, 0x582F, 0x88F3, 0x5831, 0x88F4, 0x5832, 0x88F5, 0x5833, + 0x88F6, 0x5834, 0x88F7, 0x5836, 0x88F8, 0x5837, 0x88F9, 0x5838, 0x88FA, 0x5839, 0x88FB, 0x583A, 0x88FC, 0x583B, 0x88FD, 0x583C, + 0x88FE, 0x583D, 0x8940, 0x583E, 0x8941, 0x583F, 0x8942, 0x5840, 0x8943, 0x5841, 0x8944, 0x5842, 0x8945, 0x5843, 0x8946, 0x5845, + 0x8947, 0x5846, 0x8948, 0x5847, 0x8949, 0x5848, 0x894A, 0x5849, 0x894B, 0x584A, 0x894C, 0x584B, 0x894D, 0x584E, 0x894E, 0x584F, + 0x894F, 0x5850, 0x8950, 0x5852, 0x8951, 0x5853, 0x8952, 0x5855, 0x8953, 0x5856, 0x8954, 0x5857, 0x8955, 0x5859, 0x8956, 0x585A, + 0x8957, 0x585B, 0x8958, 0x585C, 0x8959, 0x585D, 0x895A, 0x585F, 0x895B, 0x5860, 0x895C, 0x5861, 0x895D, 0x5862, 0x895E, 0x5863, + 0x895F, 0x5864, 0x8960, 0x5866, 0x8961, 0x5867, 0x8962, 0x5868, 0x8963, 0x5869, 0x8964, 0x586A, 0x8965, 0x586D, 0x8966, 0x586E, + 0x8967, 0x586F, 0x8968, 0x5870, 0x8969, 0x5871, 0x896A, 0x5872, 0x896B, 0x5873, 0x896C, 0x5874, 0x896D, 0x5875, 0x896E, 0x5876, + 0x896F, 0x5877, 0x8970, 0x5878, 0x8971, 0x5879, 0x8972, 0x587A, 0x8973, 0x587B, 0x8974, 0x587C, 0x8975, 0x587D, 0x8976, 0x587F, + 0x8977, 0x5882, 0x8978, 0x5884, 0x8979, 0x5886, 0x897A, 0x5887, 0x897B, 0x5888, 0x897C, 0x588A, 0x897D, 0x588B, 0x897E, 0x588C, + 0x8980, 0x588D, 0x8981, 0x588E, 0x8982, 0x588F, 0x8983, 0x5890, 0x8984, 0x5891, 0x8985, 0x5894, 0x8986, 0x5895, 0x8987, 0x5896, + 0x8988, 0x5897, 0x8989, 0x5898, 0x898A, 0x589B, 0x898B, 0x589C, 0x898C, 0x589D, 0x898D, 0x58A0, 0x898E, 0x58A1, 0x898F, 0x58A2, + 0x8990, 0x58A3, 0x8991, 0x58A4, 0x8992, 0x58A5, 0x8993, 0x58A6, 0x8994, 0x58A7, 0x8995, 0x58AA, 0x8996, 0x58AB, 0x8997, 0x58AC, + 0x8998, 0x58AD, 0x8999, 0x58AE, 0x899A, 0x58AF, 0x899B, 0x58B0, 0x899C, 0x58B1, 0x899D, 0x58B2, 0x899E, 0x58B3, 0x899F, 0x58B4, + 0x89A0, 0x58B5, 0x89A1, 0x58B6, 0x89A2, 0x58B7, 0x89A3, 0x58B8, 0x89A4, 0x58B9, 0x89A5, 0x58BA, 0x89A6, 0x58BB, 0x89A7, 0x58BD, + 0x89A8, 0x58BE, 0x89A9, 0x58BF, 0x89AA, 0x58C0, 0x89AB, 0x58C2, 0x89AC, 0x58C3, 0x89AD, 0x58C4, 0x89AE, 0x58C6, 0x89AF, 0x58C7, + 0x89B0, 0x58C8, 0x89B1, 0x58C9, 0x89B2, 0x58CA, 0x89B3, 0x58CB, 0x89B4, 0x58CC, 0x89B5, 0x58CD, 0x89B6, 0x58CE, 0x89B7, 0x58CF, + 0x89B8, 0x58D0, 0x89B9, 0x58D2, 0x89BA, 0x58D3, 0x89BB, 0x58D4, 0x89BC, 0x58D6, 0x89BD, 0x58D7, 0x89BE, 0x58D8, 0x89BF, 0x58D9, + 0x89C0, 0x58DA, 0x89C1, 0x58DB, 0x89C2, 0x58DC, 0x89C3, 0x58DD, 0x89C4, 0x58DE, 0x89C5, 0x58DF, 0x89C6, 0x58E0, 0x89C7, 0x58E1, + 0x89C8, 0x58E2, 0x89C9, 0x58E3, 0x89CA, 0x58E5, 0x89CB, 0x58E6, 0x89CC, 0x58E7, 0x89CD, 0x58E8, 0x89CE, 0x58E9, 0x89CF, 0x58EA, + 0x89D0, 0x58ED, 0x89D1, 0x58EF, 0x89D2, 0x58F1, 0x89D3, 0x58F2, 0x89D4, 0x58F4, 0x89D5, 0x58F5, 0x89D6, 0x58F7, 0x89D7, 0x58F8, + 0x89D8, 0x58FA, 0x89D9, 0x58FB, 0x89DA, 0x58FC, 0x89DB, 0x58FD, 0x89DC, 0x58FE, 0x89DD, 0x58FF, 0x89DE, 0x5900, 0x89DF, 0x5901, + 0x89E0, 0x5903, 0x89E1, 0x5905, 0x89E2, 0x5906, 0x89E3, 0x5908, 0x89E4, 0x5909, 0x89E5, 0x590A, 0x89E6, 0x590B, 0x89E7, 0x590C, + 0x89E8, 0x590E, 0x89E9, 0x5910, 0x89EA, 0x5911, 0x89EB, 0x5912, 0x89EC, 0x5913, 0x89ED, 0x5917, 0x89EE, 0x5918, 0x89EF, 0x591B, + 0x89F0, 0x591D, 0x89F1, 0x591E, 0x89F2, 0x5920, 0x89F3, 0x5921, 0x89F4, 0x5922, 0x89F5, 0x5923, 0x89F6, 0x5926, 0x89F7, 0x5928, + 0x89F8, 0x592C, 0x89F9, 0x5930, 0x89FA, 0x5932, 0x89FB, 0x5933, 0x89FC, 0x5935, 0x89FD, 0x5936, 0x89FE, 0x593B, 0x8A40, 0x593D, + 0x8A41, 0x593E, 0x8A42, 0x593F, 0x8A43, 0x5940, 0x8A44, 0x5943, 0x8A45, 0x5945, 0x8A46, 0x5946, 0x8A47, 0x594A, 0x8A48, 0x594C, + 0x8A49, 0x594D, 0x8A4A, 0x5950, 0x8A4B, 0x5952, 0x8A4C, 0x5953, 0x8A4D, 0x5959, 0x8A4E, 0x595B, 0x8A4F, 0x595C, 0x8A50, 0x595D, + 0x8A51, 0x595E, 0x8A52, 0x595F, 0x8A53, 0x5961, 0x8A54, 0x5963, 0x8A55, 0x5964, 0x8A56, 0x5966, 0x8A57, 0x5967, 0x8A58, 0x5968, + 0x8A59, 0x5969, 0x8A5A, 0x596A, 0x8A5B, 0x596B, 0x8A5C, 0x596C, 0x8A5D, 0x596D, 0x8A5E, 0x596E, 0x8A5F, 0x596F, 0x8A60, 0x5970, + 0x8A61, 0x5971, 0x8A62, 0x5972, 0x8A63, 0x5975, 0x8A64, 0x5977, 0x8A65, 0x597A, 0x8A66, 0x597B, 0x8A67, 0x597C, 0x8A68, 0x597E, + 0x8A69, 0x597F, 0x8A6A, 0x5980, 0x8A6B, 0x5985, 0x8A6C, 0x5989, 0x8A6D, 0x598B, 0x8A6E, 0x598C, 0x8A6F, 0x598E, 0x8A70, 0x598F, + 0x8A71, 0x5990, 0x8A72, 0x5991, 0x8A73, 0x5994, 0x8A74, 0x5995, 0x8A75, 0x5998, 0x8A76, 0x599A, 0x8A77, 0x599B, 0x8A78, 0x599C, + 0x8A79, 0x599D, 0x8A7A, 0x599F, 0x8A7B, 0x59A0, 0x8A7C, 0x59A1, 0x8A7D, 0x59A2, 0x8A7E, 0x59A6, 0x8A80, 0x59A7, 0x8A81, 0x59AC, + 0x8A82, 0x59AD, 0x8A83, 0x59B0, 0x8A84, 0x59B1, 0x8A85, 0x59B3, 0x8A86, 0x59B4, 0x8A87, 0x59B5, 0x8A88, 0x59B6, 0x8A89, 0x59B7, + 0x8A8A, 0x59B8, 0x8A8B, 0x59BA, 0x8A8C, 0x59BC, 0x8A8D, 0x59BD, 0x8A8E, 0x59BF, 0x8A8F, 0x59C0, 0x8A90, 0x59C1, 0x8A91, 0x59C2, + 0x8A92, 0x59C3, 0x8A93, 0x59C4, 0x8A94, 0x59C5, 0x8A95, 0x59C7, 0x8A96, 0x59C8, 0x8A97, 0x59C9, 0x8A98, 0x59CC, 0x8A99, 0x59CD, + 0x8A9A, 0x59CE, 0x8A9B, 0x59CF, 0x8A9C, 0x59D5, 0x8A9D, 0x59D6, 0x8A9E, 0x59D9, 0x8A9F, 0x59DB, 0x8AA0, 0x59DE, 0x8AA1, 0x59DF, + 0x8AA2, 0x59E0, 0x8AA3, 0x59E1, 0x8AA4, 0x59E2, 0x8AA5, 0x59E4, 0x8AA6, 0x59E6, 0x8AA7, 0x59E7, 0x8AA8, 0x59E9, 0x8AA9, 0x59EA, + 0x8AAA, 0x59EB, 0x8AAB, 0x59ED, 0x8AAC, 0x59EE, 0x8AAD, 0x59EF, 0x8AAE, 0x59F0, 0x8AAF, 0x59F1, 0x8AB0, 0x59F2, 0x8AB1, 0x59F3, + 0x8AB2, 0x59F4, 0x8AB3, 0x59F5, 0x8AB4, 0x59F6, 0x8AB5, 0x59F7, 0x8AB6, 0x59F8, 0x8AB7, 0x59FA, 0x8AB8, 0x59FC, 0x8AB9, 0x59FD, + 0x8ABA, 0x59FE, 0x8ABB, 0x5A00, 0x8ABC, 0x5A02, 0x8ABD, 0x5A0A, 0x8ABE, 0x5A0B, 0x8ABF, 0x5A0D, 0x8AC0, 0x5A0E, 0x8AC1, 0x5A0F, + 0x8AC2, 0x5A10, 0x8AC3, 0x5A12, 0x8AC4, 0x5A14, 0x8AC5, 0x5A15, 0x8AC6, 0x5A16, 0x8AC7, 0x5A17, 0x8AC8, 0x5A19, 0x8AC9, 0x5A1A, + 0x8ACA, 0x5A1B, 0x8ACB, 0x5A1D, 0x8ACC, 0x5A1E, 0x8ACD, 0x5A21, 0x8ACE, 0x5A22, 0x8ACF, 0x5A24, 0x8AD0, 0x5A26, 0x8AD1, 0x5A27, + 0x8AD2, 0x5A28, 0x8AD3, 0x5A2A, 0x8AD4, 0x5A2B, 0x8AD5, 0x5A2C, 0x8AD6, 0x5A2D, 0x8AD7, 0x5A2E, 0x8AD8, 0x5A2F, 0x8AD9, 0x5A30, + 0x8ADA, 0x5A33, 0x8ADB, 0x5A35, 0x8ADC, 0x5A37, 0x8ADD, 0x5A38, 0x8ADE, 0x5A39, 0x8ADF, 0x5A3A, 0x8AE0, 0x5A3B, 0x8AE1, 0x5A3D, + 0x8AE2, 0x5A3E, 0x8AE3, 0x5A3F, 0x8AE4, 0x5A41, 0x8AE5, 0x5A42, 0x8AE6, 0x5A43, 0x8AE7, 0x5A44, 0x8AE8, 0x5A45, 0x8AE9, 0x5A47, + 0x8AEA, 0x5A48, 0x8AEB, 0x5A4B, 0x8AEC, 0x5A4C, 0x8AED, 0x5A4D, 0x8AEE, 0x5A4E, 0x8AEF, 0x5A4F, 0x8AF0, 0x5A50, 0x8AF1, 0x5A51, + 0x8AF2, 0x5A52, 0x8AF3, 0x5A53, 0x8AF4, 0x5A54, 0x8AF5, 0x5A56, 0x8AF6, 0x5A57, 0x8AF7, 0x5A58, 0x8AF8, 0x5A59, 0x8AF9, 0x5A5B, + 0x8AFA, 0x5A5C, 0x8AFB, 0x5A5D, 0x8AFC, 0x5A5E, 0x8AFD, 0x5A5F, 0x8AFE, 0x5A60, 0x8B40, 0x5A61, 0x8B41, 0x5A63, 0x8B42, 0x5A64, + 0x8B43, 0x5A65, 0x8B44, 0x5A66, 0x8B45, 0x5A68, 0x8B46, 0x5A69, 0x8B47, 0x5A6B, 0x8B48, 0x5A6C, 0x8B49, 0x5A6D, 0x8B4A, 0x5A6E, + 0x8B4B, 0x5A6F, 0x8B4C, 0x5A70, 0x8B4D, 0x5A71, 0x8B4E, 0x5A72, 0x8B4F, 0x5A73, 0x8B50, 0x5A78, 0x8B51, 0x5A79, 0x8B52, 0x5A7B, + 0x8B53, 0x5A7C, 0x8B54, 0x5A7D, 0x8B55, 0x5A7E, 0x8B56, 0x5A80, 0x8B57, 0x5A81, 0x8B58, 0x5A82, 0x8B59, 0x5A83, 0x8B5A, 0x5A84, + 0x8B5B, 0x5A85, 0x8B5C, 0x5A86, 0x8B5D, 0x5A87, 0x8B5E, 0x5A88, 0x8B5F, 0x5A89, 0x8B60, 0x5A8A, 0x8B61, 0x5A8B, 0x8B62, 0x5A8C, + 0x8B63, 0x5A8D, 0x8B64, 0x5A8E, 0x8B65, 0x5A8F, 0x8B66, 0x5A90, 0x8B67, 0x5A91, 0x8B68, 0x5A93, 0x8B69, 0x5A94, 0x8B6A, 0x5A95, + 0x8B6B, 0x5A96, 0x8B6C, 0x5A97, 0x8B6D, 0x5A98, 0x8B6E, 0x5A99, 0x8B6F, 0x5A9C, 0x8B70, 0x5A9D, 0x8B71, 0x5A9E, 0x8B72, 0x5A9F, + 0x8B73, 0x5AA0, 0x8B74, 0x5AA1, 0x8B75, 0x5AA2, 0x8B76, 0x5AA3, 0x8B77, 0x5AA4, 0x8B78, 0x5AA5, 0x8B79, 0x5AA6, 0x8B7A, 0x5AA7, + 0x8B7B, 0x5AA8, 0x8B7C, 0x5AA9, 0x8B7D, 0x5AAB, 0x8B7E, 0x5AAC, 0x8B80, 0x5AAD, 0x8B81, 0x5AAE, 0x8B82, 0x5AAF, 0x8B83, 0x5AB0, + 0x8B84, 0x5AB1, 0x8B85, 0x5AB4, 0x8B86, 0x5AB6, 0x8B87, 0x5AB7, 0x8B88, 0x5AB9, 0x8B89, 0x5ABA, 0x8B8A, 0x5ABB, 0x8B8B, 0x5ABC, + 0x8B8C, 0x5ABD, 0x8B8D, 0x5ABF, 0x8B8E, 0x5AC0, 0x8B8F, 0x5AC3, 0x8B90, 0x5AC4, 0x8B91, 0x5AC5, 0x8B92, 0x5AC6, 0x8B93, 0x5AC7, + 0x8B94, 0x5AC8, 0x8B95, 0x5ACA, 0x8B96, 0x5ACB, 0x8B97, 0x5ACD, 0x8B98, 0x5ACE, 0x8B99, 0x5ACF, 0x8B9A, 0x5AD0, 0x8B9B, 0x5AD1, + 0x8B9C, 0x5AD3, 0x8B9D, 0x5AD5, 0x8B9E, 0x5AD7, 0x8B9F, 0x5AD9, 0x8BA0, 0x5ADA, 0x8BA1, 0x5ADB, 0x8BA2, 0x5ADD, 0x8BA3, 0x5ADE, + 0x8BA4, 0x5ADF, 0x8BA5, 0x5AE2, 0x8BA6, 0x5AE4, 0x8BA7, 0x5AE5, 0x8BA8, 0x5AE7, 0x8BA9, 0x5AE8, 0x8BAA, 0x5AEA, 0x8BAB, 0x5AEC, + 0x8BAC, 0x5AED, 0x8BAD, 0x5AEE, 0x8BAE, 0x5AEF, 0x8BAF, 0x5AF0, 0x8BB0, 0x5AF2, 0x8BB1, 0x5AF3, 0x8BB2, 0x5AF4, 0x8BB3, 0x5AF5, + 0x8BB4, 0x5AF6, 0x8BB5, 0x5AF7, 0x8BB6, 0x5AF8, 0x8BB7, 0x5AF9, 0x8BB8, 0x5AFA, 0x8BB9, 0x5AFB, 0x8BBA, 0x5AFC, 0x8BBB, 0x5AFD, + 0x8BBC, 0x5AFE, 0x8BBD, 0x5AFF, 0x8BBE, 0x5B00, 0x8BBF, 0x5B01, 0x8BC0, 0x5B02, 0x8BC1, 0x5B03, 0x8BC2, 0x5B04, 0x8BC3, 0x5B05, + 0x8BC4, 0x5B06, 0x8BC5, 0x5B07, 0x8BC6, 0x5B08, 0x8BC7, 0x5B0A, 0x8BC8, 0x5B0B, 0x8BC9, 0x5B0C, 0x8BCA, 0x5B0D, 0x8BCB, 0x5B0E, + 0x8BCC, 0x5B0F, 0x8BCD, 0x5B10, 0x8BCE, 0x5B11, 0x8BCF, 0x5B12, 0x8BD0, 0x5B13, 0x8BD1, 0x5B14, 0x8BD2, 0x5B15, 0x8BD3, 0x5B18, + 0x8BD4, 0x5B19, 0x8BD5, 0x5B1A, 0x8BD6, 0x5B1B, 0x8BD7, 0x5B1C, 0x8BD8, 0x5B1D, 0x8BD9, 0x5B1E, 0x8BDA, 0x5B1F, 0x8BDB, 0x5B20, + 0x8BDC, 0x5B21, 0x8BDD, 0x5B22, 0x8BDE, 0x5B23, 0x8BDF, 0x5B24, 0x8BE0, 0x5B25, 0x8BE1, 0x5B26, 0x8BE2, 0x5B27, 0x8BE3, 0x5B28, + 0x8BE4, 0x5B29, 0x8BE5, 0x5B2A, 0x8BE6, 0x5B2B, 0x8BE7, 0x5B2C, 0x8BE8, 0x5B2D, 0x8BE9, 0x5B2E, 0x8BEA, 0x5B2F, 0x8BEB, 0x5B30, + 0x8BEC, 0x5B31, 0x8BED, 0x5B33, 0x8BEE, 0x5B35, 0x8BEF, 0x5B36, 0x8BF0, 0x5B38, 0x8BF1, 0x5B39, 0x8BF2, 0x5B3A, 0x8BF3, 0x5B3B, + 0x8BF4, 0x5B3C, 0x8BF5, 0x5B3D, 0x8BF6, 0x5B3E, 0x8BF7, 0x5B3F, 0x8BF8, 0x5B41, 0x8BF9, 0x5B42, 0x8BFA, 0x5B43, 0x8BFB, 0x5B44, + 0x8BFC, 0x5B45, 0x8BFD, 0x5B46, 0x8BFE, 0x5B47, 0x8C40, 0x5B48, 0x8C41, 0x5B49, 0x8C42, 0x5B4A, 0x8C43, 0x5B4B, 0x8C44, 0x5B4C, + 0x8C45, 0x5B4D, 0x8C46, 0x5B4E, 0x8C47, 0x5B4F, 0x8C48, 0x5B52, 0x8C49, 0x5B56, 0x8C4A, 0x5B5E, 0x8C4B, 0x5B60, 0x8C4C, 0x5B61, + 0x8C4D, 0x5B67, 0x8C4E, 0x5B68, 0x8C4F, 0x5B6B, 0x8C50, 0x5B6D, 0x8C51, 0x5B6E, 0x8C52, 0x5B6F, 0x8C53, 0x5B72, 0x8C54, 0x5B74, + 0x8C55, 0x5B76, 0x8C56, 0x5B77, 0x8C57, 0x5B78, 0x8C58, 0x5B79, 0x8C59, 0x5B7B, 0x8C5A, 0x5B7C, 0x8C5B, 0x5B7E, 0x8C5C, 0x5B7F, + 0x8C5D, 0x5B82, 0x8C5E, 0x5B86, 0x8C5F, 0x5B8A, 0x8C60, 0x5B8D, 0x8C61, 0x5B8E, 0x8C62, 0x5B90, 0x8C63, 0x5B91, 0x8C64, 0x5B92, + 0x8C65, 0x5B94, 0x8C66, 0x5B96, 0x8C67, 0x5B9F, 0x8C68, 0x5BA7, 0x8C69, 0x5BA8, 0x8C6A, 0x5BA9, 0x8C6B, 0x5BAC, 0x8C6C, 0x5BAD, + 0x8C6D, 0x5BAE, 0x8C6E, 0x5BAF, 0x8C6F, 0x5BB1, 0x8C70, 0x5BB2, 0x8C71, 0x5BB7, 0x8C72, 0x5BBA, 0x8C73, 0x5BBB, 0x8C74, 0x5BBC, + 0x8C75, 0x5BC0, 0x8C76, 0x5BC1, 0x8C77, 0x5BC3, 0x8C78, 0x5BC8, 0x8C79, 0x5BC9, 0x8C7A, 0x5BCA, 0x8C7B, 0x5BCB, 0x8C7C, 0x5BCD, + 0x8C7D, 0x5BCE, 0x8C7E, 0x5BCF, 0x8C80, 0x5BD1, 0x8C81, 0x5BD4, 0x8C82, 0x5BD5, 0x8C83, 0x5BD6, 0x8C84, 0x5BD7, 0x8C85, 0x5BD8, + 0x8C86, 0x5BD9, 0x8C87, 0x5BDA, 0x8C88, 0x5BDB, 0x8C89, 0x5BDC, 0x8C8A, 0x5BE0, 0x8C8B, 0x5BE2, 0x8C8C, 0x5BE3, 0x8C8D, 0x5BE6, + 0x8C8E, 0x5BE7, 0x8C8F, 0x5BE9, 0x8C90, 0x5BEA, 0x8C91, 0x5BEB, 0x8C92, 0x5BEC, 0x8C93, 0x5BED, 0x8C94, 0x5BEF, 0x8C95, 0x5BF1, + 0x8C96, 0x5BF2, 0x8C97, 0x5BF3, 0x8C98, 0x5BF4, 0x8C99, 0x5BF5, 0x8C9A, 0x5BF6, 0x8C9B, 0x5BF7, 0x8C9C, 0x5BFD, 0x8C9D, 0x5BFE, + 0x8C9E, 0x5C00, 0x8C9F, 0x5C02, 0x8CA0, 0x5C03, 0x8CA1, 0x5C05, 0x8CA2, 0x5C07, 0x8CA3, 0x5C08, 0x8CA4, 0x5C0B, 0x8CA5, 0x5C0C, + 0x8CA6, 0x5C0D, 0x8CA7, 0x5C0E, 0x8CA8, 0x5C10, 0x8CA9, 0x5C12, 0x8CAA, 0x5C13, 0x8CAB, 0x5C17, 0x8CAC, 0x5C19, 0x8CAD, 0x5C1B, + 0x8CAE, 0x5C1E, 0x8CAF, 0x5C1F, 0x8CB0, 0x5C20, 0x8CB1, 0x5C21, 0x8CB2, 0x5C23, 0x8CB3, 0x5C26, 0x8CB4, 0x5C28, 0x8CB5, 0x5C29, + 0x8CB6, 0x5C2A, 0x8CB7, 0x5C2B, 0x8CB8, 0x5C2D, 0x8CB9, 0x5C2E, 0x8CBA, 0x5C2F, 0x8CBB, 0x5C30, 0x8CBC, 0x5C32, 0x8CBD, 0x5C33, + 0x8CBE, 0x5C35, 0x8CBF, 0x5C36, 0x8CC0, 0x5C37, 0x8CC1, 0x5C43, 0x8CC2, 0x5C44, 0x8CC3, 0x5C46, 0x8CC4, 0x5C47, 0x8CC5, 0x5C4C, + 0x8CC6, 0x5C4D, 0x8CC7, 0x5C52, 0x8CC8, 0x5C53, 0x8CC9, 0x5C54, 0x8CCA, 0x5C56, 0x8CCB, 0x5C57, 0x8CCC, 0x5C58, 0x8CCD, 0x5C5A, + 0x8CCE, 0x5C5B, 0x8CCF, 0x5C5C, 0x8CD0, 0x5C5D, 0x8CD1, 0x5C5F, 0x8CD2, 0x5C62, 0x8CD3, 0x5C64, 0x8CD4, 0x5C67, 0x8CD5, 0x5C68, + 0x8CD6, 0x5C69, 0x8CD7, 0x5C6A, 0x8CD8, 0x5C6B, 0x8CD9, 0x5C6C, 0x8CDA, 0x5C6D, 0x8CDB, 0x5C70, 0x8CDC, 0x5C72, 0x8CDD, 0x5C73, + 0x8CDE, 0x5C74, 0x8CDF, 0x5C75, 0x8CE0, 0x5C76, 0x8CE1, 0x5C77, 0x8CE2, 0x5C78, 0x8CE3, 0x5C7B, 0x8CE4, 0x5C7C, 0x8CE5, 0x5C7D, + 0x8CE6, 0x5C7E, 0x8CE7, 0x5C80, 0x8CE8, 0x5C83, 0x8CE9, 0x5C84, 0x8CEA, 0x5C85, 0x8CEB, 0x5C86, 0x8CEC, 0x5C87, 0x8CED, 0x5C89, + 0x8CEE, 0x5C8A, 0x8CEF, 0x5C8B, 0x8CF0, 0x5C8E, 0x8CF1, 0x5C8F, 0x8CF2, 0x5C92, 0x8CF3, 0x5C93, 0x8CF4, 0x5C95, 0x8CF5, 0x5C9D, + 0x8CF6, 0x5C9E, 0x8CF7, 0x5C9F, 0x8CF8, 0x5CA0, 0x8CF9, 0x5CA1, 0x8CFA, 0x5CA4, 0x8CFB, 0x5CA5, 0x8CFC, 0x5CA6, 0x8CFD, 0x5CA7, + 0x8CFE, 0x5CA8, 0x8D40, 0x5CAA, 0x8D41, 0x5CAE, 0x8D42, 0x5CAF, 0x8D43, 0x5CB0, 0x8D44, 0x5CB2, 0x8D45, 0x5CB4, 0x8D46, 0x5CB6, + 0x8D47, 0x5CB9, 0x8D48, 0x5CBA, 0x8D49, 0x5CBB, 0x8D4A, 0x5CBC, 0x8D4B, 0x5CBE, 0x8D4C, 0x5CC0, 0x8D4D, 0x5CC2, 0x8D4E, 0x5CC3, + 0x8D4F, 0x5CC5, 0x8D50, 0x5CC6, 0x8D51, 0x5CC7, 0x8D52, 0x5CC8, 0x8D53, 0x5CC9, 0x8D54, 0x5CCA, 0x8D55, 0x5CCC, 0x8D56, 0x5CCD, + 0x8D57, 0x5CCE, 0x8D58, 0x5CCF, 0x8D59, 0x5CD0, 0x8D5A, 0x5CD1, 0x8D5B, 0x5CD3, 0x8D5C, 0x5CD4, 0x8D5D, 0x5CD5, 0x8D5E, 0x5CD6, + 0x8D5F, 0x5CD7, 0x8D60, 0x5CD8, 0x8D61, 0x5CDA, 0x8D62, 0x5CDB, 0x8D63, 0x5CDC, 0x8D64, 0x5CDD, 0x8D65, 0x5CDE, 0x8D66, 0x5CDF, + 0x8D67, 0x5CE0, 0x8D68, 0x5CE2, 0x8D69, 0x5CE3, 0x8D6A, 0x5CE7, 0x8D6B, 0x5CE9, 0x8D6C, 0x5CEB, 0x8D6D, 0x5CEC, 0x8D6E, 0x5CEE, + 0x8D6F, 0x5CEF, 0x8D70, 0x5CF1, 0x8D71, 0x5CF2, 0x8D72, 0x5CF3, 0x8D73, 0x5CF4, 0x8D74, 0x5CF5, 0x8D75, 0x5CF6, 0x8D76, 0x5CF7, + 0x8D77, 0x5CF8, 0x8D78, 0x5CF9, 0x8D79, 0x5CFA, 0x8D7A, 0x5CFC, 0x8D7B, 0x5CFD, 0x8D7C, 0x5CFE, 0x8D7D, 0x5CFF, 0x8D7E, 0x5D00, + 0x8D80, 0x5D01, 0x8D81, 0x5D04, 0x8D82, 0x5D05, 0x8D83, 0x5D08, 0x8D84, 0x5D09, 0x8D85, 0x5D0A, 0x8D86, 0x5D0B, 0x8D87, 0x5D0C, + 0x8D88, 0x5D0D, 0x8D89, 0x5D0F, 0x8D8A, 0x5D10, 0x8D8B, 0x5D11, 0x8D8C, 0x5D12, 0x8D8D, 0x5D13, 0x8D8E, 0x5D15, 0x8D8F, 0x5D17, + 0x8D90, 0x5D18, 0x8D91, 0x5D19, 0x8D92, 0x5D1A, 0x8D93, 0x5D1C, 0x8D94, 0x5D1D, 0x8D95, 0x5D1F, 0x8D96, 0x5D20, 0x8D97, 0x5D21, + 0x8D98, 0x5D22, 0x8D99, 0x5D23, 0x8D9A, 0x5D25, 0x8D9B, 0x5D28, 0x8D9C, 0x5D2A, 0x8D9D, 0x5D2B, 0x8D9E, 0x5D2C, 0x8D9F, 0x5D2F, + 0x8DA0, 0x5D30, 0x8DA1, 0x5D31, 0x8DA2, 0x5D32, 0x8DA3, 0x5D33, 0x8DA4, 0x5D35, 0x8DA5, 0x5D36, 0x8DA6, 0x5D37, 0x8DA7, 0x5D38, + 0x8DA8, 0x5D39, 0x8DA9, 0x5D3A, 0x8DAA, 0x5D3B, 0x8DAB, 0x5D3C, 0x8DAC, 0x5D3F, 0x8DAD, 0x5D40, 0x8DAE, 0x5D41, 0x8DAF, 0x5D42, + 0x8DB0, 0x5D43, 0x8DB1, 0x5D44, 0x8DB2, 0x5D45, 0x8DB3, 0x5D46, 0x8DB4, 0x5D48, 0x8DB5, 0x5D49, 0x8DB6, 0x5D4D, 0x8DB7, 0x5D4E, + 0x8DB8, 0x5D4F, 0x8DB9, 0x5D50, 0x8DBA, 0x5D51, 0x8DBB, 0x5D52, 0x8DBC, 0x5D53, 0x8DBD, 0x5D54, 0x8DBE, 0x5D55, 0x8DBF, 0x5D56, + 0x8DC0, 0x5D57, 0x8DC1, 0x5D59, 0x8DC2, 0x5D5A, 0x8DC3, 0x5D5C, 0x8DC4, 0x5D5E, 0x8DC5, 0x5D5F, 0x8DC6, 0x5D60, 0x8DC7, 0x5D61, + 0x8DC8, 0x5D62, 0x8DC9, 0x5D63, 0x8DCA, 0x5D64, 0x8DCB, 0x5D65, 0x8DCC, 0x5D66, 0x8DCD, 0x5D67, 0x8DCE, 0x5D68, 0x8DCF, 0x5D6A, + 0x8DD0, 0x5D6D, 0x8DD1, 0x5D6E, 0x8DD2, 0x5D70, 0x8DD3, 0x5D71, 0x8DD4, 0x5D72, 0x8DD5, 0x5D73, 0x8DD6, 0x5D75, 0x8DD7, 0x5D76, + 0x8DD8, 0x5D77, 0x8DD9, 0x5D78, 0x8DDA, 0x5D79, 0x8DDB, 0x5D7A, 0x8DDC, 0x5D7B, 0x8DDD, 0x5D7C, 0x8DDE, 0x5D7D, 0x8DDF, 0x5D7E, + 0x8DE0, 0x5D7F, 0x8DE1, 0x5D80, 0x8DE2, 0x5D81, 0x8DE3, 0x5D83, 0x8DE4, 0x5D84, 0x8DE5, 0x5D85, 0x8DE6, 0x5D86, 0x8DE7, 0x5D87, + 0x8DE8, 0x5D88, 0x8DE9, 0x5D89, 0x8DEA, 0x5D8A, 0x8DEB, 0x5D8B, 0x8DEC, 0x5D8C, 0x8DED, 0x5D8D, 0x8DEE, 0x5D8E, 0x8DEF, 0x5D8F, + 0x8DF0, 0x5D90, 0x8DF1, 0x5D91, 0x8DF2, 0x5D92, 0x8DF3, 0x5D93, 0x8DF4, 0x5D94, 0x8DF5, 0x5D95, 0x8DF6, 0x5D96, 0x8DF7, 0x5D97, + 0x8DF8, 0x5D98, 0x8DF9, 0x5D9A, 0x8DFA, 0x5D9B, 0x8DFB, 0x5D9C, 0x8DFC, 0x5D9E, 0x8DFD, 0x5D9F, 0x8DFE, 0x5DA0, 0x8E40, 0x5DA1, + 0x8E41, 0x5DA2, 0x8E42, 0x5DA3, 0x8E43, 0x5DA4, 0x8E44, 0x5DA5, 0x8E45, 0x5DA6, 0x8E46, 0x5DA7, 0x8E47, 0x5DA8, 0x8E48, 0x5DA9, + 0x8E49, 0x5DAA, 0x8E4A, 0x5DAB, 0x8E4B, 0x5DAC, 0x8E4C, 0x5DAD, 0x8E4D, 0x5DAE, 0x8E4E, 0x5DAF, 0x8E4F, 0x5DB0, 0x8E50, 0x5DB1, + 0x8E51, 0x5DB2, 0x8E52, 0x5DB3, 0x8E53, 0x5DB4, 0x8E54, 0x5DB5, 0x8E55, 0x5DB6, 0x8E56, 0x5DB8, 0x8E57, 0x5DB9, 0x8E58, 0x5DBA, + 0x8E59, 0x5DBB, 0x8E5A, 0x5DBC, 0x8E5B, 0x5DBD, 0x8E5C, 0x5DBE, 0x8E5D, 0x5DBF, 0x8E5E, 0x5DC0, 0x8E5F, 0x5DC1, 0x8E60, 0x5DC2, + 0x8E61, 0x5DC3, 0x8E62, 0x5DC4, 0x8E63, 0x5DC6, 0x8E64, 0x5DC7, 0x8E65, 0x5DC8, 0x8E66, 0x5DC9, 0x8E67, 0x5DCA, 0x8E68, 0x5DCB, + 0x8E69, 0x5DCC, 0x8E6A, 0x5DCE, 0x8E6B, 0x5DCF, 0x8E6C, 0x5DD0, 0x8E6D, 0x5DD1, 0x8E6E, 0x5DD2, 0x8E6F, 0x5DD3, 0x8E70, 0x5DD4, + 0x8E71, 0x5DD5, 0x8E72, 0x5DD6, 0x8E73, 0x5DD7, 0x8E74, 0x5DD8, 0x8E75, 0x5DD9, 0x8E76, 0x5DDA, 0x8E77, 0x5DDC, 0x8E78, 0x5DDF, + 0x8E79, 0x5DE0, 0x8E7A, 0x5DE3, 0x8E7B, 0x5DE4, 0x8E7C, 0x5DEA, 0x8E7D, 0x5DEC, 0x8E7E, 0x5DED, 0x8E80, 0x5DF0, 0x8E81, 0x5DF5, + 0x8E82, 0x5DF6, 0x8E83, 0x5DF8, 0x8E84, 0x5DF9, 0x8E85, 0x5DFA, 0x8E86, 0x5DFB, 0x8E87, 0x5DFC, 0x8E88, 0x5DFF, 0x8E89, 0x5E00, + 0x8E8A, 0x5E04, 0x8E8B, 0x5E07, 0x8E8C, 0x5E09, 0x8E8D, 0x5E0A, 0x8E8E, 0x5E0B, 0x8E8F, 0x5E0D, 0x8E90, 0x5E0E, 0x8E91, 0x5E12, + 0x8E92, 0x5E13, 0x8E93, 0x5E17, 0x8E94, 0x5E1E, 0x8E95, 0x5E1F, 0x8E96, 0x5E20, 0x8E97, 0x5E21, 0x8E98, 0x5E22, 0x8E99, 0x5E23, + 0x8E9A, 0x5E24, 0x8E9B, 0x5E25, 0x8E9C, 0x5E28, 0x8E9D, 0x5E29, 0x8E9E, 0x5E2A, 0x8E9F, 0x5E2B, 0x8EA0, 0x5E2C, 0x8EA1, 0x5E2F, + 0x8EA2, 0x5E30, 0x8EA3, 0x5E32, 0x8EA4, 0x5E33, 0x8EA5, 0x5E34, 0x8EA6, 0x5E35, 0x8EA7, 0x5E36, 0x8EA8, 0x5E39, 0x8EA9, 0x5E3A, + 0x8EAA, 0x5E3E, 0x8EAB, 0x5E3F, 0x8EAC, 0x5E40, 0x8EAD, 0x5E41, 0x8EAE, 0x5E43, 0x8EAF, 0x5E46, 0x8EB0, 0x5E47, 0x8EB1, 0x5E48, + 0x8EB2, 0x5E49, 0x8EB3, 0x5E4A, 0x8EB4, 0x5E4B, 0x8EB5, 0x5E4D, 0x8EB6, 0x5E4E, 0x8EB7, 0x5E4F, 0x8EB8, 0x5E50, 0x8EB9, 0x5E51, + 0x8EBA, 0x5E52, 0x8EBB, 0x5E53, 0x8EBC, 0x5E56, 0x8EBD, 0x5E57, 0x8EBE, 0x5E58, 0x8EBF, 0x5E59, 0x8EC0, 0x5E5A, 0x8EC1, 0x5E5C, + 0x8EC2, 0x5E5D, 0x8EC3, 0x5E5F, 0x8EC4, 0x5E60, 0x8EC5, 0x5E63, 0x8EC6, 0x5E64, 0x8EC7, 0x5E65, 0x8EC8, 0x5E66, 0x8EC9, 0x5E67, + 0x8ECA, 0x5E68, 0x8ECB, 0x5E69, 0x8ECC, 0x5E6A, 0x8ECD, 0x5E6B, 0x8ECE, 0x5E6C, 0x8ECF, 0x5E6D, 0x8ED0, 0x5E6E, 0x8ED1, 0x5E6F, + 0x8ED2, 0x5E70, 0x8ED3, 0x5E71, 0x8ED4, 0x5E75, 0x8ED5, 0x5E77, 0x8ED6, 0x5E79, 0x8ED7, 0x5E7E, 0x8ED8, 0x5E81, 0x8ED9, 0x5E82, + 0x8EDA, 0x5E83, 0x8EDB, 0x5E85, 0x8EDC, 0x5E88, 0x8EDD, 0x5E89, 0x8EDE, 0x5E8C, 0x8EDF, 0x5E8D, 0x8EE0, 0x5E8E, 0x8EE1, 0x5E92, + 0x8EE2, 0x5E98, 0x8EE3, 0x5E9B, 0x8EE4, 0x5E9D, 0x8EE5, 0x5EA1, 0x8EE6, 0x5EA2, 0x8EE7, 0x5EA3, 0x8EE8, 0x5EA4, 0x8EE9, 0x5EA8, + 0x8EEA, 0x5EA9, 0x8EEB, 0x5EAA, 0x8EEC, 0x5EAB, 0x8EED, 0x5EAC, 0x8EEE, 0x5EAE, 0x8EEF, 0x5EAF, 0x8EF0, 0x5EB0, 0x8EF1, 0x5EB1, + 0x8EF2, 0x5EB2, 0x8EF3, 0x5EB4, 0x8EF4, 0x5EBA, 0x8EF5, 0x5EBB, 0x8EF6, 0x5EBC, 0x8EF7, 0x5EBD, 0x8EF8, 0x5EBF, 0x8EF9, 0x5EC0, + 0x8EFA, 0x5EC1, 0x8EFB, 0x5EC2, 0x8EFC, 0x5EC3, 0x8EFD, 0x5EC4, 0x8EFE, 0x5EC5, 0x8F40, 0x5EC6, 0x8F41, 0x5EC7, 0x8F42, 0x5EC8, + 0x8F43, 0x5ECB, 0x8F44, 0x5ECC, 0x8F45, 0x5ECD, 0x8F46, 0x5ECE, 0x8F47, 0x5ECF, 0x8F48, 0x5ED0, 0x8F49, 0x5ED4, 0x8F4A, 0x5ED5, + 0x8F4B, 0x5ED7, 0x8F4C, 0x5ED8, 0x8F4D, 0x5ED9, 0x8F4E, 0x5EDA, 0x8F4F, 0x5EDC, 0x8F50, 0x5EDD, 0x8F51, 0x5EDE, 0x8F52, 0x5EDF, + 0x8F53, 0x5EE0, 0x8F54, 0x5EE1, 0x8F55, 0x5EE2, 0x8F56, 0x5EE3, 0x8F57, 0x5EE4, 0x8F58, 0x5EE5, 0x8F59, 0x5EE6, 0x8F5A, 0x5EE7, + 0x8F5B, 0x5EE9, 0x8F5C, 0x5EEB, 0x8F5D, 0x5EEC, 0x8F5E, 0x5EED, 0x8F5F, 0x5EEE, 0x8F60, 0x5EEF, 0x8F61, 0x5EF0, 0x8F62, 0x5EF1, + 0x8F63, 0x5EF2, 0x8F64, 0x5EF3, 0x8F65, 0x5EF5, 0x8F66, 0x5EF8, 0x8F67, 0x5EF9, 0x8F68, 0x5EFB, 0x8F69, 0x5EFC, 0x8F6A, 0x5EFD, + 0x8F6B, 0x5F05, 0x8F6C, 0x5F06, 0x8F6D, 0x5F07, 0x8F6E, 0x5F09, 0x8F6F, 0x5F0C, 0x8F70, 0x5F0D, 0x8F71, 0x5F0E, 0x8F72, 0x5F10, + 0x8F73, 0x5F12, 0x8F74, 0x5F14, 0x8F75, 0x5F16, 0x8F76, 0x5F19, 0x8F77, 0x5F1A, 0x8F78, 0x5F1C, 0x8F79, 0x5F1D, 0x8F7A, 0x5F1E, + 0x8F7B, 0x5F21, 0x8F7C, 0x5F22, 0x8F7D, 0x5F23, 0x8F7E, 0x5F24, 0x8F80, 0x5F28, 0x8F81, 0x5F2B, 0x8F82, 0x5F2C, 0x8F83, 0x5F2E, + 0x8F84, 0x5F30, 0x8F85, 0x5F32, 0x8F86, 0x5F33, 0x8F87, 0x5F34, 0x8F88, 0x5F35, 0x8F89, 0x5F36, 0x8F8A, 0x5F37, 0x8F8B, 0x5F38, + 0x8F8C, 0x5F3B, 0x8F8D, 0x5F3D, 0x8F8E, 0x5F3E, 0x8F8F, 0x5F3F, 0x8F90, 0x5F41, 0x8F91, 0x5F42, 0x8F92, 0x5F43, 0x8F93, 0x5F44, + 0x8F94, 0x5F45, 0x8F95, 0x5F46, 0x8F96, 0x5F47, 0x8F97, 0x5F48, 0x8F98, 0x5F49, 0x8F99, 0x5F4A, 0x8F9A, 0x5F4B, 0x8F9B, 0x5F4C, + 0x8F9C, 0x5F4D, 0x8F9D, 0x5F4E, 0x8F9E, 0x5F4F, 0x8F9F, 0x5F51, 0x8FA0, 0x5F54, 0x8FA1, 0x5F59, 0x8FA2, 0x5F5A, 0x8FA3, 0x5F5B, + 0x8FA4, 0x5F5C, 0x8FA5, 0x5F5E, 0x8FA6, 0x5F5F, 0x8FA7, 0x5F60, 0x8FA8, 0x5F63, 0x8FA9, 0x5F65, 0x8FAA, 0x5F67, 0x8FAB, 0x5F68, + 0x8FAC, 0x5F6B, 0x8FAD, 0x5F6E, 0x8FAE, 0x5F6F, 0x8FAF, 0x5F72, 0x8FB0, 0x5F74, 0x8FB1, 0x5F75, 0x8FB2, 0x5F76, 0x8FB3, 0x5F78, + 0x8FB4, 0x5F7A, 0x8FB5, 0x5F7D, 0x8FB6, 0x5F7E, 0x8FB7, 0x5F7F, 0x8FB8, 0x5F83, 0x8FB9, 0x5F86, 0x8FBA, 0x5F8D, 0x8FBB, 0x5F8E, + 0x8FBC, 0x5F8F, 0x8FBD, 0x5F91, 0x8FBE, 0x5F93, 0x8FBF, 0x5F94, 0x8FC0, 0x5F96, 0x8FC1, 0x5F9A, 0x8FC2, 0x5F9B, 0x8FC3, 0x5F9D, + 0x8FC4, 0x5F9E, 0x8FC5, 0x5F9F, 0x8FC6, 0x5FA0, 0x8FC7, 0x5FA2, 0x8FC8, 0x5FA3, 0x8FC9, 0x5FA4, 0x8FCA, 0x5FA5, 0x8FCB, 0x5FA6, + 0x8FCC, 0x5FA7, 0x8FCD, 0x5FA9, 0x8FCE, 0x5FAB, 0x8FCF, 0x5FAC, 0x8FD0, 0x5FAF, 0x8FD1, 0x5FB0, 0x8FD2, 0x5FB1, 0x8FD3, 0x5FB2, + 0x8FD4, 0x5FB3, 0x8FD5, 0x5FB4, 0x8FD6, 0x5FB6, 0x8FD7, 0x5FB8, 0x8FD8, 0x5FB9, 0x8FD9, 0x5FBA, 0x8FDA, 0x5FBB, 0x8FDB, 0x5FBE, + 0x8FDC, 0x5FBF, 0x8FDD, 0x5FC0, 0x8FDE, 0x5FC1, 0x8FDF, 0x5FC2, 0x8FE0, 0x5FC7, 0x8FE1, 0x5FC8, 0x8FE2, 0x5FCA, 0x8FE3, 0x5FCB, + 0x8FE4, 0x5FCE, 0x8FE5, 0x5FD3, 0x8FE6, 0x5FD4, 0x8FE7, 0x5FD5, 0x8FE8, 0x5FDA, 0x8FE9, 0x5FDB, 0x8FEA, 0x5FDC, 0x8FEB, 0x5FDE, + 0x8FEC, 0x5FDF, 0x8FED, 0x5FE2, 0x8FEE, 0x5FE3, 0x8FEF, 0x5FE5, 0x8FF0, 0x5FE6, 0x8FF1, 0x5FE8, 0x8FF2, 0x5FE9, 0x8FF3, 0x5FEC, + 0x8FF4, 0x5FEF, 0x8FF5, 0x5FF0, 0x8FF6, 0x5FF2, 0x8FF7, 0x5FF3, 0x8FF8, 0x5FF4, 0x8FF9, 0x5FF6, 0x8FFA, 0x5FF7, 0x8FFB, 0x5FF9, + 0x8FFC, 0x5FFA, 0x8FFD, 0x5FFC, 0x8FFE, 0x6007, 0x9040, 0x6008, 0x9041, 0x6009, 0x9042, 0x600B, 0x9043, 0x600C, 0x9044, 0x6010, + 0x9045, 0x6011, 0x9046, 0x6013, 0x9047, 0x6017, 0x9048, 0x6018, 0x9049, 0x601A, 0x904A, 0x601E, 0x904B, 0x601F, 0x904C, 0x6022, + 0x904D, 0x6023, 0x904E, 0x6024, 0x904F, 0x602C, 0x9050, 0x602D, 0x9051, 0x602E, 0x9052, 0x6030, 0x9053, 0x6031, 0x9054, 0x6032, + 0x9055, 0x6033, 0x9056, 0x6034, 0x9057, 0x6036, 0x9058, 0x6037, 0x9059, 0x6038, 0x905A, 0x6039, 0x905B, 0x603A, 0x905C, 0x603D, + 0x905D, 0x603E, 0x905E, 0x6040, 0x905F, 0x6044, 0x9060, 0x6045, 0x9061, 0x6046, 0x9062, 0x6047, 0x9063, 0x6048, 0x9064, 0x6049, + 0x9065, 0x604A, 0x9066, 0x604C, 0x9067, 0x604E, 0x9068, 0x604F, 0x9069, 0x6051, 0x906A, 0x6053, 0x906B, 0x6054, 0x906C, 0x6056, + 0x906D, 0x6057, 0x906E, 0x6058, 0x906F, 0x605B, 0x9070, 0x605C, 0x9071, 0x605E, 0x9072, 0x605F, 0x9073, 0x6060, 0x9074, 0x6061, + 0x9075, 0x6065, 0x9076, 0x6066, 0x9077, 0x606E, 0x9078, 0x6071, 0x9079, 0x6072, 0x907A, 0x6074, 0x907B, 0x6075, 0x907C, 0x6077, + 0x907D, 0x607E, 0x907E, 0x6080, 0x9080, 0x6081, 0x9081, 0x6082, 0x9082, 0x6085, 0x9083, 0x6086, 0x9084, 0x6087, 0x9085, 0x6088, + 0x9086, 0x608A, 0x9087, 0x608B, 0x9088, 0x608E, 0x9089, 0x608F, 0x908A, 0x6090, 0x908B, 0x6091, 0x908C, 0x6093, 0x908D, 0x6095, + 0x908E, 0x6097, 0x908F, 0x6098, 0x9090, 0x6099, 0x9091, 0x609C, 0x9092, 0x609E, 0x9093, 0x60A1, 0x9094, 0x60A2, 0x9095, 0x60A4, + 0x9096, 0x60A5, 0x9097, 0x60A7, 0x9098, 0x60A9, 0x9099, 0x60AA, 0x909A, 0x60AE, 0x909B, 0x60B0, 0x909C, 0x60B3, 0x909D, 0x60B5, + 0x909E, 0x60B6, 0x909F, 0x60B7, 0x90A0, 0x60B9, 0x90A1, 0x60BA, 0x90A2, 0x60BD, 0x90A3, 0x60BE, 0x90A4, 0x60BF, 0x90A5, 0x60C0, + 0x90A6, 0x60C1, 0x90A7, 0x60C2, 0x90A8, 0x60C3, 0x90A9, 0x60C4, 0x90AA, 0x60C7, 0x90AB, 0x60C8, 0x90AC, 0x60C9, 0x90AD, 0x60CC, + 0x90AE, 0x60CD, 0x90AF, 0x60CE, 0x90B0, 0x60CF, 0x90B1, 0x60D0, 0x90B2, 0x60D2, 0x90B3, 0x60D3, 0x90B4, 0x60D4, 0x90B5, 0x60D6, + 0x90B6, 0x60D7, 0x90B7, 0x60D9, 0x90B8, 0x60DB, 0x90B9, 0x60DE, 0x90BA, 0x60E1, 0x90BB, 0x60E2, 0x90BC, 0x60E3, 0x90BD, 0x60E4, + 0x90BE, 0x60E5, 0x90BF, 0x60EA, 0x90C0, 0x60F1, 0x90C1, 0x60F2, 0x90C2, 0x60F5, 0x90C3, 0x60F7, 0x90C4, 0x60F8, 0x90C5, 0x60FB, + 0x90C6, 0x60FC, 0x90C7, 0x60FD, 0x90C8, 0x60FE, 0x90C9, 0x60FF, 0x90CA, 0x6102, 0x90CB, 0x6103, 0x90CC, 0x6104, 0x90CD, 0x6105, + 0x90CE, 0x6107, 0x90CF, 0x610A, 0x90D0, 0x610B, 0x90D1, 0x610C, 0x90D2, 0x6110, 0x90D3, 0x6111, 0x90D4, 0x6112, 0x90D5, 0x6113, + 0x90D6, 0x6114, 0x90D7, 0x6116, 0x90D8, 0x6117, 0x90D9, 0x6118, 0x90DA, 0x6119, 0x90DB, 0x611B, 0x90DC, 0x611C, 0x90DD, 0x611D, + 0x90DE, 0x611E, 0x90DF, 0x6121, 0x90E0, 0x6122, 0x90E1, 0x6125, 0x90E2, 0x6128, 0x90E3, 0x6129, 0x90E4, 0x612A, 0x90E5, 0x612C, + 0x90E6, 0x612D, 0x90E7, 0x612E, 0x90E8, 0x612F, 0x90E9, 0x6130, 0x90EA, 0x6131, 0x90EB, 0x6132, 0x90EC, 0x6133, 0x90ED, 0x6134, + 0x90EE, 0x6135, 0x90EF, 0x6136, 0x90F0, 0x6137, 0x90F1, 0x6138, 0x90F2, 0x6139, 0x90F3, 0x613A, 0x90F4, 0x613B, 0x90F5, 0x613C, + 0x90F6, 0x613D, 0x90F7, 0x613E, 0x90F8, 0x6140, 0x90F9, 0x6141, 0x90FA, 0x6142, 0x90FB, 0x6143, 0x90FC, 0x6144, 0x90FD, 0x6145, + 0x90FE, 0x6146, 0x9140, 0x6147, 0x9141, 0x6149, 0x9142, 0x614B, 0x9143, 0x614D, 0x9144, 0x614F, 0x9145, 0x6150, 0x9146, 0x6152, + 0x9147, 0x6153, 0x9148, 0x6154, 0x9149, 0x6156, 0x914A, 0x6157, 0x914B, 0x6158, 0x914C, 0x6159, 0x914D, 0x615A, 0x914E, 0x615B, + 0x914F, 0x615C, 0x9150, 0x615E, 0x9151, 0x615F, 0x9152, 0x6160, 0x9153, 0x6161, 0x9154, 0x6163, 0x9155, 0x6164, 0x9156, 0x6165, + 0x9157, 0x6166, 0x9158, 0x6169, 0x9159, 0x616A, 0x915A, 0x616B, 0x915B, 0x616C, 0x915C, 0x616D, 0x915D, 0x616E, 0x915E, 0x616F, + 0x915F, 0x6171, 0x9160, 0x6172, 0x9161, 0x6173, 0x9162, 0x6174, 0x9163, 0x6176, 0x9164, 0x6178, 0x9165, 0x6179, 0x9166, 0x617A, + 0x9167, 0x617B, 0x9168, 0x617C, 0x9169, 0x617D, 0x916A, 0x617E, 0x916B, 0x617F, 0x916C, 0x6180, 0x916D, 0x6181, 0x916E, 0x6182, + 0x916F, 0x6183, 0x9170, 0x6184, 0x9171, 0x6185, 0x9172, 0x6186, 0x9173, 0x6187, 0x9174, 0x6188, 0x9175, 0x6189, 0x9176, 0x618A, + 0x9177, 0x618C, 0x9178, 0x618D, 0x9179, 0x618F, 0x917A, 0x6190, 0x917B, 0x6191, 0x917C, 0x6192, 0x917D, 0x6193, 0x917E, 0x6195, + 0x9180, 0x6196, 0x9181, 0x6197, 0x9182, 0x6198, 0x9183, 0x6199, 0x9184, 0x619A, 0x9185, 0x619B, 0x9186, 0x619C, 0x9187, 0x619E, + 0x9188, 0x619F, 0x9189, 0x61A0, 0x918A, 0x61A1, 0x918B, 0x61A2, 0x918C, 0x61A3, 0x918D, 0x61A4, 0x918E, 0x61A5, 0x918F, 0x61A6, + 0x9190, 0x61AA, 0x9191, 0x61AB, 0x9192, 0x61AD, 0x9193, 0x61AE, 0x9194, 0x61AF, 0x9195, 0x61B0, 0x9196, 0x61B1, 0x9197, 0x61B2, + 0x9198, 0x61B3, 0x9199, 0x61B4, 0x919A, 0x61B5, 0x919B, 0x61B6, 0x919C, 0x61B8, 0x919D, 0x61B9, 0x919E, 0x61BA, 0x919F, 0x61BB, + 0x91A0, 0x61BC, 0x91A1, 0x61BD, 0x91A2, 0x61BF, 0x91A3, 0x61C0, 0x91A4, 0x61C1, 0x91A5, 0x61C3, 0x91A6, 0x61C4, 0x91A7, 0x61C5, + 0x91A8, 0x61C6, 0x91A9, 0x61C7, 0x91AA, 0x61C9, 0x91AB, 0x61CC, 0x91AC, 0x61CD, 0x91AD, 0x61CE, 0x91AE, 0x61CF, 0x91AF, 0x61D0, + 0x91B0, 0x61D3, 0x91B1, 0x61D5, 0x91B2, 0x61D6, 0x91B3, 0x61D7, 0x91B4, 0x61D8, 0x91B5, 0x61D9, 0x91B6, 0x61DA, 0x91B7, 0x61DB, + 0x91B8, 0x61DC, 0x91B9, 0x61DD, 0x91BA, 0x61DE, 0x91BB, 0x61DF, 0x91BC, 0x61E0, 0x91BD, 0x61E1, 0x91BE, 0x61E2, 0x91BF, 0x61E3, + 0x91C0, 0x61E4, 0x91C1, 0x61E5, 0x91C2, 0x61E7, 0x91C3, 0x61E8, 0x91C4, 0x61E9, 0x91C5, 0x61EA, 0x91C6, 0x61EB, 0x91C7, 0x61EC, + 0x91C8, 0x61ED, 0x91C9, 0x61EE, 0x91CA, 0x61EF, 0x91CB, 0x61F0, 0x91CC, 0x61F1, 0x91CD, 0x61F2, 0x91CE, 0x61F3, 0x91CF, 0x61F4, + 0x91D0, 0x61F6, 0x91D1, 0x61F7, 0x91D2, 0x61F8, 0x91D3, 0x61F9, 0x91D4, 0x61FA, 0x91D5, 0x61FB, 0x91D6, 0x61FC, 0x91D7, 0x61FD, + 0x91D8, 0x61FE, 0x91D9, 0x6200, 0x91DA, 0x6201, 0x91DB, 0x6202, 0x91DC, 0x6203, 0x91DD, 0x6204, 0x91DE, 0x6205, 0x91DF, 0x6207, + 0x91E0, 0x6209, 0x91E1, 0x6213, 0x91E2, 0x6214, 0x91E3, 0x6219, 0x91E4, 0x621C, 0x91E5, 0x621D, 0x91E6, 0x621E, 0x91E7, 0x6220, + 0x91E8, 0x6223, 0x91E9, 0x6226, 0x91EA, 0x6227, 0x91EB, 0x6228, 0x91EC, 0x6229, 0x91ED, 0x622B, 0x91EE, 0x622D, 0x91EF, 0x622F, + 0x91F0, 0x6230, 0x91F1, 0x6231, 0x91F2, 0x6232, 0x91F3, 0x6235, 0x91F4, 0x6236, 0x91F5, 0x6238, 0x91F6, 0x6239, 0x91F7, 0x623A, + 0x91F8, 0x623B, 0x91F9, 0x623C, 0x91FA, 0x6242, 0x91FB, 0x6244, 0x91FC, 0x6245, 0x91FD, 0x6246, 0x91FE, 0x624A, 0x9240, 0x624F, + 0x9241, 0x6250, 0x9242, 0x6255, 0x9243, 0x6256, 0x9244, 0x6257, 0x9245, 0x6259, 0x9246, 0x625A, 0x9247, 0x625C, 0x9248, 0x625D, + 0x9249, 0x625E, 0x924A, 0x625F, 0x924B, 0x6260, 0x924C, 0x6261, 0x924D, 0x6262, 0x924E, 0x6264, 0x924F, 0x6265, 0x9250, 0x6268, + 0x9251, 0x6271, 0x9252, 0x6272, 0x9253, 0x6274, 0x9254, 0x6275, 0x9255, 0x6277, 0x9256, 0x6278, 0x9257, 0x627A, 0x9258, 0x627B, + 0x9259, 0x627D, 0x925A, 0x6281, 0x925B, 0x6282, 0x925C, 0x6283, 0x925D, 0x6285, 0x925E, 0x6286, 0x925F, 0x6287, 0x9260, 0x6288, + 0x9261, 0x628B, 0x9262, 0x628C, 0x9263, 0x628D, 0x9264, 0x628E, 0x9265, 0x628F, 0x9266, 0x6290, 0x9267, 0x6294, 0x9268, 0x6299, + 0x9269, 0x629C, 0x926A, 0x629D, 0x926B, 0x629E, 0x926C, 0x62A3, 0x926D, 0x62A6, 0x926E, 0x62A7, 0x926F, 0x62A9, 0x9270, 0x62AA, + 0x9271, 0x62AD, 0x9272, 0x62AE, 0x9273, 0x62AF, 0x9274, 0x62B0, 0x9275, 0x62B2, 0x9276, 0x62B3, 0x9277, 0x62B4, 0x9278, 0x62B6, + 0x9279, 0x62B7, 0x927A, 0x62B8, 0x927B, 0x62BA, 0x927C, 0x62BE, 0x927D, 0x62C0, 0x927E, 0x62C1, 0x9280, 0x62C3, 0x9281, 0x62CB, + 0x9282, 0x62CF, 0x9283, 0x62D1, 0x9284, 0x62D5, 0x9285, 0x62DD, 0x9286, 0x62DE, 0x9287, 0x62E0, 0x9288, 0x62E1, 0x9289, 0x62E4, + 0x928A, 0x62EA, 0x928B, 0x62EB, 0x928C, 0x62F0, 0x928D, 0x62F2, 0x928E, 0x62F5, 0x928F, 0x62F8, 0x9290, 0x62F9, 0x9291, 0x62FA, + 0x9292, 0x62FB, 0x9293, 0x6300, 0x9294, 0x6303, 0x9295, 0x6304, 0x9296, 0x6305, 0x9297, 0x6306, 0x9298, 0x630A, 0x9299, 0x630B, + 0x929A, 0x630C, 0x929B, 0x630D, 0x929C, 0x630F, 0x929D, 0x6310, 0x929E, 0x6312, 0x929F, 0x6313, 0x92A0, 0x6314, 0x92A1, 0x6315, + 0x92A2, 0x6317, 0x92A3, 0x6318, 0x92A4, 0x6319, 0x92A5, 0x631C, 0x92A6, 0x6326, 0x92A7, 0x6327, 0x92A8, 0x6329, 0x92A9, 0x632C, + 0x92AA, 0x632D, 0x92AB, 0x632E, 0x92AC, 0x6330, 0x92AD, 0x6331, 0x92AE, 0x6333, 0x92AF, 0x6334, 0x92B0, 0x6335, 0x92B1, 0x6336, + 0x92B2, 0x6337, 0x92B3, 0x6338, 0x92B4, 0x633B, 0x92B5, 0x633C, 0x92B6, 0x633E, 0x92B7, 0x633F, 0x92B8, 0x6340, 0x92B9, 0x6341, + 0x92BA, 0x6344, 0x92BB, 0x6347, 0x92BC, 0x6348, 0x92BD, 0x634A, 0x92BE, 0x6351, 0x92BF, 0x6352, 0x92C0, 0x6353, 0x92C1, 0x6354, + 0x92C2, 0x6356, 0x92C3, 0x6357, 0x92C4, 0x6358, 0x92C5, 0x6359, 0x92C6, 0x635A, 0x92C7, 0x635B, 0x92C8, 0x635C, 0x92C9, 0x635D, + 0x92CA, 0x6360, 0x92CB, 0x6364, 0x92CC, 0x6365, 0x92CD, 0x6366, 0x92CE, 0x6368, 0x92CF, 0x636A, 0x92D0, 0x636B, 0x92D1, 0x636C, + 0x92D2, 0x636F, 0x92D3, 0x6370, 0x92D4, 0x6372, 0x92D5, 0x6373, 0x92D6, 0x6374, 0x92D7, 0x6375, 0x92D8, 0x6378, 0x92D9, 0x6379, + 0x92DA, 0x637C, 0x92DB, 0x637D, 0x92DC, 0x637E, 0x92DD, 0x637F, 0x92DE, 0x6381, 0x92DF, 0x6383, 0x92E0, 0x6384, 0x92E1, 0x6385, + 0x92E2, 0x6386, 0x92E3, 0x638B, 0x92E4, 0x638D, 0x92E5, 0x6391, 0x92E6, 0x6393, 0x92E7, 0x6394, 0x92E8, 0x6395, 0x92E9, 0x6397, + 0x92EA, 0x6399, 0x92EB, 0x639A, 0x92EC, 0x639B, 0x92ED, 0x639C, 0x92EE, 0x639D, 0x92EF, 0x639E, 0x92F0, 0x639F, 0x92F1, 0x63A1, + 0x92F2, 0x63A4, 0x92F3, 0x63A6, 0x92F4, 0x63AB, 0x92F5, 0x63AF, 0x92F6, 0x63B1, 0x92F7, 0x63B2, 0x92F8, 0x63B5, 0x92F9, 0x63B6, + 0x92FA, 0x63B9, 0x92FB, 0x63BB, 0x92FC, 0x63BD, 0x92FD, 0x63BF, 0x92FE, 0x63C0, 0x9340, 0x63C1, 0x9341, 0x63C2, 0x9342, 0x63C3, + 0x9343, 0x63C5, 0x9344, 0x63C7, 0x9345, 0x63C8, 0x9346, 0x63CA, 0x9347, 0x63CB, 0x9348, 0x63CC, 0x9349, 0x63D1, 0x934A, 0x63D3, + 0x934B, 0x63D4, 0x934C, 0x63D5, 0x934D, 0x63D7, 0x934E, 0x63D8, 0x934F, 0x63D9, 0x9350, 0x63DA, 0x9351, 0x63DB, 0x9352, 0x63DC, + 0x9353, 0x63DD, 0x9354, 0x63DF, 0x9355, 0x63E2, 0x9356, 0x63E4, 0x9357, 0x63E5, 0x9358, 0x63E6, 0x9359, 0x63E7, 0x935A, 0x63E8, + 0x935B, 0x63EB, 0x935C, 0x63EC, 0x935D, 0x63EE, 0x935E, 0x63EF, 0x935F, 0x63F0, 0x9360, 0x63F1, 0x9361, 0x63F3, 0x9362, 0x63F5, + 0x9363, 0x63F7, 0x9364, 0x63F9, 0x9365, 0x63FA, 0x9366, 0x63FB, 0x9367, 0x63FC, 0x9368, 0x63FE, 0x9369, 0x6403, 0x936A, 0x6404, + 0x936B, 0x6406, 0x936C, 0x6407, 0x936D, 0x6408, 0x936E, 0x6409, 0x936F, 0x640A, 0x9370, 0x640D, 0x9371, 0x640E, 0x9372, 0x6411, + 0x9373, 0x6412, 0x9374, 0x6415, 0x9375, 0x6416, 0x9376, 0x6417, 0x9377, 0x6418, 0x9378, 0x6419, 0x9379, 0x641A, 0x937A, 0x641D, + 0x937B, 0x641F, 0x937C, 0x6422, 0x937D, 0x6423, 0x937E, 0x6424, 0x9380, 0x6425, 0x9381, 0x6427, 0x9382, 0x6428, 0x9383, 0x6429, + 0x9384, 0x642B, 0x9385, 0x642E, 0x9386, 0x642F, 0x9387, 0x6430, 0x9388, 0x6431, 0x9389, 0x6432, 0x938A, 0x6433, 0x938B, 0x6435, + 0x938C, 0x6436, 0x938D, 0x6437, 0x938E, 0x6438, 0x938F, 0x6439, 0x9390, 0x643B, 0x9391, 0x643C, 0x9392, 0x643E, 0x9393, 0x6440, + 0x9394, 0x6442, 0x9395, 0x6443, 0x9396, 0x6449, 0x9397, 0x644B, 0x9398, 0x644C, 0x9399, 0x644D, 0x939A, 0x644E, 0x939B, 0x644F, + 0x939C, 0x6450, 0x939D, 0x6451, 0x939E, 0x6453, 0x939F, 0x6455, 0x93A0, 0x6456, 0x93A1, 0x6457, 0x93A2, 0x6459, 0x93A3, 0x645A, + 0x93A4, 0x645B, 0x93A5, 0x645C, 0x93A6, 0x645D, 0x93A7, 0x645F, 0x93A8, 0x6460, 0x93A9, 0x6461, 0x93AA, 0x6462, 0x93AB, 0x6463, + 0x93AC, 0x6464, 0x93AD, 0x6465, 0x93AE, 0x6466, 0x93AF, 0x6468, 0x93B0, 0x646A, 0x93B1, 0x646B, 0x93B2, 0x646C, 0x93B3, 0x646E, + 0x93B4, 0x646F, 0x93B5, 0x6470, 0x93B6, 0x6471, 0x93B7, 0x6472, 0x93B8, 0x6473, 0x93B9, 0x6474, 0x93BA, 0x6475, 0x93BB, 0x6476, + 0x93BC, 0x6477, 0x93BD, 0x647B, 0x93BE, 0x647C, 0x93BF, 0x647D, 0x93C0, 0x647E, 0x93C1, 0x647F, 0x93C2, 0x6480, 0x93C3, 0x6481, + 0x93C4, 0x6483, 0x93C5, 0x6486, 0x93C6, 0x6488, 0x93C7, 0x6489, 0x93C8, 0x648A, 0x93C9, 0x648B, 0x93CA, 0x648C, 0x93CB, 0x648D, + 0x93CC, 0x648E, 0x93CD, 0x648F, 0x93CE, 0x6490, 0x93CF, 0x6493, 0x93D0, 0x6494, 0x93D1, 0x6497, 0x93D2, 0x6498, 0x93D3, 0x649A, + 0x93D4, 0x649B, 0x93D5, 0x649C, 0x93D6, 0x649D, 0x93D7, 0x649F, 0x93D8, 0x64A0, 0x93D9, 0x64A1, 0x93DA, 0x64A2, 0x93DB, 0x64A3, + 0x93DC, 0x64A5, 0x93DD, 0x64A6, 0x93DE, 0x64A7, 0x93DF, 0x64A8, 0x93E0, 0x64AA, 0x93E1, 0x64AB, 0x93E2, 0x64AF, 0x93E3, 0x64B1, + 0x93E4, 0x64B2, 0x93E5, 0x64B3, 0x93E6, 0x64B4, 0x93E7, 0x64B6, 0x93E8, 0x64B9, 0x93E9, 0x64BB, 0x93EA, 0x64BD, 0x93EB, 0x64BE, + 0x93EC, 0x64BF, 0x93ED, 0x64C1, 0x93EE, 0x64C3, 0x93EF, 0x64C4, 0x93F0, 0x64C6, 0x93F1, 0x64C7, 0x93F2, 0x64C8, 0x93F3, 0x64C9, + 0x93F4, 0x64CA, 0x93F5, 0x64CB, 0x93F6, 0x64CC, 0x93F7, 0x64CF, 0x93F8, 0x64D1, 0x93F9, 0x64D3, 0x93FA, 0x64D4, 0x93FB, 0x64D5, + 0x93FC, 0x64D6, 0x93FD, 0x64D9, 0x93FE, 0x64DA, 0x9440, 0x64DB, 0x9441, 0x64DC, 0x9442, 0x64DD, 0x9443, 0x64DF, 0x9444, 0x64E0, + 0x9445, 0x64E1, 0x9446, 0x64E3, 0x9447, 0x64E5, 0x9448, 0x64E7, 0x9449, 0x64E8, 0x944A, 0x64E9, 0x944B, 0x64EA, 0x944C, 0x64EB, + 0x944D, 0x64EC, 0x944E, 0x64ED, 0x944F, 0x64EE, 0x9450, 0x64EF, 0x9451, 0x64F0, 0x9452, 0x64F1, 0x9453, 0x64F2, 0x9454, 0x64F3, + 0x9455, 0x64F4, 0x9456, 0x64F5, 0x9457, 0x64F6, 0x9458, 0x64F7, 0x9459, 0x64F8, 0x945A, 0x64F9, 0x945B, 0x64FA, 0x945C, 0x64FB, + 0x945D, 0x64FC, 0x945E, 0x64FD, 0x945F, 0x64FE, 0x9460, 0x64FF, 0x9461, 0x6501, 0x9462, 0x6502, 0x9463, 0x6503, 0x9464, 0x6504, + 0x9465, 0x6505, 0x9466, 0x6506, 0x9467, 0x6507, 0x9468, 0x6508, 0x9469, 0x650A, 0x946A, 0x650B, 0x946B, 0x650C, 0x946C, 0x650D, + 0x946D, 0x650E, 0x946E, 0x650F, 0x946F, 0x6510, 0x9470, 0x6511, 0x9471, 0x6513, 0x9472, 0x6514, 0x9473, 0x6515, 0x9474, 0x6516, + 0x9475, 0x6517, 0x9476, 0x6519, 0x9477, 0x651A, 0x9478, 0x651B, 0x9479, 0x651C, 0x947A, 0x651D, 0x947B, 0x651E, 0x947C, 0x651F, + 0x947D, 0x6520, 0x947E, 0x6521, 0x9480, 0x6522, 0x9481, 0x6523, 0x9482, 0x6524, 0x9483, 0x6526, 0x9484, 0x6527, 0x9485, 0x6528, + 0x9486, 0x6529, 0x9487, 0x652A, 0x9488, 0x652C, 0x9489, 0x652D, 0x948A, 0x6530, 0x948B, 0x6531, 0x948C, 0x6532, 0x948D, 0x6533, + 0x948E, 0x6537, 0x948F, 0x653A, 0x9490, 0x653C, 0x9491, 0x653D, 0x9492, 0x6540, 0x9493, 0x6541, 0x9494, 0x6542, 0x9495, 0x6543, + 0x9496, 0x6544, 0x9497, 0x6546, 0x9498, 0x6547, 0x9499, 0x654A, 0x949A, 0x654B, 0x949B, 0x654D, 0x949C, 0x654E, 0x949D, 0x6550, + 0x949E, 0x6552, 0x949F, 0x6553, 0x94A0, 0x6554, 0x94A1, 0x6557, 0x94A2, 0x6558, 0x94A3, 0x655A, 0x94A4, 0x655C, 0x94A5, 0x655F, + 0x94A6, 0x6560, 0x94A7, 0x6561, 0x94A8, 0x6564, 0x94A9, 0x6565, 0x94AA, 0x6567, 0x94AB, 0x6568, 0x94AC, 0x6569, 0x94AD, 0x656A, + 0x94AE, 0x656D, 0x94AF, 0x656E, 0x94B0, 0x656F, 0x94B1, 0x6571, 0x94B2, 0x6573, 0x94B3, 0x6575, 0x94B4, 0x6576, 0x94B5, 0x6578, + 0x94B6, 0x6579, 0x94B7, 0x657A, 0x94B8, 0x657B, 0x94B9, 0x657C, 0x94BA, 0x657D, 0x94BB, 0x657E, 0x94BC, 0x657F, 0x94BD, 0x6580, + 0x94BE, 0x6581, 0x94BF, 0x6582, 0x94C0, 0x6583, 0x94C1, 0x6584, 0x94C2, 0x6585, 0x94C3, 0x6586, 0x94C4, 0x6588, 0x94C5, 0x6589, + 0x94C6, 0x658A, 0x94C7, 0x658D, 0x94C8, 0x658E, 0x94C9, 0x658F, 0x94CA, 0x6592, 0x94CB, 0x6594, 0x94CC, 0x6595, 0x94CD, 0x6596, + 0x94CE, 0x6598, 0x94CF, 0x659A, 0x94D0, 0x659D, 0x94D1, 0x659E, 0x94D2, 0x65A0, 0x94D3, 0x65A2, 0x94D4, 0x65A3, 0x94D5, 0x65A6, + 0x94D6, 0x65A8, 0x94D7, 0x65AA, 0x94D8, 0x65AC, 0x94D9, 0x65AE, 0x94DA, 0x65B1, 0x94DB, 0x65B2, 0x94DC, 0x65B3, 0x94DD, 0x65B4, + 0x94DE, 0x65B5, 0x94DF, 0x65B6, 0x94E0, 0x65B7, 0x94E1, 0x65B8, 0x94E2, 0x65BA, 0x94E3, 0x65BB, 0x94E4, 0x65BE, 0x94E5, 0x65BF, + 0x94E6, 0x65C0, 0x94E7, 0x65C2, 0x94E8, 0x65C7, 0x94E9, 0x65C8, 0x94EA, 0x65C9, 0x94EB, 0x65CA, 0x94EC, 0x65CD, 0x94ED, 0x65D0, + 0x94EE, 0x65D1, 0x94EF, 0x65D3, 0x94F0, 0x65D4, 0x94F1, 0x65D5, 0x94F2, 0x65D8, 0x94F3, 0x65D9, 0x94F4, 0x65DA, 0x94F5, 0x65DB, + 0x94F6, 0x65DC, 0x94F7, 0x65DD, 0x94F8, 0x65DE, 0x94F9, 0x65DF, 0x94FA, 0x65E1, 0x94FB, 0x65E3, 0x94FC, 0x65E4, 0x94FD, 0x65EA, + 0x94FE, 0x65EB, 0x9540, 0x65F2, 0x9541, 0x65F3, 0x9542, 0x65F4, 0x9543, 0x65F5, 0x9544, 0x65F8, 0x9545, 0x65F9, 0x9546, 0x65FB, + 0x9547, 0x65FC, 0x9548, 0x65FD, 0x9549, 0x65FE, 0x954A, 0x65FF, 0x954B, 0x6601, 0x954C, 0x6604, 0x954D, 0x6605, 0x954E, 0x6607, + 0x954F, 0x6608, 0x9550, 0x6609, 0x9551, 0x660B, 0x9552, 0x660D, 0x9553, 0x6610, 0x9554, 0x6611, 0x9555, 0x6612, 0x9556, 0x6616, + 0x9557, 0x6617, 0x9558, 0x6618, 0x9559, 0x661A, 0x955A, 0x661B, 0x955B, 0x661C, 0x955C, 0x661E, 0x955D, 0x6621, 0x955E, 0x6622, + 0x955F, 0x6623, 0x9560, 0x6624, 0x9561, 0x6626, 0x9562, 0x6629, 0x9563, 0x662A, 0x9564, 0x662B, 0x9565, 0x662C, 0x9566, 0x662E, + 0x9567, 0x6630, 0x9568, 0x6632, 0x9569, 0x6633, 0x956A, 0x6637, 0x956B, 0x6638, 0x956C, 0x6639, 0x956D, 0x663A, 0x956E, 0x663B, + 0x956F, 0x663D, 0x9570, 0x663F, 0x9571, 0x6640, 0x9572, 0x6642, 0x9573, 0x6644, 0x9574, 0x6645, 0x9575, 0x6646, 0x9576, 0x6647, + 0x9577, 0x6648, 0x9578, 0x6649, 0x9579, 0x664A, 0x957A, 0x664D, 0x957B, 0x664E, 0x957C, 0x6650, 0x957D, 0x6651, 0x957E, 0x6658, + 0x9580, 0x6659, 0x9581, 0x665B, 0x9582, 0x665C, 0x9583, 0x665D, 0x9584, 0x665E, 0x9585, 0x6660, 0x9586, 0x6662, 0x9587, 0x6663, + 0x9588, 0x6665, 0x9589, 0x6667, 0x958A, 0x6669, 0x958B, 0x666A, 0x958C, 0x666B, 0x958D, 0x666C, 0x958E, 0x666D, 0x958F, 0x6671, + 0x9590, 0x6672, 0x9591, 0x6673, 0x9592, 0x6675, 0x9593, 0x6678, 0x9594, 0x6679, 0x9595, 0x667B, 0x9596, 0x667C, 0x9597, 0x667D, + 0x9598, 0x667F, 0x9599, 0x6680, 0x959A, 0x6681, 0x959B, 0x6683, 0x959C, 0x6685, 0x959D, 0x6686, 0x959E, 0x6688, 0x959F, 0x6689, + 0x95A0, 0x668A, 0x95A1, 0x668B, 0x95A2, 0x668D, 0x95A3, 0x668E, 0x95A4, 0x668F, 0x95A5, 0x6690, 0x95A6, 0x6692, 0x95A7, 0x6693, + 0x95A8, 0x6694, 0x95A9, 0x6695, 0x95AA, 0x6698, 0x95AB, 0x6699, 0x95AC, 0x669A, 0x95AD, 0x669B, 0x95AE, 0x669C, 0x95AF, 0x669E, + 0x95B0, 0x669F, 0x95B1, 0x66A0, 0x95B2, 0x66A1, 0x95B3, 0x66A2, 0x95B4, 0x66A3, 0x95B5, 0x66A4, 0x95B6, 0x66A5, 0x95B7, 0x66A6, + 0x95B8, 0x66A9, 0x95B9, 0x66AA, 0x95BA, 0x66AB, 0x95BB, 0x66AC, 0x95BC, 0x66AD, 0x95BD, 0x66AF, 0x95BE, 0x66B0, 0x95BF, 0x66B1, + 0x95C0, 0x66B2, 0x95C1, 0x66B3, 0x95C2, 0x66B5, 0x95C3, 0x66B6, 0x95C4, 0x66B7, 0x95C5, 0x66B8, 0x95C6, 0x66BA, 0x95C7, 0x66BB, + 0x95C8, 0x66BC, 0x95C9, 0x66BD, 0x95CA, 0x66BF, 0x95CB, 0x66C0, 0x95CC, 0x66C1, 0x95CD, 0x66C2, 0x95CE, 0x66C3, 0x95CF, 0x66C4, + 0x95D0, 0x66C5, 0x95D1, 0x66C6, 0x95D2, 0x66C7, 0x95D3, 0x66C8, 0x95D4, 0x66C9, 0x95D5, 0x66CA, 0x95D6, 0x66CB, 0x95D7, 0x66CC, + 0x95D8, 0x66CD, 0x95D9, 0x66CE, 0x95DA, 0x66CF, 0x95DB, 0x66D0, 0x95DC, 0x66D1, 0x95DD, 0x66D2, 0x95DE, 0x66D3, 0x95DF, 0x66D4, + 0x95E0, 0x66D5, 0x95E1, 0x66D6, 0x95E2, 0x66D7, 0x95E3, 0x66D8, 0x95E4, 0x66DA, 0x95E5, 0x66DE, 0x95E6, 0x66DF, 0x95E7, 0x66E0, + 0x95E8, 0x66E1, 0x95E9, 0x66E2, 0x95EA, 0x66E3, 0x95EB, 0x66E4, 0x95EC, 0x66E5, 0x95ED, 0x66E7, 0x95EE, 0x66E8, 0x95EF, 0x66EA, + 0x95F0, 0x66EB, 0x95F1, 0x66EC, 0x95F2, 0x66ED, 0x95F3, 0x66EE, 0x95F4, 0x66EF, 0x95F5, 0x66F1, 0x95F6, 0x66F5, 0x95F7, 0x66F6, + 0x95F8, 0x66F8, 0x95F9, 0x66FA, 0x95FA, 0x66FB, 0x95FB, 0x66FD, 0x95FC, 0x6701, 0x95FD, 0x6702, 0x95FE, 0x6703, 0x9640, 0x6704, + 0x9641, 0x6705, 0x9642, 0x6706, 0x9643, 0x6707, 0x9644, 0x670C, 0x9645, 0x670E, 0x9646, 0x670F, 0x9647, 0x6711, 0x9648, 0x6712, + 0x9649, 0x6713, 0x964A, 0x6716, 0x964B, 0x6718, 0x964C, 0x6719, 0x964D, 0x671A, 0x964E, 0x671C, 0x964F, 0x671E, 0x9650, 0x6720, + 0x9651, 0x6721, 0x9652, 0x6722, 0x9653, 0x6723, 0x9654, 0x6724, 0x9655, 0x6725, 0x9656, 0x6727, 0x9657, 0x6729, 0x9658, 0x672E, + 0x9659, 0x6730, 0x965A, 0x6732, 0x965B, 0x6733, 0x965C, 0x6736, 0x965D, 0x6737, 0x965E, 0x6738, 0x965F, 0x6739, 0x9660, 0x673B, + 0x9661, 0x673C, 0x9662, 0x673E, 0x9663, 0x673F, 0x9664, 0x6741, 0x9665, 0x6744, 0x9666, 0x6745, 0x9667, 0x6747, 0x9668, 0x674A, + 0x9669, 0x674B, 0x966A, 0x674D, 0x966B, 0x6752, 0x966C, 0x6754, 0x966D, 0x6755, 0x966E, 0x6757, 0x966F, 0x6758, 0x9670, 0x6759, + 0x9671, 0x675A, 0x9672, 0x675B, 0x9673, 0x675D, 0x9674, 0x6762, 0x9675, 0x6763, 0x9676, 0x6764, 0x9677, 0x6766, 0x9678, 0x6767, + 0x9679, 0x676B, 0x967A, 0x676C, 0x967B, 0x676E, 0x967C, 0x6771, 0x967D, 0x6774, 0x967E, 0x6776, 0x9680, 0x6778, 0x9681, 0x6779, + 0x9682, 0x677A, 0x9683, 0x677B, 0x9684, 0x677D, 0x9685, 0x6780, 0x9686, 0x6782, 0x9687, 0x6783, 0x9688, 0x6785, 0x9689, 0x6786, + 0x968A, 0x6788, 0x968B, 0x678A, 0x968C, 0x678C, 0x968D, 0x678D, 0x968E, 0x678E, 0x968F, 0x678F, 0x9690, 0x6791, 0x9691, 0x6792, + 0x9692, 0x6793, 0x9693, 0x6794, 0x9694, 0x6796, 0x9695, 0x6799, 0x9696, 0x679B, 0x9697, 0x679F, 0x9698, 0x67A0, 0x9699, 0x67A1, + 0x969A, 0x67A4, 0x969B, 0x67A6, 0x969C, 0x67A9, 0x969D, 0x67AC, 0x969E, 0x67AE, 0x969F, 0x67B1, 0x96A0, 0x67B2, 0x96A1, 0x67B4, + 0x96A2, 0x67B9, 0x96A3, 0x67BA, 0x96A4, 0x67BB, 0x96A5, 0x67BC, 0x96A6, 0x67BD, 0x96A7, 0x67BE, 0x96A8, 0x67BF, 0x96A9, 0x67C0, + 0x96AA, 0x67C2, 0x96AB, 0x67C5, 0x96AC, 0x67C6, 0x96AD, 0x67C7, 0x96AE, 0x67C8, 0x96AF, 0x67C9, 0x96B0, 0x67CA, 0x96B1, 0x67CB, + 0x96B2, 0x67CC, 0x96B3, 0x67CD, 0x96B4, 0x67CE, 0x96B5, 0x67D5, 0x96B6, 0x67D6, 0x96B7, 0x67D7, 0x96B8, 0x67DB, 0x96B9, 0x67DF, + 0x96BA, 0x67E1, 0x96BB, 0x67E3, 0x96BC, 0x67E4, 0x96BD, 0x67E6, 0x96BE, 0x67E7, 0x96BF, 0x67E8, 0x96C0, 0x67EA, 0x96C1, 0x67EB, + 0x96C2, 0x67ED, 0x96C3, 0x67EE, 0x96C4, 0x67F2, 0x96C5, 0x67F5, 0x96C6, 0x67F6, 0x96C7, 0x67F7, 0x96C8, 0x67F8, 0x96C9, 0x67F9, + 0x96CA, 0x67FA, 0x96CB, 0x67FB, 0x96CC, 0x67FC, 0x96CD, 0x67FE, 0x96CE, 0x6801, 0x96CF, 0x6802, 0x96D0, 0x6803, 0x96D1, 0x6804, + 0x96D2, 0x6806, 0x96D3, 0x680D, 0x96D4, 0x6810, 0x96D5, 0x6812, 0x96D6, 0x6814, 0x96D7, 0x6815, 0x96D8, 0x6818, 0x96D9, 0x6819, + 0x96DA, 0x681A, 0x96DB, 0x681B, 0x96DC, 0x681C, 0x96DD, 0x681E, 0x96DE, 0x681F, 0x96DF, 0x6820, 0x96E0, 0x6822, 0x96E1, 0x6823, + 0x96E2, 0x6824, 0x96E3, 0x6825, 0x96E4, 0x6826, 0x96E5, 0x6827, 0x96E6, 0x6828, 0x96E7, 0x682B, 0x96E8, 0x682C, 0x96E9, 0x682D, + 0x96EA, 0x682E, 0x96EB, 0x682F, 0x96EC, 0x6830, 0x96ED, 0x6831, 0x96EE, 0x6834, 0x96EF, 0x6835, 0x96F0, 0x6836, 0x96F1, 0x683A, + 0x96F2, 0x683B, 0x96F3, 0x683F, 0x96F4, 0x6847, 0x96F5, 0x684B, 0x96F6, 0x684D, 0x96F7, 0x684F, 0x96F8, 0x6852, 0x96F9, 0x6856, + 0x96FA, 0x6857, 0x96FB, 0x6858, 0x96FC, 0x6859, 0x96FD, 0x685A, 0x96FE, 0x685B, 0x9740, 0x685C, 0x9741, 0x685D, 0x9742, 0x685E, + 0x9743, 0x685F, 0x9744, 0x686A, 0x9745, 0x686C, 0x9746, 0x686D, 0x9747, 0x686E, 0x9748, 0x686F, 0x9749, 0x6870, 0x974A, 0x6871, + 0x974B, 0x6872, 0x974C, 0x6873, 0x974D, 0x6875, 0x974E, 0x6878, 0x974F, 0x6879, 0x9750, 0x687A, 0x9751, 0x687B, 0x9752, 0x687C, + 0x9753, 0x687D, 0x9754, 0x687E, 0x9755, 0x687F, 0x9756, 0x6880, 0x9757, 0x6882, 0x9758, 0x6884, 0x9759, 0x6887, 0x975A, 0x6888, + 0x975B, 0x6889, 0x975C, 0x688A, 0x975D, 0x688B, 0x975E, 0x688C, 0x975F, 0x688D, 0x9760, 0x688E, 0x9761, 0x6890, 0x9762, 0x6891, + 0x9763, 0x6892, 0x9764, 0x6894, 0x9765, 0x6895, 0x9766, 0x6896, 0x9767, 0x6898, 0x9768, 0x6899, 0x9769, 0x689A, 0x976A, 0x689B, + 0x976B, 0x689C, 0x976C, 0x689D, 0x976D, 0x689E, 0x976E, 0x689F, 0x976F, 0x68A0, 0x9770, 0x68A1, 0x9771, 0x68A3, 0x9772, 0x68A4, + 0x9773, 0x68A5, 0x9774, 0x68A9, 0x9775, 0x68AA, 0x9776, 0x68AB, 0x9777, 0x68AC, 0x9778, 0x68AE, 0x9779, 0x68B1, 0x977A, 0x68B2, + 0x977B, 0x68B4, 0x977C, 0x68B6, 0x977D, 0x68B7, 0x977E, 0x68B8, 0x9780, 0x68B9, 0x9781, 0x68BA, 0x9782, 0x68BB, 0x9783, 0x68BC, + 0x9784, 0x68BD, 0x9785, 0x68BE, 0x9786, 0x68BF, 0x9787, 0x68C1, 0x9788, 0x68C3, 0x9789, 0x68C4, 0x978A, 0x68C5, 0x978B, 0x68C6, + 0x978C, 0x68C7, 0x978D, 0x68C8, 0x978E, 0x68CA, 0x978F, 0x68CC, 0x9790, 0x68CE, 0x9791, 0x68CF, 0x9792, 0x68D0, 0x9793, 0x68D1, + 0x9794, 0x68D3, 0x9795, 0x68D4, 0x9796, 0x68D6, 0x9797, 0x68D7, 0x9798, 0x68D9, 0x9799, 0x68DB, 0x979A, 0x68DC, 0x979B, 0x68DD, + 0x979C, 0x68DE, 0x979D, 0x68DF, 0x979E, 0x68E1, 0x979F, 0x68E2, 0x97A0, 0x68E4, 0x97A1, 0x68E5, 0x97A2, 0x68E6, 0x97A3, 0x68E7, + 0x97A4, 0x68E8, 0x97A5, 0x68E9, 0x97A6, 0x68EA, 0x97A7, 0x68EB, 0x97A8, 0x68EC, 0x97A9, 0x68ED, 0x97AA, 0x68EF, 0x97AB, 0x68F2, + 0x97AC, 0x68F3, 0x97AD, 0x68F4, 0x97AE, 0x68F6, 0x97AF, 0x68F7, 0x97B0, 0x68F8, 0x97B1, 0x68FB, 0x97B2, 0x68FD, 0x97B3, 0x68FE, + 0x97B4, 0x68FF, 0x97B5, 0x6900, 0x97B6, 0x6902, 0x97B7, 0x6903, 0x97B8, 0x6904, 0x97B9, 0x6906, 0x97BA, 0x6907, 0x97BB, 0x6908, + 0x97BC, 0x6909, 0x97BD, 0x690A, 0x97BE, 0x690C, 0x97BF, 0x690F, 0x97C0, 0x6911, 0x97C1, 0x6913, 0x97C2, 0x6914, 0x97C3, 0x6915, + 0x97C4, 0x6916, 0x97C5, 0x6917, 0x97C6, 0x6918, 0x97C7, 0x6919, 0x97C8, 0x691A, 0x97C9, 0x691B, 0x97CA, 0x691C, 0x97CB, 0x691D, + 0x97CC, 0x691E, 0x97CD, 0x6921, 0x97CE, 0x6922, 0x97CF, 0x6923, 0x97D0, 0x6925, 0x97D1, 0x6926, 0x97D2, 0x6927, 0x97D3, 0x6928, + 0x97D4, 0x6929, 0x97D5, 0x692A, 0x97D6, 0x692B, 0x97D7, 0x692C, 0x97D8, 0x692E, 0x97D9, 0x692F, 0x97DA, 0x6931, 0x97DB, 0x6932, + 0x97DC, 0x6933, 0x97DD, 0x6935, 0x97DE, 0x6936, 0x97DF, 0x6937, 0x97E0, 0x6938, 0x97E1, 0x693A, 0x97E2, 0x693B, 0x97E3, 0x693C, + 0x97E4, 0x693E, 0x97E5, 0x6940, 0x97E6, 0x6941, 0x97E7, 0x6943, 0x97E8, 0x6944, 0x97E9, 0x6945, 0x97EA, 0x6946, 0x97EB, 0x6947, + 0x97EC, 0x6948, 0x97ED, 0x6949, 0x97EE, 0x694A, 0x97EF, 0x694B, 0x97F0, 0x694C, 0x97F1, 0x694D, 0x97F2, 0x694E, 0x97F3, 0x694F, + 0x97F4, 0x6950, 0x97F5, 0x6951, 0x97F6, 0x6952, 0x97F7, 0x6953, 0x97F8, 0x6955, 0x97F9, 0x6956, 0x97FA, 0x6958, 0x97FB, 0x6959, + 0x97FC, 0x695B, 0x97FD, 0x695C, 0x97FE, 0x695F, 0x9840, 0x6961, 0x9841, 0x6962, 0x9842, 0x6964, 0x9843, 0x6965, 0x9844, 0x6967, + 0x9845, 0x6968, 0x9846, 0x6969, 0x9847, 0x696A, 0x9848, 0x696C, 0x9849, 0x696D, 0x984A, 0x696F, 0x984B, 0x6970, 0x984C, 0x6972, + 0x984D, 0x6973, 0x984E, 0x6974, 0x984F, 0x6975, 0x9850, 0x6976, 0x9851, 0x697A, 0x9852, 0x697B, 0x9853, 0x697D, 0x9854, 0x697E, + 0x9855, 0x697F, 0x9856, 0x6981, 0x9857, 0x6983, 0x9858, 0x6985, 0x9859, 0x698A, 0x985A, 0x698B, 0x985B, 0x698C, 0x985C, 0x698E, + 0x985D, 0x698F, 0x985E, 0x6990, 0x985F, 0x6991, 0x9860, 0x6992, 0x9861, 0x6993, 0x9862, 0x6996, 0x9863, 0x6997, 0x9864, 0x6999, + 0x9865, 0x699A, 0x9866, 0x699D, 0x9867, 0x699E, 0x9868, 0x699F, 0x9869, 0x69A0, 0x986A, 0x69A1, 0x986B, 0x69A2, 0x986C, 0x69A3, + 0x986D, 0x69A4, 0x986E, 0x69A5, 0x986F, 0x69A6, 0x9870, 0x69A9, 0x9871, 0x69AA, 0x9872, 0x69AC, 0x9873, 0x69AE, 0x9874, 0x69AF, + 0x9875, 0x69B0, 0x9876, 0x69B2, 0x9877, 0x69B3, 0x9878, 0x69B5, 0x9879, 0x69B6, 0x987A, 0x69B8, 0x987B, 0x69B9, 0x987C, 0x69BA, + 0x987D, 0x69BC, 0x987E, 0x69BD, 0x9880, 0x69BE, 0x9881, 0x69BF, 0x9882, 0x69C0, 0x9883, 0x69C2, 0x9884, 0x69C3, 0x9885, 0x69C4, + 0x9886, 0x69C5, 0x9887, 0x69C6, 0x9888, 0x69C7, 0x9889, 0x69C8, 0x988A, 0x69C9, 0x988B, 0x69CB, 0x988C, 0x69CD, 0x988D, 0x69CF, + 0x988E, 0x69D1, 0x988F, 0x69D2, 0x9890, 0x69D3, 0x9891, 0x69D5, 0x9892, 0x69D6, 0x9893, 0x69D7, 0x9894, 0x69D8, 0x9895, 0x69D9, + 0x9896, 0x69DA, 0x9897, 0x69DC, 0x9898, 0x69DD, 0x9899, 0x69DE, 0x989A, 0x69E1, 0x989B, 0x69E2, 0x989C, 0x69E3, 0x989D, 0x69E4, + 0x989E, 0x69E5, 0x989F, 0x69E6, 0x98A0, 0x69E7, 0x98A1, 0x69E8, 0x98A2, 0x69E9, 0x98A3, 0x69EA, 0x98A4, 0x69EB, 0x98A5, 0x69EC, + 0x98A6, 0x69EE, 0x98A7, 0x69EF, 0x98A8, 0x69F0, 0x98A9, 0x69F1, 0x98AA, 0x69F3, 0x98AB, 0x69F4, 0x98AC, 0x69F5, 0x98AD, 0x69F6, + 0x98AE, 0x69F7, 0x98AF, 0x69F8, 0x98B0, 0x69F9, 0x98B1, 0x69FA, 0x98B2, 0x69FB, 0x98B3, 0x69FC, 0x98B4, 0x69FE, 0x98B5, 0x6A00, + 0x98B6, 0x6A01, 0x98B7, 0x6A02, 0x98B8, 0x6A03, 0x98B9, 0x6A04, 0x98BA, 0x6A05, 0x98BB, 0x6A06, 0x98BC, 0x6A07, 0x98BD, 0x6A08, + 0x98BE, 0x6A09, 0x98BF, 0x6A0B, 0x98C0, 0x6A0C, 0x98C1, 0x6A0D, 0x98C2, 0x6A0E, 0x98C3, 0x6A0F, 0x98C4, 0x6A10, 0x98C5, 0x6A11, + 0x98C6, 0x6A12, 0x98C7, 0x6A13, 0x98C8, 0x6A14, 0x98C9, 0x6A15, 0x98CA, 0x6A16, 0x98CB, 0x6A19, 0x98CC, 0x6A1A, 0x98CD, 0x6A1B, + 0x98CE, 0x6A1C, 0x98CF, 0x6A1D, 0x98D0, 0x6A1E, 0x98D1, 0x6A20, 0x98D2, 0x6A22, 0x98D3, 0x6A23, 0x98D4, 0x6A24, 0x98D5, 0x6A25, + 0x98D6, 0x6A26, 0x98D7, 0x6A27, 0x98D8, 0x6A29, 0x98D9, 0x6A2B, 0x98DA, 0x6A2C, 0x98DB, 0x6A2D, 0x98DC, 0x6A2E, 0x98DD, 0x6A30, + 0x98DE, 0x6A32, 0x98DF, 0x6A33, 0x98E0, 0x6A34, 0x98E1, 0x6A36, 0x98E2, 0x6A37, 0x98E3, 0x6A38, 0x98E4, 0x6A39, 0x98E5, 0x6A3A, + 0x98E6, 0x6A3B, 0x98E7, 0x6A3C, 0x98E8, 0x6A3F, 0x98E9, 0x6A40, 0x98EA, 0x6A41, 0x98EB, 0x6A42, 0x98EC, 0x6A43, 0x98ED, 0x6A45, + 0x98EE, 0x6A46, 0x98EF, 0x6A48, 0x98F0, 0x6A49, 0x98F1, 0x6A4A, 0x98F2, 0x6A4B, 0x98F3, 0x6A4C, 0x98F4, 0x6A4D, 0x98F5, 0x6A4E, + 0x98F6, 0x6A4F, 0x98F7, 0x6A51, 0x98F8, 0x6A52, 0x98F9, 0x6A53, 0x98FA, 0x6A54, 0x98FB, 0x6A55, 0x98FC, 0x6A56, 0x98FD, 0x6A57, + 0x98FE, 0x6A5A, 0x9940, 0x6A5C, 0x9941, 0x6A5D, 0x9942, 0x6A5E, 0x9943, 0x6A5F, 0x9944, 0x6A60, 0x9945, 0x6A62, 0x9946, 0x6A63, + 0x9947, 0x6A64, 0x9948, 0x6A66, 0x9949, 0x6A67, 0x994A, 0x6A68, 0x994B, 0x6A69, 0x994C, 0x6A6A, 0x994D, 0x6A6B, 0x994E, 0x6A6C, + 0x994F, 0x6A6D, 0x9950, 0x6A6E, 0x9951, 0x6A6F, 0x9952, 0x6A70, 0x9953, 0x6A72, 0x9954, 0x6A73, 0x9955, 0x6A74, 0x9956, 0x6A75, + 0x9957, 0x6A76, 0x9958, 0x6A77, 0x9959, 0x6A78, 0x995A, 0x6A7A, 0x995B, 0x6A7B, 0x995C, 0x6A7D, 0x995D, 0x6A7E, 0x995E, 0x6A7F, + 0x995F, 0x6A81, 0x9960, 0x6A82, 0x9961, 0x6A83, 0x9962, 0x6A85, 0x9963, 0x6A86, 0x9964, 0x6A87, 0x9965, 0x6A88, 0x9966, 0x6A89, + 0x9967, 0x6A8A, 0x9968, 0x6A8B, 0x9969, 0x6A8C, 0x996A, 0x6A8D, 0x996B, 0x6A8F, 0x996C, 0x6A92, 0x996D, 0x6A93, 0x996E, 0x6A94, + 0x996F, 0x6A95, 0x9970, 0x6A96, 0x9971, 0x6A98, 0x9972, 0x6A99, 0x9973, 0x6A9A, 0x9974, 0x6A9B, 0x9975, 0x6A9C, 0x9976, 0x6A9D, + 0x9977, 0x6A9E, 0x9978, 0x6A9F, 0x9979, 0x6AA1, 0x997A, 0x6AA2, 0x997B, 0x6AA3, 0x997C, 0x6AA4, 0x997D, 0x6AA5, 0x997E, 0x6AA6, + 0x9980, 0x6AA7, 0x9981, 0x6AA8, 0x9982, 0x6AAA, 0x9983, 0x6AAD, 0x9984, 0x6AAE, 0x9985, 0x6AAF, 0x9986, 0x6AB0, 0x9987, 0x6AB1, + 0x9988, 0x6AB2, 0x9989, 0x6AB3, 0x998A, 0x6AB4, 0x998B, 0x6AB5, 0x998C, 0x6AB6, 0x998D, 0x6AB7, 0x998E, 0x6AB8, 0x998F, 0x6AB9, + 0x9990, 0x6ABA, 0x9991, 0x6ABB, 0x9992, 0x6ABC, 0x9993, 0x6ABD, 0x9994, 0x6ABE, 0x9995, 0x6ABF, 0x9996, 0x6AC0, 0x9997, 0x6AC1, + 0x9998, 0x6AC2, 0x9999, 0x6AC3, 0x999A, 0x6AC4, 0x999B, 0x6AC5, 0x999C, 0x6AC6, 0x999D, 0x6AC7, 0x999E, 0x6AC8, 0x999F, 0x6AC9, + 0x99A0, 0x6ACA, 0x99A1, 0x6ACB, 0x99A2, 0x6ACC, 0x99A3, 0x6ACD, 0x99A4, 0x6ACE, 0x99A5, 0x6ACF, 0x99A6, 0x6AD0, 0x99A7, 0x6AD1, + 0x99A8, 0x6AD2, 0x99A9, 0x6AD3, 0x99AA, 0x6AD4, 0x99AB, 0x6AD5, 0x99AC, 0x6AD6, 0x99AD, 0x6AD7, 0x99AE, 0x6AD8, 0x99AF, 0x6AD9, + 0x99B0, 0x6ADA, 0x99B1, 0x6ADB, 0x99B2, 0x6ADC, 0x99B3, 0x6ADD, 0x99B4, 0x6ADE, 0x99B5, 0x6ADF, 0x99B6, 0x6AE0, 0x99B7, 0x6AE1, + 0x99B8, 0x6AE2, 0x99B9, 0x6AE3, 0x99BA, 0x6AE4, 0x99BB, 0x6AE5, 0x99BC, 0x6AE6, 0x99BD, 0x6AE7, 0x99BE, 0x6AE8, 0x99BF, 0x6AE9, + 0x99C0, 0x6AEA, 0x99C1, 0x6AEB, 0x99C2, 0x6AEC, 0x99C3, 0x6AED, 0x99C4, 0x6AEE, 0x99C5, 0x6AEF, 0x99C6, 0x6AF0, 0x99C7, 0x6AF1, + 0x99C8, 0x6AF2, 0x99C9, 0x6AF3, 0x99CA, 0x6AF4, 0x99CB, 0x6AF5, 0x99CC, 0x6AF6, 0x99CD, 0x6AF7, 0x99CE, 0x6AF8, 0x99CF, 0x6AF9, + 0x99D0, 0x6AFA, 0x99D1, 0x6AFB, 0x99D2, 0x6AFC, 0x99D3, 0x6AFD, 0x99D4, 0x6AFE, 0x99D5, 0x6AFF, 0x99D6, 0x6B00, 0x99D7, 0x6B01, + 0x99D8, 0x6B02, 0x99D9, 0x6B03, 0x99DA, 0x6B04, 0x99DB, 0x6B05, 0x99DC, 0x6B06, 0x99DD, 0x6B07, 0x99DE, 0x6B08, 0x99DF, 0x6B09, + 0x99E0, 0x6B0A, 0x99E1, 0x6B0B, 0x99E2, 0x6B0C, 0x99E3, 0x6B0D, 0x99E4, 0x6B0E, 0x99E5, 0x6B0F, 0x99E6, 0x6B10, 0x99E7, 0x6B11, + 0x99E8, 0x6B12, 0x99E9, 0x6B13, 0x99EA, 0x6B14, 0x99EB, 0x6B15, 0x99EC, 0x6B16, 0x99ED, 0x6B17, 0x99EE, 0x6B18, 0x99EF, 0x6B19, + 0x99F0, 0x6B1A, 0x99F1, 0x6B1B, 0x99F2, 0x6B1C, 0x99F3, 0x6B1D, 0x99F4, 0x6B1E, 0x99F5, 0x6B1F, 0x99F6, 0x6B25, 0x99F7, 0x6B26, + 0x99F8, 0x6B28, 0x99F9, 0x6B29, 0x99FA, 0x6B2A, 0x99FB, 0x6B2B, 0x99FC, 0x6B2C, 0x99FD, 0x6B2D, 0x99FE, 0x6B2E, 0x9A40, 0x6B2F, + 0x9A41, 0x6B30, 0x9A42, 0x6B31, 0x9A43, 0x6B33, 0x9A44, 0x6B34, 0x9A45, 0x6B35, 0x9A46, 0x6B36, 0x9A47, 0x6B38, 0x9A48, 0x6B3B, + 0x9A49, 0x6B3C, 0x9A4A, 0x6B3D, 0x9A4B, 0x6B3F, 0x9A4C, 0x6B40, 0x9A4D, 0x6B41, 0x9A4E, 0x6B42, 0x9A4F, 0x6B44, 0x9A50, 0x6B45, + 0x9A51, 0x6B48, 0x9A52, 0x6B4A, 0x9A53, 0x6B4B, 0x9A54, 0x6B4D, 0x9A55, 0x6B4E, 0x9A56, 0x6B4F, 0x9A57, 0x6B50, 0x9A58, 0x6B51, + 0x9A59, 0x6B52, 0x9A5A, 0x6B53, 0x9A5B, 0x6B54, 0x9A5C, 0x6B55, 0x9A5D, 0x6B56, 0x9A5E, 0x6B57, 0x9A5F, 0x6B58, 0x9A60, 0x6B5A, + 0x9A61, 0x6B5B, 0x9A62, 0x6B5C, 0x9A63, 0x6B5D, 0x9A64, 0x6B5E, 0x9A65, 0x6B5F, 0x9A66, 0x6B60, 0x9A67, 0x6B61, 0x9A68, 0x6B68, + 0x9A69, 0x6B69, 0x9A6A, 0x6B6B, 0x9A6B, 0x6B6C, 0x9A6C, 0x6B6D, 0x9A6D, 0x6B6E, 0x9A6E, 0x6B6F, 0x9A6F, 0x6B70, 0x9A70, 0x6B71, + 0x9A71, 0x6B72, 0x9A72, 0x6B73, 0x9A73, 0x6B74, 0x9A74, 0x6B75, 0x9A75, 0x6B76, 0x9A76, 0x6B77, 0x9A77, 0x6B78, 0x9A78, 0x6B7A, + 0x9A79, 0x6B7D, 0x9A7A, 0x6B7E, 0x9A7B, 0x6B7F, 0x9A7C, 0x6B80, 0x9A7D, 0x6B85, 0x9A7E, 0x6B88, 0x9A80, 0x6B8C, 0x9A81, 0x6B8E, + 0x9A82, 0x6B8F, 0x9A83, 0x6B90, 0x9A84, 0x6B91, 0x9A85, 0x6B94, 0x9A86, 0x6B95, 0x9A87, 0x6B97, 0x9A88, 0x6B98, 0x9A89, 0x6B99, + 0x9A8A, 0x6B9C, 0x9A8B, 0x6B9D, 0x9A8C, 0x6B9E, 0x9A8D, 0x6B9F, 0x9A8E, 0x6BA0, 0x9A8F, 0x6BA2, 0x9A90, 0x6BA3, 0x9A91, 0x6BA4, + 0x9A92, 0x6BA5, 0x9A93, 0x6BA6, 0x9A94, 0x6BA7, 0x9A95, 0x6BA8, 0x9A96, 0x6BA9, 0x9A97, 0x6BAB, 0x9A98, 0x6BAC, 0x9A99, 0x6BAD, + 0x9A9A, 0x6BAE, 0x9A9B, 0x6BAF, 0x9A9C, 0x6BB0, 0x9A9D, 0x6BB1, 0x9A9E, 0x6BB2, 0x9A9F, 0x6BB6, 0x9AA0, 0x6BB8, 0x9AA1, 0x6BB9, + 0x9AA2, 0x6BBA, 0x9AA3, 0x6BBB, 0x9AA4, 0x6BBC, 0x9AA5, 0x6BBD, 0x9AA6, 0x6BBE, 0x9AA7, 0x6BC0, 0x9AA8, 0x6BC3, 0x9AA9, 0x6BC4, + 0x9AAA, 0x6BC6, 0x9AAB, 0x6BC7, 0x9AAC, 0x6BC8, 0x9AAD, 0x6BC9, 0x9AAE, 0x6BCA, 0x9AAF, 0x6BCC, 0x9AB0, 0x6BCE, 0x9AB1, 0x6BD0, + 0x9AB2, 0x6BD1, 0x9AB3, 0x6BD8, 0x9AB4, 0x6BDA, 0x9AB5, 0x6BDC, 0x9AB6, 0x6BDD, 0x9AB7, 0x6BDE, 0x9AB8, 0x6BDF, 0x9AB9, 0x6BE0, + 0x9ABA, 0x6BE2, 0x9ABB, 0x6BE3, 0x9ABC, 0x6BE4, 0x9ABD, 0x6BE5, 0x9ABE, 0x6BE6, 0x9ABF, 0x6BE7, 0x9AC0, 0x6BE8, 0x9AC1, 0x6BE9, + 0x9AC2, 0x6BEC, 0x9AC3, 0x6BED, 0x9AC4, 0x6BEE, 0x9AC5, 0x6BF0, 0x9AC6, 0x6BF1, 0x9AC7, 0x6BF2, 0x9AC8, 0x6BF4, 0x9AC9, 0x6BF6, + 0x9ACA, 0x6BF7, 0x9ACB, 0x6BF8, 0x9ACC, 0x6BFA, 0x9ACD, 0x6BFB, 0x9ACE, 0x6BFC, 0x9ACF, 0x6BFE, 0x9AD0, 0x6BFF, 0x9AD1, 0x6C00, + 0x9AD2, 0x6C01, 0x9AD3, 0x6C02, 0x9AD4, 0x6C03, 0x9AD5, 0x6C04, 0x9AD6, 0x6C08, 0x9AD7, 0x6C09, 0x9AD8, 0x6C0A, 0x9AD9, 0x6C0B, + 0x9ADA, 0x6C0C, 0x9ADB, 0x6C0E, 0x9ADC, 0x6C12, 0x9ADD, 0x6C17, 0x9ADE, 0x6C1C, 0x9ADF, 0x6C1D, 0x9AE0, 0x6C1E, 0x9AE1, 0x6C20, + 0x9AE2, 0x6C23, 0x9AE3, 0x6C25, 0x9AE4, 0x6C2B, 0x9AE5, 0x6C2C, 0x9AE6, 0x6C2D, 0x9AE7, 0x6C31, 0x9AE8, 0x6C33, 0x9AE9, 0x6C36, + 0x9AEA, 0x6C37, 0x9AEB, 0x6C39, 0x9AEC, 0x6C3A, 0x9AED, 0x6C3B, 0x9AEE, 0x6C3C, 0x9AEF, 0x6C3E, 0x9AF0, 0x6C3F, 0x9AF1, 0x6C43, + 0x9AF2, 0x6C44, 0x9AF3, 0x6C45, 0x9AF4, 0x6C48, 0x9AF5, 0x6C4B, 0x9AF6, 0x6C4C, 0x9AF7, 0x6C4D, 0x9AF8, 0x6C4E, 0x9AF9, 0x6C4F, + 0x9AFA, 0x6C51, 0x9AFB, 0x6C52, 0x9AFC, 0x6C53, 0x9AFD, 0x6C56, 0x9AFE, 0x6C58, 0x9B40, 0x6C59, 0x9B41, 0x6C5A, 0x9B42, 0x6C62, + 0x9B43, 0x6C63, 0x9B44, 0x6C65, 0x9B45, 0x6C66, 0x9B46, 0x6C67, 0x9B47, 0x6C6B, 0x9B48, 0x6C6C, 0x9B49, 0x6C6D, 0x9B4A, 0x6C6E, + 0x9B4B, 0x6C6F, 0x9B4C, 0x6C71, 0x9B4D, 0x6C73, 0x9B4E, 0x6C75, 0x9B4F, 0x6C77, 0x9B50, 0x6C78, 0x9B51, 0x6C7A, 0x9B52, 0x6C7B, + 0x9B53, 0x6C7C, 0x9B54, 0x6C7F, 0x9B55, 0x6C80, 0x9B56, 0x6C84, 0x9B57, 0x6C87, 0x9B58, 0x6C8A, 0x9B59, 0x6C8B, 0x9B5A, 0x6C8D, + 0x9B5B, 0x6C8E, 0x9B5C, 0x6C91, 0x9B5D, 0x6C92, 0x9B5E, 0x6C95, 0x9B5F, 0x6C96, 0x9B60, 0x6C97, 0x9B61, 0x6C98, 0x9B62, 0x6C9A, + 0x9B63, 0x6C9C, 0x9B64, 0x6C9D, 0x9B65, 0x6C9E, 0x9B66, 0x6CA0, 0x9B67, 0x6CA2, 0x9B68, 0x6CA8, 0x9B69, 0x6CAC, 0x9B6A, 0x6CAF, + 0x9B6B, 0x6CB0, 0x9B6C, 0x6CB4, 0x9B6D, 0x6CB5, 0x9B6E, 0x6CB6, 0x9B6F, 0x6CB7, 0x9B70, 0x6CBA, 0x9B71, 0x6CC0, 0x9B72, 0x6CC1, + 0x9B73, 0x6CC2, 0x9B74, 0x6CC3, 0x9B75, 0x6CC6, 0x9B76, 0x6CC7, 0x9B77, 0x6CC8, 0x9B78, 0x6CCB, 0x9B79, 0x6CCD, 0x9B7A, 0x6CCE, + 0x9B7B, 0x6CCF, 0x9B7C, 0x6CD1, 0x9B7D, 0x6CD2, 0x9B7E, 0x6CD8, 0x9B80, 0x6CD9, 0x9B81, 0x6CDA, 0x9B82, 0x6CDC, 0x9B83, 0x6CDD, + 0x9B84, 0x6CDF, 0x9B85, 0x6CE4, 0x9B86, 0x6CE6, 0x9B87, 0x6CE7, 0x9B88, 0x6CE9, 0x9B89, 0x6CEC, 0x9B8A, 0x6CED, 0x9B8B, 0x6CF2, + 0x9B8C, 0x6CF4, 0x9B8D, 0x6CF9, 0x9B8E, 0x6CFF, 0x9B8F, 0x6D00, 0x9B90, 0x6D02, 0x9B91, 0x6D03, 0x9B92, 0x6D05, 0x9B93, 0x6D06, + 0x9B94, 0x6D08, 0x9B95, 0x6D09, 0x9B96, 0x6D0A, 0x9B97, 0x6D0D, 0x9B98, 0x6D0F, 0x9B99, 0x6D10, 0x9B9A, 0x6D11, 0x9B9B, 0x6D13, + 0x9B9C, 0x6D14, 0x9B9D, 0x6D15, 0x9B9E, 0x6D16, 0x9B9F, 0x6D18, 0x9BA0, 0x6D1C, 0x9BA1, 0x6D1D, 0x9BA2, 0x6D1F, 0x9BA3, 0x6D20, + 0x9BA4, 0x6D21, 0x9BA5, 0x6D22, 0x9BA6, 0x6D23, 0x9BA7, 0x6D24, 0x9BA8, 0x6D26, 0x9BA9, 0x6D28, 0x9BAA, 0x6D29, 0x9BAB, 0x6D2C, + 0x9BAC, 0x6D2D, 0x9BAD, 0x6D2F, 0x9BAE, 0x6D30, 0x9BAF, 0x6D34, 0x9BB0, 0x6D36, 0x9BB1, 0x6D37, 0x9BB2, 0x6D38, 0x9BB3, 0x6D3A, + 0x9BB4, 0x6D3F, 0x9BB5, 0x6D40, 0x9BB6, 0x6D42, 0x9BB7, 0x6D44, 0x9BB8, 0x6D49, 0x9BB9, 0x6D4C, 0x9BBA, 0x6D50, 0x9BBB, 0x6D55, + 0x9BBC, 0x6D56, 0x9BBD, 0x6D57, 0x9BBE, 0x6D58, 0x9BBF, 0x6D5B, 0x9BC0, 0x6D5D, 0x9BC1, 0x6D5F, 0x9BC2, 0x6D61, 0x9BC3, 0x6D62, + 0x9BC4, 0x6D64, 0x9BC5, 0x6D65, 0x9BC6, 0x6D67, 0x9BC7, 0x6D68, 0x9BC8, 0x6D6B, 0x9BC9, 0x6D6C, 0x9BCA, 0x6D6D, 0x9BCB, 0x6D70, + 0x9BCC, 0x6D71, 0x9BCD, 0x6D72, 0x9BCE, 0x6D73, 0x9BCF, 0x6D75, 0x9BD0, 0x6D76, 0x9BD1, 0x6D79, 0x9BD2, 0x6D7A, 0x9BD3, 0x6D7B, + 0x9BD4, 0x6D7D, 0x9BD5, 0x6D7E, 0x9BD6, 0x6D7F, 0x9BD7, 0x6D80, 0x9BD8, 0x6D81, 0x9BD9, 0x6D83, 0x9BDA, 0x6D84, 0x9BDB, 0x6D86, + 0x9BDC, 0x6D87, 0x9BDD, 0x6D8A, 0x9BDE, 0x6D8B, 0x9BDF, 0x6D8D, 0x9BE0, 0x6D8F, 0x9BE1, 0x6D90, 0x9BE2, 0x6D92, 0x9BE3, 0x6D96, + 0x9BE4, 0x6D97, 0x9BE5, 0x6D98, 0x9BE6, 0x6D99, 0x9BE7, 0x6D9A, 0x9BE8, 0x6D9C, 0x9BE9, 0x6DA2, 0x9BEA, 0x6DA5, 0x9BEB, 0x6DAC, + 0x9BEC, 0x6DAD, 0x9BED, 0x6DB0, 0x9BEE, 0x6DB1, 0x9BEF, 0x6DB3, 0x9BF0, 0x6DB4, 0x9BF1, 0x6DB6, 0x9BF2, 0x6DB7, 0x9BF3, 0x6DB9, + 0x9BF4, 0x6DBA, 0x9BF5, 0x6DBB, 0x9BF6, 0x6DBC, 0x9BF7, 0x6DBD, 0x9BF8, 0x6DBE, 0x9BF9, 0x6DC1, 0x9BFA, 0x6DC2, 0x9BFB, 0x6DC3, + 0x9BFC, 0x6DC8, 0x9BFD, 0x6DC9, 0x9BFE, 0x6DCA, 0x9C40, 0x6DCD, 0x9C41, 0x6DCE, 0x9C42, 0x6DCF, 0x9C43, 0x6DD0, 0x9C44, 0x6DD2, + 0x9C45, 0x6DD3, 0x9C46, 0x6DD4, 0x9C47, 0x6DD5, 0x9C48, 0x6DD7, 0x9C49, 0x6DDA, 0x9C4A, 0x6DDB, 0x9C4B, 0x6DDC, 0x9C4C, 0x6DDF, + 0x9C4D, 0x6DE2, 0x9C4E, 0x6DE3, 0x9C4F, 0x6DE5, 0x9C50, 0x6DE7, 0x9C51, 0x6DE8, 0x9C52, 0x6DE9, 0x9C53, 0x6DEA, 0x9C54, 0x6DED, + 0x9C55, 0x6DEF, 0x9C56, 0x6DF0, 0x9C57, 0x6DF2, 0x9C58, 0x6DF4, 0x9C59, 0x6DF5, 0x9C5A, 0x6DF6, 0x9C5B, 0x6DF8, 0x9C5C, 0x6DFA, + 0x9C5D, 0x6DFD, 0x9C5E, 0x6DFE, 0x9C5F, 0x6DFF, 0x9C60, 0x6E00, 0x9C61, 0x6E01, 0x9C62, 0x6E02, 0x9C63, 0x6E03, 0x9C64, 0x6E04, + 0x9C65, 0x6E06, 0x9C66, 0x6E07, 0x9C67, 0x6E08, 0x9C68, 0x6E09, 0x9C69, 0x6E0B, 0x9C6A, 0x6E0F, 0x9C6B, 0x6E12, 0x9C6C, 0x6E13, + 0x9C6D, 0x6E15, 0x9C6E, 0x6E18, 0x9C6F, 0x6E19, 0x9C70, 0x6E1B, 0x9C71, 0x6E1C, 0x9C72, 0x6E1E, 0x9C73, 0x6E1F, 0x9C74, 0x6E22, + 0x9C75, 0x6E26, 0x9C76, 0x6E27, 0x9C77, 0x6E28, 0x9C78, 0x6E2A, 0x9C79, 0x6E2C, 0x9C7A, 0x6E2E, 0x9C7B, 0x6E30, 0x9C7C, 0x6E31, + 0x9C7D, 0x6E33, 0x9C7E, 0x6E35, 0x9C80, 0x6E36, 0x9C81, 0x6E37, 0x9C82, 0x6E39, 0x9C83, 0x6E3B, 0x9C84, 0x6E3C, 0x9C85, 0x6E3D, + 0x9C86, 0x6E3E, 0x9C87, 0x6E3F, 0x9C88, 0x6E40, 0x9C89, 0x6E41, 0x9C8A, 0x6E42, 0x9C8B, 0x6E45, 0x9C8C, 0x6E46, 0x9C8D, 0x6E47, + 0x9C8E, 0x6E48, 0x9C8F, 0x6E49, 0x9C90, 0x6E4A, 0x9C91, 0x6E4B, 0x9C92, 0x6E4C, 0x9C93, 0x6E4F, 0x9C94, 0x6E50, 0x9C95, 0x6E51, + 0x9C96, 0x6E52, 0x9C97, 0x6E55, 0x9C98, 0x6E57, 0x9C99, 0x6E59, 0x9C9A, 0x6E5A, 0x9C9B, 0x6E5C, 0x9C9C, 0x6E5D, 0x9C9D, 0x6E5E, + 0x9C9E, 0x6E60, 0x9C9F, 0x6E61, 0x9CA0, 0x6E62, 0x9CA1, 0x6E63, 0x9CA2, 0x6E64, 0x9CA3, 0x6E65, 0x9CA4, 0x6E66, 0x9CA5, 0x6E67, + 0x9CA6, 0x6E68, 0x9CA7, 0x6E69, 0x9CA8, 0x6E6A, 0x9CA9, 0x6E6C, 0x9CAA, 0x6E6D, 0x9CAB, 0x6E6F, 0x9CAC, 0x6E70, 0x9CAD, 0x6E71, + 0x9CAE, 0x6E72, 0x9CAF, 0x6E73, 0x9CB0, 0x6E74, 0x9CB1, 0x6E75, 0x9CB2, 0x6E76, 0x9CB3, 0x6E77, 0x9CB4, 0x6E78, 0x9CB5, 0x6E79, + 0x9CB6, 0x6E7A, 0x9CB7, 0x6E7B, 0x9CB8, 0x6E7C, 0x9CB9, 0x6E7D, 0x9CBA, 0x6E80, 0x9CBB, 0x6E81, 0x9CBC, 0x6E82, 0x9CBD, 0x6E84, + 0x9CBE, 0x6E87, 0x9CBF, 0x6E88, 0x9CC0, 0x6E8A, 0x9CC1, 0x6E8B, 0x9CC2, 0x6E8C, 0x9CC3, 0x6E8D, 0x9CC4, 0x6E8E, 0x9CC5, 0x6E91, + 0x9CC6, 0x6E92, 0x9CC7, 0x6E93, 0x9CC8, 0x6E94, 0x9CC9, 0x6E95, 0x9CCA, 0x6E96, 0x9CCB, 0x6E97, 0x9CCC, 0x6E99, 0x9CCD, 0x6E9A, + 0x9CCE, 0x6E9B, 0x9CCF, 0x6E9D, 0x9CD0, 0x6E9E, 0x9CD1, 0x6EA0, 0x9CD2, 0x6EA1, 0x9CD3, 0x6EA3, 0x9CD4, 0x6EA4, 0x9CD5, 0x6EA6, + 0x9CD6, 0x6EA8, 0x9CD7, 0x6EA9, 0x9CD8, 0x6EAB, 0x9CD9, 0x6EAC, 0x9CDA, 0x6EAD, 0x9CDB, 0x6EAE, 0x9CDC, 0x6EB0, 0x9CDD, 0x6EB3, + 0x9CDE, 0x6EB5, 0x9CDF, 0x6EB8, 0x9CE0, 0x6EB9, 0x9CE1, 0x6EBC, 0x9CE2, 0x6EBE, 0x9CE3, 0x6EBF, 0x9CE4, 0x6EC0, 0x9CE5, 0x6EC3, + 0x9CE6, 0x6EC4, 0x9CE7, 0x6EC5, 0x9CE8, 0x6EC6, 0x9CE9, 0x6EC8, 0x9CEA, 0x6EC9, 0x9CEB, 0x6ECA, 0x9CEC, 0x6ECC, 0x9CED, 0x6ECD, + 0x9CEE, 0x6ECE, 0x9CEF, 0x6ED0, 0x9CF0, 0x6ED2, 0x9CF1, 0x6ED6, 0x9CF2, 0x6ED8, 0x9CF3, 0x6ED9, 0x9CF4, 0x6EDB, 0x9CF5, 0x6EDC, + 0x9CF6, 0x6EDD, 0x9CF7, 0x6EE3, 0x9CF8, 0x6EE7, 0x9CF9, 0x6EEA, 0x9CFA, 0x6EEB, 0x9CFB, 0x6EEC, 0x9CFC, 0x6EED, 0x9CFD, 0x6EEE, + 0x9CFE, 0x6EEF, 0x9D40, 0x6EF0, 0x9D41, 0x6EF1, 0x9D42, 0x6EF2, 0x9D43, 0x6EF3, 0x9D44, 0x6EF5, 0x9D45, 0x6EF6, 0x9D46, 0x6EF7, + 0x9D47, 0x6EF8, 0x9D48, 0x6EFA, 0x9D49, 0x6EFB, 0x9D4A, 0x6EFC, 0x9D4B, 0x6EFD, 0x9D4C, 0x6EFE, 0x9D4D, 0x6EFF, 0x9D4E, 0x6F00, + 0x9D4F, 0x6F01, 0x9D50, 0x6F03, 0x9D51, 0x6F04, 0x9D52, 0x6F05, 0x9D53, 0x6F07, 0x9D54, 0x6F08, 0x9D55, 0x6F0A, 0x9D56, 0x6F0B, + 0x9D57, 0x6F0C, 0x9D58, 0x6F0D, 0x9D59, 0x6F0E, 0x9D5A, 0x6F10, 0x9D5B, 0x6F11, 0x9D5C, 0x6F12, 0x9D5D, 0x6F16, 0x9D5E, 0x6F17, + 0x9D5F, 0x6F18, 0x9D60, 0x6F19, 0x9D61, 0x6F1A, 0x9D62, 0x6F1B, 0x9D63, 0x6F1C, 0x9D64, 0x6F1D, 0x9D65, 0x6F1E, 0x9D66, 0x6F1F, + 0x9D67, 0x6F21, 0x9D68, 0x6F22, 0x9D69, 0x6F23, 0x9D6A, 0x6F25, 0x9D6B, 0x6F26, 0x9D6C, 0x6F27, 0x9D6D, 0x6F28, 0x9D6E, 0x6F2C, + 0x9D6F, 0x6F2E, 0x9D70, 0x6F30, 0x9D71, 0x6F32, 0x9D72, 0x6F34, 0x9D73, 0x6F35, 0x9D74, 0x6F37, 0x9D75, 0x6F38, 0x9D76, 0x6F39, + 0x9D77, 0x6F3A, 0x9D78, 0x6F3B, 0x9D79, 0x6F3C, 0x9D7A, 0x6F3D, 0x9D7B, 0x6F3F, 0x9D7C, 0x6F40, 0x9D7D, 0x6F41, 0x9D7E, 0x6F42, + 0x9D80, 0x6F43, 0x9D81, 0x6F44, 0x9D82, 0x6F45, 0x9D83, 0x6F48, 0x9D84, 0x6F49, 0x9D85, 0x6F4A, 0x9D86, 0x6F4C, 0x9D87, 0x6F4E, + 0x9D88, 0x6F4F, 0x9D89, 0x6F50, 0x9D8A, 0x6F51, 0x9D8B, 0x6F52, 0x9D8C, 0x6F53, 0x9D8D, 0x6F54, 0x9D8E, 0x6F55, 0x9D8F, 0x6F56, + 0x9D90, 0x6F57, 0x9D91, 0x6F59, 0x9D92, 0x6F5A, 0x9D93, 0x6F5B, 0x9D94, 0x6F5D, 0x9D95, 0x6F5F, 0x9D96, 0x6F60, 0x9D97, 0x6F61, + 0x9D98, 0x6F63, 0x9D99, 0x6F64, 0x9D9A, 0x6F65, 0x9D9B, 0x6F67, 0x9D9C, 0x6F68, 0x9D9D, 0x6F69, 0x9D9E, 0x6F6A, 0x9D9F, 0x6F6B, + 0x9DA0, 0x6F6C, 0x9DA1, 0x6F6F, 0x9DA2, 0x6F70, 0x9DA3, 0x6F71, 0x9DA4, 0x6F73, 0x9DA5, 0x6F75, 0x9DA6, 0x6F76, 0x9DA7, 0x6F77, + 0x9DA8, 0x6F79, 0x9DA9, 0x6F7B, 0x9DAA, 0x6F7D, 0x9DAB, 0x6F7E, 0x9DAC, 0x6F7F, 0x9DAD, 0x6F80, 0x9DAE, 0x6F81, 0x9DAF, 0x6F82, + 0x9DB0, 0x6F83, 0x9DB1, 0x6F85, 0x9DB2, 0x6F86, 0x9DB3, 0x6F87, 0x9DB4, 0x6F8A, 0x9DB5, 0x6F8B, 0x9DB6, 0x6F8F, 0x9DB7, 0x6F90, + 0x9DB8, 0x6F91, 0x9DB9, 0x6F92, 0x9DBA, 0x6F93, 0x9DBB, 0x6F94, 0x9DBC, 0x6F95, 0x9DBD, 0x6F96, 0x9DBE, 0x6F97, 0x9DBF, 0x6F98, + 0x9DC0, 0x6F99, 0x9DC1, 0x6F9A, 0x9DC2, 0x6F9B, 0x9DC3, 0x6F9D, 0x9DC4, 0x6F9E, 0x9DC5, 0x6F9F, 0x9DC6, 0x6FA0, 0x9DC7, 0x6FA2, + 0x9DC8, 0x6FA3, 0x9DC9, 0x6FA4, 0x9DCA, 0x6FA5, 0x9DCB, 0x6FA6, 0x9DCC, 0x6FA8, 0x9DCD, 0x6FA9, 0x9DCE, 0x6FAA, 0x9DCF, 0x6FAB, + 0x9DD0, 0x6FAC, 0x9DD1, 0x6FAD, 0x9DD2, 0x6FAE, 0x9DD3, 0x6FAF, 0x9DD4, 0x6FB0, 0x9DD5, 0x6FB1, 0x9DD6, 0x6FB2, 0x9DD7, 0x6FB4, + 0x9DD8, 0x6FB5, 0x9DD9, 0x6FB7, 0x9DDA, 0x6FB8, 0x9DDB, 0x6FBA, 0x9DDC, 0x6FBB, 0x9DDD, 0x6FBC, 0x9DDE, 0x6FBD, 0x9DDF, 0x6FBE, + 0x9DE0, 0x6FBF, 0x9DE1, 0x6FC1, 0x9DE2, 0x6FC3, 0x9DE3, 0x6FC4, 0x9DE4, 0x6FC5, 0x9DE5, 0x6FC6, 0x9DE6, 0x6FC7, 0x9DE7, 0x6FC8, + 0x9DE8, 0x6FCA, 0x9DE9, 0x6FCB, 0x9DEA, 0x6FCC, 0x9DEB, 0x6FCD, 0x9DEC, 0x6FCE, 0x9DED, 0x6FCF, 0x9DEE, 0x6FD0, 0x9DEF, 0x6FD3, + 0x9DF0, 0x6FD4, 0x9DF1, 0x6FD5, 0x9DF2, 0x6FD6, 0x9DF3, 0x6FD7, 0x9DF4, 0x6FD8, 0x9DF5, 0x6FD9, 0x9DF6, 0x6FDA, 0x9DF7, 0x6FDB, + 0x9DF8, 0x6FDC, 0x9DF9, 0x6FDD, 0x9DFA, 0x6FDF, 0x9DFB, 0x6FE2, 0x9DFC, 0x6FE3, 0x9DFD, 0x6FE4, 0x9DFE, 0x6FE5, 0x9E40, 0x6FE6, + 0x9E41, 0x6FE7, 0x9E42, 0x6FE8, 0x9E43, 0x6FE9, 0x9E44, 0x6FEA, 0x9E45, 0x6FEB, 0x9E46, 0x6FEC, 0x9E47, 0x6FED, 0x9E48, 0x6FF0, + 0x9E49, 0x6FF1, 0x9E4A, 0x6FF2, 0x9E4B, 0x6FF3, 0x9E4C, 0x6FF4, 0x9E4D, 0x6FF5, 0x9E4E, 0x6FF6, 0x9E4F, 0x6FF7, 0x9E50, 0x6FF8, + 0x9E51, 0x6FF9, 0x9E52, 0x6FFA, 0x9E53, 0x6FFB, 0x9E54, 0x6FFC, 0x9E55, 0x6FFD, 0x9E56, 0x6FFE, 0x9E57, 0x6FFF, 0x9E58, 0x7000, + 0x9E59, 0x7001, 0x9E5A, 0x7002, 0x9E5B, 0x7003, 0x9E5C, 0x7004, 0x9E5D, 0x7005, 0x9E5E, 0x7006, 0x9E5F, 0x7007, 0x9E60, 0x7008, + 0x9E61, 0x7009, 0x9E62, 0x700A, 0x9E63, 0x700B, 0x9E64, 0x700C, 0x9E65, 0x700D, 0x9E66, 0x700E, 0x9E67, 0x700F, 0x9E68, 0x7010, + 0x9E69, 0x7012, 0x9E6A, 0x7013, 0x9E6B, 0x7014, 0x9E6C, 0x7015, 0x9E6D, 0x7016, 0x9E6E, 0x7017, 0x9E6F, 0x7018, 0x9E70, 0x7019, + 0x9E71, 0x701C, 0x9E72, 0x701D, 0x9E73, 0x701E, 0x9E74, 0x701F, 0x9E75, 0x7020, 0x9E76, 0x7021, 0x9E77, 0x7022, 0x9E78, 0x7024, + 0x9E79, 0x7025, 0x9E7A, 0x7026, 0x9E7B, 0x7027, 0x9E7C, 0x7028, 0x9E7D, 0x7029, 0x9E7E, 0x702A, 0x9E80, 0x702B, 0x9E81, 0x702C, + 0x9E82, 0x702D, 0x9E83, 0x702E, 0x9E84, 0x702F, 0x9E85, 0x7030, 0x9E86, 0x7031, 0x9E87, 0x7032, 0x9E88, 0x7033, 0x9E89, 0x7034, + 0x9E8A, 0x7036, 0x9E8B, 0x7037, 0x9E8C, 0x7038, 0x9E8D, 0x703A, 0x9E8E, 0x703B, 0x9E8F, 0x703C, 0x9E90, 0x703D, 0x9E91, 0x703E, + 0x9E92, 0x703F, 0x9E93, 0x7040, 0x9E94, 0x7041, 0x9E95, 0x7042, 0x9E96, 0x7043, 0x9E97, 0x7044, 0x9E98, 0x7045, 0x9E99, 0x7046, + 0x9E9A, 0x7047, 0x9E9B, 0x7048, 0x9E9C, 0x7049, 0x9E9D, 0x704A, 0x9E9E, 0x704B, 0x9E9F, 0x704D, 0x9EA0, 0x704E, 0x9EA1, 0x7050, + 0x9EA2, 0x7051, 0x9EA3, 0x7052, 0x9EA4, 0x7053, 0x9EA5, 0x7054, 0x9EA6, 0x7055, 0x9EA7, 0x7056, 0x9EA8, 0x7057, 0x9EA9, 0x7058, + 0x9EAA, 0x7059, 0x9EAB, 0x705A, 0x9EAC, 0x705B, 0x9EAD, 0x705C, 0x9EAE, 0x705D, 0x9EAF, 0x705F, 0x9EB0, 0x7060, 0x9EB1, 0x7061, + 0x9EB2, 0x7062, 0x9EB3, 0x7063, 0x9EB4, 0x7064, 0x9EB5, 0x7065, 0x9EB6, 0x7066, 0x9EB7, 0x7067, 0x9EB8, 0x7068, 0x9EB9, 0x7069, + 0x9EBA, 0x706A, 0x9EBB, 0x706E, 0x9EBC, 0x7071, 0x9EBD, 0x7072, 0x9EBE, 0x7073, 0x9EBF, 0x7074, 0x9EC0, 0x7077, 0x9EC1, 0x7079, + 0x9EC2, 0x707A, 0x9EC3, 0x707B, 0x9EC4, 0x707D, 0x9EC5, 0x7081, 0x9EC6, 0x7082, 0x9EC7, 0x7083, 0x9EC8, 0x7084, 0x9EC9, 0x7086, + 0x9ECA, 0x7087, 0x9ECB, 0x7088, 0x9ECC, 0x708B, 0x9ECD, 0x708C, 0x9ECE, 0x708D, 0x9ECF, 0x708F, 0x9ED0, 0x7090, 0x9ED1, 0x7091, + 0x9ED2, 0x7093, 0x9ED3, 0x7097, 0x9ED4, 0x7098, 0x9ED5, 0x709A, 0x9ED6, 0x709B, 0x9ED7, 0x709E, 0x9ED8, 0x709F, 0x9ED9, 0x70A0, + 0x9EDA, 0x70A1, 0x9EDB, 0x70A2, 0x9EDC, 0x70A3, 0x9EDD, 0x70A4, 0x9EDE, 0x70A5, 0x9EDF, 0x70A6, 0x9EE0, 0x70A7, 0x9EE1, 0x70A8, + 0x9EE2, 0x70A9, 0x9EE3, 0x70AA, 0x9EE4, 0x70B0, 0x9EE5, 0x70B2, 0x9EE6, 0x70B4, 0x9EE7, 0x70B5, 0x9EE8, 0x70B6, 0x9EE9, 0x70BA, + 0x9EEA, 0x70BE, 0x9EEB, 0x70BF, 0x9EEC, 0x70C4, 0x9EED, 0x70C5, 0x9EEE, 0x70C6, 0x9EEF, 0x70C7, 0x9EF0, 0x70C9, 0x9EF1, 0x70CB, + 0x9EF2, 0x70CC, 0x9EF3, 0x70CD, 0x9EF4, 0x70CE, 0x9EF5, 0x70CF, 0x9EF6, 0x70D0, 0x9EF7, 0x70D1, 0x9EF8, 0x70D2, 0x9EF9, 0x70D3, + 0x9EFA, 0x70D4, 0x9EFB, 0x70D5, 0x9EFC, 0x70D6, 0x9EFD, 0x70D7, 0x9EFE, 0x70DA, 0x9F40, 0x70DC, 0x9F41, 0x70DD, 0x9F42, 0x70DE, + 0x9F43, 0x70E0, 0x9F44, 0x70E1, 0x9F45, 0x70E2, 0x9F46, 0x70E3, 0x9F47, 0x70E5, 0x9F48, 0x70EA, 0x9F49, 0x70EE, 0x9F4A, 0x70F0, + 0x9F4B, 0x70F1, 0x9F4C, 0x70F2, 0x9F4D, 0x70F3, 0x9F4E, 0x70F4, 0x9F4F, 0x70F5, 0x9F50, 0x70F6, 0x9F51, 0x70F8, 0x9F52, 0x70FA, + 0x9F53, 0x70FB, 0x9F54, 0x70FC, 0x9F55, 0x70FE, 0x9F56, 0x70FF, 0x9F57, 0x7100, 0x9F58, 0x7101, 0x9F59, 0x7102, 0x9F5A, 0x7103, + 0x9F5B, 0x7104, 0x9F5C, 0x7105, 0x9F5D, 0x7106, 0x9F5E, 0x7107, 0x9F5F, 0x7108, 0x9F60, 0x710B, 0x9F61, 0x710C, 0x9F62, 0x710D, + 0x9F63, 0x710E, 0x9F64, 0x710F, 0x9F65, 0x7111, 0x9F66, 0x7112, 0x9F67, 0x7114, 0x9F68, 0x7117, 0x9F69, 0x711B, 0x9F6A, 0x711C, + 0x9F6B, 0x711D, 0x9F6C, 0x711E, 0x9F6D, 0x711F, 0x9F6E, 0x7120, 0x9F6F, 0x7121, 0x9F70, 0x7122, 0x9F71, 0x7123, 0x9F72, 0x7124, + 0x9F73, 0x7125, 0x9F74, 0x7127, 0x9F75, 0x7128, 0x9F76, 0x7129, 0x9F77, 0x712A, 0x9F78, 0x712B, 0x9F79, 0x712C, 0x9F7A, 0x712D, + 0x9F7B, 0x712E, 0x9F7C, 0x7132, 0x9F7D, 0x7133, 0x9F7E, 0x7134, 0x9F80, 0x7135, 0x9F81, 0x7137, 0x9F82, 0x7138, 0x9F83, 0x7139, + 0x9F84, 0x713A, 0x9F85, 0x713B, 0x9F86, 0x713C, 0x9F87, 0x713D, 0x9F88, 0x713E, 0x9F89, 0x713F, 0x9F8A, 0x7140, 0x9F8B, 0x7141, + 0x9F8C, 0x7142, 0x9F8D, 0x7143, 0x9F8E, 0x7144, 0x9F8F, 0x7146, 0x9F90, 0x7147, 0x9F91, 0x7148, 0x9F92, 0x7149, 0x9F93, 0x714B, + 0x9F94, 0x714D, 0x9F95, 0x714F, 0x9F96, 0x7150, 0x9F97, 0x7151, 0x9F98, 0x7152, 0x9F99, 0x7153, 0x9F9A, 0x7154, 0x9F9B, 0x7155, + 0x9F9C, 0x7156, 0x9F9D, 0x7157, 0x9F9E, 0x7158, 0x9F9F, 0x7159, 0x9FA0, 0x715A, 0x9FA1, 0x715B, 0x9FA2, 0x715D, 0x9FA3, 0x715F, + 0x9FA4, 0x7160, 0x9FA5, 0x7161, 0x9FA6, 0x7162, 0x9FA7, 0x7163, 0x9FA8, 0x7165, 0x9FA9, 0x7169, 0x9FAA, 0x716A, 0x9FAB, 0x716B, + 0x9FAC, 0x716C, 0x9FAD, 0x716D, 0x9FAE, 0x716F, 0x9FAF, 0x7170, 0x9FB0, 0x7171, 0x9FB1, 0x7174, 0x9FB2, 0x7175, 0x9FB3, 0x7176, + 0x9FB4, 0x7177, 0x9FB5, 0x7179, 0x9FB6, 0x717B, 0x9FB7, 0x717C, 0x9FB8, 0x717E, 0x9FB9, 0x717F, 0x9FBA, 0x7180, 0x9FBB, 0x7181, + 0x9FBC, 0x7182, 0x9FBD, 0x7183, 0x9FBE, 0x7185, 0x9FBF, 0x7186, 0x9FC0, 0x7187, 0x9FC1, 0x7188, 0x9FC2, 0x7189, 0x9FC3, 0x718B, + 0x9FC4, 0x718C, 0x9FC5, 0x718D, 0x9FC6, 0x718E, 0x9FC7, 0x7190, 0x9FC8, 0x7191, 0x9FC9, 0x7192, 0x9FCA, 0x7193, 0x9FCB, 0x7195, + 0x9FCC, 0x7196, 0x9FCD, 0x7197, 0x9FCE, 0x719A, 0x9FCF, 0x719B, 0x9FD0, 0x719C, 0x9FD1, 0x719D, 0x9FD2, 0x719E, 0x9FD3, 0x71A1, + 0x9FD4, 0x71A2, 0x9FD5, 0x71A3, 0x9FD6, 0x71A4, 0x9FD7, 0x71A5, 0x9FD8, 0x71A6, 0x9FD9, 0x71A7, 0x9FDA, 0x71A9, 0x9FDB, 0x71AA, + 0x9FDC, 0x71AB, 0x9FDD, 0x71AD, 0x9FDE, 0x71AE, 0x9FDF, 0x71AF, 0x9FE0, 0x71B0, 0x9FE1, 0x71B1, 0x9FE2, 0x71B2, 0x9FE3, 0x71B4, + 0x9FE4, 0x71B6, 0x9FE5, 0x71B7, 0x9FE6, 0x71B8, 0x9FE7, 0x71BA, 0x9FE8, 0x71BB, 0x9FE9, 0x71BC, 0x9FEA, 0x71BD, 0x9FEB, 0x71BE, + 0x9FEC, 0x71BF, 0x9FED, 0x71C0, 0x9FEE, 0x71C1, 0x9FEF, 0x71C2, 0x9FF0, 0x71C4, 0x9FF1, 0x71C5, 0x9FF2, 0x71C6, 0x9FF3, 0x71C7, + 0x9FF4, 0x71C8, 0x9FF5, 0x71C9, 0x9FF6, 0x71CA, 0x9FF7, 0x71CB, 0x9FF8, 0x71CC, 0x9FF9, 0x71CD, 0x9FFA, 0x71CF, 0x9FFB, 0x71D0, + 0x9FFC, 0x71D1, 0x9FFD, 0x71D2, 0x9FFE, 0x71D3, 0xA040, 0x71D6, 0xA041, 0x71D7, 0xA042, 0x71D8, 0xA043, 0x71D9, 0xA044, 0x71DA, + 0xA045, 0x71DB, 0xA046, 0x71DC, 0xA047, 0x71DD, 0xA048, 0x71DE, 0xA049, 0x71DF, 0xA04A, 0x71E1, 0xA04B, 0x71E2, 0xA04C, 0x71E3, + 0xA04D, 0x71E4, 0xA04E, 0x71E6, 0xA04F, 0x71E8, 0xA050, 0x71E9, 0xA051, 0x71EA, 0xA052, 0x71EB, 0xA053, 0x71EC, 0xA054, 0x71ED, + 0xA055, 0x71EF, 0xA056, 0x71F0, 0xA057, 0x71F1, 0xA058, 0x71F2, 0xA059, 0x71F3, 0xA05A, 0x71F4, 0xA05B, 0x71F5, 0xA05C, 0x71F6, + 0xA05D, 0x71F7, 0xA05E, 0x71F8, 0xA05F, 0x71FA, 0xA060, 0x71FB, 0xA061, 0x71FC, 0xA062, 0x71FD, 0xA063, 0x71FE, 0xA064, 0x71FF, + 0xA065, 0x7200, 0xA066, 0x7201, 0xA067, 0x7202, 0xA068, 0x7203, 0xA069, 0x7204, 0xA06A, 0x7205, 0xA06B, 0x7207, 0xA06C, 0x7208, + 0xA06D, 0x7209, 0xA06E, 0x720A, 0xA06F, 0x720B, 0xA070, 0x720C, 0xA071, 0x720D, 0xA072, 0x720E, 0xA073, 0x720F, 0xA074, 0x7210, + 0xA075, 0x7211, 0xA076, 0x7212, 0xA077, 0x7213, 0xA078, 0x7214, 0xA079, 0x7215, 0xA07A, 0x7216, 0xA07B, 0x7217, 0xA07C, 0x7218, + 0xA07D, 0x7219, 0xA07E, 0x721A, 0xA080, 0x721B, 0xA081, 0x721C, 0xA082, 0x721E, 0xA083, 0x721F, 0xA084, 0x7220, 0xA085, 0x7221, + 0xA086, 0x7222, 0xA087, 0x7223, 0xA088, 0x7224, 0xA089, 0x7225, 0xA08A, 0x7226, 0xA08B, 0x7227, 0xA08C, 0x7229, 0xA08D, 0x722B, + 0xA08E, 0x722D, 0xA08F, 0x722E, 0xA090, 0x722F, 0xA091, 0x7232, 0xA092, 0x7233, 0xA093, 0x7234, 0xA094, 0x723A, 0xA095, 0x723C, + 0xA096, 0x723E, 0xA097, 0x7240, 0xA098, 0x7241, 0xA099, 0x7242, 0xA09A, 0x7243, 0xA09B, 0x7244, 0xA09C, 0x7245, 0xA09D, 0x7246, + 0xA09E, 0x7249, 0xA09F, 0x724A, 0xA0A0, 0x724B, 0xA0A1, 0x724E, 0xA0A2, 0x724F, 0xA0A3, 0x7250, 0xA0A4, 0x7251, 0xA0A5, 0x7253, + 0xA0A6, 0x7254, 0xA0A7, 0x7255, 0xA0A8, 0x7257, 0xA0A9, 0x7258, 0xA0AA, 0x725A, 0xA0AB, 0x725C, 0xA0AC, 0x725E, 0xA0AD, 0x7260, + 0xA0AE, 0x7263, 0xA0AF, 0x7264, 0xA0B0, 0x7265, 0xA0B1, 0x7268, 0xA0B2, 0x726A, 0xA0B3, 0x726B, 0xA0B4, 0x726C, 0xA0B5, 0x726D, + 0xA0B6, 0x7270, 0xA0B7, 0x7271, 0xA0B8, 0x7273, 0xA0B9, 0x7274, 0xA0BA, 0x7276, 0xA0BB, 0x7277, 0xA0BC, 0x7278, 0xA0BD, 0x727B, + 0xA0BE, 0x727C, 0xA0BF, 0x727D, 0xA0C0, 0x7282, 0xA0C1, 0x7283, 0xA0C2, 0x7285, 0xA0C3, 0x7286, 0xA0C4, 0x7287, 0xA0C5, 0x7288, + 0xA0C6, 0x7289, 0xA0C7, 0x728C, 0xA0C8, 0x728E, 0xA0C9, 0x7290, 0xA0CA, 0x7291, 0xA0CB, 0x7293, 0xA0CC, 0x7294, 0xA0CD, 0x7295, + 0xA0CE, 0x7296, 0xA0CF, 0x7297, 0xA0D0, 0x7298, 0xA0D1, 0x7299, 0xA0D2, 0x729A, 0xA0D3, 0x729B, 0xA0D4, 0x729C, 0xA0D5, 0x729D, + 0xA0D6, 0x729E, 0xA0D7, 0x72A0, 0xA0D8, 0x72A1, 0xA0D9, 0x72A2, 0xA0DA, 0x72A3, 0xA0DB, 0x72A4, 0xA0DC, 0x72A5, 0xA0DD, 0x72A6, + 0xA0DE, 0x72A7, 0xA0DF, 0x72A8, 0xA0E0, 0x72A9, 0xA0E1, 0x72AA, 0xA0E2, 0x72AB, 0xA0E3, 0x72AE, 0xA0E4, 0x72B1, 0xA0E5, 0x72B2, + 0xA0E6, 0x72B3, 0xA0E7, 0x72B5, 0xA0E8, 0x72BA, 0xA0E9, 0x72BB, 0xA0EA, 0x72BC, 0xA0EB, 0x72BD, 0xA0EC, 0x72BE, 0xA0ED, 0x72BF, + 0xA0EE, 0x72C0, 0xA0EF, 0x72C5, 0xA0F0, 0x72C6, 0xA0F1, 0x72C7, 0xA0F2, 0x72C9, 0xA0F3, 0x72CA, 0xA0F4, 0x72CB, 0xA0F5, 0x72CC, + 0xA0F6, 0x72CF, 0xA0F7, 0x72D1, 0xA0F8, 0x72D3, 0xA0F9, 0x72D4, 0xA0FA, 0x72D5, 0xA0FB, 0x72D6, 0xA0FC, 0x72D8, 0xA0FD, 0x72DA, + 0xA0FE, 0x72DB, 0xA1A1, 0x3000, 0xA1A2, 0x3001, 0xA1A3, 0x3002, 0xA1A4, 0x00B7, 0xA1A5, 0x02C9, 0xA1A6, 0x02C7, 0xA1A7, 0x00A8, + 0xA1A8, 0x3003, 0xA1A9, 0x3005, 0xA1AA, 0x2014, 0xA1AB, 0xFF5E, 0xA1AC, 0x2016, 0xA1AD, 0x2026, 0xA1AE, 0x2018, 0xA1AF, 0x2019, + 0xA1B0, 0x201C, 0xA1B1, 0x201D, 0xA1B2, 0x3014, 0xA1B3, 0x3015, 0xA1B4, 0x3008, 0xA1B5, 0x3009, 0xA1B6, 0x300A, 0xA1B7, 0x300B, + 0xA1B8, 0x300C, 0xA1B9, 0x300D, 0xA1BA, 0x300E, 0xA1BB, 0x300F, 0xA1BC, 0x3016, 0xA1BD, 0x3017, 0xA1BE, 0x3010, 0xA1BF, 0x3011, + 0xA1C0, 0x00B1, 0xA1C1, 0x00D7, 0xA1C2, 0x00F7, 0xA1C3, 0x2236, 0xA1C4, 0x2227, 0xA1C5, 0x2228, 0xA1C6, 0x2211, 0xA1C7, 0x220F, + 0xA1C8, 0x222A, 0xA1C9, 0x2229, 0xA1CA, 0x2208, 0xA1CB, 0x2237, 0xA1CC, 0x221A, 0xA1CD, 0x22A5, 0xA1CE, 0x2225, 0xA1CF, 0x2220, + 0xA1D0, 0x2312, 0xA1D1, 0x2299, 0xA1D2, 0x222B, 0xA1D3, 0x222E, 0xA1D4, 0x2261, 0xA1D5, 0x224C, 0xA1D6, 0x2248, 0xA1D7, 0x223D, + 0xA1D8, 0x221D, 0xA1D9, 0x2260, 0xA1DA, 0x226E, 0xA1DB, 0x226F, 0xA1DC, 0x2264, 0xA1DD, 0x2265, 0xA1DE, 0x221E, 0xA1DF, 0x2235, + 0xA1E0, 0x2234, 0xA1E1, 0x2642, 0xA1E2, 0x2640, 0xA1E3, 0x00B0, 0xA1E4, 0x2032, 0xA1E5, 0x2033, 0xA1E6, 0x2103, 0xA1E7, 0xFF04, + 0xA1E8, 0x00A4, 0xA1E9, 0xFFE0, 0xA1EA, 0xFFE1, 0xA1EB, 0x2030, 0xA1EC, 0x00A7, 0xA1ED, 0x2116, 0xA1EE, 0x2606, 0xA1EF, 0x2605, + 0xA1F0, 0x25CB, 0xA1F1, 0x25CF, 0xA1F2, 0x25CE, 0xA1F3, 0x25C7, 0xA1F4, 0x25C6, 0xA1F5, 0x25A1, 0xA1F6, 0x25A0, 0xA1F7, 0x25B3, + 0xA1F8, 0x25B2, 0xA1F9, 0x203B, 0xA1FA, 0x2192, 0xA1FB, 0x2190, 0xA1FC, 0x2191, 0xA1FD, 0x2193, 0xA1FE, 0x3013, 0xA2A1, 0x2170, + 0xA2A2, 0x2171, 0xA2A3, 0x2172, 0xA2A4, 0x2173, 0xA2A5, 0x2174, 0xA2A6, 0x2175, 0xA2A7, 0x2176, 0xA2A8, 0x2177, 0xA2A9, 0x2178, + 0xA2AA, 0x2179, 0xA2B1, 0x2488, 0xA2B2, 0x2489, 0xA2B3, 0x248A, 0xA2B4, 0x248B, 0xA2B5, 0x248C, 0xA2B6, 0x248D, 0xA2B7, 0x248E, + 0xA2B8, 0x248F, 0xA2B9, 0x2490, 0xA2BA, 0x2491, 0xA2BB, 0x2492, 0xA2BC, 0x2493, 0xA2BD, 0x2494, 0xA2BE, 0x2495, 0xA2BF, 0x2496, + 0xA2C0, 0x2497, 0xA2C1, 0x2498, 0xA2C2, 0x2499, 0xA2C3, 0x249A, 0xA2C4, 0x249B, 0xA2C5, 0x2474, 0xA2C6, 0x2475, 0xA2C7, 0x2476, + 0xA2C8, 0x2477, 0xA2C9, 0x2478, 0xA2CA, 0x2479, 0xA2CB, 0x247A, 0xA2CC, 0x247B, 0xA2CD, 0x247C, 0xA2CE, 0x247D, 0xA2CF, 0x247E, + 0xA2D0, 0x247F, 0xA2D1, 0x2480, 0xA2D2, 0x2481, 0xA2D3, 0x2482, 0xA2D4, 0x2483, 0xA2D5, 0x2484, 0xA2D6, 0x2485, 0xA2D7, 0x2486, + 0xA2D8, 0x2487, 0xA2D9, 0x2460, 0xA2DA, 0x2461, 0xA2DB, 0x2462, 0xA2DC, 0x2463, 0xA2DD, 0x2464, 0xA2DE, 0x2465, 0xA2DF, 0x2466, + 0xA2E0, 0x2467, 0xA2E1, 0x2468, 0xA2E2, 0x2469, 0xA2E5, 0x3220, 0xA2E6, 0x3221, 0xA2E7, 0x3222, 0xA2E8, 0x3223, 0xA2E9, 0x3224, + 0xA2EA, 0x3225, 0xA2EB, 0x3226, 0xA2EC, 0x3227, 0xA2ED, 0x3228, 0xA2EE, 0x3229, 0xA2F1, 0x2160, 0xA2F2, 0x2161, 0xA2F3, 0x2162, + 0xA2F4, 0x2163, 0xA2F5, 0x2164, 0xA2F6, 0x2165, 0xA2F7, 0x2166, 0xA2F8, 0x2167, 0xA2F9, 0x2168, 0xA2FA, 0x2169, 0xA2FB, 0x216A, + 0xA2FC, 0x216B, 0xA3A1, 0xFF01, 0xA3A2, 0xFF02, 0xA3A3, 0xFF03, 0xA3A4, 0xFFE5, 0xA3A5, 0xFF05, 0xA3A6, 0xFF06, 0xA3A7, 0xFF07, + 0xA3A8, 0xFF08, 0xA3A9, 0xFF09, 0xA3AA, 0xFF0A, 0xA3AB, 0xFF0B, 0xA3AC, 0xFF0C, 0xA3AD, 0xFF0D, 0xA3AE, 0xFF0E, 0xA3AF, 0xFF0F, + 0xA3B0, 0xFF10, 0xA3B1, 0xFF11, 0xA3B2, 0xFF12, 0xA3B3, 0xFF13, 0xA3B4, 0xFF14, 0xA3B5, 0xFF15, 0xA3B6, 0xFF16, 0xA3B7, 0xFF17, + 0xA3B8, 0xFF18, 0xA3B9, 0xFF19, 0xA3BA, 0xFF1A, 0xA3BB, 0xFF1B, 0xA3BC, 0xFF1C, 0xA3BD, 0xFF1D, 0xA3BE, 0xFF1E, 0xA3BF, 0xFF1F, + 0xA3C0, 0xFF20, 0xA3C1, 0xFF21, 0xA3C2, 0xFF22, 0xA3C3, 0xFF23, 0xA3C4, 0xFF24, 0xA3C5, 0xFF25, 0xA3C6, 0xFF26, 0xA3C7, 0xFF27, + 0xA3C8, 0xFF28, 0xA3C9, 0xFF29, 0xA3CA, 0xFF2A, 0xA3CB, 0xFF2B, 0xA3CC, 0xFF2C, 0xA3CD, 0xFF2D, 0xA3CE, 0xFF2E, 0xA3CF, 0xFF2F, + 0xA3D0, 0xFF30, 0xA3D1, 0xFF31, 0xA3D2, 0xFF32, 0xA3D3, 0xFF33, 0xA3D4, 0xFF34, 0xA3D5, 0xFF35, 0xA3D6, 0xFF36, 0xA3D7, 0xFF37, + 0xA3D8, 0xFF38, 0xA3D9, 0xFF39, 0xA3DA, 0xFF3A, 0xA3DB, 0xFF3B, 0xA3DC, 0xFF3C, 0xA3DD, 0xFF3D, 0xA3DE, 0xFF3E, 0xA3DF, 0xFF3F, + 0xA3E0, 0xFF40, 0xA3E1, 0xFF41, 0xA3E2, 0xFF42, 0xA3E3, 0xFF43, 0xA3E4, 0xFF44, 0xA3E5, 0xFF45, 0xA3E6, 0xFF46, 0xA3E7, 0xFF47, + 0xA3E8, 0xFF48, 0xA3E9, 0xFF49, 0xA3EA, 0xFF4A, 0xA3EB, 0xFF4B, 0xA3EC, 0xFF4C, 0xA3ED, 0xFF4D, 0xA3EE, 0xFF4E, 0xA3EF, 0xFF4F, + 0xA3F0, 0xFF50, 0xA3F1, 0xFF51, 0xA3F2, 0xFF52, 0xA3F3, 0xFF53, 0xA3F4, 0xFF54, 0xA3F5, 0xFF55, 0xA3F6, 0xFF56, 0xA3F7, 0xFF57, + 0xA3F8, 0xFF58, 0xA3F9, 0xFF59, 0xA3FA, 0xFF5A, 0xA3FB, 0xFF5B, 0xA3FC, 0xFF5C, 0xA3FD, 0xFF5D, 0xA3FE, 0xFFE3, 0xA4A1, 0x3041, + 0xA4A2, 0x3042, 0xA4A3, 0x3043, 0xA4A4, 0x3044, 0xA4A5, 0x3045, 0xA4A6, 0x3046, 0xA4A7, 0x3047, 0xA4A8, 0x3048, 0xA4A9, 0x3049, + 0xA4AA, 0x304A, 0xA4AB, 0x304B, 0xA4AC, 0x304C, 0xA4AD, 0x304D, 0xA4AE, 0x304E, 0xA4AF, 0x304F, 0xA4B0, 0x3050, 0xA4B1, 0x3051, + 0xA4B2, 0x3052, 0xA4B3, 0x3053, 0xA4B4, 0x3054, 0xA4B5, 0x3055, 0xA4B6, 0x3056, 0xA4B7, 0x3057, 0xA4B8, 0x3058, 0xA4B9, 0x3059, + 0xA4BA, 0x305A, 0xA4BB, 0x305B, 0xA4BC, 0x305C, 0xA4BD, 0x305D, 0xA4BE, 0x305E, 0xA4BF, 0x305F, 0xA4C0, 0x3060, 0xA4C1, 0x3061, + 0xA4C2, 0x3062, 0xA4C3, 0x3063, 0xA4C4, 0x3064, 0xA4C5, 0x3065, 0xA4C6, 0x3066, 0xA4C7, 0x3067, 0xA4C8, 0x3068, 0xA4C9, 0x3069, + 0xA4CA, 0x306A, 0xA4CB, 0x306B, 0xA4CC, 0x306C, 0xA4CD, 0x306D, 0xA4CE, 0x306E, 0xA4CF, 0x306F, 0xA4D0, 0x3070, 0xA4D1, 0x3071, + 0xA4D2, 0x3072, 0xA4D3, 0x3073, 0xA4D4, 0x3074, 0xA4D5, 0x3075, 0xA4D6, 0x3076, 0xA4D7, 0x3077, 0xA4D8, 0x3078, 0xA4D9, 0x3079, + 0xA4DA, 0x307A, 0xA4DB, 0x307B, 0xA4DC, 0x307C, 0xA4DD, 0x307D, 0xA4DE, 0x307E, 0xA4DF, 0x307F, 0xA4E0, 0x3080, 0xA4E1, 0x3081, + 0xA4E2, 0x3082, 0xA4E3, 0x3083, 0xA4E4, 0x3084, 0xA4E5, 0x3085, 0xA4E6, 0x3086, 0xA4E7, 0x3087, 0xA4E8, 0x3088, 0xA4E9, 0x3089, + 0xA4EA, 0x308A, 0xA4EB, 0x308B, 0xA4EC, 0x308C, 0xA4ED, 0x308D, 0xA4EE, 0x308E, 0xA4EF, 0x308F, 0xA4F0, 0x3090, 0xA4F1, 0x3091, + 0xA4F2, 0x3092, 0xA4F3, 0x3093, 0xA5A1, 0x30A1, 0xA5A2, 0x30A2, 0xA5A3, 0x30A3, 0xA5A4, 0x30A4, 0xA5A5, 0x30A5, 0xA5A6, 0x30A6, + 0xA5A7, 0x30A7, 0xA5A8, 0x30A8, 0xA5A9, 0x30A9, 0xA5AA, 0x30AA, 0xA5AB, 0x30AB, 0xA5AC, 0x30AC, 0xA5AD, 0x30AD, 0xA5AE, 0x30AE, + 0xA5AF, 0x30AF, 0xA5B0, 0x30B0, 0xA5B1, 0x30B1, 0xA5B2, 0x30B2, 0xA5B3, 0x30B3, 0xA5B4, 0x30B4, 0xA5B5, 0x30B5, 0xA5B6, 0x30B6, + 0xA5B7, 0x30B7, 0xA5B8, 0x30B8, 0xA5B9, 0x30B9, 0xA5BA, 0x30BA, 0xA5BB, 0x30BB, 0xA5BC, 0x30BC, 0xA5BD, 0x30BD, 0xA5BE, 0x30BE, + 0xA5BF, 0x30BF, 0xA5C0, 0x30C0, 0xA5C1, 0x30C1, 0xA5C2, 0x30C2, 0xA5C3, 0x30C3, 0xA5C4, 0x30C4, 0xA5C5, 0x30C5, 0xA5C6, 0x30C6, + 0xA5C7, 0x30C7, 0xA5C8, 0x30C8, 0xA5C9, 0x30C9, 0xA5CA, 0x30CA, 0xA5CB, 0x30CB, 0xA5CC, 0x30CC, 0xA5CD, 0x30CD, 0xA5CE, 0x30CE, + 0xA5CF, 0x30CF, 0xA5D0, 0x30D0, 0xA5D1, 0x30D1, 0xA5D2, 0x30D2, 0xA5D3, 0x30D3, 0xA5D4, 0x30D4, 0xA5D5, 0x30D5, 0xA5D6, 0x30D6, + 0xA5D7, 0x30D7, 0xA5D8, 0x30D8, 0xA5D9, 0x30D9, 0xA5DA, 0x30DA, 0xA5DB, 0x30DB, 0xA5DC, 0x30DC, 0xA5DD, 0x30DD, 0xA5DE, 0x30DE, + 0xA5DF, 0x30DF, 0xA5E0, 0x30E0, 0xA5E1, 0x30E1, 0xA5E2, 0x30E2, 0xA5E3, 0x30E3, 0xA5E4, 0x30E4, 0xA5E5, 0x30E5, 0xA5E6, 0x30E6, + 0xA5E7, 0x30E7, 0xA5E8, 0x30E8, 0xA5E9, 0x30E9, 0xA5EA, 0x30EA, 0xA5EB, 0x30EB, 0xA5EC, 0x30EC, 0xA5ED, 0x30ED, 0xA5EE, 0x30EE, + 0xA5EF, 0x30EF, 0xA5F0, 0x30F0, 0xA5F1, 0x30F1, 0xA5F2, 0x30F2, 0xA5F3, 0x30F3, 0xA5F4, 0x30F4, 0xA5F5, 0x30F5, 0xA5F6, 0x30F6, + 0xA6A1, 0x0391, 0xA6A2, 0x0392, 0xA6A3, 0x0393, 0xA6A4, 0x0394, 0xA6A5, 0x0395, 0xA6A6, 0x0396, 0xA6A7, 0x0397, 0xA6A8, 0x0398, + 0xA6A9, 0x0399, 0xA6AA, 0x039A, 0xA6AB, 0x039B, 0xA6AC, 0x039C, 0xA6AD, 0x039D, 0xA6AE, 0x039E, 0xA6AF, 0x039F, 0xA6B0, 0x03A0, + 0xA6B1, 0x03A1, 0xA6B2, 0x03A3, 0xA6B3, 0x03A4, 0xA6B4, 0x03A5, 0xA6B5, 0x03A6, 0xA6B6, 0x03A7, 0xA6B7, 0x03A8, 0xA6B8, 0x03A9, + 0xA6C1, 0x03B1, 0xA6C2, 0x03B2, 0xA6C3, 0x03B3, 0xA6C4, 0x03B4, 0xA6C5, 0x03B5, 0xA6C6, 0x03B6, 0xA6C7, 0x03B7, 0xA6C8, 0x03B8, + 0xA6C9, 0x03B9, 0xA6CA, 0x03BA, 0xA6CB, 0x03BB, 0xA6CC, 0x03BC, 0xA6CD, 0x03BD, 0xA6CE, 0x03BE, 0xA6CF, 0x03BF, 0xA6D0, 0x03C0, + 0xA6D1, 0x03C1, 0xA6D2, 0x03C3, 0xA6D3, 0x03C4, 0xA6D4, 0x03C5, 0xA6D5, 0x03C6, 0xA6D6, 0x03C7, 0xA6D7, 0x03C8, 0xA6D8, 0x03C9, + 0xA6E0, 0xFE35, 0xA6E1, 0xFE36, 0xA6E2, 0xFE39, 0xA6E3, 0xFE3A, 0xA6E4, 0xFE3F, 0xA6E5, 0xFE40, 0xA6E6, 0xFE3D, 0xA6E7, 0xFE3E, + 0xA6E8, 0xFE41, 0xA6E9, 0xFE42, 0xA6EA, 0xFE43, 0xA6EB, 0xFE44, 0xA6EE, 0xFE3B, 0xA6EF, 0xFE3C, 0xA6F0, 0xFE37, 0xA6F1, 0xFE38, + 0xA6F2, 0xFE31, 0xA6F4, 0xFE33, 0xA6F5, 0xFE34, 0xA7A1, 0x0410, 0xA7A2, 0x0411, 0xA7A3, 0x0412, 0xA7A4, 0x0413, 0xA7A5, 0x0414, + 0xA7A6, 0x0415, 0xA7A7, 0x0401, 0xA7A8, 0x0416, 0xA7A9, 0x0417, 0xA7AA, 0x0418, 0xA7AB, 0x0419, 0xA7AC, 0x041A, 0xA7AD, 0x041B, + 0xA7AE, 0x041C, 0xA7AF, 0x041D, 0xA7B0, 0x041E, 0xA7B1, 0x041F, 0xA7B2, 0x0420, 0xA7B3, 0x0421, 0xA7B4, 0x0422, 0xA7B5, 0x0423, + 0xA7B6, 0x0424, 0xA7B7, 0x0425, 0xA7B8, 0x0426, 0xA7B9, 0x0427, 0xA7BA, 0x0428, 0xA7BB, 0x0429, 0xA7BC, 0x042A, 0xA7BD, 0x042B, + 0xA7BE, 0x042C, 0xA7BF, 0x042D, 0xA7C0, 0x042E, 0xA7C1, 0x042F, 0xA7D1, 0x0430, 0xA7D2, 0x0431, 0xA7D3, 0x0432, 0xA7D4, 0x0433, + 0xA7D5, 0x0434, 0xA7D6, 0x0435, 0xA7D7, 0x0451, 0xA7D8, 0x0436, 0xA7D9, 0x0437, 0xA7DA, 0x0438, 0xA7DB, 0x0439, 0xA7DC, 0x043A, + 0xA7DD, 0x043B, 0xA7DE, 0x043C, 0xA7DF, 0x043D, 0xA7E0, 0x043E, 0xA7E1, 0x043F, 0xA7E2, 0x0440, 0xA7E3, 0x0441, 0xA7E4, 0x0442, + 0xA7E5, 0x0443, 0xA7E6, 0x0444, 0xA7E7, 0x0445, 0xA7E8, 0x0446, 0xA7E9, 0x0447, 0xA7EA, 0x0448, 0xA7EB, 0x0449, 0xA7EC, 0x044A, + 0xA7ED, 0x044B, 0xA7EE, 0x044C, 0xA7EF, 0x044D, 0xA7F0, 0x044E, 0xA7F1, 0x044F, 0xA840, 0x02CA, 0xA841, 0x02CB, 0xA842, 0x02D9, + 0xA843, 0x2013, 0xA844, 0x2015, 0xA845, 0x2025, 0xA846, 0x2035, 0xA847, 0x2105, 0xA848, 0x2109, 0xA849, 0x2196, 0xA84A, 0x2197, + 0xA84B, 0x2198, 0xA84C, 0x2199, 0xA84D, 0x2215, 0xA84E, 0x221F, 0xA84F, 0x2223, 0xA850, 0x2252, 0xA851, 0x2266, 0xA852, 0x2267, + 0xA853, 0x22BF, 0xA854, 0x2550, 0xA855, 0x2551, 0xA856, 0x2552, 0xA857, 0x2553, 0xA858, 0x2554, 0xA859, 0x2555, 0xA85A, 0x2556, + 0xA85B, 0x2557, 0xA85C, 0x2558, 0xA85D, 0x2559, 0xA85E, 0x255A, 0xA85F, 0x255B, 0xA860, 0x255C, 0xA861, 0x255D, 0xA862, 0x255E, + 0xA863, 0x255F, 0xA864, 0x2560, 0xA865, 0x2561, 0xA866, 0x2562, 0xA867, 0x2563, 0xA868, 0x2564, 0xA869, 0x2565, 0xA86A, 0x2566, + 0xA86B, 0x2567, 0xA86C, 0x2568, 0xA86D, 0x2569, 0xA86E, 0x256A, 0xA86F, 0x256B, 0xA870, 0x256C, 0xA871, 0x256D, 0xA872, 0x256E, + 0xA873, 0x256F, 0xA874, 0x2570, 0xA875, 0x2571, 0xA876, 0x2572, 0xA877, 0x2573, 0xA878, 0x2581, 0xA879, 0x2582, 0xA87A, 0x2583, + 0xA87B, 0x2584, 0xA87C, 0x2585, 0xA87D, 0x2586, 0xA87E, 0x2587, 0xA880, 0x2588, 0xA881, 0x2589, 0xA882, 0x258A, 0xA883, 0x258B, + 0xA884, 0x258C, 0xA885, 0x258D, 0xA886, 0x258E, 0xA887, 0x258F, 0xA888, 0x2593, 0xA889, 0x2594, 0xA88A, 0x2595, 0xA88B, 0x25BC, + 0xA88C, 0x25BD, 0xA88D, 0x25E2, 0xA88E, 0x25E3, 0xA88F, 0x25E4, 0xA890, 0x25E5, 0xA891, 0x2609, 0xA892, 0x2295, 0xA893, 0x3012, + 0xA894, 0x301D, 0xA895, 0x301E, 0xA8A1, 0x0101, 0xA8A2, 0x00E1, 0xA8A3, 0x01CE, 0xA8A4, 0x00E0, 0xA8A5, 0x0113, 0xA8A6, 0x00E9, + 0xA8A7, 0x011B, 0xA8A8, 0x00E8, 0xA8A9, 0x012B, 0xA8AA, 0x00ED, 0xA8AB, 0x01D0, 0xA8AC, 0x00EC, 0xA8AD, 0x014D, 0xA8AE, 0x00F3, + 0xA8AF, 0x01D2, 0xA8B0, 0x00F2, 0xA8B1, 0x016B, 0xA8B2, 0x00FA, 0xA8B3, 0x01D4, 0xA8B4, 0x00F9, 0xA8B5, 0x01D6, 0xA8B6, 0x01D8, + 0xA8B7, 0x01DA, 0xA8B8, 0x01DC, 0xA8B9, 0x00FC, 0xA8BA, 0x00EA, 0xA8BB, 0x0251, 0xA8BD, 0x0144, 0xA8BE, 0x0148, 0xA8C0, 0x0261, + 0xA8C5, 0x3105, 0xA8C6, 0x3106, 0xA8C7, 0x3107, 0xA8C8, 0x3108, 0xA8C9, 0x3109, 0xA8CA, 0x310A, 0xA8CB, 0x310B, 0xA8CC, 0x310C, + 0xA8CD, 0x310D, 0xA8CE, 0x310E, 0xA8CF, 0x310F, 0xA8D0, 0x3110, 0xA8D1, 0x3111, 0xA8D2, 0x3112, 0xA8D3, 0x3113, 0xA8D4, 0x3114, + 0xA8D5, 0x3115, 0xA8D6, 0x3116, 0xA8D7, 0x3117, 0xA8D8, 0x3118, 0xA8D9, 0x3119, 0xA8DA, 0x311A, 0xA8DB, 0x311B, 0xA8DC, 0x311C, + 0xA8DD, 0x311D, 0xA8DE, 0x311E, 0xA8DF, 0x311F, 0xA8E0, 0x3120, 0xA8E1, 0x3121, 0xA8E2, 0x3122, 0xA8E3, 0x3123, 0xA8E4, 0x3124, + 0xA8E5, 0x3125, 0xA8E6, 0x3126, 0xA8E7, 0x3127, 0xA8E8, 0x3128, 0xA8E9, 0x3129, 0xA940, 0x3021, 0xA941, 0x3022, 0xA942, 0x3023, + 0xA943, 0x3024, 0xA944, 0x3025, 0xA945, 0x3026, 0xA946, 0x3027, 0xA947, 0x3028, 0xA948, 0x3029, 0xA949, 0x32A3, 0xA94A, 0x338E, + 0xA94B, 0x338F, 0xA94C, 0x339C, 0xA94D, 0x339D, 0xA94E, 0x339E, 0xA94F, 0x33A1, 0xA950, 0x33C4, 0xA951, 0x33CE, 0xA952, 0x33D1, + 0xA953, 0x33D2, 0xA954, 0x33D5, 0xA955, 0xFE30, 0xA956, 0xFFE2, 0xA957, 0xFFE4, 0xA959, 0x2121, 0xA95A, 0x3231, 0xA95C, 0x2010, + 0xA960, 0x30FC, 0xA961, 0x309B, 0xA962, 0x309C, 0xA963, 0x30FD, 0xA964, 0x30FE, 0xA965, 0x3006, 0xA966, 0x309D, 0xA967, 0x309E, + 0xA968, 0xFE49, 0xA969, 0xFE4A, 0xA96A, 0xFE4B, 0xA96B, 0xFE4C, 0xA96C, 0xFE4D, 0xA96D, 0xFE4E, 0xA96E, 0xFE4F, 0xA96F, 0xFE50, + 0xA970, 0xFE51, 0xA971, 0xFE52, 0xA972, 0xFE54, 0xA973, 0xFE55, 0xA974, 0xFE56, 0xA975, 0xFE57, 0xA976, 0xFE59, 0xA977, 0xFE5A, + 0xA978, 0xFE5B, 0xA979, 0xFE5C, 0xA97A, 0xFE5D, 0xA97B, 0xFE5E, 0xA97C, 0xFE5F, 0xA97D, 0xFE60, 0xA97E, 0xFE61, 0xA980, 0xFE62, + 0xA981, 0xFE63, 0xA982, 0xFE64, 0xA983, 0xFE65, 0xA984, 0xFE66, 0xA985, 0xFE68, 0xA986, 0xFE69, 0xA987, 0xFE6A, 0xA988, 0xFE6B, + 0xA996, 0x3007, 0xA9A4, 0x2500, 0xA9A5, 0x2501, 0xA9A6, 0x2502, 0xA9A7, 0x2503, 0xA9A8, 0x2504, 0xA9A9, 0x2505, 0xA9AA, 0x2506, + 0xA9AB, 0x2507, 0xA9AC, 0x2508, 0xA9AD, 0x2509, 0xA9AE, 0x250A, 0xA9AF, 0x250B, 0xA9B0, 0x250C, 0xA9B1, 0x250D, 0xA9B2, 0x250E, + 0xA9B3, 0x250F, 0xA9B4, 0x2510, 0xA9B5, 0x2511, 0xA9B6, 0x2512, 0xA9B7, 0x2513, 0xA9B8, 0x2514, 0xA9B9, 0x2515, 0xA9BA, 0x2516, + 0xA9BB, 0x2517, 0xA9BC, 0x2518, 0xA9BD, 0x2519, 0xA9BE, 0x251A, 0xA9BF, 0x251B, 0xA9C0, 0x251C, 0xA9C1, 0x251D, 0xA9C2, 0x251E, + 0xA9C3, 0x251F, 0xA9C4, 0x2520, 0xA9C5, 0x2521, 0xA9C6, 0x2522, 0xA9C7, 0x2523, 0xA9C8, 0x2524, 0xA9C9, 0x2525, 0xA9CA, 0x2526, + 0xA9CB, 0x2527, 0xA9CC, 0x2528, 0xA9CD, 0x2529, 0xA9CE, 0x252A, 0xA9CF, 0x252B, 0xA9D0, 0x252C, 0xA9D1, 0x252D, 0xA9D2, 0x252E, + 0xA9D3, 0x252F, 0xA9D4, 0x2530, 0xA9D5, 0x2531, 0xA9D6, 0x2532, 0xA9D7, 0x2533, 0xA9D8, 0x2534, 0xA9D9, 0x2535, 0xA9DA, 0x2536, + 0xA9DB, 0x2537, 0xA9DC, 0x2538, 0xA9DD, 0x2539, 0xA9DE, 0x253A, 0xA9DF, 0x253B, 0xA9E0, 0x253C, 0xA9E1, 0x253D, 0xA9E2, 0x253E, + 0xA9E3, 0x253F, 0xA9E4, 0x2540, 0xA9E5, 0x2541, 0xA9E6, 0x2542, 0xA9E7, 0x2543, 0xA9E8, 0x2544, 0xA9E9, 0x2545, 0xA9EA, 0x2546, + 0xA9EB, 0x2547, 0xA9EC, 0x2548, 0xA9ED, 0x2549, 0xA9EE, 0x254A, 0xA9EF, 0x254B, 0xAA40, 0x72DC, 0xAA41, 0x72DD, 0xAA42, 0x72DF, + 0xAA43, 0x72E2, 0xAA44, 0x72E3, 0xAA45, 0x72E4, 0xAA46, 0x72E5, 0xAA47, 0x72E6, 0xAA48, 0x72E7, 0xAA49, 0x72EA, 0xAA4A, 0x72EB, + 0xAA4B, 0x72F5, 0xAA4C, 0x72F6, 0xAA4D, 0x72F9, 0xAA4E, 0x72FD, 0xAA4F, 0x72FE, 0xAA50, 0x72FF, 0xAA51, 0x7300, 0xAA52, 0x7302, + 0xAA53, 0x7304, 0xAA54, 0x7305, 0xAA55, 0x7306, 0xAA56, 0x7307, 0xAA57, 0x7308, 0xAA58, 0x7309, 0xAA59, 0x730B, 0xAA5A, 0x730C, + 0xAA5B, 0x730D, 0xAA5C, 0x730F, 0xAA5D, 0x7310, 0xAA5E, 0x7311, 0xAA5F, 0x7312, 0xAA60, 0x7314, 0xAA61, 0x7318, 0xAA62, 0x7319, + 0xAA63, 0x731A, 0xAA64, 0x731F, 0xAA65, 0x7320, 0xAA66, 0x7323, 0xAA67, 0x7324, 0xAA68, 0x7326, 0xAA69, 0x7327, 0xAA6A, 0x7328, + 0xAA6B, 0x732D, 0xAA6C, 0x732F, 0xAA6D, 0x7330, 0xAA6E, 0x7332, 0xAA6F, 0x7333, 0xAA70, 0x7335, 0xAA71, 0x7336, 0xAA72, 0x733A, + 0xAA73, 0x733B, 0xAA74, 0x733C, 0xAA75, 0x733D, 0xAA76, 0x7340, 0xAA77, 0x7341, 0xAA78, 0x7342, 0xAA79, 0x7343, 0xAA7A, 0x7344, + 0xAA7B, 0x7345, 0xAA7C, 0x7346, 0xAA7D, 0x7347, 0xAA7E, 0x7348, 0xAA80, 0x7349, 0xAA81, 0x734A, 0xAA82, 0x734B, 0xAA83, 0x734C, + 0xAA84, 0x734E, 0xAA85, 0x734F, 0xAA86, 0x7351, 0xAA87, 0x7353, 0xAA88, 0x7354, 0xAA89, 0x7355, 0xAA8A, 0x7356, 0xAA8B, 0x7358, + 0xAA8C, 0x7359, 0xAA8D, 0x735A, 0xAA8E, 0x735B, 0xAA8F, 0x735C, 0xAA90, 0x735D, 0xAA91, 0x735E, 0xAA92, 0x735F, 0xAA93, 0x7361, + 0xAA94, 0x7362, 0xAA95, 0x7363, 0xAA96, 0x7364, 0xAA97, 0x7365, 0xAA98, 0x7366, 0xAA99, 0x7367, 0xAA9A, 0x7368, 0xAA9B, 0x7369, + 0xAA9C, 0x736A, 0xAA9D, 0x736B, 0xAA9E, 0x736E, 0xAA9F, 0x7370, 0xAAA0, 0x7371, 0xAB40, 0x7372, 0xAB41, 0x7373, 0xAB42, 0x7374, + 0xAB43, 0x7375, 0xAB44, 0x7376, 0xAB45, 0x7377, 0xAB46, 0x7378, 0xAB47, 0x7379, 0xAB48, 0x737A, 0xAB49, 0x737B, 0xAB4A, 0x737C, + 0xAB4B, 0x737D, 0xAB4C, 0x737F, 0xAB4D, 0x7380, 0xAB4E, 0x7381, 0xAB4F, 0x7382, 0xAB50, 0x7383, 0xAB51, 0x7385, 0xAB52, 0x7386, + 0xAB53, 0x7388, 0xAB54, 0x738A, 0xAB55, 0x738C, 0xAB56, 0x738D, 0xAB57, 0x738F, 0xAB58, 0x7390, 0xAB59, 0x7392, 0xAB5A, 0x7393, + 0xAB5B, 0x7394, 0xAB5C, 0x7395, 0xAB5D, 0x7397, 0xAB5E, 0x7398, 0xAB5F, 0x7399, 0xAB60, 0x739A, 0xAB61, 0x739C, 0xAB62, 0x739D, + 0xAB63, 0x739E, 0xAB64, 0x73A0, 0xAB65, 0x73A1, 0xAB66, 0x73A3, 0xAB67, 0x73A4, 0xAB68, 0x73A5, 0xAB69, 0x73A6, 0xAB6A, 0x73A7, + 0xAB6B, 0x73A8, 0xAB6C, 0x73AA, 0xAB6D, 0x73AC, 0xAB6E, 0x73AD, 0xAB6F, 0x73B1, 0xAB70, 0x73B4, 0xAB71, 0x73B5, 0xAB72, 0x73B6, + 0xAB73, 0x73B8, 0xAB74, 0x73B9, 0xAB75, 0x73BC, 0xAB76, 0x73BD, 0xAB77, 0x73BE, 0xAB78, 0x73BF, 0xAB79, 0x73C1, 0xAB7A, 0x73C3, + 0xAB7B, 0x73C4, 0xAB7C, 0x73C5, 0xAB7D, 0x73C6, 0xAB7E, 0x73C7, 0xAB80, 0x73CB, 0xAB81, 0x73CC, 0xAB82, 0x73CE, 0xAB83, 0x73D2, + 0xAB84, 0x73D3, 0xAB85, 0x73D4, 0xAB86, 0x73D5, 0xAB87, 0x73D6, 0xAB88, 0x73D7, 0xAB89, 0x73D8, 0xAB8A, 0x73DA, 0xAB8B, 0x73DB, + 0xAB8C, 0x73DC, 0xAB8D, 0x73DD, 0xAB8E, 0x73DF, 0xAB8F, 0x73E1, 0xAB90, 0x73E2, 0xAB91, 0x73E3, 0xAB92, 0x73E4, 0xAB93, 0x73E6, + 0xAB94, 0x73E8, 0xAB95, 0x73EA, 0xAB96, 0x73EB, 0xAB97, 0x73EC, 0xAB98, 0x73EE, 0xAB99, 0x73EF, 0xAB9A, 0x73F0, 0xAB9B, 0x73F1, + 0xAB9C, 0x73F3, 0xAB9D, 0x73F4, 0xAB9E, 0x73F5, 0xAB9F, 0x73F6, 0xABA0, 0x73F7, 0xAC40, 0x73F8, 0xAC41, 0x73F9, 0xAC42, 0x73FA, + 0xAC43, 0x73FB, 0xAC44, 0x73FC, 0xAC45, 0x73FD, 0xAC46, 0x73FE, 0xAC47, 0x73FF, 0xAC48, 0x7400, 0xAC49, 0x7401, 0xAC4A, 0x7402, + 0xAC4B, 0x7404, 0xAC4C, 0x7407, 0xAC4D, 0x7408, 0xAC4E, 0x740B, 0xAC4F, 0x740C, 0xAC50, 0x740D, 0xAC51, 0x740E, 0xAC52, 0x7411, + 0xAC53, 0x7412, 0xAC54, 0x7413, 0xAC55, 0x7414, 0xAC56, 0x7415, 0xAC57, 0x7416, 0xAC58, 0x7417, 0xAC59, 0x7418, 0xAC5A, 0x7419, + 0xAC5B, 0x741C, 0xAC5C, 0x741D, 0xAC5D, 0x741E, 0xAC5E, 0x741F, 0xAC5F, 0x7420, 0xAC60, 0x7421, 0xAC61, 0x7423, 0xAC62, 0x7424, + 0xAC63, 0x7427, 0xAC64, 0x7429, 0xAC65, 0x742B, 0xAC66, 0x742D, 0xAC67, 0x742F, 0xAC68, 0x7431, 0xAC69, 0x7432, 0xAC6A, 0x7437, + 0xAC6B, 0x7438, 0xAC6C, 0x7439, 0xAC6D, 0x743A, 0xAC6E, 0x743B, 0xAC6F, 0x743D, 0xAC70, 0x743E, 0xAC71, 0x743F, 0xAC72, 0x7440, + 0xAC73, 0x7442, 0xAC74, 0x7443, 0xAC75, 0x7444, 0xAC76, 0x7445, 0xAC77, 0x7446, 0xAC78, 0x7447, 0xAC79, 0x7448, 0xAC7A, 0x7449, + 0xAC7B, 0x744A, 0xAC7C, 0x744B, 0xAC7D, 0x744C, 0xAC7E, 0x744D, 0xAC80, 0x744E, 0xAC81, 0x744F, 0xAC82, 0x7450, 0xAC83, 0x7451, + 0xAC84, 0x7452, 0xAC85, 0x7453, 0xAC86, 0x7454, 0xAC87, 0x7456, 0xAC88, 0x7458, 0xAC89, 0x745D, 0xAC8A, 0x7460, 0xAC8B, 0x7461, + 0xAC8C, 0x7462, 0xAC8D, 0x7463, 0xAC8E, 0x7464, 0xAC8F, 0x7465, 0xAC90, 0x7466, 0xAC91, 0x7467, 0xAC92, 0x7468, 0xAC93, 0x7469, + 0xAC94, 0x746A, 0xAC95, 0x746B, 0xAC96, 0x746C, 0xAC97, 0x746E, 0xAC98, 0x746F, 0xAC99, 0x7471, 0xAC9A, 0x7472, 0xAC9B, 0x7473, + 0xAC9C, 0x7474, 0xAC9D, 0x7475, 0xAC9E, 0x7478, 0xAC9F, 0x7479, 0xACA0, 0x747A, 0xAD40, 0x747B, 0xAD41, 0x747C, 0xAD42, 0x747D, + 0xAD43, 0x747F, 0xAD44, 0x7482, 0xAD45, 0x7484, 0xAD46, 0x7485, 0xAD47, 0x7486, 0xAD48, 0x7488, 0xAD49, 0x7489, 0xAD4A, 0x748A, + 0xAD4B, 0x748C, 0xAD4C, 0x748D, 0xAD4D, 0x748F, 0xAD4E, 0x7491, 0xAD4F, 0x7492, 0xAD50, 0x7493, 0xAD51, 0x7494, 0xAD52, 0x7495, + 0xAD53, 0x7496, 0xAD54, 0x7497, 0xAD55, 0x7498, 0xAD56, 0x7499, 0xAD57, 0x749A, 0xAD58, 0x749B, 0xAD59, 0x749D, 0xAD5A, 0x749F, + 0xAD5B, 0x74A0, 0xAD5C, 0x74A1, 0xAD5D, 0x74A2, 0xAD5E, 0x74A3, 0xAD5F, 0x74A4, 0xAD60, 0x74A5, 0xAD61, 0x74A6, 0xAD62, 0x74AA, + 0xAD63, 0x74AB, 0xAD64, 0x74AC, 0xAD65, 0x74AD, 0xAD66, 0x74AE, 0xAD67, 0x74AF, 0xAD68, 0x74B0, 0xAD69, 0x74B1, 0xAD6A, 0x74B2, + 0xAD6B, 0x74B3, 0xAD6C, 0x74B4, 0xAD6D, 0x74B5, 0xAD6E, 0x74B6, 0xAD6F, 0x74B7, 0xAD70, 0x74B8, 0xAD71, 0x74B9, 0xAD72, 0x74BB, + 0xAD73, 0x74BC, 0xAD74, 0x74BD, 0xAD75, 0x74BE, 0xAD76, 0x74BF, 0xAD77, 0x74C0, 0xAD78, 0x74C1, 0xAD79, 0x74C2, 0xAD7A, 0x74C3, + 0xAD7B, 0x74C4, 0xAD7C, 0x74C5, 0xAD7D, 0x74C6, 0xAD7E, 0x74C7, 0xAD80, 0x74C8, 0xAD81, 0x74C9, 0xAD82, 0x74CA, 0xAD83, 0x74CB, + 0xAD84, 0x74CC, 0xAD85, 0x74CD, 0xAD86, 0x74CE, 0xAD87, 0x74CF, 0xAD88, 0x74D0, 0xAD89, 0x74D1, 0xAD8A, 0x74D3, 0xAD8B, 0x74D4, + 0xAD8C, 0x74D5, 0xAD8D, 0x74D6, 0xAD8E, 0x74D7, 0xAD8F, 0x74D8, 0xAD90, 0x74D9, 0xAD91, 0x74DA, 0xAD92, 0x74DB, 0xAD93, 0x74DD, + 0xAD94, 0x74DF, 0xAD95, 0x74E1, 0xAD96, 0x74E5, 0xAD97, 0x74E7, 0xAD98, 0x74E8, 0xAD99, 0x74E9, 0xAD9A, 0x74EA, 0xAD9B, 0x74EB, + 0xAD9C, 0x74EC, 0xAD9D, 0x74ED, 0xAD9E, 0x74F0, 0xAD9F, 0x74F1, 0xADA0, 0x74F2, 0xAE40, 0x74F3, 0xAE41, 0x74F5, 0xAE42, 0x74F8, + 0xAE43, 0x74F9, 0xAE44, 0x74FA, 0xAE45, 0x74FB, 0xAE46, 0x74FC, 0xAE47, 0x74FD, 0xAE48, 0x74FE, 0xAE49, 0x7500, 0xAE4A, 0x7501, + 0xAE4B, 0x7502, 0xAE4C, 0x7503, 0xAE4D, 0x7505, 0xAE4E, 0x7506, 0xAE4F, 0x7507, 0xAE50, 0x7508, 0xAE51, 0x7509, 0xAE52, 0x750A, + 0xAE53, 0x750B, 0xAE54, 0x750C, 0xAE55, 0x750E, 0xAE56, 0x7510, 0xAE57, 0x7512, 0xAE58, 0x7514, 0xAE59, 0x7515, 0xAE5A, 0x7516, + 0xAE5B, 0x7517, 0xAE5C, 0x751B, 0xAE5D, 0x751D, 0xAE5E, 0x751E, 0xAE5F, 0x7520, 0xAE60, 0x7521, 0xAE61, 0x7522, 0xAE62, 0x7523, + 0xAE63, 0x7524, 0xAE64, 0x7526, 0xAE65, 0x7527, 0xAE66, 0x752A, 0xAE67, 0x752E, 0xAE68, 0x7534, 0xAE69, 0x7536, 0xAE6A, 0x7539, + 0xAE6B, 0x753C, 0xAE6C, 0x753D, 0xAE6D, 0x753F, 0xAE6E, 0x7541, 0xAE6F, 0x7542, 0xAE70, 0x7543, 0xAE71, 0x7544, 0xAE72, 0x7546, + 0xAE73, 0x7547, 0xAE74, 0x7549, 0xAE75, 0x754A, 0xAE76, 0x754D, 0xAE77, 0x7550, 0xAE78, 0x7551, 0xAE79, 0x7552, 0xAE7A, 0x7553, + 0xAE7B, 0x7555, 0xAE7C, 0x7556, 0xAE7D, 0x7557, 0xAE7E, 0x7558, 0xAE80, 0x755D, 0xAE81, 0x755E, 0xAE82, 0x755F, 0xAE83, 0x7560, + 0xAE84, 0x7561, 0xAE85, 0x7562, 0xAE86, 0x7563, 0xAE87, 0x7564, 0xAE88, 0x7567, 0xAE89, 0x7568, 0xAE8A, 0x7569, 0xAE8B, 0x756B, + 0xAE8C, 0x756C, 0xAE8D, 0x756D, 0xAE8E, 0x756E, 0xAE8F, 0x756F, 0xAE90, 0x7570, 0xAE91, 0x7571, 0xAE92, 0x7573, 0xAE93, 0x7575, + 0xAE94, 0x7576, 0xAE95, 0x7577, 0xAE96, 0x757A, 0xAE97, 0x757B, 0xAE98, 0x757C, 0xAE99, 0x757D, 0xAE9A, 0x757E, 0xAE9B, 0x7580, + 0xAE9C, 0x7581, 0xAE9D, 0x7582, 0xAE9E, 0x7584, 0xAE9F, 0x7585, 0xAEA0, 0x7587, 0xAF40, 0x7588, 0xAF41, 0x7589, 0xAF42, 0x758A, + 0xAF43, 0x758C, 0xAF44, 0x758D, 0xAF45, 0x758E, 0xAF46, 0x7590, 0xAF47, 0x7593, 0xAF48, 0x7595, 0xAF49, 0x7598, 0xAF4A, 0x759B, + 0xAF4B, 0x759C, 0xAF4C, 0x759E, 0xAF4D, 0x75A2, 0xAF4E, 0x75A6, 0xAF4F, 0x75A7, 0xAF50, 0x75A8, 0xAF51, 0x75A9, 0xAF52, 0x75AA, + 0xAF53, 0x75AD, 0xAF54, 0x75B6, 0xAF55, 0x75B7, 0xAF56, 0x75BA, 0xAF57, 0x75BB, 0xAF58, 0x75BF, 0xAF59, 0x75C0, 0xAF5A, 0x75C1, + 0xAF5B, 0x75C6, 0xAF5C, 0x75CB, 0xAF5D, 0x75CC, 0xAF5E, 0x75CE, 0xAF5F, 0x75CF, 0xAF60, 0x75D0, 0xAF61, 0x75D1, 0xAF62, 0x75D3, + 0xAF63, 0x75D7, 0xAF64, 0x75D9, 0xAF65, 0x75DA, 0xAF66, 0x75DC, 0xAF67, 0x75DD, 0xAF68, 0x75DF, 0xAF69, 0x75E0, 0xAF6A, 0x75E1, + 0xAF6B, 0x75E5, 0xAF6C, 0x75E9, 0xAF6D, 0x75EC, 0xAF6E, 0x75ED, 0xAF6F, 0x75EE, 0xAF70, 0x75EF, 0xAF71, 0x75F2, 0xAF72, 0x75F3, + 0xAF73, 0x75F5, 0xAF74, 0x75F6, 0xAF75, 0x75F7, 0xAF76, 0x75F8, 0xAF77, 0x75FA, 0xAF78, 0x75FB, 0xAF79, 0x75FD, 0xAF7A, 0x75FE, + 0xAF7B, 0x7602, 0xAF7C, 0x7604, 0xAF7D, 0x7606, 0xAF7E, 0x7607, 0xAF80, 0x7608, 0xAF81, 0x7609, 0xAF82, 0x760B, 0xAF83, 0x760D, + 0xAF84, 0x760E, 0xAF85, 0x760F, 0xAF86, 0x7611, 0xAF87, 0x7612, 0xAF88, 0x7613, 0xAF89, 0x7614, 0xAF8A, 0x7616, 0xAF8B, 0x761A, + 0xAF8C, 0x761C, 0xAF8D, 0x761D, 0xAF8E, 0x761E, 0xAF8F, 0x7621, 0xAF90, 0x7623, 0xAF91, 0x7627, 0xAF92, 0x7628, 0xAF93, 0x762C, + 0xAF94, 0x762E, 0xAF95, 0x762F, 0xAF96, 0x7631, 0xAF97, 0x7632, 0xAF98, 0x7636, 0xAF99, 0x7637, 0xAF9A, 0x7639, 0xAF9B, 0x763A, + 0xAF9C, 0x763B, 0xAF9D, 0x763D, 0xAF9E, 0x7641, 0xAF9F, 0x7642, 0xAFA0, 0x7644, 0xB040, 0x7645, 0xB041, 0x7646, 0xB042, 0x7647, + 0xB043, 0x7648, 0xB044, 0x7649, 0xB045, 0x764A, 0xB046, 0x764B, 0xB047, 0x764E, 0xB048, 0x764F, 0xB049, 0x7650, 0xB04A, 0x7651, + 0xB04B, 0x7652, 0xB04C, 0x7653, 0xB04D, 0x7655, 0xB04E, 0x7657, 0xB04F, 0x7658, 0xB050, 0x7659, 0xB051, 0x765A, 0xB052, 0x765B, + 0xB053, 0x765D, 0xB054, 0x765F, 0xB055, 0x7660, 0xB056, 0x7661, 0xB057, 0x7662, 0xB058, 0x7664, 0xB059, 0x7665, 0xB05A, 0x7666, + 0xB05B, 0x7667, 0xB05C, 0x7668, 0xB05D, 0x7669, 0xB05E, 0x766A, 0xB05F, 0x766C, 0xB060, 0x766D, 0xB061, 0x766E, 0xB062, 0x7670, + 0xB063, 0x7671, 0xB064, 0x7672, 0xB065, 0x7673, 0xB066, 0x7674, 0xB067, 0x7675, 0xB068, 0x7676, 0xB069, 0x7677, 0xB06A, 0x7679, + 0xB06B, 0x767A, 0xB06C, 0x767C, 0xB06D, 0x767F, 0xB06E, 0x7680, 0xB06F, 0x7681, 0xB070, 0x7683, 0xB071, 0x7685, 0xB072, 0x7689, + 0xB073, 0x768A, 0xB074, 0x768C, 0xB075, 0x768D, 0xB076, 0x768F, 0xB077, 0x7690, 0xB078, 0x7692, 0xB079, 0x7694, 0xB07A, 0x7695, + 0xB07B, 0x7697, 0xB07C, 0x7698, 0xB07D, 0x769A, 0xB07E, 0x769B, 0xB080, 0x769C, 0xB081, 0x769D, 0xB082, 0x769E, 0xB083, 0x769F, + 0xB084, 0x76A0, 0xB085, 0x76A1, 0xB086, 0x76A2, 0xB087, 0x76A3, 0xB088, 0x76A5, 0xB089, 0x76A6, 0xB08A, 0x76A7, 0xB08B, 0x76A8, + 0xB08C, 0x76A9, 0xB08D, 0x76AA, 0xB08E, 0x76AB, 0xB08F, 0x76AC, 0xB090, 0x76AD, 0xB091, 0x76AF, 0xB092, 0x76B0, 0xB093, 0x76B3, + 0xB094, 0x76B5, 0xB095, 0x76B6, 0xB096, 0x76B7, 0xB097, 0x76B8, 0xB098, 0x76B9, 0xB099, 0x76BA, 0xB09A, 0x76BB, 0xB09B, 0x76BC, + 0xB09C, 0x76BD, 0xB09D, 0x76BE, 0xB09E, 0x76C0, 0xB09F, 0x76C1, 0xB0A0, 0x76C3, 0xB0A1, 0x554A, 0xB0A2, 0x963F, 0xB0A3, 0x57C3, + 0xB0A4, 0x6328, 0xB0A5, 0x54CE, 0xB0A6, 0x5509, 0xB0A7, 0x54C0, 0xB0A8, 0x7691, 0xB0A9, 0x764C, 0xB0AA, 0x853C, 0xB0AB, 0x77EE, + 0xB0AC, 0x827E, 0xB0AD, 0x788D, 0xB0AE, 0x7231, 0xB0AF, 0x9698, 0xB0B0, 0x978D, 0xB0B1, 0x6C28, 0xB0B2, 0x5B89, 0xB0B3, 0x4FFA, + 0xB0B4, 0x6309, 0xB0B5, 0x6697, 0xB0B6, 0x5CB8, 0xB0B7, 0x80FA, 0xB0B8, 0x6848, 0xB0B9, 0x80AE, 0xB0BA, 0x6602, 0xB0BB, 0x76CE, + 0xB0BC, 0x51F9, 0xB0BD, 0x6556, 0xB0BE, 0x71AC, 0xB0BF, 0x7FF1, 0xB0C0, 0x8884, 0xB0C1, 0x50B2, 0xB0C2, 0x5965, 0xB0C3, 0x61CA, + 0xB0C4, 0x6FB3, 0xB0C5, 0x82AD, 0xB0C6, 0x634C, 0xB0C7, 0x6252, 0xB0C8, 0x53ED, 0xB0C9, 0x5427, 0xB0CA, 0x7B06, 0xB0CB, 0x516B, + 0xB0CC, 0x75A4, 0xB0CD, 0x5DF4, 0xB0CE, 0x62D4, 0xB0CF, 0x8DCB, 0xB0D0, 0x9776, 0xB0D1, 0x628A, 0xB0D2, 0x8019, 0xB0D3, 0x575D, + 0xB0D4, 0x9738, 0xB0D5, 0x7F62, 0xB0D6, 0x7238, 0xB0D7, 0x767D, 0xB0D8, 0x67CF, 0xB0D9, 0x767E, 0xB0DA, 0x6446, 0xB0DB, 0x4F70, + 0xB0DC, 0x8D25, 0xB0DD, 0x62DC, 0xB0DE, 0x7A17, 0xB0DF, 0x6591, 0xB0E0, 0x73ED, 0xB0E1, 0x642C, 0xB0E2, 0x6273, 0xB0E3, 0x822C, + 0xB0E4, 0x9881, 0xB0E5, 0x677F, 0xB0E6, 0x7248, 0xB0E7, 0x626E, 0xB0E8, 0x62CC, 0xB0E9, 0x4F34, 0xB0EA, 0x74E3, 0xB0EB, 0x534A, + 0xB0EC, 0x529E, 0xB0ED, 0x7ECA, 0xB0EE, 0x90A6, 0xB0EF, 0x5E2E, 0xB0F0, 0x6886, 0xB0F1, 0x699C, 0xB0F2, 0x8180, 0xB0F3, 0x7ED1, + 0xB0F4, 0x68D2, 0xB0F5, 0x78C5, 0xB0F6, 0x868C, 0xB0F7, 0x9551, 0xB0F8, 0x508D, 0xB0F9, 0x8C24, 0xB0FA, 0x82DE, 0xB0FB, 0x80DE, + 0xB0FC, 0x5305, 0xB0FD, 0x8912, 0xB0FE, 0x5265, 0xB140, 0x76C4, 0xB141, 0x76C7, 0xB142, 0x76C9, 0xB143, 0x76CB, 0xB144, 0x76CC, + 0xB145, 0x76D3, 0xB146, 0x76D5, 0xB147, 0x76D9, 0xB148, 0x76DA, 0xB149, 0x76DC, 0xB14A, 0x76DD, 0xB14B, 0x76DE, 0xB14C, 0x76E0, + 0xB14D, 0x76E1, 0xB14E, 0x76E2, 0xB14F, 0x76E3, 0xB150, 0x76E4, 0xB151, 0x76E6, 0xB152, 0x76E7, 0xB153, 0x76E8, 0xB154, 0x76E9, + 0xB155, 0x76EA, 0xB156, 0x76EB, 0xB157, 0x76EC, 0xB158, 0x76ED, 0xB159, 0x76F0, 0xB15A, 0x76F3, 0xB15B, 0x76F5, 0xB15C, 0x76F6, + 0xB15D, 0x76F7, 0xB15E, 0x76FA, 0xB15F, 0x76FB, 0xB160, 0x76FD, 0xB161, 0x76FF, 0xB162, 0x7700, 0xB163, 0x7702, 0xB164, 0x7703, + 0xB165, 0x7705, 0xB166, 0x7706, 0xB167, 0x770A, 0xB168, 0x770C, 0xB169, 0x770E, 0xB16A, 0x770F, 0xB16B, 0x7710, 0xB16C, 0x7711, + 0xB16D, 0x7712, 0xB16E, 0x7713, 0xB16F, 0x7714, 0xB170, 0x7715, 0xB171, 0x7716, 0xB172, 0x7717, 0xB173, 0x7718, 0xB174, 0x771B, + 0xB175, 0x771C, 0xB176, 0x771D, 0xB177, 0x771E, 0xB178, 0x7721, 0xB179, 0x7723, 0xB17A, 0x7724, 0xB17B, 0x7725, 0xB17C, 0x7727, + 0xB17D, 0x772A, 0xB17E, 0x772B, 0xB180, 0x772C, 0xB181, 0x772E, 0xB182, 0x7730, 0xB183, 0x7731, 0xB184, 0x7732, 0xB185, 0x7733, + 0xB186, 0x7734, 0xB187, 0x7739, 0xB188, 0x773B, 0xB189, 0x773D, 0xB18A, 0x773E, 0xB18B, 0x773F, 0xB18C, 0x7742, 0xB18D, 0x7744, + 0xB18E, 0x7745, 0xB18F, 0x7746, 0xB190, 0x7748, 0xB191, 0x7749, 0xB192, 0x774A, 0xB193, 0x774B, 0xB194, 0x774C, 0xB195, 0x774D, + 0xB196, 0x774E, 0xB197, 0x774F, 0xB198, 0x7752, 0xB199, 0x7753, 0xB19A, 0x7754, 0xB19B, 0x7755, 0xB19C, 0x7756, 0xB19D, 0x7757, + 0xB19E, 0x7758, 0xB19F, 0x7759, 0xB1A0, 0x775C, 0xB1A1, 0x8584, 0xB1A2, 0x96F9, 0xB1A3, 0x4FDD, 0xB1A4, 0x5821, 0xB1A5, 0x9971, + 0xB1A6, 0x5B9D, 0xB1A7, 0x62B1, 0xB1A8, 0x62A5, 0xB1A9, 0x66B4, 0xB1AA, 0x8C79, 0xB1AB, 0x9C8D, 0xB1AC, 0x7206, 0xB1AD, 0x676F, + 0xB1AE, 0x7891, 0xB1AF, 0x60B2, 0xB1B0, 0x5351, 0xB1B1, 0x5317, 0xB1B2, 0x8F88, 0xB1B3, 0x80CC, 0xB1B4, 0x8D1D, 0xB1B5, 0x94A1, + 0xB1B6, 0x500D, 0xB1B7, 0x72C8, 0xB1B8, 0x5907, 0xB1B9, 0x60EB, 0xB1BA, 0x7119, 0xB1BB, 0x88AB, 0xB1BC, 0x5954, 0xB1BD, 0x82EF, + 0xB1BE, 0x672C, 0xB1BF, 0x7B28, 0xB1C0, 0x5D29, 0xB1C1, 0x7EF7, 0xB1C2, 0x752D, 0xB1C3, 0x6CF5, 0xB1C4, 0x8E66, 0xB1C5, 0x8FF8, + 0xB1C6, 0x903C, 0xB1C7, 0x9F3B, 0xB1C8, 0x6BD4, 0xB1C9, 0x9119, 0xB1CA, 0x7B14, 0xB1CB, 0x5F7C, 0xB1CC, 0x78A7, 0xB1CD, 0x84D6, + 0xB1CE, 0x853D, 0xB1CF, 0x6BD5, 0xB1D0, 0x6BD9, 0xB1D1, 0x6BD6, 0xB1D2, 0x5E01, 0xB1D3, 0x5E87, 0xB1D4, 0x75F9, 0xB1D5, 0x95ED, + 0xB1D6, 0x655D, 0xB1D7, 0x5F0A, 0xB1D8, 0x5FC5, 0xB1D9, 0x8F9F, 0xB1DA, 0x58C1, 0xB1DB, 0x81C2, 0xB1DC, 0x907F, 0xB1DD, 0x965B, + 0xB1DE, 0x97AD, 0xB1DF, 0x8FB9, 0xB1E0, 0x7F16, 0xB1E1, 0x8D2C, 0xB1E2, 0x6241, 0xB1E3, 0x4FBF, 0xB1E4, 0x53D8, 0xB1E5, 0x535E, + 0xB1E6, 0x8FA8, 0xB1E7, 0x8FA9, 0xB1E8, 0x8FAB, 0xB1E9, 0x904D, 0xB1EA, 0x6807, 0xB1EB, 0x5F6A, 0xB1EC, 0x8198, 0xB1ED, 0x8868, + 0xB1EE, 0x9CD6, 0xB1EF, 0x618B, 0xB1F0, 0x522B, 0xB1F1, 0x762A, 0xB1F2, 0x5F6C, 0xB1F3, 0x658C, 0xB1F4, 0x6FD2, 0xB1F5, 0x6EE8, + 0xB1F6, 0x5BBE, 0xB1F7, 0x6448, 0xB1F8, 0x5175, 0xB1F9, 0x51B0, 0xB1FA, 0x67C4, 0xB1FB, 0x4E19, 0xB1FC, 0x79C9, 0xB1FD, 0x997C, + 0xB1FE, 0x70B3, 0xB240, 0x775D, 0xB241, 0x775E, 0xB242, 0x775F, 0xB243, 0x7760, 0xB244, 0x7764, 0xB245, 0x7767, 0xB246, 0x7769, + 0xB247, 0x776A, 0xB248, 0x776D, 0xB249, 0x776E, 0xB24A, 0x776F, 0xB24B, 0x7770, 0xB24C, 0x7771, 0xB24D, 0x7772, 0xB24E, 0x7773, + 0xB24F, 0x7774, 0xB250, 0x7775, 0xB251, 0x7776, 0xB252, 0x7777, 0xB253, 0x7778, 0xB254, 0x777A, 0xB255, 0x777B, 0xB256, 0x777C, + 0xB257, 0x7781, 0xB258, 0x7782, 0xB259, 0x7783, 0xB25A, 0x7786, 0xB25B, 0x7787, 0xB25C, 0x7788, 0xB25D, 0x7789, 0xB25E, 0x778A, + 0xB25F, 0x778B, 0xB260, 0x778F, 0xB261, 0x7790, 0xB262, 0x7793, 0xB263, 0x7794, 0xB264, 0x7795, 0xB265, 0x7796, 0xB266, 0x7797, + 0xB267, 0x7798, 0xB268, 0x7799, 0xB269, 0x779A, 0xB26A, 0x779B, 0xB26B, 0x779C, 0xB26C, 0x779D, 0xB26D, 0x779E, 0xB26E, 0x77A1, + 0xB26F, 0x77A3, 0xB270, 0x77A4, 0xB271, 0x77A6, 0xB272, 0x77A8, 0xB273, 0x77AB, 0xB274, 0x77AD, 0xB275, 0x77AE, 0xB276, 0x77AF, + 0xB277, 0x77B1, 0xB278, 0x77B2, 0xB279, 0x77B4, 0xB27A, 0x77B6, 0xB27B, 0x77B7, 0xB27C, 0x77B8, 0xB27D, 0x77B9, 0xB27E, 0x77BA, + 0xB280, 0x77BC, 0xB281, 0x77BE, 0xB282, 0x77C0, 0xB283, 0x77C1, 0xB284, 0x77C2, 0xB285, 0x77C3, 0xB286, 0x77C4, 0xB287, 0x77C5, + 0xB288, 0x77C6, 0xB289, 0x77C7, 0xB28A, 0x77C8, 0xB28B, 0x77C9, 0xB28C, 0x77CA, 0xB28D, 0x77CB, 0xB28E, 0x77CC, 0xB28F, 0x77CE, + 0xB290, 0x77CF, 0xB291, 0x77D0, 0xB292, 0x77D1, 0xB293, 0x77D2, 0xB294, 0x77D3, 0xB295, 0x77D4, 0xB296, 0x77D5, 0xB297, 0x77D6, + 0xB298, 0x77D8, 0xB299, 0x77D9, 0xB29A, 0x77DA, 0xB29B, 0x77DD, 0xB29C, 0x77DE, 0xB29D, 0x77DF, 0xB29E, 0x77E0, 0xB29F, 0x77E1, + 0xB2A0, 0x77E4, 0xB2A1, 0x75C5, 0xB2A2, 0x5E76, 0xB2A3, 0x73BB, 0xB2A4, 0x83E0, 0xB2A5, 0x64AD, 0xB2A6, 0x62E8, 0xB2A7, 0x94B5, + 0xB2A8, 0x6CE2, 0xB2A9, 0x535A, 0xB2AA, 0x52C3, 0xB2AB, 0x640F, 0xB2AC, 0x94C2, 0xB2AD, 0x7B94, 0xB2AE, 0x4F2F, 0xB2AF, 0x5E1B, + 0xB2B0, 0x8236, 0xB2B1, 0x8116, 0xB2B2, 0x818A, 0xB2B3, 0x6E24, 0xB2B4, 0x6CCA, 0xB2B5, 0x9A73, 0xB2B6, 0x6355, 0xB2B7, 0x535C, + 0xB2B8, 0x54FA, 0xB2B9, 0x8865, 0xB2BA, 0x57E0, 0xB2BB, 0x4E0D, 0xB2BC, 0x5E03, 0xB2BD, 0x6B65, 0xB2BE, 0x7C3F, 0xB2BF, 0x90E8, + 0xB2C0, 0x6016, 0xB2C1, 0x64E6, 0xB2C2, 0x731C, 0xB2C3, 0x88C1, 0xB2C4, 0x6750, 0xB2C5, 0x624D, 0xB2C6, 0x8D22, 0xB2C7, 0x776C, + 0xB2C8, 0x8E29, 0xB2C9, 0x91C7, 0xB2CA, 0x5F69, 0xB2CB, 0x83DC, 0xB2CC, 0x8521, 0xB2CD, 0x9910, 0xB2CE, 0x53C2, 0xB2CF, 0x8695, + 0xB2D0, 0x6B8B, 0xB2D1, 0x60ED, 0xB2D2, 0x60E8, 0xB2D3, 0x707F, 0xB2D4, 0x82CD, 0xB2D5, 0x8231, 0xB2D6, 0x4ED3, 0xB2D7, 0x6CA7, + 0xB2D8, 0x85CF, 0xB2D9, 0x64CD, 0xB2DA, 0x7CD9, 0xB2DB, 0x69FD, 0xB2DC, 0x66F9, 0xB2DD, 0x8349, 0xB2DE, 0x5395, 0xB2DF, 0x7B56, + 0xB2E0, 0x4FA7, 0xB2E1, 0x518C, 0xB2E2, 0x6D4B, 0xB2E3, 0x5C42, 0xB2E4, 0x8E6D, 0xB2E5, 0x63D2, 0xB2E6, 0x53C9, 0xB2E7, 0x832C, + 0xB2E8, 0x8336, 0xB2E9, 0x67E5, 0xB2EA, 0x78B4, 0xB2EB, 0x643D, 0xB2EC, 0x5BDF, 0xB2ED, 0x5C94, 0xB2EE, 0x5DEE, 0xB2EF, 0x8BE7, + 0xB2F0, 0x62C6, 0xB2F1, 0x67F4, 0xB2F2, 0x8C7A, 0xB2F3, 0x6400, 0xB2F4, 0x63BA, 0xB2F5, 0x8749, 0xB2F6, 0x998B, 0xB2F7, 0x8C17, + 0xB2F8, 0x7F20, 0xB2F9, 0x94F2, 0xB2FA, 0x4EA7, 0xB2FB, 0x9610, 0xB2FC, 0x98A4, 0xB2FD, 0x660C, 0xB2FE, 0x7316, 0xB340, 0x77E6, + 0xB341, 0x77E8, 0xB342, 0x77EA, 0xB343, 0x77EF, 0xB344, 0x77F0, 0xB345, 0x77F1, 0xB346, 0x77F2, 0xB347, 0x77F4, 0xB348, 0x77F5, + 0xB349, 0x77F7, 0xB34A, 0x77F9, 0xB34B, 0x77FA, 0xB34C, 0x77FB, 0xB34D, 0x77FC, 0xB34E, 0x7803, 0xB34F, 0x7804, 0xB350, 0x7805, + 0xB351, 0x7806, 0xB352, 0x7807, 0xB353, 0x7808, 0xB354, 0x780A, 0xB355, 0x780B, 0xB356, 0x780E, 0xB357, 0x780F, 0xB358, 0x7810, + 0xB359, 0x7813, 0xB35A, 0x7815, 0xB35B, 0x7819, 0xB35C, 0x781B, 0xB35D, 0x781E, 0xB35E, 0x7820, 0xB35F, 0x7821, 0xB360, 0x7822, + 0xB361, 0x7824, 0xB362, 0x7828, 0xB363, 0x782A, 0xB364, 0x782B, 0xB365, 0x782E, 0xB366, 0x782F, 0xB367, 0x7831, 0xB368, 0x7832, + 0xB369, 0x7833, 0xB36A, 0x7835, 0xB36B, 0x7836, 0xB36C, 0x783D, 0xB36D, 0x783F, 0xB36E, 0x7841, 0xB36F, 0x7842, 0xB370, 0x7843, + 0xB371, 0x7844, 0xB372, 0x7846, 0xB373, 0x7848, 0xB374, 0x7849, 0xB375, 0x784A, 0xB376, 0x784B, 0xB377, 0x784D, 0xB378, 0x784F, + 0xB379, 0x7851, 0xB37A, 0x7853, 0xB37B, 0x7854, 0xB37C, 0x7858, 0xB37D, 0x7859, 0xB37E, 0x785A, 0xB380, 0x785B, 0xB381, 0x785C, + 0xB382, 0x785E, 0xB383, 0x785F, 0xB384, 0x7860, 0xB385, 0x7861, 0xB386, 0x7862, 0xB387, 0x7863, 0xB388, 0x7864, 0xB389, 0x7865, + 0xB38A, 0x7866, 0xB38B, 0x7867, 0xB38C, 0x7868, 0xB38D, 0x7869, 0xB38E, 0x786F, 0xB38F, 0x7870, 0xB390, 0x7871, 0xB391, 0x7872, + 0xB392, 0x7873, 0xB393, 0x7874, 0xB394, 0x7875, 0xB395, 0x7876, 0xB396, 0x7878, 0xB397, 0x7879, 0xB398, 0x787A, 0xB399, 0x787B, + 0xB39A, 0x787D, 0xB39B, 0x787E, 0xB39C, 0x787F, 0xB39D, 0x7880, 0xB39E, 0x7881, 0xB39F, 0x7882, 0xB3A0, 0x7883, 0xB3A1, 0x573A, + 0xB3A2, 0x5C1D, 0xB3A3, 0x5E38, 0xB3A4, 0x957F, 0xB3A5, 0x507F, 0xB3A6, 0x80A0, 0xB3A7, 0x5382, 0xB3A8, 0x655E, 0xB3A9, 0x7545, + 0xB3AA, 0x5531, 0xB3AB, 0x5021, 0xB3AC, 0x8D85, 0xB3AD, 0x6284, 0xB3AE, 0x949E, 0xB3AF, 0x671D, 0xB3B0, 0x5632, 0xB3B1, 0x6F6E, + 0xB3B2, 0x5DE2, 0xB3B3, 0x5435, 0xB3B4, 0x7092, 0xB3B5, 0x8F66, 0xB3B6, 0x626F, 0xB3B7, 0x64A4, 0xB3B8, 0x63A3, 0xB3B9, 0x5F7B, + 0xB3BA, 0x6F88, 0xB3BB, 0x90F4, 0xB3BC, 0x81E3, 0xB3BD, 0x8FB0, 0xB3BE, 0x5C18, 0xB3BF, 0x6668, 0xB3C0, 0x5FF1, 0xB3C1, 0x6C89, + 0xB3C2, 0x9648, 0xB3C3, 0x8D81, 0xB3C4, 0x886C, 0xB3C5, 0x6491, 0xB3C6, 0x79F0, 0xB3C7, 0x57CE, 0xB3C8, 0x6A59, 0xB3C9, 0x6210, + 0xB3CA, 0x5448, 0xB3CB, 0x4E58, 0xB3CC, 0x7A0B, 0xB3CD, 0x60E9, 0xB3CE, 0x6F84, 0xB3CF, 0x8BDA, 0xB3D0, 0x627F, 0xB3D1, 0x901E, + 0xB3D2, 0x9A8B, 0xB3D3, 0x79E4, 0xB3D4, 0x5403, 0xB3D5, 0x75F4, 0xB3D6, 0x6301, 0xB3D7, 0x5319, 0xB3D8, 0x6C60, 0xB3D9, 0x8FDF, + 0xB3DA, 0x5F1B, 0xB3DB, 0x9A70, 0xB3DC, 0x803B, 0xB3DD, 0x9F7F, 0xB3DE, 0x4F88, 0xB3DF, 0x5C3A, 0xB3E0, 0x8D64, 0xB3E1, 0x7FC5, + 0xB3E2, 0x65A5, 0xB3E3, 0x70BD, 0xB3E4, 0x5145, 0xB3E5, 0x51B2, 0xB3E6, 0x866B, 0xB3E7, 0x5D07, 0xB3E8, 0x5BA0, 0xB3E9, 0x62BD, + 0xB3EA, 0x916C, 0xB3EB, 0x7574, 0xB3EC, 0x8E0C, 0xB3ED, 0x7A20, 0xB3EE, 0x6101, 0xB3EF, 0x7B79, 0xB3F0, 0x4EC7, 0xB3F1, 0x7EF8, + 0xB3F2, 0x7785, 0xB3F3, 0x4E11, 0xB3F4, 0x81ED, 0xB3F5, 0x521D, 0xB3F6, 0x51FA, 0xB3F7, 0x6A71, 0xB3F8, 0x53A8, 0xB3F9, 0x8E87, + 0xB3FA, 0x9504, 0xB3FB, 0x96CF, 0xB3FC, 0x6EC1, 0xB3FD, 0x9664, 0xB3FE, 0x695A, 0xB440, 0x7884, 0xB441, 0x7885, 0xB442, 0x7886, + 0xB443, 0x7888, 0xB444, 0x788A, 0xB445, 0x788B, 0xB446, 0x788F, 0xB447, 0x7890, 0xB448, 0x7892, 0xB449, 0x7894, 0xB44A, 0x7895, + 0xB44B, 0x7896, 0xB44C, 0x7899, 0xB44D, 0x789D, 0xB44E, 0x789E, 0xB44F, 0x78A0, 0xB450, 0x78A2, 0xB451, 0x78A4, 0xB452, 0x78A6, + 0xB453, 0x78A8, 0xB454, 0x78A9, 0xB455, 0x78AA, 0xB456, 0x78AB, 0xB457, 0x78AC, 0xB458, 0x78AD, 0xB459, 0x78AE, 0xB45A, 0x78AF, + 0xB45B, 0x78B5, 0xB45C, 0x78B6, 0xB45D, 0x78B7, 0xB45E, 0x78B8, 0xB45F, 0x78BA, 0xB460, 0x78BB, 0xB461, 0x78BC, 0xB462, 0x78BD, + 0xB463, 0x78BF, 0xB464, 0x78C0, 0xB465, 0x78C2, 0xB466, 0x78C3, 0xB467, 0x78C4, 0xB468, 0x78C6, 0xB469, 0x78C7, 0xB46A, 0x78C8, + 0xB46B, 0x78CC, 0xB46C, 0x78CD, 0xB46D, 0x78CE, 0xB46E, 0x78CF, 0xB46F, 0x78D1, 0xB470, 0x78D2, 0xB471, 0x78D3, 0xB472, 0x78D6, + 0xB473, 0x78D7, 0xB474, 0x78D8, 0xB475, 0x78DA, 0xB476, 0x78DB, 0xB477, 0x78DC, 0xB478, 0x78DD, 0xB479, 0x78DE, 0xB47A, 0x78DF, + 0xB47B, 0x78E0, 0xB47C, 0x78E1, 0xB47D, 0x78E2, 0xB47E, 0x78E3, 0xB480, 0x78E4, 0xB481, 0x78E5, 0xB482, 0x78E6, 0xB483, 0x78E7, + 0xB484, 0x78E9, 0xB485, 0x78EA, 0xB486, 0x78EB, 0xB487, 0x78ED, 0xB488, 0x78EE, 0xB489, 0x78EF, 0xB48A, 0x78F0, 0xB48B, 0x78F1, + 0xB48C, 0x78F3, 0xB48D, 0x78F5, 0xB48E, 0x78F6, 0xB48F, 0x78F8, 0xB490, 0x78F9, 0xB491, 0x78FB, 0xB492, 0x78FC, 0xB493, 0x78FD, + 0xB494, 0x78FE, 0xB495, 0x78FF, 0xB496, 0x7900, 0xB497, 0x7902, 0xB498, 0x7903, 0xB499, 0x7904, 0xB49A, 0x7906, 0xB49B, 0x7907, + 0xB49C, 0x7908, 0xB49D, 0x7909, 0xB49E, 0x790A, 0xB49F, 0x790B, 0xB4A0, 0x790C, 0xB4A1, 0x7840, 0xB4A2, 0x50A8, 0xB4A3, 0x77D7, + 0xB4A4, 0x6410, 0xB4A5, 0x89E6, 0xB4A6, 0x5904, 0xB4A7, 0x63E3, 0xB4A8, 0x5DDD, 0xB4A9, 0x7A7F, 0xB4AA, 0x693D, 0xB4AB, 0x4F20, + 0xB4AC, 0x8239, 0xB4AD, 0x5598, 0xB4AE, 0x4E32, 0xB4AF, 0x75AE, 0xB4B0, 0x7A97, 0xB4B1, 0x5E62, 0xB4B2, 0x5E8A, 0xB4B3, 0x95EF, + 0xB4B4, 0x521B, 0xB4B5, 0x5439, 0xB4B6, 0x708A, 0xB4B7, 0x6376, 0xB4B8, 0x9524, 0xB4B9, 0x5782, 0xB4BA, 0x6625, 0xB4BB, 0x693F, + 0xB4BC, 0x9187, 0xB4BD, 0x5507, 0xB4BE, 0x6DF3, 0xB4BF, 0x7EAF, 0xB4C0, 0x8822, 0xB4C1, 0x6233, 0xB4C2, 0x7EF0, 0xB4C3, 0x75B5, + 0xB4C4, 0x8328, 0xB4C5, 0x78C1, 0xB4C6, 0x96CC, 0xB4C7, 0x8F9E, 0xB4C8, 0x6148, 0xB4C9, 0x74F7, 0xB4CA, 0x8BCD, 0xB4CB, 0x6B64, + 0xB4CC, 0x523A, 0xB4CD, 0x8D50, 0xB4CE, 0x6B21, 0xB4CF, 0x806A, 0xB4D0, 0x8471, 0xB4D1, 0x56F1, 0xB4D2, 0x5306, 0xB4D3, 0x4ECE, + 0xB4D4, 0x4E1B, 0xB4D5, 0x51D1, 0xB4D6, 0x7C97, 0xB4D7, 0x918B, 0xB4D8, 0x7C07, 0xB4D9, 0x4FC3, 0xB4DA, 0x8E7F, 0xB4DB, 0x7BE1, + 0xB4DC, 0x7A9C, 0xB4DD, 0x6467, 0xB4DE, 0x5D14, 0xB4DF, 0x50AC, 0xB4E0, 0x8106, 0xB4E1, 0x7601, 0xB4E2, 0x7CB9, 0xB4E3, 0x6DEC, + 0xB4E4, 0x7FE0, 0xB4E5, 0x6751, 0xB4E6, 0x5B58, 0xB4E7, 0x5BF8, 0xB4E8, 0x78CB, 0xB4E9, 0x64AE, 0xB4EA, 0x6413, 0xB4EB, 0x63AA, + 0xB4EC, 0x632B, 0xB4ED, 0x9519, 0xB4EE, 0x642D, 0xB4EF, 0x8FBE, 0xB4F0, 0x7B54, 0xB4F1, 0x7629, 0xB4F2, 0x6253, 0xB4F3, 0x5927, + 0xB4F4, 0x5446, 0xB4F5, 0x6B79, 0xB4F6, 0x50A3, 0xB4F7, 0x6234, 0xB4F8, 0x5E26, 0xB4F9, 0x6B86, 0xB4FA, 0x4EE3, 0xB4FB, 0x8D37, + 0xB4FC, 0x888B, 0xB4FD, 0x5F85, 0xB4FE, 0x902E, 0xB540, 0x790D, 0xB541, 0x790E, 0xB542, 0x790F, 0xB543, 0x7910, 0xB544, 0x7911, + 0xB545, 0x7912, 0xB546, 0x7914, 0xB547, 0x7915, 0xB548, 0x7916, 0xB549, 0x7917, 0xB54A, 0x7918, 0xB54B, 0x7919, 0xB54C, 0x791A, + 0xB54D, 0x791B, 0xB54E, 0x791C, 0xB54F, 0x791D, 0xB550, 0x791F, 0xB551, 0x7920, 0xB552, 0x7921, 0xB553, 0x7922, 0xB554, 0x7923, + 0xB555, 0x7925, 0xB556, 0x7926, 0xB557, 0x7927, 0xB558, 0x7928, 0xB559, 0x7929, 0xB55A, 0x792A, 0xB55B, 0x792B, 0xB55C, 0x792C, + 0xB55D, 0x792D, 0xB55E, 0x792E, 0xB55F, 0x792F, 0xB560, 0x7930, 0xB561, 0x7931, 0xB562, 0x7932, 0xB563, 0x7933, 0xB564, 0x7935, + 0xB565, 0x7936, 0xB566, 0x7937, 0xB567, 0x7938, 0xB568, 0x7939, 0xB569, 0x793D, 0xB56A, 0x793F, 0xB56B, 0x7942, 0xB56C, 0x7943, + 0xB56D, 0x7944, 0xB56E, 0x7945, 0xB56F, 0x7947, 0xB570, 0x794A, 0xB571, 0x794B, 0xB572, 0x794C, 0xB573, 0x794D, 0xB574, 0x794E, + 0xB575, 0x794F, 0xB576, 0x7950, 0xB577, 0x7951, 0xB578, 0x7952, 0xB579, 0x7954, 0xB57A, 0x7955, 0xB57B, 0x7958, 0xB57C, 0x7959, + 0xB57D, 0x7961, 0xB57E, 0x7963, 0xB580, 0x7964, 0xB581, 0x7966, 0xB582, 0x7969, 0xB583, 0x796A, 0xB584, 0x796B, 0xB585, 0x796C, + 0xB586, 0x796E, 0xB587, 0x7970, 0xB588, 0x7971, 0xB589, 0x7972, 0xB58A, 0x7973, 0xB58B, 0x7974, 0xB58C, 0x7975, 0xB58D, 0x7976, + 0xB58E, 0x7979, 0xB58F, 0x797B, 0xB590, 0x797C, 0xB591, 0x797D, 0xB592, 0x797E, 0xB593, 0x797F, 0xB594, 0x7982, 0xB595, 0x7983, + 0xB596, 0x7986, 0xB597, 0x7987, 0xB598, 0x7988, 0xB599, 0x7989, 0xB59A, 0x798B, 0xB59B, 0x798C, 0xB59C, 0x798D, 0xB59D, 0x798E, + 0xB59E, 0x7990, 0xB59F, 0x7991, 0xB5A0, 0x7992, 0xB5A1, 0x6020, 0xB5A2, 0x803D, 0xB5A3, 0x62C5, 0xB5A4, 0x4E39, 0xB5A5, 0x5355, + 0xB5A6, 0x90F8, 0xB5A7, 0x63B8, 0xB5A8, 0x80C6, 0xB5A9, 0x65E6, 0xB5AA, 0x6C2E, 0xB5AB, 0x4F46, 0xB5AC, 0x60EE, 0xB5AD, 0x6DE1, + 0xB5AE, 0x8BDE, 0xB5AF, 0x5F39, 0xB5B0, 0x86CB, 0xB5B1, 0x5F53, 0xB5B2, 0x6321, 0xB5B3, 0x515A, 0xB5B4, 0x8361, 0xB5B5, 0x6863, + 0xB5B6, 0x5200, 0xB5B7, 0x6363, 0xB5B8, 0x8E48, 0xB5B9, 0x5012, 0xB5BA, 0x5C9B, 0xB5BB, 0x7977, 0xB5BC, 0x5BFC, 0xB5BD, 0x5230, + 0xB5BE, 0x7A3B, 0xB5BF, 0x60BC, 0xB5C0, 0x9053, 0xB5C1, 0x76D7, 0xB5C2, 0x5FB7, 0xB5C3, 0x5F97, 0xB5C4, 0x7684, 0xB5C5, 0x8E6C, + 0xB5C6, 0x706F, 0xB5C7, 0x767B, 0xB5C8, 0x7B49, 0xB5C9, 0x77AA, 0xB5CA, 0x51F3, 0xB5CB, 0x9093, 0xB5CC, 0x5824, 0xB5CD, 0x4F4E, + 0xB5CE, 0x6EF4, 0xB5CF, 0x8FEA, 0xB5D0, 0x654C, 0xB5D1, 0x7B1B, 0xB5D2, 0x72C4, 0xB5D3, 0x6DA4, 0xB5D4, 0x7FDF, 0xB5D5, 0x5AE1, + 0xB5D6, 0x62B5, 0xB5D7, 0x5E95, 0xB5D8, 0x5730, 0xB5D9, 0x8482, 0xB5DA, 0x7B2C, 0xB5DB, 0x5E1D, 0xB5DC, 0x5F1F, 0xB5DD, 0x9012, + 0xB5DE, 0x7F14, 0xB5DF, 0x98A0, 0xB5E0, 0x6382, 0xB5E1, 0x6EC7, 0xB5E2, 0x7898, 0xB5E3, 0x70B9, 0xB5E4, 0x5178, 0xB5E5, 0x975B, + 0xB5E6, 0x57AB, 0xB5E7, 0x7535, 0xB5E8, 0x4F43, 0xB5E9, 0x7538, 0xB5EA, 0x5E97, 0xB5EB, 0x60E6, 0xB5EC, 0x5960, 0xB5ED, 0x6DC0, + 0xB5EE, 0x6BBF, 0xB5EF, 0x7889, 0xB5F0, 0x53FC, 0xB5F1, 0x96D5, 0xB5F2, 0x51CB, 0xB5F3, 0x5201, 0xB5F4, 0x6389, 0xB5F5, 0x540A, + 0xB5F6, 0x9493, 0xB5F7, 0x8C03, 0xB5F8, 0x8DCC, 0xB5F9, 0x7239, 0xB5FA, 0x789F, 0xB5FB, 0x8776, 0xB5FC, 0x8FED, 0xB5FD, 0x8C0D, + 0xB5FE, 0x53E0, 0xB640, 0x7993, 0xB641, 0x7994, 0xB642, 0x7995, 0xB643, 0x7996, 0xB644, 0x7997, 0xB645, 0x7998, 0xB646, 0x7999, + 0xB647, 0x799B, 0xB648, 0x799C, 0xB649, 0x799D, 0xB64A, 0x799E, 0xB64B, 0x799F, 0xB64C, 0x79A0, 0xB64D, 0x79A1, 0xB64E, 0x79A2, + 0xB64F, 0x79A3, 0xB650, 0x79A4, 0xB651, 0x79A5, 0xB652, 0x79A6, 0xB653, 0x79A8, 0xB654, 0x79A9, 0xB655, 0x79AA, 0xB656, 0x79AB, + 0xB657, 0x79AC, 0xB658, 0x79AD, 0xB659, 0x79AE, 0xB65A, 0x79AF, 0xB65B, 0x79B0, 0xB65C, 0x79B1, 0xB65D, 0x79B2, 0xB65E, 0x79B4, + 0xB65F, 0x79B5, 0xB660, 0x79B6, 0xB661, 0x79B7, 0xB662, 0x79B8, 0xB663, 0x79BC, 0xB664, 0x79BF, 0xB665, 0x79C2, 0xB666, 0x79C4, + 0xB667, 0x79C5, 0xB668, 0x79C7, 0xB669, 0x79C8, 0xB66A, 0x79CA, 0xB66B, 0x79CC, 0xB66C, 0x79CE, 0xB66D, 0x79CF, 0xB66E, 0x79D0, + 0xB66F, 0x79D3, 0xB670, 0x79D4, 0xB671, 0x79D6, 0xB672, 0x79D7, 0xB673, 0x79D9, 0xB674, 0x79DA, 0xB675, 0x79DB, 0xB676, 0x79DC, + 0xB677, 0x79DD, 0xB678, 0x79DE, 0xB679, 0x79E0, 0xB67A, 0x79E1, 0xB67B, 0x79E2, 0xB67C, 0x79E5, 0xB67D, 0x79E8, 0xB67E, 0x79EA, + 0xB680, 0x79EC, 0xB681, 0x79EE, 0xB682, 0x79F1, 0xB683, 0x79F2, 0xB684, 0x79F3, 0xB685, 0x79F4, 0xB686, 0x79F5, 0xB687, 0x79F6, + 0xB688, 0x79F7, 0xB689, 0x79F9, 0xB68A, 0x79FA, 0xB68B, 0x79FC, 0xB68C, 0x79FE, 0xB68D, 0x79FF, 0xB68E, 0x7A01, 0xB68F, 0x7A04, + 0xB690, 0x7A05, 0xB691, 0x7A07, 0xB692, 0x7A08, 0xB693, 0x7A09, 0xB694, 0x7A0A, 0xB695, 0x7A0C, 0xB696, 0x7A0F, 0xB697, 0x7A10, + 0xB698, 0x7A11, 0xB699, 0x7A12, 0xB69A, 0x7A13, 0xB69B, 0x7A15, 0xB69C, 0x7A16, 0xB69D, 0x7A18, 0xB69E, 0x7A19, 0xB69F, 0x7A1B, + 0xB6A0, 0x7A1C, 0xB6A1, 0x4E01, 0xB6A2, 0x76EF, 0xB6A3, 0x53EE, 0xB6A4, 0x9489, 0xB6A5, 0x9876, 0xB6A6, 0x9F0E, 0xB6A7, 0x952D, + 0xB6A8, 0x5B9A, 0xB6A9, 0x8BA2, 0xB6AA, 0x4E22, 0xB6AB, 0x4E1C, 0xB6AC, 0x51AC, 0xB6AD, 0x8463, 0xB6AE, 0x61C2, 0xB6AF, 0x52A8, + 0xB6B0, 0x680B, 0xB6B1, 0x4F97, 0xB6B2, 0x606B, 0xB6B3, 0x51BB, 0xB6B4, 0x6D1E, 0xB6B5, 0x515C, 0xB6B6, 0x6296, 0xB6B7, 0x6597, + 0xB6B8, 0x9661, 0xB6B9, 0x8C46, 0xB6BA, 0x9017, 0xB6BB, 0x75D8, 0xB6BC, 0x90FD, 0xB6BD, 0x7763, 0xB6BE, 0x6BD2, 0xB6BF, 0x728A, + 0xB6C0, 0x72EC, 0xB6C1, 0x8BFB, 0xB6C2, 0x5835, 0xB6C3, 0x7779, 0xB6C4, 0x8D4C, 0xB6C5, 0x675C, 0xB6C6, 0x9540, 0xB6C7, 0x809A, + 0xB6C8, 0x5EA6, 0xB6C9, 0x6E21, 0xB6CA, 0x5992, 0xB6CB, 0x7AEF, 0xB6CC, 0x77ED, 0xB6CD, 0x953B, 0xB6CE, 0x6BB5, 0xB6CF, 0x65AD, + 0xB6D0, 0x7F0E, 0xB6D1, 0x5806, 0xB6D2, 0x5151, 0xB6D3, 0x961F, 0xB6D4, 0x5BF9, 0xB6D5, 0x58A9, 0xB6D6, 0x5428, 0xB6D7, 0x8E72, + 0xB6D8, 0x6566, 0xB6D9, 0x987F, 0xB6DA, 0x56E4, 0xB6DB, 0x949D, 0xB6DC, 0x76FE, 0xB6DD, 0x9041, 0xB6DE, 0x6387, 0xB6DF, 0x54C6, + 0xB6E0, 0x591A, 0xB6E1, 0x593A, 0xB6E2, 0x579B, 0xB6E3, 0x8EB2, 0xB6E4, 0x6735, 0xB6E5, 0x8DFA, 0xB6E6, 0x8235, 0xB6E7, 0x5241, + 0xB6E8, 0x60F0, 0xB6E9, 0x5815, 0xB6EA, 0x86FE, 0xB6EB, 0x5CE8, 0xB6EC, 0x9E45, 0xB6ED, 0x4FC4, 0xB6EE, 0x989D, 0xB6EF, 0x8BB9, + 0xB6F0, 0x5A25, 0xB6F1, 0x6076, 0xB6F2, 0x5384, 0xB6F3, 0x627C, 0xB6F4, 0x904F, 0xB6F5, 0x9102, 0xB6F6, 0x997F, 0xB6F7, 0x6069, + 0xB6F8, 0x800C, 0xB6F9, 0x513F, 0xB6FA, 0x8033, 0xB6FB, 0x5C14, 0xB6FC, 0x9975, 0xB6FD, 0x6D31, 0xB6FE, 0x4E8C, 0xB740, 0x7A1D, + 0xB741, 0x7A1F, 0xB742, 0x7A21, 0xB743, 0x7A22, 0xB744, 0x7A24, 0xB745, 0x7A25, 0xB746, 0x7A26, 0xB747, 0x7A27, 0xB748, 0x7A28, + 0xB749, 0x7A29, 0xB74A, 0x7A2A, 0xB74B, 0x7A2B, 0xB74C, 0x7A2C, 0xB74D, 0x7A2D, 0xB74E, 0x7A2E, 0xB74F, 0x7A2F, 0xB750, 0x7A30, + 0xB751, 0x7A31, 0xB752, 0x7A32, 0xB753, 0x7A34, 0xB754, 0x7A35, 0xB755, 0x7A36, 0xB756, 0x7A38, 0xB757, 0x7A3A, 0xB758, 0x7A3E, + 0xB759, 0x7A40, 0xB75A, 0x7A41, 0xB75B, 0x7A42, 0xB75C, 0x7A43, 0xB75D, 0x7A44, 0xB75E, 0x7A45, 0xB75F, 0x7A47, 0xB760, 0x7A48, + 0xB761, 0x7A49, 0xB762, 0x7A4A, 0xB763, 0x7A4B, 0xB764, 0x7A4C, 0xB765, 0x7A4D, 0xB766, 0x7A4E, 0xB767, 0x7A4F, 0xB768, 0x7A50, + 0xB769, 0x7A52, 0xB76A, 0x7A53, 0xB76B, 0x7A54, 0xB76C, 0x7A55, 0xB76D, 0x7A56, 0xB76E, 0x7A58, 0xB76F, 0x7A59, 0xB770, 0x7A5A, + 0xB771, 0x7A5B, 0xB772, 0x7A5C, 0xB773, 0x7A5D, 0xB774, 0x7A5E, 0xB775, 0x7A5F, 0xB776, 0x7A60, 0xB777, 0x7A61, 0xB778, 0x7A62, + 0xB779, 0x7A63, 0xB77A, 0x7A64, 0xB77B, 0x7A65, 0xB77C, 0x7A66, 0xB77D, 0x7A67, 0xB77E, 0x7A68, 0xB780, 0x7A69, 0xB781, 0x7A6A, + 0xB782, 0x7A6B, 0xB783, 0x7A6C, 0xB784, 0x7A6D, 0xB785, 0x7A6E, 0xB786, 0x7A6F, 0xB787, 0x7A71, 0xB788, 0x7A72, 0xB789, 0x7A73, + 0xB78A, 0x7A75, 0xB78B, 0x7A7B, 0xB78C, 0x7A7C, 0xB78D, 0x7A7D, 0xB78E, 0x7A7E, 0xB78F, 0x7A82, 0xB790, 0x7A85, 0xB791, 0x7A87, + 0xB792, 0x7A89, 0xB793, 0x7A8A, 0xB794, 0x7A8B, 0xB795, 0x7A8C, 0xB796, 0x7A8E, 0xB797, 0x7A8F, 0xB798, 0x7A90, 0xB799, 0x7A93, + 0xB79A, 0x7A94, 0xB79B, 0x7A99, 0xB79C, 0x7A9A, 0xB79D, 0x7A9B, 0xB79E, 0x7A9E, 0xB79F, 0x7AA1, 0xB7A0, 0x7AA2, 0xB7A1, 0x8D30, + 0xB7A2, 0x53D1, 0xB7A3, 0x7F5A, 0xB7A4, 0x7B4F, 0xB7A5, 0x4F10, 0xB7A6, 0x4E4F, 0xB7A7, 0x9600, 0xB7A8, 0x6CD5, 0xB7A9, 0x73D0, + 0xB7AA, 0x85E9, 0xB7AB, 0x5E06, 0xB7AC, 0x756A, 0xB7AD, 0x7FFB, 0xB7AE, 0x6A0A, 0xB7AF, 0x77FE, 0xB7B0, 0x9492, 0xB7B1, 0x7E41, + 0xB7B2, 0x51E1, 0xB7B3, 0x70E6, 0xB7B4, 0x53CD, 0xB7B5, 0x8FD4, 0xB7B6, 0x8303, 0xB7B7, 0x8D29, 0xB7B8, 0x72AF, 0xB7B9, 0x996D, + 0xB7BA, 0x6CDB, 0xB7BB, 0x574A, 0xB7BC, 0x82B3, 0xB7BD, 0x65B9, 0xB7BE, 0x80AA, 0xB7BF, 0x623F, 0xB7C0, 0x9632, 0xB7C1, 0x59A8, + 0xB7C2, 0x4EFF, 0xB7C3, 0x8BBF, 0xB7C4, 0x7EBA, 0xB7C5, 0x653E, 0xB7C6, 0x83F2, 0xB7C7, 0x975E, 0xB7C8, 0x5561, 0xB7C9, 0x98DE, + 0xB7CA, 0x80A5, 0xB7CB, 0x532A, 0xB7CC, 0x8BFD, 0xB7CD, 0x5420, 0xB7CE, 0x80BA, 0xB7CF, 0x5E9F, 0xB7D0, 0x6CB8, 0xB7D1, 0x8D39, + 0xB7D2, 0x82AC, 0xB7D3, 0x915A, 0xB7D4, 0x5429, 0xB7D5, 0x6C1B, 0xB7D6, 0x5206, 0xB7D7, 0x7EB7, 0xB7D8, 0x575F, 0xB7D9, 0x711A, + 0xB7DA, 0x6C7E, 0xB7DB, 0x7C89, 0xB7DC, 0x594B, 0xB7DD, 0x4EFD, 0xB7DE, 0x5FFF, 0xB7DF, 0x6124, 0xB7E0, 0x7CAA, 0xB7E1, 0x4E30, + 0xB7E2, 0x5C01, 0xB7E3, 0x67AB, 0xB7E4, 0x8702, 0xB7E5, 0x5CF0, 0xB7E6, 0x950B, 0xB7E7, 0x98CE, 0xB7E8, 0x75AF, 0xB7E9, 0x70FD, + 0xB7EA, 0x9022, 0xB7EB, 0x51AF, 0xB7EC, 0x7F1D, 0xB7ED, 0x8BBD, 0xB7EE, 0x5949, 0xB7EF, 0x51E4, 0xB7F0, 0x4F5B, 0xB7F1, 0x5426, + 0xB7F2, 0x592B, 0xB7F3, 0x6577, 0xB7F4, 0x80A4, 0xB7F5, 0x5B75, 0xB7F6, 0x6276, 0xB7F7, 0x62C2, 0xB7F8, 0x8F90, 0xB7F9, 0x5E45, + 0xB7FA, 0x6C1F, 0xB7FB, 0x7B26, 0xB7FC, 0x4F0F, 0xB7FD, 0x4FD8, 0xB7FE, 0x670D, 0xB840, 0x7AA3, 0xB841, 0x7AA4, 0xB842, 0x7AA7, + 0xB843, 0x7AA9, 0xB844, 0x7AAA, 0xB845, 0x7AAB, 0xB846, 0x7AAE, 0xB847, 0x7AAF, 0xB848, 0x7AB0, 0xB849, 0x7AB1, 0xB84A, 0x7AB2, + 0xB84B, 0x7AB4, 0xB84C, 0x7AB5, 0xB84D, 0x7AB6, 0xB84E, 0x7AB7, 0xB84F, 0x7AB8, 0xB850, 0x7AB9, 0xB851, 0x7ABA, 0xB852, 0x7ABB, + 0xB853, 0x7ABC, 0xB854, 0x7ABD, 0xB855, 0x7ABE, 0xB856, 0x7AC0, 0xB857, 0x7AC1, 0xB858, 0x7AC2, 0xB859, 0x7AC3, 0xB85A, 0x7AC4, + 0xB85B, 0x7AC5, 0xB85C, 0x7AC6, 0xB85D, 0x7AC7, 0xB85E, 0x7AC8, 0xB85F, 0x7AC9, 0xB860, 0x7ACA, 0xB861, 0x7ACC, 0xB862, 0x7ACD, + 0xB863, 0x7ACE, 0xB864, 0x7ACF, 0xB865, 0x7AD0, 0xB866, 0x7AD1, 0xB867, 0x7AD2, 0xB868, 0x7AD3, 0xB869, 0x7AD4, 0xB86A, 0x7AD5, + 0xB86B, 0x7AD7, 0xB86C, 0x7AD8, 0xB86D, 0x7ADA, 0xB86E, 0x7ADB, 0xB86F, 0x7ADC, 0xB870, 0x7ADD, 0xB871, 0x7AE1, 0xB872, 0x7AE2, + 0xB873, 0x7AE4, 0xB874, 0x7AE7, 0xB875, 0x7AE8, 0xB876, 0x7AE9, 0xB877, 0x7AEA, 0xB878, 0x7AEB, 0xB879, 0x7AEC, 0xB87A, 0x7AEE, + 0xB87B, 0x7AF0, 0xB87C, 0x7AF1, 0xB87D, 0x7AF2, 0xB87E, 0x7AF3, 0xB880, 0x7AF4, 0xB881, 0x7AF5, 0xB882, 0x7AF6, 0xB883, 0x7AF7, + 0xB884, 0x7AF8, 0xB885, 0x7AFB, 0xB886, 0x7AFC, 0xB887, 0x7AFE, 0xB888, 0x7B00, 0xB889, 0x7B01, 0xB88A, 0x7B02, 0xB88B, 0x7B05, + 0xB88C, 0x7B07, 0xB88D, 0x7B09, 0xB88E, 0x7B0C, 0xB88F, 0x7B0D, 0xB890, 0x7B0E, 0xB891, 0x7B10, 0xB892, 0x7B12, 0xB893, 0x7B13, + 0xB894, 0x7B16, 0xB895, 0x7B17, 0xB896, 0x7B18, 0xB897, 0x7B1A, 0xB898, 0x7B1C, 0xB899, 0x7B1D, 0xB89A, 0x7B1F, 0xB89B, 0x7B21, + 0xB89C, 0x7B22, 0xB89D, 0x7B23, 0xB89E, 0x7B27, 0xB89F, 0x7B29, 0xB8A0, 0x7B2D, 0xB8A1, 0x6D6E, 0xB8A2, 0x6DAA, 0xB8A3, 0x798F, + 0xB8A4, 0x88B1, 0xB8A5, 0x5F17, 0xB8A6, 0x752B, 0xB8A7, 0x629A, 0xB8A8, 0x8F85, 0xB8A9, 0x4FEF, 0xB8AA, 0x91DC, 0xB8AB, 0x65A7, + 0xB8AC, 0x812F, 0xB8AD, 0x8151, 0xB8AE, 0x5E9C, 0xB8AF, 0x8150, 0xB8B0, 0x8D74, 0xB8B1, 0x526F, 0xB8B2, 0x8986, 0xB8B3, 0x8D4B, + 0xB8B4, 0x590D, 0xB8B5, 0x5085, 0xB8B6, 0x4ED8, 0xB8B7, 0x961C, 0xB8B8, 0x7236, 0xB8B9, 0x8179, 0xB8BA, 0x8D1F, 0xB8BB, 0x5BCC, + 0xB8BC, 0x8BA3, 0xB8BD, 0x9644, 0xB8BE, 0x5987, 0xB8BF, 0x7F1A, 0xB8C0, 0x5490, 0xB8C1, 0x5676, 0xB8C2, 0x560E, 0xB8C3, 0x8BE5, + 0xB8C4, 0x6539, 0xB8C5, 0x6982, 0xB8C6, 0x9499, 0xB8C7, 0x76D6, 0xB8C8, 0x6E89, 0xB8C9, 0x5E72, 0xB8CA, 0x7518, 0xB8CB, 0x6746, + 0xB8CC, 0x67D1, 0xB8CD, 0x7AFF, 0xB8CE, 0x809D, 0xB8CF, 0x8D76, 0xB8D0, 0x611F, 0xB8D1, 0x79C6, 0xB8D2, 0x6562, 0xB8D3, 0x8D63, + 0xB8D4, 0x5188, 0xB8D5, 0x521A, 0xB8D6, 0x94A2, 0xB8D7, 0x7F38, 0xB8D8, 0x809B, 0xB8D9, 0x7EB2, 0xB8DA, 0x5C97, 0xB8DB, 0x6E2F, + 0xB8DC, 0x6760, 0xB8DD, 0x7BD9, 0xB8DE, 0x768B, 0xB8DF, 0x9AD8, 0xB8E0, 0x818F, 0xB8E1, 0x7F94, 0xB8E2, 0x7CD5, 0xB8E3, 0x641E, + 0xB8E4, 0x9550, 0xB8E5, 0x7A3F, 0xB8E6, 0x544A, 0xB8E7, 0x54E5, 0xB8E8, 0x6B4C, 0xB8E9, 0x6401, 0xB8EA, 0x6208, 0xB8EB, 0x9E3D, + 0xB8EC, 0x80F3, 0xB8ED, 0x7599, 0xB8EE, 0x5272, 0xB8EF, 0x9769, 0xB8F0, 0x845B, 0xB8F1, 0x683C, 0xB8F2, 0x86E4, 0xB8F3, 0x9601, + 0xB8F4, 0x9694, 0xB8F5, 0x94EC, 0xB8F6, 0x4E2A, 0xB8F7, 0x5404, 0xB8F8, 0x7ED9, 0xB8F9, 0x6839, 0xB8FA, 0x8DDF, 0xB8FB, 0x8015, + 0xB8FC, 0x66F4, 0xB8FD, 0x5E9A, 0xB8FE, 0x7FB9, 0xB940, 0x7B2F, 0xB941, 0x7B30, 0xB942, 0x7B32, 0xB943, 0x7B34, 0xB944, 0x7B35, + 0xB945, 0x7B36, 0xB946, 0x7B37, 0xB947, 0x7B39, 0xB948, 0x7B3B, 0xB949, 0x7B3D, 0xB94A, 0x7B3F, 0xB94B, 0x7B40, 0xB94C, 0x7B41, + 0xB94D, 0x7B42, 0xB94E, 0x7B43, 0xB94F, 0x7B44, 0xB950, 0x7B46, 0xB951, 0x7B48, 0xB952, 0x7B4A, 0xB953, 0x7B4D, 0xB954, 0x7B4E, + 0xB955, 0x7B53, 0xB956, 0x7B55, 0xB957, 0x7B57, 0xB958, 0x7B59, 0xB959, 0x7B5C, 0xB95A, 0x7B5E, 0xB95B, 0x7B5F, 0xB95C, 0x7B61, + 0xB95D, 0x7B63, 0xB95E, 0x7B64, 0xB95F, 0x7B65, 0xB960, 0x7B66, 0xB961, 0x7B67, 0xB962, 0x7B68, 0xB963, 0x7B69, 0xB964, 0x7B6A, + 0xB965, 0x7B6B, 0xB966, 0x7B6C, 0xB967, 0x7B6D, 0xB968, 0x7B6F, 0xB969, 0x7B70, 0xB96A, 0x7B73, 0xB96B, 0x7B74, 0xB96C, 0x7B76, + 0xB96D, 0x7B78, 0xB96E, 0x7B7A, 0xB96F, 0x7B7C, 0xB970, 0x7B7D, 0xB971, 0x7B7F, 0xB972, 0x7B81, 0xB973, 0x7B82, 0xB974, 0x7B83, + 0xB975, 0x7B84, 0xB976, 0x7B86, 0xB977, 0x7B87, 0xB978, 0x7B88, 0xB979, 0x7B89, 0xB97A, 0x7B8A, 0xB97B, 0x7B8B, 0xB97C, 0x7B8C, + 0xB97D, 0x7B8E, 0xB97E, 0x7B8F, 0xB980, 0x7B91, 0xB981, 0x7B92, 0xB982, 0x7B93, 0xB983, 0x7B96, 0xB984, 0x7B98, 0xB985, 0x7B99, + 0xB986, 0x7B9A, 0xB987, 0x7B9B, 0xB988, 0x7B9E, 0xB989, 0x7B9F, 0xB98A, 0x7BA0, 0xB98B, 0x7BA3, 0xB98C, 0x7BA4, 0xB98D, 0x7BA5, + 0xB98E, 0x7BAE, 0xB98F, 0x7BAF, 0xB990, 0x7BB0, 0xB991, 0x7BB2, 0xB992, 0x7BB3, 0xB993, 0x7BB5, 0xB994, 0x7BB6, 0xB995, 0x7BB7, + 0xB996, 0x7BB9, 0xB997, 0x7BBA, 0xB998, 0x7BBB, 0xB999, 0x7BBC, 0xB99A, 0x7BBD, 0xB99B, 0x7BBE, 0xB99C, 0x7BBF, 0xB99D, 0x7BC0, + 0xB99E, 0x7BC2, 0xB99F, 0x7BC3, 0xB9A0, 0x7BC4, 0xB9A1, 0x57C2, 0xB9A2, 0x803F, 0xB9A3, 0x6897, 0xB9A4, 0x5DE5, 0xB9A5, 0x653B, + 0xB9A6, 0x529F, 0xB9A7, 0x606D, 0xB9A8, 0x9F9A, 0xB9A9, 0x4F9B, 0xB9AA, 0x8EAC, 0xB9AB, 0x516C, 0xB9AC, 0x5BAB, 0xB9AD, 0x5F13, + 0xB9AE, 0x5DE9, 0xB9AF, 0x6C5E, 0xB9B0, 0x62F1, 0xB9B1, 0x8D21, 0xB9B2, 0x5171, 0xB9B3, 0x94A9, 0xB9B4, 0x52FE, 0xB9B5, 0x6C9F, + 0xB9B6, 0x82DF, 0xB9B7, 0x72D7, 0xB9B8, 0x57A2, 0xB9B9, 0x6784, 0xB9BA, 0x8D2D, 0xB9BB, 0x591F, 0xB9BC, 0x8F9C, 0xB9BD, 0x83C7, + 0xB9BE, 0x5495, 0xB9BF, 0x7B8D, 0xB9C0, 0x4F30, 0xB9C1, 0x6CBD, 0xB9C2, 0x5B64, 0xB9C3, 0x59D1, 0xB9C4, 0x9F13, 0xB9C5, 0x53E4, + 0xB9C6, 0x86CA, 0xB9C7, 0x9AA8, 0xB9C8, 0x8C37, 0xB9C9, 0x80A1, 0xB9CA, 0x6545, 0xB9CB, 0x987E, 0xB9CC, 0x56FA, 0xB9CD, 0x96C7, + 0xB9CE, 0x522E, 0xB9CF, 0x74DC, 0xB9D0, 0x5250, 0xB9D1, 0x5BE1, 0xB9D2, 0x6302, 0xB9D3, 0x8902, 0xB9D4, 0x4E56, 0xB9D5, 0x62D0, + 0xB9D6, 0x602A, 0xB9D7, 0x68FA, 0xB9D8, 0x5173, 0xB9D9, 0x5B98, 0xB9DA, 0x51A0, 0xB9DB, 0x89C2, 0xB9DC, 0x7BA1, 0xB9DD, 0x9986, + 0xB9DE, 0x7F50, 0xB9DF, 0x60EF, 0xB9E0, 0x704C, 0xB9E1, 0x8D2F, 0xB9E2, 0x5149, 0xB9E3, 0x5E7F, 0xB9E4, 0x901B, 0xB9E5, 0x7470, + 0xB9E6, 0x89C4, 0xB9E7, 0x572D, 0xB9E8, 0x7845, 0xB9E9, 0x5F52, 0xB9EA, 0x9F9F, 0xB9EB, 0x95FA, 0xB9EC, 0x8F68, 0xB9ED, 0x9B3C, + 0xB9EE, 0x8BE1, 0xB9EF, 0x7678, 0xB9F0, 0x6842, 0xB9F1, 0x67DC, 0xB9F2, 0x8DEA, 0xB9F3, 0x8D35, 0xB9F4, 0x523D, 0xB9F5, 0x8F8A, + 0xB9F6, 0x6EDA, 0xB9F7, 0x68CD, 0xB9F8, 0x9505, 0xB9F9, 0x90ED, 0xB9FA, 0x56FD, 0xB9FB, 0x679C, 0xB9FC, 0x88F9, 0xB9FD, 0x8FC7, + 0xB9FE, 0x54C8, 0xBA40, 0x7BC5, 0xBA41, 0x7BC8, 0xBA42, 0x7BC9, 0xBA43, 0x7BCA, 0xBA44, 0x7BCB, 0xBA45, 0x7BCD, 0xBA46, 0x7BCE, + 0xBA47, 0x7BCF, 0xBA48, 0x7BD0, 0xBA49, 0x7BD2, 0xBA4A, 0x7BD4, 0xBA4B, 0x7BD5, 0xBA4C, 0x7BD6, 0xBA4D, 0x7BD7, 0xBA4E, 0x7BD8, + 0xBA4F, 0x7BDB, 0xBA50, 0x7BDC, 0xBA51, 0x7BDE, 0xBA52, 0x7BDF, 0xBA53, 0x7BE0, 0xBA54, 0x7BE2, 0xBA55, 0x7BE3, 0xBA56, 0x7BE4, + 0xBA57, 0x7BE7, 0xBA58, 0x7BE8, 0xBA59, 0x7BE9, 0xBA5A, 0x7BEB, 0xBA5B, 0x7BEC, 0xBA5C, 0x7BED, 0xBA5D, 0x7BEF, 0xBA5E, 0x7BF0, + 0xBA5F, 0x7BF2, 0xBA60, 0x7BF3, 0xBA61, 0x7BF4, 0xBA62, 0x7BF5, 0xBA63, 0x7BF6, 0xBA64, 0x7BF8, 0xBA65, 0x7BF9, 0xBA66, 0x7BFA, + 0xBA67, 0x7BFB, 0xBA68, 0x7BFD, 0xBA69, 0x7BFF, 0xBA6A, 0x7C00, 0xBA6B, 0x7C01, 0xBA6C, 0x7C02, 0xBA6D, 0x7C03, 0xBA6E, 0x7C04, + 0xBA6F, 0x7C05, 0xBA70, 0x7C06, 0xBA71, 0x7C08, 0xBA72, 0x7C09, 0xBA73, 0x7C0A, 0xBA74, 0x7C0D, 0xBA75, 0x7C0E, 0xBA76, 0x7C10, + 0xBA77, 0x7C11, 0xBA78, 0x7C12, 0xBA79, 0x7C13, 0xBA7A, 0x7C14, 0xBA7B, 0x7C15, 0xBA7C, 0x7C17, 0xBA7D, 0x7C18, 0xBA7E, 0x7C19, + 0xBA80, 0x7C1A, 0xBA81, 0x7C1B, 0xBA82, 0x7C1C, 0xBA83, 0x7C1D, 0xBA84, 0x7C1E, 0xBA85, 0x7C20, 0xBA86, 0x7C21, 0xBA87, 0x7C22, + 0xBA88, 0x7C23, 0xBA89, 0x7C24, 0xBA8A, 0x7C25, 0xBA8B, 0x7C28, 0xBA8C, 0x7C29, 0xBA8D, 0x7C2B, 0xBA8E, 0x7C2C, 0xBA8F, 0x7C2D, + 0xBA90, 0x7C2E, 0xBA91, 0x7C2F, 0xBA92, 0x7C30, 0xBA93, 0x7C31, 0xBA94, 0x7C32, 0xBA95, 0x7C33, 0xBA96, 0x7C34, 0xBA97, 0x7C35, + 0xBA98, 0x7C36, 0xBA99, 0x7C37, 0xBA9A, 0x7C39, 0xBA9B, 0x7C3A, 0xBA9C, 0x7C3B, 0xBA9D, 0x7C3C, 0xBA9E, 0x7C3D, 0xBA9F, 0x7C3E, + 0xBAA0, 0x7C42, 0xBAA1, 0x9AB8, 0xBAA2, 0x5B69, 0xBAA3, 0x6D77, 0xBAA4, 0x6C26, 0xBAA5, 0x4EA5, 0xBAA6, 0x5BB3, 0xBAA7, 0x9A87, + 0xBAA8, 0x9163, 0xBAA9, 0x61A8, 0xBAAA, 0x90AF, 0xBAAB, 0x97E9, 0xBAAC, 0x542B, 0xBAAD, 0x6DB5, 0xBAAE, 0x5BD2, 0xBAAF, 0x51FD, + 0xBAB0, 0x558A, 0xBAB1, 0x7F55, 0xBAB2, 0x7FF0, 0xBAB3, 0x64BC, 0xBAB4, 0x634D, 0xBAB5, 0x65F1, 0xBAB6, 0x61BE, 0xBAB7, 0x608D, + 0xBAB8, 0x710A, 0xBAB9, 0x6C57, 0xBABA, 0x6C49, 0xBABB, 0x592F, 0xBABC, 0x676D, 0xBABD, 0x822A, 0xBABE, 0x58D5, 0xBABF, 0x568E, + 0xBAC0, 0x8C6A, 0xBAC1, 0x6BEB, 0xBAC2, 0x90DD, 0xBAC3, 0x597D, 0xBAC4, 0x8017, 0xBAC5, 0x53F7, 0xBAC6, 0x6D69, 0xBAC7, 0x5475, + 0xBAC8, 0x559D, 0xBAC9, 0x8377, 0xBACA, 0x83CF, 0xBACB, 0x6838, 0xBACC, 0x79BE, 0xBACD, 0x548C, 0xBACE, 0x4F55, 0xBACF, 0x5408, + 0xBAD0, 0x76D2, 0xBAD1, 0x8C89, 0xBAD2, 0x9602, 0xBAD3, 0x6CB3, 0xBAD4, 0x6DB8, 0xBAD5, 0x8D6B, 0xBAD6, 0x8910, 0xBAD7, 0x9E64, + 0xBAD8, 0x8D3A, 0xBAD9, 0x563F, 0xBADA, 0x9ED1, 0xBADB, 0x75D5, 0xBADC, 0x5F88, 0xBADD, 0x72E0, 0xBADE, 0x6068, 0xBADF, 0x54FC, + 0xBAE0, 0x4EA8, 0xBAE1, 0x6A2A, 0xBAE2, 0x8861, 0xBAE3, 0x6052, 0xBAE4, 0x8F70, 0xBAE5, 0x54C4, 0xBAE6, 0x70D8, 0xBAE7, 0x8679, + 0xBAE8, 0x9E3F, 0xBAE9, 0x6D2A, 0xBAEA, 0x5B8F, 0xBAEB, 0x5F18, 0xBAEC, 0x7EA2, 0xBAED, 0x5589, 0xBAEE, 0x4FAF, 0xBAEF, 0x7334, + 0xBAF0, 0x543C, 0xBAF1, 0x539A, 0xBAF2, 0x5019, 0xBAF3, 0x540E, 0xBAF4, 0x547C, 0xBAF5, 0x4E4E, 0xBAF6, 0x5FFD, 0xBAF7, 0x745A, + 0xBAF8, 0x58F6, 0xBAF9, 0x846B, 0xBAFA, 0x80E1, 0xBAFB, 0x8774, 0xBAFC, 0x72D0, 0xBAFD, 0x7CCA, 0xBAFE, 0x6E56, 0xBB40, 0x7C43, + 0xBB41, 0x7C44, 0xBB42, 0x7C45, 0xBB43, 0x7C46, 0xBB44, 0x7C47, 0xBB45, 0x7C48, 0xBB46, 0x7C49, 0xBB47, 0x7C4A, 0xBB48, 0x7C4B, + 0xBB49, 0x7C4C, 0xBB4A, 0x7C4E, 0xBB4B, 0x7C4F, 0xBB4C, 0x7C50, 0xBB4D, 0x7C51, 0xBB4E, 0x7C52, 0xBB4F, 0x7C53, 0xBB50, 0x7C54, + 0xBB51, 0x7C55, 0xBB52, 0x7C56, 0xBB53, 0x7C57, 0xBB54, 0x7C58, 0xBB55, 0x7C59, 0xBB56, 0x7C5A, 0xBB57, 0x7C5B, 0xBB58, 0x7C5C, + 0xBB59, 0x7C5D, 0xBB5A, 0x7C5E, 0xBB5B, 0x7C5F, 0xBB5C, 0x7C60, 0xBB5D, 0x7C61, 0xBB5E, 0x7C62, 0xBB5F, 0x7C63, 0xBB60, 0x7C64, + 0xBB61, 0x7C65, 0xBB62, 0x7C66, 0xBB63, 0x7C67, 0xBB64, 0x7C68, 0xBB65, 0x7C69, 0xBB66, 0x7C6A, 0xBB67, 0x7C6B, 0xBB68, 0x7C6C, + 0xBB69, 0x7C6D, 0xBB6A, 0x7C6E, 0xBB6B, 0x7C6F, 0xBB6C, 0x7C70, 0xBB6D, 0x7C71, 0xBB6E, 0x7C72, 0xBB6F, 0x7C75, 0xBB70, 0x7C76, + 0xBB71, 0x7C77, 0xBB72, 0x7C78, 0xBB73, 0x7C79, 0xBB74, 0x7C7A, 0xBB75, 0x7C7E, 0xBB76, 0x7C7F, 0xBB77, 0x7C80, 0xBB78, 0x7C81, + 0xBB79, 0x7C82, 0xBB7A, 0x7C83, 0xBB7B, 0x7C84, 0xBB7C, 0x7C85, 0xBB7D, 0x7C86, 0xBB7E, 0x7C87, 0xBB80, 0x7C88, 0xBB81, 0x7C8A, + 0xBB82, 0x7C8B, 0xBB83, 0x7C8C, 0xBB84, 0x7C8D, 0xBB85, 0x7C8E, 0xBB86, 0x7C8F, 0xBB87, 0x7C90, 0xBB88, 0x7C93, 0xBB89, 0x7C94, + 0xBB8A, 0x7C96, 0xBB8B, 0x7C99, 0xBB8C, 0x7C9A, 0xBB8D, 0x7C9B, 0xBB8E, 0x7CA0, 0xBB8F, 0x7CA1, 0xBB90, 0x7CA3, 0xBB91, 0x7CA6, + 0xBB92, 0x7CA7, 0xBB93, 0x7CA8, 0xBB94, 0x7CA9, 0xBB95, 0x7CAB, 0xBB96, 0x7CAC, 0xBB97, 0x7CAD, 0xBB98, 0x7CAF, 0xBB99, 0x7CB0, + 0xBB9A, 0x7CB4, 0xBB9B, 0x7CB5, 0xBB9C, 0x7CB6, 0xBB9D, 0x7CB7, 0xBB9E, 0x7CB8, 0xBB9F, 0x7CBA, 0xBBA0, 0x7CBB, 0xBBA1, 0x5F27, + 0xBBA2, 0x864E, 0xBBA3, 0x552C, 0xBBA4, 0x62A4, 0xBBA5, 0x4E92, 0xBBA6, 0x6CAA, 0xBBA7, 0x6237, 0xBBA8, 0x82B1, 0xBBA9, 0x54D7, + 0xBBAA, 0x534E, 0xBBAB, 0x733E, 0xBBAC, 0x6ED1, 0xBBAD, 0x753B, 0xBBAE, 0x5212, 0xBBAF, 0x5316, 0xBBB0, 0x8BDD, 0xBBB1, 0x69D0, + 0xBBB2, 0x5F8A, 0xBBB3, 0x6000, 0xBBB4, 0x6DEE, 0xBBB5, 0x574F, 0xBBB6, 0x6B22, 0xBBB7, 0x73AF, 0xBBB8, 0x6853, 0xBBB9, 0x8FD8, + 0xBBBA, 0x7F13, 0xBBBB, 0x6362, 0xBBBC, 0x60A3, 0xBBBD, 0x5524, 0xBBBE, 0x75EA, 0xBBBF, 0x8C62, 0xBBC0, 0x7115, 0xBBC1, 0x6DA3, + 0xBBC2, 0x5BA6, 0xBBC3, 0x5E7B, 0xBBC4, 0x8352, 0xBBC5, 0x614C, 0xBBC6, 0x9EC4, 0xBBC7, 0x78FA, 0xBBC8, 0x8757, 0xBBC9, 0x7C27, + 0xBBCA, 0x7687, 0xBBCB, 0x51F0, 0xBBCC, 0x60F6, 0xBBCD, 0x714C, 0xBBCE, 0x6643, 0xBBCF, 0x5E4C, 0xBBD0, 0x604D, 0xBBD1, 0x8C0E, + 0xBBD2, 0x7070, 0xBBD3, 0x6325, 0xBBD4, 0x8F89, 0xBBD5, 0x5FBD, 0xBBD6, 0x6062, 0xBBD7, 0x86D4, 0xBBD8, 0x56DE, 0xBBD9, 0x6BC1, + 0xBBDA, 0x6094, 0xBBDB, 0x6167, 0xBBDC, 0x5349, 0xBBDD, 0x60E0, 0xBBDE, 0x6666, 0xBBDF, 0x8D3F, 0xBBE0, 0x79FD, 0xBBE1, 0x4F1A, + 0xBBE2, 0x70E9, 0xBBE3, 0x6C47, 0xBBE4, 0x8BB3, 0xBBE5, 0x8BF2, 0xBBE6, 0x7ED8, 0xBBE7, 0x8364, 0xBBE8, 0x660F, 0xBBE9, 0x5A5A, + 0xBBEA, 0x9B42, 0xBBEB, 0x6D51, 0xBBEC, 0x6DF7, 0xBBED, 0x8C41, 0xBBEE, 0x6D3B, 0xBBEF, 0x4F19, 0xBBF0, 0x706B, 0xBBF1, 0x83B7, + 0xBBF2, 0x6216, 0xBBF3, 0x60D1, 0xBBF4, 0x970D, 0xBBF5, 0x8D27, 0xBBF6, 0x7978, 0xBBF7, 0x51FB, 0xBBF8, 0x573E, 0xBBF9, 0x57FA, + 0xBBFA, 0x673A, 0xBBFB, 0x7578, 0xBBFC, 0x7A3D, 0xBBFD, 0x79EF, 0xBBFE, 0x7B95, 0xBC40, 0x7CBF, 0xBC41, 0x7CC0, 0xBC42, 0x7CC2, + 0xBC43, 0x7CC3, 0xBC44, 0x7CC4, 0xBC45, 0x7CC6, 0xBC46, 0x7CC9, 0xBC47, 0x7CCB, 0xBC48, 0x7CCE, 0xBC49, 0x7CCF, 0xBC4A, 0x7CD0, + 0xBC4B, 0x7CD1, 0xBC4C, 0x7CD2, 0xBC4D, 0x7CD3, 0xBC4E, 0x7CD4, 0xBC4F, 0x7CD8, 0xBC50, 0x7CDA, 0xBC51, 0x7CDB, 0xBC52, 0x7CDD, + 0xBC53, 0x7CDE, 0xBC54, 0x7CE1, 0xBC55, 0x7CE2, 0xBC56, 0x7CE3, 0xBC57, 0x7CE4, 0xBC58, 0x7CE5, 0xBC59, 0x7CE6, 0xBC5A, 0x7CE7, + 0xBC5B, 0x7CE9, 0xBC5C, 0x7CEA, 0xBC5D, 0x7CEB, 0xBC5E, 0x7CEC, 0xBC5F, 0x7CED, 0xBC60, 0x7CEE, 0xBC61, 0x7CF0, 0xBC62, 0x7CF1, + 0xBC63, 0x7CF2, 0xBC64, 0x7CF3, 0xBC65, 0x7CF4, 0xBC66, 0x7CF5, 0xBC67, 0x7CF6, 0xBC68, 0x7CF7, 0xBC69, 0x7CF9, 0xBC6A, 0x7CFA, + 0xBC6B, 0x7CFC, 0xBC6C, 0x7CFD, 0xBC6D, 0x7CFE, 0xBC6E, 0x7CFF, 0xBC6F, 0x7D00, 0xBC70, 0x7D01, 0xBC71, 0x7D02, 0xBC72, 0x7D03, + 0xBC73, 0x7D04, 0xBC74, 0x7D05, 0xBC75, 0x7D06, 0xBC76, 0x7D07, 0xBC77, 0x7D08, 0xBC78, 0x7D09, 0xBC79, 0x7D0B, 0xBC7A, 0x7D0C, + 0xBC7B, 0x7D0D, 0xBC7C, 0x7D0E, 0xBC7D, 0x7D0F, 0xBC7E, 0x7D10, 0xBC80, 0x7D11, 0xBC81, 0x7D12, 0xBC82, 0x7D13, 0xBC83, 0x7D14, + 0xBC84, 0x7D15, 0xBC85, 0x7D16, 0xBC86, 0x7D17, 0xBC87, 0x7D18, 0xBC88, 0x7D19, 0xBC89, 0x7D1A, 0xBC8A, 0x7D1B, 0xBC8B, 0x7D1C, + 0xBC8C, 0x7D1D, 0xBC8D, 0x7D1E, 0xBC8E, 0x7D1F, 0xBC8F, 0x7D21, 0xBC90, 0x7D23, 0xBC91, 0x7D24, 0xBC92, 0x7D25, 0xBC93, 0x7D26, + 0xBC94, 0x7D28, 0xBC95, 0x7D29, 0xBC96, 0x7D2A, 0xBC97, 0x7D2C, 0xBC98, 0x7D2D, 0xBC99, 0x7D2E, 0xBC9A, 0x7D30, 0xBC9B, 0x7D31, + 0xBC9C, 0x7D32, 0xBC9D, 0x7D33, 0xBC9E, 0x7D34, 0xBC9F, 0x7D35, 0xBCA0, 0x7D36, 0xBCA1, 0x808C, 0xBCA2, 0x9965, 0xBCA3, 0x8FF9, + 0xBCA4, 0x6FC0, 0xBCA5, 0x8BA5, 0xBCA6, 0x9E21, 0xBCA7, 0x59EC, 0xBCA8, 0x7EE9, 0xBCA9, 0x7F09, 0xBCAA, 0x5409, 0xBCAB, 0x6781, + 0xBCAC, 0x68D8, 0xBCAD, 0x8F91, 0xBCAE, 0x7C4D, 0xBCAF, 0x96C6, 0xBCB0, 0x53CA, 0xBCB1, 0x6025, 0xBCB2, 0x75BE, 0xBCB3, 0x6C72, + 0xBCB4, 0x5373, 0xBCB5, 0x5AC9, 0xBCB6, 0x7EA7, 0xBCB7, 0x6324, 0xBCB8, 0x51E0, 0xBCB9, 0x810A, 0xBCBA, 0x5DF1, 0xBCBB, 0x84DF, + 0xBCBC, 0x6280, 0xBCBD, 0x5180, 0xBCBE, 0x5B63, 0xBCBF, 0x4F0E, 0xBCC0, 0x796D, 0xBCC1, 0x5242, 0xBCC2, 0x60B8, 0xBCC3, 0x6D4E, + 0xBCC4, 0x5BC4, 0xBCC5, 0x5BC2, 0xBCC6, 0x8BA1, 0xBCC7, 0x8BB0, 0xBCC8, 0x65E2, 0xBCC9, 0x5FCC, 0xBCCA, 0x9645, 0xBCCB, 0x5993, + 0xBCCC, 0x7EE7, 0xBCCD, 0x7EAA, 0xBCCE, 0x5609, 0xBCCF, 0x67B7, 0xBCD0, 0x5939, 0xBCD1, 0x4F73, 0xBCD2, 0x5BB6, 0xBCD3, 0x52A0, + 0xBCD4, 0x835A, 0xBCD5, 0x988A, 0xBCD6, 0x8D3E, 0xBCD7, 0x7532, 0xBCD8, 0x94BE, 0xBCD9, 0x5047, 0xBCDA, 0x7A3C, 0xBCDB, 0x4EF7, + 0xBCDC, 0x67B6, 0xBCDD, 0x9A7E, 0xBCDE, 0x5AC1, 0xBCDF, 0x6B7C, 0xBCE0, 0x76D1, 0xBCE1, 0x575A, 0xBCE2, 0x5C16, 0xBCE3, 0x7B3A, + 0xBCE4, 0x95F4, 0xBCE5, 0x714E, 0xBCE6, 0x517C, 0xBCE7, 0x80A9, 0xBCE8, 0x8270, 0xBCE9, 0x5978, 0xBCEA, 0x7F04, 0xBCEB, 0x8327, + 0xBCEC, 0x68C0, 0xBCED, 0x67EC, 0xBCEE, 0x78B1, 0xBCEF, 0x7877, 0xBCF0, 0x62E3, 0xBCF1, 0x6361, 0xBCF2, 0x7B80, 0xBCF3, 0x4FED, + 0xBCF4, 0x526A, 0xBCF5, 0x51CF, 0xBCF6, 0x8350, 0xBCF7, 0x69DB, 0xBCF8, 0x9274, 0xBCF9, 0x8DF5, 0xBCFA, 0x8D31, 0xBCFB, 0x89C1, + 0xBCFC, 0x952E, 0xBCFD, 0x7BAD, 0xBCFE, 0x4EF6, 0xBD40, 0x7D37, 0xBD41, 0x7D38, 0xBD42, 0x7D39, 0xBD43, 0x7D3A, 0xBD44, 0x7D3B, + 0xBD45, 0x7D3C, 0xBD46, 0x7D3D, 0xBD47, 0x7D3E, 0xBD48, 0x7D3F, 0xBD49, 0x7D40, 0xBD4A, 0x7D41, 0xBD4B, 0x7D42, 0xBD4C, 0x7D43, + 0xBD4D, 0x7D44, 0xBD4E, 0x7D45, 0xBD4F, 0x7D46, 0xBD50, 0x7D47, 0xBD51, 0x7D48, 0xBD52, 0x7D49, 0xBD53, 0x7D4A, 0xBD54, 0x7D4B, + 0xBD55, 0x7D4C, 0xBD56, 0x7D4D, 0xBD57, 0x7D4E, 0xBD58, 0x7D4F, 0xBD59, 0x7D50, 0xBD5A, 0x7D51, 0xBD5B, 0x7D52, 0xBD5C, 0x7D53, + 0xBD5D, 0x7D54, 0xBD5E, 0x7D55, 0xBD5F, 0x7D56, 0xBD60, 0x7D57, 0xBD61, 0x7D58, 0xBD62, 0x7D59, 0xBD63, 0x7D5A, 0xBD64, 0x7D5B, + 0xBD65, 0x7D5C, 0xBD66, 0x7D5D, 0xBD67, 0x7D5E, 0xBD68, 0x7D5F, 0xBD69, 0x7D60, 0xBD6A, 0x7D61, 0xBD6B, 0x7D62, 0xBD6C, 0x7D63, + 0xBD6D, 0x7D64, 0xBD6E, 0x7D65, 0xBD6F, 0x7D66, 0xBD70, 0x7D67, 0xBD71, 0x7D68, 0xBD72, 0x7D69, 0xBD73, 0x7D6A, 0xBD74, 0x7D6B, + 0xBD75, 0x7D6C, 0xBD76, 0x7D6D, 0xBD77, 0x7D6F, 0xBD78, 0x7D70, 0xBD79, 0x7D71, 0xBD7A, 0x7D72, 0xBD7B, 0x7D73, 0xBD7C, 0x7D74, + 0xBD7D, 0x7D75, 0xBD7E, 0x7D76, 0xBD80, 0x7D78, 0xBD81, 0x7D79, 0xBD82, 0x7D7A, 0xBD83, 0x7D7B, 0xBD84, 0x7D7C, 0xBD85, 0x7D7D, + 0xBD86, 0x7D7E, 0xBD87, 0x7D7F, 0xBD88, 0x7D80, 0xBD89, 0x7D81, 0xBD8A, 0x7D82, 0xBD8B, 0x7D83, 0xBD8C, 0x7D84, 0xBD8D, 0x7D85, + 0xBD8E, 0x7D86, 0xBD8F, 0x7D87, 0xBD90, 0x7D88, 0xBD91, 0x7D89, 0xBD92, 0x7D8A, 0xBD93, 0x7D8B, 0xBD94, 0x7D8C, 0xBD95, 0x7D8D, + 0xBD96, 0x7D8E, 0xBD97, 0x7D8F, 0xBD98, 0x7D90, 0xBD99, 0x7D91, 0xBD9A, 0x7D92, 0xBD9B, 0x7D93, 0xBD9C, 0x7D94, 0xBD9D, 0x7D95, + 0xBD9E, 0x7D96, 0xBD9F, 0x7D97, 0xBDA0, 0x7D98, 0xBDA1, 0x5065, 0xBDA2, 0x8230, 0xBDA3, 0x5251, 0xBDA4, 0x996F, 0xBDA5, 0x6E10, + 0xBDA6, 0x6E85, 0xBDA7, 0x6DA7, 0xBDA8, 0x5EFA, 0xBDA9, 0x50F5, 0xBDAA, 0x59DC, 0xBDAB, 0x5C06, 0xBDAC, 0x6D46, 0xBDAD, 0x6C5F, + 0xBDAE, 0x7586, 0xBDAF, 0x848B, 0xBDB0, 0x6868, 0xBDB1, 0x5956, 0xBDB2, 0x8BB2, 0xBDB3, 0x5320, 0xBDB4, 0x9171, 0xBDB5, 0x964D, + 0xBDB6, 0x8549, 0xBDB7, 0x6912, 0xBDB8, 0x7901, 0xBDB9, 0x7126, 0xBDBA, 0x80F6, 0xBDBB, 0x4EA4, 0xBDBC, 0x90CA, 0xBDBD, 0x6D47, + 0xBDBE, 0x9A84, 0xBDBF, 0x5A07, 0xBDC0, 0x56BC, 0xBDC1, 0x6405, 0xBDC2, 0x94F0, 0xBDC3, 0x77EB, 0xBDC4, 0x4FA5, 0xBDC5, 0x811A, + 0xBDC6, 0x72E1, 0xBDC7, 0x89D2, 0xBDC8, 0x997A, 0xBDC9, 0x7F34, 0xBDCA, 0x7EDE, 0xBDCB, 0x527F, 0xBDCC, 0x6559, 0xBDCD, 0x9175, + 0xBDCE, 0x8F7F, 0xBDCF, 0x8F83, 0xBDD0, 0x53EB, 0xBDD1, 0x7A96, 0xBDD2, 0x63ED, 0xBDD3, 0x63A5, 0xBDD4, 0x7686, 0xBDD5, 0x79F8, + 0xBDD6, 0x8857, 0xBDD7, 0x9636, 0xBDD8, 0x622A, 0xBDD9, 0x52AB, 0xBDDA, 0x8282, 0xBDDB, 0x6854, 0xBDDC, 0x6770, 0xBDDD, 0x6377, + 0xBDDE, 0x776B, 0xBDDF, 0x7AED, 0xBDE0, 0x6D01, 0xBDE1, 0x7ED3, 0xBDE2, 0x89E3, 0xBDE3, 0x59D0, 0xBDE4, 0x6212, 0xBDE5, 0x85C9, + 0xBDE6, 0x82A5, 0xBDE7, 0x754C, 0xBDE8, 0x501F, 0xBDE9, 0x4ECB, 0xBDEA, 0x75A5, 0xBDEB, 0x8BEB, 0xBDEC, 0x5C4A, 0xBDED, 0x5DFE, + 0xBDEE, 0x7B4B, 0xBDEF, 0x65A4, 0xBDF0, 0x91D1, 0xBDF1, 0x4ECA, 0xBDF2, 0x6D25, 0xBDF3, 0x895F, 0xBDF4, 0x7D27, 0xBDF5, 0x9526, + 0xBDF6, 0x4EC5, 0xBDF7, 0x8C28, 0xBDF8, 0x8FDB, 0xBDF9, 0x9773, 0xBDFA, 0x664B, 0xBDFB, 0x7981, 0xBDFC, 0x8FD1, 0xBDFD, 0x70EC, + 0xBDFE, 0x6D78, 0xBE40, 0x7D99, 0xBE41, 0x7D9A, 0xBE42, 0x7D9B, 0xBE43, 0x7D9C, 0xBE44, 0x7D9D, 0xBE45, 0x7D9E, 0xBE46, 0x7D9F, + 0xBE47, 0x7DA0, 0xBE48, 0x7DA1, 0xBE49, 0x7DA2, 0xBE4A, 0x7DA3, 0xBE4B, 0x7DA4, 0xBE4C, 0x7DA5, 0xBE4D, 0x7DA7, 0xBE4E, 0x7DA8, + 0xBE4F, 0x7DA9, 0xBE50, 0x7DAA, 0xBE51, 0x7DAB, 0xBE52, 0x7DAC, 0xBE53, 0x7DAD, 0xBE54, 0x7DAF, 0xBE55, 0x7DB0, 0xBE56, 0x7DB1, + 0xBE57, 0x7DB2, 0xBE58, 0x7DB3, 0xBE59, 0x7DB4, 0xBE5A, 0x7DB5, 0xBE5B, 0x7DB6, 0xBE5C, 0x7DB7, 0xBE5D, 0x7DB8, 0xBE5E, 0x7DB9, + 0xBE5F, 0x7DBA, 0xBE60, 0x7DBB, 0xBE61, 0x7DBC, 0xBE62, 0x7DBD, 0xBE63, 0x7DBE, 0xBE64, 0x7DBF, 0xBE65, 0x7DC0, 0xBE66, 0x7DC1, + 0xBE67, 0x7DC2, 0xBE68, 0x7DC3, 0xBE69, 0x7DC4, 0xBE6A, 0x7DC5, 0xBE6B, 0x7DC6, 0xBE6C, 0x7DC7, 0xBE6D, 0x7DC8, 0xBE6E, 0x7DC9, + 0xBE6F, 0x7DCA, 0xBE70, 0x7DCB, 0xBE71, 0x7DCC, 0xBE72, 0x7DCD, 0xBE73, 0x7DCE, 0xBE74, 0x7DCF, 0xBE75, 0x7DD0, 0xBE76, 0x7DD1, + 0xBE77, 0x7DD2, 0xBE78, 0x7DD3, 0xBE79, 0x7DD4, 0xBE7A, 0x7DD5, 0xBE7B, 0x7DD6, 0xBE7C, 0x7DD7, 0xBE7D, 0x7DD8, 0xBE7E, 0x7DD9, + 0xBE80, 0x7DDA, 0xBE81, 0x7DDB, 0xBE82, 0x7DDC, 0xBE83, 0x7DDD, 0xBE84, 0x7DDE, 0xBE85, 0x7DDF, 0xBE86, 0x7DE0, 0xBE87, 0x7DE1, + 0xBE88, 0x7DE2, 0xBE89, 0x7DE3, 0xBE8A, 0x7DE4, 0xBE8B, 0x7DE5, 0xBE8C, 0x7DE6, 0xBE8D, 0x7DE7, 0xBE8E, 0x7DE8, 0xBE8F, 0x7DE9, + 0xBE90, 0x7DEA, 0xBE91, 0x7DEB, 0xBE92, 0x7DEC, 0xBE93, 0x7DED, 0xBE94, 0x7DEE, 0xBE95, 0x7DEF, 0xBE96, 0x7DF0, 0xBE97, 0x7DF1, + 0xBE98, 0x7DF2, 0xBE99, 0x7DF3, 0xBE9A, 0x7DF4, 0xBE9B, 0x7DF5, 0xBE9C, 0x7DF6, 0xBE9D, 0x7DF7, 0xBE9E, 0x7DF8, 0xBE9F, 0x7DF9, + 0xBEA0, 0x7DFA, 0xBEA1, 0x5C3D, 0xBEA2, 0x52B2, 0xBEA3, 0x8346, 0xBEA4, 0x5162, 0xBEA5, 0x830E, 0xBEA6, 0x775B, 0xBEA7, 0x6676, + 0xBEA8, 0x9CB8, 0xBEA9, 0x4EAC, 0xBEAA, 0x60CA, 0xBEAB, 0x7CBE, 0xBEAC, 0x7CB3, 0xBEAD, 0x7ECF, 0xBEAE, 0x4E95, 0xBEAF, 0x8B66, + 0xBEB0, 0x666F, 0xBEB1, 0x9888, 0xBEB2, 0x9759, 0xBEB3, 0x5883, 0xBEB4, 0x656C, 0xBEB5, 0x955C, 0xBEB6, 0x5F84, 0xBEB7, 0x75C9, + 0xBEB8, 0x9756, 0xBEB9, 0x7ADF, 0xBEBA, 0x7ADE, 0xBEBB, 0x51C0, 0xBEBC, 0x70AF, 0xBEBD, 0x7A98, 0xBEBE, 0x63EA, 0xBEBF, 0x7A76, + 0xBEC0, 0x7EA0, 0xBEC1, 0x7396, 0xBEC2, 0x97ED, 0xBEC3, 0x4E45, 0xBEC4, 0x7078, 0xBEC5, 0x4E5D, 0xBEC6, 0x9152, 0xBEC7, 0x53A9, + 0xBEC8, 0x6551, 0xBEC9, 0x65E7, 0xBECA, 0x81FC, 0xBECB, 0x8205, 0xBECC, 0x548E, 0xBECD, 0x5C31, 0xBECE, 0x759A, 0xBECF, 0x97A0, + 0xBED0, 0x62D8, 0xBED1, 0x72D9, 0xBED2, 0x75BD, 0xBED3, 0x5C45, 0xBED4, 0x9A79, 0xBED5, 0x83CA, 0xBED6, 0x5C40, 0xBED7, 0x5480, + 0xBED8, 0x77E9, 0xBED9, 0x4E3E, 0xBEDA, 0x6CAE, 0xBEDB, 0x805A, 0xBEDC, 0x62D2, 0xBEDD, 0x636E, 0xBEDE, 0x5DE8, 0xBEDF, 0x5177, + 0xBEE0, 0x8DDD, 0xBEE1, 0x8E1E, 0xBEE2, 0x952F, 0xBEE3, 0x4FF1, 0xBEE4, 0x53E5, 0xBEE5, 0x60E7, 0xBEE6, 0x70AC, 0xBEE7, 0x5267, + 0xBEE8, 0x6350, 0xBEE9, 0x9E43, 0xBEEA, 0x5A1F, 0xBEEB, 0x5026, 0xBEEC, 0x7737, 0xBEED, 0x5377, 0xBEEE, 0x7EE2, 0xBEEF, 0x6485, + 0xBEF0, 0x652B, 0xBEF1, 0x6289, 0xBEF2, 0x6398, 0xBEF3, 0x5014, 0xBEF4, 0x7235, 0xBEF5, 0x89C9, 0xBEF6, 0x51B3, 0xBEF7, 0x8BC0, + 0xBEF8, 0x7EDD, 0xBEF9, 0x5747, 0xBEFA, 0x83CC, 0xBEFB, 0x94A7, 0xBEFC, 0x519B, 0xBEFD, 0x541B, 0xBEFE, 0x5CFB, 0xBF40, 0x7DFB, + 0xBF41, 0x7DFC, 0xBF42, 0x7DFD, 0xBF43, 0x7DFE, 0xBF44, 0x7DFF, 0xBF45, 0x7E00, 0xBF46, 0x7E01, 0xBF47, 0x7E02, 0xBF48, 0x7E03, + 0xBF49, 0x7E04, 0xBF4A, 0x7E05, 0xBF4B, 0x7E06, 0xBF4C, 0x7E07, 0xBF4D, 0x7E08, 0xBF4E, 0x7E09, 0xBF4F, 0x7E0A, 0xBF50, 0x7E0B, + 0xBF51, 0x7E0C, 0xBF52, 0x7E0D, 0xBF53, 0x7E0E, 0xBF54, 0x7E0F, 0xBF55, 0x7E10, 0xBF56, 0x7E11, 0xBF57, 0x7E12, 0xBF58, 0x7E13, + 0xBF59, 0x7E14, 0xBF5A, 0x7E15, 0xBF5B, 0x7E16, 0xBF5C, 0x7E17, 0xBF5D, 0x7E18, 0xBF5E, 0x7E19, 0xBF5F, 0x7E1A, 0xBF60, 0x7E1B, + 0xBF61, 0x7E1C, 0xBF62, 0x7E1D, 0xBF63, 0x7E1E, 0xBF64, 0x7E1F, 0xBF65, 0x7E20, 0xBF66, 0x7E21, 0xBF67, 0x7E22, 0xBF68, 0x7E23, + 0xBF69, 0x7E24, 0xBF6A, 0x7E25, 0xBF6B, 0x7E26, 0xBF6C, 0x7E27, 0xBF6D, 0x7E28, 0xBF6E, 0x7E29, 0xBF6F, 0x7E2A, 0xBF70, 0x7E2B, + 0xBF71, 0x7E2C, 0xBF72, 0x7E2D, 0xBF73, 0x7E2E, 0xBF74, 0x7E2F, 0xBF75, 0x7E30, 0xBF76, 0x7E31, 0xBF77, 0x7E32, 0xBF78, 0x7E33, + 0xBF79, 0x7E34, 0xBF7A, 0x7E35, 0xBF7B, 0x7E36, 0xBF7C, 0x7E37, 0xBF7D, 0x7E38, 0xBF7E, 0x7E39, 0xBF80, 0x7E3A, 0xBF81, 0x7E3C, + 0xBF82, 0x7E3D, 0xBF83, 0x7E3E, 0xBF84, 0x7E3F, 0xBF85, 0x7E40, 0xBF86, 0x7E42, 0xBF87, 0x7E43, 0xBF88, 0x7E44, 0xBF89, 0x7E45, + 0xBF8A, 0x7E46, 0xBF8B, 0x7E48, 0xBF8C, 0x7E49, 0xBF8D, 0x7E4A, 0xBF8E, 0x7E4B, 0xBF8F, 0x7E4C, 0xBF90, 0x7E4D, 0xBF91, 0x7E4E, + 0xBF92, 0x7E4F, 0xBF93, 0x7E50, 0xBF94, 0x7E51, 0xBF95, 0x7E52, 0xBF96, 0x7E53, 0xBF97, 0x7E54, 0xBF98, 0x7E55, 0xBF99, 0x7E56, + 0xBF9A, 0x7E57, 0xBF9B, 0x7E58, 0xBF9C, 0x7E59, 0xBF9D, 0x7E5A, 0xBF9E, 0x7E5B, 0xBF9F, 0x7E5C, 0xBFA0, 0x7E5D, 0xBFA1, 0x4FCA, + 0xBFA2, 0x7AE3, 0xBFA3, 0x6D5A, 0xBFA4, 0x90E1, 0xBFA5, 0x9A8F, 0xBFA6, 0x5580, 0xBFA7, 0x5496, 0xBFA8, 0x5361, 0xBFA9, 0x54AF, + 0xBFAA, 0x5F00, 0xBFAB, 0x63E9, 0xBFAC, 0x6977, 0xBFAD, 0x51EF, 0xBFAE, 0x6168, 0xBFAF, 0x520A, 0xBFB0, 0x582A, 0xBFB1, 0x52D8, + 0xBFB2, 0x574E, 0xBFB3, 0x780D, 0xBFB4, 0x770B, 0xBFB5, 0x5EB7, 0xBFB6, 0x6177, 0xBFB7, 0x7CE0, 0xBFB8, 0x625B, 0xBFB9, 0x6297, + 0xBFBA, 0x4EA2, 0xBFBB, 0x7095, 0xBFBC, 0x8003, 0xBFBD, 0x62F7, 0xBFBE, 0x70E4, 0xBFBF, 0x9760, 0xBFC0, 0x5777, 0xBFC1, 0x82DB, + 0xBFC2, 0x67EF, 0xBFC3, 0x68F5, 0xBFC4, 0x78D5, 0xBFC5, 0x9897, 0xBFC6, 0x79D1, 0xBFC7, 0x58F3, 0xBFC8, 0x54B3, 0xBFC9, 0x53EF, + 0xBFCA, 0x6E34, 0xBFCB, 0x514B, 0xBFCC, 0x523B, 0xBFCD, 0x5BA2, 0xBFCE, 0x8BFE, 0xBFCF, 0x80AF, 0xBFD0, 0x5543, 0xBFD1, 0x57A6, + 0xBFD2, 0x6073, 0xBFD3, 0x5751, 0xBFD4, 0x542D, 0xBFD5, 0x7A7A, 0xBFD6, 0x6050, 0xBFD7, 0x5B54, 0xBFD8, 0x63A7, 0xBFD9, 0x62A0, + 0xBFDA, 0x53E3, 0xBFDB, 0x6263, 0xBFDC, 0x5BC7, 0xBFDD, 0x67AF, 0xBFDE, 0x54ED, 0xBFDF, 0x7A9F, 0xBFE0, 0x82E6, 0xBFE1, 0x9177, + 0xBFE2, 0x5E93, 0xBFE3, 0x88E4, 0xBFE4, 0x5938, 0xBFE5, 0x57AE, 0xBFE6, 0x630E, 0xBFE7, 0x8DE8, 0xBFE8, 0x80EF, 0xBFE9, 0x5757, + 0xBFEA, 0x7B77, 0xBFEB, 0x4FA9, 0xBFEC, 0x5FEB, 0xBFED, 0x5BBD, 0xBFEE, 0x6B3E, 0xBFEF, 0x5321, 0xBFF0, 0x7B50, 0xBFF1, 0x72C2, + 0xBFF2, 0x6846, 0xBFF3, 0x77FF, 0xBFF4, 0x7736, 0xBFF5, 0x65F7, 0xBFF6, 0x51B5, 0xBFF7, 0x4E8F, 0xBFF8, 0x76D4, 0xBFF9, 0x5CBF, + 0xBFFA, 0x7AA5, 0xBFFB, 0x8475, 0xBFFC, 0x594E, 0xBFFD, 0x9B41, 0xBFFE, 0x5080, 0xC040, 0x7E5E, 0xC041, 0x7E5F, 0xC042, 0x7E60, + 0xC043, 0x7E61, 0xC044, 0x7E62, 0xC045, 0x7E63, 0xC046, 0x7E64, 0xC047, 0x7E65, 0xC048, 0x7E66, 0xC049, 0x7E67, 0xC04A, 0x7E68, + 0xC04B, 0x7E69, 0xC04C, 0x7E6A, 0xC04D, 0x7E6B, 0xC04E, 0x7E6C, 0xC04F, 0x7E6D, 0xC050, 0x7E6E, 0xC051, 0x7E6F, 0xC052, 0x7E70, + 0xC053, 0x7E71, 0xC054, 0x7E72, 0xC055, 0x7E73, 0xC056, 0x7E74, 0xC057, 0x7E75, 0xC058, 0x7E76, 0xC059, 0x7E77, 0xC05A, 0x7E78, + 0xC05B, 0x7E79, 0xC05C, 0x7E7A, 0xC05D, 0x7E7B, 0xC05E, 0x7E7C, 0xC05F, 0x7E7D, 0xC060, 0x7E7E, 0xC061, 0x7E7F, 0xC062, 0x7E80, + 0xC063, 0x7E81, 0xC064, 0x7E83, 0xC065, 0x7E84, 0xC066, 0x7E85, 0xC067, 0x7E86, 0xC068, 0x7E87, 0xC069, 0x7E88, 0xC06A, 0x7E89, + 0xC06B, 0x7E8A, 0xC06C, 0x7E8B, 0xC06D, 0x7E8C, 0xC06E, 0x7E8D, 0xC06F, 0x7E8E, 0xC070, 0x7E8F, 0xC071, 0x7E90, 0xC072, 0x7E91, + 0xC073, 0x7E92, 0xC074, 0x7E93, 0xC075, 0x7E94, 0xC076, 0x7E95, 0xC077, 0x7E96, 0xC078, 0x7E97, 0xC079, 0x7E98, 0xC07A, 0x7E99, + 0xC07B, 0x7E9A, 0xC07C, 0x7E9C, 0xC07D, 0x7E9D, 0xC07E, 0x7E9E, 0xC080, 0x7EAE, 0xC081, 0x7EB4, 0xC082, 0x7EBB, 0xC083, 0x7EBC, + 0xC084, 0x7ED6, 0xC085, 0x7EE4, 0xC086, 0x7EEC, 0xC087, 0x7EF9, 0xC088, 0x7F0A, 0xC089, 0x7F10, 0xC08A, 0x7F1E, 0xC08B, 0x7F37, + 0xC08C, 0x7F39, 0xC08D, 0x7F3B, 0xC08E, 0x7F3C, 0xC08F, 0x7F3D, 0xC090, 0x7F3E, 0xC091, 0x7F3F, 0xC092, 0x7F40, 0xC093, 0x7F41, + 0xC094, 0x7F43, 0xC095, 0x7F46, 0xC096, 0x7F47, 0xC097, 0x7F48, 0xC098, 0x7F49, 0xC099, 0x7F4A, 0xC09A, 0x7F4B, 0xC09B, 0x7F4C, + 0xC09C, 0x7F4D, 0xC09D, 0x7F4E, 0xC09E, 0x7F4F, 0xC09F, 0x7F52, 0xC0A0, 0x7F53, 0xC0A1, 0x9988, 0xC0A2, 0x6127, 0xC0A3, 0x6E83, + 0xC0A4, 0x5764, 0xC0A5, 0x6606, 0xC0A6, 0x6346, 0xC0A7, 0x56F0, 0xC0A8, 0x62EC, 0xC0A9, 0x6269, 0xC0AA, 0x5ED3, 0xC0AB, 0x9614, + 0xC0AC, 0x5783, 0xC0AD, 0x62C9, 0xC0AE, 0x5587, 0xC0AF, 0x8721, 0xC0B0, 0x814A, 0xC0B1, 0x8FA3, 0xC0B2, 0x5566, 0xC0B3, 0x83B1, + 0xC0B4, 0x6765, 0xC0B5, 0x8D56, 0xC0B6, 0x84DD, 0xC0B7, 0x5A6A, 0xC0B8, 0x680F, 0xC0B9, 0x62E6, 0xC0BA, 0x7BEE, 0xC0BB, 0x9611, + 0xC0BC, 0x5170, 0xC0BD, 0x6F9C, 0xC0BE, 0x8C30, 0xC0BF, 0x63FD, 0xC0C0, 0x89C8, 0xC0C1, 0x61D2, 0xC0C2, 0x7F06, 0xC0C3, 0x70C2, + 0xC0C4, 0x6EE5, 0xC0C5, 0x7405, 0xC0C6, 0x6994, 0xC0C7, 0x72FC, 0xC0C8, 0x5ECA, 0xC0C9, 0x90CE, 0xC0CA, 0x6717, 0xC0CB, 0x6D6A, + 0xC0CC, 0x635E, 0xC0CD, 0x52B3, 0xC0CE, 0x7262, 0xC0CF, 0x8001, 0xC0D0, 0x4F6C, 0xC0D1, 0x59E5, 0xC0D2, 0x916A, 0xC0D3, 0x70D9, + 0xC0D4, 0x6D9D, 0xC0D5, 0x52D2, 0xC0D6, 0x4E50, 0xC0D7, 0x96F7, 0xC0D8, 0x956D, 0xC0D9, 0x857E, 0xC0DA, 0x78CA, 0xC0DB, 0x7D2F, + 0xC0DC, 0x5121, 0xC0DD, 0x5792, 0xC0DE, 0x64C2, 0xC0DF, 0x808B, 0xC0E0, 0x7C7B, 0xC0E1, 0x6CEA, 0xC0E2, 0x68F1, 0xC0E3, 0x695E, + 0xC0E4, 0x51B7, 0xC0E5, 0x5398, 0xC0E6, 0x68A8, 0xC0E7, 0x7281, 0xC0E8, 0x9ECE, 0xC0E9, 0x7BF1, 0xC0EA, 0x72F8, 0xC0EB, 0x79BB, + 0xC0EC, 0x6F13, 0xC0ED, 0x7406, 0xC0EE, 0x674E, 0xC0EF, 0x91CC, 0xC0F0, 0x9CA4, 0xC0F1, 0x793C, 0xC0F2, 0x8389, 0xC0F3, 0x8354, + 0xC0F4, 0x540F, 0xC0F5, 0x6817, 0xC0F6, 0x4E3D, 0xC0F7, 0x5389, 0xC0F8, 0x52B1, 0xC0F9, 0x783E, 0xC0FA, 0x5386, 0xC0FB, 0x5229, + 0xC0FC, 0x5088, 0xC0FD, 0x4F8B, 0xC0FE, 0x4FD0, 0xC140, 0x7F56, 0xC141, 0x7F59, 0xC142, 0x7F5B, 0xC143, 0x7F5C, 0xC144, 0x7F5D, + 0xC145, 0x7F5E, 0xC146, 0x7F60, 0xC147, 0x7F63, 0xC148, 0x7F64, 0xC149, 0x7F65, 0xC14A, 0x7F66, 0xC14B, 0x7F67, 0xC14C, 0x7F6B, + 0xC14D, 0x7F6C, 0xC14E, 0x7F6D, 0xC14F, 0x7F6F, 0xC150, 0x7F70, 0xC151, 0x7F73, 0xC152, 0x7F75, 0xC153, 0x7F76, 0xC154, 0x7F77, + 0xC155, 0x7F78, 0xC156, 0x7F7A, 0xC157, 0x7F7B, 0xC158, 0x7F7C, 0xC159, 0x7F7D, 0xC15A, 0x7F7F, 0xC15B, 0x7F80, 0xC15C, 0x7F82, + 0xC15D, 0x7F83, 0xC15E, 0x7F84, 0xC15F, 0x7F85, 0xC160, 0x7F86, 0xC161, 0x7F87, 0xC162, 0x7F88, 0xC163, 0x7F89, 0xC164, 0x7F8B, + 0xC165, 0x7F8D, 0xC166, 0x7F8F, 0xC167, 0x7F90, 0xC168, 0x7F91, 0xC169, 0x7F92, 0xC16A, 0x7F93, 0xC16B, 0x7F95, 0xC16C, 0x7F96, + 0xC16D, 0x7F97, 0xC16E, 0x7F98, 0xC16F, 0x7F99, 0xC170, 0x7F9B, 0xC171, 0x7F9C, 0xC172, 0x7FA0, 0xC173, 0x7FA2, 0xC174, 0x7FA3, + 0xC175, 0x7FA5, 0xC176, 0x7FA6, 0xC177, 0x7FA8, 0xC178, 0x7FA9, 0xC179, 0x7FAA, 0xC17A, 0x7FAB, 0xC17B, 0x7FAC, 0xC17C, 0x7FAD, + 0xC17D, 0x7FAE, 0xC17E, 0x7FB1, 0xC180, 0x7FB3, 0xC181, 0x7FB4, 0xC182, 0x7FB5, 0xC183, 0x7FB6, 0xC184, 0x7FB7, 0xC185, 0x7FBA, + 0xC186, 0x7FBB, 0xC187, 0x7FBE, 0xC188, 0x7FC0, 0xC189, 0x7FC2, 0xC18A, 0x7FC3, 0xC18B, 0x7FC4, 0xC18C, 0x7FC6, 0xC18D, 0x7FC7, + 0xC18E, 0x7FC8, 0xC18F, 0x7FC9, 0xC190, 0x7FCB, 0xC191, 0x7FCD, 0xC192, 0x7FCF, 0xC193, 0x7FD0, 0xC194, 0x7FD1, 0xC195, 0x7FD2, + 0xC196, 0x7FD3, 0xC197, 0x7FD6, 0xC198, 0x7FD7, 0xC199, 0x7FD9, 0xC19A, 0x7FDA, 0xC19B, 0x7FDB, 0xC19C, 0x7FDC, 0xC19D, 0x7FDD, + 0xC19E, 0x7FDE, 0xC19F, 0x7FE2, 0xC1A0, 0x7FE3, 0xC1A1, 0x75E2, 0xC1A2, 0x7ACB, 0xC1A3, 0x7C92, 0xC1A4, 0x6CA5, 0xC1A5, 0x96B6, + 0xC1A6, 0x529B, 0xC1A7, 0x7483, 0xC1A8, 0x54E9, 0xC1A9, 0x4FE9, 0xC1AA, 0x8054, 0xC1AB, 0x83B2, 0xC1AC, 0x8FDE, 0xC1AD, 0x9570, + 0xC1AE, 0x5EC9, 0xC1AF, 0x601C, 0xC1B0, 0x6D9F, 0xC1B1, 0x5E18, 0xC1B2, 0x655B, 0xC1B3, 0x8138, 0xC1B4, 0x94FE, 0xC1B5, 0x604B, + 0xC1B6, 0x70BC, 0xC1B7, 0x7EC3, 0xC1B8, 0x7CAE, 0xC1B9, 0x51C9, 0xC1BA, 0x6881, 0xC1BB, 0x7CB1, 0xC1BC, 0x826F, 0xC1BD, 0x4E24, + 0xC1BE, 0x8F86, 0xC1BF, 0x91CF, 0xC1C0, 0x667E, 0xC1C1, 0x4EAE, 0xC1C2, 0x8C05, 0xC1C3, 0x64A9, 0xC1C4, 0x804A, 0xC1C5, 0x50DA, + 0xC1C6, 0x7597, 0xC1C7, 0x71CE, 0xC1C8, 0x5BE5, 0xC1C9, 0x8FBD, 0xC1CA, 0x6F66, 0xC1CB, 0x4E86, 0xC1CC, 0x6482, 0xC1CD, 0x9563, + 0xC1CE, 0x5ED6, 0xC1CF, 0x6599, 0xC1D0, 0x5217, 0xC1D1, 0x88C2, 0xC1D2, 0x70C8, 0xC1D3, 0x52A3, 0xC1D4, 0x730E, 0xC1D5, 0x7433, + 0xC1D6, 0x6797, 0xC1D7, 0x78F7, 0xC1D8, 0x9716, 0xC1D9, 0x4E34, 0xC1DA, 0x90BB, 0xC1DB, 0x9CDE, 0xC1DC, 0x6DCB, 0xC1DD, 0x51DB, + 0xC1DE, 0x8D41, 0xC1DF, 0x541D, 0xC1E0, 0x62CE, 0xC1E1, 0x73B2, 0xC1E2, 0x83F1, 0xC1E3, 0x96F6, 0xC1E4, 0x9F84, 0xC1E5, 0x94C3, + 0xC1E6, 0x4F36, 0xC1E7, 0x7F9A, 0xC1E8, 0x51CC, 0xC1E9, 0x7075, 0xC1EA, 0x9675, 0xC1EB, 0x5CAD, 0xC1EC, 0x9886, 0xC1ED, 0x53E6, + 0xC1EE, 0x4EE4, 0xC1EF, 0x6E9C, 0xC1F0, 0x7409, 0xC1F1, 0x69B4, 0xC1F2, 0x786B, 0xC1F3, 0x998F, 0xC1F4, 0x7559, 0xC1F5, 0x5218, + 0xC1F6, 0x7624, 0xC1F7, 0x6D41, 0xC1F8, 0x67F3, 0xC1F9, 0x516D, 0xC1FA, 0x9F99, 0xC1FB, 0x804B, 0xC1FC, 0x5499, 0xC1FD, 0x7B3C, + 0xC1FE, 0x7ABF, 0xC240, 0x7FE4, 0xC241, 0x7FE7, 0xC242, 0x7FE8, 0xC243, 0x7FEA, 0xC244, 0x7FEB, 0xC245, 0x7FEC, 0xC246, 0x7FED, + 0xC247, 0x7FEF, 0xC248, 0x7FF2, 0xC249, 0x7FF4, 0xC24A, 0x7FF5, 0xC24B, 0x7FF6, 0xC24C, 0x7FF7, 0xC24D, 0x7FF8, 0xC24E, 0x7FF9, + 0xC24F, 0x7FFA, 0xC250, 0x7FFD, 0xC251, 0x7FFE, 0xC252, 0x7FFF, 0xC253, 0x8002, 0xC254, 0x8007, 0xC255, 0x8008, 0xC256, 0x8009, + 0xC257, 0x800A, 0xC258, 0x800E, 0xC259, 0x800F, 0xC25A, 0x8011, 0xC25B, 0x8013, 0xC25C, 0x801A, 0xC25D, 0x801B, 0xC25E, 0x801D, + 0xC25F, 0x801E, 0xC260, 0x801F, 0xC261, 0x8021, 0xC262, 0x8023, 0xC263, 0x8024, 0xC264, 0x802B, 0xC265, 0x802C, 0xC266, 0x802D, + 0xC267, 0x802E, 0xC268, 0x802F, 0xC269, 0x8030, 0xC26A, 0x8032, 0xC26B, 0x8034, 0xC26C, 0x8039, 0xC26D, 0x803A, 0xC26E, 0x803C, + 0xC26F, 0x803E, 0xC270, 0x8040, 0xC271, 0x8041, 0xC272, 0x8044, 0xC273, 0x8045, 0xC274, 0x8047, 0xC275, 0x8048, 0xC276, 0x8049, + 0xC277, 0x804E, 0xC278, 0x804F, 0xC279, 0x8050, 0xC27A, 0x8051, 0xC27B, 0x8053, 0xC27C, 0x8055, 0xC27D, 0x8056, 0xC27E, 0x8057, + 0xC280, 0x8059, 0xC281, 0x805B, 0xC282, 0x805C, 0xC283, 0x805D, 0xC284, 0x805E, 0xC285, 0x805F, 0xC286, 0x8060, 0xC287, 0x8061, + 0xC288, 0x8062, 0xC289, 0x8063, 0xC28A, 0x8064, 0xC28B, 0x8065, 0xC28C, 0x8066, 0xC28D, 0x8067, 0xC28E, 0x8068, 0xC28F, 0x806B, + 0xC290, 0x806C, 0xC291, 0x806D, 0xC292, 0x806E, 0xC293, 0x806F, 0xC294, 0x8070, 0xC295, 0x8072, 0xC296, 0x8073, 0xC297, 0x8074, + 0xC298, 0x8075, 0xC299, 0x8076, 0xC29A, 0x8077, 0xC29B, 0x8078, 0xC29C, 0x8079, 0xC29D, 0x807A, 0xC29E, 0x807B, 0xC29F, 0x807C, + 0xC2A0, 0x807D, 0xC2A1, 0x9686, 0xC2A2, 0x5784, 0xC2A3, 0x62E2, 0xC2A4, 0x9647, 0xC2A5, 0x697C, 0xC2A6, 0x5A04, 0xC2A7, 0x6402, + 0xC2A8, 0x7BD3, 0xC2A9, 0x6F0F, 0xC2AA, 0x964B, 0xC2AB, 0x82A6, 0xC2AC, 0x5362, 0xC2AD, 0x9885, 0xC2AE, 0x5E90, 0xC2AF, 0x7089, + 0xC2B0, 0x63B3, 0xC2B1, 0x5364, 0xC2B2, 0x864F, 0xC2B3, 0x9C81, 0xC2B4, 0x9E93, 0xC2B5, 0x788C, 0xC2B6, 0x9732, 0xC2B7, 0x8DEF, + 0xC2B8, 0x8D42, 0xC2B9, 0x9E7F, 0xC2BA, 0x6F5E, 0xC2BB, 0x7984, 0xC2BC, 0x5F55, 0xC2BD, 0x9646, 0xC2BE, 0x622E, 0xC2BF, 0x9A74, + 0xC2C0, 0x5415, 0xC2C1, 0x94DD, 0xC2C2, 0x4FA3, 0xC2C3, 0x65C5, 0xC2C4, 0x5C65, 0xC2C5, 0x5C61, 0xC2C6, 0x7F15, 0xC2C7, 0x8651, + 0xC2C8, 0x6C2F, 0xC2C9, 0x5F8B, 0xC2CA, 0x7387, 0xC2CB, 0x6EE4, 0xC2CC, 0x7EFF, 0xC2CD, 0x5CE6, 0xC2CE, 0x631B, 0xC2CF, 0x5B6A, + 0xC2D0, 0x6EE6, 0xC2D1, 0x5375, 0xC2D2, 0x4E71, 0xC2D3, 0x63A0, 0xC2D4, 0x7565, 0xC2D5, 0x62A1, 0xC2D6, 0x8F6E, 0xC2D7, 0x4F26, + 0xC2D8, 0x4ED1, 0xC2D9, 0x6CA6, 0xC2DA, 0x7EB6, 0xC2DB, 0x8BBA, 0xC2DC, 0x841D, 0xC2DD, 0x87BA, 0xC2DE, 0x7F57, 0xC2DF, 0x903B, + 0xC2E0, 0x9523, 0xC2E1, 0x7BA9, 0xC2E2, 0x9AA1, 0xC2E3, 0x88F8, 0xC2E4, 0x843D, 0xC2E5, 0x6D1B, 0xC2E6, 0x9A86, 0xC2E7, 0x7EDC, + 0xC2E8, 0x5988, 0xC2E9, 0x9EBB, 0xC2EA, 0x739B, 0xC2EB, 0x7801, 0xC2EC, 0x8682, 0xC2ED, 0x9A6C, 0xC2EE, 0x9A82, 0xC2EF, 0x561B, + 0xC2F0, 0x5417, 0xC2F1, 0x57CB, 0xC2F2, 0x4E70, 0xC2F3, 0x9EA6, 0xC2F4, 0x5356, 0xC2F5, 0x8FC8, 0xC2F6, 0x8109, 0xC2F7, 0x7792, + 0xC2F8, 0x9992, 0xC2F9, 0x86EE, 0xC2FA, 0x6EE1, 0xC2FB, 0x8513, 0xC2FC, 0x66FC, 0xC2FD, 0x6162, 0xC2FE, 0x6F2B, 0xC340, 0x807E, + 0xC341, 0x8081, 0xC342, 0x8082, 0xC343, 0x8085, 0xC344, 0x8088, 0xC345, 0x808A, 0xC346, 0x808D, 0xC347, 0x808E, 0xC348, 0x808F, + 0xC349, 0x8090, 0xC34A, 0x8091, 0xC34B, 0x8092, 0xC34C, 0x8094, 0xC34D, 0x8095, 0xC34E, 0x8097, 0xC34F, 0x8099, 0xC350, 0x809E, + 0xC351, 0x80A3, 0xC352, 0x80A6, 0xC353, 0x80A7, 0xC354, 0x80A8, 0xC355, 0x80AC, 0xC356, 0x80B0, 0xC357, 0x80B3, 0xC358, 0x80B5, + 0xC359, 0x80B6, 0xC35A, 0x80B8, 0xC35B, 0x80B9, 0xC35C, 0x80BB, 0xC35D, 0x80C5, 0xC35E, 0x80C7, 0xC35F, 0x80C8, 0xC360, 0x80C9, + 0xC361, 0x80CA, 0xC362, 0x80CB, 0xC363, 0x80CF, 0xC364, 0x80D0, 0xC365, 0x80D1, 0xC366, 0x80D2, 0xC367, 0x80D3, 0xC368, 0x80D4, + 0xC369, 0x80D5, 0xC36A, 0x80D8, 0xC36B, 0x80DF, 0xC36C, 0x80E0, 0xC36D, 0x80E2, 0xC36E, 0x80E3, 0xC36F, 0x80E6, 0xC370, 0x80EE, + 0xC371, 0x80F5, 0xC372, 0x80F7, 0xC373, 0x80F9, 0xC374, 0x80FB, 0xC375, 0x80FE, 0xC376, 0x80FF, 0xC377, 0x8100, 0xC378, 0x8101, + 0xC379, 0x8103, 0xC37A, 0x8104, 0xC37B, 0x8105, 0xC37C, 0x8107, 0xC37D, 0x8108, 0xC37E, 0x810B, 0xC380, 0x810C, 0xC381, 0x8115, + 0xC382, 0x8117, 0xC383, 0x8119, 0xC384, 0x811B, 0xC385, 0x811C, 0xC386, 0x811D, 0xC387, 0x811F, 0xC388, 0x8120, 0xC389, 0x8121, + 0xC38A, 0x8122, 0xC38B, 0x8123, 0xC38C, 0x8124, 0xC38D, 0x8125, 0xC38E, 0x8126, 0xC38F, 0x8127, 0xC390, 0x8128, 0xC391, 0x8129, + 0xC392, 0x812A, 0xC393, 0x812B, 0xC394, 0x812D, 0xC395, 0x812E, 0xC396, 0x8130, 0xC397, 0x8133, 0xC398, 0x8134, 0xC399, 0x8135, + 0xC39A, 0x8137, 0xC39B, 0x8139, 0xC39C, 0x813A, 0xC39D, 0x813B, 0xC39E, 0x813C, 0xC39F, 0x813D, 0xC3A0, 0x813F, 0xC3A1, 0x8C29, + 0xC3A2, 0x8292, 0xC3A3, 0x832B, 0xC3A4, 0x76F2, 0xC3A5, 0x6C13, 0xC3A6, 0x5FD9, 0xC3A7, 0x83BD, 0xC3A8, 0x732B, 0xC3A9, 0x8305, + 0xC3AA, 0x951A, 0xC3AB, 0x6BDB, 0xC3AC, 0x77DB, 0xC3AD, 0x94C6, 0xC3AE, 0x536F, 0xC3AF, 0x8302, 0xC3B0, 0x5192, 0xC3B1, 0x5E3D, + 0xC3B2, 0x8C8C, 0xC3B3, 0x8D38, 0xC3B4, 0x4E48, 0xC3B5, 0x73AB, 0xC3B6, 0x679A, 0xC3B7, 0x6885, 0xC3B8, 0x9176, 0xC3B9, 0x9709, + 0xC3BA, 0x7164, 0xC3BB, 0x6CA1, 0xC3BC, 0x7709, 0xC3BD, 0x5A92, 0xC3BE, 0x9541, 0xC3BF, 0x6BCF, 0xC3C0, 0x7F8E, 0xC3C1, 0x6627, + 0xC3C2, 0x5BD0, 0xC3C3, 0x59B9, 0xC3C4, 0x5A9A, 0xC3C5, 0x95E8, 0xC3C6, 0x95F7, 0xC3C7, 0x4EEC, 0xC3C8, 0x840C, 0xC3C9, 0x8499, + 0xC3CA, 0x6AAC, 0xC3CB, 0x76DF, 0xC3CC, 0x9530, 0xC3CD, 0x731B, 0xC3CE, 0x68A6, 0xC3CF, 0x5B5F, 0xC3D0, 0x772F, 0xC3D1, 0x919A, + 0xC3D2, 0x9761, 0xC3D3, 0x7CDC, 0xC3D4, 0x8FF7, 0xC3D5, 0x8C1C, 0xC3D6, 0x5F25, 0xC3D7, 0x7C73, 0xC3D8, 0x79D8, 0xC3D9, 0x89C5, + 0xC3DA, 0x6CCC, 0xC3DB, 0x871C, 0xC3DC, 0x5BC6, 0xC3DD, 0x5E42, 0xC3DE, 0x68C9, 0xC3DF, 0x7720, 0xC3E0, 0x7EF5, 0xC3E1, 0x5195, + 0xC3E2, 0x514D, 0xC3E3, 0x52C9, 0xC3E4, 0x5A29, 0xC3E5, 0x7F05, 0xC3E6, 0x9762, 0xC3E7, 0x82D7, 0xC3E8, 0x63CF, 0xC3E9, 0x7784, + 0xC3EA, 0x85D0, 0xC3EB, 0x79D2, 0xC3EC, 0x6E3A, 0xC3ED, 0x5E99, 0xC3EE, 0x5999, 0xC3EF, 0x8511, 0xC3F0, 0x706D, 0xC3F1, 0x6C11, + 0xC3F2, 0x62BF, 0xC3F3, 0x76BF, 0xC3F4, 0x654F, 0xC3F5, 0x60AF, 0xC3F6, 0x95FD, 0xC3F7, 0x660E, 0xC3F8, 0x879F, 0xC3F9, 0x9E23, + 0xC3FA, 0x94ED, 0xC3FB, 0x540D, 0xC3FC, 0x547D, 0xC3FD, 0x8C2C, 0xC3FE, 0x6478, 0xC440, 0x8140, 0xC441, 0x8141, 0xC442, 0x8142, + 0xC443, 0x8143, 0xC444, 0x8144, 0xC445, 0x8145, 0xC446, 0x8147, 0xC447, 0x8149, 0xC448, 0x814D, 0xC449, 0x814E, 0xC44A, 0x814F, + 0xC44B, 0x8152, 0xC44C, 0x8156, 0xC44D, 0x8157, 0xC44E, 0x8158, 0xC44F, 0x815B, 0xC450, 0x815C, 0xC451, 0x815D, 0xC452, 0x815E, + 0xC453, 0x815F, 0xC454, 0x8161, 0xC455, 0x8162, 0xC456, 0x8163, 0xC457, 0x8164, 0xC458, 0x8166, 0xC459, 0x8168, 0xC45A, 0x816A, + 0xC45B, 0x816B, 0xC45C, 0x816C, 0xC45D, 0x816F, 0xC45E, 0x8172, 0xC45F, 0x8173, 0xC460, 0x8175, 0xC461, 0x8176, 0xC462, 0x8177, + 0xC463, 0x8178, 0xC464, 0x8181, 0xC465, 0x8183, 0xC466, 0x8184, 0xC467, 0x8185, 0xC468, 0x8186, 0xC469, 0x8187, 0xC46A, 0x8189, + 0xC46B, 0x818B, 0xC46C, 0x818C, 0xC46D, 0x818D, 0xC46E, 0x818E, 0xC46F, 0x8190, 0xC470, 0x8192, 0xC471, 0x8193, 0xC472, 0x8194, + 0xC473, 0x8195, 0xC474, 0x8196, 0xC475, 0x8197, 0xC476, 0x8199, 0xC477, 0x819A, 0xC478, 0x819E, 0xC479, 0x819F, 0xC47A, 0x81A0, + 0xC47B, 0x81A1, 0xC47C, 0x81A2, 0xC47D, 0x81A4, 0xC47E, 0x81A5, 0xC480, 0x81A7, 0xC481, 0x81A9, 0xC482, 0x81AB, 0xC483, 0x81AC, + 0xC484, 0x81AD, 0xC485, 0x81AE, 0xC486, 0x81AF, 0xC487, 0x81B0, 0xC488, 0x81B1, 0xC489, 0x81B2, 0xC48A, 0x81B4, 0xC48B, 0x81B5, + 0xC48C, 0x81B6, 0xC48D, 0x81B7, 0xC48E, 0x81B8, 0xC48F, 0x81B9, 0xC490, 0x81BC, 0xC491, 0x81BD, 0xC492, 0x81BE, 0xC493, 0x81BF, + 0xC494, 0x81C4, 0xC495, 0x81C5, 0xC496, 0x81C7, 0xC497, 0x81C8, 0xC498, 0x81C9, 0xC499, 0x81CB, 0xC49A, 0x81CD, 0xC49B, 0x81CE, + 0xC49C, 0x81CF, 0xC49D, 0x81D0, 0xC49E, 0x81D1, 0xC49F, 0x81D2, 0xC4A0, 0x81D3, 0xC4A1, 0x6479, 0xC4A2, 0x8611, 0xC4A3, 0x6A21, + 0xC4A4, 0x819C, 0xC4A5, 0x78E8, 0xC4A6, 0x6469, 0xC4A7, 0x9B54, 0xC4A8, 0x62B9, 0xC4A9, 0x672B, 0xC4AA, 0x83AB, 0xC4AB, 0x58A8, + 0xC4AC, 0x9ED8, 0xC4AD, 0x6CAB, 0xC4AE, 0x6F20, 0xC4AF, 0x5BDE, 0xC4B0, 0x964C, 0xC4B1, 0x8C0B, 0xC4B2, 0x725F, 0xC4B3, 0x67D0, + 0xC4B4, 0x62C7, 0xC4B5, 0x7261, 0xC4B6, 0x4EA9, 0xC4B7, 0x59C6, 0xC4B8, 0x6BCD, 0xC4B9, 0x5893, 0xC4BA, 0x66AE, 0xC4BB, 0x5E55, + 0xC4BC, 0x52DF, 0xC4BD, 0x6155, 0xC4BE, 0x6728, 0xC4BF, 0x76EE, 0xC4C0, 0x7766, 0xC4C1, 0x7267, 0xC4C2, 0x7A46, 0xC4C3, 0x62FF, + 0xC4C4, 0x54EA, 0xC4C5, 0x5450, 0xC4C6, 0x94A0, 0xC4C7, 0x90A3, 0xC4C8, 0x5A1C, 0xC4C9, 0x7EB3, 0xC4CA, 0x6C16, 0xC4CB, 0x4E43, + 0xC4CC, 0x5976, 0xC4CD, 0x8010, 0xC4CE, 0x5948, 0xC4CF, 0x5357, 0xC4D0, 0x7537, 0xC4D1, 0x96BE, 0xC4D2, 0x56CA, 0xC4D3, 0x6320, + 0xC4D4, 0x8111, 0xC4D5, 0x607C, 0xC4D6, 0x95F9, 0xC4D7, 0x6DD6, 0xC4D8, 0x5462, 0xC4D9, 0x9981, 0xC4DA, 0x5185, 0xC4DB, 0x5AE9, + 0xC4DC, 0x80FD, 0xC4DD, 0x59AE, 0xC4DE, 0x9713, 0xC4DF, 0x502A, 0xC4E0, 0x6CE5, 0xC4E1, 0x5C3C, 0xC4E2, 0x62DF, 0xC4E3, 0x4F60, + 0xC4E4, 0x533F, 0xC4E5, 0x817B, 0xC4E6, 0x9006, 0xC4E7, 0x6EBA, 0xC4E8, 0x852B, 0xC4E9, 0x62C8, 0xC4EA, 0x5E74, 0xC4EB, 0x78BE, + 0xC4EC, 0x64B5, 0xC4ED, 0x637B, 0xC4EE, 0x5FF5, 0xC4EF, 0x5A18, 0xC4F0, 0x917F, 0xC4F1, 0x9E1F, 0xC4F2, 0x5C3F, 0xC4F3, 0x634F, + 0xC4F4, 0x8042, 0xC4F5, 0x5B7D, 0xC4F6, 0x556E, 0xC4F7, 0x954A, 0xC4F8, 0x954D, 0xC4F9, 0x6D85, 0xC4FA, 0x60A8, 0xC4FB, 0x67E0, + 0xC4FC, 0x72DE, 0xC4FD, 0x51DD, 0xC4FE, 0x5B81, 0xC540, 0x81D4, 0xC541, 0x81D5, 0xC542, 0x81D6, 0xC543, 0x81D7, 0xC544, 0x81D8, + 0xC545, 0x81D9, 0xC546, 0x81DA, 0xC547, 0x81DB, 0xC548, 0x81DC, 0xC549, 0x81DD, 0xC54A, 0x81DE, 0xC54B, 0x81DF, 0xC54C, 0x81E0, + 0xC54D, 0x81E1, 0xC54E, 0x81E2, 0xC54F, 0x81E4, 0xC550, 0x81E5, 0xC551, 0x81E6, 0xC552, 0x81E8, 0xC553, 0x81E9, 0xC554, 0x81EB, + 0xC555, 0x81EE, 0xC556, 0x81EF, 0xC557, 0x81F0, 0xC558, 0x81F1, 0xC559, 0x81F2, 0xC55A, 0x81F5, 0xC55B, 0x81F6, 0xC55C, 0x81F7, + 0xC55D, 0x81F8, 0xC55E, 0x81F9, 0xC55F, 0x81FA, 0xC560, 0x81FD, 0xC561, 0x81FF, 0xC562, 0x8203, 0xC563, 0x8207, 0xC564, 0x8208, + 0xC565, 0x8209, 0xC566, 0x820A, 0xC567, 0x820B, 0xC568, 0x820E, 0xC569, 0x820F, 0xC56A, 0x8211, 0xC56B, 0x8213, 0xC56C, 0x8215, + 0xC56D, 0x8216, 0xC56E, 0x8217, 0xC56F, 0x8218, 0xC570, 0x8219, 0xC571, 0x821A, 0xC572, 0x821D, 0xC573, 0x8220, 0xC574, 0x8224, + 0xC575, 0x8225, 0xC576, 0x8226, 0xC577, 0x8227, 0xC578, 0x8229, 0xC579, 0x822E, 0xC57A, 0x8232, 0xC57B, 0x823A, 0xC57C, 0x823C, + 0xC57D, 0x823D, 0xC57E, 0x823F, 0xC580, 0x8240, 0xC581, 0x8241, 0xC582, 0x8242, 0xC583, 0x8243, 0xC584, 0x8245, 0xC585, 0x8246, + 0xC586, 0x8248, 0xC587, 0x824A, 0xC588, 0x824C, 0xC589, 0x824D, 0xC58A, 0x824E, 0xC58B, 0x8250, 0xC58C, 0x8251, 0xC58D, 0x8252, + 0xC58E, 0x8253, 0xC58F, 0x8254, 0xC590, 0x8255, 0xC591, 0x8256, 0xC592, 0x8257, 0xC593, 0x8259, 0xC594, 0x825B, 0xC595, 0x825C, + 0xC596, 0x825D, 0xC597, 0x825E, 0xC598, 0x8260, 0xC599, 0x8261, 0xC59A, 0x8262, 0xC59B, 0x8263, 0xC59C, 0x8264, 0xC59D, 0x8265, + 0xC59E, 0x8266, 0xC59F, 0x8267, 0xC5A0, 0x8269, 0xC5A1, 0x62E7, 0xC5A2, 0x6CDE, 0xC5A3, 0x725B, 0xC5A4, 0x626D, 0xC5A5, 0x94AE, + 0xC5A6, 0x7EBD, 0xC5A7, 0x8113, 0xC5A8, 0x6D53, 0xC5A9, 0x519C, 0xC5AA, 0x5F04, 0xC5AB, 0x5974, 0xC5AC, 0x52AA, 0xC5AD, 0x6012, + 0xC5AE, 0x5973, 0xC5AF, 0x6696, 0xC5B0, 0x8650, 0xC5B1, 0x759F, 0xC5B2, 0x632A, 0xC5B3, 0x61E6, 0xC5B4, 0x7CEF, 0xC5B5, 0x8BFA, + 0xC5B6, 0x54E6, 0xC5B7, 0x6B27, 0xC5B8, 0x9E25, 0xC5B9, 0x6BB4, 0xC5BA, 0x85D5, 0xC5BB, 0x5455, 0xC5BC, 0x5076, 0xC5BD, 0x6CA4, + 0xC5BE, 0x556A, 0xC5BF, 0x8DB4, 0xC5C0, 0x722C, 0xC5C1, 0x5E15, 0xC5C2, 0x6015, 0xC5C3, 0x7436, 0xC5C4, 0x62CD, 0xC5C5, 0x6392, + 0xC5C6, 0x724C, 0xC5C7, 0x5F98, 0xC5C8, 0x6E43, 0xC5C9, 0x6D3E, 0xC5CA, 0x6500, 0xC5CB, 0x6F58, 0xC5CC, 0x76D8, 0xC5CD, 0x78D0, + 0xC5CE, 0x76FC, 0xC5CF, 0x7554, 0xC5D0, 0x5224, 0xC5D1, 0x53DB, 0xC5D2, 0x4E53, 0xC5D3, 0x5E9E, 0xC5D4, 0x65C1, 0xC5D5, 0x802A, + 0xC5D6, 0x80D6, 0xC5D7, 0x629B, 0xC5D8, 0x5486, 0xC5D9, 0x5228, 0xC5DA, 0x70AE, 0xC5DB, 0x888D, 0xC5DC, 0x8DD1, 0xC5DD, 0x6CE1, + 0xC5DE, 0x5478, 0xC5DF, 0x80DA, 0xC5E0, 0x57F9, 0xC5E1, 0x88F4, 0xC5E2, 0x8D54, 0xC5E3, 0x966A, 0xC5E4, 0x914D, 0xC5E5, 0x4F69, + 0xC5E6, 0x6C9B, 0xC5E7, 0x55B7, 0xC5E8, 0x76C6, 0xC5E9, 0x7830, 0xC5EA, 0x62A8, 0xC5EB, 0x70F9, 0xC5EC, 0x6F8E, 0xC5ED, 0x5F6D, + 0xC5EE, 0x84EC, 0xC5EF, 0x68DA, 0xC5F0, 0x787C, 0xC5F1, 0x7BF7, 0xC5F2, 0x81A8, 0xC5F3, 0x670B, 0xC5F4, 0x9E4F, 0xC5F5, 0x6367, + 0xC5F6, 0x78B0, 0xC5F7, 0x576F, 0xC5F8, 0x7812, 0xC5F9, 0x9739, 0xC5FA, 0x6279, 0xC5FB, 0x62AB, 0xC5FC, 0x5288, 0xC5FD, 0x7435, + 0xC5FE, 0x6BD7, 0xC640, 0x826A, 0xC641, 0x826B, 0xC642, 0x826C, 0xC643, 0x826D, 0xC644, 0x8271, 0xC645, 0x8275, 0xC646, 0x8276, + 0xC647, 0x8277, 0xC648, 0x8278, 0xC649, 0x827B, 0xC64A, 0x827C, 0xC64B, 0x8280, 0xC64C, 0x8281, 0xC64D, 0x8283, 0xC64E, 0x8285, + 0xC64F, 0x8286, 0xC650, 0x8287, 0xC651, 0x8289, 0xC652, 0x828C, 0xC653, 0x8290, 0xC654, 0x8293, 0xC655, 0x8294, 0xC656, 0x8295, + 0xC657, 0x8296, 0xC658, 0x829A, 0xC659, 0x829B, 0xC65A, 0x829E, 0xC65B, 0x82A0, 0xC65C, 0x82A2, 0xC65D, 0x82A3, 0xC65E, 0x82A7, + 0xC65F, 0x82B2, 0xC660, 0x82B5, 0xC661, 0x82B6, 0xC662, 0x82BA, 0xC663, 0x82BB, 0xC664, 0x82BC, 0xC665, 0x82BF, 0xC666, 0x82C0, + 0xC667, 0x82C2, 0xC668, 0x82C3, 0xC669, 0x82C5, 0xC66A, 0x82C6, 0xC66B, 0x82C9, 0xC66C, 0x82D0, 0xC66D, 0x82D6, 0xC66E, 0x82D9, + 0xC66F, 0x82DA, 0xC670, 0x82DD, 0xC671, 0x82E2, 0xC672, 0x82E7, 0xC673, 0x82E8, 0xC674, 0x82E9, 0xC675, 0x82EA, 0xC676, 0x82EC, + 0xC677, 0x82ED, 0xC678, 0x82EE, 0xC679, 0x82F0, 0xC67A, 0x82F2, 0xC67B, 0x82F3, 0xC67C, 0x82F5, 0xC67D, 0x82F6, 0xC67E, 0x82F8, + 0xC680, 0x82FA, 0xC681, 0x82FC, 0xC682, 0x82FD, 0xC683, 0x82FE, 0xC684, 0x82FF, 0xC685, 0x8300, 0xC686, 0x830A, 0xC687, 0x830B, + 0xC688, 0x830D, 0xC689, 0x8310, 0xC68A, 0x8312, 0xC68B, 0x8313, 0xC68C, 0x8316, 0xC68D, 0x8318, 0xC68E, 0x8319, 0xC68F, 0x831D, + 0xC690, 0x831E, 0xC691, 0x831F, 0xC692, 0x8320, 0xC693, 0x8321, 0xC694, 0x8322, 0xC695, 0x8323, 0xC696, 0x8324, 0xC697, 0x8325, + 0xC698, 0x8326, 0xC699, 0x8329, 0xC69A, 0x832A, 0xC69B, 0x832E, 0xC69C, 0x8330, 0xC69D, 0x8332, 0xC69E, 0x8337, 0xC69F, 0x833B, + 0xC6A0, 0x833D, 0xC6A1, 0x5564, 0xC6A2, 0x813E, 0xC6A3, 0x75B2, 0xC6A4, 0x76AE, 0xC6A5, 0x5339, 0xC6A6, 0x75DE, 0xC6A7, 0x50FB, + 0xC6A8, 0x5C41, 0xC6A9, 0x8B6C, 0xC6AA, 0x7BC7, 0xC6AB, 0x504F, 0xC6AC, 0x7247, 0xC6AD, 0x9A97, 0xC6AE, 0x98D8, 0xC6AF, 0x6F02, + 0xC6B0, 0x74E2, 0xC6B1, 0x7968, 0xC6B2, 0x6487, 0xC6B3, 0x77A5, 0xC6B4, 0x62FC, 0xC6B5, 0x9891, 0xC6B6, 0x8D2B, 0xC6B7, 0x54C1, + 0xC6B8, 0x8058, 0xC6B9, 0x4E52, 0xC6BA, 0x576A, 0xC6BB, 0x82F9, 0xC6BC, 0x840D, 0xC6BD, 0x5E73, 0xC6BE, 0x51ED, 0xC6BF, 0x74F6, + 0xC6C0, 0x8BC4, 0xC6C1, 0x5C4F, 0xC6C2, 0x5761, 0xC6C3, 0x6CFC, 0xC6C4, 0x9887, 0xC6C5, 0x5A46, 0xC6C6, 0x7834, 0xC6C7, 0x9B44, + 0xC6C8, 0x8FEB, 0xC6C9, 0x7C95, 0xC6CA, 0x5256, 0xC6CB, 0x6251, 0xC6CC, 0x94FA, 0xC6CD, 0x4EC6, 0xC6CE, 0x8386, 0xC6CF, 0x8461, + 0xC6D0, 0x83E9, 0xC6D1, 0x84B2, 0xC6D2, 0x57D4, 0xC6D3, 0x6734, 0xC6D4, 0x5703, 0xC6D5, 0x666E, 0xC6D6, 0x6D66, 0xC6D7, 0x8C31, + 0xC6D8, 0x66DD, 0xC6D9, 0x7011, 0xC6DA, 0x671F, 0xC6DB, 0x6B3A, 0xC6DC, 0x6816, 0xC6DD, 0x621A, 0xC6DE, 0x59BB, 0xC6DF, 0x4E03, + 0xC6E0, 0x51C4, 0xC6E1, 0x6F06, 0xC6E2, 0x67D2, 0xC6E3, 0x6C8F, 0xC6E4, 0x5176, 0xC6E5, 0x68CB, 0xC6E6, 0x5947, 0xC6E7, 0x6B67, + 0xC6E8, 0x7566, 0xC6E9, 0x5D0E, 0xC6EA, 0x8110, 0xC6EB, 0x9F50, 0xC6EC, 0x65D7, 0xC6ED, 0x7948, 0xC6EE, 0x7941, 0xC6EF, 0x9A91, + 0xC6F0, 0x8D77, 0xC6F1, 0x5C82, 0xC6F2, 0x4E5E, 0xC6F3, 0x4F01, 0xC6F4, 0x542F, 0xC6F5, 0x5951, 0xC6F6, 0x780C, 0xC6F7, 0x5668, + 0xC6F8, 0x6C14, 0xC6F9, 0x8FC4, 0xC6FA, 0x5F03, 0xC6FB, 0x6C7D, 0xC6FC, 0x6CE3, 0xC6FD, 0x8BAB, 0xC6FE, 0x6390, 0xC740, 0x833E, + 0xC741, 0x833F, 0xC742, 0x8341, 0xC743, 0x8342, 0xC744, 0x8344, 0xC745, 0x8345, 0xC746, 0x8348, 0xC747, 0x834A, 0xC748, 0x834B, + 0xC749, 0x834C, 0xC74A, 0x834D, 0xC74B, 0x834E, 0xC74C, 0x8353, 0xC74D, 0x8355, 0xC74E, 0x8356, 0xC74F, 0x8357, 0xC750, 0x8358, + 0xC751, 0x8359, 0xC752, 0x835D, 0xC753, 0x8362, 0xC754, 0x8370, 0xC755, 0x8371, 0xC756, 0x8372, 0xC757, 0x8373, 0xC758, 0x8374, + 0xC759, 0x8375, 0xC75A, 0x8376, 0xC75B, 0x8379, 0xC75C, 0x837A, 0xC75D, 0x837E, 0xC75E, 0x837F, 0xC75F, 0x8380, 0xC760, 0x8381, + 0xC761, 0x8382, 0xC762, 0x8383, 0xC763, 0x8384, 0xC764, 0x8387, 0xC765, 0x8388, 0xC766, 0x838A, 0xC767, 0x838B, 0xC768, 0x838C, + 0xC769, 0x838D, 0xC76A, 0x838F, 0xC76B, 0x8390, 0xC76C, 0x8391, 0xC76D, 0x8394, 0xC76E, 0x8395, 0xC76F, 0x8396, 0xC770, 0x8397, + 0xC771, 0x8399, 0xC772, 0x839A, 0xC773, 0x839D, 0xC774, 0x839F, 0xC775, 0x83A1, 0xC776, 0x83A2, 0xC777, 0x83A3, 0xC778, 0x83A4, + 0xC779, 0x83A5, 0xC77A, 0x83A6, 0xC77B, 0x83A7, 0xC77C, 0x83AC, 0xC77D, 0x83AD, 0xC77E, 0x83AE, 0xC780, 0x83AF, 0xC781, 0x83B5, + 0xC782, 0x83BB, 0xC783, 0x83BE, 0xC784, 0x83BF, 0xC785, 0x83C2, 0xC786, 0x83C3, 0xC787, 0x83C4, 0xC788, 0x83C6, 0xC789, 0x83C8, + 0xC78A, 0x83C9, 0xC78B, 0x83CB, 0xC78C, 0x83CD, 0xC78D, 0x83CE, 0xC78E, 0x83D0, 0xC78F, 0x83D1, 0xC790, 0x83D2, 0xC791, 0x83D3, + 0xC792, 0x83D5, 0xC793, 0x83D7, 0xC794, 0x83D9, 0xC795, 0x83DA, 0xC796, 0x83DB, 0xC797, 0x83DE, 0xC798, 0x83E2, 0xC799, 0x83E3, + 0xC79A, 0x83E4, 0xC79B, 0x83E6, 0xC79C, 0x83E7, 0xC79D, 0x83E8, 0xC79E, 0x83EB, 0xC79F, 0x83EC, 0xC7A0, 0x83ED, 0xC7A1, 0x6070, + 0xC7A2, 0x6D3D, 0xC7A3, 0x7275, 0xC7A4, 0x6266, 0xC7A5, 0x948E, 0xC7A6, 0x94C5, 0xC7A7, 0x5343, 0xC7A8, 0x8FC1, 0xC7A9, 0x7B7E, + 0xC7AA, 0x4EDF, 0xC7AB, 0x8C26, 0xC7AC, 0x4E7E, 0xC7AD, 0x9ED4, 0xC7AE, 0x94B1, 0xC7AF, 0x94B3, 0xC7B0, 0x524D, 0xC7B1, 0x6F5C, + 0xC7B2, 0x9063, 0xC7B3, 0x6D45, 0xC7B4, 0x8C34, 0xC7B5, 0x5811, 0xC7B6, 0x5D4C, 0xC7B7, 0x6B20, 0xC7B8, 0x6B49, 0xC7B9, 0x67AA, + 0xC7BA, 0x545B, 0xC7BB, 0x8154, 0xC7BC, 0x7F8C, 0xC7BD, 0x5899, 0xC7BE, 0x8537, 0xC7BF, 0x5F3A, 0xC7C0, 0x62A2, 0xC7C1, 0x6A47, + 0xC7C2, 0x9539, 0xC7C3, 0x6572, 0xC7C4, 0x6084, 0xC7C5, 0x6865, 0xC7C6, 0x77A7, 0xC7C7, 0x4E54, 0xC7C8, 0x4FA8, 0xC7C9, 0x5DE7, + 0xC7CA, 0x9798, 0xC7CB, 0x64AC, 0xC7CC, 0x7FD8, 0xC7CD, 0x5CED, 0xC7CE, 0x4FCF, 0xC7CF, 0x7A8D, 0xC7D0, 0x5207, 0xC7D1, 0x8304, + 0xC7D2, 0x4E14, 0xC7D3, 0x602F, 0xC7D4, 0x7A83, 0xC7D5, 0x94A6, 0xC7D6, 0x4FB5, 0xC7D7, 0x4EB2, 0xC7D8, 0x79E6, 0xC7D9, 0x7434, + 0xC7DA, 0x52E4, 0xC7DB, 0x82B9, 0xC7DC, 0x64D2, 0xC7DD, 0x79BD, 0xC7DE, 0x5BDD, 0xC7DF, 0x6C81, 0xC7E0, 0x9752, 0xC7E1, 0x8F7B, + 0xC7E2, 0x6C22, 0xC7E3, 0x503E, 0xC7E4, 0x537F, 0xC7E5, 0x6E05, 0xC7E6, 0x64CE, 0xC7E7, 0x6674, 0xC7E8, 0x6C30, 0xC7E9, 0x60C5, + 0xC7EA, 0x9877, 0xC7EB, 0x8BF7, 0xC7EC, 0x5E86, 0xC7ED, 0x743C, 0xC7EE, 0x7A77, 0xC7EF, 0x79CB, 0xC7F0, 0x4E18, 0xC7F1, 0x90B1, + 0xC7F2, 0x7403, 0xC7F3, 0x6C42, 0xC7F4, 0x56DA, 0xC7F5, 0x914B, 0xC7F6, 0x6CC5, 0xC7F7, 0x8D8B, 0xC7F8, 0x533A, 0xC7F9, 0x86C6, + 0xC7FA, 0x66F2, 0xC7FB, 0x8EAF, 0xC7FC, 0x5C48, 0xC7FD, 0x9A71, 0xC7FE, 0x6E20, 0xC840, 0x83EE, 0xC841, 0x83EF, 0xC842, 0x83F3, + 0xC843, 0x83F4, 0xC844, 0x83F5, 0xC845, 0x83F6, 0xC846, 0x83F7, 0xC847, 0x83FA, 0xC848, 0x83FB, 0xC849, 0x83FC, 0xC84A, 0x83FE, + 0xC84B, 0x83FF, 0xC84C, 0x8400, 0xC84D, 0x8402, 0xC84E, 0x8405, 0xC84F, 0x8407, 0xC850, 0x8408, 0xC851, 0x8409, 0xC852, 0x840A, + 0xC853, 0x8410, 0xC854, 0x8412, 0xC855, 0x8413, 0xC856, 0x8414, 0xC857, 0x8415, 0xC858, 0x8416, 0xC859, 0x8417, 0xC85A, 0x8419, + 0xC85B, 0x841A, 0xC85C, 0x841B, 0xC85D, 0x841E, 0xC85E, 0x841F, 0xC85F, 0x8420, 0xC860, 0x8421, 0xC861, 0x8422, 0xC862, 0x8423, + 0xC863, 0x8429, 0xC864, 0x842A, 0xC865, 0x842B, 0xC866, 0x842C, 0xC867, 0x842D, 0xC868, 0x842E, 0xC869, 0x842F, 0xC86A, 0x8430, + 0xC86B, 0x8432, 0xC86C, 0x8433, 0xC86D, 0x8434, 0xC86E, 0x8435, 0xC86F, 0x8436, 0xC870, 0x8437, 0xC871, 0x8439, 0xC872, 0x843A, + 0xC873, 0x843B, 0xC874, 0x843E, 0xC875, 0x843F, 0xC876, 0x8440, 0xC877, 0x8441, 0xC878, 0x8442, 0xC879, 0x8443, 0xC87A, 0x8444, + 0xC87B, 0x8445, 0xC87C, 0x8447, 0xC87D, 0x8448, 0xC87E, 0x8449, 0xC880, 0x844A, 0xC881, 0x844B, 0xC882, 0x844C, 0xC883, 0x844D, + 0xC884, 0x844E, 0xC885, 0x844F, 0xC886, 0x8450, 0xC887, 0x8452, 0xC888, 0x8453, 0xC889, 0x8454, 0xC88A, 0x8455, 0xC88B, 0x8456, + 0xC88C, 0x8458, 0xC88D, 0x845D, 0xC88E, 0x845E, 0xC88F, 0x845F, 0xC890, 0x8460, 0xC891, 0x8462, 0xC892, 0x8464, 0xC893, 0x8465, + 0xC894, 0x8466, 0xC895, 0x8467, 0xC896, 0x8468, 0xC897, 0x846A, 0xC898, 0x846E, 0xC899, 0x846F, 0xC89A, 0x8470, 0xC89B, 0x8472, + 0xC89C, 0x8474, 0xC89D, 0x8477, 0xC89E, 0x8479, 0xC89F, 0x847B, 0xC8A0, 0x847C, 0xC8A1, 0x53D6, 0xC8A2, 0x5A36, 0xC8A3, 0x9F8B, + 0xC8A4, 0x8DA3, 0xC8A5, 0x53BB, 0xC8A6, 0x5708, 0xC8A7, 0x98A7, 0xC8A8, 0x6743, 0xC8A9, 0x919B, 0xC8AA, 0x6CC9, 0xC8AB, 0x5168, + 0xC8AC, 0x75CA, 0xC8AD, 0x62F3, 0xC8AE, 0x72AC, 0xC8AF, 0x5238, 0xC8B0, 0x529D, 0xC8B1, 0x7F3A, 0xC8B2, 0x7094, 0xC8B3, 0x7638, + 0xC8B4, 0x5374, 0xC8B5, 0x9E4A, 0xC8B6, 0x69B7, 0xC8B7, 0x786E, 0xC8B8, 0x96C0, 0xC8B9, 0x88D9, 0xC8BA, 0x7FA4, 0xC8BB, 0x7136, + 0xC8BC, 0x71C3, 0xC8BD, 0x5189, 0xC8BE, 0x67D3, 0xC8BF, 0x74E4, 0xC8C0, 0x58E4, 0xC8C1, 0x6518, 0xC8C2, 0x56B7, 0xC8C3, 0x8BA9, + 0xC8C4, 0x9976, 0xC8C5, 0x6270, 0xC8C6, 0x7ED5, 0xC8C7, 0x60F9, 0xC8C8, 0x70ED, 0xC8C9, 0x58EC, 0xC8CA, 0x4EC1, 0xC8CB, 0x4EBA, + 0xC8CC, 0x5FCD, 0xC8CD, 0x97E7, 0xC8CE, 0x4EFB, 0xC8CF, 0x8BA4, 0xC8D0, 0x5203, 0xC8D1, 0x598A, 0xC8D2, 0x7EAB, 0xC8D3, 0x6254, + 0xC8D4, 0x4ECD, 0xC8D5, 0x65E5, 0xC8D6, 0x620E, 0xC8D7, 0x8338, 0xC8D8, 0x84C9, 0xC8D9, 0x8363, 0xC8DA, 0x878D, 0xC8DB, 0x7194, + 0xC8DC, 0x6EB6, 0xC8DD, 0x5BB9, 0xC8DE, 0x7ED2, 0xC8DF, 0x5197, 0xC8E0, 0x63C9, 0xC8E1, 0x67D4, 0xC8E2, 0x8089, 0xC8E3, 0x8339, + 0xC8E4, 0x8815, 0xC8E5, 0x5112, 0xC8E6, 0x5B7A, 0xC8E7, 0x5982, 0xC8E8, 0x8FB1, 0xC8E9, 0x4E73, 0xC8EA, 0x6C5D, 0xC8EB, 0x5165, + 0xC8EC, 0x8925, 0xC8ED, 0x8F6F, 0xC8EE, 0x962E, 0xC8EF, 0x854A, 0xC8F0, 0x745E, 0xC8F1, 0x9510, 0xC8F2, 0x95F0, 0xC8F3, 0x6DA6, + 0xC8F4, 0x82E5, 0xC8F5, 0x5F31, 0xC8F6, 0x6492, 0xC8F7, 0x6D12, 0xC8F8, 0x8428, 0xC8F9, 0x816E, 0xC8FA, 0x9CC3, 0xC8FB, 0x585E, + 0xC8FC, 0x8D5B, 0xC8FD, 0x4E09, 0xC8FE, 0x53C1, 0xC940, 0x847D, 0xC941, 0x847E, 0xC942, 0x847F, 0xC943, 0x8480, 0xC944, 0x8481, + 0xC945, 0x8483, 0xC946, 0x8484, 0xC947, 0x8485, 0xC948, 0x8486, 0xC949, 0x848A, 0xC94A, 0x848D, 0xC94B, 0x848F, 0xC94C, 0x8490, + 0xC94D, 0x8491, 0xC94E, 0x8492, 0xC94F, 0x8493, 0xC950, 0x8494, 0xC951, 0x8495, 0xC952, 0x8496, 0xC953, 0x8498, 0xC954, 0x849A, + 0xC955, 0x849B, 0xC956, 0x849D, 0xC957, 0x849E, 0xC958, 0x849F, 0xC959, 0x84A0, 0xC95A, 0x84A2, 0xC95B, 0x84A3, 0xC95C, 0x84A4, + 0xC95D, 0x84A5, 0xC95E, 0x84A6, 0xC95F, 0x84A7, 0xC960, 0x84A8, 0xC961, 0x84A9, 0xC962, 0x84AA, 0xC963, 0x84AB, 0xC964, 0x84AC, + 0xC965, 0x84AD, 0xC966, 0x84AE, 0xC967, 0x84B0, 0xC968, 0x84B1, 0xC969, 0x84B3, 0xC96A, 0x84B5, 0xC96B, 0x84B6, 0xC96C, 0x84B7, + 0xC96D, 0x84BB, 0xC96E, 0x84BC, 0xC96F, 0x84BE, 0xC970, 0x84C0, 0xC971, 0x84C2, 0xC972, 0x84C3, 0xC973, 0x84C5, 0xC974, 0x84C6, + 0xC975, 0x84C7, 0xC976, 0x84C8, 0xC977, 0x84CB, 0xC978, 0x84CC, 0xC979, 0x84CE, 0xC97A, 0x84CF, 0xC97B, 0x84D2, 0xC97C, 0x84D4, + 0xC97D, 0x84D5, 0xC97E, 0x84D7, 0xC980, 0x84D8, 0xC981, 0x84D9, 0xC982, 0x84DA, 0xC983, 0x84DB, 0xC984, 0x84DC, 0xC985, 0x84DE, + 0xC986, 0x84E1, 0xC987, 0x84E2, 0xC988, 0x84E4, 0xC989, 0x84E7, 0xC98A, 0x84E8, 0xC98B, 0x84E9, 0xC98C, 0x84EA, 0xC98D, 0x84EB, + 0xC98E, 0x84ED, 0xC98F, 0x84EE, 0xC990, 0x84EF, 0xC991, 0x84F1, 0xC992, 0x84F2, 0xC993, 0x84F3, 0xC994, 0x84F4, 0xC995, 0x84F5, + 0xC996, 0x84F6, 0xC997, 0x84F7, 0xC998, 0x84F8, 0xC999, 0x84F9, 0xC99A, 0x84FA, 0xC99B, 0x84FB, 0xC99C, 0x84FD, 0xC99D, 0x84FE, + 0xC99E, 0x8500, 0xC99F, 0x8501, 0xC9A0, 0x8502, 0xC9A1, 0x4F1E, 0xC9A2, 0x6563, 0xC9A3, 0x6851, 0xC9A4, 0x55D3, 0xC9A5, 0x4E27, + 0xC9A6, 0x6414, 0xC9A7, 0x9A9A, 0xC9A8, 0x626B, 0xC9A9, 0x5AC2, 0xC9AA, 0x745F, 0xC9AB, 0x8272, 0xC9AC, 0x6DA9, 0xC9AD, 0x68EE, + 0xC9AE, 0x50E7, 0xC9AF, 0x838E, 0xC9B0, 0x7802, 0xC9B1, 0x6740, 0xC9B2, 0x5239, 0xC9B3, 0x6C99, 0xC9B4, 0x7EB1, 0xC9B5, 0x50BB, + 0xC9B6, 0x5565, 0xC9B7, 0x715E, 0xC9B8, 0x7B5B, 0xC9B9, 0x6652, 0xC9BA, 0x73CA, 0xC9BB, 0x82EB, 0xC9BC, 0x6749, 0xC9BD, 0x5C71, + 0xC9BE, 0x5220, 0xC9BF, 0x717D, 0xC9C0, 0x886B, 0xC9C1, 0x95EA, 0xC9C2, 0x9655, 0xC9C3, 0x64C5, 0xC9C4, 0x8D61, 0xC9C5, 0x81B3, + 0xC9C6, 0x5584, 0xC9C7, 0x6C55, 0xC9C8, 0x6247, 0xC9C9, 0x7F2E, 0xC9CA, 0x5892, 0xC9CB, 0x4F24, 0xC9CC, 0x5546, 0xC9CD, 0x8D4F, + 0xC9CE, 0x664C, 0xC9CF, 0x4E0A, 0xC9D0, 0x5C1A, 0xC9D1, 0x88F3, 0xC9D2, 0x68A2, 0xC9D3, 0x634E, 0xC9D4, 0x7A0D, 0xC9D5, 0x70E7, + 0xC9D6, 0x828D, 0xC9D7, 0x52FA, 0xC9D8, 0x97F6, 0xC9D9, 0x5C11, 0xC9DA, 0x54E8, 0xC9DB, 0x90B5, 0xC9DC, 0x7ECD, 0xC9DD, 0x5962, + 0xC9DE, 0x8D4A, 0xC9DF, 0x86C7, 0xC9E0, 0x820C, 0xC9E1, 0x820D, 0xC9E2, 0x8D66, 0xC9E3, 0x6444, 0xC9E4, 0x5C04, 0xC9E5, 0x6151, + 0xC9E6, 0x6D89, 0xC9E7, 0x793E, 0xC9E8, 0x8BBE, 0xC9E9, 0x7837, 0xC9EA, 0x7533, 0xC9EB, 0x547B, 0xC9EC, 0x4F38, 0xC9ED, 0x8EAB, + 0xC9EE, 0x6DF1, 0xC9EF, 0x5A20, 0xC9F0, 0x7EC5, 0xC9F1, 0x795E, 0xC9F2, 0x6C88, 0xC9F3, 0x5BA1, 0xC9F4, 0x5A76, 0xC9F5, 0x751A, + 0xC9F6, 0x80BE, 0xC9F7, 0x614E, 0xC9F8, 0x6E17, 0xC9F9, 0x58F0, 0xC9FA, 0x751F, 0xC9FB, 0x7525, 0xC9FC, 0x7272, 0xC9FD, 0x5347, + 0xC9FE, 0x7EF3, 0xCA40, 0x8503, 0xCA41, 0x8504, 0xCA42, 0x8505, 0xCA43, 0x8506, 0xCA44, 0x8507, 0xCA45, 0x8508, 0xCA46, 0x8509, + 0xCA47, 0x850A, 0xCA48, 0x850B, 0xCA49, 0x850D, 0xCA4A, 0x850E, 0xCA4B, 0x850F, 0xCA4C, 0x8510, 0xCA4D, 0x8512, 0xCA4E, 0x8514, + 0xCA4F, 0x8515, 0xCA50, 0x8516, 0xCA51, 0x8518, 0xCA52, 0x8519, 0xCA53, 0x851B, 0xCA54, 0x851C, 0xCA55, 0x851D, 0xCA56, 0x851E, + 0xCA57, 0x8520, 0xCA58, 0x8522, 0xCA59, 0x8523, 0xCA5A, 0x8524, 0xCA5B, 0x8525, 0xCA5C, 0x8526, 0xCA5D, 0x8527, 0xCA5E, 0x8528, + 0xCA5F, 0x8529, 0xCA60, 0x852A, 0xCA61, 0x852D, 0xCA62, 0x852E, 0xCA63, 0x852F, 0xCA64, 0x8530, 0xCA65, 0x8531, 0xCA66, 0x8532, + 0xCA67, 0x8533, 0xCA68, 0x8534, 0xCA69, 0x8535, 0xCA6A, 0x8536, 0xCA6B, 0x853E, 0xCA6C, 0x853F, 0xCA6D, 0x8540, 0xCA6E, 0x8541, + 0xCA6F, 0x8542, 0xCA70, 0x8544, 0xCA71, 0x8545, 0xCA72, 0x8546, 0xCA73, 0x8547, 0xCA74, 0x854B, 0xCA75, 0x854C, 0xCA76, 0x854D, + 0xCA77, 0x854E, 0xCA78, 0x854F, 0xCA79, 0x8550, 0xCA7A, 0x8551, 0xCA7B, 0x8552, 0xCA7C, 0x8553, 0xCA7D, 0x8554, 0xCA7E, 0x8555, + 0xCA80, 0x8557, 0xCA81, 0x8558, 0xCA82, 0x855A, 0xCA83, 0x855B, 0xCA84, 0x855C, 0xCA85, 0x855D, 0xCA86, 0x855F, 0xCA87, 0x8560, + 0xCA88, 0x8561, 0xCA89, 0x8562, 0xCA8A, 0x8563, 0xCA8B, 0x8565, 0xCA8C, 0x8566, 0xCA8D, 0x8567, 0xCA8E, 0x8569, 0xCA8F, 0x856A, + 0xCA90, 0x856B, 0xCA91, 0x856C, 0xCA92, 0x856D, 0xCA93, 0x856E, 0xCA94, 0x856F, 0xCA95, 0x8570, 0xCA96, 0x8571, 0xCA97, 0x8573, + 0xCA98, 0x8575, 0xCA99, 0x8576, 0xCA9A, 0x8577, 0xCA9B, 0x8578, 0xCA9C, 0x857C, 0xCA9D, 0x857D, 0xCA9E, 0x857F, 0xCA9F, 0x8580, + 0xCAA0, 0x8581, 0xCAA1, 0x7701, 0xCAA2, 0x76DB, 0xCAA3, 0x5269, 0xCAA4, 0x80DC, 0xCAA5, 0x5723, 0xCAA6, 0x5E08, 0xCAA7, 0x5931, + 0xCAA8, 0x72EE, 0xCAA9, 0x65BD, 0xCAAA, 0x6E7F, 0xCAAB, 0x8BD7, 0xCAAC, 0x5C38, 0xCAAD, 0x8671, 0xCAAE, 0x5341, 0xCAAF, 0x77F3, + 0xCAB0, 0x62FE, 0xCAB1, 0x65F6, 0xCAB2, 0x4EC0, 0xCAB3, 0x98DF, 0xCAB4, 0x8680, 0xCAB5, 0x5B9E, 0xCAB6, 0x8BC6, 0xCAB7, 0x53F2, + 0xCAB8, 0x77E2, 0xCAB9, 0x4F7F, 0xCABA, 0x5C4E, 0xCABB, 0x9A76, 0xCABC, 0x59CB, 0xCABD, 0x5F0F, 0xCABE, 0x793A, 0xCABF, 0x58EB, + 0xCAC0, 0x4E16, 0xCAC1, 0x67FF, 0xCAC2, 0x4E8B, 0xCAC3, 0x62ED, 0xCAC4, 0x8A93, 0xCAC5, 0x901D, 0xCAC6, 0x52BF, 0xCAC7, 0x662F, + 0xCAC8, 0x55DC, 0xCAC9, 0x566C, 0xCACA, 0x9002, 0xCACB, 0x4ED5, 0xCACC, 0x4F8D, 0xCACD, 0x91CA, 0xCACE, 0x9970, 0xCACF, 0x6C0F, + 0xCAD0, 0x5E02, 0xCAD1, 0x6043, 0xCAD2, 0x5BA4, 0xCAD3, 0x89C6, 0xCAD4, 0x8BD5, 0xCAD5, 0x6536, 0xCAD6, 0x624B, 0xCAD7, 0x9996, + 0xCAD8, 0x5B88, 0xCAD9, 0x5BFF, 0xCADA, 0x6388, 0xCADB, 0x552E, 0xCADC, 0x53D7, 0xCADD, 0x7626, 0xCADE, 0x517D, 0xCADF, 0x852C, + 0xCAE0, 0x67A2, 0xCAE1, 0x68B3, 0xCAE2, 0x6B8A, 0xCAE3, 0x6292, 0xCAE4, 0x8F93, 0xCAE5, 0x53D4, 0xCAE6, 0x8212, 0xCAE7, 0x6DD1, + 0xCAE8, 0x758F, 0xCAE9, 0x4E66, 0xCAEA, 0x8D4E, 0xCAEB, 0x5B70, 0xCAEC, 0x719F, 0xCAED, 0x85AF, 0xCAEE, 0x6691, 0xCAEF, 0x66D9, + 0xCAF0, 0x7F72, 0xCAF1, 0x8700, 0xCAF2, 0x9ECD, 0xCAF3, 0x9F20, 0xCAF4, 0x5C5E, 0xCAF5, 0x672F, 0xCAF6, 0x8FF0, 0xCAF7, 0x6811, + 0xCAF8, 0x675F, 0xCAF9, 0x620D, 0xCAFA, 0x7AD6, 0xCAFB, 0x5885, 0xCAFC, 0x5EB6, 0xCAFD, 0x6570, 0xCAFE, 0x6F31, 0xCB40, 0x8582, + 0xCB41, 0x8583, 0xCB42, 0x8586, 0xCB43, 0x8588, 0xCB44, 0x8589, 0xCB45, 0x858A, 0xCB46, 0x858B, 0xCB47, 0x858C, 0xCB48, 0x858D, + 0xCB49, 0x858E, 0xCB4A, 0x8590, 0xCB4B, 0x8591, 0xCB4C, 0x8592, 0xCB4D, 0x8593, 0xCB4E, 0x8594, 0xCB4F, 0x8595, 0xCB50, 0x8596, + 0xCB51, 0x8597, 0xCB52, 0x8598, 0xCB53, 0x8599, 0xCB54, 0x859A, 0xCB55, 0x859D, 0xCB56, 0x859E, 0xCB57, 0x859F, 0xCB58, 0x85A0, + 0xCB59, 0x85A1, 0xCB5A, 0x85A2, 0xCB5B, 0x85A3, 0xCB5C, 0x85A5, 0xCB5D, 0x85A6, 0xCB5E, 0x85A7, 0xCB5F, 0x85A9, 0xCB60, 0x85AB, + 0xCB61, 0x85AC, 0xCB62, 0x85AD, 0xCB63, 0x85B1, 0xCB64, 0x85B2, 0xCB65, 0x85B3, 0xCB66, 0x85B4, 0xCB67, 0x85B5, 0xCB68, 0x85B6, + 0xCB69, 0x85B8, 0xCB6A, 0x85BA, 0xCB6B, 0x85BB, 0xCB6C, 0x85BC, 0xCB6D, 0x85BD, 0xCB6E, 0x85BE, 0xCB6F, 0x85BF, 0xCB70, 0x85C0, + 0xCB71, 0x85C2, 0xCB72, 0x85C3, 0xCB73, 0x85C4, 0xCB74, 0x85C5, 0xCB75, 0x85C6, 0xCB76, 0x85C7, 0xCB77, 0x85C8, 0xCB78, 0x85CA, + 0xCB79, 0x85CB, 0xCB7A, 0x85CC, 0xCB7B, 0x85CD, 0xCB7C, 0x85CE, 0xCB7D, 0x85D1, 0xCB7E, 0x85D2, 0xCB80, 0x85D4, 0xCB81, 0x85D6, + 0xCB82, 0x85D7, 0xCB83, 0x85D8, 0xCB84, 0x85D9, 0xCB85, 0x85DA, 0xCB86, 0x85DB, 0xCB87, 0x85DD, 0xCB88, 0x85DE, 0xCB89, 0x85DF, + 0xCB8A, 0x85E0, 0xCB8B, 0x85E1, 0xCB8C, 0x85E2, 0xCB8D, 0x85E3, 0xCB8E, 0x85E5, 0xCB8F, 0x85E6, 0xCB90, 0x85E7, 0xCB91, 0x85E8, + 0xCB92, 0x85EA, 0xCB93, 0x85EB, 0xCB94, 0x85EC, 0xCB95, 0x85ED, 0xCB96, 0x85EE, 0xCB97, 0x85EF, 0xCB98, 0x85F0, 0xCB99, 0x85F1, + 0xCB9A, 0x85F2, 0xCB9B, 0x85F3, 0xCB9C, 0x85F4, 0xCB9D, 0x85F5, 0xCB9E, 0x85F6, 0xCB9F, 0x85F7, 0xCBA0, 0x85F8, 0xCBA1, 0x6055, + 0xCBA2, 0x5237, 0xCBA3, 0x800D, 0xCBA4, 0x6454, 0xCBA5, 0x8870, 0xCBA6, 0x7529, 0xCBA7, 0x5E05, 0xCBA8, 0x6813, 0xCBA9, 0x62F4, + 0xCBAA, 0x971C, 0xCBAB, 0x53CC, 0xCBAC, 0x723D, 0xCBAD, 0x8C01, 0xCBAE, 0x6C34, 0xCBAF, 0x7761, 0xCBB0, 0x7A0E, 0xCBB1, 0x542E, + 0xCBB2, 0x77AC, 0xCBB3, 0x987A, 0xCBB4, 0x821C, 0xCBB5, 0x8BF4, 0xCBB6, 0x7855, 0xCBB7, 0x6714, 0xCBB8, 0x70C1, 0xCBB9, 0x65AF, + 0xCBBA, 0x6495, 0xCBBB, 0x5636, 0xCBBC, 0x601D, 0xCBBD, 0x79C1, 0xCBBE, 0x53F8, 0xCBBF, 0x4E1D, 0xCBC0, 0x6B7B, 0xCBC1, 0x8086, + 0xCBC2, 0x5BFA, 0xCBC3, 0x55E3, 0xCBC4, 0x56DB, 0xCBC5, 0x4F3A, 0xCBC6, 0x4F3C, 0xCBC7, 0x9972, 0xCBC8, 0x5DF3, 0xCBC9, 0x677E, + 0xCBCA, 0x8038, 0xCBCB, 0x6002, 0xCBCC, 0x9882, 0xCBCD, 0x9001, 0xCBCE, 0x5B8B, 0xCBCF, 0x8BBC, 0xCBD0, 0x8BF5, 0xCBD1, 0x641C, + 0xCBD2, 0x8258, 0xCBD3, 0x64DE, 0xCBD4, 0x55FD, 0xCBD5, 0x82CF, 0xCBD6, 0x9165, 0xCBD7, 0x4FD7, 0xCBD8, 0x7D20, 0xCBD9, 0x901F, + 0xCBDA, 0x7C9F, 0xCBDB, 0x50F3, 0xCBDC, 0x5851, 0xCBDD, 0x6EAF, 0xCBDE, 0x5BBF, 0xCBDF, 0x8BC9, 0xCBE0, 0x8083, 0xCBE1, 0x9178, + 0xCBE2, 0x849C, 0xCBE3, 0x7B97, 0xCBE4, 0x867D, 0xCBE5, 0x968B, 0xCBE6, 0x968F, 0xCBE7, 0x7EE5, 0xCBE8, 0x9AD3, 0xCBE9, 0x788E, + 0xCBEA, 0x5C81, 0xCBEB, 0x7A57, 0xCBEC, 0x9042, 0xCBED, 0x96A7, 0xCBEE, 0x795F, 0xCBEF, 0x5B59, 0xCBF0, 0x635F, 0xCBF1, 0x7B0B, + 0xCBF2, 0x84D1, 0xCBF3, 0x68AD, 0xCBF4, 0x5506, 0xCBF5, 0x7F29, 0xCBF6, 0x7410, 0xCBF7, 0x7D22, 0xCBF8, 0x9501, 0xCBF9, 0x6240, + 0xCBFA, 0x584C, 0xCBFB, 0x4ED6, 0xCBFC, 0x5B83, 0xCBFD, 0x5979, 0xCBFE, 0x5854, 0xCC40, 0x85F9, 0xCC41, 0x85FA, 0xCC42, 0x85FC, + 0xCC43, 0x85FD, 0xCC44, 0x85FE, 0xCC45, 0x8600, 0xCC46, 0x8601, 0xCC47, 0x8602, 0xCC48, 0x8603, 0xCC49, 0x8604, 0xCC4A, 0x8606, + 0xCC4B, 0x8607, 0xCC4C, 0x8608, 0xCC4D, 0x8609, 0xCC4E, 0x860A, 0xCC4F, 0x860B, 0xCC50, 0x860C, 0xCC51, 0x860D, 0xCC52, 0x860E, + 0xCC53, 0x860F, 0xCC54, 0x8610, 0xCC55, 0x8612, 0xCC56, 0x8613, 0xCC57, 0x8614, 0xCC58, 0x8615, 0xCC59, 0x8617, 0xCC5A, 0x8618, + 0xCC5B, 0x8619, 0xCC5C, 0x861A, 0xCC5D, 0x861B, 0xCC5E, 0x861C, 0xCC5F, 0x861D, 0xCC60, 0x861E, 0xCC61, 0x861F, 0xCC62, 0x8620, + 0xCC63, 0x8621, 0xCC64, 0x8622, 0xCC65, 0x8623, 0xCC66, 0x8624, 0xCC67, 0x8625, 0xCC68, 0x8626, 0xCC69, 0x8628, 0xCC6A, 0x862A, + 0xCC6B, 0x862B, 0xCC6C, 0x862C, 0xCC6D, 0x862D, 0xCC6E, 0x862E, 0xCC6F, 0x862F, 0xCC70, 0x8630, 0xCC71, 0x8631, 0xCC72, 0x8632, + 0xCC73, 0x8633, 0xCC74, 0x8634, 0xCC75, 0x8635, 0xCC76, 0x8636, 0xCC77, 0x8637, 0xCC78, 0x8639, 0xCC79, 0x863A, 0xCC7A, 0x863B, + 0xCC7B, 0x863D, 0xCC7C, 0x863E, 0xCC7D, 0x863F, 0xCC7E, 0x8640, 0xCC80, 0x8641, 0xCC81, 0x8642, 0xCC82, 0x8643, 0xCC83, 0x8644, + 0xCC84, 0x8645, 0xCC85, 0x8646, 0xCC86, 0x8647, 0xCC87, 0x8648, 0xCC88, 0x8649, 0xCC89, 0x864A, 0xCC8A, 0x864B, 0xCC8B, 0x864C, + 0xCC8C, 0x8652, 0xCC8D, 0x8653, 0xCC8E, 0x8655, 0xCC8F, 0x8656, 0xCC90, 0x8657, 0xCC91, 0x8658, 0xCC92, 0x8659, 0xCC93, 0x865B, + 0xCC94, 0x865C, 0xCC95, 0x865D, 0xCC96, 0x865F, 0xCC97, 0x8660, 0xCC98, 0x8661, 0xCC99, 0x8663, 0xCC9A, 0x8664, 0xCC9B, 0x8665, + 0xCC9C, 0x8666, 0xCC9D, 0x8667, 0xCC9E, 0x8668, 0xCC9F, 0x8669, 0xCCA0, 0x866A, 0xCCA1, 0x736D, 0xCCA2, 0x631E, 0xCCA3, 0x8E4B, + 0xCCA4, 0x8E0F, 0xCCA5, 0x80CE, 0xCCA6, 0x82D4, 0xCCA7, 0x62AC, 0xCCA8, 0x53F0, 0xCCA9, 0x6CF0, 0xCCAA, 0x915E, 0xCCAB, 0x592A, + 0xCCAC, 0x6001, 0xCCAD, 0x6C70, 0xCCAE, 0x574D, 0xCCAF, 0x644A, 0xCCB0, 0x8D2A, 0xCCB1, 0x762B, 0xCCB2, 0x6EE9, 0xCCB3, 0x575B, + 0xCCB4, 0x6A80, 0xCCB5, 0x75F0, 0xCCB6, 0x6F6D, 0xCCB7, 0x8C2D, 0xCCB8, 0x8C08, 0xCCB9, 0x5766, 0xCCBA, 0x6BEF, 0xCCBB, 0x8892, + 0xCCBC, 0x78B3, 0xCCBD, 0x63A2, 0xCCBE, 0x53F9, 0xCCBF, 0x70AD, 0xCCC0, 0x6C64, 0xCCC1, 0x5858, 0xCCC2, 0x642A, 0xCCC3, 0x5802, + 0xCCC4, 0x68E0, 0xCCC5, 0x819B, 0xCCC6, 0x5510, 0xCCC7, 0x7CD6, 0xCCC8, 0x5018, 0xCCC9, 0x8EBA, 0xCCCA, 0x6DCC, 0xCCCB, 0x8D9F, + 0xCCCC, 0x70EB, 0xCCCD, 0x638F, 0xCCCE, 0x6D9B, 0xCCCF, 0x6ED4, 0xCCD0, 0x7EE6, 0xCCD1, 0x8404, 0xCCD2, 0x6843, 0xCCD3, 0x9003, + 0xCCD4, 0x6DD8, 0xCCD5, 0x9676, 0xCCD6, 0x8BA8, 0xCCD7, 0x5957, 0xCCD8, 0x7279, 0xCCD9, 0x85E4, 0xCCDA, 0x817E, 0xCCDB, 0x75BC, + 0xCCDC, 0x8A8A, 0xCCDD, 0x68AF, 0xCCDE, 0x5254, 0xCCDF, 0x8E22, 0xCCE0, 0x9511, 0xCCE1, 0x63D0, 0xCCE2, 0x9898, 0xCCE3, 0x8E44, + 0xCCE4, 0x557C, 0xCCE5, 0x4F53, 0xCCE6, 0x66FF, 0xCCE7, 0x568F, 0xCCE8, 0x60D5, 0xCCE9, 0x6D95, 0xCCEA, 0x5243, 0xCCEB, 0x5C49, + 0xCCEC, 0x5929, 0xCCED, 0x6DFB, 0xCCEE, 0x586B, 0xCCEF, 0x7530, 0xCCF0, 0x751C, 0xCCF1, 0x606C, 0xCCF2, 0x8214, 0xCCF3, 0x8146, + 0xCCF4, 0x6311, 0xCCF5, 0x6761, 0xCCF6, 0x8FE2, 0xCCF7, 0x773A, 0xCCF8, 0x8DF3, 0xCCF9, 0x8D34, 0xCCFA, 0x94C1, 0xCCFB, 0x5E16, + 0xCCFC, 0x5385, 0xCCFD, 0x542C, 0xCCFE, 0x70C3, 0xCD40, 0x866D, 0xCD41, 0x866F, 0xCD42, 0x8670, 0xCD43, 0x8672, 0xCD44, 0x8673, + 0xCD45, 0x8674, 0xCD46, 0x8675, 0xCD47, 0x8676, 0xCD48, 0x8677, 0xCD49, 0x8678, 0xCD4A, 0x8683, 0xCD4B, 0x8684, 0xCD4C, 0x8685, + 0xCD4D, 0x8686, 0xCD4E, 0x8687, 0xCD4F, 0x8688, 0xCD50, 0x8689, 0xCD51, 0x868E, 0xCD52, 0x868F, 0xCD53, 0x8690, 0xCD54, 0x8691, + 0xCD55, 0x8692, 0xCD56, 0x8694, 0xCD57, 0x8696, 0xCD58, 0x8697, 0xCD59, 0x8698, 0xCD5A, 0x8699, 0xCD5B, 0x869A, 0xCD5C, 0x869B, + 0xCD5D, 0x869E, 0xCD5E, 0x869F, 0xCD5F, 0x86A0, 0xCD60, 0x86A1, 0xCD61, 0x86A2, 0xCD62, 0x86A5, 0xCD63, 0x86A6, 0xCD64, 0x86AB, + 0xCD65, 0x86AD, 0xCD66, 0x86AE, 0xCD67, 0x86B2, 0xCD68, 0x86B3, 0xCD69, 0x86B7, 0xCD6A, 0x86B8, 0xCD6B, 0x86B9, 0xCD6C, 0x86BB, + 0xCD6D, 0x86BC, 0xCD6E, 0x86BD, 0xCD6F, 0x86BE, 0xCD70, 0x86BF, 0xCD71, 0x86C1, 0xCD72, 0x86C2, 0xCD73, 0x86C3, 0xCD74, 0x86C5, + 0xCD75, 0x86C8, 0xCD76, 0x86CC, 0xCD77, 0x86CD, 0xCD78, 0x86D2, 0xCD79, 0x86D3, 0xCD7A, 0x86D5, 0xCD7B, 0x86D6, 0xCD7C, 0x86D7, + 0xCD7D, 0x86DA, 0xCD7E, 0x86DC, 0xCD80, 0x86DD, 0xCD81, 0x86E0, 0xCD82, 0x86E1, 0xCD83, 0x86E2, 0xCD84, 0x86E3, 0xCD85, 0x86E5, + 0xCD86, 0x86E6, 0xCD87, 0x86E7, 0xCD88, 0x86E8, 0xCD89, 0x86EA, 0xCD8A, 0x86EB, 0xCD8B, 0x86EC, 0xCD8C, 0x86EF, 0xCD8D, 0x86F5, + 0xCD8E, 0x86F6, 0xCD8F, 0x86F7, 0xCD90, 0x86FA, 0xCD91, 0x86FB, 0xCD92, 0x86FC, 0xCD93, 0x86FD, 0xCD94, 0x86FF, 0xCD95, 0x8701, + 0xCD96, 0x8704, 0xCD97, 0x8705, 0xCD98, 0x8706, 0xCD99, 0x870B, 0xCD9A, 0x870C, 0xCD9B, 0x870E, 0xCD9C, 0x870F, 0xCD9D, 0x8710, + 0xCD9E, 0x8711, 0xCD9F, 0x8714, 0xCDA0, 0x8716, 0xCDA1, 0x6C40, 0xCDA2, 0x5EF7, 0xCDA3, 0x505C, 0xCDA4, 0x4EAD, 0xCDA5, 0x5EAD, + 0xCDA6, 0x633A, 0xCDA7, 0x8247, 0xCDA8, 0x901A, 0xCDA9, 0x6850, 0xCDAA, 0x916E, 0xCDAB, 0x77B3, 0xCDAC, 0x540C, 0xCDAD, 0x94DC, + 0xCDAE, 0x5F64, 0xCDAF, 0x7AE5, 0xCDB0, 0x6876, 0xCDB1, 0x6345, 0xCDB2, 0x7B52, 0xCDB3, 0x7EDF, 0xCDB4, 0x75DB, 0xCDB5, 0x5077, + 0xCDB6, 0x6295, 0xCDB7, 0x5934, 0xCDB8, 0x900F, 0xCDB9, 0x51F8, 0xCDBA, 0x79C3, 0xCDBB, 0x7A81, 0xCDBC, 0x56FE, 0xCDBD, 0x5F92, + 0xCDBE, 0x9014, 0xCDBF, 0x6D82, 0xCDC0, 0x5C60, 0xCDC1, 0x571F, 0xCDC2, 0x5410, 0xCDC3, 0x5154, 0xCDC4, 0x6E4D, 0xCDC5, 0x56E2, + 0xCDC6, 0x63A8, 0xCDC7, 0x9893, 0xCDC8, 0x817F, 0xCDC9, 0x8715, 0xCDCA, 0x892A, 0xCDCB, 0x9000, 0xCDCC, 0x541E, 0xCDCD, 0x5C6F, + 0xCDCE, 0x81C0, 0xCDCF, 0x62D6, 0xCDD0, 0x6258, 0xCDD1, 0x8131, 0xCDD2, 0x9E35, 0xCDD3, 0x9640, 0xCDD4, 0x9A6E, 0xCDD5, 0x9A7C, + 0xCDD6, 0x692D, 0xCDD7, 0x59A5, 0xCDD8, 0x62D3, 0xCDD9, 0x553E, 0xCDDA, 0x6316, 0xCDDB, 0x54C7, 0xCDDC, 0x86D9, 0xCDDD, 0x6D3C, + 0xCDDE, 0x5A03, 0xCDDF, 0x74E6, 0xCDE0, 0x889C, 0xCDE1, 0x6B6A, 0xCDE2, 0x5916, 0xCDE3, 0x8C4C, 0xCDE4, 0x5F2F, 0xCDE5, 0x6E7E, + 0xCDE6, 0x73A9, 0xCDE7, 0x987D, 0xCDE8, 0x4E38, 0xCDE9, 0x70F7, 0xCDEA, 0x5B8C, 0xCDEB, 0x7897, 0xCDEC, 0x633D, 0xCDED, 0x665A, + 0xCDEE, 0x7696, 0xCDEF, 0x60CB, 0xCDF0, 0x5B9B, 0xCDF1, 0x5A49, 0xCDF2, 0x4E07, 0xCDF3, 0x8155, 0xCDF4, 0x6C6A, 0xCDF5, 0x738B, + 0xCDF6, 0x4EA1, 0xCDF7, 0x6789, 0xCDF8, 0x7F51, 0xCDF9, 0x5F80, 0xCDFA, 0x65FA, 0xCDFB, 0x671B, 0xCDFC, 0x5FD8, 0xCDFD, 0x5984, + 0xCDFE, 0x5A01, 0xCE40, 0x8719, 0xCE41, 0x871B, 0xCE42, 0x871D, 0xCE43, 0x871F, 0xCE44, 0x8720, 0xCE45, 0x8724, 0xCE46, 0x8726, + 0xCE47, 0x8727, 0xCE48, 0x8728, 0xCE49, 0x872A, 0xCE4A, 0x872B, 0xCE4B, 0x872C, 0xCE4C, 0x872D, 0xCE4D, 0x872F, 0xCE4E, 0x8730, + 0xCE4F, 0x8732, 0xCE50, 0x8733, 0xCE51, 0x8735, 0xCE52, 0x8736, 0xCE53, 0x8738, 0xCE54, 0x8739, 0xCE55, 0x873A, 0xCE56, 0x873C, + 0xCE57, 0x873D, 0xCE58, 0x8740, 0xCE59, 0x8741, 0xCE5A, 0x8742, 0xCE5B, 0x8743, 0xCE5C, 0x8744, 0xCE5D, 0x8745, 0xCE5E, 0x8746, + 0xCE5F, 0x874A, 0xCE60, 0x874B, 0xCE61, 0x874D, 0xCE62, 0x874F, 0xCE63, 0x8750, 0xCE64, 0x8751, 0xCE65, 0x8752, 0xCE66, 0x8754, + 0xCE67, 0x8755, 0xCE68, 0x8756, 0xCE69, 0x8758, 0xCE6A, 0x875A, 0xCE6B, 0x875B, 0xCE6C, 0x875C, 0xCE6D, 0x875D, 0xCE6E, 0x875E, + 0xCE6F, 0x875F, 0xCE70, 0x8761, 0xCE71, 0x8762, 0xCE72, 0x8766, 0xCE73, 0x8767, 0xCE74, 0x8768, 0xCE75, 0x8769, 0xCE76, 0x876A, + 0xCE77, 0x876B, 0xCE78, 0x876C, 0xCE79, 0x876D, 0xCE7A, 0x876F, 0xCE7B, 0x8771, 0xCE7C, 0x8772, 0xCE7D, 0x8773, 0xCE7E, 0x8775, + 0xCE80, 0x8777, 0xCE81, 0x8778, 0xCE82, 0x8779, 0xCE83, 0x877A, 0xCE84, 0x877F, 0xCE85, 0x8780, 0xCE86, 0x8781, 0xCE87, 0x8784, + 0xCE88, 0x8786, 0xCE89, 0x8787, 0xCE8A, 0x8789, 0xCE8B, 0x878A, 0xCE8C, 0x878C, 0xCE8D, 0x878E, 0xCE8E, 0x878F, 0xCE8F, 0x8790, + 0xCE90, 0x8791, 0xCE91, 0x8792, 0xCE92, 0x8794, 0xCE93, 0x8795, 0xCE94, 0x8796, 0xCE95, 0x8798, 0xCE96, 0x8799, 0xCE97, 0x879A, + 0xCE98, 0x879B, 0xCE99, 0x879C, 0xCE9A, 0x879D, 0xCE9B, 0x879E, 0xCE9C, 0x87A0, 0xCE9D, 0x87A1, 0xCE9E, 0x87A2, 0xCE9F, 0x87A3, + 0xCEA0, 0x87A4, 0xCEA1, 0x5DCD, 0xCEA2, 0x5FAE, 0xCEA3, 0x5371, 0xCEA4, 0x97E6, 0xCEA5, 0x8FDD, 0xCEA6, 0x6845, 0xCEA7, 0x56F4, + 0xCEA8, 0x552F, 0xCEA9, 0x60DF, 0xCEAA, 0x4E3A, 0xCEAB, 0x6F4D, 0xCEAC, 0x7EF4, 0xCEAD, 0x82C7, 0xCEAE, 0x840E, 0xCEAF, 0x59D4, + 0xCEB0, 0x4F1F, 0xCEB1, 0x4F2A, 0xCEB2, 0x5C3E, 0xCEB3, 0x7EAC, 0xCEB4, 0x672A, 0xCEB5, 0x851A, 0xCEB6, 0x5473, 0xCEB7, 0x754F, + 0xCEB8, 0x80C3, 0xCEB9, 0x5582, 0xCEBA, 0x9B4F, 0xCEBB, 0x4F4D, 0xCEBC, 0x6E2D, 0xCEBD, 0x8C13, 0xCEBE, 0x5C09, 0xCEBF, 0x6170, + 0xCEC0, 0x536B, 0xCEC1, 0x761F, 0xCEC2, 0x6E29, 0xCEC3, 0x868A, 0xCEC4, 0x6587, 0xCEC5, 0x95FB, 0xCEC6, 0x7EB9, 0xCEC7, 0x543B, + 0xCEC8, 0x7A33, 0xCEC9, 0x7D0A, 0xCECA, 0x95EE, 0xCECB, 0x55E1, 0xCECC, 0x7FC1, 0xCECD, 0x74EE, 0xCECE, 0x631D, 0xCECF, 0x8717, + 0xCED0, 0x6DA1, 0xCED1, 0x7A9D, 0xCED2, 0x6211, 0xCED3, 0x65A1, 0xCED4, 0x5367, 0xCED5, 0x63E1, 0xCED6, 0x6C83, 0xCED7, 0x5DEB, + 0xCED8, 0x545C, 0xCED9, 0x94A8, 0xCEDA, 0x4E4C, 0xCEDB, 0x6C61, 0xCEDC, 0x8BEC, 0xCEDD, 0x5C4B, 0xCEDE, 0x65E0, 0xCEDF, 0x829C, + 0xCEE0, 0x68A7, 0xCEE1, 0x543E, 0xCEE2, 0x5434, 0xCEE3, 0x6BCB, 0xCEE4, 0x6B66, 0xCEE5, 0x4E94, 0xCEE6, 0x6342, 0xCEE7, 0x5348, + 0xCEE8, 0x821E, 0xCEE9, 0x4F0D, 0xCEEA, 0x4FAE, 0xCEEB, 0x575E, 0xCEEC, 0x620A, 0xCEED, 0x96FE, 0xCEEE, 0x6664, 0xCEEF, 0x7269, + 0xCEF0, 0x52FF, 0xCEF1, 0x52A1, 0xCEF2, 0x609F, 0xCEF3, 0x8BEF, 0xCEF4, 0x6614, 0xCEF5, 0x7199, 0xCEF6, 0x6790, 0xCEF7, 0x897F, + 0xCEF8, 0x7852, 0xCEF9, 0x77FD, 0xCEFA, 0x6670, 0xCEFB, 0x563B, 0xCEFC, 0x5438, 0xCEFD, 0x9521, 0xCEFE, 0x727A, 0xCF40, 0x87A5, + 0xCF41, 0x87A6, 0xCF42, 0x87A7, 0xCF43, 0x87A9, 0xCF44, 0x87AA, 0xCF45, 0x87AE, 0xCF46, 0x87B0, 0xCF47, 0x87B1, 0xCF48, 0x87B2, + 0xCF49, 0x87B4, 0xCF4A, 0x87B6, 0xCF4B, 0x87B7, 0xCF4C, 0x87B8, 0xCF4D, 0x87B9, 0xCF4E, 0x87BB, 0xCF4F, 0x87BC, 0xCF50, 0x87BE, + 0xCF51, 0x87BF, 0xCF52, 0x87C1, 0xCF53, 0x87C2, 0xCF54, 0x87C3, 0xCF55, 0x87C4, 0xCF56, 0x87C5, 0xCF57, 0x87C7, 0xCF58, 0x87C8, + 0xCF59, 0x87C9, 0xCF5A, 0x87CC, 0xCF5B, 0x87CD, 0xCF5C, 0x87CE, 0xCF5D, 0x87CF, 0xCF5E, 0x87D0, 0xCF5F, 0x87D4, 0xCF60, 0x87D5, + 0xCF61, 0x87D6, 0xCF62, 0x87D7, 0xCF63, 0x87D8, 0xCF64, 0x87D9, 0xCF65, 0x87DA, 0xCF66, 0x87DC, 0xCF67, 0x87DD, 0xCF68, 0x87DE, + 0xCF69, 0x87DF, 0xCF6A, 0x87E1, 0xCF6B, 0x87E2, 0xCF6C, 0x87E3, 0xCF6D, 0x87E4, 0xCF6E, 0x87E6, 0xCF6F, 0x87E7, 0xCF70, 0x87E8, + 0xCF71, 0x87E9, 0xCF72, 0x87EB, 0xCF73, 0x87EC, 0xCF74, 0x87ED, 0xCF75, 0x87EF, 0xCF76, 0x87F0, 0xCF77, 0x87F1, 0xCF78, 0x87F2, + 0xCF79, 0x87F3, 0xCF7A, 0x87F4, 0xCF7B, 0x87F5, 0xCF7C, 0x87F6, 0xCF7D, 0x87F7, 0xCF7E, 0x87F8, 0xCF80, 0x87FA, 0xCF81, 0x87FB, + 0xCF82, 0x87FC, 0xCF83, 0x87FD, 0xCF84, 0x87FF, 0xCF85, 0x8800, 0xCF86, 0x8801, 0xCF87, 0x8802, 0xCF88, 0x8804, 0xCF89, 0x8805, + 0xCF8A, 0x8806, 0xCF8B, 0x8807, 0xCF8C, 0x8808, 0xCF8D, 0x8809, 0xCF8E, 0x880B, 0xCF8F, 0x880C, 0xCF90, 0x880D, 0xCF91, 0x880E, + 0xCF92, 0x880F, 0xCF93, 0x8810, 0xCF94, 0x8811, 0xCF95, 0x8812, 0xCF96, 0x8814, 0xCF97, 0x8817, 0xCF98, 0x8818, 0xCF99, 0x8819, + 0xCF9A, 0x881A, 0xCF9B, 0x881C, 0xCF9C, 0x881D, 0xCF9D, 0x881E, 0xCF9E, 0x881F, 0xCF9F, 0x8820, 0xCFA0, 0x8823, 0xCFA1, 0x7A00, + 0xCFA2, 0x606F, 0xCFA3, 0x5E0C, 0xCFA4, 0x6089, 0xCFA5, 0x819D, 0xCFA6, 0x5915, 0xCFA7, 0x60DC, 0xCFA8, 0x7184, 0xCFA9, 0x70EF, + 0xCFAA, 0x6EAA, 0xCFAB, 0x6C50, 0xCFAC, 0x7280, 0xCFAD, 0x6A84, 0xCFAE, 0x88AD, 0xCFAF, 0x5E2D, 0xCFB0, 0x4E60, 0xCFB1, 0x5AB3, + 0xCFB2, 0x559C, 0xCFB3, 0x94E3, 0xCFB4, 0x6D17, 0xCFB5, 0x7CFB, 0xCFB6, 0x9699, 0xCFB7, 0x620F, 0xCFB8, 0x7EC6, 0xCFB9, 0x778E, + 0xCFBA, 0x867E, 0xCFBB, 0x5323, 0xCFBC, 0x971E, 0xCFBD, 0x8F96, 0xCFBE, 0x6687, 0xCFBF, 0x5CE1, 0xCFC0, 0x4FA0, 0xCFC1, 0x72ED, + 0xCFC2, 0x4E0B, 0xCFC3, 0x53A6, 0xCFC4, 0x590F, 0xCFC5, 0x5413, 0xCFC6, 0x6380, 0xCFC7, 0x9528, 0xCFC8, 0x5148, 0xCFC9, 0x4ED9, + 0xCFCA, 0x9C9C, 0xCFCB, 0x7EA4, 0xCFCC, 0x54B8, 0xCFCD, 0x8D24, 0xCFCE, 0x8854, 0xCFCF, 0x8237, 0xCFD0, 0x95F2, 0xCFD1, 0x6D8E, + 0xCFD2, 0x5F26, 0xCFD3, 0x5ACC, 0xCFD4, 0x663E, 0xCFD5, 0x9669, 0xCFD6, 0x73B0, 0xCFD7, 0x732E, 0xCFD8, 0x53BF, 0xCFD9, 0x817A, + 0xCFDA, 0x9985, 0xCFDB, 0x7FA1, 0xCFDC, 0x5BAA, 0xCFDD, 0x9677, 0xCFDE, 0x9650, 0xCFDF, 0x7EBF, 0xCFE0, 0x76F8, 0xCFE1, 0x53A2, + 0xCFE2, 0x9576, 0xCFE3, 0x9999, 0xCFE4, 0x7BB1, 0xCFE5, 0x8944, 0xCFE6, 0x6E58, 0xCFE7, 0x4E61, 0xCFE8, 0x7FD4, 0xCFE9, 0x7965, + 0xCFEA, 0x8BE6, 0xCFEB, 0x60F3, 0xCFEC, 0x54CD, 0xCFED, 0x4EAB, 0xCFEE, 0x9879, 0xCFEF, 0x5DF7, 0xCFF0, 0x6A61, 0xCFF1, 0x50CF, + 0xCFF2, 0x5411, 0xCFF3, 0x8C61, 0xCFF4, 0x8427, 0xCFF5, 0x785D, 0xCFF6, 0x9704, 0xCFF7, 0x524A, 0xCFF8, 0x54EE, 0xCFF9, 0x56A3, + 0xCFFA, 0x9500, 0xCFFB, 0x6D88, 0xCFFC, 0x5BB5, 0xCFFD, 0x6DC6, 0xCFFE, 0x6653, 0xD040, 0x8824, 0xD041, 0x8825, 0xD042, 0x8826, + 0xD043, 0x8827, 0xD044, 0x8828, 0xD045, 0x8829, 0xD046, 0x882A, 0xD047, 0x882B, 0xD048, 0x882C, 0xD049, 0x882D, 0xD04A, 0x882E, + 0xD04B, 0x882F, 0xD04C, 0x8830, 0xD04D, 0x8831, 0xD04E, 0x8833, 0xD04F, 0x8834, 0xD050, 0x8835, 0xD051, 0x8836, 0xD052, 0x8837, + 0xD053, 0x8838, 0xD054, 0x883A, 0xD055, 0x883B, 0xD056, 0x883D, 0xD057, 0x883E, 0xD058, 0x883F, 0xD059, 0x8841, 0xD05A, 0x8842, + 0xD05B, 0x8843, 0xD05C, 0x8846, 0xD05D, 0x8847, 0xD05E, 0x8848, 0xD05F, 0x8849, 0xD060, 0x884A, 0xD061, 0x884B, 0xD062, 0x884E, + 0xD063, 0x884F, 0xD064, 0x8850, 0xD065, 0x8851, 0xD066, 0x8852, 0xD067, 0x8853, 0xD068, 0x8855, 0xD069, 0x8856, 0xD06A, 0x8858, + 0xD06B, 0x885A, 0xD06C, 0x885B, 0xD06D, 0x885C, 0xD06E, 0x885D, 0xD06F, 0x885E, 0xD070, 0x885F, 0xD071, 0x8860, 0xD072, 0x8866, + 0xD073, 0x8867, 0xD074, 0x886A, 0xD075, 0x886D, 0xD076, 0x886F, 0xD077, 0x8871, 0xD078, 0x8873, 0xD079, 0x8874, 0xD07A, 0x8875, + 0xD07B, 0x8876, 0xD07C, 0x8878, 0xD07D, 0x8879, 0xD07E, 0x887A, 0xD080, 0x887B, 0xD081, 0x887C, 0xD082, 0x8880, 0xD083, 0x8883, + 0xD084, 0x8886, 0xD085, 0x8887, 0xD086, 0x8889, 0xD087, 0x888A, 0xD088, 0x888C, 0xD089, 0x888E, 0xD08A, 0x888F, 0xD08B, 0x8890, + 0xD08C, 0x8891, 0xD08D, 0x8893, 0xD08E, 0x8894, 0xD08F, 0x8895, 0xD090, 0x8897, 0xD091, 0x8898, 0xD092, 0x8899, 0xD093, 0x889A, + 0xD094, 0x889B, 0xD095, 0x889D, 0xD096, 0x889E, 0xD097, 0x889F, 0xD098, 0x88A0, 0xD099, 0x88A1, 0xD09A, 0x88A3, 0xD09B, 0x88A5, + 0xD09C, 0x88A6, 0xD09D, 0x88A7, 0xD09E, 0x88A8, 0xD09F, 0x88A9, 0xD0A0, 0x88AA, 0xD0A1, 0x5C0F, 0xD0A2, 0x5B5D, 0xD0A3, 0x6821, + 0xD0A4, 0x8096, 0xD0A5, 0x5578, 0xD0A6, 0x7B11, 0xD0A7, 0x6548, 0xD0A8, 0x6954, 0xD0A9, 0x4E9B, 0xD0AA, 0x6B47, 0xD0AB, 0x874E, + 0xD0AC, 0x978B, 0xD0AD, 0x534F, 0xD0AE, 0x631F, 0xD0AF, 0x643A, 0xD0B0, 0x90AA, 0xD0B1, 0x659C, 0xD0B2, 0x80C1, 0xD0B3, 0x8C10, + 0xD0B4, 0x5199, 0xD0B5, 0x68B0, 0xD0B6, 0x5378, 0xD0B7, 0x87F9, 0xD0B8, 0x61C8, 0xD0B9, 0x6CC4, 0xD0BA, 0x6CFB, 0xD0BB, 0x8C22, + 0xD0BC, 0x5C51, 0xD0BD, 0x85AA, 0xD0BE, 0x82AF, 0xD0BF, 0x950C, 0xD0C0, 0x6B23, 0xD0C1, 0x8F9B, 0xD0C2, 0x65B0, 0xD0C3, 0x5FFB, + 0xD0C4, 0x5FC3, 0xD0C5, 0x4FE1, 0xD0C6, 0x8845, 0xD0C7, 0x661F, 0xD0C8, 0x8165, 0xD0C9, 0x7329, 0xD0CA, 0x60FA, 0xD0CB, 0x5174, + 0xD0CC, 0x5211, 0xD0CD, 0x578B, 0xD0CE, 0x5F62, 0xD0CF, 0x90A2, 0xD0D0, 0x884C, 0xD0D1, 0x9192, 0xD0D2, 0x5E78, 0xD0D3, 0x674F, + 0xD0D4, 0x6027, 0xD0D5, 0x59D3, 0xD0D6, 0x5144, 0xD0D7, 0x51F6, 0xD0D8, 0x80F8, 0xD0D9, 0x5308, 0xD0DA, 0x6C79, 0xD0DB, 0x96C4, + 0xD0DC, 0x718A, 0xD0DD, 0x4F11, 0xD0DE, 0x4FEE, 0xD0DF, 0x7F9E, 0xD0E0, 0x673D, 0xD0E1, 0x55C5, 0xD0E2, 0x9508, 0xD0E3, 0x79C0, + 0xD0E4, 0x8896, 0xD0E5, 0x7EE3, 0xD0E6, 0x589F, 0xD0E7, 0x620C, 0xD0E8, 0x9700, 0xD0E9, 0x865A, 0xD0EA, 0x5618, 0xD0EB, 0x987B, + 0xD0EC, 0x5F90, 0xD0ED, 0x8BB8, 0xD0EE, 0x84C4, 0xD0EF, 0x9157, 0xD0F0, 0x53D9, 0xD0F1, 0x65ED, 0xD0F2, 0x5E8F, 0xD0F3, 0x755C, + 0xD0F4, 0x6064, 0xD0F5, 0x7D6E, 0xD0F6, 0x5A7F, 0xD0F7, 0x7EEA, 0xD0F8, 0x7EED, 0xD0F9, 0x8F69, 0xD0FA, 0x55A7, 0xD0FB, 0x5BA3, + 0xD0FC, 0x60AC, 0xD0FD, 0x65CB, 0xD0FE, 0x7384, 0xD140, 0x88AC, 0xD141, 0x88AE, 0xD142, 0x88AF, 0xD143, 0x88B0, 0xD144, 0x88B2, + 0xD145, 0x88B3, 0xD146, 0x88B4, 0xD147, 0x88B5, 0xD148, 0x88B6, 0xD149, 0x88B8, 0xD14A, 0x88B9, 0xD14B, 0x88BA, 0xD14C, 0x88BB, + 0xD14D, 0x88BD, 0xD14E, 0x88BE, 0xD14F, 0x88BF, 0xD150, 0x88C0, 0xD151, 0x88C3, 0xD152, 0x88C4, 0xD153, 0x88C7, 0xD154, 0x88C8, + 0xD155, 0x88CA, 0xD156, 0x88CB, 0xD157, 0x88CC, 0xD158, 0x88CD, 0xD159, 0x88CF, 0xD15A, 0x88D0, 0xD15B, 0x88D1, 0xD15C, 0x88D3, + 0xD15D, 0x88D6, 0xD15E, 0x88D7, 0xD15F, 0x88DA, 0xD160, 0x88DB, 0xD161, 0x88DC, 0xD162, 0x88DD, 0xD163, 0x88DE, 0xD164, 0x88E0, + 0xD165, 0x88E1, 0xD166, 0x88E6, 0xD167, 0x88E7, 0xD168, 0x88E9, 0xD169, 0x88EA, 0xD16A, 0x88EB, 0xD16B, 0x88EC, 0xD16C, 0x88ED, + 0xD16D, 0x88EE, 0xD16E, 0x88EF, 0xD16F, 0x88F2, 0xD170, 0x88F5, 0xD171, 0x88F6, 0xD172, 0x88F7, 0xD173, 0x88FA, 0xD174, 0x88FB, + 0xD175, 0x88FD, 0xD176, 0x88FF, 0xD177, 0x8900, 0xD178, 0x8901, 0xD179, 0x8903, 0xD17A, 0x8904, 0xD17B, 0x8905, 0xD17C, 0x8906, + 0xD17D, 0x8907, 0xD17E, 0x8908, 0xD180, 0x8909, 0xD181, 0x890B, 0xD182, 0x890C, 0xD183, 0x890D, 0xD184, 0x890E, 0xD185, 0x890F, + 0xD186, 0x8911, 0xD187, 0x8914, 0xD188, 0x8915, 0xD189, 0x8916, 0xD18A, 0x8917, 0xD18B, 0x8918, 0xD18C, 0x891C, 0xD18D, 0x891D, + 0xD18E, 0x891E, 0xD18F, 0x891F, 0xD190, 0x8920, 0xD191, 0x8922, 0xD192, 0x8923, 0xD193, 0x8924, 0xD194, 0x8926, 0xD195, 0x8927, + 0xD196, 0x8928, 0xD197, 0x8929, 0xD198, 0x892C, 0xD199, 0x892D, 0xD19A, 0x892E, 0xD19B, 0x892F, 0xD19C, 0x8931, 0xD19D, 0x8932, + 0xD19E, 0x8933, 0xD19F, 0x8935, 0xD1A0, 0x8937, 0xD1A1, 0x9009, 0xD1A2, 0x7663, 0xD1A3, 0x7729, 0xD1A4, 0x7EDA, 0xD1A5, 0x9774, + 0xD1A6, 0x859B, 0xD1A7, 0x5B66, 0xD1A8, 0x7A74, 0xD1A9, 0x96EA, 0xD1AA, 0x8840, 0xD1AB, 0x52CB, 0xD1AC, 0x718F, 0xD1AD, 0x5FAA, + 0xD1AE, 0x65EC, 0xD1AF, 0x8BE2, 0xD1B0, 0x5BFB, 0xD1B1, 0x9A6F, 0xD1B2, 0x5DE1, 0xD1B3, 0x6B89, 0xD1B4, 0x6C5B, 0xD1B5, 0x8BAD, + 0xD1B6, 0x8BAF, 0xD1B7, 0x900A, 0xD1B8, 0x8FC5, 0xD1B9, 0x538B, 0xD1BA, 0x62BC, 0xD1BB, 0x9E26, 0xD1BC, 0x9E2D, 0xD1BD, 0x5440, + 0xD1BE, 0x4E2B, 0xD1BF, 0x82BD, 0xD1C0, 0x7259, 0xD1C1, 0x869C, 0xD1C2, 0x5D16, 0xD1C3, 0x8859, 0xD1C4, 0x6DAF, 0xD1C5, 0x96C5, + 0xD1C6, 0x54D1, 0xD1C7, 0x4E9A, 0xD1C8, 0x8BB6, 0xD1C9, 0x7109, 0xD1CA, 0x54BD, 0xD1CB, 0x9609, 0xD1CC, 0x70DF, 0xD1CD, 0x6DF9, + 0xD1CE, 0x76D0, 0xD1CF, 0x4E25, 0xD1D0, 0x7814, 0xD1D1, 0x8712, 0xD1D2, 0x5CA9, 0xD1D3, 0x5EF6, 0xD1D4, 0x8A00, 0xD1D5, 0x989C, + 0xD1D6, 0x960E, 0xD1D7, 0x708E, 0xD1D8, 0x6CBF, 0xD1D9, 0x5944, 0xD1DA, 0x63A9, 0xD1DB, 0x773C, 0xD1DC, 0x884D, 0xD1DD, 0x6F14, + 0xD1DE, 0x8273, 0xD1DF, 0x5830, 0xD1E0, 0x71D5, 0xD1E1, 0x538C, 0xD1E2, 0x781A, 0xD1E3, 0x96C1, 0xD1E4, 0x5501, 0xD1E5, 0x5F66, + 0xD1E6, 0x7130, 0xD1E7, 0x5BB4, 0xD1E8, 0x8C1A, 0xD1E9, 0x9A8C, 0xD1EA, 0x6B83, 0xD1EB, 0x592E, 0xD1EC, 0x9E2F, 0xD1ED, 0x79E7, + 0xD1EE, 0x6768, 0xD1EF, 0x626C, 0xD1F0, 0x4F6F, 0xD1F1, 0x75A1, 0xD1F2, 0x7F8A, 0xD1F3, 0x6D0B, 0xD1F4, 0x9633, 0xD1F5, 0x6C27, + 0xD1F6, 0x4EF0, 0xD1F7, 0x75D2, 0xD1F8, 0x517B, 0xD1F9, 0x6837, 0xD1FA, 0x6F3E, 0xD1FB, 0x9080, 0xD1FC, 0x8170, 0xD1FD, 0x5996, + 0xD1FE, 0x7476, 0xD240, 0x8938, 0xD241, 0x8939, 0xD242, 0x893A, 0xD243, 0x893B, 0xD244, 0x893C, 0xD245, 0x893D, 0xD246, 0x893E, + 0xD247, 0x893F, 0xD248, 0x8940, 0xD249, 0x8942, 0xD24A, 0x8943, 0xD24B, 0x8945, 0xD24C, 0x8946, 0xD24D, 0x8947, 0xD24E, 0x8948, + 0xD24F, 0x8949, 0xD250, 0x894A, 0xD251, 0x894B, 0xD252, 0x894C, 0xD253, 0x894D, 0xD254, 0x894E, 0xD255, 0x894F, 0xD256, 0x8950, + 0xD257, 0x8951, 0xD258, 0x8952, 0xD259, 0x8953, 0xD25A, 0x8954, 0xD25B, 0x8955, 0xD25C, 0x8956, 0xD25D, 0x8957, 0xD25E, 0x8958, + 0xD25F, 0x8959, 0xD260, 0x895A, 0xD261, 0x895B, 0xD262, 0x895C, 0xD263, 0x895D, 0xD264, 0x8960, 0xD265, 0x8961, 0xD266, 0x8962, + 0xD267, 0x8963, 0xD268, 0x8964, 0xD269, 0x8965, 0xD26A, 0x8967, 0xD26B, 0x8968, 0xD26C, 0x8969, 0xD26D, 0x896A, 0xD26E, 0x896B, + 0xD26F, 0x896C, 0xD270, 0x896D, 0xD271, 0x896E, 0xD272, 0x896F, 0xD273, 0x8970, 0xD274, 0x8971, 0xD275, 0x8972, 0xD276, 0x8973, + 0xD277, 0x8974, 0xD278, 0x8975, 0xD279, 0x8976, 0xD27A, 0x8977, 0xD27B, 0x8978, 0xD27C, 0x8979, 0xD27D, 0x897A, 0xD27E, 0x897C, + 0xD280, 0x897D, 0xD281, 0x897E, 0xD282, 0x8980, 0xD283, 0x8982, 0xD284, 0x8984, 0xD285, 0x8985, 0xD286, 0x8987, 0xD287, 0x8988, + 0xD288, 0x8989, 0xD289, 0x898A, 0xD28A, 0x898B, 0xD28B, 0x898C, 0xD28C, 0x898D, 0xD28D, 0x898E, 0xD28E, 0x898F, 0xD28F, 0x8990, + 0xD290, 0x8991, 0xD291, 0x8992, 0xD292, 0x8993, 0xD293, 0x8994, 0xD294, 0x8995, 0xD295, 0x8996, 0xD296, 0x8997, 0xD297, 0x8998, + 0xD298, 0x8999, 0xD299, 0x899A, 0xD29A, 0x899B, 0xD29B, 0x899C, 0xD29C, 0x899D, 0xD29D, 0x899E, 0xD29E, 0x899F, 0xD29F, 0x89A0, + 0xD2A0, 0x89A1, 0xD2A1, 0x6447, 0xD2A2, 0x5C27, 0xD2A3, 0x9065, 0xD2A4, 0x7A91, 0xD2A5, 0x8C23, 0xD2A6, 0x59DA, 0xD2A7, 0x54AC, + 0xD2A8, 0x8200, 0xD2A9, 0x836F, 0xD2AA, 0x8981, 0xD2AB, 0x8000, 0xD2AC, 0x6930, 0xD2AD, 0x564E, 0xD2AE, 0x8036, 0xD2AF, 0x7237, + 0xD2B0, 0x91CE, 0xD2B1, 0x51B6, 0xD2B2, 0x4E5F, 0xD2B3, 0x9875, 0xD2B4, 0x6396, 0xD2B5, 0x4E1A, 0xD2B6, 0x53F6, 0xD2B7, 0x66F3, + 0xD2B8, 0x814B, 0xD2B9, 0x591C, 0xD2BA, 0x6DB2, 0xD2BB, 0x4E00, 0xD2BC, 0x58F9, 0xD2BD, 0x533B, 0xD2BE, 0x63D6, 0xD2BF, 0x94F1, + 0xD2C0, 0x4F9D, 0xD2C1, 0x4F0A, 0xD2C2, 0x8863, 0xD2C3, 0x9890, 0xD2C4, 0x5937, 0xD2C5, 0x9057, 0xD2C6, 0x79FB, 0xD2C7, 0x4EEA, + 0xD2C8, 0x80F0, 0xD2C9, 0x7591, 0xD2CA, 0x6C82, 0xD2CB, 0x5B9C, 0xD2CC, 0x59E8, 0xD2CD, 0x5F5D, 0xD2CE, 0x6905, 0xD2CF, 0x8681, + 0xD2D0, 0x501A, 0xD2D1, 0x5DF2, 0xD2D2, 0x4E59, 0xD2D3, 0x77E3, 0xD2D4, 0x4EE5, 0xD2D5, 0x827A, 0xD2D6, 0x6291, 0xD2D7, 0x6613, + 0xD2D8, 0x9091, 0xD2D9, 0x5C79, 0xD2DA, 0x4EBF, 0xD2DB, 0x5F79, 0xD2DC, 0x81C6, 0xD2DD, 0x9038, 0xD2DE, 0x8084, 0xD2DF, 0x75AB, + 0xD2E0, 0x4EA6, 0xD2E1, 0x88D4, 0xD2E2, 0x610F, 0xD2E3, 0x6BC5, 0xD2E4, 0x5FC6, 0xD2E5, 0x4E49, 0xD2E6, 0x76CA, 0xD2E7, 0x6EA2, + 0xD2E8, 0x8BE3, 0xD2E9, 0x8BAE, 0xD2EA, 0x8C0A, 0xD2EB, 0x8BD1, 0xD2EC, 0x5F02, 0xD2ED, 0x7FFC, 0xD2EE, 0x7FCC, 0xD2EF, 0x7ECE, + 0xD2F0, 0x8335, 0xD2F1, 0x836B, 0xD2F2, 0x56E0, 0xD2F3, 0x6BB7, 0xD2F4, 0x97F3, 0xD2F5, 0x9634, 0xD2F6, 0x59FB, 0xD2F7, 0x541F, + 0xD2F8, 0x94F6, 0xD2F9, 0x6DEB, 0xD2FA, 0x5BC5, 0xD2FB, 0x996E, 0xD2FC, 0x5C39, 0xD2FD, 0x5F15, 0xD2FE, 0x9690, 0xD340, 0x89A2, + 0xD341, 0x89A3, 0xD342, 0x89A4, 0xD343, 0x89A5, 0xD344, 0x89A6, 0xD345, 0x89A7, 0xD346, 0x89A8, 0xD347, 0x89A9, 0xD348, 0x89AA, + 0xD349, 0x89AB, 0xD34A, 0x89AC, 0xD34B, 0x89AD, 0xD34C, 0x89AE, 0xD34D, 0x89AF, 0xD34E, 0x89B0, 0xD34F, 0x89B1, 0xD350, 0x89B2, + 0xD351, 0x89B3, 0xD352, 0x89B4, 0xD353, 0x89B5, 0xD354, 0x89B6, 0xD355, 0x89B7, 0xD356, 0x89B8, 0xD357, 0x89B9, 0xD358, 0x89BA, + 0xD359, 0x89BB, 0xD35A, 0x89BC, 0xD35B, 0x89BD, 0xD35C, 0x89BE, 0xD35D, 0x89BF, 0xD35E, 0x89C0, 0xD35F, 0x89C3, 0xD360, 0x89CD, + 0xD361, 0x89D3, 0xD362, 0x89D4, 0xD363, 0x89D5, 0xD364, 0x89D7, 0xD365, 0x89D8, 0xD366, 0x89D9, 0xD367, 0x89DB, 0xD368, 0x89DD, + 0xD369, 0x89DF, 0xD36A, 0x89E0, 0xD36B, 0x89E1, 0xD36C, 0x89E2, 0xD36D, 0x89E4, 0xD36E, 0x89E7, 0xD36F, 0x89E8, 0xD370, 0x89E9, + 0xD371, 0x89EA, 0xD372, 0x89EC, 0xD373, 0x89ED, 0xD374, 0x89EE, 0xD375, 0x89F0, 0xD376, 0x89F1, 0xD377, 0x89F2, 0xD378, 0x89F4, + 0xD379, 0x89F5, 0xD37A, 0x89F6, 0xD37B, 0x89F7, 0xD37C, 0x89F8, 0xD37D, 0x89F9, 0xD37E, 0x89FA, 0xD380, 0x89FB, 0xD381, 0x89FC, + 0xD382, 0x89FD, 0xD383, 0x89FE, 0xD384, 0x89FF, 0xD385, 0x8A01, 0xD386, 0x8A02, 0xD387, 0x8A03, 0xD388, 0x8A04, 0xD389, 0x8A05, + 0xD38A, 0x8A06, 0xD38B, 0x8A08, 0xD38C, 0x8A09, 0xD38D, 0x8A0A, 0xD38E, 0x8A0B, 0xD38F, 0x8A0C, 0xD390, 0x8A0D, 0xD391, 0x8A0E, + 0xD392, 0x8A0F, 0xD393, 0x8A10, 0xD394, 0x8A11, 0xD395, 0x8A12, 0xD396, 0x8A13, 0xD397, 0x8A14, 0xD398, 0x8A15, 0xD399, 0x8A16, + 0xD39A, 0x8A17, 0xD39B, 0x8A18, 0xD39C, 0x8A19, 0xD39D, 0x8A1A, 0xD39E, 0x8A1B, 0xD39F, 0x8A1C, 0xD3A0, 0x8A1D, 0xD3A1, 0x5370, + 0xD3A2, 0x82F1, 0xD3A3, 0x6A31, 0xD3A4, 0x5A74, 0xD3A5, 0x9E70, 0xD3A6, 0x5E94, 0xD3A7, 0x7F28, 0xD3A8, 0x83B9, 0xD3A9, 0x8424, + 0xD3AA, 0x8425, 0xD3AB, 0x8367, 0xD3AC, 0x8747, 0xD3AD, 0x8FCE, 0xD3AE, 0x8D62, 0xD3AF, 0x76C8, 0xD3B0, 0x5F71, 0xD3B1, 0x9896, + 0xD3B2, 0x786C, 0xD3B3, 0x6620, 0xD3B4, 0x54DF, 0xD3B5, 0x62E5, 0xD3B6, 0x4F63, 0xD3B7, 0x81C3, 0xD3B8, 0x75C8, 0xD3B9, 0x5EB8, + 0xD3BA, 0x96CD, 0xD3BB, 0x8E0A, 0xD3BC, 0x86F9, 0xD3BD, 0x548F, 0xD3BE, 0x6CF3, 0xD3BF, 0x6D8C, 0xD3C0, 0x6C38, 0xD3C1, 0x607F, + 0xD3C2, 0x52C7, 0xD3C3, 0x7528, 0xD3C4, 0x5E7D, 0xD3C5, 0x4F18, 0xD3C6, 0x60A0, 0xD3C7, 0x5FE7, 0xD3C8, 0x5C24, 0xD3C9, 0x7531, + 0xD3CA, 0x90AE, 0xD3CB, 0x94C0, 0xD3CC, 0x72B9, 0xD3CD, 0x6CB9, 0xD3CE, 0x6E38, 0xD3CF, 0x9149, 0xD3D0, 0x6709, 0xD3D1, 0x53CB, + 0xD3D2, 0x53F3, 0xD3D3, 0x4F51, 0xD3D4, 0x91C9, 0xD3D5, 0x8BF1, 0xD3D6, 0x53C8, 0xD3D7, 0x5E7C, 0xD3D8, 0x8FC2, 0xD3D9, 0x6DE4, + 0xD3DA, 0x4E8E, 0xD3DB, 0x76C2, 0xD3DC, 0x6986, 0xD3DD, 0x865E, 0xD3DE, 0x611A, 0xD3DF, 0x8206, 0xD3E0, 0x4F59, 0xD3E1, 0x4FDE, + 0xD3E2, 0x903E, 0xD3E3, 0x9C7C, 0xD3E4, 0x6109, 0xD3E5, 0x6E1D, 0xD3E6, 0x6E14, 0xD3E7, 0x9685, 0xD3E8, 0x4E88, 0xD3E9, 0x5A31, + 0xD3EA, 0x96E8, 0xD3EB, 0x4E0E, 0xD3EC, 0x5C7F, 0xD3ED, 0x79B9, 0xD3EE, 0x5B87, 0xD3EF, 0x8BED, 0xD3F0, 0x7FBD, 0xD3F1, 0x7389, + 0xD3F2, 0x57DF, 0xD3F3, 0x828B, 0xD3F4, 0x90C1, 0xD3F5, 0x5401, 0xD3F6, 0x9047, 0xD3F7, 0x55BB, 0xD3F8, 0x5CEA, 0xD3F9, 0x5FA1, + 0xD3FA, 0x6108, 0xD3FB, 0x6B32, 0xD3FC, 0x72F1, 0xD3FD, 0x80B2, 0xD3FE, 0x8A89, 0xD440, 0x8A1E, 0xD441, 0x8A1F, 0xD442, 0x8A20, + 0xD443, 0x8A21, 0xD444, 0x8A22, 0xD445, 0x8A23, 0xD446, 0x8A24, 0xD447, 0x8A25, 0xD448, 0x8A26, 0xD449, 0x8A27, 0xD44A, 0x8A28, + 0xD44B, 0x8A29, 0xD44C, 0x8A2A, 0xD44D, 0x8A2B, 0xD44E, 0x8A2C, 0xD44F, 0x8A2D, 0xD450, 0x8A2E, 0xD451, 0x8A2F, 0xD452, 0x8A30, + 0xD453, 0x8A31, 0xD454, 0x8A32, 0xD455, 0x8A33, 0xD456, 0x8A34, 0xD457, 0x8A35, 0xD458, 0x8A36, 0xD459, 0x8A37, 0xD45A, 0x8A38, + 0xD45B, 0x8A39, 0xD45C, 0x8A3A, 0xD45D, 0x8A3B, 0xD45E, 0x8A3C, 0xD45F, 0x8A3D, 0xD460, 0x8A3F, 0xD461, 0x8A40, 0xD462, 0x8A41, + 0xD463, 0x8A42, 0xD464, 0x8A43, 0xD465, 0x8A44, 0xD466, 0x8A45, 0xD467, 0x8A46, 0xD468, 0x8A47, 0xD469, 0x8A49, 0xD46A, 0x8A4A, + 0xD46B, 0x8A4B, 0xD46C, 0x8A4C, 0xD46D, 0x8A4D, 0xD46E, 0x8A4E, 0xD46F, 0x8A4F, 0xD470, 0x8A50, 0xD471, 0x8A51, 0xD472, 0x8A52, + 0xD473, 0x8A53, 0xD474, 0x8A54, 0xD475, 0x8A55, 0xD476, 0x8A56, 0xD477, 0x8A57, 0xD478, 0x8A58, 0xD479, 0x8A59, 0xD47A, 0x8A5A, + 0xD47B, 0x8A5B, 0xD47C, 0x8A5C, 0xD47D, 0x8A5D, 0xD47E, 0x8A5E, 0xD480, 0x8A5F, 0xD481, 0x8A60, 0xD482, 0x8A61, 0xD483, 0x8A62, + 0xD484, 0x8A63, 0xD485, 0x8A64, 0xD486, 0x8A65, 0xD487, 0x8A66, 0xD488, 0x8A67, 0xD489, 0x8A68, 0xD48A, 0x8A69, 0xD48B, 0x8A6A, + 0xD48C, 0x8A6B, 0xD48D, 0x8A6C, 0xD48E, 0x8A6D, 0xD48F, 0x8A6E, 0xD490, 0x8A6F, 0xD491, 0x8A70, 0xD492, 0x8A71, 0xD493, 0x8A72, + 0xD494, 0x8A73, 0xD495, 0x8A74, 0xD496, 0x8A75, 0xD497, 0x8A76, 0xD498, 0x8A77, 0xD499, 0x8A78, 0xD49A, 0x8A7A, 0xD49B, 0x8A7B, + 0xD49C, 0x8A7C, 0xD49D, 0x8A7D, 0xD49E, 0x8A7E, 0xD49F, 0x8A7F, 0xD4A0, 0x8A80, 0xD4A1, 0x6D74, 0xD4A2, 0x5BD3, 0xD4A3, 0x88D5, + 0xD4A4, 0x9884, 0xD4A5, 0x8C6B, 0xD4A6, 0x9A6D, 0xD4A7, 0x9E33, 0xD4A8, 0x6E0A, 0xD4A9, 0x51A4, 0xD4AA, 0x5143, 0xD4AB, 0x57A3, + 0xD4AC, 0x8881, 0xD4AD, 0x539F, 0xD4AE, 0x63F4, 0xD4AF, 0x8F95, 0xD4B0, 0x56ED, 0xD4B1, 0x5458, 0xD4B2, 0x5706, 0xD4B3, 0x733F, + 0xD4B4, 0x6E90, 0xD4B5, 0x7F18, 0xD4B6, 0x8FDC, 0xD4B7, 0x82D1, 0xD4B8, 0x613F, 0xD4B9, 0x6028, 0xD4BA, 0x9662, 0xD4BB, 0x66F0, + 0xD4BC, 0x7EA6, 0xD4BD, 0x8D8A, 0xD4BE, 0x8DC3, 0xD4BF, 0x94A5, 0xD4C0, 0x5CB3, 0xD4C1, 0x7CA4, 0xD4C2, 0x6708, 0xD4C3, 0x60A6, + 0xD4C4, 0x9605, 0xD4C5, 0x8018, 0xD4C6, 0x4E91, 0xD4C7, 0x90E7, 0xD4C8, 0x5300, 0xD4C9, 0x9668, 0xD4CA, 0x5141, 0xD4CB, 0x8FD0, + 0xD4CC, 0x8574, 0xD4CD, 0x915D, 0xD4CE, 0x6655, 0xD4CF, 0x97F5, 0xD4D0, 0x5B55, 0xD4D1, 0x531D, 0xD4D2, 0x7838, 0xD4D3, 0x6742, + 0xD4D4, 0x683D, 0xD4D5, 0x54C9, 0xD4D6, 0x707E, 0xD4D7, 0x5BB0, 0xD4D8, 0x8F7D, 0xD4D9, 0x518D, 0xD4DA, 0x5728, 0xD4DB, 0x54B1, + 0xD4DC, 0x6512, 0xD4DD, 0x6682, 0xD4DE, 0x8D5E, 0xD4DF, 0x8D43, 0xD4E0, 0x810F, 0xD4E1, 0x846C, 0xD4E2, 0x906D, 0xD4E3, 0x7CDF, + 0xD4E4, 0x51FF, 0xD4E5, 0x85FB, 0xD4E6, 0x67A3, 0xD4E7, 0x65E9, 0xD4E8, 0x6FA1, 0xD4E9, 0x86A4, 0xD4EA, 0x8E81, 0xD4EB, 0x566A, + 0xD4EC, 0x9020, 0xD4ED, 0x7682, 0xD4EE, 0x7076, 0xD4EF, 0x71E5, 0xD4F0, 0x8D23, 0xD4F1, 0x62E9, 0xD4F2, 0x5219, 0xD4F3, 0x6CFD, + 0xD4F4, 0x8D3C, 0xD4F5, 0x600E, 0xD4F6, 0x589E, 0xD4F7, 0x618E, 0xD4F8, 0x66FE, 0xD4F9, 0x8D60, 0xD4FA, 0x624E, 0xD4FB, 0x55B3, + 0xD4FC, 0x6E23, 0xD4FD, 0x672D, 0xD4FE, 0x8F67, 0xD540, 0x8A81, 0xD541, 0x8A82, 0xD542, 0x8A83, 0xD543, 0x8A84, 0xD544, 0x8A85, + 0xD545, 0x8A86, 0xD546, 0x8A87, 0xD547, 0x8A88, 0xD548, 0x8A8B, 0xD549, 0x8A8C, 0xD54A, 0x8A8D, 0xD54B, 0x8A8E, 0xD54C, 0x8A8F, + 0xD54D, 0x8A90, 0xD54E, 0x8A91, 0xD54F, 0x8A92, 0xD550, 0x8A94, 0xD551, 0x8A95, 0xD552, 0x8A96, 0xD553, 0x8A97, 0xD554, 0x8A98, + 0xD555, 0x8A99, 0xD556, 0x8A9A, 0xD557, 0x8A9B, 0xD558, 0x8A9C, 0xD559, 0x8A9D, 0xD55A, 0x8A9E, 0xD55B, 0x8A9F, 0xD55C, 0x8AA0, + 0xD55D, 0x8AA1, 0xD55E, 0x8AA2, 0xD55F, 0x8AA3, 0xD560, 0x8AA4, 0xD561, 0x8AA5, 0xD562, 0x8AA6, 0xD563, 0x8AA7, 0xD564, 0x8AA8, + 0xD565, 0x8AA9, 0xD566, 0x8AAA, 0xD567, 0x8AAB, 0xD568, 0x8AAC, 0xD569, 0x8AAD, 0xD56A, 0x8AAE, 0xD56B, 0x8AAF, 0xD56C, 0x8AB0, + 0xD56D, 0x8AB1, 0xD56E, 0x8AB2, 0xD56F, 0x8AB3, 0xD570, 0x8AB4, 0xD571, 0x8AB5, 0xD572, 0x8AB6, 0xD573, 0x8AB7, 0xD574, 0x8AB8, + 0xD575, 0x8AB9, 0xD576, 0x8ABA, 0xD577, 0x8ABB, 0xD578, 0x8ABC, 0xD579, 0x8ABD, 0xD57A, 0x8ABE, 0xD57B, 0x8ABF, 0xD57C, 0x8AC0, + 0xD57D, 0x8AC1, 0xD57E, 0x8AC2, 0xD580, 0x8AC3, 0xD581, 0x8AC4, 0xD582, 0x8AC5, 0xD583, 0x8AC6, 0xD584, 0x8AC7, 0xD585, 0x8AC8, + 0xD586, 0x8AC9, 0xD587, 0x8ACA, 0xD588, 0x8ACB, 0xD589, 0x8ACC, 0xD58A, 0x8ACD, 0xD58B, 0x8ACE, 0xD58C, 0x8ACF, 0xD58D, 0x8AD0, + 0xD58E, 0x8AD1, 0xD58F, 0x8AD2, 0xD590, 0x8AD3, 0xD591, 0x8AD4, 0xD592, 0x8AD5, 0xD593, 0x8AD6, 0xD594, 0x8AD7, 0xD595, 0x8AD8, + 0xD596, 0x8AD9, 0xD597, 0x8ADA, 0xD598, 0x8ADB, 0xD599, 0x8ADC, 0xD59A, 0x8ADD, 0xD59B, 0x8ADE, 0xD59C, 0x8ADF, 0xD59D, 0x8AE0, + 0xD59E, 0x8AE1, 0xD59F, 0x8AE2, 0xD5A0, 0x8AE3, 0xD5A1, 0x94E1, 0xD5A2, 0x95F8, 0xD5A3, 0x7728, 0xD5A4, 0x6805, 0xD5A5, 0x69A8, + 0xD5A6, 0x548B, 0xD5A7, 0x4E4D, 0xD5A8, 0x70B8, 0xD5A9, 0x8BC8, 0xD5AA, 0x6458, 0xD5AB, 0x658B, 0xD5AC, 0x5B85, 0xD5AD, 0x7A84, + 0xD5AE, 0x503A, 0xD5AF, 0x5BE8, 0xD5B0, 0x77BB, 0xD5B1, 0x6BE1, 0xD5B2, 0x8A79, 0xD5B3, 0x7C98, 0xD5B4, 0x6CBE, 0xD5B5, 0x76CF, + 0xD5B6, 0x65A9, 0xD5B7, 0x8F97, 0xD5B8, 0x5D2D, 0xD5B9, 0x5C55, 0xD5BA, 0x8638, 0xD5BB, 0x6808, 0xD5BC, 0x5360, 0xD5BD, 0x6218, + 0xD5BE, 0x7AD9, 0xD5BF, 0x6E5B, 0xD5C0, 0x7EFD, 0xD5C1, 0x6A1F, 0xD5C2, 0x7AE0, 0xD5C3, 0x5F70, 0xD5C4, 0x6F33, 0xD5C5, 0x5F20, + 0xD5C6, 0x638C, 0xD5C7, 0x6DA8, 0xD5C8, 0x6756, 0xD5C9, 0x4E08, 0xD5CA, 0x5E10, 0xD5CB, 0x8D26, 0xD5CC, 0x4ED7, 0xD5CD, 0x80C0, + 0xD5CE, 0x7634, 0xD5CF, 0x969C, 0xD5D0, 0x62DB, 0xD5D1, 0x662D, 0xD5D2, 0x627E, 0xD5D3, 0x6CBC, 0xD5D4, 0x8D75, 0xD5D5, 0x7167, + 0xD5D6, 0x7F69, 0xD5D7, 0x5146, 0xD5D8, 0x8087, 0xD5D9, 0x53EC, 0xD5DA, 0x906E, 0xD5DB, 0x6298, 0xD5DC, 0x54F2, 0xD5DD, 0x86F0, + 0xD5DE, 0x8F99, 0xD5DF, 0x8005, 0xD5E0, 0x9517, 0xD5E1, 0x8517, 0xD5E2, 0x8FD9, 0xD5E3, 0x6D59, 0xD5E4, 0x73CD, 0xD5E5, 0x659F, + 0xD5E6, 0x771F, 0xD5E7, 0x7504, 0xD5E8, 0x7827, 0xD5E9, 0x81FB, 0xD5EA, 0x8D1E, 0xD5EB, 0x9488, 0xD5EC, 0x4FA6, 0xD5ED, 0x6795, + 0xD5EE, 0x75B9, 0xD5EF, 0x8BCA, 0xD5F0, 0x9707, 0xD5F1, 0x632F, 0xD5F2, 0x9547, 0xD5F3, 0x9635, 0xD5F4, 0x84B8, 0xD5F5, 0x6323, + 0xD5F6, 0x7741, 0xD5F7, 0x5F81, 0xD5F8, 0x72F0, 0xD5F9, 0x4E89, 0xD5FA, 0x6014, 0xD5FB, 0x6574, 0xD5FC, 0x62EF, 0xD5FD, 0x6B63, + 0xD5FE, 0x653F, 0xD640, 0x8AE4, 0xD641, 0x8AE5, 0xD642, 0x8AE6, 0xD643, 0x8AE7, 0xD644, 0x8AE8, 0xD645, 0x8AE9, 0xD646, 0x8AEA, + 0xD647, 0x8AEB, 0xD648, 0x8AEC, 0xD649, 0x8AED, 0xD64A, 0x8AEE, 0xD64B, 0x8AEF, 0xD64C, 0x8AF0, 0xD64D, 0x8AF1, 0xD64E, 0x8AF2, + 0xD64F, 0x8AF3, 0xD650, 0x8AF4, 0xD651, 0x8AF5, 0xD652, 0x8AF6, 0xD653, 0x8AF7, 0xD654, 0x8AF8, 0xD655, 0x8AF9, 0xD656, 0x8AFA, + 0xD657, 0x8AFB, 0xD658, 0x8AFC, 0xD659, 0x8AFD, 0xD65A, 0x8AFE, 0xD65B, 0x8AFF, 0xD65C, 0x8B00, 0xD65D, 0x8B01, 0xD65E, 0x8B02, + 0xD65F, 0x8B03, 0xD660, 0x8B04, 0xD661, 0x8B05, 0xD662, 0x8B06, 0xD663, 0x8B08, 0xD664, 0x8B09, 0xD665, 0x8B0A, 0xD666, 0x8B0B, + 0xD667, 0x8B0C, 0xD668, 0x8B0D, 0xD669, 0x8B0E, 0xD66A, 0x8B0F, 0xD66B, 0x8B10, 0xD66C, 0x8B11, 0xD66D, 0x8B12, 0xD66E, 0x8B13, + 0xD66F, 0x8B14, 0xD670, 0x8B15, 0xD671, 0x8B16, 0xD672, 0x8B17, 0xD673, 0x8B18, 0xD674, 0x8B19, 0xD675, 0x8B1A, 0xD676, 0x8B1B, + 0xD677, 0x8B1C, 0xD678, 0x8B1D, 0xD679, 0x8B1E, 0xD67A, 0x8B1F, 0xD67B, 0x8B20, 0xD67C, 0x8B21, 0xD67D, 0x8B22, 0xD67E, 0x8B23, + 0xD680, 0x8B24, 0xD681, 0x8B25, 0xD682, 0x8B27, 0xD683, 0x8B28, 0xD684, 0x8B29, 0xD685, 0x8B2A, 0xD686, 0x8B2B, 0xD687, 0x8B2C, + 0xD688, 0x8B2D, 0xD689, 0x8B2E, 0xD68A, 0x8B2F, 0xD68B, 0x8B30, 0xD68C, 0x8B31, 0xD68D, 0x8B32, 0xD68E, 0x8B33, 0xD68F, 0x8B34, + 0xD690, 0x8B35, 0xD691, 0x8B36, 0xD692, 0x8B37, 0xD693, 0x8B38, 0xD694, 0x8B39, 0xD695, 0x8B3A, 0xD696, 0x8B3B, 0xD697, 0x8B3C, + 0xD698, 0x8B3D, 0xD699, 0x8B3E, 0xD69A, 0x8B3F, 0xD69B, 0x8B40, 0xD69C, 0x8B41, 0xD69D, 0x8B42, 0xD69E, 0x8B43, 0xD69F, 0x8B44, + 0xD6A0, 0x8B45, 0xD6A1, 0x5E27, 0xD6A2, 0x75C7, 0xD6A3, 0x90D1, 0xD6A4, 0x8BC1, 0xD6A5, 0x829D, 0xD6A6, 0x679D, 0xD6A7, 0x652F, + 0xD6A8, 0x5431, 0xD6A9, 0x8718, 0xD6AA, 0x77E5, 0xD6AB, 0x80A2, 0xD6AC, 0x8102, 0xD6AD, 0x6C41, 0xD6AE, 0x4E4B, 0xD6AF, 0x7EC7, + 0xD6B0, 0x804C, 0xD6B1, 0x76F4, 0xD6B2, 0x690D, 0xD6B3, 0x6B96, 0xD6B4, 0x6267, 0xD6B5, 0x503C, 0xD6B6, 0x4F84, 0xD6B7, 0x5740, + 0xD6B8, 0x6307, 0xD6B9, 0x6B62, 0xD6BA, 0x8DBE, 0xD6BB, 0x53EA, 0xD6BC, 0x65E8, 0xD6BD, 0x7EB8, 0xD6BE, 0x5FD7, 0xD6BF, 0x631A, + 0xD6C0, 0x63B7, 0xD6C1, 0x81F3, 0xD6C2, 0x81F4, 0xD6C3, 0x7F6E, 0xD6C4, 0x5E1C, 0xD6C5, 0x5CD9, 0xD6C6, 0x5236, 0xD6C7, 0x667A, + 0xD6C8, 0x79E9, 0xD6C9, 0x7A1A, 0xD6CA, 0x8D28, 0xD6CB, 0x7099, 0xD6CC, 0x75D4, 0xD6CD, 0x6EDE, 0xD6CE, 0x6CBB, 0xD6CF, 0x7A92, + 0xD6D0, 0x4E2D, 0xD6D1, 0x76C5, 0xD6D2, 0x5FE0, 0xD6D3, 0x949F, 0xD6D4, 0x8877, 0xD6D5, 0x7EC8, 0xD6D6, 0x79CD, 0xD6D7, 0x80BF, + 0xD6D8, 0x91CD, 0xD6D9, 0x4EF2, 0xD6DA, 0x4F17, 0xD6DB, 0x821F, 0xD6DC, 0x5468, 0xD6DD, 0x5DDE, 0xD6DE, 0x6D32, 0xD6DF, 0x8BCC, + 0xD6E0, 0x7CA5, 0xD6E1, 0x8F74, 0xD6E2, 0x8098, 0xD6E3, 0x5E1A, 0xD6E4, 0x5492, 0xD6E5, 0x76B1, 0xD6E6, 0x5B99, 0xD6E7, 0x663C, + 0xD6E8, 0x9AA4, 0xD6E9, 0x73E0, 0xD6EA, 0x682A, 0xD6EB, 0x86DB, 0xD6EC, 0x6731, 0xD6ED, 0x732A, 0xD6EE, 0x8BF8, 0xD6EF, 0x8BDB, + 0xD6F0, 0x9010, 0xD6F1, 0x7AF9, 0xD6F2, 0x70DB, 0xD6F3, 0x716E, 0xD6F4, 0x62C4, 0xD6F5, 0x77A9, 0xD6F6, 0x5631, 0xD6F7, 0x4E3B, + 0xD6F8, 0x8457, 0xD6F9, 0x67F1, 0xD6FA, 0x52A9, 0xD6FB, 0x86C0, 0xD6FC, 0x8D2E, 0xD6FD, 0x94F8, 0xD6FE, 0x7B51, 0xD740, 0x8B46, + 0xD741, 0x8B47, 0xD742, 0x8B48, 0xD743, 0x8B49, 0xD744, 0x8B4A, 0xD745, 0x8B4B, 0xD746, 0x8B4C, 0xD747, 0x8B4D, 0xD748, 0x8B4E, + 0xD749, 0x8B4F, 0xD74A, 0x8B50, 0xD74B, 0x8B51, 0xD74C, 0x8B52, 0xD74D, 0x8B53, 0xD74E, 0x8B54, 0xD74F, 0x8B55, 0xD750, 0x8B56, + 0xD751, 0x8B57, 0xD752, 0x8B58, 0xD753, 0x8B59, 0xD754, 0x8B5A, 0xD755, 0x8B5B, 0xD756, 0x8B5C, 0xD757, 0x8B5D, 0xD758, 0x8B5E, + 0xD759, 0x8B5F, 0xD75A, 0x8B60, 0xD75B, 0x8B61, 0xD75C, 0x8B62, 0xD75D, 0x8B63, 0xD75E, 0x8B64, 0xD75F, 0x8B65, 0xD760, 0x8B67, + 0xD761, 0x8B68, 0xD762, 0x8B69, 0xD763, 0x8B6A, 0xD764, 0x8B6B, 0xD765, 0x8B6D, 0xD766, 0x8B6E, 0xD767, 0x8B6F, 0xD768, 0x8B70, + 0xD769, 0x8B71, 0xD76A, 0x8B72, 0xD76B, 0x8B73, 0xD76C, 0x8B74, 0xD76D, 0x8B75, 0xD76E, 0x8B76, 0xD76F, 0x8B77, 0xD770, 0x8B78, + 0xD771, 0x8B79, 0xD772, 0x8B7A, 0xD773, 0x8B7B, 0xD774, 0x8B7C, 0xD775, 0x8B7D, 0xD776, 0x8B7E, 0xD777, 0x8B7F, 0xD778, 0x8B80, + 0xD779, 0x8B81, 0xD77A, 0x8B82, 0xD77B, 0x8B83, 0xD77C, 0x8B84, 0xD77D, 0x8B85, 0xD77E, 0x8B86, 0xD780, 0x8B87, 0xD781, 0x8B88, + 0xD782, 0x8B89, 0xD783, 0x8B8A, 0xD784, 0x8B8B, 0xD785, 0x8B8C, 0xD786, 0x8B8D, 0xD787, 0x8B8E, 0xD788, 0x8B8F, 0xD789, 0x8B90, + 0xD78A, 0x8B91, 0xD78B, 0x8B92, 0xD78C, 0x8B93, 0xD78D, 0x8B94, 0xD78E, 0x8B95, 0xD78F, 0x8B96, 0xD790, 0x8B97, 0xD791, 0x8B98, + 0xD792, 0x8B99, 0xD793, 0x8B9A, 0xD794, 0x8B9B, 0xD795, 0x8B9C, 0xD796, 0x8B9D, 0xD797, 0x8B9E, 0xD798, 0x8B9F, 0xD799, 0x8BAC, + 0xD79A, 0x8BB1, 0xD79B, 0x8BBB, 0xD79C, 0x8BC7, 0xD79D, 0x8BD0, 0xD79E, 0x8BEA, 0xD79F, 0x8C09, 0xD7A0, 0x8C1E, 0xD7A1, 0x4F4F, + 0xD7A2, 0x6CE8, 0xD7A3, 0x795D, 0xD7A4, 0x9A7B, 0xD7A5, 0x6293, 0xD7A6, 0x722A, 0xD7A7, 0x62FD, 0xD7A8, 0x4E13, 0xD7A9, 0x7816, + 0xD7AA, 0x8F6C, 0xD7AB, 0x64B0, 0xD7AC, 0x8D5A, 0xD7AD, 0x7BC6, 0xD7AE, 0x6869, 0xD7AF, 0x5E84, 0xD7B0, 0x88C5, 0xD7B1, 0x5986, + 0xD7B2, 0x649E, 0xD7B3, 0x58EE, 0xD7B4, 0x72B6, 0xD7B5, 0x690E, 0xD7B6, 0x9525, 0xD7B7, 0x8FFD, 0xD7B8, 0x8D58, 0xD7B9, 0x5760, + 0xD7BA, 0x7F00, 0xD7BB, 0x8C06, 0xD7BC, 0x51C6, 0xD7BD, 0x6349, 0xD7BE, 0x62D9, 0xD7BF, 0x5353, 0xD7C0, 0x684C, 0xD7C1, 0x7422, + 0xD7C2, 0x8301, 0xD7C3, 0x914C, 0xD7C4, 0x5544, 0xD7C5, 0x7740, 0xD7C6, 0x707C, 0xD7C7, 0x6D4A, 0xD7C8, 0x5179, 0xD7C9, 0x54A8, + 0xD7CA, 0x8D44, 0xD7CB, 0x59FF, 0xD7CC, 0x6ECB, 0xD7CD, 0x6DC4, 0xD7CE, 0x5B5C, 0xD7CF, 0x7D2B, 0xD7D0, 0x4ED4, 0xD7D1, 0x7C7D, + 0xD7D2, 0x6ED3, 0xD7D3, 0x5B50, 0xD7D4, 0x81EA, 0xD7D5, 0x6E0D, 0xD7D6, 0x5B57, 0xD7D7, 0x9B03, 0xD7D8, 0x68D5, 0xD7D9, 0x8E2A, + 0xD7DA, 0x5B97, 0xD7DB, 0x7EFC, 0xD7DC, 0x603B, 0xD7DD, 0x7EB5, 0xD7DE, 0x90B9, 0xD7DF, 0x8D70, 0xD7E0, 0x594F, 0xD7E1, 0x63CD, + 0xD7E2, 0x79DF, 0xD7E3, 0x8DB3, 0xD7E4, 0x5352, 0xD7E5, 0x65CF, 0xD7E6, 0x7956, 0xD7E7, 0x8BC5, 0xD7E8, 0x963B, 0xD7E9, 0x7EC4, + 0xD7EA, 0x94BB, 0xD7EB, 0x7E82, 0xD7EC, 0x5634, 0xD7ED, 0x9189, 0xD7EE, 0x6700, 0xD7EF, 0x7F6A, 0xD7F0, 0x5C0A, 0xD7F1, 0x9075, + 0xD7F2, 0x6628, 0xD7F3, 0x5DE6, 0xD7F4, 0x4F50, 0xD7F5, 0x67DE, 0xD7F6, 0x505A, 0xD7F7, 0x4F5C, 0xD7F8, 0x5750, 0xD7F9, 0x5EA7, + 0xD840, 0x8C38, 0xD841, 0x8C39, 0xD842, 0x8C3A, 0xD843, 0x8C3B, 0xD844, 0x8C3C, 0xD845, 0x8C3D, 0xD846, 0x8C3E, 0xD847, 0x8C3F, + 0xD848, 0x8C40, 0xD849, 0x8C42, 0xD84A, 0x8C43, 0xD84B, 0x8C44, 0xD84C, 0x8C45, 0xD84D, 0x8C48, 0xD84E, 0x8C4A, 0xD84F, 0x8C4B, + 0xD850, 0x8C4D, 0xD851, 0x8C4E, 0xD852, 0x8C4F, 0xD853, 0x8C50, 0xD854, 0x8C51, 0xD855, 0x8C52, 0xD856, 0x8C53, 0xD857, 0x8C54, + 0xD858, 0x8C56, 0xD859, 0x8C57, 0xD85A, 0x8C58, 0xD85B, 0x8C59, 0xD85C, 0x8C5B, 0xD85D, 0x8C5C, 0xD85E, 0x8C5D, 0xD85F, 0x8C5E, + 0xD860, 0x8C5F, 0xD861, 0x8C60, 0xD862, 0x8C63, 0xD863, 0x8C64, 0xD864, 0x8C65, 0xD865, 0x8C66, 0xD866, 0x8C67, 0xD867, 0x8C68, + 0xD868, 0x8C69, 0xD869, 0x8C6C, 0xD86A, 0x8C6D, 0xD86B, 0x8C6E, 0xD86C, 0x8C6F, 0xD86D, 0x8C70, 0xD86E, 0x8C71, 0xD86F, 0x8C72, + 0xD870, 0x8C74, 0xD871, 0x8C75, 0xD872, 0x8C76, 0xD873, 0x8C77, 0xD874, 0x8C7B, 0xD875, 0x8C7C, 0xD876, 0x8C7D, 0xD877, 0x8C7E, + 0xD878, 0x8C7F, 0xD879, 0x8C80, 0xD87A, 0x8C81, 0xD87B, 0x8C83, 0xD87C, 0x8C84, 0xD87D, 0x8C86, 0xD87E, 0x8C87, 0xD880, 0x8C88, + 0xD881, 0x8C8B, 0xD882, 0x8C8D, 0xD883, 0x8C8E, 0xD884, 0x8C8F, 0xD885, 0x8C90, 0xD886, 0x8C91, 0xD887, 0x8C92, 0xD888, 0x8C93, + 0xD889, 0x8C95, 0xD88A, 0x8C96, 0xD88B, 0x8C97, 0xD88C, 0x8C99, 0xD88D, 0x8C9A, 0xD88E, 0x8C9B, 0xD88F, 0x8C9C, 0xD890, 0x8C9D, + 0xD891, 0x8C9E, 0xD892, 0x8C9F, 0xD893, 0x8CA0, 0xD894, 0x8CA1, 0xD895, 0x8CA2, 0xD896, 0x8CA3, 0xD897, 0x8CA4, 0xD898, 0x8CA5, + 0xD899, 0x8CA6, 0xD89A, 0x8CA7, 0xD89B, 0x8CA8, 0xD89C, 0x8CA9, 0xD89D, 0x8CAA, 0xD89E, 0x8CAB, 0xD89F, 0x8CAC, 0xD8A0, 0x8CAD, + 0xD8A1, 0x4E8D, 0xD8A2, 0x4E0C, 0xD8A3, 0x5140, 0xD8A4, 0x4E10, 0xD8A5, 0x5EFF, 0xD8A6, 0x5345, 0xD8A7, 0x4E15, 0xD8A8, 0x4E98, + 0xD8A9, 0x4E1E, 0xD8AA, 0x9B32, 0xD8AB, 0x5B6C, 0xD8AC, 0x5669, 0xD8AD, 0x4E28, 0xD8AE, 0x79BA, 0xD8AF, 0x4E3F, 0xD8B0, 0x5315, + 0xD8B1, 0x4E47, 0xD8B2, 0x592D, 0xD8B3, 0x723B, 0xD8B4, 0x536E, 0xD8B5, 0x6C10, 0xD8B6, 0x56DF, 0xD8B7, 0x80E4, 0xD8B8, 0x9997, + 0xD8B9, 0x6BD3, 0xD8BA, 0x777E, 0xD8BB, 0x9F17, 0xD8BC, 0x4E36, 0xD8BD, 0x4E9F, 0xD8BE, 0x9F10, 0xD8BF, 0x4E5C, 0xD8C0, 0x4E69, + 0xD8C1, 0x4E93, 0xD8C2, 0x8288, 0xD8C3, 0x5B5B, 0xD8C4, 0x556C, 0xD8C5, 0x560F, 0xD8C6, 0x4EC4, 0xD8C7, 0x538D, 0xD8C8, 0x539D, + 0xD8C9, 0x53A3, 0xD8CA, 0x53A5, 0xD8CB, 0x53AE, 0xD8CC, 0x9765, 0xD8CD, 0x8D5D, 0xD8CE, 0x531A, 0xD8CF, 0x53F5, 0xD8D0, 0x5326, + 0xD8D1, 0x532E, 0xD8D2, 0x533E, 0xD8D3, 0x8D5C, 0xD8D4, 0x5366, 0xD8D5, 0x5363, 0xD8D6, 0x5202, 0xD8D7, 0x5208, 0xD8D8, 0x520E, + 0xD8D9, 0x522D, 0xD8DA, 0x5233, 0xD8DB, 0x523F, 0xD8DC, 0x5240, 0xD8DD, 0x524C, 0xD8DE, 0x525E, 0xD8DF, 0x5261, 0xD8E0, 0x525C, + 0xD8E1, 0x84AF, 0xD8E2, 0x527D, 0xD8E3, 0x5282, 0xD8E4, 0x5281, 0xD8E5, 0x5290, 0xD8E6, 0x5293, 0xD8E7, 0x5182, 0xD8E8, 0x7F54, + 0xD8E9, 0x4EBB, 0xD8EA, 0x4EC3, 0xD8EB, 0x4EC9, 0xD8EC, 0x4EC2, 0xD8ED, 0x4EE8, 0xD8EE, 0x4EE1, 0xD8EF, 0x4EEB, 0xD8F0, 0x4EDE, + 0xD8F1, 0x4F1B, 0xD8F2, 0x4EF3, 0xD8F3, 0x4F22, 0xD8F4, 0x4F64, 0xD8F5, 0x4EF5, 0xD8F6, 0x4F25, 0xD8F7, 0x4F27, 0xD8F8, 0x4F09, + 0xD8F9, 0x4F2B, 0xD8FA, 0x4F5E, 0xD8FB, 0x4F67, 0xD8FC, 0x6538, 0xD8FD, 0x4F5A, 0xD8FE, 0x4F5D, 0xD940, 0x8CAE, 0xD941, 0x8CAF, + 0xD942, 0x8CB0, 0xD943, 0x8CB1, 0xD944, 0x8CB2, 0xD945, 0x8CB3, 0xD946, 0x8CB4, 0xD947, 0x8CB5, 0xD948, 0x8CB6, 0xD949, 0x8CB7, + 0xD94A, 0x8CB8, 0xD94B, 0x8CB9, 0xD94C, 0x8CBA, 0xD94D, 0x8CBB, 0xD94E, 0x8CBC, 0xD94F, 0x8CBD, 0xD950, 0x8CBE, 0xD951, 0x8CBF, + 0xD952, 0x8CC0, 0xD953, 0x8CC1, 0xD954, 0x8CC2, 0xD955, 0x8CC3, 0xD956, 0x8CC4, 0xD957, 0x8CC5, 0xD958, 0x8CC6, 0xD959, 0x8CC7, + 0xD95A, 0x8CC8, 0xD95B, 0x8CC9, 0xD95C, 0x8CCA, 0xD95D, 0x8CCB, 0xD95E, 0x8CCC, 0xD95F, 0x8CCD, 0xD960, 0x8CCE, 0xD961, 0x8CCF, + 0xD962, 0x8CD0, 0xD963, 0x8CD1, 0xD964, 0x8CD2, 0xD965, 0x8CD3, 0xD966, 0x8CD4, 0xD967, 0x8CD5, 0xD968, 0x8CD6, 0xD969, 0x8CD7, + 0xD96A, 0x8CD8, 0xD96B, 0x8CD9, 0xD96C, 0x8CDA, 0xD96D, 0x8CDB, 0xD96E, 0x8CDC, 0xD96F, 0x8CDD, 0xD970, 0x8CDE, 0xD971, 0x8CDF, + 0xD972, 0x8CE0, 0xD973, 0x8CE1, 0xD974, 0x8CE2, 0xD975, 0x8CE3, 0xD976, 0x8CE4, 0xD977, 0x8CE5, 0xD978, 0x8CE6, 0xD979, 0x8CE7, + 0xD97A, 0x8CE8, 0xD97B, 0x8CE9, 0xD97C, 0x8CEA, 0xD97D, 0x8CEB, 0xD97E, 0x8CEC, 0xD980, 0x8CED, 0xD981, 0x8CEE, 0xD982, 0x8CEF, + 0xD983, 0x8CF0, 0xD984, 0x8CF1, 0xD985, 0x8CF2, 0xD986, 0x8CF3, 0xD987, 0x8CF4, 0xD988, 0x8CF5, 0xD989, 0x8CF6, 0xD98A, 0x8CF7, + 0xD98B, 0x8CF8, 0xD98C, 0x8CF9, 0xD98D, 0x8CFA, 0xD98E, 0x8CFB, 0xD98F, 0x8CFC, 0xD990, 0x8CFD, 0xD991, 0x8CFE, 0xD992, 0x8CFF, + 0xD993, 0x8D00, 0xD994, 0x8D01, 0xD995, 0x8D02, 0xD996, 0x8D03, 0xD997, 0x8D04, 0xD998, 0x8D05, 0xD999, 0x8D06, 0xD99A, 0x8D07, + 0xD99B, 0x8D08, 0xD99C, 0x8D09, 0xD99D, 0x8D0A, 0xD99E, 0x8D0B, 0xD99F, 0x8D0C, 0xD9A0, 0x8D0D, 0xD9A1, 0x4F5F, 0xD9A2, 0x4F57, + 0xD9A3, 0x4F32, 0xD9A4, 0x4F3D, 0xD9A5, 0x4F76, 0xD9A6, 0x4F74, 0xD9A7, 0x4F91, 0xD9A8, 0x4F89, 0xD9A9, 0x4F83, 0xD9AA, 0x4F8F, + 0xD9AB, 0x4F7E, 0xD9AC, 0x4F7B, 0xD9AD, 0x4FAA, 0xD9AE, 0x4F7C, 0xD9AF, 0x4FAC, 0xD9B0, 0x4F94, 0xD9B1, 0x4FE6, 0xD9B2, 0x4FE8, + 0xD9B3, 0x4FEA, 0xD9B4, 0x4FC5, 0xD9B5, 0x4FDA, 0xD9B6, 0x4FE3, 0xD9B7, 0x4FDC, 0xD9B8, 0x4FD1, 0xD9B9, 0x4FDF, 0xD9BA, 0x4FF8, + 0xD9BB, 0x5029, 0xD9BC, 0x504C, 0xD9BD, 0x4FF3, 0xD9BE, 0x502C, 0xD9BF, 0x500F, 0xD9C0, 0x502E, 0xD9C1, 0x502D, 0xD9C2, 0x4FFE, + 0xD9C3, 0x501C, 0xD9C4, 0x500C, 0xD9C5, 0x5025, 0xD9C6, 0x5028, 0xD9C7, 0x507E, 0xD9C8, 0x5043, 0xD9C9, 0x5055, 0xD9CA, 0x5048, + 0xD9CB, 0x504E, 0xD9CC, 0x506C, 0xD9CD, 0x507B, 0xD9CE, 0x50A5, 0xD9CF, 0x50A7, 0xD9D0, 0x50A9, 0xD9D1, 0x50BA, 0xD9D2, 0x50D6, + 0xD9D3, 0x5106, 0xD9D4, 0x50ED, 0xD9D5, 0x50EC, 0xD9D6, 0x50E6, 0xD9D7, 0x50EE, 0xD9D8, 0x5107, 0xD9D9, 0x510B, 0xD9DA, 0x4EDD, + 0xD9DB, 0x6C3D, 0xD9DC, 0x4F58, 0xD9DD, 0x4F65, 0xD9DE, 0x4FCE, 0xD9DF, 0x9FA0, 0xD9E0, 0x6C46, 0xD9E1, 0x7C74, 0xD9E2, 0x516E, + 0xD9E3, 0x5DFD, 0xD9E4, 0x9EC9, 0xD9E5, 0x9998, 0xD9E6, 0x5181, 0xD9E7, 0x5914, 0xD9E8, 0x52F9, 0xD9E9, 0x530D, 0xD9EA, 0x8A07, + 0xD9EB, 0x5310, 0xD9EC, 0x51EB, 0xD9ED, 0x5919, 0xD9EE, 0x5155, 0xD9EF, 0x4EA0, 0xD9F0, 0x5156, 0xD9F1, 0x4EB3, 0xD9F2, 0x886E, + 0xD9F3, 0x88A4, 0xD9F4, 0x4EB5, 0xD9F5, 0x8114, 0xD9F6, 0x88D2, 0xD9F7, 0x7980, 0xD9F8, 0x5B34, 0xD9F9, 0x8803, 0xD9FA, 0x7FB8, + 0xD9FB, 0x51AB, 0xD9FC, 0x51B1, 0xD9FD, 0x51BD, 0xD9FE, 0x51BC, 0xDA40, 0x8D0E, 0xDA41, 0x8D0F, 0xDA42, 0x8D10, 0xDA43, 0x8D11, + 0xDA44, 0x8D12, 0xDA45, 0x8D13, 0xDA46, 0x8D14, 0xDA47, 0x8D15, 0xDA48, 0x8D16, 0xDA49, 0x8D17, 0xDA4A, 0x8D18, 0xDA4B, 0x8D19, + 0xDA4C, 0x8D1A, 0xDA4D, 0x8D1B, 0xDA4E, 0x8D1C, 0xDA4F, 0x8D20, 0xDA50, 0x8D51, 0xDA51, 0x8D52, 0xDA52, 0x8D57, 0xDA53, 0x8D5F, + 0xDA54, 0x8D65, 0xDA55, 0x8D68, 0xDA56, 0x8D69, 0xDA57, 0x8D6A, 0xDA58, 0x8D6C, 0xDA59, 0x8D6E, 0xDA5A, 0x8D6F, 0xDA5B, 0x8D71, + 0xDA5C, 0x8D72, 0xDA5D, 0x8D78, 0xDA5E, 0x8D79, 0xDA5F, 0x8D7A, 0xDA60, 0x8D7B, 0xDA61, 0x8D7C, 0xDA62, 0x8D7D, 0xDA63, 0x8D7E, + 0xDA64, 0x8D7F, 0xDA65, 0x8D80, 0xDA66, 0x8D82, 0xDA67, 0x8D83, 0xDA68, 0x8D86, 0xDA69, 0x8D87, 0xDA6A, 0x8D88, 0xDA6B, 0x8D89, + 0xDA6C, 0x8D8C, 0xDA6D, 0x8D8D, 0xDA6E, 0x8D8E, 0xDA6F, 0x8D8F, 0xDA70, 0x8D90, 0xDA71, 0x8D92, 0xDA72, 0x8D93, 0xDA73, 0x8D95, + 0xDA74, 0x8D96, 0xDA75, 0x8D97, 0xDA76, 0x8D98, 0xDA77, 0x8D99, 0xDA78, 0x8D9A, 0xDA79, 0x8D9B, 0xDA7A, 0x8D9C, 0xDA7B, 0x8D9D, + 0xDA7C, 0x8D9E, 0xDA7D, 0x8DA0, 0xDA7E, 0x8DA1, 0xDA80, 0x8DA2, 0xDA81, 0x8DA4, 0xDA82, 0x8DA5, 0xDA83, 0x8DA6, 0xDA84, 0x8DA7, + 0xDA85, 0x8DA8, 0xDA86, 0x8DA9, 0xDA87, 0x8DAA, 0xDA88, 0x8DAB, 0xDA89, 0x8DAC, 0xDA8A, 0x8DAD, 0xDA8B, 0x8DAE, 0xDA8C, 0x8DAF, + 0xDA8D, 0x8DB0, 0xDA8E, 0x8DB2, 0xDA8F, 0x8DB6, 0xDA90, 0x8DB7, 0xDA91, 0x8DB9, 0xDA92, 0x8DBB, 0xDA93, 0x8DBD, 0xDA94, 0x8DC0, + 0xDA95, 0x8DC1, 0xDA96, 0x8DC2, 0xDA97, 0x8DC5, 0xDA98, 0x8DC7, 0xDA99, 0x8DC8, 0xDA9A, 0x8DC9, 0xDA9B, 0x8DCA, 0xDA9C, 0x8DCD, + 0xDA9D, 0x8DD0, 0xDA9E, 0x8DD2, 0xDA9F, 0x8DD3, 0xDAA0, 0x8DD4, 0xDAA1, 0x51C7, 0xDAA2, 0x5196, 0xDAA3, 0x51A2, 0xDAA4, 0x51A5, + 0xDAA5, 0x8BA0, 0xDAA6, 0x8BA6, 0xDAA7, 0x8BA7, 0xDAA8, 0x8BAA, 0xDAA9, 0x8BB4, 0xDAAA, 0x8BB5, 0xDAAB, 0x8BB7, 0xDAAC, 0x8BC2, + 0xDAAD, 0x8BC3, 0xDAAE, 0x8BCB, 0xDAAF, 0x8BCF, 0xDAB0, 0x8BCE, 0xDAB1, 0x8BD2, 0xDAB2, 0x8BD3, 0xDAB3, 0x8BD4, 0xDAB4, 0x8BD6, + 0xDAB5, 0x8BD8, 0xDAB6, 0x8BD9, 0xDAB7, 0x8BDC, 0xDAB8, 0x8BDF, 0xDAB9, 0x8BE0, 0xDABA, 0x8BE4, 0xDABB, 0x8BE8, 0xDABC, 0x8BE9, + 0xDABD, 0x8BEE, 0xDABE, 0x8BF0, 0xDABF, 0x8BF3, 0xDAC0, 0x8BF6, 0xDAC1, 0x8BF9, 0xDAC2, 0x8BFC, 0xDAC3, 0x8BFF, 0xDAC4, 0x8C00, + 0xDAC5, 0x8C02, 0xDAC6, 0x8C04, 0xDAC7, 0x8C07, 0xDAC8, 0x8C0C, 0xDAC9, 0x8C0F, 0xDACA, 0x8C11, 0xDACB, 0x8C12, 0xDACC, 0x8C14, + 0xDACD, 0x8C15, 0xDACE, 0x8C16, 0xDACF, 0x8C19, 0xDAD0, 0x8C1B, 0xDAD1, 0x8C18, 0xDAD2, 0x8C1D, 0xDAD3, 0x8C1F, 0xDAD4, 0x8C20, + 0xDAD5, 0x8C21, 0xDAD6, 0x8C25, 0xDAD7, 0x8C27, 0xDAD8, 0x8C2A, 0xDAD9, 0x8C2B, 0xDADA, 0x8C2E, 0xDADB, 0x8C2F, 0xDADC, 0x8C32, + 0xDADD, 0x8C33, 0xDADE, 0x8C35, 0xDADF, 0x8C36, 0xDAE0, 0x5369, 0xDAE1, 0x537A, 0xDAE2, 0x961D, 0xDAE3, 0x9622, 0xDAE4, 0x9621, + 0xDAE5, 0x9631, 0xDAE6, 0x962A, 0xDAE7, 0x963D, 0xDAE8, 0x963C, 0xDAE9, 0x9642, 0xDAEA, 0x9649, 0xDAEB, 0x9654, 0xDAEC, 0x965F, + 0xDAED, 0x9667, 0xDAEE, 0x966C, 0xDAEF, 0x9672, 0xDAF0, 0x9674, 0xDAF1, 0x9688, 0xDAF2, 0x968D, 0xDAF3, 0x9697, 0xDAF4, 0x96B0, + 0xDAF5, 0x9097, 0xDAF6, 0x909B, 0xDAF7, 0x909D, 0xDAF8, 0x9099, 0xDAF9, 0x90AC, 0xDAFA, 0x90A1, 0xDAFB, 0x90B4, 0xDAFC, 0x90B3, + 0xDAFD, 0x90B6, 0xDAFE, 0x90BA, 0xDB40, 0x8DD5, 0xDB41, 0x8DD8, 0xDB42, 0x8DD9, 0xDB43, 0x8DDC, 0xDB44, 0x8DE0, 0xDB45, 0x8DE1, + 0xDB46, 0x8DE2, 0xDB47, 0x8DE5, 0xDB48, 0x8DE6, 0xDB49, 0x8DE7, 0xDB4A, 0x8DE9, 0xDB4B, 0x8DED, 0xDB4C, 0x8DEE, 0xDB4D, 0x8DF0, + 0xDB4E, 0x8DF1, 0xDB4F, 0x8DF2, 0xDB50, 0x8DF4, 0xDB51, 0x8DF6, 0xDB52, 0x8DFC, 0xDB53, 0x8DFE, 0xDB54, 0x8DFF, 0xDB55, 0x8E00, + 0xDB56, 0x8E01, 0xDB57, 0x8E02, 0xDB58, 0x8E03, 0xDB59, 0x8E04, 0xDB5A, 0x8E06, 0xDB5B, 0x8E07, 0xDB5C, 0x8E08, 0xDB5D, 0x8E0B, + 0xDB5E, 0x8E0D, 0xDB5F, 0x8E0E, 0xDB60, 0x8E10, 0xDB61, 0x8E11, 0xDB62, 0x8E12, 0xDB63, 0x8E13, 0xDB64, 0x8E15, 0xDB65, 0x8E16, + 0xDB66, 0x8E17, 0xDB67, 0x8E18, 0xDB68, 0x8E19, 0xDB69, 0x8E1A, 0xDB6A, 0x8E1B, 0xDB6B, 0x8E1C, 0xDB6C, 0x8E20, 0xDB6D, 0x8E21, + 0xDB6E, 0x8E24, 0xDB6F, 0x8E25, 0xDB70, 0x8E26, 0xDB71, 0x8E27, 0xDB72, 0x8E28, 0xDB73, 0x8E2B, 0xDB74, 0x8E2D, 0xDB75, 0x8E30, + 0xDB76, 0x8E32, 0xDB77, 0x8E33, 0xDB78, 0x8E34, 0xDB79, 0x8E36, 0xDB7A, 0x8E37, 0xDB7B, 0x8E38, 0xDB7C, 0x8E3B, 0xDB7D, 0x8E3C, + 0xDB7E, 0x8E3E, 0xDB80, 0x8E3F, 0xDB81, 0x8E43, 0xDB82, 0x8E45, 0xDB83, 0x8E46, 0xDB84, 0x8E4C, 0xDB85, 0x8E4D, 0xDB86, 0x8E4E, + 0xDB87, 0x8E4F, 0xDB88, 0x8E50, 0xDB89, 0x8E53, 0xDB8A, 0x8E54, 0xDB8B, 0x8E55, 0xDB8C, 0x8E56, 0xDB8D, 0x8E57, 0xDB8E, 0x8E58, + 0xDB8F, 0x8E5A, 0xDB90, 0x8E5B, 0xDB91, 0x8E5C, 0xDB92, 0x8E5D, 0xDB93, 0x8E5E, 0xDB94, 0x8E5F, 0xDB95, 0x8E60, 0xDB96, 0x8E61, + 0xDB97, 0x8E62, 0xDB98, 0x8E63, 0xDB99, 0x8E64, 0xDB9A, 0x8E65, 0xDB9B, 0x8E67, 0xDB9C, 0x8E68, 0xDB9D, 0x8E6A, 0xDB9E, 0x8E6B, + 0xDB9F, 0x8E6E, 0xDBA0, 0x8E71, 0xDBA1, 0x90B8, 0xDBA2, 0x90B0, 0xDBA3, 0x90CF, 0xDBA4, 0x90C5, 0xDBA5, 0x90BE, 0xDBA6, 0x90D0, + 0xDBA7, 0x90C4, 0xDBA8, 0x90C7, 0xDBA9, 0x90D3, 0xDBAA, 0x90E6, 0xDBAB, 0x90E2, 0xDBAC, 0x90DC, 0xDBAD, 0x90D7, 0xDBAE, 0x90DB, + 0xDBAF, 0x90EB, 0xDBB0, 0x90EF, 0xDBB1, 0x90FE, 0xDBB2, 0x9104, 0xDBB3, 0x9122, 0xDBB4, 0x911E, 0xDBB5, 0x9123, 0xDBB6, 0x9131, + 0xDBB7, 0x912F, 0xDBB8, 0x9139, 0xDBB9, 0x9143, 0xDBBA, 0x9146, 0xDBBB, 0x520D, 0xDBBC, 0x5942, 0xDBBD, 0x52A2, 0xDBBE, 0x52AC, + 0xDBBF, 0x52AD, 0xDBC0, 0x52BE, 0xDBC1, 0x54FF, 0xDBC2, 0x52D0, 0xDBC3, 0x52D6, 0xDBC4, 0x52F0, 0xDBC5, 0x53DF, 0xDBC6, 0x71EE, + 0xDBC7, 0x77CD, 0xDBC8, 0x5EF4, 0xDBC9, 0x51F5, 0xDBCA, 0x51FC, 0xDBCB, 0x9B2F, 0xDBCC, 0x53B6, 0xDBCD, 0x5F01, 0xDBCE, 0x755A, + 0xDBCF, 0x5DEF, 0xDBD0, 0x574C, 0xDBD1, 0x57A9, 0xDBD2, 0x57A1, 0xDBD3, 0x587E, 0xDBD4, 0x58BC, 0xDBD5, 0x58C5, 0xDBD6, 0x58D1, + 0xDBD7, 0x5729, 0xDBD8, 0x572C, 0xDBD9, 0x572A, 0xDBDA, 0x5733, 0xDBDB, 0x5739, 0xDBDC, 0x572E, 0xDBDD, 0x572F, 0xDBDE, 0x575C, + 0xDBDF, 0x573B, 0xDBE0, 0x5742, 0xDBE1, 0x5769, 0xDBE2, 0x5785, 0xDBE3, 0x576B, 0xDBE4, 0x5786, 0xDBE5, 0x577C, 0xDBE6, 0x577B, + 0xDBE7, 0x5768, 0xDBE8, 0x576D, 0xDBE9, 0x5776, 0xDBEA, 0x5773, 0xDBEB, 0x57AD, 0xDBEC, 0x57A4, 0xDBED, 0x578C, 0xDBEE, 0x57B2, + 0xDBEF, 0x57CF, 0xDBF0, 0x57A7, 0xDBF1, 0x57B4, 0xDBF2, 0x5793, 0xDBF3, 0x57A0, 0xDBF4, 0x57D5, 0xDBF5, 0x57D8, 0xDBF6, 0x57DA, + 0xDBF7, 0x57D9, 0xDBF8, 0x57D2, 0xDBF9, 0x57B8, 0xDBFA, 0x57F4, 0xDBFB, 0x57EF, 0xDBFC, 0x57F8, 0xDBFD, 0x57E4, 0xDBFE, 0x57DD, + 0xDC40, 0x8E73, 0xDC41, 0x8E75, 0xDC42, 0x8E77, 0xDC43, 0x8E78, 0xDC44, 0x8E79, 0xDC45, 0x8E7A, 0xDC46, 0x8E7B, 0xDC47, 0x8E7D, + 0xDC48, 0x8E7E, 0xDC49, 0x8E80, 0xDC4A, 0x8E82, 0xDC4B, 0x8E83, 0xDC4C, 0x8E84, 0xDC4D, 0x8E86, 0xDC4E, 0x8E88, 0xDC4F, 0x8E89, + 0xDC50, 0x8E8A, 0xDC51, 0x8E8B, 0xDC52, 0x8E8C, 0xDC53, 0x8E8D, 0xDC54, 0x8E8E, 0xDC55, 0x8E91, 0xDC56, 0x8E92, 0xDC57, 0x8E93, + 0xDC58, 0x8E95, 0xDC59, 0x8E96, 0xDC5A, 0x8E97, 0xDC5B, 0x8E98, 0xDC5C, 0x8E99, 0xDC5D, 0x8E9A, 0xDC5E, 0x8E9B, 0xDC5F, 0x8E9D, + 0xDC60, 0x8E9F, 0xDC61, 0x8EA0, 0xDC62, 0x8EA1, 0xDC63, 0x8EA2, 0xDC64, 0x8EA3, 0xDC65, 0x8EA4, 0xDC66, 0x8EA5, 0xDC67, 0x8EA6, + 0xDC68, 0x8EA7, 0xDC69, 0x8EA8, 0xDC6A, 0x8EA9, 0xDC6B, 0x8EAA, 0xDC6C, 0x8EAD, 0xDC6D, 0x8EAE, 0xDC6E, 0x8EB0, 0xDC6F, 0x8EB1, + 0xDC70, 0x8EB3, 0xDC71, 0x8EB4, 0xDC72, 0x8EB5, 0xDC73, 0x8EB6, 0xDC74, 0x8EB7, 0xDC75, 0x8EB8, 0xDC76, 0x8EB9, 0xDC77, 0x8EBB, + 0xDC78, 0x8EBC, 0xDC79, 0x8EBD, 0xDC7A, 0x8EBE, 0xDC7B, 0x8EBF, 0xDC7C, 0x8EC0, 0xDC7D, 0x8EC1, 0xDC7E, 0x8EC2, 0xDC80, 0x8EC3, + 0xDC81, 0x8EC4, 0xDC82, 0x8EC5, 0xDC83, 0x8EC6, 0xDC84, 0x8EC7, 0xDC85, 0x8EC8, 0xDC86, 0x8EC9, 0xDC87, 0x8ECA, 0xDC88, 0x8ECB, + 0xDC89, 0x8ECC, 0xDC8A, 0x8ECD, 0xDC8B, 0x8ECF, 0xDC8C, 0x8ED0, 0xDC8D, 0x8ED1, 0xDC8E, 0x8ED2, 0xDC8F, 0x8ED3, 0xDC90, 0x8ED4, + 0xDC91, 0x8ED5, 0xDC92, 0x8ED6, 0xDC93, 0x8ED7, 0xDC94, 0x8ED8, 0xDC95, 0x8ED9, 0xDC96, 0x8EDA, 0xDC97, 0x8EDB, 0xDC98, 0x8EDC, + 0xDC99, 0x8EDD, 0xDC9A, 0x8EDE, 0xDC9B, 0x8EDF, 0xDC9C, 0x8EE0, 0xDC9D, 0x8EE1, 0xDC9E, 0x8EE2, 0xDC9F, 0x8EE3, 0xDCA0, 0x8EE4, + 0xDCA1, 0x580B, 0xDCA2, 0x580D, 0xDCA3, 0x57FD, 0xDCA4, 0x57ED, 0xDCA5, 0x5800, 0xDCA6, 0x581E, 0xDCA7, 0x5819, 0xDCA8, 0x5844, + 0xDCA9, 0x5820, 0xDCAA, 0x5865, 0xDCAB, 0x586C, 0xDCAC, 0x5881, 0xDCAD, 0x5889, 0xDCAE, 0x589A, 0xDCAF, 0x5880, 0xDCB0, 0x99A8, + 0xDCB1, 0x9F19, 0xDCB2, 0x61FF, 0xDCB3, 0x8279, 0xDCB4, 0x827D, 0xDCB5, 0x827F, 0xDCB6, 0x828F, 0xDCB7, 0x828A, 0xDCB8, 0x82A8, + 0xDCB9, 0x8284, 0xDCBA, 0x828E, 0xDCBB, 0x8291, 0xDCBC, 0x8297, 0xDCBD, 0x8299, 0xDCBE, 0x82AB, 0xDCBF, 0x82B8, 0xDCC0, 0x82BE, + 0xDCC1, 0x82B0, 0xDCC2, 0x82C8, 0xDCC3, 0x82CA, 0xDCC4, 0x82E3, 0xDCC5, 0x8298, 0xDCC6, 0x82B7, 0xDCC7, 0x82AE, 0xDCC8, 0x82CB, + 0xDCC9, 0x82CC, 0xDCCA, 0x82C1, 0xDCCB, 0x82A9, 0xDCCC, 0x82B4, 0xDCCD, 0x82A1, 0xDCCE, 0x82AA, 0xDCCF, 0x829F, 0xDCD0, 0x82C4, + 0xDCD1, 0x82CE, 0xDCD2, 0x82A4, 0xDCD3, 0x82E1, 0xDCD4, 0x8309, 0xDCD5, 0x82F7, 0xDCD6, 0x82E4, 0xDCD7, 0x830F, 0xDCD8, 0x8307, + 0xDCD9, 0x82DC, 0xDCDA, 0x82F4, 0xDCDB, 0x82D2, 0xDCDC, 0x82D8, 0xDCDD, 0x830C, 0xDCDE, 0x82FB, 0xDCDF, 0x82D3, 0xDCE0, 0x8311, + 0xDCE1, 0x831A, 0xDCE2, 0x8306, 0xDCE3, 0x8314, 0xDCE4, 0x8315, 0xDCE5, 0x82E0, 0xDCE6, 0x82D5, 0xDCE7, 0x831C, 0xDCE8, 0x8351, + 0xDCE9, 0x835B, 0xDCEA, 0x835C, 0xDCEB, 0x8308, 0xDCEC, 0x8392, 0xDCED, 0x833C, 0xDCEE, 0x8334, 0xDCEF, 0x8331, 0xDCF0, 0x839B, + 0xDCF1, 0x835E, 0xDCF2, 0x832F, 0xDCF3, 0x834F, 0xDCF4, 0x8347, 0xDCF5, 0x8343, 0xDCF6, 0x835F, 0xDCF7, 0x8340, 0xDCF8, 0x8317, + 0xDCF9, 0x8360, 0xDCFA, 0x832D, 0xDCFB, 0x833A, 0xDCFC, 0x8333, 0xDCFD, 0x8366, 0xDCFE, 0x8365, 0xDD40, 0x8EE5, 0xDD41, 0x8EE6, + 0xDD42, 0x8EE7, 0xDD43, 0x8EE8, 0xDD44, 0x8EE9, 0xDD45, 0x8EEA, 0xDD46, 0x8EEB, 0xDD47, 0x8EEC, 0xDD48, 0x8EED, 0xDD49, 0x8EEE, + 0xDD4A, 0x8EEF, 0xDD4B, 0x8EF0, 0xDD4C, 0x8EF1, 0xDD4D, 0x8EF2, 0xDD4E, 0x8EF3, 0xDD4F, 0x8EF4, 0xDD50, 0x8EF5, 0xDD51, 0x8EF6, + 0xDD52, 0x8EF7, 0xDD53, 0x8EF8, 0xDD54, 0x8EF9, 0xDD55, 0x8EFA, 0xDD56, 0x8EFB, 0xDD57, 0x8EFC, 0xDD58, 0x8EFD, 0xDD59, 0x8EFE, + 0xDD5A, 0x8EFF, 0xDD5B, 0x8F00, 0xDD5C, 0x8F01, 0xDD5D, 0x8F02, 0xDD5E, 0x8F03, 0xDD5F, 0x8F04, 0xDD60, 0x8F05, 0xDD61, 0x8F06, + 0xDD62, 0x8F07, 0xDD63, 0x8F08, 0xDD64, 0x8F09, 0xDD65, 0x8F0A, 0xDD66, 0x8F0B, 0xDD67, 0x8F0C, 0xDD68, 0x8F0D, 0xDD69, 0x8F0E, + 0xDD6A, 0x8F0F, 0xDD6B, 0x8F10, 0xDD6C, 0x8F11, 0xDD6D, 0x8F12, 0xDD6E, 0x8F13, 0xDD6F, 0x8F14, 0xDD70, 0x8F15, 0xDD71, 0x8F16, + 0xDD72, 0x8F17, 0xDD73, 0x8F18, 0xDD74, 0x8F19, 0xDD75, 0x8F1A, 0xDD76, 0x8F1B, 0xDD77, 0x8F1C, 0xDD78, 0x8F1D, 0xDD79, 0x8F1E, + 0xDD7A, 0x8F1F, 0xDD7B, 0x8F20, 0xDD7C, 0x8F21, 0xDD7D, 0x8F22, 0xDD7E, 0x8F23, 0xDD80, 0x8F24, 0xDD81, 0x8F25, 0xDD82, 0x8F26, + 0xDD83, 0x8F27, 0xDD84, 0x8F28, 0xDD85, 0x8F29, 0xDD86, 0x8F2A, 0xDD87, 0x8F2B, 0xDD88, 0x8F2C, 0xDD89, 0x8F2D, 0xDD8A, 0x8F2E, + 0xDD8B, 0x8F2F, 0xDD8C, 0x8F30, 0xDD8D, 0x8F31, 0xDD8E, 0x8F32, 0xDD8F, 0x8F33, 0xDD90, 0x8F34, 0xDD91, 0x8F35, 0xDD92, 0x8F36, + 0xDD93, 0x8F37, 0xDD94, 0x8F38, 0xDD95, 0x8F39, 0xDD96, 0x8F3A, 0xDD97, 0x8F3B, 0xDD98, 0x8F3C, 0xDD99, 0x8F3D, 0xDD9A, 0x8F3E, + 0xDD9B, 0x8F3F, 0xDD9C, 0x8F40, 0xDD9D, 0x8F41, 0xDD9E, 0x8F42, 0xDD9F, 0x8F43, 0xDDA0, 0x8F44, 0xDDA1, 0x8368, 0xDDA2, 0x831B, + 0xDDA3, 0x8369, 0xDDA4, 0x836C, 0xDDA5, 0x836A, 0xDDA6, 0x836D, 0xDDA7, 0x836E, 0xDDA8, 0x83B0, 0xDDA9, 0x8378, 0xDDAA, 0x83B3, + 0xDDAB, 0x83B4, 0xDDAC, 0x83A0, 0xDDAD, 0x83AA, 0xDDAE, 0x8393, 0xDDAF, 0x839C, 0xDDB0, 0x8385, 0xDDB1, 0x837C, 0xDDB2, 0x83B6, + 0xDDB3, 0x83A9, 0xDDB4, 0x837D, 0xDDB5, 0x83B8, 0xDDB6, 0x837B, 0xDDB7, 0x8398, 0xDDB8, 0x839E, 0xDDB9, 0x83A8, 0xDDBA, 0x83BA, + 0xDDBB, 0x83BC, 0xDDBC, 0x83C1, 0xDDBD, 0x8401, 0xDDBE, 0x83E5, 0xDDBF, 0x83D8, 0xDDC0, 0x5807, 0xDDC1, 0x8418, 0xDDC2, 0x840B, + 0xDDC3, 0x83DD, 0xDDC4, 0x83FD, 0xDDC5, 0x83D6, 0xDDC6, 0x841C, 0xDDC7, 0x8438, 0xDDC8, 0x8411, 0xDDC9, 0x8406, 0xDDCA, 0x83D4, + 0xDDCB, 0x83DF, 0xDDCC, 0x840F, 0xDDCD, 0x8403, 0xDDCE, 0x83F8, 0xDDCF, 0x83F9, 0xDDD0, 0x83EA, 0xDDD1, 0x83C5, 0xDDD2, 0x83C0, + 0xDDD3, 0x8426, 0xDDD4, 0x83F0, 0xDDD5, 0x83E1, 0xDDD6, 0x845C, 0xDDD7, 0x8451, 0xDDD8, 0x845A, 0xDDD9, 0x8459, 0xDDDA, 0x8473, + 0xDDDB, 0x8487, 0xDDDC, 0x8488, 0xDDDD, 0x847A, 0xDDDE, 0x8489, 0xDDDF, 0x8478, 0xDDE0, 0x843C, 0xDDE1, 0x8446, 0xDDE2, 0x8469, + 0xDDE3, 0x8476, 0xDDE4, 0x848C, 0xDDE5, 0x848E, 0xDDE6, 0x8431, 0xDDE7, 0x846D, 0xDDE8, 0x84C1, 0xDDE9, 0x84CD, 0xDDEA, 0x84D0, + 0xDDEB, 0x84E6, 0xDDEC, 0x84BD, 0xDDED, 0x84D3, 0xDDEE, 0x84CA, 0xDDEF, 0x84BF, 0xDDF0, 0x84BA, 0xDDF1, 0x84E0, 0xDDF2, 0x84A1, + 0xDDF3, 0x84B9, 0xDDF4, 0x84B4, 0xDDF5, 0x8497, 0xDDF6, 0x84E5, 0xDDF7, 0x84E3, 0xDDF8, 0x850C, 0xDDF9, 0x750D, 0xDDFA, 0x8538, + 0xDDFB, 0x84F0, 0xDDFC, 0x8539, 0xDDFD, 0x851F, 0xDDFE, 0x853A, 0xDE40, 0x8F45, 0xDE41, 0x8F46, 0xDE42, 0x8F47, 0xDE43, 0x8F48, + 0xDE44, 0x8F49, 0xDE45, 0x8F4A, 0xDE46, 0x8F4B, 0xDE47, 0x8F4C, 0xDE48, 0x8F4D, 0xDE49, 0x8F4E, 0xDE4A, 0x8F4F, 0xDE4B, 0x8F50, + 0xDE4C, 0x8F51, 0xDE4D, 0x8F52, 0xDE4E, 0x8F53, 0xDE4F, 0x8F54, 0xDE50, 0x8F55, 0xDE51, 0x8F56, 0xDE52, 0x8F57, 0xDE53, 0x8F58, + 0xDE54, 0x8F59, 0xDE55, 0x8F5A, 0xDE56, 0x8F5B, 0xDE57, 0x8F5C, 0xDE58, 0x8F5D, 0xDE59, 0x8F5E, 0xDE5A, 0x8F5F, 0xDE5B, 0x8F60, + 0xDE5C, 0x8F61, 0xDE5D, 0x8F62, 0xDE5E, 0x8F63, 0xDE5F, 0x8F64, 0xDE60, 0x8F65, 0xDE61, 0x8F6A, 0xDE62, 0x8F80, 0xDE63, 0x8F8C, + 0xDE64, 0x8F92, 0xDE65, 0x8F9D, 0xDE66, 0x8FA0, 0xDE67, 0x8FA1, 0xDE68, 0x8FA2, 0xDE69, 0x8FA4, 0xDE6A, 0x8FA5, 0xDE6B, 0x8FA6, + 0xDE6C, 0x8FA7, 0xDE6D, 0x8FAA, 0xDE6E, 0x8FAC, 0xDE6F, 0x8FAD, 0xDE70, 0x8FAE, 0xDE71, 0x8FAF, 0xDE72, 0x8FB2, 0xDE73, 0x8FB3, + 0xDE74, 0x8FB4, 0xDE75, 0x8FB5, 0xDE76, 0x8FB7, 0xDE77, 0x8FB8, 0xDE78, 0x8FBA, 0xDE79, 0x8FBB, 0xDE7A, 0x8FBC, 0xDE7B, 0x8FBF, + 0xDE7C, 0x8FC0, 0xDE7D, 0x8FC3, 0xDE7E, 0x8FC6, 0xDE80, 0x8FC9, 0xDE81, 0x8FCA, 0xDE82, 0x8FCB, 0xDE83, 0x8FCC, 0xDE84, 0x8FCD, + 0xDE85, 0x8FCF, 0xDE86, 0x8FD2, 0xDE87, 0x8FD6, 0xDE88, 0x8FD7, 0xDE89, 0x8FDA, 0xDE8A, 0x8FE0, 0xDE8B, 0x8FE1, 0xDE8C, 0x8FE3, + 0xDE8D, 0x8FE7, 0xDE8E, 0x8FEC, 0xDE8F, 0x8FEF, 0xDE90, 0x8FF1, 0xDE91, 0x8FF2, 0xDE92, 0x8FF4, 0xDE93, 0x8FF5, 0xDE94, 0x8FF6, + 0xDE95, 0x8FFA, 0xDE96, 0x8FFB, 0xDE97, 0x8FFC, 0xDE98, 0x8FFE, 0xDE99, 0x8FFF, 0xDE9A, 0x9007, 0xDE9B, 0x9008, 0xDE9C, 0x900C, + 0xDE9D, 0x900E, 0xDE9E, 0x9013, 0xDE9F, 0x9015, 0xDEA0, 0x9018, 0xDEA1, 0x8556, 0xDEA2, 0x853B, 0xDEA3, 0x84FF, 0xDEA4, 0x84FC, + 0xDEA5, 0x8559, 0xDEA6, 0x8548, 0xDEA7, 0x8568, 0xDEA8, 0x8564, 0xDEA9, 0x855E, 0xDEAA, 0x857A, 0xDEAB, 0x77A2, 0xDEAC, 0x8543, + 0xDEAD, 0x8572, 0xDEAE, 0x857B, 0xDEAF, 0x85A4, 0xDEB0, 0x85A8, 0xDEB1, 0x8587, 0xDEB2, 0x858F, 0xDEB3, 0x8579, 0xDEB4, 0x85AE, + 0xDEB5, 0x859C, 0xDEB6, 0x8585, 0xDEB7, 0x85B9, 0xDEB8, 0x85B7, 0xDEB9, 0x85B0, 0xDEBA, 0x85D3, 0xDEBB, 0x85C1, 0xDEBC, 0x85DC, + 0xDEBD, 0x85FF, 0xDEBE, 0x8627, 0xDEBF, 0x8605, 0xDEC0, 0x8629, 0xDEC1, 0x8616, 0xDEC2, 0x863C, 0xDEC3, 0x5EFE, 0xDEC4, 0x5F08, + 0xDEC5, 0x593C, 0xDEC6, 0x5941, 0xDEC7, 0x8037, 0xDEC8, 0x5955, 0xDEC9, 0x595A, 0xDECA, 0x5958, 0xDECB, 0x530F, 0xDECC, 0x5C22, + 0xDECD, 0x5C25, 0xDECE, 0x5C2C, 0xDECF, 0x5C34, 0xDED0, 0x624C, 0xDED1, 0x626A, 0xDED2, 0x629F, 0xDED3, 0x62BB, 0xDED4, 0x62CA, + 0xDED5, 0x62DA, 0xDED6, 0x62D7, 0xDED7, 0x62EE, 0xDED8, 0x6322, 0xDED9, 0x62F6, 0xDEDA, 0x6339, 0xDEDB, 0x634B, 0xDEDC, 0x6343, + 0xDEDD, 0x63AD, 0xDEDE, 0x63F6, 0xDEDF, 0x6371, 0xDEE0, 0x637A, 0xDEE1, 0x638E, 0xDEE2, 0x63B4, 0xDEE3, 0x636D, 0xDEE4, 0x63AC, + 0xDEE5, 0x638A, 0xDEE6, 0x6369, 0xDEE7, 0x63AE, 0xDEE8, 0x63BC, 0xDEE9, 0x63F2, 0xDEEA, 0x63F8, 0xDEEB, 0x63E0, 0xDEEC, 0x63FF, + 0xDEED, 0x63C4, 0xDEEE, 0x63DE, 0xDEEF, 0x63CE, 0xDEF0, 0x6452, 0xDEF1, 0x63C6, 0xDEF2, 0x63BE, 0xDEF3, 0x6445, 0xDEF4, 0x6441, + 0xDEF5, 0x640B, 0xDEF6, 0x641B, 0xDEF7, 0x6420, 0xDEF8, 0x640C, 0xDEF9, 0x6426, 0xDEFA, 0x6421, 0xDEFB, 0x645E, 0xDEFC, 0x6484, + 0xDEFD, 0x646D, 0xDEFE, 0x6496, 0xDF40, 0x9019, 0xDF41, 0x901C, 0xDF42, 0x9023, 0xDF43, 0x9024, 0xDF44, 0x9025, 0xDF45, 0x9027, + 0xDF46, 0x9028, 0xDF47, 0x9029, 0xDF48, 0x902A, 0xDF49, 0x902B, 0xDF4A, 0x902C, 0xDF4B, 0x9030, 0xDF4C, 0x9031, 0xDF4D, 0x9032, + 0xDF4E, 0x9033, 0xDF4F, 0x9034, 0xDF50, 0x9037, 0xDF51, 0x9039, 0xDF52, 0x903A, 0xDF53, 0x903D, 0xDF54, 0x903F, 0xDF55, 0x9040, + 0xDF56, 0x9043, 0xDF57, 0x9045, 0xDF58, 0x9046, 0xDF59, 0x9048, 0xDF5A, 0x9049, 0xDF5B, 0x904A, 0xDF5C, 0x904B, 0xDF5D, 0x904C, + 0xDF5E, 0x904E, 0xDF5F, 0x9054, 0xDF60, 0x9055, 0xDF61, 0x9056, 0xDF62, 0x9059, 0xDF63, 0x905A, 0xDF64, 0x905C, 0xDF65, 0x905D, + 0xDF66, 0x905E, 0xDF67, 0x905F, 0xDF68, 0x9060, 0xDF69, 0x9061, 0xDF6A, 0x9064, 0xDF6B, 0x9066, 0xDF6C, 0x9067, 0xDF6D, 0x9069, + 0xDF6E, 0x906A, 0xDF6F, 0x906B, 0xDF70, 0x906C, 0xDF71, 0x906F, 0xDF72, 0x9070, 0xDF73, 0x9071, 0xDF74, 0x9072, 0xDF75, 0x9073, + 0xDF76, 0x9076, 0xDF77, 0x9077, 0xDF78, 0x9078, 0xDF79, 0x9079, 0xDF7A, 0x907A, 0xDF7B, 0x907B, 0xDF7C, 0x907C, 0xDF7D, 0x907E, + 0xDF7E, 0x9081, 0xDF80, 0x9084, 0xDF81, 0x9085, 0xDF82, 0x9086, 0xDF83, 0x9087, 0xDF84, 0x9089, 0xDF85, 0x908A, 0xDF86, 0x908C, + 0xDF87, 0x908D, 0xDF88, 0x908E, 0xDF89, 0x908F, 0xDF8A, 0x9090, 0xDF8B, 0x9092, 0xDF8C, 0x9094, 0xDF8D, 0x9096, 0xDF8E, 0x9098, + 0xDF8F, 0x909A, 0xDF90, 0x909C, 0xDF91, 0x909E, 0xDF92, 0x909F, 0xDF93, 0x90A0, 0xDF94, 0x90A4, 0xDF95, 0x90A5, 0xDF96, 0x90A7, + 0xDF97, 0x90A8, 0xDF98, 0x90A9, 0xDF99, 0x90AB, 0xDF9A, 0x90AD, 0xDF9B, 0x90B2, 0xDF9C, 0x90B7, 0xDF9D, 0x90BC, 0xDF9E, 0x90BD, + 0xDF9F, 0x90BF, 0xDFA0, 0x90C0, 0xDFA1, 0x647A, 0xDFA2, 0x64B7, 0xDFA3, 0x64B8, 0xDFA4, 0x6499, 0xDFA5, 0x64BA, 0xDFA6, 0x64C0, + 0xDFA7, 0x64D0, 0xDFA8, 0x64D7, 0xDFA9, 0x64E4, 0xDFAA, 0x64E2, 0xDFAB, 0x6509, 0xDFAC, 0x6525, 0xDFAD, 0x652E, 0xDFAE, 0x5F0B, + 0xDFAF, 0x5FD2, 0xDFB0, 0x7519, 0xDFB1, 0x5F11, 0xDFB2, 0x535F, 0xDFB3, 0x53F1, 0xDFB4, 0x53FD, 0xDFB5, 0x53E9, 0xDFB6, 0x53E8, + 0xDFB7, 0x53FB, 0xDFB8, 0x5412, 0xDFB9, 0x5416, 0xDFBA, 0x5406, 0xDFBB, 0x544B, 0xDFBC, 0x5452, 0xDFBD, 0x5453, 0xDFBE, 0x5454, + 0xDFBF, 0x5456, 0xDFC0, 0x5443, 0xDFC1, 0x5421, 0xDFC2, 0x5457, 0xDFC3, 0x5459, 0xDFC4, 0x5423, 0xDFC5, 0x5432, 0xDFC6, 0x5482, + 0xDFC7, 0x5494, 0xDFC8, 0x5477, 0xDFC9, 0x5471, 0xDFCA, 0x5464, 0xDFCB, 0x549A, 0xDFCC, 0x549B, 0xDFCD, 0x5484, 0xDFCE, 0x5476, + 0xDFCF, 0x5466, 0xDFD0, 0x549D, 0xDFD1, 0x54D0, 0xDFD2, 0x54AD, 0xDFD3, 0x54C2, 0xDFD4, 0x54B4, 0xDFD5, 0x54D2, 0xDFD6, 0x54A7, + 0xDFD7, 0x54A6, 0xDFD8, 0x54D3, 0xDFD9, 0x54D4, 0xDFDA, 0x5472, 0xDFDB, 0x54A3, 0xDFDC, 0x54D5, 0xDFDD, 0x54BB, 0xDFDE, 0x54BF, + 0xDFDF, 0x54CC, 0xDFE0, 0x54D9, 0xDFE1, 0x54DA, 0xDFE2, 0x54DC, 0xDFE3, 0x54A9, 0xDFE4, 0x54AA, 0xDFE5, 0x54A4, 0xDFE6, 0x54DD, + 0xDFE7, 0x54CF, 0xDFE8, 0x54DE, 0xDFE9, 0x551B, 0xDFEA, 0x54E7, 0xDFEB, 0x5520, 0xDFEC, 0x54FD, 0xDFED, 0x5514, 0xDFEE, 0x54F3, + 0xDFEF, 0x5522, 0xDFF0, 0x5523, 0xDFF1, 0x550F, 0xDFF2, 0x5511, 0xDFF3, 0x5527, 0xDFF4, 0x552A, 0xDFF5, 0x5567, 0xDFF6, 0x558F, + 0xDFF7, 0x55B5, 0xDFF8, 0x5549, 0xDFF9, 0x556D, 0xDFFA, 0x5541, 0xDFFB, 0x5555, 0xDFFC, 0x553F, 0xDFFD, 0x5550, 0xDFFE, 0x553C, + 0xE040, 0x90C2, 0xE041, 0x90C3, 0xE042, 0x90C6, 0xE043, 0x90C8, 0xE044, 0x90C9, 0xE045, 0x90CB, 0xE046, 0x90CC, 0xE047, 0x90CD, + 0xE048, 0x90D2, 0xE049, 0x90D4, 0xE04A, 0x90D5, 0xE04B, 0x90D6, 0xE04C, 0x90D8, 0xE04D, 0x90D9, 0xE04E, 0x90DA, 0xE04F, 0x90DE, + 0xE050, 0x90DF, 0xE051, 0x90E0, 0xE052, 0x90E3, 0xE053, 0x90E4, 0xE054, 0x90E5, 0xE055, 0x90E9, 0xE056, 0x90EA, 0xE057, 0x90EC, + 0xE058, 0x90EE, 0xE059, 0x90F0, 0xE05A, 0x90F1, 0xE05B, 0x90F2, 0xE05C, 0x90F3, 0xE05D, 0x90F5, 0xE05E, 0x90F6, 0xE05F, 0x90F7, + 0xE060, 0x90F9, 0xE061, 0x90FA, 0xE062, 0x90FB, 0xE063, 0x90FC, 0xE064, 0x90FF, 0xE065, 0x9100, 0xE066, 0x9101, 0xE067, 0x9103, + 0xE068, 0x9105, 0xE069, 0x9106, 0xE06A, 0x9107, 0xE06B, 0x9108, 0xE06C, 0x9109, 0xE06D, 0x910A, 0xE06E, 0x910B, 0xE06F, 0x910C, + 0xE070, 0x910D, 0xE071, 0x910E, 0xE072, 0x910F, 0xE073, 0x9110, 0xE074, 0x9111, 0xE075, 0x9112, 0xE076, 0x9113, 0xE077, 0x9114, + 0xE078, 0x9115, 0xE079, 0x9116, 0xE07A, 0x9117, 0xE07B, 0x9118, 0xE07C, 0x911A, 0xE07D, 0x911B, 0xE07E, 0x911C, 0xE080, 0x911D, + 0xE081, 0x911F, 0xE082, 0x9120, 0xE083, 0x9121, 0xE084, 0x9124, 0xE085, 0x9125, 0xE086, 0x9126, 0xE087, 0x9127, 0xE088, 0x9128, + 0xE089, 0x9129, 0xE08A, 0x912A, 0xE08B, 0x912B, 0xE08C, 0x912C, 0xE08D, 0x912D, 0xE08E, 0x912E, 0xE08F, 0x9130, 0xE090, 0x9132, + 0xE091, 0x9133, 0xE092, 0x9134, 0xE093, 0x9135, 0xE094, 0x9136, 0xE095, 0x9137, 0xE096, 0x9138, 0xE097, 0x913A, 0xE098, 0x913B, + 0xE099, 0x913C, 0xE09A, 0x913D, 0xE09B, 0x913E, 0xE09C, 0x913F, 0xE09D, 0x9140, 0xE09E, 0x9141, 0xE09F, 0x9142, 0xE0A0, 0x9144, + 0xE0A1, 0x5537, 0xE0A2, 0x5556, 0xE0A3, 0x5575, 0xE0A4, 0x5576, 0xE0A5, 0x5577, 0xE0A6, 0x5533, 0xE0A7, 0x5530, 0xE0A8, 0x555C, + 0xE0A9, 0x558B, 0xE0AA, 0x55D2, 0xE0AB, 0x5583, 0xE0AC, 0x55B1, 0xE0AD, 0x55B9, 0xE0AE, 0x5588, 0xE0AF, 0x5581, 0xE0B0, 0x559F, + 0xE0B1, 0x557E, 0xE0B2, 0x55D6, 0xE0B3, 0x5591, 0xE0B4, 0x557B, 0xE0B5, 0x55DF, 0xE0B6, 0x55BD, 0xE0B7, 0x55BE, 0xE0B8, 0x5594, + 0xE0B9, 0x5599, 0xE0BA, 0x55EA, 0xE0BB, 0x55F7, 0xE0BC, 0x55C9, 0xE0BD, 0x561F, 0xE0BE, 0x55D1, 0xE0BF, 0x55EB, 0xE0C0, 0x55EC, + 0xE0C1, 0x55D4, 0xE0C2, 0x55E6, 0xE0C3, 0x55DD, 0xE0C4, 0x55C4, 0xE0C5, 0x55EF, 0xE0C6, 0x55E5, 0xE0C7, 0x55F2, 0xE0C8, 0x55F3, + 0xE0C9, 0x55CC, 0xE0CA, 0x55CD, 0xE0CB, 0x55E8, 0xE0CC, 0x55F5, 0xE0CD, 0x55E4, 0xE0CE, 0x8F94, 0xE0CF, 0x561E, 0xE0D0, 0x5608, + 0xE0D1, 0x560C, 0xE0D2, 0x5601, 0xE0D3, 0x5624, 0xE0D4, 0x5623, 0xE0D5, 0x55FE, 0xE0D6, 0x5600, 0xE0D7, 0x5627, 0xE0D8, 0x562D, + 0xE0D9, 0x5658, 0xE0DA, 0x5639, 0xE0DB, 0x5657, 0xE0DC, 0x562C, 0xE0DD, 0x564D, 0xE0DE, 0x5662, 0xE0DF, 0x5659, 0xE0E0, 0x565C, + 0xE0E1, 0x564C, 0xE0E2, 0x5654, 0xE0E3, 0x5686, 0xE0E4, 0x5664, 0xE0E5, 0x5671, 0xE0E6, 0x566B, 0xE0E7, 0x567B, 0xE0E8, 0x567C, + 0xE0E9, 0x5685, 0xE0EA, 0x5693, 0xE0EB, 0x56AF, 0xE0EC, 0x56D4, 0xE0ED, 0x56D7, 0xE0EE, 0x56DD, 0xE0EF, 0x56E1, 0xE0F0, 0x56F5, + 0xE0F1, 0x56EB, 0xE0F2, 0x56F9, 0xE0F3, 0x56FF, 0xE0F4, 0x5704, 0xE0F5, 0x570A, 0xE0F6, 0x5709, 0xE0F7, 0x571C, 0xE0F8, 0x5E0F, + 0xE0F9, 0x5E19, 0xE0FA, 0x5E14, 0xE0FB, 0x5E11, 0xE0FC, 0x5E31, 0xE0FD, 0x5E3B, 0xE0FE, 0x5E3C, 0xE140, 0x9145, 0xE141, 0x9147, + 0xE142, 0x9148, 0xE143, 0x9151, 0xE144, 0x9153, 0xE145, 0x9154, 0xE146, 0x9155, 0xE147, 0x9156, 0xE148, 0x9158, 0xE149, 0x9159, + 0xE14A, 0x915B, 0xE14B, 0x915C, 0xE14C, 0x915F, 0xE14D, 0x9160, 0xE14E, 0x9166, 0xE14F, 0x9167, 0xE150, 0x9168, 0xE151, 0x916B, + 0xE152, 0x916D, 0xE153, 0x9173, 0xE154, 0x917A, 0xE155, 0x917B, 0xE156, 0x917C, 0xE157, 0x9180, 0xE158, 0x9181, 0xE159, 0x9182, + 0xE15A, 0x9183, 0xE15B, 0x9184, 0xE15C, 0x9186, 0xE15D, 0x9188, 0xE15E, 0x918A, 0xE15F, 0x918E, 0xE160, 0x918F, 0xE161, 0x9193, + 0xE162, 0x9194, 0xE163, 0x9195, 0xE164, 0x9196, 0xE165, 0x9197, 0xE166, 0x9198, 0xE167, 0x9199, 0xE168, 0x919C, 0xE169, 0x919D, + 0xE16A, 0x919E, 0xE16B, 0x919F, 0xE16C, 0x91A0, 0xE16D, 0x91A1, 0xE16E, 0x91A4, 0xE16F, 0x91A5, 0xE170, 0x91A6, 0xE171, 0x91A7, + 0xE172, 0x91A8, 0xE173, 0x91A9, 0xE174, 0x91AB, 0xE175, 0x91AC, 0xE176, 0x91B0, 0xE177, 0x91B1, 0xE178, 0x91B2, 0xE179, 0x91B3, + 0xE17A, 0x91B6, 0xE17B, 0x91B7, 0xE17C, 0x91B8, 0xE17D, 0x91B9, 0xE17E, 0x91BB, 0xE180, 0x91BC, 0xE181, 0x91BD, 0xE182, 0x91BE, + 0xE183, 0x91BF, 0xE184, 0x91C0, 0xE185, 0x91C1, 0xE186, 0x91C2, 0xE187, 0x91C3, 0xE188, 0x91C4, 0xE189, 0x91C5, 0xE18A, 0x91C6, + 0xE18B, 0x91C8, 0xE18C, 0x91CB, 0xE18D, 0x91D0, 0xE18E, 0x91D2, 0xE18F, 0x91D3, 0xE190, 0x91D4, 0xE191, 0x91D5, 0xE192, 0x91D6, + 0xE193, 0x91D7, 0xE194, 0x91D8, 0xE195, 0x91D9, 0xE196, 0x91DA, 0xE197, 0x91DB, 0xE198, 0x91DD, 0xE199, 0x91DE, 0xE19A, 0x91DF, + 0xE19B, 0x91E0, 0xE19C, 0x91E1, 0xE19D, 0x91E2, 0xE19E, 0x91E3, 0xE19F, 0x91E4, 0xE1A0, 0x91E5, 0xE1A1, 0x5E37, 0xE1A2, 0x5E44, + 0xE1A3, 0x5E54, 0xE1A4, 0x5E5B, 0xE1A5, 0x5E5E, 0xE1A6, 0x5E61, 0xE1A7, 0x5C8C, 0xE1A8, 0x5C7A, 0xE1A9, 0x5C8D, 0xE1AA, 0x5C90, + 0xE1AB, 0x5C96, 0xE1AC, 0x5C88, 0xE1AD, 0x5C98, 0xE1AE, 0x5C99, 0xE1AF, 0x5C91, 0xE1B0, 0x5C9A, 0xE1B1, 0x5C9C, 0xE1B2, 0x5CB5, + 0xE1B3, 0x5CA2, 0xE1B4, 0x5CBD, 0xE1B5, 0x5CAC, 0xE1B6, 0x5CAB, 0xE1B7, 0x5CB1, 0xE1B8, 0x5CA3, 0xE1B9, 0x5CC1, 0xE1BA, 0x5CB7, + 0xE1BB, 0x5CC4, 0xE1BC, 0x5CD2, 0xE1BD, 0x5CE4, 0xE1BE, 0x5CCB, 0xE1BF, 0x5CE5, 0xE1C0, 0x5D02, 0xE1C1, 0x5D03, 0xE1C2, 0x5D27, + 0xE1C3, 0x5D26, 0xE1C4, 0x5D2E, 0xE1C5, 0x5D24, 0xE1C6, 0x5D1E, 0xE1C7, 0x5D06, 0xE1C8, 0x5D1B, 0xE1C9, 0x5D58, 0xE1CA, 0x5D3E, + 0xE1CB, 0x5D34, 0xE1CC, 0x5D3D, 0xE1CD, 0x5D6C, 0xE1CE, 0x5D5B, 0xE1CF, 0x5D6F, 0xE1D0, 0x5D5D, 0xE1D1, 0x5D6B, 0xE1D2, 0x5D4B, + 0xE1D3, 0x5D4A, 0xE1D4, 0x5D69, 0xE1D5, 0x5D74, 0xE1D6, 0x5D82, 0xE1D7, 0x5D99, 0xE1D8, 0x5D9D, 0xE1D9, 0x8C73, 0xE1DA, 0x5DB7, + 0xE1DB, 0x5DC5, 0xE1DC, 0x5F73, 0xE1DD, 0x5F77, 0xE1DE, 0x5F82, 0xE1DF, 0x5F87, 0xE1E0, 0x5F89, 0xE1E1, 0x5F8C, 0xE1E2, 0x5F95, + 0xE1E3, 0x5F99, 0xE1E4, 0x5F9C, 0xE1E5, 0x5FA8, 0xE1E6, 0x5FAD, 0xE1E7, 0x5FB5, 0xE1E8, 0x5FBC, 0xE1E9, 0x8862, 0xE1EA, 0x5F61, + 0xE1EB, 0x72AD, 0xE1EC, 0x72B0, 0xE1ED, 0x72B4, 0xE1EE, 0x72B7, 0xE1EF, 0x72B8, 0xE1F0, 0x72C3, 0xE1F1, 0x72C1, 0xE1F2, 0x72CE, + 0xE1F3, 0x72CD, 0xE1F4, 0x72D2, 0xE1F5, 0x72E8, 0xE1F6, 0x72EF, 0xE1F7, 0x72E9, 0xE1F8, 0x72F2, 0xE1F9, 0x72F4, 0xE1FA, 0x72F7, + 0xE1FB, 0x7301, 0xE1FC, 0x72F3, 0xE1FD, 0x7303, 0xE1FE, 0x72FA, 0xE240, 0x91E6, 0xE241, 0x91E7, 0xE242, 0x91E8, 0xE243, 0x91E9, + 0xE244, 0x91EA, 0xE245, 0x91EB, 0xE246, 0x91EC, 0xE247, 0x91ED, 0xE248, 0x91EE, 0xE249, 0x91EF, 0xE24A, 0x91F0, 0xE24B, 0x91F1, + 0xE24C, 0x91F2, 0xE24D, 0x91F3, 0xE24E, 0x91F4, 0xE24F, 0x91F5, 0xE250, 0x91F6, 0xE251, 0x91F7, 0xE252, 0x91F8, 0xE253, 0x91F9, + 0xE254, 0x91FA, 0xE255, 0x91FB, 0xE256, 0x91FC, 0xE257, 0x91FD, 0xE258, 0x91FE, 0xE259, 0x91FF, 0xE25A, 0x9200, 0xE25B, 0x9201, + 0xE25C, 0x9202, 0xE25D, 0x9203, 0xE25E, 0x9204, 0xE25F, 0x9205, 0xE260, 0x9206, 0xE261, 0x9207, 0xE262, 0x9208, 0xE263, 0x9209, + 0xE264, 0x920A, 0xE265, 0x920B, 0xE266, 0x920C, 0xE267, 0x920D, 0xE268, 0x920E, 0xE269, 0x920F, 0xE26A, 0x9210, 0xE26B, 0x9211, + 0xE26C, 0x9212, 0xE26D, 0x9213, 0xE26E, 0x9214, 0xE26F, 0x9215, 0xE270, 0x9216, 0xE271, 0x9217, 0xE272, 0x9218, 0xE273, 0x9219, + 0xE274, 0x921A, 0xE275, 0x921B, 0xE276, 0x921C, 0xE277, 0x921D, 0xE278, 0x921E, 0xE279, 0x921F, 0xE27A, 0x9220, 0xE27B, 0x9221, + 0xE27C, 0x9222, 0xE27D, 0x9223, 0xE27E, 0x9224, 0xE280, 0x9225, 0xE281, 0x9226, 0xE282, 0x9227, 0xE283, 0x9228, 0xE284, 0x9229, + 0xE285, 0x922A, 0xE286, 0x922B, 0xE287, 0x922C, 0xE288, 0x922D, 0xE289, 0x922E, 0xE28A, 0x922F, 0xE28B, 0x9230, 0xE28C, 0x9231, + 0xE28D, 0x9232, 0xE28E, 0x9233, 0xE28F, 0x9234, 0xE290, 0x9235, 0xE291, 0x9236, 0xE292, 0x9237, 0xE293, 0x9238, 0xE294, 0x9239, + 0xE295, 0x923A, 0xE296, 0x923B, 0xE297, 0x923C, 0xE298, 0x923D, 0xE299, 0x923E, 0xE29A, 0x923F, 0xE29B, 0x9240, 0xE29C, 0x9241, + 0xE29D, 0x9242, 0xE29E, 0x9243, 0xE29F, 0x9244, 0xE2A0, 0x9245, 0xE2A1, 0x72FB, 0xE2A2, 0x7317, 0xE2A3, 0x7313, 0xE2A4, 0x7321, + 0xE2A5, 0x730A, 0xE2A6, 0x731E, 0xE2A7, 0x731D, 0xE2A8, 0x7315, 0xE2A9, 0x7322, 0xE2AA, 0x7339, 0xE2AB, 0x7325, 0xE2AC, 0x732C, + 0xE2AD, 0x7338, 0xE2AE, 0x7331, 0xE2AF, 0x7350, 0xE2B0, 0x734D, 0xE2B1, 0x7357, 0xE2B2, 0x7360, 0xE2B3, 0x736C, 0xE2B4, 0x736F, + 0xE2B5, 0x737E, 0xE2B6, 0x821B, 0xE2B7, 0x5925, 0xE2B8, 0x98E7, 0xE2B9, 0x5924, 0xE2BA, 0x5902, 0xE2BB, 0x9963, 0xE2BC, 0x9967, + 0xE2BD, 0x9968, 0xE2BE, 0x9969, 0xE2BF, 0x996A, 0xE2C0, 0x996B, 0xE2C1, 0x996C, 0xE2C2, 0x9974, 0xE2C3, 0x9977, 0xE2C4, 0x997D, + 0xE2C5, 0x9980, 0xE2C6, 0x9984, 0xE2C7, 0x9987, 0xE2C8, 0x998A, 0xE2C9, 0x998D, 0xE2CA, 0x9990, 0xE2CB, 0x9991, 0xE2CC, 0x9993, + 0xE2CD, 0x9994, 0xE2CE, 0x9995, 0xE2CF, 0x5E80, 0xE2D0, 0x5E91, 0xE2D1, 0x5E8B, 0xE2D2, 0x5E96, 0xE2D3, 0x5EA5, 0xE2D4, 0x5EA0, + 0xE2D5, 0x5EB9, 0xE2D6, 0x5EB5, 0xE2D7, 0x5EBE, 0xE2D8, 0x5EB3, 0xE2D9, 0x8D53, 0xE2DA, 0x5ED2, 0xE2DB, 0x5ED1, 0xE2DC, 0x5EDB, + 0xE2DD, 0x5EE8, 0xE2DE, 0x5EEA, 0xE2DF, 0x81BA, 0xE2E0, 0x5FC4, 0xE2E1, 0x5FC9, 0xE2E2, 0x5FD6, 0xE2E3, 0x5FCF, 0xE2E4, 0x6003, + 0xE2E5, 0x5FEE, 0xE2E6, 0x6004, 0xE2E7, 0x5FE1, 0xE2E8, 0x5FE4, 0xE2E9, 0x5FFE, 0xE2EA, 0x6005, 0xE2EB, 0x6006, 0xE2EC, 0x5FEA, + 0xE2ED, 0x5FED, 0xE2EE, 0x5FF8, 0xE2EF, 0x6019, 0xE2F0, 0x6035, 0xE2F1, 0x6026, 0xE2F2, 0x601B, 0xE2F3, 0x600F, 0xE2F4, 0x600D, + 0xE2F5, 0x6029, 0xE2F6, 0x602B, 0xE2F7, 0x600A, 0xE2F8, 0x603F, 0xE2F9, 0x6021, 0xE2FA, 0x6078, 0xE2FB, 0x6079, 0xE2FC, 0x607B, + 0xE2FD, 0x607A, 0xE2FE, 0x6042, 0xE340, 0x9246, 0xE341, 0x9247, 0xE342, 0x9248, 0xE343, 0x9249, 0xE344, 0x924A, 0xE345, 0x924B, + 0xE346, 0x924C, 0xE347, 0x924D, 0xE348, 0x924E, 0xE349, 0x924F, 0xE34A, 0x9250, 0xE34B, 0x9251, 0xE34C, 0x9252, 0xE34D, 0x9253, + 0xE34E, 0x9254, 0xE34F, 0x9255, 0xE350, 0x9256, 0xE351, 0x9257, 0xE352, 0x9258, 0xE353, 0x9259, 0xE354, 0x925A, 0xE355, 0x925B, + 0xE356, 0x925C, 0xE357, 0x925D, 0xE358, 0x925E, 0xE359, 0x925F, 0xE35A, 0x9260, 0xE35B, 0x9261, 0xE35C, 0x9262, 0xE35D, 0x9263, + 0xE35E, 0x9264, 0xE35F, 0x9265, 0xE360, 0x9266, 0xE361, 0x9267, 0xE362, 0x9268, 0xE363, 0x9269, 0xE364, 0x926A, 0xE365, 0x926B, + 0xE366, 0x926C, 0xE367, 0x926D, 0xE368, 0x926E, 0xE369, 0x926F, 0xE36A, 0x9270, 0xE36B, 0x9271, 0xE36C, 0x9272, 0xE36D, 0x9273, + 0xE36E, 0x9275, 0xE36F, 0x9276, 0xE370, 0x9277, 0xE371, 0x9278, 0xE372, 0x9279, 0xE373, 0x927A, 0xE374, 0x927B, 0xE375, 0x927C, + 0xE376, 0x927D, 0xE377, 0x927E, 0xE378, 0x927F, 0xE379, 0x9280, 0xE37A, 0x9281, 0xE37B, 0x9282, 0xE37C, 0x9283, 0xE37D, 0x9284, + 0xE37E, 0x9285, 0xE380, 0x9286, 0xE381, 0x9287, 0xE382, 0x9288, 0xE383, 0x9289, 0xE384, 0x928A, 0xE385, 0x928B, 0xE386, 0x928C, + 0xE387, 0x928D, 0xE388, 0x928F, 0xE389, 0x9290, 0xE38A, 0x9291, 0xE38B, 0x9292, 0xE38C, 0x9293, 0xE38D, 0x9294, 0xE38E, 0x9295, + 0xE38F, 0x9296, 0xE390, 0x9297, 0xE391, 0x9298, 0xE392, 0x9299, 0xE393, 0x929A, 0xE394, 0x929B, 0xE395, 0x929C, 0xE396, 0x929D, + 0xE397, 0x929E, 0xE398, 0x929F, 0xE399, 0x92A0, 0xE39A, 0x92A1, 0xE39B, 0x92A2, 0xE39C, 0x92A3, 0xE39D, 0x92A4, 0xE39E, 0x92A5, + 0xE39F, 0x92A6, 0xE3A0, 0x92A7, 0xE3A1, 0x606A, 0xE3A2, 0x607D, 0xE3A3, 0x6096, 0xE3A4, 0x609A, 0xE3A5, 0x60AD, 0xE3A6, 0x609D, + 0xE3A7, 0x6083, 0xE3A8, 0x6092, 0xE3A9, 0x608C, 0xE3AA, 0x609B, 0xE3AB, 0x60EC, 0xE3AC, 0x60BB, 0xE3AD, 0x60B1, 0xE3AE, 0x60DD, + 0xE3AF, 0x60D8, 0xE3B0, 0x60C6, 0xE3B1, 0x60DA, 0xE3B2, 0x60B4, 0xE3B3, 0x6120, 0xE3B4, 0x6126, 0xE3B5, 0x6115, 0xE3B6, 0x6123, + 0xE3B7, 0x60F4, 0xE3B8, 0x6100, 0xE3B9, 0x610E, 0xE3BA, 0x612B, 0xE3BB, 0x614A, 0xE3BC, 0x6175, 0xE3BD, 0x61AC, 0xE3BE, 0x6194, + 0xE3BF, 0x61A7, 0xE3C0, 0x61B7, 0xE3C1, 0x61D4, 0xE3C2, 0x61F5, 0xE3C3, 0x5FDD, 0xE3C4, 0x96B3, 0xE3C5, 0x95E9, 0xE3C6, 0x95EB, + 0xE3C7, 0x95F1, 0xE3C8, 0x95F3, 0xE3C9, 0x95F5, 0xE3CA, 0x95F6, 0xE3CB, 0x95FC, 0xE3CC, 0x95FE, 0xE3CD, 0x9603, 0xE3CE, 0x9604, + 0xE3CF, 0x9606, 0xE3D0, 0x9608, 0xE3D1, 0x960A, 0xE3D2, 0x960B, 0xE3D3, 0x960C, 0xE3D4, 0x960D, 0xE3D5, 0x960F, 0xE3D6, 0x9612, + 0xE3D7, 0x9615, 0xE3D8, 0x9616, 0xE3D9, 0x9617, 0xE3DA, 0x9619, 0xE3DB, 0x961A, 0xE3DC, 0x4E2C, 0xE3DD, 0x723F, 0xE3DE, 0x6215, + 0xE3DF, 0x6C35, 0xE3E0, 0x6C54, 0xE3E1, 0x6C5C, 0xE3E2, 0x6C4A, 0xE3E3, 0x6CA3, 0xE3E4, 0x6C85, 0xE3E5, 0x6C90, 0xE3E6, 0x6C94, + 0xE3E7, 0x6C8C, 0xE3E8, 0x6C68, 0xE3E9, 0x6C69, 0xE3EA, 0x6C74, 0xE3EB, 0x6C76, 0xE3EC, 0x6C86, 0xE3ED, 0x6CA9, 0xE3EE, 0x6CD0, + 0xE3EF, 0x6CD4, 0xE3F0, 0x6CAD, 0xE3F1, 0x6CF7, 0xE3F2, 0x6CF8, 0xE3F3, 0x6CF1, 0xE3F4, 0x6CD7, 0xE3F5, 0x6CB2, 0xE3F6, 0x6CE0, + 0xE3F7, 0x6CD6, 0xE3F8, 0x6CFA, 0xE3F9, 0x6CEB, 0xE3FA, 0x6CEE, 0xE3FB, 0x6CB1, 0xE3FC, 0x6CD3, 0xE3FD, 0x6CEF, 0xE3FE, 0x6CFE, + 0xE440, 0x92A8, 0xE441, 0x92A9, 0xE442, 0x92AA, 0xE443, 0x92AB, 0xE444, 0x92AC, 0xE445, 0x92AD, 0xE446, 0x92AF, 0xE447, 0x92B0, + 0xE448, 0x92B1, 0xE449, 0x92B2, 0xE44A, 0x92B3, 0xE44B, 0x92B4, 0xE44C, 0x92B5, 0xE44D, 0x92B6, 0xE44E, 0x92B7, 0xE44F, 0x92B8, + 0xE450, 0x92B9, 0xE451, 0x92BA, 0xE452, 0x92BB, 0xE453, 0x92BC, 0xE454, 0x92BD, 0xE455, 0x92BE, 0xE456, 0x92BF, 0xE457, 0x92C0, + 0xE458, 0x92C1, 0xE459, 0x92C2, 0xE45A, 0x92C3, 0xE45B, 0x92C4, 0xE45C, 0x92C5, 0xE45D, 0x92C6, 0xE45E, 0x92C7, 0xE45F, 0x92C9, + 0xE460, 0x92CA, 0xE461, 0x92CB, 0xE462, 0x92CC, 0xE463, 0x92CD, 0xE464, 0x92CE, 0xE465, 0x92CF, 0xE466, 0x92D0, 0xE467, 0x92D1, + 0xE468, 0x92D2, 0xE469, 0x92D3, 0xE46A, 0x92D4, 0xE46B, 0x92D5, 0xE46C, 0x92D6, 0xE46D, 0x92D7, 0xE46E, 0x92D8, 0xE46F, 0x92D9, + 0xE470, 0x92DA, 0xE471, 0x92DB, 0xE472, 0x92DC, 0xE473, 0x92DD, 0xE474, 0x92DE, 0xE475, 0x92DF, 0xE476, 0x92E0, 0xE477, 0x92E1, + 0xE478, 0x92E2, 0xE479, 0x92E3, 0xE47A, 0x92E4, 0xE47B, 0x92E5, 0xE47C, 0x92E6, 0xE47D, 0x92E7, 0xE47E, 0x92E8, 0xE480, 0x92E9, + 0xE481, 0x92EA, 0xE482, 0x92EB, 0xE483, 0x92EC, 0xE484, 0x92ED, 0xE485, 0x92EE, 0xE486, 0x92EF, 0xE487, 0x92F0, 0xE488, 0x92F1, + 0xE489, 0x92F2, 0xE48A, 0x92F3, 0xE48B, 0x92F4, 0xE48C, 0x92F5, 0xE48D, 0x92F6, 0xE48E, 0x92F7, 0xE48F, 0x92F8, 0xE490, 0x92F9, + 0xE491, 0x92FA, 0xE492, 0x92FB, 0xE493, 0x92FC, 0xE494, 0x92FD, 0xE495, 0x92FE, 0xE496, 0x92FF, 0xE497, 0x9300, 0xE498, 0x9301, + 0xE499, 0x9302, 0xE49A, 0x9303, 0xE49B, 0x9304, 0xE49C, 0x9305, 0xE49D, 0x9306, 0xE49E, 0x9307, 0xE49F, 0x9308, 0xE4A0, 0x9309, + 0xE4A1, 0x6D39, 0xE4A2, 0x6D27, 0xE4A3, 0x6D0C, 0xE4A4, 0x6D43, 0xE4A5, 0x6D48, 0xE4A6, 0x6D07, 0xE4A7, 0x6D04, 0xE4A8, 0x6D19, + 0xE4A9, 0x6D0E, 0xE4AA, 0x6D2B, 0xE4AB, 0x6D4D, 0xE4AC, 0x6D2E, 0xE4AD, 0x6D35, 0xE4AE, 0x6D1A, 0xE4AF, 0x6D4F, 0xE4B0, 0x6D52, + 0xE4B1, 0x6D54, 0xE4B2, 0x6D33, 0xE4B3, 0x6D91, 0xE4B4, 0x6D6F, 0xE4B5, 0x6D9E, 0xE4B6, 0x6DA0, 0xE4B7, 0x6D5E, 0xE4B8, 0x6D93, + 0xE4B9, 0x6D94, 0xE4BA, 0x6D5C, 0xE4BB, 0x6D60, 0xE4BC, 0x6D7C, 0xE4BD, 0x6D63, 0xE4BE, 0x6E1A, 0xE4BF, 0x6DC7, 0xE4C0, 0x6DC5, + 0xE4C1, 0x6DDE, 0xE4C2, 0x6E0E, 0xE4C3, 0x6DBF, 0xE4C4, 0x6DE0, 0xE4C5, 0x6E11, 0xE4C6, 0x6DE6, 0xE4C7, 0x6DDD, 0xE4C8, 0x6DD9, + 0xE4C9, 0x6E16, 0xE4CA, 0x6DAB, 0xE4CB, 0x6E0C, 0xE4CC, 0x6DAE, 0xE4CD, 0x6E2B, 0xE4CE, 0x6E6E, 0xE4CF, 0x6E4E, 0xE4D0, 0x6E6B, + 0xE4D1, 0x6EB2, 0xE4D2, 0x6E5F, 0xE4D3, 0x6E86, 0xE4D4, 0x6E53, 0xE4D5, 0x6E54, 0xE4D6, 0x6E32, 0xE4D7, 0x6E25, 0xE4D8, 0x6E44, + 0xE4D9, 0x6EDF, 0xE4DA, 0x6EB1, 0xE4DB, 0x6E98, 0xE4DC, 0x6EE0, 0xE4DD, 0x6F2D, 0xE4DE, 0x6EE2, 0xE4DF, 0x6EA5, 0xE4E0, 0x6EA7, + 0xE4E1, 0x6EBD, 0xE4E2, 0x6EBB, 0xE4E3, 0x6EB7, 0xE4E4, 0x6ED7, 0xE4E5, 0x6EB4, 0xE4E6, 0x6ECF, 0xE4E7, 0x6E8F, 0xE4E8, 0x6EC2, + 0xE4E9, 0x6E9F, 0xE4EA, 0x6F62, 0xE4EB, 0x6F46, 0xE4EC, 0x6F47, 0xE4ED, 0x6F24, 0xE4EE, 0x6F15, 0xE4EF, 0x6EF9, 0xE4F0, 0x6F2F, + 0xE4F1, 0x6F36, 0xE4F2, 0x6F4B, 0xE4F3, 0x6F74, 0xE4F4, 0x6F2A, 0xE4F5, 0x6F09, 0xE4F6, 0x6F29, 0xE4F7, 0x6F89, 0xE4F8, 0x6F8D, + 0xE4F9, 0x6F8C, 0xE4FA, 0x6F78, 0xE4FB, 0x6F72, 0xE4FC, 0x6F7C, 0xE4FD, 0x6F7A, 0xE4FE, 0x6FD1, 0xE540, 0x930A, 0xE541, 0x930B, + 0xE542, 0x930C, 0xE543, 0x930D, 0xE544, 0x930E, 0xE545, 0x930F, 0xE546, 0x9310, 0xE547, 0x9311, 0xE548, 0x9312, 0xE549, 0x9313, + 0xE54A, 0x9314, 0xE54B, 0x9315, 0xE54C, 0x9316, 0xE54D, 0x9317, 0xE54E, 0x9318, 0xE54F, 0x9319, 0xE550, 0x931A, 0xE551, 0x931B, + 0xE552, 0x931C, 0xE553, 0x931D, 0xE554, 0x931E, 0xE555, 0x931F, 0xE556, 0x9320, 0xE557, 0x9321, 0xE558, 0x9322, 0xE559, 0x9323, + 0xE55A, 0x9324, 0xE55B, 0x9325, 0xE55C, 0x9326, 0xE55D, 0x9327, 0xE55E, 0x9328, 0xE55F, 0x9329, 0xE560, 0x932A, 0xE561, 0x932B, + 0xE562, 0x932C, 0xE563, 0x932D, 0xE564, 0x932E, 0xE565, 0x932F, 0xE566, 0x9330, 0xE567, 0x9331, 0xE568, 0x9332, 0xE569, 0x9333, + 0xE56A, 0x9334, 0xE56B, 0x9335, 0xE56C, 0x9336, 0xE56D, 0x9337, 0xE56E, 0x9338, 0xE56F, 0x9339, 0xE570, 0x933A, 0xE571, 0x933B, + 0xE572, 0x933C, 0xE573, 0x933D, 0xE574, 0x933F, 0xE575, 0x9340, 0xE576, 0x9341, 0xE577, 0x9342, 0xE578, 0x9343, 0xE579, 0x9344, + 0xE57A, 0x9345, 0xE57B, 0x9346, 0xE57C, 0x9347, 0xE57D, 0x9348, 0xE57E, 0x9349, 0xE580, 0x934A, 0xE581, 0x934B, 0xE582, 0x934C, + 0xE583, 0x934D, 0xE584, 0x934E, 0xE585, 0x934F, 0xE586, 0x9350, 0xE587, 0x9351, 0xE588, 0x9352, 0xE589, 0x9353, 0xE58A, 0x9354, + 0xE58B, 0x9355, 0xE58C, 0x9356, 0xE58D, 0x9357, 0xE58E, 0x9358, 0xE58F, 0x9359, 0xE590, 0x935A, 0xE591, 0x935B, 0xE592, 0x935C, + 0xE593, 0x935D, 0xE594, 0x935E, 0xE595, 0x935F, 0xE596, 0x9360, 0xE597, 0x9361, 0xE598, 0x9362, 0xE599, 0x9363, 0xE59A, 0x9364, + 0xE59B, 0x9365, 0xE59C, 0x9366, 0xE59D, 0x9367, 0xE59E, 0x9368, 0xE59F, 0x9369, 0xE5A0, 0x936B, 0xE5A1, 0x6FC9, 0xE5A2, 0x6FA7, + 0xE5A3, 0x6FB9, 0xE5A4, 0x6FB6, 0xE5A5, 0x6FC2, 0xE5A6, 0x6FE1, 0xE5A7, 0x6FEE, 0xE5A8, 0x6FDE, 0xE5A9, 0x6FE0, 0xE5AA, 0x6FEF, + 0xE5AB, 0x701A, 0xE5AC, 0x7023, 0xE5AD, 0x701B, 0xE5AE, 0x7039, 0xE5AF, 0x7035, 0xE5B0, 0x704F, 0xE5B1, 0x705E, 0xE5B2, 0x5B80, + 0xE5B3, 0x5B84, 0xE5B4, 0x5B95, 0xE5B5, 0x5B93, 0xE5B6, 0x5BA5, 0xE5B7, 0x5BB8, 0xE5B8, 0x752F, 0xE5B9, 0x9A9E, 0xE5BA, 0x6434, + 0xE5BB, 0x5BE4, 0xE5BC, 0x5BEE, 0xE5BD, 0x8930, 0xE5BE, 0x5BF0, 0xE5BF, 0x8E47, 0xE5C0, 0x8B07, 0xE5C1, 0x8FB6, 0xE5C2, 0x8FD3, + 0xE5C3, 0x8FD5, 0xE5C4, 0x8FE5, 0xE5C5, 0x8FEE, 0xE5C6, 0x8FE4, 0xE5C7, 0x8FE9, 0xE5C8, 0x8FE6, 0xE5C9, 0x8FF3, 0xE5CA, 0x8FE8, + 0xE5CB, 0x9005, 0xE5CC, 0x9004, 0xE5CD, 0x900B, 0xE5CE, 0x9026, 0xE5CF, 0x9011, 0xE5D0, 0x900D, 0xE5D1, 0x9016, 0xE5D2, 0x9021, + 0xE5D3, 0x9035, 0xE5D4, 0x9036, 0xE5D5, 0x902D, 0xE5D6, 0x902F, 0xE5D7, 0x9044, 0xE5D8, 0x9051, 0xE5D9, 0x9052, 0xE5DA, 0x9050, + 0xE5DB, 0x9068, 0xE5DC, 0x9058, 0xE5DD, 0x9062, 0xE5DE, 0x905B, 0xE5DF, 0x66B9, 0xE5E0, 0x9074, 0xE5E1, 0x907D, 0xE5E2, 0x9082, + 0xE5E3, 0x9088, 0xE5E4, 0x9083, 0xE5E5, 0x908B, 0xE5E6, 0x5F50, 0xE5E7, 0x5F57, 0xE5E8, 0x5F56, 0xE5E9, 0x5F58, 0xE5EA, 0x5C3B, + 0xE5EB, 0x54AB, 0xE5EC, 0x5C50, 0xE5ED, 0x5C59, 0xE5EE, 0x5B71, 0xE5EF, 0x5C63, 0xE5F0, 0x5C66, 0xE5F1, 0x7FBC, 0xE5F2, 0x5F2A, + 0xE5F3, 0x5F29, 0xE5F4, 0x5F2D, 0xE5F5, 0x8274, 0xE5F6, 0x5F3C, 0xE5F7, 0x9B3B, 0xE5F8, 0x5C6E, 0xE5F9, 0x5981, 0xE5FA, 0x5983, + 0xE5FB, 0x598D, 0xE5FC, 0x59A9, 0xE5FD, 0x59AA, 0xE5FE, 0x59A3, 0xE640, 0x936C, 0xE641, 0x936D, 0xE642, 0x936E, 0xE643, 0x936F, + 0xE644, 0x9370, 0xE645, 0x9371, 0xE646, 0x9372, 0xE647, 0x9373, 0xE648, 0x9374, 0xE649, 0x9375, 0xE64A, 0x9376, 0xE64B, 0x9377, + 0xE64C, 0x9378, 0xE64D, 0x9379, 0xE64E, 0x937A, 0xE64F, 0x937B, 0xE650, 0x937C, 0xE651, 0x937D, 0xE652, 0x937E, 0xE653, 0x937F, + 0xE654, 0x9380, 0xE655, 0x9381, 0xE656, 0x9382, 0xE657, 0x9383, 0xE658, 0x9384, 0xE659, 0x9385, 0xE65A, 0x9386, 0xE65B, 0x9387, + 0xE65C, 0x9388, 0xE65D, 0x9389, 0xE65E, 0x938A, 0xE65F, 0x938B, 0xE660, 0x938C, 0xE661, 0x938D, 0xE662, 0x938E, 0xE663, 0x9390, + 0xE664, 0x9391, 0xE665, 0x9392, 0xE666, 0x9393, 0xE667, 0x9394, 0xE668, 0x9395, 0xE669, 0x9396, 0xE66A, 0x9397, 0xE66B, 0x9398, + 0xE66C, 0x9399, 0xE66D, 0x939A, 0xE66E, 0x939B, 0xE66F, 0x939C, 0xE670, 0x939D, 0xE671, 0x939E, 0xE672, 0x939F, 0xE673, 0x93A0, + 0xE674, 0x93A1, 0xE675, 0x93A2, 0xE676, 0x93A3, 0xE677, 0x93A4, 0xE678, 0x93A5, 0xE679, 0x93A6, 0xE67A, 0x93A7, 0xE67B, 0x93A8, + 0xE67C, 0x93A9, 0xE67D, 0x93AA, 0xE67E, 0x93AB, 0xE680, 0x93AC, 0xE681, 0x93AD, 0xE682, 0x93AE, 0xE683, 0x93AF, 0xE684, 0x93B0, + 0xE685, 0x93B1, 0xE686, 0x93B2, 0xE687, 0x93B3, 0xE688, 0x93B4, 0xE689, 0x93B5, 0xE68A, 0x93B6, 0xE68B, 0x93B7, 0xE68C, 0x93B8, + 0xE68D, 0x93B9, 0xE68E, 0x93BA, 0xE68F, 0x93BB, 0xE690, 0x93BC, 0xE691, 0x93BD, 0xE692, 0x93BE, 0xE693, 0x93BF, 0xE694, 0x93C0, + 0xE695, 0x93C1, 0xE696, 0x93C2, 0xE697, 0x93C3, 0xE698, 0x93C4, 0xE699, 0x93C5, 0xE69A, 0x93C6, 0xE69B, 0x93C7, 0xE69C, 0x93C8, + 0xE69D, 0x93C9, 0xE69E, 0x93CB, 0xE69F, 0x93CC, 0xE6A0, 0x93CD, 0xE6A1, 0x5997, 0xE6A2, 0x59CA, 0xE6A3, 0x59AB, 0xE6A4, 0x599E, + 0xE6A5, 0x59A4, 0xE6A6, 0x59D2, 0xE6A7, 0x59B2, 0xE6A8, 0x59AF, 0xE6A9, 0x59D7, 0xE6AA, 0x59BE, 0xE6AB, 0x5A05, 0xE6AC, 0x5A06, + 0xE6AD, 0x59DD, 0xE6AE, 0x5A08, 0xE6AF, 0x59E3, 0xE6B0, 0x59D8, 0xE6B1, 0x59F9, 0xE6B2, 0x5A0C, 0xE6B3, 0x5A09, 0xE6B4, 0x5A32, + 0xE6B5, 0x5A34, 0xE6B6, 0x5A11, 0xE6B7, 0x5A23, 0xE6B8, 0x5A13, 0xE6B9, 0x5A40, 0xE6BA, 0x5A67, 0xE6BB, 0x5A4A, 0xE6BC, 0x5A55, + 0xE6BD, 0x5A3C, 0xE6BE, 0x5A62, 0xE6BF, 0x5A75, 0xE6C0, 0x80EC, 0xE6C1, 0x5AAA, 0xE6C2, 0x5A9B, 0xE6C3, 0x5A77, 0xE6C4, 0x5A7A, + 0xE6C5, 0x5ABE, 0xE6C6, 0x5AEB, 0xE6C7, 0x5AB2, 0xE6C8, 0x5AD2, 0xE6C9, 0x5AD4, 0xE6CA, 0x5AB8, 0xE6CB, 0x5AE0, 0xE6CC, 0x5AE3, + 0xE6CD, 0x5AF1, 0xE6CE, 0x5AD6, 0xE6CF, 0x5AE6, 0xE6D0, 0x5AD8, 0xE6D1, 0x5ADC, 0xE6D2, 0x5B09, 0xE6D3, 0x5B17, 0xE6D4, 0x5B16, + 0xE6D5, 0x5B32, 0xE6D6, 0x5B37, 0xE6D7, 0x5B40, 0xE6D8, 0x5C15, 0xE6D9, 0x5C1C, 0xE6DA, 0x5B5A, 0xE6DB, 0x5B65, 0xE6DC, 0x5B73, + 0xE6DD, 0x5B51, 0xE6DE, 0x5B53, 0xE6DF, 0x5B62, 0xE6E0, 0x9A75, 0xE6E1, 0x9A77, 0xE6E2, 0x9A78, 0xE6E3, 0x9A7A, 0xE6E4, 0x9A7F, + 0xE6E5, 0x9A7D, 0xE6E6, 0x9A80, 0xE6E7, 0x9A81, 0xE6E8, 0x9A85, 0xE6E9, 0x9A88, 0xE6EA, 0x9A8A, 0xE6EB, 0x9A90, 0xE6EC, 0x9A92, + 0xE6ED, 0x9A93, 0xE6EE, 0x9A96, 0xE6EF, 0x9A98, 0xE6F0, 0x9A9B, 0xE6F1, 0x9A9C, 0xE6F2, 0x9A9D, 0xE6F3, 0x9A9F, 0xE6F4, 0x9AA0, + 0xE6F5, 0x9AA2, 0xE6F6, 0x9AA3, 0xE6F7, 0x9AA5, 0xE6F8, 0x9AA7, 0xE6F9, 0x7E9F, 0xE6FA, 0x7EA1, 0xE6FB, 0x7EA3, 0xE6FC, 0x7EA5, + 0xE6FD, 0x7EA8, 0xE6FE, 0x7EA9, 0xE740, 0x93CE, 0xE741, 0x93CF, 0xE742, 0x93D0, 0xE743, 0x93D1, 0xE744, 0x93D2, 0xE745, 0x93D3, + 0xE746, 0x93D4, 0xE747, 0x93D5, 0xE748, 0x93D7, 0xE749, 0x93D8, 0xE74A, 0x93D9, 0xE74B, 0x93DA, 0xE74C, 0x93DB, 0xE74D, 0x93DC, + 0xE74E, 0x93DD, 0xE74F, 0x93DE, 0xE750, 0x93DF, 0xE751, 0x93E0, 0xE752, 0x93E1, 0xE753, 0x93E2, 0xE754, 0x93E3, 0xE755, 0x93E4, + 0xE756, 0x93E5, 0xE757, 0x93E6, 0xE758, 0x93E7, 0xE759, 0x93E8, 0xE75A, 0x93E9, 0xE75B, 0x93EA, 0xE75C, 0x93EB, 0xE75D, 0x93EC, + 0xE75E, 0x93ED, 0xE75F, 0x93EE, 0xE760, 0x93EF, 0xE761, 0x93F0, 0xE762, 0x93F1, 0xE763, 0x93F2, 0xE764, 0x93F3, 0xE765, 0x93F4, + 0xE766, 0x93F5, 0xE767, 0x93F6, 0xE768, 0x93F7, 0xE769, 0x93F8, 0xE76A, 0x93F9, 0xE76B, 0x93FA, 0xE76C, 0x93FB, 0xE76D, 0x93FC, + 0xE76E, 0x93FD, 0xE76F, 0x93FE, 0xE770, 0x93FF, 0xE771, 0x9400, 0xE772, 0x9401, 0xE773, 0x9402, 0xE774, 0x9403, 0xE775, 0x9404, + 0xE776, 0x9405, 0xE777, 0x9406, 0xE778, 0x9407, 0xE779, 0x9408, 0xE77A, 0x9409, 0xE77B, 0x940A, 0xE77C, 0x940B, 0xE77D, 0x940C, + 0xE77E, 0x940D, 0xE780, 0x940E, 0xE781, 0x940F, 0xE782, 0x9410, 0xE783, 0x9411, 0xE784, 0x9412, 0xE785, 0x9413, 0xE786, 0x9414, + 0xE787, 0x9415, 0xE788, 0x9416, 0xE789, 0x9417, 0xE78A, 0x9418, 0xE78B, 0x9419, 0xE78C, 0x941A, 0xE78D, 0x941B, 0xE78E, 0x941C, + 0xE78F, 0x941D, 0xE790, 0x941E, 0xE791, 0x941F, 0xE792, 0x9420, 0xE793, 0x9421, 0xE794, 0x9422, 0xE795, 0x9423, 0xE796, 0x9424, + 0xE797, 0x9425, 0xE798, 0x9426, 0xE799, 0x9427, 0xE79A, 0x9428, 0xE79B, 0x9429, 0xE79C, 0x942A, 0xE79D, 0x942B, 0xE79E, 0x942C, + 0xE79F, 0x942D, 0xE7A0, 0x942E, 0xE7A1, 0x7EAD, 0xE7A2, 0x7EB0, 0xE7A3, 0x7EBE, 0xE7A4, 0x7EC0, 0xE7A5, 0x7EC1, 0xE7A6, 0x7EC2, + 0xE7A7, 0x7EC9, 0xE7A8, 0x7ECB, 0xE7A9, 0x7ECC, 0xE7AA, 0x7ED0, 0xE7AB, 0x7ED4, 0xE7AC, 0x7ED7, 0xE7AD, 0x7EDB, 0xE7AE, 0x7EE0, + 0xE7AF, 0x7EE1, 0xE7B0, 0x7EE8, 0xE7B1, 0x7EEB, 0xE7B2, 0x7EEE, 0xE7B3, 0x7EEF, 0xE7B4, 0x7EF1, 0xE7B5, 0x7EF2, 0xE7B6, 0x7F0D, + 0xE7B7, 0x7EF6, 0xE7B8, 0x7EFA, 0xE7B9, 0x7EFB, 0xE7BA, 0x7EFE, 0xE7BB, 0x7F01, 0xE7BC, 0x7F02, 0xE7BD, 0x7F03, 0xE7BE, 0x7F07, + 0xE7BF, 0x7F08, 0xE7C0, 0x7F0B, 0xE7C1, 0x7F0C, 0xE7C2, 0x7F0F, 0xE7C3, 0x7F11, 0xE7C4, 0x7F12, 0xE7C5, 0x7F17, 0xE7C6, 0x7F19, + 0xE7C7, 0x7F1C, 0xE7C8, 0x7F1B, 0xE7C9, 0x7F1F, 0xE7CA, 0x7F21, 0xE7CB, 0x7F22, 0xE7CC, 0x7F23, 0xE7CD, 0x7F24, 0xE7CE, 0x7F25, + 0xE7CF, 0x7F26, 0xE7D0, 0x7F27, 0xE7D1, 0x7F2A, 0xE7D2, 0x7F2B, 0xE7D3, 0x7F2C, 0xE7D4, 0x7F2D, 0xE7D5, 0x7F2F, 0xE7D6, 0x7F30, + 0xE7D7, 0x7F31, 0xE7D8, 0x7F32, 0xE7D9, 0x7F33, 0xE7DA, 0x7F35, 0xE7DB, 0x5E7A, 0xE7DC, 0x757F, 0xE7DD, 0x5DDB, 0xE7DE, 0x753E, + 0xE7DF, 0x9095, 0xE7E0, 0x738E, 0xE7E1, 0x7391, 0xE7E2, 0x73AE, 0xE7E3, 0x73A2, 0xE7E4, 0x739F, 0xE7E5, 0x73CF, 0xE7E6, 0x73C2, + 0xE7E7, 0x73D1, 0xE7E8, 0x73B7, 0xE7E9, 0x73B3, 0xE7EA, 0x73C0, 0xE7EB, 0x73C9, 0xE7EC, 0x73C8, 0xE7ED, 0x73E5, 0xE7EE, 0x73D9, + 0xE7EF, 0x987C, 0xE7F0, 0x740A, 0xE7F1, 0x73E9, 0xE7F2, 0x73E7, 0xE7F3, 0x73DE, 0xE7F4, 0x73BA, 0xE7F5, 0x73F2, 0xE7F6, 0x740F, + 0xE7F7, 0x742A, 0xE7F8, 0x745B, 0xE7F9, 0x7426, 0xE7FA, 0x7425, 0xE7FB, 0x7428, 0xE7FC, 0x7430, 0xE7FD, 0x742E, 0xE7FE, 0x742C, + 0xE840, 0x942F, 0xE841, 0x9430, 0xE842, 0x9431, 0xE843, 0x9432, 0xE844, 0x9433, 0xE845, 0x9434, 0xE846, 0x9435, 0xE847, 0x9436, + 0xE848, 0x9437, 0xE849, 0x9438, 0xE84A, 0x9439, 0xE84B, 0x943A, 0xE84C, 0x943B, 0xE84D, 0x943C, 0xE84E, 0x943D, 0xE84F, 0x943F, + 0xE850, 0x9440, 0xE851, 0x9441, 0xE852, 0x9442, 0xE853, 0x9443, 0xE854, 0x9444, 0xE855, 0x9445, 0xE856, 0x9446, 0xE857, 0x9447, + 0xE858, 0x9448, 0xE859, 0x9449, 0xE85A, 0x944A, 0xE85B, 0x944B, 0xE85C, 0x944C, 0xE85D, 0x944D, 0xE85E, 0x944E, 0xE85F, 0x944F, + 0xE860, 0x9450, 0xE861, 0x9451, 0xE862, 0x9452, 0xE863, 0x9453, 0xE864, 0x9454, 0xE865, 0x9455, 0xE866, 0x9456, 0xE867, 0x9457, + 0xE868, 0x9458, 0xE869, 0x9459, 0xE86A, 0x945A, 0xE86B, 0x945B, 0xE86C, 0x945C, 0xE86D, 0x945D, 0xE86E, 0x945E, 0xE86F, 0x945F, + 0xE870, 0x9460, 0xE871, 0x9461, 0xE872, 0x9462, 0xE873, 0x9463, 0xE874, 0x9464, 0xE875, 0x9465, 0xE876, 0x9466, 0xE877, 0x9467, + 0xE878, 0x9468, 0xE879, 0x9469, 0xE87A, 0x946A, 0xE87B, 0x946C, 0xE87C, 0x946D, 0xE87D, 0x946E, 0xE87E, 0x946F, 0xE880, 0x9470, + 0xE881, 0x9471, 0xE882, 0x9472, 0xE883, 0x9473, 0xE884, 0x9474, 0xE885, 0x9475, 0xE886, 0x9476, 0xE887, 0x9477, 0xE888, 0x9478, + 0xE889, 0x9479, 0xE88A, 0x947A, 0xE88B, 0x947B, 0xE88C, 0x947C, 0xE88D, 0x947D, 0xE88E, 0x947E, 0xE88F, 0x947F, 0xE890, 0x9480, + 0xE891, 0x9481, 0xE892, 0x9482, 0xE893, 0x9483, 0xE894, 0x9484, 0xE895, 0x9491, 0xE896, 0x9496, 0xE897, 0x9498, 0xE898, 0x94C7, + 0xE899, 0x94CF, 0xE89A, 0x94D3, 0xE89B, 0x94D4, 0xE89C, 0x94DA, 0xE89D, 0x94E6, 0xE89E, 0x94FB, 0xE89F, 0x951C, 0xE8A0, 0x9520, + 0xE8A1, 0x741B, 0xE8A2, 0x741A, 0xE8A3, 0x7441, 0xE8A4, 0x745C, 0xE8A5, 0x7457, 0xE8A6, 0x7455, 0xE8A7, 0x7459, 0xE8A8, 0x7477, + 0xE8A9, 0x746D, 0xE8AA, 0x747E, 0xE8AB, 0x749C, 0xE8AC, 0x748E, 0xE8AD, 0x7480, 0xE8AE, 0x7481, 0xE8AF, 0x7487, 0xE8B0, 0x748B, + 0xE8B1, 0x749E, 0xE8B2, 0x74A8, 0xE8B3, 0x74A9, 0xE8B4, 0x7490, 0xE8B5, 0x74A7, 0xE8B6, 0x74D2, 0xE8B7, 0x74BA, 0xE8B8, 0x97EA, + 0xE8B9, 0x97EB, 0xE8BA, 0x97EC, 0xE8BB, 0x674C, 0xE8BC, 0x6753, 0xE8BD, 0x675E, 0xE8BE, 0x6748, 0xE8BF, 0x6769, 0xE8C0, 0x67A5, + 0xE8C1, 0x6787, 0xE8C2, 0x676A, 0xE8C3, 0x6773, 0xE8C4, 0x6798, 0xE8C5, 0x67A7, 0xE8C6, 0x6775, 0xE8C7, 0x67A8, 0xE8C8, 0x679E, + 0xE8C9, 0x67AD, 0xE8CA, 0x678B, 0xE8CB, 0x6777, 0xE8CC, 0x677C, 0xE8CD, 0x67F0, 0xE8CE, 0x6809, 0xE8CF, 0x67D8, 0xE8D0, 0x680A, + 0xE8D1, 0x67E9, 0xE8D2, 0x67B0, 0xE8D3, 0x680C, 0xE8D4, 0x67D9, 0xE8D5, 0x67B5, 0xE8D6, 0x67DA, 0xE8D7, 0x67B3, 0xE8D8, 0x67DD, + 0xE8D9, 0x6800, 0xE8DA, 0x67C3, 0xE8DB, 0x67B8, 0xE8DC, 0x67E2, 0xE8DD, 0x680E, 0xE8DE, 0x67C1, 0xE8DF, 0x67FD, 0xE8E0, 0x6832, + 0xE8E1, 0x6833, 0xE8E2, 0x6860, 0xE8E3, 0x6861, 0xE8E4, 0x684E, 0xE8E5, 0x6862, 0xE8E6, 0x6844, 0xE8E7, 0x6864, 0xE8E8, 0x6883, + 0xE8E9, 0x681D, 0xE8EA, 0x6855, 0xE8EB, 0x6866, 0xE8EC, 0x6841, 0xE8ED, 0x6867, 0xE8EE, 0x6840, 0xE8EF, 0x683E, 0xE8F0, 0x684A, + 0xE8F1, 0x6849, 0xE8F2, 0x6829, 0xE8F3, 0x68B5, 0xE8F4, 0x688F, 0xE8F5, 0x6874, 0xE8F6, 0x6877, 0xE8F7, 0x6893, 0xE8F8, 0x686B, + 0xE8F9, 0x68C2, 0xE8FA, 0x696E, 0xE8FB, 0x68FC, 0xE8FC, 0x691F, 0xE8FD, 0x6920, 0xE8FE, 0x68F9, 0xE940, 0x9527, 0xE941, 0x9533, + 0xE942, 0x953D, 0xE943, 0x9543, 0xE944, 0x9548, 0xE945, 0x954B, 0xE946, 0x9555, 0xE947, 0x955A, 0xE948, 0x9560, 0xE949, 0x956E, + 0xE94A, 0x9574, 0xE94B, 0x9575, 0xE94C, 0x9577, 0xE94D, 0x9578, 0xE94E, 0x9579, 0xE94F, 0x957A, 0xE950, 0x957B, 0xE951, 0x957C, + 0xE952, 0x957D, 0xE953, 0x957E, 0xE954, 0x9580, 0xE955, 0x9581, 0xE956, 0x9582, 0xE957, 0x9583, 0xE958, 0x9584, 0xE959, 0x9585, + 0xE95A, 0x9586, 0xE95B, 0x9587, 0xE95C, 0x9588, 0xE95D, 0x9589, 0xE95E, 0x958A, 0xE95F, 0x958B, 0xE960, 0x958C, 0xE961, 0x958D, + 0xE962, 0x958E, 0xE963, 0x958F, 0xE964, 0x9590, 0xE965, 0x9591, 0xE966, 0x9592, 0xE967, 0x9593, 0xE968, 0x9594, 0xE969, 0x9595, + 0xE96A, 0x9596, 0xE96B, 0x9597, 0xE96C, 0x9598, 0xE96D, 0x9599, 0xE96E, 0x959A, 0xE96F, 0x959B, 0xE970, 0x959C, 0xE971, 0x959D, + 0xE972, 0x959E, 0xE973, 0x959F, 0xE974, 0x95A0, 0xE975, 0x95A1, 0xE976, 0x95A2, 0xE977, 0x95A3, 0xE978, 0x95A4, 0xE979, 0x95A5, + 0xE97A, 0x95A6, 0xE97B, 0x95A7, 0xE97C, 0x95A8, 0xE97D, 0x95A9, 0xE97E, 0x95AA, 0xE980, 0x95AB, 0xE981, 0x95AC, 0xE982, 0x95AD, + 0xE983, 0x95AE, 0xE984, 0x95AF, 0xE985, 0x95B0, 0xE986, 0x95B1, 0xE987, 0x95B2, 0xE988, 0x95B3, 0xE989, 0x95B4, 0xE98A, 0x95B5, + 0xE98B, 0x95B6, 0xE98C, 0x95B7, 0xE98D, 0x95B8, 0xE98E, 0x95B9, 0xE98F, 0x95BA, 0xE990, 0x95BB, 0xE991, 0x95BC, 0xE992, 0x95BD, + 0xE993, 0x95BE, 0xE994, 0x95BF, 0xE995, 0x95C0, 0xE996, 0x95C1, 0xE997, 0x95C2, 0xE998, 0x95C3, 0xE999, 0x95C4, 0xE99A, 0x95C5, + 0xE99B, 0x95C6, 0xE99C, 0x95C7, 0xE99D, 0x95C8, 0xE99E, 0x95C9, 0xE99F, 0x95CA, 0xE9A0, 0x95CB, 0xE9A1, 0x6924, 0xE9A2, 0x68F0, + 0xE9A3, 0x690B, 0xE9A4, 0x6901, 0xE9A5, 0x6957, 0xE9A6, 0x68E3, 0xE9A7, 0x6910, 0xE9A8, 0x6971, 0xE9A9, 0x6939, 0xE9AA, 0x6960, + 0xE9AB, 0x6942, 0xE9AC, 0x695D, 0xE9AD, 0x6984, 0xE9AE, 0x696B, 0xE9AF, 0x6980, 0xE9B0, 0x6998, 0xE9B1, 0x6978, 0xE9B2, 0x6934, + 0xE9B3, 0x69CC, 0xE9B4, 0x6987, 0xE9B5, 0x6988, 0xE9B6, 0x69CE, 0xE9B7, 0x6989, 0xE9B8, 0x6966, 0xE9B9, 0x6963, 0xE9BA, 0x6979, + 0xE9BB, 0x699B, 0xE9BC, 0x69A7, 0xE9BD, 0x69BB, 0xE9BE, 0x69AB, 0xE9BF, 0x69AD, 0xE9C0, 0x69D4, 0xE9C1, 0x69B1, 0xE9C2, 0x69C1, + 0xE9C3, 0x69CA, 0xE9C4, 0x69DF, 0xE9C5, 0x6995, 0xE9C6, 0x69E0, 0xE9C7, 0x698D, 0xE9C8, 0x69FF, 0xE9C9, 0x6A2F, 0xE9CA, 0x69ED, + 0xE9CB, 0x6A17, 0xE9CC, 0x6A18, 0xE9CD, 0x6A65, 0xE9CE, 0x69F2, 0xE9CF, 0x6A44, 0xE9D0, 0x6A3E, 0xE9D1, 0x6AA0, 0xE9D2, 0x6A50, + 0xE9D3, 0x6A5B, 0xE9D4, 0x6A35, 0xE9D5, 0x6A8E, 0xE9D6, 0x6A79, 0xE9D7, 0x6A3D, 0xE9D8, 0x6A28, 0xE9D9, 0x6A58, 0xE9DA, 0x6A7C, + 0xE9DB, 0x6A91, 0xE9DC, 0x6A90, 0xE9DD, 0x6AA9, 0xE9DE, 0x6A97, 0xE9DF, 0x6AAB, 0xE9E0, 0x7337, 0xE9E1, 0x7352, 0xE9E2, 0x6B81, + 0xE9E3, 0x6B82, 0xE9E4, 0x6B87, 0xE9E5, 0x6B84, 0xE9E6, 0x6B92, 0xE9E7, 0x6B93, 0xE9E8, 0x6B8D, 0xE9E9, 0x6B9A, 0xE9EA, 0x6B9B, + 0xE9EB, 0x6BA1, 0xE9EC, 0x6BAA, 0xE9ED, 0x8F6B, 0xE9EE, 0x8F6D, 0xE9EF, 0x8F71, 0xE9F0, 0x8F72, 0xE9F1, 0x8F73, 0xE9F2, 0x8F75, + 0xE9F3, 0x8F76, 0xE9F4, 0x8F78, 0xE9F5, 0x8F77, 0xE9F6, 0x8F79, 0xE9F7, 0x8F7A, 0xE9F8, 0x8F7C, 0xE9F9, 0x8F7E, 0xE9FA, 0x8F81, + 0xE9FB, 0x8F82, 0xE9FC, 0x8F84, 0xE9FD, 0x8F87, 0xE9FE, 0x8F8B, 0xEA40, 0x95CC, 0xEA41, 0x95CD, 0xEA42, 0x95CE, 0xEA43, 0x95CF, + 0xEA44, 0x95D0, 0xEA45, 0x95D1, 0xEA46, 0x95D2, 0xEA47, 0x95D3, 0xEA48, 0x95D4, 0xEA49, 0x95D5, 0xEA4A, 0x95D6, 0xEA4B, 0x95D7, + 0xEA4C, 0x95D8, 0xEA4D, 0x95D9, 0xEA4E, 0x95DA, 0xEA4F, 0x95DB, 0xEA50, 0x95DC, 0xEA51, 0x95DD, 0xEA52, 0x95DE, 0xEA53, 0x95DF, + 0xEA54, 0x95E0, 0xEA55, 0x95E1, 0xEA56, 0x95E2, 0xEA57, 0x95E3, 0xEA58, 0x95E4, 0xEA59, 0x95E5, 0xEA5A, 0x95E6, 0xEA5B, 0x95E7, + 0xEA5C, 0x95EC, 0xEA5D, 0x95FF, 0xEA5E, 0x9607, 0xEA5F, 0x9613, 0xEA60, 0x9618, 0xEA61, 0x961B, 0xEA62, 0x961E, 0xEA63, 0x9620, + 0xEA64, 0x9623, 0xEA65, 0x9624, 0xEA66, 0x9625, 0xEA67, 0x9626, 0xEA68, 0x9627, 0xEA69, 0x9628, 0xEA6A, 0x9629, 0xEA6B, 0x962B, + 0xEA6C, 0x962C, 0xEA6D, 0x962D, 0xEA6E, 0x962F, 0xEA6F, 0x9630, 0xEA70, 0x9637, 0xEA71, 0x9638, 0xEA72, 0x9639, 0xEA73, 0x963A, + 0xEA74, 0x963E, 0xEA75, 0x9641, 0xEA76, 0x9643, 0xEA77, 0x964A, 0xEA78, 0x964E, 0xEA79, 0x964F, 0xEA7A, 0x9651, 0xEA7B, 0x9652, + 0xEA7C, 0x9653, 0xEA7D, 0x9656, 0xEA7E, 0x9657, 0xEA80, 0x9658, 0xEA81, 0x9659, 0xEA82, 0x965A, 0xEA83, 0x965C, 0xEA84, 0x965D, + 0xEA85, 0x965E, 0xEA86, 0x9660, 0xEA87, 0x9663, 0xEA88, 0x9665, 0xEA89, 0x9666, 0xEA8A, 0x966B, 0xEA8B, 0x966D, 0xEA8C, 0x966E, + 0xEA8D, 0x966F, 0xEA8E, 0x9670, 0xEA8F, 0x9671, 0xEA90, 0x9673, 0xEA91, 0x9678, 0xEA92, 0x9679, 0xEA93, 0x967A, 0xEA94, 0x967B, + 0xEA95, 0x967C, 0xEA96, 0x967D, 0xEA97, 0x967E, 0xEA98, 0x967F, 0xEA99, 0x9680, 0xEA9A, 0x9681, 0xEA9B, 0x9682, 0xEA9C, 0x9683, + 0xEA9D, 0x9684, 0xEA9E, 0x9687, 0xEA9F, 0x9689, 0xEAA0, 0x968A, 0xEAA1, 0x8F8D, 0xEAA2, 0x8F8E, 0xEAA3, 0x8F8F, 0xEAA4, 0x8F98, + 0xEAA5, 0x8F9A, 0xEAA6, 0x8ECE, 0xEAA7, 0x620B, 0xEAA8, 0x6217, 0xEAA9, 0x621B, 0xEAAA, 0x621F, 0xEAAB, 0x6222, 0xEAAC, 0x6221, + 0xEAAD, 0x6225, 0xEAAE, 0x6224, 0xEAAF, 0x622C, 0xEAB0, 0x81E7, 0xEAB1, 0x74EF, 0xEAB2, 0x74F4, 0xEAB3, 0x74FF, 0xEAB4, 0x750F, + 0xEAB5, 0x7511, 0xEAB6, 0x7513, 0xEAB7, 0x6534, 0xEAB8, 0x65EE, 0xEAB9, 0x65EF, 0xEABA, 0x65F0, 0xEABB, 0x660A, 0xEABC, 0x6619, + 0xEABD, 0x6772, 0xEABE, 0x6603, 0xEABF, 0x6615, 0xEAC0, 0x6600, 0xEAC1, 0x7085, 0xEAC2, 0x66F7, 0xEAC3, 0x661D, 0xEAC4, 0x6634, + 0xEAC5, 0x6631, 0xEAC6, 0x6636, 0xEAC7, 0x6635, 0xEAC8, 0x8006, 0xEAC9, 0x665F, 0xEACA, 0x6654, 0xEACB, 0x6641, 0xEACC, 0x664F, + 0xEACD, 0x6656, 0xEACE, 0x6661, 0xEACF, 0x6657, 0xEAD0, 0x6677, 0xEAD1, 0x6684, 0xEAD2, 0x668C, 0xEAD3, 0x66A7, 0xEAD4, 0x669D, + 0xEAD5, 0x66BE, 0xEAD6, 0x66DB, 0xEAD7, 0x66DC, 0xEAD8, 0x66E6, 0xEAD9, 0x66E9, 0xEADA, 0x8D32, 0xEADB, 0x8D33, 0xEADC, 0x8D36, + 0xEADD, 0x8D3B, 0xEADE, 0x8D3D, 0xEADF, 0x8D40, 0xEAE0, 0x8D45, 0xEAE1, 0x8D46, 0xEAE2, 0x8D48, 0xEAE3, 0x8D49, 0xEAE4, 0x8D47, + 0xEAE5, 0x8D4D, 0xEAE6, 0x8D55, 0xEAE7, 0x8D59, 0xEAE8, 0x89C7, 0xEAE9, 0x89CA, 0xEAEA, 0x89CB, 0xEAEB, 0x89CC, 0xEAEC, 0x89CE, + 0xEAED, 0x89CF, 0xEAEE, 0x89D0, 0xEAEF, 0x89D1, 0xEAF0, 0x726E, 0xEAF1, 0x729F, 0xEAF2, 0x725D, 0xEAF3, 0x7266, 0xEAF4, 0x726F, + 0xEAF5, 0x727E, 0xEAF6, 0x727F, 0xEAF7, 0x7284, 0xEAF8, 0x728B, 0xEAF9, 0x728D, 0xEAFA, 0x728F, 0xEAFB, 0x7292, 0xEAFC, 0x6308, + 0xEAFD, 0x6332, 0xEAFE, 0x63B0, 0xEB40, 0x968C, 0xEB41, 0x968E, 0xEB42, 0x9691, 0xEB43, 0x9692, 0xEB44, 0x9693, 0xEB45, 0x9695, + 0xEB46, 0x9696, 0xEB47, 0x969A, 0xEB48, 0x969B, 0xEB49, 0x969D, 0xEB4A, 0x969E, 0xEB4B, 0x969F, 0xEB4C, 0x96A0, 0xEB4D, 0x96A1, + 0xEB4E, 0x96A2, 0xEB4F, 0x96A3, 0xEB50, 0x96A4, 0xEB51, 0x96A5, 0xEB52, 0x96A6, 0xEB53, 0x96A8, 0xEB54, 0x96A9, 0xEB55, 0x96AA, + 0xEB56, 0x96AB, 0xEB57, 0x96AC, 0xEB58, 0x96AD, 0xEB59, 0x96AE, 0xEB5A, 0x96AF, 0xEB5B, 0x96B1, 0xEB5C, 0x96B2, 0xEB5D, 0x96B4, + 0xEB5E, 0x96B5, 0xEB5F, 0x96B7, 0xEB60, 0x96B8, 0xEB61, 0x96BA, 0xEB62, 0x96BB, 0xEB63, 0x96BF, 0xEB64, 0x96C2, 0xEB65, 0x96C3, + 0xEB66, 0x96C8, 0xEB67, 0x96CA, 0xEB68, 0x96CB, 0xEB69, 0x96D0, 0xEB6A, 0x96D1, 0xEB6B, 0x96D3, 0xEB6C, 0x96D4, 0xEB6D, 0x96D6, + 0xEB6E, 0x96D7, 0xEB6F, 0x96D8, 0xEB70, 0x96D9, 0xEB71, 0x96DA, 0xEB72, 0x96DB, 0xEB73, 0x96DC, 0xEB74, 0x96DD, 0xEB75, 0x96DE, + 0xEB76, 0x96DF, 0xEB77, 0x96E1, 0xEB78, 0x96E2, 0xEB79, 0x96E3, 0xEB7A, 0x96E4, 0xEB7B, 0x96E5, 0xEB7C, 0x96E6, 0xEB7D, 0x96E7, + 0xEB7E, 0x96EB, 0xEB80, 0x96EC, 0xEB81, 0x96ED, 0xEB82, 0x96EE, 0xEB83, 0x96F0, 0xEB84, 0x96F1, 0xEB85, 0x96F2, 0xEB86, 0x96F4, + 0xEB87, 0x96F5, 0xEB88, 0x96F8, 0xEB89, 0x96FA, 0xEB8A, 0x96FB, 0xEB8B, 0x96FC, 0xEB8C, 0x96FD, 0xEB8D, 0x96FF, 0xEB8E, 0x9702, + 0xEB8F, 0x9703, 0xEB90, 0x9705, 0xEB91, 0x970A, 0xEB92, 0x970B, 0xEB93, 0x970C, 0xEB94, 0x9710, 0xEB95, 0x9711, 0xEB96, 0x9712, + 0xEB97, 0x9714, 0xEB98, 0x9715, 0xEB99, 0x9717, 0xEB9A, 0x9718, 0xEB9B, 0x9719, 0xEB9C, 0x971A, 0xEB9D, 0x971B, 0xEB9E, 0x971D, + 0xEB9F, 0x971F, 0xEBA0, 0x9720, 0xEBA1, 0x643F, 0xEBA2, 0x64D8, 0xEBA3, 0x8004, 0xEBA4, 0x6BEA, 0xEBA5, 0x6BF3, 0xEBA6, 0x6BFD, + 0xEBA7, 0x6BF5, 0xEBA8, 0x6BF9, 0xEBA9, 0x6C05, 0xEBAA, 0x6C07, 0xEBAB, 0x6C06, 0xEBAC, 0x6C0D, 0xEBAD, 0x6C15, 0xEBAE, 0x6C18, + 0xEBAF, 0x6C19, 0xEBB0, 0x6C1A, 0xEBB1, 0x6C21, 0xEBB2, 0x6C29, 0xEBB3, 0x6C24, 0xEBB4, 0x6C2A, 0xEBB5, 0x6C32, 0xEBB6, 0x6535, + 0xEBB7, 0x6555, 0xEBB8, 0x656B, 0xEBB9, 0x724D, 0xEBBA, 0x7252, 0xEBBB, 0x7256, 0xEBBC, 0x7230, 0xEBBD, 0x8662, 0xEBBE, 0x5216, + 0xEBBF, 0x809F, 0xEBC0, 0x809C, 0xEBC1, 0x8093, 0xEBC2, 0x80BC, 0xEBC3, 0x670A, 0xEBC4, 0x80BD, 0xEBC5, 0x80B1, 0xEBC6, 0x80AB, + 0xEBC7, 0x80AD, 0xEBC8, 0x80B4, 0xEBC9, 0x80B7, 0xEBCA, 0x80E7, 0xEBCB, 0x80E8, 0xEBCC, 0x80E9, 0xEBCD, 0x80EA, 0xEBCE, 0x80DB, + 0xEBCF, 0x80C2, 0xEBD0, 0x80C4, 0xEBD1, 0x80D9, 0xEBD2, 0x80CD, 0xEBD3, 0x80D7, 0xEBD4, 0x6710, 0xEBD5, 0x80DD, 0xEBD6, 0x80EB, + 0xEBD7, 0x80F1, 0xEBD8, 0x80F4, 0xEBD9, 0x80ED, 0xEBDA, 0x810D, 0xEBDB, 0x810E, 0xEBDC, 0x80F2, 0xEBDD, 0x80FC, 0xEBDE, 0x6715, + 0xEBDF, 0x8112, 0xEBE0, 0x8C5A, 0xEBE1, 0x8136, 0xEBE2, 0x811E, 0xEBE3, 0x812C, 0xEBE4, 0x8118, 0xEBE5, 0x8132, 0xEBE6, 0x8148, + 0xEBE7, 0x814C, 0xEBE8, 0x8153, 0xEBE9, 0x8174, 0xEBEA, 0x8159, 0xEBEB, 0x815A, 0xEBEC, 0x8171, 0xEBED, 0x8160, 0xEBEE, 0x8169, + 0xEBEF, 0x817C, 0xEBF0, 0x817D, 0xEBF1, 0x816D, 0xEBF2, 0x8167, 0xEBF3, 0x584D, 0xEBF4, 0x5AB5, 0xEBF5, 0x8188, 0xEBF6, 0x8182, + 0xEBF7, 0x8191, 0xEBF8, 0x6ED5, 0xEBF9, 0x81A3, 0xEBFA, 0x81AA, 0xEBFB, 0x81CC, 0xEBFC, 0x6726, 0xEBFD, 0x81CA, 0xEBFE, 0x81BB, + 0xEC40, 0x9721, 0xEC41, 0x9722, 0xEC42, 0x9723, 0xEC43, 0x9724, 0xEC44, 0x9725, 0xEC45, 0x9726, 0xEC46, 0x9727, 0xEC47, 0x9728, + 0xEC48, 0x9729, 0xEC49, 0x972B, 0xEC4A, 0x972C, 0xEC4B, 0x972E, 0xEC4C, 0x972F, 0xEC4D, 0x9731, 0xEC4E, 0x9733, 0xEC4F, 0x9734, + 0xEC50, 0x9735, 0xEC51, 0x9736, 0xEC52, 0x9737, 0xEC53, 0x973A, 0xEC54, 0x973B, 0xEC55, 0x973C, 0xEC56, 0x973D, 0xEC57, 0x973F, + 0xEC58, 0x9740, 0xEC59, 0x9741, 0xEC5A, 0x9742, 0xEC5B, 0x9743, 0xEC5C, 0x9744, 0xEC5D, 0x9745, 0xEC5E, 0x9746, 0xEC5F, 0x9747, + 0xEC60, 0x9748, 0xEC61, 0x9749, 0xEC62, 0x974A, 0xEC63, 0x974B, 0xEC64, 0x974C, 0xEC65, 0x974D, 0xEC66, 0x974E, 0xEC67, 0x974F, + 0xEC68, 0x9750, 0xEC69, 0x9751, 0xEC6A, 0x9754, 0xEC6B, 0x9755, 0xEC6C, 0x9757, 0xEC6D, 0x9758, 0xEC6E, 0x975A, 0xEC6F, 0x975C, + 0xEC70, 0x975D, 0xEC71, 0x975F, 0xEC72, 0x9763, 0xEC73, 0x9764, 0xEC74, 0x9766, 0xEC75, 0x9767, 0xEC76, 0x9768, 0xEC77, 0x976A, + 0xEC78, 0x976B, 0xEC79, 0x976C, 0xEC7A, 0x976D, 0xEC7B, 0x976E, 0xEC7C, 0x976F, 0xEC7D, 0x9770, 0xEC7E, 0x9771, 0xEC80, 0x9772, + 0xEC81, 0x9775, 0xEC82, 0x9777, 0xEC83, 0x9778, 0xEC84, 0x9779, 0xEC85, 0x977A, 0xEC86, 0x977B, 0xEC87, 0x977D, 0xEC88, 0x977E, + 0xEC89, 0x977F, 0xEC8A, 0x9780, 0xEC8B, 0x9781, 0xEC8C, 0x9782, 0xEC8D, 0x9783, 0xEC8E, 0x9784, 0xEC8F, 0x9786, 0xEC90, 0x9787, + 0xEC91, 0x9788, 0xEC92, 0x9789, 0xEC93, 0x978A, 0xEC94, 0x978C, 0xEC95, 0x978E, 0xEC96, 0x978F, 0xEC97, 0x9790, 0xEC98, 0x9793, + 0xEC99, 0x9795, 0xEC9A, 0x9796, 0xEC9B, 0x9797, 0xEC9C, 0x9799, 0xEC9D, 0x979A, 0xEC9E, 0x979B, 0xEC9F, 0x979C, 0xECA0, 0x979D, + 0xECA1, 0x81C1, 0xECA2, 0x81A6, 0xECA3, 0x6B24, 0xECA4, 0x6B37, 0xECA5, 0x6B39, 0xECA6, 0x6B43, 0xECA7, 0x6B46, 0xECA8, 0x6B59, + 0xECA9, 0x98D1, 0xECAA, 0x98D2, 0xECAB, 0x98D3, 0xECAC, 0x98D5, 0xECAD, 0x98D9, 0xECAE, 0x98DA, 0xECAF, 0x6BB3, 0xECB0, 0x5F40, + 0xECB1, 0x6BC2, 0xECB2, 0x89F3, 0xECB3, 0x6590, 0xECB4, 0x9F51, 0xECB5, 0x6593, 0xECB6, 0x65BC, 0xECB7, 0x65C6, 0xECB8, 0x65C4, + 0xECB9, 0x65C3, 0xECBA, 0x65CC, 0xECBB, 0x65CE, 0xECBC, 0x65D2, 0xECBD, 0x65D6, 0xECBE, 0x7080, 0xECBF, 0x709C, 0xECC0, 0x7096, + 0xECC1, 0x709D, 0xECC2, 0x70BB, 0xECC3, 0x70C0, 0xECC4, 0x70B7, 0xECC5, 0x70AB, 0xECC6, 0x70B1, 0xECC7, 0x70E8, 0xECC8, 0x70CA, + 0xECC9, 0x7110, 0xECCA, 0x7113, 0xECCB, 0x7116, 0xECCC, 0x712F, 0xECCD, 0x7131, 0xECCE, 0x7173, 0xECCF, 0x715C, 0xECD0, 0x7168, + 0xECD1, 0x7145, 0xECD2, 0x7172, 0xECD3, 0x714A, 0xECD4, 0x7178, 0xECD5, 0x717A, 0xECD6, 0x7198, 0xECD7, 0x71B3, 0xECD8, 0x71B5, + 0xECD9, 0x71A8, 0xECDA, 0x71A0, 0xECDB, 0x71E0, 0xECDC, 0x71D4, 0xECDD, 0x71E7, 0xECDE, 0x71F9, 0xECDF, 0x721D, 0xECE0, 0x7228, + 0xECE1, 0x706C, 0xECE2, 0x7118, 0xECE3, 0x7166, 0xECE4, 0x71B9, 0xECE5, 0x623E, 0xECE6, 0x623D, 0xECE7, 0x6243, 0xECE8, 0x6248, + 0xECE9, 0x6249, 0xECEA, 0x793B, 0xECEB, 0x7940, 0xECEC, 0x7946, 0xECED, 0x7949, 0xECEE, 0x795B, 0xECEF, 0x795C, 0xECF0, 0x7953, + 0xECF1, 0x795A, 0xECF2, 0x7962, 0xECF3, 0x7957, 0xECF4, 0x7960, 0xECF5, 0x796F, 0xECF6, 0x7967, 0xECF7, 0x797A, 0xECF8, 0x7985, + 0xECF9, 0x798A, 0xECFA, 0x799A, 0xECFB, 0x79A7, 0xECFC, 0x79B3, 0xECFD, 0x5FD1, 0xECFE, 0x5FD0, 0xED40, 0x979E, 0xED41, 0x979F, + 0xED42, 0x97A1, 0xED43, 0x97A2, 0xED44, 0x97A4, 0xED45, 0x97A5, 0xED46, 0x97A6, 0xED47, 0x97A7, 0xED48, 0x97A8, 0xED49, 0x97A9, + 0xED4A, 0x97AA, 0xED4B, 0x97AC, 0xED4C, 0x97AE, 0xED4D, 0x97B0, 0xED4E, 0x97B1, 0xED4F, 0x97B3, 0xED50, 0x97B5, 0xED51, 0x97B6, + 0xED52, 0x97B7, 0xED53, 0x97B8, 0xED54, 0x97B9, 0xED55, 0x97BA, 0xED56, 0x97BB, 0xED57, 0x97BC, 0xED58, 0x97BD, 0xED59, 0x97BE, + 0xED5A, 0x97BF, 0xED5B, 0x97C0, 0xED5C, 0x97C1, 0xED5D, 0x97C2, 0xED5E, 0x97C3, 0xED5F, 0x97C4, 0xED60, 0x97C5, 0xED61, 0x97C6, + 0xED62, 0x97C7, 0xED63, 0x97C8, 0xED64, 0x97C9, 0xED65, 0x97CA, 0xED66, 0x97CB, 0xED67, 0x97CC, 0xED68, 0x97CD, 0xED69, 0x97CE, + 0xED6A, 0x97CF, 0xED6B, 0x97D0, 0xED6C, 0x97D1, 0xED6D, 0x97D2, 0xED6E, 0x97D3, 0xED6F, 0x97D4, 0xED70, 0x97D5, 0xED71, 0x97D6, + 0xED72, 0x97D7, 0xED73, 0x97D8, 0xED74, 0x97D9, 0xED75, 0x97DA, 0xED76, 0x97DB, 0xED77, 0x97DC, 0xED78, 0x97DD, 0xED79, 0x97DE, + 0xED7A, 0x97DF, 0xED7B, 0x97E0, 0xED7C, 0x97E1, 0xED7D, 0x97E2, 0xED7E, 0x97E3, 0xED80, 0x97E4, 0xED81, 0x97E5, 0xED82, 0x97E8, + 0xED83, 0x97EE, 0xED84, 0x97EF, 0xED85, 0x97F0, 0xED86, 0x97F1, 0xED87, 0x97F2, 0xED88, 0x97F4, 0xED89, 0x97F7, 0xED8A, 0x97F8, + 0xED8B, 0x97F9, 0xED8C, 0x97FA, 0xED8D, 0x97FB, 0xED8E, 0x97FC, 0xED8F, 0x97FD, 0xED90, 0x97FE, 0xED91, 0x97FF, 0xED92, 0x9800, + 0xED93, 0x9801, 0xED94, 0x9802, 0xED95, 0x9803, 0xED96, 0x9804, 0xED97, 0x9805, 0xED98, 0x9806, 0xED99, 0x9807, 0xED9A, 0x9808, + 0xED9B, 0x9809, 0xED9C, 0x980A, 0xED9D, 0x980B, 0xED9E, 0x980C, 0xED9F, 0x980D, 0xEDA0, 0x980E, 0xEDA1, 0x603C, 0xEDA2, 0x605D, + 0xEDA3, 0x605A, 0xEDA4, 0x6067, 0xEDA5, 0x6041, 0xEDA6, 0x6059, 0xEDA7, 0x6063, 0xEDA8, 0x60AB, 0xEDA9, 0x6106, 0xEDAA, 0x610D, + 0xEDAB, 0x615D, 0xEDAC, 0x61A9, 0xEDAD, 0x619D, 0xEDAE, 0x61CB, 0xEDAF, 0x61D1, 0xEDB0, 0x6206, 0xEDB1, 0x8080, 0xEDB2, 0x807F, + 0xEDB3, 0x6C93, 0xEDB4, 0x6CF6, 0xEDB5, 0x6DFC, 0xEDB6, 0x77F6, 0xEDB7, 0x77F8, 0xEDB8, 0x7800, 0xEDB9, 0x7809, 0xEDBA, 0x7817, + 0xEDBB, 0x7818, 0xEDBC, 0x7811, 0xEDBD, 0x65AB, 0xEDBE, 0x782D, 0xEDBF, 0x781C, 0xEDC0, 0x781D, 0xEDC1, 0x7839, 0xEDC2, 0x783A, + 0xEDC3, 0x783B, 0xEDC4, 0x781F, 0xEDC5, 0x783C, 0xEDC6, 0x7825, 0xEDC7, 0x782C, 0xEDC8, 0x7823, 0xEDC9, 0x7829, 0xEDCA, 0x784E, + 0xEDCB, 0x786D, 0xEDCC, 0x7856, 0xEDCD, 0x7857, 0xEDCE, 0x7826, 0xEDCF, 0x7850, 0xEDD0, 0x7847, 0xEDD1, 0x784C, 0xEDD2, 0x786A, + 0xEDD3, 0x789B, 0xEDD4, 0x7893, 0xEDD5, 0x789A, 0xEDD6, 0x7887, 0xEDD7, 0x789C, 0xEDD8, 0x78A1, 0xEDD9, 0x78A3, 0xEDDA, 0x78B2, + 0xEDDB, 0x78B9, 0xEDDC, 0x78A5, 0xEDDD, 0x78D4, 0xEDDE, 0x78D9, 0xEDDF, 0x78C9, 0xEDE0, 0x78EC, 0xEDE1, 0x78F2, 0xEDE2, 0x7905, + 0xEDE3, 0x78F4, 0xEDE4, 0x7913, 0xEDE5, 0x7924, 0xEDE6, 0x791E, 0xEDE7, 0x7934, 0xEDE8, 0x9F9B, 0xEDE9, 0x9EF9, 0xEDEA, 0x9EFB, + 0xEDEB, 0x9EFC, 0xEDEC, 0x76F1, 0xEDED, 0x7704, 0xEDEE, 0x770D, 0xEDEF, 0x76F9, 0xEDF0, 0x7707, 0xEDF1, 0x7708, 0xEDF2, 0x771A, + 0xEDF3, 0x7722, 0xEDF4, 0x7719, 0xEDF5, 0x772D, 0xEDF6, 0x7726, 0xEDF7, 0x7735, 0xEDF8, 0x7738, 0xEDF9, 0x7750, 0xEDFA, 0x7751, + 0xEDFB, 0x7747, 0xEDFC, 0x7743, 0xEDFD, 0x775A, 0xEDFE, 0x7768, 0xEE40, 0x980F, 0xEE41, 0x9810, 0xEE42, 0x9811, 0xEE43, 0x9812, + 0xEE44, 0x9813, 0xEE45, 0x9814, 0xEE46, 0x9815, 0xEE47, 0x9816, 0xEE48, 0x9817, 0xEE49, 0x9818, 0xEE4A, 0x9819, 0xEE4B, 0x981A, + 0xEE4C, 0x981B, 0xEE4D, 0x981C, 0xEE4E, 0x981D, 0xEE4F, 0x981E, 0xEE50, 0x981F, 0xEE51, 0x9820, 0xEE52, 0x9821, 0xEE53, 0x9822, + 0xEE54, 0x9823, 0xEE55, 0x9824, 0xEE56, 0x9825, 0xEE57, 0x9826, 0xEE58, 0x9827, 0xEE59, 0x9828, 0xEE5A, 0x9829, 0xEE5B, 0x982A, + 0xEE5C, 0x982B, 0xEE5D, 0x982C, 0xEE5E, 0x982D, 0xEE5F, 0x982E, 0xEE60, 0x982F, 0xEE61, 0x9830, 0xEE62, 0x9831, 0xEE63, 0x9832, + 0xEE64, 0x9833, 0xEE65, 0x9834, 0xEE66, 0x9835, 0xEE67, 0x9836, 0xEE68, 0x9837, 0xEE69, 0x9838, 0xEE6A, 0x9839, 0xEE6B, 0x983A, + 0xEE6C, 0x983B, 0xEE6D, 0x983C, 0xEE6E, 0x983D, 0xEE6F, 0x983E, 0xEE70, 0x983F, 0xEE71, 0x9840, 0xEE72, 0x9841, 0xEE73, 0x9842, + 0xEE74, 0x9843, 0xEE75, 0x9844, 0xEE76, 0x9845, 0xEE77, 0x9846, 0xEE78, 0x9847, 0xEE79, 0x9848, 0xEE7A, 0x9849, 0xEE7B, 0x984A, + 0xEE7C, 0x984B, 0xEE7D, 0x984C, 0xEE7E, 0x984D, 0xEE80, 0x984E, 0xEE81, 0x984F, 0xEE82, 0x9850, 0xEE83, 0x9851, 0xEE84, 0x9852, + 0xEE85, 0x9853, 0xEE86, 0x9854, 0xEE87, 0x9855, 0xEE88, 0x9856, 0xEE89, 0x9857, 0xEE8A, 0x9858, 0xEE8B, 0x9859, 0xEE8C, 0x985A, + 0xEE8D, 0x985B, 0xEE8E, 0x985C, 0xEE8F, 0x985D, 0xEE90, 0x985E, 0xEE91, 0x985F, 0xEE92, 0x9860, 0xEE93, 0x9861, 0xEE94, 0x9862, + 0xEE95, 0x9863, 0xEE96, 0x9864, 0xEE97, 0x9865, 0xEE98, 0x9866, 0xEE99, 0x9867, 0xEE9A, 0x9868, 0xEE9B, 0x9869, 0xEE9C, 0x986A, + 0xEE9D, 0x986B, 0xEE9E, 0x986C, 0xEE9F, 0x986D, 0xEEA0, 0x986E, 0xEEA1, 0x7762, 0xEEA2, 0x7765, 0xEEA3, 0x777F, 0xEEA4, 0x778D, + 0xEEA5, 0x777D, 0xEEA6, 0x7780, 0xEEA7, 0x778C, 0xEEA8, 0x7791, 0xEEA9, 0x779F, 0xEEAA, 0x77A0, 0xEEAB, 0x77B0, 0xEEAC, 0x77B5, + 0xEEAD, 0x77BD, 0xEEAE, 0x753A, 0xEEAF, 0x7540, 0xEEB0, 0x754E, 0xEEB1, 0x754B, 0xEEB2, 0x7548, 0xEEB3, 0x755B, 0xEEB4, 0x7572, + 0xEEB5, 0x7579, 0xEEB6, 0x7583, 0xEEB7, 0x7F58, 0xEEB8, 0x7F61, 0xEEB9, 0x7F5F, 0xEEBA, 0x8A48, 0xEEBB, 0x7F68, 0xEEBC, 0x7F74, + 0xEEBD, 0x7F71, 0xEEBE, 0x7F79, 0xEEBF, 0x7F81, 0xEEC0, 0x7F7E, 0xEEC1, 0x76CD, 0xEEC2, 0x76E5, 0xEEC3, 0x8832, 0xEEC4, 0x9485, + 0xEEC5, 0x9486, 0xEEC6, 0x9487, 0xEEC7, 0x948B, 0xEEC8, 0x948A, 0xEEC9, 0x948C, 0xEECA, 0x948D, 0xEECB, 0x948F, 0xEECC, 0x9490, + 0xEECD, 0x9494, 0xEECE, 0x9497, 0xEECF, 0x9495, 0xEED0, 0x949A, 0xEED1, 0x949B, 0xEED2, 0x949C, 0xEED3, 0x94A3, 0xEED4, 0x94A4, + 0xEED5, 0x94AB, 0xEED6, 0x94AA, 0xEED7, 0x94AD, 0xEED8, 0x94AC, 0xEED9, 0x94AF, 0xEEDA, 0x94B0, 0xEEDB, 0x94B2, 0xEEDC, 0x94B4, + 0xEEDD, 0x94B6, 0xEEDE, 0x94B7, 0xEEDF, 0x94B8, 0xEEE0, 0x94B9, 0xEEE1, 0x94BA, 0xEEE2, 0x94BC, 0xEEE3, 0x94BD, 0xEEE4, 0x94BF, + 0xEEE5, 0x94C4, 0xEEE6, 0x94C8, 0xEEE7, 0x94C9, 0xEEE8, 0x94CA, 0xEEE9, 0x94CB, 0xEEEA, 0x94CC, 0xEEEB, 0x94CD, 0xEEEC, 0x94CE, + 0xEEED, 0x94D0, 0xEEEE, 0x94D1, 0xEEEF, 0x94D2, 0xEEF0, 0x94D5, 0xEEF1, 0x94D6, 0xEEF2, 0x94D7, 0xEEF3, 0x94D9, 0xEEF4, 0x94D8, + 0xEEF5, 0x94DB, 0xEEF6, 0x94DE, 0xEEF7, 0x94DF, 0xEEF8, 0x94E0, 0xEEF9, 0x94E2, 0xEEFA, 0x94E4, 0xEEFB, 0x94E5, 0xEEFC, 0x94E7, + 0xEEFD, 0x94E8, 0xEEFE, 0x94EA, 0xEF40, 0x986F, 0xEF41, 0x9870, 0xEF42, 0x9871, 0xEF43, 0x9872, 0xEF44, 0x9873, 0xEF45, 0x9874, + 0xEF46, 0x988B, 0xEF47, 0x988E, 0xEF48, 0x9892, 0xEF49, 0x9895, 0xEF4A, 0x9899, 0xEF4B, 0x98A3, 0xEF4C, 0x98A8, 0xEF4D, 0x98A9, + 0xEF4E, 0x98AA, 0xEF4F, 0x98AB, 0xEF50, 0x98AC, 0xEF51, 0x98AD, 0xEF52, 0x98AE, 0xEF53, 0x98AF, 0xEF54, 0x98B0, 0xEF55, 0x98B1, + 0xEF56, 0x98B2, 0xEF57, 0x98B3, 0xEF58, 0x98B4, 0xEF59, 0x98B5, 0xEF5A, 0x98B6, 0xEF5B, 0x98B7, 0xEF5C, 0x98B8, 0xEF5D, 0x98B9, + 0xEF5E, 0x98BA, 0xEF5F, 0x98BB, 0xEF60, 0x98BC, 0xEF61, 0x98BD, 0xEF62, 0x98BE, 0xEF63, 0x98BF, 0xEF64, 0x98C0, 0xEF65, 0x98C1, + 0xEF66, 0x98C2, 0xEF67, 0x98C3, 0xEF68, 0x98C4, 0xEF69, 0x98C5, 0xEF6A, 0x98C6, 0xEF6B, 0x98C7, 0xEF6C, 0x98C8, 0xEF6D, 0x98C9, + 0xEF6E, 0x98CA, 0xEF6F, 0x98CB, 0xEF70, 0x98CC, 0xEF71, 0x98CD, 0xEF72, 0x98CF, 0xEF73, 0x98D0, 0xEF74, 0x98D4, 0xEF75, 0x98D6, + 0xEF76, 0x98D7, 0xEF77, 0x98DB, 0xEF78, 0x98DC, 0xEF79, 0x98DD, 0xEF7A, 0x98E0, 0xEF7B, 0x98E1, 0xEF7C, 0x98E2, 0xEF7D, 0x98E3, + 0xEF7E, 0x98E4, 0xEF80, 0x98E5, 0xEF81, 0x98E6, 0xEF82, 0x98E9, 0xEF83, 0x98EA, 0xEF84, 0x98EB, 0xEF85, 0x98EC, 0xEF86, 0x98ED, + 0xEF87, 0x98EE, 0xEF88, 0x98EF, 0xEF89, 0x98F0, 0xEF8A, 0x98F1, 0xEF8B, 0x98F2, 0xEF8C, 0x98F3, 0xEF8D, 0x98F4, 0xEF8E, 0x98F5, + 0xEF8F, 0x98F6, 0xEF90, 0x98F7, 0xEF91, 0x98F8, 0xEF92, 0x98F9, 0xEF93, 0x98FA, 0xEF94, 0x98FB, 0xEF95, 0x98FC, 0xEF96, 0x98FD, + 0xEF97, 0x98FE, 0xEF98, 0x98FF, 0xEF99, 0x9900, 0xEF9A, 0x9901, 0xEF9B, 0x9902, 0xEF9C, 0x9903, 0xEF9D, 0x9904, 0xEF9E, 0x9905, + 0xEF9F, 0x9906, 0xEFA0, 0x9907, 0xEFA1, 0x94E9, 0xEFA2, 0x94EB, 0xEFA3, 0x94EE, 0xEFA4, 0x94EF, 0xEFA5, 0x94F3, 0xEFA6, 0x94F4, + 0xEFA7, 0x94F5, 0xEFA8, 0x94F7, 0xEFA9, 0x94F9, 0xEFAA, 0x94FC, 0xEFAB, 0x94FD, 0xEFAC, 0x94FF, 0xEFAD, 0x9503, 0xEFAE, 0x9502, + 0xEFAF, 0x9506, 0xEFB0, 0x9507, 0xEFB1, 0x9509, 0xEFB2, 0x950A, 0xEFB3, 0x950D, 0xEFB4, 0x950E, 0xEFB5, 0x950F, 0xEFB6, 0x9512, + 0xEFB7, 0x9513, 0xEFB8, 0x9514, 0xEFB9, 0x9515, 0xEFBA, 0x9516, 0xEFBB, 0x9518, 0xEFBC, 0x951B, 0xEFBD, 0x951D, 0xEFBE, 0x951E, + 0xEFBF, 0x951F, 0xEFC0, 0x9522, 0xEFC1, 0x952A, 0xEFC2, 0x952B, 0xEFC3, 0x9529, 0xEFC4, 0x952C, 0xEFC5, 0x9531, 0xEFC6, 0x9532, + 0xEFC7, 0x9534, 0xEFC8, 0x9536, 0xEFC9, 0x9537, 0xEFCA, 0x9538, 0xEFCB, 0x953C, 0xEFCC, 0x953E, 0xEFCD, 0x953F, 0xEFCE, 0x9542, + 0xEFCF, 0x9535, 0xEFD0, 0x9544, 0xEFD1, 0x9545, 0xEFD2, 0x9546, 0xEFD3, 0x9549, 0xEFD4, 0x954C, 0xEFD5, 0x954E, 0xEFD6, 0x954F, + 0xEFD7, 0x9552, 0xEFD8, 0x9553, 0xEFD9, 0x9554, 0xEFDA, 0x9556, 0xEFDB, 0x9557, 0xEFDC, 0x9558, 0xEFDD, 0x9559, 0xEFDE, 0x955B, + 0xEFDF, 0x955E, 0xEFE0, 0x955F, 0xEFE1, 0x955D, 0xEFE2, 0x9561, 0xEFE3, 0x9562, 0xEFE4, 0x9564, 0xEFE5, 0x9565, 0xEFE6, 0x9566, + 0xEFE7, 0x9567, 0xEFE8, 0x9568, 0xEFE9, 0x9569, 0xEFEA, 0x956A, 0xEFEB, 0x956B, 0xEFEC, 0x956C, 0xEFED, 0x956F, 0xEFEE, 0x9571, + 0xEFEF, 0x9572, 0xEFF0, 0x9573, 0xEFF1, 0x953A, 0xEFF2, 0x77E7, 0xEFF3, 0x77EC, 0xEFF4, 0x96C9, 0xEFF5, 0x79D5, 0xEFF6, 0x79ED, + 0xEFF7, 0x79E3, 0xEFF8, 0x79EB, 0xEFF9, 0x7A06, 0xEFFA, 0x5D47, 0xEFFB, 0x7A03, 0xEFFC, 0x7A02, 0xEFFD, 0x7A1E, 0xEFFE, 0x7A14, + 0xF040, 0x9908, 0xF041, 0x9909, 0xF042, 0x990A, 0xF043, 0x990B, 0xF044, 0x990C, 0xF045, 0x990E, 0xF046, 0x990F, 0xF047, 0x9911, + 0xF048, 0x9912, 0xF049, 0x9913, 0xF04A, 0x9914, 0xF04B, 0x9915, 0xF04C, 0x9916, 0xF04D, 0x9917, 0xF04E, 0x9918, 0xF04F, 0x9919, + 0xF050, 0x991A, 0xF051, 0x991B, 0xF052, 0x991C, 0xF053, 0x991D, 0xF054, 0x991E, 0xF055, 0x991F, 0xF056, 0x9920, 0xF057, 0x9921, + 0xF058, 0x9922, 0xF059, 0x9923, 0xF05A, 0x9924, 0xF05B, 0x9925, 0xF05C, 0x9926, 0xF05D, 0x9927, 0xF05E, 0x9928, 0xF05F, 0x9929, + 0xF060, 0x992A, 0xF061, 0x992B, 0xF062, 0x992C, 0xF063, 0x992D, 0xF064, 0x992F, 0xF065, 0x9930, 0xF066, 0x9931, 0xF067, 0x9932, + 0xF068, 0x9933, 0xF069, 0x9934, 0xF06A, 0x9935, 0xF06B, 0x9936, 0xF06C, 0x9937, 0xF06D, 0x9938, 0xF06E, 0x9939, 0xF06F, 0x993A, + 0xF070, 0x993B, 0xF071, 0x993C, 0xF072, 0x993D, 0xF073, 0x993E, 0xF074, 0x993F, 0xF075, 0x9940, 0xF076, 0x9941, 0xF077, 0x9942, + 0xF078, 0x9943, 0xF079, 0x9944, 0xF07A, 0x9945, 0xF07B, 0x9946, 0xF07C, 0x9947, 0xF07D, 0x9948, 0xF07E, 0x9949, 0xF080, 0x994A, + 0xF081, 0x994B, 0xF082, 0x994C, 0xF083, 0x994D, 0xF084, 0x994E, 0xF085, 0x994F, 0xF086, 0x9950, 0xF087, 0x9951, 0xF088, 0x9952, + 0xF089, 0x9953, 0xF08A, 0x9956, 0xF08B, 0x9957, 0xF08C, 0x9958, 0xF08D, 0x9959, 0xF08E, 0x995A, 0xF08F, 0x995B, 0xF090, 0x995C, + 0xF091, 0x995D, 0xF092, 0x995E, 0xF093, 0x995F, 0xF094, 0x9960, 0xF095, 0x9961, 0xF096, 0x9962, 0xF097, 0x9964, 0xF098, 0x9966, + 0xF099, 0x9973, 0xF09A, 0x9978, 0xF09B, 0x9979, 0xF09C, 0x997B, 0xF09D, 0x997E, 0xF09E, 0x9982, 0xF09F, 0x9983, 0xF0A0, 0x9989, + 0xF0A1, 0x7A39, 0xF0A2, 0x7A37, 0xF0A3, 0x7A51, 0xF0A4, 0x9ECF, 0xF0A5, 0x99A5, 0xF0A6, 0x7A70, 0xF0A7, 0x7688, 0xF0A8, 0x768E, + 0xF0A9, 0x7693, 0xF0AA, 0x7699, 0xF0AB, 0x76A4, 0xF0AC, 0x74DE, 0xF0AD, 0x74E0, 0xF0AE, 0x752C, 0xF0AF, 0x9E20, 0xF0B0, 0x9E22, + 0xF0B1, 0x9E28, 0xF0B2, 0x9E29, 0xF0B3, 0x9E2A, 0xF0B4, 0x9E2B, 0xF0B5, 0x9E2C, 0xF0B6, 0x9E32, 0xF0B7, 0x9E31, 0xF0B8, 0x9E36, + 0xF0B9, 0x9E38, 0xF0BA, 0x9E37, 0xF0BB, 0x9E39, 0xF0BC, 0x9E3A, 0xF0BD, 0x9E3E, 0xF0BE, 0x9E41, 0xF0BF, 0x9E42, 0xF0C0, 0x9E44, + 0xF0C1, 0x9E46, 0xF0C2, 0x9E47, 0xF0C3, 0x9E48, 0xF0C4, 0x9E49, 0xF0C5, 0x9E4B, 0xF0C6, 0x9E4C, 0xF0C7, 0x9E4E, 0xF0C8, 0x9E51, + 0xF0C9, 0x9E55, 0xF0CA, 0x9E57, 0xF0CB, 0x9E5A, 0xF0CC, 0x9E5B, 0xF0CD, 0x9E5C, 0xF0CE, 0x9E5E, 0xF0CF, 0x9E63, 0xF0D0, 0x9E66, + 0xF0D1, 0x9E67, 0xF0D2, 0x9E68, 0xF0D3, 0x9E69, 0xF0D4, 0x9E6A, 0xF0D5, 0x9E6B, 0xF0D6, 0x9E6C, 0xF0D7, 0x9E71, 0xF0D8, 0x9E6D, + 0xF0D9, 0x9E73, 0xF0DA, 0x7592, 0xF0DB, 0x7594, 0xF0DC, 0x7596, 0xF0DD, 0x75A0, 0xF0DE, 0x759D, 0xF0DF, 0x75AC, 0xF0E0, 0x75A3, + 0xF0E1, 0x75B3, 0xF0E2, 0x75B4, 0xF0E3, 0x75B8, 0xF0E4, 0x75C4, 0xF0E5, 0x75B1, 0xF0E6, 0x75B0, 0xF0E7, 0x75C3, 0xF0E8, 0x75C2, + 0xF0E9, 0x75D6, 0xF0EA, 0x75CD, 0xF0EB, 0x75E3, 0xF0EC, 0x75E8, 0xF0ED, 0x75E6, 0xF0EE, 0x75E4, 0xF0EF, 0x75EB, 0xF0F0, 0x75E7, + 0xF0F1, 0x7603, 0xF0F2, 0x75F1, 0xF0F3, 0x75FC, 0xF0F4, 0x75FF, 0xF0F5, 0x7610, 0xF0F6, 0x7600, 0xF0F7, 0x7605, 0xF0F8, 0x760C, + 0xF0F9, 0x7617, 0xF0FA, 0x760A, 0xF0FB, 0x7625, 0xF0FC, 0x7618, 0xF0FD, 0x7615, 0xF0FE, 0x7619, 0xF140, 0x998C, 0xF141, 0x998E, + 0xF142, 0x999A, 0xF143, 0x999B, 0xF144, 0x999C, 0xF145, 0x999D, 0xF146, 0x999E, 0xF147, 0x999F, 0xF148, 0x99A0, 0xF149, 0x99A1, + 0xF14A, 0x99A2, 0xF14B, 0x99A3, 0xF14C, 0x99A4, 0xF14D, 0x99A6, 0xF14E, 0x99A7, 0xF14F, 0x99A9, 0xF150, 0x99AA, 0xF151, 0x99AB, + 0xF152, 0x99AC, 0xF153, 0x99AD, 0xF154, 0x99AE, 0xF155, 0x99AF, 0xF156, 0x99B0, 0xF157, 0x99B1, 0xF158, 0x99B2, 0xF159, 0x99B3, + 0xF15A, 0x99B4, 0xF15B, 0x99B5, 0xF15C, 0x99B6, 0xF15D, 0x99B7, 0xF15E, 0x99B8, 0xF15F, 0x99B9, 0xF160, 0x99BA, 0xF161, 0x99BB, + 0xF162, 0x99BC, 0xF163, 0x99BD, 0xF164, 0x99BE, 0xF165, 0x99BF, 0xF166, 0x99C0, 0xF167, 0x99C1, 0xF168, 0x99C2, 0xF169, 0x99C3, + 0xF16A, 0x99C4, 0xF16B, 0x99C5, 0xF16C, 0x99C6, 0xF16D, 0x99C7, 0xF16E, 0x99C8, 0xF16F, 0x99C9, 0xF170, 0x99CA, 0xF171, 0x99CB, + 0xF172, 0x99CC, 0xF173, 0x99CD, 0xF174, 0x99CE, 0xF175, 0x99CF, 0xF176, 0x99D0, 0xF177, 0x99D1, 0xF178, 0x99D2, 0xF179, 0x99D3, + 0xF17A, 0x99D4, 0xF17B, 0x99D5, 0xF17C, 0x99D6, 0xF17D, 0x99D7, 0xF17E, 0x99D8, 0xF180, 0x99D9, 0xF181, 0x99DA, 0xF182, 0x99DB, + 0xF183, 0x99DC, 0xF184, 0x99DD, 0xF185, 0x99DE, 0xF186, 0x99DF, 0xF187, 0x99E0, 0xF188, 0x99E1, 0xF189, 0x99E2, 0xF18A, 0x99E3, + 0xF18B, 0x99E4, 0xF18C, 0x99E5, 0xF18D, 0x99E6, 0xF18E, 0x99E7, 0xF18F, 0x99E8, 0xF190, 0x99E9, 0xF191, 0x99EA, 0xF192, 0x99EB, + 0xF193, 0x99EC, 0xF194, 0x99ED, 0xF195, 0x99EE, 0xF196, 0x99EF, 0xF197, 0x99F0, 0xF198, 0x99F1, 0xF199, 0x99F2, 0xF19A, 0x99F3, + 0xF19B, 0x99F4, 0xF19C, 0x99F5, 0xF19D, 0x99F6, 0xF19E, 0x99F7, 0xF19F, 0x99F8, 0xF1A0, 0x99F9, 0xF1A1, 0x761B, 0xF1A2, 0x763C, + 0xF1A3, 0x7622, 0xF1A4, 0x7620, 0xF1A5, 0x7640, 0xF1A6, 0x762D, 0xF1A7, 0x7630, 0xF1A8, 0x763F, 0xF1A9, 0x7635, 0xF1AA, 0x7643, + 0xF1AB, 0x763E, 0xF1AC, 0x7633, 0xF1AD, 0x764D, 0xF1AE, 0x765E, 0xF1AF, 0x7654, 0xF1B0, 0x765C, 0xF1B1, 0x7656, 0xF1B2, 0x766B, + 0xF1B3, 0x766F, 0xF1B4, 0x7FCA, 0xF1B5, 0x7AE6, 0xF1B6, 0x7A78, 0xF1B7, 0x7A79, 0xF1B8, 0x7A80, 0xF1B9, 0x7A86, 0xF1BA, 0x7A88, + 0xF1BB, 0x7A95, 0xF1BC, 0x7AA6, 0xF1BD, 0x7AA0, 0xF1BE, 0x7AAC, 0xF1BF, 0x7AA8, 0xF1C0, 0x7AAD, 0xF1C1, 0x7AB3, 0xF1C2, 0x8864, + 0xF1C3, 0x8869, 0xF1C4, 0x8872, 0xF1C5, 0x887D, 0xF1C6, 0x887F, 0xF1C7, 0x8882, 0xF1C8, 0x88A2, 0xF1C9, 0x88C6, 0xF1CA, 0x88B7, + 0xF1CB, 0x88BC, 0xF1CC, 0x88C9, 0xF1CD, 0x88E2, 0xF1CE, 0x88CE, 0xF1CF, 0x88E3, 0xF1D0, 0x88E5, 0xF1D1, 0x88F1, 0xF1D2, 0x891A, + 0xF1D3, 0x88FC, 0xF1D4, 0x88E8, 0xF1D5, 0x88FE, 0xF1D6, 0x88F0, 0xF1D7, 0x8921, 0xF1D8, 0x8919, 0xF1D9, 0x8913, 0xF1DA, 0x891B, + 0xF1DB, 0x890A, 0xF1DC, 0x8934, 0xF1DD, 0x892B, 0xF1DE, 0x8936, 0xF1DF, 0x8941, 0xF1E0, 0x8966, 0xF1E1, 0x897B, 0xF1E2, 0x758B, + 0xF1E3, 0x80E5, 0xF1E4, 0x76B2, 0xF1E5, 0x76B4, 0xF1E6, 0x77DC, 0xF1E7, 0x8012, 0xF1E8, 0x8014, 0xF1E9, 0x8016, 0xF1EA, 0x801C, + 0xF1EB, 0x8020, 0xF1EC, 0x8022, 0xF1ED, 0x8025, 0xF1EE, 0x8026, 0xF1EF, 0x8027, 0xF1F0, 0x8029, 0xF1F1, 0x8028, 0xF1F2, 0x8031, + 0xF1F3, 0x800B, 0xF1F4, 0x8035, 0xF1F5, 0x8043, 0xF1F6, 0x8046, 0xF1F7, 0x804D, 0xF1F8, 0x8052, 0xF1F9, 0x8069, 0xF1FA, 0x8071, + 0xF1FB, 0x8983, 0xF1FC, 0x9878, 0xF1FD, 0x9880, 0xF1FE, 0x9883, 0xF240, 0x99FA, 0xF241, 0x99FB, 0xF242, 0x99FC, 0xF243, 0x99FD, + 0xF244, 0x99FE, 0xF245, 0x99FF, 0xF246, 0x9A00, 0xF247, 0x9A01, 0xF248, 0x9A02, 0xF249, 0x9A03, 0xF24A, 0x9A04, 0xF24B, 0x9A05, + 0xF24C, 0x9A06, 0xF24D, 0x9A07, 0xF24E, 0x9A08, 0xF24F, 0x9A09, 0xF250, 0x9A0A, 0xF251, 0x9A0B, 0xF252, 0x9A0C, 0xF253, 0x9A0D, + 0xF254, 0x9A0E, 0xF255, 0x9A0F, 0xF256, 0x9A10, 0xF257, 0x9A11, 0xF258, 0x9A12, 0xF259, 0x9A13, 0xF25A, 0x9A14, 0xF25B, 0x9A15, + 0xF25C, 0x9A16, 0xF25D, 0x9A17, 0xF25E, 0x9A18, 0xF25F, 0x9A19, 0xF260, 0x9A1A, 0xF261, 0x9A1B, 0xF262, 0x9A1C, 0xF263, 0x9A1D, + 0xF264, 0x9A1E, 0xF265, 0x9A1F, 0xF266, 0x9A20, 0xF267, 0x9A21, 0xF268, 0x9A22, 0xF269, 0x9A23, 0xF26A, 0x9A24, 0xF26B, 0x9A25, + 0xF26C, 0x9A26, 0xF26D, 0x9A27, 0xF26E, 0x9A28, 0xF26F, 0x9A29, 0xF270, 0x9A2A, 0xF271, 0x9A2B, 0xF272, 0x9A2C, 0xF273, 0x9A2D, + 0xF274, 0x9A2E, 0xF275, 0x9A2F, 0xF276, 0x9A30, 0xF277, 0x9A31, 0xF278, 0x9A32, 0xF279, 0x9A33, 0xF27A, 0x9A34, 0xF27B, 0x9A35, + 0xF27C, 0x9A36, 0xF27D, 0x9A37, 0xF27E, 0x9A38, 0xF280, 0x9A39, 0xF281, 0x9A3A, 0xF282, 0x9A3B, 0xF283, 0x9A3C, 0xF284, 0x9A3D, + 0xF285, 0x9A3E, 0xF286, 0x9A3F, 0xF287, 0x9A40, 0xF288, 0x9A41, 0xF289, 0x9A42, 0xF28A, 0x9A43, 0xF28B, 0x9A44, 0xF28C, 0x9A45, + 0xF28D, 0x9A46, 0xF28E, 0x9A47, 0xF28F, 0x9A48, 0xF290, 0x9A49, 0xF291, 0x9A4A, 0xF292, 0x9A4B, 0xF293, 0x9A4C, 0xF294, 0x9A4D, + 0xF295, 0x9A4E, 0xF296, 0x9A4F, 0xF297, 0x9A50, 0xF298, 0x9A51, 0xF299, 0x9A52, 0xF29A, 0x9A53, 0xF29B, 0x9A54, 0xF29C, 0x9A55, + 0xF29D, 0x9A56, 0xF29E, 0x9A57, 0xF29F, 0x9A58, 0xF2A0, 0x9A59, 0xF2A1, 0x9889, 0xF2A2, 0x988C, 0xF2A3, 0x988D, 0xF2A4, 0x988F, + 0xF2A5, 0x9894, 0xF2A6, 0x989A, 0xF2A7, 0x989B, 0xF2A8, 0x989E, 0xF2A9, 0x989F, 0xF2AA, 0x98A1, 0xF2AB, 0x98A2, 0xF2AC, 0x98A5, + 0xF2AD, 0x98A6, 0xF2AE, 0x864D, 0xF2AF, 0x8654, 0xF2B0, 0x866C, 0xF2B1, 0x866E, 0xF2B2, 0x867F, 0xF2B3, 0x867A, 0xF2B4, 0x867C, + 0xF2B5, 0x867B, 0xF2B6, 0x86A8, 0xF2B7, 0x868D, 0xF2B8, 0x868B, 0xF2B9, 0x86AC, 0xF2BA, 0x869D, 0xF2BB, 0x86A7, 0xF2BC, 0x86A3, + 0xF2BD, 0x86AA, 0xF2BE, 0x8693, 0xF2BF, 0x86A9, 0xF2C0, 0x86B6, 0xF2C1, 0x86C4, 0xF2C2, 0x86B5, 0xF2C3, 0x86CE, 0xF2C4, 0x86B0, + 0xF2C5, 0x86BA, 0xF2C6, 0x86B1, 0xF2C7, 0x86AF, 0xF2C8, 0x86C9, 0xF2C9, 0x86CF, 0xF2CA, 0x86B4, 0xF2CB, 0x86E9, 0xF2CC, 0x86F1, + 0xF2CD, 0x86F2, 0xF2CE, 0x86ED, 0xF2CF, 0x86F3, 0xF2D0, 0x86D0, 0xF2D1, 0x8713, 0xF2D2, 0x86DE, 0xF2D3, 0x86F4, 0xF2D4, 0x86DF, + 0xF2D5, 0x86D8, 0xF2D6, 0x86D1, 0xF2D7, 0x8703, 0xF2D8, 0x8707, 0xF2D9, 0x86F8, 0xF2DA, 0x8708, 0xF2DB, 0x870A, 0xF2DC, 0x870D, + 0xF2DD, 0x8709, 0xF2DE, 0x8723, 0xF2DF, 0x873B, 0xF2E0, 0x871E, 0xF2E1, 0x8725, 0xF2E2, 0x872E, 0xF2E3, 0x871A, 0xF2E4, 0x873E, + 0xF2E5, 0x8748, 0xF2E6, 0x8734, 0xF2E7, 0x8731, 0xF2E8, 0x8729, 0xF2E9, 0x8737, 0xF2EA, 0x873F, 0xF2EB, 0x8782, 0xF2EC, 0x8722, + 0xF2ED, 0x877D, 0xF2EE, 0x877E, 0xF2EF, 0x877B, 0xF2F0, 0x8760, 0xF2F1, 0x8770, 0xF2F2, 0x874C, 0xF2F3, 0x876E, 0xF2F4, 0x878B, + 0xF2F5, 0x8753, 0xF2F6, 0x8763, 0xF2F7, 0x877C, 0xF2F8, 0x8764, 0xF2F9, 0x8759, 0xF2FA, 0x8765, 0xF2FB, 0x8793, 0xF2FC, 0x87AF, + 0xF2FD, 0x87A8, 0xF2FE, 0x87D2, 0xF340, 0x9A5A, 0xF341, 0x9A5B, 0xF342, 0x9A5C, 0xF343, 0x9A5D, 0xF344, 0x9A5E, 0xF345, 0x9A5F, + 0xF346, 0x9A60, 0xF347, 0x9A61, 0xF348, 0x9A62, 0xF349, 0x9A63, 0xF34A, 0x9A64, 0xF34B, 0x9A65, 0xF34C, 0x9A66, 0xF34D, 0x9A67, + 0xF34E, 0x9A68, 0xF34F, 0x9A69, 0xF350, 0x9A6A, 0xF351, 0x9A6B, 0xF352, 0x9A72, 0xF353, 0x9A83, 0xF354, 0x9A89, 0xF355, 0x9A8D, + 0xF356, 0x9A8E, 0xF357, 0x9A94, 0xF358, 0x9A95, 0xF359, 0x9A99, 0xF35A, 0x9AA6, 0xF35B, 0x9AA9, 0xF35C, 0x9AAA, 0xF35D, 0x9AAB, + 0xF35E, 0x9AAC, 0xF35F, 0x9AAD, 0xF360, 0x9AAE, 0xF361, 0x9AAF, 0xF362, 0x9AB2, 0xF363, 0x9AB3, 0xF364, 0x9AB4, 0xF365, 0x9AB5, + 0xF366, 0x9AB9, 0xF367, 0x9ABB, 0xF368, 0x9ABD, 0xF369, 0x9ABE, 0xF36A, 0x9ABF, 0xF36B, 0x9AC3, 0xF36C, 0x9AC4, 0xF36D, 0x9AC6, + 0xF36E, 0x9AC7, 0xF36F, 0x9AC8, 0xF370, 0x9AC9, 0xF371, 0x9ACA, 0xF372, 0x9ACD, 0xF373, 0x9ACE, 0xF374, 0x9ACF, 0xF375, 0x9AD0, + 0xF376, 0x9AD2, 0xF377, 0x9AD4, 0xF378, 0x9AD5, 0xF379, 0x9AD6, 0xF37A, 0x9AD7, 0xF37B, 0x9AD9, 0xF37C, 0x9ADA, 0xF37D, 0x9ADB, + 0xF37E, 0x9ADC, 0xF380, 0x9ADD, 0xF381, 0x9ADE, 0xF382, 0x9AE0, 0xF383, 0x9AE2, 0xF384, 0x9AE3, 0xF385, 0x9AE4, 0xF386, 0x9AE5, + 0xF387, 0x9AE7, 0xF388, 0x9AE8, 0xF389, 0x9AE9, 0xF38A, 0x9AEA, 0xF38B, 0x9AEC, 0xF38C, 0x9AEE, 0xF38D, 0x9AF0, 0xF38E, 0x9AF1, + 0xF38F, 0x9AF2, 0xF390, 0x9AF3, 0xF391, 0x9AF4, 0xF392, 0x9AF5, 0xF393, 0x9AF6, 0xF394, 0x9AF7, 0xF395, 0x9AF8, 0xF396, 0x9AFA, + 0xF397, 0x9AFC, 0xF398, 0x9AFD, 0xF399, 0x9AFE, 0xF39A, 0x9AFF, 0xF39B, 0x9B00, 0xF39C, 0x9B01, 0xF39D, 0x9B02, 0xF39E, 0x9B04, + 0xF39F, 0x9B05, 0xF3A0, 0x9B06, 0xF3A1, 0x87C6, 0xF3A2, 0x8788, 0xF3A3, 0x8785, 0xF3A4, 0x87AD, 0xF3A5, 0x8797, 0xF3A6, 0x8783, + 0xF3A7, 0x87AB, 0xF3A8, 0x87E5, 0xF3A9, 0x87AC, 0xF3AA, 0x87B5, 0xF3AB, 0x87B3, 0xF3AC, 0x87CB, 0xF3AD, 0x87D3, 0xF3AE, 0x87BD, + 0xF3AF, 0x87D1, 0xF3B0, 0x87C0, 0xF3B1, 0x87CA, 0xF3B2, 0x87DB, 0xF3B3, 0x87EA, 0xF3B4, 0x87E0, 0xF3B5, 0x87EE, 0xF3B6, 0x8816, + 0xF3B7, 0x8813, 0xF3B8, 0x87FE, 0xF3B9, 0x880A, 0xF3BA, 0x881B, 0xF3BB, 0x8821, 0xF3BC, 0x8839, 0xF3BD, 0x883C, 0xF3BE, 0x7F36, + 0xF3BF, 0x7F42, 0xF3C0, 0x7F44, 0xF3C1, 0x7F45, 0xF3C2, 0x8210, 0xF3C3, 0x7AFA, 0xF3C4, 0x7AFD, 0xF3C5, 0x7B08, 0xF3C6, 0x7B03, + 0xF3C7, 0x7B04, 0xF3C8, 0x7B15, 0xF3C9, 0x7B0A, 0xF3CA, 0x7B2B, 0xF3CB, 0x7B0F, 0xF3CC, 0x7B47, 0xF3CD, 0x7B38, 0xF3CE, 0x7B2A, + 0xF3CF, 0x7B19, 0xF3D0, 0x7B2E, 0xF3D1, 0x7B31, 0xF3D2, 0x7B20, 0xF3D3, 0x7B25, 0xF3D4, 0x7B24, 0xF3D5, 0x7B33, 0xF3D6, 0x7B3E, + 0xF3D7, 0x7B1E, 0xF3D8, 0x7B58, 0xF3D9, 0x7B5A, 0xF3DA, 0x7B45, 0xF3DB, 0x7B75, 0xF3DC, 0x7B4C, 0xF3DD, 0x7B5D, 0xF3DE, 0x7B60, + 0xF3DF, 0x7B6E, 0xF3E0, 0x7B7B, 0xF3E1, 0x7B62, 0xF3E2, 0x7B72, 0xF3E3, 0x7B71, 0xF3E4, 0x7B90, 0xF3E5, 0x7BA6, 0xF3E6, 0x7BA7, + 0xF3E7, 0x7BB8, 0xF3E8, 0x7BAC, 0xF3E9, 0x7B9D, 0xF3EA, 0x7BA8, 0xF3EB, 0x7B85, 0xF3EC, 0x7BAA, 0xF3ED, 0x7B9C, 0xF3EE, 0x7BA2, + 0xF3EF, 0x7BAB, 0xF3F0, 0x7BB4, 0xF3F1, 0x7BD1, 0xF3F2, 0x7BC1, 0xF3F3, 0x7BCC, 0xF3F4, 0x7BDD, 0xF3F5, 0x7BDA, 0xF3F6, 0x7BE5, + 0xF3F7, 0x7BE6, 0xF3F8, 0x7BEA, 0xF3F9, 0x7C0C, 0xF3FA, 0x7BFE, 0xF3FB, 0x7BFC, 0xF3FC, 0x7C0F, 0xF3FD, 0x7C16, 0xF3FE, 0x7C0B, + 0xF440, 0x9B07, 0xF441, 0x9B09, 0xF442, 0x9B0A, 0xF443, 0x9B0B, 0xF444, 0x9B0C, 0xF445, 0x9B0D, 0xF446, 0x9B0E, 0xF447, 0x9B10, + 0xF448, 0x9B11, 0xF449, 0x9B12, 0xF44A, 0x9B14, 0xF44B, 0x9B15, 0xF44C, 0x9B16, 0xF44D, 0x9B17, 0xF44E, 0x9B18, 0xF44F, 0x9B19, + 0xF450, 0x9B1A, 0xF451, 0x9B1B, 0xF452, 0x9B1C, 0xF453, 0x9B1D, 0xF454, 0x9B1E, 0xF455, 0x9B20, 0xF456, 0x9B21, 0xF457, 0x9B22, + 0xF458, 0x9B24, 0xF459, 0x9B25, 0xF45A, 0x9B26, 0xF45B, 0x9B27, 0xF45C, 0x9B28, 0xF45D, 0x9B29, 0xF45E, 0x9B2A, 0xF45F, 0x9B2B, + 0xF460, 0x9B2C, 0xF461, 0x9B2D, 0xF462, 0x9B2E, 0xF463, 0x9B30, 0xF464, 0x9B31, 0xF465, 0x9B33, 0xF466, 0x9B34, 0xF467, 0x9B35, + 0xF468, 0x9B36, 0xF469, 0x9B37, 0xF46A, 0x9B38, 0xF46B, 0x9B39, 0xF46C, 0x9B3A, 0xF46D, 0x9B3D, 0xF46E, 0x9B3E, 0xF46F, 0x9B3F, + 0xF470, 0x9B40, 0xF471, 0x9B46, 0xF472, 0x9B4A, 0xF473, 0x9B4B, 0xF474, 0x9B4C, 0xF475, 0x9B4E, 0xF476, 0x9B50, 0xF477, 0x9B52, + 0xF478, 0x9B53, 0xF479, 0x9B55, 0xF47A, 0x9B56, 0xF47B, 0x9B57, 0xF47C, 0x9B58, 0xF47D, 0x9B59, 0xF47E, 0x9B5A, 0xF480, 0x9B5B, + 0xF481, 0x9B5C, 0xF482, 0x9B5D, 0xF483, 0x9B5E, 0xF484, 0x9B5F, 0xF485, 0x9B60, 0xF486, 0x9B61, 0xF487, 0x9B62, 0xF488, 0x9B63, + 0xF489, 0x9B64, 0xF48A, 0x9B65, 0xF48B, 0x9B66, 0xF48C, 0x9B67, 0xF48D, 0x9B68, 0xF48E, 0x9B69, 0xF48F, 0x9B6A, 0xF490, 0x9B6B, + 0xF491, 0x9B6C, 0xF492, 0x9B6D, 0xF493, 0x9B6E, 0xF494, 0x9B6F, 0xF495, 0x9B70, 0xF496, 0x9B71, 0xF497, 0x9B72, 0xF498, 0x9B73, + 0xF499, 0x9B74, 0xF49A, 0x9B75, 0xF49B, 0x9B76, 0xF49C, 0x9B77, 0xF49D, 0x9B78, 0xF49E, 0x9B79, 0xF49F, 0x9B7A, 0xF4A0, 0x9B7B, + 0xF4A1, 0x7C1F, 0xF4A2, 0x7C2A, 0xF4A3, 0x7C26, 0xF4A4, 0x7C38, 0xF4A5, 0x7C41, 0xF4A6, 0x7C40, 0xF4A7, 0x81FE, 0xF4A8, 0x8201, + 0xF4A9, 0x8202, 0xF4AA, 0x8204, 0xF4AB, 0x81EC, 0xF4AC, 0x8844, 0xF4AD, 0x8221, 0xF4AE, 0x8222, 0xF4AF, 0x8223, 0xF4B0, 0x822D, + 0xF4B1, 0x822F, 0xF4B2, 0x8228, 0xF4B3, 0x822B, 0xF4B4, 0x8238, 0xF4B5, 0x823B, 0xF4B6, 0x8233, 0xF4B7, 0x8234, 0xF4B8, 0x823E, + 0xF4B9, 0x8244, 0xF4BA, 0x8249, 0xF4BB, 0x824B, 0xF4BC, 0x824F, 0xF4BD, 0x825A, 0xF4BE, 0x825F, 0xF4BF, 0x8268, 0xF4C0, 0x887E, + 0xF4C1, 0x8885, 0xF4C2, 0x8888, 0xF4C3, 0x88D8, 0xF4C4, 0x88DF, 0xF4C5, 0x895E, 0xF4C6, 0x7F9D, 0xF4C7, 0x7F9F, 0xF4C8, 0x7FA7, + 0xF4C9, 0x7FAF, 0xF4CA, 0x7FB0, 0xF4CB, 0x7FB2, 0xF4CC, 0x7C7C, 0xF4CD, 0x6549, 0xF4CE, 0x7C91, 0xF4CF, 0x7C9D, 0xF4D0, 0x7C9C, + 0xF4D1, 0x7C9E, 0xF4D2, 0x7CA2, 0xF4D3, 0x7CB2, 0xF4D4, 0x7CBC, 0xF4D5, 0x7CBD, 0xF4D6, 0x7CC1, 0xF4D7, 0x7CC7, 0xF4D8, 0x7CCC, + 0xF4D9, 0x7CCD, 0xF4DA, 0x7CC8, 0xF4DB, 0x7CC5, 0xF4DC, 0x7CD7, 0xF4DD, 0x7CE8, 0xF4DE, 0x826E, 0xF4DF, 0x66A8, 0xF4E0, 0x7FBF, + 0xF4E1, 0x7FCE, 0xF4E2, 0x7FD5, 0xF4E3, 0x7FE5, 0xF4E4, 0x7FE1, 0xF4E5, 0x7FE6, 0xF4E6, 0x7FE9, 0xF4E7, 0x7FEE, 0xF4E8, 0x7FF3, + 0xF4E9, 0x7CF8, 0xF4EA, 0x7D77, 0xF4EB, 0x7DA6, 0xF4EC, 0x7DAE, 0xF4ED, 0x7E47, 0xF4EE, 0x7E9B, 0xF4EF, 0x9EB8, 0xF4F0, 0x9EB4, + 0xF4F1, 0x8D73, 0xF4F2, 0x8D84, 0xF4F3, 0x8D94, 0xF4F4, 0x8D91, 0xF4F5, 0x8DB1, 0xF4F6, 0x8D67, 0xF4F7, 0x8D6D, 0xF4F8, 0x8C47, + 0xF4F9, 0x8C49, 0xF4FA, 0x914A, 0xF4FB, 0x9150, 0xF4FC, 0x914E, 0xF4FD, 0x914F, 0xF4FE, 0x9164, 0xF540, 0x9B7C, 0xF541, 0x9B7D, + 0xF542, 0x9B7E, 0xF543, 0x9B7F, 0xF544, 0x9B80, 0xF545, 0x9B81, 0xF546, 0x9B82, 0xF547, 0x9B83, 0xF548, 0x9B84, 0xF549, 0x9B85, + 0xF54A, 0x9B86, 0xF54B, 0x9B87, 0xF54C, 0x9B88, 0xF54D, 0x9B89, 0xF54E, 0x9B8A, 0xF54F, 0x9B8B, 0xF550, 0x9B8C, 0xF551, 0x9B8D, + 0xF552, 0x9B8E, 0xF553, 0x9B8F, 0xF554, 0x9B90, 0xF555, 0x9B91, 0xF556, 0x9B92, 0xF557, 0x9B93, 0xF558, 0x9B94, 0xF559, 0x9B95, + 0xF55A, 0x9B96, 0xF55B, 0x9B97, 0xF55C, 0x9B98, 0xF55D, 0x9B99, 0xF55E, 0x9B9A, 0xF55F, 0x9B9B, 0xF560, 0x9B9C, 0xF561, 0x9B9D, + 0xF562, 0x9B9E, 0xF563, 0x9B9F, 0xF564, 0x9BA0, 0xF565, 0x9BA1, 0xF566, 0x9BA2, 0xF567, 0x9BA3, 0xF568, 0x9BA4, 0xF569, 0x9BA5, + 0xF56A, 0x9BA6, 0xF56B, 0x9BA7, 0xF56C, 0x9BA8, 0xF56D, 0x9BA9, 0xF56E, 0x9BAA, 0xF56F, 0x9BAB, 0xF570, 0x9BAC, 0xF571, 0x9BAD, + 0xF572, 0x9BAE, 0xF573, 0x9BAF, 0xF574, 0x9BB0, 0xF575, 0x9BB1, 0xF576, 0x9BB2, 0xF577, 0x9BB3, 0xF578, 0x9BB4, 0xF579, 0x9BB5, + 0xF57A, 0x9BB6, 0xF57B, 0x9BB7, 0xF57C, 0x9BB8, 0xF57D, 0x9BB9, 0xF57E, 0x9BBA, 0xF580, 0x9BBB, 0xF581, 0x9BBC, 0xF582, 0x9BBD, + 0xF583, 0x9BBE, 0xF584, 0x9BBF, 0xF585, 0x9BC0, 0xF586, 0x9BC1, 0xF587, 0x9BC2, 0xF588, 0x9BC3, 0xF589, 0x9BC4, 0xF58A, 0x9BC5, + 0xF58B, 0x9BC6, 0xF58C, 0x9BC7, 0xF58D, 0x9BC8, 0xF58E, 0x9BC9, 0xF58F, 0x9BCA, 0xF590, 0x9BCB, 0xF591, 0x9BCC, 0xF592, 0x9BCD, + 0xF593, 0x9BCE, 0xF594, 0x9BCF, 0xF595, 0x9BD0, 0xF596, 0x9BD1, 0xF597, 0x9BD2, 0xF598, 0x9BD3, 0xF599, 0x9BD4, 0xF59A, 0x9BD5, + 0xF59B, 0x9BD6, 0xF59C, 0x9BD7, 0xF59D, 0x9BD8, 0xF59E, 0x9BD9, 0xF59F, 0x9BDA, 0xF5A0, 0x9BDB, 0xF5A1, 0x9162, 0xF5A2, 0x9161, + 0xF5A3, 0x9170, 0xF5A4, 0x9169, 0xF5A5, 0x916F, 0xF5A6, 0x917D, 0xF5A7, 0x917E, 0xF5A8, 0x9172, 0xF5A9, 0x9174, 0xF5AA, 0x9179, + 0xF5AB, 0x918C, 0xF5AC, 0x9185, 0xF5AD, 0x9190, 0xF5AE, 0x918D, 0xF5AF, 0x9191, 0xF5B0, 0x91A2, 0xF5B1, 0x91A3, 0xF5B2, 0x91AA, + 0xF5B3, 0x91AD, 0xF5B4, 0x91AE, 0xF5B5, 0x91AF, 0xF5B6, 0x91B5, 0xF5B7, 0x91B4, 0xF5B8, 0x91BA, 0xF5B9, 0x8C55, 0xF5BA, 0x9E7E, + 0xF5BB, 0x8DB8, 0xF5BC, 0x8DEB, 0xF5BD, 0x8E05, 0xF5BE, 0x8E59, 0xF5BF, 0x8E69, 0xF5C0, 0x8DB5, 0xF5C1, 0x8DBF, 0xF5C2, 0x8DBC, + 0xF5C3, 0x8DBA, 0xF5C4, 0x8DC4, 0xF5C5, 0x8DD6, 0xF5C6, 0x8DD7, 0xF5C7, 0x8DDA, 0xF5C8, 0x8DDE, 0xF5C9, 0x8DCE, 0xF5CA, 0x8DCF, + 0xF5CB, 0x8DDB, 0xF5CC, 0x8DC6, 0xF5CD, 0x8DEC, 0xF5CE, 0x8DF7, 0xF5CF, 0x8DF8, 0xF5D0, 0x8DE3, 0xF5D1, 0x8DF9, 0xF5D2, 0x8DFB, + 0xF5D3, 0x8DE4, 0xF5D4, 0x8E09, 0xF5D5, 0x8DFD, 0xF5D6, 0x8E14, 0xF5D7, 0x8E1D, 0xF5D8, 0x8E1F, 0xF5D9, 0x8E2C, 0xF5DA, 0x8E2E, + 0xF5DB, 0x8E23, 0xF5DC, 0x8E2F, 0xF5DD, 0x8E3A, 0xF5DE, 0x8E40, 0xF5DF, 0x8E39, 0xF5E0, 0x8E35, 0xF5E1, 0x8E3D, 0xF5E2, 0x8E31, + 0xF5E3, 0x8E49, 0xF5E4, 0x8E41, 0xF5E5, 0x8E42, 0xF5E6, 0x8E51, 0xF5E7, 0x8E52, 0xF5E8, 0x8E4A, 0xF5E9, 0x8E70, 0xF5EA, 0x8E76, + 0xF5EB, 0x8E7C, 0xF5EC, 0x8E6F, 0xF5ED, 0x8E74, 0xF5EE, 0x8E85, 0xF5EF, 0x8E8F, 0xF5F0, 0x8E94, 0xF5F1, 0x8E90, 0xF5F2, 0x8E9C, + 0xF5F3, 0x8E9E, 0xF5F4, 0x8C78, 0xF5F5, 0x8C82, 0xF5F6, 0x8C8A, 0xF5F7, 0x8C85, 0xF5F8, 0x8C98, 0xF5F9, 0x8C94, 0xF5FA, 0x659B, + 0xF5FB, 0x89D6, 0xF5FC, 0x89DE, 0xF5FD, 0x89DA, 0xF5FE, 0x89DC, 0xF640, 0x9BDC, 0xF641, 0x9BDD, 0xF642, 0x9BDE, 0xF643, 0x9BDF, + 0xF644, 0x9BE0, 0xF645, 0x9BE1, 0xF646, 0x9BE2, 0xF647, 0x9BE3, 0xF648, 0x9BE4, 0xF649, 0x9BE5, 0xF64A, 0x9BE6, 0xF64B, 0x9BE7, + 0xF64C, 0x9BE8, 0xF64D, 0x9BE9, 0xF64E, 0x9BEA, 0xF64F, 0x9BEB, 0xF650, 0x9BEC, 0xF651, 0x9BED, 0xF652, 0x9BEE, 0xF653, 0x9BEF, + 0xF654, 0x9BF0, 0xF655, 0x9BF1, 0xF656, 0x9BF2, 0xF657, 0x9BF3, 0xF658, 0x9BF4, 0xF659, 0x9BF5, 0xF65A, 0x9BF6, 0xF65B, 0x9BF7, + 0xF65C, 0x9BF8, 0xF65D, 0x9BF9, 0xF65E, 0x9BFA, 0xF65F, 0x9BFB, 0xF660, 0x9BFC, 0xF661, 0x9BFD, 0xF662, 0x9BFE, 0xF663, 0x9BFF, + 0xF664, 0x9C00, 0xF665, 0x9C01, 0xF666, 0x9C02, 0xF667, 0x9C03, 0xF668, 0x9C04, 0xF669, 0x9C05, 0xF66A, 0x9C06, 0xF66B, 0x9C07, + 0xF66C, 0x9C08, 0xF66D, 0x9C09, 0xF66E, 0x9C0A, 0xF66F, 0x9C0B, 0xF670, 0x9C0C, 0xF671, 0x9C0D, 0xF672, 0x9C0E, 0xF673, 0x9C0F, + 0xF674, 0x9C10, 0xF675, 0x9C11, 0xF676, 0x9C12, 0xF677, 0x9C13, 0xF678, 0x9C14, 0xF679, 0x9C15, 0xF67A, 0x9C16, 0xF67B, 0x9C17, + 0xF67C, 0x9C18, 0xF67D, 0x9C19, 0xF67E, 0x9C1A, 0xF680, 0x9C1B, 0xF681, 0x9C1C, 0xF682, 0x9C1D, 0xF683, 0x9C1E, 0xF684, 0x9C1F, + 0xF685, 0x9C20, 0xF686, 0x9C21, 0xF687, 0x9C22, 0xF688, 0x9C23, 0xF689, 0x9C24, 0xF68A, 0x9C25, 0xF68B, 0x9C26, 0xF68C, 0x9C27, + 0xF68D, 0x9C28, 0xF68E, 0x9C29, 0xF68F, 0x9C2A, 0xF690, 0x9C2B, 0xF691, 0x9C2C, 0xF692, 0x9C2D, 0xF693, 0x9C2E, 0xF694, 0x9C2F, + 0xF695, 0x9C30, 0xF696, 0x9C31, 0xF697, 0x9C32, 0xF698, 0x9C33, 0xF699, 0x9C34, 0xF69A, 0x9C35, 0xF69B, 0x9C36, 0xF69C, 0x9C37, + 0xF69D, 0x9C38, 0xF69E, 0x9C39, 0xF69F, 0x9C3A, 0xF6A0, 0x9C3B, 0xF6A1, 0x89E5, 0xF6A2, 0x89EB, 0xF6A3, 0x89EF, 0xF6A4, 0x8A3E, + 0xF6A5, 0x8B26, 0xF6A6, 0x9753, 0xF6A7, 0x96E9, 0xF6A8, 0x96F3, 0xF6A9, 0x96EF, 0xF6AA, 0x9706, 0xF6AB, 0x9701, 0xF6AC, 0x9708, + 0xF6AD, 0x970F, 0xF6AE, 0x970E, 0xF6AF, 0x972A, 0xF6B0, 0x972D, 0xF6B1, 0x9730, 0xF6B2, 0x973E, 0xF6B3, 0x9F80, 0xF6B4, 0x9F83, + 0xF6B5, 0x9F85, 0xF6B6, 0x9F86, 0xF6B7, 0x9F87, 0xF6B8, 0x9F88, 0xF6B9, 0x9F89, 0xF6BA, 0x9F8A, 0xF6BB, 0x9F8C, 0xF6BC, 0x9EFE, + 0xF6BD, 0x9F0B, 0xF6BE, 0x9F0D, 0xF6BF, 0x96B9, 0xF6C0, 0x96BC, 0xF6C1, 0x96BD, 0xF6C2, 0x96CE, 0xF6C3, 0x96D2, 0xF6C4, 0x77BF, + 0xF6C5, 0x96E0, 0xF6C6, 0x928E, 0xF6C7, 0x92AE, 0xF6C8, 0x92C8, 0xF6C9, 0x933E, 0xF6CA, 0x936A, 0xF6CB, 0x93CA, 0xF6CC, 0x938F, + 0xF6CD, 0x943E, 0xF6CE, 0x946B, 0xF6CF, 0x9C7F, 0xF6D0, 0x9C82, 0xF6D1, 0x9C85, 0xF6D2, 0x9C86, 0xF6D3, 0x9C87, 0xF6D4, 0x9C88, + 0xF6D5, 0x7A23, 0xF6D6, 0x9C8B, 0xF6D7, 0x9C8E, 0xF6D8, 0x9C90, 0xF6D9, 0x9C91, 0xF6DA, 0x9C92, 0xF6DB, 0x9C94, 0xF6DC, 0x9C95, + 0xF6DD, 0x9C9A, 0xF6DE, 0x9C9B, 0xF6DF, 0x9C9E, 0xF6E0, 0x9C9F, 0xF6E1, 0x9CA0, 0xF6E2, 0x9CA1, 0xF6E3, 0x9CA2, 0xF6E4, 0x9CA3, + 0xF6E5, 0x9CA5, 0xF6E6, 0x9CA6, 0xF6E7, 0x9CA7, 0xF6E8, 0x9CA8, 0xF6E9, 0x9CA9, 0xF6EA, 0x9CAB, 0xF6EB, 0x9CAD, 0xF6EC, 0x9CAE, + 0xF6ED, 0x9CB0, 0xF6EE, 0x9CB1, 0xF6EF, 0x9CB2, 0xF6F0, 0x9CB3, 0xF6F1, 0x9CB4, 0xF6F2, 0x9CB5, 0xF6F3, 0x9CB6, 0xF6F4, 0x9CB7, + 0xF6F5, 0x9CBA, 0xF6F6, 0x9CBB, 0xF6F7, 0x9CBC, 0xF6F8, 0x9CBD, 0xF6F9, 0x9CC4, 0xF6FA, 0x9CC5, 0xF6FB, 0x9CC6, 0xF6FC, 0x9CC7, + 0xF6FD, 0x9CCA, 0xF6FE, 0x9CCB, 0xF740, 0x9C3C, 0xF741, 0x9C3D, 0xF742, 0x9C3E, 0xF743, 0x9C3F, 0xF744, 0x9C40, 0xF745, 0x9C41, + 0xF746, 0x9C42, 0xF747, 0x9C43, 0xF748, 0x9C44, 0xF749, 0x9C45, 0xF74A, 0x9C46, 0xF74B, 0x9C47, 0xF74C, 0x9C48, 0xF74D, 0x9C49, + 0xF74E, 0x9C4A, 0xF74F, 0x9C4B, 0xF750, 0x9C4C, 0xF751, 0x9C4D, 0xF752, 0x9C4E, 0xF753, 0x9C4F, 0xF754, 0x9C50, 0xF755, 0x9C51, + 0xF756, 0x9C52, 0xF757, 0x9C53, 0xF758, 0x9C54, 0xF759, 0x9C55, 0xF75A, 0x9C56, 0xF75B, 0x9C57, 0xF75C, 0x9C58, 0xF75D, 0x9C59, + 0xF75E, 0x9C5A, 0xF75F, 0x9C5B, 0xF760, 0x9C5C, 0xF761, 0x9C5D, 0xF762, 0x9C5E, 0xF763, 0x9C5F, 0xF764, 0x9C60, 0xF765, 0x9C61, + 0xF766, 0x9C62, 0xF767, 0x9C63, 0xF768, 0x9C64, 0xF769, 0x9C65, 0xF76A, 0x9C66, 0xF76B, 0x9C67, 0xF76C, 0x9C68, 0xF76D, 0x9C69, + 0xF76E, 0x9C6A, 0xF76F, 0x9C6B, 0xF770, 0x9C6C, 0xF771, 0x9C6D, 0xF772, 0x9C6E, 0xF773, 0x9C6F, 0xF774, 0x9C70, 0xF775, 0x9C71, + 0xF776, 0x9C72, 0xF777, 0x9C73, 0xF778, 0x9C74, 0xF779, 0x9C75, 0xF77A, 0x9C76, 0xF77B, 0x9C77, 0xF77C, 0x9C78, 0xF77D, 0x9C79, + 0xF77E, 0x9C7A, 0xF780, 0x9C7B, 0xF781, 0x9C7D, 0xF782, 0x9C7E, 0xF783, 0x9C80, 0xF784, 0x9C83, 0xF785, 0x9C84, 0xF786, 0x9C89, + 0xF787, 0x9C8A, 0xF788, 0x9C8C, 0xF789, 0x9C8F, 0xF78A, 0x9C93, 0xF78B, 0x9C96, 0xF78C, 0x9C97, 0xF78D, 0x9C98, 0xF78E, 0x9C99, + 0xF78F, 0x9C9D, 0xF790, 0x9CAA, 0xF791, 0x9CAC, 0xF792, 0x9CAF, 0xF793, 0x9CB9, 0xF794, 0x9CBE, 0xF795, 0x9CBF, 0xF796, 0x9CC0, + 0xF797, 0x9CC1, 0xF798, 0x9CC2, 0xF799, 0x9CC8, 0xF79A, 0x9CC9, 0xF79B, 0x9CD1, 0xF79C, 0x9CD2, 0xF79D, 0x9CDA, 0xF79E, 0x9CDB, + 0xF79F, 0x9CE0, 0xF7A0, 0x9CE1, 0xF7A1, 0x9CCC, 0xF7A2, 0x9CCD, 0xF7A3, 0x9CCE, 0xF7A4, 0x9CCF, 0xF7A5, 0x9CD0, 0xF7A6, 0x9CD3, + 0xF7A7, 0x9CD4, 0xF7A8, 0x9CD5, 0xF7A9, 0x9CD7, 0xF7AA, 0x9CD8, 0xF7AB, 0x9CD9, 0xF7AC, 0x9CDC, 0xF7AD, 0x9CDD, 0xF7AE, 0x9CDF, + 0xF7AF, 0x9CE2, 0xF7B0, 0x977C, 0xF7B1, 0x9785, 0xF7B2, 0x9791, 0xF7B3, 0x9792, 0xF7B4, 0x9794, 0xF7B5, 0x97AF, 0xF7B6, 0x97AB, + 0xF7B7, 0x97A3, 0xF7B8, 0x97B2, 0xF7B9, 0x97B4, 0xF7BA, 0x9AB1, 0xF7BB, 0x9AB0, 0xF7BC, 0x9AB7, 0xF7BD, 0x9E58, 0xF7BE, 0x9AB6, + 0xF7BF, 0x9ABA, 0xF7C0, 0x9ABC, 0xF7C1, 0x9AC1, 0xF7C2, 0x9AC0, 0xF7C3, 0x9AC5, 0xF7C4, 0x9AC2, 0xF7C5, 0x9ACB, 0xF7C6, 0x9ACC, + 0xF7C7, 0x9AD1, 0xF7C8, 0x9B45, 0xF7C9, 0x9B43, 0xF7CA, 0x9B47, 0xF7CB, 0x9B49, 0xF7CC, 0x9B48, 0xF7CD, 0x9B4D, 0xF7CE, 0x9B51, + 0xF7CF, 0x98E8, 0xF7D0, 0x990D, 0xF7D1, 0x992E, 0xF7D2, 0x9955, 0xF7D3, 0x9954, 0xF7D4, 0x9ADF, 0xF7D5, 0x9AE1, 0xF7D6, 0x9AE6, + 0xF7D7, 0x9AEF, 0xF7D8, 0x9AEB, 0xF7D9, 0x9AFB, 0xF7DA, 0x9AED, 0xF7DB, 0x9AF9, 0xF7DC, 0x9B08, 0xF7DD, 0x9B0F, 0xF7DE, 0x9B13, + 0xF7DF, 0x9B1F, 0xF7E0, 0x9B23, 0xF7E1, 0x9EBD, 0xF7E2, 0x9EBE, 0xF7E3, 0x7E3B, 0xF7E4, 0x9E82, 0xF7E5, 0x9E87, 0xF7E6, 0x9E88, + 0xF7E7, 0x9E8B, 0xF7E8, 0x9E92, 0xF7E9, 0x93D6, 0xF7EA, 0x9E9D, 0xF7EB, 0x9E9F, 0xF7EC, 0x9EDB, 0xF7ED, 0x9EDC, 0xF7EE, 0x9EDD, + 0xF7EF, 0x9EE0, 0xF7F0, 0x9EDF, 0xF7F1, 0x9EE2, 0xF7F2, 0x9EE9, 0xF7F3, 0x9EE7, 0xF7F4, 0x9EE5, 0xF7F5, 0x9EEA, 0xF7F6, 0x9EEF, + 0xF7F7, 0x9F22, 0xF7F8, 0x9F2C, 0xF7F9, 0x9F2F, 0xF7FA, 0x9F39, 0xF7FB, 0x9F37, 0xF7FC, 0x9F3D, 0xF7FD, 0x9F3E, 0xF7FE, 0x9F44, + 0xF840, 0x9CE3, 0xF841, 0x9CE4, 0xF842, 0x9CE5, 0xF843, 0x9CE6, 0xF844, 0x9CE7, 0xF845, 0x9CE8, 0xF846, 0x9CE9, 0xF847, 0x9CEA, + 0xF848, 0x9CEB, 0xF849, 0x9CEC, 0xF84A, 0x9CED, 0xF84B, 0x9CEE, 0xF84C, 0x9CEF, 0xF84D, 0x9CF0, 0xF84E, 0x9CF1, 0xF84F, 0x9CF2, + 0xF850, 0x9CF3, 0xF851, 0x9CF4, 0xF852, 0x9CF5, 0xF853, 0x9CF6, 0xF854, 0x9CF7, 0xF855, 0x9CF8, 0xF856, 0x9CF9, 0xF857, 0x9CFA, + 0xF858, 0x9CFB, 0xF859, 0x9CFC, 0xF85A, 0x9CFD, 0xF85B, 0x9CFE, 0xF85C, 0x9CFF, 0xF85D, 0x9D00, 0xF85E, 0x9D01, 0xF85F, 0x9D02, + 0xF860, 0x9D03, 0xF861, 0x9D04, 0xF862, 0x9D05, 0xF863, 0x9D06, 0xF864, 0x9D07, 0xF865, 0x9D08, 0xF866, 0x9D09, 0xF867, 0x9D0A, + 0xF868, 0x9D0B, 0xF869, 0x9D0C, 0xF86A, 0x9D0D, 0xF86B, 0x9D0E, 0xF86C, 0x9D0F, 0xF86D, 0x9D10, 0xF86E, 0x9D11, 0xF86F, 0x9D12, + 0xF870, 0x9D13, 0xF871, 0x9D14, 0xF872, 0x9D15, 0xF873, 0x9D16, 0xF874, 0x9D17, 0xF875, 0x9D18, 0xF876, 0x9D19, 0xF877, 0x9D1A, + 0xF878, 0x9D1B, 0xF879, 0x9D1C, 0xF87A, 0x9D1D, 0xF87B, 0x9D1E, 0xF87C, 0x9D1F, 0xF87D, 0x9D20, 0xF87E, 0x9D21, 0xF880, 0x9D22, + 0xF881, 0x9D23, 0xF882, 0x9D24, 0xF883, 0x9D25, 0xF884, 0x9D26, 0xF885, 0x9D27, 0xF886, 0x9D28, 0xF887, 0x9D29, 0xF888, 0x9D2A, + 0xF889, 0x9D2B, 0xF88A, 0x9D2C, 0xF88B, 0x9D2D, 0xF88C, 0x9D2E, 0xF88D, 0x9D2F, 0xF88E, 0x9D30, 0xF88F, 0x9D31, 0xF890, 0x9D32, + 0xF891, 0x9D33, 0xF892, 0x9D34, 0xF893, 0x9D35, 0xF894, 0x9D36, 0xF895, 0x9D37, 0xF896, 0x9D38, 0xF897, 0x9D39, 0xF898, 0x9D3A, + 0xF899, 0x9D3B, 0xF89A, 0x9D3C, 0xF89B, 0x9D3D, 0xF89C, 0x9D3E, 0xF89D, 0x9D3F, 0xF89E, 0x9D40, 0xF89F, 0x9D41, 0xF8A0, 0x9D42, + 0xF940, 0x9D43, 0xF941, 0x9D44, 0xF942, 0x9D45, 0xF943, 0x9D46, 0xF944, 0x9D47, 0xF945, 0x9D48, 0xF946, 0x9D49, 0xF947, 0x9D4A, + 0xF948, 0x9D4B, 0xF949, 0x9D4C, 0xF94A, 0x9D4D, 0xF94B, 0x9D4E, 0xF94C, 0x9D4F, 0xF94D, 0x9D50, 0xF94E, 0x9D51, 0xF94F, 0x9D52, + 0xF950, 0x9D53, 0xF951, 0x9D54, 0xF952, 0x9D55, 0xF953, 0x9D56, 0xF954, 0x9D57, 0xF955, 0x9D58, 0xF956, 0x9D59, 0xF957, 0x9D5A, + 0xF958, 0x9D5B, 0xF959, 0x9D5C, 0xF95A, 0x9D5D, 0xF95B, 0x9D5E, 0xF95C, 0x9D5F, 0xF95D, 0x9D60, 0xF95E, 0x9D61, 0xF95F, 0x9D62, + 0xF960, 0x9D63, 0xF961, 0x9D64, 0xF962, 0x9D65, 0xF963, 0x9D66, 0xF964, 0x9D67, 0xF965, 0x9D68, 0xF966, 0x9D69, 0xF967, 0x9D6A, + 0xF968, 0x9D6B, 0xF969, 0x9D6C, 0xF96A, 0x9D6D, 0xF96B, 0x9D6E, 0xF96C, 0x9D6F, 0xF96D, 0x9D70, 0xF96E, 0x9D71, 0xF96F, 0x9D72, + 0xF970, 0x9D73, 0xF971, 0x9D74, 0xF972, 0x9D75, 0xF973, 0x9D76, 0xF974, 0x9D77, 0xF975, 0x9D78, 0xF976, 0x9D79, 0xF977, 0x9D7A, + 0xF978, 0x9D7B, 0xF979, 0x9D7C, 0xF97A, 0x9D7D, 0xF97B, 0x9D7E, 0xF97C, 0x9D7F, 0xF97D, 0x9D80, 0xF97E, 0x9D81, 0xF980, 0x9D82, + 0xF981, 0x9D83, 0xF982, 0x9D84, 0xF983, 0x9D85, 0xF984, 0x9D86, 0xF985, 0x9D87, 0xF986, 0x9D88, 0xF987, 0x9D89, 0xF988, 0x9D8A, + 0xF989, 0x9D8B, 0xF98A, 0x9D8C, 0xF98B, 0x9D8D, 0xF98C, 0x9D8E, 0xF98D, 0x9D8F, 0xF98E, 0x9D90, 0xF98F, 0x9D91, 0xF990, 0x9D92, + 0xF991, 0x9D93, 0xF992, 0x9D94, 0xF993, 0x9D95, 0xF994, 0x9D96, 0xF995, 0x9D97, 0xF996, 0x9D98, 0xF997, 0x9D99, 0xF998, 0x9D9A, + 0xF999, 0x9D9B, 0xF99A, 0x9D9C, 0xF99B, 0x9D9D, 0xF99C, 0x9D9E, 0xF99D, 0x9D9F, 0xF99E, 0x9DA0, 0xF99F, 0x9DA1, 0xF9A0, 0x9DA2, + 0xFA40, 0x9DA3, 0xFA41, 0x9DA4, 0xFA42, 0x9DA5, 0xFA43, 0x9DA6, 0xFA44, 0x9DA7, 0xFA45, 0x9DA8, 0xFA46, 0x9DA9, 0xFA47, 0x9DAA, + 0xFA48, 0x9DAB, 0xFA49, 0x9DAC, 0xFA4A, 0x9DAD, 0xFA4B, 0x9DAE, 0xFA4C, 0x9DAF, 0xFA4D, 0x9DB0, 0xFA4E, 0x9DB1, 0xFA4F, 0x9DB2, + 0xFA50, 0x9DB3, 0xFA51, 0x9DB4, 0xFA52, 0x9DB5, 0xFA53, 0x9DB6, 0xFA54, 0x9DB7, 0xFA55, 0x9DB8, 0xFA56, 0x9DB9, 0xFA57, 0x9DBA, + 0xFA58, 0x9DBB, 0xFA59, 0x9DBC, 0xFA5A, 0x9DBD, 0xFA5B, 0x9DBE, 0xFA5C, 0x9DBF, 0xFA5D, 0x9DC0, 0xFA5E, 0x9DC1, 0xFA5F, 0x9DC2, + 0xFA60, 0x9DC3, 0xFA61, 0x9DC4, 0xFA62, 0x9DC5, 0xFA63, 0x9DC6, 0xFA64, 0x9DC7, 0xFA65, 0x9DC8, 0xFA66, 0x9DC9, 0xFA67, 0x9DCA, + 0xFA68, 0x9DCB, 0xFA69, 0x9DCC, 0xFA6A, 0x9DCD, 0xFA6B, 0x9DCE, 0xFA6C, 0x9DCF, 0xFA6D, 0x9DD0, 0xFA6E, 0x9DD1, 0xFA6F, 0x9DD2, + 0xFA70, 0x9DD3, 0xFA71, 0x9DD4, 0xFA72, 0x9DD5, 0xFA73, 0x9DD6, 0xFA74, 0x9DD7, 0xFA75, 0x9DD8, 0xFA76, 0x9DD9, 0xFA77, 0x9DDA, + 0xFA78, 0x9DDB, 0xFA79, 0x9DDC, 0xFA7A, 0x9DDD, 0xFA7B, 0x9DDE, 0xFA7C, 0x9DDF, 0xFA7D, 0x9DE0, 0xFA7E, 0x9DE1, 0xFA80, 0x9DE2, + 0xFA81, 0x9DE3, 0xFA82, 0x9DE4, 0xFA83, 0x9DE5, 0xFA84, 0x9DE6, 0xFA85, 0x9DE7, 0xFA86, 0x9DE8, 0xFA87, 0x9DE9, 0xFA88, 0x9DEA, + 0xFA89, 0x9DEB, 0xFA8A, 0x9DEC, 0xFA8B, 0x9DED, 0xFA8C, 0x9DEE, 0xFA8D, 0x9DEF, 0xFA8E, 0x9DF0, 0xFA8F, 0x9DF1, 0xFA90, 0x9DF2, + 0xFA91, 0x9DF3, 0xFA92, 0x9DF4, 0xFA93, 0x9DF5, 0xFA94, 0x9DF6, 0xFA95, 0x9DF7, 0xFA96, 0x9DF8, 0xFA97, 0x9DF9, 0xFA98, 0x9DFA, + 0xFA99, 0x9DFB, 0xFA9A, 0x9DFC, 0xFA9B, 0x9DFD, 0xFA9C, 0x9DFE, 0xFA9D, 0x9DFF, 0xFA9E, 0x9E00, 0xFA9F, 0x9E01, 0xFAA0, 0x9E02, + 0xFB40, 0x9E03, 0xFB41, 0x9E04, 0xFB42, 0x9E05, 0xFB43, 0x9E06, 0xFB44, 0x9E07, 0xFB45, 0x9E08, 0xFB46, 0x9E09, 0xFB47, 0x9E0A, + 0xFB48, 0x9E0B, 0xFB49, 0x9E0C, 0xFB4A, 0x9E0D, 0xFB4B, 0x9E0E, 0xFB4C, 0x9E0F, 0xFB4D, 0x9E10, 0xFB4E, 0x9E11, 0xFB4F, 0x9E12, + 0xFB50, 0x9E13, 0xFB51, 0x9E14, 0xFB52, 0x9E15, 0xFB53, 0x9E16, 0xFB54, 0x9E17, 0xFB55, 0x9E18, 0xFB56, 0x9E19, 0xFB57, 0x9E1A, + 0xFB58, 0x9E1B, 0xFB59, 0x9E1C, 0xFB5A, 0x9E1D, 0xFB5B, 0x9E1E, 0xFB5C, 0x9E24, 0xFB5D, 0x9E27, 0xFB5E, 0x9E2E, 0xFB5F, 0x9E30, + 0xFB60, 0x9E34, 0xFB61, 0x9E3B, 0xFB62, 0x9E3C, 0xFB63, 0x9E40, 0xFB64, 0x9E4D, 0xFB65, 0x9E50, 0xFB66, 0x9E52, 0xFB67, 0x9E53, + 0xFB68, 0x9E54, 0xFB69, 0x9E56, 0xFB6A, 0x9E59, 0xFB6B, 0x9E5D, 0xFB6C, 0x9E5F, 0xFB6D, 0x9E60, 0xFB6E, 0x9E61, 0xFB6F, 0x9E62, + 0xFB70, 0x9E65, 0xFB71, 0x9E6E, 0xFB72, 0x9E6F, 0xFB73, 0x9E72, 0xFB74, 0x9E74, 0xFB75, 0x9E75, 0xFB76, 0x9E76, 0xFB77, 0x9E77, + 0xFB78, 0x9E78, 0xFB79, 0x9E79, 0xFB7A, 0x9E7A, 0xFB7B, 0x9E7B, 0xFB7C, 0x9E7C, 0xFB7D, 0x9E7D, 0xFB7E, 0x9E80, 0xFB80, 0x9E81, + 0xFB81, 0x9E83, 0xFB82, 0x9E84, 0xFB83, 0x9E85, 0xFB84, 0x9E86, 0xFB85, 0x9E89, 0xFB86, 0x9E8A, 0xFB87, 0x9E8C, 0xFB88, 0x9E8D, + 0xFB89, 0x9E8E, 0xFB8A, 0x9E8F, 0xFB8B, 0x9E90, 0xFB8C, 0x9E91, 0xFB8D, 0x9E94, 0xFB8E, 0x9E95, 0xFB8F, 0x9E96, 0xFB90, 0x9E97, + 0xFB91, 0x9E98, 0xFB92, 0x9E99, 0xFB93, 0x9E9A, 0xFB94, 0x9E9B, 0xFB95, 0x9E9C, 0xFB96, 0x9E9E, 0xFB97, 0x9EA0, 0xFB98, 0x9EA1, + 0xFB99, 0x9EA2, 0xFB9A, 0x9EA3, 0xFB9B, 0x9EA4, 0xFB9C, 0x9EA5, 0xFB9D, 0x9EA7, 0xFB9E, 0x9EA8, 0xFB9F, 0x9EA9, 0xFBA0, 0x9EAA, + 0xFC40, 0x9EAB, 0xFC41, 0x9EAC, 0xFC42, 0x9EAD, 0xFC43, 0x9EAE, 0xFC44, 0x9EAF, 0xFC45, 0x9EB0, 0xFC46, 0x9EB1, 0xFC47, 0x9EB2, + 0xFC48, 0x9EB3, 0xFC49, 0x9EB5, 0xFC4A, 0x9EB6, 0xFC4B, 0x9EB7, 0xFC4C, 0x9EB9, 0xFC4D, 0x9EBA, 0xFC4E, 0x9EBC, 0xFC4F, 0x9EBF, + 0xFC50, 0x9EC0, 0xFC51, 0x9EC1, 0xFC52, 0x9EC2, 0xFC53, 0x9EC3, 0xFC54, 0x9EC5, 0xFC55, 0x9EC6, 0xFC56, 0x9EC7, 0xFC57, 0x9EC8, + 0xFC58, 0x9ECA, 0xFC59, 0x9ECB, 0xFC5A, 0x9ECC, 0xFC5B, 0x9ED0, 0xFC5C, 0x9ED2, 0xFC5D, 0x9ED3, 0xFC5E, 0x9ED5, 0xFC5F, 0x9ED6, + 0xFC60, 0x9ED7, 0xFC61, 0x9ED9, 0xFC62, 0x9EDA, 0xFC63, 0x9EDE, 0xFC64, 0x9EE1, 0xFC65, 0x9EE3, 0xFC66, 0x9EE4, 0xFC67, 0x9EE6, + 0xFC68, 0x9EE8, 0xFC69, 0x9EEB, 0xFC6A, 0x9EEC, 0xFC6B, 0x9EED, 0xFC6C, 0x9EEE, 0xFC6D, 0x9EF0, 0xFC6E, 0x9EF1, 0xFC6F, 0x9EF2, + 0xFC70, 0x9EF3, 0xFC71, 0x9EF4, 0xFC72, 0x9EF5, 0xFC73, 0x9EF6, 0xFC74, 0x9EF7, 0xFC75, 0x9EF8, 0xFC76, 0x9EFA, 0xFC77, 0x9EFD, + 0xFC78, 0x9EFF, 0xFC79, 0x9F00, 0xFC7A, 0x9F01, 0xFC7B, 0x9F02, 0xFC7C, 0x9F03, 0xFC7D, 0x9F04, 0xFC7E, 0x9F05, 0xFC80, 0x9F06, + 0xFC81, 0x9F07, 0xFC82, 0x9F08, 0xFC83, 0x9F09, 0xFC84, 0x9F0A, 0xFC85, 0x9F0C, 0xFC86, 0x9F0F, 0xFC87, 0x9F11, 0xFC88, 0x9F12, + 0xFC89, 0x9F14, 0xFC8A, 0x9F15, 0xFC8B, 0x9F16, 0xFC8C, 0x9F18, 0xFC8D, 0x9F1A, 0xFC8E, 0x9F1B, 0xFC8F, 0x9F1C, 0xFC90, 0x9F1D, + 0xFC91, 0x9F1E, 0xFC92, 0x9F1F, 0xFC93, 0x9F21, 0xFC94, 0x9F23, 0xFC95, 0x9F24, 0xFC96, 0x9F25, 0xFC97, 0x9F26, 0xFC98, 0x9F27, + 0xFC99, 0x9F28, 0xFC9A, 0x9F29, 0xFC9B, 0x9F2A, 0xFC9C, 0x9F2B, 0xFC9D, 0x9F2D, 0xFC9E, 0x9F2E, 0xFC9F, 0x9F30, 0xFCA0, 0x9F31, + 0xFD40, 0x9F32, 0xFD41, 0x9F33, 0xFD42, 0x9F34, 0xFD43, 0x9F35, 0xFD44, 0x9F36, 0xFD45, 0x9F38, 0xFD46, 0x9F3A, 0xFD47, 0x9F3C, + 0xFD48, 0x9F3F, 0xFD49, 0x9F40, 0xFD4A, 0x9F41, 0xFD4B, 0x9F42, 0xFD4C, 0x9F43, 0xFD4D, 0x9F45, 0xFD4E, 0x9F46, 0xFD4F, 0x9F47, + 0xFD50, 0x9F48, 0xFD51, 0x9F49, 0xFD52, 0x9F4A, 0xFD53, 0x9F4B, 0xFD54, 0x9F4C, 0xFD55, 0x9F4D, 0xFD56, 0x9F4E, 0xFD57, 0x9F4F, + 0xFD58, 0x9F52, 0xFD59, 0x9F53, 0xFD5A, 0x9F54, 0xFD5B, 0x9F55, 0xFD5C, 0x9F56, 0xFD5D, 0x9F57, 0xFD5E, 0x9F58, 0xFD5F, 0x9F59, + 0xFD60, 0x9F5A, 0xFD61, 0x9F5B, 0xFD62, 0x9F5C, 0xFD63, 0x9F5D, 0xFD64, 0x9F5E, 0xFD65, 0x9F5F, 0xFD66, 0x9F60, 0xFD67, 0x9F61, + 0xFD68, 0x9F62, 0xFD69, 0x9F63, 0xFD6A, 0x9F64, 0xFD6B, 0x9F65, 0xFD6C, 0x9F66, 0xFD6D, 0x9F67, 0xFD6E, 0x9F68, 0xFD6F, 0x9F69, + 0xFD70, 0x9F6A, 0xFD71, 0x9F6B, 0xFD72, 0x9F6C, 0xFD73, 0x9F6D, 0xFD74, 0x9F6E, 0xFD75, 0x9F6F, 0xFD76, 0x9F70, 0xFD77, 0x9F71, + 0xFD78, 0x9F72, 0xFD79, 0x9F73, 0xFD7A, 0x9F74, 0xFD7B, 0x9F75, 0xFD7C, 0x9F76, 0xFD7D, 0x9F77, 0xFD7E, 0x9F78, 0xFD80, 0x9F79, + 0xFD81, 0x9F7A, 0xFD82, 0x9F7B, 0xFD83, 0x9F7C, 0xFD84, 0x9F7D, 0xFD85, 0x9F7E, 0xFD86, 0x9F81, 0xFD87, 0x9F82, 0xFD88, 0x9F8D, + 0xFD89, 0x9F8E, 0xFD8A, 0x9F8F, 0xFD8B, 0x9F90, 0xFD8C, 0x9F91, 0xFD8D, 0x9F92, 0xFD8E, 0x9F93, 0xFD8F, 0x9F94, 0xFD90, 0x9F95, + 0xFD91, 0x9F96, 0xFD92, 0x9F97, 0xFD93, 0x9F98, 0xFD94, 0x9F9C, 0xFD95, 0x9F9D, 0xFD96, 0x9F9E, 0xFD97, 0x9FA1, 0xFD98, 0x9FA2, + 0xFD99, 0x9FA3, 0xFD9A, 0x9FA4, 0xFD9B, 0x9FA5, 0xFD9C, 0xF92C, 0xFD9D, 0xF979, 0xFD9E, 0xF995, 0xFD9F, 0xF9E7, 0xFDA0, 0xF9F1, + 0xFE40, 0xFA0C, 0xFE41, 0xFA0D, 0xFE42, 0xFA0E, 0xFE43, 0xFA0F, 0xFE44, 0xFA11, 0xFE45, 0xFA13, 0xFE46, 0xFA14, 0xFE47, 0xFA18, + 0xFE48, 0xFA1F, 0xFE49, 0xFA20, 0xFE4A, 0xFA21, 0xFE4B, 0xFA23, 0xFE4C, 0xFA24, 0xFE4D, 0xFA27, 0xFE4E, 0xFA28, 0xFE4F, 0xFA29, + 0, 0 +}; +#endif + +#if FF_CODE_PAGE == 949 || FF_CODE_PAGE == 0 /* Korean */ +static const WCHAR uni2oem949[] = { /* Unicode --> Korean pairs */ + 0x00A1, 0xA2AE, 0x00A4, 0xA2B4, 0x00A7, 0xA1D7, 0x00A8, 0xA1A7, 0x00AA, 0xA8A3, 0x00AD, 0xA1A9, 0x00AE, 0xA2E7, 0x00B0, 0xA1C6, + 0x00B1, 0xA1BE, 0x00B2, 0xA9F7, 0x00B3, 0xA9F8, 0x00B4, 0xA2A5, 0x00B6, 0xA2D2, 0x00B7, 0xA1A4, 0x00B8, 0xA2AC, 0x00B9, 0xA9F6, + 0x00BA, 0xA8AC, 0x00BC, 0xA8F9, 0x00BD, 0xA8F6, 0x00BE, 0xA8FA, 0x00BF, 0xA2AF, 0x00C6, 0xA8A1, 0x00D0, 0xA8A2, 0x00D7, 0xA1BF, + 0x00D8, 0xA8AA, 0x00DE, 0xA8AD, 0x00DF, 0xA9AC, 0x00E6, 0xA9A1, 0x00F0, 0xA9A3, 0x00F7, 0xA1C0, 0x00F8, 0xA9AA, 0x00FE, 0xA9AD, + 0x0111, 0xA9A2, 0x0126, 0xA8A4, 0x0127, 0xA9A4, 0x0131, 0xA9A5, 0x0132, 0xA8A6, 0x0133, 0xA9A6, 0x0138, 0xA9A7, 0x013F, 0xA8A8, + 0x0140, 0xA9A8, 0x0141, 0xA8A9, 0x0142, 0xA9A9, 0x0149, 0xA9B0, 0x014A, 0xA8AF, 0x014B, 0xA9AF, 0x0152, 0xA8AB, 0x0153, 0xA9AB, + 0x0166, 0xA8AE, 0x0167, 0xA9AE, 0x02C7, 0xA2A7, 0x02D0, 0xA2B0, 0x02D8, 0xA2A8, 0x02D9, 0xA2AB, 0x02DA, 0xA2AA, 0x02DB, 0xA2AD, + 0x02DD, 0xA2A9, 0x0391, 0xA5C1, 0x0392, 0xA5C2, 0x0393, 0xA5C3, 0x0394, 0xA5C4, 0x0395, 0xA5C5, 0x0396, 0xA5C6, 0x0397, 0xA5C7, + 0x0398, 0xA5C8, 0x0399, 0xA5C9, 0x039A, 0xA5CA, 0x039B, 0xA5CB, 0x039C, 0xA5CC, 0x039D, 0xA5CD, 0x039E, 0xA5CE, 0x039F, 0xA5CF, + 0x03A0, 0xA5D0, 0x03A1, 0xA5D1, 0x03A3, 0xA5D2, 0x03A4, 0xA5D3, 0x03A5, 0xA5D4, 0x03A6, 0xA5D5, 0x03A7, 0xA5D6, 0x03A8, 0xA5D7, + 0x03A9, 0xA5D8, 0x03B1, 0xA5E1, 0x03B2, 0xA5E2, 0x03B3, 0xA5E3, 0x03B4, 0xA5E4, 0x03B5, 0xA5E5, 0x03B6, 0xA5E6, 0x03B7, 0xA5E7, + 0x03B8, 0xA5E8, 0x03B9, 0xA5E9, 0x03BA, 0xA5EA, 0x03BB, 0xA5EB, 0x03BC, 0xA5EC, 0x03BD, 0xA5ED, 0x03BE, 0xA5EE, 0x03BF, 0xA5EF, + 0x03C0, 0xA5F0, 0x03C1, 0xA5F1, 0x03C3, 0xA5F2, 0x03C4, 0xA5F3, 0x03C5, 0xA5F4, 0x03C6, 0xA5F5, 0x03C7, 0xA5F6, 0x03C8, 0xA5F7, + 0x03C9, 0xA5F8, 0x0401, 0xACA7, 0x0410, 0xACA1, 0x0411, 0xACA2, 0x0412, 0xACA3, 0x0413, 0xACA4, 0x0414, 0xACA5, 0x0415, 0xACA6, + 0x0416, 0xACA8, 0x0417, 0xACA9, 0x0418, 0xACAA, 0x0419, 0xACAB, 0x041A, 0xACAC, 0x041B, 0xACAD, 0x041C, 0xACAE, 0x041D, 0xACAF, + 0x041E, 0xACB0, 0x041F, 0xACB1, 0x0420, 0xACB2, 0x0421, 0xACB3, 0x0422, 0xACB4, 0x0423, 0xACB5, 0x0424, 0xACB6, 0x0425, 0xACB7, + 0x0426, 0xACB8, 0x0427, 0xACB9, 0x0428, 0xACBA, 0x0429, 0xACBB, 0x042A, 0xACBC, 0x042B, 0xACBD, 0x042C, 0xACBE, 0x042D, 0xACBF, + 0x042E, 0xACC0, 0x042F, 0xACC1, 0x0430, 0xACD1, 0x0431, 0xACD2, 0x0432, 0xACD3, 0x0433, 0xACD4, 0x0434, 0xACD5, 0x0435, 0xACD6, + 0x0436, 0xACD8, 0x0437, 0xACD9, 0x0438, 0xACDA, 0x0439, 0xACDB, 0x043A, 0xACDC, 0x043B, 0xACDD, 0x043C, 0xACDE, 0x043D, 0xACDF, + 0x043E, 0xACE0, 0x043F, 0xACE1, 0x0440, 0xACE2, 0x0441, 0xACE3, 0x0442, 0xACE4, 0x0443, 0xACE5, 0x0444, 0xACE6, 0x0445, 0xACE7, + 0x0446, 0xACE8, 0x0447, 0xACE9, 0x0448, 0xACEA, 0x0449, 0xACEB, 0x044A, 0xACEC, 0x044B, 0xACED, 0x044C, 0xACEE, 0x044D, 0xACEF, + 0x044E, 0xACF0, 0x044F, 0xACF1, 0x0451, 0xACD7, 0x2015, 0xA1AA, 0x2018, 0xA1AE, 0x2019, 0xA1AF, 0x201C, 0xA1B0, 0x201D, 0xA1B1, + 0x2020, 0xA2D3, 0x2021, 0xA2D4, 0x2025, 0xA1A5, 0x2026, 0xA1A6, 0x2030, 0xA2B6, 0x2032, 0xA1C7, 0x2033, 0xA1C8, 0x203B, 0xA1D8, + 0x2074, 0xA9F9, 0x207F, 0xA9FA, 0x2081, 0xA9FB, 0x2082, 0xA9FC, 0x2083, 0xA9FD, 0x2084, 0xA9FE, 0x20AC, 0xA2E6, 0x2103, 0xA1C9, + 0x2109, 0xA2B5, 0x2113, 0xA7A4, 0x2116, 0xA2E0, 0x2121, 0xA2E5, 0x2122, 0xA2E2, 0x2126, 0xA7D9, 0x212B, 0xA1CA, 0x2153, 0xA8F7, + 0x2154, 0xA8F8, 0x215B, 0xA8FB, 0x215C, 0xA8FC, 0x215D, 0xA8FD, 0x215E, 0xA8FE, 0x2160, 0xA5B0, 0x2161, 0xA5B1, 0x2162, 0xA5B2, + 0x2163, 0xA5B3, 0x2164, 0xA5B4, 0x2165, 0xA5B5, 0x2166, 0xA5B6, 0x2167, 0xA5B7, 0x2168, 0xA5B8, 0x2169, 0xA5B9, 0x2170, 0xA5A1, + 0x2171, 0xA5A2, 0x2172, 0xA5A3, 0x2173, 0xA5A4, 0x2174, 0xA5A5, 0x2175, 0xA5A6, 0x2176, 0xA5A7, 0x2177, 0xA5A8, 0x2178, 0xA5A9, + 0x2179, 0xA5AA, 0x2190, 0xA1E7, 0x2191, 0xA1E8, 0x2192, 0xA1E6, 0x2193, 0xA1E9, 0x2194, 0xA1EA, 0x2195, 0xA2D5, 0x2196, 0xA2D8, + 0x2197, 0xA2D6, 0x2198, 0xA2D9, 0x2199, 0xA2D7, 0x21D2, 0xA2A1, 0x21D4, 0xA2A2, 0x2200, 0xA2A3, 0x2202, 0xA1D3, 0x2203, 0xA2A4, + 0x2207, 0xA1D4, 0x2208, 0xA1F4, 0x220B, 0xA1F5, 0x220F, 0xA2B3, 0x2211, 0xA2B2, 0x221A, 0xA1EE, 0x221D, 0xA1F0, 0x221E, 0xA1C4, + 0x2220, 0xA1D0, 0x2225, 0xA1AB, 0x2227, 0xA1FC, 0x2228, 0xA1FD, 0x2229, 0xA1FB, 0x222A, 0xA1FA, 0x222B, 0xA1F2, 0x222C, 0xA1F3, + 0x222E, 0xA2B1, 0x2234, 0xA1C5, 0x2235, 0xA1F1, 0x223C, 0xA1AD, 0x223D, 0xA1EF, 0x2252, 0xA1D6, 0x2260, 0xA1C1, 0x2261, 0xA1D5, + 0x2264, 0xA1C2, 0x2265, 0xA1C3, 0x226A, 0xA1EC, 0x226B, 0xA1ED, 0x2282, 0xA1F8, 0x2283, 0xA1F9, 0x2286, 0xA1F6, 0x2287, 0xA1F7, + 0x2299, 0xA2C1, 0x22A5, 0xA1D1, 0x2312, 0xA1D2, 0x2460, 0xA8E7, 0x2461, 0xA8E8, 0x2462, 0xA8E9, 0x2463, 0xA8EA, 0x2464, 0xA8EB, + 0x2465, 0xA8EC, 0x2466, 0xA8ED, 0x2467, 0xA8EE, 0x2468, 0xA8EF, 0x2469, 0xA8F0, 0x246A, 0xA8F1, 0x246B, 0xA8F2, 0x246C, 0xA8F3, + 0x246D, 0xA8F4, 0x246E, 0xA8F5, 0x2474, 0xA9E7, 0x2475, 0xA9E8, 0x2476, 0xA9E9, 0x2477, 0xA9EA, 0x2478, 0xA9EB, 0x2479, 0xA9EC, + 0x247A, 0xA9ED, 0x247B, 0xA9EE, 0x247C, 0xA9EF, 0x247D, 0xA9F0, 0x247E, 0xA9F1, 0x247F, 0xA9F2, 0x2480, 0xA9F3, 0x2481, 0xA9F4, + 0x2482, 0xA9F5, 0x249C, 0xA9CD, 0x249D, 0xA9CE, 0x249E, 0xA9CF, 0x249F, 0xA9D0, 0x24A0, 0xA9D1, 0x24A1, 0xA9D2, 0x24A2, 0xA9D3, + 0x24A3, 0xA9D4, 0x24A4, 0xA9D5, 0x24A5, 0xA9D6, 0x24A6, 0xA9D7, 0x24A7, 0xA9D8, 0x24A8, 0xA9D9, 0x24A9, 0xA9DA, 0x24AA, 0xA9DB, + 0x24AB, 0xA9DC, 0x24AC, 0xA9DD, 0x24AD, 0xA9DE, 0x24AE, 0xA9DF, 0x24AF, 0xA9E0, 0x24B0, 0xA9E1, 0x24B1, 0xA9E2, 0x24B2, 0xA9E3, + 0x24B3, 0xA9E4, 0x24B4, 0xA9E5, 0x24B5, 0xA9E6, 0x24D0, 0xA8CD, 0x24D1, 0xA8CE, 0x24D2, 0xA8CF, 0x24D3, 0xA8D0, 0x24D4, 0xA8D1, + 0x24D5, 0xA8D2, 0x24D6, 0xA8D3, 0x24D7, 0xA8D4, 0x24D8, 0xA8D5, 0x24D9, 0xA8D6, 0x24DA, 0xA8D7, 0x24DB, 0xA8D8, 0x24DC, 0xA8D9, + 0x24DD, 0xA8DA, 0x24DE, 0xA8DB, 0x24DF, 0xA8DC, 0x24E0, 0xA8DD, 0x24E1, 0xA8DE, 0x24E2, 0xA8DF, 0x24E3, 0xA8E0, 0x24E4, 0xA8E1, + 0x24E5, 0xA8E2, 0x24E6, 0xA8E3, 0x24E7, 0xA8E4, 0x24E8, 0xA8E5, 0x24E9, 0xA8E6, 0x2500, 0xA6A1, 0x2501, 0xA6AC, 0x2502, 0xA6A2, + 0x2503, 0xA6AD, 0x250C, 0xA6A3, 0x250D, 0xA6C8, 0x250E, 0xA6C7, 0x250F, 0xA6AE, 0x2510, 0xA6A4, 0x2511, 0xA6C2, 0x2512, 0xA6C1, + 0x2513, 0xA6AF, 0x2514, 0xA6A6, 0x2515, 0xA6C6, 0x2516, 0xA6C5, 0x2517, 0xA6B1, 0x2518, 0xA6A5, 0x2519, 0xA6C4, 0x251A, 0xA6C3, + 0x251B, 0xA6B0, 0x251C, 0xA6A7, 0x251D, 0xA6BC, 0x251E, 0xA6C9, 0x251F, 0xA6CA, 0x2520, 0xA6B7, 0x2521, 0xA6CB, 0x2522, 0xA6CC, + 0x2523, 0xA6B2, 0x2524, 0xA6A9, 0x2525, 0xA6BE, 0x2526, 0xA6CD, 0x2527, 0xA6CE, 0x2528, 0xA6B9, 0x2529, 0xA6CF, 0x252A, 0xA6D0, + 0x252B, 0xA6B4, 0x252C, 0xA6A8, 0x252D, 0xA6D1, 0x252E, 0xA6D2, 0x252F, 0xA6B8, 0x2530, 0xA6BD, 0x2531, 0xA6D3, 0x2532, 0xA6D4, + 0x2533, 0xA6B3, 0x2534, 0xA6AA, 0x2535, 0xA6D5, 0x2536, 0xA6D6, 0x2537, 0xA6BA, 0x2538, 0xA6BF, 0x2539, 0xA6D7, 0x253A, 0xA6D8, + 0x253B, 0xA6B5, 0x253C, 0xA6AB, 0x253D, 0xA6D9, 0x253E, 0xA6DA, 0x253F, 0xA6BB, 0x2540, 0xA6DB, 0x2541, 0xA6DC, 0x2542, 0xA6C0, + 0x2543, 0xA6DD, 0x2544, 0xA6DE, 0x2545, 0xA6DF, 0x2546, 0xA6E0, 0x2547, 0xA6E1, 0x2548, 0xA6E2, 0x2549, 0xA6E3, 0x254A, 0xA6E4, + 0x254B, 0xA6B6, 0x2592, 0xA2C6, 0x25A0, 0xA1E1, 0x25A1, 0xA1E0, 0x25A3, 0xA2C3, 0x25A4, 0xA2C7, 0x25A5, 0xA2C8, 0x25A6, 0xA2CB, + 0x25A7, 0xA2CA, 0x25A8, 0xA2C9, 0x25A9, 0xA2CC, 0x25B2, 0xA1E3, 0x25B3, 0xA1E2, 0x25B6, 0xA2BA, 0x25B7, 0xA2B9, 0x25BC, 0xA1E5, + 0x25BD, 0xA1E4, 0x25C0, 0xA2B8, 0x25C1, 0xA2B7, 0x25C6, 0xA1DF, 0x25C7, 0xA1DE, 0x25C8, 0xA2C2, 0x25CB, 0xA1DB, 0x25CE, 0xA1DD, + 0x25CF, 0xA1DC, 0x25D0, 0xA2C4, 0x25D1, 0xA2C5, 0x2605, 0xA1DA, 0x2606, 0xA1D9, 0x260E, 0xA2CF, 0x260F, 0xA2CE, 0x261C, 0xA2D0, + 0x261E, 0xA2D1, 0x2640, 0xA1CF, 0x2642, 0xA1CE, 0x2660, 0xA2BC, 0x2661, 0xA2BD, 0x2663, 0xA2C0, 0x2664, 0xA2BB, 0x2665, 0xA2BE, + 0x2667, 0xA2BF, 0x2668, 0xA2CD, 0x2669, 0xA2DB, 0x266A, 0xA2DC, 0x266C, 0xA2DD, 0x266D, 0xA2DA, 0x3000, 0xA1A1, 0x3001, 0xA1A2, + 0x3002, 0xA1A3, 0x3003, 0xA1A8, 0x3008, 0xA1B4, 0x3009, 0xA1B5, 0x300A, 0xA1B6, 0x300B, 0xA1B7, 0x300C, 0xA1B8, 0x300D, 0xA1B9, + 0x300E, 0xA1BA, 0x300F, 0xA1BB, 0x3010, 0xA1BC, 0x3011, 0xA1BD, 0x3013, 0xA1EB, 0x3014, 0xA1B2, 0x3015, 0xA1B3, 0x3041, 0xAAA1, + 0x3042, 0xAAA2, 0x3043, 0xAAA3, 0x3044, 0xAAA4, 0x3045, 0xAAA5, 0x3046, 0xAAA6, 0x3047, 0xAAA7, 0x3048, 0xAAA8, 0x3049, 0xAAA9, + 0x304A, 0xAAAA, 0x304B, 0xAAAB, 0x304C, 0xAAAC, 0x304D, 0xAAAD, 0x304E, 0xAAAE, 0x304F, 0xAAAF, 0x3050, 0xAAB0, 0x3051, 0xAAB1, + 0x3052, 0xAAB2, 0x3053, 0xAAB3, 0x3054, 0xAAB4, 0x3055, 0xAAB5, 0x3056, 0xAAB6, 0x3057, 0xAAB7, 0x3058, 0xAAB8, 0x3059, 0xAAB9, + 0x305A, 0xAABA, 0x305B, 0xAABB, 0x305C, 0xAABC, 0x305D, 0xAABD, 0x305E, 0xAABE, 0x305F, 0xAABF, 0x3060, 0xAAC0, 0x3061, 0xAAC1, + 0x3062, 0xAAC2, 0x3063, 0xAAC3, 0x3064, 0xAAC4, 0x3065, 0xAAC5, 0x3066, 0xAAC6, 0x3067, 0xAAC7, 0x3068, 0xAAC8, 0x3069, 0xAAC9, + 0x306A, 0xAACA, 0x306B, 0xAACB, 0x306C, 0xAACC, 0x306D, 0xAACD, 0x306E, 0xAACE, 0x306F, 0xAACF, 0x3070, 0xAAD0, 0x3071, 0xAAD1, + 0x3072, 0xAAD2, 0x3073, 0xAAD3, 0x3074, 0xAAD4, 0x3075, 0xAAD5, 0x3076, 0xAAD6, 0x3077, 0xAAD7, 0x3078, 0xAAD8, 0x3079, 0xAAD9, + 0x307A, 0xAADA, 0x307B, 0xAADB, 0x307C, 0xAADC, 0x307D, 0xAADD, 0x307E, 0xAADE, 0x307F, 0xAADF, 0x3080, 0xAAE0, 0x3081, 0xAAE1, + 0x3082, 0xAAE2, 0x3083, 0xAAE3, 0x3084, 0xAAE4, 0x3085, 0xAAE5, 0x3086, 0xAAE6, 0x3087, 0xAAE7, 0x3088, 0xAAE8, 0x3089, 0xAAE9, + 0x308A, 0xAAEA, 0x308B, 0xAAEB, 0x308C, 0xAAEC, 0x308D, 0xAAED, 0x308E, 0xAAEE, 0x308F, 0xAAEF, 0x3090, 0xAAF0, 0x3091, 0xAAF1, + 0x3092, 0xAAF2, 0x3093, 0xAAF3, 0x30A1, 0xABA1, 0x30A2, 0xABA2, 0x30A3, 0xABA3, 0x30A4, 0xABA4, 0x30A5, 0xABA5, 0x30A6, 0xABA6, + 0x30A7, 0xABA7, 0x30A8, 0xABA8, 0x30A9, 0xABA9, 0x30AA, 0xABAA, 0x30AB, 0xABAB, 0x30AC, 0xABAC, 0x30AD, 0xABAD, 0x30AE, 0xABAE, + 0x30AF, 0xABAF, 0x30B0, 0xABB0, 0x30B1, 0xABB1, 0x30B2, 0xABB2, 0x30B3, 0xABB3, 0x30B4, 0xABB4, 0x30B5, 0xABB5, 0x30B6, 0xABB6, + 0x30B7, 0xABB7, 0x30B8, 0xABB8, 0x30B9, 0xABB9, 0x30BA, 0xABBA, 0x30BB, 0xABBB, 0x30BC, 0xABBC, 0x30BD, 0xABBD, 0x30BE, 0xABBE, + 0x30BF, 0xABBF, 0x30C0, 0xABC0, 0x30C1, 0xABC1, 0x30C2, 0xABC2, 0x30C3, 0xABC3, 0x30C4, 0xABC4, 0x30C5, 0xABC5, 0x30C6, 0xABC6, + 0x30C7, 0xABC7, 0x30C8, 0xABC8, 0x30C9, 0xABC9, 0x30CA, 0xABCA, 0x30CB, 0xABCB, 0x30CC, 0xABCC, 0x30CD, 0xABCD, 0x30CE, 0xABCE, + 0x30CF, 0xABCF, 0x30D0, 0xABD0, 0x30D1, 0xABD1, 0x30D2, 0xABD2, 0x30D3, 0xABD3, 0x30D4, 0xABD4, 0x30D5, 0xABD5, 0x30D6, 0xABD6, + 0x30D7, 0xABD7, 0x30D8, 0xABD8, 0x30D9, 0xABD9, 0x30DA, 0xABDA, 0x30DB, 0xABDB, 0x30DC, 0xABDC, 0x30DD, 0xABDD, 0x30DE, 0xABDE, + 0x30DF, 0xABDF, 0x30E0, 0xABE0, 0x30E1, 0xABE1, 0x30E2, 0xABE2, 0x30E3, 0xABE3, 0x30E4, 0xABE4, 0x30E5, 0xABE5, 0x30E6, 0xABE6, + 0x30E7, 0xABE7, 0x30E8, 0xABE8, 0x30E9, 0xABE9, 0x30EA, 0xABEA, 0x30EB, 0xABEB, 0x30EC, 0xABEC, 0x30ED, 0xABED, 0x30EE, 0xABEE, + 0x30EF, 0xABEF, 0x30F0, 0xABF0, 0x30F1, 0xABF1, 0x30F2, 0xABF2, 0x30F3, 0xABF3, 0x30F4, 0xABF4, 0x30F5, 0xABF5, 0x30F6, 0xABF6, + 0x3131, 0xA4A1, 0x3132, 0xA4A2, 0x3133, 0xA4A3, 0x3134, 0xA4A4, 0x3135, 0xA4A5, 0x3136, 0xA4A6, 0x3137, 0xA4A7, 0x3138, 0xA4A8, + 0x3139, 0xA4A9, 0x313A, 0xA4AA, 0x313B, 0xA4AB, 0x313C, 0xA4AC, 0x313D, 0xA4AD, 0x313E, 0xA4AE, 0x313F, 0xA4AF, 0x3140, 0xA4B0, + 0x3141, 0xA4B1, 0x3142, 0xA4B2, 0x3143, 0xA4B3, 0x3144, 0xA4B4, 0x3145, 0xA4B5, 0x3146, 0xA4B6, 0x3147, 0xA4B7, 0x3148, 0xA4B8, + 0x3149, 0xA4B9, 0x314A, 0xA4BA, 0x314B, 0xA4BB, 0x314C, 0xA4BC, 0x314D, 0xA4BD, 0x314E, 0xA4BE, 0x314F, 0xA4BF, 0x3150, 0xA4C0, + 0x3151, 0xA4C1, 0x3152, 0xA4C2, 0x3153, 0xA4C3, 0x3154, 0xA4C4, 0x3155, 0xA4C5, 0x3156, 0xA4C6, 0x3157, 0xA4C7, 0x3158, 0xA4C8, + 0x3159, 0xA4C9, 0x315A, 0xA4CA, 0x315B, 0xA4CB, 0x315C, 0xA4CC, 0x315D, 0xA4CD, 0x315E, 0xA4CE, 0x315F, 0xA4CF, 0x3160, 0xA4D0, + 0x3161, 0xA4D1, 0x3162, 0xA4D2, 0x3163, 0xA4D3, 0x3164, 0xA4D4, 0x3165, 0xA4D5, 0x3166, 0xA4D6, 0x3167, 0xA4D7, 0x3168, 0xA4D8, + 0x3169, 0xA4D9, 0x316A, 0xA4DA, 0x316B, 0xA4DB, 0x316C, 0xA4DC, 0x316D, 0xA4DD, 0x316E, 0xA4DE, 0x316F, 0xA4DF, 0x3170, 0xA4E0, + 0x3171, 0xA4E1, 0x3172, 0xA4E2, 0x3173, 0xA4E3, 0x3174, 0xA4E4, 0x3175, 0xA4E5, 0x3176, 0xA4E6, 0x3177, 0xA4E7, 0x3178, 0xA4E8, + 0x3179, 0xA4E9, 0x317A, 0xA4EA, 0x317B, 0xA4EB, 0x317C, 0xA4EC, 0x317D, 0xA4ED, 0x317E, 0xA4EE, 0x317F, 0xA4EF, 0x3180, 0xA4F0, + 0x3181, 0xA4F1, 0x3182, 0xA4F2, 0x3183, 0xA4F3, 0x3184, 0xA4F4, 0x3185, 0xA4F5, 0x3186, 0xA4F6, 0x3187, 0xA4F7, 0x3188, 0xA4F8, + 0x3189, 0xA4F9, 0x318A, 0xA4FA, 0x318B, 0xA4FB, 0x318C, 0xA4FC, 0x318D, 0xA4FD, 0x318E, 0xA4FE, 0x3200, 0xA9B1, 0x3201, 0xA9B2, + 0x3202, 0xA9B3, 0x3203, 0xA9B4, 0x3204, 0xA9B5, 0x3205, 0xA9B6, 0x3206, 0xA9B7, 0x3207, 0xA9B8, 0x3208, 0xA9B9, 0x3209, 0xA9BA, + 0x320A, 0xA9BB, 0x320B, 0xA9BC, 0x320C, 0xA9BD, 0x320D, 0xA9BE, 0x320E, 0xA9BF, 0x320F, 0xA9C0, 0x3210, 0xA9C1, 0x3211, 0xA9C2, + 0x3212, 0xA9C3, 0x3213, 0xA9C4, 0x3214, 0xA9C5, 0x3215, 0xA9C6, 0x3216, 0xA9C7, 0x3217, 0xA9C8, 0x3218, 0xA9C9, 0x3219, 0xA9CA, + 0x321A, 0xA9CB, 0x321B, 0xA9CC, 0x321C, 0xA2DF, 0x3260, 0xA8B1, 0x3261, 0xA8B2, 0x3262, 0xA8B3, 0x3263, 0xA8B4, 0x3264, 0xA8B5, + 0x3265, 0xA8B6, 0x3266, 0xA8B7, 0x3267, 0xA8B8, 0x3268, 0xA8B9, 0x3269, 0xA8BA, 0x326A, 0xA8BB, 0x326B, 0xA8BC, 0x326C, 0xA8BD, + 0x326D, 0xA8BE, 0x326E, 0xA8BF, 0x326F, 0xA8C0, 0x3270, 0xA8C1, 0x3271, 0xA8C2, 0x3272, 0xA8C3, 0x3273, 0xA8C4, 0x3274, 0xA8C5, + 0x3275, 0xA8C6, 0x3276, 0xA8C7, 0x3277, 0xA8C8, 0x3278, 0xA8C9, 0x3279, 0xA8CA, 0x327A, 0xA8CB, 0x327B, 0xA8CC, 0x327F, 0xA2DE, + 0x3380, 0xA7C9, 0x3381, 0xA7CA, 0x3382, 0xA7CB, 0x3383, 0xA7CC, 0x3384, 0xA7CD, 0x3388, 0xA7BA, 0x3389, 0xA7BB, 0x338A, 0xA7DC, + 0x338B, 0xA7DD, 0x338C, 0xA7DE, 0x338D, 0xA7B6, 0x338E, 0xA7B7, 0x338F, 0xA7B8, 0x3390, 0xA7D4, 0x3391, 0xA7D5, 0x3392, 0xA7D6, + 0x3393, 0xA7D7, 0x3394, 0xA7D8, 0x3395, 0xA7A1, 0x3396, 0xA7A2, 0x3397, 0xA7A3, 0x3398, 0xA7A5, 0x3399, 0xA7AB, 0x339A, 0xA7AC, + 0x339B, 0xA7AD, 0x339C, 0xA7AE, 0x339D, 0xA7AF, 0x339E, 0xA7B0, 0x339F, 0xA7B1, 0x33A0, 0xA7B2, 0x33A1, 0xA7B3, 0x33A2, 0xA7B4, + 0x33A3, 0xA7A7, 0x33A4, 0xA7A8, 0x33A5, 0xA7A9, 0x33A6, 0xA7AA, 0x33A7, 0xA7BD, 0x33A8, 0xA7BE, 0x33A9, 0xA7E5, 0x33AA, 0xA7E6, + 0x33AB, 0xA7E7, 0x33AC, 0xA7E8, 0x33AD, 0xA7E1, 0x33AE, 0xA7E2, 0x33AF, 0xA7E3, 0x33B0, 0xA7BF, 0x33B1, 0xA7C0, 0x33B2, 0xA7C1, + 0x33B3, 0xA7C2, 0x33B4, 0xA7C3, 0x33B5, 0xA7C4, 0x33B6, 0xA7C5, 0x33B7, 0xA7C6, 0x33B8, 0xA7C7, 0x33B9, 0xA7C8, 0x33BA, 0xA7CE, + 0x33BB, 0xA7CF, 0x33BC, 0xA7D0, 0x33BD, 0xA7D1, 0x33BE, 0xA7D2, 0x33BF, 0xA7D3, 0x33C0, 0xA7DA, 0x33C1, 0xA7DB, 0x33C2, 0xA2E3, + 0x33C3, 0xA7EC, 0x33C4, 0xA7A6, 0x33C5, 0xA7E0, 0x33C6, 0xA7EF, 0x33C7, 0xA2E1, 0x33C8, 0xA7BC, 0x33C9, 0xA7ED, 0x33CA, 0xA7B5, + 0x33CF, 0xA7B9, 0x33D0, 0xA7EA, 0x33D3, 0xA7EB, 0x33D6, 0xA7DF, 0x33D8, 0xA2E4, 0x33DB, 0xA7E4, 0x33DC, 0xA7EE, 0x33DD, 0xA7E9, + 0x4E00, 0xECE9, 0x4E01, 0xEFCB, 0x4E03, 0xF6D2, 0x4E07, 0xD8B2, 0x4E08, 0xEDDB, 0x4E09, 0xDFB2, 0x4E0A, 0xDFBE, 0x4E0B, 0xF9BB, + 0x4E0D, 0xDCF4, 0x4E11, 0xF5E4, 0x4E14, 0xF3A6, 0x4E15, 0xDDE0, 0x4E16, 0xE1A6, 0x4E18, 0xCEF8, 0x4E19, 0xDCB0, 0x4E1E, 0xE3AA, + 0x4E2D, 0xF1E9, 0x4E32, 0xCDFA, 0x4E38, 0xFCAF, 0x4E39, 0xD3A1, 0x4E3B, 0xF1AB, 0x4E42, 0xE7D1, 0x4E43, 0xD2AC, 0x4E45, 0xCEF9, + 0x4E4B, 0xF1FD, 0x4E4D, 0xDEBF, 0x4E4E, 0xFBBA, 0x4E4F, 0xF9B9, 0x4E56, 0xCED2, 0x4E58, 0xE3AB, 0x4E59, 0xEBE0, 0x4E5D, 0xCEFA, + 0x4E5E, 0xCBF7, 0x4E5F, 0xE5A5, 0x4E6B, 0xCAE1, 0x4E6D, 0xD4CC, 0x4E73, 0xEAE1, 0x4E76, 0xDCE3, 0x4E77, 0xDFAD, 0x4E7E, 0xCBEB, + 0x4E82, 0xD5AF, 0x4E86, 0xD6F5, 0x4E88, 0xE5F8, 0x4E8B, 0xDEC0, 0x4E8C, 0xECA3, 0x4E8E, 0xE9CD, 0x4E90, 0xEAA7, 0x4E91, 0xE9F6, + 0x4E92, 0xFBBB, 0x4E94, 0xE7E9, 0x4E95, 0xEFCC, 0x4E98, 0xD0E6, 0x4E9B, 0xDEC1, 0x4E9E, 0xE4AC, 0x4EA1, 0xD8CC, 0x4EA2, 0xF9F1, + 0x4EA4, 0xCEDF, 0x4EA5, 0xFAA4, 0x4EA6, 0xE6B2, 0x4EA8, 0xFAFB, 0x4EAB, 0xFABD, 0x4EAC, 0xCCC8, 0x4EAD, 0xEFCD, 0x4EAE, 0xD5D5, + 0x4EB6, 0xD3A2, 0x4EBA, 0xECD1, 0x4EC0, 0xE4A7, 0x4EC1, 0xECD2, 0x4EC4, 0xF6B1, 0x4EC7, 0xCEFB, 0x4ECA, 0xD0D1, 0x4ECB, 0xCBBF, + 0x4ECD, 0xEDA4, 0x4ED4, 0xEDA8, 0x4ED5, 0xDEC2, 0x4ED6, 0xF6E2, 0x4ED7, 0xEDDC, 0x4ED8, 0xDCF5, 0x4ED9, 0xE0B9, 0x4EDD, 0xD4CE, + 0x4EDF, 0xF4B5, 0x4EE3, 0xD3DB, 0x4EE4, 0xD6B5, 0x4EE5, 0xECA4, 0x4EF0, 0xE4E6, 0x4EF2, 0xF1EA, 0x4EF6, 0xCBEC, 0x4EF7, 0xCBC0, + 0x4EFB, 0xECF2, 0x4F01, 0xD0EA, 0x4F09, 0xF9F2, 0x4F0A, 0xECA5, 0x4F0B, 0xD0DF, 0x4F0D, 0xE7EA, 0x4F0E, 0xD0EB, 0x4F0F, 0xDCD1, + 0x4F10, 0xDBE9, 0x4F11, 0xFDCC, 0x4F2F, 0xDBD7, 0x4F34, 0xDAE1, 0x4F36, 0xD6B6, 0x4F38, 0xE3DF, 0x4F3A, 0xDEC3, 0x4F3C, 0xDEC4, + 0x4F3D, 0xCAA1, 0x4F43, 0xEEEC, 0x4F46, 0xD3A3, 0x4F47, 0xEEB7, 0x4F48, 0xF8CF, 0x4F4D, 0xEAC8, 0x4F4E, 0xEEB8, 0x4F4F, 0xF1AC, + 0x4F50, 0xF1A5, 0x4F51, 0xE9CE, 0x4F55, 0xF9BC, 0x4F59, 0xE5F9, 0x4F5A, 0xECEA, 0x4F5B, 0xDDD6, 0x4F5C, 0xEDC2, 0x4F69, 0xF8A5, + 0x4F6F, 0xE5BA, 0x4F70, 0xDBD8, 0x4F73, 0xCAA2, 0x4F76, 0xD1CD, 0x4F7A, 0xEEED, 0x4F7E, 0xECEB, 0x4F7F, 0xDEC5, 0x4F81, 0xE3E0, + 0x4F83, 0xCAC9, 0x4F84, 0xF2E9, 0x4F86, 0xD5CE, 0x4F88, 0xF6B6, 0x4F8A, 0xCEC2, 0x4F8B, 0xD6C7, 0x4F8D, 0xE3B4, 0x4F8F, 0xF1AD, + 0x4F91, 0xEAE2, 0x4F96, 0xD7C2, 0x4F98, 0xF3A7, 0x4F9B, 0xCDEA, 0x4F9D, 0xEBEE, 0x4FAE, 0xD9B2, 0x4FAF, 0xFDA5, 0x4FB5, 0xF6D5, + 0x4FB6, 0xD5E2, 0x4FBF, 0xF8B5, 0x4FC2, 0xCCF5, 0x4FC3, 0xF5B5, 0x4FC4, 0xE4AD, 0x4FC9, 0xE7EB, 0x4FCA, 0xF1D5, 0x4FCE, 0xF0BB, + 0x4FD1, 0xE9B5, 0x4FD3, 0xCCC9, 0x4FD4, 0xFAD5, 0x4FD7, 0xE1D4, 0x4FDA, 0xD7D6, 0x4FDD, 0xDCC1, 0x4FDF, 0xDEC6, 0x4FE0, 0xFAEF, + 0x4FE1, 0xE3E1, 0x4FEE, 0xE1F3, 0x4FEF, 0xDCF6, 0x4FF1, 0xCEFC, 0x4FF3, 0xDBC4, 0x4FF5, 0xF8F1, 0x4FF8, 0xDCE4, 0x4FFA, 0xE5EF, + 0x5002, 0xDCB1, 0x5006, 0xD5D6, 0x5009, 0xF3DA, 0x500B, 0xCBC1, 0x500D, 0xDBC3, 0x5011, 0xD9FA, 0x5012, 0xD3EE, 0x5016, 0xFAB8, + 0x5019, 0xFDA6, 0x501A, 0xEBEF, 0x501C, 0xF4A6, 0x501E, 0xCCCA, 0x501F, 0xF3A8, 0x5021, 0xF3DB, 0x5023, 0xDBA7, 0x5024, 0xF6B7, + 0x5026, 0xCFE6, 0x5027, 0xF0F2, 0x5028, 0xCBDA, 0x502A, 0xE7D2, 0x502B, 0xD7C3, 0x502C, 0xF6F0, 0x502D, 0xE8DE, 0x503B, 0xE5A6, + 0x5043, 0xE5E7, 0x5047, 0xCAA3, 0x5048, 0xCCA7, 0x5049, 0xEAC9, 0x504F, 0xF8B6, 0x5055, 0xFAA5, 0x505A, 0xF1AE, 0x505C, 0xEFCE, + 0x5065, 0xCBED, 0x5074, 0xF6B0, 0x5075, 0xEFCF, 0x5076, 0xE9CF, 0x5078, 0xF7DE, 0x5080, 0xCED3, 0x5085, 0xDCF7, 0x508D, 0xDBA8, + 0x5091, 0xCBF8, 0x5098, 0xDFA1, 0x5099, 0xDDE1, 0x50AC, 0xF5CA, 0x50AD, 0xE9B6, 0x50B2, 0xE7EC, 0x50B3, 0xEEEE, 0x50B5, 0xF3F0, + 0x50B7, 0xDFBF, 0x50BE, 0xCCCB, 0x50C5, 0xD0C1, 0x50C9, 0xF4D2, 0x50CA, 0xE0BA, 0x50CF, 0xDFC0, 0x50D1, 0xCEE0, 0x50D5, 0xDCD2, + 0x50D6, 0xFDEA, 0x50DA, 0xD6F6, 0x50DE, 0xEACA, 0x50E5, 0xE8E9, 0x50E7, 0xE3AC, 0x50ED, 0xF3D0, 0x50F9, 0xCAA4, 0x50FB, 0xDBF8, + 0x50FF, 0xDEC7, 0x5100, 0xEBF0, 0x5101, 0xF1D6, 0x5104, 0xE5E2, 0x5106, 0xCCCC, 0x5109, 0xCBFB, 0x5112, 0xEAE3, 0x511F, 0xDFC1, + 0x5121, 0xD6ED, 0x512A, 0xE9D0, 0x5132, 0xEEB9, 0x5137, 0xD5E3, 0x513A, 0xD1D3, 0x513C, 0xE5F0, 0x5140, 0xE8B4, 0x5141, 0xEBC3, + 0x5143, 0xEAAA, 0x5144, 0xFAFC, 0x5145, 0xF5F6, 0x5146, 0xF0BC, 0x5147, 0xFDD4, 0x5148, 0xE0BB, 0x5149, 0xCEC3, 0x514B, 0xD0BA, + 0x514C, 0xF7BA, 0x514D, 0xD8F3, 0x514E, 0xF7CD, 0x5152, 0xE4AE, 0x515C, 0xD4DF, 0x5162, 0xD0E7, 0x5165, 0xECFD, 0x5167, 0xD2AE, + 0x5168, 0xEEEF, 0x5169, 0xD5D7, 0x516A, 0xEAE4, 0x516B, 0xF8A2, 0x516C, 0xCDEB, 0x516D, 0xD7BF, 0x516E, 0xFBB1, 0x5171, 0xCDEC, + 0x5175, 0xDCB2, 0x5176, 0xD0EC, 0x5177, 0xCEFD, 0x5178, 0xEEF0, 0x517C, 0xCCC2, 0x5180, 0xD0ED, 0x5186, 0xE5F7, 0x518A, 0xF3FC, + 0x518D, 0xEEA2, 0x5192, 0xD9B3, 0x5195, 0xD8F4, 0x5197, 0xE9B7, 0x51A0, 0xCEAE, 0x51A5, 0xD9A2, 0x51AA, 0xD8F1, 0x51AC, 0xD4CF, + 0x51B6, 0xE5A7, 0x51B7, 0xD5D2, 0x51BD, 0xD6A9, 0x51C4, 0xF4A2, 0x51C6, 0xF1D7, 0x51C9, 0xD5D8, 0x51CB, 0xF0BD, 0x51CC, 0xD7D0, + 0x51CD, 0xD4D0, 0x51DC, 0xD7CF, 0x51DD, 0xEBEA, 0x51DE, 0xFDEB, 0x51E1, 0xDBED, 0x51F0, 0xFCC5, 0x51F1, 0xCBC2, 0x51F6, 0xFDD5, + 0x51F8, 0xF4C8, 0x51F9, 0xE8EA, 0x51FA, 0xF5F3, 0x51FD, 0xF9DE, 0x5200, 0xD3EF, 0x5203, 0xECD3, 0x5206, 0xDDC2, 0x5207, 0xEFB7, + 0x5208, 0xE7D4, 0x520A, 0xCACA, 0x520E, 0xD9FB, 0x5211, 0xFAFD, 0x5217, 0xD6AA, 0x521D, 0xF4F8, 0x5224, 0xF7F7, 0x5225, 0xDCAC, + 0x5229, 0xD7D7, 0x522A, 0xDFA2, 0x522E, 0xCEBE, 0x5230, 0xD3F0, 0x5236, 0xF0A4, 0x5237, 0xE1EC, 0x5238, 0xCFE7, 0x5239, 0xF3CB, + 0x523A, 0xEDA9, 0x523B, 0xCABE, 0x5243, 0xF4EF, 0x5247, 0xF6CE, 0x524A, 0xDEFB, 0x524B, 0xD0BB, 0x524C, 0xD5B7, 0x524D, 0xEEF1, + 0x5254, 0xF4A8, 0x5256, 0xDCF8, 0x525B, 0xCBA7, 0x525D, 0xDACE, 0x5261, 0xE0E6, 0x5269, 0xEDA5, 0x526A, 0xEEF2, 0x526F, 0xDCF9, + 0x5272, 0xF9DC, 0x5275, 0xF3DC, 0x527D, 0xF8F2, 0x527F, 0xF4F9, 0x5283, 0xFCF1, 0x5287, 0xD0BC, 0x5288, 0xDBF9, 0x5289, 0xD7B1, + 0x528D, 0xCBFC, 0x5291, 0xF0A5, 0x5292, 0xCBFD, 0x529B, 0xD5F4, 0x529F, 0xCDED, 0x52A0, 0xCAA5, 0x52A3, 0xD6AB, 0x52A4, 0xD0C2, + 0x52A9, 0xF0BE, 0x52AA, 0xD2BD, 0x52AB, 0xCCA4, 0x52BE, 0xFAB6, 0x52C1, 0xCCCD, 0x52C3, 0xDAFA, 0x52C5, 0xF6CF, 0x52C7, 0xE9B8, + 0x52C9, 0xD8F5, 0x52CD, 0xCCCE, 0x52D2, 0xD7CD, 0x52D5, 0xD4D1, 0x52D6, 0xE9ED, 0x52D8, 0xCAEB, 0x52D9, 0xD9E2, 0x52DB, 0xFDB2, + 0x52DD, 0xE3AD, 0x52DE, 0xD6CC, 0x52DF, 0xD9B4, 0x52E2, 0xE1A7, 0x52E3, 0xEED3, 0x52E4, 0xD0C3, 0x52F3, 0xFDB3, 0x52F5, 0xD5E4, + 0x52F8, 0xCFE8, 0x52FA, 0xEDC3, 0x52FB, 0xD0B2, 0x52FE, 0xCEFE, 0x52FF, 0xDAA8, 0x5305, 0xF8D0, 0x5308, 0xFDD6, 0x530D, 0xF8D1, + 0x530F, 0xF8D2, 0x5310, 0xDCD3, 0x5315, 0xDDE2, 0x5316, 0xFBF9, 0x5317, 0xDDC1, 0x5319, 0xE3B5, 0x5320, 0xEDDD, 0x5321, 0xCEC4, + 0x5323, 0xCBA1, 0x532A, 0xDDE3, 0x532F, 0xFCDD, 0x5339, 0xF9AF, 0x533F, 0xD2FB, 0x5340, 0xCFA1, 0x5341, 0xE4A8, 0x5343, 0xF4B6, + 0x5344, 0xECFE, 0x5347, 0xE3AE, 0x5348, 0xE7ED, 0x5349, 0xFDC1, 0x534A, 0xDAE2, 0x534D, 0xD8B3, 0x5351, 0xDDE4, 0x5352, 0xF0EF, + 0x5353, 0xF6F1, 0x5354, 0xFAF0, 0x5357, 0xD1F5, 0x535A, 0xDACF, 0x535C, 0xDCD4, 0x535E, 0xDCA6, 0x5360, 0xEFBF, 0x5366, 0xCECF, + 0x5368, 0xE0D9, 0x536F, 0xD9D6, 0x5370, 0xECD4, 0x5371, 0xEACB, 0x5374, 0xCABF, 0x5375, 0xD5B0, 0x5377, 0xCFE9, 0x537D, 0xF1ED, + 0x537F, 0xCCCF, 0x5384, 0xE4F8, 0x5393, 0xE4ED, 0x5398, 0xD7D8, 0x539A, 0xFDA7, 0x539F, 0xEAAB, 0x53A0, 0xF6B2, 0x53A5, 0xCFF0, + 0x53A6, 0xF9BD, 0x53AD, 0xE6F4, 0x53BB, 0xCBDB, 0x53C3, 0xF3D1, 0x53C8, 0xE9D1, 0x53C9, 0xF3A9, 0x53CA, 0xD0E0, 0x53CB, 0xE9D2, + 0x53CD, 0xDAE3, 0x53D4, 0xE2D2, 0x53D6, 0xF6A2, 0x53D7, 0xE1F4, 0x53DB, 0xDAE4, 0x53E1, 0xE7D5, 0x53E2, 0xF5BF, 0x53E3, 0xCFA2, + 0x53E4, 0xCDAF, 0x53E5, 0xCFA3, 0x53E9, 0xCDB0, 0x53EA, 0xF1FE, 0x53EB, 0xD0A3, 0x53EC, 0xE1AF, 0x53ED, 0xF8A3, 0x53EF, 0xCAA6, + 0x53F0, 0xF7BB, 0x53F1, 0xF2EA, 0x53F2, 0xDEC8, 0x53F3, 0xE9D3, 0x53F8, 0xDEC9, 0x5403, 0xFDDE, 0x5404, 0xCAC0, 0x5408, 0xF9EA, + 0x5409, 0xD1CE, 0x540A, 0xEED4, 0x540C, 0xD4D2, 0x540D, 0xD9A3, 0x540E, 0xFDA8, 0x540F, 0xD7D9, 0x5410, 0xF7CE, 0x5411, 0xFABE, + 0x541B, 0xCFD6, 0x541D, 0xD7F0, 0x541F, 0xEBE1, 0x5420, 0xF8C5, 0x5426, 0xDCFA, 0x5429, 0xDDC3, 0x542B, 0xF9DF, 0x5433, 0xE7EF, + 0x5438, 0xFDE5, 0x5439, 0xF6A3, 0x543B, 0xD9FC, 0x543C, 0xFDA9, 0x543E, 0xE7EE, 0x5442, 0xD5E5, 0x5448, 0xEFD0, 0x544A, 0xCDB1, + 0x5451, 0xF7A2, 0x5468, 0xF1B2, 0x546A, 0xF1B1, 0x5471, 0xCDB2, 0x5473, 0xDAAB, 0x5475, 0xCAA7, 0x547B, 0xE3E2, 0x547C, 0xFBBC, + 0x547D, 0xD9A4, 0x5480, 0xEEBA, 0x5486, 0xF8D3, 0x548C, 0xFBFA, 0x548E, 0xCFA4, 0x5490, 0xDCFB, 0x54A4, 0xF6E3, 0x54A8, 0xEDAA, + 0x54AB, 0xF2A1, 0x54AC, 0xCEE1, 0x54B3, 0xFAA6, 0x54B8, 0xF9E0, 0x54BD, 0xECD6, 0x54C0, 0xE4EE, 0x54C1, 0xF9A1, 0x54C4, 0xFBEF, + 0x54C8, 0xF9EB, 0x54C9, 0xEEA3, 0x54E1, 0xEAAC, 0x54E5, 0xCAA8, 0x54E8, 0xF4FA, 0x54ED, 0xCDD6, 0x54EE, 0xFCF6, 0x54F2, 0xF4C9, + 0x54FA, 0xF8D4, 0x5504, 0xF8A6, 0x5506, 0xDECA, 0x5507, 0xF2C6, 0x550E, 0xD7DA, 0x5510, 0xD3D0, 0x551C, 0xD8C5, 0x552F, 0xEAE6, + 0x5531, 0xF3DD, 0x5535, 0xE4DA, 0x553E, 0xF6E4, 0x5544, 0xF6F2, 0x5546, 0xDFC2, 0x554F, 0xD9FD, 0x5553, 0xCCF6, 0x5556, 0xD3BA, + 0x555E, 0xE4AF, 0x5563, 0xF9E1, 0x557C, 0xF0A6, 0x5580, 0xCBD3, 0x5584, 0xE0BC, 0x5586, 0xF4CA, 0x5587, 0xD4FA, 0x5589, 0xFDAA, + 0x558A, 0xF9E2, 0x5598, 0xF4B7, 0x5599, 0xFDC2, 0x559A, 0xFCB0, 0x559C, 0xFDEC, 0x559D, 0xCAE2, 0x55A7, 0xFDBD, 0x55A9, 0xEAE7, + 0x55AA, 0xDFC3, 0x55AB, 0xD1D2, 0x55AC, 0xCEE2, 0x55AE, 0xD3A4, 0x55C5, 0xFDAB, 0x55C7, 0xDFE0, 0x55D4, 0xF2C7, 0x55DA, 0xE7F0, + 0x55DC, 0xD0EE, 0x55DF, 0xF3AA, 0x55E3, 0xDECB, 0x55E4, 0xF6B8, 0x55FD, 0xE1F5, 0x55FE, 0xF1B3, 0x5606, 0xF7A3, 0x5609, 0xCAA9, + 0x5614, 0xCFA5, 0x5617, 0xDFC4, 0x562F, 0xE1B0, 0x5632, 0xF0BF, 0x5634, 0xF6A4, 0x5636, 0xE3B6, 0x5653, 0xFAC6, 0x5668, 0xD0EF, + 0x566B, 0xFDED, 0x5674, 0xDDC4, 0x5686, 0xFCF7, 0x56A5, 0xE6BF, 0x56AC, 0xDEAD, 0x56AE, 0xFABF, 0x56B4, 0xE5F1, 0x56BC, 0xEDC4, + 0x56CA, 0xD2A5, 0x56CD, 0xFDEE, 0x56D1, 0xF5B6, 0x56DA, 0xE1F6, 0x56DB, 0xDECC, 0x56DE, 0xFCDE, 0x56E0, 0xECD7, 0x56F0, 0xCDDD, + 0x56F9, 0xD6B7, 0x56FA, 0xCDB3, 0x5703, 0xF8D5, 0x5704, 0xE5D8, 0x5708, 0xCFEA, 0x570B, 0xCFD0, 0x570D, 0xEACC, 0x5712, 0xEAAE, + 0x5713, 0xEAAD, 0x5716, 0xD3F1, 0x5718, 0xD3A5, 0x571F, 0xF7CF, 0x5728, 0xEEA4, 0x572D, 0xD0A4, 0x5730, 0xF2A2, 0x573B, 0xD0F0, + 0x5740, 0xF2A3, 0x5742, 0xF7F8, 0x5747, 0xD0B3, 0x574A, 0xDBA9, 0x574D, 0xD3BB, 0x574E, 0xCAEC, 0x5750, 0xF1A6, 0x5751, 0xCBD5, + 0x5761, 0xF7E7, 0x5764, 0xCDDE, 0x5766, 0xF7A4, 0x576A, 0xF8C0, 0x576E, 0xD3DD, 0x5770, 0xCCD0, 0x5775, 0xCFA6, 0x577C, 0xF6F3, + 0x5782, 0xE1F7, 0x5788, 0xD3DC, 0x578B, 0xFAFE, 0x5793, 0xFAA7, 0x57A0, 0xEBD9, 0x57A2, 0xCFA7, 0x57A3, 0xEAAF, 0x57C3, 0xE4EF, + 0x57C7, 0xE9B9, 0x57C8, 0xF1D8, 0x57CB, 0xD8D8, 0x57CE, 0xE0F2, 0x57DF, 0xE6B4, 0x57E0, 0xDCFC, 0x57F0, 0xF3F1, 0x57F4, 0xE3D0, + 0x57F7, 0xF2FB, 0x57F9, 0xDBC6, 0x57FA, 0xD0F1, 0x57FC, 0xD0F2, 0x5800, 0xCFDC, 0x5802, 0xD3D1, 0x5805, 0xCCB1, 0x5806, 0xF7D8, + 0x5808, 0xCBA8, 0x5809, 0xEBBC, 0x580A, 0xE4BE, 0x581E, 0xF4DC, 0x5821, 0xDCC2, 0x5824, 0xF0A7, 0x5827, 0xE6C0, 0x582A, 0xCAED, + 0x582F, 0xE8EB, 0x5830, 0xE5E8, 0x5831, 0xDCC3, 0x5834, 0xEDDE, 0x5835, 0xD3F2, 0x583A, 0xCCF7, 0x584A, 0xCED4, 0x584B, 0xE7AB, + 0x584F, 0xCBC3, 0x5851, 0xE1B1, 0x5854, 0xF7B2, 0x5857, 0xD3F3, 0x5858, 0xD3D2, 0x585A, 0xF5C0, 0x585E, 0xDFDD, 0x5861, 0xEEF3, + 0x5862, 0xE7F1, 0x5864, 0xFDB4, 0x5875, 0xF2C8, 0x5879, 0xF3D2, 0x587C, 0xEEF4, 0x587E, 0xE2D3, 0x5883, 0xCCD1, 0x5885, 0xDFEA, + 0x5889, 0xE9BA, 0x5893, 0xD9D7, 0x589C, 0xF5CD, 0x589E, 0xF1F2, 0x589F, 0xFAC7, 0x58A8, 0xD9F8, 0x58A9, 0xD4C2, 0x58AE, 0xF6E5, + 0x58B3, 0xDDC5, 0x58BA, 0xE7F2, 0x58BB, 0xEDDF, 0x58BE, 0xCACB, 0x58C1, 0xDBFA, 0x58C5, 0xE8B5, 0x58C7, 0xD3A6, 0x58CE, 0xFDB5, + 0x58D1, 0xF9C9, 0x58D3, 0xE4E2, 0x58D5, 0xFBBD, 0x58D8, 0xD7A4, 0x58D9, 0xCEC5, 0x58DE, 0xCED5, 0x58DF, 0xD6E6, 0x58E4, 0xE5BD, + 0x58EB, 0xDECD, 0x58EC, 0xECF3, 0x58EF, 0xEDE0, 0x58F9, 0xECEC, 0x58FA, 0xFBBE, 0x58FB, 0xDFEB, 0x58FD, 0xE1F8, 0x590F, 0xF9BE, + 0x5914, 0xD0F3, 0x5915, 0xE0AA, 0x5916, 0xE8E2, 0x5919, 0xE2D4, 0x591A, 0xD2FD, 0x591C, 0xE5A8, 0x5922, 0xD9D3, 0x5927, 0xD3DE, + 0x5929, 0xF4B8, 0x592A, 0xF7BC, 0x592B, 0xDCFD, 0x592D, 0xE8EC, 0x592E, 0xE4E7, 0x5931, 0xE3F7, 0x5937, 0xECA8, 0x593E, 0xFAF1, + 0x5944, 0xE5F2, 0x5947, 0xD0F4, 0x5948, 0xD2AF, 0x5949, 0xDCE5, 0x594E, 0xD0A5, 0x594F, 0xF1B4, 0x5950, 0xFCB1, 0x5951, 0xCCF8, + 0x5954, 0xDDC6, 0x5955, 0xFAD1, 0x5957, 0xF7DF, 0x595A, 0xFAA8, 0x5960, 0xEEF5, 0x5962, 0xDECE, 0x5967, 0xE7F3, 0x596A, 0xF7AC, + 0x596B, 0xEBC4, 0x596C, 0xEDE1, 0x596D, 0xE0AB, 0x596E, 0xDDC7, 0x5973, 0xD2B3, 0x5974, 0xD2BF, 0x5978, 0xCACC, 0x597D, 0xFBBF, + 0x5982, 0xE5FD, 0x5983, 0xDDE5, 0x5984, 0xD8CD, 0x598A, 0xECF4, 0x5993, 0xD0F5, 0x5996, 0xE8ED, 0x5997, 0xD0D2, 0x5999, 0xD9D8, + 0x59A5, 0xF6E6, 0x59A8, 0xDBAA, 0x59AC, 0xF7E0, 0x59B9, 0xD8D9, 0x59BB, 0xF4A3, 0x59BE, 0xF4DD, 0x59C3, 0xEFD1, 0x59C6, 0xD9B5, + 0x59C9, 0xEDAB, 0x59CB, 0xE3B7, 0x59D0, 0xEEBB, 0x59D1, 0xCDB4, 0x59D3, 0xE0F3, 0x59D4, 0xEACD, 0x59D9, 0xECF5, 0x59DA, 0xE8EE, + 0x59DC, 0xCBA9, 0x59DD, 0xF1AF, 0x59E6, 0xCACD, 0x59E8, 0xECA9, 0x59EA, 0xF2EB, 0x59EC, 0xFDEF, 0x59EE, 0xF9F3, 0x59F8, 0xE6C1, + 0x59FB, 0xECD8, 0x59FF, 0xEDAC, 0x5A01, 0xEACE, 0x5A03, 0xE8DF, 0x5A11, 0xDECF, 0x5A18, 0xD2A6, 0x5A1B, 0xE7F4, 0x5A1C, 0xD1D6, + 0x5A1F, 0xE6C2, 0x5A20, 0xE3E3, 0x5A25, 0xE4B0, 0x5A29, 0xD8B4, 0x5A36, 0xF6A5, 0x5A3C, 0xF3DE, 0x5A41, 0xD7A5, 0x5A46, 0xF7E8, + 0x5A49, 0xE8C6, 0x5A5A, 0xFBE6, 0x5A62, 0xDDE6, 0x5A66, 0xDCFE, 0x5A92, 0xD8DA, 0x5A9A, 0xDAAC, 0x5A9B, 0xEAB0, 0x5AA4, 0xE3B8, + 0x5AC1, 0xCAAA, 0x5AC2, 0xE1F9, 0x5AC4, 0xEAB1, 0x5AC9, 0xF2EC, 0x5ACC, 0xFAEE, 0x5AE1, 0xEED5, 0x5AE6, 0xF9F4, 0x5AE9, 0xD2EC, + 0x5B05, 0xFBFB, 0x5B09, 0xFDF0, 0x5B0B, 0xE0BD, 0x5B0C, 0xCEE3, 0x5B16, 0xF8C6, 0x5B2A, 0xDEAE, 0x5B40, 0xDFC5, 0x5B43, 0xE5BE, + 0x5B50, 0xEDAD, 0x5B51, 0xFAEA, 0x5B54, 0xCDEE, 0x5B55, 0xEDA6, 0x5B57, 0xEDAE, 0x5B58, 0xF0ED, 0x5B5A, 0xDDA1, 0x5B5C, 0xEDAF, + 0x5B5D, 0xFCF8, 0x5B5F, 0xD8EB, 0x5B63, 0xCCF9, 0x5B64, 0xCDB5, 0x5B69, 0xFAA9, 0x5B6B, 0xE1DD, 0x5B70, 0xE2D5, 0x5B71, 0xEDCF, + 0x5B75, 0xDDA2, 0x5B78, 0xF9CA, 0x5B7A, 0xEAE8, 0x5B7C, 0xE5ED, 0x5B85, 0xD3EB, 0x5B87, 0xE9D4, 0x5B88, 0xE1FA, 0x5B89, 0xE4CC, + 0x5B8B, 0xE1E4, 0x5B8C, 0xE8C7, 0x5B8F, 0xCEDB, 0x5B93, 0xDCD5, 0x5B95, 0xF7B5, 0x5B96, 0xFCF3, 0x5B97, 0xF0F3, 0x5B98, 0xCEAF, + 0x5B99, 0xF1B5, 0x5B9A, 0xEFD2, 0x5B9B, 0xE8C8, 0x5B9C, 0xEBF1, 0x5BA2, 0xCBD4, 0x5BA3, 0xE0BE, 0x5BA4, 0xE3F8, 0x5BA5, 0xEAE9, + 0x5BA6, 0xFCB2, 0x5BAC, 0xE0F4, 0x5BAE, 0xCFE0, 0x5BB0, 0xEEA5, 0x5BB3, 0xFAAA, 0x5BB4, 0xE6C3, 0x5BB5, 0xE1B2, 0x5BB6, 0xCAAB, + 0x5BB8, 0xE3E4, 0x5BB9, 0xE9BB, 0x5BBF, 0xE2D6, 0x5BC0, 0xF3F2, 0x5BC2, 0xEED6, 0x5BC3, 0xEAB2, 0x5BC4, 0xD0F6, 0x5BC5, 0xECD9, + 0x5BC6, 0xDACB, 0x5BC7, 0xCFA8, 0x5BCC, 0xDDA3, 0x5BD0, 0xD8DB, 0x5BD2, 0xF9CE, 0x5BD3, 0xE9D5, 0x5BD4, 0xE3D1, 0x5BD7, 0xD2BC, + 0x5BDE, 0xD8AC, 0x5BDF, 0xF3CC, 0x5BE1, 0xCDFB, 0x5BE2, 0xF6D6, 0x5BE4, 0xE7F5, 0x5BE5, 0xE8EF, 0x5BE6, 0xE3F9, 0x5BE7, 0xD2BB, + 0x5BE8, 0xF3F3, 0x5BE9, 0xE3FB, 0x5BEB, 0xDED0, 0x5BEC, 0xCEB0, 0x5BEE, 0xD6F7, 0x5BEF, 0xF1D9, 0x5BF5, 0xF5C1, 0x5BF6, 0xDCC4, + 0x5BF8, 0xF5BB, 0x5BFA, 0xDED1, 0x5C01, 0xDCE6, 0x5C04, 0xDED2, 0x5C07, 0xEDE2, 0x5C08, 0xEEF6, 0x5C09, 0xEACF, 0x5C0A, 0xF0EE, + 0x5C0B, 0xE3FC, 0x5C0D, 0xD3DF, 0x5C0E, 0xD3F4, 0x5C0F, 0xE1B3, 0x5C11, 0xE1B4, 0x5C16, 0xF4D3, 0x5C19, 0xDFC6, 0x5C24, 0xE9D6, + 0x5C28, 0xDBAB, 0x5C31, 0xF6A6, 0x5C38, 0xE3B9, 0x5C39, 0xEBC5, 0x5C3A, 0xF4A9, 0x5C3B, 0xCDB6, 0x5C3C, 0xD2F9, 0x5C3E, 0xDAAD, + 0x5C3F, 0xD2E3, 0x5C40, 0xCFD1, 0x5C45, 0xCBDC, 0x5C46, 0xCCFA, 0x5C48, 0xCFDD, 0x5C4B, 0xE8A9, 0x5C4D, 0xE3BB, 0x5C4E, 0xE3BA, + 0x5C51, 0xE0DA, 0x5C55, 0xEEF7, 0x5C5B, 0xDCB3, 0x5C60, 0xD3F5, 0x5C62, 0xD7A6, 0x5C64, 0xF6B5, 0x5C65, 0xD7DB, 0x5C6C, 0xE1D5, + 0x5C6F, 0xD4EA, 0x5C71, 0xDFA3, 0x5C79, 0xFDDF, 0x5C90, 0xD0F7, 0x5C91, 0xEDD4, 0x5CA1, 0xCBAA, 0x5CA9, 0xE4DB, 0x5CAB, 0xE1FB, + 0x5CAC, 0xCBA2, 0x5CB1, 0xD3E0, 0x5CB3, 0xE4BF, 0x5CB5, 0xFBC0, 0x5CB7, 0xDABE, 0x5CB8, 0xE4CD, 0x5CBA, 0xD6B9, 0x5CBE, 0xEFC0, + 0x5CC0, 0xE1FC, 0x5CD9, 0xF6B9, 0x5CE0, 0xDFC7, 0x5CE8, 0xE4B1, 0x5CEF, 0xDCE7, 0x5CF0, 0xDCE8, 0x5CF4, 0xFAD6, 0x5CF6, 0xD3F6, + 0x5CFB, 0xF1DA, 0x5CFD, 0xFAF2, 0x5D07, 0xE2FD, 0x5D0D, 0xD5CF, 0x5D0E, 0xD0F8, 0x5D11, 0xCDDF, 0x5D14, 0xF5CB, 0x5D16, 0xE4F0, + 0x5D17, 0xCBAB, 0x5D19, 0xD7C4, 0x5D27, 0xE2FE, 0x5D29, 0xDDDA, 0x5D4B, 0xDAAE, 0x5D4C, 0xCAEE, 0x5D50, 0xD5B9, 0x5D69, 0xE3A1, + 0x5D6C, 0xE8E3, 0x5D6F, 0xF3AB, 0x5D87, 0xCFA9, 0x5D8B, 0xD3F7, 0x5D9D, 0xD4F1, 0x5DA0, 0xCEE4, 0x5DA2, 0xE8F2, 0x5DAA, 0xE5F5, + 0x5DB8, 0xE7AE, 0x5DBA, 0xD6BA, 0x5DBC, 0xDFEC, 0x5DBD, 0xE4C0, 0x5DCD, 0xE8E4, 0x5DD2, 0xD8B5, 0x5DD6, 0xE4DC, 0x5DDD, 0xF4B9, + 0x5DDE, 0xF1B6, 0x5DE1, 0xE2DE, 0x5DE2, 0xE1B5, 0x5DE5, 0xCDEF, 0x5DE6, 0xF1A7, 0x5DE7, 0xCEE5, 0x5DE8, 0xCBDD, 0x5DEB, 0xD9E3, + 0x5DEE, 0xF3AC, 0x5DF1, 0xD0F9, 0x5DF2, 0xECAB, 0x5DF3, 0xDED3, 0x5DF4, 0xF7E9, 0x5DF7, 0xF9F5, 0x5DFD, 0xE1DE, 0x5DFE, 0xCBEE, + 0x5E02, 0xE3BC, 0x5E03, 0xF8D6, 0x5E06, 0xDBEE, 0x5E0C, 0xFDF1, 0x5E11, 0xF7B6, 0x5E16, 0xF4DE, 0x5E19, 0xF2ED, 0x5E1B, 0xDBD9, + 0x5E1D, 0xF0A8, 0x5E25, 0xE1FD, 0x5E2B, 0xDED4, 0x5E2D, 0xE0AC, 0x5E33, 0xEDE3, 0x5E36, 0xD3E1, 0x5E38, 0xDFC8, 0x5E3D, 0xD9B6, + 0x5E3F, 0xFDAC, 0x5E40, 0xEFD3, 0x5E44, 0xE4C1, 0x5E45, 0xF8EB, 0x5E47, 0xDBAC, 0x5E4C, 0xFCC6, 0x5E55, 0xD8AD, 0x5E5F, 0xF6BA, + 0x5E61, 0xDBDF, 0x5E62, 0xD3D3, 0x5E63, 0xF8C7, 0x5E72, 0xCACE, 0x5E73, 0xF8C1, 0x5E74, 0xD2B4, 0x5E77, 0xDCB4, 0x5E78, 0xFAB9, + 0x5E79, 0xCACF, 0x5E7B, 0xFCB3, 0x5E7C, 0xEAEA, 0x5E7D, 0xEAEB, 0x5E7E, 0xD0FA, 0x5E84, 0xEDE4, 0x5E87, 0xDDE7, 0x5E8A, 0xDFC9, + 0x5E8F, 0xDFED, 0x5E95, 0xEEBC, 0x5E97, 0xEFC1, 0x5E9A, 0xCCD2, 0x5E9C, 0xDDA4, 0x5EA0, 0xDFCA, 0x5EA6, 0xD3F8, 0x5EA7, 0xF1A8, + 0x5EAB, 0xCDB7, 0x5EAD, 0xEFD4, 0x5EB5, 0xE4DD, 0x5EB6, 0xDFEE, 0x5EB7, 0xCBAC, 0x5EB8, 0xE9BC, 0x5EBE, 0xEAEC, 0x5EC2, 0xDFCB, + 0x5EC8, 0xF9BF, 0x5EC9, 0xD6AF, 0x5ECA, 0xD5C6, 0x5ED0, 0xCFAA, 0x5ED3, 0xCEA9, 0x5ED6, 0xD6F8, 0x5EDA, 0xF1B7, 0x5EDB, 0xEEF8, + 0x5EDF, 0xD9D9, 0x5EE0, 0xF3DF, 0x5EE2, 0xF8C8, 0x5EE3, 0xCEC6, 0x5EEC, 0xD5E6, 0x5EF3, 0xF4E6, 0x5EF6, 0xE6C5, 0x5EF7, 0xEFD5, + 0x5EFA, 0xCBEF, 0x5EFB, 0xFCDF, 0x5F01, 0xDCA7, 0x5F04, 0xD6E7, 0x5F0A, 0xF8C9, 0x5F0F, 0xE3D2, 0x5F11, 0xE3BD, 0x5F13, 0xCFE1, + 0x5F14, 0xF0C0, 0x5F15, 0xECDA, 0x5F17, 0xDDD7, 0x5F18, 0xFBF0, 0x5F1B, 0xECAC, 0x5F1F, 0xF0A9, 0x5F26, 0xFAD7, 0x5F27, 0xFBC1, + 0x5F29, 0xD2C0, 0x5F31, 0xE5B0, 0x5F35, 0xEDE5, 0x5F3A, 0xCBAD, 0x5F3C, 0xF9B0, 0x5F48, 0xF7A5, 0x5F4A, 0xCBAE, 0x5F4C, 0xDAAF, + 0x5F4E, 0xD8B6, 0x5F56, 0xD3A7, 0x5F57, 0xFBB2, 0x5F59, 0xFDC4, 0x5F5B, 0xECAD, 0x5F62, 0xFBA1, 0x5F66, 0xE5E9, 0x5F67, 0xE9EE, + 0x5F69, 0xF3F4, 0x5F6A, 0xF8F3, 0x5F6B, 0xF0C1, 0x5F6C, 0xDEAF, 0x5F6D, 0xF8B0, 0x5F70, 0xF3E0, 0x5F71, 0xE7AF, 0x5F77, 0xDBAD, + 0x5F79, 0xE6B5, 0x5F7C, 0xF9A8, 0x5F7F, 0xDDD8, 0x5F80, 0xE8D9, 0x5F81, 0xEFD6, 0x5F85, 0xD3E2, 0x5F87, 0xE2DF, 0x5F8A, 0xFCE0, + 0x5F8B, 0xD7C8, 0x5F8C, 0xFDAD, 0x5F90, 0xDFEF, 0x5F91, 0xCCD3, 0x5F92, 0xD3F9, 0x5F97, 0xD4F0, 0x5F98, 0xDBC7, 0x5F99, 0xDED5, + 0x5F9E, 0xF0F4, 0x5FA0, 0xD5D0, 0x5FA1, 0xE5D9, 0x5FA8, 0xFCC7, 0x5FA9, 0xDCD6, 0x5FAA, 0xE2E0, 0x5FAE, 0xDAB0, 0x5FB5, 0xF3A3, + 0x5FB7, 0xD3EC, 0x5FB9, 0xF4CB, 0x5FBD, 0xFDC5, 0x5FC3, 0xE3FD, 0x5FC5, 0xF9B1, 0x5FCC, 0xD0FB, 0x5FCD, 0xECDB, 0x5FD6, 0xF5BC, + 0x5FD7, 0xF2A4, 0x5FD8, 0xD8CE, 0x5FD9, 0xD8CF, 0x5FE0, 0xF5F7, 0x5FEB, 0xF6E1, 0x5FF5, 0xD2B7, 0x5FFD, 0xFBEC, 0x5FFF, 0xDDC8, + 0x600F, 0xE4E8, 0x6012, 0xD2C1, 0x6016, 0xF8D7, 0x601C, 0xD6BB, 0x601D, 0xDED6, 0x6020, 0xF7BD, 0x6021, 0xECAE, 0x6025, 0xD0E1, + 0x6027, 0xE0F5, 0x6028, 0xEAB3, 0x602A, 0xCED6, 0x602F, 0xCCA5, 0x6041, 0xECF6, 0x6042, 0xE2E1, 0x6043, 0xE3BE, 0x604D, 0xFCC8, + 0x6050, 0xCDF0, 0x6052, 0xF9F6, 0x6055, 0xDFF0, 0x6059, 0xE5BF, 0x605D, 0xCEBF, 0x6062, 0xFCE1, 0x6063, 0xEDB0, 0x6064, 0xFDD1, + 0x6065, 0xF6BB, 0x6068, 0xF9CF, 0x6069, 0xEBDA, 0x606A, 0xCAC1, 0x606C, 0xD2B8, 0x606D, 0xCDF1, 0x606F, 0xE3D3, 0x6070, 0xFDE6, + 0x6085, 0xE6ED, 0x6089, 0xE3FA, 0x608C, 0xF0AA, 0x608D, 0xF9D0, 0x6094, 0xFCE2, 0x6096, 0xF8A7, 0x609A, 0xE1E5, 0x609B, 0xEEF9, + 0x609F, 0xE7F6, 0x60A0, 0xEAED, 0x60A3, 0xFCB4, 0x60A4, 0xF5C2, 0x60A7, 0xD7DC, 0x60B0, 0xF0F5, 0x60B2, 0xDDE8, 0x60B3, 0xD3ED, + 0x60B4, 0xF5FC, 0x60B6, 0xDABF, 0x60B8, 0xCCFB, 0x60BC, 0xD3FA, 0x60BD, 0xF4A4, 0x60C5, 0xEFD7, 0x60C7, 0xD4C3, 0x60D1, 0xFBE3, + 0x60DA, 0xFBED, 0x60DC, 0xE0AD, 0x60DF, 0xEAEE, 0x60E0, 0xFBB3, 0x60E1, 0xE4C2, 0x60F0, 0xF6E7, 0x60F1, 0xD2DD, 0x60F3, 0xDFCC, + 0x60F6, 0xFCC9, 0x60F9, 0xE5A9, 0x60FA, 0xE0F6, 0x60FB, 0xF6B3, 0x6101, 0xE1FE, 0x6106, 0xCBF0, 0x6108, 0xEAEF, 0x6109, 0xEAF0, + 0x610D, 0xDAC0, 0x610E, 0xF8B4, 0x610F, 0xEBF2, 0x6115, 0xE4C3, 0x611A, 0xE9D7, 0x611B, 0xE4F1, 0x611F, 0xCAEF, 0x6127, 0xCED7, + 0x6130, 0xFCCA, 0x6134, 0xF3E1, 0x6137, 0xCBC4, 0x613C, 0xE3E5, 0x613E, 0xCBC5, 0x613F, 0xEAB4, 0x6142, 0xE9BD, 0x6144, 0xD7C9, + 0x6147, 0xEBDB, 0x6148, 0xEDB1, 0x614A, 0xCCC3, 0x614B, 0xF7BE, 0x614C, 0xFCCB, 0x6153, 0xF8F4, 0x6155, 0xD9B7, 0x6158, 0xF3D3, + 0x6159, 0xF3D4, 0x615D, 0xF7E4, 0x615F, 0xF7D1, 0x6162, 0xD8B7, 0x6163, 0xCEB1, 0x6164, 0xCAC2, 0x6167, 0xFBB4, 0x6168, 0xCBC6, + 0x616B, 0xF0F6, 0x616E, 0xD5E7, 0x6170, 0xEAD0, 0x6176, 0xCCD4, 0x6177, 0xCBAF, 0x617D, 0xF4AA, 0x617E, 0xE9AF, 0x6181, 0xF5C3, + 0x6182, 0xE9D8, 0x618A, 0xDDE9, 0x618E, 0xF1F3, 0x6190, 0xD5FB, 0x6191, 0xDEBB, 0x6194, 0xF4FB, 0x6198, 0xFDF3, 0x6199, 0xFDF2, + 0x619A, 0xF7A6, 0x61A4, 0xDDC9, 0x61A7, 0xD4D3, 0x61A9, 0xCCA8, 0x61AB, 0xDAC1, 0x61AC, 0xCCD5, 0x61AE, 0xD9E4, 0x61B2, 0xFACA, + 0x61B6, 0xE5E3, 0x61BA, 0xD3BC, 0x61BE, 0xCAF0, 0x61C3, 0xD0C4, 0x61C7, 0xCAD0, 0x61C8, 0xFAAB, 0x61C9, 0xEBEB, 0x61CA, 0xE7F8, + 0x61CB, 0xD9E5, 0x61E6, 0xD1D7, 0x61F2, 0xF3A4, 0x61F6, 0xD4FB, 0x61F7, 0xFCE3, 0x61F8, 0xFAD8, 0x61FA, 0xF3D5, 0x61FC, 0xCFAB, + 0x61FF, 0xEBF3, 0x6200, 0xD5FC, 0x6207, 0xD3D4, 0x6208, 0xCDFC, 0x620A, 0xD9E6, 0x620C, 0xE2F9, 0x620D, 0xE2A1, 0x620E, 0xEBD4, + 0x6210, 0xE0F7, 0x6211, 0xE4B2, 0x6212, 0xCCFC, 0x6216, 0xFBE4, 0x621A, 0xF4AB, 0x621F, 0xD0BD, 0x6221, 0xCAF1, 0x622A, 0xEFB8, + 0x622E, 0xD7C0, 0x6230, 0xEEFA, 0x6231, 0xFDF4, 0x6234, 0xD3E3, 0x6236, 0xFBC2, 0x623E, 0xD5E8, 0x623F, 0xDBAE, 0x6240, 0xE1B6, + 0x6241, 0xF8B7, 0x6247, 0xE0BF, 0x6248, 0xFBC3, 0x6249, 0xDDEA, 0x624B, 0xE2A2, 0x624D, 0xEEA6, 0x6253, 0xF6E8, 0x6258, 0xF6F5, + 0x626E, 0xDDCA, 0x6271, 0xD0E2, 0x6276, 0xDDA6, 0x6279, 0xDDEB, 0x627C, 0xE4F9, 0x627F, 0xE3AF, 0x6280, 0xD0FC, 0x6284, 0xF4FC, + 0x6289, 0xCCBC, 0x628A, 0xF7EA, 0x6291, 0xE5E4, 0x6292, 0xDFF1, 0x6295, 0xF7E1, 0x6297, 0xF9F7, 0x6298, 0xEFB9, 0x629B, 0xF8D8, + 0x62AB, 0xF9A9, 0x62B1, 0xF8D9, 0x62B5, 0xEEBD, 0x62B9, 0xD8C6, 0x62BC, 0xE4E3, 0x62BD, 0xF5CE, 0x62C2, 0xDDD9, 0x62C7, 0xD9E7, + 0x62C8, 0xD2B9, 0x62C9, 0xD5C3, 0x62CC, 0xDAE5, 0x62CD, 0xDAD0, 0x62CF, 0xD1D9, 0x62D0, 0xCED8, 0x62D2, 0xCBDE, 0x62D3, 0xF4AC, + 0x62D4, 0xDAFB, 0x62D6, 0xF6E9, 0x62D7, 0xE8F3, 0x62D8, 0xCFAC, 0x62D9, 0xF0F0, 0x62DB, 0xF4FD, 0x62DC, 0xDBC8, 0x62EC, 0xCEC0, + 0x62ED, 0xE3D4, 0x62EE, 0xD1CF, 0x62EF, 0xF1F5, 0x62F1, 0xCDF2, 0x62F3, 0xCFEB, 0x62F7, 0xCDB8, 0x62FE, 0xE3A6, 0x62FF, 0xD1DA, + 0x6301, 0xF2A5, 0x6307, 0xF2A6, 0x6309, 0xE4CE, 0x6311, 0xD3FB, 0x632B, 0xF1A9, 0x632F, 0xF2C9, 0x633A, 0xEFD8, 0x633B, 0xE6C9, + 0x633D, 0xD8B8, 0x633E, 0xFAF3, 0x6349, 0xF3B5, 0x634C, 0xF8A4, 0x634F, 0xD1F3, 0x6350, 0xE6C8, 0x6355, 0xF8DA, 0x6367, 0xDCE9, + 0x6368, 0xDED7, 0x636E, 0xCBDF, 0x6372, 0xCFEC, 0x6377, 0xF4DF, 0x637A, 0xD1F4, 0x637B, 0xD2BA, 0x637F, 0xDFF2, 0x6383, 0xE1B7, + 0x6388, 0xE2A3, 0x6389, 0xD3FC, 0x638C, 0xEDE6, 0x6392, 0xDBC9, 0x6396, 0xE4FA, 0x6398, 0xCFDE, 0x639B, 0xCED0, 0x63A0, 0xD5D3, + 0x63A1, 0xF3F5, 0x63A2, 0xF7AE, 0x63A5, 0xEFC8, 0x63A7, 0xCDF3, 0x63A8, 0xF5CF, 0x63A9, 0xE5F3, 0x63AA, 0xF0C2, 0x63C0, 0xCAD1, + 0x63C4, 0xEAF1, 0x63C6, 0xD0A6, 0x63CF, 0xD9DA, 0x63D0, 0xF0AB, 0x63D6, 0xEBE7, 0x63DA, 0xE5C0, 0x63DB, 0xFCB5, 0x63E1, 0xE4C4, + 0x63ED, 0xCCA9, 0x63EE, 0xFDC6, 0x63F4, 0xEAB5, 0x63F6, 0xE5AA, 0x63F7, 0xDFBA, 0x640D, 0xE1DF, 0x640F, 0xDAD1, 0x6414, 0xE1B8, + 0x6416, 0xE8F4, 0x6417, 0xD3FD, 0x641C, 0xE2A4, 0x6422, 0xF2CA, 0x642C, 0xDAE6, 0x642D, 0xF7B3, 0x643A, 0xFDCD, 0x643E, 0xF3B6, + 0x6458, 0xEED7, 0x6460, 0xF5C4, 0x6469, 0xD8A4, 0x646F, 0xF2A7, 0x6478, 0xD9B8, 0x6479, 0xD9B9, 0x647A, 0xEFC9, 0x6488, 0xD6CE, + 0x6491, 0xF7CB, 0x6492, 0xDFAE, 0x6493, 0xE8F5, 0x649A, 0xD2B5, 0x649E, 0xD3D5, 0x64A4, 0xF4CC, 0x64A5, 0xDAFC, 0x64AB, 0xD9E8, + 0x64AD, 0xF7EB, 0x64AE, 0xF5C9, 0x64B0, 0xF3BC, 0x64B2, 0xDAD2, 0x64BB, 0xD3B5, 0x64C1, 0xE8B6, 0x64C4, 0xD6CF, 0x64C5, 0xF4BA, + 0x64C7, 0xF7C9, 0x64CA, 0xCCAA, 0x64CD, 0xF0C3, 0x64CE, 0xCCD6, 0x64D2, 0xD0D3, 0x64D4, 0xD3BD, 0x64D8, 0xDBFB, 0x64DA, 0xCBE0, + 0x64E1, 0xD3E4, 0x64E2, 0xF6F7, 0x64E5, 0xD5BA, 0x64E6, 0xF3CD, 0x64E7, 0xCBE1, 0x64EC, 0xEBF4, 0x64F2, 0xF4AD, 0x64F4, 0xFCAA, + 0x64FA, 0xF7EC, 0x64FE, 0xE8F6, 0x6500, 0xDAE7, 0x6504, 0xF7CC, 0x6518, 0xE5C1, 0x651D, 0xE0EE, 0x6523, 0xD5FD, 0x652A, 0xCEE6, + 0x652B, 0xFCAB, 0x652C, 0xD5BB, 0x652F, 0xF2A8, 0x6536, 0xE2A5, 0x6537, 0xCDB9, 0x6538, 0xEAF2, 0x6539, 0xCBC7, 0x653B, 0xCDF4, + 0x653E, 0xDBAF, 0x653F, 0xEFD9, 0x6545, 0xCDBA, 0x6548, 0xFCF9, 0x654D, 0xDFF3, 0x654E, 0xCEE7, 0x654F, 0xDAC2, 0x6551, 0xCFAD, + 0x6556, 0xE7F9, 0x6557, 0xF8A8, 0x655E, 0xF3E2, 0x6562, 0xCAF2, 0x6563, 0xDFA4, 0x6566, 0xD4C4, 0x656C, 0xCCD7, 0x656D, 0xE5C2, + 0x6572, 0xCDBB, 0x6574, 0xEFDA, 0x6575, 0xEED8, 0x6577, 0xDDA7, 0x6578, 0xE2A6, 0x657E, 0xE0C0, 0x6582, 0xD6B0, 0x6583, 0xF8CA, + 0x6585, 0xFCFA, 0x6587, 0xD9FE, 0x658C, 0xDEB0, 0x6590, 0xDDEC, 0x6591, 0xDAE8, 0x6597, 0xD4E0, 0x6599, 0xD6F9, 0x659B, 0xCDD7, + 0x659C, 0xDED8, 0x659F, 0xF2F8, 0x65A1, 0xE4D6, 0x65A4, 0xD0C5, 0x65A5, 0xF4AE, 0x65A7, 0xDDA8, 0x65AB, 0xEDC5, 0x65AC, 0xF3D6, + 0x65AF, 0xDED9, 0x65B0, 0xE3E6, 0x65B7, 0xD3A8, 0x65B9, 0xDBB0, 0x65BC, 0xE5DA, 0x65BD, 0xE3BF, 0x65C1, 0xDBB1, 0x65C5, 0xD5E9, + 0x65CB, 0xE0C1, 0x65CC, 0xEFDB, 0x65CF, 0xF0E9, 0x65D2, 0xD7B2, 0x65D7, 0xD0FD, 0x65E0, 0xD9E9, 0x65E3, 0xD0FE, 0x65E5, 0xECED, + 0x65E6, 0xD3A9, 0x65E8, 0xF2A9, 0x65E9, 0xF0C4, 0x65EC, 0xE2E2, 0x65ED, 0xE9EF, 0x65F1, 0xF9D1, 0x65F4, 0xE9D9, 0x65FA, 0xE8DA, + 0x65FB, 0xDAC3, 0x65FC, 0xDAC4, 0x65FD, 0xD4C5, 0x65FF, 0xE7FA, 0x6606, 0xCDE0, 0x6607, 0xE3B0, 0x6609, 0xDBB2, 0x660A, 0xFBC4, + 0x660C, 0xF3E3, 0x660E, 0xD9A5, 0x660F, 0xFBE7, 0x6610, 0xDDCB, 0x6611, 0xD0D4, 0x6613, 0xE6B6, 0x6614, 0xE0AE, 0x6615, 0xFDDA, + 0x661E, 0xDCB5, 0x661F, 0xE0F8, 0x6620, 0xE7B1, 0x6625, 0xF5F0, 0x6627, 0xD8DC, 0x6628, 0xEDC6, 0x662D, 0xE1B9, 0x662F, 0xE3C0, + 0x6630, 0xF9C0, 0x6631, 0xE9F0, 0x6634, 0xD9DB, 0x6636, 0xF3E4, 0x663A, 0xDCB6, 0x663B, 0xE4E9, 0x6641, 0xF0C5, 0x6642, 0xE3C1, + 0x6643, 0xFCCC, 0x6644, 0xFCCD, 0x6649, 0xF2CB, 0x664B, 0xF2CC, 0x664F, 0xE4CF, 0x6659, 0xF1DB, 0x665B, 0xFAD9, 0x665D, 0xF1B8, + 0x665E, 0xFDF5, 0x665F, 0xE0F9, 0x6664, 0xE7FB, 0x6665, 0xFCB7, 0x6666, 0xFCE4, 0x6667, 0xFBC5, 0x6668, 0xE3E7, 0x6669, 0xD8B9, + 0x666B, 0xF6F8, 0x666E, 0xDCC5, 0x666F, 0xCCD8, 0x6673, 0xE0AF, 0x6674, 0xF4E7, 0x6676, 0xEFDC, 0x6677, 0xCFFC, 0x6678, 0xEFDD, + 0x667A, 0xF2AA, 0x6684, 0xFDBE, 0x6687, 0xCAAC, 0x6688, 0xFDBB, 0x6689, 0xFDC7, 0x668E, 0xE7B2, 0x6690, 0xEAD1, 0x6691, 0xDFF4, + 0x6696, 0xD1EC, 0x6697, 0xE4DE, 0x6698, 0xE5C3, 0x669D, 0xD9A6, 0x66A0, 0xCDBC, 0x66A2, 0xF3E5, 0x66AB, 0xEDD5, 0x66AE, 0xD9BA, + 0x66B2, 0xEDE7, 0x66B3, 0xFBB5, 0x66B4, 0xF8EC, 0x66B9, 0xE0E7, 0x66BB, 0xCCD9, 0x66BE, 0xD4C6, 0x66C4, 0xE7A5, 0x66C6, 0xD5F5, + 0x66C7, 0xD3BE, 0x66C9, 0xFCFB, 0x66D6, 0xE4F2, 0x66D9, 0xDFF5, 0x66DC, 0xE8F8, 0x66DD, 0xF8ED, 0x66E0, 0xCEC7, 0x66E6, 0xFDF6, + 0x66F0, 0xE8D8, 0x66F2, 0xCDD8, 0x66F3, 0xE7D6, 0x66F4, 0xCCDA, 0x66F7, 0xCAE3, 0x66F8, 0xDFF6, 0x66F9, 0xF0C7, 0x66FA, 0xF0C6, + 0x66FC, 0xD8BA, 0x66FE, 0xF1F4, 0x66FF, 0xF4F0, 0x6700, 0xF5CC, 0x6703, 0xFCE5, 0x6708, 0xEAC5, 0x6709, 0xEAF3, 0x670B, 0xDDDB, + 0x670D, 0xDCD7, 0x6714, 0xDEFD, 0x6715, 0xF2F9, 0x6717, 0xD5C7, 0x671B, 0xD8D0, 0x671D, 0xF0C8, 0x671E, 0xD1A1, 0x671F, 0xD1A2, + 0x6726, 0xD9D4, 0x6727, 0xD6E8, 0x6728, 0xD9CA, 0x672A, 0xDAB1, 0x672B, 0xD8C7, 0x672C, 0xDCE2, 0x672D, 0xF3CE, 0x672E, 0xF5F4, + 0x6731, 0xF1B9, 0x6734, 0xDAD3, 0x6736, 0xF6EA, 0x673A, 0xCFF5, 0x673D, 0xFDAE, 0x6746, 0xCAD2, 0x6749, 0xDFB4, 0x674E, 0xD7DD, + 0x674F, 0xFABA, 0x6750, 0xEEA7, 0x6751, 0xF5BD, 0x6753, 0xF8F5, 0x6756, 0xEDE8, 0x675C, 0xD4E1, 0x675E, 0xD1A3, 0x675F, 0xE1D6, + 0x676D, 0xF9F8, 0x676F, 0xDBCA, 0x6770, 0xCBF9, 0x6771, 0xD4D4, 0x6773, 0xD9DC, 0x6775, 0xEEBE, 0x6777, 0xF7ED, 0x677B, 0xD2EE, + 0x677E, 0xE1E6, 0x677F, 0xF7F9, 0x6787, 0xDDED, 0x6789, 0xE8DB, 0x678B, 0xDBB3, 0x678F, 0xD1F7, 0x6790, 0xE0B0, 0x6793, 0xD4E2, + 0x6795, 0xF6D7, 0x6797, 0xD7F9, 0x679A, 0xD8DD, 0x679C, 0xCDFD, 0x679D, 0xF2AB, 0x67AF, 0xCDBD, 0x67B0, 0xF8C2, 0x67B3, 0xF2AC, + 0x67B6, 0xCAAD, 0x67B7, 0xCAAE, 0x67B8, 0xCFAE, 0x67BE, 0xE3C2, 0x67C4, 0xDCB7, 0x67CF, 0xDBDA, 0x67D0, 0xD9BB, 0x67D1, 0xCAF3, + 0x67D2, 0xF6D3, 0x67D3, 0xE6F8, 0x67D4, 0xEAF5, 0x67DA, 0xEAF6, 0x67DD, 0xF6F9, 0x67E9, 0xCFAF, 0x67EC, 0xCAD3, 0x67EF, 0xCAAF, + 0x67F0, 0xD2B0, 0x67F1, 0xF1BA, 0x67F3, 0xD7B3, 0x67F4, 0xE3C3, 0x67F5, 0xF3FD, 0x67F6, 0xDEDA, 0x67FB, 0xDEDB, 0x67FE, 0xEFDE, + 0x6812, 0xE2E3, 0x6813, 0xEEFB, 0x6816, 0xDFF7, 0x6817, 0xD7CA, 0x6821, 0xCEE8, 0x6822, 0xDBDB, 0x682A, 0xF1BB, 0x682F, 0xE9F1, + 0x6838, 0xFAB7, 0x6839, 0xD0C6, 0x683C, 0xCCAB, 0x683D, 0xEEA8, 0x6840, 0xCBFA, 0x6841, 0xF9F9, 0x6842, 0xCCFD, 0x6843, 0xD3FE, + 0x6848, 0xE4D0, 0x684E, 0xF2EE, 0x6850, 0xD4D5, 0x6851, 0xDFCD, 0x6853, 0xFCB8, 0x6854, 0xD1D0, 0x686D, 0xF2CD, 0x6876, 0xF7D2, + 0x687F, 0xCAD4, 0x6881, 0xD5D9, 0x6885, 0xD8DE, 0x688F, 0xCDD9, 0x6893, 0xEEA9, 0x6894, 0xF6BC, 0x6897, 0xCCDB, 0x689D, 0xF0C9, + 0x689F, 0xFCFC, 0x68A1, 0xE8C9, 0x68A2, 0xF4FE, 0x68A7, 0xE7FC, 0x68A8, 0xD7DE, 0x68AD, 0xDEDC, 0x68AF, 0xF0AC, 0x68B0, 0xCCFE, + 0x68B1, 0xCDE1, 0x68B3, 0xE1BA, 0x68B5, 0xDBEF, 0x68B6, 0xDAB2, 0x68C4, 0xD1A5, 0x68C5, 0xDCB8, 0x68C9, 0xD8F6, 0x68CB, 0xD1A4, + 0x68CD, 0xCDE2, 0x68D2, 0xDCEA, 0x68D5, 0xF0F7, 0x68D7, 0xF0CA, 0x68D8, 0xD0BE, 0x68DA, 0xDDDC, 0x68DF, 0xD4D6, 0x68E0, 0xD3D6, + 0x68E7, 0xEDD0, 0x68E8, 0xCDA1, 0x68EE, 0xDFB5, 0x68F2, 0xDFF8, 0x68F9, 0xD4A1, 0x68FA, 0xCEB2, 0x6900, 0xE8CA, 0x6905, 0xEBF5, + 0x690D, 0xE3D5, 0x690E, 0xF5D0, 0x6912, 0xF5A1, 0x6927, 0xD9A7, 0x6930, 0xE5AB, 0x693D, 0xE6CB, 0x693F, 0xF5F1, 0x694A, 0xE5C5, + 0x6953, 0xF9A3, 0x6954, 0xE0DB, 0x6955, 0xF6EB, 0x6957, 0xCBF1, 0x6959, 0xD9EA, 0x695A, 0xF5A2, 0x695E, 0xD7D1, 0x6960, 0xD1F8, + 0x6961, 0xEAF8, 0x6962, 0xEAF9, 0x6963, 0xDAB3, 0x6968, 0xEFDF, 0x696B, 0xF1EF, 0x696D, 0xE5F6, 0x696E, 0xEEBF, 0x696F, 0xE2E4, + 0x6975, 0xD0BF, 0x6977, 0xFAAC, 0x6978, 0xF5D1, 0x6979, 0xE7B3, 0x6995, 0xE9BE, 0x699B, 0xF2CE, 0x699C, 0xDBB4, 0x69A5, 0xFCCE, + 0x69A7, 0xDDEE, 0x69AE, 0xE7B4, 0x69B4, 0xD7B4, 0x69BB, 0xF7B4, 0x69C1, 0xCDBE, 0x69C3, 0xDAE9, 0x69CB, 0xCFB0, 0x69CC, 0xF7D9, + 0x69CD, 0xF3E6, 0x69D0, 0xCED9, 0x69E8, 0xCEAA, 0x69EA, 0xCBC8, 0x69FB, 0xD0A7, 0x69FD, 0xF0CB, 0x69FF, 0xD0C7, 0x6A02, 0xE4C5, + 0x6A0A, 0xDBE0, 0x6A11, 0xD5DA, 0x6A13, 0xD7A7, 0x6A17, 0xEEC0, 0x6A19, 0xF8F6, 0x6A1E, 0xF5D2, 0x6A1F, 0xEDE9, 0x6A21, 0xD9BC, + 0x6A23, 0xE5C6, 0x6A35, 0xF5A3, 0x6A38, 0xDAD4, 0x6A39, 0xE2A7, 0x6A3A, 0xFBFC, 0x6A3D, 0xF1DC, 0x6A44, 0xCAF4, 0x6A48, 0xE8FA, + 0x6A4B, 0xCEE9, 0x6A52, 0xE9F8, 0x6A53, 0xE2E5, 0x6A58, 0xD0B9, 0x6A59, 0xD4F2, 0x6A5F, 0xD1A6, 0x6A61, 0xDFCE, 0x6A6B, 0xFCF4, + 0x6A80, 0xD3AA, 0x6A84, 0xCCAC, 0x6A89, 0xEFE0, 0x6A8D, 0xE5E5, 0x6A8E, 0xD0D5, 0x6A97, 0xDBFC, 0x6A9C, 0xFCE6, 0x6AA2, 0xCBFE, + 0x6AA3, 0xEDEA, 0x6AB3, 0xDEB1, 0x6ABB, 0xF9E3, 0x6AC2, 0xD4A2, 0x6AC3, 0xCFF6, 0x6AD3, 0xD6D0, 0x6ADA, 0xD5EA, 0x6ADB, 0xF1EE, + 0x6AF6, 0xFACB, 0x6AFB, 0xE5A1, 0x6B04, 0xD5B1, 0x6B0A, 0xCFED, 0x6B0C, 0xEDEB, 0x6B12, 0xD5B2, 0x6B16, 0xD5BC, 0x6B20, 0xFDE2, + 0x6B21, 0xF3AD, 0x6B23, 0xFDDB, 0x6B32, 0xE9B0, 0x6B3A, 0xD1A7, 0x6B3D, 0xFDE3, 0x6B3E, 0xCEB3, 0x6B46, 0xFDE4, 0x6B47, 0xFACE, + 0x6B4C, 0xCAB0, 0x6B4E, 0xF7A7, 0x6B50, 0xCFB1, 0x6B5F, 0xE6A2, 0x6B61, 0xFCB6, 0x6B62, 0xF2AD, 0x6B63, 0xEFE1, 0x6B64, 0xF3AE, + 0x6B65, 0xDCC6, 0x6B66, 0xD9EB, 0x6B6A, 0xE8E0, 0x6B72, 0xE1A8, 0x6B77, 0xD5F6, 0x6B78, 0xCFFD, 0x6B7B, 0xDEDD, 0x6B7F, 0xD9D1, + 0x6B83, 0xE4EA, 0x6B84, 0xF2CF, 0x6B86, 0xF7BF, 0x6B89, 0xE2E6, 0x6B8A, 0xE2A8, 0x6B96, 0xE3D6, 0x6B98, 0xEDD1, 0x6B9E, 0xE9F9, + 0x6BAE, 0xD6B1, 0x6BAF, 0xDEB2, 0x6BB2, 0xE0E8, 0x6BB5, 0xD3AB, 0x6BB7, 0xEBDC, 0x6BBA, 0xDFAF, 0x6BBC, 0xCAC3, 0x6BBF, 0xEEFC, + 0x6BC1, 0xFDC3, 0x6BC5, 0xEBF6, 0x6BC6, 0xCFB2, 0x6BCB, 0xD9EC, 0x6BCD, 0xD9BD, 0x6BCF, 0xD8DF, 0x6BD2, 0xD4B8, 0x6BD3, 0xEBBE, + 0x6BD4, 0xDDEF, 0x6BD6, 0xDDF0, 0x6BD7, 0xDDF1, 0x6BD8, 0xDDF2, 0x6BDB, 0xD9BE, 0x6BEB, 0xFBC6, 0x6BEC, 0xCFB3, 0x6C08, 0xEEFD, + 0x6C0F, 0xE4AB, 0x6C11, 0xDAC5, 0x6C13, 0xD8EC, 0x6C23, 0xD1A8, 0x6C34, 0xE2A9, 0x6C37, 0xDEBC, 0x6C38, 0xE7B5, 0x6C3E, 0xDBF0, + 0x6C40, 0xEFE2, 0x6C41, 0xF1F0, 0x6C42, 0xCFB4, 0x6C4E, 0xDBF1, 0x6C50, 0xE0B1, 0x6C55, 0xDFA5, 0x6C57, 0xF9D2, 0x6C5A, 0xE7FD, + 0x6C5D, 0xE6A3, 0x6C5E, 0xFBF1, 0x6C5F, 0xCBB0, 0x6C60, 0xF2AE, 0x6C68, 0xCDE7, 0x6C6A, 0xE8DC, 0x6C6D, 0xE7D7, 0x6C70, 0xF7C0, + 0x6C72, 0xD0E3, 0x6C76, 0xDAA1, 0x6C7A, 0xCCBD, 0x6C7D, 0xD1A9, 0x6C7E, 0xDDCC, 0x6C81, 0xE3FE, 0x6C82, 0xD1AA, 0x6C83, 0xE8AA, + 0x6C85, 0xEAB6, 0x6C86, 0xF9FA, 0x6C87, 0xE6CC, 0x6C88, 0xF6D8, 0x6C8C, 0xD4C7, 0x6C90, 0xD9CB, 0x6C92, 0xD9D2, 0x6C93, 0xD3CB, + 0x6C94, 0xD8F7, 0x6C95, 0xDAA9, 0x6C96, 0xF5F8, 0x6C99, 0xDEDE, 0x6C9A, 0xF2AF, 0x6C9B, 0xF8A9, 0x6CAB, 0xD8C8, 0x6CAE, 0xEEC1, + 0x6CB3, 0xF9C1, 0x6CB8, 0xDDF3, 0x6CB9, 0xEAFA, 0x6CBB, 0xF6BD, 0x6CBC, 0xE1BB, 0x6CBD, 0xCDBF, 0x6CBE, 0xF4D4, 0x6CBF, 0xE6CD, + 0x6CC1, 0xFCCF, 0x6CC2, 0xFBA2, 0x6CC4, 0xE0DC, 0x6CC9, 0xF4BB, 0x6CCA, 0xDAD5, 0x6CCC, 0xF9B2, 0x6CD3, 0xFBF2, 0x6CD5, 0xDBF6, + 0x6CD7, 0xDEDF, 0x6CDB, 0xDBF2, 0x6CE1, 0xF8DC, 0x6CE2, 0xF7EE, 0x6CE3, 0xEBE8, 0x6CE5, 0xD2FA, 0x6CE8, 0xF1BC, 0x6CEB, 0xFADA, + 0x6CEE, 0xDAEA, 0x6CEF, 0xDAC6, 0x6CF0, 0xF7C1, 0x6CF3, 0xE7B6, 0x6D0B, 0xE5C7, 0x6D0C, 0xD6AC, 0x6D11, 0xDCC7, 0x6D17, 0xE1A9, + 0x6D19, 0xE2AA, 0x6D1B, 0xD5A6, 0x6D1E, 0xD4D7, 0x6D25, 0xF2D0, 0x6D27, 0xEAFB, 0x6D29, 0xE0DD, 0x6D2A, 0xFBF3, 0x6D32, 0xF1BD, + 0x6D35, 0xE2E7, 0x6D36, 0xFDD7, 0x6D38, 0xCEC8, 0x6D39, 0xEAB7, 0x6D3B, 0xFCC0, 0x6D3D, 0xFDE7, 0x6D3E, 0xF7EF, 0x6D41, 0xD7B5, + 0x6D59, 0xEFBA, 0x6D5A, 0xF1DD, 0x6D5C, 0xDEB3, 0x6D63, 0xE8CB, 0x6D66, 0xF8DD, 0x6D69, 0xFBC7, 0x6D6A, 0xD5C8, 0x6D6C, 0xD7DF, + 0x6D6E, 0xDDA9, 0x6D74, 0xE9B1, 0x6D77, 0xFAAD, 0x6D78, 0xF6D9, 0x6D79, 0xFAF4, 0x6D7F, 0xF8AA, 0x6D85, 0xE6EE, 0x6D87, 0xCCDC, + 0x6D88, 0xE1BC, 0x6D89, 0xE0EF, 0x6D8C, 0xE9BF, 0x6D8D, 0xFCFD, 0x6D8E, 0xE6CE, 0x6D91, 0xE1D7, 0x6D93, 0xE6CF, 0x6D95, 0xF4F1, + 0x6DAF, 0xE4F3, 0x6DB2, 0xE4FB, 0x6DB5, 0xF9E4, 0x6DC0, 0xEFE3, 0x6DC3, 0xCFEE, 0x6DC4, 0xF6BE, 0x6DC5, 0xE0B2, 0x6DC6, 0xFCFE, + 0x6DC7, 0xD1AB, 0x6DCB, 0xD7FA, 0x6DCF, 0xFBC8, 0x6DD1, 0xE2D7, 0x6DD8, 0xD4A3, 0x6DD9, 0xF0F8, 0x6DDA, 0xD7A8, 0x6DDE, 0xE1E7, + 0x6DE1, 0xD3BF, 0x6DE8, 0xEFE4, 0x6DEA, 0xD7C5, 0x6DEB, 0xEBE2, 0x6DEE, 0xFCE7, 0x6DF1, 0xE4A2, 0x6DF3, 0xE2E8, 0x6DF5, 0xE6D0, + 0x6DF7, 0xFBE8, 0x6DF8, 0xF4E8, 0x6DF9, 0xE5F4, 0x6DFA, 0xF4BC, 0x6DFB, 0xF4D5, 0x6E17, 0xDFB6, 0x6E19, 0xFCB9, 0x6E1A, 0xEEC2, + 0x6E1B, 0xCAF5, 0x6E1F, 0xEFE5, 0x6E20, 0xCBE2, 0x6E21, 0xD4A4, 0x6E23, 0xDEE0, 0x6E24, 0xDAFD, 0x6E25, 0xE4C6, 0x6E26, 0xE8BE, + 0x6E2B, 0xE0DE, 0x6E2C, 0xF6B4, 0x6E2D, 0xEAD2, 0x6E2F, 0xF9FB, 0x6E32, 0xE0C2, 0x6E34, 0xCAE4, 0x6E36, 0xE7B7, 0x6E38, 0xEAFD, + 0x6E3A, 0xD9DD, 0x6E3C, 0xDAB4, 0x6E3D, 0xEEAA, 0x6E3E, 0xFBE9, 0x6E43, 0xDBCB, 0x6E44, 0xDAB5, 0x6E4A, 0xF1BE, 0x6E4D, 0xD3AC, + 0x6E56, 0xFBC9, 0x6E58, 0xDFCF, 0x6E5B, 0xD3C0, 0x6E5C, 0xE3D7, 0x6E5E, 0xEFE6, 0x6E5F, 0xFCD0, 0x6E67, 0xE9C0, 0x6E6B, 0xF5D3, + 0x6E6E, 0xECDC, 0x6E6F, 0xF7B7, 0x6E72, 0xEAB8, 0x6E73, 0xD1F9, 0x6E7A, 0xDCC8, 0x6E90, 0xEAB9, 0x6E96, 0xF1DE, 0x6E9C, 0xD7B6, + 0x6E9D, 0xCFB5, 0x6E9F, 0xD9A8, 0x6EA2, 0xECEE, 0x6EA5, 0xDDAA, 0x6EAA, 0xCDA2, 0x6EAB, 0xE8AE, 0x6EAF, 0xE1BD, 0x6EB1, 0xF2D1, + 0x6EB6, 0xE9C1, 0x6EBA, 0xD2FC, 0x6EC2, 0xDBB5, 0x6EC4, 0xF3E7, 0x6EC5, 0xD8FE, 0x6EC9, 0xFCD1, 0x6ECB, 0xEDB2, 0x6ECC, 0xF4AF, + 0x6ECE, 0xFBA3, 0x6ED1, 0xFCC1, 0x6ED3, 0xEEAB, 0x6ED4, 0xD4A5, 0x6EEF, 0xF4F2, 0x6EF4, 0xEED9, 0x6EF8, 0xFBCA, 0x6EFE, 0xCDE3, + 0x6EFF, 0xD8BB, 0x6F01, 0xE5DB, 0x6F02, 0xF8F7, 0x6F06, 0xF6D4, 0x6F0F, 0xD7A9, 0x6F11, 0xCBC9, 0x6F14, 0xE6D1, 0x6F15, 0xF0CC, + 0x6F20, 0xD8AE, 0x6F22, 0xF9D3, 0x6F23, 0xD5FE, 0x6F2B, 0xD8BC, 0x6F2C, 0xF2B0, 0x6F31, 0xE2AB, 0x6F32, 0xF3E8, 0x6F38, 0xEFC2, + 0x6F3F, 0xEDEC, 0x6F41, 0xE7B8, 0x6F51, 0xDAFE, 0x6F54, 0xCCBE, 0x6F57, 0xF2FC, 0x6F58, 0xDAEB, 0x6F5A, 0xE2D8, 0x6F5B, 0xEDD6, + 0x6F5E, 0xD6D1, 0x6F5F, 0xE0B3, 0x6F62, 0xFCD2, 0x6F64, 0xEBC8, 0x6F6D, 0xD3C1, 0x6F6E, 0xF0CD, 0x6F70, 0xCFF7, 0x6F7A, 0xEDD2, + 0x6F7C, 0xD4D8, 0x6F7D, 0xDCC9, 0x6F7E, 0xD7F1, 0x6F81, 0xDFBB, 0x6F84, 0xF3A5, 0x6F88, 0xF4CD, 0x6F8D, 0xF1BF, 0x6F8E, 0xF8B1, + 0x6F90, 0xE9FA, 0x6F94, 0xFBCB, 0x6F97, 0xCAD5, 0x6FA3, 0xF9D4, 0x6FA4, 0xF7CA, 0x6FA7, 0xD6C8, 0x6FAE, 0xFCE8, 0x6FAF, 0xF3BD, + 0x6FB1, 0xEEFE, 0x6FB3, 0xE7FE, 0x6FB9, 0xD3C2, 0x6FBE, 0xD3B6, 0x6FC0, 0xCCAD, 0x6FC1, 0xF6FA, 0x6FC2, 0xD6B2, 0x6FC3, 0xD2D8, + 0x6FCA, 0xE7D8, 0x6FD5, 0xE3A5, 0x6FDA, 0xE7B9, 0x6FDF, 0xF0AD, 0x6FE0, 0xFBCC, 0x6FE1, 0xEBA1, 0x6FE4, 0xD4A6, 0x6FE9, 0xFBCD, + 0x6FEB, 0xD5BD, 0x6FEC, 0xF1DF, 0x6FEF, 0xF6FB, 0x6FF1, 0xDEB4, 0x6FFE, 0xD5EB, 0x7001, 0xE5C8, 0x7005, 0xFBA4, 0x7006, 0xD4B9, + 0x7009, 0xDEE1, 0x700B, 0xE4A3, 0x700F, 0xD7B7, 0x7011, 0xF8EE, 0x7015, 0xDEB5, 0x7018, 0xD6D2, 0x701A, 0xF9D5, 0x701B, 0xE7BA, + 0x701C, 0xEBD5, 0x701D, 0xD5F7, 0x701E, 0xEFE7, 0x701F, 0xE1BE, 0x7023, 0xFAAE, 0x7027, 0xD6E9, 0x7028, 0xD6EE, 0x702F, 0xE7BB, + 0x7037, 0xECCB, 0x703E, 0xD5B3, 0x704C, 0xCEB4, 0x7050, 0xFBA5, 0x7051, 0xE1EE, 0x7058, 0xF7A8, 0x705D, 0xFBCE, 0x7063, 0xD8BD, + 0x706B, 0xFBFD, 0x7070, 0xFCE9, 0x7078, 0xCFB6, 0x707C, 0xEDC7, 0x707D, 0xEEAC, 0x7085, 0xCCDD, 0x708A, 0xF6A7, 0x708E, 0xE6FA, + 0x7092, 0xF5A4, 0x7098, 0xFDDC, 0x7099, 0xEDB3, 0x709A, 0xCEC9, 0x70A1, 0xEFE8, 0x70A4, 0xE1BF, 0x70AB, 0xFADB, 0x70AC, 0xCBE3, + 0x70AD, 0xF7A9, 0x70AF, 0xFBA6, 0x70B3, 0xDCB9, 0x70B7, 0xF1C0, 0x70B8, 0xEDC8, 0x70B9, 0xEFC3, 0x70C8, 0xD6AD, 0x70CB, 0xFDCE, + 0x70CF, 0xE8A1, 0x70D8, 0xFBF4, 0x70D9, 0xD5A7, 0x70DD, 0xF1F6, 0x70DF, 0xE6D3, 0x70F1, 0xCCDE, 0x70F9, 0xF8B2, 0x70FD, 0xDCEB, + 0x7104, 0xFDB6, 0x7109, 0xE5EA, 0x710C, 0xF1E0, 0x7119, 0xDBCC, 0x711A, 0xDDCD, 0x711E, 0xD4C8, 0x7121, 0xD9ED, 0x7126, 0xF5A5, + 0x7130, 0xE6FB, 0x7136, 0xE6D4, 0x7147, 0xFDC8, 0x7149, 0xD6A1, 0x714A, 0xFDBF, 0x714C, 0xFCD3, 0x714E, 0xEFA1, 0x7150, 0xE7BC, + 0x7156, 0xD1EE, 0x7159, 0xE6D5, 0x715C, 0xE9F2, 0x715E, 0xDFB0, 0x7164, 0xD8E0, 0x7165, 0xFCBA, 0x7166, 0xFDAF, 0x7167, 0xF0CE, + 0x7169, 0xDBE1, 0x716C, 0xE5C9, 0x716E, 0xEDB4, 0x717D, 0xE0C3, 0x7184, 0xE3D8, 0x7189, 0xE9FB, 0x718A, 0xEAA8, 0x718F, 0xFDB7, + 0x7192, 0xFBA7, 0x7194, 0xE9C2, 0x7199, 0xFDF7, 0x719F, 0xE2D9, 0x71A2, 0xDCEC, 0x71AC, 0xE8A2, 0x71B1, 0xE6F0, 0x71B9, 0xFDF8, + 0x71BA, 0xFDF9, 0x71BE, 0xF6BF, 0x71C1, 0xE7A7, 0x71C3, 0xE6D7, 0x71C8, 0xD4F3, 0x71C9, 0xD4C9, 0x71CE, 0xD6FA, 0x71D0, 0xD7F2, + 0x71D2, 0xE1C0, 0x71D4, 0xDBE2, 0x71D5, 0xE6D8, 0x71DF, 0xE7BD, 0x71E5, 0xF0CF, 0x71E6, 0xF3BE, 0x71E7, 0xE2AC, 0x71ED, 0xF5B7, + 0x71EE, 0xE0F0, 0x71FB, 0xFDB8, 0x71FC, 0xE3E8, 0x71FE, 0xD4A7, 0x71FF, 0xE8FC, 0x7200, 0xFAD2, 0x7206, 0xF8EF, 0x7210, 0xD6D3, + 0x721B, 0xD5B4, 0x722A, 0xF0D0, 0x722C, 0xF7F0, 0x722D, 0xEEB3, 0x7230, 0xEABA, 0x7232, 0xEAD3, 0x7235, 0xEDC9, 0x7236, 0xDDAB, + 0x723A, 0xE5AC, 0x723B, 0xFDA1, 0x723D, 0xDFD0, 0x723E, 0xECB3, 0x7240, 0xDFD1, 0x7246, 0xEDED, 0x7247, 0xF8B8, 0x7248, 0xF7FA, + 0x724C, 0xF8AB, 0x7252, 0xF4E0, 0x7258, 0xD4BA, 0x7259, 0xE4B3, 0x725B, 0xE9DA, 0x725D, 0xDEB6, 0x725F, 0xD9BF, 0x7261, 0xD9C0, + 0x7262, 0xD6EF, 0x7267, 0xD9CC, 0x7269, 0xDAAA, 0x7272, 0xDFE5, 0x7279, 0xF7E5, 0x727D, 0xCCB2, 0x7280, 0xDFF9, 0x7281, 0xD7E0, + 0x72A2, 0xD4BB, 0x72A7, 0xFDFA, 0x72AC, 0xCCB3, 0x72AF, 0xDBF3, 0x72C0, 0xDFD2, 0x72C2, 0xCECA, 0x72C4, 0xEEDA, 0x72CE, 0xE4E4, + 0x72D0, 0xFBCF, 0x72D7, 0xCFB7, 0x72D9, 0xEEC3, 0x72E1, 0xCEEA, 0x72E9, 0xE2AD, 0x72F8, 0xD7E1, 0x72F9, 0xFAF5, 0x72FC, 0xD5C9, + 0x72FD, 0xF8AC, 0x730A, 0xE7D9, 0x7316, 0xF3E9, 0x731B, 0xD8ED, 0x731C, 0xE3C4, 0x731D, 0xF0F1, 0x7325, 0xE8E5, 0x7329, 0xE0FA, + 0x732A, 0xEEC4, 0x732B, 0xD9DE, 0x7336, 0xEBA2, 0x7337, 0xEBA3, 0x733E, 0xFCC2, 0x733F, 0xEABB, 0x7344, 0xE8AB, 0x7345, 0xDEE2, + 0x7350, 0xEDEF, 0x7352, 0xE8A3, 0x7357, 0xCFF1, 0x7368, 0xD4BC, 0x736A, 0xFCEA, 0x7370, 0xE7BE, 0x7372, 0xFCF2, 0x7375, 0xD6B4, + 0x7378, 0xE2AE, 0x737A, 0xD3B7, 0x737B, 0xFACC, 0x7384, 0xFADC, 0x7386, 0xEDB5, 0x7387, 0xE1E3, 0x7389, 0xE8AC, 0x738B, 0xE8DD, + 0x738E, 0xEFE9, 0x7394, 0xF4BD, 0x7396, 0xCFB8, 0x7397, 0xE9DB, 0x7398, 0xD1AC, 0x739F, 0xDAC7, 0x73A7, 0xEBC9, 0x73A9, 0xE8CC, + 0x73AD, 0xDEB7, 0x73B2, 0xD6BC, 0x73B3, 0xD3E5, 0x73B9, 0xFADD, 0x73C0, 0xDAD6, 0x73C2, 0xCAB1, 0x73C9, 0xDAC8, 0x73CA, 0xDFA6, + 0x73CC, 0xF9B3, 0x73CD, 0xF2D2, 0x73CF, 0xCAC4, 0x73D6, 0xCECB, 0x73D9, 0xCDF5, 0x73DD, 0xFDB0, 0x73DE, 0xD5A8, 0x73E0, 0xF1C1, + 0x73E3, 0xE2E9, 0x73E4, 0xDCCA, 0x73E5, 0xECB4, 0x73E6, 0xFAC0, 0x73E9, 0xFBA8, 0x73EA, 0xD0A8, 0x73ED, 0xDAEC, 0x73F7, 0xD9EE, + 0x73F9, 0xE0FB, 0x73FD, 0xEFEA, 0x73FE, 0xFADE, 0x7401, 0xE0C4, 0x7403, 0xCFB9, 0x7405, 0xD5CA, 0x7406, 0xD7E2, 0x7407, 0xE2AF, + 0x7409, 0xD7B8, 0x7413, 0xE8CD, 0x741B, 0xF6DA, 0x7420, 0xEFA2, 0x7421, 0xE2DA, 0x7422, 0xF6FC, 0x7425, 0xFBD0, 0x7426, 0xD1AD, + 0x7428, 0xCDE4, 0x742A, 0xD1AE, 0x742B, 0xDCED, 0x742C, 0xE8CE, 0x742E, 0xF0F9, 0x742F, 0xCEB5, 0x7430, 0xE6FC, 0x7433, 0xD7FB, + 0x7434, 0xD0D6, 0x7435, 0xDDF5, 0x7436, 0xF7F1, 0x7438, 0xF6FD, 0x743A, 0xDBF7, 0x743F, 0xFBEA, 0x7440, 0xE9DC, 0x7441, 0xD9C1, + 0x7443, 0xF5F2, 0x7444, 0xE0C5, 0x744B, 0xEAD4, 0x7455, 0xF9C2, 0x7457, 0xEABC, 0x7459, 0xD2C5, 0x745A, 0xFBD1, 0x745B, 0xE7C0, + 0x745C, 0xEBA5, 0x745E, 0xDFFA, 0x745F, 0xE3A2, 0x7460, 0xD7B9, 0x7462, 0xE9C3, 0x7464, 0xE8FD, 0x7465, 0xE8AF, 0x7468, 0xF2D3, + 0x7469, 0xFBA9, 0x746A, 0xD8A5, 0x746F, 0xD5CB, 0x747E, 0xD0C8, 0x7482, 0xD1AF, 0x7483, 0xD7E3, 0x7487, 0xE0C6, 0x7489, 0xD6A2, + 0x748B, 0xEDF0, 0x7498, 0xD7F3, 0x749C, 0xFCD4, 0x749E, 0xDAD7, 0x749F, 0xCCDF, 0x74A1, 0xF2D4, 0x74A3, 0xD1B0, 0x74A5, 0xCCE0, + 0x74A7, 0xDBFD, 0x74A8, 0xF3BF, 0x74AA, 0xF0D1, 0x74B0, 0xFCBB, 0x74B2, 0xE2B0, 0x74B5, 0xE6A5, 0x74B9, 0xE2DB, 0x74BD, 0xDFDE, + 0x74BF, 0xE0C7, 0x74C6, 0xF2EF, 0x74CA, 0xCCE1, 0x74CF, 0xD6EA, 0x74D4, 0xE7C2, 0x74D8, 0xCEB6, 0x74DA, 0xF3C0, 0x74DC, 0xCDFE, + 0x74E0, 0xFBD2, 0x74E2, 0xF8F8, 0x74E3, 0xF7FB, 0x74E6, 0xE8BF, 0x74EE, 0xE8B7, 0x74F7, 0xEDB6, 0x7501, 0xDCBA, 0x7504, 0xCCB4, + 0x7511, 0xF1F7, 0x7515, 0xE8B8, 0x7518, 0xCAF6, 0x751A, 0xE4A4, 0x751B, 0xF4D6, 0x751F, 0xDFE6, 0x7523, 0xDFA7, 0x7525, 0xDFE7, + 0x7526, 0xE1C1, 0x7528, 0xE9C4, 0x752B, 0xDCCB, 0x752C, 0xE9C5, 0x7530, 0xEFA3, 0x7531, 0xEBA6, 0x7532, 0xCBA3, 0x7533, 0xE3E9, + 0x7537, 0xD1FB, 0x7538, 0xEFA4, 0x753A, 0xEFEB, 0x7547, 0xD0B4, 0x754C, 0xCDA3, 0x754F, 0xE8E6, 0x7551, 0xEFA5, 0x7553, 0xD3CC, + 0x7554, 0xDAED, 0x7559, 0xD7BA, 0x755B, 0xF2D5, 0x755C, 0xF5E5, 0x755D, 0xD9EF, 0x7562, 0xF9B4, 0x7565, 0xD5D4, 0x7566, 0xFDCF, + 0x756A, 0xDBE3, 0x756F, 0xF1E1, 0x7570, 0xECB6, 0x7575, 0xFBFE, 0x7576, 0xD3D7, 0x7578, 0xD1B1, 0x757A, 0xCBB1, 0x757F, 0xD1B2, + 0x7586, 0xCBB2, 0x7587, 0xF1C2, 0x758A, 0xF4E1, 0x758B, 0xF9B5, 0x758E, 0xE1C3, 0x758F, 0xE1C2, 0x7591, 0xEBF7, 0x759D, 0xDFA8, + 0x75A5, 0xCBCA, 0x75AB, 0xE6B9, 0x75B1, 0xF8DE, 0x75B2, 0xF9AA, 0x75B3, 0xCAF7, 0x75B5, 0xEDB7, 0x75B8, 0xD3B8, 0x75B9, 0xF2D6, + 0x75BC, 0xD4D9, 0x75BD, 0xEEC5, 0x75BE, 0xF2F0, 0x75C2, 0xCAB2, 0x75C5, 0xDCBB, 0x75C7, 0xF1F8, 0x75CD, 0xECB7, 0x75D2, 0xE5CA, + 0x75D4, 0xF6C0, 0x75D5, 0xFDDD, 0x75D8, 0xD4E3, 0x75D9, 0xCCE2, 0x75DB, 0xF7D4, 0x75E2, 0xD7E5, 0x75F0, 0xD3C3, 0x75F2, 0xD8A6, + 0x75F4, 0xF6C1, 0x75FA, 0xDDF6, 0x75FC, 0xCDC0, 0x7600, 0xE5DC, 0x760D, 0xE5CB, 0x7619, 0xE1C4, 0x761F, 0xE8B0, 0x7620, 0xF4B0, + 0x7621, 0xF3EA, 0x7622, 0xDAEE, 0x7624, 0xD7BB, 0x7626, 0xE2B1, 0x763B, 0xD7AA, 0x7642, 0xD6FB, 0x764C, 0xE4DF, 0x764E, 0xCAD6, + 0x7652, 0xEBA8, 0x7656, 0xDBFE, 0x7661, 0xF6C2, 0x7664, 0xEFBB, 0x7669, 0xD4FD, 0x766C, 0xE0C8, 0x7670, 0xE8B9, 0x7672, 0xEFA6, + 0x7678, 0xCDA4, 0x767B, 0xD4F4, 0x767C, 0xDBA1, 0x767D, 0xDBDC, 0x767E, 0xDBDD, 0x7684, 0xEEDC, 0x7686, 0xCBCB, 0x7687, 0xFCD5, + 0x768E, 0xCEEB, 0x7690, 0xCDC1, 0x7693, 0xFBD3, 0x76AE, 0xF9AB, 0x76BA, 0xF5D4, 0x76BF, 0xD9A9, 0x76C2, 0xE9DD, 0x76C3, 0xDBCD, + 0x76C6, 0xDDCE, 0x76C8, 0xE7C3, 0x76CA, 0xECCC, 0x76D2, 0xF9EC, 0x76D6, 0xCBCC, 0x76DB, 0xE0FC, 0x76DC, 0xD4A8, 0x76DE, 0xEDD3, + 0x76DF, 0xD8EF, 0x76E1, 0xF2D7, 0x76E3, 0xCAF8, 0x76E4, 0xDAEF, 0x76E7, 0xD6D4, 0x76EE, 0xD9CD, 0x76F2, 0xD8EE, 0x76F4, 0xF2C1, + 0x76F8, 0xDFD3, 0x76FC, 0xDAF0, 0x76FE, 0xE2EA, 0x7701, 0xE0FD, 0x7704, 0xD8F8, 0x7708, 0xF7AF, 0x7709, 0xDAB6, 0x770B, 0xCAD7, + 0x771E, 0xF2D8, 0x7720, 0xD8F9, 0x7729, 0xFADF, 0x7737, 0xCFEF, 0x7738, 0xD9C2, 0x773A, 0xF0D2, 0x773C, 0xE4D1, 0x7740, 0xF3B7, + 0x774D, 0xFAE0, 0x775B, 0xEFEC, 0x7761, 0xE2B2, 0x7763, 0xD4BD, 0x7766, 0xD9CE, 0x776B, 0xF4E2, 0x7779, 0xD4A9, 0x777E, 0xCDC2, + 0x777F, 0xE7DA, 0x778B, 0xF2D9, 0x7791, 0xD9AA, 0x779E, 0xD8BE, 0x77A5, 0xDCAD, 0x77AC, 0xE2EB, 0x77AD, 0xD6FC, 0x77B0, 0xCAF9, + 0x77B3, 0xD4DA, 0x77BB, 0xF4D7, 0x77BC, 0xCCA1, 0x77BF, 0xCFBA, 0x77D7, 0xF5B8, 0x77DB, 0xD9C3, 0x77DC, 0xD0E8, 0x77E2, 0xE3C5, + 0x77E3, 0xEBF8, 0x77E5, 0xF2B1, 0x77E9, 0xCFBB, 0x77ED, 0xD3AD, 0x77EE, 0xE8E1, 0x77EF, 0xCEEC, 0x77F3, 0xE0B4, 0x7802, 0xDEE3, + 0x7812, 0xDDF7, 0x7825, 0xF2B2, 0x7826, 0xF3F6, 0x7827, 0xF6DB, 0x782C, 0xD7FE, 0x7832, 0xF8DF, 0x7834, 0xF7F2, 0x7845, 0xD0A9, + 0x784F, 0xE6DA, 0x785D, 0xF5A6, 0x786B, 0xD7BC, 0x786C, 0xCCE3, 0x786F, 0xE6DB, 0x787C, 0xDDDD, 0x7881, 0xD1B3, 0x7887, 0xEFED, + 0x788C, 0xD6DE, 0x788D, 0xE4F4, 0x788E, 0xE1EF, 0x7891, 0xDDF8, 0x7897, 0xE8CF, 0x78A3, 0xCAE5, 0x78A7, 0xDCA1, 0x78A9, 0xE0B5, + 0x78BA, 0xFCAC, 0x78BB, 0xFCAD, 0x78BC, 0xD8A7, 0x78C1, 0xEDB8, 0x78C5, 0xDBB6, 0x78CA, 0xD6F0, 0x78CB, 0xF3AF, 0x78CE, 0xCDA5, + 0x78D0, 0xDAF1, 0x78E8, 0xD8A8, 0x78EC, 0xCCE4, 0x78EF, 0xD1B4, 0x78F5, 0xCAD8, 0x78FB, 0xDAF2, 0x7901, 0xF5A7, 0x790E, 0xF5A8, + 0x7916, 0xE6A6, 0x792A, 0xD5EC, 0x792B, 0xD5F8, 0x792C, 0xDAF3, 0x793A, 0xE3C6, 0x793E, 0xDEE4, 0x7940, 0xDEE5, 0x7941, 0xD1B5, + 0x7947, 0xD1B6, 0x7948, 0xD1B7, 0x7949, 0xF2B3, 0x7950, 0xE9DE, 0x7956, 0xF0D3, 0x7957, 0xF2B4, 0x795A, 0xF0D4, 0x795B, 0xCBE4, + 0x795C, 0xFBD4, 0x795D, 0xF5E6, 0x795E, 0xE3EA, 0x7960, 0xDEE6, 0x7965, 0xDFD4, 0x7968, 0xF8F9, 0x796D, 0xF0AE, 0x797A, 0xD1B8, + 0x797F, 0xD6DF, 0x7981, 0xD0D7, 0x798D, 0xFCA1, 0x798E, 0xEFEE, 0x798F, 0xDCD8, 0x7991, 0xE9DF, 0x79A6, 0xE5DD, 0x79A7, 0xFDFB, + 0x79AA, 0xE0C9, 0x79AE, 0xD6C9, 0x79B1, 0xD4AA, 0x79B3, 0xE5CC, 0x79B9, 0xE9E0, 0x79BD, 0xD0D8, 0x79BE, 0xFCA2, 0x79BF, 0xD4BE, + 0x79C0, 0xE2B3, 0x79C1, 0xDEE7, 0x79C9, 0xDCBC, 0x79CA, 0xD2B6, 0x79CB, 0xF5D5, 0x79D1, 0xCEA1, 0x79D2, 0xF5A9, 0x79D5, 0xDDF9, + 0x79D8, 0xDDFA, 0x79DF, 0xF0D5, 0x79E4, 0xF6DF, 0x79E6, 0xF2DA, 0x79E7, 0xE4EB, 0x79E9, 0xF2F1, 0x79FB, 0xECB9, 0x7A00, 0xFDFC, + 0x7A05, 0xE1AA, 0x7A08, 0xCAD9, 0x7A0B, 0xEFEF, 0x7A0D, 0xF5AA, 0x7A14, 0xECF9, 0x7A17, 0xF8AD, 0x7A19, 0xF2C2, 0x7A1A, 0xF6C3, + 0x7A1C, 0xD7D2, 0x7A1F, 0xF9A2, 0x7A20, 0xF0D6, 0x7A2E, 0xF0FA, 0x7A31, 0xF6E0, 0x7A36, 0xE9F3, 0x7A37, 0xF2C3, 0x7A3B, 0xD4AB, + 0x7A3C, 0xCAB3, 0x7A3D, 0xCDA6, 0x7A3F, 0xCDC3, 0x7A40, 0xCDDA, 0x7A46, 0xD9CF, 0x7A49, 0xF6C4, 0x7A4D, 0xEEDD, 0x7A4E, 0xE7C4, + 0x7A57, 0xE2B4, 0x7A61, 0xDFE2, 0x7A62, 0xE7DB, 0x7A69, 0xE8B1, 0x7A6B, 0xFCAE, 0x7A70, 0xE5CD, 0x7A74, 0xFAEB, 0x7A76, 0xCFBC, + 0x7A79, 0xCFE2, 0x7A7A, 0xCDF6, 0x7A7D, 0xEFF0, 0x7A7F, 0xF4BE, 0x7A81, 0xD4CD, 0x7A84, 0xF3B8, 0x7A88, 0xE9A1, 0x7A92, 0xF2F2, + 0x7A93, 0xF3EB, 0x7A95, 0xF0D7, 0x7A98, 0xCFD7, 0x7A9F, 0xCFDF, 0x7AA9, 0xE8C0, 0x7AAA, 0xE8C1, 0x7AAE, 0xCFE3, 0x7AAF, 0xE9A2, + 0x7ABA, 0xD0AA, 0x7AC4, 0xF3C1, 0x7AC5, 0xD0AB, 0x7AC7, 0xD4E4, 0x7ACA, 0xEFBC, 0x7ACB, 0xD8A1, 0x7AD7, 0xD9DF, 0x7AD9, 0xF3D7, + 0x7ADD, 0xDCBD, 0x7ADF, 0xCCE5, 0x7AE0, 0xEDF1, 0x7AE3, 0xF1E2, 0x7AE5, 0xD4DB, 0x7AEA, 0xE2B5, 0x7AED, 0xCAE6, 0x7AEF, 0xD3AE, + 0x7AF6, 0xCCE6, 0x7AF9, 0xF1D3, 0x7AFA, 0xF5E7, 0x7AFF, 0xCADA, 0x7B0F, 0xFBEE, 0x7B11, 0xE1C5, 0x7B19, 0xDFE9, 0x7B1B, 0xEEDE, + 0x7B1E, 0xF7C2, 0x7B20, 0xD8A2, 0x7B26, 0xDDAC, 0x7B2C, 0xF0AF, 0x7B2D, 0xD6BD, 0x7B39, 0xE1AB, 0x7B46, 0xF9B6, 0x7B49, 0xD4F5, + 0x7B4B, 0xD0C9, 0x7B4C, 0xEFA7, 0x7B4D, 0xE2EC, 0x7B4F, 0xDBEA, 0x7B50, 0xCECC, 0x7B51, 0xF5E8, 0x7B52, 0xF7D5, 0x7B54, 0xD3CD, + 0x7B56, 0xF3FE, 0x7B60, 0xD0B5, 0x7B6C, 0xE0FE, 0x7B6E, 0xDFFB, 0x7B75, 0xE6DD, 0x7B7D, 0xE8A4, 0x7B87, 0xCBCD, 0x7B8B, 0xEFA8, + 0x7B8F, 0xEEB4, 0x7B94, 0xDAD8, 0x7B95, 0xD1B9, 0x7B97, 0xDFA9, 0x7B9A, 0xF3B0, 0x7B9D, 0xCCC4, 0x7BA1, 0xCEB7, 0x7BAD, 0xEFA9, + 0x7BB1, 0xDFD5, 0x7BB4, 0xEDD7, 0x7BB8, 0xEEC6, 0x7BC0, 0xEFBD, 0x7BC1, 0xFCD6, 0x7BC4, 0xDBF4, 0x7BC6, 0xEFAA, 0x7BC7, 0xF8B9, + 0x7BC9, 0xF5E9, 0x7BD2, 0xE3D9, 0x7BE0, 0xE1C6, 0x7BE4, 0xD4BF, 0x7BE9, 0xDEE8, 0x7C07, 0xF0EA, 0x7C12, 0xF3C2, 0x7C1E, 0xD3AF, + 0x7C21, 0xCADB, 0x7C27, 0xFCD7, 0x7C2A, 0xEDD8, 0x7C2B, 0xE1C7, 0x7C3D, 0xF4D8, 0x7C3E, 0xD6B3, 0x7C3F, 0xDDAD, 0x7C43, 0xD5BE, + 0x7C4C, 0xF1C3, 0x7C4D, 0xEEDF, 0x7C60, 0xD6EB, 0x7C64, 0xF4D9, 0x7C6C, 0xD7E6, 0x7C73, 0xDAB7, 0x7C83, 0xDDFB, 0x7C89, 0xDDCF, + 0x7C92, 0xD8A3, 0x7C95, 0xDAD9, 0x7C97, 0xF0D8, 0x7C98, 0xEFC4, 0x7C9F, 0xE1D8, 0x7CA5, 0xF1D4, 0x7CA7, 0xEDF2, 0x7CAE, 0xD5DB, + 0x7CB1, 0xD5DC, 0x7CB2, 0xF3C4, 0x7CB3, 0xCBD7, 0x7CB9, 0xE2B6, 0x7CBE, 0xEFF1, 0x7CCA, 0xFBD5, 0x7CD6, 0xD3D8, 0x7CDE, 0xDDD0, + 0x7CDF, 0xF0D9, 0x7CE0, 0xCBB3, 0x7CE7, 0xD5DD, 0x7CFB, 0xCDA7, 0x7CFE, 0xD0AC, 0x7D00, 0xD1BA, 0x7D02, 0xF1C4, 0x7D04, 0xE5B3, + 0x7D05, 0xFBF5, 0x7D06, 0xE9E1, 0x7D07, 0xFDE0, 0x7D08, 0xFCBC, 0x7D0A, 0xDAA2, 0x7D0B, 0xDAA3, 0x7D0D, 0xD2A1, 0x7D10, 0xD2EF, + 0x7D14, 0xE2ED, 0x7D17, 0xDEE9, 0x7D18, 0xCEDC, 0x7D19, 0xF2B5, 0x7D1A, 0xD0E4, 0x7D1B, 0xDDD1, 0x7D20, 0xE1C8, 0x7D21, 0xDBB7, + 0x7D22, 0xDFE3, 0x7D2B, 0xEDB9, 0x7D2C, 0xF1C5, 0x7D2E, 0xF3CF, 0x7D2F, 0xD7AB, 0x7D30, 0xE1AC, 0x7D33, 0xE3EB, 0x7D35, 0xEEC7, + 0x7D39, 0xE1C9, 0x7D3A, 0xCAFA, 0x7D42, 0xF0FB, 0x7D43, 0xFAE1, 0x7D44, 0xF0DA, 0x7D45, 0xCCE7, 0x7D46, 0xDAF4, 0x7D50, 0xCCBF, + 0x7D5E, 0xCEED, 0x7D61, 0xD5A9, 0x7D62, 0xFAE2, 0x7D66, 0xD0E5, 0x7D68, 0xEBD6, 0x7D6A, 0xECDF, 0x7D6E, 0xDFFC, 0x7D71, 0xF7D6, + 0x7D72, 0xDEEA, 0x7D73, 0xCBB4, 0x7D76, 0xEFBE, 0x7D79, 0xCCB5, 0x7D7F, 0xCFBD, 0x7D8E, 0xEFF2, 0x7D8F, 0xE2B7, 0x7D93, 0xCCE8, + 0x7D9C, 0xF0FC, 0x7DA0, 0xD6E0, 0x7DA2, 0xF1C6, 0x7DAC, 0xE2B8, 0x7DAD, 0xEBAB, 0x7DB1, 0xCBB5, 0x7DB2, 0xD8D1, 0x7DB4, 0xF4CE, + 0x7DB5, 0xF3F7, 0x7DB8, 0xD7C6, 0x7DBA, 0xD1BB, 0x7DBB, 0xF7AA, 0x7DBD, 0xEDCA, 0x7DBE, 0xD7D3, 0x7DBF, 0xD8FA, 0x7DC7, 0xF6C5, + 0x7DCA, 0xD1CC, 0x7DCB, 0xDDFC, 0x7DD6, 0xDFFD, 0x7DD8, 0xF9E5, 0x7DDA, 0xE0CA, 0x7DDD, 0xF2FD, 0x7DDE, 0xD3B0, 0x7DE0, 0xF4F3, + 0x7DE1, 0xDAC9, 0x7DE3, 0xE6DE, 0x7DE8, 0xF8BA, 0x7DE9, 0xE8D0, 0x7DEC, 0xD8FB, 0x7DEF, 0xEAD5, 0x7DF4, 0xD6A3, 0x7DFB, 0xF6C6, + 0x7E09, 0xF2DB, 0x7E0A, 0xE4FC, 0x7E15, 0xE8B2, 0x7E1B, 0xDADA, 0x7E1D, 0xF2DC, 0x7E1E, 0xFBD6, 0x7E1F, 0xE9B2, 0x7E21, 0xEEAD, + 0x7E23, 0xFAE3, 0x7E2B, 0xDCEE, 0x7E2E, 0xF5EA, 0x7E2F, 0xE6E0, 0x7E31, 0xF0FD, 0x7E37, 0xD7AC, 0x7E3D, 0xF5C5, 0x7E3E, 0xEEE0, + 0x7E41, 0xDBE5, 0x7E43, 0xDDDE, 0x7E46, 0xD9F0, 0x7E47, 0xE9A3, 0x7E52, 0xF1F9, 0x7E54, 0xF2C4, 0x7E55, 0xE0CB, 0x7E5E, 0xE9A4, + 0x7E61, 0xE2B9, 0x7E69, 0xE3B1, 0x7E6A, 0xFCEB, 0x7E6B, 0xCDA8, 0x7E6D, 0xCCB6, 0x7E70, 0xF0DB, 0x7E79, 0xE6BA, 0x7E7C, 0xCDA9, + 0x7E82, 0xF3C3, 0x7E8C, 0xE1D9, 0x7E8F, 0xEFAB, 0x7E93, 0xE7C5, 0x7E96, 0xE0E9, 0x7E98, 0xF3C5, 0x7E9B, 0xD4C0, 0x7E9C, 0xD5BF, + 0x7F36, 0xDDAE, 0x7F38, 0xF9FC, 0x7F3A, 0xCCC0, 0x7F4C, 0xE5A2, 0x7F50, 0xCEB8, 0x7F54, 0xD8D2, 0x7F55, 0xF9D6, 0x7F6A, 0xF1AA, + 0x7F6B, 0xCED1, 0x7F6E, 0xF6C7, 0x7F70, 0xDBEB, 0x7F72, 0xDFFE, 0x7F75, 0xD8E1, 0x7F77, 0xF7F3, 0x7F79, 0xD7E7, 0x7F85, 0xD4FE, + 0x7F88, 0xD1BC, 0x7F8A, 0xE5CF, 0x7F8C, 0xCBB6, 0x7F8E, 0xDAB8, 0x7F94, 0xCDC4, 0x7F9A, 0xD6BE, 0x7F9E, 0xE2BA, 0x7FA4, 0xCFD8, + 0x7FA8, 0xE0CC, 0x7FA9, 0xEBF9, 0x7FB2, 0xFDFD, 0x7FB8, 0xD7E8, 0x7FB9, 0xCBD8, 0x7FBD, 0xE9E2, 0x7FC1, 0xE8BA, 0x7FC5, 0xE3C7, + 0x7FCA, 0xECCD, 0x7FCC, 0xECCE, 0x7FCE, 0xD6BF, 0x7FD2, 0xE3A7, 0x7FD4, 0xDFD6, 0x7FD5, 0xFDE8, 0x7FDF, 0xEEE1, 0x7FE0, 0xF6A8, + 0x7FE1, 0xDDFD, 0x7FE9, 0xF8BB, 0x7FEB, 0xE8D1, 0x7FF0, 0xF9D7, 0x7FF9, 0xCEEE, 0x7FFC, 0xECCF, 0x8000, 0xE9A5, 0x8001, 0xD6D5, + 0x8003, 0xCDC5, 0x8005, 0xEDBA, 0x8006, 0xD1BD, 0x8009, 0xCFBE, 0x800C, 0xECBB, 0x8010, 0xD2B1, 0x8015, 0xCCE9, 0x8017, 0xD9C4, + 0x8018, 0xE9FC, 0x802D, 0xD1BE, 0x8033, 0xECBC, 0x8036, 0xE5AD, 0x803D, 0xF7B0, 0x803F, 0xCCEA, 0x8043, 0xD3C4, 0x8046, 0xD6C0, + 0x804A, 0xD6FD, 0x8056, 0xE1A1, 0x8058, 0xDEBD, 0x805A, 0xF6A9, 0x805E, 0xDAA4, 0x806F, 0xD6A4, 0x8070, 0xF5C6, 0x8072, 0xE1A2, + 0x8073, 0xE9C6, 0x8077, 0xF2C5, 0x807D, 0xF4E9, 0x807E, 0xD6EC, 0x807F, 0xEBD3, 0x8084, 0xECBD, 0x8085, 0xE2DC, 0x8086, 0xDEEB, + 0x8087, 0xF0DC, 0x8089, 0xEBBF, 0x808B, 0xD7CE, 0x808C, 0xD1BF, 0x8096, 0xF5AB, 0x809B, 0xF9FD, 0x809D, 0xCADC, 0x80A1, 0xCDC6, + 0x80A2, 0xF2B6, 0x80A5, 0xDDFE, 0x80A9, 0xCCB7, 0x80AA, 0xDBB8, 0x80AF, 0xD0E9, 0x80B1, 0xCEDD, 0x80B2, 0xEBC0, 0x80B4, 0xFDA2, + 0x80BA, 0xF8CB, 0x80C3, 0xEAD6, 0x80C4, 0xF1B0, 0x80CC, 0xDBCE, 0x80CE, 0xF7C3, 0x80DA, 0xDBCF, 0x80DB, 0xCBA4, 0x80DE, 0xF8E0, + 0x80E1, 0xFBD7, 0x80E4, 0xEBCA, 0x80E5, 0xE0A1, 0x80F1, 0xCECD, 0x80F4, 0xD4DC, 0x80F8, 0xFDD8, 0x80FD, 0xD2F6, 0x8102, 0xF2B7, + 0x8105, 0xFAF6, 0x8106, 0xF6AA, 0x8107, 0xFAF7, 0x8108, 0xD8E6, 0x810A, 0xF4B1, 0x8118, 0xE8D2, 0x811A, 0xCAC5, 0x811B, 0xCCEB, + 0x8123, 0xE2EE, 0x8129, 0xE2BB, 0x812B, 0xF7AD, 0x812F, 0xF8E1, 0x8139, 0xF3EC, 0x813E, 0xDEA1, 0x814B, 0xE4FD, 0x814E, 0xE3EC, + 0x8150, 0xDDAF, 0x8151, 0xDDB0, 0x8154, 0xCBB7, 0x8155, 0xE8D3, 0x8165, 0xE1A3, 0x8166, 0xD2E0, 0x816B, 0xF0FE, 0x8170, 0xE9A6, + 0x8171, 0xCBF2, 0x8178, 0xEDF3, 0x8179, 0xDCD9, 0x817A, 0xE0CD, 0x817F, 0xF7DA, 0x8180, 0xDBB9, 0x8188, 0xCCAE, 0x818A, 0xDADB, + 0x818F, 0xCDC7, 0x819A, 0xDDB1, 0x819C, 0xD8AF, 0x819D, 0xE3A3, 0x81A0, 0xCEEF, 0x81A3, 0xF2F3, 0x81A8, 0xF8B3, 0x81B3, 0xE0CE, + 0x81B5, 0xF5FD, 0x81BA, 0xEBEC, 0x81BD, 0xD3C5, 0x81BE, 0xFCEC, 0x81BF, 0xD2DB, 0x81C0, 0xD4EB, 0x81C2, 0xDEA2, 0x81C6, 0xE5E6, + 0x81CD, 0xF0B0, 0x81D8, 0xD5C4, 0x81DF, 0xEDF4, 0x81E3, 0xE3ED, 0x81E5, 0xE8C2, 0x81E7, 0xEDF5, 0x81E8, 0xD7FC, 0x81EA, 0xEDBB, + 0x81ED, 0xF6AB, 0x81F3, 0xF2B8, 0x81F4, 0xF6C8, 0x81FA, 0xD3E6, 0x81FB, 0xF2DD, 0x81FC, 0xCFBF, 0x81FE, 0xEBAC, 0x8205, 0xCFC0, + 0x8207, 0xE6A8, 0x8208, 0xFDE9, 0x820A, 0xCFC1, 0x820C, 0xE0DF, 0x820D, 0xDEEC, 0x8212, 0xE0A2, 0x821B, 0xF4BF, 0x821C, 0xE2EF, + 0x821E, 0xD9F1, 0x821F, 0xF1C7, 0x8221, 0xCBB8, 0x822A, 0xF9FE, 0x822B, 0xDBBA, 0x822C, 0xDAF5, 0x8235, 0xF6EC, 0x8236, 0xDADC, + 0x8237, 0xFAE4, 0x8239, 0xE0CF, 0x8240, 0xDDB2, 0x8245, 0xE6A9, 0x8247, 0xEFF3, 0x8259, 0xF3ED, 0x8264, 0xEBFA, 0x8266, 0xF9E6, + 0x826E, 0xCADD, 0x826F, 0xD5DE, 0x8271, 0xCADE, 0x8272, 0xDFE4, 0x8276, 0xE6FD, 0x8278, 0xF5AC, 0x827E, 0xE4F5, 0x828B, 0xE9E3, + 0x828D, 0xEDCB, 0x828E, 0xCFE4, 0x8292, 0xD8D3, 0x8299, 0xDDB3, 0x829A, 0xD4EC, 0x829D, 0xF2B9, 0x829F, 0xDFB7, 0x82A5, 0xCBCE, + 0x82A6, 0xFBD8, 0x82A9, 0xD0D9, 0x82AC, 0xDDD2, 0x82AD, 0xF7F4, 0x82AE, 0xE7DC, 0x82AF, 0xE4A5, 0x82B1, 0xFCA3, 0x82B3, 0xDBBB, + 0x82B7, 0xF2BA, 0x82B8, 0xE9FD, 0x82B9, 0xD0CA, 0x82BB, 0xF5D6, 0x82BC, 0xD9C5, 0x82BD, 0xE4B4, 0x82BF, 0xEDA7, 0x82D1, 0xEABD, + 0x82D2, 0xE6FE, 0x82D4, 0xF7C4, 0x82D5, 0xF5AD, 0x82D7, 0xD9E0, 0x82DB, 0xCAB4, 0x82DE, 0xF8E2, 0x82DF, 0xCFC2, 0x82E1, 0xECBE, + 0x82E5, 0xE5B4, 0x82E6, 0xCDC8, 0x82E7, 0xEEC8, 0x82F1, 0xE7C8, 0x82FD, 0xCDC9, 0x82FE, 0xF9B7, 0x8301, 0xF1E8, 0x8302, 0xD9F2, + 0x8303, 0xDBF5, 0x8304, 0xCAB5, 0x8305, 0xD9C6, 0x8309, 0xD8C9, 0x8317, 0xD9AB, 0x8328, 0xEDBC, 0x832B, 0xD8D4, 0x832F, 0xDCDA, + 0x8331, 0xE2BC, 0x8334, 0xFCED, 0x8335, 0xECE0, 0x8336, 0xD2FE, 0x8338, 0xE9C7, 0x8339, 0xE6AA, 0x8340, 0xE2F0, 0x8347, 0xFABB, + 0x8349, 0xF5AE, 0x834A, 0xFBAA, 0x834F, 0xECFB, 0x8351, 0xECBF, 0x8352, 0xFCD8, 0x8373, 0xD4E5, 0x8377, 0xF9C3, 0x837B, 0xEEE2, + 0x8389, 0xD7E9, 0x838A, 0xEDF6, 0x838E, 0xDEED, 0x8396, 0xCCEC, 0x8398, 0xE3EE, 0x839E, 0xE8D4, 0x83A2, 0xFAF8, 0x83A9, 0xDDB4, + 0x83AA, 0xE4B5, 0x83AB, 0xD8B0, 0x83BD, 0xD8D5, 0x83C1, 0xF4EA, 0x83C5, 0xCEB9, 0x83C9, 0xD6E1, 0x83CA, 0xCFD2, 0x83CC, 0xD0B6, + 0x83D3, 0xCEA2, 0x83D6, 0xF3EE, 0x83DC, 0xF3F8, 0x83E9, 0xDCCC, 0x83EB, 0xD0CB, 0x83EF, 0xFCA4, 0x83F0, 0xCDCA, 0x83F1, 0xD7D4, + 0x83F2, 0xDEA3, 0x83F4, 0xE4E0, 0x83F9, 0xEEC9, 0x83FD, 0xE2DD, 0x8403, 0xF5FE, 0x8404, 0xD4AC, 0x840A, 0xD5D1, 0x840C, 0xD8F0, + 0x840D, 0xF8C3, 0x840E, 0xEAD7, 0x8429, 0xF5D7, 0x842C, 0xD8BF, 0x8431, 0xFDC0, 0x8438, 0xEBAD, 0x843D, 0xD5AA, 0x8449, 0xE7A8, + 0x8457, 0xEECA, 0x845B, 0xCAE7, 0x8461, 0xF8E3, 0x8463, 0xD4DD, 0x8466, 0xEAD8, 0x846B, 0xFBD9, 0x846C, 0xEDF7, 0x846F, 0xE5B5, + 0x8475, 0xD0AD, 0x847A, 0xF1F1, 0x8490, 0xE2BD, 0x8494, 0xE3C8, 0x8499, 0xD9D5, 0x849C, 0xDFAA, 0x84A1, 0xDBBC, 0x84B2, 0xF8E4, + 0x84B8, 0xF1FA, 0x84BB, 0xE5B6, 0x84BC, 0xF3EF, 0x84BF, 0xFBDA, 0x84C0, 0xE1E0, 0x84C2, 0xD9AC, 0x84C4, 0xF5EB, 0x84C6, 0xE0B6, + 0x84C9, 0xE9C8, 0x84CB, 0xCBCF, 0x84CD, 0xE3C9, 0x84D1, 0xDEEE, 0x84DA, 0xE2BE, 0x84EC, 0xDCEF, 0x84EE, 0xD6A5, 0x84F4, 0xE2F1, + 0x84FC, 0xD6FE, 0x8511, 0xD9A1, 0x8513, 0xD8C0, 0x8514, 0xDCDB, 0x8517, 0xEDBD, 0x8518, 0xDFB8, 0x851A, 0xEAA5, 0x851E, 0xD7AD, + 0x8521, 0xF3F9, 0x8523, 0xEDF8, 0x8525, 0xF5C7, 0x852C, 0xE1CA, 0x852D, 0xEBE3, 0x852F, 0xF2DE, 0x853D, 0xF8CC, 0x853F, 0xEAD9, + 0x8541, 0xD3C6, 0x8543, 0xDBE6, 0x8549, 0xF5AF, 0x854E, 0xCEF0, 0x8553, 0xE9FE, 0x8559, 0xFBB6, 0x8563, 0xE2F2, 0x8568, 0xCFF2, + 0x8569, 0xF7B9, 0x856A, 0xD9F3, 0x856D, 0xE1CB, 0x8584, 0xDADD, 0x8587, 0xDAB9, 0x858F, 0xEBFB, 0x8591, 0xCBB9, 0x8594, 0xEDF9, + 0x859B, 0xE0E0, 0x85A6, 0xF4C0, 0x85A8, 0xFDBC, 0x85A9, 0xDFB1, 0x85AA, 0xE3EF, 0x85AF, 0xE0A3, 0x85B0, 0xFDB9, 0x85BA, 0xF0B1, + 0x85C1, 0xCDCB, 0x85C9, 0xEDBE, 0x85CD, 0xD5C0, 0x85CE, 0xE3F0, 0x85CF, 0xEDFA, 0x85D5, 0xE9E4, 0x85DC, 0xD5ED, 0x85DD, 0xE7DD, + 0x85E4, 0xD4F6, 0x85E5, 0xE5B7, 0x85E9, 0xDBE7, 0x85EA, 0xE2BF, 0x85F7, 0xEECB, 0x85FA, 0xD7F4, 0x85FB, 0xF0DD, 0x85FF, 0xCEAB, + 0x8602, 0xE7DE, 0x8606, 0xD6D6, 0x8607, 0xE1CC, 0x860A, 0xE8B3, 0x8616, 0xE5EE, 0x8617, 0xDCA2, 0x861A, 0xE0D0, 0x862D, 0xD5B5, + 0x863F, 0xD5A1, 0x864E, 0xFBDB, 0x8650, 0xF9CB, 0x8654, 0xCBF3, 0x8655, 0xF4A5, 0x865B, 0xFAC8, 0x865C, 0xD6D7, 0x865E, 0xE9E5, + 0x865F, 0xFBDC, 0x8667, 0xFDD0, 0x8679, 0xFBF6, 0x868A, 0xDAA5, 0x868C, 0xDBBD, 0x8693, 0xECE2, 0x86A3, 0xCDF7, 0x86A4, 0xF0DE, + 0x86A9, 0xF6C9, 0x86C7, 0xDEEF, 0x86CB, 0xD3B1, 0x86D4, 0xFCEE, 0x86D9, 0xE8C3, 0x86DB, 0xF1C8, 0x86DF, 0xCEF1, 0x86E4, 0xF9ED, + 0x86ED, 0xF2F4, 0x86FE, 0xE4B6, 0x8700, 0xF5B9, 0x8702, 0xDCF0, 0x8703, 0xE3F1, 0x8708, 0xE8A5, 0x8718, 0xF2BB, 0x871A, 0xDEA4, + 0x871C, 0xDACC, 0x874E, 0xCAE9, 0x8755, 0xE3DA, 0x8757, 0xFCD9, 0x875F, 0xEADA, 0x8766, 0xF9C4, 0x8768, 0xE3A4, 0x8774, 0xFBDD, + 0x8776, 0xEFCA, 0x8778, 0xE8C4, 0x8782, 0xD5CC, 0x878D, 0xEBD7, 0x879F, 0xD9AD, 0x87A2, 0xFBAB, 0x87B3, 0xD3D9, 0x87BA, 0xD5A2, + 0x87C4, 0xF6DE, 0x87E0, 0xDAF6, 0x87EC, 0xE0D1, 0x87EF, 0xE9A8, 0x87F2, 0xF5F9, 0x87F9, 0xFAAF, 0x87FB, 0xEBFC, 0x87FE, 0xE0EA, + 0x8805, 0xE3B2, 0x881F, 0xD5C5, 0x8822, 0xF1E3, 0x8823, 0xD5EE, 0x8831, 0xCDCC, 0x8836, 0xEDD9, 0x883B, 0xD8C1, 0x8840, 0xFAEC, + 0x8846, 0xF1EB, 0x884C, 0xFABC, 0x884D, 0xE6E2, 0x8852, 0xFAE5, 0x8853, 0xE2FA, 0x8857, 0xCAB6, 0x8859, 0xE4B7, 0x885B, 0xEADB, + 0x885D, 0xF5FA, 0x8861, 0xFBAC, 0x8862, 0xCFC3, 0x8863, 0xEBFD, 0x8868, 0xF8FA, 0x886B, 0xDFB9, 0x8870, 0xE1F1, 0x8872, 0xD2A4, + 0x8877, 0xF5FB, 0x887E, 0xD0DA, 0x887F, 0xD0DB, 0x8881, 0xEABE, 0x8882, 0xD9B1, 0x8888, 0xCAB7, 0x888B, 0xD3E7, 0x888D, 0xF8E5, + 0x8892, 0xD3B2, 0x8896, 0xE2C0, 0x8897, 0xF2DF, 0x889E, 0xCDE5, 0x88AB, 0xF9AC, 0x88B4, 0xCDCD, 0x88C1, 0xEEAE, 0x88C2, 0xD6AE, + 0x88CF, 0xD7EA, 0x88D4, 0xE7E0, 0x88D5, 0xEBAE, 0x88D9, 0xCFD9, 0x88DC, 0xDCCD, 0x88DD, 0xEDFB, 0x88DF, 0xDEF0, 0x88E1, 0xD7EB, + 0x88E8, 0xDEA5, 0x88F3, 0xDFD7, 0x88F4, 0xDBD0, 0x88F5, 0xDBD1, 0x88F8, 0xD5A3, 0x88FD, 0xF0B2, 0x8907, 0xDCDC, 0x8910, 0xCAE8, + 0x8912, 0xF8E6, 0x8913, 0xDCCE, 0x8918, 0xEADC, 0x8919, 0xDBD2, 0x8925, 0xE9B3, 0x892A, 0xF7DB, 0x8936, 0xE3A8, 0x8938, 0xD7AE, + 0x893B, 0xE0E1, 0x8941, 0xCBBA, 0x8944, 0xE5D1, 0x895F, 0xD0DC, 0x8964, 0xD5C1, 0x896A, 0xD8CA, 0x8972, 0xE3A9, 0x897F, 0xE0A4, + 0x8981, 0xE9A9, 0x8983, 0xD3C7, 0x8986, 0xDCDD, 0x8987, 0xF8AE, 0x898B, 0xCCB8, 0x898F, 0xD0AE, 0x8993, 0xD8F2, 0x8996, 0xE3CA, + 0x89A1, 0xCCAF, 0x89A9, 0xD4AD, 0x89AA, 0xF6D1, 0x89B2, 0xD0CC, 0x89BA, 0xCAC6, 0x89BD, 0xD5C2, 0x89C0, 0xCEBA, 0x89D2, 0xCAC7, + 0x89E3, 0xFAB0, 0x89F4, 0xDFD8, 0x89F8, 0xF5BA, 0x8A00, 0xE5EB, 0x8A02, 0xEFF4, 0x8A03, 0xDDB5, 0x8A08, 0xCDAA, 0x8A0A, 0xE3F2, + 0x8A0C, 0xFBF7, 0x8A0E, 0xF7D0, 0x8A13, 0xFDBA, 0x8A16, 0xFDE1, 0x8A17, 0xF6FE, 0x8A18, 0xD1C0, 0x8A1B, 0xE8C5, 0x8A1D, 0xE4B8, + 0x8A1F, 0xE1E8, 0x8A23, 0xCCC1, 0x8A25, 0xD2ED, 0x8A2A, 0xDBBE, 0x8A2D, 0xE0E2, 0x8A31, 0xFAC9, 0x8A34, 0xE1CD, 0x8A36, 0xCAB8, + 0x8A3A, 0xF2E0, 0x8A3B, 0xF1C9, 0x8A50, 0xDEF1, 0x8A54, 0xF0DF, 0x8A55, 0xF8C4, 0x8A5B, 0xEECC, 0x8A5E, 0xDEF2, 0x8A60, 0xE7C9, + 0x8A62, 0xE2F3, 0x8A63, 0xE7E1, 0x8A66, 0xE3CB, 0x8A69, 0xE3CC, 0x8A6D, 0xCFF8, 0x8A6E, 0xEFAC, 0x8A70, 0xFDFE, 0x8A71, 0xFCA5, + 0x8A72, 0xFAB1, 0x8A73, 0xDFD9, 0x8A75, 0xE0D2, 0x8A79, 0xF4DA, 0x8A85, 0xF1CA, 0x8A87, 0xCEA3, 0x8A8C, 0xF2BC, 0x8A8D, 0xECE3, + 0x8A93, 0xE0A5, 0x8A95, 0xF7AB, 0x8A98, 0xEBAF, 0x8A9E, 0xE5DE, 0x8AA0, 0xE1A4, 0x8AA1, 0xCDAB, 0x8AA3, 0xD9F4, 0x8AA4, 0xE8A6, + 0x8AA5, 0xCDCE, 0x8AA6, 0xE1E9, 0x8AA8, 0xFCEF, 0x8AAA, 0xE0E3, 0x8AB0, 0xE2C1, 0x8AB2, 0xCEA4, 0x8AB9, 0xDEA6, 0x8ABC, 0xEBFE, + 0x8ABE, 0xEBDD, 0x8ABF, 0xF0E0, 0x8AC2, 0xF4DB, 0x8AC4, 0xE2F4, 0x8AC7, 0xD3C8, 0x8ACB, 0xF4EB, 0x8ACD, 0xEEB5, 0x8ACF, 0xF5D8, + 0x8AD2, 0xD5DF, 0x8AD6, 0xD6E5, 0x8ADB, 0xEBB0, 0x8ADC, 0xF4E3, 0x8AE1, 0xE3CD, 0x8AE6, 0xF4F4, 0x8AE7, 0xFAB2, 0x8AEA, 0xEFF5, + 0x8AEB, 0xCADF, 0x8AED, 0xEBB1, 0x8AEE, 0xEDBF, 0x8AF1, 0xFDC9, 0x8AF6, 0xE4A6, 0x8AF7, 0xF9A4, 0x8AF8, 0xF0B3, 0x8AFA, 0xE5EC, + 0x8AFE, 0xD1E7, 0x8B00, 0xD9C7, 0x8B01, 0xE4D7, 0x8B02, 0xEADD, 0x8B04, 0xD4F7, 0x8B0E, 0xDABA, 0x8B10, 0xDACD, 0x8B14, 0xF9CC, + 0x8B16, 0xE1DA, 0x8B17, 0xDBBF, 0x8B19, 0xCCC5, 0x8B1A, 0xECD0, 0x8B1B, 0xCBBB, 0x8B1D, 0xDEF3, 0x8B20, 0xE9AA, 0x8B28, 0xD9C8, + 0x8B2B, 0xEEE3, 0x8B2C, 0xD7BD, 0x8B33, 0xCFC4, 0x8B39, 0xD0CD, 0x8B41, 0xFCA6, 0x8B49, 0xF1FB, 0x8B4E, 0xFDD2, 0x8B4F, 0xD1C1, + 0x8B58, 0xE3DB, 0x8B5A, 0xD3C9, 0x8B5C, 0xDCCF, 0x8B66, 0xCCED, 0x8B6C, 0xDEA7, 0x8B6F, 0xE6BB, 0x8B70, 0xECA1, 0x8B74, 0xCCB9, + 0x8B77, 0xFBDE, 0x8B7D, 0xE7E2, 0x8B80, 0xD4C1, 0x8B8A, 0xDCA8, 0x8B90, 0xE2C2, 0x8B92, 0xF3D8, 0x8B93, 0xE5D3, 0x8B96, 0xF3D9, + 0x8B9A, 0xF3C6, 0x8C37, 0xCDDB, 0x8C3F, 0xCDAC, 0x8C41, 0xFCC3, 0x8C46, 0xD4E7, 0x8C48, 0xD1C2, 0x8C4A, 0xF9A5, 0x8C4C, 0xE8D5, + 0x8C55, 0xE3CE, 0x8C5A, 0xD4CA, 0x8C61, 0xDFDA, 0x8C6A, 0xFBDF, 0x8C6B, 0xE7E3, 0x8C79, 0xF8FB, 0x8C7A, 0xE3CF, 0x8C82, 0xF5B0, + 0x8C8A, 0xD8E7, 0x8C8C, 0xD9C9, 0x8C9D, 0xF8AF, 0x8C9E, 0xEFF6, 0x8CA0, 0xDDB6, 0x8CA1, 0xEEAF, 0x8CA2, 0xCDF8, 0x8CA7, 0xDEB8, + 0x8CA8, 0xFCA7, 0x8CA9, 0xF7FC, 0x8CAA, 0xF7B1, 0x8CAB, 0xCEBB, 0x8CAC, 0xF4A1, 0x8CAF, 0xEECD, 0x8CB0, 0xE1AE, 0x8CB3, 0xECC3, + 0x8CB4, 0xCFFE, 0x8CB6, 0xF8BF, 0x8CB7, 0xD8E2, 0x8CB8, 0xD3E8, 0x8CBB, 0xDEA8, 0x8CBC, 0xF4E4, 0x8CBD, 0xECC2, 0x8CBF, 0xD9F5, + 0x8CC0, 0xF9C5, 0x8CC1, 0xDDD3, 0x8CC2, 0xD6F1, 0x8CC3, 0xECFC, 0x8CC4, 0xFCF0, 0x8CC7, 0xEDC0, 0x8CC8, 0xCAB9, 0x8CCA, 0xEEE4, + 0x8CD1, 0xF2E1, 0x8CD3, 0xDEB9, 0x8CDA, 0xD6F2, 0x8CDC, 0xDEF4, 0x8CDE, 0xDFDB, 0x8CE0, 0xDBD3, 0x8CE2, 0xFAE7, 0x8CE3, 0xD8E3, + 0x8CE4, 0xF4C1, 0x8CE6, 0xDDB7, 0x8CEA, 0xF2F5, 0x8CED, 0xD4AE, 0x8CF4, 0xD6F3, 0x8CFB, 0xDDB8, 0x8CFC, 0xCFC5, 0x8CFD, 0xDFDF, + 0x8D04, 0xF2BE, 0x8D05, 0xF6A1, 0x8D07, 0xEBCB, 0x8D08, 0xF1FC, 0x8D0A, 0xF3C7, 0x8D0D, 0xE0EB, 0x8D13, 0xEDFC, 0x8D16, 0xE1DB, + 0x8D64, 0xEEE5, 0x8D66, 0xDEF5, 0x8D6B, 0xFAD3, 0x8D70, 0xF1CB, 0x8D73, 0xD0AF, 0x8D74, 0xDDB9, 0x8D77, 0xD1C3, 0x8D85, 0xF5B1, + 0x8D8A, 0xEAC6, 0x8D99, 0xF0E1, 0x8DA3, 0xF6AC, 0x8DA8, 0xF5D9, 0x8DB3, 0xF0EB, 0x8DBA, 0xDDBA, 0x8DBE, 0xF2BF, 0x8DC6, 0xF7C5, + 0x8DCB, 0xDBA2, 0x8DCC, 0xF2F6, 0x8DCF, 0xCABA, 0x8DDB, 0xF7F5, 0x8DDD, 0xCBE5, 0x8DE1, 0xEEE6, 0x8DE3, 0xE0D3, 0x8DE8, 0xCEA5, + 0x8DEF, 0xD6D8, 0x8DF3, 0xD4AF, 0x8E0A, 0xE9C9, 0x8E0F, 0xD3CE, 0x8E10, 0xF4C2, 0x8E1E, 0xCBE6, 0x8E2A, 0xF1A1, 0x8E30, 0xEBB2, + 0x8E35, 0xF1A2, 0x8E42, 0xEBB3, 0x8E44, 0xF0B4, 0x8E47, 0xCBF4, 0x8E48, 0xD4B0, 0x8E49, 0xF3B2, 0x8E4A, 0xFBB7, 0x8E59, 0xF5EC, + 0x8E5F, 0xEEE7, 0x8E60, 0xF4B2, 0x8E74, 0xF5ED, 0x8E76, 0xCFF3, 0x8E81, 0xF0E2, 0x8E87, 0xEECE, 0x8E8A, 0xF1CC, 0x8E8D, 0xE5B8, + 0x8EAA, 0xD7F5, 0x8EAB, 0xE3F3, 0x8EAC, 0xCFE5, 0x8EC0, 0xCFC6, 0x8ECA, 0xF3B3, 0x8ECB, 0xE4D8, 0x8ECC, 0xCFF9, 0x8ECD, 0xCFDA, + 0x8ED2, 0xFACD, 0x8EDF, 0xE6E3, 0x8EEB, 0xF2E2, 0x8EF8, 0xF5EE, 0x8EFB, 0xCABB, 0x8EFE, 0xE3DC, 0x8F03, 0xCEF2, 0x8F05, 0xD6D9, + 0x8F09, 0xEEB0, 0x8F12, 0xF4E5, 0x8F13, 0xD8C2, 0x8F14, 0xDCD0, 0x8F15, 0xCCEE, 0x8F1B, 0xD5E0, 0x8F1C, 0xF6CA, 0x8F1D, 0xFDCA, + 0x8F1E, 0xD8D6, 0x8F1F, 0xF4CF, 0x8F26, 0xD6A6, 0x8F27, 0xDCBE, 0x8F29, 0xDBD4, 0x8F2A, 0xD7C7, 0x8F2F, 0xF2FE, 0x8F33, 0xF1CD, + 0x8F38, 0xE2C3, 0x8F39, 0xDCDE, 0x8F3B, 0xDCDF, 0x8F3E, 0xEFAD, 0x8F3F, 0xE6AB, 0x8F44, 0xF9DD, 0x8F45, 0xEABF, 0x8F49, 0xEFAE, + 0x8F4D, 0xF4D0, 0x8F4E, 0xCEF3, 0x8F5D, 0xE6AC, 0x8F5F, 0xCEDE, 0x8F62, 0xD5F9, 0x8F9B, 0xE3F4, 0x8F9C, 0xCDD0, 0x8FA3, 0xD5B8, + 0x8FA6, 0xF7FD, 0x8FA8, 0xDCA9, 0x8FAD, 0xDEF6, 0x8FAF, 0xDCAA, 0x8FB0, 0xF2E3, 0x8FB1, 0xE9B4, 0x8FB2, 0xD2DC, 0x8FC2, 0xE9E6, + 0x8FC5, 0xE3F6, 0x8FCE, 0xE7CA, 0x8FD1, 0xD0CE, 0x8FD4, 0xDAF7, 0x8FE6, 0xCABC, 0x8FEA, 0xEEE8, 0x8FEB, 0xDADE, 0x8FED, 0xF2F7, + 0x8FF0, 0xE2FB, 0x8FF2, 0xCCA6, 0x8FF7, 0xDABB, 0x8FF9, 0xEEE9, 0x8FFD, 0xF5DA, 0x9000, 0xF7DC, 0x9001, 0xE1EA, 0x9002, 0xCEC1, + 0x9003, 0xD4B1, 0x9005, 0xFDB1, 0x9006, 0xE6BD, 0x9008, 0xFBAD, 0x900B, 0xF8E7, 0x900D, 0xE1CE, 0x900F, 0xF7E2, 0x9010, 0xF5EF, + 0x9011, 0xCFC7, 0x9014, 0xD4B2, 0x9015, 0xCCEF, 0x9017, 0xD4E8, 0x9019, 0xEECF, 0x901A, 0xF7D7, 0x901D, 0xE0A6, 0x901E, 0xD6C1, + 0x901F, 0xE1DC, 0x9020, 0xF0E3, 0x9021, 0xF1E4, 0x9022, 0xDCF1, 0x9023, 0xD6A7, 0x902E, 0xF4F5, 0x9031, 0xF1CE, 0x9032, 0xF2E4, + 0x9035, 0xD0B0, 0x9038, 0xECEF, 0x903C, 0xF9BA, 0x903E, 0xEBB5, 0x9041, 0xD4ED, 0x9042, 0xE2C4, 0x9047, 0xE9E7, 0x904A, 0xEBB4, + 0x904B, 0xEAA1, 0x904D, 0xF8BC, 0x904E, 0xCEA6, 0x9050, 0xF9C6, 0x9051, 0xFCDA, 0x9053, 0xD4B3, 0x9054, 0xD3B9, 0x9055, 0xEADE, + 0x9059, 0xE9AB, 0x905C, 0xE1E1, 0x905D, 0xD3CF, 0x905E, 0xF4F6, 0x9060, 0xEAC0, 0x9061, 0xE1CF, 0x9063, 0xCCBA, 0x9069, 0xEEEA, + 0x906D, 0xF0E4, 0x906E, 0xF3B4, 0x906F, 0xD4EE, 0x9072, 0xF2C0, 0x9075, 0xF1E5, 0x9077, 0xF4C3, 0x9078, 0xE0D4, 0x907A, 0xEBB6, + 0x907C, 0xD7A1, 0x907D, 0xCBE8, 0x907F, 0xF9AD, 0x9080, 0xE9AD, 0x9081, 0xD8E4, 0x9082, 0xFAB3, 0x9083, 0xE2C5, 0x9084, 0xFCBD, + 0x9087, 0xECC4, 0x9088, 0xD8B1, 0x908A, 0xDCAB, 0x908F, 0xD5A4, 0x9091, 0xEBE9, 0x9095, 0xE8BB, 0x9099, 0xD8D7, 0x90A2, 0xFBAE, + 0x90A3, 0xD1E1, 0x90A6, 0xDBC0, 0x90A8, 0xF5BE, 0x90AA, 0xDEF7, 0x90AF, 0xCAFB, 0x90B0, 0xF7C6, 0x90B1, 0xCFC8, 0x90B5, 0xE1D0, + 0x90B8, 0xEED0, 0x90C1, 0xE9F4, 0x90CA, 0xCEF4, 0x90DE, 0xD5CD, 0x90E1, 0xCFDB, 0x90E8, 0xDDBB, 0x90ED, 0xCEAC, 0x90F5, 0xE9E8, + 0x90FD, 0xD4B4, 0x9102, 0xE4C7, 0x9112, 0xF5DB, 0x9115, 0xFAC1, 0x9119, 0xDEA9, 0x9127, 0xD4F8, 0x912D, 0xEFF7, 0x9132, 0xD3B3, + 0x9149, 0xEBB7, 0x914A, 0xEFF8, 0x914B, 0xF5DC, 0x914C, 0xEDCC, 0x914D, 0xDBD5, 0x914E, 0xF1CF, 0x9152, 0xF1D0, 0x9162, 0xF5B2, + 0x9169, 0xD9AE, 0x916A, 0xD5AC, 0x916C, 0xE2C6, 0x9175, 0xFDA3, 0x9177, 0xFBE5, 0x9178, 0xDFAB, 0x9187, 0xE2F5, 0x9189, 0xF6AD, + 0x918B, 0xF5B3, 0x918D, 0xF0B5, 0x9192, 0xE1A5, 0x919C, 0xF5DD, 0x91AB, 0xECA2, 0x91AC, 0xEDFD, 0x91AE, 0xF5B4, 0x91AF, 0xFBB8, + 0x91B1, 0xDBA3, 0x91B4, 0xD6CA, 0x91B5, 0xCBD9, 0x91C0, 0xE5D4, 0x91C7, 0xF3FA, 0x91C9, 0xEBB8, 0x91CB, 0xE0B7, 0x91CC, 0xD7EC, + 0x91CD, 0xF1EC, 0x91CE, 0xE5AF, 0x91CF, 0xD5E1, 0x91D0, 0xD7ED, 0x91D1, 0xD1D1, 0x91D7, 0xE1F2, 0x91D8, 0xEFF9, 0x91DC, 0xDDBC, + 0x91DD, 0xF6DC, 0x91E3, 0xF0E5, 0x91E7, 0xF4C4, 0x91EA, 0xE9E9, 0x91F5, 0xF3FB, 0x920D, 0xD4EF, 0x9210, 0xCCA2, 0x9211, 0xF7FE, + 0x9212, 0xDFBC, 0x9217, 0xEBCD, 0x921E, 0xD0B7, 0x9234, 0xD6C2, 0x923A, 0xE8AD, 0x923F, 0xEFAF, 0x9240, 0xCBA5, 0x9245, 0xCBE9, + 0x9249, 0xFAE8, 0x9257, 0xCCC6, 0x925B, 0xE6E7, 0x925E, 0xEAC7, 0x9262, 0xDBA4, 0x9264, 0xCFC9, 0x9265, 0xE2FC, 0x9266, 0xEFFA, + 0x9280, 0xEBDE, 0x9283, 0xF5C8, 0x9285, 0xD4DE, 0x9291, 0xE0D5, 0x9293, 0xEFB0, 0x9296, 0xE2C7, 0x9298, 0xD9AF, 0x929C, 0xF9E7, + 0x92B3, 0xE7E5, 0x92B6, 0xCFCA, 0x92B7, 0xE1D1, 0x92B9, 0xE2C8, 0x92CC, 0xEFFB, 0x92CF, 0xFAF9, 0x92D2, 0xDCF2, 0x92E4, 0xE0A7, + 0x92EA, 0xF8E8, 0x92F8, 0xCBEA, 0x92FC, 0xCBBC, 0x9304, 0xD6E2, 0x9310, 0xF5DE, 0x9318, 0xF5DF, 0x931A, 0xEEB6, 0x931E, 0xE2F6, + 0x931F, 0xD3CA, 0x9320, 0xEFFC, 0x9321, 0xD1C4, 0x9322, 0xEFB1, 0x9324, 0xD1C5, 0x9326, 0xD0DE, 0x9328, 0xD9E1, 0x932B, 0xE0B8, + 0x932E, 0xCDD1, 0x932F, 0xF3B9, 0x9348, 0xE7CC, 0x934A, 0xD6A8, 0x934B, 0xCEA7, 0x934D, 0xD4B5, 0x9354, 0xE4C8, 0x935B, 0xD3B4, + 0x936E, 0xEBB9, 0x9375, 0xCBF5, 0x937C, 0xF6DD, 0x937E, 0xF1A3, 0x938C, 0xCCC7, 0x9394, 0xE9CA, 0x9396, 0xE1F0, 0x939A, 0xF5E0, + 0x93A3, 0xFBAF, 0x93A7, 0xCBD1, 0x93AC, 0xFBE0, 0x93AD, 0xF2E5, 0x93B0, 0xECF0, 0x93C3, 0xF0EC, 0x93D1, 0xEEEB, 0x93DE, 0xE9CB, + 0x93E1, 0xCCF0, 0x93E4, 0xD7AF, 0x93F6, 0xF3A1, 0x9404, 0xFCF5, 0x9418, 0xF1A4, 0x9425, 0xE0D6, 0x942B, 0xEFB2, 0x9435, 0xF4D1, + 0x9438, 0xF7A1, 0x9444, 0xF1D1, 0x9451, 0xCAFC, 0x9452, 0xCAFD, 0x945B, 0xCECE, 0x947D, 0xF3C8, 0x947F, 0xF3BA, 0x9577, 0xEDFE, + 0x9580, 0xDAA6, 0x9583, 0xE0EC, 0x9589, 0xF8CD, 0x958B, 0xCBD2, 0x958F, 0xEBCE, 0x9591, 0xF9D8, 0x9592, 0xF9D9, 0x9593, 0xCAE0, + 0x9594, 0xDACA, 0x9598, 0xCBA6, 0x95A3, 0xCAC8, 0x95A4, 0xF9EE, 0x95A5, 0xDBEC, 0x95A8, 0xD0B1, 0x95AD, 0xD5EF, 0x95B1, 0xE6F3, + 0x95BB, 0xE7A2, 0x95BC, 0xE4D9, 0x95C7, 0xE4E1, 0x95CA, 0xFCC4, 0x95D4, 0xF9EF, 0x95D5, 0xCFF4, 0x95D6, 0xF7E6, 0x95DC, 0xCEBC, + 0x95E1, 0xF4C5, 0x95E2, 0xDCA3, 0x961C, 0xDDBD, 0x9621, 0xF4C6, 0x962A, 0xF8A1, 0x962E, 0xE8D6, 0x9632, 0xDBC1, 0x963B, 0xF0E6, + 0x963F, 0xE4B9, 0x9640, 0xF6ED, 0x9642, 0xF9AE, 0x9644, 0xDDBE, 0x964B, 0xD7B0, 0x964C, 0xD8E8, 0x964D, 0xCBBD, 0x9650, 0xF9DA, + 0x965B, 0xF8CE, 0x965C, 0xF9F0, 0x965D, 0xE0ED, 0x965E, 0xE3B3, 0x965F, 0xF4B3, 0x9662, 0xEAC2, 0x9663, 0xF2E6, 0x9664, 0xF0B6, + 0x966A, 0xDBD6, 0x9670, 0xEBE4, 0x9673, 0xF2E7, 0x9675, 0xD7D5, 0x9676, 0xD4B6, 0x9677, 0xF9E8, 0x9678, 0xD7C1, 0x967D, 0xE5D5, + 0x9685, 0xE9EA, 0x9686, 0xD7CC, 0x968A, 0xD3E9, 0x968B, 0xE2C9, 0x968D, 0xFCDB, 0x968E, 0xCDAD, 0x9694, 0xCCB0, 0x9695, 0xEAA2, + 0x9698, 0xE4F6, 0x9699, 0xD0C0, 0x969B, 0xF0B7, 0x969C, 0xEEA1, 0x96A3, 0xD7F6, 0x96A7, 0xE2CA, 0x96A8, 0xE2CB, 0x96AA, 0xFACF, + 0x96B1, 0xEBDF, 0x96B7, 0xD6CB, 0x96BB, 0xF4B4, 0x96C0, 0xEDCD, 0x96C1, 0xE4D2, 0x96C4, 0xEAA9, 0x96C5, 0xE4BA, 0x96C6, 0xF3A2, + 0x96C7, 0xCDD2, 0x96C9, 0xF6CB, 0x96CB, 0xF1E6, 0x96CC, 0xEDC1, 0x96CD, 0xE8BC, 0x96CE, 0xEED1, 0x96D5, 0xF0E7, 0x96D6, 0xE2CC, + 0x96D9, 0xE4AA, 0x96DB, 0xF5E1, 0x96DC, 0xEDDA, 0x96E2, 0xD7EE, 0x96E3, 0xD1F1, 0x96E8, 0xE9EB, 0x96E9, 0xE9EC, 0x96EA, 0xE0E4, + 0x96EF, 0xDAA7, 0x96F0, 0xDDD4, 0x96F2, 0xEAA3, 0x96F6, 0xD6C3, 0x96F7, 0xD6F4, 0x96F9, 0xDADF, 0x96FB, 0xEFB3, 0x9700, 0xE2CD, + 0x9706, 0xEFFD, 0x9707, 0xF2E8, 0x9711, 0xEFC5, 0x9713, 0xE7E7, 0x9716, 0xD7FD, 0x9719, 0xE7CE, 0x971C, 0xDFDC, 0x971E, 0xF9C7, + 0x9727, 0xD9F6, 0x9730, 0xDFAC, 0x9732, 0xD6DA, 0x9739, 0xDCA4, 0x973D, 0xF0B8, 0x9742, 0xD5FA, 0x9744, 0xE4F7, 0x9748, 0xD6C4, + 0x9751, 0xF4EC, 0x9756, 0xEFFE, 0x975C, 0xF0A1, 0x975E, 0xDEAA, 0x9761, 0xDABC, 0x9762, 0xD8FC, 0x9769, 0xFAD4, 0x976D, 0xECE5, + 0x9774, 0xFCA8, 0x9777, 0xECE6, 0x977A, 0xD8CB, 0x978B, 0xFBB9, 0x978D, 0xE4D3, 0x978F, 0xCDF9, 0x97A0, 0xCFD3, 0x97A8, 0xCAEA, + 0x97AB, 0xCFD4, 0x97AD, 0xF8BD, 0x97C6, 0xF4C7, 0x97CB, 0xEADF, 0x97D3, 0xF9DB, 0x97DC, 0xD4B7, 0x97F3, 0xEBE5, 0x97F6, 0xE1D2, + 0x97FB, 0xEAA4, 0x97FF, 0xFAC2, 0x9800, 0xFBE1, 0x9801, 0xFAED, 0x9802, 0xF0A2, 0x9803, 0xCCF1, 0x9805, 0xFAA3, 0x9806, 0xE2F7, + 0x9808, 0xE2CE, 0x980A, 0xE9F5, 0x980C, 0xE1EB, 0x9810, 0xE7E8, 0x9811, 0xE8D7, 0x9812, 0xDAF8, 0x9813, 0xD4CB, 0x9817, 0xF7F6, + 0x9818, 0xD6C5, 0x982D, 0xD4E9, 0x9830, 0xFAFA, 0x9838, 0xCCF2, 0x9839, 0xF7DD, 0x983B, 0xDEBA, 0x9846, 0xCEA8, 0x984C, 0xF0B9, + 0x984D, 0xE4FE, 0x984E, 0xE4C9, 0x9854, 0xE4D4, 0x9858, 0xEAC3, 0x985A, 0xEFB4, 0x985E, 0xD7BE, 0x9865, 0xFBE2, 0x9867, 0xCDD3, + 0x986B, 0xEFB5, 0x986F, 0xFAE9, 0x98A8, 0xF9A6, 0x98AF, 0xDFBD, 0x98B1, 0xF7C7, 0x98C4, 0xF8FD, 0x98C7, 0xF8FC, 0x98DB, 0xDEAB, + 0x98DC, 0xDBE8, 0x98DF, 0xE3DD, 0x98E1, 0xE1E2, 0x98E2, 0xD1C6, 0x98ED, 0xF6D0, 0x98EE, 0xEBE6, 0x98EF, 0xDAF9, 0x98F4, 0xECC7, + 0x98FC, 0xDEF8, 0x98FD, 0xF8E9, 0x98FE, 0xE3DE, 0x9903, 0xCEF5, 0x9909, 0xFAC3, 0x990A, 0xE5D7, 0x990C, 0xECC8, 0x9910, 0xF3C9, + 0x9913, 0xE4BB, 0x9918, 0xE6AE, 0x991E, 0xEFB6, 0x9920, 0xDCBF, 0x9928, 0xCEBD, 0x9945, 0xD8C3, 0x9949, 0xD0CF, 0x994B, 0xCFFA, + 0x994C, 0xF3CA, 0x994D, 0xE0D7, 0x9951, 0xD1C7, 0x9952, 0xE9AE, 0x9954, 0xE8BD, 0x9957, 0xFAC4, 0x9996, 0xE2CF, 0x9999, 0xFAC5, + 0x999D, 0xF9B8, 0x99A5, 0xDCE0, 0x99A8, 0xFBB0, 0x99AC, 0xD8A9, 0x99AD, 0xE5DF, 0x99AE, 0xF9A7, 0x99B1, 0xF6EE, 0x99B3, 0xF6CC, + 0x99B4, 0xE2F8, 0x99B9, 0xECF1, 0x99C1, 0xDAE0, 0x99D0, 0xF1D2, 0x99D1, 0xD2CC, 0x99D2, 0xCFCB, 0x99D5, 0xCABD, 0x99D9, 0xDDBF, + 0x99DD, 0xF6EF, 0x99DF, 0xDEF9, 0x99ED, 0xFAB4, 0x99F1, 0xD5AD, 0x99FF, 0xF1E7, 0x9A01, 0xDEBE, 0x9A08, 0xDCC0, 0x9A0E, 0xD1C8, + 0x9A0F, 0xD1C9, 0x9A19, 0xF8BE, 0x9A2B, 0xCBF6, 0x9A30, 0xD4F9, 0x9A36, 0xF5E2, 0x9A37, 0xE1D3, 0x9A40, 0xD8E9, 0x9A43, 0xF8FE, + 0x9A45, 0xCFCC, 0x9A4D, 0xFDA4, 0x9A55, 0xCEF6, 0x9A57, 0xFAD0, 0x9A5A, 0xCCF3, 0x9A5B, 0xE6BE, 0x9A5F, 0xF6AE, 0x9A62, 0xD5F0, + 0x9A65, 0xD1CA, 0x9A69, 0xFCBE, 0x9A6A, 0xD5F1, 0x9AA8, 0xCDE9, 0x9AB8, 0xFAB5, 0x9AD3, 0xE2D0, 0x9AD4, 0xF4F7, 0x9AD8, 0xCDD4, + 0x9AE5, 0xE7A3, 0x9AEE, 0xDBA5, 0x9B1A, 0xE2D1, 0x9B27, 0xD7A2, 0x9B2A, 0xF7E3, 0x9B31, 0xEAA6, 0x9B3C, 0xD0A1, 0x9B41, 0xCEDA, + 0x9B42, 0xFBEB, 0x9B43, 0xDBA6, 0x9B44, 0xDBDE, 0x9B45, 0xD8E5, 0x9B4F, 0xEAE0, 0x9B54, 0xD8AA, 0x9B5A, 0xE5E0, 0x9B6F, 0xD6DB, + 0x9B8E, 0xEFC6, 0x9B91, 0xF8EA, 0x9B9F, 0xE4D5, 0x9BAB, 0xCEF7, 0x9BAE, 0xE0D8, 0x9BC9, 0xD7EF, 0x9BD6, 0xF4ED, 0x9BE4, 0xCDE6, + 0x9BE8, 0xCCF4, 0x9C0D, 0xF5E3, 0x9C10, 0xE4CA, 0x9C12, 0xDCE1, 0x9C15, 0xF9C8, 0x9C25, 0xFCBF, 0x9C32, 0xE8A7, 0x9C3B, 0xD8C4, + 0x9C47, 0xCBBE, 0x9C49, 0xDCAE, 0x9C57, 0xD7F7, 0x9CE5, 0xF0E8, 0x9CE7, 0xDDC0, 0x9CE9, 0xCFCD, 0x9CF3, 0xDCF3, 0x9CF4, 0xD9B0, + 0x9CF6, 0xE6E9, 0x9D09, 0xE4BC, 0x9D1B, 0xEAC4, 0x9D26, 0xE4EC, 0x9D28, 0xE4E5, 0x9D3B, 0xFBF8, 0x9D51, 0xCCBB, 0x9D5D, 0xE4BD, + 0x9D60, 0xCDDC, 0x9D61, 0xD9F7, 0x9D6C, 0xDDDF, 0x9D72, 0xEDCE, 0x9DA9, 0xD9D0, 0x9DAF, 0xE5A3, 0x9DB4, 0xF9CD, 0x9DC4, 0xCDAE, + 0x9DD7, 0xCFCE, 0x9DF2, 0xF6AF, 0x9DF8, 0xFDD3, 0x9DF9, 0xEBED, 0x9DFA, 0xD6DC, 0x9E1A, 0xE5A4, 0x9E1E, 0xD5B6, 0x9E75, 0xD6DD, + 0x9E79, 0xF9E9, 0x9E7D, 0xE7A4, 0x9E7F, 0xD6E3, 0x9E92, 0xD1CB, 0x9E93, 0xD6E4, 0x9E97, 0xD5F2, 0x9E9D, 0xDEFA, 0x9E9F, 0xD7F8, + 0x9EA5, 0xD8EA, 0x9EB4, 0xCFD5, 0x9EB5, 0xD8FD, 0x9EBB, 0xD8AB, 0x9EBE, 0xFDCB, 0x9EC3, 0xFCDC, 0x9ECD, 0xE0A8, 0x9ECE, 0xD5F3, + 0x9ED1, 0xFDD9, 0x9ED4, 0xCCA3, 0x9ED8, 0xD9F9, 0x9EDB, 0xD3EA, 0x9EDC, 0xF5F5, 0x9EDE, 0xEFC7, 0x9EE8, 0xD3DA, 0x9EF4, 0xDABD, + 0x9F07, 0xE8A8, 0x9F08, 0xDCAF, 0x9F0E, 0xF0A3, 0x9F13, 0xCDD5, 0x9F20, 0xE0A9, 0x9F3B, 0xDEAC, 0x9F4A, 0xF0BA, 0x9F4B, 0xEEB1, + 0x9F4E, 0xEEB2, 0x9F52, 0xF6CD, 0x9F5F, 0xEED2, 0x9F61, 0xD6C6, 0x9F67, 0xE0E5, 0x9F6A, 0xF3BB, 0x9F6C, 0xE5E1, 0x9F77, 0xE4CB, + 0x9F8D, 0xD7A3, 0x9F90, 0xDBC2, 0x9F95, 0xCAFE, 0x9F9C, 0xCFCF, 0xAC00, 0xB0A1, 0xAC01, 0xB0A2, 0xAC02, 0x8141, 0xAC03, 0x8142, + 0xAC04, 0xB0A3, 0xAC05, 0x8143, 0xAC06, 0x8144, 0xAC07, 0xB0A4, 0xAC08, 0xB0A5, 0xAC09, 0xB0A6, 0xAC0A, 0xB0A7, 0xAC0B, 0x8145, + 0xAC0C, 0x8146, 0xAC0D, 0x8147, 0xAC0E, 0x8148, 0xAC0F, 0x8149, 0xAC10, 0xB0A8, 0xAC11, 0xB0A9, 0xAC12, 0xB0AA, 0xAC13, 0xB0AB, + 0xAC14, 0xB0AC, 0xAC15, 0xB0AD, 0xAC16, 0xB0AE, 0xAC17, 0xB0AF, 0xAC18, 0x814A, 0xAC19, 0xB0B0, 0xAC1A, 0xB0B1, 0xAC1B, 0xB0B2, + 0xAC1C, 0xB0B3, 0xAC1D, 0xB0B4, 0xAC1E, 0x814B, 0xAC1F, 0x814C, 0xAC20, 0xB0B5, 0xAC21, 0x814D, 0xAC22, 0x814E, 0xAC23, 0x814F, + 0xAC24, 0xB0B6, 0xAC25, 0x8150, 0xAC26, 0x8151, 0xAC27, 0x8152, 0xAC28, 0x8153, 0xAC29, 0x8154, 0xAC2A, 0x8155, 0xAC2B, 0x8156, + 0xAC2C, 0xB0B7, 0xAC2D, 0xB0B8, 0xAC2E, 0x8157, 0xAC2F, 0xB0B9, 0xAC30, 0xB0BA, 0xAC31, 0xB0BB, 0xAC32, 0x8158, 0xAC33, 0x8159, + 0xAC34, 0x815A, 0xAC35, 0x8161, 0xAC36, 0x8162, 0xAC37, 0x8163, 0xAC38, 0xB0BC, 0xAC39, 0xB0BD, 0xAC3A, 0x8164, 0xAC3B, 0x8165, + 0xAC3C, 0xB0BE, 0xAC3D, 0x8166, 0xAC3E, 0x8167, 0xAC3F, 0x8168, 0xAC40, 0xB0BF, 0xAC41, 0x8169, 0xAC42, 0x816A, 0xAC43, 0x816B, + 0xAC44, 0x816C, 0xAC45, 0x816D, 0xAC46, 0x816E, 0xAC47, 0x816F, 0xAC48, 0x8170, 0xAC49, 0x8171, 0xAC4A, 0x8172, 0xAC4B, 0xB0C0, + 0xAC4C, 0x8173, 0xAC4D, 0xB0C1, 0xAC4E, 0x8174, 0xAC4F, 0x8175, 0xAC50, 0x8176, 0xAC51, 0x8177, 0xAC52, 0x8178, 0xAC53, 0x8179, + 0xAC54, 0xB0C2, 0xAC55, 0x817A, 0xAC56, 0x8181, 0xAC57, 0x8182, 0xAC58, 0xB0C3, 0xAC59, 0x8183, 0xAC5A, 0x8184, 0xAC5B, 0x8185, + 0xAC5C, 0xB0C4, 0xAC5D, 0x8186, 0xAC5E, 0x8187, 0xAC5F, 0x8188, 0xAC60, 0x8189, 0xAC61, 0x818A, 0xAC62, 0x818B, 0xAC63, 0x818C, + 0xAC64, 0x818D, 0xAC65, 0x818E, 0xAC66, 0x818F, 0xAC67, 0x8190, 0xAC68, 0x8191, 0xAC69, 0x8192, 0xAC6A, 0x8193, 0xAC6B, 0x8194, + 0xAC6C, 0x8195, 0xAC6D, 0x8196, 0xAC6E, 0x8197, 0xAC6F, 0x8198, 0xAC70, 0xB0C5, 0xAC71, 0xB0C6, 0xAC72, 0x8199, 0xAC73, 0x819A, + 0xAC74, 0xB0C7, 0xAC75, 0x819B, 0xAC76, 0x819C, 0xAC77, 0xB0C8, 0xAC78, 0xB0C9, 0xAC79, 0x819D, 0xAC7A, 0xB0CA, 0xAC7B, 0x819E, + 0xAC7C, 0x819F, 0xAC7D, 0x81A0, 0xAC7E, 0x81A1, 0xAC7F, 0x81A2, 0xAC80, 0xB0CB, 0xAC81, 0xB0CC, 0xAC82, 0x81A3, 0xAC83, 0xB0CD, + 0xAC84, 0xB0CE, 0xAC85, 0xB0CF, 0xAC86, 0xB0D0, 0xAC87, 0x81A4, 0xAC88, 0x81A5, 0xAC89, 0xB0D1, 0xAC8A, 0xB0D2, 0xAC8B, 0xB0D3, + 0xAC8C, 0xB0D4, 0xAC8D, 0x81A6, 0xAC8E, 0x81A7, 0xAC8F, 0x81A8, 0xAC90, 0xB0D5, 0xAC91, 0x81A9, 0xAC92, 0x81AA, 0xAC93, 0x81AB, + 0xAC94, 0xB0D6, 0xAC95, 0x81AC, 0xAC96, 0x81AD, 0xAC97, 0x81AE, 0xAC98, 0x81AF, 0xAC99, 0x81B0, 0xAC9A, 0x81B1, 0xAC9B, 0x81B2, + 0xAC9C, 0xB0D7, 0xAC9D, 0xB0D8, 0xAC9E, 0x81B3, 0xAC9F, 0xB0D9, 0xACA0, 0xB0DA, 0xACA1, 0xB0DB, 0xACA2, 0x81B4, 0xACA3, 0x81B5, + 0xACA4, 0x81B6, 0xACA5, 0x81B7, 0xACA6, 0x81B8, 0xACA7, 0x81B9, 0xACA8, 0xB0DC, 0xACA9, 0xB0DD, 0xACAA, 0xB0DE, 0xACAB, 0x81BA, + 0xACAC, 0xB0DF, 0xACAD, 0x81BB, 0xACAE, 0x81BC, 0xACAF, 0xB0E0, 0xACB0, 0xB0E1, 0xACB1, 0x81BD, 0xACB2, 0x81BE, 0xACB3, 0x81BF, + 0xACB4, 0x81C0, 0xACB5, 0x81C1, 0xACB6, 0x81C2, 0xACB7, 0x81C3, 0xACB8, 0xB0E2, 0xACB9, 0xB0E3, 0xACBA, 0x81C4, 0xACBB, 0xB0E4, + 0xACBC, 0xB0E5, 0xACBD, 0xB0E6, 0xACBE, 0x81C5, 0xACBF, 0x81C6, 0xACC0, 0x81C7, 0xACC1, 0xB0E7, 0xACC2, 0x81C8, 0xACC3, 0x81C9, + 0xACC4, 0xB0E8, 0xACC5, 0x81CA, 0xACC6, 0x81CB, 0xACC7, 0x81CC, 0xACC8, 0xB0E9, 0xACC9, 0x81CD, 0xACCA, 0x81CE, 0xACCB, 0x81CF, + 0xACCC, 0xB0EA, 0xACCD, 0x81D0, 0xACCE, 0x81D1, 0xACCF, 0x81D2, 0xACD0, 0x81D3, 0xACD1, 0x81D4, 0xACD2, 0x81D5, 0xACD3, 0x81D6, + 0xACD4, 0x81D7, 0xACD5, 0xB0EB, 0xACD6, 0x81D8, 0xACD7, 0xB0EC, 0xACD8, 0x81D9, 0xACD9, 0x81DA, 0xACDA, 0x81DB, 0xACDB, 0x81DC, + 0xACDC, 0x81DD, 0xACDD, 0x81DE, 0xACDE, 0x81DF, 0xACDF, 0x81E0, 0xACE0, 0xB0ED, 0xACE1, 0xB0EE, 0xACE2, 0x81E1, 0xACE3, 0x81E2, + 0xACE4, 0xB0EF, 0xACE5, 0x81E3, 0xACE6, 0x81E4, 0xACE7, 0xB0F0, 0xACE8, 0xB0F1, 0xACE9, 0x81E5, 0xACEA, 0xB0F2, 0xACEB, 0x81E6, + 0xACEC, 0xB0F3, 0xACED, 0x81E7, 0xACEE, 0x81E8, 0xACEF, 0xB0F4, 0xACF0, 0xB0F5, 0xACF1, 0xB0F6, 0xACF2, 0x81E9, 0xACF3, 0xB0F7, + 0xACF4, 0x81EA, 0xACF5, 0xB0F8, 0xACF6, 0xB0F9, 0xACF7, 0x81EB, 0xACF8, 0x81EC, 0xACF9, 0x81ED, 0xACFA, 0x81EE, 0xACFB, 0x81EF, + 0xACFC, 0xB0FA, 0xACFD, 0xB0FB, 0xACFE, 0x81F0, 0xACFF, 0x81F1, 0xAD00, 0xB0FC, 0xAD01, 0x81F2, 0xAD02, 0x81F3, 0xAD03, 0x81F4, + 0xAD04, 0xB0FD, 0xAD05, 0x81F5, 0xAD06, 0xB0FE, 0xAD07, 0x81F6, 0xAD08, 0x81F7, 0xAD09, 0x81F8, 0xAD0A, 0x81F9, 0xAD0B, 0x81FA, + 0xAD0C, 0xB1A1, 0xAD0D, 0xB1A2, 0xAD0E, 0x81FB, 0xAD0F, 0xB1A3, 0xAD10, 0x81FC, 0xAD11, 0xB1A4, 0xAD12, 0x81FD, 0xAD13, 0x81FE, + 0xAD14, 0x8241, 0xAD15, 0x8242, 0xAD16, 0x8243, 0xAD17, 0x8244, 0xAD18, 0xB1A5, 0xAD19, 0x8245, 0xAD1A, 0x8246, 0xAD1B, 0x8247, + 0xAD1C, 0xB1A6, 0xAD1D, 0x8248, 0xAD1E, 0x8249, 0xAD1F, 0x824A, 0xAD20, 0xB1A7, 0xAD21, 0x824B, 0xAD22, 0x824C, 0xAD23, 0x824D, + 0xAD24, 0x824E, 0xAD25, 0x824F, 0xAD26, 0x8250, 0xAD27, 0x8251, 0xAD28, 0x8252, 0xAD29, 0xB1A8, 0xAD2A, 0x8253, 0xAD2B, 0x8254, + 0xAD2C, 0xB1A9, 0xAD2D, 0xB1AA, 0xAD2E, 0x8255, 0xAD2F, 0x8256, 0xAD30, 0x8257, 0xAD31, 0x8258, 0xAD32, 0x8259, 0xAD33, 0x825A, + 0xAD34, 0xB1AB, 0xAD35, 0xB1AC, 0xAD36, 0x8261, 0xAD37, 0x8262, 0xAD38, 0xB1AD, 0xAD39, 0x8263, 0xAD3A, 0x8264, 0xAD3B, 0x8265, + 0xAD3C, 0xB1AE, 0xAD3D, 0x8266, 0xAD3E, 0x8267, 0xAD3F, 0x8268, 0xAD40, 0x8269, 0xAD41, 0x826A, 0xAD42, 0x826B, 0xAD43, 0x826C, + 0xAD44, 0xB1AF, 0xAD45, 0xB1B0, 0xAD46, 0x826D, 0xAD47, 0xB1B1, 0xAD48, 0x826E, 0xAD49, 0xB1B2, 0xAD4A, 0x826F, 0xAD4B, 0x8270, + 0xAD4C, 0x8271, 0xAD4D, 0x8272, 0xAD4E, 0x8273, 0xAD4F, 0x8274, 0xAD50, 0xB1B3, 0xAD51, 0x8275, 0xAD52, 0x8276, 0xAD53, 0x8277, + 0xAD54, 0xB1B4, 0xAD55, 0x8278, 0xAD56, 0x8279, 0xAD57, 0x827A, 0xAD58, 0xB1B5, 0xAD59, 0x8281, 0xAD5A, 0x8282, 0xAD5B, 0x8283, + 0xAD5C, 0x8284, 0xAD5D, 0x8285, 0xAD5E, 0x8286, 0xAD5F, 0x8287, 0xAD60, 0x8288, 0xAD61, 0xB1B6, 0xAD62, 0x8289, 0xAD63, 0xB1B7, + 0xAD64, 0x828A, 0xAD65, 0x828B, 0xAD66, 0x828C, 0xAD67, 0x828D, 0xAD68, 0x828E, 0xAD69, 0x828F, 0xAD6A, 0x8290, 0xAD6B, 0x8291, + 0xAD6C, 0xB1B8, 0xAD6D, 0xB1B9, 0xAD6E, 0x8292, 0xAD6F, 0x8293, 0xAD70, 0xB1BA, 0xAD71, 0x8294, 0xAD72, 0x8295, 0xAD73, 0xB1BB, + 0xAD74, 0xB1BC, 0xAD75, 0xB1BD, 0xAD76, 0xB1BE, 0xAD77, 0x8296, 0xAD78, 0x8297, 0xAD79, 0x8298, 0xAD7A, 0x8299, 0xAD7B, 0xB1BF, + 0xAD7C, 0xB1C0, 0xAD7D, 0xB1C1, 0xAD7E, 0x829A, 0xAD7F, 0xB1C2, 0xAD80, 0x829B, 0xAD81, 0xB1C3, 0xAD82, 0xB1C4, 0xAD83, 0x829C, + 0xAD84, 0x829D, 0xAD85, 0x829E, 0xAD86, 0x829F, 0xAD87, 0x82A0, 0xAD88, 0xB1C5, 0xAD89, 0xB1C6, 0xAD8A, 0x82A1, 0xAD8B, 0x82A2, + 0xAD8C, 0xB1C7, 0xAD8D, 0x82A3, 0xAD8E, 0x82A4, 0xAD8F, 0x82A5, 0xAD90, 0xB1C8, 0xAD91, 0x82A6, 0xAD92, 0x82A7, 0xAD93, 0x82A8, + 0xAD94, 0x82A9, 0xAD95, 0x82AA, 0xAD96, 0x82AB, 0xAD97, 0x82AC, 0xAD98, 0x82AD, 0xAD99, 0x82AE, 0xAD9A, 0x82AF, 0xAD9B, 0x82B0, + 0xAD9C, 0xB1C9, 0xAD9D, 0xB1CA, 0xAD9E, 0x82B1, 0xAD9F, 0x82B2, 0xADA0, 0x82B3, 0xADA1, 0x82B4, 0xADA2, 0x82B5, 0xADA3, 0x82B6, + 0xADA4, 0xB1CB, 0xADA5, 0x82B7, 0xADA6, 0x82B8, 0xADA7, 0x82B9, 0xADA8, 0x82BA, 0xADA9, 0x82BB, 0xADAA, 0x82BC, 0xADAB, 0x82BD, + 0xADAC, 0x82BE, 0xADAD, 0x82BF, 0xADAE, 0x82C0, 0xADAF, 0x82C1, 0xADB0, 0x82C2, 0xADB1, 0x82C3, 0xADB2, 0x82C4, 0xADB3, 0x82C5, + 0xADB4, 0x82C6, 0xADB5, 0x82C7, 0xADB6, 0x82C8, 0xADB7, 0xB1CC, 0xADB8, 0x82C9, 0xADB9, 0x82CA, 0xADBA, 0x82CB, 0xADBB, 0x82CC, + 0xADBC, 0x82CD, 0xADBD, 0x82CE, 0xADBE, 0x82CF, 0xADBF, 0x82D0, 0xADC0, 0xB1CD, 0xADC1, 0xB1CE, 0xADC2, 0x82D1, 0xADC3, 0x82D2, + 0xADC4, 0xB1CF, 0xADC5, 0x82D3, 0xADC6, 0x82D4, 0xADC7, 0x82D5, 0xADC8, 0xB1D0, 0xADC9, 0x82D6, 0xADCA, 0x82D7, 0xADCB, 0x82D8, + 0xADCC, 0x82D9, 0xADCD, 0x82DA, 0xADCE, 0x82DB, 0xADCF, 0x82DC, 0xADD0, 0xB1D1, 0xADD1, 0xB1D2, 0xADD2, 0x82DD, 0xADD3, 0xB1D3, + 0xADD4, 0x82DE, 0xADD5, 0x82DF, 0xADD6, 0x82E0, 0xADD7, 0x82E1, 0xADD8, 0x82E2, 0xADD9, 0x82E3, 0xADDA, 0x82E4, 0xADDB, 0x82E5, + 0xADDC, 0xB1D4, 0xADDD, 0x82E6, 0xADDE, 0x82E7, 0xADDF, 0x82E8, 0xADE0, 0xB1D5, 0xADE1, 0x82E9, 0xADE2, 0x82EA, 0xADE3, 0x82EB, + 0xADE4, 0xB1D6, 0xADE5, 0x82EC, 0xADE6, 0x82ED, 0xADE7, 0x82EE, 0xADE8, 0x82EF, 0xADE9, 0x82F0, 0xADEA, 0x82F1, 0xADEB, 0x82F2, + 0xADEC, 0x82F3, 0xADED, 0x82F4, 0xADEE, 0x82F5, 0xADEF, 0x82F6, 0xADF0, 0x82F7, 0xADF1, 0x82F8, 0xADF2, 0x82F9, 0xADF3, 0x82FA, + 0xADF4, 0x82FB, 0xADF5, 0x82FC, 0xADF6, 0x82FD, 0xADF7, 0x82FE, 0xADF8, 0xB1D7, 0xADF9, 0xB1D8, 0xADFA, 0x8341, 0xADFB, 0x8342, + 0xADFC, 0xB1D9, 0xADFD, 0x8343, 0xADFE, 0x8344, 0xADFF, 0xB1DA, 0xAE00, 0xB1DB, 0xAE01, 0xB1DC, 0xAE02, 0x8345, 0xAE03, 0x8346, + 0xAE04, 0x8347, 0xAE05, 0x8348, 0xAE06, 0x8349, 0xAE07, 0x834A, 0xAE08, 0xB1DD, 0xAE09, 0xB1DE, 0xAE0A, 0x834B, 0xAE0B, 0xB1DF, + 0xAE0C, 0x834C, 0xAE0D, 0xB1E0, 0xAE0E, 0x834D, 0xAE0F, 0x834E, 0xAE10, 0x834F, 0xAE11, 0x8350, 0xAE12, 0x8351, 0xAE13, 0x8352, + 0xAE14, 0xB1E1, 0xAE15, 0x8353, 0xAE16, 0x8354, 0xAE17, 0x8355, 0xAE18, 0x8356, 0xAE19, 0x8357, 0xAE1A, 0x8358, 0xAE1B, 0x8359, + 0xAE1C, 0x835A, 0xAE1D, 0x8361, 0xAE1E, 0x8362, 0xAE1F, 0x8363, 0xAE20, 0x8364, 0xAE21, 0x8365, 0xAE22, 0x8366, 0xAE23, 0x8367, + 0xAE24, 0x8368, 0xAE25, 0x8369, 0xAE26, 0x836A, 0xAE27, 0x836B, 0xAE28, 0x836C, 0xAE29, 0x836D, 0xAE2A, 0x836E, 0xAE2B, 0x836F, + 0xAE2C, 0x8370, 0xAE2D, 0x8371, 0xAE2E, 0x8372, 0xAE2F, 0x8373, 0xAE30, 0xB1E2, 0xAE31, 0xB1E3, 0xAE32, 0x8374, 0xAE33, 0x8375, + 0xAE34, 0xB1E4, 0xAE35, 0x8376, 0xAE36, 0x8377, 0xAE37, 0xB1E5, 0xAE38, 0xB1E6, 0xAE39, 0x8378, 0xAE3A, 0xB1E7, 0xAE3B, 0x8379, + 0xAE3C, 0x837A, 0xAE3D, 0x8381, 0xAE3E, 0x8382, 0xAE3F, 0x8383, 0xAE40, 0xB1E8, 0xAE41, 0xB1E9, 0xAE42, 0x8384, 0xAE43, 0xB1EA, + 0xAE44, 0x8385, 0xAE45, 0xB1EB, 0xAE46, 0xB1EC, 0xAE47, 0x8386, 0xAE48, 0x8387, 0xAE49, 0x8388, 0xAE4A, 0xB1ED, 0xAE4B, 0x8389, + 0xAE4C, 0xB1EE, 0xAE4D, 0xB1EF, 0xAE4E, 0xB1F0, 0xAE4F, 0x838A, 0xAE50, 0xB1F1, 0xAE51, 0x838B, 0xAE52, 0x838C, 0xAE53, 0x838D, + 0xAE54, 0xB1F2, 0xAE55, 0x838E, 0xAE56, 0xB1F3, 0xAE57, 0x838F, 0xAE58, 0x8390, 0xAE59, 0x8391, 0xAE5A, 0x8392, 0xAE5B, 0x8393, + 0xAE5C, 0xB1F4, 0xAE5D, 0xB1F5, 0xAE5E, 0x8394, 0xAE5F, 0xB1F6, 0xAE60, 0xB1F7, 0xAE61, 0xB1F8, 0xAE62, 0x8395, 0xAE63, 0x8396, + 0xAE64, 0x8397, 0xAE65, 0xB1F9, 0xAE66, 0x8398, 0xAE67, 0x8399, 0xAE68, 0xB1FA, 0xAE69, 0xB1FB, 0xAE6A, 0x839A, 0xAE6B, 0x839B, + 0xAE6C, 0xB1FC, 0xAE6D, 0x839C, 0xAE6E, 0x839D, 0xAE6F, 0x839E, 0xAE70, 0xB1FD, 0xAE71, 0x839F, 0xAE72, 0x83A0, 0xAE73, 0x83A1, + 0xAE74, 0x83A2, 0xAE75, 0x83A3, 0xAE76, 0x83A4, 0xAE77, 0x83A5, 0xAE78, 0xB1FE, 0xAE79, 0xB2A1, 0xAE7A, 0x83A6, 0xAE7B, 0xB2A2, + 0xAE7C, 0xB2A3, 0xAE7D, 0xB2A4, 0xAE7E, 0x83A7, 0xAE7F, 0x83A8, 0xAE80, 0x83A9, 0xAE81, 0x83AA, 0xAE82, 0x83AB, 0xAE83, 0x83AC, + 0xAE84, 0xB2A5, 0xAE85, 0xB2A6, 0xAE86, 0x83AD, 0xAE87, 0x83AE, 0xAE88, 0x83AF, 0xAE89, 0x83B0, 0xAE8A, 0x83B1, 0xAE8B, 0x83B2, + 0xAE8C, 0xB2A7, 0xAE8D, 0x83B3, 0xAE8E, 0x83B4, 0xAE8F, 0x83B5, 0xAE90, 0x83B6, 0xAE91, 0x83B7, 0xAE92, 0x83B8, 0xAE93, 0x83B9, + 0xAE94, 0x83BA, 0xAE95, 0x83BB, 0xAE96, 0x83BC, 0xAE97, 0x83BD, 0xAE98, 0x83BE, 0xAE99, 0x83BF, 0xAE9A, 0x83C0, 0xAE9B, 0x83C1, + 0xAE9C, 0x83C2, 0xAE9D, 0x83C3, 0xAE9E, 0x83C4, 0xAE9F, 0x83C5, 0xAEA0, 0x83C6, 0xAEA1, 0x83C7, 0xAEA2, 0x83C8, 0xAEA3, 0x83C9, + 0xAEA4, 0x83CA, 0xAEA5, 0x83CB, 0xAEA6, 0x83CC, 0xAEA7, 0x83CD, 0xAEA8, 0x83CE, 0xAEA9, 0x83CF, 0xAEAA, 0x83D0, 0xAEAB, 0x83D1, + 0xAEAC, 0x83D2, 0xAEAD, 0x83D3, 0xAEAE, 0x83D4, 0xAEAF, 0x83D5, 0xAEB0, 0x83D6, 0xAEB1, 0x83D7, 0xAEB2, 0x83D8, 0xAEB3, 0x83D9, + 0xAEB4, 0x83DA, 0xAEB5, 0x83DB, 0xAEB6, 0x83DC, 0xAEB7, 0x83DD, 0xAEB8, 0x83DE, 0xAEB9, 0x83DF, 0xAEBA, 0x83E0, 0xAEBB, 0x83E1, + 0xAEBC, 0xB2A8, 0xAEBD, 0xB2A9, 0xAEBE, 0xB2AA, 0xAEBF, 0x83E2, 0xAEC0, 0xB2AB, 0xAEC1, 0x83E3, 0xAEC2, 0x83E4, 0xAEC3, 0x83E5, + 0xAEC4, 0xB2AC, 0xAEC5, 0x83E6, 0xAEC6, 0x83E7, 0xAEC7, 0x83E8, 0xAEC8, 0x83E9, 0xAEC9, 0x83EA, 0xAECA, 0x83EB, 0xAECB, 0x83EC, + 0xAECC, 0xB2AD, 0xAECD, 0xB2AE, 0xAECE, 0x83ED, 0xAECF, 0xB2AF, 0xAED0, 0xB2B0, 0xAED1, 0xB2B1, 0xAED2, 0x83EE, 0xAED3, 0x83EF, + 0xAED4, 0x83F0, 0xAED5, 0x83F1, 0xAED6, 0x83F2, 0xAED7, 0x83F3, 0xAED8, 0xB2B2, 0xAED9, 0xB2B3, 0xAEDA, 0x83F4, 0xAEDB, 0x83F5, + 0xAEDC, 0xB2B4, 0xAEDD, 0x83F6, 0xAEDE, 0x83F7, 0xAEDF, 0x83F8, 0xAEE0, 0x83F9, 0xAEE1, 0x83FA, 0xAEE2, 0x83FB, 0xAEE3, 0x83FC, + 0xAEE4, 0x83FD, 0xAEE5, 0x83FE, 0xAEE6, 0x8441, 0xAEE7, 0x8442, 0xAEE8, 0xB2B5, 0xAEE9, 0x8443, 0xAEEA, 0x8444, 0xAEEB, 0xB2B6, + 0xAEEC, 0x8445, 0xAEED, 0xB2B7, 0xAEEE, 0x8446, 0xAEEF, 0x8447, 0xAEF0, 0x8448, 0xAEF1, 0x8449, 0xAEF2, 0x844A, 0xAEF3, 0x844B, + 0xAEF4, 0xB2B8, 0xAEF5, 0x844C, 0xAEF6, 0x844D, 0xAEF7, 0x844E, 0xAEF8, 0xB2B9, 0xAEF9, 0x844F, 0xAEFA, 0x8450, 0xAEFB, 0x8451, + 0xAEFC, 0xB2BA, 0xAEFD, 0x8452, 0xAEFE, 0x8453, 0xAEFF, 0x8454, 0xAF00, 0x8455, 0xAF01, 0x8456, 0xAF02, 0x8457, 0xAF03, 0x8458, + 0xAF04, 0x8459, 0xAF05, 0x845A, 0xAF06, 0x8461, 0xAF07, 0xB2BB, 0xAF08, 0xB2BC, 0xAF09, 0x8462, 0xAF0A, 0x8463, 0xAF0B, 0x8464, + 0xAF0C, 0x8465, 0xAF0D, 0xB2BD, 0xAF0E, 0x8466, 0xAF0F, 0x8467, 0xAF10, 0xB2BE, 0xAF11, 0x8468, 0xAF12, 0x8469, 0xAF13, 0x846A, + 0xAF14, 0x846B, 0xAF15, 0x846C, 0xAF16, 0x846D, 0xAF17, 0x846E, 0xAF18, 0x846F, 0xAF19, 0x8470, 0xAF1A, 0x8471, 0xAF1B, 0x8472, + 0xAF1C, 0x8473, 0xAF1D, 0x8474, 0xAF1E, 0x8475, 0xAF1F, 0x8476, 0xAF20, 0x8477, 0xAF21, 0x8478, 0xAF22, 0x8479, 0xAF23, 0x847A, + 0xAF24, 0x8481, 0xAF25, 0x8482, 0xAF26, 0x8483, 0xAF27, 0x8484, 0xAF28, 0x8485, 0xAF29, 0x8486, 0xAF2A, 0x8487, 0xAF2B, 0x8488, + 0xAF2C, 0xB2BF, 0xAF2D, 0xB2C0, 0xAF2E, 0x8489, 0xAF2F, 0x848A, 0xAF30, 0xB2C1, 0xAF31, 0x848B, 0xAF32, 0xB2C2, 0xAF33, 0x848C, + 0xAF34, 0xB2C3, 0xAF35, 0x848D, 0xAF36, 0x848E, 0xAF37, 0x848F, 0xAF38, 0x8490, 0xAF39, 0x8491, 0xAF3A, 0x8492, 0xAF3B, 0x8493, + 0xAF3C, 0xB2C4, 0xAF3D, 0xB2C5, 0xAF3E, 0x8494, 0xAF3F, 0xB2C6, 0xAF40, 0x8495, 0xAF41, 0xB2C7, 0xAF42, 0xB2C8, 0xAF43, 0xB2C9, + 0xAF44, 0x8496, 0xAF45, 0x8497, 0xAF46, 0x8498, 0xAF47, 0x8499, 0xAF48, 0xB2CA, 0xAF49, 0xB2CB, 0xAF4A, 0x849A, 0xAF4B, 0x849B, + 0xAF4C, 0x849C, 0xAF4D, 0x849D, 0xAF4E, 0x849E, 0xAF4F, 0x849F, 0xAF50, 0xB2CC, 0xAF51, 0x84A0, 0xAF52, 0x84A1, 0xAF53, 0x84A2, + 0xAF54, 0x84A3, 0xAF55, 0x84A4, 0xAF56, 0x84A5, 0xAF57, 0x84A6, 0xAF58, 0x84A7, 0xAF59, 0x84A8, 0xAF5A, 0x84A9, 0xAF5B, 0x84AA, + 0xAF5C, 0xB2CD, 0xAF5D, 0xB2CE, 0xAF5E, 0x84AB, 0xAF5F, 0x84AC, 0xAF60, 0x84AD, 0xAF61, 0x84AE, 0xAF62, 0x84AF, 0xAF63, 0x84B0, + 0xAF64, 0xB2CF, 0xAF65, 0xB2D0, 0xAF66, 0x84B1, 0xAF67, 0x84B2, 0xAF68, 0x84B3, 0xAF69, 0x84B4, 0xAF6A, 0x84B5, 0xAF6B, 0x84B6, + 0xAF6C, 0x84B7, 0xAF6D, 0x84B8, 0xAF6E, 0x84B9, 0xAF6F, 0x84BA, 0xAF70, 0x84BB, 0xAF71, 0x84BC, 0xAF72, 0x84BD, 0xAF73, 0x84BE, + 0xAF74, 0x84BF, 0xAF75, 0x84C0, 0xAF76, 0x84C1, 0xAF77, 0x84C2, 0xAF78, 0x84C3, 0xAF79, 0xB2D1, 0xAF7A, 0x84C4, 0xAF7B, 0x84C5, + 0xAF7C, 0x84C6, 0xAF7D, 0x84C7, 0xAF7E, 0x84C8, 0xAF7F, 0x84C9, 0xAF80, 0xB2D2, 0xAF81, 0x84CA, 0xAF82, 0x84CB, 0xAF83, 0x84CC, + 0xAF84, 0xB2D3, 0xAF85, 0x84CD, 0xAF86, 0x84CE, 0xAF87, 0x84CF, 0xAF88, 0xB2D4, 0xAF89, 0x84D0, 0xAF8A, 0x84D1, 0xAF8B, 0x84D2, + 0xAF8C, 0x84D3, 0xAF8D, 0x84D4, 0xAF8E, 0x84D5, 0xAF8F, 0x84D6, 0xAF90, 0xB2D5, 0xAF91, 0xB2D6, 0xAF92, 0x84D7, 0xAF93, 0x84D8, + 0xAF94, 0x84D9, 0xAF95, 0xB2D7, 0xAF96, 0x84DA, 0xAF97, 0x84DB, 0xAF98, 0x84DC, 0xAF99, 0x84DD, 0xAF9A, 0x84DE, 0xAF9B, 0x84DF, + 0xAF9C, 0xB2D8, 0xAF9D, 0x84E0, 0xAF9E, 0x84E1, 0xAF9F, 0x84E2, 0xAFA0, 0x84E3, 0xAFA1, 0x84E4, 0xAFA2, 0x84E5, 0xAFA3, 0x84E6, + 0xAFA4, 0x84E7, 0xAFA5, 0x84E8, 0xAFA6, 0x84E9, 0xAFA7, 0x84EA, 0xAFA8, 0x84EB, 0xAFA9, 0x84EC, 0xAFAA, 0x84ED, 0xAFAB, 0x84EE, + 0xAFAC, 0x84EF, 0xAFAD, 0x84F0, 0xAFAE, 0x84F1, 0xAFAF, 0x84F2, 0xAFB0, 0x84F3, 0xAFB1, 0x84F4, 0xAFB2, 0x84F5, 0xAFB3, 0x84F6, + 0xAFB4, 0x84F7, 0xAFB5, 0x84F8, 0xAFB6, 0x84F9, 0xAFB7, 0x84FA, 0xAFB8, 0xB2D9, 0xAFB9, 0xB2DA, 0xAFBA, 0x84FB, 0xAFBB, 0x84FC, + 0xAFBC, 0xB2DB, 0xAFBD, 0x84FD, 0xAFBE, 0x84FE, 0xAFBF, 0x8541, 0xAFC0, 0xB2DC, 0xAFC1, 0x8542, 0xAFC2, 0x8543, 0xAFC3, 0x8544, + 0xAFC4, 0x8545, 0xAFC5, 0x8546, 0xAFC6, 0x8547, 0xAFC7, 0xB2DD, 0xAFC8, 0xB2DE, 0xAFC9, 0xB2DF, 0xAFCA, 0x8548, 0xAFCB, 0xB2E0, + 0xAFCC, 0x8549, 0xAFCD, 0xB2E1, 0xAFCE, 0xB2E2, 0xAFCF, 0x854A, 0xAFD0, 0x854B, 0xAFD1, 0x854C, 0xAFD2, 0x854D, 0xAFD3, 0x854E, + 0xAFD4, 0xB2E3, 0xAFD5, 0x854F, 0xAFD6, 0x8550, 0xAFD7, 0x8551, 0xAFD8, 0x8552, 0xAFD9, 0x8553, 0xAFDA, 0x8554, 0xAFDB, 0x8555, + 0xAFDC, 0xB2E4, 0xAFDD, 0x8556, 0xAFDE, 0x8557, 0xAFDF, 0x8558, 0xAFE0, 0x8559, 0xAFE1, 0x855A, 0xAFE2, 0x8561, 0xAFE3, 0x8562, + 0xAFE4, 0x8563, 0xAFE5, 0x8564, 0xAFE6, 0x8565, 0xAFE7, 0x8566, 0xAFE8, 0xB2E5, 0xAFE9, 0xB2E6, 0xAFEA, 0x8567, 0xAFEB, 0x8568, + 0xAFEC, 0x8569, 0xAFED, 0x856A, 0xAFEE, 0x856B, 0xAFEF, 0x856C, 0xAFF0, 0xB2E7, 0xAFF1, 0xB2E8, 0xAFF2, 0x856D, 0xAFF3, 0x856E, + 0xAFF4, 0xB2E9, 0xAFF5, 0x856F, 0xAFF6, 0x8570, 0xAFF7, 0x8571, 0xAFF8, 0xB2EA, 0xAFF9, 0x8572, 0xAFFA, 0x8573, 0xAFFB, 0x8574, + 0xAFFC, 0x8575, 0xAFFD, 0x8576, 0xAFFE, 0x8577, 0xAFFF, 0x8578, 0xB000, 0xB2EB, 0xB001, 0xB2EC, 0xB002, 0x8579, 0xB003, 0x857A, + 0xB004, 0xB2ED, 0xB005, 0x8581, 0xB006, 0x8582, 0xB007, 0x8583, 0xB008, 0x8584, 0xB009, 0x8585, 0xB00A, 0x8586, 0xB00B, 0x8587, + 0xB00C, 0xB2EE, 0xB00D, 0x8588, 0xB00E, 0x8589, 0xB00F, 0x858A, 0xB010, 0xB2EF, 0xB011, 0x858B, 0xB012, 0x858C, 0xB013, 0x858D, + 0xB014, 0xB2F0, 0xB015, 0x858E, 0xB016, 0x858F, 0xB017, 0x8590, 0xB018, 0x8591, 0xB019, 0x8592, 0xB01A, 0x8593, 0xB01B, 0x8594, + 0xB01C, 0xB2F1, 0xB01D, 0xB2F2, 0xB01E, 0x8595, 0xB01F, 0x8596, 0xB020, 0x8597, 0xB021, 0x8598, 0xB022, 0x8599, 0xB023, 0x859A, + 0xB024, 0x859B, 0xB025, 0x859C, 0xB026, 0x859D, 0xB027, 0x859E, 0xB028, 0xB2F3, 0xB029, 0x859F, 0xB02A, 0x85A0, 0xB02B, 0x85A1, + 0xB02C, 0x85A2, 0xB02D, 0x85A3, 0xB02E, 0x85A4, 0xB02F, 0x85A5, 0xB030, 0x85A6, 0xB031, 0x85A7, 0xB032, 0x85A8, 0xB033, 0x85A9, + 0xB034, 0x85AA, 0xB035, 0x85AB, 0xB036, 0x85AC, 0xB037, 0x85AD, 0xB038, 0x85AE, 0xB039, 0x85AF, 0xB03A, 0x85B0, 0xB03B, 0x85B1, + 0xB03C, 0x85B2, 0xB03D, 0x85B3, 0xB03E, 0x85B4, 0xB03F, 0x85B5, 0xB040, 0x85B6, 0xB041, 0x85B7, 0xB042, 0x85B8, 0xB043, 0x85B9, + 0xB044, 0xB2F4, 0xB045, 0xB2F5, 0xB046, 0x85BA, 0xB047, 0x85BB, 0xB048, 0xB2F6, 0xB049, 0x85BC, 0xB04A, 0xB2F7, 0xB04B, 0x85BD, + 0xB04C, 0xB2F8, 0xB04D, 0x85BE, 0xB04E, 0xB2F9, 0xB04F, 0x85BF, 0xB050, 0x85C0, 0xB051, 0x85C1, 0xB052, 0x85C2, 0xB053, 0xB2FA, + 0xB054, 0xB2FB, 0xB055, 0xB2FC, 0xB056, 0x85C3, 0xB057, 0xB2FD, 0xB058, 0x85C4, 0xB059, 0xB2FE, 0xB05A, 0x85C5, 0xB05B, 0x85C6, + 0xB05C, 0x85C7, 0xB05D, 0xB3A1, 0xB05E, 0x85C8, 0xB05F, 0x85C9, 0xB060, 0x85CA, 0xB061, 0x85CB, 0xB062, 0x85CC, 0xB063, 0x85CD, + 0xB064, 0x85CE, 0xB065, 0x85CF, 0xB066, 0x85D0, 0xB067, 0x85D1, 0xB068, 0x85D2, 0xB069, 0x85D3, 0xB06A, 0x85D4, 0xB06B, 0x85D5, + 0xB06C, 0x85D6, 0xB06D, 0x85D7, 0xB06E, 0x85D8, 0xB06F, 0x85D9, 0xB070, 0x85DA, 0xB071, 0x85DB, 0xB072, 0x85DC, 0xB073, 0x85DD, + 0xB074, 0x85DE, 0xB075, 0x85DF, 0xB076, 0x85E0, 0xB077, 0x85E1, 0xB078, 0x85E2, 0xB079, 0x85E3, 0xB07A, 0x85E4, 0xB07B, 0x85E5, + 0xB07C, 0xB3A2, 0xB07D, 0xB3A3, 0xB07E, 0x85E6, 0xB07F, 0x85E7, 0xB080, 0xB3A4, 0xB081, 0x85E8, 0xB082, 0x85E9, 0xB083, 0x85EA, + 0xB084, 0xB3A5, 0xB085, 0x85EB, 0xB086, 0x85EC, 0xB087, 0x85ED, 0xB088, 0x85EE, 0xB089, 0x85EF, 0xB08A, 0x85F0, 0xB08B, 0x85F1, + 0xB08C, 0xB3A6, 0xB08D, 0xB3A7, 0xB08E, 0x85F2, 0xB08F, 0xB3A8, 0xB090, 0x85F3, 0xB091, 0xB3A9, 0xB092, 0x85F4, 0xB093, 0x85F5, + 0xB094, 0x85F6, 0xB095, 0x85F7, 0xB096, 0x85F8, 0xB097, 0x85F9, 0xB098, 0xB3AA, 0xB099, 0xB3AB, 0xB09A, 0xB3AC, 0xB09B, 0x85FA, + 0xB09C, 0xB3AD, 0xB09D, 0x85FB, 0xB09E, 0x85FC, 0xB09F, 0xB3AE, 0xB0A0, 0xB3AF, 0xB0A1, 0xB3B0, 0xB0A2, 0xB3B1, 0xB0A3, 0x85FD, + 0xB0A4, 0x85FE, 0xB0A5, 0x8641, 0xB0A6, 0x8642, 0xB0A7, 0x8643, 0xB0A8, 0xB3B2, 0xB0A9, 0xB3B3, 0xB0AA, 0x8644, 0xB0AB, 0xB3B4, + 0xB0AC, 0xB3B5, 0xB0AD, 0xB3B6, 0xB0AE, 0xB3B7, 0xB0AF, 0xB3B8, 0xB0B0, 0x8645, 0xB0B1, 0xB3B9, 0xB0B2, 0x8646, 0xB0B3, 0xB3BA, + 0xB0B4, 0xB3BB, 0xB0B5, 0xB3BC, 0xB0B6, 0x8647, 0xB0B7, 0x8648, 0xB0B8, 0xB3BD, 0xB0B9, 0x8649, 0xB0BA, 0x864A, 0xB0BB, 0x864B, + 0xB0BC, 0xB3BE, 0xB0BD, 0x864C, 0xB0BE, 0x864D, 0xB0BF, 0x864E, 0xB0C0, 0x864F, 0xB0C1, 0x8650, 0xB0C2, 0x8651, 0xB0C3, 0x8652, + 0xB0C4, 0xB3BF, 0xB0C5, 0xB3C0, 0xB0C6, 0x8653, 0xB0C7, 0xB3C1, 0xB0C8, 0xB3C2, 0xB0C9, 0xB3C3, 0xB0CA, 0x8654, 0xB0CB, 0x8655, + 0xB0CC, 0x8656, 0xB0CD, 0x8657, 0xB0CE, 0x8658, 0xB0CF, 0x8659, 0xB0D0, 0xB3C4, 0xB0D1, 0xB3C5, 0xB0D2, 0x865A, 0xB0D3, 0x8661, + 0xB0D4, 0xB3C6, 0xB0D5, 0x8662, 0xB0D6, 0x8663, 0xB0D7, 0x8664, 0xB0D8, 0xB3C7, 0xB0D9, 0x8665, 0xB0DA, 0x8666, 0xB0DB, 0x8667, + 0xB0DC, 0x8668, 0xB0DD, 0x8669, 0xB0DE, 0x866A, 0xB0DF, 0x866B, 0xB0E0, 0xB3C8, 0xB0E1, 0x866C, 0xB0E2, 0x866D, 0xB0E3, 0x866E, + 0xB0E4, 0x866F, 0xB0E5, 0xB3C9, 0xB0E6, 0x8670, 0xB0E7, 0x8671, 0xB0E8, 0x8672, 0xB0E9, 0x8673, 0xB0EA, 0x8674, 0xB0EB, 0x8675, + 0xB0EC, 0x8676, 0xB0ED, 0x8677, 0xB0EE, 0x8678, 0xB0EF, 0x8679, 0xB0F0, 0x867A, 0xB0F1, 0x8681, 0xB0F2, 0x8682, 0xB0F3, 0x8683, + 0xB0F4, 0x8684, 0xB0F5, 0x8685, 0xB0F6, 0x8686, 0xB0F7, 0x8687, 0xB0F8, 0x8688, 0xB0F9, 0x8689, 0xB0FA, 0x868A, 0xB0FB, 0x868B, + 0xB0FC, 0x868C, 0xB0FD, 0x868D, 0xB0FE, 0x868E, 0xB0FF, 0x868F, 0xB100, 0x8690, 0xB101, 0x8691, 0xB102, 0x8692, 0xB103, 0x8693, + 0xB104, 0x8694, 0xB105, 0x8695, 0xB106, 0x8696, 0xB107, 0x8697, 0xB108, 0xB3CA, 0xB109, 0xB3CB, 0xB10A, 0x8698, 0xB10B, 0xB3CC, + 0xB10C, 0xB3CD, 0xB10D, 0x8699, 0xB10E, 0x869A, 0xB10F, 0x869B, 0xB110, 0xB3CE, 0xB111, 0x869C, 0xB112, 0xB3CF, 0xB113, 0xB3D0, + 0xB114, 0x869D, 0xB115, 0x869E, 0xB116, 0x869F, 0xB117, 0x86A0, 0xB118, 0xB3D1, 0xB119, 0xB3D2, 0xB11A, 0x86A1, 0xB11B, 0xB3D3, + 0xB11C, 0xB3D4, 0xB11D, 0xB3D5, 0xB11E, 0x86A2, 0xB11F, 0x86A3, 0xB120, 0x86A4, 0xB121, 0x86A5, 0xB122, 0x86A6, 0xB123, 0xB3D6, + 0xB124, 0xB3D7, 0xB125, 0xB3D8, 0xB126, 0x86A7, 0xB127, 0x86A8, 0xB128, 0xB3D9, 0xB129, 0x86A9, 0xB12A, 0x86AA, 0xB12B, 0x86AB, + 0xB12C, 0xB3DA, 0xB12D, 0x86AC, 0xB12E, 0x86AD, 0xB12F, 0x86AE, 0xB130, 0x86AF, 0xB131, 0x86B0, 0xB132, 0x86B1, 0xB133, 0x86B2, + 0xB134, 0xB3DB, 0xB135, 0xB3DC, 0xB136, 0x86B3, 0xB137, 0xB3DD, 0xB138, 0xB3DE, 0xB139, 0xB3DF, 0xB13A, 0x86B4, 0xB13B, 0x86B5, + 0xB13C, 0x86B6, 0xB13D, 0x86B7, 0xB13E, 0x86B8, 0xB13F, 0x86B9, 0xB140, 0xB3E0, 0xB141, 0xB3E1, 0xB142, 0x86BA, 0xB143, 0x86BB, + 0xB144, 0xB3E2, 0xB145, 0x86BC, 0xB146, 0x86BD, 0xB147, 0x86BE, 0xB148, 0xB3E3, 0xB149, 0x86BF, 0xB14A, 0x86C0, 0xB14B, 0x86C1, + 0xB14C, 0x86C2, 0xB14D, 0x86C3, 0xB14E, 0x86C4, 0xB14F, 0x86C5, 0xB150, 0xB3E4, 0xB151, 0xB3E5, 0xB152, 0x86C6, 0xB153, 0x86C7, + 0xB154, 0xB3E6, 0xB155, 0xB3E7, 0xB156, 0x86C8, 0xB157, 0x86C9, 0xB158, 0xB3E8, 0xB159, 0x86CA, 0xB15A, 0x86CB, 0xB15B, 0x86CC, + 0xB15C, 0xB3E9, 0xB15D, 0x86CD, 0xB15E, 0x86CE, 0xB15F, 0x86CF, 0xB160, 0xB3EA, 0xB161, 0x86D0, 0xB162, 0x86D1, 0xB163, 0x86D2, + 0xB164, 0x86D3, 0xB165, 0x86D4, 0xB166, 0x86D5, 0xB167, 0x86D6, 0xB168, 0x86D7, 0xB169, 0x86D8, 0xB16A, 0x86D9, 0xB16B, 0x86DA, + 0xB16C, 0x86DB, 0xB16D, 0x86DC, 0xB16E, 0x86DD, 0xB16F, 0x86DE, 0xB170, 0x86DF, 0xB171, 0x86E0, 0xB172, 0x86E1, 0xB173, 0x86E2, + 0xB174, 0x86E3, 0xB175, 0x86E4, 0xB176, 0x86E5, 0xB177, 0x86E6, 0xB178, 0xB3EB, 0xB179, 0xB3EC, 0xB17A, 0x86E7, 0xB17B, 0x86E8, + 0xB17C, 0xB3ED, 0xB17D, 0x86E9, 0xB17E, 0x86EA, 0xB17F, 0x86EB, 0xB180, 0xB3EE, 0xB181, 0x86EC, 0xB182, 0xB3EF, 0xB183, 0x86ED, + 0xB184, 0x86EE, 0xB185, 0x86EF, 0xB186, 0x86F0, 0xB187, 0x86F1, 0xB188, 0xB3F0, 0xB189, 0xB3F1, 0xB18A, 0x86F2, 0xB18B, 0xB3F2, + 0xB18C, 0x86F3, 0xB18D, 0xB3F3, 0xB18E, 0x86F4, 0xB18F, 0x86F5, 0xB190, 0x86F6, 0xB191, 0x86F7, 0xB192, 0xB3F4, 0xB193, 0xB3F5, + 0xB194, 0xB3F6, 0xB195, 0x86F8, 0xB196, 0x86F9, 0xB197, 0x86FA, 0xB198, 0xB3F7, 0xB199, 0x86FB, 0xB19A, 0x86FC, 0xB19B, 0x86FD, + 0xB19C, 0xB3F8, 0xB19D, 0x86FE, 0xB19E, 0x8741, 0xB19F, 0x8742, 0xB1A0, 0x8743, 0xB1A1, 0x8744, 0xB1A2, 0x8745, 0xB1A3, 0x8746, + 0xB1A4, 0x8747, 0xB1A5, 0x8748, 0xB1A6, 0x8749, 0xB1A7, 0x874A, 0xB1A8, 0xB3F9, 0xB1A9, 0x874B, 0xB1AA, 0x874C, 0xB1AB, 0x874D, + 0xB1AC, 0x874E, 0xB1AD, 0x874F, 0xB1AE, 0x8750, 0xB1AF, 0x8751, 0xB1B0, 0x8752, 0xB1B1, 0x8753, 0xB1B2, 0x8754, 0xB1B3, 0x8755, + 0xB1B4, 0x8756, 0xB1B5, 0x8757, 0xB1B6, 0x8758, 0xB1B7, 0x8759, 0xB1B8, 0x875A, 0xB1B9, 0x8761, 0xB1BA, 0x8762, 0xB1BB, 0x8763, + 0xB1BC, 0x8764, 0xB1BD, 0x8765, 0xB1BE, 0x8766, 0xB1BF, 0x8767, 0xB1C0, 0x8768, 0xB1C1, 0x8769, 0xB1C2, 0x876A, 0xB1C3, 0x876B, + 0xB1C4, 0x876C, 0xB1C5, 0x876D, 0xB1C6, 0x876E, 0xB1C7, 0x876F, 0xB1C8, 0x8770, 0xB1C9, 0x8771, 0xB1CA, 0x8772, 0xB1CB, 0x8773, + 0xB1CC, 0xB3FA, 0xB1CD, 0x8774, 0xB1CE, 0x8775, 0xB1CF, 0x8776, 0xB1D0, 0xB3FB, 0xB1D1, 0x8777, 0xB1D2, 0x8778, 0xB1D3, 0x8779, + 0xB1D4, 0xB3FC, 0xB1D5, 0x877A, 0xB1D6, 0x8781, 0xB1D7, 0x8782, 0xB1D8, 0x8783, 0xB1D9, 0x8784, 0xB1DA, 0x8785, 0xB1DB, 0x8786, + 0xB1DC, 0xB3FD, 0xB1DD, 0xB3FE, 0xB1DE, 0x8787, 0xB1DF, 0xB4A1, 0xB1E0, 0x8788, 0xB1E1, 0x8789, 0xB1E2, 0x878A, 0xB1E3, 0x878B, + 0xB1E4, 0x878C, 0xB1E5, 0x878D, 0xB1E6, 0x878E, 0xB1E7, 0x878F, 0xB1E8, 0xB4A2, 0xB1E9, 0xB4A3, 0xB1EA, 0x8790, 0xB1EB, 0x8791, + 0xB1EC, 0xB4A4, 0xB1ED, 0x8792, 0xB1EE, 0x8793, 0xB1EF, 0x8794, 0xB1F0, 0xB4A5, 0xB1F1, 0x8795, 0xB1F2, 0x8796, 0xB1F3, 0x8797, + 0xB1F4, 0x8798, 0xB1F5, 0x8799, 0xB1F6, 0x879A, 0xB1F7, 0x879B, 0xB1F8, 0x879C, 0xB1F9, 0xB4A6, 0xB1FA, 0x879D, 0xB1FB, 0xB4A7, + 0xB1FC, 0x879E, 0xB1FD, 0xB4A8, 0xB1FE, 0x879F, 0xB1FF, 0x87A0, 0xB200, 0x87A1, 0xB201, 0x87A2, 0xB202, 0x87A3, 0xB203, 0x87A4, + 0xB204, 0xB4A9, 0xB205, 0xB4AA, 0xB206, 0x87A5, 0xB207, 0x87A6, 0xB208, 0xB4AB, 0xB209, 0x87A7, 0xB20A, 0x87A8, 0xB20B, 0xB4AC, + 0xB20C, 0xB4AD, 0xB20D, 0x87A9, 0xB20E, 0x87AA, 0xB20F, 0x87AB, 0xB210, 0x87AC, 0xB211, 0x87AD, 0xB212, 0x87AE, 0xB213, 0x87AF, + 0xB214, 0xB4AE, 0xB215, 0xB4AF, 0xB216, 0x87B0, 0xB217, 0xB4B0, 0xB218, 0x87B1, 0xB219, 0xB4B1, 0xB21A, 0x87B2, 0xB21B, 0x87B3, + 0xB21C, 0x87B4, 0xB21D, 0x87B5, 0xB21E, 0x87B6, 0xB21F, 0x87B7, 0xB220, 0xB4B2, 0xB221, 0x87B8, 0xB222, 0x87B9, 0xB223, 0x87BA, + 0xB224, 0x87BB, 0xB225, 0x87BC, 0xB226, 0x87BD, 0xB227, 0x87BE, 0xB228, 0x87BF, 0xB229, 0x87C0, 0xB22A, 0x87C1, 0xB22B, 0x87C2, + 0xB22C, 0x87C3, 0xB22D, 0x87C4, 0xB22E, 0x87C5, 0xB22F, 0x87C6, 0xB230, 0x87C7, 0xB231, 0x87C8, 0xB232, 0x87C9, 0xB233, 0x87CA, + 0xB234, 0xB4B3, 0xB235, 0x87CB, 0xB236, 0x87CC, 0xB237, 0x87CD, 0xB238, 0x87CE, 0xB239, 0x87CF, 0xB23A, 0x87D0, 0xB23B, 0x87D1, + 0xB23C, 0xB4B4, 0xB23D, 0x87D2, 0xB23E, 0x87D3, 0xB23F, 0x87D4, 0xB240, 0x87D5, 0xB241, 0x87D6, 0xB242, 0x87D7, 0xB243, 0x87D8, + 0xB244, 0x87D9, 0xB245, 0x87DA, 0xB246, 0x87DB, 0xB247, 0x87DC, 0xB248, 0x87DD, 0xB249, 0x87DE, 0xB24A, 0x87DF, 0xB24B, 0x87E0, + 0xB24C, 0x87E1, 0xB24D, 0x87E2, 0xB24E, 0x87E3, 0xB24F, 0x87E4, 0xB250, 0x87E5, 0xB251, 0x87E6, 0xB252, 0x87E7, 0xB253, 0x87E8, + 0xB254, 0x87E9, 0xB255, 0x87EA, 0xB256, 0x87EB, 0xB257, 0x87EC, 0xB258, 0xB4B5, 0xB259, 0x87ED, 0xB25A, 0x87EE, 0xB25B, 0x87EF, + 0xB25C, 0xB4B6, 0xB25D, 0x87F0, 0xB25E, 0x87F1, 0xB25F, 0x87F2, 0xB260, 0xB4B7, 0xB261, 0x87F3, 0xB262, 0x87F4, 0xB263, 0x87F5, + 0xB264, 0x87F6, 0xB265, 0x87F7, 0xB266, 0x87F8, 0xB267, 0x87F9, 0xB268, 0xB4B8, 0xB269, 0xB4B9, 0xB26A, 0x87FA, 0xB26B, 0x87FB, + 0xB26C, 0x87FC, 0xB26D, 0x87FD, 0xB26E, 0x87FE, 0xB26F, 0x8841, 0xB270, 0x8842, 0xB271, 0x8843, 0xB272, 0x8844, 0xB273, 0x8845, + 0xB274, 0xB4BA, 0xB275, 0xB4BB, 0xB276, 0x8846, 0xB277, 0x8847, 0xB278, 0x8848, 0xB279, 0x8849, 0xB27A, 0x884A, 0xB27B, 0x884B, + 0xB27C, 0xB4BC, 0xB27D, 0x884C, 0xB27E, 0x884D, 0xB27F, 0x884E, 0xB280, 0x884F, 0xB281, 0x8850, 0xB282, 0x8851, 0xB283, 0x8852, + 0xB284, 0xB4BD, 0xB285, 0xB4BE, 0xB286, 0x8853, 0xB287, 0x8854, 0xB288, 0x8855, 0xB289, 0xB4BF, 0xB28A, 0x8856, 0xB28B, 0x8857, + 0xB28C, 0x8858, 0xB28D, 0x8859, 0xB28E, 0x885A, 0xB28F, 0x8861, 0xB290, 0xB4C0, 0xB291, 0xB4C1, 0xB292, 0x8862, 0xB293, 0x8863, + 0xB294, 0xB4C2, 0xB295, 0x8864, 0xB296, 0x8865, 0xB297, 0x8866, 0xB298, 0xB4C3, 0xB299, 0xB4C4, 0xB29A, 0xB4C5, 0xB29B, 0x8867, + 0xB29C, 0x8868, 0xB29D, 0x8869, 0xB29E, 0x886A, 0xB29F, 0x886B, 0xB2A0, 0xB4C6, 0xB2A1, 0xB4C7, 0xB2A2, 0x886C, 0xB2A3, 0xB4C8, + 0xB2A4, 0x886D, 0xB2A5, 0xB4C9, 0xB2A6, 0xB4CA, 0xB2A7, 0x886E, 0xB2A8, 0x886F, 0xB2A9, 0x8870, 0xB2AA, 0xB4CB, 0xB2AB, 0x8871, + 0xB2AC, 0xB4CC, 0xB2AD, 0x8872, 0xB2AE, 0x8873, 0xB2AF, 0x8874, 0xB2B0, 0xB4CD, 0xB2B1, 0x8875, 0xB2B2, 0x8876, 0xB2B3, 0x8877, + 0xB2B4, 0xB4CE, 0xB2B5, 0x8878, 0xB2B6, 0x8879, 0xB2B7, 0x887A, 0xB2B8, 0x8881, 0xB2B9, 0x8882, 0xB2BA, 0x8883, 0xB2BB, 0x8884, + 0xB2BC, 0x8885, 0xB2BD, 0x8886, 0xB2BE, 0x8887, 0xB2BF, 0x8888, 0xB2C0, 0x8889, 0xB2C1, 0x888A, 0xB2C2, 0x888B, 0xB2C3, 0x888C, + 0xB2C4, 0x888D, 0xB2C5, 0x888E, 0xB2C6, 0x888F, 0xB2C7, 0x8890, 0xB2C8, 0xB4CF, 0xB2C9, 0xB4D0, 0xB2CA, 0x8891, 0xB2CB, 0x8892, + 0xB2CC, 0xB4D1, 0xB2CD, 0x8893, 0xB2CE, 0x8894, 0xB2CF, 0x8895, 0xB2D0, 0xB4D2, 0xB2D1, 0x8896, 0xB2D2, 0xB4D3, 0xB2D3, 0x8897, + 0xB2D4, 0x8898, 0xB2D5, 0x8899, 0xB2D6, 0x889A, 0xB2D7, 0x889B, 0xB2D8, 0xB4D4, 0xB2D9, 0xB4D5, 0xB2DA, 0x889C, 0xB2DB, 0xB4D6, + 0xB2DC, 0x889D, 0xB2DD, 0xB4D7, 0xB2DE, 0x889E, 0xB2DF, 0x889F, 0xB2E0, 0x88A0, 0xB2E1, 0x88A1, 0xB2E2, 0xB4D8, 0xB2E3, 0x88A2, + 0xB2E4, 0xB4D9, 0xB2E5, 0xB4DA, 0xB2E6, 0xB4DB, 0xB2E7, 0x88A3, 0xB2E8, 0xB4DC, 0xB2E9, 0x88A4, 0xB2EA, 0x88A5, 0xB2EB, 0xB4DD, + 0xB2EC, 0xB4DE, 0xB2ED, 0xB4DF, 0xB2EE, 0xB4E0, 0xB2EF, 0xB4E1, 0xB2F0, 0x88A6, 0xB2F1, 0x88A7, 0xB2F2, 0x88A8, 0xB2F3, 0xB4E2, + 0xB2F4, 0xB4E3, 0xB2F5, 0xB4E4, 0xB2F6, 0x88A9, 0xB2F7, 0xB4E5, 0xB2F8, 0xB4E6, 0xB2F9, 0xB4E7, 0xB2FA, 0xB4E8, 0xB2FB, 0xB4E9, + 0xB2FC, 0x88AA, 0xB2FD, 0x88AB, 0xB2FE, 0x88AC, 0xB2FF, 0xB4EA, 0xB300, 0xB4EB, 0xB301, 0xB4EC, 0xB302, 0x88AD, 0xB303, 0x88AE, + 0xB304, 0xB4ED, 0xB305, 0x88AF, 0xB306, 0x88B0, 0xB307, 0x88B1, 0xB308, 0xB4EE, 0xB309, 0x88B2, 0xB30A, 0x88B3, 0xB30B, 0x88B4, + 0xB30C, 0x88B5, 0xB30D, 0x88B6, 0xB30E, 0x88B7, 0xB30F, 0x88B8, 0xB310, 0xB4EF, 0xB311, 0xB4F0, 0xB312, 0x88B9, 0xB313, 0xB4F1, + 0xB314, 0xB4F2, 0xB315, 0xB4F3, 0xB316, 0x88BA, 0xB317, 0x88BB, 0xB318, 0x88BC, 0xB319, 0x88BD, 0xB31A, 0x88BE, 0xB31B, 0x88BF, + 0xB31C, 0xB4F4, 0xB31D, 0x88C0, 0xB31E, 0x88C1, 0xB31F, 0x88C2, 0xB320, 0x88C3, 0xB321, 0x88C4, 0xB322, 0x88C5, 0xB323, 0x88C6, + 0xB324, 0x88C7, 0xB325, 0x88C8, 0xB326, 0x88C9, 0xB327, 0x88CA, 0xB328, 0x88CB, 0xB329, 0x88CC, 0xB32A, 0x88CD, 0xB32B, 0x88CE, + 0xB32C, 0x88CF, 0xB32D, 0x88D0, 0xB32E, 0x88D1, 0xB32F, 0x88D2, 0xB330, 0x88D3, 0xB331, 0x88D4, 0xB332, 0x88D5, 0xB333, 0x88D6, + 0xB334, 0x88D7, 0xB335, 0x88D8, 0xB336, 0x88D9, 0xB337, 0x88DA, 0xB338, 0x88DB, 0xB339, 0x88DC, 0xB33A, 0x88DD, 0xB33B, 0x88DE, + 0xB33C, 0x88DF, 0xB33D, 0x88E0, 0xB33E, 0x88E1, 0xB33F, 0x88E2, 0xB340, 0x88E3, 0xB341, 0x88E4, 0xB342, 0x88E5, 0xB343, 0x88E6, + 0xB344, 0x88E7, 0xB345, 0x88E8, 0xB346, 0x88E9, 0xB347, 0x88EA, 0xB348, 0x88EB, 0xB349, 0x88EC, 0xB34A, 0x88ED, 0xB34B, 0x88EE, + 0xB34C, 0x88EF, 0xB34D, 0x88F0, 0xB34E, 0x88F1, 0xB34F, 0x88F2, 0xB350, 0x88F3, 0xB351, 0x88F4, 0xB352, 0x88F5, 0xB353, 0x88F6, + 0xB354, 0xB4F5, 0xB355, 0xB4F6, 0xB356, 0xB4F7, 0xB357, 0x88F7, 0xB358, 0xB4F8, 0xB359, 0x88F8, 0xB35A, 0x88F9, 0xB35B, 0xB4F9, + 0xB35C, 0xB4FA, 0xB35D, 0x88FA, 0xB35E, 0xB4FB, 0xB35F, 0xB4FC, 0xB360, 0x88FB, 0xB361, 0x88FC, 0xB362, 0x88FD, 0xB363, 0x88FE, + 0xB364, 0xB4FD, 0xB365, 0xB4FE, 0xB366, 0x8941, 0xB367, 0xB5A1, 0xB368, 0x8942, 0xB369, 0xB5A2, 0xB36A, 0x8943, 0xB36B, 0xB5A3, + 0xB36C, 0x8944, 0xB36D, 0x8945, 0xB36E, 0xB5A4, 0xB36F, 0x8946, 0xB370, 0xB5A5, 0xB371, 0xB5A6, 0xB372, 0x8947, 0xB373, 0x8948, + 0xB374, 0xB5A7, 0xB375, 0x8949, 0xB376, 0x894A, 0xB377, 0x894B, 0xB378, 0xB5A8, 0xB379, 0x894C, 0xB37A, 0x894D, 0xB37B, 0x894E, + 0xB37C, 0x894F, 0xB37D, 0x8950, 0xB37E, 0x8951, 0xB37F, 0x8952, 0xB380, 0xB5A9, 0xB381, 0xB5AA, 0xB382, 0x8953, 0xB383, 0xB5AB, + 0xB384, 0xB5AC, 0xB385, 0xB5AD, 0xB386, 0x8954, 0xB387, 0x8955, 0xB388, 0x8956, 0xB389, 0x8957, 0xB38A, 0x8958, 0xB38B, 0x8959, + 0xB38C, 0xB5AE, 0xB38D, 0x895A, 0xB38E, 0x8961, 0xB38F, 0x8962, 0xB390, 0xB5AF, 0xB391, 0x8963, 0xB392, 0x8964, 0xB393, 0x8965, + 0xB394, 0xB5B0, 0xB395, 0x8966, 0xB396, 0x8967, 0xB397, 0x8968, 0xB398, 0x8969, 0xB399, 0x896A, 0xB39A, 0x896B, 0xB39B, 0x896C, + 0xB39C, 0x896D, 0xB39D, 0x896E, 0xB39E, 0x896F, 0xB39F, 0x8970, 0xB3A0, 0xB5B1, 0xB3A1, 0xB5B2, 0xB3A2, 0x8971, 0xB3A3, 0x8972, + 0xB3A4, 0x8973, 0xB3A5, 0x8974, 0xB3A6, 0x8975, 0xB3A7, 0x8976, 0xB3A8, 0xB5B3, 0xB3A9, 0x8977, 0xB3AA, 0x8978, 0xB3AB, 0x8979, + 0xB3AC, 0xB5B4, 0xB3AD, 0x897A, 0xB3AE, 0x8981, 0xB3AF, 0x8982, 0xB3B0, 0x8983, 0xB3B1, 0x8984, 0xB3B2, 0x8985, 0xB3B3, 0x8986, + 0xB3B4, 0x8987, 0xB3B5, 0x8988, 0xB3B6, 0x8989, 0xB3B7, 0x898A, 0xB3B8, 0x898B, 0xB3B9, 0x898C, 0xB3BA, 0x898D, 0xB3BB, 0x898E, + 0xB3BC, 0x898F, 0xB3BD, 0x8990, 0xB3BE, 0x8991, 0xB3BF, 0x8992, 0xB3C0, 0x8993, 0xB3C1, 0x8994, 0xB3C2, 0x8995, 0xB3C3, 0x8996, + 0xB3C4, 0xB5B5, 0xB3C5, 0xB5B6, 0xB3C6, 0x8997, 0xB3C7, 0x8998, 0xB3C8, 0xB5B7, 0xB3C9, 0x8999, 0xB3CA, 0x899A, 0xB3CB, 0xB5B8, + 0xB3CC, 0xB5B9, 0xB3CD, 0x899B, 0xB3CE, 0xB5BA, 0xB3CF, 0x899C, 0xB3D0, 0xB5BB, 0xB3D1, 0x899D, 0xB3D2, 0x899E, 0xB3D3, 0x899F, + 0xB3D4, 0xB5BC, 0xB3D5, 0xB5BD, 0xB3D6, 0x89A0, 0xB3D7, 0xB5BE, 0xB3D8, 0x89A1, 0xB3D9, 0xB5BF, 0xB3DA, 0x89A2, 0xB3DB, 0xB5C0, + 0xB3DC, 0x89A3, 0xB3DD, 0xB5C1, 0xB3DE, 0x89A4, 0xB3DF, 0x89A5, 0xB3E0, 0xB5C2, 0xB3E1, 0x89A6, 0xB3E2, 0x89A7, 0xB3E3, 0x89A8, + 0xB3E4, 0xB5C3, 0xB3E5, 0x89A9, 0xB3E6, 0x89AA, 0xB3E7, 0x89AB, 0xB3E8, 0xB5C4, 0xB3E9, 0x89AC, 0xB3EA, 0x89AD, 0xB3EB, 0x89AE, + 0xB3EC, 0x89AF, 0xB3ED, 0x89B0, 0xB3EE, 0x89B1, 0xB3EF, 0x89B2, 0xB3F0, 0x89B3, 0xB3F1, 0x89B4, 0xB3F2, 0x89B5, 0xB3F3, 0x89B6, + 0xB3F4, 0x89B7, 0xB3F5, 0x89B8, 0xB3F6, 0x89B9, 0xB3F7, 0x89BA, 0xB3F8, 0x89BB, 0xB3F9, 0x89BC, 0xB3FA, 0x89BD, 0xB3FB, 0x89BE, + 0xB3FC, 0xB5C5, 0xB3FD, 0x89BF, 0xB3FE, 0x89C0, 0xB3FF, 0x89C1, 0xB400, 0x89C2, 0xB401, 0x89C3, 0xB402, 0x89C4, 0xB403, 0x89C5, + 0xB404, 0x89C6, 0xB405, 0x89C7, 0xB406, 0x89C8, 0xB407, 0x89C9, 0xB408, 0x89CA, 0xB409, 0x89CB, 0xB40A, 0x89CC, 0xB40B, 0x89CD, + 0xB40C, 0x89CE, 0xB40D, 0x89CF, 0xB40E, 0x89D0, 0xB40F, 0x89D1, 0xB410, 0xB5C6, 0xB411, 0x89D2, 0xB412, 0x89D3, 0xB413, 0x89D4, + 0xB414, 0x89D5, 0xB415, 0x89D6, 0xB416, 0x89D7, 0xB417, 0x89D8, 0xB418, 0xB5C7, 0xB419, 0x89D9, 0xB41A, 0x89DA, 0xB41B, 0x89DB, + 0xB41C, 0xB5C8, 0xB41D, 0x89DC, 0xB41E, 0x89DD, 0xB41F, 0x89DE, 0xB420, 0xB5C9, 0xB421, 0x89DF, 0xB422, 0x89E0, 0xB423, 0x89E1, + 0xB424, 0x89E2, 0xB425, 0x89E3, 0xB426, 0x89E4, 0xB427, 0x89E5, 0xB428, 0xB5CA, 0xB429, 0xB5CB, 0xB42A, 0x89E6, 0xB42B, 0xB5CC, + 0xB42C, 0x89E7, 0xB42D, 0x89E8, 0xB42E, 0x89E9, 0xB42F, 0x89EA, 0xB430, 0x89EB, 0xB431, 0x89EC, 0xB432, 0x89ED, 0xB433, 0x89EE, + 0xB434, 0xB5CD, 0xB435, 0x89EF, 0xB436, 0x89F0, 0xB437, 0x89F1, 0xB438, 0x89F2, 0xB439, 0x89F3, 0xB43A, 0x89F4, 0xB43B, 0x89F5, + 0xB43C, 0x89F6, 0xB43D, 0x89F7, 0xB43E, 0x89F8, 0xB43F, 0x89F9, 0xB440, 0x89FA, 0xB441, 0x89FB, 0xB442, 0x89FC, 0xB443, 0x89FD, + 0xB444, 0x89FE, 0xB445, 0x8A41, 0xB446, 0x8A42, 0xB447, 0x8A43, 0xB448, 0x8A44, 0xB449, 0x8A45, 0xB44A, 0x8A46, 0xB44B, 0x8A47, + 0xB44C, 0x8A48, 0xB44D, 0x8A49, 0xB44E, 0x8A4A, 0xB44F, 0x8A4B, 0xB450, 0xB5CE, 0xB451, 0xB5CF, 0xB452, 0x8A4C, 0xB453, 0x8A4D, + 0xB454, 0xB5D0, 0xB455, 0x8A4E, 0xB456, 0x8A4F, 0xB457, 0x8A50, 0xB458, 0xB5D1, 0xB459, 0x8A51, 0xB45A, 0x8A52, 0xB45B, 0x8A53, + 0xB45C, 0x8A54, 0xB45D, 0x8A55, 0xB45E, 0x8A56, 0xB45F, 0x8A57, 0xB460, 0xB5D2, 0xB461, 0xB5D3, 0xB462, 0x8A58, 0xB463, 0xB5D4, + 0xB464, 0x8A59, 0xB465, 0xB5D5, 0xB466, 0x8A5A, 0xB467, 0x8A61, 0xB468, 0x8A62, 0xB469, 0x8A63, 0xB46A, 0x8A64, 0xB46B, 0x8A65, + 0xB46C, 0xB5D6, 0xB46D, 0x8A66, 0xB46E, 0x8A67, 0xB46F, 0x8A68, 0xB470, 0x8A69, 0xB471, 0x8A6A, 0xB472, 0x8A6B, 0xB473, 0x8A6C, + 0xB474, 0x8A6D, 0xB475, 0x8A6E, 0xB476, 0x8A6F, 0xB477, 0x8A70, 0xB478, 0x8A71, 0xB479, 0x8A72, 0xB47A, 0x8A73, 0xB47B, 0x8A74, + 0xB47C, 0x8A75, 0xB47D, 0x8A76, 0xB47E, 0x8A77, 0xB47F, 0x8A78, 0xB480, 0xB5D7, 0xB481, 0x8A79, 0xB482, 0x8A7A, 0xB483, 0x8A81, + 0xB484, 0x8A82, 0xB485, 0x8A83, 0xB486, 0x8A84, 0xB487, 0x8A85, 0xB488, 0xB5D8, 0xB489, 0x8A86, 0xB48A, 0x8A87, 0xB48B, 0x8A88, + 0xB48C, 0x8A89, 0xB48D, 0x8A8A, 0xB48E, 0x8A8B, 0xB48F, 0x8A8C, 0xB490, 0x8A8D, 0xB491, 0x8A8E, 0xB492, 0x8A8F, 0xB493, 0x8A90, + 0xB494, 0x8A91, 0xB495, 0x8A92, 0xB496, 0x8A93, 0xB497, 0x8A94, 0xB498, 0x8A95, 0xB499, 0x8A96, 0xB49A, 0x8A97, 0xB49B, 0x8A98, + 0xB49C, 0x8A99, 0xB49D, 0xB5D9, 0xB49E, 0x8A9A, 0xB49F, 0x8A9B, 0xB4A0, 0x8A9C, 0xB4A1, 0x8A9D, 0xB4A2, 0x8A9E, 0xB4A3, 0x8A9F, + 0xB4A4, 0xB5DA, 0xB4A5, 0x8AA0, 0xB4A6, 0x8AA1, 0xB4A7, 0x8AA2, 0xB4A8, 0xB5DB, 0xB4A9, 0x8AA3, 0xB4AA, 0x8AA4, 0xB4AB, 0x8AA5, + 0xB4AC, 0xB5DC, 0xB4AD, 0x8AA6, 0xB4AE, 0x8AA7, 0xB4AF, 0x8AA8, 0xB4B0, 0x8AA9, 0xB4B1, 0x8AAA, 0xB4B2, 0x8AAB, 0xB4B3, 0x8AAC, + 0xB4B4, 0x8AAD, 0xB4B5, 0xB5DD, 0xB4B6, 0x8AAE, 0xB4B7, 0xB5DE, 0xB4B8, 0x8AAF, 0xB4B9, 0xB5DF, 0xB4BA, 0x8AB0, 0xB4BB, 0x8AB1, + 0xB4BC, 0x8AB2, 0xB4BD, 0x8AB3, 0xB4BE, 0x8AB4, 0xB4BF, 0x8AB5, 0xB4C0, 0xB5E0, 0xB4C1, 0x8AB6, 0xB4C2, 0x8AB7, 0xB4C3, 0x8AB8, + 0xB4C4, 0xB5E1, 0xB4C5, 0x8AB9, 0xB4C6, 0x8ABA, 0xB4C7, 0x8ABB, 0xB4C8, 0xB5E2, 0xB4C9, 0x8ABC, 0xB4CA, 0x8ABD, 0xB4CB, 0x8ABE, + 0xB4CC, 0x8ABF, 0xB4CD, 0x8AC0, 0xB4CE, 0x8AC1, 0xB4CF, 0x8AC2, 0xB4D0, 0xB5E3, 0xB4D1, 0x8AC3, 0xB4D2, 0x8AC4, 0xB4D3, 0x8AC5, + 0xB4D4, 0x8AC6, 0xB4D5, 0xB5E4, 0xB4D6, 0x8AC7, 0xB4D7, 0x8AC8, 0xB4D8, 0x8AC9, 0xB4D9, 0x8ACA, 0xB4DA, 0x8ACB, 0xB4DB, 0x8ACC, + 0xB4DC, 0xB5E5, 0xB4DD, 0xB5E6, 0xB4DE, 0x8ACD, 0xB4DF, 0x8ACE, 0xB4E0, 0xB5E7, 0xB4E1, 0x8ACF, 0xB4E2, 0x8AD0, 0xB4E3, 0xB5E8, + 0xB4E4, 0xB5E9, 0xB4E5, 0x8AD1, 0xB4E6, 0xB5EA, 0xB4E7, 0x8AD2, 0xB4E8, 0x8AD3, 0xB4E9, 0x8AD4, 0xB4EA, 0x8AD5, 0xB4EB, 0x8AD6, + 0xB4EC, 0xB5EB, 0xB4ED, 0xB5EC, 0xB4EE, 0x8AD7, 0xB4EF, 0xB5ED, 0xB4F0, 0x8AD8, 0xB4F1, 0xB5EE, 0xB4F2, 0x8AD9, 0xB4F3, 0x8ADA, + 0xB4F4, 0x8ADB, 0xB4F5, 0x8ADC, 0xB4F6, 0x8ADD, 0xB4F7, 0x8ADE, 0xB4F8, 0xB5EF, 0xB4F9, 0x8ADF, 0xB4FA, 0x8AE0, 0xB4FB, 0x8AE1, + 0xB4FC, 0x8AE2, 0xB4FD, 0x8AE3, 0xB4FE, 0x8AE4, 0xB4FF, 0x8AE5, 0xB500, 0x8AE6, 0xB501, 0x8AE7, 0xB502, 0x8AE8, 0xB503, 0x8AE9, + 0xB504, 0x8AEA, 0xB505, 0x8AEB, 0xB506, 0x8AEC, 0xB507, 0x8AED, 0xB508, 0x8AEE, 0xB509, 0x8AEF, 0xB50A, 0x8AF0, 0xB50B, 0x8AF1, + 0xB50C, 0x8AF2, 0xB50D, 0x8AF3, 0xB50E, 0x8AF4, 0xB50F, 0x8AF5, 0xB510, 0x8AF6, 0xB511, 0x8AF7, 0xB512, 0x8AF8, 0xB513, 0x8AF9, + 0xB514, 0xB5F0, 0xB515, 0xB5F1, 0xB516, 0x8AFA, 0xB517, 0x8AFB, 0xB518, 0xB5F2, 0xB519, 0x8AFC, 0xB51A, 0x8AFD, 0xB51B, 0xB5F3, + 0xB51C, 0xB5F4, 0xB51D, 0x8AFE, 0xB51E, 0x8B41, 0xB51F, 0x8B42, 0xB520, 0x8B43, 0xB521, 0x8B44, 0xB522, 0x8B45, 0xB523, 0x8B46, + 0xB524, 0xB5F5, 0xB525, 0xB5F6, 0xB526, 0x8B47, 0xB527, 0xB5F7, 0xB528, 0xB5F8, 0xB529, 0xB5F9, 0xB52A, 0xB5FA, 0xB52B, 0x8B48, + 0xB52C, 0x8B49, 0xB52D, 0x8B4A, 0xB52E, 0x8B4B, 0xB52F, 0x8B4C, 0xB530, 0xB5FB, 0xB531, 0xB5FC, 0xB532, 0x8B4D, 0xB533, 0x8B4E, + 0xB534, 0xB5FD, 0xB535, 0x8B4F, 0xB536, 0x8B50, 0xB537, 0x8B51, 0xB538, 0xB5FE, 0xB539, 0x8B52, 0xB53A, 0x8B53, 0xB53B, 0x8B54, + 0xB53C, 0x8B55, 0xB53D, 0x8B56, 0xB53E, 0x8B57, 0xB53F, 0x8B58, 0xB540, 0xB6A1, 0xB541, 0xB6A2, 0xB542, 0x8B59, 0xB543, 0xB6A3, + 0xB544, 0xB6A4, 0xB545, 0xB6A5, 0xB546, 0x8B5A, 0xB547, 0x8B61, 0xB548, 0x8B62, 0xB549, 0x8B63, 0xB54A, 0x8B64, 0xB54B, 0xB6A6, + 0xB54C, 0xB6A7, 0xB54D, 0xB6A8, 0xB54E, 0x8B65, 0xB54F, 0x8B66, 0xB550, 0xB6A9, 0xB551, 0x8B67, 0xB552, 0x8B68, 0xB553, 0x8B69, + 0xB554, 0xB6AA, 0xB555, 0x8B6A, 0xB556, 0x8B6B, 0xB557, 0x8B6C, 0xB558, 0x8B6D, 0xB559, 0x8B6E, 0xB55A, 0x8B6F, 0xB55B, 0x8B70, + 0xB55C, 0xB6AB, 0xB55D, 0xB6AC, 0xB55E, 0x8B71, 0xB55F, 0xB6AD, 0xB560, 0xB6AE, 0xB561, 0xB6AF, 0xB562, 0x8B72, 0xB563, 0x8B73, + 0xB564, 0x8B74, 0xB565, 0x8B75, 0xB566, 0x8B76, 0xB567, 0x8B77, 0xB568, 0x8B78, 0xB569, 0x8B79, 0xB56A, 0x8B7A, 0xB56B, 0x8B81, + 0xB56C, 0x8B82, 0xB56D, 0x8B83, 0xB56E, 0x8B84, 0xB56F, 0x8B85, 0xB570, 0x8B86, 0xB571, 0x8B87, 0xB572, 0x8B88, 0xB573, 0x8B89, + 0xB574, 0x8B8A, 0xB575, 0x8B8B, 0xB576, 0x8B8C, 0xB577, 0x8B8D, 0xB578, 0x8B8E, 0xB579, 0x8B8F, 0xB57A, 0x8B90, 0xB57B, 0x8B91, + 0xB57C, 0x8B92, 0xB57D, 0x8B93, 0xB57E, 0x8B94, 0xB57F, 0x8B95, 0xB580, 0x8B96, 0xB581, 0x8B97, 0xB582, 0x8B98, 0xB583, 0x8B99, + 0xB584, 0x8B9A, 0xB585, 0x8B9B, 0xB586, 0x8B9C, 0xB587, 0x8B9D, 0xB588, 0x8B9E, 0xB589, 0x8B9F, 0xB58A, 0x8BA0, 0xB58B, 0x8BA1, + 0xB58C, 0x8BA2, 0xB58D, 0x8BA3, 0xB58E, 0x8BA4, 0xB58F, 0x8BA5, 0xB590, 0x8BA6, 0xB591, 0x8BA7, 0xB592, 0x8BA8, 0xB593, 0x8BA9, + 0xB594, 0x8BAA, 0xB595, 0x8BAB, 0xB596, 0x8BAC, 0xB597, 0x8BAD, 0xB598, 0x8BAE, 0xB599, 0x8BAF, 0xB59A, 0x8BB0, 0xB59B, 0x8BB1, + 0xB59C, 0x8BB2, 0xB59D, 0x8BB3, 0xB59E, 0x8BB4, 0xB59F, 0x8BB5, 0xB5A0, 0xB6B0, 0xB5A1, 0xB6B1, 0xB5A2, 0x8BB6, 0xB5A3, 0x8BB7, + 0xB5A4, 0xB6B2, 0xB5A5, 0x8BB8, 0xB5A6, 0x8BB9, 0xB5A7, 0x8BBA, 0xB5A8, 0xB6B3, 0xB5A9, 0x8BBB, 0xB5AA, 0xB6B4, 0xB5AB, 0xB6B5, + 0xB5AC, 0x8BBC, 0xB5AD, 0x8BBD, 0xB5AE, 0x8BBE, 0xB5AF, 0x8BBF, 0xB5B0, 0xB6B6, 0xB5B1, 0xB6B7, 0xB5B2, 0x8BC0, 0xB5B3, 0xB6B8, + 0xB5B4, 0xB6B9, 0xB5B5, 0xB6BA, 0xB5B6, 0x8BC1, 0xB5B7, 0x8BC2, 0xB5B8, 0x8BC3, 0xB5B9, 0x8BC4, 0xB5BA, 0x8BC5, 0xB5BB, 0xB6BB, + 0xB5BC, 0xB6BC, 0xB5BD, 0xB6BD, 0xB5BE, 0x8BC6, 0xB5BF, 0x8BC7, 0xB5C0, 0xB6BE, 0xB5C1, 0x8BC8, 0xB5C2, 0x8BC9, 0xB5C3, 0x8BCA, + 0xB5C4, 0xB6BF, 0xB5C5, 0x8BCB, 0xB5C6, 0x8BCC, 0xB5C7, 0x8BCD, 0xB5C8, 0x8BCE, 0xB5C9, 0x8BCF, 0xB5CA, 0x8BD0, 0xB5CB, 0x8BD1, + 0xB5CC, 0xB6C0, 0xB5CD, 0xB6C1, 0xB5CE, 0x8BD2, 0xB5CF, 0xB6C2, 0xB5D0, 0xB6C3, 0xB5D1, 0xB6C4, 0xB5D2, 0x8BD3, 0xB5D3, 0x8BD4, + 0xB5D4, 0x8BD5, 0xB5D5, 0x8BD6, 0xB5D6, 0x8BD7, 0xB5D7, 0x8BD8, 0xB5D8, 0xB6C5, 0xB5D9, 0x8BD9, 0xB5DA, 0x8BDA, 0xB5DB, 0x8BDB, + 0xB5DC, 0x8BDC, 0xB5DD, 0x8BDD, 0xB5DE, 0x8BDE, 0xB5DF, 0x8BDF, 0xB5E0, 0x8BE0, 0xB5E1, 0x8BE1, 0xB5E2, 0x8BE2, 0xB5E3, 0x8BE3, + 0xB5E4, 0x8BE4, 0xB5E5, 0x8BE5, 0xB5E6, 0x8BE6, 0xB5E7, 0x8BE7, 0xB5E8, 0x8BE8, 0xB5E9, 0x8BE9, 0xB5EA, 0x8BEA, 0xB5EB, 0x8BEB, + 0xB5EC, 0xB6C6, 0xB5ED, 0x8BEC, 0xB5EE, 0x8BED, 0xB5EF, 0x8BEE, 0xB5F0, 0x8BEF, 0xB5F1, 0x8BF0, 0xB5F2, 0x8BF1, 0xB5F3, 0x8BF2, + 0xB5F4, 0x8BF3, 0xB5F5, 0x8BF4, 0xB5F6, 0x8BF5, 0xB5F7, 0x8BF6, 0xB5F8, 0x8BF7, 0xB5F9, 0x8BF8, 0xB5FA, 0x8BF9, 0xB5FB, 0x8BFA, + 0xB5FC, 0x8BFB, 0xB5FD, 0x8BFC, 0xB5FE, 0x8BFD, 0xB5FF, 0x8BFE, 0xB600, 0x8C41, 0xB601, 0x8C42, 0xB602, 0x8C43, 0xB603, 0x8C44, + 0xB604, 0x8C45, 0xB605, 0x8C46, 0xB606, 0x8C47, 0xB607, 0x8C48, 0xB608, 0x8C49, 0xB609, 0x8C4A, 0xB60A, 0x8C4B, 0xB60B, 0x8C4C, + 0xB60C, 0x8C4D, 0xB60D, 0x8C4E, 0xB60E, 0x8C4F, 0xB60F, 0x8C50, 0xB610, 0xB6C7, 0xB611, 0xB6C8, 0xB612, 0x8C51, 0xB613, 0x8C52, + 0xB614, 0xB6C9, 0xB615, 0x8C53, 0xB616, 0x8C54, 0xB617, 0x8C55, 0xB618, 0xB6CA, 0xB619, 0x8C56, 0xB61A, 0x8C57, 0xB61B, 0x8C58, + 0xB61C, 0x8C59, 0xB61D, 0x8C5A, 0xB61E, 0x8C61, 0xB61F, 0x8C62, 0xB620, 0x8C63, 0xB621, 0x8C64, 0xB622, 0x8C65, 0xB623, 0x8C66, + 0xB624, 0x8C67, 0xB625, 0xB6CB, 0xB626, 0x8C68, 0xB627, 0x8C69, 0xB628, 0x8C6A, 0xB629, 0x8C6B, 0xB62A, 0x8C6C, 0xB62B, 0x8C6D, + 0xB62C, 0xB6CC, 0xB62D, 0x8C6E, 0xB62E, 0x8C6F, 0xB62F, 0x8C70, 0xB630, 0x8C71, 0xB631, 0x8C72, 0xB632, 0x8C73, 0xB633, 0x8C74, + 0xB634, 0xB6CD, 0xB635, 0x8C75, 0xB636, 0x8C76, 0xB637, 0x8C77, 0xB638, 0x8C78, 0xB639, 0x8C79, 0xB63A, 0x8C7A, 0xB63B, 0x8C81, + 0xB63C, 0x8C82, 0xB63D, 0x8C83, 0xB63E, 0x8C84, 0xB63F, 0x8C85, 0xB640, 0x8C86, 0xB641, 0x8C87, 0xB642, 0x8C88, 0xB643, 0x8C89, + 0xB644, 0x8C8A, 0xB645, 0x8C8B, 0xB646, 0x8C8C, 0xB647, 0x8C8D, 0xB648, 0xB6CE, 0xB649, 0x8C8E, 0xB64A, 0x8C8F, 0xB64B, 0x8C90, + 0xB64C, 0x8C91, 0xB64D, 0x8C92, 0xB64E, 0x8C93, 0xB64F, 0x8C94, 0xB650, 0x8C95, 0xB651, 0x8C96, 0xB652, 0x8C97, 0xB653, 0x8C98, + 0xB654, 0x8C99, 0xB655, 0x8C9A, 0xB656, 0x8C9B, 0xB657, 0x8C9C, 0xB658, 0x8C9D, 0xB659, 0x8C9E, 0xB65A, 0x8C9F, 0xB65B, 0x8CA0, + 0xB65C, 0x8CA1, 0xB65D, 0x8CA2, 0xB65E, 0x8CA3, 0xB65F, 0x8CA4, 0xB660, 0x8CA5, 0xB661, 0x8CA6, 0xB662, 0x8CA7, 0xB663, 0x8CA8, + 0xB664, 0xB6CF, 0xB665, 0x8CA9, 0xB666, 0x8CAA, 0xB667, 0x8CAB, 0xB668, 0xB6D0, 0xB669, 0x8CAC, 0xB66A, 0x8CAD, 0xB66B, 0x8CAE, + 0xB66C, 0x8CAF, 0xB66D, 0x8CB0, 0xB66E, 0x8CB1, 0xB66F, 0x8CB2, 0xB670, 0x8CB3, 0xB671, 0x8CB4, 0xB672, 0x8CB5, 0xB673, 0x8CB6, + 0xB674, 0x8CB7, 0xB675, 0x8CB8, 0xB676, 0x8CB9, 0xB677, 0x8CBA, 0xB678, 0x8CBB, 0xB679, 0x8CBC, 0xB67A, 0x8CBD, 0xB67B, 0x8CBE, + 0xB67C, 0x8CBF, 0xB67D, 0x8CC0, 0xB67E, 0x8CC1, 0xB67F, 0x8CC2, 0xB680, 0x8CC3, 0xB681, 0x8CC4, 0xB682, 0x8CC5, 0xB683, 0x8CC6, + 0xB684, 0x8CC7, 0xB685, 0x8CC8, 0xB686, 0x8CC9, 0xB687, 0x8CCA, 0xB688, 0x8CCB, 0xB689, 0x8CCC, 0xB68A, 0x8CCD, 0xB68B, 0x8CCE, + 0xB68C, 0x8CCF, 0xB68D, 0x8CD0, 0xB68E, 0x8CD1, 0xB68F, 0x8CD2, 0xB690, 0x8CD3, 0xB691, 0x8CD4, 0xB692, 0x8CD5, 0xB693, 0x8CD6, + 0xB694, 0x8CD7, 0xB695, 0x8CD8, 0xB696, 0x8CD9, 0xB697, 0x8CDA, 0xB698, 0x8CDB, 0xB699, 0x8CDC, 0xB69A, 0x8CDD, 0xB69B, 0x8CDE, + 0xB69C, 0xB6D1, 0xB69D, 0xB6D2, 0xB69E, 0x8CDF, 0xB69F, 0x8CE0, 0xB6A0, 0xB6D3, 0xB6A1, 0x8CE1, 0xB6A2, 0x8CE2, 0xB6A3, 0x8CE3, + 0xB6A4, 0xB6D4, 0xB6A5, 0x8CE4, 0xB6A6, 0x8CE5, 0xB6A7, 0x8CE6, 0xB6A8, 0x8CE7, 0xB6A9, 0x8CE8, 0xB6AA, 0x8CE9, 0xB6AB, 0xB6D5, + 0xB6AC, 0xB6D6, 0xB6AD, 0x8CEA, 0xB6AE, 0x8CEB, 0xB6AF, 0x8CEC, 0xB6B0, 0x8CED, 0xB6B1, 0xB6D7, 0xB6B2, 0x8CEE, 0xB6B3, 0x8CEF, + 0xB6B4, 0x8CF0, 0xB6B5, 0x8CF1, 0xB6B6, 0x8CF2, 0xB6B7, 0x8CF3, 0xB6B8, 0x8CF4, 0xB6B9, 0x8CF5, 0xB6BA, 0x8CF6, 0xB6BB, 0x8CF7, + 0xB6BC, 0x8CF8, 0xB6BD, 0x8CF9, 0xB6BE, 0x8CFA, 0xB6BF, 0x8CFB, 0xB6C0, 0x8CFC, 0xB6C1, 0x8CFD, 0xB6C2, 0x8CFE, 0xB6C3, 0x8D41, + 0xB6C4, 0x8D42, 0xB6C5, 0x8D43, 0xB6C6, 0x8D44, 0xB6C7, 0x8D45, 0xB6C8, 0x8D46, 0xB6C9, 0x8D47, 0xB6CA, 0x8D48, 0xB6CB, 0x8D49, + 0xB6CC, 0x8D4A, 0xB6CD, 0x8D4B, 0xB6CE, 0x8D4C, 0xB6CF, 0x8D4D, 0xB6D0, 0x8D4E, 0xB6D1, 0x8D4F, 0xB6D2, 0x8D50, 0xB6D3, 0x8D51, + 0xB6D4, 0xB6D8, 0xB6D5, 0x8D52, 0xB6D6, 0x8D53, 0xB6D7, 0x8D54, 0xB6D8, 0x8D55, 0xB6D9, 0x8D56, 0xB6DA, 0x8D57, 0xB6DB, 0x8D58, + 0xB6DC, 0x8D59, 0xB6DD, 0x8D5A, 0xB6DE, 0x8D61, 0xB6DF, 0x8D62, 0xB6E0, 0x8D63, 0xB6E1, 0x8D64, 0xB6E2, 0x8D65, 0xB6E3, 0x8D66, + 0xB6E4, 0x8D67, 0xB6E5, 0x8D68, 0xB6E6, 0x8D69, 0xB6E7, 0x8D6A, 0xB6E8, 0x8D6B, 0xB6E9, 0x8D6C, 0xB6EA, 0x8D6D, 0xB6EB, 0x8D6E, + 0xB6EC, 0x8D6F, 0xB6ED, 0x8D70, 0xB6EE, 0x8D71, 0xB6EF, 0x8D72, 0xB6F0, 0xB6D9, 0xB6F1, 0x8D73, 0xB6F2, 0x8D74, 0xB6F3, 0x8D75, + 0xB6F4, 0xB6DA, 0xB6F5, 0x8D76, 0xB6F6, 0x8D77, 0xB6F7, 0x8D78, 0xB6F8, 0xB6DB, 0xB6F9, 0x8D79, 0xB6FA, 0x8D7A, 0xB6FB, 0x8D81, + 0xB6FC, 0x8D82, 0xB6FD, 0x8D83, 0xB6FE, 0x8D84, 0xB6FF, 0x8D85, 0xB700, 0xB6DC, 0xB701, 0xB6DD, 0xB702, 0x8D86, 0xB703, 0x8D87, + 0xB704, 0x8D88, 0xB705, 0xB6DE, 0xB706, 0x8D89, 0xB707, 0x8D8A, 0xB708, 0x8D8B, 0xB709, 0x8D8C, 0xB70A, 0x8D8D, 0xB70B, 0x8D8E, + 0xB70C, 0x8D8F, 0xB70D, 0x8D90, 0xB70E, 0x8D91, 0xB70F, 0x8D92, 0xB710, 0x8D93, 0xB711, 0x8D94, 0xB712, 0x8D95, 0xB713, 0x8D96, + 0xB714, 0x8D97, 0xB715, 0x8D98, 0xB716, 0x8D99, 0xB717, 0x8D9A, 0xB718, 0x8D9B, 0xB719, 0x8D9C, 0xB71A, 0x8D9D, 0xB71B, 0x8D9E, + 0xB71C, 0x8D9F, 0xB71D, 0x8DA0, 0xB71E, 0x8DA1, 0xB71F, 0x8DA2, 0xB720, 0x8DA3, 0xB721, 0x8DA4, 0xB722, 0x8DA5, 0xB723, 0x8DA6, + 0xB724, 0x8DA7, 0xB725, 0x8DA8, 0xB726, 0x8DA9, 0xB727, 0x8DAA, 0xB728, 0xB6DF, 0xB729, 0xB6E0, 0xB72A, 0x8DAB, 0xB72B, 0x8DAC, + 0xB72C, 0xB6E1, 0xB72D, 0x8DAD, 0xB72E, 0x8DAE, 0xB72F, 0xB6E2, 0xB730, 0xB6E3, 0xB731, 0x8DAF, 0xB732, 0x8DB0, 0xB733, 0x8DB1, + 0xB734, 0x8DB2, 0xB735, 0x8DB3, 0xB736, 0x8DB4, 0xB737, 0x8DB5, 0xB738, 0xB6E4, 0xB739, 0xB6E5, 0xB73A, 0x8DB6, 0xB73B, 0xB6E6, + 0xB73C, 0x8DB7, 0xB73D, 0x8DB8, 0xB73E, 0x8DB9, 0xB73F, 0x8DBA, 0xB740, 0x8DBB, 0xB741, 0x8DBC, 0xB742, 0x8DBD, 0xB743, 0x8DBE, + 0xB744, 0xB6E7, 0xB745, 0x8DBF, 0xB746, 0x8DC0, 0xB747, 0x8DC1, 0xB748, 0xB6E8, 0xB749, 0x8DC2, 0xB74A, 0x8DC3, 0xB74B, 0x8DC4, + 0xB74C, 0xB6E9, 0xB74D, 0x8DC5, 0xB74E, 0x8DC6, 0xB74F, 0x8DC7, 0xB750, 0x8DC8, 0xB751, 0x8DC9, 0xB752, 0x8DCA, 0xB753, 0x8DCB, + 0xB754, 0xB6EA, 0xB755, 0xB6EB, 0xB756, 0x8DCC, 0xB757, 0x8DCD, 0xB758, 0x8DCE, 0xB759, 0x8DCF, 0xB75A, 0x8DD0, 0xB75B, 0x8DD1, + 0xB75C, 0x8DD2, 0xB75D, 0x8DD3, 0xB75E, 0x8DD4, 0xB75F, 0x8DD5, 0xB760, 0xB6EC, 0xB761, 0x8DD6, 0xB762, 0x8DD7, 0xB763, 0x8DD8, + 0xB764, 0xB6ED, 0xB765, 0x8DD9, 0xB766, 0x8DDA, 0xB767, 0x8DDB, 0xB768, 0xB6EE, 0xB769, 0x8DDC, 0xB76A, 0x8DDD, 0xB76B, 0x8DDE, + 0xB76C, 0x8DDF, 0xB76D, 0x8DE0, 0xB76E, 0x8DE1, 0xB76F, 0x8DE2, 0xB770, 0xB6EF, 0xB771, 0xB6F0, 0xB772, 0x8DE3, 0xB773, 0xB6F1, + 0xB774, 0x8DE4, 0xB775, 0xB6F2, 0xB776, 0x8DE5, 0xB777, 0x8DE6, 0xB778, 0x8DE7, 0xB779, 0x8DE8, 0xB77A, 0x8DE9, 0xB77B, 0x8DEA, + 0xB77C, 0xB6F3, 0xB77D, 0xB6F4, 0xB77E, 0x8DEB, 0xB77F, 0x8DEC, 0xB780, 0xB6F5, 0xB781, 0x8DED, 0xB782, 0x8DEE, 0xB783, 0x8DEF, + 0xB784, 0xB6F6, 0xB785, 0x8DF0, 0xB786, 0x8DF1, 0xB787, 0x8DF2, 0xB788, 0x8DF3, 0xB789, 0x8DF4, 0xB78A, 0x8DF5, 0xB78B, 0x8DF6, + 0xB78C, 0xB6F7, 0xB78D, 0xB6F8, 0xB78E, 0x8DF7, 0xB78F, 0xB6F9, 0xB790, 0xB6FA, 0xB791, 0xB6FB, 0xB792, 0xB6FC, 0xB793, 0x8DF8, + 0xB794, 0x8DF9, 0xB795, 0x8DFA, 0xB796, 0xB6FD, 0xB797, 0xB6FE, 0xB798, 0xB7A1, 0xB799, 0xB7A2, 0xB79A, 0x8DFB, 0xB79B, 0x8DFC, + 0xB79C, 0xB7A3, 0xB79D, 0x8DFD, 0xB79E, 0x8DFE, 0xB79F, 0x8E41, 0xB7A0, 0xB7A4, 0xB7A1, 0x8E42, 0xB7A2, 0x8E43, 0xB7A3, 0x8E44, + 0xB7A4, 0x8E45, 0xB7A5, 0x8E46, 0xB7A6, 0x8E47, 0xB7A7, 0x8E48, 0xB7A8, 0xB7A5, 0xB7A9, 0xB7A6, 0xB7AA, 0x8E49, 0xB7AB, 0xB7A7, + 0xB7AC, 0xB7A8, 0xB7AD, 0xB7A9, 0xB7AE, 0x8E4A, 0xB7AF, 0x8E4B, 0xB7B0, 0x8E4C, 0xB7B1, 0x8E4D, 0xB7B2, 0x8E4E, 0xB7B3, 0x8E4F, + 0xB7B4, 0xB7AA, 0xB7B5, 0xB7AB, 0xB7B6, 0x8E50, 0xB7B7, 0x8E51, 0xB7B8, 0xB7AC, 0xB7B9, 0x8E52, 0xB7BA, 0x8E53, 0xB7BB, 0x8E54, + 0xB7BC, 0x8E55, 0xB7BD, 0x8E56, 0xB7BE, 0x8E57, 0xB7BF, 0x8E58, 0xB7C0, 0x8E59, 0xB7C1, 0x8E5A, 0xB7C2, 0x8E61, 0xB7C3, 0x8E62, + 0xB7C4, 0x8E63, 0xB7C5, 0x8E64, 0xB7C6, 0x8E65, 0xB7C7, 0xB7AD, 0xB7C8, 0x8E66, 0xB7C9, 0xB7AE, 0xB7CA, 0x8E67, 0xB7CB, 0x8E68, + 0xB7CC, 0x8E69, 0xB7CD, 0x8E6A, 0xB7CE, 0x8E6B, 0xB7CF, 0x8E6C, 0xB7D0, 0x8E6D, 0xB7D1, 0x8E6E, 0xB7D2, 0x8E6F, 0xB7D3, 0x8E70, + 0xB7D4, 0x8E71, 0xB7D5, 0x8E72, 0xB7D6, 0x8E73, 0xB7D7, 0x8E74, 0xB7D8, 0x8E75, 0xB7D9, 0x8E76, 0xB7DA, 0x8E77, 0xB7DB, 0x8E78, + 0xB7DC, 0x8E79, 0xB7DD, 0x8E7A, 0xB7DE, 0x8E81, 0xB7DF, 0x8E82, 0xB7E0, 0x8E83, 0xB7E1, 0x8E84, 0xB7E2, 0x8E85, 0xB7E3, 0x8E86, + 0xB7E4, 0x8E87, 0xB7E5, 0x8E88, 0xB7E6, 0x8E89, 0xB7E7, 0x8E8A, 0xB7E8, 0x8E8B, 0xB7E9, 0x8E8C, 0xB7EA, 0x8E8D, 0xB7EB, 0x8E8E, + 0xB7EC, 0xB7AF, 0xB7ED, 0xB7B0, 0xB7EE, 0x8E8F, 0xB7EF, 0x8E90, 0xB7F0, 0xB7B1, 0xB7F1, 0x8E91, 0xB7F2, 0x8E92, 0xB7F3, 0x8E93, + 0xB7F4, 0xB7B2, 0xB7F5, 0x8E94, 0xB7F6, 0x8E95, 0xB7F7, 0x8E96, 0xB7F8, 0x8E97, 0xB7F9, 0x8E98, 0xB7FA, 0x8E99, 0xB7FB, 0x8E9A, + 0xB7FC, 0xB7B3, 0xB7FD, 0xB7B4, 0xB7FE, 0x8E9B, 0xB7FF, 0xB7B5, 0xB800, 0xB7B6, 0xB801, 0xB7B7, 0xB802, 0x8E9C, 0xB803, 0x8E9D, + 0xB804, 0x8E9E, 0xB805, 0x8E9F, 0xB806, 0x8EA0, 0xB807, 0xB7B8, 0xB808, 0xB7B9, 0xB809, 0xB7BA, 0xB80A, 0x8EA1, 0xB80B, 0x8EA2, + 0xB80C, 0xB7BB, 0xB80D, 0x8EA3, 0xB80E, 0x8EA4, 0xB80F, 0x8EA5, 0xB810, 0xB7BC, 0xB811, 0x8EA6, 0xB812, 0x8EA7, 0xB813, 0x8EA8, + 0xB814, 0x8EA9, 0xB815, 0x8EAA, 0xB816, 0x8EAB, 0xB817, 0x8EAC, 0xB818, 0xB7BD, 0xB819, 0xB7BE, 0xB81A, 0x8EAD, 0xB81B, 0xB7BF, + 0xB81C, 0x8EAE, 0xB81D, 0xB7C0, 0xB81E, 0x8EAF, 0xB81F, 0x8EB0, 0xB820, 0x8EB1, 0xB821, 0x8EB2, 0xB822, 0x8EB3, 0xB823, 0x8EB4, + 0xB824, 0xB7C1, 0xB825, 0xB7C2, 0xB826, 0x8EB5, 0xB827, 0x8EB6, 0xB828, 0xB7C3, 0xB829, 0x8EB7, 0xB82A, 0x8EB8, 0xB82B, 0x8EB9, + 0xB82C, 0xB7C4, 0xB82D, 0x8EBA, 0xB82E, 0x8EBB, 0xB82F, 0x8EBC, 0xB830, 0x8EBD, 0xB831, 0x8EBE, 0xB832, 0x8EBF, 0xB833, 0x8EC0, + 0xB834, 0xB7C5, 0xB835, 0xB7C6, 0xB836, 0x8EC1, 0xB837, 0xB7C7, 0xB838, 0xB7C8, 0xB839, 0xB7C9, 0xB83A, 0x8EC2, 0xB83B, 0x8EC3, + 0xB83C, 0x8EC4, 0xB83D, 0x8EC5, 0xB83E, 0x8EC6, 0xB83F, 0x8EC7, 0xB840, 0xB7CA, 0xB841, 0x8EC8, 0xB842, 0x8EC9, 0xB843, 0x8ECA, + 0xB844, 0xB7CB, 0xB845, 0x8ECB, 0xB846, 0x8ECC, 0xB847, 0x8ECD, 0xB848, 0x8ECE, 0xB849, 0x8ECF, 0xB84A, 0x8ED0, 0xB84B, 0x8ED1, + 0xB84C, 0x8ED2, 0xB84D, 0x8ED3, 0xB84E, 0x8ED4, 0xB84F, 0x8ED5, 0xB850, 0x8ED6, 0xB851, 0xB7CC, 0xB852, 0x8ED7, 0xB853, 0xB7CD, + 0xB854, 0x8ED8, 0xB855, 0x8ED9, 0xB856, 0x8EDA, 0xB857, 0x8EDB, 0xB858, 0x8EDC, 0xB859, 0x8EDD, 0xB85A, 0x8EDE, 0xB85B, 0x8EDF, + 0xB85C, 0xB7CE, 0xB85D, 0xB7CF, 0xB85E, 0x8EE0, 0xB85F, 0x8EE1, 0xB860, 0xB7D0, 0xB861, 0x8EE2, 0xB862, 0x8EE3, 0xB863, 0x8EE4, + 0xB864, 0xB7D1, 0xB865, 0x8EE5, 0xB866, 0x8EE6, 0xB867, 0x8EE7, 0xB868, 0x8EE8, 0xB869, 0x8EE9, 0xB86A, 0x8EEA, 0xB86B, 0x8EEB, + 0xB86C, 0xB7D2, 0xB86D, 0xB7D3, 0xB86E, 0x8EEC, 0xB86F, 0xB7D4, 0xB870, 0x8EED, 0xB871, 0xB7D5, 0xB872, 0x8EEE, 0xB873, 0x8EEF, + 0xB874, 0x8EF0, 0xB875, 0x8EF1, 0xB876, 0x8EF2, 0xB877, 0x8EF3, 0xB878, 0xB7D6, 0xB879, 0x8EF4, 0xB87A, 0x8EF5, 0xB87B, 0x8EF6, + 0xB87C, 0xB7D7, 0xB87D, 0x8EF7, 0xB87E, 0x8EF8, 0xB87F, 0x8EF9, 0xB880, 0x8EFA, 0xB881, 0x8EFB, 0xB882, 0x8EFC, 0xB883, 0x8EFD, + 0xB884, 0x8EFE, 0xB885, 0x8F41, 0xB886, 0x8F42, 0xB887, 0x8F43, 0xB888, 0x8F44, 0xB889, 0x8F45, 0xB88A, 0x8F46, 0xB88B, 0x8F47, + 0xB88C, 0x8F48, 0xB88D, 0xB7D8, 0xB88E, 0x8F49, 0xB88F, 0x8F4A, 0xB890, 0x8F4B, 0xB891, 0x8F4C, 0xB892, 0x8F4D, 0xB893, 0x8F4E, + 0xB894, 0x8F4F, 0xB895, 0x8F50, 0xB896, 0x8F51, 0xB897, 0x8F52, 0xB898, 0x8F53, 0xB899, 0x8F54, 0xB89A, 0x8F55, 0xB89B, 0x8F56, + 0xB89C, 0x8F57, 0xB89D, 0x8F58, 0xB89E, 0x8F59, 0xB89F, 0x8F5A, 0xB8A0, 0x8F61, 0xB8A1, 0x8F62, 0xB8A2, 0x8F63, 0xB8A3, 0x8F64, + 0xB8A4, 0x8F65, 0xB8A5, 0x8F66, 0xB8A6, 0x8F67, 0xB8A7, 0x8F68, 0xB8A8, 0xB7D9, 0xB8A9, 0x8F69, 0xB8AA, 0x8F6A, 0xB8AB, 0x8F6B, + 0xB8AC, 0x8F6C, 0xB8AD, 0x8F6D, 0xB8AE, 0x8F6E, 0xB8AF, 0x8F6F, 0xB8B0, 0xB7DA, 0xB8B1, 0x8F70, 0xB8B2, 0x8F71, 0xB8B3, 0x8F72, + 0xB8B4, 0xB7DB, 0xB8B5, 0x8F73, 0xB8B6, 0x8F74, 0xB8B7, 0x8F75, 0xB8B8, 0xB7DC, 0xB8B9, 0x8F76, 0xB8BA, 0x8F77, 0xB8BB, 0x8F78, + 0xB8BC, 0x8F79, 0xB8BD, 0x8F7A, 0xB8BE, 0x8F81, 0xB8BF, 0x8F82, 0xB8C0, 0xB7DD, 0xB8C1, 0xB7DE, 0xB8C2, 0x8F83, 0xB8C3, 0xB7DF, + 0xB8C4, 0x8F84, 0xB8C5, 0xB7E0, 0xB8C6, 0x8F85, 0xB8C7, 0x8F86, 0xB8C8, 0x8F87, 0xB8C9, 0x8F88, 0xB8CA, 0x8F89, 0xB8CB, 0x8F8A, + 0xB8CC, 0xB7E1, 0xB8CD, 0x8F8B, 0xB8CE, 0x8F8C, 0xB8CF, 0x8F8D, 0xB8D0, 0xB7E2, 0xB8D1, 0x8F8E, 0xB8D2, 0x8F8F, 0xB8D3, 0x8F90, + 0xB8D4, 0xB7E3, 0xB8D5, 0x8F91, 0xB8D6, 0x8F92, 0xB8D7, 0x8F93, 0xB8D8, 0x8F94, 0xB8D9, 0x8F95, 0xB8DA, 0x8F96, 0xB8DB, 0x8F97, + 0xB8DC, 0x8F98, 0xB8DD, 0xB7E4, 0xB8DE, 0x8F99, 0xB8DF, 0xB7E5, 0xB8E0, 0x8F9A, 0xB8E1, 0xB7E6, 0xB8E2, 0x8F9B, 0xB8E3, 0x8F9C, + 0xB8E4, 0x8F9D, 0xB8E5, 0x8F9E, 0xB8E6, 0x8F9F, 0xB8E7, 0x8FA0, 0xB8E8, 0xB7E7, 0xB8E9, 0xB7E8, 0xB8EA, 0x8FA1, 0xB8EB, 0x8FA2, + 0xB8EC, 0xB7E9, 0xB8ED, 0x8FA3, 0xB8EE, 0x8FA4, 0xB8EF, 0x8FA5, 0xB8F0, 0xB7EA, 0xB8F1, 0x8FA6, 0xB8F2, 0x8FA7, 0xB8F3, 0x8FA8, + 0xB8F4, 0x8FA9, 0xB8F5, 0x8FAA, 0xB8F6, 0x8FAB, 0xB8F7, 0x8FAC, 0xB8F8, 0xB7EB, 0xB8F9, 0xB7EC, 0xB8FA, 0x8FAD, 0xB8FB, 0xB7ED, + 0xB8FC, 0x8FAE, 0xB8FD, 0xB7EE, 0xB8FE, 0x8FAF, 0xB8FF, 0x8FB0, 0xB900, 0x8FB1, 0xB901, 0x8FB2, 0xB902, 0x8FB3, 0xB903, 0x8FB4, + 0xB904, 0xB7EF, 0xB905, 0x8FB5, 0xB906, 0x8FB6, 0xB907, 0x8FB7, 0xB908, 0x8FB8, 0xB909, 0x8FB9, 0xB90A, 0x8FBA, 0xB90B, 0x8FBB, + 0xB90C, 0x8FBC, 0xB90D, 0x8FBD, 0xB90E, 0x8FBE, 0xB90F, 0x8FBF, 0xB910, 0x8FC0, 0xB911, 0x8FC1, 0xB912, 0x8FC2, 0xB913, 0x8FC3, + 0xB914, 0x8FC4, 0xB915, 0x8FC5, 0xB916, 0x8FC6, 0xB917, 0x8FC7, 0xB918, 0xB7F0, 0xB919, 0x8FC8, 0xB91A, 0x8FC9, 0xB91B, 0x8FCA, + 0xB91C, 0x8FCB, 0xB91D, 0x8FCC, 0xB91E, 0x8FCD, 0xB91F, 0x8FCE, 0xB920, 0xB7F1, 0xB921, 0x8FCF, 0xB922, 0x8FD0, 0xB923, 0x8FD1, + 0xB924, 0x8FD2, 0xB925, 0x8FD3, 0xB926, 0x8FD4, 0xB927, 0x8FD5, 0xB928, 0x8FD6, 0xB929, 0x8FD7, 0xB92A, 0x8FD8, 0xB92B, 0x8FD9, + 0xB92C, 0x8FDA, 0xB92D, 0x8FDB, 0xB92E, 0x8FDC, 0xB92F, 0x8FDD, 0xB930, 0x8FDE, 0xB931, 0x8FDF, 0xB932, 0x8FE0, 0xB933, 0x8FE1, + 0xB934, 0x8FE2, 0xB935, 0x8FE3, 0xB936, 0x8FE4, 0xB937, 0x8FE5, 0xB938, 0x8FE6, 0xB939, 0x8FE7, 0xB93A, 0x8FE8, 0xB93B, 0x8FE9, + 0xB93C, 0xB7F2, 0xB93D, 0xB7F3, 0xB93E, 0x8FEA, 0xB93F, 0x8FEB, 0xB940, 0xB7F4, 0xB941, 0x8FEC, 0xB942, 0x8FED, 0xB943, 0x8FEE, + 0xB944, 0xB7F5, 0xB945, 0x8FEF, 0xB946, 0x8FF0, 0xB947, 0x8FF1, 0xB948, 0x8FF2, 0xB949, 0x8FF3, 0xB94A, 0x8FF4, 0xB94B, 0x8FF5, + 0xB94C, 0xB7F6, 0xB94D, 0x8FF6, 0xB94E, 0x8FF7, 0xB94F, 0xB7F7, 0xB950, 0x8FF8, 0xB951, 0xB7F8, 0xB952, 0x8FF9, 0xB953, 0x8FFA, + 0xB954, 0x8FFB, 0xB955, 0x8FFC, 0xB956, 0x8FFD, 0xB957, 0x8FFE, 0xB958, 0xB7F9, 0xB959, 0xB7FA, 0xB95A, 0x9041, 0xB95B, 0x9042, + 0xB95C, 0xB7FB, 0xB95D, 0x9043, 0xB95E, 0x9044, 0xB95F, 0x9045, 0xB960, 0xB7FC, 0xB961, 0x9046, 0xB962, 0x9047, 0xB963, 0x9048, + 0xB964, 0x9049, 0xB965, 0x904A, 0xB966, 0x904B, 0xB967, 0x904C, 0xB968, 0xB7FD, 0xB969, 0xB7FE, 0xB96A, 0x904D, 0xB96B, 0xB8A1, + 0xB96C, 0x904E, 0xB96D, 0xB8A2, 0xB96E, 0x904F, 0xB96F, 0x9050, 0xB970, 0x9051, 0xB971, 0x9052, 0xB972, 0x9053, 0xB973, 0x9054, + 0xB974, 0xB8A3, 0xB975, 0xB8A4, 0xB976, 0x9055, 0xB977, 0x9056, 0xB978, 0xB8A5, 0xB979, 0x9057, 0xB97A, 0x9058, 0xB97B, 0x9059, + 0xB97C, 0xB8A6, 0xB97D, 0x905A, 0xB97E, 0x9061, 0xB97F, 0x9062, 0xB980, 0x9063, 0xB981, 0x9064, 0xB982, 0x9065, 0xB983, 0x9066, + 0xB984, 0xB8A7, 0xB985, 0xB8A8, 0xB986, 0x9067, 0xB987, 0xB8A9, 0xB988, 0x9068, 0xB989, 0xB8AA, 0xB98A, 0xB8AB, 0xB98B, 0x9069, + 0xB98C, 0x906A, 0xB98D, 0xB8AC, 0xB98E, 0xB8AD, 0xB98F, 0x906B, 0xB990, 0x906C, 0xB991, 0x906D, 0xB992, 0x906E, 0xB993, 0x906F, + 0xB994, 0x9070, 0xB995, 0x9071, 0xB996, 0x9072, 0xB997, 0x9073, 0xB998, 0x9074, 0xB999, 0x9075, 0xB99A, 0x9076, 0xB99B, 0x9077, + 0xB99C, 0x9078, 0xB99D, 0x9079, 0xB99E, 0x907A, 0xB99F, 0x9081, 0xB9A0, 0x9082, 0xB9A1, 0x9083, 0xB9A2, 0x9084, 0xB9A3, 0x9085, + 0xB9A4, 0x9086, 0xB9A5, 0x9087, 0xB9A6, 0x9088, 0xB9A7, 0x9089, 0xB9A8, 0x908A, 0xB9A9, 0x908B, 0xB9AA, 0x908C, 0xB9AB, 0x908D, + 0xB9AC, 0xB8AE, 0xB9AD, 0xB8AF, 0xB9AE, 0x908E, 0xB9AF, 0x908F, 0xB9B0, 0xB8B0, 0xB9B1, 0x9090, 0xB9B2, 0x9091, 0xB9B3, 0x9092, + 0xB9B4, 0xB8B1, 0xB9B5, 0x9093, 0xB9B6, 0x9094, 0xB9B7, 0x9095, 0xB9B8, 0x9096, 0xB9B9, 0x9097, 0xB9BA, 0x9098, 0xB9BB, 0x9099, + 0xB9BC, 0xB8B2, 0xB9BD, 0xB8B3, 0xB9BE, 0x909A, 0xB9BF, 0xB8B4, 0xB9C0, 0x909B, 0xB9C1, 0xB8B5, 0xB9C2, 0x909C, 0xB9C3, 0x909D, + 0xB9C4, 0x909E, 0xB9C5, 0x909F, 0xB9C6, 0x90A0, 0xB9C7, 0x90A1, 0xB9C8, 0xB8B6, 0xB9C9, 0xB8B7, 0xB9CA, 0x90A2, 0xB9CB, 0x90A3, + 0xB9CC, 0xB8B8, 0xB9CD, 0x90A4, 0xB9CE, 0xB8B9, 0xB9CF, 0xB8BA, 0xB9D0, 0xB8BB, 0xB9D1, 0xB8BC, 0xB9D2, 0xB8BD, 0xB9D3, 0x90A5, + 0xB9D4, 0x90A6, 0xB9D5, 0x90A7, 0xB9D6, 0x90A8, 0xB9D7, 0x90A9, 0xB9D8, 0xB8BE, 0xB9D9, 0xB8BF, 0xB9DA, 0x90AA, 0xB9DB, 0xB8C0, + 0xB9DC, 0x90AB, 0xB9DD, 0xB8C1, 0xB9DE, 0xB8C2, 0xB9DF, 0x90AC, 0xB9E0, 0x90AD, 0xB9E1, 0xB8C3, 0xB9E2, 0x90AE, 0xB9E3, 0xB8C4, + 0xB9E4, 0xB8C5, 0xB9E5, 0xB8C6, 0xB9E6, 0x90AF, 0xB9E7, 0x90B0, 0xB9E8, 0xB8C7, 0xB9E9, 0x90B1, 0xB9EA, 0x90B2, 0xB9EB, 0x90B3, + 0xB9EC, 0xB8C8, 0xB9ED, 0x90B4, 0xB9EE, 0x90B5, 0xB9EF, 0x90B6, 0xB9F0, 0x90B7, 0xB9F1, 0x90B8, 0xB9F2, 0x90B9, 0xB9F3, 0x90BA, + 0xB9F4, 0xB8C9, 0xB9F5, 0xB8CA, 0xB9F6, 0x90BB, 0xB9F7, 0xB8CB, 0xB9F8, 0xB8CC, 0xB9F9, 0xB8CD, 0xB9FA, 0xB8CE, 0xB9FB, 0x90BC, + 0xB9FC, 0x90BD, 0xB9FD, 0x90BE, 0xB9FE, 0x90BF, 0xB9FF, 0x90C0, 0xBA00, 0xB8CF, 0xBA01, 0xB8D0, 0xBA02, 0x90C1, 0xBA03, 0x90C2, + 0xBA04, 0x90C3, 0xBA05, 0x90C4, 0xBA06, 0x90C5, 0xBA07, 0x90C6, 0xBA08, 0xB8D1, 0xBA09, 0x90C7, 0xBA0A, 0x90C8, 0xBA0B, 0x90C9, + 0xBA0C, 0x90CA, 0xBA0D, 0x90CB, 0xBA0E, 0x90CC, 0xBA0F, 0x90CD, 0xBA10, 0x90CE, 0xBA11, 0x90CF, 0xBA12, 0x90D0, 0xBA13, 0x90D1, + 0xBA14, 0x90D2, 0xBA15, 0xB8D2, 0xBA16, 0x90D3, 0xBA17, 0x90D4, 0xBA18, 0x90D5, 0xBA19, 0x90D6, 0xBA1A, 0x90D7, 0xBA1B, 0x90D8, + 0xBA1C, 0x90D9, 0xBA1D, 0x90DA, 0xBA1E, 0x90DB, 0xBA1F, 0x90DC, 0xBA20, 0x90DD, 0xBA21, 0x90DE, 0xBA22, 0x90DF, 0xBA23, 0x90E0, + 0xBA24, 0x90E1, 0xBA25, 0x90E2, 0xBA26, 0x90E3, 0xBA27, 0x90E4, 0xBA28, 0x90E5, 0xBA29, 0x90E6, 0xBA2A, 0x90E7, 0xBA2B, 0x90E8, + 0xBA2C, 0x90E9, 0xBA2D, 0x90EA, 0xBA2E, 0x90EB, 0xBA2F, 0x90EC, 0xBA30, 0x90ED, 0xBA31, 0x90EE, 0xBA32, 0x90EF, 0xBA33, 0x90F0, + 0xBA34, 0x90F1, 0xBA35, 0x90F2, 0xBA36, 0x90F3, 0xBA37, 0x90F4, 0xBA38, 0xB8D3, 0xBA39, 0xB8D4, 0xBA3A, 0x90F5, 0xBA3B, 0x90F6, + 0xBA3C, 0xB8D5, 0xBA3D, 0x90F7, 0xBA3E, 0x90F8, 0xBA3F, 0x90F9, 0xBA40, 0xB8D6, 0xBA41, 0x90FA, 0xBA42, 0xB8D7, 0xBA43, 0x90FB, + 0xBA44, 0x90FC, 0xBA45, 0x90FD, 0xBA46, 0x90FE, 0xBA47, 0x9141, 0xBA48, 0xB8D8, 0xBA49, 0xB8D9, 0xBA4A, 0x9142, 0xBA4B, 0xB8DA, + 0xBA4C, 0x9143, 0xBA4D, 0xB8DB, 0xBA4E, 0xB8DC, 0xBA4F, 0x9144, 0xBA50, 0x9145, 0xBA51, 0x9146, 0xBA52, 0x9147, 0xBA53, 0xB8DD, + 0xBA54, 0xB8DE, 0xBA55, 0xB8DF, 0xBA56, 0x9148, 0xBA57, 0x9149, 0xBA58, 0xB8E0, 0xBA59, 0x914A, 0xBA5A, 0x914B, 0xBA5B, 0x914C, + 0xBA5C, 0xB8E1, 0xBA5D, 0x914D, 0xBA5E, 0x914E, 0xBA5F, 0x914F, 0xBA60, 0x9150, 0xBA61, 0x9151, 0xBA62, 0x9152, 0xBA63, 0x9153, + 0xBA64, 0xB8E2, 0xBA65, 0xB8E3, 0xBA66, 0x9154, 0xBA67, 0xB8E4, 0xBA68, 0xB8E5, 0xBA69, 0xB8E6, 0xBA6A, 0x9155, 0xBA6B, 0x9156, + 0xBA6C, 0x9157, 0xBA6D, 0x9158, 0xBA6E, 0x9159, 0xBA6F, 0x915A, 0xBA70, 0xB8E7, 0xBA71, 0xB8E8, 0xBA72, 0x9161, 0xBA73, 0x9162, + 0xBA74, 0xB8E9, 0xBA75, 0x9163, 0xBA76, 0x9164, 0xBA77, 0x9165, 0xBA78, 0xB8EA, 0xBA79, 0x9166, 0xBA7A, 0x9167, 0xBA7B, 0x9168, + 0xBA7C, 0x9169, 0xBA7D, 0x916A, 0xBA7E, 0x916B, 0xBA7F, 0x916C, 0xBA80, 0x916D, 0xBA81, 0x916E, 0xBA82, 0x916F, 0xBA83, 0xB8EB, + 0xBA84, 0xB8EC, 0xBA85, 0xB8ED, 0xBA86, 0x9170, 0xBA87, 0xB8EE, 0xBA88, 0x9171, 0xBA89, 0x9172, 0xBA8A, 0x9173, 0xBA8B, 0x9174, + 0xBA8C, 0xB8EF, 0xBA8D, 0x9175, 0xBA8E, 0x9176, 0xBA8F, 0x9177, 0xBA90, 0x9178, 0xBA91, 0x9179, 0xBA92, 0x917A, 0xBA93, 0x9181, + 0xBA94, 0x9182, 0xBA95, 0x9183, 0xBA96, 0x9184, 0xBA97, 0x9185, 0xBA98, 0x9186, 0xBA99, 0x9187, 0xBA9A, 0x9188, 0xBA9B, 0x9189, + 0xBA9C, 0x918A, 0xBA9D, 0x918B, 0xBA9E, 0x918C, 0xBA9F, 0x918D, 0xBAA0, 0x918E, 0xBAA1, 0x918F, 0xBAA2, 0x9190, 0xBAA3, 0x9191, + 0xBAA4, 0x9192, 0xBAA5, 0x9193, 0xBAA6, 0x9194, 0xBAA7, 0x9195, 0xBAA8, 0xB8F0, 0xBAA9, 0xB8F1, 0xBAAA, 0x9196, 0xBAAB, 0xB8F2, + 0xBAAC, 0xB8F3, 0xBAAD, 0x9197, 0xBAAE, 0x9198, 0xBAAF, 0x9199, 0xBAB0, 0xB8F4, 0xBAB1, 0x919A, 0xBAB2, 0xB8F5, 0xBAB3, 0x919B, + 0xBAB4, 0x919C, 0xBAB5, 0x919D, 0xBAB6, 0x919E, 0xBAB7, 0x919F, 0xBAB8, 0xB8F6, 0xBAB9, 0xB8F7, 0xBABA, 0x91A0, 0xBABB, 0xB8F8, + 0xBABC, 0x91A1, 0xBABD, 0xB8F9, 0xBABE, 0x91A2, 0xBABF, 0x91A3, 0xBAC0, 0x91A4, 0xBAC1, 0x91A5, 0xBAC2, 0x91A6, 0xBAC3, 0x91A7, + 0xBAC4, 0xB8FA, 0xBAC5, 0x91A8, 0xBAC6, 0x91A9, 0xBAC7, 0x91AA, 0xBAC8, 0xB8FB, 0xBAC9, 0x91AB, 0xBACA, 0x91AC, 0xBACB, 0x91AD, + 0xBACC, 0x91AE, 0xBACD, 0x91AF, 0xBACE, 0x91B0, 0xBACF, 0x91B1, 0xBAD0, 0x91B2, 0xBAD1, 0x91B3, 0xBAD2, 0x91B4, 0xBAD3, 0x91B5, + 0xBAD4, 0x91B6, 0xBAD5, 0x91B7, 0xBAD6, 0x91B8, 0xBAD7, 0x91B9, 0xBAD8, 0xB8FC, 0xBAD9, 0xB8FD, 0xBADA, 0x91BA, 0xBADB, 0x91BB, + 0xBADC, 0x91BC, 0xBADD, 0x91BD, 0xBADE, 0x91BE, 0xBADF, 0x91BF, 0xBAE0, 0x91C0, 0xBAE1, 0x91C1, 0xBAE2, 0x91C2, 0xBAE3, 0x91C3, + 0xBAE4, 0x91C4, 0xBAE5, 0x91C5, 0xBAE6, 0x91C6, 0xBAE7, 0x91C7, 0xBAE8, 0x91C8, 0xBAE9, 0x91C9, 0xBAEA, 0x91CA, 0xBAEB, 0x91CB, + 0xBAEC, 0x91CC, 0xBAED, 0x91CD, 0xBAEE, 0x91CE, 0xBAEF, 0x91CF, 0xBAF0, 0x91D0, 0xBAF1, 0x91D1, 0xBAF2, 0x91D2, 0xBAF3, 0x91D3, + 0xBAF4, 0x91D4, 0xBAF5, 0x91D5, 0xBAF6, 0x91D6, 0xBAF7, 0x91D7, 0xBAF8, 0x91D8, 0xBAF9, 0x91D9, 0xBAFA, 0x91DA, 0xBAFB, 0x91DB, + 0xBAFC, 0xB8FE, 0xBAFD, 0x91DC, 0xBAFE, 0x91DD, 0xBAFF, 0x91DE, 0xBB00, 0xB9A1, 0xBB01, 0x91DF, 0xBB02, 0x91E0, 0xBB03, 0x91E1, + 0xBB04, 0xB9A2, 0xBB05, 0x91E2, 0xBB06, 0x91E3, 0xBB07, 0x91E4, 0xBB08, 0x91E5, 0xBB09, 0x91E6, 0xBB0A, 0x91E7, 0xBB0B, 0x91E8, + 0xBB0C, 0x91E9, 0xBB0D, 0xB9A3, 0xBB0E, 0x91EA, 0xBB0F, 0xB9A4, 0xBB10, 0x91EB, 0xBB11, 0xB9A5, 0xBB12, 0x91EC, 0xBB13, 0x91ED, + 0xBB14, 0x91EE, 0xBB15, 0x91EF, 0xBB16, 0x91F0, 0xBB17, 0x91F1, 0xBB18, 0xB9A6, 0xBB19, 0x91F2, 0xBB1A, 0x91F3, 0xBB1B, 0x91F4, + 0xBB1C, 0xB9A7, 0xBB1D, 0x91F5, 0xBB1E, 0x91F6, 0xBB1F, 0x91F7, 0xBB20, 0xB9A8, 0xBB21, 0x91F8, 0xBB22, 0x91F9, 0xBB23, 0x91FA, + 0xBB24, 0x91FB, 0xBB25, 0x91FC, 0xBB26, 0x91FD, 0xBB27, 0x91FE, 0xBB28, 0x9241, 0xBB29, 0xB9A9, 0xBB2A, 0x9242, 0xBB2B, 0xB9AA, + 0xBB2C, 0x9243, 0xBB2D, 0x9244, 0xBB2E, 0x9245, 0xBB2F, 0x9246, 0xBB30, 0x9247, 0xBB31, 0x9248, 0xBB32, 0x9249, 0xBB33, 0x924A, + 0xBB34, 0xB9AB, 0xBB35, 0xB9AC, 0xBB36, 0xB9AD, 0xBB37, 0x924B, 0xBB38, 0xB9AE, 0xBB39, 0x924C, 0xBB3A, 0x924D, 0xBB3B, 0xB9AF, + 0xBB3C, 0xB9B0, 0xBB3D, 0xB9B1, 0xBB3E, 0xB9B2, 0xBB3F, 0x924E, 0xBB40, 0x924F, 0xBB41, 0x9250, 0xBB42, 0x9251, 0xBB43, 0x9252, + 0xBB44, 0xB9B3, 0xBB45, 0xB9B4, 0xBB46, 0x9253, 0xBB47, 0xB9B5, 0xBB48, 0x9254, 0xBB49, 0xB9B6, 0xBB4A, 0x9255, 0xBB4B, 0x9256, + 0xBB4C, 0x9257, 0xBB4D, 0xB9B7, 0xBB4E, 0x9258, 0xBB4F, 0xB9B8, 0xBB50, 0xB9B9, 0xBB51, 0x9259, 0xBB52, 0x925A, 0xBB53, 0x9261, + 0xBB54, 0xB9BA, 0xBB55, 0x9262, 0xBB56, 0x9263, 0xBB57, 0x9264, 0xBB58, 0xB9BB, 0xBB59, 0x9265, 0xBB5A, 0x9266, 0xBB5B, 0x9267, + 0xBB5C, 0x9268, 0xBB5D, 0x9269, 0xBB5E, 0x926A, 0xBB5F, 0x926B, 0xBB60, 0x926C, 0xBB61, 0xB9BC, 0xBB62, 0x926D, 0xBB63, 0xB9BD, + 0xBB64, 0x926E, 0xBB65, 0x926F, 0xBB66, 0x9270, 0xBB67, 0x9271, 0xBB68, 0x9272, 0xBB69, 0x9273, 0xBB6A, 0x9274, 0xBB6B, 0x9275, + 0xBB6C, 0xB9BE, 0xBB6D, 0x9276, 0xBB6E, 0x9277, 0xBB6F, 0x9278, 0xBB70, 0x9279, 0xBB71, 0x927A, 0xBB72, 0x9281, 0xBB73, 0x9282, + 0xBB74, 0x9283, 0xBB75, 0x9284, 0xBB76, 0x9285, 0xBB77, 0x9286, 0xBB78, 0x9287, 0xBB79, 0x9288, 0xBB7A, 0x9289, 0xBB7B, 0x928A, + 0xBB7C, 0x928B, 0xBB7D, 0x928C, 0xBB7E, 0x928D, 0xBB7F, 0x928E, 0xBB80, 0x928F, 0xBB81, 0x9290, 0xBB82, 0x9291, 0xBB83, 0x9292, + 0xBB84, 0x9293, 0xBB85, 0x9294, 0xBB86, 0x9295, 0xBB87, 0x9296, 0xBB88, 0xB9BF, 0xBB89, 0x9297, 0xBB8A, 0x9298, 0xBB8B, 0x9299, + 0xBB8C, 0xB9C0, 0xBB8D, 0x929A, 0xBB8E, 0x929B, 0xBB8F, 0x929C, 0xBB90, 0xB9C1, 0xBB91, 0x929D, 0xBB92, 0x929E, 0xBB93, 0x929F, + 0xBB94, 0x92A0, 0xBB95, 0x92A1, 0xBB96, 0x92A2, 0xBB97, 0x92A3, 0xBB98, 0x92A4, 0xBB99, 0x92A5, 0xBB9A, 0x92A6, 0xBB9B, 0x92A7, + 0xBB9C, 0x92A8, 0xBB9D, 0x92A9, 0xBB9E, 0x92AA, 0xBB9F, 0x92AB, 0xBBA0, 0x92AC, 0xBBA1, 0x92AD, 0xBBA2, 0x92AE, 0xBBA3, 0x92AF, + 0xBBA4, 0xB9C2, 0xBBA5, 0x92B0, 0xBBA6, 0x92B1, 0xBBA7, 0x92B2, 0xBBA8, 0xB9C3, 0xBBA9, 0x92B3, 0xBBAA, 0x92B4, 0xBBAB, 0x92B5, + 0xBBAC, 0xB9C4, 0xBBAD, 0x92B6, 0xBBAE, 0x92B7, 0xBBAF, 0x92B8, 0xBBB0, 0x92B9, 0xBBB1, 0x92BA, 0xBBB2, 0x92BB, 0xBBB3, 0x92BC, + 0xBBB4, 0xB9C5, 0xBBB5, 0x92BD, 0xBBB6, 0x92BE, 0xBBB7, 0xB9C6, 0xBBB8, 0x92BF, 0xBBB9, 0x92C0, 0xBBBA, 0x92C1, 0xBBBB, 0x92C2, + 0xBBBC, 0x92C3, 0xBBBD, 0x92C4, 0xBBBE, 0x92C5, 0xBBBF, 0x92C6, 0xBBC0, 0xB9C7, 0xBBC1, 0x92C7, 0xBBC2, 0x92C8, 0xBBC3, 0x92C9, + 0xBBC4, 0xB9C8, 0xBBC5, 0x92CA, 0xBBC6, 0x92CB, 0xBBC7, 0x92CC, 0xBBC8, 0xB9C9, 0xBBC9, 0x92CD, 0xBBCA, 0x92CE, 0xBBCB, 0x92CF, + 0xBBCC, 0x92D0, 0xBBCD, 0x92D1, 0xBBCE, 0x92D2, 0xBBCF, 0x92D3, 0xBBD0, 0xB9CA, 0xBBD1, 0x92D4, 0xBBD2, 0x92D5, 0xBBD3, 0xB9CB, + 0xBBD4, 0x92D6, 0xBBD5, 0x92D7, 0xBBD6, 0x92D8, 0xBBD7, 0x92D9, 0xBBD8, 0x92DA, 0xBBD9, 0x92DB, 0xBBDA, 0x92DC, 0xBBDB, 0x92DD, + 0xBBDC, 0x92DE, 0xBBDD, 0x92DF, 0xBBDE, 0x92E0, 0xBBDF, 0x92E1, 0xBBE0, 0x92E2, 0xBBE1, 0x92E3, 0xBBE2, 0x92E4, 0xBBE3, 0x92E5, + 0xBBE4, 0x92E6, 0xBBE5, 0x92E7, 0xBBE6, 0x92E8, 0xBBE7, 0x92E9, 0xBBE8, 0x92EA, 0xBBE9, 0x92EB, 0xBBEA, 0x92EC, 0xBBEB, 0x92ED, + 0xBBEC, 0x92EE, 0xBBED, 0x92EF, 0xBBEE, 0x92F0, 0xBBEF, 0x92F1, 0xBBF0, 0x92F2, 0xBBF1, 0x92F3, 0xBBF2, 0x92F4, 0xBBF3, 0x92F5, + 0xBBF4, 0x92F6, 0xBBF5, 0x92F7, 0xBBF6, 0x92F8, 0xBBF7, 0x92F9, 0xBBF8, 0xB9CC, 0xBBF9, 0xB9CD, 0xBBFA, 0x92FA, 0xBBFB, 0x92FB, + 0xBBFC, 0xB9CE, 0xBBFD, 0x92FC, 0xBBFE, 0x92FD, 0xBBFF, 0xB9CF, 0xBC00, 0xB9D0, 0xBC01, 0x92FE, 0xBC02, 0xB9D1, 0xBC03, 0x9341, + 0xBC04, 0x9342, 0xBC05, 0x9343, 0xBC06, 0x9344, 0xBC07, 0x9345, 0xBC08, 0xB9D2, 0xBC09, 0xB9D3, 0xBC0A, 0x9346, 0xBC0B, 0xB9D4, + 0xBC0C, 0xB9D5, 0xBC0D, 0xB9D6, 0xBC0E, 0x9347, 0xBC0F, 0xB9D7, 0xBC10, 0x9348, 0xBC11, 0xB9D8, 0xBC12, 0x9349, 0xBC13, 0x934A, + 0xBC14, 0xB9D9, 0xBC15, 0xB9DA, 0xBC16, 0xB9DB, 0xBC17, 0xB9DC, 0xBC18, 0xB9DD, 0xBC19, 0x934B, 0xBC1A, 0x934C, 0xBC1B, 0xB9DE, + 0xBC1C, 0xB9DF, 0xBC1D, 0xB9E0, 0xBC1E, 0xB9E1, 0xBC1F, 0xB9E2, 0xBC20, 0x934D, 0xBC21, 0x934E, 0xBC22, 0x934F, 0xBC23, 0x9350, + 0xBC24, 0xB9E3, 0xBC25, 0xB9E4, 0xBC26, 0x9351, 0xBC27, 0xB9E5, 0xBC28, 0x9352, 0xBC29, 0xB9E6, 0xBC2A, 0x9353, 0xBC2B, 0x9354, + 0xBC2C, 0x9355, 0xBC2D, 0xB9E7, 0xBC2E, 0x9356, 0xBC2F, 0x9357, 0xBC30, 0xB9E8, 0xBC31, 0xB9E9, 0xBC32, 0x9358, 0xBC33, 0x9359, + 0xBC34, 0xB9EA, 0xBC35, 0x935A, 0xBC36, 0x9361, 0xBC37, 0x9362, 0xBC38, 0xB9EB, 0xBC39, 0x9363, 0xBC3A, 0x9364, 0xBC3B, 0x9365, + 0xBC3C, 0x9366, 0xBC3D, 0x9367, 0xBC3E, 0x9368, 0xBC3F, 0x9369, 0xBC40, 0xB9EC, 0xBC41, 0xB9ED, 0xBC42, 0x936A, 0xBC43, 0xB9EE, + 0xBC44, 0xB9EF, 0xBC45, 0xB9F0, 0xBC46, 0x936B, 0xBC47, 0x936C, 0xBC48, 0x936D, 0xBC49, 0xB9F1, 0xBC4A, 0x936E, 0xBC4B, 0x936F, + 0xBC4C, 0xB9F2, 0xBC4D, 0xB9F3, 0xBC4E, 0x9370, 0xBC4F, 0x9371, 0xBC50, 0xB9F4, 0xBC51, 0x9372, 0xBC52, 0x9373, 0xBC53, 0x9374, + 0xBC54, 0x9375, 0xBC55, 0x9376, 0xBC56, 0x9377, 0xBC57, 0x9378, 0xBC58, 0x9379, 0xBC59, 0x937A, 0xBC5A, 0x9381, 0xBC5B, 0x9382, + 0xBC5C, 0x9383, 0xBC5D, 0xB9F5, 0xBC5E, 0x9384, 0xBC5F, 0x9385, 0xBC60, 0x9386, 0xBC61, 0x9387, 0xBC62, 0x9388, 0xBC63, 0x9389, + 0xBC64, 0x938A, 0xBC65, 0x938B, 0xBC66, 0x938C, 0xBC67, 0x938D, 0xBC68, 0x938E, 0xBC69, 0x938F, 0xBC6A, 0x9390, 0xBC6B, 0x9391, + 0xBC6C, 0x9392, 0xBC6D, 0x9393, 0xBC6E, 0x9394, 0xBC6F, 0x9395, 0xBC70, 0x9396, 0xBC71, 0x9397, 0xBC72, 0x9398, 0xBC73, 0x9399, + 0xBC74, 0x939A, 0xBC75, 0x939B, 0xBC76, 0x939C, 0xBC77, 0x939D, 0xBC78, 0x939E, 0xBC79, 0x939F, 0xBC7A, 0x93A0, 0xBC7B, 0x93A1, + 0xBC7C, 0x93A2, 0xBC7D, 0x93A3, 0xBC7E, 0x93A4, 0xBC7F, 0x93A5, 0xBC80, 0x93A6, 0xBC81, 0x93A7, 0xBC82, 0x93A8, 0xBC83, 0x93A9, + 0xBC84, 0xB9F6, 0xBC85, 0xB9F7, 0xBC86, 0x93AA, 0xBC87, 0x93AB, 0xBC88, 0xB9F8, 0xBC89, 0x93AC, 0xBC8A, 0x93AD, 0xBC8B, 0xB9F9, + 0xBC8C, 0xB9FA, 0xBC8D, 0x93AE, 0xBC8E, 0xB9FB, 0xBC8F, 0x93AF, 0xBC90, 0x93B0, 0xBC91, 0x93B1, 0xBC92, 0x93B2, 0xBC93, 0x93B3, + 0xBC94, 0xB9FC, 0xBC95, 0xB9FD, 0xBC96, 0x93B4, 0xBC97, 0xB9FE, 0xBC98, 0x93B5, 0xBC99, 0xBAA1, 0xBC9A, 0xBAA2, 0xBC9B, 0x93B6, + 0xBC9C, 0x93B7, 0xBC9D, 0x93B8, 0xBC9E, 0x93B9, 0xBC9F, 0x93BA, 0xBCA0, 0xBAA3, 0xBCA1, 0xBAA4, 0xBCA2, 0x93BB, 0xBCA3, 0x93BC, + 0xBCA4, 0xBAA5, 0xBCA5, 0x93BD, 0xBCA6, 0x93BE, 0xBCA7, 0xBAA6, 0xBCA8, 0xBAA7, 0xBCA9, 0x93BF, 0xBCAA, 0x93C0, 0xBCAB, 0x93C1, + 0xBCAC, 0x93C2, 0xBCAD, 0x93C3, 0xBCAE, 0x93C4, 0xBCAF, 0x93C5, 0xBCB0, 0xBAA8, 0xBCB1, 0xBAA9, 0xBCB2, 0x93C6, 0xBCB3, 0xBAAA, + 0xBCB4, 0xBAAB, 0xBCB5, 0xBAAC, 0xBCB6, 0x93C7, 0xBCB7, 0x93C8, 0xBCB8, 0x93C9, 0xBCB9, 0x93CA, 0xBCBA, 0x93CB, 0xBCBB, 0x93CC, + 0xBCBC, 0xBAAD, 0xBCBD, 0xBAAE, 0xBCBE, 0x93CD, 0xBCBF, 0x93CE, 0xBCC0, 0xBAAF, 0xBCC1, 0x93CF, 0xBCC2, 0x93D0, 0xBCC3, 0x93D1, + 0xBCC4, 0xBAB0, 0xBCC5, 0x93D2, 0xBCC6, 0x93D3, 0xBCC7, 0x93D4, 0xBCC8, 0x93D5, 0xBCC9, 0x93D6, 0xBCCA, 0x93D7, 0xBCCB, 0x93D8, + 0xBCCC, 0x93D9, 0xBCCD, 0xBAB1, 0xBCCE, 0x93DA, 0xBCCF, 0xBAB2, 0xBCD0, 0xBAB3, 0xBCD1, 0xBAB4, 0xBCD2, 0x93DB, 0xBCD3, 0x93DC, + 0xBCD4, 0x93DD, 0xBCD5, 0xBAB5, 0xBCD6, 0x93DE, 0xBCD7, 0x93DF, 0xBCD8, 0xBAB6, 0xBCD9, 0x93E0, 0xBCDA, 0x93E1, 0xBCDB, 0x93E2, + 0xBCDC, 0xBAB7, 0xBCDD, 0x93E3, 0xBCDE, 0x93E4, 0xBCDF, 0x93E5, 0xBCE0, 0x93E6, 0xBCE1, 0x93E7, 0xBCE2, 0x93E8, 0xBCE3, 0x93E9, + 0xBCE4, 0x93EA, 0xBCE5, 0x93EB, 0xBCE6, 0x93EC, 0xBCE7, 0x93ED, 0xBCE8, 0x93EE, 0xBCE9, 0x93EF, 0xBCEA, 0x93F0, 0xBCEB, 0x93F1, + 0xBCEC, 0x93F2, 0xBCED, 0x93F3, 0xBCEE, 0x93F4, 0xBCEF, 0x93F5, 0xBCF0, 0x93F6, 0xBCF1, 0x93F7, 0xBCF2, 0x93F8, 0xBCF3, 0x93F9, + 0xBCF4, 0xBAB8, 0xBCF5, 0xBAB9, 0xBCF6, 0xBABA, 0xBCF7, 0x93FA, 0xBCF8, 0xBABB, 0xBCF9, 0x93FB, 0xBCFA, 0x93FC, 0xBCFB, 0x93FD, + 0xBCFC, 0xBABC, 0xBCFD, 0x93FE, 0xBCFE, 0x9441, 0xBCFF, 0x9442, 0xBD00, 0x9443, 0xBD01, 0x9444, 0xBD02, 0x9445, 0xBD03, 0x9446, + 0xBD04, 0xBABD, 0xBD05, 0xBABE, 0xBD06, 0x9447, 0xBD07, 0xBABF, 0xBD08, 0x9448, 0xBD09, 0xBAC0, 0xBD0A, 0x9449, 0xBD0B, 0x944A, + 0xBD0C, 0x944B, 0xBD0D, 0x944C, 0xBD0E, 0x944D, 0xBD0F, 0x944E, 0xBD10, 0xBAC1, 0xBD11, 0x944F, 0xBD12, 0x9450, 0xBD13, 0x9451, + 0xBD14, 0xBAC2, 0xBD15, 0x9452, 0xBD16, 0x9453, 0xBD17, 0x9454, 0xBD18, 0x9455, 0xBD19, 0x9456, 0xBD1A, 0x9457, 0xBD1B, 0x9458, + 0xBD1C, 0x9459, 0xBD1D, 0x945A, 0xBD1E, 0x9461, 0xBD1F, 0x9462, 0xBD20, 0x9463, 0xBD21, 0x9464, 0xBD22, 0x9465, 0xBD23, 0x9466, + 0xBD24, 0xBAC3, 0xBD25, 0x9467, 0xBD26, 0x9468, 0xBD27, 0x9469, 0xBD28, 0x946A, 0xBD29, 0x946B, 0xBD2A, 0x946C, 0xBD2B, 0x946D, + 0xBD2C, 0xBAC4, 0xBD2D, 0x946E, 0xBD2E, 0x946F, 0xBD2F, 0x9470, 0xBD30, 0x9471, 0xBD31, 0x9472, 0xBD32, 0x9473, 0xBD33, 0x9474, + 0xBD34, 0x9475, 0xBD35, 0x9476, 0xBD36, 0x9477, 0xBD37, 0x9478, 0xBD38, 0x9479, 0xBD39, 0x947A, 0xBD3A, 0x9481, 0xBD3B, 0x9482, + 0xBD3C, 0x9483, 0xBD3D, 0x9484, 0xBD3E, 0x9485, 0xBD3F, 0x9486, 0xBD40, 0xBAC5, 0xBD41, 0x9487, 0xBD42, 0x9488, 0xBD43, 0x9489, + 0xBD44, 0x948A, 0xBD45, 0x948B, 0xBD46, 0x948C, 0xBD47, 0x948D, 0xBD48, 0xBAC6, 0xBD49, 0xBAC7, 0xBD4A, 0x948E, 0xBD4B, 0x948F, + 0xBD4C, 0xBAC8, 0xBD4D, 0x9490, 0xBD4E, 0x9491, 0xBD4F, 0x9492, 0xBD50, 0xBAC9, 0xBD51, 0x9493, 0xBD52, 0x9494, 0xBD53, 0x9495, + 0xBD54, 0x9496, 0xBD55, 0x9497, 0xBD56, 0x9498, 0xBD57, 0x9499, 0xBD58, 0xBACA, 0xBD59, 0xBACB, 0xBD5A, 0x949A, 0xBD5B, 0x949B, + 0xBD5C, 0x949C, 0xBD5D, 0x949D, 0xBD5E, 0x949E, 0xBD5F, 0x949F, 0xBD60, 0x94A0, 0xBD61, 0x94A1, 0xBD62, 0x94A2, 0xBD63, 0x94A3, + 0xBD64, 0xBACC, 0xBD65, 0x94A4, 0xBD66, 0x94A5, 0xBD67, 0x94A6, 0xBD68, 0xBACD, 0xBD69, 0x94A7, 0xBD6A, 0x94A8, 0xBD6B, 0x94A9, + 0xBD6C, 0x94AA, 0xBD6D, 0x94AB, 0xBD6E, 0x94AC, 0xBD6F, 0x94AD, 0xBD70, 0x94AE, 0xBD71, 0x94AF, 0xBD72, 0x94B0, 0xBD73, 0x94B1, + 0xBD74, 0x94B2, 0xBD75, 0x94B3, 0xBD76, 0x94B4, 0xBD77, 0x94B5, 0xBD78, 0x94B6, 0xBD79, 0x94B7, 0xBD7A, 0x94B8, 0xBD7B, 0x94B9, + 0xBD7C, 0x94BA, 0xBD7D, 0x94BB, 0xBD7E, 0x94BC, 0xBD7F, 0x94BD, 0xBD80, 0xBACE, 0xBD81, 0xBACF, 0xBD82, 0x94BE, 0xBD83, 0x94BF, + 0xBD84, 0xBAD0, 0xBD85, 0x94C0, 0xBD86, 0x94C1, 0xBD87, 0xBAD1, 0xBD88, 0xBAD2, 0xBD89, 0xBAD3, 0xBD8A, 0xBAD4, 0xBD8B, 0x94C2, + 0xBD8C, 0x94C3, 0xBD8D, 0x94C4, 0xBD8E, 0x94C5, 0xBD8F, 0x94C6, 0xBD90, 0xBAD5, 0xBD91, 0xBAD6, 0xBD92, 0x94C7, 0xBD93, 0xBAD7, + 0xBD94, 0x94C8, 0xBD95, 0xBAD8, 0xBD96, 0x94C9, 0xBD97, 0x94CA, 0xBD98, 0x94CB, 0xBD99, 0xBAD9, 0xBD9A, 0xBADA, 0xBD9B, 0x94CC, + 0xBD9C, 0xBADB, 0xBD9D, 0x94CD, 0xBD9E, 0x94CE, 0xBD9F, 0x94CF, 0xBDA0, 0x94D0, 0xBDA1, 0x94D1, 0xBDA2, 0x94D2, 0xBDA3, 0x94D3, + 0xBDA4, 0xBADC, 0xBDA5, 0x94D4, 0xBDA6, 0x94D5, 0xBDA7, 0x94D6, 0xBDA8, 0x94D7, 0xBDA9, 0x94D8, 0xBDAA, 0x94D9, 0xBDAB, 0x94DA, + 0xBDAC, 0x94DB, 0xBDAD, 0x94DC, 0xBDAE, 0x94DD, 0xBDAF, 0x94DE, 0xBDB0, 0xBADD, 0xBDB1, 0x94DF, 0xBDB2, 0x94E0, 0xBDB3, 0x94E1, + 0xBDB4, 0x94E2, 0xBDB5, 0x94E3, 0xBDB6, 0x94E4, 0xBDB7, 0x94E5, 0xBDB8, 0xBADE, 0xBDB9, 0x94E6, 0xBDBA, 0x94E7, 0xBDBB, 0x94E8, + 0xBDBC, 0x94E9, 0xBDBD, 0x94EA, 0xBDBE, 0x94EB, 0xBDBF, 0x94EC, 0xBDC0, 0x94ED, 0xBDC1, 0x94EE, 0xBDC2, 0x94EF, 0xBDC3, 0x94F0, + 0xBDC4, 0x94F1, 0xBDC5, 0x94F2, 0xBDC6, 0x94F3, 0xBDC7, 0x94F4, 0xBDC8, 0x94F5, 0xBDC9, 0x94F6, 0xBDCA, 0x94F7, 0xBDCB, 0x94F8, + 0xBDCC, 0x94F9, 0xBDCD, 0x94FA, 0xBDCE, 0x94FB, 0xBDCF, 0x94FC, 0xBDD0, 0x94FD, 0xBDD1, 0x94FE, 0xBDD2, 0x9541, 0xBDD3, 0x9542, + 0xBDD4, 0xBADF, 0xBDD5, 0xBAE0, 0xBDD6, 0x9543, 0xBDD7, 0x9544, 0xBDD8, 0xBAE1, 0xBDD9, 0x9545, 0xBDDA, 0x9546, 0xBDDB, 0x9547, + 0xBDDC, 0xBAE2, 0xBDDD, 0x9548, 0xBDDE, 0x9549, 0xBDDF, 0x954A, 0xBDE0, 0x954B, 0xBDE1, 0x954C, 0xBDE2, 0x954D, 0xBDE3, 0x954E, + 0xBDE4, 0x954F, 0xBDE5, 0x9550, 0xBDE6, 0x9551, 0xBDE7, 0x9552, 0xBDE8, 0x9553, 0xBDE9, 0xBAE3, 0xBDEA, 0x9554, 0xBDEB, 0x9555, + 0xBDEC, 0x9556, 0xBDED, 0x9557, 0xBDEE, 0x9558, 0xBDEF, 0x9559, 0xBDF0, 0xBAE4, 0xBDF1, 0x955A, 0xBDF2, 0x9561, 0xBDF3, 0x9562, + 0xBDF4, 0xBAE5, 0xBDF5, 0x9563, 0xBDF6, 0x9564, 0xBDF7, 0x9565, 0xBDF8, 0xBAE6, 0xBDF9, 0x9566, 0xBDFA, 0x9567, 0xBDFB, 0x9568, + 0xBDFC, 0x9569, 0xBDFD, 0x956A, 0xBDFE, 0x956B, 0xBDFF, 0x956C, 0xBE00, 0xBAE7, 0xBE01, 0x956D, 0xBE02, 0x956E, 0xBE03, 0xBAE8, + 0xBE04, 0x956F, 0xBE05, 0xBAE9, 0xBE06, 0x9570, 0xBE07, 0x9571, 0xBE08, 0x9572, 0xBE09, 0x9573, 0xBE0A, 0x9574, 0xBE0B, 0x9575, + 0xBE0C, 0xBAEA, 0xBE0D, 0xBAEB, 0xBE0E, 0x9576, 0xBE0F, 0x9577, 0xBE10, 0xBAEC, 0xBE11, 0x9578, 0xBE12, 0x9579, 0xBE13, 0x957A, + 0xBE14, 0xBAED, 0xBE15, 0x9581, 0xBE16, 0x9582, 0xBE17, 0x9583, 0xBE18, 0x9584, 0xBE19, 0x9585, 0xBE1A, 0x9586, 0xBE1B, 0x9587, + 0xBE1C, 0xBAEE, 0xBE1D, 0xBAEF, 0xBE1E, 0x9588, 0xBE1F, 0xBAF0, 0xBE20, 0x9589, 0xBE21, 0x958A, 0xBE22, 0x958B, 0xBE23, 0x958C, + 0xBE24, 0x958D, 0xBE25, 0x958E, 0xBE26, 0x958F, 0xBE27, 0x9590, 0xBE28, 0x9591, 0xBE29, 0x9592, 0xBE2A, 0x9593, 0xBE2B, 0x9594, + 0xBE2C, 0x9595, 0xBE2D, 0x9596, 0xBE2E, 0x9597, 0xBE2F, 0x9598, 0xBE30, 0x9599, 0xBE31, 0x959A, 0xBE32, 0x959B, 0xBE33, 0x959C, + 0xBE34, 0x959D, 0xBE35, 0x959E, 0xBE36, 0x959F, 0xBE37, 0x95A0, 0xBE38, 0x95A1, 0xBE39, 0x95A2, 0xBE3A, 0x95A3, 0xBE3B, 0x95A4, + 0xBE3C, 0x95A5, 0xBE3D, 0x95A6, 0xBE3E, 0x95A7, 0xBE3F, 0x95A8, 0xBE40, 0x95A9, 0xBE41, 0x95AA, 0xBE42, 0x95AB, 0xBE43, 0x95AC, + 0xBE44, 0xBAF1, 0xBE45, 0xBAF2, 0xBE46, 0x95AD, 0xBE47, 0x95AE, 0xBE48, 0xBAF3, 0xBE49, 0x95AF, 0xBE4A, 0x95B0, 0xBE4B, 0x95B1, + 0xBE4C, 0xBAF4, 0xBE4D, 0x95B2, 0xBE4E, 0xBAF5, 0xBE4F, 0x95B3, 0xBE50, 0x95B4, 0xBE51, 0x95B5, 0xBE52, 0x95B6, 0xBE53, 0x95B7, + 0xBE54, 0xBAF6, 0xBE55, 0xBAF7, 0xBE56, 0x95B8, 0xBE57, 0xBAF8, 0xBE58, 0x95B9, 0xBE59, 0xBAF9, 0xBE5A, 0xBAFA, 0xBE5B, 0xBAFB, + 0xBE5C, 0x95BA, 0xBE5D, 0x95BB, 0xBE5E, 0x95BC, 0xBE5F, 0x95BD, 0xBE60, 0xBAFC, 0xBE61, 0xBAFD, 0xBE62, 0x95BE, 0xBE63, 0x95BF, + 0xBE64, 0xBAFE, 0xBE65, 0x95C0, 0xBE66, 0x95C1, 0xBE67, 0x95C2, 0xBE68, 0xBBA1, 0xBE69, 0x95C3, 0xBE6A, 0xBBA2, 0xBE6B, 0x95C4, + 0xBE6C, 0x95C5, 0xBE6D, 0x95C6, 0xBE6E, 0x95C7, 0xBE6F, 0x95C8, 0xBE70, 0xBBA3, 0xBE71, 0xBBA4, 0xBE72, 0x95C9, 0xBE73, 0xBBA5, + 0xBE74, 0xBBA6, 0xBE75, 0xBBA7, 0xBE76, 0x95CA, 0xBE77, 0x95CB, 0xBE78, 0x95CC, 0xBE79, 0x95CD, 0xBE7A, 0x95CE, 0xBE7B, 0xBBA8, + 0xBE7C, 0xBBA9, 0xBE7D, 0xBBAA, 0xBE7E, 0x95CF, 0xBE7F, 0x95D0, 0xBE80, 0xBBAB, 0xBE81, 0x95D1, 0xBE82, 0x95D2, 0xBE83, 0x95D3, + 0xBE84, 0xBBAC, 0xBE85, 0x95D4, 0xBE86, 0x95D5, 0xBE87, 0x95D6, 0xBE88, 0x95D7, 0xBE89, 0x95D8, 0xBE8A, 0x95D9, 0xBE8B, 0x95DA, + 0xBE8C, 0xBBAD, 0xBE8D, 0xBBAE, 0xBE8E, 0x95DB, 0xBE8F, 0xBBAF, 0xBE90, 0xBBB0, 0xBE91, 0xBBB1, 0xBE92, 0x95DC, 0xBE93, 0x95DD, + 0xBE94, 0x95DE, 0xBE95, 0x95DF, 0xBE96, 0x95E0, 0xBE97, 0x95E1, 0xBE98, 0xBBB2, 0xBE99, 0xBBB3, 0xBE9A, 0x95E2, 0xBE9B, 0x95E3, + 0xBE9C, 0x95E4, 0xBE9D, 0x95E5, 0xBE9E, 0x95E6, 0xBE9F, 0x95E7, 0xBEA0, 0x95E8, 0xBEA1, 0x95E9, 0xBEA2, 0x95EA, 0xBEA3, 0x95EB, + 0xBEA4, 0x95EC, 0xBEA5, 0x95ED, 0xBEA6, 0x95EE, 0xBEA7, 0x95EF, 0xBEA8, 0xBBB4, 0xBEA9, 0x95F0, 0xBEAA, 0x95F1, 0xBEAB, 0x95F2, + 0xBEAC, 0x95F3, 0xBEAD, 0x95F4, 0xBEAE, 0x95F5, 0xBEAF, 0x95F6, 0xBEB0, 0x95F7, 0xBEB1, 0x95F8, 0xBEB2, 0x95F9, 0xBEB3, 0x95FA, + 0xBEB4, 0x95FB, 0xBEB5, 0x95FC, 0xBEB6, 0x95FD, 0xBEB7, 0x95FE, 0xBEB8, 0x9641, 0xBEB9, 0x9642, 0xBEBA, 0x9643, 0xBEBB, 0x9644, + 0xBEBC, 0x9645, 0xBEBD, 0x9646, 0xBEBE, 0x9647, 0xBEBF, 0x9648, 0xBEC0, 0x9649, 0xBEC1, 0x964A, 0xBEC2, 0x964B, 0xBEC3, 0x964C, + 0xBEC4, 0x964D, 0xBEC5, 0x964E, 0xBEC6, 0x964F, 0xBEC7, 0x9650, 0xBEC8, 0x9651, 0xBEC9, 0x9652, 0xBECA, 0x9653, 0xBECB, 0x9654, + 0xBECC, 0x9655, 0xBECD, 0x9656, 0xBECE, 0x9657, 0xBECF, 0x9658, 0xBED0, 0xBBB5, 0xBED1, 0xBBB6, 0xBED2, 0x9659, 0xBED3, 0x965A, + 0xBED4, 0xBBB7, 0xBED5, 0x9661, 0xBED6, 0x9662, 0xBED7, 0xBBB8, 0xBED8, 0xBBB9, 0xBED9, 0x9663, 0xBEDA, 0x9664, 0xBEDB, 0x9665, + 0xBEDC, 0x9666, 0xBEDD, 0x9667, 0xBEDE, 0x9668, 0xBEDF, 0x9669, 0xBEE0, 0xBBBA, 0xBEE1, 0x966A, 0xBEE2, 0x966B, 0xBEE3, 0xBBBB, + 0xBEE4, 0xBBBC, 0xBEE5, 0xBBBD, 0xBEE6, 0x966C, 0xBEE7, 0x966D, 0xBEE8, 0x966E, 0xBEE9, 0x966F, 0xBEEA, 0x9670, 0xBEEB, 0x9671, + 0xBEEC, 0xBBBE, 0xBEED, 0x9672, 0xBEEE, 0x9673, 0xBEEF, 0x9674, 0xBEF0, 0x9675, 0xBEF1, 0x9676, 0xBEF2, 0x9677, 0xBEF3, 0x9678, + 0xBEF4, 0x9679, 0xBEF5, 0x967A, 0xBEF6, 0x9681, 0xBEF7, 0x9682, 0xBEF8, 0x9683, 0xBEF9, 0x9684, 0xBEFA, 0x9685, 0xBEFB, 0x9686, + 0xBEFC, 0x9687, 0xBEFD, 0x9688, 0xBEFE, 0x9689, 0xBEFF, 0x968A, 0xBF00, 0x968B, 0xBF01, 0xBBBF, 0xBF02, 0x968C, 0xBF03, 0x968D, + 0xBF04, 0x968E, 0xBF05, 0x968F, 0xBF06, 0x9690, 0xBF07, 0x9691, 0xBF08, 0xBBC0, 0xBF09, 0xBBC1, 0xBF0A, 0x9692, 0xBF0B, 0x9693, + 0xBF0C, 0x9694, 0xBF0D, 0x9695, 0xBF0E, 0x9696, 0xBF0F, 0x9697, 0xBF10, 0x9698, 0xBF11, 0x9699, 0xBF12, 0x969A, 0xBF13, 0x969B, + 0xBF14, 0x969C, 0xBF15, 0x969D, 0xBF16, 0x969E, 0xBF17, 0x969F, 0xBF18, 0xBBC2, 0xBF19, 0xBBC3, 0xBF1A, 0x96A0, 0xBF1B, 0xBBC4, + 0xBF1C, 0xBBC5, 0xBF1D, 0xBBC6, 0xBF1E, 0x96A1, 0xBF1F, 0x96A2, 0xBF20, 0x96A3, 0xBF21, 0x96A4, 0xBF22, 0x96A5, 0xBF23, 0x96A6, + 0xBF24, 0x96A7, 0xBF25, 0x96A8, 0xBF26, 0x96A9, 0xBF27, 0x96AA, 0xBF28, 0x96AB, 0xBF29, 0x96AC, 0xBF2A, 0x96AD, 0xBF2B, 0x96AE, + 0xBF2C, 0x96AF, 0xBF2D, 0x96B0, 0xBF2E, 0x96B1, 0xBF2F, 0x96B2, 0xBF30, 0x96B3, 0xBF31, 0x96B4, 0xBF32, 0x96B5, 0xBF33, 0x96B6, + 0xBF34, 0x96B7, 0xBF35, 0x96B8, 0xBF36, 0x96B9, 0xBF37, 0x96BA, 0xBF38, 0x96BB, 0xBF39, 0x96BC, 0xBF3A, 0x96BD, 0xBF3B, 0x96BE, + 0xBF3C, 0x96BF, 0xBF3D, 0x96C0, 0xBF3E, 0x96C1, 0xBF3F, 0x96C2, 0xBF40, 0xBBC7, 0xBF41, 0xBBC8, 0xBF42, 0x96C3, 0xBF43, 0x96C4, + 0xBF44, 0xBBC9, 0xBF45, 0x96C5, 0xBF46, 0x96C6, 0xBF47, 0x96C7, 0xBF48, 0xBBCA, 0xBF49, 0x96C8, 0xBF4A, 0x96C9, 0xBF4B, 0x96CA, + 0xBF4C, 0x96CB, 0xBF4D, 0x96CC, 0xBF4E, 0x96CD, 0xBF4F, 0x96CE, 0xBF50, 0xBBCB, 0xBF51, 0xBBCC, 0xBF52, 0x96CF, 0xBF53, 0x96D0, + 0xBF54, 0x96D1, 0xBF55, 0xBBCD, 0xBF56, 0x96D2, 0xBF57, 0x96D3, 0xBF58, 0x96D4, 0xBF59, 0x96D5, 0xBF5A, 0x96D6, 0xBF5B, 0x96D7, + 0xBF5C, 0x96D8, 0xBF5D, 0x96D9, 0xBF5E, 0x96DA, 0xBF5F, 0x96DB, 0xBF60, 0x96DC, 0xBF61, 0x96DD, 0xBF62, 0x96DE, 0xBF63, 0x96DF, + 0xBF64, 0x96E0, 0xBF65, 0x96E1, 0xBF66, 0x96E2, 0xBF67, 0x96E3, 0xBF68, 0x96E4, 0xBF69, 0x96E5, 0xBF6A, 0x96E6, 0xBF6B, 0x96E7, + 0xBF6C, 0x96E8, 0xBF6D, 0x96E9, 0xBF6E, 0x96EA, 0xBF6F, 0x96EB, 0xBF70, 0x96EC, 0xBF71, 0x96ED, 0xBF72, 0x96EE, 0xBF73, 0x96EF, + 0xBF74, 0x96F0, 0xBF75, 0x96F1, 0xBF76, 0x96F2, 0xBF77, 0x96F3, 0xBF78, 0x96F4, 0xBF79, 0x96F5, 0xBF7A, 0x96F6, 0xBF7B, 0x96F7, + 0xBF7C, 0x96F8, 0xBF7D, 0x96F9, 0xBF7E, 0x96FA, 0xBF7F, 0x96FB, 0xBF80, 0x96FC, 0xBF81, 0x96FD, 0xBF82, 0x96FE, 0xBF83, 0x9741, + 0xBF84, 0x9742, 0xBF85, 0x9743, 0xBF86, 0x9744, 0xBF87, 0x9745, 0xBF88, 0x9746, 0xBF89, 0x9747, 0xBF8A, 0x9748, 0xBF8B, 0x9749, + 0xBF8C, 0x974A, 0xBF8D, 0x974B, 0xBF8E, 0x974C, 0xBF8F, 0x974D, 0xBF90, 0x974E, 0xBF91, 0x974F, 0xBF92, 0x9750, 0xBF93, 0x9751, + 0xBF94, 0xBBCE, 0xBF95, 0x9752, 0xBF96, 0x9753, 0xBF97, 0x9754, 0xBF98, 0x9755, 0xBF99, 0x9756, 0xBF9A, 0x9757, 0xBF9B, 0x9758, + 0xBF9C, 0x9759, 0xBF9D, 0x975A, 0xBF9E, 0x9761, 0xBF9F, 0x9762, 0xBFA0, 0x9763, 0xBFA1, 0x9764, 0xBFA2, 0x9765, 0xBFA3, 0x9766, + 0xBFA4, 0x9767, 0xBFA5, 0x9768, 0xBFA6, 0x9769, 0xBFA7, 0x976A, 0xBFA8, 0x976B, 0xBFA9, 0x976C, 0xBFAA, 0x976D, 0xBFAB, 0x976E, + 0xBFAC, 0x976F, 0xBFAD, 0x9770, 0xBFAE, 0x9771, 0xBFAF, 0x9772, 0xBFB0, 0xBBCF, 0xBFB1, 0x9773, 0xBFB2, 0x9774, 0xBFB3, 0x9775, + 0xBFB4, 0x9776, 0xBFB5, 0x9777, 0xBFB6, 0x9778, 0xBFB7, 0x9779, 0xBFB8, 0x977A, 0xBFB9, 0x9781, 0xBFBA, 0x9782, 0xBFBB, 0x9783, + 0xBFBC, 0x9784, 0xBFBD, 0x9785, 0xBFBE, 0x9786, 0xBFBF, 0x9787, 0xBFC0, 0x9788, 0xBFC1, 0x9789, 0xBFC2, 0x978A, 0xBFC3, 0x978B, + 0xBFC4, 0x978C, 0xBFC5, 0xBBD0, 0xBFC6, 0x978D, 0xBFC7, 0x978E, 0xBFC8, 0x978F, 0xBFC9, 0x9790, 0xBFCA, 0x9791, 0xBFCB, 0x9792, + 0xBFCC, 0xBBD1, 0xBFCD, 0xBBD2, 0xBFCE, 0x9793, 0xBFCF, 0x9794, 0xBFD0, 0xBBD3, 0xBFD1, 0x9795, 0xBFD2, 0x9796, 0xBFD3, 0x9797, + 0xBFD4, 0xBBD4, 0xBFD5, 0x9798, 0xBFD6, 0x9799, 0xBFD7, 0x979A, 0xBFD8, 0x979B, 0xBFD9, 0x979C, 0xBFDA, 0x979D, 0xBFDB, 0x979E, + 0xBFDC, 0xBBD5, 0xBFDD, 0x979F, 0xBFDE, 0x97A0, 0xBFDF, 0xBBD6, 0xBFE0, 0x97A1, 0xBFE1, 0xBBD7, 0xBFE2, 0x97A2, 0xBFE3, 0x97A3, + 0xBFE4, 0x97A4, 0xBFE5, 0x97A5, 0xBFE6, 0x97A6, 0xBFE7, 0x97A7, 0xBFE8, 0x97A8, 0xBFE9, 0x97A9, 0xBFEA, 0x97AA, 0xBFEB, 0x97AB, + 0xBFEC, 0x97AC, 0xBFED, 0x97AD, 0xBFEE, 0x97AE, 0xBFEF, 0x97AF, 0xBFF0, 0x97B0, 0xBFF1, 0x97B1, 0xBFF2, 0x97B2, 0xBFF3, 0x97B3, + 0xBFF4, 0x97B4, 0xBFF5, 0x97B5, 0xBFF6, 0x97B6, 0xBFF7, 0x97B7, 0xBFF8, 0x97B8, 0xBFF9, 0x97B9, 0xBFFA, 0x97BA, 0xBFFB, 0x97BB, + 0xBFFC, 0x97BC, 0xBFFD, 0x97BD, 0xBFFE, 0x97BE, 0xBFFF, 0x97BF, 0xC000, 0x97C0, 0xC001, 0x97C1, 0xC002, 0x97C2, 0xC003, 0x97C3, + 0xC004, 0x97C4, 0xC005, 0x97C5, 0xC006, 0x97C6, 0xC007, 0x97C7, 0xC008, 0x97C8, 0xC009, 0x97C9, 0xC00A, 0x97CA, 0xC00B, 0x97CB, + 0xC00C, 0x97CC, 0xC00D, 0x97CD, 0xC00E, 0x97CE, 0xC00F, 0x97CF, 0xC010, 0x97D0, 0xC011, 0x97D1, 0xC012, 0x97D2, 0xC013, 0x97D3, + 0xC014, 0x97D4, 0xC015, 0x97D5, 0xC016, 0x97D6, 0xC017, 0x97D7, 0xC018, 0x97D8, 0xC019, 0x97D9, 0xC01A, 0x97DA, 0xC01B, 0x97DB, + 0xC01C, 0x97DC, 0xC01D, 0x97DD, 0xC01E, 0x97DE, 0xC01F, 0x97DF, 0xC020, 0x97E0, 0xC021, 0x97E1, 0xC022, 0x97E2, 0xC023, 0x97E3, + 0xC024, 0x97E4, 0xC025, 0x97E5, 0xC026, 0x97E6, 0xC027, 0x97E7, 0xC028, 0x97E8, 0xC029, 0x97E9, 0xC02A, 0x97EA, 0xC02B, 0x97EB, + 0xC02C, 0x97EC, 0xC02D, 0x97ED, 0xC02E, 0x97EE, 0xC02F, 0x97EF, 0xC030, 0x97F0, 0xC031, 0x97F1, 0xC032, 0x97F2, 0xC033, 0x97F3, + 0xC034, 0x97F4, 0xC035, 0x97F5, 0xC036, 0x97F6, 0xC037, 0x97F7, 0xC038, 0x97F8, 0xC039, 0x97F9, 0xC03A, 0x97FA, 0xC03B, 0x97FB, + 0xC03C, 0xBBD8, 0xC03D, 0x97FC, 0xC03E, 0x97FD, 0xC03F, 0x97FE, 0xC040, 0x9841, 0xC041, 0x9842, 0xC042, 0x9843, 0xC043, 0x9844, + 0xC044, 0x9845, 0xC045, 0x9846, 0xC046, 0x9847, 0xC047, 0x9848, 0xC048, 0x9849, 0xC049, 0x984A, 0xC04A, 0x984B, 0xC04B, 0x984C, + 0xC04C, 0x984D, 0xC04D, 0x984E, 0xC04E, 0x984F, 0xC04F, 0x9850, 0xC050, 0x9851, 0xC051, 0xBBD9, 0xC052, 0x9852, 0xC053, 0x9853, + 0xC054, 0x9854, 0xC055, 0x9855, 0xC056, 0x9856, 0xC057, 0x9857, 0xC058, 0xBBDA, 0xC059, 0x9858, 0xC05A, 0x9859, 0xC05B, 0x985A, + 0xC05C, 0xBBDB, 0xC05D, 0x9861, 0xC05E, 0x9862, 0xC05F, 0x9863, 0xC060, 0xBBDC, 0xC061, 0x9864, 0xC062, 0x9865, 0xC063, 0x9866, + 0xC064, 0x9867, 0xC065, 0x9868, 0xC066, 0x9869, 0xC067, 0x986A, 0xC068, 0xBBDD, 0xC069, 0xBBDE, 0xC06A, 0x986B, 0xC06B, 0x986C, + 0xC06C, 0x986D, 0xC06D, 0x986E, 0xC06E, 0x986F, 0xC06F, 0x9870, 0xC070, 0x9871, 0xC071, 0x9872, 0xC072, 0x9873, 0xC073, 0x9874, + 0xC074, 0x9875, 0xC075, 0x9876, 0xC076, 0x9877, 0xC077, 0x9878, 0xC078, 0x9879, 0xC079, 0x987A, 0xC07A, 0x9881, 0xC07B, 0x9882, + 0xC07C, 0x9883, 0xC07D, 0x9884, 0xC07E, 0x9885, 0xC07F, 0x9886, 0xC080, 0x9887, 0xC081, 0x9888, 0xC082, 0x9889, 0xC083, 0x988A, + 0xC084, 0x988B, 0xC085, 0x988C, 0xC086, 0x988D, 0xC087, 0x988E, 0xC088, 0x988F, 0xC089, 0x9890, 0xC08A, 0x9891, 0xC08B, 0x9892, + 0xC08C, 0x9893, 0xC08D, 0x9894, 0xC08E, 0x9895, 0xC08F, 0x9896, 0xC090, 0xBBDF, 0xC091, 0xBBE0, 0xC092, 0x9897, 0xC093, 0x9898, + 0xC094, 0xBBE1, 0xC095, 0x9899, 0xC096, 0x989A, 0xC097, 0x989B, 0xC098, 0xBBE2, 0xC099, 0x989C, 0xC09A, 0x989D, 0xC09B, 0x989E, + 0xC09C, 0x989F, 0xC09D, 0x98A0, 0xC09E, 0x98A1, 0xC09F, 0x98A2, 0xC0A0, 0xBBE3, 0xC0A1, 0xBBE4, 0xC0A2, 0x98A3, 0xC0A3, 0xBBE5, + 0xC0A4, 0x98A4, 0xC0A5, 0xBBE6, 0xC0A6, 0x98A5, 0xC0A7, 0x98A6, 0xC0A8, 0x98A7, 0xC0A9, 0x98A8, 0xC0AA, 0x98A9, 0xC0AB, 0x98AA, + 0xC0AC, 0xBBE7, 0xC0AD, 0xBBE8, 0xC0AE, 0x98AB, 0xC0AF, 0xBBE9, 0xC0B0, 0xBBEA, 0xC0B1, 0x98AC, 0xC0B2, 0x98AD, 0xC0B3, 0xBBEB, + 0xC0B4, 0xBBEC, 0xC0B5, 0xBBED, 0xC0B6, 0xBBEE, 0xC0B7, 0x98AE, 0xC0B8, 0x98AF, 0xC0B9, 0x98B0, 0xC0BA, 0x98B1, 0xC0BB, 0x98B2, + 0xC0BC, 0xBBEF, 0xC0BD, 0xBBF0, 0xC0BE, 0x98B3, 0xC0BF, 0xBBF1, 0xC0C0, 0xBBF2, 0xC0C1, 0xBBF3, 0xC0C2, 0x98B4, 0xC0C3, 0x98B5, + 0xC0C4, 0x98B6, 0xC0C5, 0xBBF4, 0xC0C6, 0x98B7, 0xC0C7, 0x98B8, 0xC0C8, 0xBBF5, 0xC0C9, 0xBBF6, 0xC0CA, 0x98B9, 0xC0CB, 0x98BA, + 0xC0CC, 0xBBF7, 0xC0CD, 0x98BB, 0xC0CE, 0x98BC, 0xC0CF, 0x98BD, 0xC0D0, 0xBBF8, 0xC0D1, 0x98BE, 0xC0D2, 0x98BF, 0xC0D3, 0x98C0, + 0xC0D4, 0x98C1, 0xC0D5, 0x98C2, 0xC0D6, 0x98C3, 0xC0D7, 0x98C4, 0xC0D8, 0xBBF9, 0xC0D9, 0xBBFA, 0xC0DA, 0x98C5, 0xC0DB, 0xBBFB, + 0xC0DC, 0xBBFC, 0xC0DD, 0xBBFD, 0xC0DE, 0x98C6, 0xC0DF, 0x98C7, 0xC0E0, 0x98C8, 0xC0E1, 0x98C9, 0xC0E2, 0x98CA, 0xC0E3, 0x98CB, + 0xC0E4, 0xBBFE, 0xC0E5, 0xBCA1, 0xC0E6, 0x98CC, 0xC0E7, 0x98CD, 0xC0E8, 0xBCA2, 0xC0E9, 0x98CE, 0xC0EA, 0x98CF, 0xC0EB, 0x98D0, + 0xC0EC, 0xBCA3, 0xC0ED, 0x98D1, 0xC0EE, 0x98D2, 0xC0EF, 0x98D3, 0xC0F0, 0x98D4, 0xC0F1, 0x98D5, 0xC0F2, 0x98D6, 0xC0F3, 0x98D7, + 0xC0F4, 0xBCA4, 0xC0F5, 0xBCA5, 0xC0F6, 0x98D8, 0xC0F7, 0xBCA6, 0xC0F8, 0x98D9, 0xC0F9, 0xBCA7, 0xC0FA, 0x98DA, 0xC0FB, 0x98DB, + 0xC0FC, 0x98DC, 0xC0FD, 0x98DD, 0xC0FE, 0x98DE, 0xC0FF, 0x98DF, 0xC100, 0xBCA8, 0xC101, 0x98E0, 0xC102, 0x98E1, 0xC103, 0x98E2, + 0xC104, 0xBCA9, 0xC105, 0x98E3, 0xC106, 0x98E4, 0xC107, 0x98E5, 0xC108, 0xBCAA, 0xC109, 0x98E6, 0xC10A, 0x98E7, 0xC10B, 0x98E8, + 0xC10C, 0x98E9, 0xC10D, 0x98EA, 0xC10E, 0x98EB, 0xC10F, 0x98EC, 0xC110, 0xBCAB, 0xC111, 0x98ED, 0xC112, 0x98EE, 0xC113, 0x98EF, + 0xC114, 0x98F0, 0xC115, 0xBCAC, 0xC116, 0x98F1, 0xC117, 0x98F2, 0xC118, 0x98F3, 0xC119, 0x98F4, 0xC11A, 0x98F5, 0xC11B, 0x98F6, + 0xC11C, 0xBCAD, 0xC11D, 0xBCAE, 0xC11E, 0xBCAF, 0xC11F, 0xBCB0, 0xC120, 0xBCB1, 0xC121, 0x98F7, 0xC122, 0x98F8, 0xC123, 0xBCB2, + 0xC124, 0xBCB3, 0xC125, 0x98F9, 0xC126, 0xBCB4, 0xC127, 0xBCB5, 0xC128, 0x98FA, 0xC129, 0x98FB, 0xC12A, 0x98FC, 0xC12B, 0x98FD, + 0xC12C, 0xBCB6, 0xC12D, 0xBCB7, 0xC12E, 0x98FE, 0xC12F, 0xBCB8, 0xC130, 0xBCB9, 0xC131, 0xBCBA, 0xC132, 0x9941, 0xC133, 0x9942, + 0xC134, 0x9943, 0xC135, 0x9944, 0xC136, 0xBCBB, 0xC137, 0x9945, 0xC138, 0xBCBC, 0xC139, 0xBCBD, 0xC13A, 0x9946, 0xC13B, 0x9947, + 0xC13C, 0xBCBE, 0xC13D, 0x9948, 0xC13E, 0x9949, 0xC13F, 0x994A, 0xC140, 0xBCBF, 0xC141, 0x994B, 0xC142, 0x994C, 0xC143, 0x994D, + 0xC144, 0x994E, 0xC145, 0x994F, 0xC146, 0x9950, 0xC147, 0x9951, 0xC148, 0xBCC0, 0xC149, 0xBCC1, 0xC14A, 0x9952, 0xC14B, 0xBCC2, + 0xC14C, 0xBCC3, 0xC14D, 0xBCC4, 0xC14E, 0x9953, 0xC14F, 0x9954, 0xC150, 0x9955, 0xC151, 0x9956, 0xC152, 0x9957, 0xC153, 0x9958, + 0xC154, 0xBCC5, 0xC155, 0xBCC6, 0xC156, 0x9959, 0xC157, 0x995A, 0xC158, 0xBCC7, 0xC159, 0x9961, 0xC15A, 0x9962, 0xC15B, 0x9963, + 0xC15C, 0xBCC8, 0xC15D, 0x9964, 0xC15E, 0x9965, 0xC15F, 0x9966, 0xC160, 0x9967, 0xC161, 0x9968, 0xC162, 0x9969, 0xC163, 0x996A, + 0xC164, 0xBCC9, 0xC165, 0xBCCA, 0xC166, 0x996B, 0xC167, 0xBCCB, 0xC168, 0xBCCC, 0xC169, 0xBCCD, 0xC16A, 0x996C, 0xC16B, 0x996D, + 0xC16C, 0x996E, 0xC16D, 0x996F, 0xC16E, 0x9970, 0xC16F, 0x9971, 0xC170, 0xBCCE, 0xC171, 0x9972, 0xC172, 0x9973, 0xC173, 0x9974, + 0xC174, 0xBCCF, 0xC175, 0x9975, 0xC176, 0x9976, 0xC177, 0x9977, 0xC178, 0xBCD0, 0xC179, 0x9978, 0xC17A, 0x9979, 0xC17B, 0x997A, + 0xC17C, 0x9981, 0xC17D, 0x9982, 0xC17E, 0x9983, 0xC17F, 0x9984, 0xC180, 0x9985, 0xC181, 0x9986, 0xC182, 0x9987, 0xC183, 0x9988, + 0xC184, 0x9989, 0xC185, 0xBCD1, 0xC186, 0x998A, 0xC187, 0x998B, 0xC188, 0x998C, 0xC189, 0x998D, 0xC18A, 0x998E, 0xC18B, 0x998F, + 0xC18C, 0xBCD2, 0xC18D, 0xBCD3, 0xC18E, 0xBCD4, 0xC18F, 0x9990, 0xC190, 0xBCD5, 0xC191, 0x9991, 0xC192, 0x9992, 0xC193, 0x9993, + 0xC194, 0xBCD6, 0xC195, 0x9994, 0xC196, 0xBCD7, 0xC197, 0x9995, 0xC198, 0x9996, 0xC199, 0x9997, 0xC19A, 0x9998, 0xC19B, 0x9999, + 0xC19C, 0xBCD8, 0xC19D, 0xBCD9, 0xC19E, 0x999A, 0xC19F, 0xBCDA, 0xC1A0, 0x999B, 0xC1A1, 0xBCDB, 0xC1A2, 0x999C, 0xC1A3, 0x999D, + 0xC1A4, 0x999E, 0xC1A5, 0xBCDC, 0xC1A6, 0x999F, 0xC1A7, 0x99A0, 0xC1A8, 0xBCDD, 0xC1A9, 0xBCDE, 0xC1AA, 0x99A1, 0xC1AB, 0x99A2, + 0xC1AC, 0xBCDF, 0xC1AD, 0x99A3, 0xC1AE, 0x99A4, 0xC1AF, 0x99A5, 0xC1B0, 0xBCE0, 0xC1B1, 0x99A6, 0xC1B2, 0x99A7, 0xC1B3, 0x99A8, + 0xC1B4, 0x99A9, 0xC1B5, 0x99AA, 0xC1B6, 0x99AB, 0xC1B7, 0x99AC, 0xC1B8, 0x99AD, 0xC1B9, 0x99AE, 0xC1BA, 0x99AF, 0xC1BB, 0x99B0, + 0xC1BC, 0x99B1, 0xC1BD, 0xBCE1, 0xC1BE, 0x99B2, 0xC1BF, 0x99B3, 0xC1C0, 0x99B4, 0xC1C1, 0x99B5, 0xC1C2, 0x99B6, 0xC1C3, 0x99B7, + 0xC1C4, 0xBCE2, 0xC1C5, 0x99B8, 0xC1C6, 0x99B9, 0xC1C7, 0x99BA, 0xC1C8, 0xBCE3, 0xC1C9, 0x99BB, 0xC1CA, 0x99BC, 0xC1CB, 0x99BD, + 0xC1CC, 0xBCE4, 0xC1CD, 0x99BE, 0xC1CE, 0x99BF, 0xC1CF, 0x99C0, 0xC1D0, 0x99C1, 0xC1D1, 0x99C2, 0xC1D2, 0x99C3, 0xC1D3, 0x99C4, + 0xC1D4, 0xBCE5, 0xC1D5, 0x99C5, 0xC1D6, 0x99C6, 0xC1D7, 0xBCE6, 0xC1D8, 0xBCE7, 0xC1D9, 0x99C7, 0xC1DA, 0x99C8, 0xC1DB, 0x99C9, + 0xC1DC, 0x99CA, 0xC1DD, 0x99CB, 0xC1DE, 0x99CC, 0xC1DF, 0x99CD, 0xC1E0, 0xBCE8, 0xC1E1, 0x99CE, 0xC1E2, 0x99CF, 0xC1E3, 0x99D0, + 0xC1E4, 0xBCE9, 0xC1E5, 0x99D1, 0xC1E6, 0x99D2, 0xC1E7, 0x99D3, 0xC1E8, 0xBCEA, 0xC1E9, 0x99D4, 0xC1EA, 0x99D5, 0xC1EB, 0x99D6, + 0xC1EC, 0x99D7, 0xC1ED, 0x99D8, 0xC1EE, 0x99D9, 0xC1EF, 0x99DA, 0xC1F0, 0xBCEB, 0xC1F1, 0xBCEC, 0xC1F2, 0x99DB, 0xC1F3, 0xBCED, + 0xC1F4, 0x99DC, 0xC1F5, 0x99DD, 0xC1F6, 0x99DE, 0xC1F7, 0x99DF, 0xC1F8, 0x99E0, 0xC1F9, 0x99E1, 0xC1FA, 0x99E2, 0xC1FB, 0x99E3, + 0xC1FC, 0xBCEE, 0xC1FD, 0xBCEF, 0xC1FE, 0x99E4, 0xC1FF, 0x99E5, 0xC200, 0xBCF0, 0xC201, 0x99E6, 0xC202, 0x99E7, 0xC203, 0x99E8, + 0xC204, 0xBCF1, 0xC205, 0x99E9, 0xC206, 0x99EA, 0xC207, 0x99EB, 0xC208, 0x99EC, 0xC209, 0x99ED, 0xC20A, 0x99EE, 0xC20B, 0x99EF, + 0xC20C, 0xBCF2, 0xC20D, 0xBCF3, 0xC20E, 0x99F0, 0xC20F, 0xBCF4, 0xC210, 0x99F1, 0xC211, 0xBCF5, 0xC212, 0x99F2, 0xC213, 0x99F3, + 0xC214, 0x99F4, 0xC215, 0x99F5, 0xC216, 0x99F6, 0xC217, 0x99F7, 0xC218, 0xBCF6, 0xC219, 0xBCF7, 0xC21A, 0x99F8, 0xC21B, 0x99F9, + 0xC21C, 0xBCF8, 0xC21D, 0x99FA, 0xC21E, 0x99FB, 0xC21F, 0xBCF9, 0xC220, 0xBCFA, 0xC221, 0x99FC, 0xC222, 0x99FD, 0xC223, 0x99FE, + 0xC224, 0x9A41, 0xC225, 0x9A42, 0xC226, 0x9A43, 0xC227, 0x9A44, 0xC228, 0xBCFB, 0xC229, 0xBCFC, 0xC22A, 0x9A45, 0xC22B, 0xBCFD, + 0xC22C, 0x9A46, 0xC22D, 0xBCFE, 0xC22E, 0x9A47, 0xC22F, 0xBDA1, 0xC230, 0x9A48, 0xC231, 0xBDA2, 0xC232, 0xBDA3, 0xC233, 0x9A49, + 0xC234, 0xBDA4, 0xC235, 0x9A4A, 0xC236, 0x9A4B, 0xC237, 0x9A4C, 0xC238, 0x9A4D, 0xC239, 0x9A4E, 0xC23A, 0x9A4F, 0xC23B, 0x9A50, + 0xC23C, 0x9A51, 0xC23D, 0x9A52, 0xC23E, 0x9A53, 0xC23F, 0x9A54, 0xC240, 0x9A55, 0xC241, 0x9A56, 0xC242, 0x9A57, 0xC243, 0x9A58, + 0xC244, 0x9A59, 0xC245, 0x9A5A, 0xC246, 0x9A61, 0xC247, 0x9A62, 0xC248, 0xBDA5, 0xC249, 0x9A63, 0xC24A, 0x9A64, 0xC24B, 0x9A65, + 0xC24C, 0x9A66, 0xC24D, 0x9A67, 0xC24E, 0x9A68, 0xC24F, 0x9A69, 0xC250, 0xBDA6, 0xC251, 0xBDA7, 0xC252, 0x9A6A, 0xC253, 0x9A6B, + 0xC254, 0xBDA8, 0xC255, 0x9A6C, 0xC256, 0x9A6D, 0xC257, 0x9A6E, 0xC258, 0xBDA9, 0xC259, 0x9A6F, 0xC25A, 0x9A70, 0xC25B, 0x9A71, + 0xC25C, 0x9A72, 0xC25D, 0x9A73, 0xC25E, 0x9A74, 0xC25F, 0x9A75, 0xC260, 0xBDAA, 0xC261, 0x9A76, 0xC262, 0x9A77, 0xC263, 0x9A78, + 0xC264, 0x9A79, 0xC265, 0xBDAB, 0xC266, 0x9A7A, 0xC267, 0x9A81, 0xC268, 0x9A82, 0xC269, 0x9A83, 0xC26A, 0x9A84, 0xC26B, 0x9A85, + 0xC26C, 0xBDAC, 0xC26D, 0xBDAD, 0xC26E, 0x9A86, 0xC26F, 0x9A87, 0xC270, 0xBDAE, 0xC271, 0x9A88, 0xC272, 0x9A89, 0xC273, 0x9A8A, + 0xC274, 0xBDAF, 0xC275, 0x9A8B, 0xC276, 0x9A8C, 0xC277, 0x9A8D, 0xC278, 0x9A8E, 0xC279, 0x9A8F, 0xC27A, 0x9A90, 0xC27B, 0x9A91, + 0xC27C, 0xBDB0, 0xC27D, 0xBDB1, 0xC27E, 0x9A92, 0xC27F, 0xBDB2, 0xC280, 0x9A93, 0xC281, 0xBDB3, 0xC282, 0x9A94, 0xC283, 0x9A95, + 0xC284, 0x9A96, 0xC285, 0x9A97, 0xC286, 0x9A98, 0xC287, 0x9A99, 0xC288, 0xBDB4, 0xC289, 0xBDB5, 0xC28A, 0x9A9A, 0xC28B, 0x9A9B, + 0xC28C, 0x9A9C, 0xC28D, 0x9A9D, 0xC28E, 0x9A9E, 0xC28F, 0x9A9F, 0xC290, 0xBDB6, 0xC291, 0x9AA0, 0xC292, 0x9AA1, 0xC293, 0x9AA2, + 0xC294, 0x9AA3, 0xC295, 0x9AA4, 0xC296, 0x9AA5, 0xC297, 0x9AA6, 0xC298, 0xBDB7, 0xC299, 0x9AA7, 0xC29A, 0x9AA8, 0xC29B, 0xBDB8, + 0xC29C, 0x9AA9, 0xC29D, 0xBDB9, 0xC29E, 0x9AAA, 0xC29F, 0x9AAB, 0xC2A0, 0x9AAC, 0xC2A1, 0x9AAD, 0xC2A2, 0x9AAE, 0xC2A3, 0x9AAF, + 0xC2A4, 0xBDBA, 0xC2A5, 0xBDBB, 0xC2A6, 0x9AB0, 0xC2A7, 0x9AB1, 0xC2A8, 0xBDBC, 0xC2A9, 0x9AB2, 0xC2AA, 0x9AB3, 0xC2AB, 0x9AB4, + 0xC2AC, 0xBDBD, 0xC2AD, 0xBDBE, 0xC2AE, 0x9AB5, 0xC2AF, 0x9AB6, 0xC2B0, 0x9AB7, 0xC2B1, 0x9AB8, 0xC2B2, 0x9AB9, 0xC2B3, 0x9ABA, + 0xC2B4, 0xBDBF, 0xC2B5, 0xBDC0, 0xC2B6, 0x9ABB, 0xC2B7, 0xBDC1, 0xC2B8, 0x9ABC, 0xC2B9, 0xBDC2, 0xC2BA, 0x9ABD, 0xC2BB, 0x9ABE, + 0xC2BC, 0x9ABF, 0xC2BD, 0x9AC0, 0xC2BE, 0x9AC1, 0xC2BF, 0x9AC2, 0xC2C0, 0x9AC3, 0xC2C1, 0x9AC4, 0xC2C2, 0x9AC5, 0xC2C3, 0x9AC6, + 0xC2C4, 0x9AC7, 0xC2C5, 0x9AC8, 0xC2C6, 0x9AC9, 0xC2C7, 0x9ACA, 0xC2C8, 0x9ACB, 0xC2C9, 0x9ACC, 0xC2CA, 0x9ACD, 0xC2CB, 0x9ACE, + 0xC2CC, 0x9ACF, 0xC2CD, 0x9AD0, 0xC2CE, 0x9AD1, 0xC2CF, 0x9AD2, 0xC2D0, 0x9AD3, 0xC2D1, 0x9AD4, 0xC2D2, 0x9AD5, 0xC2D3, 0x9AD6, + 0xC2D4, 0x9AD7, 0xC2D5, 0x9AD8, 0xC2D6, 0x9AD9, 0xC2D7, 0x9ADA, 0xC2D8, 0x9ADB, 0xC2D9, 0x9ADC, 0xC2DA, 0x9ADD, 0xC2DB, 0x9ADE, + 0xC2DC, 0xBDC3, 0xC2DD, 0xBDC4, 0xC2DE, 0x9ADF, 0xC2DF, 0x9AE0, 0xC2E0, 0xBDC5, 0xC2E1, 0x9AE1, 0xC2E2, 0x9AE2, 0xC2E3, 0xBDC6, + 0xC2E4, 0xBDC7, 0xC2E5, 0x9AE3, 0xC2E6, 0x9AE4, 0xC2E7, 0x9AE5, 0xC2E8, 0x9AE6, 0xC2E9, 0x9AE7, 0xC2EA, 0x9AE8, 0xC2EB, 0xBDC8, + 0xC2EC, 0xBDC9, 0xC2ED, 0xBDCA, 0xC2EE, 0x9AE9, 0xC2EF, 0xBDCB, 0xC2F0, 0x9AEA, 0xC2F1, 0xBDCC, 0xC2F2, 0x9AEB, 0xC2F3, 0x9AEC, + 0xC2F4, 0x9AED, 0xC2F5, 0x9AEE, 0xC2F6, 0xBDCD, 0xC2F7, 0x9AEF, 0xC2F8, 0xBDCE, 0xC2F9, 0xBDCF, 0xC2FA, 0x9AF0, 0xC2FB, 0xBDD0, + 0xC2FC, 0xBDD1, 0xC2FD, 0x9AF1, 0xC2FE, 0x9AF2, 0xC2FF, 0x9AF3, 0xC300, 0xBDD2, 0xC301, 0x9AF4, 0xC302, 0x9AF5, 0xC303, 0x9AF6, + 0xC304, 0x9AF7, 0xC305, 0x9AF8, 0xC306, 0x9AF9, 0xC307, 0x9AFA, 0xC308, 0xBDD3, 0xC309, 0xBDD4, 0xC30A, 0x9AFB, 0xC30B, 0x9AFC, + 0xC30C, 0xBDD5, 0xC30D, 0xBDD6, 0xC30E, 0x9AFD, 0xC30F, 0x9AFE, 0xC310, 0x9B41, 0xC311, 0x9B42, 0xC312, 0x9B43, 0xC313, 0xBDD7, + 0xC314, 0xBDD8, 0xC315, 0xBDD9, 0xC316, 0x9B44, 0xC317, 0x9B45, 0xC318, 0xBDDA, 0xC319, 0x9B46, 0xC31A, 0x9B47, 0xC31B, 0x9B48, + 0xC31C, 0xBDDB, 0xC31D, 0x9B49, 0xC31E, 0x9B4A, 0xC31F, 0x9B4B, 0xC320, 0x9B4C, 0xC321, 0x9B4D, 0xC322, 0x9B4E, 0xC323, 0x9B4F, + 0xC324, 0xBDDC, 0xC325, 0xBDDD, 0xC326, 0x9B50, 0xC327, 0x9B51, 0xC328, 0xBDDE, 0xC329, 0xBDDF, 0xC32A, 0x9B52, 0xC32B, 0x9B53, + 0xC32C, 0x9B54, 0xC32D, 0x9B55, 0xC32E, 0x9B56, 0xC32F, 0x9B57, 0xC330, 0x9B58, 0xC331, 0x9B59, 0xC332, 0x9B5A, 0xC333, 0x9B61, + 0xC334, 0x9B62, 0xC335, 0x9B63, 0xC336, 0x9B64, 0xC337, 0x9B65, 0xC338, 0x9B66, 0xC339, 0x9B67, 0xC33A, 0x9B68, 0xC33B, 0x9B69, + 0xC33C, 0x9B6A, 0xC33D, 0x9B6B, 0xC33E, 0x9B6C, 0xC33F, 0x9B6D, 0xC340, 0x9B6E, 0xC341, 0x9B6F, 0xC342, 0x9B70, 0xC343, 0x9B71, + 0xC344, 0x9B72, 0xC345, 0xBDE0, 0xC346, 0x9B73, 0xC347, 0x9B74, 0xC348, 0x9B75, 0xC349, 0x9B76, 0xC34A, 0x9B77, 0xC34B, 0x9B78, + 0xC34C, 0x9B79, 0xC34D, 0x9B7A, 0xC34E, 0x9B81, 0xC34F, 0x9B82, 0xC350, 0x9B83, 0xC351, 0x9B84, 0xC352, 0x9B85, 0xC353, 0x9B86, + 0xC354, 0x9B87, 0xC355, 0x9B88, 0xC356, 0x9B89, 0xC357, 0x9B8A, 0xC358, 0x9B8B, 0xC359, 0x9B8C, 0xC35A, 0x9B8D, 0xC35B, 0x9B8E, + 0xC35C, 0x9B8F, 0xC35D, 0x9B90, 0xC35E, 0x9B91, 0xC35F, 0x9B92, 0xC360, 0x9B93, 0xC361, 0x9B94, 0xC362, 0x9B95, 0xC363, 0x9B96, + 0xC364, 0x9B97, 0xC365, 0x9B98, 0xC366, 0x9B99, 0xC367, 0x9B9A, 0xC368, 0xBDE1, 0xC369, 0xBDE2, 0xC36A, 0x9B9B, 0xC36B, 0x9B9C, + 0xC36C, 0xBDE3, 0xC36D, 0x9B9D, 0xC36E, 0x9B9E, 0xC36F, 0x9B9F, 0xC370, 0xBDE4, 0xC371, 0x9BA0, 0xC372, 0xBDE5, 0xC373, 0x9BA1, + 0xC374, 0x9BA2, 0xC375, 0x9BA3, 0xC376, 0x9BA4, 0xC377, 0x9BA5, 0xC378, 0xBDE6, 0xC379, 0xBDE7, 0xC37A, 0x9BA6, 0xC37B, 0x9BA7, + 0xC37C, 0xBDE8, 0xC37D, 0xBDE9, 0xC37E, 0x9BA8, 0xC37F, 0x9BA9, 0xC380, 0x9BAA, 0xC381, 0x9BAB, 0xC382, 0x9BAC, 0xC383, 0x9BAD, + 0xC384, 0xBDEA, 0xC385, 0x9BAE, 0xC386, 0x9BAF, 0xC387, 0x9BB0, 0xC388, 0xBDEB, 0xC389, 0x9BB1, 0xC38A, 0x9BB2, 0xC38B, 0x9BB3, + 0xC38C, 0xBDEC, 0xC38D, 0x9BB4, 0xC38E, 0x9BB5, 0xC38F, 0x9BB6, 0xC390, 0x9BB7, 0xC391, 0x9BB8, 0xC392, 0x9BB9, 0xC393, 0x9BBA, + 0xC394, 0x9BBB, 0xC395, 0x9BBC, 0xC396, 0x9BBD, 0xC397, 0x9BBE, 0xC398, 0x9BBF, 0xC399, 0x9BC0, 0xC39A, 0x9BC1, 0xC39B, 0x9BC2, + 0xC39C, 0x9BC3, 0xC39D, 0x9BC4, 0xC39E, 0x9BC5, 0xC39F, 0x9BC6, 0xC3A0, 0x9BC7, 0xC3A1, 0x9BC8, 0xC3A2, 0x9BC9, 0xC3A3, 0x9BCA, + 0xC3A4, 0x9BCB, 0xC3A5, 0x9BCC, 0xC3A6, 0x9BCD, 0xC3A7, 0x9BCE, 0xC3A8, 0x9BCF, 0xC3A9, 0x9BD0, 0xC3AA, 0x9BD1, 0xC3AB, 0x9BD2, + 0xC3AC, 0x9BD3, 0xC3AD, 0x9BD4, 0xC3AE, 0x9BD5, 0xC3AF, 0x9BD6, 0xC3B0, 0x9BD7, 0xC3B1, 0x9BD8, 0xC3B2, 0x9BD9, 0xC3B3, 0x9BDA, + 0xC3B4, 0x9BDB, 0xC3B5, 0x9BDC, 0xC3B6, 0x9BDD, 0xC3B7, 0x9BDE, 0xC3B8, 0x9BDF, 0xC3B9, 0x9BE0, 0xC3BA, 0x9BE1, 0xC3BB, 0x9BE2, + 0xC3BC, 0x9BE3, 0xC3BD, 0x9BE4, 0xC3BE, 0x9BE5, 0xC3BF, 0x9BE6, 0xC3C0, 0xBDED, 0xC3C1, 0x9BE7, 0xC3C2, 0x9BE8, 0xC3C3, 0x9BE9, + 0xC3C4, 0x9BEA, 0xC3C5, 0x9BEB, 0xC3C6, 0x9BEC, 0xC3C7, 0x9BED, 0xC3C8, 0x9BEE, 0xC3C9, 0x9BEF, 0xC3CA, 0x9BF0, 0xC3CB, 0x9BF1, + 0xC3CC, 0x9BF2, 0xC3CD, 0x9BF3, 0xC3CE, 0x9BF4, 0xC3CF, 0x9BF5, 0xC3D0, 0x9BF6, 0xC3D1, 0x9BF7, 0xC3D2, 0x9BF8, 0xC3D3, 0x9BF9, + 0xC3D4, 0x9BFA, 0xC3D5, 0x9BFB, 0xC3D6, 0x9BFC, 0xC3D7, 0x9BFD, 0xC3D8, 0xBDEE, 0xC3D9, 0xBDEF, 0xC3DA, 0x9BFE, 0xC3DB, 0x9C41, + 0xC3DC, 0xBDF0, 0xC3DD, 0x9C42, 0xC3DE, 0x9C43, 0xC3DF, 0xBDF1, 0xC3E0, 0xBDF2, 0xC3E1, 0x9C44, 0xC3E2, 0xBDF3, 0xC3E3, 0x9C45, + 0xC3E4, 0x9C46, 0xC3E5, 0x9C47, 0xC3E6, 0x9C48, 0xC3E7, 0x9C49, 0xC3E8, 0xBDF4, 0xC3E9, 0xBDF5, 0xC3EA, 0x9C4A, 0xC3EB, 0x9C4B, + 0xC3EC, 0x9C4C, 0xC3ED, 0xBDF6, 0xC3EE, 0x9C4D, 0xC3EF, 0x9C4E, 0xC3F0, 0x9C4F, 0xC3F1, 0x9C50, 0xC3F2, 0x9C51, 0xC3F3, 0x9C52, + 0xC3F4, 0xBDF7, 0xC3F5, 0xBDF8, 0xC3F6, 0x9C53, 0xC3F7, 0x9C54, 0xC3F8, 0xBDF9, 0xC3F9, 0x9C55, 0xC3FA, 0x9C56, 0xC3FB, 0x9C57, + 0xC3FC, 0x9C58, 0xC3FD, 0x9C59, 0xC3FE, 0x9C5A, 0xC3FF, 0x9C61, 0xC400, 0x9C62, 0xC401, 0x9C63, 0xC402, 0x9C64, 0xC403, 0x9C65, + 0xC404, 0x9C66, 0xC405, 0x9C67, 0xC406, 0x9C68, 0xC407, 0x9C69, 0xC408, 0xBDFA, 0xC409, 0x9C6A, 0xC40A, 0x9C6B, 0xC40B, 0x9C6C, + 0xC40C, 0x9C6D, 0xC40D, 0x9C6E, 0xC40E, 0x9C6F, 0xC40F, 0x9C70, 0xC410, 0xBDFB, 0xC411, 0x9C71, 0xC412, 0x9C72, 0xC413, 0x9C73, + 0xC414, 0x9C74, 0xC415, 0x9C75, 0xC416, 0x9C76, 0xC417, 0x9C77, 0xC418, 0x9C78, 0xC419, 0x9C79, 0xC41A, 0x9C7A, 0xC41B, 0x9C81, + 0xC41C, 0x9C82, 0xC41D, 0x9C83, 0xC41E, 0x9C84, 0xC41F, 0x9C85, 0xC420, 0x9C86, 0xC421, 0x9C87, 0xC422, 0x9C88, 0xC423, 0x9C89, + 0xC424, 0xBDFC, 0xC425, 0x9C8A, 0xC426, 0x9C8B, 0xC427, 0x9C8C, 0xC428, 0x9C8D, 0xC429, 0x9C8E, 0xC42A, 0x9C8F, 0xC42B, 0x9C90, + 0xC42C, 0xBDFD, 0xC42D, 0x9C91, 0xC42E, 0x9C92, 0xC42F, 0x9C93, 0xC430, 0xBDFE, 0xC431, 0x9C94, 0xC432, 0x9C95, 0xC433, 0x9C96, + 0xC434, 0xBEA1, 0xC435, 0x9C97, 0xC436, 0x9C98, 0xC437, 0x9C99, 0xC438, 0x9C9A, 0xC439, 0x9C9B, 0xC43A, 0x9C9C, 0xC43B, 0x9C9D, + 0xC43C, 0xBEA2, 0xC43D, 0xBEA3, 0xC43E, 0x9C9E, 0xC43F, 0x9C9F, 0xC440, 0x9CA0, 0xC441, 0x9CA1, 0xC442, 0x9CA2, 0xC443, 0x9CA3, + 0xC444, 0x9CA4, 0xC445, 0x9CA5, 0xC446, 0x9CA6, 0xC447, 0x9CA7, 0xC448, 0xBEA4, 0xC449, 0x9CA8, 0xC44A, 0x9CA9, 0xC44B, 0x9CAA, + 0xC44C, 0x9CAB, 0xC44D, 0x9CAC, 0xC44E, 0x9CAD, 0xC44F, 0x9CAE, 0xC450, 0x9CAF, 0xC451, 0x9CB0, 0xC452, 0x9CB1, 0xC453, 0x9CB2, + 0xC454, 0x9CB3, 0xC455, 0x9CB4, 0xC456, 0x9CB5, 0xC457, 0x9CB6, 0xC458, 0x9CB7, 0xC459, 0x9CB8, 0xC45A, 0x9CB9, 0xC45B, 0x9CBA, + 0xC45C, 0x9CBB, 0xC45D, 0x9CBC, 0xC45E, 0x9CBD, 0xC45F, 0x9CBE, 0xC460, 0x9CBF, 0xC461, 0x9CC0, 0xC462, 0x9CC1, 0xC463, 0x9CC2, + 0xC464, 0xBEA5, 0xC465, 0xBEA6, 0xC466, 0x9CC3, 0xC467, 0x9CC4, 0xC468, 0xBEA7, 0xC469, 0x9CC5, 0xC46A, 0x9CC6, 0xC46B, 0x9CC7, + 0xC46C, 0xBEA8, 0xC46D, 0x9CC8, 0xC46E, 0x9CC9, 0xC46F, 0x9CCA, 0xC470, 0x9CCB, 0xC471, 0x9CCC, 0xC472, 0x9CCD, 0xC473, 0x9CCE, + 0xC474, 0xBEA9, 0xC475, 0xBEAA, 0xC476, 0x9CCF, 0xC477, 0x9CD0, 0xC478, 0x9CD1, 0xC479, 0xBEAB, 0xC47A, 0x9CD2, 0xC47B, 0x9CD3, + 0xC47C, 0x9CD4, 0xC47D, 0x9CD5, 0xC47E, 0x9CD6, 0xC47F, 0x9CD7, 0xC480, 0xBEAC, 0xC481, 0x9CD8, 0xC482, 0x9CD9, 0xC483, 0x9CDA, + 0xC484, 0x9CDB, 0xC485, 0x9CDC, 0xC486, 0x9CDD, 0xC487, 0x9CDE, 0xC488, 0x9CDF, 0xC489, 0x9CE0, 0xC48A, 0x9CE1, 0xC48B, 0x9CE2, + 0xC48C, 0x9CE3, 0xC48D, 0x9CE4, 0xC48E, 0x9CE5, 0xC48F, 0x9CE6, 0xC490, 0x9CE7, 0xC491, 0x9CE8, 0xC492, 0x9CE9, 0xC493, 0x9CEA, + 0xC494, 0xBEAD, 0xC495, 0x9CEB, 0xC496, 0x9CEC, 0xC497, 0x9CED, 0xC498, 0x9CEE, 0xC499, 0x9CEF, 0xC49A, 0x9CF0, 0xC49B, 0x9CF1, + 0xC49C, 0xBEAE, 0xC49D, 0x9CF2, 0xC49E, 0x9CF3, 0xC49F, 0x9CF4, 0xC4A0, 0x9CF5, 0xC4A1, 0x9CF6, 0xC4A2, 0x9CF7, 0xC4A3, 0x9CF8, + 0xC4A4, 0x9CF9, 0xC4A5, 0x9CFA, 0xC4A6, 0x9CFB, 0xC4A7, 0x9CFC, 0xC4A8, 0x9CFD, 0xC4A9, 0x9CFE, 0xC4AA, 0x9D41, 0xC4AB, 0x9D42, + 0xC4AC, 0x9D43, 0xC4AD, 0x9D44, 0xC4AE, 0x9D45, 0xC4AF, 0x9D46, 0xC4B0, 0x9D47, 0xC4B1, 0x9D48, 0xC4B2, 0x9D49, 0xC4B3, 0x9D4A, + 0xC4B4, 0x9D4B, 0xC4B5, 0x9D4C, 0xC4B6, 0x9D4D, 0xC4B7, 0x9D4E, 0xC4B8, 0xBEAF, 0xC4B9, 0x9D4F, 0xC4BA, 0x9D50, 0xC4BB, 0x9D51, + 0xC4BC, 0xBEB0, 0xC4BD, 0x9D52, 0xC4BE, 0x9D53, 0xC4BF, 0x9D54, 0xC4C0, 0x9D55, 0xC4C1, 0x9D56, 0xC4C2, 0x9D57, 0xC4C3, 0x9D58, + 0xC4C4, 0x9D59, 0xC4C5, 0x9D5A, 0xC4C6, 0x9D61, 0xC4C7, 0x9D62, 0xC4C8, 0x9D63, 0xC4C9, 0x9D64, 0xC4CA, 0x9D65, 0xC4CB, 0x9D66, + 0xC4CC, 0x9D67, 0xC4CD, 0x9D68, 0xC4CE, 0x9D69, 0xC4CF, 0x9D6A, 0xC4D0, 0x9D6B, 0xC4D1, 0x9D6C, 0xC4D2, 0x9D6D, 0xC4D3, 0x9D6E, + 0xC4D4, 0x9D6F, 0xC4D5, 0x9D70, 0xC4D6, 0x9D71, 0xC4D7, 0x9D72, 0xC4D8, 0x9D73, 0xC4D9, 0x9D74, 0xC4DA, 0x9D75, 0xC4DB, 0x9D76, + 0xC4DC, 0x9D77, 0xC4DD, 0x9D78, 0xC4DE, 0x9D79, 0xC4DF, 0x9D7A, 0xC4E0, 0x9D81, 0xC4E1, 0x9D82, 0xC4E2, 0x9D83, 0xC4E3, 0x9D84, + 0xC4E4, 0x9D85, 0xC4E5, 0x9D86, 0xC4E6, 0x9D87, 0xC4E7, 0x9D88, 0xC4E8, 0x9D89, 0xC4E9, 0xBEB1, 0xC4EA, 0x9D8A, 0xC4EB, 0x9D8B, + 0xC4EC, 0x9D8C, 0xC4ED, 0x9D8D, 0xC4EE, 0x9D8E, 0xC4EF, 0x9D8F, 0xC4F0, 0xBEB2, 0xC4F1, 0xBEB3, 0xC4F2, 0x9D90, 0xC4F3, 0x9D91, + 0xC4F4, 0xBEB4, 0xC4F5, 0x9D92, 0xC4F6, 0x9D93, 0xC4F7, 0x9D94, 0xC4F8, 0xBEB5, 0xC4F9, 0x9D95, 0xC4FA, 0xBEB6, 0xC4FB, 0x9D96, + 0xC4FC, 0x9D97, 0xC4FD, 0x9D98, 0xC4FE, 0x9D99, 0xC4FF, 0xBEB7, 0xC500, 0xBEB8, 0xC501, 0xBEB9, 0xC502, 0x9D9A, 0xC503, 0x9D9B, + 0xC504, 0x9D9C, 0xC505, 0x9D9D, 0xC506, 0x9D9E, 0xC507, 0x9D9F, 0xC508, 0x9DA0, 0xC509, 0x9DA1, 0xC50A, 0x9DA2, 0xC50B, 0x9DA3, + 0xC50C, 0xBEBA, 0xC50D, 0x9DA4, 0xC50E, 0x9DA5, 0xC50F, 0x9DA6, 0xC510, 0xBEBB, 0xC511, 0x9DA7, 0xC512, 0x9DA8, 0xC513, 0x9DA9, + 0xC514, 0xBEBC, 0xC515, 0x9DAA, 0xC516, 0x9DAB, 0xC517, 0x9DAC, 0xC518, 0x9DAD, 0xC519, 0x9DAE, 0xC51A, 0x9DAF, 0xC51B, 0x9DB0, + 0xC51C, 0xBEBD, 0xC51D, 0x9DB1, 0xC51E, 0x9DB2, 0xC51F, 0x9DB3, 0xC520, 0x9DB4, 0xC521, 0x9DB5, 0xC522, 0x9DB6, 0xC523, 0x9DB7, + 0xC524, 0x9DB8, 0xC525, 0x9DB9, 0xC526, 0x9DBA, 0xC527, 0x9DBB, 0xC528, 0xBEBE, 0xC529, 0xBEBF, 0xC52A, 0x9DBC, 0xC52B, 0x9DBD, + 0xC52C, 0xBEC0, 0xC52D, 0x9DBE, 0xC52E, 0x9DBF, 0xC52F, 0x9DC0, 0xC530, 0xBEC1, 0xC531, 0x9DC1, 0xC532, 0x9DC2, 0xC533, 0x9DC3, + 0xC534, 0x9DC4, 0xC535, 0x9DC5, 0xC536, 0x9DC6, 0xC537, 0x9DC7, 0xC538, 0xBEC2, 0xC539, 0xBEC3, 0xC53A, 0x9DC8, 0xC53B, 0xBEC4, + 0xC53C, 0x9DC9, 0xC53D, 0xBEC5, 0xC53E, 0x9DCA, 0xC53F, 0x9DCB, 0xC540, 0x9DCC, 0xC541, 0x9DCD, 0xC542, 0x9DCE, 0xC543, 0x9DCF, + 0xC544, 0xBEC6, 0xC545, 0xBEC7, 0xC546, 0x9DD0, 0xC547, 0x9DD1, 0xC548, 0xBEC8, 0xC549, 0xBEC9, 0xC54A, 0xBECA, 0xC54B, 0x9DD2, + 0xC54C, 0xBECB, 0xC54D, 0xBECC, 0xC54E, 0xBECD, 0xC54F, 0x9DD3, 0xC550, 0x9DD4, 0xC551, 0x9DD5, 0xC552, 0x9DD6, 0xC553, 0xBECE, + 0xC554, 0xBECF, 0xC555, 0xBED0, 0xC556, 0x9DD7, 0xC557, 0xBED1, 0xC558, 0xBED2, 0xC559, 0xBED3, 0xC55A, 0x9DD8, 0xC55B, 0x9DD9, + 0xC55C, 0x9DDA, 0xC55D, 0xBED4, 0xC55E, 0xBED5, 0xC55F, 0x9DDB, 0xC560, 0xBED6, 0xC561, 0xBED7, 0xC562, 0x9DDC, 0xC563, 0x9DDD, + 0xC564, 0xBED8, 0xC565, 0x9DDE, 0xC566, 0x9DDF, 0xC567, 0x9DE0, 0xC568, 0xBED9, 0xC569, 0x9DE1, 0xC56A, 0x9DE2, 0xC56B, 0x9DE3, + 0xC56C, 0x9DE4, 0xC56D, 0x9DE5, 0xC56E, 0x9DE6, 0xC56F, 0x9DE7, 0xC570, 0xBEDA, 0xC571, 0xBEDB, 0xC572, 0x9DE8, 0xC573, 0xBEDC, + 0xC574, 0xBEDD, 0xC575, 0xBEDE, 0xC576, 0x9DE9, 0xC577, 0x9DEA, 0xC578, 0x9DEB, 0xC579, 0x9DEC, 0xC57A, 0x9DED, 0xC57B, 0x9DEE, + 0xC57C, 0xBEDF, 0xC57D, 0xBEE0, 0xC57E, 0x9DEF, 0xC57F, 0x9DF0, 0xC580, 0xBEE1, 0xC581, 0x9DF1, 0xC582, 0x9DF2, 0xC583, 0x9DF3, + 0xC584, 0xBEE2, 0xC585, 0x9DF4, 0xC586, 0x9DF5, 0xC587, 0xBEE3, 0xC588, 0x9DF6, 0xC589, 0x9DF7, 0xC58A, 0x9DF8, 0xC58B, 0x9DF9, + 0xC58C, 0xBEE4, 0xC58D, 0xBEE5, 0xC58E, 0x9DFA, 0xC58F, 0xBEE6, 0xC590, 0x9DFB, 0xC591, 0xBEE7, 0xC592, 0x9DFC, 0xC593, 0x9DFD, + 0xC594, 0x9DFE, 0xC595, 0xBEE8, 0xC596, 0x9E41, 0xC597, 0xBEE9, 0xC598, 0xBEEA, 0xC599, 0x9E42, 0xC59A, 0x9E43, 0xC59B, 0x9E44, + 0xC59C, 0xBEEB, 0xC59D, 0x9E45, 0xC59E, 0x9E46, 0xC59F, 0x9E47, 0xC5A0, 0xBEEC, 0xC5A1, 0x9E48, 0xC5A2, 0x9E49, 0xC5A3, 0x9E4A, + 0xC5A4, 0x9E4B, 0xC5A5, 0x9E4C, 0xC5A6, 0x9E4D, 0xC5A7, 0x9E4E, 0xC5A8, 0x9E4F, 0xC5A9, 0xBEED, 0xC5AA, 0x9E50, 0xC5AB, 0x9E51, + 0xC5AC, 0x9E52, 0xC5AD, 0x9E53, 0xC5AE, 0x9E54, 0xC5AF, 0x9E55, 0xC5B0, 0x9E56, 0xC5B1, 0x9E57, 0xC5B2, 0x9E58, 0xC5B3, 0x9E59, + 0xC5B4, 0xBEEE, 0xC5B5, 0xBEEF, 0xC5B6, 0x9E5A, 0xC5B7, 0x9E61, 0xC5B8, 0xBEF0, 0xC5B9, 0xBEF1, 0xC5BA, 0x9E62, 0xC5BB, 0xBEF2, + 0xC5BC, 0xBEF3, 0xC5BD, 0xBEF4, 0xC5BE, 0xBEF5, 0xC5BF, 0x9E63, 0xC5C0, 0x9E64, 0xC5C1, 0x9E65, 0xC5C2, 0x9E66, 0xC5C3, 0x9E67, + 0xC5C4, 0xBEF6, 0xC5C5, 0xBEF7, 0xC5C6, 0xBEF8, 0xC5C7, 0xBEF9, 0xC5C8, 0xBEFA, 0xC5C9, 0xBEFB, 0xC5CA, 0xBEFC, 0xC5CB, 0x9E68, + 0xC5CC, 0xBEFD, 0xC5CD, 0x9E69, 0xC5CE, 0xBEFE, 0xC5CF, 0x9E6A, 0xC5D0, 0xBFA1, 0xC5D1, 0xBFA2, 0xC5D2, 0x9E6B, 0xC5D3, 0x9E6C, + 0xC5D4, 0xBFA3, 0xC5D5, 0x9E6D, 0xC5D6, 0x9E6E, 0xC5D7, 0x9E6F, 0xC5D8, 0xBFA4, 0xC5D9, 0x9E70, 0xC5DA, 0x9E71, 0xC5DB, 0x9E72, + 0xC5DC, 0x9E73, 0xC5DD, 0x9E74, 0xC5DE, 0x9E75, 0xC5DF, 0x9E76, 0xC5E0, 0xBFA5, 0xC5E1, 0xBFA6, 0xC5E2, 0x9E77, 0xC5E3, 0xBFA7, + 0xC5E4, 0x9E78, 0xC5E5, 0xBFA8, 0xC5E6, 0x9E79, 0xC5E7, 0x9E7A, 0xC5E8, 0x9E81, 0xC5E9, 0x9E82, 0xC5EA, 0x9E83, 0xC5EB, 0x9E84, + 0xC5EC, 0xBFA9, 0xC5ED, 0xBFAA, 0xC5EE, 0xBFAB, 0xC5EF, 0x9E85, 0xC5F0, 0xBFAC, 0xC5F1, 0x9E86, 0xC5F2, 0x9E87, 0xC5F3, 0x9E88, + 0xC5F4, 0xBFAD, 0xC5F5, 0x9E89, 0xC5F6, 0xBFAE, 0xC5F7, 0xBFAF, 0xC5F8, 0x9E8A, 0xC5F9, 0x9E8B, 0xC5FA, 0x9E8C, 0xC5FB, 0x9E8D, + 0xC5FC, 0xBFB0, 0xC5FD, 0xBFB1, 0xC5FE, 0xBFB2, 0xC5FF, 0xBFB3, 0xC600, 0xBFB4, 0xC601, 0xBFB5, 0xC602, 0x9E8E, 0xC603, 0x9E8F, + 0xC604, 0x9E90, 0xC605, 0xBFB6, 0xC606, 0xBFB7, 0xC607, 0xBFB8, 0xC608, 0xBFB9, 0xC609, 0x9E91, 0xC60A, 0x9E92, 0xC60B, 0x9E93, + 0xC60C, 0xBFBA, 0xC60D, 0x9E94, 0xC60E, 0x9E95, 0xC60F, 0x9E96, 0xC610, 0xBFBB, 0xC611, 0x9E97, 0xC612, 0x9E98, 0xC613, 0x9E99, + 0xC614, 0x9E9A, 0xC615, 0x9E9B, 0xC616, 0x9E9C, 0xC617, 0x9E9D, 0xC618, 0xBFBC, 0xC619, 0xBFBD, 0xC61A, 0x9E9E, 0xC61B, 0xBFBE, + 0xC61C, 0xBFBF, 0xC61D, 0x9E9F, 0xC61E, 0x9EA0, 0xC61F, 0x9EA1, 0xC620, 0x9EA2, 0xC621, 0x9EA3, 0xC622, 0x9EA4, 0xC623, 0x9EA5, + 0xC624, 0xBFC0, 0xC625, 0xBFC1, 0xC626, 0x9EA6, 0xC627, 0x9EA7, 0xC628, 0xBFC2, 0xC629, 0x9EA8, 0xC62A, 0x9EA9, 0xC62B, 0x9EAA, + 0xC62C, 0xBFC3, 0xC62D, 0xBFC4, 0xC62E, 0xBFC5, 0xC62F, 0x9EAB, 0xC630, 0xBFC6, 0xC631, 0x9EAC, 0xC632, 0x9EAD, 0xC633, 0xBFC7, + 0xC634, 0xBFC8, 0xC635, 0xBFC9, 0xC636, 0x9EAE, 0xC637, 0xBFCA, 0xC638, 0x9EAF, 0xC639, 0xBFCB, 0xC63A, 0x9EB0, 0xC63B, 0xBFCC, + 0xC63C, 0x9EB1, 0xC63D, 0x9EB2, 0xC63E, 0x9EB3, 0xC63F, 0x9EB4, 0xC640, 0xBFCD, 0xC641, 0xBFCE, 0xC642, 0x9EB5, 0xC643, 0x9EB6, + 0xC644, 0xBFCF, 0xC645, 0x9EB7, 0xC646, 0x9EB8, 0xC647, 0x9EB9, 0xC648, 0xBFD0, 0xC649, 0x9EBA, 0xC64A, 0x9EBB, 0xC64B, 0x9EBC, + 0xC64C, 0x9EBD, 0xC64D, 0x9EBE, 0xC64E, 0x9EBF, 0xC64F, 0x9EC0, 0xC650, 0xBFD1, 0xC651, 0xBFD2, 0xC652, 0x9EC1, 0xC653, 0xBFD3, + 0xC654, 0xBFD4, 0xC655, 0xBFD5, 0xC656, 0x9EC2, 0xC657, 0x9EC3, 0xC658, 0x9EC4, 0xC659, 0x9EC5, 0xC65A, 0x9EC6, 0xC65B, 0x9EC7, + 0xC65C, 0xBFD6, 0xC65D, 0xBFD7, 0xC65E, 0x9EC8, 0xC65F, 0x9EC9, 0xC660, 0xBFD8, 0xC661, 0x9ECA, 0xC662, 0x9ECB, 0xC663, 0x9ECC, + 0xC664, 0x9ECD, 0xC665, 0x9ECE, 0xC666, 0x9ECF, 0xC667, 0x9ED0, 0xC668, 0x9ED1, 0xC669, 0x9ED2, 0xC66A, 0x9ED3, 0xC66B, 0x9ED4, + 0xC66C, 0xBFD9, 0xC66D, 0x9ED5, 0xC66E, 0x9ED6, 0xC66F, 0xBFDA, 0xC670, 0x9ED7, 0xC671, 0xBFDB, 0xC672, 0x9ED8, 0xC673, 0x9ED9, + 0xC674, 0x9EDA, 0xC675, 0x9EDB, 0xC676, 0x9EDC, 0xC677, 0x9EDD, 0xC678, 0xBFDC, 0xC679, 0xBFDD, 0xC67A, 0x9EDE, 0xC67B, 0x9EDF, + 0xC67C, 0xBFDE, 0xC67D, 0x9EE0, 0xC67E, 0x9EE1, 0xC67F, 0x9EE2, 0xC680, 0xBFDF, 0xC681, 0x9EE3, 0xC682, 0x9EE4, 0xC683, 0x9EE5, + 0xC684, 0x9EE6, 0xC685, 0x9EE7, 0xC686, 0x9EE8, 0xC687, 0x9EE9, 0xC688, 0xBFE0, 0xC689, 0xBFE1, 0xC68A, 0x9EEA, 0xC68B, 0xBFE2, + 0xC68C, 0x9EEB, 0xC68D, 0xBFE3, 0xC68E, 0x9EEC, 0xC68F, 0x9EED, 0xC690, 0x9EEE, 0xC691, 0x9EEF, 0xC692, 0x9EF0, 0xC693, 0x9EF1, + 0xC694, 0xBFE4, 0xC695, 0xBFE5, 0xC696, 0x9EF2, 0xC697, 0x9EF3, 0xC698, 0xBFE6, 0xC699, 0x9EF4, 0xC69A, 0x9EF5, 0xC69B, 0x9EF6, + 0xC69C, 0xBFE7, 0xC69D, 0x9EF7, 0xC69E, 0x9EF8, 0xC69F, 0x9EF9, 0xC6A0, 0x9EFA, 0xC6A1, 0x9EFB, 0xC6A2, 0x9EFC, 0xC6A3, 0x9EFD, + 0xC6A4, 0xBFE8, 0xC6A5, 0xBFE9, 0xC6A6, 0x9EFE, 0xC6A7, 0xBFEA, 0xC6A8, 0x9F41, 0xC6A9, 0xBFEB, 0xC6AA, 0x9F42, 0xC6AB, 0x9F43, + 0xC6AC, 0x9F44, 0xC6AD, 0x9F45, 0xC6AE, 0x9F46, 0xC6AF, 0x9F47, 0xC6B0, 0xBFEC, 0xC6B1, 0xBFED, 0xC6B2, 0x9F48, 0xC6B3, 0x9F49, + 0xC6B4, 0xBFEE, 0xC6B5, 0x9F4A, 0xC6B6, 0x9F4B, 0xC6B7, 0x9F4C, 0xC6B8, 0xBFEF, 0xC6B9, 0xBFF0, 0xC6BA, 0xBFF1, 0xC6BB, 0x9F4D, + 0xC6BC, 0x9F4E, 0xC6BD, 0x9F4F, 0xC6BE, 0x9F50, 0xC6BF, 0x9F51, 0xC6C0, 0xBFF2, 0xC6C1, 0xBFF3, 0xC6C2, 0x9F52, 0xC6C3, 0xBFF4, + 0xC6C4, 0x9F53, 0xC6C5, 0xBFF5, 0xC6C6, 0x9F54, 0xC6C7, 0x9F55, 0xC6C8, 0x9F56, 0xC6C9, 0x9F57, 0xC6CA, 0x9F58, 0xC6CB, 0x9F59, + 0xC6CC, 0xBFF6, 0xC6CD, 0xBFF7, 0xC6CE, 0x9F5A, 0xC6CF, 0x9F61, 0xC6D0, 0xBFF8, 0xC6D1, 0x9F62, 0xC6D2, 0x9F63, 0xC6D3, 0x9F64, + 0xC6D4, 0xBFF9, 0xC6D5, 0x9F65, 0xC6D6, 0x9F66, 0xC6D7, 0x9F67, 0xC6D8, 0x9F68, 0xC6D9, 0x9F69, 0xC6DA, 0x9F6A, 0xC6DB, 0x9F6B, + 0xC6DC, 0xBFFA, 0xC6DD, 0xBFFB, 0xC6DE, 0x9F6C, 0xC6DF, 0x9F6D, 0xC6E0, 0xBFFC, 0xC6E1, 0xBFFD, 0xC6E2, 0x9F6E, 0xC6E3, 0x9F6F, + 0xC6E4, 0x9F70, 0xC6E5, 0x9F71, 0xC6E6, 0x9F72, 0xC6E7, 0x9F73, 0xC6E8, 0xBFFE, 0xC6E9, 0xC0A1, 0xC6EA, 0x9F74, 0xC6EB, 0x9F75, + 0xC6EC, 0xC0A2, 0xC6ED, 0x9F76, 0xC6EE, 0x9F77, 0xC6EF, 0x9F78, 0xC6F0, 0xC0A3, 0xC6F1, 0x9F79, 0xC6F2, 0x9F7A, 0xC6F3, 0x9F81, + 0xC6F4, 0x9F82, 0xC6F5, 0x9F83, 0xC6F6, 0x9F84, 0xC6F7, 0x9F85, 0xC6F8, 0xC0A4, 0xC6F9, 0xC0A5, 0xC6FA, 0x9F86, 0xC6FB, 0x9F87, + 0xC6FC, 0x9F88, 0xC6FD, 0xC0A6, 0xC6FE, 0x9F89, 0xC6FF, 0x9F8A, 0xC700, 0x9F8B, 0xC701, 0x9F8C, 0xC702, 0x9F8D, 0xC703, 0x9F8E, + 0xC704, 0xC0A7, 0xC705, 0xC0A8, 0xC706, 0x9F8F, 0xC707, 0x9F90, 0xC708, 0xC0A9, 0xC709, 0x9F91, 0xC70A, 0x9F92, 0xC70B, 0x9F93, + 0xC70C, 0xC0AA, 0xC70D, 0x9F94, 0xC70E, 0x9F95, 0xC70F, 0x9F96, 0xC710, 0x9F97, 0xC711, 0x9F98, 0xC712, 0x9F99, 0xC713, 0x9F9A, + 0xC714, 0xC0AB, 0xC715, 0xC0AC, 0xC716, 0x9F9B, 0xC717, 0xC0AD, 0xC718, 0x9F9C, 0xC719, 0xC0AE, 0xC71A, 0x9F9D, 0xC71B, 0x9F9E, + 0xC71C, 0x9F9F, 0xC71D, 0x9FA0, 0xC71E, 0x9FA1, 0xC71F, 0x9FA2, 0xC720, 0xC0AF, 0xC721, 0xC0B0, 0xC722, 0x9FA3, 0xC723, 0x9FA4, + 0xC724, 0xC0B1, 0xC725, 0x9FA5, 0xC726, 0x9FA6, 0xC727, 0x9FA7, 0xC728, 0xC0B2, 0xC729, 0x9FA8, 0xC72A, 0x9FA9, 0xC72B, 0x9FAA, + 0xC72C, 0x9FAB, 0xC72D, 0x9FAC, 0xC72E, 0x9FAD, 0xC72F, 0x9FAE, 0xC730, 0xC0B3, 0xC731, 0xC0B4, 0xC732, 0x9FAF, 0xC733, 0xC0B5, + 0xC734, 0x9FB0, 0xC735, 0xC0B6, 0xC736, 0x9FB1, 0xC737, 0xC0B7, 0xC738, 0x9FB2, 0xC739, 0x9FB3, 0xC73A, 0x9FB4, 0xC73B, 0x9FB5, + 0xC73C, 0xC0B8, 0xC73D, 0xC0B9, 0xC73E, 0x9FB6, 0xC73F, 0x9FB7, 0xC740, 0xC0BA, 0xC741, 0x9FB8, 0xC742, 0x9FB9, 0xC743, 0x9FBA, + 0xC744, 0xC0BB, 0xC745, 0x9FBB, 0xC746, 0x9FBC, 0xC747, 0x9FBD, 0xC748, 0x9FBE, 0xC749, 0x9FBF, 0xC74A, 0xC0BC, 0xC74B, 0x9FC0, + 0xC74C, 0xC0BD, 0xC74D, 0xC0BE, 0xC74E, 0x9FC1, 0xC74F, 0xC0BF, 0xC750, 0x9FC2, 0xC751, 0xC0C0, 0xC752, 0xC0C1, 0xC753, 0xC0C2, + 0xC754, 0xC0C3, 0xC755, 0xC0C4, 0xC756, 0xC0C5, 0xC757, 0xC0C6, 0xC758, 0xC0C7, 0xC759, 0x9FC3, 0xC75A, 0x9FC4, 0xC75B, 0x9FC5, + 0xC75C, 0xC0C8, 0xC75D, 0x9FC6, 0xC75E, 0x9FC7, 0xC75F, 0x9FC8, 0xC760, 0xC0C9, 0xC761, 0x9FC9, 0xC762, 0x9FCA, 0xC763, 0x9FCB, + 0xC764, 0x9FCC, 0xC765, 0x9FCD, 0xC766, 0x9FCE, 0xC767, 0x9FCF, 0xC768, 0xC0CA, 0xC769, 0x9FD0, 0xC76A, 0x9FD1, 0xC76B, 0xC0CB, + 0xC76C, 0x9FD2, 0xC76D, 0x9FD3, 0xC76E, 0x9FD4, 0xC76F, 0x9FD5, 0xC770, 0x9FD6, 0xC771, 0x9FD7, 0xC772, 0x9FD8, 0xC773, 0x9FD9, + 0xC774, 0xC0CC, 0xC775, 0xC0CD, 0xC776, 0x9FDA, 0xC777, 0x9FDB, 0xC778, 0xC0CE, 0xC779, 0x9FDC, 0xC77A, 0x9FDD, 0xC77B, 0x9FDE, + 0xC77C, 0xC0CF, 0xC77D, 0xC0D0, 0xC77E, 0xC0D1, 0xC77F, 0x9FDF, 0xC780, 0x9FE0, 0xC781, 0x9FE1, 0xC782, 0x9FE2, 0xC783, 0xC0D2, + 0xC784, 0xC0D3, 0xC785, 0xC0D4, 0xC786, 0x9FE3, 0xC787, 0xC0D5, 0xC788, 0xC0D6, 0xC789, 0xC0D7, 0xC78A, 0xC0D8, 0xC78B, 0x9FE4, + 0xC78C, 0x9FE5, 0xC78D, 0x9FE6, 0xC78E, 0xC0D9, 0xC78F, 0x9FE7, 0xC790, 0xC0DA, 0xC791, 0xC0DB, 0xC792, 0x9FE8, 0xC793, 0x9FE9, + 0xC794, 0xC0DC, 0xC795, 0x9FEA, 0xC796, 0xC0DD, 0xC797, 0xC0DE, 0xC798, 0xC0DF, 0xC799, 0x9FEB, 0xC79A, 0xC0E0, 0xC79B, 0x9FEC, + 0xC79C, 0x9FED, 0xC79D, 0x9FEE, 0xC79E, 0x9FEF, 0xC79F, 0x9FF0, 0xC7A0, 0xC0E1, 0xC7A1, 0xC0E2, 0xC7A2, 0x9FF1, 0xC7A3, 0xC0E3, + 0xC7A4, 0xC0E4, 0xC7A5, 0xC0E5, 0xC7A6, 0xC0E6, 0xC7A7, 0x9FF2, 0xC7A8, 0x9FF3, 0xC7A9, 0x9FF4, 0xC7AA, 0x9FF5, 0xC7AB, 0x9FF6, + 0xC7AC, 0xC0E7, 0xC7AD, 0xC0E8, 0xC7AE, 0x9FF7, 0xC7AF, 0x9FF8, 0xC7B0, 0xC0E9, 0xC7B1, 0x9FF9, 0xC7B2, 0x9FFA, 0xC7B3, 0x9FFB, + 0xC7B4, 0xC0EA, 0xC7B5, 0x9FFC, 0xC7B6, 0x9FFD, 0xC7B7, 0x9FFE, 0xC7B8, 0xA041, 0xC7B9, 0xA042, 0xC7BA, 0xA043, 0xC7BB, 0xA044, + 0xC7BC, 0xC0EB, 0xC7BD, 0xC0EC, 0xC7BE, 0xA045, 0xC7BF, 0xC0ED, 0xC7C0, 0xC0EE, 0xC7C1, 0xC0EF, 0xC7C2, 0xA046, 0xC7C3, 0xA047, + 0xC7C4, 0xA048, 0xC7C5, 0xA049, 0xC7C6, 0xA04A, 0xC7C7, 0xA04B, 0xC7C8, 0xC0F0, 0xC7C9, 0xC0F1, 0xC7CA, 0xA04C, 0xC7CB, 0xA04D, + 0xC7CC, 0xC0F2, 0xC7CD, 0xA04E, 0xC7CE, 0xC0F3, 0xC7CF, 0xA04F, 0xC7D0, 0xC0F4, 0xC7D1, 0xA050, 0xC7D2, 0xA051, 0xC7D3, 0xA052, + 0xC7D4, 0xA053, 0xC7D5, 0xA054, 0xC7D6, 0xA055, 0xC7D7, 0xA056, 0xC7D8, 0xC0F5, 0xC7D9, 0xA057, 0xC7DA, 0xA058, 0xC7DB, 0xA059, + 0xC7DC, 0xA05A, 0xC7DD, 0xC0F6, 0xC7DE, 0xA061, 0xC7DF, 0xA062, 0xC7E0, 0xA063, 0xC7E1, 0xA064, 0xC7E2, 0xA065, 0xC7E3, 0xA066, + 0xC7E4, 0xC0F7, 0xC7E5, 0xA067, 0xC7E6, 0xA068, 0xC7E7, 0xA069, 0xC7E8, 0xC0F8, 0xC7E9, 0xA06A, 0xC7EA, 0xA06B, 0xC7EB, 0xA06C, + 0xC7EC, 0xC0F9, 0xC7ED, 0xA06D, 0xC7EE, 0xA06E, 0xC7EF, 0xA06F, 0xC7F0, 0xA070, 0xC7F1, 0xA071, 0xC7F2, 0xA072, 0xC7F3, 0xA073, + 0xC7F4, 0xA074, 0xC7F5, 0xA075, 0xC7F6, 0xA076, 0xC7F7, 0xA077, 0xC7F8, 0xA078, 0xC7F9, 0xA079, 0xC7FA, 0xA07A, 0xC7FB, 0xA081, + 0xC7FC, 0xA082, 0xC7FD, 0xA083, 0xC7FE, 0xA084, 0xC7FF, 0xA085, 0xC800, 0xC0FA, 0xC801, 0xC0FB, 0xC802, 0xA086, 0xC803, 0xA087, + 0xC804, 0xC0FC, 0xC805, 0xA088, 0xC806, 0xA089, 0xC807, 0xA08A, 0xC808, 0xC0FD, 0xC809, 0xA08B, 0xC80A, 0xC0FE, 0xC80B, 0xA08C, + 0xC80C, 0xA08D, 0xC80D, 0xA08E, 0xC80E, 0xA08F, 0xC80F, 0xA090, 0xC810, 0xC1A1, 0xC811, 0xC1A2, 0xC812, 0xA091, 0xC813, 0xC1A3, + 0xC814, 0xA092, 0xC815, 0xC1A4, 0xC816, 0xC1A5, 0xC817, 0xA093, 0xC818, 0xA094, 0xC819, 0xA095, 0xC81A, 0xA096, 0xC81B, 0xA097, + 0xC81C, 0xC1A6, 0xC81D, 0xC1A7, 0xC81E, 0xA098, 0xC81F, 0xA099, 0xC820, 0xC1A8, 0xC821, 0xA09A, 0xC822, 0xA09B, 0xC823, 0xA09C, + 0xC824, 0xC1A9, 0xC825, 0xA09D, 0xC826, 0xA09E, 0xC827, 0xA09F, 0xC828, 0xA0A0, 0xC829, 0xA0A1, 0xC82A, 0xA0A2, 0xC82B, 0xA0A3, + 0xC82C, 0xC1AA, 0xC82D, 0xC1AB, 0xC82E, 0xA0A4, 0xC82F, 0xC1AC, 0xC830, 0xA0A5, 0xC831, 0xC1AD, 0xC832, 0xA0A6, 0xC833, 0xA0A7, + 0xC834, 0xA0A8, 0xC835, 0xA0A9, 0xC836, 0xA0AA, 0xC837, 0xA0AB, 0xC838, 0xC1AE, 0xC839, 0xA0AC, 0xC83A, 0xA0AD, 0xC83B, 0xA0AE, + 0xC83C, 0xC1AF, 0xC83D, 0xA0AF, 0xC83E, 0xA0B0, 0xC83F, 0xA0B1, 0xC840, 0xC1B0, 0xC841, 0xA0B2, 0xC842, 0xA0B3, 0xC843, 0xA0B4, + 0xC844, 0xA0B5, 0xC845, 0xA0B6, 0xC846, 0xA0B7, 0xC847, 0xA0B8, 0xC848, 0xC1B1, 0xC849, 0xC1B2, 0xC84A, 0xA0B9, 0xC84B, 0xA0BA, + 0xC84C, 0xC1B3, 0xC84D, 0xC1B4, 0xC84E, 0xA0BB, 0xC84F, 0xA0BC, 0xC850, 0xA0BD, 0xC851, 0xA0BE, 0xC852, 0xA0BF, 0xC853, 0xA0C0, + 0xC854, 0xC1B5, 0xC855, 0xA0C1, 0xC856, 0xA0C2, 0xC857, 0xA0C3, 0xC858, 0xA0C4, 0xC859, 0xA0C5, 0xC85A, 0xA0C6, 0xC85B, 0xA0C7, + 0xC85C, 0xA0C8, 0xC85D, 0xA0C9, 0xC85E, 0xA0CA, 0xC85F, 0xA0CB, 0xC860, 0xA0CC, 0xC861, 0xA0CD, 0xC862, 0xA0CE, 0xC863, 0xA0CF, + 0xC864, 0xA0D0, 0xC865, 0xA0D1, 0xC866, 0xA0D2, 0xC867, 0xA0D3, 0xC868, 0xA0D4, 0xC869, 0xA0D5, 0xC86A, 0xA0D6, 0xC86B, 0xA0D7, + 0xC86C, 0xA0D8, 0xC86D, 0xA0D9, 0xC86E, 0xA0DA, 0xC86F, 0xA0DB, 0xC870, 0xC1B6, 0xC871, 0xC1B7, 0xC872, 0xA0DC, 0xC873, 0xA0DD, + 0xC874, 0xC1B8, 0xC875, 0xA0DE, 0xC876, 0xA0DF, 0xC877, 0xA0E0, 0xC878, 0xC1B9, 0xC879, 0xA0E1, 0xC87A, 0xC1BA, 0xC87B, 0xA0E2, + 0xC87C, 0xA0E3, 0xC87D, 0xA0E4, 0xC87E, 0xA0E5, 0xC87F, 0xA0E6, 0xC880, 0xC1BB, 0xC881, 0xC1BC, 0xC882, 0xA0E7, 0xC883, 0xC1BD, + 0xC884, 0xA0E8, 0xC885, 0xC1BE, 0xC886, 0xC1BF, 0xC887, 0xC1C0, 0xC888, 0xA0E9, 0xC889, 0xA0EA, 0xC88A, 0xA0EB, 0xC88B, 0xC1C1, + 0xC88C, 0xC1C2, 0xC88D, 0xC1C3, 0xC88E, 0xA0EC, 0xC88F, 0xA0ED, 0xC890, 0xA0EE, 0xC891, 0xA0EF, 0xC892, 0xA0F0, 0xC893, 0xA0F1, + 0xC894, 0xC1C4, 0xC895, 0xA0F2, 0xC896, 0xA0F3, 0xC897, 0xA0F4, 0xC898, 0xA0F5, 0xC899, 0xA0F6, 0xC89A, 0xA0F7, 0xC89B, 0xA0F8, + 0xC89C, 0xA0F9, 0xC89D, 0xC1C5, 0xC89E, 0xA0FA, 0xC89F, 0xC1C6, 0xC8A0, 0xA0FB, 0xC8A1, 0xC1C7, 0xC8A2, 0xA0FC, 0xC8A3, 0xA0FD, + 0xC8A4, 0xA0FE, 0xC8A5, 0xA141, 0xC8A6, 0xA142, 0xC8A7, 0xA143, 0xC8A8, 0xC1C8, 0xC8A9, 0xA144, 0xC8AA, 0xA145, 0xC8AB, 0xA146, + 0xC8AC, 0xA147, 0xC8AD, 0xA148, 0xC8AE, 0xA149, 0xC8AF, 0xA14A, 0xC8B0, 0xA14B, 0xC8B1, 0xA14C, 0xC8B2, 0xA14D, 0xC8B3, 0xA14E, + 0xC8B4, 0xA14F, 0xC8B5, 0xA150, 0xC8B6, 0xA151, 0xC8B7, 0xA152, 0xC8B8, 0xA153, 0xC8B9, 0xA154, 0xC8BA, 0xA155, 0xC8BB, 0xA156, + 0xC8BC, 0xC1C9, 0xC8BD, 0xC1CA, 0xC8BE, 0xA157, 0xC8BF, 0xA158, 0xC8C0, 0xA159, 0xC8C1, 0xA15A, 0xC8C2, 0xA161, 0xC8C3, 0xA162, + 0xC8C4, 0xC1CB, 0xC8C5, 0xA163, 0xC8C6, 0xA164, 0xC8C7, 0xA165, 0xC8C8, 0xC1CC, 0xC8C9, 0xA166, 0xC8CA, 0xA167, 0xC8CB, 0xA168, + 0xC8CC, 0xC1CD, 0xC8CD, 0xA169, 0xC8CE, 0xA16A, 0xC8CF, 0xA16B, 0xC8D0, 0xA16C, 0xC8D1, 0xA16D, 0xC8D2, 0xA16E, 0xC8D3, 0xA16F, + 0xC8D4, 0xC1CE, 0xC8D5, 0xC1CF, 0xC8D6, 0xA170, 0xC8D7, 0xC1D0, 0xC8D8, 0xA171, 0xC8D9, 0xC1D1, 0xC8DA, 0xA172, 0xC8DB, 0xA173, + 0xC8DC, 0xA174, 0xC8DD, 0xA175, 0xC8DE, 0xA176, 0xC8DF, 0xA177, 0xC8E0, 0xC1D2, 0xC8E1, 0xC1D3, 0xC8E2, 0xA178, 0xC8E3, 0xA179, + 0xC8E4, 0xC1D4, 0xC8E5, 0xA17A, 0xC8E6, 0xA181, 0xC8E7, 0xA182, 0xC8E8, 0xA183, 0xC8E9, 0xA184, 0xC8EA, 0xA185, 0xC8EB, 0xA186, + 0xC8EC, 0xA187, 0xC8ED, 0xA188, 0xC8EE, 0xA189, 0xC8EF, 0xA18A, 0xC8F0, 0xA18B, 0xC8F1, 0xA18C, 0xC8F2, 0xA18D, 0xC8F3, 0xA18E, + 0xC8F4, 0xA18F, 0xC8F5, 0xC1D5, 0xC8F6, 0xA190, 0xC8F7, 0xA191, 0xC8F8, 0xA192, 0xC8F9, 0xA193, 0xC8FA, 0xA194, 0xC8FB, 0xA195, + 0xC8FC, 0xC1D6, 0xC8FD, 0xC1D7, 0xC8FE, 0xA196, 0xC8FF, 0xA197, 0xC900, 0xC1D8, 0xC901, 0xA198, 0xC902, 0xA199, 0xC903, 0xA19A, + 0xC904, 0xC1D9, 0xC905, 0xC1DA, 0xC906, 0xC1DB, 0xC907, 0xA19B, 0xC908, 0xA19C, 0xC909, 0xA19D, 0xC90A, 0xA19E, 0xC90B, 0xA19F, + 0xC90C, 0xC1DC, 0xC90D, 0xC1DD, 0xC90E, 0xA1A0, 0xC90F, 0xC1DE, 0xC910, 0xA241, 0xC911, 0xC1DF, 0xC912, 0xA242, 0xC913, 0xA243, + 0xC914, 0xA244, 0xC915, 0xA245, 0xC916, 0xA246, 0xC917, 0xA247, 0xC918, 0xC1E0, 0xC919, 0xA248, 0xC91A, 0xA249, 0xC91B, 0xA24A, + 0xC91C, 0xA24B, 0xC91D, 0xA24C, 0xC91E, 0xA24D, 0xC91F, 0xA24E, 0xC920, 0xA24F, 0xC921, 0xA250, 0xC922, 0xA251, 0xC923, 0xA252, + 0xC924, 0xA253, 0xC925, 0xA254, 0xC926, 0xA255, 0xC927, 0xA256, 0xC928, 0xA257, 0xC929, 0xA258, 0xC92A, 0xA259, 0xC92B, 0xA25A, + 0xC92C, 0xC1E1, 0xC92D, 0xA261, 0xC92E, 0xA262, 0xC92F, 0xA263, 0xC930, 0xA264, 0xC931, 0xA265, 0xC932, 0xA266, 0xC933, 0xA267, + 0xC934, 0xC1E2, 0xC935, 0xA268, 0xC936, 0xA269, 0xC937, 0xA26A, 0xC938, 0xA26B, 0xC939, 0xA26C, 0xC93A, 0xA26D, 0xC93B, 0xA26E, + 0xC93C, 0xA26F, 0xC93D, 0xA270, 0xC93E, 0xA271, 0xC93F, 0xA272, 0xC940, 0xA273, 0xC941, 0xA274, 0xC942, 0xA275, 0xC943, 0xA276, + 0xC944, 0xA277, 0xC945, 0xA278, 0xC946, 0xA279, 0xC947, 0xA27A, 0xC948, 0xA281, 0xC949, 0xA282, 0xC94A, 0xA283, 0xC94B, 0xA284, + 0xC94C, 0xA285, 0xC94D, 0xA286, 0xC94E, 0xA287, 0xC94F, 0xA288, 0xC950, 0xC1E3, 0xC951, 0xC1E4, 0xC952, 0xA289, 0xC953, 0xA28A, + 0xC954, 0xC1E5, 0xC955, 0xA28B, 0xC956, 0xA28C, 0xC957, 0xA28D, 0xC958, 0xC1E6, 0xC959, 0xA28E, 0xC95A, 0xA28F, 0xC95B, 0xA290, + 0xC95C, 0xA291, 0xC95D, 0xA292, 0xC95E, 0xA293, 0xC95F, 0xA294, 0xC960, 0xC1E7, 0xC961, 0xC1E8, 0xC962, 0xA295, 0xC963, 0xC1E9, + 0xC964, 0xA296, 0xC965, 0xA297, 0xC966, 0xA298, 0xC967, 0xA299, 0xC968, 0xA29A, 0xC969, 0xA29B, 0xC96A, 0xA29C, 0xC96B, 0xA29D, + 0xC96C, 0xC1EA, 0xC96D, 0xA29E, 0xC96E, 0xA29F, 0xC96F, 0xA2A0, 0xC970, 0xC1EB, 0xC971, 0xA341, 0xC972, 0xA342, 0xC973, 0xA343, + 0xC974, 0xC1EC, 0xC975, 0xA344, 0xC976, 0xA345, 0xC977, 0xA346, 0xC978, 0xA347, 0xC979, 0xA348, 0xC97A, 0xA349, 0xC97B, 0xA34A, + 0xC97C, 0xC1ED, 0xC97D, 0xA34B, 0xC97E, 0xA34C, 0xC97F, 0xA34D, 0xC980, 0xA34E, 0xC981, 0xA34F, 0xC982, 0xA350, 0xC983, 0xA351, + 0xC984, 0xA352, 0xC985, 0xA353, 0xC986, 0xA354, 0xC987, 0xA355, 0xC988, 0xC1EE, 0xC989, 0xC1EF, 0xC98A, 0xA356, 0xC98B, 0xA357, + 0xC98C, 0xC1F0, 0xC98D, 0xA358, 0xC98E, 0xA359, 0xC98F, 0xA35A, 0xC990, 0xC1F1, 0xC991, 0xA361, 0xC992, 0xA362, 0xC993, 0xA363, + 0xC994, 0xA364, 0xC995, 0xA365, 0xC996, 0xA366, 0xC997, 0xA367, 0xC998, 0xC1F2, 0xC999, 0xC1F3, 0xC99A, 0xA368, 0xC99B, 0xC1F4, + 0xC99C, 0xA369, 0xC99D, 0xC1F5, 0xC99E, 0xA36A, 0xC99F, 0xA36B, 0xC9A0, 0xA36C, 0xC9A1, 0xA36D, 0xC9A2, 0xA36E, 0xC9A3, 0xA36F, + 0xC9A4, 0xA370, 0xC9A5, 0xA371, 0xC9A6, 0xA372, 0xC9A7, 0xA373, 0xC9A8, 0xA374, 0xC9A9, 0xA375, 0xC9AA, 0xA376, 0xC9AB, 0xA377, + 0xC9AC, 0xA378, 0xC9AD, 0xA379, 0xC9AE, 0xA37A, 0xC9AF, 0xA381, 0xC9B0, 0xA382, 0xC9B1, 0xA383, 0xC9B2, 0xA384, 0xC9B3, 0xA385, + 0xC9B4, 0xA386, 0xC9B5, 0xA387, 0xC9B6, 0xA388, 0xC9B7, 0xA389, 0xC9B8, 0xA38A, 0xC9B9, 0xA38B, 0xC9BA, 0xA38C, 0xC9BB, 0xA38D, + 0xC9BC, 0xA38E, 0xC9BD, 0xA38F, 0xC9BE, 0xA390, 0xC9BF, 0xA391, 0xC9C0, 0xC1F6, 0xC9C1, 0xC1F7, 0xC9C2, 0xA392, 0xC9C3, 0xA393, + 0xC9C4, 0xC1F8, 0xC9C5, 0xA394, 0xC9C6, 0xA395, 0xC9C7, 0xC1F9, 0xC9C8, 0xC1FA, 0xC9C9, 0xA396, 0xC9CA, 0xC1FB, 0xC9CB, 0xA397, + 0xC9CC, 0xA398, 0xC9CD, 0xA399, 0xC9CE, 0xA39A, 0xC9CF, 0xA39B, 0xC9D0, 0xC1FC, 0xC9D1, 0xC1FD, 0xC9D2, 0xA39C, 0xC9D3, 0xC1FE, + 0xC9D4, 0xA39D, 0xC9D5, 0xC2A1, 0xC9D6, 0xC2A2, 0xC9D7, 0xA39E, 0xC9D8, 0xA39F, 0xC9D9, 0xC2A3, 0xC9DA, 0xC2A4, 0xC9DB, 0xA3A0, + 0xC9DC, 0xC2A5, 0xC9DD, 0xC2A6, 0xC9DE, 0xA441, 0xC9DF, 0xA442, 0xC9E0, 0xC2A7, 0xC9E1, 0xA443, 0xC9E2, 0xC2A8, 0xC9E3, 0xA444, + 0xC9E4, 0xC2A9, 0xC9E5, 0xA445, 0xC9E6, 0xA446, 0xC9E7, 0xC2AA, 0xC9E8, 0xA447, 0xC9E9, 0xA448, 0xC9EA, 0xA449, 0xC9EB, 0xA44A, + 0xC9EC, 0xC2AB, 0xC9ED, 0xC2AC, 0xC9EE, 0xA44B, 0xC9EF, 0xC2AD, 0xC9F0, 0xC2AE, 0xC9F1, 0xC2AF, 0xC9F2, 0xA44C, 0xC9F3, 0xA44D, + 0xC9F4, 0xA44E, 0xC9F5, 0xA44F, 0xC9F6, 0xA450, 0xC9F7, 0xA451, 0xC9F8, 0xC2B0, 0xC9F9, 0xC2B1, 0xC9FA, 0xA452, 0xC9FB, 0xA453, + 0xC9FC, 0xC2B2, 0xC9FD, 0xA454, 0xC9FE, 0xA455, 0xC9FF, 0xA456, 0xCA00, 0xC2B3, 0xCA01, 0xA457, 0xCA02, 0xA458, 0xCA03, 0xA459, + 0xCA04, 0xA45A, 0xCA05, 0xA461, 0xCA06, 0xA462, 0xCA07, 0xA463, 0xCA08, 0xC2B4, 0xCA09, 0xC2B5, 0xCA0A, 0xA464, 0xCA0B, 0xC2B6, + 0xCA0C, 0xC2B7, 0xCA0D, 0xC2B8, 0xCA0E, 0xA465, 0xCA0F, 0xA466, 0xCA10, 0xA467, 0xCA11, 0xA468, 0xCA12, 0xA469, 0xCA13, 0xA46A, + 0xCA14, 0xC2B9, 0xCA15, 0xA46B, 0xCA16, 0xA46C, 0xCA17, 0xA46D, 0xCA18, 0xC2BA, 0xCA19, 0xA46E, 0xCA1A, 0xA46F, 0xCA1B, 0xA470, + 0xCA1C, 0xA471, 0xCA1D, 0xA472, 0xCA1E, 0xA473, 0xCA1F, 0xA474, 0xCA20, 0xA475, 0xCA21, 0xA476, 0xCA22, 0xA477, 0xCA23, 0xA478, + 0xCA24, 0xA479, 0xCA25, 0xA47A, 0xCA26, 0xA481, 0xCA27, 0xA482, 0xCA28, 0xA483, 0xCA29, 0xC2BB, 0xCA2A, 0xA484, 0xCA2B, 0xA485, + 0xCA2C, 0xA486, 0xCA2D, 0xA487, 0xCA2E, 0xA488, 0xCA2F, 0xA489, 0xCA30, 0xA48A, 0xCA31, 0xA48B, 0xCA32, 0xA48C, 0xCA33, 0xA48D, + 0xCA34, 0xA48E, 0xCA35, 0xA48F, 0xCA36, 0xA490, 0xCA37, 0xA491, 0xCA38, 0xA492, 0xCA39, 0xA493, 0xCA3A, 0xA494, 0xCA3B, 0xA495, + 0xCA3C, 0xA496, 0xCA3D, 0xA497, 0xCA3E, 0xA498, 0xCA3F, 0xA499, 0xCA40, 0xA49A, 0xCA41, 0xA49B, 0xCA42, 0xA49C, 0xCA43, 0xA49D, + 0xCA44, 0xA49E, 0xCA45, 0xA49F, 0xCA46, 0xA4A0, 0xCA47, 0xA541, 0xCA48, 0xA542, 0xCA49, 0xA543, 0xCA4A, 0xA544, 0xCA4B, 0xA545, + 0xCA4C, 0xC2BC, 0xCA4D, 0xC2BD, 0xCA4E, 0xA546, 0xCA4F, 0xA547, 0xCA50, 0xC2BE, 0xCA51, 0xA548, 0xCA52, 0xA549, 0xCA53, 0xA54A, + 0xCA54, 0xC2BF, 0xCA55, 0xA54B, 0xCA56, 0xA54C, 0xCA57, 0xA54D, 0xCA58, 0xA54E, 0xCA59, 0xA54F, 0xCA5A, 0xA550, 0xCA5B, 0xA551, + 0xCA5C, 0xC2C0, 0xCA5D, 0xC2C1, 0xCA5E, 0xA552, 0xCA5F, 0xC2C2, 0xCA60, 0xC2C3, 0xCA61, 0xC2C4, 0xCA62, 0xA553, 0xCA63, 0xA554, + 0xCA64, 0xA555, 0xCA65, 0xA556, 0xCA66, 0xA557, 0xCA67, 0xA558, 0xCA68, 0xC2C5, 0xCA69, 0xA559, 0xCA6A, 0xA55A, 0xCA6B, 0xA561, + 0xCA6C, 0xA562, 0xCA6D, 0xA563, 0xCA6E, 0xA564, 0xCA6F, 0xA565, 0xCA70, 0xA566, 0xCA71, 0xA567, 0xCA72, 0xA568, 0xCA73, 0xA569, + 0xCA74, 0xA56A, 0xCA75, 0xA56B, 0xCA76, 0xA56C, 0xCA77, 0xA56D, 0xCA78, 0xA56E, 0xCA79, 0xA56F, 0xCA7A, 0xA570, 0xCA7B, 0xA571, + 0xCA7C, 0xA572, 0xCA7D, 0xC2C6, 0xCA7E, 0xA573, 0xCA7F, 0xA574, 0xCA80, 0xA575, 0xCA81, 0xA576, 0xCA82, 0xA577, 0xCA83, 0xA578, + 0xCA84, 0xC2C7, 0xCA85, 0xA579, 0xCA86, 0xA57A, 0xCA87, 0xA581, 0xCA88, 0xA582, 0xCA89, 0xA583, 0xCA8A, 0xA584, 0xCA8B, 0xA585, + 0xCA8C, 0xA586, 0xCA8D, 0xA587, 0xCA8E, 0xA588, 0xCA8F, 0xA589, 0xCA90, 0xA58A, 0xCA91, 0xA58B, 0xCA92, 0xA58C, 0xCA93, 0xA58D, + 0xCA94, 0xA58E, 0xCA95, 0xA58F, 0xCA96, 0xA590, 0xCA97, 0xA591, 0xCA98, 0xC2C8, 0xCA99, 0xA592, 0xCA9A, 0xA593, 0xCA9B, 0xA594, + 0xCA9C, 0xA595, 0xCA9D, 0xA596, 0xCA9E, 0xA597, 0xCA9F, 0xA598, 0xCAA0, 0xA599, 0xCAA1, 0xA59A, 0xCAA2, 0xA59B, 0xCAA3, 0xA59C, + 0xCAA4, 0xA59D, 0xCAA5, 0xA59E, 0xCAA6, 0xA59F, 0xCAA7, 0xA5A0, 0xCAA8, 0xA641, 0xCAA9, 0xA642, 0xCAAA, 0xA643, 0xCAAB, 0xA644, + 0xCAAC, 0xA645, 0xCAAD, 0xA646, 0xCAAE, 0xA647, 0xCAAF, 0xA648, 0xCAB0, 0xA649, 0xCAB1, 0xA64A, 0xCAB2, 0xA64B, 0xCAB3, 0xA64C, + 0xCAB4, 0xA64D, 0xCAB5, 0xA64E, 0xCAB6, 0xA64F, 0xCAB7, 0xA650, 0xCAB8, 0xA651, 0xCAB9, 0xA652, 0xCABA, 0xA653, 0xCABB, 0xA654, + 0xCABC, 0xC2C9, 0xCABD, 0xC2CA, 0xCABE, 0xA655, 0xCABF, 0xA656, 0xCAC0, 0xC2CB, 0xCAC1, 0xA657, 0xCAC2, 0xA658, 0xCAC3, 0xA659, + 0xCAC4, 0xC2CC, 0xCAC5, 0xA65A, 0xCAC6, 0xA661, 0xCAC7, 0xA662, 0xCAC8, 0xA663, 0xCAC9, 0xA664, 0xCACA, 0xA665, 0xCACB, 0xA666, + 0xCACC, 0xC2CD, 0xCACD, 0xC2CE, 0xCACE, 0xA667, 0xCACF, 0xC2CF, 0xCAD0, 0xA668, 0xCAD1, 0xC2D0, 0xCAD2, 0xA669, 0xCAD3, 0xC2D1, + 0xCAD4, 0xA66A, 0xCAD5, 0xA66B, 0xCAD6, 0xA66C, 0xCAD7, 0xA66D, 0xCAD8, 0xC2D2, 0xCAD9, 0xC2D3, 0xCADA, 0xA66E, 0xCADB, 0xA66F, + 0xCADC, 0xA670, 0xCADD, 0xA671, 0xCADE, 0xA672, 0xCADF, 0xA673, 0xCAE0, 0xC2D4, 0xCAE1, 0xA674, 0xCAE2, 0xA675, 0xCAE3, 0xA676, + 0xCAE4, 0xA677, 0xCAE5, 0xA678, 0xCAE6, 0xA679, 0xCAE7, 0xA67A, 0xCAE8, 0xA681, 0xCAE9, 0xA682, 0xCAEA, 0xA683, 0xCAEB, 0xA684, + 0xCAEC, 0xC2D5, 0xCAED, 0xA685, 0xCAEE, 0xA686, 0xCAEF, 0xA687, 0xCAF0, 0xA688, 0xCAF1, 0xA689, 0xCAF2, 0xA68A, 0xCAF3, 0xA68B, + 0xCAF4, 0xC2D6, 0xCAF5, 0xA68C, 0xCAF6, 0xA68D, 0xCAF7, 0xA68E, 0xCAF8, 0xA68F, 0xCAF9, 0xA690, 0xCAFA, 0xA691, 0xCAFB, 0xA692, + 0xCAFC, 0xA693, 0xCAFD, 0xA694, 0xCAFE, 0xA695, 0xCAFF, 0xA696, 0xCB00, 0xA697, 0xCB01, 0xA698, 0xCB02, 0xA699, 0xCB03, 0xA69A, + 0xCB04, 0xA69B, 0xCB05, 0xA69C, 0xCB06, 0xA69D, 0xCB07, 0xA69E, 0xCB08, 0xC2D7, 0xCB09, 0xA69F, 0xCB0A, 0xA6A0, 0xCB0B, 0xA741, + 0xCB0C, 0xA742, 0xCB0D, 0xA743, 0xCB0E, 0xA744, 0xCB0F, 0xA745, 0xCB10, 0xC2D8, 0xCB11, 0xA746, 0xCB12, 0xA747, 0xCB13, 0xA748, + 0xCB14, 0xC2D9, 0xCB15, 0xA749, 0xCB16, 0xA74A, 0xCB17, 0xA74B, 0xCB18, 0xC2DA, 0xCB19, 0xA74C, 0xCB1A, 0xA74D, 0xCB1B, 0xA74E, + 0xCB1C, 0xA74F, 0xCB1D, 0xA750, 0xCB1E, 0xA751, 0xCB1F, 0xA752, 0xCB20, 0xC2DB, 0xCB21, 0xC2DC, 0xCB22, 0xA753, 0xCB23, 0xA754, + 0xCB24, 0xA755, 0xCB25, 0xA756, 0xCB26, 0xA757, 0xCB27, 0xA758, 0xCB28, 0xA759, 0xCB29, 0xA75A, 0xCB2A, 0xA761, 0xCB2B, 0xA762, + 0xCB2C, 0xA763, 0xCB2D, 0xA764, 0xCB2E, 0xA765, 0xCB2F, 0xA766, 0xCB30, 0xA767, 0xCB31, 0xA768, 0xCB32, 0xA769, 0xCB33, 0xA76A, + 0xCB34, 0xA76B, 0xCB35, 0xA76C, 0xCB36, 0xA76D, 0xCB37, 0xA76E, 0xCB38, 0xA76F, 0xCB39, 0xA770, 0xCB3A, 0xA771, 0xCB3B, 0xA772, + 0xCB3C, 0xA773, 0xCB3D, 0xA774, 0xCB3E, 0xA775, 0xCB3F, 0xA776, 0xCB40, 0xA777, 0xCB41, 0xC2DD, 0xCB42, 0xA778, 0xCB43, 0xA779, + 0xCB44, 0xA77A, 0xCB45, 0xA781, 0xCB46, 0xA782, 0xCB47, 0xA783, 0xCB48, 0xC2DE, 0xCB49, 0xC2DF, 0xCB4A, 0xA784, 0xCB4B, 0xA785, + 0xCB4C, 0xC2E0, 0xCB4D, 0xA786, 0xCB4E, 0xA787, 0xCB4F, 0xA788, 0xCB50, 0xC2E1, 0xCB51, 0xA789, 0xCB52, 0xA78A, 0xCB53, 0xA78B, + 0xCB54, 0xA78C, 0xCB55, 0xA78D, 0xCB56, 0xA78E, 0xCB57, 0xA78F, 0xCB58, 0xC2E2, 0xCB59, 0xC2E3, 0xCB5A, 0xA790, 0xCB5B, 0xA791, + 0xCB5C, 0xA792, 0xCB5D, 0xC2E4, 0xCB5E, 0xA793, 0xCB5F, 0xA794, 0xCB60, 0xA795, 0xCB61, 0xA796, 0xCB62, 0xA797, 0xCB63, 0xA798, + 0xCB64, 0xC2E5, 0xCB65, 0xA799, 0xCB66, 0xA79A, 0xCB67, 0xA79B, 0xCB68, 0xA79C, 0xCB69, 0xA79D, 0xCB6A, 0xA79E, 0xCB6B, 0xA79F, + 0xCB6C, 0xA7A0, 0xCB6D, 0xA841, 0xCB6E, 0xA842, 0xCB6F, 0xA843, 0xCB70, 0xA844, 0xCB71, 0xA845, 0xCB72, 0xA846, 0xCB73, 0xA847, + 0xCB74, 0xA848, 0xCB75, 0xA849, 0xCB76, 0xA84A, 0xCB77, 0xA84B, 0xCB78, 0xC2E6, 0xCB79, 0xC2E7, 0xCB7A, 0xA84C, 0xCB7B, 0xA84D, + 0xCB7C, 0xA84E, 0xCB7D, 0xA84F, 0xCB7E, 0xA850, 0xCB7F, 0xA851, 0xCB80, 0xA852, 0xCB81, 0xA853, 0xCB82, 0xA854, 0xCB83, 0xA855, + 0xCB84, 0xA856, 0xCB85, 0xA857, 0xCB86, 0xA858, 0xCB87, 0xA859, 0xCB88, 0xA85A, 0xCB89, 0xA861, 0xCB8A, 0xA862, 0xCB8B, 0xA863, + 0xCB8C, 0xA864, 0xCB8D, 0xA865, 0xCB8E, 0xA866, 0xCB8F, 0xA867, 0xCB90, 0xA868, 0xCB91, 0xA869, 0xCB92, 0xA86A, 0xCB93, 0xA86B, + 0xCB94, 0xA86C, 0xCB95, 0xA86D, 0xCB96, 0xA86E, 0xCB97, 0xA86F, 0xCB98, 0xA870, 0xCB99, 0xA871, 0xCB9A, 0xA872, 0xCB9B, 0xA873, + 0xCB9C, 0xC2E8, 0xCB9D, 0xA874, 0xCB9E, 0xA875, 0xCB9F, 0xA876, 0xCBA0, 0xA877, 0xCBA1, 0xA878, 0xCBA2, 0xA879, 0xCBA3, 0xA87A, + 0xCBA4, 0xA881, 0xCBA5, 0xA882, 0xCBA6, 0xA883, 0xCBA7, 0xA884, 0xCBA8, 0xA885, 0xCBA9, 0xA886, 0xCBAA, 0xA887, 0xCBAB, 0xA888, + 0xCBAC, 0xA889, 0xCBAD, 0xA88A, 0xCBAE, 0xA88B, 0xCBAF, 0xA88C, 0xCBB0, 0xA88D, 0xCBB1, 0xA88E, 0xCBB2, 0xA88F, 0xCBB3, 0xA890, + 0xCBB4, 0xA891, 0xCBB5, 0xA892, 0xCBB6, 0xA893, 0xCBB7, 0xA894, 0xCBB8, 0xC2E9, 0xCBB9, 0xA895, 0xCBBA, 0xA896, 0xCBBB, 0xA897, + 0xCBBC, 0xA898, 0xCBBD, 0xA899, 0xCBBE, 0xA89A, 0xCBBF, 0xA89B, 0xCBC0, 0xA89C, 0xCBC1, 0xA89D, 0xCBC2, 0xA89E, 0xCBC3, 0xA89F, + 0xCBC4, 0xA8A0, 0xCBC5, 0xA941, 0xCBC6, 0xA942, 0xCBC7, 0xA943, 0xCBC8, 0xA944, 0xCBC9, 0xA945, 0xCBCA, 0xA946, 0xCBCB, 0xA947, + 0xCBCC, 0xA948, 0xCBCD, 0xA949, 0xCBCE, 0xA94A, 0xCBCF, 0xA94B, 0xCBD0, 0xA94C, 0xCBD1, 0xA94D, 0xCBD2, 0xA94E, 0xCBD3, 0xA94F, + 0xCBD4, 0xC2EA, 0xCBD5, 0xA950, 0xCBD6, 0xA951, 0xCBD7, 0xA952, 0xCBD8, 0xA953, 0xCBD9, 0xA954, 0xCBDA, 0xA955, 0xCBDB, 0xA956, + 0xCBDC, 0xA957, 0xCBDD, 0xA958, 0xCBDE, 0xA959, 0xCBDF, 0xA95A, 0xCBE0, 0xA961, 0xCBE1, 0xA962, 0xCBE2, 0xA963, 0xCBE3, 0xA964, + 0xCBE4, 0xC2EB, 0xCBE5, 0xA965, 0xCBE6, 0xA966, 0xCBE7, 0xC2EC, 0xCBE8, 0xA967, 0xCBE9, 0xC2ED, 0xCBEA, 0xA968, 0xCBEB, 0xA969, + 0xCBEC, 0xA96A, 0xCBED, 0xA96B, 0xCBEE, 0xA96C, 0xCBEF, 0xA96D, 0xCBF0, 0xA96E, 0xCBF1, 0xA96F, 0xCBF2, 0xA970, 0xCBF3, 0xA971, + 0xCBF4, 0xA972, 0xCBF5, 0xA973, 0xCBF6, 0xA974, 0xCBF7, 0xA975, 0xCBF8, 0xA976, 0xCBF9, 0xA977, 0xCBFA, 0xA978, 0xCBFB, 0xA979, + 0xCBFC, 0xA97A, 0xCBFD, 0xA981, 0xCBFE, 0xA982, 0xCBFF, 0xA983, 0xCC00, 0xA984, 0xCC01, 0xA985, 0xCC02, 0xA986, 0xCC03, 0xA987, + 0xCC04, 0xA988, 0xCC05, 0xA989, 0xCC06, 0xA98A, 0xCC07, 0xA98B, 0xCC08, 0xA98C, 0xCC09, 0xA98D, 0xCC0A, 0xA98E, 0xCC0B, 0xA98F, + 0xCC0C, 0xC2EE, 0xCC0D, 0xC2EF, 0xCC0E, 0xA990, 0xCC0F, 0xA991, 0xCC10, 0xC2F0, 0xCC11, 0xA992, 0xCC12, 0xA993, 0xCC13, 0xA994, + 0xCC14, 0xC2F1, 0xCC15, 0xA995, 0xCC16, 0xA996, 0xCC17, 0xA997, 0xCC18, 0xA998, 0xCC19, 0xA999, 0xCC1A, 0xA99A, 0xCC1B, 0xA99B, + 0xCC1C, 0xC2F2, 0xCC1D, 0xC2F3, 0xCC1E, 0xA99C, 0xCC1F, 0xA99D, 0xCC20, 0xA99E, 0xCC21, 0xC2F4, 0xCC22, 0xC2F5, 0xCC23, 0xA99F, + 0xCC24, 0xA9A0, 0xCC25, 0xAA41, 0xCC26, 0xAA42, 0xCC27, 0xC2F6, 0xCC28, 0xC2F7, 0xCC29, 0xC2F8, 0xCC2A, 0xAA43, 0xCC2B, 0xAA44, + 0xCC2C, 0xC2F9, 0xCC2D, 0xAA45, 0xCC2E, 0xC2FA, 0xCC2F, 0xAA46, 0xCC30, 0xC2FB, 0xCC31, 0xAA47, 0xCC32, 0xAA48, 0xCC33, 0xAA49, + 0xCC34, 0xAA4A, 0xCC35, 0xAA4B, 0xCC36, 0xAA4C, 0xCC37, 0xAA4D, 0xCC38, 0xC2FC, 0xCC39, 0xC2FD, 0xCC3A, 0xAA4E, 0xCC3B, 0xC2FE, + 0xCC3C, 0xC3A1, 0xCC3D, 0xC3A2, 0xCC3E, 0xC3A3, 0xCC3F, 0xAA4F, 0xCC40, 0xAA50, 0xCC41, 0xAA51, 0xCC42, 0xAA52, 0xCC43, 0xAA53, + 0xCC44, 0xC3A4, 0xCC45, 0xC3A5, 0xCC46, 0xAA54, 0xCC47, 0xAA55, 0xCC48, 0xC3A6, 0xCC49, 0xAA56, 0xCC4A, 0xAA57, 0xCC4B, 0xAA58, + 0xCC4C, 0xC3A7, 0xCC4D, 0xAA59, 0xCC4E, 0xAA5A, 0xCC4F, 0xAA61, 0xCC50, 0xAA62, 0xCC51, 0xAA63, 0xCC52, 0xAA64, 0xCC53, 0xAA65, + 0xCC54, 0xC3A8, 0xCC55, 0xC3A9, 0xCC56, 0xAA66, 0xCC57, 0xC3AA, 0xCC58, 0xC3AB, 0xCC59, 0xC3AC, 0xCC5A, 0xAA67, 0xCC5B, 0xAA68, + 0xCC5C, 0xAA69, 0xCC5D, 0xAA6A, 0xCC5E, 0xAA6B, 0xCC5F, 0xAA6C, 0xCC60, 0xC3AD, 0xCC61, 0xAA6D, 0xCC62, 0xAA6E, 0xCC63, 0xAA6F, + 0xCC64, 0xC3AE, 0xCC65, 0xAA70, 0xCC66, 0xC3AF, 0xCC67, 0xAA71, 0xCC68, 0xC3B0, 0xCC69, 0xAA72, 0xCC6A, 0xAA73, 0xCC6B, 0xAA74, + 0xCC6C, 0xAA75, 0xCC6D, 0xAA76, 0xCC6E, 0xAA77, 0xCC6F, 0xAA78, 0xCC70, 0xC3B1, 0xCC71, 0xAA79, 0xCC72, 0xAA7A, 0xCC73, 0xAA81, + 0xCC74, 0xAA82, 0xCC75, 0xC3B2, 0xCC76, 0xAA83, 0xCC77, 0xAA84, 0xCC78, 0xAA85, 0xCC79, 0xAA86, 0xCC7A, 0xAA87, 0xCC7B, 0xAA88, + 0xCC7C, 0xAA89, 0xCC7D, 0xAA8A, 0xCC7E, 0xAA8B, 0xCC7F, 0xAA8C, 0xCC80, 0xAA8D, 0xCC81, 0xAA8E, 0xCC82, 0xAA8F, 0xCC83, 0xAA90, + 0xCC84, 0xAA91, 0xCC85, 0xAA92, 0xCC86, 0xAA93, 0xCC87, 0xAA94, 0xCC88, 0xAA95, 0xCC89, 0xAA96, 0xCC8A, 0xAA97, 0xCC8B, 0xAA98, + 0xCC8C, 0xAA99, 0xCC8D, 0xAA9A, 0xCC8E, 0xAA9B, 0xCC8F, 0xAA9C, 0xCC90, 0xAA9D, 0xCC91, 0xAA9E, 0xCC92, 0xAA9F, 0xCC93, 0xAAA0, + 0xCC94, 0xAB41, 0xCC95, 0xAB42, 0xCC96, 0xAB43, 0xCC97, 0xAB44, 0xCC98, 0xC3B3, 0xCC99, 0xC3B4, 0xCC9A, 0xAB45, 0xCC9B, 0xAB46, + 0xCC9C, 0xC3B5, 0xCC9D, 0xAB47, 0xCC9E, 0xAB48, 0xCC9F, 0xAB49, 0xCCA0, 0xC3B6, 0xCCA1, 0xAB4A, 0xCCA2, 0xAB4B, 0xCCA3, 0xAB4C, + 0xCCA4, 0xAB4D, 0xCCA5, 0xAB4E, 0xCCA6, 0xAB4F, 0xCCA7, 0xAB50, 0xCCA8, 0xC3B7, 0xCCA9, 0xC3B8, 0xCCAA, 0xAB51, 0xCCAB, 0xC3B9, + 0xCCAC, 0xC3BA, 0xCCAD, 0xC3BB, 0xCCAE, 0xAB52, 0xCCAF, 0xAB53, 0xCCB0, 0xAB54, 0xCCB1, 0xAB55, 0xCCB2, 0xAB56, 0xCCB3, 0xAB57, + 0xCCB4, 0xC3BC, 0xCCB5, 0xC3BD, 0xCCB6, 0xAB58, 0xCCB7, 0xAB59, 0xCCB8, 0xC3BE, 0xCCB9, 0xAB5A, 0xCCBA, 0xAB61, 0xCCBB, 0xAB62, + 0xCCBC, 0xC3BF, 0xCCBD, 0xAB63, 0xCCBE, 0xAB64, 0xCCBF, 0xAB65, 0xCCC0, 0xAB66, 0xCCC1, 0xAB67, 0xCCC2, 0xAB68, 0xCCC3, 0xAB69, + 0xCCC4, 0xC3C0, 0xCCC5, 0xC3C1, 0xCCC6, 0xAB6A, 0xCCC7, 0xC3C2, 0xCCC8, 0xAB6B, 0xCCC9, 0xC3C3, 0xCCCA, 0xAB6C, 0xCCCB, 0xAB6D, + 0xCCCC, 0xAB6E, 0xCCCD, 0xAB6F, 0xCCCE, 0xAB70, 0xCCCF, 0xAB71, 0xCCD0, 0xC3C4, 0xCCD1, 0xAB72, 0xCCD2, 0xAB73, 0xCCD3, 0xAB74, + 0xCCD4, 0xC3C5, 0xCCD5, 0xAB75, 0xCCD6, 0xAB76, 0xCCD7, 0xAB77, 0xCCD8, 0xAB78, 0xCCD9, 0xAB79, 0xCCDA, 0xAB7A, 0xCCDB, 0xAB81, + 0xCCDC, 0xAB82, 0xCCDD, 0xAB83, 0xCCDE, 0xAB84, 0xCCDF, 0xAB85, 0xCCE0, 0xAB86, 0xCCE1, 0xAB87, 0xCCE2, 0xAB88, 0xCCE3, 0xAB89, + 0xCCE4, 0xC3C6, 0xCCE5, 0xAB8A, 0xCCE6, 0xAB8B, 0xCCE7, 0xAB8C, 0xCCE8, 0xAB8D, 0xCCE9, 0xAB8E, 0xCCEA, 0xAB8F, 0xCCEB, 0xAB90, + 0xCCEC, 0xC3C7, 0xCCED, 0xAB91, 0xCCEE, 0xAB92, 0xCCEF, 0xAB93, 0xCCF0, 0xC3C8, 0xCCF1, 0xAB94, 0xCCF2, 0xAB95, 0xCCF3, 0xAB96, + 0xCCF4, 0xAB97, 0xCCF5, 0xAB98, 0xCCF6, 0xAB99, 0xCCF7, 0xAB9A, 0xCCF8, 0xAB9B, 0xCCF9, 0xAB9C, 0xCCFA, 0xAB9D, 0xCCFB, 0xAB9E, + 0xCCFC, 0xAB9F, 0xCCFD, 0xABA0, 0xCCFE, 0xAC41, 0xCCFF, 0xAC42, 0xCD00, 0xAC43, 0xCD01, 0xC3C9, 0xCD02, 0xAC44, 0xCD03, 0xAC45, + 0xCD04, 0xAC46, 0xCD05, 0xAC47, 0xCD06, 0xAC48, 0xCD07, 0xAC49, 0xCD08, 0xC3CA, 0xCD09, 0xC3CB, 0xCD0A, 0xAC4A, 0xCD0B, 0xAC4B, + 0xCD0C, 0xC3CC, 0xCD0D, 0xAC4C, 0xCD0E, 0xAC4D, 0xCD0F, 0xAC4E, 0xCD10, 0xC3CD, 0xCD11, 0xAC4F, 0xCD12, 0xAC50, 0xCD13, 0xAC51, + 0xCD14, 0xAC52, 0xCD15, 0xAC53, 0xCD16, 0xAC54, 0xCD17, 0xAC55, 0xCD18, 0xC3CE, 0xCD19, 0xC3CF, 0xCD1A, 0xAC56, 0xCD1B, 0xC3D0, + 0xCD1C, 0xAC57, 0xCD1D, 0xC3D1, 0xCD1E, 0xAC58, 0xCD1F, 0xAC59, 0xCD20, 0xAC5A, 0xCD21, 0xAC61, 0xCD22, 0xAC62, 0xCD23, 0xAC63, + 0xCD24, 0xC3D2, 0xCD25, 0xAC64, 0xCD26, 0xAC65, 0xCD27, 0xAC66, 0xCD28, 0xC3D3, 0xCD29, 0xAC67, 0xCD2A, 0xAC68, 0xCD2B, 0xAC69, + 0xCD2C, 0xC3D4, 0xCD2D, 0xAC6A, 0xCD2E, 0xAC6B, 0xCD2F, 0xAC6C, 0xCD30, 0xAC6D, 0xCD31, 0xAC6E, 0xCD32, 0xAC6F, 0xCD33, 0xAC70, + 0xCD34, 0xAC71, 0xCD35, 0xAC72, 0xCD36, 0xAC73, 0xCD37, 0xAC74, 0xCD38, 0xAC75, 0xCD39, 0xC3D5, 0xCD3A, 0xAC76, 0xCD3B, 0xAC77, + 0xCD3C, 0xAC78, 0xCD3D, 0xAC79, 0xCD3E, 0xAC7A, 0xCD3F, 0xAC81, 0xCD40, 0xAC82, 0xCD41, 0xAC83, 0xCD42, 0xAC84, 0xCD43, 0xAC85, + 0xCD44, 0xAC86, 0xCD45, 0xAC87, 0xCD46, 0xAC88, 0xCD47, 0xAC89, 0xCD48, 0xAC8A, 0xCD49, 0xAC8B, 0xCD4A, 0xAC8C, 0xCD4B, 0xAC8D, + 0xCD4C, 0xAC8E, 0xCD4D, 0xAC8F, 0xCD4E, 0xAC90, 0xCD4F, 0xAC91, 0xCD50, 0xAC92, 0xCD51, 0xAC93, 0xCD52, 0xAC94, 0xCD53, 0xAC95, + 0xCD54, 0xAC96, 0xCD55, 0xAC97, 0xCD56, 0xAC98, 0xCD57, 0xAC99, 0xCD58, 0xAC9A, 0xCD59, 0xAC9B, 0xCD5A, 0xAC9C, 0xCD5B, 0xAC9D, + 0xCD5C, 0xC3D6, 0xCD5D, 0xAC9E, 0xCD5E, 0xAC9F, 0xCD5F, 0xACA0, 0xCD60, 0xC3D7, 0xCD61, 0xAD41, 0xCD62, 0xAD42, 0xCD63, 0xAD43, + 0xCD64, 0xC3D8, 0xCD65, 0xAD44, 0xCD66, 0xAD45, 0xCD67, 0xAD46, 0xCD68, 0xAD47, 0xCD69, 0xAD48, 0xCD6A, 0xAD49, 0xCD6B, 0xAD4A, + 0xCD6C, 0xC3D9, 0xCD6D, 0xC3DA, 0xCD6E, 0xAD4B, 0xCD6F, 0xC3DB, 0xCD70, 0xAD4C, 0xCD71, 0xC3DC, 0xCD72, 0xAD4D, 0xCD73, 0xAD4E, + 0xCD74, 0xAD4F, 0xCD75, 0xAD50, 0xCD76, 0xAD51, 0xCD77, 0xAD52, 0xCD78, 0xC3DD, 0xCD79, 0xAD53, 0xCD7A, 0xAD54, 0xCD7B, 0xAD55, + 0xCD7C, 0xAD56, 0xCD7D, 0xAD57, 0xCD7E, 0xAD58, 0xCD7F, 0xAD59, 0xCD80, 0xAD5A, 0xCD81, 0xAD61, 0xCD82, 0xAD62, 0xCD83, 0xAD63, + 0xCD84, 0xAD64, 0xCD85, 0xAD65, 0xCD86, 0xAD66, 0xCD87, 0xAD67, 0xCD88, 0xC3DE, 0xCD89, 0xAD68, 0xCD8A, 0xAD69, 0xCD8B, 0xAD6A, + 0xCD8C, 0xAD6B, 0xCD8D, 0xAD6C, 0xCD8E, 0xAD6D, 0xCD8F, 0xAD6E, 0xCD90, 0xAD6F, 0xCD91, 0xAD70, 0xCD92, 0xAD71, 0xCD93, 0xAD72, + 0xCD94, 0xC3DF, 0xCD95, 0xC3E0, 0xCD96, 0xAD73, 0xCD97, 0xAD74, 0xCD98, 0xC3E1, 0xCD99, 0xAD75, 0xCD9A, 0xAD76, 0xCD9B, 0xAD77, + 0xCD9C, 0xC3E2, 0xCD9D, 0xAD78, 0xCD9E, 0xAD79, 0xCD9F, 0xAD7A, 0xCDA0, 0xAD81, 0xCDA1, 0xAD82, 0xCDA2, 0xAD83, 0xCDA3, 0xAD84, + 0xCDA4, 0xC3E3, 0xCDA5, 0xC3E4, 0xCDA6, 0xAD85, 0xCDA7, 0xC3E5, 0xCDA8, 0xAD86, 0xCDA9, 0xC3E6, 0xCDAA, 0xAD87, 0xCDAB, 0xAD88, + 0xCDAC, 0xAD89, 0xCDAD, 0xAD8A, 0xCDAE, 0xAD8B, 0xCDAF, 0xAD8C, 0xCDB0, 0xC3E7, 0xCDB1, 0xAD8D, 0xCDB2, 0xAD8E, 0xCDB3, 0xAD8F, + 0xCDB4, 0xAD90, 0xCDB5, 0xAD91, 0xCDB6, 0xAD92, 0xCDB7, 0xAD93, 0xCDB8, 0xAD94, 0xCDB9, 0xAD95, 0xCDBA, 0xAD96, 0xCDBB, 0xAD97, + 0xCDBC, 0xAD98, 0xCDBD, 0xAD99, 0xCDBE, 0xAD9A, 0xCDBF, 0xAD9B, 0xCDC0, 0xAD9C, 0xCDC1, 0xAD9D, 0xCDC2, 0xAD9E, 0xCDC3, 0xAD9F, + 0xCDC4, 0xC3E8, 0xCDC5, 0xADA0, 0xCDC6, 0xAE41, 0xCDC7, 0xAE42, 0xCDC8, 0xAE43, 0xCDC9, 0xAE44, 0xCDCA, 0xAE45, 0xCDCB, 0xAE46, + 0xCDCC, 0xC3E9, 0xCDCD, 0xAE47, 0xCDCE, 0xAE48, 0xCDCF, 0xAE49, 0xCDD0, 0xC3EA, 0xCDD1, 0xAE4A, 0xCDD2, 0xAE4B, 0xCDD3, 0xAE4C, + 0xCDD4, 0xAE4D, 0xCDD5, 0xAE4E, 0xCDD6, 0xAE4F, 0xCDD7, 0xAE50, 0xCDD8, 0xAE51, 0xCDD9, 0xAE52, 0xCDDA, 0xAE53, 0xCDDB, 0xAE54, + 0xCDDC, 0xAE55, 0xCDDD, 0xAE56, 0xCDDE, 0xAE57, 0xCDDF, 0xAE58, 0xCDE0, 0xAE59, 0xCDE1, 0xAE5A, 0xCDE2, 0xAE61, 0xCDE3, 0xAE62, + 0xCDE4, 0xAE63, 0xCDE5, 0xAE64, 0xCDE6, 0xAE65, 0xCDE7, 0xAE66, 0xCDE8, 0xC3EB, 0xCDE9, 0xAE67, 0xCDEA, 0xAE68, 0xCDEB, 0xAE69, + 0xCDEC, 0xC3EC, 0xCDED, 0xAE6A, 0xCDEE, 0xAE6B, 0xCDEF, 0xAE6C, 0xCDF0, 0xC3ED, 0xCDF1, 0xAE6D, 0xCDF2, 0xAE6E, 0xCDF3, 0xAE6F, + 0xCDF4, 0xAE70, 0xCDF5, 0xAE71, 0xCDF6, 0xAE72, 0xCDF7, 0xAE73, 0xCDF8, 0xC3EE, 0xCDF9, 0xC3EF, 0xCDFA, 0xAE74, 0xCDFB, 0xC3F0, + 0xCDFC, 0xAE75, 0xCDFD, 0xC3F1, 0xCDFE, 0xAE76, 0xCDFF, 0xAE77, 0xCE00, 0xAE78, 0xCE01, 0xAE79, 0xCE02, 0xAE7A, 0xCE03, 0xAE81, + 0xCE04, 0xC3F2, 0xCE05, 0xAE82, 0xCE06, 0xAE83, 0xCE07, 0xAE84, 0xCE08, 0xC3F3, 0xCE09, 0xAE85, 0xCE0A, 0xAE86, 0xCE0B, 0xAE87, + 0xCE0C, 0xC3F4, 0xCE0D, 0xAE88, 0xCE0E, 0xAE89, 0xCE0F, 0xAE8A, 0xCE10, 0xAE8B, 0xCE11, 0xAE8C, 0xCE12, 0xAE8D, 0xCE13, 0xAE8E, + 0xCE14, 0xC3F5, 0xCE15, 0xAE8F, 0xCE16, 0xAE90, 0xCE17, 0xAE91, 0xCE18, 0xAE92, 0xCE19, 0xC3F6, 0xCE1A, 0xAE93, 0xCE1B, 0xAE94, + 0xCE1C, 0xAE95, 0xCE1D, 0xAE96, 0xCE1E, 0xAE97, 0xCE1F, 0xAE98, 0xCE20, 0xC3F7, 0xCE21, 0xC3F8, 0xCE22, 0xAE99, 0xCE23, 0xAE9A, + 0xCE24, 0xC3F9, 0xCE25, 0xAE9B, 0xCE26, 0xAE9C, 0xCE27, 0xAE9D, 0xCE28, 0xC3FA, 0xCE29, 0xAE9E, 0xCE2A, 0xAE9F, 0xCE2B, 0xAEA0, + 0xCE2C, 0xAF41, 0xCE2D, 0xAF42, 0xCE2E, 0xAF43, 0xCE2F, 0xAF44, 0xCE30, 0xC3FB, 0xCE31, 0xC3FC, 0xCE32, 0xAF45, 0xCE33, 0xC3FD, + 0xCE34, 0xAF46, 0xCE35, 0xC3FE, 0xCE36, 0xAF47, 0xCE37, 0xAF48, 0xCE38, 0xAF49, 0xCE39, 0xAF4A, 0xCE3A, 0xAF4B, 0xCE3B, 0xAF4C, + 0xCE3C, 0xAF4D, 0xCE3D, 0xAF4E, 0xCE3E, 0xAF4F, 0xCE3F, 0xAF50, 0xCE40, 0xAF51, 0xCE41, 0xAF52, 0xCE42, 0xAF53, 0xCE43, 0xAF54, + 0xCE44, 0xAF55, 0xCE45, 0xAF56, 0xCE46, 0xAF57, 0xCE47, 0xAF58, 0xCE48, 0xAF59, 0xCE49, 0xAF5A, 0xCE4A, 0xAF61, 0xCE4B, 0xAF62, + 0xCE4C, 0xAF63, 0xCE4D, 0xAF64, 0xCE4E, 0xAF65, 0xCE4F, 0xAF66, 0xCE50, 0xAF67, 0xCE51, 0xAF68, 0xCE52, 0xAF69, 0xCE53, 0xAF6A, + 0xCE54, 0xAF6B, 0xCE55, 0xAF6C, 0xCE56, 0xAF6D, 0xCE57, 0xAF6E, 0xCE58, 0xC4A1, 0xCE59, 0xC4A2, 0xCE5A, 0xAF6F, 0xCE5B, 0xAF70, + 0xCE5C, 0xC4A3, 0xCE5D, 0xAF71, 0xCE5E, 0xAF72, 0xCE5F, 0xC4A4, 0xCE60, 0xC4A5, 0xCE61, 0xC4A6, 0xCE62, 0xAF73, 0xCE63, 0xAF74, + 0xCE64, 0xAF75, 0xCE65, 0xAF76, 0xCE66, 0xAF77, 0xCE67, 0xAF78, 0xCE68, 0xC4A7, 0xCE69, 0xC4A8, 0xCE6A, 0xAF79, 0xCE6B, 0xC4A9, + 0xCE6C, 0xAF7A, 0xCE6D, 0xC4AA, 0xCE6E, 0xAF81, 0xCE6F, 0xAF82, 0xCE70, 0xAF83, 0xCE71, 0xAF84, 0xCE72, 0xAF85, 0xCE73, 0xAF86, + 0xCE74, 0xC4AB, 0xCE75, 0xC4AC, 0xCE76, 0xAF87, 0xCE77, 0xAF88, 0xCE78, 0xC4AD, 0xCE79, 0xAF89, 0xCE7A, 0xAF8A, 0xCE7B, 0xAF8B, + 0xCE7C, 0xC4AE, 0xCE7D, 0xAF8C, 0xCE7E, 0xAF8D, 0xCE7F, 0xAF8E, 0xCE80, 0xAF8F, 0xCE81, 0xAF90, 0xCE82, 0xAF91, 0xCE83, 0xAF92, + 0xCE84, 0xC4AF, 0xCE85, 0xC4B0, 0xCE86, 0xAF93, 0xCE87, 0xC4B1, 0xCE88, 0xAF94, 0xCE89, 0xC4B2, 0xCE8A, 0xAF95, 0xCE8B, 0xAF96, + 0xCE8C, 0xAF97, 0xCE8D, 0xAF98, 0xCE8E, 0xAF99, 0xCE8F, 0xAF9A, 0xCE90, 0xC4B3, 0xCE91, 0xC4B4, 0xCE92, 0xAF9B, 0xCE93, 0xAF9C, + 0xCE94, 0xC4B5, 0xCE95, 0xAF9D, 0xCE96, 0xAF9E, 0xCE97, 0xAF9F, 0xCE98, 0xC4B6, 0xCE99, 0xAFA0, 0xCE9A, 0xB041, 0xCE9B, 0xB042, + 0xCE9C, 0xB043, 0xCE9D, 0xB044, 0xCE9E, 0xB045, 0xCE9F, 0xB046, 0xCEA0, 0xC4B7, 0xCEA1, 0xC4B8, 0xCEA2, 0xB047, 0xCEA3, 0xC4B9, + 0xCEA4, 0xC4BA, 0xCEA5, 0xC4BB, 0xCEA6, 0xB048, 0xCEA7, 0xB049, 0xCEA8, 0xB04A, 0xCEA9, 0xB04B, 0xCEAA, 0xB04C, 0xCEAB, 0xB04D, + 0xCEAC, 0xC4BC, 0xCEAD, 0xC4BD, 0xCEAE, 0xB04E, 0xCEAF, 0xB04F, 0xCEB0, 0xB050, 0xCEB1, 0xB051, 0xCEB2, 0xB052, 0xCEB3, 0xB053, + 0xCEB4, 0xB054, 0xCEB5, 0xB055, 0xCEB6, 0xB056, 0xCEB7, 0xB057, 0xCEB8, 0xB058, 0xCEB9, 0xB059, 0xCEBA, 0xB05A, 0xCEBB, 0xB061, + 0xCEBC, 0xB062, 0xCEBD, 0xB063, 0xCEBE, 0xB064, 0xCEBF, 0xB065, 0xCEC0, 0xB066, 0xCEC1, 0xC4BE, 0xCEC2, 0xB067, 0xCEC3, 0xB068, + 0xCEC4, 0xB069, 0xCEC5, 0xB06A, 0xCEC6, 0xB06B, 0xCEC7, 0xB06C, 0xCEC8, 0xB06D, 0xCEC9, 0xB06E, 0xCECA, 0xB06F, 0xCECB, 0xB070, + 0xCECC, 0xB071, 0xCECD, 0xB072, 0xCECE, 0xB073, 0xCECF, 0xB074, 0xCED0, 0xB075, 0xCED1, 0xB076, 0xCED2, 0xB077, 0xCED3, 0xB078, + 0xCED4, 0xB079, 0xCED5, 0xB07A, 0xCED6, 0xB081, 0xCED7, 0xB082, 0xCED8, 0xB083, 0xCED9, 0xB084, 0xCEDA, 0xB085, 0xCEDB, 0xB086, + 0xCEDC, 0xB087, 0xCEDD, 0xB088, 0xCEDE, 0xB089, 0xCEDF, 0xB08A, 0xCEE0, 0xB08B, 0xCEE1, 0xB08C, 0xCEE2, 0xB08D, 0xCEE3, 0xB08E, + 0xCEE4, 0xC4BF, 0xCEE5, 0xC4C0, 0xCEE6, 0xB08F, 0xCEE7, 0xB090, 0xCEE8, 0xC4C1, 0xCEE9, 0xB091, 0xCEEA, 0xB092, 0xCEEB, 0xC4C2, + 0xCEEC, 0xC4C3, 0xCEED, 0xB093, 0xCEEE, 0xB094, 0xCEEF, 0xB095, 0xCEF0, 0xB096, 0xCEF1, 0xB097, 0xCEF2, 0xB098, 0xCEF3, 0xB099, + 0xCEF4, 0xC4C4, 0xCEF5, 0xC4C5, 0xCEF6, 0xB09A, 0xCEF7, 0xC4C6, 0xCEF8, 0xC4C7, 0xCEF9, 0xC4C8, 0xCEFA, 0xB09B, 0xCEFB, 0xB09C, + 0xCEFC, 0xB09D, 0xCEFD, 0xB09E, 0xCEFE, 0xB09F, 0xCEFF, 0xB0A0, 0xCF00, 0xC4C9, 0xCF01, 0xC4CA, 0xCF02, 0xB141, 0xCF03, 0xB142, + 0xCF04, 0xC4CB, 0xCF05, 0xB143, 0xCF06, 0xB144, 0xCF07, 0xB145, 0xCF08, 0xC4CC, 0xCF09, 0xB146, 0xCF0A, 0xB147, 0xCF0B, 0xB148, + 0xCF0C, 0xB149, 0xCF0D, 0xB14A, 0xCF0E, 0xB14B, 0xCF0F, 0xB14C, 0xCF10, 0xC4CD, 0xCF11, 0xC4CE, 0xCF12, 0xB14D, 0xCF13, 0xC4CF, + 0xCF14, 0xB14E, 0xCF15, 0xC4D0, 0xCF16, 0xB14F, 0xCF17, 0xB150, 0xCF18, 0xB151, 0xCF19, 0xB152, 0xCF1A, 0xB153, 0xCF1B, 0xB154, + 0xCF1C, 0xC4D1, 0xCF1D, 0xB155, 0xCF1E, 0xB156, 0xCF1F, 0xB157, 0xCF20, 0xC4D2, 0xCF21, 0xB158, 0xCF22, 0xB159, 0xCF23, 0xB15A, + 0xCF24, 0xC4D3, 0xCF25, 0xB161, 0xCF26, 0xB162, 0xCF27, 0xB163, 0xCF28, 0xB164, 0xCF29, 0xB165, 0xCF2A, 0xB166, 0xCF2B, 0xB167, + 0xCF2C, 0xC4D4, 0xCF2D, 0xC4D5, 0xCF2E, 0xB168, 0xCF2F, 0xC4D6, 0xCF30, 0xC4D7, 0xCF31, 0xC4D8, 0xCF32, 0xB169, 0xCF33, 0xB16A, + 0xCF34, 0xB16B, 0xCF35, 0xB16C, 0xCF36, 0xB16D, 0xCF37, 0xB16E, 0xCF38, 0xC4D9, 0xCF39, 0xB16F, 0xCF3A, 0xB170, 0xCF3B, 0xB171, + 0xCF3C, 0xB172, 0xCF3D, 0xB173, 0xCF3E, 0xB174, 0xCF3F, 0xB175, 0xCF40, 0xB176, 0xCF41, 0xB177, 0xCF42, 0xB178, 0xCF43, 0xB179, + 0xCF44, 0xB17A, 0xCF45, 0xB181, 0xCF46, 0xB182, 0xCF47, 0xB183, 0xCF48, 0xB184, 0xCF49, 0xB185, 0xCF4A, 0xB186, 0xCF4B, 0xB187, + 0xCF4C, 0xB188, 0xCF4D, 0xB189, 0xCF4E, 0xB18A, 0xCF4F, 0xB18B, 0xCF50, 0xB18C, 0xCF51, 0xB18D, 0xCF52, 0xB18E, 0xCF53, 0xB18F, + 0xCF54, 0xC4DA, 0xCF55, 0xC4DB, 0xCF56, 0xB190, 0xCF57, 0xB191, 0xCF58, 0xC4DC, 0xCF59, 0xB192, 0xCF5A, 0xB193, 0xCF5B, 0xB194, + 0xCF5C, 0xC4DD, 0xCF5D, 0xB195, 0xCF5E, 0xB196, 0xCF5F, 0xB197, 0xCF60, 0xB198, 0xCF61, 0xB199, 0xCF62, 0xB19A, 0xCF63, 0xB19B, + 0xCF64, 0xC4DE, 0xCF65, 0xC4DF, 0xCF66, 0xB19C, 0xCF67, 0xC4E0, 0xCF68, 0xB19D, 0xCF69, 0xC4E1, 0xCF6A, 0xB19E, 0xCF6B, 0xB19F, + 0xCF6C, 0xB1A0, 0xCF6D, 0xB241, 0xCF6E, 0xB242, 0xCF6F, 0xB243, 0xCF70, 0xC4E2, 0xCF71, 0xC4E3, 0xCF72, 0xB244, 0xCF73, 0xB245, + 0xCF74, 0xC4E4, 0xCF75, 0xB246, 0xCF76, 0xB247, 0xCF77, 0xB248, 0xCF78, 0xC4E5, 0xCF79, 0xB249, 0xCF7A, 0xB24A, 0xCF7B, 0xB24B, + 0xCF7C, 0xB24C, 0xCF7D, 0xB24D, 0xCF7E, 0xB24E, 0xCF7F, 0xB24F, 0xCF80, 0xC4E6, 0xCF81, 0xB250, 0xCF82, 0xB251, 0xCF83, 0xB252, + 0xCF84, 0xB253, 0xCF85, 0xC4E7, 0xCF86, 0xB254, 0xCF87, 0xB255, 0xCF88, 0xB256, 0xCF89, 0xB257, 0xCF8A, 0xB258, 0xCF8B, 0xB259, + 0xCF8C, 0xC4E8, 0xCF8D, 0xB25A, 0xCF8E, 0xB261, 0xCF8F, 0xB262, 0xCF90, 0xB263, 0xCF91, 0xB264, 0xCF92, 0xB265, 0xCF93, 0xB266, + 0xCF94, 0xB267, 0xCF95, 0xB268, 0xCF96, 0xB269, 0xCF97, 0xB26A, 0xCF98, 0xB26B, 0xCF99, 0xB26C, 0xCF9A, 0xB26D, 0xCF9B, 0xB26E, + 0xCF9C, 0xB26F, 0xCF9D, 0xB270, 0xCF9E, 0xB271, 0xCF9F, 0xB272, 0xCFA0, 0xB273, 0xCFA1, 0xC4E9, 0xCFA2, 0xB274, 0xCFA3, 0xB275, + 0xCFA4, 0xB276, 0xCFA5, 0xB277, 0xCFA6, 0xB278, 0xCFA7, 0xB279, 0xCFA8, 0xC4EA, 0xCFA9, 0xB27A, 0xCFAA, 0xB281, 0xCFAB, 0xB282, + 0xCFAC, 0xB283, 0xCFAD, 0xB284, 0xCFAE, 0xB285, 0xCFAF, 0xB286, 0xCFB0, 0xC4EB, 0xCFB1, 0xB287, 0xCFB2, 0xB288, 0xCFB3, 0xB289, + 0xCFB4, 0xB28A, 0xCFB5, 0xB28B, 0xCFB6, 0xB28C, 0xCFB7, 0xB28D, 0xCFB8, 0xB28E, 0xCFB9, 0xB28F, 0xCFBA, 0xB290, 0xCFBB, 0xB291, + 0xCFBC, 0xB292, 0xCFBD, 0xB293, 0xCFBE, 0xB294, 0xCFBF, 0xB295, 0xCFC0, 0xB296, 0xCFC1, 0xB297, 0xCFC2, 0xB298, 0xCFC3, 0xB299, + 0xCFC4, 0xC4EC, 0xCFC5, 0xB29A, 0xCFC6, 0xB29B, 0xCFC7, 0xB29C, 0xCFC8, 0xB29D, 0xCFC9, 0xB29E, 0xCFCA, 0xB29F, 0xCFCB, 0xB2A0, + 0xCFCC, 0xB341, 0xCFCD, 0xB342, 0xCFCE, 0xB343, 0xCFCF, 0xB344, 0xCFD0, 0xB345, 0xCFD1, 0xB346, 0xCFD2, 0xB347, 0xCFD3, 0xB348, + 0xCFD4, 0xB349, 0xCFD5, 0xB34A, 0xCFD6, 0xB34B, 0xCFD7, 0xB34C, 0xCFD8, 0xB34D, 0xCFD9, 0xB34E, 0xCFDA, 0xB34F, 0xCFDB, 0xB350, + 0xCFDC, 0xB351, 0xCFDD, 0xB352, 0xCFDE, 0xB353, 0xCFDF, 0xB354, 0xCFE0, 0xC4ED, 0xCFE1, 0xC4EE, 0xCFE2, 0xB355, 0xCFE3, 0xB356, + 0xCFE4, 0xC4EF, 0xCFE5, 0xB357, 0xCFE6, 0xB358, 0xCFE7, 0xB359, 0xCFE8, 0xC4F0, 0xCFE9, 0xB35A, 0xCFEA, 0xB361, 0xCFEB, 0xB362, + 0xCFEC, 0xB363, 0xCFED, 0xB364, 0xCFEE, 0xB365, 0xCFEF, 0xB366, 0xCFF0, 0xC4F1, 0xCFF1, 0xC4F2, 0xCFF2, 0xB367, 0xCFF3, 0xC4F3, + 0xCFF4, 0xB368, 0xCFF5, 0xC4F4, 0xCFF6, 0xB369, 0xCFF7, 0xB36A, 0xCFF8, 0xB36B, 0xCFF9, 0xB36C, 0xCFFA, 0xB36D, 0xCFFB, 0xB36E, + 0xCFFC, 0xC4F5, 0xCFFD, 0xB36F, 0xCFFE, 0xB370, 0xCFFF, 0xB371, 0xD000, 0xC4F6, 0xD001, 0xB372, 0xD002, 0xB373, 0xD003, 0xB374, + 0xD004, 0xC4F7, 0xD005, 0xB375, 0xD006, 0xB376, 0xD007, 0xB377, 0xD008, 0xB378, 0xD009, 0xB379, 0xD00A, 0xB37A, 0xD00B, 0xB381, + 0xD00C, 0xB382, 0xD00D, 0xB383, 0xD00E, 0xB384, 0xD00F, 0xB385, 0xD010, 0xB386, 0xD011, 0xC4F8, 0xD012, 0xB387, 0xD013, 0xB388, + 0xD014, 0xB389, 0xD015, 0xB38A, 0xD016, 0xB38B, 0xD017, 0xB38C, 0xD018, 0xC4F9, 0xD019, 0xB38D, 0xD01A, 0xB38E, 0xD01B, 0xB38F, + 0xD01C, 0xB390, 0xD01D, 0xB391, 0xD01E, 0xB392, 0xD01F, 0xB393, 0xD020, 0xB394, 0xD021, 0xB395, 0xD022, 0xB396, 0xD023, 0xB397, + 0xD024, 0xB398, 0xD025, 0xB399, 0xD026, 0xB39A, 0xD027, 0xB39B, 0xD028, 0xB39C, 0xD029, 0xB39D, 0xD02A, 0xB39E, 0xD02B, 0xB39F, + 0xD02C, 0xB3A0, 0xD02D, 0xC4FA, 0xD02E, 0xB441, 0xD02F, 0xB442, 0xD030, 0xB443, 0xD031, 0xB444, 0xD032, 0xB445, 0xD033, 0xB446, + 0xD034, 0xC4FB, 0xD035, 0xC4FC, 0xD036, 0xB447, 0xD037, 0xB448, 0xD038, 0xC4FD, 0xD039, 0xB449, 0xD03A, 0xB44A, 0xD03B, 0xB44B, + 0xD03C, 0xC4FE, 0xD03D, 0xB44C, 0xD03E, 0xB44D, 0xD03F, 0xB44E, 0xD040, 0xB44F, 0xD041, 0xB450, 0xD042, 0xB451, 0xD043, 0xB452, + 0xD044, 0xC5A1, 0xD045, 0xC5A2, 0xD046, 0xB453, 0xD047, 0xC5A3, 0xD048, 0xB454, 0xD049, 0xC5A4, 0xD04A, 0xB455, 0xD04B, 0xB456, + 0xD04C, 0xB457, 0xD04D, 0xB458, 0xD04E, 0xB459, 0xD04F, 0xB45A, 0xD050, 0xC5A5, 0xD051, 0xB461, 0xD052, 0xB462, 0xD053, 0xB463, + 0xD054, 0xC5A6, 0xD055, 0xB464, 0xD056, 0xB465, 0xD057, 0xB466, 0xD058, 0xC5A7, 0xD059, 0xB467, 0xD05A, 0xB468, 0xD05B, 0xB469, + 0xD05C, 0xB46A, 0xD05D, 0xB46B, 0xD05E, 0xB46C, 0xD05F, 0xB46D, 0xD060, 0xC5A8, 0xD061, 0xB46E, 0xD062, 0xB46F, 0xD063, 0xB470, + 0xD064, 0xB471, 0xD065, 0xB472, 0xD066, 0xB473, 0xD067, 0xB474, 0xD068, 0xB475, 0xD069, 0xB476, 0xD06A, 0xB477, 0xD06B, 0xB478, + 0xD06C, 0xC5A9, 0xD06D, 0xC5AA, 0xD06E, 0xB479, 0xD06F, 0xB47A, 0xD070, 0xC5AB, 0xD071, 0xB481, 0xD072, 0xB482, 0xD073, 0xB483, + 0xD074, 0xC5AC, 0xD075, 0xB484, 0xD076, 0xB485, 0xD077, 0xB486, 0xD078, 0xB487, 0xD079, 0xB488, 0xD07A, 0xB489, 0xD07B, 0xB48A, + 0xD07C, 0xC5AD, 0xD07D, 0xC5AE, 0xD07E, 0xB48B, 0xD07F, 0xB48C, 0xD080, 0xB48D, 0xD081, 0xC5AF, 0xD082, 0xB48E, 0xD083, 0xB48F, + 0xD084, 0xB490, 0xD085, 0xB491, 0xD086, 0xB492, 0xD087, 0xB493, 0xD088, 0xB494, 0xD089, 0xB495, 0xD08A, 0xB496, 0xD08B, 0xB497, + 0xD08C, 0xB498, 0xD08D, 0xB499, 0xD08E, 0xB49A, 0xD08F, 0xB49B, 0xD090, 0xB49C, 0xD091, 0xB49D, 0xD092, 0xB49E, 0xD093, 0xB49F, + 0xD094, 0xB4A0, 0xD095, 0xB541, 0xD096, 0xB542, 0xD097, 0xB543, 0xD098, 0xB544, 0xD099, 0xB545, 0xD09A, 0xB546, 0xD09B, 0xB547, + 0xD09C, 0xB548, 0xD09D, 0xB549, 0xD09E, 0xB54A, 0xD09F, 0xB54B, 0xD0A0, 0xB54C, 0xD0A1, 0xB54D, 0xD0A2, 0xB54E, 0xD0A3, 0xB54F, + 0xD0A4, 0xC5B0, 0xD0A5, 0xC5B1, 0xD0A6, 0xB550, 0xD0A7, 0xB551, 0xD0A8, 0xC5B2, 0xD0A9, 0xB552, 0xD0AA, 0xB553, 0xD0AB, 0xB554, + 0xD0AC, 0xC5B3, 0xD0AD, 0xB555, 0xD0AE, 0xB556, 0xD0AF, 0xB557, 0xD0B0, 0xB558, 0xD0B1, 0xB559, 0xD0B2, 0xB55A, 0xD0B3, 0xB561, + 0xD0B4, 0xC5B4, 0xD0B5, 0xC5B5, 0xD0B6, 0xB562, 0xD0B7, 0xC5B6, 0xD0B8, 0xB563, 0xD0B9, 0xC5B7, 0xD0BA, 0xB564, 0xD0BB, 0xB565, + 0xD0BC, 0xB566, 0xD0BD, 0xB567, 0xD0BE, 0xB568, 0xD0BF, 0xB569, 0xD0C0, 0xC5B8, 0xD0C1, 0xC5B9, 0xD0C2, 0xB56A, 0xD0C3, 0xB56B, + 0xD0C4, 0xC5BA, 0xD0C5, 0xB56C, 0xD0C6, 0xB56D, 0xD0C7, 0xB56E, 0xD0C8, 0xC5BB, 0xD0C9, 0xC5BC, 0xD0CA, 0xB56F, 0xD0CB, 0xB570, + 0xD0CC, 0xB571, 0xD0CD, 0xB572, 0xD0CE, 0xB573, 0xD0CF, 0xB574, 0xD0D0, 0xC5BD, 0xD0D1, 0xC5BE, 0xD0D2, 0xB575, 0xD0D3, 0xC5BF, + 0xD0D4, 0xC5C0, 0xD0D5, 0xC5C1, 0xD0D6, 0xB576, 0xD0D7, 0xB577, 0xD0D8, 0xB578, 0xD0D9, 0xB579, 0xD0DA, 0xB57A, 0xD0DB, 0xB581, + 0xD0DC, 0xC5C2, 0xD0DD, 0xC5C3, 0xD0DE, 0xB582, 0xD0DF, 0xB583, 0xD0E0, 0xC5C4, 0xD0E1, 0xB584, 0xD0E2, 0xB585, 0xD0E3, 0xB586, + 0xD0E4, 0xC5C5, 0xD0E5, 0xB587, 0xD0E6, 0xB588, 0xD0E7, 0xB589, 0xD0E8, 0xB58A, 0xD0E9, 0xB58B, 0xD0EA, 0xB58C, 0xD0EB, 0xB58D, + 0xD0EC, 0xC5C6, 0xD0ED, 0xC5C7, 0xD0EE, 0xB58E, 0xD0EF, 0xC5C8, 0xD0F0, 0xC5C9, 0xD0F1, 0xC5CA, 0xD0F2, 0xB58F, 0xD0F3, 0xB590, + 0xD0F4, 0xB591, 0xD0F5, 0xB592, 0xD0F6, 0xB593, 0xD0F7, 0xB594, 0xD0F8, 0xC5CB, 0xD0F9, 0xB595, 0xD0FA, 0xB596, 0xD0FB, 0xB597, + 0xD0FC, 0xB598, 0xD0FD, 0xB599, 0xD0FE, 0xB59A, 0xD0FF, 0xB59B, 0xD100, 0xB59C, 0xD101, 0xB59D, 0xD102, 0xB59E, 0xD103, 0xB59F, + 0xD104, 0xB5A0, 0xD105, 0xB641, 0xD106, 0xB642, 0xD107, 0xB643, 0xD108, 0xB644, 0xD109, 0xB645, 0xD10A, 0xB646, 0xD10B, 0xB647, + 0xD10C, 0xB648, 0xD10D, 0xC5CC, 0xD10E, 0xB649, 0xD10F, 0xB64A, 0xD110, 0xB64B, 0xD111, 0xB64C, 0xD112, 0xB64D, 0xD113, 0xB64E, + 0xD114, 0xB64F, 0xD115, 0xB650, 0xD116, 0xB651, 0xD117, 0xB652, 0xD118, 0xB653, 0xD119, 0xB654, 0xD11A, 0xB655, 0xD11B, 0xB656, + 0xD11C, 0xB657, 0xD11D, 0xB658, 0xD11E, 0xB659, 0xD11F, 0xB65A, 0xD120, 0xB661, 0xD121, 0xB662, 0xD122, 0xB663, 0xD123, 0xB664, + 0xD124, 0xB665, 0xD125, 0xB666, 0xD126, 0xB667, 0xD127, 0xB668, 0xD128, 0xB669, 0xD129, 0xB66A, 0xD12A, 0xB66B, 0xD12B, 0xB66C, + 0xD12C, 0xB66D, 0xD12D, 0xB66E, 0xD12E, 0xB66F, 0xD12F, 0xB670, 0xD130, 0xC5CD, 0xD131, 0xC5CE, 0xD132, 0xB671, 0xD133, 0xB672, + 0xD134, 0xC5CF, 0xD135, 0xB673, 0xD136, 0xB674, 0xD137, 0xB675, 0xD138, 0xC5D0, 0xD139, 0xB676, 0xD13A, 0xC5D1, 0xD13B, 0xB677, + 0xD13C, 0xB678, 0xD13D, 0xB679, 0xD13E, 0xB67A, 0xD13F, 0xB681, 0xD140, 0xC5D2, 0xD141, 0xC5D3, 0xD142, 0xB682, 0xD143, 0xC5D4, + 0xD144, 0xC5D5, 0xD145, 0xC5D6, 0xD146, 0xB683, 0xD147, 0xB684, 0xD148, 0xB685, 0xD149, 0xB686, 0xD14A, 0xB687, 0xD14B, 0xB688, + 0xD14C, 0xC5D7, 0xD14D, 0xC5D8, 0xD14E, 0xB689, 0xD14F, 0xB68A, 0xD150, 0xC5D9, 0xD151, 0xB68B, 0xD152, 0xB68C, 0xD153, 0xB68D, + 0xD154, 0xC5DA, 0xD155, 0xB68E, 0xD156, 0xB68F, 0xD157, 0xB690, 0xD158, 0xB691, 0xD159, 0xB692, 0xD15A, 0xB693, 0xD15B, 0xB694, + 0xD15C, 0xC5DB, 0xD15D, 0xC5DC, 0xD15E, 0xB695, 0xD15F, 0xC5DD, 0xD160, 0xB696, 0xD161, 0xC5DE, 0xD162, 0xB697, 0xD163, 0xB698, + 0xD164, 0xB699, 0xD165, 0xB69A, 0xD166, 0xB69B, 0xD167, 0xB69C, 0xD168, 0xC5DF, 0xD169, 0xB69D, 0xD16A, 0xB69E, 0xD16B, 0xB69F, + 0xD16C, 0xC5E0, 0xD16D, 0xB6A0, 0xD16E, 0xB741, 0xD16F, 0xB742, 0xD170, 0xB743, 0xD171, 0xB744, 0xD172, 0xB745, 0xD173, 0xB746, + 0xD174, 0xB747, 0xD175, 0xB748, 0xD176, 0xB749, 0xD177, 0xB74A, 0xD178, 0xB74B, 0xD179, 0xB74C, 0xD17A, 0xB74D, 0xD17B, 0xB74E, + 0xD17C, 0xC5E1, 0xD17D, 0xB74F, 0xD17E, 0xB750, 0xD17F, 0xB751, 0xD180, 0xB752, 0xD181, 0xB753, 0xD182, 0xB754, 0xD183, 0xB755, + 0xD184, 0xC5E2, 0xD185, 0xB756, 0xD186, 0xB757, 0xD187, 0xB758, 0xD188, 0xC5E3, 0xD189, 0xB759, 0xD18A, 0xB75A, 0xD18B, 0xB761, + 0xD18C, 0xB762, 0xD18D, 0xB763, 0xD18E, 0xB764, 0xD18F, 0xB765, 0xD190, 0xB766, 0xD191, 0xB767, 0xD192, 0xB768, 0xD193, 0xB769, + 0xD194, 0xB76A, 0xD195, 0xB76B, 0xD196, 0xB76C, 0xD197, 0xB76D, 0xD198, 0xB76E, 0xD199, 0xB76F, 0xD19A, 0xB770, 0xD19B, 0xB771, + 0xD19C, 0xB772, 0xD19D, 0xB773, 0xD19E, 0xB774, 0xD19F, 0xB775, 0xD1A0, 0xC5E4, 0xD1A1, 0xC5E5, 0xD1A2, 0xB776, 0xD1A3, 0xB777, + 0xD1A4, 0xC5E6, 0xD1A5, 0xB778, 0xD1A6, 0xB779, 0xD1A7, 0xB77A, 0xD1A8, 0xC5E7, 0xD1A9, 0xB781, 0xD1AA, 0xB782, 0xD1AB, 0xB783, + 0xD1AC, 0xB784, 0xD1AD, 0xB785, 0xD1AE, 0xB786, 0xD1AF, 0xB787, 0xD1B0, 0xC5E8, 0xD1B1, 0xC5E9, 0xD1B2, 0xB788, 0xD1B3, 0xC5EA, + 0xD1B4, 0xB789, 0xD1B5, 0xC5EB, 0xD1B6, 0xB78A, 0xD1B7, 0xB78B, 0xD1B8, 0xB78C, 0xD1B9, 0xB78D, 0xD1BA, 0xC5EC, 0xD1BB, 0xB78E, + 0xD1BC, 0xC5ED, 0xD1BD, 0xB78F, 0xD1BE, 0xB790, 0xD1BF, 0xB791, 0xD1C0, 0xC5EE, 0xD1C1, 0xB792, 0xD1C2, 0xB793, 0xD1C3, 0xB794, + 0xD1C4, 0xB795, 0xD1C5, 0xB796, 0xD1C6, 0xB797, 0xD1C7, 0xB798, 0xD1C8, 0xB799, 0xD1C9, 0xB79A, 0xD1CA, 0xB79B, 0xD1CB, 0xB79C, + 0xD1CC, 0xB79D, 0xD1CD, 0xB79E, 0xD1CE, 0xB79F, 0xD1CF, 0xB7A0, 0xD1D0, 0xB841, 0xD1D1, 0xB842, 0xD1D2, 0xB843, 0xD1D3, 0xB844, + 0xD1D4, 0xB845, 0xD1D5, 0xB846, 0xD1D6, 0xB847, 0xD1D7, 0xB848, 0xD1D8, 0xC5EF, 0xD1D9, 0xB849, 0xD1DA, 0xB84A, 0xD1DB, 0xB84B, + 0xD1DC, 0xB84C, 0xD1DD, 0xB84D, 0xD1DE, 0xB84E, 0xD1DF, 0xB84F, 0xD1E0, 0xB850, 0xD1E1, 0xB851, 0xD1E2, 0xB852, 0xD1E3, 0xB853, + 0xD1E4, 0xB854, 0xD1E5, 0xB855, 0xD1E6, 0xB856, 0xD1E7, 0xB857, 0xD1E8, 0xB858, 0xD1E9, 0xB859, 0xD1EA, 0xB85A, 0xD1EB, 0xB861, + 0xD1EC, 0xB862, 0xD1ED, 0xB863, 0xD1EE, 0xB864, 0xD1EF, 0xB865, 0xD1F0, 0xB866, 0xD1F1, 0xB867, 0xD1F2, 0xB868, 0xD1F3, 0xB869, + 0xD1F4, 0xC5F0, 0xD1F5, 0xB86A, 0xD1F6, 0xB86B, 0xD1F7, 0xB86C, 0xD1F8, 0xC5F1, 0xD1F9, 0xB86D, 0xD1FA, 0xB86E, 0xD1FB, 0xB86F, + 0xD1FC, 0xB870, 0xD1FD, 0xB871, 0xD1FE, 0xB872, 0xD1FF, 0xB873, 0xD200, 0xB874, 0xD201, 0xB875, 0xD202, 0xB876, 0xD203, 0xB877, + 0xD204, 0xB878, 0xD205, 0xB879, 0xD206, 0xB87A, 0xD207, 0xC5F2, 0xD208, 0xB881, 0xD209, 0xC5F3, 0xD20A, 0xB882, 0xD20B, 0xB883, + 0xD20C, 0xB884, 0xD20D, 0xB885, 0xD20E, 0xB886, 0xD20F, 0xB887, 0xD210, 0xC5F4, 0xD211, 0xB888, 0xD212, 0xB889, 0xD213, 0xB88A, + 0xD214, 0xB88B, 0xD215, 0xB88C, 0xD216, 0xB88D, 0xD217, 0xB88E, 0xD218, 0xB88F, 0xD219, 0xB890, 0xD21A, 0xB891, 0xD21B, 0xB892, + 0xD21C, 0xB893, 0xD21D, 0xB894, 0xD21E, 0xB895, 0xD21F, 0xB896, 0xD220, 0xB897, 0xD221, 0xB898, 0xD222, 0xB899, 0xD223, 0xB89A, + 0xD224, 0xB89B, 0xD225, 0xB89C, 0xD226, 0xB89D, 0xD227, 0xB89E, 0xD228, 0xB89F, 0xD229, 0xB8A0, 0xD22A, 0xB941, 0xD22B, 0xB942, + 0xD22C, 0xC5F5, 0xD22D, 0xC5F6, 0xD22E, 0xB943, 0xD22F, 0xB944, 0xD230, 0xC5F7, 0xD231, 0xB945, 0xD232, 0xB946, 0xD233, 0xB947, + 0xD234, 0xC5F8, 0xD235, 0xB948, 0xD236, 0xB949, 0xD237, 0xB94A, 0xD238, 0xB94B, 0xD239, 0xB94C, 0xD23A, 0xB94D, 0xD23B, 0xB94E, + 0xD23C, 0xC5F9, 0xD23D, 0xC5FA, 0xD23E, 0xB94F, 0xD23F, 0xC5FB, 0xD240, 0xB950, 0xD241, 0xC5FC, 0xD242, 0xB951, 0xD243, 0xB952, + 0xD244, 0xB953, 0xD245, 0xB954, 0xD246, 0xB955, 0xD247, 0xB956, 0xD248, 0xC5FD, 0xD249, 0xB957, 0xD24A, 0xB958, 0xD24B, 0xB959, + 0xD24C, 0xB95A, 0xD24D, 0xB961, 0xD24E, 0xB962, 0xD24F, 0xB963, 0xD250, 0xB964, 0xD251, 0xB965, 0xD252, 0xB966, 0xD253, 0xB967, + 0xD254, 0xB968, 0xD255, 0xB969, 0xD256, 0xB96A, 0xD257, 0xB96B, 0xD258, 0xB96C, 0xD259, 0xB96D, 0xD25A, 0xB96E, 0xD25B, 0xB96F, + 0xD25C, 0xC5FE, 0xD25D, 0xB970, 0xD25E, 0xB971, 0xD25F, 0xB972, 0xD260, 0xB973, 0xD261, 0xB974, 0xD262, 0xB975, 0xD263, 0xB976, + 0xD264, 0xC6A1, 0xD265, 0xB977, 0xD266, 0xB978, 0xD267, 0xB979, 0xD268, 0xB97A, 0xD269, 0xB981, 0xD26A, 0xB982, 0xD26B, 0xB983, + 0xD26C, 0xB984, 0xD26D, 0xB985, 0xD26E, 0xB986, 0xD26F, 0xB987, 0xD270, 0xB988, 0xD271, 0xB989, 0xD272, 0xB98A, 0xD273, 0xB98B, + 0xD274, 0xB98C, 0xD275, 0xB98D, 0xD276, 0xB98E, 0xD277, 0xB98F, 0xD278, 0xB990, 0xD279, 0xB991, 0xD27A, 0xB992, 0xD27B, 0xB993, + 0xD27C, 0xB994, 0xD27D, 0xB995, 0xD27E, 0xB996, 0xD27F, 0xB997, 0xD280, 0xC6A2, 0xD281, 0xC6A3, 0xD282, 0xB998, 0xD283, 0xB999, + 0xD284, 0xC6A4, 0xD285, 0xB99A, 0xD286, 0xB99B, 0xD287, 0xB99C, 0xD288, 0xC6A5, 0xD289, 0xB99D, 0xD28A, 0xB99E, 0xD28B, 0xB99F, + 0xD28C, 0xB9A0, 0xD28D, 0xBA41, 0xD28E, 0xBA42, 0xD28F, 0xBA43, 0xD290, 0xC6A6, 0xD291, 0xC6A7, 0xD292, 0xBA44, 0xD293, 0xBA45, + 0xD294, 0xBA46, 0xD295, 0xC6A8, 0xD296, 0xBA47, 0xD297, 0xBA48, 0xD298, 0xBA49, 0xD299, 0xBA4A, 0xD29A, 0xBA4B, 0xD29B, 0xBA4C, + 0xD29C, 0xC6A9, 0xD29D, 0xBA4D, 0xD29E, 0xBA4E, 0xD29F, 0xBA4F, 0xD2A0, 0xC6AA, 0xD2A1, 0xBA50, 0xD2A2, 0xBA51, 0xD2A3, 0xBA52, + 0xD2A4, 0xC6AB, 0xD2A5, 0xBA53, 0xD2A6, 0xBA54, 0xD2A7, 0xBA55, 0xD2A8, 0xBA56, 0xD2A9, 0xBA57, 0xD2AA, 0xBA58, 0xD2AB, 0xBA59, + 0xD2AC, 0xC6AC, 0xD2AD, 0xBA5A, 0xD2AE, 0xBA61, 0xD2AF, 0xBA62, 0xD2B0, 0xBA63, 0xD2B1, 0xC6AD, 0xD2B2, 0xBA64, 0xD2B3, 0xBA65, + 0xD2B4, 0xBA66, 0xD2B5, 0xBA67, 0xD2B6, 0xBA68, 0xD2B7, 0xBA69, 0xD2B8, 0xC6AE, 0xD2B9, 0xC6AF, 0xD2BA, 0xBA6A, 0xD2BB, 0xBA6B, + 0xD2BC, 0xC6B0, 0xD2BD, 0xBA6C, 0xD2BE, 0xBA6D, 0xD2BF, 0xC6B1, 0xD2C0, 0xC6B2, 0xD2C1, 0xBA6E, 0xD2C2, 0xC6B3, 0xD2C3, 0xBA6F, + 0xD2C4, 0xBA70, 0xD2C5, 0xBA71, 0xD2C6, 0xBA72, 0xD2C7, 0xBA73, 0xD2C8, 0xC6B4, 0xD2C9, 0xC6B5, 0xD2CA, 0xBA74, 0xD2CB, 0xC6B6, + 0xD2CC, 0xBA75, 0xD2CD, 0xBA76, 0xD2CE, 0xBA77, 0xD2CF, 0xBA78, 0xD2D0, 0xBA79, 0xD2D1, 0xBA7A, 0xD2D2, 0xBA81, 0xD2D3, 0xBA82, + 0xD2D4, 0xC6B7, 0xD2D5, 0xBA83, 0xD2D6, 0xBA84, 0xD2D7, 0xBA85, 0xD2D8, 0xC6B8, 0xD2D9, 0xBA86, 0xD2DA, 0xBA87, 0xD2DB, 0xBA88, + 0xD2DC, 0xC6B9, 0xD2DD, 0xBA89, 0xD2DE, 0xBA8A, 0xD2DF, 0xBA8B, 0xD2E0, 0xBA8C, 0xD2E1, 0xBA8D, 0xD2E2, 0xBA8E, 0xD2E3, 0xBA8F, + 0xD2E4, 0xC6BA, 0xD2E5, 0xC6BB, 0xD2E6, 0xBA90, 0xD2E7, 0xBA91, 0xD2E8, 0xBA92, 0xD2E9, 0xBA93, 0xD2EA, 0xBA94, 0xD2EB, 0xBA95, + 0xD2EC, 0xBA96, 0xD2ED, 0xBA97, 0xD2EE, 0xBA98, 0xD2EF, 0xBA99, 0xD2F0, 0xC6BC, 0xD2F1, 0xC6BD, 0xD2F2, 0xBA9A, 0xD2F3, 0xBA9B, + 0xD2F4, 0xC6BE, 0xD2F5, 0xBA9C, 0xD2F6, 0xBA9D, 0xD2F7, 0xBA9E, 0xD2F8, 0xC6BF, 0xD2F9, 0xBA9F, 0xD2FA, 0xBAA0, 0xD2FB, 0xBB41, + 0xD2FC, 0xBB42, 0xD2FD, 0xBB43, 0xD2FE, 0xBB44, 0xD2FF, 0xBB45, 0xD300, 0xC6C0, 0xD301, 0xC6C1, 0xD302, 0xBB46, 0xD303, 0xC6C2, + 0xD304, 0xBB47, 0xD305, 0xC6C3, 0xD306, 0xBB48, 0xD307, 0xBB49, 0xD308, 0xBB4A, 0xD309, 0xBB4B, 0xD30A, 0xBB4C, 0xD30B, 0xBB4D, + 0xD30C, 0xC6C4, 0xD30D, 0xC6C5, 0xD30E, 0xC6C6, 0xD30F, 0xBB4E, 0xD310, 0xC6C7, 0xD311, 0xBB4F, 0xD312, 0xBB50, 0xD313, 0xBB51, + 0xD314, 0xC6C8, 0xD315, 0xBB52, 0xD316, 0xC6C9, 0xD317, 0xBB53, 0xD318, 0xBB54, 0xD319, 0xBB55, 0xD31A, 0xBB56, 0xD31B, 0xBB57, + 0xD31C, 0xC6CA, 0xD31D, 0xC6CB, 0xD31E, 0xBB58, 0xD31F, 0xC6CC, 0xD320, 0xC6CD, 0xD321, 0xC6CE, 0xD322, 0xBB59, 0xD323, 0xBB5A, + 0xD324, 0xBB61, 0xD325, 0xC6CF, 0xD326, 0xBB62, 0xD327, 0xBB63, 0xD328, 0xC6D0, 0xD329, 0xC6D1, 0xD32A, 0xBB64, 0xD32B, 0xBB65, + 0xD32C, 0xC6D2, 0xD32D, 0xBB66, 0xD32E, 0xBB67, 0xD32F, 0xBB68, 0xD330, 0xC6D3, 0xD331, 0xBB69, 0xD332, 0xBB6A, 0xD333, 0xBB6B, + 0xD334, 0xBB6C, 0xD335, 0xBB6D, 0xD336, 0xBB6E, 0xD337, 0xBB6F, 0xD338, 0xC6D4, 0xD339, 0xC6D5, 0xD33A, 0xBB70, 0xD33B, 0xC6D6, + 0xD33C, 0xC6D7, 0xD33D, 0xC6D8, 0xD33E, 0xBB71, 0xD33F, 0xBB72, 0xD340, 0xBB73, 0xD341, 0xBB74, 0xD342, 0xBB75, 0xD343, 0xBB76, + 0xD344, 0xC6D9, 0xD345, 0xC6DA, 0xD346, 0xBB77, 0xD347, 0xBB78, 0xD348, 0xBB79, 0xD349, 0xBB7A, 0xD34A, 0xBB81, 0xD34B, 0xBB82, + 0xD34C, 0xBB83, 0xD34D, 0xBB84, 0xD34E, 0xBB85, 0xD34F, 0xBB86, 0xD350, 0xBB87, 0xD351, 0xBB88, 0xD352, 0xBB89, 0xD353, 0xBB8A, + 0xD354, 0xBB8B, 0xD355, 0xBB8C, 0xD356, 0xBB8D, 0xD357, 0xBB8E, 0xD358, 0xBB8F, 0xD359, 0xBB90, 0xD35A, 0xBB91, 0xD35B, 0xBB92, + 0xD35C, 0xBB93, 0xD35D, 0xBB94, 0xD35E, 0xBB95, 0xD35F, 0xBB96, 0xD360, 0xBB97, 0xD361, 0xBB98, 0xD362, 0xBB99, 0xD363, 0xBB9A, + 0xD364, 0xBB9B, 0xD365, 0xBB9C, 0xD366, 0xBB9D, 0xD367, 0xBB9E, 0xD368, 0xBB9F, 0xD369, 0xBBA0, 0xD36A, 0xBC41, 0xD36B, 0xBC42, + 0xD36C, 0xBC43, 0xD36D, 0xBC44, 0xD36E, 0xBC45, 0xD36F, 0xBC46, 0xD370, 0xBC47, 0xD371, 0xBC48, 0xD372, 0xBC49, 0xD373, 0xBC4A, + 0xD374, 0xBC4B, 0xD375, 0xBC4C, 0xD376, 0xBC4D, 0xD377, 0xBC4E, 0xD378, 0xBC4F, 0xD379, 0xBC50, 0xD37A, 0xBC51, 0xD37B, 0xBC52, + 0xD37C, 0xC6DB, 0xD37D, 0xC6DC, 0xD37E, 0xBC53, 0xD37F, 0xBC54, 0xD380, 0xC6DD, 0xD381, 0xBC55, 0xD382, 0xBC56, 0xD383, 0xBC57, + 0xD384, 0xC6DE, 0xD385, 0xBC58, 0xD386, 0xBC59, 0xD387, 0xBC5A, 0xD388, 0xBC61, 0xD389, 0xBC62, 0xD38A, 0xBC63, 0xD38B, 0xBC64, + 0xD38C, 0xC6DF, 0xD38D, 0xC6E0, 0xD38E, 0xBC65, 0xD38F, 0xC6E1, 0xD390, 0xC6E2, 0xD391, 0xC6E3, 0xD392, 0xBC66, 0xD393, 0xBC67, + 0xD394, 0xBC68, 0xD395, 0xBC69, 0xD396, 0xBC6A, 0xD397, 0xBC6B, 0xD398, 0xC6E4, 0xD399, 0xC6E5, 0xD39A, 0xBC6C, 0xD39B, 0xBC6D, + 0xD39C, 0xC6E6, 0xD39D, 0xBC6E, 0xD39E, 0xBC6F, 0xD39F, 0xBC70, 0xD3A0, 0xC6E7, 0xD3A1, 0xBC71, 0xD3A2, 0xBC72, 0xD3A3, 0xBC73, + 0xD3A4, 0xBC74, 0xD3A5, 0xBC75, 0xD3A6, 0xBC76, 0xD3A7, 0xBC77, 0xD3A8, 0xC6E8, 0xD3A9, 0xC6E9, 0xD3AA, 0xBC78, 0xD3AB, 0xC6EA, + 0xD3AC, 0xBC79, 0xD3AD, 0xC6EB, 0xD3AE, 0xBC7A, 0xD3AF, 0xBC81, 0xD3B0, 0xBC82, 0xD3B1, 0xBC83, 0xD3B2, 0xBC84, 0xD3B3, 0xBC85, + 0xD3B4, 0xC6EC, 0xD3B5, 0xBC86, 0xD3B6, 0xBC87, 0xD3B7, 0xBC88, 0xD3B8, 0xC6ED, 0xD3B9, 0xBC89, 0xD3BA, 0xBC8A, 0xD3BB, 0xBC8B, + 0xD3BC, 0xC6EE, 0xD3BD, 0xBC8C, 0xD3BE, 0xBC8D, 0xD3BF, 0xBC8E, 0xD3C0, 0xBC8F, 0xD3C1, 0xBC90, 0xD3C2, 0xBC91, 0xD3C3, 0xBC92, + 0xD3C4, 0xC6EF, 0xD3C5, 0xC6F0, 0xD3C6, 0xBC93, 0xD3C7, 0xBC94, 0xD3C8, 0xC6F1, 0xD3C9, 0xC6F2, 0xD3CA, 0xBC95, 0xD3CB, 0xBC96, + 0xD3CC, 0xBC97, 0xD3CD, 0xBC98, 0xD3CE, 0xBC99, 0xD3CF, 0xBC9A, 0xD3D0, 0xC6F3, 0xD3D1, 0xBC9B, 0xD3D2, 0xBC9C, 0xD3D3, 0xBC9D, + 0xD3D4, 0xBC9E, 0xD3D5, 0xBC9F, 0xD3D6, 0xBCA0, 0xD3D7, 0xBD41, 0xD3D8, 0xC6F4, 0xD3D9, 0xBD42, 0xD3DA, 0xBD43, 0xD3DB, 0xBD44, + 0xD3DC, 0xBD45, 0xD3DD, 0xBD46, 0xD3DE, 0xBD47, 0xD3DF, 0xBD48, 0xD3E0, 0xBD49, 0xD3E1, 0xC6F5, 0xD3E2, 0xBD4A, 0xD3E3, 0xC6F6, + 0xD3E4, 0xBD4B, 0xD3E5, 0xBD4C, 0xD3E6, 0xBD4D, 0xD3E7, 0xBD4E, 0xD3E8, 0xBD4F, 0xD3E9, 0xBD50, 0xD3EA, 0xBD51, 0xD3EB, 0xBD52, + 0xD3EC, 0xC6F7, 0xD3ED, 0xC6F8, 0xD3EE, 0xBD53, 0xD3EF, 0xBD54, 0xD3F0, 0xC6F9, 0xD3F1, 0xBD55, 0xD3F2, 0xBD56, 0xD3F3, 0xBD57, + 0xD3F4, 0xC6FA, 0xD3F5, 0xBD58, 0xD3F6, 0xBD59, 0xD3F7, 0xBD5A, 0xD3F8, 0xBD61, 0xD3F9, 0xBD62, 0xD3FA, 0xBD63, 0xD3FB, 0xBD64, + 0xD3FC, 0xC6FB, 0xD3FD, 0xC6FC, 0xD3FE, 0xBD65, 0xD3FF, 0xC6FD, 0xD400, 0xBD66, 0xD401, 0xC6FE, 0xD402, 0xBD67, 0xD403, 0xBD68, + 0xD404, 0xBD69, 0xD405, 0xBD6A, 0xD406, 0xBD6B, 0xD407, 0xBD6C, 0xD408, 0xC7A1, 0xD409, 0xBD6D, 0xD40A, 0xBD6E, 0xD40B, 0xBD6F, + 0xD40C, 0xBD70, 0xD40D, 0xBD71, 0xD40E, 0xBD72, 0xD40F, 0xBD73, 0xD410, 0xBD74, 0xD411, 0xBD75, 0xD412, 0xBD76, 0xD413, 0xBD77, + 0xD414, 0xBD78, 0xD415, 0xBD79, 0xD416, 0xBD7A, 0xD417, 0xBD81, 0xD418, 0xBD82, 0xD419, 0xBD83, 0xD41A, 0xBD84, 0xD41B, 0xBD85, + 0xD41C, 0xBD86, 0xD41D, 0xC7A2, 0xD41E, 0xBD87, 0xD41F, 0xBD88, 0xD420, 0xBD89, 0xD421, 0xBD8A, 0xD422, 0xBD8B, 0xD423, 0xBD8C, + 0xD424, 0xBD8D, 0xD425, 0xBD8E, 0xD426, 0xBD8F, 0xD427, 0xBD90, 0xD428, 0xBD91, 0xD429, 0xBD92, 0xD42A, 0xBD93, 0xD42B, 0xBD94, + 0xD42C, 0xBD95, 0xD42D, 0xBD96, 0xD42E, 0xBD97, 0xD42F, 0xBD98, 0xD430, 0xBD99, 0xD431, 0xBD9A, 0xD432, 0xBD9B, 0xD433, 0xBD9C, + 0xD434, 0xBD9D, 0xD435, 0xBD9E, 0xD436, 0xBD9F, 0xD437, 0xBDA0, 0xD438, 0xBE41, 0xD439, 0xBE42, 0xD43A, 0xBE43, 0xD43B, 0xBE44, + 0xD43C, 0xBE45, 0xD43D, 0xBE46, 0xD43E, 0xBE47, 0xD43F, 0xBE48, 0xD440, 0xC7A3, 0xD441, 0xBE49, 0xD442, 0xBE4A, 0xD443, 0xBE4B, + 0xD444, 0xC7A4, 0xD445, 0xBE4C, 0xD446, 0xBE4D, 0xD447, 0xBE4E, 0xD448, 0xBE4F, 0xD449, 0xBE50, 0xD44A, 0xBE51, 0xD44B, 0xBE52, + 0xD44C, 0xBE53, 0xD44D, 0xBE54, 0xD44E, 0xBE55, 0xD44F, 0xBE56, 0xD450, 0xBE57, 0xD451, 0xBE58, 0xD452, 0xBE59, 0xD453, 0xBE5A, + 0xD454, 0xBE61, 0xD455, 0xBE62, 0xD456, 0xBE63, 0xD457, 0xBE64, 0xD458, 0xBE65, 0xD459, 0xBE66, 0xD45A, 0xBE67, 0xD45B, 0xBE68, + 0xD45C, 0xC7A5, 0xD45D, 0xBE69, 0xD45E, 0xBE6A, 0xD45F, 0xBE6B, 0xD460, 0xC7A6, 0xD461, 0xBE6C, 0xD462, 0xBE6D, 0xD463, 0xBE6E, + 0xD464, 0xC7A7, 0xD465, 0xBE6F, 0xD466, 0xBE70, 0xD467, 0xBE71, 0xD468, 0xBE72, 0xD469, 0xBE73, 0xD46A, 0xBE74, 0xD46B, 0xBE75, + 0xD46C, 0xBE76, 0xD46D, 0xC7A8, 0xD46E, 0xBE77, 0xD46F, 0xC7A9, 0xD470, 0xBE78, 0xD471, 0xBE79, 0xD472, 0xBE7A, 0xD473, 0xBE81, + 0xD474, 0xBE82, 0xD475, 0xBE83, 0xD476, 0xBE84, 0xD477, 0xBE85, 0xD478, 0xC7AA, 0xD479, 0xC7AB, 0xD47A, 0xBE86, 0xD47B, 0xBE87, + 0xD47C, 0xC7AC, 0xD47D, 0xBE88, 0xD47E, 0xBE89, 0xD47F, 0xC7AD, 0xD480, 0xC7AE, 0xD481, 0xBE8A, 0xD482, 0xC7AF, 0xD483, 0xBE8B, + 0xD484, 0xBE8C, 0xD485, 0xBE8D, 0xD486, 0xBE8E, 0xD487, 0xBE8F, 0xD488, 0xC7B0, 0xD489, 0xC7B1, 0xD48A, 0xBE90, 0xD48B, 0xC7B2, + 0xD48C, 0xBE91, 0xD48D, 0xC7B3, 0xD48E, 0xBE92, 0xD48F, 0xBE93, 0xD490, 0xBE94, 0xD491, 0xBE95, 0xD492, 0xBE96, 0xD493, 0xBE97, + 0xD494, 0xC7B4, 0xD495, 0xBE98, 0xD496, 0xBE99, 0xD497, 0xBE9A, 0xD498, 0xBE9B, 0xD499, 0xBE9C, 0xD49A, 0xBE9D, 0xD49B, 0xBE9E, + 0xD49C, 0xBE9F, 0xD49D, 0xBEA0, 0xD49E, 0xBF41, 0xD49F, 0xBF42, 0xD4A0, 0xBF43, 0xD4A1, 0xBF44, 0xD4A2, 0xBF45, 0xD4A3, 0xBF46, + 0xD4A4, 0xBF47, 0xD4A5, 0xBF48, 0xD4A6, 0xBF49, 0xD4A7, 0xBF4A, 0xD4A8, 0xBF4B, 0xD4A9, 0xC7B5, 0xD4AA, 0xBF4C, 0xD4AB, 0xBF4D, + 0xD4AC, 0xBF4E, 0xD4AD, 0xBF4F, 0xD4AE, 0xBF50, 0xD4AF, 0xBF51, 0xD4B0, 0xBF52, 0xD4B1, 0xBF53, 0xD4B2, 0xBF54, 0xD4B3, 0xBF55, + 0xD4B4, 0xBF56, 0xD4B5, 0xBF57, 0xD4B6, 0xBF58, 0xD4B7, 0xBF59, 0xD4B8, 0xBF5A, 0xD4B9, 0xBF61, 0xD4BA, 0xBF62, 0xD4BB, 0xBF63, + 0xD4BC, 0xBF64, 0xD4BD, 0xBF65, 0xD4BE, 0xBF66, 0xD4BF, 0xBF67, 0xD4C0, 0xBF68, 0xD4C1, 0xBF69, 0xD4C2, 0xBF6A, 0xD4C3, 0xBF6B, + 0xD4C4, 0xBF6C, 0xD4C5, 0xBF6D, 0xD4C6, 0xBF6E, 0xD4C7, 0xBF6F, 0xD4C8, 0xBF70, 0xD4C9, 0xBF71, 0xD4CA, 0xBF72, 0xD4CB, 0xBF73, + 0xD4CC, 0xC7B6, 0xD4CD, 0xBF74, 0xD4CE, 0xBF75, 0xD4CF, 0xBF76, 0xD4D0, 0xC7B7, 0xD4D1, 0xBF77, 0xD4D2, 0xBF78, 0xD4D3, 0xBF79, + 0xD4D4, 0xC7B8, 0xD4D5, 0xBF7A, 0xD4D6, 0xBF81, 0xD4D7, 0xBF82, 0xD4D8, 0xBF83, 0xD4D9, 0xBF84, 0xD4DA, 0xBF85, 0xD4DB, 0xBF86, + 0xD4DC, 0xC7B9, 0xD4DD, 0xBF87, 0xD4DE, 0xBF88, 0xD4DF, 0xC7BA, 0xD4E0, 0xBF89, 0xD4E1, 0xBF8A, 0xD4E2, 0xBF8B, 0xD4E3, 0xBF8C, + 0xD4E4, 0xBF8D, 0xD4E5, 0xBF8E, 0xD4E6, 0xBF8F, 0xD4E7, 0xBF90, 0xD4E8, 0xC7BB, 0xD4E9, 0xBF91, 0xD4EA, 0xBF92, 0xD4EB, 0xBF93, + 0xD4EC, 0xC7BC, 0xD4ED, 0xBF94, 0xD4EE, 0xBF95, 0xD4EF, 0xBF96, 0xD4F0, 0xC7BD, 0xD4F1, 0xBF97, 0xD4F2, 0xBF98, 0xD4F3, 0xBF99, + 0xD4F4, 0xBF9A, 0xD4F5, 0xBF9B, 0xD4F6, 0xBF9C, 0xD4F7, 0xBF9D, 0xD4F8, 0xC7BE, 0xD4F9, 0xBF9E, 0xD4FA, 0xBF9F, 0xD4FB, 0xC7BF, + 0xD4FC, 0xBFA0, 0xD4FD, 0xC7C0, 0xD4FE, 0xC041, 0xD4FF, 0xC042, 0xD500, 0xC043, 0xD501, 0xC044, 0xD502, 0xC045, 0xD503, 0xC046, + 0xD504, 0xC7C1, 0xD505, 0xC047, 0xD506, 0xC048, 0xD507, 0xC049, 0xD508, 0xC7C2, 0xD509, 0xC04A, 0xD50A, 0xC04B, 0xD50B, 0xC04C, + 0xD50C, 0xC7C3, 0xD50D, 0xC04D, 0xD50E, 0xC04E, 0xD50F, 0xC04F, 0xD510, 0xC050, 0xD511, 0xC051, 0xD512, 0xC052, 0xD513, 0xC053, + 0xD514, 0xC7C4, 0xD515, 0xC7C5, 0xD516, 0xC054, 0xD517, 0xC7C6, 0xD518, 0xC055, 0xD519, 0xC056, 0xD51A, 0xC057, 0xD51B, 0xC058, + 0xD51C, 0xC059, 0xD51D, 0xC05A, 0xD51E, 0xC061, 0xD51F, 0xC062, 0xD520, 0xC063, 0xD521, 0xC064, 0xD522, 0xC065, 0xD523, 0xC066, + 0xD524, 0xC067, 0xD525, 0xC068, 0xD526, 0xC069, 0xD527, 0xC06A, 0xD528, 0xC06B, 0xD529, 0xC06C, 0xD52A, 0xC06D, 0xD52B, 0xC06E, + 0xD52C, 0xC06F, 0xD52D, 0xC070, 0xD52E, 0xC071, 0xD52F, 0xC072, 0xD530, 0xC073, 0xD531, 0xC074, 0xD532, 0xC075, 0xD533, 0xC076, + 0xD534, 0xC077, 0xD535, 0xC078, 0xD536, 0xC079, 0xD537, 0xC07A, 0xD538, 0xC081, 0xD539, 0xC082, 0xD53A, 0xC083, 0xD53B, 0xC084, + 0xD53C, 0xC7C7, 0xD53D, 0xC7C8, 0xD53E, 0xC085, 0xD53F, 0xC086, 0xD540, 0xC7C9, 0xD541, 0xC087, 0xD542, 0xC088, 0xD543, 0xC089, + 0xD544, 0xC7CA, 0xD545, 0xC08A, 0xD546, 0xC08B, 0xD547, 0xC08C, 0xD548, 0xC08D, 0xD549, 0xC08E, 0xD54A, 0xC08F, 0xD54B, 0xC090, + 0xD54C, 0xC7CB, 0xD54D, 0xC7CC, 0xD54E, 0xC091, 0xD54F, 0xC7CD, 0xD550, 0xC092, 0xD551, 0xC7CE, 0xD552, 0xC093, 0xD553, 0xC094, + 0xD554, 0xC095, 0xD555, 0xC096, 0xD556, 0xC097, 0xD557, 0xC098, 0xD558, 0xC7CF, 0xD559, 0xC7D0, 0xD55A, 0xC099, 0xD55B, 0xC09A, + 0xD55C, 0xC7D1, 0xD55D, 0xC09B, 0xD55E, 0xC09C, 0xD55F, 0xC09D, 0xD560, 0xC7D2, 0xD561, 0xC09E, 0xD562, 0xC09F, 0xD563, 0xC0A0, + 0xD564, 0xC141, 0xD565, 0xC7D3, 0xD566, 0xC142, 0xD567, 0xC143, 0xD568, 0xC7D4, 0xD569, 0xC7D5, 0xD56A, 0xC144, 0xD56B, 0xC7D6, + 0xD56C, 0xC145, 0xD56D, 0xC7D7, 0xD56E, 0xC146, 0xD56F, 0xC147, 0xD570, 0xC148, 0xD571, 0xC149, 0xD572, 0xC14A, 0xD573, 0xC14B, + 0xD574, 0xC7D8, 0xD575, 0xC7D9, 0xD576, 0xC14C, 0xD577, 0xC14D, 0xD578, 0xC7DA, 0xD579, 0xC14E, 0xD57A, 0xC14F, 0xD57B, 0xC150, + 0xD57C, 0xC7DB, 0xD57D, 0xC151, 0xD57E, 0xC152, 0xD57F, 0xC153, 0xD580, 0xC154, 0xD581, 0xC155, 0xD582, 0xC156, 0xD583, 0xC157, + 0xD584, 0xC7DC, 0xD585, 0xC7DD, 0xD586, 0xC158, 0xD587, 0xC7DE, 0xD588, 0xC7DF, 0xD589, 0xC7E0, 0xD58A, 0xC159, 0xD58B, 0xC15A, + 0xD58C, 0xC161, 0xD58D, 0xC162, 0xD58E, 0xC163, 0xD58F, 0xC164, 0xD590, 0xC7E1, 0xD591, 0xC165, 0xD592, 0xC166, 0xD593, 0xC167, + 0xD594, 0xC168, 0xD595, 0xC169, 0xD596, 0xC16A, 0xD597, 0xC16B, 0xD598, 0xC16C, 0xD599, 0xC16D, 0xD59A, 0xC16E, 0xD59B, 0xC16F, + 0xD59C, 0xC170, 0xD59D, 0xC171, 0xD59E, 0xC172, 0xD59F, 0xC173, 0xD5A0, 0xC174, 0xD5A1, 0xC175, 0xD5A2, 0xC176, 0xD5A3, 0xC177, + 0xD5A4, 0xC178, 0xD5A5, 0xC7E2, 0xD5A6, 0xC179, 0xD5A7, 0xC17A, 0xD5A8, 0xC181, 0xD5A9, 0xC182, 0xD5AA, 0xC183, 0xD5AB, 0xC184, + 0xD5AC, 0xC185, 0xD5AD, 0xC186, 0xD5AE, 0xC187, 0xD5AF, 0xC188, 0xD5B0, 0xC189, 0xD5B1, 0xC18A, 0xD5B2, 0xC18B, 0xD5B3, 0xC18C, + 0xD5B4, 0xC18D, 0xD5B5, 0xC18E, 0xD5B6, 0xC18F, 0xD5B7, 0xC190, 0xD5B8, 0xC191, 0xD5B9, 0xC192, 0xD5BA, 0xC193, 0xD5BB, 0xC194, + 0xD5BC, 0xC195, 0xD5BD, 0xC196, 0xD5BE, 0xC197, 0xD5BF, 0xC198, 0xD5C0, 0xC199, 0xD5C1, 0xC19A, 0xD5C2, 0xC19B, 0xD5C3, 0xC19C, + 0xD5C4, 0xC19D, 0xD5C5, 0xC19E, 0xD5C6, 0xC19F, 0xD5C7, 0xC1A0, 0xD5C8, 0xC7E3, 0xD5C9, 0xC7E4, 0xD5CA, 0xC241, 0xD5CB, 0xC242, + 0xD5CC, 0xC7E5, 0xD5CD, 0xC243, 0xD5CE, 0xC244, 0xD5CF, 0xC245, 0xD5D0, 0xC7E6, 0xD5D1, 0xC246, 0xD5D2, 0xC7E7, 0xD5D3, 0xC247, + 0xD5D4, 0xC248, 0xD5D5, 0xC249, 0xD5D6, 0xC24A, 0xD5D7, 0xC24B, 0xD5D8, 0xC7E8, 0xD5D9, 0xC7E9, 0xD5DA, 0xC24C, 0xD5DB, 0xC7EA, + 0xD5DC, 0xC24D, 0xD5DD, 0xC7EB, 0xD5DE, 0xC24E, 0xD5DF, 0xC24F, 0xD5E0, 0xC250, 0xD5E1, 0xC251, 0xD5E2, 0xC252, 0xD5E3, 0xC253, + 0xD5E4, 0xC7EC, 0xD5E5, 0xC7ED, 0xD5E6, 0xC254, 0xD5E7, 0xC255, 0xD5E8, 0xC7EE, 0xD5E9, 0xC256, 0xD5EA, 0xC257, 0xD5EB, 0xC258, + 0xD5EC, 0xC7EF, 0xD5ED, 0xC259, 0xD5EE, 0xC25A, 0xD5EF, 0xC261, 0xD5F0, 0xC262, 0xD5F1, 0xC263, 0xD5F2, 0xC264, 0xD5F3, 0xC265, + 0xD5F4, 0xC7F0, 0xD5F5, 0xC7F1, 0xD5F6, 0xC266, 0xD5F7, 0xC7F2, 0xD5F8, 0xC267, 0xD5F9, 0xC7F3, 0xD5FA, 0xC268, 0xD5FB, 0xC269, + 0xD5FC, 0xC26A, 0xD5FD, 0xC26B, 0xD5FE, 0xC26C, 0xD5FF, 0xC26D, 0xD600, 0xC7F4, 0xD601, 0xC7F5, 0xD602, 0xC26E, 0xD603, 0xC26F, + 0xD604, 0xC7F6, 0xD605, 0xC270, 0xD606, 0xC271, 0xD607, 0xC272, 0xD608, 0xC7F7, 0xD609, 0xC273, 0xD60A, 0xC274, 0xD60B, 0xC275, + 0xD60C, 0xC276, 0xD60D, 0xC277, 0xD60E, 0xC278, 0xD60F, 0xC279, 0xD610, 0xC7F8, 0xD611, 0xC7F9, 0xD612, 0xC27A, 0xD613, 0xC7FA, + 0xD614, 0xC7FB, 0xD615, 0xC7FC, 0xD616, 0xC281, 0xD617, 0xC282, 0xD618, 0xC283, 0xD619, 0xC284, 0xD61A, 0xC285, 0xD61B, 0xC286, + 0xD61C, 0xC7FD, 0xD61D, 0xC287, 0xD61E, 0xC288, 0xD61F, 0xC289, 0xD620, 0xC7FE, 0xD621, 0xC28A, 0xD622, 0xC28B, 0xD623, 0xC28C, + 0xD624, 0xC8A1, 0xD625, 0xC28D, 0xD626, 0xC28E, 0xD627, 0xC28F, 0xD628, 0xC290, 0xD629, 0xC291, 0xD62A, 0xC292, 0xD62B, 0xC293, + 0xD62C, 0xC294, 0xD62D, 0xC8A2, 0xD62E, 0xC295, 0xD62F, 0xC296, 0xD630, 0xC297, 0xD631, 0xC298, 0xD632, 0xC299, 0xD633, 0xC29A, + 0xD634, 0xC29B, 0xD635, 0xC29C, 0xD636, 0xC29D, 0xD637, 0xC29E, 0xD638, 0xC8A3, 0xD639, 0xC8A4, 0xD63A, 0xC29F, 0xD63B, 0xC2A0, + 0xD63C, 0xC8A5, 0xD63D, 0xC341, 0xD63E, 0xC342, 0xD63F, 0xC343, 0xD640, 0xC8A6, 0xD641, 0xC344, 0xD642, 0xC345, 0xD643, 0xC346, + 0xD644, 0xC347, 0xD645, 0xC8A7, 0xD646, 0xC348, 0xD647, 0xC349, 0xD648, 0xC8A8, 0xD649, 0xC8A9, 0xD64A, 0xC34A, 0xD64B, 0xC8AA, + 0xD64C, 0xC34B, 0xD64D, 0xC8AB, 0xD64E, 0xC34C, 0xD64F, 0xC34D, 0xD650, 0xC34E, 0xD651, 0xC8AC, 0xD652, 0xC34F, 0xD653, 0xC350, + 0xD654, 0xC8AD, 0xD655, 0xC8AE, 0xD656, 0xC351, 0xD657, 0xC352, 0xD658, 0xC8AF, 0xD659, 0xC353, 0xD65A, 0xC354, 0xD65B, 0xC355, + 0xD65C, 0xC8B0, 0xD65D, 0xC356, 0xD65E, 0xC357, 0xD65F, 0xC358, 0xD660, 0xC359, 0xD661, 0xC35A, 0xD662, 0xC361, 0xD663, 0xC362, + 0xD664, 0xC363, 0xD665, 0xC364, 0xD666, 0xC365, 0xD667, 0xC8B1, 0xD668, 0xC366, 0xD669, 0xC8B2, 0xD66A, 0xC367, 0xD66B, 0xC368, + 0xD66C, 0xC369, 0xD66D, 0xC36A, 0xD66E, 0xC36B, 0xD66F, 0xC36C, 0xD670, 0xC8B3, 0xD671, 0xC8B4, 0xD672, 0xC36D, 0xD673, 0xC36E, + 0xD674, 0xC8B5, 0xD675, 0xC36F, 0xD676, 0xC370, 0xD677, 0xC371, 0xD678, 0xC372, 0xD679, 0xC373, 0xD67A, 0xC374, 0xD67B, 0xC375, + 0xD67C, 0xC376, 0xD67D, 0xC377, 0xD67E, 0xC378, 0xD67F, 0xC379, 0xD680, 0xC37A, 0xD681, 0xC381, 0xD682, 0xC382, 0xD683, 0xC8B6, + 0xD684, 0xC383, 0xD685, 0xC8B7, 0xD686, 0xC384, 0xD687, 0xC385, 0xD688, 0xC386, 0xD689, 0xC387, 0xD68A, 0xC388, 0xD68B, 0xC389, + 0xD68C, 0xC8B8, 0xD68D, 0xC8B9, 0xD68E, 0xC38A, 0xD68F, 0xC38B, 0xD690, 0xC8BA, 0xD691, 0xC38C, 0xD692, 0xC38D, 0xD693, 0xC38E, + 0xD694, 0xC8BB, 0xD695, 0xC38F, 0xD696, 0xC390, 0xD697, 0xC391, 0xD698, 0xC392, 0xD699, 0xC393, 0xD69A, 0xC394, 0xD69B, 0xC395, + 0xD69C, 0xC396, 0xD69D, 0xC8BC, 0xD69E, 0xC397, 0xD69F, 0xC8BD, 0xD6A0, 0xC398, 0xD6A1, 0xC8BE, 0xD6A2, 0xC399, 0xD6A3, 0xC39A, + 0xD6A4, 0xC39B, 0xD6A5, 0xC39C, 0xD6A6, 0xC39D, 0xD6A7, 0xC39E, 0xD6A8, 0xC8BF, 0xD6A9, 0xC39F, 0xD6AA, 0xC3A0, 0xD6AB, 0xC441, + 0xD6AC, 0xC8C0, 0xD6AD, 0xC442, 0xD6AE, 0xC443, 0xD6AF, 0xC444, 0xD6B0, 0xC8C1, 0xD6B1, 0xC445, 0xD6B2, 0xC446, 0xD6B3, 0xC447, + 0xD6B4, 0xC448, 0xD6B5, 0xC449, 0xD6B6, 0xC44A, 0xD6B7, 0xC44B, 0xD6B8, 0xC44C, 0xD6B9, 0xC8C2, 0xD6BA, 0xC44D, 0xD6BB, 0xC8C3, + 0xD6BC, 0xC44E, 0xD6BD, 0xC44F, 0xD6BE, 0xC450, 0xD6BF, 0xC451, 0xD6C0, 0xC452, 0xD6C1, 0xC453, 0xD6C2, 0xC454, 0xD6C3, 0xC455, + 0xD6C4, 0xC8C4, 0xD6C5, 0xC8C5, 0xD6C6, 0xC456, 0xD6C7, 0xC457, 0xD6C8, 0xC8C6, 0xD6C9, 0xC458, 0xD6CA, 0xC459, 0xD6CB, 0xC45A, + 0xD6CC, 0xC8C7, 0xD6CD, 0xC461, 0xD6CE, 0xC462, 0xD6CF, 0xC463, 0xD6D0, 0xC464, 0xD6D1, 0xC8C8, 0xD6D2, 0xC465, 0xD6D3, 0xC466, + 0xD6D4, 0xC8C9, 0xD6D5, 0xC467, 0xD6D6, 0xC468, 0xD6D7, 0xC8CA, 0xD6D8, 0xC469, 0xD6D9, 0xC8CB, 0xD6DA, 0xC46A, 0xD6DB, 0xC46B, + 0xD6DC, 0xC46C, 0xD6DD, 0xC46D, 0xD6DE, 0xC46E, 0xD6DF, 0xC46F, 0xD6E0, 0xC8CC, 0xD6E1, 0xC470, 0xD6E2, 0xC471, 0xD6E3, 0xC472, + 0xD6E4, 0xC8CD, 0xD6E5, 0xC473, 0xD6E6, 0xC474, 0xD6E7, 0xC475, 0xD6E8, 0xC8CE, 0xD6E9, 0xC476, 0xD6EA, 0xC477, 0xD6EB, 0xC478, + 0xD6EC, 0xC479, 0xD6ED, 0xC47A, 0xD6EE, 0xC481, 0xD6EF, 0xC482, 0xD6F0, 0xC8CF, 0xD6F1, 0xC483, 0xD6F2, 0xC484, 0xD6F3, 0xC485, + 0xD6F4, 0xC486, 0xD6F5, 0xC8D0, 0xD6F6, 0xC487, 0xD6F7, 0xC488, 0xD6F8, 0xC489, 0xD6F9, 0xC48A, 0xD6FA, 0xC48B, 0xD6FB, 0xC48C, + 0xD6FC, 0xC8D1, 0xD6FD, 0xC8D2, 0xD6FE, 0xC48D, 0xD6FF, 0xC48E, 0xD700, 0xC8D3, 0xD701, 0xC48F, 0xD702, 0xC490, 0xD703, 0xC491, + 0xD704, 0xC8D4, 0xD705, 0xC492, 0xD706, 0xC493, 0xD707, 0xC494, 0xD708, 0xC495, 0xD709, 0xC496, 0xD70A, 0xC497, 0xD70B, 0xC498, + 0xD70C, 0xC499, 0xD70D, 0xC49A, 0xD70E, 0xC49B, 0xD70F, 0xC49C, 0xD710, 0xC49D, 0xD711, 0xC8D5, 0xD712, 0xC49E, 0xD713, 0xC49F, + 0xD714, 0xC4A0, 0xD715, 0xC541, 0xD716, 0xC542, 0xD717, 0xC543, 0xD718, 0xC8D6, 0xD719, 0xC8D7, 0xD71A, 0xC544, 0xD71B, 0xC545, + 0xD71C, 0xC8D8, 0xD71D, 0xC546, 0xD71E, 0xC547, 0xD71F, 0xC548, 0xD720, 0xC8D9, 0xD721, 0xC549, 0xD722, 0xC54A, 0xD723, 0xC54B, + 0xD724, 0xC54C, 0xD725, 0xC54D, 0xD726, 0xC54E, 0xD727, 0xC54F, 0xD728, 0xC8DA, 0xD729, 0xC8DB, 0xD72A, 0xC550, 0xD72B, 0xC8DC, + 0xD72C, 0xC551, 0xD72D, 0xC8DD, 0xD72E, 0xC552, 0xD72F, 0xC553, 0xD730, 0xC554, 0xD731, 0xC555, 0xD732, 0xC556, 0xD733, 0xC557, + 0xD734, 0xC8DE, 0xD735, 0xC8DF, 0xD736, 0xC558, 0xD737, 0xC559, 0xD738, 0xC8E0, 0xD739, 0xC55A, 0xD73A, 0xC561, 0xD73B, 0xC562, + 0xD73C, 0xC8E1, 0xD73D, 0xC563, 0xD73E, 0xC564, 0xD73F, 0xC565, 0xD740, 0xC566, 0xD741, 0xC567, 0xD742, 0xC568, 0xD743, 0xC569, + 0xD744, 0xC8E2, 0xD745, 0xC56A, 0xD746, 0xC56B, 0xD747, 0xC8E3, 0xD748, 0xC56C, 0xD749, 0xC8E4, 0xD74A, 0xC56D, 0xD74B, 0xC56E, + 0xD74C, 0xC56F, 0xD74D, 0xC570, 0xD74E, 0xC571, 0xD74F, 0xC572, 0xD750, 0xC8E5, 0xD751, 0xC8E6, 0xD752, 0xC573, 0xD753, 0xC574, + 0xD754, 0xC8E7, 0xD755, 0xC575, 0xD756, 0xC8E8, 0xD757, 0xC8E9, 0xD758, 0xC8EA, 0xD759, 0xC8EB, 0xD75A, 0xC576, 0xD75B, 0xC577, + 0xD75C, 0xC578, 0xD75D, 0xC579, 0xD75E, 0xC57A, 0xD75F, 0xC581, 0xD760, 0xC8EC, 0xD761, 0xC8ED, 0xD762, 0xC582, 0xD763, 0xC8EE, + 0xD764, 0xC583, 0xD765, 0xC8EF, 0xD766, 0xC584, 0xD767, 0xC585, 0xD768, 0xC586, 0xD769, 0xC8F0, 0xD76A, 0xC587, 0xD76B, 0xC588, + 0xD76C, 0xC8F1, 0xD76D, 0xC589, 0xD76E, 0xC58A, 0xD76F, 0xC58B, 0xD770, 0xC8F2, 0xD771, 0xC58C, 0xD772, 0xC58D, 0xD773, 0xC58E, + 0xD774, 0xC8F3, 0xD775, 0xC58F, 0xD776, 0xC590, 0xD777, 0xC591, 0xD778, 0xC592, 0xD779, 0xC593, 0xD77A, 0xC594, 0xD77B, 0xC595, + 0xD77C, 0xC8F4, 0xD77D, 0xC8F5, 0xD77E, 0xC596, 0xD77F, 0xC597, 0xD780, 0xC598, 0xD781, 0xC8F6, 0xD782, 0xC599, 0xD783, 0xC59A, + 0xD784, 0xC59B, 0xD785, 0xC59C, 0xD786, 0xC59D, 0xD787, 0xC59E, 0xD788, 0xC8F7, 0xD789, 0xC8F8, 0xD78A, 0xC59F, 0xD78B, 0xC5A0, + 0xD78C, 0xC8F9, 0xD78D, 0xC641, 0xD78E, 0xC642, 0xD78F, 0xC643, 0xD790, 0xC8FA, 0xD791, 0xC644, 0xD792, 0xC645, 0xD793, 0xC646, + 0xD794, 0xC647, 0xD795, 0xC648, 0xD796, 0xC649, 0xD797, 0xC64A, 0xD798, 0xC8FB, 0xD799, 0xC8FC, 0xD79A, 0xC64B, 0xD79B, 0xC8FD, + 0xD79C, 0xC64C, 0xD79D, 0xC8FE, 0xD79E, 0xC64D, 0xD79F, 0xC64E, 0xD7A0, 0xC64F, 0xD7A1, 0xC650, 0xD7A2, 0xC651, 0xD7A3, 0xC652, + 0xF900, 0xCBD0, 0xF901, 0xCBD6, 0xF902, 0xCBE7, 0xF903, 0xCDCF, 0xF904, 0xCDE8, 0xF905, 0xCEAD, 0xF906, 0xCFFB, 0xF907, 0xD0A2, + 0xF908, 0xD0B8, 0xF909, 0xD0D0, 0xF90A, 0xD0DD, 0xF90B, 0xD1D4, 0xF90C, 0xD1D5, 0xF90D, 0xD1D8, 0xF90E, 0xD1DB, 0xF90F, 0xD1DC, + 0xF910, 0xD1DD, 0xF911, 0xD1DE, 0xF912, 0xD1DF, 0xF913, 0xD1E0, 0xF914, 0xD1E2, 0xF915, 0xD1E3, 0xF916, 0xD1E4, 0xF917, 0xD1E5, + 0xF918, 0xD1E6, 0xF919, 0xD1E8, 0xF91A, 0xD1E9, 0xF91B, 0xD1EA, 0xF91C, 0xD1EB, 0xF91D, 0xD1ED, 0xF91E, 0xD1EF, 0xF91F, 0xD1F0, + 0xF920, 0xD1F2, 0xF921, 0xD1F6, 0xF922, 0xD1FA, 0xF923, 0xD1FC, 0xF924, 0xD1FD, 0xF925, 0xD1FE, 0xF926, 0xD2A2, 0xF927, 0xD2A3, + 0xF928, 0xD2A7, 0xF929, 0xD2A8, 0xF92A, 0xD2A9, 0xF92B, 0xD2AA, 0xF92C, 0xD2AB, 0xF92D, 0xD2AD, 0xF92E, 0xD2B2, 0xF92F, 0xD2BE, + 0xF930, 0xD2C2, 0xF931, 0xD2C3, 0xF932, 0xD2C4, 0xF933, 0xD2C6, 0xF934, 0xD2C7, 0xF935, 0xD2C8, 0xF936, 0xD2C9, 0xF937, 0xD2CA, + 0xF938, 0xD2CB, 0xF939, 0xD2CD, 0xF93A, 0xD2CE, 0xF93B, 0xD2CF, 0xF93C, 0xD2D0, 0xF93D, 0xD2D1, 0xF93E, 0xD2D2, 0xF93F, 0xD2D3, + 0xF940, 0xD2D4, 0xF941, 0xD2D5, 0xF942, 0xD2D6, 0xF943, 0xD2D7, 0xF944, 0xD2D9, 0xF945, 0xD2DA, 0xF946, 0xD2DE, 0xF947, 0xD2DF, + 0xF948, 0xD2E1, 0xF949, 0xD2E2, 0xF94A, 0xD2E4, 0xF94B, 0xD2E5, 0xF94C, 0xD2E6, 0xF94D, 0xD2E7, 0xF94E, 0xD2E8, 0xF94F, 0xD2E9, + 0xF950, 0xD2EA, 0xF951, 0xD2EB, 0xF952, 0xD2F0, 0xF953, 0xD2F1, 0xF954, 0xD2F2, 0xF955, 0xD2F3, 0xF956, 0xD2F4, 0xF957, 0xD2F5, + 0xF958, 0xD2F7, 0xF959, 0xD2F8, 0xF95A, 0xD4E6, 0xF95B, 0xD4FC, 0xF95C, 0xD5A5, 0xF95D, 0xD5AB, 0xF95E, 0xD5AE, 0xF95F, 0xD6B8, + 0xF960, 0xD6CD, 0xF961, 0xD7CB, 0xF962, 0xD7E4, 0xF963, 0xDBC5, 0xF964, 0xDBE4, 0xF965, 0xDCA5, 0xF966, 0xDDA5, 0xF967, 0xDDD5, + 0xF968, 0xDDF4, 0xF969, 0xDEFC, 0xF96A, 0xDEFE, 0xF96B, 0xDFB3, 0xF96C, 0xDFE1, 0xF96D, 0xDFE8, 0xF96E, 0xE0F1, 0xF96F, 0xE1AD, + 0xF970, 0xE1ED, 0xF971, 0xE3F5, 0xF972, 0xE4A1, 0xF973, 0xE4A9, 0xF974, 0xE5AE, 0xF975, 0xE5B1, 0xF976, 0xE5B2, 0xF977, 0xE5B9, + 0xF978, 0xE5BB, 0xF979, 0xE5BC, 0xF97A, 0xE5C4, 0xF97B, 0xE5CE, 0xF97C, 0xE5D0, 0xF97D, 0xE5D2, 0xF97E, 0xE5D6, 0xF97F, 0xE5FA, + 0xF980, 0xE5FB, 0xF981, 0xE5FC, 0xF982, 0xE5FE, 0xF983, 0xE6A1, 0xF984, 0xE6A4, 0xF985, 0xE6A7, 0xF986, 0xE6AD, 0xF987, 0xE6AF, + 0xF988, 0xE6B0, 0xF989, 0xE6B1, 0xF98A, 0xE6B3, 0xF98B, 0xE6B7, 0xF98C, 0xE6B8, 0xF98D, 0xE6BC, 0xF98E, 0xE6C4, 0xF98F, 0xE6C6, + 0xF990, 0xE6C7, 0xF991, 0xE6CA, 0xF992, 0xE6D2, 0xF993, 0xE6D6, 0xF994, 0xE6D9, 0xF995, 0xE6DC, 0xF996, 0xE6DF, 0xF997, 0xE6E1, + 0xF998, 0xE6E4, 0xF999, 0xE6E5, 0xF99A, 0xE6E6, 0xF99B, 0xE6E8, 0xF99C, 0xE6EA, 0xF99D, 0xE6EB, 0xF99E, 0xE6EC, 0xF99F, 0xE6EF, + 0xF9A0, 0xE6F1, 0xF9A1, 0xE6F2, 0xF9A2, 0xE6F5, 0xF9A3, 0xE6F6, 0xF9A4, 0xE6F7, 0xF9A5, 0xE6F9, 0xF9A6, 0xE7A1, 0xF9A7, 0xE7A6, + 0xF9A8, 0xE7A9, 0xF9A9, 0xE7AA, 0xF9AA, 0xE7AC, 0xF9AB, 0xE7AD, 0xF9AC, 0xE7B0, 0xF9AD, 0xE7BF, 0xF9AE, 0xE7C1, 0xF9AF, 0xE7C6, + 0xF9B0, 0xE7C7, 0xF9B1, 0xE7CB, 0xF9B2, 0xE7CD, 0xF9B3, 0xE7CF, 0xF9B4, 0xE7D0, 0xF9B5, 0xE7D3, 0xF9B6, 0xE7DF, 0xF9B7, 0xE7E4, + 0xF9B8, 0xE7E6, 0xF9B9, 0xE7F7, 0xF9BA, 0xE8E7, 0xF9BB, 0xE8E8, 0xF9BC, 0xE8F0, 0xF9BD, 0xE8F1, 0xF9BE, 0xE8F7, 0xF9BF, 0xE8F9, + 0xF9C0, 0xE8FB, 0xF9C1, 0xE8FE, 0xF9C2, 0xE9A7, 0xF9C3, 0xE9AC, 0xF9C4, 0xE9CC, 0xF9C5, 0xE9F7, 0xF9C6, 0xEAC1, 0xF9C7, 0xEAE5, + 0xF9C8, 0xEAF4, 0xF9C9, 0xEAF7, 0xF9CA, 0xEAFC, 0xF9CB, 0xEAFE, 0xF9CC, 0xEBA4, 0xF9CD, 0xEBA7, 0xF9CE, 0xEBA9, 0xF9CF, 0xEBAA, + 0xF9D0, 0xEBBA, 0xF9D1, 0xEBBB, 0xF9D2, 0xEBBD, 0xF9D3, 0xEBC1, 0xF9D4, 0xEBC2, 0xF9D5, 0xEBC6, 0xF9D6, 0xEBC7, 0xF9D7, 0xEBCC, + 0xF9D8, 0xEBCF, 0xF9D9, 0xEBD0, 0xF9DA, 0xEBD1, 0xF9DB, 0xEBD2, 0xF9DC, 0xEBD8, 0xF9DD, 0xECA6, 0xF9DE, 0xECA7, 0xF9DF, 0xECAA, + 0xF9E0, 0xECAF, 0xF9E1, 0xECB0, 0xF9E2, 0xECB1, 0xF9E3, 0xECB2, 0xF9E4, 0xECB5, 0xF9E5, 0xECB8, 0xF9E6, 0xECBA, 0xF9E7, 0xECC0, + 0xF9E8, 0xECC1, 0xF9E9, 0xECC5, 0xF9EA, 0xECC6, 0xF9EB, 0xECC9, 0xF9EC, 0xECCA, 0xF9ED, 0xECD5, 0xF9EE, 0xECDD, 0xF9EF, 0xECDE, + 0xF9F0, 0xECE1, 0xF9F1, 0xECE4, 0xF9F2, 0xECE7, 0xF9F3, 0xECE8, 0xF9F4, 0xECF7, 0xF9F5, 0xECF8, 0xF9F6, 0xECFA, 0xF9F7, 0xEDA1, + 0xF9F8, 0xEDA2, 0xF9F9, 0xEDA3, 0xF9FA, 0xEDEE, 0xF9FB, 0xEEDB, 0xF9FC, 0xF2BD, 0xF9FD, 0xF2FA, 0xF9FE, 0xF3B1, 0xF9FF, 0xF4A7, + 0xFA00, 0xF4EE, 0xFA01, 0xF6F4, 0xFA02, 0xF6F6, 0xFA03, 0xF7B8, 0xFA04, 0xF7C8, 0xFA05, 0xF7D3, 0xFA06, 0xF8DB, 0xFA07, 0xF8F0, + 0xFA08, 0xFAA1, 0xFA09, 0xFAA2, 0xFA0A, 0xFAE6, 0xFA0B, 0xFCA9, 0xFF01, 0xA3A1, 0xFF02, 0xA3A2, 0xFF03, 0xA3A3, 0xFF04, 0xA3A4, + 0xFF05, 0xA3A5, 0xFF06, 0xA3A6, 0xFF07, 0xA3A7, 0xFF08, 0xA3A8, 0xFF09, 0xA3A9, 0xFF0A, 0xA3AA, 0xFF0B, 0xA3AB, 0xFF0C, 0xA3AC, + 0xFF0D, 0xA3AD, 0xFF0E, 0xA3AE, 0xFF0F, 0xA3AF, 0xFF10, 0xA3B0, 0xFF11, 0xA3B1, 0xFF12, 0xA3B2, 0xFF13, 0xA3B3, 0xFF14, 0xA3B4, + 0xFF15, 0xA3B5, 0xFF16, 0xA3B6, 0xFF17, 0xA3B7, 0xFF18, 0xA3B8, 0xFF19, 0xA3B9, 0xFF1A, 0xA3BA, 0xFF1B, 0xA3BB, 0xFF1C, 0xA3BC, + 0xFF1D, 0xA3BD, 0xFF1E, 0xA3BE, 0xFF1F, 0xA3BF, 0xFF20, 0xA3C0, 0xFF21, 0xA3C1, 0xFF22, 0xA3C2, 0xFF23, 0xA3C3, 0xFF24, 0xA3C4, + 0xFF25, 0xA3C5, 0xFF26, 0xA3C6, 0xFF27, 0xA3C7, 0xFF28, 0xA3C8, 0xFF29, 0xA3C9, 0xFF2A, 0xA3CA, 0xFF2B, 0xA3CB, 0xFF2C, 0xA3CC, + 0xFF2D, 0xA3CD, 0xFF2E, 0xA3CE, 0xFF2F, 0xA3CF, 0xFF30, 0xA3D0, 0xFF31, 0xA3D1, 0xFF32, 0xA3D2, 0xFF33, 0xA3D3, 0xFF34, 0xA3D4, + 0xFF35, 0xA3D5, 0xFF36, 0xA3D6, 0xFF37, 0xA3D7, 0xFF38, 0xA3D8, 0xFF39, 0xA3D9, 0xFF3A, 0xA3DA, 0xFF3B, 0xA3DB, 0xFF3C, 0xA1AC, + 0xFF3D, 0xA3DD, 0xFF3E, 0xA3DE, 0xFF3F, 0xA3DF, 0xFF40, 0xA3E0, 0xFF41, 0xA3E1, 0xFF42, 0xA3E2, 0xFF43, 0xA3E3, 0xFF44, 0xA3E4, + 0xFF45, 0xA3E5, 0xFF46, 0xA3E6, 0xFF47, 0xA3E7, 0xFF48, 0xA3E8, 0xFF49, 0xA3E9, 0xFF4A, 0xA3EA, 0xFF4B, 0xA3EB, 0xFF4C, 0xA3EC, + 0xFF4D, 0xA3ED, 0xFF4E, 0xA3EE, 0xFF4F, 0xA3EF, 0xFF50, 0xA3F0, 0xFF51, 0xA3F1, 0xFF52, 0xA3F2, 0xFF53, 0xA3F3, 0xFF54, 0xA3F4, + 0xFF55, 0xA3F5, 0xFF56, 0xA3F6, 0xFF57, 0xA3F7, 0xFF58, 0xA3F8, 0xFF59, 0xA3F9, 0xFF5A, 0xA3FA, 0xFF5B, 0xA3FB, 0xFF5C, 0xA3FC, + 0xFF5D, 0xA3FD, 0xFF5E, 0xA2A6, 0xFFE0, 0xA1CB, 0xFFE1, 0xA1CC, 0xFFE2, 0xA1FE, 0xFFE3, 0xA3FE, 0xFFE5, 0xA1CD, 0xFFE6, 0xA3DC, + 0, 0 +}; + +static const WCHAR oem2uni949[] = { /* Korean --> Unicode pairs */ + 0x8141, 0xAC02, 0x8142, 0xAC03, 0x8143, 0xAC05, 0x8144, 0xAC06, 0x8145, 0xAC0B, 0x8146, 0xAC0C, 0x8147, 0xAC0D, 0x8148, 0xAC0E, + 0x8149, 0xAC0F, 0x814A, 0xAC18, 0x814B, 0xAC1E, 0x814C, 0xAC1F, 0x814D, 0xAC21, 0x814E, 0xAC22, 0x814F, 0xAC23, 0x8150, 0xAC25, + 0x8151, 0xAC26, 0x8152, 0xAC27, 0x8153, 0xAC28, 0x8154, 0xAC29, 0x8155, 0xAC2A, 0x8156, 0xAC2B, 0x8157, 0xAC2E, 0x8158, 0xAC32, + 0x8159, 0xAC33, 0x815A, 0xAC34, 0x8161, 0xAC35, 0x8162, 0xAC36, 0x8163, 0xAC37, 0x8164, 0xAC3A, 0x8165, 0xAC3B, 0x8166, 0xAC3D, + 0x8167, 0xAC3E, 0x8168, 0xAC3F, 0x8169, 0xAC41, 0x816A, 0xAC42, 0x816B, 0xAC43, 0x816C, 0xAC44, 0x816D, 0xAC45, 0x816E, 0xAC46, + 0x816F, 0xAC47, 0x8170, 0xAC48, 0x8171, 0xAC49, 0x8172, 0xAC4A, 0x8173, 0xAC4C, 0x8174, 0xAC4E, 0x8175, 0xAC4F, 0x8176, 0xAC50, + 0x8177, 0xAC51, 0x8178, 0xAC52, 0x8179, 0xAC53, 0x817A, 0xAC55, 0x8181, 0xAC56, 0x8182, 0xAC57, 0x8183, 0xAC59, 0x8184, 0xAC5A, + 0x8185, 0xAC5B, 0x8186, 0xAC5D, 0x8187, 0xAC5E, 0x8188, 0xAC5F, 0x8189, 0xAC60, 0x818A, 0xAC61, 0x818B, 0xAC62, 0x818C, 0xAC63, + 0x818D, 0xAC64, 0x818E, 0xAC65, 0x818F, 0xAC66, 0x8190, 0xAC67, 0x8191, 0xAC68, 0x8192, 0xAC69, 0x8193, 0xAC6A, 0x8194, 0xAC6B, + 0x8195, 0xAC6C, 0x8196, 0xAC6D, 0x8197, 0xAC6E, 0x8198, 0xAC6F, 0x8199, 0xAC72, 0x819A, 0xAC73, 0x819B, 0xAC75, 0x819C, 0xAC76, + 0x819D, 0xAC79, 0x819E, 0xAC7B, 0x819F, 0xAC7C, 0x81A0, 0xAC7D, 0x81A1, 0xAC7E, 0x81A2, 0xAC7F, 0x81A3, 0xAC82, 0x81A4, 0xAC87, + 0x81A5, 0xAC88, 0x81A6, 0xAC8D, 0x81A7, 0xAC8E, 0x81A8, 0xAC8F, 0x81A9, 0xAC91, 0x81AA, 0xAC92, 0x81AB, 0xAC93, 0x81AC, 0xAC95, + 0x81AD, 0xAC96, 0x81AE, 0xAC97, 0x81AF, 0xAC98, 0x81B0, 0xAC99, 0x81B1, 0xAC9A, 0x81B2, 0xAC9B, 0x81B3, 0xAC9E, 0x81B4, 0xACA2, + 0x81B5, 0xACA3, 0x81B6, 0xACA4, 0x81B7, 0xACA5, 0x81B8, 0xACA6, 0x81B9, 0xACA7, 0x81BA, 0xACAB, 0x81BB, 0xACAD, 0x81BC, 0xACAE, + 0x81BD, 0xACB1, 0x81BE, 0xACB2, 0x81BF, 0xACB3, 0x81C0, 0xACB4, 0x81C1, 0xACB5, 0x81C2, 0xACB6, 0x81C3, 0xACB7, 0x81C4, 0xACBA, + 0x81C5, 0xACBE, 0x81C6, 0xACBF, 0x81C7, 0xACC0, 0x81C8, 0xACC2, 0x81C9, 0xACC3, 0x81CA, 0xACC5, 0x81CB, 0xACC6, 0x81CC, 0xACC7, + 0x81CD, 0xACC9, 0x81CE, 0xACCA, 0x81CF, 0xACCB, 0x81D0, 0xACCD, 0x81D1, 0xACCE, 0x81D2, 0xACCF, 0x81D3, 0xACD0, 0x81D4, 0xACD1, + 0x81D5, 0xACD2, 0x81D6, 0xACD3, 0x81D7, 0xACD4, 0x81D8, 0xACD6, 0x81D9, 0xACD8, 0x81DA, 0xACD9, 0x81DB, 0xACDA, 0x81DC, 0xACDB, + 0x81DD, 0xACDC, 0x81DE, 0xACDD, 0x81DF, 0xACDE, 0x81E0, 0xACDF, 0x81E1, 0xACE2, 0x81E2, 0xACE3, 0x81E3, 0xACE5, 0x81E4, 0xACE6, + 0x81E5, 0xACE9, 0x81E6, 0xACEB, 0x81E7, 0xACED, 0x81E8, 0xACEE, 0x81E9, 0xACF2, 0x81EA, 0xACF4, 0x81EB, 0xACF7, 0x81EC, 0xACF8, + 0x81ED, 0xACF9, 0x81EE, 0xACFA, 0x81EF, 0xACFB, 0x81F0, 0xACFE, 0x81F1, 0xACFF, 0x81F2, 0xAD01, 0x81F3, 0xAD02, 0x81F4, 0xAD03, + 0x81F5, 0xAD05, 0x81F6, 0xAD07, 0x81F7, 0xAD08, 0x81F8, 0xAD09, 0x81F9, 0xAD0A, 0x81FA, 0xAD0B, 0x81FB, 0xAD0E, 0x81FC, 0xAD10, + 0x81FD, 0xAD12, 0x81FE, 0xAD13, 0x8241, 0xAD14, 0x8242, 0xAD15, 0x8243, 0xAD16, 0x8244, 0xAD17, 0x8245, 0xAD19, 0x8246, 0xAD1A, + 0x8247, 0xAD1B, 0x8248, 0xAD1D, 0x8249, 0xAD1E, 0x824A, 0xAD1F, 0x824B, 0xAD21, 0x824C, 0xAD22, 0x824D, 0xAD23, 0x824E, 0xAD24, + 0x824F, 0xAD25, 0x8250, 0xAD26, 0x8251, 0xAD27, 0x8252, 0xAD28, 0x8253, 0xAD2A, 0x8254, 0xAD2B, 0x8255, 0xAD2E, 0x8256, 0xAD2F, + 0x8257, 0xAD30, 0x8258, 0xAD31, 0x8259, 0xAD32, 0x825A, 0xAD33, 0x8261, 0xAD36, 0x8262, 0xAD37, 0x8263, 0xAD39, 0x8264, 0xAD3A, + 0x8265, 0xAD3B, 0x8266, 0xAD3D, 0x8267, 0xAD3E, 0x8268, 0xAD3F, 0x8269, 0xAD40, 0x826A, 0xAD41, 0x826B, 0xAD42, 0x826C, 0xAD43, + 0x826D, 0xAD46, 0x826E, 0xAD48, 0x826F, 0xAD4A, 0x8270, 0xAD4B, 0x8271, 0xAD4C, 0x8272, 0xAD4D, 0x8273, 0xAD4E, 0x8274, 0xAD4F, + 0x8275, 0xAD51, 0x8276, 0xAD52, 0x8277, 0xAD53, 0x8278, 0xAD55, 0x8279, 0xAD56, 0x827A, 0xAD57, 0x8281, 0xAD59, 0x8282, 0xAD5A, + 0x8283, 0xAD5B, 0x8284, 0xAD5C, 0x8285, 0xAD5D, 0x8286, 0xAD5E, 0x8287, 0xAD5F, 0x8288, 0xAD60, 0x8289, 0xAD62, 0x828A, 0xAD64, + 0x828B, 0xAD65, 0x828C, 0xAD66, 0x828D, 0xAD67, 0x828E, 0xAD68, 0x828F, 0xAD69, 0x8290, 0xAD6A, 0x8291, 0xAD6B, 0x8292, 0xAD6E, + 0x8293, 0xAD6F, 0x8294, 0xAD71, 0x8295, 0xAD72, 0x8296, 0xAD77, 0x8297, 0xAD78, 0x8298, 0xAD79, 0x8299, 0xAD7A, 0x829A, 0xAD7E, + 0x829B, 0xAD80, 0x829C, 0xAD83, 0x829D, 0xAD84, 0x829E, 0xAD85, 0x829F, 0xAD86, 0x82A0, 0xAD87, 0x82A1, 0xAD8A, 0x82A2, 0xAD8B, + 0x82A3, 0xAD8D, 0x82A4, 0xAD8E, 0x82A5, 0xAD8F, 0x82A6, 0xAD91, 0x82A7, 0xAD92, 0x82A8, 0xAD93, 0x82A9, 0xAD94, 0x82AA, 0xAD95, + 0x82AB, 0xAD96, 0x82AC, 0xAD97, 0x82AD, 0xAD98, 0x82AE, 0xAD99, 0x82AF, 0xAD9A, 0x82B0, 0xAD9B, 0x82B1, 0xAD9E, 0x82B2, 0xAD9F, + 0x82B3, 0xADA0, 0x82B4, 0xADA1, 0x82B5, 0xADA2, 0x82B6, 0xADA3, 0x82B7, 0xADA5, 0x82B8, 0xADA6, 0x82B9, 0xADA7, 0x82BA, 0xADA8, + 0x82BB, 0xADA9, 0x82BC, 0xADAA, 0x82BD, 0xADAB, 0x82BE, 0xADAC, 0x82BF, 0xADAD, 0x82C0, 0xADAE, 0x82C1, 0xADAF, 0x82C2, 0xADB0, + 0x82C3, 0xADB1, 0x82C4, 0xADB2, 0x82C5, 0xADB3, 0x82C6, 0xADB4, 0x82C7, 0xADB5, 0x82C8, 0xADB6, 0x82C9, 0xADB8, 0x82CA, 0xADB9, + 0x82CB, 0xADBA, 0x82CC, 0xADBB, 0x82CD, 0xADBC, 0x82CE, 0xADBD, 0x82CF, 0xADBE, 0x82D0, 0xADBF, 0x82D1, 0xADC2, 0x82D2, 0xADC3, + 0x82D3, 0xADC5, 0x82D4, 0xADC6, 0x82D5, 0xADC7, 0x82D6, 0xADC9, 0x82D7, 0xADCA, 0x82D8, 0xADCB, 0x82D9, 0xADCC, 0x82DA, 0xADCD, + 0x82DB, 0xADCE, 0x82DC, 0xADCF, 0x82DD, 0xADD2, 0x82DE, 0xADD4, 0x82DF, 0xADD5, 0x82E0, 0xADD6, 0x82E1, 0xADD7, 0x82E2, 0xADD8, + 0x82E3, 0xADD9, 0x82E4, 0xADDA, 0x82E5, 0xADDB, 0x82E6, 0xADDD, 0x82E7, 0xADDE, 0x82E8, 0xADDF, 0x82E9, 0xADE1, 0x82EA, 0xADE2, + 0x82EB, 0xADE3, 0x82EC, 0xADE5, 0x82ED, 0xADE6, 0x82EE, 0xADE7, 0x82EF, 0xADE8, 0x82F0, 0xADE9, 0x82F1, 0xADEA, 0x82F2, 0xADEB, + 0x82F3, 0xADEC, 0x82F4, 0xADED, 0x82F5, 0xADEE, 0x82F6, 0xADEF, 0x82F7, 0xADF0, 0x82F8, 0xADF1, 0x82F9, 0xADF2, 0x82FA, 0xADF3, + 0x82FB, 0xADF4, 0x82FC, 0xADF5, 0x82FD, 0xADF6, 0x82FE, 0xADF7, 0x8341, 0xADFA, 0x8342, 0xADFB, 0x8343, 0xADFD, 0x8344, 0xADFE, + 0x8345, 0xAE02, 0x8346, 0xAE03, 0x8347, 0xAE04, 0x8348, 0xAE05, 0x8349, 0xAE06, 0x834A, 0xAE07, 0x834B, 0xAE0A, 0x834C, 0xAE0C, + 0x834D, 0xAE0E, 0x834E, 0xAE0F, 0x834F, 0xAE10, 0x8350, 0xAE11, 0x8351, 0xAE12, 0x8352, 0xAE13, 0x8353, 0xAE15, 0x8354, 0xAE16, + 0x8355, 0xAE17, 0x8356, 0xAE18, 0x8357, 0xAE19, 0x8358, 0xAE1A, 0x8359, 0xAE1B, 0x835A, 0xAE1C, 0x8361, 0xAE1D, 0x8362, 0xAE1E, + 0x8363, 0xAE1F, 0x8364, 0xAE20, 0x8365, 0xAE21, 0x8366, 0xAE22, 0x8367, 0xAE23, 0x8368, 0xAE24, 0x8369, 0xAE25, 0x836A, 0xAE26, + 0x836B, 0xAE27, 0x836C, 0xAE28, 0x836D, 0xAE29, 0x836E, 0xAE2A, 0x836F, 0xAE2B, 0x8370, 0xAE2C, 0x8371, 0xAE2D, 0x8372, 0xAE2E, + 0x8373, 0xAE2F, 0x8374, 0xAE32, 0x8375, 0xAE33, 0x8376, 0xAE35, 0x8377, 0xAE36, 0x8378, 0xAE39, 0x8379, 0xAE3B, 0x837A, 0xAE3C, + 0x8381, 0xAE3D, 0x8382, 0xAE3E, 0x8383, 0xAE3F, 0x8384, 0xAE42, 0x8385, 0xAE44, 0x8386, 0xAE47, 0x8387, 0xAE48, 0x8388, 0xAE49, + 0x8389, 0xAE4B, 0x838A, 0xAE4F, 0x838B, 0xAE51, 0x838C, 0xAE52, 0x838D, 0xAE53, 0x838E, 0xAE55, 0x838F, 0xAE57, 0x8390, 0xAE58, + 0x8391, 0xAE59, 0x8392, 0xAE5A, 0x8393, 0xAE5B, 0x8394, 0xAE5E, 0x8395, 0xAE62, 0x8396, 0xAE63, 0x8397, 0xAE64, 0x8398, 0xAE66, + 0x8399, 0xAE67, 0x839A, 0xAE6A, 0x839B, 0xAE6B, 0x839C, 0xAE6D, 0x839D, 0xAE6E, 0x839E, 0xAE6F, 0x839F, 0xAE71, 0x83A0, 0xAE72, + 0x83A1, 0xAE73, 0x83A2, 0xAE74, 0x83A3, 0xAE75, 0x83A4, 0xAE76, 0x83A5, 0xAE77, 0x83A6, 0xAE7A, 0x83A7, 0xAE7E, 0x83A8, 0xAE7F, + 0x83A9, 0xAE80, 0x83AA, 0xAE81, 0x83AB, 0xAE82, 0x83AC, 0xAE83, 0x83AD, 0xAE86, 0x83AE, 0xAE87, 0x83AF, 0xAE88, 0x83B0, 0xAE89, + 0x83B1, 0xAE8A, 0x83B2, 0xAE8B, 0x83B3, 0xAE8D, 0x83B4, 0xAE8E, 0x83B5, 0xAE8F, 0x83B6, 0xAE90, 0x83B7, 0xAE91, 0x83B8, 0xAE92, + 0x83B9, 0xAE93, 0x83BA, 0xAE94, 0x83BB, 0xAE95, 0x83BC, 0xAE96, 0x83BD, 0xAE97, 0x83BE, 0xAE98, 0x83BF, 0xAE99, 0x83C0, 0xAE9A, + 0x83C1, 0xAE9B, 0x83C2, 0xAE9C, 0x83C3, 0xAE9D, 0x83C4, 0xAE9E, 0x83C5, 0xAE9F, 0x83C6, 0xAEA0, 0x83C7, 0xAEA1, 0x83C8, 0xAEA2, + 0x83C9, 0xAEA3, 0x83CA, 0xAEA4, 0x83CB, 0xAEA5, 0x83CC, 0xAEA6, 0x83CD, 0xAEA7, 0x83CE, 0xAEA8, 0x83CF, 0xAEA9, 0x83D0, 0xAEAA, + 0x83D1, 0xAEAB, 0x83D2, 0xAEAC, 0x83D3, 0xAEAD, 0x83D4, 0xAEAE, 0x83D5, 0xAEAF, 0x83D6, 0xAEB0, 0x83D7, 0xAEB1, 0x83D8, 0xAEB2, + 0x83D9, 0xAEB3, 0x83DA, 0xAEB4, 0x83DB, 0xAEB5, 0x83DC, 0xAEB6, 0x83DD, 0xAEB7, 0x83DE, 0xAEB8, 0x83DF, 0xAEB9, 0x83E0, 0xAEBA, + 0x83E1, 0xAEBB, 0x83E2, 0xAEBF, 0x83E3, 0xAEC1, 0x83E4, 0xAEC2, 0x83E5, 0xAEC3, 0x83E6, 0xAEC5, 0x83E7, 0xAEC6, 0x83E8, 0xAEC7, + 0x83E9, 0xAEC8, 0x83EA, 0xAEC9, 0x83EB, 0xAECA, 0x83EC, 0xAECB, 0x83ED, 0xAECE, 0x83EE, 0xAED2, 0x83EF, 0xAED3, 0x83F0, 0xAED4, + 0x83F1, 0xAED5, 0x83F2, 0xAED6, 0x83F3, 0xAED7, 0x83F4, 0xAEDA, 0x83F5, 0xAEDB, 0x83F6, 0xAEDD, 0x83F7, 0xAEDE, 0x83F8, 0xAEDF, + 0x83F9, 0xAEE0, 0x83FA, 0xAEE1, 0x83FB, 0xAEE2, 0x83FC, 0xAEE3, 0x83FD, 0xAEE4, 0x83FE, 0xAEE5, 0x8441, 0xAEE6, 0x8442, 0xAEE7, + 0x8443, 0xAEE9, 0x8444, 0xAEEA, 0x8445, 0xAEEC, 0x8446, 0xAEEE, 0x8447, 0xAEEF, 0x8448, 0xAEF0, 0x8449, 0xAEF1, 0x844A, 0xAEF2, + 0x844B, 0xAEF3, 0x844C, 0xAEF5, 0x844D, 0xAEF6, 0x844E, 0xAEF7, 0x844F, 0xAEF9, 0x8450, 0xAEFA, 0x8451, 0xAEFB, 0x8452, 0xAEFD, + 0x8453, 0xAEFE, 0x8454, 0xAEFF, 0x8455, 0xAF00, 0x8456, 0xAF01, 0x8457, 0xAF02, 0x8458, 0xAF03, 0x8459, 0xAF04, 0x845A, 0xAF05, + 0x8461, 0xAF06, 0x8462, 0xAF09, 0x8463, 0xAF0A, 0x8464, 0xAF0B, 0x8465, 0xAF0C, 0x8466, 0xAF0E, 0x8467, 0xAF0F, 0x8468, 0xAF11, + 0x8469, 0xAF12, 0x846A, 0xAF13, 0x846B, 0xAF14, 0x846C, 0xAF15, 0x846D, 0xAF16, 0x846E, 0xAF17, 0x846F, 0xAF18, 0x8470, 0xAF19, + 0x8471, 0xAF1A, 0x8472, 0xAF1B, 0x8473, 0xAF1C, 0x8474, 0xAF1D, 0x8475, 0xAF1E, 0x8476, 0xAF1F, 0x8477, 0xAF20, 0x8478, 0xAF21, + 0x8479, 0xAF22, 0x847A, 0xAF23, 0x8481, 0xAF24, 0x8482, 0xAF25, 0x8483, 0xAF26, 0x8484, 0xAF27, 0x8485, 0xAF28, 0x8486, 0xAF29, + 0x8487, 0xAF2A, 0x8488, 0xAF2B, 0x8489, 0xAF2E, 0x848A, 0xAF2F, 0x848B, 0xAF31, 0x848C, 0xAF33, 0x848D, 0xAF35, 0x848E, 0xAF36, + 0x848F, 0xAF37, 0x8490, 0xAF38, 0x8491, 0xAF39, 0x8492, 0xAF3A, 0x8493, 0xAF3B, 0x8494, 0xAF3E, 0x8495, 0xAF40, 0x8496, 0xAF44, + 0x8497, 0xAF45, 0x8498, 0xAF46, 0x8499, 0xAF47, 0x849A, 0xAF4A, 0x849B, 0xAF4B, 0x849C, 0xAF4C, 0x849D, 0xAF4D, 0x849E, 0xAF4E, + 0x849F, 0xAF4F, 0x84A0, 0xAF51, 0x84A1, 0xAF52, 0x84A2, 0xAF53, 0x84A3, 0xAF54, 0x84A4, 0xAF55, 0x84A5, 0xAF56, 0x84A6, 0xAF57, + 0x84A7, 0xAF58, 0x84A8, 0xAF59, 0x84A9, 0xAF5A, 0x84AA, 0xAF5B, 0x84AB, 0xAF5E, 0x84AC, 0xAF5F, 0x84AD, 0xAF60, 0x84AE, 0xAF61, + 0x84AF, 0xAF62, 0x84B0, 0xAF63, 0x84B1, 0xAF66, 0x84B2, 0xAF67, 0x84B3, 0xAF68, 0x84B4, 0xAF69, 0x84B5, 0xAF6A, 0x84B6, 0xAF6B, + 0x84B7, 0xAF6C, 0x84B8, 0xAF6D, 0x84B9, 0xAF6E, 0x84BA, 0xAF6F, 0x84BB, 0xAF70, 0x84BC, 0xAF71, 0x84BD, 0xAF72, 0x84BE, 0xAF73, + 0x84BF, 0xAF74, 0x84C0, 0xAF75, 0x84C1, 0xAF76, 0x84C2, 0xAF77, 0x84C3, 0xAF78, 0x84C4, 0xAF7A, 0x84C5, 0xAF7B, 0x84C6, 0xAF7C, + 0x84C7, 0xAF7D, 0x84C8, 0xAF7E, 0x84C9, 0xAF7F, 0x84CA, 0xAF81, 0x84CB, 0xAF82, 0x84CC, 0xAF83, 0x84CD, 0xAF85, 0x84CE, 0xAF86, + 0x84CF, 0xAF87, 0x84D0, 0xAF89, 0x84D1, 0xAF8A, 0x84D2, 0xAF8B, 0x84D3, 0xAF8C, 0x84D4, 0xAF8D, 0x84D5, 0xAF8E, 0x84D6, 0xAF8F, + 0x84D7, 0xAF92, 0x84D8, 0xAF93, 0x84D9, 0xAF94, 0x84DA, 0xAF96, 0x84DB, 0xAF97, 0x84DC, 0xAF98, 0x84DD, 0xAF99, 0x84DE, 0xAF9A, + 0x84DF, 0xAF9B, 0x84E0, 0xAF9D, 0x84E1, 0xAF9E, 0x84E2, 0xAF9F, 0x84E3, 0xAFA0, 0x84E4, 0xAFA1, 0x84E5, 0xAFA2, 0x84E6, 0xAFA3, + 0x84E7, 0xAFA4, 0x84E8, 0xAFA5, 0x84E9, 0xAFA6, 0x84EA, 0xAFA7, 0x84EB, 0xAFA8, 0x84EC, 0xAFA9, 0x84ED, 0xAFAA, 0x84EE, 0xAFAB, + 0x84EF, 0xAFAC, 0x84F0, 0xAFAD, 0x84F1, 0xAFAE, 0x84F2, 0xAFAF, 0x84F3, 0xAFB0, 0x84F4, 0xAFB1, 0x84F5, 0xAFB2, 0x84F6, 0xAFB3, + 0x84F7, 0xAFB4, 0x84F8, 0xAFB5, 0x84F9, 0xAFB6, 0x84FA, 0xAFB7, 0x84FB, 0xAFBA, 0x84FC, 0xAFBB, 0x84FD, 0xAFBD, 0x84FE, 0xAFBE, + 0x8541, 0xAFBF, 0x8542, 0xAFC1, 0x8543, 0xAFC2, 0x8544, 0xAFC3, 0x8545, 0xAFC4, 0x8546, 0xAFC5, 0x8547, 0xAFC6, 0x8548, 0xAFCA, + 0x8549, 0xAFCC, 0x854A, 0xAFCF, 0x854B, 0xAFD0, 0x854C, 0xAFD1, 0x854D, 0xAFD2, 0x854E, 0xAFD3, 0x854F, 0xAFD5, 0x8550, 0xAFD6, + 0x8551, 0xAFD7, 0x8552, 0xAFD8, 0x8553, 0xAFD9, 0x8554, 0xAFDA, 0x8555, 0xAFDB, 0x8556, 0xAFDD, 0x8557, 0xAFDE, 0x8558, 0xAFDF, + 0x8559, 0xAFE0, 0x855A, 0xAFE1, 0x8561, 0xAFE2, 0x8562, 0xAFE3, 0x8563, 0xAFE4, 0x8564, 0xAFE5, 0x8565, 0xAFE6, 0x8566, 0xAFE7, + 0x8567, 0xAFEA, 0x8568, 0xAFEB, 0x8569, 0xAFEC, 0x856A, 0xAFED, 0x856B, 0xAFEE, 0x856C, 0xAFEF, 0x856D, 0xAFF2, 0x856E, 0xAFF3, + 0x856F, 0xAFF5, 0x8570, 0xAFF6, 0x8571, 0xAFF7, 0x8572, 0xAFF9, 0x8573, 0xAFFA, 0x8574, 0xAFFB, 0x8575, 0xAFFC, 0x8576, 0xAFFD, + 0x8577, 0xAFFE, 0x8578, 0xAFFF, 0x8579, 0xB002, 0x857A, 0xB003, 0x8581, 0xB005, 0x8582, 0xB006, 0x8583, 0xB007, 0x8584, 0xB008, + 0x8585, 0xB009, 0x8586, 0xB00A, 0x8587, 0xB00B, 0x8588, 0xB00D, 0x8589, 0xB00E, 0x858A, 0xB00F, 0x858B, 0xB011, 0x858C, 0xB012, + 0x858D, 0xB013, 0x858E, 0xB015, 0x858F, 0xB016, 0x8590, 0xB017, 0x8591, 0xB018, 0x8592, 0xB019, 0x8593, 0xB01A, 0x8594, 0xB01B, + 0x8595, 0xB01E, 0x8596, 0xB01F, 0x8597, 0xB020, 0x8598, 0xB021, 0x8599, 0xB022, 0x859A, 0xB023, 0x859B, 0xB024, 0x859C, 0xB025, + 0x859D, 0xB026, 0x859E, 0xB027, 0x859F, 0xB029, 0x85A0, 0xB02A, 0x85A1, 0xB02B, 0x85A2, 0xB02C, 0x85A3, 0xB02D, 0x85A4, 0xB02E, + 0x85A5, 0xB02F, 0x85A6, 0xB030, 0x85A7, 0xB031, 0x85A8, 0xB032, 0x85A9, 0xB033, 0x85AA, 0xB034, 0x85AB, 0xB035, 0x85AC, 0xB036, + 0x85AD, 0xB037, 0x85AE, 0xB038, 0x85AF, 0xB039, 0x85B0, 0xB03A, 0x85B1, 0xB03B, 0x85B2, 0xB03C, 0x85B3, 0xB03D, 0x85B4, 0xB03E, + 0x85B5, 0xB03F, 0x85B6, 0xB040, 0x85B7, 0xB041, 0x85B8, 0xB042, 0x85B9, 0xB043, 0x85BA, 0xB046, 0x85BB, 0xB047, 0x85BC, 0xB049, + 0x85BD, 0xB04B, 0x85BE, 0xB04D, 0x85BF, 0xB04F, 0x85C0, 0xB050, 0x85C1, 0xB051, 0x85C2, 0xB052, 0x85C3, 0xB056, 0x85C4, 0xB058, + 0x85C5, 0xB05A, 0x85C6, 0xB05B, 0x85C7, 0xB05C, 0x85C8, 0xB05E, 0x85C9, 0xB05F, 0x85CA, 0xB060, 0x85CB, 0xB061, 0x85CC, 0xB062, + 0x85CD, 0xB063, 0x85CE, 0xB064, 0x85CF, 0xB065, 0x85D0, 0xB066, 0x85D1, 0xB067, 0x85D2, 0xB068, 0x85D3, 0xB069, 0x85D4, 0xB06A, + 0x85D5, 0xB06B, 0x85D6, 0xB06C, 0x85D7, 0xB06D, 0x85D8, 0xB06E, 0x85D9, 0xB06F, 0x85DA, 0xB070, 0x85DB, 0xB071, 0x85DC, 0xB072, + 0x85DD, 0xB073, 0x85DE, 0xB074, 0x85DF, 0xB075, 0x85E0, 0xB076, 0x85E1, 0xB077, 0x85E2, 0xB078, 0x85E3, 0xB079, 0x85E4, 0xB07A, + 0x85E5, 0xB07B, 0x85E6, 0xB07E, 0x85E7, 0xB07F, 0x85E8, 0xB081, 0x85E9, 0xB082, 0x85EA, 0xB083, 0x85EB, 0xB085, 0x85EC, 0xB086, + 0x85ED, 0xB087, 0x85EE, 0xB088, 0x85EF, 0xB089, 0x85F0, 0xB08A, 0x85F1, 0xB08B, 0x85F2, 0xB08E, 0x85F3, 0xB090, 0x85F4, 0xB092, + 0x85F5, 0xB093, 0x85F6, 0xB094, 0x85F7, 0xB095, 0x85F8, 0xB096, 0x85F9, 0xB097, 0x85FA, 0xB09B, 0x85FB, 0xB09D, 0x85FC, 0xB09E, + 0x85FD, 0xB0A3, 0x85FE, 0xB0A4, 0x8641, 0xB0A5, 0x8642, 0xB0A6, 0x8643, 0xB0A7, 0x8644, 0xB0AA, 0x8645, 0xB0B0, 0x8646, 0xB0B2, + 0x8647, 0xB0B6, 0x8648, 0xB0B7, 0x8649, 0xB0B9, 0x864A, 0xB0BA, 0x864B, 0xB0BB, 0x864C, 0xB0BD, 0x864D, 0xB0BE, 0x864E, 0xB0BF, + 0x864F, 0xB0C0, 0x8650, 0xB0C1, 0x8651, 0xB0C2, 0x8652, 0xB0C3, 0x8653, 0xB0C6, 0x8654, 0xB0CA, 0x8655, 0xB0CB, 0x8656, 0xB0CC, + 0x8657, 0xB0CD, 0x8658, 0xB0CE, 0x8659, 0xB0CF, 0x865A, 0xB0D2, 0x8661, 0xB0D3, 0x8662, 0xB0D5, 0x8663, 0xB0D6, 0x8664, 0xB0D7, + 0x8665, 0xB0D9, 0x8666, 0xB0DA, 0x8667, 0xB0DB, 0x8668, 0xB0DC, 0x8669, 0xB0DD, 0x866A, 0xB0DE, 0x866B, 0xB0DF, 0x866C, 0xB0E1, + 0x866D, 0xB0E2, 0x866E, 0xB0E3, 0x866F, 0xB0E4, 0x8670, 0xB0E6, 0x8671, 0xB0E7, 0x8672, 0xB0E8, 0x8673, 0xB0E9, 0x8674, 0xB0EA, + 0x8675, 0xB0EB, 0x8676, 0xB0EC, 0x8677, 0xB0ED, 0x8678, 0xB0EE, 0x8679, 0xB0EF, 0x867A, 0xB0F0, 0x8681, 0xB0F1, 0x8682, 0xB0F2, + 0x8683, 0xB0F3, 0x8684, 0xB0F4, 0x8685, 0xB0F5, 0x8686, 0xB0F6, 0x8687, 0xB0F7, 0x8688, 0xB0F8, 0x8689, 0xB0F9, 0x868A, 0xB0FA, + 0x868B, 0xB0FB, 0x868C, 0xB0FC, 0x868D, 0xB0FD, 0x868E, 0xB0FE, 0x868F, 0xB0FF, 0x8690, 0xB100, 0x8691, 0xB101, 0x8692, 0xB102, + 0x8693, 0xB103, 0x8694, 0xB104, 0x8695, 0xB105, 0x8696, 0xB106, 0x8697, 0xB107, 0x8698, 0xB10A, 0x8699, 0xB10D, 0x869A, 0xB10E, + 0x869B, 0xB10F, 0x869C, 0xB111, 0x869D, 0xB114, 0x869E, 0xB115, 0x869F, 0xB116, 0x86A0, 0xB117, 0x86A1, 0xB11A, 0x86A2, 0xB11E, + 0x86A3, 0xB11F, 0x86A4, 0xB120, 0x86A5, 0xB121, 0x86A6, 0xB122, 0x86A7, 0xB126, 0x86A8, 0xB127, 0x86A9, 0xB129, 0x86AA, 0xB12A, + 0x86AB, 0xB12B, 0x86AC, 0xB12D, 0x86AD, 0xB12E, 0x86AE, 0xB12F, 0x86AF, 0xB130, 0x86B0, 0xB131, 0x86B1, 0xB132, 0x86B2, 0xB133, + 0x86B3, 0xB136, 0x86B4, 0xB13A, 0x86B5, 0xB13B, 0x86B6, 0xB13C, 0x86B7, 0xB13D, 0x86B8, 0xB13E, 0x86B9, 0xB13F, 0x86BA, 0xB142, + 0x86BB, 0xB143, 0x86BC, 0xB145, 0x86BD, 0xB146, 0x86BE, 0xB147, 0x86BF, 0xB149, 0x86C0, 0xB14A, 0x86C1, 0xB14B, 0x86C2, 0xB14C, + 0x86C3, 0xB14D, 0x86C4, 0xB14E, 0x86C5, 0xB14F, 0x86C6, 0xB152, 0x86C7, 0xB153, 0x86C8, 0xB156, 0x86C9, 0xB157, 0x86CA, 0xB159, + 0x86CB, 0xB15A, 0x86CC, 0xB15B, 0x86CD, 0xB15D, 0x86CE, 0xB15E, 0x86CF, 0xB15F, 0x86D0, 0xB161, 0x86D1, 0xB162, 0x86D2, 0xB163, + 0x86D3, 0xB164, 0x86D4, 0xB165, 0x86D5, 0xB166, 0x86D6, 0xB167, 0x86D7, 0xB168, 0x86D8, 0xB169, 0x86D9, 0xB16A, 0x86DA, 0xB16B, + 0x86DB, 0xB16C, 0x86DC, 0xB16D, 0x86DD, 0xB16E, 0x86DE, 0xB16F, 0x86DF, 0xB170, 0x86E0, 0xB171, 0x86E1, 0xB172, 0x86E2, 0xB173, + 0x86E3, 0xB174, 0x86E4, 0xB175, 0x86E5, 0xB176, 0x86E6, 0xB177, 0x86E7, 0xB17A, 0x86E8, 0xB17B, 0x86E9, 0xB17D, 0x86EA, 0xB17E, + 0x86EB, 0xB17F, 0x86EC, 0xB181, 0x86ED, 0xB183, 0x86EE, 0xB184, 0x86EF, 0xB185, 0x86F0, 0xB186, 0x86F1, 0xB187, 0x86F2, 0xB18A, + 0x86F3, 0xB18C, 0x86F4, 0xB18E, 0x86F5, 0xB18F, 0x86F6, 0xB190, 0x86F7, 0xB191, 0x86F8, 0xB195, 0x86F9, 0xB196, 0x86FA, 0xB197, + 0x86FB, 0xB199, 0x86FC, 0xB19A, 0x86FD, 0xB19B, 0x86FE, 0xB19D, 0x8741, 0xB19E, 0x8742, 0xB19F, 0x8743, 0xB1A0, 0x8744, 0xB1A1, + 0x8745, 0xB1A2, 0x8746, 0xB1A3, 0x8747, 0xB1A4, 0x8748, 0xB1A5, 0x8749, 0xB1A6, 0x874A, 0xB1A7, 0x874B, 0xB1A9, 0x874C, 0xB1AA, + 0x874D, 0xB1AB, 0x874E, 0xB1AC, 0x874F, 0xB1AD, 0x8750, 0xB1AE, 0x8751, 0xB1AF, 0x8752, 0xB1B0, 0x8753, 0xB1B1, 0x8754, 0xB1B2, + 0x8755, 0xB1B3, 0x8756, 0xB1B4, 0x8757, 0xB1B5, 0x8758, 0xB1B6, 0x8759, 0xB1B7, 0x875A, 0xB1B8, 0x8761, 0xB1B9, 0x8762, 0xB1BA, + 0x8763, 0xB1BB, 0x8764, 0xB1BC, 0x8765, 0xB1BD, 0x8766, 0xB1BE, 0x8767, 0xB1BF, 0x8768, 0xB1C0, 0x8769, 0xB1C1, 0x876A, 0xB1C2, + 0x876B, 0xB1C3, 0x876C, 0xB1C4, 0x876D, 0xB1C5, 0x876E, 0xB1C6, 0x876F, 0xB1C7, 0x8770, 0xB1C8, 0x8771, 0xB1C9, 0x8772, 0xB1CA, + 0x8773, 0xB1CB, 0x8774, 0xB1CD, 0x8775, 0xB1CE, 0x8776, 0xB1CF, 0x8777, 0xB1D1, 0x8778, 0xB1D2, 0x8779, 0xB1D3, 0x877A, 0xB1D5, + 0x8781, 0xB1D6, 0x8782, 0xB1D7, 0x8783, 0xB1D8, 0x8784, 0xB1D9, 0x8785, 0xB1DA, 0x8786, 0xB1DB, 0x8787, 0xB1DE, 0x8788, 0xB1E0, + 0x8789, 0xB1E1, 0x878A, 0xB1E2, 0x878B, 0xB1E3, 0x878C, 0xB1E4, 0x878D, 0xB1E5, 0x878E, 0xB1E6, 0x878F, 0xB1E7, 0x8790, 0xB1EA, + 0x8791, 0xB1EB, 0x8792, 0xB1ED, 0x8793, 0xB1EE, 0x8794, 0xB1EF, 0x8795, 0xB1F1, 0x8796, 0xB1F2, 0x8797, 0xB1F3, 0x8798, 0xB1F4, + 0x8799, 0xB1F5, 0x879A, 0xB1F6, 0x879B, 0xB1F7, 0x879C, 0xB1F8, 0x879D, 0xB1FA, 0x879E, 0xB1FC, 0x879F, 0xB1FE, 0x87A0, 0xB1FF, + 0x87A1, 0xB200, 0x87A2, 0xB201, 0x87A3, 0xB202, 0x87A4, 0xB203, 0x87A5, 0xB206, 0x87A6, 0xB207, 0x87A7, 0xB209, 0x87A8, 0xB20A, + 0x87A9, 0xB20D, 0x87AA, 0xB20E, 0x87AB, 0xB20F, 0x87AC, 0xB210, 0x87AD, 0xB211, 0x87AE, 0xB212, 0x87AF, 0xB213, 0x87B0, 0xB216, + 0x87B1, 0xB218, 0x87B2, 0xB21A, 0x87B3, 0xB21B, 0x87B4, 0xB21C, 0x87B5, 0xB21D, 0x87B6, 0xB21E, 0x87B7, 0xB21F, 0x87B8, 0xB221, + 0x87B9, 0xB222, 0x87BA, 0xB223, 0x87BB, 0xB224, 0x87BC, 0xB225, 0x87BD, 0xB226, 0x87BE, 0xB227, 0x87BF, 0xB228, 0x87C0, 0xB229, + 0x87C1, 0xB22A, 0x87C2, 0xB22B, 0x87C3, 0xB22C, 0x87C4, 0xB22D, 0x87C5, 0xB22E, 0x87C6, 0xB22F, 0x87C7, 0xB230, 0x87C8, 0xB231, + 0x87C9, 0xB232, 0x87CA, 0xB233, 0x87CB, 0xB235, 0x87CC, 0xB236, 0x87CD, 0xB237, 0x87CE, 0xB238, 0x87CF, 0xB239, 0x87D0, 0xB23A, + 0x87D1, 0xB23B, 0x87D2, 0xB23D, 0x87D3, 0xB23E, 0x87D4, 0xB23F, 0x87D5, 0xB240, 0x87D6, 0xB241, 0x87D7, 0xB242, 0x87D8, 0xB243, + 0x87D9, 0xB244, 0x87DA, 0xB245, 0x87DB, 0xB246, 0x87DC, 0xB247, 0x87DD, 0xB248, 0x87DE, 0xB249, 0x87DF, 0xB24A, 0x87E0, 0xB24B, + 0x87E1, 0xB24C, 0x87E2, 0xB24D, 0x87E3, 0xB24E, 0x87E4, 0xB24F, 0x87E5, 0xB250, 0x87E6, 0xB251, 0x87E7, 0xB252, 0x87E8, 0xB253, + 0x87E9, 0xB254, 0x87EA, 0xB255, 0x87EB, 0xB256, 0x87EC, 0xB257, 0x87ED, 0xB259, 0x87EE, 0xB25A, 0x87EF, 0xB25B, 0x87F0, 0xB25D, + 0x87F1, 0xB25E, 0x87F2, 0xB25F, 0x87F3, 0xB261, 0x87F4, 0xB262, 0x87F5, 0xB263, 0x87F6, 0xB264, 0x87F7, 0xB265, 0x87F8, 0xB266, + 0x87F9, 0xB267, 0x87FA, 0xB26A, 0x87FB, 0xB26B, 0x87FC, 0xB26C, 0x87FD, 0xB26D, 0x87FE, 0xB26E, 0x8841, 0xB26F, 0x8842, 0xB270, + 0x8843, 0xB271, 0x8844, 0xB272, 0x8845, 0xB273, 0x8846, 0xB276, 0x8847, 0xB277, 0x8848, 0xB278, 0x8849, 0xB279, 0x884A, 0xB27A, + 0x884B, 0xB27B, 0x884C, 0xB27D, 0x884D, 0xB27E, 0x884E, 0xB27F, 0x884F, 0xB280, 0x8850, 0xB281, 0x8851, 0xB282, 0x8852, 0xB283, + 0x8853, 0xB286, 0x8854, 0xB287, 0x8855, 0xB288, 0x8856, 0xB28A, 0x8857, 0xB28B, 0x8858, 0xB28C, 0x8859, 0xB28D, 0x885A, 0xB28E, + 0x8861, 0xB28F, 0x8862, 0xB292, 0x8863, 0xB293, 0x8864, 0xB295, 0x8865, 0xB296, 0x8866, 0xB297, 0x8867, 0xB29B, 0x8868, 0xB29C, + 0x8869, 0xB29D, 0x886A, 0xB29E, 0x886B, 0xB29F, 0x886C, 0xB2A2, 0x886D, 0xB2A4, 0x886E, 0xB2A7, 0x886F, 0xB2A8, 0x8870, 0xB2A9, + 0x8871, 0xB2AB, 0x8872, 0xB2AD, 0x8873, 0xB2AE, 0x8874, 0xB2AF, 0x8875, 0xB2B1, 0x8876, 0xB2B2, 0x8877, 0xB2B3, 0x8878, 0xB2B5, + 0x8879, 0xB2B6, 0x887A, 0xB2B7, 0x8881, 0xB2B8, 0x8882, 0xB2B9, 0x8883, 0xB2BA, 0x8884, 0xB2BB, 0x8885, 0xB2BC, 0x8886, 0xB2BD, + 0x8887, 0xB2BE, 0x8888, 0xB2BF, 0x8889, 0xB2C0, 0x888A, 0xB2C1, 0x888B, 0xB2C2, 0x888C, 0xB2C3, 0x888D, 0xB2C4, 0x888E, 0xB2C5, + 0x888F, 0xB2C6, 0x8890, 0xB2C7, 0x8891, 0xB2CA, 0x8892, 0xB2CB, 0x8893, 0xB2CD, 0x8894, 0xB2CE, 0x8895, 0xB2CF, 0x8896, 0xB2D1, + 0x8897, 0xB2D3, 0x8898, 0xB2D4, 0x8899, 0xB2D5, 0x889A, 0xB2D6, 0x889B, 0xB2D7, 0x889C, 0xB2DA, 0x889D, 0xB2DC, 0x889E, 0xB2DE, + 0x889F, 0xB2DF, 0x88A0, 0xB2E0, 0x88A1, 0xB2E1, 0x88A2, 0xB2E3, 0x88A3, 0xB2E7, 0x88A4, 0xB2E9, 0x88A5, 0xB2EA, 0x88A6, 0xB2F0, + 0x88A7, 0xB2F1, 0x88A8, 0xB2F2, 0x88A9, 0xB2F6, 0x88AA, 0xB2FC, 0x88AB, 0xB2FD, 0x88AC, 0xB2FE, 0x88AD, 0xB302, 0x88AE, 0xB303, + 0x88AF, 0xB305, 0x88B0, 0xB306, 0x88B1, 0xB307, 0x88B2, 0xB309, 0x88B3, 0xB30A, 0x88B4, 0xB30B, 0x88B5, 0xB30C, 0x88B6, 0xB30D, + 0x88B7, 0xB30E, 0x88B8, 0xB30F, 0x88B9, 0xB312, 0x88BA, 0xB316, 0x88BB, 0xB317, 0x88BC, 0xB318, 0x88BD, 0xB319, 0x88BE, 0xB31A, + 0x88BF, 0xB31B, 0x88C0, 0xB31D, 0x88C1, 0xB31E, 0x88C2, 0xB31F, 0x88C3, 0xB320, 0x88C4, 0xB321, 0x88C5, 0xB322, 0x88C6, 0xB323, + 0x88C7, 0xB324, 0x88C8, 0xB325, 0x88C9, 0xB326, 0x88CA, 0xB327, 0x88CB, 0xB328, 0x88CC, 0xB329, 0x88CD, 0xB32A, 0x88CE, 0xB32B, + 0x88CF, 0xB32C, 0x88D0, 0xB32D, 0x88D1, 0xB32E, 0x88D2, 0xB32F, 0x88D3, 0xB330, 0x88D4, 0xB331, 0x88D5, 0xB332, 0x88D6, 0xB333, + 0x88D7, 0xB334, 0x88D8, 0xB335, 0x88D9, 0xB336, 0x88DA, 0xB337, 0x88DB, 0xB338, 0x88DC, 0xB339, 0x88DD, 0xB33A, 0x88DE, 0xB33B, + 0x88DF, 0xB33C, 0x88E0, 0xB33D, 0x88E1, 0xB33E, 0x88E2, 0xB33F, 0x88E3, 0xB340, 0x88E4, 0xB341, 0x88E5, 0xB342, 0x88E6, 0xB343, + 0x88E7, 0xB344, 0x88E8, 0xB345, 0x88E9, 0xB346, 0x88EA, 0xB347, 0x88EB, 0xB348, 0x88EC, 0xB349, 0x88ED, 0xB34A, 0x88EE, 0xB34B, + 0x88EF, 0xB34C, 0x88F0, 0xB34D, 0x88F1, 0xB34E, 0x88F2, 0xB34F, 0x88F3, 0xB350, 0x88F4, 0xB351, 0x88F5, 0xB352, 0x88F6, 0xB353, + 0x88F7, 0xB357, 0x88F8, 0xB359, 0x88F9, 0xB35A, 0x88FA, 0xB35D, 0x88FB, 0xB360, 0x88FC, 0xB361, 0x88FD, 0xB362, 0x88FE, 0xB363, + 0x8941, 0xB366, 0x8942, 0xB368, 0x8943, 0xB36A, 0x8944, 0xB36C, 0x8945, 0xB36D, 0x8946, 0xB36F, 0x8947, 0xB372, 0x8948, 0xB373, + 0x8949, 0xB375, 0x894A, 0xB376, 0x894B, 0xB377, 0x894C, 0xB379, 0x894D, 0xB37A, 0x894E, 0xB37B, 0x894F, 0xB37C, 0x8950, 0xB37D, + 0x8951, 0xB37E, 0x8952, 0xB37F, 0x8953, 0xB382, 0x8954, 0xB386, 0x8955, 0xB387, 0x8956, 0xB388, 0x8957, 0xB389, 0x8958, 0xB38A, + 0x8959, 0xB38B, 0x895A, 0xB38D, 0x8961, 0xB38E, 0x8962, 0xB38F, 0x8963, 0xB391, 0x8964, 0xB392, 0x8965, 0xB393, 0x8966, 0xB395, + 0x8967, 0xB396, 0x8968, 0xB397, 0x8969, 0xB398, 0x896A, 0xB399, 0x896B, 0xB39A, 0x896C, 0xB39B, 0x896D, 0xB39C, 0x896E, 0xB39D, + 0x896F, 0xB39E, 0x8970, 0xB39F, 0x8971, 0xB3A2, 0x8972, 0xB3A3, 0x8973, 0xB3A4, 0x8974, 0xB3A5, 0x8975, 0xB3A6, 0x8976, 0xB3A7, + 0x8977, 0xB3A9, 0x8978, 0xB3AA, 0x8979, 0xB3AB, 0x897A, 0xB3AD, 0x8981, 0xB3AE, 0x8982, 0xB3AF, 0x8983, 0xB3B0, 0x8984, 0xB3B1, + 0x8985, 0xB3B2, 0x8986, 0xB3B3, 0x8987, 0xB3B4, 0x8988, 0xB3B5, 0x8989, 0xB3B6, 0x898A, 0xB3B7, 0x898B, 0xB3B8, 0x898C, 0xB3B9, + 0x898D, 0xB3BA, 0x898E, 0xB3BB, 0x898F, 0xB3BC, 0x8990, 0xB3BD, 0x8991, 0xB3BE, 0x8992, 0xB3BF, 0x8993, 0xB3C0, 0x8994, 0xB3C1, + 0x8995, 0xB3C2, 0x8996, 0xB3C3, 0x8997, 0xB3C6, 0x8998, 0xB3C7, 0x8999, 0xB3C9, 0x899A, 0xB3CA, 0x899B, 0xB3CD, 0x899C, 0xB3CF, + 0x899D, 0xB3D1, 0x899E, 0xB3D2, 0x899F, 0xB3D3, 0x89A0, 0xB3D6, 0x89A1, 0xB3D8, 0x89A2, 0xB3DA, 0x89A3, 0xB3DC, 0x89A4, 0xB3DE, + 0x89A5, 0xB3DF, 0x89A6, 0xB3E1, 0x89A7, 0xB3E2, 0x89A8, 0xB3E3, 0x89A9, 0xB3E5, 0x89AA, 0xB3E6, 0x89AB, 0xB3E7, 0x89AC, 0xB3E9, + 0x89AD, 0xB3EA, 0x89AE, 0xB3EB, 0x89AF, 0xB3EC, 0x89B0, 0xB3ED, 0x89B1, 0xB3EE, 0x89B2, 0xB3EF, 0x89B3, 0xB3F0, 0x89B4, 0xB3F1, + 0x89B5, 0xB3F2, 0x89B6, 0xB3F3, 0x89B7, 0xB3F4, 0x89B8, 0xB3F5, 0x89B9, 0xB3F6, 0x89BA, 0xB3F7, 0x89BB, 0xB3F8, 0x89BC, 0xB3F9, + 0x89BD, 0xB3FA, 0x89BE, 0xB3FB, 0x89BF, 0xB3FD, 0x89C0, 0xB3FE, 0x89C1, 0xB3FF, 0x89C2, 0xB400, 0x89C3, 0xB401, 0x89C4, 0xB402, + 0x89C5, 0xB403, 0x89C6, 0xB404, 0x89C7, 0xB405, 0x89C8, 0xB406, 0x89C9, 0xB407, 0x89CA, 0xB408, 0x89CB, 0xB409, 0x89CC, 0xB40A, + 0x89CD, 0xB40B, 0x89CE, 0xB40C, 0x89CF, 0xB40D, 0x89D0, 0xB40E, 0x89D1, 0xB40F, 0x89D2, 0xB411, 0x89D3, 0xB412, 0x89D4, 0xB413, + 0x89D5, 0xB414, 0x89D6, 0xB415, 0x89D7, 0xB416, 0x89D8, 0xB417, 0x89D9, 0xB419, 0x89DA, 0xB41A, 0x89DB, 0xB41B, 0x89DC, 0xB41D, + 0x89DD, 0xB41E, 0x89DE, 0xB41F, 0x89DF, 0xB421, 0x89E0, 0xB422, 0x89E1, 0xB423, 0x89E2, 0xB424, 0x89E3, 0xB425, 0x89E4, 0xB426, + 0x89E5, 0xB427, 0x89E6, 0xB42A, 0x89E7, 0xB42C, 0x89E8, 0xB42D, 0x89E9, 0xB42E, 0x89EA, 0xB42F, 0x89EB, 0xB430, 0x89EC, 0xB431, + 0x89ED, 0xB432, 0x89EE, 0xB433, 0x89EF, 0xB435, 0x89F0, 0xB436, 0x89F1, 0xB437, 0x89F2, 0xB438, 0x89F3, 0xB439, 0x89F4, 0xB43A, + 0x89F5, 0xB43B, 0x89F6, 0xB43C, 0x89F7, 0xB43D, 0x89F8, 0xB43E, 0x89F9, 0xB43F, 0x89FA, 0xB440, 0x89FB, 0xB441, 0x89FC, 0xB442, + 0x89FD, 0xB443, 0x89FE, 0xB444, 0x8A41, 0xB445, 0x8A42, 0xB446, 0x8A43, 0xB447, 0x8A44, 0xB448, 0x8A45, 0xB449, 0x8A46, 0xB44A, + 0x8A47, 0xB44B, 0x8A48, 0xB44C, 0x8A49, 0xB44D, 0x8A4A, 0xB44E, 0x8A4B, 0xB44F, 0x8A4C, 0xB452, 0x8A4D, 0xB453, 0x8A4E, 0xB455, + 0x8A4F, 0xB456, 0x8A50, 0xB457, 0x8A51, 0xB459, 0x8A52, 0xB45A, 0x8A53, 0xB45B, 0x8A54, 0xB45C, 0x8A55, 0xB45D, 0x8A56, 0xB45E, + 0x8A57, 0xB45F, 0x8A58, 0xB462, 0x8A59, 0xB464, 0x8A5A, 0xB466, 0x8A61, 0xB467, 0x8A62, 0xB468, 0x8A63, 0xB469, 0x8A64, 0xB46A, + 0x8A65, 0xB46B, 0x8A66, 0xB46D, 0x8A67, 0xB46E, 0x8A68, 0xB46F, 0x8A69, 0xB470, 0x8A6A, 0xB471, 0x8A6B, 0xB472, 0x8A6C, 0xB473, + 0x8A6D, 0xB474, 0x8A6E, 0xB475, 0x8A6F, 0xB476, 0x8A70, 0xB477, 0x8A71, 0xB478, 0x8A72, 0xB479, 0x8A73, 0xB47A, 0x8A74, 0xB47B, + 0x8A75, 0xB47C, 0x8A76, 0xB47D, 0x8A77, 0xB47E, 0x8A78, 0xB47F, 0x8A79, 0xB481, 0x8A7A, 0xB482, 0x8A81, 0xB483, 0x8A82, 0xB484, + 0x8A83, 0xB485, 0x8A84, 0xB486, 0x8A85, 0xB487, 0x8A86, 0xB489, 0x8A87, 0xB48A, 0x8A88, 0xB48B, 0x8A89, 0xB48C, 0x8A8A, 0xB48D, + 0x8A8B, 0xB48E, 0x8A8C, 0xB48F, 0x8A8D, 0xB490, 0x8A8E, 0xB491, 0x8A8F, 0xB492, 0x8A90, 0xB493, 0x8A91, 0xB494, 0x8A92, 0xB495, + 0x8A93, 0xB496, 0x8A94, 0xB497, 0x8A95, 0xB498, 0x8A96, 0xB499, 0x8A97, 0xB49A, 0x8A98, 0xB49B, 0x8A99, 0xB49C, 0x8A9A, 0xB49E, + 0x8A9B, 0xB49F, 0x8A9C, 0xB4A0, 0x8A9D, 0xB4A1, 0x8A9E, 0xB4A2, 0x8A9F, 0xB4A3, 0x8AA0, 0xB4A5, 0x8AA1, 0xB4A6, 0x8AA2, 0xB4A7, + 0x8AA3, 0xB4A9, 0x8AA4, 0xB4AA, 0x8AA5, 0xB4AB, 0x8AA6, 0xB4AD, 0x8AA7, 0xB4AE, 0x8AA8, 0xB4AF, 0x8AA9, 0xB4B0, 0x8AAA, 0xB4B1, + 0x8AAB, 0xB4B2, 0x8AAC, 0xB4B3, 0x8AAD, 0xB4B4, 0x8AAE, 0xB4B6, 0x8AAF, 0xB4B8, 0x8AB0, 0xB4BA, 0x8AB1, 0xB4BB, 0x8AB2, 0xB4BC, + 0x8AB3, 0xB4BD, 0x8AB4, 0xB4BE, 0x8AB5, 0xB4BF, 0x8AB6, 0xB4C1, 0x8AB7, 0xB4C2, 0x8AB8, 0xB4C3, 0x8AB9, 0xB4C5, 0x8ABA, 0xB4C6, + 0x8ABB, 0xB4C7, 0x8ABC, 0xB4C9, 0x8ABD, 0xB4CA, 0x8ABE, 0xB4CB, 0x8ABF, 0xB4CC, 0x8AC0, 0xB4CD, 0x8AC1, 0xB4CE, 0x8AC2, 0xB4CF, + 0x8AC3, 0xB4D1, 0x8AC4, 0xB4D2, 0x8AC5, 0xB4D3, 0x8AC6, 0xB4D4, 0x8AC7, 0xB4D6, 0x8AC8, 0xB4D7, 0x8AC9, 0xB4D8, 0x8ACA, 0xB4D9, + 0x8ACB, 0xB4DA, 0x8ACC, 0xB4DB, 0x8ACD, 0xB4DE, 0x8ACE, 0xB4DF, 0x8ACF, 0xB4E1, 0x8AD0, 0xB4E2, 0x8AD1, 0xB4E5, 0x8AD2, 0xB4E7, + 0x8AD3, 0xB4E8, 0x8AD4, 0xB4E9, 0x8AD5, 0xB4EA, 0x8AD6, 0xB4EB, 0x8AD7, 0xB4EE, 0x8AD8, 0xB4F0, 0x8AD9, 0xB4F2, 0x8ADA, 0xB4F3, + 0x8ADB, 0xB4F4, 0x8ADC, 0xB4F5, 0x8ADD, 0xB4F6, 0x8ADE, 0xB4F7, 0x8ADF, 0xB4F9, 0x8AE0, 0xB4FA, 0x8AE1, 0xB4FB, 0x8AE2, 0xB4FC, + 0x8AE3, 0xB4FD, 0x8AE4, 0xB4FE, 0x8AE5, 0xB4FF, 0x8AE6, 0xB500, 0x8AE7, 0xB501, 0x8AE8, 0xB502, 0x8AE9, 0xB503, 0x8AEA, 0xB504, + 0x8AEB, 0xB505, 0x8AEC, 0xB506, 0x8AED, 0xB507, 0x8AEE, 0xB508, 0x8AEF, 0xB509, 0x8AF0, 0xB50A, 0x8AF1, 0xB50B, 0x8AF2, 0xB50C, + 0x8AF3, 0xB50D, 0x8AF4, 0xB50E, 0x8AF5, 0xB50F, 0x8AF6, 0xB510, 0x8AF7, 0xB511, 0x8AF8, 0xB512, 0x8AF9, 0xB513, 0x8AFA, 0xB516, + 0x8AFB, 0xB517, 0x8AFC, 0xB519, 0x8AFD, 0xB51A, 0x8AFE, 0xB51D, 0x8B41, 0xB51E, 0x8B42, 0xB51F, 0x8B43, 0xB520, 0x8B44, 0xB521, + 0x8B45, 0xB522, 0x8B46, 0xB523, 0x8B47, 0xB526, 0x8B48, 0xB52B, 0x8B49, 0xB52C, 0x8B4A, 0xB52D, 0x8B4B, 0xB52E, 0x8B4C, 0xB52F, + 0x8B4D, 0xB532, 0x8B4E, 0xB533, 0x8B4F, 0xB535, 0x8B50, 0xB536, 0x8B51, 0xB537, 0x8B52, 0xB539, 0x8B53, 0xB53A, 0x8B54, 0xB53B, + 0x8B55, 0xB53C, 0x8B56, 0xB53D, 0x8B57, 0xB53E, 0x8B58, 0xB53F, 0x8B59, 0xB542, 0x8B5A, 0xB546, 0x8B61, 0xB547, 0x8B62, 0xB548, + 0x8B63, 0xB549, 0x8B64, 0xB54A, 0x8B65, 0xB54E, 0x8B66, 0xB54F, 0x8B67, 0xB551, 0x8B68, 0xB552, 0x8B69, 0xB553, 0x8B6A, 0xB555, + 0x8B6B, 0xB556, 0x8B6C, 0xB557, 0x8B6D, 0xB558, 0x8B6E, 0xB559, 0x8B6F, 0xB55A, 0x8B70, 0xB55B, 0x8B71, 0xB55E, 0x8B72, 0xB562, + 0x8B73, 0xB563, 0x8B74, 0xB564, 0x8B75, 0xB565, 0x8B76, 0xB566, 0x8B77, 0xB567, 0x8B78, 0xB568, 0x8B79, 0xB569, 0x8B7A, 0xB56A, + 0x8B81, 0xB56B, 0x8B82, 0xB56C, 0x8B83, 0xB56D, 0x8B84, 0xB56E, 0x8B85, 0xB56F, 0x8B86, 0xB570, 0x8B87, 0xB571, 0x8B88, 0xB572, + 0x8B89, 0xB573, 0x8B8A, 0xB574, 0x8B8B, 0xB575, 0x8B8C, 0xB576, 0x8B8D, 0xB577, 0x8B8E, 0xB578, 0x8B8F, 0xB579, 0x8B90, 0xB57A, + 0x8B91, 0xB57B, 0x8B92, 0xB57C, 0x8B93, 0xB57D, 0x8B94, 0xB57E, 0x8B95, 0xB57F, 0x8B96, 0xB580, 0x8B97, 0xB581, 0x8B98, 0xB582, + 0x8B99, 0xB583, 0x8B9A, 0xB584, 0x8B9B, 0xB585, 0x8B9C, 0xB586, 0x8B9D, 0xB587, 0x8B9E, 0xB588, 0x8B9F, 0xB589, 0x8BA0, 0xB58A, + 0x8BA1, 0xB58B, 0x8BA2, 0xB58C, 0x8BA3, 0xB58D, 0x8BA4, 0xB58E, 0x8BA5, 0xB58F, 0x8BA6, 0xB590, 0x8BA7, 0xB591, 0x8BA8, 0xB592, + 0x8BA9, 0xB593, 0x8BAA, 0xB594, 0x8BAB, 0xB595, 0x8BAC, 0xB596, 0x8BAD, 0xB597, 0x8BAE, 0xB598, 0x8BAF, 0xB599, 0x8BB0, 0xB59A, + 0x8BB1, 0xB59B, 0x8BB2, 0xB59C, 0x8BB3, 0xB59D, 0x8BB4, 0xB59E, 0x8BB5, 0xB59F, 0x8BB6, 0xB5A2, 0x8BB7, 0xB5A3, 0x8BB8, 0xB5A5, + 0x8BB9, 0xB5A6, 0x8BBA, 0xB5A7, 0x8BBB, 0xB5A9, 0x8BBC, 0xB5AC, 0x8BBD, 0xB5AD, 0x8BBE, 0xB5AE, 0x8BBF, 0xB5AF, 0x8BC0, 0xB5B2, + 0x8BC1, 0xB5B6, 0x8BC2, 0xB5B7, 0x8BC3, 0xB5B8, 0x8BC4, 0xB5B9, 0x8BC5, 0xB5BA, 0x8BC6, 0xB5BE, 0x8BC7, 0xB5BF, 0x8BC8, 0xB5C1, + 0x8BC9, 0xB5C2, 0x8BCA, 0xB5C3, 0x8BCB, 0xB5C5, 0x8BCC, 0xB5C6, 0x8BCD, 0xB5C7, 0x8BCE, 0xB5C8, 0x8BCF, 0xB5C9, 0x8BD0, 0xB5CA, + 0x8BD1, 0xB5CB, 0x8BD2, 0xB5CE, 0x8BD3, 0xB5D2, 0x8BD4, 0xB5D3, 0x8BD5, 0xB5D4, 0x8BD6, 0xB5D5, 0x8BD7, 0xB5D6, 0x8BD8, 0xB5D7, + 0x8BD9, 0xB5D9, 0x8BDA, 0xB5DA, 0x8BDB, 0xB5DB, 0x8BDC, 0xB5DC, 0x8BDD, 0xB5DD, 0x8BDE, 0xB5DE, 0x8BDF, 0xB5DF, 0x8BE0, 0xB5E0, + 0x8BE1, 0xB5E1, 0x8BE2, 0xB5E2, 0x8BE3, 0xB5E3, 0x8BE4, 0xB5E4, 0x8BE5, 0xB5E5, 0x8BE6, 0xB5E6, 0x8BE7, 0xB5E7, 0x8BE8, 0xB5E8, + 0x8BE9, 0xB5E9, 0x8BEA, 0xB5EA, 0x8BEB, 0xB5EB, 0x8BEC, 0xB5ED, 0x8BED, 0xB5EE, 0x8BEE, 0xB5EF, 0x8BEF, 0xB5F0, 0x8BF0, 0xB5F1, + 0x8BF1, 0xB5F2, 0x8BF2, 0xB5F3, 0x8BF3, 0xB5F4, 0x8BF4, 0xB5F5, 0x8BF5, 0xB5F6, 0x8BF6, 0xB5F7, 0x8BF7, 0xB5F8, 0x8BF8, 0xB5F9, + 0x8BF9, 0xB5FA, 0x8BFA, 0xB5FB, 0x8BFB, 0xB5FC, 0x8BFC, 0xB5FD, 0x8BFD, 0xB5FE, 0x8BFE, 0xB5FF, 0x8C41, 0xB600, 0x8C42, 0xB601, + 0x8C43, 0xB602, 0x8C44, 0xB603, 0x8C45, 0xB604, 0x8C46, 0xB605, 0x8C47, 0xB606, 0x8C48, 0xB607, 0x8C49, 0xB608, 0x8C4A, 0xB609, + 0x8C4B, 0xB60A, 0x8C4C, 0xB60B, 0x8C4D, 0xB60C, 0x8C4E, 0xB60D, 0x8C4F, 0xB60E, 0x8C50, 0xB60F, 0x8C51, 0xB612, 0x8C52, 0xB613, + 0x8C53, 0xB615, 0x8C54, 0xB616, 0x8C55, 0xB617, 0x8C56, 0xB619, 0x8C57, 0xB61A, 0x8C58, 0xB61B, 0x8C59, 0xB61C, 0x8C5A, 0xB61D, + 0x8C61, 0xB61E, 0x8C62, 0xB61F, 0x8C63, 0xB620, 0x8C64, 0xB621, 0x8C65, 0xB622, 0x8C66, 0xB623, 0x8C67, 0xB624, 0x8C68, 0xB626, + 0x8C69, 0xB627, 0x8C6A, 0xB628, 0x8C6B, 0xB629, 0x8C6C, 0xB62A, 0x8C6D, 0xB62B, 0x8C6E, 0xB62D, 0x8C6F, 0xB62E, 0x8C70, 0xB62F, + 0x8C71, 0xB630, 0x8C72, 0xB631, 0x8C73, 0xB632, 0x8C74, 0xB633, 0x8C75, 0xB635, 0x8C76, 0xB636, 0x8C77, 0xB637, 0x8C78, 0xB638, + 0x8C79, 0xB639, 0x8C7A, 0xB63A, 0x8C81, 0xB63B, 0x8C82, 0xB63C, 0x8C83, 0xB63D, 0x8C84, 0xB63E, 0x8C85, 0xB63F, 0x8C86, 0xB640, + 0x8C87, 0xB641, 0x8C88, 0xB642, 0x8C89, 0xB643, 0x8C8A, 0xB644, 0x8C8B, 0xB645, 0x8C8C, 0xB646, 0x8C8D, 0xB647, 0x8C8E, 0xB649, + 0x8C8F, 0xB64A, 0x8C90, 0xB64B, 0x8C91, 0xB64C, 0x8C92, 0xB64D, 0x8C93, 0xB64E, 0x8C94, 0xB64F, 0x8C95, 0xB650, 0x8C96, 0xB651, + 0x8C97, 0xB652, 0x8C98, 0xB653, 0x8C99, 0xB654, 0x8C9A, 0xB655, 0x8C9B, 0xB656, 0x8C9C, 0xB657, 0x8C9D, 0xB658, 0x8C9E, 0xB659, + 0x8C9F, 0xB65A, 0x8CA0, 0xB65B, 0x8CA1, 0xB65C, 0x8CA2, 0xB65D, 0x8CA3, 0xB65E, 0x8CA4, 0xB65F, 0x8CA5, 0xB660, 0x8CA6, 0xB661, + 0x8CA7, 0xB662, 0x8CA8, 0xB663, 0x8CA9, 0xB665, 0x8CAA, 0xB666, 0x8CAB, 0xB667, 0x8CAC, 0xB669, 0x8CAD, 0xB66A, 0x8CAE, 0xB66B, + 0x8CAF, 0xB66C, 0x8CB0, 0xB66D, 0x8CB1, 0xB66E, 0x8CB2, 0xB66F, 0x8CB3, 0xB670, 0x8CB4, 0xB671, 0x8CB5, 0xB672, 0x8CB6, 0xB673, + 0x8CB7, 0xB674, 0x8CB8, 0xB675, 0x8CB9, 0xB676, 0x8CBA, 0xB677, 0x8CBB, 0xB678, 0x8CBC, 0xB679, 0x8CBD, 0xB67A, 0x8CBE, 0xB67B, + 0x8CBF, 0xB67C, 0x8CC0, 0xB67D, 0x8CC1, 0xB67E, 0x8CC2, 0xB67F, 0x8CC3, 0xB680, 0x8CC4, 0xB681, 0x8CC5, 0xB682, 0x8CC6, 0xB683, + 0x8CC7, 0xB684, 0x8CC8, 0xB685, 0x8CC9, 0xB686, 0x8CCA, 0xB687, 0x8CCB, 0xB688, 0x8CCC, 0xB689, 0x8CCD, 0xB68A, 0x8CCE, 0xB68B, + 0x8CCF, 0xB68C, 0x8CD0, 0xB68D, 0x8CD1, 0xB68E, 0x8CD2, 0xB68F, 0x8CD3, 0xB690, 0x8CD4, 0xB691, 0x8CD5, 0xB692, 0x8CD6, 0xB693, + 0x8CD7, 0xB694, 0x8CD8, 0xB695, 0x8CD9, 0xB696, 0x8CDA, 0xB697, 0x8CDB, 0xB698, 0x8CDC, 0xB699, 0x8CDD, 0xB69A, 0x8CDE, 0xB69B, + 0x8CDF, 0xB69E, 0x8CE0, 0xB69F, 0x8CE1, 0xB6A1, 0x8CE2, 0xB6A2, 0x8CE3, 0xB6A3, 0x8CE4, 0xB6A5, 0x8CE5, 0xB6A6, 0x8CE6, 0xB6A7, + 0x8CE7, 0xB6A8, 0x8CE8, 0xB6A9, 0x8CE9, 0xB6AA, 0x8CEA, 0xB6AD, 0x8CEB, 0xB6AE, 0x8CEC, 0xB6AF, 0x8CED, 0xB6B0, 0x8CEE, 0xB6B2, + 0x8CEF, 0xB6B3, 0x8CF0, 0xB6B4, 0x8CF1, 0xB6B5, 0x8CF2, 0xB6B6, 0x8CF3, 0xB6B7, 0x8CF4, 0xB6B8, 0x8CF5, 0xB6B9, 0x8CF6, 0xB6BA, + 0x8CF7, 0xB6BB, 0x8CF8, 0xB6BC, 0x8CF9, 0xB6BD, 0x8CFA, 0xB6BE, 0x8CFB, 0xB6BF, 0x8CFC, 0xB6C0, 0x8CFD, 0xB6C1, 0x8CFE, 0xB6C2, + 0x8D41, 0xB6C3, 0x8D42, 0xB6C4, 0x8D43, 0xB6C5, 0x8D44, 0xB6C6, 0x8D45, 0xB6C7, 0x8D46, 0xB6C8, 0x8D47, 0xB6C9, 0x8D48, 0xB6CA, + 0x8D49, 0xB6CB, 0x8D4A, 0xB6CC, 0x8D4B, 0xB6CD, 0x8D4C, 0xB6CE, 0x8D4D, 0xB6CF, 0x8D4E, 0xB6D0, 0x8D4F, 0xB6D1, 0x8D50, 0xB6D2, + 0x8D51, 0xB6D3, 0x8D52, 0xB6D5, 0x8D53, 0xB6D6, 0x8D54, 0xB6D7, 0x8D55, 0xB6D8, 0x8D56, 0xB6D9, 0x8D57, 0xB6DA, 0x8D58, 0xB6DB, + 0x8D59, 0xB6DC, 0x8D5A, 0xB6DD, 0x8D61, 0xB6DE, 0x8D62, 0xB6DF, 0x8D63, 0xB6E0, 0x8D64, 0xB6E1, 0x8D65, 0xB6E2, 0x8D66, 0xB6E3, + 0x8D67, 0xB6E4, 0x8D68, 0xB6E5, 0x8D69, 0xB6E6, 0x8D6A, 0xB6E7, 0x8D6B, 0xB6E8, 0x8D6C, 0xB6E9, 0x8D6D, 0xB6EA, 0x8D6E, 0xB6EB, + 0x8D6F, 0xB6EC, 0x8D70, 0xB6ED, 0x8D71, 0xB6EE, 0x8D72, 0xB6EF, 0x8D73, 0xB6F1, 0x8D74, 0xB6F2, 0x8D75, 0xB6F3, 0x8D76, 0xB6F5, + 0x8D77, 0xB6F6, 0x8D78, 0xB6F7, 0x8D79, 0xB6F9, 0x8D7A, 0xB6FA, 0x8D81, 0xB6FB, 0x8D82, 0xB6FC, 0x8D83, 0xB6FD, 0x8D84, 0xB6FE, + 0x8D85, 0xB6FF, 0x8D86, 0xB702, 0x8D87, 0xB703, 0x8D88, 0xB704, 0x8D89, 0xB706, 0x8D8A, 0xB707, 0x8D8B, 0xB708, 0x8D8C, 0xB709, + 0x8D8D, 0xB70A, 0x8D8E, 0xB70B, 0x8D8F, 0xB70C, 0x8D90, 0xB70D, 0x8D91, 0xB70E, 0x8D92, 0xB70F, 0x8D93, 0xB710, 0x8D94, 0xB711, + 0x8D95, 0xB712, 0x8D96, 0xB713, 0x8D97, 0xB714, 0x8D98, 0xB715, 0x8D99, 0xB716, 0x8D9A, 0xB717, 0x8D9B, 0xB718, 0x8D9C, 0xB719, + 0x8D9D, 0xB71A, 0x8D9E, 0xB71B, 0x8D9F, 0xB71C, 0x8DA0, 0xB71D, 0x8DA1, 0xB71E, 0x8DA2, 0xB71F, 0x8DA3, 0xB720, 0x8DA4, 0xB721, + 0x8DA5, 0xB722, 0x8DA6, 0xB723, 0x8DA7, 0xB724, 0x8DA8, 0xB725, 0x8DA9, 0xB726, 0x8DAA, 0xB727, 0x8DAB, 0xB72A, 0x8DAC, 0xB72B, + 0x8DAD, 0xB72D, 0x8DAE, 0xB72E, 0x8DAF, 0xB731, 0x8DB0, 0xB732, 0x8DB1, 0xB733, 0x8DB2, 0xB734, 0x8DB3, 0xB735, 0x8DB4, 0xB736, + 0x8DB5, 0xB737, 0x8DB6, 0xB73A, 0x8DB7, 0xB73C, 0x8DB8, 0xB73D, 0x8DB9, 0xB73E, 0x8DBA, 0xB73F, 0x8DBB, 0xB740, 0x8DBC, 0xB741, + 0x8DBD, 0xB742, 0x8DBE, 0xB743, 0x8DBF, 0xB745, 0x8DC0, 0xB746, 0x8DC1, 0xB747, 0x8DC2, 0xB749, 0x8DC3, 0xB74A, 0x8DC4, 0xB74B, + 0x8DC5, 0xB74D, 0x8DC6, 0xB74E, 0x8DC7, 0xB74F, 0x8DC8, 0xB750, 0x8DC9, 0xB751, 0x8DCA, 0xB752, 0x8DCB, 0xB753, 0x8DCC, 0xB756, + 0x8DCD, 0xB757, 0x8DCE, 0xB758, 0x8DCF, 0xB759, 0x8DD0, 0xB75A, 0x8DD1, 0xB75B, 0x8DD2, 0xB75C, 0x8DD3, 0xB75D, 0x8DD4, 0xB75E, + 0x8DD5, 0xB75F, 0x8DD6, 0xB761, 0x8DD7, 0xB762, 0x8DD8, 0xB763, 0x8DD9, 0xB765, 0x8DDA, 0xB766, 0x8DDB, 0xB767, 0x8DDC, 0xB769, + 0x8DDD, 0xB76A, 0x8DDE, 0xB76B, 0x8DDF, 0xB76C, 0x8DE0, 0xB76D, 0x8DE1, 0xB76E, 0x8DE2, 0xB76F, 0x8DE3, 0xB772, 0x8DE4, 0xB774, + 0x8DE5, 0xB776, 0x8DE6, 0xB777, 0x8DE7, 0xB778, 0x8DE8, 0xB779, 0x8DE9, 0xB77A, 0x8DEA, 0xB77B, 0x8DEB, 0xB77E, 0x8DEC, 0xB77F, + 0x8DED, 0xB781, 0x8DEE, 0xB782, 0x8DEF, 0xB783, 0x8DF0, 0xB785, 0x8DF1, 0xB786, 0x8DF2, 0xB787, 0x8DF3, 0xB788, 0x8DF4, 0xB789, + 0x8DF5, 0xB78A, 0x8DF6, 0xB78B, 0x8DF7, 0xB78E, 0x8DF8, 0xB793, 0x8DF9, 0xB794, 0x8DFA, 0xB795, 0x8DFB, 0xB79A, 0x8DFC, 0xB79B, + 0x8DFD, 0xB79D, 0x8DFE, 0xB79E, 0x8E41, 0xB79F, 0x8E42, 0xB7A1, 0x8E43, 0xB7A2, 0x8E44, 0xB7A3, 0x8E45, 0xB7A4, 0x8E46, 0xB7A5, + 0x8E47, 0xB7A6, 0x8E48, 0xB7A7, 0x8E49, 0xB7AA, 0x8E4A, 0xB7AE, 0x8E4B, 0xB7AF, 0x8E4C, 0xB7B0, 0x8E4D, 0xB7B1, 0x8E4E, 0xB7B2, + 0x8E4F, 0xB7B3, 0x8E50, 0xB7B6, 0x8E51, 0xB7B7, 0x8E52, 0xB7B9, 0x8E53, 0xB7BA, 0x8E54, 0xB7BB, 0x8E55, 0xB7BC, 0x8E56, 0xB7BD, + 0x8E57, 0xB7BE, 0x8E58, 0xB7BF, 0x8E59, 0xB7C0, 0x8E5A, 0xB7C1, 0x8E61, 0xB7C2, 0x8E62, 0xB7C3, 0x8E63, 0xB7C4, 0x8E64, 0xB7C5, + 0x8E65, 0xB7C6, 0x8E66, 0xB7C8, 0x8E67, 0xB7CA, 0x8E68, 0xB7CB, 0x8E69, 0xB7CC, 0x8E6A, 0xB7CD, 0x8E6B, 0xB7CE, 0x8E6C, 0xB7CF, + 0x8E6D, 0xB7D0, 0x8E6E, 0xB7D1, 0x8E6F, 0xB7D2, 0x8E70, 0xB7D3, 0x8E71, 0xB7D4, 0x8E72, 0xB7D5, 0x8E73, 0xB7D6, 0x8E74, 0xB7D7, + 0x8E75, 0xB7D8, 0x8E76, 0xB7D9, 0x8E77, 0xB7DA, 0x8E78, 0xB7DB, 0x8E79, 0xB7DC, 0x8E7A, 0xB7DD, 0x8E81, 0xB7DE, 0x8E82, 0xB7DF, + 0x8E83, 0xB7E0, 0x8E84, 0xB7E1, 0x8E85, 0xB7E2, 0x8E86, 0xB7E3, 0x8E87, 0xB7E4, 0x8E88, 0xB7E5, 0x8E89, 0xB7E6, 0x8E8A, 0xB7E7, + 0x8E8B, 0xB7E8, 0x8E8C, 0xB7E9, 0x8E8D, 0xB7EA, 0x8E8E, 0xB7EB, 0x8E8F, 0xB7EE, 0x8E90, 0xB7EF, 0x8E91, 0xB7F1, 0x8E92, 0xB7F2, + 0x8E93, 0xB7F3, 0x8E94, 0xB7F5, 0x8E95, 0xB7F6, 0x8E96, 0xB7F7, 0x8E97, 0xB7F8, 0x8E98, 0xB7F9, 0x8E99, 0xB7FA, 0x8E9A, 0xB7FB, + 0x8E9B, 0xB7FE, 0x8E9C, 0xB802, 0x8E9D, 0xB803, 0x8E9E, 0xB804, 0x8E9F, 0xB805, 0x8EA0, 0xB806, 0x8EA1, 0xB80A, 0x8EA2, 0xB80B, + 0x8EA3, 0xB80D, 0x8EA4, 0xB80E, 0x8EA5, 0xB80F, 0x8EA6, 0xB811, 0x8EA7, 0xB812, 0x8EA8, 0xB813, 0x8EA9, 0xB814, 0x8EAA, 0xB815, + 0x8EAB, 0xB816, 0x8EAC, 0xB817, 0x8EAD, 0xB81A, 0x8EAE, 0xB81C, 0x8EAF, 0xB81E, 0x8EB0, 0xB81F, 0x8EB1, 0xB820, 0x8EB2, 0xB821, + 0x8EB3, 0xB822, 0x8EB4, 0xB823, 0x8EB5, 0xB826, 0x8EB6, 0xB827, 0x8EB7, 0xB829, 0x8EB8, 0xB82A, 0x8EB9, 0xB82B, 0x8EBA, 0xB82D, + 0x8EBB, 0xB82E, 0x8EBC, 0xB82F, 0x8EBD, 0xB830, 0x8EBE, 0xB831, 0x8EBF, 0xB832, 0x8EC0, 0xB833, 0x8EC1, 0xB836, 0x8EC2, 0xB83A, + 0x8EC3, 0xB83B, 0x8EC4, 0xB83C, 0x8EC5, 0xB83D, 0x8EC6, 0xB83E, 0x8EC7, 0xB83F, 0x8EC8, 0xB841, 0x8EC9, 0xB842, 0x8ECA, 0xB843, + 0x8ECB, 0xB845, 0x8ECC, 0xB846, 0x8ECD, 0xB847, 0x8ECE, 0xB848, 0x8ECF, 0xB849, 0x8ED0, 0xB84A, 0x8ED1, 0xB84B, 0x8ED2, 0xB84C, + 0x8ED3, 0xB84D, 0x8ED4, 0xB84E, 0x8ED5, 0xB84F, 0x8ED6, 0xB850, 0x8ED7, 0xB852, 0x8ED8, 0xB854, 0x8ED9, 0xB855, 0x8EDA, 0xB856, + 0x8EDB, 0xB857, 0x8EDC, 0xB858, 0x8EDD, 0xB859, 0x8EDE, 0xB85A, 0x8EDF, 0xB85B, 0x8EE0, 0xB85E, 0x8EE1, 0xB85F, 0x8EE2, 0xB861, + 0x8EE3, 0xB862, 0x8EE4, 0xB863, 0x8EE5, 0xB865, 0x8EE6, 0xB866, 0x8EE7, 0xB867, 0x8EE8, 0xB868, 0x8EE9, 0xB869, 0x8EEA, 0xB86A, + 0x8EEB, 0xB86B, 0x8EEC, 0xB86E, 0x8EED, 0xB870, 0x8EEE, 0xB872, 0x8EEF, 0xB873, 0x8EF0, 0xB874, 0x8EF1, 0xB875, 0x8EF2, 0xB876, + 0x8EF3, 0xB877, 0x8EF4, 0xB879, 0x8EF5, 0xB87A, 0x8EF6, 0xB87B, 0x8EF7, 0xB87D, 0x8EF8, 0xB87E, 0x8EF9, 0xB87F, 0x8EFA, 0xB880, + 0x8EFB, 0xB881, 0x8EFC, 0xB882, 0x8EFD, 0xB883, 0x8EFE, 0xB884, 0x8F41, 0xB885, 0x8F42, 0xB886, 0x8F43, 0xB887, 0x8F44, 0xB888, + 0x8F45, 0xB889, 0x8F46, 0xB88A, 0x8F47, 0xB88B, 0x8F48, 0xB88C, 0x8F49, 0xB88E, 0x8F4A, 0xB88F, 0x8F4B, 0xB890, 0x8F4C, 0xB891, + 0x8F4D, 0xB892, 0x8F4E, 0xB893, 0x8F4F, 0xB894, 0x8F50, 0xB895, 0x8F51, 0xB896, 0x8F52, 0xB897, 0x8F53, 0xB898, 0x8F54, 0xB899, + 0x8F55, 0xB89A, 0x8F56, 0xB89B, 0x8F57, 0xB89C, 0x8F58, 0xB89D, 0x8F59, 0xB89E, 0x8F5A, 0xB89F, 0x8F61, 0xB8A0, 0x8F62, 0xB8A1, + 0x8F63, 0xB8A2, 0x8F64, 0xB8A3, 0x8F65, 0xB8A4, 0x8F66, 0xB8A5, 0x8F67, 0xB8A6, 0x8F68, 0xB8A7, 0x8F69, 0xB8A9, 0x8F6A, 0xB8AA, + 0x8F6B, 0xB8AB, 0x8F6C, 0xB8AC, 0x8F6D, 0xB8AD, 0x8F6E, 0xB8AE, 0x8F6F, 0xB8AF, 0x8F70, 0xB8B1, 0x8F71, 0xB8B2, 0x8F72, 0xB8B3, + 0x8F73, 0xB8B5, 0x8F74, 0xB8B6, 0x8F75, 0xB8B7, 0x8F76, 0xB8B9, 0x8F77, 0xB8BA, 0x8F78, 0xB8BB, 0x8F79, 0xB8BC, 0x8F7A, 0xB8BD, + 0x8F81, 0xB8BE, 0x8F82, 0xB8BF, 0x8F83, 0xB8C2, 0x8F84, 0xB8C4, 0x8F85, 0xB8C6, 0x8F86, 0xB8C7, 0x8F87, 0xB8C8, 0x8F88, 0xB8C9, + 0x8F89, 0xB8CA, 0x8F8A, 0xB8CB, 0x8F8B, 0xB8CD, 0x8F8C, 0xB8CE, 0x8F8D, 0xB8CF, 0x8F8E, 0xB8D1, 0x8F8F, 0xB8D2, 0x8F90, 0xB8D3, + 0x8F91, 0xB8D5, 0x8F92, 0xB8D6, 0x8F93, 0xB8D7, 0x8F94, 0xB8D8, 0x8F95, 0xB8D9, 0x8F96, 0xB8DA, 0x8F97, 0xB8DB, 0x8F98, 0xB8DC, + 0x8F99, 0xB8DE, 0x8F9A, 0xB8E0, 0x8F9B, 0xB8E2, 0x8F9C, 0xB8E3, 0x8F9D, 0xB8E4, 0x8F9E, 0xB8E5, 0x8F9F, 0xB8E6, 0x8FA0, 0xB8E7, + 0x8FA1, 0xB8EA, 0x8FA2, 0xB8EB, 0x8FA3, 0xB8ED, 0x8FA4, 0xB8EE, 0x8FA5, 0xB8EF, 0x8FA6, 0xB8F1, 0x8FA7, 0xB8F2, 0x8FA8, 0xB8F3, + 0x8FA9, 0xB8F4, 0x8FAA, 0xB8F5, 0x8FAB, 0xB8F6, 0x8FAC, 0xB8F7, 0x8FAD, 0xB8FA, 0x8FAE, 0xB8FC, 0x8FAF, 0xB8FE, 0x8FB0, 0xB8FF, + 0x8FB1, 0xB900, 0x8FB2, 0xB901, 0x8FB3, 0xB902, 0x8FB4, 0xB903, 0x8FB5, 0xB905, 0x8FB6, 0xB906, 0x8FB7, 0xB907, 0x8FB8, 0xB908, + 0x8FB9, 0xB909, 0x8FBA, 0xB90A, 0x8FBB, 0xB90B, 0x8FBC, 0xB90C, 0x8FBD, 0xB90D, 0x8FBE, 0xB90E, 0x8FBF, 0xB90F, 0x8FC0, 0xB910, + 0x8FC1, 0xB911, 0x8FC2, 0xB912, 0x8FC3, 0xB913, 0x8FC4, 0xB914, 0x8FC5, 0xB915, 0x8FC6, 0xB916, 0x8FC7, 0xB917, 0x8FC8, 0xB919, + 0x8FC9, 0xB91A, 0x8FCA, 0xB91B, 0x8FCB, 0xB91C, 0x8FCC, 0xB91D, 0x8FCD, 0xB91E, 0x8FCE, 0xB91F, 0x8FCF, 0xB921, 0x8FD0, 0xB922, + 0x8FD1, 0xB923, 0x8FD2, 0xB924, 0x8FD3, 0xB925, 0x8FD4, 0xB926, 0x8FD5, 0xB927, 0x8FD6, 0xB928, 0x8FD7, 0xB929, 0x8FD8, 0xB92A, + 0x8FD9, 0xB92B, 0x8FDA, 0xB92C, 0x8FDB, 0xB92D, 0x8FDC, 0xB92E, 0x8FDD, 0xB92F, 0x8FDE, 0xB930, 0x8FDF, 0xB931, 0x8FE0, 0xB932, + 0x8FE1, 0xB933, 0x8FE2, 0xB934, 0x8FE3, 0xB935, 0x8FE4, 0xB936, 0x8FE5, 0xB937, 0x8FE6, 0xB938, 0x8FE7, 0xB939, 0x8FE8, 0xB93A, + 0x8FE9, 0xB93B, 0x8FEA, 0xB93E, 0x8FEB, 0xB93F, 0x8FEC, 0xB941, 0x8FED, 0xB942, 0x8FEE, 0xB943, 0x8FEF, 0xB945, 0x8FF0, 0xB946, + 0x8FF1, 0xB947, 0x8FF2, 0xB948, 0x8FF3, 0xB949, 0x8FF4, 0xB94A, 0x8FF5, 0xB94B, 0x8FF6, 0xB94D, 0x8FF7, 0xB94E, 0x8FF8, 0xB950, + 0x8FF9, 0xB952, 0x8FFA, 0xB953, 0x8FFB, 0xB954, 0x8FFC, 0xB955, 0x8FFD, 0xB956, 0x8FFE, 0xB957, 0x9041, 0xB95A, 0x9042, 0xB95B, + 0x9043, 0xB95D, 0x9044, 0xB95E, 0x9045, 0xB95F, 0x9046, 0xB961, 0x9047, 0xB962, 0x9048, 0xB963, 0x9049, 0xB964, 0x904A, 0xB965, + 0x904B, 0xB966, 0x904C, 0xB967, 0x904D, 0xB96A, 0x904E, 0xB96C, 0x904F, 0xB96E, 0x9050, 0xB96F, 0x9051, 0xB970, 0x9052, 0xB971, + 0x9053, 0xB972, 0x9054, 0xB973, 0x9055, 0xB976, 0x9056, 0xB977, 0x9057, 0xB979, 0x9058, 0xB97A, 0x9059, 0xB97B, 0x905A, 0xB97D, + 0x9061, 0xB97E, 0x9062, 0xB97F, 0x9063, 0xB980, 0x9064, 0xB981, 0x9065, 0xB982, 0x9066, 0xB983, 0x9067, 0xB986, 0x9068, 0xB988, + 0x9069, 0xB98B, 0x906A, 0xB98C, 0x906B, 0xB98F, 0x906C, 0xB990, 0x906D, 0xB991, 0x906E, 0xB992, 0x906F, 0xB993, 0x9070, 0xB994, + 0x9071, 0xB995, 0x9072, 0xB996, 0x9073, 0xB997, 0x9074, 0xB998, 0x9075, 0xB999, 0x9076, 0xB99A, 0x9077, 0xB99B, 0x9078, 0xB99C, + 0x9079, 0xB99D, 0x907A, 0xB99E, 0x9081, 0xB99F, 0x9082, 0xB9A0, 0x9083, 0xB9A1, 0x9084, 0xB9A2, 0x9085, 0xB9A3, 0x9086, 0xB9A4, + 0x9087, 0xB9A5, 0x9088, 0xB9A6, 0x9089, 0xB9A7, 0x908A, 0xB9A8, 0x908B, 0xB9A9, 0x908C, 0xB9AA, 0x908D, 0xB9AB, 0x908E, 0xB9AE, + 0x908F, 0xB9AF, 0x9090, 0xB9B1, 0x9091, 0xB9B2, 0x9092, 0xB9B3, 0x9093, 0xB9B5, 0x9094, 0xB9B6, 0x9095, 0xB9B7, 0x9096, 0xB9B8, + 0x9097, 0xB9B9, 0x9098, 0xB9BA, 0x9099, 0xB9BB, 0x909A, 0xB9BE, 0x909B, 0xB9C0, 0x909C, 0xB9C2, 0x909D, 0xB9C3, 0x909E, 0xB9C4, + 0x909F, 0xB9C5, 0x90A0, 0xB9C6, 0x90A1, 0xB9C7, 0x90A2, 0xB9CA, 0x90A3, 0xB9CB, 0x90A4, 0xB9CD, 0x90A5, 0xB9D3, 0x90A6, 0xB9D4, + 0x90A7, 0xB9D5, 0x90A8, 0xB9D6, 0x90A9, 0xB9D7, 0x90AA, 0xB9DA, 0x90AB, 0xB9DC, 0x90AC, 0xB9DF, 0x90AD, 0xB9E0, 0x90AE, 0xB9E2, + 0x90AF, 0xB9E6, 0x90B0, 0xB9E7, 0x90B1, 0xB9E9, 0x90B2, 0xB9EA, 0x90B3, 0xB9EB, 0x90B4, 0xB9ED, 0x90B5, 0xB9EE, 0x90B6, 0xB9EF, + 0x90B7, 0xB9F0, 0x90B8, 0xB9F1, 0x90B9, 0xB9F2, 0x90BA, 0xB9F3, 0x90BB, 0xB9F6, 0x90BC, 0xB9FB, 0x90BD, 0xB9FC, 0x90BE, 0xB9FD, + 0x90BF, 0xB9FE, 0x90C0, 0xB9FF, 0x90C1, 0xBA02, 0x90C2, 0xBA03, 0x90C3, 0xBA04, 0x90C4, 0xBA05, 0x90C5, 0xBA06, 0x90C6, 0xBA07, + 0x90C7, 0xBA09, 0x90C8, 0xBA0A, 0x90C9, 0xBA0B, 0x90CA, 0xBA0C, 0x90CB, 0xBA0D, 0x90CC, 0xBA0E, 0x90CD, 0xBA0F, 0x90CE, 0xBA10, + 0x90CF, 0xBA11, 0x90D0, 0xBA12, 0x90D1, 0xBA13, 0x90D2, 0xBA14, 0x90D3, 0xBA16, 0x90D4, 0xBA17, 0x90D5, 0xBA18, 0x90D6, 0xBA19, + 0x90D7, 0xBA1A, 0x90D8, 0xBA1B, 0x90D9, 0xBA1C, 0x90DA, 0xBA1D, 0x90DB, 0xBA1E, 0x90DC, 0xBA1F, 0x90DD, 0xBA20, 0x90DE, 0xBA21, + 0x90DF, 0xBA22, 0x90E0, 0xBA23, 0x90E1, 0xBA24, 0x90E2, 0xBA25, 0x90E3, 0xBA26, 0x90E4, 0xBA27, 0x90E5, 0xBA28, 0x90E6, 0xBA29, + 0x90E7, 0xBA2A, 0x90E8, 0xBA2B, 0x90E9, 0xBA2C, 0x90EA, 0xBA2D, 0x90EB, 0xBA2E, 0x90EC, 0xBA2F, 0x90ED, 0xBA30, 0x90EE, 0xBA31, + 0x90EF, 0xBA32, 0x90F0, 0xBA33, 0x90F1, 0xBA34, 0x90F2, 0xBA35, 0x90F3, 0xBA36, 0x90F4, 0xBA37, 0x90F5, 0xBA3A, 0x90F6, 0xBA3B, + 0x90F7, 0xBA3D, 0x90F8, 0xBA3E, 0x90F9, 0xBA3F, 0x90FA, 0xBA41, 0x90FB, 0xBA43, 0x90FC, 0xBA44, 0x90FD, 0xBA45, 0x90FE, 0xBA46, + 0x9141, 0xBA47, 0x9142, 0xBA4A, 0x9143, 0xBA4C, 0x9144, 0xBA4F, 0x9145, 0xBA50, 0x9146, 0xBA51, 0x9147, 0xBA52, 0x9148, 0xBA56, + 0x9149, 0xBA57, 0x914A, 0xBA59, 0x914B, 0xBA5A, 0x914C, 0xBA5B, 0x914D, 0xBA5D, 0x914E, 0xBA5E, 0x914F, 0xBA5F, 0x9150, 0xBA60, + 0x9151, 0xBA61, 0x9152, 0xBA62, 0x9153, 0xBA63, 0x9154, 0xBA66, 0x9155, 0xBA6A, 0x9156, 0xBA6B, 0x9157, 0xBA6C, 0x9158, 0xBA6D, + 0x9159, 0xBA6E, 0x915A, 0xBA6F, 0x9161, 0xBA72, 0x9162, 0xBA73, 0x9163, 0xBA75, 0x9164, 0xBA76, 0x9165, 0xBA77, 0x9166, 0xBA79, + 0x9167, 0xBA7A, 0x9168, 0xBA7B, 0x9169, 0xBA7C, 0x916A, 0xBA7D, 0x916B, 0xBA7E, 0x916C, 0xBA7F, 0x916D, 0xBA80, 0x916E, 0xBA81, + 0x916F, 0xBA82, 0x9170, 0xBA86, 0x9171, 0xBA88, 0x9172, 0xBA89, 0x9173, 0xBA8A, 0x9174, 0xBA8B, 0x9175, 0xBA8D, 0x9176, 0xBA8E, + 0x9177, 0xBA8F, 0x9178, 0xBA90, 0x9179, 0xBA91, 0x917A, 0xBA92, 0x9181, 0xBA93, 0x9182, 0xBA94, 0x9183, 0xBA95, 0x9184, 0xBA96, + 0x9185, 0xBA97, 0x9186, 0xBA98, 0x9187, 0xBA99, 0x9188, 0xBA9A, 0x9189, 0xBA9B, 0x918A, 0xBA9C, 0x918B, 0xBA9D, 0x918C, 0xBA9E, + 0x918D, 0xBA9F, 0x918E, 0xBAA0, 0x918F, 0xBAA1, 0x9190, 0xBAA2, 0x9191, 0xBAA3, 0x9192, 0xBAA4, 0x9193, 0xBAA5, 0x9194, 0xBAA6, + 0x9195, 0xBAA7, 0x9196, 0xBAAA, 0x9197, 0xBAAD, 0x9198, 0xBAAE, 0x9199, 0xBAAF, 0x919A, 0xBAB1, 0x919B, 0xBAB3, 0x919C, 0xBAB4, + 0x919D, 0xBAB5, 0x919E, 0xBAB6, 0x919F, 0xBAB7, 0x91A0, 0xBABA, 0x91A1, 0xBABC, 0x91A2, 0xBABE, 0x91A3, 0xBABF, 0x91A4, 0xBAC0, + 0x91A5, 0xBAC1, 0x91A6, 0xBAC2, 0x91A7, 0xBAC3, 0x91A8, 0xBAC5, 0x91A9, 0xBAC6, 0x91AA, 0xBAC7, 0x91AB, 0xBAC9, 0x91AC, 0xBACA, + 0x91AD, 0xBACB, 0x91AE, 0xBACC, 0x91AF, 0xBACD, 0x91B0, 0xBACE, 0x91B1, 0xBACF, 0x91B2, 0xBAD0, 0x91B3, 0xBAD1, 0x91B4, 0xBAD2, + 0x91B5, 0xBAD3, 0x91B6, 0xBAD4, 0x91B7, 0xBAD5, 0x91B8, 0xBAD6, 0x91B9, 0xBAD7, 0x91BA, 0xBADA, 0x91BB, 0xBADB, 0x91BC, 0xBADC, + 0x91BD, 0xBADD, 0x91BE, 0xBADE, 0x91BF, 0xBADF, 0x91C0, 0xBAE0, 0x91C1, 0xBAE1, 0x91C2, 0xBAE2, 0x91C3, 0xBAE3, 0x91C4, 0xBAE4, + 0x91C5, 0xBAE5, 0x91C6, 0xBAE6, 0x91C7, 0xBAE7, 0x91C8, 0xBAE8, 0x91C9, 0xBAE9, 0x91CA, 0xBAEA, 0x91CB, 0xBAEB, 0x91CC, 0xBAEC, + 0x91CD, 0xBAED, 0x91CE, 0xBAEE, 0x91CF, 0xBAEF, 0x91D0, 0xBAF0, 0x91D1, 0xBAF1, 0x91D2, 0xBAF2, 0x91D3, 0xBAF3, 0x91D4, 0xBAF4, + 0x91D5, 0xBAF5, 0x91D6, 0xBAF6, 0x91D7, 0xBAF7, 0x91D8, 0xBAF8, 0x91D9, 0xBAF9, 0x91DA, 0xBAFA, 0x91DB, 0xBAFB, 0x91DC, 0xBAFD, + 0x91DD, 0xBAFE, 0x91DE, 0xBAFF, 0x91DF, 0xBB01, 0x91E0, 0xBB02, 0x91E1, 0xBB03, 0x91E2, 0xBB05, 0x91E3, 0xBB06, 0x91E4, 0xBB07, + 0x91E5, 0xBB08, 0x91E6, 0xBB09, 0x91E7, 0xBB0A, 0x91E8, 0xBB0B, 0x91E9, 0xBB0C, 0x91EA, 0xBB0E, 0x91EB, 0xBB10, 0x91EC, 0xBB12, + 0x91ED, 0xBB13, 0x91EE, 0xBB14, 0x91EF, 0xBB15, 0x91F0, 0xBB16, 0x91F1, 0xBB17, 0x91F2, 0xBB19, 0x91F3, 0xBB1A, 0x91F4, 0xBB1B, + 0x91F5, 0xBB1D, 0x91F6, 0xBB1E, 0x91F7, 0xBB1F, 0x91F8, 0xBB21, 0x91F9, 0xBB22, 0x91FA, 0xBB23, 0x91FB, 0xBB24, 0x91FC, 0xBB25, + 0x91FD, 0xBB26, 0x91FE, 0xBB27, 0x9241, 0xBB28, 0x9242, 0xBB2A, 0x9243, 0xBB2C, 0x9244, 0xBB2D, 0x9245, 0xBB2E, 0x9246, 0xBB2F, + 0x9247, 0xBB30, 0x9248, 0xBB31, 0x9249, 0xBB32, 0x924A, 0xBB33, 0x924B, 0xBB37, 0x924C, 0xBB39, 0x924D, 0xBB3A, 0x924E, 0xBB3F, + 0x924F, 0xBB40, 0x9250, 0xBB41, 0x9251, 0xBB42, 0x9252, 0xBB43, 0x9253, 0xBB46, 0x9254, 0xBB48, 0x9255, 0xBB4A, 0x9256, 0xBB4B, + 0x9257, 0xBB4C, 0x9258, 0xBB4E, 0x9259, 0xBB51, 0x925A, 0xBB52, 0x9261, 0xBB53, 0x9262, 0xBB55, 0x9263, 0xBB56, 0x9264, 0xBB57, + 0x9265, 0xBB59, 0x9266, 0xBB5A, 0x9267, 0xBB5B, 0x9268, 0xBB5C, 0x9269, 0xBB5D, 0x926A, 0xBB5E, 0x926B, 0xBB5F, 0x926C, 0xBB60, + 0x926D, 0xBB62, 0x926E, 0xBB64, 0x926F, 0xBB65, 0x9270, 0xBB66, 0x9271, 0xBB67, 0x9272, 0xBB68, 0x9273, 0xBB69, 0x9274, 0xBB6A, + 0x9275, 0xBB6B, 0x9276, 0xBB6D, 0x9277, 0xBB6E, 0x9278, 0xBB6F, 0x9279, 0xBB70, 0x927A, 0xBB71, 0x9281, 0xBB72, 0x9282, 0xBB73, + 0x9283, 0xBB74, 0x9284, 0xBB75, 0x9285, 0xBB76, 0x9286, 0xBB77, 0x9287, 0xBB78, 0x9288, 0xBB79, 0x9289, 0xBB7A, 0x928A, 0xBB7B, + 0x928B, 0xBB7C, 0x928C, 0xBB7D, 0x928D, 0xBB7E, 0x928E, 0xBB7F, 0x928F, 0xBB80, 0x9290, 0xBB81, 0x9291, 0xBB82, 0x9292, 0xBB83, + 0x9293, 0xBB84, 0x9294, 0xBB85, 0x9295, 0xBB86, 0x9296, 0xBB87, 0x9297, 0xBB89, 0x9298, 0xBB8A, 0x9299, 0xBB8B, 0x929A, 0xBB8D, + 0x929B, 0xBB8E, 0x929C, 0xBB8F, 0x929D, 0xBB91, 0x929E, 0xBB92, 0x929F, 0xBB93, 0x92A0, 0xBB94, 0x92A1, 0xBB95, 0x92A2, 0xBB96, + 0x92A3, 0xBB97, 0x92A4, 0xBB98, 0x92A5, 0xBB99, 0x92A6, 0xBB9A, 0x92A7, 0xBB9B, 0x92A8, 0xBB9C, 0x92A9, 0xBB9D, 0x92AA, 0xBB9E, + 0x92AB, 0xBB9F, 0x92AC, 0xBBA0, 0x92AD, 0xBBA1, 0x92AE, 0xBBA2, 0x92AF, 0xBBA3, 0x92B0, 0xBBA5, 0x92B1, 0xBBA6, 0x92B2, 0xBBA7, + 0x92B3, 0xBBA9, 0x92B4, 0xBBAA, 0x92B5, 0xBBAB, 0x92B6, 0xBBAD, 0x92B7, 0xBBAE, 0x92B8, 0xBBAF, 0x92B9, 0xBBB0, 0x92BA, 0xBBB1, + 0x92BB, 0xBBB2, 0x92BC, 0xBBB3, 0x92BD, 0xBBB5, 0x92BE, 0xBBB6, 0x92BF, 0xBBB8, 0x92C0, 0xBBB9, 0x92C1, 0xBBBA, 0x92C2, 0xBBBB, + 0x92C3, 0xBBBC, 0x92C4, 0xBBBD, 0x92C5, 0xBBBE, 0x92C6, 0xBBBF, 0x92C7, 0xBBC1, 0x92C8, 0xBBC2, 0x92C9, 0xBBC3, 0x92CA, 0xBBC5, + 0x92CB, 0xBBC6, 0x92CC, 0xBBC7, 0x92CD, 0xBBC9, 0x92CE, 0xBBCA, 0x92CF, 0xBBCB, 0x92D0, 0xBBCC, 0x92D1, 0xBBCD, 0x92D2, 0xBBCE, + 0x92D3, 0xBBCF, 0x92D4, 0xBBD1, 0x92D5, 0xBBD2, 0x92D6, 0xBBD4, 0x92D7, 0xBBD5, 0x92D8, 0xBBD6, 0x92D9, 0xBBD7, 0x92DA, 0xBBD8, + 0x92DB, 0xBBD9, 0x92DC, 0xBBDA, 0x92DD, 0xBBDB, 0x92DE, 0xBBDC, 0x92DF, 0xBBDD, 0x92E0, 0xBBDE, 0x92E1, 0xBBDF, 0x92E2, 0xBBE0, + 0x92E3, 0xBBE1, 0x92E4, 0xBBE2, 0x92E5, 0xBBE3, 0x92E6, 0xBBE4, 0x92E7, 0xBBE5, 0x92E8, 0xBBE6, 0x92E9, 0xBBE7, 0x92EA, 0xBBE8, + 0x92EB, 0xBBE9, 0x92EC, 0xBBEA, 0x92ED, 0xBBEB, 0x92EE, 0xBBEC, 0x92EF, 0xBBED, 0x92F0, 0xBBEE, 0x92F1, 0xBBEF, 0x92F2, 0xBBF0, + 0x92F3, 0xBBF1, 0x92F4, 0xBBF2, 0x92F5, 0xBBF3, 0x92F6, 0xBBF4, 0x92F7, 0xBBF5, 0x92F8, 0xBBF6, 0x92F9, 0xBBF7, 0x92FA, 0xBBFA, + 0x92FB, 0xBBFB, 0x92FC, 0xBBFD, 0x92FD, 0xBBFE, 0x92FE, 0xBC01, 0x9341, 0xBC03, 0x9342, 0xBC04, 0x9343, 0xBC05, 0x9344, 0xBC06, + 0x9345, 0xBC07, 0x9346, 0xBC0A, 0x9347, 0xBC0E, 0x9348, 0xBC10, 0x9349, 0xBC12, 0x934A, 0xBC13, 0x934B, 0xBC19, 0x934C, 0xBC1A, + 0x934D, 0xBC20, 0x934E, 0xBC21, 0x934F, 0xBC22, 0x9350, 0xBC23, 0x9351, 0xBC26, 0x9352, 0xBC28, 0x9353, 0xBC2A, 0x9354, 0xBC2B, + 0x9355, 0xBC2C, 0x9356, 0xBC2E, 0x9357, 0xBC2F, 0x9358, 0xBC32, 0x9359, 0xBC33, 0x935A, 0xBC35, 0x9361, 0xBC36, 0x9362, 0xBC37, + 0x9363, 0xBC39, 0x9364, 0xBC3A, 0x9365, 0xBC3B, 0x9366, 0xBC3C, 0x9367, 0xBC3D, 0x9368, 0xBC3E, 0x9369, 0xBC3F, 0x936A, 0xBC42, + 0x936B, 0xBC46, 0x936C, 0xBC47, 0x936D, 0xBC48, 0x936E, 0xBC4A, 0x936F, 0xBC4B, 0x9370, 0xBC4E, 0x9371, 0xBC4F, 0x9372, 0xBC51, + 0x9373, 0xBC52, 0x9374, 0xBC53, 0x9375, 0xBC54, 0x9376, 0xBC55, 0x9377, 0xBC56, 0x9378, 0xBC57, 0x9379, 0xBC58, 0x937A, 0xBC59, + 0x9381, 0xBC5A, 0x9382, 0xBC5B, 0x9383, 0xBC5C, 0x9384, 0xBC5E, 0x9385, 0xBC5F, 0x9386, 0xBC60, 0x9387, 0xBC61, 0x9388, 0xBC62, + 0x9389, 0xBC63, 0x938A, 0xBC64, 0x938B, 0xBC65, 0x938C, 0xBC66, 0x938D, 0xBC67, 0x938E, 0xBC68, 0x938F, 0xBC69, 0x9390, 0xBC6A, + 0x9391, 0xBC6B, 0x9392, 0xBC6C, 0x9393, 0xBC6D, 0x9394, 0xBC6E, 0x9395, 0xBC6F, 0x9396, 0xBC70, 0x9397, 0xBC71, 0x9398, 0xBC72, + 0x9399, 0xBC73, 0x939A, 0xBC74, 0x939B, 0xBC75, 0x939C, 0xBC76, 0x939D, 0xBC77, 0x939E, 0xBC78, 0x939F, 0xBC79, 0x93A0, 0xBC7A, + 0x93A1, 0xBC7B, 0x93A2, 0xBC7C, 0x93A3, 0xBC7D, 0x93A4, 0xBC7E, 0x93A5, 0xBC7F, 0x93A6, 0xBC80, 0x93A7, 0xBC81, 0x93A8, 0xBC82, + 0x93A9, 0xBC83, 0x93AA, 0xBC86, 0x93AB, 0xBC87, 0x93AC, 0xBC89, 0x93AD, 0xBC8A, 0x93AE, 0xBC8D, 0x93AF, 0xBC8F, 0x93B0, 0xBC90, + 0x93B1, 0xBC91, 0x93B2, 0xBC92, 0x93B3, 0xBC93, 0x93B4, 0xBC96, 0x93B5, 0xBC98, 0x93B6, 0xBC9B, 0x93B7, 0xBC9C, 0x93B8, 0xBC9D, + 0x93B9, 0xBC9E, 0x93BA, 0xBC9F, 0x93BB, 0xBCA2, 0x93BC, 0xBCA3, 0x93BD, 0xBCA5, 0x93BE, 0xBCA6, 0x93BF, 0xBCA9, 0x93C0, 0xBCAA, + 0x93C1, 0xBCAB, 0x93C2, 0xBCAC, 0x93C3, 0xBCAD, 0x93C4, 0xBCAE, 0x93C5, 0xBCAF, 0x93C6, 0xBCB2, 0x93C7, 0xBCB6, 0x93C8, 0xBCB7, + 0x93C9, 0xBCB8, 0x93CA, 0xBCB9, 0x93CB, 0xBCBA, 0x93CC, 0xBCBB, 0x93CD, 0xBCBE, 0x93CE, 0xBCBF, 0x93CF, 0xBCC1, 0x93D0, 0xBCC2, + 0x93D1, 0xBCC3, 0x93D2, 0xBCC5, 0x93D3, 0xBCC6, 0x93D4, 0xBCC7, 0x93D5, 0xBCC8, 0x93D6, 0xBCC9, 0x93D7, 0xBCCA, 0x93D8, 0xBCCB, + 0x93D9, 0xBCCC, 0x93DA, 0xBCCE, 0x93DB, 0xBCD2, 0x93DC, 0xBCD3, 0x93DD, 0xBCD4, 0x93DE, 0xBCD6, 0x93DF, 0xBCD7, 0x93E0, 0xBCD9, + 0x93E1, 0xBCDA, 0x93E2, 0xBCDB, 0x93E3, 0xBCDD, 0x93E4, 0xBCDE, 0x93E5, 0xBCDF, 0x93E6, 0xBCE0, 0x93E7, 0xBCE1, 0x93E8, 0xBCE2, + 0x93E9, 0xBCE3, 0x93EA, 0xBCE4, 0x93EB, 0xBCE5, 0x93EC, 0xBCE6, 0x93ED, 0xBCE7, 0x93EE, 0xBCE8, 0x93EF, 0xBCE9, 0x93F0, 0xBCEA, + 0x93F1, 0xBCEB, 0x93F2, 0xBCEC, 0x93F3, 0xBCED, 0x93F4, 0xBCEE, 0x93F5, 0xBCEF, 0x93F6, 0xBCF0, 0x93F7, 0xBCF1, 0x93F8, 0xBCF2, + 0x93F9, 0xBCF3, 0x93FA, 0xBCF7, 0x93FB, 0xBCF9, 0x93FC, 0xBCFA, 0x93FD, 0xBCFB, 0x93FE, 0xBCFD, 0x9441, 0xBCFE, 0x9442, 0xBCFF, + 0x9443, 0xBD00, 0x9444, 0xBD01, 0x9445, 0xBD02, 0x9446, 0xBD03, 0x9447, 0xBD06, 0x9448, 0xBD08, 0x9449, 0xBD0A, 0x944A, 0xBD0B, + 0x944B, 0xBD0C, 0x944C, 0xBD0D, 0x944D, 0xBD0E, 0x944E, 0xBD0F, 0x944F, 0xBD11, 0x9450, 0xBD12, 0x9451, 0xBD13, 0x9452, 0xBD15, + 0x9453, 0xBD16, 0x9454, 0xBD17, 0x9455, 0xBD18, 0x9456, 0xBD19, 0x9457, 0xBD1A, 0x9458, 0xBD1B, 0x9459, 0xBD1C, 0x945A, 0xBD1D, + 0x9461, 0xBD1E, 0x9462, 0xBD1F, 0x9463, 0xBD20, 0x9464, 0xBD21, 0x9465, 0xBD22, 0x9466, 0xBD23, 0x9467, 0xBD25, 0x9468, 0xBD26, + 0x9469, 0xBD27, 0x946A, 0xBD28, 0x946B, 0xBD29, 0x946C, 0xBD2A, 0x946D, 0xBD2B, 0x946E, 0xBD2D, 0x946F, 0xBD2E, 0x9470, 0xBD2F, + 0x9471, 0xBD30, 0x9472, 0xBD31, 0x9473, 0xBD32, 0x9474, 0xBD33, 0x9475, 0xBD34, 0x9476, 0xBD35, 0x9477, 0xBD36, 0x9478, 0xBD37, + 0x9479, 0xBD38, 0x947A, 0xBD39, 0x9481, 0xBD3A, 0x9482, 0xBD3B, 0x9483, 0xBD3C, 0x9484, 0xBD3D, 0x9485, 0xBD3E, 0x9486, 0xBD3F, + 0x9487, 0xBD41, 0x9488, 0xBD42, 0x9489, 0xBD43, 0x948A, 0xBD44, 0x948B, 0xBD45, 0x948C, 0xBD46, 0x948D, 0xBD47, 0x948E, 0xBD4A, + 0x948F, 0xBD4B, 0x9490, 0xBD4D, 0x9491, 0xBD4E, 0x9492, 0xBD4F, 0x9493, 0xBD51, 0x9494, 0xBD52, 0x9495, 0xBD53, 0x9496, 0xBD54, + 0x9497, 0xBD55, 0x9498, 0xBD56, 0x9499, 0xBD57, 0x949A, 0xBD5A, 0x949B, 0xBD5B, 0x949C, 0xBD5C, 0x949D, 0xBD5D, 0x949E, 0xBD5E, + 0x949F, 0xBD5F, 0x94A0, 0xBD60, 0x94A1, 0xBD61, 0x94A2, 0xBD62, 0x94A3, 0xBD63, 0x94A4, 0xBD65, 0x94A5, 0xBD66, 0x94A6, 0xBD67, + 0x94A7, 0xBD69, 0x94A8, 0xBD6A, 0x94A9, 0xBD6B, 0x94AA, 0xBD6C, 0x94AB, 0xBD6D, 0x94AC, 0xBD6E, 0x94AD, 0xBD6F, 0x94AE, 0xBD70, + 0x94AF, 0xBD71, 0x94B0, 0xBD72, 0x94B1, 0xBD73, 0x94B2, 0xBD74, 0x94B3, 0xBD75, 0x94B4, 0xBD76, 0x94B5, 0xBD77, 0x94B6, 0xBD78, + 0x94B7, 0xBD79, 0x94B8, 0xBD7A, 0x94B9, 0xBD7B, 0x94BA, 0xBD7C, 0x94BB, 0xBD7D, 0x94BC, 0xBD7E, 0x94BD, 0xBD7F, 0x94BE, 0xBD82, + 0x94BF, 0xBD83, 0x94C0, 0xBD85, 0x94C1, 0xBD86, 0x94C2, 0xBD8B, 0x94C3, 0xBD8C, 0x94C4, 0xBD8D, 0x94C5, 0xBD8E, 0x94C6, 0xBD8F, + 0x94C7, 0xBD92, 0x94C8, 0xBD94, 0x94C9, 0xBD96, 0x94CA, 0xBD97, 0x94CB, 0xBD98, 0x94CC, 0xBD9B, 0x94CD, 0xBD9D, 0x94CE, 0xBD9E, + 0x94CF, 0xBD9F, 0x94D0, 0xBDA0, 0x94D1, 0xBDA1, 0x94D2, 0xBDA2, 0x94D3, 0xBDA3, 0x94D4, 0xBDA5, 0x94D5, 0xBDA6, 0x94D6, 0xBDA7, + 0x94D7, 0xBDA8, 0x94D8, 0xBDA9, 0x94D9, 0xBDAA, 0x94DA, 0xBDAB, 0x94DB, 0xBDAC, 0x94DC, 0xBDAD, 0x94DD, 0xBDAE, 0x94DE, 0xBDAF, + 0x94DF, 0xBDB1, 0x94E0, 0xBDB2, 0x94E1, 0xBDB3, 0x94E2, 0xBDB4, 0x94E3, 0xBDB5, 0x94E4, 0xBDB6, 0x94E5, 0xBDB7, 0x94E6, 0xBDB9, + 0x94E7, 0xBDBA, 0x94E8, 0xBDBB, 0x94E9, 0xBDBC, 0x94EA, 0xBDBD, 0x94EB, 0xBDBE, 0x94EC, 0xBDBF, 0x94ED, 0xBDC0, 0x94EE, 0xBDC1, + 0x94EF, 0xBDC2, 0x94F0, 0xBDC3, 0x94F1, 0xBDC4, 0x94F2, 0xBDC5, 0x94F3, 0xBDC6, 0x94F4, 0xBDC7, 0x94F5, 0xBDC8, 0x94F6, 0xBDC9, + 0x94F7, 0xBDCA, 0x94F8, 0xBDCB, 0x94F9, 0xBDCC, 0x94FA, 0xBDCD, 0x94FB, 0xBDCE, 0x94FC, 0xBDCF, 0x94FD, 0xBDD0, 0x94FE, 0xBDD1, + 0x9541, 0xBDD2, 0x9542, 0xBDD3, 0x9543, 0xBDD6, 0x9544, 0xBDD7, 0x9545, 0xBDD9, 0x9546, 0xBDDA, 0x9547, 0xBDDB, 0x9548, 0xBDDD, + 0x9549, 0xBDDE, 0x954A, 0xBDDF, 0x954B, 0xBDE0, 0x954C, 0xBDE1, 0x954D, 0xBDE2, 0x954E, 0xBDE3, 0x954F, 0xBDE4, 0x9550, 0xBDE5, + 0x9551, 0xBDE6, 0x9552, 0xBDE7, 0x9553, 0xBDE8, 0x9554, 0xBDEA, 0x9555, 0xBDEB, 0x9556, 0xBDEC, 0x9557, 0xBDED, 0x9558, 0xBDEE, + 0x9559, 0xBDEF, 0x955A, 0xBDF1, 0x9561, 0xBDF2, 0x9562, 0xBDF3, 0x9563, 0xBDF5, 0x9564, 0xBDF6, 0x9565, 0xBDF7, 0x9566, 0xBDF9, + 0x9567, 0xBDFA, 0x9568, 0xBDFB, 0x9569, 0xBDFC, 0x956A, 0xBDFD, 0x956B, 0xBDFE, 0x956C, 0xBDFF, 0x956D, 0xBE01, 0x956E, 0xBE02, + 0x956F, 0xBE04, 0x9570, 0xBE06, 0x9571, 0xBE07, 0x9572, 0xBE08, 0x9573, 0xBE09, 0x9574, 0xBE0A, 0x9575, 0xBE0B, 0x9576, 0xBE0E, + 0x9577, 0xBE0F, 0x9578, 0xBE11, 0x9579, 0xBE12, 0x957A, 0xBE13, 0x9581, 0xBE15, 0x9582, 0xBE16, 0x9583, 0xBE17, 0x9584, 0xBE18, + 0x9585, 0xBE19, 0x9586, 0xBE1A, 0x9587, 0xBE1B, 0x9588, 0xBE1E, 0x9589, 0xBE20, 0x958A, 0xBE21, 0x958B, 0xBE22, 0x958C, 0xBE23, + 0x958D, 0xBE24, 0x958E, 0xBE25, 0x958F, 0xBE26, 0x9590, 0xBE27, 0x9591, 0xBE28, 0x9592, 0xBE29, 0x9593, 0xBE2A, 0x9594, 0xBE2B, + 0x9595, 0xBE2C, 0x9596, 0xBE2D, 0x9597, 0xBE2E, 0x9598, 0xBE2F, 0x9599, 0xBE30, 0x959A, 0xBE31, 0x959B, 0xBE32, 0x959C, 0xBE33, + 0x959D, 0xBE34, 0x959E, 0xBE35, 0x959F, 0xBE36, 0x95A0, 0xBE37, 0x95A1, 0xBE38, 0x95A2, 0xBE39, 0x95A3, 0xBE3A, 0x95A4, 0xBE3B, + 0x95A5, 0xBE3C, 0x95A6, 0xBE3D, 0x95A7, 0xBE3E, 0x95A8, 0xBE3F, 0x95A9, 0xBE40, 0x95AA, 0xBE41, 0x95AB, 0xBE42, 0x95AC, 0xBE43, + 0x95AD, 0xBE46, 0x95AE, 0xBE47, 0x95AF, 0xBE49, 0x95B0, 0xBE4A, 0x95B1, 0xBE4B, 0x95B2, 0xBE4D, 0x95B3, 0xBE4F, 0x95B4, 0xBE50, + 0x95B5, 0xBE51, 0x95B6, 0xBE52, 0x95B7, 0xBE53, 0x95B8, 0xBE56, 0x95B9, 0xBE58, 0x95BA, 0xBE5C, 0x95BB, 0xBE5D, 0x95BC, 0xBE5E, + 0x95BD, 0xBE5F, 0x95BE, 0xBE62, 0x95BF, 0xBE63, 0x95C0, 0xBE65, 0x95C1, 0xBE66, 0x95C2, 0xBE67, 0x95C3, 0xBE69, 0x95C4, 0xBE6B, + 0x95C5, 0xBE6C, 0x95C6, 0xBE6D, 0x95C7, 0xBE6E, 0x95C8, 0xBE6F, 0x95C9, 0xBE72, 0x95CA, 0xBE76, 0x95CB, 0xBE77, 0x95CC, 0xBE78, + 0x95CD, 0xBE79, 0x95CE, 0xBE7A, 0x95CF, 0xBE7E, 0x95D0, 0xBE7F, 0x95D1, 0xBE81, 0x95D2, 0xBE82, 0x95D3, 0xBE83, 0x95D4, 0xBE85, + 0x95D5, 0xBE86, 0x95D6, 0xBE87, 0x95D7, 0xBE88, 0x95D8, 0xBE89, 0x95D9, 0xBE8A, 0x95DA, 0xBE8B, 0x95DB, 0xBE8E, 0x95DC, 0xBE92, + 0x95DD, 0xBE93, 0x95DE, 0xBE94, 0x95DF, 0xBE95, 0x95E0, 0xBE96, 0x95E1, 0xBE97, 0x95E2, 0xBE9A, 0x95E3, 0xBE9B, 0x95E4, 0xBE9C, + 0x95E5, 0xBE9D, 0x95E6, 0xBE9E, 0x95E7, 0xBE9F, 0x95E8, 0xBEA0, 0x95E9, 0xBEA1, 0x95EA, 0xBEA2, 0x95EB, 0xBEA3, 0x95EC, 0xBEA4, + 0x95ED, 0xBEA5, 0x95EE, 0xBEA6, 0x95EF, 0xBEA7, 0x95F0, 0xBEA9, 0x95F1, 0xBEAA, 0x95F2, 0xBEAB, 0x95F3, 0xBEAC, 0x95F4, 0xBEAD, + 0x95F5, 0xBEAE, 0x95F6, 0xBEAF, 0x95F7, 0xBEB0, 0x95F8, 0xBEB1, 0x95F9, 0xBEB2, 0x95FA, 0xBEB3, 0x95FB, 0xBEB4, 0x95FC, 0xBEB5, + 0x95FD, 0xBEB6, 0x95FE, 0xBEB7, 0x9641, 0xBEB8, 0x9642, 0xBEB9, 0x9643, 0xBEBA, 0x9644, 0xBEBB, 0x9645, 0xBEBC, 0x9646, 0xBEBD, + 0x9647, 0xBEBE, 0x9648, 0xBEBF, 0x9649, 0xBEC0, 0x964A, 0xBEC1, 0x964B, 0xBEC2, 0x964C, 0xBEC3, 0x964D, 0xBEC4, 0x964E, 0xBEC5, + 0x964F, 0xBEC6, 0x9650, 0xBEC7, 0x9651, 0xBEC8, 0x9652, 0xBEC9, 0x9653, 0xBECA, 0x9654, 0xBECB, 0x9655, 0xBECC, 0x9656, 0xBECD, + 0x9657, 0xBECE, 0x9658, 0xBECF, 0x9659, 0xBED2, 0x965A, 0xBED3, 0x9661, 0xBED5, 0x9662, 0xBED6, 0x9663, 0xBED9, 0x9664, 0xBEDA, + 0x9665, 0xBEDB, 0x9666, 0xBEDC, 0x9667, 0xBEDD, 0x9668, 0xBEDE, 0x9669, 0xBEDF, 0x966A, 0xBEE1, 0x966B, 0xBEE2, 0x966C, 0xBEE6, + 0x966D, 0xBEE7, 0x966E, 0xBEE8, 0x966F, 0xBEE9, 0x9670, 0xBEEA, 0x9671, 0xBEEB, 0x9672, 0xBEED, 0x9673, 0xBEEE, 0x9674, 0xBEEF, + 0x9675, 0xBEF0, 0x9676, 0xBEF1, 0x9677, 0xBEF2, 0x9678, 0xBEF3, 0x9679, 0xBEF4, 0x967A, 0xBEF5, 0x9681, 0xBEF6, 0x9682, 0xBEF7, + 0x9683, 0xBEF8, 0x9684, 0xBEF9, 0x9685, 0xBEFA, 0x9686, 0xBEFB, 0x9687, 0xBEFC, 0x9688, 0xBEFD, 0x9689, 0xBEFE, 0x968A, 0xBEFF, + 0x968B, 0xBF00, 0x968C, 0xBF02, 0x968D, 0xBF03, 0x968E, 0xBF04, 0x968F, 0xBF05, 0x9690, 0xBF06, 0x9691, 0xBF07, 0x9692, 0xBF0A, + 0x9693, 0xBF0B, 0x9694, 0xBF0C, 0x9695, 0xBF0D, 0x9696, 0xBF0E, 0x9697, 0xBF0F, 0x9698, 0xBF10, 0x9699, 0xBF11, 0x969A, 0xBF12, + 0x969B, 0xBF13, 0x969C, 0xBF14, 0x969D, 0xBF15, 0x969E, 0xBF16, 0x969F, 0xBF17, 0x96A0, 0xBF1A, 0x96A1, 0xBF1E, 0x96A2, 0xBF1F, + 0x96A3, 0xBF20, 0x96A4, 0xBF21, 0x96A5, 0xBF22, 0x96A6, 0xBF23, 0x96A7, 0xBF24, 0x96A8, 0xBF25, 0x96A9, 0xBF26, 0x96AA, 0xBF27, + 0x96AB, 0xBF28, 0x96AC, 0xBF29, 0x96AD, 0xBF2A, 0x96AE, 0xBF2B, 0x96AF, 0xBF2C, 0x96B0, 0xBF2D, 0x96B1, 0xBF2E, 0x96B2, 0xBF2F, + 0x96B3, 0xBF30, 0x96B4, 0xBF31, 0x96B5, 0xBF32, 0x96B6, 0xBF33, 0x96B7, 0xBF34, 0x96B8, 0xBF35, 0x96B9, 0xBF36, 0x96BA, 0xBF37, + 0x96BB, 0xBF38, 0x96BC, 0xBF39, 0x96BD, 0xBF3A, 0x96BE, 0xBF3B, 0x96BF, 0xBF3C, 0x96C0, 0xBF3D, 0x96C1, 0xBF3E, 0x96C2, 0xBF3F, + 0x96C3, 0xBF42, 0x96C4, 0xBF43, 0x96C5, 0xBF45, 0x96C6, 0xBF46, 0x96C7, 0xBF47, 0x96C8, 0xBF49, 0x96C9, 0xBF4A, 0x96CA, 0xBF4B, + 0x96CB, 0xBF4C, 0x96CC, 0xBF4D, 0x96CD, 0xBF4E, 0x96CE, 0xBF4F, 0x96CF, 0xBF52, 0x96D0, 0xBF53, 0x96D1, 0xBF54, 0x96D2, 0xBF56, + 0x96D3, 0xBF57, 0x96D4, 0xBF58, 0x96D5, 0xBF59, 0x96D6, 0xBF5A, 0x96D7, 0xBF5B, 0x96D8, 0xBF5C, 0x96D9, 0xBF5D, 0x96DA, 0xBF5E, + 0x96DB, 0xBF5F, 0x96DC, 0xBF60, 0x96DD, 0xBF61, 0x96DE, 0xBF62, 0x96DF, 0xBF63, 0x96E0, 0xBF64, 0x96E1, 0xBF65, 0x96E2, 0xBF66, + 0x96E3, 0xBF67, 0x96E4, 0xBF68, 0x96E5, 0xBF69, 0x96E6, 0xBF6A, 0x96E7, 0xBF6B, 0x96E8, 0xBF6C, 0x96E9, 0xBF6D, 0x96EA, 0xBF6E, + 0x96EB, 0xBF6F, 0x96EC, 0xBF70, 0x96ED, 0xBF71, 0x96EE, 0xBF72, 0x96EF, 0xBF73, 0x96F0, 0xBF74, 0x96F1, 0xBF75, 0x96F2, 0xBF76, + 0x96F3, 0xBF77, 0x96F4, 0xBF78, 0x96F5, 0xBF79, 0x96F6, 0xBF7A, 0x96F7, 0xBF7B, 0x96F8, 0xBF7C, 0x96F9, 0xBF7D, 0x96FA, 0xBF7E, + 0x96FB, 0xBF7F, 0x96FC, 0xBF80, 0x96FD, 0xBF81, 0x96FE, 0xBF82, 0x9741, 0xBF83, 0x9742, 0xBF84, 0x9743, 0xBF85, 0x9744, 0xBF86, + 0x9745, 0xBF87, 0x9746, 0xBF88, 0x9747, 0xBF89, 0x9748, 0xBF8A, 0x9749, 0xBF8B, 0x974A, 0xBF8C, 0x974B, 0xBF8D, 0x974C, 0xBF8E, + 0x974D, 0xBF8F, 0x974E, 0xBF90, 0x974F, 0xBF91, 0x9750, 0xBF92, 0x9751, 0xBF93, 0x9752, 0xBF95, 0x9753, 0xBF96, 0x9754, 0xBF97, + 0x9755, 0xBF98, 0x9756, 0xBF99, 0x9757, 0xBF9A, 0x9758, 0xBF9B, 0x9759, 0xBF9C, 0x975A, 0xBF9D, 0x9761, 0xBF9E, 0x9762, 0xBF9F, + 0x9763, 0xBFA0, 0x9764, 0xBFA1, 0x9765, 0xBFA2, 0x9766, 0xBFA3, 0x9767, 0xBFA4, 0x9768, 0xBFA5, 0x9769, 0xBFA6, 0x976A, 0xBFA7, + 0x976B, 0xBFA8, 0x976C, 0xBFA9, 0x976D, 0xBFAA, 0x976E, 0xBFAB, 0x976F, 0xBFAC, 0x9770, 0xBFAD, 0x9771, 0xBFAE, 0x9772, 0xBFAF, + 0x9773, 0xBFB1, 0x9774, 0xBFB2, 0x9775, 0xBFB3, 0x9776, 0xBFB4, 0x9777, 0xBFB5, 0x9778, 0xBFB6, 0x9779, 0xBFB7, 0x977A, 0xBFB8, + 0x9781, 0xBFB9, 0x9782, 0xBFBA, 0x9783, 0xBFBB, 0x9784, 0xBFBC, 0x9785, 0xBFBD, 0x9786, 0xBFBE, 0x9787, 0xBFBF, 0x9788, 0xBFC0, + 0x9789, 0xBFC1, 0x978A, 0xBFC2, 0x978B, 0xBFC3, 0x978C, 0xBFC4, 0x978D, 0xBFC6, 0x978E, 0xBFC7, 0x978F, 0xBFC8, 0x9790, 0xBFC9, + 0x9791, 0xBFCA, 0x9792, 0xBFCB, 0x9793, 0xBFCE, 0x9794, 0xBFCF, 0x9795, 0xBFD1, 0x9796, 0xBFD2, 0x9797, 0xBFD3, 0x9798, 0xBFD5, + 0x9799, 0xBFD6, 0x979A, 0xBFD7, 0x979B, 0xBFD8, 0x979C, 0xBFD9, 0x979D, 0xBFDA, 0x979E, 0xBFDB, 0x979F, 0xBFDD, 0x97A0, 0xBFDE, + 0x97A1, 0xBFE0, 0x97A2, 0xBFE2, 0x97A3, 0xBFE3, 0x97A4, 0xBFE4, 0x97A5, 0xBFE5, 0x97A6, 0xBFE6, 0x97A7, 0xBFE7, 0x97A8, 0xBFE8, + 0x97A9, 0xBFE9, 0x97AA, 0xBFEA, 0x97AB, 0xBFEB, 0x97AC, 0xBFEC, 0x97AD, 0xBFED, 0x97AE, 0xBFEE, 0x97AF, 0xBFEF, 0x97B0, 0xBFF0, + 0x97B1, 0xBFF1, 0x97B2, 0xBFF2, 0x97B3, 0xBFF3, 0x97B4, 0xBFF4, 0x97B5, 0xBFF5, 0x97B6, 0xBFF6, 0x97B7, 0xBFF7, 0x97B8, 0xBFF8, + 0x97B9, 0xBFF9, 0x97BA, 0xBFFA, 0x97BB, 0xBFFB, 0x97BC, 0xBFFC, 0x97BD, 0xBFFD, 0x97BE, 0xBFFE, 0x97BF, 0xBFFF, 0x97C0, 0xC000, + 0x97C1, 0xC001, 0x97C2, 0xC002, 0x97C3, 0xC003, 0x97C4, 0xC004, 0x97C5, 0xC005, 0x97C6, 0xC006, 0x97C7, 0xC007, 0x97C8, 0xC008, + 0x97C9, 0xC009, 0x97CA, 0xC00A, 0x97CB, 0xC00B, 0x97CC, 0xC00C, 0x97CD, 0xC00D, 0x97CE, 0xC00E, 0x97CF, 0xC00F, 0x97D0, 0xC010, + 0x97D1, 0xC011, 0x97D2, 0xC012, 0x97D3, 0xC013, 0x97D4, 0xC014, 0x97D5, 0xC015, 0x97D6, 0xC016, 0x97D7, 0xC017, 0x97D8, 0xC018, + 0x97D9, 0xC019, 0x97DA, 0xC01A, 0x97DB, 0xC01B, 0x97DC, 0xC01C, 0x97DD, 0xC01D, 0x97DE, 0xC01E, 0x97DF, 0xC01F, 0x97E0, 0xC020, + 0x97E1, 0xC021, 0x97E2, 0xC022, 0x97E3, 0xC023, 0x97E4, 0xC024, 0x97E5, 0xC025, 0x97E6, 0xC026, 0x97E7, 0xC027, 0x97E8, 0xC028, + 0x97E9, 0xC029, 0x97EA, 0xC02A, 0x97EB, 0xC02B, 0x97EC, 0xC02C, 0x97ED, 0xC02D, 0x97EE, 0xC02E, 0x97EF, 0xC02F, 0x97F0, 0xC030, + 0x97F1, 0xC031, 0x97F2, 0xC032, 0x97F3, 0xC033, 0x97F4, 0xC034, 0x97F5, 0xC035, 0x97F6, 0xC036, 0x97F7, 0xC037, 0x97F8, 0xC038, + 0x97F9, 0xC039, 0x97FA, 0xC03A, 0x97FB, 0xC03B, 0x97FC, 0xC03D, 0x97FD, 0xC03E, 0x97FE, 0xC03F, 0x9841, 0xC040, 0x9842, 0xC041, + 0x9843, 0xC042, 0x9844, 0xC043, 0x9845, 0xC044, 0x9846, 0xC045, 0x9847, 0xC046, 0x9848, 0xC047, 0x9849, 0xC048, 0x984A, 0xC049, + 0x984B, 0xC04A, 0x984C, 0xC04B, 0x984D, 0xC04C, 0x984E, 0xC04D, 0x984F, 0xC04E, 0x9850, 0xC04F, 0x9851, 0xC050, 0x9852, 0xC052, + 0x9853, 0xC053, 0x9854, 0xC054, 0x9855, 0xC055, 0x9856, 0xC056, 0x9857, 0xC057, 0x9858, 0xC059, 0x9859, 0xC05A, 0x985A, 0xC05B, + 0x9861, 0xC05D, 0x9862, 0xC05E, 0x9863, 0xC05F, 0x9864, 0xC061, 0x9865, 0xC062, 0x9866, 0xC063, 0x9867, 0xC064, 0x9868, 0xC065, + 0x9869, 0xC066, 0x986A, 0xC067, 0x986B, 0xC06A, 0x986C, 0xC06B, 0x986D, 0xC06C, 0x986E, 0xC06D, 0x986F, 0xC06E, 0x9870, 0xC06F, + 0x9871, 0xC070, 0x9872, 0xC071, 0x9873, 0xC072, 0x9874, 0xC073, 0x9875, 0xC074, 0x9876, 0xC075, 0x9877, 0xC076, 0x9878, 0xC077, + 0x9879, 0xC078, 0x987A, 0xC079, 0x9881, 0xC07A, 0x9882, 0xC07B, 0x9883, 0xC07C, 0x9884, 0xC07D, 0x9885, 0xC07E, 0x9886, 0xC07F, + 0x9887, 0xC080, 0x9888, 0xC081, 0x9889, 0xC082, 0x988A, 0xC083, 0x988B, 0xC084, 0x988C, 0xC085, 0x988D, 0xC086, 0x988E, 0xC087, + 0x988F, 0xC088, 0x9890, 0xC089, 0x9891, 0xC08A, 0x9892, 0xC08B, 0x9893, 0xC08C, 0x9894, 0xC08D, 0x9895, 0xC08E, 0x9896, 0xC08F, + 0x9897, 0xC092, 0x9898, 0xC093, 0x9899, 0xC095, 0x989A, 0xC096, 0x989B, 0xC097, 0x989C, 0xC099, 0x989D, 0xC09A, 0x989E, 0xC09B, + 0x989F, 0xC09C, 0x98A0, 0xC09D, 0x98A1, 0xC09E, 0x98A2, 0xC09F, 0x98A3, 0xC0A2, 0x98A4, 0xC0A4, 0x98A5, 0xC0A6, 0x98A6, 0xC0A7, + 0x98A7, 0xC0A8, 0x98A8, 0xC0A9, 0x98A9, 0xC0AA, 0x98AA, 0xC0AB, 0x98AB, 0xC0AE, 0x98AC, 0xC0B1, 0x98AD, 0xC0B2, 0x98AE, 0xC0B7, + 0x98AF, 0xC0B8, 0x98B0, 0xC0B9, 0x98B1, 0xC0BA, 0x98B2, 0xC0BB, 0x98B3, 0xC0BE, 0x98B4, 0xC0C2, 0x98B5, 0xC0C3, 0x98B6, 0xC0C4, + 0x98B7, 0xC0C6, 0x98B8, 0xC0C7, 0x98B9, 0xC0CA, 0x98BA, 0xC0CB, 0x98BB, 0xC0CD, 0x98BC, 0xC0CE, 0x98BD, 0xC0CF, 0x98BE, 0xC0D1, + 0x98BF, 0xC0D2, 0x98C0, 0xC0D3, 0x98C1, 0xC0D4, 0x98C2, 0xC0D5, 0x98C3, 0xC0D6, 0x98C4, 0xC0D7, 0x98C5, 0xC0DA, 0x98C6, 0xC0DE, + 0x98C7, 0xC0DF, 0x98C8, 0xC0E0, 0x98C9, 0xC0E1, 0x98CA, 0xC0E2, 0x98CB, 0xC0E3, 0x98CC, 0xC0E6, 0x98CD, 0xC0E7, 0x98CE, 0xC0E9, + 0x98CF, 0xC0EA, 0x98D0, 0xC0EB, 0x98D1, 0xC0ED, 0x98D2, 0xC0EE, 0x98D3, 0xC0EF, 0x98D4, 0xC0F0, 0x98D5, 0xC0F1, 0x98D6, 0xC0F2, + 0x98D7, 0xC0F3, 0x98D8, 0xC0F6, 0x98D9, 0xC0F8, 0x98DA, 0xC0FA, 0x98DB, 0xC0FB, 0x98DC, 0xC0FC, 0x98DD, 0xC0FD, 0x98DE, 0xC0FE, + 0x98DF, 0xC0FF, 0x98E0, 0xC101, 0x98E1, 0xC102, 0x98E2, 0xC103, 0x98E3, 0xC105, 0x98E4, 0xC106, 0x98E5, 0xC107, 0x98E6, 0xC109, + 0x98E7, 0xC10A, 0x98E8, 0xC10B, 0x98E9, 0xC10C, 0x98EA, 0xC10D, 0x98EB, 0xC10E, 0x98EC, 0xC10F, 0x98ED, 0xC111, 0x98EE, 0xC112, + 0x98EF, 0xC113, 0x98F0, 0xC114, 0x98F1, 0xC116, 0x98F2, 0xC117, 0x98F3, 0xC118, 0x98F4, 0xC119, 0x98F5, 0xC11A, 0x98F6, 0xC11B, + 0x98F7, 0xC121, 0x98F8, 0xC122, 0x98F9, 0xC125, 0x98FA, 0xC128, 0x98FB, 0xC129, 0x98FC, 0xC12A, 0x98FD, 0xC12B, 0x98FE, 0xC12E, + 0x9941, 0xC132, 0x9942, 0xC133, 0x9943, 0xC134, 0x9944, 0xC135, 0x9945, 0xC137, 0x9946, 0xC13A, 0x9947, 0xC13B, 0x9948, 0xC13D, + 0x9949, 0xC13E, 0x994A, 0xC13F, 0x994B, 0xC141, 0x994C, 0xC142, 0x994D, 0xC143, 0x994E, 0xC144, 0x994F, 0xC145, 0x9950, 0xC146, + 0x9951, 0xC147, 0x9952, 0xC14A, 0x9953, 0xC14E, 0x9954, 0xC14F, 0x9955, 0xC150, 0x9956, 0xC151, 0x9957, 0xC152, 0x9958, 0xC153, + 0x9959, 0xC156, 0x995A, 0xC157, 0x9961, 0xC159, 0x9962, 0xC15A, 0x9963, 0xC15B, 0x9964, 0xC15D, 0x9965, 0xC15E, 0x9966, 0xC15F, + 0x9967, 0xC160, 0x9968, 0xC161, 0x9969, 0xC162, 0x996A, 0xC163, 0x996B, 0xC166, 0x996C, 0xC16A, 0x996D, 0xC16B, 0x996E, 0xC16C, + 0x996F, 0xC16D, 0x9970, 0xC16E, 0x9971, 0xC16F, 0x9972, 0xC171, 0x9973, 0xC172, 0x9974, 0xC173, 0x9975, 0xC175, 0x9976, 0xC176, + 0x9977, 0xC177, 0x9978, 0xC179, 0x9979, 0xC17A, 0x997A, 0xC17B, 0x9981, 0xC17C, 0x9982, 0xC17D, 0x9983, 0xC17E, 0x9984, 0xC17F, + 0x9985, 0xC180, 0x9986, 0xC181, 0x9987, 0xC182, 0x9988, 0xC183, 0x9989, 0xC184, 0x998A, 0xC186, 0x998B, 0xC187, 0x998C, 0xC188, + 0x998D, 0xC189, 0x998E, 0xC18A, 0x998F, 0xC18B, 0x9990, 0xC18F, 0x9991, 0xC191, 0x9992, 0xC192, 0x9993, 0xC193, 0x9994, 0xC195, + 0x9995, 0xC197, 0x9996, 0xC198, 0x9997, 0xC199, 0x9998, 0xC19A, 0x9999, 0xC19B, 0x999A, 0xC19E, 0x999B, 0xC1A0, 0x999C, 0xC1A2, + 0x999D, 0xC1A3, 0x999E, 0xC1A4, 0x999F, 0xC1A6, 0x99A0, 0xC1A7, 0x99A1, 0xC1AA, 0x99A2, 0xC1AB, 0x99A3, 0xC1AD, 0x99A4, 0xC1AE, + 0x99A5, 0xC1AF, 0x99A6, 0xC1B1, 0x99A7, 0xC1B2, 0x99A8, 0xC1B3, 0x99A9, 0xC1B4, 0x99AA, 0xC1B5, 0x99AB, 0xC1B6, 0x99AC, 0xC1B7, + 0x99AD, 0xC1B8, 0x99AE, 0xC1B9, 0x99AF, 0xC1BA, 0x99B0, 0xC1BB, 0x99B1, 0xC1BC, 0x99B2, 0xC1BE, 0x99B3, 0xC1BF, 0x99B4, 0xC1C0, + 0x99B5, 0xC1C1, 0x99B6, 0xC1C2, 0x99B7, 0xC1C3, 0x99B8, 0xC1C5, 0x99B9, 0xC1C6, 0x99BA, 0xC1C7, 0x99BB, 0xC1C9, 0x99BC, 0xC1CA, + 0x99BD, 0xC1CB, 0x99BE, 0xC1CD, 0x99BF, 0xC1CE, 0x99C0, 0xC1CF, 0x99C1, 0xC1D0, 0x99C2, 0xC1D1, 0x99C3, 0xC1D2, 0x99C4, 0xC1D3, + 0x99C5, 0xC1D5, 0x99C6, 0xC1D6, 0x99C7, 0xC1D9, 0x99C8, 0xC1DA, 0x99C9, 0xC1DB, 0x99CA, 0xC1DC, 0x99CB, 0xC1DD, 0x99CC, 0xC1DE, + 0x99CD, 0xC1DF, 0x99CE, 0xC1E1, 0x99CF, 0xC1E2, 0x99D0, 0xC1E3, 0x99D1, 0xC1E5, 0x99D2, 0xC1E6, 0x99D3, 0xC1E7, 0x99D4, 0xC1E9, + 0x99D5, 0xC1EA, 0x99D6, 0xC1EB, 0x99D7, 0xC1EC, 0x99D8, 0xC1ED, 0x99D9, 0xC1EE, 0x99DA, 0xC1EF, 0x99DB, 0xC1F2, 0x99DC, 0xC1F4, + 0x99DD, 0xC1F5, 0x99DE, 0xC1F6, 0x99DF, 0xC1F7, 0x99E0, 0xC1F8, 0x99E1, 0xC1F9, 0x99E2, 0xC1FA, 0x99E3, 0xC1FB, 0x99E4, 0xC1FE, + 0x99E5, 0xC1FF, 0x99E6, 0xC201, 0x99E7, 0xC202, 0x99E8, 0xC203, 0x99E9, 0xC205, 0x99EA, 0xC206, 0x99EB, 0xC207, 0x99EC, 0xC208, + 0x99ED, 0xC209, 0x99EE, 0xC20A, 0x99EF, 0xC20B, 0x99F0, 0xC20E, 0x99F1, 0xC210, 0x99F2, 0xC212, 0x99F3, 0xC213, 0x99F4, 0xC214, + 0x99F5, 0xC215, 0x99F6, 0xC216, 0x99F7, 0xC217, 0x99F8, 0xC21A, 0x99F9, 0xC21B, 0x99FA, 0xC21D, 0x99FB, 0xC21E, 0x99FC, 0xC221, + 0x99FD, 0xC222, 0x99FE, 0xC223, 0x9A41, 0xC224, 0x9A42, 0xC225, 0x9A43, 0xC226, 0x9A44, 0xC227, 0x9A45, 0xC22A, 0x9A46, 0xC22C, + 0x9A47, 0xC22E, 0x9A48, 0xC230, 0x9A49, 0xC233, 0x9A4A, 0xC235, 0x9A4B, 0xC236, 0x9A4C, 0xC237, 0x9A4D, 0xC238, 0x9A4E, 0xC239, + 0x9A4F, 0xC23A, 0x9A50, 0xC23B, 0x9A51, 0xC23C, 0x9A52, 0xC23D, 0x9A53, 0xC23E, 0x9A54, 0xC23F, 0x9A55, 0xC240, 0x9A56, 0xC241, + 0x9A57, 0xC242, 0x9A58, 0xC243, 0x9A59, 0xC244, 0x9A5A, 0xC245, 0x9A61, 0xC246, 0x9A62, 0xC247, 0x9A63, 0xC249, 0x9A64, 0xC24A, + 0x9A65, 0xC24B, 0x9A66, 0xC24C, 0x9A67, 0xC24D, 0x9A68, 0xC24E, 0x9A69, 0xC24F, 0x9A6A, 0xC252, 0x9A6B, 0xC253, 0x9A6C, 0xC255, + 0x9A6D, 0xC256, 0x9A6E, 0xC257, 0x9A6F, 0xC259, 0x9A70, 0xC25A, 0x9A71, 0xC25B, 0x9A72, 0xC25C, 0x9A73, 0xC25D, 0x9A74, 0xC25E, + 0x9A75, 0xC25F, 0x9A76, 0xC261, 0x9A77, 0xC262, 0x9A78, 0xC263, 0x9A79, 0xC264, 0x9A7A, 0xC266, 0x9A81, 0xC267, 0x9A82, 0xC268, + 0x9A83, 0xC269, 0x9A84, 0xC26A, 0x9A85, 0xC26B, 0x9A86, 0xC26E, 0x9A87, 0xC26F, 0x9A88, 0xC271, 0x9A89, 0xC272, 0x9A8A, 0xC273, + 0x9A8B, 0xC275, 0x9A8C, 0xC276, 0x9A8D, 0xC277, 0x9A8E, 0xC278, 0x9A8F, 0xC279, 0x9A90, 0xC27A, 0x9A91, 0xC27B, 0x9A92, 0xC27E, + 0x9A93, 0xC280, 0x9A94, 0xC282, 0x9A95, 0xC283, 0x9A96, 0xC284, 0x9A97, 0xC285, 0x9A98, 0xC286, 0x9A99, 0xC287, 0x9A9A, 0xC28A, + 0x9A9B, 0xC28B, 0x9A9C, 0xC28C, 0x9A9D, 0xC28D, 0x9A9E, 0xC28E, 0x9A9F, 0xC28F, 0x9AA0, 0xC291, 0x9AA1, 0xC292, 0x9AA2, 0xC293, + 0x9AA3, 0xC294, 0x9AA4, 0xC295, 0x9AA5, 0xC296, 0x9AA6, 0xC297, 0x9AA7, 0xC299, 0x9AA8, 0xC29A, 0x9AA9, 0xC29C, 0x9AAA, 0xC29E, + 0x9AAB, 0xC29F, 0x9AAC, 0xC2A0, 0x9AAD, 0xC2A1, 0x9AAE, 0xC2A2, 0x9AAF, 0xC2A3, 0x9AB0, 0xC2A6, 0x9AB1, 0xC2A7, 0x9AB2, 0xC2A9, + 0x9AB3, 0xC2AA, 0x9AB4, 0xC2AB, 0x9AB5, 0xC2AE, 0x9AB6, 0xC2AF, 0x9AB7, 0xC2B0, 0x9AB8, 0xC2B1, 0x9AB9, 0xC2B2, 0x9ABA, 0xC2B3, + 0x9ABB, 0xC2B6, 0x9ABC, 0xC2B8, 0x9ABD, 0xC2BA, 0x9ABE, 0xC2BB, 0x9ABF, 0xC2BC, 0x9AC0, 0xC2BD, 0x9AC1, 0xC2BE, 0x9AC2, 0xC2BF, + 0x9AC3, 0xC2C0, 0x9AC4, 0xC2C1, 0x9AC5, 0xC2C2, 0x9AC6, 0xC2C3, 0x9AC7, 0xC2C4, 0x9AC8, 0xC2C5, 0x9AC9, 0xC2C6, 0x9ACA, 0xC2C7, + 0x9ACB, 0xC2C8, 0x9ACC, 0xC2C9, 0x9ACD, 0xC2CA, 0x9ACE, 0xC2CB, 0x9ACF, 0xC2CC, 0x9AD0, 0xC2CD, 0x9AD1, 0xC2CE, 0x9AD2, 0xC2CF, + 0x9AD3, 0xC2D0, 0x9AD4, 0xC2D1, 0x9AD5, 0xC2D2, 0x9AD6, 0xC2D3, 0x9AD7, 0xC2D4, 0x9AD8, 0xC2D5, 0x9AD9, 0xC2D6, 0x9ADA, 0xC2D7, + 0x9ADB, 0xC2D8, 0x9ADC, 0xC2D9, 0x9ADD, 0xC2DA, 0x9ADE, 0xC2DB, 0x9ADF, 0xC2DE, 0x9AE0, 0xC2DF, 0x9AE1, 0xC2E1, 0x9AE2, 0xC2E2, + 0x9AE3, 0xC2E5, 0x9AE4, 0xC2E6, 0x9AE5, 0xC2E7, 0x9AE6, 0xC2E8, 0x9AE7, 0xC2E9, 0x9AE8, 0xC2EA, 0x9AE9, 0xC2EE, 0x9AEA, 0xC2F0, + 0x9AEB, 0xC2F2, 0x9AEC, 0xC2F3, 0x9AED, 0xC2F4, 0x9AEE, 0xC2F5, 0x9AEF, 0xC2F7, 0x9AF0, 0xC2FA, 0x9AF1, 0xC2FD, 0x9AF2, 0xC2FE, + 0x9AF3, 0xC2FF, 0x9AF4, 0xC301, 0x9AF5, 0xC302, 0x9AF6, 0xC303, 0x9AF7, 0xC304, 0x9AF8, 0xC305, 0x9AF9, 0xC306, 0x9AFA, 0xC307, + 0x9AFB, 0xC30A, 0x9AFC, 0xC30B, 0x9AFD, 0xC30E, 0x9AFE, 0xC30F, 0x9B41, 0xC310, 0x9B42, 0xC311, 0x9B43, 0xC312, 0x9B44, 0xC316, + 0x9B45, 0xC317, 0x9B46, 0xC319, 0x9B47, 0xC31A, 0x9B48, 0xC31B, 0x9B49, 0xC31D, 0x9B4A, 0xC31E, 0x9B4B, 0xC31F, 0x9B4C, 0xC320, + 0x9B4D, 0xC321, 0x9B4E, 0xC322, 0x9B4F, 0xC323, 0x9B50, 0xC326, 0x9B51, 0xC327, 0x9B52, 0xC32A, 0x9B53, 0xC32B, 0x9B54, 0xC32C, + 0x9B55, 0xC32D, 0x9B56, 0xC32E, 0x9B57, 0xC32F, 0x9B58, 0xC330, 0x9B59, 0xC331, 0x9B5A, 0xC332, 0x9B61, 0xC333, 0x9B62, 0xC334, + 0x9B63, 0xC335, 0x9B64, 0xC336, 0x9B65, 0xC337, 0x9B66, 0xC338, 0x9B67, 0xC339, 0x9B68, 0xC33A, 0x9B69, 0xC33B, 0x9B6A, 0xC33C, + 0x9B6B, 0xC33D, 0x9B6C, 0xC33E, 0x9B6D, 0xC33F, 0x9B6E, 0xC340, 0x9B6F, 0xC341, 0x9B70, 0xC342, 0x9B71, 0xC343, 0x9B72, 0xC344, + 0x9B73, 0xC346, 0x9B74, 0xC347, 0x9B75, 0xC348, 0x9B76, 0xC349, 0x9B77, 0xC34A, 0x9B78, 0xC34B, 0x9B79, 0xC34C, 0x9B7A, 0xC34D, + 0x9B81, 0xC34E, 0x9B82, 0xC34F, 0x9B83, 0xC350, 0x9B84, 0xC351, 0x9B85, 0xC352, 0x9B86, 0xC353, 0x9B87, 0xC354, 0x9B88, 0xC355, + 0x9B89, 0xC356, 0x9B8A, 0xC357, 0x9B8B, 0xC358, 0x9B8C, 0xC359, 0x9B8D, 0xC35A, 0x9B8E, 0xC35B, 0x9B8F, 0xC35C, 0x9B90, 0xC35D, + 0x9B91, 0xC35E, 0x9B92, 0xC35F, 0x9B93, 0xC360, 0x9B94, 0xC361, 0x9B95, 0xC362, 0x9B96, 0xC363, 0x9B97, 0xC364, 0x9B98, 0xC365, + 0x9B99, 0xC366, 0x9B9A, 0xC367, 0x9B9B, 0xC36A, 0x9B9C, 0xC36B, 0x9B9D, 0xC36D, 0x9B9E, 0xC36E, 0x9B9F, 0xC36F, 0x9BA0, 0xC371, + 0x9BA1, 0xC373, 0x9BA2, 0xC374, 0x9BA3, 0xC375, 0x9BA4, 0xC376, 0x9BA5, 0xC377, 0x9BA6, 0xC37A, 0x9BA7, 0xC37B, 0x9BA8, 0xC37E, + 0x9BA9, 0xC37F, 0x9BAA, 0xC380, 0x9BAB, 0xC381, 0x9BAC, 0xC382, 0x9BAD, 0xC383, 0x9BAE, 0xC385, 0x9BAF, 0xC386, 0x9BB0, 0xC387, + 0x9BB1, 0xC389, 0x9BB2, 0xC38A, 0x9BB3, 0xC38B, 0x9BB4, 0xC38D, 0x9BB5, 0xC38E, 0x9BB6, 0xC38F, 0x9BB7, 0xC390, 0x9BB8, 0xC391, + 0x9BB9, 0xC392, 0x9BBA, 0xC393, 0x9BBB, 0xC394, 0x9BBC, 0xC395, 0x9BBD, 0xC396, 0x9BBE, 0xC397, 0x9BBF, 0xC398, 0x9BC0, 0xC399, + 0x9BC1, 0xC39A, 0x9BC2, 0xC39B, 0x9BC3, 0xC39C, 0x9BC4, 0xC39D, 0x9BC5, 0xC39E, 0x9BC6, 0xC39F, 0x9BC7, 0xC3A0, 0x9BC8, 0xC3A1, + 0x9BC9, 0xC3A2, 0x9BCA, 0xC3A3, 0x9BCB, 0xC3A4, 0x9BCC, 0xC3A5, 0x9BCD, 0xC3A6, 0x9BCE, 0xC3A7, 0x9BCF, 0xC3A8, 0x9BD0, 0xC3A9, + 0x9BD1, 0xC3AA, 0x9BD2, 0xC3AB, 0x9BD3, 0xC3AC, 0x9BD4, 0xC3AD, 0x9BD5, 0xC3AE, 0x9BD6, 0xC3AF, 0x9BD7, 0xC3B0, 0x9BD8, 0xC3B1, + 0x9BD9, 0xC3B2, 0x9BDA, 0xC3B3, 0x9BDB, 0xC3B4, 0x9BDC, 0xC3B5, 0x9BDD, 0xC3B6, 0x9BDE, 0xC3B7, 0x9BDF, 0xC3B8, 0x9BE0, 0xC3B9, + 0x9BE1, 0xC3BA, 0x9BE2, 0xC3BB, 0x9BE3, 0xC3BC, 0x9BE4, 0xC3BD, 0x9BE5, 0xC3BE, 0x9BE6, 0xC3BF, 0x9BE7, 0xC3C1, 0x9BE8, 0xC3C2, + 0x9BE9, 0xC3C3, 0x9BEA, 0xC3C4, 0x9BEB, 0xC3C5, 0x9BEC, 0xC3C6, 0x9BED, 0xC3C7, 0x9BEE, 0xC3C8, 0x9BEF, 0xC3C9, 0x9BF0, 0xC3CA, + 0x9BF1, 0xC3CB, 0x9BF2, 0xC3CC, 0x9BF3, 0xC3CD, 0x9BF4, 0xC3CE, 0x9BF5, 0xC3CF, 0x9BF6, 0xC3D0, 0x9BF7, 0xC3D1, 0x9BF8, 0xC3D2, + 0x9BF9, 0xC3D3, 0x9BFA, 0xC3D4, 0x9BFB, 0xC3D5, 0x9BFC, 0xC3D6, 0x9BFD, 0xC3D7, 0x9BFE, 0xC3DA, 0x9C41, 0xC3DB, 0x9C42, 0xC3DD, + 0x9C43, 0xC3DE, 0x9C44, 0xC3E1, 0x9C45, 0xC3E3, 0x9C46, 0xC3E4, 0x9C47, 0xC3E5, 0x9C48, 0xC3E6, 0x9C49, 0xC3E7, 0x9C4A, 0xC3EA, + 0x9C4B, 0xC3EB, 0x9C4C, 0xC3EC, 0x9C4D, 0xC3EE, 0x9C4E, 0xC3EF, 0x9C4F, 0xC3F0, 0x9C50, 0xC3F1, 0x9C51, 0xC3F2, 0x9C52, 0xC3F3, + 0x9C53, 0xC3F6, 0x9C54, 0xC3F7, 0x9C55, 0xC3F9, 0x9C56, 0xC3FA, 0x9C57, 0xC3FB, 0x9C58, 0xC3FC, 0x9C59, 0xC3FD, 0x9C5A, 0xC3FE, + 0x9C61, 0xC3FF, 0x9C62, 0xC400, 0x9C63, 0xC401, 0x9C64, 0xC402, 0x9C65, 0xC403, 0x9C66, 0xC404, 0x9C67, 0xC405, 0x9C68, 0xC406, + 0x9C69, 0xC407, 0x9C6A, 0xC409, 0x9C6B, 0xC40A, 0x9C6C, 0xC40B, 0x9C6D, 0xC40C, 0x9C6E, 0xC40D, 0x9C6F, 0xC40E, 0x9C70, 0xC40F, + 0x9C71, 0xC411, 0x9C72, 0xC412, 0x9C73, 0xC413, 0x9C74, 0xC414, 0x9C75, 0xC415, 0x9C76, 0xC416, 0x9C77, 0xC417, 0x9C78, 0xC418, + 0x9C79, 0xC419, 0x9C7A, 0xC41A, 0x9C81, 0xC41B, 0x9C82, 0xC41C, 0x9C83, 0xC41D, 0x9C84, 0xC41E, 0x9C85, 0xC41F, 0x9C86, 0xC420, + 0x9C87, 0xC421, 0x9C88, 0xC422, 0x9C89, 0xC423, 0x9C8A, 0xC425, 0x9C8B, 0xC426, 0x9C8C, 0xC427, 0x9C8D, 0xC428, 0x9C8E, 0xC429, + 0x9C8F, 0xC42A, 0x9C90, 0xC42B, 0x9C91, 0xC42D, 0x9C92, 0xC42E, 0x9C93, 0xC42F, 0x9C94, 0xC431, 0x9C95, 0xC432, 0x9C96, 0xC433, + 0x9C97, 0xC435, 0x9C98, 0xC436, 0x9C99, 0xC437, 0x9C9A, 0xC438, 0x9C9B, 0xC439, 0x9C9C, 0xC43A, 0x9C9D, 0xC43B, 0x9C9E, 0xC43E, + 0x9C9F, 0xC43F, 0x9CA0, 0xC440, 0x9CA1, 0xC441, 0x9CA2, 0xC442, 0x9CA3, 0xC443, 0x9CA4, 0xC444, 0x9CA5, 0xC445, 0x9CA6, 0xC446, + 0x9CA7, 0xC447, 0x9CA8, 0xC449, 0x9CA9, 0xC44A, 0x9CAA, 0xC44B, 0x9CAB, 0xC44C, 0x9CAC, 0xC44D, 0x9CAD, 0xC44E, 0x9CAE, 0xC44F, + 0x9CAF, 0xC450, 0x9CB0, 0xC451, 0x9CB1, 0xC452, 0x9CB2, 0xC453, 0x9CB3, 0xC454, 0x9CB4, 0xC455, 0x9CB5, 0xC456, 0x9CB6, 0xC457, + 0x9CB7, 0xC458, 0x9CB8, 0xC459, 0x9CB9, 0xC45A, 0x9CBA, 0xC45B, 0x9CBB, 0xC45C, 0x9CBC, 0xC45D, 0x9CBD, 0xC45E, 0x9CBE, 0xC45F, + 0x9CBF, 0xC460, 0x9CC0, 0xC461, 0x9CC1, 0xC462, 0x9CC2, 0xC463, 0x9CC3, 0xC466, 0x9CC4, 0xC467, 0x9CC5, 0xC469, 0x9CC6, 0xC46A, + 0x9CC7, 0xC46B, 0x9CC8, 0xC46D, 0x9CC9, 0xC46E, 0x9CCA, 0xC46F, 0x9CCB, 0xC470, 0x9CCC, 0xC471, 0x9CCD, 0xC472, 0x9CCE, 0xC473, + 0x9CCF, 0xC476, 0x9CD0, 0xC477, 0x9CD1, 0xC478, 0x9CD2, 0xC47A, 0x9CD3, 0xC47B, 0x9CD4, 0xC47C, 0x9CD5, 0xC47D, 0x9CD6, 0xC47E, + 0x9CD7, 0xC47F, 0x9CD8, 0xC481, 0x9CD9, 0xC482, 0x9CDA, 0xC483, 0x9CDB, 0xC484, 0x9CDC, 0xC485, 0x9CDD, 0xC486, 0x9CDE, 0xC487, + 0x9CDF, 0xC488, 0x9CE0, 0xC489, 0x9CE1, 0xC48A, 0x9CE2, 0xC48B, 0x9CE3, 0xC48C, 0x9CE4, 0xC48D, 0x9CE5, 0xC48E, 0x9CE6, 0xC48F, + 0x9CE7, 0xC490, 0x9CE8, 0xC491, 0x9CE9, 0xC492, 0x9CEA, 0xC493, 0x9CEB, 0xC495, 0x9CEC, 0xC496, 0x9CED, 0xC497, 0x9CEE, 0xC498, + 0x9CEF, 0xC499, 0x9CF0, 0xC49A, 0x9CF1, 0xC49B, 0x9CF2, 0xC49D, 0x9CF3, 0xC49E, 0x9CF4, 0xC49F, 0x9CF5, 0xC4A0, 0x9CF6, 0xC4A1, + 0x9CF7, 0xC4A2, 0x9CF8, 0xC4A3, 0x9CF9, 0xC4A4, 0x9CFA, 0xC4A5, 0x9CFB, 0xC4A6, 0x9CFC, 0xC4A7, 0x9CFD, 0xC4A8, 0x9CFE, 0xC4A9, + 0x9D41, 0xC4AA, 0x9D42, 0xC4AB, 0x9D43, 0xC4AC, 0x9D44, 0xC4AD, 0x9D45, 0xC4AE, 0x9D46, 0xC4AF, 0x9D47, 0xC4B0, 0x9D48, 0xC4B1, + 0x9D49, 0xC4B2, 0x9D4A, 0xC4B3, 0x9D4B, 0xC4B4, 0x9D4C, 0xC4B5, 0x9D4D, 0xC4B6, 0x9D4E, 0xC4B7, 0x9D4F, 0xC4B9, 0x9D50, 0xC4BA, + 0x9D51, 0xC4BB, 0x9D52, 0xC4BD, 0x9D53, 0xC4BE, 0x9D54, 0xC4BF, 0x9D55, 0xC4C0, 0x9D56, 0xC4C1, 0x9D57, 0xC4C2, 0x9D58, 0xC4C3, + 0x9D59, 0xC4C4, 0x9D5A, 0xC4C5, 0x9D61, 0xC4C6, 0x9D62, 0xC4C7, 0x9D63, 0xC4C8, 0x9D64, 0xC4C9, 0x9D65, 0xC4CA, 0x9D66, 0xC4CB, + 0x9D67, 0xC4CC, 0x9D68, 0xC4CD, 0x9D69, 0xC4CE, 0x9D6A, 0xC4CF, 0x9D6B, 0xC4D0, 0x9D6C, 0xC4D1, 0x9D6D, 0xC4D2, 0x9D6E, 0xC4D3, + 0x9D6F, 0xC4D4, 0x9D70, 0xC4D5, 0x9D71, 0xC4D6, 0x9D72, 0xC4D7, 0x9D73, 0xC4D8, 0x9D74, 0xC4D9, 0x9D75, 0xC4DA, 0x9D76, 0xC4DB, + 0x9D77, 0xC4DC, 0x9D78, 0xC4DD, 0x9D79, 0xC4DE, 0x9D7A, 0xC4DF, 0x9D81, 0xC4E0, 0x9D82, 0xC4E1, 0x9D83, 0xC4E2, 0x9D84, 0xC4E3, + 0x9D85, 0xC4E4, 0x9D86, 0xC4E5, 0x9D87, 0xC4E6, 0x9D88, 0xC4E7, 0x9D89, 0xC4E8, 0x9D8A, 0xC4EA, 0x9D8B, 0xC4EB, 0x9D8C, 0xC4EC, + 0x9D8D, 0xC4ED, 0x9D8E, 0xC4EE, 0x9D8F, 0xC4EF, 0x9D90, 0xC4F2, 0x9D91, 0xC4F3, 0x9D92, 0xC4F5, 0x9D93, 0xC4F6, 0x9D94, 0xC4F7, + 0x9D95, 0xC4F9, 0x9D96, 0xC4FB, 0x9D97, 0xC4FC, 0x9D98, 0xC4FD, 0x9D99, 0xC4FE, 0x9D9A, 0xC502, 0x9D9B, 0xC503, 0x9D9C, 0xC504, + 0x9D9D, 0xC505, 0x9D9E, 0xC506, 0x9D9F, 0xC507, 0x9DA0, 0xC508, 0x9DA1, 0xC509, 0x9DA2, 0xC50A, 0x9DA3, 0xC50B, 0x9DA4, 0xC50D, + 0x9DA5, 0xC50E, 0x9DA6, 0xC50F, 0x9DA7, 0xC511, 0x9DA8, 0xC512, 0x9DA9, 0xC513, 0x9DAA, 0xC515, 0x9DAB, 0xC516, 0x9DAC, 0xC517, + 0x9DAD, 0xC518, 0x9DAE, 0xC519, 0x9DAF, 0xC51A, 0x9DB0, 0xC51B, 0x9DB1, 0xC51D, 0x9DB2, 0xC51E, 0x9DB3, 0xC51F, 0x9DB4, 0xC520, + 0x9DB5, 0xC521, 0x9DB6, 0xC522, 0x9DB7, 0xC523, 0x9DB8, 0xC524, 0x9DB9, 0xC525, 0x9DBA, 0xC526, 0x9DBB, 0xC527, 0x9DBC, 0xC52A, + 0x9DBD, 0xC52B, 0x9DBE, 0xC52D, 0x9DBF, 0xC52E, 0x9DC0, 0xC52F, 0x9DC1, 0xC531, 0x9DC2, 0xC532, 0x9DC3, 0xC533, 0x9DC4, 0xC534, + 0x9DC5, 0xC535, 0x9DC6, 0xC536, 0x9DC7, 0xC537, 0x9DC8, 0xC53A, 0x9DC9, 0xC53C, 0x9DCA, 0xC53E, 0x9DCB, 0xC53F, 0x9DCC, 0xC540, + 0x9DCD, 0xC541, 0x9DCE, 0xC542, 0x9DCF, 0xC543, 0x9DD0, 0xC546, 0x9DD1, 0xC547, 0x9DD2, 0xC54B, 0x9DD3, 0xC54F, 0x9DD4, 0xC550, + 0x9DD5, 0xC551, 0x9DD6, 0xC552, 0x9DD7, 0xC556, 0x9DD8, 0xC55A, 0x9DD9, 0xC55B, 0x9DDA, 0xC55C, 0x9DDB, 0xC55F, 0x9DDC, 0xC562, + 0x9DDD, 0xC563, 0x9DDE, 0xC565, 0x9DDF, 0xC566, 0x9DE0, 0xC567, 0x9DE1, 0xC569, 0x9DE2, 0xC56A, 0x9DE3, 0xC56B, 0x9DE4, 0xC56C, + 0x9DE5, 0xC56D, 0x9DE6, 0xC56E, 0x9DE7, 0xC56F, 0x9DE8, 0xC572, 0x9DE9, 0xC576, 0x9DEA, 0xC577, 0x9DEB, 0xC578, 0x9DEC, 0xC579, + 0x9DED, 0xC57A, 0x9DEE, 0xC57B, 0x9DEF, 0xC57E, 0x9DF0, 0xC57F, 0x9DF1, 0xC581, 0x9DF2, 0xC582, 0x9DF3, 0xC583, 0x9DF4, 0xC585, + 0x9DF5, 0xC586, 0x9DF6, 0xC588, 0x9DF7, 0xC589, 0x9DF8, 0xC58A, 0x9DF9, 0xC58B, 0x9DFA, 0xC58E, 0x9DFB, 0xC590, 0x9DFC, 0xC592, + 0x9DFD, 0xC593, 0x9DFE, 0xC594, 0x9E41, 0xC596, 0x9E42, 0xC599, 0x9E43, 0xC59A, 0x9E44, 0xC59B, 0x9E45, 0xC59D, 0x9E46, 0xC59E, + 0x9E47, 0xC59F, 0x9E48, 0xC5A1, 0x9E49, 0xC5A2, 0x9E4A, 0xC5A3, 0x9E4B, 0xC5A4, 0x9E4C, 0xC5A5, 0x9E4D, 0xC5A6, 0x9E4E, 0xC5A7, + 0x9E4F, 0xC5A8, 0x9E50, 0xC5AA, 0x9E51, 0xC5AB, 0x9E52, 0xC5AC, 0x9E53, 0xC5AD, 0x9E54, 0xC5AE, 0x9E55, 0xC5AF, 0x9E56, 0xC5B0, + 0x9E57, 0xC5B1, 0x9E58, 0xC5B2, 0x9E59, 0xC5B3, 0x9E5A, 0xC5B6, 0x9E61, 0xC5B7, 0x9E62, 0xC5BA, 0x9E63, 0xC5BF, 0x9E64, 0xC5C0, + 0x9E65, 0xC5C1, 0x9E66, 0xC5C2, 0x9E67, 0xC5C3, 0x9E68, 0xC5CB, 0x9E69, 0xC5CD, 0x9E6A, 0xC5CF, 0x9E6B, 0xC5D2, 0x9E6C, 0xC5D3, + 0x9E6D, 0xC5D5, 0x9E6E, 0xC5D6, 0x9E6F, 0xC5D7, 0x9E70, 0xC5D9, 0x9E71, 0xC5DA, 0x9E72, 0xC5DB, 0x9E73, 0xC5DC, 0x9E74, 0xC5DD, + 0x9E75, 0xC5DE, 0x9E76, 0xC5DF, 0x9E77, 0xC5E2, 0x9E78, 0xC5E4, 0x9E79, 0xC5E6, 0x9E7A, 0xC5E7, 0x9E81, 0xC5E8, 0x9E82, 0xC5E9, + 0x9E83, 0xC5EA, 0x9E84, 0xC5EB, 0x9E85, 0xC5EF, 0x9E86, 0xC5F1, 0x9E87, 0xC5F2, 0x9E88, 0xC5F3, 0x9E89, 0xC5F5, 0x9E8A, 0xC5F8, + 0x9E8B, 0xC5F9, 0x9E8C, 0xC5FA, 0x9E8D, 0xC5FB, 0x9E8E, 0xC602, 0x9E8F, 0xC603, 0x9E90, 0xC604, 0x9E91, 0xC609, 0x9E92, 0xC60A, + 0x9E93, 0xC60B, 0x9E94, 0xC60D, 0x9E95, 0xC60E, 0x9E96, 0xC60F, 0x9E97, 0xC611, 0x9E98, 0xC612, 0x9E99, 0xC613, 0x9E9A, 0xC614, + 0x9E9B, 0xC615, 0x9E9C, 0xC616, 0x9E9D, 0xC617, 0x9E9E, 0xC61A, 0x9E9F, 0xC61D, 0x9EA0, 0xC61E, 0x9EA1, 0xC61F, 0x9EA2, 0xC620, + 0x9EA3, 0xC621, 0x9EA4, 0xC622, 0x9EA5, 0xC623, 0x9EA6, 0xC626, 0x9EA7, 0xC627, 0x9EA8, 0xC629, 0x9EA9, 0xC62A, 0x9EAA, 0xC62B, + 0x9EAB, 0xC62F, 0x9EAC, 0xC631, 0x9EAD, 0xC632, 0x9EAE, 0xC636, 0x9EAF, 0xC638, 0x9EB0, 0xC63A, 0x9EB1, 0xC63C, 0x9EB2, 0xC63D, + 0x9EB3, 0xC63E, 0x9EB4, 0xC63F, 0x9EB5, 0xC642, 0x9EB6, 0xC643, 0x9EB7, 0xC645, 0x9EB8, 0xC646, 0x9EB9, 0xC647, 0x9EBA, 0xC649, + 0x9EBB, 0xC64A, 0x9EBC, 0xC64B, 0x9EBD, 0xC64C, 0x9EBE, 0xC64D, 0x9EBF, 0xC64E, 0x9EC0, 0xC64F, 0x9EC1, 0xC652, 0x9EC2, 0xC656, + 0x9EC3, 0xC657, 0x9EC4, 0xC658, 0x9EC5, 0xC659, 0x9EC6, 0xC65A, 0x9EC7, 0xC65B, 0x9EC8, 0xC65E, 0x9EC9, 0xC65F, 0x9ECA, 0xC661, + 0x9ECB, 0xC662, 0x9ECC, 0xC663, 0x9ECD, 0xC664, 0x9ECE, 0xC665, 0x9ECF, 0xC666, 0x9ED0, 0xC667, 0x9ED1, 0xC668, 0x9ED2, 0xC669, + 0x9ED3, 0xC66A, 0x9ED4, 0xC66B, 0x9ED5, 0xC66D, 0x9ED6, 0xC66E, 0x9ED7, 0xC670, 0x9ED8, 0xC672, 0x9ED9, 0xC673, 0x9EDA, 0xC674, + 0x9EDB, 0xC675, 0x9EDC, 0xC676, 0x9EDD, 0xC677, 0x9EDE, 0xC67A, 0x9EDF, 0xC67B, 0x9EE0, 0xC67D, 0x9EE1, 0xC67E, 0x9EE2, 0xC67F, + 0x9EE3, 0xC681, 0x9EE4, 0xC682, 0x9EE5, 0xC683, 0x9EE6, 0xC684, 0x9EE7, 0xC685, 0x9EE8, 0xC686, 0x9EE9, 0xC687, 0x9EEA, 0xC68A, + 0x9EEB, 0xC68C, 0x9EEC, 0xC68E, 0x9EED, 0xC68F, 0x9EEE, 0xC690, 0x9EEF, 0xC691, 0x9EF0, 0xC692, 0x9EF1, 0xC693, 0x9EF2, 0xC696, + 0x9EF3, 0xC697, 0x9EF4, 0xC699, 0x9EF5, 0xC69A, 0x9EF6, 0xC69B, 0x9EF7, 0xC69D, 0x9EF8, 0xC69E, 0x9EF9, 0xC69F, 0x9EFA, 0xC6A0, + 0x9EFB, 0xC6A1, 0x9EFC, 0xC6A2, 0x9EFD, 0xC6A3, 0x9EFE, 0xC6A6, 0x9F41, 0xC6A8, 0x9F42, 0xC6AA, 0x9F43, 0xC6AB, 0x9F44, 0xC6AC, + 0x9F45, 0xC6AD, 0x9F46, 0xC6AE, 0x9F47, 0xC6AF, 0x9F48, 0xC6B2, 0x9F49, 0xC6B3, 0x9F4A, 0xC6B5, 0x9F4B, 0xC6B6, 0x9F4C, 0xC6B7, + 0x9F4D, 0xC6BB, 0x9F4E, 0xC6BC, 0x9F4F, 0xC6BD, 0x9F50, 0xC6BE, 0x9F51, 0xC6BF, 0x9F52, 0xC6C2, 0x9F53, 0xC6C4, 0x9F54, 0xC6C6, + 0x9F55, 0xC6C7, 0x9F56, 0xC6C8, 0x9F57, 0xC6C9, 0x9F58, 0xC6CA, 0x9F59, 0xC6CB, 0x9F5A, 0xC6CE, 0x9F61, 0xC6CF, 0x9F62, 0xC6D1, + 0x9F63, 0xC6D2, 0x9F64, 0xC6D3, 0x9F65, 0xC6D5, 0x9F66, 0xC6D6, 0x9F67, 0xC6D7, 0x9F68, 0xC6D8, 0x9F69, 0xC6D9, 0x9F6A, 0xC6DA, + 0x9F6B, 0xC6DB, 0x9F6C, 0xC6DE, 0x9F6D, 0xC6DF, 0x9F6E, 0xC6E2, 0x9F6F, 0xC6E3, 0x9F70, 0xC6E4, 0x9F71, 0xC6E5, 0x9F72, 0xC6E6, + 0x9F73, 0xC6E7, 0x9F74, 0xC6EA, 0x9F75, 0xC6EB, 0x9F76, 0xC6ED, 0x9F77, 0xC6EE, 0x9F78, 0xC6EF, 0x9F79, 0xC6F1, 0x9F7A, 0xC6F2, + 0x9F81, 0xC6F3, 0x9F82, 0xC6F4, 0x9F83, 0xC6F5, 0x9F84, 0xC6F6, 0x9F85, 0xC6F7, 0x9F86, 0xC6FA, 0x9F87, 0xC6FB, 0x9F88, 0xC6FC, + 0x9F89, 0xC6FE, 0x9F8A, 0xC6FF, 0x9F8B, 0xC700, 0x9F8C, 0xC701, 0x9F8D, 0xC702, 0x9F8E, 0xC703, 0x9F8F, 0xC706, 0x9F90, 0xC707, + 0x9F91, 0xC709, 0x9F92, 0xC70A, 0x9F93, 0xC70B, 0x9F94, 0xC70D, 0x9F95, 0xC70E, 0x9F96, 0xC70F, 0x9F97, 0xC710, 0x9F98, 0xC711, + 0x9F99, 0xC712, 0x9F9A, 0xC713, 0x9F9B, 0xC716, 0x9F9C, 0xC718, 0x9F9D, 0xC71A, 0x9F9E, 0xC71B, 0x9F9F, 0xC71C, 0x9FA0, 0xC71D, + 0x9FA1, 0xC71E, 0x9FA2, 0xC71F, 0x9FA3, 0xC722, 0x9FA4, 0xC723, 0x9FA5, 0xC725, 0x9FA6, 0xC726, 0x9FA7, 0xC727, 0x9FA8, 0xC729, + 0x9FA9, 0xC72A, 0x9FAA, 0xC72B, 0x9FAB, 0xC72C, 0x9FAC, 0xC72D, 0x9FAD, 0xC72E, 0x9FAE, 0xC72F, 0x9FAF, 0xC732, 0x9FB0, 0xC734, + 0x9FB1, 0xC736, 0x9FB2, 0xC738, 0x9FB3, 0xC739, 0x9FB4, 0xC73A, 0x9FB5, 0xC73B, 0x9FB6, 0xC73E, 0x9FB7, 0xC73F, 0x9FB8, 0xC741, + 0x9FB9, 0xC742, 0x9FBA, 0xC743, 0x9FBB, 0xC745, 0x9FBC, 0xC746, 0x9FBD, 0xC747, 0x9FBE, 0xC748, 0x9FBF, 0xC749, 0x9FC0, 0xC74B, + 0x9FC1, 0xC74E, 0x9FC2, 0xC750, 0x9FC3, 0xC759, 0x9FC4, 0xC75A, 0x9FC5, 0xC75B, 0x9FC6, 0xC75D, 0x9FC7, 0xC75E, 0x9FC8, 0xC75F, + 0x9FC9, 0xC761, 0x9FCA, 0xC762, 0x9FCB, 0xC763, 0x9FCC, 0xC764, 0x9FCD, 0xC765, 0x9FCE, 0xC766, 0x9FCF, 0xC767, 0x9FD0, 0xC769, + 0x9FD1, 0xC76A, 0x9FD2, 0xC76C, 0x9FD3, 0xC76D, 0x9FD4, 0xC76E, 0x9FD5, 0xC76F, 0x9FD6, 0xC770, 0x9FD7, 0xC771, 0x9FD8, 0xC772, + 0x9FD9, 0xC773, 0x9FDA, 0xC776, 0x9FDB, 0xC777, 0x9FDC, 0xC779, 0x9FDD, 0xC77A, 0x9FDE, 0xC77B, 0x9FDF, 0xC77F, 0x9FE0, 0xC780, + 0x9FE1, 0xC781, 0x9FE2, 0xC782, 0x9FE3, 0xC786, 0x9FE4, 0xC78B, 0x9FE5, 0xC78C, 0x9FE6, 0xC78D, 0x9FE7, 0xC78F, 0x9FE8, 0xC792, + 0x9FE9, 0xC793, 0x9FEA, 0xC795, 0x9FEB, 0xC799, 0x9FEC, 0xC79B, 0x9FED, 0xC79C, 0x9FEE, 0xC79D, 0x9FEF, 0xC79E, 0x9FF0, 0xC79F, + 0x9FF1, 0xC7A2, 0x9FF2, 0xC7A7, 0x9FF3, 0xC7A8, 0x9FF4, 0xC7A9, 0x9FF5, 0xC7AA, 0x9FF6, 0xC7AB, 0x9FF7, 0xC7AE, 0x9FF8, 0xC7AF, + 0x9FF9, 0xC7B1, 0x9FFA, 0xC7B2, 0x9FFB, 0xC7B3, 0x9FFC, 0xC7B5, 0x9FFD, 0xC7B6, 0x9FFE, 0xC7B7, 0xA041, 0xC7B8, 0xA042, 0xC7B9, + 0xA043, 0xC7BA, 0xA044, 0xC7BB, 0xA045, 0xC7BE, 0xA046, 0xC7C2, 0xA047, 0xC7C3, 0xA048, 0xC7C4, 0xA049, 0xC7C5, 0xA04A, 0xC7C6, + 0xA04B, 0xC7C7, 0xA04C, 0xC7CA, 0xA04D, 0xC7CB, 0xA04E, 0xC7CD, 0xA04F, 0xC7CF, 0xA050, 0xC7D1, 0xA051, 0xC7D2, 0xA052, 0xC7D3, + 0xA053, 0xC7D4, 0xA054, 0xC7D5, 0xA055, 0xC7D6, 0xA056, 0xC7D7, 0xA057, 0xC7D9, 0xA058, 0xC7DA, 0xA059, 0xC7DB, 0xA05A, 0xC7DC, + 0xA061, 0xC7DE, 0xA062, 0xC7DF, 0xA063, 0xC7E0, 0xA064, 0xC7E1, 0xA065, 0xC7E2, 0xA066, 0xC7E3, 0xA067, 0xC7E5, 0xA068, 0xC7E6, + 0xA069, 0xC7E7, 0xA06A, 0xC7E9, 0xA06B, 0xC7EA, 0xA06C, 0xC7EB, 0xA06D, 0xC7ED, 0xA06E, 0xC7EE, 0xA06F, 0xC7EF, 0xA070, 0xC7F0, + 0xA071, 0xC7F1, 0xA072, 0xC7F2, 0xA073, 0xC7F3, 0xA074, 0xC7F4, 0xA075, 0xC7F5, 0xA076, 0xC7F6, 0xA077, 0xC7F7, 0xA078, 0xC7F8, + 0xA079, 0xC7F9, 0xA07A, 0xC7FA, 0xA081, 0xC7FB, 0xA082, 0xC7FC, 0xA083, 0xC7FD, 0xA084, 0xC7FE, 0xA085, 0xC7FF, 0xA086, 0xC802, + 0xA087, 0xC803, 0xA088, 0xC805, 0xA089, 0xC806, 0xA08A, 0xC807, 0xA08B, 0xC809, 0xA08C, 0xC80B, 0xA08D, 0xC80C, 0xA08E, 0xC80D, + 0xA08F, 0xC80E, 0xA090, 0xC80F, 0xA091, 0xC812, 0xA092, 0xC814, 0xA093, 0xC817, 0xA094, 0xC818, 0xA095, 0xC819, 0xA096, 0xC81A, + 0xA097, 0xC81B, 0xA098, 0xC81E, 0xA099, 0xC81F, 0xA09A, 0xC821, 0xA09B, 0xC822, 0xA09C, 0xC823, 0xA09D, 0xC825, 0xA09E, 0xC826, + 0xA09F, 0xC827, 0xA0A0, 0xC828, 0xA0A1, 0xC829, 0xA0A2, 0xC82A, 0xA0A3, 0xC82B, 0xA0A4, 0xC82E, 0xA0A5, 0xC830, 0xA0A6, 0xC832, + 0xA0A7, 0xC833, 0xA0A8, 0xC834, 0xA0A9, 0xC835, 0xA0AA, 0xC836, 0xA0AB, 0xC837, 0xA0AC, 0xC839, 0xA0AD, 0xC83A, 0xA0AE, 0xC83B, + 0xA0AF, 0xC83D, 0xA0B0, 0xC83E, 0xA0B1, 0xC83F, 0xA0B2, 0xC841, 0xA0B3, 0xC842, 0xA0B4, 0xC843, 0xA0B5, 0xC844, 0xA0B6, 0xC845, + 0xA0B7, 0xC846, 0xA0B8, 0xC847, 0xA0B9, 0xC84A, 0xA0BA, 0xC84B, 0xA0BB, 0xC84E, 0xA0BC, 0xC84F, 0xA0BD, 0xC850, 0xA0BE, 0xC851, + 0xA0BF, 0xC852, 0xA0C0, 0xC853, 0xA0C1, 0xC855, 0xA0C2, 0xC856, 0xA0C3, 0xC857, 0xA0C4, 0xC858, 0xA0C5, 0xC859, 0xA0C6, 0xC85A, + 0xA0C7, 0xC85B, 0xA0C8, 0xC85C, 0xA0C9, 0xC85D, 0xA0CA, 0xC85E, 0xA0CB, 0xC85F, 0xA0CC, 0xC860, 0xA0CD, 0xC861, 0xA0CE, 0xC862, + 0xA0CF, 0xC863, 0xA0D0, 0xC864, 0xA0D1, 0xC865, 0xA0D2, 0xC866, 0xA0D3, 0xC867, 0xA0D4, 0xC868, 0xA0D5, 0xC869, 0xA0D6, 0xC86A, + 0xA0D7, 0xC86B, 0xA0D8, 0xC86C, 0xA0D9, 0xC86D, 0xA0DA, 0xC86E, 0xA0DB, 0xC86F, 0xA0DC, 0xC872, 0xA0DD, 0xC873, 0xA0DE, 0xC875, + 0xA0DF, 0xC876, 0xA0E0, 0xC877, 0xA0E1, 0xC879, 0xA0E2, 0xC87B, 0xA0E3, 0xC87C, 0xA0E4, 0xC87D, 0xA0E5, 0xC87E, 0xA0E6, 0xC87F, + 0xA0E7, 0xC882, 0xA0E8, 0xC884, 0xA0E9, 0xC888, 0xA0EA, 0xC889, 0xA0EB, 0xC88A, 0xA0EC, 0xC88E, 0xA0ED, 0xC88F, 0xA0EE, 0xC890, + 0xA0EF, 0xC891, 0xA0F0, 0xC892, 0xA0F1, 0xC893, 0xA0F2, 0xC895, 0xA0F3, 0xC896, 0xA0F4, 0xC897, 0xA0F5, 0xC898, 0xA0F6, 0xC899, + 0xA0F7, 0xC89A, 0xA0F8, 0xC89B, 0xA0F9, 0xC89C, 0xA0FA, 0xC89E, 0xA0FB, 0xC8A0, 0xA0FC, 0xC8A2, 0xA0FD, 0xC8A3, 0xA0FE, 0xC8A4, + 0xA141, 0xC8A5, 0xA142, 0xC8A6, 0xA143, 0xC8A7, 0xA144, 0xC8A9, 0xA145, 0xC8AA, 0xA146, 0xC8AB, 0xA147, 0xC8AC, 0xA148, 0xC8AD, + 0xA149, 0xC8AE, 0xA14A, 0xC8AF, 0xA14B, 0xC8B0, 0xA14C, 0xC8B1, 0xA14D, 0xC8B2, 0xA14E, 0xC8B3, 0xA14F, 0xC8B4, 0xA150, 0xC8B5, + 0xA151, 0xC8B6, 0xA152, 0xC8B7, 0xA153, 0xC8B8, 0xA154, 0xC8B9, 0xA155, 0xC8BA, 0xA156, 0xC8BB, 0xA157, 0xC8BE, 0xA158, 0xC8BF, + 0xA159, 0xC8C0, 0xA15A, 0xC8C1, 0xA161, 0xC8C2, 0xA162, 0xC8C3, 0xA163, 0xC8C5, 0xA164, 0xC8C6, 0xA165, 0xC8C7, 0xA166, 0xC8C9, + 0xA167, 0xC8CA, 0xA168, 0xC8CB, 0xA169, 0xC8CD, 0xA16A, 0xC8CE, 0xA16B, 0xC8CF, 0xA16C, 0xC8D0, 0xA16D, 0xC8D1, 0xA16E, 0xC8D2, + 0xA16F, 0xC8D3, 0xA170, 0xC8D6, 0xA171, 0xC8D8, 0xA172, 0xC8DA, 0xA173, 0xC8DB, 0xA174, 0xC8DC, 0xA175, 0xC8DD, 0xA176, 0xC8DE, + 0xA177, 0xC8DF, 0xA178, 0xC8E2, 0xA179, 0xC8E3, 0xA17A, 0xC8E5, 0xA181, 0xC8E6, 0xA182, 0xC8E7, 0xA183, 0xC8E8, 0xA184, 0xC8E9, + 0xA185, 0xC8EA, 0xA186, 0xC8EB, 0xA187, 0xC8EC, 0xA188, 0xC8ED, 0xA189, 0xC8EE, 0xA18A, 0xC8EF, 0xA18B, 0xC8F0, 0xA18C, 0xC8F1, + 0xA18D, 0xC8F2, 0xA18E, 0xC8F3, 0xA18F, 0xC8F4, 0xA190, 0xC8F6, 0xA191, 0xC8F7, 0xA192, 0xC8F8, 0xA193, 0xC8F9, 0xA194, 0xC8FA, + 0xA195, 0xC8FB, 0xA196, 0xC8FE, 0xA197, 0xC8FF, 0xA198, 0xC901, 0xA199, 0xC902, 0xA19A, 0xC903, 0xA19B, 0xC907, 0xA19C, 0xC908, + 0xA19D, 0xC909, 0xA19E, 0xC90A, 0xA19F, 0xC90B, 0xA1A0, 0xC90E, 0xA1A1, 0x3000, 0xA1A2, 0x3001, 0xA1A3, 0x3002, 0xA1A4, 0x00B7, + 0xA1A5, 0x2025, 0xA1A6, 0x2026, 0xA1A7, 0x00A8, 0xA1A8, 0x3003, 0xA1A9, 0x00AD, 0xA1AA, 0x2015, 0xA1AB, 0x2225, 0xA1AC, 0xFF3C, + 0xA1AD, 0x223C, 0xA1AE, 0x2018, 0xA1AF, 0x2019, 0xA1B0, 0x201C, 0xA1B1, 0x201D, 0xA1B2, 0x3014, 0xA1B3, 0x3015, 0xA1B4, 0x3008, + 0xA1B5, 0x3009, 0xA1B6, 0x300A, 0xA1B7, 0x300B, 0xA1B8, 0x300C, 0xA1B9, 0x300D, 0xA1BA, 0x300E, 0xA1BB, 0x300F, 0xA1BC, 0x3010, + 0xA1BD, 0x3011, 0xA1BE, 0x00B1, 0xA1BF, 0x00D7, 0xA1C0, 0x00F7, 0xA1C1, 0x2260, 0xA1C2, 0x2264, 0xA1C3, 0x2265, 0xA1C4, 0x221E, + 0xA1C5, 0x2234, 0xA1C6, 0x00B0, 0xA1C7, 0x2032, 0xA1C8, 0x2033, 0xA1C9, 0x2103, 0xA1CA, 0x212B, 0xA1CB, 0xFFE0, 0xA1CC, 0xFFE1, + 0xA1CD, 0xFFE5, 0xA1CE, 0x2642, 0xA1CF, 0x2640, 0xA1D0, 0x2220, 0xA1D1, 0x22A5, 0xA1D2, 0x2312, 0xA1D3, 0x2202, 0xA1D4, 0x2207, + 0xA1D5, 0x2261, 0xA1D6, 0x2252, 0xA1D7, 0x00A7, 0xA1D8, 0x203B, 0xA1D9, 0x2606, 0xA1DA, 0x2605, 0xA1DB, 0x25CB, 0xA1DC, 0x25CF, + 0xA1DD, 0x25CE, 0xA1DE, 0x25C7, 0xA1DF, 0x25C6, 0xA1E0, 0x25A1, 0xA1E1, 0x25A0, 0xA1E2, 0x25B3, 0xA1E3, 0x25B2, 0xA1E4, 0x25BD, + 0xA1E5, 0x25BC, 0xA1E6, 0x2192, 0xA1E7, 0x2190, 0xA1E8, 0x2191, 0xA1E9, 0x2193, 0xA1EA, 0x2194, 0xA1EB, 0x3013, 0xA1EC, 0x226A, + 0xA1ED, 0x226B, 0xA1EE, 0x221A, 0xA1EF, 0x223D, 0xA1F0, 0x221D, 0xA1F1, 0x2235, 0xA1F2, 0x222B, 0xA1F3, 0x222C, 0xA1F4, 0x2208, + 0xA1F5, 0x220B, 0xA1F6, 0x2286, 0xA1F7, 0x2287, 0xA1F8, 0x2282, 0xA1F9, 0x2283, 0xA1FA, 0x222A, 0xA1FB, 0x2229, 0xA1FC, 0x2227, + 0xA1FD, 0x2228, 0xA1FE, 0xFFE2, 0xA241, 0xC910, 0xA242, 0xC912, 0xA243, 0xC913, 0xA244, 0xC914, 0xA245, 0xC915, 0xA246, 0xC916, + 0xA247, 0xC917, 0xA248, 0xC919, 0xA249, 0xC91A, 0xA24A, 0xC91B, 0xA24B, 0xC91C, 0xA24C, 0xC91D, 0xA24D, 0xC91E, 0xA24E, 0xC91F, + 0xA24F, 0xC920, 0xA250, 0xC921, 0xA251, 0xC922, 0xA252, 0xC923, 0xA253, 0xC924, 0xA254, 0xC925, 0xA255, 0xC926, 0xA256, 0xC927, + 0xA257, 0xC928, 0xA258, 0xC929, 0xA259, 0xC92A, 0xA25A, 0xC92B, 0xA261, 0xC92D, 0xA262, 0xC92E, 0xA263, 0xC92F, 0xA264, 0xC930, + 0xA265, 0xC931, 0xA266, 0xC932, 0xA267, 0xC933, 0xA268, 0xC935, 0xA269, 0xC936, 0xA26A, 0xC937, 0xA26B, 0xC938, 0xA26C, 0xC939, + 0xA26D, 0xC93A, 0xA26E, 0xC93B, 0xA26F, 0xC93C, 0xA270, 0xC93D, 0xA271, 0xC93E, 0xA272, 0xC93F, 0xA273, 0xC940, 0xA274, 0xC941, + 0xA275, 0xC942, 0xA276, 0xC943, 0xA277, 0xC944, 0xA278, 0xC945, 0xA279, 0xC946, 0xA27A, 0xC947, 0xA281, 0xC948, 0xA282, 0xC949, + 0xA283, 0xC94A, 0xA284, 0xC94B, 0xA285, 0xC94C, 0xA286, 0xC94D, 0xA287, 0xC94E, 0xA288, 0xC94F, 0xA289, 0xC952, 0xA28A, 0xC953, + 0xA28B, 0xC955, 0xA28C, 0xC956, 0xA28D, 0xC957, 0xA28E, 0xC959, 0xA28F, 0xC95A, 0xA290, 0xC95B, 0xA291, 0xC95C, 0xA292, 0xC95D, + 0xA293, 0xC95E, 0xA294, 0xC95F, 0xA295, 0xC962, 0xA296, 0xC964, 0xA297, 0xC965, 0xA298, 0xC966, 0xA299, 0xC967, 0xA29A, 0xC968, + 0xA29B, 0xC969, 0xA29C, 0xC96A, 0xA29D, 0xC96B, 0xA29E, 0xC96D, 0xA29F, 0xC96E, 0xA2A0, 0xC96F, 0xA2A1, 0x21D2, 0xA2A2, 0x21D4, + 0xA2A3, 0x2200, 0xA2A4, 0x2203, 0xA2A5, 0x00B4, 0xA2A6, 0xFF5E, 0xA2A7, 0x02C7, 0xA2A8, 0x02D8, 0xA2A9, 0x02DD, 0xA2AA, 0x02DA, + 0xA2AB, 0x02D9, 0xA2AC, 0x00B8, 0xA2AD, 0x02DB, 0xA2AE, 0x00A1, 0xA2AF, 0x00BF, 0xA2B0, 0x02D0, 0xA2B1, 0x222E, 0xA2B2, 0x2211, + 0xA2B3, 0x220F, 0xA2B4, 0x00A4, 0xA2B5, 0x2109, 0xA2B6, 0x2030, 0xA2B7, 0x25C1, 0xA2B8, 0x25C0, 0xA2B9, 0x25B7, 0xA2BA, 0x25B6, + 0xA2BB, 0x2664, 0xA2BC, 0x2660, 0xA2BD, 0x2661, 0xA2BE, 0x2665, 0xA2BF, 0x2667, 0xA2C0, 0x2663, 0xA2C1, 0x2299, 0xA2C2, 0x25C8, + 0xA2C3, 0x25A3, 0xA2C4, 0x25D0, 0xA2C5, 0x25D1, 0xA2C6, 0x2592, 0xA2C7, 0x25A4, 0xA2C8, 0x25A5, 0xA2C9, 0x25A8, 0xA2CA, 0x25A7, + 0xA2CB, 0x25A6, 0xA2CC, 0x25A9, 0xA2CD, 0x2668, 0xA2CE, 0x260F, 0xA2CF, 0x260E, 0xA2D0, 0x261C, 0xA2D1, 0x261E, 0xA2D2, 0x00B6, + 0xA2D3, 0x2020, 0xA2D4, 0x2021, 0xA2D5, 0x2195, 0xA2D6, 0x2197, 0xA2D7, 0x2199, 0xA2D8, 0x2196, 0xA2D9, 0x2198, 0xA2DA, 0x266D, + 0xA2DB, 0x2669, 0xA2DC, 0x266A, 0xA2DD, 0x266C, 0xA2DE, 0x327F, 0xA2DF, 0x321C, 0xA2E0, 0x2116, 0xA2E1, 0x33C7, 0xA2E2, 0x2122, + 0xA2E3, 0x33C2, 0xA2E4, 0x33D8, 0xA2E5, 0x2121, 0xA2E6, 0x20AC, 0xA2E7, 0x00AE, 0xA341, 0xC971, 0xA342, 0xC972, 0xA343, 0xC973, + 0xA344, 0xC975, 0xA345, 0xC976, 0xA346, 0xC977, 0xA347, 0xC978, 0xA348, 0xC979, 0xA349, 0xC97A, 0xA34A, 0xC97B, 0xA34B, 0xC97D, + 0xA34C, 0xC97E, 0xA34D, 0xC97F, 0xA34E, 0xC980, 0xA34F, 0xC981, 0xA350, 0xC982, 0xA351, 0xC983, 0xA352, 0xC984, 0xA353, 0xC985, + 0xA354, 0xC986, 0xA355, 0xC987, 0xA356, 0xC98A, 0xA357, 0xC98B, 0xA358, 0xC98D, 0xA359, 0xC98E, 0xA35A, 0xC98F, 0xA361, 0xC991, + 0xA362, 0xC992, 0xA363, 0xC993, 0xA364, 0xC994, 0xA365, 0xC995, 0xA366, 0xC996, 0xA367, 0xC997, 0xA368, 0xC99A, 0xA369, 0xC99C, + 0xA36A, 0xC99E, 0xA36B, 0xC99F, 0xA36C, 0xC9A0, 0xA36D, 0xC9A1, 0xA36E, 0xC9A2, 0xA36F, 0xC9A3, 0xA370, 0xC9A4, 0xA371, 0xC9A5, + 0xA372, 0xC9A6, 0xA373, 0xC9A7, 0xA374, 0xC9A8, 0xA375, 0xC9A9, 0xA376, 0xC9AA, 0xA377, 0xC9AB, 0xA378, 0xC9AC, 0xA379, 0xC9AD, + 0xA37A, 0xC9AE, 0xA381, 0xC9AF, 0xA382, 0xC9B0, 0xA383, 0xC9B1, 0xA384, 0xC9B2, 0xA385, 0xC9B3, 0xA386, 0xC9B4, 0xA387, 0xC9B5, + 0xA388, 0xC9B6, 0xA389, 0xC9B7, 0xA38A, 0xC9B8, 0xA38B, 0xC9B9, 0xA38C, 0xC9BA, 0xA38D, 0xC9BB, 0xA38E, 0xC9BC, 0xA38F, 0xC9BD, + 0xA390, 0xC9BE, 0xA391, 0xC9BF, 0xA392, 0xC9C2, 0xA393, 0xC9C3, 0xA394, 0xC9C5, 0xA395, 0xC9C6, 0xA396, 0xC9C9, 0xA397, 0xC9CB, + 0xA398, 0xC9CC, 0xA399, 0xC9CD, 0xA39A, 0xC9CE, 0xA39B, 0xC9CF, 0xA39C, 0xC9D2, 0xA39D, 0xC9D4, 0xA39E, 0xC9D7, 0xA39F, 0xC9D8, + 0xA3A0, 0xC9DB, 0xA3A1, 0xFF01, 0xA3A2, 0xFF02, 0xA3A3, 0xFF03, 0xA3A4, 0xFF04, 0xA3A5, 0xFF05, 0xA3A6, 0xFF06, 0xA3A7, 0xFF07, + 0xA3A8, 0xFF08, 0xA3A9, 0xFF09, 0xA3AA, 0xFF0A, 0xA3AB, 0xFF0B, 0xA3AC, 0xFF0C, 0xA3AD, 0xFF0D, 0xA3AE, 0xFF0E, 0xA3AF, 0xFF0F, + 0xA3B0, 0xFF10, 0xA3B1, 0xFF11, 0xA3B2, 0xFF12, 0xA3B3, 0xFF13, 0xA3B4, 0xFF14, 0xA3B5, 0xFF15, 0xA3B6, 0xFF16, 0xA3B7, 0xFF17, + 0xA3B8, 0xFF18, 0xA3B9, 0xFF19, 0xA3BA, 0xFF1A, 0xA3BB, 0xFF1B, 0xA3BC, 0xFF1C, 0xA3BD, 0xFF1D, 0xA3BE, 0xFF1E, 0xA3BF, 0xFF1F, + 0xA3C0, 0xFF20, 0xA3C1, 0xFF21, 0xA3C2, 0xFF22, 0xA3C3, 0xFF23, 0xA3C4, 0xFF24, 0xA3C5, 0xFF25, 0xA3C6, 0xFF26, 0xA3C7, 0xFF27, + 0xA3C8, 0xFF28, 0xA3C9, 0xFF29, 0xA3CA, 0xFF2A, 0xA3CB, 0xFF2B, 0xA3CC, 0xFF2C, 0xA3CD, 0xFF2D, 0xA3CE, 0xFF2E, 0xA3CF, 0xFF2F, + 0xA3D0, 0xFF30, 0xA3D1, 0xFF31, 0xA3D2, 0xFF32, 0xA3D3, 0xFF33, 0xA3D4, 0xFF34, 0xA3D5, 0xFF35, 0xA3D6, 0xFF36, 0xA3D7, 0xFF37, + 0xA3D8, 0xFF38, 0xA3D9, 0xFF39, 0xA3DA, 0xFF3A, 0xA3DB, 0xFF3B, 0xA3DC, 0xFFE6, 0xA3DD, 0xFF3D, 0xA3DE, 0xFF3E, 0xA3DF, 0xFF3F, + 0xA3E0, 0xFF40, 0xA3E1, 0xFF41, 0xA3E2, 0xFF42, 0xA3E3, 0xFF43, 0xA3E4, 0xFF44, 0xA3E5, 0xFF45, 0xA3E6, 0xFF46, 0xA3E7, 0xFF47, + 0xA3E8, 0xFF48, 0xA3E9, 0xFF49, 0xA3EA, 0xFF4A, 0xA3EB, 0xFF4B, 0xA3EC, 0xFF4C, 0xA3ED, 0xFF4D, 0xA3EE, 0xFF4E, 0xA3EF, 0xFF4F, + 0xA3F0, 0xFF50, 0xA3F1, 0xFF51, 0xA3F2, 0xFF52, 0xA3F3, 0xFF53, 0xA3F4, 0xFF54, 0xA3F5, 0xFF55, 0xA3F6, 0xFF56, 0xA3F7, 0xFF57, + 0xA3F8, 0xFF58, 0xA3F9, 0xFF59, 0xA3FA, 0xFF5A, 0xA3FB, 0xFF5B, 0xA3FC, 0xFF5C, 0xA3FD, 0xFF5D, 0xA3FE, 0xFFE3, 0xA441, 0xC9DE, + 0xA442, 0xC9DF, 0xA443, 0xC9E1, 0xA444, 0xC9E3, 0xA445, 0xC9E5, 0xA446, 0xC9E6, 0xA447, 0xC9E8, 0xA448, 0xC9E9, 0xA449, 0xC9EA, + 0xA44A, 0xC9EB, 0xA44B, 0xC9EE, 0xA44C, 0xC9F2, 0xA44D, 0xC9F3, 0xA44E, 0xC9F4, 0xA44F, 0xC9F5, 0xA450, 0xC9F6, 0xA451, 0xC9F7, + 0xA452, 0xC9FA, 0xA453, 0xC9FB, 0xA454, 0xC9FD, 0xA455, 0xC9FE, 0xA456, 0xC9FF, 0xA457, 0xCA01, 0xA458, 0xCA02, 0xA459, 0xCA03, + 0xA45A, 0xCA04, 0xA461, 0xCA05, 0xA462, 0xCA06, 0xA463, 0xCA07, 0xA464, 0xCA0A, 0xA465, 0xCA0E, 0xA466, 0xCA0F, 0xA467, 0xCA10, + 0xA468, 0xCA11, 0xA469, 0xCA12, 0xA46A, 0xCA13, 0xA46B, 0xCA15, 0xA46C, 0xCA16, 0xA46D, 0xCA17, 0xA46E, 0xCA19, 0xA46F, 0xCA1A, + 0xA470, 0xCA1B, 0xA471, 0xCA1C, 0xA472, 0xCA1D, 0xA473, 0xCA1E, 0xA474, 0xCA1F, 0xA475, 0xCA20, 0xA476, 0xCA21, 0xA477, 0xCA22, + 0xA478, 0xCA23, 0xA479, 0xCA24, 0xA47A, 0xCA25, 0xA481, 0xCA26, 0xA482, 0xCA27, 0xA483, 0xCA28, 0xA484, 0xCA2A, 0xA485, 0xCA2B, + 0xA486, 0xCA2C, 0xA487, 0xCA2D, 0xA488, 0xCA2E, 0xA489, 0xCA2F, 0xA48A, 0xCA30, 0xA48B, 0xCA31, 0xA48C, 0xCA32, 0xA48D, 0xCA33, + 0xA48E, 0xCA34, 0xA48F, 0xCA35, 0xA490, 0xCA36, 0xA491, 0xCA37, 0xA492, 0xCA38, 0xA493, 0xCA39, 0xA494, 0xCA3A, 0xA495, 0xCA3B, + 0xA496, 0xCA3C, 0xA497, 0xCA3D, 0xA498, 0xCA3E, 0xA499, 0xCA3F, 0xA49A, 0xCA40, 0xA49B, 0xCA41, 0xA49C, 0xCA42, 0xA49D, 0xCA43, + 0xA49E, 0xCA44, 0xA49F, 0xCA45, 0xA4A0, 0xCA46, 0xA4A1, 0x3131, 0xA4A2, 0x3132, 0xA4A3, 0x3133, 0xA4A4, 0x3134, 0xA4A5, 0x3135, + 0xA4A6, 0x3136, 0xA4A7, 0x3137, 0xA4A8, 0x3138, 0xA4A9, 0x3139, 0xA4AA, 0x313A, 0xA4AB, 0x313B, 0xA4AC, 0x313C, 0xA4AD, 0x313D, + 0xA4AE, 0x313E, 0xA4AF, 0x313F, 0xA4B0, 0x3140, 0xA4B1, 0x3141, 0xA4B2, 0x3142, 0xA4B3, 0x3143, 0xA4B4, 0x3144, 0xA4B5, 0x3145, + 0xA4B6, 0x3146, 0xA4B7, 0x3147, 0xA4B8, 0x3148, 0xA4B9, 0x3149, 0xA4BA, 0x314A, 0xA4BB, 0x314B, 0xA4BC, 0x314C, 0xA4BD, 0x314D, + 0xA4BE, 0x314E, 0xA4BF, 0x314F, 0xA4C0, 0x3150, 0xA4C1, 0x3151, 0xA4C2, 0x3152, 0xA4C3, 0x3153, 0xA4C4, 0x3154, 0xA4C5, 0x3155, + 0xA4C6, 0x3156, 0xA4C7, 0x3157, 0xA4C8, 0x3158, 0xA4C9, 0x3159, 0xA4CA, 0x315A, 0xA4CB, 0x315B, 0xA4CC, 0x315C, 0xA4CD, 0x315D, + 0xA4CE, 0x315E, 0xA4CF, 0x315F, 0xA4D0, 0x3160, 0xA4D1, 0x3161, 0xA4D2, 0x3162, 0xA4D3, 0x3163, 0xA4D4, 0x3164, 0xA4D5, 0x3165, + 0xA4D6, 0x3166, 0xA4D7, 0x3167, 0xA4D8, 0x3168, 0xA4D9, 0x3169, 0xA4DA, 0x316A, 0xA4DB, 0x316B, 0xA4DC, 0x316C, 0xA4DD, 0x316D, + 0xA4DE, 0x316E, 0xA4DF, 0x316F, 0xA4E0, 0x3170, 0xA4E1, 0x3171, 0xA4E2, 0x3172, 0xA4E3, 0x3173, 0xA4E4, 0x3174, 0xA4E5, 0x3175, + 0xA4E6, 0x3176, 0xA4E7, 0x3177, 0xA4E8, 0x3178, 0xA4E9, 0x3179, 0xA4EA, 0x317A, 0xA4EB, 0x317B, 0xA4EC, 0x317C, 0xA4ED, 0x317D, + 0xA4EE, 0x317E, 0xA4EF, 0x317F, 0xA4F0, 0x3180, 0xA4F1, 0x3181, 0xA4F2, 0x3182, 0xA4F3, 0x3183, 0xA4F4, 0x3184, 0xA4F5, 0x3185, + 0xA4F6, 0x3186, 0xA4F7, 0x3187, 0xA4F8, 0x3188, 0xA4F9, 0x3189, 0xA4FA, 0x318A, 0xA4FB, 0x318B, 0xA4FC, 0x318C, 0xA4FD, 0x318D, + 0xA4FE, 0x318E, 0xA541, 0xCA47, 0xA542, 0xCA48, 0xA543, 0xCA49, 0xA544, 0xCA4A, 0xA545, 0xCA4B, 0xA546, 0xCA4E, 0xA547, 0xCA4F, + 0xA548, 0xCA51, 0xA549, 0xCA52, 0xA54A, 0xCA53, 0xA54B, 0xCA55, 0xA54C, 0xCA56, 0xA54D, 0xCA57, 0xA54E, 0xCA58, 0xA54F, 0xCA59, + 0xA550, 0xCA5A, 0xA551, 0xCA5B, 0xA552, 0xCA5E, 0xA553, 0xCA62, 0xA554, 0xCA63, 0xA555, 0xCA64, 0xA556, 0xCA65, 0xA557, 0xCA66, + 0xA558, 0xCA67, 0xA559, 0xCA69, 0xA55A, 0xCA6A, 0xA561, 0xCA6B, 0xA562, 0xCA6C, 0xA563, 0xCA6D, 0xA564, 0xCA6E, 0xA565, 0xCA6F, + 0xA566, 0xCA70, 0xA567, 0xCA71, 0xA568, 0xCA72, 0xA569, 0xCA73, 0xA56A, 0xCA74, 0xA56B, 0xCA75, 0xA56C, 0xCA76, 0xA56D, 0xCA77, + 0xA56E, 0xCA78, 0xA56F, 0xCA79, 0xA570, 0xCA7A, 0xA571, 0xCA7B, 0xA572, 0xCA7C, 0xA573, 0xCA7E, 0xA574, 0xCA7F, 0xA575, 0xCA80, + 0xA576, 0xCA81, 0xA577, 0xCA82, 0xA578, 0xCA83, 0xA579, 0xCA85, 0xA57A, 0xCA86, 0xA581, 0xCA87, 0xA582, 0xCA88, 0xA583, 0xCA89, + 0xA584, 0xCA8A, 0xA585, 0xCA8B, 0xA586, 0xCA8C, 0xA587, 0xCA8D, 0xA588, 0xCA8E, 0xA589, 0xCA8F, 0xA58A, 0xCA90, 0xA58B, 0xCA91, + 0xA58C, 0xCA92, 0xA58D, 0xCA93, 0xA58E, 0xCA94, 0xA58F, 0xCA95, 0xA590, 0xCA96, 0xA591, 0xCA97, 0xA592, 0xCA99, 0xA593, 0xCA9A, + 0xA594, 0xCA9B, 0xA595, 0xCA9C, 0xA596, 0xCA9D, 0xA597, 0xCA9E, 0xA598, 0xCA9F, 0xA599, 0xCAA0, 0xA59A, 0xCAA1, 0xA59B, 0xCAA2, + 0xA59C, 0xCAA3, 0xA59D, 0xCAA4, 0xA59E, 0xCAA5, 0xA59F, 0xCAA6, 0xA5A0, 0xCAA7, 0xA5A1, 0x2170, 0xA5A2, 0x2171, 0xA5A3, 0x2172, + 0xA5A4, 0x2173, 0xA5A5, 0x2174, 0xA5A6, 0x2175, 0xA5A7, 0x2176, 0xA5A8, 0x2177, 0xA5A9, 0x2178, 0xA5AA, 0x2179, 0xA5B0, 0x2160, + 0xA5B1, 0x2161, 0xA5B2, 0x2162, 0xA5B3, 0x2163, 0xA5B4, 0x2164, 0xA5B5, 0x2165, 0xA5B6, 0x2166, 0xA5B7, 0x2167, 0xA5B8, 0x2168, + 0xA5B9, 0x2169, 0xA5C1, 0x0391, 0xA5C2, 0x0392, 0xA5C3, 0x0393, 0xA5C4, 0x0394, 0xA5C5, 0x0395, 0xA5C6, 0x0396, 0xA5C7, 0x0397, + 0xA5C8, 0x0398, 0xA5C9, 0x0399, 0xA5CA, 0x039A, 0xA5CB, 0x039B, 0xA5CC, 0x039C, 0xA5CD, 0x039D, 0xA5CE, 0x039E, 0xA5CF, 0x039F, + 0xA5D0, 0x03A0, 0xA5D1, 0x03A1, 0xA5D2, 0x03A3, 0xA5D3, 0x03A4, 0xA5D4, 0x03A5, 0xA5D5, 0x03A6, 0xA5D6, 0x03A7, 0xA5D7, 0x03A8, + 0xA5D8, 0x03A9, 0xA5E1, 0x03B1, 0xA5E2, 0x03B2, 0xA5E3, 0x03B3, 0xA5E4, 0x03B4, 0xA5E5, 0x03B5, 0xA5E6, 0x03B6, 0xA5E7, 0x03B7, + 0xA5E8, 0x03B8, 0xA5E9, 0x03B9, 0xA5EA, 0x03BA, 0xA5EB, 0x03BB, 0xA5EC, 0x03BC, 0xA5ED, 0x03BD, 0xA5EE, 0x03BE, 0xA5EF, 0x03BF, + 0xA5F0, 0x03C0, 0xA5F1, 0x03C1, 0xA5F2, 0x03C3, 0xA5F3, 0x03C4, 0xA5F4, 0x03C5, 0xA5F5, 0x03C6, 0xA5F6, 0x03C7, 0xA5F7, 0x03C8, + 0xA5F8, 0x03C9, 0xA641, 0xCAA8, 0xA642, 0xCAA9, 0xA643, 0xCAAA, 0xA644, 0xCAAB, 0xA645, 0xCAAC, 0xA646, 0xCAAD, 0xA647, 0xCAAE, + 0xA648, 0xCAAF, 0xA649, 0xCAB0, 0xA64A, 0xCAB1, 0xA64B, 0xCAB2, 0xA64C, 0xCAB3, 0xA64D, 0xCAB4, 0xA64E, 0xCAB5, 0xA64F, 0xCAB6, + 0xA650, 0xCAB7, 0xA651, 0xCAB8, 0xA652, 0xCAB9, 0xA653, 0xCABA, 0xA654, 0xCABB, 0xA655, 0xCABE, 0xA656, 0xCABF, 0xA657, 0xCAC1, + 0xA658, 0xCAC2, 0xA659, 0xCAC3, 0xA65A, 0xCAC5, 0xA661, 0xCAC6, 0xA662, 0xCAC7, 0xA663, 0xCAC8, 0xA664, 0xCAC9, 0xA665, 0xCACA, + 0xA666, 0xCACB, 0xA667, 0xCACE, 0xA668, 0xCAD0, 0xA669, 0xCAD2, 0xA66A, 0xCAD4, 0xA66B, 0xCAD5, 0xA66C, 0xCAD6, 0xA66D, 0xCAD7, + 0xA66E, 0xCADA, 0xA66F, 0xCADB, 0xA670, 0xCADC, 0xA671, 0xCADD, 0xA672, 0xCADE, 0xA673, 0xCADF, 0xA674, 0xCAE1, 0xA675, 0xCAE2, + 0xA676, 0xCAE3, 0xA677, 0xCAE4, 0xA678, 0xCAE5, 0xA679, 0xCAE6, 0xA67A, 0xCAE7, 0xA681, 0xCAE8, 0xA682, 0xCAE9, 0xA683, 0xCAEA, + 0xA684, 0xCAEB, 0xA685, 0xCAED, 0xA686, 0xCAEE, 0xA687, 0xCAEF, 0xA688, 0xCAF0, 0xA689, 0xCAF1, 0xA68A, 0xCAF2, 0xA68B, 0xCAF3, + 0xA68C, 0xCAF5, 0xA68D, 0xCAF6, 0xA68E, 0xCAF7, 0xA68F, 0xCAF8, 0xA690, 0xCAF9, 0xA691, 0xCAFA, 0xA692, 0xCAFB, 0xA693, 0xCAFC, + 0xA694, 0xCAFD, 0xA695, 0xCAFE, 0xA696, 0xCAFF, 0xA697, 0xCB00, 0xA698, 0xCB01, 0xA699, 0xCB02, 0xA69A, 0xCB03, 0xA69B, 0xCB04, + 0xA69C, 0xCB05, 0xA69D, 0xCB06, 0xA69E, 0xCB07, 0xA69F, 0xCB09, 0xA6A0, 0xCB0A, 0xA6A1, 0x2500, 0xA6A2, 0x2502, 0xA6A3, 0x250C, + 0xA6A4, 0x2510, 0xA6A5, 0x2518, 0xA6A6, 0x2514, 0xA6A7, 0x251C, 0xA6A8, 0x252C, 0xA6A9, 0x2524, 0xA6AA, 0x2534, 0xA6AB, 0x253C, + 0xA6AC, 0x2501, 0xA6AD, 0x2503, 0xA6AE, 0x250F, 0xA6AF, 0x2513, 0xA6B0, 0x251B, 0xA6B1, 0x2517, 0xA6B2, 0x2523, 0xA6B3, 0x2533, + 0xA6B4, 0x252B, 0xA6B5, 0x253B, 0xA6B6, 0x254B, 0xA6B7, 0x2520, 0xA6B8, 0x252F, 0xA6B9, 0x2528, 0xA6BA, 0x2537, 0xA6BB, 0x253F, + 0xA6BC, 0x251D, 0xA6BD, 0x2530, 0xA6BE, 0x2525, 0xA6BF, 0x2538, 0xA6C0, 0x2542, 0xA6C1, 0x2512, 0xA6C2, 0x2511, 0xA6C3, 0x251A, + 0xA6C4, 0x2519, 0xA6C5, 0x2516, 0xA6C6, 0x2515, 0xA6C7, 0x250E, 0xA6C8, 0x250D, 0xA6C9, 0x251E, 0xA6CA, 0x251F, 0xA6CB, 0x2521, + 0xA6CC, 0x2522, 0xA6CD, 0x2526, 0xA6CE, 0x2527, 0xA6CF, 0x2529, 0xA6D0, 0x252A, 0xA6D1, 0x252D, 0xA6D2, 0x252E, 0xA6D3, 0x2531, + 0xA6D4, 0x2532, 0xA6D5, 0x2535, 0xA6D6, 0x2536, 0xA6D7, 0x2539, 0xA6D8, 0x253A, 0xA6D9, 0x253D, 0xA6DA, 0x253E, 0xA6DB, 0x2540, + 0xA6DC, 0x2541, 0xA6DD, 0x2543, 0xA6DE, 0x2544, 0xA6DF, 0x2545, 0xA6E0, 0x2546, 0xA6E1, 0x2547, 0xA6E2, 0x2548, 0xA6E3, 0x2549, + 0xA6E4, 0x254A, 0xA741, 0xCB0B, 0xA742, 0xCB0C, 0xA743, 0xCB0D, 0xA744, 0xCB0E, 0xA745, 0xCB0F, 0xA746, 0xCB11, 0xA747, 0xCB12, + 0xA748, 0xCB13, 0xA749, 0xCB15, 0xA74A, 0xCB16, 0xA74B, 0xCB17, 0xA74C, 0xCB19, 0xA74D, 0xCB1A, 0xA74E, 0xCB1B, 0xA74F, 0xCB1C, + 0xA750, 0xCB1D, 0xA751, 0xCB1E, 0xA752, 0xCB1F, 0xA753, 0xCB22, 0xA754, 0xCB23, 0xA755, 0xCB24, 0xA756, 0xCB25, 0xA757, 0xCB26, + 0xA758, 0xCB27, 0xA759, 0xCB28, 0xA75A, 0xCB29, 0xA761, 0xCB2A, 0xA762, 0xCB2B, 0xA763, 0xCB2C, 0xA764, 0xCB2D, 0xA765, 0xCB2E, + 0xA766, 0xCB2F, 0xA767, 0xCB30, 0xA768, 0xCB31, 0xA769, 0xCB32, 0xA76A, 0xCB33, 0xA76B, 0xCB34, 0xA76C, 0xCB35, 0xA76D, 0xCB36, + 0xA76E, 0xCB37, 0xA76F, 0xCB38, 0xA770, 0xCB39, 0xA771, 0xCB3A, 0xA772, 0xCB3B, 0xA773, 0xCB3C, 0xA774, 0xCB3D, 0xA775, 0xCB3E, + 0xA776, 0xCB3F, 0xA777, 0xCB40, 0xA778, 0xCB42, 0xA779, 0xCB43, 0xA77A, 0xCB44, 0xA781, 0xCB45, 0xA782, 0xCB46, 0xA783, 0xCB47, + 0xA784, 0xCB4A, 0xA785, 0xCB4B, 0xA786, 0xCB4D, 0xA787, 0xCB4E, 0xA788, 0xCB4F, 0xA789, 0xCB51, 0xA78A, 0xCB52, 0xA78B, 0xCB53, + 0xA78C, 0xCB54, 0xA78D, 0xCB55, 0xA78E, 0xCB56, 0xA78F, 0xCB57, 0xA790, 0xCB5A, 0xA791, 0xCB5B, 0xA792, 0xCB5C, 0xA793, 0xCB5E, + 0xA794, 0xCB5F, 0xA795, 0xCB60, 0xA796, 0xCB61, 0xA797, 0xCB62, 0xA798, 0xCB63, 0xA799, 0xCB65, 0xA79A, 0xCB66, 0xA79B, 0xCB67, + 0xA79C, 0xCB68, 0xA79D, 0xCB69, 0xA79E, 0xCB6A, 0xA79F, 0xCB6B, 0xA7A0, 0xCB6C, 0xA7A1, 0x3395, 0xA7A2, 0x3396, 0xA7A3, 0x3397, + 0xA7A4, 0x2113, 0xA7A5, 0x3398, 0xA7A6, 0x33C4, 0xA7A7, 0x33A3, 0xA7A8, 0x33A4, 0xA7A9, 0x33A5, 0xA7AA, 0x33A6, 0xA7AB, 0x3399, + 0xA7AC, 0x339A, 0xA7AD, 0x339B, 0xA7AE, 0x339C, 0xA7AF, 0x339D, 0xA7B0, 0x339E, 0xA7B1, 0x339F, 0xA7B2, 0x33A0, 0xA7B3, 0x33A1, + 0xA7B4, 0x33A2, 0xA7B5, 0x33CA, 0xA7B6, 0x338D, 0xA7B7, 0x338E, 0xA7B8, 0x338F, 0xA7B9, 0x33CF, 0xA7BA, 0x3388, 0xA7BB, 0x3389, + 0xA7BC, 0x33C8, 0xA7BD, 0x33A7, 0xA7BE, 0x33A8, 0xA7BF, 0x33B0, 0xA7C0, 0x33B1, 0xA7C1, 0x33B2, 0xA7C2, 0x33B3, 0xA7C3, 0x33B4, + 0xA7C4, 0x33B5, 0xA7C5, 0x33B6, 0xA7C6, 0x33B7, 0xA7C7, 0x33B8, 0xA7C8, 0x33B9, 0xA7C9, 0x3380, 0xA7CA, 0x3381, 0xA7CB, 0x3382, + 0xA7CC, 0x3383, 0xA7CD, 0x3384, 0xA7CE, 0x33BA, 0xA7CF, 0x33BB, 0xA7D0, 0x33BC, 0xA7D1, 0x33BD, 0xA7D2, 0x33BE, 0xA7D3, 0x33BF, + 0xA7D4, 0x3390, 0xA7D5, 0x3391, 0xA7D6, 0x3392, 0xA7D7, 0x3393, 0xA7D8, 0x3394, 0xA7D9, 0x2126, 0xA7DA, 0x33C0, 0xA7DB, 0x33C1, + 0xA7DC, 0x338A, 0xA7DD, 0x338B, 0xA7DE, 0x338C, 0xA7DF, 0x33D6, 0xA7E0, 0x33C5, 0xA7E1, 0x33AD, 0xA7E2, 0x33AE, 0xA7E3, 0x33AF, + 0xA7E4, 0x33DB, 0xA7E5, 0x33A9, 0xA7E6, 0x33AA, 0xA7E7, 0x33AB, 0xA7E8, 0x33AC, 0xA7E9, 0x33DD, 0xA7EA, 0x33D0, 0xA7EB, 0x33D3, + 0xA7EC, 0x33C3, 0xA7ED, 0x33C9, 0xA7EE, 0x33DC, 0xA7EF, 0x33C6, 0xA841, 0xCB6D, 0xA842, 0xCB6E, 0xA843, 0xCB6F, 0xA844, 0xCB70, + 0xA845, 0xCB71, 0xA846, 0xCB72, 0xA847, 0xCB73, 0xA848, 0xCB74, 0xA849, 0xCB75, 0xA84A, 0xCB76, 0xA84B, 0xCB77, 0xA84C, 0xCB7A, + 0xA84D, 0xCB7B, 0xA84E, 0xCB7C, 0xA84F, 0xCB7D, 0xA850, 0xCB7E, 0xA851, 0xCB7F, 0xA852, 0xCB80, 0xA853, 0xCB81, 0xA854, 0xCB82, + 0xA855, 0xCB83, 0xA856, 0xCB84, 0xA857, 0xCB85, 0xA858, 0xCB86, 0xA859, 0xCB87, 0xA85A, 0xCB88, 0xA861, 0xCB89, 0xA862, 0xCB8A, + 0xA863, 0xCB8B, 0xA864, 0xCB8C, 0xA865, 0xCB8D, 0xA866, 0xCB8E, 0xA867, 0xCB8F, 0xA868, 0xCB90, 0xA869, 0xCB91, 0xA86A, 0xCB92, + 0xA86B, 0xCB93, 0xA86C, 0xCB94, 0xA86D, 0xCB95, 0xA86E, 0xCB96, 0xA86F, 0xCB97, 0xA870, 0xCB98, 0xA871, 0xCB99, 0xA872, 0xCB9A, + 0xA873, 0xCB9B, 0xA874, 0xCB9D, 0xA875, 0xCB9E, 0xA876, 0xCB9F, 0xA877, 0xCBA0, 0xA878, 0xCBA1, 0xA879, 0xCBA2, 0xA87A, 0xCBA3, + 0xA881, 0xCBA4, 0xA882, 0xCBA5, 0xA883, 0xCBA6, 0xA884, 0xCBA7, 0xA885, 0xCBA8, 0xA886, 0xCBA9, 0xA887, 0xCBAA, 0xA888, 0xCBAB, + 0xA889, 0xCBAC, 0xA88A, 0xCBAD, 0xA88B, 0xCBAE, 0xA88C, 0xCBAF, 0xA88D, 0xCBB0, 0xA88E, 0xCBB1, 0xA88F, 0xCBB2, 0xA890, 0xCBB3, + 0xA891, 0xCBB4, 0xA892, 0xCBB5, 0xA893, 0xCBB6, 0xA894, 0xCBB7, 0xA895, 0xCBB9, 0xA896, 0xCBBA, 0xA897, 0xCBBB, 0xA898, 0xCBBC, + 0xA899, 0xCBBD, 0xA89A, 0xCBBE, 0xA89B, 0xCBBF, 0xA89C, 0xCBC0, 0xA89D, 0xCBC1, 0xA89E, 0xCBC2, 0xA89F, 0xCBC3, 0xA8A0, 0xCBC4, + 0xA8A1, 0x00C6, 0xA8A2, 0x00D0, 0xA8A3, 0x00AA, 0xA8A4, 0x0126, 0xA8A6, 0x0132, 0xA8A8, 0x013F, 0xA8A9, 0x0141, 0xA8AA, 0x00D8, + 0xA8AB, 0x0152, 0xA8AC, 0x00BA, 0xA8AD, 0x00DE, 0xA8AE, 0x0166, 0xA8AF, 0x014A, 0xA8B1, 0x3260, 0xA8B2, 0x3261, 0xA8B3, 0x3262, + 0xA8B4, 0x3263, 0xA8B5, 0x3264, 0xA8B6, 0x3265, 0xA8B7, 0x3266, 0xA8B8, 0x3267, 0xA8B9, 0x3268, 0xA8BA, 0x3269, 0xA8BB, 0x326A, + 0xA8BC, 0x326B, 0xA8BD, 0x326C, 0xA8BE, 0x326D, 0xA8BF, 0x326E, 0xA8C0, 0x326F, 0xA8C1, 0x3270, 0xA8C2, 0x3271, 0xA8C3, 0x3272, + 0xA8C4, 0x3273, 0xA8C5, 0x3274, 0xA8C6, 0x3275, 0xA8C7, 0x3276, 0xA8C8, 0x3277, 0xA8C9, 0x3278, 0xA8CA, 0x3279, 0xA8CB, 0x327A, + 0xA8CC, 0x327B, 0xA8CD, 0x24D0, 0xA8CE, 0x24D1, 0xA8CF, 0x24D2, 0xA8D0, 0x24D3, 0xA8D1, 0x24D4, 0xA8D2, 0x24D5, 0xA8D3, 0x24D6, + 0xA8D4, 0x24D7, 0xA8D5, 0x24D8, 0xA8D6, 0x24D9, 0xA8D7, 0x24DA, 0xA8D8, 0x24DB, 0xA8D9, 0x24DC, 0xA8DA, 0x24DD, 0xA8DB, 0x24DE, + 0xA8DC, 0x24DF, 0xA8DD, 0x24E0, 0xA8DE, 0x24E1, 0xA8DF, 0x24E2, 0xA8E0, 0x24E3, 0xA8E1, 0x24E4, 0xA8E2, 0x24E5, 0xA8E3, 0x24E6, + 0xA8E4, 0x24E7, 0xA8E5, 0x24E8, 0xA8E6, 0x24E9, 0xA8E7, 0x2460, 0xA8E8, 0x2461, 0xA8E9, 0x2462, 0xA8EA, 0x2463, 0xA8EB, 0x2464, + 0xA8EC, 0x2465, 0xA8ED, 0x2466, 0xA8EE, 0x2467, 0xA8EF, 0x2468, 0xA8F0, 0x2469, 0xA8F1, 0x246A, 0xA8F2, 0x246B, 0xA8F3, 0x246C, + 0xA8F4, 0x246D, 0xA8F5, 0x246E, 0xA8F6, 0x00BD, 0xA8F7, 0x2153, 0xA8F8, 0x2154, 0xA8F9, 0x00BC, 0xA8FA, 0x00BE, 0xA8FB, 0x215B, + 0xA8FC, 0x215C, 0xA8FD, 0x215D, 0xA8FE, 0x215E, 0xA941, 0xCBC5, 0xA942, 0xCBC6, 0xA943, 0xCBC7, 0xA944, 0xCBC8, 0xA945, 0xCBC9, + 0xA946, 0xCBCA, 0xA947, 0xCBCB, 0xA948, 0xCBCC, 0xA949, 0xCBCD, 0xA94A, 0xCBCE, 0xA94B, 0xCBCF, 0xA94C, 0xCBD0, 0xA94D, 0xCBD1, + 0xA94E, 0xCBD2, 0xA94F, 0xCBD3, 0xA950, 0xCBD5, 0xA951, 0xCBD6, 0xA952, 0xCBD7, 0xA953, 0xCBD8, 0xA954, 0xCBD9, 0xA955, 0xCBDA, + 0xA956, 0xCBDB, 0xA957, 0xCBDC, 0xA958, 0xCBDD, 0xA959, 0xCBDE, 0xA95A, 0xCBDF, 0xA961, 0xCBE0, 0xA962, 0xCBE1, 0xA963, 0xCBE2, + 0xA964, 0xCBE3, 0xA965, 0xCBE5, 0xA966, 0xCBE6, 0xA967, 0xCBE8, 0xA968, 0xCBEA, 0xA969, 0xCBEB, 0xA96A, 0xCBEC, 0xA96B, 0xCBED, + 0xA96C, 0xCBEE, 0xA96D, 0xCBEF, 0xA96E, 0xCBF0, 0xA96F, 0xCBF1, 0xA970, 0xCBF2, 0xA971, 0xCBF3, 0xA972, 0xCBF4, 0xA973, 0xCBF5, + 0xA974, 0xCBF6, 0xA975, 0xCBF7, 0xA976, 0xCBF8, 0xA977, 0xCBF9, 0xA978, 0xCBFA, 0xA979, 0xCBFB, 0xA97A, 0xCBFC, 0xA981, 0xCBFD, + 0xA982, 0xCBFE, 0xA983, 0xCBFF, 0xA984, 0xCC00, 0xA985, 0xCC01, 0xA986, 0xCC02, 0xA987, 0xCC03, 0xA988, 0xCC04, 0xA989, 0xCC05, + 0xA98A, 0xCC06, 0xA98B, 0xCC07, 0xA98C, 0xCC08, 0xA98D, 0xCC09, 0xA98E, 0xCC0A, 0xA98F, 0xCC0B, 0xA990, 0xCC0E, 0xA991, 0xCC0F, + 0xA992, 0xCC11, 0xA993, 0xCC12, 0xA994, 0xCC13, 0xA995, 0xCC15, 0xA996, 0xCC16, 0xA997, 0xCC17, 0xA998, 0xCC18, 0xA999, 0xCC19, + 0xA99A, 0xCC1A, 0xA99B, 0xCC1B, 0xA99C, 0xCC1E, 0xA99D, 0xCC1F, 0xA99E, 0xCC20, 0xA99F, 0xCC23, 0xA9A0, 0xCC24, 0xA9A1, 0x00E6, + 0xA9A2, 0x0111, 0xA9A3, 0x00F0, 0xA9A4, 0x0127, 0xA9A5, 0x0131, 0xA9A6, 0x0133, 0xA9A7, 0x0138, 0xA9A8, 0x0140, 0xA9A9, 0x0142, + 0xA9AA, 0x00F8, 0xA9AB, 0x0153, 0xA9AC, 0x00DF, 0xA9AD, 0x00FE, 0xA9AE, 0x0167, 0xA9AF, 0x014B, 0xA9B0, 0x0149, 0xA9B1, 0x3200, + 0xA9B2, 0x3201, 0xA9B3, 0x3202, 0xA9B4, 0x3203, 0xA9B5, 0x3204, 0xA9B6, 0x3205, 0xA9B7, 0x3206, 0xA9B8, 0x3207, 0xA9B9, 0x3208, + 0xA9BA, 0x3209, 0xA9BB, 0x320A, 0xA9BC, 0x320B, 0xA9BD, 0x320C, 0xA9BE, 0x320D, 0xA9BF, 0x320E, 0xA9C0, 0x320F, 0xA9C1, 0x3210, + 0xA9C2, 0x3211, 0xA9C3, 0x3212, 0xA9C4, 0x3213, 0xA9C5, 0x3214, 0xA9C6, 0x3215, 0xA9C7, 0x3216, 0xA9C8, 0x3217, 0xA9C9, 0x3218, + 0xA9CA, 0x3219, 0xA9CB, 0x321A, 0xA9CC, 0x321B, 0xA9CD, 0x249C, 0xA9CE, 0x249D, 0xA9CF, 0x249E, 0xA9D0, 0x249F, 0xA9D1, 0x24A0, + 0xA9D2, 0x24A1, 0xA9D3, 0x24A2, 0xA9D4, 0x24A3, 0xA9D5, 0x24A4, 0xA9D6, 0x24A5, 0xA9D7, 0x24A6, 0xA9D8, 0x24A7, 0xA9D9, 0x24A8, + 0xA9DA, 0x24A9, 0xA9DB, 0x24AA, 0xA9DC, 0x24AB, 0xA9DD, 0x24AC, 0xA9DE, 0x24AD, 0xA9DF, 0x24AE, 0xA9E0, 0x24AF, 0xA9E1, 0x24B0, + 0xA9E2, 0x24B1, 0xA9E3, 0x24B2, 0xA9E4, 0x24B3, 0xA9E5, 0x24B4, 0xA9E6, 0x24B5, 0xA9E7, 0x2474, 0xA9E8, 0x2475, 0xA9E9, 0x2476, + 0xA9EA, 0x2477, 0xA9EB, 0x2478, 0xA9EC, 0x2479, 0xA9ED, 0x247A, 0xA9EE, 0x247B, 0xA9EF, 0x247C, 0xA9F0, 0x247D, 0xA9F1, 0x247E, + 0xA9F2, 0x247F, 0xA9F3, 0x2480, 0xA9F4, 0x2481, 0xA9F5, 0x2482, 0xA9F6, 0x00B9, 0xA9F7, 0x00B2, 0xA9F8, 0x00B3, 0xA9F9, 0x2074, + 0xA9FA, 0x207F, 0xA9FB, 0x2081, 0xA9FC, 0x2082, 0xA9FD, 0x2083, 0xA9FE, 0x2084, 0xAA41, 0xCC25, 0xAA42, 0xCC26, 0xAA43, 0xCC2A, + 0xAA44, 0xCC2B, 0xAA45, 0xCC2D, 0xAA46, 0xCC2F, 0xAA47, 0xCC31, 0xAA48, 0xCC32, 0xAA49, 0xCC33, 0xAA4A, 0xCC34, 0xAA4B, 0xCC35, + 0xAA4C, 0xCC36, 0xAA4D, 0xCC37, 0xAA4E, 0xCC3A, 0xAA4F, 0xCC3F, 0xAA50, 0xCC40, 0xAA51, 0xCC41, 0xAA52, 0xCC42, 0xAA53, 0xCC43, + 0xAA54, 0xCC46, 0xAA55, 0xCC47, 0xAA56, 0xCC49, 0xAA57, 0xCC4A, 0xAA58, 0xCC4B, 0xAA59, 0xCC4D, 0xAA5A, 0xCC4E, 0xAA61, 0xCC4F, + 0xAA62, 0xCC50, 0xAA63, 0xCC51, 0xAA64, 0xCC52, 0xAA65, 0xCC53, 0xAA66, 0xCC56, 0xAA67, 0xCC5A, 0xAA68, 0xCC5B, 0xAA69, 0xCC5C, + 0xAA6A, 0xCC5D, 0xAA6B, 0xCC5E, 0xAA6C, 0xCC5F, 0xAA6D, 0xCC61, 0xAA6E, 0xCC62, 0xAA6F, 0xCC63, 0xAA70, 0xCC65, 0xAA71, 0xCC67, + 0xAA72, 0xCC69, 0xAA73, 0xCC6A, 0xAA74, 0xCC6B, 0xAA75, 0xCC6C, 0xAA76, 0xCC6D, 0xAA77, 0xCC6E, 0xAA78, 0xCC6F, 0xAA79, 0xCC71, + 0xAA7A, 0xCC72, 0xAA81, 0xCC73, 0xAA82, 0xCC74, 0xAA83, 0xCC76, 0xAA84, 0xCC77, 0xAA85, 0xCC78, 0xAA86, 0xCC79, 0xAA87, 0xCC7A, + 0xAA88, 0xCC7B, 0xAA89, 0xCC7C, 0xAA8A, 0xCC7D, 0xAA8B, 0xCC7E, 0xAA8C, 0xCC7F, 0xAA8D, 0xCC80, 0xAA8E, 0xCC81, 0xAA8F, 0xCC82, + 0xAA90, 0xCC83, 0xAA91, 0xCC84, 0xAA92, 0xCC85, 0xAA93, 0xCC86, 0xAA94, 0xCC87, 0xAA95, 0xCC88, 0xAA96, 0xCC89, 0xAA97, 0xCC8A, + 0xAA98, 0xCC8B, 0xAA99, 0xCC8C, 0xAA9A, 0xCC8D, 0xAA9B, 0xCC8E, 0xAA9C, 0xCC8F, 0xAA9D, 0xCC90, 0xAA9E, 0xCC91, 0xAA9F, 0xCC92, + 0xAAA0, 0xCC93, 0xAAA1, 0x3041, 0xAAA2, 0x3042, 0xAAA3, 0x3043, 0xAAA4, 0x3044, 0xAAA5, 0x3045, 0xAAA6, 0x3046, 0xAAA7, 0x3047, + 0xAAA8, 0x3048, 0xAAA9, 0x3049, 0xAAAA, 0x304A, 0xAAAB, 0x304B, 0xAAAC, 0x304C, 0xAAAD, 0x304D, 0xAAAE, 0x304E, 0xAAAF, 0x304F, + 0xAAB0, 0x3050, 0xAAB1, 0x3051, 0xAAB2, 0x3052, 0xAAB3, 0x3053, 0xAAB4, 0x3054, 0xAAB5, 0x3055, 0xAAB6, 0x3056, 0xAAB7, 0x3057, + 0xAAB8, 0x3058, 0xAAB9, 0x3059, 0xAABA, 0x305A, 0xAABB, 0x305B, 0xAABC, 0x305C, 0xAABD, 0x305D, 0xAABE, 0x305E, 0xAABF, 0x305F, + 0xAAC0, 0x3060, 0xAAC1, 0x3061, 0xAAC2, 0x3062, 0xAAC3, 0x3063, 0xAAC4, 0x3064, 0xAAC5, 0x3065, 0xAAC6, 0x3066, 0xAAC7, 0x3067, + 0xAAC8, 0x3068, 0xAAC9, 0x3069, 0xAACA, 0x306A, 0xAACB, 0x306B, 0xAACC, 0x306C, 0xAACD, 0x306D, 0xAACE, 0x306E, 0xAACF, 0x306F, + 0xAAD0, 0x3070, 0xAAD1, 0x3071, 0xAAD2, 0x3072, 0xAAD3, 0x3073, 0xAAD4, 0x3074, 0xAAD5, 0x3075, 0xAAD6, 0x3076, 0xAAD7, 0x3077, + 0xAAD8, 0x3078, 0xAAD9, 0x3079, 0xAADA, 0x307A, 0xAADB, 0x307B, 0xAADC, 0x307C, 0xAADD, 0x307D, 0xAADE, 0x307E, 0xAADF, 0x307F, + 0xAAE0, 0x3080, 0xAAE1, 0x3081, 0xAAE2, 0x3082, 0xAAE3, 0x3083, 0xAAE4, 0x3084, 0xAAE5, 0x3085, 0xAAE6, 0x3086, 0xAAE7, 0x3087, + 0xAAE8, 0x3088, 0xAAE9, 0x3089, 0xAAEA, 0x308A, 0xAAEB, 0x308B, 0xAAEC, 0x308C, 0xAAED, 0x308D, 0xAAEE, 0x308E, 0xAAEF, 0x308F, + 0xAAF0, 0x3090, 0xAAF1, 0x3091, 0xAAF2, 0x3092, 0xAAF3, 0x3093, 0xAB41, 0xCC94, 0xAB42, 0xCC95, 0xAB43, 0xCC96, 0xAB44, 0xCC97, + 0xAB45, 0xCC9A, 0xAB46, 0xCC9B, 0xAB47, 0xCC9D, 0xAB48, 0xCC9E, 0xAB49, 0xCC9F, 0xAB4A, 0xCCA1, 0xAB4B, 0xCCA2, 0xAB4C, 0xCCA3, + 0xAB4D, 0xCCA4, 0xAB4E, 0xCCA5, 0xAB4F, 0xCCA6, 0xAB50, 0xCCA7, 0xAB51, 0xCCAA, 0xAB52, 0xCCAE, 0xAB53, 0xCCAF, 0xAB54, 0xCCB0, + 0xAB55, 0xCCB1, 0xAB56, 0xCCB2, 0xAB57, 0xCCB3, 0xAB58, 0xCCB6, 0xAB59, 0xCCB7, 0xAB5A, 0xCCB9, 0xAB61, 0xCCBA, 0xAB62, 0xCCBB, + 0xAB63, 0xCCBD, 0xAB64, 0xCCBE, 0xAB65, 0xCCBF, 0xAB66, 0xCCC0, 0xAB67, 0xCCC1, 0xAB68, 0xCCC2, 0xAB69, 0xCCC3, 0xAB6A, 0xCCC6, + 0xAB6B, 0xCCC8, 0xAB6C, 0xCCCA, 0xAB6D, 0xCCCB, 0xAB6E, 0xCCCC, 0xAB6F, 0xCCCD, 0xAB70, 0xCCCE, 0xAB71, 0xCCCF, 0xAB72, 0xCCD1, + 0xAB73, 0xCCD2, 0xAB74, 0xCCD3, 0xAB75, 0xCCD5, 0xAB76, 0xCCD6, 0xAB77, 0xCCD7, 0xAB78, 0xCCD8, 0xAB79, 0xCCD9, 0xAB7A, 0xCCDA, + 0xAB81, 0xCCDB, 0xAB82, 0xCCDC, 0xAB83, 0xCCDD, 0xAB84, 0xCCDE, 0xAB85, 0xCCDF, 0xAB86, 0xCCE0, 0xAB87, 0xCCE1, 0xAB88, 0xCCE2, + 0xAB89, 0xCCE3, 0xAB8A, 0xCCE5, 0xAB8B, 0xCCE6, 0xAB8C, 0xCCE7, 0xAB8D, 0xCCE8, 0xAB8E, 0xCCE9, 0xAB8F, 0xCCEA, 0xAB90, 0xCCEB, + 0xAB91, 0xCCED, 0xAB92, 0xCCEE, 0xAB93, 0xCCEF, 0xAB94, 0xCCF1, 0xAB95, 0xCCF2, 0xAB96, 0xCCF3, 0xAB97, 0xCCF4, 0xAB98, 0xCCF5, + 0xAB99, 0xCCF6, 0xAB9A, 0xCCF7, 0xAB9B, 0xCCF8, 0xAB9C, 0xCCF9, 0xAB9D, 0xCCFA, 0xAB9E, 0xCCFB, 0xAB9F, 0xCCFC, 0xABA0, 0xCCFD, + 0xABA1, 0x30A1, 0xABA2, 0x30A2, 0xABA3, 0x30A3, 0xABA4, 0x30A4, 0xABA5, 0x30A5, 0xABA6, 0x30A6, 0xABA7, 0x30A7, 0xABA8, 0x30A8, + 0xABA9, 0x30A9, 0xABAA, 0x30AA, 0xABAB, 0x30AB, 0xABAC, 0x30AC, 0xABAD, 0x30AD, 0xABAE, 0x30AE, 0xABAF, 0x30AF, 0xABB0, 0x30B0, + 0xABB1, 0x30B1, 0xABB2, 0x30B2, 0xABB3, 0x30B3, 0xABB4, 0x30B4, 0xABB5, 0x30B5, 0xABB6, 0x30B6, 0xABB7, 0x30B7, 0xABB8, 0x30B8, + 0xABB9, 0x30B9, 0xABBA, 0x30BA, 0xABBB, 0x30BB, 0xABBC, 0x30BC, 0xABBD, 0x30BD, 0xABBE, 0x30BE, 0xABBF, 0x30BF, 0xABC0, 0x30C0, + 0xABC1, 0x30C1, 0xABC2, 0x30C2, 0xABC3, 0x30C3, 0xABC4, 0x30C4, 0xABC5, 0x30C5, 0xABC6, 0x30C6, 0xABC7, 0x30C7, 0xABC8, 0x30C8, + 0xABC9, 0x30C9, 0xABCA, 0x30CA, 0xABCB, 0x30CB, 0xABCC, 0x30CC, 0xABCD, 0x30CD, 0xABCE, 0x30CE, 0xABCF, 0x30CF, 0xABD0, 0x30D0, + 0xABD1, 0x30D1, 0xABD2, 0x30D2, 0xABD3, 0x30D3, 0xABD4, 0x30D4, 0xABD5, 0x30D5, 0xABD6, 0x30D6, 0xABD7, 0x30D7, 0xABD8, 0x30D8, + 0xABD9, 0x30D9, 0xABDA, 0x30DA, 0xABDB, 0x30DB, 0xABDC, 0x30DC, 0xABDD, 0x30DD, 0xABDE, 0x30DE, 0xABDF, 0x30DF, 0xABE0, 0x30E0, + 0xABE1, 0x30E1, 0xABE2, 0x30E2, 0xABE3, 0x30E3, 0xABE4, 0x30E4, 0xABE5, 0x30E5, 0xABE6, 0x30E6, 0xABE7, 0x30E7, 0xABE8, 0x30E8, + 0xABE9, 0x30E9, 0xABEA, 0x30EA, 0xABEB, 0x30EB, 0xABEC, 0x30EC, 0xABED, 0x30ED, 0xABEE, 0x30EE, 0xABEF, 0x30EF, 0xABF0, 0x30F0, + 0xABF1, 0x30F1, 0xABF2, 0x30F2, 0xABF3, 0x30F3, 0xABF4, 0x30F4, 0xABF5, 0x30F5, 0xABF6, 0x30F6, 0xAC41, 0xCCFE, 0xAC42, 0xCCFF, + 0xAC43, 0xCD00, 0xAC44, 0xCD02, 0xAC45, 0xCD03, 0xAC46, 0xCD04, 0xAC47, 0xCD05, 0xAC48, 0xCD06, 0xAC49, 0xCD07, 0xAC4A, 0xCD0A, + 0xAC4B, 0xCD0B, 0xAC4C, 0xCD0D, 0xAC4D, 0xCD0E, 0xAC4E, 0xCD0F, 0xAC4F, 0xCD11, 0xAC50, 0xCD12, 0xAC51, 0xCD13, 0xAC52, 0xCD14, + 0xAC53, 0xCD15, 0xAC54, 0xCD16, 0xAC55, 0xCD17, 0xAC56, 0xCD1A, 0xAC57, 0xCD1C, 0xAC58, 0xCD1E, 0xAC59, 0xCD1F, 0xAC5A, 0xCD20, + 0xAC61, 0xCD21, 0xAC62, 0xCD22, 0xAC63, 0xCD23, 0xAC64, 0xCD25, 0xAC65, 0xCD26, 0xAC66, 0xCD27, 0xAC67, 0xCD29, 0xAC68, 0xCD2A, + 0xAC69, 0xCD2B, 0xAC6A, 0xCD2D, 0xAC6B, 0xCD2E, 0xAC6C, 0xCD2F, 0xAC6D, 0xCD30, 0xAC6E, 0xCD31, 0xAC6F, 0xCD32, 0xAC70, 0xCD33, + 0xAC71, 0xCD34, 0xAC72, 0xCD35, 0xAC73, 0xCD36, 0xAC74, 0xCD37, 0xAC75, 0xCD38, 0xAC76, 0xCD3A, 0xAC77, 0xCD3B, 0xAC78, 0xCD3C, + 0xAC79, 0xCD3D, 0xAC7A, 0xCD3E, 0xAC81, 0xCD3F, 0xAC82, 0xCD40, 0xAC83, 0xCD41, 0xAC84, 0xCD42, 0xAC85, 0xCD43, 0xAC86, 0xCD44, + 0xAC87, 0xCD45, 0xAC88, 0xCD46, 0xAC89, 0xCD47, 0xAC8A, 0xCD48, 0xAC8B, 0xCD49, 0xAC8C, 0xCD4A, 0xAC8D, 0xCD4B, 0xAC8E, 0xCD4C, + 0xAC8F, 0xCD4D, 0xAC90, 0xCD4E, 0xAC91, 0xCD4F, 0xAC92, 0xCD50, 0xAC93, 0xCD51, 0xAC94, 0xCD52, 0xAC95, 0xCD53, 0xAC96, 0xCD54, + 0xAC97, 0xCD55, 0xAC98, 0xCD56, 0xAC99, 0xCD57, 0xAC9A, 0xCD58, 0xAC9B, 0xCD59, 0xAC9C, 0xCD5A, 0xAC9D, 0xCD5B, 0xAC9E, 0xCD5D, + 0xAC9F, 0xCD5E, 0xACA0, 0xCD5F, 0xACA1, 0x0410, 0xACA2, 0x0411, 0xACA3, 0x0412, 0xACA4, 0x0413, 0xACA5, 0x0414, 0xACA6, 0x0415, + 0xACA7, 0x0401, 0xACA8, 0x0416, 0xACA9, 0x0417, 0xACAA, 0x0418, 0xACAB, 0x0419, 0xACAC, 0x041A, 0xACAD, 0x041B, 0xACAE, 0x041C, + 0xACAF, 0x041D, 0xACB0, 0x041E, 0xACB1, 0x041F, 0xACB2, 0x0420, 0xACB3, 0x0421, 0xACB4, 0x0422, 0xACB5, 0x0423, 0xACB6, 0x0424, + 0xACB7, 0x0425, 0xACB8, 0x0426, 0xACB9, 0x0427, 0xACBA, 0x0428, 0xACBB, 0x0429, 0xACBC, 0x042A, 0xACBD, 0x042B, 0xACBE, 0x042C, + 0xACBF, 0x042D, 0xACC0, 0x042E, 0xACC1, 0x042F, 0xACD1, 0x0430, 0xACD2, 0x0431, 0xACD3, 0x0432, 0xACD4, 0x0433, 0xACD5, 0x0434, + 0xACD6, 0x0435, 0xACD7, 0x0451, 0xACD8, 0x0436, 0xACD9, 0x0437, 0xACDA, 0x0438, 0xACDB, 0x0439, 0xACDC, 0x043A, 0xACDD, 0x043B, + 0xACDE, 0x043C, 0xACDF, 0x043D, 0xACE0, 0x043E, 0xACE1, 0x043F, 0xACE2, 0x0440, 0xACE3, 0x0441, 0xACE4, 0x0442, 0xACE5, 0x0443, + 0xACE6, 0x0444, 0xACE7, 0x0445, 0xACE8, 0x0446, 0xACE9, 0x0447, 0xACEA, 0x0448, 0xACEB, 0x0449, 0xACEC, 0x044A, 0xACED, 0x044B, + 0xACEE, 0x044C, 0xACEF, 0x044D, 0xACF0, 0x044E, 0xACF1, 0x044F, 0xAD41, 0xCD61, 0xAD42, 0xCD62, 0xAD43, 0xCD63, 0xAD44, 0xCD65, + 0xAD45, 0xCD66, 0xAD46, 0xCD67, 0xAD47, 0xCD68, 0xAD48, 0xCD69, 0xAD49, 0xCD6A, 0xAD4A, 0xCD6B, 0xAD4B, 0xCD6E, 0xAD4C, 0xCD70, + 0xAD4D, 0xCD72, 0xAD4E, 0xCD73, 0xAD4F, 0xCD74, 0xAD50, 0xCD75, 0xAD51, 0xCD76, 0xAD52, 0xCD77, 0xAD53, 0xCD79, 0xAD54, 0xCD7A, + 0xAD55, 0xCD7B, 0xAD56, 0xCD7C, 0xAD57, 0xCD7D, 0xAD58, 0xCD7E, 0xAD59, 0xCD7F, 0xAD5A, 0xCD80, 0xAD61, 0xCD81, 0xAD62, 0xCD82, + 0xAD63, 0xCD83, 0xAD64, 0xCD84, 0xAD65, 0xCD85, 0xAD66, 0xCD86, 0xAD67, 0xCD87, 0xAD68, 0xCD89, 0xAD69, 0xCD8A, 0xAD6A, 0xCD8B, + 0xAD6B, 0xCD8C, 0xAD6C, 0xCD8D, 0xAD6D, 0xCD8E, 0xAD6E, 0xCD8F, 0xAD6F, 0xCD90, 0xAD70, 0xCD91, 0xAD71, 0xCD92, 0xAD72, 0xCD93, + 0xAD73, 0xCD96, 0xAD74, 0xCD97, 0xAD75, 0xCD99, 0xAD76, 0xCD9A, 0xAD77, 0xCD9B, 0xAD78, 0xCD9D, 0xAD79, 0xCD9E, 0xAD7A, 0xCD9F, + 0xAD81, 0xCDA0, 0xAD82, 0xCDA1, 0xAD83, 0xCDA2, 0xAD84, 0xCDA3, 0xAD85, 0xCDA6, 0xAD86, 0xCDA8, 0xAD87, 0xCDAA, 0xAD88, 0xCDAB, + 0xAD89, 0xCDAC, 0xAD8A, 0xCDAD, 0xAD8B, 0xCDAE, 0xAD8C, 0xCDAF, 0xAD8D, 0xCDB1, 0xAD8E, 0xCDB2, 0xAD8F, 0xCDB3, 0xAD90, 0xCDB4, + 0xAD91, 0xCDB5, 0xAD92, 0xCDB6, 0xAD93, 0xCDB7, 0xAD94, 0xCDB8, 0xAD95, 0xCDB9, 0xAD96, 0xCDBA, 0xAD97, 0xCDBB, 0xAD98, 0xCDBC, + 0xAD99, 0xCDBD, 0xAD9A, 0xCDBE, 0xAD9B, 0xCDBF, 0xAD9C, 0xCDC0, 0xAD9D, 0xCDC1, 0xAD9E, 0xCDC2, 0xAD9F, 0xCDC3, 0xADA0, 0xCDC5, + 0xAE41, 0xCDC6, 0xAE42, 0xCDC7, 0xAE43, 0xCDC8, 0xAE44, 0xCDC9, 0xAE45, 0xCDCA, 0xAE46, 0xCDCB, 0xAE47, 0xCDCD, 0xAE48, 0xCDCE, + 0xAE49, 0xCDCF, 0xAE4A, 0xCDD1, 0xAE4B, 0xCDD2, 0xAE4C, 0xCDD3, 0xAE4D, 0xCDD4, 0xAE4E, 0xCDD5, 0xAE4F, 0xCDD6, 0xAE50, 0xCDD7, + 0xAE51, 0xCDD8, 0xAE52, 0xCDD9, 0xAE53, 0xCDDA, 0xAE54, 0xCDDB, 0xAE55, 0xCDDC, 0xAE56, 0xCDDD, 0xAE57, 0xCDDE, 0xAE58, 0xCDDF, + 0xAE59, 0xCDE0, 0xAE5A, 0xCDE1, 0xAE61, 0xCDE2, 0xAE62, 0xCDE3, 0xAE63, 0xCDE4, 0xAE64, 0xCDE5, 0xAE65, 0xCDE6, 0xAE66, 0xCDE7, + 0xAE67, 0xCDE9, 0xAE68, 0xCDEA, 0xAE69, 0xCDEB, 0xAE6A, 0xCDED, 0xAE6B, 0xCDEE, 0xAE6C, 0xCDEF, 0xAE6D, 0xCDF1, 0xAE6E, 0xCDF2, + 0xAE6F, 0xCDF3, 0xAE70, 0xCDF4, 0xAE71, 0xCDF5, 0xAE72, 0xCDF6, 0xAE73, 0xCDF7, 0xAE74, 0xCDFA, 0xAE75, 0xCDFC, 0xAE76, 0xCDFE, + 0xAE77, 0xCDFF, 0xAE78, 0xCE00, 0xAE79, 0xCE01, 0xAE7A, 0xCE02, 0xAE81, 0xCE03, 0xAE82, 0xCE05, 0xAE83, 0xCE06, 0xAE84, 0xCE07, + 0xAE85, 0xCE09, 0xAE86, 0xCE0A, 0xAE87, 0xCE0B, 0xAE88, 0xCE0D, 0xAE89, 0xCE0E, 0xAE8A, 0xCE0F, 0xAE8B, 0xCE10, 0xAE8C, 0xCE11, + 0xAE8D, 0xCE12, 0xAE8E, 0xCE13, 0xAE8F, 0xCE15, 0xAE90, 0xCE16, 0xAE91, 0xCE17, 0xAE92, 0xCE18, 0xAE93, 0xCE1A, 0xAE94, 0xCE1B, + 0xAE95, 0xCE1C, 0xAE96, 0xCE1D, 0xAE97, 0xCE1E, 0xAE98, 0xCE1F, 0xAE99, 0xCE22, 0xAE9A, 0xCE23, 0xAE9B, 0xCE25, 0xAE9C, 0xCE26, + 0xAE9D, 0xCE27, 0xAE9E, 0xCE29, 0xAE9F, 0xCE2A, 0xAEA0, 0xCE2B, 0xAF41, 0xCE2C, 0xAF42, 0xCE2D, 0xAF43, 0xCE2E, 0xAF44, 0xCE2F, + 0xAF45, 0xCE32, 0xAF46, 0xCE34, 0xAF47, 0xCE36, 0xAF48, 0xCE37, 0xAF49, 0xCE38, 0xAF4A, 0xCE39, 0xAF4B, 0xCE3A, 0xAF4C, 0xCE3B, + 0xAF4D, 0xCE3C, 0xAF4E, 0xCE3D, 0xAF4F, 0xCE3E, 0xAF50, 0xCE3F, 0xAF51, 0xCE40, 0xAF52, 0xCE41, 0xAF53, 0xCE42, 0xAF54, 0xCE43, + 0xAF55, 0xCE44, 0xAF56, 0xCE45, 0xAF57, 0xCE46, 0xAF58, 0xCE47, 0xAF59, 0xCE48, 0xAF5A, 0xCE49, 0xAF61, 0xCE4A, 0xAF62, 0xCE4B, + 0xAF63, 0xCE4C, 0xAF64, 0xCE4D, 0xAF65, 0xCE4E, 0xAF66, 0xCE4F, 0xAF67, 0xCE50, 0xAF68, 0xCE51, 0xAF69, 0xCE52, 0xAF6A, 0xCE53, + 0xAF6B, 0xCE54, 0xAF6C, 0xCE55, 0xAF6D, 0xCE56, 0xAF6E, 0xCE57, 0xAF6F, 0xCE5A, 0xAF70, 0xCE5B, 0xAF71, 0xCE5D, 0xAF72, 0xCE5E, + 0xAF73, 0xCE62, 0xAF74, 0xCE63, 0xAF75, 0xCE64, 0xAF76, 0xCE65, 0xAF77, 0xCE66, 0xAF78, 0xCE67, 0xAF79, 0xCE6A, 0xAF7A, 0xCE6C, + 0xAF81, 0xCE6E, 0xAF82, 0xCE6F, 0xAF83, 0xCE70, 0xAF84, 0xCE71, 0xAF85, 0xCE72, 0xAF86, 0xCE73, 0xAF87, 0xCE76, 0xAF88, 0xCE77, + 0xAF89, 0xCE79, 0xAF8A, 0xCE7A, 0xAF8B, 0xCE7B, 0xAF8C, 0xCE7D, 0xAF8D, 0xCE7E, 0xAF8E, 0xCE7F, 0xAF8F, 0xCE80, 0xAF90, 0xCE81, + 0xAF91, 0xCE82, 0xAF92, 0xCE83, 0xAF93, 0xCE86, 0xAF94, 0xCE88, 0xAF95, 0xCE8A, 0xAF96, 0xCE8B, 0xAF97, 0xCE8C, 0xAF98, 0xCE8D, + 0xAF99, 0xCE8E, 0xAF9A, 0xCE8F, 0xAF9B, 0xCE92, 0xAF9C, 0xCE93, 0xAF9D, 0xCE95, 0xAF9E, 0xCE96, 0xAF9F, 0xCE97, 0xAFA0, 0xCE99, + 0xB041, 0xCE9A, 0xB042, 0xCE9B, 0xB043, 0xCE9C, 0xB044, 0xCE9D, 0xB045, 0xCE9E, 0xB046, 0xCE9F, 0xB047, 0xCEA2, 0xB048, 0xCEA6, + 0xB049, 0xCEA7, 0xB04A, 0xCEA8, 0xB04B, 0xCEA9, 0xB04C, 0xCEAA, 0xB04D, 0xCEAB, 0xB04E, 0xCEAE, 0xB04F, 0xCEAF, 0xB050, 0xCEB0, + 0xB051, 0xCEB1, 0xB052, 0xCEB2, 0xB053, 0xCEB3, 0xB054, 0xCEB4, 0xB055, 0xCEB5, 0xB056, 0xCEB6, 0xB057, 0xCEB7, 0xB058, 0xCEB8, + 0xB059, 0xCEB9, 0xB05A, 0xCEBA, 0xB061, 0xCEBB, 0xB062, 0xCEBC, 0xB063, 0xCEBD, 0xB064, 0xCEBE, 0xB065, 0xCEBF, 0xB066, 0xCEC0, + 0xB067, 0xCEC2, 0xB068, 0xCEC3, 0xB069, 0xCEC4, 0xB06A, 0xCEC5, 0xB06B, 0xCEC6, 0xB06C, 0xCEC7, 0xB06D, 0xCEC8, 0xB06E, 0xCEC9, + 0xB06F, 0xCECA, 0xB070, 0xCECB, 0xB071, 0xCECC, 0xB072, 0xCECD, 0xB073, 0xCECE, 0xB074, 0xCECF, 0xB075, 0xCED0, 0xB076, 0xCED1, + 0xB077, 0xCED2, 0xB078, 0xCED3, 0xB079, 0xCED4, 0xB07A, 0xCED5, 0xB081, 0xCED6, 0xB082, 0xCED7, 0xB083, 0xCED8, 0xB084, 0xCED9, + 0xB085, 0xCEDA, 0xB086, 0xCEDB, 0xB087, 0xCEDC, 0xB088, 0xCEDD, 0xB089, 0xCEDE, 0xB08A, 0xCEDF, 0xB08B, 0xCEE0, 0xB08C, 0xCEE1, + 0xB08D, 0xCEE2, 0xB08E, 0xCEE3, 0xB08F, 0xCEE6, 0xB090, 0xCEE7, 0xB091, 0xCEE9, 0xB092, 0xCEEA, 0xB093, 0xCEED, 0xB094, 0xCEEE, + 0xB095, 0xCEEF, 0xB096, 0xCEF0, 0xB097, 0xCEF1, 0xB098, 0xCEF2, 0xB099, 0xCEF3, 0xB09A, 0xCEF6, 0xB09B, 0xCEFA, 0xB09C, 0xCEFB, + 0xB09D, 0xCEFC, 0xB09E, 0xCEFD, 0xB09F, 0xCEFE, 0xB0A0, 0xCEFF, 0xB0A1, 0xAC00, 0xB0A2, 0xAC01, 0xB0A3, 0xAC04, 0xB0A4, 0xAC07, + 0xB0A5, 0xAC08, 0xB0A6, 0xAC09, 0xB0A7, 0xAC0A, 0xB0A8, 0xAC10, 0xB0A9, 0xAC11, 0xB0AA, 0xAC12, 0xB0AB, 0xAC13, 0xB0AC, 0xAC14, + 0xB0AD, 0xAC15, 0xB0AE, 0xAC16, 0xB0AF, 0xAC17, 0xB0B0, 0xAC19, 0xB0B1, 0xAC1A, 0xB0B2, 0xAC1B, 0xB0B3, 0xAC1C, 0xB0B4, 0xAC1D, + 0xB0B5, 0xAC20, 0xB0B6, 0xAC24, 0xB0B7, 0xAC2C, 0xB0B8, 0xAC2D, 0xB0B9, 0xAC2F, 0xB0BA, 0xAC30, 0xB0BB, 0xAC31, 0xB0BC, 0xAC38, + 0xB0BD, 0xAC39, 0xB0BE, 0xAC3C, 0xB0BF, 0xAC40, 0xB0C0, 0xAC4B, 0xB0C1, 0xAC4D, 0xB0C2, 0xAC54, 0xB0C3, 0xAC58, 0xB0C4, 0xAC5C, + 0xB0C5, 0xAC70, 0xB0C6, 0xAC71, 0xB0C7, 0xAC74, 0xB0C8, 0xAC77, 0xB0C9, 0xAC78, 0xB0CA, 0xAC7A, 0xB0CB, 0xAC80, 0xB0CC, 0xAC81, + 0xB0CD, 0xAC83, 0xB0CE, 0xAC84, 0xB0CF, 0xAC85, 0xB0D0, 0xAC86, 0xB0D1, 0xAC89, 0xB0D2, 0xAC8A, 0xB0D3, 0xAC8B, 0xB0D4, 0xAC8C, + 0xB0D5, 0xAC90, 0xB0D6, 0xAC94, 0xB0D7, 0xAC9C, 0xB0D8, 0xAC9D, 0xB0D9, 0xAC9F, 0xB0DA, 0xACA0, 0xB0DB, 0xACA1, 0xB0DC, 0xACA8, + 0xB0DD, 0xACA9, 0xB0DE, 0xACAA, 0xB0DF, 0xACAC, 0xB0E0, 0xACAF, 0xB0E1, 0xACB0, 0xB0E2, 0xACB8, 0xB0E3, 0xACB9, 0xB0E4, 0xACBB, + 0xB0E5, 0xACBC, 0xB0E6, 0xACBD, 0xB0E7, 0xACC1, 0xB0E8, 0xACC4, 0xB0E9, 0xACC8, 0xB0EA, 0xACCC, 0xB0EB, 0xACD5, 0xB0EC, 0xACD7, + 0xB0ED, 0xACE0, 0xB0EE, 0xACE1, 0xB0EF, 0xACE4, 0xB0F0, 0xACE7, 0xB0F1, 0xACE8, 0xB0F2, 0xACEA, 0xB0F3, 0xACEC, 0xB0F4, 0xACEF, + 0xB0F5, 0xACF0, 0xB0F6, 0xACF1, 0xB0F7, 0xACF3, 0xB0F8, 0xACF5, 0xB0F9, 0xACF6, 0xB0FA, 0xACFC, 0xB0FB, 0xACFD, 0xB0FC, 0xAD00, + 0xB0FD, 0xAD04, 0xB0FE, 0xAD06, 0xB141, 0xCF02, 0xB142, 0xCF03, 0xB143, 0xCF05, 0xB144, 0xCF06, 0xB145, 0xCF07, 0xB146, 0xCF09, + 0xB147, 0xCF0A, 0xB148, 0xCF0B, 0xB149, 0xCF0C, 0xB14A, 0xCF0D, 0xB14B, 0xCF0E, 0xB14C, 0xCF0F, 0xB14D, 0xCF12, 0xB14E, 0xCF14, + 0xB14F, 0xCF16, 0xB150, 0xCF17, 0xB151, 0xCF18, 0xB152, 0xCF19, 0xB153, 0xCF1A, 0xB154, 0xCF1B, 0xB155, 0xCF1D, 0xB156, 0xCF1E, + 0xB157, 0xCF1F, 0xB158, 0xCF21, 0xB159, 0xCF22, 0xB15A, 0xCF23, 0xB161, 0xCF25, 0xB162, 0xCF26, 0xB163, 0xCF27, 0xB164, 0xCF28, + 0xB165, 0xCF29, 0xB166, 0xCF2A, 0xB167, 0xCF2B, 0xB168, 0xCF2E, 0xB169, 0xCF32, 0xB16A, 0xCF33, 0xB16B, 0xCF34, 0xB16C, 0xCF35, + 0xB16D, 0xCF36, 0xB16E, 0xCF37, 0xB16F, 0xCF39, 0xB170, 0xCF3A, 0xB171, 0xCF3B, 0xB172, 0xCF3C, 0xB173, 0xCF3D, 0xB174, 0xCF3E, + 0xB175, 0xCF3F, 0xB176, 0xCF40, 0xB177, 0xCF41, 0xB178, 0xCF42, 0xB179, 0xCF43, 0xB17A, 0xCF44, 0xB181, 0xCF45, 0xB182, 0xCF46, + 0xB183, 0xCF47, 0xB184, 0xCF48, 0xB185, 0xCF49, 0xB186, 0xCF4A, 0xB187, 0xCF4B, 0xB188, 0xCF4C, 0xB189, 0xCF4D, 0xB18A, 0xCF4E, + 0xB18B, 0xCF4F, 0xB18C, 0xCF50, 0xB18D, 0xCF51, 0xB18E, 0xCF52, 0xB18F, 0xCF53, 0xB190, 0xCF56, 0xB191, 0xCF57, 0xB192, 0xCF59, + 0xB193, 0xCF5A, 0xB194, 0xCF5B, 0xB195, 0xCF5D, 0xB196, 0xCF5E, 0xB197, 0xCF5F, 0xB198, 0xCF60, 0xB199, 0xCF61, 0xB19A, 0xCF62, + 0xB19B, 0xCF63, 0xB19C, 0xCF66, 0xB19D, 0xCF68, 0xB19E, 0xCF6A, 0xB19F, 0xCF6B, 0xB1A0, 0xCF6C, 0xB1A1, 0xAD0C, 0xB1A2, 0xAD0D, + 0xB1A3, 0xAD0F, 0xB1A4, 0xAD11, 0xB1A5, 0xAD18, 0xB1A6, 0xAD1C, 0xB1A7, 0xAD20, 0xB1A8, 0xAD29, 0xB1A9, 0xAD2C, 0xB1AA, 0xAD2D, + 0xB1AB, 0xAD34, 0xB1AC, 0xAD35, 0xB1AD, 0xAD38, 0xB1AE, 0xAD3C, 0xB1AF, 0xAD44, 0xB1B0, 0xAD45, 0xB1B1, 0xAD47, 0xB1B2, 0xAD49, + 0xB1B3, 0xAD50, 0xB1B4, 0xAD54, 0xB1B5, 0xAD58, 0xB1B6, 0xAD61, 0xB1B7, 0xAD63, 0xB1B8, 0xAD6C, 0xB1B9, 0xAD6D, 0xB1BA, 0xAD70, + 0xB1BB, 0xAD73, 0xB1BC, 0xAD74, 0xB1BD, 0xAD75, 0xB1BE, 0xAD76, 0xB1BF, 0xAD7B, 0xB1C0, 0xAD7C, 0xB1C1, 0xAD7D, 0xB1C2, 0xAD7F, + 0xB1C3, 0xAD81, 0xB1C4, 0xAD82, 0xB1C5, 0xAD88, 0xB1C6, 0xAD89, 0xB1C7, 0xAD8C, 0xB1C8, 0xAD90, 0xB1C9, 0xAD9C, 0xB1CA, 0xAD9D, + 0xB1CB, 0xADA4, 0xB1CC, 0xADB7, 0xB1CD, 0xADC0, 0xB1CE, 0xADC1, 0xB1CF, 0xADC4, 0xB1D0, 0xADC8, 0xB1D1, 0xADD0, 0xB1D2, 0xADD1, + 0xB1D3, 0xADD3, 0xB1D4, 0xADDC, 0xB1D5, 0xADE0, 0xB1D6, 0xADE4, 0xB1D7, 0xADF8, 0xB1D8, 0xADF9, 0xB1D9, 0xADFC, 0xB1DA, 0xADFF, + 0xB1DB, 0xAE00, 0xB1DC, 0xAE01, 0xB1DD, 0xAE08, 0xB1DE, 0xAE09, 0xB1DF, 0xAE0B, 0xB1E0, 0xAE0D, 0xB1E1, 0xAE14, 0xB1E2, 0xAE30, + 0xB1E3, 0xAE31, 0xB1E4, 0xAE34, 0xB1E5, 0xAE37, 0xB1E6, 0xAE38, 0xB1E7, 0xAE3A, 0xB1E8, 0xAE40, 0xB1E9, 0xAE41, 0xB1EA, 0xAE43, + 0xB1EB, 0xAE45, 0xB1EC, 0xAE46, 0xB1ED, 0xAE4A, 0xB1EE, 0xAE4C, 0xB1EF, 0xAE4D, 0xB1F0, 0xAE4E, 0xB1F1, 0xAE50, 0xB1F2, 0xAE54, + 0xB1F3, 0xAE56, 0xB1F4, 0xAE5C, 0xB1F5, 0xAE5D, 0xB1F6, 0xAE5F, 0xB1F7, 0xAE60, 0xB1F8, 0xAE61, 0xB1F9, 0xAE65, 0xB1FA, 0xAE68, + 0xB1FB, 0xAE69, 0xB1FC, 0xAE6C, 0xB1FD, 0xAE70, 0xB1FE, 0xAE78, 0xB241, 0xCF6D, 0xB242, 0xCF6E, 0xB243, 0xCF6F, 0xB244, 0xCF72, + 0xB245, 0xCF73, 0xB246, 0xCF75, 0xB247, 0xCF76, 0xB248, 0xCF77, 0xB249, 0xCF79, 0xB24A, 0xCF7A, 0xB24B, 0xCF7B, 0xB24C, 0xCF7C, + 0xB24D, 0xCF7D, 0xB24E, 0xCF7E, 0xB24F, 0xCF7F, 0xB250, 0xCF81, 0xB251, 0xCF82, 0xB252, 0xCF83, 0xB253, 0xCF84, 0xB254, 0xCF86, + 0xB255, 0xCF87, 0xB256, 0xCF88, 0xB257, 0xCF89, 0xB258, 0xCF8A, 0xB259, 0xCF8B, 0xB25A, 0xCF8D, 0xB261, 0xCF8E, 0xB262, 0xCF8F, + 0xB263, 0xCF90, 0xB264, 0xCF91, 0xB265, 0xCF92, 0xB266, 0xCF93, 0xB267, 0xCF94, 0xB268, 0xCF95, 0xB269, 0xCF96, 0xB26A, 0xCF97, + 0xB26B, 0xCF98, 0xB26C, 0xCF99, 0xB26D, 0xCF9A, 0xB26E, 0xCF9B, 0xB26F, 0xCF9C, 0xB270, 0xCF9D, 0xB271, 0xCF9E, 0xB272, 0xCF9F, + 0xB273, 0xCFA0, 0xB274, 0xCFA2, 0xB275, 0xCFA3, 0xB276, 0xCFA4, 0xB277, 0xCFA5, 0xB278, 0xCFA6, 0xB279, 0xCFA7, 0xB27A, 0xCFA9, + 0xB281, 0xCFAA, 0xB282, 0xCFAB, 0xB283, 0xCFAC, 0xB284, 0xCFAD, 0xB285, 0xCFAE, 0xB286, 0xCFAF, 0xB287, 0xCFB1, 0xB288, 0xCFB2, + 0xB289, 0xCFB3, 0xB28A, 0xCFB4, 0xB28B, 0xCFB5, 0xB28C, 0xCFB6, 0xB28D, 0xCFB7, 0xB28E, 0xCFB8, 0xB28F, 0xCFB9, 0xB290, 0xCFBA, + 0xB291, 0xCFBB, 0xB292, 0xCFBC, 0xB293, 0xCFBD, 0xB294, 0xCFBE, 0xB295, 0xCFBF, 0xB296, 0xCFC0, 0xB297, 0xCFC1, 0xB298, 0xCFC2, + 0xB299, 0xCFC3, 0xB29A, 0xCFC5, 0xB29B, 0xCFC6, 0xB29C, 0xCFC7, 0xB29D, 0xCFC8, 0xB29E, 0xCFC9, 0xB29F, 0xCFCA, 0xB2A0, 0xCFCB, + 0xB2A1, 0xAE79, 0xB2A2, 0xAE7B, 0xB2A3, 0xAE7C, 0xB2A4, 0xAE7D, 0xB2A5, 0xAE84, 0xB2A6, 0xAE85, 0xB2A7, 0xAE8C, 0xB2A8, 0xAEBC, + 0xB2A9, 0xAEBD, 0xB2AA, 0xAEBE, 0xB2AB, 0xAEC0, 0xB2AC, 0xAEC4, 0xB2AD, 0xAECC, 0xB2AE, 0xAECD, 0xB2AF, 0xAECF, 0xB2B0, 0xAED0, + 0xB2B1, 0xAED1, 0xB2B2, 0xAED8, 0xB2B3, 0xAED9, 0xB2B4, 0xAEDC, 0xB2B5, 0xAEE8, 0xB2B6, 0xAEEB, 0xB2B7, 0xAEED, 0xB2B8, 0xAEF4, + 0xB2B9, 0xAEF8, 0xB2BA, 0xAEFC, 0xB2BB, 0xAF07, 0xB2BC, 0xAF08, 0xB2BD, 0xAF0D, 0xB2BE, 0xAF10, 0xB2BF, 0xAF2C, 0xB2C0, 0xAF2D, + 0xB2C1, 0xAF30, 0xB2C2, 0xAF32, 0xB2C3, 0xAF34, 0xB2C4, 0xAF3C, 0xB2C5, 0xAF3D, 0xB2C6, 0xAF3F, 0xB2C7, 0xAF41, 0xB2C8, 0xAF42, + 0xB2C9, 0xAF43, 0xB2CA, 0xAF48, 0xB2CB, 0xAF49, 0xB2CC, 0xAF50, 0xB2CD, 0xAF5C, 0xB2CE, 0xAF5D, 0xB2CF, 0xAF64, 0xB2D0, 0xAF65, + 0xB2D1, 0xAF79, 0xB2D2, 0xAF80, 0xB2D3, 0xAF84, 0xB2D4, 0xAF88, 0xB2D5, 0xAF90, 0xB2D6, 0xAF91, 0xB2D7, 0xAF95, 0xB2D8, 0xAF9C, + 0xB2D9, 0xAFB8, 0xB2DA, 0xAFB9, 0xB2DB, 0xAFBC, 0xB2DC, 0xAFC0, 0xB2DD, 0xAFC7, 0xB2DE, 0xAFC8, 0xB2DF, 0xAFC9, 0xB2E0, 0xAFCB, + 0xB2E1, 0xAFCD, 0xB2E2, 0xAFCE, 0xB2E3, 0xAFD4, 0xB2E4, 0xAFDC, 0xB2E5, 0xAFE8, 0xB2E6, 0xAFE9, 0xB2E7, 0xAFF0, 0xB2E8, 0xAFF1, + 0xB2E9, 0xAFF4, 0xB2EA, 0xAFF8, 0xB2EB, 0xB000, 0xB2EC, 0xB001, 0xB2ED, 0xB004, 0xB2EE, 0xB00C, 0xB2EF, 0xB010, 0xB2F0, 0xB014, + 0xB2F1, 0xB01C, 0xB2F2, 0xB01D, 0xB2F3, 0xB028, 0xB2F4, 0xB044, 0xB2F5, 0xB045, 0xB2F6, 0xB048, 0xB2F7, 0xB04A, 0xB2F8, 0xB04C, + 0xB2F9, 0xB04E, 0xB2FA, 0xB053, 0xB2FB, 0xB054, 0xB2FC, 0xB055, 0xB2FD, 0xB057, 0xB2FE, 0xB059, 0xB341, 0xCFCC, 0xB342, 0xCFCD, + 0xB343, 0xCFCE, 0xB344, 0xCFCF, 0xB345, 0xCFD0, 0xB346, 0xCFD1, 0xB347, 0xCFD2, 0xB348, 0xCFD3, 0xB349, 0xCFD4, 0xB34A, 0xCFD5, + 0xB34B, 0xCFD6, 0xB34C, 0xCFD7, 0xB34D, 0xCFD8, 0xB34E, 0xCFD9, 0xB34F, 0xCFDA, 0xB350, 0xCFDB, 0xB351, 0xCFDC, 0xB352, 0xCFDD, + 0xB353, 0xCFDE, 0xB354, 0xCFDF, 0xB355, 0xCFE2, 0xB356, 0xCFE3, 0xB357, 0xCFE5, 0xB358, 0xCFE6, 0xB359, 0xCFE7, 0xB35A, 0xCFE9, + 0xB361, 0xCFEA, 0xB362, 0xCFEB, 0xB363, 0xCFEC, 0xB364, 0xCFED, 0xB365, 0xCFEE, 0xB366, 0xCFEF, 0xB367, 0xCFF2, 0xB368, 0xCFF4, + 0xB369, 0xCFF6, 0xB36A, 0xCFF7, 0xB36B, 0xCFF8, 0xB36C, 0xCFF9, 0xB36D, 0xCFFA, 0xB36E, 0xCFFB, 0xB36F, 0xCFFD, 0xB370, 0xCFFE, + 0xB371, 0xCFFF, 0xB372, 0xD001, 0xB373, 0xD002, 0xB374, 0xD003, 0xB375, 0xD005, 0xB376, 0xD006, 0xB377, 0xD007, 0xB378, 0xD008, + 0xB379, 0xD009, 0xB37A, 0xD00A, 0xB381, 0xD00B, 0xB382, 0xD00C, 0xB383, 0xD00D, 0xB384, 0xD00E, 0xB385, 0xD00F, 0xB386, 0xD010, + 0xB387, 0xD012, 0xB388, 0xD013, 0xB389, 0xD014, 0xB38A, 0xD015, 0xB38B, 0xD016, 0xB38C, 0xD017, 0xB38D, 0xD019, 0xB38E, 0xD01A, + 0xB38F, 0xD01B, 0xB390, 0xD01C, 0xB391, 0xD01D, 0xB392, 0xD01E, 0xB393, 0xD01F, 0xB394, 0xD020, 0xB395, 0xD021, 0xB396, 0xD022, + 0xB397, 0xD023, 0xB398, 0xD024, 0xB399, 0xD025, 0xB39A, 0xD026, 0xB39B, 0xD027, 0xB39C, 0xD028, 0xB39D, 0xD029, 0xB39E, 0xD02A, + 0xB39F, 0xD02B, 0xB3A0, 0xD02C, 0xB3A1, 0xB05D, 0xB3A2, 0xB07C, 0xB3A3, 0xB07D, 0xB3A4, 0xB080, 0xB3A5, 0xB084, 0xB3A6, 0xB08C, + 0xB3A7, 0xB08D, 0xB3A8, 0xB08F, 0xB3A9, 0xB091, 0xB3AA, 0xB098, 0xB3AB, 0xB099, 0xB3AC, 0xB09A, 0xB3AD, 0xB09C, 0xB3AE, 0xB09F, + 0xB3AF, 0xB0A0, 0xB3B0, 0xB0A1, 0xB3B1, 0xB0A2, 0xB3B2, 0xB0A8, 0xB3B3, 0xB0A9, 0xB3B4, 0xB0AB, 0xB3B5, 0xB0AC, 0xB3B6, 0xB0AD, + 0xB3B7, 0xB0AE, 0xB3B8, 0xB0AF, 0xB3B9, 0xB0B1, 0xB3BA, 0xB0B3, 0xB3BB, 0xB0B4, 0xB3BC, 0xB0B5, 0xB3BD, 0xB0B8, 0xB3BE, 0xB0BC, + 0xB3BF, 0xB0C4, 0xB3C0, 0xB0C5, 0xB3C1, 0xB0C7, 0xB3C2, 0xB0C8, 0xB3C3, 0xB0C9, 0xB3C4, 0xB0D0, 0xB3C5, 0xB0D1, 0xB3C6, 0xB0D4, + 0xB3C7, 0xB0D8, 0xB3C8, 0xB0E0, 0xB3C9, 0xB0E5, 0xB3CA, 0xB108, 0xB3CB, 0xB109, 0xB3CC, 0xB10B, 0xB3CD, 0xB10C, 0xB3CE, 0xB110, + 0xB3CF, 0xB112, 0xB3D0, 0xB113, 0xB3D1, 0xB118, 0xB3D2, 0xB119, 0xB3D3, 0xB11B, 0xB3D4, 0xB11C, 0xB3D5, 0xB11D, 0xB3D6, 0xB123, + 0xB3D7, 0xB124, 0xB3D8, 0xB125, 0xB3D9, 0xB128, 0xB3DA, 0xB12C, 0xB3DB, 0xB134, 0xB3DC, 0xB135, 0xB3DD, 0xB137, 0xB3DE, 0xB138, + 0xB3DF, 0xB139, 0xB3E0, 0xB140, 0xB3E1, 0xB141, 0xB3E2, 0xB144, 0xB3E3, 0xB148, 0xB3E4, 0xB150, 0xB3E5, 0xB151, 0xB3E6, 0xB154, + 0xB3E7, 0xB155, 0xB3E8, 0xB158, 0xB3E9, 0xB15C, 0xB3EA, 0xB160, 0xB3EB, 0xB178, 0xB3EC, 0xB179, 0xB3ED, 0xB17C, 0xB3EE, 0xB180, + 0xB3EF, 0xB182, 0xB3F0, 0xB188, 0xB3F1, 0xB189, 0xB3F2, 0xB18B, 0xB3F3, 0xB18D, 0xB3F4, 0xB192, 0xB3F5, 0xB193, 0xB3F6, 0xB194, + 0xB3F7, 0xB198, 0xB3F8, 0xB19C, 0xB3F9, 0xB1A8, 0xB3FA, 0xB1CC, 0xB3FB, 0xB1D0, 0xB3FC, 0xB1D4, 0xB3FD, 0xB1DC, 0xB3FE, 0xB1DD, + 0xB441, 0xD02E, 0xB442, 0xD02F, 0xB443, 0xD030, 0xB444, 0xD031, 0xB445, 0xD032, 0xB446, 0xD033, 0xB447, 0xD036, 0xB448, 0xD037, + 0xB449, 0xD039, 0xB44A, 0xD03A, 0xB44B, 0xD03B, 0xB44C, 0xD03D, 0xB44D, 0xD03E, 0xB44E, 0xD03F, 0xB44F, 0xD040, 0xB450, 0xD041, + 0xB451, 0xD042, 0xB452, 0xD043, 0xB453, 0xD046, 0xB454, 0xD048, 0xB455, 0xD04A, 0xB456, 0xD04B, 0xB457, 0xD04C, 0xB458, 0xD04D, + 0xB459, 0xD04E, 0xB45A, 0xD04F, 0xB461, 0xD051, 0xB462, 0xD052, 0xB463, 0xD053, 0xB464, 0xD055, 0xB465, 0xD056, 0xB466, 0xD057, + 0xB467, 0xD059, 0xB468, 0xD05A, 0xB469, 0xD05B, 0xB46A, 0xD05C, 0xB46B, 0xD05D, 0xB46C, 0xD05E, 0xB46D, 0xD05F, 0xB46E, 0xD061, + 0xB46F, 0xD062, 0xB470, 0xD063, 0xB471, 0xD064, 0xB472, 0xD065, 0xB473, 0xD066, 0xB474, 0xD067, 0xB475, 0xD068, 0xB476, 0xD069, + 0xB477, 0xD06A, 0xB478, 0xD06B, 0xB479, 0xD06E, 0xB47A, 0xD06F, 0xB481, 0xD071, 0xB482, 0xD072, 0xB483, 0xD073, 0xB484, 0xD075, + 0xB485, 0xD076, 0xB486, 0xD077, 0xB487, 0xD078, 0xB488, 0xD079, 0xB489, 0xD07A, 0xB48A, 0xD07B, 0xB48B, 0xD07E, 0xB48C, 0xD07F, + 0xB48D, 0xD080, 0xB48E, 0xD082, 0xB48F, 0xD083, 0xB490, 0xD084, 0xB491, 0xD085, 0xB492, 0xD086, 0xB493, 0xD087, 0xB494, 0xD088, + 0xB495, 0xD089, 0xB496, 0xD08A, 0xB497, 0xD08B, 0xB498, 0xD08C, 0xB499, 0xD08D, 0xB49A, 0xD08E, 0xB49B, 0xD08F, 0xB49C, 0xD090, + 0xB49D, 0xD091, 0xB49E, 0xD092, 0xB49F, 0xD093, 0xB4A0, 0xD094, 0xB4A1, 0xB1DF, 0xB4A2, 0xB1E8, 0xB4A3, 0xB1E9, 0xB4A4, 0xB1EC, + 0xB4A5, 0xB1F0, 0xB4A6, 0xB1F9, 0xB4A7, 0xB1FB, 0xB4A8, 0xB1FD, 0xB4A9, 0xB204, 0xB4AA, 0xB205, 0xB4AB, 0xB208, 0xB4AC, 0xB20B, + 0xB4AD, 0xB20C, 0xB4AE, 0xB214, 0xB4AF, 0xB215, 0xB4B0, 0xB217, 0xB4B1, 0xB219, 0xB4B2, 0xB220, 0xB4B3, 0xB234, 0xB4B4, 0xB23C, + 0xB4B5, 0xB258, 0xB4B6, 0xB25C, 0xB4B7, 0xB260, 0xB4B8, 0xB268, 0xB4B9, 0xB269, 0xB4BA, 0xB274, 0xB4BB, 0xB275, 0xB4BC, 0xB27C, + 0xB4BD, 0xB284, 0xB4BE, 0xB285, 0xB4BF, 0xB289, 0xB4C0, 0xB290, 0xB4C1, 0xB291, 0xB4C2, 0xB294, 0xB4C3, 0xB298, 0xB4C4, 0xB299, + 0xB4C5, 0xB29A, 0xB4C6, 0xB2A0, 0xB4C7, 0xB2A1, 0xB4C8, 0xB2A3, 0xB4C9, 0xB2A5, 0xB4CA, 0xB2A6, 0xB4CB, 0xB2AA, 0xB4CC, 0xB2AC, + 0xB4CD, 0xB2B0, 0xB4CE, 0xB2B4, 0xB4CF, 0xB2C8, 0xB4D0, 0xB2C9, 0xB4D1, 0xB2CC, 0xB4D2, 0xB2D0, 0xB4D3, 0xB2D2, 0xB4D4, 0xB2D8, + 0xB4D5, 0xB2D9, 0xB4D6, 0xB2DB, 0xB4D7, 0xB2DD, 0xB4D8, 0xB2E2, 0xB4D9, 0xB2E4, 0xB4DA, 0xB2E5, 0xB4DB, 0xB2E6, 0xB4DC, 0xB2E8, + 0xB4DD, 0xB2EB, 0xB4DE, 0xB2EC, 0xB4DF, 0xB2ED, 0xB4E0, 0xB2EE, 0xB4E1, 0xB2EF, 0xB4E2, 0xB2F3, 0xB4E3, 0xB2F4, 0xB4E4, 0xB2F5, + 0xB4E5, 0xB2F7, 0xB4E6, 0xB2F8, 0xB4E7, 0xB2F9, 0xB4E8, 0xB2FA, 0xB4E9, 0xB2FB, 0xB4EA, 0xB2FF, 0xB4EB, 0xB300, 0xB4EC, 0xB301, + 0xB4ED, 0xB304, 0xB4EE, 0xB308, 0xB4EF, 0xB310, 0xB4F0, 0xB311, 0xB4F1, 0xB313, 0xB4F2, 0xB314, 0xB4F3, 0xB315, 0xB4F4, 0xB31C, + 0xB4F5, 0xB354, 0xB4F6, 0xB355, 0xB4F7, 0xB356, 0xB4F8, 0xB358, 0xB4F9, 0xB35B, 0xB4FA, 0xB35C, 0xB4FB, 0xB35E, 0xB4FC, 0xB35F, + 0xB4FD, 0xB364, 0xB4FE, 0xB365, 0xB541, 0xD095, 0xB542, 0xD096, 0xB543, 0xD097, 0xB544, 0xD098, 0xB545, 0xD099, 0xB546, 0xD09A, + 0xB547, 0xD09B, 0xB548, 0xD09C, 0xB549, 0xD09D, 0xB54A, 0xD09E, 0xB54B, 0xD09F, 0xB54C, 0xD0A0, 0xB54D, 0xD0A1, 0xB54E, 0xD0A2, + 0xB54F, 0xD0A3, 0xB550, 0xD0A6, 0xB551, 0xD0A7, 0xB552, 0xD0A9, 0xB553, 0xD0AA, 0xB554, 0xD0AB, 0xB555, 0xD0AD, 0xB556, 0xD0AE, + 0xB557, 0xD0AF, 0xB558, 0xD0B0, 0xB559, 0xD0B1, 0xB55A, 0xD0B2, 0xB561, 0xD0B3, 0xB562, 0xD0B6, 0xB563, 0xD0B8, 0xB564, 0xD0BA, + 0xB565, 0xD0BB, 0xB566, 0xD0BC, 0xB567, 0xD0BD, 0xB568, 0xD0BE, 0xB569, 0xD0BF, 0xB56A, 0xD0C2, 0xB56B, 0xD0C3, 0xB56C, 0xD0C5, + 0xB56D, 0xD0C6, 0xB56E, 0xD0C7, 0xB56F, 0xD0CA, 0xB570, 0xD0CB, 0xB571, 0xD0CC, 0xB572, 0xD0CD, 0xB573, 0xD0CE, 0xB574, 0xD0CF, + 0xB575, 0xD0D2, 0xB576, 0xD0D6, 0xB577, 0xD0D7, 0xB578, 0xD0D8, 0xB579, 0xD0D9, 0xB57A, 0xD0DA, 0xB581, 0xD0DB, 0xB582, 0xD0DE, + 0xB583, 0xD0DF, 0xB584, 0xD0E1, 0xB585, 0xD0E2, 0xB586, 0xD0E3, 0xB587, 0xD0E5, 0xB588, 0xD0E6, 0xB589, 0xD0E7, 0xB58A, 0xD0E8, + 0xB58B, 0xD0E9, 0xB58C, 0xD0EA, 0xB58D, 0xD0EB, 0xB58E, 0xD0EE, 0xB58F, 0xD0F2, 0xB590, 0xD0F3, 0xB591, 0xD0F4, 0xB592, 0xD0F5, + 0xB593, 0xD0F6, 0xB594, 0xD0F7, 0xB595, 0xD0F9, 0xB596, 0xD0FA, 0xB597, 0xD0FB, 0xB598, 0xD0FC, 0xB599, 0xD0FD, 0xB59A, 0xD0FE, + 0xB59B, 0xD0FF, 0xB59C, 0xD100, 0xB59D, 0xD101, 0xB59E, 0xD102, 0xB59F, 0xD103, 0xB5A0, 0xD104, 0xB5A1, 0xB367, 0xB5A2, 0xB369, + 0xB5A3, 0xB36B, 0xB5A4, 0xB36E, 0xB5A5, 0xB370, 0xB5A6, 0xB371, 0xB5A7, 0xB374, 0xB5A8, 0xB378, 0xB5A9, 0xB380, 0xB5AA, 0xB381, + 0xB5AB, 0xB383, 0xB5AC, 0xB384, 0xB5AD, 0xB385, 0xB5AE, 0xB38C, 0xB5AF, 0xB390, 0xB5B0, 0xB394, 0xB5B1, 0xB3A0, 0xB5B2, 0xB3A1, + 0xB5B3, 0xB3A8, 0xB5B4, 0xB3AC, 0xB5B5, 0xB3C4, 0xB5B6, 0xB3C5, 0xB5B7, 0xB3C8, 0xB5B8, 0xB3CB, 0xB5B9, 0xB3CC, 0xB5BA, 0xB3CE, + 0xB5BB, 0xB3D0, 0xB5BC, 0xB3D4, 0xB5BD, 0xB3D5, 0xB5BE, 0xB3D7, 0xB5BF, 0xB3D9, 0xB5C0, 0xB3DB, 0xB5C1, 0xB3DD, 0xB5C2, 0xB3E0, + 0xB5C3, 0xB3E4, 0xB5C4, 0xB3E8, 0xB5C5, 0xB3FC, 0xB5C6, 0xB410, 0xB5C7, 0xB418, 0xB5C8, 0xB41C, 0xB5C9, 0xB420, 0xB5CA, 0xB428, + 0xB5CB, 0xB429, 0xB5CC, 0xB42B, 0xB5CD, 0xB434, 0xB5CE, 0xB450, 0xB5CF, 0xB451, 0xB5D0, 0xB454, 0xB5D1, 0xB458, 0xB5D2, 0xB460, + 0xB5D3, 0xB461, 0xB5D4, 0xB463, 0xB5D5, 0xB465, 0xB5D6, 0xB46C, 0xB5D7, 0xB480, 0xB5D8, 0xB488, 0xB5D9, 0xB49D, 0xB5DA, 0xB4A4, + 0xB5DB, 0xB4A8, 0xB5DC, 0xB4AC, 0xB5DD, 0xB4B5, 0xB5DE, 0xB4B7, 0xB5DF, 0xB4B9, 0xB5E0, 0xB4C0, 0xB5E1, 0xB4C4, 0xB5E2, 0xB4C8, + 0xB5E3, 0xB4D0, 0xB5E4, 0xB4D5, 0xB5E5, 0xB4DC, 0xB5E6, 0xB4DD, 0xB5E7, 0xB4E0, 0xB5E8, 0xB4E3, 0xB5E9, 0xB4E4, 0xB5EA, 0xB4E6, + 0xB5EB, 0xB4EC, 0xB5EC, 0xB4ED, 0xB5ED, 0xB4EF, 0xB5EE, 0xB4F1, 0xB5EF, 0xB4F8, 0xB5F0, 0xB514, 0xB5F1, 0xB515, 0xB5F2, 0xB518, + 0xB5F3, 0xB51B, 0xB5F4, 0xB51C, 0xB5F5, 0xB524, 0xB5F6, 0xB525, 0xB5F7, 0xB527, 0xB5F8, 0xB528, 0xB5F9, 0xB529, 0xB5FA, 0xB52A, + 0xB5FB, 0xB530, 0xB5FC, 0xB531, 0xB5FD, 0xB534, 0xB5FE, 0xB538, 0xB641, 0xD105, 0xB642, 0xD106, 0xB643, 0xD107, 0xB644, 0xD108, + 0xB645, 0xD109, 0xB646, 0xD10A, 0xB647, 0xD10B, 0xB648, 0xD10C, 0xB649, 0xD10E, 0xB64A, 0xD10F, 0xB64B, 0xD110, 0xB64C, 0xD111, + 0xB64D, 0xD112, 0xB64E, 0xD113, 0xB64F, 0xD114, 0xB650, 0xD115, 0xB651, 0xD116, 0xB652, 0xD117, 0xB653, 0xD118, 0xB654, 0xD119, + 0xB655, 0xD11A, 0xB656, 0xD11B, 0xB657, 0xD11C, 0xB658, 0xD11D, 0xB659, 0xD11E, 0xB65A, 0xD11F, 0xB661, 0xD120, 0xB662, 0xD121, + 0xB663, 0xD122, 0xB664, 0xD123, 0xB665, 0xD124, 0xB666, 0xD125, 0xB667, 0xD126, 0xB668, 0xD127, 0xB669, 0xD128, 0xB66A, 0xD129, + 0xB66B, 0xD12A, 0xB66C, 0xD12B, 0xB66D, 0xD12C, 0xB66E, 0xD12D, 0xB66F, 0xD12E, 0xB670, 0xD12F, 0xB671, 0xD132, 0xB672, 0xD133, + 0xB673, 0xD135, 0xB674, 0xD136, 0xB675, 0xD137, 0xB676, 0xD139, 0xB677, 0xD13B, 0xB678, 0xD13C, 0xB679, 0xD13D, 0xB67A, 0xD13E, + 0xB681, 0xD13F, 0xB682, 0xD142, 0xB683, 0xD146, 0xB684, 0xD147, 0xB685, 0xD148, 0xB686, 0xD149, 0xB687, 0xD14A, 0xB688, 0xD14B, + 0xB689, 0xD14E, 0xB68A, 0xD14F, 0xB68B, 0xD151, 0xB68C, 0xD152, 0xB68D, 0xD153, 0xB68E, 0xD155, 0xB68F, 0xD156, 0xB690, 0xD157, + 0xB691, 0xD158, 0xB692, 0xD159, 0xB693, 0xD15A, 0xB694, 0xD15B, 0xB695, 0xD15E, 0xB696, 0xD160, 0xB697, 0xD162, 0xB698, 0xD163, + 0xB699, 0xD164, 0xB69A, 0xD165, 0xB69B, 0xD166, 0xB69C, 0xD167, 0xB69D, 0xD169, 0xB69E, 0xD16A, 0xB69F, 0xD16B, 0xB6A0, 0xD16D, + 0xB6A1, 0xB540, 0xB6A2, 0xB541, 0xB6A3, 0xB543, 0xB6A4, 0xB544, 0xB6A5, 0xB545, 0xB6A6, 0xB54B, 0xB6A7, 0xB54C, 0xB6A8, 0xB54D, + 0xB6A9, 0xB550, 0xB6AA, 0xB554, 0xB6AB, 0xB55C, 0xB6AC, 0xB55D, 0xB6AD, 0xB55F, 0xB6AE, 0xB560, 0xB6AF, 0xB561, 0xB6B0, 0xB5A0, + 0xB6B1, 0xB5A1, 0xB6B2, 0xB5A4, 0xB6B3, 0xB5A8, 0xB6B4, 0xB5AA, 0xB6B5, 0xB5AB, 0xB6B6, 0xB5B0, 0xB6B7, 0xB5B1, 0xB6B8, 0xB5B3, + 0xB6B9, 0xB5B4, 0xB6BA, 0xB5B5, 0xB6BB, 0xB5BB, 0xB6BC, 0xB5BC, 0xB6BD, 0xB5BD, 0xB6BE, 0xB5C0, 0xB6BF, 0xB5C4, 0xB6C0, 0xB5CC, + 0xB6C1, 0xB5CD, 0xB6C2, 0xB5CF, 0xB6C3, 0xB5D0, 0xB6C4, 0xB5D1, 0xB6C5, 0xB5D8, 0xB6C6, 0xB5EC, 0xB6C7, 0xB610, 0xB6C8, 0xB611, + 0xB6C9, 0xB614, 0xB6CA, 0xB618, 0xB6CB, 0xB625, 0xB6CC, 0xB62C, 0xB6CD, 0xB634, 0xB6CE, 0xB648, 0xB6CF, 0xB664, 0xB6D0, 0xB668, + 0xB6D1, 0xB69C, 0xB6D2, 0xB69D, 0xB6D3, 0xB6A0, 0xB6D4, 0xB6A4, 0xB6D5, 0xB6AB, 0xB6D6, 0xB6AC, 0xB6D7, 0xB6B1, 0xB6D8, 0xB6D4, + 0xB6D9, 0xB6F0, 0xB6DA, 0xB6F4, 0xB6DB, 0xB6F8, 0xB6DC, 0xB700, 0xB6DD, 0xB701, 0xB6DE, 0xB705, 0xB6DF, 0xB728, 0xB6E0, 0xB729, + 0xB6E1, 0xB72C, 0xB6E2, 0xB72F, 0xB6E3, 0xB730, 0xB6E4, 0xB738, 0xB6E5, 0xB739, 0xB6E6, 0xB73B, 0xB6E7, 0xB744, 0xB6E8, 0xB748, + 0xB6E9, 0xB74C, 0xB6EA, 0xB754, 0xB6EB, 0xB755, 0xB6EC, 0xB760, 0xB6ED, 0xB764, 0xB6EE, 0xB768, 0xB6EF, 0xB770, 0xB6F0, 0xB771, + 0xB6F1, 0xB773, 0xB6F2, 0xB775, 0xB6F3, 0xB77C, 0xB6F4, 0xB77D, 0xB6F5, 0xB780, 0xB6F6, 0xB784, 0xB6F7, 0xB78C, 0xB6F8, 0xB78D, + 0xB6F9, 0xB78F, 0xB6FA, 0xB790, 0xB6FB, 0xB791, 0xB6FC, 0xB792, 0xB6FD, 0xB796, 0xB6FE, 0xB797, 0xB741, 0xD16E, 0xB742, 0xD16F, + 0xB743, 0xD170, 0xB744, 0xD171, 0xB745, 0xD172, 0xB746, 0xD173, 0xB747, 0xD174, 0xB748, 0xD175, 0xB749, 0xD176, 0xB74A, 0xD177, + 0xB74B, 0xD178, 0xB74C, 0xD179, 0xB74D, 0xD17A, 0xB74E, 0xD17B, 0xB74F, 0xD17D, 0xB750, 0xD17E, 0xB751, 0xD17F, 0xB752, 0xD180, + 0xB753, 0xD181, 0xB754, 0xD182, 0xB755, 0xD183, 0xB756, 0xD185, 0xB757, 0xD186, 0xB758, 0xD187, 0xB759, 0xD189, 0xB75A, 0xD18A, + 0xB761, 0xD18B, 0xB762, 0xD18C, 0xB763, 0xD18D, 0xB764, 0xD18E, 0xB765, 0xD18F, 0xB766, 0xD190, 0xB767, 0xD191, 0xB768, 0xD192, + 0xB769, 0xD193, 0xB76A, 0xD194, 0xB76B, 0xD195, 0xB76C, 0xD196, 0xB76D, 0xD197, 0xB76E, 0xD198, 0xB76F, 0xD199, 0xB770, 0xD19A, + 0xB771, 0xD19B, 0xB772, 0xD19C, 0xB773, 0xD19D, 0xB774, 0xD19E, 0xB775, 0xD19F, 0xB776, 0xD1A2, 0xB777, 0xD1A3, 0xB778, 0xD1A5, + 0xB779, 0xD1A6, 0xB77A, 0xD1A7, 0xB781, 0xD1A9, 0xB782, 0xD1AA, 0xB783, 0xD1AB, 0xB784, 0xD1AC, 0xB785, 0xD1AD, 0xB786, 0xD1AE, + 0xB787, 0xD1AF, 0xB788, 0xD1B2, 0xB789, 0xD1B4, 0xB78A, 0xD1B6, 0xB78B, 0xD1B7, 0xB78C, 0xD1B8, 0xB78D, 0xD1B9, 0xB78E, 0xD1BB, + 0xB78F, 0xD1BD, 0xB790, 0xD1BE, 0xB791, 0xD1BF, 0xB792, 0xD1C1, 0xB793, 0xD1C2, 0xB794, 0xD1C3, 0xB795, 0xD1C4, 0xB796, 0xD1C5, + 0xB797, 0xD1C6, 0xB798, 0xD1C7, 0xB799, 0xD1C8, 0xB79A, 0xD1C9, 0xB79B, 0xD1CA, 0xB79C, 0xD1CB, 0xB79D, 0xD1CC, 0xB79E, 0xD1CD, + 0xB79F, 0xD1CE, 0xB7A0, 0xD1CF, 0xB7A1, 0xB798, 0xB7A2, 0xB799, 0xB7A3, 0xB79C, 0xB7A4, 0xB7A0, 0xB7A5, 0xB7A8, 0xB7A6, 0xB7A9, + 0xB7A7, 0xB7AB, 0xB7A8, 0xB7AC, 0xB7A9, 0xB7AD, 0xB7AA, 0xB7B4, 0xB7AB, 0xB7B5, 0xB7AC, 0xB7B8, 0xB7AD, 0xB7C7, 0xB7AE, 0xB7C9, + 0xB7AF, 0xB7EC, 0xB7B0, 0xB7ED, 0xB7B1, 0xB7F0, 0xB7B2, 0xB7F4, 0xB7B3, 0xB7FC, 0xB7B4, 0xB7FD, 0xB7B5, 0xB7FF, 0xB7B6, 0xB800, + 0xB7B7, 0xB801, 0xB7B8, 0xB807, 0xB7B9, 0xB808, 0xB7BA, 0xB809, 0xB7BB, 0xB80C, 0xB7BC, 0xB810, 0xB7BD, 0xB818, 0xB7BE, 0xB819, + 0xB7BF, 0xB81B, 0xB7C0, 0xB81D, 0xB7C1, 0xB824, 0xB7C2, 0xB825, 0xB7C3, 0xB828, 0xB7C4, 0xB82C, 0xB7C5, 0xB834, 0xB7C6, 0xB835, + 0xB7C7, 0xB837, 0xB7C8, 0xB838, 0xB7C9, 0xB839, 0xB7CA, 0xB840, 0xB7CB, 0xB844, 0xB7CC, 0xB851, 0xB7CD, 0xB853, 0xB7CE, 0xB85C, + 0xB7CF, 0xB85D, 0xB7D0, 0xB860, 0xB7D1, 0xB864, 0xB7D2, 0xB86C, 0xB7D3, 0xB86D, 0xB7D4, 0xB86F, 0xB7D5, 0xB871, 0xB7D6, 0xB878, + 0xB7D7, 0xB87C, 0xB7D8, 0xB88D, 0xB7D9, 0xB8A8, 0xB7DA, 0xB8B0, 0xB7DB, 0xB8B4, 0xB7DC, 0xB8B8, 0xB7DD, 0xB8C0, 0xB7DE, 0xB8C1, + 0xB7DF, 0xB8C3, 0xB7E0, 0xB8C5, 0xB7E1, 0xB8CC, 0xB7E2, 0xB8D0, 0xB7E3, 0xB8D4, 0xB7E4, 0xB8DD, 0xB7E5, 0xB8DF, 0xB7E6, 0xB8E1, + 0xB7E7, 0xB8E8, 0xB7E8, 0xB8E9, 0xB7E9, 0xB8EC, 0xB7EA, 0xB8F0, 0xB7EB, 0xB8F8, 0xB7EC, 0xB8F9, 0xB7ED, 0xB8FB, 0xB7EE, 0xB8FD, + 0xB7EF, 0xB904, 0xB7F0, 0xB918, 0xB7F1, 0xB920, 0xB7F2, 0xB93C, 0xB7F3, 0xB93D, 0xB7F4, 0xB940, 0xB7F5, 0xB944, 0xB7F6, 0xB94C, + 0xB7F7, 0xB94F, 0xB7F8, 0xB951, 0xB7F9, 0xB958, 0xB7FA, 0xB959, 0xB7FB, 0xB95C, 0xB7FC, 0xB960, 0xB7FD, 0xB968, 0xB7FE, 0xB969, + 0xB841, 0xD1D0, 0xB842, 0xD1D1, 0xB843, 0xD1D2, 0xB844, 0xD1D3, 0xB845, 0xD1D4, 0xB846, 0xD1D5, 0xB847, 0xD1D6, 0xB848, 0xD1D7, + 0xB849, 0xD1D9, 0xB84A, 0xD1DA, 0xB84B, 0xD1DB, 0xB84C, 0xD1DC, 0xB84D, 0xD1DD, 0xB84E, 0xD1DE, 0xB84F, 0xD1DF, 0xB850, 0xD1E0, + 0xB851, 0xD1E1, 0xB852, 0xD1E2, 0xB853, 0xD1E3, 0xB854, 0xD1E4, 0xB855, 0xD1E5, 0xB856, 0xD1E6, 0xB857, 0xD1E7, 0xB858, 0xD1E8, + 0xB859, 0xD1E9, 0xB85A, 0xD1EA, 0xB861, 0xD1EB, 0xB862, 0xD1EC, 0xB863, 0xD1ED, 0xB864, 0xD1EE, 0xB865, 0xD1EF, 0xB866, 0xD1F0, + 0xB867, 0xD1F1, 0xB868, 0xD1F2, 0xB869, 0xD1F3, 0xB86A, 0xD1F5, 0xB86B, 0xD1F6, 0xB86C, 0xD1F7, 0xB86D, 0xD1F9, 0xB86E, 0xD1FA, + 0xB86F, 0xD1FB, 0xB870, 0xD1FC, 0xB871, 0xD1FD, 0xB872, 0xD1FE, 0xB873, 0xD1FF, 0xB874, 0xD200, 0xB875, 0xD201, 0xB876, 0xD202, + 0xB877, 0xD203, 0xB878, 0xD204, 0xB879, 0xD205, 0xB87A, 0xD206, 0xB881, 0xD208, 0xB882, 0xD20A, 0xB883, 0xD20B, 0xB884, 0xD20C, + 0xB885, 0xD20D, 0xB886, 0xD20E, 0xB887, 0xD20F, 0xB888, 0xD211, 0xB889, 0xD212, 0xB88A, 0xD213, 0xB88B, 0xD214, 0xB88C, 0xD215, + 0xB88D, 0xD216, 0xB88E, 0xD217, 0xB88F, 0xD218, 0xB890, 0xD219, 0xB891, 0xD21A, 0xB892, 0xD21B, 0xB893, 0xD21C, 0xB894, 0xD21D, + 0xB895, 0xD21E, 0xB896, 0xD21F, 0xB897, 0xD220, 0xB898, 0xD221, 0xB899, 0xD222, 0xB89A, 0xD223, 0xB89B, 0xD224, 0xB89C, 0xD225, + 0xB89D, 0xD226, 0xB89E, 0xD227, 0xB89F, 0xD228, 0xB8A0, 0xD229, 0xB8A1, 0xB96B, 0xB8A2, 0xB96D, 0xB8A3, 0xB974, 0xB8A4, 0xB975, + 0xB8A5, 0xB978, 0xB8A6, 0xB97C, 0xB8A7, 0xB984, 0xB8A8, 0xB985, 0xB8A9, 0xB987, 0xB8AA, 0xB989, 0xB8AB, 0xB98A, 0xB8AC, 0xB98D, + 0xB8AD, 0xB98E, 0xB8AE, 0xB9AC, 0xB8AF, 0xB9AD, 0xB8B0, 0xB9B0, 0xB8B1, 0xB9B4, 0xB8B2, 0xB9BC, 0xB8B3, 0xB9BD, 0xB8B4, 0xB9BF, + 0xB8B5, 0xB9C1, 0xB8B6, 0xB9C8, 0xB8B7, 0xB9C9, 0xB8B8, 0xB9CC, 0xB8B9, 0xB9CE, 0xB8BA, 0xB9CF, 0xB8BB, 0xB9D0, 0xB8BC, 0xB9D1, + 0xB8BD, 0xB9D2, 0xB8BE, 0xB9D8, 0xB8BF, 0xB9D9, 0xB8C0, 0xB9DB, 0xB8C1, 0xB9DD, 0xB8C2, 0xB9DE, 0xB8C3, 0xB9E1, 0xB8C4, 0xB9E3, + 0xB8C5, 0xB9E4, 0xB8C6, 0xB9E5, 0xB8C7, 0xB9E8, 0xB8C8, 0xB9EC, 0xB8C9, 0xB9F4, 0xB8CA, 0xB9F5, 0xB8CB, 0xB9F7, 0xB8CC, 0xB9F8, + 0xB8CD, 0xB9F9, 0xB8CE, 0xB9FA, 0xB8CF, 0xBA00, 0xB8D0, 0xBA01, 0xB8D1, 0xBA08, 0xB8D2, 0xBA15, 0xB8D3, 0xBA38, 0xB8D4, 0xBA39, + 0xB8D5, 0xBA3C, 0xB8D6, 0xBA40, 0xB8D7, 0xBA42, 0xB8D8, 0xBA48, 0xB8D9, 0xBA49, 0xB8DA, 0xBA4B, 0xB8DB, 0xBA4D, 0xB8DC, 0xBA4E, + 0xB8DD, 0xBA53, 0xB8DE, 0xBA54, 0xB8DF, 0xBA55, 0xB8E0, 0xBA58, 0xB8E1, 0xBA5C, 0xB8E2, 0xBA64, 0xB8E3, 0xBA65, 0xB8E4, 0xBA67, + 0xB8E5, 0xBA68, 0xB8E6, 0xBA69, 0xB8E7, 0xBA70, 0xB8E8, 0xBA71, 0xB8E9, 0xBA74, 0xB8EA, 0xBA78, 0xB8EB, 0xBA83, 0xB8EC, 0xBA84, + 0xB8ED, 0xBA85, 0xB8EE, 0xBA87, 0xB8EF, 0xBA8C, 0xB8F0, 0xBAA8, 0xB8F1, 0xBAA9, 0xB8F2, 0xBAAB, 0xB8F3, 0xBAAC, 0xB8F4, 0xBAB0, + 0xB8F5, 0xBAB2, 0xB8F6, 0xBAB8, 0xB8F7, 0xBAB9, 0xB8F8, 0xBABB, 0xB8F9, 0xBABD, 0xB8FA, 0xBAC4, 0xB8FB, 0xBAC8, 0xB8FC, 0xBAD8, + 0xB8FD, 0xBAD9, 0xB8FE, 0xBAFC, 0xB941, 0xD22A, 0xB942, 0xD22B, 0xB943, 0xD22E, 0xB944, 0xD22F, 0xB945, 0xD231, 0xB946, 0xD232, + 0xB947, 0xD233, 0xB948, 0xD235, 0xB949, 0xD236, 0xB94A, 0xD237, 0xB94B, 0xD238, 0xB94C, 0xD239, 0xB94D, 0xD23A, 0xB94E, 0xD23B, + 0xB94F, 0xD23E, 0xB950, 0xD240, 0xB951, 0xD242, 0xB952, 0xD243, 0xB953, 0xD244, 0xB954, 0xD245, 0xB955, 0xD246, 0xB956, 0xD247, + 0xB957, 0xD249, 0xB958, 0xD24A, 0xB959, 0xD24B, 0xB95A, 0xD24C, 0xB961, 0xD24D, 0xB962, 0xD24E, 0xB963, 0xD24F, 0xB964, 0xD250, + 0xB965, 0xD251, 0xB966, 0xD252, 0xB967, 0xD253, 0xB968, 0xD254, 0xB969, 0xD255, 0xB96A, 0xD256, 0xB96B, 0xD257, 0xB96C, 0xD258, + 0xB96D, 0xD259, 0xB96E, 0xD25A, 0xB96F, 0xD25B, 0xB970, 0xD25D, 0xB971, 0xD25E, 0xB972, 0xD25F, 0xB973, 0xD260, 0xB974, 0xD261, + 0xB975, 0xD262, 0xB976, 0xD263, 0xB977, 0xD265, 0xB978, 0xD266, 0xB979, 0xD267, 0xB97A, 0xD268, 0xB981, 0xD269, 0xB982, 0xD26A, + 0xB983, 0xD26B, 0xB984, 0xD26C, 0xB985, 0xD26D, 0xB986, 0xD26E, 0xB987, 0xD26F, 0xB988, 0xD270, 0xB989, 0xD271, 0xB98A, 0xD272, + 0xB98B, 0xD273, 0xB98C, 0xD274, 0xB98D, 0xD275, 0xB98E, 0xD276, 0xB98F, 0xD277, 0xB990, 0xD278, 0xB991, 0xD279, 0xB992, 0xD27A, + 0xB993, 0xD27B, 0xB994, 0xD27C, 0xB995, 0xD27D, 0xB996, 0xD27E, 0xB997, 0xD27F, 0xB998, 0xD282, 0xB999, 0xD283, 0xB99A, 0xD285, + 0xB99B, 0xD286, 0xB99C, 0xD287, 0xB99D, 0xD289, 0xB99E, 0xD28A, 0xB99F, 0xD28B, 0xB9A0, 0xD28C, 0xB9A1, 0xBB00, 0xB9A2, 0xBB04, + 0xB9A3, 0xBB0D, 0xB9A4, 0xBB0F, 0xB9A5, 0xBB11, 0xB9A6, 0xBB18, 0xB9A7, 0xBB1C, 0xB9A8, 0xBB20, 0xB9A9, 0xBB29, 0xB9AA, 0xBB2B, + 0xB9AB, 0xBB34, 0xB9AC, 0xBB35, 0xB9AD, 0xBB36, 0xB9AE, 0xBB38, 0xB9AF, 0xBB3B, 0xB9B0, 0xBB3C, 0xB9B1, 0xBB3D, 0xB9B2, 0xBB3E, + 0xB9B3, 0xBB44, 0xB9B4, 0xBB45, 0xB9B5, 0xBB47, 0xB9B6, 0xBB49, 0xB9B7, 0xBB4D, 0xB9B8, 0xBB4F, 0xB9B9, 0xBB50, 0xB9BA, 0xBB54, + 0xB9BB, 0xBB58, 0xB9BC, 0xBB61, 0xB9BD, 0xBB63, 0xB9BE, 0xBB6C, 0xB9BF, 0xBB88, 0xB9C0, 0xBB8C, 0xB9C1, 0xBB90, 0xB9C2, 0xBBA4, + 0xB9C3, 0xBBA8, 0xB9C4, 0xBBAC, 0xB9C5, 0xBBB4, 0xB9C6, 0xBBB7, 0xB9C7, 0xBBC0, 0xB9C8, 0xBBC4, 0xB9C9, 0xBBC8, 0xB9CA, 0xBBD0, + 0xB9CB, 0xBBD3, 0xB9CC, 0xBBF8, 0xB9CD, 0xBBF9, 0xB9CE, 0xBBFC, 0xB9CF, 0xBBFF, 0xB9D0, 0xBC00, 0xB9D1, 0xBC02, 0xB9D2, 0xBC08, + 0xB9D3, 0xBC09, 0xB9D4, 0xBC0B, 0xB9D5, 0xBC0C, 0xB9D6, 0xBC0D, 0xB9D7, 0xBC0F, 0xB9D8, 0xBC11, 0xB9D9, 0xBC14, 0xB9DA, 0xBC15, + 0xB9DB, 0xBC16, 0xB9DC, 0xBC17, 0xB9DD, 0xBC18, 0xB9DE, 0xBC1B, 0xB9DF, 0xBC1C, 0xB9E0, 0xBC1D, 0xB9E1, 0xBC1E, 0xB9E2, 0xBC1F, + 0xB9E3, 0xBC24, 0xB9E4, 0xBC25, 0xB9E5, 0xBC27, 0xB9E6, 0xBC29, 0xB9E7, 0xBC2D, 0xB9E8, 0xBC30, 0xB9E9, 0xBC31, 0xB9EA, 0xBC34, + 0xB9EB, 0xBC38, 0xB9EC, 0xBC40, 0xB9ED, 0xBC41, 0xB9EE, 0xBC43, 0xB9EF, 0xBC44, 0xB9F0, 0xBC45, 0xB9F1, 0xBC49, 0xB9F2, 0xBC4C, + 0xB9F3, 0xBC4D, 0xB9F4, 0xBC50, 0xB9F5, 0xBC5D, 0xB9F6, 0xBC84, 0xB9F7, 0xBC85, 0xB9F8, 0xBC88, 0xB9F9, 0xBC8B, 0xB9FA, 0xBC8C, + 0xB9FB, 0xBC8E, 0xB9FC, 0xBC94, 0xB9FD, 0xBC95, 0xB9FE, 0xBC97, 0xBA41, 0xD28D, 0xBA42, 0xD28E, 0xBA43, 0xD28F, 0xBA44, 0xD292, + 0xBA45, 0xD293, 0xBA46, 0xD294, 0xBA47, 0xD296, 0xBA48, 0xD297, 0xBA49, 0xD298, 0xBA4A, 0xD299, 0xBA4B, 0xD29A, 0xBA4C, 0xD29B, + 0xBA4D, 0xD29D, 0xBA4E, 0xD29E, 0xBA4F, 0xD29F, 0xBA50, 0xD2A1, 0xBA51, 0xD2A2, 0xBA52, 0xD2A3, 0xBA53, 0xD2A5, 0xBA54, 0xD2A6, + 0xBA55, 0xD2A7, 0xBA56, 0xD2A8, 0xBA57, 0xD2A9, 0xBA58, 0xD2AA, 0xBA59, 0xD2AB, 0xBA5A, 0xD2AD, 0xBA61, 0xD2AE, 0xBA62, 0xD2AF, + 0xBA63, 0xD2B0, 0xBA64, 0xD2B2, 0xBA65, 0xD2B3, 0xBA66, 0xD2B4, 0xBA67, 0xD2B5, 0xBA68, 0xD2B6, 0xBA69, 0xD2B7, 0xBA6A, 0xD2BA, + 0xBA6B, 0xD2BB, 0xBA6C, 0xD2BD, 0xBA6D, 0xD2BE, 0xBA6E, 0xD2C1, 0xBA6F, 0xD2C3, 0xBA70, 0xD2C4, 0xBA71, 0xD2C5, 0xBA72, 0xD2C6, + 0xBA73, 0xD2C7, 0xBA74, 0xD2CA, 0xBA75, 0xD2CC, 0xBA76, 0xD2CD, 0xBA77, 0xD2CE, 0xBA78, 0xD2CF, 0xBA79, 0xD2D0, 0xBA7A, 0xD2D1, + 0xBA81, 0xD2D2, 0xBA82, 0xD2D3, 0xBA83, 0xD2D5, 0xBA84, 0xD2D6, 0xBA85, 0xD2D7, 0xBA86, 0xD2D9, 0xBA87, 0xD2DA, 0xBA88, 0xD2DB, + 0xBA89, 0xD2DD, 0xBA8A, 0xD2DE, 0xBA8B, 0xD2DF, 0xBA8C, 0xD2E0, 0xBA8D, 0xD2E1, 0xBA8E, 0xD2E2, 0xBA8F, 0xD2E3, 0xBA90, 0xD2E6, + 0xBA91, 0xD2E7, 0xBA92, 0xD2E8, 0xBA93, 0xD2E9, 0xBA94, 0xD2EA, 0xBA95, 0xD2EB, 0xBA96, 0xD2EC, 0xBA97, 0xD2ED, 0xBA98, 0xD2EE, + 0xBA99, 0xD2EF, 0xBA9A, 0xD2F2, 0xBA9B, 0xD2F3, 0xBA9C, 0xD2F5, 0xBA9D, 0xD2F6, 0xBA9E, 0xD2F7, 0xBA9F, 0xD2F9, 0xBAA0, 0xD2FA, + 0xBAA1, 0xBC99, 0xBAA2, 0xBC9A, 0xBAA3, 0xBCA0, 0xBAA4, 0xBCA1, 0xBAA5, 0xBCA4, 0xBAA6, 0xBCA7, 0xBAA7, 0xBCA8, 0xBAA8, 0xBCB0, + 0xBAA9, 0xBCB1, 0xBAAA, 0xBCB3, 0xBAAB, 0xBCB4, 0xBAAC, 0xBCB5, 0xBAAD, 0xBCBC, 0xBAAE, 0xBCBD, 0xBAAF, 0xBCC0, 0xBAB0, 0xBCC4, + 0xBAB1, 0xBCCD, 0xBAB2, 0xBCCF, 0xBAB3, 0xBCD0, 0xBAB4, 0xBCD1, 0xBAB5, 0xBCD5, 0xBAB6, 0xBCD8, 0xBAB7, 0xBCDC, 0xBAB8, 0xBCF4, + 0xBAB9, 0xBCF5, 0xBABA, 0xBCF6, 0xBABB, 0xBCF8, 0xBABC, 0xBCFC, 0xBABD, 0xBD04, 0xBABE, 0xBD05, 0xBABF, 0xBD07, 0xBAC0, 0xBD09, + 0xBAC1, 0xBD10, 0xBAC2, 0xBD14, 0xBAC3, 0xBD24, 0xBAC4, 0xBD2C, 0xBAC5, 0xBD40, 0xBAC6, 0xBD48, 0xBAC7, 0xBD49, 0xBAC8, 0xBD4C, + 0xBAC9, 0xBD50, 0xBACA, 0xBD58, 0xBACB, 0xBD59, 0xBACC, 0xBD64, 0xBACD, 0xBD68, 0xBACE, 0xBD80, 0xBACF, 0xBD81, 0xBAD0, 0xBD84, + 0xBAD1, 0xBD87, 0xBAD2, 0xBD88, 0xBAD3, 0xBD89, 0xBAD4, 0xBD8A, 0xBAD5, 0xBD90, 0xBAD6, 0xBD91, 0xBAD7, 0xBD93, 0xBAD8, 0xBD95, + 0xBAD9, 0xBD99, 0xBADA, 0xBD9A, 0xBADB, 0xBD9C, 0xBADC, 0xBDA4, 0xBADD, 0xBDB0, 0xBADE, 0xBDB8, 0xBADF, 0xBDD4, 0xBAE0, 0xBDD5, + 0xBAE1, 0xBDD8, 0xBAE2, 0xBDDC, 0xBAE3, 0xBDE9, 0xBAE4, 0xBDF0, 0xBAE5, 0xBDF4, 0xBAE6, 0xBDF8, 0xBAE7, 0xBE00, 0xBAE8, 0xBE03, + 0xBAE9, 0xBE05, 0xBAEA, 0xBE0C, 0xBAEB, 0xBE0D, 0xBAEC, 0xBE10, 0xBAED, 0xBE14, 0xBAEE, 0xBE1C, 0xBAEF, 0xBE1D, 0xBAF0, 0xBE1F, + 0xBAF1, 0xBE44, 0xBAF2, 0xBE45, 0xBAF3, 0xBE48, 0xBAF4, 0xBE4C, 0xBAF5, 0xBE4E, 0xBAF6, 0xBE54, 0xBAF7, 0xBE55, 0xBAF8, 0xBE57, + 0xBAF9, 0xBE59, 0xBAFA, 0xBE5A, 0xBAFB, 0xBE5B, 0xBAFC, 0xBE60, 0xBAFD, 0xBE61, 0xBAFE, 0xBE64, 0xBB41, 0xD2FB, 0xBB42, 0xD2FC, + 0xBB43, 0xD2FD, 0xBB44, 0xD2FE, 0xBB45, 0xD2FF, 0xBB46, 0xD302, 0xBB47, 0xD304, 0xBB48, 0xD306, 0xBB49, 0xD307, 0xBB4A, 0xD308, + 0xBB4B, 0xD309, 0xBB4C, 0xD30A, 0xBB4D, 0xD30B, 0xBB4E, 0xD30F, 0xBB4F, 0xD311, 0xBB50, 0xD312, 0xBB51, 0xD313, 0xBB52, 0xD315, + 0xBB53, 0xD317, 0xBB54, 0xD318, 0xBB55, 0xD319, 0xBB56, 0xD31A, 0xBB57, 0xD31B, 0xBB58, 0xD31E, 0xBB59, 0xD322, 0xBB5A, 0xD323, + 0xBB61, 0xD324, 0xBB62, 0xD326, 0xBB63, 0xD327, 0xBB64, 0xD32A, 0xBB65, 0xD32B, 0xBB66, 0xD32D, 0xBB67, 0xD32E, 0xBB68, 0xD32F, + 0xBB69, 0xD331, 0xBB6A, 0xD332, 0xBB6B, 0xD333, 0xBB6C, 0xD334, 0xBB6D, 0xD335, 0xBB6E, 0xD336, 0xBB6F, 0xD337, 0xBB70, 0xD33A, + 0xBB71, 0xD33E, 0xBB72, 0xD33F, 0xBB73, 0xD340, 0xBB74, 0xD341, 0xBB75, 0xD342, 0xBB76, 0xD343, 0xBB77, 0xD346, 0xBB78, 0xD347, + 0xBB79, 0xD348, 0xBB7A, 0xD349, 0xBB81, 0xD34A, 0xBB82, 0xD34B, 0xBB83, 0xD34C, 0xBB84, 0xD34D, 0xBB85, 0xD34E, 0xBB86, 0xD34F, + 0xBB87, 0xD350, 0xBB88, 0xD351, 0xBB89, 0xD352, 0xBB8A, 0xD353, 0xBB8B, 0xD354, 0xBB8C, 0xD355, 0xBB8D, 0xD356, 0xBB8E, 0xD357, + 0xBB8F, 0xD358, 0xBB90, 0xD359, 0xBB91, 0xD35A, 0xBB92, 0xD35B, 0xBB93, 0xD35C, 0xBB94, 0xD35D, 0xBB95, 0xD35E, 0xBB96, 0xD35F, + 0xBB97, 0xD360, 0xBB98, 0xD361, 0xBB99, 0xD362, 0xBB9A, 0xD363, 0xBB9B, 0xD364, 0xBB9C, 0xD365, 0xBB9D, 0xD366, 0xBB9E, 0xD367, + 0xBB9F, 0xD368, 0xBBA0, 0xD369, 0xBBA1, 0xBE68, 0xBBA2, 0xBE6A, 0xBBA3, 0xBE70, 0xBBA4, 0xBE71, 0xBBA5, 0xBE73, 0xBBA6, 0xBE74, + 0xBBA7, 0xBE75, 0xBBA8, 0xBE7B, 0xBBA9, 0xBE7C, 0xBBAA, 0xBE7D, 0xBBAB, 0xBE80, 0xBBAC, 0xBE84, 0xBBAD, 0xBE8C, 0xBBAE, 0xBE8D, + 0xBBAF, 0xBE8F, 0xBBB0, 0xBE90, 0xBBB1, 0xBE91, 0xBBB2, 0xBE98, 0xBBB3, 0xBE99, 0xBBB4, 0xBEA8, 0xBBB5, 0xBED0, 0xBBB6, 0xBED1, + 0xBBB7, 0xBED4, 0xBBB8, 0xBED7, 0xBBB9, 0xBED8, 0xBBBA, 0xBEE0, 0xBBBB, 0xBEE3, 0xBBBC, 0xBEE4, 0xBBBD, 0xBEE5, 0xBBBE, 0xBEEC, + 0xBBBF, 0xBF01, 0xBBC0, 0xBF08, 0xBBC1, 0xBF09, 0xBBC2, 0xBF18, 0xBBC3, 0xBF19, 0xBBC4, 0xBF1B, 0xBBC5, 0xBF1C, 0xBBC6, 0xBF1D, + 0xBBC7, 0xBF40, 0xBBC8, 0xBF41, 0xBBC9, 0xBF44, 0xBBCA, 0xBF48, 0xBBCB, 0xBF50, 0xBBCC, 0xBF51, 0xBBCD, 0xBF55, 0xBBCE, 0xBF94, + 0xBBCF, 0xBFB0, 0xBBD0, 0xBFC5, 0xBBD1, 0xBFCC, 0xBBD2, 0xBFCD, 0xBBD3, 0xBFD0, 0xBBD4, 0xBFD4, 0xBBD5, 0xBFDC, 0xBBD6, 0xBFDF, + 0xBBD7, 0xBFE1, 0xBBD8, 0xC03C, 0xBBD9, 0xC051, 0xBBDA, 0xC058, 0xBBDB, 0xC05C, 0xBBDC, 0xC060, 0xBBDD, 0xC068, 0xBBDE, 0xC069, + 0xBBDF, 0xC090, 0xBBE0, 0xC091, 0xBBE1, 0xC094, 0xBBE2, 0xC098, 0xBBE3, 0xC0A0, 0xBBE4, 0xC0A1, 0xBBE5, 0xC0A3, 0xBBE6, 0xC0A5, + 0xBBE7, 0xC0AC, 0xBBE8, 0xC0AD, 0xBBE9, 0xC0AF, 0xBBEA, 0xC0B0, 0xBBEB, 0xC0B3, 0xBBEC, 0xC0B4, 0xBBED, 0xC0B5, 0xBBEE, 0xC0B6, + 0xBBEF, 0xC0BC, 0xBBF0, 0xC0BD, 0xBBF1, 0xC0BF, 0xBBF2, 0xC0C0, 0xBBF3, 0xC0C1, 0xBBF4, 0xC0C5, 0xBBF5, 0xC0C8, 0xBBF6, 0xC0C9, + 0xBBF7, 0xC0CC, 0xBBF8, 0xC0D0, 0xBBF9, 0xC0D8, 0xBBFA, 0xC0D9, 0xBBFB, 0xC0DB, 0xBBFC, 0xC0DC, 0xBBFD, 0xC0DD, 0xBBFE, 0xC0E4, + 0xBC41, 0xD36A, 0xBC42, 0xD36B, 0xBC43, 0xD36C, 0xBC44, 0xD36D, 0xBC45, 0xD36E, 0xBC46, 0xD36F, 0xBC47, 0xD370, 0xBC48, 0xD371, + 0xBC49, 0xD372, 0xBC4A, 0xD373, 0xBC4B, 0xD374, 0xBC4C, 0xD375, 0xBC4D, 0xD376, 0xBC4E, 0xD377, 0xBC4F, 0xD378, 0xBC50, 0xD379, + 0xBC51, 0xD37A, 0xBC52, 0xD37B, 0xBC53, 0xD37E, 0xBC54, 0xD37F, 0xBC55, 0xD381, 0xBC56, 0xD382, 0xBC57, 0xD383, 0xBC58, 0xD385, + 0xBC59, 0xD386, 0xBC5A, 0xD387, 0xBC61, 0xD388, 0xBC62, 0xD389, 0xBC63, 0xD38A, 0xBC64, 0xD38B, 0xBC65, 0xD38E, 0xBC66, 0xD392, + 0xBC67, 0xD393, 0xBC68, 0xD394, 0xBC69, 0xD395, 0xBC6A, 0xD396, 0xBC6B, 0xD397, 0xBC6C, 0xD39A, 0xBC6D, 0xD39B, 0xBC6E, 0xD39D, + 0xBC6F, 0xD39E, 0xBC70, 0xD39F, 0xBC71, 0xD3A1, 0xBC72, 0xD3A2, 0xBC73, 0xD3A3, 0xBC74, 0xD3A4, 0xBC75, 0xD3A5, 0xBC76, 0xD3A6, + 0xBC77, 0xD3A7, 0xBC78, 0xD3AA, 0xBC79, 0xD3AC, 0xBC7A, 0xD3AE, 0xBC81, 0xD3AF, 0xBC82, 0xD3B0, 0xBC83, 0xD3B1, 0xBC84, 0xD3B2, + 0xBC85, 0xD3B3, 0xBC86, 0xD3B5, 0xBC87, 0xD3B6, 0xBC88, 0xD3B7, 0xBC89, 0xD3B9, 0xBC8A, 0xD3BA, 0xBC8B, 0xD3BB, 0xBC8C, 0xD3BD, + 0xBC8D, 0xD3BE, 0xBC8E, 0xD3BF, 0xBC8F, 0xD3C0, 0xBC90, 0xD3C1, 0xBC91, 0xD3C2, 0xBC92, 0xD3C3, 0xBC93, 0xD3C6, 0xBC94, 0xD3C7, + 0xBC95, 0xD3CA, 0xBC96, 0xD3CB, 0xBC97, 0xD3CC, 0xBC98, 0xD3CD, 0xBC99, 0xD3CE, 0xBC9A, 0xD3CF, 0xBC9B, 0xD3D1, 0xBC9C, 0xD3D2, + 0xBC9D, 0xD3D3, 0xBC9E, 0xD3D4, 0xBC9F, 0xD3D5, 0xBCA0, 0xD3D6, 0xBCA1, 0xC0E5, 0xBCA2, 0xC0E8, 0xBCA3, 0xC0EC, 0xBCA4, 0xC0F4, + 0xBCA5, 0xC0F5, 0xBCA6, 0xC0F7, 0xBCA7, 0xC0F9, 0xBCA8, 0xC100, 0xBCA9, 0xC104, 0xBCAA, 0xC108, 0xBCAB, 0xC110, 0xBCAC, 0xC115, + 0xBCAD, 0xC11C, 0xBCAE, 0xC11D, 0xBCAF, 0xC11E, 0xBCB0, 0xC11F, 0xBCB1, 0xC120, 0xBCB2, 0xC123, 0xBCB3, 0xC124, 0xBCB4, 0xC126, + 0xBCB5, 0xC127, 0xBCB6, 0xC12C, 0xBCB7, 0xC12D, 0xBCB8, 0xC12F, 0xBCB9, 0xC130, 0xBCBA, 0xC131, 0xBCBB, 0xC136, 0xBCBC, 0xC138, + 0xBCBD, 0xC139, 0xBCBE, 0xC13C, 0xBCBF, 0xC140, 0xBCC0, 0xC148, 0xBCC1, 0xC149, 0xBCC2, 0xC14B, 0xBCC3, 0xC14C, 0xBCC4, 0xC14D, + 0xBCC5, 0xC154, 0xBCC6, 0xC155, 0xBCC7, 0xC158, 0xBCC8, 0xC15C, 0xBCC9, 0xC164, 0xBCCA, 0xC165, 0xBCCB, 0xC167, 0xBCCC, 0xC168, + 0xBCCD, 0xC169, 0xBCCE, 0xC170, 0xBCCF, 0xC174, 0xBCD0, 0xC178, 0xBCD1, 0xC185, 0xBCD2, 0xC18C, 0xBCD3, 0xC18D, 0xBCD4, 0xC18E, + 0xBCD5, 0xC190, 0xBCD6, 0xC194, 0xBCD7, 0xC196, 0xBCD8, 0xC19C, 0xBCD9, 0xC19D, 0xBCDA, 0xC19F, 0xBCDB, 0xC1A1, 0xBCDC, 0xC1A5, + 0xBCDD, 0xC1A8, 0xBCDE, 0xC1A9, 0xBCDF, 0xC1AC, 0xBCE0, 0xC1B0, 0xBCE1, 0xC1BD, 0xBCE2, 0xC1C4, 0xBCE3, 0xC1C8, 0xBCE4, 0xC1CC, + 0xBCE5, 0xC1D4, 0xBCE6, 0xC1D7, 0xBCE7, 0xC1D8, 0xBCE8, 0xC1E0, 0xBCE9, 0xC1E4, 0xBCEA, 0xC1E8, 0xBCEB, 0xC1F0, 0xBCEC, 0xC1F1, + 0xBCED, 0xC1F3, 0xBCEE, 0xC1FC, 0xBCEF, 0xC1FD, 0xBCF0, 0xC200, 0xBCF1, 0xC204, 0xBCF2, 0xC20C, 0xBCF3, 0xC20D, 0xBCF4, 0xC20F, + 0xBCF5, 0xC211, 0xBCF6, 0xC218, 0xBCF7, 0xC219, 0xBCF8, 0xC21C, 0xBCF9, 0xC21F, 0xBCFA, 0xC220, 0xBCFB, 0xC228, 0xBCFC, 0xC229, + 0xBCFD, 0xC22B, 0xBCFE, 0xC22D, 0xBD41, 0xD3D7, 0xBD42, 0xD3D9, 0xBD43, 0xD3DA, 0xBD44, 0xD3DB, 0xBD45, 0xD3DC, 0xBD46, 0xD3DD, + 0xBD47, 0xD3DE, 0xBD48, 0xD3DF, 0xBD49, 0xD3E0, 0xBD4A, 0xD3E2, 0xBD4B, 0xD3E4, 0xBD4C, 0xD3E5, 0xBD4D, 0xD3E6, 0xBD4E, 0xD3E7, + 0xBD4F, 0xD3E8, 0xBD50, 0xD3E9, 0xBD51, 0xD3EA, 0xBD52, 0xD3EB, 0xBD53, 0xD3EE, 0xBD54, 0xD3EF, 0xBD55, 0xD3F1, 0xBD56, 0xD3F2, + 0xBD57, 0xD3F3, 0xBD58, 0xD3F5, 0xBD59, 0xD3F6, 0xBD5A, 0xD3F7, 0xBD61, 0xD3F8, 0xBD62, 0xD3F9, 0xBD63, 0xD3FA, 0xBD64, 0xD3FB, + 0xBD65, 0xD3FE, 0xBD66, 0xD400, 0xBD67, 0xD402, 0xBD68, 0xD403, 0xBD69, 0xD404, 0xBD6A, 0xD405, 0xBD6B, 0xD406, 0xBD6C, 0xD407, + 0xBD6D, 0xD409, 0xBD6E, 0xD40A, 0xBD6F, 0xD40B, 0xBD70, 0xD40C, 0xBD71, 0xD40D, 0xBD72, 0xD40E, 0xBD73, 0xD40F, 0xBD74, 0xD410, + 0xBD75, 0xD411, 0xBD76, 0xD412, 0xBD77, 0xD413, 0xBD78, 0xD414, 0xBD79, 0xD415, 0xBD7A, 0xD416, 0xBD81, 0xD417, 0xBD82, 0xD418, + 0xBD83, 0xD419, 0xBD84, 0xD41A, 0xBD85, 0xD41B, 0xBD86, 0xD41C, 0xBD87, 0xD41E, 0xBD88, 0xD41F, 0xBD89, 0xD420, 0xBD8A, 0xD421, + 0xBD8B, 0xD422, 0xBD8C, 0xD423, 0xBD8D, 0xD424, 0xBD8E, 0xD425, 0xBD8F, 0xD426, 0xBD90, 0xD427, 0xBD91, 0xD428, 0xBD92, 0xD429, + 0xBD93, 0xD42A, 0xBD94, 0xD42B, 0xBD95, 0xD42C, 0xBD96, 0xD42D, 0xBD97, 0xD42E, 0xBD98, 0xD42F, 0xBD99, 0xD430, 0xBD9A, 0xD431, + 0xBD9B, 0xD432, 0xBD9C, 0xD433, 0xBD9D, 0xD434, 0xBD9E, 0xD435, 0xBD9F, 0xD436, 0xBDA0, 0xD437, 0xBDA1, 0xC22F, 0xBDA2, 0xC231, + 0xBDA3, 0xC232, 0xBDA4, 0xC234, 0xBDA5, 0xC248, 0xBDA6, 0xC250, 0xBDA7, 0xC251, 0xBDA8, 0xC254, 0xBDA9, 0xC258, 0xBDAA, 0xC260, + 0xBDAB, 0xC265, 0xBDAC, 0xC26C, 0xBDAD, 0xC26D, 0xBDAE, 0xC270, 0xBDAF, 0xC274, 0xBDB0, 0xC27C, 0xBDB1, 0xC27D, 0xBDB2, 0xC27F, + 0xBDB3, 0xC281, 0xBDB4, 0xC288, 0xBDB5, 0xC289, 0xBDB6, 0xC290, 0xBDB7, 0xC298, 0xBDB8, 0xC29B, 0xBDB9, 0xC29D, 0xBDBA, 0xC2A4, + 0xBDBB, 0xC2A5, 0xBDBC, 0xC2A8, 0xBDBD, 0xC2AC, 0xBDBE, 0xC2AD, 0xBDBF, 0xC2B4, 0xBDC0, 0xC2B5, 0xBDC1, 0xC2B7, 0xBDC2, 0xC2B9, + 0xBDC3, 0xC2DC, 0xBDC4, 0xC2DD, 0xBDC5, 0xC2E0, 0xBDC6, 0xC2E3, 0xBDC7, 0xC2E4, 0xBDC8, 0xC2EB, 0xBDC9, 0xC2EC, 0xBDCA, 0xC2ED, + 0xBDCB, 0xC2EF, 0xBDCC, 0xC2F1, 0xBDCD, 0xC2F6, 0xBDCE, 0xC2F8, 0xBDCF, 0xC2F9, 0xBDD0, 0xC2FB, 0xBDD1, 0xC2FC, 0xBDD2, 0xC300, + 0xBDD3, 0xC308, 0xBDD4, 0xC309, 0xBDD5, 0xC30C, 0xBDD6, 0xC30D, 0xBDD7, 0xC313, 0xBDD8, 0xC314, 0xBDD9, 0xC315, 0xBDDA, 0xC318, + 0xBDDB, 0xC31C, 0xBDDC, 0xC324, 0xBDDD, 0xC325, 0xBDDE, 0xC328, 0xBDDF, 0xC329, 0xBDE0, 0xC345, 0xBDE1, 0xC368, 0xBDE2, 0xC369, + 0xBDE3, 0xC36C, 0xBDE4, 0xC370, 0xBDE5, 0xC372, 0xBDE6, 0xC378, 0xBDE7, 0xC379, 0xBDE8, 0xC37C, 0xBDE9, 0xC37D, 0xBDEA, 0xC384, + 0xBDEB, 0xC388, 0xBDEC, 0xC38C, 0xBDED, 0xC3C0, 0xBDEE, 0xC3D8, 0xBDEF, 0xC3D9, 0xBDF0, 0xC3DC, 0xBDF1, 0xC3DF, 0xBDF2, 0xC3E0, + 0xBDF3, 0xC3E2, 0xBDF4, 0xC3E8, 0xBDF5, 0xC3E9, 0xBDF6, 0xC3ED, 0xBDF7, 0xC3F4, 0xBDF8, 0xC3F5, 0xBDF9, 0xC3F8, 0xBDFA, 0xC408, + 0xBDFB, 0xC410, 0xBDFC, 0xC424, 0xBDFD, 0xC42C, 0xBDFE, 0xC430, 0xBE41, 0xD438, 0xBE42, 0xD439, 0xBE43, 0xD43A, 0xBE44, 0xD43B, + 0xBE45, 0xD43C, 0xBE46, 0xD43D, 0xBE47, 0xD43E, 0xBE48, 0xD43F, 0xBE49, 0xD441, 0xBE4A, 0xD442, 0xBE4B, 0xD443, 0xBE4C, 0xD445, + 0xBE4D, 0xD446, 0xBE4E, 0xD447, 0xBE4F, 0xD448, 0xBE50, 0xD449, 0xBE51, 0xD44A, 0xBE52, 0xD44B, 0xBE53, 0xD44C, 0xBE54, 0xD44D, + 0xBE55, 0xD44E, 0xBE56, 0xD44F, 0xBE57, 0xD450, 0xBE58, 0xD451, 0xBE59, 0xD452, 0xBE5A, 0xD453, 0xBE61, 0xD454, 0xBE62, 0xD455, + 0xBE63, 0xD456, 0xBE64, 0xD457, 0xBE65, 0xD458, 0xBE66, 0xD459, 0xBE67, 0xD45A, 0xBE68, 0xD45B, 0xBE69, 0xD45D, 0xBE6A, 0xD45E, + 0xBE6B, 0xD45F, 0xBE6C, 0xD461, 0xBE6D, 0xD462, 0xBE6E, 0xD463, 0xBE6F, 0xD465, 0xBE70, 0xD466, 0xBE71, 0xD467, 0xBE72, 0xD468, + 0xBE73, 0xD469, 0xBE74, 0xD46A, 0xBE75, 0xD46B, 0xBE76, 0xD46C, 0xBE77, 0xD46E, 0xBE78, 0xD470, 0xBE79, 0xD471, 0xBE7A, 0xD472, + 0xBE81, 0xD473, 0xBE82, 0xD474, 0xBE83, 0xD475, 0xBE84, 0xD476, 0xBE85, 0xD477, 0xBE86, 0xD47A, 0xBE87, 0xD47B, 0xBE88, 0xD47D, + 0xBE89, 0xD47E, 0xBE8A, 0xD481, 0xBE8B, 0xD483, 0xBE8C, 0xD484, 0xBE8D, 0xD485, 0xBE8E, 0xD486, 0xBE8F, 0xD487, 0xBE90, 0xD48A, + 0xBE91, 0xD48C, 0xBE92, 0xD48E, 0xBE93, 0xD48F, 0xBE94, 0xD490, 0xBE95, 0xD491, 0xBE96, 0xD492, 0xBE97, 0xD493, 0xBE98, 0xD495, + 0xBE99, 0xD496, 0xBE9A, 0xD497, 0xBE9B, 0xD498, 0xBE9C, 0xD499, 0xBE9D, 0xD49A, 0xBE9E, 0xD49B, 0xBE9F, 0xD49C, 0xBEA0, 0xD49D, + 0xBEA1, 0xC434, 0xBEA2, 0xC43C, 0xBEA3, 0xC43D, 0xBEA4, 0xC448, 0xBEA5, 0xC464, 0xBEA6, 0xC465, 0xBEA7, 0xC468, 0xBEA8, 0xC46C, + 0xBEA9, 0xC474, 0xBEAA, 0xC475, 0xBEAB, 0xC479, 0xBEAC, 0xC480, 0xBEAD, 0xC494, 0xBEAE, 0xC49C, 0xBEAF, 0xC4B8, 0xBEB0, 0xC4BC, + 0xBEB1, 0xC4E9, 0xBEB2, 0xC4F0, 0xBEB3, 0xC4F1, 0xBEB4, 0xC4F4, 0xBEB5, 0xC4F8, 0xBEB6, 0xC4FA, 0xBEB7, 0xC4FF, 0xBEB8, 0xC500, + 0xBEB9, 0xC501, 0xBEBA, 0xC50C, 0xBEBB, 0xC510, 0xBEBC, 0xC514, 0xBEBD, 0xC51C, 0xBEBE, 0xC528, 0xBEBF, 0xC529, 0xBEC0, 0xC52C, + 0xBEC1, 0xC530, 0xBEC2, 0xC538, 0xBEC3, 0xC539, 0xBEC4, 0xC53B, 0xBEC5, 0xC53D, 0xBEC6, 0xC544, 0xBEC7, 0xC545, 0xBEC8, 0xC548, + 0xBEC9, 0xC549, 0xBECA, 0xC54A, 0xBECB, 0xC54C, 0xBECC, 0xC54D, 0xBECD, 0xC54E, 0xBECE, 0xC553, 0xBECF, 0xC554, 0xBED0, 0xC555, + 0xBED1, 0xC557, 0xBED2, 0xC558, 0xBED3, 0xC559, 0xBED4, 0xC55D, 0xBED5, 0xC55E, 0xBED6, 0xC560, 0xBED7, 0xC561, 0xBED8, 0xC564, + 0xBED9, 0xC568, 0xBEDA, 0xC570, 0xBEDB, 0xC571, 0xBEDC, 0xC573, 0xBEDD, 0xC574, 0xBEDE, 0xC575, 0xBEDF, 0xC57C, 0xBEE0, 0xC57D, + 0xBEE1, 0xC580, 0xBEE2, 0xC584, 0xBEE3, 0xC587, 0xBEE4, 0xC58C, 0xBEE5, 0xC58D, 0xBEE6, 0xC58F, 0xBEE7, 0xC591, 0xBEE8, 0xC595, + 0xBEE9, 0xC597, 0xBEEA, 0xC598, 0xBEEB, 0xC59C, 0xBEEC, 0xC5A0, 0xBEED, 0xC5A9, 0xBEEE, 0xC5B4, 0xBEEF, 0xC5B5, 0xBEF0, 0xC5B8, + 0xBEF1, 0xC5B9, 0xBEF2, 0xC5BB, 0xBEF3, 0xC5BC, 0xBEF4, 0xC5BD, 0xBEF5, 0xC5BE, 0xBEF6, 0xC5C4, 0xBEF7, 0xC5C5, 0xBEF8, 0xC5C6, + 0xBEF9, 0xC5C7, 0xBEFA, 0xC5C8, 0xBEFB, 0xC5C9, 0xBEFC, 0xC5CA, 0xBEFD, 0xC5CC, 0xBEFE, 0xC5CE, 0xBF41, 0xD49E, 0xBF42, 0xD49F, + 0xBF43, 0xD4A0, 0xBF44, 0xD4A1, 0xBF45, 0xD4A2, 0xBF46, 0xD4A3, 0xBF47, 0xD4A4, 0xBF48, 0xD4A5, 0xBF49, 0xD4A6, 0xBF4A, 0xD4A7, + 0xBF4B, 0xD4A8, 0xBF4C, 0xD4AA, 0xBF4D, 0xD4AB, 0xBF4E, 0xD4AC, 0xBF4F, 0xD4AD, 0xBF50, 0xD4AE, 0xBF51, 0xD4AF, 0xBF52, 0xD4B0, + 0xBF53, 0xD4B1, 0xBF54, 0xD4B2, 0xBF55, 0xD4B3, 0xBF56, 0xD4B4, 0xBF57, 0xD4B5, 0xBF58, 0xD4B6, 0xBF59, 0xD4B7, 0xBF5A, 0xD4B8, + 0xBF61, 0xD4B9, 0xBF62, 0xD4BA, 0xBF63, 0xD4BB, 0xBF64, 0xD4BC, 0xBF65, 0xD4BD, 0xBF66, 0xD4BE, 0xBF67, 0xD4BF, 0xBF68, 0xD4C0, + 0xBF69, 0xD4C1, 0xBF6A, 0xD4C2, 0xBF6B, 0xD4C3, 0xBF6C, 0xD4C4, 0xBF6D, 0xD4C5, 0xBF6E, 0xD4C6, 0xBF6F, 0xD4C7, 0xBF70, 0xD4C8, + 0xBF71, 0xD4C9, 0xBF72, 0xD4CA, 0xBF73, 0xD4CB, 0xBF74, 0xD4CD, 0xBF75, 0xD4CE, 0xBF76, 0xD4CF, 0xBF77, 0xD4D1, 0xBF78, 0xD4D2, + 0xBF79, 0xD4D3, 0xBF7A, 0xD4D5, 0xBF81, 0xD4D6, 0xBF82, 0xD4D7, 0xBF83, 0xD4D8, 0xBF84, 0xD4D9, 0xBF85, 0xD4DA, 0xBF86, 0xD4DB, + 0xBF87, 0xD4DD, 0xBF88, 0xD4DE, 0xBF89, 0xD4E0, 0xBF8A, 0xD4E1, 0xBF8B, 0xD4E2, 0xBF8C, 0xD4E3, 0xBF8D, 0xD4E4, 0xBF8E, 0xD4E5, + 0xBF8F, 0xD4E6, 0xBF90, 0xD4E7, 0xBF91, 0xD4E9, 0xBF92, 0xD4EA, 0xBF93, 0xD4EB, 0xBF94, 0xD4ED, 0xBF95, 0xD4EE, 0xBF96, 0xD4EF, + 0xBF97, 0xD4F1, 0xBF98, 0xD4F2, 0xBF99, 0xD4F3, 0xBF9A, 0xD4F4, 0xBF9B, 0xD4F5, 0xBF9C, 0xD4F6, 0xBF9D, 0xD4F7, 0xBF9E, 0xD4F9, + 0xBF9F, 0xD4FA, 0xBFA0, 0xD4FC, 0xBFA1, 0xC5D0, 0xBFA2, 0xC5D1, 0xBFA3, 0xC5D4, 0xBFA4, 0xC5D8, 0xBFA5, 0xC5E0, 0xBFA6, 0xC5E1, + 0xBFA7, 0xC5E3, 0xBFA8, 0xC5E5, 0xBFA9, 0xC5EC, 0xBFAA, 0xC5ED, 0xBFAB, 0xC5EE, 0xBFAC, 0xC5F0, 0xBFAD, 0xC5F4, 0xBFAE, 0xC5F6, + 0xBFAF, 0xC5F7, 0xBFB0, 0xC5FC, 0xBFB1, 0xC5FD, 0xBFB2, 0xC5FE, 0xBFB3, 0xC5FF, 0xBFB4, 0xC600, 0xBFB5, 0xC601, 0xBFB6, 0xC605, + 0xBFB7, 0xC606, 0xBFB8, 0xC607, 0xBFB9, 0xC608, 0xBFBA, 0xC60C, 0xBFBB, 0xC610, 0xBFBC, 0xC618, 0xBFBD, 0xC619, 0xBFBE, 0xC61B, + 0xBFBF, 0xC61C, 0xBFC0, 0xC624, 0xBFC1, 0xC625, 0xBFC2, 0xC628, 0xBFC3, 0xC62C, 0xBFC4, 0xC62D, 0xBFC5, 0xC62E, 0xBFC6, 0xC630, + 0xBFC7, 0xC633, 0xBFC8, 0xC634, 0xBFC9, 0xC635, 0xBFCA, 0xC637, 0xBFCB, 0xC639, 0xBFCC, 0xC63B, 0xBFCD, 0xC640, 0xBFCE, 0xC641, + 0xBFCF, 0xC644, 0xBFD0, 0xC648, 0xBFD1, 0xC650, 0xBFD2, 0xC651, 0xBFD3, 0xC653, 0xBFD4, 0xC654, 0xBFD5, 0xC655, 0xBFD6, 0xC65C, + 0xBFD7, 0xC65D, 0xBFD8, 0xC660, 0xBFD9, 0xC66C, 0xBFDA, 0xC66F, 0xBFDB, 0xC671, 0xBFDC, 0xC678, 0xBFDD, 0xC679, 0xBFDE, 0xC67C, + 0xBFDF, 0xC680, 0xBFE0, 0xC688, 0xBFE1, 0xC689, 0xBFE2, 0xC68B, 0xBFE3, 0xC68D, 0xBFE4, 0xC694, 0xBFE5, 0xC695, 0xBFE6, 0xC698, + 0xBFE7, 0xC69C, 0xBFE8, 0xC6A4, 0xBFE9, 0xC6A5, 0xBFEA, 0xC6A7, 0xBFEB, 0xC6A9, 0xBFEC, 0xC6B0, 0xBFED, 0xC6B1, 0xBFEE, 0xC6B4, + 0xBFEF, 0xC6B8, 0xBFF0, 0xC6B9, 0xBFF1, 0xC6BA, 0xBFF2, 0xC6C0, 0xBFF3, 0xC6C1, 0xBFF4, 0xC6C3, 0xBFF5, 0xC6C5, 0xBFF6, 0xC6CC, + 0xBFF7, 0xC6CD, 0xBFF8, 0xC6D0, 0xBFF9, 0xC6D4, 0xBFFA, 0xC6DC, 0xBFFB, 0xC6DD, 0xBFFC, 0xC6E0, 0xBFFD, 0xC6E1, 0xBFFE, 0xC6E8, + 0xC041, 0xD4FE, 0xC042, 0xD4FF, 0xC043, 0xD500, 0xC044, 0xD501, 0xC045, 0xD502, 0xC046, 0xD503, 0xC047, 0xD505, 0xC048, 0xD506, + 0xC049, 0xD507, 0xC04A, 0xD509, 0xC04B, 0xD50A, 0xC04C, 0xD50B, 0xC04D, 0xD50D, 0xC04E, 0xD50E, 0xC04F, 0xD50F, 0xC050, 0xD510, + 0xC051, 0xD511, 0xC052, 0xD512, 0xC053, 0xD513, 0xC054, 0xD516, 0xC055, 0xD518, 0xC056, 0xD519, 0xC057, 0xD51A, 0xC058, 0xD51B, + 0xC059, 0xD51C, 0xC05A, 0xD51D, 0xC061, 0xD51E, 0xC062, 0xD51F, 0xC063, 0xD520, 0xC064, 0xD521, 0xC065, 0xD522, 0xC066, 0xD523, + 0xC067, 0xD524, 0xC068, 0xD525, 0xC069, 0xD526, 0xC06A, 0xD527, 0xC06B, 0xD528, 0xC06C, 0xD529, 0xC06D, 0xD52A, 0xC06E, 0xD52B, + 0xC06F, 0xD52C, 0xC070, 0xD52D, 0xC071, 0xD52E, 0xC072, 0xD52F, 0xC073, 0xD530, 0xC074, 0xD531, 0xC075, 0xD532, 0xC076, 0xD533, + 0xC077, 0xD534, 0xC078, 0xD535, 0xC079, 0xD536, 0xC07A, 0xD537, 0xC081, 0xD538, 0xC082, 0xD539, 0xC083, 0xD53A, 0xC084, 0xD53B, + 0xC085, 0xD53E, 0xC086, 0xD53F, 0xC087, 0xD541, 0xC088, 0xD542, 0xC089, 0xD543, 0xC08A, 0xD545, 0xC08B, 0xD546, 0xC08C, 0xD547, + 0xC08D, 0xD548, 0xC08E, 0xD549, 0xC08F, 0xD54A, 0xC090, 0xD54B, 0xC091, 0xD54E, 0xC092, 0xD550, 0xC093, 0xD552, 0xC094, 0xD553, + 0xC095, 0xD554, 0xC096, 0xD555, 0xC097, 0xD556, 0xC098, 0xD557, 0xC099, 0xD55A, 0xC09A, 0xD55B, 0xC09B, 0xD55D, 0xC09C, 0xD55E, + 0xC09D, 0xD55F, 0xC09E, 0xD561, 0xC09F, 0xD562, 0xC0A0, 0xD563, 0xC0A1, 0xC6E9, 0xC0A2, 0xC6EC, 0xC0A3, 0xC6F0, 0xC0A4, 0xC6F8, + 0xC0A5, 0xC6F9, 0xC0A6, 0xC6FD, 0xC0A7, 0xC704, 0xC0A8, 0xC705, 0xC0A9, 0xC708, 0xC0AA, 0xC70C, 0xC0AB, 0xC714, 0xC0AC, 0xC715, + 0xC0AD, 0xC717, 0xC0AE, 0xC719, 0xC0AF, 0xC720, 0xC0B0, 0xC721, 0xC0B1, 0xC724, 0xC0B2, 0xC728, 0xC0B3, 0xC730, 0xC0B4, 0xC731, + 0xC0B5, 0xC733, 0xC0B6, 0xC735, 0xC0B7, 0xC737, 0xC0B8, 0xC73C, 0xC0B9, 0xC73D, 0xC0BA, 0xC740, 0xC0BB, 0xC744, 0xC0BC, 0xC74A, + 0xC0BD, 0xC74C, 0xC0BE, 0xC74D, 0xC0BF, 0xC74F, 0xC0C0, 0xC751, 0xC0C1, 0xC752, 0xC0C2, 0xC753, 0xC0C3, 0xC754, 0xC0C4, 0xC755, + 0xC0C5, 0xC756, 0xC0C6, 0xC757, 0xC0C7, 0xC758, 0xC0C8, 0xC75C, 0xC0C9, 0xC760, 0xC0CA, 0xC768, 0xC0CB, 0xC76B, 0xC0CC, 0xC774, + 0xC0CD, 0xC775, 0xC0CE, 0xC778, 0xC0CF, 0xC77C, 0xC0D0, 0xC77D, 0xC0D1, 0xC77E, 0xC0D2, 0xC783, 0xC0D3, 0xC784, 0xC0D4, 0xC785, + 0xC0D5, 0xC787, 0xC0D6, 0xC788, 0xC0D7, 0xC789, 0xC0D8, 0xC78A, 0xC0D9, 0xC78E, 0xC0DA, 0xC790, 0xC0DB, 0xC791, 0xC0DC, 0xC794, + 0xC0DD, 0xC796, 0xC0DE, 0xC797, 0xC0DF, 0xC798, 0xC0E0, 0xC79A, 0xC0E1, 0xC7A0, 0xC0E2, 0xC7A1, 0xC0E3, 0xC7A3, 0xC0E4, 0xC7A4, + 0xC0E5, 0xC7A5, 0xC0E6, 0xC7A6, 0xC0E7, 0xC7AC, 0xC0E8, 0xC7AD, 0xC0E9, 0xC7B0, 0xC0EA, 0xC7B4, 0xC0EB, 0xC7BC, 0xC0EC, 0xC7BD, + 0xC0ED, 0xC7BF, 0xC0EE, 0xC7C0, 0xC0EF, 0xC7C1, 0xC0F0, 0xC7C8, 0xC0F1, 0xC7C9, 0xC0F2, 0xC7CC, 0xC0F3, 0xC7CE, 0xC0F4, 0xC7D0, + 0xC0F5, 0xC7D8, 0xC0F6, 0xC7DD, 0xC0F7, 0xC7E4, 0xC0F8, 0xC7E8, 0xC0F9, 0xC7EC, 0xC0FA, 0xC800, 0xC0FB, 0xC801, 0xC0FC, 0xC804, + 0xC0FD, 0xC808, 0xC0FE, 0xC80A, 0xC141, 0xD564, 0xC142, 0xD566, 0xC143, 0xD567, 0xC144, 0xD56A, 0xC145, 0xD56C, 0xC146, 0xD56E, + 0xC147, 0xD56F, 0xC148, 0xD570, 0xC149, 0xD571, 0xC14A, 0xD572, 0xC14B, 0xD573, 0xC14C, 0xD576, 0xC14D, 0xD577, 0xC14E, 0xD579, + 0xC14F, 0xD57A, 0xC150, 0xD57B, 0xC151, 0xD57D, 0xC152, 0xD57E, 0xC153, 0xD57F, 0xC154, 0xD580, 0xC155, 0xD581, 0xC156, 0xD582, + 0xC157, 0xD583, 0xC158, 0xD586, 0xC159, 0xD58A, 0xC15A, 0xD58B, 0xC161, 0xD58C, 0xC162, 0xD58D, 0xC163, 0xD58E, 0xC164, 0xD58F, + 0xC165, 0xD591, 0xC166, 0xD592, 0xC167, 0xD593, 0xC168, 0xD594, 0xC169, 0xD595, 0xC16A, 0xD596, 0xC16B, 0xD597, 0xC16C, 0xD598, + 0xC16D, 0xD599, 0xC16E, 0xD59A, 0xC16F, 0xD59B, 0xC170, 0xD59C, 0xC171, 0xD59D, 0xC172, 0xD59E, 0xC173, 0xD59F, 0xC174, 0xD5A0, + 0xC175, 0xD5A1, 0xC176, 0xD5A2, 0xC177, 0xD5A3, 0xC178, 0xD5A4, 0xC179, 0xD5A6, 0xC17A, 0xD5A7, 0xC181, 0xD5A8, 0xC182, 0xD5A9, + 0xC183, 0xD5AA, 0xC184, 0xD5AB, 0xC185, 0xD5AC, 0xC186, 0xD5AD, 0xC187, 0xD5AE, 0xC188, 0xD5AF, 0xC189, 0xD5B0, 0xC18A, 0xD5B1, + 0xC18B, 0xD5B2, 0xC18C, 0xD5B3, 0xC18D, 0xD5B4, 0xC18E, 0xD5B5, 0xC18F, 0xD5B6, 0xC190, 0xD5B7, 0xC191, 0xD5B8, 0xC192, 0xD5B9, + 0xC193, 0xD5BA, 0xC194, 0xD5BB, 0xC195, 0xD5BC, 0xC196, 0xD5BD, 0xC197, 0xD5BE, 0xC198, 0xD5BF, 0xC199, 0xD5C0, 0xC19A, 0xD5C1, + 0xC19B, 0xD5C2, 0xC19C, 0xD5C3, 0xC19D, 0xD5C4, 0xC19E, 0xD5C5, 0xC19F, 0xD5C6, 0xC1A0, 0xD5C7, 0xC1A1, 0xC810, 0xC1A2, 0xC811, + 0xC1A3, 0xC813, 0xC1A4, 0xC815, 0xC1A5, 0xC816, 0xC1A6, 0xC81C, 0xC1A7, 0xC81D, 0xC1A8, 0xC820, 0xC1A9, 0xC824, 0xC1AA, 0xC82C, + 0xC1AB, 0xC82D, 0xC1AC, 0xC82F, 0xC1AD, 0xC831, 0xC1AE, 0xC838, 0xC1AF, 0xC83C, 0xC1B0, 0xC840, 0xC1B1, 0xC848, 0xC1B2, 0xC849, + 0xC1B3, 0xC84C, 0xC1B4, 0xC84D, 0xC1B5, 0xC854, 0xC1B6, 0xC870, 0xC1B7, 0xC871, 0xC1B8, 0xC874, 0xC1B9, 0xC878, 0xC1BA, 0xC87A, + 0xC1BB, 0xC880, 0xC1BC, 0xC881, 0xC1BD, 0xC883, 0xC1BE, 0xC885, 0xC1BF, 0xC886, 0xC1C0, 0xC887, 0xC1C1, 0xC88B, 0xC1C2, 0xC88C, + 0xC1C3, 0xC88D, 0xC1C4, 0xC894, 0xC1C5, 0xC89D, 0xC1C6, 0xC89F, 0xC1C7, 0xC8A1, 0xC1C8, 0xC8A8, 0xC1C9, 0xC8BC, 0xC1CA, 0xC8BD, + 0xC1CB, 0xC8C4, 0xC1CC, 0xC8C8, 0xC1CD, 0xC8CC, 0xC1CE, 0xC8D4, 0xC1CF, 0xC8D5, 0xC1D0, 0xC8D7, 0xC1D1, 0xC8D9, 0xC1D2, 0xC8E0, + 0xC1D3, 0xC8E1, 0xC1D4, 0xC8E4, 0xC1D5, 0xC8F5, 0xC1D6, 0xC8FC, 0xC1D7, 0xC8FD, 0xC1D8, 0xC900, 0xC1D9, 0xC904, 0xC1DA, 0xC905, + 0xC1DB, 0xC906, 0xC1DC, 0xC90C, 0xC1DD, 0xC90D, 0xC1DE, 0xC90F, 0xC1DF, 0xC911, 0xC1E0, 0xC918, 0xC1E1, 0xC92C, 0xC1E2, 0xC934, + 0xC1E3, 0xC950, 0xC1E4, 0xC951, 0xC1E5, 0xC954, 0xC1E6, 0xC958, 0xC1E7, 0xC960, 0xC1E8, 0xC961, 0xC1E9, 0xC963, 0xC1EA, 0xC96C, + 0xC1EB, 0xC970, 0xC1EC, 0xC974, 0xC1ED, 0xC97C, 0xC1EE, 0xC988, 0xC1EF, 0xC989, 0xC1F0, 0xC98C, 0xC1F1, 0xC990, 0xC1F2, 0xC998, + 0xC1F3, 0xC999, 0xC1F4, 0xC99B, 0xC1F5, 0xC99D, 0xC1F6, 0xC9C0, 0xC1F7, 0xC9C1, 0xC1F8, 0xC9C4, 0xC1F9, 0xC9C7, 0xC1FA, 0xC9C8, + 0xC1FB, 0xC9CA, 0xC1FC, 0xC9D0, 0xC1FD, 0xC9D1, 0xC1FE, 0xC9D3, 0xC241, 0xD5CA, 0xC242, 0xD5CB, 0xC243, 0xD5CD, 0xC244, 0xD5CE, + 0xC245, 0xD5CF, 0xC246, 0xD5D1, 0xC247, 0xD5D3, 0xC248, 0xD5D4, 0xC249, 0xD5D5, 0xC24A, 0xD5D6, 0xC24B, 0xD5D7, 0xC24C, 0xD5DA, + 0xC24D, 0xD5DC, 0xC24E, 0xD5DE, 0xC24F, 0xD5DF, 0xC250, 0xD5E0, 0xC251, 0xD5E1, 0xC252, 0xD5E2, 0xC253, 0xD5E3, 0xC254, 0xD5E6, + 0xC255, 0xD5E7, 0xC256, 0xD5E9, 0xC257, 0xD5EA, 0xC258, 0xD5EB, 0xC259, 0xD5ED, 0xC25A, 0xD5EE, 0xC261, 0xD5EF, 0xC262, 0xD5F0, + 0xC263, 0xD5F1, 0xC264, 0xD5F2, 0xC265, 0xD5F3, 0xC266, 0xD5F6, 0xC267, 0xD5F8, 0xC268, 0xD5FA, 0xC269, 0xD5FB, 0xC26A, 0xD5FC, + 0xC26B, 0xD5FD, 0xC26C, 0xD5FE, 0xC26D, 0xD5FF, 0xC26E, 0xD602, 0xC26F, 0xD603, 0xC270, 0xD605, 0xC271, 0xD606, 0xC272, 0xD607, + 0xC273, 0xD609, 0xC274, 0xD60A, 0xC275, 0xD60B, 0xC276, 0xD60C, 0xC277, 0xD60D, 0xC278, 0xD60E, 0xC279, 0xD60F, 0xC27A, 0xD612, + 0xC281, 0xD616, 0xC282, 0xD617, 0xC283, 0xD618, 0xC284, 0xD619, 0xC285, 0xD61A, 0xC286, 0xD61B, 0xC287, 0xD61D, 0xC288, 0xD61E, + 0xC289, 0xD61F, 0xC28A, 0xD621, 0xC28B, 0xD622, 0xC28C, 0xD623, 0xC28D, 0xD625, 0xC28E, 0xD626, 0xC28F, 0xD627, 0xC290, 0xD628, + 0xC291, 0xD629, 0xC292, 0xD62A, 0xC293, 0xD62B, 0xC294, 0xD62C, 0xC295, 0xD62E, 0xC296, 0xD62F, 0xC297, 0xD630, 0xC298, 0xD631, + 0xC299, 0xD632, 0xC29A, 0xD633, 0xC29B, 0xD634, 0xC29C, 0xD635, 0xC29D, 0xD636, 0xC29E, 0xD637, 0xC29F, 0xD63A, 0xC2A0, 0xD63B, + 0xC2A1, 0xC9D5, 0xC2A2, 0xC9D6, 0xC2A3, 0xC9D9, 0xC2A4, 0xC9DA, 0xC2A5, 0xC9DC, 0xC2A6, 0xC9DD, 0xC2A7, 0xC9E0, 0xC2A8, 0xC9E2, + 0xC2A9, 0xC9E4, 0xC2AA, 0xC9E7, 0xC2AB, 0xC9EC, 0xC2AC, 0xC9ED, 0xC2AD, 0xC9EF, 0xC2AE, 0xC9F0, 0xC2AF, 0xC9F1, 0xC2B0, 0xC9F8, + 0xC2B1, 0xC9F9, 0xC2B2, 0xC9FC, 0xC2B3, 0xCA00, 0xC2B4, 0xCA08, 0xC2B5, 0xCA09, 0xC2B6, 0xCA0B, 0xC2B7, 0xCA0C, 0xC2B8, 0xCA0D, + 0xC2B9, 0xCA14, 0xC2BA, 0xCA18, 0xC2BB, 0xCA29, 0xC2BC, 0xCA4C, 0xC2BD, 0xCA4D, 0xC2BE, 0xCA50, 0xC2BF, 0xCA54, 0xC2C0, 0xCA5C, + 0xC2C1, 0xCA5D, 0xC2C2, 0xCA5F, 0xC2C3, 0xCA60, 0xC2C4, 0xCA61, 0xC2C5, 0xCA68, 0xC2C6, 0xCA7D, 0xC2C7, 0xCA84, 0xC2C8, 0xCA98, + 0xC2C9, 0xCABC, 0xC2CA, 0xCABD, 0xC2CB, 0xCAC0, 0xC2CC, 0xCAC4, 0xC2CD, 0xCACC, 0xC2CE, 0xCACD, 0xC2CF, 0xCACF, 0xC2D0, 0xCAD1, + 0xC2D1, 0xCAD3, 0xC2D2, 0xCAD8, 0xC2D3, 0xCAD9, 0xC2D4, 0xCAE0, 0xC2D5, 0xCAEC, 0xC2D6, 0xCAF4, 0xC2D7, 0xCB08, 0xC2D8, 0xCB10, + 0xC2D9, 0xCB14, 0xC2DA, 0xCB18, 0xC2DB, 0xCB20, 0xC2DC, 0xCB21, 0xC2DD, 0xCB41, 0xC2DE, 0xCB48, 0xC2DF, 0xCB49, 0xC2E0, 0xCB4C, + 0xC2E1, 0xCB50, 0xC2E2, 0xCB58, 0xC2E3, 0xCB59, 0xC2E4, 0xCB5D, 0xC2E5, 0xCB64, 0xC2E6, 0xCB78, 0xC2E7, 0xCB79, 0xC2E8, 0xCB9C, + 0xC2E9, 0xCBB8, 0xC2EA, 0xCBD4, 0xC2EB, 0xCBE4, 0xC2EC, 0xCBE7, 0xC2ED, 0xCBE9, 0xC2EE, 0xCC0C, 0xC2EF, 0xCC0D, 0xC2F0, 0xCC10, + 0xC2F1, 0xCC14, 0xC2F2, 0xCC1C, 0xC2F3, 0xCC1D, 0xC2F4, 0xCC21, 0xC2F5, 0xCC22, 0xC2F6, 0xCC27, 0xC2F7, 0xCC28, 0xC2F8, 0xCC29, + 0xC2F9, 0xCC2C, 0xC2FA, 0xCC2E, 0xC2FB, 0xCC30, 0xC2FC, 0xCC38, 0xC2FD, 0xCC39, 0xC2FE, 0xCC3B, 0xC341, 0xD63D, 0xC342, 0xD63E, + 0xC343, 0xD63F, 0xC344, 0xD641, 0xC345, 0xD642, 0xC346, 0xD643, 0xC347, 0xD644, 0xC348, 0xD646, 0xC349, 0xD647, 0xC34A, 0xD64A, + 0xC34B, 0xD64C, 0xC34C, 0xD64E, 0xC34D, 0xD64F, 0xC34E, 0xD650, 0xC34F, 0xD652, 0xC350, 0xD653, 0xC351, 0xD656, 0xC352, 0xD657, + 0xC353, 0xD659, 0xC354, 0xD65A, 0xC355, 0xD65B, 0xC356, 0xD65D, 0xC357, 0xD65E, 0xC358, 0xD65F, 0xC359, 0xD660, 0xC35A, 0xD661, + 0xC361, 0xD662, 0xC362, 0xD663, 0xC363, 0xD664, 0xC364, 0xD665, 0xC365, 0xD666, 0xC366, 0xD668, 0xC367, 0xD66A, 0xC368, 0xD66B, + 0xC369, 0xD66C, 0xC36A, 0xD66D, 0xC36B, 0xD66E, 0xC36C, 0xD66F, 0xC36D, 0xD672, 0xC36E, 0xD673, 0xC36F, 0xD675, 0xC370, 0xD676, + 0xC371, 0xD677, 0xC372, 0xD678, 0xC373, 0xD679, 0xC374, 0xD67A, 0xC375, 0xD67B, 0xC376, 0xD67C, 0xC377, 0xD67D, 0xC378, 0xD67E, + 0xC379, 0xD67F, 0xC37A, 0xD680, 0xC381, 0xD681, 0xC382, 0xD682, 0xC383, 0xD684, 0xC384, 0xD686, 0xC385, 0xD687, 0xC386, 0xD688, + 0xC387, 0xD689, 0xC388, 0xD68A, 0xC389, 0xD68B, 0xC38A, 0xD68E, 0xC38B, 0xD68F, 0xC38C, 0xD691, 0xC38D, 0xD692, 0xC38E, 0xD693, + 0xC38F, 0xD695, 0xC390, 0xD696, 0xC391, 0xD697, 0xC392, 0xD698, 0xC393, 0xD699, 0xC394, 0xD69A, 0xC395, 0xD69B, 0xC396, 0xD69C, + 0xC397, 0xD69E, 0xC398, 0xD6A0, 0xC399, 0xD6A2, 0xC39A, 0xD6A3, 0xC39B, 0xD6A4, 0xC39C, 0xD6A5, 0xC39D, 0xD6A6, 0xC39E, 0xD6A7, + 0xC39F, 0xD6A9, 0xC3A0, 0xD6AA, 0xC3A1, 0xCC3C, 0xC3A2, 0xCC3D, 0xC3A3, 0xCC3E, 0xC3A4, 0xCC44, 0xC3A5, 0xCC45, 0xC3A6, 0xCC48, + 0xC3A7, 0xCC4C, 0xC3A8, 0xCC54, 0xC3A9, 0xCC55, 0xC3AA, 0xCC57, 0xC3AB, 0xCC58, 0xC3AC, 0xCC59, 0xC3AD, 0xCC60, 0xC3AE, 0xCC64, + 0xC3AF, 0xCC66, 0xC3B0, 0xCC68, 0xC3B1, 0xCC70, 0xC3B2, 0xCC75, 0xC3B3, 0xCC98, 0xC3B4, 0xCC99, 0xC3B5, 0xCC9C, 0xC3B6, 0xCCA0, + 0xC3B7, 0xCCA8, 0xC3B8, 0xCCA9, 0xC3B9, 0xCCAB, 0xC3BA, 0xCCAC, 0xC3BB, 0xCCAD, 0xC3BC, 0xCCB4, 0xC3BD, 0xCCB5, 0xC3BE, 0xCCB8, + 0xC3BF, 0xCCBC, 0xC3C0, 0xCCC4, 0xC3C1, 0xCCC5, 0xC3C2, 0xCCC7, 0xC3C3, 0xCCC9, 0xC3C4, 0xCCD0, 0xC3C5, 0xCCD4, 0xC3C6, 0xCCE4, + 0xC3C7, 0xCCEC, 0xC3C8, 0xCCF0, 0xC3C9, 0xCD01, 0xC3CA, 0xCD08, 0xC3CB, 0xCD09, 0xC3CC, 0xCD0C, 0xC3CD, 0xCD10, 0xC3CE, 0xCD18, + 0xC3CF, 0xCD19, 0xC3D0, 0xCD1B, 0xC3D1, 0xCD1D, 0xC3D2, 0xCD24, 0xC3D3, 0xCD28, 0xC3D4, 0xCD2C, 0xC3D5, 0xCD39, 0xC3D6, 0xCD5C, + 0xC3D7, 0xCD60, 0xC3D8, 0xCD64, 0xC3D9, 0xCD6C, 0xC3DA, 0xCD6D, 0xC3DB, 0xCD6F, 0xC3DC, 0xCD71, 0xC3DD, 0xCD78, 0xC3DE, 0xCD88, + 0xC3DF, 0xCD94, 0xC3E0, 0xCD95, 0xC3E1, 0xCD98, 0xC3E2, 0xCD9C, 0xC3E3, 0xCDA4, 0xC3E4, 0xCDA5, 0xC3E5, 0xCDA7, 0xC3E6, 0xCDA9, + 0xC3E7, 0xCDB0, 0xC3E8, 0xCDC4, 0xC3E9, 0xCDCC, 0xC3EA, 0xCDD0, 0xC3EB, 0xCDE8, 0xC3EC, 0xCDEC, 0xC3ED, 0xCDF0, 0xC3EE, 0xCDF8, + 0xC3EF, 0xCDF9, 0xC3F0, 0xCDFB, 0xC3F1, 0xCDFD, 0xC3F2, 0xCE04, 0xC3F3, 0xCE08, 0xC3F4, 0xCE0C, 0xC3F5, 0xCE14, 0xC3F6, 0xCE19, + 0xC3F7, 0xCE20, 0xC3F8, 0xCE21, 0xC3F9, 0xCE24, 0xC3FA, 0xCE28, 0xC3FB, 0xCE30, 0xC3FC, 0xCE31, 0xC3FD, 0xCE33, 0xC3FE, 0xCE35, + 0xC441, 0xD6AB, 0xC442, 0xD6AD, 0xC443, 0xD6AE, 0xC444, 0xD6AF, 0xC445, 0xD6B1, 0xC446, 0xD6B2, 0xC447, 0xD6B3, 0xC448, 0xD6B4, + 0xC449, 0xD6B5, 0xC44A, 0xD6B6, 0xC44B, 0xD6B7, 0xC44C, 0xD6B8, 0xC44D, 0xD6BA, 0xC44E, 0xD6BC, 0xC44F, 0xD6BD, 0xC450, 0xD6BE, + 0xC451, 0xD6BF, 0xC452, 0xD6C0, 0xC453, 0xD6C1, 0xC454, 0xD6C2, 0xC455, 0xD6C3, 0xC456, 0xD6C6, 0xC457, 0xD6C7, 0xC458, 0xD6C9, + 0xC459, 0xD6CA, 0xC45A, 0xD6CB, 0xC461, 0xD6CD, 0xC462, 0xD6CE, 0xC463, 0xD6CF, 0xC464, 0xD6D0, 0xC465, 0xD6D2, 0xC466, 0xD6D3, + 0xC467, 0xD6D5, 0xC468, 0xD6D6, 0xC469, 0xD6D8, 0xC46A, 0xD6DA, 0xC46B, 0xD6DB, 0xC46C, 0xD6DC, 0xC46D, 0xD6DD, 0xC46E, 0xD6DE, + 0xC46F, 0xD6DF, 0xC470, 0xD6E1, 0xC471, 0xD6E2, 0xC472, 0xD6E3, 0xC473, 0xD6E5, 0xC474, 0xD6E6, 0xC475, 0xD6E7, 0xC476, 0xD6E9, + 0xC477, 0xD6EA, 0xC478, 0xD6EB, 0xC479, 0xD6EC, 0xC47A, 0xD6ED, 0xC481, 0xD6EE, 0xC482, 0xD6EF, 0xC483, 0xD6F1, 0xC484, 0xD6F2, + 0xC485, 0xD6F3, 0xC486, 0xD6F4, 0xC487, 0xD6F6, 0xC488, 0xD6F7, 0xC489, 0xD6F8, 0xC48A, 0xD6F9, 0xC48B, 0xD6FA, 0xC48C, 0xD6FB, + 0xC48D, 0xD6FE, 0xC48E, 0xD6FF, 0xC48F, 0xD701, 0xC490, 0xD702, 0xC491, 0xD703, 0xC492, 0xD705, 0xC493, 0xD706, 0xC494, 0xD707, + 0xC495, 0xD708, 0xC496, 0xD709, 0xC497, 0xD70A, 0xC498, 0xD70B, 0xC499, 0xD70C, 0xC49A, 0xD70D, 0xC49B, 0xD70E, 0xC49C, 0xD70F, + 0xC49D, 0xD710, 0xC49E, 0xD712, 0xC49F, 0xD713, 0xC4A0, 0xD714, 0xC4A1, 0xCE58, 0xC4A2, 0xCE59, 0xC4A3, 0xCE5C, 0xC4A4, 0xCE5F, + 0xC4A5, 0xCE60, 0xC4A6, 0xCE61, 0xC4A7, 0xCE68, 0xC4A8, 0xCE69, 0xC4A9, 0xCE6B, 0xC4AA, 0xCE6D, 0xC4AB, 0xCE74, 0xC4AC, 0xCE75, + 0xC4AD, 0xCE78, 0xC4AE, 0xCE7C, 0xC4AF, 0xCE84, 0xC4B0, 0xCE85, 0xC4B1, 0xCE87, 0xC4B2, 0xCE89, 0xC4B3, 0xCE90, 0xC4B4, 0xCE91, + 0xC4B5, 0xCE94, 0xC4B6, 0xCE98, 0xC4B7, 0xCEA0, 0xC4B8, 0xCEA1, 0xC4B9, 0xCEA3, 0xC4BA, 0xCEA4, 0xC4BB, 0xCEA5, 0xC4BC, 0xCEAC, + 0xC4BD, 0xCEAD, 0xC4BE, 0xCEC1, 0xC4BF, 0xCEE4, 0xC4C0, 0xCEE5, 0xC4C1, 0xCEE8, 0xC4C2, 0xCEEB, 0xC4C3, 0xCEEC, 0xC4C4, 0xCEF4, + 0xC4C5, 0xCEF5, 0xC4C6, 0xCEF7, 0xC4C7, 0xCEF8, 0xC4C8, 0xCEF9, 0xC4C9, 0xCF00, 0xC4CA, 0xCF01, 0xC4CB, 0xCF04, 0xC4CC, 0xCF08, + 0xC4CD, 0xCF10, 0xC4CE, 0xCF11, 0xC4CF, 0xCF13, 0xC4D0, 0xCF15, 0xC4D1, 0xCF1C, 0xC4D2, 0xCF20, 0xC4D3, 0xCF24, 0xC4D4, 0xCF2C, + 0xC4D5, 0xCF2D, 0xC4D6, 0xCF2F, 0xC4D7, 0xCF30, 0xC4D8, 0xCF31, 0xC4D9, 0xCF38, 0xC4DA, 0xCF54, 0xC4DB, 0xCF55, 0xC4DC, 0xCF58, + 0xC4DD, 0xCF5C, 0xC4DE, 0xCF64, 0xC4DF, 0xCF65, 0xC4E0, 0xCF67, 0xC4E1, 0xCF69, 0xC4E2, 0xCF70, 0xC4E3, 0xCF71, 0xC4E4, 0xCF74, + 0xC4E5, 0xCF78, 0xC4E6, 0xCF80, 0xC4E7, 0xCF85, 0xC4E8, 0xCF8C, 0xC4E9, 0xCFA1, 0xC4EA, 0xCFA8, 0xC4EB, 0xCFB0, 0xC4EC, 0xCFC4, + 0xC4ED, 0xCFE0, 0xC4EE, 0xCFE1, 0xC4EF, 0xCFE4, 0xC4F0, 0xCFE8, 0xC4F1, 0xCFF0, 0xC4F2, 0xCFF1, 0xC4F3, 0xCFF3, 0xC4F4, 0xCFF5, + 0xC4F5, 0xCFFC, 0xC4F6, 0xD000, 0xC4F7, 0xD004, 0xC4F8, 0xD011, 0xC4F9, 0xD018, 0xC4FA, 0xD02D, 0xC4FB, 0xD034, 0xC4FC, 0xD035, + 0xC4FD, 0xD038, 0xC4FE, 0xD03C, 0xC541, 0xD715, 0xC542, 0xD716, 0xC543, 0xD717, 0xC544, 0xD71A, 0xC545, 0xD71B, 0xC546, 0xD71D, + 0xC547, 0xD71E, 0xC548, 0xD71F, 0xC549, 0xD721, 0xC54A, 0xD722, 0xC54B, 0xD723, 0xC54C, 0xD724, 0xC54D, 0xD725, 0xC54E, 0xD726, + 0xC54F, 0xD727, 0xC550, 0xD72A, 0xC551, 0xD72C, 0xC552, 0xD72E, 0xC553, 0xD72F, 0xC554, 0xD730, 0xC555, 0xD731, 0xC556, 0xD732, + 0xC557, 0xD733, 0xC558, 0xD736, 0xC559, 0xD737, 0xC55A, 0xD739, 0xC561, 0xD73A, 0xC562, 0xD73B, 0xC563, 0xD73D, 0xC564, 0xD73E, + 0xC565, 0xD73F, 0xC566, 0xD740, 0xC567, 0xD741, 0xC568, 0xD742, 0xC569, 0xD743, 0xC56A, 0xD745, 0xC56B, 0xD746, 0xC56C, 0xD748, + 0xC56D, 0xD74A, 0xC56E, 0xD74B, 0xC56F, 0xD74C, 0xC570, 0xD74D, 0xC571, 0xD74E, 0xC572, 0xD74F, 0xC573, 0xD752, 0xC574, 0xD753, + 0xC575, 0xD755, 0xC576, 0xD75A, 0xC577, 0xD75B, 0xC578, 0xD75C, 0xC579, 0xD75D, 0xC57A, 0xD75E, 0xC581, 0xD75F, 0xC582, 0xD762, + 0xC583, 0xD764, 0xC584, 0xD766, 0xC585, 0xD767, 0xC586, 0xD768, 0xC587, 0xD76A, 0xC588, 0xD76B, 0xC589, 0xD76D, 0xC58A, 0xD76E, + 0xC58B, 0xD76F, 0xC58C, 0xD771, 0xC58D, 0xD772, 0xC58E, 0xD773, 0xC58F, 0xD775, 0xC590, 0xD776, 0xC591, 0xD777, 0xC592, 0xD778, + 0xC593, 0xD779, 0xC594, 0xD77A, 0xC595, 0xD77B, 0xC596, 0xD77E, 0xC597, 0xD77F, 0xC598, 0xD780, 0xC599, 0xD782, 0xC59A, 0xD783, + 0xC59B, 0xD784, 0xC59C, 0xD785, 0xC59D, 0xD786, 0xC59E, 0xD787, 0xC59F, 0xD78A, 0xC5A0, 0xD78B, 0xC5A1, 0xD044, 0xC5A2, 0xD045, + 0xC5A3, 0xD047, 0xC5A4, 0xD049, 0xC5A5, 0xD050, 0xC5A6, 0xD054, 0xC5A7, 0xD058, 0xC5A8, 0xD060, 0xC5A9, 0xD06C, 0xC5AA, 0xD06D, + 0xC5AB, 0xD070, 0xC5AC, 0xD074, 0xC5AD, 0xD07C, 0xC5AE, 0xD07D, 0xC5AF, 0xD081, 0xC5B0, 0xD0A4, 0xC5B1, 0xD0A5, 0xC5B2, 0xD0A8, + 0xC5B3, 0xD0AC, 0xC5B4, 0xD0B4, 0xC5B5, 0xD0B5, 0xC5B6, 0xD0B7, 0xC5B7, 0xD0B9, 0xC5B8, 0xD0C0, 0xC5B9, 0xD0C1, 0xC5BA, 0xD0C4, + 0xC5BB, 0xD0C8, 0xC5BC, 0xD0C9, 0xC5BD, 0xD0D0, 0xC5BE, 0xD0D1, 0xC5BF, 0xD0D3, 0xC5C0, 0xD0D4, 0xC5C1, 0xD0D5, 0xC5C2, 0xD0DC, + 0xC5C3, 0xD0DD, 0xC5C4, 0xD0E0, 0xC5C5, 0xD0E4, 0xC5C6, 0xD0EC, 0xC5C7, 0xD0ED, 0xC5C8, 0xD0EF, 0xC5C9, 0xD0F0, 0xC5CA, 0xD0F1, + 0xC5CB, 0xD0F8, 0xC5CC, 0xD10D, 0xC5CD, 0xD130, 0xC5CE, 0xD131, 0xC5CF, 0xD134, 0xC5D0, 0xD138, 0xC5D1, 0xD13A, 0xC5D2, 0xD140, + 0xC5D3, 0xD141, 0xC5D4, 0xD143, 0xC5D5, 0xD144, 0xC5D6, 0xD145, 0xC5D7, 0xD14C, 0xC5D8, 0xD14D, 0xC5D9, 0xD150, 0xC5DA, 0xD154, + 0xC5DB, 0xD15C, 0xC5DC, 0xD15D, 0xC5DD, 0xD15F, 0xC5DE, 0xD161, 0xC5DF, 0xD168, 0xC5E0, 0xD16C, 0xC5E1, 0xD17C, 0xC5E2, 0xD184, + 0xC5E3, 0xD188, 0xC5E4, 0xD1A0, 0xC5E5, 0xD1A1, 0xC5E6, 0xD1A4, 0xC5E7, 0xD1A8, 0xC5E8, 0xD1B0, 0xC5E9, 0xD1B1, 0xC5EA, 0xD1B3, + 0xC5EB, 0xD1B5, 0xC5EC, 0xD1BA, 0xC5ED, 0xD1BC, 0xC5EE, 0xD1C0, 0xC5EF, 0xD1D8, 0xC5F0, 0xD1F4, 0xC5F1, 0xD1F8, 0xC5F2, 0xD207, + 0xC5F3, 0xD209, 0xC5F4, 0xD210, 0xC5F5, 0xD22C, 0xC5F6, 0xD22D, 0xC5F7, 0xD230, 0xC5F8, 0xD234, 0xC5F9, 0xD23C, 0xC5FA, 0xD23D, + 0xC5FB, 0xD23F, 0xC5FC, 0xD241, 0xC5FD, 0xD248, 0xC5FE, 0xD25C, 0xC641, 0xD78D, 0xC642, 0xD78E, 0xC643, 0xD78F, 0xC644, 0xD791, + 0xC645, 0xD792, 0xC646, 0xD793, 0xC647, 0xD794, 0xC648, 0xD795, 0xC649, 0xD796, 0xC64A, 0xD797, 0xC64B, 0xD79A, 0xC64C, 0xD79C, + 0xC64D, 0xD79E, 0xC64E, 0xD79F, 0xC64F, 0xD7A0, 0xC650, 0xD7A1, 0xC651, 0xD7A2, 0xC652, 0xD7A3, 0xC6A1, 0xD264, 0xC6A2, 0xD280, + 0xC6A3, 0xD281, 0xC6A4, 0xD284, 0xC6A5, 0xD288, 0xC6A6, 0xD290, 0xC6A7, 0xD291, 0xC6A8, 0xD295, 0xC6A9, 0xD29C, 0xC6AA, 0xD2A0, + 0xC6AB, 0xD2A4, 0xC6AC, 0xD2AC, 0xC6AD, 0xD2B1, 0xC6AE, 0xD2B8, 0xC6AF, 0xD2B9, 0xC6B0, 0xD2BC, 0xC6B1, 0xD2BF, 0xC6B2, 0xD2C0, + 0xC6B3, 0xD2C2, 0xC6B4, 0xD2C8, 0xC6B5, 0xD2C9, 0xC6B6, 0xD2CB, 0xC6B7, 0xD2D4, 0xC6B8, 0xD2D8, 0xC6B9, 0xD2DC, 0xC6BA, 0xD2E4, + 0xC6BB, 0xD2E5, 0xC6BC, 0xD2F0, 0xC6BD, 0xD2F1, 0xC6BE, 0xD2F4, 0xC6BF, 0xD2F8, 0xC6C0, 0xD300, 0xC6C1, 0xD301, 0xC6C2, 0xD303, + 0xC6C3, 0xD305, 0xC6C4, 0xD30C, 0xC6C5, 0xD30D, 0xC6C6, 0xD30E, 0xC6C7, 0xD310, 0xC6C8, 0xD314, 0xC6C9, 0xD316, 0xC6CA, 0xD31C, + 0xC6CB, 0xD31D, 0xC6CC, 0xD31F, 0xC6CD, 0xD320, 0xC6CE, 0xD321, 0xC6CF, 0xD325, 0xC6D0, 0xD328, 0xC6D1, 0xD329, 0xC6D2, 0xD32C, + 0xC6D3, 0xD330, 0xC6D4, 0xD338, 0xC6D5, 0xD339, 0xC6D6, 0xD33B, 0xC6D7, 0xD33C, 0xC6D8, 0xD33D, 0xC6D9, 0xD344, 0xC6DA, 0xD345, + 0xC6DB, 0xD37C, 0xC6DC, 0xD37D, 0xC6DD, 0xD380, 0xC6DE, 0xD384, 0xC6DF, 0xD38C, 0xC6E0, 0xD38D, 0xC6E1, 0xD38F, 0xC6E2, 0xD390, + 0xC6E3, 0xD391, 0xC6E4, 0xD398, 0xC6E5, 0xD399, 0xC6E6, 0xD39C, 0xC6E7, 0xD3A0, 0xC6E8, 0xD3A8, 0xC6E9, 0xD3A9, 0xC6EA, 0xD3AB, + 0xC6EB, 0xD3AD, 0xC6EC, 0xD3B4, 0xC6ED, 0xD3B8, 0xC6EE, 0xD3BC, 0xC6EF, 0xD3C4, 0xC6F0, 0xD3C5, 0xC6F1, 0xD3C8, 0xC6F2, 0xD3C9, + 0xC6F3, 0xD3D0, 0xC6F4, 0xD3D8, 0xC6F5, 0xD3E1, 0xC6F6, 0xD3E3, 0xC6F7, 0xD3EC, 0xC6F8, 0xD3ED, 0xC6F9, 0xD3F0, 0xC6FA, 0xD3F4, + 0xC6FB, 0xD3FC, 0xC6FC, 0xD3FD, 0xC6FD, 0xD3FF, 0xC6FE, 0xD401, 0xC7A1, 0xD408, 0xC7A2, 0xD41D, 0xC7A3, 0xD440, 0xC7A4, 0xD444, + 0xC7A5, 0xD45C, 0xC7A6, 0xD460, 0xC7A7, 0xD464, 0xC7A8, 0xD46D, 0xC7A9, 0xD46F, 0xC7AA, 0xD478, 0xC7AB, 0xD479, 0xC7AC, 0xD47C, + 0xC7AD, 0xD47F, 0xC7AE, 0xD480, 0xC7AF, 0xD482, 0xC7B0, 0xD488, 0xC7B1, 0xD489, 0xC7B2, 0xD48B, 0xC7B3, 0xD48D, 0xC7B4, 0xD494, + 0xC7B5, 0xD4A9, 0xC7B6, 0xD4CC, 0xC7B7, 0xD4D0, 0xC7B8, 0xD4D4, 0xC7B9, 0xD4DC, 0xC7BA, 0xD4DF, 0xC7BB, 0xD4E8, 0xC7BC, 0xD4EC, + 0xC7BD, 0xD4F0, 0xC7BE, 0xD4F8, 0xC7BF, 0xD4FB, 0xC7C0, 0xD4FD, 0xC7C1, 0xD504, 0xC7C2, 0xD508, 0xC7C3, 0xD50C, 0xC7C4, 0xD514, + 0xC7C5, 0xD515, 0xC7C6, 0xD517, 0xC7C7, 0xD53C, 0xC7C8, 0xD53D, 0xC7C9, 0xD540, 0xC7CA, 0xD544, 0xC7CB, 0xD54C, 0xC7CC, 0xD54D, + 0xC7CD, 0xD54F, 0xC7CE, 0xD551, 0xC7CF, 0xD558, 0xC7D0, 0xD559, 0xC7D1, 0xD55C, 0xC7D2, 0xD560, 0xC7D3, 0xD565, 0xC7D4, 0xD568, + 0xC7D5, 0xD569, 0xC7D6, 0xD56B, 0xC7D7, 0xD56D, 0xC7D8, 0xD574, 0xC7D9, 0xD575, 0xC7DA, 0xD578, 0xC7DB, 0xD57C, 0xC7DC, 0xD584, + 0xC7DD, 0xD585, 0xC7DE, 0xD587, 0xC7DF, 0xD588, 0xC7E0, 0xD589, 0xC7E1, 0xD590, 0xC7E2, 0xD5A5, 0xC7E3, 0xD5C8, 0xC7E4, 0xD5C9, + 0xC7E5, 0xD5CC, 0xC7E6, 0xD5D0, 0xC7E7, 0xD5D2, 0xC7E8, 0xD5D8, 0xC7E9, 0xD5D9, 0xC7EA, 0xD5DB, 0xC7EB, 0xD5DD, 0xC7EC, 0xD5E4, + 0xC7ED, 0xD5E5, 0xC7EE, 0xD5E8, 0xC7EF, 0xD5EC, 0xC7F0, 0xD5F4, 0xC7F1, 0xD5F5, 0xC7F2, 0xD5F7, 0xC7F3, 0xD5F9, 0xC7F4, 0xD600, + 0xC7F5, 0xD601, 0xC7F6, 0xD604, 0xC7F7, 0xD608, 0xC7F8, 0xD610, 0xC7F9, 0xD611, 0xC7FA, 0xD613, 0xC7FB, 0xD614, 0xC7FC, 0xD615, + 0xC7FD, 0xD61C, 0xC7FE, 0xD620, 0xC8A1, 0xD624, 0xC8A2, 0xD62D, 0xC8A3, 0xD638, 0xC8A4, 0xD639, 0xC8A5, 0xD63C, 0xC8A6, 0xD640, + 0xC8A7, 0xD645, 0xC8A8, 0xD648, 0xC8A9, 0xD649, 0xC8AA, 0xD64B, 0xC8AB, 0xD64D, 0xC8AC, 0xD651, 0xC8AD, 0xD654, 0xC8AE, 0xD655, + 0xC8AF, 0xD658, 0xC8B0, 0xD65C, 0xC8B1, 0xD667, 0xC8B2, 0xD669, 0xC8B3, 0xD670, 0xC8B4, 0xD671, 0xC8B5, 0xD674, 0xC8B6, 0xD683, + 0xC8B7, 0xD685, 0xC8B8, 0xD68C, 0xC8B9, 0xD68D, 0xC8BA, 0xD690, 0xC8BB, 0xD694, 0xC8BC, 0xD69D, 0xC8BD, 0xD69F, 0xC8BE, 0xD6A1, + 0xC8BF, 0xD6A8, 0xC8C0, 0xD6AC, 0xC8C1, 0xD6B0, 0xC8C2, 0xD6B9, 0xC8C3, 0xD6BB, 0xC8C4, 0xD6C4, 0xC8C5, 0xD6C5, 0xC8C6, 0xD6C8, + 0xC8C7, 0xD6CC, 0xC8C8, 0xD6D1, 0xC8C9, 0xD6D4, 0xC8CA, 0xD6D7, 0xC8CB, 0xD6D9, 0xC8CC, 0xD6E0, 0xC8CD, 0xD6E4, 0xC8CE, 0xD6E8, + 0xC8CF, 0xD6F0, 0xC8D0, 0xD6F5, 0xC8D1, 0xD6FC, 0xC8D2, 0xD6FD, 0xC8D3, 0xD700, 0xC8D4, 0xD704, 0xC8D5, 0xD711, 0xC8D6, 0xD718, + 0xC8D7, 0xD719, 0xC8D8, 0xD71C, 0xC8D9, 0xD720, 0xC8DA, 0xD728, 0xC8DB, 0xD729, 0xC8DC, 0xD72B, 0xC8DD, 0xD72D, 0xC8DE, 0xD734, + 0xC8DF, 0xD735, 0xC8E0, 0xD738, 0xC8E1, 0xD73C, 0xC8E2, 0xD744, 0xC8E3, 0xD747, 0xC8E4, 0xD749, 0xC8E5, 0xD750, 0xC8E6, 0xD751, + 0xC8E7, 0xD754, 0xC8E8, 0xD756, 0xC8E9, 0xD757, 0xC8EA, 0xD758, 0xC8EB, 0xD759, 0xC8EC, 0xD760, 0xC8ED, 0xD761, 0xC8EE, 0xD763, + 0xC8EF, 0xD765, 0xC8F0, 0xD769, 0xC8F1, 0xD76C, 0xC8F2, 0xD770, 0xC8F3, 0xD774, 0xC8F4, 0xD77C, 0xC8F5, 0xD77D, 0xC8F6, 0xD781, + 0xC8F7, 0xD788, 0xC8F8, 0xD789, 0xC8F9, 0xD78C, 0xC8FA, 0xD790, 0xC8FB, 0xD798, 0xC8FC, 0xD799, 0xC8FD, 0xD79B, 0xC8FE, 0xD79D, + 0xCAA1, 0x4F3D, 0xCAA2, 0x4F73, 0xCAA3, 0x5047, 0xCAA4, 0x50F9, 0xCAA5, 0x52A0, 0xCAA6, 0x53EF, 0xCAA7, 0x5475, 0xCAA8, 0x54E5, + 0xCAA9, 0x5609, 0xCAAA, 0x5AC1, 0xCAAB, 0x5BB6, 0xCAAC, 0x6687, 0xCAAD, 0x67B6, 0xCAAE, 0x67B7, 0xCAAF, 0x67EF, 0xCAB0, 0x6B4C, + 0xCAB1, 0x73C2, 0xCAB2, 0x75C2, 0xCAB3, 0x7A3C, 0xCAB4, 0x82DB, 0xCAB5, 0x8304, 0xCAB6, 0x8857, 0xCAB7, 0x8888, 0xCAB8, 0x8A36, + 0xCAB9, 0x8CC8, 0xCABA, 0x8DCF, 0xCABB, 0x8EFB, 0xCABC, 0x8FE6, 0xCABD, 0x99D5, 0xCABE, 0x523B, 0xCABF, 0x5374, 0xCAC0, 0x5404, + 0xCAC1, 0x606A, 0xCAC2, 0x6164, 0xCAC3, 0x6BBC, 0xCAC4, 0x73CF, 0xCAC5, 0x811A, 0xCAC6, 0x89BA, 0xCAC7, 0x89D2, 0xCAC8, 0x95A3, + 0xCAC9, 0x4F83, 0xCACA, 0x520A, 0xCACB, 0x58BE, 0xCACC, 0x5978, 0xCACD, 0x59E6, 0xCACE, 0x5E72, 0xCACF, 0x5E79, 0xCAD0, 0x61C7, + 0xCAD1, 0x63C0, 0xCAD2, 0x6746, 0xCAD3, 0x67EC, 0xCAD4, 0x687F, 0xCAD5, 0x6F97, 0xCAD6, 0x764E, 0xCAD7, 0x770B, 0xCAD8, 0x78F5, + 0xCAD9, 0x7A08, 0xCADA, 0x7AFF, 0xCADB, 0x7C21, 0xCADC, 0x809D, 0xCADD, 0x826E, 0xCADE, 0x8271, 0xCADF, 0x8AEB, 0xCAE0, 0x9593, + 0xCAE1, 0x4E6B, 0xCAE2, 0x559D, 0xCAE3, 0x66F7, 0xCAE4, 0x6E34, 0xCAE5, 0x78A3, 0xCAE6, 0x7AED, 0xCAE7, 0x845B, 0xCAE8, 0x8910, + 0xCAE9, 0x874E, 0xCAEA, 0x97A8, 0xCAEB, 0x52D8, 0xCAEC, 0x574E, 0xCAED, 0x582A, 0xCAEE, 0x5D4C, 0xCAEF, 0x611F, 0xCAF0, 0x61BE, + 0xCAF1, 0x6221, 0xCAF2, 0x6562, 0xCAF3, 0x67D1, 0xCAF4, 0x6A44, 0xCAF5, 0x6E1B, 0xCAF6, 0x7518, 0xCAF7, 0x75B3, 0xCAF8, 0x76E3, + 0xCAF9, 0x77B0, 0xCAFA, 0x7D3A, 0xCAFB, 0x90AF, 0xCAFC, 0x9451, 0xCAFD, 0x9452, 0xCAFE, 0x9F95, 0xCBA1, 0x5323, 0xCBA2, 0x5CAC, + 0xCBA3, 0x7532, 0xCBA4, 0x80DB, 0xCBA5, 0x9240, 0xCBA6, 0x9598, 0xCBA7, 0x525B, 0xCBA8, 0x5808, 0xCBA9, 0x59DC, 0xCBAA, 0x5CA1, + 0xCBAB, 0x5D17, 0xCBAC, 0x5EB7, 0xCBAD, 0x5F3A, 0xCBAE, 0x5F4A, 0xCBAF, 0x6177, 0xCBB0, 0x6C5F, 0xCBB1, 0x757A, 0xCBB2, 0x7586, + 0xCBB3, 0x7CE0, 0xCBB4, 0x7D73, 0xCBB5, 0x7DB1, 0xCBB6, 0x7F8C, 0xCBB7, 0x8154, 0xCBB8, 0x8221, 0xCBB9, 0x8591, 0xCBBA, 0x8941, + 0xCBBB, 0x8B1B, 0xCBBC, 0x92FC, 0xCBBD, 0x964D, 0xCBBE, 0x9C47, 0xCBBF, 0x4ECB, 0xCBC0, 0x4EF7, 0xCBC1, 0x500B, 0xCBC2, 0x51F1, + 0xCBC3, 0x584F, 0xCBC4, 0x6137, 0xCBC5, 0x613E, 0xCBC6, 0x6168, 0xCBC7, 0x6539, 0xCBC8, 0x69EA, 0xCBC9, 0x6F11, 0xCBCA, 0x75A5, + 0xCBCB, 0x7686, 0xCBCC, 0x76D6, 0xCBCD, 0x7B87, 0xCBCE, 0x82A5, 0xCBCF, 0x84CB, 0xCBD0, 0xF900, 0xCBD1, 0x93A7, 0xCBD2, 0x958B, + 0xCBD3, 0x5580, 0xCBD4, 0x5BA2, 0xCBD5, 0x5751, 0xCBD6, 0xF901, 0xCBD7, 0x7CB3, 0xCBD8, 0x7FB9, 0xCBD9, 0x91B5, 0xCBDA, 0x5028, + 0xCBDB, 0x53BB, 0xCBDC, 0x5C45, 0xCBDD, 0x5DE8, 0xCBDE, 0x62D2, 0xCBDF, 0x636E, 0xCBE0, 0x64DA, 0xCBE1, 0x64E7, 0xCBE2, 0x6E20, + 0xCBE3, 0x70AC, 0xCBE4, 0x795B, 0xCBE5, 0x8DDD, 0xCBE6, 0x8E1E, 0xCBE7, 0xF902, 0xCBE8, 0x907D, 0xCBE9, 0x9245, 0xCBEA, 0x92F8, + 0xCBEB, 0x4E7E, 0xCBEC, 0x4EF6, 0xCBED, 0x5065, 0xCBEE, 0x5DFE, 0xCBEF, 0x5EFA, 0xCBF0, 0x6106, 0xCBF1, 0x6957, 0xCBF2, 0x8171, + 0xCBF3, 0x8654, 0xCBF4, 0x8E47, 0xCBF5, 0x9375, 0xCBF6, 0x9A2B, 0xCBF7, 0x4E5E, 0xCBF8, 0x5091, 0xCBF9, 0x6770, 0xCBFA, 0x6840, + 0xCBFB, 0x5109, 0xCBFC, 0x528D, 0xCBFD, 0x5292, 0xCBFE, 0x6AA2, 0xCCA1, 0x77BC, 0xCCA2, 0x9210, 0xCCA3, 0x9ED4, 0xCCA4, 0x52AB, + 0xCCA5, 0x602F, 0xCCA6, 0x8FF2, 0xCCA7, 0x5048, 0xCCA8, 0x61A9, 0xCCA9, 0x63ED, 0xCCAA, 0x64CA, 0xCCAB, 0x683C, 0xCCAC, 0x6A84, + 0xCCAD, 0x6FC0, 0xCCAE, 0x8188, 0xCCAF, 0x89A1, 0xCCB0, 0x9694, 0xCCB1, 0x5805, 0xCCB2, 0x727D, 0xCCB3, 0x72AC, 0xCCB4, 0x7504, + 0xCCB5, 0x7D79, 0xCCB6, 0x7E6D, 0xCCB7, 0x80A9, 0xCCB8, 0x898B, 0xCCB9, 0x8B74, 0xCCBA, 0x9063, 0xCCBB, 0x9D51, 0xCCBC, 0x6289, + 0xCCBD, 0x6C7A, 0xCCBE, 0x6F54, 0xCCBF, 0x7D50, 0xCCC0, 0x7F3A, 0xCCC1, 0x8A23, 0xCCC2, 0x517C, 0xCCC3, 0x614A, 0xCCC4, 0x7B9D, + 0xCCC5, 0x8B19, 0xCCC6, 0x9257, 0xCCC7, 0x938C, 0xCCC8, 0x4EAC, 0xCCC9, 0x4FD3, 0xCCCA, 0x501E, 0xCCCB, 0x50BE, 0xCCCC, 0x5106, + 0xCCCD, 0x52C1, 0xCCCE, 0x52CD, 0xCCCF, 0x537F, 0xCCD0, 0x5770, 0xCCD1, 0x5883, 0xCCD2, 0x5E9A, 0xCCD3, 0x5F91, 0xCCD4, 0x6176, + 0xCCD5, 0x61AC, 0xCCD6, 0x64CE, 0xCCD7, 0x656C, 0xCCD8, 0x666F, 0xCCD9, 0x66BB, 0xCCDA, 0x66F4, 0xCCDB, 0x6897, 0xCCDC, 0x6D87, + 0xCCDD, 0x7085, 0xCCDE, 0x70F1, 0xCCDF, 0x749F, 0xCCE0, 0x74A5, 0xCCE1, 0x74CA, 0xCCE2, 0x75D9, 0xCCE3, 0x786C, 0xCCE4, 0x78EC, + 0xCCE5, 0x7ADF, 0xCCE6, 0x7AF6, 0xCCE7, 0x7D45, 0xCCE8, 0x7D93, 0xCCE9, 0x8015, 0xCCEA, 0x803F, 0xCCEB, 0x811B, 0xCCEC, 0x8396, + 0xCCED, 0x8B66, 0xCCEE, 0x8F15, 0xCCEF, 0x9015, 0xCCF0, 0x93E1, 0xCCF1, 0x9803, 0xCCF2, 0x9838, 0xCCF3, 0x9A5A, 0xCCF4, 0x9BE8, + 0xCCF5, 0x4FC2, 0xCCF6, 0x5553, 0xCCF7, 0x583A, 0xCCF8, 0x5951, 0xCCF9, 0x5B63, 0xCCFA, 0x5C46, 0xCCFB, 0x60B8, 0xCCFC, 0x6212, + 0xCCFD, 0x6842, 0xCCFE, 0x68B0, 0xCDA1, 0x68E8, 0xCDA2, 0x6EAA, 0xCDA3, 0x754C, 0xCDA4, 0x7678, 0xCDA5, 0x78CE, 0xCDA6, 0x7A3D, + 0xCDA7, 0x7CFB, 0xCDA8, 0x7E6B, 0xCDA9, 0x7E7C, 0xCDAA, 0x8A08, 0xCDAB, 0x8AA1, 0xCDAC, 0x8C3F, 0xCDAD, 0x968E, 0xCDAE, 0x9DC4, + 0xCDAF, 0x53E4, 0xCDB0, 0x53E9, 0xCDB1, 0x544A, 0xCDB2, 0x5471, 0xCDB3, 0x56FA, 0xCDB4, 0x59D1, 0xCDB5, 0x5B64, 0xCDB6, 0x5C3B, + 0xCDB7, 0x5EAB, 0xCDB8, 0x62F7, 0xCDB9, 0x6537, 0xCDBA, 0x6545, 0xCDBB, 0x6572, 0xCDBC, 0x66A0, 0xCDBD, 0x67AF, 0xCDBE, 0x69C1, + 0xCDBF, 0x6CBD, 0xCDC0, 0x75FC, 0xCDC1, 0x7690, 0xCDC2, 0x777E, 0xCDC3, 0x7A3F, 0xCDC4, 0x7F94, 0xCDC5, 0x8003, 0xCDC6, 0x80A1, + 0xCDC7, 0x818F, 0xCDC8, 0x82E6, 0xCDC9, 0x82FD, 0xCDCA, 0x83F0, 0xCDCB, 0x85C1, 0xCDCC, 0x8831, 0xCDCD, 0x88B4, 0xCDCE, 0x8AA5, + 0xCDCF, 0xF903, 0xCDD0, 0x8F9C, 0xCDD1, 0x932E, 0xCDD2, 0x96C7, 0xCDD3, 0x9867, 0xCDD4, 0x9AD8, 0xCDD5, 0x9F13, 0xCDD6, 0x54ED, + 0xCDD7, 0x659B, 0xCDD8, 0x66F2, 0xCDD9, 0x688F, 0xCDDA, 0x7A40, 0xCDDB, 0x8C37, 0xCDDC, 0x9D60, 0xCDDD, 0x56F0, 0xCDDE, 0x5764, + 0xCDDF, 0x5D11, 0xCDE0, 0x6606, 0xCDE1, 0x68B1, 0xCDE2, 0x68CD, 0xCDE3, 0x6EFE, 0xCDE4, 0x7428, 0xCDE5, 0x889E, 0xCDE6, 0x9BE4, + 0xCDE7, 0x6C68, 0xCDE8, 0xF904, 0xCDE9, 0x9AA8, 0xCDEA, 0x4F9B, 0xCDEB, 0x516C, 0xCDEC, 0x5171, 0xCDED, 0x529F, 0xCDEE, 0x5B54, + 0xCDEF, 0x5DE5, 0xCDF0, 0x6050, 0xCDF1, 0x606D, 0xCDF2, 0x62F1, 0xCDF3, 0x63A7, 0xCDF4, 0x653B, 0xCDF5, 0x73D9, 0xCDF6, 0x7A7A, + 0xCDF7, 0x86A3, 0xCDF8, 0x8CA2, 0xCDF9, 0x978F, 0xCDFA, 0x4E32, 0xCDFB, 0x5BE1, 0xCDFC, 0x6208, 0xCDFD, 0x679C, 0xCDFE, 0x74DC, + 0xCEA1, 0x79D1, 0xCEA2, 0x83D3, 0xCEA3, 0x8A87, 0xCEA4, 0x8AB2, 0xCEA5, 0x8DE8, 0xCEA6, 0x904E, 0xCEA7, 0x934B, 0xCEA8, 0x9846, + 0xCEA9, 0x5ED3, 0xCEAA, 0x69E8, 0xCEAB, 0x85FF, 0xCEAC, 0x90ED, 0xCEAD, 0xF905, 0xCEAE, 0x51A0, 0xCEAF, 0x5B98, 0xCEB0, 0x5BEC, + 0xCEB1, 0x6163, 0xCEB2, 0x68FA, 0xCEB3, 0x6B3E, 0xCEB4, 0x704C, 0xCEB5, 0x742F, 0xCEB6, 0x74D8, 0xCEB7, 0x7BA1, 0xCEB8, 0x7F50, + 0xCEB9, 0x83C5, 0xCEBA, 0x89C0, 0xCEBB, 0x8CAB, 0xCEBC, 0x95DC, 0xCEBD, 0x9928, 0xCEBE, 0x522E, 0xCEBF, 0x605D, 0xCEC0, 0x62EC, + 0xCEC1, 0x9002, 0xCEC2, 0x4F8A, 0xCEC3, 0x5149, 0xCEC4, 0x5321, 0xCEC5, 0x58D9, 0xCEC6, 0x5EE3, 0xCEC7, 0x66E0, 0xCEC8, 0x6D38, + 0xCEC9, 0x709A, 0xCECA, 0x72C2, 0xCECB, 0x73D6, 0xCECC, 0x7B50, 0xCECD, 0x80F1, 0xCECE, 0x945B, 0xCECF, 0x5366, 0xCED0, 0x639B, + 0xCED1, 0x7F6B, 0xCED2, 0x4E56, 0xCED3, 0x5080, 0xCED4, 0x584A, 0xCED5, 0x58DE, 0xCED6, 0x602A, 0xCED7, 0x6127, 0xCED8, 0x62D0, + 0xCED9, 0x69D0, 0xCEDA, 0x9B41, 0xCEDB, 0x5B8F, 0xCEDC, 0x7D18, 0xCEDD, 0x80B1, 0xCEDE, 0x8F5F, 0xCEDF, 0x4EA4, 0xCEE0, 0x50D1, + 0xCEE1, 0x54AC, 0xCEE2, 0x55AC, 0xCEE3, 0x5B0C, 0xCEE4, 0x5DA0, 0xCEE5, 0x5DE7, 0xCEE6, 0x652A, 0xCEE7, 0x654E, 0xCEE8, 0x6821, + 0xCEE9, 0x6A4B, 0xCEEA, 0x72E1, 0xCEEB, 0x768E, 0xCEEC, 0x77EF, 0xCEED, 0x7D5E, 0xCEEE, 0x7FF9, 0xCEEF, 0x81A0, 0xCEF0, 0x854E, + 0xCEF1, 0x86DF, 0xCEF2, 0x8F03, 0xCEF3, 0x8F4E, 0xCEF4, 0x90CA, 0xCEF5, 0x9903, 0xCEF6, 0x9A55, 0xCEF7, 0x9BAB, 0xCEF8, 0x4E18, + 0xCEF9, 0x4E45, 0xCEFA, 0x4E5D, 0xCEFB, 0x4EC7, 0xCEFC, 0x4FF1, 0xCEFD, 0x5177, 0xCEFE, 0x52FE, 0xCFA1, 0x5340, 0xCFA2, 0x53E3, + 0xCFA3, 0x53E5, 0xCFA4, 0x548E, 0xCFA5, 0x5614, 0xCFA6, 0x5775, 0xCFA7, 0x57A2, 0xCFA8, 0x5BC7, 0xCFA9, 0x5D87, 0xCFAA, 0x5ED0, + 0xCFAB, 0x61FC, 0xCFAC, 0x62D8, 0xCFAD, 0x6551, 0xCFAE, 0x67B8, 0xCFAF, 0x67E9, 0xCFB0, 0x69CB, 0xCFB1, 0x6B50, 0xCFB2, 0x6BC6, + 0xCFB3, 0x6BEC, 0xCFB4, 0x6C42, 0xCFB5, 0x6E9D, 0xCFB6, 0x7078, 0xCFB7, 0x72D7, 0xCFB8, 0x7396, 0xCFB9, 0x7403, 0xCFBA, 0x77BF, + 0xCFBB, 0x77E9, 0xCFBC, 0x7A76, 0xCFBD, 0x7D7F, 0xCFBE, 0x8009, 0xCFBF, 0x81FC, 0xCFC0, 0x8205, 0xCFC1, 0x820A, 0xCFC2, 0x82DF, + 0xCFC3, 0x8862, 0xCFC4, 0x8B33, 0xCFC5, 0x8CFC, 0xCFC6, 0x8EC0, 0xCFC7, 0x9011, 0xCFC8, 0x90B1, 0xCFC9, 0x9264, 0xCFCA, 0x92B6, + 0xCFCB, 0x99D2, 0xCFCC, 0x9A45, 0xCFCD, 0x9CE9, 0xCFCE, 0x9DD7, 0xCFCF, 0x9F9C, 0xCFD0, 0x570B, 0xCFD1, 0x5C40, 0xCFD2, 0x83CA, + 0xCFD3, 0x97A0, 0xCFD4, 0x97AB, 0xCFD5, 0x9EB4, 0xCFD6, 0x541B, 0xCFD7, 0x7A98, 0xCFD8, 0x7FA4, 0xCFD9, 0x88D9, 0xCFDA, 0x8ECD, + 0xCFDB, 0x90E1, 0xCFDC, 0x5800, 0xCFDD, 0x5C48, 0xCFDE, 0x6398, 0xCFDF, 0x7A9F, 0xCFE0, 0x5BAE, 0xCFE1, 0x5F13, 0xCFE2, 0x7A79, + 0xCFE3, 0x7AAE, 0xCFE4, 0x828E, 0xCFE5, 0x8EAC, 0xCFE6, 0x5026, 0xCFE7, 0x5238, 0xCFE8, 0x52F8, 0xCFE9, 0x5377, 0xCFEA, 0x5708, + 0xCFEB, 0x62F3, 0xCFEC, 0x6372, 0xCFED, 0x6B0A, 0xCFEE, 0x6DC3, 0xCFEF, 0x7737, 0xCFF0, 0x53A5, 0xCFF1, 0x7357, 0xCFF2, 0x8568, + 0xCFF3, 0x8E76, 0xCFF4, 0x95D5, 0xCFF5, 0x673A, 0xCFF6, 0x6AC3, 0xCFF7, 0x6F70, 0xCFF8, 0x8A6D, 0xCFF9, 0x8ECC, 0xCFFA, 0x994B, + 0xCFFB, 0xF906, 0xCFFC, 0x6677, 0xCFFD, 0x6B78, 0xCFFE, 0x8CB4, 0xD0A1, 0x9B3C, 0xD0A2, 0xF907, 0xD0A3, 0x53EB, 0xD0A4, 0x572D, + 0xD0A5, 0x594E, 0xD0A6, 0x63C6, 0xD0A7, 0x69FB, 0xD0A8, 0x73EA, 0xD0A9, 0x7845, 0xD0AA, 0x7ABA, 0xD0AB, 0x7AC5, 0xD0AC, 0x7CFE, + 0xD0AD, 0x8475, 0xD0AE, 0x898F, 0xD0AF, 0x8D73, 0xD0B0, 0x9035, 0xD0B1, 0x95A8, 0xD0B2, 0x52FB, 0xD0B3, 0x5747, 0xD0B4, 0x7547, + 0xD0B5, 0x7B60, 0xD0B6, 0x83CC, 0xD0B7, 0x921E, 0xD0B8, 0xF908, 0xD0B9, 0x6A58, 0xD0BA, 0x514B, 0xD0BB, 0x524B, 0xD0BC, 0x5287, + 0xD0BD, 0x621F, 0xD0BE, 0x68D8, 0xD0BF, 0x6975, 0xD0C0, 0x9699, 0xD0C1, 0x50C5, 0xD0C2, 0x52A4, 0xD0C3, 0x52E4, 0xD0C4, 0x61C3, + 0xD0C5, 0x65A4, 0xD0C6, 0x6839, 0xD0C7, 0x69FF, 0xD0C8, 0x747E, 0xD0C9, 0x7B4B, 0xD0CA, 0x82B9, 0xD0CB, 0x83EB, 0xD0CC, 0x89B2, + 0xD0CD, 0x8B39, 0xD0CE, 0x8FD1, 0xD0CF, 0x9949, 0xD0D0, 0xF909, 0xD0D1, 0x4ECA, 0xD0D2, 0x5997, 0xD0D3, 0x64D2, 0xD0D4, 0x6611, + 0xD0D5, 0x6A8E, 0xD0D6, 0x7434, 0xD0D7, 0x7981, 0xD0D8, 0x79BD, 0xD0D9, 0x82A9, 0xD0DA, 0x887E, 0xD0DB, 0x887F, 0xD0DC, 0x895F, + 0xD0DD, 0xF90A, 0xD0DE, 0x9326, 0xD0DF, 0x4F0B, 0xD0E0, 0x53CA, 0xD0E1, 0x6025, 0xD0E2, 0x6271, 0xD0E3, 0x6C72, 0xD0E4, 0x7D1A, + 0xD0E5, 0x7D66, 0xD0E6, 0x4E98, 0xD0E7, 0x5162, 0xD0E8, 0x77DC, 0xD0E9, 0x80AF, 0xD0EA, 0x4F01, 0xD0EB, 0x4F0E, 0xD0EC, 0x5176, + 0xD0ED, 0x5180, 0xD0EE, 0x55DC, 0xD0EF, 0x5668, 0xD0F0, 0x573B, 0xD0F1, 0x57FA, 0xD0F2, 0x57FC, 0xD0F3, 0x5914, 0xD0F4, 0x5947, + 0xD0F5, 0x5993, 0xD0F6, 0x5BC4, 0xD0F7, 0x5C90, 0xD0F8, 0x5D0E, 0xD0F9, 0x5DF1, 0xD0FA, 0x5E7E, 0xD0FB, 0x5FCC, 0xD0FC, 0x6280, + 0xD0FD, 0x65D7, 0xD0FE, 0x65E3, 0xD1A1, 0x671E, 0xD1A2, 0x671F, 0xD1A3, 0x675E, 0xD1A4, 0x68CB, 0xD1A5, 0x68C4, 0xD1A6, 0x6A5F, + 0xD1A7, 0x6B3A, 0xD1A8, 0x6C23, 0xD1A9, 0x6C7D, 0xD1AA, 0x6C82, 0xD1AB, 0x6DC7, 0xD1AC, 0x7398, 0xD1AD, 0x7426, 0xD1AE, 0x742A, + 0xD1AF, 0x7482, 0xD1B0, 0x74A3, 0xD1B1, 0x7578, 0xD1B2, 0x757F, 0xD1B3, 0x7881, 0xD1B4, 0x78EF, 0xD1B5, 0x7941, 0xD1B6, 0x7947, + 0xD1B7, 0x7948, 0xD1B8, 0x797A, 0xD1B9, 0x7B95, 0xD1BA, 0x7D00, 0xD1BB, 0x7DBA, 0xD1BC, 0x7F88, 0xD1BD, 0x8006, 0xD1BE, 0x802D, + 0xD1BF, 0x808C, 0xD1C0, 0x8A18, 0xD1C1, 0x8B4F, 0xD1C2, 0x8C48, 0xD1C3, 0x8D77, 0xD1C4, 0x9321, 0xD1C5, 0x9324, 0xD1C6, 0x98E2, + 0xD1C7, 0x9951, 0xD1C8, 0x9A0E, 0xD1C9, 0x9A0F, 0xD1CA, 0x9A65, 0xD1CB, 0x9E92, 0xD1CC, 0x7DCA, 0xD1CD, 0x4F76, 0xD1CE, 0x5409, + 0xD1CF, 0x62EE, 0xD1D0, 0x6854, 0xD1D1, 0x91D1, 0xD1D2, 0x55AB, 0xD1D3, 0x513A, 0xD1D4, 0xF90B, 0xD1D5, 0xF90C, 0xD1D6, 0x5A1C, + 0xD1D7, 0x61E6, 0xD1D8, 0xF90D, 0xD1D9, 0x62CF, 0xD1DA, 0x62FF, 0xD1DB, 0xF90E, 0xD1DC, 0xF90F, 0xD1DD, 0xF910, 0xD1DE, 0xF911, + 0xD1DF, 0xF912, 0xD1E0, 0xF913, 0xD1E1, 0x90A3, 0xD1E2, 0xF914, 0xD1E3, 0xF915, 0xD1E4, 0xF916, 0xD1E5, 0xF917, 0xD1E6, 0xF918, + 0xD1E7, 0x8AFE, 0xD1E8, 0xF919, 0xD1E9, 0xF91A, 0xD1EA, 0xF91B, 0xD1EB, 0xF91C, 0xD1EC, 0x6696, 0xD1ED, 0xF91D, 0xD1EE, 0x7156, + 0xD1EF, 0xF91E, 0xD1F0, 0xF91F, 0xD1F1, 0x96E3, 0xD1F2, 0xF920, 0xD1F3, 0x634F, 0xD1F4, 0x637A, 0xD1F5, 0x5357, 0xD1F6, 0xF921, + 0xD1F7, 0x678F, 0xD1F8, 0x6960, 0xD1F9, 0x6E73, 0xD1FA, 0xF922, 0xD1FB, 0x7537, 0xD1FC, 0xF923, 0xD1FD, 0xF924, 0xD1FE, 0xF925, + 0xD2A1, 0x7D0D, 0xD2A2, 0xF926, 0xD2A3, 0xF927, 0xD2A4, 0x8872, 0xD2A5, 0x56CA, 0xD2A6, 0x5A18, 0xD2A7, 0xF928, 0xD2A8, 0xF929, + 0xD2A9, 0xF92A, 0xD2AA, 0xF92B, 0xD2AB, 0xF92C, 0xD2AC, 0x4E43, 0xD2AD, 0xF92D, 0xD2AE, 0x5167, 0xD2AF, 0x5948, 0xD2B0, 0x67F0, + 0xD2B1, 0x8010, 0xD2B2, 0xF92E, 0xD2B3, 0x5973, 0xD2B4, 0x5E74, 0xD2B5, 0x649A, 0xD2B6, 0x79CA, 0xD2B7, 0x5FF5, 0xD2B8, 0x606C, + 0xD2B9, 0x62C8, 0xD2BA, 0x637B, 0xD2BB, 0x5BE7, 0xD2BC, 0x5BD7, 0xD2BD, 0x52AA, 0xD2BE, 0xF92F, 0xD2BF, 0x5974, 0xD2C0, 0x5F29, + 0xD2C1, 0x6012, 0xD2C2, 0xF930, 0xD2C3, 0xF931, 0xD2C4, 0xF932, 0xD2C5, 0x7459, 0xD2C6, 0xF933, 0xD2C7, 0xF934, 0xD2C8, 0xF935, + 0xD2C9, 0xF936, 0xD2CA, 0xF937, 0xD2CB, 0xF938, 0xD2CC, 0x99D1, 0xD2CD, 0xF939, 0xD2CE, 0xF93A, 0xD2CF, 0xF93B, 0xD2D0, 0xF93C, + 0xD2D1, 0xF93D, 0xD2D2, 0xF93E, 0xD2D3, 0xF93F, 0xD2D4, 0xF940, 0xD2D5, 0xF941, 0xD2D6, 0xF942, 0xD2D7, 0xF943, 0xD2D8, 0x6FC3, + 0xD2D9, 0xF944, 0xD2DA, 0xF945, 0xD2DB, 0x81BF, 0xD2DC, 0x8FB2, 0xD2DD, 0x60F1, 0xD2DE, 0xF946, 0xD2DF, 0xF947, 0xD2E0, 0x8166, + 0xD2E1, 0xF948, 0xD2E2, 0xF949, 0xD2E3, 0x5C3F, 0xD2E4, 0xF94A, 0xD2E5, 0xF94B, 0xD2E6, 0xF94C, 0xD2E7, 0xF94D, 0xD2E8, 0xF94E, + 0xD2E9, 0xF94F, 0xD2EA, 0xF950, 0xD2EB, 0xF951, 0xD2EC, 0x5AE9, 0xD2ED, 0x8A25, 0xD2EE, 0x677B, 0xD2EF, 0x7D10, 0xD2F0, 0xF952, + 0xD2F1, 0xF953, 0xD2F2, 0xF954, 0xD2F3, 0xF955, 0xD2F4, 0xF956, 0xD2F5, 0xF957, 0xD2F6, 0x80FD, 0xD2F7, 0xF958, 0xD2F8, 0xF959, + 0xD2F9, 0x5C3C, 0xD2FA, 0x6CE5, 0xD2FB, 0x533F, 0xD2FC, 0x6EBA, 0xD2FD, 0x591A, 0xD2FE, 0x8336, 0xD3A1, 0x4E39, 0xD3A2, 0x4EB6, + 0xD3A3, 0x4F46, 0xD3A4, 0x55AE, 0xD3A5, 0x5718, 0xD3A6, 0x58C7, 0xD3A7, 0x5F56, 0xD3A8, 0x65B7, 0xD3A9, 0x65E6, 0xD3AA, 0x6A80, + 0xD3AB, 0x6BB5, 0xD3AC, 0x6E4D, 0xD3AD, 0x77ED, 0xD3AE, 0x7AEF, 0xD3AF, 0x7C1E, 0xD3B0, 0x7DDE, 0xD3B1, 0x86CB, 0xD3B2, 0x8892, + 0xD3B3, 0x9132, 0xD3B4, 0x935B, 0xD3B5, 0x64BB, 0xD3B6, 0x6FBE, 0xD3B7, 0x737A, 0xD3B8, 0x75B8, 0xD3B9, 0x9054, 0xD3BA, 0x5556, + 0xD3BB, 0x574D, 0xD3BC, 0x61BA, 0xD3BD, 0x64D4, 0xD3BE, 0x66C7, 0xD3BF, 0x6DE1, 0xD3C0, 0x6E5B, 0xD3C1, 0x6F6D, 0xD3C2, 0x6FB9, + 0xD3C3, 0x75F0, 0xD3C4, 0x8043, 0xD3C5, 0x81BD, 0xD3C6, 0x8541, 0xD3C7, 0x8983, 0xD3C8, 0x8AC7, 0xD3C9, 0x8B5A, 0xD3CA, 0x931F, + 0xD3CB, 0x6C93, 0xD3CC, 0x7553, 0xD3CD, 0x7B54, 0xD3CE, 0x8E0F, 0xD3CF, 0x905D, 0xD3D0, 0x5510, 0xD3D1, 0x5802, 0xD3D2, 0x5858, + 0xD3D3, 0x5E62, 0xD3D4, 0x6207, 0xD3D5, 0x649E, 0xD3D6, 0x68E0, 0xD3D7, 0x7576, 0xD3D8, 0x7CD6, 0xD3D9, 0x87B3, 0xD3DA, 0x9EE8, + 0xD3DB, 0x4EE3, 0xD3DC, 0x5788, 0xD3DD, 0x576E, 0xD3DE, 0x5927, 0xD3DF, 0x5C0D, 0xD3E0, 0x5CB1, 0xD3E1, 0x5E36, 0xD3E2, 0x5F85, + 0xD3E3, 0x6234, 0xD3E4, 0x64E1, 0xD3E5, 0x73B3, 0xD3E6, 0x81FA, 0xD3E7, 0x888B, 0xD3E8, 0x8CB8, 0xD3E9, 0x968A, 0xD3EA, 0x9EDB, + 0xD3EB, 0x5B85, 0xD3EC, 0x5FB7, 0xD3ED, 0x60B3, 0xD3EE, 0x5012, 0xD3EF, 0x5200, 0xD3F0, 0x5230, 0xD3F1, 0x5716, 0xD3F2, 0x5835, + 0xD3F3, 0x5857, 0xD3F4, 0x5C0E, 0xD3F5, 0x5C60, 0xD3F6, 0x5CF6, 0xD3F7, 0x5D8B, 0xD3F8, 0x5EA6, 0xD3F9, 0x5F92, 0xD3FA, 0x60BC, + 0xD3FB, 0x6311, 0xD3FC, 0x6389, 0xD3FD, 0x6417, 0xD3FE, 0x6843, 0xD4A1, 0x68F9, 0xD4A2, 0x6AC2, 0xD4A3, 0x6DD8, 0xD4A4, 0x6E21, + 0xD4A5, 0x6ED4, 0xD4A6, 0x6FE4, 0xD4A7, 0x71FE, 0xD4A8, 0x76DC, 0xD4A9, 0x7779, 0xD4AA, 0x79B1, 0xD4AB, 0x7A3B, 0xD4AC, 0x8404, + 0xD4AD, 0x89A9, 0xD4AE, 0x8CED, 0xD4AF, 0x8DF3, 0xD4B0, 0x8E48, 0xD4B1, 0x9003, 0xD4B2, 0x9014, 0xD4B3, 0x9053, 0xD4B4, 0x90FD, + 0xD4B5, 0x934D, 0xD4B6, 0x9676, 0xD4B7, 0x97DC, 0xD4B8, 0x6BD2, 0xD4B9, 0x7006, 0xD4BA, 0x7258, 0xD4BB, 0x72A2, 0xD4BC, 0x7368, + 0xD4BD, 0x7763, 0xD4BE, 0x79BF, 0xD4BF, 0x7BE4, 0xD4C0, 0x7E9B, 0xD4C1, 0x8B80, 0xD4C2, 0x58A9, 0xD4C3, 0x60C7, 0xD4C4, 0x6566, + 0xD4C5, 0x65FD, 0xD4C6, 0x66BE, 0xD4C7, 0x6C8C, 0xD4C8, 0x711E, 0xD4C9, 0x71C9, 0xD4CA, 0x8C5A, 0xD4CB, 0x9813, 0xD4CC, 0x4E6D, + 0xD4CD, 0x7A81, 0xD4CE, 0x4EDD, 0xD4CF, 0x51AC, 0xD4D0, 0x51CD, 0xD4D1, 0x52D5, 0xD4D2, 0x540C, 0xD4D3, 0x61A7, 0xD4D4, 0x6771, + 0xD4D5, 0x6850, 0xD4D6, 0x68DF, 0xD4D7, 0x6D1E, 0xD4D8, 0x6F7C, 0xD4D9, 0x75BC, 0xD4DA, 0x77B3, 0xD4DB, 0x7AE5, 0xD4DC, 0x80F4, + 0xD4DD, 0x8463, 0xD4DE, 0x9285, 0xD4DF, 0x515C, 0xD4E0, 0x6597, 0xD4E1, 0x675C, 0xD4E2, 0x6793, 0xD4E3, 0x75D8, 0xD4E4, 0x7AC7, + 0xD4E5, 0x8373, 0xD4E6, 0xF95A, 0xD4E7, 0x8C46, 0xD4E8, 0x9017, 0xD4E9, 0x982D, 0xD4EA, 0x5C6F, 0xD4EB, 0x81C0, 0xD4EC, 0x829A, + 0xD4ED, 0x9041, 0xD4EE, 0x906F, 0xD4EF, 0x920D, 0xD4F0, 0x5F97, 0xD4F1, 0x5D9D, 0xD4F2, 0x6A59, 0xD4F3, 0x71C8, 0xD4F4, 0x767B, + 0xD4F5, 0x7B49, 0xD4F6, 0x85E4, 0xD4F7, 0x8B04, 0xD4F8, 0x9127, 0xD4F9, 0x9A30, 0xD4FA, 0x5587, 0xD4FB, 0x61F6, 0xD4FC, 0xF95B, + 0xD4FD, 0x7669, 0xD4FE, 0x7F85, 0xD5A1, 0x863F, 0xD5A2, 0x87BA, 0xD5A3, 0x88F8, 0xD5A4, 0x908F, 0xD5A5, 0xF95C, 0xD5A6, 0x6D1B, + 0xD5A7, 0x70D9, 0xD5A8, 0x73DE, 0xD5A9, 0x7D61, 0xD5AA, 0x843D, 0xD5AB, 0xF95D, 0xD5AC, 0x916A, 0xD5AD, 0x99F1, 0xD5AE, 0xF95E, + 0xD5AF, 0x4E82, 0xD5B0, 0x5375, 0xD5B1, 0x6B04, 0xD5B2, 0x6B12, 0xD5B3, 0x703E, 0xD5B4, 0x721B, 0xD5B5, 0x862D, 0xD5B6, 0x9E1E, + 0xD5B7, 0x524C, 0xD5B8, 0x8FA3, 0xD5B9, 0x5D50, 0xD5BA, 0x64E5, 0xD5BB, 0x652C, 0xD5BC, 0x6B16, 0xD5BD, 0x6FEB, 0xD5BE, 0x7C43, + 0xD5BF, 0x7E9C, 0xD5C0, 0x85CD, 0xD5C1, 0x8964, 0xD5C2, 0x89BD, 0xD5C3, 0x62C9, 0xD5C4, 0x81D8, 0xD5C5, 0x881F, 0xD5C6, 0x5ECA, + 0xD5C7, 0x6717, 0xD5C8, 0x6D6A, 0xD5C9, 0x72FC, 0xD5CA, 0x7405, 0xD5CB, 0x746F, 0xD5CC, 0x8782, 0xD5CD, 0x90DE, 0xD5CE, 0x4F86, + 0xD5CF, 0x5D0D, 0xD5D0, 0x5FA0, 0xD5D1, 0x840A, 0xD5D2, 0x51B7, 0xD5D3, 0x63A0, 0xD5D4, 0x7565, 0xD5D5, 0x4EAE, 0xD5D6, 0x5006, + 0xD5D7, 0x5169, 0xD5D8, 0x51C9, 0xD5D9, 0x6881, 0xD5DA, 0x6A11, 0xD5DB, 0x7CAE, 0xD5DC, 0x7CB1, 0xD5DD, 0x7CE7, 0xD5DE, 0x826F, + 0xD5DF, 0x8AD2, 0xD5E0, 0x8F1B, 0xD5E1, 0x91CF, 0xD5E2, 0x4FB6, 0xD5E3, 0x5137, 0xD5E4, 0x52F5, 0xD5E5, 0x5442, 0xD5E6, 0x5EEC, + 0xD5E7, 0x616E, 0xD5E8, 0x623E, 0xD5E9, 0x65C5, 0xD5EA, 0x6ADA, 0xD5EB, 0x6FFE, 0xD5EC, 0x792A, 0xD5ED, 0x85DC, 0xD5EE, 0x8823, + 0xD5EF, 0x95AD, 0xD5F0, 0x9A62, 0xD5F1, 0x9A6A, 0xD5F2, 0x9E97, 0xD5F3, 0x9ECE, 0xD5F4, 0x529B, 0xD5F5, 0x66C6, 0xD5F6, 0x6B77, + 0xD5F7, 0x701D, 0xD5F8, 0x792B, 0xD5F9, 0x8F62, 0xD5FA, 0x9742, 0xD5FB, 0x6190, 0xD5FC, 0x6200, 0xD5FD, 0x6523, 0xD5FE, 0x6F23, + 0xD6A1, 0x7149, 0xD6A2, 0x7489, 0xD6A3, 0x7DF4, 0xD6A4, 0x806F, 0xD6A5, 0x84EE, 0xD6A6, 0x8F26, 0xD6A7, 0x9023, 0xD6A8, 0x934A, + 0xD6A9, 0x51BD, 0xD6AA, 0x5217, 0xD6AB, 0x52A3, 0xD6AC, 0x6D0C, 0xD6AD, 0x70C8, 0xD6AE, 0x88C2, 0xD6AF, 0x5EC9, 0xD6B0, 0x6582, + 0xD6B1, 0x6BAE, 0xD6B2, 0x6FC2, 0xD6B3, 0x7C3E, 0xD6B4, 0x7375, 0xD6B5, 0x4EE4, 0xD6B6, 0x4F36, 0xD6B7, 0x56F9, 0xD6B8, 0xF95F, + 0xD6B9, 0x5CBA, 0xD6BA, 0x5DBA, 0xD6BB, 0x601C, 0xD6BC, 0x73B2, 0xD6BD, 0x7B2D, 0xD6BE, 0x7F9A, 0xD6BF, 0x7FCE, 0xD6C0, 0x8046, + 0xD6C1, 0x901E, 0xD6C2, 0x9234, 0xD6C3, 0x96F6, 0xD6C4, 0x9748, 0xD6C5, 0x9818, 0xD6C6, 0x9F61, 0xD6C7, 0x4F8B, 0xD6C8, 0x6FA7, + 0xD6C9, 0x79AE, 0xD6CA, 0x91B4, 0xD6CB, 0x96B7, 0xD6CC, 0x52DE, 0xD6CD, 0xF960, 0xD6CE, 0x6488, 0xD6CF, 0x64C4, 0xD6D0, 0x6AD3, + 0xD6D1, 0x6F5E, 0xD6D2, 0x7018, 0xD6D3, 0x7210, 0xD6D4, 0x76E7, 0xD6D5, 0x8001, 0xD6D6, 0x8606, 0xD6D7, 0x865C, 0xD6D8, 0x8DEF, + 0xD6D9, 0x8F05, 0xD6DA, 0x9732, 0xD6DB, 0x9B6F, 0xD6DC, 0x9DFA, 0xD6DD, 0x9E75, 0xD6DE, 0x788C, 0xD6DF, 0x797F, 0xD6E0, 0x7DA0, + 0xD6E1, 0x83C9, 0xD6E2, 0x9304, 0xD6E3, 0x9E7F, 0xD6E4, 0x9E93, 0xD6E5, 0x8AD6, 0xD6E6, 0x58DF, 0xD6E7, 0x5F04, 0xD6E8, 0x6727, + 0xD6E9, 0x7027, 0xD6EA, 0x74CF, 0xD6EB, 0x7C60, 0xD6EC, 0x807E, 0xD6ED, 0x5121, 0xD6EE, 0x7028, 0xD6EF, 0x7262, 0xD6F0, 0x78CA, + 0xD6F1, 0x8CC2, 0xD6F2, 0x8CDA, 0xD6F3, 0x8CF4, 0xD6F4, 0x96F7, 0xD6F5, 0x4E86, 0xD6F6, 0x50DA, 0xD6F7, 0x5BEE, 0xD6F8, 0x5ED6, + 0xD6F9, 0x6599, 0xD6FA, 0x71CE, 0xD6FB, 0x7642, 0xD6FC, 0x77AD, 0xD6FD, 0x804A, 0xD6FE, 0x84FC, 0xD7A1, 0x907C, 0xD7A2, 0x9B27, + 0xD7A3, 0x9F8D, 0xD7A4, 0x58D8, 0xD7A5, 0x5A41, 0xD7A6, 0x5C62, 0xD7A7, 0x6A13, 0xD7A8, 0x6DDA, 0xD7A9, 0x6F0F, 0xD7AA, 0x763B, + 0xD7AB, 0x7D2F, 0xD7AC, 0x7E37, 0xD7AD, 0x851E, 0xD7AE, 0x8938, 0xD7AF, 0x93E4, 0xD7B0, 0x964B, 0xD7B1, 0x5289, 0xD7B2, 0x65D2, + 0xD7B3, 0x67F3, 0xD7B4, 0x69B4, 0xD7B5, 0x6D41, 0xD7B6, 0x6E9C, 0xD7B7, 0x700F, 0xD7B8, 0x7409, 0xD7B9, 0x7460, 0xD7BA, 0x7559, + 0xD7BB, 0x7624, 0xD7BC, 0x786B, 0xD7BD, 0x8B2C, 0xD7BE, 0x985E, 0xD7BF, 0x516D, 0xD7C0, 0x622E, 0xD7C1, 0x9678, 0xD7C2, 0x4F96, + 0xD7C3, 0x502B, 0xD7C4, 0x5D19, 0xD7C5, 0x6DEA, 0xD7C6, 0x7DB8, 0xD7C7, 0x8F2A, 0xD7C8, 0x5F8B, 0xD7C9, 0x6144, 0xD7CA, 0x6817, + 0xD7CB, 0xF961, 0xD7CC, 0x9686, 0xD7CD, 0x52D2, 0xD7CE, 0x808B, 0xD7CF, 0x51DC, 0xD7D0, 0x51CC, 0xD7D1, 0x695E, 0xD7D2, 0x7A1C, + 0xD7D3, 0x7DBE, 0xD7D4, 0x83F1, 0xD7D5, 0x9675, 0xD7D6, 0x4FDA, 0xD7D7, 0x5229, 0xD7D8, 0x5398, 0xD7D9, 0x540F, 0xD7DA, 0x550E, + 0xD7DB, 0x5C65, 0xD7DC, 0x60A7, 0xD7DD, 0x674E, 0xD7DE, 0x68A8, 0xD7DF, 0x6D6C, 0xD7E0, 0x7281, 0xD7E1, 0x72F8, 0xD7E2, 0x7406, + 0xD7E3, 0x7483, 0xD7E4, 0xF962, 0xD7E5, 0x75E2, 0xD7E6, 0x7C6C, 0xD7E7, 0x7F79, 0xD7E8, 0x7FB8, 0xD7E9, 0x8389, 0xD7EA, 0x88CF, + 0xD7EB, 0x88E1, 0xD7EC, 0x91CC, 0xD7ED, 0x91D0, 0xD7EE, 0x96E2, 0xD7EF, 0x9BC9, 0xD7F0, 0x541D, 0xD7F1, 0x6F7E, 0xD7F2, 0x71D0, + 0xD7F3, 0x7498, 0xD7F4, 0x85FA, 0xD7F5, 0x8EAA, 0xD7F6, 0x96A3, 0xD7F7, 0x9C57, 0xD7F8, 0x9E9F, 0xD7F9, 0x6797, 0xD7FA, 0x6DCB, + 0xD7FB, 0x7433, 0xD7FC, 0x81E8, 0xD7FD, 0x9716, 0xD7FE, 0x782C, 0xD8A1, 0x7ACB, 0xD8A2, 0x7B20, 0xD8A3, 0x7C92, 0xD8A4, 0x6469, + 0xD8A5, 0x746A, 0xD8A6, 0x75F2, 0xD8A7, 0x78BC, 0xD8A8, 0x78E8, 0xD8A9, 0x99AC, 0xD8AA, 0x9B54, 0xD8AB, 0x9EBB, 0xD8AC, 0x5BDE, + 0xD8AD, 0x5E55, 0xD8AE, 0x6F20, 0xD8AF, 0x819C, 0xD8B0, 0x83AB, 0xD8B1, 0x9088, 0xD8B2, 0x4E07, 0xD8B3, 0x534D, 0xD8B4, 0x5A29, + 0xD8B5, 0x5DD2, 0xD8B6, 0x5F4E, 0xD8B7, 0x6162, 0xD8B8, 0x633D, 0xD8B9, 0x6669, 0xD8BA, 0x66FC, 0xD8BB, 0x6EFF, 0xD8BC, 0x6F2B, + 0xD8BD, 0x7063, 0xD8BE, 0x779E, 0xD8BF, 0x842C, 0xD8C0, 0x8513, 0xD8C1, 0x883B, 0xD8C2, 0x8F13, 0xD8C3, 0x9945, 0xD8C4, 0x9C3B, + 0xD8C5, 0x551C, 0xD8C6, 0x62B9, 0xD8C7, 0x672B, 0xD8C8, 0x6CAB, 0xD8C9, 0x8309, 0xD8CA, 0x896A, 0xD8CB, 0x977A, 0xD8CC, 0x4EA1, + 0xD8CD, 0x5984, 0xD8CE, 0x5FD8, 0xD8CF, 0x5FD9, 0xD8D0, 0x671B, 0xD8D1, 0x7DB2, 0xD8D2, 0x7F54, 0xD8D3, 0x8292, 0xD8D4, 0x832B, + 0xD8D5, 0x83BD, 0xD8D6, 0x8F1E, 0xD8D7, 0x9099, 0xD8D8, 0x57CB, 0xD8D9, 0x59B9, 0xD8DA, 0x5A92, 0xD8DB, 0x5BD0, 0xD8DC, 0x6627, + 0xD8DD, 0x679A, 0xD8DE, 0x6885, 0xD8DF, 0x6BCF, 0xD8E0, 0x7164, 0xD8E1, 0x7F75, 0xD8E2, 0x8CB7, 0xD8E3, 0x8CE3, 0xD8E4, 0x9081, + 0xD8E5, 0x9B45, 0xD8E6, 0x8108, 0xD8E7, 0x8C8A, 0xD8E8, 0x964C, 0xD8E9, 0x9A40, 0xD8EA, 0x9EA5, 0xD8EB, 0x5B5F, 0xD8EC, 0x6C13, + 0xD8ED, 0x731B, 0xD8EE, 0x76F2, 0xD8EF, 0x76DF, 0xD8F0, 0x840C, 0xD8F1, 0x51AA, 0xD8F2, 0x8993, 0xD8F3, 0x514D, 0xD8F4, 0x5195, + 0xD8F5, 0x52C9, 0xD8F6, 0x68C9, 0xD8F7, 0x6C94, 0xD8F8, 0x7704, 0xD8F9, 0x7720, 0xD8FA, 0x7DBF, 0xD8FB, 0x7DEC, 0xD8FC, 0x9762, + 0xD8FD, 0x9EB5, 0xD8FE, 0x6EC5, 0xD9A1, 0x8511, 0xD9A2, 0x51A5, 0xD9A3, 0x540D, 0xD9A4, 0x547D, 0xD9A5, 0x660E, 0xD9A6, 0x669D, + 0xD9A7, 0x6927, 0xD9A8, 0x6E9F, 0xD9A9, 0x76BF, 0xD9AA, 0x7791, 0xD9AB, 0x8317, 0xD9AC, 0x84C2, 0xD9AD, 0x879F, 0xD9AE, 0x9169, + 0xD9AF, 0x9298, 0xD9B0, 0x9CF4, 0xD9B1, 0x8882, 0xD9B2, 0x4FAE, 0xD9B3, 0x5192, 0xD9B4, 0x52DF, 0xD9B5, 0x59C6, 0xD9B6, 0x5E3D, + 0xD9B7, 0x6155, 0xD9B8, 0x6478, 0xD9B9, 0x6479, 0xD9BA, 0x66AE, 0xD9BB, 0x67D0, 0xD9BC, 0x6A21, 0xD9BD, 0x6BCD, 0xD9BE, 0x6BDB, + 0xD9BF, 0x725F, 0xD9C0, 0x7261, 0xD9C1, 0x7441, 0xD9C2, 0x7738, 0xD9C3, 0x77DB, 0xD9C4, 0x8017, 0xD9C5, 0x82BC, 0xD9C6, 0x8305, + 0xD9C7, 0x8B00, 0xD9C8, 0x8B28, 0xD9C9, 0x8C8C, 0xD9CA, 0x6728, 0xD9CB, 0x6C90, 0xD9CC, 0x7267, 0xD9CD, 0x76EE, 0xD9CE, 0x7766, + 0xD9CF, 0x7A46, 0xD9D0, 0x9DA9, 0xD9D1, 0x6B7F, 0xD9D2, 0x6C92, 0xD9D3, 0x5922, 0xD9D4, 0x6726, 0xD9D5, 0x8499, 0xD9D6, 0x536F, + 0xD9D7, 0x5893, 0xD9D8, 0x5999, 0xD9D9, 0x5EDF, 0xD9DA, 0x63CF, 0xD9DB, 0x6634, 0xD9DC, 0x6773, 0xD9DD, 0x6E3A, 0xD9DE, 0x732B, + 0xD9DF, 0x7AD7, 0xD9E0, 0x82D7, 0xD9E1, 0x9328, 0xD9E2, 0x52D9, 0xD9E3, 0x5DEB, 0xD9E4, 0x61AE, 0xD9E5, 0x61CB, 0xD9E6, 0x620A, + 0xD9E7, 0x62C7, 0xD9E8, 0x64AB, 0xD9E9, 0x65E0, 0xD9EA, 0x6959, 0xD9EB, 0x6B66, 0xD9EC, 0x6BCB, 0xD9ED, 0x7121, 0xD9EE, 0x73F7, + 0xD9EF, 0x755D, 0xD9F0, 0x7E46, 0xD9F1, 0x821E, 0xD9F2, 0x8302, 0xD9F3, 0x856A, 0xD9F4, 0x8AA3, 0xD9F5, 0x8CBF, 0xD9F6, 0x9727, + 0xD9F7, 0x9D61, 0xD9F8, 0x58A8, 0xD9F9, 0x9ED8, 0xD9FA, 0x5011, 0xD9FB, 0x520E, 0xD9FC, 0x543B, 0xD9FD, 0x554F, 0xD9FE, 0x6587, + 0xDAA1, 0x6C76, 0xDAA2, 0x7D0A, 0xDAA3, 0x7D0B, 0xDAA4, 0x805E, 0xDAA5, 0x868A, 0xDAA6, 0x9580, 0xDAA7, 0x96EF, 0xDAA8, 0x52FF, + 0xDAA9, 0x6C95, 0xDAAA, 0x7269, 0xDAAB, 0x5473, 0xDAAC, 0x5A9A, 0xDAAD, 0x5C3E, 0xDAAE, 0x5D4B, 0xDAAF, 0x5F4C, 0xDAB0, 0x5FAE, + 0xDAB1, 0x672A, 0xDAB2, 0x68B6, 0xDAB3, 0x6963, 0xDAB4, 0x6E3C, 0xDAB5, 0x6E44, 0xDAB6, 0x7709, 0xDAB7, 0x7C73, 0xDAB8, 0x7F8E, + 0xDAB9, 0x8587, 0xDABA, 0x8B0E, 0xDABB, 0x8FF7, 0xDABC, 0x9761, 0xDABD, 0x9EF4, 0xDABE, 0x5CB7, 0xDABF, 0x60B6, 0xDAC0, 0x610D, + 0xDAC1, 0x61AB, 0xDAC2, 0x654F, 0xDAC3, 0x65FB, 0xDAC4, 0x65FC, 0xDAC5, 0x6C11, 0xDAC6, 0x6CEF, 0xDAC7, 0x739F, 0xDAC8, 0x73C9, + 0xDAC9, 0x7DE1, 0xDACA, 0x9594, 0xDACB, 0x5BC6, 0xDACC, 0x871C, 0xDACD, 0x8B10, 0xDACE, 0x525D, 0xDACF, 0x535A, 0xDAD0, 0x62CD, + 0xDAD1, 0x640F, 0xDAD2, 0x64B2, 0xDAD3, 0x6734, 0xDAD4, 0x6A38, 0xDAD5, 0x6CCA, 0xDAD6, 0x73C0, 0xDAD7, 0x749E, 0xDAD8, 0x7B94, + 0xDAD9, 0x7C95, 0xDADA, 0x7E1B, 0xDADB, 0x818A, 0xDADC, 0x8236, 0xDADD, 0x8584, 0xDADE, 0x8FEB, 0xDADF, 0x96F9, 0xDAE0, 0x99C1, + 0xDAE1, 0x4F34, 0xDAE2, 0x534A, 0xDAE3, 0x53CD, 0xDAE4, 0x53DB, 0xDAE5, 0x62CC, 0xDAE6, 0x642C, 0xDAE7, 0x6500, 0xDAE8, 0x6591, + 0xDAE9, 0x69C3, 0xDAEA, 0x6CEE, 0xDAEB, 0x6F58, 0xDAEC, 0x73ED, 0xDAED, 0x7554, 0xDAEE, 0x7622, 0xDAEF, 0x76E4, 0xDAF0, 0x76FC, + 0xDAF1, 0x78D0, 0xDAF2, 0x78FB, 0xDAF3, 0x792C, 0xDAF4, 0x7D46, 0xDAF5, 0x822C, 0xDAF6, 0x87E0, 0xDAF7, 0x8FD4, 0xDAF8, 0x9812, + 0xDAF9, 0x98EF, 0xDAFA, 0x52C3, 0xDAFB, 0x62D4, 0xDAFC, 0x64A5, 0xDAFD, 0x6E24, 0xDAFE, 0x6F51, 0xDBA1, 0x767C, 0xDBA2, 0x8DCB, + 0xDBA3, 0x91B1, 0xDBA4, 0x9262, 0xDBA5, 0x9AEE, 0xDBA6, 0x9B43, 0xDBA7, 0x5023, 0xDBA8, 0x508D, 0xDBA9, 0x574A, 0xDBAA, 0x59A8, + 0xDBAB, 0x5C28, 0xDBAC, 0x5E47, 0xDBAD, 0x5F77, 0xDBAE, 0x623F, 0xDBAF, 0x653E, 0xDBB0, 0x65B9, 0xDBB1, 0x65C1, 0xDBB2, 0x6609, + 0xDBB3, 0x678B, 0xDBB4, 0x699C, 0xDBB5, 0x6EC2, 0xDBB6, 0x78C5, 0xDBB7, 0x7D21, 0xDBB8, 0x80AA, 0xDBB9, 0x8180, 0xDBBA, 0x822B, + 0xDBBB, 0x82B3, 0xDBBC, 0x84A1, 0xDBBD, 0x868C, 0xDBBE, 0x8A2A, 0xDBBF, 0x8B17, 0xDBC0, 0x90A6, 0xDBC1, 0x9632, 0xDBC2, 0x9F90, + 0xDBC3, 0x500D, 0xDBC4, 0x4FF3, 0xDBC5, 0xF963, 0xDBC6, 0x57F9, 0xDBC7, 0x5F98, 0xDBC8, 0x62DC, 0xDBC9, 0x6392, 0xDBCA, 0x676F, + 0xDBCB, 0x6E43, 0xDBCC, 0x7119, 0xDBCD, 0x76C3, 0xDBCE, 0x80CC, 0xDBCF, 0x80DA, 0xDBD0, 0x88F4, 0xDBD1, 0x88F5, 0xDBD2, 0x8919, + 0xDBD3, 0x8CE0, 0xDBD4, 0x8F29, 0xDBD5, 0x914D, 0xDBD6, 0x966A, 0xDBD7, 0x4F2F, 0xDBD8, 0x4F70, 0xDBD9, 0x5E1B, 0xDBDA, 0x67CF, + 0xDBDB, 0x6822, 0xDBDC, 0x767D, 0xDBDD, 0x767E, 0xDBDE, 0x9B44, 0xDBDF, 0x5E61, 0xDBE0, 0x6A0A, 0xDBE1, 0x7169, 0xDBE2, 0x71D4, + 0xDBE3, 0x756A, 0xDBE4, 0xF964, 0xDBE5, 0x7E41, 0xDBE6, 0x8543, 0xDBE7, 0x85E9, 0xDBE8, 0x98DC, 0xDBE9, 0x4F10, 0xDBEA, 0x7B4F, + 0xDBEB, 0x7F70, 0xDBEC, 0x95A5, 0xDBED, 0x51E1, 0xDBEE, 0x5E06, 0xDBEF, 0x68B5, 0xDBF0, 0x6C3E, 0xDBF1, 0x6C4E, 0xDBF2, 0x6CDB, + 0xDBF3, 0x72AF, 0xDBF4, 0x7BC4, 0xDBF5, 0x8303, 0xDBF6, 0x6CD5, 0xDBF7, 0x743A, 0xDBF8, 0x50FB, 0xDBF9, 0x5288, 0xDBFA, 0x58C1, + 0xDBFB, 0x64D8, 0xDBFC, 0x6A97, 0xDBFD, 0x74A7, 0xDBFE, 0x7656, 0xDCA1, 0x78A7, 0xDCA2, 0x8617, 0xDCA3, 0x95E2, 0xDCA4, 0x9739, + 0xDCA5, 0xF965, 0xDCA6, 0x535E, 0xDCA7, 0x5F01, 0xDCA8, 0x8B8A, 0xDCA9, 0x8FA8, 0xDCAA, 0x8FAF, 0xDCAB, 0x908A, 0xDCAC, 0x5225, + 0xDCAD, 0x77A5, 0xDCAE, 0x9C49, 0xDCAF, 0x9F08, 0xDCB0, 0x4E19, 0xDCB1, 0x5002, 0xDCB2, 0x5175, 0xDCB3, 0x5C5B, 0xDCB4, 0x5E77, + 0xDCB5, 0x661E, 0xDCB6, 0x663A, 0xDCB7, 0x67C4, 0xDCB8, 0x68C5, 0xDCB9, 0x70B3, 0xDCBA, 0x7501, 0xDCBB, 0x75C5, 0xDCBC, 0x79C9, + 0xDCBD, 0x7ADD, 0xDCBE, 0x8F27, 0xDCBF, 0x9920, 0xDCC0, 0x9A08, 0xDCC1, 0x4FDD, 0xDCC2, 0x5821, 0xDCC3, 0x5831, 0xDCC4, 0x5BF6, + 0xDCC5, 0x666E, 0xDCC6, 0x6B65, 0xDCC7, 0x6D11, 0xDCC8, 0x6E7A, 0xDCC9, 0x6F7D, 0xDCCA, 0x73E4, 0xDCCB, 0x752B, 0xDCCC, 0x83E9, + 0xDCCD, 0x88DC, 0xDCCE, 0x8913, 0xDCCF, 0x8B5C, 0xDCD0, 0x8F14, 0xDCD1, 0x4F0F, 0xDCD2, 0x50D5, 0xDCD3, 0x5310, 0xDCD4, 0x535C, + 0xDCD5, 0x5B93, 0xDCD6, 0x5FA9, 0xDCD7, 0x670D, 0xDCD8, 0x798F, 0xDCD9, 0x8179, 0xDCDA, 0x832F, 0xDCDB, 0x8514, 0xDCDC, 0x8907, + 0xDCDD, 0x8986, 0xDCDE, 0x8F39, 0xDCDF, 0x8F3B, 0xDCE0, 0x99A5, 0xDCE1, 0x9C12, 0xDCE2, 0x672C, 0xDCE3, 0x4E76, 0xDCE4, 0x4FF8, + 0xDCE5, 0x5949, 0xDCE6, 0x5C01, 0xDCE7, 0x5CEF, 0xDCE8, 0x5CF0, 0xDCE9, 0x6367, 0xDCEA, 0x68D2, 0xDCEB, 0x70FD, 0xDCEC, 0x71A2, + 0xDCED, 0x742B, 0xDCEE, 0x7E2B, 0xDCEF, 0x84EC, 0xDCF0, 0x8702, 0xDCF1, 0x9022, 0xDCF2, 0x92D2, 0xDCF3, 0x9CF3, 0xDCF4, 0x4E0D, + 0xDCF5, 0x4ED8, 0xDCF6, 0x4FEF, 0xDCF7, 0x5085, 0xDCF8, 0x5256, 0xDCF9, 0x526F, 0xDCFA, 0x5426, 0xDCFB, 0x5490, 0xDCFC, 0x57E0, + 0xDCFD, 0x592B, 0xDCFE, 0x5A66, 0xDDA1, 0x5B5A, 0xDDA2, 0x5B75, 0xDDA3, 0x5BCC, 0xDDA4, 0x5E9C, 0xDDA5, 0xF966, 0xDDA6, 0x6276, + 0xDDA7, 0x6577, 0xDDA8, 0x65A7, 0xDDA9, 0x6D6E, 0xDDAA, 0x6EA5, 0xDDAB, 0x7236, 0xDDAC, 0x7B26, 0xDDAD, 0x7C3F, 0xDDAE, 0x7F36, + 0xDDAF, 0x8150, 0xDDB0, 0x8151, 0xDDB1, 0x819A, 0xDDB2, 0x8240, 0xDDB3, 0x8299, 0xDDB4, 0x83A9, 0xDDB5, 0x8A03, 0xDDB6, 0x8CA0, + 0xDDB7, 0x8CE6, 0xDDB8, 0x8CFB, 0xDDB9, 0x8D74, 0xDDBA, 0x8DBA, 0xDDBB, 0x90E8, 0xDDBC, 0x91DC, 0xDDBD, 0x961C, 0xDDBE, 0x9644, + 0xDDBF, 0x99D9, 0xDDC0, 0x9CE7, 0xDDC1, 0x5317, 0xDDC2, 0x5206, 0xDDC3, 0x5429, 0xDDC4, 0x5674, 0xDDC5, 0x58B3, 0xDDC6, 0x5954, + 0xDDC7, 0x596E, 0xDDC8, 0x5FFF, 0xDDC9, 0x61A4, 0xDDCA, 0x626E, 0xDDCB, 0x6610, 0xDDCC, 0x6C7E, 0xDDCD, 0x711A, 0xDDCE, 0x76C6, + 0xDDCF, 0x7C89, 0xDDD0, 0x7CDE, 0xDDD1, 0x7D1B, 0xDDD2, 0x82AC, 0xDDD3, 0x8CC1, 0xDDD4, 0x96F0, 0xDDD5, 0xF967, 0xDDD6, 0x4F5B, + 0xDDD7, 0x5F17, 0xDDD8, 0x5F7F, 0xDDD9, 0x62C2, 0xDDDA, 0x5D29, 0xDDDB, 0x670B, 0xDDDC, 0x68DA, 0xDDDD, 0x787C, 0xDDDE, 0x7E43, + 0xDDDF, 0x9D6C, 0xDDE0, 0x4E15, 0xDDE1, 0x5099, 0xDDE2, 0x5315, 0xDDE3, 0x532A, 0xDDE4, 0x5351, 0xDDE5, 0x5983, 0xDDE6, 0x5A62, + 0xDDE7, 0x5E87, 0xDDE8, 0x60B2, 0xDDE9, 0x618A, 0xDDEA, 0x6249, 0xDDEB, 0x6279, 0xDDEC, 0x6590, 0xDDED, 0x6787, 0xDDEE, 0x69A7, + 0xDDEF, 0x6BD4, 0xDDF0, 0x6BD6, 0xDDF1, 0x6BD7, 0xDDF2, 0x6BD8, 0xDDF3, 0x6CB8, 0xDDF4, 0xF968, 0xDDF5, 0x7435, 0xDDF6, 0x75FA, + 0xDDF7, 0x7812, 0xDDF8, 0x7891, 0xDDF9, 0x79D5, 0xDDFA, 0x79D8, 0xDDFB, 0x7C83, 0xDDFC, 0x7DCB, 0xDDFD, 0x7FE1, 0xDDFE, 0x80A5, + 0xDEA1, 0x813E, 0xDEA2, 0x81C2, 0xDEA3, 0x83F2, 0xDEA4, 0x871A, 0xDEA5, 0x88E8, 0xDEA6, 0x8AB9, 0xDEA7, 0x8B6C, 0xDEA8, 0x8CBB, + 0xDEA9, 0x9119, 0xDEAA, 0x975E, 0xDEAB, 0x98DB, 0xDEAC, 0x9F3B, 0xDEAD, 0x56AC, 0xDEAE, 0x5B2A, 0xDEAF, 0x5F6C, 0xDEB0, 0x658C, + 0xDEB1, 0x6AB3, 0xDEB2, 0x6BAF, 0xDEB3, 0x6D5C, 0xDEB4, 0x6FF1, 0xDEB5, 0x7015, 0xDEB6, 0x725D, 0xDEB7, 0x73AD, 0xDEB8, 0x8CA7, + 0xDEB9, 0x8CD3, 0xDEBA, 0x983B, 0xDEBB, 0x6191, 0xDEBC, 0x6C37, 0xDEBD, 0x8058, 0xDEBE, 0x9A01, 0xDEBF, 0x4E4D, 0xDEC0, 0x4E8B, + 0xDEC1, 0x4E9B, 0xDEC2, 0x4ED5, 0xDEC3, 0x4F3A, 0xDEC4, 0x4F3C, 0xDEC5, 0x4F7F, 0xDEC6, 0x4FDF, 0xDEC7, 0x50FF, 0xDEC8, 0x53F2, + 0xDEC9, 0x53F8, 0xDECA, 0x5506, 0xDECB, 0x55E3, 0xDECC, 0x56DB, 0xDECD, 0x58EB, 0xDECE, 0x5962, 0xDECF, 0x5A11, 0xDED0, 0x5BEB, + 0xDED1, 0x5BFA, 0xDED2, 0x5C04, 0xDED3, 0x5DF3, 0xDED4, 0x5E2B, 0xDED5, 0x5F99, 0xDED6, 0x601D, 0xDED7, 0x6368, 0xDED8, 0x659C, + 0xDED9, 0x65AF, 0xDEDA, 0x67F6, 0xDEDB, 0x67FB, 0xDEDC, 0x68AD, 0xDEDD, 0x6B7B, 0xDEDE, 0x6C99, 0xDEDF, 0x6CD7, 0xDEE0, 0x6E23, + 0xDEE1, 0x7009, 0xDEE2, 0x7345, 0xDEE3, 0x7802, 0xDEE4, 0x793E, 0xDEE5, 0x7940, 0xDEE6, 0x7960, 0xDEE7, 0x79C1, 0xDEE8, 0x7BE9, + 0xDEE9, 0x7D17, 0xDEEA, 0x7D72, 0xDEEB, 0x8086, 0xDEEC, 0x820D, 0xDEED, 0x838E, 0xDEEE, 0x84D1, 0xDEEF, 0x86C7, 0xDEF0, 0x88DF, + 0xDEF1, 0x8A50, 0xDEF2, 0x8A5E, 0xDEF3, 0x8B1D, 0xDEF4, 0x8CDC, 0xDEF5, 0x8D66, 0xDEF6, 0x8FAD, 0xDEF7, 0x90AA, 0xDEF8, 0x98FC, + 0xDEF9, 0x99DF, 0xDEFA, 0x9E9D, 0xDEFB, 0x524A, 0xDEFC, 0xF969, 0xDEFD, 0x6714, 0xDEFE, 0xF96A, 0xDFA1, 0x5098, 0xDFA2, 0x522A, + 0xDFA3, 0x5C71, 0xDFA4, 0x6563, 0xDFA5, 0x6C55, 0xDFA6, 0x73CA, 0xDFA7, 0x7523, 0xDFA8, 0x759D, 0xDFA9, 0x7B97, 0xDFAA, 0x849C, + 0xDFAB, 0x9178, 0xDFAC, 0x9730, 0xDFAD, 0x4E77, 0xDFAE, 0x6492, 0xDFAF, 0x6BBA, 0xDFB0, 0x715E, 0xDFB1, 0x85A9, 0xDFB2, 0x4E09, + 0xDFB3, 0xF96B, 0xDFB4, 0x6749, 0xDFB5, 0x68EE, 0xDFB6, 0x6E17, 0xDFB7, 0x829F, 0xDFB8, 0x8518, 0xDFB9, 0x886B, 0xDFBA, 0x63F7, + 0xDFBB, 0x6F81, 0xDFBC, 0x9212, 0xDFBD, 0x98AF, 0xDFBE, 0x4E0A, 0xDFBF, 0x50B7, 0xDFC0, 0x50CF, 0xDFC1, 0x511F, 0xDFC2, 0x5546, + 0xDFC3, 0x55AA, 0xDFC4, 0x5617, 0xDFC5, 0x5B40, 0xDFC6, 0x5C19, 0xDFC7, 0x5CE0, 0xDFC8, 0x5E38, 0xDFC9, 0x5E8A, 0xDFCA, 0x5EA0, + 0xDFCB, 0x5EC2, 0xDFCC, 0x60F3, 0xDFCD, 0x6851, 0xDFCE, 0x6A61, 0xDFCF, 0x6E58, 0xDFD0, 0x723D, 0xDFD1, 0x7240, 0xDFD2, 0x72C0, + 0xDFD3, 0x76F8, 0xDFD4, 0x7965, 0xDFD5, 0x7BB1, 0xDFD6, 0x7FD4, 0xDFD7, 0x88F3, 0xDFD8, 0x89F4, 0xDFD9, 0x8A73, 0xDFDA, 0x8C61, + 0xDFDB, 0x8CDE, 0xDFDC, 0x971C, 0xDFDD, 0x585E, 0xDFDE, 0x74BD, 0xDFDF, 0x8CFD, 0xDFE0, 0x55C7, 0xDFE1, 0xF96C, 0xDFE2, 0x7A61, + 0xDFE3, 0x7D22, 0xDFE4, 0x8272, 0xDFE5, 0x7272, 0xDFE6, 0x751F, 0xDFE7, 0x7525, 0xDFE8, 0xF96D, 0xDFE9, 0x7B19, 0xDFEA, 0x5885, + 0xDFEB, 0x58FB, 0xDFEC, 0x5DBC, 0xDFED, 0x5E8F, 0xDFEE, 0x5EB6, 0xDFEF, 0x5F90, 0xDFF0, 0x6055, 0xDFF1, 0x6292, 0xDFF2, 0x637F, + 0xDFF3, 0x654D, 0xDFF4, 0x6691, 0xDFF5, 0x66D9, 0xDFF6, 0x66F8, 0xDFF7, 0x6816, 0xDFF8, 0x68F2, 0xDFF9, 0x7280, 0xDFFA, 0x745E, + 0xDFFB, 0x7B6E, 0xDFFC, 0x7D6E, 0xDFFD, 0x7DD6, 0xDFFE, 0x7F72, 0xE0A1, 0x80E5, 0xE0A2, 0x8212, 0xE0A3, 0x85AF, 0xE0A4, 0x897F, + 0xE0A5, 0x8A93, 0xE0A6, 0x901D, 0xE0A7, 0x92E4, 0xE0A8, 0x9ECD, 0xE0A9, 0x9F20, 0xE0AA, 0x5915, 0xE0AB, 0x596D, 0xE0AC, 0x5E2D, + 0xE0AD, 0x60DC, 0xE0AE, 0x6614, 0xE0AF, 0x6673, 0xE0B0, 0x6790, 0xE0B1, 0x6C50, 0xE0B2, 0x6DC5, 0xE0B3, 0x6F5F, 0xE0B4, 0x77F3, + 0xE0B5, 0x78A9, 0xE0B6, 0x84C6, 0xE0B7, 0x91CB, 0xE0B8, 0x932B, 0xE0B9, 0x4ED9, 0xE0BA, 0x50CA, 0xE0BB, 0x5148, 0xE0BC, 0x5584, + 0xE0BD, 0x5B0B, 0xE0BE, 0x5BA3, 0xE0BF, 0x6247, 0xE0C0, 0x657E, 0xE0C1, 0x65CB, 0xE0C2, 0x6E32, 0xE0C3, 0x717D, 0xE0C4, 0x7401, + 0xE0C5, 0x7444, 0xE0C6, 0x7487, 0xE0C7, 0x74BF, 0xE0C8, 0x766C, 0xE0C9, 0x79AA, 0xE0CA, 0x7DDA, 0xE0CB, 0x7E55, 0xE0CC, 0x7FA8, + 0xE0CD, 0x817A, 0xE0CE, 0x81B3, 0xE0CF, 0x8239, 0xE0D0, 0x861A, 0xE0D1, 0x87EC, 0xE0D2, 0x8A75, 0xE0D3, 0x8DE3, 0xE0D4, 0x9078, + 0xE0D5, 0x9291, 0xE0D6, 0x9425, 0xE0D7, 0x994D, 0xE0D8, 0x9BAE, 0xE0D9, 0x5368, 0xE0DA, 0x5C51, 0xE0DB, 0x6954, 0xE0DC, 0x6CC4, + 0xE0DD, 0x6D29, 0xE0DE, 0x6E2B, 0xE0DF, 0x820C, 0xE0E0, 0x859B, 0xE0E1, 0x893B, 0xE0E2, 0x8A2D, 0xE0E3, 0x8AAA, 0xE0E4, 0x96EA, + 0xE0E5, 0x9F67, 0xE0E6, 0x5261, 0xE0E7, 0x66B9, 0xE0E8, 0x6BB2, 0xE0E9, 0x7E96, 0xE0EA, 0x87FE, 0xE0EB, 0x8D0D, 0xE0EC, 0x9583, + 0xE0ED, 0x965D, 0xE0EE, 0x651D, 0xE0EF, 0x6D89, 0xE0F0, 0x71EE, 0xE0F1, 0xF96E, 0xE0F2, 0x57CE, 0xE0F3, 0x59D3, 0xE0F4, 0x5BAC, + 0xE0F5, 0x6027, 0xE0F6, 0x60FA, 0xE0F7, 0x6210, 0xE0F8, 0x661F, 0xE0F9, 0x665F, 0xE0FA, 0x7329, 0xE0FB, 0x73F9, 0xE0FC, 0x76DB, + 0xE0FD, 0x7701, 0xE0FE, 0x7B6C, 0xE1A1, 0x8056, 0xE1A2, 0x8072, 0xE1A3, 0x8165, 0xE1A4, 0x8AA0, 0xE1A5, 0x9192, 0xE1A6, 0x4E16, + 0xE1A7, 0x52E2, 0xE1A8, 0x6B72, 0xE1A9, 0x6D17, 0xE1AA, 0x7A05, 0xE1AB, 0x7B39, 0xE1AC, 0x7D30, 0xE1AD, 0xF96F, 0xE1AE, 0x8CB0, + 0xE1AF, 0x53EC, 0xE1B0, 0x562F, 0xE1B1, 0x5851, 0xE1B2, 0x5BB5, 0xE1B3, 0x5C0F, 0xE1B4, 0x5C11, 0xE1B5, 0x5DE2, 0xE1B6, 0x6240, + 0xE1B7, 0x6383, 0xE1B8, 0x6414, 0xE1B9, 0x662D, 0xE1BA, 0x68B3, 0xE1BB, 0x6CBC, 0xE1BC, 0x6D88, 0xE1BD, 0x6EAF, 0xE1BE, 0x701F, + 0xE1BF, 0x70A4, 0xE1C0, 0x71D2, 0xE1C1, 0x7526, 0xE1C2, 0x758F, 0xE1C3, 0x758E, 0xE1C4, 0x7619, 0xE1C5, 0x7B11, 0xE1C6, 0x7BE0, + 0xE1C7, 0x7C2B, 0xE1C8, 0x7D20, 0xE1C9, 0x7D39, 0xE1CA, 0x852C, 0xE1CB, 0x856D, 0xE1CC, 0x8607, 0xE1CD, 0x8A34, 0xE1CE, 0x900D, + 0xE1CF, 0x9061, 0xE1D0, 0x90B5, 0xE1D1, 0x92B7, 0xE1D2, 0x97F6, 0xE1D3, 0x9A37, 0xE1D4, 0x4FD7, 0xE1D5, 0x5C6C, 0xE1D6, 0x675F, + 0xE1D7, 0x6D91, 0xE1D8, 0x7C9F, 0xE1D9, 0x7E8C, 0xE1DA, 0x8B16, 0xE1DB, 0x8D16, 0xE1DC, 0x901F, 0xE1DD, 0x5B6B, 0xE1DE, 0x5DFD, + 0xE1DF, 0x640D, 0xE1E0, 0x84C0, 0xE1E1, 0x905C, 0xE1E2, 0x98E1, 0xE1E3, 0x7387, 0xE1E4, 0x5B8B, 0xE1E5, 0x609A, 0xE1E6, 0x677E, + 0xE1E7, 0x6DDE, 0xE1E8, 0x8A1F, 0xE1E9, 0x8AA6, 0xE1EA, 0x9001, 0xE1EB, 0x980C, 0xE1EC, 0x5237, 0xE1ED, 0xF970, 0xE1EE, 0x7051, + 0xE1EF, 0x788E, 0xE1F0, 0x9396, 0xE1F1, 0x8870, 0xE1F2, 0x91D7, 0xE1F3, 0x4FEE, 0xE1F4, 0x53D7, 0xE1F5, 0x55FD, 0xE1F6, 0x56DA, + 0xE1F7, 0x5782, 0xE1F8, 0x58FD, 0xE1F9, 0x5AC2, 0xE1FA, 0x5B88, 0xE1FB, 0x5CAB, 0xE1FC, 0x5CC0, 0xE1FD, 0x5E25, 0xE1FE, 0x6101, + 0xE2A1, 0x620D, 0xE2A2, 0x624B, 0xE2A3, 0x6388, 0xE2A4, 0x641C, 0xE2A5, 0x6536, 0xE2A6, 0x6578, 0xE2A7, 0x6A39, 0xE2A8, 0x6B8A, + 0xE2A9, 0x6C34, 0xE2AA, 0x6D19, 0xE2AB, 0x6F31, 0xE2AC, 0x71E7, 0xE2AD, 0x72E9, 0xE2AE, 0x7378, 0xE2AF, 0x7407, 0xE2B0, 0x74B2, + 0xE2B1, 0x7626, 0xE2B2, 0x7761, 0xE2B3, 0x79C0, 0xE2B4, 0x7A57, 0xE2B5, 0x7AEA, 0xE2B6, 0x7CB9, 0xE2B7, 0x7D8F, 0xE2B8, 0x7DAC, + 0xE2B9, 0x7E61, 0xE2BA, 0x7F9E, 0xE2BB, 0x8129, 0xE2BC, 0x8331, 0xE2BD, 0x8490, 0xE2BE, 0x84DA, 0xE2BF, 0x85EA, 0xE2C0, 0x8896, + 0xE2C1, 0x8AB0, 0xE2C2, 0x8B90, 0xE2C3, 0x8F38, 0xE2C4, 0x9042, 0xE2C5, 0x9083, 0xE2C6, 0x916C, 0xE2C7, 0x9296, 0xE2C8, 0x92B9, + 0xE2C9, 0x968B, 0xE2CA, 0x96A7, 0xE2CB, 0x96A8, 0xE2CC, 0x96D6, 0xE2CD, 0x9700, 0xE2CE, 0x9808, 0xE2CF, 0x9996, 0xE2D0, 0x9AD3, + 0xE2D1, 0x9B1A, 0xE2D2, 0x53D4, 0xE2D3, 0x587E, 0xE2D4, 0x5919, 0xE2D5, 0x5B70, 0xE2D6, 0x5BBF, 0xE2D7, 0x6DD1, 0xE2D8, 0x6F5A, + 0xE2D9, 0x719F, 0xE2DA, 0x7421, 0xE2DB, 0x74B9, 0xE2DC, 0x8085, 0xE2DD, 0x83FD, 0xE2DE, 0x5DE1, 0xE2DF, 0x5F87, 0xE2E0, 0x5FAA, + 0xE2E1, 0x6042, 0xE2E2, 0x65EC, 0xE2E3, 0x6812, 0xE2E4, 0x696F, 0xE2E5, 0x6A53, 0xE2E6, 0x6B89, 0xE2E7, 0x6D35, 0xE2E8, 0x6DF3, + 0xE2E9, 0x73E3, 0xE2EA, 0x76FE, 0xE2EB, 0x77AC, 0xE2EC, 0x7B4D, 0xE2ED, 0x7D14, 0xE2EE, 0x8123, 0xE2EF, 0x821C, 0xE2F0, 0x8340, + 0xE2F1, 0x84F4, 0xE2F2, 0x8563, 0xE2F3, 0x8A62, 0xE2F4, 0x8AC4, 0xE2F5, 0x9187, 0xE2F6, 0x931E, 0xE2F7, 0x9806, 0xE2F8, 0x99B4, + 0xE2F9, 0x620C, 0xE2FA, 0x8853, 0xE2FB, 0x8FF0, 0xE2FC, 0x9265, 0xE2FD, 0x5D07, 0xE2FE, 0x5D27, 0xE3A1, 0x5D69, 0xE3A2, 0x745F, + 0xE3A3, 0x819D, 0xE3A4, 0x8768, 0xE3A5, 0x6FD5, 0xE3A6, 0x62FE, 0xE3A7, 0x7FD2, 0xE3A8, 0x8936, 0xE3A9, 0x8972, 0xE3AA, 0x4E1E, + 0xE3AB, 0x4E58, 0xE3AC, 0x50E7, 0xE3AD, 0x52DD, 0xE3AE, 0x5347, 0xE3AF, 0x627F, 0xE3B0, 0x6607, 0xE3B1, 0x7E69, 0xE3B2, 0x8805, + 0xE3B3, 0x965E, 0xE3B4, 0x4F8D, 0xE3B5, 0x5319, 0xE3B6, 0x5636, 0xE3B7, 0x59CB, 0xE3B8, 0x5AA4, 0xE3B9, 0x5C38, 0xE3BA, 0x5C4E, + 0xE3BB, 0x5C4D, 0xE3BC, 0x5E02, 0xE3BD, 0x5F11, 0xE3BE, 0x6043, 0xE3BF, 0x65BD, 0xE3C0, 0x662F, 0xE3C1, 0x6642, 0xE3C2, 0x67BE, + 0xE3C3, 0x67F4, 0xE3C4, 0x731C, 0xE3C5, 0x77E2, 0xE3C6, 0x793A, 0xE3C7, 0x7FC5, 0xE3C8, 0x8494, 0xE3C9, 0x84CD, 0xE3CA, 0x8996, + 0xE3CB, 0x8A66, 0xE3CC, 0x8A69, 0xE3CD, 0x8AE1, 0xE3CE, 0x8C55, 0xE3CF, 0x8C7A, 0xE3D0, 0x57F4, 0xE3D1, 0x5BD4, 0xE3D2, 0x5F0F, + 0xE3D3, 0x606F, 0xE3D4, 0x62ED, 0xE3D5, 0x690D, 0xE3D6, 0x6B96, 0xE3D7, 0x6E5C, 0xE3D8, 0x7184, 0xE3D9, 0x7BD2, 0xE3DA, 0x8755, + 0xE3DB, 0x8B58, 0xE3DC, 0x8EFE, 0xE3DD, 0x98DF, 0xE3DE, 0x98FE, 0xE3DF, 0x4F38, 0xE3E0, 0x4F81, 0xE3E1, 0x4FE1, 0xE3E2, 0x547B, + 0xE3E3, 0x5A20, 0xE3E4, 0x5BB8, 0xE3E5, 0x613C, 0xE3E6, 0x65B0, 0xE3E7, 0x6668, 0xE3E8, 0x71FC, 0xE3E9, 0x7533, 0xE3EA, 0x795E, + 0xE3EB, 0x7D33, 0xE3EC, 0x814E, 0xE3ED, 0x81E3, 0xE3EE, 0x8398, 0xE3EF, 0x85AA, 0xE3F0, 0x85CE, 0xE3F1, 0x8703, 0xE3F2, 0x8A0A, + 0xE3F3, 0x8EAB, 0xE3F4, 0x8F9B, 0xE3F5, 0xF971, 0xE3F6, 0x8FC5, 0xE3F7, 0x5931, 0xE3F8, 0x5BA4, 0xE3F9, 0x5BE6, 0xE3FA, 0x6089, + 0xE3FB, 0x5BE9, 0xE3FC, 0x5C0B, 0xE3FD, 0x5FC3, 0xE3FE, 0x6C81, 0xE4A1, 0xF972, 0xE4A2, 0x6DF1, 0xE4A3, 0x700B, 0xE4A4, 0x751A, + 0xE4A5, 0x82AF, 0xE4A6, 0x8AF6, 0xE4A7, 0x4EC0, 0xE4A8, 0x5341, 0xE4A9, 0xF973, 0xE4AA, 0x96D9, 0xE4AB, 0x6C0F, 0xE4AC, 0x4E9E, + 0xE4AD, 0x4FC4, 0xE4AE, 0x5152, 0xE4AF, 0x555E, 0xE4B0, 0x5A25, 0xE4B1, 0x5CE8, 0xE4B2, 0x6211, 0xE4B3, 0x7259, 0xE4B4, 0x82BD, + 0xE4B5, 0x83AA, 0xE4B6, 0x86FE, 0xE4B7, 0x8859, 0xE4B8, 0x8A1D, 0xE4B9, 0x963F, 0xE4BA, 0x96C5, 0xE4BB, 0x9913, 0xE4BC, 0x9D09, + 0xE4BD, 0x9D5D, 0xE4BE, 0x580A, 0xE4BF, 0x5CB3, 0xE4C0, 0x5DBD, 0xE4C1, 0x5E44, 0xE4C2, 0x60E1, 0xE4C3, 0x6115, 0xE4C4, 0x63E1, + 0xE4C5, 0x6A02, 0xE4C6, 0x6E25, 0xE4C7, 0x9102, 0xE4C8, 0x9354, 0xE4C9, 0x984E, 0xE4CA, 0x9C10, 0xE4CB, 0x9F77, 0xE4CC, 0x5B89, + 0xE4CD, 0x5CB8, 0xE4CE, 0x6309, 0xE4CF, 0x664F, 0xE4D0, 0x6848, 0xE4D1, 0x773C, 0xE4D2, 0x96C1, 0xE4D3, 0x978D, 0xE4D4, 0x9854, + 0xE4D5, 0x9B9F, 0xE4D6, 0x65A1, 0xE4D7, 0x8B01, 0xE4D8, 0x8ECB, 0xE4D9, 0x95BC, 0xE4DA, 0x5535, 0xE4DB, 0x5CA9, 0xE4DC, 0x5DD6, + 0xE4DD, 0x5EB5, 0xE4DE, 0x6697, 0xE4DF, 0x764C, 0xE4E0, 0x83F4, 0xE4E1, 0x95C7, 0xE4E2, 0x58D3, 0xE4E3, 0x62BC, 0xE4E4, 0x72CE, + 0xE4E5, 0x9D28, 0xE4E6, 0x4EF0, 0xE4E7, 0x592E, 0xE4E8, 0x600F, 0xE4E9, 0x663B, 0xE4EA, 0x6B83, 0xE4EB, 0x79E7, 0xE4EC, 0x9D26, + 0xE4ED, 0x5393, 0xE4EE, 0x54C0, 0xE4EF, 0x57C3, 0xE4F0, 0x5D16, 0xE4F1, 0x611B, 0xE4F2, 0x66D6, 0xE4F3, 0x6DAF, 0xE4F4, 0x788D, + 0xE4F5, 0x827E, 0xE4F6, 0x9698, 0xE4F7, 0x9744, 0xE4F8, 0x5384, 0xE4F9, 0x627C, 0xE4FA, 0x6396, 0xE4FB, 0x6DB2, 0xE4FC, 0x7E0A, + 0xE4FD, 0x814B, 0xE4FE, 0x984D, 0xE5A1, 0x6AFB, 0xE5A2, 0x7F4C, 0xE5A3, 0x9DAF, 0xE5A4, 0x9E1A, 0xE5A5, 0x4E5F, 0xE5A6, 0x503B, + 0xE5A7, 0x51B6, 0xE5A8, 0x591C, 0xE5A9, 0x60F9, 0xE5AA, 0x63F6, 0xE5AB, 0x6930, 0xE5AC, 0x723A, 0xE5AD, 0x8036, 0xE5AE, 0xF974, + 0xE5AF, 0x91CE, 0xE5B0, 0x5F31, 0xE5B1, 0xF975, 0xE5B2, 0xF976, 0xE5B3, 0x7D04, 0xE5B4, 0x82E5, 0xE5B5, 0x846F, 0xE5B6, 0x84BB, + 0xE5B7, 0x85E5, 0xE5B8, 0x8E8D, 0xE5B9, 0xF977, 0xE5BA, 0x4F6F, 0xE5BB, 0xF978, 0xE5BC, 0xF979, 0xE5BD, 0x58E4, 0xE5BE, 0x5B43, + 0xE5BF, 0x6059, 0xE5C0, 0x63DA, 0xE5C1, 0x6518, 0xE5C2, 0x656D, 0xE5C3, 0x6698, 0xE5C4, 0xF97A, 0xE5C5, 0x694A, 0xE5C6, 0x6A23, + 0xE5C7, 0x6D0B, 0xE5C8, 0x7001, 0xE5C9, 0x716C, 0xE5CA, 0x75D2, 0xE5CB, 0x760D, 0xE5CC, 0x79B3, 0xE5CD, 0x7A70, 0xE5CE, 0xF97B, + 0xE5CF, 0x7F8A, 0xE5D0, 0xF97C, 0xE5D1, 0x8944, 0xE5D2, 0xF97D, 0xE5D3, 0x8B93, 0xE5D4, 0x91C0, 0xE5D5, 0x967D, 0xE5D6, 0xF97E, + 0xE5D7, 0x990A, 0xE5D8, 0x5704, 0xE5D9, 0x5FA1, 0xE5DA, 0x65BC, 0xE5DB, 0x6F01, 0xE5DC, 0x7600, 0xE5DD, 0x79A6, 0xE5DE, 0x8A9E, + 0xE5DF, 0x99AD, 0xE5E0, 0x9B5A, 0xE5E1, 0x9F6C, 0xE5E2, 0x5104, 0xE5E3, 0x61B6, 0xE5E4, 0x6291, 0xE5E5, 0x6A8D, 0xE5E6, 0x81C6, + 0xE5E7, 0x5043, 0xE5E8, 0x5830, 0xE5E9, 0x5F66, 0xE5EA, 0x7109, 0xE5EB, 0x8A00, 0xE5EC, 0x8AFA, 0xE5ED, 0x5B7C, 0xE5EE, 0x8616, + 0xE5EF, 0x4FFA, 0xE5F0, 0x513C, 0xE5F1, 0x56B4, 0xE5F2, 0x5944, 0xE5F3, 0x63A9, 0xE5F4, 0x6DF9, 0xE5F5, 0x5DAA, 0xE5F6, 0x696D, + 0xE5F7, 0x5186, 0xE5F8, 0x4E88, 0xE5F9, 0x4F59, 0xE5FA, 0xF97F, 0xE5FB, 0xF980, 0xE5FC, 0xF981, 0xE5FD, 0x5982, 0xE5FE, 0xF982, + 0xE6A1, 0xF983, 0xE6A2, 0x6B5F, 0xE6A3, 0x6C5D, 0xE6A4, 0xF984, 0xE6A5, 0x74B5, 0xE6A6, 0x7916, 0xE6A7, 0xF985, 0xE6A8, 0x8207, + 0xE6A9, 0x8245, 0xE6AA, 0x8339, 0xE6AB, 0x8F3F, 0xE6AC, 0x8F5D, 0xE6AD, 0xF986, 0xE6AE, 0x9918, 0xE6AF, 0xF987, 0xE6B0, 0xF988, + 0xE6B1, 0xF989, 0xE6B2, 0x4EA6, 0xE6B3, 0xF98A, 0xE6B4, 0x57DF, 0xE6B5, 0x5F79, 0xE6B6, 0x6613, 0xE6B7, 0xF98B, 0xE6B8, 0xF98C, + 0xE6B9, 0x75AB, 0xE6BA, 0x7E79, 0xE6BB, 0x8B6F, 0xE6BC, 0xF98D, 0xE6BD, 0x9006, 0xE6BE, 0x9A5B, 0xE6BF, 0x56A5, 0xE6C0, 0x5827, + 0xE6C1, 0x59F8, 0xE6C2, 0x5A1F, 0xE6C3, 0x5BB4, 0xE6C4, 0xF98E, 0xE6C5, 0x5EF6, 0xE6C6, 0xF98F, 0xE6C7, 0xF990, 0xE6C8, 0x6350, + 0xE6C9, 0x633B, 0xE6CA, 0xF991, 0xE6CB, 0x693D, 0xE6CC, 0x6C87, 0xE6CD, 0x6CBF, 0xE6CE, 0x6D8E, 0xE6CF, 0x6D93, 0xE6D0, 0x6DF5, + 0xE6D1, 0x6F14, 0xE6D2, 0xF992, 0xE6D3, 0x70DF, 0xE6D4, 0x7136, 0xE6D5, 0x7159, 0xE6D6, 0xF993, 0xE6D7, 0x71C3, 0xE6D8, 0x71D5, + 0xE6D9, 0xF994, 0xE6DA, 0x784F, 0xE6DB, 0x786F, 0xE6DC, 0xF995, 0xE6DD, 0x7B75, 0xE6DE, 0x7DE3, 0xE6DF, 0xF996, 0xE6E0, 0x7E2F, + 0xE6E1, 0xF997, 0xE6E2, 0x884D, 0xE6E3, 0x8EDF, 0xE6E4, 0xF998, 0xE6E5, 0xF999, 0xE6E6, 0xF99A, 0xE6E7, 0x925B, 0xE6E8, 0xF99B, + 0xE6E9, 0x9CF6, 0xE6EA, 0xF99C, 0xE6EB, 0xF99D, 0xE6EC, 0xF99E, 0xE6ED, 0x6085, 0xE6EE, 0x6D85, 0xE6EF, 0xF99F, 0xE6F0, 0x71B1, + 0xE6F1, 0xF9A0, 0xE6F2, 0xF9A1, 0xE6F3, 0x95B1, 0xE6F4, 0x53AD, 0xE6F5, 0xF9A2, 0xE6F6, 0xF9A3, 0xE6F7, 0xF9A4, 0xE6F8, 0x67D3, + 0xE6F9, 0xF9A5, 0xE6FA, 0x708E, 0xE6FB, 0x7130, 0xE6FC, 0x7430, 0xE6FD, 0x8276, 0xE6FE, 0x82D2, 0xE7A1, 0xF9A6, 0xE7A2, 0x95BB, + 0xE7A3, 0x9AE5, 0xE7A4, 0x9E7D, 0xE7A5, 0x66C4, 0xE7A6, 0xF9A7, 0xE7A7, 0x71C1, 0xE7A8, 0x8449, 0xE7A9, 0xF9A8, 0xE7AA, 0xF9A9, + 0xE7AB, 0x584B, 0xE7AC, 0xF9AA, 0xE7AD, 0xF9AB, 0xE7AE, 0x5DB8, 0xE7AF, 0x5F71, 0xE7B0, 0xF9AC, 0xE7B1, 0x6620, 0xE7B2, 0x668E, + 0xE7B3, 0x6979, 0xE7B4, 0x69AE, 0xE7B5, 0x6C38, 0xE7B6, 0x6CF3, 0xE7B7, 0x6E36, 0xE7B8, 0x6F41, 0xE7B9, 0x6FDA, 0xE7BA, 0x701B, + 0xE7BB, 0x702F, 0xE7BC, 0x7150, 0xE7BD, 0x71DF, 0xE7BE, 0x7370, 0xE7BF, 0xF9AD, 0xE7C0, 0x745B, 0xE7C1, 0xF9AE, 0xE7C2, 0x74D4, + 0xE7C3, 0x76C8, 0xE7C4, 0x7A4E, 0xE7C5, 0x7E93, 0xE7C6, 0xF9AF, 0xE7C7, 0xF9B0, 0xE7C8, 0x82F1, 0xE7C9, 0x8A60, 0xE7CA, 0x8FCE, + 0xE7CB, 0xF9B1, 0xE7CC, 0x9348, 0xE7CD, 0xF9B2, 0xE7CE, 0x9719, 0xE7CF, 0xF9B3, 0xE7D0, 0xF9B4, 0xE7D1, 0x4E42, 0xE7D2, 0x502A, + 0xE7D3, 0xF9B5, 0xE7D4, 0x5208, 0xE7D5, 0x53E1, 0xE7D6, 0x66F3, 0xE7D7, 0x6C6D, 0xE7D8, 0x6FCA, 0xE7D9, 0x730A, 0xE7DA, 0x777F, + 0xE7DB, 0x7A62, 0xE7DC, 0x82AE, 0xE7DD, 0x85DD, 0xE7DE, 0x8602, 0xE7DF, 0xF9B6, 0xE7E0, 0x88D4, 0xE7E1, 0x8A63, 0xE7E2, 0x8B7D, + 0xE7E3, 0x8C6B, 0xE7E4, 0xF9B7, 0xE7E5, 0x92B3, 0xE7E6, 0xF9B8, 0xE7E7, 0x9713, 0xE7E8, 0x9810, 0xE7E9, 0x4E94, 0xE7EA, 0x4F0D, + 0xE7EB, 0x4FC9, 0xE7EC, 0x50B2, 0xE7ED, 0x5348, 0xE7EE, 0x543E, 0xE7EF, 0x5433, 0xE7F0, 0x55DA, 0xE7F1, 0x5862, 0xE7F2, 0x58BA, + 0xE7F3, 0x5967, 0xE7F4, 0x5A1B, 0xE7F5, 0x5BE4, 0xE7F6, 0x609F, 0xE7F7, 0xF9B9, 0xE7F8, 0x61CA, 0xE7F9, 0x6556, 0xE7FA, 0x65FF, + 0xE7FB, 0x6664, 0xE7FC, 0x68A7, 0xE7FD, 0x6C5A, 0xE7FE, 0x6FB3, 0xE8A1, 0x70CF, 0xE8A2, 0x71AC, 0xE8A3, 0x7352, 0xE8A4, 0x7B7D, + 0xE8A5, 0x8708, 0xE8A6, 0x8AA4, 0xE8A7, 0x9C32, 0xE8A8, 0x9F07, 0xE8A9, 0x5C4B, 0xE8AA, 0x6C83, 0xE8AB, 0x7344, 0xE8AC, 0x7389, + 0xE8AD, 0x923A, 0xE8AE, 0x6EAB, 0xE8AF, 0x7465, 0xE8B0, 0x761F, 0xE8B1, 0x7A69, 0xE8B2, 0x7E15, 0xE8B3, 0x860A, 0xE8B4, 0x5140, + 0xE8B5, 0x58C5, 0xE8B6, 0x64C1, 0xE8B7, 0x74EE, 0xE8B8, 0x7515, 0xE8B9, 0x7670, 0xE8BA, 0x7FC1, 0xE8BB, 0x9095, 0xE8BC, 0x96CD, + 0xE8BD, 0x9954, 0xE8BE, 0x6E26, 0xE8BF, 0x74E6, 0xE8C0, 0x7AA9, 0xE8C1, 0x7AAA, 0xE8C2, 0x81E5, 0xE8C3, 0x86D9, 0xE8C4, 0x8778, + 0xE8C5, 0x8A1B, 0xE8C6, 0x5A49, 0xE8C7, 0x5B8C, 0xE8C8, 0x5B9B, 0xE8C9, 0x68A1, 0xE8CA, 0x6900, 0xE8CB, 0x6D63, 0xE8CC, 0x73A9, + 0xE8CD, 0x7413, 0xE8CE, 0x742C, 0xE8CF, 0x7897, 0xE8D0, 0x7DE9, 0xE8D1, 0x7FEB, 0xE8D2, 0x8118, 0xE8D3, 0x8155, 0xE8D4, 0x839E, + 0xE8D5, 0x8C4C, 0xE8D6, 0x962E, 0xE8D7, 0x9811, 0xE8D8, 0x66F0, 0xE8D9, 0x5F80, 0xE8DA, 0x65FA, 0xE8DB, 0x6789, 0xE8DC, 0x6C6A, + 0xE8DD, 0x738B, 0xE8DE, 0x502D, 0xE8DF, 0x5A03, 0xE8E0, 0x6B6A, 0xE8E1, 0x77EE, 0xE8E2, 0x5916, 0xE8E3, 0x5D6C, 0xE8E4, 0x5DCD, + 0xE8E5, 0x7325, 0xE8E6, 0x754F, 0xE8E7, 0xF9BA, 0xE8E8, 0xF9BB, 0xE8E9, 0x50E5, 0xE8EA, 0x51F9, 0xE8EB, 0x582F, 0xE8EC, 0x592D, + 0xE8ED, 0x5996, 0xE8EE, 0x59DA, 0xE8EF, 0x5BE5, 0xE8F0, 0xF9BC, 0xE8F1, 0xF9BD, 0xE8F2, 0x5DA2, 0xE8F3, 0x62D7, 0xE8F4, 0x6416, + 0xE8F5, 0x6493, 0xE8F6, 0x64FE, 0xE8F7, 0xF9BE, 0xE8F8, 0x66DC, 0xE8F9, 0xF9BF, 0xE8FA, 0x6A48, 0xE8FB, 0xF9C0, 0xE8FC, 0x71FF, + 0xE8FD, 0x7464, 0xE8FE, 0xF9C1, 0xE9A1, 0x7A88, 0xE9A2, 0x7AAF, 0xE9A3, 0x7E47, 0xE9A4, 0x7E5E, 0xE9A5, 0x8000, 0xE9A6, 0x8170, + 0xE9A7, 0xF9C2, 0xE9A8, 0x87EF, 0xE9A9, 0x8981, 0xE9AA, 0x8B20, 0xE9AB, 0x9059, 0xE9AC, 0xF9C3, 0xE9AD, 0x9080, 0xE9AE, 0x9952, + 0xE9AF, 0x617E, 0xE9B0, 0x6B32, 0xE9B1, 0x6D74, 0xE9B2, 0x7E1F, 0xE9B3, 0x8925, 0xE9B4, 0x8FB1, 0xE9B5, 0x4FD1, 0xE9B6, 0x50AD, + 0xE9B7, 0x5197, 0xE9B8, 0x52C7, 0xE9B9, 0x57C7, 0xE9BA, 0x5889, 0xE9BB, 0x5BB9, 0xE9BC, 0x5EB8, 0xE9BD, 0x6142, 0xE9BE, 0x6995, + 0xE9BF, 0x6D8C, 0xE9C0, 0x6E67, 0xE9C1, 0x6EB6, 0xE9C2, 0x7194, 0xE9C3, 0x7462, 0xE9C4, 0x7528, 0xE9C5, 0x752C, 0xE9C6, 0x8073, + 0xE9C7, 0x8338, 0xE9C8, 0x84C9, 0xE9C9, 0x8E0A, 0xE9CA, 0x9394, 0xE9CB, 0x93DE, 0xE9CC, 0xF9C4, 0xE9CD, 0x4E8E, 0xE9CE, 0x4F51, + 0xE9CF, 0x5076, 0xE9D0, 0x512A, 0xE9D1, 0x53C8, 0xE9D2, 0x53CB, 0xE9D3, 0x53F3, 0xE9D4, 0x5B87, 0xE9D5, 0x5BD3, 0xE9D6, 0x5C24, + 0xE9D7, 0x611A, 0xE9D8, 0x6182, 0xE9D9, 0x65F4, 0xE9DA, 0x725B, 0xE9DB, 0x7397, 0xE9DC, 0x7440, 0xE9DD, 0x76C2, 0xE9DE, 0x7950, + 0xE9DF, 0x7991, 0xE9E0, 0x79B9, 0xE9E1, 0x7D06, 0xE9E2, 0x7FBD, 0xE9E3, 0x828B, 0xE9E4, 0x85D5, 0xE9E5, 0x865E, 0xE9E6, 0x8FC2, + 0xE9E7, 0x9047, 0xE9E8, 0x90F5, 0xE9E9, 0x91EA, 0xE9EA, 0x9685, 0xE9EB, 0x96E8, 0xE9EC, 0x96E9, 0xE9ED, 0x52D6, 0xE9EE, 0x5F67, + 0xE9EF, 0x65ED, 0xE9F0, 0x6631, 0xE9F1, 0x682F, 0xE9F2, 0x715C, 0xE9F3, 0x7A36, 0xE9F4, 0x90C1, 0xE9F5, 0x980A, 0xE9F6, 0x4E91, + 0xE9F7, 0xF9C5, 0xE9F8, 0x6A52, 0xE9F9, 0x6B9E, 0xE9FA, 0x6F90, 0xE9FB, 0x7189, 0xE9FC, 0x8018, 0xE9FD, 0x82B8, 0xE9FE, 0x8553, + 0xEAA1, 0x904B, 0xEAA2, 0x9695, 0xEAA3, 0x96F2, 0xEAA4, 0x97FB, 0xEAA5, 0x851A, 0xEAA6, 0x9B31, 0xEAA7, 0x4E90, 0xEAA8, 0x718A, + 0xEAA9, 0x96C4, 0xEAAA, 0x5143, 0xEAAB, 0x539F, 0xEAAC, 0x54E1, 0xEAAD, 0x5713, 0xEAAE, 0x5712, 0xEAAF, 0x57A3, 0xEAB0, 0x5A9B, + 0xEAB1, 0x5AC4, 0xEAB2, 0x5BC3, 0xEAB3, 0x6028, 0xEAB4, 0x613F, 0xEAB5, 0x63F4, 0xEAB6, 0x6C85, 0xEAB7, 0x6D39, 0xEAB8, 0x6E72, + 0xEAB9, 0x6E90, 0xEABA, 0x7230, 0xEABB, 0x733F, 0xEABC, 0x7457, 0xEABD, 0x82D1, 0xEABE, 0x8881, 0xEABF, 0x8F45, 0xEAC0, 0x9060, + 0xEAC1, 0xF9C6, 0xEAC2, 0x9662, 0xEAC3, 0x9858, 0xEAC4, 0x9D1B, 0xEAC5, 0x6708, 0xEAC6, 0x8D8A, 0xEAC7, 0x925E, 0xEAC8, 0x4F4D, + 0xEAC9, 0x5049, 0xEACA, 0x50DE, 0xEACB, 0x5371, 0xEACC, 0x570D, 0xEACD, 0x59D4, 0xEACE, 0x5A01, 0xEACF, 0x5C09, 0xEAD0, 0x6170, + 0xEAD1, 0x6690, 0xEAD2, 0x6E2D, 0xEAD3, 0x7232, 0xEAD4, 0x744B, 0xEAD5, 0x7DEF, 0xEAD6, 0x80C3, 0xEAD7, 0x840E, 0xEAD8, 0x8466, + 0xEAD9, 0x853F, 0xEADA, 0x875F, 0xEADB, 0x885B, 0xEADC, 0x8918, 0xEADD, 0x8B02, 0xEADE, 0x9055, 0xEADF, 0x97CB, 0xEAE0, 0x9B4F, + 0xEAE1, 0x4E73, 0xEAE2, 0x4F91, 0xEAE3, 0x5112, 0xEAE4, 0x516A, 0xEAE5, 0xF9C7, 0xEAE6, 0x552F, 0xEAE7, 0x55A9, 0xEAE8, 0x5B7A, + 0xEAE9, 0x5BA5, 0xEAEA, 0x5E7C, 0xEAEB, 0x5E7D, 0xEAEC, 0x5EBE, 0xEAED, 0x60A0, 0xEAEE, 0x60DF, 0xEAEF, 0x6108, 0xEAF0, 0x6109, + 0xEAF1, 0x63C4, 0xEAF2, 0x6538, 0xEAF3, 0x6709, 0xEAF4, 0xF9C8, 0xEAF5, 0x67D4, 0xEAF6, 0x67DA, 0xEAF7, 0xF9C9, 0xEAF8, 0x6961, + 0xEAF9, 0x6962, 0xEAFA, 0x6CB9, 0xEAFB, 0x6D27, 0xEAFC, 0xF9CA, 0xEAFD, 0x6E38, 0xEAFE, 0xF9CB, 0xEBA1, 0x6FE1, 0xEBA2, 0x7336, + 0xEBA3, 0x7337, 0xEBA4, 0xF9CC, 0xEBA5, 0x745C, 0xEBA6, 0x7531, 0xEBA7, 0xF9CD, 0xEBA8, 0x7652, 0xEBA9, 0xF9CE, 0xEBAA, 0xF9CF, + 0xEBAB, 0x7DAD, 0xEBAC, 0x81FE, 0xEBAD, 0x8438, 0xEBAE, 0x88D5, 0xEBAF, 0x8A98, 0xEBB0, 0x8ADB, 0xEBB1, 0x8AED, 0xEBB2, 0x8E30, + 0xEBB3, 0x8E42, 0xEBB4, 0x904A, 0xEBB5, 0x903E, 0xEBB6, 0x907A, 0xEBB7, 0x9149, 0xEBB8, 0x91C9, 0xEBB9, 0x936E, 0xEBBA, 0xF9D0, + 0xEBBB, 0xF9D1, 0xEBBC, 0x5809, 0xEBBD, 0xF9D2, 0xEBBE, 0x6BD3, 0xEBBF, 0x8089, 0xEBC0, 0x80B2, 0xEBC1, 0xF9D3, 0xEBC2, 0xF9D4, + 0xEBC3, 0x5141, 0xEBC4, 0x596B, 0xEBC5, 0x5C39, 0xEBC6, 0xF9D5, 0xEBC7, 0xF9D6, 0xEBC8, 0x6F64, 0xEBC9, 0x73A7, 0xEBCA, 0x80E4, + 0xEBCB, 0x8D07, 0xEBCC, 0xF9D7, 0xEBCD, 0x9217, 0xEBCE, 0x958F, 0xEBCF, 0xF9D8, 0xEBD0, 0xF9D9, 0xEBD1, 0xF9DA, 0xEBD2, 0xF9DB, + 0xEBD3, 0x807F, 0xEBD4, 0x620E, 0xEBD5, 0x701C, 0xEBD6, 0x7D68, 0xEBD7, 0x878D, 0xEBD8, 0xF9DC, 0xEBD9, 0x57A0, 0xEBDA, 0x6069, + 0xEBDB, 0x6147, 0xEBDC, 0x6BB7, 0xEBDD, 0x8ABE, 0xEBDE, 0x9280, 0xEBDF, 0x96B1, 0xEBE0, 0x4E59, 0xEBE1, 0x541F, 0xEBE2, 0x6DEB, + 0xEBE3, 0x852D, 0xEBE4, 0x9670, 0xEBE5, 0x97F3, 0xEBE6, 0x98EE, 0xEBE7, 0x63D6, 0xEBE8, 0x6CE3, 0xEBE9, 0x9091, 0xEBEA, 0x51DD, + 0xEBEB, 0x61C9, 0xEBEC, 0x81BA, 0xEBED, 0x9DF9, 0xEBEE, 0x4F9D, 0xEBEF, 0x501A, 0xEBF0, 0x5100, 0xEBF1, 0x5B9C, 0xEBF2, 0x610F, + 0xEBF3, 0x61FF, 0xEBF4, 0x64EC, 0xEBF5, 0x6905, 0xEBF6, 0x6BC5, 0xEBF7, 0x7591, 0xEBF8, 0x77E3, 0xEBF9, 0x7FA9, 0xEBFA, 0x8264, + 0xEBFB, 0x858F, 0xEBFC, 0x87FB, 0xEBFD, 0x8863, 0xEBFE, 0x8ABC, 0xECA1, 0x8B70, 0xECA2, 0x91AB, 0xECA3, 0x4E8C, 0xECA4, 0x4EE5, + 0xECA5, 0x4F0A, 0xECA6, 0xF9DD, 0xECA7, 0xF9DE, 0xECA8, 0x5937, 0xECA9, 0x59E8, 0xECAA, 0xF9DF, 0xECAB, 0x5DF2, 0xECAC, 0x5F1B, + 0xECAD, 0x5F5B, 0xECAE, 0x6021, 0xECAF, 0xF9E0, 0xECB0, 0xF9E1, 0xECB1, 0xF9E2, 0xECB2, 0xF9E3, 0xECB3, 0x723E, 0xECB4, 0x73E5, + 0xECB5, 0xF9E4, 0xECB6, 0x7570, 0xECB7, 0x75CD, 0xECB8, 0xF9E5, 0xECB9, 0x79FB, 0xECBA, 0xF9E6, 0xECBB, 0x800C, 0xECBC, 0x8033, + 0xECBD, 0x8084, 0xECBE, 0x82E1, 0xECBF, 0x8351, 0xECC0, 0xF9E7, 0xECC1, 0xF9E8, 0xECC2, 0x8CBD, 0xECC3, 0x8CB3, 0xECC4, 0x9087, + 0xECC5, 0xF9E9, 0xECC6, 0xF9EA, 0xECC7, 0x98F4, 0xECC8, 0x990C, 0xECC9, 0xF9EB, 0xECCA, 0xF9EC, 0xECCB, 0x7037, 0xECCC, 0x76CA, + 0xECCD, 0x7FCA, 0xECCE, 0x7FCC, 0xECCF, 0x7FFC, 0xECD0, 0x8B1A, 0xECD1, 0x4EBA, 0xECD2, 0x4EC1, 0xECD3, 0x5203, 0xECD4, 0x5370, + 0xECD5, 0xF9ED, 0xECD6, 0x54BD, 0xECD7, 0x56E0, 0xECD8, 0x59FB, 0xECD9, 0x5BC5, 0xECDA, 0x5F15, 0xECDB, 0x5FCD, 0xECDC, 0x6E6E, + 0xECDD, 0xF9EE, 0xECDE, 0xF9EF, 0xECDF, 0x7D6A, 0xECE0, 0x8335, 0xECE1, 0xF9F0, 0xECE2, 0x8693, 0xECE3, 0x8A8D, 0xECE4, 0xF9F1, + 0xECE5, 0x976D, 0xECE6, 0x9777, 0xECE7, 0xF9F2, 0xECE8, 0xF9F3, 0xECE9, 0x4E00, 0xECEA, 0x4F5A, 0xECEB, 0x4F7E, 0xECEC, 0x58F9, + 0xECED, 0x65E5, 0xECEE, 0x6EA2, 0xECEF, 0x9038, 0xECF0, 0x93B0, 0xECF1, 0x99B9, 0xECF2, 0x4EFB, 0xECF3, 0x58EC, 0xECF4, 0x598A, + 0xECF5, 0x59D9, 0xECF6, 0x6041, 0xECF7, 0xF9F4, 0xECF8, 0xF9F5, 0xECF9, 0x7A14, 0xECFA, 0xF9F6, 0xECFB, 0x834F, 0xECFC, 0x8CC3, + 0xECFD, 0x5165, 0xECFE, 0x5344, 0xEDA1, 0xF9F7, 0xEDA2, 0xF9F8, 0xEDA3, 0xF9F9, 0xEDA4, 0x4ECD, 0xEDA5, 0x5269, 0xEDA6, 0x5B55, + 0xEDA7, 0x82BF, 0xEDA8, 0x4ED4, 0xEDA9, 0x523A, 0xEDAA, 0x54A8, 0xEDAB, 0x59C9, 0xEDAC, 0x59FF, 0xEDAD, 0x5B50, 0xEDAE, 0x5B57, + 0xEDAF, 0x5B5C, 0xEDB0, 0x6063, 0xEDB1, 0x6148, 0xEDB2, 0x6ECB, 0xEDB3, 0x7099, 0xEDB4, 0x716E, 0xEDB5, 0x7386, 0xEDB6, 0x74F7, + 0xEDB7, 0x75B5, 0xEDB8, 0x78C1, 0xEDB9, 0x7D2B, 0xEDBA, 0x8005, 0xEDBB, 0x81EA, 0xEDBC, 0x8328, 0xEDBD, 0x8517, 0xEDBE, 0x85C9, + 0xEDBF, 0x8AEE, 0xEDC0, 0x8CC7, 0xEDC1, 0x96CC, 0xEDC2, 0x4F5C, 0xEDC3, 0x52FA, 0xEDC4, 0x56BC, 0xEDC5, 0x65AB, 0xEDC6, 0x6628, + 0xEDC7, 0x707C, 0xEDC8, 0x70B8, 0xEDC9, 0x7235, 0xEDCA, 0x7DBD, 0xEDCB, 0x828D, 0xEDCC, 0x914C, 0xEDCD, 0x96C0, 0xEDCE, 0x9D72, + 0xEDCF, 0x5B71, 0xEDD0, 0x68E7, 0xEDD1, 0x6B98, 0xEDD2, 0x6F7A, 0xEDD3, 0x76DE, 0xEDD4, 0x5C91, 0xEDD5, 0x66AB, 0xEDD6, 0x6F5B, + 0xEDD7, 0x7BB4, 0xEDD8, 0x7C2A, 0xEDD9, 0x8836, 0xEDDA, 0x96DC, 0xEDDB, 0x4E08, 0xEDDC, 0x4ED7, 0xEDDD, 0x5320, 0xEDDE, 0x5834, + 0xEDDF, 0x58BB, 0xEDE0, 0x58EF, 0xEDE1, 0x596C, 0xEDE2, 0x5C07, 0xEDE3, 0x5E33, 0xEDE4, 0x5E84, 0xEDE5, 0x5F35, 0xEDE6, 0x638C, + 0xEDE7, 0x66B2, 0xEDE8, 0x6756, 0xEDE9, 0x6A1F, 0xEDEA, 0x6AA3, 0xEDEB, 0x6B0C, 0xEDEC, 0x6F3F, 0xEDED, 0x7246, 0xEDEE, 0xF9FA, + 0xEDEF, 0x7350, 0xEDF0, 0x748B, 0xEDF1, 0x7AE0, 0xEDF2, 0x7CA7, 0xEDF3, 0x8178, 0xEDF4, 0x81DF, 0xEDF5, 0x81E7, 0xEDF6, 0x838A, + 0xEDF7, 0x846C, 0xEDF8, 0x8523, 0xEDF9, 0x8594, 0xEDFA, 0x85CF, 0xEDFB, 0x88DD, 0xEDFC, 0x8D13, 0xEDFD, 0x91AC, 0xEDFE, 0x9577, + 0xEEA1, 0x969C, 0xEEA2, 0x518D, 0xEEA3, 0x54C9, 0xEEA4, 0x5728, 0xEEA5, 0x5BB0, 0xEEA6, 0x624D, 0xEEA7, 0x6750, 0xEEA8, 0x683D, + 0xEEA9, 0x6893, 0xEEAA, 0x6E3D, 0xEEAB, 0x6ED3, 0xEEAC, 0x707D, 0xEEAD, 0x7E21, 0xEEAE, 0x88C1, 0xEEAF, 0x8CA1, 0xEEB0, 0x8F09, + 0xEEB1, 0x9F4B, 0xEEB2, 0x9F4E, 0xEEB3, 0x722D, 0xEEB4, 0x7B8F, 0xEEB5, 0x8ACD, 0xEEB6, 0x931A, 0xEEB7, 0x4F47, 0xEEB8, 0x4F4E, + 0xEEB9, 0x5132, 0xEEBA, 0x5480, 0xEEBB, 0x59D0, 0xEEBC, 0x5E95, 0xEEBD, 0x62B5, 0xEEBE, 0x6775, 0xEEBF, 0x696E, 0xEEC0, 0x6A17, + 0xEEC1, 0x6CAE, 0xEEC2, 0x6E1A, 0xEEC3, 0x72D9, 0xEEC4, 0x732A, 0xEEC5, 0x75BD, 0xEEC6, 0x7BB8, 0xEEC7, 0x7D35, 0xEEC8, 0x82E7, + 0xEEC9, 0x83F9, 0xEECA, 0x8457, 0xEECB, 0x85F7, 0xEECC, 0x8A5B, 0xEECD, 0x8CAF, 0xEECE, 0x8E87, 0xEECF, 0x9019, 0xEED0, 0x90B8, + 0xEED1, 0x96CE, 0xEED2, 0x9F5F, 0xEED3, 0x52E3, 0xEED4, 0x540A, 0xEED5, 0x5AE1, 0xEED6, 0x5BC2, 0xEED7, 0x6458, 0xEED8, 0x6575, + 0xEED9, 0x6EF4, 0xEEDA, 0x72C4, 0xEEDB, 0xF9FB, 0xEEDC, 0x7684, 0xEEDD, 0x7A4D, 0xEEDE, 0x7B1B, 0xEEDF, 0x7C4D, 0xEEE0, 0x7E3E, + 0xEEE1, 0x7FDF, 0xEEE2, 0x837B, 0xEEE3, 0x8B2B, 0xEEE4, 0x8CCA, 0xEEE5, 0x8D64, 0xEEE6, 0x8DE1, 0xEEE7, 0x8E5F, 0xEEE8, 0x8FEA, + 0xEEE9, 0x8FF9, 0xEEEA, 0x9069, 0xEEEB, 0x93D1, 0xEEEC, 0x4F43, 0xEEED, 0x4F7A, 0xEEEE, 0x50B3, 0xEEEF, 0x5168, 0xEEF0, 0x5178, + 0xEEF1, 0x524D, 0xEEF2, 0x526A, 0xEEF3, 0x5861, 0xEEF4, 0x587C, 0xEEF5, 0x5960, 0xEEF6, 0x5C08, 0xEEF7, 0x5C55, 0xEEF8, 0x5EDB, + 0xEEF9, 0x609B, 0xEEFA, 0x6230, 0xEEFB, 0x6813, 0xEEFC, 0x6BBF, 0xEEFD, 0x6C08, 0xEEFE, 0x6FB1, 0xEFA1, 0x714E, 0xEFA2, 0x7420, + 0xEFA3, 0x7530, 0xEFA4, 0x7538, 0xEFA5, 0x7551, 0xEFA6, 0x7672, 0xEFA7, 0x7B4C, 0xEFA8, 0x7B8B, 0xEFA9, 0x7BAD, 0xEFAA, 0x7BC6, + 0xEFAB, 0x7E8F, 0xEFAC, 0x8A6E, 0xEFAD, 0x8F3E, 0xEFAE, 0x8F49, 0xEFAF, 0x923F, 0xEFB0, 0x9293, 0xEFB1, 0x9322, 0xEFB2, 0x942B, + 0xEFB3, 0x96FB, 0xEFB4, 0x985A, 0xEFB5, 0x986B, 0xEFB6, 0x991E, 0xEFB7, 0x5207, 0xEFB8, 0x622A, 0xEFB9, 0x6298, 0xEFBA, 0x6D59, + 0xEFBB, 0x7664, 0xEFBC, 0x7ACA, 0xEFBD, 0x7BC0, 0xEFBE, 0x7D76, 0xEFBF, 0x5360, 0xEFC0, 0x5CBE, 0xEFC1, 0x5E97, 0xEFC2, 0x6F38, + 0xEFC3, 0x70B9, 0xEFC4, 0x7C98, 0xEFC5, 0x9711, 0xEFC6, 0x9B8E, 0xEFC7, 0x9EDE, 0xEFC8, 0x63A5, 0xEFC9, 0x647A, 0xEFCA, 0x8776, + 0xEFCB, 0x4E01, 0xEFCC, 0x4E95, 0xEFCD, 0x4EAD, 0xEFCE, 0x505C, 0xEFCF, 0x5075, 0xEFD0, 0x5448, 0xEFD1, 0x59C3, 0xEFD2, 0x5B9A, + 0xEFD3, 0x5E40, 0xEFD4, 0x5EAD, 0xEFD5, 0x5EF7, 0xEFD6, 0x5F81, 0xEFD7, 0x60C5, 0xEFD8, 0x633A, 0xEFD9, 0x653F, 0xEFDA, 0x6574, + 0xEFDB, 0x65CC, 0xEFDC, 0x6676, 0xEFDD, 0x6678, 0xEFDE, 0x67FE, 0xEFDF, 0x6968, 0xEFE0, 0x6A89, 0xEFE1, 0x6B63, 0xEFE2, 0x6C40, + 0xEFE3, 0x6DC0, 0xEFE4, 0x6DE8, 0xEFE5, 0x6E1F, 0xEFE6, 0x6E5E, 0xEFE7, 0x701E, 0xEFE8, 0x70A1, 0xEFE9, 0x738E, 0xEFEA, 0x73FD, + 0xEFEB, 0x753A, 0xEFEC, 0x775B, 0xEFED, 0x7887, 0xEFEE, 0x798E, 0xEFEF, 0x7A0B, 0xEFF0, 0x7A7D, 0xEFF1, 0x7CBE, 0xEFF2, 0x7D8E, + 0xEFF3, 0x8247, 0xEFF4, 0x8A02, 0xEFF5, 0x8AEA, 0xEFF6, 0x8C9E, 0xEFF7, 0x912D, 0xEFF8, 0x914A, 0xEFF9, 0x91D8, 0xEFFA, 0x9266, + 0xEFFB, 0x92CC, 0xEFFC, 0x9320, 0xEFFD, 0x9706, 0xEFFE, 0x9756, 0xF0A1, 0x975C, 0xF0A2, 0x9802, 0xF0A3, 0x9F0E, 0xF0A4, 0x5236, + 0xF0A5, 0x5291, 0xF0A6, 0x557C, 0xF0A7, 0x5824, 0xF0A8, 0x5E1D, 0xF0A9, 0x5F1F, 0xF0AA, 0x608C, 0xF0AB, 0x63D0, 0xF0AC, 0x68AF, + 0xF0AD, 0x6FDF, 0xF0AE, 0x796D, 0xF0AF, 0x7B2C, 0xF0B0, 0x81CD, 0xF0B1, 0x85BA, 0xF0B2, 0x88FD, 0xF0B3, 0x8AF8, 0xF0B4, 0x8E44, + 0xF0B5, 0x918D, 0xF0B6, 0x9664, 0xF0B7, 0x969B, 0xF0B8, 0x973D, 0xF0B9, 0x984C, 0xF0BA, 0x9F4A, 0xF0BB, 0x4FCE, 0xF0BC, 0x5146, + 0xF0BD, 0x51CB, 0xF0BE, 0x52A9, 0xF0BF, 0x5632, 0xF0C0, 0x5F14, 0xF0C1, 0x5F6B, 0xF0C2, 0x63AA, 0xF0C3, 0x64CD, 0xF0C4, 0x65E9, + 0xF0C5, 0x6641, 0xF0C6, 0x66FA, 0xF0C7, 0x66F9, 0xF0C8, 0x671D, 0xF0C9, 0x689D, 0xF0CA, 0x68D7, 0xF0CB, 0x69FD, 0xF0CC, 0x6F15, + 0xF0CD, 0x6F6E, 0xF0CE, 0x7167, 0xF0CF, 0x71E5, 0xF0D0, 0x722A, 0xF0D1, 0x74AA, 0xF0D2, 0x773A, 0xF0D3, 0x7956, 0xF0D4, 0x795A, + 0xF0D5, 0x79DF, 0xF0D6, 0x7A20, 0xF0D7, 0x7A95, 0xF0D8, 0x7C97, 0xF0D9, 0x7CDF, 0xF0DA, 0x7D44, 0xF0DB, 0x7E70, 0xF0DC, 0x8087, + 0xF0DD, 0x85FB, 0xF0DE, 0x86A4, 0xF0DF, 0x8A54, 0xF0E0, 0x8ABF, 0xF0E1, 0x8D99, 0xF0E2, 0x8E81, 0xF0E3, 0x9020, 0xF0E4, 0x906D, + 0xF0E5, 0x91E3, 0xF0E6, 0x963B, 0xF0E7, 0x96D5, 0xF0E8, 0x9CE5, 0xF0E9, 0x65CF, 0xF0EA, 0x7C07, 0xF0EB, 0x8DB3, 0xF0EC, 0x93C3, + 0xF0ED, 0x5B58, 0xF0EE, 0x5C0A, 0xF0EF, 0x5352, 0xF0F0, 0x62D9, 0xF0F1, 0x731D, 0xF0F2, 0x5027, 0xF0F3, 0x5B97, 0xF0F4, 0x5F9E, + 0xF0F5, 0x60B0, 0xF0F6, 0x616B, 0xF0F7, 0x68D5, 0xF0F8, 0x6DD9, 0xF0F9, 0x742E, 0xF0FA, 0x7A2E, 0xF0FB, 0x7D42, 0xF0FC, 0x7D9C, + 0xF0FD, 0x7E31, 0xF0FE, 0x816B, 0xF1A1, 0x8E2A, 0xF1A2, 0x8E35, 0xF1A3, 0x937E, 0xF1A4, 0x9418, 0xF1A5, 0x4F50, 0xF1A6, 0x5750, + 0xF1A7, 0x5DE6, 0xF1A8, 0x5EA7, 0xF1A9, 0x632B, 0xF1AA, 0x7F6A, 0xF1AB, 0x4E3B, 0xF1AC, 0x4F4F, 0xF1AD, 0x4F8F, 0xF1AE, 0x505A, + 0xF1AF, 0x59DD, 0xF1B0, 0x80C4, 0xF1B1, 0x546A, 0xF1B2, 0x5468, 0xF1B3, 0x55FE, 0xF1B4, 0x594F, 0xF1B5, 0x5B99, 0xF1B6, 0x5DDE, + 0xF1B7, 0x5EDA, 0xF1B8, 0x665D, 0xF1B9, 0x6731, 0xF1BA, 0x67F1, 0xF1BB, 0x682A, 0xF1BC, 0x6CE8, 0xF1BD, 0x6D32, 0xF1BE, 0x6E4A, + 0xF1BF, 0x6F8D, 0xF1C0, 0x70B7, 0xF1C1, 0x73E0, 0xF1C2, 0x7587, 0xF1C3, 0x7C4C, 0xF1C4, 0x7D02, 0xF1C5, 0x7D2C, 0xF1C6, 0x7DA2, + 0xF1C7, 0x821F, 0xF1C8, 0x86DB, 0xF1C9, 0x8A3B, 0xF1CA, 0x8A85, 0xF1CB, 0x8D70, 0xF1CC, 0x8E8A, 0xF1CD, 0x8F33, 0xF1CE, 0x9031, + 0xF1CF, 0x914E, 0xF1D0, 0x9152, 0xF1D1, 0x9444, 0xF1D2, 0x99D0, 0xF1D3, 0x7AF9, 0xF1D4, 0x7CA5, 0xF1D5, 0x4FCA, 0xF1D6, 0x5101, + 0xF1D7, 0x51C6, 0xF1D8, 0x57C8, 0xF1D9, 0x5BEF, 0xF1DA, 0x5CFB, 0xF1DB, 0x6659, 0xF1DC, 0x6A3D, 0xF1DD, 0x6D5A, 0xF1DE, 0x6E96, + 0xF1DF, 0x6FEC, 0xF1E0, 0x710C, 0xF1E1, 0x756F, 0xF1E2, 0x7AE3, 0xF1E3, 0x8822, 0xF1E4, 0x9021, 0xF1E5, 0x9075, 0xF1E6, 0x96CB, + 0xF1E7, 0x99FF, 0xF1E8, 0x8301, 0xF1E9, 0x4E2D, 0xF1EA, 0x4EF2, 0xF1EB, 0x8846, 0xF1EC, 0x91CD, 0xF1ED, 0x537D, 0xF1EE, 0x6ADB, + 0xF1EF, 0x696B, 0xF1F0, 0x6C41, 0xF1F1, 0x847A, 0xF1F2, 0x589E, 0xF1F3, 0x618E, 0xF1F4, 0x66FE, 0xF1F5, 0x62EF, 0xF1F6, 0x70DD, + 0xF1F7, 0x7511, 0xF1F8, 0x75C7, 0xF1F9, 0x7E52, 0xF1FA, 0x84B8, 0xF1FB, 0x8B49, 0xF1FC, 0x8D08, 0xF1FD, 0x4E4B, 0xF1FE, 0x53EA, + 0xF2A1, 0x54AB, 0xF2A2, 0x5730, 0xF2A3, 0x5740, 0xF2A4, 0x5FD7, 0xF2A5, 0x6301, 0xF2A6, 0x6307, 0xF2A7, 0x646F, 0xF2A8, 0x652F, + 0xF2A9, 0x65E8, 0xF2AA, 0x667A, 0xF2AB, 0x679D, 0xF2AC, 0x67B3, 0xF2AD, 0x6B62, 0xF2AE, 0x6C60, 0xF2AF, 0x6C9A, 0xF2B0, 0x6F2C, + 0xF2B1, 0x77E5, 0xF2B2, 0x7825, 0xF2B3, 0x7949, 0xF2B4, 0x7957, 0xF2B5, 0x7D19, 0xF2B6, 0x80A2, 0xF2B7, 0x8102, 0xF2B8, 0x81F3, + 0xF2B9, 0x829D, 0xF2BA, 0x82B7, 0xF2BB, 0x8718, 0xF2BC, 0x8A8C, 0xF2BD, 0xF9FC, 0xF2BE, 0x8D04, 0xF2BF, 0x8DBE, 0xF2C0, 0x9072, + 0xF2C1, 0x76F4, 0xF2C2, 0x7A19, 0xF2C3, 0x7A37, 0xF2C4, 0x7E54, 0xF2C5, 0x8077, 0xF2C6, 0x5507, 0xF2C7, 0x55D4, 0xF2C8, 0x5875, + 0xF2C9, 0x632F, 0xF2CA, 0x6422, 0xF2CB, 0x6649, 0xF2CC, 0x664B, 0xF2CD, 0x686D, 0xF2CE, 0x699B, 0xF2CF, 0x6B84, 0xF2D0, 0x6D25, + 0xF2D1, 0x6EB1, 0xF2D2, 0x73CD, 0xF2D3, 0x7468, 0xF2D4, 0x74A1, 0xF2D5, 0x755B, 0xF2D6, 0x75B9, 0xF2D7, 0x76E1, 0xF2D8, 0x771E, + 0xF2D9, 0x778B, 0xF2DA, 0x79E6, 0xF2DB, 0x7E09, 0xF2DC, 0x7E1D, 0xF2DD, 0x81FB, 0xF2DE, 0x852F, 0xF2DF, 0x8897, 0xF2E0, 0x8A3A, + 0xF2E1, 0x8CD1, 0xF2E2, 0x8EEB, 0xF2E3, 0x8FB0, 0xF2E4, 0x9032, 0xF2E5, 0x93AD, 0xF2E6, 0x9663, 0xF2E7, 0x9673, 0xF2E8, 0x9707, + 0xF2E9, 0x4F84, 0xF2EA, 0x53F1, 0xF2EB, 0x59EA, 0xF2EC, 0x5AC9, 0xF2ED, 0x5E19, 0xF2EE, 0x684E, 0xF2EF, 0x74C6, 0xF2F0, 0x75BE, + 0xF2F1, 0x79E9, 0xF2F2, 0x7A92, 0xF2F3, 0x81A3, 0xF2F4, 0x86ED, 0xF2F5, 0x8CEA, 0xF2F6, 0x8DCC, 0xF2F7, 0x8FED, 0xF2F8, 0x659F, + 0xF2F9, 0x6715, 0xF2FA, 0xF9FD, 0xF2FB, 0x57F7, 0xF2FC, 0x6F57, 0xF2FD, 0x7DDD, 0xF2FE, 0x8F2F, 0xF3A1, 0x93F6, 0xF3A2, 0x96C6, + 0xF3A3, 0x5FB5, 0xF3A4, 0x61F2, 0xF3A5, 0x6F84, 0xF3A6, 0x4E14, 0xF3A7, 0x4F98, 0xF3A8, 0x501F, 0xF3A9, 0x53C9, 0xF3AA, 0x55DF, + 0xF3AB, 0x5D6F, 0xF3AC, 0x5DEE, 0xF3AD, 0x6B21, 0xF3AE, 0x6B64, 0xF3AF, 0x78CB, 0xF3B0, 0x7B9A, 0xF3B1, 0xF9FE, 0xF3B2, 0x8E49, + 0xF3B3, 0x8ECA, 0xF3B4, 0x906E, 0xF3B5, 0x6349, 0xF3B6, 0x643E, 0xF3B7, 0x7740, 0xF3B8, 0x7A84, 0xF3B9, 0x932F, 0xF3BA, 0x947F, + 0xF3BB, 0x9F6A, 0xF3BC, 0x64B0, 0xF3BD, 0x6FAF, 0xF3BE, 0x71E6, 0xF3BF, 0x74A8, 0xF3C0, 0x74DA, 0xF3C1, 0x7AC4, 0xF3C2, 0x7C12, + 0xF3C3, 0x7E82, 0xF3C4, 0x7CB2, 0xF3C5, 0x7E98, 0xF3C6, 0x8B9A, 0xF3C7, 0x8D0A, 0xF3C8, 0x947D, 0xF3C9, 0x9910, 0xF3CA, 0x994C, + 0xF3CB, 0x5239, 0xF3CC, 0x5BDF, 0xF3CD, 0x64E6, 0xF3CE, 0x672D, 0xF3CF, 0x7D2E, 0xF3D0, 0x50ED, 0xF3D1, 0x53C3, 0xF3D2, 0x5879, + 0xF3D3, 0x6158, 0xF3D4, 0x6159, 0xF3D5, 0x61FA, 0xF3D6, 0x65AC, 0xF3D7, 0x7AD9, 0xF3D8, 0x8B92, 0xF3D9, 0x8B96, 0xF3DA, 0x5009, + 0xF3DB, 0x5021, 0xF3DC, 0x5275, 0xF3DD, 0x5531, 0xF3DE, 0x5A3C, 0xF3DF, 0x5EE0, 0xF3E0, 0x5F70, 0xF3E1, 0x6134, 0xF3E2, 0x655E, + 0xF3E3, 0x660C, 0xF3E4, 0x6636, 0xF3E5, 0x66A2, 0xF3E6, 0x69CD, 0xF3E7, 0x6EC4, 0xF3E8, 0x6F32, 0xF3E9, 0x7316, 0xF3EA, 0x7621, + 0xF3EB, 0x7A93, 0xF3EC, 0x8139, 0xF3ED, 0x8259, 0xF3EE, 0x83D6, 0xF3EF, 0x84BC, 0xF3F0, 0x50B5, 0xF3F1, 0x57F0, 0xF3F2, 0x5BC0, + 0xF3F3, 0x5BE8, 0xF3F4, 0x5F69, 0xF3F5, 0x63A1, 0xF3F6, 0x7826, 0xF3F7, 0x7DB5, 0xF3F8, 0x83DC, 0xF3F9, 0x8521, 0xF3FA, 0x91C7, + 0xF3FB, 0x91F5, 0xF3FC, 0x518A, 0xF3FD, 0x67F5, 0xF3FE, 0x7B56, 0xF4A1, 0x8CAC, 0xF4A2, 0x51C4, 0xF4A3, 0x59BB, 0xF4A4, 0x60BD, + 0xF4A5, 0x8655, 0xF4A6, 0x501C, 0xF4A7, 0xF9FF, 0xF4A8, 0x5254, 0xF4A9, 0x5C3A, 0xF4AA, 0x617D, 0xF4AB, 0x621A, 0xF4AC, 0x62D3, + 0xF4AD, 0x64F2, 0xF4AE, 0x65A5, 0xF4AF, 0x6ECC, 0xF4B0, 0x7620, 0xF4B1, 0x810A, 0xF4B2, 0x8E60, 0xF4B3, 0x965F, 0xF4B4, 0x96BB, + 0xF4B5, 0x4EDF, 0xF4B6, 0x5343, 0xF4B7, 0x5598, 0xF4B8, 0x5929, 0xF4B9, 0x5DDD, 0xF4BA, 0x64C5, 0xF4BB, 0x6CC9, 0xF4BC, 0x6DFA, + 0xF4BD, 0x7394, 0xF4BE, 0x7A7F, 0xF4BF, 0x821B, 0xF4C0, 0x85A6, 0xF4C1, 0x8CE4, 0xF4C2, 0x8E10, 0xF4C3, 0x9077, 0xF4C4, 0x91E7, + 0xF4C5, 0x95E1, 0xF4C6, 0x9621, 0xF4C7, 0x97C6, 0xF4C8, 0x51F8, 0xF4C9, 0x54F2, 0xF4CA, 0x5586, 0xF4CB, 0x5FB9, 0xF4CC, 0x64A4, + 0xF4CD, 0x6F88, 0xF4CE, 0x7DB4, 0xF4CF, 0x8F1F, 0xF4D0, 0x8F4D, 0xF4D1, 0x9435, 0xF4D2, 0x50C9, 0xF4D3, 0x5C16, 0xF4D4, 0x6CBE, + 0xF4D5, 0x6DFB, 0xF4D6, 0x751B, 0xF4D7, 0x77BB, 0xF4D8, 0x7C3D, 0xF4D9, 0x7C64, 0xF4DA, 0x8A79, 0xF4DB, 0x8AC2, 0xF4DC, 0x581E, + 0xF4DD, 0x59BE, 0xF4DE, 0x5E16, 0xF4DF, 0x6377, 0xF4E0, 0x7252, 0xF4E1, 0x758A, 0xF4E2, 0x776B, 0xF4E3, 0x8ADC, 0xF4E4, 0x8CBC, + 0xF4E5, 0x8F12, 0xF4E6, 0x5EF3, 0xF4E7, 0x6674, 0xF4E8, 0x6DF8, 0xF4E9, 0x807D, 0xF4EA, 0x83C1, 0xF4EB, 0x8ACB, 0xF4EC, 0x9751, + 0xF4ED, 0x9BD6, 0xF4EE, 0xFA00, 0xF4EF, 0x5243, 0xF4F0, 0x66FF, 0xF4F1, 0x6D95, 0xF4F2, 0x6EEF, 0xF4F3, 0x7DE0, 0xF4F4, 0x8AE6, + 0xF4F5, 0x902E, 0xF4F6, 0x905E, 0xF4F7, 0x9AD4, 0xF4F8, 0x521D, 0xF4F9, 0x527F, 0xF4FA, 0x54E8, 0xF4FB, 0x6194, 0xF4FC, 0x6284, + 0xF4FD, 0x62DB, 0xF4FE, 0x68A2, 0xF5A1, 0x6912, 0xF5A2, 0x695A, 0xF5A3, 0x6A35, 0xF5A4, 0x7092, 0xF5A5, 0x7126, 0xF5A6, 0x785D, + 0xF5A7, 0x7901, 0xF5A8, 0x790E, 0xF5A9, 0x79D2, 0xF5AA, 0x7A0D, 0xF5AB, 0x8096, 0xF5AC, 0x8278, 0xF5AD, 0x82D5, 0xF5AE, 0x8349, + 0xF5AF, 0x8549, 0xF5B0, 0x8C82, 0xF5B1, 0x8D85, 0xF5B2, 0x9162, 0xF5B3, 0x918B, 0xF5B4, 0x91AE, 0xF5B5, 0x4FC3, 0xF5B6, 0x56D1, + 0xF5B7, 0x71ED, 0xF5B8, 0x77D7, 0xF5B9, 0x8700, 0xF5BA, 0x89F8, 0xF5BB, 0x5BF8, 0xF5BC, 0x5FD6, 0xF5BD, 0x6751, 0xF5BE, 0x90A8, + 0xF5BF, 0x53E2, 0xF5C0, 0x585A, 0xF5C1, 0x5BF5, 0xF5C2, 0x60A4, 0xF5C3, 0x6181, 0xF5C4, 0x6460, 0xF5C5, 0x7E3D, 0xF5C6, 0x8070, + 0xF5C7, 0x8525, 0xF5C8, 0x9283, 0xF5C9, 0x64AE, 0xF5CA, 0x50AC, 0xF5CB, 0x5D14, 0xF5CC, 0x6700, 0xF5CD, 0x589C, 0xF5CE, 0x62BD, + 0xF5CF, 0x63A8, 0xF5D0, 0x690E, 0xF5D1, 0x6978, 0xF5D2, 0x6A1E, 0xF5D3, 0x6E6B, 0xF5D4, 0x76BA, 0xF5D5, 0x79CB, 0xF5D6, 0x82BB, + 0xF5D7, 0x8429, 0xF5D8, 0x8ACF, 0xF5D9, 0x8DA8, 0xF5DA, 0x8FFD, 0xF5DB, 0x9112, 0xF5DC, 0x914B, 0xF5DD, 0x919C, 0xF5DE, 0x9310, + 0xF5DF, 0x9318, 0xF5E0, 0x939A, 0xF5E1, 0x96DB, 0xF5E2, 0x9A36, 0xF5E3, 0x9C0D, 0xF5E4, 0x4E11, 0xF5E5, 0x755C, 0xF5E6, 0x795D, + 0xF5E7, 0x7AFA, 0xF5E8, 0x7B51, 0xF5E9, 0x7BC9, 0xF5EA, 0x7E2E, 0xF5EB, 0x84C4, 0xF5EC, 0x8E59, 0xF5ED, 0x8E74, 0xF5EE, 0x8EF8, + 0xF5EF, 0x9010, 0xF5F0, 0x6625, 0xF5F1, 0x693F, 0xF5F2, 0x7443, 0xF5F3, 0x51FA, 0xF5F4, 0x672E, 0xF5F5, 0x9EDC, 0xF5F6, 0x5145, + 0xF5F7, 0x5FE0, 0xF5F8, 0x6C96, 0xF5F9, 0x87F2, 0xF5FA, 0x885D, 0xF5FB, 0x8877, 0xF5FC, 0x60B4, 0xF5FD, 0x81B5, 0xF5FE, 0x8403, + 0xF6A1, 0x8D05, 0xF6A2, 0x53D6, 0xF6A3, 0x5439, 0xF6A4, 0x5634, 0xF6A5, 0x5A36, 0xF6A6, 0x5C31, 0xF6A7, 0x708A, 0xF6A8, 0x7FE0, + 0xF6A9, 0x805A, 0xF6AA, 0x8106, 0xF6AB, 0x81ED, 0xF6AC, 0x8DA3, 0xF6AD, 0x9189, 0xF6AE, 0x9A5F, 0xF6AF, 0x9DF2, 0xF6B0, 0x5074, + 0xF6B1, 0x4EC4, 0xF6B2, 0x53A0, 0xF6B3, 0x60FB, 0xF6B4, 0x6E2C, 0xF6B5, 0x5C64, 0xF6B6, 0x4F88, 0xF6B7, 0x5024, 0xF6B8, 0x55E4, + 0xF6B9, 0x5CD9, 0xF6BA, 0x5E5F, 0xF6BB, 0x6065, 0xF6BC, 0x6894, 0xF6BD, 0x6CBB, 0xF6BE, 0x6DC4, 0xF6BF, 0x71BE, 0xF6C0, 0x75D4, + 0xF6C1, 0x75F4, 0xF6C2, 0x7661, 0xF6C3, 0x7A1A, 0xF6C4, 0x7A49, 0xF6C5, 0x7DC7, 0xF6C6, 0x7DFB, 0xF6C7, 0x7F6E, 0xF6C8, 0x81F4, + 0xF6C9, 0x86A9, 0xF6CA, 0x8F1C, 0xF6CB, 0x96C9, 0xF6CC, 0x99B3, 0xF6CD, 0x9F52, 0xF6CE, 0x5247, 0xF6CF, 0x52C5, 0xF6D0, 0x98ED, + 0xF6D1, 0x89AA, 0xF6D2, 0x4E03, 0xF6D3, 0x67D2, 0xF6D4, 0x6F06, 0xF6D5, 0x4FB5, 0xF6D6, 0x5BE2, 0xF6D7, 0x6795, 0xF6D8, 0x6C88, + 0xF6D9, 0x6D78, 0xF6DA, 0x741B, 0xF6DB, 0x7827, 0xF6DC, 0x91DD, 0xF6DD, 0x937C, 0xF6DE, 0x87C4, 0xF6DF, 0x79E4, 0xF6E0, 0x7A31, + 0xF6E1, 0x5FEB, 0xF6E2, 0x4ED6, 0xF6E3, 0x54A4, 0xF6E4, 0x553E, 0xF6E5, 0x58AE, 0xF6E6, 0x59A5, 0xF6E7, 0x60F0, 0xF6E8, 0x6253, + 0xF6E9, 0x62D6, 0xF6EA, 0x6736, 0xF6EB, 0x6955, 0xF6EC, 0x8235, 0xF6ED, 0x9640, 0xF6EE, 0x99B1, 0xF6EF, 0x99DD, 0xF6F0, 0x502C, + 0xF6F1, 0x5353, 0xF6F2, 0x5544, 0xF6F3, 0x577C, 0xF6F4, 0xFA01, 0xF6F5, 0x6258, 0xF6F6, 0xFA02, 0xF6F7, 0x64E2, 0xF6F8, 0x666B, + 0xF6F9, 0x67DD, 0xF6FA, 0x6FC1, 0xF6FB, 0x6FEF, 0xF6FC, 0x7422, 0xF6FD, 0x7438, 0xF6FE, 0x8A17, 0xF7A1, 0x9438, 0xF7A2, 0x5451, + 0xF7A3, 0x5606, 0xF7A4, 0x5766, 0xF7A5, 0x5F48, 0xF7A6, 0x619A, 0xF7A7, 0x6B4E, 0xF7A8, 0x7058, 0xF7A9, 0x70AD, 0xF7AA, 0x7DBB, + 0xF7AB, 0x8A95, 0xF7AC, 0x596A, 0xF7AD, 0x812B, 0xF7AE, 0x63A2, 0xF7AF, 0x7708, 0xF7B0, 0x803D, 0xF7B1, 0x8CAA, 0xF7B2, 0x5854, + 0xF7B3, 0x642D, 0xF7B4, 0x69BB, 0xF7B5, 0x5B95, 0xF7B6, 0x5E11, 0xF7B7, 0x6E6F, 0xF7B8, 0xFA03, 0xF7B9, 0x8569, 0xF7BA, 0x514C, + 0xF7BB, 0x53F0, 0xF7BC, 0x592A, 0xF7BD, 0x6020, 0xF7BE, 0x614B, 0xF7BF, 0x6B86, 0xF7C0, 0x6C70, 0xF7C1, 0x6CF0, 0xF7C2, 0x7B1E, + 0xF7C3, 0x80CE, 0xF7C4, 0x82D4, 0xF7C5, 0x8DC6, 0xF7C6, 0x90B0, 0xF7C7, 0x98B1, 0xF7C8, 0xFA04, 0xF7C9, 0x64C7, 0xF7CA, 0x6FA4, + 0xF7CB, 0x6491, 0xF7CC, 0x6504, 0xF7CD, 0x514E, 0xF7CE, 0x5410, 0xF7CF, 0x571F, 0xF7D0, 0x8A0E, 0xF7D1, 0x615F, 0xF7D2, 0x6876, + 0xF7D3, 0xFA05, 0xF7D4, 0x75DB, 0xF7D5, 0x7B52, 0xF7D6, 0x7D71, 0xF7D7, 0x901A, 0xF7D8, 0x5806, 0xF7D9, 0x69CC, 0xF7DA, 0x817F, + 0xF7DB, 0x892A, 0xF7DC, 0x9000, 0xF7DD, 0x9839, 0xF7DE, 0x5078, 0xF7DF, 0x5957, 0xF7E0, 0x59AC, 0xF7E1, 0x6295, 0xF7E2, 0x900F, + 0xF7E3, 0x9B2A, 0xF7E4, 0x615D, 0xF7E5, 0x7279, 0xF7E6, 0x95D6, 0xF7E7, 0x5761, 0xF7E8, 0x5A46, 0xF7E9, 0x5DF4, 0xF7EA, 0x628A, + 0xF7EB, 0x64AD, 0xF7EC, 0x64FA, 0xF7ED, 0x6777, 0xF7EE, 0x6CE2, 0xF7EF, 0x6D3E, 0xF7F0, 0x722C, 0xF7F1, 0x7436, 0xF7F2, 0x7834, + 0xF7F3, 0x7F77, 0xF7F4, 0x82AD, 0xF7F5, 0x8DDB, 0xF7F6, 0x9817, 0xF7F7, 0x5224, 0xF7F8, 0x5742, 0xF7F9, 0x677F, 0xF7FA, 0x7248, + 0xF7FB, 0x74E3, 0xF7FC, 0x8CA9, 0xF7FD, 0x8FA6, 0xF7FE, 0x9211, 0xF8A1, 0x962A, 0xF8A2, 0x516B, 0xF8A3, 0x53ED, 0xF8A4, 0x634C, + 0xF8A5, 0x4F69, 0xF8A6, 0x5504, 0xF8A7, 0x6096, 0xF8A8, 0x6557, 0xF8A9, 0x6C9B, 0xF8AA, 0x6D7F, 0xF8AB, 0x724C, 0xF8AC, 0x72FD, + 0xF8AD, 0x7A17, 0xF8AE, 0x8987, 0xF8AF, 0x8C9D, 0xF8B0, 0x5F6D, 0xF8B1, 0x6F8E, 0xF8B2, 0x70F9, 0xF8B3, 0x81A8, 0xF8B4, 0x610E, + 0xF8B5, 0x4FBF, 0xF8B6, 0x504F, 0xF8B7, 0x6241, 0xF8B8, 0x7247, 0xF8B9, 0x7BC7, 0xF8BA, 0x7DE8, 0xF8BB, 0x7FE9, 0xF8BC, 0x904D, + 0xF8BD, 0x97AD, 0xF8BE, 0x9A19, 0xF8BF, 0x8CB6, 0xF8C0, 0x576A, 0xF8C1, 0x5E73, 0xF8C2, 0x67B0, 0xF8C3, 0x840D, 0xF8C4, 0x8A55, + 0xF8C5, 0x5420, 0xF8C6, 0x5B16, 0xF8C7, 0x5E63, 0xF8C8, 0x5EE2, 0xF8C9, 0x5F0A, 0xF8CA, 0x6583, 0xF8CB, 0x80BA, 0xF8CC, 0x853D, + 0xF8CD, 0x9589, 0xF8CE, 0x965B, 0xF8CF, 0x4F48, 0xF8D0, 0x5305, 0xF8D1, 0x530D, 0xF8D2, 0x530F, 0xF8D3, 0x5486, 0xF8D4, 0x54FA, + 0xF8D5, 0x5703, 0xF8D6, 0x5E03, 0xF8D7, 0x6016, 0xF8D8, 0x629B, 0xF8D9, 0x62B1, 0xF8DA, 0x6355, 0xF8DB, 0xFA06, 0xF8DC, 0x6CE1, + 0xF8DD, 0x6D66, 0xF8DE, 0x75B1, 0xF8DF, 0x7832, 0xF8E0, 0x80DE, 0xF8E1, 0x812F, 0xF8E2, 0x82DE, 0xF8E3, 0x8461, 0xF8E4, 0x84B2, + 0xF8E5, 0x888D, 0xF8E6, 0x8912, 0xF8E7, 0x900B, 0xF8E8, 0x92EA, 0xF8E9, 0x98FD, 0xF8EA, 0x9B91, 0xF8EB, 0x5E45, 0xF8EC, 0x66B4, + 0xF8ED, 0x66DD, 0xF8EE, 0x7011, 0xF8EF, 0x7206, 0xF8F0, 0xFA07, 0xF8F1, 0x4FF5, 0xF8F2, 0x527D, 0xF8F3, 0x5F6A, 0xF8F4, 0x6153, + 0xF8F5, 0x6753, 0xF8F6, 0x6A19, 0xF8F7, 0x6F02, 0xF8F8, 0x74E2, 0xF8F9, 0x7968, 0xF8FA, 0x8868, 0xF8FB, 0x8C79, 0xF8FC, 0x98C7, + 0xF8FD, 0x98C4, 0xF8FE, 0x9A43, 0xF9A1, 0x54C1, 0xF9A2, 0x7A1F, 0xF9A3, 0x6953, 0xF9A4, 0x8AF7, 0xF9A5, 0x8C4A, 0xF9A6, 0x98A8, + 0xF9A7, 0x99AE, 0xF9A8, 0x5F7C, 0xF9A9, 0x62AB, 0xF9AA, 0x75B2, 0xF9AB, 0x76AE, 0xF9AC, 0x88AB, 0xF9AD, 0x907F, 0xF9AE, 0x9642, + 0xF9AF, 0x5339, 0xF9B0, 0x5F3C, 0xF9B1, 0x5FC5, 0xF9B2, 0x6CCC, 0xF9B3, 0x73CC, 0xF9B4, 0x7562, 0xF9B5, 0x758B, 0xF9B6, 0x7B46, + 0xF9B7, 0x82FE, 0xF9B8, 0x999D, 0xF9B9, 0x4E4F, 0xF9BA, 0x903C, 0xF9BB, 0x4E0B, 0xF9BC, 0x4F55, 0xF9BD, 0x53A6, 0xF9BE, 0x590F, + 0xF9BF, 0x5EC8, 0xF9C0, 0x6630, 0xF9C1, 0x6CB3, 0xF9C2, 0x7455, 0xF9C3, 0x8377, 0xF9C4, 0x8766, 0xF9C5, 0x8CC0, 0xF9C6, 0x9050, + 0xF9C7, 0x971E, 0xF9C8, 0x9C15, 0xF9C9, 0x58D1, 0xF9CA, 0x5B78, 0xF9CB, 0x8650, 0xF9CC, 0x8B14, 0xF9CD, 0x9DB4, 0xF9CE, 0x5BD2, + 0xF9CF, 0x6068, 0xF9D0, 0x608D, 0xF9D1, 0x65F1, 0xF9D2, 0x6C57, 0xF9D3, 0x6F22, 0xF9D4, 0x6FA3, 0xF9D5, 0x701A, 0xF9D6, 0x7F55, + 0xF9D7, 0x7FF0, 0xF9D8, 0x9591, 0xF9D9, 0x9592, 0xF9DA, 0x9650, 0xF9DB, 0x97D3, 0xF9DC, 0x5272, 0xF9DD, 0x8F44, 0xF9DE, 0x51FD, + 0xF9DF, 0x542B, 0xF9E0, 0x54B8, 0xF9E1, 0x5563, 0xF9E2, 0x558A, 0xF9E3, 0x6ABB, 0xF9E4, 0x6DB5, 0xF9E5, 0x7DD8, 0xF9E6, 0x8266, + 0xF9E7, 0x929C, 0xF9E8, 0x9677, 0xF9E9, 0x9E79, 0xF9EA, 0x5408, 0xF9EB, 0x54C8, 0xF9EC, 0x76D2, 0xF9ED, 0x86E4, 0xF9EE, 0x95A4, + 0xF9EF, 0x95D4, 0xF9F0, 0x965C, 0xF9F1, 0x4EA2, 0xF9F2, 0x4F09, 0xF9F3, 0x59EE, 0xF9F4, 0x5AE6, 0xF9F5, 0x5DF7, 0xF9F6, 0x6052, + 0xF9F7, 0x6297, 0xF9F8, 0x676D, 0xF9F9, 0x6841, 0xF9FA, 0x6C86, 0xF9FB, 0x6E2F, 0xF9FC, 0x7F38, 0xF9FD, 0x809B, 0xF9FE, 0x822A, + 0xFAA1, 0xFA08, 0xFAA2, 0xFA09, 0xFAA3, 0x9805, 0xFAA4, 0x4EA5, 0xFAA5, 0x5055, 0xFAA6, 0x54B3, 0xFAA7, 0x5793, 0xFAA8, 0x595A, + 0xFAA9, 0x5B69, 0xFAAA, 0x5BB3, 0xFAAB, 0x61C8, 0xFAAC, 0x6977, 0xFAAD, 0x6D77, 0xFAAE, 0x7023, 0xFAAF, 0x87F9, 0xFAB0, 0x89E3, + 0xFAB1, 0x8A72, 0xFAB2, 0x8AE7, 0xFAB3, 0x9082, 0xFAB4, 0x99ED, 0xFAB5, 0x9AB8, 0xFAB6, 0x52BE, 0xFAB7, 0x6838, 0xFAB8, 0x5016, + 0xFAB9, 0x5E78, 0xFABA, 0x674F, 0xFABB, 0x8347, 0xFABC, 0x884C, 0xFABD, 0x4EAB, 0xFABE, 0x5411, 0xFABF, 0x56AE, 0xFAC0, 0x73E6, + 0xFAC1, 0x9115, 0xFAC2, 0x97FF, 0xFAC3, 0x9909, 0xFAC4, 0x9957, 0xFAC5, 0x9999, 0xFAC6, 0x5653, 0xFAC7, 0x589F, 0xFAC8, 0x865B, + 0xFAC9, 0x8A31, 0xFACA, 0x61B2, 0xFACB, 0x6AF6, 0xFACC, 0x737B, 0xFACD, 0x8ED2, 0xFACE, 0x6B47, 0xFACF, 0x96AA, 0xFAD0, 0x9A57, + 0xFAD1, 0x5955, 0xFAD2, 0x7200, 0xFAD3, 0x8D6B, 0xFAD4, 0x9769, 0xFAD5, 0x4FD4, 0xFAD6, 0x5CF4, 0xFAD7, 0x5F26, 0xFAD8, 0x61F8, + 0xFAD9, 0x665B, 0xFADA, 0x6CEB, 0xFADB, 0x70AB, 0xFADC, 0x7384, 0xFADD, 0x73B9, 0xFADE, 0x73FE, 0xFADF, 0x7729, 0xFAE0, 0x774D, + 0xFAE1, 0x7D43, 0xFAE2, 0x7D62, 0xFAE3, 0x7E23, 0xFAE4, 0x8237, 0xFAE5, 0x8852, 0xFAE6, 0xFA0A, 0xFAE7, 0x8CE2, 0xFAE8, 0x9249, + 0xFAE9, 0x986F, 0xFAEA, 0x5B51, 0xFAEB, 0x7A74, 0xFAEC, 0x8840, 0xFAED, 0x9801, 0xFAEE, 0x5ACC, 0xFAEF, 0x4FE0, 0xFAF0, 0x5354, + 0xFAF1, 0x593E, 0xFAF2, 0x5CFD, 0xFAF3, 0x633E, 0xFAF4, 0x6D79, 0xFAF5, 0x72F9, 0xFAF6, 0x8105, 0xFAF7, 0x8107, 0xFAF8, 0x83A2, + 0xFAF9, 0x92CF, 0xFAFA, 0x9830, 0xFAFB, 0x4EA8, 0xFAFC, 0x5144, 0xFAFD, 0x5211, 0xFAFE, 0x578B, 0xFBA1, 0x5F62, 0xFBA2, 0x6CC2, + 0xFBA3, 0x6ECE, 0xFBA4, 0x7005, 0xFBA5, 0x7050, 0xFBA6, 0x70AF, 0xFBA7, 0x7192, 0xFBA8, 0x73E9, 0xFBA9, 0x7469, 0xFBAA, 0x834A, + 0xFBAB, 0x87A2, 0xFBAC, 0x8861, 0xFBAD, 0x9008, 0xFBAE, 0x90A2, 0xFBAF, 0x93A3, 0xFBB0, 0x99A8, 0xFBB1, 0x516E, 0xFBB2, 0x5F57, + 0xFBB3, 0x60E0, 0xFBB4, 0x6167, 0xFBB5, 0x66B3, 0xFBB6, 0x8559, 0xFBB7, 0x8E4A, 0xFBB8, 0x91AF, 0xFBB9, 0x978B, 0xFBBA, 0x4E4E, + 0xFBBB, 0x4E92, 0xFBBC, 0x547C, 0xFBBD, 0x58D5, 0xFBBE, 0x58FA, 0xFBBF, 0x597D, 0xFBC0, 0x5CB5, 0xFBC1, 0x5F27, 0xFBC2, 0x6236, + 0xFBC3, 0x6248, 0xFBC4, 0x660A, 0xFBC5, 0x6667, 0xFBC6, 0x6BEB, 0xFBC7, 0x6D69, 0xFBC8, 0x6DCF, 0xFBC9, 0x6E56, 0xFBCA, 0x6EF8, + 0xFBCB, 0x6F94, 0xFBCC, 0x6FE0, 0xFBCD, 0x6FE9, 0xFBCE, 0x705D, 0xFBCF, 0x72D0, 0xFBD0, 0x7425, 0xFBD1, 0x745A, 0xFBD2, 0x74E0, + 0xFBD3, 0x7693, 0xFBD4, 0x795C, 0xFBD5, 0x7CCA, 0xFBD6, 0x7E1E, 0xFBD7, 0x80E1, 0xFBD8, 0x82A6, 0xFBD9, 0x846B, 0xFBDA, 0x84BF, + 0xFBDB, 0x864E, 0xFBDC, 0x865F, 0xFBDD, 0x8774, 0xFBDE, 0x8B77, 0xFBDF, 0x8C6A, 0xFBE0, 0x93AC, 0xFBE1, 0x9800, 0xFBE2, 0x9865, + 0xFBE3, 0x60D1, 0xFBE4, 0x6216, 0xFBE5, 0x9177, 0xFBE6, 0x5A5A, 0xFBE7, 0x660F, 0xFBE8, 0x6DF7, 0xFBE9, 0x6E3E, 0xFBEA, 0x743F, + 0xFBEB, 0x9B42, 0xFBEC, 0x5FFD, 0xFBED, 0x60DA, 0xFBEE, 0x7B0F, 0xFBEF, 0x54C4, 0xFBF0, 0x5F18, 0xFBF1, 0x6C5E, 0xFBF2, 0x6CD3, + 0xFBF3, 0x6D2A, 0xFBF4, 0x70D8, 0xFBF5, 0x7D05, 0xFBF6, 0x8679, 0xFBF7, 0x8A0C, 0xFBF8, 0x9D3B, 0xFBF9, 0x5316, 0xFBFA, 0x548C, + 0xFBFB, 0x5B05, 0xFBFC, 0x6A3A, 0xFBFD, 0x706B, 0xFBFE, 0x7575, 0xFCA1, 0x798D, 0xFCA2, 0x79BE, 0xFCA3, 0x82B1, 0xFCA4, 0x83EF, + 0xFCA5, 0x8A71, 0xFCA6, 0x8B41, 0xFCA7, 0x8CA8, 0xFCA8, 0x9774, 0xFCA9, 0xFA0B, 0xFCAA, 0x64F4, 0xFCAB, 0x652B, 0xFCAC, 0x78BA, + 0xFCAD, 0x78BB, 0xFCAE, 0x7A6B, 0xFCAF, 0x4E38, 0xFCB0, 0x559A, 0xFCB1, 0x5950, 0xFCB2, 0x5BA6, 0xFCB3, 0x5E7B, 0xFCB4, 0x60A3, + 0xFCB5, 0x63DB, 0xFCB6, 0x6B61, 0xFCB7, 0x6665, 0xFCB8, 0x6853, 0xFCB9, 0x6E19, 0xFCBA, 0x7165, 0xFCBB, 0x74B0, 0xFCBC, 0x7D08, + 0xFCBD, 0x9084, 0xFCBE, 0x9A69, 0xFCBF, 0x9C25, 0xFCC0, 0x6D3B, 0xFCC1, 0x6ED1, 0xFCC2, 0x733E, 0xFCC3, 0x8C41, 0xFCC4, 0x95CA, + 0xFCC5, 0x51F0, 0xFCC6, 0x5E4C, 0xFCC7, 0x5FA8, 0xFCC8, 0x604D, 0xFCC9, 0x60F6, 0xFCCA, 0x6130, 0xFCCB, 0x614C, 0xFCCC, 0x6643, + 0xFCCD, 0x6644, 0xFCCE, 0x69A5, 0xFCCF, 0x6CC1, 0xFCD0, 0x6E5F, 0xFCD1, 0x6EC9, 0xFCD2, 0x6F62, 0xFCD3, 0x714C, 0xFCD4, 0x749C, + 0xFCD5, 0x7687, 0xFCD6, 0x7BC1, 0xFCD7, 0x7C27, 0xFCD8, 0x8352, 0xFCD9, 0x8757, 0xFCDA, 0x9051, 0xFCDB, 0x968D, 0xFCDC, 0x9EC3, + 0xFCDD, 0x532F, 0xFCDE, 0x56DE, 0xFCDF, 0x5EFB, 0xFCE0, 0x5F8A, 0xFCE1, 0x6062, 0xFCE2, 0x6094, 0xFCE3, 0x61F7, 0xFCE4, 0x6666, + 0xFCE5, 0x6703, 0xFCE6, 0x6A9C, 0xFCE7, 0x6DEE, 0xFCE8, 0x6FAE, 0xFCE9, 0x7070, 0xFCEA, 0x736A, 0xFCEB, 0x7E6A, 0xFCEC, 0x81BE, + 0xFCED, 0x8334, 0xFCEE, 0x86D4, 0xFCEF, 0x8AA8, 0xFCF0, 0x8CC4, 0xFCF1, 0x5283, 0xFCF2, 0x7372, 0xFCF3, 0x5B96, 0xFCF4, 0x6A6B, + 0xFCF5, 0x9404, 0xFCF6, 0x54EE, 0xFCF7, 0x5686, 0xFCF8, 0x5B5D, 0xFCF9, 0x6548, 0xFCFA, 0x6585, 0xFCFB, 0x66C9, 0xFCFC, 0x689F, + 0xFCFD, 0x6D8D, 0xFCFE, 0x6DC6, 0xFDA1, 0x723B, 0xFDA2, 0x80B4, 0xFDA3, 0x9175, 0xFDA4, 0x9A4D, 0xFDA5, 0x4FAF, 0xFDA6, 0x5019, + 0xFDA7, 0x539A, 0xFDA8, 0x540E, 0xFDA9, 0x543C, 0xFDAA, 0x5589, 0xFDAB, 0x55C5, 0xFDAC, 0x5E3F, 0xFDAD, 0x5F8C, 0xFDAE, 0x673D, + 0xFDAF, 0x7166, 0xFDB0, 0x73DD, 0xFDB1, 0x9005, 0xFDB2, 0x52DB, 0xFDB3, 0x52F3, 0xFDB4, 0x5864, 0xFDB5, 0x58CE, 0xFDB6, 0x7104, + 0xFDB7, 0x718F, 0xFDB8, 0x71FB, 0xFDB9, 0x85B0, 0xFDBA, 0x8A13, 0xFDBB, 0x6688, 0xFDBC, 0x85A8, 0xFDBD, 0x55A7, 0xFDBE, 0x6684, + 0xFDBF, 0x714A, 0xFDC0, 0x8431, 0xFDC1, 0x5349, 0xFDC2, 0x5599, 0xFDC3, 0x6BC1, 0xFDC4, 0x5F59, 0xFDC5, 0x5FBD, 0xFDC6, 0x63EE, + 0xFDC7, 0x6689, 0xFDC8, 0x7147, 0xFDC9, 0x8AF1, 0xFDCA, 0x8F1D, 0xFDCB, 0x9EBE, 0xFDCC, 0x4F11, 0xFDCD, 0x643A, 0xFDCE, 0x70CB, + 0xFDCF, 0x7566, 0xFDD0, 0x8667, 0xFDD1, 0x6064, 0xFDD2, 0x8B4E, 0xFDD3, 0x9DF8, 0xFDD4, 0x5147, 0xFDD5, 0x51F6, 0xFDD6, 0x5308, + 0xFDD7, 0x6D36, 0xFDD8, 0x80F8, 0xFDD9, 0x9ED1, 0xFDDA, 0x6615, 0xFDDB, 0x6B23, 0xFDDC, 0x7098, 0xFDDD, 0x75D5, 0xFDDE, 0x5403, + 0xFDDF, 0x5C79, 0xFDE0, 0x7D07, 0xFDE1, 0x8A16, 0xFDE2, 0x6B20, 0xFDE3, 0x6B3D, 0xFDE4, 0x6B46, 0xFDE5, 0x5438, 0xFDE6, 0x6070, + 0xFDE7, 0x6D3D, 0xFDE8, 0x7FD5, 0xFDE9, 0x8208, 0xFDEA, 0x50D6, 0xFDEB, 0x51DE, 0xFDEC, 0x559C, 0xFDED, 0x566B, 0xFDEE, 0x56CD, + 0xFDEF, 0x59EC, 0xFDF0, 0x5B09, 0xFDF1, 0x5E0C, 0xFDF2, 0x6199, 0xFDF3, 0x6198, 0xFDF4, 0x6231, 0xFDF5, 0x665E, 0xFDF6, 0x66E6, + 0xFDF7, 0x7199, 0xFDF8, 0x71B9, 0xFDF9, 0x71BA, 0xFDFA, 0x72A7, 0xFDFB, 0x79A7, 0xFDFC, 0x7A00, 0xFDFD, 0x7FB2, 0xFDFE, 0x8A70, + 0, 0 +}; +#endif + +#if FF_CODE_PAGE == 950 || FF_CODE_PAGE == 0 /* Traditional Chinese */ +static const WCHAR uni2oem950[] = { /* Unicode --> Big5 pairs */ + 0x00A7, 0xA1B1, 0x00AF, 0xA1C2, 0x00B0, 0xA258, 0x00B1, 0xA1D3, 0x00B7, 0xA150, 0x00D7, 0xA1D1, 0x00F7, 0xA1D2, 0x02C7, 0xA3BE, + 0x02C9, 0xA3BC, 0x02CA, 0xA3BD, 0x02CB, 0xA3BF, 0x02CD, 0xA1C5, 0x02D9, 0xA3BB, 0x0391, 0xA344, 0x0392, 0xA345, 0x0393, 0xA346, + 0x0394, 0xA347, 0x0395, 0xA348, 0x0396, 0xA349, 0x0397, 0xA34A, 0x0398, 0xA34B, 0x0399, 0xA34C, 0x039A, 0xA34D, 0x039B, 0xA34E, + 0x039C, 0xA34F, 0x039D, 0xA350, 0x039E, 0xA351, 0x039F, 0xA352, 0x03A0, 0xA353, 0x03A1, 0xA354, 0x03A3, 0xA355, 0x03A4, 0xA356, + 0x03A5, 0xA357, 0x03A6, 0xA358, 0x03A7, 0xA359, 0x03A8, 0xA35A, 0x03A9, 0xA35B, 0x03B1, 0xA35C, 0x03B2, 0xA35D, 0x03B3, 0xA35E, + 0x03B4, 0xA35F, 0x03B5, 0xA360, 0x03B6, 0xA361, 0x03B7, 0xA362, 0x03B8, 0xA363, 0x03B9, 0xA364, 0x03BA, 0xA365, 0x03BB, 0xA366, + 0x03BC, 0xA367, 0x03BD, 0xA368, 0x03BE, 0xA369, 0x03BF, 0xA36A, 0x03C0, 0xA36B, 0x03C1, 0xA36C, 0x03C3, 0xA36D, 0x03C4, 0xA36E, + 0x03C5, 0xA36F, 0x03C6, 0xA370, 0x03C7, 0xA371, 0x03C8, 0xA372, 0x03C9, 0xA373, 0x2013, 0xA156, 0x2014, 0xA158, 0x2018, 0xA1A5, + 0x2019, 0xA1A6, 0x201C, 0xA1A7, 0x201D, 0xA1A8, 0x2025, 0xA14C, 0x2026, 0xA14B, 0x2027, 0xA145, 0x2032, 0xA1AC, 0x2035, 0xA1AB, + 0x203B, 0xA1B0, 0x20AC, 0xA3E1, 0x2103, 0xA24A, 0x2105, 0xA1C1, 0x2109, 0xA24B, 0x2160, 0xA2B9, 0x2161, 0xA2BA, 0x2162, 0xA2BB, + 0x2163, 0xA2BC, 0x2164, 0xA2BD, 0x2165, 0xA2BE, 0x2166, 0xA2BF, 0x2167, 0xA2C0, 0x2168, 0xA2C1, 0x2169, 0xA2C2, 0x2190, 0xA1F6, + 0x2191, 0xA1F4, 0x2192, 0xA1F7, 0x2193, 0xA1F5, 0x2196, 0xA1F8, 0x2197, 0xA1F9, 0x2198, 0xA1FB, 0x2199, 0xA1FA, 0x2215, 0xA241, + 0x221A, 0xA1D4, 0x221E, 0xA1DB, 0x221F, 0xA1E8, 0x2220, 0xA1E7, 0x2223, 0xA1FD, 0x2225, 0xA1FC, 0x2229, 0xA1E4, 0x222A, 0xA1E5, + 0x222B, 0xA1EC, 0x222E, 0xA1ED, 0x2234, 0xA1EF, 0x2235, 0xA1EE, 0x2252, 0xA1DC, 0x2260, 0xA1DA, 0x2261, 0xA1DD, 0x2266, 0xA1D8, + 0x2267, 0xA1D9, 0x2295, 0xA1F2, 0x2299, 0xA1F3, 0x22A5, 0xA1E6, 0x22BF, 0xA1E9, 0x2500, 0xA277, 0x2502, 0xA278, 0x250C, 0xA27A, + 0x2510, 0xA27B, 0x2514, 0xA27C, 0x2518, 0xA27D, 0x251C, 0xA275, 0x2524, 0xA274, 0x252C, 0xA273, 0x2534, 0xA272, 0x253C, 0xA271, + 0x2550, 0xA2A4, 0x2550, 0xF9F9, 0x2551, 0xF9F8, 0x2552, 0xF9E6, 0x2553, 0xF9EF, 0x2554, 0xF9DD, 0x2555, 0xF9E8, 0x2556, 0xF9F1, + 0x2557, 0xF9DF, 0x2558, 0xF9EC, 0x2559, 0xF9F5, 0x255A, 0xF9E3, 0x255B, 0xF9EE, 0x255C, 0xF9F7, 0x255D, 0xF9E5, 0x255E, 0xA2A5, + 0x255E, 0xF9E9, 0x255F, 0xF9F2, 0x2560, 0xF9E0, 0x2561, 0xA2A7, 0x2561, 0xF9EB, 0x2562, 0xF9F4, 0x2563, 0xF9E2, 0x2564, 0xF9E7, + 0x2565, 0xF9F0, 0x2566, 0xF9DE, 0x2567, 0xF9ED, 0x2568, 0xF9F6, 0x2569, 0xF9E4, 0x256A, 0xA2A6, 0x256A, 0xF9EA, 0x256B, 0xF9F3, + 0x256C, 0xF9E1, 0x256D, 0xA27E, 0x256D, 0xF9FA, 0x256E, 0xA2A1, 0x256E, 0xF9FB, 0x256F, 0xA2A3, 0x256F, 0xF9FD, 0x2570, 0xA2A2, + 0x2570, 0xF9FC, 0x2571, 0xA2AC, 0x2572, 0xA2AD, 0x2573, 0xA2AE, 0x2574, 0xA15A, 0x2581, 0xA262, 0x2582, 0xA263, 0x2583, 0xA264, + 0x2584, 0xA265, 0x2585, 0xA266, 0x2586, 0xA267, 0x2587, 0xA268, 0x2588, 0xA269, 0x2589, 0xA270, 0x258A, 0xA26F, 0x258B, 0xA26E, + 0x258C, 0xA26D, 0x258D, 0xA26C, 0x258E, 0xA26B, 0x258F, 0xA26A, 0x2593, 0xF9FE, 0x2594, 0xA276, 0x2595, 0xA279, 0x25A0, 0xA1BD, + 0x25A1, 0xA1BC, 0x25B2, 0xA1B6, 0x25B3, 0xA1B5, 0x25BC, 0xA1BF, 0x25BD, 0xA1BE, 0x25C6, 0xA1BB, 0x25C7, 0xA1BA, 0x25CB, 0xA1B3, + 0x25CE, 0xA1B7, 0x25CF, 0xA1B4, 0x25E2, 0xA2A8, 0x25E3, 0xA2A9, 0x25E4, 0xA2AB, 0x25E5, 0xA2AA, 0x2605, 0xA1B9, 0x2606, 0xA1B8, + 0x2640, 0xA1F0, 0x2642, 0xA1F1, 0x3000, 0xA140, 0x3001, 0xA142, 0x3002, 0xA143, 0x3003, 0xA1B2, 0x3008, 0xA171, 0x3009, 0xA172, + 0x300A, 0xA16D, 0x300B, 0xA16E, 0x300C, 0xA175, 0x300D, 0xA176, 0x300E, 0xA179, 0x300F, 0xA17A, 0x3010, 0xA169, 0x3011, 0xA16A, + 0x3012, 0xA245, 0x3014, 0xA165, 0x3015, 0xA166, 0x301D, 0xA1A9, 0x301E, 0xA1AA, 0x3021, 0xA2C3, 0x3022, 0xA2C4, 0x3023, 0xA2C5, + 0x3024, 0xA2C6, 0x3025, 0xA2C7, 0x3026, 0xA2C8, 0x3027, 0xA2C9, 0x3028, 0xA2CA, 0x3029, 0xA2CB, 0x3105, 0xA374, 0x3106, 0xA375, + 0x3107, 0xA376, 0x3108, 0xA377, 0x3109, 0xA378, 0x310A, 0xA379, 0x310B, 0xA37A, 0x310C, 0xA37B, 0x310D, 0xA37C, 0x310E, 0xA37D, + 0x310F, 0xA37E, 0x3110, 0xA3A1, 0x3111, 0xA3A2, 0x3112, 0xA3A3, 0x3113, 0xA3A4, 0x3114, 0xA3A5, 0x3115, 0xA3A6, 0x3116, 0xA3A7, + 0x3117, 0xA3A8, 0x3118, 0xA3A9, 0x3119, 0xA3AA, 0x311A, 0xA3AB, 0x311B, 0xA3AC, 0x311C, 0xA3AD, 0x311D, 0xA3AE, 0x311E, 0xA3AF, + 0x311F, 0xA3B0, 0x3120, 0xA3B1, 0x3121, 0xA3B2, 0x3122, 0xA3B3, 0x3123, 0xA3B4, 0x3124, 0xA3B5, 0x3125, 0xA3B6, 0x3126, 0xA3B7, + 0x3127, 0xA3B8, 0x3128, 0xA3B9, 0x3129, 0xA3BA, 0x32A3, 0xA1C0, 0x338E, 0xA255, 0x338F, 0xA256, 0x339C, 0xA250, 0x339D, 0xA251, + 0x339E, 0xA252, 0x33A1, 0xA254, 0x33C4, 0xA257, 0x33CE, 0xA253, 0x33D1, 0xA1EB, 0x33D2, 0xA1EA, 0x33D5, 0xA24F, 0x4E00, 0xA440, + 0x4E01, 0xA442, 0x4E03, 0xA443, 0x4E07, 0xC945, 0x4E08, 0xA456, 0x4E09, 0xA454, 0x4E0A, 0xA457, 0x4E0B, 0xA455, 0x4E0C, 0xC946, + 0x4E0D, 0xA4A3, 0x4E0E, 0xC94F, 0x4E0F, 0xC94D, 0x4E10, 0xA4A2, 0x4E11, 0xA4A1, 0x4E14, 0xA542, 0x4E15, 0xA541, 0x4E16, 0xA540, + 0x4E18, 0xA543, 0x4E19, 0xA4FE, 0x4E1E, 0xA5E0, 0x4E1F, 0xA5E1, 0x4E26, 0xA8C3, 0x4E2B, 0xA458, 0x4E2D, 0xA4A4, 0x4E2E, 0xC950, + 0x4E30, 0xA4A5, 0x4E31, 0xC963, 0x4E32, 0xA6EA, 0x4E33, 0xCBB1, 0x4E38, 0xA459, 0x4E39, 0xA4A6, 0x4E3B, 0xA544, 0x4E3C, 0xC964, + 0x4E42, 0xC940, 0x4E43, 0xA444, 0x4E45, 0xA45B, 0x4E47, 0xC947, 0x4E48, 0xA45C, 0x4E4B, 0xA4A7, 0x4E4D, 0xA545, 0x4E4E, 0xA547, + 0x4E4F, 0xA546, 0x4E52, 0xA5E2, 0x4E53, 0xA5E3, 0x4E56, 0xA8C4, 0x4E58, 0xADBC, 0x4E59, 0xA441, 0x4E5C, 0xC941, 0x4E5D, 0xA445, + 0x4E5E, 0xA45E, 0x4E5F, 0xA45D, 0x4E69, 0xA5E4, 0x4E73, 0xA8C5, 0x4E7E, 0xB0AE, 0x4E7F, 0xD44B, 0x4E82, 0xB6C3, 0x4E83, 0xDCB1, + 0x4E84, 0xDCB2, 0x4E86, 0xA446, 0x4E88, 0xA4A9, 0x4E8B, 0xA8C6, 0x4E8C, 0xA447, 0x4E8D, 0xC948, 0x4E8E, 0xA45F, 0x4E91, 0xA4AA, + 0x4E92, 0xA4AC, 0x4E93, 0xC951, 0x4E94, 0xA4AD, 0x4E95, 0xA4AB, 0x4E99, 0xA5E5, 0x4E9B, 0xA8C7, 0x4E9E, 0xA8C8, 0x4E9F, 0xAB45, + 0x4EA1, 0xA460, 0x4EA2, 0xA4AE, 0x4EA4, 0xA5E6, 0x4EA5, 0xA5E8, 0x4EA6, 0xA5E7, 0x4EA8, 0xA6EB, 0x4EAB, 0xA8C9, 0x4EAC, 0xA8CA, + 0x4EAD, 0xAB46, 0x4EAE, 0xAB47, 0x4EB3, 0xADBD, 0x4EB6, 0xDCB3, 0x4EB9, 0xF6D6, 0x4EBA, 0xA448, 0x4EC0, 0xA4B0, 0x4EC1, 0xA4AF, + 0x4EC2, 0xC952, 0x4EC3, 0xA4B1, 0x4EC4, 0xA4B7, 0x4EC6, 0xA4B2, 0x4EC7, 0xA4B3, 0x4EC8, 0xC954, 0x4EC9, 0xC953, 0x4ECA, 0xA4B5, + 0x4ECB, 0xA4B6, 0x4ECD, 0xA4B4, 0x4ED4, 0xA54A, 0x4ED5, 0xA54B, 0x4ED6, 0xA54C, 0x4ED7, 0xA54D, 0x4ED8, 0xA549, 0x4ED9, 0xA550, + 0x4EDA, 0xC96A, 0x4EDC, 0xC966, 0x4EDD, 0xC969, 0x4EDE, 0xA551, 0x4EDF, 0xA561, 0x4EE1, 0xC968, 0x4EE3, 0xA54E, 0x4EE4, 0xA54F, + 0x4EE5, 0xA548, 0x4EE8, 0xC965, 0x4EE9, 0xC967, 0x4EF0, 0xA5F5, 0x4EF1, 0xC9B0, 0x4EF2, 0xA5F2, 0x4EF3, 0xA5F6, 0x4EF4, 0xC9BA, + 0x4EF5, 0xC9AE, 0x4EF6, 0xA5F3, 0x4EF7, 0xC9B2, 0x4EFB, 0xA5F4, 0x4EFD, 0xA5F7, 0x4EFF, 0xA5E9, 0x4F00, 0xC9B1, 0x4F01, 0xA5F8, + 0x4F02, 0xC9B5, 0x4F04, 0xC9B9, 0x4F05, 0xC9B6, 0x4F08, 0xC9B3, 0x4F09, 0xA5EA, 0x4F0A, 0xA5EC, 0x4F0B, 0xA5F9, 0x4F0D, 0xA5EE, + 0x4F0E, 0xC9AB, 0x4F0F, 0xA5F1, 0x4F10, 0xA5EF, 0x4F11, 0xA5F0, 0x4F12, 0xC9BB, 0x4F13, 0xC9B8, 0x4F14, 0xC9AF, 0x4F15, 0xA5ED, + 0x4F18, 0xC9AC, 0x4F19, 0xA5EB, 0x4F1D, 0xC9B4, 0x4F22, 0xC9B7, 0x4F2C, 0xC9AD, 0x4F2D, 0xCA66, 0x4F2F, 0xA742, 0x4F30, 0xA6F4, + 0x4F33, 0xCA67, 0x4F34, 0xA6F1, 0x4F36, 0xA744, 0x4F38, 0xA6F9, 0x4F3A, 0xA6F8, 0x4F3B, 0xCA5B, 0x4F3C, 0xA6FC, 0x4F3D, 0xA6F7, + 0x4F3E, 0xCA60, 0x4F3F, 0xCA68, 0x4F41, 0xCA64, 0x4F43, 0xA6FA, 0x4F46, 0xA6FD, 0x4F47, 0xA6EE, 0x4F48, 0xA747, 0x4F49, 0xCA5D, + 0x4F4C, 0xCBBD, 0x4F4D, 0xA6EC, 0x4F4E, 0xA743, 0x4F4F, 0xA6ED, 0x4F50, 0xA6F5, 0x4F51, 0xA6F6, 0x4F52, 0xCA62, 0x4F53, 0xCA5E, + 0x4F54, 0xA6FB, 0x4F55, 0xA6F3, 0x4F56, 0xCA5A, 0x4F57, 0xA6EF, 0x4F58, 0xCA65, 0x4F59, 0xA745, 0x4F5A, 0xA748, 0x4F5B, 0xA6F2, + 0x4F5C, 0xA740, 0x4F5D, 0xA746, 0x4F5E, 0xA6F0, 0x4F5F, 0xCA63, 0x4F60, 0xA741, 0x4F61, 0xCA69, 0x4F62, 0xCA5C, 0x4F63, 0xA6FE, + 0x4F64, 0xCA5F, 0x4F67, 0xCA61, 0x4F69, 0xA8D8, 0x4F6A, 0xCBBF, 0x4F6B, 0xCBCB, 0x4F6C, 0xA8D0, 0x4F6E, 0xCBCC, 0x4F6F, 0xA8CB, + 0x4F70, 0xA8D5, 0x4F73, 0xA8CE, 0x4F74, 0xCBB9, 0x4F75, 0xA8D6, 0x4F76, 0xCBB8, 0x4F77, 0xCBBC, 0x4F78, 0xCBC3, 0x4F79, 0xCBC1, + 0x4F7A, 0xA8DE, 0x4F7B, 0xA8D9, 0x4F7C, 0xCBB3, 0x4F7D, 0xCBB5, 0x4F7E, 0xA8DB, 0x4F7F, 0xA8CF, 0x4F80, 0xCBB6, 0x4F81, 0xCBC2, + 0x4F82, 0xCBC9, 0x4F83, 0xA8D4, 0x4F84, 0xCBBB, 0x4F85, 0xCBB4, 0x4F86, 0xA8D3, 0x4F87, 0xCBB7, 0x4F88, 0xA8D7, 0x4F89, 0xCBBA, + 0x4F8B, 0xA8D2, 0x4F8D, 0xA8CD, 0x4F8F, 0xA8DC, 0x4F90, 0xCBC4, 0x4F91, 0xA8DD, 0x4F92, 0xCBC8, 0x4F94, 0xCBC6, 0x4F95, 0xCBCA, + 0x4F96, 0xA8DA, 0x4F97, 0xCBBE, 0x4F98, 0xCBB2, 0x4F9A, 0xCBC0, 0x4F9B, 0xA8D1, 0x4F9C, 0xCBC5, 0x4F9D, 0xA8CC, 0x4F9E, 0xCBC7, + 0x4FAE, 0xAB56, 0x4FAF, 0xAB4A, 0x4FB2, 0xCDE0, 0x4FB3, 0xCDE8, 0x4FB5, 0xAB49, 0x4FB6, 0xAB51, 0x4FB7, 0xAB5D, 0x4FB9, 0xCDEE, + 0x4FBA, 0xCDEC, 0x4FBB, 0xCDE7, 0x4FBF, 0xAB4B, 0x4FC0, 0xCDED, 0x4FC1, 0xCDE3, 0x4FC2, 0xAB59, 0x4FC3, 0xAB50, 0x4FC4, 0xAB58, + 0x4FC5, 0xCDDE, 0x4FC7, 0xCDEA, 0x4FC9, 0xCDE1, 0x4FCA, 0xAB54, 0x4FCB, 0xCDE2, 0x4FCD, 0xCDDD, 0x4FCE, 0xAB5B, 0x4FCF, 0xAB4E, + 0x4FD0, 0xAB57, 0x4FD1, 0xAB4D, 0x4FD3, 0xCDDF, 0x4FD4, 0xCDE4, 0x4FD6, 0xCDEB, 0x4FD7, 0xAB55, 0x4FD8, 0xAB52, 0x4FD9, 0xCDE6, + 0x4FDA, 0xAB5A, 0x4FDB, 0xCDE9, 0x4FDC, 0xCDE5, 0x4FDD, 0xAB4F, 0x4FDE, 0xAB5C, 0x4FDF, 0xAB53, 0x4FE0, 0xAB4C, 0x4FE1, 0xAB48, + 0x4FEC, 0xCDEF, 0x4FEE, 0xADD7, 0x4FEF, 0xADC1, 0x4FF1, 0xADD1, 0x4FF3, 0xADD6, 0x4FF4, 0xD0D0, 0x4FF5, 0xD0CF, 0x4FF6, 0xD0D4, + 0x4FF7, 0xD0D5, 0x4FF8, 0xADC4, 0x4FFA, 0xADCD, 0x4FFE, 0xADDA, 0x5000, 0xADCE, 0x5005, 0xD0C9, 0x5006, 0xADC7, 0x5007, 0xD0CA, + 0x5009, 0xADDC, 0x500B, 0xADD3, 0x500C, 0xADBE, 0x500D, 0xADBF, 0x500E, 0xD0DD, 0x500F, 0xB0BF, 0x5011, 0xADCC, 0x5012, 0xADCB, + 0x5013, 0xD0CB, 0x5014, 0xADCF, 0x5015, 0xD45B, 0x5016, 0xADC6, 0x5017, 0xD0D6, 0x5018, 0xADD5, 0x5019, 0xADD4, 0x501A, 0xADCA, + 0x501B, 0xD0CE, 0x501C, 0xD0D7, 0x501E, 0xD0C8, 0x501F, 0xADC9, 0x5020, 0xD0D8, 0x5021, 0xADD2, 0x5022, 0xD0CC, 0x5023, 0xADC0, + 0x5025, 0xADC3, 0x5026, 0xADC2, 0x5027, 0xD0D9, 0x5028, 0xADD0, 0x5029, 0xADC5, 0x502A, 0xADD9, 0x502B, 0xADDB, 0x502C, 0xD0D3, + 0x502D, 0xADD8, 0x502F, 0xD0DB, 0x5030, 0xD0CD, 0x5031, 0xD0DC, 0x5033, 0xD0D1, 0x5035, 0xD0DA, 0x5037, 0xD0D2, 0x503C, 0xADC8, + 0x5040, 0xD463, 0x5041, 0xD457, 0x5043, 0xB0B3, 0x5045, 0xD45C, 0x5046, 0xD462, 0x5047, 0xB0B2, 0x5048, 0xD455, 0x5049, 0xB0B6, + 0x504A, 0xD459, 0x504B, 0xD452, 0x504C, 0xB0B4, 0x504D, 0xD456, 0x504E, 0xB0B9, 0x504F, 0xB0BE, 0x5051, 0xD467, 0x5053, 0xD451, + 0x5055, 0xB0BA, 0x5057, 0xD466, 0x505A, 0xB0B5, 0x505B, 0xD458, 0x505C, 0xB0B1, 0x505D, 0xD453, 0x505E, 0xD44F, 0x505F, 0xD45D, + 0x5060, 0xD450, 0x5061, 0xD44E, 0x5062, 0xD45A, 0x5063, 0xD460, 0x5064, 0xD461, 0x5065, 0xB0B7, 0x5068, 0xD85B, 0x5069, 0xD45E, + 0x506A, 0xD44D, 0x506B, 0xD45F, 0x506D, 0xB0C1, 0x506E, 0xD464, 0x506F, 0xB0C0, 0x5070, 0xD44C, 0x5072, 0xD454, 0x5073, 0xD465, + 0x5074, 0xB0BC, 0x5075, 0xB0BB, 0x5076, 0xB0B8, 0x5077, 0xB0BD, 0x507A, 0xB0AF, 0x507D, 0xB0B0, 0x5080, 0xB3C8, 0x5082, 0xD85E, + 0x5083, 0xD857, 0x5085, 0xB3C5, 0x5087, 0xD85F, 0x508B, 0xD855, 0x508C, 0xD858, 0x508D, 0xB3C4, 0x508E, 0xD859, 0x5091, 0xB3C7, + 0x5092, 0xD85D, 0x5094, 0xD853, 0x5095, 0xD852, 0x5096, 0xB3C9, 0x5098, 0xB3CA, 0x5099, 0xB3C6, 0x509A, 0xB3CB, 0x509B, 0xD851, + 0x509C, 0xD85C, 0x509D, 0xD85A, 0x509E, 0xD854, 0x50A2, 0xB3C3, 0x50A3, 0xD856, 0x50AC, 0xB6CA, 0x50AD, 0xB6C4, 0x50AE, 0xDCB7, + 0x50AF, 0xB6CD, 0x50B0, 0xDCBD, 0x50B1, 0xDCC0, 0x50B2, 0xB6C6, 0x50B3, 0xB6C7, 0x50B4, 0xDCBA, 0x50B5, 0xB6C5, 0x50B6, 0xDCC3, + 0x50B7, 0xB6CB, 0x50B8, 0xDCC4, 0x50BA, 0xDCBF, 0x50BB, 0xB6CC, 0x50BD, 0xDCB4, 0x50BE, 0xB6C9, 0x50BF, 0xDCB5, 0x50C1, 0xDCBE, + 0x50C2, 0xDCBC, 0x50C4, 0xDCB8, 0x50C5, 0xB6C8, 0x50C6, 0xDCB6, 0x50C7, 0xB6CE, 0x50C8, 0xDCBB, 0x50C9, 0xDCC2, 0x50CA, 0xDCB9, + 0x50CB, 0xDCC1, 0x50CE, 0xB9B6, 0x50CF, 0xB9B3, 0x50D1, 0xB9B4, 0x50D3, 0xE0F9, 0x50D4, 0xE0F1, 0x50D5, 0xB9B2, 0x50D6, 0xB9AF, + 0x50D7, 0xE0F2, 0x50DA, 0xB9B1, 0x50DB, 0xE0F5, 0x50DD, 0xE0F7, 0x50E0, 0xE0FE, 0x50E3, 0xE0FD, 0x50E4, 0xE0F8, 0x50E5, 0xB9AE, + 0x50E6, 0xE0F0, 0x50E7, 0xB9AC, 0x50E8, 0xE0F3, 0x50E9, 0xB9B7, 0x50EA, 0xE0F6, 0x50EC, 0xE0FA, 0x50ED, 0xB9B0, 0x50EE, 0xB9AD, + 0x50EF, 0xE0FC, 0x50F0, 0xE0FB, 0x50F1, 0xB9B5, 0x50F3, 0xE0F4, 0x50F5, 0xBBF8, 0x50F6, 0xE4EC, 0x50F8, 0xE4E9, 0x50F9, 0xBBF9, + 0x50FB, 0xBBF7, 0x50FD, 0xE4F0, 0x50FE, 0xE4ED, 0x50FF, 0xE4E6, 0x5100, 0xBBF6, 0x5102, 0xBBFA, 0x5103, 0xE4E7, 0x5104, 0xBBF5, + 0x5105, 0xBBFD, 0x5106, 0xE4EA, 0x5107, 0xE4EB, 0x5108, 0xBBFB, 0x5109, 0xBBFC, 0x510A, 0xE4F1, 0x510B, 0xE4EE, 0x510C, 0xE4EF, + 0x5110, 0xBEAA, 0x5111, 0xE8F8, 0x5112, 0xBEA7, 0x5113, 0xE8F5, 0x5114, 0xBEA9, 0x5115, 0xBEAB, 0x5117, 0xE8F6, 0x5118, 0xBEA8, + 0x511A, 0xE8F7, 0x511C, 0xE8F4, 0x511F, 0xC076, 0x5120, 0xECBD, 0x5121, 0xC077, 0x5122, 0xECBB, 0x5124, 0xECBC, 0x5125, 0xECBA, + 0x5126, 0xECB9, 0x5129, 0xECBE, 0x512A, 0xC075, 0x512D, 0xEFB8, 0x512E, 0xEFB9, 0x5130, 0xE4E8, 0x5131, 0xEFB7, 0x5132, 0xC078, + 0x5133, 0xC35F, 0x5134, 0xF1EB, 0x5135, 0xF1EC, 0x5137, 0xC4D7, 0x5138, 0xC4D8, 0x5139, 0xF5C1, 0x513A, 0xF5C0, 0x513B, 0xC56C, + 0x513C, 0xC56B, 0x513D, 0xF7D0, 0x513F, 0xA449, 0x5140, 0xA461, 0x5141, 0xA4B9, 0x5143, 0xA4B8, 0x5144, 0xA553, 0x5145, 0xA552, + 0x5146, 0xA5FC, 0x5147, 0xA5FB, 0x5148, 0xA5FD, 0x5149, 0xA5FA, 0x514B, 0xA74A, 0x514C, 0xA749, 0x514D, 0xA74B, 0x5152, 0xA8E0, + 0x5154, 0xA8DF, 0x5155, 0xA8E1, 0x5157, 0xAB5E, 0x5159, 0xA259, 0x515A, 0xD0DE, 0x515B, 0xA25A, 0x515C, 0xB0C2, 0x515D, 0xA25C, + 0x515E, 0xA25B, 0x515F, 0xD860, 0x5161, 0xA25D, 0x5162, 0xB9B8, 0x5163, 0xA25E, 0x5165, 0xA44A, 0x5167, 0xA4BA, 0x5168, 0xA5FE, + 0x5169, 0xA8E2, 0x516B, 0xA44B, 0x516C, 0xA4BD, 0x516D, 0xA4BB, 0x516E, 0xA4BC, 0x5171, 0xA640, 0x5175, 0xA74C, 0x5176, 0xA8E4, + 0x5177, 0xA8E3, 0x5178, 0xA8E5, 0x517C, 0xADDD, 0x5180, 0xBEAC, 0x5187, 0xC94E, 0x5189, 0xA554, 0x518A, 0xA555, 0x518D, 0xA641, + 0x518F, 0xCA6A, 0x5191, 0xAB60, 0x5192, 0xAB5F, 0x5193, 0xD0E0, 0x5194, 0xD0DF, 0x5195, 0xB0C3, 0x5197, 0xA4BE, 0x5198, 0xC955, + 0x519E, 0xCBCD, 0x51A0, 0xAB61, 0x51A2, 0xADE0, 0x51A4, 0xADDE, 0x51A5, 0xADDF, 0x51AA, 0xBEAD, 0x51AC, 0xA556, 0x51B0, 0xA642, + 0x51B1, 0xC9BC, 0x51B6, 0xA74D, 0x51B7, 0xA74E, 0x51B9, 0xCA6B, 0x51BC, 0xCBCE, 0x51BD, 0xA8E6, 0x51BE, 0xCBCF, 0x51C4, 0xD0E2, + 0x51C5, 0xD0E3, 0x51C6, 0xADE3, 0x51C8, 0xD0E4, 0x51CA, 0xD0E1, 0x51CB, 0xADE4, 0x51CC, 0xADE2, 0x51CD, 0xADE1, 0x51CE, 0xD0E5, + 0x51D0, 0xD468, 0x51D4, 0xD861, 0x51D7, 0xDCC5, 0x51D8, 0xE140, 0x51DC, 0xBBFE, 0x51DD, 0xBEAE, 0x51DE, 0xE8F9, 0x51E0, 0xA44C, + 0x51E1, 0xA45A, 0x51F0, 0xB0C4, 0x51F1, 0xB3CD, 0x51F3, 0xB9B9, 0x51F5, 0xC942, 0x51F6, 0xA4BF, 0x51F8, 0xA559, 0x51F9, 0xA557, + 0x51FA, 0xA558, 0x51FD, 0xA8E7, 0x5200, 0xA44D, 0x5201, 0xA44E, 0x5203, 0xA462, 0x5206, 0xA4C0, 0x5207, 0xA4C1, 0x5208, 0xA4C2, + 0x5209, 0xC9BE, 0x520A, 0xA55A, 0x520C, 0xC96B, 0x520E, 0xA646, 0x5210, 0xC9BF, 0x5211, 0xA644, 0x5212, 0xA645, 0x5213, 0xC9BD, + 0x5216, 0xA647, 0x5217, 0xA643, 0x521C, 0xCA6C, 0x521D, 0xAAEC, 0x521E, 0xCA6D, 0x5221, 0xCA6E, 0x5224, 0xA750, 0x5225, 0xA74F, + 0x5228, 0xA753, 0x5229, 0xA751, 0x522A, 0xA752, 0x522E, 0xA8ED, 0x5230, 0xA8EC, 0x5231, 0xCBD4, 0x5232, 0xCBD1, 0x5233, 0xCBD2, + 0x5235, 0xCBD0, 0x5236, 0xA8EE, 0x5237, 0xA8EA, 0x5238, 0xA8E9, 0x523A, 0xA8EB, 0x523B, 0xA8E8, 0x5241, 0xA8EF, 0x5243, 0xAB63, + 0x5244, 0xCDF0, 0x5246, 0xCBD3, 0x5247, 0xAB68, 0x5249, 0xCDF1, 0x524A, 0xAB64, 0x524B, 0xAB67, 0x524C, 0xAB66, 0x524D, 0xAB65, + 0x524E, 0xAB62, 0x5252, 0xD0E8, 0x5254, 0xADE7, 0x5255, 0xD0EB, 0x5256, 0xADE5, 0x525A, 0xD0E7, 0x525B, 0xADE8, 0x525C, 0xADE6, + 0x525D, 0xADE9, 0x525E, 0xD0E9, 0x525F, 0xD0EA, 0x5261, 0xD0E6, 0x5262, 0xD0EC, 0x5269, 0xB3D1, 0x526A, 0xB0C5, 0x526B, 0xD469, + 0x526C, 0xD46B, 0x526D, 0xD46A, 0x526E, 0xD46C, 0x526F, 0xB0C6, 0x5272, 0xB3CE, 0x5274, 0xB3CF, 0x5275, 0xB3D0, 0x5277, 0xB6D0, + 0x5278, 0xDCC7, 0x527A, 0xDCC6, 0x527B, 0xDCC8, 0x527C, 0xDCC9, 0x527D, 0xB6D1, 0x527F, 0xB6CF, 0x5280, 0xE141, 0x5281, 0xE142, + 0x5282, 0xB9BB, 0x5283, 0xB9BA, 0x5284, 0xE35A, 0x5287, 0xBC40, 0x5288, 0xBC41, 0x5289, 0xBC42, 0x528A, 0xBC44, 0x528B, 0xE4F2, + 0x528C, 0xE4F3, 0x528D, 0xBC43, 0x5291, 0xBEAF, 0x5293, 0xBEB0, 0x5296, 0xF1ED, 0x5297, 0xF5C3, 0x5298, 0xF5C2, 0x5299, 0xF7D1, + 0x529B, 0xA44F, 0x529F, 0xA55C, 0x52A0, 0xA55B, 0x52A3, 0xA648, 0x52A6, 0xC9C0, 0x52A9, 0xA755, 0x52AA, 0xA756, 0x52AB, 0xA754, + 0x52AC, 0xA757, 0x52AD, 0xCA6F, 0x52AE, 0xCA70, 0x52BB, 0xA8F1, 0x52BC, 0xCBD5, 0x52BE, 0xA8F0, 0x52C0, 0xCDF2, 0x52C1, 0xAB6C, + 0x52C2, 0xCDF3, 0x52C3, 0xAB6B, 0x52C7, 0xAB69, 0x52C9, 0xAB6A, 0x52CD, 0xD0ED, 0x52D2, 0xB0C7, 0x52D3, 0xD46E, 0x52D5, 0xB0CA, + 0x52D6, 0xD46D, 0x52D7, 0xB1E5, 0x52D8, 0xB0C9, 0x52D9, 0xB0C8, 0x52DB, 0xB3D4, 0x52DD, 0xB3D3, 0x52DE, 0xB3D2, 0x52DF, 0xB6D2, + 0x52E2, 0xB6D5, 0x52E3, 0xB6D6, 0x52E4, 0xB6D4, 0x52E6, 0xB6D3, 0x52E9, 0xE143, 0x52EB, 0xE144, 0x52EF, 0xE4F5, 0x52F0, 0xBC45, + 0x52F1, 0xE4F4, 0x52F3, 0xBEB1, 0x52F4, 0xECBF, 0x52F5, 0xC079, 0x52F7, 0xF1EE, 0x52F8, 0xC455, 0x52FA, 0xA463, 0x52FB, 0xA4C3, + 0x52FC, 0xC956, 0x52FE, 0xA4C4, 0x52FF, 0xA4C5, 0x5305, 0xA55D, 0x5306, 0xA55E, 0x5308, 0xA649, 0x5309, 0xCA71, 0x530A, 0xCBD6, + 0x530B, 0xCBD7, 0x530D, 0xAB6D, 0x530E, 0xD0EE, 0x530F, 0xB0CC, 0x5310, 0xB0CB, 0x5311, 0xD863, 0x5312, 0xD862, 0x5315, 0xA450, + 0x5316, 0xA4C6, 0x5317, 0xA55F, 0x5319, 0xB0CD, 0x531A, 0xC943, 0x531C, 0xC96C, 0x531D, 0xA560, 0x531F, 0xC9C2, 0x5320, 0xA64B, + 0x5321, 0xA64A, 0x5322, 0xC9C1, 0x5323, 0xA758, 0x532A, 0xADEA, 0x532D, 0xD46F, 0x532F, 0xB6D7, 0x5330, 0xE145, 0x5331, 0xB9BC, + 0x5334, 0xE8FA, 0x5337, 0xF3FD, 0x5339, 0xA4C7, 0x533C, 0xCBD8, 0x533D, 0xCDF4, 0x533E, 0xB0D0, 0x533F, 0xB0CE, 0x5340, 0xB0CF, + 0x5341, 0xA2CC, 0x5341, 0xA451, 0x5343, 0xA464, 0x5344, 0xA2CD, 0x5345, 0xA2CE, 0x5345, 0xA4CA, 0x5347, 0xA4C9, 0x5348, 0xA4C8, + 0x5349, 0xA563, 0x534A, 0xA562, 0x534C, 0xC96D, 0x534D, 0xC9C3, 0x5351, 0xA8F5, 0x5352, 0xA8F2, 0x5353, 0xA8F4, 0x5354, 0xA8F3, + 0x5357, 0xAB6E, 0x535A, 0xB3D5, 0x535C, 0xA452, 0x535E, 0xA4CB, 0x5360, 0xA565, 0x5361, 0xA564, 0x5363, 0xCA72, 0x5366, 0xA8F6, + 0x536C, 0xC957, 0x536E, 0xA567, 0x536F, 0xA566, 0x5370, 0xA64C, 0x5371, 0xA64D, 0x5372, 0xCA73, 0x5373, 0xA759, 0x5375, 0xA75A, + 0x5377, 0xA8F7, 0x5378, 0xA8F8, 0x5379, 0xA8F9, 0x537B, 0xAB6F, 0x537C, 0xCDF5, 0x537F, 0xADEB, 0x5382, 0xC944, 0x5384, 0xA4CC, + 0x538A, 0xC9C4, 0x538E, 0xCA74, 0x538F, 0xCA75, 0x5392, 0xCBD9, 0x5394, 0xCBDA, 0x5396, 0xCDF7, 0x5397, 0xCDF6, 0x5398, 0xCDF9, + 0x5399, 0xCDF8, 0x539A, 0xAB70, 0x539C, 0xD470, 0x539D, 0xADED, 0x539E, 0xD0EF, 0x539F, 0xADEC, 0x53A4, 0xD864, 0x53A5, 0xB3D6, + 0x53A7, 0xD865, 0x53AC, 0xE146, 0x53AD, 0xB9BD, 0x53B2, 0xBC46, 0x53B4, 0xF1EF, 0x53B9, 0xC958, 0x53BB, 0xA568, 0x53C3, 0xB0D1, + 0x53C8, 0xA453, 0x53C9, 0xA465, 0x53CA, 0xA4CE, 0x53CB, 0xA4CD, 0x53CD, 0xA4CF, 0x53D4, 0xA8FB, 0x53D6, 0xA8FA, 0x53D7, 0xA8FC, + 0x53DB, 0xAB71, 0x53DF, 0xADEE, 0x53E1, 0xE8FB, 0x53E2, 0xC24F, 0x53E3, 0xA466, 0x53E4, 0xA56A, 0x53E5, 0xA579, 0x53E6, 0xA574, + 0x53E8, 0xA56F, 0x53E9, 0xA56E, 0x53EA, 0xA575, 0x53EB, 0xA573, 0x53EC, 0xA56C, 0x53ED, 0xA57A, 0x53EE, 0xA56D, 0x53EF, 0xA569, + 0x53F0, 0xA578, 0x53F1, 0xA577, 0x53F2, 0xA576, 0x53F3, 0xA56B, 0x53F5, 0xA572, 0x53F8, 0xA571, 0x53FB, 0xA57B, 0x53FC, 0xA570, + 0x5401, 0xA653, 0x5403, 0xA659, 0x5404, 0xA655, 0x5406, 0xA65B, 0x5407, 0xC9C5, 0x5408, 0xA658, 0x5409, 0xA64E, 0x540A, 0xA651, + 0x540B, 0xA654, 0x540C, 0xA650, 0x540D, 0xA657, 0x540E, 0xA65A, 0x540F, 0xA64F, 0x5410, 0xA652, 0x5411, 0xA656, 0x5412, 0xA65C, + 0x5418, 0xCA7E, 0x5419, 0xCA7B, 0x541B, 0xA767, 0x541C, 0xCA7C, 0x541D, 0xA75B, 0x541E, 0xA75D, 0x541F, 0xA775, 0x5420, 0xA770, + 0x5424, 0xCAA5, 0x5425, 0xCA7D, 0x5426, 0xA75F, 0x5427, 0xA761, 0x5428, 0xCAA4, 0x5429, 0xA768, 0x542A, 0xCA78, 0x542B, 0xA774, + 0x542C, 0xA776, 0x542D, 0xA75C, 0x542E, 0xA76D, 0x5430, 0xCA76, 0x5431, 0xA773, 0x5433, 0xA764, 0x5435, 0xA76E, 0x5436, 0xA76F, + 0x5437, 0xCA77, 0x5438, 0xA76C, 0x5439, 0xA76A, 0x543B, 0xA76B, 0x543C, 0xA771, 0x543D, 0xCAA1, 0x543E, 0xA75E, 0x5440, 0xA772, + 0x5441, 0xCAA3, 0x5442, 0xA766, 0x5443, 0xA763, 0x5445, 0xCA7A, 0x5446, 0xA762, 0x5447, 0xCAA6, 0x5448, 0xA765, 0x544A, 0xA769, + 0x544E, 0xA760, 0x544F, 0xCAA2, 0x5454, 0xCA79, 0x5460, 0xCBEB, 0x5461, 0xCBEA, 0x5462, 0xA94F, 0x5463, 0xCBED, 0x5464, 0xCBEF, + 0x5465, 0xCBE4, 0x5466, 0xCBE7, 0x5467, 0xCBEE, 0x5468, 0xA950, 0x546B, 0xCBE1, 0x546C, 0xCBE5, 0x546F, 0xCBE9, 0x5470, 0xCE49, + 0x5471, 0xA94B, 0x5472, 0xCE4D, 0x5473, 0xA8FD, 0x5474, 0xCBE6, 0x5475, 0xA8FE, 0x5476, 0xA94C, 0x5477, 0xA945, 0x5478, 0xA941, + 0x547A, 0xCBE2, 0x547B, 0xA944, 0x547C, 0xA949, 0x547D, 0xA952, 0x547E, 0xCBE3, 0x547F, 0xCBDC, 0x5480, 0xA943, 0x5481, 0xCBDD, + 0x5482, 0xCBDF, 0x5484, 0xA946, 0x5486, 0xA948, 0x5487, 0xCBDB, 0x5488, 0xCBE0, 0x548B, 0xA951, 0x548C, 0xA94D, 0x548D, 0xCBE8, + 0x548E, 0xA953, 0x5490, 0xA94A, 0x5491, 0xCBDE, 0x5492, 0xA947, 0x5495, 0xA942, 0x5496, 0xA940, 0x5498, 0xCBEC, 0x549A, 0xA94E, + 0x54A0, 0xCE48, 0x54A1, 0xCDFB, 0x54A2, 0xCE4B, 0x54A5, 0xCDFD, 0x54A6, 0xAB78, 0x54A7, 0xABA8, 0x54A8, 0xAB74, 0x54A9, 0xABA7, + 0x54AA, 0xAB7D, 0x54AB, 0xABA4, 0x54AC, 0xAB72, 0x54AD, 0xCDFC, 0x54AE, 0xCE43, 0x54AF, 0xABA3, 0x54B0, 0xCE4F, 0x54B1, 0xABA5, + 0x54B3, 0xAB79, 0x54B6, 0xCE45, 0x54B7, 0xCE42, 0x54B8, 0xAB77, 0x54BA, 0xCDFA, 0x54BB, 0xABA6, 0x54BC, 0xCE4A, 0x54BD, 0xAB7C, + 0x54BE, 0xCE4C, 0x54BF, 0xABA9, 0x54C0, 0xAB73, 0x54C1, 0xAB7E, 0x54C2, 0xAB7B, 0x54C3, 0xCE40, 0x54C4, 0xABA1, 0x54C5, 0xCE46, + 0x54C6, 0xCE47, 0x54C7, 0xAB7A, 0x54C8, 0xABA2, 0x54C9, 0xAB76, 0x54CE, 0xAB75, 0x54CF, 0xCDFE, 0x54D6, 0xCE44, 0x54DE, 0xCE4E, + 0x54E0, 0xD144, 0x54E1, 0xADFB, 0x54E2, 0xD0F1, 0x54E4, 0xD0F6, 0x54E5, 0xADF4, 0x54E6, 0xAE40, 0x54E7, 0xD0F4, 0x54E8, 0xADEF, + 0x54E9, 0xADF9, 0x54EA, 0xADFE, 0x54EB, 0xD0FB, 0x54ED, 0xADFA, 0x54EE, 0xADFD, 0x54F1, 0xD0FE, 0x54F2, 0xADF5, 0x54F3, 0xD0F5, + 0x54F7, 0xD142, 0x54F8, 0xD143, 0x54FA, 0xADF7, 0x54FB, 0xD141, 0x54FC, 0xADF3, 0x54FD, 0xAE43, 0x54FF, 0xD0F8, 0x5501, 0xADF1, + 0x5503, 0xD146, 0x5504, 0xD0F9, 0x5505, 0xD0FD, 0x5506, 0xADF6, 0x5507, 0xAE42, 0x5508, 0xD0FA, 0x5509, 0xADFC, 0x550A, 0xD140, + 0x550B, 0xD147, 0x550C, 0xD4A1, 0x550E, 0xD145, 0x550F, 0xAE44, 0x5510, 0xADF0, 0x5511, 0xD0FC, 0x5512, 0xD0F3, 0x5514, 0xADF8, + 0x5517, 0xD0F2, 0x551A, 0xD0F7, 0x5526, 0xD0F0, 0x5527, 0xAE41, 0x552A, 0xD477, 0x552C, 0xB0E4, 0x552D, 0xD4A7, 0x552E, 0xB0E2, + 0x552F, 0xB0DF, 0x5530, 0xD47C, 0x5531, 0xB0DB, 0x5532, 0xD4A2, 0x5533, 0xB0E6, 0x5534, 0xD476, 0x5535, 0xD47B, 0x5536, 0xD47A, + 0x5537, 0xADF2, 0x5538, 0xB0E1, 0x5539, 0xD4A5, 0x553B, 0xD4A8, 0x553C, 0xD473, 0x553E, 0xB3E8, 0x5540, 0xD4A9, 0x5541, 0xB0E7, + 0x5543, 0xB0D9, 0x5544, 0xB0D6, 0x5545, 0xD47E, 0x5546, 0xB0D3, 0x5548, 0xD4A6, 0x554A, 0xB0DA, 0x554B, 0xD4AA, 0x554D, 0xD474, + 0x554E, 0xD4A4, 0x554F, 0xB0DD, 0x5550, 0xD475, 0x5551, 0xD478, 0x5552, 0xD47D, 0x5555, 0xB0DE, 0x5556, 0xB0DC, 0x5557, 0xB0E8, + 0x555C, 0xB0E3, 0x555E, 0xB0D7, 0x555F, 0xB1D2, 0x5561, 0xB0D8, 0x5562, 0xD479, 0x5563, 0xB0E5, 0x5564, 0xB0E0, 0x5565, 0xD4A3, + 0x5566, 0xB0D5, 0x556A, 0xB0D4, 0x5575, 0xD471, 0x5576, 0xD472, 0x5577, 0xD86A, 0x557B, 0xB3D7, 0x557C, 0xB3DA, 0x557D, 0xD875, + 0x557E, 0xB3EE, 0x557F, 0xD878, 0x5580, 0xB3D8, 0x5581, 0xD871, 0x5582, 0xB3DE, 0x5583, 0xB3E4, 0x5584, 0xB5BD, 0x5587, 0xB3E2, + 0x5588, 0xD86E, 0x5589, 0xB3EF, 0x558A, 0xB3DB, 0x558B, 0xB3E3, 0x558C, 0xD876, 0x558D, 0xDCD7, 0x558E, 0xD87B, 0x558F, 0xD86F, + 0x5591, 0xD866, 0x5592, 0xD873, 0x5593, 0xD86D, 0x5594, 0xB3E1, 0x5595, 0xD879, 0x5598, 0xB3DD, 0x5599, 0xB3F1, 0x559A, 0xB3EA, + 0x559C, 0xB3DF, 0x559D, 0xB3DC, 0x559F, 0xB3E7, 0x55A1, 0xD87A, 0x55A2, 0xD86C, 0x55A3, 0xD872, 0x55A4, 0xD874, 0x55A5, 0xD868, + 0x55A6, 0xD877, 0x55A7, 0xB3D9, 0x55A8, 0xD867, 0x55AA, 0xB3E0, 0x55AB, 0xB3F0, 0x55AC, 0xB3EC, 0x55AD, 0xD869, 0x55AE, 0xB3E6, + 0x55B1, 0xB3ED, 0x55B2, 0xB3E9, 0x55B3, 0xB3E5, 0x55B5, 0xD870, 0x55BB, 0xB3EB, 0x55BF, 0xDCD5, 0x55C0, 0xDCD1, 0x55C2, 0xDCE0, + 0x55C3, 0xDCCA, 0x55C4, 0xDCD3, 0x55C5, 0xB6E5, 0x55C6, 0xB6E6, 0x55C7, 0xB6DE, 0x55C8, 0xDCDC, 0x55C9, 0xB6E8, 0x55CA, 0xDCCF, + 0x55CB, 0xDCCE, 0x55CC, 0xDCCC, 0x55CD, 0xDCDE, 0x55CE, 0xB6DC, 0x55CF, 0xDCD8, 0x55D0, 0xDCCD, 0x55D1, 0xB6DF, 0x55D2, 0xDCD6, + 0x55D3, 0xB6DA, 0x55D4, 0xDCD2, 0x55D5, 0xDCD9, 0x55D6, 0xDCDB, 0x55D9, 0xDCDF, 0x55DA, 0xB6E3, 0x55DB, 0xDCCB, 0x55DC, 0xB6DD, + 0x55DD, 0xDCD0, 0x55DF, 0xB6D8, 0x55E1, 0xB6E4, 0x55E2, 0xDCDA, 0x55E3, 0xB6E0, 0x55E4, 0xB6E1, 0x55E5, 0xB6E7, 0x55E6, 0xB6DB, + 0x55E7, 0xA25F, 0x55E8, 0xB6D9, 0x55E9, 0xDCD4, 0x55EF, 0xB6E2, 0x55F2, 0xDCDD, 0x55F6, 0xB9CD, 0x55F7, 0xB9C8, 0x55F9, 0xE155, + 0x55FA, 0xE151, 0x55FC, 0xE14B, 0x55FD, 0xB9C2, 0x55FE, 0xB9BE, 0x55FF, 0xE154, 0x5600, 0xB9BF, 0x5601, 0xE14E, 0x5602, 0xE150, + 0x5604, 0xE153, 0x5606, 0xB9C4, 0x5608, 0xB9CB, 0x5609, 0xB9C5, 0x560C, 0xE149, 0x560D, 0xB9C6, 0x560E, 0xB9C7, 0x560F, 0xE14C, + 0x5610, 0xB9CC, 0x5612, 0xE14A, 0x5613, 0xE14F, 0x5614, 0xB9C3, 0x5615, 0xE148, 0x5616, 0xB9C9, 0x5617, 0xB9C1, 0x561B, 0xB9C0, + 0x561C, 0xE14D, 0x561D, 0xE152, 0x561F, 0xB9CA, 0x5627, 0xE147, 0x5629, 0xBC4D, 0x562A, 0xE547, 0x562C, 0xE544, 0x562E, 0xBC47, + 0x562F, 0xBC53, 0x5630, 0xBC54, 0x5632, 0xBC4A, 0x5633, 0xE542, 0x5634, 0xBC4C, 0x5635, 0xE4F9, 0x5636, 0xBC52, 0x5638, 0xE546, + 0x5639, 0xBC49, 0x563A, 0xE548, 0x563B, 0xBC48, 0x563D, 0xE543, 0x563E, 0xE545, 0x563F, 0xBC4B, 0x5640, 0xE541, 0x5641, 0xE4FA, + 0x5642, 0xE4F7, 0x5645, 0xD86B, 0x5646, 0xE4FD, 0x5648, 0xE4F6, 0x5649, 0xE4FC, 0x564A, 0xE4FB, 0x564C, 0xE4F8, 0x564E, 0xBC4F, + 0x5653, 0xBC4E, 0x5657, 0xBC50, 0x5658, 0xE4FE, 0x5659, 0xBEB2, 0x565A, 0xE540, 0x565E, 0xE945, 0x5660, 0xE8FD, 0x5662, 0xBEBE, + 0x5663, 0xE942, 0x5664, 0xBEB6, 0x5665, 0xBEBA, 0x5666, 0xE941, 0x5668, 0xBEB9, 0x5669, 0xBEB5, 0x566A, 0xBEB8, 0x566B, 0xBEB3, + 0x566C, 0xBEBD, 0x566D, 0xE943, 0x566E, 0xE8FE, 0x566F, 0xBEBC, 0x5670, 0xE8FC, 0x5671, 0xBEBB, 0x5672, 0xE944, 0x5673, 0xE940, + 0x5674, 0xBC51, 0x5676, 0xBEBF, 0x5677, 0xE946, 0x5678, 0xBEB7, 0x5679, 0xBEB4, 0x567E, 0xECC6, 0x567F, 0xECC8, 0x5680, 0xC07B, + 0x5681, 0xECC9, 0x5682, 0xECC7, 0x5683, 0xECC5, 0x5684, 0xECC4, 0x5685, 0xC07D, 0x5686, 0xECC3, 0x5687, 0xC07E, 0x568C, 0xECC1, + 0x568D, 0xECC2, 0x568E, 0xC07A, 0x568F, 0xC0A1, 0x5690, 0xC07C, 0x5693, 0xECC0, 0x5695, 0xC250, 0x5697, 0xEFBC, 0x5698, 0xEFBA, + 0x5699, 0xEFBF, 0x569A, 0xEFBD, 0x569C, 0xEFBB, 0x569D, 0xEFBE, 0x56A5, 0xC360, 0x56A6, 0xF1F2, 0x56A7, 0xF1F3, 0x56A8, 0xC456, + 0x56AA, 0xF1F4, 0x56AB, 0xF1F0, 0x56AC, 0xF1F5, 0x56AD, 0xF1F1, 0x56AE, 0xC251, 0x56B2, 0xF3FE, 0x56B3, 0xF441, 0x56B4, 0xC459, + 0x56B5, 0xF440, 0x56B6, 0xC458, 0x56B7, 0xC457, 0x56BC, 0xC45A, 0x56BD, 0xF5C5, 0x56BE, 0xF5C6, 0x56C0, 0xC4DA, 0x56C1, 0xC4D9, + 0x56C2, 0xC4DB, 0x56C3, 0xF5C4, 0x56C5, 0xF6D8, 0x56C6, 0xF6D7, 0x56C8, 0xC56D, 0x56C9, 0xC56F, 0x56CA, 0xC56E, 0x56CB, 0xF6D9, + 0x56CC, 0xC5C8, 0x56CD, 0xF8A6, 0x56D1, 0xC5F1, 0x56D3, 0xF8A5, 0x56D4, 0xF8EE, 0x56D7, 0xC949, 0x56DA, 0xA57D, 0x56DB, 0xA57C, + 0x56DD, 0xA65F, 0x56DE, 0xA65E, 0x56DF, 0xC9C7, 0x56E0, 0xA65D, 0x56E1, 0xC9C6, 0x56E4, 0xA779, 0x56E5, 0xCAA9, 0x56E7, 0xCAA8, + 0x56EA, 0xA777, 0x56EB, 0xA77A, 0x56EE, 0xCAA7, 0x56F0, 0xA778, 0x56F7, 0xCBF0, 0x56F9, 0xCBF1, 0x56FA, 0xA954, 0x56FF, 0xABAA, + 0x5701, 0xD148, 0x5702, 0xD149, 0x5703, 0xAE45, 0x5704, 0xAE46, 0x5707, 0xD4AC, 0x5708, 0xB0E9, 0x5709, 0xB0EB, 0x570A, 0xD4AB, + 0x570B, 0xB0EA, 0x570C, 0xD87C, 0x570D, 0xB3F2, 0x5712, 0xB6E9, 0x5713, 0xB6EA, 0x5714, 0xDCE1, 0x5716, 0xB9CF, 0x5718, 0xB9CE, + 0x571A, 0xE549, 0x571B, 0xE948, 0x571C, 0xE947, 0x571E, 0xF96B, 0x571F, 0xA467, 0x5720, 0xC959, 0x5722, 0xC96E, 0x5723, 0xC96F, + 0x5728, 0xA662, 0x5729, 0xA666, 0x572A, 0xC9C9, 0x572C, 0xA664, 0x572D, 0xA663, 0x572E, 0xC9C8, 0x572F, 0xA665, 0x5730, 0xA661, + 0x5733, 0xA660, 0x5734, 0xC9CA, 0x573B, 0xA7A6, 0x573E, 0xA7A3, 0x5740, 0xA77D, 0x5741, 0xCAAA, 0x5745, 0xCAAB, 0x5747, 0xA7A1, + 0x5749, 0xCAAD, 0x574A, 0xA77B, 0x574B, 0xCAAE, 0x574C, 0xCAAC, 0x574D, 0xA77E, 0x574E, 0xA7A2, 0x574F, 0xA7A5, 0x5750, 0xA7A4, + 0x5751, 0xA77C, 0x5752, 0xCAAF, 0x5761, 0xA959, 0x5762, 0xCBFE, 0x5764, 0xA95B, 0x5766, 0xA95A, 0x5768, 0xCC40, 0x5769, 0xA958, + 0x576A, 0xA957, 0x576B, 0xCBF5, 0x576D, 0xCBF4, 0x576F, 0xCBF2, 0x5770, 0xCBF7, 0x5771, 0xCBF6, 0x5772, 0xCBF3, 0x5773, 0xCBFC, + 0x5774, 0xCBFD, 0x5775, 0xCBFA, 0x5776, 0xCBF8, 0x5777, 0xA956, 0x577B, 0xCBFB, 0x577C, 0xA95C, 0x577D, 0xCC41, 0x5780, 0xCBF9, + 0x5782, 0xABAB, 0x5783, 0xA955, 0x578B, 0xABAC, 0x578C, 0xCE54, 0x578F, 0xCE5A, 0x5793, 0xABB2, 0x5794, 0xCE58, 0x5795, 0xCE5E, + 0x5797, 0xCE55, 0x5798, 0xCE59, 0x5799, 0xCE5B, 0x579A, 0xCE5D, 0x579B, 0xCE57, 0x579D, 0xCE56, 0x579E, 0xCE51, 0x579F, 0xCE52, + 0x57A0, 0xABAD, 0x57A2, 0xABAF, 0x57A3, 0xABAE, 0x57A4, 0xCE53, 0x57A5, 0xCE5C, 0x57AE, 0xABB1, 0x57B5, 0xCE50, 0x57B6, 0xD153, + 0x57B8, 0xD152, 0x57B9, 0xD157, 0x57BA, 0xD14E, 0x57BC, 0xD151, 0x57BD, 0xD150, 0x57BF, 0xD154, 0x57C1, 0xD158, 0x57C2, 0xAE47, + 0x57C3, 0xAE4A, 0x57C6, 0xD14F, 0x57C7, 0xD155, 0x57CB, 0xAE49, 0x57CC, 0xD14A, 0x57CE, 0xABB0, 0x57CF, 0xD4BA, 0x57D0, 0xD156, + 0x57D2, 0xD14D, 0x57D4, 0xAE48, 0x57D5, 0xD14C, 0x57DC, 0xD4B1, 0x57DF, 0xB0EC, 0x57E0, 0xB0F0, 0x57E1, 0xD4C1, 0x57E2, 0xD4AF, + 0x57E3, 0xD4BD, 0x57E4, 0xB0F1, 0x57E5, 0xD4BF, 0x57E7, 0xD4C5, 0x57E9, 0xD4C9, 0x57EC, 0xD4C0, 0x57ED, 0xD4B4, 0x57EE, 0xD4BC, + 0x57F0, 0xD4CA, 0x57F1, 0xD4C8, 0x57F2, 0xD4BE, 0x57F3, 0xD4B9, 0x57F4, 0xD4B2, 0x57F5, 0xD8A6, 0x57F6, 0xD4B0, 0x57F7, 0xB0F5, + 0x57F8, 0xD4B7, 0x57F9, 0xB0F6, 0x57FA, 0xB0F2, 0x57FB, 0xD4AD, 0x57FC, 0xD4C3, 0x57FD, 0xD4B5, 0x5800, 0xD4B3, 0x5801, 0xD4C6, + 0x5802, 0xB0F3, 0x5804, 0xD4CC, 0x5805, 0xB0ED, 0x5806, 0xB0EF, 0x5807, 0xD4BB, 0x5808, 0xD4B6, 0x5809, 0xAE4B, 0x580A, 0xB0EE, + 0x580B, 0xD4B8, 0x580C, 0xD4C7, 0x580D, 0xD4CB, 0x580E, 0xD4C2, 0x5810, 0xD4C4, 0x5814, 0xD4AE, 0x5819, 0xD8A1, 0x581B, 0xD8AA, + 0x581C, 0xD8A9, 0x581D, 0xB3FA, 0x581E, 0xD8A2, 0x5820, 0xB3FB, 0x5821, 0xB3F9, 0x5823, 0xD8A4, 0x5824, 0xB3F6, 0x5825, 0xD8A8, + 0x5827, 0xD8A3, 0x5828, 0xD8A5, 0x5829, 0xD87D, 0x582A, 0xB3F4, 0x582C, 0xD8B2, 0x582D, 0xD8B1, 0x582E, 0xD8AE, 0x582F, 0xB3F3, + 0x5830, 0xB3F7, 0x5831, 0xB3F8, 0x5832, 0xD14B, 0x5833, 0xD8AB, 0x5834, 0xB3F5, 0x5835, 0xB0F4, 0x5836, 0xD8AD, 0x5837, 0xD87E, + 0x5838, 0xD8B0, 0x5839, 0xD8AF, 0x583B, 0xD8B3, 0x583D, 0xDCEF, 0x583F, 0xD8AC, 0x5848, 0xD8A7, 0x5849, 0xDCE7, 0x584A, 0xB6F4, + 0x584B, 0xB6F7, 0x584C, 0xB6F2, 0x584D, 0xDCE6, 0x584E, 0xDCEA, 0x584F, 0xDCE5, 0x5851, 0xB6EC, 0x5852, 0xB6F6, 0x5853, 0xDCE2, + 0x5854, 0xB6F0, 0x5855, 0xDCE9, 0x5857, 0xB6EE, 0x5858, 0xB6ED, 0x5859, 0xDCEC, 0x585A, 0xB6EF, 0x585B, 0xDCEE, 0x585D, 0xDCEB, + 0x585E, 0xB6EB, 0x5862, 0xB6F5, 0x5863, 0xDCF0, 0x5864, 0xDCE4, 0x5865, 0xDCED, 0x5868, 0xDCE3, 0x586B, 0xB6F1, 0x586D, 0xB6F3, + 0x586F, 0xDCE8, 0x5871, 0xDCF1, 0x5874, 0xE15D, 0x5875, 0xB9D0, 0x5876, 0xE163, 0x5879, 0xB9D5, 0x587A, 0xE15F, 0x587B, 0xE166, + 0x587C, 0xE157, 0x587D, 0xB9D7, 0x587E, 0xB9D1, 0x587F, 0xE15C, 0x5880, 0xBC55, 0x5881, 0xE15B, 0x5882, 0xE164, 0x5883, 0xB9D2, + 0x5885, 0xB9D6, 0x5886, 0xE15A, 0x5887, 0xE160, 0x5888, 0xE165, 0x5889, 0xE156, 0x588A, 0xB9D4, 0x588B, 0xE15E, 0x588E, 0xE162, + 0x588F, 0xE168, 0x5890, 0xE158, 0x5891, 0xE161, 0x5893, 0xB9D3, 0x5894, 0xE167, 0x5898, 0xE159, 0x589C, 0xBC59, 0x589D, 0xE54B, + 0x589E, 0xBC57, 0x589F, 0xBC56, 0x58A0, 0xE54D, 0x58A1, 0xE552, 0x58A3, 0xE54E, 0x58A5, 0xE551, 0x58A6, 0xBC5C, 0x58A8, 0xBEA5, + 0x58A9, 0xBC5B, 0x58AB, 0xE54A, 0x58AC, 0xE550, 0x58AE, 0xBC5A, 0x58AF, 0xE54F, 0x58B1, 0xE54C, 0x58B3, 0xBC58, 0x58BA, 0xE94D, + 0x58BB, 0xF9D9, 0x58BC, 0xE94F, 0x58BD, 0xE94A, 0x58BE, 0xBEC1, 0x58BF, 0xE94C, 0x58C1, 0xBEC0, 0x58C2, 0xE94E, 0x58C5, 0xBEC3, + 0x58C6, 0xE950, 0x58C7, 0xBEC2, 0x58C8, 0xE949, 0x58C9, 0xE94B, 0x58CE, 0xC0A5, 0x58CF, 0xECCC, 0x58D1, 0xC0A4, 0x58D2, 0xECCD, + 0x58D3, 0xC0A3, 0x58D4, 0xECCB, 0x58D5, 0xC0A2, 0x58D6, 0xECCA, 0x58D8, 0xC253, 0x58D9, 0xC252, 0x58DA, 0xF1F6, 0x58DB, 0xF1F8, + 0x58DD, 0xF1F7, 0x58DE, 0xC361, 0x58DF, 0xC362, 0x58E2, 0xC363, 0x58E3, 0xF442, 0x58E4, 0xC45B, 0x58E7, 0xF7D3, 0x58E8, 0xF7D2, + 0x58E9, 0xC5F2, 0x58EB, 0xA468, 0x58EC, 0xA4D0, 0x58EF, 0xA7A7, 0x58F4, 0xCE5F, 0x58F9, 0xB3FC, 0x58FA, 0xB3FD, 0x58FC, 0xDCF2, + 0x58FD, 0xB9D8, 0x58FE, 0xE169, 0x58FF, 0xE553, 0x5903, 0xC95A, 0x5906, 0xCAB0, 0x590C, 0xCC42, 0x590D, 0xCE60, 0x590E, 0xD159, + 0x590F, 0xAE4C, 0x5912, 0xF1F9, 0x5914, 0xC4DC, 0x5915, 0xA469, 0x5916, 0xA57E, 0x5917, 0xC970, 0x5919, 0xA667, 0x591A, 0xA668, + 0x591C, 0xA95D, 0x5920, 0xB0F7, 0x5922, 0xB9DA, 0x5924, 0xB9DB, 0x5925, 0xB9D9, 0x5927, 0xA46A, 0x5929, 0xA4D1, 0x592A, 0xA4D3, + 0x592B, 0xA4D2, 0x592C, 0xC95B, 0x592D, 0xA4D4, 0x592E, 0xA5A1, 0x592F, 0xC971, 0x5931, 0xA5A2, 0x5937, 0xA669, 0x5938, 0xA66A, + 0x593C, 0xC9CB, 0x593E, 0xA7A8, 0x5940, 0xCAB1, 0x5944, 0xA961, 0x5945, 0xCC43, 0x5947, 0xA95F, 0x5948, 0xA960, 0x5949, 0xA95E, + 0x594A, 0xD15A, 0x594E, 0xABB6, 0x594F, 0xABB5, 0x5950, 0xABB7, 0x5951, 0xABB4, 0x5953, 0xCE61, 0x5954, 0xA962, 0x5955, 0xABB3, + 0x5957, 0xAE4D, 0x5958, 0xAE4E, 0x595A, 0xAE4F, 0x595C, 0xD4CD, 0x5960, 0xB3FE, 0x5961, 0xD8B4, 0x5962, 0xB0F8, 0x5967, 0xB6F8, + 0x5969, 0xB9DD, 0x596A, 0xB9DC, 0x596B, 0xE16A, 0x596D, 0xBC5D, 0x596E, 0xBEC4, 0x5970, 0xEFC0, 0x5971, 0xF6DA, 0x5972, 0xF7D4, + 0x5973, 0xA46B, 0x5974, 0xA5A3, 0x5976, 0xA5A4, 0x5977, 0xC9D1, 0x5978, 0xA66C, 0x5979, 0xA66F, 0x597B, 0xC9CF, 0x597C, 0xC9CD, + 0x597D, 0xA66E, 0x597E, 0xC9D0, 0x597F, 0xC9D2, 0x5980, 0xC9CC, 0x5981, 0xA671, 0x5982, 0xA670, 0x5983, 0xA66D, 0x5984, 0xA66B, + 0x5985, 0xC9CE, 0x598A, 0xA7B3, 0x598D, 0xA7B0, 0x598E, 0xCAB6, 0x598F, 0xCAB9, 0x5990, 0xCAB8, 0x5992, 0xA7AA, 0x5993, 0xA7B2, + 0x5996, 0xA7AF, 0x5997, 0xCAB5, 0x5998, 0xCAB3, 0x5999, 0xA7AE, 0x599D, 0xA7A9, 0x599E, 0xA7AC, 0x59A0, 0xCAB4, 0x59A1, 0xCABB, + 0x59A2, 0xCAB7, 0x59A3, 0xA7AD, 0x59A4, 0xA7B1, 0x59A5, 0xA7B4, 0x59A6, 0xCAB2, 0x59A7, 0xCABA, 0x59A8, 0xA7AB, 0x59AE, 0xA967, + 0x59AF, 0xA96F, 0x59B1, 0xCC4F, 0x59B2, 0xCC48, 0x59B3, 0xA970, 0x59B4, 0xCC53, 0x59B5, 0xCC44, 0x59B6, 0xCC4B, 0x59B9, 0xA966, + 0x59BA, 0xCC45, 0x59BB, 0xA964, 0x59BC, 0xCC4C, 0x59BD, 0xCC50, 0x59BE, 0xA963, 0x59C0, 0xCC51, 0x59C1, 0xCC4A, 0x59C3, 0xCC4D, + 0x59C5, 0xA972, 0x59C6, 0xA969, 0x59C7, 0xCC54, 0x59C8, 0xCC52, 0x59CA, 0xA96E, 0x59CB, 0xA96C, 0x59CC, 0xCC49, 0x59CD, 0xA96B, + 0x59CE, 0xCC47, 0x59CF, 0xCC46, 0x59D0, 0xA96A, 0x59D1, 0xA968, 0x59D2, 0xA971, 0x59D3, 0xA96D, 0x59D4, 0xA965, 0x59D6, 0xCC4E, + 0x59D8, 0xABB9, 0x59DA, 0xABC0, 0x59DB, 0xCE6F, 0x59DC, 0xABB8, 0x59DD, 0xCE67, 0x59DE, 0xCE63, 0x59E0, 0xCE73, 0x59E1, 0xCE62, + 0x59E3, 0xABBB, 0x59E4, 0xCE6C, 0x59E5, 0xABBE, 0x59E6, 0xABC1, 0x59E8, 0xABBC, 0x59E9, 0xCE70, 0x59EA, 0xABBF, 0x59EC, 0xAE56, + 0x59ED, 0xCE76, 0x59EE, 0xCE64, 0x59F1, 0xCE66, 0x59F2, 0xCE6D, 0x59F3, 0xCE71, 0x59F4, 0xCE75, 0x59F5, 0xCE72, 0x59F6, 0xCE6B, + 0x59F7, 0xCE6E, 0x59FA, 0xCE68, 0x59FB, 0xABC3, 0x59FC, 0xCE6A, 0x59FD, 0xCE69, 0x59FE, 0xCE74, 0x59FF, 0xABBA, 0x5A00, 0xCE65, + 0x5A01, 0xABC2, 0x5A03, 0xABBD, 0x5A09, 0xAE5C, 0x5A0A, 0xD162, 0x5A0C, 0xAE5B, 0x5A0F, 0xD160, 0x5A11, 0xAE50, 0x5A13, 0xAE55, + 0x5A15, 0xD15F, 0x5A16, 0xD15C, 0x5A17, 0xD161, 0x5A18, 0xAE51, 0x5A19, 0xD15B, 0x5A1B, 0xAE54, 0x5A1C, 0xAE52, 0x5A1E, 0xD163, + 0x5A1F, 0xAE53, 0x5A20, 0xAE57, 0x5A23, 0xAE58, 0x5A25, 0xAE5A, 0x5A29, 0xAE59, 0x5A2D, 0xD15D, 0x5A2E, 0xD15E, 0x5A33, 0xD164, + 0x5A35, 0xD4D4, 0x5A36, 0xB0F9, 0x5A37, 0xD8C2, 0x5A38, 0xD4D3, 0x5A39, 0xD4E6, 0x5A3C, 0xB140, 0x5A3E, 0xD4E4, 0x5A40, 0xB0FE, + 0x5A41, 0xB0FA, 0x5A42, 0xD4ED, 0x5A43, 0xD4DD, 0x5A44, 0xD4E0, 0x5A46, 0xB143, 0x5A47, 0xD4EA, 0x5A48, 0xD4E2, 0x5A49, 0xB0FB, + 0x5A4A, 0xB144, 0x5A4C, 0xD4E7, 0x5A4D, 0xD4E5, 0x5A50, 0xD4D6, 0x5A51, 0xD4EB, 0x5A52, 0xD4DF, 0x5A53, 0xD4DA, 0x5A55, 0xD4D0, + 0x5A56, 0xD4EC, 0x5A57, 0xD4DC, 0x5A58, 0xD4CF, 0x5A5A, 0xB142, 0x5A5B, 0xD4E1, 0x5A5C, 0xD4EE, 0x5A5D, 0xD4DE, 0x5A5E, 0xD4D2, + 0x5A5F, 0xD4D7, 0x5A60, 0xD4CE, 0x5A62, 0xB141, 0x5A64, 0xD4DB, 0x5A65, 0xD4D8, 0x5A66, 0xB0FC, 0x5A67, 0xD4D1, 0x5A69, 0xD4E9, + 0x5A6A, 0xB0FD, 0x5A6C, 0xD4D9, 0x5A6D, 0xD4D5, 0x5A70, 0xD4E8, 0x5A77, 0xB440, 0x5A78, 0xD8BB, 0x5A7A, 0xD8B8, 0x5A7B, 0xD8C9, + 0x5A7C, 0xD8BD, 0x5A7D, 0xD8CA, 0x5A7F, 0xB442, 0x5A83, 0xD8C6, 0x5A84, 0xD8C3, 0x5A8A, 0xD8C4, 0x5A8B, 0xD8C7, 0x5A8C, 0xD8CB, + 0x5A8E, 0xD4E3, 0x5A8F, 0xD8CD, 0x5A90, 0xDD47, 0x5A92, 0xB443, 0x5A93, 0xD8CE, 0x5A94, 0xD8B6, 0x5A95, 0xD8C0, 0x5A97, 0xD8C5, + 0x5A9A, 0xB441, 0x5A9B, 0xB444, 0x5A9C, 0xD8CC, 0x5A9D, 0xD8CF, 0x5A9E, 0xD8BA, 0x5A9F, 0xD8B7, 0x5AA2, 0xD8B9, 0x5AA5, 0xD8BE, + 0x5AA6, 0xD8BC, 0x5AA7, 0xB445, 0x5AA9, 0xD8C8, 0x5AAC, 0xD8BF, 0x5AAE, 0xD8C1, 0x5AAF, 0xD8B5, 0x5AB0, 0xDCFA, 0x5AB1, 0xDCF8, + 0x5AB2, 0xB742, 0x5AB3, 0xB740, 0x5AB4, 0xDD43, 0x5AB5, 0xDCF9, 0x5AB6, 0xDD44, 0x5AB7, 0xDD40, 0x5AB8, 0xDCF7, 0x5AB9, 0xDD46, + 0x5ABA, 0xDCF6, 0x5ABB, 0xDCFD, 0x5ABC, 0xB6FE, 0x5ABD, 0xB6FD, 0x5ABE, 0xB6FC, 0x5ABF, 0xDCFB, 0x5AC0, 0xDD41, 0x5AC1, 0xB6F9, + 0x5AC2, 0xB741, 0x5AC4, 0xDCF4, 0x5AC6, 0xDCFE, 0x5AC7, 0xDCF3, 0x5AC8, 0xDCFC, 0x5AC9, 0xB6FA, 0x5ACA, 0xDD42, 0x5ACB, 0xDCF5, + 0x5ACC, 0xB6FB, 0x5ACD, 0xDD45, 0x5AD5, 0xE16E, 0x5AD6, 0xB9E2, 0x5AD7, 0xB9E1, 0x5AD8, 0xB9E3, 0x5AD9, 0xE17A, 0x5ADA, 0xE170, + 0x5ADB, 0xE176, 0x5ADC, 0xE16B, 0x5ADD, 0xE179, 0x5ADE, 0xE178, 0x5ADF, 0xE17C, 0x5AE0, 0xE175, 0x5AE1, 0xB9DE, 0x5AE2, 0xE174, + 0x5AE3, 0xB9E4, 0x5AE5, 0xE16D, 0x5AE6, 0xB9DF, 0x5AE8, 0xE17B, 0x5AE9, 0xB9E0, 0x5AEA, 0xE16F, 0x5AEB, 0xE172, 0x5AEC, 0xE177, + 0x5AED, 0xE171, 0x5AEE, 0xE16C, 0x5AF3, 0xE173, 0x5AF4, 0xE555, 0x5AF5, 0xBC61, 0x5AF6, 0xE558, 0x5AF7, 0xE557, 0x5AF8, 0xE55A, + 0x5AF9, 0xE55C, 0x5AFA, 0xF9DC, 0x5AFB, 0xBC5F, 0x5AFD, 0xE556, 0x5AFF, 0xE554, 0x5B01, 0xE55D, 0x5B02, 0xE55B, 0x5B03, 0xE559, + 0x5B05, 0xE55F, 0x5B07, 0xE55E, 0x5B08, 0xBC63, 0x5B09, 0xBC5E, 0x5B0B, 0xBC60, 0x5B0C, 0xBC62, 0x5B0F, 0xE560, 0x5B10, 0xE957, + 0x5B13, 0xE956, 0x5B14, 0xE955, 0x5B16, 0xE958, 0x5B17, 0xE951, 0x5B19, 0xE952, 0x5B1A, 0xE95A, 0x5B1B, 0xE953, 0x5B1D, 0xBEC5, + 0x5B1E, 0xE95C, 0x5B20, 0xE95B, 0x5B21, 0xE954, 0x5B23, 0xECD1, 0x5B24, 0xC0A8, 0x5B25, 0xECCF, 0x5B26, 0xECD4, 0x5B27, 0xECD3, + 0x5B28, 0xE959, 0x5B2A, 0xC0A7, 0x5B2C, 0xECD2, 0x5B2D, 0xECCE, 0x5B2E, 0xECD6, 0x5B2F, 0xECD5, 0x5B30, 0xC0A6, 0x5B32, 0xECD0, + 0x5B34, 0xBEC6, 0x5B38, 0xC254, 0x5B3C, 0xEFC1, 0x5B3D, 0xF1FA, 0x5B3E, 0xF1FB, 0x5B3F, 0xF1FC, 0x5B40, 0xC45C, 0x5B43, 0xC45D, + 0x5B45, 0xF443, 0x5B47, 0xF5C8, 0x5B48, 0xF5C7, 0x5B4B, 0xF6DB, 0x5B4C, 0xF6DC, 0x5B4D, 0xF7D5, 0x5B4E, 0xF8A7, 0x5B50, 0xA46C, + 0x5B51, 0xA46D, 0x5B53, 0xA46E, 0x5B54, 0xA4D5, 0x5B55, 0xA5A5, 0x5B56, 0xC9D3, 0x5B57, 0xA672, 0x5B58, 0xA673, 0x5B5A, 0xA7B7, + 0x5B5B, 0xA7B8, 0x5B5C, 0xA7B6, 0x5B5D, 0xA7B5, 0x5B5F, 0xA973, 0x5B62, 0xCC55, 0x5B63, 0xA975, 0x5B64, 0xA974, 0x5B65, 0xCC56, + 0x5B69, 0xABC4, 0x5B6B, 0xAE5D, 0x5B6C, 0xD165, 0x5B6E, 0xD4F0, 0x5B70, 0xB145, 0x5B71, 0xB447, 0x5B72, 0xD4EF, 0x5B73, 0xB446, + 0x5B75, 0xB9E5, 0x5B77, 0xE17D, 0x5B78, 0xBEC7, 0x5B7A, 0xC0A9, 0x5B7B, 0xECD7, 0x5B7D, 0xC45E, 0x5B7F, 0xC570, 0x5B81, 0xC972, + 0x5B83, 0xA5A6, 0x5B84, 0xC973, 0x5B85, 0xA676, 0x5B87, 0xA674, 0x5B88, 0xA675, 0x5B89, 0xA677, 0x5B8B, 0xA7BA, 0x5B8C, 0xA7B9, + 0x5B8E, 0xCABC, 0x5B8F, 0xA7BB, 0x5B92, 0xCABD, 0x5B93, 0xCC57, 0x5B95, 0xCC58, 0x5B97, 0xA976, 0x5B98, 0xA978, 0x5B99, 0xA97A, + 0x5B9A, 0xA977, 0x5B9B, 0xA97B, 0x5B9C, 0xA979, 0x5BA2, 0xABC8, 0x5BA3, 0xABC5, 0x5BA4, 0xABC7, 0x5BA5, 0xABC9, 0x5BA6, 0xABC6, + 0x5BA7, 0xD166, 0x5BA8, 0xCE77, 0x5BAC, 0xD168, 0x5BAD, 0xD167, 0x5BAE, 0xAE63, 0x5BB0, 0xAE5F, 0x5BB3, 0xAE60, 0x5BB4, 0xAE62, + 0x5BB5, 0xAE64, 0x5BB6, 0xAE61, 0x5BB8, 0xAE66, 0x5BB9, 0xAE65, 0x5BBF, 0xB14A, 0x5BC0, 0xD4F2, 0x5BC1, 0xD4F1, 0x5BC2, 0xB149, + 0x5BC4, 0xB148, 0x5BC5, 0xB147, 0x5BC6, 0xB14B, 0x5BC7, 0xB146, 0x5BCA, 0xD8D5, 0x5BCB, 0xD8D2, 0x5BCC, 0xB449, 0x5BCD, 0xD8D1, + 0x5BCE, 0xD8D6, 0x5BD0, 0xB44B, 0x5BD1, 0xD8D4, 0x5BD2, 0xB448, 0x5BD3, 0xB44A, 0x5BD4, 0xD8D3, 0x5BD6, 0xDD48, 0x5BD8, 0xDD49, + 0x5BD9, 0xDD4A, 0x5BDE, 0xB9E6, 0x5BDF, 0xB9EE, 0x5BE0, 0xE17E, 0x5BE1, 0xB9E8, 0x5BE2, 0xB9EC, 0x5BE3, 0xE1A1, 0x5BE4, 0xB9ED, + 0x5BE5, 0xB9E9, 0x5BE6, 0xB9EA, 0x5BE7, 0xB9E7, 0x5BE8, 0xB9EB, 0x5BE9, 0xBC66, 0x5BEA, 0xD8D0, 0x5BEB, 0xBC67, 0x5BEC, 0xBC65, + 0x5BEE, 0xBC64, 0x5BEF, 0xE95D, 0x5BF0, 0xBEC8, 0x5BF1, 0xECD8, 0x5BF2, 0xECD9, 0x5BF5, 0xC364, 0x5BF6, 0xC45F, 0x5BF8, 0xA46F, + 0x5BFA, 0xA678, 0x5C01, 0xABCA, 0x5C03, 0xD169, 0x5C04, 0xAE67, 0x5C07, 0xB14E, 0x5C08, 0xB14D, 0x5C09, 0xB14C, 0x5C0A, 0xB44C, + 0x5C0B, 0xB44D, 0x5C0C, 0xD8D7, 0x5C0D, 0xB9EF, 0x5C0E, 0xBEC9, 0x5C0F, 0xA470, 0x5C10, 0xC95C, 0x5C11, 0xA4D6, 0x5C12, 0xC974, + 0x5C15, 0xC9D4, 0x5C16, 0xA679, 0x5C1A, 0xA97C, 0x5C1F, 0xDD4B, 0x5C22, 0xA471, 0x5C24, 0xA4D7, 0x5C25, 0xC9D5, 0x5C28, 0xCABE, + 0x5C2A, 0xCABF, 0x5C2C, 0xA7BC, 0x5C30, 0xD8D8, 0x5C31, 0xB44E, 0x5C33, 0xDD4C, 0x5C37, 0xC0AA, 0x5C38, 0xA472, 0x5C39, 0xA4A8, + 0x5C3A, 0xA4D8, 0x5C3B, 0xC975, 0x5C3C, 0xA5A7, 0x5C3E, 0xA7C0, 0x5C3F, 0xA7BF, 0x5C40, 0xA7BD, 0x5C41, 0xA7BE, 0x5C44, 0xCC59, + 0x5C45, 0xA97E, 0x5C46, 0xA9A1, 0x5C47, 0xCC5A, 0x5C48, 0xA97D, 0x5C4B, 0xABCE, 0x5C4C, 0xCE78, 0x5C4D, 0xABCD, 0x5C4E, 0xABCB, + 0x5C4F, 0xABCC, 0x5C50, 0xAE6A, 0x5C51, 0xAE68, 0x5C54, 0xD16B, 0x5C55, 0xAE69, 0x5C56, 0xD16A, 0x5C58, 0xAE5E, 0x5C59, 0xD4F3, + 0x5C5C, 0xB150, 0x5C5D, 0xB151, 0x5C60, 0xB14F, 0x5C62, 0xB9F0, 0x5C63, 0xE1A2, 0x5C64, 0xBC68, 0x5C65, 0xBC69, 0x5C67, 0xE561, + 0x5C68, 0xC0AB, 0x5C69, 0xEFC2, 0x5C6A, 0xEFC3, 0x5C6C, 0xC4DD, 0x5C6D, 0xF8A8, 0x5C6E, 0xC94B, 0x5C6F, 0xA4D9, 0x5C71, 0xA473, + 0x5C73, 0xC977, 0x5C74, 0xC976, 0x5C79, 0xA67A, 0x5C7A, 0xC9D7, 0x5C7B, 0xC9D8, 0x5C7C, 0xC9D6, 0x5C7E, 0xC9D9, 0x5C86, 0xCAC7, + 0x5C88, 0xCAC2, 0x5C89, 0xCAC4, 0x5C8A, 0xCAC6, 0x5C8B, 0xCAC3, 0x5C8C, 0xA7C4, 0x5C8D, 0xCAC0, 0x5C8F, 0xCAC1, 0x5C90, 0xA7C1, + 0x5C91, 0xA7C2, 0x5C92, 0xCAC5, 0x5C93, 0xCAC8, 0x5C94, 0xA7C3, 0x5C95, 0xCAC9, 0x5C9D, 0xCC68, 0x5C9F, 0xCC62, 0x5CA0, 0xCC5D, + 0x5CA1, 0xA9A3, 0x5CA2, 0xCC65, 0x5CA3, 0xCC63, 0x5CA4, 0xCC5C, 0x5CA5, 0xCC69, 0x5CA6, 0xCC6C, 0x5CA7, 0xCC67, 0x5CA8, 0xCC60, + 0x5CA9, 0xA9A5, 0x5CAA, 0xCC66, 0x5CAB, 0xA9A6, 0x5CAC, 0xCC61, 0x5CAD, 0xCC64, 0x5CAE, 0xCC5B, 0x5CAF, 0xCC5F, 0x5CB0, 0xCC6B, + 0x5CB1, 0xA9A7, 0x5CB3, 0xA9A8, 0x5CB5, 0xCC5E, 0x5CB6, 0xCC6A, 0x5CB7, 0xA9A2, 0x5CB8, 0xA9A4, 0x5CC6, 0xCEAB, 0x5CC7, 0xCEA4, + 0x5CC8, 0xCEAA, 0x5CC9, 0xCEA3, 0x5CCA, 0xCEA5, 0x5CCB, 0xCE7D, 0x5CCC, 0xCE7B, 0x5CCE, 0xCEAC, 0x5CCF, 0xCEA9, 0x5CD0, 0xCE79, + 0x5CD2, 0xABD0, 0x5CD3, 0xCEA7, 0x5CD4, 0xCEA8, 0x5CD6, 0xCEA6, 0x5CD7, 0xCE7C, 0x5CD8, 0xCE7A, 0x5CD9, 0xABCF, 0x5CDA, 0xCEA2, + 0x5CDB, 0xCE7E, 0x5CDE, 0xCEA1, 0x5CDF, 0xCEAD, 0x5CE8, 0xAE6F, 0x5CEA, 0xAE6E, 0x5CEC, 0xD16C, 0x5CED, 0xAE6B, 0x5CEE, 0xD16E, + 0x5CF0, 0xAE70, 0x5CF1, 0xD16F, 0x5CF4, 0xAE73, 0x5CF6, 0xAE71, 0x5CF7, 0xD170, 0x5CF8, 0xCEAE, 0x5CF9, 0xD172, 0x5CFB, 0xAE6D, + 0x5CFD, 0xAE6C, 0x5CFF, 0xD16D, 0x5D00, 0xD171, 0x5D01, 0xAE72, 0x5D06, 0xB153, 0x5D07, 0xB152, 0x5D0B, 0xD4F5, 0x5D0C, 0xD4F9, + 0x5D0D, 0xD4FB, 0x5D0E, 0xB154, 0x5D0F, 0xD4FE, 0x5D11, 0xB158, 0x5D12, 0xD541, 0x5D14, 0xB15A, 0x5D16, 0xB156, 0x5D17, 0xB15E, + 0x5D19, 0xB15B, 0x5D1A, 0xD4F7, 0x5D1B, 0xB155, 0x5D1D, 0xD4F6, 0x5D1E, 0xD4F4, 0x5D1F, 0xD543, 0x5D20, 0xD4F8, 0x5D22, 0xB157, + 0x5D23, 0xD542, 0x5D24, 0xB15C, 0x5D25, 0xD4FD, 0x5D26, 0xD4FC, 0x5D27, 0xB15D, 0x5D28, 0xD4FA, 0x5D29, 0xB159, 0x5D2E, 0xD544, + 0x5D30, 0xD540, 0x5D31, 0xD8E7, 0x5D32, 0xD8EE, 0x5D33, 0xD8E3, 0x5D34, 0xB451, 0x5D35, 0xD8DF, 0x5D36, 0xD8EF, 0x5D37, 0xD8D9, + 0x5D38, 0xD8EC, 0x5D39, 0xD8EA, 0x5D3A, 0xD8E4, 0x5D3C, 0xD8ED, 0x5D3D, 0xD8E6, 0x5D3F, 0xD8DE, 0x5D40, 0xD8F0, 0x5D41, 0xD8DC, + 0x5D42, 0xD8E9, 0x5D43, 0xD8DA, 0x5D45, 0xD8F1, 0x5D47, 0xB452, 0x5D49, 0xD8EB, 0x5D4A, 0xDD4F, 0x5D4B, 0xD8DD, 0x5D4C, 0xB44F, + 0x5D4E, 0xD8E1, 0x5D50, 0xB450, 0x5D51, 0xD8E0, 0x5D52, 0xD8E5, 0x5D55, 0xD8E2, 0x5D59, 0xD8E8, 0x5D5E, 0xDD53, 0x5D62, 0xDD56, + 0x5D63, 0xDD4E, 0x5D65, 0xDD50, 0x5D67, 0xDD55, 0x5D68, 0xDD54, 0x5D69, 0xB743, 0x5D6B, 0xD8DB, 0x5D6C, 0xDD52, 0x5D6F, 0xB744, + 0x5D71, 0xDD4D, 0x5D72, 0xDD51, 0x5D77, 0xE1A9, 0x5D79, 0xE1B0, 0x5D7A, 0xE1A7, 0x5D7C, 0xE1AE, 0x5D7D, 0xE1A5, 0x5D7E, 0xE1AD, + 0x5D7F, 0xE1B1, 0x5D80, 0xE1A4, 0x5D81, 0xE1A8, 0x5D82, 0xE1A3, 0x5D84, 0xB9F1, 0x5D86, 0xE1A6, 0x5D87, 0xB9F2, 0x5D88, 0xE1AC, + 0x5D89, 0xE1AB, 0x5D8A, 0xE1AA, 0x5D8D, 0xE1AF, 0x5D92, 0xE565, 0x5D93, 0xE567, 0x5D94, 0xBC6B, 0x5D95, 0xE568, 0x5D97, 0xE563, + 0x5D99, 0xE562, 0x5D9A, 0xE56C, 0x5D9C, 0xE56A, 0x5D9D, 0xBC6A, 0x5D9E, 0xE56D, 0x5D9F, 0xE564, 0x5DA0, 0xE569, 0x5DA1, 0xE56B, + 0x5DA2, 0xE566, 0x5DA7, 0xE961, 0x5DA8, 0xE966, 0x5DA9, 0xE960, 0x5DAA, 0xE965, 0x5DAC, 0xE95E, 0x5DAD, 0xE968, 0x5DAE, 0xE964, + 0x5DAF, 0xE969, 0x5DB0, 0xE963, 0x5DB1, 0xE95F, 0x5DB2, 0xE967, 0x5DB4, 0xE96A, 0x5DB5, 0xE962, 0x5DB7, 0xECDA, 0x5DB8, 0xC0AF, + 0x5DBA, 0xC0AD, 0x5DBC, 0xC0AC, 0x5DBD, 0xC0AE, 0x5DC0, 0xEFC4, 0x5DC2, 0xF172, 0x5DC3, 0xF1FD, 0x5DC6, 0xF444, 0x5DC7, 0xF445, + 0x5DC9, 0xC460, 0x5DCB, 0xF5C9, 0x5DCD, 0xC4DE, 0x5DCF, 0xF5CA, 0x5DD1, 0xF6DE, 0x5DD2, 0xC572, 0x5DD4, 0xC571, 0x5DD5, 0xF6DD, + 0x5DD6, 0xC5C9, 0x5DD8, 0xF7D6, 0x5DDD, 0xA474, 0x5DDE, 0xA67B, 0x5DDF, 0xC9DA, 0x5DE0, 0xCACA, 0x5DE1, 0xA8B5, 0x5DE2, 0xB15F, + 0x5DE5, 0xA475, 0x5DE6, 0xA5AA, 0x5DE7, 0xA5A9, 0x5DE8, 0xA5A8, 0x5DEB, 0xA7C5, 0x5DEE, 0xAE74, 0x5DF0, 0xDD57, 0x5DF1, 0xA476, + 0x5DF2, 0xA477, 0x5DF3, 0xA478, 0x5DF4, 0xA4DA, 0x5DF7, 0xABD1, 0x5DF9, 0xCEAF, 0x5DFD, 0xB453, 0x5DFE, 0xA479, 0x5DFF, 0xC95D, + 0x5E02, 0xA5AB, 0x5E03, 0xA5AC, 0x5E04, 0xC978, 0x5E06, 0xA67C, 0x5E0A, 0xCACB, 0x5E0C, 0xA7C6, 0x5E0E, 0xCACC, 0x5E11, 0xA9AE, + 0x5E14, 0xCC6E, 0x5E15, 0xA9AC, 0x5E16, 0xA9AB, 0x5E17, 0xCC6D, 0x5E18, 0xA9A9, 0x5E19, 0xCC6F, 0x5E1A, 0xA9AA, 0x5E1B, 0xA9AD, + 0x5E1D, 0xABD2, 0x5E1F, 0xABD4, 0x5E20, 0xCEB3, 0x5E21, 0xCEB0, 0x5E22, 0xCEB1, 0x5E23, 0xCEB2, 0x5E24, 0xCEB4, 0x5E25, 0xABD3, + 0x5E28, 0xD174, 0x5E29, 0xD173, 0x5E2B, 0xAE76, 0x5E2D, 0xAE75, 0x5E33, 0xB162, 0x5E34, 0xD546, 0x5E36, 0xB161, 0x5E37, 0xB163, + 0x5E38, 0xB160, 0x5E3D, 0xB455, 0x5E3E, 0xD545, 0x5E40, 0xB456, 0x5E41, 0xD8F3, 0x5E43, 0xB457, 0x5E44, 0xD8F2, 0x5E45, 0xB454, + 0x5E4A, 0xDD5A, 0x5E4B, 0xDD5C, 0x5E4C, 0xB745, 0x5E4D, 0xDD5B, 0x5E4E, 0xDD59, 0x5E4F, 0xDD58, 0x5E53, 0xE1B4, 0x5E54, 0xB9F7, + 0x5E55, 0xB9F5, 0x5E57, 0xB9F6, 0x5E58, 0xE1B2, 0x5E59, 0xE1B3, 0x5E5B, 0xB9F3, 0x5E5C, 0xE571, 0x5E5D, 0xE56F, 0x5E5F, 0xBC6D, + 0x5E60, 0xE570, 0x5E61, 0xBC6E, 0x5E62, 0xBC6C, 0x5E63, 0xB9F4, 0x5E66, 0xE96D, 0x5E67, 0xE96B, 0x5E68, 0xE96C, 0x5E69, 0xE56E, + 0x5E6A, 0xECDC, 0x5E6B, 0xC0B0, 0x5E6C, 0xECDB, 0x5E6D, 0xEFC5, 0x5E6E, 0xEFC6, 0x5E6F, 0xE96E, 0x5E70, 0xF1FE, 0x5E72, 0xA47A, + 0x5E73, 0xA5AD, 0x5E74, 0xA67E, 0x5E75, 0xC9DB, 0x5E76, 0xA67D, 0x5E78, 0xA9AF, 0x5E79, 0xB746, 0x5E7B, 0xA4DB, 0x5E7C, 0xA5AE, + 0x5E7D, 0xABD5, 0x5E7E, 0xB458, 0x5E80, 0xC979, 0x5E82, 0xC97A, 0x5E84, 0xC9DC, 0x5E87, 0xA7C8, 0x5E88, 0xCAD0, 0x5E89, 0xCACE, + 0x5E8A, 0xA7C9, 0x5E8B, 0xCACD, 0x5E8C, 0xCACF, 0x5E8D, 0xCAD1, 0x5E8F, 0xA7C7, 0x5E95, 0xA9B3, 0x5E96, 0xA9B4, 0x5E97, 0xA9B1, + 0x5E9A, 0xA9B0, 0x5E9B, 0xCEB8, 0x5E9C, 0xA9B2, 0x5EA0, 0xABD6, 0x5EA2, 0xCEB7, 0x5EA3, 0xCEB9, 0x5EA4, 0xCEB6, 0x5EA5, 0xCEBA, + 0x5EA6, 0xABD7, 0x5EA7, 0xAE79, 0x5EA8, 0xD175, 0x5EAA, 0xD177, 0x5EAB, 0xAE77, 0x5EAC, 0xD178, 0x5EAD, 0xAE78, 0x5EAE, 0xD176, + 0x5EB0, 0xCEB5, 0x5EB1, 0xD547, 0x5EB2, 0xD54A, 0x5EB3, 0xD54B, 0x5EB4, 0xD548, 0x5EB5, 0xB167, 0x5EB6, 0xB166, 0x5EB7, 0xB164, + 0x5EB8, 0xB165, 0x5EB9, 0xD549, 0x5EBE, 0xB168, 0x5EC1, 0xB45A, 0x5EC2, 0xB45B, 0x5EC4, 0xB45C, 0x5EC5, 0xDD5D, 0x5EC6, 0xDD5F, + 0x5EC7, 0xDD61, 0x5EC8, 0xB748, 0x5EC9, 0xB747, 0x5ECA, 0xB459, 0x5ECB, 0xDD60, 0x5ECC, 0xDD5E, 0x5ECE, 0xE1B8, 0x5ED1, 0xE1B6, + 0x5ED2, 0xE1BC, 0x5ED3, 0xB9F8, 0x5ED4, 0xE1BD, 0x5ED5, 0xE1BA, 0x5ED6, 0xB9F9, 0x5ED7, 0xE1B7, 0x5ED8, 0xE1B5, 0x5ED9, 0xE1BB, + 0x5EDA, 0xBC70, 0x5EDB, 0xE573, 0x5EDC, 0xE1B9, 0x5EDD, 0xBC72, 0x5EDE, 0xE574, 0x5EDF, 0xBC71, 0x5EE0, 0xBC74, 0x5EE1, 0xE575, + 0x5EE2, 0xBC6F, 0x5EE3, 0xBC73, 0x5EE5, 0xE973, 0x5EE6, 0xE971, 0x5EE7, 0xE970, 0x5EE8, 0xE972, 0x5EE9, 0xE96F, 0x5EEC, 0xC366, + 0x5EEE, 0xF446, 0x5EEF, 0xF447, 0x5EF1, 0xF5CB, 0x5EF2, 0xF6DF, 0x5EF3, 0xC655, 0x5EF6, 0xA9B5, 0x5EF7, 0xA7CA, 0x5EFA, 0xABD8, + 0x5EFE, 0xA47B, 0x5EFF, 0xA4DC, 0x5F01, 0xA5AF, 0x5F02, 0xC9DD, 0x5F04, 0xA7CB, 0x5F05, 0xCAD2, 0x5F07, 0xCEBB, 0x5F08, 0xABD9, + 0x5F0A, 0xB9FA, 0x5F0B, 0xA47C, 0x5F0F, 0xA6A1, 0x5F12, 0xB749, 0x5F13, 0xA47D, 0x5F14, 0xA4DD, 0x5F15, 0xA4DE, 0x5F17, 0xA5B1, + 0x5F18, 0xA5B0, 0x5F1A, 0xC9DE, 0x5F1B, 0xA6A2, 0x5F1D, 0xCAD3, 0x5F1F, 0xA7CC, 0x5F22, 0xCC71, 0x5F23, 0xCC72, 0x5F24, 0xCC73, + 0x5F26, 0xA9B6, 0x5F27, 0xA9B7, 0x5F28, 0xCC70, 0x5F29, 0xA9B8, 0x5F2D, 0xABDA, 0x5F2E, 0xCEBC, 0x5F30, 0xD17A, 0x5F31, 0xAE7A, + 0x5F33, 0xD179, 0x5F35, 0xB169, 0x5F36, 0xD54C, 0x5F37, 0xB16A, 0x5F38, 0xD54D, 0x5F3C, 0xB45D, 0x5F40, 0xDD62, 0x5F43, 0xE1BF, + 0x5F44, 0xE1BE, 0x5F46, 0xB9FB, 0x5F48, 0xBC75, 0x5F49, 0xE576, 0x5F4A, 0xBECA, 0x5F4B, 0xE974, 0x5F4C, 0xC0B1, 0x5F4E, 0xC573, + 0x5F4F, 0xF7D8, 0x5F54, 0xCC74, 0x5F56, 0xCEBD, 0x5F57, 0xB16B, 0x5F58, 0xD8F4, 0x5F59, 0xB74A, 0x5F5D, 0xC255, 0x5F62, 0xA7CE, + 0x5F64, 0xA7CD, 0x5F65, 0xABDB, 0x5F67, 0xD17B, 0x5F69, 0xB16D, 0x5F6A, 0xB343, 0x5F6B, 0xB16E, 0x5F6C, 0xB16C, 0x5F6D, 0xB45E, + 0x5F6F, 0xE1C0, 0x5F70, 0xB9FC, 0x5F71, 0xBC76, 0x5F73, 0xC94C, 0x5F74, 0xC9DF, 0x5F76, 0xCAD5, 0x5F77, 0xA7CF, 0x5F78, 0xCAD4, + 0x5F79, 0xA7D0, 0x5F7C, 0xA9BC, 0x5F7D, 0xCC77, 0x5F7E, 0xCC76, 0x5F7F, 0xA9BB, 0x5F80, 0xA9B9, 0x5F81, 0xA9BA, 0x5F82, 0xCC75, + 0x5F85, 0xABDD, 0x5F86, 0xCEBE, 0x5F87, 0xABE0, 0x5F88, 0xABDC, 0x5F89, 0xABE2, 0x5F8A, 0xABDE, 0x5F8B, 0xABDF, 0x5F8C, 0xABE1, + 0x5F90, 0xAE7D, 0x5F91, 0xAE7C, 0x5F92, 0xAE7B, 0x5F96, 0xD54F, 0x5F97, 0xB16F, 0x5F98, 0xB172, 0x5F99, 0xB170, 0x5F9B, 0xD54E, + 0x5F9C, 0xB175, 0x5F9E, 0xB171, 0x5F9F, 0xD550, 0x5FA0, 0xB174, 0x5FA1, 0xB173, 0x5FA5, 0xD8F6, 0x5FA6, 0xD8F5, 0x5FA8, 0xB461, + 0x5FA9, 0xB45F, 0x5FAA, 0xB460, 0x5FAB, 0xD8F7, 0x5FAC, 0xB74B, 0x5FAD, 0xDD64, 0x5FAE, 0xB74C, 0x5FAF, 0xDD63, 0x5FB2, 0xE577, + 0x5FB5, 0xBC78, 0x5FB6, 0xE1C1, 0x5FB7, 0xBC77, 0x5FB9, 0xB9FD, 0x5FBB, 0xECDE, 0x5FBC, 0xE975, 0x5FBD, 0xC0B2, 0x5FBE, 0xECDD, + 0x5FBF, 0xF240, 0x5FC0, 0xF448, 0x5FC1, 0xF449, 0x5FC3, 0xA4DF, 0x5FC5, 0xA5B2, 0x5FC9, 0xC97B, 0x5FCC, 0xA7D2, 0x5FCD, 0xA7D4, + 0x5FCF, 0xC9E2, 0x5FD0, 0xCAD8, 0x5FD1, 0xCAD7, 0x5FD2, 0xCAD6, 0x5FD4, 0xC9E1, 0x5FD5, 0xC9E0, 0x5FD6, 0xA6A4, 0x5FD7, 0xA7D3, + 0x5FD8, 0xA7D1, 0x5FD9, 0xA6A3, 0x5FDD, 0xA9BD, 0x5FDE, 0xCC78, 0x5FE0, 0xA9BE, 0x5FE1, 0xCADD, 0x5FE3, 0xCADF, 0x5FE4, 0xCADE, + 0x5FE5, 0xCC79, 0x5FE8, 0xCADA, 0x5FEA, 0xA7D8, 0x5FEB, 0xA7D6, 0x5FED, 0xCAD9, 0x5FEE, 0xCADB, 0x5FEF, 0xCAE1, 0x5FF1, 0xA7D5, + 0x5FF3, 0xCADC, 0x5FF4, 0xCAE5, 0x5FF5, 0xA9C0, 0x5FF7, 0xCAE2, 0x5FF8, 0xA7D7, 0x5FFA, 0xCAE0, 0x5FFB, 0xCAE3, 0x5FFD, 0xA9BF, + 0x5FFF, 0xA9C1, 0x6000, 0xCAE4, 0x6009, 0xCCAF, 0x600A, 0xCCA2, 0x600B, 0xCC7E, 0x600C, 0xCCAE, 0x600D, 0xCCA9, 0x600E, 0xABE7, + 0x600F, 0xA9C2, 0x6010, 0xCCAA, 0x6011, 0xCCAD, 0x6012, 0xABE3, 0x6013, 0xCCAC, 0x6014, 0xA9C3, 0x6015, 0xA9C8, 0x6016, 0xA9C6, + 0x6017, 0xCCA3, 0x6019, 0xCC7C, 0x601A, 0xCCA5, 0x601B, 0xA9CD, 0x601C, 0xCCB0, 0x601D, 0xABE4, 0x601E, 0xCCA6, 0x6020, 0xABE5, + 0x6021, 0xA9C9, 0x6022, 0xCCA8, 0x6024, 0xCECD, 0x6025, 0xABE6, 0x6026, 0xCC7B, 0x6027, 0xA9CA, 0x6028, 0xABE8, 0x6029, 0xA9CB, + 0x602A, 0xA9C7, 0x602B, 0xA9CC, 0x602C, 0xCCA7, 0x602D, 0xCC7A, 0x602E, 0xCCAB, 0x602F, 0xA9C4, 0x6032, 0xCC7D, 0x6033, 0xCCA4, + 0x6034, 0xCCA1, 0x6035, 0xA9C5, 0x6037, 0xCEBF, 0x6039, 0xCEC0, 0x6040, 0xCECA, 0x6041, 0xD1A1, 0x6042, 0xCECB, 0x6043, 0xABEE, + 0x6044, 0xCECE, 0x6045, 0xCEC4, 0x6046, 0xABED, 0x6047, 0xCEC6, 0x6049, 0xCEC7, 0x604C, 0xCEC9, 0x604D, 0xABE9, 0x6050, 0xAEA3, + 0x6052, 0xF9DA, 0x6053, 0xCEC5, 0x6054, 0xCEC1, 0x6055, 0xAEA4, 0x6058, 0xCECF, 0x6059, 0xAE7E, 0x605A, 0xD17D, 0x605B, 0xCEC8, + 0x605D, 0xD17C, 0x605E, 0xCEC3, 0x605F, 0xCECC, 0x6062, 0xABEC, 0x6063, 0xAEA1, 0x6064, 0xABF2, 0x6065, 0xAEA2, 0x6066, 0xCED0, + 0x6067, 0xD17E, 0x6068, 0xABEB, 0x6069, 0xAEA6, 0x606A, 0xABF1, 0x606B, 0xABF0, 0x606C, 0xABEF, 0x606D, 0xAEA5, 0x606E, 0xCED1, + 0x606F, 0xAEA7, 0x6070, 0xABEA, 0x6072, 0xCEC2, 0x607F, 0xB176, 0x6080, 0xD1A4, 0x6081, 0xD1A6, 0x6083, 0xD1A8, 0x6084, 0xAEA8, + 0x6085, 0xAEAE, 0x6086, 0xD553, 0x6087, 0xD1AC, 0x6088, 0xD1A3, 0x6089, 0xB178, 0x608A, 0xD551, 0x608C, 0xAEAD, 0x608D, 0xAEAB, + 0x608E, 0xD1AE, 0x6090, 0xD552, 0x6092, 0xD1A5, 0x6094, 0xAEAC, 0x6095, 0xD1A9, 0x6096, 0xAEAF, 0x6097, 0xD1AB, 0x609A, 0xAEAA, + 0x609B, 0xD1AA, 0x609C, 0xD1AD, 0x609D, 0xD1A7, 0x609F, 0xAEA9, 0x60A0, 0xB179, 0x60A2, 0xD1A2, 0x60A3, 0xB177, 0x60A8, 0xB17A, + 0x60B0, 0xD555, 0x60B1, 0xD55E, 0x60B2, 0xB464, 0x60B4, 0xB17C, 0x60B5, 0xB1A3, 0x60B6, 0xB465, 0x60B7, 0xD560, 0x60B8, 0xB1AA, + 0x60B9, 0xD8F9, 0x60BA, 0xD556, 0x60BB, 0xB1A2, 0x60BC, 0xB1A5, 0x60BD, 0xB17E, 0x60BE, 0xD554, 0x60BF, 0xD562, 0x60C0, 0xD565, + 0x60C1, 0xD949, 0x60C3, 0xD563, 0x60C4, 0xD8FD, 0x60C5, 0xB1A1, 0x60C6, 0xB1A8, 0x60C7, 0xB1AC, 0x60C8, 0xD55D, 0x60C9, 0xD8F8, + 0x60CA, 0xD561, 0x60CB, 0xB17B, 0x60CC, 0xD8FA, 0x60CD, 0xD564, 0x60CE, 0xD8FC, 0x60CF, 0xD559, 0x60D1, 0xB462, 0x60D3, 0xD557, + 0x60D4, 0xD558, 0x60D5, 0xB1A7, 0x60D8, 0xB1A6, 0x60D9, 0xD55B, 0x60DA, 0xB1AB, 0x60DB, 0xD55F, 0x60DC, 0xB1A4, 0x60DD, 0xD55C, + 0x60DF, 0xB1A9, 0x60E0, 0xB466, 0x60E1, 0xB463, 0x60E2, 0xD8FB, 0x60E4, 0xD55A, 0x60E6, 0xB17D, 0x60F0, 0xB46B, 0x60F1, 0xB46F, + 0x60F2, 0xD940, 0x60F3, 0xB751, 0x60F4, 0xB46D, 0x60F5, 0xD944, 0x60F6, 0xB471, 0x60F7, 0xDD65, 0x60F8, 0xD946, 0x60F9, 0xB753, + 0x60FA, 0xB469, 0x60FB, 0xB46C, 0x60FC, 0xD947, 0x60FE, 0xD948, 0x60FF, 0xD94E, 0x6100, 0xB473, 0x6101, 0xB754, 0x6103, 0xD94A, + 0x6104, 0xD94F, 0x6105, 0xD943, 0x6106, 0xB75E, 0x6108, 0xB755, 0x6109, 0xB472, 0x610A, 0xD941, 0x610B, 0xD950, 0x610D, 0xB75D, + 0x610E, 0xB470, 0x610F, 0xB74E, 0x6110, 0xD94D, 0x6112, 0xB474, 0x6113, 0xD945, 0x6114, 0xD8FE, 0x6115, 0xB46A, 0x6116, 0xD942, + 0x6118, 0xD94B, 0x611A, 0xB74D, 0x611B, 0xB752, 0x611C, 0xB467, 0x611D, 0xD94C, 0x611F, 0xB750, 0x6123, 0xB468, 0x6127, 0xB75C, + 0x6128, 0xE1C3, 0x6129, 0xDD70, 0x612B, 0xDD68, 0x612C, 0xE1C2, 0x612E, 0xDD6C, 0x612F, 0xDD6E, 0x6132, 0xDD6B, 0x6134, 0xB75B, + 0x6136, 0xDD6A, 0x6137, 0xB75F, 0x613B, 0xE1D2, 0x613E, 0xB75A, 0x613F, 0xBA40, 0x6140, 0xDD71, 0x6141, 0xE1C4, 0x6144, 0xB758, + 0x6145, 0xDD69, 0x6146, 0xDD6D, 0x6147, 0xB9FE, 0x6148, 0xB74F, 0x6149, 0xDD66, 0x614A, 0xDD67, 0x614B, 0xBA41, 0x614C, 0xB757, + 0x614D, 0xB759, 0x614E, 0xB756, 0x614F, 0xDD6F, 0x6152, 0xE1C8, 0x6153, 0xE1C9, 0x6154, 0xE1CE, 0x6155, 0xBC7D, 0x6156, 0xE1D5, + 0x6158, 0xBA47, 0x615A, 0xBA46, 0x615B, 0xE1D0, 0x615D, 0xBC7C, 0x615E, 0xE1C5, 0x615F, 0xBA45, 0x6161, 0xE1D4, 0x6162, 0xBA43, + 0x6163, 0xBA44, 0x6165, 0xE1D1, 0x6166, 0xE5AA, 0x6167, 0xBC7A, 0x6168, 0xB46E, 0x616A, 0xE1D3, 0x616B, 0xBCA3, 0x616C, 0xE1CB, + 0x616E, 0xBC7B, 0x6170, 0xBCA2, 0x6171, 0xE1C6, 0x6172, 0xE1CA, 0x6173, 0xE1C7, 0x6174, 0xE1CD, 0x6175, 0xBA48, 0x6176, 0xBC79, + 0x6177, 0xBA42, 0x6179, 0xE57A, 0x617A, 0xE1CF, 0x617C, 0xBCA1, 0x617E, 0xBCA4, 0x6180, 0xE1CC, 0x6182, 0xBC7E, 0x6183, 0xE579, + 0x6189, 0xE57E, 0x618A, 0xBECE, 0x618B, 0xE578, 0x618C, 0xE9A3, 0x618D, 0xE5A9, 0x618E, 0xBCA8, 0x6190, 0xBCA6, 0x6191, 0xBECC, + 0x6192, 0xE5A6, 0x6193, 0xE5A2, 0x6194, 0xBCAC, 0x6196, 0xE978, 0x619A, 0xBCAA, 0x619B, 0xE5A1, 0x619D, 0xE976, 0x619F, 0xE5A5, + 0x61A1, 0xE5A8, 0x61A2, 0xE57D, 0x61A4, 0xBCAB, 0x61A7, 0xBCA5, 0x61A8, 0xE977, 0x61A9, 0xBECD, 0x61AA, 0xE5A7, 0x61AB, 0xBCA7, + 0x61AC, 0xBCA9, 0x61AD, 0xE5A4, 0x61AE, 0xBCAD, 0x61AF, 0xE5A3, 0x61B0, 0xE57C, 0x61B1, 0xE57B, 0x61B2, 0xBECB, 0x61B3, 0xE5AB, + 0x61B4, 0xE97A, 0x61B5, 0xECE0, 0x61B6, 0xBED0, 0x61B8, 0xE9A2, 0x61BA, 0xE97E, 0x61BC, 0xECE1, 0x61BE, 0xBED1, 0x61BF, 0xE9A1, + 0x61C1, 0xE97C, 0x61C2, 0xC0B4, 0x61C3, 0xECDF, 0x61C5, 0xE979, 0x61C6, 0xE97B, 0x61C7, 0xC0B5, 0x61C8, 0xBED3, 0x61C9, 0xC0B3, + 0x61CA, 0xBED2, 0x61CB, 0xC0B7, 0x61CC, 0xE97D, 0x61CD, 0xBECF, 0x61D6, 0xEFCF, 0x61D8, 0xEFC7, 0x61DE, 0xECE7, 0x61DF, 0xEFC8, + 0x61E0, 0xECE3, 0x61E3, 0xC256, 0x61E4, 0xECE5, 0x61E5, 0xECE4, 0x61E6, 0xC0B6, 0x61E7, 0xECE2, 0x61E8, 0xECE6, 0x61E9, 0xEFD0, + 0x61EA, 0xEFCC, 0x61EB, 0xEFCE, 0x61ED, 0xEFC9, 0x61EE, 0xEFCA, 0x61F0, 0xEFCD, 0x61F1, 0xEFCB, 0x61F2, 0xC367, 0x61F5, 0xC36A, + 0x61F6, 0xC369, 0x61F7, 0xC368, 0x61F8, 0xC461, 0x61F9, 0xF44A, 0x61FA, 0xC462, 0x61FB, 0xF241, 0x61FC, 0xC4DF, 0x61FD, 0xF5CC, + 0x61FE, 0xC4E0, 0x61FF, 0xC574, 0x6200, 0xC5CA, 0x6201, 0xF7D9, 0x6203, 0xF7DA, 0x6204, 0xF7DB, 0x6207, 0xF9BA, 0x6208, 0xA4E0, + 0x6209, 0xC97C, 0x620A, 0xA5B3, 0x620C, 0xA6A6, 0x620D, 0xA6A7, 0x620E, 0xA6A5, 0x6210, 0xA6A8, 0x6211, 0xA7DA, 0x6212, 0xA7D9, + 0x6214, 0xCCB1, 0x6215, 0xA9CF, 0x6216, 0xA9CE, 0x6219, 0xD1AF, 0x621A, 0xB1AD, 0x621B, 0xB1AE, 0x621F, 0xB475, 0x6220, 0xDD72, + 0x6221, 0xB760, 0x6222, 0xB761, 0x6223, 0xDD74, 0x6224, 0xDD76, 0x6225, 0xDD75, 0x6227, 0xE1D7, 0x6229, 0xE1D6, 0x622A, 0xBA49, + 0x622B, 0xE1D8, 0x622D, 0xE5AC, 0x622E, 0xBCAE, 0x6230, 0xBED4, 0x6232, 0xC0B8, 0x6233, 0xC257, 0x6234, 0xC0B9, 0x6236, 0xA4E1, + 0x623A, 0xCAE6, 0x623D, 0xCCB2, 0x623E, 0xA9D1, 0x623F, 0xA9D0, 0x6240, 0xA9D2, 0x6241, 0xABF3, 0x6242, 0xCED2, 0x6243, 0xCED3, + 0x6246, 0xD1B0, 0x6247, 0xAEB0, 0x6248, 0xB1AF, 0x6249, 0xB476, 0x624A, 0xD951, 0x624B, 0xA4E2, 0x624D, 0xA47E, 0x624E, 0xA4E3, + 0x6250, 0xC97D, 0x6251, 0xA5B7, 0x6252, 0xA5B6, 0x6253, 0xA5B4, 0x6254, 0xA5B5, 0x6258, 0xA6AB, 0x6259, 0xC9E9, 0x625A, 0xC9EB, + 0x625B, 0xA6AA, 0x625C, 0xC9E3, 0x625E, 0xC9E4, 0x6260, 0xC9EA, 0x6261, 0xC9E6, 0x6262, 0xC9E8, 0x6263, 0xA6A9, 0x6264, 0xC9E5, + 0x6265, 0xC9EC, 0x6266, 0xC9E7, 0x626D, 0xA7E1, 0x626E, 0xA7EA, 0x626F, 0xA7E8, 0x6270, 0xCAF0, 0x6271, 0xCAED, 0x6272, 0xCAF5, + 0x6273, 0xA7E6, 0x6274, 0xCAF6, 0x6276, 0xA7DF, 0x6277, 0xCAF3, 0x6279, 0xA7E5, 0x627A, 0xCAEF, 0x627B, 0xCAEE, 0x627C, 0xA7E3, + 0x627D, 0xCAF4, 0x627E, 0xA7E4, 0x627F, 0xA9D3, 0x6280, 0xA7DE, 0x6281, 0xCAF1, 0x6283, 0xCAE7, 0x6284, 0xA7DB, 0x6286, 0xA7EE, + 0x6287, 0xCAEC, 0x6288, 0xCAF2, 0x6289, 0xA7E0, 0x628A, 0xA7E2, 0x628C, 0xCAE8, 0x628E, 0xCAE9, 0x628F, 0xCAEA, 0x6291, 0xA7ED, + 0x6292, 0xA7E7, 0x6293, 0xA7EC, 0x6294, 0xCAEB, 0x6295, 0xA7EB, 0x6296, 0xA7DD, 0x6297, 0xA7DC, 0x6298, 0xA7E9, 0x62A8, 0xA9E1, + 0x62A9, 0xCCBE, 0x62AA, 0xCCB7, 0x62AB, 0xA9DC, 0x62AC, 0xA9EF, 0x62AD, 0xCCB3, 0x62AE, 0xCCBA, 0x62AF, 0xCCBC, 0x62B0, 0xCCBF, + 0x62B1, 0xA9EA, 0x62B3, 0xCCBB, 0x62B4, 0xCCB4, 0x62B5, 0xA9E8, 0x62B6, 0xCCB8, 0x62B8, 0xCCC0, 0x62B9, 0xA9D9, 0x62BB, 0xCCBD, + 0x62BC, 0xA9E3, 0x62BD, 0xA9E2, 0x62BE, 0xCCB6, 0x62BF, 0xA9D7, 0x62C2, 0xA9D8, 0x62C4, 0xA9D6, 0x62C6, 0xA9EE, 0x62C7, 0xA9E6, + 0x62C8, 0xA9E0, 0x62C9, 0xA9D4, 0x62CA, 0xCCB9, 0x62CB, 0xA9DF, 0x62CC, 0xA9D5, 0x62CD, 0xA9E7, 0x62CE, 0xA9F0, 0x62CF, 0xCED4, + 0x62D0, 0xA9E4, 0x62D1, 0xCCB5, 0x62D2, 0xA9DA, 0x62D3, 0xA9DD, 0x62D4, 0xA9DE, 0x62D6, 0xA9EC, 0x62D7, 0xA9ED, 0x62D8, 0xA9EB, + 0x62D9, 0xA9E5, 0x62DA, 0xA9E9, 0x62DB, 0xA9DB, 0x62DC, 0xABF4, 0x62EB, 0xCEDA, 0x62EC, 0xAC41, 0x62ED, 0xABF8, 0x62EE, 0xABFA, + 0x62EF, 0xAC40, 0x62F0, 0xCEE6, 0x62F1, 0xABFD, 0x62F2, 0xD1B1, 0x62F3, 0xAEB1, 0x62F4, 0xAC43, 0x62F5, 0xCED7, 0x62F6, 0xCEDF, + 0x62F7, 0xABFE, 0x62F8, 0xCEDE, 0x62F9, 0xCEDB, 0x62FA, 0xCEE3, 0x62FB, 0xCEE5, 0x62FC, 0xABF7, 0x62FD, 0xABFB, 0x62FE, 0xAC42, + 0x62FF, 0xAEB3, 0x6300, 0xCEE0, 0x6301, 0xABF9, 0x6302, 0xAC45, 0x6303, 0xCED9, 0x6307, 0xABFC, 0x6308, 0xAEB2, 0x6309, 0xABF6, + 0x630B, 0xCED6, 0x630C, 0xCEDD, 0x630D, 0xCED5, 0x630E, 0xCED8, 0x630F, 0xCEDC, 0x6310, 0xD1B2, 0x6311, 0xAC44, 0x6313, 0xCEE1, + 0x6314, 0xCEE2, 0x6315, 0xCEE4, 0x6316, 0xABF5, 0x6328, 0xAEC1, 0x6329, 0xD1BE, 0x632A, 0xAEBF, 0x632B, 0xAEC0, 0x632C, 0xD1B4, + 0x632D, 0xD1C4, 0x632F, 0xAEB6, 0x6332, 0xD566, 0x6333, 0xD1C6, 0x6334, 0xD1C0, 0x6336, 0xD1B7, 0x6338, 0xD1C9, 0x6339, 0xD1BA, + 0x633A, 0xAEBC, 0x633B, 0xD57D, 0x633C, 0xD1BD, 0x633D, 0xAEBE, 0x633E, 0xAEB5, 0x6340, 0xD1CB, 0x6341, 0xD1BF, 0x6342, 0xAEB8, + 0x6343, 0xD1B8, 0x6344, 0xD1B5, 0x6345, 0xD1B6, 0x6346, 0xAEB9, 0x6347, 0xD1C5, 0x6348, 0xD1CC, 0x6349, 0xAEBB, 0x634A, 0xD1BC, + 0x634B, 0xD1BB, 0x634C, 0xAEC3, 0x634D, 0xAEC2, 0x634E, 0xAEB4, 0x634F, 0xAEBA, 0x6350, 0xAEBD, 0x6351, 0xD1C8, 0x6354, 0xD1C2, + 0x6355, 0xAEB7, 0x6356, 0xD1B3, 0x6357, 0xD1CA, 0x6358, 0xD1C1, 0x6359, 0xD1C3, 0x635A, 0xD1C7, 0x6365, 0xD567, 0x6367, 0xB1B7, + 0x6368, 0xB1CB, 0x6369, 0xB1CA, 0x636B, 0xB1BF, 0x636D, 0xD579, 0x636E, 0xD575, 0x636F, 0xD572, 0x6370, 0xD5A6, 0x6371, 0xB1BA, + 0x6372, 0xB1B2, 0x6375, 0xD577, 0x6376, 0xB4A8, 0x6377, 0xB1B6, 0x6378, 0xD5A1, 0x637A, 0xB1CC, 0x637B, 0xB1C9, 0x637C, 0xD57B, + 0x637D, 0xD56A, 0x6380, 0xB1C8, 0x6381, 0xD5A3, 0x6382, 0xD569, 0x6383, 0xB1BD, 0x6384, 0xB1C1, 0x6385, 0xD5A2, 0x6387, 0xD573, + 0x6388, 0xB1C2, 0x6389, 0xB1BC, 0x638A, 0xD568, 0x638C, 0xB478, 0x638D, 0xD5A5, 0x638E, 0xD571, 0x638F, 0xB1C7, 0x6390, 0xD574, + 0x6391, 0xD5A4, 0x6392, 0xB1C6, 0x6394, 0xD952, 0x6396, 0xB1B3, 0x6397, 0xD56F, 0x6398, 0xB1B8, 0x6399, 0xB1C3, 0x639B, 0xB1BE, + 0x639C, 0xD578, 0x639D, 0xD56E, 0x639E, 0xD56C, 0x639F, 0xD57E, 0x63A0, 0xB1B0, 0x63A1, 0xB1C4, 0x63A2, 0xB1B4, 0x63A3, 0xB477, + 0x63A4, 0xD57C, 0x63A5, 0xB1B5, 0x63A7, 0xB1B1, 0x63A8, 0xB1C0, 0x63A9, 0xB1BB, 0x63AA, 0xB1B9, 0x63AB, 0xD570, 0x63AC, 0xB1C5, + 0x63AD, 0xD56D, 0x63AE, 0xD57A, 0x63AF, 0xD576, 0x63B0, 0xD954, 0x63B1, 0xD953, 0x63BD, 0xD56B, 0x63BE, 0xD964, 0x63C0, 0xB47A, + 0x63C2, 0xD96A, 0x63C3, 0xD959, 0x63C4, 0xD967, 0x63C5, 0xDD77, 0x63C6, 0xB47D, 0x63C7, 0xD96B, 0x63C8, 0xD96E, 0x63C9, 0xB47C, + 0x63CA, 0xD95C, 0x63CB, 0xD96D, 0x63CC, 0xD96C, 0x63CD, 0xB47E, 0x63CE, 0xD955, 0x63CF, 0xB479, 0x63D0, 0xB4A3, 0x63D2, 0xB4A1, + 0x63D3, 0xD969, 0x63D5, 0xD95F, 0x63D6, 0xB4A5, 0x63D7, 0xD970, 0x63D8, 0xD968, 0x63D9, 0xD971, 0x63DA, 0xB4AD, 0x63DB, 0xB4AB, + 0x63DC, 0xD966, 0x63DD, 0xD965, 0x63DF, 0xD963, 0x63E0, 0xD95D, 0x63E1, 0xB4A4, 0x63E3, 0xB4A2, 0x63E4, 0xD1B9, 0x63E5, 0xD956, + 0x63E7, 0xDDB7, 0x63E8, 0xD957, 0x63E9, 0xB47B, 0x63EA, 0xB4AA, 0x63EB, 0xDD79, 0x63ED, 0xB4A6, 0x63EE, 0xB4A7, 0x63EF, 0xD958, + 0x63F0, 0xD96F, 0x63F1, 0xDD78, 0x63F2, 0xD960, 0x63F3, 0xD95B, 0x63F4, 0xB4A9, 0x63F5, 0xD961, 0x63F6, 0xD95E, 0x63F9, 0xB4AE, + 0x6406, 0xB770, 0x6409, 0xDD7C, 0x640A, 0xDDB1, 0x640B, 0xDDB6, 0x640C, 0xDDAA, 0x640D, 0xB76C, 0x640E, 0xDDBB, 0x640F, 0xB769, + 0x6410, 0xDD7A, 0x6412, 0xDD7B, 0x6413, 0xB762, 0x6414, 0xB76B, 0x6415, 0xDDA4, 0x6416, 0xB76E, 0x6417, 0xB76F, 0x6418, 0xDDA5, + 0x641A, 0xDDB2, 0x641B, 0xDDB8, 0x641C, 0xB76A, 0x641E, 0xB764, 0x641F, 0xDDA3, 0x6420, 0xDD7D, 0x6421, 0xDDBA, 0x6422, 0xDDA8, + 0x6423, 0xDDA9, 0x6424, 0xDD7E, 0x6425, 0xDDB4, 0x6426, 0xDDAB, 0x6427, 0xDDB5, 0x6428, 0xDDAD, 0x642A, 0xB765, 0x642B, 0xE1D9, + 0x642C, 0xB768, 0x642D, 0xB766, 0x642E, 0xDDB9, 0x642F, 0xDDB0, 0x6430, 0xDDAC, 0x6433, 0xDDA1, 0x6434, 0xBA53, 0x6435, 0xDDAF, + 0x6436, 0xB76D, 0x6437, 0xDDA7, 0x6439, 0xDDA6, 0x643D, 0xB767, 0x643E, 0xB763, 0x643F, 0xE1EE, 0x6440, 0xDDB3, 0x6441, 0xDDAE, + 0x6443, 0xDDA2, 0x644B, 0xE1E9, 0x644D, 0xE1DA, 0x644E, 0xE1E5, 0x6450, 0xE1EC, 0x6451, 0xBA51, 0x6452, 0xB4AC, 0x6453, 0xE1EA, + 0x6454, 0xBA4C, 0x6458, 0xBA4B, 0x6459, 0xE1F1, 0x645B, 0xE1DB, 0x645C, 0xE1E8, 0x645D, 0xE1DC, 0x645E, 0xE1E7, 0x645F, 0xBA4F, + 0x6460, 0xE1EB, 0x6461, 0xD962, 0x6465, 0xE1F2, 0x6466, 0xE1E3, 0x6467, 0xBA52, 0x6468, 0xE5BA, 0x6469, 0xBCAF, 0x646B, 0xE1F0, + 0x646C, 0xE1EF, 0x646D, 0xBA54, 0x646E, 0xE5AD, 0x646F, 0xBCB0, 0x6470, 0xE5AE, 0x6472, 0xE1DF, 0x6473, 0xE1E0, 0x6474, 0xE1DD, + 0x6475, 0xE1E2, 0x6476, 0xE1DE, 0x6477, 0xE1F3, 0x6478, 0xBA4E, 0x6479, 0xBCB1, 0x647A, 0xBA50, 0x647B, 0xBA55, 0x647D, 0xE1E1, + 0x647F, 0xE1ED, 0x6482, 0xE1E6, 0x6485, 0xE5B1, 0x6487, 0xBA4A, 0x6488, 0xBCB4, 0x6489, 0xE9AA, 0x648A, 0xE5B6, 0x648B, 0xE5B5, + 0x648C, 0xE5B7, 0x648F, 0xE5B4, 0x6490, 0xBCB5, 0x6492, 0xBCBB, 0x6493, 0xBCB8, 0x6495, 0xBCB9, 0x6496, 0xE5AF, 0x6497, 0xE5B2, + 0x6498, 0xE5BC, 0x6499, 0xBCC1, 0x649A, 0xBCBF, 0x649C, 0xE5B3, 0x649D, 0xD95A, 0x649E, 0xBCB2, 0x649F, 0xE5B9, 0x64A0, 0xE5B0, + 0x64A2, 0xBCC2, 0x64A3, 0xE5B8, 0x64A4, 0xBA4D, 0x64A5, 0xBCB7, 0x64A6, 0xE1E4, 0x64A9, 0xBCBA, 0x64AB, 0xBCBE, 0x64AC, 0xBCC0, + 0x64AD, 0xBCBD, 0x64AE, 0xBCBC, 0x64B0, 0xBCB6, 0x64B1, 0xE5BB, 0x64B2, 0xBCB3, 0x64B3, 0xBCC3, 0x64BB, 0xBED8, 0x64BC, 0xBED9, + 0x64BD, 0xE9A9, 0x64BE, 0xBEE2, 0x64BF, 0xBEDF, 0x64C1, 0xBED6, 0x64C2, 0xBEDD, 0x64C3, 0xE9AB, 0x64C4, 0xBEDB, 0x64C5, 0xBED5, + 0x64C7, 0xBEDC, 0x64C9, 0xE9A8, 0x64CA, 0xC0BB, 0x64CB, 0xBED7, 0x64CD, 0xBEDE, 0x64CE, 0xC0BA, 0x64CF, 0xE9A7, 0x64D0, 0xE9A6, + 0x64D2, 0xBEE0, 0x64D4, 0xBEE1, 0x64D6, 0xE9A5, 0x64D7, 0xE9A4, 0x64D8, 0xC0BC, 0x64D9, 0xE9AE, 0x64DA, 0xBEDA, 0x64DB, 0xE9AC, + 0x64E0, 0xC0BD, 0x64E2, 0xC0C2, 0x64E3, 0xECEA, 0x64E4, 0xECEC, 0x64E6, 0xC0BF, 0x64E8, 0xECED, 0x64E9, 0xECE9, 0x64EB, 0xECEB, + 0x64EC, 0xC0C0, 0x64ED, 0xC0C3, 0x64EF, 0xECE8, 0x64F0, 0xC0BE, 0x64F1, 0xC0C1, 0x64F2, 0xC259, 0x64F3, 0xE9AD, 0x64F4, 0xC258, + 0x64F7, 0xC25E, 0x64F8, 0xEFD4, 0x64FA, 0xC25C, 0x64FB, 0xC25D, 0x64FC, 0xEFD7, 0x64FD, 0xEFD3, 0x64FE, 0xC25A, 0x64FF, 0xEFD1, + 0x6500, 0xC36B, 0x6501, 0xEFD5, 0x6503, 0xEFD6, 0x6504, 0xEFD2, 0x6506, 0xC25B, 0x6507, 0xF242, 0x6509, 0xF245, 0x650C, 0xF246, + 0x650D, 0xF244, 0x650E, 0xF247, 0x650F, 0xC36C, 0x6510, 0xF243, 0x6513, 0xF44E, 0x6514, 0xC464, 0x6515, 0xF44D, 0x6516, 0xF44C, + 0x6517, 0xF44B, 0x6518, 0xC463, 0x6519, 0xC465, 0x651B, 0xF5CD, 0x651C, 0xC4E2, 0x651D, 0xC4E1, 0x6520, 0xF6E1, 0x6521, 0xF6E0, + 0x6522, 0xF6E3, 0x6523, 0xC5CB, 0x6524, 0xC575, 0x6525, 0xF7DD, 0x6526, 0xF6E2, 0x6529, 0xF7DC, 0x652A, 0xC5CD, 0x652B, 0xC5CC, + 0x652C, 0xC5F3, 0x652D, 0xF8A9, 0x652E, 0xF8EF, 0x652F, 0xA4E4, 0x6532, 0xD972, 0x6533, 0xE9AF, 0x6536, 0xA6AC, 0x6537, 0xCAF7, + 0x6538, 0xA7F1, 0x6539, 0xA7EF, 0x653B, 0xA7F0, 0x653D, 0xCCC1, 0x653E, 0xA9F1, 0x653F, 0xAC46, 0x6541, 0xCEE7, 0x6543, 0xCEE8, + 0x6545, 0xAC47, 0x6546, 0xD1CE, 0x6548, 0xAEC4, 0x6549, 0xAEC5, 0x654A, 0xD1CD, 0x654F, 0xB1D3, 0x6551, 0xB1CF, 0x6553, 0xD5A7, + 0x6554, 0xB1D6, 0x6555, 0xB1D5, 0x6556, 0xB1CE, 0x6557, 0xB1D1, 0x6558, 0xB1D4, 0x6559, 0xB1D0, 0x655C, 0xD976, 0x655D, 0xB1CD, + 0x655E, 0xB4AF, 0x6562, 0xB4B1, 0x6563, 0xB4B2, 0x6564, 0xD975, 0x6565, 0xD978, 0x6566, 0xB4B0, 0x6567, 0xD973, 0x6568, 0xD977, + 0x656A, 0xD974, 0x656C, 0xB771, 0x656F, 0xDDBC, 0x6572, 0xBA56, 0x6573, 0xE1F4, 0x6574, 0xBEE3, 0x6575, 0xBCC4, 0x6576, 0xE5BD, + 0x6577, 0xBCC5, 0x6578, 0xBCC6, 0x6579, 0xE5BF, 0x657A, 0xE5BE, 0x657B, 0xE5C0, 0x657C, 0xE9B1, 0x657F, 0xE9B0, 0x6580, 0xECEF, + 0x6581, 0xECEE, 0x6582, 0xC0C4, 0x6583, 0xC0C5, 0x6584, 0xF248, 0x6587, 0xA4E5, 0x658C, 0xD979, 0x6590, 0xB4B4, 0x6591, 0xB4B3, + 0x6592, 0xDDBD, 0x6594, 0xEFD8, 0x6595, 0xC4E3, 0x6596, 0xF7DE, 0x6597, 0xA4E6, 0x6599, 0xAEC6, 0x659B, 0xB1D8, 0x659C, 0xB1D7, + 0x659D, 0xD97A, 0x659E, 0xD97B, 0x659F, 0xB772, 0x65A0, 0xE1F5, 0x65A1, 0xBA57, 0x65A2, 0xE9B2, 0x65A4, 0xA4E7, 0x65A5, 0xA5B8, + 0x65A7, 0xA9F2, 0x65A8, 0xCCC2, 0x65AA, 0xCEE9, 0x65AB, 0xAC48, 0x65AC, 0xB1D9, 0x65AE, 0xD97C, 0x65AF, 0xB4B5, 0x65B0, 0xB773, + 0x65B2, 0xE5C1, 0x65B3, 0xE5C2, 0x65B6, 0xECF0, 0x65B7, 0xC25F, 0x65B8, 0xF8F0, 0x65B9, 0xA4E8, 0x65BB, 0xCCC3, 0x65BC, 0xA9F3, + 0x65BD, 0xAC49, 0x65BF, 0xCEEA, 0x65C1, 0xAEC7, 0x65C2, 0xD1D2, 0x65C3, 0xD1D0, 0x65C4, 0xD1D1, 0x65C5, 0xAEC8, 0x65C6, 0xD1CF, + 0x65CB, 0xB1DB, 0x65CC, 0xB1DC, 0x65CD, 0xD5A8, 0x65CE, 0xB1DD, 0x65CF, 0xB1DA, 0x65D0, 0xD97D, 0x65D2, 0xD97E, 0x65D3, 0xDDBE, + 0x65D6, 0xBA59, 0x65D7, 0xBA58, 0x65DA, 0xECF1, 0x65DB, 0xEFD9, 0x65DD, 0xF24A, 0x65DE, 0xF249, 0x65DF, 0xF44F, 0x65E1, 0xC95E, + 0x65E2, 0xAC4A, 0x65E5, 0xA4E9, 0x65E6, 0xA5B9, 0x65E8, 0xA6AE, 0x65E9, 0xA6AD, 0x65EC, 0xA6AF, 0x65ED, 0xA6B0, 0x65EE, 0xC9EE, + 0x65EF, 0xC9ED, 0x65F0, 0xCAF8, 0x65F1, 0xA7F2, 0x65F2, 0xCAFB, 0x65F3, 0xCAFA, 0x65F4, 0xCAF9, 0x65F5, 0xCAFC, 0x65FA, 0xA9F4, + 0x65FB, 0xCCC9, 0x65FC, 0xCCC5, 0x65FD, 0xCCCE, 0x6600, 0xA9FB, 0x6602, 0xA9F9, 0x6603, 0xCCCA, 0x6604, 0xCCC6, 0x6605, 0xCCCD, + 0x6606, 0xA9F8, 0x6607, 0xAA40, 0x6608, 0xCCC8, 0x6609, 0xCCC4, 0x660A, 0xA9FE, 0x660B, 0xCCCB, 0x660C, 0xA9F7, 0x660D, 0xCCCC, + 0x660E, 0xA9FA, 0x660F, 0xA9FC, 0x6610, 0xCCD0, 0x6611, 0xCCCF, 0x6612, 0xCCC7, 0x6613, 0xA9F6, 0x6614, 0xA9F5, 0x6615, 0xA9FD, + 0x661C, 0xCEEF, 0x661D, 0xCEF5, 0x661F, 0xAC50, 0x6620, 0xAC4D, 0x6621, 0xCEEC, 0x6622, 0xCEF1, 0x6624, 0xAC53, 0x6625, 0xAC4B, + 0x6626, 0xCEF0, 0x6627, 0xAC4E, 0x6628, 0xAC51, 0x662B, 0xCEF3, 0x662D, 0xAC4C, 0x662E, 0xCEF8, 0x662F, 0xAC4F, 0x6631, 0xAC52, + 0x6632, 0xCEED, 0x6633, 0xCEF2, 0x6634, 0xCEF6, 0x6635, 0xCEEE, 0x6636, 0xCEEB, 0x6639, 0xCEF7, 0x663A, 0xCEF4, 0x6641, 0xAED0, + 0x6642, 0xAEC9, 0x6643, 0xAECC, 0x6645, 0xAECF, 0x6647, 0xD1D5, 0x6649, 0xAECA, 0x664A, 0xD1D3, 0x664C, 0xAECE, 0x664F, 0xAECB, + 0x6651, 0xD1D6, 0x6652, 0xAECD, 0x6659, 0xD5AC, 0x665A, 0xB1DF, 0x665B, 0xD5AB, 0x665C, 0xD5AD, 0x665D, 0xB1DE, 0x665E, 0xB1E3, + 0x665F, 0xD1D4, 0x6661, 0xD5AA, 0x6662, 0xD5AE, 0x6664, 0xB1E0, 0x6665, 0xD5A9, 0x6666, 0xB1E2, 0x6668, 0xB1E1, 0x666A, 0xD9A7, + 0x666C, 0xD9A2, 0x666E, 0xB4B6, 0x666F, 0xB4BA, 0x6670, 0xB4B7, 0x6671, 0xD9A5, 0x6672, 0xD9A8, 0x6674, 0xB4B8, 0x6676, 0xB4B9, + 0x6677, 0xB4BE, 0x6678, 0xDDC7, 0x6679, 0xD9A6, 0x667A, 0xB4BC, 0x667B, 0xD9A3, 0x667C, 0xD9A1, 0x667E, 0xB4BD, 0x6680, 0xD9A4, + 0x6684, 0xB779, 0x6686, 0xDDBF, 0x6687, 0xB776, 0x6688, 0xB777, 0x6689, 0xB775, 0x668A, 0xDDC4, 0x668B, 0xDDC3, 0x668C, 0xDDC0, + 0x668D, 0xB77B, 0x6690, 0xDDC2, 0x6691, 0xB4BB, 0x6694, 0xDDC6, 0x6695, 0xDDC1, 0x6696, 0xB778, 0x6697, 0xB774, 0x6698, 0xB77A, + 0x6699, 0xDDC5, 0x669D, 0xBA5C, 0x669F, 0xE1F8, 0x66A0, 0xE1F7, 0x66A1, 0xE1F6, 0x66A2, 0xBA5A, 0x66A8, 0xBA5B, 0x66A9, 0xE5C5, + 0x66AA, 0xE5C8, 0x66AB, 0xBCC8, 0x66AE, 0xBCC7, 0x66AF, 0xE5C9, 0x66B0, 0xE5C4, 0x66B1, 0xBCCA, 0x66B2, 0xE5C6, 0x66B4, 0xBCC9, + 0x66B5, 0xE5C3, 0x66B7, 0xE5C7, 0x66B8, 0xBEE9, 0x66B9, 0xBEE6, 0x66BA, 0xE9BB, 0x66BB, 0xE9BA, 0x66BD, 0xE9B9, 0x66BE, 0xE9B4, + 0x66C0, 0xE9B5, 0x66C4, 0xBEE7, 0x66C6, 0xBEE4, 0x66C7, 0xBEE8, 0x66C8, 0xE9B3, 0x66C9, 0xBEE5, 0x66CA, 0xE9B6, 0x66CB, 0xE9B7, + 0x66CC, 0xE9BC, 0x66CF, 0xE9B8, 0x66D2, 0xECF2, 0x66D6, 0xC0C7, 0x66D8, 0xEFDC, 0x66D9, 0xC0C6, 0x66DA, 0xEFDA, 0x66DB, 0xEFDB, + 0x66DC, 0xC260, 0x66DD, 0xC36E, 0x66DE, 0xF24B, 0x66E0, 0xC36D, 0x66E3, 0xF451, 0x66E4, 0xF452, 0x66E6, 0xC466, 0x66E8, 0xF450, + 0x66E9, 0xC4E4, 0x66EB, 0xF7DF, 0x66EC, 0xC5CE, 0x66ED, 0xF8AA, 0x66EE, 0xF8AB, 0x66F0, 0xA4EA, 0x66F2, 0xA6B1, 0x66F3, 0xA6B2, + 0x66F4, 0xA7F3, 0x66F6, 0xCCD1, 0x66F7, 0xAC54, 0x66F8, 0xAED1, 0x66F9, 0xB1E4, 0x66FC, 0xB0D2, 0x66FE, 0xB4BF, 0x66FF, 0xB4C0, + 0x6700, 0xB3CC, 0x6701, 0xD9A9, 0x6703, 0xB77C, 0x6704, 0xE1FA, 0x6705, 0xE1F9, 0x6708, 0xA4EB, 0x6709, 0xA6B3, 0x670A, 0xCCD2, + 0x670B, 0xAA42, 0x670D, 0xAA41, 0x670F, 0xCEF9, 0x6710, 0xCEFA, 0x6712, 0xD1D7, 0x6713, 0xD1D8, 0x6714, 0xAED2, 0x6715, 0xAED3, + 0x6717, 0xAED4, 0x6718, 0xD5AF, 0x671B, 0xB1E6, 0x671D, 0xB4C2, 0x671F, 0xB4C1, 0x6720, 0xDDC8, 0x6721, 0xDF7A, 0x6722, 0xE1FB, + 0x6723, 0xE9BD, 0x6726, 0xC261, 0x6727, 0xC467, 0x6728, 0xA4EC, 0x672A, 0xA5BC, 0x672B, 0xA5BD, 0x672C, 0xA5BB, 0x672D, 0xA5BE, + 0x672E, 0xA5BA, 0x6731, 0xA6B6, 0x6733, 0xC9F6, 0x6734, 0xA6B5, 0x6735, 0xA6B7, 0x6738, 0xC9F1, 0x6739, 0xC9F0, 0x673A, 0xC9F3, + 0x673B, 0xC9F2, 0x673C, 0xC9F5, 0x673D, 0xA6B4, 0x673E, 0xC9EF, 0x673F, 0xC9F4, 0x6745, 0xCAFD, 0x6746, 0xA7FD, 0x6747, 0xCAFE, + 0x6748, 0xCB43, 0x6749, 0xA7FC, 0x674B, 0xCB47, 0x674C, 0xCB42, 0x674D, 0xCB45, 0x674E, 0xA7F5, 0x674F, 0xA7F6, 0x6750, 0xA7F7, + 0x6751, 0xA7F8, 0x6753, 0xA840, 0x6755, 0xCB41, 0x6756, 0xA7FA, 0x6757, 0xA841, 0x6759, 0xCB40, 0x675A, 0xCB46, 0x675C, 0xA7F9, + 0x675D, 0xCB44, 0x675E, 0xA7FB, 0x675F, 0xA7F4, 0x6760, 0xA7FE, 0x676A, 0xAA57, 0x676C, 0xCCD4, 0x676D, 0xAA43, 0x676F, 0xAA4D, + 0x6770, 0xAA4E, 0x6771, 0xAA46, 0x6772, 0xAA58, 0x6773, 0xAA48, 0x6774, 0xCCDC, 0x6775, 0xAA53, 0x6776, 0xCCD7, 0x6777, 0xAA49, + 0x6778, 0xCCE6, 0x6779, 0xCCE7, 0x677A, 0xCCDF, 0x677B, 0xCCD8, 0x677C, 0xAA56, 0x677D, 0xCCE4, 0x677E, 0xAA51, 0x677F, 0xAA4F, + 0x6781, 0xCCE5, 0x6783, 0xCCE3, 0x6784, 0xCCDB, 0x6785, 0xCCD3, 0x6786, 0xCCDA, 0x6787, 0xAA4A, 0x6789, 0xAA50, 0x678B, 0xAA44, + 0x678C, 0xCCDE, 0x678D, 0xCCDD, 0x678E, 0xCCD5, 0x6790, 0xAA52, 0x6791, 0xCCE1, 0x6792, 0xCCD6, 0x6793, 0xAA55, 0x6794, 0xCCE8, + 0x6795, 0xAA45, 0x6797, 0xAA4C, 0x6798, 0xCCD9, 0x6799, 0xCCE2, 0x679A, 0xAA54, 0x679C, 0xAA47, 0x679D, 0xAA4B, 0x679F, 0xCCE0, + 0x67AE, 0xCF5B, 0x67AF, 0xAC5C, 0x67B0, 0xAC69, 0x67B2, 0xCF56, 0x67B3, 0xCF4C, 0x67B4, 0xAC62, 0x67B5, 0xCF4A, 0x67B6, 0xAC5B, + 0x67B7, 0xCF45, 0x67B8, 0xAC65, 0x67B9, 0xCF52, 0x67BA, 0xCEFE, 0x67BB, 0xCF41, 0x67C0, 0xCF44, 0x67C1, 0xCEFB, 0x67C2, 0xCF51, + 0x67C3, 0xCF61, 0x67C4, 0xAC60, 0x67C5, 0xCF46, 0x67C6, 0xCF58, 0x67C8, 0xCEFD, 0x67C9, 0xCF5F, 0x67CA, 0xCF60, 0x67CB, 0xCF63, + 0x67CC, 0xCF5A, 0x67CD, 0xCF4B, 0x67CE, 0xCF53, 0x67CF, 0xAC66, 0x67D0, 0xAC59, 0x67D1, 0xAC61, 0x67D2, 0xAC6D, 0x67D3, 0xAC56, + 0x67D4, 0xAC58, 0x67D8, 0xCF43, 0x67D9, 0xAC6A, 0x67DA, 0xAC63, 0x67DB, 0xCF5D, 0x67DC, 0xCF40, 0x67DD, 0xAC6C, 0x67DE, 0xAC67, + 0x67DF, 0xCF49, 0x67E2, 0xAC6B, 0x67E3, 0xCF50, 0x67E4, 0xCF48, 0x67E5, 0xAC64, 0x67E6, 0xCF5C, 0x67E7, 0xCF54, 0x67E9, 0xAC5E, + 0x67EA, 0xCF62, 0x67EB, 0xCF47, 0x67EC, 0xAC5A, 0x67ED, 0xCF59, 0x67EE, 0xCF4F, 0x67EF, 0xAC5F, 0x67F0, 0xCF55, 0x67F1, 0xAC57, + 0x67F2, 0xCEFC, 0x67F3, 0xAC68, 0x67F4, 0xAEE3, 0x67F5, 0xAC5D, 0x67F6, 0xCF4E, 0x67F7, 0xCF4D, 0x67F8, 0xCF42, 0x67FA, 0xCF5E, + 0x67FC, 0xCF57, 0x67FF, 0xAC55, 0x6812, 0xD1EC, 0x6813, 0xAEEA, 0x6814, 0xD1ED, 0x6816, 0xD1E1, 0x6817, 0xAEDF, 0x6818, 0xAEEB, + 0x681A, 0xD1DA, 0x681C, 0xD1E3, 0x681D, 0xD1EB, 0x681F, 0xD1D9, 0x6820, 0xD1F4, 0x6821, 0xAED5, 0x6825, 0xD1F3, 0x6826, 0xD1EE, + 0x6828, 0xD1EF, 0x6829, 0xAEDD, 0x682A, 0xAEE8, 0x682B, 0xD1E5, 0x682D, 0xD1E6, 0x682E, 0xD1F0, 0x682F, 0xD1E7, 0x6831, 0xD1E2, + 0x6832, 0xD1DC, 0x6833, 0xD1DD, 0x6834, 0xD1EA, 0x6835, 0xD1E4, 0x6838, 0xAED6, 0x6839, 0xAEDA, 0x683A, 0xD1F2, 0x683B, 0xD1DE, + 0x683C, 0xAEE6, 0x683D, 0xAEE2, 0x6840, 0xAEE5, 0x6841, 0xAEEC, 0x6842, 0xAEDB, 0x6843, 0xAEE7, 0x6844, 0xD1E9, 0x6845, 0xAEE9, + 0x6846, 0xAED8, 0x6848, 0xAED7, 0x6849, 0xD1DB, 0x684B, 0xD1DF, 0x684C, 0xAEE0, 0x684D, 0xD1F1, 0x684E, 0xD1E8, 0x684F, 0xD1E0, + 0x6850, 0xAEE4, 0x6851, 0xAEE1, 0x6853, 0xAED9, 0x6854, 0xAEDC, 0x686B, 0xD5C4, 0x686D, 0xD5B4, 0x686E, 0xD5B5, 0x686F, 0xD5B9, + 0x6871, 0xD5C8, 0x6872, 0xD5C5, 0x6874, 0xD5BE, 0x6875, 0xD5BD, 0x6876, 0xB1ED, 0x6877, 0xD5C1, 0x6878, 0xD5D0, 0x6879, 0xD5B0, + 0x687B, 0xD5D1, 0x687C, 0xD5C3, 0x687D, 0xD5D5, 0x687E, 0xD5C9, 0x687F, 0xB1EC, 0x6880, 0xD5C7, 0x6881, 0xB1E7, 0x6882, 0xB1FC, + 0x6883, 0xB1F2, 0x6885, 0xB1F6, 0x6886, 0xB1F5, 0x6887, 0xD5B1, 0x6889, 0xD5CE, 0x688A, 0xD5D4, 0x688B, 0xD5CC, 0x688C, 0xD5D3, + 0x688F, 0xD5C0, 0x6890, 0xD5B2, 0x6891, 0xD5D2, 0x6892, 0xD5C2, 0x6893, 0xB1EA, 0x6894, 0xB1F7, 0x6896, 0xD5CB, 0x6897, 0xB1F0, + 0x689B, 0xD5CA, 0x689C, 0xD5B3, 0x689D, 0xB1F8, 0x689F, 0xB1FA, 0x68A0, 0xD5CD, 0x68A1, 0xB1FB, 0x68A2, 0xB1E9, 0x68A3, 0xD5BA, + 0x68A4, 0xD5CF, 0x68A7, 0xB1EF, 0x68A8, 0xB1F9, 0x68A9, 0xD5BC, 0x68AA, 0xD5C6, 0x68AB, 0xD5B7, 0x68AC, 0xD5BB, 0x68AD, 0xB1F4, + 0x68AE, 0xD5B6, 0x68AF, 0xB1E8, 0x68B0, 0xB1F1, 0x68B1, 0xB1EE, 0x68B2, 0xD5BF, 0x68B3, 0xAEDE, 0x68B4, 0xD9C0, 0x68B5, 0xB1EB, + 0x68C4, 0xB1F3, 0x68C6, 0xD9C3, 0x68C7, 0xD9D9, 0x68C8, 0xD9CE, 0x68C9, 0xB4D6, 0x68CB, 0xB4D1, 0x68CC, 0xD9BD, 0x68CD, 0xB4D2, + 0x68CE, 0xD9CD, 0x68D0, 0xD9C6, 0x68D1, 0xD9D3, 0x68D2, 0xB4CE, 0x68D3, 0xD9AB, 0x68D4, 0xD9D5, 0x68D5, 0xB4C4, 0x68D6, 0xD9B3, + 0x68D7, 0xB4C7, 0x68D8, 0xB4C6, 0x68DA, 0xB4D7, 0x68DC, 0xD9AD, 0x68DD, 0xD9CF, 0x68DE, 0xD9D0, 0x68DF, 0xB4C9, 0x68E0, 0xB4C5, + 0x68E1, 0xD9BB, 0x68E3, 0xB4D0, 0x68E4, 0xD9B6, 0x68E6, 0xD9D1, 0x68E7, 0xB4CC, 0x68E8, 0xD9C9, 0x68E9, 0xD9D6, 0x68EA, 0xD9B0, + 0x68EB, 0xD9B5, 0x68EC, 0xD9AF, 0x68EE, 0xB4CB, 0x68EF, 0xD9C2, 0x68F0, 0xDDDE, 0x68F1, 0xD9B1, 0x68F2, 0xB4CF, 0x68F3, 0xD9BA, + 0x68F4, 0xD9D2, 0x68F5, 0xB4CA, 0x68F6, 0xD9B7, 0x68F7, 0xD9B4, 0x68F8, 0xD9C5, 0x68F9, 0xB4CD, 0x68FA, 0xB4C3, 0x68FB, 0xB4D9, + 0x68FC, 0xD9C8, 0x68FD, 0xD9C7, 0x6904, 0xD9AC, 0x6905, 0xB4C8, 0x6906, 0xD9D4, 0x6907, 0xD9BC, 0x6908, 0xD9BE, 0x690A, 0xD9CB, + 0x690B, 0xD9CA, 0x690C, 0xD9AA, 0x690D, 0xB4D3, 0x690E, 0xB4D5, 0x690F, 0xD9B2, 0x6910, 0xD9B9, 0x6911, 0xD9C1, 0x6912, 0xB4D4, + 0x6913, 0xD9B8, 0x6914, 0xD9C4, 0x6915, 0xD9D7, 0x6917, 0xD9CC, 0x6925, 0xD9D8, 0x692A, 0xD9AE, 0x692F, 0xDDF2, 0x6930, 0xB7A6, + 0x6932, 0xDDF0, 0x6933, 0xDDDB, 0x6934, 0xDDE0, 0x6935, 0xDDD9, 0x6937, 0xDDEC, 0x6938, 0xDDCB, 0x6939, 0xDDD2, 0x693B, 0xDDEA, + 0x693C, 0xDDF4, 0x693D, 0xDDDC, 0x693F, 0xDDCF, 0x6940, 0xDDE2, 0x6941, 0xDDE7, 0x6942, 0xDDD3, 0x6944, 0xDDE4, 0x6945, 0xDDD0, + 0x6948, 0xDDD7, 0x6949, 0xDDD8, 0x694A, 0xB7A8, 0x694B, 0xDDEB, 0x694C, 0xDDE9, 0x694E, 0xDDCC, 0x694F, 0xDDEE, 0x6951, 0xDDEF, + 0x6952, 0xDDF1, 0x6953, 0xB7AC, 0x6954, 0xB7A4, 0x6956, 0xD5B8, 0x6957, 0xDDD4, 0x6958, 0xDDE6, 0x6959, 0xDDD5, 0x695A, 0xB7A1, + 0x695B, 0xB7B1, 0x695C, 0xDDED, 0x695D, 0xB7AF, 0x695E, 0xB7AB, 0x695F, 0xDDCA, 0x6960, 0xB7A3, 0x6962, 0xDDCD, 0x6963, 0xB7B0, + 0x6965, 0xDDDD, 0x6966, 0xDDC9, 0x6968, 0xB7A9, 0x6969, 0xDDE1, 0x696A, 0xDDD1, 0x696B, 0xB7AA, 0x696C, 0xDDDA, 0x696D, 0xB77E, + 0x696E, 0xB4D8, 0x696F, 0xDDE3, 0x6970, 0xD9BF, 0x6971, 0xDDCE, 0x6974, 0xDDE8, 0x6975, 0xB7A5, 0x6976, 0xDDE5, 0x6977, 0xB7A2, + 0x6978, 0xDDDF, 0x6979, 0xB7AD, 0x697A, 0xDDD6, 0x697B, 0xDDF3, 0x6982, 0xB7A7, 0x6983, 0xDEC6, 0x6986, 0xB7AE, 0x698D, 0xE24A, + 0x698E, 0xE248, 0x6990, 0xE25E, 0x6991, 0xE246, 0x6993, 0xE258, 0x6994, 0xB77D, 0x6995, 0xBA5F, 0x6996, 0xE242, 0x6997, 0xE25D, + 0x6999, 0xE247, 0x699A, 0xE255, 0x699B, 0xBA64, 0x699C, 0xBA5D, 0x699E, 0xE25B, 0x69A0, 0xE240, 0x69A1, 0xE25A, 0x69A3, 0xBA6F, + 0x69A4, 0xE251, 0x69A5, 0xE261, 0x69A6, 0xBA6D, 0x69A7, 0xE249, 0x69A8, 0xBA5E, 0x69A9, 0xE24B, 0x69AA, 0xE259, 0x69AB, 0xBA67, + 0x69AC, 0xE244, 0x69AD, 0xBA6B, 0x69AE, 0xBA61, 0x69AF, 0xE24D, 0x69B0, 0xE243, 0x69B1, 0xE1FC, 0x69B3, 0xE257, 0x69B4, 0xBA68, + 0x69B5, 0xE260, 0x69B6, 0xE1FD, 0x69B7, 0xBA65, 0x69B9, 0xE253, 0x69BB, 0xBA66, 0x69BC, 0xE245, 0x69BD, 0xE250, 0x69BE, 0xE24C, + 0x69BF, 0xE24E, 0x69C1, 0xBA60, 0x69C2, 0xE25F, 0x69C3, 0xBA6E, 0x69C4, 0xE24F, 0x69C6, 0xE262, 0x69C9, 0xE1FE, 0x69CA, 0xE254, + 0x69CB, 0xBA63, 0x69CC, 0xBA6C, 0x69CD, 0xBA6A, 0x69CE, 0xE241, 0x69CF, 0xE256, 0x69D0, 0xBA69, 0x69D3, 0xBA62, 0x69D4, 0xE252, + 0x69D9, 0xE25C, 0x69E2, 0xE5D5, 0x69E4, 0xE5D1, 0x69E5, 0xE5CD, 0x69E6, 0xE5E1, 0x69E7, 0xE5DE, 0x69E8, 0xBCCD, 0x69EB, 0xE5E5, + 0x69EC, 0xE5D4, 0x69ED, 0xBCD8, 0x69EE, 0xE5DB, 0x69F1, 0xE5D0, 0x69F2, 0xE5DA, 0x69F3, 0xBCD5, 0x69F4, 0xE5EE, 0x69F6, 0xE5EB, + 0x69F7, 0xE5DD, 0x69F8, 0xE5CE, 0x69FB, 0xE5E2, 0x69FC, 0xE5E4, 0x69FD, 0xBCD1, 0x69FE, 0xE5D8, 0x69FF, 0xE5D3, 0x6A00, 0xE5CA, + 0x6A01, 0xBCCE, 0x6A02, 0xBCD6, 0x6A04, 0xE5E7, 0x6A05, 0xBCD7, 0x6A06, 0xE5CB, 0x6A07, 0xE5ED, 0x6A08, 0xE5E0, 0x6A09, 0xE5E6, + 0x6A0A, 0xBCD4, 0x6A0D, 0xE5E3, 0x6A0F, 0xE5EA, 0x6A11, 0xBCD9, 0x6A13, 0xBCD3, 0x6A14, 0xE5DC, 0x6A15, 0xE5CF, 0x6A16, 0xE5EF, + 0x6A17, 0xE5CC, 0x6A18, 0xE5E8, 0x6A19, 0xBCD0, 0x6A1B, 0xE5D6, 0x6A1D, 0xE5D7, 0x6A1E, 0xBCCF, 0x6A1F, 0xBCCC, 0x6A20, 0xE5D2, + 0x6A21, 0xBCD2, 0x6A23, 0xBCCB, 0x6A25, 0xE5E9, 0x6A26, 0xE5EC, 0x6A27, 0xE5D9, 0x6A28, 0xE9CA, 0x6A32, 0xE9C2, 0x6A34, 0xE9BE, + 0x6A35, 0xBEF6, 0x6A38, 0xBEEB, 0x6A39, 0xBEF0, 0x6A3A, 0xBEEC, 0x6A3B, 0xE9CC, 0x6A3C, 0xE9D7, 0x6A3D, 0xBEEA, 0x6A3E, 0xE9C4, + 0x6A3F, 0xE9CD, 0x6A40, 0xE5DF, 0x6A41, 0xE9CE, 0x6A44, 0xBEF1, 0x6A46, 0xE9DD, 0x6A47, 0xBEF5, 0x6A48, 0xBEF8, 0x6A49, 0xE9C0, + 0x6A4B, 0xBEF4, 0x6A4D, 0xE9DB, 0x6A4E, 0xE9DC, 0x6A4F, 0xE9D2, 0x6A50, 0xE9D1, 0x6A51, 0xE9C9, 0x6A54, 0xE9D3, 0x6A55, 0xE9DA, + 0x6A56, 0xE9D9, 0x6A58, 0xBEEF, 0x6A59, 0xBEED, 0x6A5A, 0xE9CB, 0x6A5B, 0xE9C8, 0x6A5D, 0xE9C5, 0x6A5E, 0xE9D8, 0x6A5F, 0xBEF7, + 0x6A60, 0xE9D6, 0x6A61, 0xBEF3, 0x6A62, 0xBEF2, 0x6A64, 0xE9D0, 0x6A66, 0xE9BF, 0x6A67, 0xE9C1, 0x6A68, 0xE9C3, 0x6A69, 0xE9D5, + 0x6A6A, 0xE9CF, 0x6A6B, 0xBEEE, 0x6A6D, 0xE9C6, 0x6A6F, 0xE9D4, 0x6A76, 0xE9C7, 0x6A7E, 0xC0CF, 0x6A7F, 0xED45, 0x6A80, 0xC0C8, + 0x6A81, 0xECF5, 0x6A83, 0xED41, 0x6A84, 0xC0CA, 0x6A85, 0xED48, 0x6A87, 0xECFC, 0x6A89, 0xECF7, 0x6A8C, 0xED49, 0x6A8D, 0xECF3, + 0x6A8E, 0xECFE, 0x6A90, 0xC0D1, 0x6A91, 0xED44, 0x6A92, 0xED4A, 0x6A93, 0xECFD, 0x6A94, 0xC0C9, 0x6A95, 0xED40, 0x6A96, 0xECF4, + 0x6A97, 0xC0D0, 0x6A9A, 0xED47, 0x6A9B, 0xECF9, 0x6A9C, 0xC0CC, 0x6A9E, 0xECFB, 0x6A9F, 0xECF8, 0x6AA0, 0xC0D2, 0x6AA1, 0xECFA, + 0x6AA2, 0xC0CB, 0x6AA3, 0xC0CE, 0x6AA4, 0xED43, 0x6AA5, 0xECF6, 0x6AA6, 0xED46, 0x6AA8, 0xED42, 0x6AAC, 0xC263, 0x6AAD, 0xEFE7, + 0x6AAE, 0xC268, 0x6AAF, 0xC269, 0x6AB3, 0xC262, 0x6AB4, 0xEFE6, 0x6AB6, 0xEFE3, 0x6AB7, 0xEFE4, 0x6AB8, 0xC266, 0x6AB9, 0xEFDE, + 0x6ABA, 0xEFE2, 0x6ABB, 0xC265, 0x6ABD, 0xEFDF, 0x6AC2, 0xC267, 0x6AC3, 0xC264, 0x6AC5, 0xEFDD, 0x6AC6, 0xEFE1, 0x6AC7, 0xEFE5, + 0x6ACB, 0xF251, 0x6ACC, 0xF24E, 0x6ACD, 0xF257, 0x6ACF, 0xF256, 0x6AD0, 0xF254, 0x6AD1, 0xF24F, 0x6AD3, 0xC372, 0x6AD9, 0xF250, + 0x6ADA, 0xC371, 0x6ADB, 0xC0CD, 0x6ADC, 0xF253, 0x6ADD, 0xC370, 0x6ADE, 0xF258, 0x6ADF, 0xF252, 0x6AE0, 0xF24D, 0x6AE1, 0xEFE0, + 0x6AE5, 0xC36F, 0x6AE7, 0xF24C, 0x6AE8, 0xF456, 0x6AEA, 0xF455, 0x6AEB, 0xF255, 0x6AEC, 0xC468, 0x6AEE, 0xF459, 0x6AEF, 0xF45A, + 0x6AF0, 0xF454, 0x6AF1, 0xF458, 0x6AF3, 0xF453, 0x6AF8, 0xF5D1, 0x6AF9, 0xF457, 0x6AFA, 0xC4E7, 0x6AFB, 0xC4E5, 0x6AFC, 0xF5CF, + 0x6B00, 0xF5D2, 0x6B02, 0xF5CE, 0x6B03, 0xF5D0, 0x6B04, 0xC4E6, 0x6B08, 0xF6E5, 0x6B09, 0xF6E6, 0x6B0A, 0xC576, 0x6B0B, 0xF6E4, + 0x6B0F, 0xF7E2, 0x6B10, 0xC5CF, 0x6B11, 0xF7E0, 0x6B12, 0xF7E1, 0x6B13, 0xF8AC, 0x6B16, 0xC656, 0x6B17, 0xF8F3, 0x6B18, 0xF8F1, + 0x6B19, 0xF8F2, 0x6B1A, 0xF8F4, 0x6B1E, 0xF9BB, 0x6B20, 0xA4ED, 0x6B21, 0xA6B8, 0x6B23, 0xAA59, 0x6B25, 0xCCE9, 0x6B28, 0xCF64, + 0x6B2C, 0xD1F5, 0x6B2D, 0xD1F7, 0x6B2F, 0xD1F6, 0x6B31, 0xD1F8, 0x6B32, 0xB1FD, 0x6B33, 0xD5D7, 0x6B34, 0xD1F9, 0x6B36, 0xD5D6, + 0x6B37, 0xD5D8, 0x6B38, 0xD5D9, 0x6B39, 0xD9DA, 0x6B3A, 0xB4DB, 0x6B3B, 0xD9DB, 0x6B3C, 0xD9DD, 0x6B3D, 0xB4DC, 0x6B3E, 0xB4DA, + 0x6B3F, 0xD9DC, 0x6B41, 0xDDFA, 0x6B42, 0xDDF8, 0x6B43, 0xDDF7, 0x6B45, 0xDDF6, 0x6B46, 0xDDF5, 0x6B47, 0xB7B2, 0x6B48, 0xDDF9, + 0x6B49, 0xBA70, 0x6B4A, 0xE263, 0x6B4B, 0xE265, 0x6B4C, 0xBA71, 0x6B4D, 0xE264, 0x6B4E, 0xBCDB, 0x6B50, 0xBCDA, 0x6B51, 0xE5F0, + 0x6B54, 0xE9DF, 0x6B55, 0xE9DE, 0x6B56, 0xE9E0, 0x6B59, 0xBEF9, 0x6B5B, 0xED4B, 0x6B5C, 0xC0D3, 0x6B5E, 0xEFE8, 0x6B5F, 0xC26A, + 0x6B60, 0xF259, 0x6B61, 0xC577, 0x6B62, 0xA4EE, 0x6B63, 0xA5BF, 0x6B64, 0xA6B9, 0x6B65, 0xA842, 0x6B66, 0xAA5A, 0x6B67, 0xAA5B, + 0x6B6A, 0xAC6E, 0x6B6D, 0xD1FA, 0x6B72, 0xB7B3, 0x6B76, 0xE6D1, 0x6B77, 0xBEFA, 0x6B78, 0xC26B, 0x6B79, 0xA4EF, 0x6B7B, 0xA6BA, + 0x6B7E, 0xCCEB, 0x6B7F, 0xAA5C, 0x6B80, 0xCCEA, 0x6B82, 0xCF65, 0x6B83, 0xAC6F, 0x6B84, 0xCF66, 0x6B86, 0xAC70, 0x6B88, 0xD1FC, + 0x6B89, 0xAEEE, 0x6B8A, 0xAEED, 0x6B8C, 0xD5DE, 0x6B8D, 0xD5DC, 0x6B8E, 0xD5DD, 0x6B8F, 0xD5DB, 0x6B91, 0xD5DA, 0x6B94, 0xD9DE, + 0x6B95, 0xD9E1, 0x6B96, 0xB4DE, 0x6B97, 0xD9DF, 0x6B98, 0xB4DD, 0x6B99, 0xD9E0, 0x6B9B, 0xDDFB, 0x6B9E, 0xE266, 0x6B9F, 0xE267, + 0x6BA0, 0xE268, 0x6BA2, 0xE5F3, 0x6BA3, 0xE5F2, 0x6BA4, 0xBCDC, 0x6BA5, 0xE5F1, 0x6BA6, 0xE5F4, 0x6BA7, 0xE9E1, 0x6BAA, 0xE9E2, + 0x6BAB, 0xE9E3, 0x6BAD, 0xED4C, 0x6BAE, 0xC0D4, 0x6BAF, 0xC26C, 0x6BB0, 0xF25A, 0x6BB2, 0xC4E8, 0x6BB3, 0xC95F, 0x6BB5, 0xAC71, + 0x6BB6, 0xCF67, 0x6BB7, 0xAEEF, 0x6BBA, 0xB1FE, 0x6BBC, 0xB4DF, 0x6BBD, 0xD9E2, 0x6BBF, 0xB7B5, 0x6BC0, 0xB7B4, 0x6BC3, 0xE269, + 0x6BC4, 0xE26A, 0x6BC5, 0xBCDD, 0x6BC6, 0xBCDE, 0x6BC7, 0xE9E5, 0x6BC8, 0xE9E4, 0x6BC9, 0xEFE9, 0x6BCA, 0xF7E3, 0x6BCB, 0xA4F0, + 0x6BCC, 0xC960, 0x6BCD, 0xA5C0, 0x6BCF, 0xA843, 0x6BD0, 0xCB48, 0x6BD2, 0xAC72, 0x6BD3, 0xB7B6, 0x6BD4, 0xA4F1, 0x6BD6, 0xCF68, + 0x6BD7, 0xAC73, 0x6BD8, 0xCF69, 0x6BDA, 0xC0D5, 0x6BDB, 0xA4F2, 0x6BDE, 0xCCEC, 0x6BE0, 0xCF6A, 0x6BE2, 0xD242, 0x6BE3, 0xD241, + 0x6BE4, 0xD1FE, 0x6BE6, 0xD1FD, 0x6BE7, 0xD243, 0x6BE8, 0xD240, 0x6BEB, 0xB240, 0x6BEC, 0xB241, 0x6BEF, 0xB4E0, 0x6BF0, 0xD9E3, + 0x6BF2, 0xD9E4, 0x6BF3, 0xD9E5, 0x6BF7, 0xDE41, 0x6BF8, 0xDE42, 0x6BF9, 0xDE40, 0x6BFB, 0xDDFD, 0x6BFC, 0xDDFE, 0x6BFD, 0xB7B7, + 0x6BFE, 0xE26B, 0x6BFF, 0xE5F7, 0x6C00, 0xE5F6, 0x6C01, 0xE5F5, 0x6C02, 0xE5F8, 0x6C03, 0xE9E7, 0x6C04, 0xE9E6, 0x6C05, 0xBEFB, + 0x6C06, 0xE9E8, 0x6C08, 0xC0D6, 0x6C09, 0xED4D, 0x6C0B, 0xEFEA, 0x6C0C, 0xF25B, 0x6C0D, 0xF6E7, 0x6C0F, 0xA4F3, 0x6C10, 0xA5C2, + 0x6C11, 0xA5C1, 0x6C13, 0xAA5D, 0x6C14, 0xC961, 0x6C15, 0xC97E, 0x6C16, 0xA6BB, 0x6C18, 0xC9F7, 0x6C19, 0xCB49, 0x6C1A, 0xCB4A, + 0x6C1B, 0xAA5E, 0x6C1D, 0xCCED, 0x6C1F, 0xAC74, 0x6C20, 0xCF6B, 0x6C21, 0xCF6C, 0x6C23, 0xAEF0, 0x6C24, 0xAEF4, 0x6C25, 0xD244, + 0x6C26, 0xAEF3, 0x6C27, 0xAEF1, 0x6C28, 0xAEF2, 0x6C2A, 0xD5DF, 0x6C2B, 0xB242, 0x6C2C, 0xB4E3, 0x6C2E, 0xB4E1, 0x6C2F, 0xB4E2, + 0x6C30, 0xD9E6, 0x6C33, 0xBA72, 0x6C34, 0xA4F4, 0x6C36, 0xC9A1, 0x6C38, 0xA5C3, 0x6C3B, 0xC9A4, 0x6C3E, 0xA5C6, 0x6C3F, 0xC9A3, + 0x6C40, 0xA5C5, 0x6C41, 0xA5C4, 0x6C42, 0xA844, 0x6C43, 0xC9A2, 0x6C46, 0xC9F8, 0x6C4A, 0xC9FC, 0x6C4B, 0xC9FE, 0x6C4C, 0xCA40, + 0x6C4D, 0xA6C5, 0x6C4E, 0xA6C6, 0x6C4F, 0xC9FB, 0x6C50, 0xA6C1, 0x6C52, 0xC9F9, 0x6C54, 0xC9FD, 0x6C55, 0xA6C2, 0x6C57, 0xA6BD, + 0x6C59, 0xA6BE, 0x6C5B, 0xA6C4, 0x6C5C, 0xC9FA, 0x6C5D, 0xA6BC, 0x6C5E, 0xA845, 0x6C5F, 0xA6BF, 0x6C60, 0xA6C0, 0x6C61, 0xA6C3, + 0x6C65, 0xCB5B, 0x6C66, 0xCB59, 0x6C67, 0xCB4C, 0x6C68, 0xA851, 0x6C69, 0xCB53, 0x6C6A, 0xA84C, 0x6C6B, 0xCB4D, 0x6C6D, 0xCB55, + 0x6C6F, 0xCB52, 0x6C70, 0xA84F, 0x6C71, 0xCB51, 0x6C72, 0xA856, 0x6C73, 0xCB5A, 0x6C74, 0xA858, 0x6C76, 0xA85A, 0x6C78, 0xCB4B, + 0x6C7A, 0xA84D, 0x6C7B, 0xCB5C, 0x6C7D, 0xA854, 0x6C7E, 0xA857, 0x6C80, 0xCD45, 0x6C81, 0xA847, 0x6C82, 0xA85E, 0x6C83, 0xA855, + 0x6C84, 0xCB4E, 0x6C85, 0xA84A, 0x6C86, 0xA859, 0x6C87, 0xCB56, 0x6C88, 0xA848, 0x6C89, 0xA849, 0x6C8A, 0xCD43, 0x6C8B, 0xCB4F, + 0x6C8C, 0xA850, 0x6C8D, 0xA85B, 0x6C8E, 0xCB5D, 0x6C8F, 0xCB50, 0x6C90, 0xA84E, 0x6C92, 0xA853, 0x6C93, 0xCCEE, 0x6C94, 0xA85C, + 0x6C95, 0xCB57, 0x6C96, 0xA852, 0x6C98, 0xA85D, 0x6C99, 0xA846, 0x6C9A, 0xCB54, 0x6C9B, 0xA84B, 0x6C9C, 0xCB58, 0x6C9D, 0xCD44, + 0x6CAB, 0xAA6A, 0x6CAC, 0xAA7A, 0x6CAD, 0xCCF5, 0x6CAE, 0xAA71, 0x6CB0, 0xCD4B, 0x6CB1, 0xAA62, 0x6CB3, 0xAA65, 0x6CB4, 0xCD42, + 0x6CB6, 0xCCF3, 0x6CB7, 0xCCF7, 0x6CB8, 0xAA6D, 0x6CB9, 0xAA6F, 0x6CBA, 0xCCFA, 0x6CBB, 0xAA76, 0x6CBC, 0xAA68, 0x6CBD, 0xAA66, + 0x6CBE, 0xAA67, 0x6CBF, 0xAA75, 0x6CC0, 0xCD47, 0x6CC1, 0xAA70, 0x6CC2, 0xCCF9, 0x6CC3, 0xCCFB, 0x6CC4, 0xAA6E, 0x6CC5, 0xAA73, + 0x6CC6, 0xCCFC, 0x6CC7, 0xCD4A, 0x6CC9, 0xAC75, 0x6CCA, 0xAA79, 0x6CCC, 0xAA63, 0x6CCD, 0xCD49, 0x6CCF, 0xCD4D, 0x6CD0, 0xCCF8, + 0x6CD1, 0xCD4F, 0x6CD2, 0xCD40, 0x6CD3, 0xAA6C, 0x6CD4, 0xCCF4, 0x6CD5, 0xAA6B, 0x6CD6, 0xAA7D, 0x6CD7, 0xAA72, 0x6CD9, 0xCCF2, + 0x6CDA, 0xCF75, 0x6CDB, 0xAA78, 0x6CDC, 0xAA7C, 0x6CDD, 0xCD41, 0x6CDE, 0xCD46, 0x6CE0, 0xAA7E, 0x6CE1, 0xAA77, 0x6CE2, 0xAA69, + 0x6CE3, 0xAA5F, 0x6CE5, 0xAA64, 0x6CE7, 0xCCF6, 0x6CE8, 0xAA60, 0x6CE9, 0xCD4E, 0x6CEB, 0xCCF0, 0x6CEC, 0xCCEF, 0x6CED, 0xCCFD, + 0x6CEE, 0xCCF1, 0x6CEF, 0xAA7B, 0x6CF0, 0xAEF5, 0x6CF1, 0xAA74, 0x6CF2, 0xCCFE, 0x6CF3, 0xAA61, 0x6CF5, 0xACA6, 0x6CF9, 0xCD4C, + 0x6D00, 0xCF7C, 0x6D01, 0xCFA1, 0x6D03, 0xCFA4, 0x6D04, 0xCF77, 0x6D07, 0xCFA7, 0x6D08, 0xCFAA, 0x6D09, 0xCFAC, 0x6D0A, 0xCF74, + 0x6D0B, 0xAC76, 0x6D0C, 0xAC7B, 0x6D0D, 0xD249, 0x6D0E, 0xACAD, 0x6D0F, 0xCFA5, 0x6D10, 0xCFAD, 0x6D11, 0xCF7B, 0x6D12, 0xCF73, + 0x6D16, 0xD264, 0x6D17, 0xAC7E, 0x6D18, 0xCFA2, 0x6D19, 0xCF78, 0x6D1A, 0xCF7A, 0x6D1B, 0xACA5, 0x6D1D, 0xCF7D, 0x6D1E, 0xAC7D, + 0x6D1F, 0xCF70, 0x6D20, 0xCFA8, 0x6D22, 0xCFAB, 0x6D25, 0xAC7A, 0x6D27, 0xACA8, 0x6D28, 0xCF6D, 0x6D29, 0xACAA, 0x6D2A, 0xAC78, + 0x6D2B, 0xACAE, 0x6D2C, 0xCFA9, 0x6D2D, 0xCF6F, 0x6D2E, 0xACAB, 0x6D2F, 0xD25E, 0x6D30, 0xCD48, 0x6D31, 0xAC7C, 0x6D32, 0xAC77, + 0x6D33, 0xCF76, 0x6D34, 0xCF6E, 0x6D35, 0xACAC, 0x6D36, 0xACA4, 0x6D37, 0xCFA3, 0x6D38, 0xACA9, 0x6D39, 0xACA7, 0x6D3A, 0xCF79, + 0x6D3B, 0xACA1, 0x6D3C, 0xCF71, 0x6D3D, 0xACA2, 0x6D3E, 0xACA3, 0x6D3F, 0xCF72, 0x6D40, 0xCFA6, 0x6D41, 0xAC79, 0x6D42, 0xCF7E, + 0x6D58, 0xD24C, 0x6D59, 0xAEFD, 0x6D5A, 0xAF43, 0x6D5E, 0xD255, 0x6D5F, 0xD25B, 0x6D60, 0xD257, 0x6D61, 0xD24A, 0x6D62, 0xD24D, + 0x6D63, 0xD246, 0x6D64, 0xD247, 0x6D65, 0xAF4A, 0x6D66, 0xAEFA, 0x6D67, 0xD256, 0x6D68, 0xD25F, 0x6D69, 0xAF45, 0x6D6A, 0xAEF6, + 0x6D6C, 0xAF40, 0x6D6D, 0xD24E, 0x6D6E, 0xAF42, 0x6D6F, 0xD24F, 0x6D70, 0xD259, 0x6D74, 0xAF44, 0x6D75, 0xD268, 0x6D76, 0xD248, + 0x6D77, 0xAEFC, 0x6D78, 0xAEFB, 0x6D79, 0xAF48, 0x6D7A, 0xD245, 0x6D7B, 0xD266, 0x6D7C, 0xD25A, 0x6D7D, 0xD267, 0x6D7E, 0xD261, + 0x6D7F, 0xD253, 0x6D80, 0xD262, 0x6D82, 0xD25C, 0x6D83, 0xD265, 0x6D84, 0xD263, 0x6D85, 0xAF49, 0x6D86, 0xD254, 0x6D87, 0xAEF9, + 0x6D88, 0xAEF8, 0x6D89, 0xAF41, 0x6D8A, 0xAF47, 0x6D8B, 0xD260, 0x6D8C, 0xAF46, 0x6D8D, 0xD251, 0x6D8E, 0xB243, 0x6D90, 0xD269, + 0x6D91, 0xD250, 0x6D92, 0xD24B, 0x6D93, 0xAEFE, 0x6D94, 0xAF4B, 0x6D95, 0xAEF7, 0x6D97, 0xD258, 0x6D98, 0xD25D, 0x6DAA, 0xB265, + 0x6DAB, 0xD5E1, 0x6DAC, 0xD5E5, 0x6DAE, 0xB252, 0x6DAF, 0xB250, 0x6DB2, 0xB247, 0x6DB3, 0xD5E3, 0x6DB4, 0xD5E2, 0x6DB5, 0xB25B, + 0x6DB7, 0xD5E8, 0x6DB8, 0xB255, 0x6DBA, 0xD5FA, 0x6DBB, 0xD647, 0x6DBC, 0xB244, 0x6DBD, 0xD5F7, 0x6DBE, 0xD5F0, 0x6DBF, 0xB267, + 0x6DC0, 0xD5E0, 0x6DC2, 0xD5FC, 0x6DC4, 0xB264, 0x6DC5, 0xB258, 0x6DC6, 0xB263, 0x6DC7, 0xB24E, 0x6DC8, 0xD5EC, 0x6DC9, 0xD5FE, + 0x6DCA, 0xD5F6, 0x6DCB, 0xB24F, 0x6DCC, 0xB249, 0x6DCD, 0xD645, 0x6DCF, 0xD5FD, 0x6DD0, 0xD640, 0x6DD1, 0xB251, 0x6DD2, 0xB259, + 0x6DD3, 0xD642, 0x6DD4, 0xD5EA, 0x6DD5, 0xD5FB, 0x6DD6, 0xD5EF, 0x6DD7, 0xD644, 0x6DD8, 0xB25E, 0x6DD9, 0xB246, 0x6DDA, 0xB25C, + 0x6DDB, 0xD5F4, 0x6DDC, 0xD5F2, 0x6DDD, 0xD5F3, 0x6DDE, 0xB253, 0x6DDF, 0xD5EE, 0x6DE0, 0xD5ED, 0x6DE1, 0xB248, 0x6DE2, 0xD5E7, + 0x6DE3, 0xD646, 0x6DE4, 0xB24A, 0x6DE5, 0xD5F1, 0x6DE6, 0xB268, 0x6DE8, 0xB262, 0x6DE9, 0xD5E6, 0x6DEA, 0xB25F, 0x6DEB, 0xB25D, + 0x6DEC, 0xB266, 0x6DED, 0xD5F8, 0x6DEE, 0xB261, 0x6DEF, 0xD252, 0x6DF0, 0xD5F9, 0x6DF1, 0xB260, 0x6DF2, 0xD641, 0x6DF3, 0xB245, + 0x6DF4, 0xD5F5, 0x6DF5, 0xB257, 0x6DF6, 0xD5E9, 0x6DF7, 0xB256, 0x6DF9, 0xB254, 0x6DFA, 0xB24C, 0x6DFB, 0xB24B, 0x6DFC, 0xD9E7, + 0x6DFD, 0xD643, 0x6E00, 0xD5EB, 0x6E03, 0xD9FC, 0x6E05, 0xB24D, 0x6E19, 0xB541, 0x6E1A, 0xB25A, 0x6E1B, 0xB4EE, 0x6E1C, 0xD9F6, + 0x6E1D, 0xB4FC, 0x6E1F, 0xD9EA, 0x6E20, 0xB4EB, 0x6E21, 0xB4E7, 0x6E22, 0xDA49, 0x6E23, 0xB4ED, 0x6E24, 0xB4F1, 0x6E25, 0xB4EC, + 0x6E26, 0xB4F5, 0x6E27, 0xDA4D, 0x6E28, 0xDA44, 0x6E2B, 0xD9F1, 0x6E2C, 0xB4FA, 0x6E2D, 0xB4F4, 0x6E2E, 0xD9FD, 0x6E2F, 0xB4E4, + 0x6E30, 0xDA4A, 0x6E31, 0xDA43, 0x6E32, 0xB4E8, 0x6E33, 0xD9F7, 0x6E34, 0xB4F7, 0x6E35, 0xDA55, 0x6E36, 0xDA56, 0x6E38, 0xB4E5, + 0x6E39, 0xDA48, 0x6E3A, 0xB4F9, 0x6E3B, 0xD9FB, 0x6E3C, 0xD9ED, 0x6E3D, 0xD9EE, 0x6E3E, 0xB4FD, 0x6E3F, 0xD9F2, 0x6E40, 0xD9F9, + 0x6E41, 0xD9F3, 0x6E43, 0xB4FB, 0x6E44, 0xB544, 0x6E45, 0xD9EF, 0x6E46, 0xD9E8, 0x6E47, 0xD9E9, 0x6E49, 0xD9EB, 0x6E4A, 0xB4EA, + 0x6E4B, 0xD9F8, 0x6E4D, 0xB4F8, 0x6E4E, 0xB542, 0x6E51, 0xD9FA, 0x6E52, 0xDA53, 0x6E53, 0xDA4B, 0x6E54, 0xB4E6, 0x6E55, 0xDA51, + 0x6E56, 0xB4F2, 0x6E58, 0xB4F0, 0x6E5A, 0xDA57, 0x6E5B, 0xB4EF, 0x6E5C, 0xDA41, 0x6E5D, 0xD9F4, 0x6E5E, 0xD9FE, 0x6E5F, 0xB547, + 0x6E60, 0xDA45, 0x6E61, 0xDA42, 0x6E62, 0xD9F0, 0x6E63, 0xB543, 0x6E64, 0xDA4F, 0x6E65, 0xDA4C, 0x6E66, 0xDA54, 0x6E67, 0xB4E9, + 0x6E68, 0xDA40, 0x6E69, 0xB546, 0x6E6B, 0xDA47, 0x6E6E, 0xB4F3, 0x6E6F, 0xB4F6, 0x6E71, 0xDA46, 0x6E72, 0xB545, 0x6E73, 0xD9F5, + 0x6E74, 0xD5E4, 0x6E77, 0xDA50, 0x6E78, 0xDA4E, 0x6E79, 0xDA52, 0x6E88, 0xD9EC, 0x6E89, 0xB540, 0x6E8D, 0xDE61, 0x6E8E, 0xDE60, + 0x6E8F, 0xDE46, 0x6E90, 0xB7BD, 0x6E92, 0xDE5F, 0x6E93, 0xDE49, 0x6E94, 0xDE4A, 0x6E96, 0xB7C7, 0x6E97, 0xDE68, 0x6E98, 0xB7C2, + 0x6E99, 0xDE5E, 0x6E9B, 0xDE43, 0x6E9C, 0xB7C8, 0x6E9D, 0xB7BE, 0x6E9E, 0xDE52, 0x6E9F, 0xDE48, 0x6EA0, 0xDE4B, 0x6EA1, 0xDE63, + 0x6EA2, 0xB7B8, 0x6EA3, 0xDE6A, 0x6EA4, 0xDE62, 0x6EA5, 0xB7C1, 0x6EA6, 0xDE57, 0x6EA7, 0xB7CC, 0x6EAA, 0xB7CB, 0x6EAB, 0xB7C5, + 0x6EAE, 0xDE69, 0x6EAF, 0xB7B9, 0x6EB0, 0xDE55, 0x6EB1, 0xDE4C, 0x6EB2, 0xDE59, 0x6EB3, 0xDE65, 0x6EB4, 0xB7CD, 0x6EB6, 0xB7BB, + 0x6EB7, 0xDE54, 0x6EB9, 0xDE4D, 0x6EBA, 0xB7C4, 0x6EBC, 0xB7C3, 0x6EBD, 0xDE50, 0x6EBE, 0xDE5A, 0x6EBF, 0xDE64, 0x6EC0, 0xDE47, + 0x6EC1, 0xDE51, 0x6EC2, 0xB7BC, 0x6EC3, 0xDE5B, 0x6EC4, 0xB7C9, 0x6EC5, 0xB7C0, 0x6EC6, 0xDE4E, 0x6EC7, 0xB7BF, 0x6EC8, 0xDE45, + 0x6EC9, 0xDE53, 0x6ECA, 0xDE67, 0x6ECB, 0xB4FE, 0x6ECC, 0xBAB0, 0x6ECD, 0xDE56, 0x6ECE, 0xE26C, 0x6ECF, 0xDE58, 0x6ED0, 0xDE66, + 0x6ED1, 0xB7C6, 0x6ED2, 0xDE4F, 0x6ED3, 0xB7BA, 0x6ED4, 0xB7CA, 0x6ED5, 0xBCF0, 0x6ED6, 0xDE44, 0x6ED8, 0xDE5D, 0x6EDC, 0xDE5C, + 0x6EEB, 0xE2AA, 0x6EEC, 0xBAAD, 0x6EED, 0xE27D, 0x6EEE, 0xE2A4, 0x6EEF, 0xBAA2, 0x6EF1, 0xE26E, 0x6EF2, 0xBAAF, 0x6EF4, 0xBA77, + 0x6EF5, 0xE26D, 0x6EF6, 0xE2B0, 0x6EF7, 0xBAB1, 0x6EF8, 0xE271, 0x6EF9, 0xE2A3, 0x6EFB, 0xE273, 0x6EFC, 0xE2B3, 0x6EFD, 0xE2AF, + 0x6EFE, 0xBA75, 0x6EFF, 0xBAA1, 0x6F00, 0xE653, 0x6F01, 0xBAAE, 0x6F02, 0xBA7D, 0x6F03, 0xE26F, 0x6F05, 0xE2AE, 0x6F06, 0xBAA3, + 0x6F07, 0xE2AB, 0x6F08, 0xE2B8, 0x6F09, 0xE275, 0x6F0A, 0xE27E, 0x6F0D, 0xE2B6, 0x6F0E, 0xE2AC, 0x6F0F, 0xBA7C, 0x6F12, 0xE27C, + 0x6F13, 0xBA76, 0x6F14, 0xBA74, 0x6F15, 0xBAA8, 0x6F18, 0xE27A, 0x6F19, 0xE277, 0x6F1A, 0xE278, 0x6F1C, 0xE2B2, 0x6F1E, 0xE2B7, + 0x6F1F, 0xE2B5, 0x6F20, 0xBA7A, 0x6F21, 0xE2B9, 0x6F22, 0xBA7E, 0x6F23, 0xBAA7, 0x6F25, 0xE270, 0x6F26, 0xE5FA, 0x6F27, 0xE279, + 0x6F29, 0xBA78, 0x6F2A, 0xBAAC, 0x6F2B, 0xBAA9, 0x6F2C, 0xBA7B, 0x6F2D, 0xE2A5, 0x6F2E, 0xE274, 0x6F2F, 0xBAAA, 0x6F30, 0xE2A7, + 0x6F31, 0xBAA4, 0x6F32, 0xBAA6, 0x6F33, 0xBA73, 0x6F35, 0xE2A9, 0x6F36, 0xE2A1, 0x6F37, 0xE272, 0x6F38, 0xBAA5, 0x6F39, 0xE2B1, + 0x6F3A, 0xE2B4, 0x6F3B, 0xE27B, 0x6F3C, 0xE2A8, 0x6F3E, 0xBA79, 0x6F3F, 0xBCDF, 0x6F40, 0xE2A6, 0x6F41, 0xE5F9, 0x6F43, 0xE2AD, + 0x6F4E, 0xE276, 0x6F4F, 0xE644, 0x6F50, 0xE64E, 0x6F51, 0xBCE2, 0x6F52, 0xE64D, 0x6F53, 0xE659, 0x6F54, 0xBCE4, 0x6F55, 0xE64B, + 0x6F57, 0xE64F, 0x6F58, 0xBCEF, 0x6F5A, 0xE646, 0x6F5B, 0xBCE7, 0x6F5D, 0xE652, 0x6F5E, 0xE9F0, 0x6F5F, 0xBCF3, 0x6F60, 0xBCF2, + 0x6F61, 0xE654, 0x6F62, 0xE643, 0x6F63, 0xE65E, 0x6F64, 0xBCED, 0x6F66, 0xBCE3, 0x6F67, 0xE657, 0x6F69, 0xE65B, 0x6F6A, 0xE660, + 0x6F6B, 0xE655, 0x6F6C, 0xE649, 0x6F6D, 0xBCE6, 0x6F6E, 0xBCE9, 0x6F6F, 0xBCF1, 0x6F70, 0xBCEC, 0x6F72, 0xE64C, 0x6F73, 0xE2A2, + 0x6F76, 0xE648, 0x6F77, 0xE65F, 0x6F78, 0xBCE8, 0x6F7A, 0xBCEB, 0x6F7B, 0xE661, 0x6F7C, 0xBCE0, 0x6F7D, 0xE656, 0x6F7E, 0xE5FB, + 0x6F7F, 0xE65C, 0x6F80, 0xC0DF, 0x6F82, 0xE64A, 0x6F84, 0xBCE1, 0x6F85, 0xE645, 0x6F86, 0xBCE5, 0x6F87, 0xE5FC, 0x6F88, 0xBAAB, + 0x6F89, 0xE641, 0x6F8B, 0xE65A, 0x6F8C, 0xE642, 0x6F8D, 0xE640, 0x6F8E, 0xBCEA, 0x6F90, 0xE658, 0x6F92, 0xE5FE, 0x6F93, 0xE651, + 0x6F94, 0xE650, 0x6F95, 0xE65D, 0x6F96, 0xE647, 0x6F97, 0xBCEE, 0x6F9E, 0xE9F3, 0x6FA0, 0xBF49, 0x6FA1, 0xBEFE, 0x6FA2, 0xEA40, + 0x6FA3, 0xE9EB, 0x6FA4, 0xBF41, 0x6FA5, 0xE9F7, 0x6FA6, 0xBF48, 0x6FA7, 0xBF43, 0x6FA8, 0xE9F5, 0x6FA9, 0xED4F, 0x6FAA, 0xE9FB, + 0x6FAB, 0xEA42, 0x6FAC, 0xE9FA, 0x6FAD, 0xE9E9, 0x6FAE, 0xE9F8, 0x6FAF, 0xEA44, 0x6FB0, 0xEA46, 0x6FB1, 0xBEFD, 0x6FB2, 0xEA45, + 0x6FB3, 0xBF44, 0x6FB4, 0xBF4A, 0x6FB6, 0xBF47, 0x6FB8, 0xE9FE, 0x6FB9, 0xBF46, 0x6FBA, 0xE9F9, 0x6FBC, 0xE9ED, 0x6FBD, 0xE9F2, + 0x6FBF, 0xE9FD, 0x6FC0, 0xBF45, 0x6FC1, 0xBF42, 0x6FC2, 0xBEFC, 0x6FC3, 0xBF40, 0x6FC4, 0xE9F1, 0x6FC6, 0xE5FD, 0x6FC7, 0xE9EC, + 0x6FC8, 0xE9EF, 0x6FC9, 0xEA41, 0x6FCA, 0xE9F4, 0x6FCB, 0xE9EA, 0x6FCC, 0xED4E, 0x6FCD, 0xEA43, 0x6FCE, 0xE9EE, 0x6FCF, 0xE9FC, + 0x6FD4, 0xED51, 0x6FD5, 0xC0E3, 0x6FD8, 0xC0D7, 0x6FDB, 0xC0DB, 0x6FDC, 0xED53, 0x6FDD, 0xED59, 0x6FDE, 0xED57, 0x6FDF, 0xC0D9, + 0x6FE0, 0xC0DA, 0x6FE1, 0xC0E1, 0x6FE2, 0xED5A, 0x6FE3, 0xED52, 0x6FE4, 0xC0DC, 0x6FE6, 0xED56, 0x6FE7, 0xED55, 0x6FE8, 0xED5B, + 0x6FE9, 0xC0E2, 0x6FEB, 0xC0DD, 0x6FEC, 0xC0E0, 0x6FED, 0xED54, 0x6FEE, 0xC0E4, 0x6FEF, 0xC0DE, 0x6FF0, 0xC0E5, 0x6FF1, 0xC0D8, + 0x6FF2, 0xED58, 0x6FF4, 0xED50, 0x6FF7, 0xEFF7, 0x6FFA, 0xC271, 0x6FFB, 0xEFF4, 0x6FFC, 0xEFF6, 0x6FFE, 0xC26F, 0x6FFF, 0xEFF2, + 0x7000, 0xEFF3, 0x7001, 0xEFEE, 0x7004, 0xE9F6, 0x7005, 0xEFEF, 0x7006, 0xC270, 0x7007, 0xEFEB, 0x7009, 0xC26D, 0x700A, 0xEFF8, + 0x700B, 0xC26E, 0x700C, 0xEFEC, 0x700D, 0xEFED, 0x700E, 0xEFF1, 0x700F, 0xC273, 0x7011, 0xC272, 0x7014, 0xEFF0, 0x7015, 0xC378, + 0x7016, 0xF25F, 0x7017, 0xF265, 0x7018, 0xC379, 0x7019, 0xF25C, 0x701A, 0xC376, 0x701B, 0xC373, 0x701C, 0xF267, 0x701D, 0xC377, + 0x701F, 0xC374, 0x7020, 0xF25E, 0x7021, 0xF261, 0x7022, 0xF262, 0x7023, 0xF263, 0x7024, 0xF266, 0x7026, 0xEFF5, 0x7027, 0xF25D, + 0x7028, 0xC375, 0x7029, 0xF264, 0x702A, 0xF268, 0x702B, 0xF260, 0x702F, 0xF45D, 0x7030, 0xC46A, 0x7031, 0xF460, 0x7032, 0xC46B, + 0x7033, 0xF468, 0x7034, 0xF45F, 0x7035, 0xF45C, 0x7037, 0xF45E, 0x7038, 0xF462, 0x7039, 0xF465, 0x703A, 0xF464, 0x703B, 0xF467, + 0x703C, 0xF45B, 0x703E, 0xC469, 0x703F, 0xF463, 0x7040, 0xF466, 0x7041, 0xF469, 0x7042, 0xF461, 0x7043, 0xF5D3, 0x7044, 0xF5D4, + 0x7045, 0xF5D8, 0x7046, 0xF5D9, 0x7048, 0xF5D6, 0x7049, 0xF5D7, 0x704A, 0xF5D5, 0x704C, 0xC4E9, 0x7051, 0xC578, 0x7052, 0xF6EB, + 0x7055, 0xF6E8, 0x7056, 0xF6E9, 0x7057, 0xF6EA, 0x7058, 0xC579, 0x705A, 0xF7E5, 0x705B, 0xF7E4, 0x705D, 0xF8AF, 0x705E, 0xC5F4, + 0x705F, 0xF8AD, 0x7060, 0xF8B0, 0x7061, 0xF8AE, 0x7062, 0xF8F5, 0x7063, 0xC657, 0x7064, 0xC665, 0x7065, 0xF9A3, 0x7066, 0xF96C, + 0x7068, 0xF9A2, 0x7069, 0xF9D0, 0x706A, 0xF9D1, 0x706B, 0xA4F5, 0x7070, 0xA6C7, 0x7071, 0xCA41, 0x7074, 0xCB5E, 0x7076, 0xA85F, + 0x7078, 0xA862, 0x707A, 0xCB5F, 0x707C, 0xA860, 0x707D, 0xA861, 0x7082, 0xCD58, 0x7083, 0xCD5A, 0x7084, 0xCD55, 0x7085, 0xCD52, + 0x7086, 0xCD54, 0x708A, 0xAAA4, 0x708E, 0xAAA2, 0x7091, 0xCD56, 0x7092, 0xAAA3, 0x7093, 0xCD53, 0x7094, 0xCD50, 0x7095, 0xAAA1, + 0x7096, 0xCD57, 0x7098, 0xCD51, 0x7099, 0xAAA5, 0x709A, 0xCD59, 0x709F, 0xCFAF, 0x70A1, 0xCFB3, 0x70A4, 0xACB7, 0x70A9, 0xCFB6, + 0x70AB, 0xACAF, 0x70AC, 0xACB2, 0x70AD, 0xACB4, 0x70AE, 0xACB6, 0x70AF, 0xACB3, 0x70B0, 0xCFB2, 0x70B1, 0xCFB1, 0x70B3, 0xACB1, + 0x70B4, 0xCFB4, 0x70B5, 0xCFB5, 0x70B7, 0xCFAE, 0x70B8, 0xACB5, 0x70BA, 0xACB0, 0x70BE, 0xCFB0, 0x70C5, 0xD277, 0x70C6, 0xD278, + 0x70C7, 0xD279, 0x70C8, 0xAF50, 0x70CA, 0xAF4C, 0x70CB, 0xD26E, 0x70CD, 0xD276, 0x70CE, 0xD27B, 0x70CF, 0xAF51, 0x70D1, 0xD26C, + 0x70D2, 0xD272, 0x70D3, 0xD26B, 0x70D4, 0xD275, 0x70D7, 0xD271, 0x70D8, 0xAF4D, 0x70D9, 0xAF4F, 0x70DA, 0xD27A, 0x70DC, 0xD26A, + 0x70DD, 0xD26D, 0x70DE, 0xD273, 0x70E0, 0xD274, 0x70E1, 0xD27C, 0x70E2, 0xD270, 0x70E4, 0xAF4E, 0x70EF, 0xB26D, 0x70F0, 0xD64E, + 0x70F3, 0xD650, 0x70F4, 0xD64C, 0x70F6, 0xD658, 0x70F7, 0xD64A, 0x70F8, 0xD657, 0x70F9, 0xB269, 0x70FA, 0xD648, 0x70FB, 0xDA5B, + 0x70FC, 0xD652, 0x70FD, 0xB26C, 0x70FF, 0xD653, 0x7100, 0xD656, 0x7102, 0xD65A, 0x7104, 0xD64F, 0x7106, 0xD654, 0x7109, 0xB26A, + 0x710A, 0xB26B, 0x710B, 0xD659, 0x710C, 0xD64D, 0x710D, 0xD649, 0x710E, 0xD65B, 0x7110, 0xD651, 0x7113, 0xD655, 0x7117, 0xD64B, + 0x7119, 0xB548, 0x711A, 0xB549, 0x711B, 0xDA65, 0x711C, 0xB54F, 0x711E, 0xDA59, 0x711F, 0xDA62, 0x7120, 0xDA58, 0x7121, 0xB54C, + 0x7122, 0xDA60, 0x7123, 0xDA5E, 0x7125, 0xDA5F, 0x7126, 0xB54A, 0x7128, 0xDA63, 0x712E, 0xDA5C, 0x712F, 0xDA5A, 0x7130, 0xB54B, + 0x7131, 0xDA5D, 0x7132, 0xDA61, 0x7136, 0xB54D, 0x713A, 0xDA64, 0x7141, 0xDE70, 0x7142, 0xDE77, 0x7143, 0xDE79, 0x7144, 0xDEA1, + 0x7146, 0xB7DA, 0x7147, 0xDE6B, 0x7149, 0xB7D2, 0x714B, 0xDE7A, 0x714C, 0xB7D7, 0x714D, 0xDEA2, 0x714E, 0xB7CE, 0x7150, 0xDE7D, + 0x7152, 0xDE6D, 0x7153, 0xDE7E, 0x7154, 0xDE6C, 0x7156, 0xB7DC, 0x7158, 0xDE78, 0x7159, 0xB7CF, 0x715A, 0xDEA3, 0x715C, 0xB7D4, + 0x715D, 0xDE71, 0x715E, 0xB7D9, 0x715F, 0xDE7C, 0x7160, 0xDE6F, 0x7161, 0xDE76, 0x7162, 0xDE72, 0x7163, 0xDE6E, 0x7164, 0xB7D1, + 0x7165, 0xB7D8, 0x7166, 0xB7D6, 0x7167, 0xB7D3, 0x7168, 0xB7DB, 0x7169, 0xB7D0, 0x716A, 0xDE75, 0x716C, 0xB7D5, 0x716E, 0xB54E, + 0x7170, 0xDE7B, 0x7172, 0xDE73, 0x7178, 0xDE74, 0x717B, 0xE2C1, 0x717D, 0xBAB4, 0x7180, 0xE2BD, 0x7181, 0xE2C3, 0x7182, 0xE2BF, + 0x7184, 0xBAB6, 0x7185, 0xE2BE, 0x7186, 0xE2C2, 0x7187, 0xE2BA, 0x7189, 0xE2BC, 0x718A, 0xBAB5, 0x718F, 0xE2C0, 0x7190, 0xE2BB, + 0x7192, 0xBAB7, 0x7194, 0xBAB2, 0x7197, 0xE2C4, 0x7199, 0xBAB3, 0x719A, 0xE667, 0x719B, 0xE664, 0x719C, 0xE670, 0x719D, 0xE66A, + 0x719E, 0xE66C, 0x719F, 0xBCF4, 0x71A0, 0xE666, 0x71A1, 0xE66E, 0x71A4, 0xE66D, 0x71A5, 0xE66B, 0x71A7, 0xE671, 0x71A8, 0xBCF7, + 0x71A9, 0xE668, 0x71AA, 0xE66F, 0x71AC, 0xBCF5, 0x71AF, 0xE663, 0x71B0, 0xE665, 0x71B1, 0xBCF6, 0x71B2, 0xE662, 0x71B3, 0xE672, + 0x71B5, 0xE669, 0x71B8, 0xEA4A, 0x71B9, 0xBF51, 0x71BC, 0xEA55, 0x71BD, 0xEA53, 0x71BE, 0xBF4B, 0x71BF, 0xEA49, 0x71C0, 0xEA4C, + 0x71C1, 0xEA4D, 0x71C2, 0xEA48, 0x71C3, 0xBF55, 0x71C4, 0xBF56, 0x71C5, 0xEA47, 0x71C6, 0xEA56, 0x71C7, 0xEA51, 0x71C8, 0xBF4F, + 0x71C9, 0xBF4C, 0x71CA, 0xEA50, 0x71CB, 0xEA4E, 0x71CE, 0xBF52, 0x71CF, 0xEA52, 0x71D0, 0xBF4D, 0x71D2, 0xBF4E, 0x71D4, 0xEA4F, + 0x71D5, 0xBF50, 0x71D6, 0xEA4B, 0x71D8, 0xEA54, 0x71D9, 0xBF53, 0x71DA, 0xEA57, 0x71DB, 0xEA58, 0x71DC, 0xBF54, 0x71DF, 0xC0E7, + 0x71E0, 0xC0EE, 0x71E1, 0xED5C, 0x71E2, 0xED62, 0x71E4, 0xED60, 0x71E5, 0xC0EA, 0x71E6, 0xC0E9, 0x71E7, 0xC0E6, 0x71E8, 0xED5E, + 0x71EC, 0xC0EC, 0x71ED, 0xC0EB, 0x71EE, 0xC0E8, 0x71F0, 0xED61, 0x71F1, 0xED5D, 0x71F2, 0xED5F, 0x71F4, 0xC0ED, 0x71F8, 0xC277, + 0x71F9, 0xEFFB, 0x71FB, 0xC274, 0x71FC, 0xC275, 0x71FD, 0xEFFD, 0x71FE, 0xC276, 0x71FF, 0xEFFA, 0x7201, 0xEFF9, 0x7202, 0xF26C, + 0x7203, 0xEFFC, 0x7205, 0xF26D, 0x7206, 0xC37A, 0x7207, 0xF26B, 0x720A, 0xF26A, 0x720C, 0xF269, 0x720D, 0xC37B, 0x7210, 0xC46C, + 0x7213, 0xF46A, 0x7214, 0xF46B, 0x7219, 0xF5DC, 0x721A, 0xF5DB, 0x721B, 0xC4EA, 0x721D, 0xF5DA, 0x721E, 0xF6EC, 0x721F, 0xF6ED, + 0x7222, 0xF7E6, 0x7223, 0xF8B1, 0x7226, 0xF8F6, 0x7227, 0xF9BC, 0x7228, 0xC679, 0x7229, 0xF9C6, 0x722A, 0xA4F6, 0x722C, 0xAAA6, + 0x722D, 0xAAA7, 0x7230, 0xACB8, 0x7235, 0xC0EF, 0x7236, 0xA4F7, 0x7238, 0xAAA8, 0x7239, 0xAF52, 0x723A, 0xB7DD, 0x723B, 0xA4F8, + 0x723D, 0xB26E, 0x723E, 0xBAB8, 0x723F, 0xC962, 0x7241, 0xCFB7, 0x7242, 0xD27D, 0x7244, 0xE2C5, 0x7246, 0xC0F0, 0x7247, 0xA4F9, + 0x7248, 0xAAA9, 0x7249, 0xCFB8, 0x724A, 0xCFB9, 0x724B, 0xDA66, 0x724C, 0xB550, 0x724F, 0xDEA4, 0x7252, 0xB7DE, 0x7253, 0xE2C6, + 0x7256, 0xBCF8, 0x7258, 0xC37C, 0x7259, 0xA4FA, 0x725A, 0xDA67, 0x725B, 0xA4FB, 0x725D, 0xA6C9, 0x725E, 0xCA42, 0x725F, 0xA6C8, + 0x7260, 0xA865, 0x7261, 0xA864, 0x7262, 0xA863, 0x7263, 0xCB60, 0x7267, 0xAAAA, 0x7269, 0xAAAB, 0x726A, 0xCD5B, 0x726C, 0xCFBA, + 0x726E, 0xCFBD, 0x726F, 0xACBA, 0x7270, 0xCFBB, 0x7272, 0xACB9, 0x7273, 0xCFBC, 0x7274, 0xACBB, 0x7276, 0xD2A2, 0x7277, 0xD2A1, + 0x7278, 0xD27E, 0x7279, 0xAF53, 0x727B, 0xD65D, 0x727C, 0xD65E, 0x727D, 0xB26F, 0x727E, 0xD65C, 0x727F, 0xD65F, 0x7280, 0xB552, + 0x7281, 0xB270, 0x7284, 0xB551, 0x7285, 0xDA6B, 0x7286, 0xDA6A, 0x7288, 0xDA68, 0x7289, 0xDA69, 0x728B, 0xDA6C, 0x728C, 0xDEA6, + 0x728D, 0xDEA5, 0x728E, 0xDEA9, 0x7290, 0xDEA8, 0x7291, 0xDEA7, 0x7292, 0xBAB9, 0x7293, 0xE2C9, 0x7295, 0xE2C8, 0x7296, 0xBABA, + 0x7297, 0xE2C7, 0x7298, 0xE673, 0x729A, 0xE674, 0x729B, 0xBCF9, 0x729D, 0xEA59, 0x729E, 0xEA5A, 0x72A1, 0xF272, 0x72A2, 0xC37D, + 0x72A3, 0xF271, 0x72A4, 0xF270, 0x72A5, 0xF26E, 0x72A6, 0xF26F, 0x72A7, 0xC4EB, 0x72A8, 0xF46C, 0x72A9, 0xF6EE, 0x72AA, 0xF8F7, + 0x72AC, 0xA4FC, 0x72AE, 0xC9A5, 0x72AF, 0xA5C7, 0x72B0, 0xC9A6, 0x72B4, 0xCA43, 0x72B5, 0xCA44, 0x72BA, 0xCB66, 0x72BD, 0xCB62, + 0x72BF, 0xCB61, 0x72C0, 0xAAAC, 0x72C1, 0xCB65, 0x72C2, 0xA867, 0x72C3, 0xCB63, 0x72C4, 0xA866, 0x72C5, 0xCB67, 0x72C6, 0xCB64, + 0x72C9, 0xCD5F, 0x72CA, 0xCFBE, 0x72CB, 0xCD5D, 0x72CC, 0xCD64, 0x72CE, 0xAAAD, 0x72D0, 0xAAB0, 0x72D1, 0xCD65, 0x72D2, 0xCD61, + 0x72D4, 0xCD62, 0x72D6, 0xCD5C, 0x72D7, 0xAAAF, 0x72D8, 0xCD5E, 0x72D9, 0xAAAE, 0x72DA, 0xCD63, 0x72DC, 0xCD60, 0x72DF, 0xCFC2, + 0x72E0, 0xACBD, 0x72E1, 0xACBE, 0x72E3, 0xCFC5, 0x72E4, 0xCFBF, 0x72E6, 0xCFC4, 0x72E8, 0xCFC0, 0x72E9, 0xACBC, 0x72EA, 0xCFC3, + 0x72EB, 0xCFC1, 0x72F3, 0xD2A8, 0x72F4, 0xD2A5, 0x72F6, 0xD2A7, 0x72F7, 0xAF58, 0x72F8, 0xAF57, 0x72F9, 0xAF55, 0x72FA, 0xD2A4, + 0x72FB, 0xD2A9, 0x72FC, 0xAF54, 0x72FD, 0xAF56, 0x72FE, 0xD2A6, 0x72FF, 0xD667, 0x7300, 0xD2A3, 0x7301, 0xD2AA, 0x7307, 0xD662, + 0x7308, 0xD666, 0x730A, 0xD665, 0x730B, 0xDA6E, 0x730C, 0xDA79, 0x730F, 0xD668, 0x7311, 0xD663, 0x7312, 0xDA6D, 0x7313, 0xB274, + 0x7316, 0xB273, 0x7317, 0xD661, 0x7318, 0xD664, 0x7319, 0xB275, 0x731B, 0xB272, 0x731C, 0xB271, 0x731D, 0xD660, 0x731E, 0xD669, + 0x7322, 0xDA70, 0x7323, 0xDA77, 0x7325, 0xB554, 0x7326, 0xDA76, 0x7327, 0xDA73, 0x7329, 0xB556, 0x732D, 0xDA75, 0x7330, 0xDA6F, + 0x7331, 0xDA71, 0x7332, 0xDA74, 0x7333, 0xDA72, 0x7334, 0xB555, 0x7335, 0xDA78, 0x7336, 0xB553, 0x7337, 0xB7DF, 0x733A, 0xDEAD, + 0x733B, 0xDEAC, 0x733C, 0xDEAA, 0x733E, 0xB7E2, 0x733F, 0xB7E1, 0x7340, 0xDEAE, 0x7342, 0xDEAB, 0x7343, 0xE2CA, 0x7344, 0xBABB, + 0x7345, 0xB7E0, 0x7349, 0xDEB0, 0x734A, 0xDEAF, 0x734C, 0xE2CD, 0x734D, 0xE2CB, 0x734E, 0xBCFA, 0x7350, 0xBABC, 0x7351, 0xE2CC, + 0x7352, 0xE676, 0x7357, 0xBCFB, 0x7358, 0xE675, 0x7359, 0xE67E, 0x735A, 0xE67D, 0x735B, 0xE67B, 0x735D, 0xE67A, 0x735E, 0xE677, + 0x735F, 0xE678, 0x7360, 0xE679, 0x7361, 0xE67C, 0x7362, 0xE6A1, 0x7365, 0xEA5F, 0x7366, 0xEA5C, 0x7367, 0xEA5D, 0x7368, 0xBF57, + 0x7369, 0xEA5B, 0x736A, 0xEA61, 0x736B, 0xEA60, 0x736C, 0xEA5E, 0x736E, 0xED64, 0x736F, 0xED65, 0x7370, 0xC0F1, 0x7372, 0xC0F2, + 0x7373, 0xED63, 0x7375, 0xC279, 0x7376, 0xEFFE, 0x7377, 0xC278, 0x7378, 0xC37E, 0x737A, 0xC3A1, 0x737B, 0xC46D, 0x737C, 0xF46E, + 0x737D, 0xF46D, 0x737E, 0xF5DD, 0x737F, 0xF6EF, 0x7380, 0xC57A, 0x7381, 0xF7E8, 0x7382, 0xF7E7, 0x7383, 0xF7E9, 0x7384, 0xA5C8, + 0x7385, 0xCFC6, 0x7386, 0xAF59, 0x7387, 0xB276, 0x7388, 0xD66A, 0x7389, 0xA5C9, 0x738A, 0xC9A7, 0x738B, 0xA4FD, 0x738E, 0xCA45, + 0x7392, 0xCB6C, 0x7393, 0xCB6A, 0x7394, 0xCB6B, 0x7395, 0xCB68, 0x7396, 0xA868, 0x7397, 0xCB69, 0x739D, 0xCD6D, 0x739F, 0xAAB3, + 0x73A0, 0xCD6B, 0x73A1, 0xCD67, 0x73A2, 0xCD6A, 0x73A4, 0xCD66, 0x73A5, 0xAAB5, 0x73A6, 0xCD69, 0x73A8, 0xAAB2, 0x73A9, 0xAAB1, + 0x73AB, 0xAAB4, 0x73AC, 0xCD6C, 0x73AD, 0xCD68, 0x73B2, 0xACC2, 0x73B3, 0xACC5, 0x73B4, 0xCFCE, 0x73B5, 0xCFCD, 0x73B6, 0xCFCC, + 0x73B7, 0xACBF, 0x73B8, 0xCFD5, 0x73B9, 0xCFCB, 0x73BB, 0xACC1, 0x73BC, 0xD2AF, 0x73BE, 0xCFD2, 0x73BF, 0xCFD0, 0x73C0, 0xACC4, + 0x73C2, 0xCFC8, 0x73C3, 0xCFD3, 0x73C5, 0xCFCA, 0x73C6, 0xCFD4, 0x73C7, 0xCFD1, 0x73C8, 0xCFC9, 0x73CA, 0xACC0, 0x73CB, 0xCFD6, + 0x73CC, 0xCFC7, 0x73CD, 0xACC3, 0x73D2, 0xD2B4, 0x73D3, 0xD2AB, 0x73D4, 0xD2B6, 0x73D6, 0xD2AE, 0x73D7, 0xD2B9, 0x73D8, 0xD2BA, + 0x73D9, 0xD2AC, 0x73DA, 0xD2B8, 0x73DB, 0xD2B5, 0x73DC, 0xD2B3, 0x73DD, 0xD2B7, 0x73DE, 0xAF5F, 0x73E0, 0xAF5D, 0x73E3, 0xD2B1, + 0x73E5, 0xD2AD, 0x73E7, 0xD2B0, 0x73E8, 0xD2BB, 0x73E9, 0xD2B2, 0x73EA, 0xAF5E, 0x73EB, 0xCFCF, 0x73ED, 0xAF5A, 0x73EE, 0xAF5C, + 0x73F4, 0xD678, 0x73F5, 0xD66D, 0x73F6, 0xD66B, 0x73F8, 0xD66C, 0x73FA, 0xD673, 0x73FC, 0xD674, 0x73FD, 0xD670, 0x73FE, 0xB27B, + 0x73FF, 0xD675, 0x7400, 0xD672, 0x7401, 0xD66F, 0x7403, 0xB279, 0x7404, 0xD66E, 0x7405, 0xB277, 0x7406, 0xB27A, 0x7407, 0xD671, + 0x7408, 0xD679, 0x7409, 0xAF5B, 0x740A, 0xB278, 0x740B, 0xD677, 0x740C, 0xD676, 0x740D, 0xB27C, 0x7416, 0xDA7E, 0x741A, 0xDAA1, + 0x741B, 0xB560, 0x741D, 0xDAA7, 0x7420, 0xDAA9, 0x7421, 0xDAA2, 0x7422, 0xB55A, 0x7423, 0xDAA6, 0x7424, 0xDAA5, 0x7425, 0xB55B, + 0x7426, 0xB561, 0x7428, 0xB562, 0x7429, 0xDAA8, 0x742A, 0xB558, 0x742B, 0xDA7D, 0x742C, 0xDA7B, 0x742D, 0xDAA3, 0x742E, 0xDA7A, + 0x742F, 0xB55F, 0x7430, 0xDA7C, 0x7431, 0xDAA4, 0x7432, 0xDAAA, 0x7433, 0xB559, 0x7434, 0xB55E, 0x7435, 0xB55C, 0x7436, 0xB55D, + 0x743A, 0xB557, 0x743F, 0xB7E9, 0x7440, 0xDEB7, 0x7441, 0xB7E8, 0x7442, 0xDEBB, 0x7444, 0xDEB1, 0x7446, 0xDEBC, 0x744A, 0xDEB2, + 0x744B, 0xDEB3, 0x744D, 0xDEBD, 0x744E, 0xDEBA, 0x744F, 0xDEB8, 0x7450, 0xDEB9, 0x7451, 0xDEB5, 0x7452, 0xDEB4, 0x7454, 0xDEBE, + 0x7455, 0xB7E5, 0x7457, 0xDEB6, 0x7459, 0xB7EA, 0x745A, 0xB7E4, 0x745B, 0xB7EB, 0x745C, 0xB7EC, 0x745E, 0xB7E7, 0x745F, 0xB7E6, + 0x7462, 0xE2CE, 0x7463, 0xBABE, 0x7464, 0xBABD, 0x7467, 0xE2D3, 0x7469, 0xBCFC, 0x746A, 0xBABF, 0x746D, 0xBAC1, 0x746E, 0xE2D4, + 0x746F, 0xB7E3, 0x7470, 0xBAC0, 0x7471, 0xE2D0, 0x7472, 0xE2D2, 0x7473, 0xE2CF, 0x7475, 0xE2D1, 0x7479, 0xE6AB, 0x747C, 0xE6AA, + 0x747D, 0xE6A7, 0x747E, 0xBD40, 0x747F, 0xEA62, 0x7480, 0xBD41, 0x7481, 0xE6A6, 0x7483, 0xBCFE, 0x7485, 0xE6A8, 0x7486, 0xE6A5, + 0x7487, 0xE6A2, 0x7488, 0xE6A9, 0x7489, 0xE6A3, 0x748A, 0xE6A4, 0x748B, 0xBCFD, 0x7490, 0xED69, 0x7492, 0xEA66, 0x7494, 0xEA65, + 0x7495, 0xEA67, 0x7497, 0xED66, 0x7498, 0xBF5A, 0x749A, 0xEA63, 0x749C, 0xBF58, 0x749E, 0xBF5C, 0x749F, 0xBF5B, 0x74A0, 0xEA64, + 0x74A1, 0xEA68, 0x74A3, 0xBF59, 0x74A5, 0xED6D, 0x74A6, 0xC0F5, 0x74A7, 0xC27A, 0x74A8, 0xC0F6, 0x74A9, 0xC0F3, 0x74AA, 0xED6A, + 0x74AB, 0xED68, 0x74AD, 0xED6B, 0x74AF, 0xED6E, 0x74B0, 0xC0F4, 0x74B1, 0xED6C, 0x74B2, 0xED67, 0x74B5, 0xF042, 0x74B6, 0xF045, + 0x74B7, 0xF275, 0x74B8, 0xF040, 0x74BA, 0xF46F, 0x74BB, 0xF046, 0x74BD, 0xC3A2, 0x74BE, 0xF044, 0x74BF, 0xC27B, 0x74C0, 0xF041, + 0x74C1, 0xF043, 0x74C2, 0xF047, 0x74C3, 0xF276, 0x74C5, 0xF274, 0x74CA, 0xC3A3, 0x74CB, 0xF273, 0x74CF, 0xC46E, 0x74D4, 0xC4ED, + 0x74D5, 0xF6F1, 0x74D6, 0xC4EC, 0x74D7, 0xF6F3, 0x74D8, 0xF6F0, 0x74D9, 0xF6F2, 0x74DA, 0xC5D0, 0x74DB, 0xF8B2, 0x74DC, 0xA5CA, + 0x74DD, 0xCD6E, 0x74DE, 0xD2BC, 0x74DF, 0xD2BD, 0x74E0, 0xB27D, 0x74E1, 0xDEBF, 0x74E2, 0xBF5D, 0x74E3, 0xC3A4, 0x74E4, 0xC57B, + 0x74E5, 0xF8B3, 0x74E6, 0xA5CB, 0x74E8, 0xCD6F, 0x74E9, 0xA260, 0x74EC, 0xCFD7, 0x74EE, 0xCFD8, 0x74F4, 0xD2BE, 0x74F5, 0xD2BF, + 0x74F6, 0xB27E, 0x74F7, 0xB2A1, 0x74FB, 0xDAAB, 0x74FD, 0xDEC2, 0x74FE, 0xDEC1, 0x74FF, 0xDEC0, 0x7500, 0xE2D5, 0x7502, 0xE2D6, + 0x7503, 0xE2D7, 0x7504, 0xBAC2, 0x7507, 0xE6AD, 0x7508, 0xE6AC, 0x750B, 0xEA69, 0x750C, 0xBF5E, 0x750D, 0xBF5F, 0x750F, 0xED72, + 0x7510, 0xED6F, 0x7511, 0xED70, 0x7512, 0xED71, 0x7513, 0xF049, 0x7514, 0xF048, 0x7515, 0xC27C, 0x7516, 0xF277, 0x7517, 0xF5DE, + 0x7518, 0xA5CC, 0x751A, 0xACC6, 0x751C, 0xB2A2, 0x751D, 0xDEC3, 0x751F, 0xA5CD, 0x7521, 0xD2C0, 0x7522, 0xB2A3, 0x7525, 0xB563, + 0x7526, 0xB564, 0x7528, 0xA5CE, 0x7529, 0xA5CF, 0x752A, 0xCA46, 0x752B, 0xA86A, 0x752C, 0xA869, 0x752D, 0xACC7, 0x752E, 0xCFD9, + 0x752F, 0xDAAC, 0x7530, 0xA5D0, 0x7531, 0xA5D1, 0x7532, 0xA5D2, 0x7533, 0xA5D3, 0x7537, 0xA86B, 0x7538, 0xA86C, 0x7539, 0xCB6E, + 0x753A, 0xCB6D, 0x753D, 0xAAB6, 0x753E, 0xCD72, 0x753F, 0xCD70, 0x7540, 0xCD71, 0x7547, 0xCFDA, 0x7548, 0xCFDB, 0x754B, 0xACCB, + 0x754C, 0xACC9, 0x754E, 0xACCA, 0x754F, 0xACC8, 0x7554, 0xAF60, 0x7559, 0xAF64, 0x755A, 0xAF63, 0x755B, 0xD2C1, 0x755C, 0xAF62, + 0x755D, 0xAF61, 0x755F, 0xD2C2, 0x7562, 0xB2A6, 0x7563, 0xD67B, 0x7564, 0xD67A, 0x7565, 0xB2A4, 0x7566, 0xB2A5, 0x756A, 0xB566, + 0x756B, 0xB565, 0x756C, 0xDAAE, 0x756F, 0xDAAD, 0x7570, 0xB2A7, 0x7576, 0xB7ED, 0x7577, 0xDEC5, 0x7578, 0xB7EE, 0x7579, 0xDEC4, + 0x757D, 0xE2D8, 0x757E, 0xE6AE, 0x757F, 0xBD42, 0x7580, 0xEA6A, 0x7584, 0xED73, 0x7586, 0xC3A6, 0x7587, 0xC3A5, 0x758A, 0xC57C, + 0x758B, 0xA5D4, 0x758C, 0xCD73, 0x758F, 0xB2A8, 0x7590, 0xE2D9, 0x7591, 0xBAC3, 0x7594, 0xCB6F, 0x7595, 0xCB70, 0x7598, 0xCD74, + 0x7599, 0xAAB8, 0x759A, 0xAAB9, 0x759D, 0xAAB7, 0x75A2, 0xACCF, 0x75A3, 0xACD0, 0x75A4, 0xACCD, 0x75A5, 0xACCE, 0x75A7, 0xCFDC, + 0x75AA, 0xCFDD, 0x75AB, 0xACCC, 0x75B0, 0xD2C3, 0x75B2, 0xAF68, 0x75B3, 0xAF69, 0x75B5, 0xB2AB, 0x75B6, 0xD2C9, 0x75B8, 0xAF6E, + 0x75B9, 0xAF6C, 0x75BA, 0xD2CA, 0x75BB, 0xD2C5, 0x75BC, 0xAF6B, 0x75BD, 0xAF6A, 0x75BE, 0xAF65, 0x75BF, 0xD2C8, 0x75C0, 0xD2C7, + 0x75C1, 0xD2C4, 0x75C2, 0xAF6D, 0x75C4, 0xD2C6, 0x75C5, 0xAF66, 0x75C7, 0xAF67, 0x75CA, 0xB2AC, 0x75CB, 0xD6A1, 0x75CC, 0xD6A2, + 0x75CD, 0xB2AD, 0x75CE, 0xD67C, 0x75CF, 0xD67E, 0x75D0, 0xD6A4, 0x75D1, 0xD6A3, 0x75D2, 0xD67D, 0x75D4, 0xB2A9, 0x75D5, 0xB2AA, + 0x75D7, 0xDAB6, 0x75D8, 0xB56B, 0x75D9, 0xB56A, 0x75DA, 0xDAB0, 0x75DB, 0xB568, 0x75DD, 0xDAB3, 0x75DE, 0xB56C, 0x75DF, 0xDAB4, + 0x75E0, 0xB56D, 0x75E1, 0xDAB1, 0x75E2, 0xB567, 0x75E3, 0xB569, 0x75E4, 0xDAB5, 0x75E6, 0xDAB2, 0x75E7, 0xDAAF, 0x75ED, 0xDED2, + 0x75EF, 0xDEC7, 0x75F0, 0xB7F0, 0x75F1, 0xB7F3, 0x75F2, 0xB7F2, 0x75F3, 0xB7F7, 0x75F4, 0xB7F6, 0x75F5, 0xDED3, 0x75F6, 0xDED1, + 0x75F7, 0xDECA, 0x75F8, 0xDECE, 0x75F9, 0xDECD, 0x75FA, 0xB7F4, 0x75FB, 0xDED0, 0x75FC, 0xDECC, 0x75FD, 0xDED4, 0x75FE, 0xDECB, + 0x75FF, 0xB7F5, 0x7600, 0xB7EF, 0x7601, 0xB7F1, 0x7603, 0xDEC9, 0x7608, 0xE2DB, 0x7609, 0xBAC7, 0x760A, 0xE2DF, 0x760B, 0xBAC6, + 0x760C, 0xE2DC, 0x760D, 0xBAC5, 0x760F, 0xDEC8, 0x7610, 0xDECF, 0x7611, 0xE2DE, 0x7613, 0xBAC8, 0x7614, 0xE2E0, 0x7615, 0xE2DD, + 0x7616, 0xE2DA, 0x7619, 0xE6B1, 0x761A, 0xE6B5, 0x761B, 0xE6B7, 0x761C, 0xE6B3, 0x761D, 0xE6B2, 0x761E, 0xE6B0, 0x761F, 0xBD45, + 0x7620, 0xBD43, 0x7621, 0xBD48, 0x7622, 0xBD49, 0x7623, 0xE6B4, 0x7624, 0xBD46, 0x7625, 0xE6AF, 0x7626, 0xBD47, 0x7627, 0xBAC4, + 0x7628, 0xE6B6, 0x7629, 0xBD44, 0x762D, 0xEA6C, 0x762F, 0xEA6B, 0x7630, 0xEA73, 0x7631, 0xEA6D, 0x7632, 0xEA72, 0x7633, 0xEA6F, + 0x7634, 0xBF60, 0x7635, 0xEA71, 0x7638, 0xBF61, 0x763A, 0xBF62, 0x763C, 0xEA70, 0x763D, 0xEA6E, 0x7642, 0xC0F8, 0x7643, 0xED74, + 0x7646, 0xC0F7, 0x7647, 0xED77, 0x7648, 0xED75, 0x7649, 0xED76, 0x764C, 0xC0F9, 0x7650, 0xF04D, 0x7652, 0xC2A1, 0x7653, 0xF04E, + 0x7656, 0xC27D, 0x7657, 0xF04F, 0x7658, 0xC27E, 0x7659, 0xF04C, 0x765A, 0xF050, 0x765C, 0xF04A, 0x765F, 0xC3A7, 0x7660, 0xF278, + 0x7661, 0xC3A8, 0x7662, 0xC46F, 0x7664, 0xF04B, 0x7665, 0xC470, 0x7669, 0xC4EE, 0x766A, 0xF5DF, 0x766C, 0xC57E, 0x766D, 0xF6F4, + 0x766E, 0xC57D, 0x7670, 0xF7EA, 0x7671, 0xC5F5, 0x7672, 0xC5F6, 0x7675, 0xF9CC, 0x7678, 0xACD1, 0x7679, 0xCFDE, 0x767B, 0xB56E, + 0x767C, 0xB56F, 0x767D, 0xA5D5, 0x767E, 0xA6CA, 0x767F, 0xCA47, 0x7681, 0xCB71, 0x7682, 0xA86D, 0x7684, 0xAABA, 0x7686, 0xACD2, + 0x7687, 0xACD3, 0x7688, 0xACD4, 0x7689, 0xD6A6, 0x768A, 0xD2CB, 0x768B, 0xAF6F, 0x768E, 0xB2AE, 0x768F, 0xD6A5, 0x7692, 0xDAB8, + 0x7693, 0xB571, 0x7695, 0xDAB7, 0x7696, 0xB570, 0x7699, 0xDED5, 0x769A, 0xBD4A, 0x769B, 0xE6BB, 0x769C, 0xE6B8, 0x769D, 0xE6B9, + 0x769E, 0xE6BA, 0x76A4, 0xED78, 0x76A6, 0xF051, 0x76AA, 0xF471, 0x76AB, 0xF470, 0x76AD, 0xF6F5, 0x76AE, 0xA5D6, 0x76AF, 0xCD75, + 0x76B0, 0xAF70, 0x76B4, 0xB572, 0x76B5, 0xDED6, 0x76B8, 0xE2E1, 0x76BA, 0xBD4B, 0x76BB, 0xEA74, 0x76BD, 0xF052, 0x76BE, 0xF472, + 0x76BF, 0xA5D7, 0x76C2, 0xAABB, 0x76C3, 0xACD7, 0x76C4, 0xCFDF, 0x76C5, 0xACD8, 0x76C6, 0xACD6, 0x76C8, 0xACD5, 0x76C9, 0xD2CC, + 0x76CA, 0xAF71, 0x76CD, 0xAF72, 0x76CE, 0xAF73, 0x76D2, 0xB2B0, 0x76D3, 0xD6A7, 0x76D4, 0xB2AF, 0x76DA, 0xDAB9, 0x76DB, 0xB2B1, + 0x76DC, 0xB573, 0x76DD, 0xDED7, 0x76DE, 0xB7F8, 0x76DF, 0xB7F9, 0x76E1, 0xBAC9, 0x76E3, 0xBACA, 0x76E4, 0xBD4C, 0x76E5, 0xBF64, + 0x76E6, 0xEA75, 0x76E7, 0xBF63, 0x76E9, 0xED79, 0x76EA, 0xC0FA, 0x76EC, 0xF053, 0x76ED, 0xF473, 0x76EE, 0xA5D8, 0x76EF, 0xA86E, + 0x76F0, 0xCD78, 0x76F1, 0xCD77, 0x76F2, 0xAABC, 0x76F3, 0xCD76, 0x76F4, 0xAABD, 0x76F5, 0xCD79, 0x76F7, 0xCFE5, 0x76F8, 0xACDB, + 0x76F9, 0xACDA, 0x76FA, 0xCFE7, 0x76FB, 0xCFE6, 0x76FC, 0xACDF, 0x76FE, 0xACDE, 0x7701, 0xACD9, 0x7703, 0xCFE1, 0x7704, 0xCFE2, + 0x7705, 0xCFE3, 0x7707, 0xACE0, 0x7708, 0xCFE0, 0x7709, 0xACDC, 0x770A, 0xCFE4, 0x770B, 0xACDD, 0x7710, 0xD2CF, 0x7711, 0xD2D3, + 0x7712, 0xD2D1, 0x7713, 0xD2D0, 0x7715, 0xD2D4, 0x7719, 0xD2D5, 0x771A, 0xD2D6, 0x771B, 0xD2CE, 0x771D, 0xD2CD, 0x771F, 0xAF75, + 0x7720, 0xAF76, 0x7722, 0xD2D7, 0x7723, 0xD2D2, 0x7725, 0xD6B0, 0x7727, 0xD2D8, 0x7728, 0xAF77, 0x7729, 0xAF74, 0x772D, 0xD6AA, + 0x772F, 0xD6A9, 0x7731, 0xD6AB, 0x7732, 0xD6AC, 0x7733, 0xD6AE, 0x7734, 0xD6AD, 0x7735, 0xD6B2, 0x7736, 0xB2B5, 0x7737, 0xB2B2, + 0x7738, 0xB2B6, 0x7739, 0xD6A8, 0x773A, 0xB2B7, 0x773B, 0xD6B1, 0x773C, 0xB2B4, 0x773D, 0xD6AF, 0x773E, 0xB2B3, 0x7744, 0xDABC, + 0x7745, 0xDABE, 0x7746, 0xDABA, 0x7747, 0xDABB, 0x774A, 0xDABF, 0x774B, 0xDAC1, 0x774C, 0xDAC2, 0x774D, 0xDABD, 0x774E, 0xDAC0, + 0x774F, 0xB574, 0x7752, 0xDEDB, 0x7754, 0xDEE0, 0x7755, 0xDED8, 0x7756, 0xDEDC, 0x7759, 0xDEE1, 0x775A, 0xDEDD, 0x775B, 0xB7FA, + 0x775C, 0xB843, 0x775E, 0xB7FD, 0x775F, 0xDED9, 0x7760, 0xDEDA, 0x7761, 0xBACE, 0x7762, 0xB846, 0x7763, 0xB7FE, 0x7765, 0xB844, + 0x7766, 0xB7FC, 0x7767, 0xDEDF, 0x7768, 0xB845, 0x7769, 0xDEDE, 0x776A, 0xB841, 0x776B, 0xB7FB, 0x776C, 0xB842, 0x776D, 0xDEE2, + 0x776E, 0xE2E6, 0x776F, 0xE2E8, 0x7779, 0xB840, 0x777C, 0xE2E3, 0x777D, 0xBACC, 0x777E, 0xE2E9, 0x777F, 0xBACD, 0x7780, 0xE2E7, + 0x7781, 0xE2E2, 0x7782, 0xE2E5, 0x7783, 0xE2EA, 0x7784, 0xBACB, 0x7785, 0xE2E4, 0x7787, 0xBD4E, 0x7788, 0xE6BF, 0x7789, 0xE6BE, + 0x778B, 0xBD51, 0x778C, 0xBD4F, 0x778D, 0xE6BC, 0x778E, 0xBD4D, 0x778F, 0xE6BD, 0x7791, 0xBD50, 0x7795, 0xEA7D, 0x7797, 0xEAA1, + 0x7799, 0xEA7E, 0x779A, 0xEA76, 0x779B, 0xEA7A, 0x779C, 0xEA79, 0x779D, 0xEA77, 0x779E, 0xBF66, 0x779F, 0xBF67, 0x77A0, 0xBF65, + 0x77A1, 0xEA78, 0x77A2, 0xEA7B, 0x77A3, 0xEA7C, 0x77A5, 0xBF68, 0x77A7, 0xC140, 0x77A8, 0xEDA3, 0x77AA, 0xC0FC, 0x77AB, 0xED7B, + 0x77AC, 0xC0FE, 0x77AD, 0xC141, 0x77B0, 0xC0FD, 0x77B1, 0xEDA2, 0x77B2, 0xED7C, 0x77B3, 0xC0FB, 0x77B4, 0xEDA1, 0x77B5, 0xED7A, + 0x77B6, 0xED7E, 0x77B7, 0xED7D, 0x77BA, 0xF055, 0x77BB, 0xC2A4, 0x77BC, 0xC2A5, 0x77BD, 0xC2A2, 0x77BF, 0xC2A3, 0x77C2, 0xF054, + 0x77C4, 0xF27B, 0x77C7, 0xC3A9, 0x77C9, 0xF279, 0x77CA, 0xF27A, 0x77CC, 0xF474, 0x77CD, 0xF477, 0x77CE, 0xF475, 0x77CF, 0xF476, + 0x77D0, 0xF5E0, 0x77D3, 0xC4EF, 0x77D4, 0xF7EB, 0x77D5, 0xF8B4, 0x77D7, 0xC5F7, 0x77D8, 0xF8F8, 0x77D9, 0xF8F9, 0x77DA, 0xC666, + 0x77DB, 0xA5D9, 0x77DC, 0xACE1, 0x77DE, 0xDAC3, 0x77E0, 0xDEE3, 0x77E2, 0xA5DA, 0x77E3, 0xA86F, 0x77E5, 0xAABE, 0x77E7, 0xCFE8, + 0x77E8, 0xCFE9, 0x77E9, 0xAF78, 0x77EC, 0xDAC4, 0x77ED, 0xB575, 0x77EE, 0xB847, 0x77EF, 0xC142, 0x77F0, 0xEDA4, 0x77F1, 0xF27C, + 0x77F2, 0xF478, 0x77F3, 0xA5DB, 0x77F7, 0xCDA1, 0x77F8, 0xCD7A, 0x77F9, 0xCD7C, 0x77FA, 0xCD7E, 0x77FB, 0xCD7D, 0x77FC, 0xCD7B, + 0x77FD, 0xAABF, 0x7802, 0xACE2, 0x7803, 0xCFF2, 0x7805, 0xCFED, 0x7806, 0xCFEA, 0x7809, 0xCFF1, 0x780C, 0xACE4, 0x780D, 0xACE5, + 0x780E, 0xCFF0, 0x780F, 0xCFEF, 0x7810, 0xCFEE, 0x7811, 0xCFEB, 0x7812, 0xCFEC, 0x7813, 0xCFF3, 0x7814, 0xACE3, 0x781D, 0xAF7C, + 0x781F, 0xAFA4, 0x7820, 0xAFA3, 0x7821, 0xD2E1, 0x7822, 0xD2DB, 0x7823, 0xD2D9, 0x7825, 0xAFA1, 0x7826, 0xD6B9, 0x7827, 0xAF7A, + 0x7828, 0xD2DE, 0x7829, 0xD2E2, 0x782A, 0xD2E4, 0x782B, 0xD2E0, 0x782C, 0xD2DA, 0x782D, 0xAFA2, 0x782E, 0xD2DF, 0x782F, 0xD2DD, + 0x7830, 0xAF79, 0x7831, 0xD2E5, 0x7832, 0xAFA5, 0x7833, 0xD2E3, 0x7834, 0xAF7D, 0x7835, 0xD2DC, 0x7837, 0xAF7E, 0x7838, 0xAF7B, + 0x7843, 0xB2B9, 0x7845, 0xD6BA, 0x7848, 0xD6B3, 0x7849, 0xD6B5, 0x784A, 0xD6B7, 0x784C, 0xD6B8, 0x784D, 0xD6B6, 0x784E, 0xB2BA, + 0x7850, 0xD6BB, 0x7852, 0xD6B4, 0x785C, 0xDAC8, 0x785D, 0xB576, 0x785E, 0xDAD0, 0x7860, 0xDAC5, 0x7862, 0xDAD1, 0x7864, 0xDAC6, + 0x7865, 0xDAC7, 0x7868, 0xDACF, 0x7869, 0xDACE, 0x786A, 0xDACB, 0x786B, 0xB2B8, 0x786C, 0xB577, 0x786D, 0xDAC9, 0x786E, 0xDACC, + 0x786F, 0xB578, 0x7870, 0xDACD, 0x7871, 0xDACA, 0x7879, 0xDEEE, 0x787B, 0xDEF2, 0x787C, 0xB84E, 0x787E, 0xE2F0, 0x787F, 0xB851, + 0x7880, 0xDEF0, 0x7881, 0xF9D6, 0x7883, 0xDEED, 0x7884, 0xDEE8, 0x7885, 0xDEEA, 0x7886, 0xDEEB, 0x7887, 0xDEE4, 0x7889, 0xB84D, + 0x788C, 0xB84C, 0x788E, 0xB848, 0x788F, 0xDEE7, 0x7891, 0xB84F, 0x7893, 0xB850, 0x7894, 0xDEE6, 0x7895, 0xDEE9, 0x7896, 0xDEF1, + 0x7897, 0xB84A, 0x7898, 0xB84B, 0x7899, 0xDEEF, 0x789A, 0xDEE5, 0x789E, 0xE2F2, 0x789F, 0xBAD0, 0x78A0, 0xE2F4, 0x78A1, 0xDEEC, + 0x78A2, 0xE2F6, 0x78A3, 0xBAD4, 0x78A4, 0xE2F7, 0x78A5, 0xE2F3, 0x78A7, 0xBAD1, 0x78A8, 0xE2EF, 0x78A9, 0xBAD3, 0x78AA, 0xE2EC, + 0x78AB, 0xE2F1, 0x78AC, 0xE2F5, 0x78AD, 0xE2EE, 0x78B0, 0xB849, 0x78B2, 0xE2EB, 0x78B3, 0xBAD2, 0x78B4, 0xE2ED, 0x78BA, 0xBD54, + 0x78BB, 0xE6C1, 0x78BC, 0xBD58, 0x78BE, 0xBD56, 0x78C1, 0xBACF, 0x78C3, 0xE6C8, 0x78C4, 0xE6C9, 0x78C5, 0xBD53, 0x78C8, 0xE6C7, + 0x78C9, 0xE6CA, 0x78CA, 0xBD55, 0x78CB, 0xBD52, 0x78CC, 0xE6C3, 0x78CD, 0xE6C0, 0x78CE, 0xE6C5, 0x78CF, 0xE6C2, 0x78D0, 0xBD59, + 0x78D1, 0xE6C4, 0x78D4, 0xE6C6, 0x78D5, 0xBD57, 0x78DA, 0xBF6A, 0x78DB, 0xEAA8, 0x78DD, 0xEAA2, 0x78DE, 0xEAA6, 0x78DF, 0xEAAC, + 0x78E0, 0xEAAD, 0x78E1, 0xEAA9, 0x78E2, 0xEAAA, 0x78E3, 0xEAA7, 0x78E5, 0xEAA4, 0x78E7, 0xBF6C, 0x78E8, 0xBF69, 0x78E9, 0xEAA3, + 0x78EA, 0xEAA5, 0x78EC, 0xBF6B, 0x78ED, 0xEAAB, 0x78EF, 0xC146, 0x78F2, 0xEDAA, 0x78F3, 0xEDA5, 0x78F4, 0xC145, 0x78F7, 0xC143, + 0x78F9, 0xEDAC, 0x78FA, 0xC144, 0x78FB, 0xEDA8, 0x78FC, 0xEDA9, 0x78FD, 0xEDA6, 0x78FE, 0xEDAD, 0x78FF, 0xF056, 0x7901, 0xC147, + 0x7902, 0xEDA7, 0x7904, 0xEDAE, 0x7905, 0xEDAB, 0x7909, 0xF05A, 0x790C, 0xF057, 0x790E, 0xC2A6, 0x7910, 0xF05B, 0x7911, 0xF05D, + 0x7912, 0xF05C, 0x7913, 0xF058, 0x7914, 0xF059, 0x7917, 0xF2A3, 0x7919, 0xC3AA, 0x791B, 0xF27E, 0x791C, 0xF2A2, 0x791D, 0xF27D, + 0x791E, 0xF2A4, 0x7921, 0xF2A1, 0x7923, 0xF47A, 0x7924, 0xF47D, 0x7925, 0xF479, 0x7926, 0xC471, 0x7927, 0xF47B, 0x7928, 0xF47C, + 0x7929, 0xF47E, 0x792A, 0xC472, 0x792B, 0xC474, 0x792C, 0xC473, 0x792D, 0xF5E1, 0x792F, 0xF5E3, 0x7931, 0xF5E2, 0x7935, 0xF6F6, + 0x7938, 0xF8B5, 0x7939, 0xF8FA, 0x793A, 0xA5DC, 0x793D, 0xCB72, 0x793E, 0xAAC0, 0x793F, 0xCDA3, 0x7940, 0xAAC1, 0x7941, 0xAAC2, + 0x7942, 0xCDA2, 0x7944, 0xCFF8, 0x7945, 0xCFF7, 0x7946, 0xACE6, 0x7947, 0xACE9, 0x7948, 0xACE8, 0x7949, 0xACE7, 0x794A, 0xCFF4, + 0x794B, 0xCFF6, 0x794C, 0xCFF5, 0x794F, 0xD2E8, 0x7950, 0xAFA7, 0x7951, 0xD2EC, 0x7952, 0xD2EB, 0x7953, 0xD2EA, 0x7954, 0xD2E6, + 0x7955, 0xAFA6, 0x7956, 0xAFAA, 0x7957, 0xAFAD, 0x795A, 0xAFAE, 0x795B, 0xD2E7, 0x795C, 0xD2E9, 0x795D, 0xAFAC, 0x795E, 0xAFAB, + 0x795F, 0xAFA9, 0x7960, 0xAFA8, 0x7961, 0xD6C2, 0x7963, 0xD6C0, 0x7964, 0xD6BC, 0x7965, 0xB2BB, 0x7967, 0xD6BD, 0x7968, 0xB2BC, + 0x7969, 0xD6BE, 0x796A, 0xD6BF, 0x796B, 0xD6C1, 0x796D, 0xB2BD, 0x7970, 0xDAD5, 0x7972, 0xDAD4, 0x7973, 0xDAD3, 0x7974, 0xDAD2, + 0x7979, 0xDEF6, 0x797A, 0xB852, 0x797C, 0xDEF3, 0x797D, 0xDEF5, 0x797F, 0xB853, 0x7981, 0xB854, 0x7982, 0xDEF4, 0x7988, 0xE341, + 0x798A, 0xE2F9, 0x798B, 0xE2FA, 0x798D, 0xBAD7, 0x798E, 0xBAD5, 0x798F, 0xBAD6, 0x7990, 0xE343, 0x7992, 0xE342, 0x7993, 0xE2FE, + 0x7994, 0xE2FD, 0x7995, 0xE2FC, 0x7996, 0xE2FB, 0x7997, 0xE340, 0x7998, 0xE2F8, 0x799A, 0xE6CB, 0x799B, 0xE6D0, 0x799C, 0xE6CE, + 0x79A0, 0xE6CD, 0x79A1, 0xE6CC, 0x79A2, 0xE6CF, 0x79A4, 0xEAAE, 0x79A6, 0xBF6D, 0x79A7, 0xC148, 0x79A8, 0xEDB0, 0x79AA, 0xC149, + 0x79AB, 0xEDAF, 0x79AC, 0xF05F, 0x79AD, 0xF05E, 0x79AE, 0xC2A7, 0x79B0, 0xF2A5, 0x79B1, 0xC3AB, 0x79B2, 0xF4A1, 0x79B3, 0xC5A1, + 0x79B4, 0xF6F7, 0x79B6, 0xF8B7, 0x79B7, 0xF8B6, 0x79B8, 0xC9A8, 0x79B9, 0xACEA, 0x79BA, 0xACEB, 0x79BB, 0xD6C3, 0x79BD, 0xB856, + 0x79BE, 0xA5DD, 0x79BF, 0xA872, 0x79C0, 0xA871, 0x79C1, 0xA870, 0x79C5, 0xCDA4, 0x79C8, 0xAAC4, 0x79C9, 0xAAC3, 0x79CB, 0xACEE, + 0x79CD, 0xCFFA, 0x79CE, 0xCFFD, 0x79CF, 0xCFFB, 0x79D1, 0xACEC, 0x79D2, 0xACED, 0x79D5, 0xCFF9, 0x79D6, 0xCFFC, 0x79D8, 0xAFB5, + 0x79DC, 0xD2F3, 0x79DD, 0xD2F5, 0x79DE, 0xD2F4, 0x79DF, 0xAFB2, 0x79E0, 0xD2EF, 0x79E3, 0xAFB0, 0x79E4, 0xAFAF, 0x79E6, 0xAFB3, + 0x79E7, 0xAFB1, 0x79E9, 0xAFB4, 0x79EA, 0xD2F2, 0x79EB, 0xD2ED, 0x79EC, 0xD2EE, 0x79ED, 0xD2F1, 0x79EE, 0xD2F0, 0x79F6, 0xD6C6, + 0x79F7, 0xD6C7, 0x79F8, 0xD6C5, 0x79FA, 0xD6C4, 0x79FB, 0xB2BE, 0x7A00, 0xB57D, 0x7A02, 0xDAD6, 0x7A03, 0xDAD8, 0x7A04, 0xDADA, + 0x7A05, 0xB57C, 0x7A08, 0xB57A, 0x7A0A, 0xDAD7, 0x7A0B, 0xB57B, 0x7A0C, 0xDAD9, 0x7A0D, 0xB579, 0x7A10, 0xDF41, 0x7A11, 0xDEF7, + 0x7A12, 0xDEFA, 0x7A13, 0xDEFE, 0x7A14, 0xB85A, 0x7A15, 0xDEFC, 0x7A17, 0xDEFB, 0x7A18, 0xDEF8, 0x7A19, 0xDEF9, 0x7A1A, 0xB858, + 0x7A1B, 0xDF40, 0x7A1C, 0xB857, 0x7A1E, 0xB85C, 0x7A1F, 0xB85B, 0x7A20, 0xB859, 0x7A22, 0xDEFD, 0x7A26, 0xE349, 0x7A28, 0xE348, + 0x7A2B, 0xE344, 0x7A2E, 0xBAD8, 0x7A2F, 0xE347, 0x7A30, 0xE346, 0x7A31, 0xBAD9, 0x7A37, 0xBD5E, 0x7A39, 0xE6D2, 0x7A3B, 0xBD5F, + 0x7A3C, 0xBD5B, 0x7A3D, 0xBD5D, 0x7A3F, 0xBD5A, 0x7A40, 0xBD5C, 0x7A44, 0xEAAF, 0x7A46, 0xBF70, 0x7A47, 0xEAB1, 0x7A48, 0xEAB0, + 0x7A4A, 0xE345, 0x7A4B, 0xBF72, 0x7A4C, 0xBF71, 0x7A4D, 0xBF6E, 0x7A4E, 0xBF6F, 0x7A54, 0xEDB5, 0x7A56, 0xEDB3, 0x7A57, 0xC14A, + 0x7A58, 0xEDB4, 0x7A5A, 0xEDB6, 0x7A5B, 0xEDB2, 0x7A5C, 0xEDB1, 0x7A5F, 0xF060, 0x7A60, 0xC2AA, 0x7A61, 0xC2A8, 0x7A62, 0xC2A9, + 0x7A67, 0xF2A6, 0x7A68, 0xF2A7, 0x7A69, 0xC3AD, 0x7A6B, 0xC3AC, 0x7A6C, 0xF4A3, 0x7A6D, 0xF4A4, 0x7A6E, 0xF4A2, 0x7A70, 0xF6F8, + 0x7A71, 0xF6F9, 0x7A74, 0xA5DE, 0x7A75, 0xCA48, 0x7A76, 0xA873, 0x7A78, 0xCDA5, 0x7A79, 0xAAC6, 0x7A7A, 0xAAC5, 0x7A7B, 0xCDA6, + 0x7A7E, 0xD040, 0x7A7F, 0xACEF, 0x7A80, 0xCFFE, 0x7A81, 0xACF0, 0x7A84, 0xAFB6, 0x7A85, 0xD2F8, 0x7A86, 0xD2F6, 0x7A87, 0xD2FC, + 0x7A88, 0xAFB7, 0x7A89, 0xD2F7, 0x7A8A, 0xD2FB, 0x7A8B, 0xD2F9, 0x7A8C, 0xD2FA, 0x7A8F, 0xD6C8, 0x7A90, 0xD6CA, 0x7A92, 0xB2BF, + 0x7A94, 0xD6C9, 0x7A95, 0xB2C0, 0x7A96, 0xB5A2, 0x7A97, 0xB5A1, 0x7A98, 0xB57E, 0x7A99, 0xDADB, 0x7A9E, 0xDF44, 0x7A9F, 0xB85D, + 0x7AA0, 0xB85E, 0x7AA2, 0xDF43, 0x7AA3, 0xDF42, 0x7AA8, 0xE34A, 0x7AA9, 0xBADB, 0x7AAA, 0xBADA, 0x7AAB, 0xE34B, 0x7AAC, 0xE34C, + 0x7AAE, 0xBD61, 0x7AAF, 0xBD60, 0x7AB1, 0xEAB5, 0x7AB2, 0xE6D3, 0x7AB3, 0xE6D5, 0x7AB4, 0xE6D4, 0x7AB5, 0xEAB4, 0x7AB6, 0xEAB2, + 0x7AB7, 0xEAB6, 0x7AB8, 0xEAB3, 0x7ABA, 0xBF73, 0x7ABE, 0xEDB7, 0x7ABF, 0xC14B, 0x7AC0, 0xEDB8, 0x7AC1, 0xEDB9, 0x7AC4, 0xC2AB, + 0x7AC5, 0xC2AC, 0x7AC7, 0xC475, 0x7ACA, 0xC5D1, 0x7ACB, 0xA5DF, 0x7AD1, 0xD041, 0x7AD8, 0xD2FD, 0x7AD9, 0xAFB8, 0x7ADF, 0xB3BA, + 0x7AE0, 0xB3B9, 0x7AE3, 0xB5A4, 0x7AE4, 0xDADD, 0x7AE5, 0xB5A3, 0x7AE6, 0xDADC, 0x7AEB, 0xDF45, 0x7AED, 0xBADC, 0x7AEE, 0xE34D, + 0x7AEF, 0xBADD, 0x7AF6, 0xC476, 0x7AF7, 0xF4A5, 0x7AF9, 0xA6CB, 0x7AFA, 0xAAC7, 0x7AFB, 0xCDA7, 0x7AFD, 0xACF2, 0x7AFF, 0xACF1, + 0x7B00, 0xD042, 0x7B01, 0xD043, 0x7B04, 0xD340, 0x7B05, 0xD342, 0x7B06, 0xAFB9, 0x7B08, 0xD344, 0x7B09, 0xD347, 0x7B0A, 0xD345, + 0x7B0E, 0xD346, 0x7B0F, 0xD343, 0x7B10, 0xD2FE, 0x7B11, 0xAFBA, 0x7B12, 0xD348, 0x7B13, 0xD341, 0x7B18, 0xD6D3, 0x7B19, 0xB2C6, + 0x7B1A, 0xD6DC, 0x7B1B, 0xB2C3, 0x7B1D, 0xD6D5, 0x7B1E, 0xB2C7, 0x7B20, 0xB2C1, 0x7B22, 0xD6D0, 0x7B23, 0xD6DD, 0x7B24, 0xD6D1, + 0x7B25, 0xD6CE, 0x7B26, 0xB2C5, 0x7B28, 0xB2C2, 0x7B2A, 0xD6D4, 0x7B2B, 0xD6D7, 0x7B2C, 0xB2C4, 0x7B2D, 0xD6D8, 0x7B2E, 0xB2C8, + 0x7B2F, 0xD6D9, 0x7B30, 0xD6CF, 0x7B31, 0xD6D6, 0x7B32, 0xD6DA, 0x7B33, 0xD6D2, 0x7B34, 0xD6CD, 0x7B35, 0xD6CB, 0x7B38, 0xD6DB, + 0x7B3B, 0xDADF, 0x7B40, 0xDAE4, 0x7B44, 0xDAE0, 0x7B45, 0xDAE6, 0x7B46, 0xB5A7, 0x7B47, 0xD6CC, 0x7B48, 0xDAE1, 0x7B49, 0xB5A5, + 0x7B4A, 0xDADE, 0x7B4B, 0xB5AC, 0x7B4C, 0xDAE2, 0x7B4D, 0xB5AB, 0x7B4E, 0xDAE3, 0x7B4F, 0xB5AD, 0x7B50, 0xB5A8, 0x7B51, 0xB5AE, + 0x7B52, 0xB5A9, 0x7B54, 0xB5AA, 0x7B56, 0xB5A6, 0x7B58, 0xDAE5, 0x7B60, 0xB861, 0x7B61, 0xDF50, 0x7B63, 0xDF53, 0x7B64, 0xDF47, + 0x7B65, 0xDF4C, 0x7B66, 0xDF46, 0x7B67, 0xB863, 0x7B69, 0xDF4A, 0x7B6D, 0xDF48, 0x7B6E, 0xB862, 0x7B70, 0xDF4F, 0x7B71, 0xDF4E, + 0x7B72, 0xDF4B, 0x7B73, 0xDF4D, 0x7B74, 0xDF49, 0x7B75, 0xBAE1, 0x7B76, 0xDF52, 0x7B77, 0xB85F, 0x7B78, 0xDF51, 0x7B82, 0xE35D, + 0x7B84, 0xBAE8, 0x7B85, 0xE358, 0x7B87, 0xBAE7, 0x7B88, 0xE34E, 0x7B8A, 0xE350, 0x7B8B, 0xBAE0, 0x7B8C, 0xE355, 0x7B8D, 0xE354, + 0x7B8E, 0xE357, 0x7B8F, 0xBAE5, 0x7B90, 0xE352, 0x7B91, 0xE351, 0x7B94, 0xBAE4, 0x7B95, 0xBADF, 0x7B96, 0xE353, 0x7B97, 0xBAE2, + 0x7B98, 0xE359, 0x7B99, 0xE35B, 0x7B9B, 0xE356, 0x7B9C, 0xE34F, 0x7B9D, 0xBAE3, 0x7BA0, 0xBD69, 0x7BA1, 0xBADE, 0x7BA4, 0xE35C, + 0x7BAC, 0xE6D9, 0x7BAD, 0xBD62, 0x7BAF, 0xE6DB, 0x7BB1, 0xBD63, 0x7BB4, 0xBD65, 0x7BB5, 0xE6DE, 0x7BB7, 0xE6D6, 0x7BB8, 0xBAE6, + 0x7BB9, 0xE6DC, 0x7BBE, 0xE6D8, 0x7BC0, 0xB860, 0x7BC1, 0xBD68, 0x7BC4, 0xBD64, 0x7BC6, 0xBD66, 0x7BC7, 0xBD67, 0x7BC9, 0xBF76, + 0x7BCA, 0xE6DD, 0x7BCB, 0xE6D7, 0x7BCC, 0xBD6A, 0x7BCE, 0xE6DA, 0x7BD4, 0xEAC0, 0x7BD5, 0xEABB, 0x7BD8, 0xEAC5, 0x7BD9, 0xBF74, + 0x7BDA, 0xEABD, 0x7BDB, 0xBF78, 0x7BDC, 0xEAC3, 0x7BDD, 0xEABA, 0x7BDE, 0xEAB7, 0x7BDF, 0xEAC6, 0x7BE0, 0xC151, 0x7BE1, 0xBF79, + 0x7BE2, 0xEAC2, 0x7BE3, 0xEAB8, 0x7BE4, 0xBF77, 0x7BE5, 0xEABC, 0x7BE6, 0xBF7B, 0x7BE7, 0xEAB9, 0x7BE8, 0xEABE, 0x7BE9, 0xBF7A, + 0x7BEA, 0xEAC1, 0x7BEB, 0xEAC4, 0x7BF0, 0xEDCB, 0x7BF1, 0xEDCC, 0x7BF2, 0xEDBC, 0x7BF3, 0xEDC3, 0x7BF4, 0xEDC1, 0x7BF7, 0xC14F, + 0x7BF8, 0xEDC8, 0x7BF9, 0xEABF, 0x7BFB, 0xEDBF, 0x7BFD, 0xEDC9, 0x7BFE, 0xC14E, 0x7BFF, 0xEDBE, 0x7C00, 0xEDBD, 0x7C01, 0xEDC7, + 0x7C02, 0xEDC4, 0x7C03, 0xEDC6, 0x7C05, 0xEDBA, 0x7C06, 0xEDCA, 0x7C07, 0xC14C, 0x7C09, 0xEDC5, 0x7C0A, 0xEDCE, 0x7C0B, 0xEDC2, + 0x7C0C, 0xC150, 0x7C0D, 0xC14D, 0x7C0E, 0xEDC0, 0x7C0F, 0xEDBB, 0x7C10, 0xEDCD, 0x7C11, 0xBF75, 0x7C19, 0xF063, 0x7C1C, 0xF061, + 0x7C1D, 0xF067, 0x7C1E, 0xC2B0, 0x7C1F, 0xF065, 0x7C20, 0xF064, 0x7C21, 0xC2B2, 0x7C22, 0xF06A, 0x7C23, 0xC2B1, 0x7C25, 0xF06B, + 0x7C26, 0xF068, 0x7C27, 0xC2AE, 0x7C28, 0xF069, 0x7C29, 0xF062, 0x7C2A, 0xC2AF, 0x7C2B, 0xC2AD, 0x7C2C, 0xF2AB, 0x7C2D, 0xF066, + 0x7C30, 0xF06C, 0x7C33, 0xF2A8, 0x7C37, 0xC3B2, 0x7C38, 0xC3B0, 0x7C39, 0xF2AA, 0x7C3B, 0xF2AC, 0x7C3C, 0xF2A9, 0x7C3D, 0xC3B1, + 0x7C3E, 0xC3AE, 0x7C3F, 0xC3AF, 0x7C40, 0xC3B3, 0x7C43, 0xC478, 0x7C45, 0xF4AA, 0x7C47, 0xF4A9, 0x7C48, 0xF4A7, 0x7C49, 0xF4A6, + 0x7C4A, 0xF4A8, 0x7C4C, 0xC477, 0x7C4D, 0xC479, 0x7C50, 0xC4F0, 0x7C53, 0xF5E5, 0x7C54, 0xF5E4, 0x7C57, 0xF6FA, 0x7C59, 0xF6FC, + 0x7C5A, 0xF6FE, 0x7C5B, 0xF6FD, 0x7C5C, 0xF6FB, 0x7C5F, 0xC5A3, 0x7C60, 0xC5A2, 0x7C63, 0xC5D3, 0x7C64, 0xC5D2, 0x7C65, 0xC5D4, + 0x7C66, 0xF7ED, 0x7C67, 0xF7EC, 0x7C69, 0xF8FB, 0x7C6A, 0xF8B8, 0x7C6B, 0xF8FC, 0x7C6C, 0xC658, 0x7C6E, 0xC659, 0x7C6F, 0xF96D, + 0x7C72, 0xC67E, 0x7C73, 0xA6CC, 0x7C75, 0xCDA8, 0x7C78, 0xD045, 0x7C79, 0xD046, 0x7C7A, 0xD044, 0x7C7D, 0xACF3, 0x7C7F, 0xD047, + 0x7C80, 0xD048, 0x7C81, 0xD049, 0x7C84, 0xD349, 0x7C85, 0xD34F, 0x7C88, 0xD34D, 0x7C89, 0xAFBB, 0x7C8A, 0xD34B, 0x7C8C, 0xD34C, + 0x7C8D, 0xD34E, 0x7C91, 0xD34A, 0x7C92, 0xB2C9, 0x7C94, 0xD6DE, 0x7C95, 0xB2CB, 0x7C96, 0xD6E0, 0x7C97, 0xB2CA, 0x7C98, 0xD6DF, + 0x7C9E, 0xDAE8, 0x7C9F, 0xB5AF, 0x7CA1, 0xDAEA, 0x7CA2, 0xDAE7, 0x7CA3, 0xD6E1, 0x7CA5, 0xB5B0, 0x7CA7, 0xF9DB, 0x7CA8, 0xDAE9, + 0x7CAF, 0xDF56, 0x7CB1, 0xB864, 0x7CB2, 0xDF54, 0x7CB3, 0xB865, 0x7CB4, 0xDF55, 0x7CB5, 0xB866, 0x7CB9, 0xBAE9, 0x7CBA, 0xE361, + 0x7CBB, 0xE35E, 0x7CBC, 0xE360, 0x7CBD, 0xBAEA, 0x7CBE, 0xBAEB, 0x7CBF, 0xE35F, 0x7CC5, 0xE6DF, 0x7CC8, 0xE6E0, 0x7CCA, 0xBD6B, + 0x7CCB, 0xE6E2, 0x7CCC, 0xE6E1, 0x7CCE, 0xA261, 0x7CD0, 0xEACA, 0x7CD1, 0xEACB, 0x7CD2, 0xEAC7, 0x7CD4, 0xEAC8, 0x7CD5, 0xBF7C, + 0x7CD6, 0xBF7D, 0x7CD7, 0xEAC9, 0x7CD9, 0xC157, 0x7CDC, 0xC153, 0x7CDD, 0xC158, 0x7CDE, 0xC154, 0x7CDF, 0xC156, 0x7CE0, 0xC152, + 0x7CE2, 0xC155, 0x7CE7, 0xC2B3, 0x7CE8, 0xEDCF, 0x7CEA, 0xF2AE, 0x7CEC, 0xF2AD, 0x7CEE, 0xF4AB, 0x7CEF, 0xC47A, 0x7CF0, 0xC47B, + 0x7CF1, 0xF741, 0x7CF2, 0xF5E6, 0x7CF4, 0xF740, 0x7CF6, 0xF8FD, 0x7CF7, 0xF9A4, 0x7CF8, 0xA6CD, 0x7CFB, 0xA874, 0x7CFD, 0xCDA9, + 0x7CFE, 0xAAC8, 0x7D00, 0xACF6, 0x7D01, 0xD04C, 0x7D02, 0xACF4, 0x7D03, 0xD04A, 0x7D04, 0xACF9, 0x7D05, 0xACF5, 0x7D06, 0xACFA, + 0x7D07, 0xACF8, 0x7D08, 0xD04B, 0x7D09, 0xACF7, 0x7D0A, 0xAFBF, 0x7D0B, 0xAFBE, 0x7D0C, 0xD35A, 0x7D0D, 0xAFC7, 0x7D0E, 0xD353, + 0x7D0F, 0xD359, 0x7D10, 0xAFC3, 0x7D11, 0xD352, 0x7D12, 0xD358, 0x7D13, 0xD356, 0x7D14, 0xAFC2, 0x7D15, 0xAFC4, 0x7D16, 0xD355, + 0x7D17, 0xAFBD, 0x7D18, 0xD354, 0x7D19, 0xAFC8, 0x7D1A, 0xAFC5, 0x7D1B, 0xAFC9, 0x7D1C, 0xAFC6, 0x7D1D, 0xD351, 0x7D1E, 0xD350, + 0x7D1F, 0xD357, 0x7D20, 0xAFC0, 0x7D21, 0xAFBC, 0x7D22, 0xAFC1, 0x7D28, 0xD6F0, 0x7D29, 0xD6E9, 0x7D2B, 0xB5B5, 0x7D2C, 0xD6E8, + 0x7D2E, 0xB2CF, 0x7D2F, 0xB2D6, 0x7D30, 0xB2D3, 0x7D31, 0xB2D9, 0x7D32, 0xB2D8, 0x7D33, 0xB2D4, 0x7D35, 0xD6E2, 0x7D36, 0xD6E5, + 0x7D38, 0xD6E4, 0x7D39, 0xB2D0, 0x7D3A, 0xD6E6, 0x7D3B, 0xD6EF, 0x7D3C, 0xB2D1, 0x7D3D, 0xD6E3, 0x7D3E, 0xD6EC, 0x7D3F, 0xD6ED, + 0x7D40, 0xB2D2, 0x7D41, 0xD6EA, 0x7D42, 0xB2D7, 0x7D43, 0xB2CD, 0x7D44, 0xB2D5, 0x7D45, 0xD6E7, 0x7D46, 0xB2CC, 0x7D47, 0xD6EB, + 0x7D4A, 0xD6EE, 0x7D4E, 0xDAFB, 0x7D4F, 0xDAF2, 0x7D50, 0xB5B2, 0x7D51, 0xDAF9, 0x7D52, 0xDAF6, 0x7D53, 0xDAEE, 0x7D54, 0xDAF7, + 0x7D55, 0xB5B4, 0x7D56, 0xDAEF, 0x7D58, 0xDAEB, 0x7D5B, 0xB86C, 0x7D5C, 0xDAF4, 0x7D5E, 0xB5B1, 0x7D5F, 0xDAFA, 0x7D61, 0xB5B8, + 0x7D62, 0xB5BA, 0x7D63, 0xDAED, 0x7D66, 0xB5B9, 0x7D67, 0xDAF0, 0x7D68, 0xB5B3, 0x7D69, 0xDAF8, 0x7D6A, 0xDAF1, 0x7D6B, 0xDAF5, + 0x7D6D, 0xDAF3, 0x7D6E, 0xB5B6, 0x7D6F, 0xDAEC, 0x7D70, 0xB5BB, 0x7D71, 0xB2CE, 0x7D72, 0xB5B7, 0x7D73, 0xB5BC, 0x7D79, 0xB868, + 0x7D7A, 0xDF5D, 0x7D7B, 0xDF5F, 0x7D7C, 0xDF61, 0x7D7D, 0xDF65, 0x7D7F, 0xDF5B, 0x7D80, 0xDF59, 0x7D81, 0xB86A, 0x7D83, 0xDF60, + 0x7D84, 0xDF64, 0x7D85, 0xDF5C, 0x7D86, 0xDF58, 0x7D88, 0xDF57, 0x7D8C, 0xDF62, 0x7D8D, 0xDF5A, 0x7D8E, 0xDF5E, 0x7D8F, 0xB86B, + 0x7D91, 0xB869, 0x7D92, 0xDF66, 0x7D93, 0xB867, 0x7D94, 0xDF63, 0x7D96, 0xE372, 0x7D9C, 0xBAEE, 0x7D9D, 0xE36A, 0x7D9E, 0xBD78, + 0x7D9F, 0xE374, 0x7DA0, 0xBAF1, 0x7DA1, 0xE378, 0x7DA2, 0xBAF7, 0x7DA3, 0xE365, 0x7DA6, 0xE375, 0x7DA7, 0xE362, 0x7DA9, 0xE377, + 0x7DAA, 0xE366, 0x7DAC, 0xBAFE, 0x7DAD, 0xBAFB, 0x7DAE, 0xE376, 0x7DAF, 0xE370, 0x7DB0, 0xBAED, 0x7DB1, 0xBAF5, 0x7DB2, 0xBAF4, + 0x7DB4, 0xBAF3, 0x7DB5, 0xBAF9, 0x7DB7, 0xE363, 0x7DB8, 0xBAFA, 0x7DB9, 0xE371, 0x7DBA, 0xBAF6, 0x7DBB, 0xBAEC, 0x7DBC, 0xE373, + 0x7DBD, 0xBAEF, 0x7DBE, 0xBAF0, 0x7DBF, 0xBAF8, 0x7DC0, 0xE368, 0x7DC1, 0xE367, 0x7DC2, 0xE364, 0x7DC4, 0xE36C, 0x7DC5, 0xE369, + 0x7DC6, 0xE36D, 0x7DC7, 0xBAFD, 0x7DC9, 0xE379, 0x7DCA, 0xBAF2, 0x7DCB, 0xE36E, 0x7DCC, 0xE36F, 0x7DCE, 0xE36B, 0x7DD2, 0xBAFC, + 0x7DD7, 0xE6E7, 0x7DD8, 0xBD70, 0x7DD9, 0xBD79, 0x7DDA, 0xBD75, 0x7DDB, 0xE6E4, 0x7DDD, 0xBD72, 0x7DDE, 0xBD76, 0x7DDF, 0xE6F0, + 0x7DE0, 0xBD6C, 0x7DE1, 0xE6E8, 0x7DE3, 0xBD74, 0x7DE6, 0xE6EB, 0x7DE7, 0xE6E6, 0x7DE8, 0xBD73, 0x7DE9, 0xBD77, 0x7DEA, 0xE6E5, + 0x7DEC, 0xBD71, 0x7DEE, 0xE6EF, 0x7DEF, 0xBD6E, 0x7DF0, 0xE6EE, 0x7DF1, 0xE6ED, 0x7DF2, 0xBD7A, 0x7DF3, 0xE572, 0x7DF4, 0xBD6D, + 0x7DF6, 0xE6EC, 0x7DF7, 0xE6E3, 0x7DF9, 0xBD7B, 0x7DFA, 0xE6EA, 0x7DFB, 0xBD6F, 0x7E03, 0xE6E9, 0x7E08, 0xBFA2, 0x7E09, 0xBFA7, + 0x7E0A, 0xBF7E, 0x7E0B, 0xEAD8, 0x7E0C, 0xEACF, 0x7E0D, 0xEADB, 0x7E0E, 0xEAD3, 0x7E0F, 0xEAD9, 0x7E10, 0xBFA8, 0x7E11, 0xBFA1, + 0x7E12, 0xEACC, 0x7E13, 0xEAD2, 0x7E14, 0xEADC, 0x7E15, 0xEAD5, 0x7E16, 0xEADA, 0x7E17, 0xEACE, 0x7E1A, 0xEAD6, 0x7E1B, 0xBFA3, + 0x7E1C, 0xEAD4, 0x7E1D, 0xBFA6, 0x7E1E, 0xBFA5, 0x7E1F, 0xEAD0, 0x7E20, 0xEAD1, 0x7E21, 0xEACD, 0x7E22, 0xEAD7, 0x7E23, 0xBFA4, + 0x7E24, 0xEADE, 0x7E25, 0xEADD, 0x7E29, 0xEDDA, 0x7E2A, 0xEDD6, 0x7E2B, 0xC15F, 0x7E2D, 0xEDD0, 0x7E2E, 0xC159, 0x7E2F, 0xC169, + 0x7E30, 0xEDDC, 0x7E31, 0xC161, 0x7E32, 0xC15D, 0x7E33, 0xEDD3, 0x7E34, 0xC164, 0x7E35, 0xC167, 0x7E36, 0xEDDE, 0x7E37, 0xC15C, + 0x7E38, 0xEDD5, 0x7E39, 0xC165, 0x7E3A, 0xEDE0, 0x7E3B, 0xEDDD, 0x7E3C, 0xEDD1, 0x7E3D, 0xC160, 0x7E3E, 0xC15A, 0x7E3F, 0xC168, + 0x7E40, 0xEDD8, 0x7E41, 0xC163, 0x7E42, 0xEDD2, 0x7E43, 0xC15E, 0x7E44, 0xEDDF, 0x7E45, 0xC162, 0x7E46, 0xC15B, 0x7E47, 0xEDD9, + 0x7E48, 0xC166, 0x7E49, 0xEDD7, 0x7E4C, 0xEDDB, 0x7E50, 0xF06E, 0x7E51, 0xF074, 0x7E52, 0xC2B9, 0x7E53, 0xF077, 0x7E54, 0xC2B4, + 0x7E55, 0xC2B5, 0x7E56, 0xF06F, 0x7E57, 0xF076, 0x7E58, 0xF071, 0x7E59, 0xC2BA, 0x7E5A, 0xC2B7, 0x7E5C, 0xF06D, 0x7E5E, 0xC2B6, + 0x7E5F, 0xF073, 0x7E60, 0xF075, 0x7E61, 0xC2B8, 0x7E62, 0xF072, 0x7E63, 0xF070, 0x7E68, 0xF2B8, 0x7E69, 0xC3B7, 0x7E6A, 0xC3B8, + 0x7E6B, 0xC3B4, 0x7E6D, 0xC3B5, 0x7E6F, 0xF2B4, 0x7E70, 0xF2B2, 0x7E72, 0xF2B6, 0x7E73, 0xC3BA, 0x7E74, 0xF2B7, 0x7E75, 0xF2B0, + 0x7E76, 0xF2AF, 0x7E77, 0xF2B3, 0x7E78, 0xF2B1, 0x7E79, 0xC3B6, 0x7E7A, 0xF2B5, 0x7E7B, 0xF4AC, 0x7E7C, 0xC47E, 0x7E7D, 0xC47D, + 0x7E7E, 0xF4AD, 0x7E80, 0xF4AF, 0x7E81, 0xF4AE, 0x7E82, 0xC4A1, 0x7E86, 0xF5EB, 0x7E87, 0xF5E8, 0x7E88, 0xF5E9, 0x7E8A, 0xF5E7, + 0x7E8B, 0xF5EA, 0x7E8C, 0xC4F2, 0x7E8D, 0xF5EC, 0x7E8F, 0xC4F1, 0x7E91, 0xF742, 0x7E93, 0xC5D5, 0x7E94, 0xC5D7, 0x7E95, 0xF7EE, + 0x7E96, 0xC5D6, 0x7E97, 0xF8B9, 0x7E98, 0xF940, 0x7E99, 0xF942, 0x7E9A, 0xF8FE, 0x7E9B, 0xF941, 0x7E9C, 0xC66C, 0x7F36, 0xA6CE, + 0x7F38, 0xACFB, 0x7F39, 0xD26F, 0x7F3A, 0xAFCA, 0x7F3D, 0xB2DA, 0x7F3E, 0xDAFC, 0x7F3F, 0xDAFD, 0x7F43, 0xEADF, 0x7F44, 0xC16A, + 0x7F45, 0xEDE1, 0x7F48, 0xC2BB, 0x7F4A, 0xF2BA, 0x7F4B, 0xF2B9, 0x7F4C, 0xC4A2, 0x7F4D, 0xF5ED, 0x7F4F, 0xF743, 0x7F50, 0xC5F8, + 0x7F51, 0xCA49, 0x7F54, 0xAAC9, 0x7F55, 0xA875, 0x7F58, 0xD04D, 0x7F5B, 0xD360, 0x7F5C, 0xD35B, 0x7F5D, 0xD35F, 0x7F5E, 0xD35D, + 0x7F5F, 0xAFCB, 0x7F60, 0xD35E, 0x7F61, 0xD35C, 0x7F63, 0xD6F1, 0x7F65, 0xDAFE, 0x7F66, 0xDB40, 0x7F67, 0xDF69, 0x7F68, 0xDF6A, + 0x7F69, 0xB86E, 0x7F6A, 0xB86F, 0x7F6B, 0xDF68, 0x7F6C, 0xDF6B, 0x7F6D, 0xDF67, 0x7F6E, 0xB86D, 0x7F70, 0xBB40, 0x7F72, 0xB870, + 0x7F73, 0xE37A, 0x7F75, 0xBD7C, 0x7F76, 0xE6F1, 0x7F77, 0xBD7D, 0x7F79, 0xBFA9, 0x7F7A, 0xEAE2, 0x7F7B, 0xEAE0, 0x7F7C, 0xEAE1, + 0x7F7D, 0xEDE4, 0x7F7E, 0xEDE3, 0x7F7F, 0xEDE2, 0x7F83, 0xF2BB, 0x7F85, 0xC3B9, 0x7F86, 0xF2BC, 0x7F87, 0xF744, 0x7F88, 0xC5F9, + 0x7F89, 0xF8BA, 0x7F8A, 0xA6CF, 0x7F8B, 0xAACB, 0x7F8C, 0xAACA, 0x7F8D, 0xD04F, 0x7F8E, 0xACFC, 0x7F91, 0xD04E, 0x7F92, 0xD362, + 0x7F94, 0xAFCC, 0x7F95, 0xD6F2, 0x7F96, 0xD361, 0x7F9A, 0xB2DC, 0x7F9B, 0xD6F5, 0x7F9C, 0xD6F3, 0x7F9D, 0xD6F4, 0x7F9E, 0xB2DB, + 0x7FA0, 0xDB42, 0x7FA1, 0xDB43, 0x7FA2, 0xDB41, 0x7FA4, 0xB873, 0x7FA5, 0xDF6D, 0x7FA6, 0xDF6C, 0x7FA7, 0xDF6E, 0x7FA8, 0xB872, + 0x7FA9, 0xB871, 0x7FAC, 0xE6F2, 0x7FAD, 0xE6F4, 0x7FAF, 0xBD7E, 0x7FB0, 0xE6F3, 0x7FB1, 0xEAE3, 0x7FB2, 0xBFAA, 0x7FB3, 0xF079, + 0x7FB5, 0xF078, 0x7FB6, 0xC3BB, 0x7FB7, 0xF2BD, 0x7FB8, 0xC3BD, 0x7FB9, 0xC3BC, 0x7FBA, 0xF4B0, 0x7FBB, 0xF5EE, 0x7FBC, 0xC4F3, + 0x7FBD, 0xA6D0, 0x7FBE, 0xD050, 0x7FBF, 0xACFD, 0x7FC0, 0xD365, 0x7FC1, 0xAFCE, 0x7FC2, 0xD364, 0x7FC3, 0xD363, 0x7FC5, 0xAFCD, + 0x7FC7, 0xD6FB, 0x7FC9, 0xD6FD, 0x7FCA, 0xD6F6, 0x7FCB, 0xD6F7, 0x7FCC, 0xB2DD, 0x7FCD, 0xD6F8, 0x7FCE, 0xB2DE, 0x7FCF, 0xD6FC, + 0x7FD0, 0xD6F9, 0x7FD1, 0xD6FA, 0x7FD2, 0xB2DF, 0x7FD4, 0xB5BE, 0x7FD5, 0xB5BF, 0x7FD7, 0xDB44, 0x7FDB, 0xDF6F, 0x7FDC, 0xDF70, + 0x7FDE, 0xE37E, 0x7FDF, 0xBB43, 0x7FE0, 0xBB41, 0x7FE1, 0xBB42, 0x7FE2, 0xE37B, 0x7FE3, 0xE37C, 0x7FE5, 0xE37D, 0x7FE6, 0xE6F9, + 0x7FE8, 0xE6FA, 0x7FE9, 0xBDA1, 0x7FEA, 0xE6F7, 0x7FEB, 0xE6F6, 0x7FEC, 0xE6F8, 0x7FED, 0xE6F5, 0x7FEE, 0xBFAD, 0x7FEF, 0xEAE4, + 0x7FF0, 0xBFAB, 0x7FF1, 0xBFAC, 0x7FF2, 0xEDE6, 0x7FF3, 0xC16B, 0x7FF4, 0xEDE5, 0x7FF5, 0xEFA8, 0x7FF7, 0xF07A, 0x7FF8, 0xF07B, + 0x7FF9, 0xC2BC, 0x7FFB, 0xC2BD, 0x7FFC, 0xC16C, 0x7FFD, 0xF2BE, 0x7FFE, 0xF2BF, 0x7FFF, 0xF4B1, 0x8000, 0xC4A3, 0x8001, 0xA6D1, + 0x8003, 0xA6D2, 0x8004, 0xACFE, 0x8005, 0xAACC, 0x8006, 0xAFCF, 0x8007, 0xD051, 0x800B, 0xB5C0, 0x800C, 0xA6D3, 0x800D, 0xAD41, + 0x800E, 0xD052, 0x800F, 0xD053, 0x8010, 0xAD40, 0x8011, 0xAD42, 0x8012, 0xA6D4, 0x8014, 0xD054, 0x8015, 0xAFD1, 0x8016, 0xD366, + 0x8017, 0xAFD3, 0x8018, 0xAFD0, 0x8019, 0xAFD2, 0x801B, 0xD741, 0x801C, 0xB2E0, 0x801E, 0xD740, 0x801F, 0xD6FE, 0x8021, 0xDF71, + 0x8024, 0xE3A1, 0x8026, 0xBDA2, 0x8028, 0xBFAE, 0x8029, 0xEAE6, 0x802A, 0xEAE5, 0x802C, 0xEDE7, 0x8030, 0xF5EF, 0x8033, 0xA6D5, + 0x8034, 0xCB73, 0x8035, 0xCDAA, 0x8036, 0xAD43, 0x8037, 0xD055, 0x8039, 0xD368, 0x803D, 0xAFD4, 0x803E, 0xD367, 0x803F, 0xAFD5, + 0x8043, 0xD743, 0x8046, 0xB2E2, 0x8047, 0xD742, 0x8048, 0xD744, 0x804A, 0xB2E1, 0x804F, 0xDB46, 0x8050, 0xDB47, 0x8051, 0xDB45, + 0x8052, 0xB5C1, 0x8056, 0xB874, 0x8058, 0xB875, 0x805A, 0xBB45, 0x805C, 0xE3A3, 0x805D, 0xE3A2, 0x805E, 0xBB44, 0x8064, 0xE6FB, + 0x8067, 0xE6FC, 0x806C, 0xEAE7, 0x806F, 0xC170, 0x8070, 0xC16F, 0x8071, 0xC16D, 0x8072, 0xC16E, 0x8073, 0xC171, 0x8075, 0xF07C, + 0x8076, 0xC2BF, 0x8077, 0xC2BE, 0x8078, 0xF2C0, 0x8079, 0xF4B2, 0x807D, 0xC5A5, 0x807E, 0xC5A4, 0x807F, 0xA6D6, 0x8082, 0xD1FB, + 0x8084, 0xB877, 0x8085, 0xB5C2, 0x8086, 0xB876, 0x8087, 0xBB46, 0x8089, 0xA6D7, 0x808A, 0xC9A9, 0x808B, 0xA6D8, 0x808C, 0xA6D9, + 0x808F, 0xCDAB, 0x8090, 0xCB76, 0x8092, 0xCB77, 0x8093, 0xA877, 0x8095, 0xCB74, 0x8096, 0xA876, 0x8098, 0xA879, 0x8099, 0xCB75, + 0x809A, 0xA87B, 0x809B, 0xA87A, 0x809C, 0xCB78, 0x809D, 0xA878, 0x80A1, 0xAAD1, 0x80A2, 0xAACF, 0x80A3, 0xCDAD, 0x80A5, 0xAACE, + 0x80A9, 0xAAD3, 0x80AA, 0xAAD5, 0x80AB, 0xAAD2, 0x80AD, 0xCDB0, 0x80AE, 0xCDAC, 0x80AF, 0xAAD6, 0x80B1, 0xAAD0, 0x80B2, 0xA87C, + 0x80B4, 0xAAD4, 0x80B5, 0xCDAF, 0x80B8, 0xCDAE, 0x80BA, 0xAACD, 0x80C2, 0xD05B, 0x80C3, 0xAD47, 0x80C4, 0xAD48, 0x80C5, 0xD05D, + 0x80C7, 0xD057, 0x80C8, 0xD05A, 0x80C9, 0xD063, 0x80CA, 0xD061, 0x80CC, 0xAD49, 0x80CD, 0xD067, 0x80CE, 0xAD4C, 0x80CF, 0xD064, + 0x80D0, 0xD05C, 0x80D1, 0xD059, 0x80D4, 0xDB49, 0x80D5, 0xD062, 0x80D6, 0xAD44, 0x80D7, 0xD065, 0x80D8, 0xD056, 0x80D9, 0xD05F, + 0x80DA, 0xAD46, 0x80DB, 0xAD4B, 0x80DC, 0xD060, 0x80DD, 0xAD4F, 0x80DE, 0xAD4D, 0x80E0, 0xD058, 0x80E1, 0xAD4A, 0x80E3, 0xD05E, + 0x80E4, 0xAD4E, 0x80E5, 0xAD45, 0x80E6, 0xD066, 0x80ED, 0xAFDA, 0x80EF, 0xAFE3, 0x80F0, 0xAFD8, 0x80F1, 0xAFD6, 0x80F2, 0xD36A, + 0x80F3, 0xAFDE, 0x80F4, 0xAFDB, 0x80F5, 0xD36C, 0x80F8, 0xAFDD, 0x80F9, 0xD36B, 0x80FA, 0xD369, 0x80FB, 0xD36E, 0x80FC, 0xAFE2, + 0x80FD, 0xAFE0, 0x80FE, 0xDB48, 0x8100, 0xD36F, 0x8101, 0xD36D, 0x8102, 0xAFD7, 0x8105, 0xAFD9, 0x8106, 0xAFDC, 0x8108, 0xAFDF, + 0x810A, 0xAFE1, 0x8115, 0xD74E, 0x8116, 0xB2E4, 0x8118, 0xD745, 0x8119, 0xD747, 0x811B, 0xD748, 0x811D, 0xD750, 0x811E, 0xD74C, + 0x811F, 0xD74A, 0x8121, 0xD74D, 0x8122, 0xD751, 0x8123, 0xB2E5, 0x8124, 0xB2E9, 0x8125, 0xD746, 0x8127, 0xD74F, 0x8129, 0xB2E7, + 0x812B, 0xB2E6, 0x812C, 0xD74B, 0x812D, 0xD749, 0x812F, 0xB2E3, 0x8130, 0xB2E8, 0x8139, 0xB5C8, 0x813A, 0xDB51, 0x813D, 0xDB4F, + 0x813E, 0xB5CA, 0x8143, 0xDB4A, 0x8144, 0xDFA1, 0x8146, 0xB5C9, 0x8147, 0xDB4E, 0x814A, 0xDB4B, 0x814B, 0xB5C5, 0x814C, 0xB5CB, + 0x814D, 0xDB50, 0x814E, 0xB5C7, 0x814F, 0xDB4D, 0x8150, 0xBB47, 0x8151, 0xB5C6, 0x8152, 0xDB4C, 0x8153, 0xB5CC, 0x8154, 0xB5C4, + 0x8155, 0xB5C3, 0x815B, 0xDF77, 0x815C, 0xDF75, 0x815E, 0xDF7B, 0x8160, 0xDF73, 0x8161, 0xDFA2, 0x8162, 0xDF78, 0x8164, 0xDF72, + 0x8165, 0xB87B, 0x8166, 0xB8A3, 0x8167, 0xDF7D, 0x8169, 0xDF76, 0x816B, 0xB87E, 0x816E, 0xB87C, 0x816F, 0xDF7E, 0x8170, 0xB879, + 0x8171, 0xB878, 0x8172, 0xDF79, 0x8173, 0xB87D, 0x8174, 0xB5CD, 0x8176, 0xDF7C, 0x8177, 0xDF74, 0x8178, 0xB87A, 0x8179, 0xB8A1, + 0x817A, 0xB8A2, 0x817F, 0xBB4C, 0x8180, 0xBB48, 0x8182, 0xBB4D, 0x8183, 0xE3A6, 0x8186, 0xE3A5, 0x8187, 0xE3A7, 0x8188, 0xBB4A, + 0x8189, 0xE3A4, 0x818A, 0xBB4B, 0x818B, 0xE3AA, 0x818C, 0xE3A9, 0x818D, 0xE3A8, 0x818F, 0xBB49, 0x8195, 0xE741, 0x8197, 0xE744, + 0x8198, 0xBDA8, 0x8199, 0xE743, 0x819A, 0xBDA7, 0x819B, 0xBDA3, 0x819C, 0xBDA4, 0x819D, 0xBDA5, 0x819E, 0xE740, 0x819F, 0xE6FE, + 0x81A0, 0xBDA6, 0x81A2, 0xE742, 0x81A3, 0xE6FD, 0x81A6, 0xEAE9, 0x81A7, 0xEAF3, 0x81A8, 0xBFB1, 0x81A9, 0xBFB0, 0x81AB, 0xEAED, + 0x81AC, 0xEAEF, 0x81AE, 0xEAEA, 0x81B0, 0xEAEE, 0x81B1, 0xEAE8, 0x81B2, 0xEAF1, 0x81B3, 0xBFAF, 0x81B4, 0xEAF0, 0x81B5, 0xEAEC, + 0x81B7, 0xEAF2, 0x81B9, 0xEAEB, 0x81BA, 0xC174, 0x81BB, 0xEDE8, 0x81BC, 0xEDEE, 0x81BD, 0xC178, 0x81BE, 0xC17A, 0x81BF, 0xC177, + 0x81C0, 0xC176, 0x81C2, 0xC175, 0x81C3, 0xC173, 0x81C4, 0xEDE9, 0x81C5, 0xEDEC, 0x81C6, 0xC172, 0x81C7, 0xEDED, 0x81C9, 0xC179, + 0x81CA, 0xEDEB, 0x81CC, 0xEDEA, 0x81CD, 0xC2C0, 0x81CF, 0xC2C1, 0x81D0, 0xF0A1, 0x81D1, 0xF07D, 0x81D2, 0xF07E, 0x81D5, 0xF2C2, + 0x81D7, 0xF2C1, 0x81D8, 0xC3BE, 0x81D9, 0xF4B4, 0x81DA, 0xC4A4, 0x81DB, 0xF4B3, 0x81DD, 0xF5F0, 0x81DE, 0xF745, 0x81DF, 0xC5A6, + 0x81E0, 0xF943, 0x81E1, 0xF944, 0x81E2, 0xC5D8, 0x81E3, 0xA6DA, 0x81E5, 0xAAD7, 0x81E6, 0xDB52, 0x81E7, 0xBB4E, 0x81E8, 0xC17B, + 0x81E9, 0xEDEF, 0x81EA, 0xA6DB, 0x81EC, 0xAFE5, 0x81ED, 0xAFE4, 0x81EE, 0xDB53, 0x81F2, 0xEAF4, 0x81F3, 0xA6DC, 0x81F4, 0xAD50, + 0x81F7, 0xDB54, 0x81F8, 0xDB55, 0x81F9, 0xDB56, 0x81FA, 0xBB4F, 0x81FB, 0xBFB2, 0x81FC, 0xA6DD, 0x81FE, 0xAAD8, 0x81FF, 0xD068, + 0x8200, 0xAFE6, 0x8201, 0xD370, 0x8202, 0xB2EA, 0x8204, 0xDB57, 0x8205, 0xB8A4, 0x8207, 0xBB50, 0x8208, 0xBFB3, 0x8209, 0xC17C, + 0x820A, 0xC2C2, 0x820B, 0xF4B5, 0x820C, 0xA6DE, 0x820D, 0xAAD9, 0x8210, 0xAFE7, 0x8211, 0xD752, 0x8212, 0xB5CE, 0x8214, 0xBB51, + 0x8215, 0xE3AB, 0x8216, 0xE745, 0x821B, 0xA6DF, 0x821C, 0xB5CF, 0x821D, 0xDFA3, 0x821E, 0xBB52, 0x821F, 0xA6E0, 0x8220, 0xCDB1, + 0x8221, 0xD069, 0x8222, 0xAD51, 0x8225, 0xD372, 0x8228, 0xAFEA, 0x822A, 0xAFE8, 0x822B, 0xAFE9, 0x822C, 0xAFEB, 0x822F, 0xD371, + 0x8232, 0xD757, 0x8233, 0xD754, 0x8234, 0xD756, 0x8235, 0xB2EB, 0x8236, 0xB2ED, 0x8237, 0xB2EC, 0x8238, 0xD753, 0x8239, 0xB2EE, + 0x823A, 0xD755, 0x823C, 0xDB58, 0x823D, 0xDB59, 0x823F, 0xDB5A, 0x8240, 0xDFA6, 0x8242, 0xDFA7, 0x8244, 0xDFA5, 0x8245, 0xDFA8, + 0x8247, 0xB8A5, 0x8249, 0xDFA4, 0x824B, 0xBB53, 0x824E, 0xE74A, 0x824F, 0xE746, 0x8250, 0xE749, 0x8251, 0xE74B, 0x8252, 0xE748, + 0x8253, 0xE747, 0x8255, 0xEAF5, 0x8256, 0xEAF6, 0x8257, 0xEAF7, 0x8258, 0xBFB4, 0x8259, 0xBFB5, 0x825A, 0xEDF1, 0x825B, 0xEDF0, + 0x825C, 0xEDF2, 0x825E, 0xF0A3, 0x825F, 0xF0A2, 0x8261, 0xF2C4, 0x8263, 0xF2C5, 0x8264, 0xF2C3, 0x8266, 0xC4A5, 0x8268, 0xF4B6, + 0x8269, 0xF4B7, 0x826B, 0xF746, 0x826C, 0xF7EF, 0x826D, 0xF8BB, 0x826E, 0xA6E1, 0x826F, 0xA87D, 0x8271, 0xC17D, 0x8272, 0xA6E2, + 0x8274, 0xD758, 0x8275, 0xDB5B, 0x8277, 0xC641, 0x8278, 0xCA4A, 0x827C, 0xCA4B, 0x827D, 0xCA4D, 0x827E, 0xA6E3, 0x827F, 0xCA4E, + 0x8280, 0xCA4C, 0x8283, 0xCBA2, 0x8284, 0xCBA3, 0x8285, 0xCB7B, 0x828A, 0xCBA1, 0x828B, 0xA8A1, 0x828D, 0xA8A2, 0x828E, 0xCB7C, + 0x828F, 0xCB7A, 0x8290, 0xCB79, 0x8291, 0xCB7D, 0x8292, 0xA87E, 0x8293, 0xCB7E, 0x8294, 0xD06A, 0x8298, 0xCDB6, 0x8299, 0xAADC, + 0x829A, 0xCDB5, 0x829B, 0xCDB7, 0x829D, 0xAADB, 0x829E, 0xCDBC, 0x829F, 0xAADF, 0x82A0, 0xCDB2, 0x82A1, 0xCDC0, 0x82A2, 0xCDC6, + 0x82A3, 0xAAE6, 0x82A4, 0xCDC3, 0x82A5, 0xAAE3, 0x82A7, 0xCDB9, 0x82A8, 0xCDBF, 0x82A9, 0xCDC1, 0x82AB, 0xCDB4, 0x82AC, 0xAAE2, + 0x82AD, 0xAADD, 0x82AE, 0xCDBA, 0x82AF, 0xAAE4, 0x82B0, 0xAAE7, 0x82B1, 0xAAE1, 0x82B3, 0xAADA, 0x82B4, 0xCDBE, 0x82B5, 0xCDB8, + 0x82B6, 0xCDC5, 0x82B7, 0xAAE9, 0x82B8, 0xAAE5, 0x82B9, 0xAAE0, 0x82BA, 0xCDBD, 0x82BB, 0xAFEC, 0x82BC, 0xCDBB, 0x82BD, 0xAADE, + 0x82BE, 0xAAE8, 0x82C0, 0xCDB3, 0x82C2, 0xCDC2, 0x82C3, 0xCDC4, 0x82D1, 0xAD62, 0x82D2, 0xAD5C, 0x82D3, 0xAD64, 0x82D4, 0xAD61, + 0x82D5, 0xD071, 0x82D6, 0xD074, 0x82D7, 0xAD5D, 0x82D9, 0xD06B, 0x82DB, 0xAD56, 0x82DC, 0xAD60, 0x82DE, 0xAD63, 0x82DF, 0xAD65, + 0x82E0, 0xD0A2, 0x82E1, 0xD077, 0x82E3, 0xAD55, 0x82E4, 0xD0A1, 0x82E5, 0xAD59, 0x82E6, 0xAD57, 0x82E7, 0xAD52, 0x82E8, 0xD06F, + 0x82EA, 0xD07E, 0x82EB, 0xD073, 0x82EC, 0xD076, 0x82ED, 0xD0A5, 0x82EF, 0xAD66, 0x82F0, 0xD07D, 0x82F1, 0xAD5E, 0x82F2, 0xD078, + 0x82F3, 0xD0A4, 0x82F4, 0xD075, 0x82F5, 0xD079, 0x82F6, 0xD07C, 0x82F9, 0xD06D, 0x82FA, 0xD0A3, 0x82FB, 0xD07B, 0x82FE, 0xD06C, + 0x8300, 0xD070, 0x8301, 0xAD5F, 0x8302, 0xAD5A, 0x8303, 0xAD53, 0x8304, 0xAD58, 0x8305, 0xAD54, 0x8306, 0xAD67, 0x8307, 0xD06E, + 0x8308, 0xD3A5, 0x8309, 0xAD5B, 0x830C, 0xD07A, 0x830D, 0xCE41, 0x8316, 0xD3A8, 0x8317, 0xAFFA, 0x8319, 0xD376, 0x831B, 0xD3A3, + 0x831C, 0xD37D, 0x831E, 0xD3B2, 0x8320, 0xD3AA, 0x8322, 0xD37E, 0x8324, 0xD3A9, 0x8325, 0xD378, 0x8326, 0xD37C, 0x8327, 0xD3B5, + 0x8328, 0xAFFD, 0x8329, 0xD3AD, 0x832A, 0xD3A4, 0x832B, 0xAFED, 0x832C, 0xD3B3, 0x832D, 0xD374, 0x832F, 0xD3AC, 0x8331, 0xAFFC, + 0x8332, 0xAFF7, 0x8333, 0xD373, 0x8334, 0xAFF5, 0x8335, 0xAFF4, 0x8336, 0xAFF9, 0x8337, 0xD3AB, 0x8338, 0xAFF1, 0x8339, 0xAFF8, + 0x833A, 0xD072, 0x833B, 0xDB5C, 0x833C, 0xD3A6, 0x833F, 0xD37A, 0x8340, 0xAFFB, 0x8341, 0xD37B, 0x8342, 0xD3A1, 0x8343, 0xAFFE, + 0x8344, 0xD375, 0x8345, 0xD3AF, 0x8347, 0xD3AE, 0x8348, 0xD3B6, 0x8349, 0xAFF3, 0x834A, 0xAFF0, 0x834B, 0xD3B4, 0x834C, 0xD3B0, + 0x834D, 0xD3A7, 0x834E, 0xD3A2, 0x834F, 0xAFF6, 0x8350, 0xAFF2, 0x8351, 0xD377, 0x8352, 0xAFEE, 0x8353, 0xD3B1, 0x8354, 0xAFEF, + 0x8356, 0xD379, 0x8373, 0xD75E, 0x8374, 0xD760, 0x8375, 0xD765, 0x8376, 0xD779, 0x8377, 0xB2FC, 0x8378, 0xB2F2, 0x837A, 0xD75D, + 0x837B, 0xB2FD, 0x837C, 0xB2FE, 0x837D, 0xD768, 0x837E, 0xD76F, 0x837F, 0xD775, 0x8381, 0xD762, 0x8383, 0xD769, 0x8386, 0xB340, + 0x8387, 0xD777, 0x8388, 0xD772, 0x8389, 0xB2FA, 0x838A, 0xB2F8, 0x838B, 0xD76E, 0x838C, 0xD76A, 0x838D, 0xD75C, 0x838E, 0xB2EF, + 0x838F, 0xD761, 0x8390, 0xD759, 0x8392, 0xB2F7, 0x8393, 0xB2F9, 0x8394, 0xD766, 0x8395, 0xD763, 0x8396, 0xB2F4, 0x8397, 0xD773, + 0x8398, 0xB2F1, 0x8399, 0xD764, 0x839A, 0xD77A, 0x839B, 0xD76C, 0x839D, 0xD76B, 0x839E, 0xB2F0, 0x83A0, 0xB2FB, 0x83A2, 0xB2F3, + 0x83A3, 0xD75A, 0x83A4, 0xD75F, 0x83A5, 0xD770, 0x83A6, 0xD776, 0x83A7, 0xB341, 0x83A8, 0xD75B, 0x83A9, 0xD767, 0x83AA, 0xD76D, + 0x83AB, 0xB2F6, 0x83AE, 0xD778, 0x83AF, 0xD771, 0x83B0, 0xD774, 0x83BD, 0xB2F5, 0x83BF, 0xDB6C, 0x83C0, 0xDB60, 0x83C1, 0xB5D7, + 0x83C2, 0xDB7D, 0x83C3, 0xDBA7, 0x83C4, 0xDBAA, 0x83C5, 0xB5D5, 0x83C6, 0xDB68, 0x83C7, 0xDBA3, 0x83C8, 0xDB69, 0x83C9, 0xDB77, + 0x83CA, 0xB5E2, 0x83CB, 0xDB73, 0x83CC, 0xB5DF, 0x83CE, 0xDB74, 0x83CF, 0xDB5D, 0x83D1, 0xDBA4, 0x83D4, 0xB5E8, 0x83D5, 0xDBA1, + 0x83D6, 0xDB75, 0x83D7, 0xDBAC, 0x83D8, 0xDB70, 0x83D9, 0xDFC8, 0x83DB, 0xDBAF, 0x83DC, 0xB5E6, 0x83DD, 0xDB6E, 0x83DE, 0xDB7A, + 0x83DF, 0xB5E9, 0x83E0, 0xB5D4, 0x83E1, 0xDB72, 0x83E2, 0xDBAD, 0x83E3, 0xDB6B, 0x83E4, 0xDB64, 0x83E5, 0xDB6F, 0x83E7, 0xDB63, + 0x83E8, 0xDB61, 0x83E9, 0xB5D0, 0x83EA, 0xDBA5, 0x83EB, 0xDB6A, 0x83EC, 0xDBA8, 0x83EE, 0xDBA9, 0x83EF, 0xB5D8, 0x83F0, 0xB5DD, + 0x83F1, 0xB5D9, 0x83F2, 0xB5E1, 0x83F3, 0xDB7E, 0x83F4, 0xB5DA, 0x83F5, 0xDB76, 0x83F6, 0xDB66, 0x83F8, 0xB5D2, 0x83F9, 0xDB5E, + 0x83FA, 0xDBA2, 0x83FB, 0xDBAB, 0x83FC, 0xDB65, 0x83FD, 0xB5E0, 0x83FE, 0xDBB0, 0x83FF, 0xDB71, 0x8401, 0xDB6D, 0x8403, 0xB5D1, + 0x8404, 0xB5E5, 0x8406, 0xDB7C, 0x8407, 0xB5E7, 0x8409, 0xDB78, 0x840A, 0xB5DC, 0x840B, 0xB5D6, 0x840C, 0xB5DE, 0x840D, 0xB5D3, + 0x840E, 0xB5E4, 0x840F, 0xDB79, 0x8410, 0xDB67, 0x8411, 0xDB7B, 0x8412, 0xDB62, 0x8413, 0xDBA6, 0x841B, 0xDBAE, 0x8423, 0xDB5F, + 0x8429, 0xDFC7, 0x842B, 0xDFDD, 0x842C, 0xB855, 0x842D, 0xDFCC, 0x842F, 0xDFCA, 0x8430, 0xDFB5, 0x8431, 0xB8A9, 0x8432, 0xDFC5, + 0x8433, 0xDFD9, 0x8434, 0xDFC1, 0x8435, 0xB8B1, 0x8436, 0xDFD8, 0x8437, 0xDFBF, 0x8438, 0xB5E3, 0x8439, 0xDFCF, 0x843A, 0xDFC0, + 0x843B, 0xDFD6, 0x843C, 0xB8B0, 0x843D, 0xB8A8, 0x843F, 0xDFAA, 0x8440, 0xDFB2, 0x8442, 0xDFCB, 0x8443, 0xDFC3, 0x8444, 0xDFDC, + 0x8445, 0xDFC6, 0x8446, 0xB8B6, 0x8447, 0xDFD7, 0x8449, 0xB8AD, 0x844B, 0xDFC9, 0x844C, 0xDFD1, 0x844D, 0xDFB6, 0x844E, 0xDFD0, + 0x8450, 0xDFE1, 0x8451, 0xDFB1, 0x8452, 0xDFD2, 0x8454, 0xDFDF, 0x8456, 0xDFAB, 0x8457, 0xB5DB, 0x8459, 0xDFB9, 0x845A, 0xDFB8, + 0x845B, 0xB8AF, 0x845D, 0xDFBC, 0x845E, 0xDFBE, 0x845F, 0xDFCD, 0x8460, 0xDFDE, 0x8461, 0xB8B2, 0x8463, 0xB8B3, 0x8465, 0xDFB0, + 0x8466, 0xB8AB, 0x8467, 0xDFB4, 0x8468, 0xDFDA, 0x8469, 0xB8B4, 0x846B, 0xB8AC, 0x846C, 0xB8AE, 0x846D, 0xB8B5, 0x846E, 0xDFE0, + 0x846F, 0xDFD3, 0x8470, 0xDFCE, 0x8473, 0xDFBB, 0x8474, 0xDFBA, 0x8475, 0xB8AA, 0x8476, 0xDFAC, 0x8477, 0xB8A7, 0x8478, 0xDFC4, + 0x8479, 0xDFAD, 0x847A, 0xDFC2, 0x847D, 0xDFB7, 0x847E, 0xDFDB, 0x8482, 0xB8A6, 0x8486, 0xDFB3, 0x848D, 0xDFAF, 0x848E, 0xDFD5, + 0x848F, 0xDFAE, 0x8490, 0xBB60, 0x8491, 0xE3D3, 0x8494, 0xE3C2, 0x8497, 0xE3AC, 0x8498, 0xE3CA, 0x8499, 0xBB58, 0x849A, 0xE3BB, + 0x849B, 0xE3C5, 0x849C, 0xBB5B, 0x849D, 0xE3BE, 0x849E, 0xBB59, 0x849F, 0xE3AF, 0x84A0, 0xE3CD, 0x84A1, 0xE3AE, 0x84A2, 0xE3C1, + 0x84A4, 0xE3AD, 0x84A7, 0xE3BF, 0x84A8, 0xE3C8, 0x84A9, 0xE3C6, 0x84AA, 0xE3BA, 0x84AB, 0xE3B5, 0x84AC, 0xE3B3, 0x84AE, 0xE3B4, + 0x84AF, 0xE3C7, 0x84B0, 0xE3D2, 0x84B1, 0xE3BC, 0x84B2, 0xBB5A, 0x84B4, 0xE3B7, 0x84B6, 0xE3CB, 0x84B8, 0xBB5D, 0x84B9, 0xE3B6, + 0x84BA, 0xE3B0, 0x84BB, 0xE3C0, 0x84BC, 0xBB61, 0x84BF, 0xBB55, 0x84C0, 0xBB5E, 0x84C1, 0xE3B8, 0x84C2, 0xE3B2, 0x84C4, 0xBB57, + 0x84C5, 0xDFD4, 0x84C6, 0xBB56, 0x84C7, 0xE3C3, 0x84C9, 0xBB54, 0x84CA, 0xBB63, 0x84CB, 0xBB5C, 0x84CC, 0xE3C4, 0x84CD, 0xE3B9, + 0x84CE, 0xE3B1, 0x84CF, 0xE3CC, 0x84D0, 0xE3BD, 0x84D1, 0xBB62, 0x84D2, 0xE3D0, 0x84D3, 0xBB5F, 0x84D4, 0xE3CF, 0x84D6, 0xE3C9, + 0x84D7, 0xE3CE, 0x84DB, 0xE3D1, 0x84E7, 0xE773, 0x84E8, 0xE774, 0x84E9, 0xE767, 0x84EA, 0xE766, 0x84EB, 0xE762, 0x84EC, 0xBDB4, + 0x84EE, 0xBDAC, 0x84EF, 0xE776, 0x84F0, 0xE775, 0x84F1, 0xDFA9, 0x84F2, 0xE75F, 0x84F3, 0xE763, 0x84F4, 0xE75D, 0x84F6, 0xE770, + 0x84F7, 0xE761, 0x84F9, 0xE777, 0x84FA, 0xE75A, 0x84FB, 0xE758, 0x84FC, 0xE764, 0x84FD, 0xE76E, 0x84FE, 0xE769, 0x84FF, 0xBDB6, + 0x8500, 0xE74F, 0x8502, 0xE76D, 0x8506, 0xBDB7, 0x8507, 0xDFBD, 0x8508, 0xE75B, 0x8509, 0xE752, 0x850A, 0xE755, 0x850B, 0xE77B, + 0x850C, 0xE75C, 0x850D, 0xE753, 0x850E, 0xE751, 0x850F, 0xE74E, 0x8511, 0xBDB0, 0x8512, 0xE765, 0x8513, 0xBDAF, 0x8514, 0xBDB3, + 0x8515, 0xE760, 0x8516, 0xE768, 0x8517, 0xBDA9, 0x8518, 0xE778, 0x8519, 0xE77C, 0x851A, 0xBDAB, 0x851C, 0xE757, 0x851D, 0xE76B, + 0x851E, 0xE76F, 0x851F, 0xE754, 0x8520, 0xE779, 0x8521, 0xBDB2, 0x8523, 0xBDB1, 0x8524, 0xE74C, 0x8525, 0xBDB5, 0x8526, 0xE772, + 0x8527, 0xE756, 0x8528, 0xE76A, 0x8529, 0xE750, 0x852A, 0xE75E, 0x852B, 0xE759, 0x852C, 0xBDAD, 0x852D, 0xBDAE, 0x852E, 0xE76C, + 0x852F, 0xE77D, 0x8530, 0xE77A, 0x8531, 0xE771, 0x853B, 0xE74D, 0x853D, 0xBDAA, 0x853E, 0xEB49, 0x8540, 0xEB40, 0x8541, 0xEB43, + 0x8543, 0xBFBB, 0x8544, 0xEB45, 0x8545, 0xEAF9, 0x8546, 0xEB41, 0x8547, 0xEB47, 0x8548, 0xBFB8, 0x8549, 0xBFBC, 0x854A, 0xBFB6, + 0x854D, 0xEAFB, 0x854E, 0xEB4C, 0x8551, 0xEB46, 0x8553, 0xEAFC, 0x8554, 0xEB55, 0x8555, 0xEB4F, 0x8556, 0xEAF8, 0x8557, 0xEE46, + 0x8558, 0xEAFE, 0x8559, 0xBFB7, 0x855B, 0xEB4A, 0x855D, 0xEB54, 0x855E, 0xBFBF, 0x8560, 0xEB51, 0x8561, 0xEAFD, 0x8562, 0xEB44, + 0x8563, 0xEB48, 0x8564, 0xEB42, 0x8565, 0xEB56, 0x8566, 0xEB53, 0x8567, 0xEB50, 0x8568, 0xBFB9, 0x8569, 0xBFBA, 0x856A, 0xBFBE, + 0x856B, 0xEAFA, 0x856C, 0xEB57, 0x856D, 0xBFBD, 0x856E, 0xEB4D, 0x8571, 0xEB4B, 0x8575, 0xEB4E, 0x8576, 0xEE53, 0x8577, 0xEE40, + 0x8578, 0xEE45, 0x8579, 0xEE52, 0x857A, 0xEE44, 0x857B, 0xEDFB, 0x857C, 0xEE41, 0x857E, 0xC1A2, 0x8580, 0xEDF4, 0x8581, 0xEE4D, + 0x8582, 0xEE4F, 0x8583, 0xEDF3, 0x8584, 0xC1A1, 0x8585, 0xEE51, 0x8586, 0xEE49, 0x8587, 0xC1A8, 0x8588, 0xEE50, 0x8589, 0xEE42, + 0x858A, 0xC1AA, 0x858B, 0xEDF9, 0x858C, 0xEB52, 0x858D, 0xEE4A, 0x858E, 0xEE47, 0x858F, 0xEDF5, 0x8590, 0xEE55, 0x8591, 0xC1A4, + 0x8594, 0xC1A5, 0x8595, 0xEDF7, 0x8596, 0xEE48, 0x8598, 0xEE54, 0x8599, 0xEE4B, 0x859A, 0xEDFD, 0x859B, 0xC1A7, 0x859C, 0xC1A3, + 0x859D, 0xEE4C, 0x859E, 0xEDFE, 0x859F, 0xEE56, 0x85A0, 0xEDF8, 0x85A1, 0xEE43, 0x85A2, 0xEE4E, 0x85A3, 0xEDFA, 0x85A4, 0xEDFC, + 0x85A6, 0xC2CB, 0x85A7, 0xEDF6, 0x85A8, 0xC1A9, 0x85A9, 0xC2C4, 0x85AA, 0xC17E, 0x85AF, 0xC1A6, 0x85B0, 0xC2C8, 0x85B1, 0xF0B3, + 0x85B3, 0xF0A9, 0x85B4, 0xF0A4, 0x85B5, 0xF0AA, 0x85B6, 0xF0B4, 0x85B7, 0xF0B8, 0x85B8, 0xF0B7, 0x85B9, 0xC2CA, 0x85BA, 0xC2C9, + 0x85BD, 0xF0AB, 0x85BE, 0xF0B9, 0x85BF, 0xF0AE, 0x85C0, 0xF0A6, 0x85C2, 0xF0A8, 0x85C3, 0xF0A7, 0x85C4, 0xF0AD, 0x85C5, 0xF0B2, + 0x85C6, 0xF0A5, 0x85C7, 0xF0AC, 0x85C8, 0xF0B1, 0x85C9, 0xC2C7, 0x85CB, 0xF0AF, 0x85CD, 0xC2C5, 0x85CE, 0xF0B0, 0x85CF, 0xC2C3, + 0x85D0, 0xC2C6, 0x85D1, 0xF2D5, 0x85D2, 0xF0B5, 0x85D5, 0xC3C2, 0x85D7, 0xF2CD, 0x85D8, 0xF2D1, 0x85D9, 0xF2C9, 0x85DA, 0xF2CC, + 0x85DC, 0xF2D4, 0x85DD, 0xC3C0, 0x85DE, 0xF2D9, 0x85DF, 0xF2D2, 0x85E1, 0xF2CA, 0x85E2, 0xF2DA, 0x85E3, 0xF2D3, 0x85E4, 0xC3C3, + 0x85E5, 0xC3C4, 0x85E6, 0xF2D7, 0x85E8, 0xF2CB, 0x85E9, 0xC3BF, 0x85EA, 0xC3C1, 0x85EB, 0xF2C6, 0x85EC, 0xF2CE, 0x85ED, 0xF2C8, + 0x85EF, 0xF2D8, 0x85F0, 0xF2D6, 0x85F1, 0xF2C7, 0x85F2, 0xF2CF, 0x85F6, 0xF4BE, 0x85F7, 0xC3C5, 0x85F8, 0xF2D0, 0x85F9, 0xC4A7, + 0x85FA, 0xC4A9, 0x85FB, 0xC4A6, 0x85FD, 0xF4C3, 0x85FE, 0xF4BB, 0x85FF, 0xF4B9, 0x8600, 0xF4BD, 0x8601, 0xF4BA, 0x8604, 0xF4BF, + 0x8605, 0xF4C1, 0x8606, 0xC4AA, 0x8607, 0xC4AC, 0x8609, 0xF4C0, 0x860A, 0xC4AD, 0x860B, 0xC4AB, 0x860C, 0xF4C2, 0x8611, 0xC4A8, + 0x8617, 0xC4F4, 0x8618, 0xF5F1, 0x8619, 0xF5F7, 0x861A, 0xC4F6, 0x861B, 0xF4BC, 0x861C, 0xF5F6, 0x861E, 0xF5FD, 0x861F, 0xF5F4, + 0x8620, 0xF5FB, 0x8621, 0xF5FA, 0x8622, 0xF4B8, 0x8623, 0xF5F5, 0x8624, 0xF0B6, 0x8625, 0xF5FE, 0x8626, 0xF5F3, 0x8627, 0xF5F8, + 0x8629, 0xF5FC, 0x862A, 0xF5F2, 0x862C, 0xF74A, 0x862D, 0xC4F5, 0x862E, 0xF5F9, 0x8631, 0xF7F4, 0x8632, 0xF74B, 0x8633, 0xF749, + 0x8634, 0xF747, 0x8635, 0xF748, 0x8636, 0xF74C, 0x8638, 0xC5D9, 0x8639, 0xF7F2, 0x863A, 0xF7F0, 0x863B, 0xF7F5, 0x863C, 0xF7F3, + 0x863E, 0xF7F6, 0x863F, 0xC5DA, 0x8640, 0xF7F1, 0x8643, 0xF8BC, 0x8646, 0xF945, 0x8647, 0xF946, 0x8648, 0xF947, 0x864B, 0xF9C7, + 0x864C, 0xF9BD, 0x864D, 0xCA4F, 0x864E, 0xAAEA, 0x8650, 0xAD68, 0x8652, 0xD3B8, 0x8653, 0xD3B7, 0x8654, 0xB040, 0x8655, 0xB342, + 0x8656, 0xD77C, 0x8659, 0xD77B, 0x865B, 0xB5EA, 0x865C, 0xB8B8, 0x865E, 0xB8B7, 0x865F, 0xB8B9, 0x8661, 0xE3D4, 0x8662, 0xE77E, + 0x8663, 0xEB58, 0x8664, 0xEB5A, 0x8665, 0xEB59, 0x8667, 0xC1AB, 0x8668, 0xEE57, 0x8669, 0xF0BA, 0x866A, 0xF9A5, 0x866B, 0xA6E4, + 0x866D, 0xCDC9, 0x866E, 0xCDCA, 0x866F, 0xCDC8, 0x8670, 0xCDC7, 0x8671, 0xAAEB, 0x8673, 0xD0A9, 0x8674, 0xD0A7, 0x8677, 0xD0A6, + 0x8679, 0xAD69, 0x867A, 0xAD6B, 0x867B, 0xAD6A, 0x867C, 0xD0A8, 0x8685, 0xD3C4, 0x8686, 0xD3C1, 0x8687, 0xD3BF, 0x868A, 0xB041, + 0x868B, 0xD3C2, 0x868C, 0xB046, 0x868D, 0xD3BC, 0x868E, 0xD3CB, 0x8690, 0xD3CD, 0x8691, 0xD3BD, 0x8693, 0xB043, 0x8694, 0xD3CE, + 0x8695, 0xD3C9, 0x8696, 0xD3BB, 0x8697, 0xD3C0, 0x8698, 0xD3CA, 0x8699, 0xD3C6, 0x869A, 0xD3C3, 0x869C, 0xB048, 0x869D, 0xD3CC, + 0x869E, 0xD3BE, 0x86A1, 0xD3C7, 0x86A2, 0xD3B9, 0x86A3, 0xB047, 0x86A4, 0xB044, 0x86A5, 0xD3C5, 0x86A7, 0xD3C8, 0x86A8, 0xD3BA, + 0x86A9, 0xB045, 0x86AA, 0xB042, 0x86AF, 0xB34C, 0x86B0, 0xD7A5, 0x86B1, 0xB34B, 0x86B3, 0xD7A8, 0x86B4, 0xD7AB, 0x86B5, 0xB348, + 0x86B6, 0xB346, 0x86B7, 0xD77E, 0x86B8, 0xD7A9, 0x86B9, 0xD7A7, 0x86BA, 0xD7A4, 0x86BB, 0xD7AC, 0x86BC, 0xD7AD, 0x86BD, 0xD7AF, + 0x86BE, 0xD7B0, 0x86BF, 0xD77D, 0x86C0, 0xB345, 0x86C1, 0xD7A2, 0x86C2, 0xD7A1, 0x86C3, 0xD7AE, 0x86C4, 0xB347, 0x86C5, 0xD7A3, + 0x86C6, 0xB349, 0x86C7, 0xB344, 0x86C8, 0xD7A6, 0x86C9, 0xB34D, 0x86CB, 0xB34A, 0x86CC, 0xD7AA, 0x86D0, 0xB5F1, 0x86D1, 0xDBBF, + 0x86D3, 0xDBB4, 0x86D4, 0xB5EE, 0x86D6, 0xDFE7, 0x86D7, 0xDBBD, 0x86D8, 0xDBB1, 0x86D9, 0xB5EC, 0x86DA, 0xDBB6, 0x86DB, 0xB5EF, + 0x86DC, 0xDBBA, 0x86DD, 0xDBB8, 0x86DE, 0xB5F2, 0x86DF, 0xB5EB, 0x86E2, 0xDBB2, 0x86E3, 0xDBB5, 0x86E4, 0xB5F0, 0x86E6, 0xDBB3, + 0x86E8, 0xDBBE, 0x86E9, 0xDBBC, 0x86EA, 0xDBB7, 0x86EB, 0xDBB9, 0x86EC, 0xDBBB, 0x86ED, 0xB5ED, 0x86F5, 0xDFE8, 0x86F6, 0xDFEE, + 0x86F7, 0xDFE4, 0x86F8, 0xDFEA, 0x86F9, 0xB8BA, 0x86FA, 0xDFE6, 0x86FB, 0xB8C0, 0x86FE, 0xB8BF, 0x8700, 0xB8BE, 0x8701, 0xDFED, + 0x8702, 0xB8C1, 0x8703, 0xB8C2, 0x8704, 0xDFE3, 0x8705, 0xDFF0, 0x8706, 0xB8C3, 0x8707, 0xB8BD, 0x8708, 0xB8BC, 0x8709, 0xDFEC, + 0x870A, 0xB8C4, 0x870B, 0xDFE2, 0x870C, 0xDFE5, 0x870D, 0xDFEF, 0x870E, 0xDFEB, 0x8711, 0xE3F4, 0x8712, 0xE3E9, 0x8713, 0xB8BB, + 0x8718, 0xBB6A, 0x8719, 0xE3DD, 0x871A, 0xE3F2, 0x871B, 0xE3DE, 0x871C, 0xBB65, 0x871E, 0xE3DB, 0x8720, 0xE3E4, 0x8721, 0xE3DC, + 0x8722, 0xBB67, 0x8723, 0xE3D6, 0x8724, 0xE3F1, 0x8725, 0xBB68, 0x8726, 0xE3EE, 0x8727, 0xE3EF, 0x8728, 0xE3D7, 0x8729, 0xBB6D, + 0x872A, 0xE3E6, 0x872C, 0xE3E0, 0x872D, 0xE3E7, 0x872E, 0xE3DA, 0x8730, 0xE3F3, 0x8731, 0xE3EB, 0x8732, 0xE3E5, 0x8733, 0xE3D5, + 0x8734, 0xBB69, 0x8735, 0xE3EC, 0x8737, 0xBB6C, 0x8738, 0xE3F0, 0x873A, 0xE3EA, 0x873B, 0xBB66, 0x873C, 0xE3E8, 0x873E, 0xE3E2, + 0x873F, 0xBB64, 0x8740, 0xE3D9, 0x8741, 0xE3E1, 0x8742, 0xE3ED, 0x8743, 0xE3DF, 0x8746, 0xE3E3, 0x874C, 0xBDC1, 0x874D, 0xDFE9, + 0x874E, 0xE7B2, 0x874F, 0xE7BB, 0x8750, 0xE7B1, 0x8751, 0xE7AD, 0x8752, 0xE7AA, 0x8753, 0xBDC2, 0x8754, 0xE7A8, 0x8755, 0xBB6B, + 0x8756, 0xE7A1, 0x8757, 0xBDC0, 0x8758, 0xE7A7, 0x8759, 0xBDBF, 0x875A, 0xE7AC, 0x875B, 0xE7A9, 0x875C, 0xE7B9, 0x875D, 0xE7B4, + 0x875E, 0xE7AE, 0x875F, 0xE7B3, 0x8760, 0xBDBB, 0x8761, 0xE7AB, 0x8762, 0xE7BE, 0x8763, 0xE7A2, 0x8764, 0xE7A3, 0x8765, 0xE7BA, + 0x8766, 0xBDBC, 0x8767, 0xE7BF, 0x8768, 0xBDBE, 0x8769, 0xE7C0, 0x876A, 0xE7B0, 0x876B, 0xE3D8, 0x876C, 0xE7B6, 0x876D, 0xE7AF, + 0x876E, 0xE7B8, 0x876F, 0xE7B5, 0x8773, 0xE7A6, 0x8774, 0xBDB9, 0x8775, 0xE7BD, 0x8776, 0xBDBA, 0x8777, 0xE7A4, 0x8778, 0xBDBD, + 0x8779, 0xEB64, 0x877A, 0xE7B7, 0x877B, 0xE7BC, 0x8781, 0xEB61, 0x8782, 0xBDB8, 0x8783, 0xBFC0, 0x8784, 0xEB6B, 0x8785, 0xEB67, + 0x8787, 0xEB65, 0x8788, 0xEB60, 0x8789, 0xEB6F, 0x878D, 0xBFC4, 0x878F, 0xEB5C, 0x8790, 0xEB68, 0x8791, 0xEB69, 0x8792, 0xEB5F, + 0x8793, 0xEB5E, 0x8794, 0xEB6C, 0x8796, 0xEB62, 0x8797, 0xEB5D, 0x8798, 0xEB63, 0x879A, 0xEB6E, 0x879B, 0xEB5B, 0x879C, 0xEB6D, + 0x879D, 0xEB6A, 0x879E, 0xBFC2, 0x879F, 0xBFC1, 0x87A2, 0xBFC3, 0x87A3, 0xEB66, 0x87A4, 0xF0CB, 0x87AA, 0xEE59, 0x87AB, 0xC1B1, + 0x87AC, 0xEE5D, 0x87AD, 0xEE5A, 0x87AE, 0xEE61, 0x87AF, 0xEE67, 0x87B0, 0xEE5C, 0x87B2, 0xEE70, 0x87B3, 0xC1AE, 0x87B4, 0xEE6A, + 0x87B5, 0xEE5F, 0x87B6, 0xEE6B, 0x87B7, 0xEE66, 0x87B8, 0xEE6D, 0x87B9, 0xEE5E, 0x87BA, 0xC1B3, 0x87BB, 0xC1B2, 0x87BC, 0xEE60, + 0x87BD, 0xEE6E, 0x87BE, 0xEE58, 0x87BF, 0xEE6C, 0x87C0, 0xC1AC, 0x87C2, 0xEE64, 0x87C3, 0xEE63, 0x87C4, 0xEE68, 0x87C5, 0xEE5B, + 0x87C6, 0xC1B0, 0x87C8, 0xC1B4, 0x87C9, 0xEE62, 0x87CA, 0xEE69, 0x87CB, 0xC1B5, 0x87CC, 0xEE65, 0x87D1, 0xC1AD, 0x87D2, 0xC1AF, + 0x87D3, 0xF0C7, 0x87D4, 0xF0C5, 0x87D7, 0xF0CC, 0x87D8, 0xF0C9, 0x87D9, 0xF0CD, 0x87DB, 0xF0BE, 0x87DC, 0xF0C6, 0x87DD, 0xF0D1, + 0x87DE, 0xEE6F, 0x87DF, 0xF0C2, 0x87E0, 0xC2CF, 0x87E1, 0xE7A5, 0x87E2, 0xF0BD, 0x87E3, 0xF0CA, 0x87E4, 0xF0C4, 0x87E5, 0xF0C1, + 0x87E6, 0xF0BC, 0x87E7, 0xF0BB, 0x87E8, 0xF0D0, 0x87EA, 0xF0C0, 0x87EB, 0xF0BF, 0x87EC, 0xC2CD, 0x87ED, 0xF0C8, 0x87EF, 0xC2CC, + 0x87F2, 0xC2CE, 0x87F3, 0xF0C3, 0x87F4, 0xF0CF, 0x87F6, 0xF2DE, 0x87F7, 0xF2DF, 0x87F9, 0xC3C9, 0x87FA, 0xF2DC, 0x87FB, 0xC3C6, + 0x87FC, 0xF2E4, 0x87FE, 0xC3CA, 0x87FF, 0xF2E6, 0x8800, 0xF2DB, 0x8801, 0xF0CE, 0x8802, 0xF2E8, 0x8803, 0xF2DD, 0x8805, 0xC3C7, + 0x8806, 0xF2E3, 0x8808, 0xF2E5, 0x8809, 0xF2E0, 0x880A, 0xF2E7, 0x880B, 0xF2E2, 0x880C, 0xF2E1, 0x880D, 0xC3C8, 0x8810, 0xF4C5, + 0x8811, 0xF4C6, 0x8813, 0xF4C8, 0x8814, 0xC4AE, 0x8815, 0xC4AF, 0x8816, 0xF4C9, 0x8817, 0xF4C7, 0x8819, 0xF4C4, 0x881B, 0xF642, + 0x881C, 0xF645, 0x881D, 0xF641, 0x881F, 0xC4FA, 0x8820, 0xF643, 0x8821, 0xC4F9, 0x8822, 0xC4F8, 0x8823, 0xC4F7, 0x8824, 0xF644, + 0x8825, 0xF751, 0x8826, 0xF74F, 0x8828, 0xF74E, 0x8829, 0xF640, 0x882A, 0xF750, 0x882B, 0xF646, 0x882C, 0xF74D, 0x882E, 0xF7F9, + 0x882F, 0xF7D7, 0x8830, 0xF7F7, 0x8831, 0xC5DB, 0x8832, 0xF7F8, 0x8833, 0xF7FA, 0x8835, 0xF8BF, 0x8836, 0xC5FA, 0x8837, 0xF8BE, + 0x8838, 0xF8BD, 0x8839, 0xC5FB, 0x883B, 0xC65A, 0x883C, 0xF96E, 0x883D, 0xF9A7, 0x883E, 0xF9A6, 0x883F, 0xF9A8, 0x8840, 0xA6E5, + 0x8841, 0xD0AA, 0x8843, 0xD3CF, 0x8844, 0xD3D0, 0x8848, 0xDBC0, 0x884A, 0xF647, 0x884B, 0xF8C0, 0x884C, 0xA6E6, 0x884D, 0xAD6C, + 0x884E, 0xD0AB, 0x8852, 0xD7B1, 0x8853, 0xB34E, 0x8855, 0xDBC2, 0x8856, 0xDBC1, 0x8857, 0xB5F3, 0x8859, 0xB8C5, 0x885A, 0xE7C1, + 0x885B, 0xBDC3, 0x885D, 0xBDC4, 0x8861, 0xBFC5, 0x8862, 0xC5FC, 0x8863, 0xA6E7, 0x8867, 0xD0AC, 0x8868, 0xAAED, 0x8869, 0xD0AE, + 0x886A, 0xD0AD, 0x886B, 0xAD6D, 0x886D, 0xD3D1, 0x886F, 0xD3D8, 0x8870, 0xB049, 0x8871, 0xD3D6, 0x8872, 0xD3D4, 0x8874, 0xD3DB, + 0x8875, 0xD3D2, 0x8876, 0xD3D3, 0x8877, 0xB04A, 0x8879, 0xB04E, 0x887C, 0xD3DC, 0x887D, 0xB04D, 0x887E, 0xD3DA, 0x887F, 0xD3D7, + 0x8880, 0xD3D5, 0x8881, 0xB04B, 0x8882, 0xB04C, 0x8883, 0xD3D9, 0x8888, 0xB350, 0x8889, 0xD7B2, 0x888B, 0xB355, 0x888C, 0xD7C2, + 0x888D, 0xB354, 0x888E, 0xD7C4, 0x8891, 0xD7B8, 0x8892, 0xB352, 0x8893, 0xD7C3, 0x8895, 0xD7B3, 0x8896, 0xB353, 0x8897, 0xD7BF, + 0x8898, 0xD7BB, 0x8899, 0xD7BD, 0x889A, 0xD7B7, 0x889B, 0xD7BE, 0x889E, 0xB34F, 0x889F, 0xD7BA, 0x88A1, 0xD7B9, 0x88A2, 0xD7B5, + 0x88A4, 0xD7C0, 0x88A7, 0xD7BC, 0x88A8, 0xD7B4, 0x88AA, 0xD7B6, 0x88AB, 0xB351, 0x88AC, 0xD7C1, 0x88B1, 0xB5F6, 0x88B2, 0xDBCD, + 0x88B6, 0xDBC9, 0x88B7, 0xDBCB, 0x88B8, 0xDBC6, 0x88B9, 0xDBC5, 0x88BA, 0xDBC3, 0x88BC, 0xDBCA, 0x88BD, 0xDBCC, 0x88BE, 0xDBC8, + 0x88C0, 0xDBC7, 0x88C1, 0xB5F4, 0x88C2, 0xB5F5, 0x88C9, 0xDBCF, 0x88CA, 0xB8CD, 0x88CB, 0xDFF2, 0x88CC, 0xDFF8, 0x88CD, 0xDFF3, + 0x88CE, 0xDFF4, 0x88CF, 0xF9D8, 0x88D0, 0xDFF9, 0x88D2, 0xB8CF, 0x88D4, 0xB8C7, 0x88D5, 0xB8CE, 0x88D6, 0xDFF1, 0x88D7, 0xDBC4, + 0x88D8, 0xB8CA, 0x88D9, 0xB8C8, 0x88DA, 0xDFF7, 0x88DB, 0xDFF6, 0x88DC, 0xB8C9, 0x88DD, 0xB8CB, 0x88DE, 0xDFF5, 0x88DF, 0xB8C6, + 0x88E1, 0xB8CC, 0x88E7, 0xE3F6, 0x88E8, 0xBB74, 0x88EB, 0xE442, 0x88EC, 0xE441, 0x88EE, 0xE3FB, 0x88EF, 0xBB76, 0x88F0, 0xE440, + 0x88F1, 0xE3F7, 0x88F2, 0xE3F8, 0x88F3, 0xBB6E, 0x88F4, 0xBB70, 0x88F6, 0xE3FD, 0x88F7, 0xE3F5, 0x88F8, 0xBB72, 0x88F9, 0xBB71, + 0x88FA, 0xE3F9, 0x88FB, 0xE3FE, 0x88FC, 0xE3FC, 0x88FD, 0xBB73, 0x88FE, 0xE3FA, 0x8901, 0xDBCE, 0x8902, 0xBB6F, 0x8905, 0xE7C2, + 0x8906, 0xE7C9, 0x8907, 0xBDC6, 0x8909, 0xE7CD, 0x890A, 0xBDCA, 0x890B, 0xE7C5, 0x890C, 0xE7C3, 0x890E, 0xE7CC, 0x8910, 0xBDC5, + 0x8911, 0xE7CB, 0x8912, 0xBDC7, 0x8913, 0xBDC8, 0x8914, 0xE7C4, 0x8915, 0xBDC9, 0x8916, 0xE7CA, 0x8917, 0xE7C6, 0x8918, 0xE7C7, + 0x8919, 0xE7C8, 0x891A, 0xBB75, 0x891E, 0xEB70, 0x891F, 0xEB7C, 0x8921, 0xBFCA, 0x8922, 0xEB77, 0x8923, 0xEB79, 0x8925, 0xBFC8, + 0x8926, 0xEB71, 0x8927, 0xEB75, 0x8929, 0xEB78, 0x892A, 0xBFC6, 0x892B, 0xBFC9, 0x892C, 0xEB7B, 0x892D, 0xEB73, 0x892E, 0xEB74, + 0x892F, 0xEB7A, 0x8930, 0xEB72, 0x8931, 0xEB76, 0x8932, 0xBFC7, 0x8933, 0xEE72, 0x8935, 0xEE71, 0x8936, 0xC1B7, 0x8937, 0xEE77, + 0x8938, 0xC1B9, 0x893B, 0xC1B6, 0x893C, 0xEE73, 0x893D, 0xC1BA, 0x893E, 0xEE74, 0x8941, 0xEE75, 0x8942, 0xEE78, 0x8944, 0xC1B8, + 0x8946, 0xF0D6, 0x8949, 0xF0D9, 0x894B, 0xF0D3, 0x894C, 0xF0D5, 0x894F, 0xF0D4, 0x8950, 0xF0D7, 0x8951, 0xF0D8, 0x8952, 0xEE76, + 0x8953, 0xF0D2, 0x8956, 0xC3CD, 0x8957, 0xF2EC, 0x8958, 0xF2EF, 0x8959, 0xF2F1, 0x895A, 0xF2EA, 0x895B, 0xF2EB, 0x895C, 0xF2EE, + 0x895D, 0xF2F0, 0x895E, 0xC3CE, 0x895F, 0xC3CC, 0x8960, 0xC3CB, 0x8961, 0xF2ED, 0x8962, 0xF2E9, 0x8963, 0xF4CA, 0x8964, 0xC4B0, + 0x8966, 0xF4CB, 0x8969, 0xF649, 0x896A, 0xC4FB, 0x896B, 0xF64B, 0x896C, 0xC4FC, 0x896D, 0xF648, 0x896E, 0xF64A, 0x896F, 0xC5A8, + 0x8971, 0xF752, 0x8972, 0xC5A7, 0x8973, 0xF7FD, 0x8974, 0xF7FC, 0x8976, 0xF7FB, 0x8979, 0xF948, 0x897A, 0xF949, 0x897B, 0xF94B, + 0x897C, 0xF94A, 0x897E, 0xCA50, 0x897F, 0xA6E8, 0x8981, 0xAD6E, 0x8982, 0xD7C5, 0x8983, 0xB5F7, 0x8985, 0xDFFA, 0x8986, 0xC2D0, + 0x8988, 0xF2F2, 0x898B, 0xA8A3, 0x898F, 0xB357, 0x8993, 0xB356, 0x8995, 0xDBD0, 0x8996, 0xB5F8, 0x8997, 0xDBD2, 0x8998, 0xDBD1, + 0x899B, 0xDFFB, 0x899C, 0xB8D0, 0x899D, 0xE443, 0x899E, 0xE446, 0x899F, 0xE445, 0x89A1, 0xE444, 0x89A2, 0xE7CE, 0x89A3, 0xE7D0, + 0x89A4, 0xE7CF, 0x89A6, 0xBFCC, 0x89AA, 0xBFCB, 0x89AC, 0xC1BB, 0x89AD, 0xEE79, 0x89AE, 0xEE7B, 0x89AF, 0xEE7A, 0x89B2, 0xC2D1, + 0x89B6, 0xF2F4, 0x89B7, 0xF2F3, 0x89B9, 0xF4CC, 0x89BA, 0xC4B1, 0x89BD, 0xC4FD, 0x89BE, 0xF754, 0x89BF, 0xF753, 0x89C0, 0xC65B, + 0x89D2, 0xA8A4, 0x89D3, 0xD0AF, 0x89D4, 0xAD6F, 0x89D5, 0xD7C8, 0x89D6, 0xD7C6, 0x89D9, 0xD7C7, 0x89DA, 0xDBD4, 0x89DB, 0xDBD5, + 0x89DC, 0xE043, 0x89DD, 0xDBD3, 0x89DF, 0xDFFC, 0x89E0, 0xE041, 0x89E1, 0xE040, 0x89E2, 0xE042, 0x89E3, 0xB8D1, 0x89E4, 0xDFFE, + 0x89E5, 0xDFFD, 0x89E6, 0xE044, 0x89E8, 0xE449, 0x89E9, 0xE447, 0x89EB, 0xE448, 0x89EC, 0xE7D3, 0x89ED, 0xE7D1, 0x89F0, 0xE7D2, + 0x89F1, 0xEB7D, 0x89F2, 0xEE7C, 0x89F3, 0xEE7D, 0x89F4, 0xC2D2, 0x89F6, 0xF2F5, 0x89F7, 0xF4CD, 0x89F8, 0xC4B2, 0x89FA, 0xF64C, + 0x89FB, 0xF755, 0x89FC, 0xC5A9, 0x89FE, 0xF7FE, 0x89FF, 0xF94C, 0x8A00, 0xA8A5, 0x8A02, 0xAD71, 0x8A03, 0xAD72, 0x8A04, 0xD0B0, + 0x8A07, 0xD0B1, 0x8A08, 0xAD70, 0x8A0A, 0xB054, 0x8A0C, 0xB052, 0x8A0E, 0xB051, 0x8A0F, 0xB058, 0x8A10, 0xB050, 0x8A11, 0xB059, + 0x8A12, 0xD3DD, 0x8A13, 0xB056, 0x8A15, 0xB053, 0x8A16, 0xB057, 0x8A17, 0xB055, 0x8A18, 0xB04F, 0x8A1B, 0xB35F, 0x8A1D, 0xB359, + 0x8A1E, 0xD7CC, 0x8A1F, 0xB35E, 0x8A22, 0xB360, 0x8A23, 0xB35A, 0x8A25, 0xB35B, 0x8A27, 0xD7CA, 0x8A2A, 0xB358, 0x8A2C, 0xD7CB, + 0x8A2D, 0xB35D, 0x8A30, 0xD7C9, 0x8A31, 0xB35C, 0x8A34, 0xB644, 0x8A36, 0xB646, 0x8A39, 0xDBD8, 0x8A3A, 0xB645, 0x8A3B, 0xB5F9, + 0x8A3C, 0xB5FD, 0x8A3E, 0xB8E4, 0x8A3F, 0xE049, 0x8A40, 0xDBDA, 0x8A41, 0xB5FE, 0x8A44, 0xDBDD, 0x8A45, 0xDBDE, 0x8A46, 0xB643, + 0x8A48, 0xDBE0, 0x8A4A, 0xDBE2, 0x8A4C, 0xDBE3, 0x8A4D, 0xDBD7, 0x8A4E, 0xDBD6, 0x8A4F, 0xDBE4, 0x8A50, 0xB642, 0x8A51, 0xDBE1, + 0x8A52, 0xDBDF, 0x8A54, 0xB640, 0x8A55, 0xB5FB, 0x8A56, 0xB647, 0x8A57, 0xDBDB, 0x8A58, 0xDBDC, 0x8A59, 0xDBD9, 0x8A5B, 0xB641, + 0x8A5E, 0xB5FC, 0x8A60, 0xB5FA, 0x8A61, 0xE048, 0x8A62, 0xB8DF, 0x8A63, 0xB8DA, 0x8A66, 0xB8D5, 0x8A68, 0xB8E5, 0x8A69, 0xB8D6, + 0x8A6B, 0xB8D2, 0x8A6C, 0xB8E1, 0x8A6D, 0xB8DE, 0x8A6E, 0xB8E0, 0x8A70, 0xB8D7, 0x8A71, 0xB8DC, 0x8A72, 0xB8D3, 0x8A73, 0xB8D4, + 0x8A74, 0xE050, 0x8A75, 0xE04D, 0x8A76, 0xE045, 0x8A77, 0xE04A, 0x8A79, 0xB8E2, 0x8A7A, 0xE051, 0x8A7B, 0xB8E3, 0x8A7C, 0xB8D9, + 0x8A7F, 0xE047, 0x8A81, 0xE04F, 0x8A82, 0xE04B, 0x8A83, 0xE04E, 0x8A84, 0xE04C, 0x8A85, 0xB8DD, 0x8A86, 0xE046, 0x8A87, 0xB8D8, + 0x8A8B, 0xE44C, 0x8A8C, 0xBB78, 0x8A8D, 0xBB7B, 0x8A8F, 0xE44E, 0x8A91, 0xBBA5, 0x8A92, 0xE44D, 0x8A93, 0xBB7D, 0x8A95, 0xBDCF, + 0x8A96, 0xE44F, 0x8A98, 0xBBA4, 0x8A99, 0xE44B, 0x8A9A, 0xBBA6, 0x8A9E, 0xBB79, 0x8AA0, 0xB8DB, 0x8AA1, 0xBB7C, 0x8AA3, 0xBB7A, + 0x8AA4, 0xBB7E, 0x8AA5, 0xBBA2, 0x8AA6, 0xBB77, 0x8AA7, 0xBBA7, 0x8AA8, 0xBBA3, 0x8AAA, 0xBBA1, 0x8AAB, 0xE44A, 0x8AB0, 0xBDD6, + 0x8AB2, 0xBDD2, 0x8AB6, 0xBDD9, 0x8AB8, 0xE7D6, 0x8AB9, 0xBDDA, 0x8ABA, 0xE7E2, 0x8ABB, 0xE7DB, 0x8ABC, 0xBDCB, 0x8ABD, 0xE7E3, + 0x8ABE, 0xE7DD, 0x8ABF, 0xBDD5, 0x8AC0, 0xE7DE, 0x8AC2, 0xBDD4, 0x8AC3, 0xE7E1, 0x8AC4, 0xBDCE, 0x8AC5, 0xE7DF, 0x8AC6, 0xE7D5, + 0x8AC7, 0xBDCD, 0x8AC8, 0xEBAA, 0x8AC9, 0xBDD3, 0x8ACB, 0xBDD0, 0x8ACD, 0xBDD8, 0x8ACF, 0xE7D4, 0x8AD1, 0xE7D8, 0x8AD2, 0xBDCC, + 0x8AD3, 0xE7D7, 0x8AD4, 0xE7D9, 0x8AD5, 0xE7DA, 0x8AD6, 0xBDD7, 0x8AD7, 0xE7DC, 0x8AD8, 0xE7E0, 0x8AD9, 0xE7E4, 0x8ADB, 0xBDDB, + 0x8ADC, 0xBFD2, 0x8ADD, 0xEBA5, 0x8ADE, 0xEBAB, 0x8ADF, 0xEBA8, 0x8AE0, 0xEB7E, 0x8AE1, 0xEBAC, 0x8AE2, 0xEBA1, 0x8AE4, 0xEBA7, + 0x8AE6, 0xBFCD, 0x8AE7, 0xBFD3, 0x8AE8, 0xEBAD, 0x8AEB, 0xBFCF, 0x8AED, 0xBFD9, 0x8AEE, 0xBFD4, 0x8AEF, 0xEBAF, 0x8AF0, 0xEBA9, + 0x8AF1, 0xBFD0, 0x8AF2, 0xEBA2, 0x8AF3, 0xBFDA, 0x8AF4, 0xEBA3, 0x8AF5, 0xEBA4, 0x8AF6, 0xBFDB, 0x8AF7, 0xBFD8, 0x8AF8, 0xBDD1, + 0x8AFA, 0xBFCE, 0x8AFB, 0xEBB0, 0x8AFC, 0xBFDC, 0x8AFE, 0xBFD5, 0x8AFF, 0xEBAE, 0x8B00, 0xBFD1, 0x8B01, 0xBFD6, 0x8B02, 0xBFD7, + 0x8B04, 0xC1C3, 0x8B05, 0xEEA4, 0x8B06, 0xEEAD, 0x8B07, 0xEEAA, 0x8B08, 0xEEAC, 0x8B0A, 0xC1C0, 0x8B0B, 0xEEA5, 0x8B0D, 0xEEAB, + 0x8B0E, 0xC1BC, 0x8B0F, 0xEEA7, 0x8B10, 0xC1C4, 0x8B11, 0xEEA3, 0x8B12, 0xEEA8, 0x8B13, 0xEEAF, 0x8B14, 0xEBA6, 0x8B15, 0xEEA9, + 0x8B16, 0xEEA2, 0x8B17, 0xC1BD, 0x8B18, 0xEEA1, 0x8B19, 0xC1BE, 0x8B1A, 0xEEB0, 0x8B1B, 0xC1BF, 0x8B1C, 0xEEAE, 0x8B1D, 0xC1C2, + 0x8B1E, 0xEE7E, 0x8B20, 0xC1C1, 0x8B22, 0xEEA6, 0x8B23, 0xF0DC, 0x8B24, 0xF0EA, 0x8B25, 0xF0E5, 0x8B26, 0xF0E7, 0x8B27, 0xF0DB, + 0x8B28, 0xC2D3, 0x8B2A, 0xF0DA, 0x8B2B, 0xC2D6, 0x8B2C, 0xC2D5, 0x8B2E, 0xF0E9, 0x8B2F, 0xF0E1, 0x8B30, 0xF0DE, 0x8B31, 0xF0E4, + 0x8B33, 0xF0DD, 0x8B35, 0xF0DF, 0x8B36, 0xF0E8, 0x8B37, 0xF0E6, 0x8B39, 0xC2D4, 0x8B3A, 0xF0ED, 0x8B3B, 0xF0EB, 0x8B3C, 0xF0E2, + 0x8B3D, 0xF0EC, 0x8B3E, 0xF0E3, 0x8B40, 0xF2F9, 0x8B41, 0xC3CF, 0x8B42, 0xF341, 0x8B45, 0xF64F, 0x8B46, 0xC3D6, 0x8B47, 0xF0E0, + 0x8B48, 0xF2F7, 0x8B49, 0xC3D2, 0x8B4A, 0xF2F8, 0x8B4B, 0xF2FD, 0x8B4E, 0xC3D4, 0x8B4F, 0xC3D5, 0x8B50, 0xF2F6, 0x8B51, 0xF340, + 0x8B52, 0xF342, 0x8B53, 0xF2FA, 0x8B54, 0xF2FC, 0x8B55, 0xF2FE, 0x8B56, 0xF2FB, 0x8B57, 0xF343, 0x8B58, 0xC3D1, 0x8B59, 0xC3D7, + 0x8B5A, 0xC3D3, 0x8B5C, 0xC3D0, 0x8B5D, 0xF4D0, 0x8B5F, 0xC4B7, 0x8B60, 0xF4CE, 0x8B63, 0xF4D2, 0x8B65, 0xF4D3, 0x8B66, 0xC4B5, + 0x8B67, 0xF4D4, 0x8B68, 0xF4D1, 0x8B6A, 0xF4CF, 0x8B6B, 0xC4B8, 0x8B6C, 0xC4B4, 0x8B6D, 0xF4D5, 0x8B6F, 0xC4B6, 0x8B70, 0xC4B3, + 0x8B74, 0xC4FE, 0x8B77, 0xC540, 0x8B78, 0xF64E, 0x8B79, 0xF64D, 0x8B7A, 0xF650, 0x8B7B, 0xF651, 0x8B7D, 0xC541, 0x8B7E, 0xF756, + 0x8B7F, 0xF75B, 0x8B80, 0xC5AA, 0x8B82, 0xF758, 0x8B84, 0xF757, 0x8B85, 0xF75A, 0x8B86, 0xF759, 0x8B88, 0xF843, 0x8B8A, 0xC5DC, + 0x8B8B, 0xF842, 0x8B8C, 0xF840, 0x8B8E, 0xF841, 0x8B92, 0xC5FE, 0x8B93, 0xC5FD, 0x8B94, 0xF8C1, 0x8B95, 0xF8C2, 0x8B96, 0xC640, + 0x8B98, 0xF94D, 0x8B99, 0xF94E, 0x8B9A, 0xC667, 0x8B9C, 0xC66D, 0x8B9E, 0xF9A9, 0x8B9F, 0xF9C8, 0x8C37, 0xA8A6, 0x8C39, 0xD7CD, + 0x8C3B, 0xD7CE, 0x8C3C, 0xE052, 0x8C3D, 0xE450, 0x8C3E, 0xE7E5, 0x8C3F, 0xC1C6, 0x8C41, 0xC1C5, 0x8C42, 0xF0EE, 0x8C43, 0xF344, + 0x8C45, 0xF844, 0x8C46, 0xA8A7, 0x8C47, 0xD3DE, 0x8C48, 0xB05A, 0x8C49, 0xB361, 0x8C4A, 0xE054, 0x8C4B, 0xE053, 0x8C4C, 0xBDDC, + 0x8C4D, 0xE7E6, 0x8C4E, 0xBDDD, 0x8C4F, 0xEEB1, 0x8C50, 0xC2D7, 0x8C54, 0xC676, 0x8C55, 0xA8A8, 0x8C56, 0xCDCB, 0x8C57, 0xD3DF, + 0x8C5A, 0xB362, 0x8C5C, 0xD7CF, 0x8C5D, 0xD7D0, 0x8C5F, 0xDBE5, 0x8C61, 0xB648, 0x8C62, 0xB8E6, 0x8C64, 0xE056, 0x8C65, 0xE055, + 0x8C66, 0xE057, 0x8C68, 0xE451, 0x8C69, 0xE452, 0x8C6A, 0xBBA8, 0x8C6B, 0xBFDD, 0x8C6C, 0xBDDE, 0x8C6D, 0xBFDE, 0x8C6F, 0xEEB5, + 0x8C70, 0xEEB2, 0x8C71, 0xEEB4, 0x8C72, 0xEEB3, 0x8C73, 0xC1C7, 0x8C75, 0xF0EF, 0x8C76, 0xF346, 0x8C77, 0xF345, 0x8C78, 0xCBA4, + 0x8C79, 0xB05C, 0x8C7A, 0xB05B, 0x8C7B, 0xD3E0, 0x8C7D, 0xD7D1, 0x8C80, 0xDBE7, 0x8C81, 0xDBE6, 0x8C82, 0xB649, 0x8C84, 0xE059, + 0x8C85, 0xE05A, 0x8C86, 0xE058, 0x8C89, 0xB8E8, 0x8C8A, 0xB8E7, 0x8C8C, 0xBBAA, 0x8C8D, 0xBBA9, 0x8C8F, 0xE7E7, 0x8C90, 0xEBB3, + 0x8C91, 0xEBB1, 0x8C92, 0xEBB2, 0x8C93, 0xBFDF, 0x8C94, 0xEEB7, 0x8C95, 0xEEB6, 0x8C97, 0xF0F2, 0x8C98, 0xF0F1, 0x8C99, 0xF0F0, + 0x8C9A, 0xF347, 0x8C9C, 0xF9AA, 0x8C9D, 0xA8A9, 0x8C9E, 0xAD73, 0x8CA0, 0xAD74, 0x8CA1, 0xB05D, 0x8CA2, 0xB05E, 0x8CA3, 0xD3E2, + 0x8CA4, 0xD3E1, 0x8CA5, 0xD7D2, 0x8CA7, 0xB368, 0x8CA8, 0xB366, 0x8CA9, 0xB363, 0x8CAA, 0xB367, 0x8CAB, 0xB365, 0x8CAC, 0xB364, + 0x8CAF, 0xB64A, 0x8CB0, 0xDBEA, 0x8CB2, 0xB8ED, 0x8CB3, 0xB64C, 0x8CB4, 0xB651, 0x8CB5, 0xDBEC, 0x8CB6, 0xB653, 0x8CB7, 0xB652, + 0x8CB8, 0xB655, 0x8CB9, 0xDBEB, 0x8CBA, 0xDBE8, 0x8CBB, 0xB64F, 0x8CBC, 0xB64B, 0x8CBD, 0xB64D, 0x8CBE, 0xDBE9, 0x8CBF, 0xB654, + 0x8CC0, 0xB650, 0x8CC1, 0xB64E, 0x8CC2, 0xB8EF, 0x8CC3, 0xB8EE, 0x8CC4, 0xB8EC, 0x8CC5, 0xB8F0, 0x8CC7, 0xB8EA, 0x8CC8, 0xB8EB, + 0x8CCA, 0xB8E9, 0x8CCC, 0xE05B, 0x8CCF, 0xE454, 0x8CD1, 0xBBAC, 0x8CD2, 0xBBAD, 0x8CD3, 0xBBAB, 0x8CD5, 0xE453, 0x8CD7, 0xE455, + 0x8CD9, 0xE7EA, 0x8CDA, 0xE7EC, 0x8CDC, 0xBDE7, 0x8CDD, 0xE7ED, 0x8CDE, 0xBDE0, 0x8CDF, 0xE7E9, 0x8CE0, 0xBDDF, 0x8CE1, 0xBDE9, + 0x8CE2, 0xBDE5, 0x8CE3, 0xBDE6, 0x8CE4, 0xBDE2, 0x8CE5, 0xE7E8, 0x8CE6, 0xBDE1, 0x8CE7, 0xE7EE, 0x8CE8, 0xE7EB, 0x8CEA, 0xBDE8, + 0x8CEC, 0xBDE3, 0x8CED, 0xBDE4, 0x8CEE, 0xEBB5, 0x8CF0, 0xEBB7, 0x8CF1, 0xEBB6, 0x8CF3, 0xEBB8, 0x8CF4, 0xBFE0, 0x8CF5, 0xEBB4, + 0x8CF8, 0xC1CB, 0x8CF9, 0xEEB8, 0x8CFA, 0xC1C8, 0x8CFB, 0xC1CC, 0x8CFC, 0xC1CA, 0x8CFD, 0xC1C9, 0x8CFE, 0xF0F3, 0x8D00, 0xF0F6, + 0x8D02, 0xF0F5, 0x8D04, 0xF0F4, 0x8D05, 0xC2D8, 0x8D06, 0xF348, 0x8D07, 0xF349, 0x8D08, 0xC3D8, 0x8D09, 0xF34A, 0x8D0A, 0xC3D9, + 0x8D0D, 0xC4BA, 0x8D0F, 0xC4B9, 0x8D10, 0xF652, 0x8D13, 0xC542, 0x8D14, 0xF653, 0x8D15, 0xF75C, 0x8D16, 0xC5AB, 0x8D17, 0xC5AC, + 0x8D19, 0xF845, 0x8D1B, 0xC642, 0x8D64, 0xA8AA, 0x8D66, 0xB36A, 0x8D67, 0xB369, 0x8D68, 0xE05C, 0x8D69, 0xE05D, 0x8D6B, 0xBBAE, + 0x8D6C, 0xEBB9, 0x8D6D, 0xBDEA, 0x8D6E, 0xEBBA, 0x8D6F, 0xEEB9, 0x8D70, 0xA8AB, 0x8D72, 0xD0B2, 0x8D73, 0xAD76, 0x8D74, 0xAD75, + 0x8D76, 0xD3E3, 0x8D77, 0xB05F, 0x8D78, 0xD3E4, 0x8D79, 0xD7D5, 0x8D7B, 0xD7D4, 0x8D7D, 0xD7D3, 0x8D80, 0xDBEE, 0x8D81, 0xB658, + 0x8D84, 0xDBED, 0x8D85, 0xB657, 0x8D89, 0xDBEF, 0x8D8A, 0xB656, 0x8D8C, 0xE05F, 0x8D8D, 0xE062, 0x8D8E, 0xE060, 0x8D8F, 0xE061, + 0x8D90, 0xE065, 0x8D91, 0xE05E, 0x8D92, 0xE066, 0x8D93, 0xE063, 0x8D94, 0xE064, 0x8D95, 0xBBB0, 0x8D96, 0xE456, 0x8D99, 0xBBAF, + 0x8D9B, 0xE7F2, 0x8D9C, 0xE7F0, 0x8D9F, 0xBDEB, 0x8DA0, 0xE7EF, 0x8DA1, 0xE7F1, 0x8DA3, 0xBDEC, 0x8DA5, 0xEBBB, 0x8DA7, 0xEBBC, + 0x8DA8, 0xC1CD, 0x8DAA, 0xF34C, 0x8DAB, 0xF34E, 0x8DAC, 0xF34B, 0x8DAD, 0xF34D, 0x8DAE, 0xF4D6, 0x8DAF, 0xF654, 0x8DB2, 0xF96F, + 0x8DB3, 0xA8AC, 0x8DB4, 0xAD77, 0x8DB5, 0xD3E5, 0x8DB6, 0xD3E7, 0x8DB7, 0xD3E6, 0x8DB9, 0xD7D8, 0x8DBA, 0xB36C, 0x8DBC, 0xD7D6, + 0x8DBE, 0xB36B, 0x8DBF, 0xD7D9, 0x8DC1, 0xD7DA, 0x8DC2, 0xD7D7, 0x8DC5, 0xDBFB, 0x8DC6, 0xB660, 0x8DC7, 0xDBF3, 0x8DC8, 0xDBF9, + 0x8DCB, 0xB65B, 0x8DCC, 0xB65E, 0x8DCD, 0xDBF2, 0x8DCE, 0xB659, 0x8DCF, 0xDBF6, 0x8DD0, 0xE06C, 0x8DD1, 0xB65D, 0x8DD3, 0xDBF1, + 0x8DD5, 0xDBF7, 0x8DD6, 0xDBF4, 0x8DD7, 0xDBFA, 0x8DD8, 0xDBF0, 0x8DD9, 0xDBF8, 0x8DDA, 0xB65C, 0x8DDB, 0xB65F, 0x8DDC, 0xDBF5, + 0x8DDD, 0xB65A, 0x8DDF, 0xB8F2, 0x8DE0, 0xE068, 0x8DE1, 0xB8F1, 0x8DE2, 0xE06F, 0x8DE3, 0xE06E, 0x8DE4, 0xB8F8, 0x8DE6, 0xB8F9, + 0x8DE7, 0xE070, 0x8DE8, 0xB8F3, 0x8DE9, 0xE06D, 0x8DEA, 0xB8F7, 0x8DEB, 0xE072, 0x8DEC, 0xE069, 0x8DEE, 0xE06B, 0x8DEF, 0xB8F4, + 0x8DF0, 0xE067, 0x8DF1, 0xE06A, 0x8DF2, 0xE071, 0x8DF3, 0xB8F5, 0x8DF4, 0xE073, 0x8DFA, 0xB8F6, 0x8DFC, 0xBBB1, 0x8DFD, 0xE45B, + 0x8DFE, 0xE461, 0x8DFF, 0xE459, 0x8E00, 0xE462, 0x8E02, 0xE458, 0x8E03, 0xE45D, 0x8E04, 0xE463, 0x8E05, 0xE460, 0x8E06, 0xE45F, + 0x8E07, 0xE45E, 0x8E09, 0xE457, 0x8E0A, 0xE45C, 0x8E0D, 0xE45A, 0x8E0F, 0xBDF1, 0x8E10, 0xBDEE, 0x8E11, 0xE7FB, 0x8E12, 0xE841, + 0x8E13, 0xE843, 0x8E14, 0xE840, 0x8E15, 0xE7F8, 0x8E16, 0xE7FA, 0x8E17, 0xE845, 0x8E18, 0xE842, 0x8E19, 0xE7FC, 0x8E1A, 0xE846, + 0x8E1B, 0xE7F9, 0x8E1C, 0xE844, 0x8E1D, 0xBDEF, 0x8E1E, 0xBDF5, 0x8E1F, 0xBDF3, 0x8E20, 0xE7F3, 0x8E21, 0xBDF4, 0x8E22, 0xBDF0, + 0x8E23, 0xE7F4, 0x8E24, 0xE7F6, 0x8E25, 0xE7F5, 0x8E26, 0xE7FD, 0x8E27, 0xE7FE, 0x8E29, 0xBDF2, 0x8E2B, 0xBDED, 0x8E2E, 0xE7F7, + 0x8E30, 0xEBC6, 0x8E31, 0xBFE2, 0x8E33, 0xEBBD, 0x8E34, 0xBFE3, 0x8E35, 0xBFE6, 0x8E36, 0xEBC2, 0x8E38, 0xEBBF, 0x8E39, 0xBFE5, + 0x8E3C, 0xEBC3, 0x8E3D, 0xEBC4, 0x8E3E, 0xEBBE, 0x8E3F, 0xEBC7, 0x8E40, 0xEBC0, 0x8E41, 0xEBC5, 0x8E42, 0xBFE4, 0x8E44, 0xBFE1, + 0x8E45, 0xEBC1, 0x8E47, 0xEEBF, 0x8E48, 0xC1D0, 0x8E49, 0xC1CE, 0x8E4A, 0xC1D1, 0x8E4B, 0xC1CF, 0x8E4C, 0xEEBE, 0x8E4D, 0xEEBB, + 0x8E4E, 0xEEBA, 0x8E50, 0xEEBD, 0x8E53, 0xEEBC, 0x8E54, 0xF145, 0x8E55, 0xC2DE, 0x8E56, 0xF0FB, 0x8E57, 0xF0FA, 0x8E59, 0xC2D9, + 0x8E5A, 0xF141, 0x8E5B, 0xF140, 0x8E5C, 0xF0F7, 0x8E5D, 0xF143, 0x8E5E, 0xF0FC, 0x8E5F, 0xC2DD, 0x8E60, 0xF0F9, 0x8E61, 0xF142, + 0x8E62, 0xF0F8, 0x8E63, 0xC2DA, 0x8E64, 0xC2DC, 0x8E65, 0xF0FD, 0x8E66, 0xC2DB, 0x8E67, 0xF0FE, 0x8E69, 0xF144, 0x8E6A, 0xF352, + 0x8E6C, 0xC3DE, 0x8E6D, 0xF34F, 0x8E6F, 0xF353, 0x8E72, 0xC3DB, 0x8E73, 0xF351, 0x8E74, 0xC3E0, 0x8E76, 0xC3DD, 0x8E78, 0xF350, + 0x8E7A, 0xC3DF, 0x8E7B, 0xF354, 0x8E7C, 0xC3DA, 0x8E81, 0xC4BC, 0x8E82, 0xC4BE, 0x8E84, 0xF4D9, 0x8E85, 0xC4BD, 0x8E86, 0xF4D7, + 0x8E87, 0xC3DC, 0x8E88, 0xF4D8, 0x8E89, 0xC4BB, 0x8E8A, 0xC543, 0x8E8B, 0xC545, 0x8E8C, 0xF656, 0x8E8D, 0xC544, 0x8E8E, 0xF655, + 0x8E90, 0xF761, 0x8E91, 0xC5AD, 0x8E92, 0xF760, 0x8E93, 0xC5AE, 0x8E94, 0xF75E, 0x8E95, 0xF75D, 0x8E96, 0xF762, 0x8E97, 0xF763, + 0x8E98, 0xF846, 0x8E9A, 0xF75F, 0x8E9D, 0xF8C6, 0x8E9E, 0xF8C3, 0x8E9F, 0xF8C4, 0x8EA0, 0xF8C5, 0x8EA1, 0xC65C, 0x8EA3, 0xF951, + 0x8EA4, 0xF950, 0x8EA5, 0xF94F, 0x8EA6, 0xF970, 0x8EA8, 0xF9BE, 0x8EA9, 0xF9AB, 0x8EAA, 0xC66E, 0x8EAB, 0xA8AD, 0x8EAC, 0xB060, + 0x8EB2, 0xB8FA, 0x8EBA, 0xBDF6, 0x8EBD, 0xEBC8, 0x8EC0, 0xC2DF, 0x8EC2, 0xF355, 0x8EC9, 0xF9AC, 0x8ECA, 0xA8AE, 0x8ECB, 0xAAEE, + 0x8ECC, 0xAD79, 0x8ECD, 0xAD78, 0x8ECF, 0xB063, 0x8ED1, 0xD3E8, 0x8ED2, 0xB061, 0x8ED3, 0xD3E9, 0x8ED4, 0xB062, 0x8ED7, 0xD7DF, + 0x8ED8, 0xD7DB, 0x8EDB, 0xB36D, 0x8EDC, 0xD7DE, 0x8EDD, 0xD7DD, 0x8EDE, 0xD7DC, 0x8EDF, 0xB36E, 0x8EE0, 0xD7E0, 0x8EE1, 0xD7E1, + 0x8EE5, 0xDC43, 0x8EE6, 0xDC41, 0x8EE7, 0xDC45, 0x8EE8, 0xDC46, 0x8EE9, 0xDC4C, 0x8EEB, 0xDC48, 0x8EEC, 0xDC4A, 0x8EEE, 0xDC42, + 0x8EEF, 0xDBFC, 0x8EF1, 0xDC49, 0x8EF4, 0xDC4B, 0x8EF5, 0xDC44, 0x8EF6, 0xDC47, 0x8EF7, 0xDBFD, 0x8EF8, 0xB662, 0x8EF9, 0xDC40, + 0x8EFA, 0xDBFE, 0x8EFB, 0xB661, 0x8EFC, 0xB663, 0x8EFE, 0xB8FD, 0x8EFF, 0xE075, 0x8F00, 0xE077, 0x8F01, 0xE076, 0x8F02, 0xE07B, + 0x8F03, 0xB8FB, 0x8F05, 0xE078, 0x8F06, 0xE074, 0x8F07, 0xE079, 0x8F08, 0xE07A, 0x8F09, 0xB8FC, 0x8F0A, 0xB8FE, 0x8F0B, 0xE07C, + 0x8F0D, 0xE467, 0x8F0E, 0xE466, 0x8F10, 0xE464, 0x8F11, 0xE465, 0x8F12, 0xBBB3, 0x8F13, 0xBBB5, 0x8F14, 0xBBB2, 0x8F15, 0xBBB4, + 0x8F16, 0xE84D, 0x8F17, 0xE84E, 0x8F18, 0xE849, 0x8F1A, 0xE84A, 0x8F1B, 0xBDF8, 0x8F1C, 0xBDFD, 0x8F1D, 0xBDF7, 0x8F1E, 0xBDFE, + 0x8F1F, 0xBDF9, 0x8F20, 0xE84B, 0x8F23, 0xE84C, 0x8F24, 0xE848, 0x8F25, 0xBE40, 0x8F26, 0xBDFB, 0x8F29, 0xBDFA, 0x8F2A, 0xBDFC, + 0x8F2C, 0xE847, 0x8F2E, 0xEBCA, 0x8F2F, 0xBFE8, 0x8F32, 0xEBCC, 0x8F33, 0xBFEA, 0x8F34, 0xEBCF, 0x8F35, 0xEBCB, 0x8F36, 0xEBC9, + 0x8F37, 0xEBCE, 0x8F38, 0xBFE9, 0x8F39, 0xEBCD, 0x8F3B, 0xBFE7, 0x8F3E, 0xC1D3, 0x8F3F, 0xC1D6, 0x8F40, 0xEEC1, 0x8F42, 0xC1D4, + 0x8F43, 0xEEC0, 0x8F44, 0xC1D2, 0x8F45, 0xC1D5, 0x8F46, 0xF146, 0x8F47, 0xF147, 0x8F48, 0xF148, 0x8F49, 0xC2E0, 0x8F4B, 0xF149, + 0x8F4D, 0xC2E1, 0x8F4E, 0xC3E2, 0x8F4F, 0xF358, 0x8F50, 0xF359, 0x8F51, 0xF357, 0x8F52, 0xF356, 0x8F53, 0xF35A, 0x8F54, 0xC3E1, + 0x8F55, 0xF4DD, 0x8F56, 0xF4DB, 0x8F57, 0xF4DC, 0x8F58, 0xF4DE, 0x8F59, 0xF4DA, 0x8F5A, 0xF4DF, 0x8F5B, 0xF658, 0x8F5D, 0xF659, + 0x8F5E, 0xF657, 0x8F5F, 0xC546, 0x8F60, 0xF764, 0x8F61, 0xC5AF, 0x8F62, 0xF765, 0x8F63, 0xF848, 0x8F64, 0xF847, 0x8F9B, 0xA8AF, + 0x8F9C, 0xB664, 0x8F9F, 0xB940, 0x8FA3, 0xBBB6, 0x8FA6, 0xBFEC, 0x8FA8, 0xBFEB, 0x8FAD, 0xC3E3, 0x8FAE, 0xC47C, 0x8FAF, 0xC547, + 0x8FB0, 0xA8B0, 0x8FB1, 0xB064, 0x8FB2, 0xB941, 0x8FB4, 0xF35B, 0x8FBF, 0xCBA6, 0x8FC2, 0xA8B1, 0x8FC4, 0xA8B4, 0x8FC5, 0xA8B3, + 0x8FC6, 0xA8B2, 0x8FC9, 0xCBA5, 0x8FCB, 0xCDCD, 0x8FCD, 0xCDCF, 0x8FCE, 0xAAEF, 0x8FD1, 0xAAF1, 0x8FD2, 0xCDCC, 0x8FD3, 0xCDCE, + 0x8FD4, 0xAAF0, 0x8FD5, 0xCDD1, 0x8FD6, 0xCDD0, 0x8FD7, 0xCDD2, 0x8FE0, 0xD0B6, 0x8FE1, 0xD0B4, 0x8FE2, 0xAD7C, 0x8FE3, 0xD0B3, + 0x8FE4, 0xADA3, 0x8FE5, 0xAD7E, 0x8FE6, 0xAD7B, 0x8FE8, 0xADA4, 0x8FEA, 0xAD7D, 0x8FEB, 0xADA2, 0x8FED, 0xADA1, 0x8FEE, 0xD0B5, + 0x8FF0, 0xAD7A, 0x8FF4, 0xB06A, 0x8FF5, 0xD3EB, 0x8FF6, 0xD3F1, 0x8FF7, 0xB067, 0x8FF8, 0xB06E, 0x8FFA, 0xB069, 0x8FFB, 0xD3EE, + 0x8FFC, 0xD3F0, 0x8FFD, 0xB06C, 0x8FFE, 0xD3EA, 0x8FFF, 0xD3ED, 0x9000, 0xB068, 0x9001, 0xB065, 0x9002, 0xD3EC, 0x9003, 0xB06B, + 0x9004, 0xD3EF, 0x9005, 0xB06D, 0x9006, 0xB066, 0x900B, 0xD7E3, 0x900C, 0xD7E6, 0x900D, 0xB370, 0x900F, 0xB37A, 0x9010, 0xB376, + 0x9011, 0xD7E4, 0x9014, 0xB37E, 0x9015, 0xB377, 0x9016, 0xB37C, 0x9017, 0xB372, 0x9019, 0xB36F, 0x901A, 0xB371, 0x901B, 0xB37D, + 0x901C, 0xD7E5, 0x901D, 0xB375, 0x901E, 0xB378, 0x901F, 0xB374, 0x9020, 0xB379, 0x9021, 0xD7E7, 0x9022, 0xB37B, 0x9023, 0xB373, + 0x9024, 0xD7E2, 0x902D, 0xDC4D, 0x902E, 0xB665, 0x902F, 0xDC4F, 0x9031, 0xB667, 0x9032, 0xB669, 0x9034, 0xDC4E, 0x9035, 0xB666, + 0x9036, 0xB66A, 0x9038, 0xB668, 0x903C, 0xB947, 0x903D, 0xE0A3, 0x903E, 0xB94F, 0x903F, 0xE07E, 0x9041, 0xB950, 0x9042, 0xB945, + 0x9044, 0xE0A1, 0x9047, 0xB94A, 0x9049, 0xE0A2, 0x904A, 0xB943, 0x904B, 0xB942, 0x904D, 0xB94D, 0x904E, 0xB94C, 0x904F, 0xB94B, + 0x9050, 0xB949, 0x9051, 0xB94E, 0x9052, 0xE07D, 0x9053, 0xB944, 0x9054, 0xB946, 0x9055, 0xB948, 0x9058, 0xBBB8, 0x9059, 0xBBBB, + 0x905B, 0xBBBF, 0x905C, 0xBBB9, 0x905D, 0xBBBE, 0x905E, 0xBBBC, 0x9060, 0xBBB7, 0x9062, 0xBBBD, 0x9063, 0xBBBA, 0x9067, 0xE852, + 0x9068, 0xBE43, 0x9069, 0xBE41, 0x906B, 0xE853, 0x906D, 0xBE44, 0x906E, 0xBE42, 0x906F, 0xE851, 0x9070, 0xE850, 0x9072, 0xBFF0, + 0x9073, 0xE84F, 0x9074, 0xBFEE, 0x9075, 0xBFED, 0x9076, 0xEBD0, 0x9077, 0xBE45, 0x9078, 0xBFEF, 0x9079, 0xEBD1, 0x907A, 0xBFF2, + 0x907B, 0xEBD2, 0x907C, 0xBFF1, 0x907D, 0xC1D8, 0x907E, 0xEEC3, 0x907F, 0xC1D7, 0x9080, 0xC1DC, 0x9081, 0xC1DA, 0x9082, 0xC1DB, + 0x9083, 0xC2E3, 0x9084, 0xC1D9, 0x9085, 0xEEC2, 0x9086, 0xEBD3, 0x9087, 0xC2E2, 0x9088, 0xC2E4, 0x908A, 0xC3E4, 0x908B, 0xC3E5, + 0x908D, 0xF4E0, 0x908F, 0xC5DE, 0x9090, 0xC5DD, 0x9091, 0xA8B6, 0x9094, 0xCA55, 0x9095, 0xB06F, 0x9097, 0xCA52, 0x9098, 0xCA53, + 0x9099, 0xCA51, 0x909B, 0xCA54, 0x909E, 0xCBAA, 0x909F, 0xCBA7, 0x90A0, 0xCBAC, 0x90A1, 0xCBA8, 0x90A2, 0xA8B7, 0x90A3, 0xA8BA, + 0x90A5, 0xCBA9, 0x90A6, 0xA8B9, 0x90A7, 0xCBAB, 0x90AA, 0xA8B8, 0x90AF, 0xCDD5, 0x90B0, 0xCDD7, 0x90B1, 0xAAF4, 0x90B2, 0xCDD3, + 0x90B3, 0xCDD6, 0x90B4, 0xCDD4, 0x90B5, 0xAAF2, 0x90B6, 0xAAF5, 0x90B8, 0xAAF3, 0x90BD, 0xD0B8, 0x90BE, 0xD0BC, 0x90BF, 0xD0B9, + 0x90C1, 0xADA7, 0x90C3, 0xADA8, 0x90C5, 0xD0BB, 0x90C7, 0xD0BD, 0x90C8, 0xD0BF, 0x90CA, 0xADA5, 0x90CB, 0xD0BE, 0x90CE, 0xADA6, + 0x90D4, 0xD7EE, 0x90D5, 0xD0BA, 0x90D6, 0xD3F2, 0x90D7, 0xD3FB, 0x90D8, 0xD3F9, 0x90D9, 0xD3F4, 0x90DA, 0xD3F5, 0x90DB, 0xD3FA, + 0x90DC, 0xD3FC, 0x90DD, 0xB071, 0x90DF, 0xD3F7, 0x90E0, 0xD3F3, 0x90E1, 0xB070, 0x90E2, 0xB072, 0x90E3, 0xD3F6, 0x90E4, 0xD3FD, + 0x90E5, 0xD3F8, 0x90E8, 0xB3A1, 0x90E9, 0xD7F1, 0x90EA, 0xD7E9, 0x90EB, 0xD7EF, 0x90EC, 0xD7F0, 0x90ED, 0xB3A2, 0x90EF, 0xD7E8, + 0x90F0, 0xD7EA, 0x90F1, 0xD0B7, 0x90F2, 0xD7EC, 0x90F3, 0xD7ED, 0x90F4, 0xD7EB, 0x90F5, 0xB66C, 0x90F9, 0xDC56, 0x90FA, 0xEBD4, + 0x90FB, 0xDC57, 0x90FC, 0xDC54, 0x90FD, 0xB3A3, 0x90FE, 0xB66E, 0x90FF, 0xDC53, 0x9100, 0xDC59, 0x9101, 0xDC58, 0x9102, 0xB66B, + 0x9103, 0xDC5C, 0x9104, 0xDC52, 0x9105, 0xDC5B, 0x9106, 0xDC50, 0x9107, 0xDC5A, 0x9108, 0xDC55, 0x9109, 0xB66D, 0x910B, 0xE0AA, + 0x910D, 0xE0A5, 0x910E, 0xE0AB, 0x910F, 0xE0A6, 0x9110, 0xE0A4, 0x9111, 0xE0A7, 0x9112, 0xB951, 0x9114, 0xE0A9, 0x9116, 0xE0A8, + 0x9117, 0xB952, 0x9118, 0xBBC1, 0x9119, 0xBBC0, 0x911A, 0xE46E, 0x911B, 0xE471, 0x911C, 0xE469, 0x911D, 0xE46D, 0x911E, 0xBBC2, + 0x911F, 0xE46C, 0x9120, 0xE46A, 0x9121, 0xE470, 0x9122, 0xE46B, 0x9123, 0xE468, 0x9124, 0xE46F, 0x9126, 0xE859, 0x9127, 0xBE48, + 0x9128, 0xF14A, 0x9129, 0xE856, 0x912A, 0xE857, 0x912B, 0xE855, 0x912C, 0xDC51, 0x912D, 0xBE47, 0x912E, 0xE85A, 0x912F, 0xE854, + 0x9130, 0xBE46, 0x9131, 0xBE49, 0x9132, 0xE858, 0x9133, 0xEBD5, 0x9134, 0xBFF3, 0x9135, 0xEBD6, 0x9136, 0xEBD7, 0x9138, 0xEEC4, + 0x9139, 0xC1DD, 0x913A, 0xF14B, 0x913B, 0xF14C, 0x913E, 0xF14D, 0x913F, 0xF35D, 0x9140, 0xF35C, 0x9141, 0xF4E2, 0x9143, 0xF4E1, + 0x9144, 0xF65B, 0x9145, 0xF65C, 0x9146, 0xF65A, 0x9147, 0xF766, 0x9148, 0xC5B0, 0x9149, 0xA8BB, 0x914A, 0xADAA, 0x914B, 0xADA9, + 0x914C, 0xB075, 0x914D, 0xB074, 0x914E, 0xD440, 0x914F, 0xD441, 0x9150, 0xD3FE, 0x9152, 0xB073, 0x9153, 0xD7F5, 0x9155, 0xD7F6, + 0x9156, 0xD7F2, 0x9157, 0xB3A4, 0x9158, 0xD7F3, 0x915A, 0xD7F4, 0x915F, 0xDC5F, 0x9160, 0xDC61, 0x9161, 0xDC5D, 0x9162, 0xDC60, + 0x9163, 0xB66F, 0x9164, 0xDC5E, 0x9165, 0xB670, 0x9168, 0xDD73, 0x9169, 0xB955, 0x916A, 0xB954, 0x916C, 0xB953, 0x916E, 0xE0AC, + 0x916F, 0xE0AD, 0x9172, 0xE473, 0x9173, 0xE475, 0x9174, 0xBBC6, 0x9175, 0xBBC3, 0x9177, 0xBBC5, 0x9178, 0xBBC4, 0x9179, 0xE474, + 0x917A, 0xE472, 0x9180, 0xE861, 0x9181, 0xE85E, 0x9182, 0xE85F, 0x9183, 0xBE4D, 0x9184, 0xE860, 0x9185, 0xE85B, 0x9186, 0xE85C, + 0x9187, 0xBE4A, 0x9189, 0xBE4B, 0x918A, 0xE85D, 0x918B, 0xBE4C, 0x918D, 0xEBDB, 0x918F, 0xEBDC, 0x9190, 0xEBD9, 0x9191, 0xEBDA, + 0x9192, 0xBFF4, 0x9193, 0xEBD8, 0x9199, 0xEEC8, 0x919A, 0xEEC5, 0x919B, 0xEEC7, 0x919C, 0xC1E0, 0x919D, 0xEECB, 0x919E, 0xC1DF, + 0x919F, 0xEEC9, 0x91A0, 0xEECC, 0x91A1, 0xEECA, 0x91A2, 0xEEC6, 0x91A3, 0xC1DE, 0x91A5, 0xF14F, 0x91A7, 0xF150, 0x91A8, 0xF14E, + 0x91AA, 0xF152, 0x91AB, 0xC2E5, 0x91AC, 0xC2E6, 0x91AD, 0xF35F, 0x91AE, 0xC3E7, 0x91AF, 0xF151, 0x91B0, 0xF35E, 0x91B1, 0xC3E6, + 0x91B2, 0xF4E5, 0x91B3, 0xF4E6, 0x91B4, 0xC4BF, 0x91B5, 0xF4E4, 0x91B7, 0xF4E3, 0x91B9, 0xF65D, 0x91BA, 0xC548, 0x91BC, 0xF849, + 0x91BD, 0xF8C8, 0x91BE, 0xF8C7, 0x91C0, 0xC643, 0x91C1, 0xC65D, 0x91C2, 0xF8C9, 0x91C3, 0xF971, 0x91C5, 0xC66F, 0x91C6, 0xA8BC, + 0x91C7, 0xAAF6, 0x91C9, 0xB956, 0x91CB, 0xC4C0, 0x91CC, 0xA8BD, 0x91CD, 0xADAB, 0x91CE, 0xB3A5, 0x91CF, 0xB671, 0x91D0, 0xC2E7, + 0x91D1, 0xAAF7, 0x91D3, 0xD0C1, 0x91D4, 0xD0C0, 0x91D5, 0xD442, 0x91D7, 0xB078, 0x91D8, 0xB076, 0x91D9, 0xB07A, 0x91DA, 0xD444, + 0x91DC, 0xB079, 0x91DD, 0xB077, 0x91E2, 0xD443, 0x91E3, 0xB3A8, 0x91E4, 0xD7FC, 0x91E6, 0xB3A7, 0x91E7, 0xB3A9, 0x91E8, 0xD842, + 0x91E9, 0xB3AB, 0x91EA, 0xD7FE, 0x91EB, 0xD840, 0x91EC, 0xD7F7, 0x91ED, 0xB3AA, 0x91EE, 0xD843, 0x91F1, 0xD7F9, 0x91F3, 0xD7FA, + 0x91F4, 0xD7F8, 0x91F5, 0xB3A6, 0x91F7, 0xD841, 0x91F8, 0xD7FB, 0x91F9, 0xD7FD, 0x91FD, 0xDC6D, 0x91FF, 0xDC6C, 0x9200, 0xDC6A, + 0x9201, 0xDC62, 0x9202, 0xDC71, 0x9203, 0xDC65, 0x9204, 0xDC6F, 0x9205, 0xDC76, 0x9206, 0xDC6E, 0x9207, 0xB679, 0x9209, 0xB675, + 0x920A, 0xDC63, 0x920C, 0xDC69, 0x920D, 0xB677, 0x920F, 0xDC68, 0x9210, 0xB678, 0x9211, 0xB67A, 0x9212, 0xDC6B, 0x9214, 0xB672, + 0x9215, 0xB673, 0x9216, 0xDC77, 0x9217, 0xDC75, 0x9219, 0xDC74, 0x921A, 0xDC66, 0x921C, 0xDC72, 0x921E, 0xB676, 0x9223, 0xB674, + 0x9224, 0xDC73, 0x9225, 0xDC64, 0x9226, 0xDC67, 0x9227, 0xDC70, 0x922D, 0xE4BA, 0x922E, 0xE0B7, 0x9230, 0xE0B0, 0x9231, 0xE0C3, + 0x9232, 0xE0CC, 0x9233, 0xE0B3, 0x9234, 0xB961, 0x9236, 0xE0C0, 0x9237, 0xB957, 0x9238, 0xB959, 0x9239, 0xB965, 0x923A, 0xE0B1, + 0x923D, 0xB95A, 0x923E, 0xB95C, 0x923F, 0xB966, 0x9240, 0xB95B, 0x9245, 0xB964, 0x9246, 0xE0B9, 0x9248, 0xE0AE, 0x9249, 0xB962, + 0x924A, 0xE0B8, 0x924B, 0xB95E, 0x924C, 0xE0CA, 0x924D, 0xB963, 0x924E, 0xE0C8, 0x924F, 0xE0BC, 0x9250, 0xE0C6, 0x9251, 0xB960, + 0x9252, 0xE0AF, 0x9253, 0xE0C9, 0x9254, 0xE0C4, 0x9256, 0xE0CB, 0x9257, 0xB958, 0x925A, 0xB967, 0x925B, 0xB95D, 0x925E, 0xE0B5, + 0x9260, 0xE0BD, 0x9261, 0xE0C1, 0x9263, 0xE0C5, 0x9264, 0xB95F, 0x9265, 0xE0B4, 0x9266, 0xE0B2, 0x9267, 0xE0BE, 0x926C, 0xE0BB, + 0x926D, 0xE0BA, 0x926F, 0xE0BF, 0x9270, 0xE0C2, 0x9272, 0xE0C7, 0x9276, 0xE478, 0x9278, 0xBBC7, 0x9279, 0xE4A4, 0x927A, 0xE47A, + 0x927B, 0xBBCC, 0x927C, 0xBBD0, 0x927D, 0xE4AD, 0x927E, 0xE4B5, 0x927F, 0xE4A6, 0x9280, 0xBBC8, 0x9282, 0xE4AA, 0x9283, 0xE0B6, + 0x9285, 0xBBC9, 0x9286, 0xE4B1, 0x9287, 0xE4B6, 0x9288, 0xE4AE, 0x928A, 0xE4B0, 0x928B, 0xE4B9, 0x928C, 0xE4B2, 0x928D, 0xE47E, + 0x928E, 0xE4A9, 0x9291, 0xBBD1, 0x9293, 0xBBCD, 0x9294, 0xE47C, 0x9295, 0xE4AB, 0x9296, 0xBBCB, 0x9297, 0xE4A5, 0x9298, 0xBBCA, + 0x9299, 0xE4B3, 0x929A, 0xE4A2, 0x929B, 0xE479, 0x929C, 0xBBCE, 0x929D, 0xE4B8, 0x92A0, 0xE47B, 0x92A1, 0xE4AF, 0x92A2, 0xE4AC, + 0x92A3, 0xE4A7, 0x92A4, 0xE477, 0x92A5, 0xE476, 0x92A6, 0xE4A1, 0x92A7, 0xE4B4, 0x92A8, 0xBBCF, 0x92A9, 0xE4B7, 0x92AA, 0xE47D, + 0x92AB, 0xE4A3, 0x92AC, 0xBE52, 0x92B2, 0xBE5A, 0x92B3, 0xBE55, 0x92B4, 0xE8A4, 0x92B5, 0xE8A1, 0x92B6, 0xE867, 0x92B7, 0xBE50, + 0x92B9, 0xF9D7, 0x92BB, 0xBE4F, 0x92BC, 0xBE56, 0x92C0, 0xE865, 0x92C1, 0xBE54, 0x92C2, 0xE871, 0x92C3, 0xE863, 0x92C4, 0xE864, + 0x92C5, 0xBE4E, 0x92C6, 0xE8A3, 0x92C7, 0xBE58, 0x92C8, 0xE874, 0x92C9, 0xE879, 0x92CA, 0xE873, 0x92CB, 0xEBEE, 0x92CC, 0xE86F, + 0x92CD, 0xE877, 0x92CE, 0xE875, 0x92CF, 0xE868, 0x92D0, 0xE862, 0x92D1, 0xE87D, 0x92D2, 0xBE57, 0x92D3, 0xE87E, 0x92D5, 0xE878, + 0x92D7, 0xE86D, 0x92D8, 0xE86B, 0x92D9, 0xE866, 0x92DD, 0xE86E, 0x92DE, 0xE87B, 0x92DF, 0xE86A, 0x92E0, 0xE87A, 0x92E1, 0xE8A2, + 0x92E4, 0xBE53, 0x92E6, 0xE876, 0x92E7, 0xE87C, 0x92E8, 0xE872, 0x92E9, 0xE86C, 0x92EA, 0xBE51, 0x92EE, 0xE4A8, 0x92EF, 0xE870, + 0x92F0, 0xBE59, 0x92F1, 0xE869, 0x92F7, 0xEBF4, 0x92F8, 0xBFF7, 0x92F9, 0xEBF3, 0x92FA, 0xEBF0, 0x92FB, 0xEC44, 0x92FC, 0xBFFB, + 0x92FE, 0xEC41, 0x92FF, 0xEBF8, 0x9300, 0xEC43, 0x9301, 0xEBE9, 0x9302, 0xEBF6, 0x9304, 0xBFFD, 0x9306, 0xEBE1, 0x9308, 0xEBDF, + 0x9309, 0xEC42, 0x930B, 0xEC40, 0x930C, 0xEBFE, 0x930D, 0xEBED, 0x930E, 0xEBEC, 0x930F, 0xEBE2, 0x9310, 0xC040, 0x9312, 0xEBE8, + 0x9313, 0xEBF2, 0x9314, 0xEBFD, 0x9315, 0xC043, 0x9316, 0xEC45, 0x9318, 0xC1E8, 0x9319, 0xC045, 0x931A, 0xBFFE, 0x931B, 0xEBE6, + 0x931D, 0xEBEF, 0x931E, 0xEBDE, 0x931F, 0xEBE0, 0x9320, 0xBFF5, 0x9321, 0xC042, 0x9322, 0xBFFA, 0x9323, 0xEBE7, 0x9324, 0xEBF7, + 0x9325, 0xEBF1, 0x9326, 0xC041, 0x9327, 0xEBDD, 0x9328, 0xC1E3, 0x9329, 0xEBF9, 0x932A, 0xEBFC, 0x932B, 0xBFFC, 0x932D, 0xEBEB, + 0x932E, 0xC044, 0x932F, 0xBFF9, 0x9333, 0xBFF8, 0x9334, 0xEBF5, 0x9335, 0xEBFB, 0x9336, 0xBFF6, 0x9338, 0xEBE4, 0x9339, 0xEBFA, + 0x933C, 0xEBE5, 0x9346, 0xEBEA, 0x9347, 0xEED2, 0x9349, 0xEED7, 0x934A, 0xC1E5, 0x934B, 0xC1E7, 0x934C, 0xEEDD, 0x934D, 0xC1E1, + 0x934E, 0xEEEC, 0x934F, 0xEEE3, 0x9350, 0xEED8, 0x9351, 0xEED9, 0x9352, 0xEEE2, 0x9354, 0xC1EE, 0x9355, 0xEEE1, 0x9356, 0xEED1, + 0x9357, 0xEEE0, 0x9358, 0xEED4, 0x9359, 0xEEED, 0x935A, 0xC1ED, 0x935B, 0xC1EB, 0x935C, 0xEED5, 0x935E, 0xEEE8, 0x9360, 0xEEDA, + 0x9361, 0xEEE7, 0x9363, 0xEEE9, 0x9364, 0xEED0, 0x9365, 0xC1E6, 0x9367, 0xEEEA, 0x936A, 0xEEDE, 0x936C, 0xC1EA, 0x936D, 0xEEDB, + 0x9370, 0xC1EC, 0x9371, 0xEEE4, 0x9375, 0xC1E4, 0x9376, 0xEED6, 0x9377, 0xEEE5, 0x9379, 0xEEDF, 0x937A, 0xEBE3, 0x937B, 0xEEE6, + 0x937C, 0xEED3, 0x937E, 0xC1E9, 0x9380, 0xEEEB, 0x9382, 0xC1E2, 0x9383, 0xEECE, 0x9388, 0xF160, 0x9389, 0xF159, 0x938A, 0xC2E9, + 0x938C, 0xF154, 0x938D, 0xF163, 0x938E, 0xF15B, 0x938F, 0xEEDC, 0x9391, 0xF165, 0x9392, 0xF155, 0x9394, 0xC2E8, 0x9395, 0xF15F, + 0x9396, 0xC2EA, 0x9397, 0xC2F2, 0x9398, 0xC2F0, 0x9399, 0xF161, 0x939A, 0xC2F1, 0x939B, 0xF157, 0x939D, 0xF158, 0x939E, 0xF15D, + 0x939F, 0xF162, 0x93A1, 0xEECD, 0x93A2, 0xC2EB, 0x93A3, 0xF16A, 0x93A4, 0xF167, 0x93A5, 0xF16B, 0x93A6, 0xF15E, 0x93A7, 0xF15A, + 0x93A8, 0xF168, 0x93A9, 0xF36A, 0x93AA, 0xF15C, 0x93AC, 0xC2EE, 0x93AE, 0xC2ED, 0x93AF, 0xEECF, 0x93B0, 0xC2EF, 0x93B1, 0xF164, + 0x93B2, 0xF166, 0x93B3, 0xC2EC, 0x93B4, 0xF169, 0x93B5, 0xF153, 0x93B7, 0xF156, 0x93C0, 0xF373, 0x93C2, 0xF363, 0x93C3, 0xC3EB, + 0x93C4, 0xF371, 0x93C7, 0xF361, 0x93C8, 0xC3EC, 0x93CA, 0xF36C, 0x93CC, 0xF368, 0x93CD, 0xC3F1, 0x93CE, 0xF372, 0x93CF, 0xF362, + 0x93D0, 0xF365, 0x93D1, 0xC3E9, 0x93D2, 0xF374, 0x93D4, 0xF36D, 0x93D5, 0xF370, 0x93D6, 0xC3EF, 0x93D7, 0xC3F4, 0x93D8, 0xC3F2, + 0x93D9, 0xF369, 0x93DA, 0xF364, 0x93DC, 0xC3ED, 0x93DD, 0xC3EE, 0x93DE, 0xF360, 0x93DF, 0xC3EA, 0x93E1, 0xC3E8, 0x93E2, 0xC3F0, + 0x93E3, 0xF36F, 0x93E4, 0xC3F3, 0x93E6, 0xF36B, 0x93E7, 0xF375, 0x93E8, 0xC3F5, 0x93EC, 0xF367, 0x93EE, 0xF36E, 0x93F5, 0xF4F3, + 0x93F6, 0xF542, 0x93F7, 0xF4F5, 0x93F8, 0xF4FC, 0x93F9, 0xF366, 0x93FA, 0xF4FA, 0x93FB, 0xF4E9, 0x93FC, 0xF540, 0x93FD, 0xC4C3, + 0x93FE, 0xF4ED, 0x93FF, 0xF4FE, 0x9400, 0xF4F4, 0x9403, 0xC4C2, 0x9406, 0xF544, 0x9407, 0xF4F6, 0x9409, 0xF4FB, 0x940A, 0xF4FD, + 0x940B, 0xF4E7, 0x940C, 0xF541, 0x940D, 0xF4F2, 0x940E, 0xF4F7, 0x940F, 0xF4EB, 0x9410, 0xF4EF, 0x9411, 0xF543, 0x9412, 0xF4F9, + 0x9413, 0xF4E8, 0x9414, 0xF4EC, 0x9415, 0xF4EE, 0x9416, 0xF4F8, 0x9418, 0xC4C1, 0x9419, 0xF4F1, 0x9420, 0xF4EA, 0x9428, 0xF4F0, + 0x9429, 0xF661, 0x942A, 0xF666, 0x942B, 0xC54F, 0x942C, 0xF668, 0x942E, 0xC549, 0x9430, 0xF664, 0x9431, 0xF66A, 0x9432, 0xC54E, + 0x9433, 0xC54A, 0x9435, 0xC54B, 0x9436, 0xF660, 0x9437, 0xF667, 0x9438, 0xC54D, 0x9439, 0xF665, 0x943A, 0xC54C, 0x943B, 0xF65F, + 0x943C, 0xF663, 0x943D, 0xF662, 0x943F, 0xF65E, 0x9440, 0xF669, 0x9444, 0xC5B1, 0x9445, 0xF76D, 0x9446, 0xF770, 0x9447, 0xF76C, + 0x9448, 0xF76E, 0x9449, 0xF76F, 0x944A, 0xF769, 0x944B, 0xF76A, 0x944C, 0xF767, 0x944F, 0xF76B, 0x9450, 0xF768, 0x9451, 0xC5B2, + 0x9452, 0xC5B3, 0x9455, 0xF84B, 0x9457, 0xF84D, 0x945D, 0xF84C, 0x945E, 0xF84E, 0x9460, 0xC5E0, 0x9462, 0xF84A, 0x9463, 0xC5DF, + 0x9464, 0xC5E1, 0x9468, 0xF8CB, 0x9469, 0xF8CC, 0x946A, 0xC644, 0x946B, 0xF8CA, 0x946D, 0xF953, 0x946E, 0xF952, 0x946F, 0xF954, + 0x9470, 0xC65F, 0x9471, 0xF955, 0x9472, 0xC65E, 0x9473, 0xF956, 0x9474, 0xF972, 0x9475, 0xF975, 0x9476, 0xF974, 0x9477, 0xC668, + 0x9478, 0xF973, 0x947C, 0xC672, 0x947D, 0xC670, 0x947E, 0xC671, 0x947F, 0xC677, 0x9480, 0xF9C0, 0x9481, 0xF9C1, 0x9482, 0xF9BF, + 0x9483, 0xF9C9, 0x9577, 0xAAF8, 0x957A, 0xD844, 0x957B, 0xDC78, 0x957C, 0xE8A5, 0x957D, 0xF376, 0x9580, 0xAAF9, 0x9582, 0xADAC, + 0x9583, 0xB07B, 0x9586, 0xD845, 0x9588, 0xD846, 0x9589, 0xB3AC, 0x958B, 0xB67D, 0x958C, 0xDC7A, 0x958D, 0xDC79, 0x958E, 0xB6A3, + 0x958F, 0xB67C, 0x9590, 0xDC7B, 0x9591, 0xB67E, 0x9592, 0xB6A2, 0x9593, 0xB6A1, 0x9594, 0xB67B, 0x9598, 0xB968, 0x959B, 0xE0D0, + 0x959C, 0xE0CE, 0x959E, 0xE0CF, 0x959F, 0xE0CD, 0x95A1, 0xBBD2, 0x95A3, 0xBBD5, 0x95A4, 0xBBD7, 0x95A5, 0xBBD6, 0x95A8, 0xBBD3, + 0x95A9, 0xBBD4, 0x95AB, 0xE8A7, 0x95AC, 0xE8A6, 0x95AD, 0xBE5B, 0x95AE, 0xE8A8, 0x95B0, 0xE8A9, 0x95B1, 0xBE5C, 0x95B5, 0xEC4D, + 0x95B6, 0xEC4B, 0x95B7, 0xEEF3, 0x95B9, 0xEC49, 0x95BA, 0xEC4A, 0x95BB, 0xC046, 0x95BC, 0xEC46, 0x95BD, 0xEC4E, 0x95BE, 0xEC48, + 0x95BF, 0xEC4C, 0x95C0, 0xEEEF, 0x95C3, 0xEEF1, 0x95C5, 0xEEF2, 0x95C6, 0xC1F3, 0x95C7, 0xEEEE, 0x95C8, 0xC1F2, 0x95C9, 0xEEF0, + 0x95CA, 0xC1EF, 0x95CB, 0xC1F0, 0x95CC, 0xC1F1, 0x95CD, 0xEC47, 0x95D0, 0xC2F5, 0x95D1, 0xF16E, 0x95D2, 0xF16C, 0x95D3, 0xF16D, + 0x95D4, 0xC2F3, 0x95D5, 0xC2F6, 0x95D6, 0xC2F4, 0x95DA, 0xF377, 0x95DB, 0xF378, 0x95DC, 0xC3F6, 0x95DE, 0xF545, 0x95DF, 0xF547, + 0x95E0, 0xF546, 0x95E1, 0xC4C4, 0x95E2, 0xC550, 0x95E3, 0xF66D, 0x95E4, 0xF66C, 0x95E5, 0xF66B, 0x961C, 0xAAFA, 0x961E, 0xC9AA, + 0x9620, 0xCA58, 0x9621, 0xA6E9, 0x9622, 0xCA56, 0x9623, 0xCA59, 0x9624, 0xCA57, 0x9628, 0xCBAE, 0x962A, 0xA8C1, 0x962C, 0xA8C2, + 0x962D, 0xCBB0, 0x962E, 0xA8BF, 0x962F, 0xCBAF, 0x9630, 0xCBAD, 0x9631, 0xA8C0, 0x9632, 0xA8BE, 0x9639, 0xCDD8, 0x963A, 0xCDDB, + 0x963B, 0xAAFD, 0x963C, 0xCDDA, 0x963D, 0xCDD9, 0x963F, 0xAAFC, 0x9640, 0xAAFB, 0x9642, 0xAB40, 0x9643, 0xCDDC, 0x9644, 0xAAFE, + 0x964A, 0xD0C6, 0x964B, 0xADAE, 0x964C, 0xADAF, 0x964D, 0xADB0, 0x964E, 0xD0C7, 0x964F, 0xD0C3, 0x9650, 0xADAD, 0x9651, 0xD0C4, + 0x9653, 0xD0C5, 0x9654, 0xD0C2, 0x9658, 0xB0A4, 0x965B, 0xB0A1, 0x965C, 0xD445, 0x965D, 0xB0A2, 0x965E, 0xB0A5, 0x965F, 0xD446, + 0x9661, 0xB07E, 0x9662, 0xB07C, 0x9663, 0xB07D, 0x9664, 0xB0A3, 0x966A, 0xB3AD, 0x966B, 0xD849, 0x966C, 0xB3B5, 0x966D, 0xD848, + 0x966F, 0xD84B, 0x9670, 0xB3B1, 0x9671, 0xD84A, 0x9672, 0xB6AB, 0x9673, 0xB3AF, 0x9674, 0xB3B2, 0x9675, 0xB3AE, 0x9676, 0xB3B3, + 0x9677, 0xB3B4, 0x9678, 0xB3B0, 0x967C, 0xD847, 0x967D, 0xB6A7, 0x967E, 0xDC7D, 0x9680, 0xDCA3, 0x9683, 0xDCA2, 0x9684, 0xB6AC, + 0x9685, 0xB6A8, 0x9686, 0xB6A9, 0x9687, 0xDC7C, 0x9688, 0xDC7E, 0x9689, 0xDCA1, 0x968A, 0xB6A4, 0x968B, 0xB6A6, 0x968D, 0xB6AA, + 0x968E, 0xB6A5, 0x9691, 0xE0D3, 0x9692, 0xE0D1, 0x9693, 0xE0D2, 0x9694, 0xB96A, 0x9695, 0xB96B, 0x9697, 0xE0D4, 0x9698, 0xB969, + 0x9699, 0xBBD8, 0x969B, 0xBBDA, 0x969C, 0xBBD9, 0x969E, 0xE4BB, 0x96A1, 0xE4BC, 0x96A2, 0xE8AB, 0x96A4, 0xE8AA, 0x96A7, 0xC047, + 0x96A8, 0xC048, 0x96A9, 0xEC4F, 0x96AA, 0xC049, 0x96AC, 0xEEF6, 0x96AE, 0xEEF4, 0x96B0, 0xEEF5, 0x96B1, 0xC1F4, 0x96B3, 0xF16F, + 0x96B4, 0xC3F7, 0x96B8, 0xC1F5, 0x96B9, 0xAB41, 0x96BB, 0xB0A6, 0x96BC, 0xD447, 0x96BF, 0xD84C, 0x96C0, 0xB3B6, 0x96C1, 0xB6AD, + 0x96C2, 0xDCA4, 0x96C3, 0xDCA6, 0x96C4, 0xB6AF, 0x96C5, 0xB6AE, 0x96C6, 0xB6B0, 0x96C7, 0xB6B1, 0x96C8, 0xDCA5, 0x96C9, 0xB96E, + 0x96CA, 0xB96F, 0x96CB, 0xB96D, 0x96CC, 0xBBDB, 0x96CD, 0xB96C, 0x96CE, 0xE0D5, 0x96D2, 0xBBDC, 0x96D3, 0xE8AC, 0x96D4, 0xEC50, + 0x96D5, 0xC04A, 0x96D6, 0xC1F6, 0x96D7, 0xF170, 0x96D8, 0xF174, 0x96D9, 0xC2F9, 0x96DA, 0xF171, 0x96DB, 0xC2FA, 0x96DC, 0xC2F8, + 0x96DD, 0xF175, 0x96DE, 0xC2FB, 0x96DF, 0xF173, 0x96E1, 0xF379, 0x96E2, 0xC2F7, 0x96E3, 0xC3F8, 0x96E5, 0xF8CD, 0x96E8, 0xAB42, + 0x96E9, 0xB3B8, 0x96EA, 0xB3B7, 0x96EF, 0xB6B2, 0x96F0, 0xDCA8, 0x96F1, 0xDCA7, 0x96F2, 0xB6B3, 0x96F5, 0xE0D9, 0x96F6, 0xB973, + 0x96F7, 0xB970, 0x96F8, 0xE0D8, 0x96F9, 0xB972, 0x96FA, 0xE0D6, 0x96FB, 0xB971, 0x96FD, 0xE0D7, 0x96FF, 0xE4BD, 0x9700, 0xBBDD, + 0x9702, 0xE8AF, 0x9704, 0xBE5D, 0x9705, 0xE8AD, 0x9706, 0xBE5E, 0x9707, 0xBE5F, 0x9708, 0xE8AE, 0x9709, 0xBE60, 0x970B, 0xEC51, + 0x970D, 0xC04E, 0x970E, 0xC04B, 0x970F, 0xC050, 0x9710, 0xEC53, 0x9711, 0xC04C, 0x9712, 0xEC52, 0x9713, 0xC04F, 0x9716, 0xC04D, + 0x9718, 0xEEF9, 0x9719, 0xEEFB, 0x971C, 0xC1F7, 0x971D, 0xEEFA, 0x971E, 0xC1F8, 0x971F, 0xEEF8, 0x9720, 0xEEF7, 0x9722, 0xF177, + 0x9723, 0xF176, 0x9724, 0xC2FC, 0x9725, 0xF178, 0x9726, 0xF37E, 0x9727, 0xC3FA, 0x9728, 0xF37D, 0x9729, 0xF37A, 0x972A, 0xC3F9, + 0x972B, 0xF37B, 0x972C, 0xF37C, 0x972E, 0xF548, 0x972F, 0xF549, 0x9730, 0xC4C5, 0x9732, 0xC553, 0x9735, 0xF66E, 0x9738, 0xC551, + 0x9739, 0xC552, 0x973A, 0xF66F, 0x973D, 0xC5B4, 0x973E, 0xC5B5, 0x973F, 0xF771, 0x9742, 0xC645, 0x9743, 0xF8CF, 0x9744, 0xC647, + 0x9746, 0xF8CE, 0x9747, 0xF8D0, 0x9748, 0xC646, 0x9749, 0xF957, 0x974B, 0xF9AD, 0x9752, 0xAB43, 0x9756, 0xB974, 0x9758, 0xE4BE, + 0x975A, 0xE8B0, 0x975B, 0xC051, 0x975C, 0xC052, 0x975E, 0xAB44, 0x9760, 0xBE61, 0x9761, 0xC3FB, 0x9762, 0xADB1, 0x9766, 0xC053, + 0x9768, 0xC5E2, 0x9769, 0xADB2, 0x976A, 0xD84D, 0x976C, 0xDCA9, 0x976E, 0xDCAB, 0x9770, 0xDCAA, 0x9772, 0xE0DD, 0x9773, 0xE0DA, + 0x9774, 0xB975, 0x9776, 0xB976, 0x9777, 0xE0DB, 0x9778, 0xE0DC, 0x977A, 0xE4C0, 0x977B, 0xE4C5, 0x977C, 0xBBDE, 0x977D, 0xE4BF, + 0x977E, 0xE4C1, 0x977F, 0xE4C8, 0x9780, 0xE4C3, 0x9781, 0xE4C7, 0x9782, 0xE4C4, 0x9783, 0xE4C2, 0x9784, 0xE4C6, 0x9785, 0xBBDF, + 0x9788, 0xE8B3, 0x978A, 0xE8B1, 0x978B, 0xBE63, 0x978D, 0xBE62, 0x978E, 0xE8B2, 0x978F, 0xBE64, 0x9794, 0xEC56, 0x9797, 0xEC55, + 0x9798, 0xC054, 0x9799, 0xEC54, 0x979A, 0xEEFC, 0x979C, 0xEEFE, 0x979D, 0xEF41, 0x979E, 0xEF40, 0x97A0, 0xC1F9, 0x97A1, 0xEEFD, + 0x97A2, 0xF1A1, 0x97A3, 0xC2FD, 0x97A4, 0xF17D, 0x97A5, 0xF1A2, 0x97A6, 0xC2FE, 0x97A8, 0xF17B, 0x97AA, 0xF17E, 0x97AB, 0xF17C, + 0x97AC, 0xF179, 0x97AD, 0xC340, 0x97AE, 0xF17A, 0x97B3, 0xF3A1, 0x97B6, 0xF3A3, 0x97B7, 0xF3A2, 0x97B9, 0xF54A, 0x97BB, 0xF54B, + 0x97BF, 0xF670, 0x97C1, 0xC5B7, 0x97C3, 0xC5B6, 0x97C4, 0xF84F, 0x97C5, 0xF850, 0x97C6, 0xC648, 0x97C7, 0xF8D1, 0x97C9, 0xC669, + 0x97CB, 0xADB3, 0x97CC, 0xB6B4, 0x97CD, 0xE4CA, 0x97CE, 0xE4C9, 0x97CF, 0xE8B5, 0x97D0, 0xE8B4, 0x97D3, 0xC1FA, 0x97D4, 0xEF43, + 0x97D5, 0xEF42, 0x97D6, 0xF1A5, 0x97D7, 0xF1A3, 0x97D8, 0xF1A6, 0x97D9, 0xF1A4, 0x97DC, 0xC3FC, 0x97DD, 0xF3A4, 0x97DE, 0xF3A5, + 0x97DF, 0xF3A6, 0x97E1, 0xF671, 0x97E3, 0xF772, 0x97E5, 0xF8D2, 0x97ED, 0xADB4, 0x97F0, 0xEC57, 0x97F1, 0xEF44, 0x97F3, 0xADB5, + 0x97F6, 0xBBE0, 0x97F8, 0xEC58, 0x97F9, 0xC341, 0x97FA, 0xF1A7, 0x97FB, 0xC3FD, 0x97FD, 0xF54C, 0x97FE, 0xF54D, 0x97FF, 0xC554, + 0x9800, 0xF851, 0x9801, 0xADB6, 0x9802, 0xB3BB, 0x9803, 0xB3BC, 0x9804, 0xD84E, 0x9805, 0xB6B5, 0x9806, 0xB6B6, 0x9807, 0xDCAC, + 0x9808, 0xB6B7, 0x980A, 0xB97A, 0x980C, 0xB97C, 0x980D, 0xE0DF, 0x980E, 0xE0E0, 0x980F, 0xE0DE, 0x9810, 0xB977, 0x9811, 0xB978, + 0x9812, 0xB97B, 0x9813, 0xB979, 0x9816, 0xE4CB, 0x9817, 0xBBE1, 0x9818, 0xBBE2, 0x981B, 0xE8BC, 0x981C, 0xBE67, 0x981D, 0xE8B7, + 0x981E, 0xE8B6, 0x9820, 0xE8BB, 0x9821, 0xBE65, 0x9824, 0xC05B, 0x9826, 0xE8B8, 0x9827, 0xE8BD, 0x9828, 0xE8BA, 0x9829, 0xE8B9, + 0x982B, 0xBE66, 0x982D, 0xC059, 0x982F, 0xEC5A, 0x9830, 0xC055, 0x9832, 0xEC5B, 0x9835, 0xEC59, 0x9837, 0xC058, 0x9838, 0xC056, + 0x9839, 0xC05A, 0x983B, 0xC057, 0x9841, 0xEF45, 0x9843, 0xEF4A, 0x9844, 0xEF46, 0x9845, 0xEF49, 0x9846, 0xC1FB, 0x9848, 0xEDD4, + 0x9849, 0xEF48, 0x984A, 0xEF47, 0x984C, 0xC344, 0x984D, 0xC342, 0x984E, 0xC345, 0x984F, 0xC343, 0x9850, 0xF1A8, 0x9851, 0xF1A9, + 0x9852, 0xF1AA, 0x9853, 0xC346, 0x9857, 0xF3AA, 0x9858, 0xC440, 0x9859, 0xF3A8, 0x985B, 0xC441, 0x985C, 0xF3A7, 0x985D, 0xF3A9, + 0x985E, 0xC3FE, 0x985F, 0xF551, 0x9860, 0xF54E, 0x9862, 0xF54F, 0x9863, 0xF550, 0x9864, 0xF672, 0x9865, 0xC556, 0x9867, 0xC555, + 0x9869, 0xF774, 0x986A, 0xF773, 0x986B, 0xC5B8, 0x986F, 0xC5E3, 0x9870, 0xC649, 0x9871, 0xC660, 0x9872, 0xF958, 0x9873, 0xF9AE, + 0x9874, 0xF9AF, 0x98A8, 0xADB7, 0x98A9, 0xDCAD, 0x98AC, 0xE0E1, 0x98AD, 0xE4CC, 0x98AE, 0xE4CD, 0x98AF, 0xBBE3, 0x98B1, 0xBBE4, + 0x98B2, 0xE8BE, 0x98B3, 0xBE68, 0x98B6, 0xC1FC, 0x98B8, 0xF1AB, 0x98BA, 0xC347, 0x98BB, 0xF3AD, 0x98BC, 0xC442, 0x98BD, 0xF3AC, + 0x98BE, 0xF3AE, 0x98BF, 0xF3AB, 0x98C0, 0xF675, 0x98C1, 0xF552, 0x98C2, 0xF553, 0x98C4, 0xC4C6, 0x98C6, 0xF674, 0x98C9, 0xF673, + 0x98CB, 0xF775, 0x98CC, 0xF9B0, 0x98DB, 0xADB8, 0x98DF, 0xADB9, 0x98E2, 0xB0A7, 0x98E3, 0xD448, 0x98E5, 0xD84F, 0x98E7, 0xB6B8, + 0x98E9, 0xB6BB, 0x98EA, 0xB6B9, 0x98EB, 0xDCAE, 0x98ED, 0xB6BD, 0x98EF, 0xB6BA, 0x98F2, 0xB6BC, 0x98F4, 0xB97E, 0x98F6, 0xE0E2, + 0x98F9, 0xE0E3, 0x98FA, 0xE8C0, 0x98FC, 0xB97D, 0x98FD, 0xB9A1, 0x98FE, 0xB9A2, 0x9900, 0xE4CF, 0x9902, 0xE4CE, 0x9903, 0xBBE5, + 0x9905, 0xBBE6, 0x9907, 0xE4D0, 0x9908, 0xE8BF, 0x9909, 0xBBE8, 0x990A, 0xBE69, 0x990C, 0xBBE7, 0x9910, 0xC05C, 0x9911, 0xE8C1, + 0x9912, 0xBE6B, 0x9913, 0xBE6A, 0x9914, 0xE8C2, 0x9915, 0xE8C5, 0x9916, 0xE8C3, 0x9917, 0xE8C4, 0x9918, 0xBE6C, 0x991A, 0xC061, + 0x991B, 0xC05F, 0x991E, 0xC05E, 0x991F, 0xEC5D, 0x9921, 0xC060, 0x9924, 0xEC5C, 0x9925, 0xEF4B, 0x9927, 0xEC5E, 0x9928, 0xC05D, + 0x9929, 0xEC5F, 0x992A, 0xEF4E, 0x992B, 0xEF4C, 0x992C, 0xEF4D, 0x992D, 0xEF52, 0x992E, 0xC34B, 0x992F, 0xEF51, 0x9930, 0xEF54, + 0x9931, 0xEF53, 0x9932, 0xEF50, 0x9933, 0xEF4F, 0x9935, 0xC1FD, 0x993A, 0xF1AE, 0x993C, 0xF1AD, 0x993D, 0xC34A, 0x993E, 0xC348, + 0x993F, 0xC349, 0x9941, 0xF1AC, 0x9943, 0xF3B1, 0x9945, 0xC443, 0x9947, 0xF3B0, 0x9948, 0xF3AF, 0x9949, 0xC444, 0x994B, 0xF558, + 0x994C, 0xF557, 0x994E, 0xF555, 0x9950, 0xF554, 0x9951, 0xC4C8, 0x9952, 0xC4C7, 0x9953, 0xF559, 0x9954, 0xF776, 0x9955, 0xC5B9, + 0x9956, 0xF677, 0x9957, 0xC557, 0x9958, 0xF676, 0x9959, 0xF556, 0x995B, 0xF777, 0x995C, 0xC5E4, 0x995E, 0xC661, 0x995F, 0xF959, + 0x9961, 0xF9B1, 0x9996, 0xADBA, 0x9997, 0xD850, 0x9998, 0xEF55, 0x9999, 0xADBB, 0x999C, 0xE4D2, 0x999D, 0xE4D1, 0x999E, 0xEC60, + 0x99A1, 0xEF57, 0x99A3, 0xEF56, 0x99A5, 0xC34C, 0x99A6, 0xF3B2, 0x99A7, 0xF3B3, 0x99A8, 0xC4C9, 0x99AB, 0xF9B2, 0x99AC, 0xB0A8, + 0x99AD, 0xB6BF, 0x99AE, 0xB6BE, 0x99AF, 0xE0E4, 0x99B0, 0xE0E6, 0x99B1, 0xB9A4, 0x99B2, 0xE0E5, 0x99B3, 0xB9A3, 0x99B4, 0xB9A5, + 0x99B5, 0xE0E7, 0x99B9, 0xE4D4, 0x99BA, 0xE4D6, 0x99BB, 0xE4D5, 0x99BD, 0xE4D8, 0x99C1, 0xBBE9, 0x99C2, 0xE4D7, 0x99C3, 0xE4D3, + 0x99C7, 0xE4D9, 0x99C9, 0xE8CC, 0x99CB, 0xE8CF, 0x99CC, 0xE8D1, 0x99CD, 0xE8C7, 0x99CE, 0xE8CB, 0x99CF, 0xE8C8, 0x99D0, 0xBE6E, + 0x99D1, 0xBE71, 0x99D2, 0xBE73, 0x99D3, 0xE8C9, 0x99D4, 0xE8CA, 0x99D5, 0xBE72, 0x99D6, 0xE8CD, 0x99D7, 0xE8D0, 0x99D8, 0xE8CE, + 0x99D9, 0xBE74, 0x99DB, 0xBE70, 0x99DC, 0xE8C6, 0x99DD, 0xBE6D, 0x99DF, 0xBE6F, 0x99E2, 0xC063, 0x99E3, 0xEC66, 0x99E4, 0xEC64, + 0x99E5, 0xEC63, 0x99E7, 0xEC69, 0x99E9, 0xEC68, 0x99EA, 0xEC67, 0x99EC, 0xEC62, 0x99ED, 0xC062, 0x99EE, 0xEC61, 0x99F0, 0xEC65, + 0x99F1, 0xC064, 0x99F4, 0xEF5A, 0x99F6, 0xEF5E, 0x99F7, 0xEF5B, 0x99F8, 0xEF5D, 0x99F9, 0xEF5C, 0x99FA, 0xEF59, 0x99FB, 0xEF5F, + 0x99FC, 0xEF62, 0x99FD, 0xEF60, 0x99FE, 0xEF61, 0x99FF, 0xC240, 0x9A01, 0xC1FE, 0x9A02, 0xEF58, 0x9A03, 0xEF63, 0x9A04, 0xF1B3, + 0x9A05, 0xF1B6, 0x9A06, 0xF1B8, 0x9A07, 0xF1B7, 0x9A09, 0xF1B1, 0x9A0A, 0xF1B5, 0x9A0B, 0xF1B0, 0x9A0D, 0xF1B2, 0x9A0E, 0xC34D, + 0x9A0F, 0xF1AF, 0x9A11, 0xF1B4, 0x9A14, 0xF3C0, 0x9A15, 0xF3B5, 0x9A16, 0xC445, 0x9A19, 0xC446, 0x9A1A, 0xF3B4, 0x9A1B, 0xF3B9, + 0x9A1C, 0xF3BF, 0x9A1D, 0xF3B7, 0x9A1E, 0xF3BE, 0x9A20, 0xF3BB, 0x9A22, 0xF3BA, 0x9A23, 0xF3BD, 0x9A24, 0xF3B8, 0x9A25, 0xF3B6, + 0x9A27, 0xF3BC, 0x9A29, 0xF560, 0x9A2A, 0xF55E, 0x9A2B, 0xC4CA, 0x9A2C, 0xF55D, 0x9A2D, 0xF563, 0x9A2E, 0xF561, 0x9A30, 0xC4CB, + 0x9A31, 0xF55C, 0x9A32, 0xF55A, 0x9A34, 0xF55B, 0x9A35, 0xC4CD, 0x9A36, 0xF55F, 0x9A37, 0xC4CC, 0x9A38, 0xF562, 0x9A39, 0xF678, + 0x9A3A, 0xF67E, 0x9A3D, 0xF679, 0x9A3E, 0xC55B, 0x9A3F, 0xF6A1, 0x9A40, 0xC55A, 0x9A41, 0xF67D, 0x9A42, 0xF67C, 0x9A43, 0xC559, + 0x9A44, 0xF67B, 0x9A45, 0xC558, 0x9A46, 0xF67A, 0x9A48, 0xF77D, 0x9A49, 0xF7A1, 0x9A4A, 0xF77E, 0x9A4C, 0xF77B, 0x9A4D, 0xC5BB, + 0x9A4E, 0xF778, 0x9A4F, 0xF77C, 0x9A50, 0xF7A3, 0x9A52, 0xF7A2, 0x9A53, 0xF779, 0x9A54, 0xF77A, 0x9A55, 0xC5BA, 0x9A56, 0xF852, + 0x9A57, 0xC5E7, 0x9A59, 0xF853, 0x9A5A, 0xC5E5, 0x9A5B, 0xC5E6, 0x9A5E, 0xF8D3, 0x9A5F, 0xC64A, 0x9A60, 0xF976, 0x9A62, 0xC66A, + 0x9A64, 0xF9B3, 0x9A65, 0xC66B, 0x9A66, 0xF9B4, 0x9A67, 0xF9B5, 0x9A68, 0xF9C3, 0x9A69, 0xF9C2, 0x9A6A, 0xC67A, 0x9A6B, 0xF9CD, + 0x9AA8, 0xB0A9, 0x9AAB, 0xE0E9, 0x9AAD, 0xE0E8, 0x9AAF, 0xBBEA, 0x9AB0, 0xBBEB, 0x9AB1, 0xE4DA, 0x9AB3, 0xE8D2, 0x9AB4, 0xEC6C, + 0x9AB7, 0xBE75, 0x9AB8, 0xC065, 0x9AB9, 0xEC6A, 0x9ABB, 0xEC6D, 0x9ABC, 0xC066, 0x9ABE, 0xEF64, 0x9ABF, 0xEC6B, 0x9AC0, 0xF1B9, + 0x9AC1, 0xC34E, 0x9AC2, 0xF3C1, 0x9AC6, 0xF566, 0x9AC7, 0xF564, 0x9ACA, 0xF565, 0x9ACD, 0xF6A2, 0x9ACF, 0xC55C, 0x9AD0, 0xF7A4, + 0x9AD1, 0xC5EA, 0x9AD2, 0xC5BC, 0x9AD3, 0xC5E8, 0x9AD4, 0xC5E9, 0x9AD5, 0xF8D4, 0x9AD6, 0xC662, 0x9AD8, 0xB0AA, 0x9ADC, 0xF1BA, + 0x9ADF, 0xD449, 0x9AE1, 0xB9A6, 0x9AE3, 0xE4DB, 0x9AE6, 0xBBEC, 0x9AE7, 0xE4DC, 0x9AEB, 0xE8D4, 0x9AEC, 0xE8D3, 0x9AED, 0xC068, + 0x9AEE, 0xBE76, 0x9AEF, 0xBE77, 0x9AF1, 0xE8D7, 0x9AF2, 0xE8D6, 0x9AF3, 0xE8D5, 0x9AF6, 0xEC6E, 0x9AF7, 0xEC71, 0x9AF9, 0xEC70, + 0x9AFA, 0xEC6F, 0x9AFB, 0xC067, 0x9AFC, 0xEF68, 0x9AFD, 0xEF66, 0x9AFE, 0xEF65, 0x9B01, 0xEF67, 0x9B03, 0xC34F, 0x9B04, 0xF1BC, + 0x9B05, 0xF1BD, 0x9B06, 0xC350, 0x9B08, 0xF1BB, 0x9B0A, 0xF3C3, 0x9B0B, 0xF3C2, 0x9B0C, 0xF3C5, 0x9B0D, 0xC447, 0x9B0E, 0xF3C4, + 0x9B10, 0xF567, 0x9B11, 0xF569, 0x9B12, 0xF568, 0x9B15, 0xF6A3, 0x9B16, 0xF6A6, 0x9B17, 0xF6A4, 0x9B18, 0xF6A5, 0x9B19, 0xF7A5, + 0x9B1A, 0xC5BD, 0x9B1E, 0xF854, 0x9B1F, 0xF855, 0x9B20, 0xF856, 0x9B22, 0xC64B, 0x9B23, 0xC663, 0x9B24, 0xF9B6, 0x9B25, 0xB0AB, + 0x9B27, 0xBE78, 0x9B28, 0xC069, 0x9B29, 0xF1BE, 0x9B2B, 0xF7A6, 0x9B2E, 0xF9C4, 0x9B2F, 0xD44A, 0x9B31, 0xC67B, 0x9B32, 0xB0AC, + 0x9B33, 0xEC72, 0x9B35, 0xF1BF, 0x9B37, 0xF3C6, 0x9B3A, 0xF6A7, 0x9B3B, 0xF7A7, 0x9B3C, 0xB0AD, 0x9B3E, 0xE4DD, 0x9B3F, 0xE4DE, + 0x9B41, 0xBBED, 0x9B42, 0xBBEE, 0x9B43, 0xE8D9, 0x9B44, 0xBE7A, 0x9B45, 0xBE79, 0x9B46, 0xE8D8, 0x9B48, 0xEF69, 0x9B4A, 0xF1C0, + 0x9B4B, 0xF1C2, 0x9B4C, 0xF1C1, 0x9B4D, 0xC353, 0x9B4E, 0xC352, 0x9B4F, 0xC351, 0x9B51, 0xC55E, 0x9B52, 0xF6A8, 0x9B54, 0xC55D, + 0x9B55, 0xF7A9, 0x9B56, 0xF7A8, 0x9B58, 0xC64C, 0x9B59, 0xF8D5, 0x9B5A, 0xB3BD, 0x9B5B, 0xE0EA, 0x9B5F, 0xE4E1, 0x9B60, 0xE4DF, + 0x9B61, 0xE4E0, 0x9B64, 0xE8E2, 0x9B66, 0xE8DD, 0x9B67, 0xE8DA, 0x9B68, 0xE8E1, 0x9B6C, 0xE8E3, 0x9B6F, 0xBE7C, 0x9B70, 0xE8E0, + 0x9B71, 0xE8DC, 0x9B74, 0xE8DB, 0x9B75, 0xE8DF, 0x9B76, 0xE8DE, 0x9B77, 0xBE7B, 0x9B7A, 0xEC7D, 0x9B7B, 0xEC78, 0x9B7C, 0xEC76, + 0x9B7D, 0xECA1, 0x9B7E, 0xEC77, 0x9B80, 0xEC73, 0x9B82, 0xEC79, 0x9B85, 0xEC74, 0x9B86, 0xEF72, 0x9B87, 0xEC75, 0x9B88, 0xECA2, + 0x9B90, 0xEC7C, 0x9B91, 0xC06A, 0x9B92, 0xEC7B, 0x9B93, 0xEC7A, 0x9B95, 0xEC7E, 0x9B9A, 0xEF6A, 0x9B9B, 0xEF6D, 0x9B9E, 0xEF6C, + 0x9BA0, 0xEF74, 0x9BA1, 0xEF6F, 0x9BA2, 0xEF73, 0x9BA4, 0xEF71, 0x9BA5, 0xEF70, 0x9BA6, 0xEF6E, 0x9BA8, 0xEF6B, 0x9BAA, 0xC243, + 0x9BAB, 0xC242, 0x9BAD, 0xC244, 0x9BAE, 0xC241, 0x9BAF, 0xEF75, 0x9BB5, 0xF1C8, 0x9BB6, 0xF1CB, 0x9BB8, 0xF1C9, 0x9BB9, 0xF1CD, + 0x9BBD, 0xF1CE, 0x9BBF, 0xF1C6, 0x9BC0, 0xC358, 0x9BC1, 0xF1C7, 0x9BC3, 0xF1C5, 0x9BC4, 0xF1CC, 0x9BC6, 0xF1C4, 0x9BC7, 0xF1C3, + 0x9BC8, 0xC357, 0x9BC9, 0xC355, 0x9BCA, 0xC354, 0x9BD3, 0xF1CA, 0x9BD4, 0xF3CF, 0x9BD5, 0xF3D5, 0x9BD6, 0xC44A, 0x9BD7, 0xF3D0, + 0x9BD9, 0xF3D3, 0x9BDA, 0xF3D7, 0x9BDB, 0xC44B, 0x9BDC, 0xF3D2, 0x9BDE, 0xF3CA, 0x9BE0, 0xF3C9, 0x9BE1, 0xF3D6, 0x9BE2, 0xF3CD, + 0x9BE4, 0xF3CB, 0x9BE5, 0xF3D4, 0x9BE6, 0xF3CC, 0x9BE7, 0xC449, 0x9BE8, 0xC448, 0x9BEA, 0xF3C7, 0x9BEB, 0xF3C8, 0x9BEC, 0xF3D1, + 0x9BF0, 0xF3CE, 0x9BF7, 0xF56C, 0x9BF8, 0xF56F, 0x9BFD, 0xC356, 0x9C05, 0xF56D, 0x9C06, 0xF573, 0x9C07, 0xF571, 0x9C08, 0xF56B, + 0x9C09, 0xF576, 0x9C0B, 0xF56A, 0x9C0D, 0xC4CF, 0x9C0E, 0xF572, 0x9C12, 0xF56E, 0x9C13, 0xC4CE, 0x9C14, 0xF575, 0x9C17, 0xF574, + 0x9C1C, 0xF6AB, 0x9C1D, 0xF6AA, 0x9C21, 0xF6B1, 0x9C23, 0xF6AD, 0x9C24, 0xF6B0, 0x9C25, 0xC560, 0x9C28, 0xF6AE, 0x9C29, 0xF6AF, + 0x9C2B, 0xF6A9, 0x9C2C, 0xF6AC, 0x9C2D, 0xC55F, 0x9C31, 0xC5BF, 0x9C32, 0xF7B4, 0x9C33, 0xF7AF, 0x9C34, 0xF7B3, 0x9C36, 0xF7B6, + 0x9C37, 0xF7B2, 0x9C39, 0xF7AE, 0x9C3B, 0xC5C1, 0x9C3C, 0xF7B1, 0x9C3D, 0xF7B5, 0x9C3E, 0xC5C0, 0x9C3F, 0xF7AC, 0x9C40, 0xF570, + 0x9C41, 0xF7B0, 0x9C44, 0xF7AD, 0x9C46, 0xF7AA, 0x9C48, 0xF7AB, 0x9C49, 0xC5BE, 0x9C4A, 0xF85A, 0x9C4B, 0xF85C, 0x9C4C, 0xF85F, + 0x9C4D, 0xF85B, 0x9C4E, 0xF860, 0x9C50, 0xF859, 0x9C52, 0xF857, 0x9C54, 0xC5EB, 0x9C55, 0xF85D, 0x9C56, 0xC5ED, 0x9C57, 0xC5EC, + 0x9C58, 0xF858, 0x9C59, 0xF85E, 0x9C5E, 0xF8DA, 0x9C5F, 0xC64D, 0x9C60, 0xF8DB, 0x9C62, 0xF8D9, 0x9C63, 0xF8D6, 0x9C66, 0xF8D8, + 0x9C67, 0xF8D7, 0x9C68, 0xF95A, 0x9C6D, 0xF95C, 0x9C6E, 0xF95B, 0x9C71, 0xF979, 0x9C73, 0xF978, 0x9C74, 0xF977, 0x9C75, 0xF97A, + 0x9C77, 0xC673, 0x9C78, 0xC674, 0x9C79, 0xF9CA, 0x9C7A, 0xF9CE, 0x9CE5, 0xB3BE, 0x9CE6, 0xDCAF, 0x9CE7, 0xE0ED, 0x9CE9, 0xB9A7, + 0x9CEA, 0xE0EB, 0x9CED, 0xE0EC, 0x9CF1, 0xE4E2, 0x9CF2, 0xE4E3, 0x9CF3, 0xBBF1, 0x9CF4, 0xBBEF, 0x9CF5, 0xE4E4, 0x9CF6, 0xBBF0, + 0x9CF7, 0xE8E8, 0x9CF9, 0xE8EB, 0x9CFA, 0xE8E5, 0x9CFB, 0xE8EC, 0x9CFC, 0xE8E4, 0x9CFD, 0xE8E6, 0x9CFF, 0xE8E7, 0x9D00, 0xE8EA, + 0x9D03, 0xBEA1, 0x9D04, 0xE8EF, 0x9D05, 0xE8EE, 0x9D06, 0xBE7D, 0x9D07, 0xE8E9, 0x9D08, 0xE8ED, 0x9D09, 0xBE7E, 0x9D10, 0xECAC, + 0x9D12, 0xC06F, 0x9D14, 0xECA7, 0x9D15, 0xC06B, 0x9D17, 0xECA4, 0x9D18, 0xECAA, 0x9D19, 0xECAD, 0x9D1B, 0xC070, 0x9D1D, 0xECA9, + 0x9D1E, 0xECA6, 0x9D1F, 0xECAE, 0x9D20, 0xECA5, 0x9D22, 0xECAB, 0x9D23, 0xC06C, 0x9D25, 0xECA3, 0x9D26, 0xC06D, 0x9D28, 0xC06E, + 0x9D29, 0xECA8, 0x9D2D, 0xEFA9, 0x9D2E, 0xEF7A, 0x9D2F, 0xEF7B, 0x9D30, 0xEF7E, 0x9D31, 0xEF7C, 0x9D33, 0xEF76, 0x9D36, 0xEF79, + 0x9D37, 0xEFA5, 0x9D38, 0xEF7D, 0x9D3B, 0xC245, 0x9D3D, 0xEFA7, 0x9D3E, 0xEFA4, 0x9D3F, 0xC246, 0x9D40, 0xEFA6, 0x9D41, 0xEF77, + 0x9D42, 0xEFA2, 0x9D43, 0xEFA3, 0x9D45, 0xEFA1, 0x9D4A, 0xF1D2, 0x9D4B, 0xF1D4, 0x9D4C, 0xF1D7, 0x9D4F, 0xF1D1, 0x9D51, 0xC359, + 0x9D52, 0xF1D9, 0x9D53, 0xF1D0, 0x9D54, 0xF1DA, 0x9D56, 0xF1D6, 0x9D57, 0xF1D8, 0x9D58, 0xF1DC, 0x9D59, 0xF1D5, 0x9D5A, 0xF1DD, + 0x9D5B, 0xF1D3, 0x9D5C, 0xF1CF, 0x9D5D, 0xC35A, 0x9D5F, 0xF1DB, 0x9D60, 0xC35B, 0x9D61, 0xC44D, 0x9D67, 0xEF78, 0x9D68, 0xF3F1, + 0x9D69, 0xF3E8, 0x9D6A, 0xC44F, 0x9D6B, 0xF3E4, 0x9D6C, 0xC450, 0x9D6F, 0xF3ED, 0x9D70, 0xF3E7, 0x9D71, 0xF3DD, 0x9D72, 0xC44E, + 0x9D73, 0xF3EA, 0x9D74, 0xF3E5, 0x9D75, 0xF3E6, 0x9D77, 0xF3D8, 0x9D78, 0xF3DF, 0x9D79, 0xF3EE, 0x9D7B, 0xF3EB, 0x9D7D, 0xF3E3, + 0x9D7F, 0xF3EF, 0x9D80, 0xF3DE, 0x9D81, 0xF3D9, 0x9D82, 0xF3EC, 0x9D84, 0xF3DB, 0x9D85, 0xF3E9, 0x9D86, 0xF3E0, 0x9D87, 0xF3F0, + 0x9D88, 0xF3DC, 0x9D89, 0xC44C, 0x9D8A, 0xF3DA, 0x9D8B, 0xF3E1, 0x9D8C, 0xF3E2, 0x9D90, 0xF57D, 0x9D92, 0xF57B, 0x9D94, 0xF5A2, + 0x9D96, 0xF5AE, 0x9D97, 0xF5A5, 0x9D98, 0xF57C, 0x9D99, 0xF578, 0x9D9A, 0xF5A7, 0x9D9B, 0xF57E, 0x9D9C, 0xF5A3, 0x9D9D, 0xF57A, + 0x9D9E, 0xF5AA, 0x9D9F, 0xF577, 0x9DA0, 0xF5A1, 0x9DA1, 0xF5A6, 0x9DA2, 0xF5A8, 0x9DA3, 0xF5AB, 0x9DA4, 0xF579, 0x9DA6, 0xF5AF, + 0x9DA7, 0xF5B0, 0x9DA8, 0xF5A9, 0x9DA9, 0xF5AD, 0x9DAA, 0xF5A4, 0x9DAC, 0xF6C1, 0x9DAD, 0xF6C4, 0x9DAF, 0xC561, 0x9DB1, 0xF6C3, + 0x9DB2, 0xF6C8, 0x9DB3, 0xF6C6, 0x9DB4, 0xC562, 0x9DB5, 0xF6BD, 0x9DB6, 0xF6B3, 0x9DB7, 0xF6B2, 0x9DB8, 0xC564, 0x9DB9, 0xF6BF, + 0x9DBA, 0xF6C0, 0x9DBB, 0xF6BC, 0x9DBC, 0xF6B4, 0x9DBE, 0xF6B9, 0x9DBF, 0xF5AC, 0x9DC1, 0xF6B5, 0x9DC2, 0xC563, 0x9DC3, 0xF6BB, + 0x9DC5, 0xF6BA, 0x9DC7, 0xF6B6, 0x9DC8, 0xF6C2, 0x9DCA, 0xF6B7, 0x9DCB, 0xF7BB, 0x9DCC, 0xF6C5, 0x9DCD, 0xF6C7, 0x9DCE, 0xF6BE, + 0x9DCF, 0xF6B8, 0x9DD0, 0xF7BC, 0x9DD1, 0xF7BE, 0x9DD2, 0xF7B8, 0x9DD3, 0xC5C2, 0x9DD5, 0xF7C5, 0x9DD6, 0xF7C3, 0x9DD7, 0xC5C3, + 0x9DD8, 0xF7C2, 0x9DD9, 0xF7C1, 0x9DDA, 0xF7BA, 0x9DDB, 0xF7B7, 0x9DDC, 0xF7BD, 0x9DDD, 0xF7C6, 0x9DDE, 0xF7B9, 0x9DDF, 0xF7BF, + 0x9DE1, 0xF869, 0x9DE2, 0xF86E, 0x9DE3, 0xF864, 0x9DE4, 0xF867, 0x9DE5, 0xC5EE, 0x9DE6, 0xF86B, 0x9DE8, 0xF872, 0x9DE9, 0xF7C0, + 0x9DEB, 0xF865, 0x9DEC, 0xF86F, 0x9DED, 0xF873, 0x9DEE, 0xF86A, 0x9DEF, 0xF863, 0x9DF0, 0xF86D, 0x9DF2, 0xF86C, 0x9DF3, 0xF871, + 0x9DF4, 0xF870, 0x9DF5, 0xF7C4, 0x9DF6, 0xF868, 0x9DF7, 0xF862, 0x9DF8, 0xF866, 0x9DF9, 0xC64E, 0x9DFA, 0xC64F, 0x9DFB, 0xF861, + 0x9DFD, 0xF8E6, 0x9DFE, 0xF8DD, 0x9DFF, 0xF8E5, 0x9E00, 0xF8E2, 0x9E01, 0xF8E3, 0x9E02, 0xF8DC, 0x9E03, 0xF8DF, 0x9E04, 0xF8E7, + 0x9E05, 0xF8E1, 0x9E06, 0xF8E0, 0x9E07, 0xF8DE, 0x9E09, 0xF8E4, 0x9E0B, 0xF95D, 0x9E0D, 0xF95E, 0x9E0F, 0xF960, 0x9E10, 0xF95F, + 0x9E11, 0xF962, 0x9E12, 0xF961, 0x9E13, 0xF97C, 0x9E14, 0xF97B, 0x9E15, 0xF9B7, 0x9E17, 0xF9B8, 0x9E19, 0xF9C5, 0x9E1A, 0xC678, + 0x9E1B, 0xC67C, 0x9E1D, 0xF9CF, 0x9E1E, 0xC67D, 0x9E75, 0xB3BF, 0x9E79, 0xC4D0, 0x9E7A, 0xF6C9, 0x9E7C, 0xC650, 0x9E7D, 0xC651, + 0x9E7F, 0xB3C0, 0x9E80, 0xE0EE, 0x9E82, 0xB9A8, 0x9E83, 0xE8F0, 0x9E86, 0xECB0, 0x9E87, 0xECB1, 0x9E88, 0xECAF, 0x9E89, 0xEFAB, + 0x9E8A, 0xEFAA, 0x9E8B, 0xC247, 0x9E8C, 0xF1DF, 0x9E8D, 0xEFAC, 0x9E8E, 0xF1DE, 0x9E91, 0xF3F3, 0x9E92, 0xC451, 0x9E93, 0xC453, + 0x9E94, 0xF3F2, 0x9E97, 0xC452, 0x9E99, 0xF5B1, 0x9E9A, 0xF5B3, 0x9E9B, 0xF5B2, 0x9E9C, 0xF6CA, 0x9E9D, 0xC565, 0x9E9F, 0xC5EF, + 0x9EA0, 0xF8E8, 0x9EA1, 0xF963, 0x9EA4, 0xF9D2, 0x9EA5, 0xB3C1, 0x9EA7, 0xE4E5, 0x9EA9, 0xBEA2, 0x9EAD, 0xECB3, 0x9EAE, 0xECB2, + 0x9EB0, 0xEFAD, 0x9EB4, 0xC454, 0x9EB5, 0xC4D1, 0x9EB6, 0xF7C7, 0x9EB7, 0xF9CB, 0x9EBB, 0xB3C2, 0x9EBC, 0xBBF2, 0x9EBE, 0xBEA3, + 0x9EC0, 0xF3F4, 0x9EC2, 0xF874, 0x9EC3, 0xB6C0, 0x9EC8, 0xEFAE, 0x9ECC, 0xC664, 0x9ECD, 0xB6C1, 0x9ECE, 0xBEA4, 0x9ECF, 0xC248, + 0x9ED0, 0xF875, 0x9ED1, 0xB6C2, 0x9ED3, 0xE8F1, 0x9ED4, 0xC072, 0x9ED5, 0xECB4, 0x9ED6, 0xECB5, 0x9ED8, 0xC071, 0x9EDA, 0xEFAF, + 0x9EDB, 0xC24C, 0x9EDC, 0xC24A, 0x9EDD, 0xC24B, 0x9EDE, 0xC249, 0x9EDF, 0xF1E0, 0x9EE0, 0xC35C, 0x9EE4, 0xF5B5, 0x9EE5, 0xF5B4, + 0x9EE6, 0xF5B7, 0x9EE7, 0xF5B6, 0x9EE8, 0xC4D2, 0x9EEB, 0xF6CB, 0x9EED, 0xF6CD, 0x9EEE, 0xF6CC, 0x9EEF, 0xC566, 0x9EF0, 0xF7C8, + 0x9EF2, 0xF876, 0x9EF3, 0xF877, 0x9EF4, 0xC5F0, 0x9EF5, 0xF964, 0x9EF6, 0xF97D, 0x9EF7, 0xC675, 0x9EF9, 0xDCB0, 0x9EFA, 0xECB6, + 0x9EFB, 0xEFB0, 0x9EFC, 0xF3F5, 0x9EFD, 0xE0EF, 0x9EFF, 0xEFB1, 0x9F00, 0xF1E2, 0x9F01, 0xF1E1, 0x9F06, 0xF878, 0x9F07, 0xC652, + 0x9F09, 0xF965, 0x9F0A, 0xF97E, 0x9F0E, 0xB9A9, 0x9F0F, 0xE8F2, 0x9F10, 0xE8F3, 0x9F12, 0xECB7, 0x9F13, 0xB9AA, 0x9F15, 0xC35D, + 0x9F16, 0xF1E3, 0x9F18, 0xF6CF, 0x9F19, 0xC567, 0x9F1A, 0xF6D0, 0x9F1B, 0xF6CE, 0x9F1C, 0xF879, 0x9F1E, 0xF8E9, 0x9F20, 0xB9AB, + 0x9F22, 0xEFB4, 0x9F23, 0xEFB3, 0x9F24, 0xEFB2, 0x9F25, 0xF1E4, 0x9F28, 0xF1E8, 0x9F29, 0xF1E7, 0x9F2A, 0xF1E6, 0x9F2B, 0xF1E5, + 0x9F2C, 0xC35E, 0x9F2D, 0xF3F6, 0x9F2E, 0xF5B9, 0x9F2F, 0xC4D3, 0x9F30, 0xF5B8, 0x9F31, 0xF6D1, 0x9F32, 0xF7CB, 0x9F33, 0xF7CA, + 0x9F34, 0xC5C4, 0x9F35, 0xF7C9, 0x9F36, 0xF87C, 0x9F37, 0xF87B, 0x9F38, 0xF87A, 0x9F3B, 0xBBF3, 0x9F3D, 0xECB8, 0x9F3E, 0xC24D, + 0x9F40, 0xF3F7, 0x9F41, 0xF3F8, 0x9F42, 0xF7CC, 0x9F43, 0xF87D, 0x9F46, 0xF8EA, 0x9F47, 0xF966, 0x9F48, 0xF9B9, 0x9F49, 0xF9D4, + 0x9F4A, 0xBBF4, 0x9F4B, 0xC24E, 0x9F4C, 0xF1E9, 0x9F4D, 0xF3F9, 0x9F4E, 0xF6D2, 0x9F4F, 0xF87E, 0x9F52, 0xBEA6, 0x9F54, 0xEFB5, + 0x9F55, 0xF1EA, 0x9F56, 0xF3FA, 0x9F57, 0xF3FB, 0x9F58, 0xF3FC, 0x9F59, 0xF5BE, 0x9F5B, 0xF5BA, 0x9F5C, 0xC568, 0x9F5D, 0xF5BD, + 0x9F5E, 0xF5BC, 0x9F5F, 0xC4D4, 0x9F60, 0xF5BB, 0x9F61, 0xC4D6, 0x9F63, 0xC4D5, 0x9F64, 0xF6D4, 0x9F65, 0xF6D3, 0x9F66, 0xC569, + 0x9F67, 0xC56A, 0x9F6A, 0xC5C6, 0x9F6B, 0xF7CD, 0x9F6C, 0xC5C5, 0x9F6E, 0xF8A3, 0x9F6F, 0xF8A4, 0x9F70, 0xF8A2, 0x9F71, 0xF8A1, + 0x9F72, 0xC654, 0x9F74, 0xF8EB, 0x9F75, 0xF8EC, 0x9F76, 0xF8ED, 0x9F77, 0xC653, 0x9F78, 0xF967, 0x9F79, 0xF96A, 0x9F7A, 0xF969, + 0x9F7B, 0xF968, 0x9F7E, 0xF9D3, 0x9F8D, 0xC073, 0x9F90, 0xC365, 0x9F91, 0xF5BF, 0x9F92, 0xF6D5, 0x9F94, 0xC5C7, 0x9F95, 0xF7CE, + 0x9F98, 0xF9D5, 0x9F9C, 0xC074, 0x9FA0, 0xEFB6, 0x9FA2, 0xF7CF, 0x9FA4, 0xF9A1, 0xFA0C, 0xC94A, 0xFA0D, 0xDDFC, 0xFE30, 0xA14A, + 0xFE31, 0xA157, 0xFE33, 0xA159, 0xFE34, 0xA15B, 0xFE35, 0xA15F, 0xFE36, 0xA160, 0xFE37, 0xA163, 0xFE38, 0xA164, 0xFE39, 0xA167, + 0xFE3A, 0xA168, 0xFE3B, 0xA16B, 0xFE3C, 0xA16C, 0xFE3D, 0xA16F, 0xFE3E, 0xA170, 0xFE3F, 0xA173, 0xFE40, 0xA174, 0xFE41, 0xA177, + 0xFE42, 0xA178, 0xFE43, 0xA17B, 0xFE44, 0xA17C, 0xFE49, 0xA1C6, 0xFE4A, 0xA1C7, 0xFE4B, 0xA1CA, 0xFE4C, 0xA1CB, 0xFE4D, 0xA1C8, + 0xFE4E, 0xA1C9, 0xFE4F, 0xA15C, 0xFE50, 0xA14D, 0xFE51, 0xA14E, 0xFE52, 0xA14F, 0xFE54, 0xA151, 0xFE55, 0xA152, 0xFE56, 0xA153, + 0xFE57, 0xA154, 0xFE59, 0xA17D, 0xFE5A, 0xA17E, 0xFE5B, 0xA1A1, 0xFE5C, 0xA1A2, 0xFE5D, 0xA1A3, 0xFE5E, 0xA1A4, 0xFE5F, 0xA1CC, + 0xFE60, 0xA1CD, 0xFE61, 0xA1CE, 0xFE62, 0xA1DE, 0xFE63, 0xA1DF, 0xFE64, 0xA1E0, 0xFE65, 0xA1E1, 0xFE66, 0xA1E2, 0xFE68, 0xA242, + 0xFE69, 0xA24C, 0xFE6A, 0xA24D, 0xFE6B, 0xA24E, 0xFF01, 0xA149, 0xFF03, 0xA1AD, 0xFF04, 0xA243, 0xFF05, 0xA248, 0xFF06, 0xA1AE, + 0xFF08, 0xA15D, 0xFF09, 0xA15E, 0xFF0A, 0xA1AF, 0xFF0B, 0xA1CF, 0xFF0C, 0xA141, 0xFF0D, 0xA1D0, 0xFF0E, 0xA144, 0xFF0F, 0xA1FE, + 0xFF10, 0xA2AF, 0xFF11, 0xA2B0, 0xFF12, 0xA2B1, 0xFF13, 0xA2B2, 0xFF14, 0xA2B3, 0xFF15, 0xA2B4, 0xFF16, 0xA2B5, 0xFF17, 0xA2B6, + 0xFF18, 0xA2B7, 0xFF19, 0xA2B8, 0xFF1A, 0xA147, 0xFF1B, 0xA146, 0xFF1C, 0xA1D5, 0xFF1D, 0xA1D7, 0xFF1E, 0xA1D6, 0xFF1F, 0xA148, + 0xFF20, 0xA249, 0xFF21, 0xA2CF, 0xFF22, 0xA2D0, 0xFF23, 0xA2D1, 0xFF24, 0xA2D2, 0xFF25, 0xA2D3, 0xFF26, 0xA2D4, 0xFF27, 0xA2D5, + 0xFF28, 0xA2D6, 0xFF29, 0xA2D7, 0xFF2A, 0xA2D8, 0xFF2B, 0xA2D9, 0xFF2C, 0xA2DA, 0xFF2D, 0xA2DB, 0xFF2E, 0xA2DC, 0xFF2F, 0xA2DD, + 0xFF30, 0xA2DE, 0xFF31, 0xA2DF, 0xFF32, 0xA2E0, 0xFF33, 0xA2E1, 0xFF34, 0xA2E2, 0xFF35, 0xA2E3, 0xFF36, 0xA2E4, 0xFF37, 0xA2E5, + 0xFF38, 0xA2E6, 0xFF39, 0xA2E7, 0xFF3A, 0xA2E8, 0xFF3C, 0xA240, 0xFF3F, 0xA1C4, 0xFF41, 0xA2E9, 0xFF42, 0xA2EA, 0xFF43, 0xA2EB, + 0xFF44, 0xA2EC, 0xFF45, 0xA2ED, 0xFF46, 0xA2EE, 0xFF47, 0xA2EF, 0xFF48, 0xA2F0, 0xFF49, 0xA2F1, 0xFF4A, 0xA2F2, 0xFF4B, 0xA2F3, + 0xFF4C, 0xA2F4, 0xFF4D, 0xA2F5, 0xFF4E, 0xA2F6, 0xFF4F, 0xA2F7, 0xFF50, 0xA2F8, 0xFF51, 0xA2F9, 0xFF52, 0xA2FA, 0xFF53, 0xA2FB, + 0xFF54, 0xA2FC, 0xFF55, 0xA2FD, 0xFF56, 0xA2FE, 0xFF57, 0xA340, 0xFF58, 0xA341, 0xFF59, 0xA342, 0xFF5A, 0xA343, 0xFF5B, 0xA161, + 0xFF5C, 0xA155, 0xFF5D, 0xA162, 0xFF5E, 0xA1E3, 0xFFE0, 0xA246, 0xFFE1, 0xA247, 0xFFE3, 0xA1C3, 0xFFE5, 0xA244, 0, 0 +}; + +static const WCHAR oem2uni950[] = { /* Big5 --> Unicode pairs */ + 0xA140, 0x3000, 0xA141, 0xFF0C, 0xA142, 0x3001, 0xA143, 0x3002, 0xA144, 0xFF0E, 0xA145, 0x2027, 0xA146, 0xFF1B, 0xA147, 0xFF1A, + 0xA148, 0xFF1F, 0xA149, 0xFF01, 0xA14A, 0xFE30, 0xA14B, 0x2026, 0xA14C, 0x2025, 0xA14D, 0xFE50, 0xA14E, 0xFE51, 0xA14F, 0xFE52, + 0xA150, 0x00B7, 0xA151, 0xFE54, 0xA152, 0xFE55, 0xA153, 0xFE56, 0xA154, 0xFE57, 0xA155, 0xFF5C, 0xA156, 0x2013, 0xA157, 0xFE31, + 0xA158, 0x2014, 0xA159, 0xFE33, 0xA15A, 0x2574, 0xA15B, 0xFE34, 0xA15C, 0xFE4F, 0xA15D, 0xFF08, 0xA15E, 0xFF09, 0xA15F, 0xFE35, + 0xA160, 0xFE36, 0xA161, 0xFF5B, 0xA162, 0xFF5D, 0xA163, 0xFE37, 0xA164, 0xFE38, 0xA165, 0x3014, 0xA166, 0x3015, 0xA167, 0xFE39, + 0xA168, 0xFE3A, 0xA169, 0x3010, 0xA16A, 0x3011, 0xA16B, 0xFE3B, 0xA16C, 0xFE3C, 0xA16D, 0x300A, 0xA16E, 0x300B, 0xA16F, 0xFE3D, + 0xA170, 0xFE3E, 0xA171, 0x3008, 0xA172, 0x3009, 0xA173, 0xFE3F, 0xA174, 0xFE40, 0xA175, 0x300C, 0xA176, 0x300D, 0xA177, 0xFE41, + 0xA178, 0xFE42, 0xA179, 0x300E, 0xA17A, 0x300F, 0xA17B, 0xFE43, 0xA17C, 0xFE44, 0xA17D, 0xFE59, 0xA17E, 0xFE5A, 0xA1A1, 0xFE5B, + 0xA1A2, 0xFE5C, 0xA1A3, 0xFE5D, 0xA1A4, 0xFE5E, 0xA1A5, 0x2018, 0xA1A6, 0x2019, 0xA1A7, 0x201C, 0xA1A8, 0x201D, 0xA1A9, 0x301D, + 0xA1AA, 0x301E, 0xA1AB, 0x2035, 0xA1AC, 0x2032, 0xA1AD, 0xFF03, 0xA1AE, 0xFF06, 0xA1AF, 0xFF0A, 0xA1B0, 0x203B, 0xA1B1, 0x00A7, + 0xA1B2, 0x3003, 0xA1B3, 0x25CB, 0xA1B4, 0x25CF, 0xA1B5, 0x25B3, 0xA1B6, 0x25B2, 0xA1B7, 0x25CE, 0xA1B8, 0x2606, 0xA1B9, 0x2605, + 0xA1BA, 0x25C7, 0xA1BB, 0x25C6, 0xA1BC, 0x25A1, 0xA1BD, 0x25A0, 0xA1BE, 0x25BD, 0xA1BF, 0x25BC, 0xA1C0, 0x32A3, 0xA1C1, 0x2105, + 0xA1C2, 0x00AF, 0xA1C3, 0xFFE3, 0xA1C4, 0xFF3F, 0xA1C5, 0x02CD, 0xA1C6, 0xFE49, 0xA1C7, 0xFE4A, 0xA1C8, 0xFE4D, 0xA1C9, 0xFE4E, + 0xA1CA, 0xFE4B, 0xA1CB, 0xFE4C, 0xA1CC, 0xFE5F, 0xA1CD, 0xFE60, 0xA1CE, 0xFE61, 0xA1CF, 0xFF0B, 0xA1D0, 0xFF0D, 0xA1D1, 0x00D7, + 0xA1D2, 0x00F7, 0xA1D3, 0x00B1, 0xA1D4, 0x221A, 0xA1D5, 0xFF1C, 0xA1D6, 0xFF1E, 0xA1D7, 0xFF1D, 0xA1D8, 0x2266, 0xA1D9, 0x2267, + 0xA1DA, 0x2260, 0xA1DB, 0x221E, 0xA1DC, 0x2252, 0xA1DD, 0x2261, 0xA1DE, 0xFE62, 0xA1DF, 0xFE63, 0xA1E0, 0xFE64, 0xA1E1, 0xFE65, + 0xA1E2, 0xFE66, 0xA1E3, 0xFF5E, 0xA1E4, 0x2229, 0xA1E5, 0x222A, 0xA1E6, 0x22A5, 0xA1E7, 0x2220, 0xA1E8, 0x221F, 0xA1E9, 0x22BF, + 0xA1EA, 0x33D2, 0xA1EB, 0x33D1, 0xA1EC, 0x222B, 0xA1ED, 0x222E, 0xA1EE, 0x2235, 0xA1EF, 0x2234, 0xA1F0, 0x2640, 0xA1F1, 0x2642, + 0xA1F2, 0x2295, 0xA1F3, 0x2299, 0xA1F4, 0x2191, 0xA1F5, 0x2193, 0xA1F6, 0x2190, 0xA1F7, 0x2192, 0xA1F8, 0x2196, 0xA1F9, 0x2197, + 0xA1FA, 0x2199, 0xA1FB, 0x2198, 0xA1FC, 0x2225, 0xA1FD, 0x2223, 0xA1FE, 0xFF0F, 0xA240, 0xFF3C, 0xA241, 0x2215, 0xA242, 0xFE68, + 0xA243, 0xFF04, 0xA244, 0xFFE5, 0xA245, 0x3012, 0xA246, 0xFFE0, 0xA247, 0xFFE1, 0xA248, 0xFF05, 0xA249, 0xFF20, 0xA24A, 0x2103, + 0xA24B, 0x2109, 0xA24C, 0xFE69, 0xA24D, 0xFE6A, 0xA24E, 0xFE6B, 0xA24F, 0x33D5, 0xA250, 0x339C, 0xA251, 0x339D, 0xA252, 0x339E, + 0xA253, 0x33CE, 0xA254, 0x33A1, 0xA255, 0x338E, 0xA256, 0x338F, 0xA257, 0x33C4, 0xA258, 0x00B0, 0xA259, 0x5159, 0xA25A, 0x515B, + 0xA25B, 0x515E, 0xA25C, 0x515D, 0xA25D, 0x5161, 0xA25E, 0x5163, 0xA25F, 0x55E7, 0xA260, 0x74E9, 0xA261, 0x7CCE, 0xA262, 0x2581, + 0xA263, 0x2582, 0xA264, 0x2583, 0xA265, 0x2584, 0xA266, 0x2585, 0xA267, 0x2586, 0xA268, 0x2587, 0xA269, 0x2588, 0xA26A, 0x258F, + 0xA26B, 0x258E, 0xA26C, 0x258D, 0xA26D, 0x258C, 0xA26E, 0x258B, 0xA26F, 0x258A, 0xA270, 0x2589, 0xA271, 0x253C, 0xA272, 0x2534, + 0xA273, 0x252C, 0xA274, 0x2524, 0xA275, 0x251C, 0xA276, 0x2594, 0xA277, 0x2500, 0xA278, 0x2502, 0xA279, 0x2595, 0xA27A, 0x250C, + 0xA27B, 0x2510, 0xA27C, 0x2514, 0xA27D, 0x2518, 0xA27E, 0x256D, 0xA2A1, 0x256E, 0xA2A2, 0x2570, 0xA2A3, 0x256F, 0xA2A4, 0x2550, + 0xA2A5, 0x255E, 0xA2A6, 0x256A, 0xA2A7, 0x2561, 0xA2A8, 0x25E2, 0xA2A9, 0x25E3, 0xA2AA, 0x25E5, 0xA2AB, 0x25E4, 0xA2AC, 0x2571, + 0xA2AD, 0x2572, 0xA2AE, 0x2573, 0xA2AF, 0xFF10, 0xA2B0, 0xFF11, 0xA2B1, 0xFF12, 0xA2B2, 0xFF13, 0xA2B3, 0xFF14, 0xA2B4, 0xFF15, + 0xA2B5, 0xFF16, 0xA2B6, 0xFF17, 0xA2B7, 0xFF18, 0xA2B8, 0xFF19, 0xA2B9, 0x2160, 0xA2BA, 0x2161, 0xA2BB, 0x2162, 0xA2BC, 0x2163, + 0xA2BD, 0x2164, 0xA2BE, 0x2165, 0xA2BF, 0x2166, 0xA2C0, 0x2167, 0xA2C1, 0x2168, 0xA2C2, 0x2169, 0xA2C3, 0x3021, 0xA2C4, 0x3022, + 0xA2C5, 0x3023, 0xA2C6, 0x3024, 0xA2C7, 0x3025, 0xA2C8, 0x3026, 0xA2C9, 0x3027, 0xA2CA, 0x3028, 0xA2CB, 0x3029, 0xA2CC, 0x5341, + 0xA2CD, 0x5344, 0xA2CE, 0x5345, 0xA2CF, 0xFF21, 0xA2D0, 0xFF22, 0xA2D1, 0xFF23, 0xA2D2, 0xFF24, 0xA2D3, 0xFF25, 0xA2D4, 0xFF26, + 0xA2D5, 0xFF27, 0xA2D6, 0xFF28, 0xA2D7, 0xFF29, 0xA2D8, 0xFF2A, 0xA2D9, 0xFF2B, 0xA2DA, 0xFF2C, 0xA2DB, 0xFF2D, 0xA2DC, 0xFF2E, + 0xA2DD, 0xFF2F, 0xA2DE, 0xFF30, 0xA2DF, 0xFF31, 0xA2E0, 0xFF32, 0xA2E1, 0xFF33, 0xA2E2, 0xFF34, 0xA2E3, 0xFF35, 0xA2E4, 0xFF36, + 0xA2E5, 0xFF37, 0xA2E6, 0xFF38, 0xA2E7, 0xFF39, 0xA2E8, 0xFF3A, 0xA2E9, 0xFF41, 0xA2EA, 0xFF42, 0xA2EB, 0xFF43, 0xA2EC, 0xFF44, + 0xA2ED, 0xFF45, 0xA2EE, 0xFF46, 0xA2EF, 0xFF47, 0xA2F0, 0xFF48, 0xA2F1, 0xFF49, 0xA2F2, 0xFF4A, 0xA2F3, 0xFF4B, 0xA2F4, 0xFF4C, + 0xA2F5, 0xFF4D, 0xA2F6, 0xFF4E, 0xA2F7, 0xFF4F, 0xA2F8, 0xFF50, 0xA2F9, 0xFF51, 0xA2FA, 0xFF52, 0xA2FB, 0xFF53, 0xA2FC, 0xFF54, + 0xA2FD, 0xFF55, 0xA2FE, 0xFF56, 0xA340, 0xFF57, 0xA341, 0xFF58, 0xA342, 0xFF59, 0xA343, 0xFF5A, 0xA344, 0x0391, 0xA345, 0x0392, + 0xA346, 0x0393, 0xA347, 0x0394, 0xA348, 0x0395, 0xA349, 0x0396, 0xA34A, 0x0397, 0xA34B, 0x0398, 0xA34C, 0x0399, 0xA34D, 0x039A, + 0xA34E, 0x039B, 0xA34F, 0x039C, 0xA350, 0x039D, 0xA351, 0x039E, 0xA352, 0x039F, 0xA353, 0x03A0, 0xA354, 0x03A1, 0xA355, 0x03A3, + 0xA356, 0x03A4, 0xA357, 0x03A5, 0xA358, 0x03A6, 0xA359, 0x03A7, 0xA35A, 0x03A8, 0xA35B, 0x03A9, 0xA35C, 0x03B1, 0xA35D, 0x03B2, + 0xA35E, 0x03B3, 0xA35F, 0x03B4, 0xA360, 0x03B5, 0xA361, 0x03B6, 0xA362, 0x03B7, 0xA363, 0x03B8, 0xA364, 0x03B9, 0xA365, 0x03BA, + 0xA366, 0x03BB, 0xA367, 0x03BC, 0xA368, 0x03BD, 0xA369, 0x03BE, 0xA36A, 0x03BF, 0xA36B, 0x03C0, 0xA36C, 0x03C1, 0xA36D, 0x03C3, + 0xA36E, 0x03C4, 0xA36F, 0x03C5, 0xA370, 0x03C6, 0xA371, 0x03C7, 0xA372, 0x03C8, 0xA373, 0x03C9, 0xA374, 0x3105, 0xA375, 0x3106, + 0xA376, 0x3107, 0xA377, 0x3108, 0xA378, 0x3109, 0xA379, 0x310A, 0xA37A, 0x310B, 0xA37B, 0x310C, 0xA37C, 0x310D, 0xA37D, 0x310E, + 0xA37E, 0x310F, 0xA3A1, 0x3110, 0xA3A2, 0x3111, 0xA3A3, 0x3112, 0xA3A4, 0x3113, 0xA3A5, 0x3114, 0xA3A6, 0x3115, 0xA3A7, 0x3116, + 0xA3A8, 0x3117, 0xA3A9, 0x3118, 0xA3AA, 0x3119, 0xA3AB, 0x311A, 0xA3AC, 0x311B, 0xA3AD, 0x311C, 0xA3AE, 0x311D, 0xA3AF, 0x311E, + 0xA3B0, 0x311F, 0xA3B1, 0x3120, 0xA3B2, 0x3121, 0xA3B3, 0x3122, 0xA3B4, 0x3123, 0xA3B5, 0x3124, 0xA3B6, 0x3125, 0xA3B7, 0x3126, + 0xA3B8, 0x3127, 0xA3B9, 0x3128, 0xA3BA, 0x3129, 0xA3BB, 0x02D9, 0xA3BC, 0x02C9, 0xA3BD, 0x02CA, 0xA3BE, 0x02C7, 0xA3BF, 0x02CB, + 0xA3E1, 0x20AC, 0xA440, 0x4E00, 0xA441, 0x4E59, 0xA442, 0x4E01, 0xA443, 0x4E03, 0xA444, 0x4E43, 0xA445, 0x4E5D, 0xA446, 0x4E86, + 0xA447, 0x4E8C, 0xA448, 0x4EBA, 0xA449, 0x513F, 0xA44A, 0x5165, 0xA44B, 0x516B, 0xA44C, 0x51E0, 0xA44D, 0x5200, 0xA44E, 0x5201, + 0xA44F, 0x529B, 0xA450, 0x5315, 0xA451, 0x5341, 0xA452, 0x535C, 0xA453, 0x53C8, 0xA454, 0x4E09, 0xA455, 0x4E0B, 0xA456, 0x4E08, + 0xA457, 0x4E0A, 0xA458, 0x4E2B, 0xA459, 0x4E38, 0xA45A, 0x51E1, 0xA45B, 0x4E45, 0xA45C, 0x4E48, 0xA45D, 0x4E5F, 0xA45E, 0x4E5E, + 0xA45F, 0x4E8E, 0xA460, 0x4EA1, 0xA461, 0x5140, 0xA462, 0x5203, 0xA463, 0x52FA, 0xA464, 0x5343, 0xA465, 0x53C9, 0xA466, 0x53E3, + 0xA467, 0x571F, 0xA468, 0x58EB, 0xA469, 0x5915, 0xA46A, 0x5927, 0xA46B, 0x5973, 0xA46C, 0x5B50, 0xA46D, 0x5B51, 0xA46E, 0x5B53, + 0xA46F, 0x5BF8, 0xA470, 0x5C0F, 0xA471, 0x5C22, 0xA472, 0x5C38, 0xA473, 0x5C71, 0xA474, 0x5DDD, 0xA475, 0x5DE5, 0xA476, 0x5DF1, + 0xA477, 0x5DF2, 0xA478, 0x5DF3, 0xA479, 0x5DFE, 0xA47A, 0x5E72, 0xA47B, 0x5EFE, 0xA47C, 0x5F0B, 0xA47D, 0x5F13, 0xA47E, 0x624D, + 0xA4A1, 0x4E11, 0xA4A2, 0x4E10, 0xA4A3, 0x4E0D, 0xA4A4, 0x4E2D, 0xA4A5, 0x4E30, 0xA4A6, 0x4E39, 0xA4A7, 0x4E4B, 0xA4A8, 0x5C39, + 0xA4A9, 0x4E88, 0xA4AA, 0x4E91, 0xA4AB, 0x4E95, 0xA4AC, 0x4E92, 0xA4AD, 0x4E94, 0xA4AE, 0x4EA2, 0xA4AF, 0x4EC1, 0xA4B0, 0x4EC0, + 0xA4B1, 0x4EC3, 0xA4B2, 0x4EC6, 0xA4B3, 0x4EC7, 0xA4B4, 0x4ECD, 0xA4B5, 0x4ECA, 0xA4B6, 0x4ECB, 0xA4B7, 0x4EC4, 0xA4B8, 0x5143, + 0xA4B9, 0x5141, 0xA4BA, 0x5167, 0xA4BB, 0x516D, 0xA4BC, 0x516E, 0xA4BD, 0x516C, 0xA4BE, 0x5197, 0xA4BF, 0x51F6, 0xA4C0, 0x5206, + 0xA4C1, 0x5207, 0xA4C2, 0x5208, 0xA4C3, 0x52FB, 0xA4C4, 0x52FE, 0xA4C5, 0x52FF, 0xA4C6, 0x5316, 0xA4C7, 0x5339, 0xA4C8, 0x5348, + 0xA4C9, 0x5347, 0xA4CA, 0x5345, 0xA4CB, 0x535E, 0xA4CC, 0x5384, 0xA4CD, 0x53CB, 0xA4CE, 0x53CA, 0xA4CF, 0x53CD, 0xA4D0, 0x58EC, + 0xA4D1, 0x5929, 0xA4D2, 0x592B, 0xA4D3, 0x592A, 0xA4D4, 0x592D, 0xA4D5, 0x5B54, 0xA4D6, 0x5C11, 0xA4D7, 0x5C24, 0xA4D8, 0x5C3A, + 0xA4D9, 0x5C6F, 0xA4DA, 0x5DF4, 0xA4DB, 0x5E7B, 0xA4DC, 0x5EFF, 0xA4DD, 0x5F14, 0xA4DE, 0x5F15, 0xA4DF, 0x5FC3, 0xA4E0, 0x6208, + 0xA4E1, 0x6236, 0xA4E2, 0x624B, 0xA4E3, 0x624E, 0xA4E4, 0x652F, 0xA4E5, 0x6587, 0xA4E6, 0x6597, 0xA4E7, 0x65A4, 0xA4E8, 0x65B9, + 0xA4E9, 0x65E5, 0xA4EA, 0x66F0, 0xA4EB, 0x6708, 0xA4EC, 0x6728, 0xA4ED, 0x6B20, 0xA4EE, 0x6B62, 0xA4EF, 0x6B79, 0xA4F0, 0x6BCB, + 0xA4F1, 0x6BD4, 0xA4F2, 0x6BDB, 0xA4F3, 0x6C0F, 0xA4F4, 0x6C34, 0xA4F5, 0x706B, 0xA4F6, 0x722A, 0xA4F7, 0x7236, 0xA4F8, 0x723B, + 0xA4F9, 0x7247, 0xA4FA, 0x7259, 0xA4FB, 0x725B, 0xA4FC, 0x72AC, 0xA4FD, 0x738B, 0xA4FE, 0x4E19, 0xA540, 0x4E16, 0xA541, 0x4E15, + 0xA542, 0x4E14, 0xA543, 0x4E18, 0xA544, 0x4E3B, 0xA545, 0x4E4D, 0xA546, 0x4E4F, 0xA547, 0x4E4E, 0xA548, 0x4EE5, 0xA549, 0x4ED8, + 0xA54A, 0x4ED4, 0xA54B, 0x4ED5, 0xA54C, 0x4ED6, 0xA54D, 0x4ED7, 0xA54E, 0x4EE3, 0xA54F, 0x4EE4, 0xA550, 0x4ED9, 0xA551, 0x4EDE, + 0xA552, 0x5145, 0xA553, 0x5144, 0xA554, 0x5189, 0xA555, 0x518A, 0xA556, 0x51AC, 0xA557, 0x51F9, 0xA558, 0x51FA, 0xA559, 0x51F8, + 0xA55A, 0x520A, 0xA55B, 0x52A0, 0xA55C, 0x529F, 0xA55D, 0x5305, 0xA55E, 0x5306, 0xA55F, 0x5317, 0xA560, 0x531D, 0xA561, 0x4EDF, + 0xA562, 0x534A, 0xA563, 0x5349, 0xA564, 0x5361, 0xA565, 0x5360, 0xA566, 0x536F, 0xA567, 0x536E, 0xA568, 0x53BB, 0xA569, 0x53EF, + 0xA56A, 0x53E4, 0xA56B, 0x53F3, 0xA56C, 0x53EC, 0xA56D, 0x53EE, 0xA56E, 0x53E9, 0xA56F, 0x53E8, 0xA570, 0x53FC, 0xA571, 0x53F8, + 0xA572, 0x53F5, 0xA573, 0x53EB, 0xA574, 0x53E6, 0xA575, 0x53EA, 0xA576, 0x53F2, 0xA577, 0x53F1, 0xA578, 0x53F0, 0xA579, 0x53E5, + 0xA57A, 0x53ED, 0xA57B, 0x53FB, 0xA57C, 0x56DB, 0xA57D, 0x56DA, 0xA57E, 0x5916, 0xA5A1, 0x592E, 0xA5A2, 0x5931, 0xA5A3, 0x5974, + 0xA5A4, 0x5976, 0xA5A5, 0x5B55, 0xA5A6, 0x5B83, 0xA5A7, 0x5C3C, 0xA5A8, 0x5DE8, 0xA5A9, 0x5DE7, 0xA5AA, 0x5DE6, 0xA5AB, 0x5E02, + 0xA5AC, 0x5E03, 0xA5AD, 0x5E73, 0xA5AE, 0x5E7C, 0xA5AF, 0x5F01, 0xA5B0, 0x5F18, 0xA5B1, 0x5F17, 0xA5B2, 0x5FC5, 0xA5B3, 0x620A, + 0xA5B4, 0x6253, 0xA5B5, 0x6254, 0xA5B6, 0x6252, 0xA5B7, 0x6251, 0xA5B8, 0x65A5, 0xA5B9, 0x65E6, 0xA5BA, 0x672E, 0xA5BB, 0x672C, + 0xA5BC, 0x672A, 0xA5BD, 0x672B, 0xA5BE, 0x672D, 0xA5BF, 0x6B63, 0xA5C0, 0x6BCD, 0xA5C1, 0x6C11, 0xA5C2, 0x6C10, 0xA5C3, 0x6C38, + 0xA5C4, 0x6C41, 0xA5C5, 0x6C40, 0xA5C6, 0x6C3E, 0xA5C7, 0x72AF, 0xA5C8, 0x7384, 0xA5C9, 0x7389, 0xA5CA, 0x74DC, 0xA5CB, 0x74E6, + 0xA5CC, 0x7518, 0xA5CD, 0x751F, 0xA5CE, 0x7528, 0xA5CF, 0x7529, 0xA5D0, 0x7530, 0xA5D1, 0x7531, 0xA5D2, 0x7532, 0xA5D3, 0x7533, + 0xA5D4, 0x758B, 0xA5D5, 0x767D, 0xA5D6, 0x76AE, 0xA5D7, 0x76BF, 0xA5D8, 0x76EE, 0xA5D9, 0x77DB, 0xA5DA, 0x77E2, 0xA5DB, 0x77F3, + 0xA5DC, 0x793A, 0xA5DD, 0x79BE, 0xA5DE, 0x7A74, 0xA5DF, 0x7ACB, 0xA5E0, 0x4E1E, 0xA5E1, 0x4E1F, 0xA5E2, 0x4E52, 0xA5E3, 0x4E53, + 0xA5E4, 0x4E69, 0xA5E5, 0x4E99, 0xA5E6, 0x4EA4, 0xA5E7, 0x4EA6, 0xA5E8, 0x4EA5, 0xA5E9, 0x4EFF, 0xA5EA, 0x4F09, 0xA5EB, 0x4F19, + 0xA5EC, 0x4F0A, 0xA5ED, 0x4F15, 0xA5EE, 0x4F0D, 0xA5EF, 0x4F10, 0xA5F0, 0x4F11, 0xA5F1, 0x4F0F, 0xA5F2, 0x4EF2, 0xA5F3, 0x4EF6, + 0xA5F4, 0x4EFB, 0xA5F5, 0x4EF0, 0xA5F6, 0x4EF3, 0xA5F7, 0x4EFD, 0xA5F8, 0x4F01, 0xA5F9, 0x4F0B, 0xA5FA, 0x5149, 0xA5FB, 0x5147, + 0xA5FC, 0x5146, 0xA5FD, 0x5148, 0xA5FE, 0x5168, 0xA640, 0x5171, 0xA641, 0x518D, 0xA642, 0x51B0, 0xA643, 0x5217, 0xA644, 0x5211, + 0xA645, 0x5212, 0xA646, 0x520E, 0xA647, 0x5216, 0xA648, 0x52A3, 0xA649, 0x5308, 0xA64A, 0x5321, 0xA64B, 0x5320, 0xA64C, 0x5370, + 0xA64D, 0x5371, 0xA64E, 0x5409, 0xA64F, 0x540F, 0xA650, 0x540C, 0xA651, 0x540A, 0xA652, 0x5410, 0xA653, 0x5401, 0xA654, 0x540B, + 0xA655, 0x5404, 0xA656, 0x5411, 0xA657, 0x540D, 0xA658, 0x5408, 0xA659, 0x5403, 0xA65A, 0x540E, 0xA65B, 0x5406, 0xA65C, 0x5412, + 0xA65D, 0x56E0, 0xA65E, 0x56DE, 0xA65F, 0x56DD, 0xA660, 0x5733, 0xA661, 0x5730, 0xA662, 0x5728, 0xA663, 0x572D, 0xA664, 0x572C, + 0xA665, 0x572F, 0xA666, 0x5729, 0xA667, 0x5919, 0xA668, 0x591A, 0xA669, 0x5937, 0xA66A, 0x5938, 0xA66B, 0x5984, 0xA66C, 0x5978, + 0xA66D, 0x5983, 0xA66E, 0x597D, 0xA66F, 0x5979, 0xA670, 0x5982, 0xA671, 0x5981, 0xA672, 0x5B57, 0xA673, 0x5B58, 0xA674, 0x5B87, + 0xA675, 0x5B88, 0xA676, 0x5B85, 0xA677, 0x5B89, 0xA678, 0x5BFA, 0xA679, 0x5C16, 0xA67A, 0x5C79, 0xA67B, 0x5DDE, 0xA67C, 0x5E06, + 0xA67D, 0x5E76, 0xA67E, 0x5E74, 0xA6A1, 0x5F0F, 0xA6A2, 0x5F1B, 0xA6A3, 0x5FD9, 0xA6A4, 0x5FD6, 0xA6A5, 0x620E, 0xA6A6, 0x620C, + 0xA6A7, 0x620D, 0xA6A8, 0x6210, 0xA6A9, 0x6263, 0xA6AA, 0x625B, 0xA6AB, 0x6258, 0xA6AC, 0x6536, 0xA6AD, 0x65E9, 0xA6AE, 0x65E8, + 0xA6AF, 0x65EC, 0xA6B0, 0x65ED, 0xA6B1, 0x66F2, 0xA6B2, 0x66F3, 0xA6B3, 0x6709, 0xA6B4, 0x673D, 0xA6B5, 0x6734, 0xA6B6, 0x6731, + 0xA6B7, 0x6735, 0xA6B8, 0x6B21, 0xA6B9, 0x6B64, 0xA6BA, 0x6B7B, 0xA6BB, 0x6C16, 0xA6BC, 0x6C5D, 0xA6BD, 0x6C57, 0xA6BE, 0x6C59, + 0xA6BF, 0x6C5F, 0xA6C0, 0x6C60, 0xA6C1, 0x6C50, 0xA6C2, 0x6C55, 0xA6C3, 0x6C61, 0xA6C4, 0x6C5B, 0xA6C5, 0x6C4D, 0xA6C6, 0x6C4E, + 0xA6C7, 0x7070, 0xA6C8, 0x725F, 0xA6C9, 0x725D, 0xA6CA, 0x767E, 0xA6CB, 0x7AF9, 0xA6CC, 0x7C73, 0xA6CD, 0x7CF8, 0xA6CE, 0x7F36, + 0xA6CF, 0x7F8A, 0xA6D0, 0x7FBD, 0xA6D1, 0x8001, 0xA6D2, 0x8003, 0xA6D3, 0x800C, 0xA6D4, 0x8012, 0xA6D5, 0x8033, 0xA6D6, 0x807F, + 0xA6D7, 0x8089, 0xA6D8, 0x808B, 0xA6D9, 0x808C, 0xA6DA, 0x81E3, 0xA6DB, 0x81EA, 0xA6DC, 0x81F3, 0xA6DD, 0x81FC, 0xA6DE, 0x820C, + 0xA6DF, 0x821B, 0xA6E0, 0x821F, 0xA6E1, 0x826E, 0xA6E2, 0x8272, 0xA6E3, 0x827E, 0xA6E4, 0x866B, 0xA6E5, 0x8840, 0xA6E6, 0x884C, + 0xA6E7, 0x8863, 0xA6E8, 0x897F, 0xA6E9, 0x9621, 0xA6EA, 0x4E32, 0xA6EB, 0x4EA8, 0xA6EC, 0x4F4D, 0xA6ED, 0x4F4F, 0xA6EE, 0x4F47, + 0xA6EF, 0x4F57, 0xA6F0, 0x4F5E, 0xA6F1, 0x4F34, 0xA6F2, 0x4F5B, 0xA6F3, 0x4F55, 0xA6F4, 0x4F30, 0xA6F5, 0x4F50, 0xA6F6, 0x4F51, + 0xA6F7, 0x4F3D, 0xA6F8, 0x4F3A, 0xA6F9, 0x4F38, 0xA6FA, 0x4F43, 0xA6FB, 0x4F54, 0xA6FC, 0x4F3C, 0xA6FD, 0x4F46, 0xA6FE, 0x4F63, + 0xA740, 0x4F5C, 0xA741, 0x4F60, 0xA742, 0x4F2F, 0xA743, 0x4F4E, 0xA744, 0x4F36, 0xA745, 0x4F59, 0xA746, 0x4F5D, 0xA747, 0x4F48, + 0xA748, 0x4F5A, 0xA749, 0x514C, 0xA74A, 0x514B, 0xA74B, 0x514D, 0xA74C, 0x5175, 0xA74D, 0x51B6, 0xA74E, 0x51B7, 0xA74F, 0x5225, + 0xA750, 0x5224, 0xA751, 0x5229, 0xA752, 0x522A, 0xA753, 0x5228, 0xA754, 0x52AB, 0xA755, 0x52A9, 0xA756, 0x52AA, 0xA757, 0x52AC, + 0xA758, 0x5323, 0xA759, 0x5373, 0xA75A, 0x5375, 0xA75B, 0x541D, 0xA75C, 0x542D, 0xA75D, 0x541E, 0xA75E, 0x543E, 0xA75F, 0x5426, + 0xA760, 0x544E, 0xA761, 0x5427, 0xA762, 0x5446, 0xA763, 0x5443, 0xA764, 0x5433, 0xA765, 0x5448, 0xA766, 0x5442, 0xA767, 0x541B, + 0xA768, 0x5429, 0xA769, 0x544A, 0xA76A, 0x5439, 0xA76B, 0x543B, 0xA76C, 0x5438, 0xA76D, 0x542E, 0xA76E, 0x5435, 0xA76F, 0x5436, + 0xA770, 0x5420, 0xA771, 0x543C, 0xA772, 0x5440, 0xA773, 0x5431, 0xA774, 0x542B, 0xA775, 0x541F, 0xA776, 0x542C, 0xA777, 0x56EA, + 0xA778, 0x56F0, 0xA779, 0x56E4, 0xA77A, 0x56EB, 0xA77B, 0x574A, 0xA77C, 0x5751, 0xA77D, 0x5740, 0xA77E, 0x574D, 0xA7A1, 0x5747, + 0xA7A2, 0x574E, 0xA7A3, 0x573E, 0xA7A4, 0x5750, 0xA7A5, 0x574F, 0xA7A6, 0x573B, 0xA7A7, 0x58EF, 0xA7A8, 0x593E, 0xA7A9, 0x599D, + 0xA7AA, 0x5992, 0xA7AB, 0x59A8, 0xA7AC, 0x599E, 0xA7AD, 0x59A3, 0xA7AE, 0x5999, 0xA7AF, 0x5996, 0xA7B0, 0x598D, 0xA7B1, 0x59A4, + 0xA7B2, 0x5993, 0xA7B3, 0x598A, 0xA7B4, 0x59A5, 0xA7B5, 0x5B5D, 0xA7B6, 0x5B5C, 0xA7B7, 0x5B5A, 0xA7B8, 0x5B5B, 0xA7B9, 0x5B8C, + 0xA7BA, 0x5B8B, 0xA7BB, 0x5B8F, 0xA7BC, 0x5C2C, 0xA7BD, 0x5C40, 0xA7BE, 0x5C41, 0xA7BF, 0x5C3F, 0xA7C0, 0x5C3E, 0xA7C1, 0x5C90, + 0xA7C2, 0x5C91, 0xA7C3, 0x5C94, 0xA7C4, 0x5C8C, 0xA7C5, 0x5DEB, 0xA7C6, 0x5E0C, 0xA7C7, 0x5E8F, 0xA7C8, 0x5E87, 0xA7C9, 0x5E8A, + 0xA7CA, 0x5EF7, 0xA7CB, 0x5F04, 0xA7CC, 0x5F1F, 0xA7CD, 0x5F64, 0xA7CE, 0x5F62, 0xA7CF, 0x5F77, 0xA7D0, 0x5F79, 0xA7D1, 0x5FD8, + 0xA7D2, 0x5FCC, 0xA7D3, 0x5FD7, 0xA7D4, 0x5FCD, 0xA7D5, 0x5FF1, 0xA7D6, 0x5FEB, 0xA7D7, 0x5FF8, 0xA7D8, 0x5FEA, 0xA7D9, 0x6212, + 0xA7DA, 0x6211, 0xA7DB, 0x6284, 0xA7DC, 0x6297, 0xA7DD, 0x6296, 0xA7DE, 0x6280, 0xA7DF, 0x6276, 0xA7E0, 0x6289, 0xA7E1, 0x626D, + 0xA7E2, 0x628A, 0xA7E3, 0x627C, 0xA7E4, 0x627E, 0xA7E5, 0x6279, 0xA7E6, 0x6273, 0xA7E7, 0x6292, 0xA7E8, 0x626F, 0xA7E9, 0x6298, + 0xA7EA, 0x626E, 0xA7EB, 0x6295, 0xA7EC, 0x6293, 0xA7ED, 0x6291, 0xA7EE, 0x6286, 0xA7EF, 0x6539, 0xA7F0, 0x653B, 0xA7F1, 0x6538, + 0xA7F2, 0x65F1, 0xA7F3, 0x66F4, 0xA7F4, 0x675F, 0xA7F5, 0x674E, 0xA7F6, 0x674F, 0xA7F7, 0x6750, 0xA7F8, 0x6751, 0xA7F9, 0x675C, + 0xA7FA, 0x6756, 0xA7FB, 0x675E, 0xA7FC, 0x6749, 0xA7FD, 0x6746, 0xA7FE, 0x6760, 0xA840, 0x6753, 0xA841, 0x6757, 0xA842, 0x6B65, + 0xA843, 0x6BCF, 0xA844, 0x6C42, 0xA845, 0x6C5E, 0xA846, 0x6C99, 0xA847, 0x6C81, 0xA848, 0x6C88, 0xA849, 0x6C89, 0xA84A, 0x6C85, + 0xA84B, 0x6C9B, 0xA84C, 0x6C6A, 0xA84D, 0x6C7A, 0xA84E, 0x6C90, 0xA84F, 0x6C70, 0xA850, 0x6C8C, 0xA851, 0x6C68, 0xA852, 0x6C96, + 0xA853, 0x6C92, 0xA854, 0x6C7D, 0xA855, 0x6C83, 0xA856, 0x6C72, 0xA857, 0x6C7E, 0xA858, 0x6C74, 0xA859, 0x6C86, 0xA85A, 0x6C76, + 0xA85B, 0x6C8D, 0xA85C, 0x6C94, 0xA85D, 0x6C98, 0xA85E, 0x6C82, 0xA85F, 0x7076, 0xA860, 0x707C, 0xA861, 0x707D, 0xA862, 0x7078, + 0xA863, 0x7262, 0xA864, 0x7261, 0xA865, 0x7260, 0xA866, 0x72C4, 0xA867, 0x72C2, 0xA868, 0x7396, 0xA869, 0x752C, 0xA86A, 0x752B, + 0xA86B, 0x7537, 0xA86C, 0x7538, 0xA86D, 0x7682, 0xA86E, 0x76EF, 0xA86F, 0x77E3, 0xA870, 0x79C1, 0xA871, 0x79C0, 0xA872, 0x79BF, + 0xA873, 0x7A76, 0xA874, 0x7CFB, 0xA875, 0x7F55, 0xA876, 0x8096, 0xA877, 0x8093, 0xA878, 0x809D, 0xA879, 0x8098, 0xA87A, 0x809B, + 0xA87B, 0x809A, 0xA87C, 0x80B2, 0xA87D, 0x826F, 0xA87E, 0x8292, 0xA8A1, 0x828B, 0xA8A2, 0x828D, 0xA8A3, 0x898B, 0xA8A4, 0x89D2, + 0xA8A5, 0x8A00, 0xA8A6, 0x8C37, 0xA8A7, 0x8C46, 0xA8A8, 0x8C55, 0xA8A9, 0x8C9D, 0xA8AA, 0x8D64, 0xA8AB, 0x8D70, 0xA8AC, 0x8DB3, + 0xA8AD, 0x8EAB, 0xA8AE, 0x8ECA, 0xA8AF, 0x8F9B, 0xA8B0, 0x8FB0, 0xA8B1, 0x8FC2, 0xA8B2, 0x8FC6, 0xA8B3, 0x8FC5, 0xA8B4, 0x8FC4, + 0xA8B5, 0x5DE1, 0xA8B6, 0x9091, 0xA8B7, 0x90A2, 0xA8B8, 0x90AA, 0xA8B9, 0x90A6, 0xA8BA, 0x90A3, 0xA8BB, 0x9149, 0xA8BC, 0x91C6, + 0xA8BD, 0x91CC, 0xA8BE, 0x9632, 0xA8BF, 0x962E, 0xA8C0, 0x9631, 0xA8C1, 0x962A, 0xA8C2, 0x962C, 0xA8C3, 0x4E26, 0xA8C4, 0x4E56, + 0xA8C5, 0x4E73, 0xA8C6, 0x4E8B, 0xA8C7, 0x4E9B, 0xA8C8, 0x4E9E, 0xA8C9, 0x4EAB, 0xA8CA, 0x4EAC, 0xA8CB, 0x4F6F, 0xA8CC, 0x4F9D, + 0xA8CD, 0x4F8D, 0xA8CE, 0x4F73, 0xA8CF, 0x4F7F, 0xA8D0, 0x4F6C, 0xA8D1, 0x4F9B, 0xA8D2, 0x4F8B, 0xA8D3, 0x4F86, 0xA8D4, 0x4F83, + 0xA8D5, 0x4F70, 0xA8D6, 0x4F75, 0xA8D7, 0x4F88, 0xA8D8, 0x4F69, 0xA8D9, 0x4F7B, 0xA8DA, 0x4F96, 0xA8DB, 0x4F7E, 0xA8DC, 0x4F8F, + 0xA8DD, 0x4F91, 0xA8DE, 0x4F7A, 0xA8DF, 0x5154, 0xA8E0, 0x5152, 0xA8E1, 0x5155, 0xA8E2, 0x5169, 0xA8E3, 0x5177, 0xA8E4, 0x5176, + 0xA8E5, 0x5178, 0xA8E6, 0x51BD, 0xA8E7, 0x51FD, 0xA8E8, 0x523B, 0xA8E9, 0x5238, 0xA8EA, 0x5237, 0xA8EB, 0x523A, 0xA8EC, 0x5230, + 0xA8ED, 0x522E, 0xA8EE, 0x5236, 0xA8EF, 0x5241, 0xA8F0, 0x52BE, 0xA8F1, 0x52BB, 0xA8F2, 0x5352, 0xA8F3, 0x5354, 0xA8F4, 0x5353, + 0xA8F5, 0x5351, 0xA8F6, 0x5366, 0xA8F7, 0x5377, 0xA8F8, 0x5378, 0xA8F9, 0x5379, 0xA8FA, 0x53D6, 0xA8FB, 0x53D4, 0xA8FC, 0x53D7, + 0xA8FD, 0x5473, 0xA8FE, 0x5475, 0xA940, 0x5496, 0xA941, 0x5478, 0xA942, 0x5495, 0xA943, 0x5480, 0xA944, 0x547B, 0xA945, 0x5477, + 0xA946, 0x5484, 0xA947, 0x5492, 0xA948, 0x5486, 0xA949, 0x547C, 0xA94A, 0x5490, 0xA94B, 0x5471, 0xA94C, 0x5476, 0xA94D, 0x548C, + 0xA94E, 0x549A, 0xA94F, 0x5462, 0xA950, 0x5468, 0xA951, 0x548B, 0xA952, 0x547D, 0xA953, 0x548E, 0xA954, 0x56FA, 0xA955, 0x5783, + 0xA956, 0x5777, 0xA957, 0x576A, 0xA958, 0x5769, 0xA959, 0x5761, 0xA95A, 0x5766, 0xA95B, 0x5764, 0xA95C, 0x577C, 0xA95D, 0x591C, + 0xA95E, 0x5949, 0xA95F, 0x5947, 0xA960, 0x5948, 0xA961, 0x5944, 0xA962, 0x5954, 0xA963, 0x59BE, 0xA964, 0x59BB, 0xA965, 0x59D4, + 0xA966, 0x59B9, 0xA967, 0x59AE, 0xA968, 0x59D1, 0xA969, 0x59C6, 0xA96A, 0x59D0, 0xA96B, 0x59CD, 0xA96C, 0x59CB, 0xA96D, 0x59D3, + 0xA96E, 0x59CA, 0xA96F, 0x59AF, 0xA970, 0x59B3, 0xA971, 0x59D2, 0xA972, 0x59C5, 0xA973, 0x5B5F, 0xA974, 0x5B64, 0xA975, 0x5B63, + 0xA976, 0x5B97, 0xA977, 0x5B9A, 0xA978, 0x5B98, 0xA979, 0x5B9C, 0xA97A, 0x5B99, 0xA97B, 0x5B9B, 0xA97C, 0x5C1A, 0xA97D, 0x5C48, + 0xA97E, 0x5C45, 0xA9A1, 0x5C46, 0xA9A2, 0x5CB7, 0xA9A3, 0x5CA1, 0xA9A4, 0x5CB8, 0xA9A5, 0x5CA9, 0xA9A6, 0x5CAB, 0xA9A7, 0x5CB1, + 0xA9A8, 0x5CB3, 0xA9A9, 0x5E18, 0xA9AA, 0x5E1A, 0xA9AB, 0x5E16, 0xA9AC, 0x5E15, 0xA9AD, 0x5E1B, 0xA9AE, 0x5E11, 0xA9AF, 0x5E78, + 0xA9B0, 0x5E9A, 0xA9B1, 0x5E97, 0xA9B2, 0x5E9C, 0xA9B3, 0x5E95, 0xA9B4, 0x5E96, 0xA9B5, 0x5EF6, 0xA9B6, 0x5F26, 0xA9B7, 0x5F27, + 0xA9B8, 0x5F29, 0xA9B9, 0x5F80, 0xA9BA, 0x5F81, 0xA9BB, 0x5F7F, 0xA9BC, 0x5F7C, 0xA9BD, 0x5FDD, 0xA9BE, 0x5FE0, 0xA9BF, 0x5FFD, + 0xA9C0, 0x5FF5, 0xA9C1, 0x5FFF, 0xA9C2, 0x600F, 0xA9C3, 0x6014, 0xA9C4, 0x602F, 0xA9C5, 0x6035, 0xA9C6, 0x6016, 0xA9C7, 0x602A, + 0xA9C8, 0x6015, 0xA9C9, 0x6021, 0xA9CA, 0x6027, 0xA9CB, 0x6029, 0xA9CC, 0x602B, 0xA9CD, 0x601B, 0xA9CE, 0x6216, 0xA9CF, 0x6215, + 0xA9D0, 0x623F, 0xA9D1, 0x623E, 0xA9D2, 0x6240, 0xA9D3, 0x627F, 0xA9D4, 0x62C9, 0xA9D5, 0x62CC, 0xA9D6, 0x62C4, 0xA9D7, 0x62BF, + 0xA9D8, 0x62C2, 0xA9D9, 0x62B9, 0xA9DA, 0x62D2, 0xA9DB, 0x62DB, 0xA9DC, 0x62AB, 0xA9DD, 0x62D3, 0xA9DE, 0x62D4, 0xA9DF, 0x62CB, + 0xA9E0, 0x62C8, 0xA9E1, 0x62A8, 0xA9E2, 0x62BD, 0xA9E3, 0x62BC, 0xA9E4, 0x62D0, 0xA9E5, 0x62D9, 0xA9E6, 0x62C7, 0xA9E7, 0x62CD, + 0xA9E8, 0x62B5, 0xA9E9, 0x62DA, 0xA9EA, 0x62B1, 0xA9EB, 0x62D8, 0xA9EC, 0x62D6, 0xA9ED, 0x62D7, 0xA9EE, 0x62C6, 0xA9EF, 0x62AC, + 0xA9F0, 0x62CE, 0xA9F1, 0x653E, 0xA9F2, 0x65A7, 0xA9F3, 0x65BC, 0xA9F4, 0x65FA, 0xA9F5, 0x6614, 0xA9F6, 0x6613, 0xA9F7, 0x660C, + 0xA9F8, 0x6606, 0xA9F9, 0x6602, 0xA9FA, 0x660E, 0xA9FB, 0x6600, 0xA9FC, 0x660F, 0xA9FD, 0x6615, 0xA9FE, 0x660A, 0xAA40, 0x6607, + 0xAA41, 0x670D, 0xAA42, 0x670B, 0xAA43, 0x676D, 0xAA44, 0x678B, 0xAA45, 0x6795, 0xAA46, 0x6771, 0xAA47, 0x679C, 0xAA48, 0x6773, + 0xAA49, 0x6777, 0xAA4A, 0x6787, 0xAA4B, 0x679D, 0xAA4C, 0x6797, 0xAA4D, 0x676F, 0xAA4E, 0x6770, 0xAA4F, 0x677F, 0xAA50, 0x6789, + 0xAA51, 0x677E, 0xAA52, 0x6790, 0xAA53, 0x6775, 0xAA54, 0x679A, 0xAA55, 0x6793, 0xAA56, 0x677C, 0xAA57, 0x676A, 0xAA58, 0x6772, + 0xAA59, 0x6B23, 0xAA5A, 0x6B66, 0xAA5B, 0x6B67, 0xAA5C, 0x6B7F, 0xAA5D, 0x6C13, 0xAA5E, 0x6C1B, 0xAA5F, 0x6CE3, 0xAA60, 0x6CE8, + 0xAA61, 0x6CF3, 0xAA62, 0x6CB1, 0xAA63, 0x6CCC, 0xAA64, 0x6CE5, 0xAA65, 0x6CB3, 0xAA66, 0x6CBD, 0xAA67, 0x6CBE, 0xAA68, 0x6CBC, + 0xAA69, 0x6CE2, 0xAA6A, 0x6CAB, 0xAA6B, 0x6CD5, 0xAA6C, 0x6CD3, 0xAA6D, 0x6CB8, 0xAA6E, 0x6CC4, 0xAA6F, 0x6CB9, 0xAA70, 0x6CC1, + 0xAA71, 0x6CAE, 0xAA72, 0x6CD7, 0xAA73, 0x6CC5, 0xAA74, 0x6CF1, 0xAA75, 0x6CBF, 0xAA76, 0x6CBB, 0xAA77, 0x6CE1, 0xAA78, 0x6CDB, + 0xAA79, 0x6CCA, 0xAA7A, 0x6CAC, 0xAA7B, 0x6CEF, 0xAA7C, 0x6CDC, 0xAA7D, 0x6CD6, 0xAA7E, 0x6CE0, 0xAAA1, 0x7095, 0xAAA2, 0x708E, + 0xAAA3, 0x7092, 0xAAA4, 0x708A, 0xAAA5, 0x7099, 0xAAA6, 0x722C, 0xAAA7, 0x722D, 0xAAA8, 0x7238, 0xAAA9, 0x7248, 0xAAAA, 0x7267, + 0xAAAB, 0x7269, 0xAAAC, 0x72C0, 0xAAAD, 0x72CE, 0xAAAE, 0x72D9, 0xAAAF, 0x72D7, 0xAAB0, 0x72D0, 0xAAB1, 0x73A9, 0xAAB2, 0x73A8, + 0xAAB3, 0x739F, 0xAAB4, 0x73AB, 0xAAB5, 0x73A5, 0xAAB6, 0x753D, 0xAAB7, 0x759D, 0xAAB8, 0x7599, 0xAAB9, 0x759A, 0xAABA, 0x7684, + 0xAABB, 0x76C2, 0xAABC, 0x76F2, 0xAABD, 0x76F4, 0xAABE, 0x77E5, 0xAABF, 0x77FD, 0xAAC0, 0x793E, 0xAAC1, 0x7940, 0xAAC2, 0x7941, + 0xAAC3, 0x79C9, 0xAAC4, 0x79C8, 0xAAC5, 0x7A7A, 0xAAC6, 0x7A79, 0xAAC7, 0x7AFA, 0xAAC8, 0x7CFE, 0xAAC9, 0x7F54, 0xAACA, 0x7F8C, + 0xAACB, 0x7F8B, 0xAACC, 0x8005, 0xAACD, 0x80BA, 0xAACE, 0x80A5, 0xAACF, 0x80A2, 0xAAD0, 0x80B1, 0xAAD1, 0x80A1, 0xAAD2, 0x80AB, + 0xAAD3, 0x80A9, 0xAAD4, 0x80B4, 0xAAD5, 0x80AA, 0xAAD6, 0x80AF, 0xAAD7, 0x81E5, 0xAAD8, 0x81FE, 0xAAD9, 0x820D, 0xAADA, 0x82B3, + 0xAADB, 0x829D, 0xAADC, 0x8299, 0xAADD, 0x82AD, 0xAADE, 0x82BD, 0xAADF, 0x829F, 0xAAE0, 0x82B9, 0xAAE1, 0x82B1, 0xAAE2, 0x82AC, + 0xAAE3, 0x82A5, 0xAAE4, 0x82AF, 0xAAE5, 0x82B8, 0xAAE6, 0x82A3, 0xAAE7, 0x82B0, 0xAAE8, 0x82BE, 0xAAE9, 0x82B7, 0xAAEA, 0x864E, + 0xAAEB, 0x8671, 0xAAEC, 0x521D, 0xAAED, 0x8868, 0xAAEE, 0x8ECB, 0xAAEF, 0x8FCE, 0xAAF0, 0x8FD4, 0xAAF1, 0x8FD1, 0xAAF2, 0x90B5, + 0xAAF3, 0x90B8, 0xAAF4, 0x90B1, 0xAAF5, 0x90B6, 0xAAF6, 0x91C7, 0xAAF7, 0x91D1, 0xAAF8, 0x9577, 0xAAF9, 0x9580, 0xAAFA, 0x961C, + 0xAAFB, 0x9640, 0xAAFC, 0x963F, 0xAAFD, 0x963B, 0xAAFE, 0x9644, 0xAB40, 0x9642, 0xAB41, 0x96B9, 0xAB42, 0x96E8, 0xAB43, 0x9752, + 0xAB44, 0x975E, 0xAB45, 0x4E9F, 0xAB46, 0x4EAD, 0xAB47, 0x4EAE, 0xAB48, 0x4FE1, 0xAB49, 0x4FB5, 0xAB4A, 0x4FAF, 0xAB4B, 0x4FBF, + 0xAB4C, 0x4FE0, 0xAB4D, 0x4FD1, 0xAB4E, 0x4FCF, 0xAB4F, 0x4FDD, 0xAB50, 0x4FC3, 0xAB51, 0x4FB6, 0xAB52, 0x4FD8, 0xAB53, 0x4FDF, + 0xAB54, 0x4FCA, 0xAB55, 0x4FD7, 0xAB56, 0x4FAE, 0xAB57, 0x4FD0, 0xAB58, 0x4FC4, 0xAB59, 0x4FC2, 0xAB5A, 0x4FDA, 0xAB5B, 0x4FCE, + 0xAB5C, 0x4FDE, 0xAB5D, 0x4FB7, 0xAB5E, 0x5157, 0xAB5F, 0x5192, 0xAB60, 0x5191, 0xAB61, 0x51A0, 0xAB62, 0x524E, 0xAB63, 0x5243, + 0xAB64, 0x524A, 0xAB65, 0x524D, 0xAB66, 0x524C, 0xAB67, 0x524B, 0xAB68, 0x5247, 0xAB69, 0x52C7, 0xAB6A, 0x52C9, 0xAB6B, 0x52C3, + 0xAB6C, 0x52C1, 0xAB6D, 0x530D, 0xAB6E, 0x5357, 0xAB6F, 0x537B, 0xAB70, 0x539A, 0xAB71, 0x53DB, 0xAB72, 0x54AC, 0xAB73, 0x54C0, + 0xAB74, 0x54A8, 0xAB75, 0x54CE, 0xAB76, 0x54C9, 0xAB77, 0x54B8, 0xAB78, 0x54A6, 0xAB79, 0x54B3, 0xAB7A, 0x54C7, 0xAB7B, 0x54C2, + 0xAB7C, 0x54BD, 0xAB7D, 0x54AA, 0xAB7E, 0x54C1, 0xABA1, 0x54C4, 0xABA2, 0x54C8, 0xABA3, 0x54AF, 0xABA4, 0x54AB, 0xABA5, 0x54B1, + 0xABA6, 0x54BB, 0xABA7, 0x54A9, 0xABA8, 0x54A7, 0xABA9, 0x54BF, 0xABAA, 0x56FF, 0xABAB, 0x5782, 0xABAC, 0x578B, 0xABAD, 0x57A0, + 0xABAE, 0x57A3, 0xABAF, 0x57A2, 0xABB0, 0x57CE, 0xABB1, 0x57AE, 0xABB2, 0x5793, 0xABB3, 0x5955, 0xABB4, 0x5951, 0xABB5, 0x594F, + 0xABB6, 0x594E, 0xABB7, 0x5950, 0xABB8, 0x59DC, 0xABB9, 0x59D8, 0xABBA, 0x59FF, 0xABBB, 0x59E3, 0xABBC, 0x59E8, 0xABBD, 0x5A03, + 0xABBE, 0x59E5, 0xABBF, 0x59EA, 0xABC0, 0x59DA, 0xABC1, 0x59E6, 0xABC2, 0x5A01, 0xABC3, 0x59FB, 0xABC4, 0x5B69, 0xABC5, 0x5BA3, + 0xABC6, 0x5BA6, 0xABC7, 0x5BA4, 0xABC8, 0x5BA2, 0xABC9, 0x5BA5, 0xABCA, 0x5C01, 0xABCB, 0x5C4E, 0xABCC, 0x5C4F, 0xABCD, 0x5C4D, + 0xABCE, 0x5C4B, 0xABCF, 0x5CD9, 0xABD0, 0x5CD2, 0xABD1, 0x5DF7, 0xABD2, 0x5E1D, 0xABD3, 0x5E25, 0xABD4, 0x5E1F, 0xABD5, 0x5E7D, + 0xABD6, 0x5EA0, 0xABD7, 0x5EA6, 0xABD8, 0x5EFA, 0xABD9, 0x5F08, 0xABDA, 0x5F2D, 0xABDB, 0x5F65, 0xABDC, 0x5F88, 0xABDD, 0x5F85, + 0xABDE, 0x5F8A, 0xABDF, 0x5F8B, 0xABE0, 0x5F87, 0xABE1, 0x5F8C, 0xABE2, 0x5F89, 0xABE3, 0x6012, 0xABE4, 0x601D, 0xABE5, 0x6020, + 0xABE6, 0x6025, 0xABE7, 0x600E, 0xABE8, 0x6028, 0xABE9, 0x604D, 0xABEA, 0x6070, 0xABEB, 0x6068, 0xABEC, 0x6062, 0xABED, 0x6046, + 0xABEE, 0x6043, 0xABEF, 0x606C, 0xABF0, 0x606B, 0xABF1, 0x606A, 0xABF2, 0x6064, 0xABF3, 0x6241, 0xABF4, 0x62DC, 0xABF5, 0x6316, + 0xABF6, 0x6309, 0xABF7, 0x62FC, 0xABF8, 0x62ED, 0xABF9, 0x6301, 0xABFA, 0x62EE, 0xABFB, 0x62FD, 0xABFC, 0x6307, 0xABFD, 0x62F1, + 0xABFE, 0x62F7, 0xAC40, 0x62EF, 0xAC41, 0x62EC, 0xAC42, 0x62FE, 0xAC43, 0x62F4, 0xAC44, 0x6311, 0xAC45, 0x6302, 0xAC46, 0x653F, + 0xAC47, 0x6545, 0xAC48, 0x65AB, 0xAC49, 0x65BD, 0xAC4A, 0x65E2, 0xAC4B, 0x6625, 0xAC4C, 0x662D, 0xAC4D, 0x6620, 0xAC4E, 0x6627, + 0xAC4F, 0x662F, 0xAC50, 0x661F, 0xAC51, 0x6628, 0xAC52, 0x6631, 0xAC53, 0x6624, 0xAC54, 0x66F7, 0xAC55, 0x67FF, 0xAC56, 0x67D3, + 0xAC57, 0x67F1, 0xAC58, 0x67D4, 0xAC59, 0x67D0, 0xAC5A, 0x67EC, 0xAC5B, 0x67B6, 0xAC5C, 0x67AF, 0xAC5D, 0x67F5, 0xAC5E, 0x67E9, + 0xAC5F, 0x67EF, 0xAC60, 0x67C4, 0xAC61, 0x67D1, 0xAC62, 0x67B4, 0xAC63, 0x67DA, 0xAC64, 0x67E5, 0xAC65, 0x67B8, 0xAC66, 0x67CF, + 0xAC67, 0x67DE, 0xAC68, 0x67F3, 0xAC69, 0x67B0, 0xAC6A, 0x67D9, 0xAC6B, 0x67E2, 0xAC6C, 0x67DD, 0xAC6D, 0x67D2, 0xAC6E, 0x6B6A, + 0xAC6F, 0x6B83, 0xAC70, 0x6B86, 0xAC71, 0x6BB5, 0xAC72, 0x6BD2, 0xAC73, 0x6BD7, 0xAC74, 0x6C1F, 0xAC75, 0x6CC9, 0xAC76, 0x6D0B, + 0xAC77, 0x6D32, 0xAC78, 0x6D2A, 0xAC79, 0x6D41, 0xAC7A, 0x6D25, 0xAC7B, 0x6D0C, 0xAC7C, 0x6D31, 0xAC7D, 0x6D1E, 0xAC7E, 0x6D17, + 0xACA1, 0x6D3B, 0xACA2, 0x6D3D, 0xACA3, 0x6D3E, 0xACA4, 0x6D36, 0xACA5, 0x6D1B, 0xACA6, 0x6CF5, 0xACA7, 0x6D39, 0xACA8, 0x6D27, + 0xACA9, 0x6D38, 0xACAA, 0x6D29, 0xACAB, 0x6D2E, 0xACAC, 0x6D35, 0xACAD, 0x6D0E, 0xACAE, 0x6D2B, 0xACAF, 0x70AB, 0xACB0, 0x70BA, + 0xACB1, 0x70B3, 0xACB2, 0x70AC, 0xACB3, 0x70AF, 0xACB4, 0x70AD, 0xACB5, 0x70B8, 0xACB6, 0x70AE, 0xACB7, 0x70A4, 0xACB8, 0x7230, + 0xACB9, 0x7272, 0xACBA, 0x726F, 0xACBB, 0x7274, 0xACBC, 0x72E9, 0xACBD, 0x72E0, 0xACBE, 0x72E1, 0xACBF, 0x73B7, 0xACC0, 0x73CA, + 0xACC1, 0x73BB, 0xACC2, 0x73B2, 0xACC3, 0x73CD, 0xACC4, 0x73C0, 0xACC5, 0x73B3, 0xACC6, 0x751A, 0xACC7, 0x752D, 0xACC8, 0x754F, + 0xACC9, 0x754C, 0xACCA, 0x754E, 0xACCB, 0x754B, 0xACCC, 0x75AB, 0xACCD, 0x75A4, 0xACCE, 0x75A5, 0xACCF, 0x75A2, 0xACD0, 0x75A3, + 0xACD1, 0x7678, 0xACD2, 0x7686, 0xACD3, 0x7687, 0xACD4, 0x7688, 0xACD5, 0x76C8, 0xACD6, 0x76C6, 0xACD7, 0x76C3, 0xACD8, 0x76C5, + 0xACD9, 0x7701, 0xACDA, 0x76F9, 0xACDB, 0x76F8, 0xACDC, 0x7709, 0xACDD, 0x770B, 0xACDE, 0x76FE, 0xACDF, 0x76FC, 0xACE0, 0x7707, + 0xACE1, 0x77DC, 0xACE2, 0x7802, 0xACE3, 0x7814, 0xACE4, 0x780C, 0xACE5, 0x780D, 0xACE6, 0x7946, 0xACE7, 0x7949, 0xACE8, 0x7948, + 0xACE9, 0x7947, 0xACEA, 0x79B9, 0xACEB, 0x79BA, 0xACEC, 0x79D1, 0xACED, 0x79D2, 0xACEE, 0x79CB, 0xACEF, 0x7A7F, 0xACF0, 0x7A81, + 0xACF1, 0x7AFF, 0xACF2, 0x7AFD, 0xACF3, 0x7C7D, 0xACF4, 0x7D02, 0xACF5, 0x7D05, 0xACF6, 0x7D00, 0xACF7, 0x7D09, 0xACF8, 0x7D07, + 0xACF9, 0x7D04, 0xACFA, 0x7D06, 0xACFB, 0x7F38, 0xACFC, 0x7F8E, 0xACFD, 0x7FBF, 0xACFE, 0x8004, 0xAD40, 0x8010, 0xAD41, 0x800D, + 0xAD42, 0x8011, 0xAD43, 0x8036, 0xAD44, 0x80D6, 0xAD45, 0x80E5, 0xAD46, 0x80DA, 0xAD47, 0x80C3, 0xAD48, 0x80C4, 0xAD49, 0x80CC, + 0xAD4A, 0x80E1, 0xAD4B, 0x80DB, 0xAD4C, 0x80CE, 0xAD4D, 0x80DE, 0xAD4E, 0x80E4, 0xAD4F, 0x80DD, 0xAD50, 0x81F4, 0xAD51, 0x8222, + 0xAD52, 0x82E7, 0xAD53, 0x8303, 0xAD54, 0x8305, 0xAD55, 0x82E3, 0xAD56, 0x82DB, 0xAD57, 0x82E6, 0xAD58, 0x8304, 0xAD59, 0x82E5, + 0xAD5A, 0x8302, 0xAD5B, 0x8309, 0xAD5C, 0x82D2, 0xAD5D, 0x82D7, 0xAD5E, 0x82F1, 0xAD5F, 0x8301, 0xAD60, 0x82DC, 0xAD61, 0x82D4, + 0xAD62, 0x82D1, 0xAD63, 0x82DE, 0xAD64, 0x82D3, 0xAD65, 0x82DF, 0xAD66, 0x82EF, 0xAD67, 0x8306, 0xAD68, 0x8650, 0xAD69, 0x8679, + 0xAD6A, 0x867B, 0xAD6B, 0x867A, 0xAD6C, 0x884D, 0xAD6D, 0x886B, 0xAD6E, 0x8981, 0xAD6F, 0x89D4, 0xAD70, 0x8A08, 0xAD71, 0x8A02, + 0xAD72, 0x8A03, 0xAD73, 0x8C9E, 0xAD74, 0x8CA0, 0xAD75, 0x8D74, 0xAD76, 0x8D73, 0xAD77, 0x8DB4, 0xAD78, 0x8ECD, 0xAD79, 0x8ECC, + 0xAD7A, 0x8FF0, 0xAD7B, 0x8FE6, 0xAD7C, 0x8FE2, 0xAD7D, 0x8FEA, 0xAD7E, 0x8FE5, 0xADA1, 0x8FED, 0xADA2, 0x8FEB, 0xADA3, 0x8FE4, + 0xADA4, 0x8FE8, 0xADA5, 0x90CA, 0xADA6, 0x90CE, 0xADA7, 0x90C1, 0xADA8, 0x90C3, 0xADA9, 0x914B, 0xADAA, 0x914A, 0xADAB, 0x91CD, + 0xADAC, 0x9582, 0xADAD, 0x9650, 0xADAE, 0x964B, 0xADAF, 0x964C, 0xADB0, 0x964D, 0xADB1, 0x9762, 0xADB2, 0x9769, 0xADB3, 0x97CB, + 0xADB4, 0x97ED, 0xADB5, 0x97F3, 0xADB6, 0x9801, 0xADB7, 0x98A8, 0xADB8, 0x98DB, 0xADB9, 0x98DF, 0xADBA, 0x9996, 0xADBB, 0x9999, + 0xADBC, 0x4E58, 0xADBD, 0x4EB3, 0xADBE, 0x500C, 0xADBF, 0x500D, 0xADC0, 0x5023, 0xADC1, 0x4FEF, 0xADC2, 0x5026, 0xADC3, 0x5025, + 0xADC4, 0x4FF8, 0xADC5, 0x5029, 0xADC6, 0x5016, 0xADC7, 0x5006, 0xADC8, 0x503C, 0xADC9, 0x501F, 0xADCA, 0x501A, 0xADCB, 0x5012, + 0xADCC, 0x5011, 0xADCD, 0x4FFA, 0xADCE, 0x5000, 0xADCF, 0x5014, 0xADD0, 0x5028, 0xADD1, 0x4FF1, 0xADD2, 0x5021, 0xADD3, 0x500B, + 0xADD4, 0x5019, 0xADD5, 0x5018, 0xADD6, 0x4FF3, 0xADD7, 0x4FEE, 0xADD8, 0x502D, 0xADD9, 0x502A, 0xADDA, 0x4FFE, 0xADDB, 0x502B, + 0xADDC, 0x5009, 0xADDD, 0x517C, 0xADDE, 0x51A4, 0xADDF, 0x51A5, 0xADE0, 0x51A2, 0xADE1, 0x51CD, 0xADE2, 0x51CC, 0xADE3, 0x51C6, + 0xADE4, 0x51CB, 0xADE5, 0x5256, 0xADE6, 0x525C, 0xADE7, 0x5254, 0xADE8, 0x525B, 0xADE9, 0x525D, 0xADEA, 0x532A, 0xADEB, 0x537F, + 0xADEC, 0x539F, 0xADED, 0x539D, 0xADEE, 0x53DF, 0xADEF, 0x54E8, 0xADF0, 0x5510, 0xADF1, 0x5501, 0xADF2, 0x5537, 0xADF3, 0x54FC, + 0xADF4, 0x54E5, 0xADF5, 0x54F2, 0xADF6, 0x5506, 0xADF7, 0x54FA, 0xADF8, 0x5514, 0xADF9, 0x54E9, 0xADFA, 0x54ED, 0xADFB, 0x54E1, + 0xADFC, 0x5509, 0xADFD, 0x54EE, 0xADFE, 0x54EA, 0xAE40, 0x54E6, 0xAE41, 0x5527, 0xAE42, 0x5507, 0xAE43, 0x54FD, 0xAE44, 0x550F, + 0xAE45, 0x5703, 0xAE46, 0x5704, 0xAE47, 0x57C2, 0xAE48, 0x57D4, 0xAE49, 0x57CB, 0xAE4A, 0x57C3, 0xAE4B, 0x5809, 0xAE4C, 0x590F, + 0xAE4D, 0x5957, 0xAE4E, 0x5958, 0xAE4F, 0x595A, 0xAE50, 0x5A11, 0xAE51, 0x5A18, 0xAE52, 0x5A1C, 0xAE53, 0x5A1F, 0xAE54, 0x5A1B, + 0xAE55, 0x5A13, 0xAE56, 0x59EC, 0xAE57, 0x5A20, 0xAE58, 0x5A23, 0xAE59, 0x5A29, 0xAE5A, 0x5A25, 0xAE5B, 0x5A0C, 0xAE5C, 0x5A09, + 0xAE5D, 0x5B6B, 0xAE5E, 0x5C58, 0xAE5F, 0x5BB0, 0xAE60, 0x5BB3, 0xAE61, 0x5BB6, 0xAE62, 0x5BB4, 0xAE63, 0x5BAE, 0xAE64, 0x5BB5, + 0xAE65, 0x5BB9, 0xAE66, 0x5BB8, 0xAE67, 0x5C04, 0xAE68, 0x5C51, 0xAE69, 0x5C55, 0xAE6A, 0x5C50, 0xAE6B, 0x5CED, 0xAE6C, 0x5CFD, + 0xAE6D, 0x5CFB, 0xAE6E, 0x5CEA, 0xAE6F, 0x5CE8, 0xAE70, 0x5CF0, 0xAE71, 0x5CF6, 0xAE72, 0x5D01, 0xAE73, 0x5CF4, 0xAE74, 0x5DEE, + 0xAE75, 0x5E2D, 0xAE76, 0x5E2B, 0xAE77, 0x5EAB, 0xAE78, 0x5EAD, 0xAE79, 0x5EA7, 0xAE7A, 0x5F31, 0xAE7B, 0x5F92, 0xAE7C, 0x5F91, + 0xAE7D, 0x5F90, 0xAE7E, 0x6059, 0xAEA1, 0x6063, 0xAEA2, 0x6065, 0xAEA3, 0x6050, 0xAEA4, 0x6055, 0xAEA5, 0x606D, 0xAEA6, 0x6069, + 0xAEA7, 0x606F, 0xAEA8, 0x6084, 0xAEA9, 0x609F, 0xAEAA, 0x609A, 0xAEAB, 0x608D, 0xAEAC, 0x6094, 0xAEAD, 0x608C, 0xAEAE, 0x6085, + 0xAEAF, 0x6096, 0xAEB0, 0x6247, 0xAEB1, 0x62F3, 0xAEB2, 0x6308, 0xAEB3, 0x62FF, 0xAEB4, 0x634E, 0xAEB5, 0x633E, 0xAEB6, 0x632F, + 0xAEB7, 0x6355, 0xAEB8, 0x6342, 0xAEB9, 0x6346, 0xAEBA, 0x634F, 0xAEBB, 0x6349, 0xAEBC, 0x633A, 0xAEBD, 0x6350, 0xAEBE, 0x633D, + 0xAEBF, 0x632A, 0xAEC0, 0x632B, 0xAEC1, 0x6328, 0xAEC2, 0x634D, 0xAEC3, 0x634C, 0xAEC4, 0x6548, 0xAEC5, 0x6549, 0xAEC6, 0x6599, + 0xAEC7, 0x65C1, 0xAEC8, 0x65C5, 0xAEC9, 0x6642, 0xAECA, 0x6649, 0xAECB, 0x664F, 0xAECC, 0x6643, 0xAECD, 0x6652, 0xAECE, 0x664C, + 0xAECF, 0x6645, 0xAED0, 0x6641, 0xAED1, 0x66F8, 0xAED2, 0x6714, 0xAED3, 0x6715, 0xAED4, 0x6717, 0xAED5, 0x6821, 0xAED6, 0x6838, + 0xAED7, 0x6848, 0xAED8, 0x6846, 0xAED9, 0x6853, 0xAEDA, 0x6839, 0xAEDB, 0x6842, 0xAEDC, 0x6854, 0xAEDD, 0x6829, 0xAEDE, 0x68B3, + 0xAEDF, 0x6817, 0xAEE0, 0x684C, 0xAEE1, 0x6851, 0xAEE2, 0x683D, 0xAEE3, 0x67F4, 0xAEE4, 0x6850, 0xAEE5, 0x6840, 0xAEE6, 0x683C, + 0xAEE7, 0x6843, 0xAEE8, 0x682A, 0xAEE9, 0x6845, 0xAEEA, 0x6813, 0xAEEB, 0x6818, 0xAEEC, 0x6841, 0xAEED, 0x6B8A, 0xAEEE, 0x6B89, + 0xAEEF, 0x6BB7, 0xAEF0, 0x6C23, 0xAEF1, 0x6C27, 0xAEF2, 0x6C28, 0xAEF3, 0x6C26, 0xAEF4, 0x6C24, 0xAEF5, 0x6CF0, 0xAEF6, 0x6D6A, + 0xAEF7, 0x6D95, 0xAEF8, 0x6D88, 0xAEF9, 0x6D87, 0xAEFA, 0x6D66, 0xAEFB, 0x6D78, 0xAEFC, 0x6D77, 0xAEFD, 0x6D59, 0xAEFE, 0x6D93, + 0xAF40, 0x6D6C, 0xAF41, 0x6D89, 0xAF42, 0x6D6E, 0xAF43, 0x6D5A, 0xAF44, 0x6D74, 0xAF45, 0x6D69, 0xAF46, 0x6D8C, 0xAF47, 0x6D8A, + 0xAF48, 0x6D79, 0xAF49, 0x6D85, 0xAF4A, 0x6D65, 0xAF4B, 0x6D94, 0xAF4C, 0x70CA, 0xAF4D, 0x70D8, 0xAF4E, 0x70E4, 0xAF4F, 0x70D9, + 0xAF50, 0x70C8, 0xAF51, 0x70CF, 0xAF52, 0x7239, 0xAF53, 0x7279, 0xAF54, 0x72FC, 0xAF55, 0x72F9, 0xAF56, 0x72FD, 0xAF57, 0x72F8, + 0xAF58, 0x72F7, 0xAF59, 0x7386, 0xAF5A, 0x73ED, 0xAF5B, 0x7409, 0xAF5C, 0x73EE, 0xAF5D, 0x73E0, 0xAF5E, 0x73EA, 0xAF5F, 0x73DE, + 0xAF60, 0x7554, 0xAF61, 0x755D, 0xAF62, 0x755C, 0xAF63, 0x755A, 0xAF64, 0x7559, 0xAF65, 0x75BE, 0xAF66, 0x75C5, 0xAF67, 0x75C7, + 0xAF68, 0x75B2, 0xAF69, 0x75B3, 0xAF6A, 0x75BD, 0xAF6B, 0x75BC, 0xAF6C, 0x75B9, 0xAF6D, 0x75C2, 0xAF6E, 0x75B8, 0xAF6F, 0x768B, + 0xAF70, 0x76B0, 0xAF71, 0x76CA, 0xAF72, 0x76CD, 0xAF73, 0x76CE, 0xAF74, 0x7729, 0xAF75, 0x771F, 0xAF76, 0x7720, 0xAF77, 0x7728, + 0xAF78, 0x77E9, 0xAF79, 0x7830, 0xAF7A, 0x7827, 0xAF7B, 0x7838, 0xAF7C, 0x781D, 0xAF7D, 0x7834, 0xAF7E, 0x7837, 0xAFA1, 0x7825, + 0xAFA2, 0x782D, 0xAFA3, 0x7820, 0xAFA4, 0x781F, 0xAFA5, 0x7832, 0xAFA6, 0x7955, 0xAFA7, 0x7950, 0xAFA8, 0x7960, 0xAFA9, 0x795F, + 0xAFAA, 0x7956, 0xAFAB, 0x795E, 0xAFAC, 0x795D, 0xAFAD, 0x7957, 0xAFAE, 0x795A, 0xAFAF, 0x79E4, 0xAFB0, 0x79E3, 0xAFB1, 0x79E7, + 0xAFB2, 0x79DF, 0xAFB3, 0x79E6, 0xAFB4, 0x79E9, 0xAFB5, 0x79D8, 0xAFB6, 0x7A84, 0xAFB7, 0x7A88, 0xAFB8, 0x7AD9, 0xAFB9, 0x7B06, + 0xAFBA, 0x7B11, 0xAFBB, 0x7C89, 0xAFBC, 0x7D21, 0xAFBD, 0x7D17, 0xAFBE, 0x7D0B, 0xAFBF, 0x7D0A, 0xAFC0, 0x7D20, 0xAFC1, 0x7D22, + 0xAFC2, 0x7D14, 0xAFC3, 0x7D10, 0xAFC4, 0x7D15, 0xAFC5, 0x7D1A, 0xAFC6, 0x7D1C, 0xAFC7, 0x7D0D, 0xAFC8, 0x7D19, 0xAFC9, 0x7D1B, + 0xAFCA, 0x7F3A, 0xAFCB, 0x7F5F, 0xAFCC, 0x7F94, 0xAFCD, 0x7FC5, 0xAFCE, 0x7FC1, 0xAFCF, 0x8006, 0xAFD0, 0x8018, 0xAFD1, 0x8015, + 0xAFD2, 0x8019, 0xAFD3, 0x8017, 0xAFD4, 0x803D, 0xAFD5, 0x803F, 0xAFD6, 0x80F1, 0xAFD7, 0x8102, 0xAFD8, 0x80F0, 0xAFD9, 0x8105, + 0xAFDA, 0x80ED, 0xAFDB, 0x80F4, 0xAFDC, 0x8106, 0xAFDD, 0x80F8, 0xAFDE, 0x80F3, 0xAFDF, 0x8108, 0xAFE0, 0x80FD, 0xAFE1, 0x810A, + 0xAFE2, 0x80FC, 0xAFE3, 0x80EF, 0xAFE4, 0x81ED, 0xAFE5, 0x81EC, 0xAFE6, 0x8200, 0xAFE7, 0x8210, 0xAFE8, 0x822A, 0xAFE9, 0x822B, + 0xAFEA, 0x8228, 0xAFEB, 0x822C, 0xAFEC, 0x82BB, 0xAFED, 0x832B, 0xAFEE, 0x8352, 0xAFEF, 0x8354, 0xAFF0, 0x834A, 0xAFF1, 0x8338, + 0xAFF2, 0x8350, 0xAFF3, 0x8349, 0xAFF4, 0x8335, 0xAFF5, 0x8334, 0xAFF6, 0x834F, 0xAFF7, 0x8332, 0xAFF8, 0x8339, 0xAFF9, 0x8336, + 0xAFFA, 0x8317, 0xAFFB, 0x8340, 0xAFFC, 0x8331, 0xAFFD, 0x8328, 0xAFFE, 0x8343, 0xB040, 0x8654, 0xB041, 0x868A, 0xB042, 0x86AA, + 0xB043, 0x8693, 0xB044, 0x86A4, 0xB045, 0x86A9, 0xB046, 0x868C, 0xB047, 0x86A3, 0xB048, 0x869C, 0xB049, 0x8870, 0xB04A, 0x8877, + 0xB04B, 0x8881, 0xB04C, 0x8882, 0xB04D, 0x887D, 0xB04E, 0x8879, 0xB04F, 0x8A18, 0xB050, 0x8A10, 0xB051, 0x8A0E, 0xB052, 0x8A0C, + 0xB053, 0x8A15, 0xB054, 0x8A0A, 0xB055, 0x8A17, 0xB056, 0x8A13, 0xB057, 0x8A16, 0xB058, 0x8A0F, 0xB059, 0x8A11, 0xB05A, 0x8C48, + 0xB05B, 0x8C7A, 0xB05C, 0x8C79, 0xB05D, 0x8CA1, 0xB05E, 0x8CA2, 0xB05F, 0x8D77, 0xB060, 0x8EAC, 0xB061, 0x8ED2, 0xB062, 0x8ED4, + 0xB063, 0x8ECF, 0xB064, 0x8FB1, 0xB065, 0x9001, 0xB066, 0x9006, 0xB067, 0x8FF7, 0xB068, 0x9000, 0xB069, 0x8FFA, 0xB06A, 0x8FF4, + 0xB06B, 0x9003, 0xB06C, 0x8FFD, 0xB06D, 0x9005, 0xB06E, 0x8FF8, 0xB06F, 0x9095, 0xB070, 0x90E1, 0xB071, 0x90DD, 0xB072, 0x90E2, + 0xB073, 0x9152, 0xB074, 0x914D, 0xB075, 0x914C, 0xB076, 0x91D8, 0xB077, 0x91DD, 0xB078, 0x91D7, 0xB079, 0x91DC, 0xB07A, 0x91D9, + 0xB07B, 0x9583, 0xB07C, 0x9662, 0xB07D, 0x9663, 0xB07E, 0x9661, 0xB0A1, 0x965B, 0xB0A2, 0x965D, 0xB0A3, 0x9664, 0xB0A4, 0x9658, + 0xB0A5, 0x965E, 0xB0A6, 0x96BB, 0xB0A7, 0x98E2, 0xB0A8, 0x99AC, 0xB0A9, 0x9AA8, 0xB0AA, 0x9AD8, 0xB0AB, 0x9B25, 0xB0AC, 0x9B32, + 0xB0AD, 0x9B3C, 0xB0AE, 0x4E7E, 0xB0AF, 0x507A, 0xB0B0, 0x507D, 0xB0B1, 0x505C, 0xB0B2, 0x5047, 0xB0B3, 0x5043, 0xB0B4, 0x504C, + 0xB0B5, 0x505A, 0xB0B6, 0x5049, 0xB0B7, 0x5065, 0xB0B8, 0x5076, 0xB0B9, 0x504E, 0xB0BA, 0x5055, 0xB0BB, 0x5075, 0xB0BC, 0x5074, + 0xB0BD, 0x5077, 0xB0BE, 0x504F, 0xB0BF, 0x500F, 0xB0C0, 0x506F, 0xB0C1, 0x506D, 0xB0C2, 0x515C, 0xB0C3, 0x5195, 0xB0C4, 0x51F0, + 0xB0C5, 0x526A, 0xB0C6, 0x526F, 0xB0C7, 0x52D2, 0xB0C8, 0x52D9, 0xB0C9, 0x52D8, 0xB0CA, 0x52D5, 0xB0CB, 0x5310, 0xB0CC, 0x530F, + 0xB0CD, 0x5319, 0xB0CE, 0x533F, 0xB0CF, 0x5340, 0xB0D0, 0x533E, 0xB0D1, 0x53C3, 0xB0D2, 0x66FC, 0xB0D3, 0x5546, 0xB0D4, 0x556A, + 0xB0D5, 0x5566, 0xB0D6, 0x5544, 0xB0D7, 0x555E, 0xB0D8, 0x5561, 0xB0D9, 0x5543, 0xB0DA, 0x554A, 0xB0DB, 0x5531, 0xB0DC, 0x5556, + 0xB0DD, 0x554F, 0xB0DE, 0x5555, 0xB0DF, 0x552F, 0xB0E0, 0x5564, 0xB0E1, 0x5538, 0xB0E2, 0x552E, 0xB0E3, 0x555C, 0xB0E4, 0x552C, + 0xB0E5, 0x5563, 0xB0E6, 0x5533, 0xB0E7, 0x5541, 0xB0E8, 0x5557, 0xB0E9, 0x5708, 0xB0EA, 0x570B, 0xB0EB, 0x5709, 0xB0EC, 0x57DF, + 0xB0ED, 0x5805, 0xB0EE, 0x580A, 0xB0EF, 0x5806, 0xB0F0, 0x57E0, 0xB0F1, 0x57E4, 0xB0F2, 0x57FA, 0xB0F3, 0x5802, 0xB0F4, 0x5835, + 0xB0F5, 0x57F7, 0xB0F6, 0x57F9, 0xB0F7, 0x5920, 0xB0F8, 0x5962, 0xB0F9, 0x5A36, 0xB0FA, 0x5A41, 0xB0FB, 0x5A49, 0xB0FC, 0x5A66, + 0xB0FD, 0x5A6A, 0xB0FE, 0x5A40, 0xB140, 0x5A3C, 0xB141, 0x5A62, 0xB142, 0x5A5A, 0xB143, 0x5A46, 0xB144, 0x5A4A, 0xB145, 0x5B70, + 0xB146, 0x5BC7, 0xB147, 0x5BC5, 0xB148, 0x5BC4, 0xB149, 0x5BC2, 0xB14A, 0x5BBF, 0xB14B, 0x5BC6, 0xB14C, 0x5C09, 0xB14D, 0x5C08, + 0xB14E, 0x5C07, 0xB14F, 0x5C60, 0xB150, 0x5C5C, 0xB151, 0x5C5D, 0xB152, 0x5D07, 0xB153, 0x5D06, 0xB154, 0x5D0E, 0xB155, 0x5D1B, + 0xB156, 0x5D16, 0xB157, 0x5D22, 0xB158, 0x5D11, 0xB159, 0x5D29, 0xB15A, 0x5D14, 0xB15B, 0x5D19, 0xB15C, 0x5D24, 0xB15D, 0x5D27, + 0xB15E, 0x5D17, 0xB15F, 0x5DE2, 0xB160, 0x5E38, 0xB161, 0x5E36, 0xB162, 0x5E33, 0xB163, 0x5E37, 0xB164, 0x5EB7, 0xB165, 0x5EB8, + 0xB166, 0x5EB6, 0xB167, 0x5EB5, 0xB168, 0x5EBE, 0xB169, 0x5F35, 0xB16A, 0x5F37, 0xB16B, 0x5F57, 0xB16C, 0x5F6C, 0xB16D, 0x5F69, + 0xB16E, 0x5F6B, 0xB16F, 0x5F97, 0xB170, 0x5F99, 0xB171, 0x5F9E, 0xB172, 0x5F98, 0xB173, 0x5FA1, 0xB174, 0x5FA0, 0xB175, 0x5F9C, + 0xB176, 0x607F, 0xB177, 0x60A3, 0xB178, 0x6089, 0xB179, 0x60A0, 0xB17A, 0x60A8, 0xB17B, 0x60CB, 0xB17C, 0x60B4, 0xB17D, 0x60E6, + 0xB17E, 0x60BD, 0xB1A1, 0x60C5, 0xB1A2, 0x60BB, 0xB1A3, 0x60B5, 0xB1A4, 0x60DC, 0xB1A5, 0x60BC, 0xB1A6, 0x60D8, 0xB1A7, 0x60D5, + 0xB1A8, 0x60C6, 0xB1A9, 0x60DF, 0xB1AA, 0x60B8, 0xB1AB, 0x60DA, 0xB1AC, 0x60C7, 0xB1AD, 0x621A, 0xB1AE, 0x621B, 0xB1AF, 0x6248, + 0xB1B0, 0x63A0, 0xB1B1, 0x63A7, 0xB1B2, 0x6372, 0xB1B3, 0x6396, 0xB1B4, 0x63A2, 0xB1B5, 0x63A5, 0xB1B6, 0x6377, 0xB1B7, 0x6367, + 0xB1B8, 0x6398, 0xB1B9, 0x63AA, 0xB1BA, 0x6371, 0xB1BB, 0x63A9, 0xB1BC, 0x6389, 0xB1BD, 0x6383, 0xB1BE, 0x639B, 0xB1BF, 0x636B, + 0xB1C0, 0x63A8, 0xB1C1, 0x6384, 0xB1C2, 0x6388, 0xB1C3, 0x6399, 0xB1C4, 0x63A1, 0xB1C5, 0x63AC, 0xB1C6, 0x6392, 0xB1C7, 0x638F, + 0xB1C8, 0x6380, 0xB1C9, 0x637B, 0xB1CA, 0x6369, 0xB1CB, 0x6368, 0xB1CC, 0x637A, 0xB1CD, 0x655D, 0xB1CE, 0x6556, 0xB1CF, 0x6551, + 0xB1D0, 0x6559, 0xB1D1, 0x6557, 0xB1D2, 0x555F, 0xB1D3, 0x654F, 0xB1D4, 0x6558, 0xB1D5, 0x6555, 0xB1D6, 0x6554, 0xB1D7, 0x659C, + 0xB1D8, 0x659B, 0xB1D9, 0x65AC, 0xB1DA, 0x65CF, 0xB1DB, 0x65CB, 0xB1DC, 0x65CC, 0xB1DD, 0x65CE, 0xB1DE, 0x665D, 0xB1DF, 0x665A, + 0xB1E0, 0x6664, 0xB1E1, 0x6668, 0xB1E2, 0x6666, 0xB1E3, 0x665E, 0xB1E4, 0x66F9, 0xB1E5, 0x52D7, 0xB1E6, 0x671B, 0xB1E7, 0x6881, + 0xB1E8, 0x68AF, 0xB1E9, 0x68A2, 0xB1EA, 0x6893, 0xB1EB, 0x68B5, 0xB1EC, 0x687F, 0xB1ED, 0x6876, 0xB1EE, 0x68B1, 0xB1EF, 0x68A7, + 0xB1F0, 0x6897, 0xB1F1, 0x68B0, 0xB1F2, 0x6883, 0xB1F3, 0x68C4, 0xB1F4, 0x68AD, 0xB1F5, 0x6886, 0xB1F6, 0x6885, 0xB1F7, 0x6894, + 0xB1F8, 0x689D, 0xB1F9, 0x68A8, 0xB1FA, 0x689F, 0xB1FB, 0x68A1, 0xB1FC, 0x6882, 0xB1FD, 0x6B32, 0xB1FE, 0x6BBA, 0xB240, 0x6BEB, + 0xB241, 0x6BEC, 0xB242, 0x6C2B, 0xB243, 0x6D8E, 0xB244, 0x6DBC, 0xB245, 0x6DF3, 0xB246, 0x6DD9, 0xB247, 0x6DB2, 0xB248, 0x6DE1, + 0xB249, 0x6DCC, 0xB24A, 0x6DE4, 0xB24B, 0x6DFB, 0xB24C, 0x6DFA, 0xB24D, 0x6E05, 0xB24E, 0x6DC7, 0xB24F, 0x6DCB, 0xB250, 0x6DAF, + 0xB251, 0x6DD1, 0xB252, 0x6DAE, 0xB253, 0x6DDE, 0xB254, 0x6DF9, 0xB255, 0x6DB8, 0xB256, 0x6DF7, 0xB257, 0x6DF5, 0xB258, 0x6DC5, + 0xB259, 0x6DD2, 0xB25A, 0x6E1A, 0xB25B, 0x6DB5, 0xB25C, 0x6DDA, 0xB25D, 0x6DEB, 0xB25E, 0x6DD8, 0xB25F, 0x6DEA, 0xB260, 0x6DF1, + 0xB261, 0x6DEE, 0xB262, 0x6DE8, 0xB263, 0x6DC6, 0xB264, 0x6DC4, 0xB265, 0x6DAA, 0xB266, 0x6DEC, 0xB267, 0x6DBF, 0xB268, 0x6DE6, + 0xB269, 0x70F9, 0xB26A, 0x7109, 0xB26B, 0x710A, 0xB26C, 0x70FD, 0xB26D, 0x70EF, 0xB26E, 0x723D, 0xB26F, 0x727D, 0xB270, 0x7281, + 0xB271, 0x731C, 0xB272, 0x731B, 0xB273, 0x7316, 0xB274, 0x7313, 0xB275, 0x7319, 0xB276, 0x7387, 0xB277, 0x7405, 0xB278, 0x740A, + 0xB279, 0x7403, 0xB27A, 0x7406, 0xB27B, 0x73FE, 0xB27C, 0x740D, 0xB27D, 0x74E0, 0xB27E, 0x74F6, 0xB2A1, 0x74F7, 0xB2A2, 0x751C, + 0xB2A3, 0x7522, 0xB2A4, 0x7565, 0xB2A5, 0x7566, 0xB2A6, 0x7562, 0xB2A7, 0x7570, 0xB2A8, 0x758F, 0xB2A9, 0x75D4, 0xB2AA, 0x75D5, + 0xB2AB, 0x75B5, 0xB2AC, 0x75CA, 0xB2AD, 0x75CD, 0xB2AE, 0x768E, 0xB2AF, 0x76D4, 0xB2B0, 0x76D2, 0xB2B1, 0x76DB, 0xB2B2, 0x7737, + 0xB2B3, 0x773E, 0xB2B4, 0x773C, 0xB2B5, 0x7736, 0xB2B6, 0x7738, 0xB2B7, 0x773A, 0xB2B8, 0x786B, 0xB2B9, 0x7843, 0xB2BA, 0x784E, + 0xB2BB, 0x7965, 0xB2BC, 0x7968, 0xB2BD, 0x796D, 0xB2BE, 0x79FB, 0xB2BF, 0x7A92, 0xB2C0, 0x7A95, 0xB2C1, 0x7B20, 0xB2C2, 0x7B28, + 0xB2C3, 0x7B1B, 0xB2C4, 0x7B2C, 0xB2C5, 0x7B26, 0xB2C6, 0x7B19, 0xB2C7, 0x7B1E, 0xB2C8, 0x7B2E, 0xB2C9, 0x7C92, 0xB2CA, 0x7C97, + 0xB2CB, 0x7C95, 0xB2CC, 0x7D46, 0xB2CD, 0x7D43, 0xB2CE, 0x7D71, 0xB2CF, 0x7D2E, 0xB2D0, 0x7D39, 0xB2D1, 0x7D3C, 0xB2D2, 0x7D40, + 0xB2D3, 0x7D30, 0xB2D4, 0x7D33, 0xB2D5, 0x7D44, 0xB2D6, 0x7D2F, 0xB2D7, 0x7D42, 0xB2D8, 0x7D32, 0xB2D9, 0x7D31, 0xB2DA, 0x7F3D, + 0xB2DB, 0x7F9E, 0xB2DC, 0x7F9A, 0xB2DD, 0x7FCC, 0xB2DE, 0x7FCE, 0xB2DF, 0x7FD2, 0xB2E0, 0x801C, 0xB2E1, 0x804A, 0xB2E2, 0x8046, + 0xB2E3, 0x812F, 0xB2E4, 0x8116, 0xB2E5, 0x8123, 0xB2E6, 0x812B, 0xB2E7, 0x8129, 0xB2E8, 0x8130, 0xB2E9, 0x8124, 0xB2EA, 0x8202, + 0xB2EB, 0x8235, 0xB2EC, 0x8237, 0xB2ED, 0x8236, 0xB2EE, 0x8239, 0xB2EF, 0x838E, 0xB2F0, 0x839E, 0xB2F1, 0x8398, 0xB2F2, 0x8378, + 0xB2F3, 0x83A2, 0xB2F4, 0x8396, 0xB2F5, 0x83BD, 0xB2F6, 0x83AB, 0xB2F7, 0x8392, 0xB2F8, 0x838A, 0xB2F9, 0x8393, 0xB2FA, 0x8389, + 0xB2FB, 0x83A0, 0xB2FC, 0x8377, 0xB2FD, 0x837B, 0xB2FE, 0x837C, 0xB340, 0x8386, 0xB341, 0x83A7, 0xB342, 0x8655, 0xB343, 0x5F6A, + 0xB344, 0x86C7, 0xB345, 0x86C0, 0xB346, 0x86B6, 0xB347, 0x86C4, 0xB348, 0x86B5, 0xB349, 0x86C6, 0xB34A, 0x86CB, 0xB34B, 0x86B1, + 0xB34C, 0x86AF, 0xB34D, 0x86C9, 0xB34E, 0x8853, 0xB34F, 0x889E, 0xB350, 0x8888, 0xB351, 0x88AB, 0xB352, 0x8892, 0xB353, 0x8896, + 0xB354, 0x888D, 0xB355, 0x888B, 0xB356, 0x8993, 0xB357, 0x898F, 0xB358, 0x8A2A, 0xB359, 0x8A1D, 0xB35A, 0x8A23, 0xB35B, 0x8A25, + 0xB35C, 0x8A31, 0xB35D, 0x8A2D, 0xB35E, 0x8A1F, 0xB35F, 0x8A1B, 0xB360, 0x8A22, 0xB361, 0x8C49, 0xB362, 0x8C5A, 0xB363, 0x8CA9, + 0xB364, 0x8CAC, 0xB365, 0x8CAB, 0xB366, 0x8CA8, 0xB367, 0x8CAA, 0xB368, 0x8CA7, 0xB369, 0x8D67, 0xB36A, 0x8D66, 0xB36B, 0x8DBE, + 0xB36C, 0x8DBA, 0xB36D, 0x8EDB, 0xB36E, 0x8EDF, 0xB36F, 0x9019, 0xB370, 0x900D, 0xB371, 0x901A, 0xB372, 0x9017, 0xB373, 0x9023, + 0xB374, 0x901F, 0xB375, 0x901D, 0xB376, 0x9010, 0xB377, 0x9015, 0xB378, 0x901E, 0xB379, 0x9020, 0xB37A, 0x900F, 0xB37B, 0x9022, + 0xB37C, 0x9016, 0xB37D, 0x901B, 0xB37E, 0x9014, 0xB3A1, 0x90E8, 0xB3A2, 0x90ED, 0xB3A3, 0x90FD, 0xB3A4, 0x9157, 0xB3A5, 0x91CE, + 0xB3A6, 0x91F5, 0xB3A7, 0x91E6, 0xB3A8, 0x91E3, 0xB3A9, 0x91E7, 0xB3AA, 0x91ED, 0xB3AB, 0x91E9, 0xB3AC, 0x9589, 0xB3AD, 0x966A, + 0xB3AE, 0x9675, 0xB3AF, 0x9673, 0xB3B0, 0x9678, 0xB3B1, 0x9670, 0xB3B2, 0x9674, 0xB3B3, 0x9676, 0xB3B4, 0x9677, 0xB3B5, 0x966C, + 0xB3B6, 0x96C0, 0xB3B7, 0x96EA, 0xB3B8, 0x96E9, 0xB3B9, 0x7AE0, 0xB3BA, 0x7ADF, 0xB3BB, 0x9802, 0xB3BC, 0x9803, 0xB3BD, 0x9B5A, + 0xB3BE, 0x9CE5, 0xB3BF, 0x9E75, 0xB3C0, 0x9E7F, 0xB3C1, 0x9EA5, 0xB3C2, 0x9EBB, 0xB3C3, 0x50A2, 0xB3C4, 0x508D, 0xB3C5, 0x5085, + 0xB3C6, 0x5099, 0xB3C7, 0x5091, 0xB3C8, 0x5080, 0xB3C9, 0x5096, 0xB3CA, 0x5098, 0xB3CB, 0x509A, 0xB3CC, 0x6700, 0xB3CD, 0x51F1, + 0xB3CE, 0x5272, 0xB3CF, 0x5274, 0xB3D0, 0x5275, 0xB3D1, 0x5269, 0xB3D2, 0x52DE, 0xB3D3, 0x52DD, 0xB3D4, 0x52DB, 0xB3D5, 0x535A, + 0xB3D6, 0x53A5, 0xB3D7, 0x557B, 0xB3D8, 0x5580, 0xB3D9, 0x55A7, 0xB3DA, 0x557C, 0xB3DB, 0x558A, 0xB3DC, 0x559D, 0xB3DD, 0x5598, + 0xB3DE, 0x5582, 0xB3DF, 0x559C, 0xB3E0, 0x55AA, 0xB3E1, 0x5594, 0xB3E2, 0x5587, 0xB3E3, 0x558B, 0xB3E4, 0x5583, 0xB3E5, 0x55B3, + 0xB3E6, 0x55AE, 0xB3E7, 0x559F, 0xB3E8, 0x553E, 0xB3E9, 0x55B2, 0xB3EA, 0x559A, 0xB3EB, 0x55BB, 0xB3EC, 0x55AC, 0xB3ED, 0x55B1, + 0xB3EE, 0x557E, 0xB3EF, 0x5589, 0xB3F0, 0x55AB, 0xB3F1, 0x5599, 0xB3F2, 0x570D, 0xB3F3, 0x582F, 0xB3F4, 0x582A, 0xB3F5, 0x5834, + 0xB3F6, 0x5824, 0xB3F7, 0x5830, 0xB3F8, 0x5831, 0xB3F9, 0x5821, 0xB3FA, 0x581D, 0xB3FB, 0x5820, 0xB3FC, 0x58F9, 0xB3FD, 0x58FA, + 0xB3FE, 0x5960, 0xB440, 0x5A77, 0xB441, 0x5A9A, 0xB442, 0x5A7F, 0xB443, 0x5A92, 0xB444, 0x5A9B, 0xB445, 0x5AA7, 0xB446, 0x5B73, + 0xB447, 0x5B71, 0xB448, 0x5BD2, 0xB449, 0x5BCC, 0xB44A, 0x5BD3, 0xB44B, 0x5BD0, 0xB44C, 0x5C0A, 0xB44D, 0x5C0B, 0xB44E, 0x5C31, + 0xB44F, 0x5D4C, 0xB450, 0x5D50, 0xB451, 0x5D34, 0xB452, 0x5D47, 0xB453, 0x5DFD, 0xB454, 0x5E45, 0xB455, 0x5E3D, 0xB456, 0x5E40, + 0xB457, 0x5E43, 0xB458, 0x5E7E, 0xB459, 0x5ECA, 0xB45A, 0x5EC1, 0xB45B, 0x5EC2, 0xB45C, 0x5EC4, 0xB45D, 0x5F3C, 0xB45E, 0x5F6D, + 0xB45F, 0x5FA9, 0xB460, 0x5FAA, 0xB461, 0x5FA8, 0xB462, 0x60D1, 0xB463, 0x60E1, 0xB464, 0x60B2, 0xB465, 0x60B6, 0xB466, 0x60E0, + 0xB467, 0x611C, 0xB468, 0x6123, 0xB469, 0x60FA, 0xB46A, 0x6115, 0xB46B, 0x60F0, 0xB46C, 0x60FB, 0xB46D, 0x60F4, 0xB46E, 0x6168, + 0xB46F, 0x60F1, 0xB470, 0x610E, 0xB471, 0x60F6, 0xB472, 0x6109, 0xB473, 0x6100, 0xB474, 0x6112, 0xB475, 0x621F, 0xB476, 0x6249, + 0xB477, 0x63A3, 0xB478, 0x638C, 0xB479, 0x63CF, 0xB47A, 0x63C0, 0xB47B, 0x63E9, 0xB47C, 0x63C9, 0xB47D, 0x63C6, 0xB47E, 0x63CD, + 0xB4A1, 0x63D2, 0xB4A2, 0x63E3, 0xB4A3, 0x63D0, 0xB4A4, 0x63E1, 0xB4A5, 0x63D6, 0xB4A6, 0x63ED, 0xB4A7, 0x63EE, 0xB4A8, 0x6376, + 0xB4A9, 0x63F4, 0xB4AA, 0x63EA, 0xB4AB, 0x63DB, 0xB4AC, 0x6452, 0xB4AD, 0x63DA, 0xB4AE, 0x63F9, 0xB4AF, 0x655E, 0xB4B0, 0x6566, + 0xB4B1, 0x6562, 0xB4B2, 0x6563, 0xB4B3, 0x6591, 0xB4B4, 0x6590, 0xB4B5, 0x65AF, 0xB4B6, 0x666E, 0xB4B7, 0x6670, 0xB4B8, 0x6674, + 0xB4B9, 0x6676, 0xB4BA, 0x666F, 0xB4BB, 0x6691, 0xB4BC, 0x667A, 0xB4BD, 0x667E, 0xB4BE, 0x6677, 0xB4BF, 0x66FE, 0xB4C0, 0x66FF, + 0xB4C1, 0x671F, 0xB4C2, 0x671D, 0xB4C3, 0x68FA, 0xB4C4, 0x68D5, 0xB4C5, 0x68E0, 0xB4C6, 0x68D8, 0xB4C7, 0x68D7, 0xB4C8, 0x6905, + 0xB4C9, 0x68DF, 0xB4CA, 0x68F5, 0xB4CB, 0x68EE, 0xB4CC, 0x68E7, 0xB4CD, 0x68F9, 0xB4CE, 0x68D2, 0xB4CF, 0x68F2, 0xB4D0, 0x68E3, + 0xB4D1, 0x68CB, 0xB4D2, 0x68CD, 0xB4D3, 0x690D, 0xB4D4, 0x6912, 0xB4D5, 0x690E, 0xB4D6, 0x68C9, 0xB4D7, 0x68DA, 0xB4D8, 0x696E, + 0xB4D9, 0x68FB, 0xB4DA, 0x6B3E, 0xB4DB, 0x6B3A, 0xB4DC, 0x6B3D, 0xB4DD, 0x6B98, 0xB4DE, 0x6B96, 0xB4DF, 0x6BBC, 0xB4E0, 0x6BEF, + 0xB4E1, 0x6C2E, 0xB4E2, 0x6C2F, 0xB4E3, 0x6C2C, 0xB4E4, 0x6E2F, 0xB4E5, 0x6E38, 0xB4E6, 0x6E54, 0xB4E7, 0x6E21, 0xB4E8, 0x6E32, + 0xB4E9, 0x6E67, 0xB4EA, 0x6E4A, 0xB4EB, 0x6E20, 0xB4EC, 0x6E25, 0xB4ED, 0x6E23, 0xB4EE, 0x6E1B, 0xB4EF, 0x6E5B, 0xB4F0, 0x6E58, + 0xB4F1, 0x6E24, 0xB4F2, 0x6E56, 0xB4F3, 0x6E6E, 0xB4F4, 0x6E2D, 0xB4F5, 0x6E26, 0xB4F6, 0x6E6F, 0xB4F7, 0x6E34, 0xB4F8, 0x6E4D, + 0xB4F9, 0x6E3A, 0xB4FA, 0x6E2C, 0xB4FB, 0x6E43, 0xB4FC, 0x6E1D, 0xB4FD, 0x6E3E, 0xB4FE, 0x6ECB, 0xB540, 0x6E89, 0xB541, 0x6E19, + 0xB542, 0x6E4E, 0xB543, 0x6E63, 0xB544, 0x6E44, 0xB545, 0x6E72, 0xB546, 0x6E69, 0xB547, 0x6E5F, 0xB548, 0x7119, 0xB549, 0x711A, + 0xB54A, 0x7126, 0xB54B, 0x7130, 0xB54C, 0x7121, 0xB54D, 0x7136, 0xB54E, 0x716E, 0xB54F, 0x711C, 0xB550, 0x724C, 0xB551, 0x7284, + 0xB552, 0x7280, 0xB553, 0x7336, 0xB554, 0x7325, 0xB555, 0x7334, 0xB556, 0x7329, 0xB557, 0x743A, 0xB558, 0x742A, 0xB559, 0x7433, + 0xB55A, 0x7422, 0xB55B, 0x7425, 0xB55C, 0x7435, 0xB55D, 0x7436, 0xB55E, 0x7434, 0xB55F, 0x742F, 0xB560, 0x741B, 0xB561, 0x7426, + 0xB562, 0x7428, 0xB563, 0x7525, 0xB564, 0x7526, 0xB565, 0x756B, 0xB566, 0x756A, 0xB567, 0x75E2, 0xB568, 0x75DB, 0xB569, 0x75E3, + 0xB56A, 0x75D9, 0xB56B, 0x75D8, 0xB56C, 0x75DE, 0xB56D, 0x75E0, 0xB56E, 0x767B, 0xB56F, 0x767C, 0xB570, 0x7696, 0xB571, 0x7693, + 0xB572, 0x76B4, 0xB573, 0x76DC, 0xB574, 0x774F, 0xB575, 0x77ED, 0xB576, 0x785D, 0xB577, 0x786C, 0xB578, 0x786F, 0xB579, 0x7A0D, + 0xB57A, 0x7A08, 0xB57B, 0x7A0B, 0xB57C, 0x7A05, 0xB57D, 0x7A00, 0xB57E, 0x7A98, 0xB5A1, 0x7A97, 0xB5A2, 0x7A96, 0xB5A3, 0x7AE5, + 0xB5A4, 0x7AE3, 0xB5A5, 0x7B49, 0xB5A6, 0x7B56, 0xB5A7, 0x7B46, 0xB5A8, 0x7B50, 0xB5A9, 0x7B52, 0xB5AA, 0x7B54, 0xB5AB, 0x7B4D, + 0xB5AC, 0x7B4B, 0xB5AD, 0x7B4F, 0xB5AE, 0x7B51, 0xB5AF, 0x7C9F, 0xB5B0, 0x7CA5, 0xB5B1, 0x7D5E, 0xB5B2, 0x7D50, 0xB5B3, 0x7D68, + 0xB5B4, 0x7D55, 0xB5B5, 0x7D2B, 0xB5B6, 0x7D6E, 0xB5B7, 0x7D72, 0xB5B8, 0x7D61, 0xB5B9, 0x7D66, 0xB5BA, 0x7D62, 0xB5BB, 0x7D70, + 0xB5BC, 0x7D73, 0xB5BD, 0x5584, 0xB5BE, 0x7FD4, 0xB5BF, 0x7FD5, 0xB5C0, 0x800B, 0xB5C1, 0x8052, 0xB5C2, 0x8085, 0xB5C3, 0x8155, + 0xB5C4, 0x8154, 0xB5C5, 0x814B, 0xB5C6, 0x8151, 0xB5C7, 0x814E, 0xB5C8, 0x8139, 0xB5C9, 0x8146, 0xB5CA, 0x813E, 0xB5CB, 0x814C, + 0xB5CC, 0x8153, 0xB5CD, 0x8174, 0xB5CE, 0x8212, 0xB5CF, 0x821C, 0xB5D0, 0x83E9, 0xB5D1, 0x8403, 0xB5D2, 0x83F8, 0xB5D3, 0x840D, + 0xB5D4, 0x83E0, 0xB5D5, 0x83C5, 0xB5D6, 0x840B, 0xB5D7, 0x83C1, 0xB5D8, 0x83EF, 0xB5D9, 0x83F1, 0xB5DA, 0x83F4, 0xB5DB, 0x8457, + 0xB5DC, 0x840A, 0xB5DD, 0x83F0, 0xB5DE, 0x840C, 0xB5DF, 0x83CC, 0xB5E0, 0x83FD, 0xB5E1, 0x83F2, 0xB5E2, 0x83CA, 0xB5E3, 0x8438, + 0xB5E4, 0x840E, 0xB5E5, 0x8404, 0xB5E6, 0x83DC, 0xB5E7, 0x8407, 0xB5E8, 0x83D4, 0xB5E9, 0x83DF, 0xB5EA, 0x865B, 0xB5EB, 0x86DF, + 0xB5EC, 0x86D9, 0xB5ED, 0x86ED, 0xB5EE, 0x86D4, 0xB5EF, 0x86DB, 0xB5F0, 0x86E4, 0xB5F1, 0x86D0, 0xB5F2, 0x86DE, 0xB5F3, 0x8857, + 0xB5F4, 0x88C1, 0xB5F5, 0x88C2, 0xB5F6, 0x88B1, 0xB5F7, 0x8983, 0xB5F8, 0x8996, 0xB5F9, 0x8A3B, 0xB5FA, 0x8A60, 0xB5FB, 0x8A55, + 0xB5FC, 0x8A5E, 0xB5FD, 0x8A3C, 0xB5FE, 0x8A41, 0xB640, 0x8A54, 0xB641, 0x8A5B, 0xB642, 0x8A50, 0xB643, 0x8A46, 0xB644, 0x8A34, + 0xB645, 0x8A3A, 0xB646, 0x8A36, 0xB647, 0x8A56, 0xB648, 0x8C61, 0xB649, 0x8C82, 0xB64A, 0x8CAF, 0xB64B, 0x8CBC, 0xB64C, 0x8CB3, + 0xB64D, 0x8CBD, 0xB64E, 0x8CC1, 0xB64F, 0x8CBB, 0xB650, 0x8CC0, 0xB651, 0x8CB4, 0xB652, 0x8CB7, 0xB653, 0x8CB6, 0xB654, 0x8CBF, + 0xB655, 0x8CB8, 0xB656, 0x8D8A, 0xB657, 0x8D85, 0xB658, 0x8D81, 0xB659, 0x8DCE, 0xB65A, 0x8DDD, 0xB65B, 0x8DCB, 0xB65C, 0x8DDA, + 0xB65D, 0x8DD1, 0xB65E, 0x8DCC, 0xB65F, 0x8DDB, 0xB660, 0x8DC6, 0xB661, 0x8EFB, 0xB662, 0x8EF8, 0xB663, 0x8EFC, 0xB664, 0x8F9C, + 0xB665, 0x902E, 0xB666, 0x9035, 0xB667, 0x9031, 0xB668, 0x9038, 0xB669, 0x9032, 0xB66A, 0x9036, 0xB66B, 0x9102, 0xB66C, 0x90F5, + 0xB66D, 0x9109, 0xB66E, 0x90FE, 0xB66F, 0x9163, 0xB670, 0x9165, 0xB671, 0x91CF, 0xB672, 0x9214, 0xB673, 0x9215, 0xB674, 0x9223, + 0xB675, 0x9209, 0xB676, 0x921E, 0xB677, 0x920D, 0xB678, 0x9210, 0xB679, 0x9207, 0xB67A, 0x9211, 0xB67B, 0x9594, 0xB67C, 0x958F, + 0xB67D, 0x958B, 0xB67E, 0x9591, 0xB6A1, 0x9593, 0xB6A2, 0x9592, 0xB6A3, 0x958E, 0xB6A4, 0x968A, 0xB6A5, 0x968E, 0xB6A6, 0x968B, + 0xB6A7, 0x967D, 0xB6A8, 0x9685, 0xB6A9, 0x9686, 0xB6AA, 0x968D, 0xB6AB, 0x9672, 0xB6AC, 0x9684, 0xB6AD, 0x96C1, 0xB6AE, 0x96C5, + 0xB6AF, 0x96C4, 0xB6B0, 0x96C6, 0xB6B1, 0x96C7, 0xB6B2, 0x96EF, 0xB6B3, 0x96F2, 0xB6B4, 0x97CC, 0xB6B5, 0x9805, 0xB6B6, 0x9806, + 0xB6B7, 0x9808, 0xB6B8, 0x98E7, 0xB6B9, 0x98EA, 0xB6BA, 0x98EF, 0xB6BB, 0x98E9, 0xB6BC, 0x98F2, 0xB6BD, 0x98ED, 0xB6BE, 0x99AE, + 0xB6BF, 0x99AD, 0xB6C0, 0x9EC3, 0xB6C1, 0x9ECD, 0xB6C2, 0x9ED1, 0xB6C3, 0x4E82, 0xB6C4, 0x50AD, 0xB6C5, 0x50B5, 0xB6C6, 0x50B2, + 0xB6C7, 0x50B3, 0xB6C8, 0x50C5, 0xB6C9, 0x50BE, 0xB6CA, 0x50AC, 0xB6CB, 0x50B7, 0xB6CC, 0x50BB, 0xB6CD, 0x50AF, 0xB6CE, 0x50C7, + 0xB6CF, 0x527F, 0xB6D0, 0x5277, 0xB6D1, 0x527D, 0xB6D2, 0x52DF, 0xB6D3, 0x52E6, 0xB6D4, 0x52E4, 0xB6D5, 0x52E2, 0xB6D6, 0x52E3, + 0xB6D7, 0x532F, 0xB6D8, 0x55DF, 0xB6D9, 0x55E8, 0xB6DA, 0x55D3, 0xB6DB, 0x55E6, 0xB6DC, 0x55CE, 0xB6DD, 0x55DC, 0xB6DE, 0x55C7, + 0xB6DF, 0x55D1, 0xB6E0, 0x55E3, 0xB6E1, 0x55E4, 0xB6E2, 0x55EF, 0xB6E3, 0x55DA, 0xB6E4, 0x55E1, 0xB6E5, 0x55C5, 0xB6E6, 0x55C6, + 0xB6E7, 0x55E5, 0xB6E8, 0x55C9, 0xB6E9, 0x5712, 0xB6EA, 0x5713, 0xB6EB, 0x585E, 0xB6EC, 0x5851, 0xB6ED, 0x5858, 0xB6EE, 0x5857, + 0xB6EF, 0x585A, 0xB6F0, 0x5854, 0xB6F1, 0x586B, 0xB6F2, 0x584C, 0xB6F3, 0x586D, 0xB6F4, 0x584A, 0xB6F5, 0x5862, 0xB6F6, 0x5852, + 0xB6F7, 0x584B, 0xB6F8, 0x5967, 0xB6F9, 0x5AC1, 0xB6FA, 0x5AC9, 0xB6FB, 0x5ACC, 0xB6FC, 0x5ABE, 0xB6FD, 0x5ABD, 0xB6FE, 0x5ABC, + 0xB740, 0x5AB3, 0xB741, 0x5AC2, 0xB742, 0x5AB2, 0xB743, 0x5D69, 0xB744, 0x5D6F, 0xB745, 0x5E4C, 0xB746, 0x5E79, 0xB747, 0x5EC9, + 0xB748, 0x5EC8, 0xB749, 0x5F12, 0xB74A, 0x5F59, 0xB74B, 0x5FAC, 0xB74C, 0x5FAE, 0xB74D, 0x611A, 0xB74E, 0x610F, 0xB74F, 0x6148, + 0xB750, 0x611F, 0xB751, 0x60F3, 0xB752, 0x611B, 0xB753, 0x60F9, 0xB754, 0x6101, 0xB755, 0x6108, 0xB756, 0x614E, 0xB757, 0x614C, + 0xB758, 0x6144, 0xB759, 0x614D, 0xB75A, 0x613E, 0xB75B, 0x6134, 0xB75C, 0x6127, 0xB75D, 0x610D, 0xB75E, 0x6106, 0xB75F, 0x6137, + 0xB760, 0x6221, 0xB761, 0x6222, 0xB762, 0x6413, 0xB763, 0x643E, 0xB764, 0x641E, 0xB765, 0x642A, 0xB766, 0x642D, 0xB767, 0x643D, + 0xB768, 0x642C, 0xB769, 0x640F, 0xB76A, 0x641C, 0xB76B, 0x6414, 0xB76C, 0x640D, 0xB76D, 0x6436, 0xB76E, 0x6416, 0xB76F, 0x6417, + 0xB770, 0x6406, 0xB771, 0x656C, 0xB772, 0x659F, 0xB773, 0x65B0, 0xB774, 0x6697, 0xB775, 0x6689, 0xB776, 0x6687, 0xB777, 0x6688, + 0xB778, 0x6696, 0xB779, 0x6684, 0xB77A, 0x6698, 0xB77B, 0x668D, 0xB77C, 0x6703, 0xB77D, 0x6994, 0xB77E, 0x696D, 0xB7A1, 0x695A, + 0xB7A2, 0x6977, 0xB7A3, 0x6960, 0xB7A4, 0x6954, 0xB7A5, 0x6975, 0xB7A6, 0x6930, 0xB7A7, 0x6982, 0xB7A8, 0x694A, 0xB7A9, 0x6968, + 0xB7AA, 0x696B, 0xB7AB, 0x695E, 0xB7AC, 0x6953, 0xB7AD, 0x6979, 0xB7AE, 0x6986, 0xB7AF, 0x695D, 0xB7B0, 0x6963, 0xB7B1, 0x695B, + 0xB7B2, 0x6B47, 0xB7B3, 0x6B72, 0xB7B4, 0x6BC0, 0xB7B5, 0x6BBF, 0xB7B6, 0x6BD3, 0xB7B7, 0x6BFD, 0xB7B8, 0x6EA2, 0xB7B9, 0x6EAF, + 0xB7BA, 0x6ED3, 0xB7BB, 0x6EB6, 0xB7BC, 0x6EC2, 0xB7BD, 0x6E90, 0xB7BE, 0x6E9D, 0xB7BF, 0x6EC7, 0xB7C0, 0x6EC5, 0xB7C1, 0x6EA5, + 0xB7C2, 0x6E98, 0xB7C3, 0x6EBC, 0xB7C4, 0x6EBA, 0xB7C5, 0x6EAB, 0xB7C6, 0x6ED1, 0xB7C7, 0x6E96, 0xB7C8, 0x6E9C, 0xB7C9, 0x6EC4, + 0xB7CA, 0x6ED4, 0xB7CB, 0x6EAA, 0xB7CC, 0x6EA7, 0xB7CD, 0x6EB4, 0xB7CE, 0x714E, 0xB7CF, 0x7159, 0xB7D0, 0x7169, 0xB7D1, 0x7164, + 0xB7D2, 0x7149, 0xB7D3, 0x7167, 0xB7D4, 0x715C, 0xB7D5, 0x716C, 0xB7D6, 0x7166, 0xB7D7, 0x714C, 0xB7D8, 0x7165, 0xB7D9, 0x715E, + 0xB7DA, 0x7146, 0xB7DB, 0x7168, 0xB7DC, 0x7156, 0xB7DD, 0x723A, 0xB7DE, 0x7252, 0xB7DF, 0x7337, 0xB7E0, 0x7345, 0xB7E1, 0x733F, + 0xB7E2, 0x733E, 0xB7E3, 0x746F, 0xB7E4, 0x745A, 0xB7E5, 0x7455, 0xB7E6, 0x745F, 0xB7E7, 0x745E, 0xB7E8, 0x7441, 0xB7E9, 0x743F, + 0xB7EA, 0x7459, 0xB7EB, 0x745B, 0xB7EC, 0x745C, 0xB7ED, 0x7576, 0xB7EE, 0x7578, 0xB7EF, 0x7600, 0xB7F0, 0x75F0, 0xB7F1, 0x7601, + 0xB7F2, 0x75F2, 0xB7F3, 0x75F1, 0xB7F4, 0x75FA, 0xB7F5, 0x75FF, 0xB7F6, 0x75F4, 0xB7F7, 0x75F3, 0xB7F8, 0x76DE, 0xB7F9, 0x76DF, + 0xB7FA, 0x775B, 0xB7FB, 0x776B, 0xB7FC, 0x7766, 0xB7FD, 0x775E, 0xB7FE, 0x7763, 0xB840, 0x7779, 0xB841, 0x776A, 0xB842, 0x776C, + 0xB843, 0x775C, 0xB844, 0x7765, 0xB845, 0x7768, 0xB846, 0x7762, 0xB847, 0x77EE, 0xB848, 0x788E, 0xB849, 0x78B0, 0xB84A, 0x7897, + 0xB84B, 0x7898, 0xB84C, 0x788C, 0xB84D, 0x7889, 0xB84E, 0x787C, 0xB84F, 0x7891, 0xB850, 0x7893, 0xB851, 0x787F, 0xB852, 0x797A, + 0xB853, 0x797F, 0xB854, 0x7981, 0xB855, 0x842C, 0xB856, 0x79BD, 0xB857, 0x7A1C, 0xB858, 0x7A1A, 0xB859, 0x7A20, 0xB85A, 0x7A14, + 0xB85B, 0x7A1F, 0xB85C, 0x7A1E, 0xB85D, 0x7A9F, 0xB85E, 0x7AA0, 0xB85F, 0x7B77, 0xB860, 0x7BC0, 0xB861, 0x7B60, 0xB862, 0x7B6E, + 0xB863, 0x7B67, 0xB864, 0x7CB1, 0xB865, 0x7CB3, 0xB866, 0x7CB5, 0xB867, 0x7D93, 0xB868, 0x7D79, 0xB869, 0x7D91, 0xB86A, 0x7D81, + 0xB86B, 0x7D8F, 0xB86C, 0x7D5B, 0xB86D, 0x7F6E, 0xB86E, 0x7F69, 0xB86F, 0x7F6A, 0xB870, 0x7F72, 0xB871, 0x7FA9, 0xB872, 0x7FA8, + 0xB873, 0x7FA4, 0xB874, 0x8056, 0xB875, 0x8058, 0xB876, 0x8086, 0xB877, 0x8084, 0xB878, 0x8171, 0xB879, 0x8170, 0xB87A, 0x8178, + 0xB87B, 0x8165, 0xB87C, 0x816E, 0xB87D, 0x8173, 0xB87E, 0x816B, 0xB8A1, 0x8179, 0xB8A2, 0x817A, 0xB8A3, 0x8166, 0xB8A4, 0x8205, + 0xB8A5, 0x8247, 0xB8A6, 0x8482, 0xB8A7, 0x8477, 0xB8A8, 0x843D, 0xB8A9, 0x8431, 0xB8AA, 0x8475, 0xB8AB, 0x8466, 0xB8AC, 0x846B, + 0xB8AD, 0x8449, 0xB8AE, 0x846C, 0xB8AF, 0x845B, 0xB8B0, 0x843C, 0xB8B1, 0x8435, 0xB8B2, 0x8461, 0xB8B3, 0x8463, 0xB8B4, 0x8469, + 0xB8B5, 0x846D, 0xB8B6, 0x8446, 0xB8B7, 0x865E, 0xB8B8, 0x865C, 0xB8B9, 0x865F, 0xB8BA, 0x86F9, 0xB8BB, 0x8713, 0xB8BC, 0x8708, + 0xB8BD, 0x8707, 0xB8BE, 0x8700, 0xB8BF, 0x86FE, 0xB8C0, 0x86FB, 0xB8C1, 0x8702, 0xB8C2, 0x8703, 0xB8C3, 0x8706, 0xB8C4, 0x870A, + 0xB8C5, 0x8859, 0xB8C6, 0x88DF, 0xB8C7, 0x88D4, 0xB8C8, 0x88D9, 0xB8C9, 0x88DC, 0xB8CA, 0x88D8, 0xB8CB, 0x88DD, 0xB8CC, 0x88E1, + 0xB8CD, 0x88CA, 0xB8CE, 0x88D5, 0xB8CF, 0x88D2, 0xB8D0, 0x899C, 0xB8D1, 0x89E3, 0xB8D2, 0x8A6B, 0xB8D3, 0x8A72, 0xB8D4, 0x8A73, + 0xB8D5, 0x8A66, 0xB8D6, 0x8A69, 0xB8D7, 0x8A70, 0xB8D8, 0x8A87, 0xB8D9, 0x8A7C, 0xB8DA, 0x8A63, 0xB8DB, 0x8AA0, 0xB8DC, 0x8A71, + 0xB8DD, 0x8A85, 0xB8DE, 0x8A6D, 0xB8DF, 0x8A62, 0xB8E0, 0x8A6E, 0xB8E1, 0x8A6C, 0xB8E2, 0x8A79, 0xB8E3, 0x8A7B, 0xB8E4, 0x8A3E, + 0xB8E5, 0x8A68, 0xB8E6, 0x8C62, 0xB8E7, 0x8C8A, 0xB8E8, 0x8C89, 0xB8E9, 0x8CCA, 0xB8EA, 0x8CC7, 0xB8EB, 0x8CC8, 0xB8EC, 0x8CC4, + 0xB8ED, 0x8CB2, 0xB8EE, 0x8CC3, 0xB8EF, 0x8CC2, 0xB8F0, 0x8CC5, 0xB8F1, 0x8DE1, 0xB8F2, 0x8DDF, 0xB8F3, 0x8DE8, 0xB8F4, 0x8DEF, + 0xB8F5, 0x8DF3, 0xB8F6, 0x8DFA, 0xB8F7, 0x8DEA, 0xB8F8, 0x8DE4, 0xB8F9, 0x8DE6, 0xB8FA, 0x8EB2, 0xB8FB, 0x8F03, 0xB8FC, 0x8F09, + 0xB8FD, 0x8EFE, 0xB8FE, 0x8F0A, 0xB940, 0x8F9F, 0xB941, 0x8FB2, 0xB942, 0x904B, 0xB943, 0x904A, 0xB944, 0x9053, 0xB945, 0x9042, + 0xB946, 0x9054, 0xB947, 0x903C, 0xB948, 0x9055, 0xB949, 0x9050, 0xB94A, 0x9047, 0xB94B, 0x904F, 0xB94C, 0x904E, 0xB94D, 0x904D, + 0xB94E, 0x9051, 0xB94F, 0x903E, 0xB950, 0x9041, 0xB951, 0x9112, 0xB952, 0x9117, 0xB953, 0x916C, 0xB954, 0x916A, 0xB955, 0x9169, + 0xB956, 0x91C9, 0xB957, 0x9237, 0xB958, 0x9257, 0xB959, 0x9238, 0xB95A, 0x923D, 0xB95B, 0x9240, 0xB95C, 0x923E, 0xB95D, 0x925B, + 0xB95E, 0x924B, 0xB95F, 0x9264, 0xB960, 0x9251, 0xB961, 0x9234, 0xB962, 0x9249, 0xB963, 0x924D, 0xB964, 0x9245, 0xB965, 0x9239, + 0xB966, 0x923F, 0xB967, 0x925A, 0xB968, 0x9598, 0xB969, 0x9698, 0xB96A, 0x9694, 0xB96B, 0x9695, 0xB96C, 0x96CD, 0xB96D, 0x96CB, + 0xB96E, 0x96C9, 0xB96F, 0x96CA, 0xB970, 0x96F7, 0xB971, 0x96FB, 0xB972, 0x96F9, 0xB973, 0x96F6, 0xB974, 0x9756, 0xB975, 0x9774, + 0xB976, 0x9776, 0xB977, 0x9810, 0xB978, 0x9811, 0xB979, 0x9813, 0xB97A, 0x980A, 0xB97B, 0x9812, 0xB97C, 0x980C, 0xB97D, 0x98FC, + 0xB97E, 0x98F4, 0xB9A1, 0x98FD, 0xB9A2, 0x98FE, 0xB9A3, 0x99B3, 0xB9A4, 0x99B1, 0xB9A5, 0x99B4, 0xB9A6, 0x9AE1, 0xB9A7, 0x9CE9, + 0xB9A8, 0x9E82, 0xB9A9, 0x9F0E, 0xB9AA, 0x9F13, 0xB9AB, 0x9F20, 0xB9AC, 0x50E7, 0xB9AD, 0x50EE, 0xB9AE, 0x50E5, 0xB9AF, 0x50D6, + 0xB9B0, 0x50ED, 0xB9B1, 0x50DA, 0xB9B2, 0x50D5, 0xB9B3, 0x50CF, 0xB9B4, 0x50D1, 0xB9B5, 0x50F1, 0xB9B6, 0x50CE, 0xB9B7, 0x50E9, + 0xB9B8, 0x5162, 0xB9B9, 0x51F3, 0xB9BA, 0x5283, 0xB9BB, 0x5282, 0xB9BC, 0x5331, 0xB9BD, 0x53AD, 0xB9BE, 0x55FE, 0xB9BF, 0x5600, + 0xB9C0, 0x561B, 0xB9C1, 0x5617, 0xB9C2, 0x55FD, 0xB9C3, 0x5614, 0xB9C4, 0x5606, 0xB9C5, 0x5609, 0xB9C6, 0x560D, 0xB9C7, 0x560E, + 0xB9C8, 0x55F7, 0xB9C9, 0x5616, 0xB9CA, 0x561F, 0xB9CB, 0x5608, 0xB9CC, 0x5610, 0xB9CD, 0x55F6, 0xB9CE, 0x5718, 0xB9CF, 0x5716, + 0xB9D0, 0x5875, 0xB9D1, 0x587E, 0xB9D2, 0x5883, 0xB9D3, 0x5893, 0xB9D4, 0x588A, 0xB9D5, 0x5879, 0xB9D6, 0x5885, 0xB9D7, 0x587D, + 0xB9D8, 0x58FD, 0xB9D9, 0x5925, 0xB9DA, 0x5922, 0xB9DB, 0x5924, 0xB9DC, 0x596A, 0xB9DD, 0x5969, 0xB9DE, 0x5AE1, 0xB9DF, 0x5AE6, + 0xB9E0, 0x5AE9, 0xB9E1, 0x5AD7, 0xB9E2, 0x5AD6, 0xB9E3, 0x5AD8, 0xB9E4, 0x5AE3, 0xB9E5, 0x5B75, 0xB9E6, 0x5BDE, 0xB9E7, 0x5BE7, + 0xB9E8, 0x5BE1, 0xB9E9, 0x5BE5, 0xB9EA, 0x5BE6, 0xB9EB, 0x5BE8, 0xB9EC, 0x5BE2, 0xB9ED, 0x5BE4, 0xB9EE, 0x5BDF, 0xB9EF, 0x5C0D, + 0xB9F0, 0x5C62, 0xB9F1, 0x5D84, 0xB9F2, 0x5D87, 0xB9F3, 0x5E5B, 0xB9F4, 0x5E63, 0xB9F5, 0x5E55, 0xB9F6, 0x5E57, 0xB9F7, 0x5E54, + 0xB9F8, 0x5ED3, 0xB9F9, 0x5ED6, 0xB9FA, 0x5F0A, 0xB9FB, 0x5F46, 0xB9FC, 0x5F70, 0xB9FD, 0x5FB9, 0xB9FE, 0x6147, 0xBA40, 0x613F, + 0xBA41, 0x614B, 0xBA42, 0x6177, 0xBA43, 0x6162, 0xBA44, 0x6163, 0xBA45, 0x615F, 0xBA46, 0x615A, 0xBA47, 0x6158, 0xBA48, 0x6175, + 0xBA49, 0x622A, 0xBA4A, 0x6487, 0xBA4B, 0x6458, 0xBA4C, 0x6454, 0xBA4D, 0x64A4, 0xBA4E, 0x6478, 0xBA4F, 0x645F, 0xBA50, 0x647A, + 0xBA51, 0x6451, 0xBA52, 0x6467, 0xBA53, 0x6434, 0xBA54, 0x646D, 0xBA55, 0x647B, 0xBA56, 0x6572, 0xBA57, 0x65A1, 0xBA58, 0x65D7, + 0xBA59, 0x65D6, 0xBA5A, 0x66A2, 0xBA5B, 0x66A8, 0xBA5C, 0x669D, 0xBA5D, 0x699C, 0xBA5E, 0x69A8, 0xBA5F, 0x6995, 0xBA60, 0x69C1, + 0xBA61, 0x69AE, 0xBA62, 0x69D3, 0xBA63, 0x69CB, 0xBA64, 0x699B, 0xBA65, 0x69B7, 0xBA66, 0x69BB, 0xBA67, 0x69AB, 0xBA68, 0x69B4, + 0xBA69, 0x69D0, 0xBA6A, 0x69CD, 0xBA6B, 0x69AD, 0xBA6C, 0x69CC, 0xBA6D, 0x69A6, 0xBA6E, 0x69C3, 0xBA6F, 0x69A3, 0xBA70, 0x6B49, + 0xBA71, 0x6B4C, 0xBA72, 0x6C33, 0xBA73, 0x6F33, 0xBA74, 0x6F14, 0xBA75, 0x6EFE, 0xBA76, 0x6F13, 0xBA77, 0x6EF4, 0xBA78, 0x6F29, + 0xBA79, 0x6F3E, 0xBA7A, 0x6F20, 0xBA7B, 0x6F2C, 0xBA7C, 0x6F0F, 0xBA7D, 0x6F02, 0xBA7E, 0x6F22, 0xBAA1, 0x6EFF, 0xBAA2, 0x6EEF, + 0xBAA3, 0x6F06, 0xBAA4, 0x6F31, 0xBAA5, 0x6F38, 0xBAA6, 0x6F32, 0xBAA7, 0x6F23, 0xBAA8, 0x6F15, 0xBAA9, 0x6F2B, 0xBAAA, 0x6F2F, + 0xBAAB, 0x6F88, 0xBAAC, 0x6F2A, 0xBAAD, 0x6EEC, 0xBAAE, 0x6F01, 0xBAAF, 0x6EF2, 0xBAB0, 0x6ECC, 0xBAB1, 0x6EF7, 0xBAB2, 0x7194, + 0xBAB3, 0x7199, 0xBAB4, 0x717D, 0xBAB5, 0x718A, 0xBAB6, 0x7184, 0xBAB7, 0x7192, 0xBAB8, 0x723E, 0xBAB9, 0x7292, 0xBABA, 0x7296, + 0xBABB, 0x7344, 0xBABC, 0x7350, 0xBABD, 0x7464, 0xBABE, 0x7463, 0xBABF, 0x746A, 0xBAC0, 0x7470, 0xBAC1, 0x746D, 0xBAC2, 0x7504, + 0xBAC3, 0x7591, 0xBAC4, 0x7627, 0xBAC5, 0x760D, 0xBAC6, 0x760B, 0xBAC7, 0x7609, 0xBAC8, 0x7613, 0xBAC9, 0x76E1, 0xBACA, 0x76E3, + 0xBACB, 0x7784, 0xBACC, 0x777D, 0xBACD, 0x777F, 0xBACE, 0x7761, 0xBACF, 0x78C1, 0xBAD0, 0x789F, 0xBAD1, 0x78A7, 0xBAD2, 0x78B3, + 0xBAD3, 0x78A9, 0xBAD4, 0x78A3, 0xBAD5, 0x798E, 0xBAD6, 0x798F, 0xBAD7, 0x798D, 0xBAD8, 0x7A2E, 0xBAD9, 0x7A31, 0xBADA, 0x7AAA, + 0xBADB, 0x7AA9, 0xBADC, 0x7AED, 0xBADD, 0x7AEF, 0xBADE, 0x7BA1, 0xBADF, 0x7B95, 0xBAE0, 0x7B8B, 0xBAE1, 0x7B75, 0xBAE2, 0x7B97, + 0xBAE3, 0x7B9D, 0xBAE4, 0x7B94, 0xBAE5, 0x7B8F, 0xBAE6, 0x7BB8, 0xBAE7, 0x7B87, 0xBAE8, 0x7B84, 0xBAE9, 0x7CB9, 0xBAEA, 0x7CBD, + 0xBAEB, 0x7CBE, 0xBAEC, 0x7DBB, 0xBAED, 0x7DB0, 0xBAEE, 0x7D9C, 0xBAEF, 0x7DBD, 0xBAF0, 0x7DBE, 0xBAF1, 0x7DA0, 0xBAF2, 0x7DCA, + 0xBAF3, 0x7DB4, 0xBAF4, 0x7DB2, 0xBAF5, 0x7DB1, 0xBAF6, 0x7DBA, 0xBAF7, 0x7DA2, 0xBAF8, 0x7DBF, 0xBAF9, 0x7DB5, 0xBAFA, 0x7DB8, + 0xBAFB, 0x7DAD, 0xBAFC, 0x7DD2, 0xBAFD, 0x7DC7, 0xBAFE, 0x7DAC, 0xBB40, 0x7F70, 0xBB41, 0x7FE0, 0xBB42, 0x7FE1, 0xBB43, 0x7FDF, + 0xBB44, 0x805E, 0xBB45, 0x805A, 0xBB46, 0x8087, 0xBB47, 0x8150, 0xBB48, 0x8180, 0xBB49, 0x818F, 0xBB4A, 0x8188, 0xBB4B, 0x818A, + 0xBB4C, 0x817F, 0xBB4D, 0x8182, 0xBB4E, 0x81E7, 0xBB4F, 0x81FA, 0xBB50, 0x8207, 0xBB51, 0x8214, 0xBB52, 0x821E, 0xBB53, 0x824B, + 0xBB54, 0x84C9, 0xBB55, 0x84BF, 0xBB56, 0x84C6, 0xBB57, 0x84C4, 0xBB58, 0x8499, 0xBB59, 0x849E, 0xBB5A, 0x84B2, 0xBB5B, 0x849C, + 0xBB5C, 0x84CB, 0xBB5D, 0x84B8, 0xBB5E, 0x84C0, 0xBB5F, 0x84D3, 0xBB60, 0x8490, 0xBB61, 0x84BC, 0xBB62, 0x84D1, 0xBB63, 0x84CA, + 0xBB64, 0x873F, 0xBB65, 0x871C, 0xBB66, 0x873B, 0xBB67, 0x8722, 0xBB68, 0x8725, 0xBB69, 0x8734, 0xBB6A, 0x8718, 0xBB6B, 0x8755, + 0xBB6C, 0x8737, 0xBB6D, 0x8729, 0xBB6E, 0x88F3, 0xBB6F, 0x8902, 0xBB70, 0x88F4, 0xBB71, 0x88F9, 0xBB72, 0x88F8, 0xBB73, 0x88FD, + 0xBB74, 0x88E8, 0xBB75, 0x891A, 0xBB76, 0x88EF, 0xBB77, 0x8AA6, 0xBB78, 0x8A8C, 0xBB79, 0x8A9E, 0xBB7A, 0x8AA3, 0xBB7B, 0x8A8D, + 0xBB7C, 0x8AA1, 0xBB7D, 0x8A93, 0xBB7E, 0x8AA4, 0xBBA1, 0x8AAA, 0xBBA2, 0x8AA5, 0xBBA3, 0x8AA8, 0xBBA4, 0x8A98, 0xBBA5, 0x8A91, + 0xBBA6, 0x8A9A, 0xBBA7, 0x8AA7, 0xBBA8, 0x8C6A, 0xBBA9, 0x8C8D, 0xBBAA, 0x8C8C, 0xBBAB, 0x8CD3, 0xBBAC, 0x8CD1, 0xBBAD, 0x8CD2, + 0xBBAE, 0x8D6B, 0xBBAF, 0x8D99, 0xBBB0, 0x8D95, 0xBBB1, 0x8DFC, 0xBBB2, 0x8F14, 0xBBB3, 0x8F12, 0xBBB4, 0x8F15, 0xBBB5, 0x8F13, + 0xBBB6, 0x8FA3, 0xBBB7, 0x9060, 0xBBB8, 0x9058, 0xBBB9, 0x905C, 0xBBBA, 0x9063, 0xBBBB, 0x9059, 0xBBBC, 0x905E, 0xBBBD, 0x9062, + 0xBBBE, 0x905D, 0xBBBF, 0x905B, 0xBBC0, 0x9119, 0xBBC1, 0x9118, 0xBBC2, 0x911E, 0xBBC3, 0x9175, 0xBBC4, 0x9178, 0xBBC5, 0x9177, + 0xBBC6, 0x9174, 0xBBC7, 0x9278, 0xBBC8, 0x9280, 0xBBC9, 0x9285, 0xBBCA, 0x9298, 0xBBCB, 0x9296, 0xBBCC, 0x927B, 0xBBCD, 0x9293, + 0xBBCE, 0x929C, 0xBBCF, 0x92A8, 0xBBD0, 0x927C, 0xBBD1, 0x9291, 0xBBD2, 0x95A1, 0xBBD3, 0x95A8, 0xBBD4, 0x95A9, 0xBBD5, 0x95A3, + 0xBBD6, 0x95A5, 0xBBD7, 0x95A4, 0xBBD8, 0x9699, 0xBBD9, 0x969C, 0xBBDA, 0x969B, 0xBBDB, 0x96CC, 0xBBDC, 0x96D2, 0xBBDD, 0x9700, + 0xBBDE, 0x977C, 0xBBDF, 0x9785, 0xBBE0, 0x97F6, 0xBBE1, 0x9817, 0xBBE2, 0x9818, 0xBBE3, 0x98AF, 0xBBE4, 0x98B1, 0xBBE5, 0x9903, + 0xBBE6, 0x9905, 0xBBE7, 0x990C, 0xBBE8, 0x9909, 0xBBE9, 0x99C1, 0xBBEA, 0x9AAF, 0xBBEB, 0x9AB0, 0xBBEC, 0x9AE6, 0xBBED, 0x9B41, + 0xBBEE, 0x9B42, 0xBBEF, 0x9CF4, 0xBBF0, 0x9CF6, 0xBBF1, 0x9CF3, 0xBBF2, 0x9EBC, 0xBBF3, 0x9F3B, 0xBBF4, 0x9F4A, 0xBBF5, 0x5104, + 0xBBF6, 0x5100, 0xBBF7, 0x50FB, 0xBBF8, 0x50F5, 0xBBF9, 0x50F9, 0xBBFA, 0x5102, 0xBBFB, 0x5108, 0xBBFC, 0x5109, 0xBBFD, 0x5105, + 0xBBFE, 0x51DC, 0xBC40, 0x5287, 0xBC41, 0x5288, 0xBC42, 0x5289, 0xBC43, 0x528D, 0xBC44, 0x528A, 0xBC45, 0x52F0, 0xBC46, 0x53B2, + 0xBC47, 0x562E, 0xBC48, 0x563B, 0xBC49, 0x5639, 0xBC4A, 0x5632, 0xBC4B, 0x563F, 0xBC4C, 0x5634, 0xBC4D, 0x5629, 0xBC4E, 0x5653, + 0xBC4F, 0x564E, 0xBC50, 0x5657, 0xBC51, 0x5674, 0xBC52, 0x5636, 0xBC53, 0x562F, 0xBC54, 0x5630, 0xBC55, 0x5880, 0xBC56, 0x589F, + 0xBC57, 0x589E, 0xBC58, 0x58B3, 0xBC59, 0x589C, 0xBC5A, 0x58AE, 0xBC5B, 0x58A9, 0xBC5C, 0x58A6, 0xBC5D, 0x596D, 0xBC5E, 0x5B09, + 0xBC5F, 0x5AFB, 0xBC60, 0x5B0B, 0xBC61, 0x5AF5, 0xBC62, 0x5B0C, 0xBC63, 0x5B08, 0xBC64, 0x5BEE, 0xBC65, 0x5BEC, 0xBC66, 0x5BE9, + 0xBC67, 0x5BEB, 0xBC68, 0x5C64, 0xBC69, 0x5C65, 0xBC6A, 0x5D9D, 0xBC6B, 0x5D94, 0xBC6C, 0x5E62, 0xBC6D, 0x5E5F, 0xBC6E, 0x5E61, + 0xBC6F, 0x5EE2, 0xBC70, 0x5EDA, 0xBC71, 0x5EDF, 0xBC72, 0x5EDD, 0xBC73, 0x5EE3, 0xBC74, 0x5EE0, 0xBC75, 0x5F48, 0xBC76, 0x5F71, + 0xBC77, 0x5FB7, 0xBC78, 0x5FB5, 0xBC79, 0x6176, 0xBC7A, 0x6167, 0xBC7B, 0x616E, 0xBC7C, 0x615D, 0xBC7D, 0x6155, 0xBC7E, 0x6182, + 0xBCA1, 0x617C, 0xBCA2, 0x6170, 0xBCA3, 0x616B, 0xBCA4, 0x617E, 0xBCA5, 0x61A7, 0xBCA6, 0x6190, 0xBCA7, 0x61AB, 0xBCA8, 0x618E, + 0xBCA9, 0x61AC, 0xBCAA, 0x619A, 0xBCAB, 0x61A4, 0xBCAC, 0x6194, 0xBCAD, 0x61AE, 0xBCAE, 0x622E, 0xBCAF, 0x6469, 0xBCB0, 0x646F, + 0xBCB1, 0x6479, 0xBCB2, 0x649E, 0xBCB3, 0x64B2, 0xBCB4, 0x6488, 0xBCB5, 0x6490, 0xBCB6, 0x64B0, 0xBCB7, 0x64A5, 0xBCB8, 0x6493, + 0xBCB9, 0x6495, 0xBCBA, 0x64A9, 0xBCBB, 0x6492, 0xBCBC, 0x64AE, 0xBCBD, 0x64AD, 0xBCBE, 0x64AB, 0xBCBF, 0x649A, 0xBCC0, 0x64AC, + 0xBCC1, 0x6499, 0xBCC2, 0x64A2, 0xBCC3, 0x64B3, 0xBCC4, 0x6575, 0xBCC5, 0x6577, 0xBCC6, 0x6578, 0xBCC7, 0x66AE, 0xBCC8, 0x66AB, + 0xBCC9, 0x66B4, 0xBCCA, 0x66B1, 0xBCCB, 0x6A23, 0xBCCC, 0x6A1F, 0xBCCD, 0x69E8, 0xBCCE, 0x6A01, 0xBCCF, 0x6A1E, 0xBCD0, 0x6A19, + 0xBCD1, 0x69FD, 0xBCD2, 0x6A21, 0xBCD3, 0x6A13, 0xBCD4, 0x6A0A, 0xBCD5, 0x69F3, 0xBCD6, 0x6A02, 0xBCD7, 0x6A05, 0xBCD8, 0x69ED, + 0xBCD9, 0x6A11, 0xBCDA, 0x6B50, 0xBCDB, 0x6B4E, 0xBCDC, 0x6BA4, 0xBCDD, 0x6BC5, 0xBCDE, 0x6BC6, 0xBCDF, 0x6F3F, 0xBCE0, 0x6F7C, + 0xBCE1, 0x6F84, 0xBCE2, 0x6F51, 0xBCE3, 0x6F66, 0xBCE4, 0x6F54, 0xBCE5, 0x6F86, 0xBCE6, 0x6F6D, 0xBCE7, 0x6F5B, 0xBCE8, 0x6F78, + 0xBCE9, 0x6F6E, 0xBCEA, 0x6F8E, 0xBCEB, 0x6F7A, 0xBCEC, 0x6F70, 0xBCED, 0x6F64, 0xBCEE, 0x6F97, 0xBCEF, 0x6F58, 0xBCF0, 0x6ED5, + 0xBCF1, 0x6F6F, 0xBCF2, 0x6F60, 0xBCF3, 0x6F5F, 0xBCF4, 0x719F, 0xBCF5, 0x71AC, 0xBCF6, 0x71B1, 0xBCF7, 0x71A8, 0xBCF8, 0x7256, + 0xBCF9, 0x729B, 0xBCFA, 0x734E, 0xBCFB, 0x7357, 0xBCFC, 0x7469, 0xBCFD, 0x748B, 0xBCFE, 0x7483, 0xBD40, 0x747E, 0xBD41, 0x7480, + 0xBD42, 0x757F, 0xBD43, 0x7620, 0xBD44, 0x7629, 0xBD45, 0x761F, 0xBD46, 0x7624, 0xBD47, 0x7626, 0xBD48, 0x7621, 0xBD49, 0x7622, + 0xBD4A, 0x769A, 0xBD4B, 0x76BA, 0xBD4C, 0x76E4, 0xBD4D, 0x778E, 0xBD4E, 0x7787, 0xBD4F, 0x778C, 0xBD50, 0x7791, 0xBD51, 0x778B, + 0xBD52, 0x78CB, 0xBD53, 0x78C5, 0xBD54, 0x78BA, 0xBD55, 0x78CA, 0xBD56, 0x78BE, 0xBD57, 0x78D5, 0xBD58, 0x78BC, 0xBD59, 0x78D0, + 0xBD5A, 0x7A3F, 0xBD5B, 0x7A3C, 0xBD5C, 0x7A40, 0xBD5D, 0x7A3D, 0xBD5E, 0x7A37, 0xBD5F, 0x7A3B, 0xBD60, 0x7AAF, 0xBD61, 0x7AAE, + 0xBD62, 0x7BAD, 0xBD63, 0x7BB1, 0xBD64, 0x7BC4, 0xBD65, 0x7BB4, 0xBD66, 0x7BC6, 0xBD67, 0x7BC7, 0xBD68, 0x7BC1, 0xBD69, 0x7BA0, + 0xBD6A, 0x7BCC, 0xBD6B, 0x7CCA, 0xBD6C, 0x7DE0, 0xBD6D, 0x7DF4, 0xBD6E, 0x7DEF, 0xBD6F, 0x7DFB, 0xBD70, 0x7DD8, 0xBD71, 0x7DEC, + 0xBD72, 0x7DDD, 0xBD73, 0x7DE8, 0xBD74, 0x7DE3, 0xBD75, 0x7DDA, 0xBD76, 0x7DDE, 0xBD77, 0x7DE9, 0xBD78, 0x7D9E, 0xBD79, 0x7DD9, + 0xBD7A, 0x7DF2, 0xBD7B, 0x7DF9, 0xBD7C, 0x7F75, 0xBD7D, 0x7F77, 0xBD7E, 0x7FAF, 0xBDA1, 0x7FE9, 0xBDA2, 0x8026, 0xBDA3, 0x819B, + 0xBDA4, 0x819C, 0xBDA5, 0x819D, 0xBDA6, 0x81A0, 0xBDA7, 0x819A, 0xBDA8, 0x8198, 0xBDA9, 0x8517, 0xBDAA, 0x853D, 0xBDAB, 0x851A, + 0xBDAC, 0x84EE, 0xBDAD, 0x852C, 0xBDAE, 0x852D, 0xBDAF, 0x8513, 0xBDB0, 0x8511, 0xBDB1, 0x8523, 0xBDB2, 0x8521, 0xBDB3, 0x8514, + 0xBDB4, 0x84EC, 0xBDB5, 0x8525, 0xBDB6, 0x84FF, 0xBDB7, 0x8506, 0xBDB8, 0x8782, 0xBDB9, 0x8774, 0xBDBA, 0x8776, 0xBDBB, 0x8760, + 0xBDBC, 0x8766, 0xBDBD, 0x8778, 0xBDBE, 0x8768, 0xBDBF, 0x8759, 0xBDC0, 0x8757, 0xBDC1, 0x874C, 0xBDC2, 0x8753, 0xBDC3, 0x885B, + 0xBDC4, 0x885D, 0xBDC5, 0x8910, 0xBDC6, 0x8907, 0xBDC7, 0x8912, 0xBDC8, 0x8913, 0xBDC9, 0x8915, 0xBDCA, 0x890A, 0xBDCB, 0x8ABC, + 0xBDCC, 0x8AD2, 0xBDCD, 0x8AC7, 0xBDCE, 0x8AC4, 0xBDCF, 0x8A95, 0xBDD0, 0x8ACB, 0xBDD1, 0x8AF8, 0xBDD2, 0x8AB2, 0xBDD3, 0x8AC9, + 0xBDD4, 0x8AC2, 0xBDD5, 0x8ABF, 0xBDD6, 0x8AB0, 0xBDD7, 0x8AD6, 0xBDD8, 0x8ACD, 0xBDD9, 0x8AB6, 0xBDDA, 0x8AB9, 0xBDDB, 0x8ADB, + 0xBDDC, 0x8C4C, 0xBDDD, 0x8C4E, 0xBDDE, 0x8C6C, 0xBDDF, 0x8CE0, 0xBDE0, 0x8CDE, 0xBDE1, 0x8CE6, 0xBDE2, 0x8CE4, 0xBDE3, 0x8CEC, + 0xBDE4, 0x8CED, 0xBDE5, 0x8CE2, 0xBDE6, 0x8CE3, 0xBDE7, 0x8CDC, 0xBDE8, 0x8CEA, 0xBDE9, 0x8CE1, 0xBDEA, 0x8D6D, 0xBDEB, 0x8D9F, + 0xBDEC, 0x8DA3, 0xBDED, 0x8E2B, 0xBDEE, 0x8E10, 0xBDEF, 0x8E1D, 0xBDF0, 0x8E22, 0xBDF1, 0x8E0F, 0xBDF2, 0x8E29, 0xBDF3, 0x8E1F, + 0xBDF4, 0x8E21, 0xBDF5, 0x8E1E, 0xBDF6, 0x8EBA, 0xBDF7, 0x8F1D, 0xBDF8, 0x8F1B, 0xBDF9, 0x8F1F, 0xBDFA, 0x8F29, 0xBDFB, 0x8F26, + 0xBDFC, 0x8F2A, 0xBDFD, 0x8F1C, 0xBDFE, 0x8F1E, 0xBE40, 0x8F25, 0xBE41, 0x9069, 0xBE42, 0x906E, 0xBE43, 0x9068, 0xBE44, 0x906D, + 0xBE45, 0x9077, 0xBE46, 0x9130, 0xBE47, 0x912D, 0xBE48, 0x9127, 0xBE49, 0x9131, 0xBE4A, 0x9187, 0xBE4B, 0x9189, 0xBE4C, 0x918B, + 0xBE4D, 0x9183, 0xBE4E, 0x92C5, 0xBE4F, 0x92BB, 0xBE50, 0x92B7, 0xBE51, 0x92EA, 0xBE52, 0x92AC, 0xBE53, 0x92E4, 0xBE54, 0x92C1, + 0xBE55, 0x92B3, 0xBE56, 0x92BC, 0xBE57, 0x92D2, 0xBE58, 0x92C7, 0xBE59, 0x92F0, 0xBE5A, 0x92B2, 0xBE5B, 0x95AD, 0xBE5C, 0x95B1, + 0xBE5D, 0x9704, 0xBE5E, 0x9706, 0xBE5F, 0x9707, 0xBE60, 0x9709, 0xBE61, 0x9760, 0xBE62, 0x978D, 0xBE63, 0x978B, 0xBE64, 0x978F, + 0xBE65, 0x9821, 0xBE66, 0x982B, 0xBE67, 0x981C, 0xBE68, 0x98B3, 0xBE69, 0x990A, 0xBE6A, 0x9913, 0xBE6B, 0x9912, 0xBE6C, 0x9918, + 0xBE6D, 0x99DD, 0xBE6E, 0x99D0, 0xBE6F, 0x99DF, 0xBE70, 0x99DB, 0xBE71, 0x99D1, 0xBE72, 0x99D5, 0xBE73, 0x99D2, 0xBE74, 0x99D9, + 0xBE75, 0x9AB7, 0xBE76, 0x9AEE, 0xBE77, 0x9AEF, 0xBE78, 0x9B27, 0xBE79, 0x9B45, 0xBE7A, 0x9B44, 0xBE7B, 0x9B77, 0xBE7C, 0x9B6F, + 0xBE7D, 0x9D06, 0xBE7E, 0x9D09, 0xBEA1, 0x9D03, 0xBEA2, 0x9EA9, 0xBEA3, 0x9EBE, 0xBEA4, 0x9ECE, 0xBEA5, 0x58A8, 0xBEA6, 0x9F52, + 0xBEA7, 0x5112, 0xBEA8, 0x5118, 0xBEA9, 0x5114, 0xBEAA, 0x5110, 0xBEAB, 0x5115, 0xBEAC, 0x5180, 0xBEAD, 0x51AA, 0xBEAE, 0x51DD, + 0xBEAF, 0x5291, 0xBEB0, 0x5293, 0xBEB1, 0x52F3, 0xBEB2, 0x5659, 0xBEB3, 0x566B, 0xBEB4, 0x5679, 0xBEB5, 0x5669, 0xBEB6, 0x5664, + 0xBEB7, 0x5678, 0xBEB8, 0x566A, 0xBEB9, 0x5668, 0xBEBA, 0x5665, 0xBEBB, 0x5671, 0xBEBC, 0x566F, 0xBEBD, 0x566C, 0xBEBE, 0x5662, + 0xBEBF, 0x5676, 0xBEC0, 0x58C1, 0xBEC1, 0x58BE, 0xBEC2, 0x58C7, 0xBEC3, 0x58C5, 0xBEC4, 0x596E, 0xBEC5, 0x5B1D, 0xBEC6, 0x5B34, + 0xBEC7, 0x5B78, 0xBEC8, 0x5BF0, 0xBEC9, 0x5C0E, 0xBECA, 0x5F4A, 0xBECB, 0x61B2, 0xBECC, 0x6191, 0xBECD, 0x61A9, 0xBECE, 0x618A, + 0xBECF, 0x61CD, 0xBED0, 0x61B6, 0xBED1, 0x61BE, 0xBED2, 0x61CA, 0xBED3, 0x61C8, 0xBED4, 0x6230, 0xBED5, 0x64C5, 0xBED6, 0x64C1, + 0xBED7, 0x64CB, 0xBED8, 0x64BB, 0xBED9, 0x64BC, 0xBEDA, 0x64DA, 0xBEDB, 0x64C4, 0xBEDC, 0x64C7, 0xBEDD, 0x64C2, 0xBEDE, 0x64CD, + 0xBEDF, 0x64BF, 0xBEE0, 0x64D2, 0xBEE1, 0x64D4, 0xBEE2, 0x64BE, 0xBEE3, 0x6574, 0xBEE4, 0x66C6, 0xBEE5, 0x66C9, 0xBEE6, 0x66B9, + 0xBEE7, 0x66C4, 0xBEE8, 0x66C7, 0xBEE9, 0x66B8, 0xBEEA, 0x6A3D, 0xBEEB, 0x6A38, 0xBEEC, 0x6A3A, 0xBEED, 0x6A59, 0xBEEE, 0x6A6B, + 0xBEEF, 0x6A58, 0xBEF0, 0x6A39, 0xBEF1, 0x6A44, 0xBEF2, 0x6A62, 0xBEF3, 0x6A61, 0xBEF4, 0x6A4B, 0xBEF5, 0x6A47, 0xBEF6, 0x6A35, + 0xBEF7, 0x6A5F, 0xBEF8, 0x6A48, 0xBEF9, 0x6B59, 0xBEFA, 0x6B77, 0xBEFB, 0x6C05, 0xBEFC, 0x6FC2, 0xBEFD, 0x6FB1, 0xBEFE, 0x6FA1, + 0xBF40, 0x6FC3, 0xBF41, 0x6FA4, 0xBF42, 0x6FC1, 0xBF43, 0x6FA7, 0xBF44, 0x6FB3, 0xBF45, 0x6FC0, 0xBF46, 0x6FB9, 0xBF47, 0x6FB6, + 0xBF48, 0x6FA6, 0xBF49, 0x6FA0, 0xBF4A, 0x6FB4, 0xBF4B, 0x71BE, 0xBF4C, 0x71C9, 0xBF4D, 0x71D0, 0xBF4E, 0x71D2, 0xBF4F, 0x71C8, + 0xBF50, 0x71D5, 0xBF51, 0x71B9, 0xBF52, 0x71CE, 0xBF53, 0x71D9, 0xBF54, 0x71DC, 0xBF55, 0x71C3, 0xBF56, 0x71C4, 0xBF57, 0x7368, + 0xBF58, 0x749C, 0xBF59, 0x74A3, 0xBF5A, 0x7498, 0xBF5B, 0x749F, 0xBF5C, 0x749E, 0xBF5D, 0x74E2, 0xBF5E, 0x750C, 0xBF5F, 0x750D, + 0xBF60, 0x7634, 0xBF61, 0x7638, 0xBF62, 0x763A, 0xBF63, 0x76E7, 0xBF64, 0x76E5, 0xBF65, 0x77A0, 0xBF66, 0x779E, 0xBF67, 0x779F, + 0xBF68, 0x77A5, 0xBF69, 0x78E8, 0xBF6A, 0x78DA, 0xBF6B, 0x78EC, 0xBF6C, 0x78E7, 0xBF6D, 0x79A6, 0xBF6E, 0x7A4D, 0xBF6F, 0x7A4E, + 0xBF70, 0x7A46, 0xBF71, 0x7A4C, 0xBF72, 0x7A4B, 0xBF73, 0x7ABA, 0xBF74, 0x7BD9, 0xBF75, 0x7C11, 0xBF76, 0x7BC9, 0xBF77, 0x7BE4, + 0xBF78, 0x7BDB, 0xBF79, 0x7BE1, 0xBF7A, 0x7BE9, 0xBF7B, 0x7BE6, 0xBF7C, 0x7CD5, 0xBF7D, 0x7CD6, 0xBF7E, 0x7E0A, 0xBFA1, 0x7E11, + 0xBFA2, 0x7E08, 0xBFA3, 0x7E1B, 0xBFA4, 0x7E23, 0xBFA5, 0x7E1E, 0xBFA6, 0x7E1D, 0xBFA7, 0x7E09, 0xBFA8, 0x7E10, 0xBFA9, 0x7F79, + 0xBFAA, 0x7FB2, 0xBFAB, 0x7FF0, 0xBFAC, 0x7FF1, 0xBFAD, 0x7FEE, 0xBFAE, 0x8028, 0xBFAF, 0x81B3, 0xBFB0, 0x81A9, 0xBFB1, 0x81A8, + 0xBFB2, 0x81FB, 0xBFB3, 0x8208, 0xBFB4, 0x8258, 0xBFB5, 0x8259, 0xBFB6, 0x854A, 0xBFB7, 0x8559, 0xBFB8, 0x8548, 0xBFB9, 0x8568, + 0xBFBA, 0x8569, 0xBFBB, 0x8543, 0xBFBC, 0x8549, 0xBFBD, 0x856D, 0xBFBE, 0x856A, 0xBFBF, 0x855E, 0xBFC0, 0x8783, 0xBFC1, 0x879F, + 0xBFC2, 0x879E, 0xBFC3, 0x87A2, 0xBFC4, 0x878D, 0xBFC5, 0x8861, 0xBFC6, 0x892A, 0xBFC7, 0x8932, 0xBFC8, 0x8925, 0xBFC9, 0x892B, + 0xBFCA, 0x8921, 0xBFCB, 0x89AA, 0xBFCC, 0x89A6, 0xBFCD, 0x8AE6, 0xBFCE, 0x8AFA, 0xBFCF, 0x8AEB, 0xBFD0, 0x8AF1, 0xBFD1, 0x8B00, + 0xBFD2, 0x8ADC, 0xBFD3, 0x8AE7, 0xBFD4, 0x8AEE, 0xBFD5, 0x8AFE, 0xBFD6, 0x8B01, 0xBFD7, 0x8B02, 0xBFD8, 0x8AF7, 0xBFD9, 0x8AED, + 0xBFDA, 0x8AF3, 0xBFDB, 0x8AF6, 0xBFDC, 0x8AFC, 0xBFDD, 0x8C6B, 0xBFDE, 0x8C6D, 0xBFDF, 0x8C93, 0xBFE0, 0x8CF4, 0xBFE1, 0x8E44, + 0xBFE2, 0x8E31, 0xBFE3, 0x8E34, 0xBFE4, 0x8E42, 0xBFE5, 0x8E39, 0xBFE6, 0x8E35, 0xBFE7, 0x8F3B, 0xBFE8, 0x8F2F, 0xBFE9, 0x8F38, + 0xBFEA, 0x8F33, 0xBFEB, 0x8FA8, 0xBFEC, 0x8FA6, 0xBFED, 0x9075, 0xBFEE, 0x9074, 0xBFEF, 0x9078, 0xBFF0, 0x9072, 0xBFF1, 0x907C, + 0xBFF2, 0x907A, 0xBFF3, 0x9134, 0xBFF4, 0x9192, 0xBFF5, 0x9320, 0xBFF6, 0x9336, 0xBFF7, 0x92F8, 0xBFF8, 0x9333, 0xBFF9, 0x932F, + 0xBFFA, 0x9322, 0xBFFB, 0x92FC, 0xBFFC, 0x932B, 0xBFFD, 0x9304, 0xBFFE, 0x931A, 0xC040, 0x9310, 0xC041, 0x9326, 0xC042, 0x9321, + 0xC043, 0x9315, 0xC044, 0x932E, 0xC045, 0x9319, 0xC046, 0x95BB, 0xC047, 0x96A7, 0xC048, 0x96A8, 0xC049, 0x96AA, 0xC04A, 0x96D5, + 0xC04B, 0x970E, 0xC04C, 0x9711, 0xC04D, 0x9716, 0xC04E, 0x970D, 0xC04F, 0x9713, 0xC050, 0x970F, 0xC051, 0x975B, 0xC052, 0x975C, + 0xC053, 0x9766, 0xC054, 0x9798, 0xC055, 0x9830, 0xC056, 0x9838, 0xC057, 0x983B, 0xC058, 0x9837, 0xC059, 0x982D, 0xC05A, 0x9839, + 0xC05B, 0x9824, 0xC05C, 0x9910, 0xC05D, 0x9928, 0xC05E, 0x991E, 0xC05F, 0x991B, 0xC060, 0x9921, 0xC061, 0x991A, 0xC062, 0x99ED, + 0xC063, 0x99E2, 0xC064, 0x99F1, 0xC065, 0x9AB8, 0xC066, 0x9ABC, 0xC067, 0x9AFB, 0xC068, 0x9AED, 0xC069, 0x9B28, 0xC06A, 0x9B91, + 0xC06B, 0x9D15, 0xC06C, 0x9D23, 0xC06D, 0x9D26, 0xC06E, 0x9D28, 0xC06F, 0x9D12, 0xC070, 0x9D1B, 0xC071, 0x9ED8, 0xC072, 0x9ED4, + 0xC073, 0x9F8D, 0xC074, 0x9F9C, 0xC075, 0x512A, 0xC076, 0x511F, 0xC077, 0x5121, 0xC078, 0x5132, 0xC079, 0x52F5, 0xC07A, 0x568E, + 0xC07B, 0x5680, 0xC07C, 0x5690, 0xC07D, 0x5685, 0xC07E, 0x5687, 0xC0A1, 0x568F, 0xC0A2, 0x58D5, 0xC0A3, 0x58D3, 0xC0A4, 0x58D1, + 0xC0A5, 0x58CE, 0xC0A6, 0x5B30, 0xC0A7, 0x5B2A, 0xC0A8, 0x5B24, 0xC0A9, 0x5B7A, 0xC0AA, 0x5C37, 0xC0AB, 0x5C68, 0xC0AC, 0x5DBC, + 0xC0AD, 0x5DBA, 0xC0AE, 0x5DBD, 0xC0AF, 0x5DB8, 0xC0B0, 0x5E6B, 0xC0B1, 0x5F4C, 0xC0B2, 0x5FBD, 0xC0B3, 0x61C9, 0xC0B4, 0x61C2, + 0xC0B5, 0x61C7, 0xC0B6, 0x61E6, 0xC0B7, 0x61CB, 0xC0B8, 0x6232, 0xC0B9, 0x6234, 0xC0BA, 0x64CE, 0xC0BB, 0x64CA, 0xC0BC, 0x64D8, + 0xC0BD, 0x64E0, 0xC0BE, 0x64F0, 0xC0BF, 0x64E6, 0xC0C0, 0x64EC, 0xC0C1, 0x64F1, 0xC0C2, 0x64E2, 0xC0C3, 0x64ED, 0xC0C4, 0x6582, + 0xC0C5, 0x6583, 0xC0C6, 0x66D9, 0xC0C7, 0x66D6, 0xC0C8, 0x6A80, 0xC0C9, 0x6A94, 0xC0CA, 0x6A84, 0xC0CB, 0x6AA2, 0xC0CC, 0x6A9C, + 0xC0CD, 0x6ADB, 0xC0CE, 0x6AA3, 0xC0CF, 0x6A7E, 0xC0D0, 0x6A97, 0xC0D1, 0x6A90, 0xC0D2, 0x6AA0, 0xC0D3, 0x6B5C, 0xC0D4, 0x6BAE, + 0xC0D5, 0x6BDA, 0xC0D6, 0x6C08, 0xC0D7, 0x6FD8, 0xC0D8, 0x6FF1, 0xC0D9, 0x6FDF, 0xC0DA, 0x6FE0, 0xC0DB, 0x6FDB, 0xC0DC, 0x6FE4, + 0xC0DD, 0x6FEB, 0xC0DE, 0x6FEF, 0xC0DF, 0x6F80, 0xC0E0, 0x6FEC, 0xC0E1, 0x6FE1, 0xC0E2, 0x6FE9, 0xC0E3, 0x6FD5, 0xC0E4, 0x6FEE, + 0xC0E5, 0x6FF0, 0xC0E6, 0x71E7, 0xC0E7, 0x71DF, 0xC0E8, 0x71EE, 0xC0E9, 0x71E6, 0xC0EA, 0x71E5, 0xC0EB, 0x71ED, 0xC0EC, 0x71EC, + 0xC0ED, 0x71F4, 0xC0EE, 0x71E0, 0xC0EF, 0x7235, 0xC0F0, 0x7246, 0xC0F1, 0x7370, 0xC0F2, 0x7372, 0xC0F3, 0x74A9, 0xC0F4, 0x74B0, + 0xC0F5, 0x74A6, 0xC0F6, 0x74A8, 0xC0F7, 0x7646, 0xC0F8, 0x7642, 0xC0F9, 0x764C, 0xC0FA, 0x76EA, 0xC0FB, 0x77B3, 0xC0FC, 0x77AA, + 0xC0FD, 0x77B0, 0xC0FE, 0x77AC, 0xC140, 0x77A7, 0xC141, 0x77AD, 0xC142, 0x77EF, 0xC143, 0x78F7, 0xC144, 0x78FA, 0xC145, 0x78F4, + 0xC146, 0x78EF, 0xC147, 0x7901, 0xC148, 0x79A7, 0xC149, 0x79AA, 0xC14A, 0x7A57, 0xC14B, 0x7ABF, 0xC14C, 0x7C07, 0xC14D, 0x7C0D, + 0xC14E, 0x7BFE, 0xC14F, 0x7BF7, 0xC150, 0x7C0C, 0xC151, 0x7BE0, 0xC152, 0x7CE0, 0xC153, 0x7CDC, 0xC154, 0x7CDE, 0xC155, 0x7CE2, + 0xC156, 0x7CDF, 0xC157, 0x7CD9, 0xC158, 0x7CDD, 0xC159, 0x7E2E, 0xC15A, 0x7E3E, 0xC15B, 0x7E46, 0xC15C, 0x7E37, 0xC15D, 0x7E32, + 0xC15E, 0x7E43, 0xC15F, 0x7E2B, 0xC160, 0x7E3D, 0xC161, 0x7E31, 0xC162, 0x7E45, 0xC163, 0x7E41, 0xC164, 0x7E34, 0xC165, 0x7E39, + 0xC166, 0x7E48, 0xC167, 0x7E35, 0xC168, 0x7E3F, 0xC169, 0x7E2F, 0xC16A, 0x7F44, 0xC16B, 0x7FF3, 0xC16C, 0x7FFC, 0xC16D, 0x8071, + 0xC16E, 0x8072, 0xC16F, 0x8070, 0xC170, 0x806F, 0xC171, 0x8073, 0xC172, 0x81C6, 0xC173, 0x81C3, 0xC174, 0x81BA, 0xC175, 0x81C2, + 0xC176, 0x81C0, 0xC177, 0x81BF, 0xC178, 0x81BD, 0xC179, 0x81C9, 0xC17A, 0x81BE, 0xC17B, 0x81E8, 0xC17C, 0x8209, 0xC17D, 0x8271, + 0xC17E, 0x85AA, 0xC1A1, 0x8584, 0xC1A2, 0x857E, 0xC1A3, 0x859C, 0xC1A4, 0x8591, 0xC1A5, 0x8594, 0xC1A6, 0x85AF, 0xC1A7, 0x859B, + 0xC1A8, 0x8587, 0xC1A9, 0x85A8, 0xC1AA, 0x858A, 0xC1AB, 0x8667, 0xC1AC, 0x87C0, 0xC1AD, 0x87D1, 0xC1AE, 0x87B3, 0xC1AF, 0x87D2, + 0xC1B0, 0x87C6, 0xC1B1, 0x87AB, 0xC1B2, 0x87BB, 0xC1B3, 0x87BA, 0xC1B4, 0x87C8, 0xC1B5, 0x87CB, 0xC1B6, 0x893B, 0xC1B7, 0x8936, + 0xC1B8, 0x8944, 0xC1B9, 0x8938, 0xC1BA, 0x893D, 0xC1BB, 0x89AC, 0xC1BC, 0x8B0E, 0xC1BD, 0x8B17, 0xC1BE, 0x8B19, 0xC1BF, 0x8B1B, + 0xC1C0, 0x8B0A, 0xC1C1, 0x8B20, 0xC1C2, 0x8B1D, 0xC1C3, 0x8B04, 0xC1C4, 0x8B10, 0xC1C5, 0x8C41, 0xC1C6, 0x8C3F, 0xC1C7, 0x8C73, + 0xC1C8, 0x8CFA, 0xC1C9, 0x8CFD, 0xC1CA, 0x8CFC, 0xC1CB, 0x8CF8, 0xC1CC, 0x8CFB, 0xC1CD, 0x8DA8, 0xC1CE, 0x8E49, 0xC1CF, 0x8E4B, + 0xC1D0, 0x8E48, 0xC1D1, 0x8E4A, 0xC1D2, 0x8F44, 0xC1D3, 0x8F3E, 0xC1D4, 0x8F42, 0xC1D5, 0x8F45, 0xC1D6, 0x8F3F, 0xC1D7, 0x907F, + 0xC1D8, 0x907D, 0xC1D9, 0x9084, 0xC1DA, 0x9081, 0xC1DB, 0x9082, 0xC1DC, 0x9080, 0xC1DD, 0x9139, 0xC1DE, 0x91A3, 0xC1DF, 0x919E, + 0xC1E0, 0x919C, 0xC1E1, 0x934D, 0xC1E2, 0x9382, 0xC1E3, 0x9328, 0xC1E4, 0x9375, 0xC1E5, 0x934A, 0xC1E6, 0x9365, 0xC1E7, 0x934B, + 0xC1E8, 0x9318, 0xC1E9, 0x937E, 0xC1EA, 0x936C, 0xC1EB, 0x935B, 0xC1EC, 0x9370, 0xC1ED, 0x935A, 0xC1EE, 0x9354, 0xC1EF, 0x95CA, + 0xC1F0, 0x95CB, 0xC1F1, 0x95CC, 0xC1F2, 0x95C8, 0xC1F3, 0x95C6, 0xC1F4, 0x96B1, 0xC1F5, 0x96B8, 0xC1F6, 0x96D6, 0xC1F7, 0x971C, + 0xC1F8, 0x971E, 0xC1F9, 0x97A0, 0xC1FA, 0x97D3, 0xC1FB, 0x9846, 0xC1FC, 0x98B6, 0xC1FD, 0x9935, 0xC1FE, 0x9A01, 0xC240, 0x99FF, + 0xC241, 0x9BAE, 0xC242, 0x9BAB, 0xC243, 0x9BAA, 0xC244, 0x9BAD, 0xC245, 0x9D3B, 0xC246, 0x9D3F, 0xC247, 0x9E8B, 0xC248, 0x9ECF, + 0xC249, 0x9EDE, 0xC24A, 0x9EDC, 0xC24B, 0x9EDD, 0xC24C, 0x9EDB, 0xC24D, 0x9F3E, 0xC24E, 0x9F4B, 0xC24F, 0x53E2, 0xC250, 0x5695, + 0xC251, 0x56AE, 0xC252, 0x58D9, 0xC253, 0x58D8, 0xC254, 0x5B38, 0xC255, 0x5F5D, 0xC256, 0x61E3, 0xC257, 0x6233, 0xC258, 0x64F4, + 0xC259, 0x64F2, 0xC25A, 0x64FE, 0xC25B, 0x6506, 0xC25C, 0x64FA, 0xC25D, 0x64FB, 0xC25E, 0x64F7, 0xC25F, 0x65B7, 0xC260, 0x66DC, + 0xC261, 0x6726, 0xC262, 0x6AB3, 0xC263, 0x6AAC, 0xC264, 0x6AC3, 0xC265, 0x6ABB, 0xC266, 0x6AB8, 0xC267, 0x6AC2, 0xC268, 0x6AAE, + 0xC269, 0x6AAF, 0xC26A, 0x6B5F, 0xC26B, 0x6B78, 0xC26C, 0x6BAF, 0xC26D, 0x7009, 0xC26E, 0x700B, 0xC26F, 0x6FFE, 0xC270, 0x7006, + 0xC271, 0x6FFA, 0xC272, 0x7011, 0xC273, 0x700F, 0xC274, 0x71FB, 0xC275, 0x71FC, 0xC276, 0x71FE, 0xC277, 0x71F8, 0xC278, 0x7377, + 0xC279, 0x7375, 0xC27A, 0x74A7, 0xC27B, 0x74BF, 0xC27C, 0x7515, 0xC27D, 0x7656, 0xC27E, 0x7658, 0xC2A1, 0x7652, 0xC2A2, 0x77BD, + 0xC2A3, 0x77BF, 0xC2A4, 0x77BB, 0xC2A5, 0x77BC, 0xC2A6, 0x790E, 0xC2A7, 0x79AE, 0xC2A8, 0x7A61, 0xC2A9, 0x7A62, 0xC2AA, 0x7A60, + 0xC2AB, 0x7AC4, 0xC2AC, 0x7AC5, 0xC2AD, 0x7C2B, 0xC2AE, 0x7C27, 0xC2AF, 0x7C2A, 0xC2B0, 0x7C1E, 0xC2B1, 0x7C23, 0xC2B2, 0x7C21, + 0xC2B3, 0x7CE7, 0xC2B4, 0x7E54, 0xC2B5, 0x7E55, 0xC2B6, 0x7E5E, 0xC2B7, 0x7E5A, 0xC2B8, 0x7E61, 0xC2B9, 0x7E52, 0xC2BA, 0x7E59, + 0xC2BB, 0x7F48, 0xC2BC, 0x7FF9, 0xC2BD, 0x7FFB, 0xC2BE, 0x8077, 0xC2BF, 0x8076, 0xC2C0, 0x81CD, 0xC2C1, 0x81CF, 0xC2C2, 0x820A, + 0xC2C3, 0x85CF, 0xC2C4, 0x85A9, 0xC2C5, 0x85CD, 0xC2C6, 0x85D0, 0xC2C7, 0x85C9, 0xC2C8, 0x85B0, 0xC2C9, 0x85BA, 0xC2CA, 0x85B9, + 0xC2CB, 0x85A6, 0xC2CC, 0x87EF, 0xC2CD, 0x87EC, 0xC2CE, 0x87F2, 0xC2CF, 0x87E0, 0xC2D0, 0x8986, 0xC2D1, 0x89B2, 0xC2D2, 0x89F4, + 0xC2D3, 0x8B28, 0xC2D4, 0x8B39, 0xC2D5, 0x8B2C, 0xC2D6, 0x8B2B, 0xC2D7, 0x8C50, 0xC2D8, 0x8D05, 0xC2D9, 0x8E59, 0xC2DA, 0x8E63, + 0xC2DB, 0x8E66, 0xC2DC, 0x8E64, 0xC2DD, 0x8E5F, 0xC2DE, 0x8E55, 0xC2DF, 0x8EC0, 0xC2E0, 0x8F49, 0xC2E1, 0x8F4D, 0xC2E2, 0x9087, + 0xC2E3, 0x9083, 0xC2E4, 0x9088, 0xC2E5, 0x91AB, 0xC2E6, 0x91AC, 0xC2E7, 0x91D0, 0xC2E8, 0x9394, 0xC2E9, 0x938A, 0xC2EA, 0x9396, + 0xC2EB, 0x93A2, 0xC2EC, 0x93B3, 0xC2ED, 0x93AE, 0xC2EE, 0x93AC, 0xC2EF, 0x93B0, 0xC2F0, 0x9398, 0xC2F1, 0x939A, 0xC2F2, 0x9397, + 0xC2F3, 0x95D4, 0xC2F4, 0x95D6, 0xC2F5, 0x95D0, 0xC2F6, 0x95D5, 0xC2F7, 0x96E2, 0xC2F8, 0x96DC, 0xC2F9, 0x96D9, 0xC2FA, 0x96DB, + 0xC2FB, 0x96DE, 0xC2FC, 0x9724, 0xC2FD, 0x97A3, 0xC2FE, 0x97A6, 0xC340, 0x97AD, 0xC341, 0x97F9, 0xC342, 0x984D, 0xC343, 0x984F, + 0xC344, 0x984C, 0xC345, 0x984E, 0xC346, 0x9853, 0xC347, 0x98BA, 0xC348, 0x993E, 0xC349, 0x993F, 0xC34A, 0x993D, 0xC34B, 0x992E, + 0xC34C, 0x99A5, 0xC34D, 0x9A0E, 0xC34E, 0x9AC1, 0xC34F, 0x9B03, 0xC350, 0x9B06, 0xC351, 0x9B4F, 0xC352, 0x9B4E, 0xC353, 0x9B4D, + 0xC354, 0x9BCA, 0xC355, 0x9BC9, 0xC356, 0x9BFD, 0xC357, 0x9BC8, 0xC358, 0x9BC0, 0xC359, 0x9D51, 0xC35A, 0x9D5D, 0xC35B, 0x9D60, + 0xC35C, 0x9EE0, 0xC35D, 0x9F15, 0xC35E, 0x9F2C, 0xC35F, 0x5133, 0xC360, 0x56A5, 0xC361, 0x58DE, 0xC362, 0x58DF, 0xC363, 0x58E2, + 0xC364, 0x5BF5, 0xC365, 0x9F90, 0xC366, 0x5EEC, 0xC367, 0x61F2, 0xC368, 0x61F7, 0xC369, 0x61F6, 0xC36A, 0x61F5, 0xC36B, 0x6500, + 0xC36C, 0x650F, 0xC36D, 0x66E0, 0xC36E, 0x66DD, 0xC36F, 0x6AE5, 0xC370, 0x6ADD, 0xC371, 0x6ADA, 0xC372, 0x6AD3, 0xC373, 0x701B, + 0xC374, 0x701F, 0xC375, 0x7028, 0xC376, 0x701A, 0xC377, 0x701D, 0xC378, 0x7015, 0xC379, 0x7018, 0xC37A, 0x7206, 0xC37B, 0x720D, + 0xC37C, 0x7258, 0xC37D, 0x72A2, 0xC37E, 0x7378, 0xC3A1, 0x737A, 0xC3A2, 0x74BD, 0xC3A3, 0x74CA, 0xC3A4, 0x74E3, 0xC3A5, 0x7587, + 0xC3A6, 0x7586, 0xC3A7, 0x765F, 0xC3A8, 0x7661, 0xC3A9, 0x77C7, 0xC3AA, 0x7919, 0xC3AB, 0x79B1, 0xC3AC, 0x7A6B, 0xC3AD, 0x7A69, + 0xC3AE, 0x7C3E, 0xC3AF, 0x7C3F, 0xC3B0, 0x7C38, 0xC3B1, 0x7C3D, 0xC3B2, 0x7C37, 0xC3B3, 0x7C40, 0xC3B4, 0x7E6B, 0xC3B5, 0x7E6D, + 0xC3B6, 0x7E79, 0xC3B7, 0x7E69, 0xC3B8, 0x7E6A, 0xC3B9, 0x7F85, 0xC3BA, 0x7E73, 0xC3BB, 0x7FB6, 0xC3BC, 0x7FB9, 0xC3BD, 0x7FB8, + 0xC3BE, 0x81D8, 0xC3BF, 0x85E9, 0xC3C0, 0x85DD, 0xC3C1, 0x85EA, 0xC3C2, 0x85D5, 0xC3C3, 0x85E4, 0xC3C4, 0x85E5, 0xC3C5, 0x85F7, + 0xC3C6, 0x87FB, 0xC3C7, 0x8805, 0xC3C8, 0x880D, 0xC3C9, 0x87F9, 0xC3CA, 0x87FE, 0xC3CB, 0x8960, 0xC3CC, 0x895F, 0xC3CD, 0x8956, + 0xC3CE, 0x895E, 0xC3CF, 0x8B41, 0xC3D0, 0x8B5C, 0xC3D1, 0x8B58, 0xC3D2, 0x8B49, 0xC3D3, 0x8B5A, 0xC3D4, 0x8B4E, 0xC3D5, 0x8B4F, + 0xC3D6, 0x8B46, 0xC3D7, 0x8B59, 0xC3D8, 0x8D08, 0xC3D9, 0x8D0A, 0xC3DA, 0x8E7C, 0xC3DB, 0x8E72, 0xC3DC, 0x8E87, 0xC3DD, 0x8E76, + 0xC3DE, 0x8E6C, 0xC3DF, 0x8E7A, 0xC3E0, 0x8E74, 0xC3E1, 0x8F54, 0xC3E2, 0x8F4E, 0xC3E3, 0x8FAD, 0xC3E4, 0x908A, 0xC3E5, 0x908B, + 0xC3E6, 0x91B1, 0xC3E7, 0x91AE, 0xC3E8, 0x93E1, 0xC3E9, 0x93D1, 0xC3EA, 0x93DF, 0xC3EB, 0x93C3, 0xC3EC, 0x93C8, 0xC3ED, 0x93DC, + 0xC3EE, 0x93DD, 0xC3EF, 0x93D6, 0xC3F0, 0x93E2, 0xC3F1, 0x93CD, 0xC3F2, 0x93D8, 0xC3F3, 0x93E4, 0xC3F4, 0x93D7, 0xC3F5, 0x93E8, + 0xC3F6, 0x95DC, 0xC3F7, 0x96B4, 0xC3F8, 0x96E3, 0xC3F9, 0x972A, 0xC3FA, 0x9727, 0xC3FB, 0x9761, 0xC3FC, 0x97DC, 0xC3FD, 0x97FB, + 0xC3FE, 0x985E, 0xC440, 0x9858, 0xC441, 0x985B, 0xC442, 0x98BC, 0xC443, 0x9945, 0xC444, 0x9949, 0xC445, 0x9A16, 0xC446, 0x9A19, + 0xC447, 0x9B0D, 0xC448, 0x9BE8, 0xC449, 0x9BE7, 0xC44A, 0x9BD6, 0xC44B, 0x9BDB, 0xC44C, 0x9D89, 0xC44D, 0x9D61, 0xC44E, 0x9D72, + 0xC44F, 0x9D6A, 0xC450, 0x9D6C, 0xC451, 0x9E92, 0xC452, 0x9E97, 0xC453, 0x9E93, 0xC454, 0x9EB4, 0xC455, 0x52F8, 0xC456, 0x56A8, + 0xC457, 0x56B7, 0xC458, 0x56B6, 0xC459, 0x56B4, 0xC45A, 0x56BC, 0xC45B, 0x58E4, 0xC45C, 0x5B40, 0xC45D, 0x5B43, 0xC45E, 0x5B7D, + 0xC45F, 0x5BF6, 0xC460, 0x5DC9, 0xC461, 0x61F8, 0xC462, 0x61FA, 0xC463, 0x6518, 0xC464, 0x6514, 0xC465, 0x6519, 0xC466, 0x66E6, + 0xC467, 0x6727, 0xC468, 0x6AEC, 0xC469, 0x703E, 0xC46A, 0x7030, 0xC46B, 0x7032, 0xC46C, 0x7210, 0xC46D, 0x737B, 0xC46E, 0x74CF, + 0xC46F, 0x7662, 0xC470, 0x7665, 0xC471, 0x7926, 0xC472, 0x792A, 0xC473, 0x792C, 0xC474, 0x792B, 0xC475, 0x7AC7, 0xC476, 0x7AF6, + 0xC477, 0x7C4C, 0xC478, 0x7C43, 0xC479, 0x7C4D, 0xC47A, 0x7CEF, 0xC47B, 0x7CF0, 0xC47C, 0x8FAE, 0xC47D, 0x7E7D, 0xC47E, 0x7E7C, + 0xC4A1, 0x7E82, 0xC4A2, 0x7F4C, 0xC4A3, 0x8000, 0xC4A4, 0x81DA, 0xC4A5, 0x8266, 0xC4A6, 0x85FB, 0xC4A7, 0x85F9, 0xC4A8, 0x8611, + 0xC4A9, 0x85FA, 0xC4AA, 0x8606, 0xC4AB, 0x860B, 0xC4AC, 0x8607, 0xC4AD, 0x860A, 0xC4AE, 0x8814, 0xC4AF, 0x8815, 0xC4B0, 0x8964, + 0xC4B1, 0x89BA, 0xC4B2, 0x89F8, 0xC4B3, 0x8B70, 0xC4B4, 0x8B6C, 0xC4B5, 0x8B66, 0xC4B6, 0x8B6F, 0xC4B7, 0x8B5F, 0xC4B8, 0x8B6B, + 0xC4B9, 0x8D0F, 0xC4BA, 0x8D0D, 0xC4BB, 0x8E89, 0xC4BC, 0x8E81, 0xC4BD, 0x8E85, 0xC4BE, 0x8E82, 0xC4BF, 0x91B4, 0xC4C0, 0x91CB, + 0xC4C1, 0x9418, 0xC4C2, 0x9403, 0xC4C3, 0x93FD, 0xC4C4, 0x95E1, 0xC4C5, 0x9730, 0xC4C6, 0x98C4, 0xC4C7, 0x9952, 0xC4C8, 0x9951, + 0xC4C9, 0x99A8, 0xC4CA, 0x9A2B, 0xC4CB, 0x9A30, 0xC4CC, 0x9A37, 0xC4CD, 0x9A35, 0xC4CE, 0x9C13, 0xC4CF, 0x9C0D, 0xC4D0, 0x9E79, + 0xC4D1, 0x9EB5, 0xC4D2, 0x9EE8, 0xC4D3, 0x9F2F, 0xC4D4, 0x9F5F, 0xC4D5, 0x9F63, 0xC4D6, 0x9F61, 0xC4D7, 0x5137, 0xC4D8, 0x5138, + 0xC4D9, 0x56C1, 0xC4DA, 0x56C0, 0xC4DB, 0x56C2, 0xC4DC, 0x5914, 0xC4DD, 0x5C6C, 0xC4DE, 0x5DCD, 0xC4DF, 0x61FC, 0xC4E0, 0x61FE, + 0xC4E1, 0x651D, 0xC4E2, 0x651C, 0xC4E3, 0x6595, 0xC4E4, 0x66E9, 0xC4E5, 0x6AFB, 0xC4E6, 0x6B04, 0xC4E7, 0x6AFA, 0xC4E8, 0x6BB2, + 0xC4E9, 0x704C, 0xC4EA, 0x721B, 0xC4EB, 0x72A7, 0xC4EC, 0x74D6, 0xC4ED, 0x74D4, 0xC4EE, 0x7669, 0xC4EF, 0x77D3, 0xC4F0, 0x7C50, + 0xC4F1, 0x7E8F, 0xC4F2, 0x7E8C, 0xC4F3, 0x7FBC, 0xC4F4, 0x8617, 0xC4F5, 0x862D, 0xC4F6, 0x861A, 0xC4F7, 0x8823, 0xC4F8, 0x8822, + 0xC4F9, 0x8821, 0xC4FA, 0x881F, 0xC4FB, 0x896A, 0xC4FC, 0x896C, 0xC4FD, 0x89BD, 0xC4FE, 0x8B74, 0xC540, 0x8B77, 0xC541, 0x8B7D, + 0xC542, 0x8D13, 0xC543, 0x8E8A, 0xC544, 0x8E8D, 0xC545, 0x8E8B, 0xC546, 0x8F5F, 0xC547, 0x8FAF, 0xC548, 0x91BA, 0xC549, 0x942E, + 0xC54A, 0x9433, 0xC54B, 0x9435, 0xC54C, 0x943A, 0xC54D, 0x9438, 0xC54E, 0x9432, 0xC54F, 0x942B, 0xC550, 0x95E2, 0xC551, 0x9738, + 0xC552, 0x9739, 0xC553, 0x9732, 0xC554, 0x97FF, 0xC555, 0x9867, 0xC556, 0x9865, 0xC557, 0x9957, 0xC558, 0x9A45, 0xC559, 0x9A43, + 0xC55A, 0x9A40, 0xC55B, 0x9A3E, 0xC55C, 0x9ACF, 0xC55D, 0x9B54, 0xC55E, 0x9B51, 0xC55F, 0x9C2D, 0xC560, 0x9C25, 0xC561, 0x9DAF, + 0xC562, 0x9DB4, 0xC563, 0x9DC2, 0xC564, 0x9DB8, 0xC565, 0x9E9D, 0xC566, 0x9EEF, 0xC567, 0x9F19, 0xC568, 0x9F5C, 0xC569, 0x9F66, + 0xC56A, 0x9F67, 0xC56B, 0x513C, 0xC56C, 0x513B, 0xC56D, 0x56C8, 0xC56E, 0x56CA, 0xC56F, 0x56C9, 0xC570, 0x5B7F, 0xC571, 0x5DD4, + 0xC572, 0x5DD2, 0xC573, 0x5F4E, 0xC574, 0x61FF, 0xC575, 0x6524, 0xC576, 0x6B0A, 0xC577, 0x6B61, 0xC578, 0x7051, 0xC579, 0x7058, + 0xC57A, 0x7380, 0xC57B, 0x74E4, 0xC57C, 0x758A, 0xC57D, 0x766E, 0xC57E, 0x766C, 0xC5A1, 0x79B3, 0xC5A2, 0x7C60, 0xC5A3, 0x7C5F, + 0xC5A4, 0x807E, 0xC5A5, 0x807D, 0xC5A6, 0x81DF, 0xC5A7, 0x8972, 0xC5A8, 0x896F, 0xC5A9, 0x89FC, 0xC5AA, 0x8B80, 0xC5AB, 0x8D16, + 0xC5AC, 0x8D17, 0xC5AD, 0x8E91, 0xC5AE, 0x8E93, 0xC5AF, 0x8F61, 0xC5B0, 0x9148, 0xC5B1, 0x9444, 0xC5B2, 0x9451, 0xC5B3, 0x9452, + 0xC5B4, 0x973D, 0xC5B5, 0x973E, 0xC5B6, 0x97C3, 0xC5B7, 0x97C1, 0xC5B8, 0x986B, 0xC5B9, 0x9955, 0xC5BA, 0x9A55, 0xC5BB, 0x9A4D, + 0xC5BC, 0x9AD2, 0xC5BD, 0x9B1A, 0xC5BE, 0x9C49, 0xC5BF, 0x9C31, 0xC5C0, 0x9C3E, 0xC5C1, 0x9C3B, 0xC5C2, 0x9DD3, 0xC5C3, 0x9DD7, + 0xC5C4, 0x9F34, 0xC5C5, 0x9F6C, 0xC5C6, 0x9F6A, 0xC5C7, 0x9F94, 0xC5C8, 0x56CC, 0xC5C9, 0x5DD6, 0xC5CA, 0x6200, 0xC5CB, 0x6523, + 0xC5CC, 0x652B, 0xC5CD, 0x652A, 0xC5CE, 0x66EC, 0xC5CF, 0x6B10, 0xC5D0, 0x74DA, 0xC5D1, 0x7ACA, 0xC5D2, 0x7C64, 0xC5D3, 0x7C63, + 0xC5D4, 0x7C65, 0xC5D5, 0x7E93, 0xC5D6, 0x7E96, 0xC5D7, 0x7E94, 0xC5D8, 0x81E2, 0xC5D9, 0x8638, 0xC5DA, 0x863F, 0xC5DB, 0x8831, + 0xC5DC, 0x8B8A, 0xC5DD, 0x9090, 0xC5DE, 0x908F, 0xC5DF, 0x9463, 0xC5E0, 0x9460, 0xC5E1, 0x9464, 0xC5E2, 0x9768, 0xC5E3, 0x986F, + 0xC5E4, 0x995C, 0xC5E5, 0x9A5A, 0xC5E6, 0x9A5B, 0xC5E7, 0x9A57, 0xC5E8, 0x9AD3, 0xC5E9, 0x9AD4, 0xC5EA, 0x9AD1, 0xC5EB, 0x9C54, + 0xC5EC, 0x9C57, 0xC5ED, 0x9C56, 0xC5EE, 0x9DE5, 0xC5EF, 0x9E9F, 0xC5F0, 0x9EF4, 0xC5F1, 0x56D1, 0xC5F2, 0x58E9, 0xC5F3, 0x652C, + 0xC5F4, 0x705E, 0xC5F5, 0x7671, 0xC5F6, 0x7672, 0xC5F7, 0x77D7, 0xC5F8, 0x7F50, 0xC5F9, 0x7F88, 0xC5FA, 0x8836, 0xC5FB, 0x8839, + 0xC5FC, 0x8862, 0xC5FD, 0x8B93, 0xC5FE, 0x8B92, 0xC640, 0x8B96, 0xC641, 0x8277, 0xC642, 0x8D1B, 0xC643, 0x91C0, 0xC644, 0x946A, + 0xC645, 0x9742, 0xC646, 0x9748, 0xC647, 0x9744, 0xC648, 0x97C6, 0xC649, 0x9870, 0xC64A, 0x9A5F, 0xC64B, 0x9B22, 0xC64C, 0x9B58, + 0xC64D, 0x9C5F, 0xC64E, 0x9DF9, 0xC64F, 0x9DFA, 0xC650, 0x9E7C, 0xC651, 0x9E7D, 0xC652, 0x9F07, 0xC653, 0x9F77, 0xC654, 0x9F72, + 0xC655, 0x5EF3, 0xC656, 0x6B16, 0xC657, 0x7063, 0xC658, 0x7C6C, 0xC659, 0x7C6E, 0xC65A, 0x883B, 0xC65B, 0x89C0, 0xC65C, 0x8EA1, + 0xC65D, 0x91C1, 0xC65E, 0x9472, 0xC65F, 0x9470, 0xC660, 0x9871, 0xC661, 0x995E, 0xC662, 0x9AD6, 0xC663, 0x9B23, 0xC664, 0x9ECC, + 0xC665, 0x7064, 0xC666, 0x77DA, 0xC667, 0x8B9A, 0xC668, 0x9477, 0xC669, 0x97C9, 0xC66A, 0x9A62, 0xC66B, 0x9A65, 0xC66C, 0x7E9C, + 0xC66D, 0x8B9C, 0xC66E, 0x8EAA, 0xC66F, 0x91C5, 0xC670, 0x947D, 0xC671, 0x947E, 0xC672, 0x947C, 0xC673, 0x9C77, 0xC674, 0x9C78, + 0xC675, 0x9EF7, 0xC676, 0x8C54, 0xC677, 0x947F, 0xC678, 0x9E1A, 0xC679, 0x7228, 0xC67A, 0x9A6A, 0xC67B, 0x9B31, 0xC67C, 0x9E1B, + 0xC67D, 0x9E1E, 0xC67E, 0x7C72, 0xC940, 0x4E42, 0xC941, 0x4E5C, 0xC942, 0x51F5, 0xC943, 0x531A, 0xC944, 0x5382, 0xC945, 0x4E07, + 0xC946, 0x4E0C, 0xC947, 0x4E47, 0xC948, 0x4E8D, 0xC949, 0x56D7, 0xC94A, 0xFA0C, 0xC94B, 0x5C6E, 0xC94C, 0x5F73, 0xC94D, 0x4E0F, + 0xC94E, 0x5187, 0xC94F, 0x4E0E, 0xC950, 0x4E2E, 0xC951, 0x4E93, 0xC952, 0x4EC2, 0xC953, 0x4EC9, 0xC954, 0x4EC8, 0xC955, 0x5198, + 0xC956, 0x52FC, 0xC957, 0x536C, 0xC958, 0x53B9, 0xC959, 0x5720, 0xC95A, 0x5903, 0xC95B, 0x592C, 0xC95C, 0x5C10, 0xC95D, 0x5DFF, + 0xC95E, 0x65E1, 0xC95F, 0x6BB3, 0xC960, 0x6BCC, 0xC961, 0x6C14, 0xC962, 0x723F, 0xC963, 0x4E31, 0xC964, 0x4E3C, 0xC965, 0x4EE8, + 0xC966, 0x4EDC, 0xC967, 0x4EE9, 0xC968, 0x4EE1, 0xC969, 0x4EDD, 0xC96A, 0x4EDA, 0xC96B, 0x520C, 0xC96C, 0x531C, 0xC96D, 0x534C, + 0xC96E, 0x5722, 0xC96F, 0x5723, 0xC970, 0x5917, 0xC971, 0x592F, 0xC972, 0x5B81, 0xC973, 0x5B84, 0xC974, 0x5C12, 0xC975, 0x5C3B, + 0xC976, 0x5C74, 0xC977, 0x5C73, 0xC978, 0x5E04, 0xC979, 0x5E80, 0xC97A, 0x5E82, 0xC97B, 0x5FC9, 0xC97C, 0x6209, 0xC97D, 0x6250, + 0xC97E, 0x6C15, 0xC9A1, 0x6C36, 0xC9A2, 0x6C43, 0xC9A3, 0x6C3F, 0xC9A4, 0x6C3B, 0xC9A5, 0x72AE, 0xC9A6, 0x72B0, 0xC9A7, 0x738A, + 0xC9A8, 0x79B8, 0xC9A9, 0x808A, 0xC9AA, 0x961E, 0xC9AB, 0x4F0E, 0xC9AC, 0x4F18, 0xC9AD, 0x4F2C, 0xC9AE, 0x4EF5, 0xC9AF, 0x4F14, + 0xC9B0, 0x4EF1, 0xC9B1, 0x4F00, 0xC9B2, 0x4EF7, 0xC9B3, 0x4F08, 0xC9B4, 0x4F1D, 0xC9B5, 0x4F02, 0xC9B6, 0x4F05, 0xC9B7, 0x4F22, + 0xC9B8, 0x4F13, 0xC9B9, 0x4F04, 0xC9BA, 0x4EF4, 0xC9BB, 0x4F12, 0xC9BC, 0x51B1, 0xC9BD, 0x5213, 0xC9BE, 0x5209, 0xC9BF, 0x5210, + 0xC9C0, 0x52A6, 0xC9C1, 0x5322, 0xC9C2, 0x531F, 0xC9C3, 0x534D, 0xC9C4, 0x538A, 0xC9C5, 0x5407, 0xC9C6, 0x56E1, 0xC9C7, 0x56DF, + 0xC9C8, 0x572E, 0xC9C9, 0x572A, 0xC9CA, 0x5734, 0xC9CB, 0x593C, 0xC9CC, 0x5980, 0xC9CD, 0x597C, 0xC9CE, 0x5985, 0xC9CF, 0x597B, + 0xC9D0, 0x597E, 0xC9D1, 0x5977, 0xC9D2, 0x597F, 0xC9D3, 0x5B56, 0xC9D4, 0x5C15, 0xC9D5, 0x5C25, 0xC9D6, 0x5C7C, 0xC9D7, 0x5C7A, + 0xC9D8, 0x5C7B, 0xC9D9, 0x5C7E, 0xC9DA, 0x5DDF, 0xC9DB, 0x5E75, 0xC9DC, 0x5E84, 0xC9DD, 0x5F02, 0xC9DE, 0x5F1A, 0xC9DF, 0x5F74, + 0xC9E0, 0x5FD5, 0xC9E1, 0x5FD4, 0xC9E2, 0x5FCF, 0xC9E3, 0x625C, 0xC9E4, 0x625E, 0xC9E5, 0x6264, 0xC9E6, 0x6261, 0xC9E7, 0x6266, + 0xC9E8, 0x6262, 0xC9E9, 0x6259, 0xC9EA, 0x6260, 0xC9EB, 0x625A, 0xC9EC, 0x6265, 0xC9ED, 0x65EF, 0xC9EE, 0x65EE, 0xC9EF, 0x673E, + 0xC9F0, 0x6739, 0xC9F1, 0x6738, 0xC9F2, 0x673B, 0xC9F3, 0x673A, 0xC9F4, 0x673F, 0xC9F5, 0x673C, 0xC9F6, 0x6733, 0xC9F7, 0x6C18, + 0xC9F8, 0x6C46, 0xC9F9, 0x6C52, 0xC9FA, 0x6C5C, 0xC9FB, 0x6C4F, 0xC9FC, 0x6C4A, 0xC9FD, 0x6C54, 0xC9FE, 0x6C4B, 0xCA40, 0x6C4C, + 0xCA41, 0x7071, 0xCA42, 0x725E, 0xCA43, 0x72B4, 0xCA44, 0x72B5, 0xCA45, 0x738E, 0xCA46, 0x752A, 0xCA47, 0x767F, 0xCA48, 0x7A75, + 0xCA49, 0x7F51, 0xCA4A, 0x8278, 0xCA4B, 0x827C, 0xCA4C, 0x8280, 0xCA4D, 0x827D, 0xCA4E, 0x827F, 0xCA4F, 0x864D, 0xCA50, 0x897E, + 0xCA51, 0x9099, 0xCA52, 0x9097, 0xCA53, 0x9098, 0xCA54, 0x909B, 0xCA55, 0x9094, 0xCA56, 0x9622, 0xCA57, 0x9624, 0xCA58, 0x9620, + 0xCA59, 0x9623, 0xCA5A, 0x4F56, 0xCA5B, 0x4F3B, 0xCA5C, 0x4F62, 0xCA5D, 0x4F49, 0xCA5E, 0x4F53, 0xCA5F, 0x4F64, 0xCA60, 0x4F3E, + 0xCA61, 0x4F67, 0xCA62, 0x4F52, 0xCA63, 0x4F5F, 0xCA64, 0x4F41, 0xCA65, 0x4F58, 0xCA66, 0x4F2D, 0xCA67, 0x4F33, 0xCA68, 0x4F3F, + 0xCA69, 0x4F61, 0xCA6A, 0x518F, 0xCA6B, 0x51B9, 0xCA6C, 0x521C, 0xCA6D, 0x521E, 0xCA6E, 0x5221, 0xCA6F, 0x52AD, 0xCA70, 0x52AE, + 0xCA71, 0x5309, 0xCA72, 0x5363, 0xCA73, 0x5372, 0xCA74, 0x538E, 0xCA75, 0x538F, 0xCA76, 0x5430, 0xCA77, 0x5437, 0xCA78, 0x542A, + 0xCA79, 0x5454, 0xCA7A, 0x5445, 0xCA7B, 0x5419, 0xCA7C, 0x541C, 0xCA7D, 0x5425, 0xCA7E, 0x5418, 0xCAA1, 0x543D, 0xCAA2, 0x544F, + 0xCAA3, 0x5441, 0xCAA4, 0x5428, 0xCAA5, 0x5424, 0xCAA6, 0x5447, 0xCAA7, 0x56EE, 0xCAA8, 0x56E7, 0xCAA9, 0x56E5, 0xCAAA, 0x5741, + 0xCAAB, 0x5745, 0xCAAC, 0x574C, 0xCAAD, 0x5749, 0xCAAE, 0x574B, 0xCAAF, 0x5752, 0xCAB0, 0x5906, 0xCAB1, 0x5940, 0xCAB2, 0x59A6, + 0xCAB3, 0x5998, 0xCAB4, 0x59A0, 0xCAB5, 0x5997, 0xCAB6, 0x598E, 0xCAB7, 0x59A2, 0xCAB8, 0x5990, 0xCAB9, 0x598F, 0xCABA, 0x59A7, + 0xCABB, 0x59A1, 0xCABC, 0x5B8E, 0xCABD, 0x5B92, 0xCABE, 0x5C28, 0xCABF, 0x5C2A, 0xCAC0, 0x5C8D, 0xCAC1, 0x5C8F, 0xCAC2, 0x5C88, + 0xCAC3, 0x5C8B, 0xCAC4, 0x5C89, 0xCAC5, 0x5C92, 0xCAC6, 0x5C8A, 0xCAC7, 0x5C86, 0xCAC8, 0x5C93, 0xCAC9, 0x5C95, 0xCACA, 0x5DE0, + 0xCACB, 0x5E0A, 0xCACC, 0x5E0E, 0xCACD, 0x5E8B, 0xCACE, 0x5E89, 0xCACF, 0x5E8C, 0xCAD0, 0x5E88, 0xCAD1, 0x5E8D, 0xCAD2, 0x5F05, + 0xCAD3, 0x5F1D, 0xCAD4, 0x5F78, 0xCAD5, 0x5F76, 0xCAD6, 0x5FD2, 0xCAD7, 0x5FD1, 0xCAD8, 0x5FD0, 0xCAD9, 0x5FED, 0xCADA, 0x5FE8, + 0xCADB, 0x5FEE, 0xCADC, 0x5FF3, 0xCADD, 0x5FE1, 0xCADE, 0x5FE4, 0xCADF, 0x5FE3, 0xCAE0, 0x5FFA, 0xCAE1, 0x5FEF, 0xCAE2, 0x5FF7, + 0xCAE3, 0x5FFB, 0xCAE4, 0x6000, 0xCAE5, 0x5FF4, 0xCAE6, 0x623A, 0xCAE7, 0x6283, 0xCAE8, 0x628C, 0xCAE9, 0x628E, 0xCAEA, 0x628F, + 0xCAEB, 0x6294, 0xCAEC, 0x6287, 0xCAED, 0x6271, 0xCAEE, 0x627B, 0xCAEF, 0x627A, 0xCAF0, 0x6270, 0xCAF1, 0x6281, 0xCAF2, 0x6288, + 0xCAF3, 0x6277, 0xCAF4, 0x627D, 0xCAF5, 0x6272, 0xCAF6, 0x6274, 0xCAF7, 0x6537, 0xCAF8, 0x65F0, 0xCAF9, 0x65F4, 0xCAFA, 0x65F3, + 0xCAFB, 0x65F2, 0xCAFC, 0x65F5, 0xCAFD, 0x6745, 0xCAFE, 0x6747, 0xCB40, 0x6759, 0xCB41, 0x6755, 0xCB42, 0x674C, 0xCB43, 0x6748, + 0xCB44, 0x675D, 0xCB45, 0x674D, 0xCB46, 0x675A, 0xCB47, 0x674B, 0xCB48, 0x6BD0, 0xCB49, 0x6C19, 0xCB4A, 0x6C1A, 0xCB4B, 0x6C78, + 0xCB4C, 0x6C67, 0xCB4D, 0x6C6B, 0xCB4E, 0x6C84, 0xCB4F, 0x6C8B, 0xCB50, 0x6C8F, 0xCB51, 0x6C71, 0xCB52, 0x6C6F, 0xCB53, 0x6C69, + 0xCB54, 0x6C9A, 0xCB55, 0x6C6D, 0xCB56, 0x6C87, 0xCB57, 0x6C95, 0xCB58, 0x6C9C, 0xCB59, 0x6C66, 0xCB5A, 0x6C73, 0xCB5B, 0x6C65, + 0xCB5C, 0x6C7B, 0xCB5D, 0x6C8E, 0xCB5E, 0x7074, 0xCB5F, 0x707A, 0xCB60, 0x7263, 0xCB61, 0x72BF, 0xCB62, 0x72BD, 0xCB63, 0x72C3, + 0xCB64, 0x72C6, 0xCB65, 0x72C1, 0xCB66, 0x72BA, 0xCB67, 0x72C5, 0xCB68, 0x7395, 0xCB69, 0x7397, 0xCB6A, 0x7393, 0xCB6B, 0x7394, + 0xCB6C, 0x7392, 0xCB6D, 0x753A, 0xCB6E, 0x7539, 0xCB6F, 0x7594, 0xCB70, 0x7595, 0xCB71, 0x7681, 0xCB72, 0x793D, 0xCB73, 0x8034, + 0xCB74, 0x8095, 0xCB75, 0x8099, 0xCB76, 0x8090, 0xCB77, 0x8092, 0xCB78, 0x809C, 0xCB79, 0x8290, 0xCB7A, 0x828F, 0xCB7B, 0x8285, + 0xCB7C, 0x828E, 0xCB7D, 0x8291, 0xCB7E, 0x8293, 0xCBA1, 0x828A, 0xCBA2, 0x8283, 0xCBA3, 0x8284, 0xCBA4, 0x8C78, 0xCBA5, 0x8FC9, + 0xCBA6, 0x8FBF, 0xCBA7, 0x909F, 0xCBA8, 0x90A1, 0xCBA9, 0x90A5, 0xCBAA, 0x909E, 0xCBAB, 0x90A7, 0xCBAC, 0x90A0, 0xCBAD, 0x9630, + 0xCBAE, 0x9628, 0xCBAF, 0x962F, 0xCBB0, 0x962D, 0xCBB1, 0x4E33, 0xCBB2, 0x4F98, 0xCBB3, 0x4F7C, 0xCBB4, 0x4F85, 0xCBB5, 0x4F7D, + 0xCBB6, 0x4F80, 0xCBB7, 0x4F87, 0xCBB8, 0x4F76, 0xCBB9, 0x4F74, 0xCBBA, 0x4F89, 0xCBBB, 0x4F84, 0xCBBC, 0x4F77, 0xCBBD, 0x4F4C, + 0xCBBE, 0x4F97, 0xCBBF, 0x4F6A, 0xCBC0, 0x4F9A, 0xCBC1, 0x4F79, 0xCBC2, 0x4F81, 0xCBC3, 0x4F78, 0xCBC4, 0x4F90, 0xCBC5, 0x4F9C, + 0xCBC6, 0x4F94, 0xCBC7, 0x4F9E, 0xCBC8, 0x4F92, 0xCBC9, 0x4F82, 0xCBCA, 0x4F95, 0xCBCB, 0x4F6B, 0xCBCC, 0x4F6E, 0xCBCD, 0x519E, + 0xCBCE, 0x51BC, 0xCBCF, 0x51BE, 0xCBD0, 0x5235, 0xCBD1, 0x5232, 0xCBD2, 0x5233, 0xCBD3, 0x5246, 0xCBD4, 0x5231, 0xCBD5, 0x52BC, + 0xCBD6, 0x530A, 0xCBD7, 0x530B, 0xCBD8, 0x533C, 0xCBD9, 0x5392, 0xCBDA, 0x5394, 0xCBDB, 0x5487, 0xCBDC, 0x547F, 0xCBDD, 0x5481, + 0xCBDE, 0x5491, 0xCBDF, 0x5482, 0xCBE0, 0x5488, 0xCBE1, 0x546B, 0xCBE2, 0x547A, 0xCBE3, 0x547E, 0xCBE4, 0x5465, 0xCBE5, 0x546C, + 0xCBE6, 0x5474, 0xCBE7, 0x5466, 0xCBE8, 0x548D, 0xCBE9, 0x546F, 0xCBEA, 0x5461, 0xCBEB, 0x5460, 0xCBEC, 0x5498, 0xCBED, 0x5463, + 0xCBEE, 0x5467, 0xCBEF, 0x5464, 0xCBF0, 0x56F7, 0xCBF1, 0x56F9, 0xCBF2, 0x576F, 0xCBF3, 0x5772, 0xCBF4, 0x576D, 0xCBF5, 0x576B, + 0xCBF6, 0x5771, 0xCBF7, 0x5770, 0xCBF8, 0x5776, 0xCBF9, 0x5780, 0xCBFA, 0x5775, 0xCBFB, 0x577B, 0xCBFC, 0x5773, 0xCBFD, 0x5774, + 0xCBFE, 0x5762, 0xCC40, 0x5768, 0xCC41, 0x577D, 0xCC42, 0x590C, 0xCC43, 0x5945, 0xCC44, 0x59B5, 0xCC45, 0x59BA, 0xCC46, 0x59CF, + 0xCC47, 0x59CE, 0xCC48, 0x59B2, 0xCC49, 0x59CC, 0xCC4A, 0x59C1, 0xCC4B, 0x59B6, 0xCC4C, 0x59BC, 0xCC4D, 0x59C3, 0xCC4E, 0x59D6, + 0xCC4F, 0x59B1, 0xCC50, 0x59BD, 0xCC51, 0x59C0, 0xCC52, 0x59C8, 0xCC53, 0x59B4, 0xCC54, 0x59C7, 0xCC55, 0x5B62, 0xCC56, 0x5B65, + 0xCC57, 0x5B93, 0xCC58, 0x5B95, 0xCC59, 0x5C44, 0xCC5A, 0x5C47, 0xCC5B, 0x5CAE, 0xCC5C, 0x5CA4, 0xCC5D, 0x5CA0, 0xCC5E, 0x5CB5, + 0xCC5F, 0x5CAF, 0xCC60, 0x5CA8, 0xCC61, 0x5CAC, 0xCC62, 0x5C9F, 0xCC63, 0x5CA3, 0xCC64, 0x5CAD, 0xCC65, 0x5CA2, 0xCC66, 0x5CAA, + 0xCC67, 0x5CA7, 0xCC68, 0x5C9D, 0xCC69, 0x5CA5, 0xCC6A, 0x5CB6, 0xCC6B, 0x5CB0, 0xCC6C, 0x5CA6, 0xCC6D, 0x5E17, 0xCC6E, 0x5E14, + 0xCC6F, 0x5E19, 0xCC70, 0x5F28, 0xCC71, 0x5F22, 0xCC72, 0x5F23, 0xCC73, 0x5F24, 0xCC74, 0x5F54, 0xCC75, 0x5F82, 0xCC76, 0x5F7E, + 0xCC77, 0x5F7D, 0xCC78, 0x5FDE, 0xCC79, 0x5FE5, 0xCC7A, 0x602D, 0xCC7B, 0x6026, 0xCC7C, 0x6019, 0xCC7D, 0x6032, 0xCC7E, 0x600B, + 0xCCA1, 0x6034, 0xCCA2, 0x600A, 0xCCA3, 0x6017, 0xCCA4, 0x6033, 0xCCA5, 0x601A, 0xCCA6, 0x601E, 0xCCA7, 0x602C, 0xCCA8, 0x6022, + 0xCCA9, 0x600D, 0xCCAA, 0x6010, 0xCCAB, 0x602E, 0xCCAC, 0x6013, 0xCCAD, 0x6011, 0xCCAE, 0x600C, 0xCCAF, 0x6009, 0xCCB0, 0x601C, + 0xCCB1, 0x6214, 0xCCB2, 0x623D, 0xCCB3, 0x62AD, 0xCCB4, 0x62B4, 0xCCB5, 0x62D1, 0xCCB6, 0x62BE, 0xCCB7, 0x62AA, 0xCCB8, 0x62B6, + 0xCCB9, 0x62CA, 0xCCBA, 0x62AE, 0xCCBB, 0x62B3, 0xCCBC, 0x62AF, 0xCCBD, 0x62BB, 0xCCBE, 0x62A9, 0xCCBF, 0x62B0, 0xCCC0, 0x62B8, + 0xCCC1, 0x653D, 0xCCC2, 0x65A8, 0xCCC3, 0x65BB, 0xCCC4, 0x6609, 0xCCC5, 0x65FC, 0xCCC6, 0x6604, 0xCCC7, 0x6612, 0xCCC8, 0x6608, + 0xCCC9, 0x65FB, 0xCCCA, 0x6603, 0xCCCB, 0x660B, 0xCCCC, 0x660D, 0xCCCD, 0x6605, 0xCCCE, 0x65FD, 0xCCCF, 0x6611, 0xCCD0, 0x6610, + 0xCCD1, 0x66F6, 0xCCD2, 0x670A, 0xCCD3, 0x6785, 0xCCD4, 0x676C, 0xCCD5, 0x678E, 0xCCD6, 0x6792, 0xCCD7, 0x6776, 0xCCD8, 0x677B, + 0xCCD9, 0x6798, 0xCCDA, 0x6786, 0xCCDB, 0x6784, 0xCCDC, 0x6774, 0xCCDD, 0x678D, 0xCCDE, 0x678C, 0xCCDF, 0x677A, 0xCCE0, 0x679F, + 0xCCE1, 0x6791, 0xCCE2, 0x6799, 0xCCE3, 0x6783, 0xCCE4, 0x677D, 0xCCE5, 0x6781, 0xCCE6, 0x6778, 0xCCE7, 0x6779, 0xCCE8, 0x6794, + 0xCCE9, 0x6B25, 0xCCEA, 0x6B80, 0xCCEB, 0x6B7E, 0xCCEC, 0x6BDE, 0xCCED, 0x6C1D, 0xCCEE, 0x6C93, 0xCCEF, 0x6CEC, 0xCCF0, 0x6CEB, + 0xCCF1, 0x6CEE, 0xCCF2, 0x6CD9, 0xCCF3, 0x6CB6, 0xCCF4, 0x6CD4, 0xCCF5, 0x6CAD, 0xCCF6, 0x6CE7, 0xCCF7, 0x6CB7, 0xCCF8, 0x6CD0, + 0xCCF9, 0x6CC2, 0xCCFA, 0x6CBA, 0xCCFB, 0x6CC3, 0xCCFC, 0x6CC6, 0xCCFD, 0x6CED, 0xCCFE, 0x6CF2, 0xCD40, 0x6CD2, 0xCD41, 0x6CDD, + 0xCD42, 0x6CB4, 0xCD43, 0x6C8A, 0xCD44, 0x6C9D, 0xCD45, 0x6C80, 0xCD46, 0x6CDE, 0xCD47, 0x6CC0, 0xCD48, 0x6D30, 0xCD49, 0x6CCD, + 0xCD4A, 0x6CC7, 0xCD4B, 0x6CB0, 0xCD4C, 0x6CF9, 0xCD4D, 0x6CCF, 0xCD4E, 0x6CE9, 0xCD4F, 0x6CD1, 0xCD50, 0x7094, 0xCD51, 0x7098, + 0xCD52, 0x7085, 0xCD53, 0x7093, 0xCD54, 0x7086, 0xCD55, 0x7084, 0xCD56, 0x7091, 0xCD57, 0x7096, 0xCD58, 0x7082, 0xCD59, 0x709A, + 0xCD5A, 0x7083, 0xCD5B, 0x726A, 0xCD5C, 0x72D6, 0xCD5D, 0x72CB, 0xCD5E, 0x72D8, 0xCD5F, 0x72C9, 0xCD60, 0x72DC, 0xCD61, 0x72D2, + 0xCD62, 0x72D4, 0xCD63, 0x72DA, 0xCD64, 0x72CC, 0xCD65, 0x72D1, 0xCD66, 0x73A4, 0xCD67, 0x73A1, 0xCD68, 0x73AD, 0xCD69, 0x73A6, + 0xCD6A, 0x73A2, 0xCD6B, 0x73A0, 0xCD6C, 0x73AC, 0xCD6D, 0x739D, 0xCD6E, 0x74DD, 0xCD6F, 0x74E8, 0xCD70, 0x753F, 0xCD71, 0x7540, + 0xCD72, 0x753E, 0xCD73, 0x758C, 0xCD74, 0x7598, 0xCD75, 0x76AF, 0xCD76, 0x76F3, 0xCD77, 0x76F1, 0xCD78, 0x76F0, 0xCD79, 0x76F5, + 0xCD7A, 0x77F8, 0xCD7B, 0x77FC, 0xCD7C, 0x77F9, 0xCD7D, 0x77FB, 0xCD7E, 0x77FA, 0xCDA1, 0x77F7, 0xCDA2, 0x7942, 0xCDA3, 0x793F, + 0xCDA4, 0x79C5, 0xCDA5, 0x7A78, 0xCDA6, 0x7A7B, 0xCDA7, 0x7AFB, 0xCDA8, 0x7C75, 0xCDA9, 0x7CFD, 0xCDAA, 0x8035, 0xCDAB, 0x808F, + 0xCDAC, 0x80AE, 0xCDAD, 0x80A3, 0xCDAE, 0x80B8, 0xCDAF, 0x80B5, 0xCDB0, 0x80AD, 0xCDB1, 0x8220, 0xCDB2, 0x82A0, 0xCDB3, 0x82C0, + 0xCDB4, 0x82AB, 0xCDB5, 0x829A, 0xCDB6, 0x8298, 0xCDB7, 0x829B, 0xCDB8, 0x82B5, 0xCDB9, 0x82A7, 0xCDBA, 0x82AE, 0xCDBB, 0x82BC, + 0xCDBC, 0x829E, 0xCDBD, 0x82BA, 0xCDBE, 0x82B4, 0xCDBF, 0x82A8, 0xCDC0, 0x82A1, 0xCDC1, 0x82A9, 0xCDC2, 0x82C2, 0xCDC3, 0x82A4, + 0xCDC4, 0x82C3, 0xCDC5, 0x82B6, 0xCDC6, 0x82A2, 0xCDC7, 0x8670, 0xCDC8, 0x866F, 0xCDC9, 0x866D, 0xCDCA, 0x866E, 0xCDCB, 0x8C56, + 0xCDCC, 0x8FD2, 0xCDCD, 0x8FCB, 0xCDCE, 0x8FD3, 0xCDCF, 0x8FCD, 0xCDD0, 0x8FD6, 0xCDD1, 0x8FD5, 0xCDD2, 0x8FD7, 0xCDD3, 0x90B2, + 0xCDD4, 0x90B4, 0xCDD5, 0x90AF, 0xCDD6, 0x90B3, 0xCDD7, 0x90B0, 0xCDD8, 0x9639, 0xCDD9, 0x963D, 0xCDDA, 0x963C, 0xCDDB, 0x963A, + 0xCDDC, 0x9643, 0xCDDD, 0x4FCD, 0xCDDE, 0x4FC5, 0xCDDF, 0x4FD3, 0xCDE0, 0x4FB2, 0xCDE1, 0x4FC9, 0xCDE2, 0x4FCB, 0xCDE3, 0x4FC1, + 0xCDE4, 0x4FD4, 0xCDE5, 0x4FDC, 0xCDE6, 0x4FD9, 0xCDE7, 0x4FBB, 0xCDE8, 0x4FB3, 0xCDE9, 0x4FDB, 0xCDEA, 0x4FC7, 0xCDEB, 0x4FD6, + 0xCDEC, 0x4FBA, 0xCDED, 0x4FC0, 0xCDEE, 0x4FB9, 0xCDEF, 0x4FEC, 0xCDF0, 0x5244, 0xCDF1, 0x5249, 0xCDF2, 0x52C0, 0xCDF3, 0x52C2, + 0xCDF4, 0x533D, 0xCDF5, 0x537C, 0xCDF6, 0x5397, 0xCDF7, 0x5396, 0xCDF8, 0x5399, 0xCDF9, 0x5398, 0xCDFA, 0x54BA, 0xCDFB, 0x54A1, + 0xCDFC, 0x54AD, 0xCDFD, 0x54A5, 0xCDFE, 0x54CF, 0xCE40, 0x54C3, 0xCE41, 0x830D, 0xCE42, 0x54B7, 0xCE43, 0x54AE, 0xCE44, 0x54D6, + 0xCE45, 0x54B6, 0xCE46, 0x54C5, 0xCE47, 0x54C6, 0xCE48, 0x54A0, 0xCE49, 0x5470, 0xCE4A, 0x54BC, 0xCE4B, 0x54A2, 0xCE4C, 0x54BE, + 0xCE4D, 0x5472, 0xCE4E, 0x54DE, 0xCE4F, 0x54B0, 0xCE50, 0x57B5, 0xCE51, 0x579E, 0xCE52, 0x579F, 0xCE53, 0x57A4, 0xCE54, 0x578C, + 0xCE55, 0x5797, 0xCE56, 0x579D, 0xCE57, 0x579B, 0xCE58, 0x5794, 0xCE59, 0x5798, 0xCE5A, 0x578F, 0xCE5B, 0x5799, 0xCE5C, 0x57A5, + 0xCE5D, 0x579A, 0xCE5E, 0x5795, 0xCE5F, 0x58F4, 0xCE60, 0x590D, 0xCE61, 0x5953, 0xCE62, 0x59E1, 0xCE63, 0x59DE, 0xCE64, 0x59EE, + 0xCE65, 0x5A00, 0xCE66, 0x59F1, 0xCE67, 0x59DD, 0xCE68, 0x59FA, 0xCE69, 0x59FD, 0xCE6A, 0x59FC, 0xCE6B, 0x59F6, 0xCE6C, 0x59E4, + 0xCE6D, 0x59F2, 0xCE6E, 0x59F7, 0xCE6F, 0x59DB, 0xCE70, 0x59E9, 0xCE71, 0x59F3, 0xCE72, 0x59F5, 0xCE73, 0x59E0, 0xCE74, 0x59FE, + 0xCE75, 0x59F4, 0xCE76, 0x59ED, 0xCE77, 0x5BA8, 0xCE78, 0x5C4C, 0xCE79, 0x5CD0, 0xCE7A, 0x5CD8, 0xCE7B, 0x5CCC, 0xCE7C, 0x5CD7, + 0xCE7D, 0x5CCB, 0xCE7E, 0x5CDB, 0xCEA1, 0x5CDE, 0xCEA2, 0x5CDA, 0xCEA3, 0x5CC9, 0xCEA4, 0x5CC7, 0xCEA5, 0x5CCA, 0xCEA6, 0x5CD6, + 0xCEA7, 0x5CD3, 0xCEA8, 0x5CD4, 0xCEA9, 0x5CCF, 0xCEAA, 0x5CC8, 0xCEAB, 0x5CC6, 0xCEAC, 0x5CCE, 0xCEAD, 0x5CDF, 0xCEAE, 0x5CF8, + 0xCEAF, 0x5DF9, 0xCEB0, 0x5E21, 0xCEB1, 0x5E22, 0xCEB2, 0x5E23, 0xCEB3, 0x5E20, 0xCEB4, 0x5E24, 0xCEB5, 0x5EB0, 0xCEB6, 0x5EA4, + 0xCEB7, 0x5EA2, 0xCEB8, 0x5E9B, 0xCEB9, 0x5EA3, 0xCEBA, 0x5EA5, 0xCEBB, 0x5F07, 0xCEBC, 0x5F2E, 0xCEBD, 0x5F56, 0xCEBE, 0x5F86, + 0xCEBF, 0x6037, 0xCEC0, 0x6039, 0xCEC1, 0x6054, 0xCEC2, 0x6072, 0xCEC3, 0x605E, 0xCEC4, 0x6045, 0xCEC5, 0x6053, 0xCEC6, 0x6047, + 0xCEC7, 0x6049, 0xCEC8, 0x605B, 0xCEC9, 0x604C, 0xCECA, 0x6040, 0xCECB, 0x6042, 0xCECC, 0x605F, 0xCECD, 0x6024, 0xCECE, 0x6044, + 0xCECF, 0x6058, 0xCED0, 0x6066, 0xCED1, 0x606E, 0xCED2, 0x6242, 0xCED3, 0x6243, 0xCED4, 0x62CF, 0xCED5, 0x630D, 0xCED6, 0x630B, + 0xCED7, 0x62F5, 0xCED8, 0x630E, 0xCED9, 0x6303, 0xCEDA, 0x62EB, 0xCEDB, 0x62F9, 0xCEDC, 0x630F, 0xCEDD, 0x630C, 0xCEDE, 0x62F8, + 0xCEDF, 0x62F6, 0xCEE0, 0x6300, 0xCEE1, 0x6313, 0xCEE2, 0x6314, 0xCEE3, 0x62FA, 0xCEE4, 0x6315, 0xCEE5, 0x62FB, 0xCEE6, 0x62F0, + 0xCEE7, 0x6541, 0xCEE8, 0x6543, 0xCEE9, 0x65AA, 0xCEEA, 0x65BF, 0xCEEB, 0x6636, 0xCEEC, 0x6621, 0xCEED, 0x6632, 0xCEEE, 0x6635, + 0xCEEF, 0x661C, 0xCEF0, 0x6626, 0xCEF1, 0x6622, 0xCEF2, 0x6633, 0xCEF3, 0x662B, 0xCEF4, 0x663A, 0xCEF5, 0x661D, 0xCEF6, 0x6634, + 0xCEF7, 0x6639, 0xCEF8, 0x662E, 0xCEF9, 0x670F, 0xCEFA, 0x6710, 0xCEFB, 0x67C1, 0xCEFC, 0x67F2, 0xCEFD, 0x67C8, 0xCEFE, 0x67BA, + 0xCF40, 0x67DC, 0xCF41, 0x67BB, 0xCF42, 0x67F8, 0xCF43, 0x67D8, 0xCF44, 0x67C0, 0xCF45, 0x67B7, 0xCF46, 0x67C5, 0xCF47, 0x67EB, + 0xCF48, 0x67E4, 0xCF49, 0x67DF, 0xCF4A, 0x67B5, 0xCF4B, 0x67CD, 0xCF4C, 0x67B3, 0xCF4D, 0x67F7, 0xCF4E, 0x67F6, 0xCF4F, 0x67EE, + 0xCF50, 0x67E3, 0xCF51, 0x67C2, 0xCF52, 0x67B9, 0xCF53, 0x67CE, 0xCF54, 0x67E7, 0xCF55, 0x67F0, 0xCF56, 0x67B2, 0xCF57, 0x67FC, + 0xCF58, 0x67C6, 0xCF59, 0x67ED, 0xCF5A, 0x67CC, 0xCF5B, 0x67AE, 0xCF5C, 0x67E6, 0xCF5D, 0x67DB, 0xCF5E, 0x67FA, 0xCF5F, 0x67C9, + 0xCF60, 0x67CA, 0xCF61, 0x67C3, 0xCF62, 0x67EA, 0xCF63, 0x67CB, 0xCF64, 0x6B28, 0xCF65, 0x6B82, 0xCF66, 0x6B84, 0xCF67, 0x6BB6, + 0xCF68, 0x6BD6, 0xCF69, 0x6BD8, 0xCF6A, 0x6BE0, 0xCF6B, 0x6C20, 0xCF6C, 0x6C21, 0xCF6D, 0x6D28, 0xCF6E, 0x6D34, 0xCF6F, 0x6D2D, + 0xCF70, 0x6D1F, 0xCF71, 0x6D3C, 0xCF72, 0x6D3F, 0xCF73, 0x6D12, 0xCF74, 0x6D0A, 0xCF75, 0x6CDA, 0xCF76, 0x6D33, 0xCF77, 0x6D04, + 0xCF78, 0x6D19, 0xCF79, 0x6D3A, 0xCF7A, 0x6D1A, 0xCF7B, 0x6D11, 0xCF7C, 0x6D00, 0xCF7D, 0x6D1D, 0xCF7E, 0x6D42, 0xCFA1, 0x6D01, + 0xCFA2, 0x6D18, 0xCFA3, 0x6D37, 0xCFA4, 0x6D03, 0xCFA5, 0x6D0F, 0xCFA6, 0x6D40, 0xCFA7, 0x6D07, 0xCFA8, 0x6D20, 0xCFA9, 0x6D2C, + 0xCFAA, 0x6D08, 0xCFAB, 0x6D22, 0xCFAC, 0x6D09, 0xCFAD, 0x6D10, 0xCFAE, 0x70B7, 0xCFAF, 0x709F, 0xCFB0, 0x70BE, 0xCFB1, 0x70B1, + 0xCFB2, 0x70B0, 0xCFB3, 0x70A1, 0xCFB4, 0x70B4, 0xCFB5, 0x70B5, 0xCFB6, 0x70A9, 0xCFB7, 0x7241, 0xCFB8, 0x7249, 0xCFB9, 0x724A, + 0xCFBA, 0x726C, 0xCFBB, 0x7270, 0xCFBC, 0x7273, 0xCFBD, 0x726E, 0xCFBE, 0x72CA, 0xCFBF, 0x72E4, 0xCFC0, 0x72E8, 0xCFC1, 0x72EB, + 0xCFC2, 0x72DF, 0xCFC3, 0x72EA, 0xCFC4, 0x72E6, 0xCFC5, 0x72E3, 0xCFC6, 0x7385, 0xCFC7, 0x73CC, 0xCFC8, 0x73C2, 0xCFC9, 0x73C8, + 0xCFCA, 0x73C5, 0xCFCB, 0x73B9, 0xCFCC, 0x73B6, 0xCFCD, 0x73B5, 0xCFCE, 0x73B4, 0xCFCF, 0x73EB, 0xCFD0, 0x73BF, 0xCFD1, 0x73C7, + 0xCFD2, 0x73BE, 0xCFD3, 0x73C3, 0xCFD4, 0x73C6, 0xCFD5, 0x73B8, 0xCFD6, 0x73CB, 0xCFD7, 0x74EC, 0xCFD8, 0x74EE, 0xCFD9, 0x752E, + 0xCFDA, 0x7547, 0xCFDB, 0x7548, 0xCFDC, 0x75A7, 0xCFDD, 0x75AA, 0xCFDE, 0x7679, 0xCFDF, 0x76C4, 0xCFE0, 0x7708, 0xCFE1, 0x7703, + 0xCFE2, 0x7704, 0xCFE3, 0x7705, 0xCFE4, 0x770A, 0xCFE5, 0x76F7, 0xCFE6, 0x76FB, 0xCFE7, 0x76FA, 0xCFE8, 0x77E7, 0xCFE9, 0x77E8, + 0xCFEA, 0x7806, 0xCFEB, 0x7811, 0xCFEC, 0x7812, 0xCFED, 0x7805, 0xCFEE, 0x7810, 0xCFEF, 0x780F, 0xCFF0, 0x780E, 0xCFF1, 0x7809, + 0xCFF2, 0x7803, 0xCFF3, 0x7813, 0xCFF4, 0x794A, 0xCFF5, 0x794C, 0xCFF6, 0x794B, 0xCFF7, 0x7945, 0xCFF8, 0x7944, 0xCFF9, 0x79D5, + 0xCFFA, 0x79CD, 0xCFFB, 0x79CF, 0xCFFC, 0x79D6, 0xCFFD, 0x79CE, 0xCFFE, 0x7A80, 0xD040, 0x7A7E, 0xD041, 0x7AD1, 0xD042, 0x7B00, + 0xD043, 0x7B01, 0xD044, 0x7C7A, 0xD045, 0x7C78, 0xD046, 0x7C79, 0xD047, 0x7C7F, 0xD048, 0x7C80, 0xD049, 0x7C81, 0xD04A, 0x7D03, + 0xD04B, 0x7D08, 0xD04C, 0x7D01, 0xD04D, 0x7F58, 0xD04E, 0x7F91, 0xD04F, 0x7F8D, 0xD050, 0x7FBE, 0xD051, 0x8007, 0xD052, 0x800E, + 0xD053, 0x800F, 0xD054, 0x8014, 0xD055, 0x8037, 0xD056, 0x80D8, 0xD057, 0x80C7, 0xD058, 0x80E0, 0xD059, 0x80D1, 0xD05A, 0x80C8, + 0xD05B, 0x80C2, 0xD05C, 0x80D0, 0xD05D, 0x80C5, 0xD05E, 0x80E3, 0xD05F, 0x80D9, 0xD060, 0x80DC, 0xD061, 0x80CA, 0xD062, 0x80D5, + 0xD063, 0x80C9, 0xD064, 0x80CF, 0xD065, 0x80D7, 0xD066, 0x80E6, 0xD067, 0x80CD, 0xD068, 0x81FF, 0xD069, 0x8221, 0xD06A, 0x8294, + 0xD06B, 0x82D9, 0xD06C, 0x82FE, 0xD06D, 0x82F9, 0xD06E, 0x8307, 0xD06F, 0x82E8, 0xD070, 0x8300, 0xD071, 0x82D5, 0xD072, 0x833A, + 0xD073, 0x82EB, 0xD074, 0x82D6, 0xD075, 0x82F4, 0xD076, 0x82EC, 0xD077, 0x82E1, 0xD078, 0x82F2, 0xD079, 0x82F5, 0xD07A, 0x830C, + 0xD07B, 0x82FB, 0xD07C, 0x82F6, 0xD07D, 0x82F0, 0xD07E, 0x82EA, 0xD0A1, 0x82E4, 0xD0A2, 0x82E0, 0xD0A3, 0x82FA, 0xD0A4, 0x82F3, + 0xD0A5, 0x82ED, 0xD0A6, 0x8677, 0xD0A7, 0x8674, 0xD0A8, 0x867C, 0xD0A9, 0x8673, 0xD0AA, 0x8841, 0xD0AB, 0x884E, 0xD0AC, 0x8867, + 0xD0AD, 0x886A, 0xD0AE, 0x8869, 0xD0AF, 0x89D3, 0xD0B0, 0x8A04, 0xD0B1, 0x8A07, 0xD0B2, 0x8D72, 0xD0B3, 0x8FE3, 0xD0B4, 0x8FE1, + 0xD0B5, 0x8FEE, 0xD0B6, 0x8FE0, 0xD0B7, 0x90F1, 0xD0B8, 0x90BD, 0xD0B9, 0x90BF, 0xD0BA, 0x90D5, 0xD0BB, 0x90C5, 0xD0BC, 0x90BE, + 0xD0BD, 0x90C7, 0xD0BE, 0x90CB, 0xD0BF, 0x90C8, 0xD0C0, 0x91D4, 0xD0C1, 0x91D3, 0xD0C2, 0x9654, 0xD0C3, 0x964F, 0xD0C4, 0x9651, + 0xD0C5, 0x9653, 0xD0C6, 0x964A, 0xD0C7, 0x964E, 0xD0C8, 0x501E, 0xD0C9, 0x5005, 0xD0CA, 0x5007, 0xD0CB, 0x5013, 0xD0CC, 0x5022, + 0xD0CD, 0x5030, 0xD0CE, 0x501B, 0xD0CF, 0x4FF5, 0xD0D0, 0x4FF4, 0xD0D1, 0x5033, 0xD0D2, 0x5037, 0xD0D3, 0x502C, 0xD0D4, 0x4FF6, + 0xD0D5, 0x4FF7, 0xD0D6, 0x5017, 0xD0D7, 0x501C, 0xD0D8, 0x5020, 0xD0D9, 0x5027, 0xD0DA, 0x5035, 0xD0DB, 0x502F, 0xD0DC, 0x5031, + 0xD0DD, 0x500E, 0xD0DE, 0x515A, 0xD0DF, 0x5194, 0xD0E0, 0x5193, 0xD0E1, 0x51CA, 0xD0E2, 0x51C4, 0xD0E3, 0x51C5, 0xD0E4, 0x51C8, + 0xD0E5, 0x51CE, 0xD0E6, 0x5261, 0xD0E7, 0x525A, 0xD0E8, 0x5252, 0xD0E9, 0x525E, 0xD0EA, 0x525F, 0xD0EB, 0x5255, 0xD0EC, 0x5262, + 0xD0ED, 0x52CD, 0xD0EE, 0x530E, 0xD0EF, 0x539E, 0xD0F0, 0x5526, 0xD0F1, 0x54E2, 0xD0F2, 0x5517, 0xD0F3, 0x5512, 0xD0F4, 0x54E7, + 0xD0F5, 0x54F3, 0xD0F6, 0x54E4, 0xD0F7, 0x551A, 0xD0F8, 0x54FF, 0xD0F9, 0x5504, 0xD0FA, 0x5508, 0xD0FB, 0x54EB, 0xD0FC, 0x5511, + 0xD0FD, 0x5505, 0xD0FE, 0x54F1, 0xD140, 0x550A, 0xD141, 0x54FB, 0xD142, 0x54F7, 0xD143, 0x54F8, 0xD144, 0x54E0, 0xD145, 0x550E, + 0xD146, 0x5503, 0xD147, 0x550B, 0xD148, 0x5701, 0xD149, 0x5702, 0xD14A, 0x57CC, 0xD14B, 0x5832, 0xD14C, 0x57D5, 0xD14D, 0x57D2, + 0xD14E, 0x57BA, 0xD14F, 0x57C6, 0xD150, 0x57BD, 0xD151, 0x57BC, 0xD152, 0x57B8, 0xD153, 0x57B6, 0xD154, 0x57BF, 0xD155, 0x57C7, + 0xD156, 0x57D0, 0xD157, 0x57B9, 0xD158, 0x57C1, 0xD159, 0x590E, 0xD15A, 0x594A, 0xD15B, 0x5A19, 0xD15C, 0x5A16, 0xD15D, 0x5A2D, + 0xD15E, 0x5A2E, 0xD15F, 0x5A15, 0xD160, 0x5A0F, 0xD161, 0x5A17, 0xD162, 0x5A0A, 0xD163, 0x5A1E, 0xD164, 0x5A33, 0xD165, 0x5B6C, + 0xD166, 0x5BA7, 0xD167, 0x5BAD, 0xD168, 0x5BAC, 0xD169, 0x5C03, 0xD16A, 0x5C56, 0xD16B, 0x5C54, 0xD16C, 0x5CEC, 0xD16D, 0x5CFF, + 0xD16E, 0x5CEE, 0xD16F, 0x5CF1, 0xD170, 0x5CF7, 0xD171, 0x5D00, 0xD172, 0x5CF9, 0xD173, 0x5E29, 0xD174, 0x5E28, 0xD175, 0x5EA8, + 0xD176, 0x5EAE, 0xD177, 0x5EAA, 0xD178, 0x5EAC, 0xD179, 0x5F33, 0xD17A, 0x5F30, 0xD17B, 0x5F67, 0xD17C, 0x605D, 0xD17D, 0x605A, + 0xD17E, 0x6067, 0xD1A1, 0x6041, 0xD1A2, 0x60A2, 0xD1A3, 0x6088, 0xD1A4, 0x6080, 0xD1A5, 0x6092, 0xD1A6, 0x6081, 0xD1A7, 0x609D, + 0xD1A8, 0x6083, 0xD1A9, 0x6095, 0xD1AA, 0x609B, 0xD1AB, 0x6097, 0xD1AC, 0x6087, 0xD1AD, 0x609C, 0xD1AE, 0x608E, 0xD1AF, 0x6219, + 0xD1B0, 0x6246, 0xD1B1, 0x62F2, 0xD1B2, 0x6310, 0xD1B3, 0x6356, 0xD1B4, 0x632C, 0xD1B5, 0x6344, 0xD1B6, 0x6345, 0xD1B7, 0x6336, + 0xD1B8, 0x6343, 0xD1B9, 0x63E4, 0xD1BA, 0x6339, 0xD1BB, 0x634B, 0xD1BC, 0x634A, 0xD1BD, 0x633C, 0xD1BE, 0x6329, 0xD1BF, 0x6341, + 0xD1C0, 0x6334, 0xD1C1, 0x6358, 0xD1C2, 0x6354, 0xD1C3, 0x6359, 0xD1C4, 0x632D, 0xD1C5, 0x6347, 0xD1C6, 0x6333, 0xD1C7, 0x635A, + 0xD1C8, 0x6351, 0xD1C9, 0x6338, 0xD1CA, 0x6357, 0xD1CB, 0x6340, 0xD1CC, 0x6348, 0xD1CD, 0x654A, 0xD1CE, 0x6546, 0xD1CF, 0x65C6, + 0xD1D0, 0x65C3, 0xD1D1, 0x65C4, 0xD1D2, 0x65C2, 0xD1D3, 0x664A, 0xD1D4, 0x665F, 0xD1D5, 0x6647, 0xD1D6, 0x6651, 0xD1D7, 0x6712, + 0xD1D8, 0x6713, 0xD1D9, 0x681F, 0xD1DA, 0x681A, 0xD1DB, 0x6849, 0xD1DC, 0x6832, 0xD1DD, 0x6833, 0xD1DE, 0x683B, 0xD1DF, 0x684B, + 0xD1E0, 0x684F, 0xD1E1, 0x6816, 0xD1E2, 0x6831, 0xD1E3, 0x681C, 0xD1E4, 0x6835, 0xD1E5, 0x682B, 0xD1E6, 0x682D, 0xD1E7, 0x682F, + 0xD1E8, 0x684E, 0xD1E9, 0x6844, 0xD1EA, 0x6834, 0xD1EB, 0x681D, 0xD1EC, 0x6812, 0xD1ED, 0x6814, 0xD1EE, 0x6826, 0xD1EF, 0x6828, + 0xD1F0, 0x682E, 0xD1F1, 0x684D, 0xD1F2, 0x683A, 0xD1F3, 0x6825, 0xD1F4, 0x6820, 0xD1F5, 0x6B2C, 0xD1F6, 0x6B2F, 0xD1F7, 0x6B2D, + 0xD1F8, 0x6B31, 0xD1F9, 0x6B34, 0xD1FA, 0x6B6D, 0xD1FB, 0x8082, 0xD1FC, 0x6B88, 0xD1FD, 0x6BE6, 0xD1FE, 0x6BE4, 0xD240, 0x6BE8, + 0xD241, 0x6BE3, 0xD242, 0x6BE2, 0xD243, 0x6BE7, 0xD244, 0x6C25, 0xD245, 0x6D7A, 0xD246, 0x6D63, 0xD247, 0x6D64, 0xD248, 0x6D76, + 0xD249, 0x6D0D, 0xD24A, 0x6D61, 0xD24B, 0x6D92, 0xD24C, 0x6D58, 0xD24D, 0x6D62, 0xD24E, 0x6D6D, 0xD24F, 0x6D6F, 0xD250, 0x6D91, + 0xD251, 0x6D8D, 0xD252, 0x6DEF, 0xD253, 0x6D7F, 0xD254, 0x6D86, 0xD255, 0x6D5E, 0xD256, 0x6D67, 0xD257, 0x6D60, 0xD258, 0x6D97, + 0xD259, 0x6D70, 0xD25A, 0x6D7C, 0xD25B, 0x6D5F, 0xD25C, 0x6D82, 0xD25D, 0x6D98, 0xD25E, 0x6D2F, 0xD25F, 0x6D68, 0xD260, 0x6D8B, + 0xD261, 0x6D7E, 0xD262, 0x6D80, 0xD263, 0x6D84, 0xD264, 0x6D16, 0xD265, 0x6D83, 0xD266, 0x6D7B, 0xD267, 0x6D7D, 0xD268, 0x6D75, + 0xD269, 0x6D90, 0xD26A, 0x70DC, 0xD26B, 0x70D3, 0xD26C, 0x70D1, 0xD26D, 0x70DD, 0xD26E, 0x70CB, 0xD26F, 0x7F39, 0xD270, 0x70E2, + 0xD271, 0x70D7, 0xD272, 0x70D2, 0xD273, 0x70DE, 0xD274, 0x70E0, 0xD275, 0x70D4, 0xD276, 0x70CD, 0xD277, 0x70C5, 0xD278, 0x70C6, + 0xD279, 0x70C7, 0xD27A, 0x70DA, 0xD27B, 0x70CE, 0xD27C, 0x70E1, 0xD27D, 0x7242, 0xD27E, 0x7278, 0xD2A1, 0x7277, 0xD2A2, 0x7276, + 0xD2A3, 0x7300, 0xD2A4, 0x72FA, 0xD2A5, 0x72F4, 0xD2A6, 0x72FE, 0xD2A7, 0x72F6, 0xD2A8, 0x72F3, 0xD2A9, 0x72FB, 0xD2AA, 0x7301, + 0xD2AB, 0x73D3, 0xD2AC, 0x73D9, 0xD2AD, 0x73E5, 0xD2AE, 0x73D6, 0xD2AF, 0x73BC, 0xD2B0, 0x73E7, 0xD2B1, 0x73E3, 0xD2B2, 0x73E9, + 0xD2B3, 0x73DC, 0xD2B4, 0x73D2, 0xD2B5, 0x73DB, 0xD2B6, 0x73D4, 0xD2B7, 0x73DD, 0xD2B8, 0x73DA, 0xD2B9, 0x73D7, 0xD2BA, 0x73D8, + 0xD2BB, 0x73E8, 0xD2BC, 0x74DE, 0xD2BD, 0x74DF, 0xD2BE, 0x74F4, 0xD2BF, 0x74F5, 0xD2C0, 0x7521, 0xD2C1, 0x755B, 0xD2C2, 0x755F, + 0xD2C3, 0x75B0, 0xD2C4, 0x75C1, 0xD2C5, 0x75BB, 0xD2C6, 0x75C4, 0xD2C7, 0x75C0, 0xD2C8, 0x75BF, 0xD2C9, 0x75B6, 0xD2CA, 0x75BA, + 0xD2CB, 0x768A, 0xD2CC, 0x76C9, 0xD2CD, 0x771D, 0xD2CE, 0x771B, 0xD2CF, 0x7710, 0xD2D0, 0x7713, 0xD2D1, 0x7712, 0xD2D2, 0x7723, + 0xD2D3, 0x7711, 0xD2D4, 0x7715, 0xD2D5, 0x7719, 0xD2D6, 0x771A, 0xD2D7, 0x7722, 0xD2D8, 0x7727, 0xD2D9, 0x7823, 0xD2DA, 0x782C, + 0xD2DB, 0x7822, 0xD2DC, 0x7835, 0xD2DD, 0x782F, 0xD2DE, 0x7828, 0xD2DF, 0x782E, 0xD2E0, 0x782B, 0xD2E1, 0x7821, 0xD2E2, 0x7829, + 0xD2E3, 0x7833, 0xD2E4, 0x782A, 0xD2E5, 0x7831, 0xD2E6, 0x7954, 0xD2E7, 0x795B, 0xD2E8, 0x794F, 0xD2E9, 0x795C, 0xD2EA, 0x7953, + 0xD2EB, 0x7952, 0xD2EC, 0x7951, 0xD2ED, 0x79EB, 0xD2EE, 0x79EC, 0xD2EF, 0x79E0, 0xD2F0, 0x79EE, 0xD2F1, 0x79ED, 0xD2F2, 0x79EA, + 0xD2F3, 0x79DC, 0xD2F4, 0x79DE, 0xD2F5, 0x79DD, 0xD2F6, 0x7A86, 0xD2F7, 0x7A89, 0xD2F8, 0x7A85, 0xD2F9, 0x7A8B, 0xD2FA, 0x7A8C, + 0xD2FB, 0x7A8A, 0xD2FC, 0x7A87, 0xD2FD, 0x7AD8, 0xD2FE, 0x7B10, 0xD340, 0x7B04, 0xD341, 0x7B13, 0xD342, 0x7B05, 0xD343, 0x7B0F, + 0xD344, 0x7B08, 0xD345, 0x7B0A, 0xD346, 0x7B0E, 0xD347, 0x7B09, 0xD348, 0x7B12, 0xD349, 0x7C84, 0xD34A, 0x7C91, 0xD34B, 0x7C8A, + 0xD34C, 0x7C8C, 0xD34D, 0x7C88, 0xD34E, 0x7C8D, 0xD34F, 0x7C85, 0xD350, 0x7D1E, 0xD351, 0x7D1D, 0xD352, 0x7D11, 0xD353, 0x7D0E, + 0xD354, 0x7D18, 0xD355, 0x7D16, 0xD356, 0x7D13, 0xD357, 0x7D1F, 0xD358, 0x7D12, 0xD359, 0x7D0F, 0xD35A, 0x7D0C, 0xD35B, 0x7F5C, + 0xD35C, 0x7F61, 0xD35D, 0x7F5E, 0xD35E, 0x7F60, 0xD35F, 0x7F5D, 0xD360, 0x7F5B, 0xD361, 0x7F96, 0xD362, 0x7F92, 0xD363, 0x7FC3, + 0xD364, 0x7FC2, 0xD365, 0x7FC0, 0xD366, 0x8016, 0xD367, 0x803E, 0xD368, 0x8039, 0xD369, 0x80FA, 0xD36A, 0x80F2, 0xD36B, 0x80F9, + 0xD36C, 0x80F5, 0xD36D, 0x8101, 0xD36E, 0x80FB, 0xD36F, 0x8100, 0xD370, 0x8201, 0xD371, 0x822F, 0xD372, 0x8225, 0xD373, 0x8333, + 0xD374, 0x832D, 0xD375, 0x8344, 0xD376, 0x8319, 0xD377, 0x8351, 0xD378, 0x8325, 0xD379, 0x8356, 0xD37A, 0x833F, 0xD37B, 0x8341, + 0xD37C, 0x8326, 0xD37D, 0x831C, 0xD37E, 0x8322, 0xD3A1, 0x8342, 0xD3A2, 0x834E, 0xD3A3, 0x831B, 0xD3A4, 0x832A, 0xD3A5, 0x8308, + 0xD3A6, 0x833C, 0xD3A7, 0x834D, 0xD3A8, 0x8316, 0xD3A9, 0x8324, 0xD3AA, 0x8320, 0xD3AB, 0x8337, 0xD3AC, 0x832F, 0xD3AD, 0x8329, + 0xD3AE, 0x8347, 0xD3AF, 0x8345, 0xD3B0, 0x834C, 0xD3B1, 0x8353, 0xD3B2, 0x831E, 0xD3B3, 0x832C, 0xD3B4, 0x834B, 0xD3B5, 0x8327, + 0xD3B6, 0x8348, 0xD3B7, 0x8653, 0xD3B8, 0x8652, 0xD3B9, 0x86A2, 0xD3BA, 0x86A8, 0xD3BB, 0x8696, 0xD3BC, 0x868D, 0xD3BD, 0x8691, + 0xD3BE, 0x869E, 0xD3BF, 0x8687, 0xD3C0, 0x8697, 0xD3C1, 0x8686, 0xD3C2, 0x868B, 0xD3C3, 0x869A, 0xD3C4, 0x8685, 0xD3C5, 0x86A5, + 0xD3C6, 0x8699, 0xD3C7, 0x86A1, 0xD3C8, 0x86A7, 0xD3C9, 0x8695, 0xD3CA, 0x8698, 0xD3CB, 0x868E, 0xD3CC, 0x869D, 0xD3CD, 0x8690, + 0xD3CE, 0x8694, 0xD3CF, 0x8843, 0xD3D0, 0x8844, 0xD3D1, 0x886D, 0xD3D2, 0x8875, 0xD3D3, 0x8876, 0xD3D4, 0x8872, 0xD3D5, 0x8880, + 0xD3D6, 0x8871, 0xD3D7, 0x887F, 0xD3D8, 0x886F, 0xD3D9, 0x8883, 0xD3DA, 0x887E, 0xD3DB, 0x8874, 0xD3DC, 0x887C, 0xD3DD, 0x8A12, + 0xD3DE, 0x8C47, 0xD3DF, 0x8C57, 0xD3E0, 0x8C7B, 0xD3E1, 0x8CA4, 0xD3E2, 0x8CA3, 0xD3E3, 0x8D76, 0xD3E4, 0x8D78, 0xD3E5, 0x8DB5, + 0xD3E6, 0x8DB7, 0xD3E7, 0x8DB6, 0xD3E8, 0x8ED1, 0xD3E9, 0x8ED3, 0xD3EA, 0x8FFE, 0xD3EB, 0x8FF5, 0xD3EC, 0x9002, 0xD3ED, 0x8FFF, + 0xD3EE, 0x8FFB, 0xD3EF, 0x9004, 0xD3F0, 0x8FFC, 0xD3F1, 0x8FF6, 0xD3F2, 0x90D6, 0xD3F3, 0x90E0, 0xD3F4, 0x90D9, 0xD3F5, 0x90DA, + 0xD3F6, 0x90E3, 0xD3F7, 0x90DF, 0xD3F8, 0x90E5, 0xD3F9, 0x90D8, 0xD3FA, 0x90DB, 0xD3FB, 0x90D7, 0xD3FC, 0x90DC, 0xD3FD, 0x90E4, + 0xD3FE, 0x9150, 0xD440, 0x914E, 0xD441, 0x914F, 0xD442, 0x91D5, 0xD443, 0x91E2, 0xD444, 0x91DA, 0xD445, 0x965C, 0xD446, 0x965F, + 0xD447, 0x96BC, 0xD448, 0x98E3, 0xD449, 0x9ADF, 0xD44A, 0x9B2F, 0xD44B, 0x4E7F, 0xD44C, 0x5070, 0xD44D, 0x506A, 0xD44E, 0x5061, + 0xD44F, 0x505E, 0xD450, 0x5060, 0xD451, 0x5053, 0xD452, 0x504B, 0xD453, 0x505D, 0xD454, 0x5072, 0xD455, 0x5048, 0xD456, 0x504D, + 0xD457, 0x5041, 0xD458, 0x505B, 0xD459, 0x504A, 0xD45A, 0x5062, 0xD45B, 0x5015, 0xD45C, 0x5045, 0xD45D, 0x505F, 0xD45E, 0x5069, + 0xD45F, 0x506B, 0xD460, 0x5063, 0xD461, 0x5064, 0xD462, 0x5046, 0xD463, 0x5040, 0xD464, 0x506E, 0xD465, 0x5073, 0xD466, 0x5057, + 0xD467, 0x5051, 0xD468, 0x51D0, 0xD469, 0x526B, 0xD46A, 0x526D, 0xD46B, 0x526C, 0xD46C, 0x526E, 0xD46D, 0x52D6, 0xD46E, 0x52D3, + 0xD46F, 0x532D, 0xD470, 0x539C, 0xD471, 0x5575, 0xD472, 0x5576, 0xD473, 0x553C, 0xD474, 0x554D, 0xD475, 0x5550, 0xD476, 0x5534, + 0xD477, 0x552A, 0xD478, 0x5551, 0xD479, 0x5562, 0xD47A, 0x5536, 0xD47B, 0x5535, 0xD47C, 0x5530, 0xD47D, 0x5552, 0xD47E, 0x5545, + 0xD4A1, 0x550C, 0xD4A2, 0x5532, 0xD4A3, 0x5565, 0xD4A4, 0x554E, 0xD4A5, 0x5539, 0xD4A6, 0x5548, 0xD4A7, 0x552D, 0xD4A8, 0x553B, + 0xD4A9, 0x5540, 0xD4AA, 0x554B, 0xD4AB, 0x570A, 0xD4AC, 0x5707, 0xD4AD, 0x57FB, 0xD4AE, 0x5814, 0xD4AF, 0x57E2, 0xD4B0, 0x57F6, + 0xD4B1, 0x57DC, 0xD4B2, 0x57F4, 0xD4B3, 0x5800, 0xD4B4, 0x57ED, 0xD4B5, 0x57FD, 0xD4B6, 0x5808, 0xD4B7, 0x57F8, 0xD4B8, 0x580B, + 0xD4B9, 0x57F3, 0xD4BA, 0x57CF, 0xD4BB, 0x5807, 0xD4BC, 0x57EE, 0xD4BD, 0x57E3, 0xD4BE, 0x57F2, 0xD4BF, 0x57E5, 0xD4C0, 0x57EC, + 0xD4C1, 0x57E1, 0xD4C2, 0x580E, 0xD4C3, 0x57FC, 0xD4C4, 0x5810, 0xD4C5, 0x57E7, 0xD4C6, 0x5801, 0xD4C7, 0x580C, 0xD4C8, 0x57F1, + 0xD4C9, 0x57E9, 0xD4CA, 0x57F0, 0xD4CB, 0x580D, 0xD4CC, 0x5804, 0xD4CD, 0x595C, 0xD4CE, 0x5A60, 0xD4CF, 0x5A58, 0xD4D0, 0x5A55, + 0xD4D1, 0x5A67, 0xD4D2, 0x5A5E, 0xD4D3, 0x5A38, 0xD4D4, 0x5A35, 0xD4D5, 0x5A6D, 0xD4D6, 0x5A50, 0xD4D7, 0x5A5F, 0xD4D8, 0x5A65, + 0xD4D9, 0x5A6C, 0xD4DA, 0x5A53, 0xD4DB, 0x5A64, 0xD4DC, 0x5A57, 0xD4DD, 0x5A43, 0xD4DE, 0x5A5D, 0xD4DF, 0x5A52, 0xD4E0, 0x5A44, + 0xD4E1, 0x5A5B, 0xD4E2, 0x5A48, 0xD4E3, 0x5A8E, 0xD4E4, 0x5A3E, 0xD4E5, 0x5A4D, 0xD4E6, 0x5A39, 0xD4E7, 0x5A4C, 0xD4E8, 0x5A70, + 0xD4E9, 0x5A69, 0xD4EA, 0x5A47, 0xD4EB, 0x5A51, 0xD4EC, 0x5A56, 0xD4ED, 0x5A42, 0xD4EE, 0x5A5C, 0xD4EF, 0x5B72, 0xD4F0, 0x5B6E, + 0xD4F1, 0x5BC1, 0xD4F2, 0x5BC0, 0xD4F3, 0x5C59, 0xD4F4, 0x5D1E, 0xD4F5, 0x5D0B, 0xD4F6, 0x5D1D, 0xD4F7, 0x5D1A, 0xD4F8, 0x5D20, + 0xD4F9, 0x5D0C, 0xD4FA, 0x5D28, 0xD4FB, 0x5D0D, 0xD4FC, 0x5D26, 0xD4FD, 0x5D25, 0xD4FE, 0x5D0F, 0xD540, 0x5D30, 0xD541, 0x5D12, + 0xD542, 0x5D23, 0xD543, 0x5D1F, 0xD544, 0x5D2E, 0xD545, 0x5E3E, 0xD546, 0x5E34, 0xD547, 0x5EB1, 0xD548, 0x5EB4, 0xD549, 0x5EB9, + 0xD54A, 0x5EB2, 0xD54B, 0x5EB3, 0xD54C, 0x5F36, 0xD54D, 0x5F38, 0xD54E, 0x5F9B, 0xD54F, 0x5F96, 0xD550, 0x5F9F, 0xD551, 0x608A, + 0xD552, 0x6090, 0xD553, 0x6086, 0xD554, 0x60BE, 0xD555, 0x60B0, 0xD556, 0x60BA, 0xD557, 0x60D3, 0xD558, 0x60D4, 0xD559, 0x60CF, + 0xD55A, 0x60E4, 0xD55B, 0x60D9, 0xD55C, 0x60DD, 0xD55D, 0x60C8, 0xD55E, 0x60B1, 0xD55F, 0x60DB, 0xD560, 0x60B7, 0xD561, 0x60CA, + 0xD562, 0x60BF, 0xD563, 0x60C3, 0xD564, 0x60CD, 0xD565, 0x60C0, 0xD566, 0x6332, 0xD567, 0x6365, 0xD568, 0x638A, 0xD569, 0x6382, + 0xD56A, 0x637D, 0xD56B, 0x63BD, 0xD56C, 0x639E, 0xD56D, 0x63AD, 0xD56E, 0x639D, 0xD56F, 0x6397, 0xD570, 0x63AB, 0xD571, 0x638E, + 0xD572, 0x636F, 0xD573, 0x6387, 0xD574, 0x6390, 0xD575, 0x636E, 0xD576, 0x63AF, 0xD577, 0x6375, 0xD578, 0x639C, 0xD579, 0x636D, + 0xD57A, 0x63AE, 0xD57B, 0x637C, 0xD57C, 0x63A4, 0xD57D, 0x633B, 0xD57E, 0x639F, 0xD5A1, 0x6378, 0xD5A2, 0x6385, 0xD5A3, 0x6381, + 0xD5A4, 0x6391, 0xD5A5, 0x638D, 0xD5A6, 0x6370, 0xD5A7, 0x6553, 0xD5A8, 0x65CD, 0xD5A9, 0x6665, 0xD5AA, 0x6661, 0xD5AB, 0x665B, + 0xD5AC, 0x6659, 0xD5AD, 0x665C, 0xD5AE, 0x6662, 0xD5AF, 0x6718, 0xD5B0, 0x6879, 0xD5B1, 0x6887, 0xD5B2, 0x6890, 0xD5B3, 0x689C, + 0xD5B4, 0x686D, 0xD5B5, 0x686E, 0xD5B6, 0x68AE, 0xD5B7, 0x68AB, 0xD5B8, 0x6956, 0xD5B9, 0x686F, 0xD5BA, 0x68A3, 0xD5BB, 0x68AC, + 0xD5BC, 0x68A9, 0xD5BD, 0x6875, 0xD5BE, 0x6874, 0xD5BF, 0x68B2, 0xD5C0, 0x688F, 0xD5C1, 0x6877, 0xD5C2, 0x6892, 0xD5C3, 0x687C, + 0xD5C4, 0x686B, 0xD5C5, 0x6872, 0xD5C6, 0x68AA, 0xD5C7, 0x6880, 0xD5C8, 0x6871, 0xD5C9, 0x687E, 0xD5CA, 0x689B, 0xD5CB, 0x6896, + 0xD5CC, 0x688B, 0xD5CD, 0x68A0, 0xD5CE, 0x6889, 0xD5CF, 0x68A4, 0xD5D0, 0x6878, 0xD5D1, 0x687B, 0xD5D2, 0x6891, 0xD5D3, 0x688C, + 0xD5D4, 0x688A, 0xD5D5, 0x687D, 0xD5D6, 0x6B36, 0xD5D7, 0x6B33, 0xD5D8, 0x6B37, 0xD5D9, 0x6B38, 0xD5DA, 0x6B91, 0xD5DB, 0x6B8F, + 0xD5DC, 0x6B8D, 0xD5DD, 0x6B8E, 0xD5DE, 0x6B8C, 0xD5DF, 0x6C2A, 0xD5E0, 0x6DC0, 0xD5E1, 0x6DAB, 0xD5E2, 0x6DB4, 0xD5E3, 0x6DB3, + 0xD5E4, 0x6E74, 0xD5E5, 0x6DAC, 0xD5E6, 0x6DE9, 0xD5E7, 0x6DE2, 0xD5E8, 0x6DB7, 0xD5E9, 0x6DF6, 0xD5EA, 0x6DD4, 0xD5EB, 0x6E00, + 0xD5EC, 0x6DC8, 0xD5ED, 0x6DE0, 0xD5EE, 0x6DDF, 0xD5EF, 0x6DD6, 0xD5F0, 0x6DBE, 0xD5F1, 0x6DE5, 0xD5F2, 0x6DDC, 0xD5F3, 0x6DDD, + 0xD5F4, 0x6DDB, 0xD5F5, 0x6DF4, 0xD5F6, 0x6DCA, 0xD5F7, 0x6DBD, 0xD5F8, 0x6DED, 0xD5F9, 0x6DF0, 0xD5FA, 0x6DBA, 0xD5FB, 0x6DD5, + 0xD5FC, 0x6DC2, 0xD5FD, 0x6DCF, 0xD5FE, 0x6DC9, 0xD640, 0x6DD0, 0xD641, 0x6DF2, 0xD642, 0x6DD3, 0xD643, 0x6DFD, 0xD644, 0x6DD7, + 0xD645, 0x6DCD, 0xD646, 0x6DE3, 0xD647, 0x6DBB, 0xD648, 0x70FA, 0xD649, 0x710D, 0xD64A, 0x70F7, 0xD64B, 0x7117, 0xD64C, 0x70F4, + 0xD64D, 0x710C, 0xD64E, 0x70F0, 0xD64F, 0x7104, 0xD650, 0x70F3, 0xD651, 0x7110, 0xD652, 0x70FC, 0xD653, 0x70FF, 0xD654, 0x7106, + 0xD655, 0x7113, 0xD656, 0x7100, 0xD657, 0x70F8, 0xD658, 0x70F6, 0xD659, 0x710B, 0xD65A, 0x7102, 0xD65B, 0x710E, 0xD65C, 0x727E, + 0xD65D, 0x727B, 0xD65E, 0x727C, 0xD65F, 0x727F, 0xD660, 0x731D, 0xD661, 0x7317, 0xD662, 0x7307, 0xD663, 0x7311, 0xD664, 0x7318, + 0xD665, 0x730A, 0xD666, 0x7308, 0xD667, 0x72FF, 0xD668, 0x730F, 0xD669, 0x731E, 0xD66A, 0x7388, 0xD66B, 0x73F6, 0xD66C, 0x73F8, + 0xD66D, 0x73F5, 0xD66E, 0x7404, 0xD66F, 0x7401, 0xD670, 0x73FD, 0xD671, 0x7407, 0xD672, 0x7400, 0xD673, 0x73FA, 0xD674, 0x73FC, + 0xD675, 0x73FF, 0xD676, 0x740C, 0xD677, 0x740B, 0xD678, 0x73F4, 0xD679, 0x7408, 0xD67A, 0x7564, 0xD67B, 0x7563, 0xD67C, 0x75CE, + 0xD67D, 0x75D2, 0xD67E, 0x75CF, 0xD6A1, 0x75CB, 0xD6A2, 0x75CC, 0xD6A3, 0x75D1, 0xD6A4, 0x75D0, 0xD6A5, 0x768F, 0xD6A6, 0x7689, + 0xD6A7, 0x76D3, 0xD6A8, 0x7739, 0xD6A9, 0x772F, 0xD6AA, 0x772D, 0xD6AB, 0x7731, 0xD6AC, 0x7732, 0xD6AD, 0x7734, 0xD6AE, 0x7733, + 0xD6AF, 0x773D, 0xD6B0, 0x7725, 0xD6B1, 0x773B, 0xD6B2, 0x7735, 0xD6B3, 0x7848, 0xD6B4, 0x7852, 0xD6B5, 0x7849, 0xD6B6, 0x784D, + 0xD6B7, 0x784A, 0xD6B8, 0x784C, 0xD6B9, 0x7826, 0xD6BA, 0x7845, 0xD6BB, 0x7850, 0xD6BC, 0x7964, 0xD6BD, 0x7967, 0xD6BE, 0x7969, + 0xD6BF, 0x796A, 0xD6C0, 0x7963, 0xD6C1, 0x796B, 0xD6C2, 0x7961, 0xD6C3, 0x79BB, 0xD6C4, 0x79FA, 0xD6C5, 0x79F8, 0xD6C6, 0x79F6, + 0xD6C7, 0x79F7, 0xD6C8, 0x7A8F, 0xD6C9, 0x7A94, 0xD6CA, 0x7A90, 0xD6CB, 0x7B35, 0xD6CC, 0x7B47, 0xD6CD, 0x7B34, 0xD6CE, 0x7B25, + 0xD6CF, 0x7B30, 0xD6D0, 0x7B22, 0xD6D1, 0x7B24, 0xD6D2, 0x7B33, 0xD6D3, 0x7B18, 0xD6D4, 0x7B2A, 0xD6D5, 0x7B1D, 0xD6D6, 0x7B31, + 0xD6D7, 0x7B2B, 0xD6D8, 0x7B2D, 0xD6D9, 0x7B2F, 0xD6DA, 0x7B32, 0xD6DB, 0x7B38, 0xD6DC, 0x7B1A, 0xD6DD, 0x7B23, 0xD6DE, 0x7C94, + 0xD6DF, 0x7C98, 0xD6E0, 0x7C96, 0xD6E1, 0x7CA3, 0xD6E2, 0x7D35, 0xD6E3, 0x7D3D, 0xD6E4, 0x7D38, 0xD6E5, 0x7D36, 0xD6E6, 0x7D3A, + 0xD6E7, 0x7D45, 0xD6E8, 0x7D2C, 0xD6E9, 0x7D29, 0xD6EA, 0x7D41, 0xD6EB, 0x7D47, 0xD6EC, 0x7D3E, 0xD6ED, 0x7D3F, 0xD6EE, 0x7D4A, + 0xD6EF, 0x7D3B, 0xD6F0, 0x7D28, 0xD6F1, 0x7F63, 0xD6F2, 0x7F95, 0xD6F3, 0x7F9C, 0xD6F4, 0x7F9D, 0xD6F5, 0x7F9B, 0xD6F6, 0x7FCA, + 0xD6F7, 0x7FCB, 0xD6F8, 0x7FCD, 0xD6F9, 0x7FD0, 0xD6FA, 0x7FD1, 0xD6FB, 0x7FC7, 0xD6FC, 0x7FCF, 0xD6FD, 0x7FC9, 0xD6FE, 0x801F, + 0xD740, 0x801E, 0xD741, 0x801B, 0xD742, 0x8047, 0xD743, 0x8043, 0xD744, 0x8048, 0xD745, 0x8118, 0xD746, 0x8125, 0xD747, 0x8119, + 0xD748, 0x811B, 0xD749, 0x812D, 0xD74A, 0x811F, 0xD74B, 0x812C, 0xD74C, 0x811E, 0xD74D, 0x8121, 0xD74E, 0x8115, 0xD74F, 0x8127, + 0xD750, 0x811D, 0xD751, 0x8122, 0xD752, 0x8211, 0xD753, 0x8238, 0xD754, 0x8233, 0xD755, 0x823A, 0xD756, 0x8234, 0xD757, 0x8232, + 0xD758, 0x8274, 0xD759, 0x8390, 0xD75A, 0x83A3, 0xD75B, 0x83A8, 0xD75C, 0x838D, 0xD75D, 0x837A, 0xD75E, 0x8373, 0xD75F, 0x83A4, + 0xD760, 0x8374, 0xD761, 0x838F, 0xD762, 0x8381, 0xD763, 0x8395, 0xD764, 0x8399, 0xD765, 0x8375, 0xD766, 0x8394, 0xD767, 0x83A9, + 0xD768, 0x837D, 0xD769, 0x8383, 0xD76A, 0x838C, 0xD76B, 0x839D, 0xD76C, 0x839B, 0xD76D, 0x83AA, 0xD76E, 0x838B, 0xD76F, 0x837E, + 0xD770, 0x83A5, 0xD771, 0x83AF, 0xD772, 0x8388, 0xD773, 0x8397, 0xD774, 0x83B0, 0xD775, 0x837F, 0xD776, 0x83A6, 0xD777, 0x8387, + 0xD778, 0x83AE, 0xD779, 0x8376, 0xD77A, 0x839A, 0xD77B, 0x8659, 0xD77C, 0x8656, 0xD77D, 0x86BF, 0xD77E, 0x86B7, 0xD7A1, 0x86C2, + 0xD7A2, 0x86C1, 0xD7A3, 0x86C5, 0xD7A4, 0x86BA, 0xD7A5, 0x86B0, 0xD7A6, 0x86C8, 0xD7A7, 0x86B9, 0xD7A8, 0x86B3, 0xD7A9, 0x86B8, + 0xD7AA, 0x86CC, 0xD7AB, 0x86B4, 0xD7AC, 0x86BB, 0xD7AD, 0x86BC, 0xD7AE, 0x86C3, 0xD7AF, 0x86BD, 0xD7B0, 0x86BE, 0xD7B1, 0x8852, + 0xD7B2, 0x8889, 0xD7B3, 0x8895, 0xD7B4, 0x88A8, 0xD7B5, 0x88A2, 0xD7B6, 0x88AA, 0xD7B7, 0x889A, 0xD7B8, 0x8891, 0xD7B9, 0x88A1, + 0xD7BA, 0x889F, 0xD7BB, 0x8898, 0xD7BC, 0x88A7, 0xD7BD, 0x8899, 0xD7BE, 0x889B, 0xD7BF, 0x8897, 0xD7C0, 0x88A4, 0xD7C1, 0x88AC, + 0xD7C2, 0x888C, 0xD7C3, 0x8893, 0xD7C4, 0x888E, 0xD7C5, 0x8982, 0xD7C6, 0x89D6, 0xD7C7, 0x89D9, 0xD7C8, 0x89D5, 0xD7C9, 0x8A30, + 0xD7CA, 0x8A27, 0xD7CB, 0x8A2C, 0xD7CC, 0x8A1E, 0xD7CD, 0x8C39, 0xD7CE, 0x8C3B, 0xD7CF, 0x8C5C, 0xD7D0, 0x8C5D, 0xD7D1, 0x8C7D, + 0xD7D2, 0x8CA5, 0xD7D3, 0x8D7D, 0xD7D4, 0x8D7B, 0xD7D5, 0x8D79, 0xD7D6, 0x8DBC, 0xD7D7, 0x8DC2, 0xD7D8, 0x8DB9, 0xD7D9, 0x8DBF, + 0xD7DA, 0x8DC1, 0xD7DB, 0x8ED8, 0xD7DC, 0x8EDE, 0xD7DD, 0x8EDD, 0xD7DE, 0x8EDC, 0xD7DF, 0x8ED7, 0xD7E0, 0x8EE0, 0xD7E1, 0x8EE1, + 0xD7E2, 0x9024, 0xD7E3, 0x900B, 0xD7E4, 0x9011, 0xD7E5, 0x901C, 0xD7E6, 0x900C, 0xD7E7, 0x9021, 0xD7E8, 0x90EF, 0xD7E9, 0x90EA, + 0xD7EA, 0x90F0, 0xD7EB, 0x90F4, 0xD7EC, 0x90F2, 0xD7ED, 0x90F3, 0xD7EE, 0x90D4, 0xD7EF, 0x90EB, 0xD7F0, 0x90EC, 0xD7F1, 0x90E9, + 0xD7F2, 0x9156, 0xD7F3, 0x9158, 0xD7F4, 0x915A, 0xD7F5, 0x9153, 0xD7F6, 0x9155, 0xD7F7, 0x91EC, 0xD7F8, 0x91F4, 0xD7F9, 0x91F1, + 0xD7FA, 0x91F3, 0xD7FB, 0x91F8, 0xD7FC, 0x91E4, 0xD7FD, 0x91F9, 0xD7FE, 0x91EA, 0xD840, 0x91EB, 0xD841, 0x91F7, 0xD842, 0x91E8, + 0xD843, 0x91EE, 0xD844, 0x957A, 0xD845, 0x9586, 0xD846, 0x9588, 0xD847, 0x967C, 0xD848, 0x966D, 0xD849, 0x966B, 0xD84A, 0x9671, + 0xD84B, 0x966F, 0xD84C, 0x96BF, 0xD84D, 0x976A, 0xD84E, 0x9804, 0xD84F, 0x98E5, 0xD850, 0x9997, 0xD851, 0x509B, 0xD852, 0x5095, + 0xD853, 0x5094, 0xD854, 0x509E, 0xD855, 0x508B, 0xD856, 0x50A3, 0xD857, 0x5083, 0xD858, 0x508C, 0xD859, 0x508E, 0xD85A, 0x509D, + 0xD85B, 0x5068, 0xD85C, 0x509C, 0xD85D, 0x5092, 0xD85E, 0x5082, 0xD85F, 0x5087, 0xD860, 0x515F, 0xD861, 0x51D4, 0xD862, 0x5312, + 0xD863, 0x5311, 0xD864, 0x53A4, 0xD865, 0x53A7, 0xD866, 0x5591, 0xD867, 0x55A8, 0xD868, 0x55A5, 0xD869, 0x55AD, 0xD86A, 0x5577, + 0xD86B, 0x5645, 0xD86C, 0x55A2, 0xD86D, 0x5593, 0xD86E, 0x5588, 0xD86F, 0x558F, 0xD870, 0x55B5, 0xD871, 0x5581, 0xD872, 0x55A3, + 0xD873, 0x5592, 0xD874, 0x55A4, 0xD875, 0x557D, 0xD876, 0x558C, 0xD877, 0x55A6, 0xD878, 0x557F, 0xD879, 0x5595, 0xD87A, 0x55A1, + 0xD87B, 0x558E, 0xD87C, 0x570C, 0xD87D, 0x5829, 0xD87E, 0x5837, 0xD8A1, 0x5819, 0xD8A2, 0x581E, 0xD8A3, 0x5827, 0xD8A4, 0x5823, + 0xD8A5, 0x5828, 0xD8A6, 0x57F5, 0xD8A7, 0x5848, 0xD8A8, 0x5825, 0xD8A9, 0x581C, 0xD8AA, 0x581B, 0xD8AB, 0x5833, 0xD8AC, 0x583F, + 0xD8AD, 0x5836, 0xD8AE, 0x582E, 0xD8AF, 0x5839, 0xD8B0, 0x5838, 0xD8B1, 0x582D, 0xD8B2, 0x582C, 0xD8B3, 0x583B, 0xD8B4, 0x5961, + 0xD8B5, 0x5AAF, 0xD8B6, 0x5A94, 0xD8B7, 0x5A9F, 0xD8B8, 0x5A7A, 0xD8B9, 0x5AA2, 0xD8BA, 0x5A9E, 0xD8BB, 0x5A78, 0xD8BC, 0x5AA6, + 0xD8BD, 0x5A7C, 0xD8BE, 0x5AA5, 0xD8BF, 0x5AAC, 0xD8C0, 0x5A95, 0xD8C1, 0x5AAE, 0xD8C2, 0x5A37, 0xD8C3, 0x5A84, 0xD8C4, 0x5A8A, + 0xD8C5, 0x5A97, 0xD8C6, 0x5A83, 0xD8C7, 0x5A8B, 0xD8C8, 0x5AA9, 0xD8C9, 0x5A7B, 0xD8CA, 0x5A7D, 0xD8CB, 0x5A8C, 0xD8CC, 0x5A9C, + 0xD8CD, 0x5A8F, 0xD8CE, 0x5A93, 0xD8CF, 0x5A9D, 0xD8D0, 0x5BEA, 0xD8D1, 0x5BCD, 0xD8D2, 0x5BCB, 0xD8D3, 0x5BD4, 0xD8D4, 0x5BD1, + 0xD8D5, 0x5BCA, 0xD8D6, 0x5BCE, 0xD8D7, 0x5C0C, 0xD8D8, 0x5C30, 0xD8D9, 0x5D37, 0xD8DA, 0x5D43, 0xD8DB, 0x5D6B, 0xD8DC, 0x5D41, + 0xD8DD, 0x5D4B, 0xD8DE, 0x5D3F, 0xD8DF, 0x5D35, 0xD8E0, 0x5D51, 0xD8E1, 0x5D4E, 0xD8E2, 0x5D55, 0xD8E3, 0x5D33, 0xD8E4, 0x5D3A, + 0xD8E5, 0x5D52, 0xD8E6, 0x5D3D, 0xD8E7, 0x5D31, 0xD8E8, 0x5D59, 0xD8E9, 0x5D42, 0xD8EA, 0x5D39, 0xD8EB, 0x5D49, 0xD8EC, 0x5D38, + 0xD8ED, 0x5D3C, 0xD8EE, 0x5D32, 0xD8EF, 0x5D36, 0xD8F0, 0x5D40, 0xD8F1, 0x5D45, 0xD8F2, 0x5E44, 0xD8F3, 0x5E41, 0xD8F4, 0x5F58, + 0xD8F5, 0x5FA6, 0xD8F6, 0x5FA5, 0xD8F7, 0x5FAB, 0xD8F8, 0x60C9, 0xD8F9, 0x60B9, 0xD8FA, 0x60CC, 0xD8FB, 0x60E2, 0xD8FC, 0x60CE, + 0xD8FD, 0x60C4, 0xD8FE, 0x6114, 0xD940, 0x60F2, 0xD941, 0x610A, 0xD942, 0x6116, 0xD943, 0x6105, 0xD944, 0x60F5, 0xD945, 0x6113, + 0xD946, 0x60F8, 0xD947, 0x60FC, 0xD948, 0x60FE, 0xD949, 0x60C1, 0xD94A, 0x6103, 0xD94B, 0x6118, 0xD94C, 0x611D, 0xD94D, 0x6110, + 0xD94E, 0x60FF, 0xD94F, 0x6104, 0xD950, 0x610B, 0xD951, 0x624A, 0xD952, 0x6394, 0xD953, 0x63B1, 0xD954, 0x63B0, 0xD955, 0x63CE, + 0xD956, 0x63E5, 0xD957, 0x63E8, 0xD958, 0x63EF, 0xD959, 0x63C3, 0xD95A, 0x649D, 0xD95B, 0x63F3, 0xD95C, 0x63CA, 0xD95D, 0x63E0, + 0xD95E, 0x63F6, 0xD95F, 0x63D5, 0xD960, 0x63F2, 0xD961, 0x63F5, 0xD962, 0x6461, 0xD963, 0x63DF, 0xD964, 0x63BE, 0xD965, 0x63DD, + 0xD966, 0x63DC, 0xD967, 0x63C4, 0xD968, 0x63D8, 0xD969, 0x63D3, 0xD96A, 0x63C2, 0xD96B, 0x63C7, 0xD96C, 0x63CC, 0xD96D, 0x63CB, + 0xD96E, 0x63C8, 0xD96F, 0x63F0, 0xD970, 0x63D7, 0xD971, 0x63D9, 0xD972, 0x6532, 0xD973, 0x6567, 0xD974, 0x656A, 0xD975, 0x6564, + 0xD976, 0x655C, 0xD977, 0x6568, 0xD978, 0x6565, 0xD979, 0x658C, 0xD97A, 0x659D, 0xD97B, 0x659E, 0xD97C, 0x65AE, 0xD97D, 0x65D0, + 0xD97E, 0x65D2, 0xD9A1, 0x667C, 0xD9A2, 0x666C, 0xD9A3, 0x667B, 0xD9A4, 0x6680, 0xD9A5, 0x6671, 0xD9A6, 0x6679, 0xD9A7, 0x666A, + 0xD9A8, 0x6672, 0xD9A9, 0x6701, 0xD9AA, 0x690C, 0xD9AB, 0x68D3, 0xD9AC, 0x6904, 0xD9AD, 0x68DC, 0xD9AE, 0x692A, 0xD9AF, 0x68EC, + 0xD9B0, 0x68EA, 0xD9B1, 0x68F1, 0xD9B2, 0x690F, 0xD9B3, 0x68D6, 0xD9B4, 0x68F7, 0xD9B5, 0x68EB, 0xD9B6, 0x68E4, 0xD9B7, 0x68F6, + 0xD9B8, 0x6913, 0xD9B9, 0x6910, 0xD9BA, 0x68F3, 0xD9BB, 0x68E1, 0xD9BC, 0x6907, 0xD9BD, 0x68CC, 0xD9BE, 0x6908, 0xD9BF, 0x6970, + 0xD9C0, 0x68B4, 0xD9C1, 0x6911, 0xD9C2, 0x68EF, 0xD9C3, 0x68C6, 0xD9C4, 0x6914, 0xD9C5, 0x68F8, 0xD9C6, 0x68D0, 0xD9C7, 0x68FD, + 0xD9C8, 0x68FC, 0xD9C9, 0x68E8, 0xD9CA, 0x690B, 0xD9CB, 0x690A, 0xD9CC, 0x6917, 0xD9CD, 0x68CE, 0xD9CE, 0x68C8, 0xD9CF, 0x68DD, + 0xD9D0, 0x68DE, 0xD9D1, 0x68E6, 0xD9D2, 0x68F4, 0xD9D3, 0x68D1, 0xD9D4, 0x6906, 0xD9D5, 0x68D4, 0xD9D6, 0x68E9, 0xD9D7, 0x6915, + 0xD9D8, 0x6925, 0xD9D9, 0x68C7, 0xD9DA, 0x6B39, 0xD9DB, 0x6B3B, 0xD9DC, 0x6B3F, 0xD9DD, 0x6B3C, 0xD9DE, 0x6B94, 0xD9DF, 0x6B97, + 0xD9E0, 0x6B99, 0xD9E1, 0x6B95, 0xD9E2, 0x6BBD, 0xD9E3, 0x6BF0, 0xD9E4, 0x6BF2, 0xD9E5, 0x6BF3, 0xD9E6, 0x6C30, 0xD9E7, 0x6DFC, + 0xD9E8, 0x6E46, 0xD9E9, 0x6E47, 0xD9EA, 0x6E1F, 0xD9EB, 0x6E49, 0xD9EC, 0x6E88, 0xD9ED, 0x6E3C, 0xD9EE, 0x6E3D, 0xD9EF, 0x6E45, + 0xD9F0, 0x6E62, 0xD9F1, 0x6E2B, 0xD9F2, 0x6E3F, 0xD9F3, 0x6E41, 0xD9F4, 0x6E5D, 0xD9F5, 0x6E73, 0xD9F6, 0x6E1C, 0xD9F7, 0x6E33, + 0xD9F8, 0x6E4B, 0xD9F9, 0x6E40, 0xD9FA, 0x6E51, 0xD9FB, 0x6E3B, 0xD9FC, 0x6E03, 0xD9FD, 0x6E2E, 0xD9FE, 0x6E5E, 0xDA40, 0x6E68, + 0xDA41, 0x6E5C, 0xDA42, 0x6E61, 0xDA43, 0x6E31, 0xDA44, 0x6E28, 0xDA45, 0x6E60, 0xDA46, 0x6E71, 0xDA47, 0x6E6B, 0xDA48, 0x6E39, + 0xDA49, 0x6E22, 0xDA4A, 0x6E30, 0xDA4B, 0x6E53, 0xDA4C, 0x6E65, 0xDA4D, 0x6E27, 0xDA4E, 0x6E78, 0xDA4F, 0x6E64, 0xDA50, 0x6E77, + 0xDA51, 0x6E55, 0xDA52, 0x6E79, 0xDA53, 0x6E52, 0xDA54, 0x6E66, 0xDA55, 0x6E35, 0xDA56, 0x6E36, 0xDA57, 0x6E5A, 0xDA58, 0x7120, + 0xDA59, 0x711E, 0xDA5A, 0x712F, 0xDA5B, 0x70FB, 0xDA5C, 0x712E, 0xDA5D, 0x7131, 0xDA5E, 0x7123, 0xDA5F, 0x7125, 0xDA60, 0x7122, + 0xDA61, 0x7132, 0xDA62, 0x711F, 0xDA63, 0x7128, 0xDA64, 0x713A, 0xDA65, 0x711B, 0xDA66, 0x724B, 0xDA67, 0x725A, 0xDA68, 0x7288, + 0xDA69, 0x7289, 0xDA6A, 0x7286, 0xDA6B, 0x7285, 0xDA6C, 0x728B, 0xDA6D, 0x7312, 0xDA6E, 0x730B, 0xDA6F, 0x7330, 0xDA70, 0x7322, + 0xDA71, 0x7331, 0xDA72, 0x7333, 0xDA73, 0x7327, 0xDA74, 0x7332, 0xDA75, 0x732D, 0xDA76, 0x7326, 0xDA77, 0x7323, 0xDA78, 0x7335, + 0xDA79, 0x730C, 0xDA7A, 0x742E, 0xDA7B, 0x742C, 0xDA7C, 0x7430, 0xDA7D, 0x742B, 0xDA7E, 0x7416, 0xDAA1, 0x741A, 0xDAA2, 0x7421, + 0xDAA3, 0x742D, 0xDAA4, 0x7431, 0xDAA5, 0x7424, 0xDAA6, 0x7423, 0xDAA7, 0x741D, 0xDAA8, 0x7429, 0xDAA9, 0x7420, 0xDAAA, 0x7432, + 0xDAAB, 0x74FB, 0xDAAC, 0x752F, 0xDAAD, 0x756F, 0xDAAE, 0x756C, 0xDAAF, 0x75E7, 0xDAB0, 0x75DA, 0xDAB1, 0x75E1, 0xDAB2, 0x75E6, + 0xDAB3, 0x75DD, 0xDAB4, 0x75DF, 0xDAB5, 0x75E4, 0xDAB6, 0x75D7, 0xDAB7, 0x7695, 0xDAB8, 0x7692, 0xDAB9, 0x76DA, 0xDABA, 0x7746, + 0xDABB, 0x7747, 0xDABC, 0x7744, 0xDABD, 0x774D, 0xDABE, 0x7745, 0xDABF, 0x774A, 0xDAC0, 0x774E, 0xDAC1, 0x774B, 0xDAC2, 0x774C, + 0xDAC3, 0x77DE, 0xDAC4, 0x77EC, 0xDAC5, 0x7860, 0xDAC6, 0x7864, 0xDAC7, 0x7865, 0xDAC8, 0x785C, 0xDAC9, 0x786D, 0xDACA, 0x7871, + 0xDACB, 0x786A, 0xDACC, 0x786E, 0xDACD, 0x7870, 0xDACE, 0x7869, 0xDACF, 0x7868, 0xDAD0, 0x785E, 0xDAD1, 0x7862, 0xDAD2, 0x7974, + 0xDAD3, 0x7973, 0xDAD4, 0x7972, 0xDAD5, 0x7970, 0xDAD6, 0x7A02, 0xDAD7, 0x7A0A, 0xDAD8, 0x7A03, 0xDAD9, 0x7A0C, 0xDADA, 0x7A04, + 0xDADB, 0x7A99, 0xDADC, 0x7AE6, 0xDADD, 0x7AE4, 0xDADE, 0x7B4A, 0xDADF, 0x7B3B, 0xDAE0, 0x7B44, 0xDAE1, 0x7B48, 0xDAE2, 0x7B4C, + 0xDAE3, 0x7B4E, 0xDAE4, 0x7B40, 0xDAE5, 0x7B58, 0xDAE6, 0x7B45, 0xDAE7, 0x7CA2, 0xDAE8, 0x7C9E, 0xDAE9, 0x7CA8, 0xDAEA, 0x7CA1, + 0xDAEB, 0x7D58, 0xDAEC, 0x7D6F, 0xDAED, 0x7D63, 0xDAEE, 0x7D53, 0xDAEF, 0x7D56, 0xDAF0, 0x7D67, 0xDAF1, 0x7D6A, 0xDAF2, 0x7D4F, + 0xDAF3, 0x7D6D, 0xDAF4, 0x7D5C, 0xDAF5, 0x7D6B, 0xDAF6, 0x7D52, 0xDAF7, 0x7D54, 0xDAF8, 0x7D69, 0xDAF9, 0x7D51, 0xDAFA, 0x7D5F, + 0xDAFB, 0x7D4E, 0xDAFC, 0x7F3E, 0xDAFD, 0x7F3F, 0xDAFE, 0x7F65, 0xDB40, 0x7F66, 0xDB41, 0x7FA2, 0xDB42, 0x7FA0, 0xDB43, 0x7FA1, + 0xDB44, 0x7FD7, 0xDB45, 0x8051, 0xDB46, 0x804F, 0xDB47, 0x8050, 0xDB48, 0x80FE, 0xDB49, 0x80D4, 0xDB4A, 0x8143, 0xDB4B, 0x814A, + 0xDB4C, 0x8152, 0xDB4D, 0x814F, 0xDB4E, 0x8147, 0xDB4F, 0x813D, 0xDB50, 0x814D, 0xDB51, 0x813A, 0xDB52, 0x81E6, 0xDB53, 0x81EE, + 0xDB54, 0x81F7, 0xDB55, 0x81F8, 0xDB56, 0x81F9, 0xDB57, 0x8204, 0xDB58, 0x823C, 0xDB59, 0x823D, 0xDB5A, 0x823F, 0xDB5B, 0x8275, + 0xDB5C, 0x833B, 0xDB5D, 0x83CF, 0xDB5E, 0x83F9, 0xDB5F, 0x8423, 0xDB60, 0x83C0, 0xDB61, 0x83E8, 0xDB62, 0x8412, 0xDB63, 0x83E7, + 0xDB64, 0x83E4, 0xDB65, 0x83FC, 0xDB66, 0x83F6, 0xDB67, 0x8410, 0xDB68, 0x83C6, 0xDB69, 0x83C8, 0xDB6A, 0x83EB, 0xDB6B, 0x83E3, + 0xDB6C, 0x83BF, 0xDB6D, 0x8401, 0xDB6E, 0x83DD, 0xDB6F, 0x83E5, 0xDB70, 0x83D8, 0xDB71, 0x83FF, 0xDB72, 0x83E1, 0xDB73, 0x83CB, + 0xDB74, 0x83CE, 0xDB75, 0x83D6, 0xDB76, 0x83F5, 0xDB77, 0x83C9, 0xDB78, 0x8409, 0xDB79, 0x840F, 0xDB7A, 0x83DE, 0xDB7B, 0x8411, + 0xDB7C, 0x8406, 0xDB7D, 0x83C2, 0xDB7E, 0x83F3, 0xDBA1, 0x83D5, 0xDBA2, 0x83FA, 0xDBA3, 0x83C7, 0xDBA4, 0x83D1, 0xDBA5, 0x83EA, + 0xDBA6, 0x8413, 0xDBA7, 0x83C3, 0xDBA8, 0x83EC, 0xDBA9, 0x83EE, 0xDBAA, 0x83C4, 0xDBAB, 0x83FB, 0xDBAC, 0x83D7, 0xDBAD, 0x83E2, + 0xDBAE, 0x841B, 0xDBAF, 0x83DB, 0xDBB0, 0x83FE, 0xDBB1, 0x86D8, 0xDBB2, 0x86E2, 0xDBB3, 0x86E6, 0xDBB4, 0x86D3, 0xDBB5, 0x86E3, + 0xDBB6, 0x86DA, 0xDBB7, 0x86EA, 0xDBB8, 0x86DD, 0xDBB9, 0x86EB, 0xDBBA, 0x86DC, 0xDBBB, 0x86EC, 0xDBBC, 0x86E9, 0xDBBD, 0x86D7, + 0xDBBE, 0x86E8, 0xDBBF, 0x86D1, 0xDBC0, 0x8848, 0xDBC1, 0x8856, 0xDBC2, 0x8855, 0xDBC3, 0x88BA, 0xDBC4, 0x88D7, 0xDBC5, 0x88B9, + 0xDBC6, 0x88B8, 0xDBC7, 0x88C0, 0xDBC8, 0x88BE, 0xDBC9, 0x88B6, 0xDBCA, 0x88BC, 0xDBCB, 0x88B7, 0xDBCC, 0x88BD, 0xDBCD, 0x88B2, + 0xDBCE, 0x8901, 0xDBCF, 0x88C9, 0xDBD0, 0x8995, 0xDBD1, 0x8998, 0xDBD2, 0x8997, 0xDBD3, 0x89DD, 0xDBD4, 0x89DA, 0xDBD5, 0x89DB, + 0xDBD6, 0x8A4E, 0xDBD7, 0x8A4D, 0xDBD8, 0x8A39, 0xDBD9, 0x8A59, 0xDBDA, 0x8A40, 0xDBDB, 0x8A57, 0xDBDC, 0x8A58, 0xDBDD, 0x8A44, + 0xDBDE, 0x8A45, 0xDBDF, 0x8A52, 0xDBE0, 0x8A48, 0xDBE1, 0x8A51, 0xDBE2, 0x8A4A, 0xDBE3, 0x8A4C, 0xDBE4, 0x8A4F, 0xDBE5, 0x8C5F, + 0xDBE6, 0x8C81, 0xDBE7, 0x8C80, 0xDBE8, 0x8CBA, 0xDBE9, 0x8CBE, 0xDBEA, 0x8CB0, 0xDBEB, 0x8CB9, 0xDBEC, 0x8CB5, 0xDBED, 0x8D84, + 0xDBEE, 0x8D80, 0xDBEF, 0x8D89, 0xDBF0, 0x8DD8, 0xDBF1, 0x8DD3, 0xDBF2, 0x8DCD, 0xDBF3, 0x8DC7, 0xDBF4, 0x8DD6, 0xDBF5, 0x8DDC, + 0xDBF6, 0x8DCF, 0xDBF7, 0x8DD5, 0xDBF8, 0x8DD9, 0xDBF9, 0x8DC8, 0xDBFA, 0x8DD7, 0xDBFB, 0x8DC5, 0xDBFC, 0x8EEF, 0xDBFD, 0x8EF7, + 0xDBFE, 0x8EFA, 0xDC40, 0x8EF9, 0xDC41, 0x8EE6, 0xDC42, 0x8EEE, 0xDC43, 0x8EE5, 0xDC44, 0x8EF5, 0xDC45, 0x8EE7, 0xDC46, 0x8EE8, + 0xDC47, 0x8EF6, 0xDC48, 0x8EEB, 0xDC49, 0x8EF1, 0xDC4A, 0x8EEC, 0xDC4B, 0x8EF4, 0xDC4C, 0x8EE9, 0xDC4D, 0x902D, 0xDC4E, 0x9034, + 0xDC4F, 0x902F, 0xDC50, 0x9106, 0xDC51, 0x912C, 0xDC52, 0x9104, 0xDC53, 0x90FF, 0xDC54, 0x90FC, 0xDC55, 0x9108, 0xDC56, 0x90F9, + 0xDC57, 0x90FB, 0xDC58, 0x9101, 0xDC59, 0x9100, 0xDC5A, 0x9107, 0xDC5B, 0x9105, 0xDC5C, 0x9103, 0xDC5D, 0x9161, 0xDC5E, 0x9164, + 0xDC5F, 0x915F, 0xDC60, 0x9162, 0xDC61, 0x9160, 0xDC62, 0x9201, 0xDC63, 0x920A, 0xDC64, 0x9225, 0xDC65, 0x9203, 0xDC66, 0x921A, + 0xDC67, 0x9226, 0xDC68, 0x920F, 0xDC69, 0x920C, 0xDC6A, 0x9200, 0xDC6B, 0x9212, 0xDC6C, 0x91FF, 0xDC6D, 0x91FD, 0xDC6E, 0x9206, + 0xDC6F, 0x9204, 0xDC70, 0x9227, 0xDC71, 0x9202, 0xDC72, 0x921C, 0xDC73, 0x9224, 0xDC74, 0x9219, 0xDC75, 0x9217, 0xDC76, 0x9205, + 0xDC77, 0x9216, 0xDC78, 0x957B, 0xDC79, 0x958D, 0xDC7A, 0x958C, 0xDC7B, 0x9590, 0xDC7C, 0x9687, 0xDC7D, 0x967E, 0xDC7E, 0x9688, + 0xDCA1, 0x9689, 0xDCA2, 0x9683, 0xDCA3, 0x9680, 0xDCA4, 0x96C2, 0xDCA5, 0x96C8, 0xDCA6, 0x96C3, 0xDCA7, 0x96F1, 0xDCA8, 0x96F0, + 0xDCA9, 0x976C, 0xDCAA, 0x9770, 0xDCAB, 0x976E, 0xDCAC, 0x9807, 0xDCAD, 0x98A9, 0xDCAE, 0x98EB, 0xDCAF, 0x9CE6, 0xDCB0, 0x9EF9, + 0xDCB1, 0x4E83, 0xDCB2, 0x4E84, 0xDCB3, 0x4EB6, 0xDCB4, 0x50BD, 0xDCB5, 0x50BF, 0xDCB6, 0x50C6, 0xDCB7, 0x50AE, 0xDCB8, 0x50C4, + 0xDCB9, 0x50CA, 0xDCBA, 0x50B4, 0xDCBB, 0x50C8, 0xDCBC, 0x50C2, 0xDCBD, 0x50B0, 0xDCBE, 0x50C1, 0xDCBF, 0x50BA, 0xDCC0, 0x50B1, + 0xDCC1, 0x50CB, 0xDCC2, 0x50C9, 0xDCC3, 0x50B6, 0xDCC4, 0x50B8, 0xDCC5, 0x51D7, 0xDCC6, 0x527A, 0xDCC7, 0x5278, 0xDCC8, 0x527B, + 0xDCC9, 0x527C, 0xDCCA, 0x55C3, 0xDCCB, 0x55DB, 0xDCCC, 0x55CC, 0xDCCD, 0x55D0, 0xDCCE, 0x55CB, 0xDCCF, 0x55CA, 0xDCD0, 0x55DD, + 0xDCD1, 0x55C0, 0xDCD2, 0x55D4, 0xDCD3, 0x55C4, 0xDCD4, 0x55E9, 0xDCD5, 0x55BF, 0xDCD6, 0x55D2, 0xDCD7, 0x558D, 0xDCD8, 0x55CF, + 0xDCD9, 0x55D5, 0xDCDA, 0x55E2, 0xDCDB, 0x55D6, 0xDCDC, 0x55C8, 0xDCDD, 0x55F2, 0xDCDE, 0x55CD, 0xDCDF, 0x55D9, 0xDCE0, 0x55C2, + 0xDCE1, 0x5714, 0xDCE2, 0x5853, 0xDCE3, 0x5868, 0xDCE4, 0x5864, 0xDCE5, 0x584F, 0xDCE6, 0x584D, 0xDCE7, 0x5849, 0xDCE8, 0x586F, + 0xDCE9, 0x5855, 0xDCEA, 0x584E, 0xDCEB, 0x585D, 0xDCEC, 0x5859, 0xDCED, 0x5865, 0xDCEE, 0x585B, 0xDCEF, 0x583D, 0xDCF0, 0x5863, + 0xDCF1, 0x5871, 0xDCF2, 0x58FC, 0xDCF3, 0x5AC7, 0xDCF4, 0x5AC4, 0xDCF5, 0x5ACB, 0xDCF6, 0x5ABA, 0xDCF7, 0x5AB8, 0xDCF8, 0x5AB1, + 0xDCF9, 0x5AB5, 0xDCFA, 0x5AB0, 0xDCFB, 0x5ABF, 0xDCFC, 0x5AC8, 0xDCFD, 0x5ABB, 0xDCFE, 0x5AC6, 0xDD40, 0x5AB7, 0xDD41, 0x5AC0, + 0xDD42, 0x5ACA, 0xDD43, 0x5AB4, 0xDD44, 0x5AB6, 0xDD45, 0x5ACD, 0xDD46, 0x5AB9, 0xDD47, 0x5A90, 0xDD48, 0x5BD6, 0xDD49, 0x5BD8, + 0xDD4A, 0x5BD9, 0xDD4B, 0x5C1F, 0xDD4C, 0x5C33, 0xDD4D, 0x5D71, 0xDD4E, 0x5D63, 0xDD4F, 0x5D4A, 0xDD50, 0x5D65, 0xDD51, 0x5D72, + 0xDD52, 0x5D6C, 0xDD53, 0x5D5E, 0xDD54, 0x5D68, 0xDD55, 0x5D67, 0xDD56, 0x5D62, 0xDD57, 0x5DF0, 0xDD58, 0x5E4F, 0xDD59, 0x5E4E, + 0xDD5A, 0x5E4A, 0xDD5B, 0x5E4D, 0xDD5C, 0x5E4B, 0xDD5D, 0x5EC5, 0xDD5E, 0x5ECC, 0xDD5F, 0x5EC6, 0xDD60, 0x5ECB, 0xDD61, 0x5EC7, + 0xDD62, 0x5F40, 0xDD63, 0x5FAF, 0xDD64, 0x5FAD, 0xDD65, 0x60F7, 0xDD66, 0x6149, 0xDD67, 0x614A, 0xDD68, 0x612B, 0xDD69, 0x6145, + 0xDD6A, 0x6136, 0xDD6B, 0x6132, 0xDD6C, 0x612E, 0xDD6D, 0x6146, 0xDD6E, 0x612F, 0xDD6F, 0x614F, 0xDD70, 0x6129, 0xDD71, 0x6140, + 0xDD72, 0x6220, 0xDD73, 0x9168, 0xDD74, 0x6223, 0xDD75, 0x6225, 0xDD76, 0x6224, 0xDD77, 0x63C5, 0xDD78, 0x63F1, 0xDD79, 0x63EB, + 0xDD7A, 0x6410, 0xDD7B, 0x6412, 0xDD7C, 0x6409, 0xDD7D, 0x6420, 0xDD7E, 0x6424, 0xDDA1, 0x6433, 0xDDA2, 0x6443, 0xDDA3, 0x641F, + 0xDDA4, 0x6415, 0xDDA5, 0x6418, 0xDDA6, 0x6439, 0xDDA7, 0x6437, 0xDDA8, 0x6422, 0xDDA9, 0x6423, 0xDDAA, 0x640C, 0xDDAB, 0x6426, + 0xDDAC, 0x6430, 0xDDAD, 0x6428, 0xDDAE, 0x6441, 0xDDAF, 0x6435, 0xDDB0, 0x642F, 0xDDB1, 0x640A, 0xDDB2, 0x641A, 0xDDB3, 0x6440, + 0xDDB4, 0x6425, 0xDDB5, 0x6427, 0xDDB6, 0x640B, 0xDDB7, 0x63E7, 0xDDB8, 0x641B, 0xDDB9, 0x642E, 0xDDBA, 0x6421, 0xDDBB, 0x640E, + 0xDDBC, 0x656F, 0xDDBD, 0x6592, 0xDDBE, 0x65D3, 0xDDBF, 0x6686, 0xDDC0, 0x668C, 0xDDC1, 0x6695, 0xDDC2, 0x6690, 0xDDC3, 0x668B, + 0xDDC4, 0x668A, 0xDDC5, 0x6699, 0xDDC6, 0x6694, 0xDDC7, 0x6678, 0xDDC8, 0x6720, 0xDDC9, 0x6966, 0xDDCA, 0x695F, 0xDDCB, 0x6938, + 0xDDCC, 0x694E, 0xDDCD, 0x6962, 0xDDCE, 0x6971, 0xDDCF, 0x693F, 0xDDD0, 0x6945, 0xDDD1, 0x696A, 0xDDD2, 0x6939, 0xDDD3, 0x6942, + 0xDDD4, 0x6957, 0xDDD5, 0x6959, 0xDDD6, 0x697A, 0xDDD7, 0x6948, 0xDDD8, 0x6949, 0xDDD9, 0x6935, 0xDDDA, 0x696C, 0xDDDB, 0x6933, + 0xDDDC, 0x693D, 0xDDDD, 0x6965, 0xDDDE, 0x68F0, 0xDDDF, 0x6978, 0xDDE0, 0x6934, 0xDDE1, 0x6969, 0xDDE2, 0x6940, 0xDDE3, 0x696F, + 0xDDE4, 0x6944, 0xDDE5, 0x6976, 0xDDE6, 0x6958, 0xDDE7, 0x6941, 0xDDE8, 0x6974, 0xDDE9, 0x694C, 0xDDEA, 0x693B, 0xDDEB, 0x694B, + 0xDDEC, 0x6937, 0xDDED, 0x695C, 0xDDEE, 0x694F, 0xDDEF, 0x6951, 0xDDF0, 0x6932, 0xDDF1, 0x6952, 0xDDF2, 0x692F, 0xDDF3, 0x697B, + 0xDDF4, 0x693C, 0xDDF5, 0x6B46, 0xDDF6, 0x6B45, 0xDDF7, 0x6B43, 0xDDF8, 0x6B42, 0xDDF9, 0x6B48, 0xDDFA, 0x6B41, 0xDDFB, 0x6B9B, + 0xDDFC, 0xFA0D, 0xDDFD, 0x6BFB, 0xDDFE, 0x6BFC, 0xDE40, 0x6BF9, 0xDE41, 0x6BF7, 0xDE42, 0x6BF8, 0xDE43, 0x6E9B, 0xDE44, 0x6ED6, + 0xDE45, 0x6EC8, 0xDE46, 0x6E8F, 0xDE47, 0x6EC0, 0xDE48, 0x6E9F, 0xDE49, 0x6E93, 0xDE4A, 0x6E94, 0xDE4B, 0x6EA0, 0xDE4C, 0x6EB1, + 0xDE4D, 0x6EB9, 0xDE4E, 0x6EC6, 0xDE4F, 0x6ED2, 0xDE50, 0x6EBD, 0xDE51, 0x6EC1, 0xDE52, 0x6E9E, 0xDE53, 0x6EC9, 0xDE54, 0x6EB7, + 0xDE55, 0x6EB0, 0xDE56, 0x6ECD, 0xDE57, 0x6EA6, 0xDE58, 0x6ECF, 0xDE59, 0x6EB2, 0xDE5A, 0x6EBE, 0xDE5B, 0x6EC3, 0xDE5C, 0x6EDC, + 0xDE5D, 0x6ED8, 0xDE5E, 0x6E99, 0xDE5F, 0x6E92, 0xDE60, 0x6E8E, 0xDE61, 0x6E8D, 0xDE62, 0x6EA4, 0xDE63, 0x6EA1, 0xDE64, 0x6EBF, + 0xDE65, 0x6EB3, 0xDE66, 0x6ED0, 0xDE67, 0x6ECA, 0xDE68, 0x6E97, 0xDE69, 0x6EAE, 0xDE6A, 0x6EA3, 0xDE6B, 0x7147, 0xDE6C, 0x7154, + 0xDE6D, 0x7152, 0xDE6E, 0x7163, 0xDE6F, 0x7160, 0xDE70, 0x7141, 0xDE71, 0x715D, 0xDE72, 0x7162, 0xDE73, 0x7172, 0xDE74, 0x7178, + 0xDE75, 0x716A, 0xDE76, 0x7161, 0xDE77, 0x7142, 0xDE78, 0x7158, 0xDE79, 0x7143, 0xDE7A, 0x714B, 0xDE7B, 0x7170, 0xDE7C, 0x715F, + 0xDE7D, 0x7150, 0xDE7E, 0x7153, 0xDEA1, 0x7144, 0xDEA2, 0x714D, 0xDEA3, 0x715A, 0xDEA4, 0x724F, 0xDEA5, 0x728D, 0xDEA6, 0x728C, + 0xDEA7, 0x7291, 0xDEA8, 0x7290, 0xDEA9, 0x728E, 0xDEAA, 0x733C, 0xDEAB, 0x7342, 0xDEAC, 0x733B, 0xDEAD, 0x733A, 0xDEAE, 0x7340, + 0xDEAF, 0x734A, 0xDEB0, 0x7349, 0xDEB1, 0x7444, 0xDEB2, 0x744A, 0xDEB3, 0x744B, 0xDEB4, 0x7452, 0xDEB5, 0x7451, 0xDEB6, 0x7457, + 0xDEB7, 0x7440, 0xDEB8, 0x744F, 0xDEB9, 0x7450, 0xDEBA, 0x744E, 0xDEBB, 0x7442, 0xDEBC, 0x7446, 0xDEBD, 0x744D, 0xDEBE, 0x7454, + 0xDEBF, 0x74E1, 0xDEC0, 0x74FF, 0xDEC1, 0x74FE, 0xDEC2, 0x74FD, 0xDEC3, 0x751D, 0xDEC4, 0x7579, 0xDEC5, 0x7577, 0xDEC6, 0x6983, + 0xDEC7, 0x75EF, 0xDEC8, 0x760F, 0xDEC9, 0x7603, 0xDECA, 0x75F7, 0xDECB, 0x75FE, 0xDECC, 0x75FC, 0xDECD, 0x75F9, 0xDECE, 0x75F8, + 0xDECF, 0x7610, 0xDED0, 0x75FB, 0xDED1, 0x75F6, 0xDED2, 0x75ED, 0xDED3, 0x75F5, 0xDED4, 0x75FD, 0xDED5, 0x7699, 0xDED6, 0x76B5, + 0xDED7, 0x76DD, 0xDED8, 0x7755, 0xDED9, 0x775F, 0xDEDA, 0x7760, 0xDEDB, 0x7752, 0xDEDC, 0x7756, 0xDEDD, 0x775A, 0xDEDE, 0x7769, + 0xDEDF, 0x7767, 0xDEE0, 0x7754, 0xDEE1, 0x7759, 0xDEE2, 0x776D, 0xDEE3, 0x77E0, 0xDEE4, 0x7887, 0xDEE5, 0x789A, 0xDEE6, 0x7894, + 0xDEE7, 0x788F, 0xDEE8, 0x7884, 0xDEE9, 0x7895, 0xDEEA, 0x7885, 0xDEEB, 0x7886, 0xDEEC, 0x78A1, 0xDEED, 0x7883, 0xDEEE, 0x7879, + 0xDEEF, 0x7899, 0xDEF0, 0x7880, 0xDEF1, 0x7896, 0xDEF2, 0x787B, 0xDEF3, 0x797C, 0xDEF4, 0x7982, 0xDEF5, 0x797D, 0xDEF6, 0x7979, + 0xDEF7, 0x7A11, 0xDEF8, 0x7A18, 0xDEF9, 0x7A19, 0xDEFA, 0x7A12, 0xDEFB, 0x7A17, 0xDEFC, 0x7A15, 0xDEFD, 0x7A22, 0xDEFE, 0x7A13, + 0xDF40, 0x7A1B, 0xDF41, 0x7A10, 0xDF42, 0x7AA3, 0xDF43, 0x7AA2, 0xDF44, 0x7A9E, 0xDF45, 0x7AEB, 0xDF46, 0x7B66, 0xDF47, 0x7B64, + 0xDF48, 0x7B6D, 0xDF49, 0x7B74, 0xDF4A, 0x7B69, 0xDF4B, 0x7B72, 0xDF4C, 0x7B65, 0xDF4D, 0x7B73, 0xDF4E, 0x7B71, 0xDF4F, 0x7B70, + 0xDF50, 0x7B61, 0xDF51, 0x7B78, 0xDF52, 0x7B76, 0xDF53, 0x7B63, 0xDF54, 0x7CB2, 0xDF55, 0x7CB4, 0xDF56, 0x7CAF, 0xDF57, 0x7D88, + 0xDF58, 0x7D86, 0xDF59, 0x7D80, 0xDF5A, 0x7D8D, 0xDF5B, 0x7D7F, 0xDF5C, 0x7D85, 0xDF5D, 0x7D7A, 0xDF5E, 0x7D8E, 0xDF5F, 0x7D7B, + 0xDF60, 0x7D83, 0xDF61, 0x7D7C, 0xDF62, 0x7D8C, 0xDF63, 0x7D94, 0xDF64, 0x7D84, 0xDF65, 0x7D7D, 0xDF66, 0x7D92, 0xDF67, 0x7F6D, + 0xDF68, 0x7F6B, 0xDF69, 0x7F67, 0xDF6A, 0x7F68, 0xDF6B, 0x7F6C, 0xDF6C, 0x7FA6, 0xDF6D, 0x7FA5, 0xDF6E, 0x7FA7, 0xDF6F, 0x7FDB, + 0xDF70, 0x7FDC, 0xDF71, 0x8021, 0xDF72, 0x8164, 0xDF73, 0x8160, 0xDF74, 0x8177, 0xDF75, 0x815C, 0xDF76, 0x8169, 0xDF77, 0x815B, + 0xDF78, 0x8162, 0xDF79, 0x8172, 0xDF7A, 0x6721, 0xDF7B, 0x815E, 0xDF7C, 0x8176, 0xDF7D, 0x8167, 0xDF7E, 0x816F, 0xDFA1, 0x8144, + 0xDFA2, 0x8161, 0xDFA3, 0x821D, 0xDFA4, 0x8249, 0xDFA5, 0x8244, 0xDFA6, 0x8240, 0xDFA7, 0x8242, 0xDFA8, 0x8245, 0xDFA9, 0x84F1, + 0xDFAA, 0x843F, 0xDFAB, 0x8456, 0xDFAC, 0x8476, 0xDFAD, 0x8479, 0xDFAE, 0x848F, 0xDFAF, 0x848D, 0xDFB0, 0x8465, 0xDFB1, 0x8451, + 0xDFB2, 0x8440, 0xDFB3, 0x8486, 0xDFB4, 0x8467, 0xDFB5, 0x8430, 0xDFB6, 0x844D, 0xDFB7, 0x847D, 0xDFB8, 0x845A, 0xDFB9, 0x8459, + 0xDFBA, 0x8474, 0xDFBB, 0x8473, 0xDFBC, 0x845D, 0xDFBD, 0x8507, 0xDFBE, 0x845E, 0xDFBF, 0x8437, 0xDFC0, 0x843A, 0xDFC1, 0x8434, + 0xDFC2, 0x847A, 0xDFC3, 0x8443, 0xDFC4, 0x8478, 0xDFC5, 0x8432, 0xDFC6, 0x8445, 0xDFC7, 0x8429, 0xDFC8, 0x83D9, 0xDFC9, 0x844B, + 0xDFCA, 0x842F, 0xDFCB, 0x8442, 0xDFCC, 0x842D, 0xDFCD, 0x845F, 0xDFCE, 0x8470, 0xDFCF, 0x8439, 0xDFD0, 0x844E, 0xDFD1, 0x844C, + 0xDFD2, 0x8452, 0xDFD3, 0x846F, 0xDFD4, 0x84C5, 0xDFD5, 0x848E, 0xDFD6, 0x843B, 0xDFD7, 0x8447, 0xDFD8, 0x8436, 0xDFD9, 0x8433, + 0xDFDA, 0x8468, 0xDFDB, 0x847E, 0xDFDC, 0x8444, 0xDFDD, 0x842B, 0xDFDE, 0x8460, 0xDFDF, 0x8454, 0xDFE0, 0x846E, 0xDFE1, 0x8450, + 0xDFE2, 0x870B, 0xDFE3, 0x8704, 0xDFE4, 0x86F7, 0xDFE5, 0x870C, 0xDFE6, 0x86FA, 0xDFE7, 0x86D6, 0xDFE8, 0x86F5, 0xDFE9, 0x874D, + 0xDFEA, 0x86F8, 0xDFEB, 0x870E, 0xDFEC, 0x8709, 0xDFED, 0x8701, 0xDFEE, 0x86F6, 0xDFEF, 0x870D, 0xDFF0, 0x8705, 0xDFF1, 0x88D6, + 0xDFF2, 0x88CB, 0xDFF3, 0x88CD, 0xDFF4, 0x88CE, 0xDFF5, 0x88DE, 0xDFF6, 0x88DB, 0xDFF7, 0x88DA, 0xDFF8, 0x88CC, 0xDFF9, 0x88D0, + 0xDFFA, 0x8985, 0xDFFB, 0x899B, 0xDFFC, 0x89DF, 0xDFFD, 0x89E5, 0xDFFE, 0x89E4, 0xE040, 0x89E1, 0xE041, 0x89E0, 0xE042, 0x89E2, + 0xE043, 0x89DC, 0xE044, 0x89E6, 0xE045, 0x8A76, 0xE046, 0x8A86, 0xE047, 0x8A7F, 0xE048, 0x8A61, 0xE049, 0x8A3F, 0xE04A, 0x8A77, + 0xE04B, 0x8A82, 0xE04C, 0x8A84, 0xE04D, 0x8A75, 0xE04E, 0x8A83, 0xE04F, 0x8A81, 0xE050, 0x8A74, 0xE051, 0x8A7A, 0xE052, 0x8C3C, + 0xE053, 0x8C4B, 0xE054, 0x8C4A, 0xE055, 0x8C65, 0xE056, 0x8C64, 0xE057, 0x8C66, 0xE058, 0x8C86, 0xE059, 0x8C84, 0xE05A, 0x8C85, + 0xE05B, 0x8CCC, 0xE05C, 0x8D68, 0xE05D, 0x8D69, 0xE05E, 0x8D91, 0xE05F, 0x8D8C, 0xE060, 0x8D8E, 0xE061, 0x8D8F, 0xE062, 0x8D8D, + 0xE063, 0x8D93, 0xE064, 0x8D94, 0xE065, 0x8D90, 0xE066, 0x8D92, 0xE067, 0x8DF0, 0xE068, 0x8DE0, 0xE069, 0x8DEC, 0xE06A, 0x8DF1, + 0xE06B, 0x8DEE, 0xE06C, 0x8DD0, 0xE06D, 0x8DE9, 0xE06E, 0x8DE3, 0xE06F, 0x8DE2, 0xE070, 0x8DE7, 0xE071, 0x8DF2, 0xE072, 0x8DEB, + 0xE073, 0x8DF4, 0xE074, 0x8F06, 0xE075, 0x8EFF, 0xE076, 0x8F01, 0xE077, 0x8F00, 0xE078, 0x8F05, 0xE079, 0x8F07, 0xE07A, 0x8F08, + 0xE07B, 0x8F02, 0xE07C, 0x8F0B, 0xE07D, 0x9052, 0xE07E, 0x903F, 0xE0A1, 0x9044, 0xE0A2, 0x9049, 0xE0A3, 0x903D, 0xE0A4, 0x9110, + 0xE0A5, 0x910D, 0xE0A6, 0x910F, 0xE0A7, 0x9111, 0xE0A8, 0x9116, 0xE0A9, 0x9114, 0xE0AA, 0x910B, 0xE0AB, 0x910E, 0xE0AC, 0x916E, + 0xE0AD, 0x916F, 0xE0AE, 0x9248, 0xE0AF, 0x9252, 0xE0B0, 0x9230, 0xE0B1, 0x923A, 0xE0B2, 0x9266, 0xE0B3, 0x9233, 0xE0B4, 0x9265, + 0xE0B5, 0x925E, 0xE0B6, 0x9283, 0xE0B7, 0x922E, 0xE0B8, 0x924A, 0xE0B9, 0x9246, 0xE0BA, 0x926D, 0xE0BB, 0x926C, 0xE0BC, 0x924F, + 0xE0BD, 0x9260, 0xE0BE, 0x9267, 0xE0BF, 0x926F, 0xE0C0, 0x9236, 0xE0C1, 0x9261, 0xE0C2, 0x9270, 0xE0C3, 0x9231, 0xE0C4, 0x9254, + 0xE0C5, 0x9263, 0xE0C6, 0x9250, 0xE0C7, 0x9272, 0xE0C8, 0x924E, 0xE0C9, 0x9253, 0xE0CA, 0x924C, 0xE0CB, 0x9256, 0xE0CC, 0x9232, + 0xE0CD, 0x959F, 0xE0CE, 0x959C, 0xE0CF, 0x959E, 0xE0D0, 0x959B, 0xE0D1, 0x9692, 0xE0D2, 0x9693, 0xE0D3, 0x9691, 0xE0D4, 0x9697, + 0xE0D5, 0x96CE, 0xE0D6, 0x96FA, 0xE0D7, 0x96FD, 0xE0D8, 0x96F8, 0xE0D9, 0x96F5, 0xE0DA, 0x9773, 0xE0DB, 0x9777, 0xE0DC, 0x9778, + 0xE0DD, 0x9772, 0xE0DE, 0x980F, 0xE0DF, 0x980D, 0xE0E0, 0x980E, 0xE0E1, 0x98AC, 0xE0E2, 0x98F6, 0xE0E3, 0x98F9, 0xE0E4, 0x99AF, + 0xE0E5, 0x99B2, 0xE0E6, 0x99B0, 0xE0E7, 0x99B5, 0xE0E8, 0x9AAD, 0xE0E9, 0x9AAB, 0xE0EA, 0x9B5B, 0xE0EB, 0x9CEA, 0xE0EC, 0x9CED, + 0xE0ED, 0x9CE7, 0xE0EE, 0x9E80, 0xE0EF, 0x9EFD, 0xE0F0, 0x50E6, 0xE0F1, 0x50D4, 0xE0F2, 0x50D7, 0xE0F3, 0x50E8, 0xE0F4, 0x50F3, + 0xE0F5, 0x50DB, 0xE0F6, 0x50EA, 0xE0F7, 0x50DD, 0xE0F8, 0x50E4, 0xE0F9, 0x50D3, 0xE0FA, 0x50EC, 0xE0FB, 0x50F0, 0xE0FC, 0x50EF, + 0xE0FD, 0x50E3, 0xE0FE, 0x50E0, 0xE140, 0x51D8, 0xE141, 0x5280, 0xE142, 0x5281, 0xE143, 0x52E9, 0xE144, 0x52EB, 0xE145, 0x5330, + 0xE146, 0x53AC, 0xE147, 0x5627, 0xE148, 0x5615, 0xE149, 0x560C, 0xE14A, 0x5612, 0xE14B, 0x55FC, 0xE14C, 0x560F, 0xE14D, 0x561C, + 0xE14E, 0x5601, 0xE14F, 0x5613, 0xE150, 0x5602, 0xE151, 0x55FA, 0xE152, 0x561D, 0xE153, 0x5604, 0xE154, 0x55FF, 0xE155, 0x55F9, + 0xE156, 0x5889, 0xE157, 0x587C, 0xE158, 0x5890, 0xE159, 0x5898, 0xE15A, 0x5886, 0xE15B, 0x5881, 0xE15C, 0x587F, 0xE15D, 0x5874, + 0xE15E, 0x588B, 0xE15F, 0x587A, 0xE160, 0x5887, 0xE161, 0x5891, 0xE162, 0x588E, 0xE163, 0x5876, 0xE164, 0x5882, 0xE165, 0x5888, + 0xE166, 0x587B, 0xE167, 0x5894, 0xE168, 0x588F, 0xE169, 0x58FE, 0xE16A, 0x596B, 0xE16B, 0x5ADC, 0xE16C, 0x5AEE, 0xE16D, 0x5AE5, + 0xE16E, 0x5AD5, 0xE16F, 0x5AEA, 0xE170, 0x5ADA, 0xE171, 0x5AED, 0xE172, 0x5AEB, 0xE173, 0x5AF3, 0xE174, 0x5AE2, 0xE175, 0x5AE0, + 0xE176, 0x5ADB, 0xE177, 0x5AEC, 0xE178, 0x5ADE, 0xE179, 0x5ADD, 0xE17A, 0x5AD9, 0xE17B, 0x5AE8, 0xE17C, 0x5ADF, 0xE17D, 0x5B77, + 0xE17E, 0x5BE0, 0xE1A1, 0x5BE3, 0xE1A2, 0x5C63, 0xE1A3, 0x5D82, 0xE1A4, 0x5D80, 0xE1A5, 0x5D7D, 0xE1A6, 0x5D86, 0xE1A7, 0x5D7A, + 0xE1A8, 0x5D81, 0xE1A9, 0x5D77, 0xE1AA, 0x5D8A, 0xE1AB, 0x5D89, 0xE1AC, 0x5D88, 0xE1AD, 0x5D7E, 0xE1AE, 0x5D7C, 0xE1AF, 0x5D8D, + 0xE1B0, 0x5D79, 0xE1B1, 0x5D7F, 0xE1B2, 0x5E58, 0xE1B3, 0x5E59, 0xE1B4, 0x5E53, 0xE1B5, 0x5ED8, 0xE1B6, 0x5ED1, 0xE1B7, 0x5ED7, + 0xE1B8, 0x5ECE, 0xE1B9, 0x5EDC, 0xE1BA, 0x5ED5, 0xE1BB, 0x5ED9, 0xE1BC, 0x5ED2, 0xE1BD, 0x5ED4, 0xE1BE, 0x5F44, 0xE1BF, 0x5F43, + 0xE1C0, 0x5F6F, 0xE1C1, 0x5FB6, 0xE1C2, 0x612C, 0xE1C3, 0x6128, 0xE1C4, 0x6141, 0xE1C5, 0x615E, 0xE1C6, 0x6171, 0xE1C7, 0x6173, + 0xE1C8, 0x6152, 0xE1C9, 0x6153, 0xE1CA, 0x6172, 0xE1CB, 0x616C, 0xE1CC, 0x6180, 0xE1CD, 0x6174, 0xE1CE, 0x6154, 0xE1CF, 0x617A, + 0xE1D0, 0x615B, 0xE1D1, 0x6165, 0xE1D2, 0x613B, 0xE1D3, 0x616A, 0xE1D4, 0x6161, 0xE1D5, 0x6156, 0xE1D6, 0x6229, 0xE1D7, 0x6227, + 0xE1D8, 0x622B, 0xE1D9, 0x642B, 0xE1DA, 0x644D, 0xE1DB, 0x645B, 0xE1DC, 0x645D, 0xE1DD, 0x6474, 0xE1DE, 0x6476, 0xE1DF, 0x6472, + 0xE1E0, 0x6473, 0xE1E1, 0x647D, 0xE1E2, 0x6475, 0xE1E3, 0x6466, 0xE1E4, 0x64A6, 0xE1E5, 0x644E, 0xE1E6, 0x6482, 0xE1E7, 0x645E, + 0xE1E8, 0x645C, 0xE1E9, 0x644B, 0xE1EA, 0x6453, 0xE1EB, 0x6460, 0xE1EC, 0x6450, 0xE1ED, 0x647F, 0xE1EE, 0x643F, 0xE1EF, 0x646C, + 0xE1F0, 0x646B, 0xE1F1, 0x6459, 0xE1F2, 0x6465, 0xE1F3, 0x6477, 0xE1F4, 0x6573, 0xE1F5, 0x65A0, 0xE1F6, 0x66A1, 0xE1F7, 0x66A0, + 0xE1F8, 0x669F, 0xE1F9, 0x6705, 0xE1FA, 0x6704, 0xE1FB, 0x6722, 0xE1FC, 0x69B1, 0xE1FD, 0x69B6, 0xE1FE, 0x69C9, 0xE240, 0x69A0, + 0xE241, 0x69CE, 0xE242, 0x6996, 0xE243, 0x69B0, 0xE244, 0x69AC, 0xE245, 0x69BC, 0xE246, 0x6991, 0xE247, 0x6999, 0xE248, 0x698E, + 0xE249, 0x69A7, 0xE24A, 0x698D, 0xE24B, 0x69A9, 0xE24C, 0x69BE, 0xE24D, 0x69AF, 0xE24E, 0x69BF, 0xE24F, 0x69C4, 0xE250, 0x69BD, + 0xE251, 0x69A4, 0xE252, 0x69D4, 0xE253, 0x69B9, 0xE254, 0x69CA, 0xE255, 0x699A, 0xE256, 0x69CF, 0xE257, 0x69B3, 0xE258, 0x6993, + 0xE259, 0x69AA, 0xE25A, 0x69A1, 0xE25B, 0x699E, 0xE25C, 0x69D9, 0xE25D, 0x6997, 0xE25E, 0x6990, 0xE25F, 0x69C2, 0xE260, 0x69B5, + 0xE261, 0x69A5, 0xE262, 0x69C6, 0xE263, 0x6B4A, 0xE264, 0x6B4D, 0xE265, 0x6B4B, 0xE266, 0x6B9E, 0xE267, 0x6B9F, 0xE268, 0x6BA0, + 0xE269, 0x6BC3, 0xE26A, 0x6BC4, 0xE26B, 0x6BFE, 0xE26C, 0x6ECE, 0xE26D, 0x6EF5, 0xE26E, 0x6EF1, 0xE26F, 0x6F03, 0xE270, 0x6F25, + 0xE271, 0x6EF8, 0xE272, 0x6F37, 0xE273, 0x6EFB, 0xE274, 0x6F2E, 0xE275, 0x6F09, 0xE276, 0x6F4E, 0xE277, 0x6F19, 0xE278, 0x6F1A, + 0xE279, 0x6F27, 0xE27A, 0x6F18, 0xE27B, 0x6F3B, 0xE27C, 0x6F12, 0xE27D, 0x6EED, 0xE27E, 0x6F0A, 0xE2A1, 0x6F36, 0xE2A2, 0x6F73, + 0xE2A3, 0x6EF9, 0xE2A4, 0x6EEE, 0xE2A5, 0x6F2D, 0xE2A6, 0x6F40, 0xE2A7, 0x6F30, 0xE2A8, 0x6F3C, 0xE2A9, 0x6F35, 0xE2AA, 0x6EEB, + 0xE2AB, 0x6F07, 0xE2AC, 0x6F0E, 0xE2AD, 0x6F43, 0xE2AE, 0x6F05, 0xE2AF, 0x6EFD, 0xE2B0, 0x6EF6, 0xE2B1, 0x6F39, 0xE2B2, 0x6F1C, + 0xE2B3, 0x6EFC, 0xE2B4, 0x6F3A, 0xE2B5, 0x6F1F, 0xE2B6, 0x6F0D, 0xE2B7, 0x6F1E, 0xE2B8, 0x6F08, 0xE2B9, 0x6F21, 0xE2BA, 0x7187, + 0xE2BB, 0x7190, 0xE2BC, 0x7189, 0xE2BD, 0x7180, 0xE2BE, 0x7185, 0xE2BF, 0x7182, 0xE2C0, 0x718F, 0xE2C1, 0x717B, 0xE2C2, 0x7186, + 0xE2C3, 0x7181, 0xE2C4, 0x7197, 0xE2C5, 0x7244, 0xE2C6, 0x7253, 0xE2C7, 0x7297, 0xE2C8, 0x7295, 0xE2C9, 0x7293, 0xE2CA, 0x7343, + 0xE2CB, 0x734D, 0xE2CC, 0x7351, 0xE2CD, 0x734C, 0xE2CE, 0x7462, 0xE2CF, 0x7473, 0xE2D0, 0x7471, 0xE2D1, 0x7475, 0xE2D2, 0x7472, + 0xE2D3, 0x7467, 0xE2D4, 0x746E, 0xE2D5, 0x7500, 0xE2D6, 0x7502, 0xE2D7, 0x7503, 0xE2D8, 0x757D, 0xE2D9, 0x7590, 0xE2DA, 0x7616, + 0xE2DB, 0x7608, 0xE2DC, 0x760C, 0xE2DD, 0x7615, 0xE2DE, 0x7611, 0xE2DF, 0x760A, 0xE2E0, 0x7614, 0xE2E1, 0x76B8, 0xE2E2, 0x7781, + 0xE2E3, 0x777C, 0xE2E4, 0x7785, 0xE2E5, 0x7782, 0xE2E6, 0x776E, 0xE2E7, 0x7780, 0xE2E8, 0x776F, 0xE2E9, 0x777E, 0xE2EA, 0x7783, + 0xE2EB, 0x78B2, 0xE2EC, 0x78AA, 0xE2ED, 0x78B4, 0xE2EE, 0x78AD, 0xE2EF, 0x78A8, 0xE2F0, 0x787E, 0xE2F1, 0x78AB, 0xE2F2, 0x789E, + 0xE2F3, 0x78A5, 0xE2F4, 0x78A0, 0xE2F5, 0x78AC, 0xE2F6, 0x78A2, 0xE2F7, 0x78A4, 0xE2F8, 0x7998, 0xE2F9, 0x798A, 0xE2FA, 0x798B, + 0xE2FB, 0x7996, 0xE2FC, 0x7995, 0xE2FD, 0x7994, 0xE2FE, 0x7993, 0xE340, 0x7997, 0xE341, 0x7988, 0xE342, 0x7992, 0xE343, 0x7990, + 0xE344, 0x7A2B, 0xE345, 0x7A4A, 0xE346, 0x7A30, 0xE347, 0x7A2F, 0xE348, 0x7A28, 0xE349, 0x7A26, 0xE34A, 0x7AA8, 0xE34B, 0x7AAB, + 0xE34C, 0x7AAC, 0xE34D, 0x7AEE, 0xE34E, 0x7B88, 0xE34F, 0x7B9C, 0xE350, 0x7B8A, 0xE351, 0x7B91, 0xE352, 0x7B90, 0xE353, 0x7B96, + 0xE354, 0x7B8D, 0xE355, 0x7B8C, 0xE356, 0x7B9B, 0xE357, 0x7B8E, 0xE358, 0x7B85, 0xE359, 0x7B98, 0xE35A, 0x5284, 0xE35B, 0x7B99, + 0xE35C, 0x7BA4, 0xE35D, 0x7B82, 0xE35E, 0x7CBB, 0xE35F, 0x7CBF, 0xE360, 0x7CBC, 0xE361, 0x7CBA, 0xE362, 0x7DA7, 0xE363, 0x7DB7, + 0xE364, 0x7DC2, 0xE365, 0x7DA3, 0xE366, 0x7DAA, 0xE367, 0x7DC1, 0xE368, 0x7DC0, 0xE369, 0x7DC5, 0xE36A, 0x7D9D, 0xE36B, 0x7DCE, + 0xE36C, 0x7DC4, 0xE36D, 0x7DC6, 0xE36E, 0x7DCB, 0xE36F, 0x7DCC, 0xE370, 0x7DAF, 0xE371, 0x7DB9, 0xE372, 0x7D96, 0xE373, 0x7DBC, + 0xE374, 0x7D9F, 0xE375, 0x7DA6, 0xE376, 0x7DAE, 0xE377, 0x7DA9, 0xE378, 0x7DA1, 0xE379, 0x7DC9, 0xE37A, 0x7F73, 0xE37B, 0x7FE2, + 0xE37C, 0x7FE3, 0xE37D, 0x7FE5, 0xE37E, 0x7FDE, 0xE3A1, 0x8024, 0xE3A2, 0x805D, 0xE3A3, 0x805C, 0xE3A4, 0x8189, 0xE3A5, 0x8186, + 0xE3A6, 0x8183, 0xE3A7, 0x8187, 0xE3A8, 0x818D, 0xE3A9, 0x818C, 0xE3AA, 0x818B, 0xE3AB, 0x8215, 0xE3AC, 0x8497, 0xE3AD, 0x84A4, + 0xE3AE, 0x84A1, 0xE3AF, 0x849F, 0xE3B0, 0x84BA, 0xE3B1, 0x84CE, 0xE3B2, 0x84C2, 0xE3B3, 0x84AC, 0xE3B4, 0x84AE, 0xE3B5, 0x84AB, + 0xE3B6, 0x84B9, 0xE3B7, 0x84B4, 0xE3B8, 0x84C1, 0xE3B9, 0x84CD, 0xE3BA, 0x84AA, 0xE3BB, 0x849A, 0xE3BC, 0x84B1, 0xE3BD, 0x84D0, + 0xE3BE, 0x849D, 0xE3BF, 0x84A7, 0xE3C0, 0x84BB, 0xE3C1, 0x84A2, 0xE3C2, 0x8494, 0xE3C3, 0x84C7, 0xE3C4, 0x84CC, 0xE3C5, 0x849B, + 0xE3C6, 0x84A9, 0xE3C7, 0x84AF, 0xE3C8, 0x84A8, 0xE3C9, 0x84D6, 0xE3CA, 0x8498, 0xE3CB, 0x84B6, 0xE3CC, 0x84CF, 0xE3CD, 0x84A0, + 0xE3CE, 0x84D7, 0xE3CF, 0x84D4, 0xE3D0, 0x84D2, 0xE3D1, 0x84DB, 0xE3D2, 0x84B0, 0xE3D3, 0x8491, 0xE3D4, 0x8661, 0xE3D5, 0x8733, + 0xE3D6, 0x8723, 0xE3D7, 0x8728, 0xE3D8, 0x876B, 0xE3D9, 0x8740, 0xE3DA, 0x872E, 0xE3DB, 0x871E, 0xE3DC, 0x8721, 0xE3DD, 0x8719, + 0xE3DE, 0x871B, 0xE3DF, 0x8743, 0xE3E0, 0x872C, 0xE3E1, 0x8741, 0xE3E2, 0x873E, 0xE3E3, 0x8746, 0xE3E4, 0x8720, 0xE3E5, 0x8732, + 0xE3E6, 0x872A, 0xE3E7, 0x872D, 0xE3E8, 0x873C, 0xE3E9, 0x8712, 0xE3EA, 0x873A, 0xE3EB, 0x8731, 0xE3EC, 0x8735, 0xE3ED, 0x8742, + 0xE3EE, 0x8726, 0xE3EF, 0x8727, 0xE3F0, 0x8738, 0xE3F1, 0x8724, 0xE3F2, 0x871A, 0xE3F3, 0x8730, 0xE3F4, 0x8711, 0xE3F5, 0x88F7, + 0xE3F6, 0x88E7, 0xE3F7, 0x88F1, 0xE3F8, 0x88F2, 0xE3F9, 0x88FA, 0xE3FA, 0x88FE, 0xE3FB, 0x88EE, 0xE3FC, 0x88FC, 0xE3FD, 0x88F6, + 0xE3FE, 0x88FB, 0xE440, 0x88F0, 0xE441, 0x88EC, 0xE442, 0x88EB, 0xE443, 0x899D, 0xE444, 0x89A1, 0xE445, 0x899F, 0xE446, 0x899E, + 0xE447, 0x89E9, 0xE448, 0x89EB, 0xE449, 0x89E8, 0xE44A, 0x8AAB, 0xE44B, 0x8A99, 0xE44C, 0x8A8B, 0xE44D, 0x8A92, 0xE44E, 0x8A8F, + 0xE44F, 0x8A96, 0xE450, 0x8C3D, 0xE451, 0x8C68, 0xE452, 0x8C69, 0xE453, 0x8CD5, 0xE454, 0x8CCF, 0xE455, 0x8CD7, 0xE456, 0x8D96, + 0xE457, 0x8E09, 0xE458, 0x8E02, 0xE459, 0x8DFF, 0xE45A, 0x8E0D, 0xE45B, 0x8DFD, 0xE45C, 0x8E0A, 0xE45D, 0x8E03, 0xE45E, 0x8E07, + 0xE45F, 0x8E06, 0xE460, 0x8E05, 0xE461, 0x8DFE, 0xE462, 0x8E00, 0xE463, 0x8E04, 0xE464, 0x8F10, 0xE465, 0x8F11, 0xE466, 0x8F0E, + 0xE467, 0x8F0D, 0xE468, 0x9123, 0xE469, 0x911C, 0xE46A, 0x9120, 0xE46B, 0x9122, 0xE46C, 0x911F, 0xE46D, 0x911D, 0xE46E, 0x911A, + 0xE46F, 0x9124, 0xE470, 0x9121, 0xE471, 0x911B, 0xE472, 0x917A, 0xE473, 0x9172, 0xE474, 0x9179, 0xE475, 0x9173, 0xE476, 0x92A5, + 0xE477, 0x92A4, 0xE478, 0x9276, 0xE479, 0x929B, 0xE47A, 0x927A, 0xE47B, 0x92A0, 0xE47C, 0x9294, 0xE47D, 0x92AA, 0xE47E, 0x928D, + 0xE4A1, 0x92A6, 0xE4A2, 0x929A, 0xE4A3, 0x92AB, 0xE4A4, 0x9279, 0xE4A5, 0x9297, 0xE4A6, 0x927F, 0xE4A7, 0x92A3, 0xE4A8, 0x92EE, + 0xE4A9, 0x928E, 0xE4AA, 0x9282, 0xE4AB, 0x9295, 0xE4AC, 0x92A2, 0xE4AD, 0x927D, 0xE4AE, 0x9288, 0xE4AF, 0x92A1, 0xE4B0, 0x928A, + 0xE4B1, 0x9286, 0xE4B2, 0x928C, 0xE4B3, 0x9299, 0xE4B4, 0x92A7, 0xE4B5, 0x927E, 0xE4B6, 0x9287, 0xE4B7, 0x92A9, 0xE4B8, 0x929D, + 0xE4B9, 0x928B, 0xE4BA, 0x922D, 0xE4BB, 0x969E, 0xE4BC, 0x96A1, 0xE4BD, 0x96FF, 0xE4BE, 0x9758, 0xE4BF, 0x977D, 0xE4C0, 0x977A, + 0xE4C1, 0x977E, 0xE4C2, 0x9783, 0xE4C3, 0x9780, 0xE4C4, 0x9782, 0xE4C5, 0x977B, 0xE4C6, 0x9784, 0xE4C7, 0x9781, 0xE4C8, 0x977F, + 0xE4C9, 0x97CE, 0xE4CA, 0x97CD, 0xE4CB, 0x9816, 0xE4CC, 0x98AD, 0xE4CD, 0x98AE, 0xE4CE, 0x9902, 0xE4CF, 0x9900, 0xE4D0, 0x9907, + 0xE4D1, 0x999D, 0xE4D2, 0x999C, 0xE4D3, 0x99C3, 0xE4D4, 0x99B9, 0xE4D5, 0x99BB, 0xE4D6, 0x99BA, 0xE4D7, 0x99C2, 0xE4D8, 0x99BD, + 0xE4D9, 0x99C7, 0xE4DA, 0x9AB1, 0xE4DB, 0x9AE3, 0xE4DC, 0x9AE7, 0xE4DD, 0x9B3E, 0xE4DE, 0x9B3F, 0xE4DF, 0x9B60, 0xE4E0, 0x9B61, + 0xE4E1, 0x9B5F, 0xE4E2, 0x9CF1, 0xE4E3, 0x9CF2, 0xE4E4, 0x9CF5, 0xE4E5, 0x9EA7, 0xE4E6, 0x50FF, 0xE4E7, 0x5103, 0xE4E8, 0x5130, + 0xE4E9, 0x50F8, 0xE4EA, 0x5106, 0xE4EB, 0x5107, 0xE4EC, 0x50F6, 0xE4ED, 0x50FE, 0xE4EE, 0x510B, 0xE4EF, 0x510C, 0xE4F0, 0x50FD, + 0xE4F1, 0x510A, 0xE4F2, 0x528B, 0xE4F3, 0x528C, 0xE4F4, 0x52F1, 0xE4F5, 0x52EF, 0xE4F6, 0x5648, 0xE4F7, 0x5642, 0xE4F8, 0x564C, + 0xE4F9, 0x5635, 0xE4FA, 0x5641, 0xE4FB, 0x564A, 0xE4FC, 0x5649, 0xE4FD, 0x5646, 0xE4FE, 0x5658, 0xE540, 0x565A, 0xE541, 0x5640, + 0xE542, 0x5633, 0xE543, 0x563D, 0xE544, 0x562C, 0xE545, 0x563E, 0xE546, 0x5638, 0xE547, 0x562A, 0xE548, 0x563A, 0xE549, 0x571A, + 0xE54A, 0x58AB, 0xE54B, 0x589D, 0xE54C, 0x58B1, 0xE54D, 0x58A0, 0xE54E, 0x58A3, 0xE54F, 0x58AF, 0xE550, 0x58AC, 0xE551, 0x58A5, + 0xE552, 0x58A1, 0xE553, 0x58FF, 0xE554, 0x5AFF, 0xE555, 0x5AF4, 0xE556, 0x5AFD, 0xE557, 0x5AF7, 0xE558, 0x5AF6, 0xE559, 0x5B03, + 0xE55A, 0x5AF8, 0xE55B, 0x5B02, 0xE55C, 0x5AF9, 0xE55D, 0x5B01, 0xE55E, 0x5B07, 0xE55F, 0x5B05, 0xE560, 0x5B0F, 0xE561, 0x5C67, + 0xE562, 0x5D99, 0xE563, 0x5D97, 0xE564, 0x5D9F, 0xE565, 0x5D92, 0xE566, 0x5DA2, 0xE567, 0x5D93, 0xE568, 0x5D95, 0xE569, 0x5DA0, + 0xE56A, 0x5D9C, 0xE56B, 0x5DA1, 0xE56C, 0x5D9A, 0xE56D, 0x5D9E, 0xE56E, 0x5E69, 0xE56F, 0x5E5D, 0xE570, 0x5E60, 0xE571, 0x5E5C, + 0xE572, 0x7DF3, 0xE573, 0x5EDB, 0xE574, 0x5EDE, 0xE575, 0x5EE1, 0xE576, 0x5F49, 0xE577, 0x5FB2, 0xE578, 0x618B, 0xE579, 0x6183, + 0xE57A, 0x6179, 0xE57B, 0x61B1, 0xE57C, 0x61B0, 0xE57D, 0x61A2, 0xE57E, 0x6189, 0xE5A1, 0x619B, 0xE5A2, 0x6193, 0xE5A3, 0x61AF, + 0xE5A4, 0x61AD, 0xE5A5, 0x619F, 0xE5A6, 0x6192, 0xE5A7, 0x61AA, 0xE5A8, 0x61A1, 0xE5A9, 0x618D, 0xE5AA, 0x6166, 0xE5AB, 0x61B3, + 0xE5AC, 0x622D, 0xE5AD, 0x646E, 0xE5AE, 0x6470, 0xE5AF, 0x6496, 0xE5B0, 0x64A0, 0xE5B1, 0x6485, 0xE5B2, 0x6497, 0xE5B3, 0x649C, + 0xE5B4, 0x648F, 0xE5B5, 0x648B, 0xE5B6, 0x648A, 0xE5B7, 0x648C, 0xE5B8, 0x64A3, 0xE5B9, 0x649F, 0xE5BA, 0x6468, 0xE5BB, 0x64B1, + 0xE5BC, 0x6498, 0xE5BD, 0x6576, 0xE5BE, 0x657A, 0xE5BF, 0x6579, 0xE5C0, 0x657B, 0xE5C1, 0x65B2, 0xE5C2, 0x65B3, 0xE5C3, 0x66B5, + 0xE5C4, 0x66B0, 0xE5C5, 0x66A9, 0xE5C6, 0x66B2, 0xE5C7, 0x66B7, 0xE5C8, 0x66AA, 0xE5C9, 0x66AF, 0xE5CA, 0x6A00, 0xE5CB, 0x6A06, + 0xE5CC, 0x6A17, 0xE5CD, 0x69E5, 0xE5CE, 0x69F8, 0xE5CF, 0x6A15, 0xE5D0, 0x69F1, 0xE5D1, 0x69E4, 0xE5D2, 0x6A20, 0xE5D3, 0x69FF, + 0xE5D4, 0x69EC, 0xE5D5, 0x69E2, 0xE5D6, 0x6A1B, 0xE5D7, 0x6A1D, 0xE5D8, 0x69FE, 0xE5D9, 0x6A27, 0xE5DA, 0x69F2, 0xE5DB, 0x69EE, + 0xE5DC, 0x6A14, 0xE5DD, 0x69F7, 0xE5DE, 0x69E7, 0xE5DF, 0x6A40, 0xE5E0, 0x6A08, 0xE5E1, 0x69E6, 0xE5E2, 0x69FB, 0xE5E3, 0x6A0D, + 0xE5E4, 0x69FC, 0xE5E5, 0x69EB, 0xE5E6, 0x6A09, 0xE5E7, 0x6A04, 0xE5E8, 0x6A18, 0xE5E9, 0x6A25, 0xE5EA, 0x6A0F, 0xE5EB, 0x69F6, + 0xE5EC, 0x6A26, 0xE5ED, 0x6A07, 0xE5EE, 0x69F4, 0xE5EF, 0x6A16, 0xE5F0, 0x6B51, 0xE5F1, 0x6BA5, 0xE5F2, 0x6BA3, 0xE5F3, 0x6BA2, + 0xE5F4, 0x6BA6, 0xE5F5, 0x6C01, 0xE5F6, 0x6C00, 0xE5F7, 0x6BFF, 0xE5F8, 0x6C02, 0xE5F9, 0x6F41, 0xE5FA, 0x6F26, 0xE5FB, 0x6F7E, + 0xE5FC, 0x6F87, 0xE5FD, 0x6FC6, 0xE5FE, 0x6F92, 0xE640, 0x6F8D, 0xE641, 0x6F89, 0xE642, 0x6F8C, 0xE643, 0x6F62, 0xE644, 0x6F4F, + 0xE645, 0x6F85, 0xE646, 0x6F5A, 0xE647, 0x6F96, 0xE648, 0x6F76, 0xE649, 0x6F6C, 0xE64A, 0x6F82, 0xE64B, 0x6F55, 0xE64C, 0x6F72, + 0xE64D, 0x6F52, 0xE64E, 0x6F50, 0xE64F, 0x6F57, 0xE650, 0x6F94, 0xE651, 0x6F93, 0xE652, 0x6F5D, 0xE653, 0x6F00, 0xE654, 0x6F61, + 0xE655, 0x6F6B, 0xE656, 0x6F7D, 0xE657, 0x6F67, 0xE658, 0x6F90, 0xE659, 0x6F53, 0xE65A, 0x6F8B, 0xE65B, 0x6F69, 0xE65C, 0x6F7F, + 0xE65D, 0x6F95, 0xE65E, 0x6F63, 0xE65F, 0x6F77, 0xE660, 0x6F6A, 0xE661, 0x6F7B, 0xE662, 0x71B2, 0xE663, 0x71AF, 0xE664, 0x719B, + 0xE665, 0x71B0, 0xE666, 0x71A0, 0xE667, 0x719A, 0xE668, 0x71A9, 0xE669, 0x71B5, 0xE66A, 0x719D, 0xE66B, 0x71A5, 0xE66C, 0x719E, + 0xE66D, 0x71A4, 0xE66E, 0x71A1, 0xE66F, 0x71AA, 0xE670, 0x719C, 0xE671, 0x71A7, 0xE672, 0x71B3, 0xE673, 0x7298, 0xE674, 0x729A, + 0xE675, 0x7358, 0xE676, 0x7352, 0xE677, 0x735E, 0xE678, 0x735F, 0xE679, 0x7360, 0xE67A, 0x735D, 0xE67B, 0x735B, 0xE67C, 0x7361, + 0xE67D, 0x735A, 0xE67E, 0x7359, 0xE6A1, 0x7362, 0xE6A2, 0x7487, 0xE6A3, 0x7489, 0xE6A4, 0x748A, 0xE6A5, 0x7486, 0xE6A6, 0x7481, + 0xE6A7, 0x747D, 0xE6A8, 0x7485, 0xE6A9, 0x7488, 0xE6AA, 0x747C, 0xE6AB, 0x7479, 0xE6AC, 0x7508, 0xE6AD, 0x7507, 0xE6AE, 0x757E, + 0xE6AF, 0x7625, 0xE6B0, 0x761E, 0xE6B1, 0x7619, 0xE6B2, 0x761D, 0xE6B3, 0x761C, 0xE6B4, 0x7623, 0xE6B5, 0x761A, 0xE6B6, 0x7628, + 0xE6B7, 0x761B, 0xE6B8, 0x769C, 0xE6B9, 0x769D, 0xE6BA, 0x769E, 0xE6BB, 0x769B, 0xE6BC, 0x778D, 0xE6BD, 0x778F, 0xE6BE, 0x7789, + 0xE6BF, 0x7788, 0xE6C0, 0x78CD, 0xE6C1, 0x78BB, 0xE6C2, 0x78CF, 0xE6C3, 0x78CC, 0xE6C4, 0x78D1, 0xE6C5, 0x78CE, 0xE6C6, 0x78D4, + 0xE6C7, 0x78C8, 0xE6C8, 0x78C3, 0xE6C9, 0x78C4, 0xE6CA, 0x78C9, 0xE6CB, 0x799A, 0xE6CC, 0x79A1, 0xE6CD, 0x79A0, 0xE6CE, 0x799C, + 0xE6CF, 0x79A2, 0xE6D0, 0x799B, 0xE6D1, 0x6B76, 0xE6D2, 0x7A39, 0xE6D3, 0x7AB2, 0xE6D4, 0x7AB4, 0xE6D5, 0x7AB3, 0xE6D6, 0x7BB7, + 0xE6D7, 0x7BCB, 0xE6D8, 0x7BBE, 0xE6D9, 0x7BAC, 0xE6DA, 0x7BCE, 0xE6DB, 0x7BAF, 0xE6DC, 0x7BB9, 0xE6DD, 0x7BCA, 0xE6DE, 0x7BB5, + 0xE6DF, 0x7CC5, 0xE6E0, 0x7CC8, 0xE6E1, 0x7CCC, 0xE6E2, 0x7CCB, 0xE6E3, 0x7DF7, 0xE6E4, 0x7DDB, 0xE6E5, 0x7DEA, 0xE6E6, 0x7DE7, + 0xE6E7, 0x7DD7, 0xE6E8, 0x7DE1, 0xE6E9, 0x7E03, 0xE6EA, 0x7DFA, 0xE6EB, 0x7DE6, 0xE6EC, 0x7DF6, 0xE6ED, 0x7DF1, 0xE6EE, 0x7DF0, + 0xE6EF, 0x7DEE, 0xE6F0, 0x7DDF, 0xE6F1, 0x7F76, 0xE6F2, 0x7FAC, 0xE6F3, 0x7FB0, 0xE6F4, 0x7FAD, 0xE6F5, 0x7FED, 0xE6F6, 0x7FEB, + 0xE6F7, 0x7FEA, 0xE6F8, 0x7FEC, 0xE6F9, 0x7FE6, 0xE6FA, 0x7FE8, 0xE6FB, 0x8064, 0xE6FC, 0x8067, 0xE6FD, 0x81A3, 0xE6FE, 0x819F, + 0xE740, 0x819E, 0xE741, 0x8195, 0xE742, 0x81A2, 0xE743, 0x8199, 0xE744, 0x8197, 0xE745, 0x8216, 0xE746, 0x824F, 0xE747, 0x8253, + 0xE748, 0x8252, 0xE749, 0x8250, 0xE74A, 0x824E, 0xE74B, 0x8251, 0xE74C, 0x8524, 0xE74D, 0x853B, 0xE74E, 0x850F, 0xE74F, 0x8500, + 0xE750, 0x8529, 0xE751, 0x850E, 0xE752, 0x8509, 0xE753, 0x850D, 0xE754, 0x851F, 0xE755, 0x850A, 0xE756, 0x8527, 0xE757, 0x851C, + 0xE758, 0x84FB, 0xE759, 0x852B, 0xE75A, 0x84FA, 0xE75B, 0x8508, 0xE75C, 0x850C, 0xE75D, 0x84F4, 0xE75E, 0x852A, 0xE75F, 0x84F2, + 0xE760, 0x8515, 0xE761, 0x84F7, 0xE762, 0x84EB, 0xE763, 0x84F3, 0xE764, 0x84FC, 0xE765, 0x8512, 0xE766, 0x84EA, 0xE767, 0x84E9, + 0xE768, 0x8516, 0xE769, 0x84FE, 0xE76A, 0x8528, 0xE76B, 0x851D, 0xE76C, 0x852E, 0xE76D, 0x8502, 0xE76E, 0x84FD, 0xE76F, 0x851E, + 0xE770, 0x84F6, 0xE771, 0x8531, 0xE772, 0x8526, 0xE773, 0x84E7, 0xE774, 0x84E8, 0xE775, 0x84F0, 0xE776, 0x84EF, 0xE777, 0x84F9, + 0xE778, 0x8518, 0xE779, 0x8520, 0xE77A, 0x8530, 0xE77B, 0x850B, 0xE77C, 0x8519, 0xE77D, 0x852F, 0xE77E, 0x8662, 0xE7A1, 0x8756, + 0xE7A2, 0x8763, 0xE7A3, 0x8764, 0xE7A4, 0x8777, 0xE7A5, 0x87E1, 0xE7A6, 0x8773, 0xE7A7, 0x8758, 0xE7A8, 0x8754, 0xE7A9, 0x875B, + 0xE7AA, 0x8752, 0xE7AB, 0x8761, 0xE7AC, 0x875A, 0xE7AD, 0x8751, 0xE7AE, 0x875E, 0xE7AF, 0x876D, 0xE7B0, 0x876A, 0xE7B1, 0x8750, + 0xE7B2, 0x874E, 0xE7B3, 0x875F, 0xE7B4, 0x875D, 0xE7B5, 0x876F, 0xE7B6, 0x876C, 0xE7B7, 0x877A, 0xE7B8, 0x876E, 0xE7B9, 0x875C, + 0xE7BA, 0x8765, 0xE7BB, 0x874F, 0xE7BC, 0x877B, 0xE7BD, 0x8775, 0xE7BE, 0x8762, 0xE7BF, 0x8767, 0xE7C0, 0x8769, 0xE7C1, 0x885A, + 0xE7C2, 0x8905, 0xE7C3, 0x890C, 0xE7C4, 0x8914, 0xE7C5, 0x890B, 0xE7C6, 0x8917, 0xE7C7, 0x8918, 0xE7C8, 0x8919, 0xE7C9, 0x8906, + 0xE7CA, 0x8916, 0xE7CB, 0x8911, 0xE7CC, 0x890E, 0xE7CD, 0x8909, 0xE7CE, 0x89A2, 0xE7CF, 0x89A4, 0xE7D0, 0x89A3, 0xE7D1, 0x89ED, + 0xE7D2, 0x89F0, 0xE7D3, 0x89EC, 0xE7D4, 0x8ACF, 0xE7D5, 0x8AC6, 0xE7D6, 0x8AB8, 0xE7D7, 0x8AD3, 0xE7D8, 0x8AD1, 0xE7D9, 0x8AD4, + 0xE7DA, 0x8AD5, 0xE7DB, 0x8ABB, 0xE7DC, 0x8AD7, 0xE7DD, 0x8ABE, 0xE7DE, 0x8AC0, 0xE7DF, 0x8AC5, 0xE7E0, 0x8AD8, 0xE7E1, 0x8AC3, + 0xE7E2, 0x8ABA, 0xE7E3, 0x8ABD, 0xE7E4, 0x8AD9, 0xE7E5, 0x8C3E, 0xE7E6, 0x8C4D, 0xE7E7, 0x8C8F, 0xE7E8, 0x8CE5, 0xE7E9, 0x8CDF, + 0xE7EA, 0x8CD9, 0xE7EB, 0x8CE8, 0xE7EC, 0x8CDA, 0xE7ED, 0x8CDD, 0xE7EE, 0x8CE7, 0xE7EF, 0x8DA0, 0xE7F0, 0x8D9C, 0xE7F1, 0x8DA1, + 0xE7F2, 0x8D9B, 0xE7F3, 0x8E20, 0xE7F4, 0x8E23, 0xE7F5, 0x8E25, 0xE7F6, 0x8E24, 0xE7F7, 0x8E2E, 0xE7F8, 0x8E15, 0xE7F9, 0x8E1B, + 0xE7FA, 0x8E16, 0xE7FB, 0x8E11, 0xE7FC, 0x8E19, 0xE7FD, 0x8E26, 0xE7FE, 0x8E27, 0xE840, 0x8E14, 0xE841, 0x8E12, 0xE842, 0x8E18, + 0xE843, 0x8E13, 0xE844, 0x8E1C, 0xE845, 0x8E17, 0xE846, 0x8E1A, 0xE847, 0x8F2C, 0xE848, 0x8F24, 0xE849, 0x8F18, 0xE84A, 0x8F1A, + 0xE84B, 0x8F20, 0xE84C, 0x8F23, 0xE84D, 0x8F16, 0xE84E, 0x8F17, 0xE84F, 0x9073, 0xE850, 0x9070, 0xE851, 0x906F, 0xE852, 0x9067, + 0xE853, 0x906B, 0xE854, 0x912F, 0xE855, 0x912B, 0xE856, 0x9129, 0xE857, 0x912A, 0xE858, 0x9132, 0xE859, 0x9126, 0xE85A, 0x912E, + 0xE85B, 0x9185, 0xE85C, 0x9186, 0xE85D, 0x918A, 0xE85E, 0x9181, 0xE85F, 0x9182, 0xE860, 0x9184, 0xE861, 0x9180, 0xE862, 0x92D0, + 0xE863, 0x92C3, 0xE864, 0x92C4, 0xE865, 0x92C0, 0xE866, 0x92D9, 0xE867, 0x92B6, 0xE868, 0x92CF, 0xE869, 0x92F1, 0xE86A, 0x92DF, + 0xE86B, 0x92D8, 0xE86C, 0x92E9, 0xE86D, 0x92D7, 0xE86E, 0x92DD, 0xE86F, 0x92CC, 0xE870, 0x92EF, 0xE871, 0x92C2, 0xE872, 0x92E8, + 0xE873, 0x92CA, 0xE874, 0x92C8, 0xE875, 0x92CE, 0xE876, 0x92E6, 0xE877, 0x92CD, 0xE878, 0x92D5, 0xE879, 0x92C9, 0xE87A, 0x92E0, + 0xE87B, 0x92DE, 0xE87C, 0x92E7, 0xE87D, 0x92D1, 0xE87E, 0x92D3, 0xE8A1, 0x92B5, 0xE8A2, 0x92E1, 0xE8A3, 0x92C6, 0xE8A4, 0x92B4, + 0xE8A5, 0x957C, 0xE8A6, 0x95AC, 0xE8A7, 0x95AB, 0xE8A8, 0x95AE, 0xE8A9, 0x95B0, 0xE8AA, 0x96A4, 0xE8AB, 0x96A2, 0xE8AC, 0x96D3, + 0xE8AD, 0x9705, 0xE8AE, 0x9708, 0xE8AF, 0x9702, 0xE8B0, 0x975A, 0xE8B1, 0x978A, 0xE8B2, 0x978E, 0xE8B3, 0x9788, 0xE8B4, 0x97D0, + 0xE8B5, 0x97CF, 0xE8B6, 0x981E, 0xE8B7, 0x981D, 0xE8B8, 0x9826, 0xE8B9, 0x9829, 0xE8BA, 0x9828, 0xE8BB, 0x9820, 0xE8BC, 0x981B, + 0xE8BD, 0x9827, 0xE8BE, 0x98B2, 0xE8BF, 0x9908, 0xE8C0, 0x98FA, 0xE8C1, 0x9911, 0xE8C2, 0x9914, 0xE8C3, 0x9916, 0xE8C4, 0x9917, + 0xE8C5, 0x9915, 0xE8C6, 0x99DC, 0xE8C7, 0x99CD, 0xE8C8, 0x99CF, 0xE8C9, 0x99D3, 0xE8CA, 0x99D4, 0xE8CB, 0x99CE, 0xE8CC, 0x99C9, + 0xE8CD, 0x99D6, 0xE8CE, 0x99D8, 0xE8CF, 0x99CB, 0xE8D0, 0x99D7, 0xE8D1, 0x99CC, 0xE8D2, 0x9AB3, 0xE8D3, 0x9AEC, 0xE8D4, 0x9AEB, + 0xE8D5, 0x9AF3, 0xE8D6, 0x9AF2, 0xE8D7, 0x9AF1, 0xE8D8, 0x9B46, 0xE8D9, 0x9B43, 0xE8DA, 0x9B67, 0xE8DB, 0x9B74, 0xE8DC, 0x9B71, + 0xE8DD, 0x9B66, 0xE8DE, 0x9B76, 0xE8DF, 0x9B75, 0xE8E0, 0x9B70, 0xE8E1, 0x9B68, 0xE8E2, 0x9B64, 0xE8E3, 0x9B6C, 0xE8E4, 0x9CFC, + 0xE8E5, 0x9CFA, 0xE8E6, 0x9CFD, 0xE8E7, 0x9CFF, 0xE8E8, 0x9CF7, 0xE8E9, 0x9D07, 0xE8EA, 0x9D00, 0xE8EB, 0x9CF9, 0xE8EC, 0x9CFB, + 0xE8ED, 0x9D08, 0xE8EE, 0x9D05, 0xE8EF, 0x9D04, 0xE8F0, 0x9E83, 0xE8F1, 0x9ED3, 0xE8F2, 0x9F0F, 0xE8F3, 0x9F10, 0xE8F4, 0x511C, + 0xE8F5, 0x5113, 0xE8F6, 0x5117, 0xE8F7, 0x511A, 0xE8F8, 0x5111, 0xE8F9, 0x51DE, 0xE8FA, 0x5334, 0xE8FB, 0x53E1, 0xE8FC, 0x5670, + 0xE8FD, 0x5660, 0xE8FE, 0x566E, 0xE940, 0x5673, 0xE941, 0x5666, 0xE942, 0x5663, 0xE943, 0x566D, 0xE944, 0x5672, 0xE945, 0x565E, + 0xE946, 0x5677, 0xE947, 0x571C, 0xE948, 0x571B, 0xE949, 0x58C8, 0xE94A, 0x58BD, 0xE94B, 0x58C9, 0xE94C, 0x58BF, 0xE94D, 0x58BA, + 0xE94E, 0x58C2, 0xE94F, 0x58BC, 0xE950, 0x58C6, 0xE951, 0x5B17, 0xE952, 0x5B19, 0xE953, 0x5B1B, 0xE954, 0x5B21, 0xE955, 0x5B14, + 0xE956, 0x5B13, 0xE957, 0x5B10, 0xE958, 0x5B16, 0xE959, 0x5B28, 0xE95A, 0x5B1A, 0xE95B, 0x5B20, 0xE95C, 0x5B1E, 0xE95D, 0x5BEF, + 0xE95E, 0x5DAC, 0xE95F, 0x5DB1, 0xE960, 0x5DA9, 0xE961, 0x5DA7, 0xE962, 0x5DB5, 0xE963, 0x5DB0, 0xE964, 0x5DAE, 0xE965, 0x5DAA, + 0xE966, 0x5DA8, 0xE967, 0x5DB2, 0xE968, 0x5DAD, 0xE969, 0x5DAF, 0xE96A, 0x5DB4, 0xE96B, 0x5E67, 0xE96C, 0x5E68, 0xE96D, 0x5E66, + 0xE96E, 0x5E6F, 0xE96F, 0x5EE9, 0xE970, 0x5EE7, 0xE971, 0x5EE6, 0xE972, 0x5EE8, 0xE973, 0x5EE5, 0xE974, 0x5F4B, 0xE975, 0x5FBC, + 0xE976, 0x619D, 0xE977, 0x61A8, 0xE978, 0x6196, 0xE979, 0x61C5, 0xE97A, 0x61B4, 0xE97B, 0x61C6, 0xE97C, 0x61C1, 0xE97D, 0x61CC, + 0xE97E, 0x61BA, 0xE9A1, 0x61BF, 0xE9A2, 0x61B8, 0xE9A3, 0x618C, 0xE9A4, 0x64D7, 0xE9A5, 0x64D6, 0xE9A6, 0x64D0, 0xE9A7, 0x64CF, + 0xE9A8, 0x64C9, 0xE9A9, 0x64BD, 0xE9AA, 0x6489, 0xE9AB, 0x64C3, 0xE9AC, 0x64DB, 0xE9AD, 0x64F3, 0xE9AE, 0x64D9, 0xE9AF, 0x6533, + 0xE9B0, 0x657F, 0xE9B1, 0x657C, 0xE9B2, 0x65A2, 0xE9B3, 0x66C8, 0xE9B4, 0x66BE, 0xE9B5, 0x66C0, 0xE9B6, 0x66CA, 0xE9B7, 0x66CB, + 0xE9B8, 0x66CF, 0xE9B9, 0x66BD, 0xE9BA, 0x66BB, 0xE9BB, 0x66BA, 0xE9BC, 0x66CC, 0xE9BD, 0x6723, 0xE9BE, 0x6A34, 0xE9BF, 0x6A66, + 0xE9C0, 0x6A49, 0xE9C1, 0x6A67, 0xE9C2, 0x6A32, 0xE9C3, 0x6A68, 0xE9C4, 0x6A3E, 0xE9C5, 0x6A5D, 0xE9C6, 0x6A6D, 0xE9C7, 0x6A76, + 0xE9C8, 0x6A5B, 0xE9C9, 0x6A51, 0xE9CA, 0x6A28, 0xE9CB, 0x6A5A, 0xE9CC, 0x6A3B, 0xE9CD, 0x6A3F, 0xE9CE, 0x6A41, 0xE9CF, 0x6A6A, + 0xE9D0, 0x6A64, 0xE9D1, 0x6A50, 0xE9D2, 0x6A4F, 0xE9D3, 0x6A54, 0xE9D4, 0x6A6F, 0xE9D5, 0x6A69, 0xE9D6, 0x6A60, 0xE9D7, 0x6A3C, + 0xE9D8, 0x6A5E, 0xE9D9, 0x6A56, 0xE9DA, 0x6A55, 0xE9DB, 0x6A4D, 0xE9DC, 0x6A4E, 0xE9DD, 0x6A46, 0xE9DE, 0x6B55, 0xE9DF, 0x6B54, + 0xE9E0, 0x6B56, 0xE9E1, 0x6BA7, 0xE9E2, 0x6BAA, 0xE9E3, 0x6BAB, 0xE9E4, 0x6BC8, 0xE9E5, 0x6BC7, 0xE9E6, 0x6C04, 0xE9E7, 0x6C03, + 0xE9E8, 0x6C06, 0xE9E9, 0x6FAD, 0xE9EA, 0x6FCB, 0xE9EB, 0x6FA3, 0xE9EC, 0x6FC7, 0xE9ED, 0x6FBC, 0xE9EE, 0x6FCE, 0xE9EF, 0x6FC8, + 0xE9F0, 0x6F5E, 0xE9F1, 0x6FC4, 0xE9F2, 0x6FBD, 0xE9F3, 0x6F9E, 0xE9F4, 0x6FCA, 0xE9F5, 0x6FA8, 0xE9F6, 0x7004, 0xE9F7, 0x6FA5, + 0xE9F8, 0x6FAE, 0xE9F9, 0x6FBA, 0xE9FA, 0x6FAC, 0xE9FB, 0x6FAA, 0xE9FC, 0x6FCF, 0xE9FD, 0x6FBF, 0xE9FE, 0x6FB8, 0xEA40, 0x6FA2, + 0xEA41, 0x6FC9, 0xEA42, 0x6FAB, 0xEA43, 0x6FCD, 0xEA44, 0x6FAF, 0xEA45, 0x6FB2, 0xEA46, 0x6FB0, 0xEA47, 0x71C5, 0xEA48, 0x71C2, + 0xEA49, 0x71BF, 0xEA4A, 0x71B8, 0xEA4B, 0x71D6, 0xEA4C, 0x71C0, 0xEA4D, 0x71C1, 0xEA4E, 0x71CB, 0xEA4F, 0x71D4, 0xEA50, 0x71CA, + 0xEA51, 0x71C7, 0xEA52, 0x71CF, 0xEA53, 0x71BD, 0xEA54, 0x71D8, 0xEA55, 0x71BC, 0xEA56, 0x71C6, 0xEA57, 0x71DA, 0xEA58, 0x71DB, + 0xEA59, 0x729D, 0xEA5A, 0x729E, 0xEA5B, 0x7369, 0xEA5C, 0x7366, 0xEA5D, 0x7367, 0xEA5E, 0x736C, 0xEA5F, 0x7365, 0xEA60, 0x736B, + 0xEA61, 0x736A, 0xEA62, 0x747F, 0xEA63, 0x749A, 0xEA64, 0x74A0, 0xEA65, 0x7494, 0xEA66, 0x7492, 0xEA67, 0x7495, 0xEA68, 0x74A1, + 0xEA69, 0x750B, 0xEA6A, 0x7580, 0xEA6B, 0x762F, 0xEA6C, 0x762D, 0xEA6D, 0x7631, 0xEA6E, 0x763D, 0xEA6F, 0x7633, 0xEA70, 0x763C, + 0xEA71, 0x7635, 0xEA72, 0x7632, 0xEA73, 0x7630, 0xEA74, 0x76BB, 0xEA75, 0x76E6, 0xEA76, 0x779A, 0xEA77, 0x779D, 0xEA78, 0x77A1, + 0xEA79, 0x779C, 0xEA7A, 0x779B, 0xEA7B, 0x77A2, 0xEA7C, 0x77A3, 0xEA7D, 0x7795, 0xEA7E, 0x7799, 0xEAA1, 0x7797, 0xEAA2, 0x78DD, + 0xEAA3, 0x78E9, 0xEAA4, 0x78E5, 0xEAA5, 0x78EA, 0xEAA6, 0x78DE, 0xEAA7, 0x78E3, 0xEAA8, 0x78DB, 0xEAA9, 0x78E1, 0xEAAA, 0x78E2, + 0xEAAB, 0x78ED, 0xEAAC, 0x78DF, 0xEAAD, 0x78E0, 0xEAAE, 0x79A4, 0xEAAF, 0x7A44, 0xEAB0, 0x7A48, 0xEAB1, 0x7A47, 0xEAB2, 0x7AB6, + 0xEAB3, 0x7AB8, 0xEAB4, 0x7AB5, 0xEAB5, 0x7AB1, 0xEAB6, 0x7AB7, 0xEAB7, 0x7BDE, 0xEAB8, 0x7BE3, 0xEAB9, 0x7BE7, 0xEABA, 0x7BDD, + 0xEABB, 0x7BD5, 0xEABC, 0x7BE5, 0xEABD, 0x7BDA, 0xEABE, 0x7BE8, 0xEABF, 0x7BF9, 0xEAC0, 0x7BD4, 0xEAC1, 0x7BEA, 0xEAC2, 0x7BE2, + 0xEAC3, 0x7BDC, 0xEAC4, 0x7BEB, 0xEAC5, 0x7BD8, 0xEAC6, 0x7BDF, 0xEAC7, 0x7CD2, 0xEAC8, 0x7CD4, 0xEAC9, 0x7CD7, 0xEACA, 0x7CD0, + 0xEACB, 0x7CD1, 0xEACC, 0x7E12, 0xEACD, 0x7E21, 0xEACE, 0x7E17, 0xEACF, 0x7E0C, 0xEAD0, 0x7E1F, 0xEAD1, 0x7E20, 0xEAD2, 0x7E13, + 0xEAD3, 0x7E0E, 0xEAD4, 0x7E1C, 0xEAD5, 0x7E15, 0xEAD6, 0x7E1A, 0xEAD7, 0x7E22, 0xEAD8, 0x7E0B, 0xEAD9, 0x7E0F, 0xEADA, 0x7E16, + 0xEADB, 0x7E0D, 0xEADC, 0x7E14, 0xEADD, 0x7E25, 0xEADE, 0x7E24, 0xEADF, 0x7F43, 0xEAE0, 0x7F7B, 0xEAE1, 0x7F7C, 0xEAE2, 0x7F7A, + 0xEAE3, 0x7FB1, 0xEAE4, 0x7FEF, 0xEAE5, 0x802A, 0xEAE6, 0x8029, 0xEAE7, 0x806C, 0xEAE8, 0x81B1, 0xEAE9, 0x81A6, 0xEAEA, 0x81AE, + 0xEAEB, 0x81B9, 0xEAEC, 0x81B5, 0xEAED, 0x81AB, 0xEAEE, 0x81B0, 0xEAEF, 0x81AC, 0xEAF0, 0x81B4, 0xEAF1, 0x81B2, 0xEAF2, 0x81B7, + 0xEAF3, 0x81A7, 0xEAF4, 0x81F2, 0xEAF5, 0x8255, 0xEAF6, 0x8256, 0xEAF7, 0x8257, 0xEAF8, 0x8556, 0xEAF9, 0x8545, 0xEAFA, 0x856B, + 0xEAFB, 0x854D, 0xEAFC, 0x8553, 0xEAFD, 0x8561, 0xEAFE, 0x8558, 0xEB40, 0x8540, 0xEB41, 0x8546, 0xEB42, 0x8564, 0xEB43, 0x8541, + 0xEB44, 0x8562, 0xEB45, 0x8544, 0xEB46, 0x8551, 0xEB47, 0x8547, 0xEB48, 0x8563, 0xEB49, 0x853E, 0xEB4A, 0x855B, 0xEB4B, 0x8571, + 0xEB4C, 0x854E, 0xEB4D, 0x856E, 0xEB4E, 0x8575, 0xEB4F, 0x8555, 0xEB50, 0x8567, 0xEB51, 0x8560, 0xEB52, 0x858C, 0xEB53, 0x8566, + 0xEB54, 0x855D, 0xEB55, 0x8554, 0xEB56, 0x8565, 0xEB57, 0x856C, 0xEB58, 0x8663, 0xEB59, 0x8665, 0xEB5A, 0x8664, 0xEB5B, 0x879B, + 0xEB5C, 0x878F, 0xEB5D, 0x8797, 0xEB5E, 0x8793, 0xEB5F, 0x8792, 0xEB60, 0x8788, 0xEB61, 0x8781, 0xEB62, 0x8796, 0xEB63, 0x8798, + 0xEB64, 0x8779, 0xEB65, 0x8787, 0xEB66, 0x87A3, 0xEB67, 0x8785, 0xEB68, 0x8790, 0xEB69, 0x8791, 0xEB6A, 0x879D, 0xEB6B, 0x8784, + 0xEB6C, 0x8794, 0xEB6D, 0x879C, 0xEB6E, 0x879A, 0xEB6F, 0x8789, 0xEB70, 0x891E, 0xEB71, 0x8926, 0xEB72, 0x8930, 0xEB73, 0x892D, + 0xEB74, 0x892E, 0xEB75, 0x8927, 0xEB76, 0x8931, 0xEB77, 0x8922, 0xEB78, 0x8929, 0xEB79, 0x8923, 0xEB7A, 0x892F, 0xEB7B, 0x892C, + 0xEB7C, 0x891F, 0xEB7D, 0x89F1, 0xEB7E, 0x8AE0, 0xEBA1, 0x8AE2, 0xEBA2, 0x8AF2, 0xEBA3, 0x8AF4, 0xEBA4, 0x8AF5, 0xEBA5, 0x8ADD, + 0xEBA6, 0x8B14, 0xEBA7, 0x8AE4, 0xEBA8, 0x8ADF, 0xEBA9, 0x8AF0, 0xEBAA, 0x8AC8, 0xEBAB, 0x8ADE, 0xEBAC, 0x8AE1, 0xEBAD, 0x8AE8, + 0xEBAE, 0x8AFF, 0xEBAF, 0x8AEF, 0xEBB0, 0x8AFB, 0xEBB1, 0x8C91, 0xEBB2, 0x8C92, 0xEBB3, 0x8C90, 0xEBB4, 0x8CF5, 0xEBB5, 0x8CEE, + 0xEBB6, 0x8CF1, 0xEBB7, 0x8CF0, 0xEBB8, 0x8CF3, 0xEBB9, 0x8D6C, 0xEBBA, 0x8D6E, 0xEBBB, 0x8DA5, 0xEBBC, 0x8DA7, 0xEBBD, 0x8E33, + 0xEBBE, 0x8E3E, 0xEBBF, 0x8E38, 0xEBC0, 0x8E40, 0xEBC1, 0x8E45, 0xEBC2, 0x8E36, 0xEBC3, 0x8E3C, 0xEBC4, 0x8E3D, 0xEBC5, 0x8E41, + 0xEBC6, 0x8E30, 0xEBC7, 0x8E3F, 0xEBC8, 0x8EBD, 0xEBC9, 0x8F36, 0xEBCA, 0x8F2E, 0xEBCB, 0x8F35, 0xEBCC, 0x8F32, 0xEBCD, 0x8F39, + 0xEBCE, 0x8F37, 0xEBCF, 0x8F34, 0xEBD0, 0x9076, 0xEBD1, 0x9079, 0xEBD2, 0x907B, 0xEBD3, 0x9086, 0xEBD4, 0x90FA, 0xEBD5, 0x9133, + 0xEBD6, 0x9135, 0xEBD7, 0x9136, 0xEBD8, 0x9193, 0xEBD9, 0x9190, 0xEBDA, 0x9191, 0xEBDB, 0x918D, 0xEBDC, 0x918F, 0xEBDD, 0x9327, + 0xEBDE, 0x931E, 0xEBDF, 0x9308, 0xEBE0, 0x931F, 0xEBE1, 0x9306, 0xEBE2, 0x930F, 0xEBE3, 0x937A, 0xEBE4, 0x9338, 0xEBE5, 0x933C, + 0xEBE6, 0x931B, 0xEBE7, 0x9323, 0xEBE8, 0x9312, 0xEBE9, 0x9301, 0xEBEA, 0x9346, 0xEBEB, 0x932D, 0xEBEC, 0x930E, 0xEBED, 0x930D, + 0xEBEE, 0x92CB, 0xEBEF, 0x931D, 0xEBF0, 0x92FA, 0xEBF1, 0x9325, 0xEBF2, 0x9313, 0xEBF3, 0x92F9, 0xEBF4, 0x92F7, 0xEBF5, 0x9334, + 0xEBF6, 0x9302, 0xEBF7, 0x9324, 0xEBF8, 0x92FF, 0xEBF9, 0x9329, 0xEBFA, 0x9339, 0xEBFB, 0x9335, 0xEBFC, 0x932A, 0xEBFD, 0x9314, + 0xEBFE, 0x930C, 0xEC40, 0x930B, 0xEC41, 0x92FE, 0xEC42, 0x9309, 0xEC43, 0x9300, 0xEC44, 0x92FB, 0xEC45, 0x9316, 0xEC46, 0x95BC, + 0xEC47, 0x95CD, 0xEC48, 0x95BE, 0xEC49, 0x95B9, 0xEC4A, 0x95BA, 0xEC4B, 0x95B6, 0xEC4C, 0x95BF, 0xEC4D, 0x95B5, 0xEC4E, 0x95BD, + 0xEC4F, 0x96A9, 0xEC50, 0x96D4, 0xEC51, 0x970B, 0xEC52, 0x9712, 0xEC53, 0x9710, 0xEC54, 0x9799, 0xEC55, 0x9797, 0xEC56, 0x9794, + 0xEC57, 0x97F0, 0xEC58, 0x97F8, 0xEC59, 0x9835, 0xEC5A, 0x982F, 0xEC5B, 0x9832, 0xEC5C, 0x9924, 0xEC5D, 0x991F, 0xEC5E, 0x9927, + 0xEC5F, 0x9929, 0xEC60, 0x999E, 0xEC61, 0x99EE, 0xEC62, 0x99EC, 0xEC63, 0x99E5, 0xEC64, 0x99E4, 0xEC65, 0x99F0, 0xEC66, 0x99E3, + 0xEC67, 0x99EA, 0xEC68, 0x99E9, 0xEC69, 0x99E7, 0xEC6A, 0x9AB9, 0xEC6B, 0x9ABF, 0xEC6C, 0x9AB4, 0xEC6D, 0x9ABB, 0xEC6E, 0x9AF6, + 0xEC6F, 0x9AFA, 0xEC70, 0x9AF9, 0xEC71, 0x9AF7, 0xEC72, 0x9B33, 0xEC73, 0x9B80, 0xEC74, 0x9B85, 0xEC75, 0x9B87, 0xEC76, 0x9B7C, + 0xEC77, 0x9B7E, 0xEC78, 0x9B7B, 0xEC79, 0x9B82, 0xEC7A, 0x9B93, 0xEC7B, 0x9B92, 0xEC7C, 0x9B90, 0xEC7D, 0x9B7A, 0xEC7E, 0x9B95, + 0xECA1, 0x9B7D, 0xECA2, 0x9B88, 0xECA3, 0x9D25, 0xECA4, 0x9D17, 0xECA5, 0x9D20, 0xECA6, 0x9D1E, 0xECA7, 0x9D14, 0xECA8, 0x9D29, + 0xECA9, 0x9D1D, 0xECAA, 0x9D18, 0xECAB, 0x9D22, 0xECAC, 0x9D10, 0xECAD, 0x9D19, 0xECAE, 0x9D1F, 0xECAF, 0x9E88, 0xECB0, 0x9E86, + 0xECB1, 0x9E87, 0xECB2, 0x9EAE, 0xECB3, 0x9EAD, 0xECB4, 0x9ED5, 0xECB5, 0x9ED6, 0xECB6, 0x9EFA, 0xECB7, 0x9F12, 0xECB8, 0x9F3D, + 0xECB9, 0x5126, 0xECBA, 0x5125, 0xECBB, 0x5122, 0xECBC, 0x5124, 0xECBD, 0x5120, 0xECBE, 0x5129, 0xECBF, 0x52F4, 0xECC0, 0x5693, + 0xECC1, 0x568C, 0xECC2, 0x568D, 0xECC3, 0x5686, 0xECC4, 0x5684, 0xECC5, 0x5683, 0xECC6, 0x567E, 0xECC7, 0x5682, 0xECC8, 0x567F, + 0xECC9, 0x5681, 0xECCA, 0x58D6, 0xECCB, 0x58D4, 0xECCC, 0x58CF, 0xECCD, 0x58D2, 0xECCE, 0x5B2D, 0xECCF, 0x5B25, 0xECD0, 0x5B32, + 0xECD1, 0x5B23, 0xECD2, 0x5B2C, 0xECD3, 0x5B27, 0xECD4, 0x5B26, 0xECD5, 0x5B2F, 0xECD6, 0x5B2E, 0xECD7, 0x5B7B, 0xECD8, 0x5BF1, + 0xECD9, 0x5BF2, 0xECDA, 0x5DB7, 0xECDB, 0x5E6C, 0xECDC, 0x5E6A, 0xECDD, 0x5FBE, 0xECDE, 0x5FBB, 0xECDF, 0x61C3, 0xECE0, 0x61B5, + 0xECE1, 0x61BC, 0xECE2, 0x61E7, 0xECE3, 0x61E0, 0xECE4, 0x61E5, 0xECE5, 0x61E4, 0xECE6, 0x61E8, 0xECE7, 0x61DE, 0xECE8, 0x64EF, + 0xECE9, 0x64E9, 0xECEA, 0x64E3, 0xECEB, 0x64EB, 0xECEC, 0x64E4, 0xECED, 0x64E8, 0xECEE, 0x6581, 0xECEF, 0x6580, 0xECF0, 0x65B6, + 0xECF1, 0x65DA, 0xECF2, 0x66D2, 0xECF3, 0x6A8D, 0xECF4, 0x6A96, 0xECF5, 0x6A81, 0xECF6, 0x6AA5, 0xECF7, 0x6A89, 0xECF8, 0x6A9F, + 0xECF9, 0x6A9B, 0xECFA, 0x6AA1, 0xECFB, 0x6A9E, 0xECFC, 0x6A87, 0xECFD, 0x6A93, 0xECFE, 0x6A8E, 0xED40, 0x6A95, 0xED41, 0x6A83, + 0xED42, 0x6AA8, 0xED43, 0x6AA4, 0xED44, 0x6A91, 0xED45, 0x6A7F, 0xED46, 0x6AA6, 0xED47, 0x6A9A, 0xED48, 0x6A85, 0xED49, 0x6A8C, + 0xED4A, 0x6A92, 0xED4B, 0x6B5B, 0xED4C, 0x6BAD, 0xED4D, 0x6C09, 0xED4E, 0x6FCC, 0xED4F, 0x6FA9, 0xED50, 0x6FF4, 0xED51, 0x6FD4, + 0xED52, 0x6FE3, 0xED53, 0x6FDC, 0xED54, 0x6FED, 0xED55, 0x6FE7, 0xED56, 0x6FE6, 0xED57, 0x6FDE, 0xED58, 0x6FF2, 0xED59, 0x6FDD, + 0xED5A, 0x6FE2, 0xED5B, 0x6FE8, 0xED5C, 0x71E1, 0xED5D, 0x71F1, 0xED5E, 0x71E8, 0xED5F, 0x71F2, 0xED60, 0x71E4, 0xED61, 0x71F0, + 0xED62, 0x71E2, 0xED63, 0x7373, 0xED64, 0x736E, 0xED65, 0x736F, 0xED66, 0x7497, 0xED67, 0x74B2, 0xED68, 0x74AB, 0xED69, 0x7490, + 0xED6A, 0x74AA, 0xED6B, 0x74AD, 0xED6C, 0x74B1, 0xED6D, 0x74A5, 0xED6E, 0x74AF, 0xED6F, 0x7510, 0xED70, 0x7511, 0xED71, 0x7512, + 0xED72, 0x750F, 0xED73, 0x7584, 0xED74, 0x7643, 0xED75, 0x7648, 0xED76, 0x7649, 0xED77, 0x7647, 0xED78, 0x76A4, 0xED79, 0x76E9, + 0xED7A, 0x77B5, 0xED7B, 0x77AB, 0xED7C, 0x77B2, 0xED7D, 0x77B7, 0xED7E, 0x77B6, 0xEDA1, 0x77B4, 0xEDA2, 0x77B1, 0xEDA3, 0x77A8, + 0xEDA4, 0x77F0, 0xEDA5, 0x78F3, 0xEDA6, 0x78FD, 0xEDA7, 0x7902, 0xEDA8, 0x78FB, 0xEDA9, 0x78FC, 0xEDAA, 0x78F2, 0xEDAB, 0x7905, + 0xEDAC, 0x78F9, 0xEDAD, 0x78FE, 0xEDAE, 0x7904, 0xEDAF, 0x79AB, 0xEDB0, 0x79A8, 0xEDB1, 0x7A5C, 0xEDB2, 0x7A5B, 0xEDB3, 0x7A56, + 0xEDB4, 0x7A58, 0xEDB5, 0x7A54, 0xEDB6, 0x7A5A, 0xEDB7, 0x7ABE, 0xEDB8, 0x7AC0, 0xEDB9, 0x7AC1, 0xEDBA, 0x7C05, 0xEDBB, 0x7C0F, + 0xEDBC, 0x7BF2, 0xEDBD, 0x7C00, 0xEDBE, 0x7BFF, 0xEDBF, 0x7BFB, 0xEDC0, 0x7C0E, 0xEDC1, 0x7BF4, 0xEDC2, 0x7C0B, 0xEDC3, 0x7BF3, + 0xEDC4, 0x7C02, 0xEDC5, 0x7C09, 0xEDC6, 0x7C03, 0xEDC7, 0x7C01, 0xEDC8, 0x7BF8, 0xEDC9, 0x7BFD, 0xEDCA, 0x7C06, 0xEDCB, 0x7BF0, + 0xEDCC, 0x7BF1, 0xEDCD, 0x7C10, 0xEDCE, 0x7C0A, 0xEDCF, 0x7CE8, 0xEDD0, 0x7E2D, 0xEDD1, 0x7E3C, 0xEDD2, 0x7E42, 0xEDD3, 0x7E33, + 0xEDD4, 0x9848, 0xEDD5, 0x7E38, 0xEDD6, 0x7E2A, 0xEDD7, 0x7E49, 0xEDD8, 0x7E40, 0xEDD9, 0x7E47, 0xEDDA, 0x7E29, 0xEDDB, 0x7E4C, + 0xEDDC, 0x7E30, 0xEDDD, 0x7E3B, 0xEDDE, 0x7E36, 0xEDDF, 0x7E44, 0xEDE0, 0x7E3A, 0xEDE1, 0x7F45, 0xEDE2, 0x7F7F, 0xEDE3, 0x7F7E, + 0xEDE4, 0x7F7D, 0xEDE5, 0x7FF4, 0xEDE6, 0x7FF2, 0xEDE7, 0x802C, 0xEDE8, 0x81BB, 0xEDE9, 0x81C4, 0xEDEA, 0x81CC, 0xEDEB, 0x81CA, + 0xEDEC, 0x81C5, 0xEDED, 0x81C7, 0xEDEE, 0x81BC, 0xEDEF, 0x81E9, 0xEDF0, 0x825B, 0xEDF1, 0x825A, 0xEDF2, 0x825C, 0xEDF3, 0x8583, + 0xEDF4, 0x8580, 0xEDF5, 0x858F, 0xEDF6, 0x85A7, 0xEDF7, 0x8595, 0xEDF8, 0x85A0, 0xEDF9, 0x858B, 0xEDFA, 0x85A3, 0xEDFB, 0x857B, + 0xEDFC, 0x85A4, 0xEDFD, 0x859A, 0xEDFE, 0x859E, 0xEE40, 0x8577, 0xEE41, 0x857C, 0xEE42, 0x8589, 0xEE43, 0x85A1, 0xEE44, 0x857A, + 0xEE45, 0x8578, 0xEE46, 0x8557, 0xEE47, 0x858E, 0xEE48, 0x8596, 0xEE49, 0x8586, 0xEE4A, 0x858D, 0xEE4B, 0x8599, 0xEE4C, 0x859D, + 0xEE4D, 0x8581, 0xEE4E, 0x85A2, 0xEE4F, 0x8582, 0xEE50, 0x8588, 0xEE51, 0x8585, 0xEE52, 0x8579, 0xEE53, 0x8576, 0xEE54, 0x8598, + 0xEE55, 0x8590, 0xEE56, 0x859F, 0xEE57, 0x8668, 0xEE58, 0x87BE, 0xEE59, 0x87AA, 0xEE5A, 0x87AD, 0xEE5B, 0x87C5, 0xEE5C, 0x87B0, + 0xEE5D, 0x87AC, 0xEE5E, 0x87B9, 0xEE5F, 0x87B5, 0xEE60, 0x87BC, 0xEE61, 0x87AE, 0xEE62, 0x87C9, 0xEE63, 0x87C3, 0xEE64, 0x87C2, + 0xEE65, 0x87CC, 0xEE66, 0x87B7, 0xEE67, 0x87AF, 0xEE68, 0x87C4, 0xEE69, 0x87CA, 0xEE6A, 0x87B4, 0xEE6B, 0x87B6, 0xEE6C, 0x87BF, + 0xEE6D, 0x87B8, 0xEE6E, 0x87BD, 0xEE6F, 0x87DE, 0xEE70, 0x87B2, 0xEE71, 0x8935, 0xEE72, 0x8933, 0xEE73, 0x893C, 0xEE74, 0x893E, + 0xEE75, 0x8941, 0xEE76, 0x8952, 0xEE77, 0x8937, 0xEE78, 0x8942, 0xEE79, 0x89AD, 0xEE7A, 0x89AF, 0xEE7B, 0x89AE, 0xEE7C, 0x89F2, + 0xEE7D, 0x89F3, 0xEE7E, 0x8B1E, 0xEEA1, 0x8B18, 0xEEA2, 0x8B16, 0xEEA3, 0x8B11, 0xEEA4, 0x8B05, 0xEEA5, 0x8B0B, 0xEEA6, 0x8B22, + 0xEEA7, 0x8B0F, 0xEEA8, 0x8B12, 0xEEA9, 0x8B15, 0xEEAA, 0x8B07, 0xEEAB, 0x8B0D, 0xEEAC, 0x8B08, 0xEEAD, 0x8B06, 0xEEAE, 0x8B1C, + 0xEEAF, 0x8B13, 0xEEB0, 0x8B1A, 0xEEB1, 0x8C4F, 0xEEB2, 0x8C70, 0xEEB3, 0x8C72, 0xEEB4, 0x8C71, 0xEEB5, 0x8C6F, 0xEEB6, 0x8C95, + 0xEEB7, 0x8C94, 0xEEB8, 0x8CF9, 0xEEB9, 0x8D6F, 0xEEBA, 0x8E4E, 0xEEBB, 0x8E4D, 0xEEBC, 0x8E53, 0xEEBD, 0x8E50, 0xEEBE, 0x8E4C, + 0xEEBF, 0x8E47, 0xEEC0, 0x8F43, 0xEEC1, 0x8F40, 0xEEC2, 0x9085, 0xEEC3, 0x907E, 0xEEC4, 0x9138, 0xEEC5, 0x919A, 0xEEC6, 0x91A2, + 0xEEC7, 0x919B, 0xEEC8, 0x9199, 0xEEC9, 0x919F, 0xEECA, 0x91A1, 0xEECB, 0x919D, 0xEECC, 0x91A0, 0xEECD, 0x93A1, 0xEECE, 0x9383, + 0xEECF, 0x93AF, 0xEED0, 0x9364, 0xEED1, 0x9356, 0xEED2, 0x9347, 0xEED3, 0x937C, 0xEED4, 0x9358, 0xEED5, 0x935C, 0xEED6, 0x9376, + 0xEED7, 0x9349, 0xEED8, 0x9350, 0xEED9, 0x9351, 0xEEDA, 0x9360, 0xEEDB, 0x936D, 0xEEDC, 0x938F, 0xEEDD, 0x934C, 0xEEDE, 0x936A, + 0xEEDF, 0x9379, 0xEEE0, 0x9357, 0xEEE1, 0x9355, 0xEEE2, 0x9352, 0xEEE3, 0x934F, 0xEEE4, 0x9371, 0xEEE5, 0x9377, 0xEEE6, 0x937B, + 0xEEE7, 0x9361, 0xEEE8, 0x935E, 0xEEE9, 0x9363, 0xEEEA, 0x9367, 0xEEEB, 0x9380, 0xEEEC, 0x934E, 0xEEED, 0x9359, 0xEEEE, 0x95C7, + 0xEEEF, 0x95C0, 0xEEF0, 0x95C9, 0xEEF1, 0x95C3, 0xEEF2, 0x95C5, 0xEEF3, 0x95B7, 0xEEF4, 0x96AE, 0xEEF5, 0x96B0, 0xEEF6, 0x96AC, + 0xEEF7, 0x9720, 0xEEF8, 0x971F, 0xEEF9, 0x9718, 0xEEFA, 0x971D, 0xEEFB, 0x9719, 0xEEFC, 0x979A, 0xEEFD, 0x97A1, 0xEEFE, 0x979C, + 0xEF40, 0x979E, 0xEF41, 0x979D, 0xEF42, 0x97D5, 0xEF43, 0x97D4, 0xEF44, 0x97F1, 0xEF45, 0x9841, 0xEF46, 0x9844, 0xEF47, 0x984A, + 0xEF48, 0x9849, 0xEF49, 0x9845, 0xEF4A, 0x9843, 0xEF4B, 0x9925, 0xEF4C, 0x992B, 0xEF4D, 0x992C, 0xEF4E, 0x992A, 0xEF4F, 0x9933, + 0xEF50, 0x9932, 0xEF51, 0x992F, 0xEF52, 0x992D, 0xEF53, 0x9931, 0xEF54, 0x9930, 0xEF55, 0x9998, 0xEF56, 0x99A3, 0xEF57, 0x99A1, + 0xEF58, 0x9A02, 0xEF59, 0x99FA, 0xEF5A, 0x99F4, 0xEF5B, 0x99F7, 0xEF5C, 0x99F9, 0xEF5D, 0x99F8, 0xEF5E, 0x99F6, 0xEF5F, 0x99FB, + 0xEF60, 0x99FD, 0xEF61, 0x99FE, 0xEF62, 0x99FC, 0xEF63, 0x9A03, 0xEF64, 0x9ABE, 0xEF65, 0x9AFE, 0xEF66, 0x9AFD, 0xEF67, 0x9B01, + 0xEF68, 0x9AFC, 0xEF69, 0x9B48, 0xEF6A, 0x9B9A, 0xEF6B, 0x9BA8, 0xEF6C, 0x9B9E, 0xEF6D, 0x9B9B, 0xEF6E, 0x9BA6, 0xEF6F, 0x9BA1, + 0xEF70, 0x9BA5, 0xEF71, 0x9BA4, 0xEF72, 0x9B86, 0xEF73, 0x9BA2, 0xEF74, 0x9BA0, 0xEF75, 0x9BAF, 0xEF76, 0x9D33, 0xEF77, 0x9D41, + 0xEF78, 0x9D67, 0xEF79, 0x9D36, 0xEF7A, 0x9D2E, 0xEF7B, 0x9D2F, 0xEF7C, 0x9D31, 0xEF7D, 0x9D38, 0xEF7E, 0x9D30, 0xEFA1, 0x9D45, + 0xEFA2, 0x9D42, 0xEFA3, 0x9D43, 0xEFA4, 0x9D3E, 0xEFA5, 0x9D37, 0xEFA6, 0x9D40, 0xEFA7, 0x9D3D, 0xEFA8, 0x7FF5, 0xEFA9, 0x9D2D, + 0xEFAA, 0x9E8A, 0xEFAB, 0x9E89, 0xEFAC, 0x9E8D, 0xEFAD, 0x9EB0, 0xEFAE, 0x9EC8, 0xEFAF, 0x9EDA, 0xEFB0, 0x9EFB, 0xEFB1, 0x9EFF, + 0xEFB2, 0x9F24, 0xEFB3, 0x9F23, 0xEFB4, 0x9F22, 0xEFB5, 0x9F54, 0xEFB6, 0x9FA0, 0xEFB7, 0x5131, 0xEFB8, 0x512D, 0xEFB9, 0x512E, + 0xEFBA, 0x5698, 0xEFBB, 0x569C, 0xEFBC, 0x5697, 0xEFBD, 0x569A, 0xEFBE, 0x569D, 0xEFBF, 0x5699, 0xEFC0, 0x5970, 0xEFC1, 0x5B3C, + 0xEFC2, 0x5C69, 0xEFC3, 0x5C6A, 0xEFC4, 0x5DC0, 0xEFC5, 0x5E6D, 0xEFC6, 0x5E6E, 0xEFC7, 0x61D8, 0xEFC8, 0x61DF, 0xEFC9, 0x61ED, + 0xEFCA, 0x61EE, 0xEFCB, 0x61F1, 0xEFCC, 0x61EA, 0xEFCD, 0x61F0, 0xEFCE, 0x61EB, 0xEFCF, 0x61D6, 0xEFD0, 0x61E9, 0xEFD1, 0x64FF, + 0xEFD2, 0x6504, 0xEFD3, 0x64FD, 0xEFD4, 0x64F8, 0xEFD5, 0x6501, 0xEFD6, 0x6503, 0xEFD7, 0x64FC, 0xEFD8, 0x6594, 0xEFD9, 0x65DB, + 0xEFDA, 0x66DA, 0xEFDB, 0x66DB, 0xEFDC, 0x66D8, 0xEFDD, 0x6AC5, 0xEFDE, 0x6AB9, 0xEFDF, 0x6ABD, 0xEFE0, 0x6AE1, 0xEFE1, 0x6AC6, + 0xEFE2, 0x6ABA, 0xEFE3, 0x6AB6, 0xEFE4, 0x6AB7, 0xEFE5, 0x6AC7, 0xEFE6, 0x6AB4, 0xEFE7, 0x6AAD, 0xEFE8, 0x6B5E, 0xEFE9, 0x6BC9, + 0xEFEA, 0x6C0B, 0xEFEB, 0x7007, 0xEFEC, 0x700C, 0xEFED, 0x700D, 0xEFEE, 0x7001, 0xEFEF, 0x7005, 0xEFF0, 0x7014, 0xEFF1, 0x700E, + 0xEFF2, 0x6FFF, 0xEFF3, 0x7000, 0xEFF4, 0x6FFB, 0xEFF5, 0x7026, 0xEFF6, 0x6FFC, 0xEFF7, 0x6FF7, 0xEFF8, 0x700A, 0xEFF9, 0x7201, + 0xEFFA, 0x71FF, 0xEFFB, 0x71F9, 0xEFFC, 0x7203, 0xEFFD, 0x71FD, 0xEFFE, 0x7376, 0xF040, 0x74B8, 0xF041, 0x74C0, 0xF042, 0x74B5, + 0xF043, 0x74C1, 0xF044, 0x74BE, 0xF045, 0x74B6, 0xF046, 0x74BB, 0xF047, 0x74C2, 0xF048, 0x7514, 0xF049, 0x7513, 0xF04A, 0x765C, + 0xF04B, 0x7664, 0xF04C, 0x7659, 0xF04D, 0x7650, 0xF04E, 0x7653, 0xF04F, 0x7657, 0xF050, 0x765A, 0xF051, 0x76A6, 0xF052, 0x76BD, + 0xF053, 0x76EC, 0xF054, 0x77C2, 0xF055, 0x77BA, 0xF056, 0x78FF, 0xF057, 0x790C, 0xF058, 0x7913, 0xF059, 0x7914, 0xF05A, 0x7909, + 0xF05B, 0x7910, 0xF05C, 0x7912, 0xF05D, 0x7911, 0xF05E, 0x79AD, 0xF05F, 0x79AC, 0xF060, 0x7A5F, 0xF061, 0x7C1C, 0xF062, 0x7C29, + 0xF063, 0x7C19, 0xF064, 0x7C20, 0xF065, 0x7C1F, 0xF066, 0x7C2D, 0xF067, 0x7C1D, 0xF068, 0x7C26, 0xF069, 0x7C28, 0xF06A, 0x7C22, + 0xF06B, 0x7C25, 0xF06C, 0x7C30, 0xF06D, 0x7E5C, 0xF06E, 0x7E50, 0xF06F, 0x7E56, 0xF070, 0x7E63, 0xF071, 0x7E58, 0xF072, 0x7E62, + 0xF073, 0x7E5F, 0xF074, 0x7E51, 0xF075, 0x7E60, 0xF076, 0x7E57, 0xF077, 0x7E53, 0xF078, 0x7FB5, 0xF079, 0x7FB3, 0xF07A, 0x7FF7, + 0xF07B, 0x7FF8, 0xF07C, 0x8075, 0xF07D, 0x81D1, 0xF07E, 0x81D2, 0xF0A1, 0x81D0, 0xF0A2, 0x825F, 0xF0A3, 0x825E, 0xF0A4, 0x85B4, + 0xF0A5, 0x85C6, 0xF0A6, 0x85C0, 0xF0A7, 0x85C3, 0xF0A8, 0x85C2, 0xF0A9, 0x85B3, 0xF0AA, 0x85B5, 0xF0AB, 0x85BD, 0xF0AC, 0x85C7, + 0xF0AD, 0x85C4, 0xF0AE, 0x85BF, 0xF0AF, 0x85CB, 0xF0B0, 0x85CE, 0xF0B1, 0x85C8, 0xF0B2, 0x85C5, 0xF0B3, 0x85B1, 0xF0B4, 0x85B6, + 0xF0B5, 0x85D2, 0xF0B6, 0x8624, 0xF0B7, 0x85B8, 0xF0B8, 0x85B7, 0xF0B9, 0x85BE, 0xF0BA, 0x8669, 0xF0BB, 0x87E7, 0xF0BC, 0x87E6, + 0xF0BD, 0x87E2, 0xF0BE, 0x87DB, 0xF0BF, 0x87EB, 0xF0C0, 0x87EA, 0xF0C1, 0x87E5, 0xF0C2, 0x87DF, 0xF0C3, 0x87F3, 0xF0C4, 0x87E4, + 0xF0C5, 0x87D4, 0xF0C6, 0x87DC, 0xF0C7, 0x87D3, 0xF0C8, 0x87ED, 0xF0C9, 0x87D8, 0xF0CA, 0x87E3, 0xF0CB, 0x87A4, 0xF0CC, 0x87D7, + 0xF0CD, 0x87D9, 0xF0CE, 0x8801, 0xF0CF, 0x87F4, 0xF0D0, 0x87E8, 0xF0D1, 0x87DD, 0xF0D2, 0x8953, 0xF0D3, 0x894B, 0xF0D4, 0x894F, + 0xF0D5, 0x894C, 0xF0D6, 0x8946, 0xF0D7, 0x8950, 0xF0D8, 0x8951, 0xF0D9, 0x8949, 0xF0DA, 0x8B2A, 0xF0DB, 0x8B27, 0xF0DC, 0x8B23, + 0xF0DD, 0x8B33, 0xF0DE, 0x8B30, 0xF0DF, 0x8B35, 0xF0E0, 0x8B47, 0xF0E1, 0x8B2F, 0xF0E2, 0x8B3C, 0xF0E3, 0x8B3E, 0xF0E4, 0x8B31, + 0xF0E5, 0x8B25, 0xF0E6, 0x8B37, 0xF0E7, 0x8B26, 0xF0E8, 0x8B36, 0xF0E9, 0x8B2E, 0xF0EA, 0x8B24, 0xF0EB, 0x8B3B, 0xF0EC, 0x8B3D, + 0xF0ED, 0x8B3A, 0xF0EE, 0x8C42, 0xF0EF, 0x8C75, 0xF0F0, 0x8C99, 0xF0F1, 0x8C98, 0xF0F2, 0x8C97, 0xF0F3, 0x8CFE, 0xF0F4, 0x8D04, + 0xF0F5, 0x8D02, 0xF0F6, 0x8D00, 0xF0F7, 0x8E5C, 0xF0F8, 0x8E62, 0xF0F9, 0x8E60, 0xF0FA, 0x8E57, 0xF0FB, 0x8E56, 0xF0FC, 0x8E5E, + 0xF0FD, 0x8E65, 0xF0FE, 0x8E67, 0xF140, 0x8E5B, 0xF141, 0x8E5A, 0xF142, 0x8E61, 0xF143, 0x8E5D, 0xF144, 0x8E69, 0xF145, 0x8E54, + 0xF146, 0x8F46, 0xF147, 0x8F47, 0xF148, 0x8F48, 0xF149, 0x8F4B, 0xF14A, 0x9128, 0xF14B, 0x913A, 0xF14C, 0x913B, 0xF14D, 0x913E, + 0xF14E, 0x91A8, 0xF14F, 0x91A5, 0xF150, 0x91A7, 0xF151, 0x91AF, 0xF152, 0x91AA, 0xF153, 0x93B5, 0xF154, 0x938C, 0xF155, 0x9392, + 0xF156, 0x93B7, 0xF157, 0x939B, 0xF158, 0x939D, 0xF159, 0x9389, 0xF15A, 0x93A7, 0xF15B, 0x938E, 0xF15C, 0x93AA, 0xF15D, 0x939E, + 0xF15E, 0x93A6, 0xF15F, 0x9395, 0xF160, 0x9388, 0xF161, 0x9399, 0xF162, 0x939F, 0xF163, 0x938D, 0xF164, 0x93B1, 0xF165, 0x9391, + 0xF166, 0x93B2, 0xF167, 0x93A4, 0xF168, 0x93A8, 0xF169, 0x93B4, 0xF16A, 0x93A3, 0xF16B, 0x93A5, 0xF16C, 0x95D2, 0xF16D, 0x95D3, + 0xF16E, 0x95D1, 0xF16F, 0x96B3, 0xF170, 0x96D7, 0xF171, 0x96DA, 0xF172, 0x5DC2, 0xF173, 0x96DF, 0xF174, 0x96D8, 0xF175, 0x96DD, + 0xF176, 0x9723, 0xF177, 0x9722, 0xF178, 0x9725, 0xF179, 0x97AC, 0xF17A, 0x97AE, 0xF17B, 0x97A8, 0xF17C, 0x97AB, 0xF17D, 0x97A4, + 0xF17E, 0x97AA, 0xF1A1, 0x97A2, 0xF1A2, 0x97A5, 0xF1A3, 0x97D7, 0xF1A4, 0x97D9, 0xF1A5, 0x97D6, 0xF1A6, 0x97D8, 0xF1A7, 0x97FA, + 0xF1A8, 0x9850, 0xF1A9, 0x9851, 0xF1AA, 0x9852, 0xF1AB, 0x98B8, 0xF1AC, 0x9941, 0xF1AD, 0x993C, 0xF1AE, 0x993A, 0xF1AF, 0x9A0F, + 0xF1B0, 0x9A0B, 0xF1B1, 0x9A09, 0xF1B2, 0x9A0D, 0xF1B3, 0x9A04, 0xF1B4, 0x9A11, 0xF1B5, 0x9A0A, 0xF1B6, 0x9A05, 0xF1B7, 0x9A07, + 0xF1B8, 0x9A06, 0xF1B9, 0x9AC0, 0xF1BA, 0x9ADC, 0xF1BB, 0x9B08, 0xF1BC, 0x9B04, 0xF1BD, 0x9B05, 0xF1BE, 0x9B29, 0xF1BF, 0x9B35, + 0xF1C0, 0x9B4A, 0xF1C1, 0x9B4C, 0xF1C2, 0x9B4B, 0xF1C3, 0x9BC7, 0xF1C4, 0x9BC6, 0xF1C5, 0x9BC3, 0xF1C6, 0x9BBF, 0xF1C7, 0x9BC1, + 0xF1C8, 0x9BB5, 0xF1C9, 0x9BB8, 0xF1CA, 0x9BD3, 0xF1CB, 0x9BB6, 0xF1CC, 0x9BC4, 0xF1CD, 0x9BB9, 0xF1CE, 0x9BBD, 0xF1CF, 0x9D5C, + 0xF1D0, 0x9D53, 0xF1D1, 0x9D4F, 0xF1D2, 0x9D4A, 0xF1D3, 0x9D5B, 0xF1D4, 0x9D4B, 0xF1D5, 0x9D59, 0xF1D6, 0x9D56, 0xF1D7, 0x9D4C, + 0xF1D8, 0x9D57, 0xF1D9, 0x9D52, 0xF1DA, 0x9D54, 0xF1DB, 0x9D5F, 0xF1DC, 0x9D58, 0xF1DD, 0x9D5A, 0xF1DE, 0x9E8E, 0xF1DF, 0x9E8C, + 0xF1E0, 0x9EDF, 0xF1E1, 0x9F01, 0xF1E2, 0x9F00, 0xF1E3, 0x9F16, 0xF1E4, 0x9F25, 0xF1E5, 0x9F2B, 0xF1E6, 0x9F2A, 0xF1E7, 0x9F29, + 0xF1E8, 0x9F28, 0xF1E9, 0x9F4C, 0xF1EA, 0x9F55, 0xF1EB, 0x5134, 0xF1EC, 0x5135, 0xF1ED, 0x5296, 0xF1EE, 0x52F7, 0xF1EF, 0x53B4, + 0xF1F0, 0x56AB, 0xF1F1, 0x56AD, 0xF1F2, 0x56A6, 0xF1F3, 0x56A7, 0xF1F4, 0x56AA, 0xF1F5, 0x56AC, 0xF1F6, 0x58DA, 0xF1F7, 0x58DD, + 0xF1F8, 0x58DB, 0xF1F9, 0x5912, 0xF1FA, 0x5B3D, 0xF1FB, 0x5B3E, 0xF1FC, 0x5B3F, 0xF1FD, 0x5DC3, 0xF1FE, 0x5E70, 0xF240, 0x5FBF, + 0xF241, 0x61FB, 0xF242, 0x6507, 0xF243, 0x6510, 0xF244, 0x650D, 0xF245, 0x6509, 0xF246, 0x650C, 0xF247, 0x650E, 0xF248, 0x6584, + 0xF249, 0x65DE, 0xF24A, 0x65DD, 0xF24B, 0x66DE, 0xF24C, 0x6AE7, 0xF24D, 0x6AE0, 0xF24E, 0x6ACC, 0xF24F, 0x6AD1, 0xF250, 0x6AD9, + 0xF251, 0x6ACB, 0xF252, 0x6ADF, 0xF253, 0x6ADC, 0xF254, 0x6AD0, 0xF255, 0x6AEB, 0xF256, 0x6ACF, 0xF257, 0x6ACD, 0xF258, 0x6ADE, + 0xF259, 0x6B60, 0xF25A, 0x6BB0, 0xF25B, 0x6C0C, 0xF25C, 0x7019, 0xF25D, 0x7027, 0xF25E, 0x7020, 0xF25F, 0x7016, 0xF260, 0x702B, + 0xF261, 0x7021, 0xF262, 0x7022, 0xF263, 0x7023, 0xF264, 0x7029, 0xF265, 0x7017, 0xF266, 0x7024, 0xF267, 0x701C, 0xF268, 0x702A, + 0xF269, 0x720C, 0xF26A, 0x720A, 0xF26B, 0x7207, 0xF26C, 0x7202, 0xF26D, 0x7205, 0xF26E, 0x72A5, 0xF26F, 0x72A6, 0xF270, 0x72A4, + 0xF271, 0x72A3, 0xF272, 0x72A1, 0xF273, 0x74CB, 0xF274, 0x74C5, 0xF275, 0x74B7, 0xF276, 0x74C3, 0xF277, 0x7516, 0xF278, 0x7660, + 0xF279, 0x77C9, 0xF27A, 0x77CA, 0xF27B, 0x77C4, 0xF27C, 0x77F1, 0xF27D, 0x791D, 0xF27E, 0x791B, 0xF2A1, 0x7921, 0xF2A2, 0x791C, + 0xF2A3, 0x7917, 0xF2A4, 0x791E, 0xF2A5, 0x79B0, 0xF2A6, 0x7A67, 0xF2A7, 0x7A68, 0xF2A8, 0x7C33, 0xF2A9, 0x7C3C, 0xF2AA, 0x7C39, + 0xF2AB, 0x7C2C, 0xF2AC, 0x7C3B, 0xF2AD, 0x7CEC, 0xF2AE, 0x7CEA, 0xF2AF, 0x7E76, 0xF2B0, 0x7E75, 0xF2B1, 0x7E78, 0xF2B2, 0x7E70, + 0xF2B3, 0x7E77, 0xF2B4, 0x7E6F, 0xF2B5, 0x7E7A, 0xF2B6, 0x7E72, 0xF2B7, 0x7E74, 0xF2B8, 0x7E68, 0xF2B9, 0x7F4B, 0xF2BA, 0x7F4A, + 0xF2BB, 0x7F83, 0xF2BC, 0x7F86, 0xF2BD, 0x7FB7, 0xF2BE, 0x7FFD, 0xF2BF, 0x7FFE, 0xF2C0, 0x8078, 0xF2C1, 0x81D7, 0xF2C2, 0x81D5, + 0xF2C3, 0x8264, 0xF2C4, 0x8261, 0xF2C5, 0x8263, 0xF2C6, 0x85EB, 0xF2C7, 0x85F1, 0xF2C8, 0x85ED, 0xF2C9, 0x85D9, 0xF2CA, 0x85E1, + 0xF2CB, 0x85E8, 0xF2CC, 0x85DA, 0xF2CD, 0x85D7, 0xF2CE, 0x85EC, 0xF2CF, 0x85F2, 0xF2D0, 0x85F8, 0xF2D1, 0x85D8, 0xF2D2, 0x85DF, + 0xF2D3, 0x85E3, 0xF2D4, 0x85DC, 0xF2D5, 0x85D1, 0xF2D6, 0x85F0, 0xF2D7, 0x85E6, 0xF2D8, 0x85EF, 0xF2D9, 0x85DE, 0xF2DA, 0x85E2, + 0xF2DB, 0x8800, 0xF2DC, 0x87FA, 0xF2DD, 0x8803, 0xF2DE, 0x87F6, 0xF2DF, 0x87F7, 0xF2E0, 0x8809, 0xF2E1, 0x880C, 0xF2E2, 0x880B, + 0xF2E3, 0x8806, 0xF2E4, 0x87FC, 0xF2E5, 0x8808, 0xF2E6, 0x87FF, 0xF2E7, 0x880A, 0xF2E8, 0x8802, 0xF2E9, 0x8962, 0xF2EA, 0x895A, + 0xF2EB, 0x895B, 0xF2EC, 0x8957, 0xF2ED, 0x8961, 0xF2EE, 0x895C, 0xF2EF, 0x8958, 0xF2F0, 0x895D, 0xF2F1, 0x8959, 0xF2F2, 0x8988, + 0xF2F3, 0x89B7, 0xF2F4, 0x89B6, 0xF2F5, 0x89F6, 0xF2F6, 0x8B50, 0xF2F7, 0x8B48, 0xF2F8, 0x8B4A, 0xF2F9, 0x8B40, 0xF2FA, 0x8B53, + 0xF2FB, 0x8B56, 0xF2FC, 0x8B54, 0xF2FD, 0x8B4B, 0xF2FE, 0x8B55, 0xF340, 0x8B51, 0xF341, 0x8B42, 0xF342, 0x8B52, 0xF343, 0x8B57, + 0xF344, 0x8C43, 0xF345, 0x8C77, 0xF346, 0x8C76, 0xF347, 0x8C9A, 0xF348, 0x8D06, 0xF349, 0x8D07, 0xF34A, 0x8D09, 0xF34B, 0x8DAC, + 0xF34C, 0x8DAA, 0xF34D, 0x8DAD, 0xF34E, 0x8DAB, 0xF34F, 0x8E6D, 0xF350, 0x8E78, 0xF351, 0x8E73, 0xF352, 0x8E6A, 0xF353, 0x8E6F, + 0xF354, 0x8E7B, 0xF355, 0x8EC2, 0xF356, 0x8F52, 0xF357, 0x8F51, 0xF358, 0x8F4F, 0xF359, 0x8F50, 0xF35A, 0x8F53, 0xF35B, 0x8FB4, + 0xF35C, 0x9140, 0xF35D, 0x913F, 0xF35E, 0x91B0, 0xF35F, 0x91AD, 0xF360, 0x93DE, 0xF361, 0x93C7, 0xF362, 0x93CF, 0xF363, 0x93C2, + 0xF364, 0x93DA, 0xF365, 0x93D0, 0xF366, 0x93F9, 0xF367, 0x93EC, 0xF368, 0x93CC, 0xF369, 0x93D9, 0xF36A, 0x93A9, 0xF36B, 0x93E6, + 0xF36C, 0x93CA, 0xF36D, 0x93D4, 0xF36E, 0x93EE, 0xF36F, 0x93E3, 0xF370, 0x93D5, 0xF371, 0x93C4, 0xF372, 0x93CE, 0xF373, 0x93C0, + 0xF374, 0x93D2, 0xF375, 0x93E7, 0xF376, 0x957D, 0xF377, 0x95DA, 0xF378, 0x95DB, 0xF379, 0x96E1, 0xF37A, 0x9729, 0xF37B, 0x972B, + 0xF37C, 0x972C, 0xF37D, 0x9728, 0xF37E, 0x9726, 0xF3A1, 0x97B3, 0xF3A2, 0x97B7, 0xF3A3, 0x97B6, 0xF3A4, 0x97DD, 0xF3A5, 0x97DE, + 0xF3A6, 0x97DF, 0xF3A7, 0x985C, 0xF3A8, 0x9859, 0xF3A9, 0x985D, 0xF3AA, 0x9857, 0xF3AB, 0x98BF, 0xF3AC, 0x98BD, 0xF3AD, 0x98BB, + 0xF3AE, 0x98BE, 0xF3AF, 0x9948, 0xF3B0, 0x9947, 0xF3B1, 0x9943, 0xF3B2, 0x99A6, 0xF3B3, 0x99A7, 0xF3B4, 0x9A1A, 0xF3B5, 0x9A15, + 0xF3B6, 0x9A25, 0xF3B7, 0x9A1D, 0xF3B8, 0x9A24, 0xF3B9, 0x9A1B, 0xF3BA, 0x9A22, 0xF3BB, 0x9A20, 0xF3BC, 0x9A27, 0xF3BD, 0x9A23, + 0xF3BE, 0x9A1E, 0xF3BF, 0x9A1C, 0xF3C0, 0x9A14, 0xF3C1, 0x9AC2, 0xF3C2, 0x9B0B, 0xF3C3, 0x9B0A, 0xF3C4, 0x9B0E, 0xF3C5, 0x9B0C, + 0xF3C6, 0x9B37, 0xF3C7, 0x9BEA, 0xF3C8, 0x9BEB, 0xF3C9, 0x9BE0, 0xF3CA, 0x9BDE, 0xF3CB, 0x9BE4, 0xF3CC, 0x9BE6, 0xF3CD, 0x9BE2, + 0xF3CE, 0x9BF0, 0xF3CF, 0x9BD4, 0xF3D0, 0x9BD7, 0xF3D1, 0x9BEC, 0xF3D2, 0x9BDC, 0xF3D3, 0x9BD9, 0xF3D4, 0x9BE5, 0xF3D5, 0x9BD5, + 0xF3D6, 0x9BE1, 0xF3D7, 0x9BDA, 0xF3D8, 0x9D77, 0xF3D9, 0x9D81, 0xF3DA, 0x9D8A, 0xF3DB, 0x9D84, 0xF3DC, 0x9D88, 0xF3DD, 0x9D71, + 0xF3DE, 0x9D80, 0xF3DF, 0x9D78, 0xF3E0, 0x9D86, 0xF3E1, 0x9D8B, 0xF3E2, 0x9D8C, 0xF3E3, 0x9D7D, 0xF3E4, 0x9D6B, 0xF3E5, 0x9D74, + 0xF3E6, 0x9D75, 0xF3E7, 0x9D70, 0xF3E8, 0x9D69, 0xF3E9, 0x9D85, 0xF3EA, 0x9D73, 0xF3EB, 0x9D7B, 0xF3EC, 0x9D82, 0xF3ED, 0x9D6F, + 0xF3EE, 0x9D79, 0xF3EF, 0x9D7F, 0xF3F0, 0x9D87, 0xF3F1, 0x9D68, 0xF3F2, 0x9E94, 0xF3F3, 0x9E91, 0xF3F4, 0x9EC0, 0xF3F5, 0x9EFC, + 0xF3F6, 0x9F2D, 0xF3F7, 0x9F40, 0xF3F8, 0x9F41, 0xF3F9, 0x9F4D, 0xF3FA, 0x9F56, 0xF3FB, 0x9F57, 0xF3FC, 0x9F58, 0xF3FD, 0x5337, + 0xF3FE, 0x56B2, 0xF440, 0x56B5, 0xF441, 0x56B3, 0xF442, 0x58E3, 0xF443, 0x5B45, 0xF444, 0x5DC6, 0xF445, 0x5DC7, 0xF446, 0x5EEE, + 0xF447, 0x5EEF, 0xF448, 0x5FC0, 0xF449, 0x5FC1, 0xF44A, 0x61F9, 0xF44B, 0x6517, 0xF44C, 0x6516, 0xF44D, 0x6515, 0xF44E, 0x6513, + 0xF44F, 0x65DF, 0xF450, 0x66E8, 0xF451, 0x66E3, 0xF452, 0x66E4, 0xF453, 0x6AF3, 0xF454, 0x6AF0, 0xF455, 0x6AEA, 0xF456, 0x6AE8, + 0xF457, 0x6AF9, 0xF458, 0x6AF1, 0xF459, 0x6AEE, 0xF45A, 0x6AEF, 0xF45B, 0x703C, 0xF45C, 0x7035, 0xF45D, 0x702F, 0xF45E, 0x7037, + 0xF45F, 0x7034, 0xF460, 0x7031, 0xF461, 0x7042, 0xF462, 0x7038, 0xF463, 0x703F, 0xF464, 0x703A, 0xF465, 0x7039, 0xF466, 0x7040, + 0xF467, 0x703B, 0xF468, 0x7033, 0xF469, 0x7041, 0xF46A, 0x7213, 0xF46B, 0x7214, 0xF46C, 0x72A8, 0xF46D, 0x737D, 0xF46E, 0x737C, + 0xF46F, 0x74BA, 0xF470, 0x76AB, 0xF471, 0x76AA, 0xF472, 0x76BE, 0xF473, 0x76ED, 0xF474, 0x77CC, 0xF475, 0x77CE, 0xF476, 0x77CF, + 0xF477, 0x77CD, 0xF478, 0x77F2, 0xF479, 0x7925, 0xF47A, 0x7923, 0xF47B, 0x7927, 0xF47C, 0x7928, 0xF47D, 0x7924, 0xF47E, 0x7929, + 0xF4A1, 0x79B2, 0xF4A2, 0x7A6E, 0xF4A3, 0x7A6C, 0xF4A4, 0x7A6D, 0xF4A5, 0x7AF7, 0xF4A6, 0x7C49, 0xF4A7, 0x7C48, 0xF4A8, 0x7C4A, + 0xF4A9, 0x7C47, 0xF4AA, 0x7C45, 0xF4AB, 0x7CEE, 0xF4AC, 0x7E7B, 0xF4AD, 0x7E7E, 0xF4AE, 0x7E81, 0xF4AF, 0x7E80, 0xF4B0, 0x7FBA, + 0xF4B1, 0x7FFF, 0xF4B2, 0x8079, 0xF4B3, 0x81DB, 0xF4B4, 0x81D9, 0xF4B5, 0x820B, 0xF4B6, 0x8268, 0xF4B7, 0x8269, 0xF4B8, 0x8622, + 0xF4B9, 0x85FF, 0xF4BA, 0x8601, 0xF4BB, 0x85FE, 0xF4BC, 0x861B, 0xF4BD, 0x8600, 0xF4BE, 0x85F6, 0xF4BF, 0x8604, 0xF4C0, 0x8609, + 0xF4C1, 0x8605, 0xF4C2, 0x860C, 0xF4C3, 0x85FD, 0xF4C4, 0x8819, 0xF4C5, 0x8810, 0xF4C6, 0x8811, 0xF4C7, 0x8817, 0xF4C8, 0x8813, + 0xF4C9, 0x8816, 0xF4CA, 0x8963, 0xF4CB, 0x8966, 0xF4CC, 0x89B9, 0xF4CD, 0x89F7, 0xF4CE, 0x8B60, 0xF4CF, 0x8B6A, 0xF4D0, 0x8B5D, + 0xF4D1, 0x8B68, 0xF4D2, 0x8B63, 0xF4D3, 0x8B65, 0xF4D4, 0x8B67, 0xF4D5, 0x8B6D, 0xF4D6, 0x8DAE, 0xF4D7, 0x8E86, 0xF4D8, 0x8E88, + 0xF4D9, 0x8E84, 0xF4DA, 0x8F59, 0xF4DB, 0x8F56, 0xF4DC, 0x8F57, 0xF4DD, 0x8F55, 0xF4DE, 0x8F58, 0xF4DF, 0x8F5A, 0xF4E0, 0x908D, + 0xF4E1, 0x9143, 0xF4E2, 0x9141, 0xF4E3, 0x91B7, 0xF4E4, 0x91B5, 0xF4E5, 0x91B2, 0xF4E6, 0x91B3, 0xF4E7, 0x940B, 0xF4E8, 0x9413, + 0xF4E9, 0x93FB, 0xF4EA, 0x9420, 0xF4EB, 0x940F, 0xF4EC, 0x9414, 0xF4ED, 0x93FE, 0xF4EE, 0x9415, 0xF4EF, 0x9410, 0xF4F0, 0x9428, + 0xF4F1, 0x9419, 0xF4F2, 0x940D, 0xF4F3, 0x93F5, 0xF4F4, 0x9400, 0xF4F5, 0x93F7, 0xF4F6, 0x9407, 0xF4F7, 0x940E, 0xF4F8, 0x9416, + 0xF4F9, 0x9412, 0xF4FA, 0x93FA, 0xF4FB, 0x9409, 0xF4FC, 0x93F8, 0xF4FD, 0x940A, 0xF4FE, 0x93FF, 0xF540, 0x93FC, 0xF541, 0x940C, + 0xF542, 0x93F6, 0xF543, 0x9411, 0xF544, 0x9406, 0xF545, 0x95DE, 0xF546, 0x95E0, 0xF547, 0x95DF, 0xF548, 0x972E, 0xF549, 0x972F, + 0xF54A, 0x97B9, 0xF54B, 0x97BB, 0xF54C, 0x97FD, 0xF54D, 0x97FE, 0xF54E, 0x9860, 0xF54F, 0x9862, 0xF550, 0x9863, 0xF551, 0x985F, + 0xF552, 0x98C1, 0xF553, 0x98C2, 0xF554, 0x9950, 0xF555, 0x994E, 0xF556, 0x9959, 0xF557, 0x994C, 0xF558, 0x994B, 0xF559, 0x9953, + 0xF55A, 0x9A32, 0xF55B, 0x9A34, 0xF55C, 0x9A31, 0xF55D, 0x9A2C, 0xF55E, 0x9A2A, 0xF55F, 0x9A36, 0xF560, 0x9A29, 0xF561, 0x9A2E, + 0xF562, 0x9A38, 0xF563, 0x9A2D, 0xF564, 0x9AC7, 0xF565, 0x9ACA, 0xF566, 0x9AC6, 0xF567, 0x9B10, 0xF568, 0x9B12, 0xF569, 0x9B11, + 0xF56A, 0x9C0B, 0xF56B, 0x9C08, 0xF56C, 0x9BF7, 0xF56D, 0x9C05, 0xF56E, 0x9C12, 0xF56F, 0x9BF8, 0xF570, 0x9C40, 0xF571, 0x9C07, + 0xF572, 0x9C0E, 0xF573, 0x9C06, 0xF574, 0x9C17, 0xF575, 0x9C14, 0xF576, 0x9C09, 0xF577, 0x9D9F, 0xF578, 0x9D99, 0xF579, 0x9DA4, + 0xF57A, 0x9D9D, 0xF57B, 0x9D92, 0xF57C, 0x9D98, 0xF57D, 0x9D90, 0xF57E, 0x9D9B, 0xF5A1, 0x9DA0, 0xF5A2, 0x9D94, 0xF5A3, 0x9D9C, + 0xF5A4, 0x9DAA, 0xF5A5, 0x9D97, 0xF5A6, 0x9DA1, 0xF5A7, 0x9D9A, 0xF5A8, 0x9DA2, 0xF5A9, 0x9DA8, 0xF5AA, 0x9D9E, 0xF5AB, 0x9DA3, + 0xF5AC, 0x9DBF, 0xF5AD, 0x9DA9, 0xF5AE, 0x9D96, 0xF5AF, 0x9DA6, 0xF5B0, 0x9DA7, 0xF5B1, 0x9E99, 0xF5B2, 0x9E9B, 0xF5B3, 0x9E9A, + 0xF5B4, 0x9EE5, 0xF5B5, 0x9EE4, 0xF5B6, 0x9EE7, 0xF5B7, 0x9EE6, 0xF5B8, 0x9F30, 0xF5B9, 0x9F2E, 0xF5BA, 0x9F5B, 0xF5BB, 0x9F60, + 0xF5BC, 0x9F5E, 0xF5BD, 0x9F5D, 0xF5BE, 0x9F59, 0xF5BF, 0x9F91, 0xF5C0, 0x513A, 0xF5C1, 0x5139, 0xF5C2, 0x5298, 0xF5C3, 0x5297, + 0xF5C4, 0x56C3, 0xF5C5, 0x56BD, 0xF5C6, 0x56BE, 0xF5C7, 0x5B48, 0xF5C8, 0x5B47, 0xF5C9, 0x5DCB, 0xF5CA, 0x5DCF, 0xF5CB, 0x5EF1, + 0xF5CC, 0x61FD, 0xF5CD, 0x651B, 0xF5CE, 0x6B02, 0xF5CF, 0x6AFC, 0xF5D0, 0x6B03, 0xF5D1, 0x6AF8, 0xF5D2, 0x6B00, 0xF5D3, 0x7043, + 0xF5D4, 0x7044, 0xF5D5, 0x704A, 0xF5D6, 0x7048, 0xF5D7, 0x7049, 0xF5D8, 0x7045, 0xF5D9, 0x7046, 0xF5DA, 0x721D, 0xF5DB, 0x721A, + 0xF5DC, 0x7219, 0xF5DD, 0x737E, 0xF5DE, 0x7517, 0xF5DF, 0x766A, 0xF5E0, 0x77D0, 0xF5E1, 0x792D, 0xF5E2, 0x7931, 0xF5E3, 0x792F, + 0xF5E4, 0x7C54, 0xF5E5, 0x7C53, 0xF5E6, 0x7CF2, 0xF5E7, 0x7E8A, 0xF5E8, 0x7E87, 0xF5E9, 0x7E88, 0xF5EA, 0x7E8B, 0xF5EB, 0x7E86, + 0xF5EC, 0x7E8D, 0xF5ED, 0x7F4D, 0xF5EE, 0x7FBB, 0xF5EF, 0x8030, 0xF5F0, 0x81DD, 0xF5F1, 0x8618, 0xF5F2, 0x862A, 0xF5F3, 0x8626, + 0xF5F4, 0x861F, 0xF5F5, 0x8623, 0xF5F6, 0x861C, 0xF5F7, 0x8619, 0xF5F8, 0x8627, 0xF5F9, 0x862E, 0xF5FA, 0x8621, 0xF5FB, 0x8620, + 0xF5FC, 0x8629, 0xF5FD, 0x861E, 0xF5FE, 0x8625, 0xF640, 0x8829, 0xF641, 0x881D, 0xF642, 0x881B, 0xF643, 0x8820, 0xF644, 0x8824, + 0xF645, 0x881C, 0xF646, 0x882B, 0xF647, 0x884A, 0xF648, 0x896D, 0xF649, 0x8969, 0xF64A, 0x896E, 0xF64B, 0x896B, 0xF64C, 0x89FA, + 0xF64D, 0x8B79, 0xF64E, 0x8B78, 0xF64F, 0x8B45, 0xF650, 0x8B7A, 0xF651, 0x8B7B, 0xF652, 0x8D10, 0xF653, 0x8D14, 0xF654, 0x8DAF, + 0xF655, 0x8E8E, 0xF656, 0x8E8C, 0xF657, 0x8F5E, 0xF658, 0x8F5B, 0xF659, 0x8F5D, 0xF65A, 0x9146, 0xF65B, 0x9144, 0xF65C, 0x9145, + 0xF65D, 0x91B9, 0xF65E, 0x943F, 0xF65F, 0x943B, 0xF660, 0x9436, 0xF661, 0x9429, 0xF662, 0x943D, 0xF663, 0x943C, 0xF664, 0x9430, + 0xF665, 0x9439, 0xF666, 0x942A, 0xF667, 0x9437, 0xF668, 0x942C, 0xF669, 0x9440, 0xF66A, 0x9431, 0xF66B, 0x95E5, 0xF66C, 0x95E4, + 0xF66D, 0x95E3, 0xF66E, 0x9735, 0xF66F, 0x973A, 0xF670, 0x97BF, 0xF671, 0x97E1, 0xF672, 0x9864, 0xF673, 0x98C9, 0xF674, 0x98C6, + 0xF675, 0x98C0, 0xF676, 0x9958, 0xF677, 0x9956, 0xF678, 0x9A39, 0xF679, 0x9A3D, 0xF67A, 0x9A46, 0xF67B, 0x9A44, 0xF67C, 0x9A42, + 0xF67D, 0x9A41, 0xF67E, 0x9A3A, 0xF6A1, 0x9A3F, 0xF6A2, 0x9ACD, 0xF6A3, 0x9B15, 0xF6A4, 0x9B17, 0xF6A5, 0x9B18, 0xF6A6, 0x9B16, + 0xF6A7, 0x9B3A, 0xF6A8, 0x9B52, 0xF6A9, 0x9C2B, 0xF6AA, 0x9C1D, 0xF6AB, 0x9C1C, 0xF6AC, 0x9C2C, 0xF6AD, 0x9C23, 0xF6AE, 0x9C28, + 0xF6AF, 0x9C29, 0xF6B0, 0x9C24, 0xF6B1, 0x9C21, 0xF6B2, 0x9DB7, 0xF6B3, 0x9DB6, 0xF6B4, 0x9DBC, 0xF6B5, 0x9DC1, 0xF6B6, 0x9DC7, + 0xF6B7, 0x9DCA, 0xF6B8, 0x9DCF, 0xF6B9, 0x9DBE, 0xF6BA, 0x9DC5, 0xF6BB, 0x9DC3, 0xF6BC, 0x9DBB, 0xF6BD, 0x9DB5, 0xF6BE, 0x9DCE, + 0xF6BF, 0x9DB9, 0xF6C0, 0x9DBA, 0xF6C1, 0x9DAC, 0xF6C2, 0x9DC8, 0xF6C3, 0x9DB1, 0xF6C4, 0x9DAD, 0xF6C5, 0x9DCC, 0xF6C6, 0x9DB3, + 0xF6C7, 0x9DCD, 0xF6C8, 0x9DB2, 0xF6C9, 0x9E7A, 0xF6CA, 0x9E9C, 0xF6CB, 0x9EEB, 0xF6CC, 0x9EEE, 0xF6CD, 0x9EED, 0xF6CE, 0x9F1B, + 0xF6CF, 0x9F18, 0xF6D0, 0x9F1A, 0xF6D1, 0x9F31, 0xF6D2, 0x9F4E, 0xF6D3, 0x9F65, 0xF6D4, 0x9F64, 0xF6D5, 0x9F92, 0xF6D6, 0x4EB9, + 0xF6D7, 0x56C6, 0xF6D8, 0x56C5, 0xF6D9, 0x56CB, 0xF6DA, 0x5971, 0xF6DB, 0x5B4B, 0xF6DC, 0x5B4C, 0xF6DD, 0x5DD5, 0xF6DE, 0x5DD1, + 0xF6DF, 0x5EF2, 0xF6E0, 0x6521, 0xF6E1, 0x6520, 0xF6E2, 0x6526, 0xF6E3, 0x6522, 0xF6E4, 0x6B0B, 0xF6E5, 0x6B08, 0xF6E6, 0x6B09, + 0xF6E7, 0x6C0D, 0xF6E8, 0x7055, 0xF6E9, 0x7056, 0xF6EA, 0x7057, 0xF6EB, 0x7052, 0xF6EC, 0x721E, 0xF6ED, 0x721F, 0xF6EE, 0x72A9, + 0xF6EF, 0x737F, 0xF6F0, 0x74D8, 0xF6F1, 0x74D5, 0xF6F2, 0x74D9, 0xF6F3, 0x74D7, 0xF6F4, 0x766D, 0xF6F5, 0x76AD, 0xF6F6, 0x7935, + 0xF6F7, 0x79B4, 0xF6F8, 0x7A70, 0xF6F9, 0x7A71, 0xF6FA, 0x7C57, 0xF6FB, 0x7C5C, 0xF6FC, 0x7C59, 0xF6FD, 0x7C5B, 0xF6FE, 0x7C5A, + 0xF740, 0x7CF4, 0xF741, 0x7CF1, 0xF742, 0x7E91, 0xF743, 0x7F4F, 0xF744, 0x7F87, 0xF745, 0x81DE, 0xF746, 0x826B, 0xF747, 0x8634, + 0xF748, 0x8635, 0xF749, 0x8633, 0xF74A, 0x862C, 0xF74B, 0x8632, 0xF74C, 0x8636, 0xF74D, 0x882C, 0xF74E, 0x8828, 0xF74F, 0x8826, + 0xF750, 0x882A, 0xF751, 0x8825, 0xF752, 0x8971, 0xF753, 0x89BF, 0xF754, 0x89BE, 0xF755, 0x89FB, 0xF756, 0x8B7E, 0xF757, 0x8B84, + 0xF758, 0x8B82, 0xF759, 0x8B86, 0xF75A, 0x8B85, 0xF75B, 0x8B7F, 0xF75C, 0x8D15, 0xF75D, 0x8E95, 0xF75E, 0x8E94, 0xF75F, 0x8E9A, + 0xF760, 0x8E92, 0xF761, 0x8E90, 0xF762, 0x8E96, 0xF763, 0x8E97, 0xF764, 0x8F60, 0xF765, 0x8F62, 0xF766, 0x9147, 0xF767, 0x944C, + 0xF768, 0x9450, 0xF769, 0x944A, 0xF76A, 0x944B, 0xF76B, 0x944F, 0xF76C, 0x9447, 0xF76D, 0x9445, 0xF76E, 0x9448, 0xF76F, 0x9449, + 0xF770, 0x9446, 0xF771, 0x973F, 0xF772, 0x97E3, 0xF773, 0x986A, 0xF774, 0x9869, 0xF775, 0x98CB, 0xF776, 0x9954, 0xF777, 0x995B, + 0xF778, 0x9A4E, 0xF779, 0x9A53, 0xF77A, 0x9A54, 0xF77B, 0x9A4C, 0xF77C, 0x9A4F, 0xF77D, 0x9A48, 0xF77E, 0x9A4A, 0xF7A1, 0x9A49, + 0xF7A2, 0x9A52, 0xF7A3, 0x9A50, 0xF7A4, 0x9AD0, 0xF7A5, 0x9B19, 0xF7A6, 0x9B2B, 0xF7A7, 0x9B3B, 0xF7A8, 0x9B56, 0xF7A9, 0x9B55, + 0xF7AA, 0x9C46, 0xF7AB, 0x9C48, 0xF7AC, 0x9C3F, 0xF7AD, 0x9C44, 0xF7AE, 0x9C39, 0xF7AF, 0x9C33, 0xF7B0, 0x9C41, 0xF7B1, 0x9C3C, + 0xF7B2, 0x9C37, 0xF7B3, 0x9C34, 0xF7B4, 0x9C32, 0xF7B5, 0x9C3D, 0xF7B6, 0x9C36, 0xF7B7, 0x9DDB, 0xF7B8, 0x9DD2, 0xF7B9, 0x9DDE, + 0xF7BA, 0x9DDA, 0xF7BB, 0x9DCB, 0xF7BC, 0x9DD0, 0xF7BD, 0x9DDC, 0xF7BE, 0x9DD1, 0xF7BF, 0x9DDF, 0xF7C0, 0x9DE9, 0xF7C1, 0x9DD9, + 0xF7C2, 0x9DD8, 0xF7C3, 0x9DD6, 0xF7C4, 0x9DF5, 0xF7C5, 0x9DD5, 0xF7C6, 0x9DDD, 0xF7C7, 0x9EB6, 0xF7C8, 0x9EF0, 0xF7C9, 0x9F35, + 0xF7CA, 0x9F33, 0xF7CB, 0x9F32, 0xF7CC, 0x9F42, 0xF7CD, 0x9F6B, 0xF7CE, 0x9F95, 0xF7CF, 0x9FA2, 0xF7D0, 0x513D, 0xF7D1, 0x5299, + 0xF7D2, 0x58E8, 0xF7D3, 0x58E7, 0xF7D4, 0x5972, 0xF7D5, 0x5B4D, 0xF7D6, 0x5DD8, 0xF7D7, 0x882F, 0xF7D8, 0x5F4F, 0xF7D9, 0x6201, + 0xF7DA, 0x6203, 0xF7DB, 0x6204, 0xF7DC, 0x6529, 0xF7DD, 0x6525, 0xF7DE, 0x6596, 0xF7DF, 0x66EB, 0xF7E0, 0x6B11, 0xF7E1, 0x6B12, + 0xF7E2, 0x6B0F, 0xF7E3, 0x6BCA, 0xF7E4, 0x705B, 0xF7E5, 0x705A, 0xF7E6, 0x7222, 0xF7E7, 0x7382, 0xF7E8, 0x7381, 0xF7E9, 0x7383, + 0xF7EA, 0x7670, 0xF7EB, 0x77D4, 0xF7EC, 0x7C67, 0xF7ED, 0x7C66, 0xF7EE, 0x7E95, 0xF7EF, 0x826C, 0xF7F0, 0x863A, 0xF7F1, 0x8640, + 0xF7F2, 0x8639, 0xF7F3, 0x863C, 0xF7F4, 0x8631, 0xF7F5, 0x863B, 0xF7F6, 0x863E, 0xF7F7, 0x8830, 0xF7F8, 0x8832, 0xF7F9, 0x882E, + 0xF7FA, 0x8833, 0xF7FB, 0x8976, 0xF7FC, 0x8974, 0xF7FD, 0x8973, 0xF7FE, 0x89FE, 0xF840, 0x8B8C, 0xF841, 0x8B8E, 0xF842, 0x8B8B, + 0xF843, 0x8B88, 0xF844, 0x8C45, 0xF845, 0x8D19, 0xF846, 0x8E98, 0xF847, 0x8F64, 0xF848, 0x8F63, 0xF849, 0x91BC, 0xF84A, 0x9462, + 0xF84B, 0x9455, 0xF84C, 0x945D, 0xF84D, 0x9457, 0xF84E, 0x945E, 0xF84F, 0x97C4, 0xF850, 0x97C5, 0xF851, 0x9800, 0xF852, 0x9A56, + 0xF853, 0x9A59, 0xF854, 0x9B1E, 0xF855, 0x9B1F, 0xF856, 0x9B20, 0xF857, 0x9C52, 0xF858, 0x9C58, 0xF859, 0x9C50, 0xF85A, 0x9C4A, + 0xF85B, 0x9C4D, 0xF85C, 0x9C4B, 0xF85D, 0x9C55, 0xF85E, 0x9C59, 0xF85F, 0x9C4C, 0xF860, 0x9C4E, 0xF861, 0x9DFB, 0xF862, 0x9DF7, + 0xF863, 0x9DEF, 0xF864, 0x9DE3, 0xF865, 0x9DEB, 0xF866, 0x9DF8, 0xF867, 0x9DE4, 0xF868, 0x9DF6, 0xF869, 0x9DE1, 0xF86A, 0x9DEE, + 0xF86B, 0x9DE6, 0xF86C, 0x9DF2, 0xF86D, 0x9DF0, 0xF86E, 0x9DE2, 0xF86F, 0x9DEC, 0xF870, 0x9DF4, 0xF871, 0x9DF3, 0xF872, 0x9DE8, + 0xF873, 0x9DED, 0xF874, 0x9EC2, 0xF875, 0x9ED0, 0xF876, 0x9EF2, 0xF877, 0x9EF3, 0xF878, 0x9F06, 0xF879, 0x9F1C, 0xF87A, 0x9F38, + 0xF87B, 0x9F37, 0xF87C, 0x9F36, 0xF87D, 0x9F43, 0xF87E, 0x9F4F, 0xF8A1, 0x9F71, 0xF8A2, 0x9F70, 0xF8A3, 0x9F6E, 0xF8A4, 0x9F6F, + 0xF8A5, 0x56D3, 0xF8A6, 0x56CD, 0xF8A7, 0x5B4E, 0xF8A8, 0x5C6D, 0xF8A9, 0x652D, 0xF8AA, 0x66ED, 0xF8AB, 0x66EE, 0xF8AC, 0x6B13, + 0xF8AD, 0x705F, 0xF8AE, 0x7061, 0xF8AF, 0x705D, 0xF8B0, 0x7060, 0xF8B1, 0x7223, 0xF8B2, 0x74DB, 0xF8B3, 0x74E5, 0xF8B4, 0x77D5, + 0xF8B5, 0x7938, 0xF8B6, 0x79B7, 0xF8B7, 0x79B6, 0xF8B8, 0x7C6A, 0xF8B9, 0x7E97, 0xF8BA, 0x7F89, 0xF8BB, 0x826D, 0xF8BC, 0x8643, + 0xF8BD, 0x8838, 0xF8BE, 0x8837, 0xF8BF, 0x8835, 0xF8C0, 0x884B, 0xF8C1, 0x8B94, 0xF8C2, 0x8B95, 0xF8C3, 0x8E9E, 0xF8C4, 0x8E9F, + 0xF8C5, 0x8EA0, 0xF8C6, 0x8E9D, 0xF8C7, 0x91BE, 0xF8C8, 0x91BD, 0xF8C9, 0x91C2, 0xF8CA, 0x946B, 0xF8CB, 0x9468, 0xF8CC, 0x9469, + 0xF8CD, 0x96E5, 0xF8CE, 0x9746, 0xF8CF, 0x9743, 0xF8D0, 0x9747, 0xF8D1, 0x97C7, 0xF8D2, 0x97E5, 0xF8D3, 0x9A5E, 0xF8D4, 0x9AD5, + 0xF8D5, 0x9B59, 0xF8D6, 0x9C63, 0xF8D7, 0x9C67, 0xF8D8, 0x9C66, 0xF8D9, 0x9C62, 0xF8DA, 0x9C5E, 0xF8DB, 0x9C60, 0xF8DC, 0x9E02, + 0xF8DD, 0x9DFE, 0xF8DE, 0x9E07, 0xF8DF, 0x9E03, 0xF8E0, 0x9E06, 0xF8E1, 0x9E05, 0xF8E2, 0x9E00, 0xF8E3, 0x9E01, 0xF8E4, 0x9E09, + 0xF8E5, 0x9DFF, 0xF8E6, 0x9DFD, 0xF8E7, 0x9E04, 0xF8E8, 0x9EA0, 0xF8E9, 0x9F1E, 0xF8EA, 0x9F46, 0xF8EB, 0x9F74, 0xF8EC, 0x9F75, + 0xF8ED, 0x9F76, 0xF8EE, 0x56D4, 0xF8EF, 0x652E, 0xF8F0, 0x65B8, 0xF8F1, 0x6B18, 0xF8F2, 0x6B19, 0xF8F3, 0x6B17, 0xF8F4, 0x6B1A, + 0xF8F5, 0x7062, 0xF8F6, 0x7226, 0xF8F7, 0x72AA, 0xF8F8, 0x77D8, 0xF8F9, 0x77D9, 0xF8FA, 0x7939, 0xF8FB, 0x7C69, 0xF8FC, 0x7C6B, + 0xF8FD, 0x7CF6, 0xF8FE, 0x7E9A, 0xF940, 0x7E98, 0xF941, 0x7E9B, 0xF942, 0x7E99, 0xF943, 0x81E0, 0xF944, 0x81E1, 0xF945, 0x8646, + 0xF946, 0x8647, 0xF947, 0x8648, 0xF948, 0x8979, 0xF949, 0x897A, 0xF94A, 0x897C, 0xF94B, 0x897B, 0xF94C, 0x89FF, 0xF94D, 0x8B98, + 0xF94E, 0x8B99, 0xF94F, 0x8EA5, 0xF950, 0x8EA4, 0xF951, 0x8EA3, 0xF952, 0x946E, 0xF953, 0x946D, 0xF954, 0x946F, 0xF955, 0x9471, + 0xF956, 0x9473, 0xF957, 0x9749, 0xF958, 0x9872, 0xF959, 0x995F, 0xF95A, 0x9C68, 0xF95B, 0x9C6E, 0xF95C, 0x9C6D, 0xF95D, 0x9E0B, + 0xF95E, 0x9E0D, 0xF95F, 0x9E10, 0xF960, 0x9E0F, 0xF961, 0x9E12, 0xF962, 0x9E11, 0xF963, 0x9EA1, 0xF964, 0x9EF5, 0xF965, 0x9F09, + 0xF966, 0x9F47, 0xF967, 0x9F78, 0xF968, 0x9F7B, 0xF969, 0x9F7A, 0xF96A, 0x9F79, 0xF96B, 0x571E, 0xF96C, 0x7066, 0xF96D, 0x7C6F, + 0xF96E, 0x883C, 0xF96F, 0x8DB2, 0xF970, 0x8EA6, 0xF971, 0x91C3, 0xF972, 0x9474, 0xF973, 0x9478, 0xF974, 0x9476, 0xF975, 0x9475, + 0xF976, 0x9A60, 0xF977, 0x9C74, 0xF978, 0x9C73, 0xF979, 0x9C71, 0xF97A, 0x9C75, 0xF97B, 0x9E14, 0xF97C, 0x9E13, 0xF97D, 0x9EF6, + 0xF97E, 0x9F0A, 0xF9A1, 0x9FA4, 0xF9A2, 0x7068, 0xF9A3, 0x7065, 0xF9A4, 0x7CF7, 0xF9A5, 0x866A, 0xF9A6, 0x883E, 0xF9A7, 0x883D, + 0xF9A8, 0x883F, 0xF9A9, 0x8B9E, 0xF9AA, 0x8C9C, 0xF9AB, 0x8EA9, 0xF9AC, 0x8EC9, 0xF9AD, 0x974B, 0xF9AE, 0x9873, 0xF9AF, 0x9874, + 0xF9B0, 0x98CC, 0xF9B1, 0x9961, 0xF9B2, 0x99AB, 0xF9B3, 0x9A64, 0xF9B4, 0x9A66, 0xF9B5, 0x9A67, 0xF9B6, 0x9B24, 0xF9B7, 0x9E15, + 0xF9B8, 0x9E17, 0xF9B9, 0x9F48, 0xF9BA, 0x6207, 0xF9BB, 0x6B1E, 0xF9BC, 0x7227, 0xF9BD, 0x864C, 0xF9BE, 0x8EA8, 0xF9BF, 0x9482, + 0xF9C0, 0x9480, 0xF9C1, 0x9481, 0xF9C2, 0x9A69, 0xF9C3, 0x9A68, 0xF9C4, 0x9B2E, 0xF9C5, 0x9E19, 0xF9C6, 0x7229, 0xF9C7, 0x864B, + 0xF9C8, 0x8B9F, 0xF9C9, 0x9483, 0xF9CA, 0x9C79, 0xF9CB, 0x9EB7, 0xF9CC, 0x7675, 0xF9CD, 0x9A6B, 0xF9CE, 0x9C7A, 0xF9CF, 0x9E1D, + 0xF9D0, 0x7069, 0xF9D1, 0x706A, 0xF9D2, 0x9EA4, 0xF9D3, 0x9F7E, 0xF9D4, 0x9F49, 0xF9D5, 0x9F98, 0xF9D6, 0x7881, 0xF9D7, 0x92B9, + 0xF9D8, 0x88CF, 0xF9D9, 0x58BB, 0xF9DA, 0x6052, 0xF9DB, 0x7CA7, 0xF9DC, 0x5AFA, 0xF9DD, 0x2554, 0xF9DE, 0x2566, 0xF9DF, 0x2557, + 0xF9E0, 0x2560, 0xF9E1, 0x256C, 0xF9E2, 0x2563, 0xF9E3, 0x255A, 0xF9E4, 0x2569, 0xF9E5, 0x255D, 0xF9E6, 0x2552, 0xF9E7, 0x2564, + 0xF9E8, 0x2555, 0xF9E9, 0x255E, 0xF9EA, 0x256A, 0xF9EB, 0x2561, 0xF9EC, 0x2558, 0xF9ED, 0x2567, 0xF9EE, 0x255B, 0xF9EF, 0x2553, + 0xF9F0, 0x2565, 0xF9F1, 0x2556, 0xF9F2, 0x255F, 0xF9F3, 0x256B, 0xF9F4, 0x2562, 0xF9F5, 0x2559, 0xF9F6, 0x2568, 0xF9F7, 0x255C, + 0xF9F8, 0x2551, 0xF9F9, 0x2550, 0xF9FA, 0x256D, 0xF9FB, 0x256E, 0xF9FC, 0x2570, 0xF9FD, 0x256F, 0xF9FE, 0x2593, 0, 0 +}; +#endif + +#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 in UTF-16, 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 in UTF-16, 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 in UTF-16, 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/fusee/program/source/fatfs/fusee_diskio.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/fusee_diskio.cpp new file mode 100644 index 00000000..9afad4ff --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fatfs/fusee_diskio.cpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "diskio_cpp.h" +#include "../fusee_sd_card.hpp" +#include "../fusee_emummc.hpp" + +bool diskio_read_sd_card(void *dst, size_t size, size_t sector_index, size_t sector_count) { + return R_SUCCEEDED(::ams::nxboot::ReadSdCard(dst, size, sector_index, sector_count)); +} + +bool diskio_write_sd_card(size_t sector_index, size_t sector_count, const void *src, size_t size) { + return R_SUCCEEDED(::ams::nxboot::WriteSdCard(sector_index, sector_count, src, size)); +} + +bool diskio_read_system(void *dst, size_t size, size_t sector_index, size_t sector_count) { + return false; +} + +bool diskio_write_system(size_t sector_index, size_t sector_count, const void *src, size_t size) { + return false; +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fs/fusee_fs_api.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fs/fusee_fs_api.cpp new file mode 100644 index 00000000..d7218924 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fs/fusee_fs_api.cpp @@ -0,0 +1,327 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../fatfs/ff.h" +#include "fusee_fs_api.hpp" + +namespace ams::fs { + + static_assert(sizeof(DirectoryEntry) == sizeof(FILINFO)); + + namespace { + + constexpr size_t MaxFiles = 8 + 64; + constexpr size_t MaxDirectories = 2; + + constinit bool g_is_sd_mounted = false; + + alignas(0x10) constinit FATFS g_sd_fs = {}; + + alignas(0x10) constinit FIL g_files[MaxFiles] = {}; + alignas(0x10) constinit DIR g_dirs[MaxDirectories] = {}; + constinit bool g_files_opened[MaxFiles] = {}; + constinit bool g_dirs_opened[MaxFiles] = {}; + constinit int g_open_modes[MaxFiles] = {}; + + Result TranslateFatFsError(FRESULT res) { + switch (res) { + case FR_OK: + R_SUCCEED(); + case FR_DISK_ERR: + R_THROW(fs::ResultMmcAccessFailed()); + case FR_INT_ERR: + R_THROW(fs::ResultPreconditionViolation()); + case FR_NOT_READY: + R_THROW(fs::ResultMmcAccessFailed()); + case FR_NO_FILE: + R_THROW(fs::ResultPathNotFound()); + case FR_NO_PATH: + R_THROW(fs::ResultPathNotFound()); + case FR_INVALID_NAME: + R_THROW(fs::ResultInvalidPath()); + case FR_DENIED: + R_THROW(fs::ResultPermissionDenied()); + case FR_EXIST: + R_THROW(fs::ResultPathAlreadyExists()); + case FR_INVALID_OBJECT: + R_THROW(fs::ResultInvalidArgument()); + case FR_WRITE_PROTECTED: + R_THROW(fs::ResultWriteNotPermitted()); + case FR_INVALID_DRIVE: + R_THROW(fs::ResultInvalidMountName()); + case FR_NOT_ENABLED: + R_THROW(fs::ResultInvalidMountName()); /* BAD/TODO */ + case FR_NO_FILESYSTEM: + R_THROW(fs::ResultInvalidMountName()); /* BAD/TODO */ + case FR_TIMEOUT: + R_THROW(fs::ResultTargetLocked()); /* BAD/TODO */ + case FR_LOCKED: + R_THROW(fs::ResultTargetLocked()); + case FR_NOT_ENOUGH_CORE: + R_THROW(fs::ResultPreconditionViolation()); /* BAD/TODO */ + case FR_TOO_MANY_OPEN_FILES: + R_THROW(fs::ResultPreconditionViolation()); /* BAD/TODO */ + case FR_INVALID_PARAMETER: + R_THROW(fs::ResultInvalidArgument()); + default: + R_THROW(fs::ResultInternal()); + } + } + + int TranslateToFatFsMode(int mode) { + int fmode = FA_OPEN_EXISTING; + if ((mode & OpenMode_Read) != 0) { + fmode |= FA_READ; + } + if ((mode & OpenMode_Write) != 0) { + fmode |= FA_WRITE; + } + if ((mode & OpenMode_AllowAppend) != 0) { + fmode |= FA_OPEN_APPEND; + } + return fmode; + } + + FIL *GetInternalFile(FileHandle handle) { + return static_cast<FIL *>(handle._handle); + } + + DIR *GetInternalDirectory(DirectoryHandle handle) { + return static_cast<DIR *>(handle._handle); + } + + ALWAYS_INLINE size_t GetFileIndex(FIL *fp) { + const size_t file_index = (fp - g_files); + AMS_ASSERT(file_index < MaxFiles); + return file_index; + } + + ALWAYS_INLINE size_t GetDirectoryIndex(DIR *dp) { + const size_t dir_index = (dp - g_dirs); + AMS_ASSERT(dir_index < MaxDirectories); + return dir_index; + } + + } + + bool MountSdCard() { + AMS_ASSERT(!g_is_sd_mounted); + g_is_sd_mounted = f_mount(std::addressof(g_sd_fs), "sdmc:", 1) == FR_OK; + return g_is_sd_mounted; + } + + void UnmountSdCard() { + AMS_ASSERT(g_is_sd_mounted); + f_unmount("sdmc:"); + g_is_sd_mounted = false; + } + + Result GetEntryType(DirectoryEntryType *out_entry_type, bool *out_archive, const char *path) { + /* Get the file info. */ + FILINFO info; + R_TRY(TranslateFatFsError(f_stat(path, std::addressof(info)))); + + /* Handle the file. */ + *out_entry_type = (info.fattrib & AM_DIR) ? DirectoryEntryType_Directory : DirectoryEntryType_File; + *out_archive = (info.fattrib & AM_ARC); + + R_SUCCEED(); + } + + Result CreateFile(const char *path, s64 size) { + /* Create the file. */ + FIL fp; + R_TRY(TranslateFatFsError(f_open(std::addressof(fp), path, FA_CREATE_NEW | FA_READ | FA_WRITE))); + + /* Ensure that we close the file when we're done with it. */ + ON_SCOPE_EXIT { f_close(std::addressof(fp)); }; + + /* Expand the file. */ + R_TRY(TranslateFatFsError(f_expand(std::addressof(fp), size, 1))); + + R_SUCCEED(); + } + + Result CreateDirectory(const char *path) { + R_RETURN(TranslateFatFsError(f_mkdir(path))); + } + + Result OpenFile(FileHandle *out_file, const char *path, int mode) { + /* Find a free file. */ + for (size_t i = 0; i < MaxFiles; ++i) { + if (!g_files_opened[i]) { + /* Open the file. */ + FIL *fp = std::addressof(g_files[i]); + R_TRY(TranslateFatFsError(f_open(fp, path, TranslateToFatFsMode(mode)))); + + /* Set the output. */ + out_file->_handle = fp; + g_files_opened[i] = true; + g_open_modes[i] = mode; + R_SUCCEED(); + } + } + R_THROW(fs::ResultOpenCountLimit()); + } + + Result OpenDirectory(DirectoryHandle *out_dir, const char *path) { + /* Find a free directory. */ + for (size_t i = 0; i < MaxDirectories; ++i) { + if (!g_dirs_opened[i]) { + /* Open the file. */ + DIR *dp = std::addressof(g_dirs[i]); + R_TRY(TranslateFatFsError(f_opendir(dp, path))); + + /* Set the output. */ + out_dir->_handle = dp; + g_dirs_opened[i] = true; + R_SUCCEED(); + } + } + R_THROW(fs::ResultOpenCountLimit()); + } + + Result ReadDirectory(s64 *out_count, DirectoryEntry *out_entries, DirectoryHandle handle, s64 max_entries) { + DIR * const dp = GetInternalDirectory(handle); + + s64 count = 0; + while (count < max_entries) { + R_TRY(TranslateFatFsError(f_readdir(dp, reinterpret_cast<FILINFO *>(out_entries + count)))); + + if (out_entries[count].file_name[0] == '\x00') { + break; + } + + ++count; + } + + *out_count = count; + R_SUCCEED(); + } + + void CloseDirectory(DirectoryHandle handle) { + const size_t index = GetDirectoryIndex(GetInternalDirectory(handle)); + f_closedir(std::addressof(g_dirs[index])); + g_dirs_opened[index] = false; + } + + Result ReadFile(FileHandle handle, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) { + /* Option is unused. */ + AMS_UNUSED(option); + + /* Seek to the offset we're reading at. */ + R_TRY(TranslateFatFsError(f_lseek(GetInternalFile(handle), offset))); + + /* Read the data. */ + UINT br; + R_TRY(TranslateFatFsError(f_read(GetInternalFile(handle), buffer, size, std::addressof(br)))); + + /* Check that we read the correct amount. */ + R_UNLESS(br == size, fs::ResultOutOfRange()); + + R_SUCCEED(); + } + + Result ReadFile(FileHandle handle, s64 offset, void *buffer, size_t size) { + R_RETURN(ReadFile(handle, offset, buffer, size, fs::ReadOption::None)); + } + + Result ReadFile(size_t *out, FileHandle handle, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) { + /* Option is unused. */ + AMS_UNUSED(option); + + /* Seek to the offset we're reading at. */ + R_TRY(TranslateFatFsError(f_lseek(GetInternalFile(handle), offset))); + + /* Read the data. */ + UINT br; + R_TRY(TranslateFatFsError(f_read(GetInternalFile(handle), buffer, size, std::addressof(br)))); + + /* Set the output size. */ + *out = br; + + R_SUCCEED(); + } + + Result ReadFile(size_t *out, FileHandle handle, s64 offset, void *buffer, size_t size) { + R_RETURN(ReadFile(out, handle, offset, buffer, size, fs::ReadOption::None)); + } + + Result GetFileSize(s64 *out, FileHandle handle) { + FIL *fp = GetInternalFile(handle); + *out = f_size(fp); + R_SUCCEED(); + } + + Result FlushFile(FileHandle handle) { + R_RETURN(TranslateFatFsError(f_sync(GetInternalFile(handle)))); + } + + Result WriteFile(FileHandle handle, s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) { + /* Seek to the offset we're writing at. */ + R_TRY(TranslateFatFsError(f_lseek(GetInternalFile(handle), offset))); + + /* Write the data. */ + UINT bw; + R_TRY(TranslateFatFsError(f_write(GetInternalFile(handle), buffer, size, std::addressof(bw)))); + + /* Check that we wrote the correct amount. */ + R_UNLESS(bw == size, fs::ResultOutOfRange()); + + /* If we should, flush the file. */ + if (option.HasFlushFlag()) { + R_TRY(FlushFile(handle)); + } + + R_SUCCEED(); + } + + Result SetFileSize(FileHandle handle, s64 size) { + FIL *fp = GetInternalFile(handle); + + /* Check if we have nothing to do. */ + const size_t fsize = f_size(fp); + R_SUCCEED_IF(static_cast<FSIZE_t>(size) == fsize); + + /* NOTE/TODO: This may not preserve file data. Do this in a way that does? */ + + /* Truncate the file. */ + R_TRY(TranslateFatFsError(f_truncate(fp))); + + /* Expand the file. */ + R_TRY(TranslateFatFsError(f_expand(fp, size, 1))); + + /* Ensure the file is synchronized. */ + R_TRY(FlushFile(handle)); + + /* Check that our expansion succeeded. */ + AMS_ASSERT(f_size(fp) == static_cast<FSIZE_t>(size)); + + R_SUCCEED(); + } + + int GetFileOpenMode(FileHandle handle) { + return g_open_modes[GetFileIndex(GetInternalFile(handle))]; + } + + void CloseFile(FileHandle handle) { + const size_t index = GetFileIndex(GetInternalFile(handle)); + f_close(std::addressof(g_files[index])); + g_open_modes[index] = 0; + g_files_opened[index] = false; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fs/fusee_fs_api.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fs/fusee_fs_api.hpp new file mode 100644 index 00000000..d47ec195 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fs/fusee_fs_api.hpp @@ -0,0 +1,123 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::fs { + + enum OpenMode { + OpenMode_Read = (1 << 0), + OpenMode_Write = (1 << 1), + OpenMode_AllowAppend = (1 << 2), + + OpenMode_ReadWrite = (OpenMode_Read | OpenMode_Write), + OpenMode_All = (OpenMode_ReadWrite | OpenMode_AllowAppend), + }; + + struct ReadOption { + u32 _value; + + static const ReadOption None; + }; + + inline constexpr const ReadOption ReadOption::None = {0}; + + inline constexpr bool operator==(const ReadOption &lhs, const ReadOption &rhs) { + return lhs._value == rhs._value; + } + + inline constexpr bool operator!=(const ReadOption &lhs, const ReadOption &rhs) { + return !(lhs == rhs); + } + + static_assert(util::is_pod<ReadOption>::value && sizeof(ReadOption) == sizeof(u32)); + + struct WriteOption { + u32 _value; + + constexpr inline bool HasFlushFlag() const { + return _value & 1; + } + + static const WriteOption None; + static const WriteOption Flush; + }; + + inline constexpr const WriteOption WriteOption::None = {0}; + inline constexpr const WriteOption WriteOption::Flush = {1}; + + inline constexpr bool operator==(const WriteOption &lhs, const WriteOption &rhs) { + return lhs._value == rhs._value; + } + + inline constexpr bool operator!=(const WriteOption &lhs, const WriteOption &rhs) { + return !(lhs == rhs); + } + + static_assert(util::is_pod<WriteOption>::value && sizeof(WriteOption) == sizeof(u32)); + + enum DirectoryEntryType { + DirectoryEntryType_Directory = 0, + DirectoryEntryType_File = 1, + }; + + struct DirectoryEntry { + u64 file_size; + u16 file_date; + u16 file_time; + u8 file_attr; + char altname[13]; + char file_name[0x100]; + }; + + constexpr ALWAYS_INLINE DirectoryEntryType GetEntryType(const DirectoryEntry &entry) { + return (entry.file_attr & 0x10) ? DirectoryEntryType_Directory : DirectoryEntryType_File; + } + + struct FileHandle { + void *_handle; + }; + + struct DirectoryHandle { + void *_handle; + }; + + bool MountSdCard(); + void UnmountSdCard(); + + Result GetEntryType(DirectoryEntryType *out_entry_type, bool *out_archive, const char *path); + + Result CreateFile(const char *path, s64 size); + Result CreateDirectory(const char *path); + Result OpenFile(FileHandle *out_file, const char *path, int mode); + + Result OpenDirectory(DirectoryHandle *out_dir, const char *path); + + Result ReadDirectory(s64 *out_count, DirectoryEntry *out_entries, DirectoryHandle handle, s64 max_entries); + void CloseDirectory(DirectoryHandle handle); + + Result ReadFile(FileHandle handle, s64 offset, void *buffer, size_t size, const fs::ReadOption &option); + Result ReadFile(FileHandle handle, s64 offset, void *buffer, size_t size); + Result ReadFile(size_t *out, FileHandle handle, s64 offset, void *buffer, size_t size, const fs::ReadOption &option); + Result ReadFile(size_t *out, FileHandle handle, s64 offset, void *buffer, size_t size); + Result GetFileSize(s64 *out, FileHandle handle); + Result FlushFile(FileHandle handle); + Result WriteFile(FileHandle handle, s64 offset, const void *buffer, size_t size, const fs::WriteOption &option); + Result SetFileSize(FileHandle handle, s64 size); + int GetFileOpenMode(FileHandle handle); + void CloseFile(FileHandle handle); + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fs/fusee_fs_file_storage.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fs/fusee_fs_file_storage.cpp new file mode 100644 index 00000000..79c22a99 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fs/fusee_fs_file_storage.cpp @@ -0,0 +1,73 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_fs_storage.hpp" + +namespace ams::fs { + + Result FileHandleStorage::UpdateSize() { + R_SUCCEED_IF(m_size != InvalidSize); + R_RETURN(GetFileSize(std::addressof(m_size), m_handle)); + } + + Result FileHandleStorage::Read(s64 offset, void *buffer, size_t size) { + /* Immediately succeed if there's nothing to read. */ + R_SUCCEED_IF(size == 0); + + /* Validate buffer. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Ensure our size is valid. */ + R_TRY(this->UpdateSize()); + + /* Ensure our access is valid. */ + R_TRY(IStorage::CheckAccessRange(offset, size, m_size)); + + R_RETURN(ReadFile(m_handle, offset, buffer, size, fs::ReadOption())); + } + + Result FileHandleStorage::Write(s64 offset, const void *buffer, size_t size) { + /* Immediately succeed if there's nothing to write. */ + R_SUCCEED_IF(size == 0); + + /* Validate buffer. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Ensure our size is valid. */ + R_TRY(this->UpdateSize()); + + /* Ensure our access is valid. */ + R_TRY(IStorage::CheckAccessRange(offset, size, m_size)); + + R_RETURN(WriteFile(m_handle, offset, buffer, size, fs::WriteOption())); + } + + Result FileHandleStorage::Flush() { + R_RETURN(FlushFile(m_handle)); + } + + Result FileHandleStorage::GetSize(s64 *out_size) { + R_TRY(this->UpdateSize()); + *out_size = m_size; + R_SUCCEED(); + } + + Result FileHandleStorage::SetSize(s64 size) { + m_size = InvalidSize; + R_RETURN(SetFileSize(m_handle, size)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fs/fusee_fs_storage.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fs/fusee_fs_storage.hpp new file mode 100644 index 00000000..286a6bbd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fs/fusee_fs_storage.hpp @@ -0,0 +1,158 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> +#include "fusee_fs_api.hpp" + +namespace ams::fs { + + class IStorage { + public: + virtual Result Read(s64 offset, void *buffer, size_t size) = 0; + + virtual Result Write(s64 offset, const void *buffer, size_t size) = 0; + + virtual Result Flush() = 0; + + virtual Result SetSize(s64 size) = 0; + + virtual Result GetSize(s64 *out) = 0; + public: + static inline Result CheckAccessRange(s64 offset, s64 size, s64 total_size) { + R_UNLESS(offset >= 0, fs::ResultInvalidOffset()); + R_UNLESS(size >= 0, fs::ResultInvalidSize()); + R_UNLESS(util::CanAddWithoutOverflow<s64>(offset, size), fs::ResultOutOfRange()); + R_UNLESS(offset + size <= total_size, fs::ResultOutOfRange()); + R_SUCCEED(); + } + + static ALWAYS_INLINE Result CheckAccessRange(s64 offset, size_t size, s64 total_size) { + R_RETURN(CheckAccessRange(offset, static_cast<s64>(size), total_size)); + } + + static inline Result CheckOffsetAndSize(s64 offset, s64 size) { + R_UNLESS(offset >= 0, fs::ResultInvalidOffset()); + R_UNLESS(size >= 0, fs::ResultInvalidSize()); + R_UNLESS(util::CanAddWithoutOverflow<s64>(offset, size), fs::ResultOutOfRange()); + R_SUCCEED(); + } + + static ALWAYS_INLINE Result CheckOffsetAndSize(s64 offset, size_t size) { + R_RETURN(CheckOffsetAndSize(offset, static_cast<s64>(size))); + } + + static inline Result CheckOffsetAndSizeWithResult(s64 offset, s64 size, Result fail_result) { + R_TRY_CATCH(CheckOffsetAndSize(offset, size)) { + R_CONVERT_ALL(fail_result); + } R_END_TRY_CATCH; + R_SUCCEED(); + } + + static ALWAYS_INLINE Result CheckOffsetAndSizeWithResult(s64 offset, size_t size, Result fail_result) { + R_RETURN(CheckOffsetAndSizeWithResult(offset, static_cast<s64>(size), fail_result)); + } + }; + + class ReadOnlyStorageAdapter : public IStorage { + private: + IStorage &m_storage; + public: + ReadOnlyStorageAdapter(IStorage &s) : m_storage(s) { /* ... */ } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + R_RETURN(m_storage.Read(offset, buffer, size)); + } + + virtual Result Flush() override { + R_RETURN(m_storage.Flush()); + } + + virtual Result GetSize(s64 *out) override { + R_RETURN(m_storage.GetSize(out)); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + R_THROW(fs::ResultUnsupportedOperation()); + } + + virtual Result SetSize(s64 size) override { + R_THROW(fs::ResultUnsupportedOperation()); + } + }; + + class SubStorage : public IStorage { + private: + IStorage &m_storage; + s64 m_offset; + s64 m_size; + public: + SubStorage(IStorage &s, s64 o, s64 sz) : m_storage(s), m_offset(o), m_size(sz) { /* ... */ } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + /* Succeed immediately on zero-sized operation. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments and read. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + R_TRY(IStorage::CheckAccessRange(offset, size, m_size)); + R_RETURN(m_storage.Read(m_offset + offset, buffer, size)); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override{ + /* Succeed immediately on zero-sized operation. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments and write. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + R_TRY(IStorage::CheckAccessRange(offset, size, m_size)); + R_RETURN(m_storage.Write(m_offset + offset, buffer, size)); + } + + virtual Result Flush() override { + R_RETURN(m_storage.Flush()); + } + + virtual Result GetSize(s64 *out) override { + *out = m_size; + R_SUCCEED(); + } + + virtual Result SetSize(s64 size) override { + R_THROW(fs::ResultUnsupportedSetSizeForNotResizableSubStorage()); + } + }; + + class FileHandleStorage : public IStorage { + private: + static constexpr s64 InvalidSize = -1; + private: + FileHandle m_handle; + s64 m_size; + public: + constexpr explicit FileHandleStorage(FileHandle handle) : m_handle(handle), m_size(InvalidSize) { /* ... */ } + + ~FileHandleStorage() { fs::CloseFile(m_handle); } + protected: + Result UpdateSize(); + public: + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + virtual Result Flush() override; + virtual Result GetSize(s64 *out_size) override; + virtual Result SetSize(s64 size) override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_cpu.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_cpu.cpp new file mode 100644 index 00000000..2a5f07a6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_cpu.cpp @@ -0,0 +1,181 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_cpu.hpp" + +namespace ams::nxboot { + + namespace { + + constexpr inline const uintptr_t CLKRST = secmon::MemoryRegionPhysicalDeviceClkRst.GetAddress(); + constexpr inline const uintptr_t PMC = secmon::MemoryRegionPhysicalDevicePmc.GetAddress(); + constexpr inline const uintptr_t FLOW = secmon::MemoryRegionPhysicalDeviceFlowController.GetAddress(); + constexpr inline const uintptr_t EVP = secmon::MemoryRegionPhysicalDeviceExceptionVectors.GetAddress(); + constexpr inline const uintptr_t SYSTEM = secmon::MemoryRegionPhysicalDeviceSystem.GetAddress(); + + bool IsPartitionPowered(u32 mask) { + return (reg::Read(PMC + APBDEV_PMC_PWRGATE_STATUS) & mask) == mask; + } + + void PowerOnPartition(u32 status_mask, u32 toggle_mask) { + /* Check if the partition is already powered on. */ + if (IsPartitionPowered(status_mask)) { + return; + } + + /* Wait for PWRGATE_TOGGLE to be idle. */ + auto timeout = 5000; + while (true) { + if (reg::HasValue(PMC + APBDEV_PMC_PWRGATE_TOGGLE, PMC_REG_BITS_ENUM(PWRGATE_TOGGLE_START, DISABLE))) { + break; + } + + util::WaitMicroSeconds(1); + if ((--timeout) < 0) { + return; + } + } + + /* Toggle on the desired partition. */ + reg::SetField(toggle_mask, PMC_REG_BITS_ENUM(PWRGATE_TOGGLE_START, ENABLE)); + reg::Write(PMC + APBDEV_PMC_PWRGATE_TOGGLE, toggle_mask); + + /* Wait for the partition to be powered. */ + timeout = 5000; + while (true) { + if (IsPartitionPowered(status_mask)) { + break; + } + + util::WaitMicroSeconds(1); + if ((--timeout) < 0) { + return; + } + } + } + + } + + void SetupCpu(uintptr_t entrypoint) { + /* Set ACTIVE_CLUSTER to FAST. */ + reg::ReadWrite(FLOW + FLOW_CTLR_BPMP_CLUSTER_CONTROL, FLOW_REG_BITS_ENUM(BPMP_CLUSTER_CONTROL_ACTIVE_CLUSTER, FAST)); + + /* Enable VDD_CPU. */ + pmic::EnableVddCpu(fuse::GetRegulator()); + + /* Enable clock to the cpu. */ + { + /* Initialize PllX */ + if (!reg::HasValue(CLKRST + CLK_RST_CONTROLLER_PLLX_BASE, CLK_RST_REG_BITS_ENUM(PLLX_BASE_PLLX_ENABLE, ENABLE))) { + /* Disable IDDQ. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_PLLX_MISC3, CLK_RST_REG_BITS_VALUE(PLLX_MISC3_PLLX_IDDQ, 0)); + + /* Wait two microseconds. */ + util::WaitMicroSeconds(2); + + /* Configure PLLX dividers. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLX_BASE, 0x80404E02); + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLX_BASE, 0x00404E02); + + /* Set PLLX_LOCK_ENABLE. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_PLLX_MISC, CLK_RST_REG_BITS_ENUM(PLLX_MISC_PLLX_LOCK_ENABLE, ENABLE)); + + /* Enable PLLX. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLX_BASE, 0x40404E02); + } + + /* Wait for PLLX to be locked. */ + while (!reg::HasValue(CLKRST + CLK_RST_CONTROLLER_PLLX_BASE, CLK_RST_REG_BITS_ENUM(PLLX_BASE_PLLX_LOCK, LOCK))) { + /* ... */ + } + + /* Select MSELECT clock source as PLLP_OUT0 with divider of 4. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_MSELECT, CLK_RST_REG_BITS_ENUM (CLK_SOURCE_MSELECT_MSELECT_CLK_SRC, PLLP_OUT0), + CLK_RST_REG_BITS_VALUE(CLK_SOURCE_MSELECT_MSELECT_CLK_DIVISOR, 6)); + + /* Enable clock to MSELECT. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_V, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_V_CLK_ENB_MSELECT, ENABLE)); + + /* Configure CCLK_BURST_POLICY. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_CCLK_BURST_POLICY, CLK_RST_REG_BITS_ENUM(CCLK_BURST_POLICY_CWAKEUP_IDLE_SOURCE, PLLX_OUT0_LJ), + CLK_RST_REG_BITS_ENUM(CCLK_BURST_POLICY_CWAKEUP_RUN_SOURCE, PLLX_OUT0_LJ), + CLK_RST_REG_BITS_ENUM(CCLK_BURST_POLICY_CWAKEUP_IRQ_SOURCE, PLLX_OUT0_LJ), + CLK_RST_REG_BITS_ENUM(CCLK_BURST_POLICY_CWAKEUP_FIQ_SOURCE, PLLX_OUT0_LJ), + CLK_RST_REG_BITS_ENUM(CCLK_BURST_POLICY_CPU_STATE, RUN)); + + /* Configure SUPER_CCLK_DIVIDER. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_SUPER_CCLK_DIVIDER, CLK_RST_REG_BITS_ENUM (SUPER_CCLK_DIVIDER_SUPER_CDIV_ENB, ENABLE), + CLK_RST_REG_BITS_ENUM (SUPER_CCLK_DIVIDER_SUPER_CDIV_DIS_FROM_COP_FIQ, NO_IMPACT), + CLK_RST_REG_BITS_ENUM (SUPER_CCLK_DIVIDER_SUPER_CDIV_DIS_FROM_CPU_FIQ, NO_IMPACT), + CLK_RST_REG_BITS_ENUM (SUPER_CCLK_DIVIDER_SUPER_CDIV_DIS_FROM_COP_IRQ, NO_IMPACT), + CLK_RST_REG_BITS_ENUM (SUPER_CCLK_DIVIDER_SUPER_CDIV_DIS_FROM_CPU_IRQ, NO_IMPACT), + CLK_RST_REG_BITS_VALUE(SUPER_CCLK_DIVIDER_SUPER_CDIV_DIVIDEND, 0), + CLK_RST_REG_BITS_VALUE(SUPER_CCLK_DIVIDER_SUPER_CDIV_DIVISOR, 0)); + + + /* Enable CPUG. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_V_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_V_SET_SET_CLK_ENB_CPUG, ENABLE)); + } + + /* Enable coresight. */ + clkrst::EnableCsiteClock(); + + /* Restore PROD setting to CPU_SOFTRST_CTRL2 by clearing CAR2PMC_CPU_ACK_WIDTH. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_CPU_SOFTRST_CTRL2, CLK_RST_REG_BITS_VALUE(CPU_SOFTRST_CTRL2_CAR2PMC_CPU_ACK_WIDTH, 0)); + + /* Power on cpu rails. */ + { + PowerOnPartition(reg::EncodeValue(PMC_REG_BITS_ENUM(PWRGATE_STATUS_CRAIL, ON)), reg::EncodeValue(PMC_REG_BITS_ENUM(PWRGATE_TOGGLE_PARTID, CRAIL))); + PowerOnPartition(reg::EncodeValue(PMC_REG_BITS_ENUM(PWRGATE_STATUS_C0NC, ON)), reg::EncodeValue(PMC_REG_BITS_ENUM(PWRGATE_TOGGLE_PARTID, C0NC))); + PowerOnPartition(reg::EncodeValue(PMC_REG_BITS_ENUM(PWRGATE_STATUS_CE0, ON)), reg::EncodeValue(PMC_REG_BITS_ENUM(PWRGATE_TOGGLE_PARTID, CE0))); + } + + /* Do RAM Repair. */ + { + reg::Write(FLOW + FLOW_CTLR_RAM_REPAIR, FLOW_REG_BITS_ENUM(RAM_REPAIR_REQ, ENABLE)); + + while (!reg::HasValue(FLOW + FLOW_CTLR_RAM_REPAIR, FLOW_REG_BITS_ENUM(RAM_REPAIR_STS, DONE))) { + /* ... */ + } + } + + /* Configure CPU reset vector. */ + reg::Write(EVP + EVP_CPU_RESET_VECTOR, 0); + + reg::Write(SYSTEM + SB_AA64_RESET_LOW, entrypoint | 0x1); + reg::Write(SYSTEM + SB_AA64_RESET_HIGH, 0); + reg::Write(SYSTEM + SB_CSR, SB_REG_BITS_ENUM(CSR_NS_RST_VEC_WR_DIS, DISABLE)); + reg::Read(SYSTEM + SB_CSR); + } + + void StartCpu() { + /* NOTE: Here nintendo sets CPU_STRICT_TZ_APERTURE_CHECK, which we will not set. */ + + /* Clear MSELECT reset. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_RST_DEVICES_V, CLK_RST_REG_BITS_ENUM(RST_DEVICES_V_SWR_MSELECT_RST, DISABLE)); + + /* Take non-cpu out of reset. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_RST_CPUG_CMPLX_CLR, CLK_RST_REG_BITS_ENUM(RST_CPUG_CMPLX_CLR_CLR_NONCPURESET, ENABLE)); + + /* Clear cpu reset. */ + + reg::Write(CLKRST + CLK_RST_CONTROLLER_RST_CPUG_CMPLX_CLR, CLK_RST_REG_BITS_ENUM(RST_CPUG_CMPLX_CLR_CLR_CPURESET0, ENABLE), + CLK_RST_REG_BITS_ENUM(RST_CPUG_CMPLX_CLR_CLR_CORERESET0, ENABLE), + CLK_RST_REG_BITS_ENUM(RST_CPUG_CMPLX_CLR_CLR_PRESETDBG, ENABLE), + CLK_RST_REG_BITS_ENUM(RST_CPUG_CMPLX_CLR_CLR_L2RESET, ENABLE)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_cpu.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_cpu.hpp new file mode 100644 index 00000000..6d79bbfa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_cpu.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::nxboot { + + void SetupCpu(uintptr_t entrypoint); + void StartCpu(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_crt0.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_crt0.cpp new file mode 100644 index 00000000..78421f6c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_crt0.cpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_exception_handler.hpp" + +extern "C" void __libc_init_array(); + +namespace ams::nxboot::crt0 { + + namespace { + + ALWAYS_INLINE void SetExceptionVector(u32 which, uintptr_t impl) { + reg::Write(secmon::MemoryRegionPhysicalDeviceExceptionVectors.GetAddress() + 0x200 + sizeof(u32) * which, static_cast<u32>(impl)); + } + + } + + void Initialize() { + /* TODO: Collect timing information? */ + + /* Setup exception vectors. */ + { + SetExceptionVector(0, reinterpret_cast<uintptr_t>(::ams::nxboot::ExceptionHandler0)); + SetExceptionVector(1, reinterpret_cast<uintptr_t>(::ams::nxboot::ExceptionHandler1)); + SetExceptionVector(2, reinterpret_cast<uintptr_t>(::ams::nxboot::ExceptionHandler2)); + SetExceptionVector(3, reinterpret_cast<uintptr_t>(::ams::nxboot::ExceptionHandler3)); + SetExceptionVector(4, reinterpret_cast<uintptr_t>(::ams::nxboot::ExceptionHandler4)); + SetExceptionVector(5, reinterpret_cast<uintptr_t>(::ams::nxboot::ExceptionHandler5)); + SetExceptionVector(6, reinterpret_cast<uintptr_t>(::ams::nxboot::ExceptionHandler6)); + SetExceptionVector(7, reinterpret_cast<uintptr_t>(::ams::nxboot::ExceptionHandler7)); + } + + /* Call init array. */ + __libc_init_array(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_display.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_display.cpp new file mode 100644 index 00000000..4e5ade79 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_display.cpp @@ -0,0 +1,653 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_registers_di.hpp" +#include "fusee_display.hpp" +#include "fusee_print.hpp" +#include "fusee_fatal.hpp" + +namespace ams::nxboot { + + namespace { + + #include "fusee_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::MemoryRegionPhysicalDevicePmc .GetAddress(); + constexpr inline const uintptr_t g_disp1_regs = secmon::MemoryRegionPhysicalDeviceDisp1 .GetAddress(); + constexpr inline const uintptr_t g_dsi_regs = secmon::MemoryRegionPhysicalDeviceDsi .GetAddress(); + constexpr inline const uintptr_t g_clk_rst_regs = secmon::MemoryRegionPhysicalDeviceClkRst .GetAddress(); + constexpr inline const uintptr_t g_gpio_regs = secmon::MemoryRegionPhysicalDeviceGpio .GetAddress(); + constexpr inline const uintptr_t g_apb_misc_regs = secmon::MemoryRegionPhysicalDeviceApbMisc.GetAddress(); + constexpr inline const uintptr_t g_mipi_cal_regs = secmon::MemoryRegionPhysicalDeviceMipiCal.GetAddress(); + + constinit u32 *g_frame_buffer = nullptr; + + constinit u32 g_lcd_vendor = 0; + constinit bool g_display_initialized = false; + + 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 (fuse::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) { + g_frame_buffer = reinterpret_cast<u32 *>(0xC0400000); + } + hw::FlushDataCache(g_frame_buffer, FrameBufferSize); + } + + [[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); + } + } + } + + bool IsDisplayInitialized() { + return g_display_initialized; + } + + void InitializeDisplay() { + if (IsDisplayInitialized()) { + return; + } + + /* Setup the framebuffer. */ + InitializeFrameBuffer(); + + /* Get the hardware type. */ + const auto hw_type = fuse::GetHardwareType(); + + /* Turn on DSI/voltage rail. */ + { + if (fuse::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 (fuse::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; + } + g_lcd_vendor = (lcd_vendor & 0xFFFFFF00) | (host_response[2] & 0xFF); + } + + /* LCD vendor specific configuration. */ + switch (g_lcd_vendor) { + 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 (fuse::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 (g_lcd_vendor != 0x2050) { + util::WaitMicroSeconds(35'000ul); + } + + g_display_initialized = true; + } + + void FinalizeDisplay() { + if (!IsDisplayInitialized()) { + return; + } + + /* 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 (g_lcd_vendor == 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 (g_lcd_vendor == 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); + + g_display_initialized = false; + } + + void ShowDisplay() { + /* Enable backlight. */ + constexpr auto DisplayBrightness = 100; + if (g_lcd_vendor == 0x2050) { + EnableBacklightForVendor2050ForAula(DisplayBrightness); + } else { + EnableBacklightForGeneric(DisplayBrightness); + } + } + + u16 GetDisplayLcdVendor() { + return g_lcd_vendor; + } + + void ShowFatalError(const ams::impl::FatalErrorContext *f_ctx, const Result save_result) { + /* If needed, initialize the display. */ + if (!IsDisplayInitialized()) { + InitializeDisplay(); + } + + /* 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%" PRIx32 ")\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! (%08" PRIx32 ")\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); + + /* Show the console. */ + ShowDisplay(); + } + + void ShowFatalError(const char *fmt, ...) { + /* If needed, initialize the display. */ + if (!IsDisplayInitialized()) { + InitializeDisplay(); + } + + /* Initialize the console. */ + InitializeConsole(g_frame_buffer); + + { + Print("%s\n", "A fatal error occurred when running Fus" "\xe9" "e."); + { + std::va_list vl; + va_start(vl, fmt); + VPrint(fmt, vl); + va_end(vl); + } + Print("\n"); + + Print("\nPress POWER to reboot.\n"); + } + + /* Ensure the device will see consistent data. */ + hw::FlushDataCache(g_frame_buffer, FrameBufferSize); + + /* Show the console. */ + ShowDisplay(); + + WaitForReboot(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_display.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_display.hpp new file mode 100644 index 00000000..1a1a24a9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_display.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::nxboot { + + constexpr inline size_t FrameBufferHeight = 768; + constexpr inline size_t FrameBufferWidth = 1280; + constexpr inline size_t FrameBufferSize = FrameBufferHeight * FrameBufferWidth * sizeof(u32); + + bool IsDisplayInitialized(); + void InitializeDisplay(); + void FinalizeDisplay(); + + u16 GetDisplayLcdVendor(); + + void ShowDisplay(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_display_config.inc b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_display_config.inc new file mode 100644 index 00000000..84af10a1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_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 <http://www.gnu.org/licenses/>. + */ + +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 = 0xC0400000; + +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/fusee/program/source/fusee_emummc.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_emummc.cpp new file mode 100644 index 00000000..d30cc245 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_emummc.cpp @@ -0,0 +1,359 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_emummc.hpp" +#include "fusee_mmc.hpp" +#include "fusee_sd_card.hpp" +#include "fusee_fatal.hpp" +#include "fusee_malloc.hpp" +#include "fs/fusee_fs_api.hpp" +#include "fs/fusee_fs_storage.hpp" + +namespace ams::nxboot { + + namespace { + + class SdCardStorage : public fs::IStorage { + public: + virtual Result Read(s64 offset, void *buffer, size_t size) override { + if (!util::IsAligned(offset, sdmmc::SectorSize) || !util::IsAligned(size, sdmmc::SectorSize)) { + ShowFatalError("SdCard: unaligned access to %" PRIx64 ", size=%" PRIx64"\n", static_cast<u64>(offset), static_cast<u64>(size)); + } + + R_RETURN(ReadSdCard(buffer, size, offset / sdmmc::SectorSize, size / sdmmc::SectorSize)); + } + + virtual Result Flush() override { + R_SUCCEED(); + } + + virtual Result GetSize(s64 *out) override { + u32 num_sectors; + R_TRY(GetSdCardMemoryCapacity(std::addressof(num_sectors))); + + *out = static_cast<s64>(num_sectors) * static_cast<s64>(sdmmc::SectorSize); + R_SUCCEED(); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + R_THROW(fs::ResultUnsupportedOperation()); + } + + virtual Result SetSize(s64 size) override { + R_THROW(fs::ResultUnsupportedOperation()); + } + }; + + template<sdmmc::MmcPartition Partition> + class MmcPartitionStorage : public fs::IStorage { + public: + constexpr MmcPartitionStorage() { /* ... */ } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + if (!util::IsAligned(offset, sdmmc::SectorSize) || !util::IsAligned(size, sdmmc::SectorSize)) { + ShowFatalError("SdCard: unaligned access to %" PRIx64 ", size=%" PRIx64"\n", static_cast<u64>(offset), static_cast<u64>(size)); + } + + R_RETURN(ReadMmc(buffer, size, Partition, offset / sdmmc::SectorSize, size / sdmmc::SectorSize)); + } + + virtual Result Flush() override { + R_SUCCEED(); + } + + virtual Result GetSize(s64 *out) override { + u32 num_sectors; + R_TRY(GetMmcMemoryCapacity(std::addressof(num_sectors), Partition)); + + *out = num_sectors * sdmmc::SectorSize; + R_SUCCEED(); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + R_THROW(fs::ResultUnsupportedOperation()); + } + + virtual Result SetSize(s64 size) override { + R_THROW(fs::ResultUnsupportedOperation()); + } + }; + + using MmcBoot0Storage = MmcPartitionStorage<sdmmc::MmcPartition_BootPartition1>; + using MmcUserStorage = MmcPartitionStorage<sdmmc::MmcPartition_UserData>; + + constinit char g_emummc_path[0x300]; + + class EmummcFileStorage : public fs::IStorage { + private: + s64 m_file_size; + fs::FileHandle m_handles[64]; + bool m_open[64]; + int m_file_path_ofs; + private: + void EnsureFile(int id) { + if (!m_open[id]) { + /* Update path. */ + g_emummc_path[m_file_path_ofs + 1] = '0' + (id % 10); + g_emummc_path[m_file_path_ofs + 0] = '0' + (id / 10); + + /* Open new file. */ + const Result result = fs::OpenFile(m_handles + id, g_emummc_path, fs::OpenMode_Read); + if (R_FAILED(result)) { + ShowFatalError("Failed to open emummc user %02d file: 0x%08" PRIx32 "!\n", id, result.GetValue()); + } + + m_open[id] = true; + } + } + public: + EmummcFileStorage(fs::FileHandle user00, int ofs) : m_file_path_ofs(ofs) { + const Result result = fs::GetFileSize(std::addressof(m_file_size), user00); + if (R_FAILED(result)) { + ShowFatalError("Failed to get emummc file size: 0x%08" PRIx32 "!\n", result.GetValue()); + } + + for (size_t i = 0; i < util::size(m_handles); ++i) { + m_open[i] = false; + } + + m_handles[0] = user00; + m_open[0] = true; + } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + int file = offset / m_file_size; + s64 subofs = offset % m_file_size; + + u8 *cur_dst = static_cast<u8 *>(buffer); + + for (/* ... */; size > 0; ++file) { + /* Ensure the current file is open. */ + EnsureFile(file); + + /* Perform the current read. */ + const size_t cur_size = std::min<size_t>(m_file_size - subofs, size); + R_TRY(fs::ReadFile(m_handles[file], subofs, cur_dst, cur_size)); + + /* Advance. */ + cur_dst += cur_size; + size -= cur_size; + subofs = 0; + } + + R_SUCCEED(); + } + + virtual Result Flush() override { + R_THROW(fs::ResultUnsupportedOperation()); + } + + virtual Result GetSize(s64 *out) override { + R_THROW(fs::ResultUnsupportedOperation()); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + R_THROW(fs::ResultUnsupportedOperation()); + } + + virtual Result SetSize(s64 size) override { + R_THROW(fs::ResultUnsupportedOperation()); + } + }; + + constinit SdCardStorage g_sd_card_storage; + + constinit MmcBoot0Storage g_mmc_boot0_storage; + constinit MmcUserStorage g_mmc_user_storage; + + constinit fs::IStorage *g_boot0_storage = nullptr; + constinit fs::IStorage *g_user_storage = nullptr; + + constinit fs::SubStorage *g_package2_storage = nullptr; + + struct Guid { + u32 data1; + u16 data2; + u16 data3; + u8 data4[8]; + }; + static_assert(sizeof(Guid) == 0x10); + + struct GptHeader { + char signature[8]; + u32 revision; + u32 header_size; + u32 header_crc32; + u32 reserved0; + u64 my_lba; + u64 alt_lba; + u64 first_usable_lba; + u64 last_usable_lba; + Guid disk_guid; + u64 partition_entry_lba; + u32 number_of_partition_entries; + u32 size_of_partition_entry; + u32 partition_entry_array_crc32; + u32 reserved1; + }; + static_assert(sizeof(GptHeader) == 0x60); + + struct GptPartitionEntry { + Guid partition_type_guid; + Guid unique_partition_guid; + u64 starting_lba; + u64 ending_lba; + u64 attributes; + char partition_name[0x48]; + }; + static_assert(sizeof(GptPartitionEntry) == 0x80); + + struct Gpt { + GptHeader header; + u8 padding[0x1A0]; + GptPartitionEntry entries[128]; + }; + static_assert(sizeof(Gpt) == 16_KB + 0x200); + + constexpr const u16 Package2PartitionName[] = { + 'B', 'C', 'P', 'K', 'G', '2', '-', '1', '-', 'N', 'o', 'r', 'm', 'a', 'l', '-', 'M', 'a', 'i', 'n', 0 + }; + + } + + void InitializeEmummc(bool emummc_enabled, const secmon::EmummcConfiguration &emummc_cfg) { + Result result; + if (emummc_enabled) { + /* Get sd card size. */ + s64 sd_card_size; + if (R_FAILED((result = g_sd_card_storage.GetSize(std::addressof(sd_card_size))))) { + ShowFatalError("Failed to get sd card size: 0x%08" PRIx32 "!\n", result.GetValue()); + } + + if (emummc_cfg.base_cfg.type == secmon::EmummcType_Partition) { + const s64 partition_start = emummc_cfg.partition_cfg.start_sector * sdmmc::SectorSize; + g_boot0_storage = AllocateObject<fs::SubStorage>(g_sd_card_storage, partition_start, 4_MB); + g_user_storage = AllocateObject<fs::SubStorage>(g_sd_card_storage, partition_start + 8_MB, sd_card_size - (partition_start + 8_MB)); + } else if (emummc_cfg.base_cfg.type == secmon::EmummcType_File) { + /* Get the base emummc path. */ + std::memcpy(g_emummc_path, emummc_cfg.file_cfg.path.str, sizeof(emummc_cfg.file_cfg.path.str)); + + /* Get path length. */ + auto len = std::strlen(g_emummc_path); + + /* Append emmc. */ + std::memcpy(g_emummc_path + len, "/eMMC", 6); + len += 5; + + /* Open boot0. */ + fs::FileHandle boot0_file; + std::memcpy(g_emummc_path + len, "/boot0", 7); + if (R_FAILED((result = fs::OpenFile(std::addressof(boot0_file), g_emummc_path, fs::OpenMode_Read)))) { + ShowFatalError("Failed to open emummc boot0 file: 0x%08" PRIx32 "!\n", result.GetValue()); + } + + /* Open boot1. */ + g_emummc_path[len + 5] = '1'; + { + fs::DirectoryEntryType entry_type; + bool is_archive; + if (R_FAILED((result = fs::GetEntryType(std::addressof(entry_type), std::addressof(is_archive), g_emummc_path)))) { + ShowFatalError("Failed to find emummc boot1 file: 0x%08" PRIx32 "!\n", result.GetValue()); + } + + if (entry_type != fs::DirectoryEntryType_File) { + ShowFatalError("emummc boot1 file is not a file!\n"); + } + } + + /* Open userdata. */ + std::memcpy(g_emummc_path + len, "/00", 4); + fs::FileHandle user00_file; + if (R_FAILED((result = fs::OpenFile(std::addressof(user00_file), g_emummc_path, fs::OpenMode_Read)))) { + ShowFatalError("Failed to open emummc user %02d file: 0x%08" PRIx32 "!\n", 0, result.GetValue()); + } + + /* Create partitions. */ + g_boot0_storage = AllocateObject<fs::FileHandleStorage>(boot0_file); + g_user_storage = AllocateObject<EmummcFileStorage>(user00_file, len + 1); + } else { + ShowFatalError("Unknown emummc type %d\n", static_cast<int>(emummc_cfg.base_cfg.type)); + } + } else { + /* Initialize access to mmc. */ + { + const Result result = InitializeMmc(); + if (R_FAILED(result)) { + ShowFatalError("Failed to initialize mmc: 0x%08" PRIx32 "\n", result.GetValue()); + } + } + + /* Create storages. */ + g_boot0_storage = std::addressof(g_mmc_boot0_storage); + g_user_storage = std::addressof(g_mmc_user_storage); + } + if (g_boot0_storage == nullptr) { + ShowFatalError("Failed to initialize BOOT0\n"); + } + if (g_user_storage == nullptr) { + ShowFatalError("Failed to initialize Raw EMMC\n"); + } + + /* Read the GPT. */ + Gpt *gpt = static_cast<Gpt *>(AllocateAligned(sizeof(Gpt), 0x200)); + { + const Result result = g_user_storage->Read(0x200, gpt, sizeof(*gpt)); + if (R_FAILED(result)) { + ShowFatalError("Failed to read GPT: 0x%08" PRIx32 "\n", result.GetValue()); + } + } + + /* Check the GPT. */ + if (std::memcmp(gpt->header.signature, "EFI PART", 8) != 0) { + ShowFatalError("Invalid GPT signature\n"); + } + if (gpt->header.number_of_partition_entries > util::size(gpt->entries)) { + ShowFatalError("Too many GPT entries\n"); + } + + /* Create system storage. */ + for (u32 i = 0; i < gpt->header.number_of_partition_entries; ++i) { + if (gpt->entries[i].starting_lba < gpt->header.first_usable_lba) { + continue; + } + + const s64 offset = INT64_C(0x200) * gpt->entries[i].starting_lba; + const u64 size = UINT64_C(0x200) * (gpt->entries[i].ending_lba + 1 - gpt->entries[i].starting_lba); + + if (std::memcmp(gpt->entries[i].partition_name, Package2PartitionName, sizeof(Package2PartitionName)) == 0) { + g_package2_storage = AllocateObject<fs::SubStorage>(*g_user_storage, offset, size); + } + } + + /* Check that we created package2 storage. */ + if (g_package2_storage == nullptr) { + ShowFatalError("Failed to initialize Package2\n"); + } + } + + Result ReadBoot0(s64 offset, void *dst, size_t size) { + R_RETURN(g_boot0_storage->Read(offset, dst, size)); + } + + Result ReadPackage2(s64 offset, void *dst, size_t size) { + R_RETURN(g_package2_storage->Read(offset, dst, size)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_emummc.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_emummc.hpp new file mode 100644 index 00000000..5dd40aa8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_emummc.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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <vapours.hpp> +#include <exosphere/secmon/secmon_emummc_context.hpp> + +namespace ams::nxboot { + + void InitializeEmummc(bool emummc_enabled, const secmon::EmummcConfiguration &emummc_cfg); + + Result ReadBoot0(s64 offset, void *dst, size_t size); + Result ReadPackage2(s64 offset, void *dst, size_t size); + Result ReadSystem(s64 offset, void *dst, size_t size); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_exception_handler.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_exception_handler.cpp new file mode 100644 index 00000000..64f6cbc2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_exception_handler.cpp @@ -0,0 +1,84 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_exception_handler.hpp" +#include "fusee_fatal.hpp" + +namespace ams::nxboot { + + NORETURN void ExceptionHandlerImpl(s32 which, u32 lr, u32 svc_lr) { + ShowFatalError("Exception: which=%" PRId32 ", lr=%p, svc_lr=%p\n", which, reinterpret_cast<void *>(lr), reinterpret_cast<void *>(svc_lr)); + } + +} + +namespace ams::diag { + + NORETURN void AbortImpl(const char *expr, const char *func, const char *file, int line) { + AMS_UNUSED(expr, func, line, file); + + u32 lr; + __asm__ __volatile__("mov %0, lr" : "=r"(lr) :: "memory"); + nxboot::ShowFatalError("Abort called, lr=%p\n", reinterpret_cast<void *>(lr)); + } + + NORETURN void AbortImpl(const char *expr, const char *func, const char *file, int line, const char *format, ...) { + AMS_UNUSED(expr, func, line, file, format); + + u32 lr; + __asm__ __volatile__("mov %0, lr" : "=r"(lr) :: "memory"); + nxboot::ShowFatalError("Abort called, lr=%p\n", reinterpret_cast<void *>(lr)); + } + + NORETURN void AbortImpl(const char *expr, const char *func, const char *file, int line, const ::ams::Result *result, const char *format, ...) { + AMS_UNUSED(expr, func, line, file, result, format); + + u32 lr; + __asm__ __volatile__("mov %0, lr" : "=r"(lr) :: "memory"); + nxboot::ShowFatalError("Abort called, lr=%p, result=0x%08" PRIX32 "\n", reinterpret_cast<void *>(lr), result != nullptr ? result->GetValue() : 0); + } + + NORETURN void AbortImpl(const char *expr, const char *func, const char *file, int line, const ::ams::Result *result, const ::ams::os::UserExceptionInfo *exception_info, const char *format, ...) { + AMS_UNUSED(expr, func, line, file, result, exception_info, format); + + u32 lr; + __asm__ __volatile__("mov %0, lr" : "=r"(lr) :: "memory"); + nxboot::ShowFatalError("Abort called, lr=%p, result=0x%08" PRIX32 "\n", reinterpret_cast<void *>(lr), result != nullptr ? result->GetValue() : 0); + } + + NORETURN void AbortImpl() { + u32 lr; + __asm__ __volatile__("mov %0, lr" : "=r"(lr) :: "memory"); + nxboot::ShowFatalError("Abort called, lr=%p\n", reinterpret_cast<void *>(lr)); + } + + NORETURN void OnAssertionFailure(AssertionType type, const char *expr, const char *func, const char *file, int line) { + AMS_UNUSED(type, expr, func, file, line); + + u32 lr; + __asm__ __volatile__("mov %0, lr" : "=r"(lr) :: "memory"); + nxboot::ShowFatalError("Assert called, lr=%p\n", reinterpret_cast<void *>(lr)); + } + + NORETURN void OnAssertionFailure(AssertionType type, const char *expr, const char *func, const char *file, int line, const char *format, ...) { + AMS_UNUSED(type, expr, func, file, line, format); + + u32 lr; + __asm__ __volatile__("mov %0, lr" : "=r"(lr) :: "memory"); + nxboot::ShowFatalError("Assert called, lr=%p\n", reinterpret_cast<void *>(lr)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_exception_handler.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_exception_handler.hpp new file mode 100644 index 00000000..9124774c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_exception_handler.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#pragma once + +namespace ams::nxboot { + + NORETURN void ExceptionHandler(); + + NORETURN void ExceptionHandler0(); + NORETURN void ExceptionHandler1(); + NORETURN void ExceptionHandler2(); + NORETURN void ExceptionHandler3(); + NORETURN void ExceptionHandler4(); + NORETURN void ExceptionHandler5(); + NORETURN void ExceptionHandler6(); + NORETURN void ExceptionHandler7(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_exception_handler_asm.s b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_exception_handler_asm.s new file mode 100644 index 00000000..0f06dcc7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_exception_handler_asm.s @@ -0,0 +1,114 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +.section .text._ZN3ams6nxboot17ExceptionHandlerNEl, "ax", %progbits +.arm +.align 5 +.global _ZN3ams6nxboot17ExceptionHandlerNEl +.type _ZN3ams6nxboot17ExceptionHandlerNEl, %function +_ZN3ams6nxboot17ExceptionHandlerNEl: + /* Get the normal return address. */ + mov r1, lr + + /* Get the SVC return address. */ + msr cpsr_cf, #0xD3 + mov r2, lr + + /* Invoke the exception handler in abort mode. */ + msr cpsr_cf, #0xD7 + b _ZN3ams6nxboot20ExceptionHandlerImplElmm + + +.section .text._ZN3ams6nxboot16ExceptionHandlerEv, "ax", %progbits +.arm +.align 5 +.global _ZN3ams6nxboot16ExceptionHandlerEv +.type _ZN3ams6nxboot16ExceptionHandlerEv, %function +_ZN3ams6nxboot16ExceptionHandlerEv: + mov r0, #-1 + b _ZN3ams6nxboot17ExceptionHandlerNEl + +.section .text._ZN3ams6nxboot17ExceptionHandler0Ev, "ax", %progbits +.arm +.align 5 +.global _ZN3ams6nxboot17ExceptionHandler0Ev +.type _ZN3ams6nxboot17ExceptionHandler0Ev, %function +_ZN3ams6nxboot17ExceptionHandler0Ev: + mov r0, #0 + b _ZN3ams6nxboot17ExceptionHandlerNEl + +.section .text._ZN3ams6nxboot17ExceptionHandler1Ev, "ax", %progbits +.arm +.align 5 +.global _ZN3ams6nxboot17ExceptionHandler1Ev +.type _ZN3ams6nxboot17ExceptionHandler1Ev, %function +_ZN3ams6nxboot17ExceptionHandler1Ev: + mov r0, #1 + b _ZN3ams6nxboot17ExceptionHandlerNEl + +.section .text._ZN3ams6nxboot17ExceptionHandler2Ev, "ax", %progbits +.arm +.align 5 +.global _ZN3ams6nxboot17ExceptionHandler2Ev +.type _ZN3ams6nxboot17ExceptionHandler2Ev, %function +_ZN3ams6nxboot17ExceptionHandler2Ev: + mov r0, #2 + b _ZN3ams6nxboot17ExceptionHandlerNEl + +.section .text._ZN3ams6nxboot17ExceptionHandler3Ev, "ax", %progbits +.arm +.align 5 +.global _ZN3ams6nxboot17ExceptionHandler3Ev +.type _ZN3ams6nxboot17ExceptionHandler3Ev, %function +_ZN3ams6nxboot17ExceptionHandler3Ev: + mov r0, #3 + b _ZN3ams6nxboot17ExceptionHandlerNEl + +.section .text._ZN3ams6nxboot17ExceptionHandler4Ev, "ax", %progbits +.arm +.align 5 +.global _ZN3ams6nxboot17ExceptionHandler4Ev +.type _ZN3ams6nxboot17ExceptionHandler4Ev, %function +_ZN3ams6nxboot17ExceptionHandler4Ev: + mov r0, #4 + b _ZN3ams6nxboot17ExceptionHandlerNEl + +.section .text._ZN3ams6nxboot17ExceptionHandler5Ev, "ax", %progbits +.arm +.align 5 +.global _ZN3ams6nxboot17ExceptionHandler5Ev +.type _ZN3ams6nxboot17ExceptionHandler5Ev, %function +_ZN3ams6nxboot17ExceptionHandler5Ev: + mov r0, #5 + b _ZN3ams6nxboot17ExceptionHandlerNEl + +.section .text._ZN3ams6nxboot17ExceptionHandler6Ev, "ax", %progbits +.arm +.align 5 +.global _ZN3ams6nxboot17ExceptionHandler6Ev +.type _ZN3ams6nxboot17ExceptionHandler6Ev, %function +_ZN3ams6nxboot17ExceptionHandler6Ev: + mov r0, #6 + b _ZN3ams6nxboot17ExceptionHandlerNEl + +.section .text._ZN3ams6nxboot17ExceptionHandler7Ev, "ax", %progbits +.arm +.align 5 +.global _ZN3ams6nxboot17ExceptionHandler7Ev +.type _ZN3ams6nxboot17ExceptionHandler7Ev, %function +_ZN3ams6nxboot17ExceptionHandler7Ev: + mov r0, #7 + b _ZN3ams6nxboot17ExceptionHandlerNEl diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_external_package.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_external_package.hpp new file mode 100644 index 00000000..34e990ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_external_package.hpp @@ -0,0 +1,88 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> +#include "fusee_display.hpp" + +namespace ams::nxboot { + + constexpr inline const size_t ExternalPackageSize = 8_MB; + + constexpr inline const size_t InitialProcessStorageSizeMax = 3_MB / 8; + + struct ExternalPackageContentMeta { + u32 offset; + u32 size; + u8 type; + u8 flags[3]; + u32 pad; + char name[0x10]; + }; + static_assert(sizeof(ExternalPackageContentMeta) == 0x20); + + struct ExternalPackageKipMeta { + u64 program_id; + u32 offset; + u32 size; + se::Sha256Hash hash; + }; + static_assert(sizeof(ExternalPackageKipMeta) == 0x30); + + struct ExternalPackageHeader { + static constexpr u32 Magic = util::FourCC<'P', 'K', '3', '1'>::Code; + static constexpr u32 LegacyMagic = util::FourCC<'F','S','S','0'>::Code; + + u32 magic; /* Previously entrypoint. */ + u32 metadata_offset; + u32 flags; + u32 meso_size; + u32 num_kips; + u32 reserved1[3]; + u32 legacy_magic; + u32 total_size; + u32 reserved2; /* Previously crt0 offset. */ + u32 content_header_offset; + u32 num_content_headers; + u32 supported_hos_version; + u32 release_version; + u32 git_revision; + ExternalPackageContentMeta content_metas[(0x400 - 0x40) / sizeof(ExternalPackageContentMeta)]; + ExternalPackageKipMeta emummc_meta; + ExternalPackageKipMeta kip_metas[8]; + u8 reserved3[0x800 - (0x400 + 9 * sizeof(ExternalPackageKipMeta))]; + }; + static_assert(sizeof(ExternalPackageHeader) == 0x800); + + struct ExternalPackage { + ExternalPackageHeader header; /* 0x000000-0x000800 */ + u8 warmboot[0x1800]; /* 0x000800-0x002000 */ + u8 tsec_keygen[0x2000]; /* 0x002000-0x004000 */ + u8 mariko_fatal[0x1C000]; /* 0x004000-0x020000 */ + u8 ovl_mtc_erista[0x14000]; /* 0x020000-0x034000 */ + u8 ovl_mtc_mariko[0x14000]; /* 0x034000-0x048000 */ + u8 exosphere[0xE000]; /* 0x048000-0x056000 */ + u8 mesosphere[0xAA000]; /* 0x056000-0x100000 */ + u8 kips[3_MB]; /* 0x100000-0x400000 */ + u8 splash_screen_fb[FrameBufferSize]; /* 0x400000-0x7C0000 */ + u8 fusee[0x20000]; /* 0x7C0000-0x7E0000 */ + u8 reboot_stub[0x1000]; /* 0x7E0000-0x7E1000 */ + u8 reserved[0x1F000]; /* 0x7E1000-0x800000 */ + }; + static_assert(sizeof(ExternalPackage) == ExternalPackageSize); + + ALWAYS_INLINE const ExternalPackage &GetExternalPackage() { return *reinterpret_cast<const ExternalPackage *>(0xC0000000); } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_fatal.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_fatal.cpp new file mode 100644 index 00000000..843b1c23 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_fatal.cpp @@ -0,0 +1,124 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_fatal.hpp" +#include "fusee_external_package.hpp" +#include "fs/fusee_fs_api.hpp" + +namespace ams::nxboot { + + namespace { + + constexpr inline const uintptr_t PMC = secmon::MemoryRegionPhysicalDevicePmc.GetAddress(); + + Result SaveFatalErrorContext(const ams::impl::FatalErrorContext *ctx) { + /* Create and open the file. */ + fs::FileHandle file; + { + /* Generate the file path. */ + char path[0x40]; + util::TSNPrintf(path, sizeof(path), "sdmc:/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(); + } + + } + + NORETURN void RebootToSelf() { + /* Patch SDRAM init to perform an SVC immediately after second write. */ + reg::Write(PMC + APBDEV_PMC_SCRATCH45, 0x2E38DFFF); + reg::Write(PMC + APBDEV_PMC_SCRATCH46, 0x6001DC28); + + /* Set SVC handler to jump to reboot stub in IRAM. */ + reg::Write(PMC + APBDEV_PMC_SCRATCH33, 0x4003F000); + reg::Write(PMC + APBDEV_PMC_SCRATCH40, 0x6000F208); + + /* Set boot as warmboot. */ + reg::Write(PMC + APBDEV_PMC_SCRATCH0, (1 << 0)); + + /* Copy reboot stub into high IRAM. */ + std::memcpy(reinterpret_cast<void *>(0x4003F000), GetExternalPackage().reboot_stub, sizeof(GetExternalPackage().reboot_stub)); + + /* Copy our main payload into low IRAM. */ + std::memcpy(reinterpret_cast<void *>(0x40010000), GetExternalPackage().fusee, sizeof(GetExternalPackage().fusee)); + + /* Reboot. */ + reg::Write(PMC + APBDEV_PMC_CNTRL, PMC_REG_BITS_ENUM(CNTRL_MAIN_RESET, ENABLE)); + + /* Wait for the reboot to take. */ + AMS_INFINITE_LOOP(); + } + + void SaveAndShowFatalError() { + /* Get the context (at static location in memory). */ + ams::impl::FatalErrorContext *f_ctx = reinterpret_cast<ams::impl::FatalErrorContext *>(0x4003E000); + + /* Check for valid magic. */ + if (f_ctx->magic != ams::impl::FatalErrorContext::Magic) { + return; + } + + /* Show the fatal error. */ + ShowFatalError(f_ctx, SaveFatalErrorContext(f_ctx)); + + /* Clear the magic. */ + f_ctx->magic = ~f_ctx->magic; + + /* Wait for reboot. */ + WaitForReboot(); + } + + void WaitForReboot() { + /* Wait for power button to be pressed. */ + while (!pmic::IsPowerButtonPressed()) { + util::WaitMicroSeconds(100); + } + + /* If not erista, just do a normal reboot. */ + if (fuse::GetSocType() != fuse::SocType_Erista) { + /* Reboot. */ + pmic::ShutdownSystem(true); + + /* Wait for our reboot to complete. */ + AMS_INFINITE_LOOP(); + } + + /* Reboot to self, if we can. */ + if (GetExternalPackage().header.magic == ExternalPackageHeader::Magic) { + RebootToSelf(); + } else { + /* Just do a normal reboot. */ + pmic::ShutdownSystem(true); + + /* Wait for our reboot to complete. */ + AMS_INFINITE_LOOP(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_fatal.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_fatal.hpp new file mode 100644 index 00000000..951e4e68 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_fatal.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::nxboot { + + NORETURN void ShowFatalError(const char *fmt, ...) __attribute__((format(printf, 1, 2))); + void ShowFatalError(const ams::impl::FatalErrorContext *f_ctx, const Result save_result); + + void SaveAndShowFatalError(); + + NORETURN void WaitForReboot(); + + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_font.inc b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_font.inc new file mode 100644 index 00000000..37e1453f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_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 <http://www.gnu.org/licenses/>. + */ +/* + * (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/fusee/program/source/fusee_ini.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_ini.cpp new file mode 100644 index 00000000..5e9b6eb6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_ini.cpp @@ -0,0 +1,196 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_ini.hpp" +#include "fusee_malloc.hpp" +#include "fs/fusee_fs_api.hpp" + +namespace ams::nxboot { + + namespace { + + constexpr s64 IniFileSizeMax = 64_KB; + + constexpr bool IsWhiteSpace(char c) { + return c == ' ' || c == '\r'; + } + + } + + ParseIniResult ParseIniFile(IniSectionList &out_sections, const char *ini_path) { + /* Open the ini file. */ + fs::FileHandle file; + if (R_FAILED(fs::OpenFile(std::addressof(file), ini_path, fs::OpenMode_Read))) { + return ParseIniResult_NoFile; + } + + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Get file size. */ + s64 file_size; + if (R_FAILED(fs::GetFileSize(std::addressof(file_size), file))) { + return ParseIniResult_NoFile; + } + + /* Cap file size. */ + file_size = std::min(IniFileSizeMax, file_size); + + /* Allocate memory for file. */ + char *buffer = static_cast<char *>(AllocateAligned(util::AlignUp(file_size + 1, 0x10), 0x10)); + buffer[file_size] = '\x00'; + + /* Read file. */ + if (R_FAILED(fs::ReadFile(file, 0, buffer, file_size))) { + return ParseIniResult_NoFile; + } + + /* Parse the file. */ + enum class State { + Newline, + Comment, + SectionName, + Key, + KvSpace, + KvSpace2, + Value, + TrailingSpace, + }; + + char *sec_start, *key_start, *val_start, *val_end; + IniSection *cur_sec = nullptr; + + State state = State::Newline; + for (int i = 0; i < file_size; ++i) { + const char c = buffer[i]; + + switch (state) { + case State::Newline: + if (c == '[') { + sec_start = buffer + i + 1; + state = State::SectionName; + } else if (c == ';' || c == '#') { + state = State::Comment; + } else if (IsWhiteSpace(c) || c == '\n') { + state = State::Newline; + } else if (cur_sec != nullptr) { + key_start = buffer + i; + state = State::Key; + } else { + return ParseIniResult_InvalidFormat; + } + break; + case State::Comment: + if (c == '\n') { + state = State::Newline; + } + break; + case State::SectionName: + if (c == '\n') { + return ParseIniResult_InvalidFormat; + } else if (c == ']') { + cur_sec = AllocateObject<IniSection>(); + + cur_sec->name = sec_start; + buffer[i] = '\x00'; + + out_sections.push_back(*cur_sec); + + state = State::TrailingSpace; + } + break; + case State::Key: + if (c == '\n') { + return ParseIniResult_InvalidFormat; + } else if (IsWhiteSpace(c)) { + buffer[i] = '\x00'; + + state = State::KvSpace; + } else if (c == '=') { + buffer[i] = '\x00'; + + state = State::KvSpace2; + } + break; + case State::KvSpace: + if (c == '=') { + state = State::KvSpace2; + } else if (!IsWhiteSpace(c)) { + return ParseIniResult_InvalidFormat; + } + break; + case State::KvSpace2: + if (c == '\n') { + buffer[i] = '\x00'; + + auto *entry = AllocateObject<IniKeyValueEntry>(); + entry->key = key_start; + entry->value = buffer + i; + + cur_sec->kv_list.push_back(*entry); + + state = State::Newline; + } else if (!IsWhiteSpace(c)) { + val_start = buffer + i; + val_end = buffer + i + 1; + + state = State::Value; + } + break; + case State::Value: + if (c == '\r' || c == '\n') { + buffer[i] = '\x00'; + *val_end = '\x00'; + + auto *entry = AllocateObject<IniKeyValueEntry>(); + entry->key = key_start; + entry->value = val_start; + + cur_sec->kv_list.push_back(*entry); + + state = (c == '\n') ? State::Newline : State::TrailingSpace; + } else if (c != ' ') { + val_end = buffer + i + 1; + } + break; + case State::TrailingSpace: + if (c == '\n') { + state = State::Newline; + } else if (!IsWhiteSpace(c)) { + return ParseIniResult_InvalidFormat; + } + break; + } + } + + /* Accept value-state. */ + if (state == State::Value) { + auto *entry = AllocateObject<IniKeyValueEntry>(); + entry->key = key_start; + entry->value = val_start; + + cur_sec->kv_list.push_back(*entry); + + return ParseIniResult_Success; + } else if (state == State::TrailingSpace || state == State::Comment || state == State::Newline) { + return ParseIniResult_Success; + } else { + return ParseIniResult_InvalidFormat; + } + + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_ini.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_ini.hpp new file mode 100644 index 00000000..706e0b7c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_ini.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> +#pragma once + +namespace ams::nxboot { + + struct IniKeyValueEntry { + util::IntrusiveListNode list_node; + char *key; + char *value; + }; + + using IniKeyValueList = typename util::IntrusiveListMemberTraits<&IniKeyValueEntry::list_node>::ListType; + + struct IniSection { + util::IntrusiveListNode list_node; + IniKeyValueList kv_list; + char *name; + }; + + using IniSectionList = typename util::IntrusiveListMemberTraits<&IniSection::list_node>::ListType; + + enum ParseIniResult { + ParseIniResult_Success, + ParseIniResult_NoFile, + ParseIniResult_InvalidFormat, + }; + + ParseIniResult ParseIniFile(IniSectionList &out_sections, const char *ini_path); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_key_derivation.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_key_derivation.cpp new file mode 100644 index 00000000..bca7c4e9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_key_derivation.cpp @@ -0,0 +1,342 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_key_derivation.hpp" +#include "fusee_fatal.hpp" + +namespace ams::nxboot { + + namespace { + + alignas(se::AesBlockSize) constexpr inline const u8 MarikoMasterKekSource[se::AesBlockSize] = { + /* TODO: Update on next change of keys. */ + 0x1A, 0x31, 0x62, 0x87, 0xA8, 0x09, 0xCA, 0xF8, 0x69, 0x15, 0x45, 0xC2, 0x6B, 0xAA, 0x5A, 0x8A + }; + + alignas(se::AesBlockSize) constexpr inline const u8 MarikoMasterKekSourceDev[se::AesBlockSize] = { + /* TODO: Update on next change of keys. */ + 0x8C, 0x2E, 0xC1, 0x1C, 0xA0, 0x28, 0x35, 0xFC, 0x9A, 0x9F, 0x1D, 0x9B, 0x4E, 0xDF, 0x1E, 0x03 + }; + + alignas(se::AesBlockSize) constexpr inline const u8 EristaMasterKekSource[se::AesBlockSize] = { + /* TODO: Update on next change of keys. */ + 0xA1, 0x7D, 0x34, 0xDB, 0x2D, 0x9D, 0xDA, 0xE5, 0xF8, 0x15, 0x63, 0x4C, 0x8F, 0xE7, 0x6C, 0xD8 + }; + + alignas(se::AesBlockSize) constexpr inline const u8 KeyblobKeySource[se::AesBlockSize] = { + 0xDF, 0x20, 0x6F, 0x59, 0x44, 0x54, 0xEF, 0xDC, 0x70, 0x74, 0x48, 0x3B, 0x0D, 0xED, 0x9F, 0xD3 + }; + + alignas(se::AesBlockSize) constexpr inline const u8 MasterKeySource[se::AesBlockSize] = { + 0xD8, 0xA2, 0x41, 0x0A, 0xC6, 0xC5, 0x90, 0x01, 0xC6, 0x1D, 0x6A, 0x26, 0x7C, 0x51, 0x3F, 0x3C + }; + + alignas(se::AesBlockSize) constexpr inline const u8 DeviceKeySource[se::AesBlockSize] = { + 0x4F, 0x02, 0x5F, 0x0E, 0xB6, 0x6D, 0x11, 0x0E, 0xDC, 0x32, 0x7D, 0x41, 0x86, 0xC2, 0xF4, 0x78 + }; + + alignas(se::AesBlockSize) constexpr inline const u8 DeviceMasterKeySourceKekSource[se::AesBlockSize] = { + 0x0C, 0x91, 0x09, 0xDB, 0x93, 0x93, 0x07, 0x81, 0x07, 0x3C, 0xC4, 0x16, 0x22, 0x7C, 0x6C, 0x28 + }; + + alignas(se::AesBlockSize) constexpr inline const u8 DeviceMasterKekSource[se::AesBlockSize] = { + 0x2D, 0xC1, 0xF4, 0x8D, 0xF3, 0x5B, 0x69, 0x33, 0x42, 0x10, 0xAC, 0x65, 0xDA, 0x90, 0x46, 0x66 + }; + + alignas(se::AesBlockSize) constexpr inline const u8 DeviceMasterKeySourceSources[pkg1::OldDeviceMasterKeyCount][se::AesBlockSize] = { + { 0x8B, 0x4E, 0x1C, 0x22, 0x42, 0x07, 0xC8, 0x73, 0x56, 0x94, 0x08, 0x8B, 0xCC, 0x47, 0x0F, 0x5D }, /* 4.x Device Master Key Source Source. */ + { 0x6C, 0xEF, 0xC6, 0x27, 0x8B, 0xEC, 0x8A, 0x91, 0x99, 0xAB, 0x24, 0xAC, 0x4F, 0x1C, 0x8F, 0x1C }, /* 5.x Device Master Key Source Source. */ + { 0x70, 0x08, 0x1B, 0x97, 0x44, 0x64, 0xF8, 0x91, 0x54, 0x9D, 0xC6, 0x84, 0x8F, 0x1A, 0xB2, 0xE4 }, /* 6.x Device Master Key Source Source. */ + { 0x8E, 0x09, 0x1F, 0x7A, 0xBB, 0xCA, 0x6A, 0xFB, 0xB8, 0x9B, 0xD5, 0xC1, 0x25, 0x9C, 0xA9, 0x17 }, /* 6.2.0 Device Master Key Source Source. */ + { 0x8F, 0x77, 0x5A, 0x96, 0xB0, 0x94, 0xFD, 0x8D, 0x28, 0xE4, 0x19, 0xC8, 0x16, 0x1C, 0xDB, 0x3D }, /* 7.0.0 Device Master Key Source Source. */ + { 0x67, 0x62, 0xD4, 0x8E, 0x55, 0xCF, 0xFF, 0x41, 0x31, 0x15, 0x3B, 0x24, 0x0C, 0x7C, 0x07, 0xAE }, /* 8.1.0 Device Master Key Source Source. */ + { 0x4A, 0xC3, 0x4E, 0x14, 0x8B, 0x96, 0x4A, 0xD5, 0xD4, 0x99, 0x73, 0xC4, 0x45, 0xAB, 0x8B, 0x49 }, /* 9.0.0 Device Master Key Source Source. */ + { 0x14, 0xB8, 0x74, 0x12, 0xCB, 0xBD, 0x0B, 0x8F, 0x20, 0xFB, 0x30, 0xDA, 0x27, 0xE4, 0x58, 0x94 }, /* 9.1.0 Device Master Key Source Source. */ + { 0xAA, 0xFD, 0xBC, 0xBB, 0x25, 0xC3, 0xA4, 0xEF, 0xE3, 0xEE, 0x58, 0x53, 0xB7, 0xF8, 0xDD, 0xD6 }, /* 12.1.0 Device Master Key Source Source. */ + { 0xE4, 0xF3, 0x45, 0x6F, 0x18, 0xA1, 0x89, 0xF8, 0xDA, 0x4C, 0x64, 0x75, 0x68, 0xE6, 0xBD, 0x4F }, /* 13.0.0 Device Master Key Source Source. */ + { 0x5B, 0x94, 0x63, 0xF7, 0xAD, 0x96, 0x1B, 0xA6, 0x23, 0x30, 0x06, 0x4D, 0x01, 0xE4, 0xCE, 0x1D }, /* 14.0.0 Device Master Key Source Source. */ + { 0x5E, 0xC9, 0xC5, 0x0A, 0xD0, 0x5F, 0x8B, 0x7B, 0xA7, 0x39, 0xEA, 0xBC, 0x60, 0x0F, 0x74, 0xE6 }, /* 15.0.0 Device Master Key Source Source. */ + { 0xEA, 0x90, 0x6E, 0xA8, 0xAE, 0x92, 0x99, 0x64, 0x36, 0xC1, 0xF3, 0x1C, 0xC6, 0x32, 0x83, 0x8C }, /* 16.0.0 Device Master Key Source Source. */ + { 0xDA, 0xB9, 0xD6, 0x77, 0x52, 0x2D, 0x1F, 0x78, 0x73, 0xC9, 0x98, 0x5B, 0x06, 0xFE, 0xA0, 0x52 }, /* 17.0.0 Device Master Key Source Source. */ + { 0x14, 0xF5, 0xA5, 0xD0, 0x73, 0x6D, 0x44, 0x80, 0x5F, 0x31, 0x5A, 0x8F, 0x1E, 0xD4, 0x0D, 0x63 }, /* 18.0.0 Device Master Key Source Source. */ + { 0x07, 0x38, 0x9A, 0xEC, 0x9C, 0xBD, 0x50, 0x4A, 0x4C, 0x1F, 0x04, 0xDA, 0x40, 0x68, 0x29, 0xE3 }, /* 19.0.0 Device Master Key Source Source. */ + { 0xA3, 0x6B, 0x0A, 0xB5, 0x6F, 0x57, 0x4C, 0x5E, 0x00, 0xFD, 0x56, 0x21, 0xF5, 0x06, 0x6B, 0xD1 }, /* 20.0.0 Device Master Key Source Source. */ + }; + + alignas(se::AesBlockSize) constexpr inline const u8 DeviceMasterKekSources[pkg1::OldDeviceMasterKeyCount][se::AesBlockSize] = { + { 0x88, 0x62, 0x34, 0x6E, 0xFA, 0xF7, 0xD8, 0x3F, 0xE1, 0x30, 0x39, 0x50, 0xF0, 0xB7, 0x5D, 0x5D }, /* 4.x Device Master Kek Source. */ + { 0x06, 0x1E, 0x7B, 0xE9, 0x6D, 0x47, 0x8C, 0x77, 0xC5, 0xC8, 0xE7, 0x94, 0x9A, 0xA8, 0x5F, 0x2E }, /* 5.x Device Master Kek Source. */ + { 0x99, 0xFA, 0x98, 0xBD, 0x15, 0x1C, 0x72, 0xFD, 0x7D, 0x9A, 0xD5, 0x41, 0x00, 0xFD, 0xB2, 0xEF }, /* 6.x Device Master Kek Source. */ + { 0x81, 0x3C, 0x6C, 0xBF, 0x5D, 0x21, 0xDE, 0x77, 0x20, 0xD9, 0x6C, 0xE3, 0x22, 0x06, 0xAE, 0xBB }, /* 6.2.0 Device Master Kek Source. */ + { 0x86, 0x61, 0xB0, 0x16, 0xFA, 0x7A, 0x9A, 0xEA, 0xF6, 0xF5, 0xBE, 0x1A, 0x13, 0x5B, 0x6D, 0x9E }, /* 7.0.0 Device Master Kek Source. */ + { 0xA6, 0x81, 0x71, 0xE7, 0xB5, 0x23, 0x74, 0xB0, 0x39, 0x8C, 0xB7, 0xFF, 0xA0, 0x62, 0x9F, 0x8D }, /* 8.1.0 Device Master Kek Source. */ + { 0x03, 0xE7, 0xEB, 0x43, 0x1B, 0xCF, 0x5F, 0xB5, 0xED, 0xDC, 0x97, 0xAE, 0x21, 0x8D, 0x19, 0xED }, /* 9.0.0 Device Master Kek Source. */ + { 0xCE, 0xFE, 0x41, 0x0F, 0x46, 0x9A, 0x30, 0xD6, 0xF2, 0xE9, 0x0C, 0x6B, 0xB7, 0x15, 0x91, 0x36 }, /* 9.1.0 Device Master Kek Source. */ + { 0xC2, 0x65, 0x34, 0x6E, 0xC7, 0xC6, 0x5D, 0x97, 0x3E, 0x34, 0x5C, 0x6B, 0xB3, 0x7E, 0xC6, 0xE3 }, /* 12.1.0 Device Master Kek Source. */ + { 0x77, 0x52, 0x92, 0xF0, 0xAA, 0xE3, 0xFB, 0xE0, 0x60, 0x16, 0xB3, 0x78, 0x68, 0x53, 0xF7, 0xA8 }, /* 13.0.0 Device Master Kek Source. */ + { 0x67, 0xD5, 0xD6, 0x0C, 0x08, 0xF5, 0xA3, 0x11, 0xBD, 0x6D, 0x5A, 0xEB, 0x96, 0x24, 0xB0, 0xD2 }, /* 14.0.0 Device Master Kek Source. */ + { 0x7C, 0x30, 0xED, 0x8B, 0x39, 0x25, 0x2C, 0x08, 0x8F, 0x48, 0xDC, 0x28, 0xE6, 0x1A, 0x6B, 0x49 }, /* 15.0.0 Device Master Kek Source. */ + { 0xF0, 0xF3, 0xFF, 0x52, 0x75, 0x2F, 0xBA, 0x4D, 0x09, 0x72, 0x30, 0x89, 0xA9, 0xDF, 0xFE, 0x1F }, /* 16.0.0 Device Master Kek Source. */ + { 0x21, 0xD6, 0x35, 0xF1, 0x0F, 0x7A, 0xF0, 0x5D, 0xDF, 0x79, 0x1C, 0x7A, 0xE4, 0x32, 0x82, 0x9E }, /* 17.0.0 Device Master Kek Source. */ + { 0xE7, 0x85, 0x8C, 0xA2, 0xF4, 0x49, 0xCB, 0x07, 0xD1, 0x8E, 0x48, 0x1B, 0xE8, 0x1E, 0x28, 0x3B }, /* 18.0.0 Device Master Kek Source. */ + { 0x9B, 0xA5, 0xFD, 0x74, 0x7F, 0xCD, 0x23, 0xD1, 0xD9, 0xBD, 0x6C, 0x51, 0x72, 0x5F, 0x3D, 0x1F }, /* 19.0.0 Device Master Kek Source. */ + { 0xDA, 0xFB, 0x61, 0x39, 0x48, 0x2D, 0xC2, 0x7E, 0x0D, 0x8E, 0x8F, 0x98, 0x57, 0x20, 0xB8, 0x15 }, /* 20.0.0 Device Master Kek Source. */ + }; + + alignas(se::AesBlockSize) constexpr inline const u8 DeviceMasterKekSourcesDev[pkg1::OldDeviceMasterKeyCount][se::AesBlockSize] = { + { 0xD6, 0xBD, 0x9F, 0xC6, 0x18, 0x09, 0xE1, 0x96, 0x20, 0x39, 0x60, 0xD2, 0x89, 0x83, 0x31, 0x34 }, /* 4.x Device Master Kek Source. */ + { 0x59, 0x2D, 0x20, 0x69, 0x33, 0xB5, 0x17, 0xBA, 0xCF, 0xB1, 0x4E, 0xFD, 0xE4, 0xC2, 0x7B, 0xA8 }, /* 5.x Device Master Kek Source. */ + { 0xF6, 0xD8, 0x59, 0x63, 0x8F, 0x47, 0xCB, 0x4A, 0xD8, 0x74, 0x05, 0x7F, 0x88, 0x92, 0x33, 0xA5 }, /* 6.x Device Master Kek Source. */ + { 0x20, 0xAB, 0xF2, 0x0F, 0x05, 0xE3, 0xDE, 0x2E, 0xA1, 0xFB, 0x37, 0x5E, 0x8B, 0x22, 0x1A, 0x38 }, /* 6.2.0 Device Master Kek Source. */ + { 0x60, 0xAE, 0x56, 0x68, 0x11, 0xE2, 0x0C, 0x99, 0xDE, 0x05, 0xAE, 0x68, 0x78, 0x85, 0x04, 0xAE }, /* 7.0.0 Device Master Kek Source. */ + { 0x94, 0xD6, 0xA8, 0xC0, 0x95, 0xAF, 0xD0, 0xA6, 0x27, 0x53, 0x5E, 0xE5, 0x8E, 0x70, 0x1F, 0x87 }, /* 8.1.0 Device Master Kek Source. */ + { 0x61, 0x6A, 0x88, 0x21, 0xA3, 0x52, 0xB0, 0x19, 0x16, 0x25, 0xA4, 0xE3, 0x4C, 0x54, 0x02, 0x0F }, /* 9.0.0 Device Master Kek Source. */ + { 0x9D, 0xB1, 0xAE, 0xCB, 0xF6, 0xF6, 0xE3, 0xFE, 0xAB, 0x6F, 0xCB, 0xAF, 0x38, 0x03, 0xFC, 0x7B }, /* 9.1.0 Device Master Kek Source. */ + { 0xC4, 0xBB, 0xF3, 0x9F, 0xA3, 0xAA, 0x00, 0x99, 0x7C, 0x97, 0xAD, 0x91, 0x8F, 0xE8, 0x45, 0xCB }, /* 12.1.0 Device Master Kek Source. */ + { 0x20, 0x20, 0xAA, 0xFB, 0x89, 0xC2, 0xF0, 0x70, 0xB5, 0xE0, 0xA3, 0x11, 0x8A, 0x29, 0x8D, 0x0F }, /* 13.0.0 Device Master Kek Source. */ + { 0xCE, 0x14, 0x74, 0x66, 0x98, 0xA8, 0x6D, 0x7D, 0xBD, 0x54, 0x91, 0x68, 0x5F, 0x1D, 0x0E, 0xEA }, /* 14.0.0 Device Master Kek Source. */ + { 0xAE, 0x05, 0x48, 0x65, 0xAB, 0x17, 0x9D, 0x3D, 0x51, 0xB7, 0x56, 0xBD, 0x9B, 0x0B, 0x5B, 0x6E }, /* 15.0.0 Device Master Kek Source. */ + { 0xFF, 0xF6, 0x4B, 0x0F, 0xFF, 0x0D, 0xC0, 0x4F, 0x56, 0x8A, 0x40, 0x74, 0x67, 0xC5, 0xFE, 0x9F }, /* 16.0.0 Device Master Kek Source. */ + { 0x4E, 0xCE, 0x7B, 0x2A, 0xEA, 0x2E, 0x3D, 0x16, 0xD5, 0x2A, 0xDE, 0xF6, 0xF8, 0x6A, 0x7D, 0x43 }, /* 17.0.0 Device Master Kek Source. */ + { 0x3B, 0x00, 0x89, 0xD7, 0xA9, 0x9E, 0xB7, 0x70, 0x86, 0x00, 0xC3, 0x49, 0x52, 0x8C, 0xA4, 0xAF }, /* 18.0.0 Device Master Kek Source. */ + { 0xAE, 0x78, 0x36, 0xB6, 0x91, 0xEB, 0xAF, 0x9C, 0x18, 0xF1, 0xC0, 0xD5, 0x8A, 0x0C, 0x7C, 0xA1 }, /* 19.0.0 Device Master Kek Source. */ + { 0x09, 0x12, 0x4F, 0x26, 0x90, 0xB9, 0xA6, 0xF5, 0xA5, 0x18, 0x74, 0xB6, 0x8D, 0x80, 0x59, 0x3D }, /* 20.0.0 Device Master Kek Source. */ + }; + + alignas(se::AesBlockSize) constexpr inline const u8 MasterKeySources[pkg1::KeyGeneration_Count][se::AesBlockSize] = { + { 0x0C, 0xF0, 0x59, 0xAC, 0x85, 0xF6, 0x26, 0x65, 0xE1, 0xE9, 0x19, 0x55, 0xE6, 0xF2, 0x67, 0x3D }, /* Zeroes encrypted with Master Key 00. */ + { 0x29, 0x4C, 0x04, 0xC8, 0xEB, 0x10, 0xED, 0x9D, 0x51, 0x64, 0x97, 0xFB, 0xF3, 0x4D, 0x50, 0xDD }, /* Master key 00 encrypted with Master key 01. */ + { 0xDE, 0xCF, 0xEB, 0xEB, 0x10, 0xAE, 0x74, 0xD8, 0xAD, 0x7C, 0xF4, 0x9E, 0x62, 0xE0, 0xE8, 0x72 }, /* Master key 01 encrypted with Master key 02. */ + { 0x0A, 0x0D, 0xDF, 0x34, 0x22, 0x06, 0x6C, 0xA4, 0xE6, 0xB1, 0xEC, 0x71, 0x85, 0xCA, 0x4E, 0x07 }, /* Master key 02 encrypted with Master key 03. */ + { 0x6E, 0x7D, 0x2D, 0xC3, 0x0F, 0x59, 0xC8, 0xFA, 0x87, 0xA8, 0x2E, 0xD5, 0x89, 0x5E, 0xF3, 0xE9 }, /* Master key 03 encrypted with Master key 04. */ + { 0xEB, 0xF5, 0x6F, 0x83, 0x61, 0x9E, 0xF8, 0xFA, 0xE0, 0x87, 0xD7, 0xA1, 0x4E, 0x25, 0x36, 0xEE }, /* Master key 04 encrypted with Master key 05. */ + { 0x1E, 0x1E, 0x22, 0xC0, 0x5A, 0x33, 0x3C, 0xB9, 0x0B, 0xA9, 0x03, 0x04, 0xBA, 0xDB, 0x07, 0x57 }, /* Master key 05 encrypted with Master key 06. */ + { 0xA4, 0xD4, 0x52, 0x6F, 0xD1, 0xE4, 0x36, 0xAA, 0x9F, 0xCB, 0x61, 0x27, 0x1C, 0x67, 0x65, 0x1F }, /* Master key 06 encrypted with Master key 07. */ + { 0xEA, 0x60, 0xB3, 0xEA, 0xCE, 0x8F, 0x24, 0x46, 0x7D, 0x33, 0x9C, 0xD1, 0xBC, 0x24, 0x98, 0x29 }, /* Master key 07 encrypted with Master key 08. */ + { 0x4D, 0xD9, 0x98, 0x42, 0x45, 0x0D, 0xB1, 0x3C, 0x52, 0x0C, 0x9A, 0x44, 0xBB, 0xAD, 0xAF, 0x80 }, /* Master key 08 encrypted with Master key 09. */ + { 0xB8, 0x96, 0x9E, 0x4A, 0x00, 0x0D, 0xD6, 0x28, 0xB3, 0xD1, 0xDB, 0x68, 0x5F, 0xFB, 0xE1, 0x2A }, /* Master key 09 encrypted with Master key 0A. */ + { 0xC1, 0x8D, 0x16, 0xBB, 0x2A, 0xE4, 0x1D, 0xD4, 0xC2, 0xC1, 0xB6, 0x40, 0x94, 0x35, 0x63, 0x98 }, /* Master key 0A encrypted with Master key 0B. */ + { 0xA3, 0x24, 0x65, 0x75, 0xEA, 0xCC, 0x6E, 0x8D, 0xFB, 0x5A, 0x16, 0x50, 0x74, 0xD2, 0x15, 0x06 }, /* Master key 0B encrypted with Master key 0C. */ + { 0x83, 0x67, 0xAF, 0x01, 0xCF, 0x93, 0xA1, 0xAB, 0x80, 0x45, 0xF7, 0x3F, 0x72, 0xFD, 0x3B, 0x38 }, /* Master key 0C encrypted with Master key 0D. */ + { 0xB1, 0x81, 0xA6, 0x0D, 0x72, 0xC7, 0xEE, 0x15, 0x21, 0xF3, 0xC0, 0xB5, 0x6B, 0x61, 0x6D, 0xE7 }, /* Master key 0D encrypted with Master key 0E. */ + { 0xAF, 0x11, 0x4C, 0x67, 0x17, 0x7A, 0x52, 0x43, 0xF7, 0x70, 0x2F, 0xC7, 0xEF, 0x81, 0x72, 0x16 }, /* Master key 0E encrypted with Master key 0F. */ + { 0x25, 0x12, 0x8B, 0xCB, 0xB5, 0x46, 0xA1, 0xF8, 0xE0, 0x52, 0x15, 0xB7, 0x0B, 0x57, 0x00, 0xBD }, /* Master key 0F encrypted with Master key 10. */ + { 0x58, 0x15, 0xD2, 0xF6, 0x8A, 0xE8, 0x19, 0xAB, 0xFB, 0x2D, 0x52, 0x9D, 0xE7, 0x55, 0xF3, 0x93 }, /* Master key 10 encrypted with Master key 11. */ + { 0x4A, 0x01, 0x3B, 0xC7, 0x44, 0x6E, 0x45, 0xBD, 0xE6, 0x5E, 0x2B, 0xEC, 0x07, 0x37, 0x52, 0x86 }, /* Master key 11 encrypted with Master key 12. */ + { 0x97, 0xE4, 0x11, 0xAB, 0x22, 0x72, 0x1A, 0x1F, 0x70, 0x5C, 0x00, 0xB3, 0x96, 0x30, 0x05, 0x28 }, /* Master key 12 encrypted with Master key 13. */ + }; + + alignas(se::AesBlockSize) constexpr inline const u8 MasterKeySourcesDev[pkg1::KeyGeneration_Count][se::AesBlockSize] = { + { 0x46, 0x22, 0xB4, 0x51, 0x9A, 0x7E, 0xA7, 0x7F, 0x62, 0xA1, 0x1F, 0x8F, 0xC5, 0x3A, 0xDB, 0xFE }, /* Zeroes encrypted with Master Key 00. */ + { 0x39, 0x33, 0xF9, 0x31, 0xBA, 0xE4, 0xA7, 0x21, 0x2C, 0xDD, 0xB7, 0xD8, 0xB4, 0x4E, 0x37, 0x23 }, /* Master key 00 encrypted with Master key 01. */ + { 0x97, 0x29, 0xB0, 0x32, 0x43, 0x14, 0x8C, 0xA6, 0x85, 0xE9, 0x5A, 0x94, 0x99, 0x39, 0xAC, 0x5D }, /* Master key 01 encrypted with Master key 02. */ + { 0x2C, 0xCA, 0x9C, 0x31, 0x1E, 0x07, 0xB0, 0x02, 0x97, 0x0A, 0xD8, 0x03, 0xA2, 0x76, 0x3F, 0xA3 }, /* Master key 02 encrypted with Master key 03. */ + { 0x9B, 0x84, 0x76, 0x14, 0x72, 0x94, 0x52, 0xCB, 0x54, 0x92, 0x9B, 0xC4, 0x8C, 0x5B, 0x0F, 0xBA }, /* Master key 03 encrypted with Master key 04. */ + { 0x78, 0xD5, 0xF1, 0x20, 0x3D, 0x16, 0xE9, 0x30, 0x32, 0x27, 0x34, 0x6F, 0xCF, 0xE0, 0x27, 0xDC }, /* Master key 04 encrypted with Master key 05. */ + { 0x6F, 0xD2, 0x84, 0x1D, 0x05, 0xEC, 0x40, 0x94, 0x5F, 0x18, 0xB3, 0x81, 0x09, 0x98, 0x8D, 0x4E }, /* Master key 05 encrypted with Master key 06. */ + { 0x37, 0xAF, 0xAB, 0x35, 0x79, 0x09, 0xD9, 0x48, 0x29, 0xD2, 0xDB, 0xA5, 0xA5, 0xF5, 0x30, 0x19 }, /* Master key 06 encrypted with Master key 07. */ + { 0xEC, 0xE1, 0x46, 0x89, 0x37, 0xFD, 0xD2, 0x15, 0x8C, 0x3F, 0x24, 0x82, 0xEF, 0x49, 0x68, 0x04 }, /* Master key 07 encrypted with Master key 08. */ + { 0x43, 0x3D, 0xC5, 0x3B, 0xEF, 0x91, 0x02, 0x21, 0x61, 0x54, 0x63, 0x8A, 0x35, 0xE7, 0xCA, 0xEE }, /* Master key 08 encrypted with Master key 09. */ + { 0x6C, 0x2E, 0xCD, 0xB3, 0x34, 0x61, 0x77, 0xF5, 0xF9, 0xB1, 0xDD, 0x61, 0x98, 0x19, 0x3E, 0xD4 }, /* Master key 09 encrypted with Master key 0A. */ + { 0x21, 0x88, 0x6B, 0x10, 0x9E, 0x83, 0xD6, 0x52, 0xAB, 0x08, 0xDB, 0x6D, 0x39, 0xFF, 0x1C, 0x9C }, /* Master key 0A encrypted with Master key 0B. */ + { 0x8A, 0xCE, 0xC4, 0x7F, 0xBE, 0x08, 0x61, 0x88, 0xD3, 0x73, 0x64, 0x51, 0xE2, 0xB6, 0x53, 0x15 }, /* Master key 0B encrypted with Master key 0C. */ + { 0x08, 0xE0, 0xF4, 0xBE, 0xAA, 0x6E, 0x5A, 0xC3, 0xA6, 0xBC, 0xFE, 0xB9, 0xE2, 0xA3, 0x24, 0x12 }, /* Master key 0C encrypted with Master key 0D. */ + { 0xD6, 0x80, 0x98, 0xC0, 0xFA, 0xC7, 0x13, 0xCB, 0x93, 0xD2, 0x0B, 0x82, 0x4C, 0xA1, 0x7B, 0x8D }, /* Master key 0D encrypted with Master key 0E. */ + { 0x78, 0x66, 0x19, 0xBD, 0x86, 0xE7, 0xC1, 0x09, 0x9B, 0x6F, 0x92, 0xB2, 0x58, 0x7D, 0xCF, 0x26 }, /* Master key 0E encrypted with Master key 0F. */ + { 0x39, 0x1E, 0x7E, 0xF8, 0x7E, 0x73, 0xEA, 0x6F, 0xAF, 0x00, 0x3A, 0xB4, 0xAA, 0xB8, 0xB7, 0x59 }, /* Master key 0F encrypted with Master key 10. */ + { 0x0C, 0x75, 0x39, 0x15, 0x53, 0xEA, 0x81, 0x11, 0xA3, 0xE0, 0xDC, 0x3D, 0x0E, 0x76, 0xC6, 0xB8 }, /* Master key 10 encrypted with Master key 11. */ + { 0x90, 0x64, 0xF9, 0x08, 0x29, 0x88, 0xD4, 0xDC, 0x73, 0xA4, 0xA1, 0x13, 0x9E, 0x59, 0x85, 0xA0 }, /* Master key 11 encrypted with Master key 12. */ + { 0x94, 0x46, 0x3B, 0xFA, 0x7D, 0xB9, 0xE2, 0x94, 0xC2, 0x9D, 0xB9, 0xA4, 0xB2, 0x56, 0xCA, 0xFE }, /* Master key 12 encrypted with Master key 13. */ + }; + + alignas(se::AesBlockSize) constinit u8 MasterKeys[pkg1::OldMasterKeyCount][se::AesBlockSize] = {}; + alignas(se::AesBlockSize) constinit u8 DeviceMasterKeys[pkg1::OldDeviceMasterKeyCount][se::AesBlockSize] = {}; + + void DeriveMasterKeys(bool is_prod) { + /* Decrypt the vector chain from current generation to start. */ + int slot = pkg1::AesKeySlot_BootloaderMaster; + for (int i = pkg1::KeyGeneration_Current; i > pkg1::KeyGeneration_1_0_0; --i) { + /* Decrypt the old master key. */ + se::DecryptAes128(MasterKeys[i - 1], se::AesBlockSize, slot, is_prod ? MasterKeySources[i] : MasterKeySourcesDev[i], se::AesBlockSize); + + /* Set the old master key into a temporary keyslot. */ + se::SetAesKey(pkg1::AesKeySlot_BootloaderTemporary, MasterKeys[i - 1], se::AesBlockSize); + + /* Perform the next decryption with the older master key. */ + slot = pkg1::AesKeySlot_BootloaderTemporary; + } + + /* Decrypt the final vector. */ + alignas(se::AesBlockSize) u8 test_vector[se::AesBlockSize]; + se::DecryptAes128(test_vector, se::AesBlockSize, pkg1::AesKeySlot_BootloaderTemporary, is_prod ? MasterKeySources[pkg1::KeyGeneration_1_0_0] : MasterKeySourcesDev[pkg1::KeyGeneration_1_0_0], se::AesBlockSize); + + /* Verify the vector chain. */ + alignas(se::AesBlockSize) constexpr u8 ZeroBlock[se::AesBlockSize] = {}; + if (!crypto::IsSameBytes(ZeroBlock, test_vector, se::AesBlockSize)) { + ShowFatalError("Failed to derive master keys!\n"); + } + } + + void DeriveDeviceMasterKeys(fuse::SocType soc_type, bool is_prod) { + alignas(se::AesBlockSize) u8 work_block[se::AesBlockSize]; + + /* Iterate for all generations. */ + for (int i = 0; i < pkg1::OldDeviceMasterKeyCount; ++i) { + const int generation = pkg1::KeyGeneration_4_0_0 + i; + + /* Load the first master key into the temporary keyslot keyslot. */ + se::SetAesKey(pkg1::AesKeySlot_BootloaderTemporary, MasterKeys[pkg1::KeyGeneration_1_0_0], se::AesBlockSize); + + /* Decrypt the device master kek for the generation. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_BootloaderTemporary, pkg1::AesKeySlot_BootloaderTemporary, is_prod ? DeviceMasterKekSources[i] : DeviceMasterKekSourcesDev[i], se::AesBlockSize); + + /* Decrypt the device master key source into the work block. */ + se::DecryptAes128(work_block, se::AesBlockSize, pkg1::AesKeySlot_DeviceMasterKeySourceKekErista, DeviceMasterKeySourceSources[i], se::AesBlockSize); + + if (generation == pkg1::KeyGeneration_Current) { + se::SetEncryptedAesKey128(pkg1::AesKeySlot_BootloaderDeviceMaster, pkg1::AesKeySlot_BootloaderTemporary, work_block, se::AesBlockSize); + + /* If on erista, derive the current device master key into the DeviceMaster key slot. */ + if (soc_type == fuse::SocType_Erista) { + se::SetEncryptedAesKey128(pkg1::AesKeySlot_DeviceMaster, pkg1::AesKeySlot_BootloaderTemporary, work_block, se::AesBlockSize); + } + } else { + /* Decrypt the device master key. */ + se::DecryptAes128(DeviceMasterKeys[i], se::AesBlockSize, pkg1::AesKeySlot_BootloaderTemporary, work_block, se::AesBlockSize); + + /* If on mariko, ensure that we derive device key 0 here. */ + if (soc_type == fuse::SocType_Mariko && i == 0) { + se::SetAesKey(pkg1::AesKeySlot_Device, DeviceMasterKeys[i], se::AesBlockSize); + } + } + } + } + + alignas(se::AesBlockSize) constexpr inline const u8 GeneratePersonalizedAesKeyKekKekSource[se::AesBlockSize] = { + 0x4D, 0x87, 0x09, 0x86, 0xC4, 0x5D, 0x20, 0x72, 0x2F, 0xBA, 0x10, 0x53, 0xDA, 0x92, 0xE8, 0xA9 + }; + + alignas(se::AesBlockSize) constexpr inline const u8 GeneratePersonalizedAesKeyKeyKekSource[se::AesBlockSize] = { + 0x89, 0x61, 0x5E, 0xE0, 0x5C, 0x31, 0xB6, 0x80, 0x5F, 0xE5, 0x8F, 0x3D, 0xA2, 0x4F, 0x7A, 0xA8 + }; + + void GeneratePersonalizedAesKeyForBis(int slot, const void *kek_source, const void *key_source, int generation) { + /* Derive kek. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_BootloaderTemporary, PrepareDeviceMasterKey(generation), GeneratePersonalizedAesKeyKekKekSource, se::AesBlockSize); + se::SetEncryptedAesKey128(pkg1::AesKeySlot_BootloaderTemporary, pkg1::AesKeySlot_BootloaderTemporary, kek_source, se::AesBlockSize); + + /* Derive key. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_BootloaderTemporary, pkg1::AesKeySlot_BootloaderTemporary, GeneratePersonalizedAesKeyKeyKekSource, se::AesBlockSize); + se::SetEncryptedAesKey128(slot, pkg1::AesKeySlot_BootloaderTemporary, key_source, se::AesBlockSize); + } + + alignas(se::AesBlockSize) constexpr inline const u8 BisKekSource[se::AesBlockSize] = { + 0x34, 0xC1, 0xA0, 0xC4, 0x82, 0x58, 0xF8, 0xB4, 0xFA, 0x9E, 0x5E, 0x6A, 0xDA, 0xFC, 0x7E, 0x4F + }; + + alignas(se::AesBlockSize) constexpr inline const u8 BisPartitionSystemKeySources[2][se::AesBlockSize] = { + { 0x52, 0xC2, 0xE9, 0xEB, 0x09, 0xE3, 0xEE, 0x29, 0x32, 0xA1, 0x0C, 0x1F, 0xB6, 0xA0, 0x92, 0x6C }, + { 0x4D, 0x12, 0xE1, 0x4B, 0x2A, 0x47, 0x4C, 0x1C, 0x09, 0xCB, 0x03, 0x59, 0xF0, 0x15, 0xF4, 0xE4 }, + }; + + void DeriveBisPartitionSystemKeys() { + /* Determine key generation. */ + const int key_generation = std::max<int>(0, static_cast<int>(fuse::GetDeviceUniqueKeyGeneration()) - 1); + + /* Generate desired keys. */ + GeneratePersonalizedAesKeyForBis(pkg1::AesKeySlot_BootloaderSystem0, BisKekSource, BisPartitionSystemKeySources[0], key_generation); + GeneratePersonalizedAesKeyForBis(pkg1::AesKeySlot_BootloaderSystem1, BisKekSource, BisPartitionSystemKeySources[1], key_generation); + } + + } + + void DeriveKeysErista() { + /* Get work buffer. */ + alignas(se::AesBlockSize) u8 work_buffer[se::AesBlockSize]; + + /* Get whether we're using dev keys. */ + const bool is_prod = fuse::GetHardwareState() == fuse::HardwareState_Production; + + /* Derive Keyblob Key. */ + se::DecryptAes128(work_buffer, se::AesBlockSize, pkg1::AesKeySlot_Tsec, KeyblobKeySource, se::AesBlockSize); + se::SetEncryptedAesKey128(pkg1::AesKeySlot_Device, pkg1::AesKeySlot_SecureBoot, work_buffer, se::AesBlockSize); + + /* Derive Master Kek. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_MasterKek, is_prod ? pkg1::AesKeySlot_TsecRoot : pkg1::AesKeySlot_TsecRootDev, EristaMasterKekSource, se::AesBlockSize); + + /* Derive Master Key. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_BootloaderMaster, pkg1::AesKeySlot_MasterKek, MasterKeySource, se::AesBlockSize); + se::SetEncryptedAesKey128(pkg1::AesKeySlot_Master, pkg1::AesKeySlot_MasterKek, MasterKeySource, se::AesBlockSize); + + /* Derive Device Master Key Source Kek, Device Key. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_DeviceMasterKeySourceKekErista, pkg1::AesKeySlot_Device, DeviceMasterKeySourceKekSource, se::AesBlockSize); + se::SetEncryptedAesKey128(pkg1::AesKeySlot_Device, pkg1::AesKeySlot_Device, DeviceKeySource, se::AesBlockSize); + + /* Derive all master keys. */ + DeriveMasterKeys(is_prod); + + /* Derive all device master keys. */ + DeriveDeviceMasterKeys(fuse::SocType_Erista, is_prod); + + /* Derive system partition keys. */ + DeriveBisPartitionSystemKeys(); + } + + void DeriveKeysMariko() { + /* Get whether we're using dev keys. */ + const bool is_prod = fuse::GetHardwareState() == fuse::HardwareState_Production; + + /* Derive Device Master Key Source Kek, Master Key. */ + se::SetEncryptedAesKey128(pkg1::AesKeySlot_DeviceMasterKeySourceKekErista, pkg1::AesKeySlot_SecureBoot, DeviceMasterKeySourceKekSource, se::AesBlockSize); + se::SetEncryptedAesKey128(pkg1::AesKeySlot_BootloaderMaster, pkg1::AesKeySlot_MarikoKek, is_prod ? MarikoMasterKekSource : MarikoMasterKekSourceDev, se::AesBlockSize); + se::SetEncryptedAesKey128(pkg1::AesKeySlot_BootloaderMaster, pkg1::AesKeySlot_BootloaderMaster, MasterKeySource, se::AesBlockSize); + + /* Derive all master keys. */ + DeriveMasterKeys(is_prod); + + /* Derive all device master keys. */ + DeriveDeviceMasterKeys(fuse::SocType_Mariko, is_prod); + + /* Derive system partition keys. */ + DeriveBisPartitionSystemKeys(); + } + + int PrepareMasterKey(int generation) { + if (generation == pkg1::KeyGeneration_Current) { + return pkg1::AesKeySlot_BootloaderMaster; + } + + se::SetAesKey(pkg1::AesKeySlot_BootloaderTemporary, MasterKeys[generation], se::AesBlockSize); + + return pkg1::AesKeySlot_BootloaderTemporary; + } + + int PrepareDeviceMasterKey(int generation) { + if (generation == pkg1::KeyGeneration_1_0_0) { + return pkg1::AesKeySlot_Device; + } + + if (generation == pkg1::KeyGeneration_Current) { + return pkg1::AesKeySlot_BootloaderDeviceMaster; + } + + const int index = std::max(0, generation - pkg1::KeyGeneration_4_0_0); + se::SetAesKey(pkg1::AesKeySlot_BootloaderTemporary, DeviceMasterKeys[index], se::AesBlockSize); + + return pkg1::AesKeySlot_BootloaderTemporary; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_key_derivation.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_key_derivation.hpp new file mode 100644 index 00000000..c71dc89c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_key_derivation.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> +#pragma once + +namespace ams::nxboot { + + void DeriveKeysErista(); + void DeriveKeysMariko(); + + int PrepareMasterKey(int generation); + int PrepareDeviceMasterKey(int generation); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_main.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_main.cpp new file mode 100644 index 00000000..9409d174 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_main.cpp @@ -0,0 +1,156 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_display.hpp" +#include "sein/fusee_secure_initialize.hpp" +#include "sdram/fusee_sdram.hpp" +#include "mtc/fusee_mtc.hpp" +#include "fs/fusee_fs_api.hpp" +#include "fusee_overlay_manager.hpp" +#include "fusee_sd_card.hpp" +#include "fusee_fatal.hpp" +#include "fusee_external_package.hpp" +#include "fusee_setup_horizon.hpp" +#include "fusee_secmon_sync.hpp" + +namespace ams::nxboot { + + namespace { + + constexpr const char ExternalPackageFilePath[] = "sdmc:/atmosphere/package3"; + + constinit fs::FileHandle g_package_file; + + void OpenExternalPackage() { + Result result; + + /* Open external package. */ + if (R_FAILED((result = fs::OpenFile(std::addressof(g_package_file), ExternalPackageFilePath, fs::OpenMode_Read)))) { + ShowFatalError("Failed to open %s!\n", ExternalPackageFilePath); + } + + /* Get file size. */ + s64 file_size; + if (R_FAILED((result = fs::GetFileSize(std::addressof(file_size), g_package_file)))) { + ShowFatalError("Failed to get package3 size: 0x%08" PRIx32 "\n", result.GetValue()); + } + + /* Check file size. */ + if (static_cast<size_t>(file_size) != ExternalPackageSize) { + ShowFatalError("package3 seems corrupted (size 0x%zx != 0x%zx)", static_cast<size_t>(file_size), ExternalPackageSize); + } + } + + void ReadFullExternalPackage() { + Result result; + + if (R_FAILED((result = fs::ReadFile(g_package_file, 0, const_cast<void *>(static_cast<const void *>(std::addressof(GetExternalPackage()))), ExternalPackageSize)))) { + ShowFatalError("Failed to read %s!\n", ExternalPackageFilePath); + } + } + + void CloseExternalPackage() { + fs::CloseFile(g_package_file); + } + + } + + void Main() { + /* Perform secure hardware initialization. */ + SecureInitialize(true); + + /* Overclock the bpmp. */ + clkrst::SetBpmpClockRate(fuse::GetSocType() == fuse::SocType_Mariko ? clkrst::BpmpClockRate_589MHz : clkrst::BpmpClockRate_576MHz); + + /* Initialize Sdram. */ + InitializeSdram(); + + /* Initialize cache. */ + hw::InitializeDataCache(); + + /* Initialize SD card. */ + { + Result result = InitializeSdCard(); + if (R_FAILED(result)) { + ShowFatalError("Failed to initialize the SD card: 0x%08" PRIx32 "\n", result.GetValue()); + } + } + + /* Mount SD card. */ + if (!fs::MountSdCard()) { + ShowFatalError("Failed to mount the SD card."); + } + + /* If we have a fatal error, save and display it. */ + SaveAndShowFatalError(); + + /* Open the external package. */ + OpenExternalPackage(); + + /* Load the memory training overlay. */ + LoadOverlay(g_package_file, OverlayId_MemoryTraining); + + /* Do memory training. */ + DoMemoryTraining(); + + /* Read the rest of the archive file. */ + ReadFullExternalPackage(); + + /* Save the memory training overlay. */ + SaveMemoryTrainingOverlay(); + + /* Initialize display (splash screen will be visible from this point onwards). */ + InitializeDisplay(); + ShowDisplay(); + + /* Close the external package. */ + CloseExternalPackage(); + + /* Perform rest of the boot process. */ + SetupAndStartHorizon(); + + /* Restore the memory training overlay. */ + RestoreMemoryTrainingOverlay(); + + /* Restore memory clock rate. */ + RestoreMemoryClockRate(); + + /* Restore secure monitor code. */ + RestoreSecureMonitorOverlay(); + + /* Finalize display. */ + FinalizeDisplay(); + + /* Finalize sd card. */ + FinalizeSdCard(); + + /* Finalize the data cache. */ + hw::FinalizeDataCache(); + + /* Downclock the bpmp. */ + clkrst::SetBpmpClockRate(clkrst::BpmpClockRate_408MHz); + + /* Signal to the secure monitor that we're done. */ + SetBootloaderState(pkg1::BootloaderState_Done); + + /* Halt ourselves. */ + while (true) { + reg::Write(secmon::MemoryRegionPhysicalDeviceFlowController.GetAddress() + FLOW_CTLR_HALT_COP_EVENTS, FLOW_REG_BITS_ENUM(HALT_COP_EVENTS_MODE, FLOW_MODE_STOP), + FLOW_REG_BITS_ENUM(HALT_COP_EVENTS_JTAG, ENABLED)); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_malloc.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_malloc.cpp new file mode 100644 index 00000000..133354b4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_malloc.cpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_malloc.hpp" +#include "fusee_fatal.hpp" + +namespace ams::nxboot { + + namespace { + + constinit uintptr_t g_heap_address = 0xC1000000; + + } + + void *AllocateMemory(size_t size) { + /* Get the current heap address. */ + void * const allocated = reinterpret_cast<void *>(g_heap_address); + + /* Advance the current heap address. */ + g_heap_address += size; + + /* Return the allocated chunk. */ + return allocated; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_malloc.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_malloc.hpp new file mode 100644 index 00000000..b3c48025 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_malloc.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> +#pragma once + +namespace ams::nxboot { + + void *AllocateMemory(size_t size); + + ALWAYS_INLINE void *AllocateAligned(size_t size, size_t align) { + return reinterpret_cast<void *>(util::AlignUp(reinterpret_cast<uintptr_t>(AllocateMemory(size + align)), align)); + } + + template<typename T, typename... Args> requires std::constructible_from<T, Args...> + inline T *AllocateObject(Args &&... args) { + T * const obj = static_cast<T *>(AllocateAligned(sizeof(T), alignof(T))); + + std::construct_at(obj, std::forward<Args>(args)...); + + return obj; + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_mmc.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_mmc.cpp new file mode 100644 index 00000000..639c83e6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_mmc.cpp @@ -0,0 +1,75 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_mmc.hpp" + +namespace ams::nxboot { + + namespace { + + constexpr inline auto MmcPort = sdmmc::Port_Mmc0; + + alignas(0x10) constinit u8 g_mmc_work_buffer[sdmmc::MmcWorkBufferSize]; + + constinit inline auto g_mmc_partition = sdmmc::MmcPartition_Unknown; + + Result SelectMmcPartition(sdmmc::MmcPartition partition) { + /* Change partition, if we need to. */ + if (partition != g_mmc_partition) { + R_TRY(sdmmc::SelectMmcPartition(MmcPort, partition)); + + g_mmc_partition = partition; + } + + R_SUCCEED(); + } + + } + + Result InitializeMmc() { + /* Initialize the mmc. */ + sdmmc::Initialize(MmcPort); + + /* Set the mmc work buffer. */ + sdmmc::SetMmcWorkBuffer(MmcPort, g_mmc_work_buffer, sizeof(g_mmc_work_buffer)); + + /* Activate the mmc. */ + R_RETURN(sdmmc::Activate(MmcPort)); + } + + Result CheckMmcConnection(sdmmc::SpeedMode *out_sm, sdmmc::BusWidth *out_bw) { + R_RETURN(sdmmc::CheckMmcConnection(out_sm, out_bw, MmcPort)); + } + + Result GetMmcMemoryCapacity(u32 *out_num_sectors, sdmmc::MmcPartition partition) { + if (partition == sdmmc::MmcPartition_UserData) { + R_RETURN(sdmmc::GetDeviceMemoryCapacity(out_num_sectors, MmcPort)); + } else { + R_RETURN(sdmmc::GetMmcBootPartitionCapacity(out_num_sectors, MmcPort)); + } + } + + Result ReadMmc(void *dst, size_t size, sdmmc::MmcPartition partition, size_t sector_index, size_t sector_count) { + R_TRY(SelectMmcPartition(partition)); + R_RETURN(sdmmc::Read(dst, size, MmcPort, sector_index, sector_count)); + } + + Result WriteMmc(sdmmc::MmcPartition partition, size_t sector_index, size_t sector_count, const void *src, size_t size) { + R_TRY(SelectMmcPartition(partition)); + R_RETURN(sdmmc::Write(MmcPort, sector_index, sector_count, src, size)); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_mmc.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_mmc.hpp new file mode 100644 index 00000000..f69ba61a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_mmc.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> +#pragma once + +namespace ams::nxboot { + + Result InitializeMmc(); + Result CheckMmcConnection(sdmmc::SpeedMode *out_sm, sdmmc::BusWidth *out_bw); + Result GetMmcMemoryCapacity(u32 *out_num_sectors, sdmmc::MmcPartition partition); + + Result ReadMmc(void *dst, size_t size, sdmmc::MmcPartition partition, size_t sector_index, size_t sector_count); + Result WriteMmc(sdmmc::MmcPartition partition, size_t sector_index, size_t sector_count, const void *src, size_t size); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_overlay_manager.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_overlay_manager.cpp new file mode 100644 index 00000000..109d3e0d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_overlay_manager.cpp @@ -0,0 +1,91 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_overlay_manager.hpp" +#include "fusee_external_package.hpp" +#include "fusee_fatal.hpp" + +namespace ams::nxboot { + + namespace { + + constinit u8 g_secmon_debug_storage[secmon::MemoryRegionPhysicalIramSecureMonitorDebug.GetSize()]; + + ALWAYS_INLINE void *GetOverlayDestination() { + return reinterpret_cast<void *>(0x4002C000); + } + + void LoadMemoryTrainingOverlay(fs::FileHandle archive_file) { + Result result; + u32 verif_hash; + u32 store_hash; + if (fuse::GetSocType() == fuse::SocType_Erista) { + result = fs::ReadFile(archive_file, AMS_OFFSETOF(ExternalPackage, ovl_mtc_erista), GetOverlayDestination(), sizeof(ExternalPackage{}.ovl_mtc_erista)); + verif_hash = reinterpret_cast<const u32 *>(GetOverlayDestination())[-2]; + store_hash = reinterpret_cast<const u32 *>(GetOverlayDestination())[(sizeof(ExternalPackage{}.ovl_mtc_erista) / sizeof(u32)) - 1]; + } else /* if (fuse::GetSocType() == fuse::SocType_Mariko) */ { + result = fs::ReadFile(archive_file, AMS_OFFSETOF(ExternalPackage, ovl_mtc_mariko), GetOverlayDestination(), sizeof(ExternalPackage{}.ovl_mtc_mariko)); + verif_hash = reinterpret_cast<const u32 *>(GetOverlayDestination())[-1]; + store_hash = reinterpret_cast<const u32 *>(GetOverlayDestination())[(sizeof(ExternalPackage{}.ovl_mtc_mariko) / sizeof(u32)) - 1]; + } + + if (R_FAILED(result)) { + ShowFatalError("Failed to load MTC overlay: 0x%08" PRIx32 "\n", result.GetValue()); + } + + if (verif_hash != store_hash) { + ShowFatalError("Incorrect fusee version! (program=0x%08" PRIx32 ", mtc=0x%08" PRIx32 ")\n", verif_hash, store_hash); + } + } + + } + + void LoadOverlay(fs::FileHandle archive_file, OverlayId ovl) { + switch (ovl) { + case OverlayId_MemoryTraining: + LoadMemoryTrainingOverlay(archive_file); + break; + } + } + + void SaveMemoryTrainingOverlay() { + if (fuse::GetSocType() == fuse::SocType_Erista) { + /* NOTE: Erista does not do memory clock restoration. */ + /* std::memcpy(const_cast<u8 *>(GetExternalPackage().ovl_mtc_erista), GetOverlayDestination(), sizeof(ExternalPackage{}.ovl_mtc_erista)); */ + } else /* if (fuse::GetSocType() == fuse::SocType_Mariko) */ { + std::memcpy(const_cast<u8 *>(GetExternalPackage().ovl_mtc_mariko), GetOverlayDestination(), sizeof(ExternalPackage{}.ovl_mtc_mariko) - 0x2000); + } + } + + void RestoreMemoryTrainingOverlay() { + if (fuse::GetSocType() == fuse::SocType_Erista) { + /* NOTE: Erista does not do memory clock restoration. */ + /* std::memcpy(GetOverlayDestination(), GetExternalPackage().ovl_mtc_erista, sizeof(ExternalPackage{}.ovl_mtc_erista)); */ + } else /* if (fuse::GetSocType() == fuse::SocType_Mariko) */ { + std::memcpy(g_secmon_debug_storage, secmon::MemoryRegionPhysicalIramSecureMonitorDebug.GetPointer<void>(), sizeof(g_secmon_debug_storage)); + std::memcpy(GetOverlayDestination(), GetExternalPackage().ovl_mtc_mariko, sizeof(ExternalPackage{}.ovl_mtc_mariko) - 0x2000); + } + } + + void RestoreSecureMonitorOverlay() { + if (fuse::GetSocType() == fuse::SocType_Erista) { + /* NOTE: Erista does not do memory clock restoration. */ + } else /* if (fuse::GetSocType() == fuse::SocType_Mariko) */ { + std::memcpy(secmon::MemoryRegionPhysicalIramSecureMonitorDebug.GetPointer<void>(), g_secmon_debug_storage, sizeof(g_secmon_debug_storage)); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_overlay_manager.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_overlay_manager.hpp new file mode 100644 index 00000000..54eb2b7e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_overlay_manager.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> +#include "fs/fusee_fs_api.hpp" + +namespace ams::nxboot { + + enum OverlayId { + /* OverlayId_SecureInitializer = 0, */ + OverlayId_MemoryTraining = 1, + }; + + void LoadOverlay(fs::FileHandle archive_file, OverlayId ovl); + + void SaveMemoryTrainingOverlay(); + void RestoreMemoryTrainingOverlay(); + void RestoreSecureMonitorOverlay(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_package2.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_package2.cpp new file mode 100644 index 00000000..3a75a043 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_package2.cpp @@ -0,0 +1,165 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_package2.hpp" +#include "fusee_key_derivation.hpp" +#include "fusee_fatal.hpp" + +namespace ams::nxboot { + + namespace { + + alignas(se::AesBlockSize) constexpr inline const u8 Package2KeySource[se::AesBlockSize] = { + 0xFB, 0x8B, 0x6A, 0x9C, 0x79, 0x00, 0xC8, 0x49, 0xEF, 0xD2, 0x4D, 0x85, 0x4D, 0x30, 0xA0, 0xC7 + }; + + void PreparePackage2Key(int pkg2_slot, int key_generation) { + /* Get keyslot for the desired master key. */ + const int master_slot = PrepareMasterKey(key_generation); + + /* Load the package2 key into the desired keyslot. */ + se::SetEncryptedAesKey128(pkg2_slot, master_slot, Package2KeySource, sizeof(Package2KeySource)); + } + + void DecryptPackage2(void *dst, size_t dst_size, const void *src, size_t src_size, const void *iv, size_t iv_size, u8 key_generation) { + /* Ensure that the SE sees consistent data. */ + hw::FlushDataCache(src, src_size); + if (src != dst) { + hw::FlushDataCache(dst, dst_size); + } + + /* Load the package2 key into the temporary keyslot. */ + PreparePackage2Key(pkg1::AesKeySlot_Temporary, key_generation); + + /* Decrypt the data. */ + se::ComputeAes128Ctr(dst, dst_size, pkg1::AesKeySlot_Temporary, src, src_size, iv, iv_size); + + /* Clear the keyslot we just used. */ + se::ClearAesKeySlot(pkg1::AesKeySlot_Temporary); + + /* Ensure that the cpu sees consistent data. */ + hw::InvalidateDataCache(dst, dst_size); + } + + void DecryptPackage2Header(pkg2::Package2Meta *dst, const pkg2::Package2Meta &src) { + constexpr int IvSize = 0x10; + + /* Decrypt the header. */ + DecryptPackage2(dst, sizeof(*dst), std::addressof(src), sizeof(src), std::addressof(src), IvSize, src.GetKeyGeneration()); + + /* Copy back the iv, which encodes encrypted metadata. */ + std::memcpy(dst, std::addressof(src), IvSize); + } + + bool VerifyPackage2Meta(const pkg2::Package2Meta &meta) { + /* Get the obfuscated metadata. */ + const size_t size = meta.GetSize(); + const u8 key_generation = meta.GetKeyGeneration(); + + /* Check that size is big enough for the header. */ + if (size <= sizeof(pkg2::Package2Header)) { + return false; + } + + /* Check that the size isn't larger than what we allow. */ + if (size > pkg2::Package2SizeMax) { + return false; + } + + /* Check that the key generation is one that we can use. */ + static_assert(pkg1::KeyGeneration_Count == 20); + if (key_generation >= pkg1::KeyGeneration_Count) { + return false; + } + + /* Check the magic number. */ + if (!crypto::IsSameBytes(meta.magic, pkg2::Package2Meta::Magic::String, sizeof(meta.magic))) { + return false; + } + + /* Check the payload alignments. */ + if ((meta.entrypoint % pkg2::PayloadAlignment) != 0) { + return false; + } + + for (int i = 0; i < pkg2::PayloadCount; ++i) { + if ((meta.payload_sizes[i] % pkg2::PayloadAlignment) != 0) { + return false; + } + } + + /* Check that the sizes sum to the total. */ + if (size != sizeof(pkg2::Package2Header) + meta.payload_sizes[0] + meta.payload_sizes[1] + meta.payload_sizes[2]) { + return false; + } + + /* Check that the payloads do not overflow. */ + for (int i = 0; i < pkg2::PayloadCount; ++i) { + if (meta.payload_offsets[i] > meta.payload_offsets[i] + meta.payload_sizes[i]) { + return false; + } + } + + /* Verify that no payloads overlap. */ + for (int i = 0; i < pkg2::PayloadCount - 1; ++i) { + for (int j = i + 1; j < pkg2::PayloadCount; ++j) { + if (util::HasOverlap(meta.payload_offsets[i], meta.payload_sizes[i], meta.payload_offsets[j], meta.payload_sizes[j])) { + return false; + } + } + } + + /* Check whether any payload contains the entrypoint. */ + for (int i = 0; i < pkg2::PayloadCount; ++i) { + if (util::Contains(meta.payload_offsets[i], meta.payload_sizes[i], meta.entrypoint)) { + return true; + } + } + + /* No payload contains the entrypoint, so we're not valid. */ + return false; + } + + } + + void DecryptPackage2(u8 *package2) { + /* Decrypt package2 header. */ + pkg2::Package2Header *header = reinterpret_cast<pkg2::Package2Header *>(package2); + { + pkg2::Package2Header tmp = *header; + DecryptPackage2Header(std::addressof(header->meta), tmp.meta); + } + + /* Check package2 magic. */ + if (!VerifyPackage2Meta(header->meta)) { + ShowFatalError("Package2 meta is invalid!\n"); + } + + /* Decrypt package2 payloads. */ + u8 *payload = package2 + sizeof(*header); + const u8 key_generation = header->meta.GetKeyGeneration(); + for (int i = 0; i < pkg2::PayloadCount; ++i) { + if (header->meta.payload_sizes[i] == 0) { + continue; + } + + DecryptPackage2(payload, header->meta.payload_sizes[i], payload, header->meta.payload_sizes[i], header->meta.payload_ivs[i], sizeof(header->meta.payload_ivs[i]), key_generation); + + payload += header->meta.payload_sizes[i]; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_package2.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_package2.hpp new file mode 100644 index 00000000..687c5c4f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_package2.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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> +#pragma once + +namespace ams::nxboot { + + void DecryptPackage2(u8 *package2); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_print.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_print.cpp new file mode 100644 index 00000000..4ea9dd5d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_print.cpp @@ -0,0 +1,126 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_display.hpp" +#include "fusee_print.hpp" + +namespace ams::nxboot { + + namespace { + + #include "fusee_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 VPrint(const char *fmt, std::va_list vl) { + /* Generate the string. */ + char log_str[1_KB]; + util::TVSNPrintf(log_str, sizeof(log_str), fmt, vl); + + /* Print each character. */ + const size_t len = std::strlen(log_str); + for (size_t i = 0; i < len; ++i) { + PutChar(log_str[i]); + } + } + + void Print(const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + VPrint(fmt, vl); + va_end(vl); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_print.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_print.hpp new file mode 100644 index 00000000..74810e38 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_print.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::nxboot { + + void InitializeConsole(u32 *frame_buffer); + void Print(const char *fmt, ...) __attribute__((format(printf, 1, 2))); + void VPrint(const char *fmt, std::va_list vl); + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_registers_di.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_registers_di.hpp new file mode 100644 index 00000000..c6743ba3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_registers_di.hpp @@ -0,0 +1,354 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +#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 + +#define NV_PVIC_THI_SLCG_OVERRIDE_LOW_A 0x8C diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_sd_card.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_sd_card.cpp new file mode 100644 index 00000000..0b9ec98e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_sd_card.cpp @@ -0,0 +1,108 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_sd_card.hpp" + +namespace ams::nxboot { + + namespace { + + constexpr inline auto SdCardPort = sdmmc::Port_SdCard0; + + constexpr inline const uintptr_t APB = secmon::MemoryRegionPhysicalDeviceApbMisc.GetAddress(); + + alignas(0x10) constinit u8 g_sd_work_buffer[sdmmc::SdCardWorkBufferSize]; + + void ConfigureInitialSdCardPinmux() { + /* Normally, these pints get configured by boot sysmodule during initial pinmux config. */ + /* However, they're required to access the SD card, so we must do them ahead of time. */ + reg::ReadWrite(APB + PINMUX_AUX_SDMMC1_CLK, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_DOWN), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_CLK_PM, SDMMC1)); + + reg::ReadWrite(APB + PINMUX_AUX_SDMMC1_CMD, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_UP), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_CMD_PM, SDMMC1)); + + reg::ReadWrite(APB + PINMUX_AUX_SDMMC1_DAT3, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_UP), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_DAT3_PM, SDMMC1)); + + reg::ReadWrite(APB + PINMUX_AUX_SDMMC1_DAT2, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_UP), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_DAT2_PM, SDMMC1)); + + reg::ReadWrite(APB + PINMUX_AUX_SDMMC1_DAT1, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_UP), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_DAT1_PM, SDMMC1)); + + reg::ReadWrite(APB + PINMUX_AUX_SDMMC1_DAT0, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_UP), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_DAT0_PM, SDMMC1)); + + reg::ReadWrite(APB + PINMUX_AUX_DMIC3_CLK, PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_DAT0_PM, RSVD2)); + } + + } + + Result InitializeSdCard() { + /* Perform initial pinmux config to enable sd card access. */ + ConfigureInitialSdCardPinmux(); + + /* Initialize the SD card. */ + sdmmc::Initialize(SdCardPort); + + /* Set the SD card work buffer. */ + sdmmc::SetSdCardWorkBuffer(SdCardPort, g_sd_work_buffer, sizeof(g_sd_work_buffer)); + + /* Activate the SD card. */ + R_RETURN(sdmmc::Activate(SdCardPort)); + } + + void FinalizeSdCard() { + /* Deactivate the SD card. */ + sdmmc::Deactivate(SdCardPort); + + /* Finalize the SD card. */ + sdmmc::Finalize(SdCardPort); + } + + Result CheckSdCardConnection(sdmmc::SpeedMode *out_sm, sdmmc::BusWidth *out_bw) { + R_RETURN(sdmmc::CheckSdCardConnection(out_sm, out_bw, SdCardPort)); + } + + Result GetSdCardMemoryCapacity(u32 *out_num_sectors) { + R_RETURN(sdmmc::GetDeviceMemoryCapacity(out_num_sectors, SdCardPort)); + } + + Result ReadSdCard(void *dst, size_t size, size_t sector_index, size_t sector_count) { + R_RETURN(sdmmc::Read(dst, size, SdCardPort, sector_index, sector_count)); + } + + Result WriteSdCard(size_t sector_index, size_t sector_count, const void *src, size_t size) { + R_RETURN(sdmmc::Write(SdCardPort, sector_index, sector_count, src, size)); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_sd_card.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_sd_card.hpp new file mode 100644 index 00000000..2caa3a86 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_sd_card.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> +#pragma once + +namespace ams::nxboot { + + Result InitializeSdCard(); + Result CheckSdCardConnection(sdmmc::SpeedMode *out_sm, sdmmc::BusWidth *out_bw); + Result GetSdCardMemoryCapacity(u32 *out_num_sectors); + + 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); + + void FinalizeSdCard(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_secmon_sync.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_secmon_sync.cpp new file mode 100644 index 00000000..a5b2b101 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_secmon_sync.cpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_secmon_sync.hpp" + +namespace ams::nxboot { + + namespace { + + ALWAYS_INLINE pkg1::SecureMonitorParameters &GetSecureMonitorParameters() { + return *secmon::MemoryRegionPhysicalDeviceBootloaderParams.GetPointer<pkg1::SecureMonitorParameters>(); + } + + } + + void InitializeSecureMonitorMailbox() { + std::memset(std::addressof(GetSecureMonitorParameters()), 0, sizeof(GetSecureMonitorParameters())); + } + + void WaitSecureMonitorState(pkg1::SecureMonitorState state) { + auto &secmon_params = GetSecureMonitorParameters(); + + while (secmon_params.secmon_state != state) { + hw::InvalidateDataCache(std::addressof(secmon_params.secmon_state), sizeof(secmon_params.secmon_state)); + util::WaitMicroSeconds(1); + } + } + + void SetBootloaderState(pkg1::BootloaderState state) { + auto &secmon_params = GetSecureMonitorParameters(); + secmon_params.bootloader_state = state; + hw::FlushDataCache(std::addressof(secmon_params.bootloader_state), sizeof(secmon_params.bootloader_state)); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_secmon_sync.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_secmon_sync.hpp new file mode 100644 index 00000000..b918f7ec --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_secmon_sync.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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#pragma once + +namespace ams::nxboot { + + void InitializeSecureMonitorMailbox(); + + void WaitSecureMonitorState(pkg1::SecureMonitorState state); + void SetBootloaderState(pkg1::BootloaderState state); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_setup_horizon.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_setup_horizon.cpp new file mode 100644 index 00000000..000bbe52 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_setup_horizon.cpp @@ -0,0 +1,770 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include <exosphere/secmon/secmon_monitor_context.hpp> +#include "fusee_key_derivation.hpp" +#include "fusee_external_package.hpp" +#include "fusee_setup_horizon.hpp" +#include "fusee_ini.hpp" +#include "fusee_emummc.hpp" +#include "fusee_mmc.hpp" +#include "fusee_cpu.hpp" +#include "fusee_fatal.hpp" +#include "fusee_package2.hpp" +#include "fusee_malloc.hpp" +#include "fusee_secmon_sync.hpp" +#include "fusee_stratosphere.hpp" +#include "fs/fusee_fs_api.hpp" + +namespace ams::nxboot { + + namespace { + + constexpr inline const uintptr_t CLKRST = secmon::MemoryRegionPhysicalDeviceClkRst.GetAddress(); + constexpr inline const uintptr_t PMC = secmon::MemoryRegionPhysicalDevicePmc.GetAddress(); + constexpr inline const uintptr_t MC = secmon::MemoryRegionPhysicalDeviceMemoryController.GetAddress(); + + constinit secmon::EmummcConfiguration g_emummc_cfg = {}; + + void DeriveAllKeys(const fuse::SocType soc_type) { + /* If on erista, run the TSEC keygen firmware. */ + if (soc_type == fuse::SocType_Erista) { + clkrst::SetBpmpClockRate(clkrst::BpmpClockRate_408MHz); + + if (!tsec::RunTsecFirmware(GetExternalPackage().tsec_keygen, sizeof(GetExternalPackage().tsec_keygen))) { + ShowFatalError("Failed to run tsec_keygen firmware!\n"); + } + + clkrst::SetBpmpClockRate(clkrst::BpmpClockRate_576MHz); + } + + /* Derive master/device keys. */ + if (soc_type == fuse::SocType_Erista) { + DeriveKeysErista(); + } else /* if (soc_type == fuse::SocType_Mariko) */ { + DeriveKeysMariko(); + } + } + + bool ParseIniSafe(IniSectionList &out_sections, const char *ini_path) { + const auto result = ParseIniFile(out_sections, ini_path); + if (result == ParseIniResult_Success) { + return true; + } else if (result == ParseIniResult_NoFile) { + return false; + } else { + ShowFatalError("Failed to parse %s!\n", ini_path); + } + } + + u32 ParseHexInteger(const char *s) { + u32 x = 0; + if (s[0] == '0' && s[1] == 'x') { + s += 2; + } + + while (true) { + const char c = *(s++); + + if (c == '\x00') { + return x; + } else { + x <<= 4; + + if ('0' <= c && c <= '9') { + x |= (c - '0'); + } else if ('a' <= c && c <= 'f') { + x |= (c - 'a') + 10; + } else if ('A' <= c && c <= 'F') { + x |= (c - 'A') + 10; + } + } + } + } + + u32 ParseDecimalInteger(const char *s) { + u32 x = 0; + while (true) { + const char c = *(s++); + + if (c == '\x00') { + return x; + } else { + x *= 10; + + if ('0' <= c && c <= '9') { + x += c - '0'; + } + } + } + } + + bool IsDirectoryExist(const char *path) { + fs::DirectoryEntryType entry_type; + bool archive; + return R_SUCCEEDED(fs::GetEntryType(std::addressof(entry_type), std::addressof(archive), path)) && entry_type == fs::DirectoryEntryType_Directory; + } + + bool IsFileExist(const char *path) { + fs::DirectoryEntryType entry_type; + bool archive; + return R_SUCCEEDED(fs::GetEntryType(std::addressof(entry_type), std::addressof(archive), path)) && entry_type == fs::DirectoryEntryType_File; + } + + bool ConfigureEmummc() { + /* Set magic. */ + g_emummc_cfg.base_cfg.magic = secmon::EmummcBaseConfiguration::Magic; + + /* Parse ini. */ + bool enabled = false; + u32 id = 0; + u32 sector = 0; + const char *path = ""; + const char *n_path = ""; + { + IniSectionList sections; + if (ParseIniSafe(sections, "sdmc:/emummc/emummc.ini")) { + for (const auto §ion : sections) { + /* We only care about the [emummc] section. */ + if (std::strcmp(section.name, "emummc")) { + continue; + } + + /* Handle individual fields. */ + for (const auto &entry : section.kv_list) { + if (std::strcmp(entry.key, "enabled") == 0) { + enabled = entry.value[0] != '0'; + } else if (std::strcmp(entry.key, "id") == 0) { + id = ParseHexInteger(entry.value); + } else if (std::strcmp(entry.key, "sector") == 0) { + sector = ParseHexInteger(entry.value); + } else if (std::strcmp(entry.key, "path") == 0) { + path = entry.value; + } else if (std::strcmp(entry.key, "nintendo_path") == 0) { + n_path = entry.value; + } + } + } + } + } + + /* Set values parsed from config. */ + g_emummc_cfg.base_cfg.id = id; + std::strncpy(g_emummc_cfg.emu_dir_path.str, n_path, sizeof(g_emummc_cfg.emu_dir_path.str)); + g_emummc_cfg.emu_dir_path.str[sizeof(g_emummc_cfg.emu_dir_path.str) - 1] = '\x00'; + + if (enabled) { + if (sector > 0) { + g_emummc_cfg.base_cfg.type = secmon::EmummcType_Partition; + g_emummc_cfg.partition_cfg.start_sector = sector; + } else if (path[0] != '\x00' && IsDirectoryExist(path)) { + g_emummc_cfg.base_cfg.type = secmon::EmummcType_File; + + std::strncpy(g_emummc_cfg.file_cfg.path.str, path, sizeof(g_emummc_cfg.file_cfg.path.str)); + g_emummc_cfg.file_cfg.path.str[sizeof(g_emummc_cfg.file_cfg.path.str) - 1] = '\x00'; + } else { + ShowFatalError("Invalid emummc setting!\n"); + } + } + + return enabled; + } + + u8 *LoadPackage1(fuse::SocType soc_type) { + u8 *package1 = static_cast<u8 *>(AllocateAligned(0x40000, 0x1000)); + + const Result result = ReadBoot0(0x100000, package1, 0x40000); + if (R_FAILED(result)) { + ShowFatalError("Failed to read boot0: 0x%08" PRIx32 "!\n", result.GetValue()); + } + + if (soc_type == fuse::SocType_Mariko) { + package1 += 0x170; + + se::DecryptAes128Cbc(package1 + 0x20, 0x40000 - (0x20 + 0x170), pkg1::AesKeySlot_MarikoBek, package1 + 0x20, 0x40000 - (0x20 + 0x170), package1 + 0x10, se::AesBlockSize); + + hw::InvalidateDataCache(package1 + 0x20, 0x40000 - (0x20 + 0x170)); + + if (std::memcmp(package1, package1 + 0x20, 0x20) != 0) { + ShowFatalError("Package1 seems corrupt!\n"); + } + } + + return package1; + } + + ams::TargetFirmware GetApproximateTargetFirmware(const u8 *package1) { + /* Get an approximation of the target firmware. */ + switch (package1[0x1F]) { + case 0x01: + return ams::TargetFirmware_1_0_0; + case 0x02: + return ams::TargetFirmware_2_0_0; + case 0x04: + return ams::TargetFirmware_3_0_0; + case 0x07: + return ams::TargetFirmware_4_0_0; + case 0x0B: + return ams::TargetFirmware_5_0_0; + case 0x0E: + if (std::memcmp(package1 + 0x10, "20180802", 8) == 0) { + return ams::TargetFirmware_6_0_0; + } else if (std::memcmp(package1 + 0x10, "20181107", 8) == 0) { + return ams::TargetFirmware_6_2_0; + } + break; + case 0x0F: + return ams::TargetFirmware_7_0_0; + case 0x10: + if (std::memcmp(package1 + 0x10, "20190314", 8) == 0) { + return ams::TargetFirmware_8_0_0; + } else if (std::memcmp(package1 + 0x10, "20190531", 8) == 0) { + return ams::TargetFirmware_8_1_0; + } else if (std::memcmp(package1 + 0x10, "20190809", 8) == 0) { + return ams::TargetFirmware_9_0_0; + } else if (std::memcmp(package1 + 0x10, "20191021", 8) == 0) { + return ams::TargetFirmware_9_1_0; + } else if (std::memcmp(package1 + 0x10, "20200303", 8) == 0) { + return ams::TargetFirmware_10_0_0; + } else if (std::memcmp(package1 + 0x10, "20201030", 8) == 0) { + return ams::TargetFirmware_11_0_0; + } else if (std::memcmp(package1 + 0x10, "20210129", 8) == 0) { + return ams::TargetFirmware_12_0_0; + } else if (std::memcmp(package1 + 0x10, "20210422", 8) == 0) { + return ams::TargetFirmware_12_0_2; + } else if (std::memcmp(package1 + 0x10, "20210607", 8) == 0) { + return ams::TargetFirmware_12_1_0; + } else if (std::memcmp(package1 + 0x10, "20210805", 8) == 0) { + return ams::TargetFirmware_13_0_0; + } else if (std::memcmp(package1 + 0x10, "20220105", 8) == 0) { + return ams::TargetFirmware_13_2_1; + } else if (std::memcmp(package1 + 0x10, "20220209", 8) == 0) { + return ams::TargetFirmware_14_0_0; + } else if (std::memcmp(package1 + 0x10, "20220801", 8) == 0) { + return ams::TargetFirmware_15_0_0; + } else if (std::memcmp(package1 + 0x10, "20230111", 8) == 0) { + return ams::TargetFirmware_16_0_0; + } else if (std::memcmp(package1 + 0x10, "20230906", 8) == 0) { + return ams::TargetFirmware_17_0_0; + } else if (std::memcmp(package1 + 0x10, "20240207", 8) == 0) { + return ams::TargetFirmware_18_0_0; + } else if (std::memcmp(package1 + 0x10, "20240808", 8) == 0) { + return ams::TargetFirmware_19_0_0; + } else if (std::memcmp(package1 + 0x10, "20250206", 8) == 0) { + return ams::TargetFirmware_20_0_0; + } + break; + default: + break; + } + + ShowFatalError("Unable to identify package1!\n"); + } + + u8 *LoadBootConfigAndPackage2() { + Result result; + + /* Load boot config. */ + if (R_FAILED((result = ReadPackage2(0, secmon::MemoryRegionPhysicalIramBootConfig.GetPointer<void>(), secmon::MemoryRegionPhysicalIramBootConfig.GetSize())))) { + ShowFatalError("Failed to read boot config: 0x%08" PRIx32 "!\n", result.GetValue()); + } + + /* Read package2 header. */ + u8 *package2; + size_t package2_size; + { + constexpr s64 Package2Offset = AMS_OFFSETOF(pkg2::StorageLayout, package2_header); + + pkg2::Package2Header header; + if (R_FAILED((result = ReadPackage2(Package2Offset, std::addressof(header), sizeof(header))))) { + ShowFatalError("Failed to read package2 header: 0x%08" PRIx32 "!\n", result.GetValue()); + } + + package2_size = header.meta.GetSize(); + package2 = static_cast<u8 *>(AllocateAligned(util::AlignUp(package2_size, 0x4000), 0x4000)); + + if (R_FAILED((result = ReadPackage2(Package2Offset, package2, util::AlignUp(package2_size, 0x4000))))) { + ShowFatalError("Failed to read package2: 0x%08" PRIx32 "!\n", result.GetValue()); + } + } + + /* Decrypt package2. */ + DecryptPackage2(package2); + + return package2; + } + + constexpr inline const u8 PkcModulusErista[0x100] = { + 0xF7, 0x86, 0x47, 0xAB, 0x71, 0x89, 0x81, 0xB5, 0xCF, 0x0C, 0xB0, 0xE8, 0x48, 0xA7, 0xFD, 0xAD, + 0xCB, 0x4E, 0x4A, 0x52, 0x0B, 0x1A, 0x8E, 0xDE, 0x41, 0x87, 0x6F, 0xB7, 0x31, 0x05, 0x5F, 0xAA, + 0xEA, 0x97, 0x76, 0x21, 0x20, 0x2B, 0x40, 0x48, 0x76, 0x55, 0x35, 0x03, 0xFE, 0x7F, 0x67, 0x62, + 0xFD, 0x4E, 0xE1, 0x22, 0xF8, 0xF0, 0x97, 0x39, 0xEF, 0xEA, 0x47, 0x89, 0x3C, 0xDB, 0xF0, 0x02, + 0xAD, 0x0C, 0x96, 0xCA, 0x82, 0xAB, 0xB3, 0xCB, 0x98, 0xC8, 0xDC, 0xC6, 0xAC, 0x5C, 0x93, 0x3B, + 0x84, 0x3D, 0x51, 0x91, 0x9E, 0xC1, 0x29, 0x22, 0x95, 0xF0, 0xA1, 0x51, 0xBA, 0xAF, 0x5D, 0xC3, + 0xAB, 0x04, 0x1B, 0x43, 0x61, 0x7D, 0xEA, 0x65, 0x95, 0x24, 0x3C, 0x51, 0x3E, 0x8F, 0xDB, 0xDB, + 0xC1, 0xC4, 0x2D, 0x04, 0x29, 0x5A, 0xD7, 0x34, 0x6B, 0xCC, 0xF1, 0x06, 0xF9, 0xC9, 0xE1, 0xF9, + 0x61, 0x52, 0xE2, 0x05, 0x51, 0xB1, 0x3D, 0x88, 0xF9, 0xA9, 0x27, 0xA5, 0x6F, 0x4D, 0xE7, 0x22, + 0x48, 0xA5, 0xF8, 0x12, 0xA2, 0xC2, 0x5A, 0xA0, 0xBF, 0xC8, 0x76, 0x4B, 0x66, 0xFE, 0x1C, 0x73, + 0x00, 0x29, 0x26, 0xCD, 0x18, 0x4F, 0xC2, 0xB0, 0x51, 0x77, 0x2E, 0x91, 0x09, 0x1B, 0x41, 0x5D, + 0x89, 0x5E, 0xEE, 0x24, 0x22, 0x47, 0xE5, 0xE5, 0xF1, 0x86, 0x99, 0x67, 0x08, 0x28, 0x42, 0xF0, + 0x58, 0x62, 0x54, 0xC6, 0x5B, 0xDC, 0xE6, 0x80, 0x85, 0x6F, 0xE2, 0x72, 0xB9, 0x7E, 0x36, 0x64, + 0x48, 0x85, 0x10, 0xA4, 0x75, 0x38, 0x79, 0x76, 0x8B, 0x51, 0xD5, 0x87, 0xC3, 0x02, 0xC9, 0x1B, + 0x93, 0x22, 0x49, 0xEA, 0xAB, 0xA0, 0xB5, 0xB1, 0x3C, 0x10, 0xC4, 0x71, 0xF0, 0xF1, 0x81, 0x1A, + 0x3A, 0x9C, 0xFC, 0x51, 0x61, 0xB1, 0x4B, 0x18, 0xB2, 0x3D, 0xAA, 0xD6, 0xAC, 0x72, 0x26, 0xB7 + }; + + constexpr inline const u8 PkcModulusDevelopmentErista[0x100] = { + 0x37, 0x84, 0x14, 0xB3, 0x78, 0xA4, 0x7F, 0xD8, 0x71, 0x45, 0xCD, 0x90, 0x51, 0x51, 0xBF, 0x2C, + 0x27, 0x03, 0x30, 0x46, 0xBE, 0x8F, 0x99, 0x3E, 0x9F, 0x36, 0x4D, 0xEB, 0xF7, 0x0E, 0x81, 0x7F, + 0xE4, 0x6B, 0xA8, 0x42, 0x8A, 0xA5, 0x4F, 0x76, 0xCC, 0xCB, 0xC5, 0x31, 0xA8, 0x5A, 0x70, 0x51, + 0x34, 0xBF, 0x1E, 0x8D, 0x6E, 0xCF, 0x05, 0x84, 0xCF, 0x8B, 0xE5, 0x9C, 0x3A, 0xA5, 0xCD, 0x1A, + 0x9C, 0xAC, 0x59, 0x30, 0x09, 0x21, 0x3C, 0xBE, 0x07, 0x5C, 0x8D, 0x1C, 0xD1, 0xA3, 0xC9, 0x8F, + 0x26, 0xE2, 0x99, 0xB2, 0x3C, 0x28, 0xAD, 0x63, 0x0F, 0xF5, 0xA0, 0x1C, 0xA2, 0x34, 0xC4, 0x0E, + 0xDB, 0xD7, 0xE1, 0xA9, 0x5E, 0xE9, 0xA5, 0xA8, 0x64, 0x3A, 0xFC, 0x48, 0xB5, 0x97, 0xDF, 0x55, + 0x7C, 0x9A, 0xD2, 0x8C, 0x32, 0x36, 0x1D, 0xC5, 0xA0, 0xC5, 0x66, 0xDF, 0x8A, 0xAD, 0x76, 0x18, + 0x46, 0x3E, 0xDF, 0xD8, 0xEF, 0xB9, 0xE5, 0xDC, 0xCD, 0x08, 0x59, 0xBC, 0x36, 0x68, 0xD6, 0xFC, + 0x3F, 0xFA, 0x11, 0x00, 0x0D, 0x50, 0xE0, 0x69, 0x0F, 0x70, 0x78, 0x7E, 0xD1, 0xA5, 0x85, 0xCD, + 0x13, 0xBC, 0x42, 0x74, 0x33, 0x0C, 0x11, 0x24, 0x1E, 0x33, 0xD5, 0x31, 0xB7, 0x3E, 0x48, 0x94, + 0xCC, 0x81, 0x29, 0x1E, 0xB1, 0xCF, 0x4C, 0x36, 0x7F, 0xE1, 0x1C, 0x15, 0xD4, 0x3F, 0xFB, 0x12, + 0xC2, 0x73, 0x22, 0x16, 0x52, 0xE0, 0x5C, 0x4C, 0x94, 0xE0, 0x87, 0x47, 0xEA, 0xD0, 0x9F, 0x42, + 0x9B, 0xAC, 0xB6, 0xB5, 0xB6, 0x34, 0xE4, 0x55, 0x49, 0xD7, 0xC0, 0xAE, 0xD4, 0x22, 0xB3, 0x5C, + 0x87, 0x64, 0x42, 0xEC, 0x11, 0x6D, 0xBC, 0x09, 0xC0, 0x80, 0x07, 0xD0, 0xBD, 0xBA, 0x45, 0xFE, + 0xD5, 0x52, 0xDA, 0xEC, 0x41, 0xA4, 0xAD, 0x7B, 0x36, 0x86, 0x18, 0xB4, 0x5B, 0xD1, 0x30, 0xBB + }; + + void LoadWarmbootFirmware(fuse::SocType soc_type, ams::TargetFirmware target_firmware, const u8 *package1) { + u8 *warmboot_dst = secmon::MemoryRegionPhysicalIramWarmbootBin.GetPointer<u8>(); + size_t warmboot_size = std::min(sizeof(GetExternalPackage().warmboot), secmon::MemoryRegionPhysicalIramWarmbootBin.GetSize()); + if (soc_type == fuse::SocType_Erista) { + /* Copy the ams warmboot binary. */ + std::memcpy(warmboot_dst, GetExternalPackage().warmboot, warmboot_size); + + /* Set the rsa modulus. */ + if (fuse::GetHardwareState() == fuse::HardwareState_Production) { + std::memcpy(warmboot_dst + 0x10, PkcModulusErista, sizeof(PkcModulusErista)); + } else { + std::memcpy(warmboot_dst + 0x10, PkcModulusDevelopmentErista, sizeof(PkcModulusDevelopmentErista)); + } + + /* Set the target firmware. */ + std::memcpy(warmboot_dst + 0x248, std::addressof(target_firmware), sizeof(target_firmware)); + } else /* if (soc_type == fuse::SocType_Mariko) */ { + /* Declare path for mariko warmboot files. */ + char warmboot_path[0x80] = "sdmc:/warmboot_mariko/wb_xx.bin"; + + auto UpdateWarmbootPath = [&warmboot_path](u8 fuses) { + warmboot_path[0x19] = "0123456789abcdef"[(fuses >> 4) & 0xF]; + warmboot_path[0x1A] = "0123456789abcdef"[(fuses >> 0) & 0xF]; + }; + + /* Get expected/burnt fuse counts. */ + const u32 expected_fuses = fuse::GetExpectedFuseVersion(target_firmware); + const u32 burnt_fuses = fuse::GetFuseVersion(); + u32 used_fuses = expected_fuses; + + /* Get warmboot from package1. */ + const u8 *warmboot_src = nullptr; + size_t warmboot_src_size = 0; + { + const u32 *package1_pk11 = reinterpret_cast<const u32 *>(package1 + (target_firmware >= ams::TargetFirmware_6_2_0 ? 0x7000 : 0x4000)); + if (std::memcmp(package1_pk11, "PK11", 4) != 0) { + ShowFatalError("Invalid package1 magic!\n"); + } + + const u32 *package1_pk11_data = reinterpret_cast<const u32 *>(package1_pk11 + (0x20 / sizeof(u32))); + for (size_t i = 0; i < 3; ++i) { + switch (*package1_pk11_data) { + case 0xD5034FDF: + package1_pk11_data += package1_pk11[6] / sizeof(u32); + break; + case 0xE328F0C0: + case 0xF0C0A7F0: + package1_pk11_data += package1_pk11[4] / sizeof(u32); + break; + default: + warmboot_src = reinterpret_cast<const u8 *>(package1_pk11_data); + i = 3; + break; + } + } + + warmboot_src_size = *package1_pk11_data; + if (!(0x800 <= warmboot_src_size && warmboot_src_size < 0x1000)) { + ShowFatalError("Package1 warmboot firmware seems invalid!\n"); + } + + /* If we should, save the current warmboot firmware. */ + UpdateWarmbootPath(expected_fuses); + if (!IsFileExist(warmboot_path)) { + fs::CreateDirectory("sdmc:/warmboot_mariko"); + fs::CreateFile(warmboot_path, warmboot_src_size); + + Result result; + fs::FileHandle file; + if (R_FAILED((result = fs::OpenFile(std::addressof(file), warmboot_path, fs::OpenMode_ReadWrite)))) { + ShowFatalError("Failed to save %s!\n", warmboot_path); + } + + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + if (R_FAILED((result = fs::WriteFile(file, 0, warmboot_src, warmboot_src_size, fs::WriteOption::Flush)))) { + ShowFatalError("Failed to save %s!\n", warmboot_path); + } + } + + /* If we need to, find a cached warmboot firmware that we can use. */ + if (burnt_fuses > expected_fuses) { + warmboot_src = nullptr; + warmboot_src_size = 0; + for (u32 attempt = burnt_fuses; attempt <= 32; ++attempt) { + /* Open the current cache file. */ + UpdateWarmbootPath(attempt); + + fs::FileHandle file; + if (R_FAILED(fs::OpenFile(std::addressof(file), warmboot_path, fs::OpenMode_Read))) { + continue; + } + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Get the size. */ + s64 size; + if (R_FAILED(fs::GetFileSize(std::addressof(size), file)) || !(0x800 <= size && size < 0x1000)) { + continue; + } + + /* Allocate memory. */ + warmboot_src_size = static_cast<size_t>(size); + void *tmp = AllocateAligned(warmboot_src_size, 0x10); + + /* Read the file. */ + if (R_FAILED(fs::ReadFile(file, 0, tmp, warmboot_src_size))) { + continue; + } + + /* Use the cached file. */ + used_fuses = attempt; + warmboot_src = static_cast<const u8 *>(tmp); + break; + } + } + + /* Check that we found a firmware. */ + if (warmboot_src == nullptr) { + ShowFatalError("Failed to locate warmboot firmware!\n"); + } + + /* Copy the warmboot firmware. */ + std::memcpy(warmboot_dst, warmboot_src, std::min(warmboot_size, warmboot_src_size)); + + /* Set the warmboot firmware magic. */ + switch (used_fuses) { + case 7: + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH32, 0x87); + case 8: + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH32, 0xA8); + default: + reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH32, (0x108 + 0x21 * (used_fuses - 8))); + break; + } + reg::SetBits(PMC + APBDEV_PMC_SEC_DISABLE3, (1 << 16)); + } + } + } + + void ConfigureExosphere(fuse::SocType soc_type, ams::TargetFirmware target_firmware, bool emummc_enabled, u32 fs_version) { + /* Get monitor configuration. */ + auto &storage_ctx = *secmon::MemoryRegionPhysicalDramMonitorConfiguration.GetPointer<secmon::SecureMonitorStorageConfiguration>(); + std::memset(std::addressof(storage_ctx), 0, sizeof(storage_ctx)); + + /* Set magic. */ + storage_ctx.magic = secmon::SecureMonitorStorageConfiguration::Magic; + + /* Set some defaults. */ + storage_ctx.target_firmware = target_firmware; + storage_ctx.lcd_vendor = GetDisplayLcdVendor(); + storage_ctx.emummc_cfg = g_emummc_cfg; + storage_ctx.flags[0] = secmon::SecureMonitorConfigurationFlag_Default; + storage_ctx.flags[1] = secmon::SecureMonitorConfigurationFlag_None; + storage_ctx.log_port = uart::Port_ReservedDebug; + storage_ctx.log_baud_rate = 115200; + + /* Set the fs version. */ + storage_ctx.emummc_cfg.base_cfg.fs_version = fs_version; + + /* Parse fields from exosphere.ini */ + { + IniSectionList sections; + if (ParseIniSafe(sections, "sdmc:/exosphere.ini")) { + for (const auto §ion : sections) { + /* We only care about the [exosphere] section. */ + if (std::strcmp(section.name, "exosphere")) { + continue; + } + + /* Handle individual fields. */ + for (const auto &entry : section.kv_list) { + if (std::strcmp(entry.key, "debugmode") == 0) { + if (entry.value[0] == '1') { + storage_ctx.flags[0] |= secmon::SecureMonitorConfigurationFlag_IsDevelopmentFunctionEnabledForKernel; + } else { + storage_ctx.flags[0] &= ~secmon::SecureMonitorConfigurationFlag_IsDevelopmentFunctionEnabledForKernel; + } + } else if (std::strcmp(entry.key, "debugmode_user") == 0) { + if (entry.value[0] == '1') { + storage_ctx.flags[0] |= secmon::SecureMonitorConfigurationFlag_IsDevelopmentFunctionEnabledForUser; + } else { + storage_ctx.flags[0] &= ~secmon::SecureMonitorConfigurationFlag_IsDevelopmentFunctionEnabledForUser; + } + } else if (std::strcmp(entry.key, "disable_user_exception_handlers") == 0) { + if (entry.value[0] == '1') { + storage_ctx.flags[0] |= secmon::SecureMonitorConfigurationFlag_DisableUserModeExceptionHandlers; + } else { + storage_ctx.flags[0] &= ~secmon::SecureMonitorConfigurationFlag_DisableUserModeExceptionHandlers; + } + } else if (std::strcmp(entry.key, "enable_user_pmu_access") == 0) { + if (entry.value[0] == '1') { + storage_ctx.flags[0] |= secmon::SecureMonitorConfigurationFlag_EnableUserModePerformanceCounterAccess; + } else { + storage_ctx.flags[0] &= ~secmon::SecureMonitorConfigurationFlag_EnableUserModePerformanceCounterAccess; + } + } else if (std::strcmp(entry.key, "blank_prodinfo_sysmmc") == 0) { + if (!emummc_enabled) { + if (entry.value[0] == '1') { + storage_ctx.flags[0] |= secmon::SecureMonitorConfigurationFlag_ShouldUseBlankCalibrationBinary; + } else { + storage_ctx.flags[0] &= ~secmon::SecureMonitorConfigurationFlag_ShouldUseBlankCalibrationBinary; + } + } + } else if (std::strcmp(entry.key, "blank_prodinfo_emummc") == 0) { + if (emummc_enabled) { + if (entry.value[0] == '1') { + storage_ctx.flags[0] |= secmon::SecureMonitorConfigurationFlag_ShouldUseBlankCalibrationBinary; + } else { + storage_ctx.flags[0] &= ~secmon::SecureMonitorConfigurationFlag_ShouldUseBlankCalibrationBinary; + } + } + } else if (std::strcmp(entry.key, "allow_writing_to_cal_sysmmc") == 0) { + if (entry.value[0] == '1') { + storage_ctx.flags[0] |= secmon::SecureMonitorConfigurationFlag_AllowWritingToCalibrationBinarySysmmc; + } else { + storage_ctx.flags[0] &= ~secmon::SecureMonitorConfigurationFlag_AllowWritingToCalibrationBinarySysmmc; + } + } else if (std::strcmp(entry.key, "log_port") == 0) { + const u32 log_port = ParseDecimalInteger(entry.value); + if (0 <= log_port && log_port < 4) { + storage_ctx.log_port = log_port; + } + } else if (std::strcmp(entry.key, "log_baud_rate") == 0) { + storage_ctx.log_baud_rate = ParseDecimalInteger(entry.value); + } else if (std::strcmp(entry.key, "log_inverted") == 0) { + if (entry.value[0] == '1') { + storage_ctx.log_flags |= uart::Flag_Inverted; + } + } + } + } + } + } + + /* Parse usb setting from system_settings.ini */ + { + IniSectionList sections; + if (ParseIniSafe(sections, "sdmc:/atmosphere/config/system_settings.ini")) { + for (const auto §ion : sections) { + /* We only care about the [usb] section. */ + if (std::strcmp(section.name, "usb")) { + continue; + } + + /* Handle individual fields. */ + for (const auto &entry : section.kv_list) { + if (std::strcmp(entry.key, "usb30_force_enabled") == 0) { + if (std::strcmp(entry.value, "u8!0x1") == 0) { + storage_ctx.flags[0] |= secmon::SecureMonitorConfigurationFlag_ForceEnableUsb30; + } + } + } + } + } + } + + /* Copy exosphere. */ + void *exosphere_dst = reinterpret_cast<void *>(0x40030000); + bool use_sd_exo = false; + { + /* Try to use an sd card file, if present. */ + fs::FileHandle exo_file; + if (R_SUCCEEDED(fs::OpenFile(std::addressof(exo_file), "sdmc:/atmosphere/exosphere.bin", fs::OpenMode_Read))) { + ON_SCOPE_EXIT { fs::CloseFile(exo_file); }; + + /* Note that we're using sd_exo. */ + use_sd_exo = true; + + Result result; + + /* Get the size. */ + s64 size; + if (R_FAILED((result = fs::GetFileSize(std::addressof(size), exo_file))) || size > sizeof(GetExternalPackage().exosphere)) { + ShowFatalError("Invalid SD exosphere size: 0x%08" PRIx32 ", %" PRIx64 "!\n", result.GetValue(), static_cast<u64>(size)); + } + + /* Read the file. */ + if (R_FAILED((result = fs::ReadFile(exo_file, 0, exosphere_dst, size)))) { + ShowFatalError("Failed to read SD exosphere: 0x%08" PRIx32 "!\n", result.GetValue()); + } + } + } + + if (!use_sd_exo) { + std::memcpy(exosphere_dst, GetExternalPackage().exosphere, sizeof(GetExternalPackage().exosphere)); + } + + /* Copy mariko fatal. */ + if (soc_type == fuse::SocType_Mariko) { + u8 *mariko_fatal_dst = secmon::MemoryRegionPhysicalMarikoProgramImage.GetPointer<u8>(); + bool use_sd_mariko_fatal = false; + { + /* Try to use an sd card file, if present. */ + fs::FileHandle mariko_program_file; + if (R_SUCCEEDED(fs::OpenFile(std::addressof(mariko_program_file), "sdmc:/atmosphere/mariko_fatal.bin", fs::OpenMode_Read))) { + ON_SCOPE_EXIT { fs::CloseFile(mariko_program_file); }; + + /* Note that we're using sd mariko fatal. */ + use_sd_mariko_fatal = true; + + Result result; + + /* Get the size. */ + s64 size; + if (R_FAILED((result = fs::GetFileSize(std::addressof(size), mariko_program_file))) || size > sizeof(GetExternalPackage().mariko_fatal)) { + ShowFatalError("Invalid SD mariko_fatal size: 0x%08" PRIx32 ", %" PRIx64 "!\n", result.GetValue(), static_cast<u64>(size)); + } + + /* Read the file. */ + if (R_FAILED((result = fs::ReadFile(mariko_program_file, 0, mariko_fatal_dst, size)))) { + ShowFatalError("Failed to read SD mariko_fatal: 0x%08" PRIx32 "!\n", result.GetValue()); + } + + /* Clear the remainder. */ + std::memset(mariko_fatal_dst + size, 0, sizeof(GetExternalPackage().mariko_fatal) - size); + } + } + + if (!use_sd_mariko_fatal) { + std::memcpy(mariko_fatal_dst, GetExternalPackage().mariko_fatal, sizeof(GetExternalPackage().mariko_fatal)); + } + } + + /* Setup the CPU to boot exosphere. */ + SetupCpu(reinterpret_cast<uintptr_t>(exosphere_dst)); + + /* Initialize bootloader parameters. */ + InitializeSecureMonitorMailbox(); + + /* Set our bootloader state. */ + SetBootloaderState(pkg1::BootloaderState_LoadedBootConfig); + + /* Ensure that the CPU will see consistent data. */ + hw::FlushEntireDataCache(); + } + + bool IsNogcEnabled(ams::TargetFirmware target_firmware) { + /* First parse from ini. */ + { + IniSectionList sections; + if (ParseIniSafe(sections, "sdmc:/atmosphere/config/stratosphere.ini")) { + for (const auto §ion : sections) { + /* We only care about the [stratosphere] section. */ + if (std::strcmp(section.name, "stratosphere")) { + continue; + } + + /* Handle individual fields. */ + for (const auto &entry : section.kv_list) { + if (std::strcmp(entry.key, "nogc") == 0) { + return entry.value[0] == '1'; + } + } + } + } + } + + /* That failed, so try to decide automatically. */ + const auto fuse_version = fuse::GetFuseVersion(); + if (target_firmware >= ams::TargetFirmware_12_0_2 && fuse_version < fuse::GetExpectedFuseVersion(ams::TargetFirmware_12_0_2)) { + return true; + } + if (target_firmware >= ams::TargetFirmware_11_0_0 && fuse_version < fuse::GetExpectedFuseVersion(ams::TargetFirmware_11_0_0)) { + return true; + } + if (target_firmware >= ams::TargetFirmware_9_0_0 && fuse_version < fuse::GetExpectedFuseVersion(ams::TargetFirmware_9_0_0)) { + return true; + } + if (target_firmware >= ams::TargetFirmware_4_0_0 && fuse_version < fuse::GetExpectedFuseVersion(ams::TargetFirmware_4_0_0)) { + return true; + } + + return false; + } + + } + + void SetupAndStartHorizon() { + /* Get soc type. */ + const auto soc_type = fuse::GetSocType(); + + /* Derive all keys. */ + DeriveAllKeys(soc_type); + + /* Determine whether we're using emummc. */ + const bool emummc_enabled = ConfigureEmummc(); + + /* Initialize emummc. */ + /* NOTE: SYSTEM:/ accessible past this point. */ + InitializeEmummc(emummc_enabled, g_emummc_cfg); + + /* Read bootloader. */ + const u8 * const package1 = LoadPackage1(soc_type); + + /* Get target firmware. */ + const auto target_firmware = GetApproximateTargetFirmware(package1); + + /* Read/decrypt package2. */ + u8 * const package2 = LoadBootConfigAndPackage2(); + + /* Setup warmboot firmware. */ + LoadWarmbootFirmware(soc_type, target_firmware, package1); + + /* Decide whether to use nogc patches. */ + const bool nogc_enabled = IsNogcEnabled(target_firmware); + + /* Decide what KIPs/patches we're loading. */ + const auto fs_version = ConfigureStratosphere(package2, target_firmware, emummc_enabled, nogc_enabled); + + /* Setup exosphere. */ + ConfigureExosphere(soc_type, target_firmware, emummc_enabled, fs_version); + + /* Start CPU. */ + StartCpu(); + + /* Build modified package2. */ + RebuildPackage2(target_firmware, emummc_enabled); + + /* Wait for confirmation that exosphere is ready. */ + WaitSecureMonitorState(pkg1::SecureMonitorState_Initialized); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_setup_horizon.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_setup_horizon.hpp new file mode 100644 index 00000000..2a64a9a0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_setup_horizon.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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#pragma once + +namespace ams::nxboot { + + void SetupAndStartHorizon(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_start.s b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_start.s new file mode 100644 index 00000000..e27f912b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_start.s @@ -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 <http://www.gnu.org/licenses/>. + */ + +.section .crt0._ZN3ams6nxboot5StartEv, "ax", %progbits +.arm +.align 5 +.global _ZN3ams6nxboot5StartEv +.type _ZN3ams6nxboot5StartEv, %function +_ZN3ams6nxboot5StartEv: + /* Setup all registers as = 0. */ + msr cpsr_f, #0xC0 + mov r0, #0 + mov r1, #0 + mov r2, #0 + mov r3, #0 + mov r4, #0 + mov r5, #0 + mov r6, #0 + mov r7, #0 + mov r8, #0 + mov r9, #0 + mov r10, #0 + mov r11, #0 + mov r12, #0 + + /* Setup all modes as pointing to our exception handler. */ + msr cpsr_cf, #0xDF + ldr sp, =0x40001000 + ldr lr, =_ZN3ams6nxboot16ExceptionHandlerEv + + msr cpsr_cf, #0xD2 + ldr sp, =0x40001000 + ldr lr, =_ZN3ams6nxboot16ExceptionHandlerEv + + msr cpsr_cf, #0xD1 + ldr sp, =0x40001000 + ldr lr, =_ZN3ams6nxboot16ExceptionHandlerEv + + msr cpsr_cf, #0xD7 + ldr sp, =0x40001000 + ldr lr, =_ZN3ams6nxboot16ExceptionHandlerEv + + msr cpsr_cf, #0xDB + ldr sp, =0x40001000 + ldr lr, =_ZN3ams6nxboot16ExceptionHandlerEv + + msr cpsr_cf, #0xD3 + ldr sp, =0x40001000 + ldr lr, =_ZN3ams6nxboot16ExceptionHandlerEv + + /* Perform runtime initialization. */ + bl _ZN3ams6nxboot4crt010InitializeEv + + /* Perform nx boot procedure. */ + bl _ZN3ams6nxboot4MainEv diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_stratosphere.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_stratosphere.cpp new file mode 100644 index 00000000..34e88dfb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_stratosphere.cpp @@ -0,0 +1,1117 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_stratosphere.hpp" +#include "fusee_fatal.hpp" +#include "fusee_malloc.hpp" +#include "fusee_external_package.hpp" +#include "fs/fusee_fs_api.hpp" + +namespace ams::nxboot { + + namespace { + + constexpr u32 MesoshereMetadataLayout0Magic = util::FourCC<'M','S','S','0'>::Code; + constexpr u32 MesoshereMetadataLayout1Magic = util::FourCC<'M','S','S','1'>::Code; + + struct InitialProcessBinaryHeader { + static constexpr u32 Magic = util::FourCC<'I','N','I','1'>::Code; + + u32 magic; + u32 size; + u32 num_processes; + u32 reserved; + }; + + struct InitialProcessHeader { + static constexpr u32 Magic = util::FourCC<'K','I','P','1'>::Code; + + u32 magic; + u8 name[12]; + u64 program_id; + u32 version; + u8 priority; + u8 ideal_core_id; + u8 _1E; + u8 flags; + u32 rx_address; + u32 rx_size; + u32 rx_compressed_size; + u32 affinity_mask; + u32 ro_address; + u32 ro_size; + u32 ro_compressed_size; + u32 stack_size; + u32 rw_address; + u32 rw_size; + u32 rw_compressed_size; + u32 _4C; + u32 bss_address; + u32 bss_size; + u32 pad[(0x80 - 0x58) / sizeof(u32)]; + u32 capabilities[0x80 / sizeof(u32)]; + }; + static_assert(sizeof(InitialProcessHeader) == 0x100); + + struct PatchMeta { + PatchMeta *next; + bool is_memset; + u32 start_segment; + u32 rel_offset; + const void *data; + u32 size; + }; + + struct alignas(0x10) InitialProcessMeta { + InitialProcessMeta *next = nullptr; + const InitialProcessHeader *kip; + u32 kip_size; + PatchMeta *patches_head; + PatchMeta *patches_tail; + u32 patch_segments; + u64 program_id; + se::Sha256Hash kip_hash; + }; + static_assert(sizeof(InitialProcessMeta) == 0x40); + static_assert(alignof(InitialProcessMeta) == 0x10); + + constexpr inline const u64 FsProgramId = 0x0100000000000000; + + enum FsVersion { + FsVersion_1_0_0 = 0, + + FsVersion_2_0_0, + FsVersion_2_0_0_Exfat, + + FsVersion_2_1_0, + FsVersion_2_1_0_Exfat, + + FsVersion_3_0_0, + FsVersion_3_0_0_Exfat, + + FsVersion_3_0_1, + FsVersion_3_0_1_Exfat, + + FsVersion_4_0_0, + FsVersion_4_0_0_Exfat, + + FsVersion_4_1_0, + FsVersion_4_1_0_Exfat, + + FsVersion_5_0_0, + FsVersion_5_0_0_Exfat, + + FsVersion_5_1_0, + FsVersion_5_1_0_Exfat, + + FsVersion_6_0_0, + FsVersion_6_0_0_Exfat, + + FsVersion_7_0_0, + FsVersion_7_0_0_Exfat, + + FsVersion_8_0_0, + FsVersion_8_0_0_Exfat, + + FsVersion_8_1_0, + FsVersion_8_1_0_Exfat, + + FsVersion_9_0_0, + FsVersion_9_0_0_Exfat, + + FsVersion_9_1_0, + FsVersion_9_1_0_Exfat, + + FsVersion_10_0_0, + FsVersion_10_0_0_Exfat, + + FsVersion_10_2_0, + FsVersion_10_2_0_Exfat, + + FsVersion_11_0_0, + FsVersion_11_0_0_Exfat, + + FsVersion_12_0_0, + FsVersion_12_0_0_Exfat, + + FsVersion_12_0_3, + FsVersion_12_0_3_Exfat, + + FsVersion_13_0_0, + FsVersion_13_0_0_Exfat, + + FsVersion_13_1_0, + FsVersion_13_1_0_Exfat, + + FsVersion_14_0_0, + FsVersion_14_0_0_Exfat, + + FsVersion_15_0_0, + FsVersion_15_0_0_Exfat, + + FsVersion_16_0_0, + FsVersion_16_0_0_Exfat, + + FsVersion_16_0_3, + FsVersion_16_0_3_Exfat, + + FsVersion_17_0_0, + FsVersion_17_0_0_Exfat, + + FsVersion_18_0_0, + FsVersion_18_0_0_Exfat, + + FsVersion_18_1_0, + FsVersion_18_1_0_Exfat, + + FsVersion_19_0_0, + FsVersion_19_0_0_Exfat, + + FsVersion_20_0_0, + FsVersion_20_0_0_Exfat, + + FsVersion_20_1_0, + FsVersion_20_1_0_Exfat, + + FsVersion_Count, + }; + + constexpr const u8 FsHashes[FsVersion_Count][8] = { + { 0xDE, 0x9F, 0xDD, 0xA4, 0x08, 0x5D, 0xD5, 0xFE }, /* FsVersion_1_0_0 */ + + { 0xCD, 0x7B, 0xBE, 0x18, 0xD6, 0x13, 0x0B, 0x28 }, /* FsVersion_2_0_0 */ + { 0xE7, 0x66, 0x92, 0xDF, 0xAA, 0x04, 0x20, 0xE9 }, /* FsVersion_2_0_0_Exfat */ + + { 0x0D, 0x70, 0x05, 0x62, 0x7B, 0x07, 0x76, 0x7C }, /* FsVersion_2_1_0 */ + { 0xDB, 0xD8, 0x5F, 0xCA, 0xCC, 0x19, 0x3D, 0xA8 }, /* FsVersion_2_1_0_Exfat */ + + { 0xA8, 0x6D, 0xA5, 0xE8, 0x7E, 0xF1, 0x09, 0x7B }, /* FsVersion_3_0_0 */ + { 0x98, 0x1C, 0x57, 0xE7, 0xF0, 0x2F, 0x70, 0xF7 }, /* FsVersion_3_0_0_Exfat */ + + { 0x57, 0x39, 0x7C, 0x06, 0x3F, 0x10, 0xB6, 0x31 }, /* FsVersion_3_0_1 */ + { 0x07, 0x30, 0x99, 0xD7, 0xC6, 0xAD, 0x7D, 0x89 }, /* FsVersion_3_0_1_Exfat */ + + { 0x06, 0xE9, 0x07, 0x19, 0x59, 0x5A, 0x01, 0x0C }, /* FsVersion_4_0_0 */ + { 0x54, 0x9B, 0x0F, 0x8D, 0x6F, 0x72, 0xC4, 0xE9 }, /* FsVersion_4_0_0_Exfat */ + + { 0x80, 0x96, 0xAF, 0x7C, 0x6A, 0x35, 0xAA, 0x82 }, /* FsVersion_4_1_0 */ + { 0x02, 0xD5, 0xAB, 0xAA, 0xFD, 0x20, 0xC8, 0xB0 }, /* FsVersion_4_1_0_Exfat */ + + { 0xA6, 0xF2, 0x7A, 0xD9, 0xAC, 0x7C, 0x73, 0xAD }, /* FsVersion_5_0_0 */ + { 0xCE, 0x3E, 0xCB, 0xA2, 0xF2, 0xF0, 0x62, 0xF5 }, /* FsVersion_5_0_0_Exfat */ + + { 0x76, 0xF8, 0x74, 0x02, 0xC9, 0x38, 0x7C, 0x0F }, /* FsVersion_5_1_0 */ + { 0x10, 0xB2, 0xD8, 0x16, 0x05, 0x48, 0x85, 0x99 }, /* FsVersion_5_1_0_Exfat */ + + { 0x3A, 0x57, 0x4D, 0x43, 0x61, 0x86, 0x19, 0x1D }, /* FsVersion_6_0_0 */ + { 0x33, 0x05, 0x53, 0xF6, 0xB5, 0xFB, 0x55, 0xC4 }, /* FsVersion_6_0_0_Exfat */ + + { 0x2A, 0xDB, 0xE9, 0x7E, 0x9B, 0x5F, 0x41, 0x77 }, /* FsVersion_7_0_0 */ + { 0x2C, 0xCE, 0x65, 0x9C, 0xEC, 0x53, 0x6A, 0x8E }, /* FsVersion_7_0_0_Exfat */ + + { 0xB2, 0xF5, 0x17, 0x6B, 0x35, 0x48, 0x36, 0x4D }, /* FsVersion_8_0_0 */ + { 0xDB, 0xD9, 0x41, 0xC0, 0xC5, 0x3C, 0x52, 0xCC }, /* FsVersion_8_0_0_Exfat */ + + { 0x6B, 0x09, 0xB6, 0x7B, 0x29, 0xC0, 0x20, 0x24 }, /* FsVersion_8_1_0 */ + { 0xB4, 0xCA, 0xE1, 0xF2, 0x49, 0x65, 0xD9, 0x2E }, /* FsVersion_8_1_0_Exfat */ + + { 0x46, 0x87, 0x40, 0x76, 0x1E, 0x19, 0x3E, 0xB7 }, /* FsVersion_9_0_0 */ + { 0x7C, 0x95, 0x13, 0x76, 0xE5, 0xC1, 0x2D, 0xF8 }, /* FsVersion_9_0_0_Exfat */ + + { 0xB5, 0xE7, 0xA6, 0x4C, 0x6F, 0x5C, 0x4F, 0xE3 }, /* FsVersion_9_1_0 */ + { 0xF1, 0x96, 0xD1, 0x44, 0xD0, 0x44, 0x45, 0xB6 }, /* FsVersion_9_1_0_Exfat */ + + { 0x3E, 0xEB, 0xD9, 0xB7, 0xBC, 0xD1, 0xB5, 0xE0 }, /* FsVersion_10_0_0 */ + { 0x81, 0x7E, 0xA2, 0xB0, 0xB7, 0x02, 0xC1, 0xF3 }, /* FsVersion_10_0_0_Exfat */ + + { 0xA9, 0x52, 0xB6, 0x57, 0xAD, 0xF9, 0xC2, 0xBA }, /* FsVersion_10_2_0 */ + { 0x16, 0x0D, 0x3E, 0x10, 0x4E, 0xAD, 0x61, 0x76 }, /* FsVersion_10_2_0_Exfat */ + + { 0xE3, 0x99, 0x15, 0x6E, 0x84, 0x4E, 0xB0, 0xAA }, /* FsVersion_11_0_0 */ + { 0x0B, 0xA1, 0x5B, 0xB3, 0x04, 0xB5, 0x05, 0x63 }, /* FsVersion_11_0_0_Exfat */ + + { 0xDC, 0x2A, 0x08, 0x49, 0x96, 0xBB, 0x3C, 0x01 }, /* FsVersion_12_0_0 */ + { 0xD5, 0xA5, 0xBF, 0x36, 0x64, 0x0C, 0x49, 0xEA }, /* FsVersion_12_0_0_Exfat */ + + { 0xC8, 0x67, 0x62, 0xBE, 0x19, 0xA5, 0x1F, 0xA0 }, /* FsVersion_12_0_3 */ + { 0xE1, 0xE8, 0xD3, 0xD6, 0xA2, 0xFE, 0x0B, 0x10 }, /* FsVersion_12_0_3_Exfat */ + + { 0x7D, 0x20, 0x05, 0x47, 0x17, 0x8A, 0x83, 0x6A }, /* FsVersion_13_0_0 */ + { 0x51, 0xEB, 0xFA, 0x9C, 0xCF, 0x66, 0xC0, 0x9E }, /* FsVersion_13_0_0_Exfat */ + + { 0x91, 0xBA, 0x65, 0xA2, 0x1C, 0x1D, 0x50, 0xAE }, /* FsVersion_13_1_0 */ + { 0x76, 0x38, 0x27, 0xEE, 0x9C, 0x20, 0x7E, 0x5B }, /* FsVersion_13_1_0_Exfat */ + + { 0x88, 0x7A, 0xC1, 0x50, 0x80, 0x6C, 0x75, 0xCC }, /* FsVersion_14_0_0 */ + { 0xD4, 0x88, 0xD1, 0xF2, 0x92, 0x17, 0x35, 0x5C }, /* FsVersion_14_0_0_Exfat */ + + { 0xD0, 0xD4, 0x49, 0x18, 0x14, 0xB5, 0x62, 0xAF }, /* FsVersion_15_0_0 */ + { 0x34, 0xC0, 0xD9, 0xED, 0x6A, 0xD1, 0x87, 0x3D }, /* FsVersion_15_0_0_Exfat */ + + { 0x56, 0xE8, 0x56, 0x56, 0x6C, 0x38, 0xD8, 0xBE }, /* FsVersion_16_0_0 */ + { 0xCF, 0xAB, 0x45, 0x0C, 0x2C, 0x53, 0x9D, 0xA9 }, /* FsVersion_16_0_0_Exfat */ + + { 0x39, 0xEE, 0x1F, 0x1E, 0x0E, 0xA7, 0x32, 0x5D }, /* FsVersion_16_0_3 */ + { 0x62, 0xC6, 0x5E, 0xFD, 0x9A, 0xBF, 0x7C, 0x43 }, /* FsVersion_16_0_3_Exfat */ + + { 0x27, 0x07, 0x3B, 0xF0, 0xA1, 0xB8, 0xCE, 0x61 }, /* FsVersion_17_0_0 */ + { 0xEE, 0x0F, 0x4B, 0xAC, 0x6D, 0x1F, 0xFC, 0x4B }, /* FsVersion_17_0_0_Exfat */ + + { 0x79, 0x5F, 0x5A, 0x5E, 0xB0, 0xC6, 0x77, 0x9E }, /* FsVersion_18_0_0 */ + { 0x1E, 0x2C, 0x64, 0xB1, 0xCC, 0xE2, 0x78, 0x24 }, /* FsVersion_18_0_0_Exfat */ + + { 0xA3, 0x39, 0xF0, 0x1C, 0x95, 0xBF, 0xA7, 0x68 }, /* FsVersion_18_1_0 */ + { 0x20, 0x4C, 0xBA, 0x86, 0xDE, 0x08, 0x44, 0x6A }, /* FsVersion_18_1_0_Exfat */ + + { 0xD9, 0x4C, 0x68, 0x15, 0xF8, 0xF5, 0x0A, 0x20 }, /* FsVersion_19_0_0 */ + { 0xED, 0xA8, 0x78, 0x68, 0xA4, 0x49, 0x07, 0x50 }, /* FsVersion_19_0_0_Exfat */ + + { 0x63, 0x54, 0x96, 0x9E, 0x60, 0xA7, 0x97, 0x7B }, /* FsVersion_20_0_0 */ + { 0x47, 0x41, 0x07, 0x10, 0x65, 0x4F, 0xA4, 0x3F }, /* FsVersion_20_0_0_Exfat */ + + { 0xED, 0x34, 0xB4, 0x50, 0x58, 0x4A, 0x5B, 0x43 }, /* FsVersion_20_1_0 */ + { 0xA5, 0x1A, 0xA4, 0x92, 0x6C, 0x41, 0x87, 0x59 }, /* FsVersion_20_1_0_Exfat */ + }; + + const InitialProcessBinaryHeader *FindInitialProcessBinary(const pkg2::Package2Header *header, const u8 *data, ams::TargetFirmware target_firmware) { + if (target_firmware >= ams::TargetFirmware_17_0_0) { + const u32 *data_32 = reinterpret_cast<const u32 *>(data); + const u32 branch_target = (data_32[0] & 0x00FFFFFF); + for (size_t i = branch_target; i < branch_target + 0x1000 / sizeof(u32); ++i) { + const u32 ini_offset = (i * sizeof(u32)) + data_32[i]; + if (data_32[i + 1] == 0 && ini_offset <= header->meta.payload_sizes[0] && std::memcmp(data + ini_offset, "INI1", 4) == 0) { + return reinterpret_cast<const InitialProcessBinaryHeader *>(data + ini_offset); + } + } + + return nullptr; + } else if (target_firmware >= ams::TargetFirmware_8_0_0) { + /* Try to find initial process binary. */ + const u32 *data_32 = reinterpret_cast<const u32 *>(data); + for (size_t i = 0; i < 0x1000 / sizeof(u32); ++i) { + if (data_32[i] == 0 && data_32[i + 8] <= header->meta.payload_sizes[0] && std::memcmp(data + data_32[i + 8], "INI1", 4) == 0) { + return reinterpret_cast<const InitialProcessBinaryHeader *>(data + data_32[i + 8]); + } + } + + return nullptr; + } else { + return reinterpret_cast<const InitialProcessBinaryHeader *>(data + header->meta.payload_sizes[0]); + } + } + + constexpr size_t GetInitialProcessSize(const InitialProcessHeader *kip) { + return sizeof(*kip) + kip->rx_compressed_size + kip->ro_compressed_size + kip->rw_compressed_size; + } + + const InitialProcessHeader *FindInitialProcessInBinary(const InitialProcessBinaryHeader *ini, u64 program_id) { + const u8 *data = reinterpret_cast<const u8 *>(ini + 1); + for (u32 i = 0; i < ini->num_processes; ++i) { + const InitialProcessHeader *kip = reinterpret_cast<const InitialProcessHeader *>(data); + if (kip->magic != InitialProcessHeader::Magic) { + return nullptr; + } + + if (kip->program_id == program_id) { + return kip; + } + + data += GetInitialProcessSize(kip); + } + + return nullptr; + } + + FsVersion GetFsVersion(const se::Sha256Hash &fs_hash) { + for (size_t i = 0; i < util::size(FsHashes); ++i) { + if (std::memcmp(fs_hash.bytes, FsHashes[i], sizeof(FsHashes[i])) == 0) { + return static_cast<FsVersion>(i); + } + } + + return FsVersion_Count; + } + + constinit InitialProcessMeta g_initial_process_meta = {}; + constinit size_t g_initial_process_binary_size = 0; + + void AddInitialProcessImpl(InitialProcessMeta *meta, const InitialProcessHeader *kip, const se::Sha256Hash *hash) { + /* Set the meta's fields. */ + meta->next = nullptr; + meta->program_id = kip->program_id; + meta->kip = kip; + meta->kip_size = GetInitialProcessSize(kip); + + /* Copy or calculate hash. */ + if (hash != nullptr) { + std::memcpy(std::addressof(meta->kip_hash), hash, sizeof(meta->kip_hash)); + } else { + se::CalculateSha256(std::addressof(meta->kip_hash), kip, meta->kip_size); + } + + /* Clear patches. */ + meta->patches_head = nullptr; + meta->patches_tail = nullptr; + meta->patch_segments = 0; + + /* Increase the initial process binary's size. */ + g_initial_process_binary_size += meta->kip_size; + } + + bool AddInitialProcess(const InitialProcessHeader *kip, const se::Sha256Hash *hash = nullptr) { + /* Check kip magic. */ + if (kip->magic != InitialProcessHeader::Magic) { + ShowFatalError("KIP seems corrupted!\n"); + } + + /* Handle the initial case. */ + if (g_initial_process_binary_size == 0) { + AddInitialProcessImpl(std::addressof(g_initial_process_meta), kip, hash); + return true; + } + + /* Check if we've already added the program id. */ + InitialProcessMeta *cur = std::addressof(g_initial_process_meta); + while (true) { + if (cur->program_id == kip->program_id) { + return false; + } + + if (cur->next != nullptr) { + cur = cur->next; + } else { + break; + } + } + + /* Allocate an initial process meta. */ + auto *new_meta = static_cast<InitialProcessMeta *>(AllocateAligned(sizeof(InitialProcessMeta), alignof(InitialProcessMeta))); + + /* Insert the new meta. */ + cur->next = new_meta; + AddInitialProcessImpl(new_meta, kip, hash); + return true; + } + + InitialProcessMeta *FindInitialProcess(u64 program_id) { + for (InitialProcessMeta *cur = std::addressof(g_initial_process_meta); cur != nullptr; cur = cur->next) { + if (cur->program_id == program_id) { + return cur; + } + } + return nullptr; + } + + u32 GetPatchSegments(const InitialProcessHeader *kip, u32 offset, size_t size) { + /* Create segment mask. */ + u32 segments = 0; + + /* Get the segment extents. */ + const u32 rx_start = kip->rx_address; + const u32 ro_start = kip->ro_address; + const u32 rw_start = kip->rw_address; + const u32 rx_end = ro_start; + const u32 ro_end = rw_start; + const u32 rw_end = rw_start + kip->rw_size; + + /* If the offset is below the kip header, ignore it. */ + if (offset < sizeof(*kip)) { + return segments; + } + + /* Adjust the offset in bounds. */ + offset -= sizeof(*kip); + + /* Check if the offset strays out of bounds. */ + if (offset + size > rw_end) { + return segments; + } + + /* Set bits for the affected segments. */ + if (util::HasOverlap(offset, size, rx_start, rx_end - rx_start)) { + segments |= (1 << 0); + } + if (util::HasOverlap(offset, size, ro_start, ro_end - ro_start)) { + segments |= (1 << 1); + } + if (util::HasOverlap(offset, size, rw_start, rw_end - rw_start)) { + segments |= (1 << 2); + } + + return segments; + } + + void AddPatch(InitialProcessMeta *meta, u32 offset, const void *data, size_t data_size, bool is_memset = false) { + /* Determine the segment. */ + const u32 segments = GetPatchSegments(meta->kip, offset, data_size); + + /* If the patch hits no segments, we don't need it. */ + if (segments == 0) { + return; + } + + /* Update patch segments. */ + meta->patch_segments |= segments; + + /* Adjust offset. */ + const u32 start_segment = util::CountTrailingZeros(segments); + offset -= sizeof(*meta->kip); + switch (start_segment) { + case 0: offset -= meta->kip->rx_address; break; + case 1: offset -= meta->kip->ro_address; break; + case 2: offset -= meta->kip->rw_address; break; + } + + /* Create patch. */ + auto *new_patch = static_cast<PatchMeta *>(AllocateAligned(sizeof(PatchMeta), alignof(PatchMeta))); + + new_patch->next = nullptr; + new_patch->is_memset = is_memset; + new_patch->start_segment = start_segment; + new_patch->rel_offset = offset; + new_patch->data = data; + new_patch->size = data_size; + + /* Add the patch. */ + if (meta->patches_head == nullptr) { + meta->patches_head = new_patch; + } else { + meta->patches_tail->next = new_patch; + } + + meta->patches_tail = new_patch; + } + + constexpr const u8 NogcPatch0[] = { + 0x80 + }; + + constexpr const u8 NogcPatch1[] = { + 0xE0, 0x03, 0x1F, 0x2A, 0xC0, 0x03, 0x5F, 0xD6, + }; + + void AddNogcPatches(InitialProcessMeta *fs_meta, FsVersion fs_version) { + switch (fs_version) { + case FsVersion_1_0_0: + case FsVersion_2_0_0: + case FsVersion_2_0_0_Exfat: + case FsVersion_2_1_0: + case FsVersion_2_1_0_Exfat: + case FsVersion_3_0_0: + case FsVersion_3_0_0_Exfat: + case FsVersion_3_0_1: + case FsVersion_3_0_1_Exfat: + /* There were no lotus firmware updates prior to 4.0.0. */ + /* TODO: Implement patches, regardless? */ + break; + case FsVersion_4_0_0: + case FsVersion_4_0_0_Exfat: + AddPatch(fs_meta, 0x0A3539, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x0AAC44, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_4_1_0: + case FsVersion_4_1_0_Exfat: + AddPatch(fs_meta, 0x0A35BD, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x0AACA8, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_5_0_0: + case FsVersion_5_0_0_Exfat: + AddPatch(fs_meta, 0x0CF4C5, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x0D74A0, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_5_1_0: + case FsVersion_5_1_0_Exfat: + AddPatch(fs_meta, 0x0CF895, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x0D7870, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_6_0_0: + AddPatch(fs_meta, 0x1539F5, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x12CD20, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_6_0_0_Exfat: + AddPatch(fs_meta, 0x15F0F5, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x138420, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_7_0_0: + AddPatch(fs_meta, 0x15C005, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x134260, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_7_0_0_Exfat: + AddPatch(fs_meta, 0x1675B5, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x13F810, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_8_0_0: + case FsVersion_8_1_0: + AddPatch(fs_meta, 0x15EC95, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x136900, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_8_0_0_Exfat: + case FsVersion_8_1_0_Exfat: + AddPatch(fs_meta, 0x16A245, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x141EB0, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_9_0_0: + case FsVersion_9_0_0_Exfat: + AddPatch(fs_meta, 0x143369, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x129520, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_9_1_0: + case FsVersion_9_1_0_Exfat: + AddPatch(fs_meta, 0x143379, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x129530, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_10_0_0: + case FsVersion_10_0_0_Exfat: + AddPatch(fs_meta, 0x14DF09, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x13BF90, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_10_2_0: + case FsVersion_10_2_0_Exfat: + AddPatch(fs_meta, 0x14E369, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x13C3F0, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_11_0_0: + case FsVersion_11_0_0_Exfat: + AddPatch(fs_meta, 0x156FB9, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x1399B4, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_12_0_0: + case FsVersion_12_0_0_Exfat: + AddPatch(fs_meta, 0x155469, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x13EB24, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_12_0_3: + case FsVersion_12_0_3_Exfat: + AddPatch(fs_meta, 0x155579, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x13EC34, NogcPatch1, sizeof(NogcPatch1)); + case FsVersion_13_0_0: + case FsVersion_13_0_0_Exfat: + AddPatch(fs_meta, 0x159119, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x1426D0, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_13_1_0: + case FsVersion_13_1_0_Exfat: + AddPatch(fs_meta, 0x1590B9, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x142670, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_14_0_0: + AddPatch(fs_meta, 0x18A3E9, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x164330, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_14_0_0_Exfat: + AddPatch(fs_meta, 0x195769, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x16F6B0, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_15_0_0: + AddPatch(fs_meta, 0x184259, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x15EDE4, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_15_0_0_Exfat: + AddPatch(fs_meta, 0x18F1E9, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x169D74, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_16_0_0: + AddPatch(fs_meta, 0x1866D9, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x160C70, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_16_0_0_Exfat: + AddPatch(fs_meta, 0x1913B9, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x16B950, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_16_0_3: + AddPatch(fs_meta, 0x186729, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x160CC0, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_16_0_3_Exfat: + AddPatch(fs_meta, 0x191409, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x16B9A0, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_17_0_0: + AddPatch(fs_meta, 0x18B149, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x165200, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_17_0_0_Exfat: + AddPatch(fs_meta, 0x195FA9, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x170060, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_18_0_0: + AddPatch(fs_meta, 0x18AF49, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x164B50, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_18_0_0_Exfat: + AddPatch(fs_meta, 0x195FD9, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x16FBE0, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_18_1_0: + AddPatch(fs_meta, 0x18AF49, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x164B50, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_18_1_0_Exfat: + AddPatch(fs_meta, 0x195FD9, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x16FBE0, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_19_0_0: + AddPatch(fs_meta, 0x195C75, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x195E75, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x16F170, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_19_0_0_Exfat: + AddPatch(fs_meta, 0x1A14A5, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x1A16A5, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x17A9A0, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_20_0_0: + case FsVersion_20_1_0: + AddPatch(fs_meta, 0x1A7E25, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x1A8025, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x17C250, NogcPatch1, sizeof(NogcPatch1)); + break; + case FsVersion_20_0_0_Exfat: + case FsVersion_20_1_0_Exfat: + AddPatch(fs_meta, 0x1B3745, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x1B3945, NogcPatch0, sizeof(NogcPatch0)); + AddPatch(fs_meta, 0x187B70, NogcPatch1, sizeof(NogcPatch1)); + break; + default: + break; + } + } + + struct BlzSegmentFlags { + using Offset = util::BitPack16::Field<0, 12, u32>; + using Size = util::BitPack16::Field<Offset::Next, 4, u32>; + }; + + void BlzUncompress(void *_end) { + /* Parse the footer, endian agnostic. */ + static_assert(sizeof(u32) == 4); + static_assert(sizeof(u16) == 2); + static_assert(sizeof(u8) == 1); + + u8 *end = static_cast<u8 *>(_end); + const u32 total_size = (end[-12] << 0) | (end[-11] << 8) | (end[-10] << 16) | (end[- 9] << 24); + const u32 footer_size = (end[- 8] << 0) | (end[- 7] << 8) | (end[- 6] << 16) | (end[- 5] << 24); + const u32 additional_size = (end[- 4] << 0) | (end[- 3] << 8) | (end[- 2] << 16) | (end[- 1] << 24); + + /* Prepare to decompress. */ + u8 *cmp_start = end - total_size; + u32 cmp_ofs = total_size - footer_size; + u32 out_ofs = total_size + additional_size; + + /* Decompress. */ + while (out_ofs) { + u8 control = cmp_start[--cmp_ofs]; + + /* Each bit in the control byte is a flag indicating compressed or not compressed. */ + for (size_t i = 0; i < 8 && out_ofs; ++i, control <<= 1) { + if (control & 0x80) { + /* NOTE: Nintendo does not check if it's possible to decompress. */ + /* As such, we will leave the following as a debug assertion, and not a release assertion. */ + AMS_AUDIT(cmp_ofs >= sizeof(u16)); + cmp_ofs -= sizeof(u16); + + /* Extract segment bounds. */ + const util::BitPack16 seg_flags{static_cast<u16>((cmp_start[cmp_ofs] << 0) | (cmp_start[cmp_ofs + 1] << 8))}; + const u32 seg_ofs = seg_flags.Get<BlzSegmentFlags::Offset>() + 3; + const u32 seg_size = std::min(seg_flags.Get<BlzSegmentFlags::Size>() + 3, out_ofs); + AMS_AUDIT(out_ofs + seg_ofs <= total_size + additional_size); + + /* Copy the data. */ + out_ofs -= seg_size; + for (size_t j = 0; j < seg_size; j++) { + cmp_start[out_ofs + j] = cmp_start[out_ofs + seg_ofs + j]; + } + } else { + /* NOTE: Nintendo does not check if it's possible to copy. */ + /* As such, we will leave the following as a debug assertion, and not a release assertion. */ + AMS_AUDIT(cmp_ofs >= sizeof(u8)); + cmp_start[--out_ofs] = cmp_start[--cmp_ofs]; + } + } + } + } + + void *ReadFile(s64 *out_size, const char *path, size_t align = 0x10) { + fs::FileHandle file; + if (R_SUCCEEDED(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Read))) { + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + Result result; + + /* Get the kip size. */ + if (R_FAILED((result = fs::GetFileSize(out_size, file)))) { + ShowFatalError("Failed to get size (0x%08" PRIx32 ") of %s!\n", result.GetValue(), path); + } + + /* Allocate file. */ + void *data = AllocateAligned(*out_size, std::max<size_t>(align, 0x10)); + + /* Read the file. */ + if (R_FAILED((result = fs::ReadFile(file, 0, data, *out_size)))) { + ShowFatalError("Failed to read (0x%08" PRIx32 ") %s!\n", result.GetValue(), path); + } + + return data; + } else { + return nullptr; + } + } + + } + + u32 ConfigureStratosphere(const u8 *nn_package2, ams::TargetFirmware target_firmware, bool emummc_enabled, bool nogc_enabled) { + /* Load KIPs off the SD card. */ + { + /* Create kip dir path. */ + char kip_path[0x120]; + std::memcpy(kip_path, "sdmc:/atmosphere/kips", 0x16); + + fs::DirectoryHandle kip_dir; + if (R_SUCCEEDED(fs::OpenDirectory(std::addressof(kip_dir), kip_path))) { + ON_SCOPE_EXIT { fs::CloseDirectory(kip_dir); }; + + s64 count; + fs::DirectoryEntry entries[1]; + while (R_SUCCEEDED(fs::ReadDirectory(std::addressof(count), entries, kip_dir, util::size(entries))) && count > 0) { + /* Check that file is a file. */ + if (fs::GetEntryType(entries[0]) != fs::DirectoryEntryType_File) { + continue; + } + + /* Get filename length. */ + const int name_len = std::strlen(entries[0].file_name); + + /* Adjust kip path. */ + kip_path[0x15] = '/'; + std::memcpy(kip_path + 0x16, entries[0].file_name, name_len + 1); + + /* Check that file is ".kip" or ".kip1" file. */ + const int path_len = 0x16 + name_len; + if (std::memcmp(kip_path + path_len - 4, ".kip", 5) != 0 && std::memcmp(kip_path + path_len - 5, ".kip1", 6) != 0) { + continue; + } + + /* Read the kip. */ + s64 file_size; + if (InitialProcessHeader *kip = static_cast<InitialProcessHeader *>(ReadFile(std::addressof(file_size), kip_path, alignof(InitialProcessHeader))); kip != nullptr) { + /* If the kip is valid, add it. */ + if (kip->magic == InitialProcessHeader::Magic && file_size == GetInitialProcessSize(kip)) { + AddInitialProcess(kip); + } + } + } + } + } + + /* Add the stratosphere kips. */ + { + const auto &external_package = GetExternalPackage(); + for (u32 i = 0; i < external_package.header.num_kips; ++i) { + const auto &meta = external_package.header.kip_metas[i]; + + AddInitialProcess(reinterpret_cast<const InitialProcessHeader *>(external_package.kips + meta.offset), std::addressof(meta.hash)); + } + } + + /* Get meta for FS process. */ + auto *fs_meta = FindInitialProcess(FsProgramId); + if (fs_meta == nullptr) { + /* Get nintendo header/data. */ + const pkg2::Package2Header *nn_header = reinterpret_cast<const pkg2::Package2Header *>(nn_package2); + const u8 *nn_data = nn_package2 + sizeof(*nn_header); + + /* Get Nintendo INI1. */ + const InitialProcessBinaryHeader *nn_ini = FindInitialProcessBinary(nn_header, nn_data, target_firmware); + if (nn_ini == nullptr || nn_ini->magic != InitialProcessBinaryHeader::Magic) { + ShowFatalError("Failed to find INI1!\n"); + } + + /* Find FS KIP. */ + const InitialProcessHeader *nn_fs_kip = FindInitialProcessInBinary(nn_ini, FsProgramId); + if (nn_fs_kip == nullptr) { + ShowFatalError("Failed to find FS!\n"); + } + + /* Add to binary. */ + AddInitialProcess(nn_fs_kip); + + /* Re-find meta. */ + fs_meta = FindInitialProcess(FsProgramId); + } + + /* Check that we found FS. */ + if (fs_meta == nullptr) { + ShowFatalError("Failed to find FS!\n"); + } + + /* Get FS version. */ + const auto fs_version = GetFsVersion(fs_meta->kip_hash); + if (fs_version >= FsVersion_Count) { + if (emummc_enabled || nogc_enabled) { + ShowFatalError("Failed to identify FS!\n"); + } + } + + /* If emummc is enabled, we need to decompress fs .text. */ + if (emummc_enabled) { + fs_meta->patch_segments |= (1 << 0); + } + + /* Parse/prepare relevant nogc/kip patches. */ + { + /* Add nogc patches. */ + if (nogc_enabled) { + AddNogcPatches(fs_meta, fs_version); + } + + /* TODO ams.tma2: add mount_host patches. */ + } + + /* Return the fs version we're using. */ + return static_cast<u32>(fs_version); + } + + void RebuildPackage2(ams::TargetFirmware target_firmware, bool emummc_enabled) { + /* Get the external package. */ + const auto &external_package = GetExternalPackage(); + + /* Clear package2 header. */ + auto *package2 = secmon::MemoryRegionDramPackage2.GetPointer<pkg2::Package2Header>(); + std::memset(package2, 0, sizeof(*package2)); + + /* Get payload data pointer. */ + u8 * const payload_data = reinterpret_cast<u8 *>(package2 + 1); + + /* Useful values. */ + constexpr u32 KernelPayloadBase = 0x60000; + + /* Set fields. */ + package2->meta.key_generation = pkg1::KeyGeneration_Current; + std::memcpy(package2->meta.magic, pkg2::Package2Meta::Magic::String, sizeof(package2->meta.magic)); + package2->meta.entrypoint = KernelPayloadBase; + package2->meta.bootloader_version = pkg2::CurrentBootloaderVersion; + package2->meta.package2_version = pkg2::MinimumValidDataVersion; + + /* Load mesosphere. */ + s64 meso_size; + if (void *sd_meso = ReadFile(std::addressof(meso_size), "sdmc:/atmosphere/mesosphere.bin"); sd_meso != nullptr) { + std::memcpy(payload_data, sd_meso, meso_size); + } else { + meso_size = external_package.header.meso_size; + std::memcpy(payload_data, external_package.mesosphere, meso_size); + } + + /* Read emummc, if needed. */ + const InitialProcessHeader *emummc; + s64 emummc_size; + if (emummc_enabled) { + emummc = static_cast<const InitialProcessHeader *>(ReadFile(std::addressof(emummc_size), "sdmc:/atmosphere/emummc.kip")); + if (emummc == nullptr) { + emummc = reinterpret_cast<const InitialProcessHeader *>(external_package.kips + external_package.header.emummc_meta.offset); + emummc_size = external_package.header.emummc_meta.size; + } + } + + /* Set the embedded ini pointer. */ + const u32 magic = *reinterpret_cast<const u32 *>(payload_data + 4); + if (magic == MesoshereMetadataLayout0Magic) { + std::memcpy(payload_data + 8, std::addressof(meso_size), sizeof(meso_size)); + } else if (magic == MesoshereMetadataLayout1Magic) { + if (const u32 meta_offset = *reinterpret_cast<const u32 *>(payload_data + 8); meta_offset <= meso_size - sizeof(meso_size)) { + s64 relative_offset = meso_size - meta_offset; + std::memcpy(payload_data + meta_offset, std::addressof(relative_offset), sizeof(relative_offset)); + } else { + ShowFatalError("Invalid mesosphere metadata layout!\n"); + } + } else { + ShowFatalError("Unknown mesosphere metadata version!\n"); + } + + + /* Get the ini pointer. */ + InitialProcessBinaryHeader * const ini = reinterpret_cast<InitialProcessBinaryHeader *>(payload_data + meso_size); + + /* Set ini fields. */ + ini->magic = InitialProcessBinaryHeader::Magic; + ini->num_processes = 0; + ini->reserved = 0; + + /* Iterate all processes. */ + u8 * const dst_kip_start = reinterpret_cast<u8 *>(ini + 1); + u8 * dst_kip_cur = dst_kip_start; + + for (InitialProcessMeta *meta = std::addressof(g_initial_process_meta); meta != nullptr; meta = meta->next) { + /* Get the current kip. */ + const auto *src_kip = meta->kip; + auto *dst_kip = reinterpret_cast<InitialProcessHeader *>(dst_kip_cur); + + /* Copy the kip header */ + std::memcpy(dst_kip, src_kip, sizeof(*src_kip)); + + const u8 *src_kip_data = reinterpret_cast<const u8 *>(src_kip + 1); + u8 *dst_kip_data = reinterpret_cast< u8 *>(dst_kip + 1); + + /* If necessary, inject emummc. */ + u32 addl_text_offset = 0; + if (dst_kip->program_id == FsProgramId && emummc_enabled) { + /* Get emummc extents. */ + addl_text_offset = emummc->bss_address + emummc->bss_size; + if ((emummc->flags & 7) || !util::IsAligned(addl_text_offset, 0x1000)) { + ShowFatalError("Invalid emummc kip!\n"); + } + + /* Copy emummc capabilities. */ + { + std::memcpy(dst_kip->capabilities, emummc->capabilities, sizeof(emummc->capabilities)); + + if (target_firmware <= ams::TargetFirmware_1_0_0) { + for (size_t i = 0; i < util::size(dst_kip->capabilities); ++i) { + if (dst_kip->capabilities[i] == 0xFFFFFFFF) { + dst_kip->capabilities[i] = 0x07000E7F; + break; + } + } + } + } + + /* Update section headers. */ + dst_kip->ro_address += addl_text_offset; + dst_kip->rw_address += addl_text_offset; + dst_kip->bss_address += addl_text_offset; + + /* Get emummc sections. */ + const u8 *emummc_data = reinterpret_cast<const u8 *>(emummc + 1); + + /* Copy emummc sections. */ + std::memcpy(dst_kip_data + emummc->rx_address, emummc_data, emummc->rx_compressed_size); + std::memcpy(dst_kip_data + emummc->ro_address, emummc_data + emummc->rx_compressed_size, emummc->ro_compressed_size); + std::memcpy(dst_kip_data + emummc->rw_address, emummc_data + emummc->rx_compressed_size + emummc->ro_compressed_size, emummc->rw_compressed_size); + std::memset(dst_kip_data + emummc->bss_address, 0, emummc->bss_size); + + /* Advance. */ + dst_kip_data += addl_text_offset; + } + + /* Prepare to process segments. */ + u8 *dst_rx_data, *dst_ro_data, *dst_rw_data; + + /* Process .text. */ + { + dst_rx_data = dst_kip_data; + + std::memcpy(dst_kip_data, src_kip_data, src_kip->rx_compressed_size); + + /* Uncompress, if necessary. */ + if ((meta->patch_segments & src_kip->flags) & (1 << 0)) { + BlzUncompress(dst_kip_data + dst_kip->rx_compressed_size); + + dst_kip->rx_compressed_size = dst_kip->rx_size; + } + + /* Advance. */ + dst_kip_data += dst_kip->rx_compressed_size; + src_kip_data += src_kip->rx_compressed_size; + + /* Account for potential emummc. */ + dst_kip->rx_size += addl_text_offset; + dst_kip->rx_compressed_size += addl_text_offset; + } + + /* Process .rodata. */ + { + dst_ro_data = dst_kip_data; + + std::memcpy(dst_kip_data, src_kip_data, src_kip->ro_compressed_size); + + /* Uncompress, if necessary. */ + if ((meta->patch_segments & src_kip->flags) & (1 << 1)) { + BlzUncompress(dst_kip_data + dst_kip->ro_compressed_size); + + dst_kip->ro_compressed_size = dst_kip->ro_size; + } + + /* Advance. */ + dst_kip_data += dst_kip->ro_compressed_size; + src_kip_data += src_kip->ro_compressed_size; + } + + /* Process .rwdata. */ + { + dst_rw_data = dst_kip_data; + + std::memcpy(dst_kip_data, src_kip_data, src_kip->rw_compressed_size); + + /* Uncompress, if necessary. */ + if ((meta->patch_segments & src_kip->flags) & (1 << 2)) { + BlzUncompress(dst_kip_data + dst_kip->rw_compressed_size); + + dst_kip->rw_compressed_size = dst_kip->rw_size; + } + + /* Advance. */ + dst_kip_data += dst_kip->rw_compressed_size; + src_kip_data += src_kip->rw_compressed_size; + } + + /* Adjust flags. */ + dst_kip->flags &= ~meta->patch_segments; + + /* Apply patches. */ + for (auto *patch = meta->patches_head; patch != nullptr; patch = patch->next) { + /* Get the destination segment. */ + u8 *patch_dst_segment; + switch (patch->start_segment) { + case 0: patch_dst_segment = dst_rx_data; break; + case 1: patch_dst_segment = dst_ro_data; break; + case 2: patch_dst_segment = dst_rw_data; break; + default: ShowFatalError("Unknown patch segment %" PRIu32 "\n", patch->start_segment); break; + } + + /* Get the destination. */ + u8 * const patch_dst = patch_dst_segment + patch->rel_offset; + + /* Apply the patch. */ + if (patch->is_memset) { + const u8 val = *static_cast<const u8 *>(patch->data); + std::memset(patch_dst, val, patch->size); + } else { + std::memcpy(patch_dst, patch->data, patch->size); + } + } + + /* Advance. */ + dst_kip_cur += GetInitialProcessSize(dst_kip); + + /* Increment num kips. */ + ++ini->num_processes; + } + + /* Set INI size. */ + ini->size = sizeof(*ini) + (dst_kip_cur - dst_kip_start); + if (ini->size > 12_MB) { + ShowFatalError("INI is too big! (0x%08" PRIx32 ")\n", ini->size); + } + + /* Set the payload size/offset. */ + package2->meta.payload_offsets[0] = KernelPayloadBase; + package2->meta.payload_sizes[0] = util::AlignUp(meso_size + ini->size, 0x10); + + + /* Set total size. */ + package2->meta.package2_size = sizeof(*package2) + package2->meta.payload_sizes[0]; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_stratosphere.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_stratosphere.hpp new file mode 100644 index 00000000..0882111e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_stratosphere.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> +#pragma once + +namespace ams::nxboot { + + u32 ConfigureStratosphere(const u8 *nn_package2, ams::TargetFirmware target_firmware, bool emummc_enabled, bool nogc_enabled); + + void RebuildPackage2(ams::TargetFirmware target_firmware, bool emummc_enabled); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_uncompress.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_uncompress.cpp new file mode 100644 index 00000000..aec5f7f2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_uncompress.hpp" + +namespace ams::nxboot { + + 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<const u8 *>(src)), m_src_size(src_size), m_src_offset(0), m_dst(static_cast<u8 *>(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/fusee/program/source/fusee_uncompress.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_uncompress.hpp new file mode 100644 index 00000000..3bdfe805 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/fusee_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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> +#pragma once + +namespace ams::nxboot { + + 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/fusee/program/source/mtc/fusee_mtc.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc.cpp new file mode 100644 index 00000000..7b513c9e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc.cpp @@ -0,0 +1,98 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::nxboot { + + void DoMemoryTrainingErista(int index, void *mtc_tables_buffer); + void DoMemoryTrainingMariko(bool *out_did_training, int index, void *mtc_tables_buffer); + + void RestoreMemoryClockRateMariko(void *mtc_tables_buffer); + + namespace { + + alignas(4) constinit u8 g_mtc_tables_buffer[0x26C0]; + + constinit bool g_did_training_mariko = false; + + constexpr const u8 MemoryTrainingTableIndex_Invalid = std::numeric_limits<u8>::max(); + + constexpr const u8 MemoryTrainingTableIndices[] = { + /* DramId_EristaIcosaSamsung4gb */ 0x00, + /* DramId_EristaIcosaHynix4gb */ 0x02, + /* DramId_EristaIcosaMicron4gb */ 0x03, + /* DramId_MarikoIowaHynix1y4gb */ 0x10, + /* DramId_EristaIcosaSamsung6gb */ 0x01, + /* DramId_MarikoHoagHynix1y4gb */ 0x10, + /* DramId_MarikoAulaHynix1y4gb */ 0x10, + /* DramId_MarikoIowax1x2Samsung4gb */ 0x00, + /* DramId_MarikoIowaSamsung4gb */ 0x05, + /* DramId_MarikoIowaSamsung8gb */ 0x06, + /* DramId_MarikoIowaHynix4gb */ 0x07, + /* DramId_MarikoIowaMicron4gb */ 0x08, + /* DramId_MarikoHoagSamsung4gb */ 0x05, + /* DramId_MarikoHoagSamsung8gb */ 0x06, + /* DramId_MarikoHoagHynix4gb */ 0x07, + /* DramId_MarikoHoagMicron4gb */ 0x08, + /* DramId_MarikoIowaSamsung4gbY */ 0x09, + /* DramId_MarikoIowaSamsung1y4gbX */ 0x0C, + /* DramId_MarikoIowaSamsung1y8gbX */ 0x0D, + /* DramId_MarikoHoagSamsung1y4gbX */ 0x0C, + /* DramId_MarikoIowaSamsung1z4gb */ 0x12, + /* DramId_MarikoHoagSamsung1z4gb */ 0x12, + /* DramId_MarikoAulaSamsung1z4gb */ 0x12, + /* DramId_MarikoHoagSamsung1y8gbX */ 0x0D, + /* DramId_MarikoAulaSamsung1y4gbX */ 0x0C, + /* DramId_MarikoIowaMicron1y4gb */ 0x0F, + /* DramId_MarikoHoagMicron1y4gb */ 0x0F, + /* DramId_MarikoAulaMicron1y4gb */ 0x0F, + /* DramId_MarikoAulaSamsung1y8gbX */ 0x0D, + /* DramId_MarikoIowaHynix1a4gb */ 0x13, + /* DramId_MarikoHoagHynix1a4gb */ 0x13, + /* DramId_MarikoAulaHynix1a4gb */ 0x13, + /* DramId_MarikoIowaMicron1a4gb */ 0x14, + /* DramId_MarikoHoagMicron1a4gb */ 0x14, + /* DramId_MarikoAulaMicron1a4gb */ 0x14, + }; + + int GetMemoryTrainingTableIndex() { + if (const auto dram_id = fuse::GetDramId(); dram_id < util::size(MemoryTrainingTableIndices) && MemoryTrainingTableIndices[dram_id] != MemoryTrainingTableIndex_Invalid) { + return static_cast<int>(MemoryTrainingTableIndices[dram_id]); + } else { + return -1; + } + } + + } + + void DoMemoryTraining() { + const auto index = GetMemoryTrainingTableIndex(); + + if (fuse::GetSocType() == fuse::SocType_Erista) { + DoMemoryTrainingErista(index, g_mtc_tables_buffer); + } else { + DoMemoryTrainingMariko(std::addressof(g_did_training_mariko), index, g_mtc_tables_buffer); + } + } + + void RestoreMemoryClockRate() { + /* NOTE: This resolves an off-by-one issue in PCV's detection of memory clock rate on Mariko. */ + if (fuse::GetSocType() == fuse::SocType_Mariko && g_did_training_mariko) { + RestoreMemoryClockRateMariko(g_mtc_tables_buffer); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc.hpp new file mode 100644 index 00000000..0bc492f0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::nxboot { + + void DoMemoryTraining(); + + void RestoreMemoryClockRate(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_erista.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_erista.cpp new file mode 100644 index 00000000..2eb4bcef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_erista.cpp @@ -0,0 +1,2879 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../fusee_fatal.hpp" +#include "fusee_mtc.hpp" +#include "fusee_mtc_timing_table_erista.hpp" + +namespace ams::nxboot { + + namespace { + + constexpr inline const uintptr_t CLKRST = secmon::MemoryRegionPhysicalDeviceClkRst.GetAddress(); + constexpr inline const uintptr_t MC = MC_BASE; + constexpr inline const uintptr_t EMC = EMC_BASE; + constexpr inline const uintptr_t EMC0 = EMC0_BASE; + constexpr inline const uintptr_t EMC1 = EMC1_BASE; + + static constinit bool g_next_pll = false; + static constinit bool g_did_first_training = false; + static constinit bool g_fsp_for_next_freq = false; + + #include "fusee_mtc_tables_erista.inc" + #include "fusee_mtc_ram_training_pattern.inc" + + #define DECLARE_OFFSET_HANDLER(BASE, REG, NAME) REG, + #define DECLARE_REGISTER_HANDLER(BASE, REG, NAME) BASE + REG, + + constexpr inline const u16 BurstRegistersOffsets[] = { + FOREACH_BURST_REG(DECLARE_OFFSET_HANDLER) + }; + + constexpr inline const u32 TrimRegisters[] = { + FOREACH_TRIM_REG(DECLARE_REGISTER_HANDLER) + }; + + constexpr inline const u32 BurstMcRegisters[] = { + FOREACH_BURST_MC_REG(DECLARE_REGISTER_HANDLER) + }; + + constexpr inline const u32 LaScaleRegisters[] = { + FOREACH_LA_SCALE_REG(DECLARE_REGISTER_HANDLER) + }; + + constexpr inline const u32 PerChannelTrimRegisters[] = { + FOREACH_PER_CHANNEL_TRIM_REG(DECLARE_REGISTER_HANDLER) + }; + + constexpr inline const u32 PerChannelBurstRegisters[] = { + FOREACH_PER_CHANNEL_BURST_REG(DECLARE_REGISTER_HANDLER) + }; + + constexpr inline const u32 PerChannelVrefRegisters[] = { + FOREACH_PER_CHANNEL_VREF_REG(DECLARE_REGISTER_HANDLER) + }; + + constexpr inline const u32 PerChannelTrainingModRegisters[] = { + FOREACH_PER_CHANNEL_TRAINING_MOD_REG(DECLARE_REGISTER_HANDLER) + }; + + using EmcDvfsTimingTable = erista::EmcDvfsTimingTable; + + EmcDvfsTimingTable *GetEmcDvfsTimingTables(int index, void *mtc_tables_buffer) { + switch (index) { + case 0: + case 3: + //std::memcpy(mtc_tables_buffer, T210SdevEmcDvfsTableS4gb01, sizeof(T210SdevEmcDvfsTableS4gb01)); + return reinterpret_cast<EmcDvfsTimingTable *>(const_cast<u8 *>(T210SdevEmcDvfsTableS4gb01)); + case 1: + //std::memcpy(mtc_tables_buffer, T210SdevEmcDvfsTableS6gb01, sizeof(T210SdevEmcDvfsTableS6gb01)); + return reinterpret_cast<EmcDvfsTimingTable *>(const_cast<u8 *>(T210SdevEmcDvfsTableS6gb01)); + case 2: + //std::memcpy(mtc_tables_buffer, T210SdevEmcDvfsTableH4gb01, sizeof(T210SdevEmcDvfsTableH4gb01)); + return reinterpret_cast<EmcDvfsTimingTable *>(const_cast<u8 *>(T210SdevEmcDvfsTableH4gb01)); + default: + ShowFatalError("Unknown EmcDvfsTimingTableIndex: %d\n", index); + } + } + + bool IsSamePll(u32 next_2x, u32 prev_2x) { + if (next_2x == prev_2x) { + return true; + } else if ((next_2x == PLLM_OUT0 || next_2x == PLLM_UD) && (prev_2x == PLLM_OUT0 || prev_2x == PLLM_UD)) { + return true; + } else { + return false; + } + } + + bool PllReprogram(u32 next_rate_khz, u32 next_clk_src, u32 prev_rate_khz, u32 prev_clk_src) { + /* Get current pll/divp value. */ + u32 pll_base, pll_p; + switch (reg::GetValue(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC))) { + case PLLM_UD: + case PLLM_OUT0: + pll_base = reg::Read(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE); + pll_p = reg::GetField(pll_base, CLK_RST_REG_BITS_MASK(PLLM_BASE_PLLM_DIVP)); + break; + case PLLMB_UD: + case PLLMB_OUT0: + pll_base = reg::Read(CLKRST + CLK_RST_CONTROLLER_PLLMB_BASE); + pll_p = reg::GetField(pll_base, CLK_RST_REG_BITS_MASK(PLLMB_BASE_PLLMB_DIVP)); + break; + default: + pll_base = 0; + pll_p = 0; + } + + /* Check pll divp. */ + if (pll_p > 5) { + ShowFatalError("Invalid PLL divp: %" PRIu32 "\n", pll_p); + } + + /* Get clk src/divisor. */ + const u32 next_2x = reg::GetField(next_clk_src, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC)); + const u32 prev_2x = reg::GetField(prev_clk_src, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC)); + u32 next_div = reg::GetField(next_clk_src, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_DIVISOR)); + u32 prev_div = reg::GetField(prev_clk_src, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_DIVISOR)); + + /* Update divisor, if necessary. */ + if (next_2x == PLLM_UD || next_2x == PLLMB_UD) { + next_div = 0; + } + if (prev_2x == PLLM_UD || prev_2x == PLLMB_UD) { + prev_div = 0; + } + + /* If the pll is different, reprogramming is necessary. */ + if (!IsSamePll(next_2x, prev_2x)) { + return true; + } + + /* Return whether the ratios are different. */ + const float next_freq = next_rate_khz * (1 + (next_div >> 1) + (0.5 * (next_div & 1))) * (pll_p + 1); + const float prev_freq = prev_rate_khz * (1 + (prev_div >> 1) + (0.5 * (prev_div & 1))) * (pll_p + 1); + + const float ratio = prev_freq / next_freq; + + return ratio > 1.01 || ratio < 0.99; + } + + u32 ProgramPllm(u32 next_rate_khz, u32 next_clk_src, bool is_pllmb) { + /* Hardcode values for 1600MHz. */ + u32 divn, divm, divp; + if (next_rate_khz == 1600000) { + divn = 0x7D; + divm = 0x03; + divp = 0x00; + } else if (next_rate_khz == 800000) { + divn = 0x7D; + divm = 0x03; + divp = 0x01; + } else { + ShowFatalError("Unexpected ProgramPllm next rate %" PRIu32 "\n", next_rate_khz); + } + + const auto next_2x = reg::GetField(next_clk_src, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC)); + if (is_pllmb) { + /* Set divisors. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLMB_BASE, CLK_RST_REG_BITS_VALUE(PLLMB_BASE_PLLMB_DIVM, divm), + CLK_RST_REG_BITS_VALUE(PLLMB_BASE_PLLMB_DIVN, divn), + CLK_RST_REG_BITS_VALUE(PLLMB_BASE_PLLMB_DIVP, divp)); + reg::Read(CLKRST + CLK_RST_CONTROLLER_PLLMB_BASE); + + /* Set enable. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_PLLMB_BASE, CLK_RST_REG_BITS_ENUM(PLLMB_BASE_PLLMB_ENABLE, ENABLE)); + + /* Adjust next clock source. */ + if (next_2x == PLLM_UD) { + reg::SetField(next_clk_src, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, PLLMB_UD)); + } else if (next_2x == PLLM_OUT0) { + reg::SetField(next_clk_src, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, PLLMB_OUT0)); + } + + /* Wait for pll to lock. */ + while (!reg::HasValue(CLKRST + CLK_RST_CONTROLLER_PLLMB_BASE, CLK_RST_REG_BITS_ENUM(PLLMB_BASE_PLLMB_LOCK, LOCK))) { + /* ... */ + } + } else { + /* Set divisors. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE, CLK_RST_REG_BITS_VALUE(PLLM_BASE_PLLM_DIVM, divm), + CLK_RST_REG_BITS_VALUE(PLLM_BASE_PLLM_DIVN, divn), + CLK_RST_REG_BITS_VALUE(PLLM_BASE_PLLM_DIVP, divp)); + reg::Read(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE); + + /* Set LKCDET. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_PLLM_MISC2, CLK_RST_REG_BITS_ENUM(PLLM_MISC2_PLLM_EN_LCKDET, ENABLE)); + + /* Set enable. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE, CLK_RST_REG_BITS_ENUM(PLLM_BASE_PLLM_ENABLE, ENABLE)); + + /* Adjust next clock source. */ + if (next_2x == PLLM_UD) { + reg::SetField(next_clk_src, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, PLLM_UD)); + } else if (next_2x == PLLM_OUT0) { + reg::SetField(next_clk_src, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, PLLM_OUT0)); + } + + /* Wait for pll to lock. */ + while (!reg::HasValue(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE, CLK_RST_REG_BITS_ENUM(PLLM_BASE_PLLM_LOCK, LOCK))) { + /* ... */ + } + } + + return next_clk_src; + } + + u32 GetDllState(EmcDvfsTimingTable *timing) { + return (!(timing->emc_emrs & 0x1)) ? DLL_ON : DLL_OFF; + } + + int WaitForUpdate(u32 reg_offset, u32 mask, bool updated, u32 fbio_cfg7) { + constexpr int StatusUpdateTimeout = 1000; + + int result = 0; + + if (true /* reg::HasValue(fbio_cfg7, EMC_REG_BITS_ENUM(FBIO_CFG7_CH0_ENABLE, ENABLE)) */) { + bool success = false; + for (int i = 0; i < StatusUpdateTimeout; ++i) { + if (((reg::Read(EMC + reg_offset) & mask) != 0) == updated) { + success = true; + break; + } + util::WaitMicroSeconds(1); + } + result |= success ? 0 : 4; + } + + if (reg::GetField(fbio_cfg7, EMC_REG_BITS_MASK(FBIO_CFG7_CH1_ENABLE)) == EMC_FBIO_CFG7_CH1_ENABLE_ENABLE) { + bool success = false; + for (int i = 0; i < StatusUpdateTimeout; ++i) { + if (((reg::Read(EMC1 + reg_offset) & mask) != 0) == updated) { + success = true; + break; + } + util::WaitMicroSeconds(1); + } + result |= success ? 0 : 4; + } + + return result; + } + + void TimingUpdate(u32 fbio_cfg7) { + /* Trigger the timing update event. */ + reg::Write(EMC + EMC_TIMING_CONTROL, 1); + + /* Wait for the update to finish. */ + WaitForUpdate(EMC_EMC_STATUS, 0x800000, false, fbio_cfg7); + } + + void DllDisable(u32 fbio_cfg7) { + /* Disable dll. */ + reg::ClearBits(EMC + EMC_CFG_DIG_DLL, 0x1); + + /* Request a timing update event */ + TimingUpdate(fbio_cfg7); + + /* Wait until CFG_DLL_EN is cleared. */ + WaitForUpdate(EMC_CFG_DIG_DLL, 0x1, false, fbio_cfg7); + } + + void DllEnableStall(u32 fbio_cfg7) { + /* Enable DLL */ + uint32_t emc_cfg_dig_dll = (reg::Read(EMC + EMC_CFG_DIG_DLL) & 0xFFFFFF24) | 0x89; + reg::Write(EMC + EMC_CFG_DIG_DLL, emc_cfg_dig_dll); + + /* Request a timing update event */ + TimingUpdate(fbio_cfg7); + + /* Wait until CFG_DLL_EN is set for EMC */ + WaitForUpdate(EMC_CFG_DIG_DLL, 0x1, true, fbio_cfg7); + } + + void ChangeDllSrc(EmcDvfsTimingTable *dst_timing, u32 next_clk_src) { + u32 dll_setting = ((next_clk_src & 0xE00000FF) | (dst_timing->dll_clk_src & 0x1FFFFF00)) & 0xFFFFF3FF; + switch (reg::GetField(next_clk_src, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC))) { + case PLLMB_UD: + dll_setting |= 0x400; /* PLLM_VCOB */ + break; + case PLLM_UD: + dll_setting |= 0x000; /* PLLM_VCOA */ + break; + default: + dll_setting |= 0x800; /* EMC_DLL_SWITCH_OUT */ + break; + } + + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC_DLL, dll_setting); + reg::Read(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_X); + util::WaitMicroSeconds(2); + + if (dst_timing->clk_out_enb_x_0_clk_enb_emc_dll) { + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_X_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_X_CLK_ENB_EMC_DLL, ENABLE)); + } else { + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_X_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_X_CLK_ENB_EMC_DLL, ENABLE)); + } + reg::Read(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_X); + util::WaitMicroSeconds(2); + } + + void SetShadowBypass(u32 val) { + reg::ReadWrite(EMC + EMC_DBG, EMC_REG_BITS_VALUE(DBG_WRITE_MUX, val)); + } + + u32 DllPrelock(EmcDvfsTimingTable *dst_timing, bool training_enabled, u32 next_clk_src) { + /* Check for dual channel LPDDR4 */ + u32 fbio_cfg7 = reg::Read(EMC + EMC_FBIO_CFG7); + + uint32_t emc_dig_dll_status = 0; + uint32_t emc_cfg_dig_dll = (reg::Read(EMC1 + EMC_CFG_DIG_DLL) & 0xFFFFF824) | 0x3C8; + + /* Update EMC_CFG_DIG_DLL_0 */ + reg::Write(EMC + EMC_CFG_DIG_DLL, emc_cfg_dig_dll); + + /* Request a timing update event */ + TimingUpdate(fbio_cfg7); + + /* Wait until CFG_DLL_EN is cleared for EMC */ + WaitForUpdate(EMC_CFG_DIG_DLL, 0x1, false, fbio_cfg7); + + reg::Write(EMC + EMC_DLL_CFG_0, dst_timing->burst_regs.emc_dll_cfg_0); + reg::Write(EMC + EMC_DLL_CFG_1, dst_timing->burst_regs.emc_dll_cfg_1); + + /* Configure the clock and reset controller for EMC DLL */ + ChangeDllSrc(dst_timing, next_clk_src); + + /* Enable DLL */ + reg::SetBits(EMC + EMC_CFG_DIG_DLL, 0x1); + + /* Request a timing update event */ + TimingUpdate(fbio_cfg7); + + /* Wait until CFG_DLL_EN is set for EMC */ + WaitForUpdate(EMC_CFG_DIG_DLL, 0x1, true, fbio_cfg7); + + /* Wait until DLL_PRIV_UPDATED or DLL_LOCK have been cleared */ + do { + emc_dig_dll_status = reg::Read(EMC + EMC_DIG_DLL_STATUS); + } while ((~emc_dig_dll_status & 0x28000) != 0); + + if (training_enabled) { + /* Set WRITE_MUX to ACTIVE */ + SetShadowBypass(ACTIVE); + + /* Disable DLL */ + reg::ClearBits(EMC + EMC_CFG_DIG_DLL, 0x1); + + /* Set WRITE_MUX to ASSEMBLY */ + SetShadowBypass(ASSEMBLY); + + /* Wait until CFG_DLL_EN is cleared for EMC */ + WaitForUpdate(EMC_CFG_DIG_DLL, 0x1, false, fbio_cfg7); + } + + /* Return the DLL_OUT value */ + return (reg::Read(EMC1 + EMC_DIG_DLL_STATUS) & 0x7FF); + } + + void CcfifoWrite(u32 addr, u32 data, u32 wait) { + reg::Write(EMC + EMC_CCFIFO_DATA, data); + reg::Write(EMC + EMC_CCFIFO_ADDR, (addr & 0xFFFF) | ((wait & 0x7FFF) << 16) | 0x80000000); + } + + u32 ActualOscClocks(u32 in) { + if (in < 0x40) { + return in * 0x10; + } else if (in < 0x80) { + return 0x800; + } else if (in < 0xC0) { + return 0x1000; + } else { + return 0x2000; + } + } + + void StartPeriodicCompensation() { + reg::Write(EMC + EMC_MPC, 0x4B); + reg::Read(EMC + EMC_MPC); + } + + u32 UpdateClockTreeDelay(EmcDvfsTimingTable *src_timing, EmcDvfsTimingTable *dst_timing, u32 dram_dev_num, u32 fbio_cfg7, int type) { + uint32_t mrr_req = 0, mrr_data = 0; + uint32_t temp0_0 = 0, temp0_1 = 0, temp1_0 = 0, temp1_1 = 0; + int tdel = 0, tmdel = 0, adel = 0; + uint32_t cval; + uint32_t src_timing_rate_mhz = (src_timing->rate_khz / 1000); + uint32_t dst_timing_rate_mhz = (dst_timing->rate_khz / 1000); + bool dvfs_pt1 = (type == DVFS_PT1); + bool training_pt1 = (type == TRAINING_PT1); + bool dvfs_update = (type == DVFS_UPDATE); + bool training_update = (type == TRAINING_UPDATE); + bool periodic_training_update = (type == PERIODIC_TRAINING_UPDATE); + + const bool dual_channel = reg::GetField(fbio_cfg7, EMC_REG_BITS_MASK(FBIO_CFG7_CH1_ENABLE)) == EMC_FBIO_CFG7_CH1_ENABLE_ENABLE; + + /* Dev0 MSB. */ + if (dvfs_pt1 || training_pt1 || periodic_training_update) { + mrr_req = ((2 << 30) | (19 << 16)); + reg::Write(EMC + EMC_MRR, mrr_req); + + WaitForUpdate(EMC_EMC_STATUS, 0x100000, true, fbio_cfg7); + + mrr_data = ((reg::Read(EMC + EMC_MRR) & 0xFFFF) << 0); + + temp0_0 = ((mrr_data & 0xff) << 8); + temp0_1 = (mrr_data & 0xff00); + + if (dual_channel) { + mrr_data = ((reg::Read(EMC1 + EMC_MRR) & 0xFFFF) << 0); + temp1_0 = ((mrr_data & 0xff) << 8); + temp1_1 = (mrr_data & 0xff00); + } + + /* Dev0 LSB. */ + mrr_req = ((mrr_req & ~0xFF0000) | (18 << 16)); + reg::Write(EMC + EMC_MRR, mrr_req); + + WaitForUpdate(EMC_EMC_STATUS, 0x100000, true, fbio_cfg7); + + mrr_data = ((reg::Read(EMC + EMC_MRR) & 0xFFFF) << 0); + + temp0_0 |= (mrr_data & 0xff); + temp0_1 |= ((mrr_data & 0xff00) >> 8); + + if (dual_channel) { + mrr_data = ((reg::Read(EMC1 + EMC_MRR) & 0xFFFF) << 0); + temp1_0 |= (mrr_data & 0xff); + temp1_1 |= ((mrr_data & 0xff00) >> 8); + } + } + + cval = ((1000000 * ActualOscClocks(src_timing->run_clocks)) / (src_timing_rate_mhz * 2 * temp0_0)); + + if (dvfs_pt1 || training_pt1) + __INCREMENT_PTFV(c0d0u0, cval); + else if (dvfs_update) + __AVERAGE_PTFV(c0d0u0); + else if (training_update) + __AVERAGE_WRITE_PTFV(c0d0u0); + else if (periodic_training_update) + __WEIGHTED_UPDATE_PTFV(c0d0u0, cval); + + if (dvfs_update || training_update || periodic_training_update) { + tdel = (dst_timing->current_dram_clktree_c0d0u0 - __MOVAVG_AC(dst_timing, c0d0u0)); + tmdel = (tdel < 0) ? -1 * tdel : tdel; + adel = tmdel; + if ((tmdel * 128 * dst_timing_rate_mhz / 1000000) > dst_timing->tree_margin) + dst_timing->current_dram_clktree_c0d0u0 = __MOVAVG_AC(dst_timing, c0d0u0); + } + + cval = ((1000000 * ActualOscClocks(src_timing->run_clocks)) / + (src_timing_rate_mhz * 2 * temp0_1)); + + if (dvfs_pt1 || training_pt1) + __INCREMENT_PTFV(c0d0u1, cval); + else if (dvfs_update) + __AVERAGE_PTFV(c0d0u1); + else if (training_update) + __AVERAGE_WRITE_PTFV(c0d0u1); + else if (periodic_training_update) + __WEIGHTED_UPDATE_PTFV(c0d0u1, cval); + + if (dvfs_update || training_update || periodic_training_update) { + tdel = dst_timing->current_dram_clktree_c0d0u1 - __MOVAVG_AC(dst_timing, c0d0u1); + tmdel = (tdel < 0) ? -1 * tdel : tdel; + + if (tmdel > adel) + adel = tmdel; + + if ((tmdel * 128 * dst_timing_rate_mhz / 1000000) > dst_timing->tree_margin) + dst_timing->current_dram_clktree_c0d0u1 = __MOVAVG_AC(dst_timing, c0d0u1); + } + + if (dual_channel) { + cval = ((1000000 * ActualOscClocks(src_timing->run_clocks)) / (src_timing_rate_mhz * 2 * temp1_0)); + + if (dvfs_pt1 || training_pt1) + __INCREMENT_PTFV(c1d0u0, cval); + else if (dvfs_update) + __AVERAGE_PTFV(c1d0u0); + else if (training_update) + __AVERAGE_WRITE_PTFV(c1d0u0); + else if (periodic_training_update) + __WEIGHTED_UPDATE_PTFV(c1d0u0, cval); + + if (dvfs_update || training_update || periodic_training_update) { + tdel = dst_timing->current_dram_clktree_c1d0u0 - __MOVAVG_AC(dst_timing, c1d0u0); + tmdel = (tdel < 0) ? -1 * tdel : tdel; + + if (tmdel > adel) + adel = tmdel; + + if ((tmdel * 128 * dst_timing_rate_mhz / 1000000) > dst_timing->tree_margin) + dst_timing->current_dram_clktree_c1d0u0 = __MOVAVG_AC(dst_timing, c1d0u0); + } + + cval = ((1000000 * ActualOscClocks(src_timing->run_clocks)) / (src_timing_rate_mhz * 2 * temp1_1)); + + if (dvfs_pt1 || training_pt1) + __INCREMENT_PTFV(c1d0u1, cval); + else if (dvfs_update) + __AVERAGE_PTFV(c1d0u1); + else if (training_update) + __AVERAGE_WRITE_PTFV(c1d0u1); + else if (periodic_training_update) + __WEIGHTED_UPDATE_PTFV(c1d0u1, cval); + + if (dvfs_update || training_update || periodic_training_update) { + tdel = dst_timing->current_dram_clktree_c1d0u1 - __MOVAVG_AC(dst_timing, c1d0u1); + tmdel = (tdel < 0) ? -1 * tdel : tdel; + + if (tmdel > adel) + adel = tmdel; + + if ((tmdel * 128 * dst_timing_rate_mhz / 1000000) > dst_timing->tree_margin) + dst_timing->current_dram_clktree_c1d0u1 = __MOVAVG_AC(dst_timing, c1d0u1); + } + } + + if (dram_dev_num != TWO_RANK) + return adel; + + /* Dev1 MSB. */ + if (dvfs_pt1 || training_pt1 || periodic_training_update) { + mrr_req = ((1 << 30) | (19 << 16)); + reg::Write(EMC + EMC_MRR, mrr_req); + + WaitForUpdate(EMC_EMC_STATUS, 0x100000, true, fbio_cfg7); + + mrr_data = ((reg::Read(EMC + EMC_MRR) & 0xFFFF) << 0); + + temp0_0 = ((mrr_data & 0xff) << 8); + temp0_1 = (mrr_data & 0xff00); + + if (dual_channel) { + mrr_data = ((reg::Read(EMC1 + EMC_MRR) & 0xFFFF) << 0); + temp1_0 = ((mrr_data & 0xff) << 8); + temp1_1 = (mrr_data & 0xff00); + } + + /* Dev1 LSB. */ + mrr_req = ((mrr_req & ~0xFF0000) | (18 << 16)); + reg::Write(EMC + EMC_MRR, mrr_req); + + WaitForUpdate(EMC_EMC_STATUS, 0x100000, true, fbio_cfg7); + + mrr_data = ((reg::Read(EMC + EMC_MRR) & 0xFFFF) << 0); + + temp0_0 |= (mrr_data & 0xff); + temp0_1 |= ((mrr_data & 0xff00) >> 8); + + if (dual_channel) { + mrr_data = ((reg::Read(EMC1 + EMC_MRR) & 0xFFFF) << 0); + temp1_0 |= (mrr_data & 0xff); + temp1_1 |= ((mrr_data & 0xff00) >> 8); + } + } + + cval = ((1000000 * ActualOscClocks(src_timing->run_clocks)) / (src_timing_rate_mhz * 2 * temp0_0)); + + if (dvfs_pt1 || training_pt1) + __INCREMENT_PTFV(c0d1u0, cval); + else if (dvfs_update) + __AVERAGE_PTFV(c0d1u0); + else if (training_update) + __AVERAGE_WRITE_PTFV(c0d1u0); + else if (periodic_training_update) + __WEIGHTED_UPDATE_PTFV(c0d1u0, cval); + + if (dvfs_update || training_update || periodic_training_update) { + tdel = dst_timing->current_dram_clktree_c0d1u0 - __MOVAVG_AC(dst_timing, c0d1u0); + tmdel = (tdel < 0) ? -1 * tdel : tdel; + + if (tmdel > adel) + adel = tmdel; + + if ((tmdel * 128 * dst_timing_rate_mhz / 1000000) > dst_timing->tree_margin) + dst_timing->current_dram_clktree_c0d1u0 = __MOVAVG_AC(dst_timing, c0d1u0); + } + + cval = ((1000000 * ActualOscClocks(src_timing->run_clocks)) / (src_timing_rate_mhz * 2 * temp0_1)); + + if (dvfs_pt1 || training_pt1) + __INCREMENT_PTFV(c0d1u1, cval); + else if (dvfs_update) + __AVERAGE_PTFV(c0d1u1); + else if (training_update) + __AVERAGE_WRITE_PTFV(c0d1u1); + else if (periodic_training_update) + __WEIGHTED_UPDATE_PTFV(c0d1u1, cval); + + if (dvfs_update || training_update || periodic_training_update) { + tdel = dst_timing->current_dram_clktree_c0d1u1 - __MOVAVG_AC(dst_timing, c0d1u1); + tmdel = (tdel < 0) ? -1 * tdel : tdel; + + if (tmdel > adel) + adel = tmdel; + + if ((tmdel * 128 * dst_timing_rate_mhz / 1000000) > dst_timing->tree_margin) + dst_timing->current_dram_clktree_c0d1u1 = __MOVAVG_AC(dst_timing, c0d1u1); + } + + if (dual_channel) { + cval = ((1000000 * ActualOscClocks(src_timing->run_clocks)) / (src_timing_rate_mhz * 2 * temp1_0)); + + if (dvfs_pt1 || training_pt1) + __INCREMENT_PTFV(c1d1u0, cval); + else if (dvfs_update) + __AVERAGE_PTFV(c1d1u0); + else if (training_update) + __AVERAGE_WRITE_PTFV(c1d1u0); + else if (periodic_training_update) + __WEIGHTED_UPDATE_PTFV(c1d1u0, cval); + + if (dvfs_update || training_update || periodic_training_update) { + tdel = dst_timing->current_dram_clktree_c1d1u0 - __MOVAVG_AC(dst_timing, c1d1u0); + + tmdel = (tdel < 0) ? -1 * tdel : tdel; + + if (tmdel > adel) + adel = tmdel; + + if ((tmdel * 128 * dst_timing_rate_mhz / 1000000) > dst_timing->tree_margin) + dst_timing->current_dram_clktree_c1d1u0 = __MOVAVG_AC(dst_timing, c1d1u0); + } + + cval = ((1000000 * ActualOscClocks(src_timing->run_clocks)) / (src_timing_rate_mhz * 2 * temp1_1)); + + if (dvfs_pt1 || training_pt1) + __INCREMENT_PTFV(c1d1u1, cval); + else if (dvfs_update) + __AVERAGE_PTFV(c1d1u1); + else if (training_update) + __AVERAGE_WRITE_PTFV(c1d1u1); + else if (periodic_training_update) + __WEIGHTED_UPDATE_PTFV(c1d1u1, cval); + + if (dvfs_update || training_update || periodic_training_update) { + tdel = dst_timing->current_dram_clktree_c1d1u1 - __MOVAVG_AC(dst_timing, c1d1u1); + tmdel = (tdel < 0) ? -1 * tdel : tdel; + + if (tmdel > adel) + adel = tmdel; + + if ((tmdel * 128 * dst_timing_rate_mhz / 1000000) > dst_timing->tree_margin) + dst_timing->current_dram_clktree_c1d1u1 = __MOVAVG_AC(dst_timing, c1d1u1); + } + } + + if (training_update) { + dst_timing->trained_dram_clktree_c0d0u0 = dst_timing->current_dram_clktree_c0d0u0; + dst_timing->trained_dram_clktree_c0d0u1 = dst_timing->current_dram_clktree_c0d0u1; + dst_timing->trained_dram_clktree_c0d1u0 = dst_timing->current_dram_clktree_c0d1u0; + dst_timing->trained_dram_clktree_c0d1u1 = dst_timing->current_dram_clktree_c0d1u1; + dst_timing->trained_dram_clktree_c1d0u0 = dst_timing->current_dram_clktree_c1d0u0; + dst_timing->trained_dram_clktree_c1d0u1 = dst_timing->current_dram_clktree_c1d0u1; + dst_timing->trained_dram_clktree_c1d1u0 = dst_timing->current_dram_clktree_c1d1u0; + dst_timing->trained_dram_clktree_c1d1u1 = dst_timing->current_dram_clktree_c1d1u1; + } + + return adel; + + } + + u32 PeriodicCompensationHandler(int type, u32 dram_dev_num, u32 fbio_cfg7, EmcDvfsTimingTable *src_timing, EmcDvfsTimingTable *dst_timing) { + #define __COPY_EMA(nt, lt, dev) \ + ({ __MOVAVG(nt, dev) = __MOVAVG(lt, dev) * \ + (nt)->ptfv_dvfs_samples; }) + + uint32_t adel = 0; + uint32_t samples = dst_timing->ptfv_dvfs_samples; + uint32_t samples_write = dst_timing->ptfv_write_samples; + uint32_t delay = 2 + (1000 * ActualOscClocks(src_timing->run_clocks) / src_timing->rate_khz); + + if (!dst_timing->periodic_training) + return 0; + + if (type == DVFS_SEQUENCE) { + if (src_timing->periodic_training && (dst_timing->ptfv_config_ctrl & 1)) { + /* + * If the previous frequency was using periodic + * calibration then we can reuse the previous + * frequencies EMA data. + */ + __COPY_EMA(dst_timing, src_timing, c0d0u0); + __COPY_EMA(dst_timing, src_timing, c0d0u1); + __COPY_EMA(dst_timing, src_timing, c1d0u0); + __COPY_EMA(dst_timing, src_timing, c1d0u1); + __COPY_EMA(dst_timing, src_timing, c0d1u0); + __COPY_EMA(dst_timing, src_timing, c0d1u1); + __COPY_EMA(dst_timing, src_timing, c1d1u0); + __COPY_EMA(dst_timing, src_timing, c1d1u1); + } else { + /* Reset the EMA.*/ + __MOVAVG(dst_timing, c0d0u0) = 0; + __MOVAVG(dst_timing, c0d0u1) = 0; + __MOVAVG(dst_timing, c1d0u0) = 0; + __MOVAVG(dst_timing, c1d0u1) = 0; + __MOVAVG(dst_timing, c0d1u0) = 0; + __MOVAVG(dst_timing, c0d1u1) = 0; + __MOVAVG(dst_timing, c1d1u0) = 0; + __MOVAVG(dst_timing, c1d1u1) = 0; + + for (uint32_t i = 0; i < samples; i++) { + StartPeriodicCompensation(); + util::WaitMicroSeconds(delay); + + /* Generate next sample of data. */ + adel = UpdateClockTreeDelay(src_timing, dst_timing, dram_dev_num, fbio_cfg7, DVFS_PT1); + } + } + + adel = UpdateClockTreeDelay(src_timing, dst_timing, dram_dev_num, fbio_cfg7, DVFS_UPDATE); + } else if (type == WRITE_TRAINING_SEQUENCE) { + /* Reset the EMA.*/ + __MOVAVG(dst_timing, c0d0u0) = 0; + __MOVAVG(dst_timing, c0d0u1) = 0; + __MOVAVG(dst_timing, c1d0u0) = 0; + __MOVAVG(dst_timing, c1d0u1) = 0; + __MOVAVG(dst_timing, c0d1u0) = 0; + __MOVAVG(dst_timing, c0d1u1) = 0; + __MOVAVG(dst_timing, c1d1u0) = 0; + __MOVAVG(dst_timing, c1d1u1) = 0; + + for (uint32_t i = 0; i < samples_write; i++) { + StartPeriodicCompensation(); + util::WaitMicroSeconds(delay); + + /* Generate next sample of data. */ + UpdateClockTreeDelay(src_timing, dst_timing, dram_dev_num, fbio_cfg7, TRAINING_PT1); + } + + adel = UpdateClockTreeDelay(src_timing, dst_timing, dram_dev_num, fbio_cfg7, TRAINING_UPDATE); + } else if (type == PERIODIC_TRAINING_SEQUENCE) { + StartPeriodicCompensation(); + util::WaitMicroSeconds(delay); + + adel = UpdateClockTreeDelay(src_timing, dst_timing, dram_dev_num, fbio_cfg7, PERIODIC_TRAINING_UPDATE); + } + + return adel; + } + + uint32_t ApplyPeriodicCompensationTrimmer(EmcDvfsTimingTable *dst_timing, uint32_t offset) { + #define TRIM_REG(chan, rank, reg, byte) \ + ((EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ## reg ## \ + _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte ## _MASK & \ + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank ## rank ## _ ## reg ) >> \ + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ## reg ## \ + _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte ## _SHIFT) \ + + \ + (((EMC_DATA_BRLSHFT_ ## rank ## _RANK ## rank ## _BYTE ## \ + byte ## _DATA_BRLSHFT_MASK & \ + dst_timing->trim_perch_regs.emc ## chan ## _data_brlshft_ ## rank ) >> \ + EMC_DATA_BRLSHFT_ ## rank ## _RANK ## rank ## _BYTE ## \ + byte ## _DATA_BRLSHFT_SHIFT) * 64) + + #define CALC_TEMP(rank, reg, byte1, byte2, n) \ + ((adj[n] << EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ## \ + reg ## _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte1 ## _SHIFT) & \ + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ## reg ## \ + _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte1 ## _MASK) \ + | \ + ((adj[n + 1] << EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ## \ + reg ## _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte2 ## _SHIFT) & \ + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK ## rank ## _ ## reg ## \ + _OB_DDLL_LONG_DQ_RANK ## rank ## _BYTE ## byte2 ## _MASK) \ + + + uint32_t temp = 0; + uint32_t dst_rate_mhz = dst_timing->rate_khz / 1000; + int tree_delta[4] = {0}; + u32 tree_delta_taps[4] = {0}; + int adj[] = { + static_cast<int>(TRIM_REG(0, 0, 0, 0)), + static_cast<int>(TRIM_REG(0, 0, 0, 1)), + static_cast<int>(TRIM_REG(0, 0, 1, 2)), + static_cast<int>(TRIM_REG(0, 0, 1, 3)), + + static_cast<int>(TRIM_REG(1, 0, 2, 4)), + static_cast<int>(TRIM_REG(1, 0, 2, 5)), + static_cast<int>(TRIM_REG(1, 0, 3, 6)), + static_cast<int>(TRIM_REG(1, 0, 3, 7)), + + static_cast<int>(TRIM_REG(0, 1, 0, 0)), + static_cast<int>(TRIM_REG(0, 1, 0, 1)), + static_cast<int>(TRIM_REG(0, 1, 1, 2)), + static_cast<int>(TRIM_REG(0, 1, 1, 3)), + + static_cast<int>(TRIM_REG(1, 1, 2, 4)), + static_cast<int>(TRIM_REG(1, 1, 2, 5)), + static_cast<int>(TRIM_REG(1, 1, 3, 6)), + static_cast<int>(TRIM_REG(1, 1, 3, 7)) + }; + + switch (offset) { + case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0: + case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1: + case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2: + case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3: + case EMC_DATA_BRLSHFT_0: + tree_delta[0] = 128 * (dst_timing->current_dram_clktree_c0d0u0 - dst_timing->trained_dram_clktree_c0d0u0); + tree_delta[1] = 128 * (dst_timing->current_dram_clktree_c0d0u1 - dst_timing->trained_dram_clktree_c0d0u1); + tree_delta[2] = 128 * (dst_timing->current_dram_clktree_c1d0u0 - dst_timing->trained_dram_clktree_c1d0u0); + tree_delta[3] = 128 * (dst_timing->current_dram_clktree_c1d0u1 - dst_timing->trained_dram_clktree_c1d0u1); + tree_delta_taps[0] = (tree_delta[0] * static_cast<int>(dst_rate_mhz)) / 1000000; + tree_delta_taps[1] = (tree_delta[1] * static_cast<int>(dst_rate_mhz)) / 1000000; + tree_delta_taps[2] = (tree_delta[2] * static_cast<int>(dst_rate_mhz)) / 1000000; + tree_delta_taps[3] = (tree_delta[3] * static_cast<int>(dst_rate_mhz)) / 1000000; + + for (int i = 0; i < 4; i++) { + if ((tree_delta_taps[i] > dst_timing->tree_margin) || (tree_delta_taps[i] < (-1 * dst_timing->tree_margin))) { + adj[i * 2] = adj[i * 2] + tree_delta_taps[i]; + adj[i * 2 + 1] = adj[i * 2 + 1] + tree_delta_taps[i]; + } + } + + if (offset == EMC_DATA_BRLSHFT_0) { + for (int i = 0; i < 8; i++) { + adj[i] = adj[i] / 64; + } + } else { + for (int i = 0; i < 8; i++) { + adj[i] = adj[i] % 64; + } + } + break; + case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0: + case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1: + case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2: + case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3: + case EMC_DATA_BRLSHFT_1: + tree_delta[0] = 128 * (dst_timing->current_dram_clktree_c0d1u0 - dst_timing->trained_dram_clktree_c0d1u0); + tree_delta[1] = 128 * (dst_timing->current_dram_clktree_c0d1u1 - dst_timing->trained_dram_clktree_c0d1u1); + tree_delta[2] = 128 * (dst_timing->current_dram_clktree_c1d1u0 - dst_timing->trained_dram_clktree_c1d1u0); + tree_delta[3] = 128 * (dst_timing->current_dram_clktree_c1d1u1 - dst_timing->trained_dram_clktree_c1d1u1); + tree_delta_taps[0] = (tree_delta[0] * static_cast<int>(dst_rate_mhz)) / 1000000; + tree_delta_taps[1] = (tree_delta[1] * static_cast<int>(dst_rate_mhz)) / 1000000; + tree_delta_taps[2] = (tree_delta[2] * static_cast<int>(dst_rate_mhz)) / 1000000; + tree_delta_taps[3] = (tree_delta[3] * static_cast<int>(dst_rate_mhz)) / 1000000; + + for (int i = 0; i < 4; i++) { + if ((tree_delta_taps[i] > dst_timing->tree_margin) || (tree_delta_taps[i] < (-1 * dst_timing->tree_margin))) { + adj[8 + i * 2] = adj[8 + i * 2] + tree_delta_taps[i]; + adj[8 + i * 2 + 1] = adj[8 + i * 2 + 1] + tree_delta_taps[i]; + } + } + + if (offset == EMC_DATA_BRLSHFT_1) { + for (int i = 0; i < 8; i++) { + adj[i + 8] = adj[i + 8] / 64; + } + } else { + for (int i = 0; i < 8; i++) { + adj[i + 8] = adj[i + 8] % 64; + } + } + break; + } + + switch (offset) { + case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0: + temp = CALC_TEMP(0, 0, 0, 1, 0); + break; + case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1: + temp = CALC_TEMP(0, 1, 2, 3, 2); + break; + case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2: + temp = CALC_TEMP(0, 2, 4, 5, 4); + break; + case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3: + temp = CALC_TEMP(0, 3, 6, 7, 6); + break; + case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0: + temp = CALC_TEMP(1, 0, 0, 1, 8); + break; + case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1: + temp = CALC_TEMP(1, 1, 2, 3, 10); + break; + case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2: + temp = CALC_TEMP(1, 2, 4, 5, 12); + break; + case EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3: + temp = CALC_TEMP(1, 3, 6, 7, 14); + break; + case EMC_DATA_BRLSHFT_0: + temp = ((adj[0] << + EMC_DATA_BRLSHFT_0_RANK0_BYTE0_DATA_BRLSHFT_SHIFT) & + EMC_DATA_BRLSHFT_0_RANK0_BYTE0_DATA_BRLSHFT_MASK) | + ((adj[1] << + EMC_DATA_BRLSHFT_0_RANK0_BYTE1_DATA_BRLSHFT_SHIFT) & + EMC_DATA_BRLSHFT_0_RANK0_BYTE1_DATA_BRLSHFT_MASK) | + ((adj[2] << + EMC_DATA_BRLSHFT_0_RANK0_BYTE2_DATA_BRLSHFT_SHIFT) & + EMC_DATA_BRLSHFT_0_RANK0_BYTE2_DATA_BRLSHFT_MASK) | + ((adj[3] << + EMC_DATA_BRLSHFT_0_RANK0_BYTE3_DATA_BRLSHFT_SHIFT) & + EMC_DATA_BRLSHFT_0_RANK0_BYTE3_DATA_BRLSHFT_MASK) | + ((adj[4] << + EMC_DATA_BRLSHFT_0_RANK0_BYTE4_DATA_BRLSHFT_SHIFT) & + EMC_DATA_BRLSHFT_0_RANK0_BYTE4_DATA_BRLSHFT_MASK) | + ((adj[5] << + EMC_DATA_BRLSHFT_0_RANK0_BYTE5_DATA_BRLSHFT_SHIFT) & + EMC_DATA_BRLSHFT_0_RANK0_BYTE5_DATA_BRLSHFT_MASK) | + ((adj[6] << + EMC_DATA_BRLSHFT_0_RANK0_BYTE6_DATA_BRLSHFT_SHIFT) & + EMC_DATA_BRLSHFT_0_RANK0_BYTE6_DATA_BRLSHFT_MASK) | + ((adj[7] << + EMC_DATA_BRLSHFT_0_RANK0_BYTE7_DATA_BRLSHFT_SHIFT) & + EMC_DATA_BRLSHFT_0_RANK0_BYTE7_DATA_BRLSHFT_MASK); + break; + case EMC_DATA_BRLSHFT_1: + temp = ((adj[8] << + EMC_DATA_BRLSHFT_1_RANK1_BYTE0_DATA_BRLSHFT_SHIFT) & + EMC_DATA_BRLSHFT_1_RANK1_BYTE0_DATA_BRLSHFT_MASK) | + ((adj[9] << + EMC_DATA_BRLSHFT_1_RANK1_BYTE1_DATA_BRLSHFT_SHIFT) & + EMC_DATA_BRLSHFT_1_RANK1_BYTE1_DATA_BRLSHFT_MASK) | + ((adj[10] << + EMC_DATA_BRLSHFT_1_RANK1_BYTE2_DATA_BRLSHFT_SHIFT) & + EMC_DATA_BRLSHFT_1_RANK1_BYTE2_DATA_BRLSHFT_MASK) | + ((adj[11] << + EMC_DATA_BRLSHFT_1_RANK1_BYTE3_DATA_BRLSHFT_SHIFT) & + EMC_DATA_BRLSHFT_1_RANK1_BYTE3_DATA_BRLSHFT_MASK) | + ((adj[12] << + EMC_DATA_BRLSHFT_1_RANK1_BYTE4_DATA_BRLSHFT_SHIFT) & + EMC_DATA_BRLSHFT_1_RANK1_BYTE4_DATA_BRLSHFT_MASK) | + ((adj[13] << + EMC_DATA_BRLSHFT_1_RANK1_BYTE5_DATA_BRLSHFT_SHIFT) & + EMC_DATA_BRLSHFT_1_RANK1_BYTE5_DATA_BRLSHFT_MASK) | + ((adj[14] << + EMC_DATA_BRLSHFT_1_RANK1_BYTE6_DATA_BRLSHFT_SHIFT) & + EMC_DATA_BRLSHFT_1_RANK1_BYTE6_DATA_BRLSHFT_MASK) | + ((adj[15] << + EMC_DATA_BRLSHFT_1_RANK1_BYTE7_DATA_BRLSHFT_SHIFT) & + EMC_DATA_BRLSHFT_1_RANK1_BYTE7_DATA_BRLSHFT_MASK); + break; + default: + break; + } + + #undef TRIM_REG + #undef CALC_TEMP + + return temp; + } + + uint32_t DvfsPowerRampDown(bool flip_backward, EmcDvfsTimingTable *src_timing, EmcDvfsTimingTable *dst_timing, uint32_t clk) { + uint32_t ramp_down_wait = 0; + uint32_t seq_wait = 0; + uint32_t pmacro_cmd_pad = 0; + uint32_t pmacro_dq_pad = 0; + uint32_t pmacro_cfg5 = 0; + uint32_t pmacro_rfu1 = 0; + uint32_t pmacro_common_tx = 0; + + if (flip_backward) { + pmacro_cmd_pad = dst_timing->burst_regs.emc_pmacro_cmd_pad_tx_ctrl; + pmacro_dq_pad = dst_timing->burst_regs.emc_pmacro_data_pad_tx_ctrl; + pmacro_rfu1 = dst_timing->burst_regs.emc_pmacro_brick_ctrl_rfu1; + pmacro_cfg5 = dst_timing->burst_regs.emc_fbio_cfg5; + pmacro_common_tx = dst_timing->burst_regs.emc_pmacro_common_pad_tx_ctrl; + } else { + pmacro_cmd_pad = src_timing->burst_regs.emc_pmacro_cmd_pad_tx_ctrl; + pmacro_dq_pad = ((dst_timing->burst_regs.emc_pmacro_data_pad_tx_ctrl & 0x101) | src_timing->burst_regs.emc_pmacro_data_pad_tx_ctrl); + pmacro_rfu1 = src_timing->burst_regs.emc_pmacro_brick_ctrl_rfu1; + pmacro_cfg5 = src_timing->burst_regs.emc_fbio_cfg5; + pmacro_common_tx = src_timing->burst_regs.emc_pmacro_common_pad_tx_ctrl; + } + + pmacro_cmd_pad |= (1 << 26); + + CcfifoWrite(EMC_PMACRO_CMD_PAD_TX_CTRL, pmacro_cmd_pad, 0); + CcfifoWrite(EMC_FBIO_CFG5, pmacro_cfg5 | (1 << 8), 12); + + ramp_down_wait = clk * 12; + seq_wait = (100000 / clk) + 1; + + if (clk < (1000000 / 1000)) { + if (clk < (1000000 / 2400)) { + pmacro_cmd_pad &= ~((1 << 1) | (1 << 24)); + pmacro_cmd_pad |= (1 << 9) | (1 << 16); + + CcfifoWrite(EMC_PMACRO_CMD_PAD_TX_CTRL, pmacro_cmd_pad, seq_wait); + ramp_down_wait += 100000; + + pmacro_dq_pad &= ~((1 << 1) | (1 << 24)); + pmacro_dq_pad |= (1 << 9) | (1 << 16); + + CcfifoWrite(EMC_PMACRO_DATA_PAD_TX_CTRL, pmacro_dq_pad, 0); + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, pmacro_rfu1 & ~0x01120112, 0); + } else { + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, pmacro_rfu1 & ~0x01120112, seq_wait); + ramp_down_wait += 100000; + } + + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, pmacro_rfu1 & ~0x01bf01bf, seq_wait); + ramp_down_wait += 100000; + + if (clk < (1000000 / 2400)) { + pmacro_cmd_pad &= ~((1 << 1) | (1 << 24) | (1 << 9) | (1 << 16)); + + CcfifoWrite(EMC_PMACRO_CMD_PAD_TX_CTRL, pmacro_cmd_pad, seq_wait); + ramp_down_wait += 100000; + + pmacro_dq_pad &= ~((1 << 1) | (1 << 24) | (1 << 9) | (1 << 16)); + + CcfifoWrite(EMC_PMACRO_DATA_PAD_TX_CTRL, pmacro_dq_pad, 0); + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, pmacro_rfu1 & ~0x07ff07ff, 0); + } else { + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, pmacro_rfu1 & ~0x07ff07ff, seq_wait); + ramp_down_wait += 100000; + } + } else { + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, pmacro_rfu1 & ~0x07ff07ff, seq_wait + 19); + ramp_down_wait += (100000 + (20 * clk)); + } + + if (clk < (1000000 / 600)) { + ramp_down_wait += 100000; + CcfifoWrite(EMC_PMACRO_COMMON_PAD_TX_CTRL, pmacro_common_tx & ~0x5, seq_wait); + ramp_down_wait += 100000; + CcfifoWrite(EMC_PMACRO_COMMON_PAD_TX_CTRL, pmacro_common_tx & ~0xf, seq_wait); + ramp_down_wait += 100000; + CcfifoWrite(0, 0, seq_wait); + ramp_down_wait += 100000; + } else { + CcfifoWrite(EMC_PMACRO_COMMON_PAD_TX_CTRL, pmacro_common_tx & ~0xf, seq_wait); + } + + return ramp_down_wait; + } + + uint32_t DvfsPowerRampUp(bool flip_backward, EmcDvfsTimingTable *src_timing, EmcDvfsTimingTable *dst_timing, uint32_t training, uint32_t clk) { + uint32_t ramp_up_wait = 0; + uint32_t pmacro_cmd_pad = 0; + uint32_t pmacro_dq_pad = 0; + uint32_t pmacro_cfg5 = 0; + uint32_t pmacro_rfu1 = 0; + uint32_t pmacro_common_tx = 0; + + if (flip_backward) { + pmacro_cmd_pad = src_timing->burst_regs.emc_pmacro_cmd_pad_tx_ctrl; + pmacro_dq_pad = src_timing->burst_regs.emc_pmacro_data_pad_tx_ctrl; + pmacro_rfu1 = src_timing->burst_regs.emc_pmacro_brick_ctrl_rfu1; + pmacro_cfg5 = src_timing->burst_regs.emc_fbio_cfg5; + pmacro_common_tx = src_timing->burst_regs.emc_pmacro_common_pad_tx_ctrl; + } else if (training & 3) { + pmacro_cmd_pad = dst_timing->shadow_regs_ca_train.emc_pmacro_cmd_pad_tx_ctrl; + pmacro_dq_pad = dst_timing->shadow_regs_ca_train.emc_pmacro_data_pad_tx_ctrl; + pmacro_rfu1 = dst_timing->shadow_regs_ca_train.emc_pmacro_brick_ctrl_rfu1; + pmacro_cfg5 = dst_timing->shadow_regs_ca_train.emc_fbio_cfg5; + pmacro_common_tx = dst_timing->shadow_regs_ca_train.emc_pmacro_common_pad_tx_ctrl; + } else if (training & 0xC) { + pmacro_cmd_pad = dst_timing->shadow_regs_quse_train.emc_pmacro_cmd_pad_tx_ctrl; + pmacro_dq_pad = dst_timing->shadow_regs_quse_train.emc_pmacro_data_pad_tx_ctrl; + pmacro_rfu1 = dst_timing->shadow_regs_quse_train.emc_pmacro_brick_ctrl_rfu1; + pmacro_cfg5 = dst_timing->shadow_regs_quse_train.emc_fbio_cfg5; + pmacro_common_tx = dst_timing->shadow_regs_quse_train.emc_pmacro_common_pad_tx_ctrl; + } else if (training & 0xF0) { + pmacro_cmd_pad = dst_timing->shadow_regs_rdwr_train.emc_pmacro_cmd_pad_tx_ctrl; + pmacro_dq_pad = dst_timing->shadow_regs_rdwr_train.emc_pmacro_data_pad_tx_ctrl; + pmacro_rfu1 = dst_timing->shadow_regs_rdwr_train.emc_pmacro_brick_ctrl_rfu1; + pmacro_cfg5 = dst_timing->shadow_regs_rdwr_train.emc_fbio_cfg5; + pmacro_common_tx = dst_timing->shadow_regs_rdwr_train.emc_pmacro_common_pad_tx_ctrl; + } else { + pmacro_cmd_pad = dst_timing->burst_regs.emc_pmacro_cmd_pad_tx_ctrl; + pmacro_dq_pad = dst_timing->burst_regs.emc_pmacro_data_pad_tx_ctrl; + pmacro_rfu1 = dst_timing->burst_regs.emc_pmacro_brick_ctrl_rfu1; + pmacro_cfg5 = dst_timing->burst_regs.emc_fbio_cfg5; + pmacro_common_tx = dst_timing->burst_regs.emc_pmacro_common_pad_tx_ctrl; + } + + pmacro_cmd_pad |= (1 << 26); + + if (clk < 1000000 / 600) { + CcfifoWrite(EMC_PMACRO_COMMON_PAD_TX_CTRL, pmacro_common_tx & 0xa, 0); + CcfifoWrite(EMC_PMACRO_COMMON_PAD_TX_CTRL, pmacro_common_tx & 0xf, (100000 / clk) + 1); + ramp_up_wait += 100000; + } else { + CcfifoWrite(EMC_PMACRO_COMMON_PAD_TX_CTRL, pmacro_common_tx | 0x8, 0); + } + + if (clk < 1000000 / 1000) { + if (clk < 1000000 / 2400) { + pmacro_cmd_pad |= ((1 << 9) | (1 << 16)); + pmacro_cmd_pad &= ~((1 << 1) | (1 << 24)); + + CcfifoWrite(EMC_PMACRO_CMD_PAD_TX_CTRL, pmacro_cmd_pad, (100000 / clk) + 1); + ramp_up_wait += 100000; + + pmacro_dq_pad |= ((1 << 9) | (1 << 16)); + pmacro_dq_pad &= ~((1 << 1) | (1 << 24)); + + CcfifoWrite(EMC_PMACRO_DATA_PAD_TX_CTRL, pmacro_dq_pad, 0); + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, pmacro_rfu1 & 0xfe40fe40, 0); + } else { + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, pmacro_rfu1 & 0xfe40fe40, (100000 / clk) + 1); + ramp_up_wait += 100000; + } + + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, pmacro_rfu1 & 0xfeedfeed, (100000 / clk) + 1); + ramp_up_wait += 100000; + + if (clk < 1000000 / 2400) { + pmacro_cmd_pad |= ((1 << 9) | (1 << 16) | (1 << 1) | (1 << 24)); + + CcfifoWrite(EMC_PMACRO_CMD_PAD_TX_CTRL, pmacro_cmd_pad, (100000 / clk) + 1); + ramp_up_wait += 100000; + + pmacro_dq_pad |= ((1 << 9) | (1 << 16) | (1 << 1) | (1 << 24)); + + CcfifoWrite(EMC_PMACRO_DATA_PAD_TX_CTRL, pmacro_dq_pad, 0); + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, pmacro_rfu1, 0); + } else { + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, pmacro_rfu1, (100000 / clk) + 1); + ramp_up_wait += 100000; + } + + CcfifoWrite(EMC_FBIO_CFG5, pmacro_cfg5 & ~(1 << 8), (100000 / clk) + 10); + ramp_up_wait += (100000 + (10 * clk)); + } else if (clk < 1000000 / 600) { + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, pmacro_rfu1 | 0x06000600, (100000 / clk) + 1); + CcfifoWrite(EMC_FBIO_CFG5, pmacro_cfg5 & ~(1 << 8), (100000 / clk) + 10); + ramp_up_wait += (100000 + 10 * clk); + } else { + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, pmacro_rfu1 | 0x00000600, 0); + CcfifoWrite(EMC_FBIO_CFG5, pmacro_cfg5 & ~(1 << 8), 12); + ramp_up_wait += (12 * clk); + } + + pmacro_cmd_pad &= ~(1 << 26); + CcfifoWrite(EMC_PMACRO_CMD_PAD_TX_CTRL, pmacro_cmd_pad, 5); + + return ramp_up_wait; + } + + void FreqChange(EmcDvfsTimingTable *src_timing, EmcDvfsTimingTable *dst_timing, u32 training, u32 next_clk_src) { + /* Extract training values. */ + const bool train_ca = (training & CA_TRAINING); + const bool train_ca_vref = (training & CA_VREF_TRAINING); + const bool train_quse = (training & QUSE_TRAINING); + const bool train_quse_vref = (training & QUSE_VREF_TRAINING); + const bool train_wr = (training & WRITE_TRAINING); + const bool train_wr_vref = (training & WRITE_VREF_TRAINING); + const bool train_rd = (training & READ_TRAINING); + const bool train_rd_vref = (training & READ_VREF_TRAINING); + const bool train_second_rank = (training & TRAIN_SECOND_RANK); + const bool train_bit_level = (training & BIT_LEVEL_TRAINING); + + /* Check if we should do training. */ + const bool training_enabled = (training & (CA_TRAINING | CA_VREF_TRAINING | QUSE_TRAINING | WRITE_TRAINING | WRITE_VREF_TRAINING | READ_TRAINING | READ_VREF_TRAINING)); + + /* Declare variables. */ + bool skip_zqcal = false; + bool compensate_trimmer_applicable = false; + uint32_t zqcal_before_cc_cutoff = 2400; /* In picoseconds */ + int zq_latch_dvfs_wait_time; + + uint32_t mr13_catr_enable; + uint32_t mr13_flip_fspwr; + uint32_t mr13_flip_fspop; + + int next_push, next_dq_e_ivref, next_dqs_e_ivref; + + uint32_t zq_wait_long; + uint32_t zq_wait_short; + + uint32_t tRTM; + uint32_t RP_war; + uint32_t R2P_war; + uint32_t TRPab_war; + int nRTP; + uint32_t deltaTWATM; + uint32_t W2P_war; + uint32_t tRPST; + + uint32_t mrw_req; + uint32_t adel = 0; + uint32_t dst_rate_mhz = dst_timing->rate_khz / 1000; + + /* Set some common values needed. */ + const int dram_type = reg::GetValue(EMC + EMC_FBIO_CFG5, EMC_REG_BITS_MASK(FBIO_CFG5_DRAM_TYPE)); + const int dram_dev_num = (reg::Read(MC + MC_EMEM_ADR_CFG) & 1) + 1; + const bool shared_zq_resistor = ((src_timing->burst_regs.emc_zcal_wait_cnt >> 31) & 1); + const u32 fbio_cfg7 = src_timing->burst_regs.emc_fbio_cfg7; + const bool is_lpddr3 = (dram_type == DRAM_TYPE_LPDDR2) && ((dst_timing->burst_regs.emc_fbio_cfg5 >> 25) & 1); + bool opt_zcal_en_cc = ((dst_timing->burst_regs.emc_zcal_interval && !src_timing->burst_regs.emc_zcal_interval) || (dram_type == DRAM_TYPE_LPDDR4)); + bool opt_war_200024907 = (dram_type == DRAM_TYPE_LPDDR4); + bool opt_do_sw_qrst = false; + bool opt_cc_short_zcal = true; + bool opt_short_zcal = true; + bool save_restore_clkstop_pd = true; + uint32_t opt_dll_mode = (dram_type == DRAM_TYPE_DDR4) ? GetDllState(dst_timing) : DLL_OFF; + uint32_t opt_dvfs_mode = MAN_SR; + uint32_t emc_auto_cal_config = reg::Read(EMC + EMC_AUTO_CAL_CONFIG); + + /* In picoseconds. */ + uint32_t source_clock_period = 1000000000 / src_timing->rate_khz; + uint32_t destination_clock_period = 1000000000 / dst_timing->rate_khz; + + uint32_t tFC_lpddr4 = 1000 * dst_timing->dram_timings.t_fc_lpddr4; + uint32_t tZQCAL_lpddr4 = 1000000; + int tZQCAL_lpddr4_fc_adj = (source_clock_period > zqcal_before_cc_cutoff) ? tZQCAL_lpddr4 / destination_clock_period : (tZQCAL_lpddr4 - tFC_lpddr4) / destination_clock_period; + + g_fsp_for_next_freq = !g_fsp_for_next_freq; + + uint32_t emc_dbg_o = reg::Read(EMC + EMC_DBG); + uint32_t emc_pin_o = reg::Read(EMC + EMC_PIN); + uint32_t emc_cfg_pipe_clk_o = reg::Read(EMC + EMC_CFG_PIPE_CLK); + uint32_t emc_dbg = emc_dbg_o; + + uint32_t emc_cfg = dst_timing->burst_regs.emc_cfg & 0x0FFFFFFF; + uint32_t emc_sel_dpd_ctrl = dst_timing->emc_sel_dpd_ctrl & 0xFFFFFEC3; + + /* Step 1.1: Disable DLL. */ + DllDisable(fbio_cfg7); + + /* Step 1.2: Disable AUTOCAL. */ + emc_auto_cal_config = dst_timing->emc_auto_cal_config; + u32 auto_cal_en = (emc_auto_cal_config & (1 << 29)); + emc_auto_cal_config &= 0x7FFFF9FF; + emc_auto_cal_config |= 0x600; + reg::Write(EMC + EMC_AUTO_CAL_CONFIG, emc_auto_cal_config); + + /* Step 1.3: Disable other power features. */ + SetShadowBypass(ACTIVE); + reg::Write(EMC + EMC_CFG, emc_cfg); + reg::Write(EMC + EMC_SEL_DPD_CTRL, emc_sel_dpd_ctrl); + SetShadowBypass(ASSEMBLY); + + /* Skip this if training_enabled is set. */ + if (!training_enabled && dst_timing->periodic_training) { + /* Wait for DRAM to get out of power down. */ + if (dram_dev_num == TWO_RANK) { + WaitForUpdate(EMC_EMC_STATUS, 0x30, false, fbio_cfg7); + } else { + WaitForUpdate(EMC_EMC_STATUS, 0x10, false, fbio_cfg7); + } + + /* Wait for DRAM to get out of self refresh. */ + WaitForUpdate(EMC_EMC_STATUS, 0x300, false, fbio_cfg7); + + /* Reset all clock tree values. */ + dst_timing->current_dram_clktree_c0d0u0 = dst_timing->trained_dram_clktree_c0d0u0; + dst_timing->current_dram_clktree_c0d0u1 = dst_timing->trained_dram_clktree_c0d0u1; + dst_timing->current_dram_clktree_c0d1u0 = dst_timing->trained_dram_clktree_c0d1u0; + dst_timing->current_dram_clktree_c0d1u1 = dst_timing->trained_dram_clktree_c0d1u1; + dst_timing->current_dram_clktree_c1d0u0 = dst_timing->trained_dram_clktree_c1d0u0; + dst_timing->current_dram_clktree_c1d0u1 = dst_timing->trained_dram_clktree_c1d0u1; + dst_timing->current_dram_clktree_c1d1u0 = dst_timing->trained_dram_clktree_c1d1u0; + dst_timing->current_dram_clktree_c1d1u1 = dst_timing->trained_dram_clktree_c1d1u1; + + /* Do DVFS_SEQUENCE. */ + adel = PeriodicCompensationHandler(DVFS_SEQUENCE, dram_dev_num, fbio_cfg7, src_timing, dst_timing); + + /* Check if we should use compensate trimmer. */ + compensate_trimmer_applicable = dst_timing->periodic_training && ((adel * 128 * dst_rate_mhz) / 1000000) > dst_timing->tree_margin; + } + + reg::Write(EMC + EMC_INTSTATUS, 0x10); + SetShadowBypass(ACTIVE); + reg::Write(EMC + EMC_CFG, emc_cfg); + reg::Write(EMC + EMC_SEL_DPD_CTRL, emc_sel_dpd_ctrl); + reg::Write(EMC + EMC_CFG_PIPE_CLK, emc_cfg_pipe_clk_o | 0x1); + reg::Write(EMC + EMC_FDPD_CTRL_CMD_NO_RAMP, dst_timing->emc_fdpd_ctrl_cmd_no_ramp & ~0x1); + + uint32_t bg_regulator_mode_change = ((dst_timing->burst_regs.emc_pmacro_bg_bias_ctrl_0 & (1 << 2)) ^ (src_timing->burst_regs.emc_pmacro_bg_bias_ctrl_0 & (1 << 2))) || ((dst_timing->burst_regs.emc_pmacro_bg_bias_ctrl_0 & (1 << 0)) ^ (src_timing->burst_regs.emc_pmacro_bg_bias_ctrl_0 & (1 << 0))); + + uint32_t enable_bg_regulator = (dst_timing->burst_regs.emc_pmacro_bg_bias_ctrl_0 & (1 << 0)) == 0; + + /* Check if we need to change BG the regulator. */ + if (bg_regulator_mode_change) { + if (enable_bg_regulator) { + reg::Write(EMC + EMC_PMACRO_BG_BIAS_CTRL_0, src_timing->burst_regs.emc_pmacro_bg_bias_ctrl_0 & ~(1 << 0)); + } else { + reg::Write(EMC + EMC_PMACRO_BG_BIAS_CTRL_0, src_timing->burst_regs.emc_pmacro_bg_bias_ctrl_0 & ~(1 << 2)); + } + } + + /* Check if we need to turn on VREF generator. */ + if ((((!(src_timing->burst_regs.emc_pmacro_data_pad_tx_ctrl & (1 << 0)))) && + ((dst_timing->burst_regs.emc_pmacro_data_pad_tx_ctrl & (1 << 0)))) || + ((!(src_timing->burst_regs.emc_pmacro_data_pad_tx_ctrl & (1 << 8))) && + ((dst_timing->burst_regs.emc_pmacro_data_pad_tx_ctrl & (1 << 8))))) + { + uint32_t pad_tx_ctrl = dst_timing->burst_regs.emc_pmacro_data_pad_tx_ctrl; + uint32_t last_pad_tx_ctrl = src_timing->burst_regs.emc_pmacro_data_pad_tx_ctrl; + + next_dqs_e_ivref = pad_tx_ctrl & (1 << 8); + next_dq_e_ivref = pad_tx_ctrl & (1 << 0); + next_push = (last_pad_tx_ctrl & ~(1 << 0) & ~(1 << 8)) | next_dq_e_ivref | next_dqs_e_ivref; + reg::Write(EMC + EMC_PMACRO_DATA_PAD_TX_CTRL, next_push); + util::WaitMicroSeconds(1); + } else if (bg_regulator_mode_change) { + util::WaitMicroSeconds(1); + } + + SetShadowBypass(ASSEMBLY); + + /* Step 2: + * Prelock the DLL. + */ + if (dst_timing->burst_regs.emc_cfg_dig_dll & 0x1) { + DllPrelock(dst_timing, training_enabled, next_clk_src); + } else { + ChangeDllSrc(dst_timing, next_clk_src); + DllDisable(fbio_cfg7); + } + + /* Step 3: + * Prepare autocal for the clock change. + */ + SetShadowBypass(ACTIVE); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG2, dst_timing->emc_auto_cal_config2); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG3, dst_timing->emc_auto_cal_config3); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG4, dst_timing->emc_auto_cal_config4); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG5, dst_timing->emc_auto_cal_config5); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG6, dst_timing->emc_auto_cal_config6); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG7, dst_timing->emc_auto_cal_config7); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG8, dst_timing->emc_auto_cal_config8); + SetShadowBypass(ASSEMBLY); + + emc_auto_cal_config |= (0x1 | auto_cal_en); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG, emc_auto_cal_config); + + /* Step 4: + * Update EMC_CFG. + */ + if ((source_clock_period > 50000) && (dram_type == DRAM_TYPE_LPDDR4)) { + CcfifoWrite(EMC_SELF_REF, 1, 0); + } else { + reg::Write(EMC + EMC_CFG_2, dst_timing->emc_cfg_2); + } + + /* Step 5: + * Prepare reference variables for ZQCAL regs. + */ + uint32_t emc_zcal_interval = src_timing->burst_regs.emc_zcal_interval; + emc_zcal_interval &= 0xFF000000; + uint32_t emc_zcal_wait_cnt_old = src_timing->burst_regs.emc_zcal_wait_cnt; + uint32_t emc_zcal_wait_cnt_new = dst_timing->burst_regs.emc_zcal_wait_cnt; + emc_zcal_wait_cnt_old &= ~0x7ff; + emc_zcal_wait_cnt_new &= ~0x7ff; + + if (dram_type == DRAM_TYPE_LPDDR4) { + zq_wait_long = std::max<u32>(1, util::DivideUp(1000000, destination_clock_period)); + } else if (dram_type == DRAM_TYPE_LPDDR2 || is_lpddr3) { + zq_wait_long = std::max<u32>(dst_timing->min_mrs_wait, util::DivideUp(360000, destination_clock_period)) + 4; + } else if (dram_type == DRAM_TYPE_DDR4) { + zq_wait_long = std::max<u32>(256, util::DivideUp(320000, destination_clock_period) + 2); + } else { + zq_wait_long = 0; + } + + + if (dram_type == DRAM_TYPE_LPDDR2 || is_lpddr3) { + zq_wait_short = std::max<u32>(std::max<u32>(dst_timing->min_mrs_wait, 6), util::DivideUp(90000, destination_clock_period)) + 4; + } else if (dram_type == DRAM_TYPE_DDR4) { + zq_wait_short = std::max<u32>(64, util::DivideUp(80000, destination_clock_period)) + 2; + } else { + zq_wait_short = 0; + } + + /* TODO: Actually use the reference variables. */ + AMS_UNUSED(zq_wait_long, zq_wait_short); + + /* Step 6: + * Training code. + */ + if ((train_ca || train_ca_vref) && (dram_dev_num == TWO_RANK)) { + reg::Write(EMC + EMC_PIN, 0x107); + } + + /* Step 7: + * Program FSP reference registers and send MRWs to new FSPWR. + */ + + /* Step 7.1: Bug 200024907 - Patch RP R2P */ + if (opt_war_200024907) { + nRTP = 16; + if (source_clock_period >= 1000000/1866) /* 535.91 ps */ + nRTP = 14; + if (source_clock_period >= 1000000/1600) /* 625.00 ps */ + nRTP = 12; + if (source_clock_period >= 1000000/1333) /* 750.19 ps */ + nRTP = 10; + if (source_clock_period >= 1000000/1066) /* 938.09 ps */ + nRTP = 8; + + deltaTWATM = std::max<u32>(util::DivideUp(7500, source_clock_period), 8); + + /* + * Originally there was a + .5 in the tRPST calculation. + * However since we can't do FP in the kernel and the tRTM + * computation was in a floating point ceiling function, adding + * one to tRTP should be ok. There is no other source of non + * integer values, so the result was always going to be + * something for the form: f_ceil(N + .5) = N + 1; + */ + tRPST = ((src_timing->emc_mrw & 0x80) >> 7); + tRTM = src_timing->dram_timings.rl + util::DivideUp(3600, source_clock_period) + std::max<u32>(util::DivideUp(7500, source_clock_period), 8) + tRPST + 1 + nRTP; + + if (src_timing->burst_regs.emc_rp < tRTM) { + if (tRTM > (src_timing->burst_regs.emc_r2p + src_timing->burst_regs.emc_rp)) { + R2P_war = tRTM - src_timing->burst_regs.emc_rp; + RP_war = src_timing->burst_regs.emc_rp; + TRPab_war = src_timing->burst_regs.emc_trpab; + if (R2P_war > 63) { + RP_war = R2P_war + src_timing->burst_regs.emc_rp - 63; + if (TRPab_war < RP_war) + TRPab_war = RP_war; + R2P_war = 63; + } + } else { + R2P_war = src_timing-> burst_regs.emc_r2p; + RP_war = src_timing->burst_regs.emc_rp; + TRPab_war = src_timing->burst_regs.emc_trpab; + } + + if (RP_war < deltaTWATM) { + W2P_war = src_timing->burst_regs.emc_w2p + deltaTWATM - RP_war; + if (W2P_war > 63) { + RP_war = RP_war + W2P_war - 63; + if (TRPab_war < RP_war) + TRPab_war = RP_war; + W2P_war = 63; + } + } else { + W2P_war = src_timing->burst_regs.emc_w2p; + } + + if ((src_timing->burst_regs.emc_w2p != W2P_war) + || (src_timing->burst_regs.emc_r2p != R2P_war) + || (src_timing->burst_regs.emc_rp != RP_war) + || (src_timing->burst_regs.emc_trpab != TRPab_war)) + { + SetShadowBypass(ACTIVE); + reg::Write(EMC + EMC_RP, RP_war); + reg::Write(EMC + EMC_R2P, R2P_war); + reg::Write(EMC + EMC_W2P, W2P_war); + reg::Write(EMC + EMC_TRPAB, TRPab_war); + SetShadowBypass(ASSEMBLY); + util::WaitMicroSeconds(1); + } + } + } + + if (!g_fsp_for_next_freq) { + mr13_flip_fspwr = (dst_timing->emc_mrw3 & 0xffffff3f) | 0x80; + mr13_flip_fspop = (dst_timing->emc_mrw3 & 0xffffff3f) | 0x00; + } else { + mr13_flip_fspwr = (dst_timing->emc_mrw3 & 0xffffff3f) | 0x40; + mr13_flip_fspop = (dst_timing->emc_mrw3 & 0xffffff3f) | 0xc0; + } + + mr13_catr_enable = (mr13_flip_fspwr & 0xFFFFFFFE) | 0x01; + + if (dram_dev_num == TWO_RANK) { + if (train_ca || train_ca_vref) { + if (train_second_rank) { + mr13_flip_fspop = (mr13_flip_fspop & 0x3FFFFFFF) | 0x80000000; + mr13_catr_enable = (mr13_catr_enable & 0x3FFFFFFF)| 0x40000000; + } else { + mr13_flip_fspop = (mr13_flip_fspop & 0x3FFFFFFF) | 0x40000000; + mr13_catr_enable = (mr13_catr_enable & 0x3FFFFFFF) | 0x80000000; + } + } else { + if (train_second_rank) { + mr13_catr_enable = (mr13_catr_enable & 0x3FFFFFFF) | 0x40000000; + } else { + mr13_catr_enable = (mr13_catr_enable & 0x3FFFFFFF) | 0x80000000; + } + } + } + + if (dram_type == DRAM_TYPE_LPDDR4) { + reg::Write(EMC + EMC_MRW3, mr13_flip_fspwr); + reg::Write(EMC + EMC_MRW, dst_timing->emc_mrw); + reg::Write(EMC + EMC_MRW2, dst_timing->emc_mrw2); + } + + /* Step 8: + * Program the shadow registers. + */ + + /* Set burst registers. */ + for (u32 i = 0; i < dst_timing->num_burst; i++) { + uint32_t var = 0; + uint32_t wval = 0; + + if (!BurstRegistersOffsets[i]) { + continue; + } + + var = BurstRegistersOffsets[i]; + + if (train_ca || train_ca_vref) { + wval = dst_timing->shadow_regs_ca_train_arr[i]; + } else if (train_quse || train_quse_vref) { + wval = dst_timing->shadow_regs_quse_train_arr[i]; + } else if (train_wr || train_wr_vref || train_rd || train_rd_vref) { + wval = dst_timing->shadow_regs_rdwr_train_arr[i]; + } else { + wval = dst_timing->burst_regs_arr[i]; + } + + + if (dram_type != DRAM_TYPE_LPDDR4 && + (var == EMC_MRW6 || var == EMC_MRW7 || + var == EMC_MRW8 || var == EMC_MRW9 || + var == EMC_MRW10 || var == EMC_MRW11 || + var == EMC_MRW12 || var == EMC_MRW13 || + var == EMC_MRW14 || var == EMC_MRW15 || + var == EMC_TRAINING_CTRL)) + { + continue; + } + + if (var == EMC_CFG) { + wval &= (dram_type == DRAM_TYPE_LPDDR4) ? 0x0FFFFFFF : 0xCFFFFFFF; + } else if ((var == EMC_ZCAL_INTERVAL) && opt_zcal_en_cc) { + wval = 0; /* EMC_ZCAL_INTERVAL reset value. */ + } else if (var == EMC_PMACRO_AUTOCAL_CFG_COMMON) { + wval |= (1 << 16); + } else if (var == EMC_PMACRO_DATA_PAD_TX_CTRL) { + wval &= 0xFEFEFDFD; + } else if (var == EMC_PMACRO_CMD_PAD_TX_CTRL) { + wval &= 0xFAFEFDFD; + wval |= 0x04000000; + } else if (var == EMC_PMACRO_BRICK_CTRL_RFU1) { + wval &= 0xf800f800; + } else if (var == EMC_PMACRO_COMMON_PAD_TX_CTRL) { + wval &= 0xfffffff0; + } else if (var == EMC_TRAINING_CTRL) { + wval |= train_second_rank ? (1 << 14) : 0; + } + + reg::Write(EMC + var, wval); + } + + if (dram_type == DRAM_TYPE_LPDDR4) { + /* Use the current timing when training. */ + if (training_enabled) { + mrw_req = (23 << 16) | (src_timing->run_clocks & 0xFF); + } else { + mrw_req = (23 << 16) | (dst_timing->run_clocks & 0xFF); + } + + reg::Write(EMC + EMC_MRW, mrw_req); + } + + /* Per channel burst registers. */ + const bool dual_channel = reg::GetField(fbio_cfg7, EMC_REG_BITS_MASK(FBIO_CFG7_CH1_ENABLE)) == EMC_FBIO_CFG7_CH1_ENABLE_ENABLE; + for (u32 i = 0; i < dst_timing->num_burst_per_ch; i++) { + if (!PerChannelBurstRegisters[i]) { + continue; + } + + const u32 addr = PerChannelBurstRegisters[i]; + const u32 base = addr & ~0xFFF; + const u32 off = addr & 0xFFF; + + if (dram_type != DRAM_TYPE_LPDDR4 && + (off == EMC_MRW6 || + off == EMC_MRW7 || + off == EMC_MRW8 || + off == EMC_MRW9 || + off == EMC_MRW10 || + off == EMC_MRW11 || + off == EMC_MRW12 || + off == EMC_MRW13 || + off == EMC_MRW14 || + off == EMC_MRW15) + ) + { + continue; + } + + /* Filter out second channel if not in DUAL_CHANNEL mode. */ + if (!dual_channel && base == EMC1) { + continue; + } + + /* Write the value. */ + reg::Write(addr, dst_timing->burst_perch_regs_arr[i]); + } + + /* Vref regs. */ + for (u32 i = 0; i < dst_timing->vref_num; i++) { + if (!PerChannelVrefRegisters[i]) { + continue; + } + + const u32 addr = PerChannelVrefRegisters[i]; + const u32 base = addr & ~0xFFF; + + /* Filter out second channel if not in DUAL_CHANNEL mode. */ + if (!dual_channel && base == EMC1) { + continue; + } + + /* Write the value. */ + reg::Write(addr, dst_timing->vref_perch_regs_arr[i]); + } + + /* Training regs. */ + if (training_enabled) { + for (u32 i = 0; i < dst_timing->training_mod_num; i++) { + if (!PerChannelTrainingModRegisters[i]) { + continue; + } + + const u32 addr = PerChannelTrainingModRegisters[i]; + const u32 base = addr & ~0xFFF; + + /* Filter out second channel if not in DUAL_CHANNEL mode. */ + if (!dual_channel && base == EMC1) { + continue; + } + + /* Write the value. */ + reg::Write(addr, dst_timing->training_mod_regs_arr[i]); + } + } + + /* Trimmers. */ + for (u32 i = 0; i < dst_timing->num_trim; i++) { + if (!TrimRegisters[i]) { + continue; + } + + const u32 addr = TrimRegisters[i]; + const u32 ofs = addr & 0xFFF; + + u32 wval = dst_timing->trim_regs_arr[i]; + + if (compensate_trimmer_applicable && + (ofs == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0 || + ofs == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1 || + ofs == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2 || + ofs == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3 || + ofs == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0 || + ofs == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1 || + ofs == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2 || + ofs == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3 || + ofs == EMC_DATA_BRLSHFT_0 || + ofs == EMC_DATA_BRLSHFT_1)) + { + wval = ApplyPeriodicCompensationTrimmer(dst_timing, ofs); + } + + /* Write the value. */ + reg::Write(addr, wval); + } + + /* Per-channel trimmers. */ + for (u32 i = 0; i < dst_timing->num_trim_per_ch; i++) { + if (!PerChannelTrimRegisters[i]) { + continue; + } + + const u32 addr = PerChannelTrimRegisters[i]; + const u32 base = addr & ~0xFFF; + const u32 ofs = addr & 0xFFF; + + /* Filter out second channel if not in DUAL_CHANNEL mode. */ + if (!dual_channel && base == EMC1) { + continue; + } + + u32 wval = dst_timing->trim_perch_regs_arr[i]; + + if (compensate_trimmer_applicable && + (ofs == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0 || + ofs == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1 || + ofs == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2 || + ofs == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3 || + ofs == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0 || + ofs == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1 || + ofs == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2 || + ofs == EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3 || + ofs == EMC_DATA_BRLSHFT_0 || + ofs == EMC_DATA_BRLSHFT_1)) + { + wval = ApplyPeriodicCompensationTrimmer(dst_timing, ofs); + } + + /* Write the value. */ + reg::Write(addr, wval); + } + + if (training_enabled) { + if (train_wr && dst_timing->periodic_training && (dram_type == DRAM_TYPE_LPDDR4)) { + PeriodicCompensationHandler(WRITE_TRAINING_SEQUENCE, dram_dev_num, fbio_cfg7, src_timing, dst_timing); + } + } else { + /* Write burst_mc_regs. */ + for (u32 i = 0; i < dst_timing->num_mc_regs; i++) { + reg::Write(BurstMcRegisters[i], dst_timing->burst_mc_regs_arr[i]); + } + } + + /* Registers to be programmed on the faster clock. */ + if (!training_enabled && (dst_timing->rate_khz < src_timing->rate_khz)) { + for (u32 i = 0; i < dst_timing->num_up_down; i++) { + reg::Write(LaScaleRegisters[i], dst_timing->la_scale_regs_arr[i]); + } + } + + /* Step 9: + * LPDDR4 section A. + */ + if (dram_type == DRAM_TYPE_LPDDR4) { + reg::Write(EMC + EMC_ZCAL_INTERVAL, emc_zcal_interval); + reg::Write(EMC + EMC_ZCAL_WAIT_CNT, emc_zcal_wait_cnt_new); + reg::Write(EMC + EMC_DBG, emc_dbg_o | 0x40000002); + reg::Write(EMC + EMC_ZCAL_INTERVAL, emc_zcal_interval); + reg::Write(EMC + EMC_DBG, emc_dbg_o); + + if (training_enabled) { + SetShadowBypass(ACTIVE); + + reg::Write(EMC + EMC_PMACRO_AUTOCAL_CFG_COMMON, dst_timing->burst_regs.emc_pmacro_autocal_cfg_common | (1 << 16)); + + if (train_ca || train_ca_vref) { + reg::Write(EMC + EMC_FBIO_CFG5, src_timing->burst_regs.emc_fbio_cfg5 | (1 << 27)); + } + + SetShadowBypass(ASSEMBLY); + + if (dual_channel) { + CcfifoWrite(EMC_CFG_SYNC, 0, 0); + } + + /* Change CFG_SWAP. */ + CcfifoWrite(EMC_DBG, ((emc_dbg_o & 0xF3FFFFFF) | 0x4000000), 0); + } + } + + /* Step 10: + * LPDDR4 and DDR3 common section. + */ + if (opt_dvfs_mode == MAN_SR || dram_type == DRAM_TYPE_LPDDR4) { + if (dram_type == DRAM_TYPE_LPDDR4) { + CcfifoWrite(EMC_SELF_REF, 0x101, 0); + } else { + CcfifoWrite(EMC_SELF_REF, 0x1, 0); + } + + if (!(train_ca || train_ca_vref) && (dram_type == DRAM_TYPE_LPDDR4) && (source_clock_period <= zqcal_before_cc_cutoff)) { + CcfifoWrite(EMC_MRW3, mr13_flip_fspwr ^ 0x40, 0); + CcfifoWrite(EMC_MRW6, (dst_timing->burst_regs.emc_mrw6 & 0xFFFF3F3F) | (src_timing->burst_regs.emc_mrw6 & 0x0000C0C0), 0); + CcfifoWrite(EMC_MRW14, (dst_timing->burst_regs.emc_mrw14 & 0xFFFF0707) | (src_timing->burst_regs.emc_mrw14 & 0x00003838), 0); + + if (dram_dev_num == TWO_RANK) { + CcfifoWrite(EMC_MRW7, (dst_timing->burst_regs.emc_mrw7 & 0xFFFF3F3F) | (src_timing->burst_regs.emc_mrw7 & 0x0000C0C0), 0); + CcfifoWrite(EMC_MRW15, (dst_timing->burst_regs.emc_mrw15 & 0xFFFF0707) | (src_timing->burst_regs.emc_mrw15 & 0x00003838), 0); + } + + if (opt_zcal_en_cc) { + if ((dram_dev_num == ONE_RANK) || shared_zq_resistor) { + CcfifoWrite(EMC_ZQ_CAL, 2 << 30 | (1 << 0), 0); + } else { + CcfifoWrite(EMC_ZQ_CAL, (1 << 0), 0); + } + } + } + } + + emc_dbg = emc_dbg_o; + if (dram_type == DRAM_TYPE_LPDDR4) { + if (training_enabled) { + /* Change CFG_SWAP. */ + emc_dbg = ((emc_dbg_o & 0xF3FFFFFF) | 0x4000000 | (1 << 30)); + CcfifoWrite(EMC_DBG, emc_dbg, 0); + } + if (train_ca || train_ca_vref) { + CcfifoWrite(EMC_PMACRO_DATA_RX_TERM_MODE, src_timing->burst_regs.emc_pmacro_data_rx_term_mode & 0xFFFFFCCC, 0); + + if ((dram_dev_num == TWO_RANK) && train_second_rank) { + CcfifoWrite(EMC_MRW3, mr13_flip_fspop | 0x8, (1000 * src_timing->dram_timings.t_rp) / source_clock_period); + CcfifoWrite(EMC_MRW3, mr13_catr_enable | 0x8, 0); + } else { + CcfifoWrite(EMC_MRW3, mr13_catr_enable | 0x8, (1000 * src_timing->dram_timings.t_rp) / source_clock_period); + } + + CcfifoWrite(EMC_TR_CTRL_0, 0x15A, 0); + CcfifoWrite(EMC_INTSTATUS, 0, 1000000 / source_clock_period); + } else { + CcfifoWrite(EMC_MRW3, mr13_flip_fspop | 0x8, (1000 * src_timing->dram_timings.t_rp) / source_clock_period); + CcfifoWrite(EMC_INTSTATUS, 0, tFC_lpddr4 / source_clock_period); + } + } + + bool ref_b4_sref_en = false; + bool cya_issue_pc_ref = false; + bool cya_allow_ref_cc = false; + + if ((dram_type == DRAM_TYPE_LPDDR4) || (opt_dvfs_mode != MAN_SR)) { + uint32_t t = 30 + (cya_allow_ref_cc ? (4000 * src_timing->dram_timings.t_rfc) + ((1000 * src_timing->dram_timings.t_rp) / source_clock_period) : 0); + CcfifoWrite(EMC_PIN, emc_pin_o & 0xFFFFFFF8, t); + } + + uint32_t ref_delay_mult = 1; + ref_delay_mult += ref_b4_sref_en ? 1 : 0; + ref_delay_mult += cya_allow_ref_cc ? 1 : 0; + ref_delay_mult += cya_issue_pc_ref ? 1 : 0; + uint32_t ref_delay = ref_delay_mult * ((1000 * src_timing->dram_timings.t_rp / source_clock_period) + (1000 * src_timing->dram_timings.t_rfc / source_clock_period)) + 20; + + /* Step 11: + * Ramp down. + */ + CcfifoWrite(EMC_CFG_SYNC, 0, (dram_type == DRAM_TYPE_LPDDR4) ? 0 : ref_delay); + CcfifoWrite(EMC_DBG, emc_dbg | ((1 << 1) | (1 << 30)), 0); + uint32_t ramp_down_wait = DvfsPowerRampDown(false, src_timing, dst_timing, source_clock_period); + + /* Step 12: + * Trigger the clock change. + */ + CcfifoWrite(EMC_STALL_THEN_EXE_AFTER_CLKCHANGE, 1, 0); + if (!training_enabled) { + CcfifoWrite(EMC_DBG, (emc_dbg & ~(1 << 30)) | (1 << 1), 0); + } + + /* Step 13: + * Ramp up. + */ + uint32_t ramp_up_wait = DvfsPowerRampUp(false, src_timing, dst_timing, training, destination_clock_period); + CcfifoWrite(EMC_DBG, emc_dbg, 0); + + /* Step 14: + * Bringup CKE pins. + */ + if ((dram_type == DRAM_TYPE_LPDDR4)) { + uint32_t r = emc_pin_o & 0xFFFFFFF8; + if (train_ca || train_ca_vref) { + if (dram_dev_num == TWO_RANK) { + if (train_second_rank) { + CcfifoWrite(EMC_PIN, r | 5, 0); + } else { + CcfifoWrite(EMC_PIN, r | 6, 0); + } + } else { + CcfifoWrite(EMC_PIN, r, 0); + } + } else if (dram_dev_num == TWO_RANK) { + CcfifoWrite(EMC_PIN, r | 7, 0); + } else { + CcfifoWrite(EMC_PIN, r | 1, 0); + } + } + + /* Step 15: + * Calculate zqlatch wait time; has dependency on ramping times. + */ + if (source_clock_period <= zqcal_before_cc_cutoff) { + int t = (int)(ramp_up_wait + ramp_down_wait) / (int)destination_clock_period; + zq_latch_dvfs_wait_time = (int)tZQCAL_lpddr4_fc_adj - t; + } else { + zq_latch_dvfs_wait_time = tZQCAL_lpddr4_fc_adj - util::DivideUp(1000 * dst_timing->dram_timings.t_pdex, destination_clock_period); + } + + if (!(train_ca || train_ca_vref) && (dram_type == DRAM_TYPE_LPDDR4) && opt_zcal_en_cc) { + if (dram_dev_num == ONE_RANK) { + if (source_clock_period > zqcal_before_cc_cutoff) { + CcfifoWrite(EMC_ZQ_CAL, (2 << 30) | (1 << 0), util::DivideUp(1000 * dst_timing->dram_timings.t_pdex, destination_clock_period)); + } + + if (!training_enabled) { + CcfifoWrite(EMC_MRW3, (mr13_flip_fspop & 0xF3FFFFF7) | 0xC000000, util::DivideUp(1000 * dst_timing->dram_timings.t_pdex, destination_clock_period)); + CcfifoWrite(EMC_SELF_REF, 0x100, 0); + CcfifoWrite(EMC_REF, 0, 0); + } + + CcfifoWrite(EMC_ZQ_CAL, (2 << 30) | (1 << 1), std::max<int>(0, zq_latch_dvfs_wait_time)); + } else if (shared_zq_resistor) { + if (source_clock_period > zqcal_before_cc_cutoff) { + CcfifoWrite(EMC_ZQ_CAL, (2 << 30) | (1 << 0), util::DivideUp(1000 * dst_timing->dram_timings.t_pdex, destination_clock_period)); + } + + CcfifoWrite(EMC_ZQ_CAL, (2 << 30) | (1 << 1), std::max<int>(0, zq_latch_dvfs_wait_time) + util::DivideUp(1000 * dst_timing->dram_timings.t_pdex, destination_clock_period)); + CcfifoWrite(EMC_ZQ_CAL, (1 << 30) | (1 << 1), 0); + + if (!training_enabled) { + CcfifoWrite(EMC_MRW3, (mr13_flip_fspop & 0xF3FFFFF7) | 0xC000000, 0); + CcfifoWrite(EMC_SELF_REF, 0x100, 0); + CcfifoWrite(EMC_REF, 0, 0); + } + + CcfifoWrite(EMC_ZQ_CAL, (1 << 30) | (1 << 1), tZQCAL_lpddr4 / destination_clock_period); + } else { + if (source_clock_period > zqcal_before_cc_cutoff) { + CcfifoWrite(EMC_ZQ_CAL, (1 << 0), util::DivideUp(1000 * dst_timing->dram_timings.t_pdex, destination_clock_period)); + } + + if (!training_enabled) { + CcfifoWrite(EMC_MRW3, (mr13_flip_fspop & 0xF3FFFFF7) | 0xC000000, util::DivideUp(1000 * dst_timing->dram_timings.t_pdex, destination_clock_period)); + CcfifoWrite(EMC_SELF_REF, 0x100, 0); + CcfifoWrite(EMC_REF, 0, 0); + } + + CcfifoWrite(EMC_ZQ_CAL, (1 << 1), std::max<int>(0, zq_latch_dvfs_wait_time)); + } + } + + /* WAR: delay for zqlatch */ + CcfifoWrite(EMC_INTSTATUS, 0, 10); + + /* Step 16: + * LPDDR4 Conditional Training Kickoff. + */ + if (training_enabled && (dram_type == DRAM_TYPE_LPDDR4)) { + CcfifoWrite(EMC_INTSTATUS, 0, (1020000 / destination_clock_period)); + + uint32_t train_cmd = 0; + + if (train_ca) + train_cmd |= (1 << 1); /* CA */ + if (train_ca_vref) + train_cmd |= (1 << 5); /* CA_VREF */ + if (train_quse) + train_cmd |= (1 << 4); /* QUSE */ + if (train_quse_vref) + train_cmd |= (1 << 8); /* QUSE_VREF */ + if (train_wr) + train_cmd |= (1 << 3); /* WR */ + if (train_wr_vref) + train_cmd |= (1 << 6); /* WR_VREF */ + if (train_rd) + train_cmd |= (1 << 2); /* RD */ + if (train_rd_vref) + train_cmd |= (1 << 7); /* RD_VREF */ + + train_cmd |= (1 << 31); /* GO */ + + CcfifoWrite(EMC_TRAINING_CMD, train_cmd, 0); + + if (bg_regulator_mode_change) { + if (enable_bg_regulator) + CcfifoWrite(EMC_PMACRO_BG_BIAS_CTRL_0, src_timing->burst_regs.emc_pmacro_bg_bias_ctrl_0 & ~(1 << 0), 0); + else + CcfifoWrite(EMC_PMACRO_BG_BIAS_CTRL_0, src_timing->burst_regs.emc_pmacro_bg_bias_ctrl_0 & ~(1 << 2), 0); + } + + CcfifoWrite(EMC_SWITCH_BACK_CTRL, 1, 0); + + if (!(train_ca || train_ca_vref) || train_second_rank) { + CcfifoWrite(EMC_MRW3, mr13_flip_fspop ^ 0xC0, 0); + CcfifoWrite(EMC_INTSTATUS, 0, (1000000 / destination_clock_period)); + } + + CcfifoWrite(EMC_PIN, emc_pin_o & 0xFFFFFFF8, 0); + CcfifoWrite(EMC_CFG_SYNC, 0, 0); + CcfifoWrite(EMC_DBG, emc_dbg | ((1 << 30) | (1 << 1)), 0); + + DvfsPowerRampDown(true, src_timing, dst_timing, destination_clock_period); + + CcfifoWrite(EMC_STALL_THEN_EXE_AFTER_CLKCHANGE, 1, 0); + CcfifoWrite(EMC_DBG, (emc_dbg & ~(1 << 30)) | (1 << 1), 0); + + DvfsPowerRampUp(true, src_timing, dst_timing, training, source_clock_period); + + CcfifoWrite(EMC_DBG, emc_dbg, 0); + + if (dram_dev_num == TWO_RANK) { + CcfifoWrite(EMC_PIN, emc_pin_o | 7, 0); + } else { + CcfifoWrite(EMC_PIN, ((emc_pin_o & 0xFFFFFFF8) | 1), 0); + } + + if (train_ca || train_ca_vref) { + CcfifoWrite(EMC_TR_CTRL_0, 0x4A, (200000 / source_clock_period)); + CcfifoWrite(EMC_TR_CTRL_0, 0x40, (1000000 / source_clock_period)); + CcfifoWrite(EMC_MRW3, mr13_catr_enable & 0xFFFFFFFE, 0); + CcfifoWrite(EMC_INTSTATUS, 0, (1000000 / source_clock_period)); + CcfifoWrite(EMC_PMACRO_DATA_RX_TERM_MODE, src_timing->burst_regs.emc_pmacro_data_rx_term_mode, 0); + } + + CcfifoWrite(EMC_DBG, emc_dbg_o, 0); + + if (opt_zcal_en_cc) { + CcfifoWrite(EMC_ZQ_CAL, (2 << 30) | (1 << 0), 0); + CcfifoWrite(EMC_ZQ_CAL, (2 << 30) | (1 << 1), (1000000 / source_clock_period)); + + if (dram_dev_num == TWO_RANK) { + if (shared_zq_resistor) { + if (!(train_ca || train_ca_vref) || train_second_rank) { + CcfifoWrite(EMC_ZQ_CAL, (1 << 30) | (1 << 0), 0); + CcfifoWrite(EMC_ZQ_CAL, (1 << 30) | (1 << 1), (1000000 / source_clock_period)); + + if (!(train_ca || train_ca_vref)) + CcfifoWrite(EMC_MRW3, ((mr13_flip_fspop ^ 0xC0) & 0xF3FFFFF7) | 0xC000000, 0); + } + + CcfifoWrite(EMC_SELF_REF, 0x100, 0); + skip_zqcal = true; + } else { + if ((train_ca || train_ca_vref) && !train_second_rank) { + CcfifoWrite(EMC_SELF_REF, 0x100, 0); + skip_zqcal = true; + } else { + CcfifoWrite(EMC_ZQ_CAL, (1 << 30) | (1 << 0), 0); + CcfifoWrite(EMC_ZQ_CAL, (1 << 30) | (1 << 1), (1000000 / source_clock_period)); + } + } + } + } + + if (!skip_zqcal) { + if (!(train_ca || train_ca_vref)) + CcfifoWrite(EMC_MRW3, ((mr13_flip_fspop ^ 0xC0) & 0xF3FFFFF7) | 0xC000000, 0); + + CcfifoWrite(EMC_SELF_REF, 0x100, 0); + } + } + + if (!skip_zqcal) { + /* Step 17: + * MANSR exit self refresh. + */ + + if ((opt_dvfs_mode == MAN_SR) && (dram_type != DRAM_TYPE_LPDDR4)) + CcfifoWrite(EMC_SELF_REF, 0, 0); + + /* Step 18: + * Send MRWs to LPDDR3/DDR3. + */ + + if (dram_type == DRAM_TYPE_LPDDR2) { + CcfifoWrite(EMC_MRW2, dst_timing->emc_mrw2, 0); + CcfifoWrite(EMC_MRW, dst_timing->emc_mrw, 0); + + if (is_lpddr3) { + CcfifoWrite(EMC_MRW4, dst_timing->emc_mrw4, 0); + } + } else if (dram_type == DRAM_TYPE_DDR4) { + if (opt_dll_mode == DLL_ON) { + CcfifoWrite(EMC_EMRS, dst_timing->emc_emrs & ~(1 << 26), 0); + } + CcfifoWrite(EMC_EMRS2, dst_timing->emc_emrs2 & ~(1 << 26), 0); + CcfifoWrite(EMC_MRS, dst_timing->emc_mrs | (1 << 26), 0); + } + + /* Step 19: + * ZQCAL for LPDDR3/DDR3 + */ + + if (opt_zcal_en_cc) { + if (dram_type == DRAM_TYPE_LPDDR2) { + uint32_t r; + uint32_t zq_op = opt_cc_short_zcal ? 0x56 : 0xAB; + uint32_t zcal_wait_time_ps = opt_cc_short_zcal ? 90000 : 360000; + uint32_t zcal_wait_time_clocks = util::DivideUp(zcal_wait_time_ps, destination_clock_period); + r = (zcal_wait_time_clocks << 16) | (zcal_wait_time_clocks << 0); + + CcfifoWrite(EMC_MRS_WAIT_CNT2, r, 0); + CcfifoWrite(EMC_MRW, (2 << 30) | (1 << 27) | (10 << 16) | (zq_op << 0), 0); + + if (dram_dev_num == TWO_RANK) { + r = (1 << 30) | (1 << 27) | (10 << 16) | (zq_op << 0); + CcfifoWrite(EMC_MRW, r, 0); + } + } else if (dram_type == DRAM_TYPE_DDR4) { + uint32_t zq_op = opt_cc_short_zcal ? 0 : (1 << 4); + CcfifoWrite(EMC_ZQ_CAL, zq_op | (2 << 30) | (1 << 0), 0); + + if (dram_dev_num == TWO_RANK) { + CcfifoWrite(EMC_ZQ_CAL, zq_op | (1 << 30) | (1 << 0), 0); + } + } + } + } + + if (bg_regulator_mode_change) { + SetShadowBypass(ACTIVE); + + uint32_t bg_regulator_switch_complete_wait_clks = ramp_up_wait > 1250000 ? 0 : (1250000 - ramp_up_wait) / destination_clock_period; + + if (training_enabled) { + bg_regulator_switch_complete_wait_clks = (1250000 / source_clock_period); + CcfifoWrite(EMC_PMACRO_BG_BIAS_CTRL_0, src_timing->burst_regs.emc_pmacro_bg_bias_ctrl_0, bg_regulator_switch_complete_wait_clks); + } else { + CcfifoWrite(EMC_PMACRO_BG_BIAS_CTRL_0, dst_timing->burst_regs.emc_pmacro_bg_bias_ctrl_0, bg_regulator_switch_complete_wait_clks); + } + + SetShadowBypass(ASSEMBLY); + } + + /* Step 20: + * Issue ref and optional QRST. + */ + if (training_enabled || (dram_type != DRAM_TYPE_LPDDR4)) { + CcfifoWrite(EMC_REF, 0, 0); + } + + if (opt_do_sw_qrst) { + CcfifoWrite(EMC_ISSUE_QRST, 1, 0); + CcfifoWrite(EMC_ISSUE_QRST, 0, 2); + } + + /* Step 21: + * Restore ZCAL and ZCAL interval. + */ + if (save_restore_clkstop_pd || opt_zcal_en_cc) { + SetShadowBypass(ACTIVE); + + if (opt_zcal_en_cc) { + if (training_enabled) { + CcfifoWrite(EMC_ZCAL_INTERVAL, src_timing->burst_regs.emc_zcal_interval, 0); + } else if (dram_type != DRAM_TYPE_LPDDR4) { + CcfifoWrite(EMC_ZCAL_INTERVAL, dst_timing->burst_regs.emc_zcal_interval, 0); + } + } + + if (save_restore_clkstop_pd) { + CcfifoWrite(EMC_CFG, dst_timing->burst_regs.emc_cfg & ~(1 << 28), 0); + } + + if (training_enabled && (dram_type == DRAM_TYPE_LPDDR4)) { + CcfifoWrite(EMC_SEL_DPD_CTRL, src_timing->emc_sel_dpd_ctrl, 0); + } + + SetShadowBypass(ASSEMBLY); + } + + /* Step 22: + * Restore EMC_CFG_PIPE_CLK. + */ + CcfifoWrite(EMC_CFG_PIPE_CLK, emc_cfg_pipe_clk_o, 0); + + if (bg_regulator_mode_change) { + if (enable_bg_regulator) { + reg::Write(EMC + EMC_PMACRO_BG_BIAS_CTRL_0, dst_timing->burst_regs.emc_pmacro_bg_bias_ctrl_0 & ~(1 << 2)); + } else { + reg::Write(EMC + EMC_PMACRO_BG_BIAS_CTRL_0, dst_timing->burst_regs.emc_pmacro_bg_bias_ctrl_0 & ~(1 << 0)); + } + } + + /* Step 23: + * Do clock change. + */ + if (training_enabled) { + u32 cur_clk_src = reg::Read(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC); + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC_SAFE, cur_clk_src); + ChangeDllSrc(src_timing, cur_clk_src); + } + + uint32_t cfg_dig_dll_tmp = (reg::Read(EMC + EMC_CFG_DIG_DLL) & 0xFFFFFF24) | 0x88; + reg::Write(EMC + EMC_CFG_DIG_DLL, cfg_dig_dll_tmp); + + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC, next_clk_src); + WaitForUpdate(EMC_INTSTATUS, 0x10, true, fbio_cfg7); + + /* Step 24: + * Save training results. + */ + if (training_enabled) { + uint32_t emc_dbg_tmp = reg::Read(EMC + EMC_DBG); + reg::Write(EMC + EMC_DBG, emc_dbg_tmp | 1); /* Set READ_MUX to ASSEMBLY. */ + + /* Save CA results. */ + if (train_ca) { + dst_timing->trim_perch_regs.emc0_cmd_brlshft_0 = reg::Read(EMC0 + EMC_CMD_BRLSHFT_0); + dst_timing->trim_perch_regs.emc1_cmd_brlshft_1 = dual_channel ? reg::Read(EMC1 + EMC_CMD_BRLSHFT_1): 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_4 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_4); + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_5 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_5) : 0; + + if (train_bit_level) { + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd0_0 = reg::Read(EMC + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd0_1 = reg::Read(EMC + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd0_2 = reg::Read(EMC + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd1_0 = reg::Read(EMC + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd1_1 = reg::Read(EMC + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd1_2 = reg::Read(EMC + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd2_0 = reg::Read(EMC + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd2_1 = reg::Read(EMC + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd2_2 = reg::Read(EMC + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd3_0 = reg::Read(EMC + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd3_1 = reg::Read(EMC + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd3_2 = reg::Read(EMC + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_2); + } + } + + /* Save CA_VREF results. */ + if (train_ca_vref) { + dst_timing->burst_perch_regs.emc0_mrw10 = (reg::Read(EMC0 + EMC_TRAINING_OPT_CA_VREF) & 0xFFFF) | 0x880C0000; + dst_timing->burst_perch_regs.emc1_mrw10 = (dual_channel ? reg::Read(EMC1 + EMC_TRAINING_OPT_CA_VREF) & 0xFFFF : 0) | 0x880C0000; + + if (dram_dev_num == TWO_RANK) { + dst_timing->burst_perch_regs.emc0_mrw11 = ((reg::Read(EMC0 + EMC_TRAINING_OPT_CA_VREF) >> 16) & 0xFF) | (reg::Read(EMC0 + EMC_TRAINING_OPT_CA_VREF) >> 24 << 8) | (0x480C0000 & 0xFFFFFF00); + dst_timing->burst_perch_regs.emc1_mrw11 = (((dual_channel ? reg::Read(EMC1 + EMC_TRAINING_OPT_CA_VREF) : 0) >> 16) & 0xFF) | ((dual_channel ? reg::Read(EMC1 + EMC_TRAINING_OPT_CA_VREF) : 0) >> 24 << 8) | (0x480C0000 & 0xFFFFFF00); + } else { + dst_timing->burst_perch_regs.emc0_mrw11 = ((reg::Read(EMC0 + EMC_TRAINING_OPT_CA_VREF) >> 16) & 0xFF) | (reg::Read(EMC0 + EMC_TRAINING_OPT_CA_VREF) >> 24 << 8) | (0xC80C0000 & 0xFFFFFF00); + dst_timing->burst_perch_regs.emc1_mrw11 = (((dual_channel ? reg::Read(EMC1 + EMC_TRAINING_OPT_CA_VREF) : 0) >> 16) & 0xFF) | ((dual_channel ? reg::Read(EMC1 + EMC_TRAINING_OPT_CA_VREF) : 0) >> 24 << 8) | (0xC80C0000 & 0xFFFFFF00); + } + } + + /* Save QUSE results. */ + if (train_quse || train_rd) { + dst_timing->trim_perch_regs.emc0_quse_brlshft_0 = reg::Read(EMC0 + EMC_QUSE_BRLSHFT_0); + dst_timing->trim_perch_regs.emc1_quse_brlshft_1 = dual_channel ? reg::Read(EMC1 + EMC_QUSE_BRLSHFT_1) : 0; + + dst_timing->trim_regs.emc_pmacro_quse_ddll_rank0_0 = reg::Read(EMC0 + EMC_PMACRO_QUSE_DDLL_RANK0_0); + dst_timing->trim_regs.emc_pmacro_quse_ddll_rank0_1= reg::Read(EMC0 + EMC_PMACRO_QUSE_DDLL_RANK0_1); + dst_timing->trim_regs.emc_pmacro_quse_ddll_rank0_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_QUSE_DDLL_RANK0_2) : 0; + dst_timing->trim_regs.emc_pmacro_quse_ddll_rank0_3 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_QUSE_DDLL_RANK0_3) : 0; + + if (dram_dev_num == TWO_RANK) { + dst_timing->trim_perch_regs.emc0_quse_brlshft_2 = reg::Read(EMC0 + EMC_QUSE_BRLSHFT_2); + dst_timing->trim_perch_regs.emc1_quse_brlshft_3 = dual_channel ? reg::Read(EMC1 + EMC_QUSE_BRLSHFT_3) : 0; + + dst_timing->trim_regs.emc_pmacro_quse_ddll_rank1_0 = reg::Read(EMC0 + EMC_PMACRO_QUSE_DDLL_RANK1_0); + dst_timing->trim_regs.emc_pmacro_quse_ddll_rank1_1 = reg::Read(EMC0 + EMC_PMACRO_QUSE_DDLL_RANK1_1); + dst_timing->trim_regs.emc_pmacro_quse_ddll_rank1_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_QUSE_DDLL_RANK1_2) : 0; + dst_timing->trim_regs.emc_pmacro_quse_ddll_rank1_3 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_QUSE_DDLL_RANK1_3) : 0; + } + } + + /* Save QUSE_VREF results. */ + if (train_quse_vref) { + if (dram_dev_num == TWO_RANK) { + uint32_t emc0_opt_dqs_array[4] = {0}; + uint32_t emc1_opt_dqs_array[4] = {0}; + uint32_t emc1_training_opt_dqs_ib_vref_rank0_val = dual_channel ? reg::Read(EMC1 + EMC_TRAINING_OPT_DQS_IB_VREF_RANK0) : 0; + uint32_t emc1_training_opt_dqs_ib_vref_rank1_val = dual_channel ? reg::Read(EMC1 + EMC_TRAINING_OPT_DQS_IB_VREF_RANK1) : 0; + + for (int i = 0; i < 4; i++) { + emc0_opt_dqs_array[i] = (reg::Read(EMC0 + EMC_TRAINING_OPT_DQS_IB_VREF_RANK0) >> (8 * i)) & 0xFF; + emc1_opt_dqs_array[i] = (emc1_training_opt_dqs_ib_vref_rank0_val >> (8 * i)) & 0xFF; + } + + uint32_t ib_vref_dqs_0 = 0; + uint32_t ib_vref_dqs_1 = 0; + for (int i = 0; i < 4; i++) + { + ib_vref_dqs_0 |= (emc0_opt_dqs_array[i] + ((reg::Read(EMC0 + EMC_TRAINING_OPT_DQS_IB_VREF_RANK1) >> (8 * i)) & 0xFF)) >> 1 << (8 * i); + ib_vref_dqs_1 |= (emc1_opt_dqs_array[i] + ((emc1_training_opt_dqs_ib_vref_rank1_val >> (8 * i)) & 0xFF)) >> 1 << (8 * i); + } + + dst_timing->trim_regs.emc_pmacro_ib_vref_dqs_0 = ib_vref_dqs_0; + dst_timing->trim_regs.emc_pmacro_ib_vref_dqs_1 = ib_vref_dqs_1; + } + else + { + dst_timing->trim_regs.emc_pmacro_ib_vref_dqs_0 = reg::Read(EMC + EMC_PMACRO_IB_VREF_DQS_0); + dst_timing->trim_regs.emc_pmacro_ib_vref_dqs_1 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_VREF_DQS_1) : 0; + } + } + + /* Save RD results. */ + if (train_rd) { + dst_timing->trim_regs.emc_pmacro_ib_ddll_long_dqs_rank0_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_long_dqs_rank0_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_long_dqs_rank0_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_2) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_long_dqs_rank0_3 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_3) : 0; + + if (dram_dev_num == TWO_RANK) { + dst_timing->trim_regs.emc_pmacro_ib_ddll_long_dqs_rank1_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_long_dqs_rank1_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_long_dqs_rank1_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_2) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_long_dqs_rank1_3 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_3) : 0; + } + + if (train_bit_level) { + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte0_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte0_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte0_2 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte1_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte1_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte1_2 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte2_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte2_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte2_2 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte3_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte3_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte3_2 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte4_0 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_0) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte4_1 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_1) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte4_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_2) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte5_0 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_0) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte5_1 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_1) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte5_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_2) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte6_0 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_0) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte6_1 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_1) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte6_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_2) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte7_0 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_0) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte7_1 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_1) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte7_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_2) : 0; + + if (dram_dev_num == TWO_RANK) { + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte0_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte0_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte0_2 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte1_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte1_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte1_2 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte2_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte2_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte2_2 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte3_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte3_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte3_2 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte4_0 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_0) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte4_1 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_1) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte4_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_2) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte5_0 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_0) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte5_1 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_1) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte5_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_2) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte6_0 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_0) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte6_1 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_1) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte6_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_2) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte7_0 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_0) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte7_1 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_1) : 0; + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte7_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_2) : 0; + } + } + + /* Save RD_VREF results. */ + if (train_rd_vref) { + uint8_t ib_vref_dq_byte0_icr = (reg::Read(EMC + EMC_PMACRO_IB_VREF_DQ_0) & 0x7F) + (dst_timing->save_restore_mod_regs[0] & 0x7F); + if (dst_timing->save_restore_mod_regs[0] & 0x80000000) + ib_vref_dq_byte0_icr = (reg::Read(EMC + EMC_PMACRO_IB_VREF_DQ_0) & 0x7F) - (dst_timing->save_restore_mod_regs[0] & 0x7F); + + uint8_t ib_vref_dq_byte1_icr = ((reg::Read(EMC + EMC_PMACRO_IB_VREF_DQ_0) >> 8) & 0x7F) + (dst_timing->save_restore_mod_regs[1] & 0x7F); + if (dst_timing->save_restore_mod_regs[1] & 0x80000000) + ib_vref_dq_byte1_icr = ((reg::Read(EMC + EMC_PMACRO_IB_VREF_DQ_0) >> 8) & 0x7F) - (dst_timing->save_restore_mod_regs[1] & 0x7F); + + uint8_t ib_vref_dq_byte2_icr = ((reg::Read(EMC + EMC_PMACRO_IB_VREF_DQ_0) >> 16) & 0x7F) + (dst_timing->save_restore_mod_regs[2] & 0x7F); + if (dst_timing->save_restore_mod_regs[2] & 0x80000000) + ib_vref_dq_byte2_icr = ((reg::Read(EMC + EMC_PMACRO_IB_VREF_DQ_0) >> 16) & 0x7F) - (dst_timing->save_restore_mod_regs[2] & 0x7F); + + uint8_t ib_vref_dq_byte3_icr = ((reg::Read(EMC + EMC_PMACRO_IB_VREF_DQ_0) >> 24) & 0x7F) + (dst_timing->save_restore_mod_regs[3] & 0x7F); + if (dst_timing->save_restore_mod_regs[3] & 0x80000000) + ib_vref_dq_byte3_icr = ((reg::Read(EMC + EMC_PMACRO_IB_VREF_DQ_0) >> 24) & 0x7F) - (dst_timing->save_restore_mod_regs[3] & 0x7F); + + dst_timing->trim_regs.emc_pmacro_ib_vref_dq_0 = ((ib_vref_dq_byte0_icr & 0x7F) | (ib_vref_dq_byte1_icr & 0x7F) << 8) | ((ib_vref_dq_byte2_icr & 0x7F) << 16) | ((ib_vref_dq_byte3_icr & 0x7F) << 24); + + uint8_t ib_vref_dq_byte4_icr = (reg::Read(EMC + EMC_PMACRO_IB_VREF_DQ_1) & 0x7F) + (dst_timing->save_restore_mod_regs[4] & 0x7F); + if (dst_timing->save_restore_mod_regs[4] & 0x80000000) + ib_vref_dq_byte4_icr = (reg::Read(EMC + EMC_PMACRO_IB_VREF_DQ_1) & 0x7F) - (dst_timing->save_restore_mod_regs[4] & 0x7F); + + uint8_t ib_vref_dq_byte5_icr = ((reg::Read(EMC + EMC_PMACRO_IB_VREF_DQ_1) >> 8) & 0x7F) + (dst_timing->save_restore_mod_regs[5] & 0x7F); + if (dst_timing->save_restore_mod_regs[5] & 0x80000000) + ib_vref_dq_byte5_icr = ((reg::Read(EMC + EMC_PMACRO_IB_VREF_DQ_1) >> 8) & 0x7F) - (dst_timing->save_restore_mod_regs[5] & 0x7F); + + uint8_t ib_vref_dq_byte6_icr = ((reg::Read(EMC + EMC_PMACRO_IB_VREF_DQ_1) >> 16) & 0x7F) + (dst_timing->save_restore_mod_regs[6] & 0x7F); + if (dst_timing->save_restore_mod_regs[6] & 0x80000000) + ib_vref_dq_byte6_icr = ((reg::Read(EMC + EMC_PMACRO_IB_VREF_DQ_1) >> 16) & 0x7F) - (dst_timing->save_restore_mod_regs[6] & 0x7F); + + uint8_t ib_vref_dq_byte7_icr = ((reg::Read(EMC + EMC_PMACRO_IB_VREF_DQ_1) >> 24) & 0x7F) + (dst_timing->save_restore_mod_regs[7] & 0x7F); + if (dst_timing->save_restore_mod_regs[7] & 0x80000000) + ib_vref_dq_byte7_icr = ((reg::Read(EMC + EMC_PMACRO_IB_VREF_DQ_1) >> 24) & 0x7F) - (dst_timing->save_restore_mod_regs[7] & 0x7F); + + dst_timing->trim_regs.emc_pmacro_ib_vref_dq_1 = ((ib_vref_dq_byte4_icr & 0x7F) | (ib_vref_dq_byte5_icr & 0x7F) << 8) | ((ib_vref_dq_byte6_icr & 0x7F) << 16) | ((ib_vref_dq_byte7_icr & 0x7F) << 24); + } + } + + /* Save WR results. */ + if (train_wr) { + dst_timing->trim_perch_regs.emc0_data_brlshft_0 = reg::Read(EMC0 + EMC_DATA_BRLSHFT_0); + dst_timing->trim_perch_regs.emc1_data_brlshft_0 = dual_channel ? reg::Read(EMC1 + EMC_DATA_BRLSHFT_0) : 0; + + if (dram_dev_num == TWO_RANK) { + dst_timing->trim_perch_regs.emc0_data_brlshft_1 = reg::Read(EMC0 + EMC_DATA_BRLSHFT_1); + dst_timing->trim_perch_regs.emc1_data_brlshft_1 = dual_channel ? reg::Read(EMC1 + EMC_DATA_BRLSHFT_1) : 0; + } + + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_3 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3) : 0; + + if (dram_dev_num == TWO_RANK) { + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank1_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank1_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank1_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank1_3 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3) : 0; + } + + if (train_bit_level) { + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte0_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte0_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte0_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte1_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte1_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte1_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte2_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte2_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte2_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte3_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte3_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte3_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte4_0 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_0) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte4_1 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_1) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte4_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_2) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte5_0 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_0) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte5_1 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_1) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte5_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_2) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte6_0 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_0) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte6_1 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_1) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte6_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_2) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte7_0 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_0) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte7_1 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_1) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte7_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_2) : 0; + + if (dram_dev_num == TWO_RANK) { + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte0_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte0_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte0_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte1_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte1_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte1_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte2_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte2_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte2_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte3_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte3_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte3_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte4_0 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_0) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte4_1 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_1) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte4_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_2) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte5_0 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_0) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte5_1 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_1) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte5_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_2) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte6_0 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_0) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte6_1 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_1) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte6_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_2) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte7_0 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_0) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte7_1 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_1) : 0; + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte7_2 = dual_channel ? reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_2) : 0; + } + } + + /* Save WR_VREF results. */ + if (train_wr_vref) { + uint32_t emc1_ranks_sub_partitions = dual_channel ? reg::Read(EMC1 + EMC_TRAINING_OPT_DQ_OB_VREF) : 0; + + uint8_t emc0_ib_vref_dq_byte8_modded_plus = dst_timing->save_restore_mod_regs[8] + reg::Read(EMC0 + EMC_TRAINING_OPT_DQ_OB_VREF); + if (dst_timing->save_restore_mod_regs[8] & 0x80000000) + emc0_ib_vref_dq_byte8_modded_plus = reg::Read(EMC0 + EMC_TRAINING_OPT_DQ_OB_VREF) - dst_timing->save_restore_mod_regs[8]; + + uint8_t emc0_mrw12_op_sp1 = ((reg::Read(EMC0 + EMC_TRAINING_OPT_DQ_OB_VREF) & 0xFFFF) >> 8) + dst_timing->save_restore_mod_regs[9]; + if (dst_timing->save_restore_mod_regs[9] & 0x80000000) + emc0_mrw12_op_sp1 = ((reg::Read(EMC0 + EMC_TRAINING_OPT_DQ_OB_VREF) & 0xFFFF) >> 8) - dst_timing->save_restore_mod_regs[9]; + + uint8_t emc0_mrw13_op_sp0 = ((reg::Read(EMC0 + EMC_TRAINING_OPT_DQ_OB_VREF) >> 16) & 0xFF) + dst_timing->save_restore_mod_regs[8]; + if (dst_timing->save_restore_mod_regs[8] & 0x80000000) + emc0_mrw13_op_sp0 = ((reg::Read(EMC0 + EMC_TRAINING_OPT_DQ_OB_VREF) >> 16) & 0xFF) - dst_timing->save_restore_mod_regs[8]; + + uint8_t emc0_ib_vref_dq_byte9_modded_a_plus = dst_timing->save_restore_mod_regs[9] + (reg::Read(EMC0 + EMC_TRAINING_OPT_DQ_OB_VREF) >> 24); + if (dst_timing->save_restore_mod_regs[9] & 0x80000000) + emc0_ib_vref_dq_byte9_modded_a_plus = (reg::Read(EMC0 + EMC_TRAINING_OPT_DQ_OB_VREF) >> 24) - (uint8_t)dst_timing->save_restore_mod_regs[9]; + + uint8_t emc0_ib_vref_dq_byte10_modded_plus = emc1_ranks_sub_partitions + dst_timing->save_restore_mod_regs[10]; + if (dst_timing->save_restore_mod_regs[10] & 0x80000000) + emc0_ib_vref_dq_byte10_modded_plus = emc1_ranks_sub_partitions - dst_timing->save_restore_mod_regs[10]; + + uint8_t emc0_ib_vref_dq_byte11_modded_plus = ((emc1_ranks_sub_partitions & 0xFFFF) >> 8) + dst_timing->save_restore_mod_regs[11]; + if (dst_timing->save_restore_mod_regs[11] & 0x80000000) + emc0_ib_vref_dq_byte11_modded_plus = ((emc1_ranks_sub_partitions & 0xFFFF) >> 8) - dst_timing->save_restore_mod_regs[11]; + + uint8_t emc1_mrw13_op_sp0 = ((emc1_ranks_sub_partitions >> 16) & 0xFF) + dst_timing->save_restore_mod_regs[10]; + if (dst_timing->save_restore_mod_regs[10] & 0x80000000) + emc1_mrw13_op_sp0 = ((emc1_ranks_sub_partitions >> 16) & 0xFF) - dst_timing->save_restore_mod_regs[10]; + + uint8_t emc1_mrw13_op_sp1 = (emc1_ranks_sub_partitions >> 24) + dst_timing->save_restore_mod_regs[11]; + if (dst_timing->save_restore_mod_regs[11] & 0x80000000) + emc1_mrw13_op_sp1 = (emc1_ranks_sub_partitions >> 24) - dst_timing->save_restore_mod_regs[11]; + + dst_timing->burst_perch_regs.emc1_mrw12 = (uint8_t)emc0_ib_vref_dq_byte10_modded_plus | 0x880E0000 | (emc0_ib_vref_dq_byte11_modded_plus << 8); + dst_timing->burst_perch_regs.emc0_mrw12 = emc0_ib_vref_dq_byte8_modded_plus | 0x880E0000 | (emc0_mrw12_op_sp1 << 8); + + if (dram_dev_num == TWO_RANK) { + dst_timing->burst_perch_regs.emc0_mrw13 = emc0_ib_vref_dq_byte9_modded_a_plus << 8 | emc0_mrw13_op_sp0 | 0x480E0000; + dst_timing->burst_perch_regs.emc1_mrw13 = (emc1_mrw13_op_sp1 << 8) | emc1_mrw13_op_sp0 | 0x480E0000; + } else { + dst_timing->burst_perch_regs.emc0_mrw13 = emc0_ib_vref_dq_byte9_modded_a_plus << 8 | emc0_mrw13_op_sp0 | 0xC80E0000; + dst_timing->burst_perch_regs.emc1_mrw13 = (emc1_mrw13_op_sp1 << 8) | emc1_mrw13_op_sp0 | 0xC80E0000; + } + } + } + + reg::Write(EMC + EMC_DBG, emc_dbg_tmp); + } + + /* Step 25: + * Program MC updown registers. + */ + if ((dst_timing->rate_khz > src_timing->rate_khz) && !training_enabled) { + for (u32 i = 0; i < dst_timing->num_up_down; i++) { + reg::Write(LaScaleRegisters[i], dst_timing->la_scale_regs_arr[i]); + } + + /* Request a timing update. */ + TimingUpdate(fbio_cfg7); + } + + /* Step 26: + * Restore ZCAL registers. + */ + if (dram_type == DRAM_TYPE_LPDDR4) { + SetShadowBypass(ACTIVE); + reg::Write(EMC + EMC_ZCAL_WAIT_CNT, dst_timing->burst_regs.emc_zcal_wait_cnt); + reg::Write(EMC + EMC_ZCAL_INTERVAL, dst_timing->burst_regs.emc_zcal_interval); + SetShadowBypass(ASSEMBLY); + } + + if ((dram_type != DRAM_TYPE_LPDDR4) + && opt_zcal_en_cc + && !opt_short_zcal + && opt_cc_short_zcal) + { + util::WaitMicroSeconds(2); + + SetShadowBypass(ACTIVE); + if (dram_type == DRAM_TYPE_LPDDR2) { + reg::Write(EMC + EMC_MRS_WAIT_CNT, dst_timing->burst_regs.emc_mrs_wait_cnt); + } else if (dram_type == DRAM_TYPE_DDR4) { + reg::Write(EMC + EMC_ZCAL_WAIT_CNT, dst_timing->burst_regs.emc_zcal_wait_cnt); + } + SetShadowBypass(ASSEMBLY); + } + + /* Step 27: + * Restore EMC_CFG, FDPD registers. + */ + SetShadowBypass(ACTIVE); + reg::Write(EMC + EMC_CFG, dst_timing->burst_regs.emc_cfg); + SetShadowBypass(ASSEMBLY); + reg::Write(EMC + EMC_FDPD_CTRL_CMD_NO_RAMP, dst_timing->emc_fdpd_ctrl_cmd_no_ramp); + reg::Write(EMC + EMC_SEL_DPD_CTRL, dst_timing->emc_sel_dpd_ctrl); + + /* Step 28: + * Training recover. + */ + if (training_enabled && (dram_type == DRAM_TYPE_LPDDR4)) { + SetShadowBypass(ACTIVE); + reg::Write(EMC + EMC_CFG, dst_timing->burst_regs.emc_cfg); + reg::Write(EMC + EMC_SEL_DPD_CTRL, dst_timing->emc_sel_dpd_ctrl); + reg::Write(EMC + EMC_ZCAL_WAIT_CNT, src_timing->burst_regs.emc_zcal_wait_cnt); + reg::Write(EMC + EMC_ZCAL_INTERVAL, src_timing->burst_regs.emc_zcal_interval); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG2, src_timing->emc_auto_cal_config2); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG3, src_timing->emc_auto_cal_config3); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG4, src_timing->emc_auto_cal_config4); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG5, src_timing->emc_auto_cal_config5); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG6, src_timing->emc_auto_cal_config6); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG7, src_timing->emc_auto_cal_config7); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG8, src_timing->emc_auto_cal_config8); + SetShadowBypass(ASSEMBLY); + reg::Write(EMC + EMC_TR_DVFS, dst_timing->burst_regs.emc_tr_dvfs & ~(1 << 0)); + } + + SetShadowBypass(ACTIVE); + reg::Write(EMC + EMC_PMACRO_AUTOCAL_CFG_COMMON, dst_timing->burst_regs.emc_pmacro_autocal_cfg_common); + SetShadowBypass(ASSEMBLY); + + /* Step 29: + * Power fix WAR. + */ + reg::Write(EMC + EMC_PMACRO_CFG_PM_GLOBAL_0, 0xFF0000); + reg::Write(EMC + EMC_PMACRO_TRAINING_CTRL_0, 0x8); + reg::Write(EMC + EMC_PMACRO_TRAINING_CTRL_1, 0x8); + reg::Write(EMC + EMC_PMACRO_CFG_PM_GLOBAL_0, 0); + + /* Step 30: + * Re-enable autocal. + */ + if (training_enabled) { + emc_auto_cal_config = src_timing->emc_auto_cal_config; + + /* Restore FSP to account for switch back. Only needed in training. */ + g_fsp_for_next_freq = !g_fsp_for_next_freq; + } else { + emc_auto_cal_config = dst_timing->emc_auto_cal_config; + + if (dst_timing->burst_regs.emc_cfg_dig_dll & 0x1) { + DllEnableStall(fbio_cfg7); + } + } + + reg::Write(EMC + EMC_AUTO_CAL_CONFIG, emc_auto_cal_config); + } + + void CleanupActiveShadowCopy(EmcDvfsTimingTable *src_timing, EmcDvfsTimingTable *dst_timing) { + const int dram_type = reg::GetValue(EMC + EMC_FBIO_CFG5, EMC_REG_BITS_MASK(FBIO_CFG5_DRAM_TYPE)); + const u32 fbio_cfg7 = reg::Read(EMC + EMC_FBIO_CFG7); + + /* Change CFG_SWAP to ASSEMBLY_ONLY */ + uint32_t emc_dbg = reg::Read(EMC + EMC_DBG); + emc_dbg = ((emc_dbg & 0xF3FFFFFF) | 0x8000000); + reg::Write(EMC + EMC_DBG, emc_dbg); + + /* Change UPDATE_AUTO_CAL_IN_UPDATE to ALWAYS */ + uint32_t emc_cfg_update = reg::Read(EMC + EMC_CFG_UPDATE); + emc_cfg_update = ((emc_cfg_update & 0xFFFFFFF9) | 0x04); + reg::Write(EMC + EMC_CFG_UPDATE, emc_cfg_update); + + /* Request a timing update event */ + TimingUpdate(fbio_cfg7); + + /* Change UPDATE_AUTO_CAL_IN_UPDATE to NEVER */ + emc_cfg_update = reg::Read(EMC + EMC_CFG_UPDATE); + emc_cfg_update &= 0xFFFFFFF9; + reg::Write(EMC + EMC_CFG_UPDATE, emc_cfg_update); + + /* Change CFG_SWAP to ACTIVE_ONLY */ + emc_dbg = reg::Read(EMC + EMC_DBG); + emc_dbg &= 0xF3FFFFFF; + reg::Write(EMC + EMC_DBG, emc_dbg); + + /* Disable DLL and change CFG_DLL_MODE to RUN_PERIODIC */ + uint32_t emc_cfg_dig_dll = reg::Read(EMC + EMC_CFG_DIG_DLL); + emc_cfg_dig_dll = ((emc_cfg_dig_dll & 0xFFFFFF3E) | 0x80); + reg::Write(EMC + EMC_CFG_DIG_DLL, emc_cfg_dig_dll); + + /* Request a timing update event */ + TimingUpdate(fbio_cfg7); + + /* Disable or enable DLL */ + emc_cfg_dig_dll = reg::Read(EMC + EMC_CFG_DIG_DLL); + if (dst_timing->burst_regs.emc_cfg_dig_dll == 0x01) { + emc_cfg_dig_dll |= 0x01; + } else { + emc_cfg_dig_dll &= 0xFFFFFFFE; + } + + /* Change CFG_DLL_MODE to RUN_PERIODIC */ + emc_cfg_dig_dll = ((emc_cfg_dig_dll & 0xFFFFFF3F) | 0x80); + reg::Write(EMC + EMC_CFG_DIG_DLL, emc_cfg_dig_dll); + + /* Request a timing update event */ + TimingUpdate(fbio_cfg7); + + /* Wait for DLL_LOCK to be set */ + uint32_t emc_dig_dll_status = 0; + do { + emc_dig_dll_status = reg::Read(EMC + EMC_DIG_DLL_STATUS); + } while (!(emc_dig_dll_status & (1 << 15))); + + /* Check if DRAM is LPDDR4 */ + if (dram_type == DRAM_TYPE_LPDDR4) { + reg::Write(EMC + EMC_RP, src_timing->burst_regs.emc_rp); + reg::Write(EMC + EMC_R2P, src_timing->burst_regs.emc_r2p); + reg::Write(EMC + EMC_W2P, src_timing->burst_regs.emc_w2p); + reg::Write(EMC + EMC_TRPAB, src_timing->burst_regs.emc_trpab); + } + + /* Request a timing update event */ + TimingUpdate(fbio_cfg7); + } + + void TrainFreq(EmcDvfsTimingTable *src_timing, EmcDvfsTimingTable *dst_timing, u32 next_clk_src) { + /* Get dram dev num. */ + const u32 dram_dev_num = (reg::Read(MC + MC_EMEM_ADR_CFG) & 1) + 1; + + /* Write RAM patterns, if first training. */ + if (!g_did_first_training) { + const auto * const pattern = GetEmcRamTrainingPattern(); + for (u32 i = 0; i < 0x100; ++i) { + reg::Write(EMC + EMC_TRAINING_PATRAM_DQ, pattern[dst_timing->training_pattern].dq[i]); + reg::Write(EMC + EMC_TRAINING_PATRAM_DMI, pattern[dst_timing->training_pattern].dmi[i]); + reg::Write(EMC + EMC_TRAINING_PATRAM_CTRL, 0x80000000 | i); + } + + g_did_first_training = true; + } + + /* Do training, if we need to. */ + const u32 needed_training = dst_timing->needs_training; + if (needed_training && !dst_timing->trained) { + /* Determine what training to do. */ + u32 training_params[8]; + u32 num_params = 0; + + if (needed_training & (CA_TRAINING | CA_VREF_TRAINING)) { + training_params[num_params++] = (needed_training & (CA_TRAINING | CA_VREF_TRAINING | BIT_LEVEL_TRAINING)); + } + if (dram_dev_num == TWO_RANK) { + if (needed_training & (CA_TRAINING | CA_VREF_TRAINING)) { + training_params[num_params++] = (needed_training & (CA_TRAINING | CA_VREF_TRAINING | TRAIN_SECOND_RANK | BIT_LEVEL_TRAINING)); + } + if (needed_training & (QUSE_TRAINING | QUSE_VREF_TRAINING)) { + training_params[num_params++] = (needed_training & (QUSE_TRAINING | QUSE_VREF_TRAINING | BIT_LEVEL_TRAINING)); + training_params[num_params++] = (needed_training & (QUSE_TRAINING | BIT_LEVEL_TRAINING)); + } + } else { + if (needed_training & (QUSE_TRAINING | QUSE_VREF_TRAINING)) { + training_params[num_params++] = (needed_training & (QUSE_TRAINING | QUSE_VREF_TRAINING | BIT_LEVEL_TRAINING)); + } + } + if (needed_training & (WRITE_TRAINING | WRITE_VREF_TRAINING | READ_TRAINING | READ_VREF_TRAINING)) { + training_params[num_params++] = (needed_training & (WRITE_TRAINING | WRITE_VREF_TRAINING | READ_TRAINING | READ_VREF_TRAINING | BIT_LEVEL_TRAINING)); + } + + /* Apply all training. */ + for (u32 i = 0; i < num_params; ++i) { + FreqChange(src_timing, dst_timing, training_params[i], next_clk_src); + CleanupActiveShadowCopy(src_timing, dst_timing); + } + + /* Set tables as trained. */ + dst_timing->trained = 1; + } + } + + constexpr inline const u16 PeriodicCompensationRegisters[] = { + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0, + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1, + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2, + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3, + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0, + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1, + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2, + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3, + EMC_DATA_BRLSHFT_0, + EMC_DATA_BRLSHFT_1 + }; + + void PeriodicCompensationRoutine(EmcDvfsTimingTable *timing) { + if (timing->periodic_training) { + const int dram_dev_num = (reg::Read(MC + MC_EMEM_ADR_CFG) & 1) + 1; + const u32 fbio_cfg7 = timing->burst_regs.emc_fbio_cfg7; + + uint32_t emc_cfg_o = reg::Read(EMC + EMC_CFG); + uint32_t emc_cfg_dig_dll_o = reg::Read(EMC + EMC_CFG_DIG_DLL); + uint32_t emc_cfg_update_o = reg::Read(EMC + EMC_CFG_UPDATE); + /* + * 1. Power optimizations should be off. + */ + reg::Write(EMC + EMC_CFG_DIG_DLL, emc_cfg_dig_dll_o & 0xFFFFFFFE); + reg::Write(EMC + EMC_CFG_UPDATE, (emc_cfg_update_o & 0xFFFFF9FF) | 0x400); + reg::Write(EMC + EMC_CFG, emc_cfg_o & 0x0FFFFFFF); + + /* Do timing update. */ + TimingUpdate(fbio_cfg7); + + if (dram_dev_num == TWO_RANK) { + WaitForUpdate(EMC_EMC_STATUS, 0x30, false, fbio_cfg7); + } else { + WaitForUpdate(EMC_EMC_STATUS, 0x10, false, fbio_cfg7); + } + + WaitForUpdate(EMC_EMC_STATUS, 0x300, false, fbio_cfg7); + + WaitForUpdate(EMC_EMC_STATUS, 0x01, false, fbio_cfg7); + + /* + * 2. osc kick off - this assumes training and dvfs have set + * correct MR23. + */ + StartPeriodicCompensation(); + + /* + * 3. Let dram capture its clock tree delays. + */ + util::WaitMicroSeconds(2 + ((ActualOscClocks(timing->run_clocks) * 1000) / timing->rate_khz)); + + /* + * 4. Check delta wrt previous values (save value if margin + * exceeds what is set in table). + */ + uint32_t del = UpdateClockTreeDelay(timing, timing, dram_dev_num, fbio_cfg7, PERIODIC_TRAINING_UPDATE); + + /* + * 5. Apply compensation w.r.t. trained values (if clock tree + * has drifted more than the set margin). + */ + if (timing->tree_margin < ((del * 128 * (timing->rate_khz / 1000)) / 1000000)) { + for (u32 i = 0; i < util::size(PeriodicCompensationRegisters); ++i) { + reg::Write(EMC + PeriodicCompensationRegisters[i], ApplyPeriodicCompensationTrimmer(timing, PeriodicCompensationRegisters[i])); + } + } + + /* Restore register values. */ + reg::Write(EMC + EMC_CFG, emc_cfg_o); + reg::Write(EMC + EMC_CFG_DIG_DLL, emc_cfg_dig_dll_o); + reg::Write(EMC + EMC_TIMING_CONTROL, 1); + reg::Write(EMC + EMC_CFG_UPDATE, emc_cfg_update_o); + } + } + + void Dvfs(EmcDvfsTimingTable *dst_timing, EmcDvfsTimingTable *src_timing, bool train) { + /* Get the old 2x clock source. */ + const u32 prev_2x_clk_src = reg::GetValue(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC)); + + /* Set g_next_pll. */ + g_next_pll = prev_2x_clk_src == PLLMB_UD || prev_2x_clk_src == PLLMB_OUT0; + + /* Reprogram pll. */ + u32 next_clk_src; + if (PllReprogram(dst_timing->rate_khz, dst_timing->clk_src_emc, src_timing->rate_khz, src_timing->clk_src_emc)) { + if (prev_2x_clk_src == PLLMB_UD || prev_2x_clk_src == PLLMB_OUT0) { + g_next_pll = 0; + } else if (prev_2x_clk_src == PLLM_UD || prev_2x_clk_src == PLLM_OUT0) { + g_next_pll = !g_next_pll; + } + + next_clk_src = ProgramPllm(dst_timing->rate_khz, dst_timing->clk_src_emc, g_next_pll); + } else { + next_clk_src = dst_timing->clk_src_emc; + + const u32 next_2x_clk_src = reg::GetField(next_clk_src, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC)); + if (next_2x_clk_src == PLLM_UD || next_2x_clk_src == PLLMB_UD) { + if (g_next_pll) { + reg::SetField(next_clk_src, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, PLLMB_UD)); + } + } else if (next_2x_clk_src == PLLM_OUT0 || next_2x_clk_src == PLLMB_OUT0) { + if (g_next_pll) { + reg::SetField(next_clk_src, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, PLLMB_OUT0)); + } + } + } + + if (train) { + TrainFreq(src_timing, dst_timing, next_clk_src); + if (PllReprogram(dst_timing->rate_khz, dst_timing->clk_src_emc, src_timing->rate_khz, src_timing->clk_src_emc)) { + g_next_pll = !g_next_pll; + } + } else { + FreqChange(src_timing, dst_timing, 0, next_clk_src); + PeriodicCompensationRoutine(dst_timing); + } + } + + } + + void DoMemoryTrainingErista(int index, void *mtc_tables_buffer) { + /* Get timing tables. */ + auto *timing_tables = GetEmcDvfsTimingTables(index, mtc_tables_buffer); + auto *timing_204 = timing_tables + 0; + auto *timing_800 = timing_tables + 1; + auto *timing_1600 = timing_tables + 2; + + /* Check timing tables. */ + if (timing_204->rate_khz != 204000 || timing_1600->rate_khz != 1600000) { + ShowFatalError("EmcDvfsTimingTables seem corrupted %" PRIu32 " %" PRIu32 " %" PRIu32 "?\n", timing_204->rate_khz, timing_800->rate_khz, timing_1600->rate_khz); + } + + /* Check that we should do training. */ + if (timing_204->clk_src_emc != reg::Read(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC)) { + /* Our clock source isn't what's expected, so presumably training has already been done? */ + /* Either way, the safe bet is to skip it. */ + return; + } + + /* Train 800MHz. */ + Dvfs(timing_800, timing_204, true); + + /* Train 1600MHz. */ + Dvfs(timing_1600, timing_204, true); + + /* Switch to 800MHz. */ + Dvfs(timing_800, timing_204, false); + + /* Switch to 1600MHz. */ + Dvfs(timing_1600, timing_800, false); + + /* Wait 100ms. */ + util::WaitMicroSeconds(100000); + + /* Do Periodic compensation */ + PeriodicCompensationRoutine(timing_1600); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_mariko.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_mariko.cpp new file mode 100644 index 00000000..e6497af7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_mariko.cpp @@ -0,0 +1,2841 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../fusee_fatal.hpp" +#include "../fusee_uncompress.hpp" +#include "fusee_mtc.hpp" +#include "fusee_mtc_timing_table_mariko.hpp" + +namespace ams::nxboot { + + namespace { + + constexpr inline const uintptr_t CLKRST = secmon::MemoryRegionPhysicalDeviceClkRst.GetAddress(); + constexpr inline const uintptr_t MC = MC_BASE; + constexpr inline const uintptr_t EMC = EMC_BASE; + constexpr inline const uintptr_t EMC0 = EMC0_BASE; + constexpr inline const uintptr_t EMC1 = EMC1_BASE; + + static constinit bool g_next_pll = false; + static constinit bool g_did_first_training = false; + static constinit bool g_fsp_for_next_freq = false; + + #include "fusee_mtc_tables_mariko.inc" + #include "fusee_mtc_ram_training_pattern.inc" + + #define DECLARE_REGISTER_HANDLER(BASE, REG, NAME) BASE + REG, + + constexpr inline const u32 BurstRegisters[] = { + FOREACH_BURST_REG(DECLARE_REGISTER_HANDLER) + }; + + constexpr inline const u32 TrimRegisters[] = { + FOREACH_TRIM_REG(DECLARE_REGISTER_HANDLER) + }; + + constexpr inline const u32 BurstMcRegisters[] = { + FOREACH_BURST_MC_REG(DECLARE_REGISTER_HANDLER) + }; + + constexpr inline const u32 LaScaleRegisters[] = { + FOREACH_LA_SCALE_REG(DECLARE_REGISTER_HANDLER) + }; + + constexpr inline const u32 PerChannelTrimRegisters[] = { + FOREACH_PER_CHANNEL_TRIM_REG(DECLARE_REGISTER_HANDLER) + }; + + constexpr inline const u32 PerChannelBurstRegisters[] = { + FOREACH_PER_CHANNEL_BURST_REG(DECLARE_REGISTER_HANDLER) + }; + + constexpr inline const u32 PerChannelVrefRegisters[] = { + FOREACH_PER_CHANNEL_VREF_REG(DECLARE_REGISTER_HANDLER) + }; + + constexpr inline const u32 PerChannelTrainingModRegisters[] = { + FOREACH_PER_CHANNEL_TRAINING_MOD_REG(DECLARE_REGISTER_HANDLER) + }; + + using EmcDvfsTimingTable = mariko::EmcDvfsTimingTable; + + EmcDvfsTimingTable *GetEmcDvfsTimingTables(int index, void *mtc_tables_buffer) { + /* Get the compressed table. */ + const u8 *cmp_table; + size_t cmp_table_size; + switch (index) { + #define HANDLE_CASE(N, TABLE) \ + case N: \ + cmp_table = TABLE; \ + cmp_table_size = sizeof(TABLE); \ + break; + HANDLE_CASE(0x00, T210b01SdevEmcDvfsTableS4gb01) + HANDLE_CASE(0x05, T210b01SdevEmcDvfsTableS4gb03) + HANDLE_CASE(0x06, T210b01SdevEmcDvfsTableS8gb03) + HANDLE_CASE(0x07, T210b01SdevEmcDvfsTableH4gb03) + HANDLE_CASE(0x08, T210b01SdevEmcDvfsTableM4gb03) + HANDLE_CASE(0x09, T210b01SdevEmcDvfsTableS4gbY01) + HANDLE_CASE(0x0A, T210b01SdevEmcDvfsTableS1y4gbY01) + HANDLE_CASE(0x0B, T210b01SdevEmcDvfsTableS1y8gbY01) + HANDLE_CASE(0x0C, T210b01SdevEmcDvfsTableS1y4gbX03) + HANDLE_CASE(0x0D, T210b01SdevEmcDvfsTableS1y8gbX03) + HANDLE_CASE(0x0E, T210b01SdevEmcDvfsTableS1y4gb01) + HANDLE_CASE(0x0F, T210b01SdevEmcDvfsTableM1y4gb01) + HANDLE_CASE(0x10, T210b01SdevEmcDvfsTableH1y4gb01) + HANDLE_CASE(0x11, T210b01SdevEmcDvfsTableS1y8gb04) + HANDLE_CASE(0x12, T210b01SdevEmcDvfsTableS1z4gb01) + HANDLE_CASE(0x13, T210b01SdevEmcDvfsTableH1a4gb01) + HANDLE_CASE(0x14, T210b01SdevEmcDvfsTableM1a4gb01) + default: + ShowFatalError("Unknown EmcDvfsTimingTableIndex: %d\n", index); + } + + /* Uncompress the table. */ + EmcDvfsTimingTable *out_tables = reinterpret_cast<EmcDvfsTimingTable *>(mtc_tables_buffer); + Uncompress(out_tables, 2 * sizeof(EmcDvfsTimingTable), cmp_table, cmp_table_size); + + return out_tables; + } + + bool IsSamePll(u32 next_2x, u32 prev_2x) { + if (next_2x == prev_2x) { + return true; + } else if ((next_2x == PLLM_OUT0 || next_2x == PLLM_UD) && (prev_2x == PLLM_OUT0 || prev_2x == PLLM_UD)) { + return true; + } else { + return false; + } + } + + bool PllReprogram(u32 next_rate_khz, u32 next_clk_src, u32 prev_rate_khz, u32 prev_clk_src) { + /* Get current divp value. */ + u32 pll_p; + switch (reg::GetValue(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC))) { + case PLLM_UD: + case PLLM_OUT0: + pll_p = reg::GetValue(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE, CLK_RST_REG_BITS_MASK(PLLM_BASE_PLLM_DIVP_B01)); + break; + case PLLMB_UD: + case PLLMB_OUT0: + pll_p = reg::GetValue(CLKRST + CLK_RST_CONTROLLER_PLLMB_BASE, CLK_RST_REG_BITS_MASK(PLLMB_BASE_PLLMB_DIVP_B01)); + break; + default: + pll_p = 0; + break; + } + + /* Get clk src/divisor. */ + const u32 next_2x = reg::GetField(next_clk_src, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC)); + const u32 prev_2x = reg::GetField(prev_clk_src, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC)); + u32 next_div = reg::GetField(next_clk_src, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_DIVISOR)); + u32 prev_div = reg::GetField(prev_clk_src, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_DIVISOR)); + + /* Update divisor, if necessary. */ + if (next_2x == PLLM_UD || next_2x == PLLMB_UD) { + next_div = 0; + } + if (prev_2x == PLLM_UD || prev_2x == PLLMB_UD) { + prev_div = 0; + } + + /* If the pll is different, reprogramming is necessary. */ + if (!IsSamePll(next_2x, prev_2x)) { + return true; + } + + /* Return whether the ratios are different. */ + const float next_freq = next_rate_khz * (1 + (next_div >> 1) + (0.5 * (next_div & 1))) * (pll_p + 1); + const float prev_freq = prev_rate_khz * (1 + (prev_div >> 1) + (0.5 * (prev_div & 1))) * (pll_p + 1); + + const float ratio = prev_freq / next_freq; + + return ratio > 1.01 || ratio < 0.99; + } + + u32 ProgramPllm(u32 next_rate_khz, u32 next_clk_src, u32 ret_clk_src, bool is_pllmb, EmcDvfsTimingTable *timing) { + u32 ret = ret_clk_src; + + const uint32_t base = ((timing->pllmb_divm & 0xFF) | ((timing->pllmb_divn & 0xFF) << 8) | ((timing->pllmb_divp & 1) << 20)); + if (is_pllmb) { + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLMB_BASE, base); + reg::Read(CLKRST + CLK_RST_CONTROLLER_PLLMB_BASE); + + reg::SetBits(CLKRST + CLK_RST_CONTROLLER_PLLMB_MISC1, 0x10000000); + + if (timing->pll_en_ssc & 1) { + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLMB_SS_CFG, timing->pllmb_ss_cfg); + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLMB_SS_CTRL1, timing->pllmb_ss_ctrl1); + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLMB_SS_CTRL2, timing->pllmb_ss_ctrl2); + } else { + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLMB_SS_CFG, timing->pllmb_ss_cfg & 0xBFFFFFFF); + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLMB_SS_CTRL2, timing->pllmb_ss_ctrl2 & 0x0000FFFF); + } + + reg::SetBits(CLKRST + CLK_RST_CONTROLLER_PLLMB_BASE, 0x40000000); + + switch (reg::GetField(ret, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC))) { + case PLLM_OUT0: + reg::SetField(ret, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, PLLMB_OUT0)); + break; + case PLLM_UD: + reg::SetField(ret, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, PLLMB_UD)); + break; + } + + while ((reg::Read(CLKRST + CLK_RST_CONTROLLER_PLLMB_BASE) & 0x8000000) == 0) { /* ... */ } + + return ret; + } else { + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE, base); + reg::Read(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE); + + reg::SetBits(CLKRST + CLK_RST_CONTROLLER_PLLM_MISC2, 0x10); + + if (timing->pll_en_ssc & 1) { + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLM_SS_CFG, timing->pllm_ss_cfg); + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLM_SS_CTRL1, timing->pllm_ss_ctrl1); + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLM_SS_CTRL2, timing->pllm_ss_ctrl2); + } else { + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLM_SS_CFG, timing->pllm_ss_cfg & 0xBFFFFFFF); + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLM_SS_CTRL2, timing->pllm_ss_ctrl2 & 0x0000FFFF); + } + + reg::SetBits(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE, 0x40000000); + + switch (reg::GetField(ret, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC))) { + case PLLM_OUT0: + reg::SetField(ret, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, PLLM_OUT0)); + break; + case PLLM_UD: + reg::SetField(ret, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, PLLM_UD)); + break; + } + + while ((reg::Read(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE) & 0x8000000) == 0) { /* ... */ } + + return ret; + } + } + + u32 GetDllState(EmcDvfsTimingTable *timing) { + return (!(timing->emc_emrs & 0x1)) ? DLL_ON : DLL_OFF; + } + + int WaitForUpdate(u32 reg_offset, u32 mask, bool updated, u32 fbio_cfg7) { + constexpr int StatusUpdateTimeout = 1000; + + int result = 0; + + if (reg::GetField(fbio_cfg7, EMC_REG_BITS_MASK(FBIO_CFG7_CH0_ENABLE)) == EMC_FBIO_CFG7_CH0_ENABLE_ENABLE) { + bool success = false; + for (int i = 0; i < StatusUpdateTimeout; ++i) { + if (((reg::Read(EMC0 + reg_offset) & mask) != 0) == updated) { + success = true; + break; + } + util::WaitMicroSeconds(1); + } + result |= success ? 0 : 4; + } + + if (reg::GetField(fbio_cfg7, EMC_REG_BITS_MASK(FBIO_CFG7_CH1_ENABLE)) == EMC_FBIO_CFG7_CH1_ENABLE_ENABLE) { + bool success = false; + for (int i = 0; i < StatusUpdateTimeout; ++i) { + if (((reg::Read(EMC1 + reg_offset) & mask) != 0) == updated) { + success = true; + break; + } + util::WaitMicroSeconds(1); + } + result |= success ? 0 : 4; + } + + return result; + } + + void TimingUpdate(u32 fbio_cfg7) { + /* Trigger the timing update event. */ + reg::Write(EMC + EMC_TIMING_CONTROL, 1); + + /* Wait for the update to finish. */ + WaitForUpdate(EMC_EMC_STATUS, 0x800000, false, fbio_cfg7); + } + + void CcfifoWrite(u32 addr, u32 data, u32 wait) { + reg::Write(EMC + EMC_CCFIFO_DATA, data); + reg::Write(EMC + EMC_CCFIFO_ADDR, (addr & 0xFFFF) | ((wait & 0x7FFF) << 16) | 0x80000000); + } + + u32 ActualOscClocks(u32 in) { + if (in < 0x40) { + return in * 0x10; + } else if (in < 0x80) { + return 0x800; + } else if (in < 0xC0) { + return 0x1000; + } else { + return 0x2000; + } + } + + u32 DivideUpFloat(u32 a, u32 b) { + const float res = a / b; + + const u32 floor = static_cast<u32>(res); + + return floor + ((static_cast<float>(floor) + 0.01 < res) ? 1 : 0); + } + + void StartPeriodicCompensation() { + reg::Write(EMC + EMC_MPC, 0x4B); + reg::Read(EMC + EMC_MPC); + } + + u32 SetShadowBypass(u32 val, u32 emc_dbg) { + reg::SetField(emc_dbg, EMC_REG_BITS_VALUE(DBG_WRITE_MUX, val)); + return emc_dbg; + } + + constinit uint32_t g_periodic_timmer_compensation_intermediates[9 * 0x10] = {}; + + uint32_t ApplyPeriodicCompensationTrimmer(EmcDvfsTimingTable *timing, uint32_t trim_reg) { + /* Initialize variables. */ + uint32_t rate_mhz = timing->rate_khz / 1000; + uint32_t adj[0x10] = { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 }; + + int tree_delta[4] = {0}; + uint32_t tree_delta_taps[4] = {0}; + + /* Generate the intermediate array. */ + #define SET_TRIM_INTERMEDIATE(_arr_, _emc_, _rank_, _byte_) \ + ({ \ + const uint32_t shft = timing->trim_perch_regs.emc## _emc_ ##_data_brlshft_## _rank_; \ + const uint32_t base = ((shft >> (3 * _byte_)) & 7) << 6; \ + const uint32_t val0 = timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank ## _rank_ ## _byte ## _byte_ ## _0; \ + const uint32_t val1 = timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank ## _rank_ ## _byte ## _byte_ ## _1; \ + const uint32_t val2 = timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank ## _rank_ ## _byte ## _byte_ ## _2; \ + _arr_[9 * (8 * _rank_ + _byte_) + 0] = base + ((val0 >> 0) & 0xFF); \ + _arr_[9 * (8 * _rank_ + _byte_) + 1] = base + ((val0 >> 8) & 0xFF); \ + _arr_[9 * (8 * _rank_ + _byte_) + 2] = base + ((val0 >> 16) & 0xFF); \ + _arr_[9 * (8 * _rank_ + _byte_) + 3] = base + ((val0 >> 24) & 0xFF); \ + _arr_[9 * (8 * _rank_ + _byte_) + 4] = base + ((val1 >> 0) & 0xFF); \ + _arr_[9 * (8 * _rank_ + _byte_) + 5] = base + ((val1 >> 8) & 0xFF); \ + _arr_[9 * (8 * _rank_ + _byte_) + 6] = base + ((val1 >> 16) & 0xFF); \ + _arr_[9 * (8 * _rank_ + _byte_) + 7] = base + ((val1 >> 24) & 0xFF); \ + _arr_[9 * (8 * _rank_ + _byte_) + 8] = base + ((val2 >> 0) & 0xFF); \ + }) + { + SET_TRIM_INTERMEDIATE(g_periodic_timmer_compensation_intermediates, 0, 0, 0); + SET_TRIM_INTERMEDIATE(g_periodic_timmer_compensation_intermediates, 0, 0, 1); + SET_TRIM_INTERMEDIATE(g_periodic_timmer_compensation_intermediates, 0, 0, 2); + SET_TRIM_INTERMEDIATE(g_periodic_timmer_compensation_intermediates, 0, 0, 3); + SET_TRIM_INTERMEDIATE(g_periodic_timmer_compensation_intermediates, 1, 0, 4); + SET_TRIM_INTERMEDIATE(g_periodic_timmer_compensation_intermediates, 1, 0, 5); + SET_TRIM_INTERMEDIATE(g_periodic_timmer_compensation_intermediates, 1, 0, 6); + SET_TRIM_INTERMEDIATE(g_periodic_timmer_compensation_intermediates, 1, 0, 7); + SET_TRIM_INTERMEDIATE(g_periodic_timmer_compensation_intermediates, 0, 1, 0); + SET_TRIM_INTERMEDIATE(g_periodic_timmer_compensation_intermediates, 0, 1, 1); + SET_TRIM_INTERMEDIATE(g_periodic_timmer_compensation_intermediates, 0, 1, 2); + SET_TRIM_INTERMEDIATE(g_periodic_timmer_compensation_intermediates, 0, 1, 3); + SET_TRIM_INTERMEDIATE(g_periodic_timmer_compensation_intermediates, 1, 1, 4); + SET_TRIM_INTERMEDIATE(g_periodic_timmer_compensation_intermediates, 1, 1, 5); + SET_TRIM_INTERMEDIATE(g_periodic_timmer_compensation_intermediates, 1, 1, 6); + SET_TRIM_INTERMEDIATE(g_periodic_timmer_compensation_intermediates, 1, 1, 7); + } + #undef SET_TRIM_INTERMEDIATE + + switch (trim_reg) { + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_2: + case EMC0_BASE + EMC_DATA_BRLSHFT_0: + case EMC1_BASE + EMC_DATA_BRLSHFT_0: + { + tree_delta[0] = 128 * (timing->current_dram_clktree_c0d0u0 - timing->trained_dram_clktree_c0d0u0); + tree_delta[1] = 128 * (timing->current_dram_clktree_c0d0u1 - timing->trained_dram_clktree_c0d0u1); + tree_delta[2] = 128 * (timing->current_dram_clktree_c1d0u0 - timing->trained_dram_clktree_c1d0u0); + tree_delta[3] = 128 * (timing->current_dram_clktree_c1d0u1 - timing->trained_dram_clktree_c1d0u1); + tree_delta_taps[0] = (tree_delta[0] * (int)rate_mhz) / 1000000; + tree_delta_taps[1] = (tree_delta[1] * (int)rate_mhz) / 1000000; + tree_delta_taps[2] = (tree_delta[2] * (int)rate_mhz) / 1000000; + tree_delta_taps[3] = (tree_delta[3] * (int)rate_mhz) / 1000000; + + for (int i = 0; i < 4; ++i) { + const uint32_t sum = (tree_delta_taps[i] <= timing->tree_margin) ? 0 : tree_delta_taps[i]; + + for (int j = 0; j < 18; ++j) { + const uint32_t v = (g_periodic_timmer_compensation_intermediates[18 * i + j] += sum); + if (v < (adj[2 * i + (j < 9)] << 6)) { + adj[2 * i + (j < 9)] = v >> 6; + } + } + for (int j = 0; j < 18; ++j) { + g_periodic_timmer_compensation_intermediates[18 * i + j] -= (adj[2 * i + (j < 9)] << 6); + } + } + } + break; + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_2: + case EMC0_BASE + EMC_DATA_BRLSHFT_1: + case EMC1_BASE + EMC_DATA_BRLSHFT_1: + { + tree_delta[0] = 128 * (timing->current_dram_clktree_c0d1u0 - timing->trained_dram_clktree_c0d1u0); + tree_delta[1] = 128 * (timing->current_dram_clktree_c0d1u1 - timing->trained_dram_clktree_c0d1u1); + tree_delta[2] = 128 * (timing->current_dram_clktree_c1d1u0 - timing->trained_dram_clktree_c1d1u0); + tree_delta[3] = 128 * (timing->current_dram_clktree_c1d1u1 - timing->trained_dram_clktree_c1d1u1); + tree_delta_taps[0] = (tree_delta[0] * (int)rate_mhz) / 1000000; + tree_delta_taps[1] = (tree_delta[1] * (int)rate_mhz) / 1000000; + tree_delta_taps[2] = (tree_delta[2] * (int)rate_mhz) / 1000000; + tree_delta_taps[3] = (tree_delta[3] * (int)rate_mhz) / 1000000; + + for (int i = 0; i < 4; ++i) { + const uint32_t sum = (tree_delta_taps[i] <= timing->tree_margin) ? 0 : tree_delta_taps[i]; + + for (int j = 0; j < 18; ++j) { + const uint32_t v = (g_periodic_timmer_compensation_intermediates[72 + 18 * i + j] += sum); + if (v < (adj[8 + 2 * i + (j < 9)] << 6)) { + adj[8 + 2 * i + (j < 9)] = v >> 6; + } + } + for (int j = 0; j < 18; ++j) { + g_periodic_timmer_compensation_intermediates[72 + 18 * i + j] -= (adj[8 + 2 * i + (j < 9)] << 6); + } + } + } + break; + } + + uint32_t result = 0; + switch (trim_reg) { + case EMC0_BASE + EMC_DATA_BRLSHFT_0: + result = ((adj[ 0] & 7) << 0) | ((adj[ 1] & 7) << 3) | ((adj[ 2] & 7) << 6) | ((adj[ 3] & 7) << 9); + break; + case EMC1_BASE + EMC_DATA_BRLSHFT_0: + result = ((adj[ 4] & 7) << 12) | ((adj[ 5] & 7) << 15) | ((adj[ 6] & 7) << 18) | ((adj[ 7] & 7) << 21); + break; + case EMC0_BASE + EMC_DATA_BRLSHFT_1: + result = ((adj[ 8] & 7) << 0) | ((adj[ 9] & 7) << 3) | ((adj[10] & 7) << 6) | ((adj[11] & 7) << 9); + break; + case EMC1_BASE + EMC_DATA_BRLSHFT_1: + result = ((adj[12] & 7) << 12) | ((adj[13] & 7) << 15) | ((adj[14] & 7) << 18) | ((adj[15] & 7) << 21); + break; + #define ADD_TRIM_CASE(_ARR_, _RANK_, _BYTE_) \ + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK ## _RANK_ ## _BYTE ## _BYTE_ ##_0: \ + result = ((_ARR_[9 * (8 * _RANK_ + _BYTE_) + 0] & 0xFF) << 0) | ((_ARR_[9 * (8 * _RANK_ + _BYTE_) + 1] & 0xFF) << 8) | ((_ARR_[9 * (8 * _RANK_ + _BYTE_) + 2] & 0xFF) << 16) | ((_ARR_[9 * (8 * _RANK_ + _BYTE_) + 3] & 0xFF) << 24); \ + break; \ + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK ## _RANK_ ## _BYTE ## _BYTE_ ##_1: \ + result = ((_ARR_[9 * (8 * _RANK_ + _BYTE_) + 4] & 0xFF) << 0) | ((_ARR_[9 * (8 * _RANK_ + _BYTE_) + 5] & 0xFF) << 8) | ((_ARR_[9 * (8 * _RANK_ + _BYTE_) + 6] & 0xFF) << 16) | ((_ARR_[9 * (8 * _RANK_ + _BYTE_) + 7] & 0xFF) << 24); \ + break; \ + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK ## _RANK_ ## _BYTE ## _BYTE_ ##_2: \ + result = ((_ARR_[9 * (8 * _RANK_ + _BYTE_) + 8] & 0xFF) << 0); \ + break; + ADD_TRIM_CASE(g_periodic_timmer_compensation_intermediates, 0, 0); + ADD_TRIM_CASE(g_periodic_timmer_compensation_intermediates, 0, 1); + ADD_TRIM_CASE(g_periodic_timmer_compensation_intermediates, 0, 2); + ADD_TRIM_CASE(g_periodic_timmer_compensation_intermediates, 0, 3); + ADD_TRIM_CASE(g_periodic_timmer_compensation_intermediates, 0, 4); + ADD_TRIM_CASE(g_periodic_timmer_compensation_intermediates, 0, 5); + ADD_TRIM_CASE(g_periodic_timmer_compensation_intermediates, 0, 6); + ADD_TRIM_CASE(g_periodic_timmer_compensation_intermediates, 0, 7); + ADD_TRIM_CASE(g_periodic_timmer_compensation_intermediates, 1, 0); + ADD_TRIM_CASE(g_periodic_timmer_compensation_intermediates, 1, 1); + ADD_TRIM_CASE(g_periodic_timmer_compensation_intermediates, 1, 2); + ADD_TRIM_CASE(g_periodic_timmer_compensation_intermediates, 1, 3); + ADD_TRIM_CASE(g_periodic_timmer_compensation_intermediates, 1, 4); + ADD_TRIM_CASE(g_periodic_timmer_compensation_intermediates, 1, 5); + ADD_TRIM_CASE(g_periodic_timmer_compensation_intermediates, 1, 6); + ADD_TRIM_CASE(g_periodic_timmer_compensation_intermediates, 1, 7); + #undef ADD_TRIM_CASE + } + + return result; + } + + u32 UpdateClockTreeDelay(EmcDvfsTimingTable *src_timing, EmcDvfsTimingTable *dst_timing, u32 dram_dev_num, u32 mode, int type) { + uint32_t mrr_req = 0, mrr_data = 0; + uint32_t temp0_0 = 0, temp0_1 = 0, temp1_0 = 0, temp1_1 = 0; + int tdel = 0, tmdel = 0, adel = 0; + + uint32_t current_timing_rate_mhz = src_timing->rate_khz / 1000; + uint32_t next_timing_rate_mhz = dst_timing->rate_khz / 1000; + uint32_t fbio_cfg7 = dst_timing->emc_fbio_cfg7; + + const bool ch0_enable = reg::GetField(fbio_cfg7, EMC_REG_BITS_MASK(FBIO_CFG7_CH0_ENABLE)) == EMC_FBIO_CFG7_CH0_ENABLE_ENABLE; + const bool ch1_enable = reg::GetField(fbio_cfg7, EMC_REG_BITS_MASK(FBIO_CFG7_CH1_ENABLE)) == EMC_FBIO_CFG7_CH1_ENABLE_ENABLE; + + bool dvfs_pt1 = (type == DVFS_PT1); + bool training_pt1 = (type == TRAINING_PT1); + bool dvfs_update = (type == DVFS_UPDATE); + bool training_update = (type == TRAINING_UPDATE); + bool periodic_training_update = (type == PERIODIC_TRAINING_UPDATE); + + /* Dev0 MSB. */ + if (dvfs_pt1 || training_pt1 || periodic_training_update) { + mrr_req = ((2 << 30) | (19 << 16)); + reg::Write(EMC + EMC_MRR, mrr_req); + + WaitForUpdate(EMC_EMC_STATUS, (1 << 20), true, fbio_cfg7); + + if (ch0_enable) { + mrr_data = (reg::Read(EMC0 + EMC_MRR) & 0xFFFF); + + temp0_0 = ((mrr_data & 0xff) << 8); + temp0_1 = (mrr_data & 0xff00); + } else { + temp0_0 = temp0_1 = 0; + } + if (ch1_enable) { + mrr_data = (reg::Read(EMC1 + EMC_MRR) & 0xFFFF); + + temp1_0 = ((mrr_data & 0xff) << 8); + temp1_1 = (mrr_data & 0xff00); + } else { + temp1_0 = temp1_1 = 0; + } + + /* Dev0 LSB. */ + mrr_req = ((mrr_req & ~(0xFF << 16)) | (18 << 16)); + reg::Write(EMC + EMC_MRR, mrr_req); + + WaitForUpdate(EMC_EMC_STATUS, (1 << 20), true, fbio_cfg7); + + if (ch0_enable) { + mrr_data = (reg::Read(EMC0 + EMC_MRR) & 0xFFFF); + + temp0_0 |= (mrr_data & 0xff); + temp0_1 |= (mrr_data & 0xff00) >> 8; + } + if (ch1_enable) { + mrr_data = (reg::Read(EMC1 + EMC_MRR) & 0xFFFF); + + temp1_0 |= (mrr_data & 0xff); + temp1_1 |= (mrr_data & 0xff00) >> 8; + } + } + + #define CVAL(v) ((uint32_t)((1000 * ((1000 * ActualOscClocks(src_timing->run_clocks)) / current_timing_rate_mhz)) / (2 * v))) + + if (ch0_enable) { + if (dvfs_pt1 || training_pt1) + __INCREMENT_PTFV(c0d0u0, CVAL(temp0_0)); + else if (dvfs_update) + __AVERAGE_PTFV(c0d0u0); + else if (training_update) + __AVERAGE_WRITE_PTFV(c0d0u0); + else if (periodic_training_update) + __WEIGHTED_UPDATE_PTFV(c0d0u0, CVAL(temp0_0)); + + if (dvfs_update || training_update || periodic_training_update) { + tdel = (dst_timing->current_dram_clktree_c0d0u0 - __MOVAVG_AC(dst_timing, c0d0u0)); + tmdel = (tdel < 0) ? ~tdel : tdel; + adel = tmdel; + if (mode == 1 || ((adel * 128 * next_timing_rate_mhz) / 1000000) > dst_timing->tree_margin) + dst_timing->current_dram_clktree_c0d0u0 = __MOVAVG_AC(dst_timing, c0d0u0); + } + + if (dvfs_pt1 || training_pt1) + __INCREMENT_PTFV(c0d0u1, CVAL(temp0_1)); + else if (dvfs_update) + __AVERAGE_PTFV(c0d0u1); + else if (training_update) + __AVERAGE_WRITE_PTFV(c0d0u1); + else if (periodic_training_update) + __WEIGHTED_UPDATE_PTFV(c0d0u1, CVAL(temp0_1)); + + if (dvfs_update || training_update || periodic_training_update) { + tdel = (dst_timing->current_dram_clktree_c0d0u1 - __MOVAVG_AC(dst_timing, c0d0u1)); + tmdel = (tdel < 0) ? -1 * tdel : tdel; + + if (tmdel > adel) + adel = tmdel; + + if (mode == 1 || (tmdel * 128 * next_timing_rate_mhz / 1000000) > dst_timing->tree_margin) + dst_timing->current_dram_clktree_c0d0u1 = __MOVAVG_AC(dst_timing, c0d0u1); + } + } else { + adel = 0; + } + + if (ch1_enable) { + if (dvfs_pt1 || training_pt1) + __INCREMENT_PTFV(c1d0u0, CVAL(temp1_0)); + else if (dvfs_update) + __AVERAGE_PTFV(c1d0u0); + else if (training_update) + __AVERAGE_WRITE_PTFV(c1d0u0); + else if (periodic_training_update) + __WEIGHTED_UPDATE_PTFV(c1d0u0, CVAL(temp1_0)); + + if (dvfs_update || training_update || periodic_training_update) { + tdel = (dst_timing->current_dram_clktree_c1d0u0 - __MOVAVG_AC(dst_timing, c1d0u0)); + tmdel = (tdel < 0) ? -1 * tdel : tdel; + + if (tmdel > adel) + adel = tmdel; + + if (mode == 1 || (tmdel * 128 * next_timing_rate_mhz / 1000000) > dst_timing->tree_margin) + dst_timing->current_dram_clktree_c1d0u0 = __MOVAVG_AC(dst_timing, c1d0u0); + } + + if (dvfs_pt1 || training_pt1) + __INCREMENT_PTFV(c1d0u1, CVAL(temp1_1)); + else if (dvfs_update) + __AVERAGE_PTFV(c1d0u1); + else if (training_update) + __AVERAGE_WRITE_PTFV(c1d0u1); + else if (periodic_training_update) + __WEIGHTED_UPDATE_PTFV(c1d0u1, CVAL(temp1_1)); + + if (dvfs_update || training_update || periodic_training_update) { + tdel = (dst_timing->current_dram_clktree_c1d0u1 - __MOVAVG_AC(dst_timing, c1d0u1)); + tmdel = (tdel < 0) ? -1 * tdel : tdel; + + if (tmdel > adel) + adel = tmdel; + + if (mode == 1 || (tmdel * 128 * next_timing_rate_mhz / 1000000) > dst_timing->tree_margin) + dst_timing->current_dram_clktree_c1d0u1 = __MOVAVG_AC(dst_timing, c1d0u1); + } + } + + if (dram_dev_num == TWO_RANK) { + /* Dev1 MSB. */ + if (dvfs_pt1 || training_pt1 || periodic_training_update) { + mrr_req = ((1 << 30) | (19 << 16)); + reg::Write(EMC + EMC_MRR, mrr_req); + + WaitForUpdate(EMC_EMC_STATUS, (1 << 20), true, fbio_cfg7); + + if (ch0_enable) { + mrr_data = (reg::Read(EMC0 + EMC_MRR) & 0xFFFF); + + temp0_0 = ((mrr_data & 0xff) << 8); + temp0_1 = (mrr_data & 0xff00); + } + if (ch1_enable) { + mrr_data = (reg::Read(EMC1 + EMC_MRR) & 0xFFFF); + + temp1_0 = ((mrr_data & 0xff) << 8); + temp1_1 = (mrr_data & 0xff00); + } + + /* Dev1 LSB. */ + mrr_req = ((mrr_req & ~(0xFF << 16)) | (18 << 16)); + reg::Write(EMC + EMC_MRR, mrr_req); + + WaitForUpdate(EMC_EMC_STATUS, (1 << 20), true, fbio_cfg7); + + if (ch0_enable) { + mrr_data = (reg::Read(EMC0 + EMC_MRR) & 0xFFFF); + + temp0_0 |= ((mrr_data & 0xff) << 8); + temp0_1 |= (mrr_data & 0xff00); + } + if (ch1_enable) { + mrr_data = (reg::Read(EMC1 + EMC_MRR) & 0xFFFF); + + temp1_0 |= ((mrr_data & 0xff) << 8); + temp1_1 |= (mrr_data & 0xff00); + } + } + + if (ch0_enable) { + if (dvfs_pt1 || training_pt1) + __INCREMENT_PTFV(c0d1u0, CVAL(temp0_0)); + else if (dvfs_update) + __AVERAGE_PTFV(c0d1u0); + else if (training_update) + __AVERAGE_WRITE_PTFV(c0d1u0); + else if (periodic_training_update) + __WEIGHTED_UPDATE_PTFV(c0d1u0, CVAL(temp0_0)); + + if (dvfs_update || training_update || periodic_training_update) { + tdel = (dst_timing->current_dram_clktree_c0d1u0 - __MOVAVG_AC(dst_timing, c0d1u0)); + tmdel = (tdel < 0) ? -1 * tdel : tdel; + + if (tmdel > adel) + adel = tmdel; + + if (mode == 1 || (tmdel * 128 * next_timing_rate_mhz / 1000000) > dst_timing->tree_margin) + dst_timing->current_dram_clktree_c0d1u0 = __MOVAVG_AC(dst_timing, c0d1u0); + } + + if (dvfs_pt1 || training_pt1) + __INCREMENT_PTFV(c0d1u1, CVAL(temp0_1)); + else if (dvfs_update) + __AVERAGE_PTFV(c0d1u1); + else if (training_update) + __AVERAGE_WRITE_PTFV(c0d1u1); + else if (periodic_training_update) + __WEIGHTED_UPDATE_PTFV(c0d1u1, CVAL(temp0_1)); + + if (dvfs_update || training_update || periodic_training_update) { + tdel = (dst_timing->current_dram_clktree_c0d1u1 - __MOVAVG_AC(dst_timing, c0d1u1)); + tmdel = (tdel < 0) ? -1 * tdel : tdel; + + if (tmdel > adel) + adel = tmdel; + + if (mode == 1 || (tmdel * 128 * next_timing_rate_mhz / 1000000) > dst_timing->tree_margin) + dst_timing->current_dram_clktree_c0d1u1 = __MOVAVG_AC(dst_timing, c0d1u1); + } + } + + if (ch1_enable) { + if (dvfs_pt1 || training_pt1) + __INCREMENT_PTFV(c1d1u0, CVAL(temp1_0)); + else if (dvfs_update) + __AVERAGE_PTFV(c1d1u0); + else if (training_update) + __AVERAGE_WRITE_PTFV(c1d1u0); + else if (periodic_training_update) + __WEIGHTED_UPDATE_PTFV(c1d1u0, CVAL(temp1_0)); + + if (dvfs_update || training_update || periodic_training_update) { + tdel = (dst_timing->current_dram_clktree_c1d1u0 - __MOVAVG_AC(dst_timing, c1d1u0)); + tmdel = (tdel < 0) ? -1 * tdel : tdel; + + if (tmdel > adel) + adel = tmdel; + + if (mode == 1 || (tmdel * 128 * next_timing_rate_mhz / 1000000) > dst_timing->tree_margin) + dst_timing->current_dram_clktree_c1d1u0 = __MOVAVG_AC(dst_timing, c1d1u0); + } + + if (dvfs_pt1 || training_pt1) + __INCREMENT_PTFV(c1d1u1, CVAL(temp1_1)); + else if (dvfs_update) + __AVERAGE_PTFV(c1d1u1); + else if (training_update) + __AVERAGE_WRITE_PTFV(c1d1u1); + else if (periodic_training_update) + __WEIGHTED_UPDATE_PTFV(c1d1u1, CVAL(temp1_1)); + + if (dvfs_update || training_update || periodic_training_update) { + tdel = (dst_timing->current_dram_clktree_c1d1u1 - __MOVAVG_AC(dst_timing, c1d1u1)); + tmdel = (tdel < 0) ? -1 * tdel : tdel; + + if (tmdel > adel) + adel = tmdel; + + if (mode == 1 || (tmdel * 128 * next_timing_rate_mhz / 1000000) > dst_timing->tree_margin) + dst_timing->current_dram_clktree_c1d1u1 = __MOVAVG_AC(dst_timing, c1d1u1); + } + } + } + + #undef CVAL + + if (mode == 1) { + dst_timing->trained_dram_clktree_c0d0u0 = dst_timing->current_dram_clktree_c0d0u0; + dst_timing->trained_dram_clktree_c0d0u1 = dst_timing->current_dram_clktree_c0d0u1; + dst_timing->trained_dram_clktree_c0d1u0 = dst_timing->current_dram_clktree_c0d1u0; + dst_timing->trained_dram_clktree_c0d1u1 = dst_timing->current_dram_clktree_c0d1u1; + dst_timing->trained_dram_clktree_c1d0u0 = dst_timing->current_dram_clktree_c1d0u0; + dst_timing->trained_dram_clktree_c1d0u1 = dst_timing->current_dram_clktree_c1d0u1; + dst_timing->trained_dram_clktree_c1d1u0 = dst_timing->current_dram_clktree_c1d1u0; + dst_timing->trained_dram_clktree_c1d1u1 = dst_timing->current_dram_clktree_c1d1u1; + } + + return adel; + } + + u32 PeriodicCompensationHandler(int type, u32 dram_dev_num, EmcDvfsTimingTable *src_timing, EmcDvfsTimingTable *dst_timing) { + if (!dst_timing->periodic_training) { + return 0; + } + + uint32_t adel = 0; + uint32_t samples = dst_timing->ptfv_dvfs_samples; + uint32_t samples_write = dst_timing->ptfv_write_samples; + uint32_t delay = 2 + (1000 * ActualOscClocks(src_timing->run_clocks) / src_timing->rate_khz); + + if (type == DVFS_SEQUENCE) { + if (src_timing->periodic_training && (dst_timing->ptfv_config_ctrl & 1)) { + /* If the previous frequency was using periodic calibration then we can reuse the previous frequencies EMA data. */ + dst_timing->ptfv_dqsosc_movavg_c0d0u0 = src_timing->ptfv_dqsosc_movavg_c0d0u0 * samples; + dst_timing->ptfv_dqsosc_movavg_c0d0u1 = src_timing->ptfv_dqsosc_movavg_c0d0u1 * samples; + dst_timing->ptfv_dqsosc_movavg_c1d0u0 = src_timing->ptfv_dqsosc_movavg_c1d0u0 * samples; + dst_timing->ptfv_dqsosc_movavg_c1d0u1 = src_timing->ptfv_dqsosc_movavg_c1d0u1 * samples; + dst_timing->ptfv_dqsosc_movavg_c0d1u0 = src_timing->ptfv_dqsosc_movavg_c0d1u0 * samples; + dst_timing->ptfv_dqsosc_movavg_c0d1u1 = src_timing->ptfv_dqsosc_movavg_c0d1u1 * samples; + dst_timing->ptfv_dqsosc_movavg_c1d1u0 = src_timing->ptfv_dqsosc_movavg_c1d1u0 * samples; + dst_timing->ptfv_dqsosc_movavg_c1d1u1 = src_timing->ptfv_dqsosc_movavg_c1d1u1 * samples; + } else { + /* Reset the EMA. */ + dst_timing->ptfv_dqsosc_movavg_c0d0u0 = 0; + dst_timing->ptfv_dqsosc_movavg_c0d0u1 = 0; + dst_timing->ptfv_dqsosc_movavg_c0d1u0 = 0; + dst_timing->ptfv_dqsosc_movavg_c0d1u1 = 0; + dst_timing->ptfv_dqsosc_movavg_c1d0u0 = 0; + dst_timing->ptfv_dqsosc_movavg_c1d0u1 = 0; + dst_timing->ptfv_dqsosc_movavg_c1d1u0 = 0; + dst_timing->ptfv_dqsosc_movavg_c1d1u1 = 0; + + for (uint32_t i = 0; i < samples; ++i) { + StartPeriodicCompensation(); + + util::WaitMicroSeconds(delay); + + /* Generate next sample of data. */ + adel = UpdateClockTreeDelay(src_timing, dst_timing, dram_dev_num, 0, DVFS_PT1); + } + } + + adel = UpdateClockTreeDelay(src_timing, dst_timing, dram_dev_num, 0, DVFS_UPDATE); + } else if (type == WRITE_TRAINING_SEQUENCE) { + /* Reset the EMA. */ + dst_timing->ptfv_dqsosc_movavg_c0d0u0 = 0; + dst_timing->ptfv_dqsosc_movavg_c0d0u1 = 0; + dst_timing->ptfv_dqsosc_movavg_c0d1u0 = 0; + dst_timing->ptfv_dqsosc_movavg_c0d1u1 = 0; + dst_timing->ptfv_dqsosc_movavg_c1d0u0 = 0; + dst_timing->ptfv_dqsosc_movavg_c1d0u1 = 0; + dst_timing->ptfv_dqsosc_movavg_c1d1u0 = 0; + dst_timing->ptfv_dqsosc_movavg_c1d1u1 = 0; + + for (uint32_t i = 0; i < samples_write; ++i) { + StartPeriodicCompensation(); + + util::WaitMicroSeconds(delay); + + /* Generate next sample of data. */ + adel = UpdateClockTreeDelay(src_timing, dst_timing, dram_dev_num, 1, TRAINING_PT1); + } + + adel = UpdateClockTreeDelay(src_timing, dst_timing, dram_dev_num, 1, TRAINING_UPDATE); + } else if (type == PERIODIC_TRAINING_SEQUENCE) { + StartPeriodicCompensation(); + + util::WaitMicroSeconds(delay); + + adel = UpdateClockTreeDelay(src_timing, dst_timing, dram_dev_num, 0, PERIODIC_TRAINING_UPDATE); + } + + return adel; + } + + void ChangeDllSrc(EmcDvfsTimingTable *dst_timing, u32 next_clk_src) { + u32 dll_setting = ((next_clk_src & 0xE00000FF) | (dst_timing->dll_clk_src & 0x1FFFFF00)) & 0xFFFFF3FF; + switch (reg::GetField(next_clk_src, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC))) { + case PLLMB_UD: + dll_setting |= 0x400; /* PLLM_VCOB */ + break; + case PLLM_UD: + dll_setting |= 0x000; /* PLLM_VCOA */ + break; + default: + dll_setting |= 0x800; /* EMC_DLL_SWITCH_OUT */ + break; + } + + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC_DLL, dll_setting); + + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_X, CLK_RST_REG_BITS_ENUM_SEL(CLK_ENB_X_CLK_ENB_EMC_DLL, (dst_timing->clk_out_enb_x_0_clk_enb_emc_dll & 1), ENABLE, DISABLE)); + reg::Read(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_X); + } + + void DllPrelock(EmcDvfsTimingTable *dst_timing, EmcDvfsTimingTable *src_timing, bool training_enabled, u32 next_clk_src) { + /* Update EMC_CFG_DIG_DLL */ + reg::Write(EMC + EMC_CFG_DIG_DLL, (reg::Read(EMC + EMC_CFG_DIG_DLL) & 0xFFFFFFE4) | 0x00000008); + + /* Request a timing update event */ + TimingUpdate(dst_timing->emc_fbio_cfg7); + + /* Update EMC_CFG_DIG_DLL */ + reg::Write(EMC + EMC_CFG_DIG_DLL, (reg::Read(EMC + EMC_CFG_DIG_DLL) & 0xFFFFF824) | 0x000003C8); + + /* Request a timing update event */ + TimingUpdate(dst_timing->emc_fbio_cfg7); + + /* Wait until CFG_DLL_EN is cleared. */ + WaitForUpdate(EMC_CFG_DIG_DLL, (1 << 0), false, dst_timing->emc_fbio_cfg7); + + /* Configure PMACRO_DLL_CFG */ + reg::Write(EMC + EMC_PMACRO_DLL_CFG_0, dst_timing->burst_regs.emc_pmacro_dll_cfg_0); + reg::Read(EMC + EMC_PMACRO_DLL_CFG_1); + + reg::Write(EMC + EMC_PMACRO_DLL_CFG_1, (dst_timing->burst_regs.emc_pmacro_dll_cfg_1 & 0xFFFFDFFF) | (reg::Read(EMC + EMC_PMACRO_DLL_CFG_1) & 0x00002000)); + + /* Request a timing update event */ + TimingUpdate(dst_timing->emc_fbio_cfg7); + + /* Change the dll clock source. */ + ChangeDllSrc(dst_timing, next_clk_src); + + /* Wait 2 us. */ + util::WaitMicroSeconds(2); + + /* Enable dll. */ + reg::SetBits(EMC + EMC_CFG_DIG_DLL, 0x1); + + /* Request a timing update event */ + TimingUpdate(dst_timing->emc_fbio_cfg7); + + /* Wait until CFG_DLL_EN is set. */ + WaitForUpdate(EMC_CFG_DIG_DLL, (1 << 0), true, dst_timing->emc_fbio_cfg7); + + /* Wait for DLL_LOCK to be set */ + WaitForUpdate(EMC_DIG_DLL_STATUS, (1 << 2), true, dst_timing->emc_fbio_cfg7); + + if (training_enabled) { + /* Disable dll. */ + reg::SetBits(EMC + EMC_DBG, 0x2); + reg::ClearBits(EMC + EMC_CFG_DIG_DLL, 0x1); + reg::ClearBits(EMC + EMC_DBG, 0x2); + + /* Wait until CFG_DLL_EN is cleared. */ + WaitForUpdate(EMC_CFG_DIG_DLL, (1 << 0), false, dst_timing->emc_fbio_cfg7); + } + + reg::Read(EMC + EMC_PMACRO_DIG_DLL_STATUS_0); + } + + void DllDisable(u32 fbio_cfg7) { + /* Disable dll. */ + reg::ClearBits(EMC + EMC_CFG_DIG_DLL, 0x1); + + /* Request a timing update event */ + TimingUpdate(fbio_cfg7); + + /* Wait until CFG_DLL_EN is cleared. */ + WaitForUpdate(EMC_CFG_DIG_DLL, (1 << 0), false, fbio_cfg7); + } + + void PllDisable(u32 dst_clk_src) { + switch (reg::GetField(dst_clk_src, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC))) { + case PLLM_OUT0: + case PLLM_UD: + reg::ClearBits(CLKRST + CLK_RST_CONTROLLER_PLLMB_BASE, 0x40000000); + break; + case PLLMB_OUT0: + case PLLMB_UD: + reg::ClearBits(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE, 0x40000000); + break; + default: + reg::ClearBits(CLKRST + CLK_RST_CONTROLLER_PLLMB_BASE, 0x40000000); + reg::ClearBits(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE, 0x40000000); + break; + } + } + + void DvfsPowerRampDown(EmcDvfsTimingTable *src_timing, EmcDvfsTimingTable *dst_timing, bool flip_backward, u32 vtt_vdda_channel) { + auto *from_table = (flip_backward ? dst_timing : src_timing); + auto *to_table = (flip_backward ? src_timing : dst_timing); + + uint32_t from_rate_khz = from_table->rate_khz; + uint32_t from_period = 1000000000 / from_rate_khz; + + uint32_t to_rate_khz = to_table->rate_khz; + uint32_t clk_div = 1000 * dst_timing->src_clock_div; + + uint32_t delay = util::DivideUp(clk_div, from_period); + + if (from_rate_khz >= 407997 || to_rate_khz <= 407996) { + if (from_rate_khz >= 407997 && to_rate_khz <= 407996) { + uint32_t pmacro_vttgen_ctrl_1 = reg::Read(EMC + EMC_PMACRO_VTTGEN_CTRL_1); + + if (dst_timing->vtt_vdda_dual_channel) { + if (vtt_vdda_channel != 1) { + return; + } + + CcfifoWrite(EMC_PMACRO_VTTGEN_CTRL_1, (pmacro_vttgen_ctrl_1 & 0xFFFF03FF) | ((to_table->vtt_vdda_ctrl_4 & 0x3F) << 10), delay); + CcfifoWrite(EMC_PMACRO_VTTGEN_CTRL_1, (pmacro_vttgen_ctrl_1 & 0xFFFF03FF) | ((to_table->vtt_vdda_ctrl_0 & 0x3F) << 10), delay * 2); + } else { + CcfifoWrite(EMC_PMACRO_VTTGEN_CTRL_1, (pmacro_vttgen_ctrl_1 & 0xFFFF03FF) | ((dst_timing->vtt_vdda_ctrl_0 & 0x3F) << 10), 0); + } + } + } else { + uint32_t pmacro_vttgen_ctrl_1 = reg::Read(EMC + EMC_PMACRO_VTTGEN_CTRL_1); + + if (dst_timing->vtt_vdda_dual_channel) { + if (vtt_vdda_channel == 1) { + CcfifoWrite(EMC_PMACRO_VTTGEN_CTRL_1, (pmacro_vttgen_ctrl_1 & 0xFFFF03FF) | ((dst_timing->vtt_vdda_ctrl_3 & 0x3F) << 10), delay); + CcfifoWrite(EMC_PMACRO_VTTGEN_CTRL_1, (pmacro_vttgen_ctrl_1 & 0xFFFF03FF) | ((dst_timing->vtt_vdda_ctrl_0 & 0x3F) << 10), delay); + } else if (vtt_vdda_channel == 0) { + CcfifoWrite(EMC_PMACRO_VTTGEN_CTRL_1, (pmacro_vttgen_ctrl_1 & 0xFFFF03FF) | ((dst_timing->vtt_vdda_ctrl_1 & 0x3F) << 10), delay); + CcfifoWrite(EMC_PMACRO_VTTGEN_CTRL_1, (pmacro_vttgen_ctrl_1 & 0xFFFF03FF) | ((dst_timing->vtt_vdda_ctrl_2 & 0x3F) << 10), delay); + } + } else { + CcfifoWrite(EMC_PMACRO_VTTGEN_CTRL_1, (pmacro_vttgen_ctrl_1 & 0xFFFF03FF) | ((dst_timing->vtt_vdda_ctrl_0 & 0x3F) << 10), 0); + } + } + } + + u32 DvfsPowerRampUp(u32 dst_clock_period, bool flip_backward, EmcDvfsTimingTable *src_timing, EmcDvfsTimingTable *dst_timing, u32 training) { + uint32_t misc_cfg_1 = flip_backward ? src_timing->misc_cfg_1 : dst_timing->misc_cfg_1; + + uint32_t emc_pmacro_cmd_pad_tx_ctrl, emc_pmacro_brick_ctrl_rfu1, emc_fbio_cfg5; + if (flip_backward) { + emc_pmacro_cmd_pad_tx_ctrl = src_timing->burst_regs.emc_pmacro_cmd_pad_tx_ctrl; + emc_pmacro_brick_ctrl_rfu1 = src_timing->burst_regs.emc_pmacro_brick_ctrl_rfu1; + emc_fbio_cfg5 = src_timing->burst_regs.emc_fbio_cfg5; + } else if (training & (CA_TRAINING | CA_VREF_TRAINING)) { + emc_pmacro_cmd_pad_tx_ctrl = dst_timing->shadow_regs_ca_train.emc_pmacro_cmd_pad_tx_ctrl; + emc_pmacro_brick_ctrl_rfu1 = dst_timing->shadow_regs_ca_train.emc_pmacro_brick_ctrl_rfu1; + emc_fbio_cfg5 = dst_timing->shadow_regs_ca_train.emc_fbio_cfg5; + } else if (training & (WRITE_TRAINING | WRITE_VREF_TRAINING | READ_TRAINING | READ_VREF_TRAINING)) { + emc_pmacro_cmd_pad_tx_ctrl = dst_timing->shadow_regs_rdwr_train.emc_pmacro_cmd_pad_tx_ctrl; + emc_pmacro_brick_ctrl_rfu1 = dst_timing->shadow_regs_rdwr_train.emc_pmacro_brick_ctrl_rfu1; + emc_fbio_cfg5 = dst_timing->shadow_regs_rdwr_train.emc_fbio_cfg5; + } else { + emc_pmacro_cmd_pad_tx_ctrl = dst_timing->burst_regs.emc_pmacro_cmd_pad_tx_ctrl; + emc_pmacro_brick_ctrl_rfu1 = dst_timing->burst_regs.emc_pmacro_brick_ctrl_rfu1; + emc_fbio_cfg5 = dst_timing->burst_regs.emc_fbio_cfg5; + } + + bool misc_flag = (misc_cfg_1 & 3) == 3; + uint32_t timescale = 100000 << ((misc_cfg_1 >> 2) & 7); + uint32_t delay = (timescale / dst_clock_period); + + if (dst_clock_period < 869 || misc_flag) { + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, emc_pmacro_brick_ctrl_rfu1 & 0xFE40FE40, delay + 1); + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, emc_pmacro_brick_ctrl_rfu1 & 0xFEEDFEED, delay + 1); + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, emc_pmacro_brick_ctrl_rfu1 & 0xFFFFFFFF, delay + 1); + + CcfifoWrite(EMC_FBIO_CFG5, emc_fbio_cfg5 & 0xFFFFFEFF, delay + 10); + + CcfifoWrite(EMC_PMACRO_CMD_PAD_TX_CTRL, emc_pmacro_cmd_pad_tx_ctrl & 0xFBFFFFFF, 5); + + return timescale + 10 * dst_clock_period + 3 * timescale; + } else if (dst_clock_period > 1665) { + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, emc_pmacro_brick_ctrl_rfu1 | 0x00000600, 0); + + CcfifoWrite(EMC_FBIO_CFG5, emc_fbio_cfg5 & 0xFFFFFEFF, 12); + + CcfifoWrite(EMC_PMACRO_CMD_PAD_TX_CTRL, emc_pmacro_cmd_pad_tx_ctrl & 0xFBFFFFFF, 5); + + return 12 * dst_clock_period; + } else { + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, emc_pmacro_brick_ctrl_rfu1 | 0x06000600, delay + 1); + + CcfifoWrite(EMC_FBIO_CFG5, emc_fbio_cfg5 & 0xFFFFFEFF, delay + 10); + + CcfifoWrite(EMC_PMACRO_CMD_PAD_TX_CTRL, emc_pmacro_cmd_pad_tx_ctrl & 0xFBFFFFFF, 5); + + return timescale + 10 * dst_clock_period; + } + } + + void FreqChange(EmcDvfsTimingTable *src_timing, EmcDvfsTimingTable *dst_timing, u32 training, u32 dst_clk_src) { + /* Extract training values */ + const bool train_ca = (training & CA_TRAINING); + const bool train_ca_vref = (training & CA_VREF_TRAINING); + //const bool train_quse = (training & QUSE_TRAINING); + //const bool train_quse_vref = (training & QUSE_VREF_TRAINING); + const bool train_wr = (training & WRITE_TRAINING); + const bool train_wr_vref = (training & WRITE_VREF_TRAINING); + const bool train_rd = (training & READ_TRAINING); + const bool train_rd_vref = (training & READ_VREF_TRAINING); + const bool train_second_rank = (training & TRAIN_SECOND_RANK); + const bool train_bit_level = (training & BIT_LEVEL_TRAINING); + + /* Check if we should do training. */ + const bool training_enabled = (training & (CA_TRAINING | CA_VREF_TRAINING | WRITE_TRAINING | WRITE_VREF_TRAINING | READ_TRAINING | READ_VREF_TRAINING)); + + uint32_t dst_emc_fbio_cfg7 = dst_timing->emc_fbio_cfg7; + uint32_t dst_misc_cfg_0 = dst_timing->misc_cfg_0; + uint32_t dst_misc_cfg_1 = dst_timing->misc_cfg_1; + uint32_t dst_misc_cfg_2 = dst_timing->misc_cfg_2; + uint32_t src_misc_cfg_0 = src_timing->misc_cfg_0; + uint32_t src_misc_cfg_1 = src_timing->misc_cfg_1; + uint32_t src_t_rp = src_timing->dram_timings.t_rp; + uint32_t src_t_rfc = src_timing->dram_timings.t_rfc; + uint32_t dst_t_pdex = dst_timing->dram_timings.t_pdex; + uint32_t dst_t_fc_lpddr4 = dst_timing->dram_timings.t_fc_lpddr4; + + const bool ch0_enable = reg::GetField(dst_emc_fbio_cfg7, EMC_REG_BITS_MASK(FBIO_CFG7_CH0_ENABLE)) == EMC_FBIO_CFG7_CH0_ENABLE_ENABLE; + const bool ch1_enable = reg::GetField(dst_emc_fbio_cfg7, EMC_REG_BITS_MASK(FBIO_CFG7_CH1_ENABLE)) == EMC_FBIO_CFG7_CH1_ENABLE_ENABLE; + + g_fsp_for_next_freq = !g_fsp_for_next_freq; + + const int dram_type = reg::GetValue(EMC + EMC_FBIO_CFG5, EMC_REG_BITS_MASK(FBIO_CFG5_DRAM_TYPE)); + uint32_t src_emc_zcal_wait_cnt = src_timing->burst_regs.emc_zcal_wait_cnt; + + bool shared_zq_resistor = (src_emc_zcal_wait_cnt >> 31) & 1; + + bool opt_zcal_en_cc = (dst_timing->burst_regs.emc_zcal_interval && !src_timing->burst_regs.emc_zcal_interval) || (dram_type == DRAM_TYPE_LPDDR4); + + uint32_t dst_t_fc_lpddr4_hz = 1000 * dst_t_fc_lpddr4; + + bool is_lpddr2 = (dram_type == DRAM_TYPE_LPDDR2); + bool is_lpddr3 = is_lpddr2 && ((dst_timing->burst_regs.emc_fbio_cfg5 >> 25) & 1); + uint32_t opt_dll_mode = (dram_type == DRAM_TYPE_DDR4) ? GetDllState(dst_timing) : DLL_OFF; + + int dram_dev_num = ((reg::Read(MC + MC_EMEM_ADR_CFG) & 1) + 1); + + uint32_t tZQCAL_lpddr4 = dst_timing->tZQCAL_lpddr4; + uint32_t zqcal_before_cc_cutoff = dst_timing->zqcal_before_cc_cutoff; + uint32_t opt_cc_short_zcal = dst_timing->opt_cc_short_zcal; + uint32_t opt_short_zcal = dst_timing->opt_short_zcal; + uint32_t opt_do_sw_qrst = dst_timing->opt_do_sw_qrst; + uint32_t save_restore_clkstop_pd = dst_timing->save_restore_clkstop_pd; + // uint32_t opt_E90 = dst_timing->opt_E90; + uint32_t cya_allow_ref_cc = dst_timing->cya_allow_ref_cc; + uint32_t ref_b4_sref_en = dst_timing->ref_b4_sref_en; + uint32_t cya_issue_pc_ref = dst_timing->cya_issue_pc_ref; + + uint32_t src_rate_khz = src_timing->rate_khz; + uint32_t dst_rate_khz = dst_timing->rate_khz; + + uint32_t src_clock_period = 1000000000 / src_rate_khz; + uint32_t dst_clock_period = 1000000000 / dst_rate_khz; + + uint32_t emc_auto_cal_config = reg::Read(EMC + EMC_AUTO_CAL_CONFIG); + + uint32_t adj_dst_t_fc_lpddr4 = (dst_clock_period <= zqcal_before_cc_cutoff) ? dst_t_fc_lpddr4_hz : 0; + + uint32_t emc_dbg_o = reg::Read(EMC + EMC_DBG); + uint32_t emc_pin_o = reg::Read(EMC + EMC_PIN); + uint32_t emc_cfg_pipe_clk_o = reg::Read(EMC + EMC_CFG_PIPE_CLK); + uint32_t emc_dbg = emc_dbg_o; + + uint32_t emc_cfg = dst_timing->burst_regs.emc_cfg; + uint32_t emc_sel_dpd_ctrl = dst_timing->emc_sel_dpd_ctrl; + + uint32_t next_push, next_dq_e_ivref, next_dqs_e_ivref; + + /* Step 1: + * Pre DVFS SW sequence. + */ + + /* Step 1.1: Disable DLL. */ + uint32_t tmp = reg::Read(EMC + EMC_CFG_DIG_DLL); + tmp &= ~(1 << 0); + reg::Write(EMC + EMC_CFG_DIG_DLL, tmp); + + /* Calculate 14000 / dst_period. */ + uint32_t div_14000_by_dst_period = std::max<u32>(util::DivideUp(14000, dst_clock_period), 10); + + /* Request a timing update. */ + TimingUpdate(dst_emc_fbio_cfg7); + + /* Wait for DLL to be disabled. */ + WaitForUpdate(EMC_CFG_DIG_DLL, (1 << 0), false, dst_emc_fbio_cfg7); + + /* Step 1.2: Disable AUTOCAL. */ + emc_auto_cal_config = (dst_timing->emc_auto_cal_config & 0x7FFFF9FF) | 0x600; + reg::Write(EMC + EMC_AUTO_CAL_CONFIG, emc_auto_cal_config); + reg::Read(EMC + EMC_AUTO_CAL_CONFIG); + + /* Step 1.3: Disable other power features. */ + emc_dbg = SetShadowBypass(ACTIVE, emc_dbg_o); + reg::Write(EMC + EMC_DBG, emc_dbg); + reg::Write(EMC + EMC_CFG, emc_cfg & 0x0FFFFFFF); + reg::Write(EMC + EMC_SEL_DPD_CTRL, emc_sel_dpd_ctrl & 0xFFFFFEC3); + reg::Write(EMC + EMC_DBG, emc_dbg_o); + + /* Skip this if dvfs_with_training is set. */ + bool compensate_trimmer_applicable = false; + uint32_t adel = 0; + if (!training_enabled && dst_timing->periodic_training) { + /* Wait for DRAM to get out of power down. */ + WaitForUpdate(EMC_EMC_STATUS, dram_dev_num == TWO_RANK ? 0x30 : 0x10, false, dst_emc_fbio_cfg7); + + /* Wait for DRAM to get out of self refresh. */ + WaitForUpdate(EMC_EMC_STATUS, 0x300, false, dst_emc_fbio_cfg7); + + if (dst_timing->periodic_training) { + /* Reset all clock tree values. */ + dst_timing->current_dram_clktree_c0d0u0 = dst_timing->trained_dram_clktree_c0d0u0; + dst_timing->current_dram_clktree_c0d0u1 = dst_timing->trained_dram_clktree_c0d0u1; + dst_timing->current_dram_clktree_c0d1u0 = dst_timing->trained_dram_clktree_c0d1u0; + dst_timing->current_dram_clktree_c0d1u1 = dst_timing->trained_dram_clktree_c0d1u1; + dst_timing->current_dram_clktree_c1d0u0 = dst_timing->trained_dram_clktree_c1d0u0; + dst_timing->current_dram_clktree_c1d0u1 = dst_timing->trained_dram_clktree_c1d0u1; + dst_timing->current_dram_clktree_c1d1u0 = dst_timing->trained_dram_clktree_c1d1u0; + dst_timing->current_dram_clktree_c1d1u1 = dst_timing->trained_dram_clktree_c1d1u1; + + /* Do DVFS_SEQUENCE. */ + adel = PeriodicCompensationHandler(DVFS_SEQUENCE, dram_dev_num, src_timing, dst_timing); + + /* Check if we should use compensate trimmer. */ + compensate_trimmer_applicable = dst_timing->periodic_training && ((adel * 128 * (dst_rate_khz / 1000)) / 1000000) > dst_timing->tree_margin; + } + } + + reg::Write(EMC + EMC_INTSTATUS, (1 << 4)); + + emc_dbg = SetShadowBypass(ACTIVE, emc_dbg); + reg::Write(EMC + EMC_DBG, emc_dbg); + reg::Write(EMC + EMC_CFG, emc_cfg & 0x0FFFFFFF); + reg::Write(EMC + EMC_SEL_DPD_CTRL, emc_sel_dpd_ctrl & 0xFFFFFEC3); + reg::Write(EMC + EMC_CFG_PIPE_CLK, emc_cfg_pipe_clk_o | (1 << 0)); + reg::Write(EMC + EMC_FDPD_CTRL_CMD_NO_RAMP, dst_timing->emc_fdpd_ctrl_cmd_no_ramp & ~(1 << 0)); + + /* Adjust pllm_misc1 as needed. */ + if (dst_timing->pllm_misc1_0_pllm_clamp_ph90) { + reg::ClearBits(CLKRST + CLK_RST_CONTROLLER_PLLM_MISC1, 0x80000000); + } + + /* Check if we need to turn on VREF generator. */ + if (((!(src_timing->burst_regs.emc_pmacro_data_pad_tx_ctrl & + (1 << 0))) && + ((dst_timing->burst_regs.emc_pmacro_data_pad_tx_ctrl & + (1 << 0)))) || + ((!(src_timing->burst_regs.emc_pmacro_data_pad_tx_ctrl & + (1 << 10))) && + ((dst_timing->burst_regs.emc_pmacro_data_pad_tx_ctrl & + (1 << 10))))) + { + uint32_t pad_tx_ctrl = dst_timing->burst_regs.emc_pmacro_data_pad_tx_ctrl; + uint32_t last_pad_tx_ctrl = src_timing->burst_regs.emc_pmacro_data_pad_tx_ctrl; + + next_dqs_e_ivref = pad_tx_ctrl & (1 << 10); + next_dq_e_ivref = pad_tx_ctrl & (1 << 0); + next_push = (last_pad_tx_ctrl & ~(1 << 0) & ~(1 << 10)) | next_dq_e_ivref | next_dqs_e_ivref; + reg::Write(EMC + EMC_PMACRO_DATA_PAD_TX_CTRL, next_push); + + reg::Write(EMC + EMC_DBG, emc_dbg_o); + util::WaitMicroSeconds(1); + } else { + reg::Write(EMC + EMC_DBG, emc_dbg_o); + } + + /* Check if we need to fixup xm2comppadctrl */ + if ((dst_misc_cfg_1 & 0x20) == 0) { + reg::Write(EMC + EMC_DBG, SetShadowBypass(ACTIVE, emc_dbg_o)); + + uint32_t xm2comppadctrl = reg::Read(EMC + EMC_XM2COMPPADCTRL); + + reg::Write(EMC + EMC_XM2COMPPADCTRL, xm2comppadctrl | 0x08000000); + util::WaitMicroSeconds(1); + reg::Write(EMC + EMC_XM2COMPPADCTRL, xm2comppadctrl | 0x18000000); + util::WaitMicroSeconds(1); + reg::Write(EMC + EMC_XM2COMPPADCTRL, xm2comppadctrl | 0x38000000); + util::WaitMicroSeconds(1); + + reg::Write(EMC + EMC_DBG, SetShadowBypass(ASSEMBLY, emc_dbg_o)); + } + + /* Step 2: + * Prelock the DLL. + */ + + if (dst_timing->burst_regs.emc_cfg_dig_dll & (1 << 0)) { + reg::Write(EMC + EMC_DBG, SetShadowBypass(ACTIVE, emc_dbg_o)); + + reg::ClearBits(EMC + EMC_PMACRO_DLL_CFG_1, 0x2000); + reg::Read(EMC + EMC_PMACRO_DLL_CFG_1); + + reg::Write(EMC + EMC_DBG, SetShadowBypass(ASSEMBLY, emc_dbg_o)); + reg::Read(EMC + EMC_DBG); + + DllPrelock(dst_timing, src_timing, training_enabled, dst_clk_src); + } else { + DllDisable(dst_emc_fbio_cfg7); + } + + /* Step 3: + * Prepare autocal for the clock change. + */ + + /* Disable AUTOCAL. */ + emc_auto_cal_config = (dst_timing->emc_auto_cal_config & 0x7FFFF9FF) | 0x600; + reg::Write(EMC + EMC_AUTO_CAL_CONFIG, emc_auto_cal_config); + + reg::Write(EMC + EMC_DBG, SetShadowBypass(ACTIVE, emc_dbg_o)); + + reg::Write(EMC + EMC_AUTO_CAL_CONFIG2, dst_timing->emc_auto_cal_config2); + + if (ch0_enable || ch1_enable) { + reg::Write(EMC0 + EMC_AUTO_CAL_CONFIG3, dst_timing->emc_auto_cal_config3); + reg::Write(EMC0 + EMC_AUTO_CAL_CONFIG4, dst_timing->emc_auto_cal_config4); + reg::Write(EMC0 + EMC_AUTO_CAL_CONFIG5, dst_timing->emc_auto_cal_config5); + reg::Write(EMC0 + EMC_AUTO_CAL_CONFIG6, dst_timing->emc_auto_cal_config6); + reg::Write(EMC0 + EMC_AUTO_CAL_CONFIG7, dst_timing->emc_auto_cal_config7); + reg::Write(EMC0 + EMC_AUTO_CAL_CONFIG8, dst_timing->emc_auto_cal_config8); + } + + reg::Write(EMC + EMC_DBG, emc_dbg_o); + + emc_auto_cal_config = (dst_timing->emc_auto_cal_config & 0x7FFFF9FE) | 0x601; + reg::Write(EMC + EMC_AUTO_CAL_CONFIG, emc_auto_cal_config); + + /* Step 4: + * Update EMC_CFG. + */ + + reg::ClearBits(EMC + EMC_CFG, 0x10000000); + reg::Write(EMC + EMC_CFG_2, dst_timing->emc_cfg_2); + + /* Step 5: + * Prepare reference variables for ZQCAL regs. + */ + //print(SCREEN_LOG_LEVEL_DEBUG, "[MTC]: freq_change - step 5\n"); + //mdelay(500); + + uint32_t emc_zcal_interval = src_timing->burst_regs.emc_zcal_interval & 0xFF000000; + uint32_t dst_emc_zcal_wait_cnt = dst_timing->burst_regs.emc_zcal_wait_cnt; + + uint32_t zq_wait_long, zq_wait_short; + + if (dram_type == DRAM_TYPE_LPDDR4) { + zq_wait_long = std::max<u32>(util::DivideUp(1000000, dst_clock_period), 1); + zq_wait_short = std::max<u32>(util::DivideUp(30000, dst_clock_period), 8) + 1; + } else if (is_lpddr2 || is_lpddr3) { + zq_wait_long = std::max<u32>(util::DivideUp(360000, dst_clock_period), dst_timing->min_mrs_wait) + 4; + zq_wait_short = 0; + } else if (dram_type == DRAM_TYPE_DDR4) { + zq_wait_long = std::max<u32>(util::DivideUp(320000, dst_clock_period), 256); + zq_wait_short = 0; + } else { + zq_wait_long = 0; + zq_wait_short = 0; + } + + /* Step 6: + * Training code. + */ + + { + uint32_t pintemp = reg::Read(EMC + EMC_PIN); + if ((train_ca || train_ca_vref) && (dram_dev_num == TWO_RANK)) { + reg::Write(EMC + EMC_PIN, pintemp | 0x7); + } + } + + /* Step 7: + * Program FSP reference registers and send MRWs to new FSPWR. + */ + + uint32_t mr13_flip_fspop, mr13_flip_fspwr; + if (!g_fsp_for_next_freq) { + mr13_flip_fspwr = (dst_timing->emc_mrw3 & 0xffffff3f) | 0x80; + mr13_flip_fspop = (dst_timing->emc_mrw3 & 0xffffff3f) | 0x00; + } else { + mr13_flip_fspwr = (dst_timing->emc_mrw3 & 0xffffff3f) | 0x40; + mr13_flip_fspop = (dst_timing->emc_mrw3 & 0xffffff3f) | 0xc0; + } + + uint32_t mr13_catr_enable = mr13_flip_fspwr | 1; + + if (dram_dev_num == TWO_RANK) { + if (train_ca || train_ca_vref) { + mr13_flip_fspop = (mr13_flip_fspop & 0x3FFFFFFF) | (train_second_rank ? 0x80000000 : 0x40000000); + } + + mr13_catr_enable = (mr13_catr_enable & 0x3FFFFFFF) | (train_second_rank ? 0x40000000 : 0x80000000); + } + + if (dram_type == DRAM_TYPE_LPDDR4) { + reg::Write(EMC + EMC_MRW3, mr13_flip_fspwr); + reg::Write(EMC + EMC_MRW, dst_timing->emc_mrw); + reg::Write(EMC + EMC_MRW2, dst_timing->emc_mrw2); + } + + /* Step 8: + * Program the shadow registers. + */ + + /* Set burst registers. */ + { + uint32_t pmacro_vttgen_ctrl_1 = reg::Read(EMC + EMC_PMACRO_VTTGEN_CTRL_1); + uint32_t xm2comppadctrl = reg::Read(EMC + EMC_XM2COMPPADCTRL); + for (u32 i = 0; i < dst_timing->num_burst; ++i) { + if (!BurstRegisters[i]) { + continue; + } + + const u32 reg_addr = BurstRegisters[i]; + + uint32_t wval; + if (train_ca || train_ca_vref) { + wval = dst_timing->shadow_regs_ca_train_arr[i]; + } else if (train_wr || train_wr_vref || train_rd || train_rd_vref) { + wval = dst_timing->shadow_regs_rdwr_train_arr[i]; + } else { + wval = dst_timing->burst_regs_arr[i]; + } + + /* Adjust the value to write. */ + switch (reg_addr) { + case EMC_BASE + EMC_CFG: + wval &= (dram_type == DRAM_TYPE_LPDDR4) ? 0x0FFFFFFF : 0xCFFFFFFF; + break; + case EMC_BASE + EMC_MRS_WAIT_CNT: + if (opt_zcal_en_cc && is_lpddr2 && (opt_cc_short_zcal == 0) && (opt_short_zcal != 0)) { + wval = (wval & 0xFFFFFC00) | (zq_wait_long & 0x3FF); + } + break; + case EMC_BASE + EMC_ZCAL_WAIT_CNT: + if ((opt_short_zcal != 0) && opt_zcal_en_cc && (opt_cc_short_zcal == 0) && (dram_type == DRAM_TYPE_DDR4)) { + wval = (wval & 0xFFFFF800) | (zq_wait_long & 0x7FF); + } + break; + case EMC_BASE + EMC_ZCAL_INTERVAL: + if (opt_zcal_en_cc) { + wval = 0; + } + break; + case EMC_BASE + EMC_PMACRO_BRICK_CTRL_RFU1: + wval &= 0xF800F800; + break; + case EMC_BASE + EMC_PMACRO_CMD_PAD_TX_CTRL: + wval |= 0x04000000; + break; + case EMC_BASE + EMC_PMACRO_AUTOCAL_CFG_COMMON: + wval |= 0x00010000; + break; + case EMC_BASE + EMC_TRAINING_CTRL: + if (train_second_rank) { + wval |= 0x4000; + } + break; + case EMC_BASE + EMC_REFRESH: + case EMC_BASE + EMC_TREFBW: + wval >>= 0; + break; + case EMC_BASE + EMC_XM2COMPPADCTRL: + if ((dst_misc_cfg_1 & 0x20) == 0) { + wval = (wval & 0x00FFFFFF) | (xm2comppadctrl & 0xFF000000); + } + break; + case EMC_BASE + EMC_DLL_CFG_1: + wval = (wval & 0xFFFFDFFF) | (reg::Read(EMC + EMC_PMACRO_DLL_CFG_1) & 0x00002000); + break; + case EMC_BASE + EMC_PMACRO_VTTGEN_CTRL_1: + wval = (wval & 0xFFFF03FF) | (pmacro_vttgen_ctrl_1 & 0xFC00); + break; + case EMC_BASE + EMC_MRW6: + case EMC_BASE + EMC_MRW7: + case EMC_BASE + EMC_MRW8: + case EMC_BASE + EMC_MRW9: + case EMC_BASE + EMC_MRW14: + case EMC_BASE + EMC_MRW15: + case EMC0_BASE + EMC_MRW10: + case EMC0_BASE + EMC_MRW11: + case EMC0_BASE + EMC_MRW12: + case EMC0_BASE + EMC_MRW13: + case EMC1_BASE + EMC_MRW10: + case EMC1_BASE + EMC_MRW11: + case EMC1_BASE + EMC_MRW12: + case EMC1_BASE + EMC_MRW13: + if (dram_type != DRAM_TYPE_LPDDR4) { + continue; + } + break; + } + + /* Write the value. */ + reg::Write(reg_addr, wval); + } + } + + if (dram_type == DRAM_TYPE_LPDDR4) { + /* Use the current timing when training. */ + uint32_t mrw_req; + if (training_enabled) + mrw_req = (23 << 16) | (src_timing->run_clocks & (0xFF << 0)); + else + mrw_req = (23 << 16) | (dst_timing->run_clocks & (0xFF << 0)); + + reg::Write(EMC + EMC_MRW, mrw_req); + } + + /* Per channel burst registers. */ + if (dram_type == DRAM_TYPE_LPDDR4) { + for (u32 i = 0; i < dst_timing->num_burst_per_ch; i++) { + if (!PerChannelBurstRegisters[i]) { + continue; + } + + const u32 addr = PerChannelBurstRegisters[i]; + const u32 base = addr & ~0xFFF; + + /* Filter out channels. */ + if ((!ch0_enable && base == EMC0) || (!ch1_enable && base == EMC1)) { + continue; + } + + /* Write the value. */ + reg::Write(addr, dst_timing->burst_perch_regs_arr[i]); + } + } + + /* Vref regs. */ + for (u32 i = 0; i < dst_timing->vref_num; i++) { + if (!PerChannelVrefRegisters[i]) { + continue; + } + + const u32 addr = PerChannelVrefRegisters[i]; + const u32 base = addr & ~0xFFF; + + /* Filter out channels. */ + if ((!ch0_enable && base == EMC0) || (!ch1_enable && base == EMC1)) { + continue; + } + + /* Write the value. */ + reg::Write(addr, dst_timing->vref_perch_regs_arr[i]); + } + + /* Training regs. */ + if (training_enabled) { + for (u32 i = 0; i < dst_timing->training_mod_num; i++) { + if (!PerChannelTrainingModRegisters[i]) { + continue; + } + + const u32 addr = PerChannelTrainingModRegisters[i]; + const u32 base = addr & ~0xFFF; + + /* Filter out channels. */ + if ((!ch0_enable && base == EMC0) || (!ch1_enable && base == EMC1)) { + continue; + } + + /* Write the value. */ + reg::Write(addr, dst_timing->training_mod_regs_arr[i]); + } + } + + /* Per channel trimmers. */ + for (u32 i = 0; i < dst_timing->num_trim_per_ch; i++) { + if (!PerChannelTrimRegisters[i]) { + continue; + } + + const u32 addr = PerChannelTrimRegisters[i]; + const u32 base = addr & ~0xFFF; + + /* Filter out channels. */ + if ((!ch0_enable && base == EMC0) || (!ch1_enable && base == EMC1)) { + continue; + } + + uint32_t wval = dst_timing->trim_perch_regs_arr[i]; + + if (compensate_trimmer_applicable) { + switch (addr) { + case EMC0_BASE + EMC_DATA_BRLSHFT_0: + case EMC1_BASE + EMC_DATA_BRLSHFT_0: + case EMC0_BASE + EMC_DATA_BRLSHFT_1: + case EMC1_BASE + EMC_DATA_BRLSHFT_1: + wval = ApplyPeriodicCompensationTrimmer(dst_timing, addr); + break; + } + } + + /* Write the value. */ + reg::Write(addr, wval); + } + + /* Trimmers. */ + for (u32 i = 0; i < dst_timing->num_trim; ++i) { + if (!TrimRegisters[i]) { + continue; + } + + const u32 addr = TrimRegisters[i]; + + u32 wval = dst_timing->trim_regs_arr[i]; + + if (compensate_trimmer_applicable) { + switch (addr) { + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_2: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_0: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_1: + case EMC_BASE + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_2: + wval = ApplyPeriodicCompensationTrimmer(dst_timing, addr); + break; + } + } + + /* Write the value. */ + reg::Write(addr, wval); + } + + if (training_enabled) { + if (train_wr && dst_timing->periodic_training) { + PeriodicCompensationHandler(WRITE_TRAINING_SEQUENCE, dram_dev_num, src_timing, dst_timing); + } + } else { + /* Write burst_mc_regs. */ + for (u32 i = 0; i < dst_timing->num_mc_regs; i++) { + reg::Write(BurstMcRegisters[i], dst_timing->burst_mc_regs_arr[i]); + } + + /* Registers to be programmed on the faster clock. */ + if (dst_timing->rate_khz < src_timing->rate_khz) { + for (u32 i = 0; i < dst_timing->num_up_down; i++) { + reg::Write(LaScaleRegisters[i], dst_timing->la_scale_regs_arr[i]); + } + } + } + + if ((dst_misc_cfg_1 & 2) != 0 && (dst_misc_cfg_1 & 1) == 0) { + reg::Write(EMC + EMC_PMACRO_BRICK_CTRL_RFU1, dst_timing->burst_regs.emc_pmacro_brick_ctrl_rfu1); + reg::Write(EMC + EMC_PMACRO_CMD_PAD_TX_CTRL, dst_timing->burst_regs.emc_pmacro_cmd_pad_tx_ctrl); + } + + reg::Write(EMC + EMC_CFG_PIPE_CLK, (1 << 0)); + reg::Write(EMC + EMC_FDPD_CTRL_CMD_NO_RAMP, dst_timing->emc_fdpd_ctrl_cmd_no_ramp & ~(1 << 0)); + + + /* Step 9: + * LPDDR4 section A. + */ + + uint32_t emc_dbg_write_active = emc_dbg_o; + if (dram_type == DRAM_TYPE_LPDDR4) { + reg::Write(EMC + EMC_ZCAL_INTERVAL, emc_zcal_interval); + reg::Write(EMC + EMC_ZCAL_WAIT_CNT, (dst_emc_zcal_wait_cnt & 0xFFFFF800) | 1); + reg::Write(EMC + EMC_DBG, emc_dbg_o | ((1 << 1) | (1 << 30))); + reg::Write(EMC + EMC_ZCAL_INTERVAL, emc_zcal_interval); + reg::Write(EMC + EMC_DBG, emc_dbg_o); + + if (training_enabled) { + reg::Write(EMC + EMC_DBG, SetShadowBypass(ACTIVE, emc_dbg_o)); + reg::Write(EMC + EMC_PMACRO_AUTOCAL_CFG_COMMON, dst_timing->burst_regs.emc_pmacro_autocal_cfg_common | (1 << 16)); + + if (train_ca || train_ca_vref) { + reg::Write(EMC + EMC_FBIO_CFG5, src_timing->burst_regs.emc_fbio_cfg5 | (1 << 27)); + } + + reg::Write(EMC + EMC_DBG, emc_dbg_o); + + if (dst_emc_fbio_cfg7 == 0x6) { + CcfifoWrite(EMC_CFG_SYNC, 1, 0); + } + + /* Change CFG_SWAP. */ + CcfifoWrite(EMC_DBG, ((emc_dbg_o & 0xF3FFFFFF) | 0x4000000), 0); + } + + CcfifoWrite(EMC_SELF_REF, 0x101, 0); + + if (!(train_ca || train_ca_vref) && dst_clock_period <= zqcal_before_cc_cutoff) { + CcfifoWrite(EMC_MRW3, mr13_flip_fspwr ^ 0x40, 0); + CcfifoWrite(EMC_MRW6, (src_timing->burst_regs.emc_mrw6 & 0x0000C0C0) | (dst_timing->burst_regs.emc_mrw6 & 0xFFFF3F3F), 0); + CcfifoWrite(EMC_MRW14, (src_timing->burst_regs.emc_mrw14 & 0x00003838) | (dst_timing->burst_regs.emc_mrw14 & 0xFFFF0707), 0); + + if (dram_dev_num == TWO_RANK) { + CcfifoWrite(EMC_MRW7, (src_timing->burst_regs.emc_mrw7 & 0x0000C0C0) | (dst_timing->burst_regs.emc_mrw7 & 0xFFFF3F3F), 0); + CcfifoWrite(EMC_MRW15, (src_timing->burst_regs.emc_mrw15 & 0x00003838) | (dst_timing->burst_regs.emc_mrw15 & 0xFFFF0707), 0); + } + + if (opt_zcal_en_cc) { + if (dram_dev_num == ONE_RANK || shared_zq_resistor) { + CcfifoWrite(EMC_ZQ_CAL, (2 << 30) | (1 << 0), 0); + } else { + CcfifoWrite(EMC_ZQ_CAL, (1 << 0), 0); + } + } + } + + if (training_enabled) { + emc_dbg_write_active = (emc_dbg_o & 0xF7FFFFFF) | 0x4000000 | (1 << 30); + CcfifoWrite(EMC_DBG, emc_dbg_write_active, 0); + } + + if (train_ca || train_ca_vref) { + CcfifoWrite(EMC_PMACRO_DATA_RX_TERM_MODE, src_timing->burst_regs.emc_pmacro_data_rx_term_mode & 0xFFFFFCCC, 0); + + if (dram_dev_num == TWO_RANK && train_second_rank) { + CcfifoWrite(EMC_MRW3, mr13_flip_fspop | 0x8, (1000 * src_t_rp) / src_clock_period); + CcfifoWrite(EMC_MRW3, mr13_catr_enable | 0x8, 0); + } else { + CcfifoWrite(EMC_MRW3, mr13_catr_enable | 0x8, (1000 * src_t_rp) / src_clock_period); + } + + CcfifoWrite(EMC_TR_CTRL_0, (dst_timing->emc_tr_ctrl_0 & 0x3F1000) | 0x100012A, 0); + + CcfifoWrite(EMC_INTSTATUS, 0, 1000000 / src_clock_period); + } else { + CcfifoWrite(EMC_MRW3, mr13_flip_fspop | 0x8, (1000 * src_t_rp) / src_clock_period); + + CcfifoWrite(EMC_INTSTATUS, 0, std::max<u32>(DivideUpFloat(dst_t_fc_lpddr4_hz, src_clock_period), std::max<u32>(DivideUpFloat(14000, src_clock_period), 10))); + } + + uint32_t t = 30 + (cya_allow_ref_cc ? ((4000 * src_t_rfc + 1000 * src_t_rp) / src_clock_period) : 0); + CcfifoWrite(EMC_PIN, emc_pin_o & 0xFFFFFFF8, t); + } else { + CcfifoWrite(EMC_SELF_REF, 0x1, 0); + } + + uint32_t ref_delay_mult = 1; + if (ref_b4_sref_en) ++ref_delay_mult; + if (cya_allow_ref_cc) ++ref_delay_mult; + if (cya_issue_pc_ref) ++ref_delay_mult; + + uint32_t ref_delay = 20 + ref_delay_mult * (((1000 * src_t_rfc) / src_clock_period) + ((1000 * src_t_rp) / src_clock_period)); + + /* Step 11: + * Ramp down. + */ + + CcfifoWrite(EMC_CFG_SYNC, 1, (dram_type == DRAM_TYPE_LPDDR4) ? 0 : ref_delay); + + bool do_ramp_up = (dst_misc_cfg_1 & 2) == 0 || (dst_misc_cfg_1 & 1) != 0; + if (!do_ramp_up) { + CcfifoWrite(EMC_FBIO_CFG5, reg::Read(EMC + EMC_FBIO_CFG5) & 0xF7FFFFFF, 12); + } + + CcfifoWrite(EMC_DBG, SetShadowBypass(ACTIVE, emc_dbg_write_active & 0xBFFFFFFF), 0); + + if ((dst_misc_cfg_2 & 0x10) == 0) { + DvfsPowerRampDown(src_timing, dst_timing, false, 0); + } + + CcfifoWrite(EMC_DBG, SetShadowBypass(ACTIVE, emc_dbg_write_active | 0x40000000), 0); + + uint32_t ramp_down_wait = 0; + if (do_ramp_up) { + CcfifoWrite(EMC_PMACRO_CMD_PAD_TX_CTRL, src_timing->burst_regs.emc_pmacro_cmd_pad_tx_ctrl | (1 << 26), 0); + CcfifoWrite(EMC_FBIO_CFG5, src_timing->burst_regs.emc_fbio_cfg5 | 0x100, 12); + + bool misc_flag = (dst_misc_cfg_1 & 3) == 3; + + uint32_t timescale = (100000 << ((dst_misc_cfg_1 >> 2) & 7)); + uint32_t delay = (timescale / src_clock_period); + + if (src_rate_khz > 1150747 || misc_flag) { + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, src_timing->burst_regs.emc_pmacro_brick_ctrl_rfu1 & 0xFEEDFEED, delay + 1); + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, src_timing->burst_regs.emc_pmacro_brick_ctrl_rfu1 & 0xFE40FE40, delay + 1); + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, src_timing->burst_regs.emc_pmacro_brick_ctrl_rfu1 & 0xF800F800, delay + 1); + + + ramp_down_wait = 3 * timescale + 12 * src_clock_period; + } else { + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, src_timing->burst_regs.emc_pmacro_brick_ctrl_rfu1 & 0xF800F800, delay + 20); + + ramp_down_wait = timescale + 32 * src_clock_period; + } + + if (src_rate_khz > 600240 || misc_flag) { + CcfifoWrite(EMC_INTSTATUS, 0, delay + 1); + + ramp_down_wait += 2 * timescale; + } + } + + /* Step 12: + * Trigger the clock change. + */ + + CcfifoWrite(EMC_STALL_THEN_EXE_AFTER_CLKCHANGE, 1, 0); + CcfifoWrite(EMC_INTSTATUS, 0, dst_timing->clkchange_delay); + + CcfifoWrite(EMC_DBG, SetShadowBypass(ACTIVE, emc_dbg_write_active & 0xBFFFFFFF), 0); + + if ((dst_misc_cfg_2 & 0x10) == 0) { + DvfsPowerRampDown(src_timing, dst_timing, false, 1); + } + + if (training_enabled) { + CcfifoWrite(EMC_DBG, SetShadowBypass(ACTIVE, emc_dbg_write_active | 0x40000000), 0); + } + + /* Step 13: + * Ramp up. + */ + + uint32_t ramp_up_wait = 0; + if (do_ramp_up) { + ramp_up_wait = DvfsPowerRampUp(dst_clock_period, false, src_timing, dst_timing, training); + } + + if (ramp_up_wait < 1001000 && src_timing->ramp_wait != dst_timing->ramp_wait) { + CcfifoWrite(EMC_INTSTATUS, 0, 1 + ((1001000 - ramp_up_wait) / dst_clock_period)); + } + + if (do_ramp_up) { + CcfifoWrite(EMC_DBG, emc_dbg_write_active, 0); + } else { + CcfifoWrite(EMC_FBIO_CFG5, reg::Read(EMC + EMC_FBIO_CFG5), 0); + CcfifoWrite(EMC_DBG, emc_dbg_write_active, 12); + } + + /* Step 14: + * Bringup CKE pins. + */ + + if (dram_type == DRAM_TYPE_LPDDR4) { + uint32_t pin_val; + if (train_ca || train_ca_vref) { + pin_val = (dst_misc_cfg_0 & 1) ? ((emc_pin_o & 0xFFFCFFF8) | (((dst_misc_cfg_0 >> 1) & 3) << 16)) : emc_pin_o; + pin_val &= 0xFFFFFFF8; + if (dram_dev_num == TWO_RANK) { + if (train_second_rank) { + pin_val |= 5; + } else { + pin_val |= 6; + } + } + } else { + pin_val = (dst_misc_cfg_0 & 1) ? ((emc_pin_o & 0xFFFCFFFF) | (((dst_misc_cfg_0 >> 1) & 3) << 16)) : emc_pin_o; + pin_val &= 0xFFFFFFF8; + if (dram_dev_num == TWO_RANK) { + pin_val |= 7; + } else { + pin_val |= 1; + } + } + + CcfifoWrite(EMC_PIN, pin_val, 0); + } + + /* Step 15: + * Calculate zqlatch wait time; has dependency on ramping times. + */ + + uint32_t dst_t_pdex_hz = 1000 * dst_t_pdex; + + uint32_t zq_latch_dvfs_wait_time; + if (dst_clock_period <= zqcal_before_cc_cutoff) { + zq_latch_dvfs_wait_time = (ramp_up_wait + ramp_down_wait) / dst_clock_period; + } else { + zq_latch_dvfs_wait_time = util::DivideUp(dst_t_pdex_hz, dst_clock_period); + } + + if (!(train_ca || train_ca_vref) && (dram_type == DRAM_TYPE_LPDDR4) && opt_zcal_en_cc) { + int offset = (int)((tZQCAL_lpddr4 - adj_dst_t_fc_lpddr4) / dst_clock_period) - (int)zq_latch_dvfs_wait_time; + int addl_wait = (int)util::DivideUp(dst_t_pdex_hz, dst_clock_period); + + if (dram_dev_num == TWO_RANK) { + if (shared_zq_resistor) { + if (dst_clock_period > zqcal_before_cc_cutoff) { + CcfifoWrite(EMC_ZQ_CAL, (2 << 30) | (1 << 0), std::max<int>(0, addl_wait)); + } + + CcfifoWrite(EMC_ZQ_CAL, (2 << 30) | (1 << 1), std::max<int>(0, offset + addl_wait)); + CcfifoWrite(EMC_ZQ_CAL, (1 << 30) | (1 << 0), 0); + + if (!training_enabled) { + CcfifoWrite(EMC_MRW3, (mr13_flip_fspop & 0xFFFFFFF7) | 0xC000000 | (dst_misc_cfg_2 & 8), 0); + CcfifoWrite(EMC_SELF_REF, 0, 0); + CcfifoWrite(EMC_REF, 0, 0); + } + + CcfifoWrite(EMC_ZQ_CAL, (1 << 30) | (1 << 1), zq_wait_short + div_14000_by_dst_period + (tZQCAL_lpddr4 / dst_clock_period)); + } else { + if (dst_clock_period > zqcal_before_cc_cutoff) { + CcfifoWrite(EMC_ZQ_CAL, (0 << 30) | (1 << 0), std::max<int>(0, addl_wait)); + } + + if (!training_enabled) { + CcfifoWrite(EMC_MRW3, (mr13_flip_fspop & 0xFFFFFFF7) | 0xC000000 | (dst_misc_cfg_2 & 8), std::max<int>(0, addl_wait)); + CcfifoWrite(EMC_SELF_REF, 0, 0); + CcfifoWrite(EMC_REF, 0, 0); + } + + CcfifoWrite(EMC_ZQ_CAL, (0 << 30) | (1 << 1), div_14000_by_dst_period + std::max<int>(0, offset)); + } + } else { + if (dst_clock_period > zqcal_before_cc_cutoff) { + CcfifoWrite(EMC_ZQ_CAL, (2 << 30) | (1 << 0), std::max<int>(0, addl_wait)); + } + + if (!training_enabled) { + CcfifoWrite(EMC_MRW3, (mr13_flip_fspop & 0xFFFFFFF7) | 0xC000000 | (dst_misc_cfg_2 & 8), std::max<int>(0, addl_wait)); + CcfifoWrite(EMC_SELF_REF, 0, 0); + CcfifoWrite(EMC_REF, 0x80000000, 0); + } + + CcfifoWrite(EMC_ZQ_CAL, (2 << 30) | (1 << 1), div_14000_by_dst_period + std::max<int>(0, offset)); + } + } + + /* WAR: delay for zqlatch */ + CcfifoWrite(EMC_INTSTATUS, 0, 10); + + /* Step 16: + * LPDDR4 Conditional Training Kickoff. + */ + + if (training_enabled && dram_type == DRAM_TYPE_LPDDR4) { + if (opt_do_sw_qrst) { + CcfifoWrite(EMC_ISSUE_QRST, 1, 0); + CcfifoWrite(EMC_ISSUE_QRST, 0, 2); + } + + CcfifoWrite(EMC_INTSTATUS, 0, (1020000 / dst_clock_period)); + + { + uint32_t train_cmd = 0; + if (train_ca) { + train_cmd |= (1 << 1); /* CA */ + } + if (train_ca_vref) { + train_cmd |= (1 << 5); /* CA_VREF */ + } + if (train_wr) { + train_cmd |= (1 << 3); /* WR */ + } + if (train_wr_vref) { + train_cmd |= (1 << 6); /* WR_VREF */ + } + if (train_rd) { + train_cmd |= (1 << 2); /* RD */ + } + if (train_rd_vref) { + train_cmd |= (1 << 7); /* RD_VREF */ + } + + train_cmd |= (1 << 31); /* GO */ + + CcfifoWrite(EMC_TRAINING_CMD, train_cmd, 0); + } + + CcfifoWrite(EMC_SWITCH_BACK_CTRL, 1, 0); + + if (!(train_ca || train_ca_vref) || train_second_rank) { + CcfifoWrite(EMC_MRW3, mr13_flip_fspop ^ 0xC0, 0); + CcfifoWrite(EMC_INTSTATUS, 0, (1000000 / dst_clock_period)); + } + + { + uint32_t pin_val = (dst_misc_cfg_0 & 1) ? ((emc_pin_o & 0xFFFCFFFF) | (((dst_misc_cfg_0 >> 1) & 3) << 16)) : emc_pin_o; + CcfifoWrite(EMC_PIN, pin_val & 0xFFFFFFF8, 0); + } + + CcfifoWrite(EMC_CFG_SYNC, 1, 0); + + if ((src_misc_cfg_1 & 3) != 2) { + CcfifoWrite(EMC_DBG, SetShadowBypass(ACTIVE, emc_dbg_write_active | 0x40000000), 0); + + CcfifoWrite(EMC_PMACRO_CMD_PAD_TX_CTRL, dst_timing->burst_regs.emc_pmacro_cmd_pad_tx_ctrl | (1 << 26), 0); + CcfifoWrite(EMC_FBIO_CFG5, dst_timing->burst_regs.emc_fbio_cfg5 | 0x100, 12); + + bool misc_flag = (src_misc_cfg_1 & 3) == 3; + + uint32_t timescale = (100000 << ((src_misc_cfg_1 >> 2) & 7)); + uint32_t delay = (timescale / dst_clock_period); + + if (dst_rate_khz > 1150747 || misc_flag) { + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, dst_timing->burst_regs.emc_pmacro_brick_ctrl_rfu1 & 0xFEEDFEED, delay + 1); + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, dst_timing->burst_regs.emc_pmacro_brick_ctrl_rfu1 & 0xFE40FE40, delay + 1); + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, dst_timing->burst_regs.emc_pmacro_brick_ctrl_rfu1 & 0xF800F800, delay + 1); + } else { + CcfifoWrite(EMC_PMACRO_BRICK_CTRL_RFU1, dst_timing->burst_regs.emc_pmacro_brick_ctrl_rfu1 & 0xF800F800, delay + 20); + } + + if (dst_rate_khz > 600240 || misc_flag) { + CcfifoWrite(EMC_INTSTATUS, 0, delay + 1); + } + } else { + CcfifoWrite(EMC_FBIO_CFG5, reg::Read(EMC + EMC_FBIO_CFG5) & 0xF7FFFFFF, 12); + CcfifoWrite(EMC_DBG, SetShadowBypass(ACTIVE, emc_dbg_write_active | 0x40000000), 0); + } + + CcfifoWrite(EMC_STALL_THEN_EXE_AFTER_CLKCHANGE, 1, 0); + CcfifoWrite(EMC_DBG, SetShadowBypass(ACTIVE, emc_dbg_write_active & 0xBFFFFFFF), 0); + + if ((dst_misc_cfg_2 & 0x10) == 0) { + DvfsPowerRampDown(src_timing, dst_timing, true, 1); + } + + if ((src_misc_cfg_1 & 3) != 2) { + DvfsPowerRampUp(src_clock_period, true, src_timing, dst_timing, training); + + if (ramp_up_wait < 1001000 && src_timing->ramp_wait != dst_timing->ramp_wait) { + CcfifoWrite(EMC_INTSTATUS, 0, 1 + ((1001000 - ramp_up_wait) / dst_clock_period)); + } + + CcfifoWrite(EMC_DBG, emc_dbg_write_active, 0); + } else { + if (ramp_up_wait < 1001000 && src_timing->ramp_wait != dst_timing->ramp_wait) { + CcfifoWrite(EMC_INTSTATUS, 0, 1 + ((1001000 - ramp_up_wait) / dst_clock_period)); + } + + CcfifoWrite(EMC_FBIO_CFG5, reg::Read(EMC + EMC_FBIO_CFG5), 0); + CcfifoWrite(EMC_DBG, emc_dbg_write_active, 12); + } + + { + uint32_t pin_val = (src_misc_cfg_0 & 1) ? ((emc_pin_o & 0xFFFCFFFF) | (((src_misc_cfg_0 >> 1) & 3) << 16)) : emc_pin_o; + pin_val &= 0xFFFFFFF8; + pin_val |= (dram_dev_num == TWO_RANK) ? 7 : 1; + CcfifoWrite(EMC_PIN, pin_val, 0); + } + + if (train_ca || train_ca_vref) { + CcfifoWrite(EMC_TR_CTRL_0, 0x2A, (200000 / src_clock_period)); + CcfifoWrite(EMC_TR_CTRL_0, 0x20, (1000000 / src_clock_period)); + CcfifoWrite(EMC_MRW3, mr13_catr_enable & 0xFFFFFFFE, 0); + CcfifoWrite(EMC_INTSTATUS, 0, (1000000 / src_clock_period)); + CcfifoWrite(EMC_PMACRO_DATA_RX_TERM_MODE, src_timing->burst_regs.emc_pmacro_data_rx_term_mode, 0); + } + + CcfifoWrite(EMC_DBG, emc_dbg_o, 0); + + if (opt_zcal_en_cc) { + if (shared_zq_resistor) { + CcfifoWrite(EMC_ZQ_CAL, (2 << 30) | (1 << 0), 0); + CcfifoWrite(EMC_ZQ_CAL, (2 << 30) | (1 << 1), 1 + util::DivideUp(1000000, src_clock_period)); + + if ((!(train_ca || train_ca_vref) || train_second_rank) && dram_dev_num == TWO_RANK) { + CcfifoWrite(EMC_ZQ_CAL, (1 << 30) | (1 << 0), 0); + CcfifoWrite(EMC_ZQ_CAL, (1 << 30) | (1 << 1), 1 + util::DivideUp(1000000, src_clock_period)); + } + } else { + CcfifoWrite(EMC_ZQ_CAL, (dram_dev_num == ONE_RANK ? (2 << 30) : (0 << 30)) | (1 << 0), 0); + CcfifoWrite(EMC_ZQ_CAL, (dram_dev_num == ONE_RANK ? (2 << 30) : (0 << 30)) | (1 << 1), 1 + util::DivideUp(1000000, src_clock_period)); + } + } + + if (!(train_ca || train_ca_vref)) { + CcfifoWrite(EMC_MRW3, ((mr13_flip_fspop & 0xF3FFFFF7) | (dst_misc_cfg_2 & 8)) ^ 0x0C0000C0, 0); + } + + CcfifoWrite(EMC_SELF_REF, 0x0, 0); + } + + /* Step 17: + * exit self refresh. + */ + + if (dram_type != DRAM_TYPE_LPDDR4) { + CcfifoWrite(EMC_SELF_REF, 0, 0); + } + + /* Step 18: + * Send MRWs to LPDDR3/DDR3. + */ + + if (is_lpddr2) { + CcfifoWrite(EMC_MRW2, dst_timing->emc_mrw2, 0); + CcfifoWrite(EMC_MRW, dst_timing->emc_mrw, 0); + + if (is_lpddr3) { + CcfifoWrite(EMC_MRW4, dst_timing->emc_mrw4, 0); + } + } else if (dram_type == DRAM_TYPE_DDR4) { + if (opt_dll_mode) { + CcfifoWrite(EMC_EMRS, dst_timing->emc_emrs & ~(1 << 26), 0); + } + CcfifoWrite(EMC_EMRS2, dst_timing->emc_emrs2 & ~(1 << 26), 0); + CcfifoWrite(EMC_MRS, dst_timing->emc_mrs | (1 << 26), 0); + } + + /* Step 19: + * ZQCAL for LPDDR3/DDR3 + */ + + if (opt_zcal_en_cc) { + if (is_lpddr2) { + uint32_t zq_op = opt_cc_short_zcal ? dst_timing->zq_op_cc_short_zcal : dst_timing->zq_op_cc_long_zcal; + uint32_t zcal_wait_time_ps = opt_cc_short_zcal ? dst_timing->zcal_wait_time_ps_cc_short_zcal : dst_timing->zcal_wait_time_ps_cc_long_zcal; + uint32_t zcal_wait_time_clocks = util::DivideUp(zcal_wait_time_ps, dst_clock_period); + + CcfifoWrite(EMC_MRS_WAIT_CNT2, (zcal_wait_time_clocks & 0x3FF) | ((zcal_wait_time_clocks & 0x7FF) << 16), 0); + CcfifoWrite(EMC_MRW, (zq_op | 0x880C0000) - 0x20000, 0); + + if (dram_dev_num == TWO_RANK) { + CcfifoWrite(EMC_MRW, zq_op | 0x480A0000, 0); + } + } else if (dram_type == DRAM_TYPE_DDR4) { + CcfifoWrite(EMC_ZQ_CAL, (2 << 30) | (1 << 0) | (opt_cc_short_zcal ? 0 : (1 << 4)), 0); + if (dram_dev_num == TWO_RANK) { + CcfifoWrite(EMC_ZQ_CAL, (1 << 30) | (1 << 0) | (opt_cc_short_zcal ? 0 : (1 << 4)), 0); + } + } + } + + /* Step 20: + * Issue ref and optional QRST. + */ + + if (training_enabled || dram_type != DRAM_TYPE_LPDDR4) { + CcfifoWrite(EMC_REF, dram_dev_num == ONE_RANK ? 0x80000000 : 0x00000000, 0); + } + + if (opt_do_sw_qrst) { + CcfifoWrite(EMC_ISSUE_QRST, 1, 0); + CcfifoWrite(EMC_ISSUE_QRST, 0, 2); + } + + /* Step 21: + * Restore ZCAL and ZCAL interval. + */ + + if (save_restore_clkstop_pd || opt_zcal_en_cc || (training_enabled && dram_type == DRAM_TYPE_LPDDR4)) { + CcfifoWrite(EMC_DBG, SetShadowBypass(ACTIVE, emc_dbg_o), 0); + + if (opt_zcal_en_cc) { + if (training_enabled) { + CcfifoWrite(EMC_ZCAL_INTERVAL, (dst_misc_cfg_2 & 2) ? 0 : src_timing->burst_regs.emc_zcal_interval, 0); + } else if (dram_type != DRAM_TYPE_LPDDR4) { + CcfifoWrite(EMC_ZCAL_INTERVAL, (dst_misc_cfg_2 & 2) ? 0 : dst_timing->burst_regs.emc_zcal_interval, 0); + } + } + + if (save_restore_clkstop_pd || (training_enabled && dram_type == DRAM_TYPE_LPDDR4)) { + CcfifoWrite(EMC_CFG, dst_timing->burst_regs.emc_cfg & ~(1 << 28), 0); + } + + if (training_enabled && dram_type == DRAM_TYPE_LPDDR4) { + CcfifoWrite(EMC_SEL_DPD_CTRL, src_timing->emc_sel_dpd_ctrl, 0); + } + + CcfifoWrite(EMC_DBG, emc_dbg_o, 0); + } + + /* Step 22: + * Restore EMC_CFG_PIPE_CLK. + */ + + CcfifoWrite(EMC_CFG_PIPE_CLK, emc_cfg_pipe_clk_o, 0); + CcfifoWrite(EMC_INTSTATUS, 0, dst_timing->pipe_clk_delay); + + /* Step 23: + * Do clock change. + */ + + if (training_enabled) { + uint32_t clk_source_emc; + if (ch0_enable || ch1_enable) { + clk_source_emc = reg::Read(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC); + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC_SAFE, clk_source_emc); + } else { + clk_source_emc = 0; + } + + ChangeDllSrc(src_timing, clk_source_emc); + } + + { + uint32_t dig_dll = (reg::Read(EMC + EMC_CFG_DIG_DLL) & 0xFFFFFFE4) | 8; + if ((dst_misc_cfg_2 & 1) == 0) { + dig_dll = (dig_dll & 0xFFFFFF3F) | 0x80; + } + reg::Write(EMC + EMC_CFG_DIG_DLL, dig_dll); + reg::Read(EMC + EMC_CFG_DIG_DLL); + } + + reg::Read(MC + MC_EMEM_ADR_CFG); + reg::Read(EMC + EMC_INTSTATUS); + + if (ch0_enable || ch1_enable) { + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC, dst_clk_src); + reg::Read(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC); + } + + if (WaitForUpdate(EMC_INTSTATUS, (1 << 4), true, dst_emc_fbio_cfg7)) { + return; + } + + /* Step 24: + * Save training results. + */ + + if (training_enabled) { + uint32_t tmp_emem_numdev = reg::Read(MC + MC_EMEM_ADR_CFG) & 1; + + uint32_t emc_dbg_tmp = reg::Read(EMC + EMC_DBG); + reg::Write(EMC + EMC_DBG, emc_dbg_tmp | 1); /* Set READ_MUX to ASSEMBLY. */ + + uint32_t tmp_dram_dev_num = 1 + tmp_emem_numdev; + + /* Save CA results. */ + if (train_ca) { + dst_timing->trim_perch_regs.emc0_cmd_brlshft_0 = reg::Read(EMC0 + EMC_CMD_BRLSHFT_0); + dst_timing->trim_perch_regs.emc1_cmd_brlshft_1 = reg::Read(EMC1 + EMC_CMD_BRLSHFT_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_4 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_4); + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_5 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_5); + + if (train_bit_level) { + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd0_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd0_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd0_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd1_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd1_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd1_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd2_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd2_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd2_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd3_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd3_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_cmd3_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_2); + } + } + + /* Save CA_VREF results. */ + if (train_ca_vref) { + uint32_t emc0_training_opt_ca_vref = reg::Read(EMC0 + EMC_TRAINING_OPT_CA_VREF); + uint32_t emc1_training_opt_ca_vref = reg::Read(EMC1 + EMC_TRAINING_OPT_CA_VREF); + + uint32_t rank_mask = tmp_dram_dev_num == TWO_RANK ? 0x480C0000 : 0xC80C0000; + + dst_timing->burst_perch_regs.emc0_mrw10 = (emc0_training_opt_ca_vref & 0xFFFF) | 0x880C0000; + dst_timing->burst_perch_regs.emc1_mrw10 = (emc1_training_opt_ca_vref & 0xFFFF) | 0x880C0000; + dst_timing->burst_perch_regs.emc0_mrw11 = (rank_mask & 0xFFFFFF00) | ((emc0_training_opt_ca_vref >> 16) & 0xFFFF); + dst_timing->burst_perch_regs.emc1_mrw11 = (rank_mask & 0xFFFFFF00) | ((emc1_training_opt_ca_vref >> 16) & 0xFFFF); + } + + /* Save RD results. */ + if (train_rd) { + dst_timing->trim_regs.emc_pmacro_ib_ddll_long_dqs_rank0_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_long_dqs_rank0_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_long_dqs_rank0_2 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_long_dqs_rank0_3 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_3); + + if (tmp_dram_dev_num == TWO_RANK) { + dst_timing->trim_regs.emc_pmacro_ib_ddll_long_dqs_rank1_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_long_dqs_rank1_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_long_dqs_rank1_2 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_long_dqs_rank1_3 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_3); + } + + if (train_bit_level) { + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte0_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte0_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte0_2 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte1_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte1_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte1_2 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte2_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte2_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte2_2 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte3_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte3_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte3_2 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte4_0 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte4_1 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte4_2 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte5_0 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte5_1 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte5_2 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte6_0 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte6_1 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte6_2 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte7_0 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte7_1 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank0_byte7_2 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_2); + + if (tmp_dram_dev_num == TWO_RANK) { + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte0_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte0_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte0_2 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte1_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte1_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte1_2 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte2_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte2_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte2_2 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte3_0 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte3_1 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte3_2 = reg::Read(EMC0 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte4_0 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte4_1 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte4_2 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte5_0 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte5_1 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte5_2 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte6_0 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte6_1 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte6_2 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_2); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte7_0 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_0); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte7_1 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_1); + dst_timing->trim_regs.emc_pmacro_ib_ddll_short_dq_rank1_byte7_2 = reg::Read(EMC1 + EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_2); + } + } + + /* Save RD_VREF results. */ + if (train_rd_vref) { + uint32_t emc_pmacro_ib_vref_dq_0 = reg::Read(EMC0 + EMC_PMACRO_IB_VREF_DQ_0); + uint32_t emc_pmacro_ib_vref_dq_1 = reg::Read(EMC0 + EMC_PMACRO_IB_VREF_DQ_1); + + #define GET_SAVE_RESTORE_MOD_REG(n) ((dst_timing->save_restore_mod_regs[n] & 0x80000000) ? (~dst_timing->save_restore_mod_regs[n]) : (dst_timing->save_restore_mod_regs[n])) + + uint8_t ib_vref_dq_byte0_icr = ((emc_pmacro_ib_vref_dq_0 >> 0) & 0x7F) + (GET_SAVE_RESTORE_MOD_REG(0) & 0x7F); + uint8_t ib_vref_dq_byte1_icr = ((emc_pmacro_ib_vref_dq_0 >> 8) & 0x7F) + (GET_SAVE_RESTORE_MOD_REG(1) & 0x7F); + uint8_t ib_vref_dq_byte2_icr = ((emc_pmacro_ib_vref_dq_0 >> 16) & 0x7F) + (GET_SAVE_RESTORE_MOD_REG(2) & 0x7F); + uint8_t ib_vref_dq_byte3_icr = ((emc_pmacro_ib_vref_dq_0 >> 24) & 0x7F) + (GET_SAVE_RESTORE_MOD_REG(3) & 0x7F); + uint8_t ib_vref_dq_byte4_icr = ((emc_pmacro_ib_vref_dq_1 >> 0) & 0x7F) + (GET_SAVE_RESTORE_MOD_REG(4) & 0x7F); + uint8_t ib_vref_dq_byte5_icr = ((emc_pmacro_ib_vref_dq_1 >> 8) & 0x7F) + (GET_SAVE_RESTORE_MOD_REG(5) & 0x7F); + uint8_t ib_vref_dq_byte6_icr = ((emc_pmacro_ib_vref_dq_1 >> 16) & 0x7F) + (GET_SAVE_RESTORE_MOD_REG(6) & 0x7F); + uint8_t ib_vref_dq_byte7_icr = ((emc_pmacro_ib_vref_dq_1 >> 24) & 0x7F) + (GET_SAVE_RESTORE_MOD_REG(7) & 0x7F); + + dst_timing->trim_regs.emc_pmacro_ib_vref_dq_0 = ((ib_vref_dq_byte0_icr & 0x7F) | (ib_vref_dq_byte1_icr & 0x7F) << 8) | ((ib_vref_dq_byte2_icr & 0x7F) << 16) | ((ib_vref_dq_byte3_icr & 0x7F) << 24); + + + dst_timing->trim_regs.emc_pmacro_ib_vref_dq_1 = ((ib_vref_dq_byte4_icr & 0x7F) | (ib_vref_dq_byte5_icr & 0x7F) << 8) | ((ib_vref_dq_byte6_icr & 0x7F) << 16) | ((ib_vref_dq_byte7_icr & 0x7F) << 24); + + #undef GET_SAVE_RESTORE_MOD_REG + } + } + + /* Save WR results. */ + if (train_wr) { + dst_timing->trim_perch_regs.emc0_data_brlshft_0 = reg::Read(EMC0 + EMC_DATA_BRLSHFT_0); + dst_timing->trim_perch_regs.emc1_data_brlshft_0 = reg::Read(EMC1 + EMC_DATA_BRLSHFT_0); + + if (tmp_dram_dev_num == TWO_RANK) { + dst_timing->trim_perch_regs.emc0_data_brlshft_1 = reg::Read(EMC0 + EMC_DATA_BRLSHFT_1); + dst_timing->trim_perch_regs.emc1_data_brlshft_1 = reg::Read(EMC1 + EMC_DATA_BRLSHFT_1); + } + + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_2 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank0_3 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3); + + if (tmp_dram_dev_num == TWO_RANK) { + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank1_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank1_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank1_2 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_long_dq_rank1_3 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3); + } + + if (train_bit_level) { + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte0_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte0_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte0_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte1_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte1_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte1_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte2_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte2_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte2_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte3_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte3_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte3_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte4_0 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte4_1 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte4_2 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte5_0 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte5_1 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte5_2 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte6_0 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte6_1 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte6_2 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte7_0 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte7_1 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank0_byte7_2 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_2); + + if (tmp_dram_dev_num == TWO_RANK) { + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte0_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte0_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte0_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte1_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte1_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte1_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte2_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte2_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte2_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte3_0 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte3_1 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte3_2 = reg::Read(EMC0 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte4_0 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte4_1 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte4_2 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte5_0 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte5_1 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte5_2 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte6_0 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte6_1 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte6_2 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_2); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte7_0 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_0); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte7_1 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_1); + dst_timing->trim_regs.emc_pmacro_ob_ddll_short_dq_rank1_byte7_2 = reg::Read(EMC1 + EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_2); + } + } + + /* Save WR_VREF results. */ + if (train_wr_vref) { + uint32_t emc0_training_opt_dq_ob_vref = reg::Read(EMC0 + EMC_TRAINING_OPT_DQ_OB_VREF); + uint32_t emc1_training_opt_dq_ob_vref = reg::Read(EMC1 + EMC_TRAINING_OPT_DQ_OB_VREF); + + #define GET_SAVE_RESTORE_MOD_REG(n) ((dst_timing->save_restore_mod_regs[n] & 0x80000000) ? (~dst_timing->save_restore_mod_regs[n]) : (dst_timing->save_restore_mod_regs[n])) + uint8_t mod_reg_8 = GET_SAVE_RESTORE_MOD_REG( 8); + uint8_t mod_reg_9 = GET_SAVE_RESTORE_MOD_REG( 9); + uint8_t mod_reg_10 = GET_SAVE_RESTORE_MOD_REG(10); + uint8_t mod_reg_11 = GET_SAVE_RESTORE_MOD_REG(11); + #undef GET_SAVE_RESTORE_MOD_REG + + uint32_t rank_mask = tmp_dram_dev_num == TWO_RANK ? 0x480E0000 : 0xC80E0000; + + uint8_t emc0_mrw12_byte0 = (mod_reg_8 + ((emc0_training_opt_dq_ob_vref >> 0) & 0xFF)); + uint8_t emc0_mrw12_byte1 = (mod_reg_9 + ((emc0_training_opt_dq_ob_vref >> 8) & 0xFF)); + uint8_t emc0_mrw13_byte0 = (mod_reg_8 + ((emc0_training_opt_dq_ob_vref >> 16) & 0xFF)); + uint8_t emc0_mrw13_byte1 = (mod_reg_9 + ((emc0_training_opt_dq_ob_vref >> 24) & 0xFF)); + uint8_t emc1_mrw12_byte0 = (mod_reg_10 + ((emc1_training_opt_dq_ob_vref >> 0) & 0xFF)); + uint8_t emc1_mrw12_byte1 = (mod_reg_11 + ((emc1_training_opt_dq_ob_vref >> 8) & 0xFF)); + uint8_t emc1_mrw13_byte0 = (mod_reg_10 + ((emc1_training_opt_dq_ob_vref >> 16) & 0xFF)); + uint8_t emc1_mrw13_byte1 = (mod_reg_11 + ((emc1_training_opt_dq_ob_vref >> 24) & 0xFF)); + + dst_timing->burst_perch_regs.emc0_mrw12 = (emc0_mrw12_byte0 << 0) | (emc0_mrw12_byte1 << 8) | 0x880E0000; + dst_timing->burst_perch_regs.emc1_mrw12 = (emc1_mrw12_byte0 << 0) | (emc1_mrw12_byte1 << 8) | 0x880E0000; + dst_timing->burst_perch_regs.emc1_mrw13 = (emc1_mrw13_byte0 << 0) | (emc1_mrw13_byte1 << 8) | rank_mask; + dst_timing->burst_perch_regs.emc0_mrw13 = (emc0_mrw13_byte0 << 0) | (emc0_mrw13_byte1 << 8) | rank_mask; + } + } + + reg::Write(EMC + EMC_DBG, emc_dbg_tmp); + } + + /* Step 25: + * Program MC updown registers. + */ + + if (dst_timing->rate_khz > src_timing->rate_khz && !training_enabled) { + for (u32 i = 0; i < dst_timing->num_up_down; i++) { + reg::Write(LaScaleRegisters[i], dst_timing->la_scale_regs_arr[i]); + } + + /* Request a timing update. */ + TimingUpdate(dst_emc_fbio_cfg7); + } + + /* Step 26: + * Restore ZCAL registers. + */ + + if (dram_type == DRAM_TYPE_LPDDR4 && !training_enabled) { + reg::Write(EMC + EMC_DBG, SetShadowBypass(ACTIVE, emc_dbg_o)); + + reg::Write(EMC + EMC_ZCAL_WAIT_CNT, dst_timing->burst_regs.emc_zcal_wait_cnt); + reg::Write(EMC + EMC_ZCAL_INTERVAL, (dst_misc_cfg_2 & 2) ? 0 : dst_timing->burst_regs.emc_zcal_interval); + + reg::Write(EMC + EMC_DBG, emc_dbg_o); + } + + if (dram_type != DRAM_TYPE_LPDDR4 && opt_cc_short_zcal && opt_zcal_en_cc && !opt_short_zcal) { + util::WaitMicroSeconds(2); + + if (is_lpddr2) { + reg::Write(EMC + EMC_DBG, SetShadowBypass(ACTIVE, emc_dbg_o)); + + reg::Write(EMC + EMC_MRS_WAIT_CNT, dst_timing->burst_regs.emc_mrs_wait_cnt); + + reg::Write(EMC + EMC_DBG, emc_dbg_o); + } else if (dram_type == DRAM_TYPE_DDR4) { + reg::Write(EMC + EMC_DBG, SetShadowBypass(ACTIVE, emc_dbg_o)); + + reg::Write(EMC + EMC_ZCAL_WAIT_CNT, dst_timing->burst_regs.emc_zcal_wait_cnt); + + reg::Write(EMC + EMC_DBG, emc_dbg_o); + } + } + + if (training_enabled) { + if (!src_timing->pllm_misc1_0_pllm_clamp_ph90) { + reg::SetBits(CLKRST + CLK_RST_CONTROLLER_PLLM_MISC1, 0x80000000); + } + } else { + if (!dst_timing->pllm_misc1_0_pllm_clamp_ph90) { + reg::SetBits(CLKRST + CLK_RST_CONTROLLER_PLLM_MISC1, 0x80000000); + } + } + + /* Step 27: + * Restore EMC_CFG, FDPD registers. + */ + + reg::Write(EMC + EMC_DBG, SetShadowBypass(ACTIVE, emc_dbg_o)); + reg::Write(EMC + EMC_CFG, dst_timing->burst_regs.emc_cfg); + reg::Write(EMC + EMC_DBG, emc_dbg_o); + + reg::Write(EMC + EMC_FDPD_CTRL_CMD_NO_RAMP, dst_timing->emc_fdpd_ctrl_cmd_no_ramp); + reg::Write(EMC + EMC_SEL_DPD_CTRL, dst_timing->emc_sel_dpd_ctrl); + + /* Step 28: + * Training recover. + */ + + if (training_enabled && dram_type == DRAM_TYPE_LPDDR4) { + reg::Write(EMC + EMC_DBG, SetShadowBypass(ACTIVE, emc_dbg_o)); + + reg::Write(EMC + EMC_CFG, dst_timing->burst_regs.emc_cfg); + reg::Write(EMC + EMC_SEL_DPD_CTRL, dst_timing->emc_sel_dpd_ctrl); + reg::Write(EMC + EMC_ZCAL_WAIT_CNT, src_timing->burst_regs.emc_zcal_wait_cnt); + reg::Write(EMC + EMC_ZCAL_INTERVAL, (dst_misc_cfg_2 & 2) ? 0 : dst_timing->burst_regs.emc_zcal_interval); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG2, dst_timing->emc_auto_cal_config2); + if (ch0_enable || ch1_enable) { + reg::Write(EMC0 + EMC_AUTO_CAL_CONFIG3, dst_timing->emc_auto_cal_config3); + reg::Write(EMC0 + EMC_AUTO_CAL_CONFIG4, dst_timing->emc_auto_cal_config4); + reg::Write(EMC0 + EMC_AUTO_CAL_CONFIG5, dst_timing->emc_auto_cal_config5); + reg::Write(EMC0 + EMC_AUTO_CAL_CONFIG6, dst_timing->emc_auto_cal_config6); + reg::Write(EMC0 + EMC_AUTO_CAL_CONFIG7, dst_timing->emc_auto_cal_config7); + reg::Write(EMC0 + EMC_AUTO_CAL_CONFIG8, dst_timing->emc_auto_cal_config8); + } + + reg::Write(EMC + EMC_DBG, emc_dbg_o); + + reg::Write(EMC + EMC_TR_DVFS, dst_timing->burst_regs.emc_tr_dvfs & ~(1 << 0)); + } + + /* Step 29: + * Power fix WAR. + */ + + reg::Write(EMC + EMC_DBG, SetShadowBypass(ACTIVE, emc_dbg_o)); + + reg::Write(EMC + EMC_PMACRO_AUTOCAL_CFG_COMMON, dst_timing->burst_regs.emc_pmacro_autocal_cfg_common); + + reg::Write(EMC + EMC_DBG, emc_dbg_o); + + reg::Write(EMC + EMC_PMACRO_CFG_PM_GLOBAL_0, 0xFF0000); + reg::Write(EMC + EMC_PMACRO_TRAINING_CTRL_0, 0x8); + reg::Write(EMC + EMC_PMACRO_TRAINING_CTRL_1, 0x8); + reg::Write(EMC + EMC_PMACRO_CFG_PM_GLOBAL_0, 0); + + reg::Write(EMC + EMC_DBG, SetShadowBypass(ACTIVE, emc_dbg_o)); + + /* Fixup xm2comppadctrl */ + if ((dst_misc_cfg_1 & 0x20) == 0) { + uint32_t xm2comppadctrl = reg::Read(EMC + EMC_XM2COMPPADCTRL); + + reg::Write(EMC + EMC_XM2COMPPADCTRL, xm2comppadctrl & 0x1CFFFFFF); + util::WaitMicroSeconds(1); + reg::Write(EMC + EMC_XM2COMPPADCTRL, xm2comppadctrl & 0x0CFFFFFF); + util::WaitMicroSeconds(1); + reg::Write(EMC + EMC_XM2COMPPADCTRL, xm2comppadctrl & 0x04FFFFFF); + util::WaitMicroSeconds(1); + } + + reg::SetBits(EMC + EMC_PMACRO_DLL_CFG_1, 0x2000); + reg::Read(EMC + EMC_PMACRO_DLL_CFG_1); + util::WaitMicroSeconds(2); + + /* Select EMC DLL clock source. */ + { + reg::SetBits(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC_DLL, 0xC00); + reg::Read(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC_DLL); + } + + reg::Write(EMC + EMC_DBG, SetShadowBypass(ASSEMBLY, emc_dbg_o)); + reg::Read(EMC + EMC_DBG); + + /* Step 30: + * Re-enable autocal. + */ + + if (!training_enabled) { + if (dst_timing->burst_regs.emc_cfg_dig_dll & 1) { + uint32_t dig_dll = (reg::Read(EMC + EMC_CFG_DIG_DLL) & 0xFFFFFFE4) | 9; + if ((dst_misc_cfg_2 & 1) == 0) { + dig_dll = (dig_dll & 0xFFFFFF3F) | 0x80; + } + reg::Write(EMC + EMC_CFG_DIG_DLL, dig_dll); + + /* Request a timing update. */ + TimingUpdate(dst_emc_fbio_cfg7); + } + } + + if (!(training_enabled && dram_type == DRAM_TYPE_LPDDR4)) { + reg::Write(EMC + EMC_AUTO_CAL_CONFIG, training_enabled ? src_timing->emc_auto_cal_config : dst_timing->emc_auto_cal_config); + } + + if (training_enabled) { + g_fsp_for_next_freq = !g_fsp_for_next_freq; + } + + if (src_timing->periodic_training) { + /* Reset all clock tree values. */ + src_timing->current_dram_clktree_c0d0u0 = src_timing->trained_dram_clktree_c0d0u0; + src_timing->current_dram_clktree_c0d0u1 = src_timing->trained_dram_clktree_c0d0u1; + src_timing->current_dram_clktree_c0d1u0 = src_timing->trained_dram_clktree_c0d1u0; + src_timing->current_dram_clktree_c0d1u1 = src_timing->trained_dram_clktree_c0d1u1; + src_timing->current_dram_clktree_c1d0u0 = src_timing->trained_dram_clktree_c1d0u0; + src_timing->current_dram_clktree_c1d0u1 = src_timing->trained_dram_clktree_c1d0u1; + src_timing->current_dram_clktree_c1d1u0 = src_timing->trained_dram_clktree_c1d1u0; + src_timing->current_dram_clktree_c1d1u1 = src_timing->trained_dram_clktree_c1d1u1; + } + + /* Disable pll. */ + PllDisable(dst_clk_src); + } + + void CleanupActiveShadowCopy(EmcDvfsTimingTable *src_timing, EmcDvfsTimingTable *dst_timing) { + /* Change CFG_SWAP to ASSEMBLY_ONLY */ + uint32_t emc_dbg = reg::Read(EMC + EMC_DBG); + emc_dbg = ((emc_dbg & 0xF3FFFFFF) | 0x8000000); + reg::Write(EMC + EMC_DBG, emc_dbg); + + /* Wait for update. */ + TimingUpdate(src_timing->emc_fbio_cfg7); + + /* Change CFG_SWAP to ACTIVE_ONLY */ + emc_dbg = reg::Read(EMC + EMC_DBG); + emc_dbg &= 0xF3FFFFFF; + reg::Write(EMC + EMC_DBG, emc_dbg); + + /* Set PMACRO_DLL_CFG_1, preserving MDDLL_SEL_CLK_SRC */ + reg::Write(EMC + EMC_PMACRO_DLL_CFG_1, (reg::Read(EMC + EMC_PMACRO_DLL_CFG_1) & 0x00002000) | (src_timing->burst_regs.emc_pmacro_dll_cfg_1 & 0xFFFFDFFF)); + + /* Update CFG_DIG_DLL */ + uint32_t emc_cfg_dig_dll = reg::Read(EMC + EMC_CFG_DIG_DLL); + emc_cfg_dig_dll = (dst_timing->misc_cfg_2 & 1) ? (emc_cfg_dig_dll & 0xFFFFFFFE) : ((emc_cfg_dig_dll & 0xFFFFFF3E) | 0x80); + reg::Write(EMC + EMC_CFG_DIG_DLL, emc_cfg_dig_dll); + + /* Wait for update. */ + TimingUpdate(src_timing->emc_fbio_cfg7); + + /* Disable or enable DLL */ + emc_cfg_dig_dll = reg::Read(EMC + EMC_CFG_DIG_DLL); + if (src_timing->burst_regs.emc_cfg_dig_dll & 1) { + emc_cfg_dig_dll |= 0x01; + } else { + emc_cfg_dig_dll &= 0xFFFFFFFE; + } + if ((dst_timing->misc_cfg_2 & 1) == 0) { + emc_cfg_dig_dll = ((emc_cfg_dig_dll & 0xFFFFFF3F) | 0x80); + } + reg::Write(EMC + EMC_CFG_DIG_DLL, emc_cfg_dig_dll); + + /* Wait for update. */ + TimingUpdate(src_timing->emc_fbio_cfg7); + + /* Wait for DLL_LOCK to be set */ + WaitForUpdate(EMC_DIG_DLL_STATUS, (1 << 2), true, src_timing->emc_fbio_cfg7); + + /* Wait for update. */ + TimingUpdate(src_timing->emc_fbio_cfg7); + + /* Set AUTO_CAL_CONFIG. */ + uint32_t emc_auto_cal_config = reg::Read(EMC + EMC_AUTO_CAL_CONFIG); + emc_auto_cal_config = (dst_timing->misc_cfg_2 & 4) ? (emc_auto_cal_config & 0xDFFFF9FF) : ((emc_auto_cal_config & 0x5FFFF9FF) | 0x80000000); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG, emc_auto_cal_config | 0x20000000); + } + + void TrainFreq(EmcDvfsTimingTable *src_timing, EmcDvfsTimingTable *dst_timing, u32 next_clk_src) { + /* Get dram dev num. */ + const u32 dram_dev_num = (reg::Read(MC + MC_EMEM_ADR_CFG) & 1) + 1; + + /* Write RAM patterns, if first training. */ + if (!g_did_first_training) { + const auto * const pattern = GetEmcRamTrainingPattern(); + for (u32 i = 0; i < 0x100; ++i) { + reg::Write(EMC + EMC_TRAINING_PATRAM_DQ, pattern[dst_timing->training_pattern].dq[i]); + reg::Write(EMC + EMC_TRAINING_PATRAM_DMI, pattern[dst_timing->training_pattern].dmi[i]); + reg::Write(EMC + EMC_TRAINING_PATRAM_CTRL, 0x80000000 | i); + } + + g_did_first_training = true; + } + + reg::Write(EMC + EMC_TRAINING_QUSE_CTRL_MISC, (dst_timing->burst_regs.emc_training_read_ctrl_misc & 0xFFFF0000) | 0x00001000); + + /* Do training, if we need to. */ + const u32 needed_training = dst_timing->needs_training; + if (needed_training && !dst_timing->trained) { + /* Determine what training to do. */ + u32 training_params[4]; + u32 num_params = 0; + + if (needed_training & (CA_TRAINING | CA_VREF_TRAINING)) { + training_params[num_params++] = (needed_training & (CA_TRAINING | CA_VREF_TRAINING | BIT_LEVEL_TRAINING)); + if (dram_dev_num == TWO_RANK) { + training_params[num_params++] = (needed_training & (CA_TRAINING | CA_VREF_TRAINING | TRAIN_SECOND_RANK | BIT_LEVEL_TRAINING)); + } + } + + if (needed_training & (WRITE_TRAINING | WRITE_VREF_TRAINING | READ_TRAINING | READ_VREF_TRAINING)) { + training_params[num_params++] = (needed_training & (WRITE_TRAINING | WRITE_VREF_TRAINING | READ_TRAINING | READ_VREF_TRAINING | BIT_LEVEL_TRAINING)); + } + + /* Apply all training. */ + for (u32 i = 0; i < num_params; ++i) { + FreqChange(src_timing, dst_timing, training_params[i], next_clk_src); + CleanupActiveShadowCopy(src_timing, dst_timing); + } + + /* Set tables as trained. */ + dst_timing->trained = 1; + } + } + + void Dvfs(EmcDvfsTimingTable *dst_timing, EmcDvfsTimingTable *src_timing, bool train) { + /* Get the clock sources/rates. */ + u32 clk_src_emc_from = src_timing->clk_src_emc; + u32 clk_src_emc_to = dst_timing->clk_src_emc; + u32 rate_from = src_timing->rate_khz; + u32 rate_to = dst_timing->rate_khz; + + /* Get channel enables. */ + const bool ch0_enable = reg::GetField(dst_timing->emc_fbio_cfg7, EMC_REG_BITS_MASK(FBIO_CFG7_CH0_ENABLE)) == EMC_FBIO_CFG7_CH0_ENABLE_ENABLE; + const bool ch1_enable = reg::GetField(dst_timing->emc_fbio_cfg7, EMC_REG_BITS_MASK(FBIO_CFG7_CH1_ENABLE)) == EMC_FBIO_CFG7_CH1_ENABLE_ENABLE; + + /* Reprogram pll. */ + const u32 prev_2x_clk_src = reg::GetField(clk_src_emc_from, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC)); + const u32 next_2x_clk_src = reg::GetField(clk_src_emc_to, CLK_RST_REG_BITS_MASK(CLK_SOURCE_EMC_EMC_2X_CLK_SRC)); + if (next_2x_clk_src != PLLP_OUT0 && next_2x_clk_src != PLLP_UD) { + if (ch0_enable || ch1_enable) { + if (PllReprogram(rate_to, clk_src_emc_to, rate_from, clk_src_emc_from)) { + if (prev_2x_clk_src == PLLMB_UD || prev_2x_clk_src == PLLMB_OUT0) { + g_next_pll = 0; + } else if (prev_2x_clk_src == PLLM_UD || prev_2x_clk_src == PLLM_OUT0) { + g_next_pll = !g_next_pll; + } + + clk_src_emc_to = ProgramPllm(rate_to, clk_src_emc_to, clk_src_emc_to, g_next_pll, dst_timing); + } else { + if (next_2x_clk_src == PLLM_UD || next_2x_clk_src == PLLMB_UD) { + if (g_next_pll) { + reg::SetField(clk_src_emc_to, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, PLLMB_UD)); + } + } else if (next_2x_clk_src == PLLM_OUT0 || next_2x_clk_src == PLLMB_OUT0) { + if (g_next_pll) { + reg::SetField(clk_src_emc_to, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, PLLMB_OUT0)); + } + } + } + } + } + + if (train) { + TrainFreq(src_timing, dst_timing, clk_src_emc_to); + if (ch0_enable || ch1_enable) { + if (PllReprogram(dst_timing->rate_khz, dst_timing->clk_src_emc, src_timing->rate_khz, src_timing->clk_src_emc)) { + g_next_pll = !g_next_pll; + } + } + } else { + FreqChange(src_timing, dst_timing, 0, clk_src_emc_to); + } + } + + } + + void DoMemoryTrainingMariko(bool *out_did_training, int index, void *mtc_tables_buffer) { + /* Get timing tables. */ + auto *timing = GetEmcDvfsTimingTables(index, mtc_tables_buffer); + auto *src_timing = timing + 0; + auto *dst_timing = timing + 1; + + /* Check timing tables. */ + if (src_timing->rate_khz != 204000 || dst_timing->rate_khz != 1600000) { + ShowFatalError("EmcDvfsTimingTables seem corrupted %" PRIu32 " %" PRIu32 "?\n", src_timing->rate_khz, dst_timing->rate_khz); + } + + /* Check that we should do training. */ + if (src_timing->clk_src_emc != reg::Read(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC)) { + /* Our clock source isn't what's expected, so presumably training has already been done? */ + /* Either way, the safe bet is to skip it. */ + *out_did_training = false; + return; + } + + /* Train 1600MHz. */ + Dvfs(dst_timing, src_timing, true); + + /* Switch to 1600MHz. */ + Dvfs(dst_timing, src_timing, false); + + /* Set ourselves as having done training */ + *out_did_training = true; + } + + void RestoreMemoryClockRateMariko(void *mtc_tables_buffer) { + /* Get timing tables. */ + auto *timing_tables = reinterpret_cast<EmcDvfsTimingTable *>(mtc_tables_buffer); + auto *src_timing = timing_tables + 0; + auto *dst_timing = timing_tables + 1; + + /* Check timing tables. */ + if (src_timing->rate_khz != 204000 || dst_timing->rate_khz != 1600000) { + ShowFatalError("EmcDvfsTimingTables seem corrupted %" PRIu32 " %" PRIu32 "?\n", src_timing->rate_khz, dst_timing->rate_khz); + } + + /* Switch to 204MHz */ + Dvfs(src_timing, dst_timing, false); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_ram_training_pattern.inc b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_ram_training_pattern.inc new file mode 100644 index 00000000..9e36044f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_ram_training_pattern.inc @@ -0,0 +1,428 @@ +/* + * Copyright (c) Atmosphre-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 <http://www.gnu.org/licenses/>. + */ + +struct EmcRamTrainingPattern { + u32 dq[0x100]; + u8 dmi[0x100]; +}; + +constexpr const u8 EmcRamTrainingPatternData[] { + 0x18, 0x18, 0x18, 0x18, 0x61, 0x61, 0x61, 0x61, 0x85, 0x85, 0x85, 0x85, 0x14, 0x14, 0x14, 0x14, + 0x51, 0x51, 0x51, 0x51, 0x47, 0x47, 0x47, 0x47, 0x1E, 0x1E, 0x1E, 0x1E, 0x79, 0x79, 0x79, 0x79, + 0xE5, 0xE5, 0xE5, 0xE5, 0x94, 0x94, 0x94, 0x94, 0x51, 0x51, 0x51, 0x51, 0x46, 0x46, 0x46, 0x46, + 0x19, 0x19, 0x19, 0x19, 0x67, 0x67, 0x67, 0x67, 0x9C, 0x9C, 0x9C, 0x9C, 0x71, 0x71, 0x71, 0x71, + 0xC5, 0xC5, 0xC5, 0xC5, 0x17, 0x17, 0x17, 0x17, 0x5F, 0x5F, 0x5F, 0x5F, 0x7E, 0x7E, 0x7E, 0x7E, + 0xFB, 0xFB, 0xFB, 0xFB, 0xED, 0xED, 0xED, 0xED, 0xB4, 0xB4, 0xB4, 0xB4, 0xD2, 0xD2, 0xD2, 0xD2, + 0x48, 0x48, 0x48, 0x48, 0x21, 0x21, 0x21, 0x21, 0x85, 0x85, 0x85, 0x85, 0x16, 0x16, 0x16, 0x16, + 0x59, 0x59, 0x59, 0x59, 0x66, 0x66, 0x66, 0x66, 0x9A, 0x9A, 0x9A, 0x9A, 0x69, 0x69, 0x69, 0x69, + 0xA4, 0xA4, 0xA4, 0xA4, 0x93, 0x93, 0x93, 0x93, 0x4F, 0x4F, 0x4F, 0x4F, 0x3F, 0x3F, 0x3F, 0x3F, + 0xFC, 0xFC, 0xFC, 0xFC, 0xF3, 0xF3, 0xF3, 0xF3, 0xCD, 0xCD, 0xCD, 0xCD, 0x37, 0x37, 0x37, 0x37, + 0xDC, 0xDC, 0xDC, 0xDC, 0x70, 0x70, 0x70, 0x70, 0xC3, 0xC3, 0xC3, 0xC3, 0x0F, 0x0F, 0x0F, 0x0F, + 0x3E, 0x3E, 0x3E, 0x3E, 0xFA, 0xFA, 0xFA, 0xFA, 0xEB, 0xEB, 0xEB, 0xEB, 0xAC, 0xAC, 0xAC, 0xAC, + 0xB3, 0xB3, 0xB3, 0xB3, 0xCC, 0xCC, 0xCC, 0xCC, 0x31, 0x31, 0x31, 0x31, 0xC5, 0xC5, 0xC5, 0xC5, + 0x15, 0x15, 0x15, 0x15, 0x57, 0x57, 0x57, 0x57, 0x5F, 0x5F, 0x5F, 0x5F, 0x7F, 0x7F, 0x7F, 0x7F, + 0xFD, 0xFD, 0xFD, 0xFD, 0xF4, 0xF4, 0xF4, 0xF4, 0xD0, 0xD0, 0xD0, 0xD0, 0x42, 0x42, 0x42, 0x42, + 0x08, 0x08, 0x08, 0x08, 0x23, 0x23, 0x23, 0x23, 0x8F, 0x8F, 0x8F, 0x8F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x18, 0x18, 0x18, 0x18, 0x61, 0x61, 0x61, 0x61, 0x85, 0x85, 0x85, 0x85, 0x14, 0x14, 0x14, 0x14, + 0x51, 0x51, 0x51, 0x51, 0x47, 0x47, 0x47, 0x47, 0x1E, 0x1E, 0x1E, 0x1E, 0x79, 0x79, 0x79, 0x79, + 0xE5, 0xE5, 0xE5, 0xE5, 0x94, 0x94, 0x94, 0x94, 0x51, 0x51, 0x51, 0x51, 0x46, 0x46, 0x46, 0x46, + 0x19, 0x19, 0x19, 0x19, 0x67, 0x67, 0x67, 0x67, 0x9C, 0x9C, 0x9C, 0x9C, 0x71, 0x71, 0x71, 0x71, + 0xC5, 0xC5, 0xC5, 0xC5, 0x17, 0x17, 0x17, 0x17, 0x5F, 0x5F, 0x5F, 0x5F, 0x7E, 0x7E, 0x7E, 0x7E, + 0xFB, 0xFB, 0xFB, 0xFB, 0xED, 0xED, 0xED, 0xED, 0xB4, 0xB4, 0xB4, 0xB4, 0xD2, 0xD2, 0xD2, 0xD2, + 0x48, 0x48, 0x48, 0x48, 0x21, 0x21, 0x21, 0x21, 0x85, 0x85, 0x85, 0x85, 0x16, 0x16, 0x16, 0x16, + 0x59, 0x59, 0x59, 0x59, 0x66, 0x66, 0x66, 0x66, 0x9A, 0x9A, 0x9A, 0x9A, 0x69, 0x69, 0x69, 0x69, + 0xA4, 0xA4, 0xA4, 0xA4, 0x93, 0x93, 0x93, 0x93, 0x4F, 0x4F, 0x4F, 0x4F, 0x3F, 0x3F, 0x3F, 0x3F, + 0xFC, 0xFC, 0xFC, 0xFC, 0xF3, 0xF3, 0xF3, 0xF3, 0xCD, 0xCD, 0xCD, 0xCD, 0x37, 0x37, 0x37, 0x37, + 0xDC, 0xDC, 0xDC, 0xDC, 0x70, 0x70, 0x70, 0x70, 0xC3, 0xC3, 0xC3, 0xC3, 0x0F, 0x0F, 0x0F, 0x0F, + 0x3E, 0x3E, 0x3E, 0x3E, 0xFA, 0xFA, 0xFA, 0xFA, 0xEB, 0xEB, 0xEB, 0xEB, 0xAC, 0xAC, 0xAC, 0xAC, + 0xB3, 0xB3, 0xB3, 0xB3, 0xCC, 0xCC, 0xCC, 0xCC, 0x31, 0x31, 0x31, 0x31, 0xC5, 0xC5, 0xC5, 0xC5, + 0x15, 0x15, 0x15, 0x15, 0x57, 0x57, 0x57, 0x57, 0x5F, 0x5F, 0x5F, 0x5F, 0x7F, 0x7F, 0x7F, 0x7F, + 0xFD, 0xFD, 0xFD, 0xFD, 0xF4, 0xF4, 0xF4, 0xF4, 0xD0, 0xD0, 0xD0, 0xD0, 0x42, 0x42, 0x42, 0x42, + 0x08, 0x08, 0x08, 0x08, 0x23, 0x23, 0x23, 0x23, 0x8F, 0x8F, 0x8F, 0x8F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x06, 0x06, 0x06, 0x06, 0x18, 0x18, 0x18, 0x18, 0x21, 0x21, 0x21, 0x21, 0x05, 0x05, 0x05, 0x05, + 0x14, 0x14, 0x14, 0x14, 0x11, 0x11, 0x11, 0x11, 0x07, 0x07, 0x07, 0x07, 0x1E, 0x1E, 0x1E, 0x1E, + 0x39, 0x39, 0x39, 0x39, 0x25, 0x25, 0x25, 0x25, 0x14, 0x14, 0x14, 0x14, 0x11, 0x11, 0x11, 0x11, + 0x06, 0x06, 0x06, 0x06, 0x19, 0x19, 0x19, 0x19, 0x27, 0x27, 0x27, 0x27, 0x1C, 0x1C, 0x1C, 0x1C, + 0x31, 0x31, 0x31, 0x31, 0x05, 0x05, 0x05, 0x05, 0x17, 0x17, 0x17, 0x17, 0x1F, 0x1F, 0x1F, 0x1F, + 0x3E, 0x3E, 0x3E, 0x3E, 0x3B, 0x3B, 0x3B, 0x3B, 0x2D, 0x2D, 0x2D, 0x2D, 0x34, 0x34, 0x34, 0x34, + 0x12, 0x12, 0x12, 0x12, 0x08, 0x08, 0x08, 0x08, 0x21, 0x21, 0x21, 0x21, 0x05, 0x05, 0x05, 0x05, + 0x16, 0x16, 0x16, 0x16, 0x19, 0x19, 0x19, 0x19, 0x26, 0x26, 0x26, 0x26, 0x1A, 0x1A, 0x1A, 0x1A, + 0x29, 0x29, 0x29, 0x29, 0x24, 0x24, 0x24, 0x24, 0x13, 0x13, 0x13, 0x13, 0x0F, 0x0F, 0x0F, 0x0F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3C, 0x3C, 0x3C, 0x3C, 0x33, 0x33, 0x33, 0x33, 0x0D, 0x0D, 0x0D, 0x0D, + 0x37, 0x37, 0x37, 0x37, 0x1C, 0x1C, 0x1C, 0x1C, 0x30, 0x30, 0x30, 0x30, 0x03, 0x03, 0x03, 0x03, + 0x0F, 0x0F, 0x0F, 0x0F, 0x3E, 0x3E, 0x3E, 0x3E, 0x3A, 0x3A, 0x3A, 0x3A, 0x2B, 0x2B, 0x2B, 0x2B, + 0x2C, 0x2C, 0x2C, 0x2C, 0x33, 0x33, 0x33, 0x33, 0x0C, 0x0C, 0x0C, 0x0C, 0x31, 0x31, 0x31, 0x31, + 0x05, 0x05, 0x05, 0x05, 0x15, 0x15, 0x15, 0x15, 0x17, 0x17, 0x17, 0x17, 0x1F, 0x1F, 0x1F, 0x1F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3D, 0x3D, 0x3D, 0x3D, 0x34, 0x34, 0x34, 0x34, 0x10, 0x10, 0x10, 0x10, + 0x02, 0x02, 0x02, 0x02, 0x08, 0x08, 0x08, 0x08, 0x23, 0x23, 0x23, 0x23, 0x0F, 0x0F, 0x0F, 0x0F, + 0x06, 0x06, 0x06, 0x06, 0x18, 0x18, 0x18, 0x18, 0x21, 0x21, 0x21, 0x21, 0x05, 0x05, 0x05, 0x05, + 0x14, 0x14, 0x14, 0x14, 0x11, 0x11, 0x11, 0x11, 0x07, 0x07, 0x07, 0x07, 0x1E, 0x1E, 0x1E, 0x1E, + 0x39, 0x39, 0x39, 0x39, 0x25, 0x25, 0x25, 0x25, 0x14, 0x14, 0x14, 0x14, 0x11, 0x11, 0x11, 0x11, + 0x06, 0x06, 0x06, 0x06, 0x19, 0x19, 0x19, 0x19, 0x27, 0x27, 0x27, 0x27, 0x1C, 0x1C, 0x1C, 0x1C, + 0x31, 0x31, 0x31, 0x31, 0x05, 0x05, 0x05, 0x05, 0x17, 0x17, 0x17, 0x17, 0x1F, 0x1F, 0x1F, 0x1F, + 0x3E, 0x3E, 0x3E, 0x3E, 0x3B, 0x3B, 0x3B, 0x3B, 0x2D, 0x2D, 0x2D, 0x2D, 0x34, 0x34, 0x34, 0x34, + 0x12, 0x12, 0x12, 0x12, 0x08, 0x08, 0x08, 0x08, 0x21, 0x21, 0x21, 0x21, 0x05, 0x05, 0x05, 0x05, + 0x16, 0x16, 0x16, 0x16, 0x19, 0x19, 0x19, 0x19, 0x26, 0x26, 0x26, 0x26, 0x1A, 0x1A, 0x1A, 0x1A, + 0x29, 0x29, 0x29, 0x29, 0x24, 0x24, 0x24, 0x24, 0x13, 0x13, 0x13, 0x13, 0x0F, 0x0F, 0x0F, 0x0F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3C, 0x3C, 0x3C, 0x3C, 0x33, 0x33, 0x33, 0x33, 0x0D, 0x0D, 0x0D, 0x0D, + 0x37, 0x37, 0x37, 0x37, 0x1C, 0x1C, 0x1C, 0x1C, 0x30, 0x30, 0x30, 0x30, 0x03, 0x03, 0x03, 0x03, + 0x0F, 0x0F, 0x0F, 0x0F, 0x3E, 0x3E, 0x3E, 0x3E, 0x3A, 0x3A, 0x3A, 0x3A, 0x2B, 0x2B, 0x2B, 0x2B, + 0x2C, 0x2C, 0x2C, 0x2C, 0x33, 0x33, 0x33, 0x33, 0x0C, 0x0C, 0x0C, 0x0C, 0x31, 0x31, 0x31, 0x31, + 0x05, 0x05, 0x05, 0x05, 0x15, 0x15, 0x15, 0x15, 0x17, 0x17, 0x17, 0x17, 0x1F, 0x1F, 0x1F, 0x1F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3D, 0x3D, 0x3D, 0x3D, 0x34, 0x34, 0x34, 0x34, 0x10, 0x10, 0x10, 0x10, + 0x02, 0x02, 0x02, 0x02, 0x08, 0x08, 0x08, 0x08, 0x23, 0x23, 0x23, 0x23, 0x0F, 0x0F, 0x0F, 0x0F, + 0x0F, 0x0F, 0x00, 0x0F, 0x0F, 0x00, 0x0F, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x0F, 0x00, 0x0F, 0x0F, + 0x0F, 0x0F, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x0F, 0x00, 0x00, 0x0F, 0x00, + 0x0F, 0x0F, 0x0F, 0x00, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x0F, 0x00, 0x0F, + 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x00, + 0x0F, 0x0F, 0x00, 0x0F, 0x0F, 0x00, 0x0F, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x0F, 0x00, 0x0F, 0x0F, + 0x0F, 0x0F, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x0F, 0x00, 0x00, 0x0F, 0x00, + 0x0F, 0x0F, 0x0F, 0x00, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x0F, 0x00, 0x0F, + 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, + 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x00, + 0x0F, 0x0F, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x00, 0x0F, 0x0F, 0x0F, + 0x0F, 0x0F, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x00, + 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, + 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x00, + 0x0F, 0x0F, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x00, 0x0F, 0x0F, 0x0F, + 0x0F, 0x0F, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, + 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, + 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, + 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, + 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, + 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, + 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, + 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, + 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x40, 0x40, 0x40, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x20, 0x20, 0x20, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, + 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x04, 0x04, 0x04, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x40, 0x40, 0x40, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x20, 0x20, 0x20, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, + 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x04, 0x04, 0x04, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xCC, 0xCC, 0xCC, 0xCC, 0x33, 0x33, 0x33, 0x33, + 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, + 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, + 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, + 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, + 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, + 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, + 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, + 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, + 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, + 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, + 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, + 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, + 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, + 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, + 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, + 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03, 0x0A, 0x05, 0x0C, 0x03 +}; +static_assert(sizeof(EmcRamTrainingPatternData) % sizeof(EmcRamTrainingPattern) == 0); + +ALWAYS_INLINE const EmcRamTrainingPattern *GetEmcRamTrainingPattern() { + return reinterpret_cast<const EmcRamTrainingPattern *>(EmcRamTrainingPatternData); +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_tables_erista.inc b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_tables_erista.inc new file mode 100644 index 00000000..6fc664d4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_tables_erista.inc @@ -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 <http://www.gnu.org/licenses/>. + */ + +constexpr const u8 T210SdevEmcDvfsTableS6gb01[] = { + #embed "../../mtc_tables/combined/T210SdevEmcDvfsTableS6gb01/table.bin" +}; + +constexpr const u8 T210SdevEmcDvfsTableH4gb01[] = { + #embed "../../mtc_tables/combined/T210SdevEmcDvfsTableH4gb01/table.bin" +}; + +constexpr const u8 T210SdevEmcDvfsTableS4gb01[] = { + #embed "../../mtc_tables/combined/T210SdevEmcDvfsTableS4gb01/table.bin" +}; + diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_tables_mariko.inc b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_tables_mariko.inc new file mode 100644 index 00000000..de7f89df --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_tables_mariko.inc @@ -0,0 +1,84 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +constexpr const u8 T210b01SdevEmcDvfsTableM4gb03[] = { + #embed "../../mtc_tables/combined/T210b01SdevEmcDvfsTableM4gb03/table.bin" +}; + +constexpr const u8 T210b01SdevEmcDvfsTableS4gb01[] = { + #embed "../../mtc_tables/combined/T210b01SdevEmcDvfsTableS4gb01/table.bin" +}; + +constexpr const u8 T210b01SdevEmcDvfsTableS1z4gb01[] = { + #embed "../../mtc_tables/combined/T210b01SdevEmcDvfsTableS1z4gb01/table.bin" +}; + +constexpr const u8 T210b01SdevEmcDvfsTableS1y8gbY01[] = { + #embed "../../mtc_tables/combined/T210b01SdevEmcDvfsTableS1y8gbY01/table.bin" +}; + +constexpr const u8 T210b01SdevEmcDvfsTableM1a4gb01[] = { + #embed "../../mtc_tables/combined/T210b01SdevEmcDvfsTableM1a4gb01/table.bin" +}; + +constexpr const u8 T210b01SdevEmcDvfsTableH4gb03[] = { + #embed "../../mtc_tables/combined/T210b01SdevEmcDvfsTableH4gb03/table.bin" +}; + +constexpr const u8 T210b01SdevEmcDvfsTableS1y4gbX03[] = { + #embed "../../mtc_tables/combined/T210b01SdevEmcDvfsTableS1y4gbX03/table.bin" +}; + +constexpr const u8 T210b01SdevEmcDvfsTableM1y4gb01[] = { + #embed "../../mtc_tables/combined/T210b01SdevEmcDvfsTableM1y4gb01/table.bin" +}; + +constexpr const u8 T210b01SdevEmcDvfsTableS1y4gb01[] = { + #embed "../../mtc_tables/combined/T210b01SdevEmcDvfsTableS1y4gb01/table.bin" +}; + +constexpr const u8 T210b01SdevEmcDvfsTableH1y4gb01[] = { + #embed "../../mtc_tables/combined/T210b01SdevEmcDvfsTableH1y4gb01/table.bin" +}; + +constexpr const u8 T210b01SdevEmcDvfsTableS4gb03[] = { + #embed "../../mtc_tables/combined/T210b01SdevEmcDvfsTableS4gb03/table.bin" +}; + +constexpr const u8 T210b01SdevEmcDvfsTableS4gbY01[] = { + #embed "../../mtc_tables/combined/T210b01SdevEmcDvfsTableS4gbY01/table.bin" +}; + +constexpr const u8 T210b01SdevEmcDvfsTableS1y8gb04[] = { + #embed "../../mtc_tables/combined/T210b01SdevEmcDvfsTableS1y8gb04/table.bin" +}; + +constexpr const u8 T210b01SdevEmcDvfsTableH1a4gb01[] = { + #embed "../../mtc_tables/combined/T210b01SdevEmcDvfsTableH1a4gb01/table.bin" +}; + +constexpr const u8 T210b01SdevEmcDvfsTableS1y4gbY01[] = { + #embed "../../mtc_tables/combined/T210b01SdevEmcDvfsTableS1y4gbY01/table.bin" +}; + +constexpr const u8 T210b01SdevEmcDvfsTableS8gb03[] = { + #embed "../../mtc_tables/combined/T210b01SdevEmcDvfsTableS8gb03/table.bin" +}; + +constexpr const u8 T210b01SdevEmcDvfsTableS1y8gbX03[] = { + #embed "../../mtc_tables/combined/T210b01SdevEmcDvfsTableS1y8gbX03/table.bin" +}; + diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_timing_table_common.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_timing_table_common.hpp new file mode 100644 index 00000000..5cfc24ab --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_timing_table_common.hpp @@ -0,0 +1,527 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> + +namespace ams::nxboot { + + #define MC_BASE (0x70019000) + #define EMC_BASE (0x7001B000) + #define EMC0_BASE (0x7001E000) + #define EMC1_BASE (0x7001F000) + + enum { + PLLM_OUT0 = CLK_RST_CONTROLLER_CLK_SOURCE_EMC_EMC_2X_CLK_SRC_PLLM_OUT0, + PLLC_OUT0 = CLK_RST_CONTROLLER_CLK_SOURCE_EMC_EMC_2X_CLK_SRC_PLLC_OUT0, + PLLP_OUT0 = CLK_RST_CONTROLLER_CLK_SOURCE_EMC_EMC_2X_CLK_SRC_PLLP_OUT0, + CLK_M = CLK_RST_CONTROLLER_CLK_SOURCE_EMC_EMC_2X_CLK_SRC_CLK_M, + PLLM_UD = CLK_RST_CONTROLLER_CLK_SOURCE_EMC_EMC_2X_CLK_SRC_PLLM_UD, + PLLMB_UD = CLK_RST_CONTROLLER_CLK_SOURCE_EMC_EMC_2X_CLK_SRC_PLLMB_UD, + PLLMB_OUT0 = CLK_RST_CONTROLLER_CLK_SOURCE_EMC_EMC_2X_CLK_SRC_PLLMB_OUT0, + PLLP_UD = CLK_RST_CONTROLLER_CLK_SOURCE_EMC_EMC_2X_CLK_SRC_PLLP_UD + }; + + enum { + ONE_RANK = 1, + TWO_RANK = 2, + }; + + enum { + DLL_OFF = 0, + DLL_ON = 1, + }; + + enum { + AUTO_PD = 0, + MAN_SR = 2, + }; + + enum { + NO_TRAINING = (0 << 0), + CA_TRAINING = (1 << 0), + CA_VREF_TRAINING = (1 << 1), + QUSE_TRAINING = (1 << 2), + QUSE_VREF_TRAINING = (1 << 3), + WRITE_TRAINING = (1 << 4), + WRITE_VREF_TRAINING = (1 << 5), + READ_TRAINING = (1 << 6), + READ_VREF_TRAINING = (1 << 7), + TRAIN_SECOND_RANK = (1 << 8), + BIT_LEVEL_TRAINING = (1 << 9), + }; + + enum { + DRAM_TYPE_DDR4 = EMC_FBIO_CFG5_DRAM_TYPE_DDR4, + DRAM_TYPE_LPDDR4 = EMC_FBIO_CFG5_DRAM_TYPE_LPDDR4, + DRAM_TYPE_LPDDR2 = EMC_FBIO_CFG5_DRAM_TYPE_LPDDR2, + DRAM_TYPE_DDR2 = EMC_FBIO_CFG5_DRAM_TYPE_DDR2 + }; + + enum { + ASSEMBLY = EMC_DBG_WRITE_MUX_ASSEMBLY, + ACTIVE = EMC_DBG_WRITE_MUX_ACTIVE, + }; + + enum { + DVFS_SEQUENCE = 1, + WRITE_TRAINING_SEQUENCE = 2, + PERIODIC_TRAINING_SEQUENCE = 3, + DVFS_PT1 = 10, + DVFS_UPDATE = 11, + TRAINING_PT1 = 12, + TRAINING_UPDATE = 13, + PERIODIC_TRAINING_UPDATE = 14, + }; + + /* + * Do arithmetic in fixed point. + */ + #define MOVAVG_PRECISION_FACTOR 100 + + /* + * The division portion of the average operation. + */ + #define __AVERAGE_PTFV(dev) \ + ({ dst_timing->ptfv_dqsosc_movavg_##dev = \ + dst_timing->ptfv_dqsosc_movavg_##dev / \ + dst_timing->ptfv_dvfs_samples; }) + + /* + * The division portion of the average write operation. + */ + #define __AVERAGE_WRITE_PTFV(dev) \ + ({ dst_timing->ptfv_dqsosc_movavg_##dev = \ + dst_timing->ptfv_dqsosc_movavg_##dev / \ + dst_timing->ptfv_write_samples; }) + + /* + * Convert val to fixed point and add it to the temporary average. + */ + #define __INCREMENT_PTFV(dev, val) \ + ({ dst_timing->ptfv_dqsosc_movavg_##dev += \ + ((val) * MOVAVG_PRECISION_FACTOR); }) + + /* + * Convert a moving average back to integral form and return the value. + */ + #define __MOVAVG_AC(timing, dev) \ + ((timing)->ptfv_dqsosc_movavg_##dev / \ + MOVAVG_PRECISION_FACTOR) + + /* Weighted update. */ + #define __WEIGHTED_UPDATE_PTFV(dev, nval) \ + do { \ + dst_timing->ptfv_dqsosc_movavg_##dev = \ + ((nval * MOVAVG_PRECISION_FACTOR) + \ + (dst_timing->ptfv_dqsosc_movavg_##dev * \ + dst_timing->ptfv_movavg_weight)) / \ + (dst_timing->ptfv_movavg_weight + 1); \ + } while (0) + + /* Access a particular average. */ + #define __MOVAVG(timing, dev) \ + ((timing)->ptfv_dqsosc_movavg_##dev) + + #define FOREACH_PER_CHANNEL_BURST_REG(HANDLER) \ + HANDLER(EMC0, EMC_MRW10, emc0_mrw10) \ + HANDLER(EMC1, EMC_MRW10, emc1_mrw10) \ + HANDLER(EMC0, EMC_MRW11, emc0_mrw11) \ + HANDLER(EMC1, EMC_MRW11, emc1_mrw11) \ + HANDLER(EMC0, EMC_MRW12, emc0_mrw12) \ + HANDLER(EMC1, EMC_MRW12, emc1_mrw12) \ + HANDLER(EMC0, EMC_MRW13, emc0_mrw13) \ + HANDLER(EMC1, EMC_MRW13, emc1_mrw13) \ + + #define FOREACH_TRIM_REG(HANDLER) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_0, emc_pmacro_ib_ddll_long_dqs_rank0_0) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_1, emc_pmacro_ib_ddll_long_dqs_rank0_1) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_2, emc_pmacro_ib_ddll_long_dqs_rank0_2) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_3, emc_pmacro_ib_ddll_long_dqs_rank0_3) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_0, emc_pmacro_ib_ddll_long_dqs_rank1_0) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_1, emc_pmacro_ib_ddll_long_dqs_rank1_1) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_2, emc_pmacro_ib_ddll_long_dqs_rank1_2) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_3, emc_pmacro_ib_ddll_long_dqs_rank1_3) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_0, emc_pmacro_ib_ddll_short_dq_rank0_byte0_0) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_1, emc_pmacro_ib_ddll_short_dq_rank0_byte0_1) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_2, emc_pmacro_ib_ddll_short_dq_rank0_byte0_2) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_0, emc_pmacro_ib_ddll_short_dq_rank0_byte1_0) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_1, emc_pmacro_ib_ddll_short_dq_rank0_byte1_1) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_2, emc_pmacro_ib_ddll_short_dq_rank0_byte1_2) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_0, emc_pmacro_ib_ddll_short_dq_rank0_byte2_0) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_1, emc_pmacro_ib_ddll_short_dq_rank0_byte2_1) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_2, emc_pmacro_ib_ddll_short_dq_rank0_byte2_2) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_0, emc_pmacro_ib_ddll_short_dq_rank0_byte3_0) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_1, emc_pmacro_ib_ddll_short_dq_rank0_byte3_1) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_2, emc_pmacro_ib_ddll_short_dq_rank0_byte3_2) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_0, emc_pmacro_ib_ddll_short_dq_rank0_byte4_0) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_1, emc_pmacro_ib_ddll_short_dq_rank0_byte4_1) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_2, emc_pmacro_ib_ddll_short_dq_rank0_byte4_2) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_0, emc_pmacro_ib_ddll_short_dq_rank0_byte5_0) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_1, emc_pmacro_ib_ddll_short_dq_rank0_byte5_1) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_2, emc_pmacro_ib_ddll_short_dq_rank0_byte5_2) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_0, emc_pmacro_ib_ddll_short_dq_rank0_byte6_0) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_1, emc_pmacro_ib_ddll_short_dq_rank0_byte6_1) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_2, emc_pmacro_ib_ddll_short_dq_rank0_byte6_2) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_0, emc_pmacro_ib_ddll_short_dq_rank0_byte7_0) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_1, emc_pmacro_ib_ddll_short_dq_rank0_byte7_1) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_2, emc_pmacro_ib_ddll_short_dq_rank0_byte7_2) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_0, emc_pmacro_ib_ddll_short_dq_rank1_byte0_0) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_1, emc_pmacro_ib_ddll_short_dq_rank1_byte0_1) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_2, emc_pmacro_ib_ddll_short_dq_rank1_byte0_2) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_0, emc_pmacro_ib_ddll_short_dq_rank1_byte1_0) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_1, emc_pmacro_ib_ddll_short_dq_rank1_byte1_1) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_2, emc_pmacro_ib_ddll_short_dq_rank1_byte1_2) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_0, emc_pmacro_ib_ddll_short_dq_rank1_byte2_0) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_1, emc_pmacro_ib_ddll_short_dq_rank1_byte2_1) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_2, emc_pmacro_ib_ddll_short_dq_rank1_byte2_2) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_0, emc_pmacro_ib_ddll_short_dq_rank1_byte3_0) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_1, emc_pmacro_ib_ddll_short_dq_rank1_byte3_1) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_2, emc_pmacro_ib_ddll_short_dq_rank1_byte3_2) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_0, emc_pmacro_ib_ddll_short_dq_rank1_byte4_0) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_1, emc_pmacro_ib_ddll_short_dq_rank1_byte4_1) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_2, emc_pmacro_ib_ddll_short_dq_rank1_byte4_2) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_0, emc_pmacro_ib_ddll_short_dq_rank1_byte5_0) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_1, emc_pmacro_ib_ddll_short_dq_rank1_byte5_1) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_2, emc_pmacro_ib_ddll_short_dq_rank1_byte5_2) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_0, emc_pmacro_ib_ddll_short_dq_rank1_byte6_0) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_1, emc_pmacro_ib_ddll_short_dq_rank1_byte6_1) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_2, emc_pmacro_ib_ddll_short_dq_rank1_byte6_2) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_0, emc_pmacro_ib_ddll_short_dq_rank1_byte7_0) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_1, emc_pmacro_ib_ddll_short_dq_rank1_byte7_1) \ + HANDLER(EMC, EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_2, emc_pmacro_ib_ddll_short_dq_rank1_byte7_2) \ + HANDLER(EMC, EMC_PMACRO_IB_VREF_DQS_0, emc_pmacro_ib_vref_dqs_0) \ + HANDLER(EMC, EMC_PMACRO_IB_VREF_DQS_1, emc_pmacro_ib_vref_dqs_1) \ + HANDLER(EMC, EMC_PMACRO_IB_VREF_DQ_0, emc_pmacro_ib_vref_dq_0) \ + HANDLER(EMC, EMC_PMACRO_IB_VREF_DQ_1, emc_pmacro_ib_vref_dq_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0, emc_pmacro_ob_ddll_long_dq_rank0_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1, emc_pmacro_ob_ddll_long_dq_rank0_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2, emc_pmacro_ob_ddll_long_dq_rank0_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3, emc_pmacro_ob_ddll_long_dq_rank0_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_4, emc_pmacro_ob_ddll_long_dq_rank0_4) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_5, emc_pmacro_ob_ddll_long_dq_rank0_5) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0, emc_pmacro_ob_ddll_long_dq_rank1_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1, emc_pmacro_ob_ddll_long_dq_rank1_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2, emc_pmacro_ob_ddll_long_dq_rank1_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3, emc_pmacro_ob_ddll_long_dq_rank1_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_0, emc_pmacro_ob_ddll_short_dq_rank0_byte0_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_1, emc_pmacro_ob_ddll_short_dq_rank0_byte0_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_2, emc_pmacro_ob_ddll_short_dq_rank0_byte0_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_0, emc_pmacro_ob_ddll_short_dq_rank0_byte1_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_1, emc_pmacro_ob_ddll_short_dq_rank0_byte1_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_2, emc_pmacro_ob_ddll_short_dq_rank0_byte1_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_0, emc_pmacro_ob_ddll_short_dq_rank0_byte2_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_1, emc_pmacro_ob_ddll_short_dq_rank0_byte2_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_2, emc_pmacro_ob_ddll_short_dq_rank0_byte2_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_0, emc_pmacro_ob_ddll_short_dq_rank0_byte3_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_1, emc_pmacro_ob_ddll_short_dq_rank0_byte3_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_2, emc_pmacro_ob_ddll_short_dq_rank0_byte3_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_0, emc_pmacro_ob_ddll_short_dq_rank0_byte4_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_1, emc_pmacro_ob_ddll_short_dq_rank0_byte4_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_2, emc_pmacro_ob_ddll_short_dq_rank0_byte4_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_0, emc_pmacro_ob_ddll_short_dq_rank0_byte5_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_1, emc_pmacro_ob_ddll_short_dq_rank0_byte5_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_2, emc_pmacro_ob_ddll_short_dq_rank0_byte5_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_0, emc_pmacro_ob_ddll_short_dq_rank0_byte6_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_1, emc_pmacro_ob_ddll_short_dq_rank0_byte6_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_2, emc_pmacro_ob_ddll_short_dq_rank0_byte6_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_0, emc_pmacro_ob_ddll_short_dq_rank0_byte7_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_1, emc_pmacro_ob_ddll_short_dq_rank0_byte7_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_2, emc_pmacro_ob_ddll_short_dq_rank0_byte7_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_0, emc_pmacro_ob_ddll_short_dq_rank0_cmd0_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_1, emc_pmacro_ob_ddll_short_dq_rank0_cmd0_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_2, emc_pmacro_ob_ddll_short_dq_rank0_cmd0_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_0, emc_pmacro_ob_ddll_short_dq_rank0_cmd1_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_1, emc_pmacro_ob_ddll_short_dq_rank0_cmd1_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_2, emc_pmacro_ob_ddll_short_dq_rank0_cmd1_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_0, emc_pmacro_ob_ddll_short_dq_rank0_cmd2_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_1, emc_pmacro_ob_ddll_short_dq_rank0_cmd2_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_2, emc_pmacro_ob_ddll_short_dq_rank0_cmd2_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_0, emc_pmacro_ob_ddll_short_dq_rank0_cmd3_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_1, emc_pmacro_ob_ddll_short_dq_rank0_cmd3_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_2, emc_pmacro_ob_ddll_short_dq_rank0_cmd3_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_0, emc_pmacro_ob_ddll_short_dq_rank1_byte0_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_1, emc_pmacro_ob_ddll_short_dq_rank1_byte0_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_2, emc_pmacro_ob_ddll_short_dq_rank1_byte0_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_0, emc_pmacro_ob_ddll_short_dq_rank1_byte1_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_1, emc_pmacro_ob_ddll_short_dq_rank1_byte1_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_2, emc_pmacro_ob_ddll_short_dq_rank1_byte1_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_0, emc_pmacro_ob_ddll_short_dq_rank1_byte2_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_1, emc_pmacro_ob_ddll_short_dq_rank1_byte2_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_2, emc_pmacro_ob_ddll_short_dq_rank1_byte2_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_0, emc_pmacro_ob_ddll_short_dq_rank1_byte3_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_1, emc_pmacro_ob_ddll_short_dq_rank1_byte3_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_2, emc_pmacro_ob_ddll_short_dq_rank1_byte3_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_0, emc_pmacro_ob_ddll_short_dq_rank1_byte4_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_1, emc_pmacro_ob_ddll_short_dq_rank1_byte4_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_2, emc_pmacro_ob_ddll_short_dq_rank1_byte4_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_0, emc_pmacro_ob_ddll_short_dq_rank1_byte5_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_1, emc_pmacro_ob_ddll_short_dq_rank1_byte5_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_2, emc_pmacro_ob_ddll_short_dq_rank1_byte5_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_0, emc_pmacro_ob_ddll_short_dq_rank1_byte6_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_1, emc_pmacro_ob_ddll_short_dq_rank1_byte6_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_2, emc_pmacro_ob_ddll_short_dq_rank1_byte6_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_0, emc_pmacro_ob_ddll_short_dq_rank1_byte7_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_1, emc_pmacro_ob_ddll_short_dq_rank1_byte7_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_2, emc_pmacro_ob_ddll_short_dq_rank1_byte7_2) \ + HANDLER(EMC, EMC_PMACRO_QUSE_DDLL_RANK0_0, emc_pmacro_quse_ddll_rank0_0) \ + HANDLER(EMC, EMC_PMACRO_QUSE_DDLL_RANK0_1, emc_pmacro_quse_ddll_rank0_1) \ + HANDLER(EMC, EMC_PMACRO_QUSE_DDLL_RANK0_2, emc_pmacro_quse_ddll_rank0_2) \ + HANDLER(EMC, EMC_PMACRO_QUSE_DDLL_RANK0_3, emc_pmacro_quse_ddll_rank0_3) \ + HANDLER(EMC, EMC_PMACRO_QUSE_DDLL_RANK1_0, emc_pmacro_quse_ddll_rank1_0) \ + HANDLER(EMC, EMC_PMACRO_QUSE_DDLL_RANK1_1, emc_pmacro_quse_ddll_rank1_1) \ + HANDLER(EMC, EMC_PMACRO_QUSE_DDLL_RANK1_2, emc_pmacro_quse_ddll_rank1_2) \ + HANDLER(EMC, EMC_PMACRO_QUSE_DDLL_RANK1_3, emc_pmacro_quse_ddll_rank1_3) + + #define FOREACH_PER_CHANNEL_TRIM_REG(HANDLER) \ + HANDLER(EMC0, EMC_CMD_BRLSHFT_0, emc0_cmd_brlshft_0) \ + HANDLER(EMC1, EMC_CMD_BRLSHFT_1, emc1_cmd_brlshft_1) \ + HANDLER(EMC0, EMC_DATA_BRLSHFT_0, emc0_data_brlshft_0) \ + HANDLER(EMC1, EMC_DATA_BRLSHFT_0, emc1_data_brlshft_0) \ + HANDLER(EMC0, EMC_DATA_BRLSHFT_1, emc0_data_brlshft_1) \ + HANDLER(EMC1, EMC_DATA_BRLSHFT_1, emc1_data_brlshft_1) \ + HANDLER(EMC0, EMC_QUSE_BRLSHFT_0, emc0_quse_brlshft_0) \ + HANDLER(EMC1, EMC_QUSE_BRLSHFT_1, emc1_quse_brlshft_1) \ + HANDLER(EMC0, EMC_QUSE_BRLSHFT_2, emc0_quse_brlshft_2) \ + HANDLER(EMC1, EMC_QUSE_BRLSHFT_3, emc1_quse_brlshft_3) + + #define FOREACH_PER_CHANNEL_VREF_REG(HANDLER) \ + HANDLER(EMC0, EMC_TRAINING_OPT_DQS_IB_VREF_RANK0, emc0_training_opt_dqs_ib_vref_rank0) \ + HANDLER(EMC1, EMC_TRAINING_OPT_DQS_IB_VREF_RANK0, emc1_training_opt_dqs_ib_vref_rank0) \ + HANDLER(EMC0, EMC_TRAINING_OPT_DQS_IB_VREF_RANK1, emc0_training_opt_dqs_ib_vref_rank1) \ + HANDLER(EMC1, EMC_TRAINING_OPT_DQS_IB_VREF_RANK1, emc1_training_opt_dqs_ib_vref_rank1) + + #define FOREACH_PER_CHANNEL_TRAINING_MOD_REG(HANDLER) \ + HANDLER(EMC0, EMC_TRAINING_RW_OFFSET_IB_BYTE0, emc0_training_rw_offset_ib_byte0) \ + HANDLER(EMC1, EMC_TRAINING_RW_OFFSET_IB_BYTE0, emc1_training_rw_offset_ib_byte0) \ + HANDLER(EMC0, EMC_TRAINING_RW_OFFSET_IB_BYTE1, emc0_training_rw_offset_ib_byte1) \ + HANDLER(EMC1, EMC_TRAINING_RW_OFFSET_IB_BYTE1, emc1_training_rw_offset_ib_byte1) \ + HANDLER(EMC0, EMC_TRAINING_RW_OFFSET_IB_BYTE2, emc0_training_rw_offset_ib_byte2) \ + HANDLER(EMC1, EMC_TRAINING_RW_OFFSET_IB_BYTE2, emc1_training_rw_offset_ib_byte2) \ + HANDLER(EMC0, EMC_TRAINING_RW_OFFSET_IB_BYTE3, emc0_training_rw_offset_ib_byte3) \ + HANDLER(EMC1, EMC_TRAINING_RW_OFFSET_IB_BYTE3, emc1_training_rw_offset_ib_byte3) \ + HANDLER(EMC0, EMC_TRAINING_RW_OFFSET_IB_MISC, emc0_training_rw_offset_ib_misc) \ + HANDLER(EMC1, EMC_TRAINING_RW_OFFSET_IB_MISC, emc1_training_rw_offset_ib_misc) \ + HANDLER(EMC0, EMC_TRAINING_RW_OFFSET_OB_BYTE0, emc0_training_rw_offset_ob_byte0) \ + HANDLER(EMC1, EMC_TRAINING_RW_OFFSET_OB_BYTE0, emc1_training_rw_offset_ob_byte0) \ + HANDLER(EMC0, EMC_TRAINING_RW_OFFSET_OB_BYTE1, emc0_training_rw_offset_ob_byte1) \ + HANDLER(EMC1, EMC_TRAINING_RW_OFFSET_OB_BYTE1, emc1_training_rw_offset_ob_byte1) \ + HANDLER(EMC0, EMC_TRAINING_RW_OFFSET_OB_BYTE2, emc0_training_rw_offset_ob_byte2) \ + HANDLER(EMC1, EMC_TRAINING_RW_OFFSET_OB_BYTE2, emc1_training_rw_offset_ob_byte2) \ + HANDLER(EMC0, EMC_TRAINING_RW_OFFSET_OB_BYTE3, emc0_training_rw_offset_ob_byte3) \ + HANDLER(EMC1, EMC_TRAINING_RW_OFFSET_OB_BYTE3, emc1_training_rw_offset_ob_byte3) \ + HANDLER(EMC0, EMC_TRAINING_RW_OFFSET_OB_MISC, emc0_training_rw_offset_ob_misc) \ + HANDLER(EMC1, EMC_TRAINING_RW_OFFSET_OB_MISC, emc1_training_rw_offset_ob_misc) + + #define FOREACH_BURST_MC_REG(HANDLER) \ + HANDLER(MC, MC_EMEM_ARB_CFG, mc_emem_arb_cfg) \ + HANDLER(MC, MC_EMEM_ARB_OUTSTANDING_REQ, mc_emem_arb_outstanding_req) \ + HANDLER(MC, MC_EMEM_ARB_REFPB_HP_CTRL, mc_emem_arb_refpb_hp_ctrl) \ + HANDLER(MC, MC_EMEM_ARB_REFPB_BANK_CTRL, mc_emem_arb_refpb_bank_ctrl) \ + HANDLER(MC, MC_EMEM_ARB_TIMING_RCD, mc_emem_arb_timing_rcd) \ + HANDLER(MC, MC_EMEM_ARB_TIMING_RP, mc_emem_arb_timing_rp) \ + HANDLER(MC, MC_EMEM_ARB_TIMING_RC, mc_emem_arb_timing_rc) \ + HANDLER(MC, MC_EMEM_ARB_TIMING_RAS, mc_emem_arb_timing_ras) \ + HANDLER(MC, MC_EMEM_ARB_TIMING_FAW, mc_emem_arb_timing_faw) \ + HANDLER(MC, MC_EMEM_ARB_TIMING_RRD, mc_emem_arb_timing_rrd) \ + HANDLER(MC, MC_EMEM_ARB_TIMING_RAP2PRE, mc_emem_arb_timing_rap2pre) \ + HANDLER(MC, MC_EMEM_ARB_TIMING_WAP2PRE, mc_emem_arb_timing_wap2pre) \ + HANDLER(MC, MC_EMEM_ARB_TIMING_R2R, mc_emem_arb_timing_r2r) \ + HANDLER(MC, MC_EMEM_ARB_TIMING_W2W, mc_emem_arb_timing_w2w) \ + HANDLER(MC, MC_EMEM_ARB_TIMING_R2W, mc_emem_arb_timing_r2w) \ + HANDLER(MC, MC_EMEM_ARB_TIMING_CCDMW, mc_emem_arb_timing_ccdmw) \ + HANDLER(MC, MC_EMEM_ARB_TIMING_W2R, mc_emem_arb_timing_w2r) \ + HANDLER(MC, MC_EMEM_ARB_TIMING_RFCPB, mc_emem_arb_timing_rfcpb) \ + HANDLER(MC, MC_EMEM_ARB_DA_TURNS, mc_emem_arb_da_turns) \ + HANDLER(MC, MC_EMEM_ARB_DA_COVERS, mc_emem_arb_da_covers) \ + HANDLER(MC, MC_EMEM_ARB_MISC0, mc_emem_arb_misc0) \ + HANDLER(MC, MC_EMEM_ARB_MISC1, mc_emem_arb_misc1) \ + HANDLER(MC, MC_EMEM_ARB_MISC2, mc_emem_arb_misc2) \ + HANDLER(MC, MC_EMEM_ARB_RING1_THROTTLE, mc_emem_arb_ring1_throttle) \ + HANDLER(MC, MC_EMEM_ARB_DHYST_CTRL, mc_emem_arb_dhyst_ctrl) \ + HANDLER(MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_0, mc_emem_arb_dhyst_timeout_util_0) \ + HANDLER(MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_1, mc_emem_arb_dhyst_timeout_util_1) \ + HANDLER(MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_2, mc_emem_arb_dhyst_timeout_util_2) \ + HANDLER(MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_3, mc_emem_arb_dhyst_timeout_util_3) \ + HANDLER(MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_4, mc_emem_arb_dhyst_timeout_util_4) \ + HANDLER(MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_5, mc_emem_arb_dhyst_timeout_util_5) \ + HANDLER(MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_6, mc_emem_arb_dhyst_timeout_util_6) \ + HANDLER(MC, MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_7, mc_emem_arb_dhyst_timeout_util_7) + + #define FOREACH_LA_SCALE_REG(HANDLER) \ + HANDLER(MC, MC_MLL_MPCORER_PTSA_RATE, mc_mll_mpcorer_ptsa_rate) \ + HANDLER(MC, MC_FTOP_PTSA_RATE, mc_ftop_ptsa_rate) \ + HANDLER(MC, MC_PTSA_GRANT_DECREMENT, mc_ptsa_grant_decrement) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_XUSB_0, mc_latency_allowance_xusb_0) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_XUSB_1, mc_latency_allowance_xusb_1) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_TSEC_0, mc_latency_allowance_tsec_0) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_SDMMCA_0, mc_latency_allowance_sdmmca_0) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_SDMMCAA_0, mc_latency_allowance_sdmmcaa_0) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_SDMMC_0, mc_latency_allowance_sdmmc_0) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_SDMMCAB_0, mc_latency_allowance_sdmmcab_0) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_PPCS_0, mc_latency_allowance_ppcs_0) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_PPCS_1, mc_latency_allowance_ppcs_1) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_MPCORE_0, mc_latency_allowance_mpcore_0) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_HC_0, mc_latency_allowance_hc_0) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_HC_1, mc_latency_allowance_hc_1) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_AVPC_0, mc_latency_allowance_avpc_0) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_GPU_0, mc_latency_allowance_gpu_0) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_GPU2_0, mc_latency_allowance_gpu2_0) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_NVENC_0, mc_latency_allowance_nvenc_0) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_NVDEC_0, mc_latency_allowance_nvdec_0) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_VIC_0, mc_latency_allowance_vic_0) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_VI2_0, mc_latency_allowance_vi2_0) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_ISP2_0, mc_latency_allowance_isp2_0) \ + HANDLER(MC, MC_LATENCY_ALLOWANCE_ISP2_1, mc_latency_allowance_isp2_1) + + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE1_SHIFT \ + 16 + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE1_MASK \ + 0x3ff << \ + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE1_SHIFT + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE0_SHIFT \ + 0 + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE0_MASK \ + 0x3ff << \ + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_OB_DDLL_LONG_DQ_RANK0_BYTE0_SHIFT + + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE3_SHIFT \ + 16 + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE3_MASK \ + 0x3ff << \ + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE3_SHIFT + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE2_SHIFT \ + 0 + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE2_MASK \ + 0x3ff << \ + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_OB_DDLL_LONG_DQ_RANK0_BYTE2_SHIFT + + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE5_SHIFT \ + 16 + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE5_MASK \ + 0x3ff << \ + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE5_SHIFT + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE4_SHIFT \ + 0 + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE4_MASK \ + 0x3ff << \ + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_OB_DDLL_LONG_DQ_RANK0_BYTE4_SHIFT + + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE7_SHIFT \ + 16 + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE7_MASK \ + 0x3ff << \ + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE7_SHIFT + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE6_SHIFT \ + 0 + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE6_MASK \ + 0x3ff << \ + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_OB_DDLL_LONG_DQ_RANK0_BYTE6_SHIFT + + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE1_SHIFT \ + 16 + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE1_MASK \ + 0x3ff << \ + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE1_SHIFT + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE0_SHIFT \ + 0 + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE0_MASK \ + 0x3ff << \ + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_OB_DDLL_LONG_DQ_RANK1_BYTE0_SHIFT + + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE3_SHIFT \ + 16 + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE3_MASK \ + 0x3ff << \ + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE3_SHIFT + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE2_SHIFT \ + 0 + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE2_MASK \ + 0x3ff << \ + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_OB_DDLL_LONG_DQ_RANK1_BYTE2_SHIFT + + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE5_SHIFT \ + 16 + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE5_MASK \ + 0x3ff << \ + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE5_SHIFT + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE4_SHIFT \ + 0 + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE4_MASK \ + 0x3ff << \ + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_OB_DDLL_LONG_DQ_RANK1_BYTE4_SHIFT + + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE7_SHIFT \ + 16 + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE7_MASK \ + 0x3ff << \ + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE7_SHIFT + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE6_SHIFT \ + 0 + #define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE6_MASK \ + 0x3ff << \ + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_OB_DDLL_LONG_DQ_RANK1_BYTE6_SHIFT + + #define EMC_DATA_BRLSHFT_0_RANK0_BYTE7_DATA_BRLSHFT_SHIFT 21 + #define EMC_DATA_BRLSHFT_0_RANK0_BYTE7_DATA_BRLSHFT_MASK \ + (0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE7_DATA_BRLSHFT_SHIFT) + #define EMC_DATA_BRLSHFT_0_RANK0_BYTE6_DATA_BRLSHFT_SHIFT 18 + #define EMC_DATA_BRLSHFT_0_RANK0_BYTE6_DATA_BRLSHFT_MASK \ + (0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE6_DATA_BRLSHFT_SHIFT) + #define EMC_DATA_BRLSHFT_0_RANK0_BYTE5_DATA_BRLSHFT_SHIFT 15 + #define EMC_DATA_BRLSHFT_0_RANK0_BYTE5_DATA_BRLSHFT_MASK \ + (0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE5_DATA_BRLSHFT_SHIFT) + #define EMC_DATA_BRLSHFT_0_RANK0_BYTE4_DATA_BRLSHFT_SHIFT 12 + #define EMC_DATA_BRLSHFT_0_RANK0_BYTE4_DATA_BRLSHFT_MASK \ + (0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE4_DATA_BRLSHFT_SHIFT) + #define EMC_DATA_BRLSHFT_0_RANK0_BYTE3_DATA_BRLSHFT_SHIFT 9 + #define EMC_DATA_BRLSHFT_0_RANK0_BYTE3_DATA_BRLSHFT_MASK \ + (0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE3_DATA_BRLSHFT_SHIFT) + #define EMC_DATA_BRLSHFT_0_RANK0_BYTE2_DATA_BRLSHFT_SHIFT 6 + #define EMC_DATA_BRLSHFT_0_RANK0_BYTE2_DATA_BRLSHFT_MASK \ + (0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE2_DATA_BRLSHFT_SHIFT) + #define EMC_DATA_BRLSHFT_0_RANK0_BYTE1_DATA_BRLSHFT_SHIFT 3 + #define EMC_DATA_BRLSHFT_0_RANK0_BYTE1_DATA_BRLSHFT_MASK \ + (0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE1_DATA_BRLSHFT_SHIFT) + #define EMC_DATA_BRLSHFT_0_RANK0_BYTE0_DATA_BRLSHFT_SHIFT 0 + #define EMC_DATA_BRLSHFT_0_RANK0_BYTE0_DATA_BRLSHFT_MASK \ + (0x7 << EMC_DATA_BRLSHFT_0_RANK0_BYTE0_DATA_BRLSHFT_SHIFT) + + #define EMC_DATA_BRLSHFT_1_RANK1_BYTE7_DATA_BRLSHFT_SHIFT 21 + #define EMC_DATA_BRLSHFT_1_RANK1_BYTE7_DATA_BRLSHFT_MASK \ + (0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE7_DATA_BRLSHFT_SHIFT) + #define EMC_DATA_BRLSHFT_1_RANK1_BYTE6_DATA_BRLSHFT_SHIFT 18 + #define EMC_DATA_BRLSHFT_1_RANK1_BYTE6_DATA_BRLSHFT_MASK \ + (0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE6_DATA_BRLSHFT_SHIFT) + #define EMC_DATA_BRLSHFT_1_RANK1_BYTE5_DATA_BRLSHFT_SHIFT 15 + #define EMC_DATA_BRLSHFT_1_RANK1_BYTE5_DATA_BRLSHFT_MASK \ + (0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE5_DATA_BRLSHFT_SHIFT) + #define EMC_DATA_BRLSHFT_1_RANK1_BYTE4_DATA_BRLSHFT_SHIFT 12 + #define EMC_DATA_BRLSHFT_1_RANK1_BYTE4_DATA_BRLSHFT_MASK \ + (0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE4_DATA_BRLSHFT_SHIFT) + #define EMC_DATA_BRLSHFT_1_RANK1_BYTE3_DATA_BRLSHFT_SHIFT 9 + #define EMC_DATA_BRLSHFT_1_RANK1_BYTE3_DATA_BRLSHFT_MASK \ + (0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE3_DATA_BRLSHFT_SHIFT) + #define EMC_DATA_BRLSHFT_1_RANK1_BYTE2_DATA_BRLSHFT_SHIFT 6 + #define EMC_DATA_BRLSHFT_1_RANK1_BYTE2_DATA_BRLSHFT_MASK \ + (0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE2_DATA_BRLSHFT_SHIFT) + #define EMC_DATA_BRLSHFT_1_RANK1_BYTE1_DATA_BRLSHFT_SHIFT 3 + #define EMC_DATA_BRLSHFT_1_RANK1_BYTE1_DATA_BRLSHFT_MASK \ + (0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE1_DATA_BRLSHFT_SHIFT) + #define EMC_DATA_BRLSHFT_1_RANK1_BYTE0_DATA_BRLSHFT_SHIFT 0 + #define EMC_DATA_BRLSHFT_1_RANK1_BYTE0_DATA_BRLSHFT_MASK \ + (0x7 << EMC_DATA_BRLSHFT_1_RANK1_BYTE0_DATA_BRLSHFT_SHIFT) + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_timing_table_erista.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_timing_table_erista.hpp new file mode 100644 index 00000000..89828d00 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_timing_table_erista.hpp @@ -0,0 +1,359 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> +#include "fusee_mtc_timing_table_common.hpp" + +namespace ams::nxboot::erista { + + #define FOREACH_BURST_REG(HANDLER) \ + HANDLER(EMC, EMC_RC, emc_rc) \ + HANDLER(EMC, EMC_RFC, emc_rfc) \ + HANDLER(EMC, EMC_RFCPB, emc_rfcpb) \ + HANDLER(EMC, EMC_REFCTRL2, emc_refctrl2) \ + HANDLER(EMC, EMC_RFC_SLR, emc_rfc_slr) \ + HANDLER(EMC, EMC_RAS, emc_ras) \ + HANDLER(EMC, EMC_RP, emc_rp) \ + HANDLER(EMC, EMC_R2W, emc_r2w) \ + HANDLER(EMC, EMC_W2R, emc_w2r) \ + HANDLER(EMC, EMC_R2P, emc_r2p) \ + HANDLER(EMC, EMC_W2P, emc_w2p) \ + HANDLER(EMC, EMC_R2R, emc_r2r) \ + HANDLER(EMC, EMC_TPPD, emc_tppd) \ + HANDLER(EMC, EMC_CCDMW, emc_ccdmw) \ + HANDLER(EMC, EMC_RD_RCD, emc_rd_rcd) \ + HANDLER(EMC, EMC_WR_RCD, emc_wr_rcd) \ + HANDLER(EMC, EMC_RRD, emc_rrd) \ + HANDLER(EMC, EMC_REXT, emc_rext) \ + HANDLER(EMC, EMC_WEXT, emc_wext) \ + HANDLER(EMC, EMC_WDV_CHK, emc_wdv_chk) \ + HANDLER(EMC, EMC_WDV, emc_wdv) \ + HANDLER(EMC, EMC_WSV, emc_wsv) \ + HANDLER(EMC, EMC_WEV, emc_wev) \ + HANDLER(EMC, EMC_WDV_MASK, emc_wdv_mask) \ + HANDLER(EMC, EMC_WS_DURATION, emc_ws_duration) \ + HANDLER(EMC, EMC_WE_DURATION, emc_we_duration) \ + HANDLER(EMC, EMC_QUSE, emc_quse) \ + HANDLER(EMC, EMC_QUSE_WIDTH, emc_quse_width) \ + HANDLER(EMC, EMC_IBDLY, emc_ibdly) \ + HANDLER(EMC, EMC_OBDLY, emc_obdly) \ + HANDLER(EMC, EMC_EINPUT, emc_einput) \ + HANDLER(EMC, EMC_MRW6, emc_mrw6) \ + HANDLER(EMC, EMC_EINPUT_DURATION, emc_einput_duration) \ + HANDLER(EMC, EMC_PUTERM_EXTRA, emc_puterm_extra) \ + HANDLER(EMC, EMC_PUTERM_WIDTH, emc_puterm_width) \ + HANDLER(EMC, EMC_QRST, emc_qrst) \ + HANDLER(EMC, EMC_QSAFE, emc_qsafe) \ + HANDLER(EMC, EMC_RDV, emc_rdv) \ + HANDLER(EMC, EMC_RDV_MASK, emc_rdv_mask) \ + HANDLER(EMC, EMC_RDV_EARLY, emc_rdv_early) \ + HANDLER(EMC, EMC_RDV_EARLY_MASK, emc_rdv_early_mask) \ + HANDLER(EMC, EMC_REFRESH, emc_refresh) \ + HANDLER(EMC, EMC_BURST_REFRESH_NUM, emc_burst_refresh_num) \ + HANDLER(EMC, EMC_PRE_REFRESH_REQ_CNT, emc_pre_refresh_req_cnt) \ + HANDLER(EMC, EMC_PDEX2WR, emc_pdex2wr) \ + HANDLER(EMC, EMC_PDEX2RD, emc_pdex2rd) \ + HANDLER(EMC, EMC_PCHG2PDEN, emc_pchg2pden) \ + HANDLER(EMC, EMC_ACT2PDEN, emc_act2pden) \ + HANDLER(EMC, EMC_AR2PDEN, emc_ar2pden) \ + HANDLER(EMC, EMC_RW2PDEN, emc_rw2pden) \ + HANDLER(EMC, EMC_CKE2PDEN, emc_cke2pden) \ + HANDLER(EMC, EMC_PDEX2CKE, emc_pdex2cke) \ + HANDLER(EMC, EMC_PDEX2MRR, emc_pdex2mrr) \ + HANDLER(EMC, EMC_TXSR, emc_txsr) \ + HANDLER(EMC, EMC_TXSRDLL, emc_txsrdll) \ + HANDLER(EMC, EMC_TCKE, emc_tcke) \ + HANDLER(EMC, EMC_TCKESR, emc_tckesr) \ + HANDLER(EMC, EMC_TPD, emc_tpd) \ + HANDLER(EMC, EMC_TFAW, emc_tfaw) \ + HANDLER(EMC, EMC_TRPAB, emc_trpab) \ + HANDLER(EMC, EMC_TCLKSTABLE, emc_tclkstable) \ + HANDLER(EMC, EMC_TCLKSTOP, emc_tclkstop) \ + HANDLER(EMC, EMC_MRW7, emc_mrw7) \ + HANDLER(EMC, EMC_TREFBW, emc_trefbw) \ + HANDLER(EMC, EMC_ODT_WRITE, emc_odt_write) \ + HANDLER(EMC, EMC_FBIO_CFG5, emc_fbio_cfg5) \ + HANDLER(EMC, EMC_FBIO_CFG7, emc_fbio_cfg7) \ + HANDLER(EMC, EMC_CFG_DIG_DLL, emc_cfg_dig_dll) \ + HANDLER(EMC, EMC_CFG_DIG_DLL_PERIOD, emc_cfg_dig_dll_period) \ + HANDLER(EMC, EMC_PMACRO_IB_RXRT, emc_pmacro_ib_rxrt) \ + HANDLER(EMC, EMC_CFG_PIPE_1, emc_cfg_pipe_1) \ + HANDLER(EMC, EMC_CFG_PIPE_2, emc_cfg_pipe_2) \ + HANDLER(EMC, EMC_PMACRO_QUSE_DDLL_RANK0_4, emc_pmacro_quse_ddll_rank0_4) \ + HANDLER(EMC, EMC_PMACRO_QUSE_DDLL_RANK0_5, emc_pmacro_quse_ddll_rank0_5) \ + HANDLER(EMC, EMC_PMACRO_QUSE_DDLL_RANK1_4, emc_pmacro_quse_ddll_rank1_4) \ + HANDLER(EMC, EMC_PMACRO_QUSE_DDLL_RANK1_5, emc_pmacro_quse_ddll_rank1_5) \ + HANDLER(EMC, EMC_MRW8, emc_mrw8) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_4, emc_pmacro_ob_ddll_long_dq_rank1_4) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_5, emc_pmacro_ob_ddll_long_dq_rank1_5) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_0, emc_pmacro_ob_ddll_long_dqs_rank0_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_1, emc_pmacro_ob_ddll_long_dqs_rank0_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_2, emc_pmacro_ob_ddll_long_dqs_rank0_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_3, emc_pmacro_ob_ddll_long_dqs_rank0_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_4, emc_pmacro_ob_ddll_long_dqs_rank0_4) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_5, emc_pmacro_ob_ddll_long_dqs_rank0_5) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_0, emc_pmacro_ob_ddll_long_dqs_rank1_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_1, emc_pmacro_ob_ddll_long_dqs_rank1_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_2, emc_pmacro_ob_ddll_long_dqs_rank1_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_3, emc_pmacro_ob_ddll_long_dqs_rank1_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_4, emc_pmacro_ob_ddll_long_dqs_rank1_4) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_5, emc_pmacro_ob_ddll_long_dqs_rank1_5) \ + HANDLER(EMC, EMC_PMACRO_DDLL_LONG_CMD_0, emc_pmacro_ddll_long_cmd_0) \ + HANDLER(EMC, EMC_PMACRO_DDLL_LONG_CMD_1, emc_pmacro_ddll_long_cmd_1) \ + HANDLER(EMC, EMC_PMACRO_DDLL_LONG_CMD_2, emc_pmacro_ddll_long_cmd_2) \ + HANDLER(EMC, EMC_PMACRO_DDLL_LONG_CMD_3, emc_pmacro_ddll_long_cmd_3) \ + HANDLER(EMC, EMC_PMACRO_DDLL_LONG_CMD_4, emc_pmacro_ddll_long_cmd_4) \ + HANDLER(EMC, EMC_PMACRO_DDLL_SHORT_CMD_0, emc_pmacro_ddll_short_cmd_0) \ + HANDLER(EMC, EMC_PMACRO_DDLL_SHORT_CMD_1, emc_pmacro_ddll_short_cmd_1) \ + HANDLER(EMC, EMC_PMACRO_DDLL_SHORT_CMD_2, emc_pmacro_ddll_short_cmd_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_3, emc_pmacro_ob_ddll_short_dq_rank0_byte0_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_3, emc_pmacro_ob_ddll_short_dq_rank0_byte1_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_3, emc_pmacro_ob_ddll_short_dq_rank0_byte2_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_3, emc_pmacro_ob_ddll_short_dq_rank0_byte3_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_3, emc_pmacro_ob_ddll_short_dq_rank0_byte4_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_3, emc_pmacro_ob_ddll_short_dq_rank0_byte5_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_3, emc_pmacro_ob_ddll_short_dq_rank0_byte6_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_3, emc_pmacro_ob_ddll_short_dq_rank0_byte7_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_3, emc_pmacro_ob_ddll_short_dq_rank0_cmd0_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_3, emc_pmacro_ob_ddll_short_dq_rank0_cmd1_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_3, emc_pmacro_ob_ddll_short_dq_rank0_cmd2_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_3, emc_pmacro_ob_ddll_short_dq_rank0_cmd3_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_3, emc_pmacro_ob_ddll_short_dq_rank1_byte0_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_3, emc_pmacro_ob_ddll_short_dq_rank1_byte1_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_3, emc_pmacro_ob_ddll_short_dq_rank1_byte2_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_3, emc_pmacro_ob_ddll_short_dq_rank1_byte3_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_3, emc_pmacro_ob_ddll_short_dq_rank1_byte4_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_3, emc_pmacro_ob_ddll_short_dq_rank1_byte5_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_3, emc_pmacro_ob_ddll_short_dq_rank1_byte6_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_3, emc_pmacro_ob_ddll_short_dq_rank1_byte7_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_0, emc_pmacro_ob_ddll_short_dq_rank1_cmd0_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_1, emc_pmacro_ob_ddll_short_dq_rank1_cmd0_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_2, emc_pmacro_ob_ddll_short_dq_rank1_cmd0_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_3, emc_pmacro_ob_ddll_short_dq_rank1_cmd0_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_0, emc_pmacro_ob_ddll_short_dq_rank1_cmd1_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_1, emc_pmacro_ob_ddll_short_dq_rank1_cmd1_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_2, emc_pmacro_ob_ddll_short_dq_rank1_cmd1_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_3, emc_pmacro_ob_ddll_short_dq_rank1_cmd1_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_0, emc_pmacro_ob_ddll_short_dq_rank1_cmd2_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_1, emc_pmacro_ob_ddll_short_dq_rank1_cmd2_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_2, emc_pmacro_ob_ddll_short_dq_rank1_cmd2_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_3, emc_pmacro_ob_ddll_short_dq_rank1_cmd2_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_0, emc_pmacro_ob_ddll_short_dq_rank1_cmd3_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_1, emc_pmacro_ob_ddll_short_dq_rank1_cmd3_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_2, emc_pmacro_ob_ddll_short_dq_rank1_cmd3_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_3, emc_pmacro_ob_ddll_short_dq_rank1_cmd3_3) \ + HANDLER(EMC, EMC_TXDSRVTTGEN, emc_txdsrvttgen) \ + HANDLER(EMC, EMC_FDPD_CTRL_DQ, emc_fdpd_ctrl_dq) \ + HANDLER(EMC, EMC_FDPD_CTRL_CMD, emc_fdpd_ctrl_cmd) \ + HANDLER(EMC, EMC_FBIO_SPARE, emc_fbio_spare) \ + HANDLER(EMC, EMC_ZCAL_INTERVAL, emc_zcal_interval) \ + HANDLER(EMC, EMC_ZCAL_WAIT_CNT, emc_zcal_wait_cnt) \ + HANDLER(EMC, EMC_MRS_WAIT_CNT, emc_mrs_wait_cnt) \ + HANDLER(EMC, EMC_MRS_WAIT_CNT2, emc_mrs_wait_cnt2) \ + HANDLER(EMC, EMC_AUTO_CAL_CHANNEL, emc_auto_cal_channel) \ + HANDLER(EMC, EMC_DLL_CFG_0, emc_dll_cfg_0) \ + HANDLER(EMC, EMC_DLL_CFG_1, emc_dll_cfg_1) \ + HANDLER(EMC, EMC_PMACRO_AUTOCAL_CFG_COMMON, emc_pmacro_autocal_cfg_common) \ + HANDLER(EMC, EMC_PMACRO_ZCTRL, emc_pmacro_zctrl) \ + HANDLER(EMC, EMC_CFG, emc_cfg) \ + HANDLER(EMC, EMC_CFG_PIPE, emc_cfg_pipe) \ + HANDLER(EMC, EMC_DYN_SELF_REF_CONTROL, emc_dyn_self_ref_control) \ + HANDLER(EMC, EMC_QPOP, emc_qpop) \ + HANDLER(EMC, EMC_DQS_BRLSHFT_0, emc_dqs_brlshft_0) \ + HANDLER(EMC, EMC_DQS_BRLSHFT_1, emc_dqs_brlshft_1) \ + HANDLER(EMC, EMC_CMD_BRLSHFT_2, emc_cmd_brlshft_2) \ + HANDLER(EMC, EMC_CMD_BRLSHFT_3, emc_cmd_brlshft_3) \ + HANDLER(EMC, EMC_PMACRO_PAD_CFG_CTRL, emc_pmacro_pad_cfg_ctrl) \ + HANDLER(EMC, EMC_PMACRO_DATA_PAD_RX_CTRL, emc_pmacro_data_pad_rx_ctrl) \ + HANDLER(EMC, EMC_PMACRO_CMD_PAD_RX_CTRL, emc_pmacro_cmd_pad_rx_ctrl) \ + HANDLER(EMC, EMC_PMACRO_DATA_RX_TERM_MODE, emc_pmacro_data_rx_term_mode) \ + HANDLER(EMC, EMC_PMACRO_CMD_RX_TERM_MODE, emc_pmacro_cmd_rx_term_mode) \ + HANDLER(EMC, EMC_PMACRO_CMD_PAD_TX_CTRL, emc_pmacro_cmd_pad_tx_ctrl) \ + HANDLER(EMC, EMC_PMACRO_DATA_PAD_TX_CTRL, emc_pmacro_data_pad_tx_ctrl) \ + HANDLER(EMC, EMC_PMACRO_COMMON_PAD_TX_CTRL, emc_pmacro_common_pad_tx_ctrl) \ + HANDLER(EMC, EMC_PMACRO_VTTGEN_CTRL_0, emc_pmacro_vttgen_ctrl_0) \ + HANDLER(EMC, EMC_PMACRO_VTTGEN_CTRL_1, emc_pmacro_vttgen_ctrl_1) \ + HANDLER(EMC, EMC_PMACRO_VTTGEN_CTRL_2, emc_pmacro_vttgen_ctrl_2) \ + HANDLER(EMC, EMC_PMACRO_BRICK_CTRL_RFU1, emc_pmacro_brick_ctrl_rfu1) \ + HANDLER(EMC, EMC_PMACRO_CMD_BRICK_CTRL_FDPD, emc_pmacro_cmd_brick_ctrl_fdpd) \ + HANDLER(EMC, EMC_PMACRO_BRICK_CTRL_RFU2, emc_pmacro_brick_ctrl_rfu2) \ + HANDLER(EMC, EMC_PMACRO_DATA_BRICK_CTRL_FDPD, emc_pmacro_data_brick_ctrl_fdpd) \ + HANDLER(EMC, EMC_PMACRO_BG_BIAS_CTRL_0, emc_pmacro_bg_bias_ctrl_0) \ + HANDLER(EMC, EMC_CFG_3, emc_cfg_3) \ + HANDLER(EMC, EMC_PMACRO_TX_PWRD_0, emc_pmacro_tx_pwrd_0) \ + HANDLER(EMC, EMC_PMACRO_TX_PWRD_1, emc_pmacro_tx_pwrd_1) \ + HANDLER(EMC, EMC_PMACRO_TX_PWRD_2, emc_pmacro_tx_pwrd_2) \ + HANDLER(EMC, EMC_PMACRO_TX_PWRD_3, emc_pmacro_tx_pwrd_3) \ + HANDLER(EMC, EMC_PMACRO_TX_PWRD_4, emc_pmacro_tx_pwrd_4) \ + HANDLER(EMC, EMC_PMACRO_TX_PWRD_5, emc_pmacro_tx_pwrd_5) \ + HANDLER(EMC, EMC_CONFIG_SAMPLE_DELAY, emc_config_sample_delay) \ + HANDLER(EMC, EMC_PMACRO_TX_SEL_CLK_SRC_0, emc_pmacro_tx_sel_clk_src_0) \ + HANDLER(EMC, EMC_PMACRO_TX_SEL_CLK_SRC_1, emc_pmacro_tx_sel_clk_src_1) \ + HANDLER(EMC, EMC_PMACRO_TX_SEL_CLK_SRC_2, emc_pmacro_tx_sel_clk_src_2) \ + HANDLER(EMC, EMC_PMACRO_TX_SEL_CLK_SRC_3, emc_pmacro_tx_sel_clk_src_3) \ + HANDLER(EMC, EMC_PMACRO_TX_SEL_CLK_SRC_4, emc_pmacro_tx_sel_clk_src_4) \ + HANDLER(EMC, EMC_PMACRO_TX_SEL_CLK_SRC_5, emc_pmacro_tx_sel_clk_src_5) \ + HANDLER(EMC, EMC_PMACRO_DDLL_BYPASS, emc_pmacro_ddll_bypass) \ + HANDLER(EMC, EMC_PMACRO_DDLL_PWRD_0, emc_pmacro_ddll_pwrd_0) \ + HANDLER(EMC, EMC_PMACRO_DDLL_PWRD_1, emc_pmacro_ddll_pwrd_1) \ + HANDLER(EMC, EMC_PMACRO_DDLL_PWRD_2, emc_pmacro_ddll_pwrd_2) \ + HANDLER(EMC, EMC_PMACRO_CMD_CTRL_0, emc_pmacro_cmd_ctrl_0) \ + HANDLER(EMC, EMC_PMACRO_CMD_CTRL_1, emc_pmacro_cmd_ctrl_1) \ + HANDLER(EMC, EMC_PMACRO_CMD_CTRL_2, emc_pmacro_cmd_ctrl_2) \ + HANDLER(EMC, EMC_TR_TIMING_0, emc_tr_timing_0) \ + HANDLER(EMC, EMC_TR_DVFS, emc_tr_dvfs) \ + HANDLER(EMC, EMC_TR_CTRL_1, emc_tr_ctrl_1) \ + HANDLER(EMC, EMC_TR_RDV, emc_tr_rdv) \ + HANDLER(EMC, EMC_TR_QPOP, emc_tr_qpop) \ + HANDLER(EMC, EMC_TR_RDV_MASK, emc_tr_rdv_mask) \ + HANDLER(EMC, EMC_MRW14, emc_mrw14) \ + HANDLER(EMC, EMC_TR_QSAFE, emc_tr_qsafe) \ + HANDLER(EMC, EMC_TR_QRST, emc_tr_qrst) \ + HANDLER(EMC, EMC_TRAINING_CTRL, emc_training_ctrl) \ + HANDLER(EMC, EMC_TRAINING_SETTLE, emc_training_settle) \ + HANDLER(EMC, EMC_TRAINING_VREF_SETTLE, emc_training_vref_settle) \ + HANDLER(EMC, EMC_TRAINING_CA_FINE_CTRL, emc_training_ca_fine_ctrl) \ + HANDLER(EMC, EMC_TRAINING_CA_CTRL_MISC, emc_training_ca_ctrl_misc) \ + HANDLER(EMC, EMC_TRAINING_CA_CTRL_MISC1, emc_training_ca_ctrl_misc1) \ + HANDLER(EMC, EMC_TRAINING_CA_VREF_CTRL, emc_training_ca_vref_ctrl) \ + HANDLER(EMC, EMC_TRAINING_QUSE_CORS_CTRL, emc_training_quse_cors_ctrl) \ + HANDLER(EMC, EMC_TRAINING_QUSE_FINE_CTRL, emc_training_quse_fine_ctrl) \ + HANDLER(EMC, EMC_TRAINING_QUSE_CTRL_MISC, emc_training_quse_ctrl_misc) \ + HANDLER(EMC, EMC_TRAINING_QUSE_VREF_CTRL, emc_training_quse_vref_ctrl) \ + HANDLER(EMC, EMC_TRAINING_READ_FINE_CTRL, emc_training_read_fine_ctrl) \ + HANDLER(EMC, EMC_TRAINING_READ_CTRL_MISC, emc_training_read_ctrl_misc) \ + HANDLER(EMC, EMC_TRAINING_READ_VREF_CTRL, emc_training_read_vref_ctrl) \ + HANDLER(EMC, EMC_TRAINING_WRITE_FINE_CTRL, emc_training_write_fine_ctrl) \ + HANDLER(EMC, EMC_TRAINING_WRITE_CTRL_MISC, emc_training_write_ctrl_misc) \ + HANDLER(EMC, EMC_TRAINING_WRITE_VREF_CTRL, emc_training_write_vref_ctrl) \ + HANDLER(EMC, EMC_TRAINING_MPC, emc_training_mpc) \ + HANDLER(EMC, EMC_MRW15, emc_mrw15) + + #define DECLARE_STRUCT_MEMBER_HANDLER(BASE, REG, NAME) uint32_t NAME; + + #define DECLARE_ARRAY_AND_STRUCT_MEMBERS(NAME, FOREACH_HANDLER) \ + union { struct { FOREACH_HANDLER(DECLARE_STRUCT_MEMBER_HANDLER) } NAME; uint32_t NAME##_arr[sizeof(NAME) / sizeof(uint32_t)]; } + + struct EmcDvfsTimingTable { + uint32_t rev; + char dvfs_ver[60]; + uint32_t rate_khz; + uint32_t min_volt; + uint32_t gpu_min_volt; + char clock_src[32]; + uint32_t clk_src_emc; + uint32_t needs_training; + uint32_t training_pattern; + uint32_t trained; + + uint32_t periodic_training; + uint32_t trained_dram_clktree_c0d0u0; + uint32_t trained_dram_clktree_c0d0u1; + uint32_t trained_dram_clktree_c0d1u0; + uint32_t trained_dram_clktree_c0d1u1; + uint32_t trained_dram_clktree_c1d0u0; + uint32_t trained_dram_clktree_c1d0u1; + uint32_t trained_dram_clktree_c1d1u0; + uint32_t trained_dram_clktree_c1d1u1; + uint32_t current_dram_clktree_c0d0u0; + uint32_t current_dram_clktree_c0d0u1; + uint32_t current_dram_clktree_c0d1u0; + uint32_t current_dram_clktree_c0d1u1; + uint32_t current_dram_clktree_c1d0u0; + uint32_t current_dram_clktree_c1d0u1; + uint32_t current_dram_clktree_c1d1u0; + uint32_t current_dram_clktree_c1d1u1; + uint32_t run_clocks; + uint32_t tree_margin; + + uint32_t num_burst; + uint32_t num_burst_per_ch; + uint32_t num_trim; + uint32_t num_trim_per_ch; + uint32_t num_mc_regs; + uint32_t num_up_down; + uint32_t vref_num; + uint32_t training_mod_num; + uint32_t dram_timing_num; + + uint32_t ptfv_dqsosc_movavg_c0d0u0; + uint32_t ptfv_dqsosc_movavg_c0d0u1; + uint32_t ptfv_dqsosc_movavg_c0d1u0; + uint32_t ptfv_dqsosc_movavg_c0d1u1; + uint32_t ptfv_dqsosc_movavg_c1d0u0; + uint32_t ptfv_dqsosc_movavg_c1d0u1; + uint32_t ptfv_dqsosc_movavg_c1d1u0; + uint32_t ptfv_dqsosc_movavg_c1d1u1; + uint32_t ptfv_write_samples; + uint32_t ptfv_dvfs_samples; + uint32_t ptfv_movavg_weight; + uint32_t ptfv_config_ctrl; + + DECLARE_ARRAY_AND_STRUCT_MEMBERS(burst_regs, FOREACH_BURST_REG); + DECLARE_ARRAY_AND_STRUCT_MEMBERS(burst_perch_regs, FOREACH_PER_CHANNEL_BURST_REG); + DECLARE_ARRAY_AND_STRUCT_MEMBERS(shadow_regs_ca_train, FOREACH_BURST_REG); + DECLARE_ARRAY_AND_STRUCT_MEMBERS(shadow_regs_quse_train, FOREACH_BURST_REG); + DECLARE_ARRAY_AND_STRUCT_MEMBERS(shadow_regs_rdwr_train, FOREACH_BURST_REG); + DECLARE_ARRAY_AND_STRUCT_MEMBERS(trim_regs, FOREACH_TRIM_REG); + DECLARE_ARRAY_AND_STRUCT_MEMBERS(trim_perch_regs, FOREACH_PER_CHANNEL_TRIM_REG); + DECLARE_ARRAY_AND_STRUCT_MEMBERS(vref_perch_regs, FOREACH_PER_CHANNEL_VREF_REG); + + struct { + uint32_t t_rp; + uint32_t t_fc_lpddr4; + uint32_t t_rfc; + uint32_t t_pdex; + uint32_t rl; + } dram_timings; + + DECLARE_ARRAY_AND_STRUCT_MEMBERS(training_mod_regs, FOREACH_PER_CHANNEL_TRAINING_MOD_REG); + + uint32_t save_restore_mod_regs[12]; + + DECLARE_ARRAY_AND_STRUCT_MEMBERS(burst_mc_regs, FOREACH_BURST_MC_REG); + DECLARE_ARRAY_AND_STRUCT_MEMBERS(la_scale_regs, FOREACH_LA_SCALE_REG); + + uint32_t min_mrs_wait; + uint32_t emc_mrw; + uint32_t emc_mrw2; + uint32_t emc_mrw3; + uint32_t emc_mrw4; + uint32_t emc_mrw9; + uint32_t emc_mrs; + uint32_t emc_emrs; + uint32_t emc_emrs2; + uint32_t emc_auto_cal_config; + uint32_t emc_auto_cal_config2; + uint32_t emc_auto_cal_config3; + uint32_t emc_auto_cal_config4; + uint32_t emc_auto_cal_config5; + uint32_t emc_auto_cal_config6; + uint32_t emc_auto_cal_config7; + uint32_t emc_auto_cal_config8; + uint32_t emc_cfg_2; + uint32_t emc_sel_dpd_ctrl; + uint32_t emc_fdpd_ctrl_cmd_no_ramp; + uint32_t dll_clk_src; + uint32_t clk_out_enb_x_0_clk_enb_emc_dll; + uint32_t latency; + }; + + #undef DECLARE_STRUCT_MEMBER_HANDLER + #undef DECLARE_ARRAY_AND_STRUCT_MEMBERS + + static_assert(sizeof(EmcDvfsTimingTable) == 0x1340); + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_timing_table_mariko.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_timing_table_mariko.hpp new file mode 100644 index 00000000..fc03dd2d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/mtc/fusee_mtc_timing_table_mariko.hpp @@ -0,0 +1,409 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <exosphere.hpp> +#include "fusee_mtc_timing_table_common.hpp" + +namespace ams::nxboot::mariko { + + #define FOREACH_BURST_REG(HANDLER) \ + HANDLER(EMC, EMC_RC, emc_rc) \ + HANDLER(EMC, EMC_RFC, emc_rfc) \ + HANDLER(EMC, EMC_RFCPB, emc_rfcpb) \ + HANDLER(EMC, EMC_REFCTRL2, emc_refctrl2) \ + HANDLER(EMC, EMC_RFC_SLR, emc_rfc_slr) \ + HANDLER(EMC, EMC_RAS, emc_ras) \ + HANDLER(EMC, EMC_RP, emc_rp) \ + HANDLER(EMC, EMC_R2W, emc_r2w) \ + HANDLER(EMC, EMC_W2R, emc_w2r) \ + HANDLER(EMC, EMC_R2P, emc_r2p) \ + HANDLER(EMC, EMC_W2P, emc_w2p) \ + HANDLER(EMC, EMC_R2R, emc_r2r) \ + HANDLER(EMC, EMC_TPPD, emc_tppd) \ + HANDLER(EMC, EMC_TRTM, emc_trtm) \ + HANDLER(EMC, EMC_TWTM, emc_twtm) \ + HANDLER(EMC, EMC_TRATM, emc_tratm) \ + HANDLER(EMC, EMC_TWATM, emc_twatm) \ + HANDLER(EMC, EMC_TR2REF, emc_tr2ref) \ + HANDLER(EMC, EMC_CCDMW, emc_ccdmw) \ + HANDLER(EMC, EMC_RD_RCD, emc_rd_rcd) \ + HANDLER(EMC, EMC_WR_RCD, emc_wr_rcd) \ + HANDLER(EMC, EMC_RRD, emc_rrd) \ + HANDLER(EMC, EMC_REXT, emc_rext) \ + HANDLER(EMC, EMC_WEXT, emc_wext) \ + HANDLER(EMC, EMC_WDV_CHK, emc_wdv_chk) \ + HANDLER(EMC, EMC_WDV, emc_wdv) \ + HANDLER(EMC, EMC_WSV, emc_wsv) \ + HANDLER(EMC, EMC_WEV, emc_wev) \ + HANDLER(EMC, EMC_WDV_MASK, emc_wdv_mask) \ + HANDLER(EMC, EMC_WS_DURATION, emc_ws_duration) \ + HANDLER(EMC, EMC_WE_DURATION, emc_we_duration) \ + HANDLER(EMC, EMC_QUSE, emc_quse) \ + HANDLER(EMC, EMC_QUSE_WIDTH, emc_quse_width) \ + HANDLER(EMC, EMC_IBDLY, emc_ibdly) \ + HANDLER(EMC, EMC_OBDLY, emc_obdly) \ + HANDLER(EMC, EMC_EINPUT, emc_einput) \ + HANDLER(EMC, EMC_MRW6, emc_mrw6) \ + HANDLER(EMC, EMC_EINPUT_DURATION, emc_einput_duration) \ + HANDLER(EMC, EMC_PUTERM_EXTRA, emc_puterm_extra) \ + HANDLER(EMC, EMC_PUTERM_WIDTH, emc_puterm_width) \ + HANDLER(EMC, EMC_QRST, emc_qrst) \ + HANDLER(EMC, EMC_QSAFE, emc_qsafe) \ + HANDLER(EMC, EMC_RDV, emc_rdv) \ + HANDLER(EMC, EMC_RDV_MASK, emc_rdv_mask) \ + HANDLER(EMC, EMC_RDV_EARLY, emc_rdv_early) \ + HANDLER(EMC, EMC_RDV_EARLY_MASK, emc_rdv_early_mask) \ + HANDLER(EMC, EMC_REFRESH, emc_refresh) \ + HANDLER(EMC, EMC_BURST_REFRESH_NUM, emc_burst_refresh_num) \ + HANDLER(EMC, EMC_PRE_REFRESH_REQ_CNT, emc_pre_refresh_req_cnt) \ + HANDLER(EMC, EMC_PDEX2WR, emc_pdex2wr) \ + HANDLER(EMC, EMC_PDEX2RD, emc_pdex2rd) \ + HANDLER(EMC, EMC_PCHG2PDEN, emc_pchg2pden) \ + HANDLER(EMC, EMC_ACT2PDEN, emc_act2pden) \ + HANDLER(EMC, EMC_AR2PDEN, emc_ar2pden) \ + HANDLER(EMC, EMC_RW2PDEN, emc_rw2pden) \ + HANDLER(EMC, EMC_CKE2PDEN, emc_cke2pden) \ + HANDLER(EMC, EMC_PDEX2CKE, emc_pdex2cke) \ + HANDLER(EMC, EMC_PDEX2MRR, emc_pdex2mrr) \ + HANDLER(EMC, EMC_TXSR, emc_txsr) \ + HANDLER(EMC, EMC_TXSRDLL, emc_txsrdll) \ + HANDLER(EMC, EMC_TCKE, emc_tcke) \ + HANDLER(EMC, EMC_TCKESR, emc_tckesr) \ + HANDLER(EMC, EMC_TPD, emc_tpd) \ + HANDLER(EMC, EMC_TFAW, emc_tfaw) \ + HANDLER(EMC, EMC_TRPAB, emc_trpab) \ + HANDLER(EMC, EMC_TCLKSTABLE, emc_tclkstable) \ + HANDLER(EMC, EMC_TCLKSTOP, emc_tclkstop) \ + HANDLER(EMC, EMC_MRW7, emc_mrw7) \ + HANDLER(EMC, EMC_TREFBW, emc_trefbw) \ + HANDLER(EMC, EMC_ODT_WRITE, emc_odt_write) \ + HANDLER(EMC, EMC_FBIO_CFG5, emc_fbio_cfg5) \ + HANDLER(EMC, EMC_FBIO_CFG7, emc_fbio_cfg7) \ + HANDLER(EMC, EMC_CFG_DIG_DLL, emc_cfg_dig_dll) \ + HANDLER(EMC, EMC_CFG_DIG_DLL_PERIOD, emc_cfg_dig_dll_period) \ + HANDLER(EMC, EMC_PMACRO_IB_RXRT, emc_pmacro_ib_rxrt) \ + HANDLER(EMC, EMC_CFG_PIPE_1, emc_cfg_pipe_1) \ + HANDLER(EMC, EMC_CFG_PIPE_2, emc_cfg_pipe_2) \ + HANDLER(EMC, EMC_PMACRO_QUSE_DDLL_RANK0_4, emc_pmacro_quse_ddll_rank0_4) \ + HANDLER(EMC, EMC_PMACRO_QUSE_DDLL_RANK0_5, emc_pmacro_quse_ddll_rank0_5) \ + HANDLER(EMC, EMC_PMACRO_QUSE_DDLL_RANK1_4, emc_pmacro_quse_ddll_rank1_4) \ + HANDLER(EMC, EMC_PMACRO_QUSE_DDLL_RANK1_5, emc_pmacro_quse_ddll_rank1_5) \ + HANDLER(EMC, EMC_MRW8, emc_mrw8) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_4, emc_pmacro_ob_ddll_long_dq_rank1_4) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_5, emc_pmacro_ob_ddll_long_dq_rank1_5) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_0, emc_pmacro_ob_ddll_long_dqs_rank0_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_1, emc_pmacro_ob_ddll_long_dqs_rank0_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_2, emc_pmacro_ob_ddll_long_dqs_rank0_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_3, emc_pmacro_ob_ddll_long_dqs_rank0_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_4, emc_pmacro_ob_ddll_long_dqs_rank0_4) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_5, emc_pmacro_ob_ddll_long_dqs_rank0_5) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_0, emc_pmacro_ob_ddll_long_dqs_rank1_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_1, emc_pmacro_ob_ddll_long_dqs_rank1_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_2, emc_pmacro_ob_ddll_long_dqs_rank1_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_3, emc_pmacro_ob_ddll_long_dqs_rank1_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_4, emc_pmacro_ob_ddll_long_dqs_rank1_4) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_5, emc_pmacro_ob_ddll_long_dqs_rank1_5) \ + HANDLER(EMC, EMC_PMACRO_DDLL_LONG_CMD_0, emc_pmacro_ddll_long_cmd_0) \ + HANDLER(EMC, EMC_PMACRO_DDLL_LONG_CMD_1, emc_pmacro_ddll_long_cmd_1) \ + HANDLER(EMC, EMC_PMACRO_DDLL_LONG_CMD_2, emc_pmacro_ddll_long_cmd_2) \ + HANDLER(EMC, EMC_PMACRO_DDLL_LONG_CMD_3, emc_pmacro_ddll_long_cmd_3) \ + HANDLER(EMC, EMC_PMACRO_DDLL_LONG_CMD_4, emc_pmacro_ddll_long_cmd_4) \ + HANDLER(EMC, EMC_PMACRO_DDLL_SHORT_CMD_0, emc_pmacro_ddll_short_cmd_0) \ + HANDLER(EMC, EMC_PMACRO_DDLL_SHORT_CMD_1, emc_pmacro_ddll_short_cmd_1) \ + HANDLER(EMC, EMC_PMACRO_DDLL_SHORT_CMD_2, emc_pmacro_ddll_short_cmd_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_3, emc_pmacro_ob_ddll_short_dq_rank0_byte0_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_3, emc_pmacro_ob_ddll_short_dq_rank0_byte1_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_3, emc_pmacro_ob_ddll_short_dq_rank0_byte2_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_3, emc_pmacro_ob_ddll_short_dq_rank0_byte3_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_3, emc_pmacro_ob_ddll_short_dq_rank0_byte4_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_3, emc_pmacro_ob_ddll_short_dq_rank0_byte5_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_3, emc_pmacro_ob_ddll_short_dq_rank0_byte6_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_3, emc_pmacro_ob_ddll_short_dq_rank0_byte7_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_3, emc_pmacro_ob_ddll_short_dq_rank0_cmd0_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_3, emc_pmacro_ob_ddll_short_dq_rank0_cmd1_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_3, emc_pmacro_ob_ddll_short_dq_rank0_cmd2_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_3, emc_pmacro_ob_ddll_short_dq_rank0_cmd3_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_3, emc_pmacro_ob_ddll_short_dq_rank1_byte0_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_3, emc_pmacro_ob_ddll_short_dq_rank1_byte1_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_3, emc_pmacro_ob_ddll_short_dq_rank1_byte2_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_3, emc_pmacro_ob_ddll_short_dq_rank1_byte3_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_3, emc_pmacro_ob_ddll_short_dq_rank1_byte4_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_3, emc_pmacro_ob_ddll_short_dq_rank1_byte5_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_3, emc_pmacro_ob_ddll_short_dq_rank1_byte6_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_3, emc_pmacro_ob_ddll_short_dq_rank1_byte7_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_0, emc_pmacro_ob_ddll_short_dq_rank1_cmd0_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_1, emc_pmacro_ob_ddll_short_dq_rank1_cmd0_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_2, emc_pmacro_ob_ddll_short_dq_rank1_cmd0_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_3, emc_pmacro_ob_ddll_short_dq_rank1_cmd0_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_0, emc_pmacro_ob_ddll_short_dq_rank1_cmd1_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_1, emc_pmacro_ob_ddll_short_dq_rank1_cmd1_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_2, emc_pmacro_ob_ddll_short_dq_rank1_cmd1_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_3, emc_pmacro_ob_ddll_short_dq_rank1_cmd1_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_0, emc_pmacro_ob_ddll_short_dq_rank1_cmd2_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_1, emc_pmacro_ob_ddll_short_dq_rank1_cmd2_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_2, emc_pmacro_ob_ddll_short_dq_rank1_cmd2_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_3, emc_pmacro_ob_ddll_short_dq_rank1_cmd2_3) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_0, emc_pmacro_ob_ddll_short_dq_rank1_cmd3_0) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_1, emc_pmacro_ob_ddll_short_dq_rank1_cmd3_1) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_2, emc_pmacro_ob_ddll_short_dq_rank1_cmd3_2) \ + HANDLER(EMC, EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_3, emc_pmacro_ob_ddll_short_dq_rank1_cmd3_3) \ + HANDLER(EMC, EMC_TXDSRVTTGEN, emc_txdsrvttgen) \ + HANDLER(EMC, EMC_FDPD_CTRL_DQ, emc_fdpd_ctrl_dq) \ + HANDLER(EMC, EMC_FDPD_CTRL_CMD, emc_fdpd_ctrl_cmd) \ + HANDLER(EMC, EMC_FBIO_SPARE, emc_fbio_spare) \ + HANDLER(EMC, EMC_ZCAL_INTERVAL, emc_zcal_interval) \ + HANDLER(EMC, EMC_ZCAL_WAIT_CNT, emc_zcal_wait_cnt) \ + HANDLER(EMC, EMC_MRS_WAIT_CNT, emc_mrs_wait_cnt) \ + HANDLER(EMC, EMC_MRS_WAIT_CNT2, emc_mrs_wait_cnt2) \ + HANDLER(EMC, EMC_AUTO_CAL_CHANNEL, emc_auto_cal_channel) \ + HANDLER(EMC, EMC_PMACRO_DLL_CFG_0, emc_pmacro_dll_cfg_0) \ + HANDLER(EMC, EMC_PMACRO_DLL_CFG_1, emc_pmacro_dll_cfg_1) \ + HANDLER(EMC, EMC_PMACRO_DLL_CFG_2, emc_pmacro_dll_cfg_2) \ + HANDLER(EMC, EMC_PMACRO_AUTOCAL_CFG_COMMON, emc_pmacro_autocal_cfg_common) \ + HANDLER(EMC, EMC_PMACRO_ZCTRL, emc_pmacro_zctrl) \ + HANDLER(EMC, EMC_CFG, emc_cfg) \ + HANDLER(EMC, EMC_CFG_PIPE, emc_cfg_pipe) \ + HANDLER(EMC, EMC_DYN_SELF_REF_CONTROL, emc_dyn_self_ref_control) \ + HANDLER(EMC, EMC_QPOP, emc_qpop) \ + HANDLER(EMC, EMC_DQS_BRLSHFT_0, emc_dqs_brlshft_0) \ + HANDLER(EMC, EMC_DQS_BRLSHFT_1, emc_dqs_brlshft_1) \ + HANDLER(EMC, EMC_CMD_BRLSHFT_2, emc_cmd_brlshft_2) \ + HANDLER(EMC, EMC_CMD_BRLSHFT_3, emc_cmd_brlshft_3) \ + HANDLER(EMC, EMC_PMACRO_PAD_CFG_CTRL, emc_pmacro_pad_cfg_ctrl) \ + HANDLER(EMC, EMC_PMACRO_DATA_PAD_RX_CTRL, emc_pmacro_data_pad_rx_ctrl) \ + HANDLER(EMC, EMC_PMACRO_CMD_PAD_RX_CTRL, emc_pmacro_cmd_pad_rx_ctrl) \ + HANDLER(EMC, EMC_PMACRO_DATA_RX_TERM_MODE, emc_pmacro_data_rx_term_mode) \ + HANDLER(EMC, EMC_PMACRO_CMD_RX_TERM_MODE, emc_pmacro_cmd_rx_term_mode) \ + HANDLER(EMC, EMC_PMACRO_CMD_PAD_TX_CTRL, emc_pmacro_cmd_pad_tx_ctrl) \ + HANDLER(EMC, EMC_PMACRO_DATA_PAD_TX_CTRL, emc_pmacro_data_pad_tx_ctrl) \ + HANDLER(EMC, EMC_PMACRO_VTTGEN_CTRL_0, emc_pmacro_vttgen_ctrl_0) \ + HANDLER(EMC, EMC_PMACRO_VTTGEN_CTRL_1, emc_pmacro_vttgen_ctrl_1) \ + HANDLER(EMC, EMC_PMACRO_VTTGEN_CTRL_2, emc_pmacro_vttgen_ctrl_2) \ + HANDLER(EMC, EMC_PMACRO_BRICK_CTRL_RFU1, emc_pmacro_brick_ctrl_rfu1) \ + HANDLER(EMC, EMC_PMACRO_CMD_BRICK_CTRL_FDPD, emc_pmacro_cmd_brick_ctrl_fdpd) \ + HANDLER(EMC, EMC_PMACRO_BRICK_CTRL_RFU2, emc_pmacro_brick_ctrl_rfu2) \ + HANDLER(EMC, EMC_PMACRO_DATA_BRICK_CTRL_FDPD, emc_pmacro_data_brick_ctrl_fdpd) \ + HANDLER(EMC, EMC_PMACRO_BG_BIAS_CTRL_0, emc_pmacro_bg_bias_ctrl_0) \ + HANDLER(EMC, EMC_CFG_3, emc_cfg_3) \ + HANDLER(EMC, EMC_PMACRO_TX_PWRD_0, emc_pmacro_tx_pwrd_0) \ + HANDLER(EMC, EMC_PMACRO_TX_PWRD_1, emc_pmacro_tx_pwrd_1) \ + HANDLER(EMC, EMC_PMACRO_TX_PWRD_2, emc_pmacro_tx_pwrd_2) \ + HANDLER(EMC, EMC_PMACRO_TX_PWRD_3, emc_pmacro_tx_pwrd_3) \ + HANDLER(EMC, EMC_PMACRO_TX_PWRD_4, emc_pmacro_tx_pwrd_4) \ + HANDLER(EMC, EMC_PMACRO_TX_PWRD_5, emc_pmacro_tx_pwrd_5) \ + HANDLER(EMC, EMC_CONFIG_SAMPLE_DELAY, emc_config_sample_delay) \ + HANDLER(EMC, EMC_PMACRO_TX_SEL_CLK_SRC_0, emc_pmacro_tx_sel_clk_src_0) \ + HANDLER(EMC, EMC_PMACRO_TX_SEL_CLK_SRC_1, emc_pmacro_tx_sel_clk_src_1) \ + HANDLER(EMC, EMC_PMACRO_TX_SEL_CLK_SRC_2, emc_pmacro_tx_sel_clk_src_2) \ + HANDLER(EMC, EMC_PMACRO_TX_SEL_CLK_SRC_3, emc_pmacro_tx_sel_clk_src_3) \ + HANDLER(EMC, EMC_PMACRO_TX_SEL_CLK_SRC_4, emc_pmacro_tx_sel_clk_src_4) \ + HANDLER(EMC, EMC_PMACRO_TX_SEL_CLK_SRC_5, emc_pmacro_tx_sel_clk_src_5) \ + HANDLER(EMC, EMC_PMACRO_DDLL_BYPASS, emc_pmacro_ddll_bypass) \ + HANDLER(EMC, EMC_PMACRO_DDLL_PWRD_0, emc_pmacro_ddll_pwrd_0) \ + HANDLER(EMC, EMC_PMACRO_DDLL_PWRD_1, emc_pmacro_ddll_pwrd_1) \ + HANDLER(EMC, EMC_PMACRO_DDLL_PWRD_2, emc_pmacro_ddll_pwrd_2) \ + HANDLER(EMC, EMC_PMACRO_CMD_CTRL_0, emc_pmacro_cmd_ctrl_0) \ + HANDLER(EMC, EMC_PMACRO_CMD_CTRL_1, emc_pmacro_cmd_ctrl_1) \ + HANDLER(EMC, EMC_PMACRO_CMD_CTRL_2, emc_pmacro_cmd_ctrl_2) \ + HANDLER(EMC, EMC_PMACRO_DATA_PI_CTRL, emc_pmacro_data_pi_ctrl) \ + HANDLER(EMC, EMC_PMACRO_CMD_PI_CTRL, emc_pmacro_cmd_pi_ctrl) \ + HANDLER(EMC, EMC_TR_TIMING_0, emc_tr_timing_0) \ + HANDLER(EMC, EMC_TR_DVFS, emc_tr_dvfs) \ + HANDLER(EMC, EMC_TR_CTRL_1, emc_tr_ctrl_1) \ + HANDLER(EMC, EMC_TR_RDV, emc_tr_rdv) \ + HANDLER(EMC, EMC_TR_QPOP, emc_tr_qpop) \ + HANDLER(EMC, EMC_TR_RDV_MASK, emc_tr_rdv_mask) \ + HANDLER(EMC, EMC_MRW14, emc_mrw14) \ + HANDLER(EMC, EMC_TR_QSAFE, emc_tr_qsafe) \ + HANDLER(EMC, EMC_TR_QRST, emc_tr_qrst) \ + HANDLER(EMC, EMC_TRAINING_CTRL, emc_training_ctrl) \ + HANDLER(EMC, EMC_TRAINING_SETTLE, emc_training_settle) \ + HANDLER(EMC, EMC_TRAINING_VREF_SETTLE, emc_training_vref_settle) \ + HANDLER(EMC, EMC_TRAINING_CA_FINE_CTRL, emc_training_ca_fine_ctrl) \ + HANDLER(EMC, EMC_TRAINING_CA_CTRL_MISC, emc_training_ca_ctrl_misc) \ + HANDLER(EMC, EMC_TRAINING_CA_CTRL_MISC1, emc_training_ca_ctrl_misc1) \ + HANDLER(EMC, EMC_TRAINING_CA_VREF_CTRL, emc_training_ca_vref_ctrl) \ + HANDLER(EMC, EMC_TRAINING_QUSE_CORS_CTRL, emc_training_quse_cors_ctrl) \ + HANDLER(EMC, EMC_TRAINING_QUSE_FINE_CTRL, emc_training_quse_fine_ctrl) \ + HANDLER(EMC, EMC_TRAINING_QUSE_CTRL_MISC, emc_training_quse_ctrl_misc) \ + HANDLER(EMC, EMC_TRAINING_QUSE_VREF_CTRL, emc_training_quse_vref_ctrl) \ + HANDLER(EMC, EMC_TRAINING_READ_FINE_CTRL, emc_training_read_fine_ctrl) \ + HANDLER(EMC, EMC_TRAINING_READ_CTRL_MISC, emc_training_read_ctrl_misc) \ + HANDLER(EMC, EMC_TRAINING_READ_VREF_CTRL, emc_training_read_vref_ctrl) \ + HANDLER(EMC, EMC_TRAINING_WRITE_FINE_CTRL, emc_training_write_fine_ctrl) \ + HANDLER(EMC, EMC_TRAINING_WRITE_CTRL_MISC, emc_training_write_ctrl_misc) \ + HANDLER(EMC, EMC_TRAINING_WRITE_VREF_CTRL, emc_training_write_vref_ctrl) \ + HANDLER(EMC, EMC_TRAINING_MPC, emc_training_mpc) \ + HANDLER(EMC, EMC_MRW15, emc_mrw15) + + #define DECLARE_STRUCT_MEMBER_HANDLER(BASE, REG, NAME) uint32_t NAME; + + #define DECLARE_ARRAY_AND_STRUCT_MEMBERS(NAME, FOREACH_HANDLER) \ + union { struct { FOREACH_HANDLER(DECLARE_STRUCT_MEMBER_HANDLER) } NAME; uint32_t NAME##_arr[sizeof(NAME) / sizeof(uint32_t)]; } + + struct EmcDvfsTimingTable { + uint32_t rev; + char dvfs_ver[60]; + uint32_t rate_khz; + uint32_t min_volt; + uint32_t gpu_min_volt; + char clock_src[32]; + uint32_t clk_src_emc; + uint32_t pll_en_ssc; + uint32_t needs_training; + uint32_t training_pattern; + uint32_t trained; + + uint32_t periodic_training; + uint32_t trained_dram_clktree_c0d0u0; + uint32_t trained_dram_clktree_c0d0u1; + uint32_t trained_dram_clktree_c0d1u0; + uint32_t trained_dram_clktree_c0d1u1; + uint32_t trained_dram_clktree_c1d0u0; + uint32_t trained_dram_clktree_c1d0u1; + uint32_t trained_dram_clktree_c1d1u0; + uint32_t trained_dram_clktree_c1d1u1; + uint32_t current_dram_clktree_c0d0u0; + uint32_t current_dram_clktree_c0d0u1; + uint32_t current_dram_clktree_c0d1u0; + uint32_t current_dram_clktree_c0d1u1; + uint32_t current_dram_clktree_c1d0u0; + uint32_t current_dram_clktree_c1d0u1; + uint32_t current_dram_clktree_c1d1u0; + uint32_t current_dram_clktree_c1d1u1; + uint32_t emc_fbio_cfg7; + uint32_t run_clocks; + uint32_t tree_margin; + + uint32_t num_burst; + uint32_t num_burst_per_ch; + uint32_t num_trim; + uint32_t num_trim_per_ch; + uint32_t num_mc_regs; + uint32_t num_up_down; + uint32_t vref_num; + uint32_t training_mod_num; + uint32_t dram_timing_num; + + uint32_t ptfv_dqsosc_movavg_c0d0u0; + uint32_t ptfv_dqsosc_movavg_c0d0u1; + uint32_t ptfv_dqsosc_movavg_c0d1u0; + uint32_t ptfv_dqsosc_movavg_c0d1u1; + uint32_t ptfv_dqsosc_movavg_c1d0u0; + uint32_t ptfv_dqsosc_movavg_c1d0u1; + uint32_t ptfv_dqsosc_movavg_c1d1u0; + uint32_t ptfv_dqsosc_movavg_c1d1u1; + uint32_t ptfv_write_samples; + uint32_t ptfv_dvfs_samples; + uint32_t ptfv_movavg_weight; + uint32_t ptfv_config_ctrl; + + DECLARE_ARRAY_AND_STRUCT_MEMBERS(burst_regs, FOREACH_BURST_REG); + DECLARE_ARRAY_AND_STRUCT_MEMBERS(burst_perch_regs, FOREACH_PER_CHANNEL_BURST_REG); + DECLARE_ARRAY_AND_STRUCT_MEMBERS(shadow_regs_ca_train, FOREACH_BURST_REG); + DECLARE_ARRAY_AND_STRUCT_MEMBERS(shadow_regs_rdwr_train, FOREACH_BURST_REG); + DECLARE_ARRAY_AND_STRUCT_MEMBERS(trim_regs, FOREACH_TRIM_REG); + DECLARE_ARRAY_AND_STRUCT_MEMBERS(trim_perch_regs, FOREACH_PER_CHANNEL_TRIM_REG); + DECLARE_ARRAY_AND_STRUCT_MEMBERS(vref_perch_regs, FOREACH_PER_CHANNEL_VREF_REG); + + struct { + uint32_t t_rp; + uint32_t t_fc_lpddr4; + uint32_t t_rfc; + uint32_t t_pdex; + uint32_t rl; + } dram_timings; + + uint32_t zq_op_cc_long_zcal; + uint32_t zq_op_cc_short_zcal; + uint32_t zcal_wait_time_ps_cc_long_zcal; + uint32_t zcal_wait_time_ps_cc_short_zcal; + uint32_t tZQCAL_lpddr4; + uint32_t zqcal_before_cc_cutoff; + uint32_t opt_cc_short_zcal; + uint32_t opt_short_zcal; + uint32_t opt_do_sw_qrst; + uint32_t save_restore_clkstop_pd; + uint32_t opt_E90; + uint32_t cya_allow_ref_cc; + uint32_t ref_b4_sref_en; + uint32_t cya_issue_pc_ref; + + DECLARE_ARRAY_AND_STRUCT_MEMBERS(training_mod_regs, FOREACH_PER_CHANNEL_TRAINING_MOD_REG); + + uint32_t save_restore_mod_regs[12]; + + DECLARE_ARRAY_AND_STRUCT_MEMBERS(burst_mc_regs, FOREACH_BURST_MC_REG); + DECLARE_ARRAY_AND_STRUCT_MEMBERS(la_scale_regs, FOREACH_LA_SCALE_REG); + + uint32_t unk_0; + uint32_t vtt_vdda_ctrl_0; + uint32_t src_clock_div; + uint32_t vtt_vdda_dual_channel; + uint32_t vtt_vdda_ctrl_1; + uint32_t vtt_vdda_ctrl_2; + uint32_t vtt_vdda_ctrl_3; + uint32_t vtt_vdda_ctrl_4; + uint32_t misc_cfg_0; + uint32_t misc_cfg_1; + uint32_t misc_cfg_2; + uint32_t unk_1; + uint32_t unk_2; + uint32_t pipe_clk_delay; + uint32_t clkchange_delay; + uint32_t pllm_ss_cfg; + uint32_t pllm_ss_ctrl1; + uint32_t pllm_ss_ctrl2; + uint32_t pllmb_ss_cfg; + uint32_t pllmb_ss_ctrl1; + uint32_t pllmb_ss_ctrl2; + uint32_t pllmb_divm; + uint32_t pllmb_divn; + uint32_t pllmb_divp; + uint32_t min_mrs_wait; + uint32_t ramp_wait; + uint32_t emc_mrw; + uint32_t emc_mrw2; + uint32_t emc_mrw3; + uint32_t emc_mrw4; + uint32_t emc_mrw9; + uint32_t emc_mrs; + uint32_t emc_emrs; + uint32_t emc_emrs2; + uint32_t emc_auto_cal_config; + uint32_t emc_auto_cal_config2; + uint32_t emc_auto_cal_config3; + uint32_t emc_auto_cal_config4; + uint32_t emc_auto_cal_config5; + uint32_t emc_auto_cal_config6; + uint32_t emc_auto_cal_config7; + uint32_t emc_auto_cal_config8; + uint32_t emc_cfg_2; + uint32_t emc_sel_dpd_ctrl; + uint32_t emc_fdpd_ctrl_cmd_no_ramp; + uint32_t emc_tr_ctrl_0; + uint32_t dll_clk_src; + uint32_t clk_out_enb_x_0_clk_enb_emc_dll; + uint32_t latency; + uint32_t pllm_misc1_0_pllm_clamp_ph90; + }; + + #undef DECLARE_STRUCT_MEMBER_HANDLER + #undef DECLARE_ARRAY_AND_STRUCT_MEMBERS + + static_assert(sizeof(EmcDvfsTimingTable) == 0x10CC); + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/sdram/fusee_sdram.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/sdram/fusee_sdram.cpp new file mode 100644 index 00000000..1ad265a1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/sdram/fusee_sdram.cpp @@ -0,0 +1,1115 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_sdram.hpp" +#include "../fusee_uncompress.hpp" + +namespace ams::nxboot { + + namespace { + + template<fuse::SocType SocType> + struct SdramParamsImpl; + + template<> struct SdramParamsImpl<fuse::SocType_Erista> { using Type = br::erista::BootSdramParams; }; + template<> struct SdramParamsImpl<fuse::SocType_Mariko> { using Type = br::mariko::BootSdramParams; }; + + template<fuse::SocType SocType> + using BootSdramParams = SdramParamsImpl<SocType>::Type; + + constexpr inline const uintptr_t CLKRST = secmon::MemoryRegionPhysicalDeviceClkRst.GetAddress(); + constexpr inline const uintptr_t PMC = secmon::MemoryRegionPhysicalDevicePmc.GetAddress(); + constexpr inline const uintptr_t APB = secmon::MemoryRegionPhysicalDeviceApbMisc.GetAddress(); + constexpr inline const uintptr_t MC = secmon::MemoryRegionPhysicalDeviceMemoryController.GetAddress(); + constexpr inline const uintptr_t EMC = EMC_ADDRESS(0); + constexpr inline const uintptr_t AHB = AHB_ARBC(0); + + #include "fusee_sdram_params.inc" + + #include "fusee_sdram_params_lp0_erista.inc" + #include "fusee_sdram_params_lp0_mariko.inc" + + void *GetSdramParams(fuse::SocType soc_type) { + /* Get DRAM Id. */ + const auto dram_id = fuse::GetDramId(); + + /* Extract to work buffer. */ + void *sdram_params_work_buffer; + + if (soc_type == fuse::SocType_Erista) { + sdram_params_work_buffer = reinterpret_cast<void *>(0x4003E000 - 2 * sizeof(BootSdramParams<fuse::SocType_Erista>)); + #define HANDLE_DRAM_CASE(_DRAM_ID_, _INDEX_) \ + case _DRAM_ID_: \ + Uncompress(sdram_params_work_buffer, 2 * sizeof(BootSdramParams<fuse::SocType_Erista>), SdramParamsErista##_INDEX_, SdramParamsSizeErista##_INDEX_); \ + if (_INDEX_ & 1) { \ + sdram_params_work_buffer = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(sdram_params_work_buffer) + sizeof(BootSdramParams<fuse::SocType_Erista>)); \ + } \ + break; + switch (dram_id) { + HANDLE_DRAM_CASE(0, 0) + HANDLE_DRAM_CASE(1, 1) + HANDLE_DRAM_CASE(2, 2) + HANDLE_DRAM_CASE(3, 3) + HANDLE_DRAM_CASE(4, 4) + HANDLE_DRAM_CASE(5, 5) + HANDLE_DRAM_CASE(6, 6) + default: + AMS_ABORT("Invalid DRAM id"); + } + #undef HANDLE_DRAM_CASE + + return static_cast<BootSdramParams<fuse::SocType_Erista> *>(sdram_params_work_buffer); + } else /* if (soc_type == fuse::SocType_Mariko) */ { + sdram_params_work_buffer = reinterpret_cast<void *>(0x4003E000 - 2 * sizeof(BootSdramParams<fuse::SocType_Mariko>)); + #define HANDLE_DRAM_CASE(_DRAM_ID_, _INDEX_) \ + case _DRAM_ID_: \ + Uncompress(sdram_params_work_buffer, 2 * sizeof(BootSdramParams<fuse::SocType_Mariko>), SdramParamsMariko##_INDEX_, SdramParamsSizeMariko##_INDEX_); \ + if (_INDEX_ & 1) { \ + sdram_params_work_buffer = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(sdram_params_work_buffer) + sizeof(BootSdramParams<fuse::SocType_Mariko>)); \ + } \ + break; + switch (dram_id) { + HANDLE_DRAM_CASE( 3, 12) + HANDLE_DRAM_CASE( 5, 12) + HANDLE_DRAM_CASE( 6, 12) + HANDLE_DRAM_CASE( 8, 1) + HANDLE_DRAM_CASE( 9, 2) + HANDLE_DRAM_CASE(10, 3) + HANDLE_DRAM_CASE(11, 4) + HANDLE_DRAM_CASE(12, 1) + HANDLE_DRAM_CASE(13, 2) + HANDLE_DRAM_CASE(14, 3) + HANDLE_DRAM_CASE(15, 4) + HANDLE_DRAM_CASE(17, 6) + HANDLE_DRAM_CASE(18, 7) + HANDLE_DRAM_CASE(19, 6) + HANDLE_DRAM_CASE(20, 10) + HANDLE_DRAM_CASE(21, 10) + HANDLE_DRAM_CASE(22, 10) + HANDLE_DRAM_CASE(23, 7) + HANDLE_DRAM_CASE(24, 6) + HANDLE_DRAM_CASE(25, 11) + HANDLE_DRAM_CASE(26, 11) + HANDLE_DRAM_CASE(27, 11) + HANDLE_DRAM_CASE(28, 7) + HANDLE_DRAM_CASE(29, 0) + HANDLE_DRAM_CASE(30, 0) + HANDLE_DRAM_CASE(31, 0) + HANDLE_DRAM_CASE(32, 5) + HANDLE_DRAM_CASE(33, 5) + HANDLE_DRAM_CASE(34, 5) + default: + AMS_ABORT("Invalid DRAM id"); + } + #undef HANDLE_DRAM_CASE + + return static_cast<BootSdramParams<fuse::SocType_Mariko> *>(sdram_params_work_buffer); + } + } + + template<fuse::SocType SocType> + void SpareWrite(u32 reg, u32 value) { + if (reg) { + if constexpr (SocType == fuse::SocType_Erista) { + reg::Write(reinterpret_cast<volatile u32 *>(reg), value); + } else if constexpr (SocType == fuse::SocType_Mariko) { + /* TODO: Validate the write. */ + reg::Write(reinterpret_cast<volatile u32 *>(reg), value); + } + } + } + + template<fuse::SocType SocType> + void InitializeSdramImpl(BootSdramParams<SocType> *params) { + /* Perform initial soc-specific setup. */ + if constexpr (SocType == fuse::SocType_Erista) { + /* Enable sel_dpd on unused pins. */ + reg::Write(PMC + APBDEV_PMC_IO_DPD3_REQ, (((params->EmcPmcScratch1 & 0x3FFFFFFF) | 0x80000000) ^ 0xFFFF) & 0xC000FFFF); + util::WaitMicroSeconds(params->PmcIoDpd3ReqWait); + + /* Disable e_dpd_vttgen. */ + u32 dpd4 = (params->EmcPmcScratch2 & 0x3FFFFFFF) | 0x80000000; + reg::Write(PMC + APBDEV_PMC_IO_DPD4_REQ, (dpd4 ^ 0x3FFF0000) & 0xFFFF0000); + util::WaitMicroSeconds(params->PmcIoDpd4ReqWait); + + /* Disable e_dpd_bg. */ + reg::Write(PMC + APBDEV_PMC_IO_DPD4_REQ, (dpd4 ^ 0x0000FFFF) & 0xC000FFFF); + util::WaitMicroSeconds(params->PmcIoDpd4ReqWait); + + reg::Write(PMC + APBDEV_PMC_WEAK_BIAS, 0); + util::WaitMicroSeconds(1); + + /* Enable memory clock. */ + { + /* Initialize pllm. */ + { + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLM_MISC1, params->PllMSetupControl); + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLM_MISC2, 0); + + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE, CLK_RST_REG_BITS_ENUM (PLLM_BASE_PLLM_ENABLE, DISABLE), + CLK_RST_REG_BITS_VALUE(PLLM_BASE_PLLM_DIVP, params->PllMPostDivider), + CLK_RST_REG_BITS_VALUE(PLLM_BASE_PLLM_DIVN, params->PllMFeedbackDivider), + CLK_RST_REG_BITS_VALUE(PLLM_BASE_PLLM_DIVM, params->PllMInputDivider)); + + + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE, CLK_RST_REG_BITS_ENUM (PLLM_BASE_PLLM_ENABLE, ENABLE), + CLK_RST_REG_BITS_VALUE(PLLM_BASE_PLLM_DIVP, params->PllMPostDivider), + CLK_RST_REG_BITS_VALUE(PLLM_BASE_PLLM_DIVN, params->PllMFeedbackDivider), + CLK_RST_REG_BITS_VALUE(PLLM_BASE_PLLM_DIVM, params->PllMInputDivider)); + + /* Wait 300us for stability. */ + const auto stable_time = util::GetMicroSeconds() + 300; + while (true) { + if (reg::HasValue(CLKRST + CLK_RST_CONTROLLER_PLLM_BASE, CLK_RST_REG_BITS_ENUM(PLLM_BASE_PLLM_LOCK, LOCK))) { + util::WaitMicroSeconds(10); + break; + } + + if (util::GetMicroSeconds() >= stable_time) { + break; + } + } + } + + /* Set CLK_SOURCE_EMC, using McEmcmArbMisc0 as MC_EMC_SAME_FREQ. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC, (params->EmcClockSource & ~0x10000) | ((params->McEmemArbMisc0 >> 11) & 0x10000)); + + if (params->EmcClockSourceDll) { + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC_DLL, params->EmcClockSourceDll); + } + + if (params->ClearClk2Mc1) { + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_W_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_W_CLK_ENB_MC1, ENABLE)); + } + + /* Enable EMC/Mem. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_H_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_H_CLK_ENB_EMC, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_H_CLK_ENB_MEM, ENABLE)); + + /* Enable EMC DLL. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_X_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_X_CLK_ENB_EMC_DLL, ENABLE)); + } + + /* Clear reset for MEM/EMC. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_RST_DEV_H_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_H_EMC_RST, ENABLE), + CLK_RST_REG_BITS_ENUM(RST_DEV_H_MEM_RST, ENABLE)); + + /* Set pad macros. */ + reg::Write(EMC + EMC_PMACRO_VTTGEN_CTRL_0, params->EmcPmacroVttgenCtrl0); + reg::Write(EMC + EMC_PMACRO_VTTGEN_CTRL_1, params->EmcPmacroVttgenCtrl1); + reg::Write(EMC + EMC_PMACRO_VTTGEN_CTRL_2, params->EmcPmacroVttgenCtrl2); + + reg::Write(EMC + EMC_TIMING_CONTROL, 1); + util::WaitMicroSeconds(1); + + /* Select EMC write mux. */ + reg::Write(EMC + EMC_DBG, params->EmcDbg | reg::EncodeValue(EMC_REG_BITS_VALUE(DBG_WRITE_MUX, params->EmcDbgWriteMux))); + + /* Patch 2. */ + SpareWrite<SocType>(params->EmcBctSpare2, params->EmcBctSpare3); + } else if constexpr (SocType == fuse::SocType_Mariko) { + /* Patch 1 */ + SpareWrite<SocType>(params->EmcBctSpare0, params->EmcBctSpare1); + + if (params->ClkRstControllerPllmMisc2OverrideEnable) { + reg::Write(CLKRST + CLK_RST_CONTROLLER_PLLM_MISC2, params->ClkRstControllerPllmMisc2Override); + } + + /* Enable sel_dpd on unused pins. */ + { + u32 val = (~params->EmcPmcScratch1 & 0x00000FFF) << 18; + val |= ((~params->EmcPmcScratch1 & 0x00001000) << 19) | ((~params->EmcPmcScratch1 & 0x00008000) << 15); + reg::Write(PMC + APBDEV_PMC_WEAK_BIAS, val); + } + + reg::Write(PMC + APBDEV_PMC_IO_DPD3_REQ, 0x80000000 | (~params->EmcPmcScratch1 & 0x00009FFF)); + util::WaitMicroSeconds(params->PmcIoDpd3ReqWait); + + /* Disable e_dpd_vttgen. */ + reg::Write(PMC + APBDEV_PMC_IO_DPD4_REQ, 0x80000000 | (~params->EmcPmcScratch2 & 0x3FFF0000)); + util::WaitMicroSeconds(params->PmcIoDpd4ReqWait); + + /* Disable e_dpd_bg. */ + reg::Write(PMC + APBDEV_PMC_IO_DPD4_REQ, 0x80000000 | (~params->EmcPmcScratch2 & 0x00001FFF)); + util::WaitMicroSeconds(1); + } + + /* Common phase 1. */ + + /* Program CMD mapping. */ + reg::Write(EMC + EMC_FBIO_CFG7, params->EmcFbioCfg7); + + reg::Write(EMC + EMC_CMD_MAPPING_CMD0_0, params->EmcCmdMappingCmd0_0); + reg::Write(EMC + EMC_CMD_MAPPING_CMD0_1, params->EmcCmdMappingCmd0_1); + reg::Write(EMC + EMC_CMD_MAPPING_CMD0_2, params->EmcCmdMappingCmd0_2); + reg::Write(EMC + EMC_CMD_MAPPING_CMD1_0, params->EmcCmdMappingCmd1_0); + reg::Write(EMC + EMC_CMD_MAPPING_CMD1_1, params->EmcCmdMappingCmd1_1); + reg::Write(EMC + EMC_CMD_MAPPING_CMD1_2, params->EmcCmdMappingCmd1_2); + reg::Write(EMC + EMC_CMD_MAPPING_CMD2_0, params->EmcCmdMappingCmd2_0); + reg::Write(EMC + EMC_CMD_MAPPING_CMD2_1, params->EmcCmdMappingCmd2_1); + reg::Write(EMC + EMC_CMD_MAPPING_CMD2_2, params->EmcCmdMappingCmd2_2); + reg::Write(EMC + EMC_CMD_MAPPING_CMD3_0, params->EmcCmdMappingCmd3_0); + reg::Write(EMC + EMC_CMD_MAPPING_CMD3_1, params->EmcCmdMappingCmd3_1); + reg::Write(EMC + EMC_CMD_MAPPING_CMD3_2, params->EmcCmdMappingCmd3_2); + reg::Write(EMC + EMC_CMD_MAPPING_BYTE, params->EmcCmdMappingByte); + + /* Program brick mapping. */ + reg::Write(EMC + EMC_PMACRO_BRICK_MAPPING_0, params->EmcPmacroBrickMapping0); + reg::Write(EMC + EMC_PMACRO_BRICK_MAPPING_1, params->EmcPmacroBrickMapping1); + reg::Write(EMC + EMC_PMACRO_BRICK_MAPPING_2, params->EmcPmacroBrickMapping2); + + /* Specific phase 2. */ + if constexpr (SocType == fuse::SocType_Erista) { + reg::Write(EMC + EMC_PMACRO_BRICK_CTRL_RFU1, (params->EmcPmacroBrickCtrlRfu1 | ~0x01120112) & 0x1FFF1FFF); + } else if constexpr (SocType == fuse::SocType_Mariko) { + /* Set pad macros. */ + reg::Write(EMC + EMC_PMACRO_VTTGEN_CTRL_0, params->EmcPmacroVttgenCtrl0); + reg::Write(EMC + EMC_PMACRO_VTTGEN_CTRL_1, params->EmcPmacroVttgenCtrl1); + reg::Write(EMC + EMC_PMACRO_VTTGEN_CTRL_2, params->EmcPmacroVttgenCtrl2); + + /* Set pad macro bias. */ + reg::Write(EMC + EMC_PMACRO_BG_BIAS_CTRL_0, params->EmcPmacroBgBiasCtrl0); + + SpareWrite<SocType>(params->EmcBctSpareSecure0, params->EmcBctSpareSecure1); + SpareWrite<SocType>(params->EmcBctSpareSecure2, params->EmcBctSpareSecure3); + SpareWrite<SocType>(params->EmcBctSpareSecure4, params->EmcBctSpareSecure5); + + /* Trigger timing update. */ + reg::Write(EMC + EMC_TIMING_CONTROL, 1); + util::WaitMicroSeconds(params->PmcVddpSelWait + 2); + + /* Set clock sources. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC, params->EmcClockSource); + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC_DLL, params->EmcClockSourceDll); + + /* Select EMC write mux. */ + reg::Write(EMC + EMC_DBG, params->EmcDbg | reg::EncodeValue(EMC_REG_BITS_VALUE(DBG_WRITE_MUX, params->EmcDbgWriteMux))); + + /* Patch 2. */ + SpareWrite<SocType>(params->EmcBctSpare2, params->EmcBctSpare3); + } + + /* Common phase 2. */ + reg::Write(EMC + EMC_CONFIG_SAMPLE_DELAY, params->EmcConfigSampleDelay); + reg::Write(EMC + EMC_FBIO_CFG8, params->EmcFbioCfg8); + + /* Program swizzle registers. */ + reg::Write(EMC + EMC_SWIZZLE_RANK0_BYTE0, params->EmcSwizzleRank0Byte0); + reg::Write(EMC + EMC_SWIZZLE_RANK0_BYTE1, params->EmcSwizzleRank0Byte1); + reg::Write(EMC + EMC_SWIZZLE_RANK0_BYTE2, params->EmcSwizzleRank0Byte2); + reg::Write(EMC + EMC_SWIZZLE_RANK0_BYTE3, params->EmcSwizzleRank0Byte3); + reg::Write(EMC + EMC_SWIZZLE_RANK1_BYTE0, params->EmcSwizzleRank1Byte0); + reg::Write(EMC + EMC_SWIZZLE_RANK1_BYTE1, params->EmcSwizzleRank1Byte1); + reg::Write(EMC + EMC_SWIZZLE_RANK1_BYTE2, params->EmcSwizzleRank1Byte2); + reg::Write(EMC + EMC_SWIZZLE_RANK1_BYTE3, params->EmcSwizzleRank1Byte3); + + /* Patch 3. */ + SpareWrite<SocType>(params->EmcBctSpare6, params->EmcBctSpare7); + + /* Program pad controls. */ + reg::Write(EMC + EMC_XM2COMPPADCTRL, params->EmcXm2CompPadCtrl); + reg::Write(EMC + EMC_XM2COMPPADCTRL2, params->EmcXm2CompPadCtrl2); + reg::Write(EMC + EMC_XM2COMPPADCTRL3, params->EmcXm2CompPadCtrl3); + + /* Program autocal controls with shadowed register fields. */ + reg::Write(EMC + EMC_AUTO_CAL_CONFIG2, params->EmcAutoCalConfig2); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG3, params->EmcAutoCalConfig3); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG4, params->EmcAutoCalConfig4); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG5, params->EmcAutoCalConfig5); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG6, params->EmcAutoCalConfig6); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG7, params->EmcAutoCalConfig7); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG8, params->EmcAutoCalConfig8); + + reg::Write(EMC + EMC_PMACRO_RX_TERM, params->EmcPmacroRxTerm); + reg::Write(EMC + EMC_PMACRO_DQ_TX_DRV, params->EmcPmacroDqTxDrv); + reg::Write(EMC + EMC_PMACRO_CA_TX_DRV, params->EmcPmacroCaTxDrv); + reg::Write(EMC + EMC_PMACRO_CMD_TX_DRV, params->EmcPmacroCmdTxDrv); + reg::Write(EMC + EMC_PMACRO_AUTOCAL_CFG_COMMON, params->EmcPmacroAutocalCfgCommon); + reg::Write(EMC + EMC_AUTO_CAL_CHANNEL, params->EmcAutoCalChannel); + reg::Write(EMC + EMC_PMACRO_ZCTRL, params->EmcPmacroZctrl); + + if constexpr (SocType == fuse::SocType_Erista) { + reg::Write(EMC + EMC_DLL_CFG_0, params->EmcDllCfg0); + reg::Write(EMC + EMC_DLL_CFG_1, params->EmcDllCfg1); + } else if constexpr (SocType == fuse::SocType_Mariko) { + reg::Write(EMC + EMC_PMACRO_DLL_CFG_0, params->EmcPmacroDllCfg0); + reg::Write(EMC + EMC_PMACRO_DLL_CFG_1, params->EmcPmacroDllCfg1); + } + + reg::Write(EMC + EMC_CFG_DIG_DLL_1, params->EmcCfgDigDll_1); + + reg::Write(EMC + EMC_DATA_BRLSHFT_0, params->EmcDataBrlshft0); + reg::Write(EMC + EMC_DATA_BRLSHFT_1, params->EmcDataBrlshft1); + reg::Write(EMC + EMC_DQS_BRLSHFT_0, params->EmcDqsBrlshft0); + reg::Write(EMC + EMC_DQS_BRLSHFT_1, params->EmcDqsBrlshft1); + reg::Write(EMC + EMC_CMD_BRLSHFT_0, params->EmcCmdBrlshft0); + reg::Write(EMC + EMC_CMD_BRLSHFT_1, params->EmcCmdBrlshft1); + reg::Write(EMC + EMC_CMD_BRLSHFT_2, params->EmcCmdBrlshft2); + reg::Write(EMC + EMC_CMD_BRLSHFT_3, params->EmcCmdBrlshft3); + reg::Write(EMC + EMC_QUSE_BRLSHFT_0, params->EmcQuseBrlshft0); + reg::Write(EMC + EMC_QUSE_BRLSHFT_1, params->EmcQuseBrlshft1); + reg::Write(EMC + EMC_QUSE_BRLSHFT_2, params->EmcQuseBrlshft2); + reg::Write(EMC + EMC_QUSE_BRLSHFT_3, params->EmcQuseBrlshft3); + + if constexpr (SocType == fuse::SocType_Erista) { + reg::Write(EMC + EMC_PMACRO_BRICK_CTRL_RFU1, (params->EmcPmacroBrickCtrlRfu1 | ~0x01BF01BF) & 0x1FFF1FFF); + } else if constexpr (SocType == fuse::SocType_Mariko) { + reg::Write(EMC + EMC_PMACRO_BRICK_CTRL_RFU1, params->EmcPmacroBrickCtrlRfu1); + } + + reg::Write(EMC + EMC_PMACRO_PAD_CFG_CTRL, params->EmcPmacroPadCfgCtrl); + reg::Write(EMC + EMC_PMACRO_CMD_BRICK_CTRL_FDPD, params->EmcPmacroCmdBrickCtrlFdpd); + + if constexpr (SocType == fuse::SocType_Erista) { + reg::Write(EMC + EMC_PMACRO_BRICK_CTRL_RFU2, params->EmcPmacroBrickCtrlRfu2 & 0xFF7FFF7F); + } else if constexpr (SocType == fuse::SocType_Mariko) { + reg::Write(EMC + EMC_PMACRO_BRICK_CTRL_RFU2, params->EmcPmacroBrickCtrlRfu2); + } + + reg::Write(EMC + EMC_PMACRO_DATA_BRICK_CTRL_FDPD, params->EmcPmacroDataBrickCtrlFdpd); + + if constexpr (SocType == fuse::SocType_Erista) { + reg::Write(EMC + EMC_PMACRO_BG_BIAS_CTRL_0, params->EmcPmacroBgBiasCtrl0); + } + + reg::Write(EMC + EMC_PMACRO_DATA_PAD_RX_CTRL, params->EmcPmacroDataPadRxCtrl); + reg::Write(EMC + EMC_PMACRO_CMD_PAD_RX_CTRL, params->EmcPmacroCmdPadRxCtrl); + reg::Write(EMC + EMC_PMACRO_DATA_PAD_TX_CTRL, params->EmcPmacroDataPadTxCtrl); + reg::Write(EMC + EMC_PMACRO_DATA_RX_TERM_MODE, params->EmcPmacroDataRxTermMode); + reg::Write(EMC + EMC_PMACRO_CMD_RX_TERM_MODE, params->EmcPmacroCmdRxTermMode); + + if constexpr (SocType == fuse::SocType_Erista) { + reg::Write(EMC + EMC_PMACRO_CMD_PAD_TX_CTRL, params->EmcPmacroCmdPadTxCtrl); + } else if constexpr (SocType == fuse::SocType_Mariko) { + reg::Write(EMC + EMC_PMACRO_CMD_PAD_TX_CTRL, params->EmcPmacroCmdPadTxCtrl & 0xEFFFFFFF); + } + + reg::Write(EMC + EMC_CFG_3, params->EmcCfg3); + + reg::Write(EMC + EMC_PMACRO_TX_PWRD_0, params->EmcPmacroTxPwrd0); + reg::Write(EMC + EMC_PMACRO_TX_PWRD_1, params->EmcPmacroTxPwrd1); + reg::Write(EMC + EMC_PMACRO_TX_PWRD_2, params->EmcPmacroTxPwrd2); + reg::Write(EMC + EMC_PMACRO_TX_PWRD_3, params->EmcPmacroTxPwrd3); + reg::Write(EMC + EMC_PMACRO_TX_PWRD_4, params->EmcPmacroTxPwrd4); + reg::Write(EMC + EMC_PMACRO_TX_PWRD_5, params->EmcPmacroTxPwrd5); + + reg::Write(EMC + EMC_PMACRO_TX_SEL_CLK_SRC_0, params->EmcPmacroTxSelClkSrc0); + reg::Write(EMC + EMC_PMACRO_TX_SEL_CLK_SRC_1, params->EmcPmacroTxSelClkSrc1); + reg::Write(EMC + EMC_PMACRO_TX_SEL_CLK_SRC_2, params->EmcPmacroTxSelClkSrc2); + reg::Write(EMC + EMC_PMACRO_TX_SEL_CLK_SRC_3, params->EmcPmacroTxSelClkSrc3); + reg::Write(EMC + EMC_PMACRO_TX_SEL_CLK_SRC_4, params->EmcPmacroTxSelClkSrc4); + reg::Write(EMC + EMC_PMACRO_TX_SEL_CLK_SRC_5, params->EmcPmacroTxSelClkSrc5); + + if constexpr (SocType == fuse::SocType_Mariko) { + reg::Write(EMC + EMC_PMACRO_PERBIT_FGCG_CTRL_0, params->EmcPmacroPerbitFgcgCtrl0); + reg::Write(EMC + EMC_PMACRO_PERBIT_FGCG_CTRL_1, params->EmcPmacroPerbitFgcgCtrl1); + reg::Write(EMC + EMC_PMACRO_PERBIT_FGCG_CTRL_2, params->EmcPmacroPerbitFgcgCtrl2); + reg::Write(EMC + EMC_PMACRO_PERBIT_FGCG_CTRL_3, params->EmcPmacroPerbitFgcgCtrl3); + reg::Write(EMC + EMC_PMACRO_PERBIT_FGCG_CTRL_4, params->EmcPmacroPerbitFgcgCtrl4); + reg::Write(EMC + EMC_PMACRO_PERBIT_FGCG_CTRL_5, params->EmcPmacroPerbitFgcgCtrl5); + + reg::Write(EMC + EMC_PMACRO_PERBIT_RFU_CTRL_0, params->EmcPmacroPerbitRfuCtrl0); + reg::Write(EMC + EMC_PMACRO_PERBIT_RFU_CTRL_1, params->EmcPmacroPerbitRfuCtrl1); + reg::Write(EMC + EMC_PMACRO_PERBIT_RFU_CTRL_2, params->EmcPmacroPerbitRfuCtrl2); + reg::Write(EMC + EMC_PMACRO_PERBIT_RFU_CTRL_3, params->EmcPmacroPerbitRfuCtrl3); + reg::Write(EMC + EMC_PMACRO_PERBIT_RFU_CTRL_4, params->EmcPmacroPerbitRfuCtrl4); + reg::Write(EMC + EMC_PMACRO_PERBIT_RFU_CTRL_5, params->EmcPmacroPerbitRfuCtrl5); + + reg::Write(EMC + EMC_PMACRO_PERBIT_RFU1_CTRL_0, params->EmcPmacroPerbitRfu1Ctrl0); + reg::Write(EMC + EMC_PMACRO_PERBIT_RFU1_CTRL_1, params->EmcPmacroPerbitRfu1Ctrl1); + reg::Write(EMC + EMC_PMACRO_PERBIT_RFU1_CTRL_2, params->EmcPmacroPerbitRfu1Ctrl2); + reg::Write(EMC + EMC_PMACRO_PERBIT_RFU1_CTRL_3, params->EmcPmacroPerbitRfu1Ctrl3); + reg::Write(EMC + EMC_PMACRO_PERBIT_RFU1_CTRL_4, params->EmcPmacroPerbitRfu1Ctrl4); + reg::Write(EMC + EMC_PMACRO_PERBIT_RFU1_CTRL_5, params->EmcPmacroPerbitRfu1Ctrl5); + + reg::Write(EMC + EMC_PMACRO_DATA_PI_CTRL, params->EmcPmacroDataPiCtrl); + reg::Write(EMC + EMC_PMACRO_CMD_PI_CTRL, params->EmcPmacroCmdPiCtrl); + } + + reg::Write(EMC + EMC_PMACRO_DDLL_BYPASS, params->EmcPmacroDdllBypass); + reg::Write(EMC + EMC_PMACRO_DDLL_PWRD_0, params->EmcPmacroDdllPwrd0); + reg::Write(EMC + EMC_PMACRO_DDLL_PWRD_1, params->EmcPmacroDdllPwrd1); + reg::Write(EMC + EMC_PMACRO_DDLL_PWRD_2, params->EmcPmacroDdllPwrd2); + reg::Write(EMC + EMC_PMACRO_CMD_CTRL_0, params->EmcPmacroCmdCtrl0); + reg::Write(EMC + EMC_PMACRO_CMD_CTRL_1, params->EmcPmacroCmdCtrl1); + reg::Write(EMC + EMC_PMACRO_CMD_CTRL_2, params->EmcPmacroCmdCtrl2); + reg::Write(EMC + EMC_PMACRO_IB_VREF_DQ_0, params->EmcPmacroIbVrefDq_0); + reg::Write(EMC + EMC_PMACRO_IB_VREF_DQ_1, params->EmcPmacroIbVrefDq_1); + reg::Write(EMC + EMC_PMACRO_IB_VREF_DQS_0, params->EmcPmacroIbVrefDqs_0); + reg::Write(EMC + EMC_PMACRO_IB_VREF_DQS_1, params->EmcPmacroIbVrefDqs_1); + reg::Write(EMC + EMC_PMACRO_IB_RXRT, params->EmcPmacroIbRxrt); + + reg::Write(EMC + EMC_PMACRO_QUSE_DDLL_RANK0_0, params->EmcPmacroQuseDdllRank0_0); + reg::Write(EMC + EMC_PMACRO_QUSE_DDLL_RANK0_1, params->EmcPmacroQuseDdllRank0_1); + reg::Write(EMC + EMC_PMACRO_QUSE_DDLL_RANK0_2, params->EmcPmacroQuseDdllRank0_2); + reg::Write(EMC + EMC_PMACRO_QUSE_DDLL_RANK0_3, params->EmcPmacroQuseDdllRank0_3); + reg::Write(EMC + EMC_PMACRO_QUSE_DDLL_RANK0_4, params->EmcPmacroQuseDdllRank0_4); + reg::Write(EMC + EMC_PMACRO_QUSE_DDLL_RANK0_5, params->EmcPmacroQuseDdllRank0_5); + reg::Write(EMC + EMC_PMACRO_QUSE_DDLL_RANK1_0, params->EmcPmacroQuseDdllRank1_0); + reg::Write(EMC + EMC_PMACRO_QUSE_DDLL_RANK1_1, params->EmcPmacroQuseDdllRank1_1); + reg::Write(EMC + EMC_PMACRO_QUSE_DDLL_RANK1_2, params->EmcPmacroQuseDdllRank1_2); + reg::Write(EMC + EMC_PMACRO_QUSE_DDLL_RANK1_3, params->EmcPmacroQuseDdllRank1_3); + reg::Write(EMC + EMC_PMACRO_QUSE_DDLL_RANK1_4, params->EmcPmacroQuseDdllRank1_4); + reg::Write(EMC + EMC_PMACRO_QUSE_DDLL_RANK1_5, params->EmcPmacroQuseDdllRank1_5); + + if constexpr (SocType == fuse::SocType_Erista) { + reg::Write(EMC + EMC_PMACRO_BRICK_CTRL_RFU1, params->EmcPmacroBrickCtrlRfu1); + } + + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0, params->EmcPmacroObDdllLongDqRank0_0); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1, params->EmcPmacroObDdllLongDqRank0_1); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2, params->EmcPmacroObDdllLongDqRank0_2); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3, params->EmcPmacroObDdllLongDqRank0_3); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_4, params->EmcPmacroObDdllLongDqRank0_4); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_5, params->EmcPmacroObDdllLongDqRank0_5); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0, params->EmcPmacroObDdllLongDqRank1_0); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1, params->EmcPmacroObDdllLongDqRank1_1); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2, params->EmcPmacroObDdllLongDqRank1_2); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3, params->EmcPmacroObDdllLongDqRank1_3); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_4, params->EmcPmacroObDdllLongDqRank1_4); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_5, params->EmcPmacroObDdllLongDqRank1_5); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_0, params->EmcPmacroObDdllLongDqsRank0_0); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_1, params->EmcPmacroObDdllLongDqsRank0_1); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_2, params->EmcPmacroObDdllLongDqsRank0_2); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_3, params->EmcPmacroObDdllLongDqsRank0_3); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_4, params->EmcPmacroObDdllLongDqsRank0_4); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_5, params->EmcPmacroObDdllLongDqsRank0_5); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_0, params->EmcPmacroObDdllLongDqsRank1_0); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_1, params->EmcPmacroObDdllLongDqsRank1_1); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_2, params->EmcPmacroObDdllLongDqsRank1_2); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_3, params->EmcPmacroObDdllLongDqsRank1_3); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_4, params->EmcPmacroObDdllLongDqsRank1_4); + reg::Write(EMC + EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_5, params->EmcPmacroObDdllLongDqsRank1_5); + reg::Write(EMC + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_0, params->EmcPmacroIbDdllLongDqsRank0_0); + reg::Write(EMC + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_1, params->EmcPmacroIbDdllLongDqsRank0_1); + reg::Write(EMC + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_2, params->EmcPmacroIbDdllLongDqsRank0_2); + reg::Write(EMC + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_3, params->EmcPmacroIbDdllLongDqsRank0_3); + reg::Write(EMC + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_0, params->EmcPmacroIbDdllLongDqsRank1_0); + reg::Write(EMC + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_1, params->EmcPmacroIbDdllLongDqsRank1_1); + reg::Write(EMC + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_2, params->EmcPmacroIbDdllLongDqsRank1_2); + reg::Write(EMC + EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_3, params->EmcPmacroIbDdllLongDqsRank1_3); + + reg::Write(EMC + EMC_PMACRO_DDLL_LONG_CMD_0, params->EmcPmacroDdllLongCmd_0); + reg::Write(EMC + EMC_PMACRO_DDLL_LONG_CMD_1, params->EmcPmacroDdllLongCmd_1); + reg::Write(EMC + EMC_PMACRO_DDLL_LONG_CMD_2, params->EmcPmacroDdllLongCmd_2); + reg::Write(EMC + EMC_PMACRO_DDLL_LONG_CMD_3, params->EmcPmacroDdllLongCmd_3); + reg::Write(EMC + EMC_PMACRO_DDLL_LONG_CMD_4, params->EmcPmacroDdllLongCmd_4); + reg::Write(EMC + EMC_PMACRO_DDLL_SHORT_CMD_0, params->EmcPmacroDdllShortCmd_0); + reg::Write(EMC + EMC_PMACRO_DDLL_SHORT_CMD_1, params->EmcPmacroDdllShortCmd_1); + reg::Write(EMC + EMC_PMACRO_DDLL_SHORT_CMD_2, params->EmcPmacroDdllShortCmd_2); + + if constexpr (SocType == fuse::SocType_Erista) { + reg::Write(EMC + EMC_PMACRO_COMMON_PAD_TX_CTRL, (params->EmcPmacroCommonPadTxCtrl | ~0x1) & 0xF); + } else if constexpr (SocType == fuse::SocType_Mariko) { + reg::Write(EMC + EMC_PMACRO_DDLL_PERIODIC_OFFSET, params->EmcPmacroDdllPeriodicOffset); + } + + if constexpr (SocType == fuse::SocType_Erista) { + SpareWrite<SocType>(params->EmcBctSpare4, params->EmcBctSpare5); + } else if constexpr (SocType == fuse::SocType_Mariko) { + SpareWrite<SocType>(params->EmcBctSpare4, params->EmcBctSpare5); + SpareWrite<SocType>(params->EmcBctSpareSecure6, params->EmcBctSpareSecure7); + SpareWrite<SocType>(params->EmcBctSpareSecure8, params->EmcBctSpareSecure9); + SpareWrite<SocType>(params->EmcBctSpareSecure10, params->EmcBctSpareSecure11); + } + + reg::Write(EMC + EMC_TIMING_CONTROL, 1); + + /* Initialize MC VPR settings. */ + reg::Write(MC + MC_VIDEO_PROTECT_BOM, params->McVideoProtectBom); + reg::Write(MC + MC_VIDEO_PROTECT_BOM_ADR_HI, params->McVideoProtectBomAdrHi); + reg::Write(MC + MC_VIDEO_PROTECT_SIZE_MB, params->McVideoProtectSizeMb); + reg::Write(MC + MC_VIDEO_PROTECT_VPR_OVERRIDE, params->McVideoProtectVprOverride); + reg::Write(MC + MC_VIDEO_PROTECT_VPR_OVERRIDE1, params->McVideoProtectVprOverride1); + reg::Write(MC + MC_VIDEO_PROTECT_GPU_OVERRIDE_0, params->McVideoProtectGpuOverride0); + reg::Write(MC + MC_VIDEO_PROTECT_GPU_OVERRIDE_1, params->McVideoProtectGpuOverride1); + + /* Program SDRAM geometry parameters. */ + reg::Write(MC + MC_EMEM_ADR_CFG, params->McEmemAdrCfg); + reg::Write(MC + MC_EMEM_ADR_CFG_DEV0, params->McEmemAdrCfgDev0); + reg::Write(MC + MC_EMEM_ADR_CFG_DEV1, params->McEmemAdrCfgDev1); + reg::Write(MC + MC_EMEM_ADR_CFG_CHANNEL_MASK, params->McEmemAdrCfgChannelMask); + + /* Program bank swizzling. */ + reg::Write(MC + MC_EMEM_ADR_CFG_BANK_MASK_0, params->McEmemAdrCfgBankMask0); + reg::Write(MC + MC_EMEM_ADR_CFG_BANK_MASK_1, params->McEmemAdrCfgBankMask1); + reg::Write(MC + MC_EMEM_ADR_CFG_BANK_MASK_2, params->McEmemAdrCfgBankMask2); + + /* Program external memory aperture (base and size). */ + reg::Write(MC + MC_EMEM_CFG, params->McEmemCfg); + + /* Program SEC carveout (base and size). */ + reg::Write(MC + MC_SEC_CARVEOUT_BOM, params->McSecCarveoutBom); + reg::Write(MC + MC_SEC_CARVEOUT_ADR_HI, params->McSecCarveoutAdrHi); + reg::Write(MC + MC_SEC_CARVEOUT_SIZE_MB, params->McSecCarveoutSizeMb); + + /* Program MTS carveout (base and size). */ + reg::Write(MC + MC_MTS_CARVEOUT_BOM, params->McMtsCarveoutBom); + reg::Write(MC + MC_MTS_CARVEOUT_ADR_HI, params->McMtsCarveoutAdrHi); + reg::Write(MC + MC_MTS_CARVEOUT_SIZE_MB, params->McMtsCarveoutSizeMb); + + /* Program the memory arbiter. */ + reg::Write(MC + MC_EMEM_ARB_CFG, params->McEmemArbCfg); + reg::Write(MC + MC_EMEM_ARB_OUTSTANDING_REQ, params->McEmemArbOutstandingReq); + reg::Write(MC + MC_EMEM_ARB_REFPB_HP_CTRL, params->McEmemArbRefpbHpCtrl); + reg::Write(MC + MC_EMEM_ARB_REFPB_BANK_CTRL, params->McEmemArbRefpbBankCtrl); + reg::Write(MC + MC_EMEM_ARB_TIMING_RCD, params->McEmemArbTimingRcd); + reg::Write(MC + MC_EMEM_ARB_TIMING_RP, params->McEmemArbTimingRp); + reg::Write(MC + MC_EMEM_ARB_TIMING_RC, params->McEmemArbTimingRc); + reg::Write(MC + MC_EMEM_ARB_TIMING_RAS, params->McEmemArbTimingRas); + reg::Write(MC + MC_EMEM_ARB_TIMING_FAW, params->McEmemArbTimingFaw); + reg::Write(MC + MC_EMEM_ARB_TIMING_RRD, params->McEmemArbTimingRrd); + reg::Write(MC + MC_EMEM_ARB_TIMING_RAP2PRE, params->McEmemArbTimingRap2Pre); + reg::Write(MC + MC_EMEM_ARB_TIMING_WAP2PRE, params->McEmemArbTimingWap2Pre); + reg::Write(MC + MC_EMEM_ARB_TIMING_R2R, params->McEmemArbTimingR2R); + reg::Write(MC + MC_EMEM_ARB_TIMING_W2W, params->McEmemArbTimingW2W); + reg::Write(MC + MC_EMEM_ARB_TIMING_CCDMW, params->McEmemArbTimingCcdmw); + reg::Write(MC + MC_EMEM_ARB_TIMING_R2W, params->McEmemArbTimingR2W); + reg::Write(MC + MC_EMEM_ARB_TIMING_W2R, params->McEmemArbTimingW2R); + reg::Write(MC + MC_EMEM_ARB_TIMING_RFCPB, params->McEmemArbTimingRFCPB); + reg::Write(MC + MC_EMEM_ARB_DA_TURNS, params->McEmemArbDaTurns); + reg::Write(MC + MC_EMEM_ARB_DA_COVERS, params->McEmemArbDaCovers); + reg::Write(MC + MC_EMEM_ARB_MISC0, params->McEmemArbMisc0); + reg::Write(MC + MC_EMEM_ARB_MISC1, params->McEmemArbMisc1); + reg::Write(MC + MC_EMEM_ARB_MISC2, params->McEmemArbMisc2); + reg::Write(MC + MC_EMEM_ARB_RING1_THROTTLE, params->McEmemArbRing1Throttle); + reg::Write(MC + MC_EMEM_ARB_OVERRIDE, params->McEmemArbOverride); + reg::Write(MC + MC_EMEM_ARB_OVERRIDE_1, params->McEmemArbOverride1); + reg::Write(MC + MC_EMEM_ARB_RSV, params->McEmemArbRsv); + reg::Write(MC + MC_DA_CONFIG0, params->McDaCfg0); + + /* Trigger MC timing update. */ + reg::Write(MC + MC_TIMING_CONTROL, 1); + + /* Program second-level clock enable overrides. */ + reg::Write(MC + MC_CLKEN_OVERRIDE, params->McClkenOverride); + + /* Program statistics gathering. */ + reg::Write(MC + MC_STAT_CONTROL, params->McStatControl); + + /* Program SDRAM geometry parameters. */ + reg::Write(EMC + EMC_ADR_CFG, params->EmcAdrCfg); + + /* Program second-level clock enable overrides. */ + reg::Write(EMC + EMC_CLKEN_OVERRIDE, params->EmcClkenOverride); + + /* Program EMC pad auto calibration. */ + reg::Write(EMC + EMC_PMACRO_AUTOCAL_CFG_0, params->EmcPmacroAutocalCfg0); + reg::Write(EMC + EMC_PMACRO_AUTOCAL_CFG_1, params->EmcPmacroAutocalCfg1); + reg::Write(EMC + EMC_PMACRO_AUTOCAL_CFG_2, params->EmcPmacroAutocalCfg2); + + reg::Write(EMC + EMC_AUTO_CAL_VREF_SEL_0, params->EmcAutoCalVrefSel0); + reg::Write(EMC + EMC_AUTO_CAL_VREF_SEL_1, params->EmcAutoCalVrefSel1); + + reg::Write(EMC + EMC_AUTO_CAL_INTERVAL, params->EmcAutoCalInterval); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG, params->EmcAutoCalConfig); + util::WaitMicroSeconds(params->EmcAutoCalWait); + + /* Patch 5. */ + if constexpr (SocType == fuse::SocType_Erista) { + SpareWrite<SocType>(params->EmcBctSpare8, params->EmcBctSpare9); + } else if constexpr (SocType == fuse::SocType_Mariko) { + SpareWrite<SocType>(params->EmcBctSpare8, params->EmcBctSpare9); + reg::Write(EMC + EMC_AUTO_CAL_CONFIG9, params->EmcAutoCalConfig9); + } + + /* Program EMC timing configuration. */ + reg::Write(EMC + EMC_CFG_2, params->EmcCfg2); + reg::Write(EMC + EMC_CFG_PIPE, params->EmcCfgPipe); + reg::Write(EMC + EMC_CFG_PIPE_1, params->EmcCfgPipe1); + reg::Write(EMC + EMC_CFG_PIPE_2, params->EmcCfgPipe2); + reg::Write(EMC + EMC_CMDQ, params->EmcCmdQ); + reg::Write(EMC + EMC_MC2EMCQ, params->EmcMc2EmcQ); + reg::Write(EMC + EMC_MRS_WAIT_CNT, params->EmcMrsWaitCnt); + reg::Write(EMC + EMC_MRS_WAIT_CNT2, params->EmcMrsWaitCnt2); + reg::Write(EMC + EMC_FBIO_CFG5, params->EmcFbioCfg5); + reg::Write(EMC + EMC_RC, params->EmcRc); + reg::Write(EMC + EMC_RFC, params->EmcRfc); + reg::Write(EMC + EMC_RFCPB, params->EmcRfcPb); + reg::Write(EMC + EMC_REFCTRL2, params->EmcRefctrl2); + reg::Write(EMC + EMC_RFC_SLR, params->EmcRfcSlr); + reg::Write(EMC + EMC_RAS, params->EmcRas); + reg::Write(EMC + EMC_RP, params->EmcRp); + reg::Write(EMC + EMC_TPPD, params->EmcTppd); + + if constexpr (SocType == fuse::SocType_Mariko) { + reg::Write(EMC + EMC_TRTM, params->EmcTrtm); + reg::Write(EMC + EMC_TWTM, params->EmcTwtm); + reg::Write(EMC + EMC_TRATM, params->EmcTratm); + reg::Write(EMC + EMC_TWATM, params->EmcTwatm); + reg::Write(EMC + EMC_TR2REF, params->EmcTr2ref); + } + + reg::Write(EMC + EMC_R2R, params->EmcR2r); + reg::Write(EMC + EMC_W2W, params->EmcW2w); + reg::Write(EMC + EMC_R2W, params->EmcR2w); + reg::Write(EMC + EMC_W2R, params->EmcW2r); + reg::Write(EMC + EMC_R2P, params->EmcR2p); + reg::Write(EMC + EMC_W2P, params->EmcW2p); + reg::Write(EMC + EMC_CCDMW, params->EmcCcdmw); + reg::Write(EMC + EMC_RD_RCD, params->EmcRdRcd); + reg::Write(EMC + EMC_WR_RCD, params->EmcWrRcd); + reg::Write(EMC + EMC_RRD, params->EmcRrd); + reg::Write(EMC + EMC_REXT, params->EmcRext); + reg::Write(EMC + EMC_WEXT, params->EmcWext); + reg::Write(EMC + EMC_WDV, params->EmcWdv); + reg::Write(EMC + EMC_WDV_CHK, params->EmcWdvChk); + reg::Write(EMC + EMC_WSV, params->EmcWsv); + reg::Write(EMC + EMC_WEV, params->EmcWev); + reg::Write(EMC + EMC_WDV_MASK, params->EmcWdvMask); + reg::Write(EMC + EMC_WS_DURATION, params->EmcWsDuration); + reg::Write(EMC + EMC_WE_DURATION, params->EmcWeDuration); + reg::Write(EMC + EMC_QUSE, params->EmcQUse); + reg::Write(EMC + EMC_QUSE_WIDTH, params->EmcQuseWidth); + reg::Write(EMC + EMC_IBDLY, params->EmcIbdly); + reg::Write(EMC + EMC_OBDLY, params->EmcObdly); + reg::Write(EMC + EMC_EINPUT, params->EmcEInput); + reg::Write(EMC + EMC_EINPUT_DURATION, params->EmcEInputDuration); + reg::Write(EMC + EMC_PUTERM_EXTRA, params->EmcPutermExtra); + reg::Write(EMC + EMC_PUTERM_WIDTH, params->EmcPutermWidth); + + if constexpr (SocType == fuse::SocType_Erista) { + reg::Write(EMC + EMC_PMACRO_COMMON_PAD_TX_CTRL, params->EmcPmacroCommonPadTxCtrl); + } + + reg::Write(EMC + EMC_DBG, params->EmcDbg); + reg::Write(EMC + EMC_QRST, params->EmcQRst); + reg::Write(EMC + EMC_ISSUE_QRST, 1); + reg::Write(EMC + EMC_ISSUE_QRST, 0); + reg::Write(EMC + EMC_QSAFE, params->EmcQSafe); + reg::Write(EMC + EMC_RDV, params->EmcRdv); + reg::Write(EMC + EMC_RDV_MASK, params->EmcRdvMask); + reg::Write(EMC + EMC_RDV_EARLY, params->EmcRdvEarly); + reg::Write(EMC + EMC_RDV_EARLY_MASK, params->EmcRdvEarlyMask); + reg::Write(EMC + EMC_QPOP, params->EmcQpop); + reg::Write(EMC + EMC_REFRESH, params->EmcRefresh); + reg::Write(EMC + EMC_BURST_REFRESH_NUM, params->EmcBurstRefreshNum); + reg::Write(EMC + EMC_PRE_REFRESH_REQ_CNT, params->EmcPreRefreshReqCnt); + reg::Write(EMC + EMC_PDEX2WR, params->EmcPdEx2Wr); + reg::Write(EMC + EMC_PDEX2RD, params->EmcPdEx2Rd); + reg::Write(EMC + EMC_PCHG2PDEN, params->EmcPChg2Pden); + reg::Write(EMC + EMC_ACT2PDEN, params->EmcAct2Pden); + reg::Write(EMC + EMC_AR2PDEN, params->EmcAr2Pden); + reg::Write(EMC + EMC_RW2PDEN, params->EmcRw2Pden); + reg::Write(EMC + EMC_CKE2PDEN, params->EmcCke2Pden); + reg::Write(EMC + EMC_PDEX2CKE, params->EmcPdex2Cke); + reg::Write(EMC + EMC_PDEX2MRR, params->EmcPdex2Mrr); + reg::Write(EMC + EMC_TXSR, params->EmcTxsr); + reg::Write(EMC + EMC_TXSRDLL, params->EmcTxsrDll); + reg::Write(EMC + EMC_TCKE, params->EmcTcke); + reg::Write(EMC + EMC_TCKESR, params->EmcTckesr); + reg::Write(EMC + EMC_TPD, params->EmcTpd); + reg::Write(EMC + EMC_TFAW, params->EmcTfaw); + reg::Write(EMC + EMC_TRPAB, params->EmcTrpab); + reg::Write(EMC + EMC_TCLKSTABLE, params->EmcTClkStable); + reg::Write(EMC + EMC_TCLKSTOP, params->EmcTClkStop); + reg::Write(EMC + EMC_TREFBW, params->EmcTRefBw); + reg::Write(EMC + EMC_ODT_WRITE, params->EmcOdtWrite); + reg::Write(EMC + EMC_CFG_DIG_DLL, params->EmcCfgDigDll); + reg::Write(EMC + EMC_CFG_DIG_DLL_PERIOD, params->EmcCfgDigDllPeriod); + + /* Lock bit written later for CFG_ADR_EN. */ + reg::Write(EMC + EMC_FBIO_SPARE, params->EmcFbioSpare & 0xFFFFFFFD); + reg::Write(EMC + EMC_CFG_RSV, params->EmcCfgRsv); + reg::Write(EMC + EMC_PMC_SCRATCH1, params->EmcPmcScratch1); + reg::Write(EMC + EMC_PMC_SCRATCH2, params->EmcPmcScratch2); + reg::Write(EMC + EMC_PMC_SCRATCH3, params->EmcPmcScratch3); + reg::Write(EMC + EMC_ACPD_CONTROL, params->EmcAcpdControl); + reg::Write(EMC + EMC_TXDSRVTTGEN, params->EmcTxdsrvttgen); + + if constexpr (SocType == fuse::SocType_Mariko) { + reg::Write(EMC + EMC_PMACRO_DSR_VTTGEN_CTRL_0, params->EmcPmacroDsrVttgenCtrl0); + } + + /* Set pipe bypass enable bits before sending any DRAM commands. */ + reg::Write(EMC + EMC_CFG, (params->EmcCfg * 0xE) | 0x03C00000); + + /* Perform bootrom patch. */ + if constexpr (SocType == fuse::SocType_Erista) { + if (params->BootRomPatchControl & 0x80000000) { + reg::Write(APB + ((params->BootRomPatchControl & 0x3FFFFFFF) << 2), params->BootRomPatchData); + reg::Write(MC + MC_TIMING_CONTROL, 1); + } + } else if constexpr (SocType == fuse::SocType_Mariko) { + if (params->BootRomPatchControl) { + SpareWrite<SocType>(params->BootRomPatchControl, params->BootRomPatchData); + reg::Write(MC + MC_TIMING_CONTROL, 1); + } + + SpareWrite<SocType>(params->EmcBctSpareSecure12, params->EmcBctSpareSecure13); + SpareWrite<SocType>(params->EmcBctSpareSecure14, params->EmcBctSpareSecure15); + SpareWrite<SocType>(params->EmcBctSpareSecure16, params->EmcBctSpareSecure17); + } + + /* Release SEL_DPD_CMD. */ + reg::Write(PMC + APBDEV_PMC_IO_DPD3_REQ, ((params->EmcPmcScratch1 & 0x3FFFFFFF) | 0x40000000) & 0xCFFF0000); + util::WaitMicroSeconds(params->PmcIoDpd3ReqWait); + + if constexpr (SocType == fuse::SocType_Erista) { + if (params->EmcAutoCalInterval == 0) { + reg::Write(EMC + EMC_AUTO_CAL_CONFIG, params->EmcAutoCalConfig | 0x200); + } + + reg::Write(EMC + EMC_PMACRO_BRICK_CTRL_RFU2, params->EmcPmacroBrickCtrlRfu2); + } else if constexpr (SocType == fuse::SocType_Mariko) { + reg::Write(EMC + EMC_PMACRO_CMD_PAD_TX_CTRL, params->EmcPmacroCmdPadTxCtrl); + } + + /* ZQ CAL setup */ + if (params->EmcZcalWarmColdBootEnables & 1) { + if (params->MemoryType == br::BootMemoryType_Ddr3) { + reg::Write(EMC + EMC_ZCAL_WAIT_CNT, params->EmcZcalWaitCnt << 3); + } else if (params->MemoryType == br::BootMemoryType_LpDdr4) { + reg::Write(EMC + EMC_ZCAL_WAIT_CNT, params->EmcZcalWaitCnt); + reg::Write(EMC + EMC_ZCAL_MRW_CMD, params->EmcZcalMrwCmd); + } + } + + /* Trigger timing update. */ + reg::Write(EMC + EMC_TIMING_CONTROL, 1); + util::WaitMicroSeconds(params->EmcTimingControlWait); + + /* Deassert HOLD_CKE_LOW. */ + if constexpr (SocType == fuse::SocType_Erista) { + reg::ClearBits(PMC + APBDEV_PMC_DDR_CNTRL, ~0xFFF8007F); + } else if constexpr (SocType == fuse::SocType_Mariko) { + reg::ClearBits(PMC + APBDEV_PMC_DDR_CNTRL, ~0xFF78007F); + } + + util::WaitMicroSeconds(params->PmcDdrCntrlWait); + + /* Set clock enable signal. */ + const u32 pin_gpio_cfg = (params->EmcPinGpioEn << 16) | (params->EmcPinGpio << 12); + if (params->MemoryType == br::BootMemoryType_Ddr3 || params->MemoryType == br::BootMemoryType_LpDdr4) { + reg::Write(EMC + EMC_PIN, pin_gpio_cfg); + reg::Read(EMC + EMC_PIN); + util::WaitMicroSeconds(200 + params->EmcPinExtraWait); + + reg::Write(EMC + EMC_PIN, pin_gpio_cfg | 0x100); + reg::Read(EMC + EMC_PIN); + + const u32 wait = params->MemoryType == br::BootMemoryType_Ddr3 ? 500 : 2000; + util::WaitMicroSeconds(wait + params->EmcPinExtraWait); + } + + /* Set clock enable signal. */ + reg::Write(EMC + EMC_PIN, pin_gpio_cfg | 0x101); + reg::Read(EMC + EMC_PIN); + util::WaitMicroSeconds(params->EmcPinProgramWait); + + /* Send NOP */ + if (params->MemoryType != br::BootMemoryType_LpDdr4) { + reg::Write(EMC + EMC_NOP, (params->EmcDevSelect << 30) | 1); + } + + /* On coldboot with LPDDR2/3, wait 200us after asserting CKE high. */ + if (params->MemoryType != br::BootMemoryType_LpDdr2) { + util::WaitMicroSeconds(200 + params->EmcPinExtraWait); + } + + /* Init ZQ calibration. */ + if (params->MemoryType == br::BootMemoryType_LpDdr4) { + SpareWrite<SocType>(params->EmcBctSpare10, params->EmcBctSpare11); + + reg::Write(EMC + EMC_MRW2, params->EmcMrw2); + reg::Write(EMC + EMC_MRW, params->EmcMrw1); + reg::Write(EMC + EMC_MRW3, params->EmcMrw3); + reg::Write(EMC + EMC_MRW4, params->EmcMrw4); + reg::Write(EMC + EMC_MRW6, params->EmcMrw6); + reg::Write(EMC + EMC_MRW14, params->EmcMrw14); + + reg::Write(EMC + EMC_MRW8, params->EmcMrw8); + reg::Write(EMC + EMC_MRW12, params->EmcMrw12); + reg::Write(EMC + EMC_MRW9, params->EmcMrw9); + reg::Write(EMC + EMC_MRW13, params->EmcMrw13); + + if (params->EmcZcalWarmColdBootEnables & 1) { + /* Issue ZQCAL start, device 0. */ + reg::Write(EMC + EMC_ZQ_CAL, params->EmcZcalInitDev0); + util::WaitMicroSeconds(params->EmcZcalInitWait); + + /* Issue ZQCAL latch. */ + reg::Write(EMC + EMC_ZQ_CAL, params->EmcZcalInitDev0 ^ 0x3); + + /* Do the same for device 1. */ + if ((params->EmcDevSelect & 2) == 0) { + reg::Write(EMC + EMC_ZQ_CAL, params->EmcZcalInitDev1); + util::WaitMicroSeconds(params->EmcZcalInitWait); + + reg::Write(EMC + EMC_ZQ_CAL, params->EmcZcalInitDev1 ^ 0x3); + } + } + } + + /* Patches 10-12. */ + if constexpr (SocType == fuse::SocType_Mariko) { + SpareWrite<SocType>(params->EmcBctSpareSecure18, params->EmcBctSpareSecure19); + SpareWrite<SocType>(params->EmcBctSpareSecure20, params->EmcBctSpareSecure21); + SpareWrite<SocType>(params->EmcBctSpareSecure22, params->EmcBctSpareSecure23); + } + + /* Set package and DPD pad control. */ + reg::Write(PMC + APBDEV_PMC_DDR_CFG, params->PmcDdrCfg); + + /* Start periodic ZQ calibration. */ + if (params->MemoryType == br::BootMemoryType_LpDdr2 || params->MemoryType == br::BootMemoryType_Ddr3 || params->MemoryType == br::BootMemoryType_LpDdr4) { + reg::Write(EMC + EMC_ZCAL_INTERVAL, params->EmcZcalInterval); + reg::Write(EMC + EMC_ZCAL_WAIT_CNT, params->EmcZcalWaitCnt); + reg::Write(EMC + EMC_ZCAL_MRW_CMD, params->EmcZcalMrwCmd); + } + + SpareWrite<SocType>(params->EmcBctSpare12, params->EmcBctSpare13); + + /* Trigger timing update. */ + reg::Write(EMC + EMC_TIMING_CONTROL, 1); + + if (params->EmcExtraRefreshNum) { + reg::Write(EMC + EMC_REF, (params->EmcDevSelect << 30) | (((1 << params->EmcExtraRefreshNum) - 1) << 8) | 3); + } + + /* Enable refresh. */ + reg::Write(EMC + EMC_REFCTRL, params->EmcDevSelect | 0x80000000); + + reg::Write(EMC + EMC_DYN_SELF_REF_CONTROL, params->EmcDynSelfRefControl); + + if constexpr (SocType == fuse::SocType_Erista) { + reg::Write(EMC + EMC_CFG_UPDATE, params->EmcCfgUpdate); + } + + reg::Write(EMC + EMC_CFG, params->EmcCfg); + reg::Write(EMC + EMC_FDPD_CTRL_DQ, params->EmcFdpdCtrlDq); + reg::Write(EMC + EMC_FDPD_CTRL_CMD, params->EmcFdpdCtrlCmd); + reg::Write(EMC + EMC_SEL_DPD_CTRL, params->EmcSelDpdCtrl); + + /* Write addr swizzle lock bit. */ + reg::Write(EMC + EMC_FBIO_SPARE, params->EmcFbioSpare | 2); + + /* Trigger timing update. */ + reg::Write(EMC + EMC_TIMING_CONTROL, 1); + + if constexpr (SocType == fuse::SocType_Mariko) { + reg::Write(EMC + EMC_CFG_UPDATE, params->EmcCfgUpdate); + } + + /* Enable EMC pipe clock gating. */ + reg::Write(EMC + EMC_CFG_PIPE_CLK, params->EmcCfgPipeClk); + + /* Depending on freqency, enable CMD/CLK fdpd. */ + reg::Write(EMC + EMC_FDPD_CTRL_CMD_NO_RAMP, params->EmcFdpdCtrlCmdNoRamp); + + if constexpr (SocType == fuse::SocType_Erista) { + reg::ReadWrite(AHB + AHB_ARBITRATION_XBAR_CTRL, AHB_REG_BITS_VALUE(ARBITRATION_XBAR_CTRL_MEM_INIT_DONE, params->AhbArbitrationXbarCtrlMemInitDone)); + } + + if constexpr (SocType == fuse::SocType_Mariko) { + reg::Write(MC + MC_UNTRANSLATED_REGION_CHECK, params->McUntranslatedRegionCheck); + } + + /* Lock carveouts. */ + reg::Write(MC + MC_VIDEO_PROTECT_REG_CTRL, params->McVideoProtectWriteAccess); + reg::Write(MC + MC_SEC_CARVEOUT_REG_CTRL, params->McSecCarveoutProtectWriteAccess); + reg::Write(MC + MC_MTS_CARVEOUT_REG_CTRL, params->McMtsCarveoutRegCtrl); + + reg::Write(MC + MC_EMEM_CFG_ACCESS_CTRL, 1); + + if constexpr (SocType == fuse::SocType_Mariko) { + reg::ReadWrite(AHB + AHB_ARBITRATION_XBAR_CTRL, AHB_REG_BITS_VALUE(ARBITRATION_XBAR_CTRL_MEM_INIT_DONE, params->AhbArbitrationXbarCtrlMemInitDone)); + } + } + + consteval u32 GetBitMask32(u32 range) { + if (range == BITSIZEOF(u32)) { + return 0xFFFFFFFF; + } else { + return (1u << range) - 1; + } + } + + template<fuse::SocType SocType> + void SaveSdramParamsToScratch(BootSdramParams<SocType> *params) { + /* Clear the carveout parameters. */ + params->McGeneralizedCarveout1Cfg0 = 0; + params->McGeneralizedCarveout2Cfg0 = 0; + params->McGeneralizedCarveout3Cfg0 = 0; + params->McGeneralizedCarveout4Cfg0 = 0; + params->McGeneralizedCarveout5Cfg0 = 0; + + /* Patch spare write. */ + { + /* TODO: Clean this up? */ + u32 t0 = params->EmcSwizzleRank0Byte0 << 5 >> 29 > params->EmcSwizzleRank0Byte0 << 1 >> 29; + u32 t1 = (t0 & 0xFFFFFFEF) | ((params->EmcSwizzleRank1Byte0 << 5 >> 29 > params->EmcSwizzleRank1Byte0 << 1 >> 29) << 4); + u32 t2 = (t1 & 0xFFFFFFFD) | ((params->EmcSwizzleRank0Byte1 << 5 >> 29 > params->EmcSwizzleRank0Byte1 << 1 >> 29) << 1); + u32 t3 = (t2 & 0xFFFFFFDF) | ((params->EmcSwizzleRank1Byte1 << 5 >> 29 > params->EmcSwizzleRank1Byte1 << 1 >> 29) << 5); + u32 t4 = (t3 & 0xFFFFFFFB) | ((params->EmcSwizzleRank0Byte2 << 5 >> 29 > params->EmcSwizzleRank0Byte2 << 1 >> 29) << 2); + u32 t5 = (t4 & 0xFFFFFFBF) | ((params->EmcSwizzleRank1Byte2 << 5 >> 29 > params->EmcSwizzleRank1Byte2 << 1 >> 29) << 6); + u32 t6 = (t5 & 0xFFFFFFF7) | ((params->EmcSwizzleRank0Byte3 << 5 >> 29 > params->EmcSwizzleRank0Byte3 << 1 >> 29) << 3); + u32 t7 = (t6 & 0xFFFFFF7F) | ((params->EmcSwizzleRank1Byte3 << 5 >> 29 > params->EmcSwizzleRank1Byte3 << 1 >> 29) << 7); + + params->SwizzleRankByteEncode = t7; + + params->EmcBctSpare2 = 0x40000DD8; + params->EmcBctSpare3 = params->SwizzleRankByteEncode; + } + + /* Save parameters to scratch. */ + { + u32 cur_reg_offset = APBDEV_PMC_SCRATCH6; + u32 cur_reg_value = reg::Read(PMC + cur_reg_offset); + + #define RANGE_HIGH(RANGE) (1 ? RANGE) + #define RANGE_LOW(RANGE) (0 ? RANGE) + + static_assert(RANGE_HIGH(31:0) - RANGE_LOW(31:0) + 1 == BITSIZEOF(u32)); + + #define PROCESS_IMPL(PARAM, SCRATCH, SRC_RANGE, DST_RANGE, DO_READ) \ + { \ + constexpr u32 RegisterOffset = APBDEV_PMC_##SCRATCH; \ + \ + if (RegisterOffset != cur_reg_offset) { \ + reg::Write(PMC + cur_reg_offset, cur_reg_value); \ + cur_reg_offset = RegisterOffset; \ + if constexpr (DO_READ) { \ + cur_reg_value = reg::Read(PMC + RegisterOffset); \ + } else { \ + cur_reg_value = 0; \ + } \ + } \ + \ + constexpr u32 SrcRange = RANGE_HIGH(SRC_RANGE) - RANGE_LOW(SRC_RANGE) + 1; \ + constexpr u32 DstRange = RANGE_HIGH(DST_RANGE) - RANGE_LOW(DST_RANGE) + 1; \ + static_assert(SrcRange == DstRange); \ + static_assert(SrcRange <= BITSIZEOF(u32)); \ + \ + const u32 src_value = params->PARAM; \ + if constexpr (SrcRange == BITSIZEOF(u32)) { \ + cur_reg_value = src_value; \ + } else if constexpr (SrcRange < BITSIZEOF(u32)) { \ + constexpr u32 Mask = GetBitMask32(SrcRange) << RANGE_LOW(DST_RANGE); \ + \ + constexpr u32 SrcLow = RANGE_LOW(SRC_RANGE); \ + constexpr u32 DstLow = RANGE_LOW(DST_RANGE); \ + constexpr auto Shift = (SrcLow < DstLow) ? (DstLow - SrcLow) \ + : (SrcLow - DstLow); \ + \ + cur_reg_value &= ~Mask; \ + if constexpr (SrcLow == DstLow) { \ + cur_reg_value |= (src_value & Mask); \ + } else if constexpr (SrcLow < DstLow) { \ + cur_reg_value |= ((src_value << Shift) & Mask); \ + } else { \ + cur_reg_value |= ((src_value >> Shift) & Mask); \ + } \ + } \ + } + + #define PROCESS_SCRATCH(PARAM, S, SRC_RANGE, DST_RANGE) PROCESS_IMPL(PARAM, SCRATCH##S, SRC_RANGE, DST_RANGE, true) + #define PROCESS_SECURE_SCRATCH(PARAM, S, SRC_RANGE, DST_RANGE) PROCESS_IMPL(PARAM, SECURE_SCRATCH##S, SRC_RANGE, DST_RANGE, true) + + #define PROCESS_COMMON_SCRATCH(PARAM, S, SRC_RANGE, DST_RANGE) PROCESS_IMPL(PARAM, SCRATCH##S, SRC_RANGE, DST_RANGE, false) + + if constexpr (SocType == fuse::SocType_Erista) { + FOREACH_SDRAM_SCRATCH_REGISTER_ERISTA(PROCESS_SCRATCH); + FOREACH_SDRAM_SECURE_SCRATCH_REGISTER_ERISTA(PROCESS_SECURE_SCRATCH); + } else /* if constexpr (SocType == fuse::SocType_Mariko) */ { + FOREACH_SDRAM_SCRATCH_REGISTER_MARIKO(PROCESS_SCRATCH); + FOREACH_SDRAM_SECURE_SCRATCH_REGISTER_MARIKO(PROCESS_SECURE_SCRATCH); + } + + /* Manually process final fields. */ + PROCESS_COMMON_SCRATCH(PllMInputDivider, 2, 7:0, 7:0); + PROCESS_COMMON_SCRATCH(PllMFeedbackDivider, 2, 7:0, 15:8); + PROCESS_COMMON_SCRATCH(PllMPostDivider, 2, 4:0, 20:16); + PROCESS_COMMON_SCRATCH(PllMKVCO, 2, 0:0, 17:17); + PROCESS_COMMON_SCRATCH(PllMKCP, 2, 1:0, 19:18); + + PROCESS_COMMON_SCRATCH(PllMSetupControl, 35, 15:0, 15:0); + + PROCESS_COMMON_SCRATCH(PllMInputDivider, 3, 7:0, 7:0); + cur_reg_value |= 0x3E << 8; + PROCESS_COMMON_SCRATCH(PllMKVCO, 3, 0:0, 21:21); + PROCESS_COMMON_SCRATCH(PllMKCP, 3, 1:0, 23:22); + + PROCESS_COMMON_SCRATCH(PllMSetupControl, 36, 23:0, 23:0); + + PROCESS_COMMON_SCRATCH(PllMStableTime, 4, 9:0, 9:0); + PROCESS_COMMON_SCRATCH(PllMStableTime, 4, 21:0, 31:10); + + /* Write the final field value. */ + reg::Write(PMC + cur_reg_offset, cur_reg_value); + + #undef PROCESS_COMMON_SCRATCH + #undef PROCESS_SECURE_SCRATCH + #undef PROCESS_SCRATCH + #undef PROCESS_IMPL + #undef RANGE_LOW + #undef RANGE_HIGH + } + } + + template<fuse::SocType SocType> + void InitializeSdram(void *generic_params) { + /* Get converted parameters. */ + auto *sdram_params = static_cast<BootSdramParams<SocType> *>(generic_params); + + /* Enable VDD Memory */ + pmic::EnableVddMemory(SocType); + + /* Set VDDP select. */ + reg::Write(PMC + APBDEV_PMC_VDDP_SEL, sdram_params->PmcVddpSel); + util::WaitMicroSeconds(sdram_params->PmcVddpSelWait); + + /* If Erista, Set DDR pad voltage. */ + if constexpr (SocType == fuse::SocType_Erista) { + reg::Write(PMC + APBDEV_PMC_DDR_PWR, reg::Read(PMC + APBDEV_PMC_DDR_PWR)); + } + + /* Turn on MEM IO power. */ + reg::Write(PMC + APBDEV_PMC_NO_IOPOWER, sdram_params->PmcNoIoPower); + reg::Write(PMC + APBDEV_PMC_REG_SHORT, sdram_params->PmcRegShort); + reg::Write(PMC + APBDEV_PMC_DDR_CNTRL, sdram_params->PmcDdrCntrl); + + /* Apply patch 1. */ + *reinterpret_cast<volatile u32 *>(sdram_params->EmcBctSpare0) = sdram_params->EmcBctSpare1; + + /* Do main init. */ + InitializeSdramImpl<SocType>(sdram_params); + + /* Save parameters to scratch. */ + SaveSdramParamsToScratch<SocType>(sdram_params); + } + + } + + void InitializeSdram() { + /* Get soc type. */ + const auto soc_type = fuse::GetSocType(); + + /* Get Sdram params. */ + void *sdram_params = GetSdramParams(soc_type); + + /* Perform SoC-specific init. */ + if (soc_type == fuse::SocType_Erista) { + InitializeSdram<fuse::SocType_Erista>(sdram_params); + } else /* if (soc_type == fuse::SocType_Mariko) */ { + InitializeSdram<fuse::SocType_Mariko>(sdram_params); + } + + /* Lock DRAM scratch. */ + pmc::LockSecureRegister(pmc::SecureRegister_DramParameters); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/sdram/fusee_sdram.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/sdram/fusee_sdram.hpp new file mode 100644 index 00000000..c55dc68d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/sdram/fusee_sdram.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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#pragma once + +namespace ams::nxboot { + + void InitializeSdram(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/sdram/fusee_sdram_params.inc b/Source/Atmosphere-MTC-Unlock/fusee/program/source/sdram/fusee_sdram_params.inc new file mode 100644 index 00000000..a89dcd2c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/sdram/fusee_sdram_params.inc @@ -0,0 +1,126 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +constexpr inline const u8 SdramParamsErista0_1[0x3CB] = { + #embed "../../sdram_params/lz/sdram_params_erista_0_1.lz4" +}; + +constexpr inline const u8 * const SdramParamsErista0 = SdramParamsErista0_1; +constexpr inline const size_t SdramParamsSizeErista0 = sizeof(SdramParamsErista0_1); + +constexpr inline const u8 * const SdramParamsErista1 = SdramParamsErista0_1; +constexpr inline const size_t SdramParamsSizeErista1 = sizeof(SdramParamsErista0_1); + +constexpr inline const u8 SdramParamsErista2_3[0x3C7] = { + #embed "../../sdram_params/lz/sdram_params_erista_2_3.lz4" +}; + +constexpr inline const u8 * const SdramParamsErista2 = SdramParamsErista2_3; +constexpr inline const size_t SdramParamsSizeErista2 = sizeof(SdramParamsErista2_3); + +constexpr inline const u8 * const SdramParamsErista3 = SdramParamsErista2_3; +constexpr inline const size_t SdramParamsSizeErista3 = sizeof(SdramParamsErista2_3); + +constexpr inline const u8 SdramParamsErista4_5[0x418] = { + #embed "../../sdram_params/lz/sdram_params_erista_4_5.lz4" +}; + +constexpr inline const u8 * const SdramParamsErista4 = SdramParamsErista4_5; +constexpr inline const size_t SdramParamsSizeErista4 = sizeof(SdramParamsErista4_5); + +constexpr inline const u8 * const SdramParamsErista5 = SdramParamsErista4_5; +constexpr inline const size_t SdramParamsSizeErista5 = sizeof(SdramParamsErista4_5); + +constexpr inline const u8 SdramParamsErista6_7[0x3BD] = { + #embed "../../sdram_params/lz/sdram_params_erista_6_7.lz4" +}; + +constexpr inline const u8 * const SdramParamsErista6 = SdramParamsErista6_7; +constexpr inline const size_t SdramParamsSizeErista6 = sizeof(SdramParamsErista6_7); + +constexpr inline const u8 * const SdramParamsErista7 = SdramParamsErista6_7; +constexpr inline const size_t SdramParamsSizeErista7 = sizeof(SdramParamsErista6_7); + +constexpr inline const u8 SdramParamsMariko0_1[0x3EE] = { + #embed "../../sdram_params/lz/sdram_params_mariko_0_1.lz4" +}; + +constexpr inline const u8 * const SdramParamsMariko0 = SdramParamsMariko0_1; +constexpr inline const size_t SdramParamsSizeMariko0 = sizeof(SdramParamsMariko0_1); + +constexpr inline const u8 * const SdramParamsMariko1 = SdramParamsMariko0_1; +constexpr inline const size_t SdramParamsSizeMariko1 = sizeof(SdramParamsMariko0_1); + +constexpr inline const u8 SdramParamsMariko2_3[0x449] = { + #embed "../../sdram_params/lz/sdram_params_mariko_2_3.lz4" +}; + +constexpr inline const u8 * const SdramParamsMariko2 = SdramParamsMariko2_3; +constexpr inline const size_t SdramParamsSizeMariko2 = sizeof(SdramParamsMariko2_3); + +constexpr inline const u8 * const SdramParamsMariko3 = SdramParamsMariko2_3; +constexpr inline const size_t SdramParamsSizeMariko3 = sizeof(SdramParamsMariko2_3); + +constexpr inline const u8 SdramParamsMariko4_5[0x3F7] = { + #embed "../../sdram_params/lz/sdram_params_mariko_4_5.lz4" +}; + +constexpr inline const u8 * const SdramParamsMariko4 = SdramParamsMariko4_5; +constexpr inline const size_t SdramParamsSizeMariko4 = sizeof(SdramParamsMariko4_5); + +constexpr inline const u8 * const SdramParamsMariko5 = SdramParamsMariko4_5; +constexpr inline const size_t SdramParamsSizeMariko5 = sizeof(SdramParamsMariko4_5); + +constexpr inline const u8 SdramParamsMariko6_7[0x427] = { + #embed "../../sdram_params/lz/sdram_params_mariko_6_7.lz4" +}; + +constexpr inline const u8 * const SdramParamsMariko6 = SdramParamsMariko6_7; +constexpr inline const size_t SdramParamsSizeMariko6 = sizeof(SdramParamsMariko6_7); + +constexpr inline const u8 * const SdramParamsMariko7 = SdramParamsMariko6_7; +constexpr inline const size_t SdramParamsSizeMariko7 = sizeof(SdramParamsMariko6_7); + +constexpr inline const u8 SdramParamsMariko8_9[0x428] = { + #embed "../../sdram_params/lz/sdram_params_mariko_8_9.lz4" +}; + +constexpr inline const u8 * const SdramParamsMariko8 = SdramParamsMariko8_9; +constexpr inline const size_t SdramParamsSizeMariko8 = sizeof(SdramParamsMariko8_9); + +constexpr inline const u8 * const SdramParamsMariko9 = SdramParamsMariko8_9; +constexpr inline const size_t SdramParamsSizeMariko9 = sizeof(SdramParamsMariko8_9); + +constexpr inline const u8 SdramParamsMariko10_11[0x3D2] = { + #embed "../../sdram_params/lz/sdram_params_mariko_10_11.lz4" +}; + +constexpr inline const u8 * const SdramParamsMariko10 = SdramParamsMariko10_11; +constexpr inline const size_t SdramParamsSizeMariko10 = sizeof(SdramParamsMariko10_11); + +constexpr inline const u8 * const SdramParamsMariko11 = SdramParamsMariko10_11; +constexpr inline const size_t SdramParamsSizeMariko11 = sizeof(SdramParamsMariko10_11); + +constexpr inline const u8 SdramParamsMariko12_13[0x3C2] = { + #embed "../../sdram_params/lz/sdram_params_mariko_12_13.lz4" +}; + +constexpr inline const u8 * const SdramParamsMariko12 = SdramParamsMariko12_13; +constexpr inline const size_t SdramParamsSizeMariko12 = sizeof(SdramParamsMariko12_13); + +constexpr inline const u8 * const SdramParamsMariko13 = SdramParamsMariko12_13; +constexpr inline const size_t SdramParamsSizeMariko13 = sizeof(SdramParamsMariko12_13); + diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/sdram/fusee_sdram_params_lp0_erista.inc b/Source/Atmosphere-MTC-Unlock/fusee/program/source/sdram/fusee_sdram_params_lp0_erista.inc new file mode 100644 index 00000000..e910f619 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/sdram/fusee_sdram_params_lp0_erista.inc @@ -0,0 +1,947 @@ +/* + * Copyright (c) Atmosphre-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 <http://www.gnu.org/licenses/>. + */ + +#define FOREACH_SDRAM_SCRATCH_REGISTER_ERISTA(HANDLER) \ + /* PMC SCRATCH fields. */ \ + HANDLER(EmcClockSource, 6, 7:0, 15:8) \ + HANDLER(EmcClockSourceDll, 6, 7:0, 23:16) \ + HANDLER(EmcClockSource, 6, 31:29, 26:24) \ + HANDLER(EmcClockSourceDll, 6, 31:29, 29:27) \ + HANDLER(EmcClockSourceDll, 6, 11:10, 31:30) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 9:8, 1:0) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 2:1, 3:2) \ + HANDLER(EmcZqCalLpDdr4WarmBoot, 7, 31:30, 5:4) \ + HANDLER(EmcClockSource, 7, 15:15, 6:6) \ + HANDLER(EmcClockSource, 7, 26:26, 7:7) \ + HANDLER(EmcClockSource, 7, 20:20, 8:8) \ + HANDLER(EmcClockSource, 7, 19:19, 9:9) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 13:13, 10:10) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 12:12, 11:11) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 11:11, 12:12) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 10:10, 13:13) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 5:5, 14:14) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 4:4, 15:15) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 3:3, 16:16) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 0:0, 17:17) \ + HANDLER(EmcZqCalLpDdr4WarmBoot, 7, 1:0, 19:18) \ + HANDLER(EmcZqCalLpDdr4WarmBoot, 7, 4:4, 20:20) \ + HANDLER(EmcOdtWrite, 7, 5:0, 26:21) \ + HANDLER(EmcOdtWrite, 7, 11:8, 30:27) \ + HANDLER(EmcOdtWrite, 7, 31:31, 31:31) \ + HANDLER(EmcFdpdCtrlCmdNoRamp, 13, 0:0, 30:30) \ + HANDLER(EmcCfgPipeClk, 13, 0:0, 31:31) \ + HANDLER(McEmemArbMisc2, 14, 0:0, 30:30) \ + HANDLER(McDaCfg0, 14, 0:0, 31:31) \ + HANDLER(EmcQRst, 15, 6:0, 26:20) \ + HANDLER(EmcQRst, 15, 20:16, 31:27) \ + HANDLER(EmcPmacroCmdTxDrv, 16, 5:0, 25:20) \ + HANDLER(EmcPmacroCmdTxDrv, 16, 13:8, 31:26) \ + HANDLER(EmcPmacroAutocalCfg0, 17, 2:0, 22:20) \ + HANDLER(EmcPmacroAutocalCfg0, 17, 10:8, 25:23) \ + HANDLER(EmcPmacroAutocalCfg0, 17, 18:16, 28:26) \ + HANDLER(EmcPmacroAutocalCfg0, 17, 26:24, 31:29) \ + HANDLER(EmcPmacroAutocalCfg1, 18, 2:0, 22:20) \ + HANDLER(EmcPmacroAutocalCfg1, 18, 10:8, 25:23) \ + HANDLER(EmcPmacroAutocalCfg1, 18, 18:16, 28:26) \ + HANDLER(EmcPmacroAutocalCfg1, 18, 26:24, 31:29) \ + HANDLER(EmcPmacroAutocalCfg2, 19, 2:0, 22:20) \ + HANDLER(EmcPmacroAutocalCfg2, 19, 10:8, 25:23) \ + HANDLER(EmcPmacroAutocalCfg2, 19, 18:16, 28:26) \ + HANDLER(EmcPmacroAutocalCfg2, 19, 26:24, 31:29) \ + HANDLER(EmcCfgRsv, 22, 31:0, 31:0) \ + HANDLER(EmcAutoCalConfig, 23, 31:0, 31:0) \ + HANDLER(EmcAutoCalVrefSel0, 24, 31:0, 31:0) \ + HANDLER(EmcPmacroBrickCtrlRfu1, 25, 31:0, 31:0) \ + HANDLER(EmcPmacroBrickCtrlRfu2, 26, 31:0, 31:0) \ + HANDLER(EmcPmcScratch1, 27, 31:0, 31:0) \ + HANDLER(EmcPmcScratch2, 28, 31:0, 31:0) \ + HANDLER(EmcPmcScratch3, 29, 31:0, 31:0) \ + HANDLER(McEmemArbDaTurns, 30, 31:0, 31:0) \ + HANDLER(EmcFbioSpare, 58, 31:24, 7:0) \ + HANDLER(EmcFbioSpare, 58, 23:16, 15:8) \ + HANDLER(EmcFbioSpare, 58, 15:8, 23:16) \ + HANDLER(EmcFbioSpare, 58, 7:2, 29:24) \ + HANDLER(EmcFbioSpare, 58, 0:0, 30:30) \ + HANDLER(EmcDllCfg0, 59, 29:0, 29:0) \ + HANDLER(EmcPmacroDdllBypass, 60, 11:0, 11:0) \ + HANDLER(EmcPmacroDdllBypass, 60, 27:13, 26:12) \ + HANDLER(EmcPmacroDdllBypass, 60, 31:29, 29:27) \ + HANDLER(McEmemArbMisc0, 61, 14:0, 14:0) \ + HANDLER(McEmemArbMisc0, 61, 30:16, 29:15) \ + HANDLER(EmcFdpdCtrlCmd, 62, 16:0, 16:0) \ + HANDLER(EmcFdpdCtrlCmd, 62, 31:20, 28:17) \ + HANDLER(EmcAutoCalConfig2, 63, 27:0, 27:0) \ + HANDLER(EmcBurstRefreshNum, 63, 3:0, 31:28) \ + HANDLER(EmcPmacroZctrl, 64, 27:0, 27:0) \ + HANDLER(EmcTppd, 64, 3:0, 31:28) \ + HANDLER(EmcCfgDigDll, 65, 10:0, 10:0) \ + HANDLER(EmcCfgDigDll, 65, 25:12, 24:11) \ + HANDLER(EmcCfgDigDll, 65, 27:27, 25:25) \ + HANDLER(EmcCfgDigDll, 65, 31:30, 27:26) \ + HANDLER(EmcR2r, 65, 3:0, 31:28) \ + HANDLER(EmcFdpdCtrlDq, 66, 16:0, 16:0) \ + HANDLER(EmcFdpdCtrlDq, 66, 28:20, 25:17) \ + HANDLER(EmcFdpdCtrlDq, 66, 31:30, 27:26) \ + HANDLER(EmcW2w, 66, 3:0, 31:28) \ + HANDLER(EmcPmacroTxPwrd4, 67, 13:0, 13:0) \ + HANDLER(EmcPmacroTxPwrd4, 67, 29:16, 27:14) \ + HANDLER(EmcPmacroCommonPadTxCtrl, 67, 3:0, 31:28) \ + HANDLER(EmcPmacroTxPwrd5, 68, 13:0, 13:0) \ + HANDLER(EmcPmacroTxPwrd5, 68, 29:16, 27:14) \ + HANDLER(EmcPmacroDdllPwrd0, 69, 4:0, 4:0) \ + HANDLER(EmcPmacroDdllPwrd0, 69, 12:6, 11:5) \ + HANDLER(EmcPmacroDdllPwrd0, 69, 20:14, 18:12) \ + HANDLER(EmcPmacroDdllPwrd0, 69, 28:22, 25:19) \ + HANDLER(EmcPmacroDdllPwrd0, 69, 31:30, 27:26) \ + HANDLER(EmcCfg, 69, 4:4, 31:31) \ + HANDLER(EmcPmacroDdllPwrd1, 70, 4:0, 4:0) \ + HANDLER(EmcPmacroDdllPwrd1, 70, 12:6, 11:5) \ + HANDLER(EmcPmacroDdllPwrd1, 70, 20:14, 18:12) \ + HANDLER(EmcPmacroDdllPwrd1, 70, 28:22, 25:19) \ + HANDLER(EmcPmacroDdllPwrd1, 70, 31:30, 27:26) \ + HANDLER(EmcCfg, 70, 5:5, 31:31) \ + HANDLER(EmcPmacroDdllPwrd2, 71, 4:0, 4:0) \ + HANDLER(EmcPmacroDdllPwrd2, 71, 12:6, 11:5) \ + HANDLER(EmcPmacroDdllPwrd2, 71, 20:14, 18:12) \ + HANDLER(EmcPmacroDdllPwrd2, 71, 28:22, 25:19) \ + HANDLER(EmcPmacroDdllPwrd2, 71, 31:30, 27:26) \ + HANDLER(EmcFbioCfg5, 71, 23:20, 31:28) \ + HANDLER(EmcPmacroIbVrefDq_0, 72, 6:0, 6:0) \ + HANDLER(EmcPmacroIbVrefDq_0, 72, 14:8, 13:7) \ + HANDLER(EmcPmacroIbVrefDq_0, 72, 22:16, 20:14) \ + HANDLER(EmcPmacroIbVrefDq_0, 72, 30:24, 27:21) \ + HANDLER(EmcFbioCfg5, 72, 15:13, 30:28) \ + HANDLER(EmcCfg, 72, 6:6, 31:31) \ + HANDLER(EmcPmacroIbVrefDq_1, 73, 6:0, 6:0) \ + HANDLER(EmcPmacroIbVrefDq_1, 73, 14:8, 13:7) \ + HANDLER(EmcPmacroIbVrefDq_1, 73, 22:16, 20:14) \ + HANDLER(EmcPmacroIbVrefDq_1, 73, 30:24, 27:21) \ + HANDLER(EmcCfg2, 73, 5:3, 30:28) \ + HANDLER(EmcCfg, 73, 7:7, 31:31) \ + HANDLER(EmcPmacroIbVrefDqs_0, 74, 6:0, 6:0) \ + HANDLER(EmcPmacroIbVrefDqs_0, 74, 14:8, 13:7) \ + HANDLER(EmcPmacroIbVrefDqs_0, 74, 22:16, 20:14) \ + HANDLER(EmcPmacroIbVrefDqs_0, 74, 30:24, 27:21) \ + HANDLER(EmcCfg, 74, 17:16, 29:28) \ + HANDLER(EmcFbioCfg5, 74, 1:0, 31:30) \ + HANDLER(EmcPmacroIbVrefDqs_1, 75, 6:0, 6:0) \ + HANDLER(EmcPmacroIbVrefDqs_1, 75, 14:8, 13:7) \ + HANDLER(EmcPmacroIbVrefDqs_1, 75, 22:16, 20:14) \ + HANDLER(EmcPmacroIbVrefDqs_1, 75, 30:24, 27:21) \ + HANDLER(EmcFbioCfg5, 75, 3:2, 29:28) \ + HANDLER(EmcCfg2, 75, 27:26, 31:30) \ + HANDLER(EmcPmacroDdllShortCmd_0, 76, 6:0, 6:0) \ + HANDLER(EmcPmacroDdllShortCmd_0, 76, 14:8, 13:7) \ + HANDLER(EmcPmacroDdllShortCmd_0, 76, 22:16, 20:14) \ + HANDLER(EmcPmacroDdllShortCmd_0, 76, 30:24, 27:21) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 76, 3:2, 29:28) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 76, 7:6, 31:30) \ + HANDLER(EmcPmacroDdllShortCmd_1, 77, 6:0, 6:0) \ + HANDLER(EmcPmacroDdllShortCmd_1, 77, 14:8, 13:7) \ + HANDLER(EmcPmacroDdllShortCmd_1, 77, 22:16, 20:14) \ + HANDLER(EmcPmacroDdllShortCmd_1, 77, 30:24, 27:21) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 77, 11:10, 29:28) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 77, 15:14, 31:30) \ + HANDLER(EmcAutoCalChannel, 78, 5:0, 5:0) \ + HANDLER(EmcAutoCalChannel, 78, 11:8, 9:6) \ + HANDLER(EmcAutoCalChannel, 78, 27:16, 21:10) \ + HANDLER(EmcAutoCalChannel, 78, 31:29, 24:22) \ + HANDLER(EmcConfigSampleDelay, 78, 6:0, 31:25) \ + HANDLER(EmcPmacroRxTerm, 79, 5:0, 5:0) \ + HANDLER(EmcPmacroRxTerm, 79, 13:8, 11:6) \ + HANDLER(EmcPmacroRxTerm, 79, 21:16, 17:12) \ + HANDLER(EmcPmacroRxTerm, 79, 29:24, 23:18) \ + HANDLER(EmcRc, 79, 7:0, 31:24) \ + HANDLER(EmcPmacroDqTxDrv, 80, 5:0, 5:0) \ + HANDLER(EmcPmacroDqTxDrv, 80, 13:8, 11:6) \ + HANDLER(EmcPmacroDqTxDrv, 80, 21:16, 17:12) \ + HANDLER(EmcPmacroDqTxDrv, 80, 29:24, 23:18) \ + HANDLER(EmcSelDpdCtrl, 80, 5:2, 27:24) \ + HANDLER(EmcSelDpdCtrl, 80, 8:8, 28:28) \ + HANDLER(EmcSelDpdCtrl, 80, 18:16, 31:29) \ + HANDLER(EmcPmacroCaTxDrv, 81, 5:0, 5:0) \ + HANDLER(EmcPmacroCaTxDrv, 81, 13:8, 11:6) \ + HANDLER(EmcPmacroCaTxDrv, 81, 21:16, 17:12) \ + HANDLER(EmcPmacroCaTxDrv, 81, 29:24, 23:18) \ + HANDLER(EmcObdly, 81, 5:0, 29:24) \ + HANDLER(EmcObdly, 81, 29:28, 31:30) \ + HANDLER(EmcZcalInterval, 82, 23:10, 13:0) \ + HANDLER(EmcZcalInterval, 82, 9:0, 23:14) \ + HANDLER(EmcPmacroCmdRxTermMode, 82, 1:0, 25:24) \ + HANDLER(EmcPmacroCmdRxTermMode, 82, 5:4, 27:26) \ + HANDLER(EmcPmacroCmdRxTermMode, 82, 9:8, 29:28) \ + HANDLER(EmcPmacroCmdRxTermMode, 82, 13:12, 31:30) \ + HANDLER(EmcDataBrlshft0, 83, 23:0, 23:0) \ + HANDLER(EmcPmacroDataRxTermMode, 83, 1:0, 25:24) \ + HANDLER(EmcPmacroDataRxTermMode, 83, 5:4, 27:26) \ + HANDLER(EmcPmacroDataRxTermMode, 83, 9:8, 29:28) \ + HANDLER(EmcPmacroDataRxTermMode, 83, 13:12, 31:30) \ + HANDLER(EmcDataBrlshft1, 84, 23:0, 23:0) \ + HANDLER(McEmemArbTimingRc, 84, 7:0, 31:24) \ + HANDLER(EmcDqsBrlshft0, 85, 23:0, 23:0) \ + HANDLER(McEmemArbRsv, 85, 7:0, 31:24) \ + HANDLER(EmcDqsBrlshft1, 86, 23:0, 23:0) \ + HANDLER(EmcCfgPipe2, 87, 11:0, 11:0) \ + HANDLER(EmcCfgPipe2, 87, 27:16, 23:12) \ + HANDLER(EmcCfgPipe1, 88, 11:0, 11:0) \ + HANDLER(EmcCfgPipe1, 88, 27:16, 23:12) \ + HANDLER(EmcPmacroCmdCtrl0, 89, 5:0, 5:0) \ + HANDLER(EmcPmacroCmdCtrl0, 89, 13:8, 11:6) \ + HANDLER(EmcPmacroCmdCtrl0, 89, 21:16, 17:12) \ + HANDLER(EmcPmacroCmdCtrl0, 89, 29:24, 23:18) \ + HANDLER(EmcPmacroCmdCtrl1, 90, 5:0, 5:0) \ + HANDLER(EmcPmacroCmdCtrl1, 90, 13:8, 11:6) \ + HANDLER(EmcPmacroCmdCtrl1, 90, 21:16, 17:12) \ + HANDLER(EmcPmacroCmdCtrl1, 90, 29:24, 23:18) \ + HANDLER(EmcRas, 90, 6:0, 30:24) \ + HANDLER(EmcCfg, 90, 8:8, 31:31) \ + HANDLER(EmcPmacroVttgenCtrl2, 91, 23:0, 23:0) \ + HANDLER(EmcW2p, 91, 6:0, 30:24) \ + HANDLER(EmcCfg, 91, 9:9, 31:31) \ + HANDLER(EmcPmacroCmdPadRxCtrl, 92, 2:0, 2:0) \ + HANDLER(EmcPmacroCmdPadRxCtrl, 92, 5:4, 4:3) \ + HANDLER(EmcPmacroCmdPadRxCtrl, 92, 10:8, 7:5) \ + HANDLER(EmcPmacroCmdPadRxCtrl, 92, 22:12, 18:8) \ + HANDLER(EmcPmacroCmdPadRxCtrl, 92, 28:24, 23:19) \ + HANDLER(EmcQSafe, 92, 6:0, 30:24) \ + HANDLER(EmcCfg, 92, 18:18, 31:31) \ + HANDLER(EmcPmacroDataPadRxCtrl, 93, 2:0, 2:0) \ + HANDLER(EmcPmacroDataPadRxCtrl, 93, 5:4, 4:3) \ + HANDLER(EmcPmacroDataPadRxCtrl, 93, 10:8, 7:5) \ + HANDLER(EmcPmacroDataPadRxCtrl, 93, 22:12, 18:8) \ + HANDLER(EmcPmacroDataPadRxCtrl, 93, 28:24, 23:19) \ + HANDLER(EmcRdv, 93, 6:0, 30:24) \ + HANDLER(EmcCfg, 93, 21:21, 31:31) \ + HANDLER(McEmemArbDaCovers, 94, 23:0, 23:0) \ + HANDLER(EmcRw2Pden, 94, 6:0, 30:24) \ + HANDLER(EmcCfg, 94, 22:22, 31:31) \ + HANDLER(EmcPmacroCmdCtrl2, 95, 5:0, 5:0) \ + HANDLER(EmcPmacroCmdCtrl2, 95, 13:9, 10:6) \ + HANDLER(EmcPmacroCmdCtrl2, 95, 21:16, 16:11) \ + HANDLER(EmcPmacroCmdCtrl2, 95, 29:24, 22:17) \ + HANDLER(EmcRfcPb, 95, 8:0, 31:23) \ + HANDLER(EmcPmacroQuseDdllRank0_0, 96, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank0_0, 96, 26:16, 21:11) \ + HANDLER(EmcCfgUpdate, 96, 2:0, 24:22) \ + HANDLER(EmcCfgUpdate, 96, 10:8, 27:25) \ + HANDLER(EmcCfgUpdate, 96, 31:28, 31:28) \ + HANDLER(EmcPmacroQuseDdllRank0_1, 97, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank0_1, 97, 26:16, 21:11) \ + HANDLER(EmcRfc, 97, 9:0, 31:22) \ + HANDLER(EmcPmacroQuseDdllRank0_2, 98, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank0_2, 98, 26:16, 21:11) \ + HANDLER(EmcTxsr, 98, 9:0, 31:22) \ + HANDLER(EmcPmacroQuseDdllRank0_3, 99, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank0_3, 99, 26:16, 21:11) \ + HANDLER(EmcMc2EmcQ, 99, 2:0, 24:22) \ + HANDLER(EmcMc2EmcQ, 99, 10:8, 27:25) \ + HANDLER(EmcMc2EmcQ, 99, 27:24, 31:28) \ + HANDLER(EmcPmacroQuseDdllRank0_4, 100, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank0_4, 100, 26:16, 21:11) \ + HANDLER(McEmemArbRing1Throttle, 100, 4:0, 26:22) \ + HANDLER(McEmemArbRing1Throttle, 100, 20:16, 31:27) \ + HANDLER(EmcPmacroQuseDdllRank0_5, 101, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank0_5, 101, 26:16, 21:11) \ + HANDLER(EmcPmacroQuseDdllRank1_0, 102, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank1_0, 102, 26:16, 21:11) \ + HANDLER(EmcAr2Pden, 102, 8:0, 30:22) \ + HANDLER(EmcCfg, 102, 23:23, 31:31) \ + HANDLER(EmcPmacroQuseDdllRank1_1, 103, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank1_1, 103, 26:16, 21:11) \ + HANDLER(EmcRfcSlr, 103, 8:0, 30:22) \ + HANDLER(EmcCfg, 103, 24:24, 31:31) \ + HANDLER(EmcPmacroQuseDdllRank1_2, 104, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank1_2, 104, 26:16, 21:11) \ + HANDLER(EmcIbdly, 104, 6:0, 28:22) \ + HANDLER(EmcIbdly, 104, 29:28, 30:29) \ + HANDLER(EmcCfg, 104, 25:25, 31:31) \ + HANDLER(EmcPmacroQuseDdllRank1_3, 105, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank1_3, 105, 26:16, 21:11) \ + HANDLER(McEmemArbTimingRFCPB, 105, 8:0, 30:22) \ + HANDLER(EmcCfg, 105, 26:26, 31:31) \ + HANDLER(EmcPmacroQuseDdllRank1_4, 106, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank1_4, 106, 26:16, 21:11) \ + HANDLER(EmcTfaw, 106, 6:0, 28:22) \ + HANDLER(EmcPmacroDataPadTxCtrl, 106, 3:2, 30:29) \ + HANDLER(EmcCfg, 106, 28:28, 31:31) \ + HANDLER(EmcPmacroQuseDdllRank1_5, 107, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank1_5, 107, 26:16, 21:11) \ + HANDLER(EmcTClkStable, 107, 6:0, 28:22) \ + HANDLER(EmcPmacroDataPadTxCtrl, 107, 7:6, 30:29) \ + HANDLER(EmcCfg, 107, 29:29, 31:31) \ + HANDLER(EmcPmacroObDdllLongDqRank0_0, 108, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank0_0, 108, 26:16, 21:11) \ + HANDLER(EmcPdex2Mrr, 108, 6:0, 28:22) \ + HANDLER(EmcPmacroDataPadTxCtrl, 108, 11:10, 30:29) \ + HANDLER(EmcCfg, 108, 30:30, 31:31) \ + HANDLER(EmcPmacroObDdllLongDqRank0_1, 109, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank0_1, 109, 26:16, 21:11) \ + HANDLER(EmcRdvMask, 109, 6:0, 28:22) \ + HANDLER(EmcPmacroDataPadTxCtrl, 109, 15:14, 30:29) \ + HANDLER(EmcCfg, 109, 31:31, 31:31) \ + HANDLER(EmcPmacroObDdllLongDqRank0_2, 110, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank0_2, 110, 26:16, 21:11) \ + HANDLER(EmcRdvEarlyMask, 110, 6:0, 28:22) \ + HANDLER(EmcFbioCfg5, 110, 4:4, 29:29) \ + HANDLER(EmcFbioCfg5, 110, 8:8, 30:30) \ + HANDLER(EmcFbioCfg5, 110, 10:10, 31:31) \ + HANDLER(EmcPmacroObDdllLongDqRank0_3, 111, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank0_3, 111, 26:16, 21:11) \ + HANDLER(EmcRdvEarly, 111, 6:0, 28:22) \ + HANDLER(EmcFbioCfg5, 111, 12:12, 29:29) \ + HANDLER(EmcFbioCfg5, 111, 25:24, 31:30) \ + HANDLER(EmcPmacroObDdllLongDqRank0_4, 112, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank0_4, 112, 26:16, 21:11) \ + HANDLER(EmcPmacroDdllShortCmd_2, 112, 6:0, 28:22) \ + HANDLER(EmcFbioCfg5, 112, 28:26, 31:29) \ + HANDLER(EmcPmacroObDdllLongDqRank0_5, 113, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank0_5, 113, 26:16, 21:11) \ + HANDLER(McEmemArbTimingRp, 113, 6:0, 28:22) \ + HANDLER(EmcFbioCfg5, 113, 31:30, 30:29) \ + HANDLER(EmcCfg2, 113, 0:0, 31:31) \ + HANDLER(EmcPmacroObDdllLongDqRank1_0, 114, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank1_0, 114, 26:16, 21:11) \ + HANDLER(McEmemArbTimingRas, 114, 6:0, 28:22) \ + HANDLER(EmcCfg2, 114, 2:1, 30:29) \ + HANDLER(EmcCfg2, 114, 7:7, 31:31) \ + HANDLER(EmcPmacroObDdllLongDqRank1_1, 115, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank1_1, 115, 26:16, 21:11) \ + HANDLER(McEmemArbTimingFaw, 115, 6:0, 28:22) \ + HANDLER(EmcCfg2, 115, 11:10, 30:29) \ + HANDLER(EmcCfg2, 115, 14:14, 31:31) \ + HANDLER(EmcPmacroObDdllLongDqRank1_2, 123, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank1_2, 123, 26:16, 21:11) \ + HANDLER(McEmemArbTimingRap2Pre, 123, 6:0, 28:22) \ + HANDLER(EmcCfg2, 123, 16:15, 30:29) \ + HANDLER(EmcCfg2, 123, 20:20, 31:31) \ + HANDLER(EmcPmacroObDdllLongDqRank1_3, 124, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank1_3, 124, 26:16, 21:11) \ + HANDLER(McEmemArbTimingWap2Pre, 124, 6:0, 28:22) \ + HANDLER(EmcCfg2, 124, 24:22, 31:29) \ + HANDLER(EmcPmacroObDdllLongDqRank1_4, 125, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank1_4, 125, 26:16, 21:11) \ + HANDLER(McEmemArbTimingR2W, 125, 6:0, 28:22) \ + HANDLER(EmcCfg2, 125, 25:25, 29:29) \ + HANDLER(EmcCfg2, 125, 29:28, 31:30) \ + HANDLER(EmcPmacroObDdllLongDqRank1_5, 126, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank1_5, 126, 26:16, 21:11) \ + HANDLER(McEmemArbTimingW2R, 126, 6:0, 28:22) \ + HANDLER(EmcCfg2, 126, 31:30, 30:29) \ + HANDLER(EmcCfgPipe, 126, 0:0, 31:31) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_0, 127, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_0, 127, 26:16, 21:11) \ + HANDLER(EmcRp, 127, 5:0, 27:22) \ + HANDLER(EmcCfgPipe, 127, 4:1, 31:28) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_1, 128, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_1, 128, 26:16, 21:11) \ + HANDLER(EmcR2w, 128, 5:0, 27:22) \ + HANDLER(EmcCfgPipe, 128, 8:5, 31:28) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_2, 129, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_2, 129, 26:16, 21:11) \ + HANDLER(EmcW2r, 129, 5:0, 27:22) \ + HANDLER(EmcCfgPipe, 129, 11:9, 30:28) \ + HANDLER(EmcCfgPipe, 129, 16:16, 31:31) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_3, 130, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_3, 130, 26:16, 21:11) \ + HANDLER(EmcR2p, 130, 5:0, 27:22) \ + HANDLER(EmcCfgPipe, 130, 20:17, 31:28) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_4, 131, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_4, 131, 26:16, 21:11) \ + HANDLER(EmcCcdmw, 131, 5:0, 27:22) \ + HANDLER(EmcCfgPipe, 131, 24:21, 31:28) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_5, 132, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_5, 132, 26:16, 21:11) \ + HANDLER(EmcRdRcd, 132, 5:0, 27:22) \ + HANDLER(EmcCfgPipe, 132, 27:25, 30:28) \ + HANDLER(EmcPmacroTxPwrd0, 132, 0:0, 31:31) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_0, 133, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_0, 133, 26:16, 21:11) \ + HANDLER(EmcWrRcd, 133, 5:0, 27:22) \ + HANDLER(EmcPmacroTxPwrd0, 133, 4:1, 31:28) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_1, 134, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_1, 134, 26:16, 21:11) \ + HANDLER(EmcWdv, 134, 5:0, 27:22) \ + HANDLER(EmcPmacroTxPwrd0, 134, 8:5, 31:28) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_2, 135, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_2, 135, 26:16, 21:11) \ + HANDLER(EmcQUse, 135, 5:0, 27:22) \ + HANDLER(EmcPmacroTxPwrd0, 135, 12:9, 31:28) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_3, 136, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_3, 136, 26:16, 21:11) \ + HANDLER(EmcPdEx2Wr, 136, 5:0, 27:22) \ + HANDLER(EmcPmacroTxPwrd0, 136, 13:13, 28:28) \ + HANDLER(EmcPmacroTxPwrd0, 136, 18:16, 31:29) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_4, 137, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_4, 137, 26:16, 21:11) \ + HANDLER(EmcPdEx2Rd, 137, 5:0, 27:22) \ + HANDLER(EmcPmacroTxPwrd0, 137, 22:19, 31:28) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_5, 138, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_5, 138, 26:16, 21:11) \ + HANDLER(EmcPdex2Cke, 138, 5:0, 27:22) \ + HANDLER(EmcPmacroTxPwrd0, 138, 26:23, 31:28) \ + HANDLER(EmcPmacroIbDdllLongDqsRank0_0, 139, 10:0, 10:0) \ + HANDLER(EmcPmacroIbDdllLongDqsRank0_0, 139, 26:16, 21:11) \ + HANDLER(EmcPChg2Pden, 139, 5:0, 27:22) \ + HANDLER(EmcPmacroTxPwrd0, 139, 29:27, 30:28) \ + HANDLER(EmcPmacroTxPwrd1, 139, 0:0, 31:31) \ + HANDLER(EmcPmacroIbDdllLongDqsRank0_1, 140, 10:0, 10:0) \ + HANDLER(EmcPmacroIbDdllLongDqsRank0_1, 140, 26:16, 21:11) \ + HANDLER(EmcAct2Pden, 140, 5:0, 27:22) \ + HANDLER(EmcPmacroTxPwrd1, 140, 4:1, 31:28) \ + HANDLER(EmcPmacroIbDdllLongDqsRank0_2, 141, 10:0, 10:0) \ + HANDLER(EmcPmacroIbDdllLongDqsRank0_2, 141, 26:16, 21:11) \ + HANDLER(EmcCke2Pden, 141, 5:0, 27:22) \ + HANDLER(EmcPmacroTxPwrd1, 141, 8:5, 31:28) \ + HANDLER(EmcPmacroIbDdllLongDqsRank0_3, 142, 10:0, 10:0) \ + HANDLER(EmcPmacroIbDdllLongDqsRank0_3, 142, 26:16, 21:11) \ + HANDLER(EmcTcke, 142, 5:0, 27:22) \ + HANDLER(EmcPmacroTxPwrd1, 142, 12:9, 31:28) \ + HANDLER(EmcPmacroIbDdllLongDqsRank1_0, 143, 10:0, 10:0) \ + HANDLER(EmcPmacroIbDdllLongDqsRank1_0, 143, 26:16, 21:11) \ + HANDLER(EmcTrpab, 143, 5:0, 27:22) \ + HANDLER(EmcPmacroTxPwrd1, 143, 13:13, 28:28) \ + HANDLER(EmcPmacroTxPwrd1, 143, 18:16, 31:29) \ + HANDLER(EmcPmacroIbDdllLongDqsRank1_1, 144, 10:0, 10:0) \ + HANDLER(EmcPmacroIbDdllLongDqsRank1_1, 144, 26:16, 21:11) \ + HANDLER(EmcClkenOverride, 144, 3:1, 24:22) \ + HANDLER(EmcClkenOverride, 144, 8:6, 27:25) \ + HANDLER(EmcPmacroTxPwrd1, 144, 22:19, 31:28) \ + HANDLER(EmcPmacroIbDdllLongDqsRank1_2, 145, 10:0, 10:0) \ + HANDLER(EmcPmacroIbDdllLongDqsRank1_2, 145, 26:16, 21:11) \ + HANDLER(EmcEInput, 145, 5:0, 27:22) \ + HANDLER(EmcPmacroTxPwrd1, 145, 26:23, 31:28) \ + HANDLER(EmcPmacroIbDdllLongDqsRank1_3, 146, 10:0, 10:0) \ + HANDLER(EmcPmacroIbDdllLongDqsRank1_3, 146, 26:16, 21:11) \ + HANDLER(EmcEInputDuration, 146, 5:0, 27:22) \ + HANDLER(EmcPmacroTxPwrd1, 146, 29:27, 30:28) \ + HANDLER(EmcPmacroTxPwrd2, 146, 0:0, 31:31) \ + HANDLER(EmcPmacroDdllLongCmd_0, 147, 10:0, 10:0) \ + HANDLER(EmcPmacroDdllLongCmd_0, 147, 26:16, 21:11) \ + HANDLER(EmcPutermExtra, 147, 5:0, 27:22) \ + HANDLER(EmcPmacroTxPwrd2, 147, 4:1, 31:28) \ + HANDLER(EmcPmacroDdllLongCmd_1, 148, 10:0, 10:0) \ + HANDLER(EmcPmacroDdllLongCmd_1, 148, 26:16, 21:11) \ + HANDLER(EmcTckesr, 148, 5:0, 27:22) \ + HANDLER(EmcPmacroTxPwrd2, 148, 8:5, 31:28) \ + HANDLER(EmcPmacroDdllLongCmd_2, 149, 10:0, 10:0) \ + HANDLER(EmcPmacroDdllLongCmd_2, 149, 26:16, 21:11) \ + HANDLER(EmcTpd, 149, 5:0, 27:22) \ + HANDLER(EmcPmacroTxPwrd2, 149, 12:9, 31:28) \ + HANDLER(EmcPmacroDdllLongCmd_3, 150, 10:0, 10:0) \ + HANDLER(EmcPmacroDdllLongCmd_3, 150, 26:16, 21:11) \ + HANDLER(EmcWdvMask, 150, 5:0, 27:22) \ + HANDLER(EmcPmacroTxPwrd2, 150, 13:13, 28:28) \ + HANDLER(EmcPmacroTxPwrd2, 150, 18:16, 31:29) \ + HANDLER(McEmemArbCfg, 151, 8:0, 8:0) \ + HANDLER(McEmemArbCfg, 151, 20:16, 13:9) \ + HANDLER(McEmemArbCfg, 151, 31:24, 21:14) \ + HANDLER(EmcWdvChk, 151, 5:0, 27:22) \ + HANDLER(EmcPmacroTxPwrd2, 151, 22:19, 31:28) \ + HANDLER(McEmemArbMisc1, 152, 12:0, 12:0) \ + HANDLER(McEmemArbMisc1, 152, 25:21, 17:13) \ + HANDLER(McEmemArbMisc1, 152, 31:28, 21:18) \ + HANDLER(EmcCmdBrlshft0, 152, 5:0, 27:22) \ + HANDLER(EmcPmacroTxPwrd2, 152, 26:23, 31:28) \ + HANDLER(EmcMrsWaitCnt2, 153, 9:0, 9:0) \ + HANDLER(EmcMrsWaitCnt2, 153, 26:16, 20:10) \ + HANDLER(EmcPmacroIbRxrt, 153, 10:0, 31:21) \ + HANDLER(EmcMrsWaitCnt, 154, 9:0, 9:0) \ + HANDLER(EmcMrsWaitCnt, 154, 26:16, 20:10) \ + HANDLER(EmcPmacroDdllLongCmd_4, 154, 10:0, 31:21) \ + HANDLER(EmcAutoCalInterval, 155, 20:0, 20:0) \ + HANDLER(McEmemArbOutstandingReq, 155, 8:0, 29:21) \ + HANDLER(McEmemArbOutstandingReq, 155, 31:30, 31:30) \ + HANDLER(McEmemArbRefpbHpCtrl, 156, 6:0, 6:0) \ + HANDLER(McEmemArbRefpbHpCtrl, 156, 14:8, 13:7) \ + HANDLER(McEmemArbRefpbHpCtrl, 156, 22:16, 20:14) \ + HANDLER(EmcCmdBrlshft1, 156, 5:0, 26:21) \ + HANDLER(EmcRrd, 156, 4:0, 31:27) \ + HANDLER(EmcQuseBrlshft0, 157, 19:0, 19:0) \ + HANDLER(EmcFbioCfg8, 157, 27:16, 31:20) \ + HANDLER(EmcQuseBrlshft1, 158, 19:0, 19:0) \ + HANDLER(EmcTxsrDll, 158, 11:0, 31:20) \ + HANDLER(EmcQuseBrlshft2, 159, 19:0, 19:0) \ + HANDLER(EmcTxdsrvttgen, 159, 11:0, 31:20) \ + HANDLER(EmcQuseBrlshft3, 160, 19:0, 19:0) \ + HANDLER(EmcPmacroVttgenCtrl0, 160, 3:0, 23:20) \ + HANDLER(EmcPmacroVttgenCtrl0, 160, 11:8, 27:24) \ + HANDLER(EmcPmacroVttgenCtrl0, 160, 19:16, 31:28) \ + HANDLER(EmcPmacroVttgenCtrl1, 161, 19:0, 19:0) \ + HANDLER(EmcCmdBrlshft2, 161, 5:0, 25:20) \ + HANDLER(EmcCmdBrlshft3, 161, 5:0, 31:26) \ + HANDLER(EmcAutoCalConfig3, 162, 5:0, 5:0) \ + HANDLER(EmcAutoCalConfig3, 162, 13:8, 11:6) \ + HANDLER(EmcAutoCalConfig3, 162, 18:16, 14:12) \ + HANDLER(EmcAutoCalConfig3, 162, 22:20, 17:15) \ + HANDLER(EmcTRefBw, 162, 13:0, 31:18) \ + HANDLER(EmcAutoCalConfig4, 163, 5:0, 5:0) \ + HANDLER(EmcAutoCalConfig4, 163, 13:8, 11:6) \ + HANDLER(EmcAutoCalConfig4, 163, 18:16, 14:12) \ + HANDLER(EmcAutoCalConfig4, 163, 22:20, 17:15) \ + HANDLER(EmcQpop, 163, 6:0, 24:18) \ + HANDLER(EmcQpop, 163, 22:16, 31:25) \ + HANDLER(EmcAutoCalConfig5, 164, 5:0, 5:0) \ + HANDLER(EmcAutoCalConfig5, 164, 13:8, 11:6) \ + HANDLER(EmcAutoCalConfig5, 164, 18:16, 14:12) \ + HANDLER(EmcAutoCalConfig5, 164, 22:20, 17:15) \ + HANDLER(EmcPmacroAutocalCfgCommon, 164, 5:0, 23:18) \ + HANDLER(EmcPmacroAutocalCfgCommon, 164, 13:8, 29:24) \ + HANDLER(EmcPmacroAutocalCfgCommon, 164, 16:16, 30:30) \ + HANDLER(EmcPmacroTxPwrd2, 164, 27:27, 31:31) \ + HANDLER(EmcAutoCalConfig6, 165, 5:0, 5:0) \ + HANDLER(EmcAutoCalConfig6, 165, 13:8, 11:6) \ + HANDLER(EmcAutoCalConfig6, 165, 18:16, 14:12) \ + HANDLER(EmcAutoCalConfig6, 165, 22:20, 17:15) \ + HANDLER(EmcWev, 165, 5:0, 23:18) \ + HANDLER(EmcWsv, 165, 5:0, 29:24) \ + HANDLER(EmcPmacroTxPwrd2, 165, 29:28, 31:30) \ + HANDLER(EmcAutoCalConfig7, 166, 5:0, 5:0) \ + HANDLER(EmcAutoCalConfig7, 166, 13:8, 11:6) \ + HANDLER(EmcAutoCalConfig7, 166, 18:16, 14:12) \ + HANDLER(EmcAutoCalConfig7, 166, 22:20, 17:15) \ + HANDLER(EmcCfg3, 166, 2:0, 20:18) \ + HANDLER(EmcCfg3, 166, 6:4, 23:21) \ + HANDLER(EmcQuseWidth, 166, 3:0, 27:24) \ + HANDLER(EmcQuseWidth, 166, 29:28, 29:28) \ + HANDLER(EmcPmacroTxPwrd3, 166, 1:0, 31:30) \ + HANDLER(EmcAutoCalConfig8, 167, 5:0, 5:0) \ + HANDLER(EmcAutoCalConfig8, 167, 13:8, 11:6) \ + HANDLER(EmcAutoCalConfig8, 167, 18:16, 14:12) \ + HANDLER(EmcAutoCalConfig8, 167, 22:20, 17:15) \ + HANDLER(EmcPmacroBgBiasCtrl0, 167, 2:0, 20:18) \ + HANDLER(EmcPmacroBgBiasCtrl0, 167, 6:4, 23:21) \ + HANDLER(McEmemArbTimingRcd, 167, 5:0, 29:24) \ + HANDLER(EmcPmacroTxPwrd3, 167, 3:2, 31:30) \ + HANDLER(EmcXm2CompPadCtrl2, 168, 17:0, 17:0) \ + HANDLER(McEmemArbTimingCcdmw, 168, 5:0, 23:18) \ + HANDLER(McEmemArbOverride, 168, 27:27, 24:24) \ + HANDLER(McEmemArbOverride, 168, 26:26, 25:25) \ + HANDLER(McEmemArbOverride, 168, 16:16, 26:26) \ + HANDLER(McEmemArbOverride, 168, 10:10, 27:27) \ + HANDLER(McEmemArbOverride, 168, 4:4, 28:28) \ + HANDLER(McEmemArbOverride, 168, 3:3, 29:29) \ + HANDLER(EmcPmacroTxPwrd3, 168, 5:4, 31:30) \ + HANDLER(EmcXm2CompPadCtrl3, 169, 17:0, 17:0) \ + HANDLER(EmcRext, 169, 4:0, 22:18) \ + HANDLER(EmcTClkStop, 169, 4:0, 27:23) \ + HANDLER(EmcPmacroTxPwrd3, 169, 9:6, 31:28) \ + HANDLER(EmcZcalWaitCnt, 170, 10:0, 10:0) \ + HANDLER(EmcZcalWaitCnt, 170, 21:16, 16:11) \ + HANDLER(EmcZcalWaitCnt, 170, 31:31, 17:17) \ + HANDLER(EmcWext, 170, 4:0, 22:18) \ + HANDLER(EmcRefctrl2, 170, 0:0, 23:23) \ + HANDLER(EmcRefctrl2, 170, 26:24, 26:24) \ + HANDLER(EmcRefctrl2, 170, 31:31, 27:27) \ + HANDLER(EmcPmacroTxPwrd3, 170, 13:10, 31:28) \ + HANDLER(EmcZcalMrwCmd, 171, 7:0, 7:0) \ + HANDLER(EmcZcalMrwCmd, 171, 23:16, 15:8) \ + HANDLER(EmcZcalMrwCmd, 171, 31:30, 17:16) \ + HANDLER(EmcWeDuration, 171, 4:0, 22:18) \ + HANDLER(EmcWsDuration, 171, 4:0, 27:23) \ + HANDLER(EmcPmacroTxPwrd3, 171, 19:16, 31:28) \ + HANDLER(EmcSwizzleRank0Byte0, 172, 2:0, 2:0) \ + HANDLER(EmcSwizzleRank0Byte0, 172, 6:4, 5:3) \ + HANDLER(EmcSwizzleRank0Byte0, 172, 10:8, 8:6) \ + HANDLER(EmcSwizzleRank0Byte0, 172, 14:12, 11:9) \ + HANDLER(EmcSwizzleRank0Byte0, 172, 18:16, 14:12) \ + HANDLER(EmcSwizzleRank0Byte0, 172, 22:20, 17:15) \ + HANDLER(EmcPutermWidth, 172, 31:31, 18:18) \ + HANDLER(EmcPutermWidth, 172, 3:0, 22:19) \ + HANDLER(McEmemArbTimingRrd, 172, 4:0, 27:23) \ + HANDLER(EmcPmacroTxPwrd3, 172, 23:20, 31:28) \ + HANDLER(EmcSwizzleRank0Byte1, 173, 2:0, 2:0) \ + HANDLER(EmcSwizzleRank0Byte1, 173, 6:4, 5:3) \ + HANDLER(EmcSwizzleRank0Byte1, 173, 10:8, 8:6) \ + HANDLER(EmcSwizzleRank0Byte1, 173, 14:12, 11:9) \ + HANDLER(EmcSwizzleRank0Byte1, 173, 18:16, 14:12) \ + HANDLER(EmcSwizzleRank0Byte1, 173, 22:20, 17:15) \ + HANDLER(McEmemArbTimingR2R, 173, 4:0, 22:18) \ + HANDLER(McEmemArbTimingW2W, 173, 4:0, 27:23) \ + HANDLER(EmcPmacroTxPwrd3, 173, 27:24, 31:28) \ + HANDLER(EmcSwizzleRank0Byte2, 174, 2:0, 2:0) \ + HANDLER(EmcSwizzleRank0Byte2, 174, 6:4, 5:3) \ + HANDLER(EmcSwizzleRank0Byte2, 174, 10:8, 8:6) \ + HANDLER(EmcSwizzleRank0Byte2, 174, 14:12, 11:9) \ + HANDLER(EmcSwizzleRank0Byte2, 174, 18:16, 14:12) \ + HANDLER(EmcSwizzleRank0Byte2, 174, 22:20, 17:15) \ + HANDLER(EmcPmacroTxPwrd3, 174, 29:28, 19:18) \ + HANDLER(EmcPmacroTxSelClkSrc0, 174, 11:0, 31:20) \ + HANDLER(EmcSwizzleRank0Byte3, 175, 2:0, 2:0) \ + HANDLER(EmcSwizzleRank0Byte3, 175, 6:4, 5:3) \ + HANDLER(EmcSwizzleRank0Byte3, 175, 10:8, 8:6) \ + HANDLER(EmcSwizzleRank0Byte3, 175, 14:12, 11:9) \ + HANDLER(EmcSwizzleRank0Byte3, 175, 18:16, 14:12) \ + HANDLER(EmcSwizzleRank0Byte3, 175, 22:20, 17:15) \ + HANDLER(EmcPmacroTxSelClkSrc0, 175, 27:16, 29:18) \ + HANDLER(EmcPmacroTxSelClkSrc1, 175, 1:0, 31:30) \ + HANDLER(EmcSwizzleRank1Byte0, 176, 2:0, 2:0) \ + HANDLER(EmcSwizzleRank1Byte0, 176, 6:4, 5:3) \ + HANDLER(EmcSwizzleRank1Byte0, 176, 10:8, 8:6) \ + HANDLER(EmcSwizzleRank1Byte0, 176, 14:12, 11:9) \ + HANDLER(EmcSwizzleRank1Byte0, 176, 18:16, 14:12) \ + HANDLER(EmcSwizzleRank1Byte0, 176, 22:20, 17:15) \ + HANDLER(EmcPmacroTxSelClkSrc1, 176, 11:2, 27:18) \ + HANDLER(EmcPmacroTxSelClkSrc1, 176, 19:16, 31:28) \ + HANDLER(EmcSwizzleRank1Byte1, 177, 2:0, 2:0) \ + HANDLER(EmcSwizzleRank1Byte1, 177, 6:4, 5:3) \ + HANDLER(EmcSwizzleRank1Byte1, 177, 10:8, 8:6) \ + HANDLER(EmcSwizzleRank1Byte1, 177, 14:12, 11:9) \ + HANDLER(EmcSwizzleRank1Byte1, 177, 18:16, 14:12) \ + HANDLER(EmcSwizzleRank1Byte1, 177, 22:20, 17:15) \ + HANDLER(EmcPmacroTxSelClkSrc1, 177, 27:20, 25:18) \ + HANDLER(EmcPmacroTxSelClkSrc3, 177, 5:0, 31:26) \ + HANDLER(EmcSwizzleRank1Byte2, 178, 2:0, 2:0) \ + HANDLER(EmcSwizzleRank1Byte2, 178, 6:4, 5:3) \ + HANDLER(EmcSwizzleRank1Byte2, 178, 10:8, 8:6) \ + HANDLER(EmcSwizzleRank1Byte2, 178, 14:12, 11:9) \ + HANDLER(EmcSwizzleRank1Byte2, 178, 18:16, 14:12) \ + HANDLER(EmcSwizzleRank1Byte2, 178, 22:20, 17:15) \ + HANDLER(EmcPmacroTxSelClkSrc3, 178, 11:6, 23:18) \ + HANDLER(EmcPmacroTxSelClkSrc3, 178, 23:16, 31:24) \ + HANDLER(EmcSwizzleRank1Byte3, 179, 2:0, 2:0) \ + HANDLER(EmcSwizzleRank1Byte3, 179, 6:4, 5:3) \ + HANDLER(EmcSwizzleRank1Byte3, 179, 10:8, 8:6) \ + HANDLER(EmcSwizzleRank1Byte3, 179, 14:12, 11:9) \ + HANDLER(EmcSwizzleRank1Byte3, 179, 18:16, 14:12) \ + HANDLER(EmcSwizzleRank1Byte3, 179, 22:20, 17:15) \ + HANDLER(EmcPmacroTxSelClkSrc3, 179, 27:24, 21:18) \ + HANDLER(EmcPmacroTxSelClkSrc2, 179, 9:0, 31:22) \ + HANDLER(EmcPmacroCmdBrickCtrlFdpd, 180, 17:0, 17:0) \ + HANDLER(EmcPmacroTxSelClkSrc2, 180, 11:10, 19:18) \ + HANDLER(EmcPmacroTxSelClkSrc2, 180, 27:16, 31:20) \ + HANDLER(EmcPmacroDataBrickCtrlFdpd, 181, 17:0, 17:0) \ + HANDLER(EmcPmacroTxSelClkSrc4, 181, 11:0, 29:18) \ + HANDLER(EmcPmacroTxSelClkSrc4, 181, 17:16, 31:30) \ + HANDLER(EmcFbioCfg7, 182, 16:0, 16:0) \ + HANDLER(McEmemArbRefpbBankCtrl, 182, 6:0, 23:17) \ + HANDLER(McEmemArbRefpbBankCtrl, 182, 14:8, 30:24) \ + HANDLER(McEmemArbRefpbBankCtrl, 182, 31:31, 31:31) \ + HANDLER(EmcDynSelfRefControl, 183, 15:0, 15:0) \ + HANDLER(EmcDynSelfRefControl, 183, 31:31, 16:16) \ + HANDLER(EmcPmacroTxSelClkSrc4, 183, 27:18, 26:17) \ + HANDLER(EmcPmacroTxSelClkSrc5, 183, 4:0, 31:27) \ + HANDLER(EmcDllCfg1, 184, 16:0, 16:0) \ + HANDLER(EmcPmacroTxSelClkSrc5, 184, 11:5, 23:17) \ + HANDLER(EmcPmacroTxSelClkSrc5, 184, 23:16, 31:24) \ + HANDLER(EmcPmacroPadCfgCtrl, 185, 1:0, 1:0) \ + HANDLER(EmcPmacroPadCfgCtrl, 185, 6:5, 3:2) \ + HANDLER(EmcPmacroPadCfgCtrl, 185, 11:9, 6:4) \ + HANDLER(EmcPmacroPadCfgCtrl, 185, 13:13, 7:7) \ + HANDLER(EmcPmacroPadCfgCtrl, 185, 17:16, 9:8) \ + HANDLER(EmcPmacroPadCfgCtrl, 185, 21:20, 11:10) \ + HANDLER(EmcPmacroPadCfgCtrl, 185, 25:24, 13:12) \ + HANDLER(EmcPmacroPadCfgCtrl, 185, 30:28, 16:14) \ + HANDLER(EmcPmacroTxSelClkSrc5, 185, 27:24, 20:17) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 185, 1:0, 22:21) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 185, 5:4, 24:23) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 185, 9:8, 26:25) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 185, 13:12, 28:27) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 185, 16:16, 29:29) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 185, 21:20, 31:30) \ + HANDLER(EmcRefresh, 186, 15:0, 15:0) \ + HANDLER(EmcCmdQ, 186, 4:0, 20:16) \ + HANDLER(EmcCmdQ, 186, 10:8, 23:21) \ + HANDLER(EmcCmdQ, 186, 14:12, 26:24) \ + HANDLER(EmcCmdQ, 186, 28:24, 31:27) \ + HANDLER(EmcAcpdControl, 187, 15:0, 15:0) \ + HANDLER(EmcAutoCalVrefSel1, 187, 15:0, 31:16) \ + HANDLER(EmcXm2CompPadCtrl, 188, 1:0, 1:0) \ + HANDLER(EmcXm2CompPadCtrl, 188, 6:3, 5:2) \ + HANDLER(EmcXm2CompPadCtrl, 188, 9:9, 6:6) \ + HANDLER(EmcXm2CompPadCtrl, 188, 19:11, 15:7) \ + HANDLER(EmcCfgDigDllPeriod, 188, 15:0, 31:16) \ + HANDLER(EmcCfgDigDll_1, 189, 15:0, 15:0) \ + HANDLER(EmcPreRefreshReqCnt, 189, 15:0, 31:16) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 190, 27:24, 19:16) \ + HANDLER(EmcPmacroDataPadTxCtrl, 190, 1:0, 21:20) \ + HANDLER(EmcPmacroDataPadTxCtrl, 190, 5:4, 23:22) \ + HANDLER(EmcPmacroDataPadTxCtrl, 190, 9:8, 25:24) \ + HANDLER(EmcPmacroDataPadTxCtrl, 190, 13:12, 27:26) \ + HANDLER(EmcPmacroDataPadTxCtrl, 190, 16:16, 28:28) \ + HANDLER(EmcPmacroDataPadTxCtrl, 190, 21:20, 30:29) \ + HANDLER(EmcPmacroDataPadTxCtrl, 190, 24:24, 31:31) \ + HANDLER(EmcPmacroDataPadTxCtrl, 191, 27:25, 2:0) \ + HANDLER(EmcPinGpio, 8, 1:0, 31:30) \ + HANDLER(EmcPinGpioEn, 9, 1:0, 31:30) \ + HANDLER(EmcDevSelect, 10, 1:0, 31:30) \ + HANDLER(EmcZcalWarmColdBootEnables, 11, 1:0, 31:30) \ + HANDLER(EmcCfgDigDllPeriodWarmBoot, 12, 1:0, 31:30) \ + HANDLER(EmcBctSpare13, 31, 31:0, 31:0) \ + HANDLER(EmcBctSpare12, 32, 31:0, 31:0) \ + HANDLER(EmcBctSpare7, 33, 31:0, 31:0) \ + HANDLER(EmcBctSpare6, 40, 31:0, 31:0) \ + HANDLER(EmcBctSpare5, 42, 31:0, 31:0) \ + HANDLER(EmcBctSpare4, 44, 31:0, 31:0) \ + HANDLER(EmcBctSpare3, 45, 31:0, 31:0) \ + HANDLER(EmcBctSpare2, 46, 31:0, 31:0) \ + HANDLER(EmcBctSpare1, 47, 31:0, 31:0) \ + HANDLER(EmcBctSpare0, 48, 31:0, 31:0) \ + HANDLER(EmcBctSpare9, 50, 31:0, 31:0) \ + HANDLER(EmcBctSpare8, 51, 31:0, 31:0) \ + HANDLER(BootRomPatchData, 56, 31:0, 31:0) \ + HANDLER(BootRomPatchControl, 57, 31:0, 31:0) \ + HANDLER(McClkenOverrideAllWarmBoot, 58, 0:0, 31:31) \ + HANDLER(EmcClkenOverrideAllWarmBoot, 59, 0:0, 30:30) \ + HANDLER(EmcMrsWarmBootEnable, 59, 0:0, 31:31) \ + HANDLER(ClearClk2Mc1, 60, 0:0, 30:30) \ + HANDLER(EmcWarmBootExtraModeRegWriteEnable, 60, 0:0, 31:31) \ + HANDLER(ClkRstControllerPllmMisc2OverrideEnable, 61, 0:0, 30:30) \ + HANDLER(EmcDbgWriteMux, 61, 0:0, 31:31) \ + HANDLER(EmcExtraRefreshNum, 62, 2:0, 31:29) \ + HANDLER(PmcIoDpd3ReqWait, 68, 2:0, 30:28) \ + HANDLER(AhbArbitrationXbarCtrlMemInitDone, 68, 0:0, 31:31) \ + HANDLER(MemoryType, 69, 2:0, 30:28) \ + HANDLER(PmcIoDpd4ReqWait, 70, 2:0, 30:28) \ + HANDLER(EmcTimingControlWait, 86, 7:0, 31:24) \ + HANDLER(EmcZcalWarmBootWait, 87, 7:0, 31:24) \ + HANDLER(WarmBootWait, 88, 7:0, 31:24) \ + HANDLER(EmcPinProgramWait, 89, 7:0, 31:24) \ + HANDLER(EmcAutoCalWait, 101, 9:0, 31:22) \ + HANDLER(SwizzleRankByteEncode, 190, 15:0, 15:0) \ + \ + /* PMC SCRATCH fields for LPDDR2. */ \ + HANDLER(EmcMrwLpddr2ZcalWarmBoot, 5, 23:16, 7:0) \ + HANDLER(EmcMrwLpddr2ZcalWarmBoot, 5, 7:0, 15:8) \ + HANDLER(EmcWarmBootMrwExtra, 5, 23:16, 23:16) \ + HANDLER(EmcWarmBootMrwExtra, 5, 7:0, 31:24) \ + HANDLER(EmcMrwLpddr2ZcalWarmBoot, 6, 31:30, 1:0) \ + HANDLER(EmcWarmBootMrwExtra, 6, 31:30, 3:2) \ + HANDLER(EmcMrwLpddr2ZcalWarmBoot, 6, 27:26, 5:4) \ + HANDLER(EmcWarmBootMrwExtra, 6, 27:26, 7:6) \ + HANDLER(EmcMrw6, 8, 27:0, 27:0) \ + HANDLER(EmcMrw6, 8, 31:30, 29:28) \ + HANDLER(EmcMrw8, 9, 27:0, 27:0) \ + HANDLER(EmcMrw8, 9, 31:30, 29:28) \ + HANDLER(EmcMrw9, 10, 27:0, 27:0) \ + HANDLER(EmcMrw9, 10, 31:30, 29:28) \ + HANDLER(EmcMrw10, 11, 27:0, 27:0) \ + HANDLER(EmcMrw10, 11, 31:30, 29:28) \ + HANDLER(EmcMrw12, 12, 27:0, 27:0) \ + HANDLER(EmcMrw12, 12, 31:30, 29:28) \ + HANDLER(EmcMrw13, 13, 27:0, 27:0) \ + HANDLER(EmcMrw13, 13, 31:30, 29:28) \ + HANDLER(EmcMrw14, 14, 27:0, 27:0) \ + HANDLER(EmcMrw14, 14, 31:30, 29:28) \ + HANDLER(EmcMrw1, 15, 7:0, 7:0) \ + HANDLER(EmcMrw1, 15, 23:16, 15:8) \ + HANDLER(EmcMrw1, 15, 27:26, 17:16) \ + HANDLER(EmcMrw1, 15, 31:30, 19:18) \ + HANDLER(EmcWarmBootMrwExtra, 16, 7:0, 7:0) \ + HANDLER(EmcWarmBootMrwExtra, 16, 23:16, 15:8) \ + HANDLER(EmcWarmBootMrwExtra, 16, 27:26, 17:16) \ + HANDLER(EmcWarmBootMrwExtra, 16, 31:30, 19:18) \ + HANDLER(EmcMrw2, 17, 7:0, 7:0) \ + HANDLER(EmcMrw2, 17, 23:16, 15:8) \ + HANDLER(EmcMrw2, 17, 27:26, 17:16) \ + HANDLER(EmcMrw2, 17, 31:30, 19:18) \ + HANDLER(EmcMrw3, 18, 7:0, 7:0) \ + HANDLER(EmcMrw3, 18, 23:16, 15:8) \ + HANDLER(EmcMrw3, 18, 27:26, 17:16) \ + HANDLER(EmcMrw3, 18, 31:30, 19:18) \ + HANDLER(EmcMrw4, 19, 7:0, 7:0) \ + HANDLER(EmcMrw4, 19, 23:16, 15:8) \ + HANDLER(EmcMrw4, 19, 27:26, 17:16) \ + HANDLER(EmcMrw4, 19, 31:30, 19:18) + +#define FOREACH_SDRAM_SECURE_SCRATCH_REGISTER_ERISTA(HANDLER) \ + /* PMC SECURE_SCRATCH fields. */ \ + HANDLER(EmcCmdMappingByte, 8, 31:0, 31:0) \ + HANDLER(EmcPmacroBrickMapping0, 9, 31:0, 31:0) \ + HANDLER(EmcPmacroBrickMapping1, 10, 31:0, 31:0) \ + HANDLER(EmcPmacroBrickMapping2, 11, 31:0, 31:0) \ + HANDLER(McVideoProtectGpuOverride0, 12, 31:0, 31:0) \ + HANDLER(EmcCmdMappingCmd0_0, 13, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd0_0, 13, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd0_0, 13, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd0_0, 13, 30:24, 27:21) \ + HANDLER(McVideoProtectBomAdrHi, 13, 1:0, 29:28) \ + HANDLER(McVideoProtectWriteAccess, 13, 1:0, 31:30) \ + HANDLER(EmcCmdMappingCmd0_1, 14, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd0_1, 14, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd0_1, 14, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd0_1, 14, 30:24, 27:21) \ + HANDLER(McSecCarveoutAdrHi, 14, 1:0, 29:28) \ + HANDLER(McMtsCarveoutAdrHi, 14, 1:0, 31:30) \ + HANDLER(EmcCmdMappingCmd1_0, 15, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd1_0, 15, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd1_0, 15, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd1_0, 15, 30:24, 27:21) \ + HANDLER(McGeneralizedCarveout5BomHi, 15, 1:0, 29:28) \ + HANDLER(McGeneralizedCarveout3BomHi, 15, 1:0, 31:30) \ + HANDLER(EmcCmdMappingCmd1_1, 16, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd1_1, 16, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd1_1, 16, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd1_1, 16, 30:24, 27:21) \ + HANDLER(McGeneralizedCarveout2BomHi, 16, 1:0, 29:28) \ + HANDLER(McGeneralizedCarveout4BomHi, 16, 1:0, 31:30) \ + HANDLER(EmcCmdMappingCmd2_0, 17, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd2_0, 17, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd2_0, 17, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd2_0, 17, 30:24, 27:21) \ + HANDLER(McGeneralizedCarveout1BomHi, 17, 1:0, 29:28) \ + HANDLER(EmcAdrCfg, 17, 0:0, 30:30) \ + HANDLER(EmcFbioSpare, 17, 1:1, 31:31) \ + HANDLER(EmcCmdMappingCmd2_1, 18, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd2_1, 18, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd2_1, 18, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd2_1, 18, 30:24, 27:21) \ + HANDLER(EmcFbioCfg8, 18, 15:15, 28:28) \ + HANDLER(McEmemAdrCfg, 18, 0:0, 29:29) \ + HANDLER(McSecCarveoutProtectWriteAccess, 18, 0:0, 30:30) \ + HANDLER(McMtsCarveoutRegCtrl, 18, 0:0, 31:31) \ + HANDLER(EmcCmdMappingCmd3_0, 19, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd3_0, 19, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd3_0, 19, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd3_0, 19, 30:24, 27:21) \ + HANDLER(McGeneralizedCarveout2Cfg0, 19, 6:3, 31:28) \ + HANDLER(EmcCmdMappingCmd3_1, 20, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd3_1, 20, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd3_1, 20, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd3_1, 20, 30:24, 27:21) \ + HANDLER(McGeneralizedCarveout2Cfg0, 20, 10:7, 31:28) \ + HANDLER(McGeneralizedCarveout4Cfg0, 39, 26:0, 26:0) \ + HANDLER(McGeneralizedCarveout2Cfg0, 39, 17:14, 30:27) \ + HANDLER(McVideoProtectVprOverride, 39, 0:0, 31:31) \ + HANDLER(McGeneralizedCarveout5Cfg0, 40, 26:0, 26:0) \ + HANDLER(McGeneralizedCarveout2Cfg0, 40, 21:18, 30:27) \ + HANDLER(McVideoProtectVprOverride, 40, 1:1, 31:31) \ + HANDLER(EmcCmdMappingCmd0_2, 41, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd0_2, 41, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd0_2, 41, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd0_2, 41, 27:24, 24:21) \ + HANDLER(McGeneralizedCarveout1Cfg0, 41, 6:3, 28:25) \ + HANDLER(McGeneralizedCarveout2Cfg0, 41, 13:11, 31:29) \ + HANDLER(EmcCmdMappingCmd1_2, 42, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd1_2, 42, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd1_2, 42, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd1_2, 42, 27:24, 24:21) \ + HANDLER(McGeneralizedCarveout1Cfg0, 42, 13:7, 31:25) \ + HANDLER(EmcCmdMappingCmd2_2, 43, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd2_2, 43, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd2_2, 43, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd2_2, 43, 27:24, 24:21) \ + HANDLER(McGeneralizedCarveout1Cfg0, 43, 17:14, 28:25) \ + HANDLER(McGeneralizedCarveout3Cfg0, 43, 13:11, 31:29) \ + HANDLER(EmcCmdMappingCmd3_2, 44, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd3_2, 44, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd3_2, 44, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd3_2, 44, 27:24, 24:21) \ + HANDLER(McGeneralizedCarveout1Cfg0, 44, 21:18, 28:25) \ + HANDLER(McVideoProtectVprOverride, 44, 3:2, 30:29) \ + HANDLER(McVideoProtectVprOverride, 44, 6:6, 31:31) \ + HANDLER(McEmemAdrCfgChannelMask, 45, 31:9, 22:0) \ + HANDLER(McEmemAdrCfgDev0, 45, 2:0, 25:23) \ + HANDLER(McEmemAdrCfgDev0, 45, 9:8, 27:26) \ + HANDLER(McEmemAdrCfgDev0, 45, 19:16, 31:28) \ + HANDLER(McEmemAdrCfgBankMask0, 46, 31:10, 21:0) \ + HANDLER(McEmemAdrCfgDev1, 46, 2:0, 24:22) \ + HANDLER(McEmemAdrCfgDev1, 46, 9:8, 26:25) \ + HANDLER(McEmemAdrCfgDev1, 46, 19:16, 30:27) \ + HANDLER(McVideoProtectVprOverride, 46, 7:7, 31:31) \ + HANDLER(McEmemAdrCfgBankMask1, 47, 31:10, 21:0) \ + HANDLER(McGeneralizedCarveout3Cfg0, 47, 10:3, 29:22) \ + HANDLER(McVideoProtectVprOverride, 47, 9:8, 31:30) \ + HANDLER(McEmemAdrCfgBankMask2, 48, 31:10, 21:0) \ + HANDLER(McGeneralizedCarveout3Cfg0, 48, 21:14, 29:22) \ + HANDLER(McVideoProtectVprOverride, 48, 11:11, 30:30) \ + HANDLER(McVideoProtectVprOverride, 48, 14:14, 31:31) \ + HANDLER(McVideoProtectGpuOverride1, 49, 15:0, 15:0) \ + HANDLER(McEmemCfg, 49, 13:0, 29:16) \ + HANDLER(McEmemCfg, 49, 31:31, 30:30) \ + HANDLER(McVideoProtectVprOverride, 49, 15:15, 31:31) \ + HANDLER(McGeneralizedCarveout3Bom, 50, 31:17, 14:0) \ + HANDLER(McGeneralizedCarveout1Bom, 50, 31:17, 29:15) \ + HANDLER(McVideoProtectVprOverride, 50, 18:17, 31:30) \ + HANDLER(McGeneralizedCarveout4Bom, 51, 31:17, 14:0) \ + HANDLER(McGeneralizedCarveout2Bom, 51, 31:17, 29:15) \ + HANDLER(McVideoProtectVprOverride, 51, 20:19, 31:30) \ + HANDLER(McGeneralizedCarveout5Bom, 52, 31:17, 14:0) \ + HANDLER(McVideoProtectBom, 52, 31:20, 26:15) \ + HANDLER(McVideoProtectVprOverride, 52, 23:21, 29:27) \ + HANDLER(McVideoProtectVprOverride, 52, 26:26, 30:30) \ + HANDLER(McVideoProtectVprOverride, 52, 29:29, 31:31) \ + HANDLER(McVideoProtectSizeMb, 53, 11:0, 11:0) \ + HANDLER(McSecCarveoutBom, 53, 31:20, 23:12) \ + HANDLER(McVideoProtectVprOverride, 53, 31:30, 25:24) \ + HANDLER(McVideoProtectVprOverride1, 53, 1:0, 27:26) \ + HANDLER(McVideoProtectVprOverride1, 53, 7:4, 31:28) \ + HANDLER(McSecCarveoutSizeMb, 54, 11:0, 11:0) \ + HANDLER(McMtsCarveoutBom, 54, 31:20, 23:12) \ + HANDLER(McVideoProtectVprOverride1, 54, 15:8, 31:24) \ + HANDLER(McMtsCarveoutSizeMb, 55, 11:0, 11:0) \ + HANDLER(McGeneralizedCarveout4Size128kb, 55, 11:0, 23:12) \ + HANDLER(McVideoProtectVprOverride1, 55, 16:16, 24:24) \ + HANDLER(McGeneralizedCarveout2Cfg0, 55, 2:0, 27:25) \ + HANDLER(McGeneralizedCarveout2Cfg0, 55, 25:22, 31:28) \ + HANDLER(McGeneralizedCarveout3Size128kb, 56, 11:0, 11:0) \ + HANDLER(McGeneralizedCarveout2Size128kb, 56, 11:0, 23:12) \ + HANDLER(McGeneralizedCarveout2Cfg0, 56, 26:26, 24:24) \ + HANDLER(McGeneralizedCarveout1Cfg0, 56, 2:0, 27:25) \ + HANDLER(McGeneralizedCarveout1Cfg0, 56, 25:22, 31:28) \ + HANDLER(McGeneralizedCarveout1Size128kb, 57, 11:0, 11:0) \ + HANDLER(McGeneralizedCarveout5Size128kb, 57, 11:0, 23:12) \ + HANDLER(McGeneralizedCarveout1Cfg0, 57, 26:26, 24:24) \ + HANDLER(McGeneralizedCarveout3Cfg0, 57, 2:0, 27:25) \ + HANDLER(McGeneralizedCarveout3Cfg0, 57, 25:22, 31:28) \ + HANDLER(McGeneralizedCarveout3Cfg0, 58, 26:26, 0:0) \ + HANDLER(McGeneralizedCarveout1Access0, 59, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout1Access1, 60, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout1Access2, 61, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout1Access3, 62, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout1Access4, 63, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2Access0, 64, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2Access1, 65, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2Access2, 66, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2Access3, 67, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2Access4, 68, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3Access0, 69, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3Access1, 70, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3Access2, 71, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3Access3, 72, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3Access4, 73, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4Access0, 74, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4Access1, 75, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4Access2, 76, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4Access3, 77, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4Access4, 78, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout5Access0, 79, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout5Access1, 80, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout5Access2, 81, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout5Access3, 82, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout1ForceInternalAccess0, 84, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout1ForceInternalAccess1, 85, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout1ForceInternalAccess2, 86, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout1ForceInternalAccess3, 87, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout1ForceInternalAccess4, 88, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2ForceInternalAccess0, 89, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2ForceInternalAccess1, 90, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2ForceInternalAccess2, 91, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2ForceInternalAccess3, 92, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2ForceInternalAccess4, 93, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3ForceInternalAccess0, 94, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3ForceInternalAccess1, 95, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3ForceInternalAccess2, 96, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3ForceInternalAccess3, 97, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3ForceInternalAccess4, 98, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4ForceInternalAccess0, 99, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4ForceInternalAccess1, 100, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4ForceInternalAccess2, 101, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4ForceInternalAccess3, 102, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4ForceInternalAccess4, 103, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout5ForceInternalAccess0, 104, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout5ForceInternalAccess1, 105, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout5ForceInternalAccess2, 106, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout5ForceInternalAccess3, 107, 31:0, 31:0) diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/sdram/fusee_sdram_params_lp0_mariko.inc b/Source/Atmosphere-MTC-Unlock/fusee/program/source/sdram/fusee_sdram_params_lp0_mariko.inc new file mode 100644 index 00000000..0728aaa7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/sdram/fusee_sdram_params_lp0_mariko.inc @@ -0,0 +1,1037 @@ +/* + * Copyright (c) Atmosphre-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 <http://www.gnu.org/licenses/>. + */ + +#define FOREACH_SDRAM_SCRATCH_REGISTER_MARIKO(HANDLER) \ + /* PMC SCRATCH fields. */ \ + HANDLER(EmcClockSource, 6, 7:0, 15:8) \ + HANDLER(EmcClockSourceDll, 6, 7:0, 23:16) \ + HANDLER(EmcClockSource, 6, 31:29, 26:24) \ + HANDLER(EmcClockSourceDll, 6, 31:29, 29:27) \ + HANDLER(EmcClockSourceDll, 6, 11:10, 31:30) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 9:8, 1:0) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 2:1, 3:2) \ + HANDLER(EmcZqCalLpDdr4WarmBoot, 7, 31:30, 5:4) \ + HANDLER(EmcClockSource, 7, 27:27, 6:6) \ + HANDLER(EmcClockSource, 7, 26:26, 7:7) \ + HANDLER(EmcClockSource, 7, 15:15, 8:8) \ + HANDLER(EmcClockSource, 7, 25:25, 9:9) \ + HANDLER(EmcClockSource, 7, 20:19, 11:10) \ + HANDLER(EmcClockSource, 7, 16:16, 12:12) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 13:13, 13:13) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 12:12, 14:14) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 11:11, 15:15) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 10:10, 16:16) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 5:5, 17:17) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 4:4, 18:18) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 3:3, 19:19) \ + HANDLER(ClkRstControllerPllmMisc2Override, 7, 0:0, 20:20) \ + HANDLER(EmcZqCalLpDdr4WarmBoot, 7, 1:0, 22:21) \ + HANDLER(EmcZqCalLpDdr4WarmBoot, 7, 4:4, 23:23) \ + HANDLER(EmcRc, 7, 7:0, 31:24) \ + HANDLER(EmcPmacroBgBiasCtrl0, 8, 13:12, 31:30) \ + HANDLER(EmcFdpdCtrlCmdNoRamp, 14, 0:0, 30:30) \ + HANDLER(EmcCfgPipeClk, 14, 0:0, 31:31) \ + HANDLER(EmcQRst, 15, 6:0, 26:20) \ + HANDLER(EmcQRst, 15, 20:16, 31:27) \ + HANDLER(EmcPmacroCmdTxDrv, 16, 5:0, 25:20) \ + HANDLER(EmcPmacroCmdTxDrv, 16, 13:8, 31:26) \ + HANDLER(EmcFbioCfg8, 17, 27:16, 31:20) \ + HANDLER(EmcTxsrDll, 18, 11:0, 31:20) \ + HANDLER(EmcTxdsrvttgen, 19, 11:0, 31:20) \ + HANDLER(EmcCfgRsv, 22, 31:0, 31:0) \ + HANDLER(EmcAutoCalConfig, 23, 31:0, 31:0) \ + HANDLER(EmcAutoCalVrefSel0, 24, 31:0, 31:0) \ + HANDLER(EmcPmacroBrickCtrlRfu1, 25, 31:0, 31:0) \ + HANDLER(EmcPmacroBrickCtrlRfu2, 26, 31:0, 31:0) \ + HANDLER(EmcPmcScratch1, 27, 31:0, 31:0) \ + HANDLER(EmcPmcScratch2, 28, 31:0, 31:0) \ + HANDLER(EmcPmcScratch3, 29, 31:0, 31:0) \ + HANDLER(EmcPmacroPerbitRfuCtrl0, 30, 31:0, 31:0) \ + HANDLER(EmcPmacroPerbitRfuCtrl1, 31, 31:0, 31:0) \ + HANDLER(EmcPmacroPerbitRfuCtrl2, 32, 31:0, 31:0) \ + HANDLER(EmcPmacroPerbitRfuCtrl3, 33, 31:0, 31:0) \ + HANDLER(EmcPmacroPerbitRfuCtrl4, 40, 31:0, 31:0) \ + HANDLER(EmcPmacroPerbitRfuCtrl5, 42, 31:0, 31:0) \ + HANDLER(McEmemArbDaTurns, 44, 31:0, 31:0) \ + HANDLER(EmcFbioSpare, 64, 31:24, 7:0) \ + HANDLER(EmcFbioSpare, 64, 23:16, 15:8) \ + HANDLER(EmcFbioSpare, 64, 15:8, 23:16) \ + HANDLER(EmcFbioSpare, 64, 7:2, 29:24) \ + HANDLER(EmcFbioSpare, 64, 0:0, 30:30) \ + HANDLER(McEmemArbMisc2, 64, 0:0, 31:31) \ + HANDLER(McEmemArbMisc0, 65, 14:0, 14:0) \ + HANDLER(McEmemArbMisc0, 65, 30:16, 29:15) \ + HANDLER(McDaCfg0, 65, 0:0, 30:30) \ + HANDLER(EmcFdpdCtrlCmd, 66, 16:0, 16:0) \ + HANDLER(EmcFdpdCtrlCmd, 66, 31:20, 28:17) \ + HANDLER(EmcAutoCalConfig2, 67, 27:0, 27:0) \ + HANDLER(EmcBurstRefreshNum, 67, 3:0, 31:28) \ + HANDLER(EmcCfgDigDll, 68, 10:0, 10:0) \ + HANDLER(EmcCfgDigDll, 68, 25:12, 24:11) \ + HANDLER(EmcCfgDigDll, 68, 27:27, 25:25) \ + HANDLER(EmcCfgDigDll, 68, 31:30, 27:26) \ + HANDLER(EmcTppd, 68, 3:0, 31:28) \ + HANDLER(EmcFdpdCtrlDq, 69, 16:0, 16:0) \ + HANDLER(EmcFdpdCtrlDq, 69, 28:20, 25:17) \ + HANDLER(EmcFdpdCtrlDq, 69, 31:30, 27:26) \ + HANDLER(EmcR2r, 69, 3:0, 31:28) \ + HANDLER(EmcPmacroIbVrefDq_0, 70, 6:0, 6:0) \ + HANDLER(EmcPmacroIbVrefDq_0, 70, 14:8, 13:7) \ + HANDLER(EmcPmacroIbVrefDq_0, 70, 22:16, 20:14) \ + HANDLER(EmcPmacroIbVrefDq_0, 70, 30:24, 27:21) \ + HANDLER(EmcW2w, 70, 3:0, 31:28) \ + HANDLER(EmcPmacroIbVrefDq_1, 71, 6:0, 6:0) \ + HANDLER(EmcPmacroIbVrefDq_1, 71, 14:8, 13:7) \ + HANDLER(EmcPmacroIbVrefDq_1, 71, 22:16, 20:14) \ + HANDLER(EmcPmacroIbVrefDq_1, 71, 30:24, 27:21) \ + HANDLER(EmcPmacroVttgenCtrl0, 71, 19:16, 31:28) \ + HANDLER(EmcPmacroIbVrefDqs_0, 72, 6:0, 6:0) \ + HANDLER(EmcPmacroIbVrefDqs_0, 72, 14:8, 13:7) \ + HANDLER(EmcPmacroIbVrefDqs_0, 72, 22:16, 20:14) \ + HANDLER(EmcPmacroIbVrefDqs_0, 72, 30:24, 27:21) \ + HANDLER(EmcPmacroIbVrefDqs_1, 73, 6:0, 6:0) \ + HANDLER(EmcPmacroIbVrefDqs_1, 73, 14:8, 13:7) \ + HANDLER(EmcPmacroIbVrefDqs_1, 73, 22:16, 20:14) \ + HANDLER(EmcPmacroIbVrefDqs_1, 73, 30:24, 27:21) \ + HANDLER(EmcPmacroDdllShortCmd_0, 74, 6:0, 6:0) \ + HANDLER(EmcPmacroDdllShortCmd_0, 74, 14:8, 13:7) \ + HANDLER(EmcPmacroDdllShortCmd_0, 74, 22:16, 20:14) \ + HANDLER(EmcPmacroDdllShortCmd_0, 74, 30:24, 27:21) \ + HANDLER(EmcPmacroDdllShortCmd_1, 75, 6:0, 6:0) \ + HANDLER(EmcPmacroDdllShortCmd_1, 75, 14:8, 13:7) \ + HANDLER(EmcPmacroDdllShortCmd_1, 75, 22:16, 20:14) \ + HANDLER(EmcPmacroDdllShortCmd_1, 75, 30:24, 27:21) \ + HANDLER(EmcPmacroDllCfg0, 76, 29:4, 25:0) \ + HANDLER(EmcRp, 76, 5:0, 31:26) \ + HANDLER(EmcPmacroTxPwrd0, 77, 10:0, 10:0) \ + HANDLER(EmcPmacroTxPwrd0, 77, 13:12, 12:11) \ + HANDLER(EmcPmacroTxPwrd0, 77, 26:16, 23:13) \ + HANDLER(EmcPmacroTxPwrd0, 77, 29:28, 25:24) \ + HANDLER(EmcR2w, 77, 5:0, 31:26) \ + HANDLER(EmcPmacroTxPwrd1, 78, 10:0, 10:0) \ + HANDLER(EmcPmacroTxPwrd1, 78, 13:12, 12:11) \ + HANDLER(EmcPmacroTxPwrd1, 78, 26:16, 23:13) \ + HANDLER(EmcPmacroTxPwrd1, 78, 29:28, 25:24) \ + HANDLER(EmcW2r, 78, 5:0, 31:26) \ + HANDLER(EmcPmacroTxPwrd2, 79, 10:0, 10:0) \ + HANDLER(EmcPmacroTxPwrd2, 79, 13:12, 12:11) \ + HANDLER(EmcPmacroTxPwrd2, 79, 26:16, 23:13) \ + HANDLER(EmcPmacroTxPwrd2, 79, 29:28, 25:24) \ + HANDLER(EmcR2p, 79, 5:0, 31:26) \ + HANDLER(EmcPmacroTxPwrd3, 80, 10:0, 10:0) \ + HANDLER(EmcPmacroTxPwrd3, 80, 13:12, 12:11) \ + HANDLER(EmcPmacroTxPwrd3, 80, 26:16, 23:13) \ + HANDLER(EmcPmacroTxPwrd3, 80, 29:28, 25:24) \ + HANDLER(EmcCcdmw, 80, 5:0, 31:26) \ + HANDLER(EmcPmacroTxPwrd4, 81, 10:0, 10:0) \ + HANDLER(EmcPmacroTxPwrd4, 81, 13:12, 12:11) \ + HANDLER(EmcPmacroTxPwrd4, 81, 26:16, 23:13) \ + HANDLER(EmcPmacroTxPwrd4, 81, 29:28, 25:24) \ + HANDLER(EmcRdRcd, 81, 5:0, 31:26) \ + HANDLER(EmcPmacroTxPwrd5, 82, 10:0, 10:0) \ + HANDLER(EmcPmacroTxPwrd5, 82, 13:12, 12:11) \ + HANDLER(EmcPmacroTxPwrd5, 82, 26:16, 23:13) \ + HANDLER(EmcPmacroTxPwrd5, 82, 29:28, 25:24) \ + HANDLER(EmcWrRcd, 82, 5:0, 31:26) \ + HANDLER(EmcAutoCalChannel, 83, 5:0, 5:0) \ + HANDLER(EmcAutoCalChannel, 83, 11:8, 9:6) \ + HANDLER(EmcAutoCalChannel, 83, 27:16, 21:10) \ + HANDLER(EmcAutoCalChannel, 83, 31:29, 24:22) \ + HANDLER(EmcConfigSampleDelay, 83, 6:0, 31:25) \ + HANDLER(EmcPmacroRxTerm, 84, 5:0, 5:0) \ + HANDLER(EmcPmacroRxTerm, 84, 13:8, 11:6) \ + HANDLER(EmcPmacroRxTerm, 84, 21:16, 17:12) \ + HANDLER(EmcPmacroRxTerm, 84, 29:24, 23:18) \ + HANDLER(EmcSelDpdCtrl, 84, 5:2, 27:24) \ + HANDLER(EmcSelDpdCtrl, 84, 8:8, 28:28) \ + HANDLER(EmcSelDpdCtrl, 84, 18:16, 31:29) \ + HANDLER(EmcPmacroDqTxDrv, 85, 5:0, 5:0) \ + HANDLER(EmcPmacroDqTxDrv, 85, 13:8, 11:6) \ + HANDLER(EmcPmacroDqTxDrv, 85, 21:16, 17:12) \ + HANDLER(EmcPmacroDqTxDrv, 85, 29:24, 23:18) \ + HANDLER(EmcObdly, 85, 5:0, 29:24) \ + HANDLER(EmcObdly, 85, 29:28, 31:30) \ + HANDLER(EmcPmacroCaTxDrv, 86, 5:0, 5:0) \ + HANDLER(EmcPmacroCaTxDrv, 86, 13:8, 11:6) \ + HANDLER(EmcPmacroCaTxDrv, 86, 21:16, 17:12) \ + HANDLER(EmcPmacroCaTxDrv, 86, 29:24, 23:18) \ + HANDLER(EmcPmacroVttgenCtrl1, 86, 15:10, 29:24) \ + HANDLER(EmcPmacroVttgenCtrl1, 86, 21:20, 31:30) \ + HANDLER(EmcPmacroZctrl, 87, 27:4, 23:0) \ + HANDLER(EmcPmacroVttgenCtrl2, 87, 23:16, 31:24) \ + HANDLER(EmcZcalInterval, 88, 23:10, 13:0) \ + HANDLER(EmcZcalInterval, 88, 9:0, 23:14) \ + HANDLER(McEmemArbTimingRc, 88, 7:0, 31:24) \ + HANDLER(EmcDataBrlshft0, 89, 23:0, 23:0) \ + HANDLER(McEmemArbRsv, 89, 7:0, 31:24) \ + HANDLER(EmcDataBrlshft1, 90, 23:0, 23:0) \ + HANDLER(EmcDqsBrlshft0, 91, 23:0, 23:0) \ + HANDLER(EmcDqsBrlshft1, 92, 23:0, 23:0) \ + HANDLER(EmcSwizzleRank0Byte0, 93, 2:0, 2:0) \ + HANDLER(EmcSwizzleRank0Byte0, 93, 6:4, 5:3) \ + HANDLER(EmcSwizzleRank0Byte0, 93, 10:8, 8:6) \ + HANDLER(EmcSwizzleRank0Byte0, 93, 14:12, 11:9) \ + HANDLER(EmcSwizzleRank0Byte0, 93, 18:16, 14:12) \ + HANDLER(EmcSwizzleRank0Byte0, 93, 22:20, 17:15) \ + HANDLER(EmcSwizzleRank0Byte0, 93, 26:24, 20:18) \ + HANDLER(EmcSwizzleRank0Byte0, 93, 30:28, 23:21) \ + HANDLER(EmcSwizzleRank0Byte1, 94, 2:0, 2:0) \ + HANDLER(EmcSwizzleRank0Byte1, 94, 6:4, 5:3) \ + HANDLER(EmcSwizzleRank0Byte1, 94, 10:8, 8:6) \ + HANDLER(EmcSwizzleRank0Byte1, 94, 14:12, 11:9) \ + HANDLER(EmcSwizzleRank0Byte1, 94, 18:16, 14:12) \ + HANDLER(EmcSwizzleRank0Byte1, 94, 22:20, 17:15) \ + HANDLER(EmcSwizzleRank0Byte1, 94, 26:24, 20:18) \ + HANDLER(EmcSwizzleRank0Byte1, 94, 30:28, 23:21) \ + HANDLER(EmcRas, 94, 6:0, 30:24) \ + HANDLER(EmcCfg, 94, 4:4, 31:31) \ + HANDLER(EmcSwizzleRank0Byte2, 95, 2:0, 2:0) \ + HANDLER(EmcSwizzleRank0Byte2, 95, 6:4, 5:3) \ + HANDLER(EmcSwizzleRank0Byte2, 95, 10:8, 8:6) \ + HANDLER(EmcSwizzleRank0Byte2, 95, 14:12, 11:9) \ + HANDLER(EmcSwizzleRank0Byte2, 95, 18:16, 14:12) \ + HANDLER(EmcSwizzleRank0Byte2, 95, 22:20, 17:15) \ + HANDLER(EmcSwizzleRank0Byte2, 95, 26:24, 20:18) \ + HANDLER(EmcSwizzleRank0Byte2, 95, 30:28, 23:21) \ + HANDLER(EmcW2p, 95, 6:0, 30:24) \ + HANDLER(EmcCfg, 95, 5:5, 31:31) \ + HANDLER(EmcSwizzleRank0Byte3, 96, 2:0, 2:0) \ + HANDLER(EmcSwizzleRank0Byte3, 96, 6:4, 5:3) \ + HANDLER(EmcSwizzleRank0Byte3, 96, 10:8, 8:6) \ + HANDLER(EmcSwizzleRank0Byte3, 96, 14:12, 11:9) \ + HANDLER(EmcSwizzleRank0Byte3, 96, 18:16, 14:12) \ + HANDLER(EmcSwizzleRank0Byte3, 96, 22:20, 17:15) \ + HANDLER(EmcSwizzleRank0Byte3, 96, 26:24, 20:18) \ + HANDLER(EmcSwizzleRank0Byte3, 96, 30:28, 23:21) \ + HANDLER(EmcQSafe, 96, 6:0, 30:24) \ + HANDLER(EmcCfg, 96, 6:6, 31:31) \ + HANDLER(EmcSwizzleRank1Byte0, 97, 2:0, 2:0) \ + HANDLER(EmcSwizzleRank1Byte0, 97, 6:4, 5:3) \ + HANDLER(EmcSwizzleRank1Byte0, 97, 10:8, 8:6) \ + HANDLER(EmcSwizzleRank1Byte0, 97, 14:12, 11:9) \ + HANDLER(EmcSwizzleRank1Byte0, 97, 18:16, 14:12) \ + HANDLER(EmcSwizzleRank1Byte0, 97, 22:20, 17:15) \ + HANDLER(EmcSwizzleRank1Byte0, 97, 26:24, 20:18) \ + HANDLER(EmcSwizzleRank1Byte0, 97, 30:28, 23:21) \ + HANDLER(EmcRdv, 97, 6:0, 30:24) \ + HANDLER(EmcCfg, 97, 7:7, 31:31) \ + HANDLER(EmcSwizzleRank1Byte1, 98, 2:0, 2:0) \ + HANDLER(EmcSwizzleRank1Byte1, 98, 6:4, 5:3) \ + HANDLER(EmcSwizzleRank1Byte1, 98, 10:8, 8:6) \ + HANDLER(EmcSwizzleRank1Byte1, 98, 14:12, 11:9) \ + HANDLER(EmcSwizzleRank1Byte1, 98, 18:16, 14:12) \ + HANDLER(EmcSwizzleRank1Byte1, 98, 22:20, 17:15) \ + HANDLER(EmcSwizzleRank1Byte1, 98, 26:24, 20:18) \ + HANDLER(EmcSwizzleRank1Byte1, 98, 30:28, 23:21) \ + HANDLER(EmcRw2Pden, 98, 6:0, 30:24) \ + HANDLER(EmcCfg, 98, 8:8, 31:31) \ + HANDLER(EmcSwizzleRank1Byte2, 99, 2:0, 2:0) \ + HANDLER(EmcSwizzleRank1Byte2, 99, 6:4, 5:3) \ + HANDLER(EmcSwizzleRank1Byte2, 99, 10:8, 8:6) \ + HANDLER(EmcSwizzleRank1Byte2, 99, 14:12, 11:9) \ + HANDLER(EmcSwizzleRank1Byte2, 99, 18:16, 14:12) \ + HANDLER(EmcSwizzleRank1Byte2, 99, 22:20, 17:15) \ + HANDLER(EmcSwizzleRank1Byte2, 99, 26:24, 20:18) \ + HANDLER(EmcSwizzleRank1Byte2, 99, 30:28, 23:21) \ + HANDLER(EmcTfaw, 99, 6:0, 30:24) \ + HANDLER(EmcCfg, 99, 9:9, 31:31) \ + HANDLER(EmcSwizzleRank1Byte3, 100, 2:0, 2:0) \ + HANDLER(EmcSwizzleRank1Byte3, 100, 6:4, 5:3) \ + HANDLER(EmcSwizzleRank1Byte3, 100, 10:8, 8:6) \ + HANDLER(EmcSwizzleRank1Byte3, 100, 14:12, 11:9) \ + HANDLER(EmcSwizzleRank1Byte3, 100, 18:16, 14:12) \ + HANDLER(EmcSwizzleRank1Byte3, 100, 22:20, 17:15) \ + HANDLER(EmcSwizzleRank1Byte3, 100, 26:24, 20:18) \ + HANDLER(EmcSwizzleRank1Byte3, 100, 30:28, 23:21) \ + HANDLER(EmcTClkStable, 100, 6:0, 30:24) \ + HANDLER(EmcCfg, 100, 18:18, 31:31) \ + HANDLER(EmcCfgPipe2, 101, 11:0, 11:0) \ + HANDLER(EmcCfgPipe2, 101, 27:16, 23:12) \ + HANDLER(EmcTrtm, 101, 6:0, 30:24) \ + HANDLER(EmcCfg, 101, 21:21, 31:31) \ + HANDLER(EmcCfgPipe1, 102, 11:0, 11:0) \ + HANDLER(EmcCfgPipe1, 102, 27:16, 23:12) \ + HANDLER(EmcTwtm, 102, 6:0, 30:24) \ + HANDLER(EmcCfg, 102, 22:22, 31:31) \ + HANDLER(EmcPmacroDdllPwrd0, 103, 4:1, 3:0) \ + HANDLER(EmcPmacroDdllPwrd0, 103, 7:6, 5:4) \ + HANDLER(EmcPmacroDdllPwrd0, 103, 12:9, 9:6) \ + HANDLER(EmcPmacroDdllPwrd0, 103, 15:14, 11:10) \ + HANDLER(EmcPmacroDdllPwrd0, 103, 20:17, 15:12) \ + HANDLER(EmcPmacroDdllPwrd0, 103, 23:22, 17:16) \ + HANDLER(EmcPmacroDdllPwrd0, 103, 28:25, 21:18) \ + HANDLER(EmcPmacroDdllPwrd0, 103, 31:30, 23:22) \ + HANDLER(EmcTratm, 103, 6:0, 30:24) \ + HANDLER(EmcCfg, 103, 23:23, 31:31) \ + HANDLER(EmcPmacroDdllPwrd1, 104, 4:1, 3:0) \ + HANDLER(EmcPmacroDdllPwrd1, 104, 7:6, 5:4) \ + HANDLER(EmcPmacroDdllPwrd1, 104, 12:9, 9:6) \ + HANDLER(EmcPmacroDdllPwrd1, 104, 15:14, 11:10) \ + HANDLER(EmcPmacroDdllPwrd1, 104, 20:17, 15:12) \ + HANDLER(EmcPmacroDdllPwrd1, 104, 23:22, 17:16) \ + HANDLER(EmcPmacroDdllPwrd1, 104, 28:25, 21:18) \ + HANDLER(EmcPmacroDdllPwrd1, 104, 31:30, 23:22) \ + HANDLER(EmcTwatm, 104, 6:0, 30:24) \ + HANDLER(EmcCfg, 104, 24:24, 31:31) \ + HANDLER(EmcPmacroDdllPwrd2, 105, 4:1, 3:0) \ + HANDLER(EmcPmacroDdllPwrd2, 105, 7:6, 5:4) \ + HANDLER(EmcPmacroDdllPwrd2, 105, 12:9, 9:6) \ + HANDLER(EmcPmacroDdllPwrd2, 105, 15:14, 11:10) \ + HANDLER(EmcPmacroDdllPwrd2, 105, 20:17, 15:12) \ + HANDLER(EmcPmacroDdllPwrd2, 105, 23:22, 17:16) \ + HANDLER(EmcPmacroDdllPwrd2, 105, 28:25, 21:18) \ + HANDLER(EmcPmacroDdllPwrd2, 105, 31:30, 23:22) \ + HANDLER(EmcTr2ref, 105, 6:0, 30:24) \ + HANDLER(EmcCfg, 105, 25:25, 31:31) \ + HANDLER(EmcPmacroDdllPeriodicOffset, 106, 5:0, 5:0) \ + HANDLER(EmcPmacroDdllPeriodicOffset, 106, 16:8, 14:6) \ + HANDLER(EmcPmacroDdllPeriodicOffset, 106, 28:20, 23:15) \ + HANDLER(EmcPdex2Mrr, 106, 6:0, 30:24) \ + HANDLER(EmcCfg, 106, 26:26, 31:31) \ + HANDLER(McEmemArbDaCovers, 107, 23:0, 23:0) \ + HANDLER(EmcClkenOverride, 107, 3:1, 26:24) \ + HANDLER(EmcClkenOverride, 107, 8:6, 29:27) \ + HANDLER(EmcClkenOverride, 107, 16:16, 30:30) \ + HANDLER(EmcCfg, 107, 28:28, 31:31) \ + HANDLER(EmcXm2CompPadCtrl, 108, 1:0, 1:0) \ + HANDLER(EmcXm2CompPadCtrl, 108, 6:4, 4:2) \ + HANDLER(EmcXm2CompPadCtrl, 108, 9:9, 5:5) \ + HANDLER(EmcXm2CompPadCtrl, 108, 19:11, 14:6) \ + HANDLER(EmcXm2CompPadCtrl, 108, 31:24, 22:15) \ + HANDLER(EmcRfcPb, 108, 8:0, 31:23) \ + HANDLER(EmcAutoCalConfig3, 109, 6:0, 6:0) \ + HANDLER(EmcAutoCalConfig3, 109, 14:8, 13:7) \ + HANDLER(EmcAutoCalConfig3, 109, 23:16, 21:14) \ + HANDLER(EmcCfgUpdate, 109, 2:0, 24:22) \ + HANDLER(EmcCfgUpdate, 109, 10:8, 27:25) \ + HANDLER(EmcCfgUpdate, 109, 31:28, 31:28) \ + HANDLER(EmcAutoCalConfig4, 110, 6:0, 6:0) \ + HANDLER(EmcAutoCalConfig4, 110, 14:8, 13:7) \ + HANDLER(EmcAutoCalConfig4, 110, 23:16, 21:14) \ + HANDLER(EmcRfc, 110, 9:0, 31:22) \ + HANDLER(EmcAutoCalConfig5, 111, 6:0, 6:0) \ + HANDLER(EmcAutoCalConfig5, 111, 14:8, 13:7) \ + HANDLER(EmcAutoCalConfig5, 111, 23:16, 21:14) \ + HANDLER(EmcTxsr, 111, 9:0, 31:22) \ + HANDLER(EmcAutoCalConfig6, 112, 6:0, 6:0) \ + HANDLER(EmcAutoCalConfig6, 112, 14:8, 13:7) \ + HANDLER(EmcAutoCalConfig6, 112, 23:16, 21:14) \ + HANDLER(EmcMc2EmcQ, 112, 2:0, 24:22) \ + HANDLER(EmcMc2EmcQ, 112, 10:8, 27:25) \ + HANDLER(EmcMc2EmcQ, 112, 27:24, 31:28) \ + HANDLER(EmcAutoCalConfig7, 113, 6:0, 6:0) \ + HANDLER(EmcAutoCalConfig7, 113, 14:8, 13:7) \ + HANDLER(EmcAutoCalConfig7, 113, 23:16, 21:14) \ + HANDLER(McEmemArbRing1Throttle, 113, 4:0, 26:22) \ + HANDLER(McEmemArbRing1Throttle, 113, 20:16, 31:27) \ + HANDLER(EmcAutoCalConfig8, 114, 6:0, 6:0) \ + HANDLER(EmcAutoCalConfig8, 114, 14:8, 13:7) \ + HANDLER(EmcAutoCalConfig8, 114, 23:16, 21:14) \ + HANDLER(EmcFbioCfg7, 115, 21:0, 21:0) \ + HANDLER(EmcAr2Pden, 115, 8:0, 30:22) \ + HANDLER(EmcCfg, 115, 29:29, 31:31) \ + HANDLER(EmcPmacroQuseDdllRank0_0, 123, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank0_0, 123, 26:16, 21:11) \ + HANDLER(EmcRfcSlr, 123, 8:0, 30:22) \ + HANDLER(EmcCfg, 123, 30:30, 31:31) \ + HANDLER(EmcPmacroQuseDdllRank0_1, 124, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank0_1, 124, 26:16, 21:11) \ + HANDLER(EmcIbdly, 124, 6:0, 28:22) \ + HANDLER(EmcIbdly, 124, 29:28, 30:29) \ + HANDLER(EmcCfg, 124, 31:31, 31:31) \ + HANDLER(EmcPmacroQuseDdllRank0_2, 125, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank0_2, 125, 26:16, 21:11) \ + HANDLER(McEmemArbTimingRFCPB, 125, 8:0, 30:22) \ + HANDLER(EmcFbioCfg5, 125, 4:4, 31:31) \ + HANDLER(EmcPmacroQuseDdllRank0_3, 126, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank0_3, 126, 26:16, 21:11) \ + HANDLER(EmcAutoCalConfig9, 126, 6:0, 28:22) \ + HANDLER(EmcFbioCfg5, 126, 15:13, 31:29) \ + HANDLER(EmcPmacroQuseDdllRank0_4, 127, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank0_4, 127, 26:16, 21:11) \ + HANDLER(EmcRdvMask, 127, 6:0, 28:22) \ + HANDLER(EmcCfg2, 127, 5:3, 31:29) \ + HANDLER(EmcPmacroQuseDdllRank0_5, 128, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank0_5, 128, 26:16, 21:11) \ + HANDLER(EmcRdvEarlyMask, 128, 6:0, 28:22) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 128, 4:2, 31:29) \ + HANDLER(EmcPmacroQuseDdllRank1_0, 129, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank1_0, 129, 26:16, 21:11) \ + HANDLER(EmcRdvEarly, 129, 6:0, 28:22) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 129, 9:7, 31:29) \ + HANDLER(EmcPmacroQuseDdllRank1_1, 130, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank1_1, 130, 26:16, 21:11) \ + HANDLER(EmcQuseWidth, 130, 4:0, 26:22) \ + HANDLER(EmcQuseWidth, 130, 29:28, 28:27) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 130, 14:12, 31:29) \ + HANDLER(EmcPmacroQuseDdllRank1_2, 131, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank1_2, 131, 26:16, 21:11) \ + HANDLER(EmcPmacroDdllShortCmd_2, 131, 6:0, 28:22) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 131, 19:17, 31:29) \ + HANDLER(EmcPmacroQuseDdllRank1_3, 132, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank1_3, 132, 26:16, 21:11) \ + HANDLER(EmcPmacroCmdRxTermMode, 132, 1:0, 23:22) \ + HANDLER(EmcPmacroCmdRxTermMode, 132, 5:4, 25:24) \ + HANDLER(EmcPmacroCmdRxTermMode, 132, 9:8, 27:26) \ + HANDLER(EmcPmacroCmdRxTermMode, 132, 13:13, 28:28) \ + HANDLER(EmcPmacroDataPadTxCtrl, 132, 4:2, 31:29) \ + HANDLER(EmcPmacroQuseDdllRank1_4, 133, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank1_4, 133, 26:16, 21:11) \ + HANDLER(EmcPmacroDataRxTermMode, 133, 1:0, 23:22) \ + HANDLER(EmcPmacroDataRxTermMode, 133, 5:4, 25:24) \ + HANDLER(EmcPmacroDataRxTermMode, 133, 9:8, 27:26) \ + HANDLER(EmcPmacroDataRxTermMode, 133, 13:13, 28:28) \ + HANDLER(EmcPmacroDataPadTxCtrl, 133, 9:7, 31:29) \ + HANDLER(EmcPmacroQuseDdllRank1_5, 134, 10:0, 10:0) \ + HANDLER(EmcPmacroQuseDdllRank1_5, 134, 26:16, 21:11) \ + HANDLER(McEmemArbTimingRp, 134, 6:0, 28:22) \ + HANDLER(EmcPmacroDataPadTxCtrl, 134, 14:12, 31:29) \ + HANDLER(EmcPmacroObDdllLongDqRank0_0, 135, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank0_0, 135, 26:16, 21:11) \ + HANDLER(McEmemArbTimingRas, 135, 6:0, 28:22) \ + HANDLER(EmcPmacroDataPadTxCtrl, 135, 19:17, 31:29) \ + HANDLER(EmcPmacroObDdllLongDqRank0_1, 136, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank0_1, 136, 26:16, 21:11) \ + HANDLER(McEmemArbTimingFaw, 136, 6:0, 28:22) \ + HANDLER(EmcCfg, 136, 17:16, 30:29) \ + HANDLER(EmcFbioCfg5, 136, 8:8, 31:31) \ + HANDLER(EmcPmacroObDdllLongDqRank0_2, 137, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank0_2, 137, 26:16, 21:11) \ + HANDLER(McEmemArbTimingRap2Pre, 137, 6:0, 28:22) \ + HANDLER(EmcFbioCfg5, 137, 1:0, 30:29) \ + HANDLER(EmcFbioCfg5, 137, 10:10, 31:31) \ + HANDLER(EmcPmacroObDdllLongDqRank0_3, 138, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank0_3, 138, 26:16, 21:11) \ + HANDLER(McEmemArbTimingWap2Pre, 138, 6:0, 28:22) \ + HANDLER(EmcFbioCfg5, 138, 3:2, 30:29) \ + HANDLER(EmcFbioCfg5, 138, 12:12, 31:31) \ + HANDLER(EmcPmacroObDdllLongDqRank0_4, 139, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank0_4, 139, 26:16, 21:11) \ + HANDLER(McEmemArbTimingR2W, 139, 6:0, 28:22) \ + HANDLER(EmcCfg2, 139, 27:26, 30:29) \ + HANDLER(EmcFbioCfg5, 139, 24:24, 31:31) \ + HANDLER(EmcPmacroObDdllLongDqRank0_5, 140, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank0_5, 140, 26:16, 21:11) \ + HANDLER(McEmemArbTimingW2R, 140, 6:0, 28:22) \ + HANDLER(EmcFbioCfg5, 140, 27:25, 31:29) \ + HANDLER(EmcPmacroObDdllLongDqRank1_0, 141, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank1_0, 141, 26:16, 21:11) \ + HANDLER(EmcWdv, 141, 5:0, 27:22) \ + HANDLER(EmcFbioCfg5, 141, 23:20, 31:28) \ + HANDLER(EmcPmacroObDdllLongDqRank1_1, 142, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank1_1, 142, 26:16, 21:11) \ + HANDLER(EmcQUse, 142, 5:0, 27:22) \ + HANDLER(EmcFbioCfg5, 142, 28:28, 28:28) \ + HANDLER(EmcFbioCfg5, 142, 31:30, 30:29) \ + HANDLER(EmcCfg2, 142, 0:0, 31:31) \ + HANDLER(EmcPmacroObDdllLongDqRank1_2, 143, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank1_2, 143, 26:16, 21:11) \ + HANDLER(EmcPdEx2Wr, 143, 5:0, 27:22) \ + HANDLER(EmcCfg2, 143, 2:1, 29:28) \ + HANDLER(EmcCfg2, 143, 7:7, 30:30) \ + HANDLER(EmcCfg2, 143, 10:10, 31:31) \ + HANDLER(EmcPmacroObDdllLongDqRank1_3, 144, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank1_3, 144, 26:16, 21:11) \ + HANDLER(EmcPdEx2Rd, 144, 5:0, 27:22) \ + HANDLER(EmcCfg2, 144, 11:11, 28:28) \ + HANDLER(EmcCfg2, 144, 16:14, 31:29) \ + HANDLER(EmcPmacroObDdllLongDqRank1_4, 145, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank1_4, 145, 26:16, 21:11) \ + HANDLER(EmcPdex2Cke, 145, 5:0, 27:22) \ + HANDLER(EmcCfg2, 145, 20:20, 28:28) \ + HANDLER(EmcCfg2, 145, 24:22, 31:29) \ + HANDLER(EmcPmacroObDdllLongDqRank1_5, 146, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqRank1_5, 146, 26:16, 21:11) \ + HANDLER(EmcPChg2Pden, 146, 5:0, 27:22) \ + HANDLER(EmcCfg2, 146, 25:25, 28:28) \ + HANDLER(EmcCfg2, 146, 30:28, 31:29) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_0, 147, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_0, 147, 26:16, 21:11) \ + HANDLER(EmcAct2Pden, 147, 5:0, 27:22) \ + HANDLER(EmcCfg2, 147, 31:31, 28:28) \ + HANDLER(EmcCfgPipe, 147, 2:0, 31:29) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_1, 148, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_1, 148, 26:16, 21:11) \ + HANDLER(EmcCke2Pden, 148, 5:0, 27:22) \ + HANDLER(EmcCfgPipe, 148, 6:3, 31:28) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_2, 149, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_2, 149, 26:16, 21:11) \ + HANDLER(EmcTcke, 149, 5:0, 27:22) \ + HANDLER(EmcCfgPipe, 149, 10:7, 31:28) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_3, 150, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_3, 150, 26:16, 21:11) \ + HANDLER(EmcTrpab, 150, 5:0, 27:22) \ + HANDLER(EmcCfgPipe, 150, 11:11, 28:28) \ + HANDLER(EmcCfgPipe, 150, 18:16, 31:29) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_4, 151, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_4, 151, 26:16, 21:11) \ + HANDLER(EmcEInput, 151, 5:0, 27:22) \ + HANDLER(EmcCfgPipe, 151, 22:19, 31:28) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_5, 152, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank0_5, 152, 26:16, 21:11) \ + HANDLER(EmcEInputDuration, 152, 5:0, 27:22) \ + HANDLER(EmcCfgPipe, 152, 26:23, 31:28) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_0, 153, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_0, 153, 26:16, 21:11) \ + HANDLER(EmcPutermExtra, 153, 5:0, 27:22) \ + HANDLER(EmcCfgPipe, 153, 27:27, 28:28) \ + HANDLER(EmcPmacroTxSelClkSrc0, 153, 2:0, 31:29) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_1, 154, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_1, 154, 26:16, 21:11) \ + HANDLER(EmcTckesr, 154, 5:0, 27:22) \ + HANDLER(EmcPmacroTxSelClkSrc0, 154, 6:3, 31:28) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_2, 155, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_2, 155, 26:16, 21:11) \ + HANDLER(EmcTpd, 155, 5:0, 27:22) \ + HANDLER(EmcPmacroTxSelClkSrc0, 155, 10:7, 31:28) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_3, 156, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_3, 156, 26:16, 21:11) \ + HANDLER(EmcWdvMask, 156, 5:0, 27:22) \ + HANDLER(EmcPmacroTxSelClkSrc0, 156, 19:16, 31:28) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_4, 157, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_4, 157, 26:16, 21:11) \ + HANDLER(EmcWdvChk, 157, 5:0, 27:22) \ + HANDLER(EmcPmacroTxSelClkSrc0, 157, 23:20, 31:28) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_5, 158, 10:0, 10:0) \ + HANDLER(EmcPmacroObDdllLongDqsRank1_5, 158, 26:16, 21:11) \ + HANDLER(EmcCmdBrlshft0, 158, 5:0, 27:22) \ + HANDLER(EmcPmacroTxSelClkSrc0, 158, 26:24, 30:28) \ + HANDLER(EmcPmacroTxSelClkSrc1, 158, 0:0, 31:31) \ + HANDLER(EmcPmacroIbDdllLongDqsRank0_0, 159, 10:0, 10:0) \ + HANDLER(EmcPmacroIbDdllLongDqsRank0_0, 159, 26:16, 21:11) \ + HANDLER(EmcCmdBrlshft1, 159, 5:0, 27:22) \ + HANDLER(EmcPmacroTxSelClkSrc1, 159, 4:1, 31:28) \ + HANDLER(EmcPmacroIbDdllLongDqsRank0_1, 160, 10:0, 10:0) \ + HANDLER(EmcPmacroIbDdllLongDqsRank0_1, 160, 26:16, 21:11) \ + HANDLER(EmcCmdBrlshft2, 160, 5:0, 27:22) \ + HANDLER(EmcPmacroTxSelClkSrc1, 160, 8:5, 31:28) \ + HANDLER(EmcPmacroIbDdllLongDqsRank0_2, 161, 10:0, 10:0) \ + HANDLER(EmcPmacroIbDdllLongDqsRank0_2, 161, 26:16, 21:11) \ + HANDLER(EmcCmdBrlshft3, 161, 5:0, 27:22) \ + HANDLER(EmcPmacroTxSelClkSrc1, 161, 10:9, 29:28) \ + HANDLER(EmcPmacroTxSelClkSrc1, 161, 17:16, 31:30) \ + HANDLER(EmcPmacroIbDdllLongDqsRank0_3, 162, 10:0, 10:0) \ + HANDLER(EmcPmacroIbDdllLongDqsRank0_3, 162, 26:16, 21:11) \ + HANDLER(EmcWev, 162, 5:0, 27:22) \ + HANDLER(EmcPmacroTxSelClkSrc1, 162, 21:18, 31:28) \ + HANDLER(EmcPmacroIbDdllLongDqsRank1_0, 163, 10:0, 10:0) \ + HANDLER(EmcPmacroIbDdllLongDqsRank1_0, 163, 26:16, 21:11) \ + HANDLER(EmcWsv, 163, 5:0, 27:22) \ + HANDLER(EmcPmacroTxSelClkSrc1, 163, 25:22, 31:28) \ + HANDLER(EmcPmacroIbDdllLongDqsRank1_1, 164, 10:0, 10:0) \ + HANDLER(EmcPmacroIbDdllLongDqsRank1_1, 164, 26:16, 21:11) \ + HANDLER(EmcCfg3, 164, 2:0, 24:22) \ + HANDLER(EmcCfg3, 164, 6:4, 27:25) \ + HANDLER(EmcPmacroTxSelClkSrc1, 164, 26:26, 28:28) \ + HANDLER(EmcPmacroTxSelClkSrc3, 164, 2:0, 31:29) \ + HANDLER(EmcPmacroIbDdllLongDqsRank1_2, 165, 10:0, 10:0) \ + HANDLER(EmcPmacroIbDdllLongDqsRank1_2, 165, 26:16, 21:11) \ + HANDLER(EmcPutermWidth, 165, 31:31, 22:22) \ + HANDLER(EmcPutermWidth, 165, 4:0, 27:23) \ + HANDLER(EmcPmacroTxSelClkSrc3, 165, 6:3, 31:28) \ + HANDLER(EmcPmacroIbDdllLongDqsRank1_3, 166, 10:0, 10:0) \ + HANDLER(EmcPmacroIbDdllLongDqsRank1_3, 166, 26:16, 21:11) \ + HANDLER(McEmemArbTimingRcd, 166, 5:0, 27:22) \ + HANDLER(EmcPmacroTxSelClkSrc3, 166, 10:7, 31:28) \ + HANDLER(EmcPmacroDdllLongCmd_0, 167, 10:0, 10:0) \ + HANDLER(EmcPmacroDdllLongCmd_0, 167, 26:16, 21:11) \ + HANDLER(McEmemArbTimingCcdmw, 167, 5:0, 27:22) \ + HANDLER(EmcPmacroTxSelClkSrc3, 167, 19:16, 31:28) \ + HANDLER(EmcPmacroDdllLongCmd_1, 168, 10:0, 10:0) \ + HANDLER(EmcPmacroDdllLongCmd_1, 168, 26:16, 21:11) \ + HANDLER(McEmemArbOverride, 168, 27:27, 22:22) \ + HANDLER(McEmemArbOverride, 168, 26:26, 23:23) \ + HANDLER(McEmemArbOverride, 168, 16:16, 24:24) \ + HANDLER(McEmemArbOverride, 168, 10:10, 25:25) \ + HANDLER(McEmemArbOverride, 168, 4:4, 26:26) \ + HANDLER(McEmemArbOverride, 168, 3:3, 27:27) \ + HANDLER(EmcPmacroTxSelClkSrc3, 168, 23:20, 31:28) \ + HANDLER(EmcPmacroDdllLongCmd_2, 169, 10:0, 10:0) \ + HANDLER(EmcPmacroDdllLongCmd_2, 169, 26:16, 21:11) \ + HANDLER(EmcRrd, 169, 4:0, 26:22) \ + HANDLER(EmcRext, 169, 4:0, 31:27) \ + HANDLER(EmcPmacroDdllLongCmd_3, 170, 10:0, 10:0) \ + HANDLER(EmcPmacroDdllLongCmd_3, 170, 26:16, 21:11) \ + HANDLER(EmcTClkStop, 170, 4:0, 26:22) \ + HANDLER(EmcWext, 170, 4:0, 31:27) \ + HANDLER(EmcPmacroPerbitFgcgCtrl0, 171, 10:0, 10:0) \ + HANDLER(EmcPmacroPerbitFgcgCtrl0, 171, 26:16, 21:11) \ + HANDLER(EmcRefctrl2, 171, 0:0, 22:22) \ + HANDLER(EmcRefctrl2, 171, 26:24, 25:23) \ + HANDLER(EmcRefctrl2, 171, 31:31, 26:26) \ + HANDLER(EmcWeDuration, 171, 4:0, 31:27) \ + HANDLER(EmcPmacroPerbitFgcgCtrl1, 172, 10:0, 10:0) \ + HANDLER(EmcPmacroPerbitFgcgCtrl1, 172, 26:16, 21:11) \ + HANDLER(EmcWsDuration, 172, 4:0, 26:22) \ + HANDLER(EmcPmacroPadCfgCtrl, 172, 0:0, 27:27) \ + HANDLER(EmcPmacroPadCfgCtrl, 172, 9:9, 28:28) \ + HANDLER(EmcPmacroPadCfgCtrl, 172, 13:13, 29:29) \ + HANDLER(EmcPmacroPadCfgCtrl, 172, 17:16, 31:30) \ + HANDLER(EmcPmacroPerbitFgcgCtrl2, 173, 10:0, 10:0) \ + HANDLER(EmcPmacroPerbitFgcgCtrl2, 173, 26:16, 21:11) \ + HANDLER(McEmemArbTimingRrd, 173, 4:0, 26:22) \ + HANDLER(McEmemArbTimingR2R, 173, 4:0, 31:27) \ + HANDLER(EmcPmacroPerbitFgcgCtrl3, 174, 10:0, 10:0) \ + HANDLER(EmcPmacroPerbitFgcgCtrl3, 174, 26:16, 21:11) \ + HANDLER(McEmemArbTimingW2W, 174, 4:0, 26:22) \ + HANDLER(EmcPmacroTxSelClkSrc3, 174, 26:24, 29:27) \ + HANDLER(EmcPmacroTxSelClkSrc2, 174, 1:0, 31:30) \ + HANDLER(EmcPmacroPerbitFgcgCtrl4, 175, 10:0, 10:0) \ + HANDLER(EmcPmacroPerbitFgcgCtrl4, 175, 26:16, 21:11) \ + HANDLER(EmcPmacroTxSelClkSrc2, 175, 10:2, 30:22) \ + HANDLER(EmcPmacroTxSelClkSrc2, 175, 16:16, 31:31) \ + HANDLER(EmcPmacroPerbitFgcgCtrl5, 176, 10:0, 10:0) \ + HANDLER(EmcPmacroPerbitFgcgCtrl5, 176, 26:16, 21:11) \ + HANDLER(EmcPmacroTxSelClkSrc2, 176, 26:17, 31:22) \ + HANDLER(McEmemArbCfg, 177, 8:0, 8:0) \ + HANDLER(McEmemArbCfg, 177, 20:16, 13:9) \ + HANDLER(McEmemArbCfg, 177, 31:24, 21:14) \ + HANDLER(EmcPmacroTxSelClkSrc4, 177, 9:0, 31:22) \ + HANDLER(McEmemArbMisc1, 178, 12:0, 12:0) \ + HANDLER(McEmemArbMisc1, 178, 25:21, 17:13) \ + HANDLER(McEmemArbMisc1, 178, 31:28, 21:18) \ + HANDLER(EmcPmacroTxSelClkSrc4, 178, 10:10, 22:22) \ + HANDLER(EmcPmacroTxSelClkSrc4, 178, 24:16, 31:23) \ + HANDLER(EmcMrsWaitCnt2, 179, 9:0, 9:0) \ + HANDLER(EmcMrsWaitCnt2, 179, 26:16, 20:10) \ + HANDLER(EmcOdtWrite, 179, 5:0, 26:21) \ + HANDLER(EmcOdtWrite, 179, 11:8, 30:27) \ + HANDLER(EmcOdtWrite, 179, 31:31, 31:31) \ + HANDLER(EmcMrsWaitCnt, 180, 9:0, 9:0) \ + HANDLER(EmcMrsWaitCnt, 180, 26:16, 20:10) \ + HANDLER(EmcPmacroIbRxrt, 180, 10:0, 31:21) \ + HANDLER(EmcAutoCalInterval, 181, 20:0, 20:0) \ + HANDLER(EmcPmacroDdllLongCmd_4, 181, 10:0, 31:21) \ + HANDLER(McEmemArbRefpbHpCtrl, 182, 6:0, 6:0) \ + HANDLER(McEmemArbRefpbHpCtrl, 182, 14:8, 13:7) \ + HANDLER(McEmemArbRefpbHpCtrl, 182, 22:16, 20:14) \ + HANDLER(McEmemArbOutstandingReq, 182, 8:0, 29:21) \ + HANDLER(McEmemArbOutstandingReq, 182, 31:30, 31:30) \ + HANDLER(EmcXm2CompPadCtrl2, 183, 5:0, 5:0) \ + HANDLER(EmcXm2CompPadCtrl2, 183, 17:12, 11:6) \ + HANDLER(EmcXm2CompPadCtrl2, 183, 21:20, 13:12) \ + HANDLER(EmcXm2CompPadCtrl2, 183, 29:24, 19:14) \ + HANDLER(EmcPmacroCmdCtrl0, 183, 0:0, 20:20) \ + HANDLER(EmcPmacroCmdCtrl0, 183, 5:4, 22:21) \ + HANDLER(EmcPmacroCmdCtrl0, 183, 8:8, 23:23) \ + HANDLER(EmcPmacroCmdCtrl0, 183, 13:12, 25:24) \ + HANDLER(EmcPmacroCmdCtrl0, 183, 16:16, 26:26) \ + HANDLER(EmcPmacroCmdCtrl0, 183, 21:20, 28:27) \ + HANDLER(EmcPmacroCmdCtrl0, 183, 24:24, 29:29) \ + HANDLER(EmcPmacroCmdCtrl0, 183, 29:28, 31:30) \ + HANDLER(EmcCfgDigDll_1, 184, 19:0, 19:0) \ + HANDLER(EmcPmacroCmdCtrl1, 184, 0:0, 20:20) \ + HANDLER(EmcPmacroCmdCtrl1, 184, 5:4, 22:21) \ + HANDLER(EmcPmacroCmdCtrl1, 184, 8:8, 23:23) \ + HANDLER(EmcPmacroCmdCtrl1, 184, 13:12, 25:24) \ + HANDLER(EmcPmacroCmdCtrl1, 184, 16:16, 26:26) \ + HANDLER(EmcPmacroCmdCtrl1, 184, 21:20, 28:27) \ + HANDLER(EmcPmacroCmdCtrl1, 184, 24:24, 29:29) \ + HANDLER(EmcPmacroCmdCtrl1, 184, 29:28, 31:30) \ + HANDLER(EmcQuseBrlshft0, 185, 19:0, 19:0) \ + HANDLER(EmcPmacroCmdCtrl2, 185, 0:0, 20:20) \ + HANDLER(EmcPmacroCmdCtrl2, 185, 5:4, 22:21) \ + HANDLER(EmcPmacroCmdCtrl2, 185, 8:8, 23:23) \ + HANDLER(EmcPmacroCmdCtrl2, 185, 13:12, 25:24) \ + HANDLER(EmcPmacroCmdCtrl2, 185, 16:16, 26:26) \ + HANDLER(EmcPmacroCmdCtrl2, 185, 21:20, 28:27) \ + HANDLER(EmcPmacroCmdCtrl2, 185, 24:24, 29:29) \ + HANDLER(EmcPmacroCmdCtrl2, 185, 29:28, 31:30) \ + HANDLER(EmcQuseBrlshft1, 186, 19:0, 19:0) \ + HANDLER(EmcPmacroDsrVttgenCtrl0, 186, 3:0, 23:20) \ + HANDLER(EmcPmacroDsrVttgenCtrl0, 186, 15:8, 31:24) \ + HANDLER(EmcQuseBrlshft2, 187, 19:0, 19:0) \ + HANDLER(EmcPmacroPerbitRfu1Ctrl0, 187, 5:0, 25:20) \ + HANDLER(EmcPmacroPerbitRfu1Ctrl0, 187, 21:16, 31:26) \ + HANDLER(EmcQuseBrlshft3, 188, 19:0, 19:0) \ + HANDLER(EmcPmacroPerbitRfu1Ctrl1, 188, 5:0, 25:20) \ + HANDLER(EmcPmacroPerbitRfu1Ctrl1, 188, 21:16, 31:26) \ + HANDLER(EmcDbg, 189, 4:0, 4:0) \ + HANDLER(EmcDbg, 189, 13:9, 9:5) \ + HANDLER(EmcDbg, 189, 31:24, 17:10) \ + HANDLER(EmcTRefBw, 189, 13:0, 31:18) \ + HANDLER(EmcZcalWaitCnt, 191, 10:0, 10:0) \ + HANDLER(EmcZcalWaitCnt, 191, 21:16, 16:11) \ + HANDLER(EmcZcalWaitCnt, 191, 31:31, 17:17) \ + HANDLER(EmcQpop, 191, 6:0, 24:18) \ + HANDLER(EmcQpop, 191, 22:16, 31:25) \ + HANDLER(EmcZcalMrwCmd, 192, 7:0, 7:0) \ + HANDLER(EmcZcalMrwCmd, 192, 23:16, 15:8) \ + HANDLER(EmcZcalMrwCmd, 192, 31:30, 17:16) \ + HANDLER(EmcPmacroAutocalCfgCommon, 192, 5:0, 23:18) \ + HANDLER(EmcPmacroAutocalCfgCommon, 192, 13:8, 29:24) \ + HANDLER(EmcPmacroAutocalCfgCommon, 192, 16:16, 30:30) \ + HANDLER(EmcPmacroTxSelClkSrc4, 192, 25:25, 31:31) \ + HANDLER(EmcPmacroDllCfg1, 193, 10:0, 10:0) \ + HANDLER(EmcPmacroDllCfg1, 193, 13:12, 12:11) \ + HANDLER(EmcPmacroDllCfg1, 193, 17:16, 14:13) \ + HANDLER(EmcPmacroDllCfg1, 193, 21:20, 16:15) \ + HANDLER(EmcPmacroDllCfg1, 193, 24:24, 17:17) \ + HANDLER(EmcPmacroPerbitRfu1Ctrl2, 193, 5:0, 23:18) \ + HANDLER(EmcPmacroPerbitRfu1Ctrl2, 193, 21:16, 29:24) \ + HANDLER(EmcPmacroTxSelClkSrc4, 193, 26:26, 30:30) \ + HANDLER(EmcPmacroTxSelClkSrc5, 193, 0:0, 31:31) \ + HANDLER(EmcPmacroCmdBrickCtrlFdpd, 194, 17:0, 17:0) \ + HANDLER(EmcPmacroPerbitRfu1Ctrl3, 194, 5:0, 23:18) \ + HANDLER(EmcPmacroPerbitRfu1Ctrl3, 194, 21:16, 29:24) \ + HANDLER(EmcPmacroTxSelClkSrc5, 194, 2:1, 31:30) \ + HANDLER(EmcPmacroDataBrickCtrlFdpd, 195, 17:0, 17:0) \ + HANDLER(EmcPmacroPerbitRfu1Ctrl4, 195, 5:0, 23:18) \ + HANDLER(EmcPmacroPerbitRfu1Ctrl4, 195, 21:16, 29:24) \ + HANDLER(EmcPmacroTxSelClkSrc5, 195, 4:3, 31:30) \ + HANDLER(EmcDynSelfRefControl, 196, 15:0, 15:0) \ + HANDLER(EmcDynSelfRefControl, 196, 31:31, 16:16) \ + HANDLER(McEmemArbRefpbBankCtrl, 196, 6:0, 23:17) \ + HANDLER(McEmemArbRefpbBankCtrl, 196, 14:8, 30:24) \ + HANDLER(McEmemArbRefpbBankCtrl, 196, 31:31, 31:31) \ + HANDLER(EmcPmacroCmdPadRxCtrl, 197, 1:0, 1:0) \ + HANDLER(EmcPmacroCmdPadRxCtrl, 197, 5:4, 3:2) \ + HANDLER(EmcPmacroCmdPadRxCtrl, 197, 12:12, 4:4) \ + HANDLER(EmcPmacroCmdPadRxCtrl, 197, 19:15, 9:5) \ + HANDLER(EmcPmacroCmdPadRxCtrl, 197, 27:21, 16:10) \ + HANDLER(EmcPmacroPerbitRfu1Ctrl5, 197, 5:0, 22:17) \ + HANDLER(EmcPmacroPerbitRfu1Ctrl5, 197, 21:16, 28:23) \ + HANDLER(EmcPmacroTxSelClkSrc5, 197, 7:5, 31:29) \ + HANDLER(EmcPmacroDataPadRxCtrl, 198, 1:0, 1:0) \ + HANDLER(EmcPmacroDataPadRxCtrl, 198, 5:4, 3:2) \ + HANDLER(EmcPmacroDataPadRxCtrl, 198, 12:12, 4:4) \ + HANDLER(EmcPmacroDataPadRxCtrl, 198, 19:15, 9:5) \ + HANDLER(EmcPmacroDataPadRxCtrl, 198, 27:21, 16:10) \ + HANDLER(EmcPmacroTxSelClkSrc5, 198, 10:8, 19:17) \ + HANDLER(EmcPmacroTxSelClkSrc5, 198, 26:16, 30:20) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 198, 0:0, 31:31) \ + HANDLER(EmcRefresh, 199, 15:0, 15:0) \ + HANDLER(EmcCmdQ, 199, 4:0, 20:16) \ + HANDLER(EmcCmdQ, 199, 10:8, 23:21) \ + HANDLER(EmcCmdQ, 199, 14:12, 26:24) \ + HANDLER(EmcCmdQ, 199, 28:24, 31:27) \ + HANDLER(EmcAcpdControl, 210, 15:0, 15:0) \ + HANDLER(EmcAutoCalVrefSel1, 210, 15:0, 31:16) \ + HANDLER(EmcPmacroAutocalCfg0, 211, 3:0, 3:0) \ + HANDLER(EmcPmacroAutocalCfg0, 211, 11:8, 7:4) \ + HANDLER(EmcPmacroAutocalCfg0, 211, 19:16, 11:8) \ + HANDLER(EmcPmacroAutocalCfg0, 211, 27:24, 15:12) \ + HANDLER(EmcPmacroAutocalCfg1, 211, 3:0, 19:16) \ + HANDLER(EmcPmacroAutocalCfg1, 211, 11:8, 23:20) \ + HANDLER(EmcPmacroAutocalCfg1, 211, 19:16, 27:24) \ + HANDLER(EmcPmacroAutocalCfg1, 211, 27:24, 31:28) \ + HANDLER(EmcPmacroAutocalCfg2, 212, 3:0, 3:0) \ + HANDLER(EmcPmacroAutocalCfg2, 212, 11:8, 7:4) \ + HANDLER(EmcPmacroAutocalCfg2, 212, 19:16, 11:8) \ + HANDLER(EmcPmacroAutocalCfg2, 212, 27:24, 15:12) \ + HANDLER(EmcXm2CompPadCtrl3, 212, 5:0, 21:16) \ + HANDLER(EmcXm2CompPadCtrl3, 212, 17:12, 27:22) \ + HANDLER(EmcXm2CompPadCtrl3, 212, 23:20, 31:28) \ + HANDLER(EmcCfgDigDllPeriod, 213, 15:0, 15:0) \ + HANDLER(EmcPreRefreshReqCnt, 213, 15:0, 31:16) \ + HANDLER(EmcPmacroDdllBypass, 214, 0:0, 0:0) \ + HANDLER(EmcPmacroDdllBypass, 214, 11:8, 4:1) \ + HANDLER(EmcPmacroDdllBypass, 214, 16:13, 8:5) \ + HANDLER(EmcPmacroDdllBypass, 214, 27:24, 12:9) \ + HANDLER(EmcPmacroDdllBypass, 214, 31:29, 15:13) \ + HANDLER(EmcPmacroDataPiCtrl, 214, 4:0, 20:16) \ + HANDLER(EmcPmacroDataPiCtrl, 214, 12:8, 25:21) \ + HANDLER(EmcPmacroDataPiCtrl, 214, 21:16, 31:26) \ + HANDLER(EmcPmacroCmdPiCtrl, 215, 4:0, 4:0) \ + HANDLER(EmcPmacroCmdPiCtrl, 215, 12:8, 9:5) \ + HANDLER(EmcPmacroCmdPiCtrl, 215, 21:16, 15:10) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 216, 6:5, 1:0) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 216, 10:10, 2:2) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 216, 16:15, 4:3) \ + HANDLER(EmcPmacroCmdPadTxCtrl, 216, 30:21, 14:5) \ + HANDLER(EmcPmacroDataPadTxCtrl, 216, 0:0, 15:15) \ + HANDLER(EmcPmacroDataPadTxCtrl, 216, 6:5, 17:16) \ + HANDLER(EmcPmacroDataPadTxCtrl, 216, 10:10, 18:18) \ + HANDLER(EmcPmacroDataPadTxCtrl, 216, 16:15, 20:19) \ + HANDLER(EmcPmacroDataPadTxCtrl, 216, 30:21, 30:21) \ + HANDLER(EmcPinGpio, 9, 1:0, 31:30) \ + HANDLER(EmcPinGpioEn, 10, 1:0, 31:30) \ + HANDLER(EmcDevSelect, 11, 1:0, 31:30) \ + HANDLER(EmcZcalWarmColdBootEnables, 12, 1:0, 31:30) \ + HANDLER(EmcCfgDigDllPeriodWarmBoot, 13, 1:0, 31:30) \ + HANDLER(EmcBctSpare13, 45, 31:0, 31:0) \ + HANDLER(EmcBctSpare12, 46, 31:0, 31:0) \ + HANDLER(EmcBctSpare7, 47, 31:0, 31:0) \ + HANDLER(EmcBctSpare6, 48, 31:0, 31:0) \ + HANDLER(EmcBctSpare5, 50, 31:0, 31:0) \ + HANDLER(EmcBctSpare4, 51, 31:0, 31:0) \ + HANDLER(EmcBctSpare3, 56, 31:0, 31:0) \ + HANDLER(EmcBctSpare2, 57, 31:0, 31:0) \ + HANDLER(EmcBctSpare1, 58, 31:0, 31:0) \ + HANDLER(EmcBctSpare0, 59, 31:0, 31:0) \ + HANDLER(EmcBctSpare9, 60, 31:0, 31:0) \ + HANDLER(EmcBctSpare8, 61, 31:0, 31:0) \ + HANDLER(BootRomPatchData, 62, 31:0, 31:0) \ + HANDLER(BootRomPatchControl, 63, 31:0, 31:0) \ + HANDLER(McClkenOverrideAllWarmBoot, 65, 0:0, 31:31) \ + HANDLER(EmcExtraRefreshNum, 66, 2:0, 31:29) \ + HANDLER(PmcIoDpd3ReqWait, 72, 2:0, 30:28) \ + HANDLER(EmcClkenOverrideAllWarmBoot, 72, 0:0, 31:31) \ + HANDLER(MemoryType, 73, 2:0, 30:28) \ + HANDLER(EmcMrsWarmBootEnable, 73, 0:0, 31:31) \ + HANDLER(PmcIoDpd4ReqWait, 74, 2:0, 30:28) \ + HANDLER(ClearClk2Mc1, 74, 0:0, 31:31) \ + HANDLER(EmcWarmBootExtraModeRegWriteEnable, 75, 0:0, 28:28) \ + HANDLER(ClkRstControllerPllmMisc2OverrideEnable, 75, 0:0, 29:29) \ + HANDLER(EmcDbgWriteMux, 75, 0:0, 30:30) \ + HANDLER(AhbArbitrationXbarCtrlMemInitDone, 75, 0:0, 31:31) \ + HANDLER(EmcTimingControlWait, 90, 7:0, 31:24) \ + HANDLER(EmcZcalWarmBootWait, 91, 7:0, 31:24) \ + HANDLER(WarmBootWait, 92, 7:0, 31:24) \ + HANDLER(EmcPinProgramWait, 93, 7:0, 31:24) \ + HANDLER(EmcAutoCalWait, 114, 9:0, 31:22) \ + HANDLER(SwizzleRankByteEncode, 215, 15:0, 31:16) \ + \ + /* PMC SCRATCH fields for LPDDR2. */ \ + HANDLER(EmcMrwLpddr2ZcalWarmBoot, 5, 23:16, 7:0) \ + HANDLER(EmcMrwLpddr2ZcalWarmBoot, 5, 7:0, 15:8) \ + HANDLER(EmcWarmBootMrwExtra, 5, 23:16, 23:16) \ + HANDLER(EmcWarmBootMrwExtra, 5, 7:0, 31:24) \ + HANDLER(EmcMrwLpddr2ZcalWarmBoot, 6, 31:30, 1:0) \ + HANDLER(EmcWarmBootMrwExtra, 6, 31:30, 3:2) \ + HANDLER(EmcMrwLpddr2ZcalWarmBoot, 6, 27:26, 5:4) \ + HANDLER(EmcWarmBootMrwExtra, 6, 27:26, 7:6) \ + HANDLER(EmcMrw6, 8, 27:0, 27:0) \ + HANDLER(EmcMrw6, 8, 31:30, 29:28) \ + HANDLER(EmcMrw8, 9, 27:0, 27:0) \ + HANDLER(EmcMrw8, 9, 31:30, 29:28) \ + HANDLER(EmcMrw9, 10, 27:0, 27:0) \ + HANDLER(EmcMrw9, 10, 31:30, 29:28) \ + HANDLER(EmcMrw10, 11, 27:0, 27:0) \ + HANDLER(EmcMrw10, 11, 31:30, 29:28) \ + HANDLER(EmcMrw12, 12, 27:0, 27:0) \ + HANDLER(EmcMrw12, 12, 31:30, 29:28) \ + HANDLER(EmcMrw13, 13, 27:0, 27:0) \ + HANDLER(EmcMrw13, 13, 31:30, 29:28) \ + HANDLER(EmcMrw14, 14, 27:0, 27:0) \ + HANDLER(EmcMrw14, 14, 31:30, 29:28) \ + HANDLER(EmcMrw1, 15, 7:0, 7:0) \ + HANDLER(EmcMrw1, 15, 23:16, 15:8) \ + HANDLER(EmcMrw1, 15, 27:26, 17:16) \ + HANDLER(EmcMrw1, 15, 31:30, 19:18) \ + HANDLER(EmcWarmBootMrwExtra, 16, 7:0, 7:0) \ + HANDLER(EmcWarmBootMrwExtra, 16, 23:16, 15:8) \ + HANDLER(EmcWarmBootMrwExtra, 16, 27:26, 17:16) \ + HANDLER(EmcWarmBootMrwExtra, 16, 31:30, 19:18) \ + HANDLER(EmcMrw2, 17, 7:0, 7:0) \ + HANDLER(EmcMrw2, 17, 23:16, 15:8) \ + HANDLER(EmcMrw2, 17, 27:26, 17:16) \ + HANDLER(EmcMrw2, 17, 31:30, 19:18) \ + HANDLER(EmcMrw3, 18, 7:0, 7:0) \ + HANDLER(EmcMrw3, 18, 23:16, 15:8) \ + HANDLER(EmcMrw3, 18, 27:26, 17:16) \ + HANDLER(EmcMrw3, 18, 31:30, 19:18) \ + HANDLER(EmcMrw4, 19, 7:0, 7:0) \ + HANDLER(EmcMrw4, 19, 23:16, 15:8) \ + HANDLER(EmcMrw4, 19, 27:26, 17:16) \ + HANDLER(EmcMrw4, 19, 31:30, 19:18) + +#define FOREACH_SDRAM_SECURE_SCRATCH_REGISTER_MARIKO(HANDLER) \ + /* PMC SECURE_SCRATCH fields. */ \ + HANDLER(EmcCmdMappingByte, 8, 31:0, 31:0) \ + HANDLER(EmcPmacroBrickMapping0, 9, 31:0, 31:0) \ + HANDLER(EmcPmacroBrickMapping1, 10, 31:0, 31:0) \ + HANDLER(EmcPmacroBrickMapping2, 11, 31:0, 31:0) \ + HANDLER(McVideoProtectGpuOverride0, 12, 31:0, 31:0) \ + HANDLER(EmcCmdMappingCmd0_0, 13, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd0_0, 13, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd0_0, 13, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd0_0, 13, 30:24, 27:21) \ + HANDLER(McUntranslatedRegionCheck, 13, 0:0, 28:28) \ + HANDLER(McUntranslatedRegionCheck, 13, 9:8, 30:29) \ + HANDLER(EmcAdrCfg, 13, 0:0, 31:31) \ + HANDLER(EmcCmdMappingCmd0_1, 14, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd0_1, 14, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd0_1, 14, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd0_1, 14, 30:24, 27:21) \ + HANDLER(McVideoProtectBomAdrHi, 14, 1:0, 29:28) \ + HANDLER(McVideoProtectWriteAccess, 14, 1:0, 31:30) \ + HANDLER(EmcCmdMappingCmd1_0, 15, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd1_0, 15, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd1_0, 15, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd1_0, 15, 30:24, 27:21) \ + HANDLER(McSecCarveoutAdrHi, 15, 1:0, 29:28) \ + HANDLER(McMtsCarveoutAdrHi, 15, 1:0, 31:30) \ + HANDLER(EmcCmdMappingCmd1_1, 16, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd1_1, 16, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd1_1, 16, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd1_1, 16, 30:24, 27:21) \ + HANDLER(McGeneralizedCarveout5BomHi, 16, 1:0, 29:28) \ + HANDLER(McGeneralizedCarveout3BomHi, 16, 1:0, 31:30) \ + HANDLER(EmcCmdMappingCmd2_0, 17, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd2_0, 17, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd2_0, 17, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd2_0, 17, 30:24, 27:21) \ + HANDLER(McGeneralizedCarveout2BomHi, 17, 1:0, 29:28) \ + HANDLER(McGeneralizedCarveout4BomHi, 17, 1:0, 31:30) \ + HANDLER(EmcCmdMappingCmd2_1, 18, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd2_1, 18, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd2_1, 18, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd2_1, 18, 30:24, 27:21) \ + HANDLER(McGeneralizedCarveout1BomHi, 18, 1:0, 29:28) \ + HANDLER(EmcFbioSpare, 18, 1:1, 30:30) \ + HANDLER(EmcFbioCfg8, 18, 15:15, 31:31) \ + HANDLER(EmcCmdMappingCmd3_0, 19, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd3_0, 19, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd3_0, 19, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd3_0, 19, 30:24, 27:21) \ + HANDLER(McEmemAdrCfg, 19, 0:0, 28:28) \ + HANDLER(McSecCarveoutProtectWriteAccess, 19, 0:0, 29:29) \ + HANDLER(McMtsCarveoutRegCtrl, 19, 0:0, 30:30) \ + HANDLER(McVideoProtectVprOverride, 19, 0:0, 31:31) \ + HANDLER(EmcCmdMappingCmd3_1, 20, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd3_1, 20, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd3_1, 20, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd3_1, 20, 30:24, 27:21) \ + HANDLER(McGeneralizedCarveout2Cfg0, 20, 6:3, 31:28) \ + HANDLER(McGeneralizedCarveout4Cfg0, 39, 26:0, 26:0) \ + HANDLER(McGeneralizedCarveout2Cfg0, 39, 10:7, 30:27) \ + HANDLER(McVideoProtectVprOverride, 39, 1:1, 31:31) \ + HANDLER(McGeneralizedCarveout5Cfg0, 40, 26:0, 26:0) \ + HANDLER(McGeneralizedCarveout2Cfg0, 40, 17:14, 30:27) \ + HANDLER(McVideoProtectVprOverride, 40, 2:2, 31:31) \ + HANDLER(EmcCmdMappingCmd0_2, 41, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd0_2, 41, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd0_2, 41, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd0_2, 41, 27:24, 24:21) \ + HANDLER(McGeneralizedCarveout2Cfg0, 41, 21:18, 28:25) \ + HANDLER(McGeneralizedCarveout2Cfg0, 41, 13:11, 31:29) \ + HANDLER(EmcCmdMappingCmd1_2, 42, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd1_2, 42, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd1_2, 42, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd1_2, 42, 27:24, 24:21) \ + HANDLER(McGeneralizedCarveout1Cfg0, 42, 6:3, 28:25) \ + HANDLER(McGeneralizedCarveout1Cfg0, 42, 13:11, 31:29) \ + HANDLER(EmcCmdMappingCmd2_2, 43, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd2_2, 43, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd2_2, 43, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd2_2, 43, 27:24, 24:21) \ + HANDLER(McGeneralizedCarveout1Cfg0, 43, 10:7, 28:25) \ + HANDLER(McGeneralizedCarveout3Cfg0, 43, 13:11, 31:29) \ + HANDLER(EmcCmdMappingCmd3_2, 44, 6:0, 6:0) \ + HANDLER(EmcCmdMappingCmd3_2, 44, 14:8, 13:7) \ + HANDLER(EmcCmdMappingCmd3_2, 44, 22:16, 20:14) \ + HANDLER(EmcCmdMappingCmd3_2, 44, 27:24, 24:21) \ + HANDLER(McGeneralizedCarveout1Cfg0, 44, 17:14, 28:25) \ + HANDLER(McVideoProtectVprOverride, 44, 3:3, 29:29) \ + HANDLER(McVideoProtectVprOverride, 44, 7:6, 31:30) \ + HANDLER(McEmemAdrCfgChannelMask, 45, 31:9, 22:0) \ + HANDLER(McEmemAdrCfgDev0, 45, 2:0, 25:23) \ + HANDLER(McEmemAdrCfgDev0, 45, 9:8, 27:26) \ + HANDLER(McEmemAdrCfgDev0, 45, 19:16, 31:28) \ + HANDLER(McEmemAdrCfgBankMask0, 46, 31:10, 21:0) \ + HANDLER(McEmemAdrCfgDev1, 46, 2:0, 24:22) \ + HANDLER(McEmemAdrCfgDev1, 46, 9:8, 26:25) \ + HANDLER(McEmemAdrCfgDev1, 46, 19:16, 30:27) \ + HANDLER(McVideoProtectVprOverride, 46, 8:8, 31:31) \ + HANDLER(McEmemAdrCfgBankMask1, 47, 31:10, 21:0) \ + HANDLER(McGeneralizedCarveout1Cfg0, 47, 21:18, 25:22) \ + HANDLER(McGeneralizedCarveout3Cfg0, 47, 6:3, 29:26) \ + HANDLER(McVideoProtectVprOverride, 47, 9:9, 30:30) \ + HANDLER(McVideoProtectVprOverride, 47, 11:11, 31:31) \ + HANDLER(McEmemAdrCfgBankMask2, 48, 31:10, 21:0) \ + HANDLER(McGeneralizedCarveout3Cfg0, 48, 10:7, 25:22) \ + HANDLER(McGeneralizedCarveout3Cfg0, 48, 17:14, 29:26) \ + HANDLER(McVideoProtectVprOverride, 48, 15:14, 31:30) \ + HANDLER(McVideoProtectGpuOverride1, 49, 15:0, 15:0) \ + HANDLER(McEmemCfg, 49, 13:0, 29:16) \ + HANDLER(McEmemCfg, 49, 31:31, 30:30) \ + HANDLER(McVideoProtectVprOverride, 49, 17:17, 31:31) \ + HANDLER(McGeneralizedCarveout3Bom, 50, 31:17, 14:0) \ + HANDLER(McGeneralizedCarveout1Bom, 50, 31:17, 29:15) \ + HANDLER(McVideoProtectVprOverride, 50, 19:18, 31:30) \ + HANDLER(McGeneralizedCarveout4Bom, 51, 31:17, 14:0) \ + HANDLER(McGeneralizedCarveout2Bom, 51, 31:17, 29:15) \ + HANDLER(McVideoProtectVprOverride, 51, 21:20, 31:30) \ + HANDLER(McGeneralizedCarveout5Bom, 52, 31:17, 14:0) \ + HANDLER(McVideoProtectBom, 52, 31:20, 26:15) \ + HANDLER(McGeneralizedCarveout3Cfg0, 52, 21:18, 30:27) \ + HANDLER(McVideoProtectVprOverride, 52, 22:22, 31:31) \ + HANDLER(McVideoProtectSizeMb, 53, 11:0, 11:0) \ + HANDLER(McSecCarveoutBom, 53, 31:20, 23:12) \ + HANDLER(McVideoProtectVprOverride, 53, 23:23, 24:24) \ + HANDLER(McVideoProtectVprOverride, 53, 26:26, 25:25) \ + HANDLER(McVideoProtectVprOverride, 53, 31:29, 28:26) \ + HANDLER(McVideoProtectVprOverride1, 53, 1:0, 30:29) \ + HANDLER(McVideoProtectVprOverride1, 53, 4:4, 31:31) \ + HANDLER(McSecCarveoutSizeMb, 54, 11:0, 11:0) \ + HANDLER(McMtsCarveoutBom, 54, 31:20, 23:12) \ + HANDLER(McVideoProtectVprOverride1, 54, 12:5, 31:24) \ + HANDLER(McMtsCarveoutSizeMb, 55, 11:0, 11:0) \ + HANDLER(McGeneralizedCarveout4Size128kb, 55, 11:0, 23:12) \ + HANDLER(McVideoProtectVprOverride1, 55, 16:13, 27:24) \ + HANDLER(McVideoProtectVprOverride1, 55, 26:25, 29:28) \ + HANDLER(McGeneralizedCarveout2Cfg0, 55, 1:0, 31:30) \ + HANDLER(McGeneralizedCarveout3Size128kb, 56, 11:0, 11:0) \ + HANDLER(McGeneralizedCarveout2Size128kb, 56, 11:0, 23:12) \ + HANDLER(McGeneralizedCarveout2Cfg0, 56, 2:2, 24:24) \ + HANDLER(McGeneralizedCarveout2Cfg0, 56, 26:22, 29:25) \ + HANDLER(McGeneralizedCarveout1Cfg0, 56, 1:0, 31:30) \ + HANDLER(McGeneralizedCarveout1Size128kb, 57, 11:0, 11:0) \ + HANDLER(McGeneralizedCarveout5Size128kb, 57, 11:0, 23:12) \ + HANDLER(McGeneralizedCarveout1Cfg0, 57, 2:2, 24:24) \ + HANDLER(McGeneralizedCarveout1Cfg0, 57, 26:22, 29:25) \ + HANDLER(McGeneralizedCarveout3Cfg0, 57, 1:0, 31:30) \ + HANDLER(McGeneralizedCarveout3Cfg0, 58, 2:2, 0:0) \ + HANDLER(McGeneralizedCarveout3Cfg0, 58, 26:22, 5:1) \ + HANDLER(McGeneralizedCarveout1Access0, 59, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout1Access1, 60, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout1Access2, 61, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout1Access3, 62, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout1Access4, 63, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2Access0, 64, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2Access1, 65, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2Access2, 66, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2Access3, 67, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2Access4, 68, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3Access0, 69, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3Access1, 70, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3Access2, 71, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3Access3, 72, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3Access4, 73, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4Access0, 74, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4Access1, 75, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4Access2, 76, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4Access3, 77, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4Access4, 78, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout5Access0, 79, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout5Access1, 80, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout5Access2, 81, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout5Access3, 82, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout1ForceInternalAccess0, 84, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout1ForceInternalAccess1, 85, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout1ForceInternalAccess2, 86, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout1ForceInternalAccess3, 87, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout1ForceInternalAccess4, 88, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2ForceInternalAccess0, 89, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2ForceInternalAccess1, 90, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2ForceInternalAccess2, 91, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2ForceInternalAccess3, 92, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout2ForceInternalAccess4, 93, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3ForceInternalAccess0, 94, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3ForceInternalAccess1, 95, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3ForceInternalAccess2, 96, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3ForceInternalAccess3, 97, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout3ForceInternalAccess4, 98, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4ForceInternalAccess0, 99, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4ForceInternalAccess1, 100, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4ForceInternalAccess2, 101, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4ForceInternalAccess3, 102, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout4ForceInternalAccess4, 103, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout5ForceInternalAccess0, 104, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout5ForceInternalAccess1, 105, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout5ForceInternalAccess2, 106, 31:0, 31:0) \ + HANDLER(McGeneralizedCarveout5ForceInternalAccess3, 107, 31:0, 31:0) diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/sein/fusee_secure_initialize.cpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/sein/fusee_secure_initialize.cpp new file mode 100644 index 00000000..25076494 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/sein/fusee_secure_initialize.cpp @@ -0,0 +1,377 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fusee_secure_initialize.hpp" +#include "../fusee_registers_di.hpp" + +namespace ams::nxboot { + + namespace { + + constexpr inline const uintptr_t CLKRST = secmon::MemoryRegionPhysicalDeviceClkRst.GetAddress(); + constexpr inline const uintptr_t PMC = secmon::MemoryRegionPhysicalDevicePmc.GetAddress(); + constexpr inline const uintptr_t MC = secmon::MemoryRegionPhysicalDeviceMemoryController.GetAddress(); + constexpr inline const uintptr_t APB = secmon::MemoryRegionPhysicalDeviceApbMisc.GetAddress(); + constexpr inline const uintptr_t AHB = AHB_ARBC(0); + constexpr inline const uintptr_t I2S = I2S_REG(0); + constexpr inline const uintptr_t DISP1 = secmon::MemoryRegionPhysicalDeviceDisp1.GetAddress(); + constexpr inline const uintptr_t VIC = secmon::MemoryRegionPhysicalDeviceDsi.GetAddress() + 0x40000; + constexpr inline const uintptr_t TIMER = secmon::MemoryRegionPhysicalDeviceTimer.GetAddress(); + constexpr inline const uintptr_t SYSCTR0 = secmon::MemoryRegionPhysicalDeviceSysCtr0.GetAddress(); + + void DoRcmWorkaround(const void *sbk, size_t sbk_size) { + /* Set the SBK inside the security engine. */ + se::SetAesKey(pkg1::AesKeySlot_SecureBoot, sbk, sbk_size); + + /* Lock the SBK/SSK as unreadable. */ + se::LockAesKeySlot(pkg1::AesKeySlot_SecureBoot, se::KeySlotLockFlags_KeyRead); + se::LockAesKeySlot(pkg1::AesKeySlot_SecureStorage, se::KeySlotLockFlags_KeyRead); + + /* Clear TZRAM. */ + std::memset(secmon::MemoryRegionPhysicalTzram.GetPointer(), 0, secmon::MemoryRegionPhysicalTzram.GetSize()); + + /* Clear APBDEV_PMC_CRYPTO_OP. */ + reg::Write(PMC + APBDEV_PMC_CRYPTO_OP, 0); + + /* Clear the boot reason. */ + reg::Write(PMC + APBDEV_PMC_SCRATCH200, 0); + reg::Write(PMC + APBDEV_PMC_CRYPTO_OP, 0); + + /* Clear OBS_OVERRIDE/APB2JTAG_OVERRIDE */ + reg::ReadWrite(AHB + AHB_AHB_SPARE_REG, AHB_REG_BITS_ENUM(AHB_SPARE_REG_OBS_OVERRIDE_EN, DISABLE), + AHB_REG_BITS_ENUM(AHB_SPARE_REG_APB2JTAG_OVERRIDE_EN, DISABLE)); + + /* Clear low bits of APBDEV_PMC_SCRATCH49 */ + reg::ClearBits(PMC + APBDEV_PMC_SCRATCH49, 0x3); + } + + void DoMbistWorkaround() { + /* Enable AHUB/APE clock prior to I2S accesses. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_V, CLK_RST_REG_BITS_ENUM(CLK_ENB_V_CLK_ENB_AHUB, ENABLE)); + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_Y, CLK_RST_REG_BITS_ENUM(CLK_ENB_Y_CLK_ENB_APE, ENABLE)); + + /* Configure CLK_RST_CONTROLLER_CLK_SOURCE_SOR1. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_SOR1, CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SOR1_SOR1_CLK_SEL0, MUX), + CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SOR1_SOR1_CLK_SEL1, SOR1_CLOCK_SWITCH)); + + /* Set CSI clock source as PLLD. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_PLLD_BASE, CLK_RST_REG_BITS_ENUM(PLLD_BASE_PLLD_ENABLE, ENABLE), + CLK_RST_REG_BITS_ENUM(PLLD_BASE_CSI_CLK_SRC, PLL_D)); + + /* Clear APE, VIC, HOST1X, DISP1 reset. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_RST_DEV_Y_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_Y_APE_RST, ENABLE)); + reg::Write(CLKRST + CLK_RST_CONTROLLER_RST_DEV_X_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_X_VIC_RST, ENABLE)); + reg::Write(CLKRST + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_DISP1_RST, ENABLE), CLK_RST_REG_BITS_ENUM(RST_DEV_L_HOST1X_RST, ENABLE)); + + /* Wait two microseconds for the devices to come out of reset. */ + util::WaitMicroSeconds(2); + + /* Set I2S_CTRL.MASTER and clear I2S_CG.SCLG_ENABLE for all I2S registers. */ + reg::ReadWrite(I2S + I2S0_I2S_CTRL, I2S_REG_BITS_ENUM(I2S_CTRL_MASTER, ENABLE)); + reg::ReadWrite(I2S + I2S0_I2S_CG, I2S_REG_BITS_ENUM(I2S_CG_SLCG_ENABLE, FALSE)); + reg::ReadWrite(I2S + I2S1_I2S_CTRL, I2S_REG_BITS_ENUM(I2S_CTRL_MASTER, ENABLE)); + reg::ReadWrite(I2S + I2S1_I2S_CG, I2S_REG_BITS_ENUM(I2S_CG_SLCG_ENABLE, FALSE)); + reg::ReadWrite(I2S + I2S2_I2S_CTRL, I2S_REG_BITS_ENUM(I2S_CTRL_MASTER, ENABLE)); + reg::ReadWrite(I2S + I2S2_I2S_CG, I2S_REG_BITS_ENUM(I2S_CG_SLCG_ENABLE, FALSE)); + reg::ReadWrite(I2S + I2S3_I2S_CTRL, I2S_REG_BITS_ENUM(I2S_CTRL_MASTER, ENABLE)); + reg::ReadWrite(I2S + I2S3_I2S_CG, I2S_REG_BITS_ENUM(I2S_CG_SLCG_ENABLE, FALSE)); + reg::ReadWrite(I2S + I2S4_I2S_CTRL, I2S_REG_BITS_ENUM(I2S_CTRL_MASTER, ENABLE)); + reg::ReadWrite(I2S + I2S4_I2S_CG, I2S_REG_BITS_ENUM(I2S_CG_SLCG_ENABLE, FALSE)); + + /* Set DC_COM_DSC_TOP_CTL.DSC_SLG_OVERRIDE */ + reg::SetBits(DISP1 + DC_COM_DSC_TOP_CTL * sizeof(u32), 0x4); + + /* Set NV_PVIC_THI_SLCG_OVERRIDE_LOW_A */ + reg::SetBits(VIC + NV_PVIC_THI_SLCG_OVERRIDE_LOW_A, 0xFFFFFFFF); + + /* Wait two microseconds for configuration to take. */ + util::WaitMicroSeconds(2); + + /* Set APE, VIC, HOST1X, DISP1 reset. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_RST_DEV_Y_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_Y_APE_RST, ENABLE)); + reg::Write(CLKRST + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_DISP1_RST, ENABLE), CLK_RST_REG_BITS_ENUM(RST_DEV_L_HOST1X_RST, ENABLE)); + reg::Write(CLKRST + CLK_RST_CONTROLLER_RST_DEV_X_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_X_VIC_RST, ENABLE)); + + /* Set clock enable for a select few devices. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_H, CLK_RST_REG_BITS_ENUM(CLK_ENB_H_CLK_ENB_FUSE, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_H_CLK_ENB_PMC, ENABLE)); + + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_CACHE2, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_GPIO, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_TMR, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_RTC, ENABLE)); + + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_U, CLK_RST_REG_BITS_ENUM(CLK_ENB_U_CLK_ENB_CRAM2, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_U_CLK_ENB_IRAMD, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_U_CLK_ENB_IRAMC, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_U_CLK_ENB_IRAMB, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_U_CLK_ENB_IRAMA, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_U_CLK_ENB_CSITE, ENABLE)); + + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_V, CLK_RST_REG_BITS_ENUM(CLK_ENB_V_CLK_ENB_SE, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_V_CLK_ENB_SPDIF_DOUBLER, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_V_CLK_ENB_APB2APE, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_V_CLK_ENB_MSELECT, ENABLE)); + + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_W, CLK_RST_REG_BITS_ENUM(CLK_ENB_W_CLK_ENB_MC1, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_W_CLK_ENB_ENTROPY, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_W_CLK_ENB_PCIERX5, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_W_CLK_ENB_PCIERX4, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_W_CLK_ENB_PCIERX3, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_W_CLK_ENB_PCIERX2, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_W_CLK_ENB_PCIERX1, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_W_CLK_ENB_PCIERX0, ENABLE)); + + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_X, CLK_RST_REG_BITS_ENUM(CLK_ENB_X_CLK_ENB_PLLG_REF, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_X_CLK_ENB_DBGAPB, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_X_CLK_ENB_GPU, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_X_CLK_ENB_MC_BBC, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_X_CLK_ENB_MC_CPU, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_X_CLK_ENB_MC_CBPA, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_X_CLK_ENB_MC_CAPA, ENABLE)); + + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_OUT_ENB_Y, CLK_RST_REG_BITS_ENUM(CLK_ENB_Y_CLK_ENB_MC_CDPA, ENABLE), + CLK_RST_REG_BITS_ENUM(CLK_ENB_Y_CLK_ENB_MC_CCPA, ENABLE)); + + /* Clear all LVL2 clock gate overrides to zero. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRA, 0); + reg::Write(CLKRST + CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRB, 0); + reg::Write(CLKRST + CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRC, 0); + reg::Write(CLKRST + CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRD, 0); + reg::Write(CLKRST + CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRE, 0); + + /* Reset CSI clock source. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_PLLD_BASE, CLK_RST_REG_BITS_ENUM(PLLD_BASE_PLLD_BYPASS, DISABLE), + CLK_RST_REG_BITS_ENUM(PLLD_BASE_PLLD_ENABLE, DISABLE), + CLK_RST_REG_BITS_ENUM(PLLD_BASE_PLLD_REF_DIS, REF_ENABLE), + CLK_RST_REG_BITS_ENUM(PLLD_BASE_CSI_CLK_SRC, BRICK)); + + /* Configure CLK_RST_CONTROLLER_CLK_SOURCE_SOR1. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_SOR1, CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SOR1_SOR1_CLK_SEL0, MUX), + CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SOR1_SOR1_CLK_SEL1, SAFE_CLOCK)); + + /* Configure VI, HOST1X, NVENC clock sources. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_VI, CLK_RST_REG_BITS_ENUM(CLK_SOURCE_VI_VI_CLK_SRC, PLLP_OUT0)); + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_HOST1X, CLK_RST_REG_BITS_ENUM(CLK_SOURCE_HOST1X_HOST1X_CLK_SRC, PLLP_OUT0)); + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_NVENC, CLK_RST_REG_BITS_ENUM(CLK_SOURCE_NVENC_NVENC_CLK_SRC, PLLP_OUT0)); + } + + void EnableArc() { + /* Enable clocks for EMC/MC, using PLLP_OUT0. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_CLK_SOURCE_EMC, CLK_RST_REG_BITS_ENUM(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, PLLP_OUT0)); + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_H_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_H_CLK_ENB_EMC, ENABLE)); + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_H_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_H_CLK_ENB_MEM, ENABLE)); + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_CLK_ENB_X_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_X_CLK_ENB_EMC_DLL, ENABLE)); + + /* Clear reset for MEM/EMC. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_RST_DEV_H_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_H_EMC_RST, ENABLE), + CLK_RST_REG_BITS_ENUM(RST_DEV_H_MEM_RST, ENABLE)); + + /* Wait 5 microseconds for configuration to take. */ + util::WaitMicroSeconds(5); + + /* Enable ARC_CLK_OVR_ON. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRD, CLK_RST_REG_BITS_ENUM(LVL2_CLK_GATE_OVRD_ARC_CLK_OVR_ON, ON)); + + /* Enable the ARC. */ + reg::ReadWrite(MC + MC_IRAM_REG_CTRL, MC_REG_BITS_ENUM(IRAM_REG_CTRL_IRAM_CFG_WRITE_ACCESS, ENABLED)); + + /* Set IRAM BOM/TOP to open up access to all mmio. */ + reg::Write(MC + MC_IRAM_BOM, 0x40000000); + reg::Write(MC + MC_IRAM_TOM, 0x80000000); + + /* Read to ensure our configuration takes. */ + reg::Read(MC + MC_IRAM_REG_CTRL); + } + + void InitializeClock() { + /* Set SPARE_REG0 clock divisor 2. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_SPARE_REG0, CLK_RST_REG_BITS_ENUM(SPARE_REG0_CLK_M_DIVISOR, CLK_M_DIVISOR2)); + + /* Set system counter frequency. */ + reg::Write(SYSCTR0 + SYSCTR0_CNTFID0, 19'200'000); + + /* Restore TIMERUS config to 19.2 MHz. */ + reg::Write(TIMER + TIMERUS_USEC_CFG, TIMER_REG_BITS_VALUE(USEC_CFG_USEC_DIVIDEND, 5 - 1), + TIMER_REG_BITS_VALUE(USEC_CFG_USEC_DIVISOR, 96 - 1)); + + /* Enable the crystal oscillator, and copy the drive strength from pmc. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_OSC_CTRL, CLK_RST_REG_BITS_ENUM (OSC_CTRL_OSC_FREQ, OSC38P4), + CLK_RST_REG_BITS_ENUM (OSC_CTRL_XOE, ENABLE), + CLK_RST_REG_BITS_VALUE(OSC_CTRL_XOFS, 7)); + + /* Set the crystal oscillator value in PMC. */ + reg::ReadWrite(PMC + APBDEV_PMC_OSC_EDPD_OVER, PMC_REG_BITS_VALUE(OSC_EDPD_OVER_XOFS, 7)); + + /* Configure the crystal oscillator to use PMC value on warmboot. */ + reg::ReadWrite(PMC + APBDEV_PMC_OSC_EDPD_OVER, PMC_REG_BITS_ENUM(OSC_EDPD_OVER_OSC_CTRL_SELECT, PMC)); + + /* Set HOLD_CKE_LOW_EN. */ + reg::ReadWrite(PMC + APBDEV_PMC_CNTRL2, PMC_REG_BITS_ENUM(CNTRL2_HOLD_CKE_LOW_EN, ENABLE)); + + /* Set CFG2TMC_RAM_SVOP_PDP to 2. */ + /* NOTE: Nintendo acidentally writes this to the PMC instead of the APB due to a bug. */ + reg::ReadWrite(APB + APB_MISC_GP_ASDBGREG, APB_MISC_REG_BITS_VALUE(GP_ASDBGREG_CFG2TMC_RAM_SVOP_PDP, 2)); + + /* Set CLK_SYSTEM_RATE. */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_SYSTEM_RATE, CLK_RST_REG_BITS_VALUE(CLK_SYSTEM_RATE_HCLK_DIS, 0), + CLK_RST_REG_BITS_VALUE(CLK_SYSTEM_RATE_AHB_RATE, 1), + CLK_RST_REG_BITS_VALUE(CLK_SYSTEM_RATE_PCLK_DIS, 0), + CLK_RST_REG_BITS_VALUE(CLK_SYSTEM_RATE_APB_RATE, 0)); + + /* Configure PLLMB_BASE. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_PLLMB_BASE, CLK_RST_REG_BITS_ENUM(PLLMB_BASE_PLLMB_ENABLE, DISABLE)); + + /* Configure TSC_MULT. */ + constexpr u32 TscMultValue = 19'200'000 * 16 / 32768; + reg::ReadWrite(PMC + APBDEV_PMC_TSC_MULT, PMC_REG_BITS_VALUE(TSC_MULT_MULT_VAL, TscMultValue)); + + /* Configure SCLK_BURST_POLICY */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_SCLK_BURST_POLICY, CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SYS_STATE, RUN), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_COP_AUTO_SWAKEUP_FROM_FIQ, NOP), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_CPU_AUTO_SWAKEUP_FROM_FIQ, NOP), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_COP_AUTO_SWAKEUP_FROM_IRQ, NOP), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_CPU_AUTO_SWAKEUP_FROM_IRQ, NOP), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_FIQ_SOURCE, PLLP_OUT2), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_IRQ_SOURCE, PLLP_OUT2), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_RUN_SOURCE, PLLP_OUT2), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_IDLE_SOURCE, PLLP_OUT2)); + + /* Configure SUPER_SCLK_DIVIDER */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_SUPER_SCLK_DIVIDER, CLK_RST_REG_BITS_ENUM (SUPER_SCLK_DIVIDER_SUPER_SDIV_ENB, ENABLE), + CLK_RST_REG_BITS_ENUM (SUPER_SCLK_DIVIDER_SUPER_SDIV_DIS_FROM_COP_FIQ, NOP), + CLK_RST_REG_BITS_ENUM (SUPER_SCLK_DIVIDER_SUPER_SDIV_DIS_FROM_CPU_FIQ, NOP), + CLK_RST_REG_BITS_ENUM (SUPER_SCLK_DIVIDER_SUPER_SDIV_DIS_FROM_COP_IRQ, NOP), + CLK_RST_REG_BITS_ENUM (SUPER_SCLK_DIVIDER_SUPER_SDIV_DIS_FROM_CPU_IRQ, NOP), + CLK_RST_REG_BITS_VALUE(SUPER_SCLK_DIVIDER_SUPER_SDIV_DIVIDEND, 0), + CLK_RST_REG_BITS_VALUE(SUPER_SCLK_DIVIDER_SUPER_SDIV_DIVISOR, 0)); + + /* Set CLK_SYSTEM_RATE */ + reg::Write(CLKRST + CLK_RST_CONTROLLER_CLK_SYSTEM_RATE, CLK_RST_REG_BITS_VALUE(CLK_SYSTEM_RATE_HCLK_DIS, 0), + CLK_RST_REG_BITS_VALUE(CLK_SYSTEM_RATE_AHB_RATE, 0), + CLK_RST_REG_BITS_VALUE(CLK_SYSTEM_RATE_PCLK_DIS, 0), + CLK_RST_REG_BITS_VALUE(CLK_SYSTEM_RATE_APB_RATE, 2)); + } + + void InitializePinmux(fuse::HardwareType hw_type) { + /* Clear global pinmux control register */ + reg::Write(APB + APB_MISC_PP_PINMUX_GLOBAL_0, 0); + + /* Perform initial pinmux setup. */ + pinmux::SetupFirst(hw_type); + + /* Setup important pinmux devices. */ + pinmux::SetupI2c1(); + pinmux::SetupI2c5(); + pinmux::SetupUartA(); + pinmux::SetupVolumeButton(); + pinmux::SetupHomeButton(); + } + + } + + void SecureInitialize(bool enable_log) { + /* Get SoC type/hardware type. */ + const auto soc_type = fuse::GetSocType(); + const auto hw_type = fuse::GetHardwareType(); + + /* If Erista, perform bootrom logic (to compensate for RCM exploit) and MBIST workaround. */ + if (soc_type == fuse::SocType_Erista) { + /* Potentially perform bootrom compensation. */ + { + u32 sbk[4]; + if (fuse::GetSecureBootKey(sbk)) { + DoRcmWorkaround(sbk, sizeof(sbk)); + } + } + DoMbistWorkaround(); + } + + /* Initialize security engine clock. */ + clkrst::EnableSeClock(); + + /* Set fuse visibility. */ + clkrst::SetFuseVisibility(true); + + /* Disable fuse programming. */ + fuse::Lockout(); + + /* Initialize the security engine. */ + se::Initialize(); + + /* Enable the arc. */ + EnableArc(); + + /* Setup initial clocks. */ + InitializeClock(); + + /* Setup initial pinmux. */ + InitializePinmux(hw_type); + + /* Initialize logging. */ + if (enable_log) { + clkrst::EnableUartAClock(); + } + + /* Enable various clocks. */ + clkrst::EnableCldvfsClock(); + clkrst::EnableI2c1Clock(); + clkrst::EnableI2c5Clock(); + clkrst::EnableTzramClock(); + + /* Ensure avp cache is enabled, since we'll be using it. */ + clkrst::EnableCache2Clock(); + clkrst::EnableCram2Clock(); + + /* Initialize I2C5. */ + i2c::Initialize(i2c::Port_5); + + /* Configure pmic system setting. */ + pmic::SetSystemSetting(soc_type); + + /* Enable VDD core */ + pmic::EnableVddCore(soc_type); + + /* On hoag, enable Ldo8 */ + if (hw_type == fuse::HardwareType_Hoag) { + pmic::EnableLdo8(); + } + + /* Initialize I2C1. */ + i2c::Initialize(i2c::Port_1); + + /* Configure SCLK_BURST_POLICY. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_SCLK_BURST_POLICY, CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_FIQ_SOURCE, PLLP_OUT0), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_IRQ_SOURCE, PLLP_OUT0), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_RUN_SOURCE, PLLP_OUT0), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_IDLE_SOURCE, PLLP_OUT0)); + + /* Do mariko-only TZRAM configuration. */ + if (soc_type == fuse::SocType_Mariko) { + reg::ReadWrite(PMC + APBDEV_PMC_TZRAM_PWR_CNTRL, PMC_REG_BITS_VALUE(TZRAM_PWR_CNTRL_TZRAM_SD, 0)); + + reg::Write(PMC + APBDEV_PMC_TZRAM_NON_SEC_DISABLE, PMC_REG_BITS_ENUM(TZRAM_NON_SEC_DISABLE_SD_WRITE, ON), + PMC_REG_BITS_ENUM(TZRAM_NON_SEC_DISABLE_SD_READ, ON)); + + reg::Write(PMC + APBDEV_PMC_TZRAM_SEC_DISABLE, PMC_REG_BITS_ENUM(TZRAM_SEC_DISABLE_SD_WRITE, ON), + PMC_REG_BITS_ENUM(TZRAM_SEC_DISABLE_SD_READ, ON)); + } + + /* Hold certain devices in reset. */ + reg::ReadWrite(CLKRST + CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_REG_BITS_ENUM(RST_DEVICES_L_SWR_USBD_RST, ENABLE)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/source/sein/fusee_secure_initialize.hpp b/Source/Atmosphere-MTC-Unlock/fusee/program/source/sein/fusee_secure_initialize.hpp new file mode 100644 index 00000000..beadd8bf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/source/sein/fusee_secure_initialize.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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#pragma once + +namespace ams::nxboot { + + void SecureInitialize(bool enable_log); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/update_mtc_tables.py b/Source/Atmosphere-MTC-Unlock/fusee/program/update_mtc_tables.py new file mode 100644 index 00000000..a6f73b07 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/update_mtc_tables.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +import sys, lz4, os +from struct import unpack as up + +def lz4_compress(data): + try: + import lz4.block as block + except ImportError: + block = lz4.LZ4_compress + return block.compress(data, 'high_compression', store_size=False) + +def read_file(fn): + with open(fn, 'rb') as f: + return f.read() + +def write_file(fn, data): + with open(fn, 'wb') as f: + f.write(data) + +def get_param_size(soc): + return { + 'erista' : 0x1340, + 'mariko' : 0x10CC, + }[soc] + +def main(argc, argv): + if argc != 1: + print('Usage: %s' % argv[0]) + return 1 + params = { + 'erista' : {}, + 'mariko' : {}, + } + compressed_params = { + 'erista' : {}, + 'mariko' : {}, + } + for board in os.listdir('mtc_tables/bin'): + if board.startswith('T210b01Sdev'): + soc = 'mariko' + count = 3 + else: + assert board.startswith('T210Sdev') + soc = 'erista' + count = 10 + assert os.listdir('mtc_tables/bin/%s' % board) == ['%d.bin' % i for i in xrange(count)] + params[soc][board] = [] + compressed_params[soc][board] = [] + for i in xrange(count): + uncompressed = read_file(os.path.join('mtc_tables/bin/%s' % board, '%d.bin' % i)) + assert len(uncompressed) == get_param_size(soc) + compressed = lz4_compress(uncompressed) + params[soc][board].append(uncompressed) + compressed_params[soc][board].append(compressed) + try: + os.makedirs('mtc_tables/lz/%s' % board) + except: + pass + write_file(os.path.join('mtc_tables/lz/%s' % board, '%d.lz4' % i), compressed) + try: + os.makedirs('mtc_tables/combined/%s' % board) + except: + pass + data_1600 = params[soc][board][-1] + data_800 = params[soc][board][-4] if soc == 'erista' else '' + data_204 = params[soc][board][0] if soc == 'mariko' else params[soc][board][3] + assert up('<I', data_1600[0x40:0x44])[0] == 1600000 + assert up('<I', data_204[0x40:0x44])[0] == 204000 + if soc == 'erista': + assert up('<I', data_800[0x40:0x44])[0] == 800000 + if soc == 'mariko': + data = lz4_compress(data_204 + data_1600) + else: + data = data_204 + data_800 + data_1600 + write_file(os.path.join('mtc_tables/combined/%s' % board, 'table.bin'), data) + for soc in ('erista', 'mariko'): + with open('source/mtc/fusee_mtc_tables_%s.inc' % soc, 'w') as f: + f.write('%s\n' % "/*") + f.write('%s\n' % " * Copyright (c) Atmosph\xc3\xa8re-NX") + f.write('%s\n' % " *") + f.write('%s\n' % " * This program is free software; you can redistribute it and/or modify it") + f.write('%s\n' % " * under the terms and conditions of the GNU General Public License,") + f.write('%s\n' % " * version 2, as published by the Free Software Foundation.") + f.write('%s\n' % " *") + f.write('%s\n' % " * This program is distributed in the hope it will be useful, but WITHOUT") + f.write('%s\n' % " * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or") + f.write('%s\n' % " * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for") + f.write('%s\n' % " * more details.") + f.write('%s\n' % " *") + f.write('%s\n' % " * You should have received a copy of the GNU General Public License") + f.write('%s\n' % " * along with this program. If not, see <http://www.gnu.org/licenses/>.") + f.write('%s\n' % " */") + f.write('\n') + for board in compressed_params[soc].keys(): + f.write('%s\n' % ('constexpr const u8 %s[] = {' % (board))) + f.write('%s\n' % (' #embed "../../mtc_tables/combined/%s/table.bin"' % (board))) + f.write('};\n\n') + return 0 + +if __name__ == '__main__': + sys.exit(main(len(sys.argv), sys.argv)) diff --git a/Source/Atmosphere-MTC-Unlock/fusee/program/update_sdram_params.py b/Source/Atmosphere-MTC-Unlock/fusee/program/update_sdram_params.py new file mode 100644 index 00000000..150ee866 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/fusee/program/update_sdram_params.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python +import sys, lz4, os +from struct import unpack as up + +def lz4_compress(data): + try: + import lz4.block as block + except ImportError: + block = lz4.LZ4_compress + return block.compress(data, 'high_compression', store_size=False) + +def read_file(fn): + with open(fn, 'rb') as f: + return f.read() + +def write_file(fn, data): + with open(fn, 'wb') as f: + f.write(data) + +def get_param_size(soc): + return { + 'erista' : 0x768, + 'mariko' : 0x838, + }[soc] + +def main(argc, argv): + if argc != 1: + print('Usage: %s' % argv[0]) + return 1 + params = { + 'erista' : {}, + 'mariko' : {}, + } + compressed_params = { + 'erista' : {}, + 'mariko' : {}, + } + for fn in os.listdir('sdram_params/bin'): + assert fn.startswith('sdram_params_') and fn.endswith('.bin') + (_sdram, _params, soc, _id_bin) = tuple(fn.split('_')) + param_id = int(_id_bin[:-len('.bin')]) + assert soc in params.keys() + uncompressed = read_file(os.path.join('sdram_params/bin', fn)) + assert len(uncompressed) == get_param_size(soc) + params[soc][param_id] = uncompressed + for soc in ('erista', 'mariko'): + empty_params = '\x00' * get_param_size(soc) + for param_id in xrange(0, max(params[soc].keys()) + 4, 2): + param_id_l = param_id + param_id_h = param_id + 1 + if param_id_l in params[soc] or param_id_h in params[soc]: + print soc, param_id_l, param_id_h + param_l = params[soc][param_id_l] if param_id_l in params[soc] else empty_params + param_h = params[soc][param_id_h] if param_id_h in params[soc] else empty_params + compressed = lz4_compress(param_l + param_h) + compressed_params[soc][(param_id_l, param_id_h)] = compressed + write_file(os.path.join('sdram_params/lz', 'sdram_params_%s_%d_%d.lz4' % (soc, param_id_l, param_id_h)), compressed) + with open('source/sdram/fusee_sdram_params.inc', 'w') as f: + f.write('%s\n' % "/*") + f.write('%s\n' % " * Copyright (c) Atmosph\xc3\xa8re-NX") + f.write('%s\n' % " *") + f.write('%s\n' % " * This program is free software; you can redistribute it and/or modify it") + f.write('%s\n' % " * under the terms and conditions of the GNU General Public License,") + f.write('%s\n' % " * version 2, as published by the Free Software Foundation.") + f.write('%s\n' % " *") + f.write('%s\n' % " * This program is distributed in the hope it will be useful, but WITHOUT") + f.write('%s\n' % " * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or") + f.write('%s\n' % " * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for") + f.write('%s\n' % " * more details.") + f.write('%s\n' % " *") + f.write('%s\n' % " * You should have received a copy of the GNU General Public License") + f.write('%s\n' % " * along with this program. If not, see <http://www.gnu.org/licenses/>.") + f.write('%s\n' % " */") + f.write('\n') + for soc in ('Erista', 'Mariko'): + for (param_id_l, param_id_h) in sorted(compressed_params[soc.lower()].keys(), key=lambda (l, h): l): + compressed = compressed_params[soc.lower()][(param_id_l, param_id_h)] + f.write('%s\n' % ('constexpr inline const u8 SdramParams%s%d_%d[0x%03X] = {' % (soc, param_id_l, param_id_h, len(compressed)))) + f.write('%s\n' % (' #embed "../../sdram_params/lz/sdram_params_%s_%d_%d.lz4"' % (soc.lower(), param_id_l, param_id_h))) + f.write('};\n\n') + f.write('%s\n' % ('constexpr inline const u8 * const SdramParams%s%d = SdramParams%s%d_%d;' % (soc, param_id_l, soc, param_id_l, param_id_h))) + f.write('%s\n' % ('constexpr inline const size_t SdramParamsSize%s%d = sizeof(SdramParams%s%d_%d);' % (soc, param_id_l, soc, param_id_l, param_id_h))) + f.write('\n') + f.write('%s\n' % ('constexpr inline const u8 * const SdramParams%s%d = SdramParams%s%d_%d;' % (soc, param_id_h, soc, param_id_l, param_id_h))) + f.write('%s\n' % ('constexpr inline const size_t SdramParamsSize%s%d = sizeof(SdramParams%s%d_%d);' % (soc, param_id_h, soc, param_id_l, param_id_h))) + f.write('\n') + return 0 + +if __name__ == '__main__': + sys.exit(main(len(sys.argv), sys.argv)) diff --git a/Source/Atmosphere-MTC-Unlock/img/atmosphere.sketch b/Source/Atmosphere-MTC-Unlock/img/atmosphere.sketch new file mode 100644 index 00000000..fc8089af Binary files /dev/null and b/Source/Atmosphere-MTC-Unlock/img/atmosphere.sketch differ diff --git a/Source/Atmosphere-MTC-Unlock/img/banner.png b/Source/Atmosphere-MTC-Unlock/img/banner.png new file mode 100644 index 00000000..699c997c Binary files /dev/null and b/Source/Atmosphere-MTC-Unlock/img/banner.png differ diff --git a/Source/Atmosphere-MTC-Unlock/img/banner.svg b/Source/Atmosphere-MTC-Unlock/img/banner.svg new file mode 100644 index 00000000..b93036dc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/img/banner.svg @@ -0,0 +1,21 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="888" height="182" viewBox="0 0 888 182" style="background:#37394c"> + <defs> + <linearGradient id="a" x1="39.803%" x2="39.803%" y1="59.878%" y2="121.158%"> + <stop stop-color="#FFFFFF" offset="0%"/> + <stop stop-color="#9FC1DB" offset="100%"/> + </linearGradient> + <linearGradient id="b" x1="50%" x2="25.242%" y1="0%" y2="111.09%"> + <stop stop-color="#90B9D9" offset="0%"/> + <stop stop-color="#554BBA" offset="100%"/> + </linearGradient> + </defs> + <g fill="none" fill-rule="evenodd" transform="translate(298 8)"> + <text fill="#FFFFFF" font-family="Montserrat-Light, Montserrat" font-size="42" font-weight="300" letter-spacing="4.5"> + <tspan x=".422" y="137">atmosphèr</tspan> <tspan x="266.882" y="137">e</tspan> + </text> + <g transform="translate(105.688 28.125)"> + <path fill="url(#a)" d="M66.0430676,63.5217824 C61.640625,55.912573 51.6395147,45.9010989 40.3125,39.3366975 C29.8643779,33.2816451 18.75,32.7997325 10.5346026,32.0300063 L32.9407656,3.62403252 C36.1396414,-0.431422363 42.0332805,-1.13590717 46.104577,2.05052167 C46.6922079,2.51043508 47.2225245,3.03868818 47.6842344,3.62403252 L79.2870412,43.6892817 C80.6225515,45.3824084 80.6225515,47.7656947 79.2870412,49.4588214 L69.5981824,61.7421146 C68.7153105,62.859323 67.4009621,63.4842566 66.0430676,63.5217824 Z"/> + <path fill="url(#b)" d="M55.3274635,59.7986478 C50.5476221,58.4831234 45.5124609,57.7802772 40.3125,57.7802772 C34.254043,57.7802772 28.4192935,58.7343558 22.9514143,60.4999072 C20.4993035,61.2916817 18.1209755,62.2466549 15.8293423,63.3519647 C14.0617017,63.7956084 12.1588174,63.1772357 11.000017,61.7081375 L1.33795884,49.4588214 C0.00244845846,47.7656947 0.00244845846,45.3824084 1.33795884,43.6892817 L10.8472428,31.633649 C10.9813889,31.6328283 11.1156463,31.6324173 11.25,31.6324173 C34.9481937,31.6324173 55.6482774,44.4189052 66.7765263,63.4437348 C66.1753801,63.5558102 65.5509135,63.5512838 64.9390071,63.4213566 C61.8802947,61.935349 58.6661639,60.7175365 55.3274635,59.7986478 Z"/> + </g> + </g> +</svg> diff --git a/Source/Atmosphere-MTC-Unlock/img/banner_inline.png b/Source/Atmosphere-MTC-Unlock/img/banner_inline.png new file mode 100644 index 00000000..e5d79f29 Binary files /dev/null and b/Source/Atmosphere-MTC-Unlock/img/banner_inline.png differ diff --git a/Source/Atmosphere-MTC-Unlock/img/banner_inline.svg b/Source/Atmosphere-MTC-Unlock/img/banner_inline.svg new file mode 100644 index 00000000..da731a88 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/img/banner_inline.svg @@ -0,0 +1,21 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="540" height="140" viewBox="0 0 540 140" style="background:#37394c"> + <defs> + <linearGradient id="a" x1="39.803%" x2="39.803%" y1="59.878%" y2="121.158%"> + <stop stop-color="#FFFFFF" offset="0%"/> + <stop stop-color="#9FC1DB" offset="100%"/> + </linearGradient> + <linearGradient id="b" x1="50%" x2="25.242%" y1="0%" y2="111.09%"> + <stop stop-color="#90B9D9" offset="0%"/> + <stop stop-color="#554BBA" offset="100%"/> + </linearGradient> + </defs> + <g fill="none" fill-rule="evenodd" transform="translate(68 20)"> + <text fill="#FFFFFF" font-family="Montserrat-Light, Montserrat" font-size="42" font-weight="300" letter-spacing="4.5"> + <tspan x="113.422" y="63">atmosphèr</tspan> <tspan x="379.882" y="63">e</tspan> + </text> + <g transform="translate(16.406 23.438)"> + <path fill="url(#a)" d="M55.0358896,52.9348187 C51.3671875,46.5938108 43.0329289,38.2509158 33.59375,32.7805812 C24.8869816,27.7347042 15.625,27.3331104 8.77883554,26.6916719 L27.450638,3.0200271 C30.1163678,-0.359518636 35.0277338,-0.946589306 38.4204809,1.70876806 C38.9101732,2.09202924 39.3521037,2.53224015 39.736862,3.0200271 L66.0725343,36.4077347 C67.1854596,37.8186736 67.1854596,39.8047456 66.0725343,41.2156845 L57.9984854,51.4517621 C57.2627587,52.3827692 56.1674684,52.9035472 55.0358896,52.9348187 Z"/> + <path fill="url(#b)" d="M46.1062195,49.8322065 C42.1230184,48.7359361 37.9270508,48.150231 33.59375,48.150231 C28.5450359,48.150231 23.6827446,48.9452965 19.1261786,50.4165893 C17.0827529,51.0764015 15.1008129,51.8722124 13.1911186,52.7933039 C11.7180848,53.163007 10.1323478,52.6476964 9.16668086,51.4234479 L1.1149657,41.2156845 C0.00204038205,39.8047456 0.00204038205,37.8186736 1.1149657,36.4077347 L9.03936898,26.3613741 C9.15115745,26.3606902 9.26303857,26.3603478 9.375,26.3603478 C29.1234948,26.3603478 46.3735645,37.0157543 55.6471053,52.869779 C55.1461501,52.9631752 54.6257613,52.9594031 54.1158393,52.8511305 C51.5669123,51.6127908 48.8884699,50.5979471 46.1062195,49.8322065 Z"/> + </g> + </g> +</svg> diff --git a/Source/Atmosphere-MTC-Unlock/img/banner_light.png b/Source/Atmosphere-MTC-Unlock/img/banner_light.png new file mode 100644 index 00000000..da8227cf Binary files /dev/null and b/Source/Atmosphere-MTC-Unlock/img/banner_light.png differ diff --git a/Source/Atmosphere-MTC-Unlock/img/banner_light.svg b/Source/Atmosphere-MTC-Unlock/img/banner_light.svg new file mode 100644 index 00000000..2fa9b45c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/img/banner_light.svg @@ -0,0 +1,17 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="888" height="182" viewBox="0 0 888 182" style="background:#fff"> + <defs> + <linearGradient id="a" x1="50%" x2="25.242%" y1="0%" y2="111.09%"> + <stop stop-color="#90C4D9" offset="0%"/> + <stop stop-color="#A298FF" offset="100%"/> + </linearGradient> + </defs> + <g fill="none" fill-rule="evenodd" transform="translate(298 10)"> + <text fill="#4E4E67" font-family="Montserrat-Light, Montserrat" font-size="42" font-weight="300" letter-spacing="4.5"> + <tspan x=".422" y="135">atmosphèr</tspan> <tspan x="266.882" y="135">e</tspan> + </text> + <g transform="translate(105.688 28.125)"> + <path fill="#4E4E67" d="M66.0430676,63.5217824 C61.640625,55.912573 51.6395147,45.9010989 40.3125,39.3366975 C29.8643779,33.2816451 18.75,32.7997325 10.5346026,32.0300063 L32.9407656,3.62403252 C36.1396414,-0.431422363 42.0332805,-1.13590717 46.104577,2.05052167 C46.6922079,2.51043508 47.2225245,3.03868818 47.6842344,3.62403252 L79.2870412,43.6892817 C80.6225515,45.3824084 80.6225515,47.7656947 79.2870412,49.4588214 L69.5981824,61.7421146 C68.7153105,62.859323 67.4009621,63.4842566 66.0430676,63.5217824 Z"/> + <path fill="url(#a)" d="M55.3274635,59.7986478 C50.5476221,58.4831234 45.5124609,57.7802772 40.3125,57.7802772 C34.254043,57.7802772 28.4192935,58.7343558 22.9514143,60.4999072 C20.4993035,61.2916817 18.1209755,62.2466549 15.8293423,63.3519647 C14.0617017,63.7956084 12.1588174,63.1772357 11.000017,61.7081375 L1.33795884,49.4588214 C0.00244845846,47.7656947 0.00244845846,45.3824084 1.33795884,43.6892817 L10.8472428,31.633649 C10.9813889,31.6328283 11.1156463,31.6324173 11.25,31.6324173 C34.9481937,31.6324173 55.6482774,44.4189052 66.7765263,63.4437348 C66.1753801,63.5558102 65.5509135,63.5512838 64.9390071,63.4213566 C61.8802947,61.935349 58.6661639,60.7175365 55.3274635,59.7986478 Z"/> + </g> + </g> +</svg> diff --git a/Source/Atmosphere-MTC-Unlock/img/banner_twitter.png b/Source/Atmosphere-MTC-Unlock/img/banner_twitter.png new file mode 100644 index 00000000..e4a029d7 Binary files /dev/null and b/Source/Atmosphere-MTC-Unlock/img/banner_twitter.png differ diff --git a/Source/Atmosphere-MTC-Unlock/img/banner_twitter.svg b/Source/Atmosphere-MTC-Unlock/img/banner_twitter.svg new file mode 100644 index 00000000..c0e61b46 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/img/banner_twitter.svg @@ -0,0 +1,38 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="1500" height="500" viewBox="0 0 1500 500" style="background:#2a2e45"> + <defs> + <linearGradient id="a" x1="50.142%" x2="50.142%" y1="100%" y2="0%"> + <stop stop-color="#3952A8" offset="0%"/> + <stop stop-color="#36378C" stop-opacity=".529" offset="51.507%"/> + <stop stop-color="#151A32" stop-opacity=".097" offset="100%"/> + </linearGradient> + <linearGradient id="b" x1="39.803%" x2="39.803%" y1="59.878%" y2="121.158%"> + <stop stop-color="#FFFFFF" offset="0%"/> + <stop stop-color="#9FC1DB" offset="100%"/> + </linearGradient> + <linearGradient id="c" x1="50%" x2="25.242%" y1="0%" y2="111.09%"> + <stop stop-color="#90B9D9" offset="0%"/> + <stop stop-color="#554BBA" offset="100%"/> + </linearGradient> + </defs> + <g fill="none" fill-rule="evenodd"> + <g fill="#8089CA" transform="translate(36 13)" opacity=".504"> + <path d="M1118.35504 29.2853938C1123.45168 29.9569767 1126 30.6951788 1126 31.5 1126 32.3048214 1123.45168 33.0430237 1118.35504 33.7146068L1118.35504 33.7146091C1116.98168 33.8955757 1115.91079 34.9933097 1115.76388 36.3707235 1115.07918 42.7902412 1114.32455 46 1113.5 46 1112.67545 46 1111.92082 42.790238 1111.23612 36.370714L1111.23611 36.370715C1111.0892 34.9933041 1110.01831 33.8955725 1108.64496 33.7146062 1103.54832 33.0430233 1101 32.3048212 1101 31.5 1101 30.6951786 1103.54832 29.9569763 1108.64496 29.2853932L1108.64496 29.2853909C1110.01832 29.1044243 1111.08921 28.0066903 1111.23612 26.6292765 1111.92082 20.2097588 1112.67545 17 1113.5 17 1114.32455 17 1115.07918 20.209762 1115.76388 26.629286L1115.76389 26.629285C1115.9108 28.0066959 1116.98169 29.1044275 1118.35504 29.2853938zM197.35504 135.285394C202.45168 135.956977 205 136.695179 205 137.5 205 138.304821 202.451678 139.043024 197.355035 139.714607L197.355036 139.714609C195.981681 139.895576 194.910793 140.99331 194.763879 142.370724 194.079178 148.790241 193.324552 152 192.5 152 191.675448 152 190.920821 148.790238 190.23612 142.370714L190.236111 142.370715C190.089198 140.993304 189.018312 139.895572 187.64496 139.714606 182.54832 139.043023 180 138.304821 180 137.5 180 136.695179 182.548322 135.956976 187.644965 135.285393L187.644964 135.285391C189.018319 135.104424 190.089207 134.00669 190.236121 132.629276 190.920822 126.209759 191.675448 123 192.5 123 193.324552 123 194.079179 126.209762 194.76388 132.629286L194.763889 132.629285C194.910802 134.006696 195.981688 135.104428 197.35504 135.285394zM88.1319455 317.326734C92.0439818 317.987238 94 318.711659 94 319.5 94 320.288339 92.0439884 321.01276 88.1319651 321.673262L88.1319626 321.673248C86.8725782 321.885881 85.8858984 322.872561 85.6732656 324.131946 85.0127624 328.043982 84.2883406 330 83.5 330 82.7116607 330 81.98724 328.043988 81.3267377 324.131965L81.3267523 324.131963C81.114119 322.872578 80.1274389 321.885898 78.8680545 321.673266 74.9560182 321.012762 73 320.288341 73 319.5 73 318.711661 74.9560116 317.98724 78.8680349 317.326738L78.8680374 317.326752C80.1274218 317.114119 81.1141016 316.127439 81.3267344 314.868054 81.9872376 310.956018 82.7116594 309 83.5 309 84.2883393 309 85.01276 310.956012 85.6732623 314.868035L85.6732477 314.868037C85.885881 316.127422 86.8725611 317.114102 88.1319455 317.326734zM502.131946 25.3267344C506.043982 25.9872376 508 26.7116594 508 27.5 508 28.2883393 506.043988 29.01276 502.131965 29.6732623L502.131963 29.6732477C500.872578 29.885881 499.885898 30.8725611 499.673266 32.1319455 499.012762 36.0439818 498.288341 38 497.5 38 496.711661 38 495.98724 36.0439884 495.326738 32.1319651L495.326752 32.1319626C495.114119 30.8725782 494.127439 29.8858984 492.868054 29.6732656 488.956018 29.0127624 487 28.2883406 487 27.5 487 26.7116607 488.956012 25.98724 492.868035 25.3267377L492.868037 25.3267523C494.127422 25.114119 495.114102 24.1274389 495.326734 22.8680545 495.987238 18.9560182 496.711659 17 497.5 17 498.288339 17 499.01276 18.9560116 499.673262 22.8680349L499.673248 22.8680374C499.885881 24.1274218 500.872561 25.1141016 502.131946 25.3267344zM1279.13195 150.326734C1283.04398 150.987238 1285 151.711659 1285 152.5 1285 153.288339 1283.04399 154.01276 1279.13197 154.673262L1279.13196 154.673248C1277.87258 154.885881 1276.8859 155.872561 1276.67327 157.131946 1276.01276 161.043982 1275.28834 163 1274.5 163 1273.71166 163 1272.98724 161.043988 1272.32674 157.131965L1272.32675 157.131963C1272.11412 155.872578 1271.12744 154.885898 1269.86805 154.673266 1265.95602 154.012762 1264 153.288341 1264 152.5 1264 151.711661 1265.95601 150.98724 1269.86803 150.326738L1269.86804 150.326752C1271.12742 150.114119 1272.1141 149.127439 1272.32673 147.868054 1272.98724 143.956018 1273.71166 142 1274.5 142 1275.28834 142 1276.01276 143.956012 1276.67326 147.868035L1276.67325 147.868037C1276.88588 149.127422 1277.87256 150.114102 1279.13195 150.326734zM1258.13195 338.326734C1262.04398 338.987238 1264 339.711659 1264 340.5 1264 341.288339 1262.04399 342.01276 1258.13197 342.673262L1258.13196 342.673248C1256.87258 342.885881 1255.8859 343.872561 1255.67327 345.131946 1255.01276 349.043982 1254.28834 351 1253.5 351 1252.71166 351 1251.98724 349.043988 1251.32674 345.131965L1251.32675 345.131963C1251.11412 343.872578 1250.12744 342.885898 1248.86805 342.673266 1244.95602 342.012762 1243 341.288341 1243 340.5 1243 339.711661 1244.95601 338.98724 1248.86803 338.326738L1248.86804 338.326752C1250.12742 338.114119 1251.1141 337.127439 1251.32673 335.868054 1251.98724 331.956018 1252.71166 330 1253.5 330 1254.28834 330 1255.01276 331.956012 1255.67326 335.868035L1255.67325 335.868037C1255.88588 337.127422 1256.87256 338.114102 1258.13195 338.326734z"/> + <path d="M423.75138,218.258283 C426.931003,218.793842 428.520815,219.381353 428.520815,220.020815 C428.520815,220.660277 426.931006,221.247787 423.751389,221.783346 L423.751388,221.783339 C422.743132,221.953165 421.953173,222.743125 421.783347,223.75138 C421.247789,226.931003 420.660278,228.520815 420.020815,228.520815 C419.381353,228.520815 418.793843,226.931006 418.258285,223.751389 L418.258291,223.751388 C418.088466,222.743132 417.298506,221.953173 416.290251,221.783347 C413.110627,221.247789 411.520815,220.660278 411.520815,220.020815 C411.520815,219.381353 413.110624,218.793843 416.290242,218.258285 L416.290243,218.258291 C417.298498,218.088466 418.088458,217.298506 418.258283,216.290251 C418.793842,213.110627 419.381353,211.520815 420.020815,211.520815 C420.660277,211.520815 421.247787,213.110624 421.783346,216.290242 L421.783339,216.290243 C421.953165,217.298498 422.743125,218.088458 423.75138,218.258283 Z" transform="rotate(45 420.02 220.02)"/> + <path d="M15.7513799,50.2582831 C18.9310035,50.793842 20.5208153,51.3813527 20.5208153,52.0208153 C20.5208153,52.6602772 18.9310065,53.2477874 15.7513888,53.7833459 L15.7513877,53.7833393 C14.7431324,53.9531646 13.9531726,54.7431246 13.7833474,55.7513799 C13.2477885,58.9310035 12.6602778,60.5208153 12.0208153,60.5208153 C11.3813533,60.5208153 10.7938431,58.9310065 10.2582846,55.7513888 L10.2582913,55.7513877 C10.0884659,54.7431324 9.29850601,53.9531726 8.29025065,53.7833474 C5.11062707,53.2477885 3.52081528,52.6602778 3.52081528,52.0208153 C3.52081528,51.3813533 5.1106241,50.7938431 8.29024173,50.2582846 L8.29024285,50.2582913 C9.29849818,50.0884659 10.088458,49.298506 10.2582831,48.2902507 C10.793842,45.1106271 11.3813527,43.5208153 12.0208153,43.5208153 C12.6602772,43.5208153 13.2477874,45.1106241 13.7833459,48.2902417 L13.7833393,48.2902429 C13.9531646,49.2984982 14.7431246,50.088458 15.7513799,50.2582831 Z" transform="rotate(45 12.02 52.02)"/> + <path d="M175.230565 6.73746786C178.410188 7.27302675 180 7.86053747 180 8.5 180 9.13946194 178.410191 9.72697215 175.230574 10.2625306L175.230572 10.262524C174.222317 10.4323493 173.432357 11.2223093 173.262532 12.2305646 172.726973 15.4101882 172.139463 17 171.5 17 170.860538 17 170.273028 15.4101912 169.737469 12.2305735L169.737476 12.2305724C169.567651 11.2223171 168.777691 10.4323573 167.769435 10.2625321 164.589812 9.72697325 163 9.13946253 163 8.5 163 7.86053806 164.589809 7.27302785 167.769426 6.73746936L167.769428 6.73747601C168.777683 6.56765066 169.567643 5.77769073 169.737468 4.76943537 170.273027 1.58981179 170.860537 0 171.5 0 172.139462 0 172.726972 1.58980882 173.262531 4.76942645L173.262524 4.76942757C173.432349 5.7776829 174.222309 6.56764268 175.230565 6.73746786zM1418.23056 41.7374679C1421.41019 42.2730268 1423 42.8605375 1423 43.5 1423 44.1394619 1421.41019 44.7269722 1418.23057 45.2625306L1418.23057 45.262524C1417.22232 45.4323493 1416.43236 46.2223093 1416.26253 47.2305646 1415.72697 50.4101882 1415.13946 52 1414.5 52 1413.86054 52 1413.27303 50.4101912 1412.73747 47.2305735L1412.73748 47.2305724C1412.56765 46.2223171 1411.77769 45.4323573 1410.76944 45.2625321 1407.58981 44.7269732 1406 44.1394625 1406 43.5 1406 42.8605381 1407.58981 42.2730278 1410.76943 41.7374694L1410.76943 41.737476C1411.77768 41.5676507 1412.56764 40.7776907 1412.73747 39.7694354 1413.27303 36.5898118 1413.86054 35 1414.5 35 1415.13946 35 1415.72697 36.5898088 1416.26253 39.7694265L1416.26252 39.7694276C1416.43235 40.7776829 1417.22231 41.5676427 1418.23056 41.7374679z"/> + <path d="M959.75138,256.258283 C962.931003,256.793842 964.520815,257.381353 964.520815,258.020815 C964.520815,258.660277 962.931006,259.247787 959.751389,259.783346 L959.751388,259.783339 C958.743132,259.953165 957.953173,260.743125 957.783347,261.75138 C957.247789,264.931003 956.660278,266.520815 956.020815,266.520815 C955.381353,266.520815 954.793843,264.931006 954.258285,261.751389 L954.258291,261.751388 C954.088466,260.743132 953.298506,259.953173 952.290251,259.783347 C949.110627,259.247789 947.520815,258.660278 947.520815,258.020815 C947.520815,257.381353 949.110624,256.793843 952.290242,256.258285 L952.290243,256.258291 C953.298498,256.088466 954.088458,255.298506 954.258283,254.290251 C954.793842,251.110627 955.381353,249.520815 956.020815,249.520815 C956.660277,249.520815 957.247787,251.110624 957.783346,254.290242 L957.783339,254.290243 C957.953165,255.298498 958.743125,256.088458 959.75138,256.258283 Z" transform="rotate(45 956.02 258.02)"/> + <path d="M679.75138,74.2582831 C682.931003,74.793842 684.520815,75.3813527 684.520815,76.0208153 C684.520815,76.6602772 682.931006,77.2477874 679.751389,77.7833459 L679.751388,77.7833393 C678.743132,77.9531646 677.953173,78.7431246 677.783347,79.7513799 C677.247789,82.9310035 676.660278,84.5208153 676.020815,84.5208153 C675.381353,84.5208153 674.793843,82.9310065 674.258285,79.7513888 L674.258291,79.7513877 C674.088466,78.7431324 673.298506,77.9531726 672.290251,77.7833474 C669.110627,77.2477885 667.520815,76.6602778 667.520815,76.0208153 C667.520815,75.3813533 669.110624,74.7938431 672.290242,74.2582846 L672.290243,74.2582913 C673.298498,74.0884659 674.088458,73.298506 674.258283,72.2902507 C674.793842,69.1106271 675.381353,67.5208153 676.020815,67.5208153 C676.660277,67.5208153 677.247787,69.1106241 677.783346,72.2902417 L677.783339,72.2902429 C677.953165,73.2984982 678.743125,74.088458 679.75138,74.2582831 Z" transform="rotate(45 676.02 76.02)"/> + <path d="M859.75138,121.258283 C862.931003,121.793842 864.520815,122.381353 864.520815,123.020815 C864.520815,123.660277 862.931006,124.247787 859.751389,124.783346 L859.751388,124.783339 C858.743132,124.953165 857.953173,125.743125 857.783347,126.75138 C857.247789,129.931003 856.660278,131.520815 856.020815,131.520815 C855.381353,131.520815 854.793843,129.931006 854.258285,126.751389 L854.258291,126.751388 C854.088466,125.743132 853.298506,124.953173 852.290251,124.783347 C849.110627,124.247789 847.520815,123.660278 847.520815,123.020815 C847.520815,122.381353 849.110624,121.793843 852.290242,121.258285 L852.290243,121.258291 C853.298498,121.088466 854.088458,120.298506 854.258283,119.290251 C854.793842,116.110627 855.381353,114.520815 856.020815,114.520815 C856.660277,114.520815 857.247787,116.110624 857.783346,119.290242 L857.783339,119.290243 C857.953165,120.298498 858.743125,121.088458 859.75138,121.258283 Z" transform="rotate(45 856.02 123.02)"/> + </g> + <rect width="1500" height="557" x="1" y="13" fill="url(#a)"/> + <g transform="translate(575 127)"> + <text fill="#FFFFFF" font-family="Montserrat-Light, Montserrat" font-size="52" font-weight="300" letter-spacing="4.5"> + <tspan x=".082" y="206">atmosphèr</tspan> <tspan x="320.342" y="206">e</tspan> + </text> + <g transform="translate(111.5 45)"> + <path fill="url(#b)" d="M105.668908,101.634852 C98.625,89.4601167 82.6232236,73.4417583 64.5,62.938716 C47.7830047,53.2506321 30,52.479572 16.8553642,51.2480101 L52.7052249,5.79845203 C57.8234262,-0.69027578 67.2532489,-1.81745147 73.7673233,3.28083467 C74.7075326,4.01669613 75.5560392,4.86190108 76.2947751,5.79845203 L126.859266,69.9028507 C128.996082,72.6118534 128.996082,76.4251116 126.859266,79.1341143 L111.357092,98.7873833 C109.944497,100.574917 107.841539,101.574811 105.668908,101.634852 Z"/> + <path fill="url(#c)" d="M88.5239415,95.6778365 C80.8761953,93.5729974 72.8199375,92.4484436 64.5,92.4484436 C54.8064688,92.4484436 45.4708696,93.9749693 36.7222629,96.7998515 C32.7988857,98.0666908 28.9935608,99.5946478 25.3269476,101.363144 C22.4987227,102.072973 19.4541078,101.083577 17.6000273,98.73302 L2.14073415,79.1341143 C0.00391753354,76.4251116 0.00391753354,72.6118534 2.14073415,69.9028507 L17.3555884,50.6138384 C17.5702223,50.6125253 17.7850341,50.6118677 18,50.6118677 C55.91711,50.6118677 89.0372438,71.0702483 106.842442,101.509976 C105.880608,101.689296 104.881462,101.682054 103.902411,101.47417 C99.0084715,99.0965583 93.8658623,97.1480584 88.5239415,95.6778365 Z"/> + </g> + </g> + </g> +</svg> diff --git a/Source/Atmosphere-MTC-Unlock/img/lockscreen_750_1334.png b/Source/Atmosphere-MTC-Unlock/img/lockscreen_750_1334.png new file mode 100644 index 00000000..dd352ef3 Binary files /dev/null and b/Source/Atmosphere-MTC-Unlock/img/lockscreen_750_1334.png differ diff --git a/Source/Atmosphere-MTC-Unlock/img/lockscreen_750_1334.svg b/Source/Atmosphere-MTC-Unlock/img/lockscreen_750_1334.svg new file mode 100644 index 00000000..edae737d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/img/lockscreen_750_1334.svg @@ -0,0 +1,40 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="750" height="1334" viewBox="0 0 750 1334" xmlns:xlink="http://www.w3.org/1999/xlink" style="background:#272c45"> + <defs> + <linearGradient id="a" x1="50.142%" x2="50.142%" y1="100%" y2="0%"> + <stop stop-color="#3952A8" offset="0%"/> + <stop stop-color="#36378C" stop-opacity=".529" offset="51.507%"/> + <stop stop-color="#151A32" stop-opacity=".097" offset="100%"/> + </linearGradient> + <path id="b" d="M-3.41060513e-13,1191.83865 C125.657381,1183.98544 252.363266,1180 380,1180 C504.251642,1180 627.621156,1183.77684 750,1191.22198 L750,1334 L0,1334 L0,1191.83865 Z"/> + <linearGradient id="c" x1="39.803%" x2="39.803%" y1="59.878%" y2="121.158%"> + <stop stop-color="#FFFFFF" offset="0%"/> + <stop stop-color="#9FC1DB" offset="100%"/> + </linearGradient> + <linearGradient id="d" x1="50%" x2="25.242%" y1="0%" y2="111.09%"> + <stop stop-color="#90B9D9" offset="0%"/> + <stop stop-color="#554BBA" offset="100%"/> + </linearGradient> + </defs> + <g fill="none" fill-rule="evenodd"> + <g fill="#D3DBE7" transform="translate(51 30)" opacity=".595"> + <path d="M162.35504 118.285394C167.45168 118.956977 170 119.695179 170 120.5 170 121.304821 167.451678 122.043024 162.355035 122.714607L162.355036 122.714609C160.981681 122.895576 159.910793 123.99331 159.763879 125.370724 159.079178 131.790241 158.324552 135 157.5 135 156.675448 135 155.920821 131.790238 155.23612 125.370714L155.236111 125.370715C155.089198 123.993304 154.018312 122.895572 152.64496 122.714606 147.54832 122.043023 145 121.304821 145 120.5 145 119.695179 147.548322 118.956976 152.644965 118.285393L152.644964 118.285391C154.018319 118.104424 155.089207 117.00669 155.236121 115.629276 155.920822 109.209759 156.675448 106 157.5 106 158.324552 106 159.079179 109.209762 159.76388 115.629286L159.763889 115.629285C159.910802 117.006696 160.981688 118.104428 162.35504 118.285394zM63.1319455 413.326734C67.0439818 413.987238 69 414.711659 69 415.5 69 416.288339 67.0439884 417.01276 63.1319651 417.673262L63.1319626 417.673248C61.8725782 417.885881 60.8858984 418.872561 60.6732656 420.131946 60.0127624 424.043982 59.2883406 426 58.5 426 57.7116607 426 56.98724 424.043988 56.3267377 420.131965L56.3267523 420.131963C56.114119 418.872578 55.1274389 417.885898 53.8680545 417.673266 49.9560182 417.012762 48 416.288341 48 415.5 48 414.711661 49.9560116 413.98724 53.8680349 413.326738L53.8680374 413.326752C55.1274218 413.114119 56.1141016 412.127439 56.3267344 410.868054 56.9872376 406.956018 57.7116594 405 58.5 405 59.2883393 405 60.01276 406.956012 60.6732623 410.868035L60.6732477 410.868037C60.885881 412.127422 61.8725611 413.114102 63.1319455 413.326734zM451.131946 8.3267344C455.043982 8.98723756 457 9.71165943 457 10.5 457 11.2883393 455.043988 12.01276 451.131965 12.6732623L451.131963 12.6732477C449.872578 12.885881 448.885898 13.8725611 448.673266 15.1319455 448.012762 19.0439818 447.288341 21 446.5 21 445.711661 21 444.98724 19.0439884 444.326738 15.1319651L444.326752 15.1319626C444.114119 13.8725782 443.127439 12.8858984 441.868054 12.6732656 437.956018 12.0127624 436 11.2883406 436 10.5 436 9.71166074 437.956012 8.98723998 441.868035 8.32673769L441.868037 8.32675227C443.127422 8.11411902 444.114102 7.1274389 444.326734 5.86805448 444.987238 1.95601816 445.711659 0 446.5 0 447.288339 0 448.01276 1.95601165 448.673262 5.86803495L448.673248 5.86803741C448.885881 7.12742176 449.872561 8.11410155 451.131946 8.3267344z"/> + <path d="M310.75138,308.258283 C313.931003,308.793842 315.520815,309.381353 315.520815,310.020815 C315.520815,310.660277 313.931006,311.247787 310.751389,311.783346 L310.751388,311.783339 C309.743132,311.953165 308.953173,312.743125 308.783347,313.75138 C308.247789,316.931003 307.660278,318.520815 307.020815,318.520815 C306.381353,318.520815 305.793843,316.931006 305.258285,313.751389 L305.258291,313.751388 C305.088466,312.743132 304.298506,311.953173 303.290251,311.783347 C300.110627,311.247789 298.520815,310.660278 298.520815,310.020815 C298.520815,309.381353 300.110624,308.793843 303.290242,308.258285 L303.290243,308.258291 C304.298498,308.088466 305.088458,307.298506 305.258283,306.290251 C305.793842,303.110627 306.381353,301.520815 307.020815,301.520815 C307.660277,301.520815 308.247787,303.110624 308.783346,306.290242 L308.783339,306.290243 C308.953165,307.298498 309.743125,308.088458 310.75138,308.258283 Z" transform="rotate(45 307.02 310.02)"/> + <path d="M15.7513799,36.2582831 C18.9310035,36.793842 20.5208153,37.3813527 20.5208153,38.0208153 C20.5208153,38.6602772 18.9310065,39.2477874 15.7513888,39.7833459 L15.7513877,39.7833393 C14.7431324,39.9531646 13.9531726,40.7431246 13.7833474,41.7513799 C13.2477885,44.9310035 12.6602778,46.5208153 12.0208153,46.5208153 C11.3813533,46.5208153 10.7938431,44.9310065 10.2582846,41.7513888 L10.2582913,41.7513877 C10.0884659,40.7431324 9.29850601,39.9531726 8.29025065,39.7833474 C5.11062707,39.2477885 3.52081528,38.6602778 3.52081528,38.0208153 C3.52081528,37.3813533 5.1106241,36.7938431 8.29024173,36.2582846 L8.29024285,36.2582913 C9.29849818,36.0884659 10.088458,35.298506 10.2582831,34.2902507 C10.793842,31.1106271 11.3813527,29.5208153 12.0208153,29.5208153 C12.6602772,29.5208153 13.2477874,31.1106241 13.7833459,34.2902417 L13.7833393,34.2902429 C13.9531646,35.2984982 14.7431246,36.088458 15.7513799,36.2582831 Z" transform="rotate(45 12.02 38.02)"/> + <path d="M514.35504 369.285394C519.45168 369.956977 522 370.695179 522 371.5 522 372.304821 519.451678 373.043024 514.355035 373.714607L514.355036 373.714609C512.981681 373.895576 511.910793 374.99331 511.763879 376.370724 511.079178 382.790241 510.324552 386 509.5 386 508.675448 386 507.920821 382.790238 507.23612 376.370714L507.236111 376.370715C507.089198 374.993304 506.018312 373.895572 504.64496 373.714606 499.54832 373.043023 497 372.304821 497 371.5 497 370.695179 499.548322 369.956976 504.644965 369.285393L504.644964 369.285391C506.018319 369.104424 507.089207 368.00669 507.236121 366.629276 507.920822 360.209759 508.675448 357 509.5 357 510.324552 357 511.079179 360.209762 511.76388 366.629286L511.763889 366.629285C511.910802 368.006696 512.981688 369.104428 514.35504 369.285394zM638.131946 509.326734C642.043982 509.987238 644 510.711659 644 511.5 644 512.288339 642.043988 513.01276 638.131965 513.673262L638.131963 513.673248C636.872578 513.885881 635.885898 514.872561 635.673266 516.131946 635.012762 520.043982 634.288341 522 633.5 522 632.711661 522 631.98724 520.043988 631.326738 516.131965L631.326752 516.131963C631.114119 514.872578 630.127439 513.885898 628.868054 513.673266 624.956018 513.012762 623 512.288341 623 511.5 623 510.711661 624.956012 509.98724 628.868035 509.326738L628.868037 509.326752C630.127422 509.114119 631.114102 508.127439 631.326734 506.868054 631.987238 502.956018 632.711659 501 633.5 501 634.288339 501 635.01276 502.956012 635.673262 506.868035L635.673248 506.868037C635.885881 508.127422 636.872561 509.114102 638.131946 509.326734zM638.131946 143.326734C642.043982 143.987238 644 144.711659 644 145.5 644 146.288339 642.043988 147.01276 638.131965 147.673262L638.131963 147.673248C636.872578 147.885881 635.885898 148.872561 635.673266 150.131946 635.012762 154.043982 634.288341 156 633.5 156 632.711661 156 631.98724 154.043988 631.326738 150.131965L631.326752 150.131963C631.114119 148.872578 630.127439 147.885898 628.868054 147.673266 624.956018 147.012762 623 146.288341 623 145.5 623 144.711661 624.956012 143.98724 628.868035 143.326738L628.868037 143.326752C630.127422 143.114119 631.114102 142.127439 631.326734 140.868054 631.987238 136.956018 632.711659 135 633.5 135 634.288339 135 635.01276 136.956012 635.673262 140.868035L635.673248 140.868037C635.885881 142.127422 636.872561 143.114102 638.131946 143.326734z"/> + </g> + <rect width="750" height="1428" y="-228" fill="url(#a)"/> + <use fill="#1C2149" xlink:href="#b"/> + <path stroke="#1C2149" stroke-opacity=".3" stroke-width="20" d="M-0.623753654,1181.85813 C125.696923,1173.96345 252.611387,1170 380,1170 C504.021509,1170 627.594032,1173.7567 750.607246,1181.24044 L760,1181.81186 L760,1344 L-10,1344 L-10,1182.44411 L-0.623753654,1181.85813 Z"/> + <path stroke="#1C2149" stroke-opacity=".3" stroke-width="40" d="M-1.24750731,1171.8776 C125.280796,1163.96995 252.40328,1160 380,1160 C504.224099,1160 627.999131,1163.76286 751.214492,1171.25889 L770,1172.40174 L770,1354 L-20,1354 L-20,1173.04957 L-1.24750731,1171.8776 Z"/> + <path stroke="#1C2149" stroke-opacity=".4" stroke-width="80" d="M-2.49501461,1151.91654 C124.448543,1143.98294 251.987065,1140 380,1140 C504.62928,1140 628.809329,1143.77517 752.428983,1151.2958 L790,1153.5815 L790,1374 L-40,1374 L-40,1154.26049 L-2.49501461,1151.91654 Z"/> + <g transform="translate(139 512)"> + <text fill="#FFFFFF" font-family="Montserrat-Light, Montserrat" font-size="70" font-weight="300" letter-spacing="6"> + <tspan x=".12" y="283">atmosphèr</tspan> <tspan x="430.72" y="283">e</tspan> + </text> + <g transform="translate(150 60)"> + <path fill="url(#c)" d="M140.891877,135.513136 C131.5,119.280156 110.164298,97.9223443 86,83.9182879 C63.7106729,71.0008429 40,69.9727626 22.473819,68.3306802 L70.2736332,7.73126938 C77.0979016,-0.920367707 89.6709985,-2.42326862 98.356431,4.37444623 C99.6100435,5.35559484 100.741386,6.48253478 101.726367,7.73126938 L169.145688,93.2038009 C171.994777,96.8158045 171.994777,101.900149 169.145688,105.512152 L148.476123,131.716511 C146.592662,134.099889 143.788719,135.433081 140.891877,135.513136 Z"/> + <path fill="url(#d)" d="M118.031922,127.570449 C107.834927,124.763997 97.0932499,123.264591 86,123.264591 C73.0752918,123.264591 60.6278261,125.299959 48.9630173,129.066469 C43.7318475,130.755588 38.6580811,132.792864 33.7692635,135.150858 C29.998297,136.097298 25.9388104,134.778103 23.466703,131.644027 L2.85431219,105.512152 C0.00522337805,101.900149 0.00522337805,96.8158045 2.85431219,93.2038009 L23.1407846,67.4851178 C23.4269631,67.483367 23.7133787,67.4824903 24,67.4824903 C74.5561466,67.4824903 118.716325,94.760331 142.45659,135.346634 C141.174144,135.585728 139.841949,135.576072 138.536549,135.298894 C132.011295,132.128744 125.154483,129.530745 118.031922,127.570449 Z"/> + </g> + </g> + </g> +</svg> diff --git a/Source/Atmosphere-MTC-Unlock/img/logo.png b/Source/Atmosphere-MTC-Unlock/img/logo.png new file mode 100644 index 00000000..a14f114c Binary files /dev/null and b/Source/Atmosphere-MTC-Unlock/img/logo.png differ diff --git a/Source/Atmosphere-MTC-Unlock/img/logo.svg b/Source/Atmosphere-MTC-Unlock/img/logo.svg new file mode 100644 index 00000000..9653c6bc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/img/logo.svg @@ -0,0 +1,16 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256"> + <defs> + <linearGradient id="a" x1="39.803%" x2="39.803%" y1="59.878%" y2="121.158%"> + <stop stop-color="#FFFFFF" offset="0%"/> + <stop stop-color="#9FC1DB" offset="100%"/> + </linearGradient> + <linearGradient id="b" x1="50%" x2="25.242%" y1="0%" y2="111.09%"> + <stop stop-color="#90B9D9" offset="0%"/> + <stop stop-color="#554BBA" offset="100%"/> + </linearGradient> + </defs> + <g fill="none" fill-rule="evenodd" transform="translate(42 60)"> + <path fill="url(#a)" d="M140.891877,135.513136 C131.5,119.280156 110.164298,97.9223443 86,83.9182879 C63.7106729,71.0008429 40,69.9727626 22.473819,68.3306802 L70.2736332,7.73126938 C77.0979016,-0.920367707 89.6709985,-2.42326862 98.356431,4.37444623 C99.6100435,5.35559484 100.741386,6.48253478 101.726367,7.73126938 L169.145688,93.2038009 C171.994777,96.8158045 171.994777,101.900149 169.145688,105.512152 L148.476123,131.716511 C146.592662,134.099889 143.788719,135.433081 140.891877,135.513136 Z"/> + <path fill="url(#b)" d="M118.031922,127.570449 C107.834927,124.763997 97.0932499,123.264591 86,123.264591 C73.0752918,123.264591 60.6278261,125.299959 48.9630173,129.066469 C43.7318475,130.755588 38.6580811,132.792864 33.7692635,135.150858 C29.998297,136.097298 25.9388104,134.778103 23.466703,131.644027 L2.85431219,105.512152 C0.00522337805,101.900149 0.00522337805,96.8158045 2.85431219,93.2038009 L23.1407846,67.4851178 C23.4269631,67.483367 23.7133787,67.4824903 24,67.4824903 C74.5561466,67.4824903 118.716325,94.760331 142.45659,135.346634 C141.174144,135.585728 139.841949,135.576072 138.536549,135.298894 C132.011295,132.128744 125.154483,129.530745 118.031922,127.570449 Z"/> + </g> +</svg> diff --git a/Source/Atmosphere-MTC-Unlock/img/logo_light.png b/Source/Atmosphere-MTC-Unlock/img/logo_light.png new file mode 100644 index 00000000..27472644 Binary files /dev/null and b/Source/Atmosphere-MTC-Unlock/img/logo_light.png differ diff --git a/Source/Atmosphere-MTC-Unlock/img/logo_light.svg b/Source/Atmosphere-MTC-Unlock/img/logo_light.svg new file mode 100644 index 00000000..ede33b18 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/img/logo_light.svg @@ -0,0 +1,12 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256"> + <defs> + <linearGradient id="a" x1="50%" x2="25.242%" y1="0%" y2="111.09%"> + <stop stop-color="#90C4D9" offset="0%"/> + <stop stop-color="#A298FF" offset="100%"/> + </linearGradient> + </defs> + <g fill="none" fill-rule="evenodd" transform="translate(42 60)"> + <path fill="#4E4E67" d="M140.891877,135.513136 C131.5,119.280156 110.164298,97.9223443 86,83.9182879 C63.7106729,71.0008429 40,69.9727626 22.473819,68.3306802 L70.2736332,7.73126938 C77.0979016,-0.920367707 89.6709985,-2.42326862 98.356431,4.37444623 C99.6100435,5.35559484 100.741386,6.48253478 101.726367,7.73126938 L169.145688,93.2038009 C171.994777,96.8158045 171.994777,101.900149 169.145688,105.512152 L148.476123,131.716511 C146.592662,134.099889 143.788719,135.433081 140.891877,135.513136 Z"/> + <path fill="url(#a)" d="M118.031922,127.570449 C107.834927,124.763997 97.0932499,123.264591 86,123.264591 C73.0752918,123.264591 60.6278261,125.299959 48.9630173,129.066469 C43.7318475,130.755588 38.6580811,132.792864 33.7692635,135.150858 C29.998297,136.097298 25.9388104,134.778103 23.466703,131.644027 L2.85431219,105.512152 C0.00522337805,101.900149 0.00522337805,96.8158045 2.85431219,93.2038009 L23.1407846,67.4851178 C23.4269631,67.483367 23.7133787,67.4824903 24,67.4824903 C74.5561466,67.4824903 118.716325,94.760331 142.45659,135.346634 C141.174144,135.585728 139.841949,135.576072 138.536549,135.298894 C132.011295,132.128744 125.154483,129.530745 118.031922,127.570449 Z"/> + </g> +</svg> diff --git a/Source/Atmosphere-MTC-Unlock/img/np++.png b/Source/Atmosphere-MTC-Unlock/img/np++.png new file mode 100644 index 00000000..bcbc6b58 Binary files /dev/null and b/Source/Atmosphere-MTC-Unlock/img/np++.png differ diff --git a/Source/Atmosphere-MTC-Unlock/img/splash.png b/Source/Atmosphere-MTC-Unlock/img/splash.png new file mode 100644 index 00000000..d00fc5a1 Binary files /dev/null and b/Source/Atmosphere-MTC-Unlock/img/splash.png differ diff --git a/Source/Atmosphere-MTC-Unlock/img/splash.svg b/Source/Atmosphere-MTC-Unlock/img/splash.svg new file mode 100644 index 00000000..c377899a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/img/splash.svg @@ -0,0 +1,44 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720" xmlns:xlink="http://www.w3.org/1999/xlink" style="background:#141932"> + <defs> + <linearGradient id="a" x1="50.142%" x2="50.142%" y1="100%" y2="0%"> + <stop stop-color="#3952A8" offset="0%"/> + <stop stop-color="#36378C" stop-opacity=".529" offset="51.507%"/> + <stop stop-color="#151A32" stop-opacity=".097" offset="100%"/> + </linearGradient> + <path id="b" d="M-3.41060513e-13,633.731488 C210.281502,611.434419 423.805425,600 640,600 C856.194575,600 1069.7185,611.434419 1280,633.731488 L1280,720 L0,720 L0,633.731488 Z"/> + <linearGradient id="c" x1="39.803%" x2="39.803%" y1="59.878%" y2="121.158%"> + <stop stop-color="#FFFFFF" offset="0%"/> + <stop stop-color="#9FC1DB" offset="100%"/> + </linearGradient> + <linearGradient id="d" x1="50%" x2="25.242%" y1="0%" y2="111.09%"> + <stop stop-color="#90B9D9" offset="0%"/> + <stop stop-color="#554BBA" offset="100%"/> + </linearGradient> + </defs> + <g fill="none" fill-rule="evenodd"> + <g fill="#8089CA" transform="translate(36 30)" opacity=".504"> + <path d="M948.35504 33.2853938C953.45168 33.9569767 956 34.6951788 956 35.5 956 36.3048214 953.451678 37.0430237 948.355035 37.7146068L948.355036 37.7146091C946.981681 37.8955757 945.910793 38.9933097 945.763879 40.3707235 945.079178 46.7902412 944.324552 50 943.5 50 942.675448 50 941.920821 46.790238 941.23612 40.370714L941.236111 40.370715C941.089198 38.9933041 940.018312 37.8955725 938.64496 37.7146062 933.54832 37.0430233 931 36.3048212 931 35.5 931 34.6951786 933.548322 33.9569763 938.644965 33.2853932L938.644964 33.2853909C940.018319 33.1044243 941.089207 32.0066903 941.236121 30.6292765 941.920822 24.2097588 942.675448 21 943.5 21 944.324552 21 945.079179 24.209762 945.76388 30.629286L945.763889 30.629285C945.910802 32.0066959 946.981688 33.1044275 948.35504 33.2853938zM177.35504 118.285394C182.45168 118.956977 185 119.695179 185 120.5 185 121.304821 182.451678 122.043024 177.355035 122.714607L177.355036 122.714609C175.981681 122.895576 174.910793 123.99331 174.763879 125.370724 174.079178 131.790241 173.324552 135 172.5 135 171.675448 135 170.920821 131.790238 170.23612 125.370714L170.236111 125.370715C170.089198 123.993304 169.018312 122.895572 167.64496 122.714606 162.54832 122.043023 160 121.304821 160 120.5 160 119.695179 162.548322 118.956976 167.644965 118.285393L167.644964 118.285391C169.018319 118.104424 170.089207 117.00669 170.236121 115.629276 170.920822 109.209759 171.675448 106 172.5 106 173.324552 106 174.079179 109.209762 174.76388 115.629286L174.763889 115.629285C174.910802 117.006696 175.981688 118.104428 177.35504 118.285394zM78.1319455 270.326734C82.0439818 270.987238 84 271.711659 84 272.5 84 273.288339 82.0439884 274.01276 78.1319651 274.673262L78.1319626 274.673248C76.8725782 274.885881 75.8858984 275.872561 75.6732656 277.131946 75.0127624 281.043982 74.2883406 283 73.5 283 72.7116607 283 71.98724 281.043988 71.3267377 277.131965L71.3267523 277.131963C71.114119 275.872578 70.1274389 274.885898 68.8680545 274.673266 64.9560182 274.012762 63 273.288341 63 272.5 63 271.711661 64.9560116 270.98724 68.8680349 270.326738L68.8680374 270.326752C70.1274218 270.114119 71.1141016 269.127439 71.3267344 267.868054 71.9872376 263.956018 72.7116594 262 73.5 262 74.2883393 262 75.01276 263.956012 75.6732623 267.868035L75.6732477 267.868037C75.885881 269.127422 76.8725611 270.114102 78.1319455 270.326734zM426.131946 8.3267344C430.043982 8.98723756 432 9.71165943 432 10.5 432 11.2883393 430.043988 12.01276 426.131965 12.6732623L426.131963 12.6732477C424.872578 12.885881 423.885898 13.8725611 423.673266 15.1319455 423.012762 19.0439818 422.288341 21 421.5 21 420.711661 21 419.98724 19.0439884 419.326738 15.1319651L419.326752 15.1319626C419.114119 13.8725782 418.127439 12.8858984 416.868054 12.6732656 412.956018 12.0127624 411 11.2883406 411 10.5 411 9.71166074 412.956012 8.98723998 416.868035 8.32673769L416.868037 8.32675227C418.127422 8.11411902 419.114102 7.1274389 419.326734 5.86805448 419.987238 1.95601816 420.711659 0 421.5 0 422.288339 0 423.01276 1.95601165 423.673262 5.86803495L423.673248 5.86803741C423.885881 7.12742176 424.872561 8.11410155 426.131946 8.3267344zM1134.13195 133.326734C1138.04398 133.987238 1140 134.711659 1140 135.5 1140 136.288339 1138.04399 137.01276 1134.13197 137.673262L1134.13196 137.673248C1132.87258 137.885881 1131.8859 138.872561 1131.67327 140.131946 1131.01276 144.043982 1130.28834 146 1129.5 146 1128.71166 146 1127.98724 144.043988 1127.32674 140.131965L1127.32675 140.131963C1127.11412 138.872578 1126.12744 137.885898 1124.86805 137.673266 1120.95602 137.012762 1119 136.288341 1119 135.5 1119 134.711661 1120.95601 133.98724 1124.86803 133.326738L1124.86804 133.326752C1126.12742 133.114119 1127.1141 132.127439 1127.32673 130.868054 1127.98724 126.956018 1128.71166 125 1129.5 125 1130.28834 125 1131.01276 126.956012 1131.67326 130.868035L1131.67325 130.868037C1131.88588 132.127422 1132.87256 133.114102 1134.13195 133.326734zM1187.13195 320.326734C1191.04398 320.987238 1193 321.711659 1193 322.5 1193 323.288339 1191.04399 324.01276 1187.13197 324.673262L1187.13196 324.673248C1185.87258 324.885881 1184.8859 325.872561 1184.67327 327.131946 1184.01276 331.043982 1183.28834 333 1182.5 333 1181.71166 333 1180.98724 331.043988 1180.32674 327.131965L1180.32675 327.131963C1180.11412 325.872578 1179.12744 324.885898 1177.86805 324.673266 1173.95602 324.012762 1172 323.288341 1172 322.5 1172 321.711661 1173.95601 320.98724 1177.86803 320.326738L1177.86804 320.326752C1179.12742 320.114119 1180.1141 319.127439 1180.32673 317.868054 1180.98724 313.956018 1181.71166 312 1182.5 312 1183.28834 312 1184.01276 313.956012 1184.67326 317.868035L1184.67325 317.868037C1184.88588 319.127422 1185.87256 320.114102 1187.13195 320.326734z"/> + <path d="M345.75138,198.258283 C348.931003,198.793842 350.520815,199.381353 350.520815,200.020815 C350.520815,200.660277 348.931006,201.247787 345.751389,201.783346 L345.751388,201.783339 C344.743132,201.953165 343.953173,202.743125 343.783347,203.75138 C343.247789,206.931003 342.660278,208.520815 342.020815,208.520815 C341.381353,208.520815 340.793843,206.931006 340.258285,203.751389 L340.258291,203.751388 C340.088466,202.743132 339.298506,201.953173 338.290251,201.783347 C335.110627,201.247789 333.520815,200.660278 333.520815,200.020815 C333.520815,199.381353 335.110624,198.793843 338.290242,198.258285 L338.290243,198.258291 C339.298498,198.088466 340.088458,197.298506 340.258283,196.290251 C340.793842,193.110627 341.381353,191.520815 342.020815,191.520815 C342.660277,191.520815 343.247787,193.110624 343.783346,196.290242 L343.783339,196.290243 C343.953165,197.298498 344.743125,198.088458 345.75138,198.258283 Z" transform="rotate(45 342.02 200.02)"/> + <path d="M15.7513799,33.2582831 C18.9310035,33.793842 20.5208153,34.3813527 20.5208153,35.0208153 C20.5208153,35.6602772 18.9310065,36.2477874 15.7513888,36.7833459 L15.7513877,36.7833393 C14.7431324,36.9531646 13.9531726,37.7431246 13.7833474,38.7513799 C13.2477885,41.9310035 12.6602778,43.5208153 12.0208153,43.5208153 C11.3813533,43.5208153 10.7938431,41.9310065 10.2582846,38.7513888 L10.2582913,38.7513877 C10.0884659,37.7431324 9.29850601,36.9531726 8.29025065,36.7833474 C5.11062707,36.2477885 3.52081528,35.6602778 3.52081528,35.0208153 C3.52081528,34.3813533 5.1106241,33.7938431 8.29024173,33.2582846 L8.29024285,33.2582913 C9.29849818,33.0884659 10.088458,32.298506 10.2582831,31.2902507 C10.793842,28.1106271 11.3813527,26.5208153 12.0208153,26.5208153 C12.6602772,26.5208153 13.2477874,28.1106241 13.7833459,31.2902417 L13.7833393,31.2902429 C13.9531646,32.2984982 14.7431246,33.088458 15.7513799,33.2582831 Z" transform="rotate(45 12.02 35.02)"/> + <path d="M869.75138,213.258283 C872.931003,213.793842 874.520815,214.381353 874.520815,215.020815 C874.520815,215.660277 872.931006,216.247787 869.751389,216.783346 L869.751388,216.783339 C868.743132,216.953165 867.953173,217.743125 867.783347,218.75138 C867.247789,221.931003 866.660278,223.520815 866.020815,223.520815 C865.381353,223.520815 864.793843,221.931006 864.258285,218.751389 L864.258291,218.751388 C864.088466,217.743132 863.298506,216.953173 862.290251,216.783347 C859.110627,216.247789 857.520815,215.660278 857.520815,215.020815 C857.520815,214.381353 859.110624,213.793843 862.290242,213.258285 L862.290243,213.258291 C863.298498,213.088466 864.088458,212.298506 864.258283,211.290251 C864.793842,208.110627 865.381353,206.520815 866.020815,206.520815 C866.660277,206.520815 867.247787,208.110624 867.783346,211.290242 L867.783339,211.290243 C867.953165,212.298498 868.743125,213.088458 869.75138,213.258283 Z" transform="rotate(45 866.02 215.02)"/> + <path d="M679.75138,57.2582831 C682.931003,57.793842 684.520815,58.3813527 684.520815,59.0208153 C684.520815,59.6602772 682.931006,60.2477874 679.751389,60.7833459 L679.751388,60.7833393 C678.743132,60.9531646 677.953173,61.7431246 677.783347,62.7513799 C677.247789,65.9310035 676.660278,67.5208153 676.020815,67.5208153 C675.381353,67.5208153 674.793843,65.9310065 674.258285,62.7513888 L674.258291,62.7513877 C674.088466,61.7431324 673.298506,60.9531726 672.290251,60.7833474 C669.110627,60.2477885 667.520815,59.6602778 667.520815,59.0208153 C667.520815,58.3813533 669.110624,57.7938431 672.290242,57.2582846 L672.290243,57.2582913 C673.298498,57.0884659 674.088458,56.298506 674.258283,55.2902507 C674.793842,52.1106271 675.381353,50.5208153 676.020815,50.5208153 C676.660277,50.5208153 677.247787,52.1106241 677.783346,55.2902417 L677.783339,55.2902429 C677.953165,56.2984982 678.743125,57.088458 679.75138,57.2582831 Z" transform="rotate(45 676.02 59.02)"/> + </g> + <rect width="1280" height="720" fill="url(#a)"/> + <use fill="#1C2149" xlink:href="#b"/> + <path stroke="#1C2149" stroke-opacity=".3" stroke-width="20" d="M-1.05443273,623.787235 C210.851493,601.317921 424.73221,590 640,590 C855.26779,590 1069.14851,601.317921 1281.05443,623.787235 L1290,624.735773 L1290,730 L-10,730 L-10,624.735773 L-1.05443273,623.787235 Z"/> + <path stroke="#1C2149" stroke-opacity=".3" stroke-width="40" d="M-2.10886547,613.842982 C210.147152,591.336546 424.379836,580 640,580 C855.620164,580 1069.85285,591.336546 1282.10887,613.842982 L1300,615.740057 L1300,740 L-20,740 L-20,615.740057 L-2.10886547,613.842982 Z"/> + <path stroke="#21254D" stroke-opacity=".4" stroke-width="80" d="M-4.21773094,593.954476 C208.73847,571.373797 423.675088,560 640,560 C856.324912,560 1071.26153,571.373797 1284.21773,593.954476 L1320,597.748626 L1320,760 L-40,760 L-40,597.748626 L-4.21773094,593.954476 Z"/> + <g fill="#5B89D5" transform="translate(606 632)"> + <path d="M25.4268,38.9907 C25.2979994,38.851166 25.2336,38.6740677 25.2336,38.4594 L25.2336,17.4328 C25.2336,17.1966655 25.2979994,17.008834 25.4268,16.8693 C25.5556006,16.729766 25.7273323,16.66 25.942,16.66 L33.7988,16.66 C36.2460122,16.66 38.1618931,17.2342276 39.5465,18.3827 C40.9311069,19.5311724 41.6234,21.2001891 41.6234,23.3898 C41.6234,25.0212748 41.2155374,26.3629281 40.3998,27.4148 C39.5840626,28.4666719 38.4463406,29.1965313 36.9866,29.6044 L41.9454,38.2018 C42.0098003,38.3306006 42.042,38.4486661 42.042,38.556 C42.042,38.7277342 41.9776006,38.8779994 41.8488,39.0068 C41.7199994,39.1356006 41.5697342,39.2 41.398,39.2 L39.9812,39.2 C39.6591984,39.2 39.4069676,39.1248674 39.2245,38.9746 C39.0420324,38.8243326 38.8756674,38.6096681 38.7254,38.3306 L33.992,30.0874 L28.2604,30.0874 L28.2604,38.4594 C28.2604,38.6740677 28.1852674,38.851166 28.035,38.9907 C27.8847326,39.130234 27.7022677,39.2 27.4876,39.2 L25.942,39.2 C25.7273323,39.2 25.5556006,39.130234 25.4268,38.9907 Z M33.67,27.447 C36.9114829,27.447 38.5322,26.0838803 38.5322,23.3576 C38.5322,20.6313197 36.9114829,19.2682 33.67,19.2682 L28.2604,19.2682 L28.2604,27.447 L33.67,27.447 Z M53.2,31.860303 L57.060303,28 L53.2,24.139697 L53.2,15.402449 C53.2,11.5350265 50.0649486,8.4 46.197551,8.4 L42.000818,8.4 C40.4510219,8.4 39.2,7.14770536 39.2,5.60292163 L39.2,2.79707837 C39.2,1.25302663 40.4539689,0 42.000818,0 L46.197551,0 C54.7041256,0 61.6,6.89581971 61.6,15.402449 L61.6,20.660303 L65.7219978,24.7823008 C67.500435,26.560738 67.4968427,29.4428543 65.7219978,31.2176992 L61.6,35.339697 L61.6,40.597551 C61.6,49.1041256 54.7041803,56 46.197551,56 L42.000818,56 C40.4510219,56 39.2,54.7477054 39.2,53.2029216 L39.2,50.3970784 C39.2,48.8530266 40.4539689,47.6 42.000818,47.6 L46.197551,47.6 C50.0649735,47.6 53.2,44.4649486 53.2,40.597551 L53.2,31.860303 Z M25.2,50.4 L21.002449,50.4 C15.5886363,50.4 11.2,46.011353 11.2,40.597551 L11.2,33.020101 L6.17989899,28 L11.2,22.979899 L11.2,15.402449 C11.2,9.98863626 15.588647,5.6 21.002449,5.6 L25.2,5.6 L25.2,2.8 L21.002449,2.8 C14.0422512,2.8 8.4,8.44223742 8.4,15.402449 L8.4,21.820101 L3.45790122,26.7621998 C2.774514,27.445587 2.77494166,28.5548406 3.45790122,29.2378002 L8.4,34.179899 L8.4,40.597551 C8.4,47.5577488 14.0422374,53.2 21.002449,53.2 L25.2,53.2 L25.2,50.4 Z M1.47800224,24.7823008 L5.6,20.660303 L5.6,15.402449 C5.6,6.89581971 12.4958744,0 21.002449,0 L25.199182,0 C26.7460311,0 28,1.25302663 28,2.79707837 L28,5.60292163 C28,7.14770536 26.7489781,8.4 25.199182,8.4 L21.002449,8.4 C17.1350514,8.4 14,11.5350265 14,15.402449 L14,24.139697 L10.139697,28 L14,31.860303 L14,40.597551 C14,44.4649486 17.1350265,47.6 21.002449,47.6 L25.199182,47.6 C26.7460311,47.6 28,48.8530266 28,50.3970784 L28,53.2029216 C28,54.7477054 26.7489781,56 25.199182,56 L21.002449,56 C12.4958197,56 5.6,49.1041256 5.6,40.597551 L5.6,35.339697 L1.47800224,31.2176992 C-0.296842703,29.4428543 -0.30043498,26.560738 1.47800224,24.7823008 Z"/> + </g> + <g transform="translate(414 192)"> + <text fill="#FFFFFF" font-family="Montserrat-Light, Montserrat" font-size="62" font-weight="300" letter-spacing="8"> + <tspan x=".144" y="236">atmosphèr</tspan> <tspan x="405.704" y="236" letter-spacing="9.697">e</tspan> + </text> + <g transform="translate(152.094 51.563)"> + <path fill="url(#c)" d="M121.078957,116.456601 C113.007813,102.506384 94.6724437,84.1520147 73.90625,72.1172787 C54.7513596,61.0163493 34.375,60.1328429 19.3134382,58.7216783 L60.3914036,6.64405962 C66.2560092,-0.790940998 77.0610143,-2.08249647 84.5250579,3.75928973 C85.6023811,4.60246432 86.5746282,5.57092832 87.4210964,6.64405962 L145.359575,80.0970164 C147.808011,83.201082 147.808011,87.5704404 145.359575,90.674506 L127.596668,113.193877 C125.978069,115.242092 123.568431,116.387804 121.078957,116.456601 Z"/> + <path fill="url(#d)" d="M101.433683,109.630854 C92.6706405,107.21906 83.4395117,105.930508 73.90625,105.930508 C62.7990789,105.930508 52.1020381,107.679652 42.077593,110.916497 C37.5820565,112.368083 33.2217884,114.118867 29.0204608,116.145269 C25.7797865,116.958615 22.2911652,115.824932 20.1666979,113.131585 L2.45292454,90.674506 C0.00448884051,87.5704404 0.00448884051,83.201082 2.45292454,80.0970164 L19.8866117,57.9950231 C20.1325464,57.9935185 20.3786849,57.9927651 20.625,57.9927651 C64.0716885,57.9927651 102.021842,81.4346595 122.423632,116.313514 C121.32153,116.518985 120.176675,116.510687 119.054846,116.272487 C113.447207,113.54814 107.554634,111.315484 101.433683,109.630854 Z"/> + </g> + </g> + </g> +</svg> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/.gitignore b/Source/Atmosphere-MTC-Unlock/libraries/.gitignore new file mode 100644 index 00000000..f55c55a6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/.gitignore @@ -0,0 +1,74 @@ +# 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 +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# 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 + +.**/ + +# NOTE: make sure to make exceptions to this pattern when needed! +*.bin + +**/out +**/build diff --git a/Source/sys-clk/sysmodule/src/emc.h b/Source/Atmosphere-MTC-Unlock/libraries/.gitmodules similarity index 100% rename from Source/sys-clk/sysmodule/src/emc.h rename to Source/Atmosphere-MTC-Unlock/libraries/.gitmodules diff --git a/Source/Atmosphere-MTC-Unlock/libraries/.gitrepo b/Source/Atmosphere-MTC-Unlock/libraries/.gitrepo new file mode 100644 index 00000000..67ca6a70 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/.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/Atmosphere-NX/Atmosphere-libs + branch = master + commit = efd3d931a426f72d4cc468c8fc2bee54da53290d + parent = 982f0e4fd422fcff568d95ba3ce60d10bff8272e + method = merge + cmdver = 0.4.1 diff --git a/Source/Atmosphere-MTC-Unlock/libraries/LICENSE b/Source/Atmosphere-MTC-Unlock/libraries/LICENSE new file mode 100644 index 00000000..ecbc0593 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 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. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/README.md b/Source/Atmosphere-MTC-Unlock/libraries/README.md new file mode 100644 index 00000000..c330899a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/README.md @@ -0,0 +1,31 @@ +![License](https://img.shields.io/badge/License-GPLv2-blue.svg) + +Atmosphere-libs is a collection of libraries for doing operating system development for the Nintendo Switch. + +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: +* The [yuzu Nintendo Switch emulator](https://github.com/yuzu-emu/yuzu) and the [Ryujinx Team and Contributors](https://github.com/orgs/Ryujinx) are exempt from GPLv2 licensing. They are permitted, each at their individual discretion, to instead license any source code authored for the atmosphere-libs project as either GPLv2 or later or the [MIT license](https://github.com/Atmosphere-NX/Atmosphere/blob/master/docs/licensing_exemptions/MIT_LICENSE). In doing so, they may alter, supplement, or entirely remove the copyright notice for each file they choose to relicense. Neither the Atmosphère project nor its individual contributors shall assert their moral rights against any of the aforementioned projects. +* [Nintendo](https://github.com/Nintendo) is exempt from GPLv2 licensing and may (at its option) instead license any source code authored for the atmosphere-libs project under the Zero-Clause BSD license. + +Credits +===== + +Atmosphere-libs is currently being developed and maintained by __SciresM__.<br> + +In addition to those credited in [Atmosphère's credits](https://github.com/Atmosphere-NX/Atmosphere/blob/master/README.md#Credits), we would like to thank for contributing to atmosphere-libs in some significant way: + +* @[devkitPro](https://github.com/devkitPro) +* @[yellows8](https://github.com/yellows8) +* @[qlutoo](https://github.com/plutooo) +* @[hedgeberg](https://github.com/hedgeberg) +* @[Nintendo](https://github.com/Nintendo) +* @[NVidia](https://github.com/NVidia) +* @[Kaphotics](https://github.com/kwsch) + +Additional credits may be found in the README.md for specific libraries. \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm/arch.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm/arch.mk new file mode 100644 index 00000000..f629bb39 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm/arch.mk @@ -0,0 +1,11 @@ +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro") +endif + +include $(DEVKITPRO)/devkitARM/base_rules + +export ATMOSPHERE_DEFINES += -DATMOSPHERE_ARCH_ARM +export ATMOSPHERE_SETTINGS += +export ATMOSPHERE_CFLAGS += +export ATMOSPHERE_CXXFLAGS += +export ATMOSPHERE_ASFLAGS += diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm/cpu/arm7tdmi/cpu.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm/cpu/arm7tdmi/cpu.mk new file mode 100644 index 00000000..5e3faf31 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm/cpu/arm7tdmi/cpu.mk @@ -0,0 +1,5 @@ +export ATMOSPHERE_DEFINES += -DATMOSPHERE_CPU_ARM7TDMI +export ATMOSPHERE_SETTINGS += -march=armv4t -mtune=arm7tdmi -mthumb -mthumb-interwork -fstrict-volatile-bitfields +export ATMOSPHERE_CFLAGS += +export ATMOSPHERE_CXXFLAGS += +export ATMOSPHERE_ASFLAGS += \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm64/arch.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm64/arch.mk new file mode 100644 index 00000000..307f669f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm64/arch.mk @@ -0,0 +1,23 @@ +ifeq ($(strip $(ATMOSPHERE_OS_NAME)),horizon) + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro") +endif + +include $(DEVKITPRO)/devkitA64/base_rules + +else + +include $(ATMOSPHERE_ARCH_MAKE_DIR)/base_rules + +endif + +export ATMOSPHERE_DEFINES += -DATMOSPHERE_ARCH_ARM64 +export ATMOSPHERE_SETTINGS += +export ATMOSPHERE_CFLAGS += +export ATMOSPHERE_CXXFLAGS += +export ATMOSPHERE_ASFLAGS += + +ifeq ($(strip $(ATMOSPHERE_OS_NAME)),horizon) +export ATMOSPHERE_SETTINGS += -mtp=soft +endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm64/base_rules b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm64/base_rules new file mode 100644 index 00000000..f8458521 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm64/base_rules @@ -0,0 +1,28 @@ +include $(ATMOSPHERE_ARCH_MAKE_DIR)/base_tools + +#--------------------------------------------------------------------------------- +%.a: +#--------------------------------------------------------------------------------- + $(SILENTMSG) $(notdir $@) + @rm -f $@ + $(SILENTCMD)$(AR) -rc $@ $^ + +#--------------------------------------------------------------------------------- +%.o: %.cpp + $(SILENTMSG) $(notdir $<) + $(SILENTCMD)$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.o: %.c + $(SILENTMSG) $(notdir $<) + $(SILENTCMD)$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.o: %.s + @echo $(notdir $<) + $(SILENTCMD)$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.o: %.S + $(SILENTMSG) $(notdir $<) + $(SILENTCMD)$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@ $(ERROR_FILTER) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm64/base_tools b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm64/base_tools new file mode 100644 index 00000000..4cfd84c5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm64/base_tools @@ -0,0 +1,52 @@ +#--------------------------------------------------------------------------------- +# the prefix on the compiler executables +#--------------------------------------------------------------------------------- +PREFIX := + +ifeq ($(strip $(ATMOSPHERE_COMPILER_NAME)),gcc) + +export CC := gcc +export CXX := g++ +export AS := as +export AR := gcc-ar +export OBJCOPY := objcopy +export STRIP := strip +export NM := gcc-nm +export RANLIB := gcc-ranlib + +else ifeq ($(strip $(ATMOSPHERE_COMPILER_NAME)),clang) + +export CC := clang +export CXX := clang++ +export AS := llvm-as +export AR := llvm-ar +export OBJCOPY := llvm-objcopy +export STRIP := llvm-strip +export NM := llvm-nm +export RANLIB := llvm-ranlib + +endif + +ISVC=$(or $(VCBUILDHELPER_COMMAND),$(MSBUILDEXTENSIONSPATH32),$(MSBUILDEXTENSIONSPATH)) + +ifneq (,$(ISVC)) + ERROR_FILTER := 2>&1 | sed -e 's/\(.[a-zA-Z]\+\):\([0-9]\+\):/\1(\2):/g' +endif + +#--------------------------------------------------------------------------------- +# allow seeing compiler command lines with make V=1 (similar to autotools' silent) +#--------------------------------------------------------------------------------- +ifeq ($(V),1) + SILENTMSG := @true + SILENTCMD := +else + SILENTMSG := @echo + SILENTCMD := @ +endif + +#--------------------------------------------------------------------------------- +# canned command sequence for binary data +#--------------------------------------------------------------------------------- +define bin2o + bin2s -a 8 -H `(echo $(<F) | tr . _)`.h $< | $(AS) -o $(<F).o +endef \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm64/cpu/cortex_a57/cpu.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm64/cpu/cortex_a57/cpu.mk new file mode 100644 index 00000000..b4cc5264 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm64/cpu/cortex_a57/cpu.mk @@ -0,0 +1,5 @@ +export ATMOSPHERE_DEFINES += -DATMOSPHERE_CPU_ARM_CORTEX_A57 +export ATMOSPHERE_SETTINGS += -march=armv8-a+crc+crypto -mtune=cortex-a57 +export ATMOSPHERE_CFLAGS += +export ATMOSPHERE_CXXFLAGS += +export ATMOSPHERE_ASFLAGS += \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm64/cpu/generic_arm64/cpu.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm64/cpu/generic_arm64/cpu.mk new file mode 100644 index 00000000..4d5ee37b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/arm64/cpu/generic_arm64/cpu.mk @@ -0,0 +1,5 @@ +export ATMOSPHERE_DEFINES += -DATMOSPHERE_CPU_GENERIC_ARM64 +export ATMOSPHERE_SETTINGS += -march=armv8-a+crc+crypto -mno-outline-atomics +export ATMOSPHERE_CFLAGS += +export ATMOSPHERE_CXXFLAGS += +export ATMOSPHERE_ASFLAGS += \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/arch/armv4t/arch.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/armv4t/arch.mk new file mode 100644 index 00000000..d9a14fb8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/armv4t/arch.mk @@ -0,0 +1,11 @@ +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro") +endif + +include $(DEVKITPRO)/devkitARM/base_rules + +export ATMOSPHERE_DEFINES += -DATMOSPHERE_ARCH_ARM_V4T +export ATMOSPHERE_SETTINGS += +export ATMOSPHERE_CFLAGS += +export ATMOSPHERE_CXXFLAGS += +export ATMOSPHERE_ASFLAGS += diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/arch/armv8a/arch.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/armv8a/arch.mk new file mode 100644 index 00000000..f8e56daf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/armv8a/arch.mk @@ -0,0 +1,11 @@ +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro") +endif + +include $(DEVKITPRO)/devkitA64/base_rules + +export ATMOSPHERE_DEFINES += -DATMOSPHERE_ARCH_ARM_V8A +export ATMOSPHERE_SETTINGS += +export ATMOSPHERE_CFLAGS += +export ATMOSPHERE_CXXFLAGS += +export ATMOSPHERE_ASFLAGS += diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/arch/x64/arch.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/x64/arch.mk new file mode 100644 index 00000000..181d7aad --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/x64/arch.mk @@ -0,0 +1,7 @@ +include $(ATMOSPHERE_ARCH_MAKE_DIR)/base_rules + +export ATMOSPHERE_DEFINES += -DATMOSPHERE_ARCH_X64 +export ATMOSPHERE_SETTINGS += +export ATMOSPHERE_CFLAGS += +export ATMOSPHERE_CXXFLAGS += +export ATMOSPHERE_ASFLAGS += diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/arch/x64/base_rules b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/x64/base_rules new file mode 100644 index 00000000..f8458521 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/x64/base_rules @@ -0,0 +1,28 @@ +include $(ATMOSPHERE_ARCH_MAKE_DIR)/base_tools + +#--------------------------------------------------------------------------------- +%.a: +#--------------------------------------------------------------------------------- + $(SILENTMSG) $(notdir $@) + @rm -f $@ + $(SILENTCMD)$(AR) -rc $@ $^ + +#--------------------------------------------------------------------------------- +%.o: %.cpp + $(SILENTMSG) $(notdir $<) + $(SILENTCMD)$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.o: %.c + $(SILENTMSG) $(notdir $<) + $(SILENTCMD)$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.o: %.s + @echo $(notdir $<) + $(SILENTCMD)$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.o: %.S + $(SILENTMSG) $(notdir $<) + $(SILENTCMD)$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@ $(ERROR_FILTER) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/arch/x64/base_tools b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/x64/base_tools new file mode 100644 index 00000000..4cfd84c5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/x64/base_tools @@ -0,0 +1,52 @@ +#--------------------------------------------------------------------------------- +# the prefix on the compiler executables +#--------------------------------------------------------------------------------- +PREFIX := + +ifeq ($(strip $(ATMOSPHERE_COMPILER_NAME)),gcc) + +export CC := gcc +export CXX := g++ +export AS := as +export AR := gcc-ar +export OBJCOPY := objcopy +export STRIP := strip +export NM := gcc-nm +export RANLIB := gcc-ranlib + +else ifeq ($(strip $(ATMOSPHERE_COMPILER_NAME)),clang) + +export CC := clang +export CXX := clang++ +export AS := llvm-as +export AR := llvm-ar +export OBJCOPY := llvm-objcopy +export STRIP := llvm-strip +export NM := llvm-nm +export RANLIB := llvm-ranlib + +endif + +ISVC=$(or $(VCBUILDHELPER_COMMAND),$(MSBUILDEXTENSIONSPATH32),$(MSBUILDEXTENSIONSPATH)) + +ifneq (,$(ISVC)) + ERROR_FILTER := 2>&1 | sed -e 's/\(.[a-zA-Z]\+\):\([0-9]\+\):/\1(\2):/g' +endif + +#--------------------------------------------------------------------------------- +# allow seeing compiler command lines with make V=1 (similar to autotools' silent) +#--------------------------------------------------------------------------------- +ifeq ($(V),1) + SILENTMSG := @true + SILENTCMD := +else + SILENTMSG := @echo + SILENTCMD := @ +endif + +#--------------------------------------------------------------------------------- +# canned command sequence for binary data +#--------------------------------------------------------------------------------- +define bin2o + bin2s -a 8 -H `(echo $(<F) | tr . _)`.h $< | $(AS) -o $(<F).o +endef \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/arch/x64/cpu/generic_x64/cpu.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/x64/cpu/generic_x64/cpu.mk new file mode 100644 index 00000000..11eb931d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/arch/x64/cpu/generic_x64/cpu.mk @@ -0,0 +1,5 @@ +export ATMOSPHERE_DEFINES += -DATMOSPHERE_CPU_GENERIC_X64 +export ATMOSPHERE_SETTINGS += -march=native -mtune=generic +export ATMOSPHERE_CFLAGS += +export ATMOSPHERE_CXXFLAGS += +export ATMOSPHERE_ASFLAGS += \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/board/generic/linux/board.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/board/generic/linux/board.mk new file mode 100644 index 00000000..92745300 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/board/generic/linux/board.mk @@ -0,0 +1,19 @@ +export ATMOSPHERE_DEFINES += -DATMOSPHERE_BOARD_GENERIC_LINUX +export ATMOSPHERE_SETTINGS += +export ATMOSPHERE_CFLAGS += +export ATMOSPHERE_CXXFLAGS += +export ATMOSPHERE_ASFLAGS += + +ifeq ($(strip $(ATMOSPHERE_COMPILER_NAME)),clang) +export ATMOSPHERE_CXXFLAGS += -stdlib=libc++ + +ifeq ($(strip $(ATMOSPHERE_ARCH_NAME)),x64) +export ATMOSPHERE_SETTINGS += -target x86_64-pc-linux-gnu +else ifeq ($(strip $(ATMOSPHERE_ARCH_NAME)),arm64) +export ATMOSPHERE_SETTINGS += -target aarch64-linux-gnu +endif + +endif + +# TODO: Better way of doing this? +export ATMOSPHERE_CXXFLAGS += -I/opt/libjpeg-turbo/include \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/board/generic/macos/board.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/board/generic/macos/board.mk new file mode 100644 index 00000000..756ff9a8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/board/generic/macos/board.mk @@ -0,0 +1,23 @@ +export ATMOSPHERE_DEFINES += -DATMOSPHERE_BOARD_GENERIC_MACOS +export ATMOSPHERE_SETTINGS += +export ATMOSPHERE_CFLAGS += +export ATMOSPHERE_CXXFLAGS += +export ATMOSPHERE_ASFLAGS += + +ifeq ($(strip $(ATMOSPHERE_COMPILER_NAME)),clang) +export ATMOSPHERE_CXXFLAGS += -stdlib=libc++ + +ifeq ($(strip $(ATMOSPHERE_ARCH_NAME)),x64) +export ATMOSPHERE_SETTINGS += -target x86_64-apple-darwin +else ifeq ($(strip $(ATMOSPHERE_ARCH_NAME)),arm64) +export ATMOSPHERE_SETTINGS += -target aarch64-apple-darwin +endif + +endif + +ifeq ($(strip $(SDKROOT)),) +export SDKROOT := $(shell xcrun --sdk macosx --show-sdk-path) +endif + +# TODO: Better way of doing this? +export ATMOSPHERE_CXXFLAGS += -I/opt/libjpeg-turbo/include \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/board/generic/windows/board.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/board/generic/windows/board.mk new file mode 100644 index 00000000..1c428c9f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/board/generic/windows/board.mk @@ -0,0 +1,5 @@ +export ATMOSPHERE_DEFINES += -DATMOSPHERE_BOARD_GENERIC_WINDOWS +export ATMOSPHERE_SETTINGS += +export ATMOSPHERE_CFLAGS += +export ATMOSPHERE_CXXFLAGS += +export ATMOSPHERE_ASFLAGS += \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/board/nintendo/nx/board.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/board/nintendo/nx/board.mk new file mode 100644 index 00000000..0a7acfca --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/board/nintendo/nx/board.mk @@ -0,0 +1,5 @@ +export ATMOSPHERE_DEFINES += -DATMOSPHERE_BOARD_NINTENDO_NX -D__SWITCH__ +export ATMOSPHERE_SETTINGS += +export ATMOSPHERE_CFLAGS += +export ATMOSPHERE_CXXFLAGS += +export ATMOSPHERE_ASFLAGS += \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/board/nintendo/nx_bpmp/board.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/board/nintendo/nx_bpmp/board.mk new file mode 100644 index 00000000..e267e24d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/board/nintendo/nx_bpmp/board.mk @@ -0,0 +1,5 @@ +export ATMOSPHERE_DEFINES += -DATMOSPHERE_BOARD_NINTENDO_NX -D__BPMP__ +export ATMOSPHERE_SETTINGS += +export ATMOSPHERE_CFLAGS += +export ATMOSPHERE_CXXFLAGS += +export ATMOSPHERE_ASFLAGS += \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/board/qemu/virt/board.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/board/qemu/virt/board.mk new file mode 100644 index 00000000..752aad41 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/board/qemu/virt/board.mk @@ -0,0 +1,5 @@ +export ATMOSPHERE_DEFINES += -DATMOSPHERE_BOARD_QEMU_VIRT -D__SWITCH__ +export ATMOSPHERE_SETTINGS += +export ATMOSPHERE_CFLAGS += +export ATMOSPHERE_CXXFLAGS += +export ATMOSPHERE_ASFLAGS += \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/common.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/common.mk new file mode 100644 index 00000000..7ef3708f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/common.mk @@ -0,0 +1,325 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- +DIR_WILDCARD=$(foreach d,$(wildcard $(1:=/*)),$(if $(wildcard $d/.),$(call DIR_WILDCARD,$d) $d,)) + +export ATMOSPHERE_CONFIG_MAKE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) +export ATMOSPHERE_LIBRARIES_DIR := $(ATMOSPHERE_CONFIG_MAKE_DIR)/.. + +ifeq ($(strip $(ATMOSPHERE_BOARD)),) +export ATMOSPHERE_BOARD := nx-hac-001 + +ifeq ($(strip $(ATMOSPHERE_CPU)),) +export ATMOSPHERE_CPU := arm-cortex-a57 +endif + +endif + +ifeq ($(ATMOSPHERE_BUILD_NAME),) +export ATMOSPHERE_BUILD_NAME := release +endif + +ifeq ($(strip $(ATMOSPHERE_COMPILER_NAME)),) + +ifneq ($(strip $(ATMOSPHERE_BOARD)),generic_macos) +export ATMOSPHERE_COMPILER_NAME := gcc +else +export ATMOSPHERE_COMPILER_NAME := clang +endif + +export ATMOSPHERE_BUILD_NAME := release +endif + +ATMOSPHERE_BUILD_SETTINGS ?= + +export ATMOSPHERE_DEFINES := -DATMOSPHERE +export ATMOSPHERE_SETTINGS := -fPIE -g $(ATMOSPHERE_BUILD_SETTINGS) +export ATMOSPHERE_CFLAGS := -Wall -ffunction-sections -fdata-sections -fno-strict-aliasing -fwrapv \ + -fno-asynchronous-unwind-tables -fno-unwind-tables -fno-stack-protector \ + -Wno-format-zero-length + +ifeq ($(strip $(ATMOSPHERE_COMPILER_NAME)),gcc) +export ATMOSPHERE_CFLAGS += -Wno-stringop-truncation -Wno-format-truncation +else ifeq ($(strip $(ATMOSPHERE_COMPILER_NAME)),clang) +export ATMOSPHERE_CFLAGS += -Wno-c99-designator -Wno-gnu-alignof-expression -Wno-unused-private-field +endif + +export ATMOSPHERE_CXXFLAGS := -fno-rtti -fno-exceptions -std=gnu++23 -Wno-invalid-offsetof +export ATMOSPHERE_ASFLAGS := + + +ifeq ($(ATMOSPHERE_BOARD),nx-hac-001) + +ifeq ($(ATMOSPHERE_CPU),arm-cortex-a57) +export ATMOSPHERE_ARCH_DIR := arm64 +export ATMOSPHERE_BOARD_DIR := nintendo/nx +export ATMOSPHERE_OS_DIR := horizon + +export ATMOSPHERE_ARCH_NAME := arm64 +export ATMOSPHERE_BOARD_NAME := nintendo_nx +export ATMOSPHERE_OS_NAME := horizon + +export ATMOSPHERE_SUB_ARCH_DIR := armv8a +export ATMOSPHERE_SUB_ARCH_NAME := armv8a + +export ATMOSPHERE_CPU_EXTENSIONS := arm_crypto_extension aarch64_crypto_extension + +export ATMOSPHERE_BOOT_CPU := arm7tdmi +export ATMOSPHERE_BOOT_ARCH_NAME := arm +export ATMOSPHERE_BOOT_BOARD_NAME := nintendo_nx +export ATMOSPHERE_BOOT_OS_NAME := horizon +export ATMOSPHERE_BOOT_SUB_ARCH_NAME := armv4t +else ifeq ($(ATMOSPHERE_CPU),arm7tdmi) +export ATMOSPHERE_ARCH_DIR := arm +export ATMOSPHERE_BOARD_DIR := nintendo/nx_bpmp +export ATMOSPHERE_OS_DIR := horizon + +export ATMOSPHERE_ARCH_NAME := arm +export ATMOSPHERE_BOARD_NAME := nintendo_nx +export ATMOSPHERE_OS_NAME := horizon + +export ATMOSPHERE_SUB_ARCH_DIR := armv4t +export ATMOSPHERE_SUB_ARCH_NAME := armv4t + +export ATMOSPHERE_CPU_EXTENSIONS := +endif + +export ATMOSPHERE_LIBDIRS := + +else ifeq ($(ATMOSPHERE_BOARD),qemu-virt) + + +ifeq ($(ATMOSPHERE_CPU),arm-cortex-a57) +export ATMOSPHERE_ARCH_DIR := arm64 +export ATMOSPHERE_BOARD_DIR := qemu/virt +export ATMOSPHERE_OS_DIR := horizon + +export ATMOSPHERE_ARCH_NAME := arm64 +export ATMOSPHERE_BOARD_NAME := qemu_virt +export ATMOSPHERE_OS_NAME := horizon + +export ATMOSPHERE_SUB_ARCH_DIR = armv8a +export ATMOSPHERE_SUB_ARCH_NAME = armv8a + +export ATMOSPHERE_CPU_EXTENSIONS := arm_crypto_extension aarch64_crypto_extension +endif + +export ATMOSPHERE_LIBDIRS := + +else ifeq ($(ATMOSPHERE_BOARD),generic_windows) + +ifeq ($(ATMOSPHERE_CPU),generic_x64) +export ATMOSPHERE_ARCH_DIR := x64 +export ATMOSPHERE_BOARD_DIR := generic/windows +export ATMOSPHERE_OS_DIR := windows + +export ATMOSPHERE_ARCH_NAME := x64 +export ATMOSPHERE_BOARD_NAME := generic_windows +export ATMOSPHERE_OS_NAME := windows + +endif + +else ifeq ($(ATMOSPHERE_BOARD),generic_linux) + +ifeq ($(ATMOSPHERE_CPU),generic_x64) +export ATMOSPHERE_ARCH_DIR := x64 +export ATMOSPHERE_ARCH_NAME := x64 +else ifeq ($(ATMOSPHERE_CPU),generic_arm64) +export ATMOSPHERE_ARCH_DIR := arm64 +export ATMOSPHERE_ARCH_NAME := arm64 +endif + +export ATMOSPHERE_BOARD_DIR := generic/linux +export ATMOSPHERE_OS_DIR := linux + +export ATMOSPHERE_BOARD_NAME := generic_linux +export ATMOSPHERE_OS_NAME := linux + +else ifeq ($(ATMOSPHERE_BOARD),generic_macos) + +ifeq ($(ATMOSPHERE_CPU),generic_x64) +export ATMOSPHERE_ARCH_DIR := x64 +export ATMOSPHERE_ARCH_NAME := x64 +else ifeq ($(ATMOSPHERE_CPU),generic_arm64) +export ATMOSPHERE_ARCH_DIR := arm64 +export ATMOSPHERE_ARCH_NAME := arm64 +endif + +export ATMOSPHERE_BOARD_DIR := generic/macos +export ATMOSPHERE_OS_DIR := macos + +export ATMOSPHERE_BOARD_NAME := generic_macos +export ATMOSPHERE_OS_NAME := macos + +endif + +ifeq ($(ATMOSPHERE_CPU),arm-cortex-a57) +export ATMOSPHERE_CPU_DIR := cortex_a57 +export ATMOSPHERE_CPU_NAME := arm_cortex_a57 +endif + +ifeq ($(ATMOSPHERE_CPU),arm7tdmi) +export ATMOSPHERE_CPU_DIR := arm7tdmi +export ATMOSPHERE_CPU_NAME := arm7tdmi +endif + +ifeq ($(ATMOSPHERE_CPU),generic_x64) +export ATMOSPHERE_CPU_DIR := generic_x64 +export ATMOSPHERE_CPU_NAME := generic_x64 +endif + +ifeq ($(ATMOSPHERE_CPU),generic_arm64) +export ATMOSPHERE_CPU_DIR := generic_arm64 +export ATMOSPHERE_CPU_NAME := generic_arm64 +endif + +export ATMOSPHERE_ARCH_MAKE_DIR := $(ATMOSPHERE_CONFIG_MAKE_DIR)/arch/$(ATMOSPHERE_ARCH_DIR) +export ATMOSPHERE_BOARD_MAKE_DIR := $(ATMOSPHERE_CONFIG_MAKE_DIR)/board/$(ATMOSPHERE_BOARD_DIR) +export ATMOSPHERE_OS_MAKE_DIR := $(ATMOSPHERE_CONFIG_MAKE_DIR)/os/$(ATMOSPHERE_OS_DIR) +export ATMOSPHERE_CPU_MAKE_DIR := $(ATMOSPHERE_ARCH_MAKE_DIR)/cpu/$(ATMOSPHERE_CPU_DIR) + +ifneq ($(strip $(ATMOSPHERE_SUB_ARCH_NAME)),) +export ATMOSPHERE_FULL_NAME := $(ATMOSPHERE_BOARD_NAME)_$(ATMOSPHERE_ARCH_NAME)_$(ATMOSPHERE_SUB_ARCH_NAME)_$(ATMOSPHERE_BUILD_NAME) +export ATMOSPHERE_LIBRARY_DIR := lib/$(ATMOSPHERE_BOARD_NAME)_$(ATMOSPHERE_ARCH_NAME)_$(ATMOSPHERE_SUB_ARCH_NAME)/$(ATMOSPHERE_BUILD_NAME) +export ATMOSPHERE_BUILD_DIR := build/$(ATMOSPHERE_BOARD_NAME)_$(ATMOSPHERE_ARCH_NAME)_$(ATMOSPHERE_SUB_ARCH_NAME)/$(ATMOSPHERE_BUILD_NAME) +export ATMOSPHERE_OUT_DIR := out/$(ATMOSPHERE_BOARD_NAME)_$(ATMOSPHERE_ARCH_NAME)_$(ATMOSPHERE_SUB_ARCH_NAME)/$(ATMOSPHERE_BUILD_NAME) +else +export ATMOSPHERE_FULL_NAME := $(ATMOSPHERE_BOARD_NAME)_$(ATMOSPHERE_ARCH_NAME)_$(ATMOSPHERE_BUILD_NAME) +export ATMOSPHERE_LIBRARY_DIR := lib/$(ATMOSPHERE_BOARD_NAME)_$(ATMOSPHERE_ARCH_NAME)/$(ATMOSPHERE_BUILD_NAME) +export ATMOSPHERE_BUILD_DIR := build/$(ATMOSPHERE_BOARD_NAME)_$(ATMOSPHERE_ARCH_NAME)/$(ATMOSPHERE_BUILD_NAME) +export ATMOSPHERE_OUT_DIR := out/$(ATMOSPHERE_BOARD_NAME)_$(ATMOSPHERE_ARCH_NAME)/$(ATMOSPHERE_BUILD_NAME) +endif + +ifneq ($(strip $(ATMOSPHERE_BOOT_ARCH_NAME)),) + +ifneq ($(strip $(ATMOSPHERE_SUB_ARCH_NAME)),) +export ATMOSPHERE_BOOT_FULL_NAME := $(ATMOSPHERE_BOOT_BOARD_NAME)_$(ATMOSPHERE_BOOT_ARCH_NAME)_$(ATMOSPHERE_BOOT_SUB_ARCH_NAME)_$(ATMOSPHERE_BUILD_NAME) +export ATMOSPHERE_BOOT_LIBRARY_DIR := lib/$(ATMOSPHERE_BOOT_BOARD_NAME)_$(ATMOSPHERE_BOOT_ARCH_NAME)_$(ATMOSPHERE_BOOT_SUB_ARCH_NAME)/$(ATMOSPHERE_BUILD_NAME) +export ATMOSPHERE_BOOT_BUILD_DIR := build/$(ATMOSPHERE_BOOT_BOARD_NAME)_$(ATMOSPHERE_BOOT_ARCH_NAME)_$(ATMOSPHERE_BOOT_SUB_ARCH_NAME)/$(ATMOSPHERE_BUILD_NAME) +export ATMOSPHERE_BOOT_OUT_DIR := out/$(ATMOSPHERE_BOOT_BOARD_NAME)_$(ATMOSPHERE_BOOT_ARCH_NAME)_$(ATMOSPHERE_BOOT_SUB_ARCH_NAME)/$(ATMOSPHERE_BUILD_NAME) +else +export ATMOSPHERE_BOOT_FULL_NAME := $(ATMOSPHERE_BOOT_BOARD_NAME)_$(ATMOSPHERE_BOOT_ARCH_NAME)_$(ATMOSPHERE_BUILD_NAME) +export ATMOSPHERE_BOOT_LIBRARY_DIR := lib/$(ATMOSPHERE_BOOT_BOARD_NAME)_$(ATMOSPHERE_BOOT_ARCH_NAME)/$(ATMOSPHERE_BUILD_NAME) +export ATMOSPHERE_BOOT_BUILD_DIR := build/$(ATMOSPHERE_BOOT_BOARD_NAME)_$(ATMOSPHERE_BOOT_ARCH_NAME)/$(ATMOSPHERE_BUILD_NAME) +export ATMOSPHERE_BOOT_OUT_DIR := out/$(ATMOSPHERE_BOOT_BOARD_NAME)_$(ATMOSPHERE_BOOT_ARCH_NAME)/$(ATMOSPHERE_BUILD_NAME) +endif + +else +export ATMOSPHERE_BOOT_FULL_NAME := $(ATMOSPHERE_FULL_NAME) +export ATMOSPHERE_BOOT_LIBRARY_DIR := $(ATMOSPHERE_LIBRARY_DIR) +export ATMOSPHERE_BOOT_BUILD_DIR := $(ATMOSPHERE_BUILD_DIR) +export ATMOSPHERE_BOOT_OUT_DIR := $(ATMOSPHERE_OUT_DIR) +endif + +ifeq ($(strip $(ATMOSPHERE_BOOT_CPU)),) +export ATMOSPHERE_BOOT_CPU := $(ATMOSPHERE_CPU) +endif + +include $(ATMOSPHERE_ARCH_MAKE_DIR)/arch.mk +include $(ATMOSPHERE_BOARD_MAKE_DIR)/board.mk +include $(ATMOSPHERE_OS_MAKE_DIR)/os.mk +include $(ATMOSPHERE_CPU_MAKE_DIR)/cpu.mk + + +ifneq ($(strip $(ATMOSPHERE_SUB_ARCH_NAME)),) +export ATMOSPHERE_SUB_ARCH_MAKE_DIR := $(ATMOSPHERE_CONFIG_MAKE_DIR)/arch/$(ATMOSPHERE_SUB_ARCH_DIR) + +include $(ATMOSPHERE_SUB_ARCH_MAKE_DIR)/arch.mk +endif + + +#--------------------------------------------------------------------------------- +# get atmosphere git revision information +#--------------------------------------------------------------------------------- +export ATMOSPHERE_GIT_BRANCH := $(shell git symbolic-ref --short HEAD) + +ifeq ($(strip $(shell git status --porcelain 2>/dev/null)),) +export ATMOSPHERE_GIT_REVISION := $(ATMOSPHERE_GIT_BRANCH)-$(shell git rev-parse --short HEAD) +else +export ATMOSPHERE_GIT_REVISION := $(ATMOSPHERE_GIT_BRANCH)-$(shell git rev-parse --short HEAD)-dirty +endif + +export ATMOSPHERE_GIT_HASH := $(shell git rev-parse --short=16 HEAD) + +ATMOSPHERE_DEFINES += -DATMOSPHERE_GIT_BRANCH=\"$(ATMOSPHERE_GIT_BRANCH)\" -DATMOSPHERE_GIT_REVISION=\"$(ATMOSPHERE_GIT_REVISION)\" -DATMOSPHERE_GIT_HASH="0x$(ATMOSPHERE_GIT_HASH)" + +#--------------------------------------------------------------------------------- +# Ensure top directory is set. +#--------------------------------------------------------------------------------- +TOPDIR ?= $(CURDIR) + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +DATA := data +INCLUDES := include + +GENERAL_SOURCE_DIRS=$1 $(foreach d,$(filter-out $1/arch $1/board $1/os $1/cpu $1,$(wildcard $1/*)),$(if $(wildcard $d/.),$(filter-out $d,$(call GENERAL_SOURCE_DIRS,$d)) $d,)) + +SPECIFIC_SOURCE_DIRS=$(if $(wildcard $1/$2/$3/.*),$1/$2/$3 $(call DIR_WILDCARD,$1/$2/$3),$(if $(wildcard $1/$2/generic/.*), $1/$2/generic $(call DIR_WILDCARD,$1/$2/generic),)) + +ifneq ($(strip $(ATMOSPHERE_SUB_ARCH_NAME)),) +SPECIFIC_SOURCE_DIRS_ARCH=$(if $(wildcard $1/$2/$3/.*),$1/$2/$3 $(call DIR_WILDCARD,$1/$2/$3),$(if $(wildcard $1/$2/$4/.*),$1/$2/$4 $(call DIR_WILDCARD,$1/$2/$4),$(if $(wildcard $1/$2/generic/.*), $1/$2/generic $(call DIR_WILDCARD,$1/$2/generic),))) +else +SPECIFIC_SOURCE_DIRS_ARCH=$(if $(wildcard $1/$2/$3/.*),$1/$2/$3 $(call DIR_WILDCARD,$1/$2/$3),$(if $(wildcard $1/$2/generic/.*), $1/$2/generic $(call DIR_WILDCARD,$1/$2/generic),)) +endif + +UNFILTERED_SOURCE_DIRS=$1 $(foreach d,$(wildcard $1/*),$(if $(wildcard $d/.),$(call DIR_WILDCARD,$d) $d,)) + +ALL_SOURCE_DIRS=$(foreach d,$(call GENERAL_SOURCE_DIRS,$1), \ + $d \ + $(call SPECIFIC_SOURCE_DIRS_ARCH,$d,arch,$(ATMOSPHERE_ARCH_DIR),$(ATMOSPHERE_SUB_ARCH_DIR)) \ + $(call SPECIFIC_SOURCE_DIRS,$d,board,$(ATMOSPHERE_BOARD_DIR)) \ + $(call SPECIFIC_SOURCE_DIRS,$d,os,$(ATMOSPHERE_OS_DIR)) \ + $(call SPECIFIC_SOURCE_DIRS,$d,cpu,$(ATMOSPHERE_ARCH_DIR)/$(ATMOSPHERE_CPU_DIR)) \ + ) + +SOURCES ?= $(call ALL_SOURCE_DIRS,source) + +FIND_SPECIFIC_SOURCE_FILES= $(notdir $(wildcard $1/*.$2.$3.$4)) $(filter-out $(subst .$2.$3.,.$2.generic.,$(notdir $(wildcard $1/*.$2.$3.$4))),$(notdir $(wildcard $1/*.$2.generic.$4))) + +FIND_SPECIFIC_SOURCE_FILES_EX=$(foreach ext,$3,$(notdir $(wildcard $1/*.$2.$(ext).$4))) $(filter-out $(foreach ext,$3,$(subst .$2.$(ext).,.$2.generic.,$(notdir $(wildcard $1/*.$2.$(ext).$4)))),$(notdir $(wildcard $1/*.$2.generic.$4))) + +FIND_SOURCE_FILES=$(foreach dir,$1,$(filter-out $(notdir $(wildcard $(dir)/*.arch.*.$2)) \ + $(notdir $(wildcard $(dir)/*.board.*.$2)) \ + $(notdir $(wildcard $(dir)/*.os.*.$2)) \ + $(notdir $(wildcard $(dir)/.cpu.*.$2)), \ + $(notdir $(wildcard $(dir)/*.$2)))) \ + $(foreach dir,$1,$(call FIND_SPECIFIC_SOURCE_FILES_EX,$(dir),arch,$(ATMOSPHERE_ARCH_NAME) $(ATMOSPHERE_SUB_ARCH_NAME),$2)) \ + $(foreach dir,$1,$(call FIND_SPECIFIC_SOURCE_FILES,$(dir),board,$(ATMOSPHERE_BOARD_NAME),$2)) \ + $(foreach dir,$1,$(call FIND_SPECIFIC_SOURCE_FILES,$(dir),os,$(ATMOSPHERE_OS_NAME),$2)) \ + $(foreach dir,$1,$(call FIND_SPECIFIC_SOURCE_FILES_EX,$(dir),cpu,$(ATMOSPHERE_CPU_NAME) $(ATMOSPHERE_CPU_EXTENSIONS),$2)) + +ATMOSPHERE_GCH_IDENTIFIER := $(ATMOSPHERE_FULL_NAME) + +#--------------------------------------------------------------------------------- +# Python. The scripts should work with Python 2 or 3, but 2 is preferred. +#--------------------------------------------------------------------------------- +PYTHON = $(shell command -v python >/dev/null && echo python || echo python3) + +#--------------------------------------------------------------------------------- +# Export MAKE: +# GCC's LTO driver invokes Make internally. This invocation respects both $(MAKE) +# and $(MAKEFLAGS), but only if they're in the environment. By default, MAKEFLAGS +# is in the environment while MAKE isn't, so GCC will always use the default +# `make` command, yet it inherits MAKEFLAGS from the copy of Make the user +# invoked, which might have incompatible flags. In practice this is an issue on +# macOS when running e.g. `gmake -j32 -Otarget`. This behavior is arguably a bug +# in GCC and/or Make, but we can work around it by exporting MAKE. +#--------------------------------------------------------------------------------- +export MAKE + +#--------------------------------------------------------------------------------- +# Rules for compiling pre-compiled headers +#--------------------------------------------------------------------------------- +%.hpp.gch/$(ATMOSPHERE_GCH_IDENTIFIER): %.hpp %.hpp.gch + @echo Precompiling $(notdir $<) for $(ATMOSPHERE_GCH_IDENTIFIER) + $(SILENTCMD)$(CXX) -w -x c++-header -MMD -MP -MQ$@ -MF $(DEPSDIR)/$(notdir $*).d $(CXXFLAGS) -c $< -o $@ $(ERROR_FILTER) + +%.hpp.gch: %.hpp + @[ -d $@ ] || mkdir -p $@ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/os/horizon/os.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/os/horizon/os.mk new file mode 100644 index 00000000..5c972b74 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/os/horizon/os.mk @@ -0,0 +1,5 @@ +export ATMOSPHERE_DEFINES += -DATMOSPHERE_OS_HORIZON +export ATMOSPHERE_SETTINGS += +export ATMOSPHERE_CFLAGS += +export ATMOSPHERE_CXXFLAGS += +export ATMOSPHERE_ASFLAGS += diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/os/linux/os.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/os/linux/os.mk new file mode 100644 index 00000000..ee225a6f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/os/linux/os.mk @@ -0,0 +1,5 @@ +export ATMOSPHERE_DEFINES += -DATMOSPHERE_OS_LINUX +export ATMOSPHERE_SETTINGS += -fno-omit-frame-pointer +export ATMOSPHERE_CFLAGS += +export ATMOSPHERE_CXXFLAGS += +export ATMOSPHERE_ASFLAGS += diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/os/macos/os.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/os/macos/os.mk new file mode 100644 index 00000000..f6e4e1af --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/os/macos/os.mk @@ -0,0 +1,5 @@ +export ATMOSPHERE_DEFINES += -DATMOSPHERE_OS_MACOS +export ATMOSPHERE_SETTINGS += -fno-omit-frame-pointer +export ATMOSPHERE_CFLAGS += +export ATMOSPHERE_CXXFLAGS += +export ATMOSPHERE_ASFLAGS += diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/os/windows/os.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/os/windows/os.mk new file mode 100644 index 00000000..55e8a06c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/os/windows/os.mk @@ -0,0 +1,5 @@ +export ATMOSPHERE_DEFINES += -DATMOSPHERE_OS_WINDOWS +export ATMOSPHERE_SETTINGS += -fno-omit-frame-pointer -fno-data-sections +export ATMOSPHERE_CFLAGS += +export ATMOSPHERE_CXXFLAGS += +export ATMOSPHERE_ASFLAGS += diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/templates/exosphere.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/templates/exosphere.mk new file mode 100644 index 00000000..3539d038 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/templates/exosphere.mk @@ -0,0 +1,64 @@ +#--------------------------------------------------------------------------------- +# pull in common atmosphere configuration +#--------------------------------------------------------------------------------- +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../common.mk + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ifeq ($(strip $(ATMOSPHERE_ARCH_NAME)),arm64) +ifeq ($(ATMOSPHERE_BUILD_FOR_DEBUGGING),1) +ATMOSPHERE_OPTIMIZATION_FLAG := -Os +else +ATMOSPHERE_OPTIMIZATION_FLAG := -Os +endif + +DEFINES := $(ATMOSPHERE_DEFINES) -DATMOSPHERE_IS_EXOSPHERE -DAMS_FORCE_DISABLE_DETAILED_ASSERTIONS +SETTINGS := $(ATMOSPHERE_SETTINGS) -mgeneral-regs-only -ffixed-x18 $(ATMOSPHERE_OPTIMIZATION_FLAG) -Wextra -Werror -fno-non-call-exceptions \ + -Wno-array-bounds \ + -Wno-stringop-overflow \ + -Wno-stringop-overread +CFLAGS := $(ATMOSPHERE_CFLAGS) $(SETTINGS) $(DEFINES) $(INCLUDE) +CXXFLAGS := $(CFLAGS) $(ATMOSPHERE_CXXFLAGS) -fno-use-cxa-atexit +ASFLAGS := $(ATMOSPHERE_ASFLAGS) $(SETTINGS) +else ifeq ($(strip $(ATMOSPHERE_ARCH_NAME)),arm) +ifeq ($(ATMOSPHERE_BUILD_FOR_DEBUGGING),1) +ATMOSPHERE_OPTIMIZATION_FLAG := -Os +else +ATMOSPHERE_OPTIMIZATION_FLAG := -O2 +endif + +DEFINES := $(ATMOSPHERE_DEFINES) -DATMOSPHERE_IS_EXOSPHERE -DAMS_FORCE_DISABLE_DETAILED_ASSERTIONS +SETTINGS := $(ATMOSPHERE_SETTINGS) $(ATMOSPHERE_OPTIMIZATION_FLAG) -Werror -fno-non-call-exceptions \ + -Wno-array-bounds \ + -Wno-stringop-overflow \ + -Wno-stringop-overread +CFLAGS := $(ATMOSPHERE_CFLAGS) $(SETTINGS) $(DEFINES) $(INCLUDE) +CXXFLAGS := $(CFLAGS) $(ATMOSPHERE_CXXFLAGS) -fno-use-cxa-atexit +ASFLAGS := $(ATMOSPHERE_ASFLAGS) $(SETTINGS) +endif + +export LDFLAGS = -specs=$(ATMOSPHERE_TOPDIR)/$(notdir $(ATMOSPHERE_TOPDIR)).specs -fno-asynchronous-unwind-tables -fno-unwind-tables -fno-exceptions -fno-rtti -fno-use-cxa-atexit -nostdlib -nostartfiles -g $(CXXFLAGS) -Wl,-Map,$(notdir $*.map) -Wl,-z,relro,-z,now + +export CXXWRAPS := -Wl,--wrap,__cxa_pure_virtual \ + -Wl,--wrap,__cxa_throw \ + -Wl,--wrap,__cxa_rethrow \ + -Wl,--wrap,__cxa_allocate_exception \ + -Wl,--wrap,__cxa_free_exception \ + -Wl,--wrap,__cxa_begin_catch \ + -Wl,--wrap,__cxa_end_catch \ + -Wl,--wrap,__cxa_call_unexpected \ + -Wl,--wrap,__cxa_call_terminate \ + -Wl,--wrap,__gxx_personality_v0 \ + -Wl,--wrap,_Unwind_Resume \ + -Wl,--wrap,_ZSt19__throw_logic_errorPKc \ + -Wl,--wrap,_ZSt20__throw_length_errorPKc \ + -Wl,--wrap,_ZNSt11logic_errorC2EPKc + +export LIBS := -lexosphere -lgcc + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(ATMOSPHERE_LIBRARIES_DIR)/libvapours $(ATMOSPHERE_LIBRARIES_DIR)/libexosphere diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/templates/mesosphere.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/templates/mesosphere.mk new file mode 100644 index 00000000..6205650b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/templates/mesosphere.mk @@ -0,0 +1,44 @@ +#--------------------------------------------------------------------------------- +# pull in common atmosphere configuration +#--------------------------------------------------------------------------------- +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../common.mk + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ifeq ($(ATMOSPHERE_BUILD_FOR_DEBUGGING),1) +ATMOSPHERE_OPTIMIZATION_FLAG := -Os +else +ATMOSPHERE_OPTIMIZATION_FLAG := -O3 +endif + +export DEFINES := $(ATMOSPHERE_DEFINES) -DATMOSPHERE_IS_MESOSPHERE +export SETTINGS := $(ATMOSPHERE_SETTINGS) $(ATMOSPHERE_OPTIMIZATION_FLAG) -mgeneral-regs-only -ffixed-x18 -Wextra -Werror -fno-non-call-exceptions +export CFLAGS := $(ATMOSPHERE_CFLAGS) $(SETTINGS) $(DEFINES) $(INCLUDE) +export CXXFLAGS := $(CFLAGS) $(ATMOSPHERE_CXXFLAGS) -fno-use-cxa-atexit +export ASFLAGS := $(ATMOSPHERE_ASFLAGS) $(SETTINGS) $(DEFINES) $(INCLUDE) + +export LDFLAGS = -specs=$(ATMOSPHERE_TOPDIR)/$(notdir $(ATMOSPHERE_TOPDIR)).specs -fno-asynchronous-unwind-tables -fno-unwind-tables -nostdlib -nostartfiles -g $(CXXFLAGS) -Wl,-Map,$(notdir $*.map) -Wl,-z,relro,-z,now + +export CXXWRAPS := -Wl,--wrap,__cxa_pure_virtual \ + -Wl,--wrap,__cxa_throw \ + -Wl,--wrap,__cxa_rethrow \ + -Wl,--wrap,__cxa_allocate_exception \ + -Wl,--wrap,__cxa_free_exception \ + -Wl,--wrap,__cxa_begin_catch \ + -Wl,--wrap,__cxa_end_catch \ + -Wl,--wrap,__cxa_call_unexpected \ + -Wl,--wrap,__cxa_call_terminate \ + -Wl,--wrap,__gxx_personality_v0 \ + -Wl,--wrap,_Unwind_Resume \ + -Wl,--wrap,_ZSt19__throw_logic_errorPKc \ + -Wl,--wrap,_ZSt20__throw_length_errorPKc \ + -Wl,--wrap,_ZNSt11logic_errorC2EPKc + +export LIBS := -lmesosphere + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(ATMOSPHERE_LIBRARIES_DIR)/libvapours $(ATMOSPHERE_LIBRARIES_DIR)/libmesosphere diff --git a/Source/Atmosphere-MTC-Unlock/libraries/config/templates/stratosphere.mk b/Source/Atmosphere-MTC-Unlock/libraries/config/templates/stratosphere.mk new file mode 100644 index 00000000..06fb362c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/config/templates/stratosphere.mk @@ -0,0 +1,107 @@ +#--------------------------------------------------------------------------------- +# pull in common atmosphere configuration +#--------------------------------------------------------------------------------- +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../common.mk + +ifeq ($(ATMOSPHERE_BOARD),nx-hac-001) + +#--------------------------------------------------------------------------------- +# pull in switch rules +#--------------------------------------------------------------------------------- +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro") +endif + +include $(DEVKITPRO)/libnx/switch_rules + +endif + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ifeq ($(ATMOSPHERE_BUILD_FOR_DEBUGGING),1) +ATMOSPHERE_OPTIMIZATION_FLAG := -Os +else +ATMOSPHERE_OPTIMIZATION_FLAG := -O2 +endif + +export DEFINES = $(ATMOSPHERE_DEFINES) -DATMOSPHERE_IS_STRATOSPHERE -D_GNU_SOURCE +export SETTINGS = $(ATMOSPHERE_SETTINGS) $(ATMOSPHERE_OPTIMIZATION_FLAG) -Wextra -Werror -Wno-missing-field-initializers +export CFLAGS = $(ATMOSPHERE_CFLAGS) $(SETTINGS) $(DEFINES) $(INCLUDE) +export CXXFLAGS = $(CFLAGS) $(ATMOSPHERE_CXXFLAGS) +export ASFLAGS = $(ATMOSPHERE_ASFLAGS) $(SETTINGS) $(DEFINES) + +ifeq ($(ATMOSPHERE_BOARD),nx-hac-001) +export CXXREQUIRED := -Wl,--require-defined,__libnx_initheap \ + -Wl,--require-defined,__libnx_exception_handler \ + -Wl,--require-defined,__libnx_alloc \ + -Wl,--require-defined,__libnx_aligned_alloc \ + -Wl,--require-defined,__libnx_free \ + -Wl,--require-defined,__appInit \ + -Wl,--require-defined,__appExit \ + -Wl,--require-defined,argvSetup + +export CXXWRAPS := -Wl,--wrap,__cxa_pure_virtual \ + -Wl,--wrap,__cxa_throw \ + -Wl,--wrap,__cxa_rethrow \ + -Wl,--wrap,__cxa_allocate_exception \ + -Wl,--wrap,__cxa_free_exception \ + -Wl,--wrap,__cxa_begin_catch \ + -Wl,--wrap,__cxa_end_catch \ + -Wl,--wrap,__cxa_call_unexpected \ + -Wl,--wrap,__cxa_call_terminate \ + -Wl,--wrap,__gxx_personality_v0 \ + -Wl,--wrap,_Unwind_Resume \ + -Wl,--wrap,_ZSt19__throw_logic_errorPKc \ + -Wl,--wrap,_ZSt20__throw_length_errorPKc \ + -Wl,--wrap,_ZNSt11logic_errorC2EPKc \ + -Wl,--wrap,exit +else ifeq ($(ATMOSPHERE_BOARD),generic_windows) +export CXXREQUIRED := +export CXXWRAPS := -Wl,--wrap,__p__acmdln -Wl,--wrap,_set_invalid_parameter_handler +else +export CXXREQUIRED := +export CXXWRAPS := +endif + + +ifeq ($(ATMOSPHERE_BOARD),nx-hac-001) +export LDFLAGS = -specs=$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/stratosphere.specs -specs=$(DEVKITPRO)/libnx/switch.specs $(CXXFLAGS) $(CXXWRAPS) $(CXXREQUIRED) -Wl,-Map,$(notdir $*.map) -Wl,-z,relro,-z,now +else ifeq ($(ATMOSPHERE_OS_NAME),macos) +export LDFLAGS = $(CXXFLAGS) $(CXXWRAPS) $(CXXREQUIRED) -Wl,-map,$(notdir $@.map) +else +export LDFLAGS = $(CXXFLAGS) $(CXXWRAPS) $(CXXREQUIRED) -Wl,-Map,$(notdir $@.map) +endif + +ifeq ($(ATMOSPHERE_COMPILER_NAME),clang) +export LDFLAGS += -fuse-ld=lld +endif + +ifeq ($(ATMOSPHERE_BOARD),nx-hac-001) +export LIBS := -lstratosphere -lnx +else ifeq ($(ATMOSPHERE_BOARD),generic_windows) +export LIBS := -lstratosphere -lwinmm -lws2_32 -lbcrypt -static -static-libgcc -static-libstdc++ +else ifeq ($(ATMOSPHERE_BOARD),generic_linux) +export LIBS := -lstratosphere -pthread -lbfd +else ifeq ($(ATMOSPHERE_BOARD),generic_macos) +export LIBS := -lstratosphere -pthread +else +export LIBS := -lstratosphere +endif + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +ifeq ($(ATMOSPHERE_BOARD),nx-hac-001) +export LIBDIRS = $(PORTLIBS) $(LIBNX) +else +export LIBDIRS = +endif + +export AMS_LIBDIRS = $(ATMOSPHERE_LIBRARIES_DIR)/libvapours $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere + +#--------------------------------------------------------------------------------- +# stratosphere sysmodules may (but usually do not) have an exefs source dir +#--------------------------------------------------------------------------------- +export EXEFS_SRC = exefs_src \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/Makefile b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/Makefile new file mode 100644 index 00000000..8886e59c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/Makefile @@ -0,0 +1,42 @@ +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)/libexosphere.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)/libexosphere.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,)) +$(eval $(call ATMOSPHERE_ADD_TARGETS, nx_bpmp, nx-hac-001, arm7tdmi,)) + +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/libraries/libexosphere/include/exosphere.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere.hpp new file mode 100644 index 00000000..11b2b142 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +#include <exosphere/common.hpp> +#include <exosphere/hw.hpp> +#include <exosphere/util.hpp> +#include <exosphere/mmu.hpp> +#include <exosphere/br.hpp> +#include <exosphere/charger.hpp> +#include <exosphere/gic.hpp> +#include <exosphere/wdt.hpp> +#include <exosphere/pkg1.hpp> +#include <exosphere/pkg2.hpp> +#include <exosphere/tsec.hpp> +#include <exosphere/se.hpp> +#include <exosphere/flow.hpp> +#include <exosphere/fuse.hpp> +#include <exosphere/i2c.hpp> +#include <exosphere/uart.hpp> +#include <exosphere/pinmux.hpp> +#include <exosphere/pmic.hpp> +#include <exosphere/pmic_setup.hpp> +#include <exosphere/rtc.hpp> +#include <exosphere/log.hpp> +#include <exosphere/clkrst.hpp> +#include <exosphere/actmon.hpp> +#include <exosphere/pmc.hpp> +#include <exosphere/secmon.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/actmon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/actmon.hpp new file mode 100644 index 00000000..e42989c9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/actmon.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::actmon { + + using InterruptHandler = void(*)(); + + void SetRegisterAddress(uintptr_t address); + + void HandleInterrupt(); + + void StartMonitoringBpmp(InterruptHandler handler); + void StopMonitoringBpmp(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/br.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/br.hpp new file mode 100644 index 00000000..de1f9a70 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/br.hpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +#include <exosphere/br/br_types.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/br/br_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/br/br_types.hpp new file mode 100644 index 00000000..8a119762 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/br/br_types.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +#include <exosphere/br/impl/br_erista_types.hpp> +#include <exosphere/br/impl/br_mariko_types.hpp> + +namespace ams::br { + + struct BootEcid { + u32 ecid[4]; + }; + static_assert(util::is_pod<BootEcid>::value); + static_assert(sizeof(BootEcid) == 0x10); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/br/impl/br_common_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/br/impl/br_common_types.hpp new file mode 100644 index 00000000..dcd3e413 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/br/impl/br_common_types.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::br { + + enum BootMemoryType : u32 { + BootMemoryType_None, + BootMemoryType_LpDdr2, + BootMemoryType_Ddr3, + BootMemoryType_LpDdr4, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/br/impl/br_erista_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/br/impl/br_erista_types.hpp new file mode 100644 index 00000000..c5055927 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/br/impl/br_erista_types.hpp @@ -0,0 +1,500 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <exosphere/br/impl/br_common_types.hpp> + +namespace ams::br::erista { + + struct BootSdramParams { + BootMemoryType MemoryType; + u32 PllMInputDivider; + u32 PllMFeedbackDivider; + u32 PllMStableTime; + u32 PllMSetupControl; + u32 PllMPostDivider; + u32 PllMKCP; + u32 PllMKVCO; + u32 EmcBctSpare0; + u32 EmcBctSpare1; + u32 EmcBctSpare2; + u32 EmcBctSpare3; + u32 EmcBctSpare4; + u32 EmcBctSpare5; + u32 EmcBctSpare6; + u32 EmcBctSpare7; + u32 EmcBctSpare8; + u32 EmcBctSpare9; + u32 EmcBctSpare10; + u32 EmcBctSpare11; + u32 EmcBctSpare12; + u32 EmcBctSpare13; + u32 EmcClockSource; + u32 EmcClockSourceDll; + u32 ClkRstControllerPllmMisc2Override; + u32 ClkRstControllerPllmMisc2OverrideEnable; + u32 ClearClk2Mc1; + u32 EmcAutoCalInterval; + u32 EmcAutoCalConfig; + u32 EmcAutoCalConfig2; + u32 EmcAutoCalConfig3; + u32 EmcAutoCalConfig4; + u32 EmcAutoCalConfig5; + u32 EmcAutoCalConfig6; + u32 EmcAutoCalConfig7; + u32 EmcAutoCalConfig8; + u32 EmcAutoCalVrefSel0; + u32 EmcAutoCalVrefSel1; + u32 EmcAutoCalChannel; + u32 EmcPmacroAutocalCfg0; + u32 EmcPmacroAutocalCfg1; + u32 EmcPmacroAutocalCfg2; + u32 EmcPmacroRxTerm; + u32 EmcPmacroDqTxDrv; + u32 EmcPmacroCaTxDrv; + u32 EmcPmacroCmdTxDrv; + u32 EmcPmacroAutocalCfgCommon; + u32 EmcPmacroZctrl; + u32 EmcAutoCalWait; + u32 EmcXm2CompPadCtrl; + u32 EmcXm2CompPadCtrl2; + u32 EmcXm2CompPadCtrl3; + u32 EmcAdrCfg; + u32 EmcPinProgramWait; + u32 EmcPinExtraWait; + u32 EmcPinGpioEn; + u32 EmcPinGpio; + u32 EmcTimingControlWait; + u32 EmcRc; + u32 EmcRfc; + u32 EmcRfcPb; + u32 EmcRefctrl2; + u32 EmcRfcSlr; + u32 EmcRas; + u32 EmcRp; + u32 EmcR2r; + u32 EmcW2w; + u32 EmcR2w; + u32 EmcW2r; + u32 EmcR2p; + u32 EmcW2p; + u32 EmcTppd; + u32 EmcCcdmw; + u32 EmcRdRcd; + u32 EmcWrRcd; + u32 EmcRrd; + u32 EmcRext; + u32 EmcWext; + u32 EmcWdv; + u32 EmcWdvChk; + u32 EmcWsv; + u32 EmcWev; + u32 EmcWdvMask; + u32 EmcWsDuration; + u32 EmcWeDuration; + u32 EmcQUse; + u32 EmcQuseWidth; + u32 EmcIbdly; + u32 EmcObdly; + u32 EmcEInput; + u32 EmcEInputDuration; + u32 EmcPutermExtra; + u32 EmcPutermWidth; + u32 EmcQRst; + u32 EmcQSafe; + u32 EmcRdv; + u32 EmcRdvMask; + u32 EmcRdvEarly; + u32 EmcRdvEarlyMask; + u32 EmcQpop; + u32 EmcRefresh; + u32 EmcBurstRefreshNum; + u32 EmcPreRefreshReqCnt; + u32 EmcPdEx2Wr; + u32 EmcPdEx2Rd; + u32 EmcPChg2Pden; + u32 EmcAct2Pden; + u32 EmcAr2Pden; + u32 EmcRw2Pden; + u32 EmcCke2Pden; + u32 EmcPdex2Cke; + u32 EmcPdex2Mrr; + u32 EmcTxsr; + u32 EmcTxsrDll; + u32 EmcTcke; + u32 EmcTckesr; + u32 EmcTpd; + u32 EmcTfaw; + u32 EmcTrpab; + u32 EmcTClkStable; + u32 EmcTClkStop; + u32 EmcTRefBw; + u32 EmcFbioCfg5; + u32 EmcFbioCfg7; + u32 EmcFbioCfg8; + u32 EmcCmdMappingCmd0_0; + u32 EmcCmdMappingCmd0_1; + u32 EmcCmdMappingCmd0_2; + u32 EmcCmdMappingCmd1_0; + u32 EmcCmdMappingCmd1_1; + u32 EmcCmdMappingCmd1_2; + u32 EmcCmdMappingCmd2_0; + u32 EmcCmdMappingCmd2_1; + u32 EmcCmdMappingCmd2_2; + u32 EmcCmdMappingCmd3_0; + u32 EmcCmdMappingCmd3_1; + u32 EmcCmdMappingCmd3_2; + u32 EmcCmdMappingByte; + u32 EmcFbioSpare; + u32 EmcCfgRsv; + u32 EmcMrs; + u32 EmcEmrs; + u32 EmcEmrs2; + u32 EmcEmrs3; + u32 EmcMrw1; + u32 EmcMrw2; + u32 EmcMrw3; + u32 EmcMrw4; + u32 EmcMrw6; + u32 EmcMrw8; + u32 EmcMrw9; + u32 EmcMrw10; + u32 EmcMrw12; + u32 EmcMrw13; + u32 EmcMrw14; + u32 EmcMrwExtra; + u32 EmcWarmBootMrwExtra; + u32 EmcWarmBootExtraModeRegWriteEnable; + u32 EmcExtraModeRegWriteEnable; + u32 EmcMrwResetCommand; + u32 EmcMrwResetNInitWait; + u32 EmcMrsWaitCnt; + u32 EmcMrsWaitCnt2; + u32 EmcCfg; + u32 EmcCfg2; + u32 EmcCfgPipe; + u32 EmcCfgPipeClk; + u32 EmcFdpdCtrlCmdNoRamp; + u32 EmcCfgUpdate; + u32 EmcDbg; + u32 EmcDbgWriteMux; + u32 EmcCmdQ; + u32 EmcMc2EmcQ; + u32 EmcDynSelfRefControl; + u32 AhbArbitrationXbarCtrlMemInitDone; + u32 EmcCfgDigDll; + u32 EmcCfgDigDll_1; + u32 EmcCfgDigDllPeriod; + u32 EmcDevSelect; + u32 EmcSelDpdCtrl; + u32 EmcFdpdCtrlDq; + u32 EmcFdpdCtrlCmd; + u32 EmcPmacroIbVrefDq_0; + u32 EmcPmacroIbVrefDq_1; + u32 EmcPmacroIbVrefDqs_0; + u32 EmcPmacroIbVrefDqs_1; + u32 EmcPmacroIbRxrt; + u32 EmcCfgPipe1; + u32 EmcCfgPipe2; + u32 EmcPmacroQuseDdllRank0_0; + u32 EmcPmacroQuseDdllRank0_1; + u32 EmcPmacroQuseDdllRank0_2; + u32 EmcPmacroQuseDdllRank0_3; + u32 EmcPmacroQuseDdllRank0_4; + u32 EmcPmacroQuseDdllRank0_5; + u32 EmcPmacroQuseDdllRank1_0; + u32 EmcPmacroQuseDdllRank1_1; + u32 EmcPmacroQuseDdllRank1_2; + u32 EmcPmacroQuseDdllRank1_3; + u32 EmcPmacroQuseDdllRank1_4; + u32 EmcPmacroQuseDdllRank1_5; + u32 EmcPmacroObDdllLongDqRank0_0; + u32 EmcPmacroObDdllLongDqRank0_1; + u32 EmcPmacroObDdllLongDqRank0_2; + u32 EmcPmacroObDdllLongDqRank0_3; + u32 EmcPmacroObDdllLongDqRank0_4; + u32 EmcPmacroObDdllLongDqRank0_5; + u32 EmcPmacroObDdllLongDqRank1_0; + u32 EmcPmacroObDdllLongDqRank1_1; + u32 EmcPmacroObDdllLongDqRank1_2; + u32 EmcPmacroObDdllLongDqRank1_3; + u32 EmcPmacroObDdllLongDqRank1_4; + u32 EmcPmacroObDdllLongDqRank1_5; + u32 EmcPmacroObDdllLongDqsRank0_0; + u32 EmcPmacroObDdllLongDqsRank0_1; + u32 EmcPmacroObDdllLongDqsRank0_2; + u32 EmcPmacroObDdllLongDqsRank0_3; + u32 EmcPmacroObDdllLongDqsRank0_4; + u32 EmcPmacroObDdllLongDqsRank0_5; + u32 EmcPmacroObDdllLongDqsRank1_0; + u32 EmcPmacroObDdllLongDqsRank1_1; + u32 EmcPmacroObDdllLongDqsRank1_2; + u32 EmcPmacroObDdllLongDqsRank1_3; + u32 EmcPmacroObDdllLongDqsRank1_4; + u32 EmcPmacroObDdllLongDqsRank1_5; + u32 EmcPmacroIbDdllLongDqsRank0_0; + u32 EmcPmacroIbDdllLongDqsRank0_1; + u32 EmcPmacroIbDdllLongDqsRank0_2; + u32 EmcPmacroIbDdllLongDqsRank0_3; + u32 EmcPmacroIbDdllLongDqsRank1_0; + u32 EmcPmacroIbDdllLongDqsRank1_1; + u32 EmcPmacroIbDdllLongDqsRank1_2; + u32 EmcPmacroIbDdllLongDqsRank1_3; + u32 EmcPmacroDdllLongCmd_0; + u32 EmcPmacroDdllLongCmd_1; + u32 EmcPmacroDdllLongCmd_2; + u32 EmcPmacroDdllLongCmd_3; + u32 EmcPmacroDdllLongCmd_4; + u32 EmcPmacroDdllShortCmd_0; + u32 EmcPmacroDdllShortCmd_1; + u32 EmcPmacroDdllShortCmd_2; + u32 WarmBootWait; + u32 EmcOdtWrite; + u32 EmcZcalInterval; + u32 EmcZcalWaitCnt; + u32 EmcZcalMrwCmd; + u32 EmcMrsResetDll; + u32 EmcZcalInitDev0; + u32 EmcZcalInitDev1; + u32 EmcZcalInitWait; + u32 EmcZcalWarmColdBootEnables; + u32 EmcMrwLpddr2ZcalWarmBoot; + u32 EmcZqCalDdr3WarmBoot; + u32 EmcZqCalLpDdr4WarmBoot; + u32 EmcZcalWarmBootWait; + u32 EmcMrsWarmBootEnable; + u32 EmcMrsResetDllWait; + u32 EmcMrsExtra; + u32 EmcWarmBootMrsExtra; + u32 EmcEmrsDdr2DllEnable; + u32 EmcMrsDdr2DllReset; + u32 EmcEmrsDdr2OcdCalib; + u32 EmcDdr2Wait; + u32 EmcClkenOverride; + u32 EmcExtraRefreshNum; + u32 EmcClkenOverrideAllWarmBoot; + u32 McClkenOverrideAllWarmBoot; + u32 EmcCfgDigDllPeriodWarmBoot; + u32 PmcVddpSel; + u32 PmcVddpSelWait; + u32 PmcDdrPwr; + u32 PmcDdrCfg; + u32 PmcIoDpd3Req; + u32 PmcIoDpd3ReqWait; + u32 PmcIoDpd4ReqWait; + u32 PmcRegShort; + u32 PmcNoIoPower; + u32 PmcDdrCntrlWait; + u32 PmcDdrCntrl; + u32 EmcAcpdControl; + u32 EmcSwizzleRank0Byte0; + u32 EmcSwizzleRank0Byte1; + u32 EmcSwizzleRank0Byte2; + u32 EmcSwizzleRank0Byte3; + u32 EmcSwizzleRank1Byte0; + u32 EmcSwizzleRank1Byte1; + u32 EmcSwizzleRank1Byte2; + u32 EmcSwizzleRank1Byte3; + u32 EmcTxdsrvttgen; + u32 EmcDataBrlshft0; + u32 EmcDataBrlshft1; + u32 EmcDqsBrlshft0; + u32 EmcDqsBrlshft1; + u32 EmcCmdBrlshft0; + u32 EmcCmdBrlshft1; + u32 EmcCmdBrlshft2; + u32 EmcCmdBrlshft3; + u32 EmcQuseBrlshft0; + u32 EmcQuseBrlshft1; + u32 EmcQuseBrlshft2; + u32 EmcQuseBrlshft3; + u32 EmcDllCfg0; + u32 EmcDllCfg1; + u32 EmcPmcScratch1; + u32 EmcPmcScratch2; + u32 EmcPmcScratch3; + u32 EmcPmacroPadCfgCtrl; + u32 EmcPmacroVttgenCtrl0; + u32 EmcPmacroVttgenCtrl1; + u32 EmcPmacroVttgenCtrl2; + u32 EmcPmacroBrickCtrlRfu1; + u32 EmcPmacroCmdBrickCtrlFdpd; + u32 EmcPmacroBrickCtrlRfu2; + u32 EmcPmacroDataBrickCtrlFdpd; + u32 EmcPmacroBgBiasCtrl0; + u32 EmcPmacroDataPadRxCtrl; + u32 EmcPmacroCmdPadRxCtrl; + u32 EmcPmacroDataRxTermMode; + u32 EmcPmacroCmdRxTermMode; + u32 EmcPmacroDataPadTxCtrl; + u32 EmcPmacroCommonPadTxCtrl; + u32 EmcPmacroCmdPadTxCtrl; + u32 EmcCfg3; + u32 EmcPmacroTxPwrd0; + u32 EmcPmacroTxPwrd1; + u32 EmcPmacroTxPwrd2; + u32 EmcPmacroTxPwrd3; + u32 EmcPmacroTxPwrd4; + u32 EmcPmacroTxPwrd5; + u32 EmcConfigSampleDelay; + u32 EmcPmacroBrickMapping0; + u32 EmcPmacroBrickMapping1; + u32 EmcPmacroBrickMapping2; + u32 EmcPmacroTxSelClkSrc0; + u32 EmcPmacroTxSelClkSrc1; + u32 EmcPmacroTxSelClkSrc2; + u32 EmcPmacroTxSelClkSrc3; + u32 EmcPmacroTxSelClkSrc4; + u32 EmcPmacroTxSelClkSrc5; + u32 EmcPmacroDdllBypass; + u32 EmcPmacroDdllPwrd0; + u32 EmcPmacroDdllPwrd1; + u32 EmcPmacroDdllPwrd2; + u32 EmcPmacroCmdCtrl0; + u32 EmcPmacroCmdCtrl1; + u32 EmcPmacroCmdCtrl2; + u32 McEmemAdrCfg; + u32 McEmemAdrCfgDev0; + u32 McEmemAdrCfgDev1; + u32 McEmemAdrCfgChannelMask; + u32 McEmemAdrCfgBankMask0; + u32 McEmemAdrCfgBankMask1; + u32 McEmemAdrCfgBankMask2; + u32 McEmemCfg; + u32 McEmemArbCfg; + u32 McEmemArbOutstandingReq; + u32 McEmemArbRefpbHpCtrl; + u32 McEmemArbRefpbBankCtrl; + u32 McEmemArbTimingRcd; + u32 McEmemArbTimingRp; + u32 McEmemArbTimingRc; + u32 McEmemArbTimingRas; + u32 McEmemArbTimingFaw; + u32 McEmemArbTimingRrd; + u32 McEmemArbTimingRap2Pre; + u32 McEmemArbTimingWap2Pre; + u32 McEmemArbTimingR2R; + u32 McEmemArbTimingW2W; + u32 McEmemArbTimingR2W; + u32 McEmemArbTimingW2R; + u32 McEmemArbTimingRFCPB; + u32 McEmemArbDaTurns; + u32 McEmemArbDaCovers; + u32 McEmemArbMisc0; + u32 McEmemArbMisc1; + u32 McEmemArbMisc2; + u32 McEmemArbRing1Throttle; + u32 McEmemArbOverride; + u32 McEmemArbOverride1; + u32 McEmemArbRsv; + u32 McDaCfg0; + u32 McEmemArbTimingCcdmw; + u32 McClkenOverride; + u32 McStatControl; + u32 McVideoProtectBom; + u32 McVideoProtectBomAdrHi; + u32 McVideoProtectSizeMb; + u32 McVideoProtectVprOverride; + u32 McVideoProtectVprOverride1; + u32 McVideoProtectGpuOverride0; + u32 McVideoProtectGpuOverride1; + u32 McSecCarveoutBom; + u32 McSecCarveoutAdrHi; + u32 McSecCarveoutSizeMb; + u32 McVideoProtectWriteAccess; + u32 McSecCarveoutProtectWriteAccess; + u32 McGeneralizedCarveout1Bom; + u32 McGeneralizedCarveout1BomHi; + u32 McGeneralizedCarveout1Size128kb; + u32 McGeneralizedCarveout1Access0; + u32 McGeneralizedCarveout1Access1; + u32 McGeneralizedCarveout1Access2; + u32 McGeneralizedCarveout1Access3; + u32 McGeneralizedCarveout1Access4; + u32 McGeneralizedCarveout1ForceInternalAccess0; + u32 McGeneralizedCarveout1ForceInternalAccess1; + u32 McGeneralizedCarveout1ForceInternalAccess2; + u32 McGeneralizedCarveout1ForceInternalAccess3; + u32 McGeneralizedCarveout1ForceInternalAccess4; + u32 McGeneralizedCarveout1Cfg0; + u32 McGeneralizedCarveout2Bom; + u32 McGeneralizedCarveout2BomHi; + u32 McGeneralizedCarveout2Size128kb; + u32 McGeneralizedCarveout2Access0; + u32 McGeneralizedCarveout2Access1; + u32 McGeneralizedCarveout2Access2; + u32 McGeneralizedCarveout2Access3; + u32 McGeneralizedCarveout2Access4; + u32 McGeneralizedCarveout2ForceInternalAccess0; + u32 McGeneralizedCarveout2ForceInternalAccess1; + u32 McGeneralizedCarveout2ForceInternalAccess2; + u32 McGeneralizedCarveout2ForceInternalAccess3; + u32 McGeneralizedCarveout2ForceInternalAccess4; + u32 McGeneralizedCarveout2Cfg0; + u32 McGeneralizedCarveout3Bom; + u32 McGeneralizedCarveout3BomHi; + u32 McGeneralizedCarveout3Size128kb; + u32 McGeneralizedCarveout3Access0; + u32 McGeneralizedCarveout3Access1; + u32 McGeneralizedCarveout3Access2; + u32 McGeneralizedCarveout3Access3; + u32 McGeneralizedCarveout3Access4; + u32 McGeneralizedCarveout3ForceInternalAccess0; + u32 McGeneralizedCarveout3ForceInternalAccess1; + u32 McGeneralizedCarveout3ForceInternalAccess2; + u32 McGeneralizedCarveout3ForceInternalAccess3; + u32 McGeneralizedCarveout3ForceInternalAccess4; + u32 McGeneralizedCarveout3Cfg0; + u32 McGeneralizedCarveout4Bom; + u32 McGeneralizedCarveout4BomHi; + u32 McGeneralizedCarveout4Size128kb; + u32 McGeneralizedCarveout4Access0; + u32 McGeneralizedCarveout4Access1; + u32 McGeneralizedCarveout4Access2; + u32 McGeneralizedCarveout4Access3; + u32 McGeneralizedCarveout4Access4; + u32 McGeneralizedCarveout4ForceInternalAccess0; + u32 McGeneralizedCarveout4ForceInternalAccess1; + u32 McGeneralizedCarveout4ForceInternalAccess2; + u32 McGeneralizedCarveout4ForceInternalAccess3; + u32 McGeneralizedCarveout4ForceInternalAccess4; + u32 McGeneralizedCarveout4Cfg0; + u32 McGeneralizedCarveout5Bom; + u32 McGeneralizedCarveout5BomHi; + u32 McGeneralizedCarveout5Size128kb; + u32 McGeneralizedCarveout5Access0; + u32 McGeneralizedCarveout5Access1; + u32 McGeneralizedCarveout5Access2; + u32 McGeneralizedCarveout5Access3; + u32 McGeneralizedCarveout5Access4; + u32 McGeneralizedCarveout5ForceInternalAccess0; + u32 McGeneralizedCarveout5ForceInternalAccess1; + u32 McGeneralizedCarveout5ForceInternalAccess2; + u32 McGeneralizedCarveout5ForceInternalAccess3; + u32 McGeneralizedCarveout5ForceInternalAccess4; + u32 McGeneralizedCarveout5Cfg0; + u32 EmcCaTrainingEnable; + u32 SwizzleRankByteEncode; + u32 BootRomPatchControl; + u32 BootRomPatchData; + u32 McMtsCarveoutBom; + u32 McMtsCarveoutAdrHi; + u32 McMtsCarveoutSizeMb; + u32 McMtsCarveoutRegCtrl; + }; + static_assert(sizeof(BootSdramParams) == 0x768); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/br/impl/br_mariko_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/br/impl/br_mariko_types.hpp new file mode 100644 index 00000000..c2128ed0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/br/impl/br_mariko_types.hpp @@ -0,0 +1,552 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <exosphere/br/impl/br_common_types.hpp> + +namespace ams::br::mariko { + + struct BootSdramParams { + BootMemoryType MemoryType; + u32 PllMInputDivider; + u32 PllMFeedbackDivider; + u32 PllMStableTime; + u32 PllMSetupControl; + u32 PllMPostDivider; + u32 PllMKCP; + u32 PllMKVCO; + u32 EmcBctSpare0; + u32 EmcBctSpare1; + u32 EmcBctSpare2; + u32 EmcBctSpare3; + u32 EmcBctSpare4; + u32 EmcBctSpare5; + u32 EmcBctSpare6; + u32 EmcBctSpare7; + u32 EmcBctSpare8; + u32 EmcBctSpare9; + u32 EmcBctSpare10; + u32 EmcBctSpare11; + u32 EmcBctSpare12; + u32 EmcBctSpare13; + u32 EmcBctSpareSecure0; + u32 EmcBctSpareSecure1; + u32 EmcBctSpareSecure2; + u32 EmcBctSpareSecure3; + u32 EmcBctSpareSecure4; + u32 EmcBctSpareSecure5; + u32 EmcBctSpareSecure6; + u32 EmcBctSpareSecure7; + u32 EmcBctSpareSecure8; + u32 EmcBctSpareSecure9; + u32 EmcBctSpareSecure10; + u32 EmcBctSpareSecure11; + u32 EmcBctSpareSecure12; + u32 EmcBctSpareSecure13; + u32 EmcBctSpareSecure14; + u32 EmcBctSpareSecure15; + u32 EmcBctSpareSecure16; + u32 EmcBctSpareSecure17; + u32 EmcBctSpareSecure18; + u32 EmcBctSpareSecure19; + u32 EmcBctSpareSecure20; + u32 EmcBctSpareSecure21; + u32 EmcBctSpareSecure22; + u32 EmcBctSpareSecure23; + u32 EmcClockSource; + u32 EmcClockSourceDll; + u32 ClkRstControllerPllmMisc2Override; + u32 ClkRstControllerPllmMisc2OverrideEnable; + u32 ClearClk2Mc1; + u32 EmcAutoCalInterval; + u32 EmcAutoCalConfig; + u32 EmcAutoCalConfig2; + u32 EmcAutoCalConfig3; + u32 EmcAutoCalConfig4; + u32 EmcAutoCalConfig5; + u32 EmcAutoCalConfig6; + u32 EmcAutoCalConfig7; + u32 EmcAutoCalConfig8; + u32 EmcAutoCalConfig9; + u32 EmcAutoCalVrefSel0; + u32 EmcAutoCalVrefSel1; + u32 EmcAutoCalChannel; + u32 EmcPmacroAutocalCfg0; + u32 EmcPmacroAutocalCfg1; + u32 EmcPmacroAutocalCfg2; + u32 EmcPmacroRxTerm; + u32 EmcPmacroDqTxDrv; + u32 EmcPmacroCaTxDrv; + u32 EmcPmacroCmdTxDrv; + u32 EmcPmacroAutocalCfgCommon; + u32 EmcPmacroZctrl; + u32 EmcAutoCalWait; + u32 EmcXm2CompPadCtrl; + u32 EmcXm2CompPadCtrl2; + u32 EmcXm2CompPadCtrl3; + u32 EmcAdrCfg; + u32 EmcPinProgramWait; + u32 EmcPinExtraWait; + u32 EmcPinGpioEn; + u32 EmcPinGpio; + u32 EmcTimingControlWait; + u32 EmcRc; + u32 EmcRfc; + u32 EmcRfcPb; + u32 EmcRefctrl2; + u32 EmcRfcSlr; + u32 EmcRas; + u32 EmcRp; + u32 EmcR2r; + u32 EmcW2w; + u32 EmcR2w; + u32 EmcW2r; + u32 EmcR2p; + u32 EmcW2p; + u32 EmcTppd; + u32 EmcTrtm; + u32 EmcTwtm; + u32 EmcTratm; + u32 EmcTwatm; + u32 EmcTr2ref; + u32 EmcCcdmw; + u32 EmcRdRcd; + u32 EmcWrRcd; + u32 EmcRrd; + u32 EmcRext; + u32 EmcWext; + u32 EmcWdv; + u32 EmcWdvChk; + u32 EmcWsv; + u32 EmcWev; + u32 EmcWdvMask; + u32 EmcWsDuration; + u32 EmcWeDuration; + u32 EmcQUse; + u32 EmcQuseWidth; + u32 EmcIbdly; + u32 EmcObdly; + u32 EmcEInput; + u32 EmcEInputDuration; + u32 EmcPutermExtra; + u32 EmcPutermWidth; + u32 EmcQRst; + u32 EmcQSafe; + u32 EmcRdv; + u32 EmcRdvMask; + u32 EmcRdvEarly; + u32 EmcRdvEarlyMask; + u32 EmcQpop; + u32 EmcRefresh; + u32 EmcBurstRefreshNum; + u32 EmcPreRefreshReqCnt; + u32 EmcPdEx2Wr; + u32 EmcPdEx2Rd; + u32 EmcPChg2Pden; + u32 EmcAct2Pden; + u32 EmcAr2Pden; + u32 EmcRw2Pden; + u32 EmcCke2Pden; + u32 EmcPdex2Cke; + u32 EmcPdex2Mrr; + u32 EmcTxsr; + u32 EmcTxsrDll; + u32 EmcTcke; + u32 EmcTckesr; + u32 EmcTpd; + u32 EmcTfaw; + u32 EmcTrpab; + u32 EmcTClkStable; + u32 EmcTClkStop; + u32 EmcTRefBw; + u32 EmcFbioCfg5; + u32 EmcFbioCfg7; + u32 EmcFbioCfg8; + u32 EmcCmdMappingCmd0_0; + u32 EmcCmdMappingCmd0_1; + u32 EmcCmdMappingCmd0_2; + u32 EmcCmdMappingCmd1_0; + u32 EmcCmdMappingCmd1_1; + u32 EmcCmdMappingCmd1_2; + u32 EmcCmdMappingCmd2_0; + u32 EmcCmdMappingCmd2_1; + u32 EmcCmdMappingCmd2_2; + u32 EmcCmdMappingCmd3_0; + u32 EmcCmdMappingCmd3_1; + u32 EmcCmdMappingCmd3_2; + u32 EmcCmdMappingByte; + u32 EmcFbioSpare; + u32 EmcCfgRsv; + u32 EmcMrs; + u32 EmcEmrs; + u32 EmcEmrs2; + u32 EmcEmrs3; + u32 EmcMrw1; + u32 EmcMrw2; + u32 EmcMrw3; + u32 EmcMrw4; + u32 EmcMrw6; + u32 EmcMrw8; + u32 EmcMrw9; + u32 EmcMrw10; + u32 EmcMrw12; + u32 EmcMrw13; + u32 EmcMrw14; + u32 EmcMrwExtra; + u32 EmcWarmBootMrwExtra; + u32 EmcWarmBootExtraModeRegWriteEnable; + u32 EmcExtraModeRegWriteEnable; + u32 EmcMrwResetCommand; + u32 EmcMrwResetNInitWait; + u32 EmcMrsWaitCnt; + u32 EmcMrsWaitCnt2; + u32 EmcCfg; + u32 EmcCfg2; + u32 EmcCfgPipe; + u32 EmcCfgPipeClk; + u32 EmcFdpdCtrlCmdNoRamp; + u32 EmcCfgUpdate; + u32 EmcDbg; + u32 EmcDbgWriteMux; + u32 EmcCmdQ; + u32 EmcMc2EmcQ; + u32 EmcDynSelfRefControl; + u32 AhbArbitrationXbarCtrlMemInitDone; + u32 EmcCfgDigDll; + u32 EmcCfgDigDll_1; + u32 EmcCfgDigDllPeriod; + u32 EmcDevSelect; + u32 EmcSelDpdCtrl; + u32 EmcFdpdCtrlDq; + u32 EmcFdpdCtrlCmd; + u32 EmcPmacroIbVrefDq_0; + u32 EmcPmacroIbVrefDq_1; + u32 EmcPmacroIbVrefDqs_0; + u32 EmcPmacroIbVrefDqs_1; + u32 EmcPmacroIbRxrt; + u32 EmcCfgPipe1; + u32 EmcCfgPipe2; + u32 EmcPmacroQuseDdllRank0_0; + u32 EmcPmacroQuseDdllRank0_1; + u32 EmcPmacroQuseDdllRank0_2; + u32 EmcPmacroQuseDdllRank0_3; + u32 EmcPmacroQuseDdllRank0_4; + u32 EmcPmacroQuseDdllRank0_5; + u32 EmcPmacroQuseDdllRank1_0; + u32 EmcPmacroQuseDdllRank1_1; + u32 EmcPmacroQuseDdllRank1_2; + u32 EmcPmacroQuseDdllRank1_3; + u32 EmcPmacroQuseDdllRank1_4; + u32 EmcPmacroQuseDdllRank1_5; + u32 EmcPmacroObDdllLongDqRank0_0; + u32 EmcPmacroObDdllLongDqRank0_1; + u32 EmcPmacroObDdllLongDqRank0_2; + u32 EmcPmacroObDdllLongDqRank0_3; + u32 EmcPmacroObDdllLongDqRank0_4; + u32 EmcPmacroObDdllLongDqRank0_5; + u32 EmcPmacroObDdllLongDqRank1_0; + u32 EmcPmacroObDdllLongDqRank1_1; + u32 EmcPmacroObDdllLongDqRank1_2; + u32 EmcPmacroObDdllLongDqRank1_3; + u32 EmcPmacroObDdllLongDqRank1_4; + u32 EmcPmacroObDdllLongDqRank1_5; + u32 EmcPmacroObDdllLongDqsRank0_0; + u32 EmcPmacroObDdllLongDqsRank0_1; + u32 EmcPmacroObDdllLongDqsRank0_2; + u32 EmcPmacroObDdllLongDqsRank0_3; + u32 EmcPmacroObDdllLongDqsRank0_4; + u32 EmcPmacroObDdllLongDqsRank0_5; + u32 EmcPmacroObDdllLongDqsRank1_0; + u32 EmcPmacroObDdllLongDqsRank1_1; + u32 EmcPmacroObDdllLongDqsRank1_2; + u32 EmcPmacroObDdllLongDqsRank1_3; + u32 EmcPmacroObDdllLongDqsRank1_4; + u32 EmcPmacroObDdllLongDqsRank1_5; + u32 EmcPmacroIbDdllLongDqsRank0_0; + u32 EmcPmacroIbDdllLongDqsRank0_1; + u32 EmcPmacroIbDdllLongDqsRank0_2; + u32 EmcPmacroIbDdllLongDqsRank0_3; + u32 EmcPmacroIbDdllLongDqsRank1_0; + u32 EmcPmacroIbDdllLongDqsRank1_1; + u32 EmcPmacroIbDdllLongDqsRank1_2; + u32 EmcPmacroIbDdllLongDqsRank1_3; + u32 EmcPmacroDdllLongCmd_0; + u32 EmcPmacroDdllLongCmd_1; + u32 EmcPmacroDdllLongCmd_2; + u32 EmcPmacroDdllLongCmd_3; + u32 EmcPmacroDdllLongCmd_4; + u32 EmcPmacroDdllShortCmd_0; + u32 EmcPmacroDdllShortCmd_1; + u32 EmcPmacroDdllShortCmd_2; + u32 EmcPmacroDdllPeriodicOffset; + u32 WarmBootWait; + u32 EmcOdtWrite; + u32 EmcZcalInterval; + u32 EmcZcalWaitCnt; + u32 EmcZcalMrwCmd; + u32 EmcMrsResetDll; + u32 EmcZcalInitDev0; + u32 EmcZcalInitDev1; + u32 EmcZcalInitWait; + u32 EmcZcalWarmColdBootEnables; + u32 EmcMrwLpddr2ZcalWarmBoot; + u32 EmcZqCalDdr3WarmBoot; + u32 EmcZqCalLpDdr4WarmBoot; + u32 EmcZcalWarmBootWait; + u32 EmcMrsWarmBootEnable; + u32 EmcMrsResetDllWait; + u32 EmcMrsExtra; + u32 EmcWarmBootMrsExtra; + u32 EmcEmrsDdr2DllEnable; + u32 EmcMrsDdr2DllReset; + u32 EmcEmrsDdr2OcdCalib; + u32 EmcDdr2Wait; + u32 EmcClkenOverride; + u32 EmcExtraRefreshNum; + u32 EmcClkenOverrideAllWarmBoot; + u32 McClkenOverrideAllWarmBoot; + u32 EmcCfgDigDllPeriodWarmBoot; + u32 PmcVddpSel; + u32 PmcVddpSelWait; + u32 PmcDdrCfg; + u32 PmcIoDpd3Req; + u32 PmcIoDpd3ReqWait; + u32 PmcIoDpd4ReqWait; + u32 PmcRegShort; + u32 PmcNoIoPower; + u32 PmcDdrCntrlWait; + u32 PmcDdrCntrl; + u32 EmcAcpdControl; + u32 EmcSwizzleRank0Byte0; + u32 EmcSwizzleRank0Byte1; + u32 EmcSwizzleRank0Byte2; + u32 EmcSwizzleRank0Byte3; + u32 EmcSwizzleRank1Byte0; + u32 EmcSwizzleRank1Byte1; + u32 EmcSwizzleRank1Byte2; + u32 EmcSwizzleRank1Byte3; + u32 EmcTxdsrvttgen; + u32 EmcDataBrlshft0; + u32 EmcDataBrlshft1; + u32 EmcDqsBrlshft0; + u32 EmcDqsBrlshft1; + u32 EmcCmdBrlshft0; + u32 EmcCmdBrlshft1; + u32 EmcCmdBrlshft2; + u32 EmcCmdBrlshft3; + u32 EmcQuseBrlshft0; + u32 EmcQuseBrlshft1; + u32 EmcQuseBrlshft2; + u32 EmcQuseBrlshft3; + u32 EmcPmacroDllCfg0; + u32 EmcPmacroDllCfg1; + u32 EmcPmcScratch1; + u32 EmcPmcScratch2; + u32 EmcPmcScratch3; + u32 EmcPmacroPadCfgCtrl; + u32 EmcPmacroVttgenCtrl0; + u32 EmcPmacroVttgenCtrl1; + u32 EmcPmacroVttgenCtrl2; + u32 EmcPmacroDsrVttgenCtrl0; + u32 EmcPmacroBrickCtrlRfu1; + u32 EmcPmacroCmdBrickCtrlFdpd; + u32 EmcPmacroBrickCtrlRfu2; + u32 EmcPmacroDataBrickCtrlFdpd; + u32 EmcPmacroBgBiasCtrl0; + u32 EmcPmacroDataPadRxCtrl; + u32 EmcPmacroCmdPadRxCtrl; + u32 EmcPmacroDataRxTermMode; + u32 EmcPmacroCmdRxTermMode; + u32 EmcPmacroDataPadTxCtrl; + u32 EmcPmacroCmdPadTxCtrl; + u32 EmcCfg3; + u32 EmcPmacroTxPwrd0; + u32 EmcPmacroTxPwrd1; + u32 EmcPmacroTxPwrd2; + u32 EmcPmacroTxPwrd3; + u32 EmcPmacroTxPwrd4; + u32 EmcPmacroTxPwrd5; + u32 EmcConfigSampleDelay; + u32 EmcPmacroBrickMapping0; + u32 EmcPmacroBrickMapping1; + u32 EmcPmacroBrickMapping2; + u32 EmcPmacroTxSelClkSrc0; + u32 EmcPmacroTxSelClkSrc1; + u32 EmcPmacroTxSelClkSrc2; + u32 EmcPmacroTxSelClkSrc3; + u32 EmcPmacroTxSelClkSrc4; + u32 EmcPmacroTxSelClkSrc5; + u32 EmcPmacroPerbitFgcgCtrl0; + u32 EmcPmacroPerbitFgcgCtrl1; + u32 EmcPmacroPerbitFgcgCtrl2; + u32 EmcPmacroPerbitFgcgCtrl3; + u32 EmcPmacroPerbitFgcgCtrl4; + u32 EmcPmacroPerbitFgcgCtrl5; + u32 EmcPmacroPerbitRfuCtrl0; + u32 EmcPmacroPerbitRfuCtrl1; + u32 EmcPmacroPerbitRfuCtrl2; + u32 EmcPmacroPerbitRfuCtrl3; + u32 EmcPmacroPerbitRfuCtrl4; + u32 EmcPmacroPerbitRfuCtrl5; + u32 EmcPmacroPerbitRfu1Ctrl0; + u32 EmcPmacroPerbitRfu1Ctrl1; + u32 EmcPmacroPerbitRfu1Ctrl2; + u32 EmcPmacroPerbitRfu1Ctrl3; + u32 EmcPmacroPerbitRfu1Ctrl4; + u32 EmcPmacroPerbitRfu1Ctrl5; + u32 EmcPmacroDataPiCtrl; + u32 EmcPmacroCmdPiCtrl; + u32 EmcPmacroDdllBypass; + u32 EmcPmacroDdllPwrd0; + u32 EmcPmacroDdllPwrd1; + u32 EmcPmacroDdllPwrd2; + u32 EmcPmacroCmdCtrl0; + u32 EmcPmacroCmdCtrl1; + u32 EmcPmacroCmdCtrl2; + u32 McEmemAdrCfg; + u32 McEmemAdrCfgDev0; + u32 McEmemAdrCfgDev1; + u32 McEmemAdrCfgChannelMask; + u32 McEmemAdrCfgBankMask0; + u32 McEmemAdrCfgBankMask1; + u32 McEmemAdrCfgBankMask2; + u32 McEmemCfg; + u32 McEmemArbCfg; + u32 McEmemArbOutstandingReq; + u32 McEmemArbRefpbHpCtrl; + u32 McEmemArbRefpbBankCtrl; + u32 McEmemArbTimingRcd; + u32 McEmemArbTimingRp; + u32 McEmemArbTimingRc; + u32 McEmemArbTimingRas; + u32 McEmemArbTimingFaw; + u32 McEmemArbTimingRrd; + u32 McEmemArbTimingRap2Pre; + u32 McEmemArbTimingWap2Pre; + u32 McEmemArbTimingR2R; + u32 McEmemArbTimingW2W; + u32 McEmemArbTimingR2W; + u32 McEmemArbTimingW2R; + u32 McEmemArbTimingRFCPB; + u32 McEmemArbDaTurns; + u32 McEmemArbDaCovers; + u32 McEmemArbMisc0; + u32 McEmemArbMisc1; + u32 McEmemArbMisc2; + u32 McEmemArbRing1Throttle; + u32 McEmemArbOverride; + u32 McEmemArbOverride1; + u32 McEmemArbRsv; + u32 McDaCfg0; + u32 McEmemArbTimingCcdmw; + u32 McClkenOverride; + u32 McStatControl; + u32 McVideoProtectBom; + u32 McVideoProtectBomAdrHi; + u32 McVideoProtectSizeMb; + u32 McVideoProtectVprOverride; + u32 McVideoProtectVprOverride1; + u32 McVideoProtectGpuOverride0; + u32 McVideoProtectGpuOverride1; + u32 McSecCarveoutBom; + u32 McSecCarveoutAdrHi; + u32 McSecCarveoutSizeMb; + u32 McVideoProtectWriteAccess; + u32 McSecCarveoutProtectWriteAccess; + u32 McGeneralizedCarveout1Bom; + u32 McGeneralizedCarveout1BomHi; + u32 McGeneralizedCarveout1Size128kb; + u32 McGeneralizedCarveout1Access0; + u32 McGeneralizedCarveout1Access1; + u32 McGeneralizedCarveout1Access2; + u32 McGeneralizedCarveout1Access3; + u32 McGeneralizedCarveout1Access4; + u32 McGeneralizedCarveout1ForceInternalAccess0; + u32 McGeneralizedCarveout1ForceInternalAccess1; + u32 McGeneralizedCarveout1ForceInternalAccess2; + u32 McGeneralizedCarveout1ForceInternalAccess3; + u32 McGeneralizedCarveout1ForceInternalAccess4; + u32 McGeneralizedCarveout1Cfg0; + u32 McGeneralizedCarveout2Bom; + u32 McGeneralizedCarveout2BomHi; + u32 McGeneralizedCarveout2Size128kb; + u32 McGeneralizedCarveout2Access0; + u32 McGeneralizedCarveout2Access1; + u32 McGeneralizedCarveout2Access2; + u32 McGeneralizedCarveout2Access3; + u32 McGeneralizedCarveout2Access4; + u32 McGeneralizedCarveout2ForceInternalAccess0; + u32 McGeneralizedCarveout2ForceInternalAccess1; + u32 McGeneralizedCarveout2ForceInternalAccess2; + u32 McGeneralizedCarveout2ForceInternalAccess3; + u32 McGeneralizedCarveout2ForceInternalAccess4; + u32 McGeneralizedCarveout2Cfg0; + u32 McGeneralizedCarveout3Bom; + u32 McGeneralizedCarveout3BomHi; + u32 McGeneralizedCarveout3Size128kb; + u32 McGeneralizedCarveout3Access0; + u32 McGeneralizedCarveout3Access1; + u32 McGeneralizedCarveout3Access2; + u32 McGeneralizedCarveout3Access3; + u32 McGeneralizedCarveout3Access4; + u32 McGeneralizedCarveout3ForceInternalAccess0; + u32 McGeneralizedCarveout3ForceInternalAccess1; + u32 McGeneralizedCarveout3ForceInternalAccess2; + u32 McGeneralizedCarveout3ForceInternalAccess3; + u32 McGeneralizedCarveout3ForceInternalAccess4; + u32 McGeneralizedCarveout3Cfg0; + u32 McGeneralizedCarveout4Bom; + u32 McGeneralizedCarveout4BomHi; + u32 McGeneralizedCarveout4Size128kb; + u32 McGeneralizedCarveout4Access0; + u32 McGeneralizedCarveout4Access1; + u32 McGeneralizedCarveout4Access2; + u32 McGeneralizedCarveout4Access3; + u32 McGeneralizedCarveout4Access4; + u32 McGeneralizedCarveout4ForceInternalAccess0; + u32 McGeneralizedCarveout4ForceInternalAccess1; + u32 McGeneralizedCarveout4ForceInternalAccess2; + u32 McGeneralizedCarveout4ForceInternalAccess3; + u32 McGeneralizedCarveout4ForceInternalAccess4; + u32 McGeneralizedCarveout4Cfg0; + u32 McGeneralizedCarveout5Bom; + u32 McGeneralizedCarveout5BomHi; + u32 McGeneralizedCarveout5Size128kb; + u32 McGeneralizedCarveout5Access0; + u32 McGeneralizedCarveout5Access1; + u32 McGeneralizedCarveout5Access2; + u32 McGeneralizedCarveout5Access3; + u32 McGeneralizedCarveout5Access4; + u32 McGeneralizedCarveout5ForceInternalAccess0; + u32 McGeneralizedCarveout5ForceInternalAccess1; + u32 McGeneralizedCarveout5ForceInternalAccess2; + u32 McGeneralizedCarveout5ForceInternalAccess3; + u32 McGeneralizedCarveout5ForceInternalAccess4; + u32 McGeneralizedCarveout5Cfg0; + u32 EmcCaTrainingEnable; + u32 SwizzleRankByteEncode; + u32 BootRomPatchControl; + u32 BootRomPatchData; + u32 McMtsCarveoutBom; + u32 McMtsCarveoutAdrHi; + u32 McMtsCarveoutSizeMb; + u32 McMtsCarveoutRegCtrl; + u32 McUntranslatedRegionCheck; + u32 BCT_NA; + }; + static_assert(sizeof(BootSdramParams) == 0x838); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/charger.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/charger.hpp new file mode 100644 index 00000000..bbaaeee3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/charger.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::charger { + + bool IsHiZMode(); + void EnterHiZMode(); + void ExitHiZMode(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/clkrst.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/clkrst.hpp new file mode 100644 index 00000000..98567b4f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/clkrst.hpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::clkrst { + + void SetRegisterAddress(uintptr_t address); + + void SetFuseVisibility(bool visible); + + void EnableUartAClock(); + void EnableUartBClock(); + void EnableUartCClock(); + void EnableActmonClock(); + void EnableI2c1Clock(); + void EnableI2c5Clock(); + + void EnableSeClock(); + void EnableCldvfsClock(); + void EnableCsiteClock(); + void EnableTzramClock(); + + void EnableCache2Clock(); + void EnableCram2Clock(); + + void EnableHost1xClock(); + void EnableTsecClock(); + void EnableSorSafeClock(); + void EnableSor0Clock(); + void EnableSor1Clock(); + void EnableKfuseClock(); + + void DisableI2c1Clock(); + + void DisableHost1xClock(); + void DisableTsecClock(); + void DisableSorSafeClock(); + void DisableSor0Clock(); + void DisableSor1Clock(); + void DisableKfuseClock(); + + + enum BpmpClockRate { + BpmpClockRate_408MHz, + BpmpClockRate_544MHz, + BpmpClockRate_576MHz, + BpmpClockRate_589MHz, + + BpmpClockRate_Count, + }; + + BpmpClockRate GetBpmpClockRate(); + BpmpClockRate SetBpmpClockRate(BpmpClockRate rate); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/common.hpp new file mode 100644 index 00000000..9187a705 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/common.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +#if 1 || defined(AMS_BUILD_FOR_AUDITING) +#define EXOSPHERE_BUILD_FOR_AUDITING +#endif + +#if defined(EXOSPHERE_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING) +#define EXOSPHERE_BUILD_FOR_DEBUGGING +#endif + +#ifdef EXOSPHERE_BUILD_FOR_DEBUGGING +#define EXOSPHERE_ENABLE_ASSERTIONS +#define EXOSPHERE_ENABLE_DEBUG_PRINT +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/diag/diag_detailed_assertion_impl.inc b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/diag/diag_detailed_assertion_impl.inc new file mode 100644 index 00000000..845d3575 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/diag/diag_detailed_assertion_impl.inc @@ -0,0 +1,119 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +void AbortImpl(const char *expr, const char *func, const char *file, int line) { + #if defined(AMS_ENABLE_DETAILED_ASSERTIONS) + { + AMS_LOG("Abort Called\n"); + AMS_LOG(" Location: %s:%d\n", file, line); + AMS_LOG(" Function: %s\n", func); + AMS_LOG(" Expression: %s\n", expr); + AMS_LOG("\n"); + } + #else + AMS_UNUSED(file, line, func, expr); + #endif + AbortImpl(); +} + +void AbortImpl(const char *expr, const char *func, const char *file, int line, const char *format, ...) { + #if defined(AMS_ENABLE_DETAILED_ASSERTIONS) + { + AMS_LOG("Abort Called\n"); + AMS_LOG(" Location: %s:%d\n", file, line); + AMS_LOG(" Function: %s\n", func); + AMS_LOG(" Expression: %s\n", expr); + AMS_LOG(" Value: %016" PRIx64 "\n", value); + AMS_LOG("\n"); + { + ::std::va_list vl; + va_start(vl, format); + AMS_VLOG(format, vl); + va_end(vl); + AMS_LOG("\n"); + } + } + #else + AMS_UNUSED(file, line, func, expr, format); + #endif + AbortImpl(); +} + +void AbortImpl(const char *expr, const char *func, const char *file, int line, const ::ams::Result *result, const char *format, ...) { + #if defined(AMS_ENABLE_DETAILED_ASSERTIONS) + { + AMS_LOG("Abort Called\n"); + AMS_LOG(" Location: %s:%d\n", file, line); + AMS_LOG(" Function: %s\n", func); + AMS_LOG(" Expression: %s\n", expr); + AMS_LOG(" Result: 0x%08" PRIx32 "\n", result->GetValue()); + AMS_LOG("\n"); + { + ::std::va_list vl; + va_start(vl, format); + AMS_VLOG(format, vl); + va_end(vl); + AMS_LOG("\n"); + } + } + #else + AMS_UNUSED(file, line, func, expr, result, format); + #endif + AbortImpl(); +} + +void OnAssertionFailure(AssertionType type, const char *expr, const char *func, const char *file, int line) { + #if defined(AMS_ENABLE_DETAILED_ASSERTIONS) + { + AMS_LOG("Assertion Failure\n"); + AMS_LOG(" Location: %s:%d\n", file, line); + AMS_LOG(" Function: %s\n", func); + AMS_LOG(" Expression: %s\n", expr); + AMS_LOG(" Value: %016" PRIx64 "\n", value); + AMS_LOG("\n"); + + AMS_UNUSED(type); + } + #else + AMS_UNUSED(type, expr, func, file, line); + #endif + AbortImpl(); +} + +void OnAssertionFailure(AssertionType type, const char *expr, const char *func, const char *file, int line, const char *format, ...) { + #if defined(AMS_ENABLE_DETAILED_ASSERTIONS) + { + AMS_LOG("Assertion Failure\n"); + AMS_LOG(" Location: %s:%d\n", file, line); + AMS_LOG(" Function: %s\n", func); + AMS_LOG(" Expression: %s\n", expr); + AMS_LOG(" Value: %016" PRIx64 "\n", value); + AMS_LOG("\n"); + { + ::std::va_list vl; + va_start(vl, format); + AMS_VLOG(format, vl); + va_end(vl); + AMS_LOG("\n"); + } + + AMS_UNUSED(type); + } + #else + AMS_UNUSED(type, expr, func, file, line, format); + #endif + AbortImpl(); +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/flow.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/flow.hpp new file mode 100644 index 00000000..2d6c2094 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/flow.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::flow { + + void SetRegisterAddress(uintptr_t address); + + void ResetCpuRegisters(int core); + + void SetCpuCsr(int core, u32 enable_ext); + void SetHaltCpuEvents(int core, bool resume_on_irq); + void SetCc4Ctrl(int core, u32 value); + void ClearL2FlushControl(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/fuse.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/fuse.hpp new file mode 100644 index 00000000..34d15fa1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/fuse.hpp @@ -0,0 +1,128 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <exosphere/br.hpp> +#include <exosphere/pmic.hpp> + +namespace ams::fuse { + + enum HardwareType { + HardwareType_Icosa = 0, + HardwareType_Copper = 1, + HardwareType_Hoag = 2, + HardwareType_Iowa = 3, + HardwareType_Calcio = 4, + HardwareType_Aula = 5, + + HardwareType_Undefined = 0xF, + }; + + enum SocType { + SocType_Erista = 0, + SocType_Mariko = 1, + SocType_Undefined = 0xF, + }; + + enum HardwareState { + HardwareState_Development = 0, + HardwareState_Production = 1, + HardwareState_Undefined = 2, + }; + + enum PatchVersion { + PatchVersion_Odnx02A2 = (SocType_Erista << 12) | 0x07F, + }; + + enum DramId { + DramId_IcosaSamsung4GB = 0, + DramId_IcosaHynix4GB = 1, + DramId_IcosaMicron4GB = 2, + DramId_IowaHynix1y4GB = 3, + DramId_IcosaSamsung6GB = 4, + DramId_HoagHynix1y4GB = 5, + DramId_AulaHynix1y4GB = 6, + DramId_Deprecated7 = 7, + DramId_IowaSansung4GB = 8, + DramId_IowaSamsung8GB = 9, + DramId_IowaHynix4GB = 10, + DramId_IowaMicron4GB = 11, + DramId_HoagSamsung4GB = 12, + DramId_HoagSamsung8GB = 13, + DramId_HoagHynix4GB = 14, + DramId_HoagMicron4GB = 15, + DramId_Deprecated16 = 16, + DramId_IowaSamsung1y4GBX = 17, + DramId_IowaSamsung1y8GBX = 18, + DramId_HoagSamsung1y4GBX = 19, + DramId_IowaSamsung1z4GB = 20, + DramId_HoagSamsung1z4GB = 21, + DramId_AulaSamsung1z4GB = 22, + DramId_HoagSamsung1y8GBX = 23, + DramId_AulaSamsung1y4GBX = 24, + DramId_IowaMicron1y4GB = 25, + DramId_HoagMicron1y4GB = 26, + DramId_AulaMicron1y4GB = 27, + DramId_AulaSamsung1y8GBX = 28, + DramId_IowaX1X2Samsung4GB = 29, + DramId_HoagX1X2Samsung4GB = 30, + DramId_AulaX1X2Samsung4GB = 31, + DramId_IowaSamsung4GBY = 32, + DramId_HoagSamsung4GBY = 33, + DramId_AulaSamsung4GBY = 34, + + DramId_Count, + }; + + enum RetailInteractiveDisplayState { + RetailInteractiveDisplayState_Disabled = 0, + RetailInteractiveDisplayState_Enabled = 1, + }; + + void SetRegisterAddress(uintptr_t address); + void SetWriteSecureOnly(); + void Lockout(); + + void Activate(); + void Deactivate(); + void Reload(); + + u32 ReadWord(int address); + + u32 GetOdmWord(int index); + + DramId GetDramId(); + + bool GetSecureBootKey(void *dst); + + void GetEcid(br::BootEcid *out); + HardwareType GetHardwareType(); + HardwareState GetHardwareState(); + u64 GetDeviceId(); + PatchVersion GetPatchVersion(); + RetailInteractiveDisplayState GetRetailInteractiveDisplayState(); + pmic::Regulator GetRegulator(); + int GetDeviceUniqueKeyGeneration(); + + SocType GetSocType(); + int GetExpectedFuseVersion(TargetFirmware target_fw); + int GetFuseVersion(); + bool HasRcmVulnerabilityPatch(); + + bool IsOdmProductionMode(); + void ConfigureFuseBypass(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/gic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/gic.hpp new file mode 100644 index 00000000..7bbd895a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/gic.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::gic { + + enum InterruptMode { + InterruptMode_Level = 0, + InterruptMode_Edge = 1, + }; + + constexpr inline s32 HighestPriority = 0; + constexpr inline s32 InterruptCount = 224; + + void SetRegisterAddress(uintptr_t distributor_address, uintptr_t cpu_interface_address); + void InitializeCommon(); + void InitializeCoreUnique(); + + void SetPriority(int interrupt_id, int priority); + void SetInterruptGroup(int interrupt_id, int group); + void SetEnable(int interrupt_id, bool enable); + void SetSpiTargetCpu(int interrupt_id, u32 cpu_mask); + void SetSpiMode(int interrupt_id, InterruptMode mode); + + void SetPending(int interrupt_id); + + int GetInterruptRequestId(); + void SetEndOfInterrupt(int interrupt_id); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/hw.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/hw.hpp new file mode 100644 index 00000000..1ccd0ca8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/hw.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +#if defined(ATMOSPHERE_ARCH_ARM64) + #include <exosphere/hw/hw_arm64.hpp> + namespace ams::hw { using namespace ams::hw::arch::arm64; } +#elif defined(ATMOSPHERE_ARCH_ARM) + #include <exosphere/hw/hw_arm.hpp> + namespace ams::hw { using namespace ams::hw::arch::arm; } +#else + #error "Unknown architecture for hw!" +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/hw/hw_arm.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/hw/hw_arm.hpp new file mode 100644 index 00000000..888a9394 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/hw/hw_arm.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::hw::arch::arm { + +#ifdef __BPMP__ + constexpr inline size_t DataCacheLineSize = 0x20; + constexpr inline size_t DataCacheSize = 32_KB; + + ALWAYS_INLINE void DataSynchronizationBarrier() { + /* ... */ + } + + ALWAYS_INLINE void DataSynchronizationBarrierInnerShareable() { + /* ... */ + } + + ALWAYS_INLINE void DataMemoryBarrier() { + /* ... */ + } + + ALWAYS_INLINE void InstructionSynchronizationBarrier() { + /* ... */ + } + + void InitializeDataCache(); + void FinalizeDataCache(); + + void InvalidateEntireDataCache(); + void StoreEntireDataCache(); + void FlushEntireDataCache(); + + void InvalidateDataCacheLine(void *ptr); + void StoreDataCacheLine(void *ptr); + void FlushDataCacheLine(void *ptr); + + void InvalidateDataCache(void *ptr, size_t size); + void StoreDataCache(const void *ptr, size_t size); + void FlushDataCache(const void *ptr, size_t size); +#else + #error "Unknown ARM board for ams::hw" +#endif + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/hw/hw_arm64.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/hw/hw_arm64.hpp new file mode 100644 index 00000000..b9b99e58 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/hw/hw_arm64.hpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <exosphere/hw/hw_arm64_system_registers.hpp> +#include <exosphere/hw/hw_arm64_cache.hpp> + +namespace ams::hw::arch::arm64 { + + ALWAYS_INLINE void DataSynchronizationBarrier() { + __asm__ __volatile__("dsb sy" ::: "memory"); + } + + ALWAYS_INLINE void DataSynchronizationBarrierInnerShareable() { + __asm__ __volatile__("dsb ish" ::: "memory"); + } + + ALWAYS_INLINE void DataMemoryBarrier() { + __asm__ __volatile__("dmb sy" ::: "memory"); + } + + ALWAYS_INLINE void InstructionSynchronizationBarrier() { + __asm__ __volatile__("isb" ::: "memory"); + } + + ALWAYS_INLINE void WaitForInterrupt() { + __asm__ __volatile__("wfi" ::: "memory"); + } + + ALWAYS_INLINE void WaitForEvent() { + __asm__ __volatile__("wfe" ::: "memory"); + } + + ALWAYS_INLINE void SendEvent() { + __asm__ __volatile__("sev" ::: "memory"); + } + + ALWAYS_INLINE int CountLeadingZeros(u64 v) { + u64 z; + __asm__ __volatile__("clz %[z], %[v]" : [z]"=r"(z) : [v]"r"(v)); + return z; + } + + ALWAYS_INLINE int CountLeadingZeros(u32 v) { + u32 z; + __asm__ __volatile__("clz %w[z], %w[v]" : [z]"=r"(z) : [v]"r"(v)); + return z; + } + + ALWAYS_INLINE int GetCurrentCoreId() { + u64 mpidr; + HW_CPU_GET_MPIDR_EL1(mpidr); + return mpidr & 0xFF; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/hw/hw_arm64_cache.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/hw/hw_arm64_cache.hpp new file mode 100644 index 00000000..96703f93 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/hw/hw_arm64_cache.hpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::hw::arch::arm64 { + +#if defined(ATMOSPHERE_CPU_ARM_CORTEX_A57) || defined(ATMOSPHERE_CPU_ARM_CORTEX_A53) + constexpr inline size_t InstructionCacheLineSize = 0x40; + constexpr inline size_t DataCacheLineSize = 0x40; + constexpr inline size_t NumPerformanceCounters = 6; +#else + #error "Unknown CPU for cache line sizes" +#endif + + ALWAYS_INLINE void InvalidateEntireTlb() { + __asm__ __volatile__("tlbi alle3is" ::: "memory"); + } + + ALWAYS_INLINE void InvalidateDataCacheLine(void *ptr) { + __asm __volatile__("dc ivac, %[ptr]" :: [ptr]"r"(ptr) : "memory"); + } + + ALWAYS_INLINE void FlushDataCacheLine(void *ptr) { + __asm __volatile__("dc civac, %[ptr]" :: [ptr]"r"(ptr) : "memory"); + } + + ALWAYS_INLINE void InvalidateEntireInstructionCache() { + __asm__ __volatile__("ic iallu" ::: "memory"); + } + + ALWAYS_INLINE void InvalidateTlb(uintptr_t address) { + const uintptr_t page_index = address / 4_KB; + __asm__ __volatile__("tlbi vae3is, %[page_index]" :: [page_index]"r"(page_index) : "memory"); + } + + ALWAYS_INLINE void InvalidateTlbLastLevel(uintptr_t address) { + const uintptr_t page_index = address / 4_KB; + __asm__ __volatile__("tlbi vale3is, %[page_index]" :: [page_index]"r"(page_index) : "memory"); + } + + void FlushDataCache(const void *ptr, size_t size); + void InvalidateDataCache(const void *ptr, size_t size); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/hw/hw_arm64_system_registers.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/hw/hw_arm64_system_registers.hpp new file mode 100644 index 00000000..8ea91328 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/hw/hw_arm64_system_registers.hpp @@ -0,0 +1,348 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::hw::arch::arm64 { + + #define HW_CPU_GET_SYSREG(name, value) __asm__ __volatile__("mrs %0, " #name "" : "=&r"(value) :: "memory"); + + #define HW_CPU_SET_SYSREG(name, value) __asm__ __volatile__("msr " #name ", %0" :: "r"(value) : "memory", "cc") + + #define HW_CPU_GET_SCTLR_EL3(value) HW_CPU_GET_SYSREG(sctlr_el3, value) + #define HW_CPU_SET_SCTLR_EL3(value) HW_CPU_SET_SYSREG(sctlr_el3, value) + + #define HW_CPU_GET_SCR_EL3(value) HW_CPU_GET_SYSREG(scr_el3, value) + #define HW_CPU_SET_SCR_EL3(value) HW_CPU_SET_SYSREG(scr_el3, value) + + #define HW_CPU_GET_CPTR_EL3(value) HW_CPU_GET_SYSREG(cptr_el3, value) + #define HW_CPU_SET_CPTR_EL3(value) HW_CPU_SET_SYSREG(cptr_el3, value) + + #define HW_CPU_GET_TTBR0_EL3(value) HW_CPU_GET_SYSREG(ttbr0_el3, value) + #define HW_CPU_SET_TTBR0_EL3(value) HW_CPU_SET_SYSREG(ttbr0_el3, value) + + #define HW_CPU_GET_TCR_EL3(value) HW_CPU_GET_SYSREG(tcr_el3, value) + #define HW_CPU_SET_TCR_EL3(value) HW_CPU_SET_SYSREG(tcr_el3, value) + + #define HW_CPU_GET_MAIR_EL3(value) HW_CPU_GET_SYSREG(mair_el3, value) + #define HW_CPU_SET_MAIR_EL3(value) HW_CPU_SET_SYSREG(mair_el3, value) + + #define HW_CPU_GET_VBAR_EL3(value) HW_CPU_GET_SYSREG(vbar_el3, value) + #define HW_CPU_SET_VBAR_EL3(value) HW_CPU_SET_SYSREG(vbar_el3, value) + + #define HW_CPU_GET_ELR_EL3(value) HW_CPU_GET_SYSREG(elr_el3, value) + #define HW_CPU_SET_ELR_EL3(value) HW_CPU_SET_SYSREG(elr_el3, value) + + #define HW_CPU_GET_FAR_EL3(value) HW_CPU_GET_SYSREG(far_el3, value) + #define HW_CPU_SET_FAR_EL3(value) HW_CPU_SET_SYSREG(far_el3, value) + + #define HW_CPU_GET_ESR_EL3(value) HW_CPU_GET_SYSREG(esr_el3, value) + #define HW_CPU_SET_ESR_EL3(value) HW_CPU_SET_SYSREG(esr_el3, value) + + #define HW_CPU_GET_CLIDR_EL1(value) HW_CPU_GET_SYSREG(clidr_el1, value) + #define HW_CPU_SET_CLIDR_EL1(value) HW_CPU_SET_SYSREG(clidr_el1, value) + + #define HW_CPU_GET_CCSIDR_EL1(value) HW_CPU_GET_SYSREG(ccsidr_el1, value) + #define HW_CPU_SET_CCSIDR_EL1(value) HW_CPU_SET_SYSREG(ccsidr_el1, value) + + #define HW_CPU_GET_CSSELR_EL1(value) HW_CPU_GET_SYSREG(csselr_el1, value) + #define HW_CPU_SET_CSSELR_EL1(value) HW_CPU_SET_SYSREG(csselr_el1, value) + + #define HW_CPU_GET_CPUACTLR_EL1(value) HW_CPU_GET_SYSREG(s3_1_c15_c2_0, value) + #define HW_CPU_SET_CPUACTLR_EL1(value) HW_CPU_SET_SYSREG(s3_1_c15_c2_0, value) + + #define HW_CPU_GET_CPUECTLR_EL1(value) HW_CPU_GET_SYSREG(s3_1_c15_c2_1, value) + #define HW_CPU_SET_CPUECTLR_EL1(value) HW_CPU_SET_SYSREG(s3_1_c15_c2_1, value) + + #define HW_CPU_GET_DBGAUTHSTATUS_EL1(value) HW_CPU_GET_SYSREG(dbgauthstatus_el1, value) + #define HW_CPU_SET_DBGAUTHSTATUS_EL1(value) HW_CPU_SET_SYSREG(dbgauthstatus_el1, value) + + #define HW_CPU_GET_MPIDR_EL1(value) HW_CPU_GET_SYSREG(mpidr_el1, value) + + #define HW_CPU_GET_OSLAR_EL1(value) HW_CPU_GET_SYSREG(oslar_el1, value) + #define HW_CPU_SET_OSLAR_EL1(value) HW_CPU_SET_SYSREG(oslar_el1, value) + + #define HW_CPU_GET_OSDTRRX_EL1(value) HW_CPU_GET_SYSREG(osdtrrx_el1, value) + #define HW_CPU_SET_OSDTRRX_EL1(value) HW_CPU_SET_SYSREG(osdtrrx_el1, value) + + #define HW_CPU_GET_OSDTRTX_EL1(value) HW_CPU_GET_SYSREG(osdtrtx_el1, value) + #define HW_CPU_SET_OSDTRTX_EL1(value) HW_CPU_SET_SYSREG(osdtrtx_el1, value) + + #define HW_CPU_GET_MDSCR_EL1(value) HW_CPU_GET_SYSREG(mdscr_el1, value) + #define HW_CPU_SET_MDSCR_EL1(value) HW_CPU_SET_SYSREG(mdscr_el1, value) + + #define HW_CPU_GET_OSECCR_EL1(value) HW_CPU_GET_SYSREG(oseccr_el1, value) + #define HW_CPU_SET_OSECCR_EL1(value) HW_CPU_SET_SYSREG(oseccr_el1, value) + + #define HW_CPU_GET_MDCCINT_EL1(value) HW_CPU_GET_SYSREG(mdccint_el1, value) + #define HW_CPU_SET_MDCCINT_EL1(value) HW_CPU_SET_SYSREG(mdccint_el1, value) + + #define HW_CPU_GET_DBGCLAIMCLR_EL1(value) HW_CPU_GET_SYSREG(dbgclaimclr_el1, value) + #define HW_CPU_SET_DBGCLAIMCLR_EL1(value) HW_CPU_SET_SYSREG(dbgclaimclr_el1, value) + + #define HW_CPU_GET_FAR_EL1(value) HW_CPU_GET_SYSREG(far_el1, value) + #define HW_CPU_SET_FAR_EL1(value) HW_CPU_SET_SYSREG(far_el1, value) + + #define HW_CPU_GET_DBGVCR32_EL2(value) HW_CPU_GET_SYSREG(dbgvcr32_el2, value) + #define HW_CPU_SET_DBGVCR32_EL2(value) HW_CPU_SET_SYSREG(dbgvcr32_el2, value) + + #define HW_CPU_GET_SDER32_EL3(value) HW_CPU_GET_SYSREG(sder32_el3, value) + #define HW_CPU_SET_SDER32_EL3(value) HW_CPU_SET_SYSREG(sder32_el3, value) + + #define HW_CPU_GET_MDCR_EL2(value) HW_CPU_GET_SYSREG(mdcr_el2, value) + #define HW_CPU_SET_MDCR_EL2(value) HW_CPU_SET_SYSREG(mdcr_el2, value) + + #define HW_CPU_GET_MDCR_EL3(value) HW_CPU_GET_SYSREG(mdcr_el3, value) + #define HW_CPU_SET_MDCR_EL3(value) HW_CPU_SET_SYSREG(mdcr_el3, value) + + #define HW_CPU_GET_SPSR_EL3(value) HW_CPU_GET_SYSREG(spsr_el3, value) + #define HW_CPU_SET_SPSR_EL3(value) HW_CPU_SET_SYSREG(spsr_el3, value) + + #define HW_CPU_GET_DBGBVR0_EL1(value) HW_CPU_GET_SYSREG(dbgbvr0_el1, value) + #define HW_CPU_SET_DBGBVR0_EL1(value) HW_CPU_SET_SYSREG(dbgbvr0_el1, value) + #define HW_CPU_GET_DBGBCR0_EL1(value) HW_CPU_GET_SYSREG(dbgbcr0_el1, value) + #define HW_CPU_SET_DBGBCR0_EL1(value) HW_CPU_SET_SYSREG(dbgbcr0_el1, value) + #define HW_CPU_GET_DBGBVR1_EL1(value) HW_CPU_GET_SYSREG(dbgbvr1_el1, value) + #define HW_CPU_SET_DBGBVR1_EL1(value) HW_CPU_SET_SYSREG(dbgbvr1_el1, value) + #define HW_CPU_GET_DBGBCR1_EL1(value) HW_CPU_GET_SYSREG(dbgbcr1_el1, value) + #define HW_CPU_SET_DBGBCR1_EL1(value) HW_CPU_SET_SYSREG(dbgbcr1_el1, value) + #define HW_CPU_GET_DBGBVR2_EL1(value) HW_CPU_GET_SYSREG(dbgbvr2_el1, value) + #define HW_CPU_SET_DBGBVR2_EL1(value) HW_CPU_SET_SYSREG(dbgbvr2_el1, value) + #define HW_CPU_GET_DBGBCR2_EL1(value) HW_CPU_GET_SYSREG(dbgbcr2_el1, value) + #define HW_CPU_SET_DBGBCR2_EL1(value) HW_CPU_SET_SYSREG(dbgbcr2_el1, value) + #define HW_CPU_GET_DBGBVR3_EL1(value) HW_CPU_GET_SYSREG(dbgbvr3_el1, value) + #define HW_CPU_SET_DBGBVR3_EL1(value) HW_CPU_SET_SYSREG(dbgbvr3_el1, value) + #define HW_CPU_GET_DBGBCR3_EL1(value) HW_CPU_GET_SYSREG(dbgbcr3_el1, value) + #define HW_CPU_SET_DBGBCR3_EL1(value) HW_CPU_SET_SYSREG(dbgbcr3_el1, value) + #define HW_CPU_GET_DBGBVR4_EL1(value) HW_CPU_GET_SYSREG(dbgbvr4_el1, value) + #define HW_CPU_SET_DBGBVR4_EL1(value) HW_CPU_SET_SYSREG(dbgbvr4_el1, value) + #define HW_CPU_GET_DBGBCR4_EL1(value) HW_CPU_GET_SYSREG(dbgbcr4_el1, value) + #define HW_CPU_SET_DBGBCR4_EL1(value) HW_CPU_SET_SYSREG(dbgbcr4_el1, value) + #define HW_CPU_GET_DBGBVR5_EL1(value) HW_CPU_GET_SYSREG(dbgbvr5_el1, value) + #define HW_CPU_SET_DBGBVR5_EL1(value) HW_CPU_SET_SYSREG(dbgbvr5_el1, value) + #define HW_CPU_GET_DBGBCR5_EL1(value) HW_CPU_GET_SYSREG(dbgbcr5_el1, value) + #define HW_CPU_SET_DBGBCR5_EL1(value) HW_CPU_SET_SYSREG(dbgbcr5_el1, value) + + #define HW_CPU_GET_DBGWVR0_EL1(value) HW_CPU_GET_SYSREG(dbgwvr0_el1, value) + #define HW_CPU_SET_DBGWVR0_EL1(value) HW_CPU_SET_SYSREG(dbgwvr0_el1, value) + #define HW_CPU_GET_DBGWCR0_EL1(value) HW_CPU_GET_SYSREG(dbgwcr0_el1, value) + #define HW_CPU_SET_DBGWCR0_EL1(value) HW_CPU_SET_SYSREG(dbgwcr0_el1, value) + #define HW_CPU_GET_DBGWVR1_EL1(value) HW_CPU_GET_SYSREG(dbgwvr1_el1, value) + #define HW_CPU_SET_DBGWVR1_EL1(value) HW_CPU_SET_SYSREG(dbgwvr1_el1, value) + #define HW_CPU_GET_DBGWCR1_EL1(value) HW_CPU_GET_SYSREG(dbgwcr1_el1, value) + #define HW_CPU_SET_DBGWCR1_EL1(value) HW_CPU_SET_SYSREG(dbgwcr1_el1, value) + #define HW_CPU_GET_DBGWVR2_EL1(value) HW_CPU_GET_SYSREG(dbgwvr2_el1, value) + #define HW_CPU_SET_DBGWVR2_EL1(value) HW_CPU_SET_SYSREG(dbgwvr2_el1, value) + #define HW_CPU_GET_DBGWCR2_EL1(value) HW_CPU_GET_SYSREG(dbgwcr2_el1, value) + #define HW_CPU_SET_DBGWCR2_EL1(value) HW_CPU_SET_SYSREG(dbgwcr2_el1, value) + #define HW_CPU_GET_DBGWVR3_EL1(value) HW_CPU_GET_SYSREG(dbgwvr3_el1, value) + #define HW_CPU_SET_DBGWVR3_EL1(value) HW_CPU_SET_SYSREG(dbgwvr3_el1, value) + #define HW_CPU_GET_DBGWCR3_EL1(value) HW_CPU_GET_SYSREG(dbgwcr3_el1, value) + #define HW_CPU_SET_DBGWCR3_EL1(value) HW_CPU_SET_SYSREG(dbgwcr3_el1, value) + + #define HW_CPU_GET_CNTFRQ_EL0(value) HW_CPU_GET_SYSREG(cntfrq_el0, value) + #define HW_CPU_SET_CNTFRQ_EL0(value) HW_CPU_SET_SYSREG(cntfrq_el0, value) + + #define HW_CPU_GET_CNTHCTL_EL2(value) HW_CPU_GET_SYSREG(cnthctl_el2, value) + #define HW_CPU_SET_CNTHCTL_EL2(value) HW_CPU_SET_SYSREG(cnthctl_el2, value) + + #define HW_CPU_GET_ACTLR_EL2(value) HW_CPU_GET_SYSREG(actlr_el2, value) + #define HW_CPU_SET_ACTLR_EL2(value) HW_CPU_SET_SYSREG(actlr_el2, value) + + #define HW_CPU_GET_ACTLR_EL3(value) HW_CPU_GET_SYSREG(actlr_el3, value) + #define HW_CPU_SET_ACTLR_EL3(value) HW_CPU_SET_SYSREG(actlr_el3, value) + + #define HW_CPU_GET_HCR_EL2(value) HW_CPU_GET_SYSREG(hcr_el2, value) + #define HW_CPU_SET_HCR_EL2(value) HW_CPU_SET_SYSREG(hcr_el2, value) + + #define HW_CPU_GET_DACR32_EL2(value) HW_CPU_GET_SYSREG(dacr32_el2, value) + #define HW_CPU_SET_DACR32_EL2(value) HW_CPU_SET_SYSREG(dacr32_el2, value) + + #define HW_CPU_GET_SCTLR_EL2(value) HW_CPU_GET_SYSREG(sctlr_el2, value) + #define HW_CPU_SET_SCTLR_EL2(value) HW_CPU_SET_SYSREG(sctlr_el2, value) + + #define HW_CPU_GET_SCTLR_EL1(value) HW_CPU_GET_SYSREG(sctlr_el1, value) + #define HW_CPU_SET_SCTLR_EL1(value) HW_CPU_SET_SYSREG(sctlr_el1, value) + + /* https://developer.arm.com/docs/ddi0488/h/system-control/aarch64-register-descriptions/system-control-register-el3 */ + struct SctlrEl3 { + using M = util::BitPack64::Field< 0, 1>; + using A = util::BitPack64::Field< 1, 1>; + using C = util::BitPack64::Field< 2, 1>; + using Sa = util::BitPack64::Field< 3, 1>; + using I = util::BitPack64::Field<12, 1>; + using Wxn = util::BitPack64::Field<19, 1>; + using Ee = util::BitPack64::Field<25, 1>; + + static constexpr u64 Res1 = 0x30C50830; + }; + + /* https://static.docs.arm.com/ddi0487/fb/DDI0487F_b_armv8_arm.pdf */ + struct SctlrEl2 { + using M = util::BitPack64::Field< 0, 1>; + using A = util::BitPack64::Field< 1, 1>; + using C = util::BitPack64::Field< 2, 1>; + using Sa = util::BitPack64::Field< 3, 1>; + using I = util::BitPack64::Field<12, 1>; + using Wxn = util::BitPack64::Field<19, 1>; + using Ee = util::BitPack64::Field<25, 1>; + + static constexpr u64 Res1 = 0x30C50830; + }; + + /* https://developer.arm.com/docs/ddi0488/h/system-control/aarch64-register-descriptions/system-control-register-el1 */ + struct SctlrEl1 { + using M = util::BitPack64::Field< 0, 1>; + using A = util::BitPack64::Field< 1, 1>; + using C = util::BitPack64::Field< 2, 1>; + using Sa = util::BitPack64::Field< 3, 1>; + using Sa0 = util::BitPack64::Field< 4, 1>; + using Cp15BEn = util::BitPack64::Field< 5, 1>; + using Thee = util::BitPack64::Field< 6, 1>; + using Itd = util::BitPack64::Field< 7, 1>; + using Sed = util::BitPack64::Field< 8, 1>; + using Uma = util::BitPack64::Field< 9, 1>; + using I = util::BitPack64::Field<12, 1>; + using Dze = util::BitPack64::Field<14, 1>; + using Uct = util::BitPack64::Field<15, 1>; + using Ntwi = util::BitPack64::Field<16, 1>; + using Ntwe = util::BitPack64::Field<18, 1>; + using Wxn = util::BitPack64::Field<19, 1>; + using E0e = util::BitPack64::Field<24, 1>; + using Ee = util::BitPack64::Field<25, 1>; + using Uci = util::BitPack64::Field<26, 1>; + + static constexpr u64 Res1 = 0x30D00800; + }; + + /* http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0488c/BABGIHHJ.html */ + struct ScrEl3 { + using Ns = util::BitPack32::Field< 0, 1>; + using Irq = util::BitPack32::Field< 1, 1>; + using Fiq = util::BitPack32::Field< 2, 1>; + using Ea = util::BitPack32::Field< 3, 1>; + using Fw = util::BitPack32::Field< 4, 1>; + using Aw = util::BitPack32::Field< 5, 1>; + using Net = util::BitPack32::Field< 6, 1>; + using Smd = util::BitPack32::Field< 7, 1>; + using Hce = util::BitPack32::Field< 8, 1>; + using Sif = util::BitPack32::Field< 9, 1>; + using RwCortexA53 = util::BitPack32::Field<10, 1>; + using StCortexA53 = util::BitPack32::Field<11, 1>; + using Twi = util::BitPack32::Field<12, 1>; + using Twe = util::BitPack32::Field<13, 1>; + }; + + /* http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0488c/CIHDEBIG.html */ + struct CptrEl3 { + using Tfp = util::BitPack32::Field<10, 1>; + using Tta = util::BitPack32::Field<20, 1>; + using Tcpac = util::BitPack32::Field<31, 1>; + }; + + /* https://developer.arm.com/docs/ddi0488/h/system-control/aarch64-register-descriptions/translation-control-register-el3 */ + struct TcrEl3 { + using T0sz = util::BitPack32::Field< 0, 6>; + using Irgn0 = util::BitPack32::Field< 8, 2>; + using Orgn0 = util::BitPack32::Field<10, 2>; + using Sh0 = util::BitPack32::Field<12, 2>; + using Tg0 = util::BitPack32::Field<14, 2>; + using Ps = util::BitPack32::Field<16, 3>; + using Tbi = util::BitPack32::Field<20, 1>; + + static constexpr u32 Res1 = 0x80800000; + }; + + struct ClidrEl1 { + using Ctype1 = util::BitPack32::Field< 0, 3>; + using Ctype2 = util::BitPack32::Field< 3, 3>; + using Ctype3 = util::BitPack32::Field< 6, 3>; + using Louis = util::BitPack32::Field<21, 3>; + using Loc = util::BitPack32::Field<24, 3>; + using Louu = util::BitPack32::Field<27, 3>; + }; + + struct CcsidrEl1 { + using LineSize = util::BitPack32::Field< 0, 3>; + using Associativity = util::BitPack32::Field< 3, 10>; + using NumSets = util::BitPack32::Field<13, 15>; + using Wa = util::BitPack32::Field<28, 1>; + using Ra = util::BitPack32::Field<29, 1>; + using Wb = util::BitPack32::Field<30, 1>; + using Wt = util::BitPack32::Field<31, 1>; + }; + + struct CsselrEl1 { + using InD = util::BitPack32::Field<0, 1>; + using Level = util::BitPack32::Field<1, 3>; + }; + + /* https://developer.arm.com/docs/ddi0488/h/system-control/aarch64-register-descriptions/cpu-auxiliary-control-register-el1 */ + struct CpuactlrEl1CortexA57 { + /* TODO: Other bits */ + using NonCacheableStreamingEnhancement = util::BitPack64::Field<24, 1>; + /* TODO: Other bits */ + using DisableLoadPassDmb = util::BitPack64::Field<59, 1>; + using ForceProcessorRcgEnablesActive = util::BitPack64::Field<63, 1>; + }; + + /* https://developer.arm.com/docs/ddi0488/h/system-control/aarch64-register-descriptions/cpu-extended-control-register-el1 */ + struct CpuectlrEl1CortexA57 { + using ProcessorDynamicRetentionControl = util::BitPack64::Field< 0, 2>; + using Smpen = util::BitPack64::Field< 6, 1>; + using L2LoadStoreDataPrefetchDistance = util::BitPack64::Field<32, 2>; + using L2InstructionFetchPrefetchDistance = util::BitPack64::Field<35, 2>; + using DisableTableWalkDescriptorAccessPrefetch = util::BitPack64::Field<38, 1>; + }; + + /* https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/dbgauthstatus_el1 */ + struct DbgAuthStatusEl1 { + using Nsid = util::BitPack32::Field<0, 2>; + using Nsnid = util::BitPack32::Field<2, 2>; + using Sid = util::BitPack32::Field<4, 2>; + using Snid = util::BitPack32::Field<6, 2>; + }; + + /* https://developer.arm.com/docs/ddi0595/b/aarch64-system-registers/cnthctl_el2 */ + struct CnthctlEl2 { + using El1PctEn = util::BitPack32::Field<0, 1>; + using El1PcEn = util::BitPack32::Field<1, 1>; + using EvntEn = util::BitPack32::Field<2, 1>; + using EvntDir = util::BitPack32::Field<3, 1>; + using EvntI = util::BitPack32::Field<4, 4>; + }; + + /* https://developer.arm.com/docs/ddi0488/h/system-control/aarch64-register-descriptions/auxiliary-control-register-el3 */ + struct ActlrCortexA57 { + using Cpuactlr = util::BitPack32::Field<0, 1>; + using Cpuectlr = util::BitPack32::Field<1, 1>; + using L2ctlr = util::BitPack32::Field<4, 1>; + using L2ectlr = util::BitPack32::Field<5, 1>; + using L2actlr = util::BitPack32::Field<6, 1>; + }; + + /* https://developer.arm.com/docs/ddi0488/h/system-control/aarch64-register-descriptions/hypervisor-configuration-register-el2 */ + struct HcrEl2 { + using Rw = util::BitPack64::Field<31, 1>; + }; + + struct EsrEl3 { + using Iss = util::BitPack32::Field< 0, 25>; + using Il = util::BitPack32::Field<25, 1>; + using Ec = util::BitPack32::Field<26, 6>; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/i2c.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/i2c.hpp new file mode 100644 index 00000000..62b3d8ec --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/i2c.hpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::i2c { + + enum Port { + Port_1 = 0, + /* TODO: Support other ports? */ + Port_5 = 4, + + Port_Count = 5, + }; + + void SetRegisterAddress(Port port, uintptr_t address); + + void Initialize(Port port); + + bool Query(void *dst, size_t dst_size, Port port, int address, int r); + bool Send(Port port, int address, int r, const void *src, size_t src_size); + + inline u8 QueryByte(Port port, int address, int r) { + u8 byte; + Query(std::addressof(byte), sizeof(byte), port, address, r); + return byte; + } + + inline void SendByte(Port port, int address, int r, u8 byte) { + Send(port, address, r, std::addressof(byte), sizeof(byte)); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/log.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/log.hpp new file mode 100644 index 00000000..427e2ef7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/log.hpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::log { + + #if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING) + #define AMS_IMPL_ENABLE_LOG + #endif + + #if defined(AMS_IMPL_ENABLE_LOG) + #define AMS_LOG(...) ::ams::log::Printf(__VA_ARGS__) + #define AMS_VLOG(...) ::ams::log::VPrintf(__VA_ARGS__) + #define AMS_DUMP(...) ::ams::log::Dump(__VA_ARGS__) + #define AMS_LOG_FLUSH() ::ams::log::Flush() + #else + #define AMS_LOG(...) static_cast<void>(0) + #define AMS_VLOG(...) static_cast<void>(0) + #define AMS_DUMP(...) static_cast<void>(0) + #define AMS_LOG_FLUSH() static_cast<void>(0) + #endif + + void Initialize(); + void Initialize(uart::Port port, u32 baud_rate, u32 flags); + void Finalize(); + + void Printf(const char *fmt, ...) __attribute__((format(printf, 1, 2))); + void VPrintf(const char *fmt, ::std::va_list vl); + void Dump(const void *src, size_t size); + + void SendText(const void *text, size_t size); + void Flush(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/mmu.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/mmu.hpp new file mode 100644 index 00000000..eef6f44e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/mmu.hpp @@ -0,0 +1,18 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <exosphere/mmu/mmu_api.hpp> \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/mmu/mmu_api.arch.arm.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/mmu/mmu_api.arch.arm.hpp new file mode 100644 index 00000000..32510624 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/mmu/mmu_api.arch.arm.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::mmu::arch::arm { + + /* NOTE: mmu is not supported by libexosphere-on-arm. */ + /* However, we want the memory layout to be parseable, so we will include some arm64 mmu definitions. */ + constexpr inline u64 L1EntryShift = 30; + constexpr inline u64 L2EntryShift = 21; + constexpr inline u64 L3EntryShift = 12; + + constexpr inline u64 L1EntrySize = 1_GB; + constexpr inline u64 L2EntrySize = 2_MB; + constexpr inline u64 L3EntrySize = 4_KB; + + constexpr inline u64 PageSize = L3EntrySize; + + constexpr inline u64 L1EntryMask = ((static_cast<u64>(1) << (48 - L1EntryShift)) - 1) << L1EntryShift; + constexpr inline u64 L2EntryMask = ((static_cast<u64>(1) << (48 - L2EntryShift)) - 1) << L2EntryShift; + constexpr inline u64 L3EntryMask = ((static_cast<u64>(1) << (48 - L3EntryShift)) - 1) << L3EntryShift; + + constexpr inline u64 TableEntryMask = L3EntryMask; + + static_assert(L1EntryMask == 0x0000FFFFC0000000ul); + static_assert(L2EntryMask == 0x0000FFFFFFE00000ul); + static_assert(L3EntryMask == 0x0000FFFFFFFFF000ul); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/mmu/mmu_api.arch.arm64.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/mmu/mmu_api.arch.arm64.hpp new file mode 100644 index 00000000..dc9c53d7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/mmu/mmu_api.arch.arm64.hpp @@ -0,0 +1,286 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::mmu::arch::arm64 { + + enum PageTableTableAttribute : u64 { + PageTableTableAttribute_None = (0ul << 0), + PageTableTableAttribute_PrivilegedExecuteNever = (1ul << 59), + PageTableTableAttribute_ExecuteNever = (1ul << 60), + PageTableTableAttribute_NonSecure = (1ul << 63), + + PageTableTableAttributes_El3SecureCode = PageTableTableAttribute_None, + PageTableTableAttributes_El3SecureData = PageTableTableAttribute_ExecuteNever, + PageTableTableAttributes_El3NonSecureCode = PageTableTableAttributes_El3SecureCode | PageTableTableAttribute_NonSecure, + PageTableTableAttributes_El3NonSecureData = PageTableTableAttributes_El3SecureData | PageTableTableAttribute_NonSecure, + }; + + enum PageTableMappingAttribute : u64{ + /* Security. */ + PageTableMappingAttribute_NonSecure = (1ul << 5), + + /* El1 Access. */ + PageTableMappingAttribute_El1NotAllowed = (0ul << 6), + PageTableMappingAttribute_El1Allowed = (1ul << 6), + + /* RW Permission. */ + PageTableMappingAttribute_PermissionReadWrite = (0ul << 7), + PageTableMappingAttribute_PermissionReadOnly = (1ul << 7), + + /* Shareability. */ + PageTableMappingAttribute_ShareabilityNonShareable = (0ul << 8), + PageTableMappingAttribute_ShareabiltiyOuterShareable = (2ul << 8), + PageTableMappingAttribute_ShareabilityInnerShareable = (3ul << 8), + + /* Access flag. */ + PageTableMappingAttribute_AccessFlagNotAccessed = (0ul << 10), + PageTableMappingAttribute_AccessFlagAccessed = (1ul << 10), + + /* Global. */ + PageTableMappingAttribute_Global = (0ul << 11), + PageTableMappingAttribute_NonGlobal = (1ul << 11), + + /* Contiguous */ + PageTableMappingAttribute_NonContiguous = (0ul << 52), + PageTableMappingAttribute_Contiguous = (1ul << 52), + + /* Privileged Execute Never */ + PageTableMappingAttribute_PrivilegedExecuteNever = (1ul << 53), + + /* Execute Never */ + PageTableMappingAttribute_ExecuteNever = (1ul << 54), + + + /* Useful definitions. */ + PageTableMappingAttributes_El3SecureRwCode = ( + PageTableMappingAttribute_PermissionReadWrite | + PageTableMappingAttribute_ShareabilityInnerShareable + ), + + PageTableMappingAttributes_El3SecureRoCode = ( + PageTableMappingAttribute_PermissionReadOnly | + PageTableMappingAttribute_ShareabilityInnerShareable + ), + + PageTableMappingAttributes_El3SecureRoData = ( + PageTableMappingAttribute_PermissionReadOnly | + PageTableMappingAttribute_ShareabilityInnerShareable | + PageTableMappingAttribute_ExecuteNever + ), + + PageTableMappingAttributes_El3SecureRwData = ( + PageTableMappingAttribute_PermissionReadWrite | + PageTableMappingAttribute_ShareabilityInnerShareable | + PageTableMappingAttribute_ExecuteNever + ), + + PageTableMappingAttributes_El3NonSecureRwCode = PageTableMappingAttributes_El3SecureRwCode | PageTableMappingAttribute_NonSecure, + PageTableMappingAttributes_El3NonSecureRoCode = PageTableMappingAttributes_El3SecureRoCode | PageTableMappingAttribute_NonSecure, + PageTableMappingAttributes_El3NonSecureRoData = PageTableMappingAttributes_El3SecureRoData | PageTableMappingAttribute_NonSecure, + PageTableMappingAttributes_El3NonSecureRwData = PageTableMappingAttributes_El3SecureRwData | PageTableMappingAttribute_NonSecure, + + + PageTableMappingAttributes_El3SecureDevice = PageTableMappingAttributes_El3SecureRwData, + PageTableMappingAttributes_El3NonSecureDevice = PageTableMappingAttributes_El3NonSecureRwData, + }; + + enum MemoryRegionAttribute : u64 { + MemoryRegionAttribute_Device_nGnRnE = (0ul << 2), + MemoryRegionAttribute_Device_nGnRE = (1ul << 2), + MemoryRegionAttribute_NormalMemory = (2ul << 2), + MemoryRegionAttribute_NormalMemoryNotCacheable = (3ul << 2), + + MemoryRegionAttribute_NormalInnerShift = 0, + MemoryRegionAttribute_NormalOuterShift = 4, + + #define AMS_MRA_DEFINE_NORMAL_ATTR(__NAME__, __VAL__) \ + MemoryRegionAttribute_NormalInner##__NAME__ = (__VAL__ << MemoryRegionAttribute_NormalInnerShift), \ + MemoryRegionAttribute_NormalOuter##__NAME__ = (__VAL__ << MemoryRegionAttribute_NormalOuterShift) + + AMS_MRA_DEFINE_NORMAL_ATTR(NonCacheable, 4), + + AMS_MRA_DEFINE_NORMAL_ATTR(WriteAllocate, (1ul << 0)), + AMS_MRA_DEFINE_NORMAL_ATTR(ReadAllocate, (1ul << 1)), + + AMS_MRA_DEFINE_NORMAL_ATTR(WriteThroughTransient, (0ul << 2)), + AMS_MRA_DEFINE_NORMAL_ATTR(WriteBackTransient, (1ul << 2)), + AMS_MRA_DEFINE_NORMAL_ATTR(WriteThroughNonTransient, (2ul << 2)), + AMS_MRA_DEFINE_NORMAL_ATTR(WriteBackNonTransient, (3ul << 2)), + + #undef AMS_MRA_DEFINE_NORMAL_ATTR + + MemoryRegionAttributes_Normal = ( + MemoryRegionAttribute_NormalInnerReadAllocate | + MemoryRegionAttribute_NormalOuterReadAllocate | + MemoryRegionAttribute_NormalInnerWriteAllocate | + MemoryRegionAttribute_NormalOuterWriteAllocate | + MemoryRegionAttribute_NormalInnerWriteBackNonTransient | + MemoryRegionAttribute_NormalOuterWriteBackNonTransient + ), + + MemoryRegionAttributes_Device = ( + MemoryRegionAttribute_Device_nGnRE + ), + }; + + constexpr inline u64 MemoryRegionAttributeWidth = 8; + + constexpr ALWAYS_INLINE PageTableMappingAttribute AddMappingAttributeIndex(PageTableMappingAttribute attr, int index) { + return static_cast<PageTableMappingAttribute>(attr | (static_cast<typename std::underlying_type<PageTableMappingAttribute>::type>(index) << 2)); + } + + constexpr inline u64 L1EntryShift = 30; + constexpr inline u64 L2EntryShift = 21; + constexpr inline u64 L3EntryShift = 12; + + constexpr inline u64 L1EntrySize = 1_GB; + constexpr inline u64 L2EntrySize = 2_MB; + constexpr inline u64 L3EntrySize = 4_KB; + + constexpr inline u64 PageSize = L3EntrySize; + + constexpr inline u64 L1EntryMask = ((1ul << (48 - L1EntryShift)) - 1) << L1EntryShift; + constexpr inline u64 L2EntryMask = ((1ul << (48 - L2EntryShift)) - 1) << L2EntryShift; + constexpr inline u64 L3EntryMask = ((1ul << (48 - L3EntryShift)) - 1) << L3EntryShift; + + constexpr inline u64 TableEntryMask = L3EntryMask; + + static_assert(L1EntryMask == 0x0000FFFFC0000000ul); + static_assert(L2EntryMask == 0x0000FFFFFFE00000ul); + static_assert(L3EntryMask == 0x0000FFFFFFFFF000ul); + + constexpr inline u64 TableEntryIndexMask = 0x1FF; + + constexpr inline u64 EntryBlock = 0x1ul; + constexpr inline u64 EntryPage = 0x3ul; + + constexpr ALWAYS_INLINE u64 MakeTableEntry(u64 address, PageTableTableAttribute attr) { + return address | static_cast<u64>(attr) | 0x3ul; + } + + constexpr ALWAYS_INLINE u64 MakeL1BlockEntry(u64 address, PageTableMappingAttribute attr) { + return address | static_cast<u64>(attr) | static_cast<u64>(PageTableMappingAttribute_AccessFlagAccessed) | 0x1ul; + } + + constexpr ALWAYS_INLINE u64 MakeL2BlockEntry(u64 address, PageTableMappingAttribute attr) { + return address | static_cast<u64>(attr) | static_cast<u64>(PageTableMappingAttribute_AccessFlagAccessed) | 0x1ul; + } + + constexpr ALWAYS_INLINE u64 MakeL3BlockEntry(u64 address, PageTableMappingAttribute attr) { + return address | static_cast<u64>(attr) | static_cast<u64>(PageTableMappingAttribute_AccessFlagAccessed) | 0x3ul; + } + + constexpr ALWAYS_INLINE uintptr_t GetL2Offset(uintptr_t address) { + return address & ((1ul << L2EntryShift) - 1); + } + + constexpr ALWAYS_INLINE u64 GetL1EntryIndex(uintptr_t address) { + return ((address >> L1EntryShift) & TableEntryIndexMask); + } + + constexpr ALWAYS_INLINE u64 GetL2EntryIndex(uintptr_t address) { + return ((address >> L2EntryShift) & TableEntryIndexMask); + } + + constexpr ALWAYS_INLINE u64 GetL3EntryIndex(uintptr_t address) { + return ((address >> L3EntryShift) & TableEntryIndexMask); + } + + constexpr ALWAYS_INLINE void SetTableEntryImpl(volatile u64 *table, u64 index, u64 value) { + /* Write the value. */ + table[index] = value; + } + + constexpr ALWAYS_INLINE void SetTableEntry(u64 *table, u64 index, u64 value) { + /* Ensure (for constexpr validation purposes) that the entry we set is clear. */ + if (std::is_constant_evaluated()) { + if (table[index]) { + __builtin_unreachable(); + } + } + + /* Set the value. */ + SetTableEntryImpl(table, index, value); + } + + constexpr ALWAYS_INLINE void SetL1TableEntry(u64 *table, uintptr_t virt_addr, uintptr_t phys_addr, PageTableTableAttribute attr) { + SetTableEntry(table, GetL1EntryIndex(virt_addr), MakeTableEntry(phys_addr & TableEntryMask, attr)); + } + + constexpr ALWAYS_INLINE void SetL2TableEntry(u64 *table, uintptr_t virt_addr, uintptr_t phys_addr, PageTableTableAttribute attr) { + SetTableEntry(table, GetL2EntryIndex(virt_addr), MakeTableEntry(phys_addr & TableEntryMask, attr)); + } + + constexpr ALWAYS_INLINE void SetL1BlockEntry(u64 *table, uintptr_t virt_addr, uintptr_t phys_addr, size_t size, PageTableMappingAttribute attr) { + const u64 start = GetL1EntryIndex(virt_addr); + const u64 count = (size >> L1EntryShift); + + for (u64 i = 0; i < count; ++i) { + SetTableEntry(table, start + i, MakeL1BlockEntry((phys_addr & L1EntryMask) + (i << L1EntryShift), attr)); + } + } + + constexpr ALWAYS_INLINE void SetL2BlockEntry(u64 *table, uintptr_t virt_addr, uintptr_t phys_addr, size_t size, PageTableMappingAttribute attr) { + const u64 start = GetL2EntryIndex(virt_addr); + const u64 count = (size >> L2EntryShift); + + for (u64 i = 0; i < count; ++i) { + SetTableEntry(table, start + i, MakeL2BlockEntry((phys_addr & L2EntryMask) + (i << L2EntryShift), attr)); + } + } + + constexpr ALWAYS_INLINE void SetL3BlockEntry(u64 *table, uintptr_t virt_addr, uintptr_t phys_addr, size_t size, PageTableMappingAttribute attr) { + const u64 start = GetL3EntryIndex(virt_addr); + const u64 count = (size >> L3EntryShift); + + for (u64 i = 0; i < count; ++i) { + SetTableEntry(table, start + i, MakeL3BlockEntry((phys_addr & L3EntryMask) + (i << L3EntryShift), attr)); + } + } + + constexpr ALWAYS_INLINE void InvalidateL1Entries(volatile u64 *table, uintptr_t virt_addr, size_t size) { + const u64 start = GetL1EntryIndex(virt_addr); + const u64 count = (size >> L1EntryShift); + const u64 end = start + count; + + for (u64 i = start; i < end; ++i) { + table[i] = 0; + } + } + + constexpr ALWAYS_INLINE void InvalidateL2Entries(volatile u64 *table, uintptr_t virt_addr, size_t size) { + const u64 start = GetL2EntryIndex(virt_addr); + const u64 count = (size >> L2EntryShift); + const u64 end = start + count; + + for (u64 i = start; i < end; ++i) { + table[i] = 0; + } + } + + constexpr ALWAYS_INLINE void InvalidateL3Entries(volatile u64 *table, uintptr_t virt_addr, size_t size) { + const u64 start = GetL3EntryIndex(virt_addr); + const u64 count = (size >> L3EntryShift); + const u64 end = start + count; + + for (u64 i = start; i < end; ++i) { + table[i] = 0; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/mmu/mmu_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/mmu/mmu_api.hpp new file mode 100644 index 00000000..3c021734 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/mmu/mmu_api.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +#if defined(ATMOSPHERE_ARCH_ARM64) + #include <exosphere/mmu/mmu_api.arch.arm64.hpp> +#elif defined(ATMOSPHERE_ARCH_ARM) + #include <exosphere/mmu/mmu_api.arch.arm.hpp> +#else + #error "Unknown architecture for mmu!" +#endif + +namespace ams::mmu { + + #if defined(ATMOSPHERE_ARCH_ARM64) + using namespace ams::mmu::arch::arm64; + #elif defined(ATMOSPHERE_ARCH_ARM) + using namespace ams::mmu::arch::arm; + #else + #error "Unknown architecture for mmu!" + #endif + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pinmux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pinmux.hpp new file mode 100644 index 00000000..1e98b673 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pinmux.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <exosphere/fuse.hpp> + +namespace ams::pinmux { + + void SetRegisterAddress(uintptr_t pinmux_address, uintptr_t gpio_address); + + void SetupFirst(fuse::HardwareType hw_type); + + void SetupUartA(); + void SetupUartB(); + void SetupUartC(); + void SetupI2c1(); + void SetupI2c5(); + + void SetupVolumeButton(); + void SetupHomeButton(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1.hpp new file mode 100644 index 00000000..ec68b8b5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <exosphere/pkg1/pkg1_bootloader_parameters.hpp> +#include <exosphere/pkg1/pkg1_boot_config.hpp> +#include <exosphere/pkg1/pkg1_error_types.hpp> +#include <exosphere/pkg1/pkg1_key_generation.hpp> +#include <exosphere/pkg1/pkg1_se_key_slots.hpp> +#include <exosphere/pkg1/pkg1_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1/pkg1_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1/pkg1_api.hpp new file mode 100644 index 00000000..b45b5e14 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1/pkg1_api.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::pkg1 { + + bool IsProduction(); + bool IsProductionForVersionCheck(); + bool IsProductionForPublicKey(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1/pkg1_boot_config.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1/pkg1_boot_config.hpp new file mode 100644 index 00000000..b61fb7e0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1/pkg1_boot_config.hpp @@ -0,0 +1,145 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::pkg1 { + + enum MemorySize { + MemorySize_4GB = 0, + MemorySize_6GB = 1, + MemorySize_8GB = 2, + }; + + enum MemoryArrange { + MemoryArrange_Normal = 1, + MemoryArrange_AppletDev = 2, + MemoryArrange_SystemDev = 3, + }; + + enum MemoryMode { + MemoryMode_SizeShift = 4, + MemoryMode_SizeMask = 0x30, + + MemoryMode_ArrangeMask = 0x0F, + + MemoryMode_Auto = 0x00, + + MemoryMode_4GB = ((MemorySize_4GB << MemoryMode_SizeShift) | (MemoryArrange_Normal)), + MemoryMode_4GBAppletDev = ((MemorySize_4GB << MemoryMode_SizeShift) | (MemoryArrange_AppletDev)), + MemoryMode_4GBSystemDev = ((MemorySize_4GB << MemoryMode_SizeShift) | (MemoryArrange_SystemDev)), + + MemoryMode_6GB = ((MemorySize_6GB << MemoryMode_SizeShift) | (MemoryArrange_Normal)), + MemoryMode_6GBAppletDev = ((MemorySize_6GB << MemoryMode_SizeShift) | (MemoryArrange_AppletDev)), + + MemoryMode_8GB = ((MemorySize_8GB << MemoryMode_SizeShift) | (MemoryArrange_Normal)), + }; + + constexpr ALWAYS_INLINE MemorySize GetMemorySize(MemoryMode mode) { + return static_cast<MemorySize>(mode >> MemoryMode_SizeShift); + } + + constexpr ALWAYS_INLINE MemoryArrange GetMemoryArrange(MemoryMode mode) { + return static_cast<MemoryArrange>(mode & MemoryMode_ArrangeMask); + } + + constexpr ALWAYS_INLINE MemoryMode MakeMemoryMode(MemorySize size, MemoryArrange arrange) { + return static_cast<MemoryMode>((size << MemoryMode_SizeShift) | (arrange)); + } + + struct BootConfigData { + u32 version; + u32 reserved_04; + u32 reserved_08; + u32 reserved_0C; + u8 flags1[0x10]; + u8 flags0[0x10]; + u64 initial_tsc_value; + u8 padding_38[0x200 - 0x38]; + + constexpr bool IsDevelopmentFunctionEnabled() const { + return (this->flags1[0] & (1 << 1)) != 0; + } + + constexpr bool IsSErrorDebugEnabled() const { + return (this->flags1[0] & (1 << 2)) != 0; + } + + constexpr u8 GetKernelFlags0() const { + return this->flags0[1]; + } + + constexpr u8 GetKernelFlags1() const { + return this->flags1[0]; + } + + constexpr MemoryMode GetMemoryMode() const { + return static_cast<MemoryMode>(this->flags0[3]); + } + + constexpr bool IsInitialTscValueValid() const { + return (this->flags0[4] & (1 << 0)) != 0; + } + + constexpr u64 GetInitialTscValue() const { + return this->IsInitialTscValueValid() ? this->initial_tsc_value : 0; + } + }; + static_assert(util::is_pod<BootConfigData>::value); + static_assert(sizeof(BootConfigData) == 0x200); + + struct BootConfigSignedData { + u32 version; + u32 reserved_04; + u8 flags; + u8 reserved_09[0x10 - 9]; + u8 ecid[0x10]; + u8 flags1[0x10]; + u8 flags0[0x10]; + u8 padding_40[0x100 - 0x40]; + + constexpr bool IsPackage2EncryptionDisabled() const { + return (this->flags & (1 << 0)) != 0; + } + + constexpr bool IsPackage2SignatureVerificationDisabled() const { + return (this->flags & (1 << 1)) != 0; + } + + constexpr bool IsProgramVerificationDisabled() const { + return (this->flags1[0] & (1 << 0)) != 0; + } + + constexpr void SetPackage2SignatureVerificationDisabled(bool decrypted) { + this->flags |= decrypted ? (1 << 1) : (0 << 0); + } + + constexpr void SetPackage2EncryptionDisabled(bool decrypted) { + this->flags |= decrypted ? (1 << 0) : (0 << 0); + } + }; + static_assert(util::is_pod<BootConfigSignedData>::value); + static_assert(sizeof(BootConfigSignedData) == 0x100); + + struct BootConfig { + BootConfigData data; + u8 signature[0x100]; + BootConfigSignedData signed_data; + }; + static_assert(util::is_pod<BootConfig>::value); + static_assert(sizeof(BootConfig) == 0x400); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1/pkg1_bootloader_parameters.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1/pkg1_bootloader_parameters.hpp new file mode 100644 index 00000000..b3de1ea4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1/pkg1_bootloader_parameters.hpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::pkg1 { + + enum BootloaderState { + BootloaderState_Start = 0, + BootloaderState_LoadedBootConfig = 1, + BootloaderState_InitializedDram = 2, + BootloaderState_LoadedPackage2 = 3, + BootloaderState_Done = 4, + }; + + enum SecureMonitorState { + SecureMonitorState_Start = 0, + SecureMonitorState_Initialized = 1, + }; + + struct BctParameters { + u32 bootloader_version; + u32 bootloader_start_block; + u32 bootloader_start_page; + u32 bootloader_attributes; + }; + static_assert(util::is_pod<BctParameters>::value && sizeof(BctParameters) == 0x10); + + struct SecureMonitorParameters { + u32 bootloader_start_time; + u32 bootloader_end_time; + u32 secmon_start_time; + u32 secmon_end_time; + BctParameters bct_params; + u32 deprecated_boot_reason_value; + u8 deprecated_boot_reason_state; + u8 reserved[0xD3]; + u32 bootloader_state; + u32 secmon_state; + u8 reserved2[0x100]; + }; + static_assert(util::is_pod<SecureMonitorParameters>::value); + static_assert(sizeof(SecureMonitorParameters) == 0x200); + + static_assert(AMS_OFFSETOF(SecureMonitorParameters, bct_params) == 0x10); + static_assert(AMS_OFFSETOF(SecureMonitorParameters, bootloader_state) == 0xF8); + static_assert(AMS_OFFSETOF(SecureMonitorParameters, secmon_state) == 0xFC); + + enum BootloaderAttribute { + BootloaderAttribute_None = (0u << 0), + BootloaderAttribute_RecoveryBoot = (1u << 0), + + BootloaderAttribute_RestrictedSmcShift = 1, + BootloaderAttribute_RestrictedSmcMask = (0xFu << BootloaderAttribute_RestrictedSmcShift), + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1/pkg1_error_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1/pkg1_error_types.hpp new file mode 100644 index 00000000..7278961d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1/pkg1_error_types.hpp @@ -0,0 +1,108 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::pkg1 { + + enum ErrorReason { + ErrorReason_None = 0, + ErrorReason_InvalidPackage2Signature = 1, + ErrorReason_InvalidPackage2Meta = 2, + ErrorReason_InvalidPackage2Version = 3, + ErrorReason_InvalidPackage2Payload = 4, + ErrorReason_UnknownSmc = 5, + ErrorReason_UnknownAbort = 6, + ErrorReason_InvalidCoreContext = 7, + ErrorReason_InvalidSecurityEngineStickyBits = 8, + ErrorReason_UnexpectedReset = 9, + + ErrorReason_Exception = 0x10, + + ErrorReason_TransitionToSafeMode = 0x20, + ErrorReason_SecureInitializerReboot = 0x21, + + ErrorReason_SdmmcError = 0x30, + ErrorReason_InvalidDramId = 0x31, + ErrorReason_InvalidPackage2 = 0x32, + ErrorReason_InvalidBct = 0x33, + ErrorReason_InvalidGpt = 0x34, + ErrorReason_FailedToTransitionToSafeMode = 0x35, + ErrorReason_ActivityMonitorInterrupt = 0x36, + + ErrorReason_KernelPanic = 0x40, + }; + + enum ErrorColor { + ErrorColor_Black = 0x000, + + ErrorColor_Red = 0x00F, + ErrorColor_Yellow = 0x0FF, + ErrorColor_Orange = 0x07F, + ErrorColor_Blue = 0xF00, + ErrorColor_LightBlue = 0xFF0, + ErrorColor_Pink = 0xF7F, + ErrorColor_Purple = 0xF0A, + }; + + enum ErrorInfo { + ErrorInfo_ReasonMask = 0xFF, + ErrorInfo_ColorShift = 20, + + #define MAKE_ERROR_INFO(_COLOR_, _DESC_) ((static_cast<u32>(ErrorColor_##_COLOR_) << ErrorInfo_ColorShift) | (ErrorReason_##_DESC_)) + + ErrorInfo_None = MAKE_ERROR_INFO(Black, None), + + ErrorInfo_InvalidPackage2Signature = MAKE_ERROR_INFO(Blue, InvalidPackage2Signature), + ErrorInfo_InvalidPackage2Meta = MAKE_ERROR_INFO(Blue, InvalidPackage2Meta), + ErrorInfo_InvalidPackage2Version = MAKE_ERROR_INFO(Blue, InvalidPackage2Version), + ErrorInfo_InvalidPackage2Payload = MAKE_ERROR_INFO(Blue, InvalidPackage2Payload), + + ErrorInfo_UnknownSmc = MAKE_ERROR_INFO(LightBlue, UnknownSmc), + + ErrorInfo_UnknownAbort = MAKE_ERROR_INFO(Yellow, UnknownAbort), + + ErrorInfo_InvalidCoreContext = MAKE_ERROR_INFO(Pink, InvalidCoreContext), + ErrorInfo_InvalidSecurityEngineStickyBits = MAKE_ERROR_INFO(Pink, InvalidSecurityEngineStickyBits), + ErrorInfo_UnexpectedReset = MAKE_ERROR_INFO(Pink, UnexpectedReset), + + ErrorInfo_Exception = MAKE_ERROR_INFO(Orange, Exception), + + ErrorInfo_TransitionToSafeMode = MAKE_ERROR_INFO(Black, TransitionToSafeMode), + ErrorInfo_SecureInitializerReboot = MAKE_ERROR_INFO(Black, SecureInitializerReboot), + + ErrorInfo_SdmmcError = MAKE_ERROR_INFO(Purple, SdmmcError), + ErrorInfo_InvalidDramId = MAKE_ERROR_INFO(Purple, InvalidDramId), + ErrorInfo_InvalidPackage2 = MAKE_ERROR_INFO(Purple, InvalidPackage2), + ErrorInfo_InvalidBct = MAKE_ERROR_INFO(Purple, InvalidBct), + ErrorInfo_InvalidGpt = MAKE_ERROR_INFO(Purple, InvalidGpt), + ErrorInfo_FailedToTransitionToSafeMode = MAKE_ERROR_INFO(Purple, FailedToTransitionToSafeMode), + ErrorInfo_ActivityMonitorInterrupt = MAKE_ERROR_INFO(Purple, ActivityMonitorInterrupt), + + #undef MAKE_ERROR_INFO + }; + + constexpr inline ErrorReason GetErrorReason(u32 info) { + return static_cast<ErrorReason>(info & ErrorInfo_ReasonMask); + } + + constexpr inline ErrorInfo MakeKernelPanicResetInfo(u32 color) { + return static_cast<ErrorInfo>((color << ErrorInfo_ColorShift) | (ErrorReason_KernelPanic)); + } + + #define PKG1_SECURE_MONITOR_PMC_ERROR_SCRATCH (0x840) + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1/pkg1_key_generation.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1/pkg1_key_generation.hpp new file mode 100644 index 00000000..282c2332 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1/pkg1_key_generation.hpp @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::pkg1 { + + enum KeyGeneration : int { + + KeyGeneration_1_0_0 = 0x00, + KeyGeneration_3_0_0 = 0x01, + KeyGeneration_3_0_1 = 0x02, + KeyGeneration_4_0_0 = 0x03, + KeyGeneration_5_0_0 = 0x04, + KeyGeneration_6_0_0 = 0x05, + KeyGeneration_6_2_0 = 0x06, + KeyGeneration_7_0_0 = 0x07, + KeyGeneration_8_1_0 = 0x08, + KeyGeneration_9_0_0 = 0x09, + KeyGeneration_9_1_0 = 0x0A, + KeyGeneration_12_1_0 = 0x0B, + KeyGeneration_13_0_0 = 0x0C, + KeyGeneration_14_0_0 = 0x0D, + KeyGeneration_15_0_0 = 0x0E, + KeyGeneration_16_0_0 = 0x0F, + KeyGeneration_17_0_0 = 0x10, + KeyGeneration_18_0_0 = 0x11, + KeyGeneration_19_0_0 = 0x12, + KeyGeneration_20_0_0 = 0x13, + + KeyGeneration_Count, + + KeyGeneration_Current = KeyGeneration_Count - 1, + + KeyGeneration_Min = 0x00, + KeyGeneration_Max = 0x20, + }; + static_assert(KeyGeneration_Count <= KeyGeneration_Max); + + constexpr inline const int OldMasterKeyCount = KeyGeneration_Count - 1; + constexpr inline const int OldDeviceMasterKeyCount = KeyGeneration_Count - KeyGeneration_4_0_0; + + constexpr bool IsValidDeviceUniqueKeyGeneration(int generation) { + return generation == KeyGeneration_1_0_0 || (KeyGeneration_4_0_0 <= generation && generation <= KeyGeneration_Current); + } + + constexpr bool IsValidKeyGeneration(int generation) { + return KeyGeneration_Min <= generation && generation <= KeyGeneration_Current; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1/pkg1_se_key_slots.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1/pkg1_se_key_slots.hpp new file mode 100644 index 00000000..32a77e88 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg1/pkg1_se_key_slots.hpp @@ -0,0 +1,75 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::pkg1 { + + enum AesKeySlot { + AesKeySlot_UserStart = 0, + + AesKeySlot_TzramSaveKek = 2, + AesKeySlot_TzramSaveKey = 3, + + AesKeySlot_UserLast = 5, + AesKeySlot_UserEnd = AesKeySlot_UserLast + 1, + + AesKeySlot_SecmonStart = 8, + + AesKeySlot_Temporary = 8, + AesKeySlot_Smc = 9, + AesKeySlot_RandomForUserWrap = 10, + AesKeySlot_RandomForKeyStorageWrap = 11, + AesKeySlot_DeviceMaster = 12, + AesKeySlot_Master = 13, + AesKeySlot_Device = 15, + + AesKeySlot_Count = 16, + AesKeySlot_SecmonEnd = AesKeySlot_Count, + + /* Used only during boot. */ + AesKeySlot_TsecRootDev = 11, + AesKeySlot_Tsec = 12, + AesKeySlot_TsecRoot = 13, + AesKeySlot_SecureBoot = 14, + AesKeySlot_SecureStorage = 15, + + AesKeySlot_DeviceMasterKeySourceKekErista = 10, + AesKeySlot_MasterKek = 13, + AesKeySlot_DeviceMasterKeySourceKekMariko = 14, + + /* Mariko only keyslots, used during boot. */ + AesKeySlot_MarikoKek = 12, + AesKeySlot_MarikoBek = 13, + + /* Bootloader keyslots, for fusee only. */ + AesKeySlot_BootloaderSystem0 = 2, + AesKeySlot_BootloaderSystem1 = 3, + AesKeySlot_BootloaderDeviceMaster = 6, + AesKeySlot_BootloaderMaster = 7, + AesKeySlot_BootloaderTemporary = 8, + }; + + enum RsaKeySlot { + RsaKeySlot_Temporary = 0, + RsaKeySlot_PrivateKey = 1, + }; + + constexpr bool IsUserAesKeySlot(int slot) { + return AesKeySlot_UserStart <= slot && slot < AesKeySlot_UserEnd; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg2.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg2.hpp new file mode 100644 index 00000000..d6649d9d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pkg2.hpp @@ -0,0 +1,81 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::pkg2 { + + constexpr inline size_t Package2SizeMax = 8_MB - 16_KB; + constexpr inline size_t PayloadAlignment = 4; + + constexpr inline int PayloadCount = 3; + + constexpr inline int MinimumValidDataVersion = 0; /* We allow older package2 to load; this value is currently 0x18 in Nintendo's code. */ + constexpr inline int CurrentBootloaderVersion = 0x17; + + struct Package2Meta { + using Magic = util::FourCC<'P','K','2','1'>; + + u32 package2_size; + u8 key_generation; + u8 header_iv_remainder[11]; + u8 payload_ivs[PayloadCount][0x10]; + u8 padding_40[0x10]; + u8 magic[4]; + u32 entrypoint; + u8 padding_58[4]; + u8 package2_version; + u8 bootloader_version; + u8 padding_5E[2]; + u32 payload_sizes[PayloadCount]; + u8 padding_6C[4]; + u32 payload_offsets[PayloadCount]; + u8 padding_7C[4]; + u8 payload_hashes[PayloadCount][crypto::Sha256Generator::HashSize]; + u8 padding_E0[0x20]; + + private: + static ALWAYS_INLINE u32 ReadWord(const void *ptr, int offset) { + return util::LoadLittleEndian(reinterpret_cast<const u32 *>(reinterpret_cast<uintptr_t>(ptr) + offset)); + } + public: + ALWAYS_INLINE u8 GetKeyGeneration() const { + return static_cast<u8>(std::max<s32>(0, static_cast<s32>(this->key_generation ^ this->header_iv_remainder[1] ^ this->header_iv_remainder[2]) - 1)); + } + + ALWAYS_INLINE u32 GetSize() const { + return this->package2_size ^ ReadWord(this->header_iv_remainder, 3) ^ ReadWord(this->header_iv_remainder, 7); + } + }; + static_assert(util::is_pod<Package2Meta>::value); + static_assert(sizeof(Package2Meta) == 0x100); + + struct Package2Header { + u8 signature[0x100]; + Package2Meta meta; + }; + static_assert(util::is_pod<Package2Header>::value); + static_assert(sizeof(Package2Header) == 0x200); + + struct StorageLayout { + u8 boot_config[16_KB]; + Package2Header package2_header; + u8 data[Package2SizeMax - sizeof(Package2Header)]; + }; + static_assert(util::is_pod<StorageLayout>::value); + static_assert(sizeof(StorageLayout) == 8_MB); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pmc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pmc.hpp new file mode 100644 index 00000000..8581464a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pmc.hpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::pmc { + + enum SecureRegister { + SecureRegister_Other = (1 << 0), + SecureRegister_DramParameters = (1 << 1), + SecureRegister_ResetVector = (1 << 2), + SecureRegister_Carveout = (1 << 3), + SecureRegister_CmacWrite = (1 << 4), + SecureRegister_CmacRead = (1 << 5), + SecureRegister_KeySourceWrite = (1 << 6), + SecureRegister_KeySourceRead = (1 << 7), + SecureRegister_Srk = (1 << 8), + + SecureRegister_CmacReadWrite = SecureRegister_CmacRead | SecureRegister_CmacWrite, + SecureRegister_KeySourceReadWrite = SecureRegister_KeySourceRead | SecureRegister_KeySourceWrite, + }; + + void SetRegisterAddress(uintptr_t address); + + void InitializeRandomScratch(); + void EnableWakeEventDetection(); + void ConfigureForSc7Entry(); + + void LockSecureRegister(SecureRegister reg); + + enum class LockState { + Locked = 0, + NotLocked = 1, + PartiallyLocked = 2, + }; + + LockState GetSecureRegisterLockState(SecureRegister reg); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pmic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pmic.hpp new file mode 100644 index 00000000..d0767c99 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pmic.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::pmic { + + enum Regulator { + /* Erista regulators. */ + Regulator_Erista_Max77621 = 0, /* Device code 0x3A000001 */ + + /* Mariko regulators. */ + Regulator_Mariko_Max77812_A = 1, /* Device code 0x3A000002 */ + Regulator_Mariko_Max77812_B = 2, /* Device code 0x3A000006 */ + }; + + void SetEnBit(Regulator regulator); + void EnableVddCpu(Regulator regulator); + void DisableVddCpu(Regulator regulator); + void EnableSleep(); + void PowerOff(); + void ShutdownSystem(bool reboot); + bool IsAcOk(); + bool IsPowerButtonPressed(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pmic_setup.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pmic_setup.hpp new file mode 100644 index 00000000..07cf04cd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/pmic_setup.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <exosphere/pmic.hpp> +#include <exosphere/fuse.hpp> + +namespace ams::pmic { + + void SetSystemSetting(fuse::SocType soc_type); + void EnableVddCore(fuse::SocType soc_type); + void EnableLdo8(); + + void EnableVddMemory(fuse::SocType soc_type); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/rtc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/rtc.hpp new file mode 100644 index 00000000..c643755b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/rtc.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::rtc { + + void StopAlarm(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se.hpp new file mode 100644 index 00000000..158fc9d8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +#include <exosphere/se/se_common.hpp> +#include <exosphere/se/se_management.hpp> +#include <exosphere/se/se_aes.hpp> +#include <exosphere/se/se_hash.hpp> +#include <exosphere/se/se_oaep.hpp> +#include <exosphere/se/se_rsa.hpp> +#include <exosphere/se/se_rng.hpp> +#include <exosphere/se/se_suspend.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_aes.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_aes.hpp new file mode 100644 index 00000000..381095f6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_aes.hpp @@ -0,0 +1,56 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <exosphere/se/se_common.hpp> + +namespace ams::se { + + constexpr inline int AesKeySlotCount = 16; + constexpr inline size_t AesBlockSize = crypto::AesEncryptor128::BlockSize; + + void ClearAesKeySlot(int slot); + void ClearAesKeyIv(int slot); + void LockAesKeySlot(int slot, u32 flags); + + /* NOTE: This is Nintendo's API, but if we actually want to use SE2 we should use a different one. */ + void ClearAesKeySlot2(int slot); + + void SetAesKey(int slot, const void *key, size_t key_size); + + void SetEncryptedAesKey128(int dst_slot, int kek_slot, const void *key, size_t key_size); + void SetEncryptedAesKey256(int dst_slot, int kek_slot, const void *key, size_t key_size); + + void EncryptAes128(void *dst, size_t dst_size, int slot, const void *src, size_t src_size); + void DecryptAes128(void *dst, size_t dst_size, int slot, const void *src, size_t src_size); + + void ComputeAes128Ctr(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, const void *iv, size_t iv_size); + + void ComputeAes128Cmac(void *dst, size_t dst_size, int slot, const void *src, size_t src_size); + void ComputeAes256Cmac(void *dst, size_t dst_size, int slot, const void *src, size_t src_size); + + void EncryptAes128Cbc(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, const void *iv, size_t iv_size); + void EncryptAes256Cbc(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, const void *iv, size_t iv_size); + void DecryptAes128Cbc(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, const void *iv, size_t iv_size); + void DecryptAes256Cbc(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, const void *iv, size_t iv_size); + + void DecryptAes128Xts(void *dst, size_t dst_size, int slot_enc, int slot_tweak, const void *src, size_t src_size, size_t sector); + + void EncryptAes128CbcAsync(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, const void *iv, size_t iv_size, DoneHandler handler); + void DecryptAes128CbcAsync(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, const void *iv, size_t iv_size, DoneHandler handler); + void ComputeAes128CtrAsync(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, const void *iv, size_t iv_size, DoneHandler handler); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_common.hpp new file mode 100644 index 00000000..f9bdbebb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_common.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::secmon { + + u8 *GetSecurityEngineEphemeralWorkBlock(); + +} + +namespace ams::se { + + using DoneHandler = void(*)(); + + enum KeySlotLockFlags { + KeySlotLockFlags_None = 0, + KeySlotLockFlags_KeyRead = (1u << 0), + KeySlotLockFlags_KeyWrite = (1u << 1), + KeySlotLockFlags_OriginalIvRead = (1u << 2), + KeySlotLockFlags_OriginalIvWrite = (1u << 3), + KeySlotLockFlags_UpdatedIvRead = (1u << 4), + KeySlotLockFlags_UpdatedIvWrite = (1u << 5), + KeySlotLockFlags_KeyUse = (1u << 6), + KeySlotLockFlags_DstKeyTableOnly = (1u << 7), + KeySlotLockFlags_PerKey = (1u << 8), + + KeySlotLockFlags_AllReadLock = (KeySlotLockFlags_KeyRead | KeySlotLockFlags_OriginalIvRead | KeySlotLockFlags_UpdatedIvRead), + KeySlotLockFlags_AllLockKek = 0x1FF, + KeySlotLockFlags_AllLockKey = (KeySlotLockFlags_AllLockKek & ~KeySlotLockFlags_DstKeyTableOnly), + + KeySlotLockFlags_EristaMask = 0x7F, + KeySlotLockFlags_MarikoMask = 0xFF, + }; + + ALWAYS_INLINE u8 *GetEphemeralWorkBlock() { + return ::ams::secmon::GetSecurityEngineEphemeralWorkBlock(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_hash.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_hash.hpp new file mode 100644 index 00000000..a95637ae --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_hash.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::se { + + constexpr inline int Sha256HashSize = crypto::Sha256Generator::HashSize; + + union Sha256Hash { + u8 bytes[Sha256HashSize / sizeof(u8) ]; + u32 words[Sha256HashSize / sizeof(u32)]; + }; + + void CalculateSha256(Sha256Hash *dst, const void *src, size_t src_size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_management.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_management.hpp new file mode 100644 index 00000000..dfd41ff8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_management.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::se { + + void SetRegisterAddress(uintptr_t address, uintptr_t address2); + + void Initialize(); + + void SetSecure(bool secure); + void SetTzramSecure(); + void SetPerKeySecure(); + void SetContextSaveSecure(); + + void Lockout(); + + void HandleInterrupt(); + + void ValidateErrStatus(); + void ValidateAesOperationResult(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_oaep.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_oaep.hpp new file mode 100644 index 00000000..0f69a954 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_oaep.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::se { + + size_t DecodeRsaOaepSha256(void *dst, size_t dst_size, void *src, size_t src_size, const void *label_digest, size_t label_digest_size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_rng.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_rng.hpp new file mode 100644 index 00000000..73ea4929 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_rng.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::se { + + void InitializeRandom(); + + void GenerateRandomBytes(void *dst, size_t size); + void SetRandomKey(int slot); + + void GenerateSrk(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_rsa.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_rsa.hpp new file mode 100644 index 00000000..8e276131 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_rsa.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <exosphere/se/se_common.hpp> + +namespace ams::se { + + constexpr inline int RsaKeySlotCount = 2; + constexpr inline int RsaSize = 0x100; + + void ClearRsaKeySlot(int slot); + void LockRsaKeySlot(int slot, u32 flags); + + void SetRsaKey(int slot, const void *mod, size_t mod_size, const void *exp, size_t exp_size); + + void ModularExponentiate(void *dst, size_t dst_size, int slot, const void *src, size_t src_size); + void ModularExponentiateAsync(int slot, const void *src, size_t src_size, DoneHandler handler); + + void GetRsaResult(void *dst, size_t dst_size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_suspend.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_suspend.hpp new file mode 100644 index 00000000..0f4e3916 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/se/se_suspend.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <exosphere/se/se_aes.hpp> +#include <exosphere/se/se_rsa.hpp> + +namespace ams::se { + + /* 256-bit AES keyslots are two 128-bit keys. */ + constexpr inline int AesKeySlotPartCount = 2; + + /* RSA keys are both a modulus and an exponent. */ + constexpr inline int RsaKeySlotPartCount = 2; + + constexpr inline size_t StickyBitContextSize = 2 * AesBlockSize; + + struct Context { + u8 random[AesBlockSize]; + u8 sticky_bits[StickyBitContextSize / AesBlockSize][AesBlockSize]; + u8 aes_key[AesKeySlotCount][AesKeySlotPartCount][AesBlockSize]; + u8 aes_oiv[AesKeySlotCount][AesBlockSize]; + u8 aes_uiv[AesKeySlotCount][AesBlockSize]; + u8 rsa_key[RsaKeySlotCount][RsaKeySlotPartCount][RsaSize / AesBlockSize][AesBlockSize]; + u8 fixed_pattern[AesBlockSize]; + }; + static_assert(sizeof(Context) == 0x840); + static_assert(util::is_pod<Context>::value); + + struct StickyBits { + u8 se_security; + u8 tzram_security; + u16 crypto_security_perkey; + u8 crypto_keytable_access[AesKeySlotCount]; + u8 rsa_security_perkey; + u8 rsa_keytable_access[RsaKeySlotCount]; + }; + static_assert(util::is_pod<StickyBits>::value); + + bool ValidateStickyBits(const StickyBits &bits); + void SaveContext(Context *dst); + + void ConfigureAutomaticContextSave(); + void SaveContextAutomatic(); + void SaveTzramAutomatic(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon.hpp new file mode 100644 index 00000000..bacb5433 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon.hpp @@ -0,0 +1,21 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <exosphere/secmon/secmon_log.hpp> +#include <exosphere/secmon/secmon_memory_layout.hpp> +#include <exosphere/secmon/secmon_configuration_context.hpp> +#include <exosphere/secmon/secmon_volatile_context.hpp> \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_configuration_context.arch.arm64.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_configuration_context.arch.arm64.hpp new file mode 100644 index 00000000..9eacd856 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_configuration_context.arch.arm64.hpp @@ -0,0 +1,139 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <exosphere/pkg1.hpp> +#include <exosphere/se.hpp> +#include <exosphere/secmon/secmon_monitor_context.hpp> + +namespace ams::secmon { + + struct ConfigurationContext { + union { + SecureMonitorConfiguration secmon_cfg; + u8 _raw_exosphere_config[0x80]; + }; + union { + EmummcConfiguration emummc_cfg; + u8 _raw_emummc_config[0x120]; + }; + u8 sealed_device_keys[pkg1::KeyGeneration_Max][se::AesBlockSize]; + u8 sealed_master_keys[pkg1::KeyGeneration_Max][se::AesBlockSize]; + pkg1::BootConfig boot_config; + u8 rsa_private_exponents[4][se::RsaSize]; + union { + u8 _misc_data[0xFC0 - sizeof(_raw_exosphere_config) - sizeof(_raw_emummc_config) - sizeof(sealed_device_keys) - sizeof(sealed_master_keys) - sizeof(boot_config) - sizeof(rsa_private_exponents)]; + }; + /* u8 l1_page_table[0x40]; */ + }; + static_assert(sizeof(ConfigurationContext) == 0xFC0); + static_assert(util::is_pod<ConfigurationContext>::value); + + namespace impl { + + ALWAYS_INLINE uintptr_t GetConfigurationContextAddress() { + register uintptr_t x18 asm("x18"); + __asm__ __volatile__("" : [x18]"=r"(x18)); + return x18; + } + + ALWAYS_INLINE ConfigurationContext &GetConfigurationContext() { + return *reinterpret_cast<ConfigurationContext *>(GetConfigurationContextAddress()); + } + + ALWAYS_INLINE u8 *GetMasterKeyStorage(int generation) { + return GetConfigurationContext().sealed_master_keys[generation]; + } + + ALWAYS_INLINE u8 *GetDeviceMasterKeyStorage(int generation) { + return GetConfigurationContext().sealed_device_keys[generation]; + } + + ALWAYS_INLINE u8 *GetRsaPrivateExponentStorage(int which) { + return GetConfigurationContext().rsa_private_exponents[which]; + } + + ALWAYS_INLINE void SetKeyGeneration(int generation) { + GetConfigurationContext().secmon_cfg.key_generation = generation; + } + + ALWAYS_INLINE void SetTargetFirmware(ams::TargetFirmware target_firmware) { + GetConfigurationContext().secmon_cfg.target_firmware = target_firmware; + } + + ALWAYS_INLINE pkg1::BootConfig *GetBootConfigStorage() { + return std::addressof(GetConfigurationContext().boot_config); + } + + } + + ALWAYS_INLINE const ConfigurationContext &GetConfigurationContext() { + return *reinterpret_cast<const ConfigurationContext *>(impl::GetConfigurationContextAddress()); + } + + ALWAYS_INLINE const SecureMonitorConfiguration &GetSecmonConfiguration() { + return GetConfigurationContext().secmon_cfg; + } + + ALWAYS_INLINE const EmummcConfiguration &GetEmummcConfiguration() { + return GetConfigurationContext().emummc_cfg; + } + + ALWAYS_INLINE const pkg1::BootConfig &GetBootConfig() { + return GetConfigurationContext().boot_config; + } + + ALWAYS_INLINE ams::TargetFirmware GetTargetFirmware() { + return GetSecmonConfiguration().GetTargetFirmware(); + } + + ALWAYS_INLINE int GetKeyGeneration() { + return GetSecmonConfiguration().GetKeyGeneration(); + } + + ALWAYS_INLINE fuse::HardwareType GetHardwareType() { + return GetSecmonConfiguration().GetHardwareType(); + } + + ALWAYS_INLINE fuse::SocType GetSocType() { + return GetSecmonConfiguration().GetSocType(); + } + + ALWAYS_INLINE fuse::HardwareState GetHardwareState() { + return GetSecmonConfiguration().GetHardwareState(); + } + + ALWAYS_INLINE u16 GetLcdVendor() { + return GetSecmonConfiguration().GetLcdVendor(); + } + + ALWAYS_INLINE uart::Port GetLogPort() { + return GetSecmonConfiguration().GetLogPort(); + } + + ALWAYS_INLINE u8 GetLogFlags() { + return GetSecmonConfiguration().GetLogFlags(); + } + + ALWAYS_INLINE u32 GetLogBaudRate() { + return GetSecmonConfiguration().GetLogBaudRate(); + } + + ALWAYS_INLINE bool IsProduction() { + return GetSecmonConfiguration().IsProduction(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_configuration_context.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_configuration_context.hpp new file mode 100644 index 00000000..eca155c5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_configuration_context.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +#if defined(ATMOSPHERE_ARCH_ARM64) + #include <exosphere/secmon/secmon_configuration_context.arch.arm64.hpp> +#elif defined(ATMOSPHERE_ARCH_ARM) + /* Nothing to include. */ +#else + #error "Unknown architecture for secmon::ConfigurationContext.hpp" +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_emummc_context.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_emummc_context.hpp new file mode 100644 index 00000000..d42d4e2f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_emummc_context.hpp @@ -0,0 +1,89 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::secmon { + + enum EmummcType : u32 { + EmummcType_None = 0, + EmummcType_Partition = 1, + EmummcType_File = 2, + }; + + enum EmummcMmc { + EmummcMmc_Nand = 0, + EmummcMmc_Sd = 1, + EmummcMmc_Gc = 2, + }; + + constexpr inline size_t EmummcFilePathLengthMax = 0x80; + + struct EmummcFilePath { + char str[EmummcFilePathLengthMax]; + }; + static_assert(util::is_pod<EmummcFilePath>::value); + static_assert(sizeof(EmummcFilePath) == EmummcFilePathLengthMax); + + struct EmummcBaseConfiguration { + static constexpr u32 Magic = util::FourCC<'E','F','S','0'>::Code; + + u32 magic; + EmummcType type; + u32 id; + u32 fs_version; + + constexpr bool IsValid() const { + return this->magic == Magic; + } + + constexpr bool IsEmummcActive() const { + return this->IsValid() && this->type != EmummcType_None; + } + }; + static_assert(util::is_pod<EmummcBaseConfiguration>::value); + static_assert(sizeof(EmummcBaseConfiguration) == 0x10); + + struct EmummcPartitionConfiguration { + u64 start_sector; + }; + static_assert(util::is_pod<EmummcPartitionConfiguration>::value); + + struct EmummcFileConfiguration { + EmummcFilePath path; + }; + static_assert(util::is_pod<EmummcFileConfiguration>::value); + + struct EmummcConfiguration { + EmummcBaseConfiguration base_cfg; + union { + EmummcPartitionConfiguration partition_cfg; + EmummcFileConfiguration file_cfg; + }; + EmummcFilePath emu_dir_path; + + constexpr bool IsValid() const { + return this->base_cfg.IsValid(); + } + + constexpr bool IsEmummcActive() const { + return this->base_cfg.IsEmummcActive(); + } + }; + static_assert(util::is_pod<EmummcConfiguration>::value); + static_assert(sizeof(EmummcConfiguration) <= 0x200); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_log.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_log.hpp new file mode 100644 index 00000000..16602a2c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_log.hpp @@ -0,0 +1,20 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <exosphere/log.hpp> + +#define AMS_SECMON_LOG(...) AMS_LOG(" [secmon] " __VA_ARGS__) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_memory_layout.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_memory_layout.hpp new file mode 100644 index 00000000..891d9d98 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_memory_layout.hpp @@ -0,0 +1,348 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <exosphere/mmu.hpp> + +namespace ams::secmon { + + using Address = u64; + + struct MemoryRegion { + private: + Address m_start_address; + Address m_end_address; + public: + consteval MemoryRegion(Address address, size_t size) : m_start_address(address), m_end_address(address + size) { + if (m_end_address < m_start_address) { + __builtin_unreachable(); + } + } + + constexpr Address GetStartAddress() const { + return m_start_address; + } + + constexpr Address GetAddress() const { + return this->GetStartAddress(); + } + + constexpr Address GetEndAddress() const { + return m_end_address; + } + + constexpr Address GetLastAddress() const { + return m_end_address - 1; + } + + constexpr size_t GetSize() const { + return m_end_address - m_start_address; + } + + constexpr bool Contains(Address address, size_t size) const { + return m_start_address <= address && (address + size - 1) <= this->GetLastAddress(); + } + + constexpr bool Contains(const MemoryRegion &rhs) const { + return this->Contains(rhs.GetStartAddress(), rhs.GetSize()); + } + + template<typename T = void> requires (std::is_same<T, void>::value || util::is_pod<T>::value) + ALWAYS_INLINE T *GetPointer() const { + return reinterpret_cast<T *>(this->GetAddress()); + } + + template<typename T = void> requires (std::is_same<T, void>::value || util::is_pod<T>::value) + ALWAYS_INLINE T *GetEndPointer() const { + return reinterpret_cast<T *>(this->GetEndAddress()); + } + }; + + constexpr inline const MemoryRegion MemoryRegionVirtual = MemoryRegion(UINT64_C(0x1F0000000), 2_MB); + constexpr inline const MemoryRegion MemoryRegionPhysical = MemoryRegion(UINT64_C( 0x40000000), 1_GB); + constexpr inline const MemoryRegion MemoryRegionDram = MemoryRegion(UINT64_C( 0x80000000), 2_GB); + constexpr inline const MemoryRegion MemoryRegionDramHigh = MemoryRegion(MemoryRegionDram.GetEndAddress(), 2_GB); + + constexpr inline const MemoryRegion MemoryRegionDramForMarikoProgram = MemoryRegion(UINT64_C(0xC0000000), 1_GB); + constexpr inline const MemoryRegion MemoryRegionDramDcFramebuffer = MemoryRegion(UINT64_C(0xC0000000), 4_MB); + static_assert(MemoryRegionDram.Contains(MemoryRegionDramForMarikoProgram)); + static_assert(MemoryRegionDramForMarikoProgram.Contains(MemoryRegionDramDcFramebuffer)); + + constexpr inline const MemoryRegion MemoryRegionDramGpuCarveout = MemoryRegion(UINT64_C(0x80020000), UINT64_C(0x40000)); + static_assert(MemoryRegionDram.Contains(MemoryRegionDramGpuCarveout)); + + constexpr inline const MemoryRegion MemoryRegionDramDefaultKernelCarveout = MemoryRegion(UINT64_C(0x80060000), UINT64_C(0x1FFE0000)); + static_assert(MemoryRegionDram.Contains(MemoryRegionDramDefaultKernelCarveout)); + + constexpr inline const MemoryRegion MemoryRegionDramPackage2Payloads = MemoryRegion(MemoryRegionDram.GetAddress(), 8_MB); + static_assert(MemoryRegionDram.Contains(MemoryRegionDramPackage2Payloads)); + + constexpr inline const MemoryRegion MemoryRegionDramPackage2 = MemoryRegion(UINT64_C(0xA9800000), UINT64_C(0x07FC0000)); + static_assert(MemoryRegionDram.Contains(MemoryRegionDramPackage2)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalIram = MemoryRegion(UINT64_C(0x40000000), 0x40000); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzram = MemoryRegion(UINT64_C(0x7C010000), 0x10000); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramMariko = MemoryRegion(UINT64_C(0x7C010000), 0x40000); + static_assert(MemoryRegionPhysical.Contains(MemoryRegionPhysicalIram)); + static_assert(MemoryRegionPhysical.Contains(MemoryRegionPhysicalTzram)); + static_assert(MemoryRegionPhysicalTzramMariko.Contains(MemoryRegionPhysicalTzram)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramVolatile(UINT64_C(0x7C010000), 0x2000); + static_assert(MemoryRegionPhysicalTzram.Contains(MemoryRegionPhysicalTzramVolatile)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramNonVolatile(UINT64_C(0x7C012000), 0xE000); + static_assert(MemoryRegionPhysicalTzram.Contains(MemoryRegionPhysicalTzramNonVolatile)); + + static_assert(MemoryRegionPhysicalTzram.GetSize() == MemoryRegionPhysicalTzramNonVolatile.GetSize() + MemoryRegionPhysicalTzramVolatile.GetSize()); + + constexpr inline const MemoryRegion MemoryRegionVirtualL1 = MemoryRegion(util::AlignDown(MemoryRegionVirtual.GetAddress(), mmu::L1EntrySize), mmu::L1EntrySize); + constexpr inline const MemoryRegion MemoryRegionPhysicalL1 = MemoryRegion(util::AlignDown(MemoryRegionPhysical.GetAddress(), mmu::L1EntrySize), mmu::L1EntrySize); + static_assert(MemoryRegionVirtualL1.Contains(MemoryRegionVirtual)); + static_assert(MemoryRegionPhysicalL1.Contains(MemoryRegionPhysical)); + + constexpr inline const MemoryRegion MemoryRegionVirtualL2 = MemoryRegion(util::AlignDown(MemoryRegionVirtual.GetAddress(), mmu::L2EntrySize), mmu::L2EntrySize); + constexpr inline const MemoryRegion MemoryRegionPhysicalIramL2 = MemoryRegion(util::AlignDown(MemoryRegionPhysicalIram.GetAddress(), mmu::L2EntrySize), mmu::L2EntrySize); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramL2 = MemoryRegion(util::AlignDown(MemoryRegionPhysicalTzram.GetAddress(), mmu::L2EntrySize), mmu::L2EntrySize); + static_assert(MemoryRegionVirtualL2.Contains(MemoryRegionVirtual)); + static_assert(MemoryRegionPhysicalIramL2.Contains(MemoryRegionPhysicalIram)); + static_assert(MemoryRegionPhysicalTzramL2.Contains(MemoryRegionPhysicalTzram)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalIramBootCode = MemoryRegion(UINT64_C(0x40020000), 0x20000); + static_assert(MemoryRegionPhysicalIram.Contains(MemoryRegionPhysicalIramBootCode)); + + constexpr inline const MemoryRegion MemoryRegionVirtualDevice = MemoryRegion(UINT64_C(0x1F0040000), UINT64_C(0x40000)); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualDevice)); + + constexpr inline const MemoryRegion MemoryRegionVirtualDeviceEmpty = MemoryRegion(MemoryRegionVirtualDevice.GetStartAddress(), 0); + + #define AMS_SECMON_FOREACH_DEVICE_REGION(HANDLER, ...) \ + HANDLER(GicDistributor, Empty, UINT64_C(0x50041000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(GicCpuInterface, GicDistributor, UINT64_C(0x50042000), UINT64_C(0x2000), true, ## __VA_ARGS__) \ + HANDLER(Uart, GicCpuInterface, UINT64_C(0x70006000), UINT64_C(0x1000), false, ## __VA_ARGS__) \ + HANDLER(ClkRst, Uart, UINT64_C(0x60006000), UINT64_C(0x1000), false, ## __VA_ARGS__) \ + HANDLER(RtcPmc, ClkRst, UINT64_C(0x7000E000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(Timer, RtcPmc, UINT64_C(0x60005000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(System, Timer, UINT64_C(0x6000C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(SecurityEngine, System, UINT64_C(0x70012000), UINT64_C(0x2000), true, ## __VA_ARGS__) \ + HANDLER(SecurityEngine2, SecurityEngine, UINT64_C(0x70412000), UINT64_C(0x2000), true, ## __VA_ARGS__) \ + HANDLER(SysCtr0, SecurityEngine2, UINT64_C(0x700F0000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(MemoryController, SysCtr0, UINT64_C(0x70019000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(ExternalMemoryController, MemoryController, UINT64_C(0x7001b000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(FuseKFuse, ExternalMemoryController, UINT64_C(0x7000F000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(ApbMisc, FuseKFuse, UINT64_C(0x70000000), UINT64_C(0x4000), true, ## __VA_ARGS__) \ + HANDLER(FlowController, ApbMisc, UINT64_C(0x60007000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(BootloaderParams, FlowController, UINT64_C(0x40000000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(I2c5, BootloaderParams, UINT64_C(0x7000D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(Gpio, I2c5, UINT64_C(0x6000D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(I2c1, Gpio, UINT64_C(0x7000C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(ExceptionVectors, I2c1, UINT64_C(0x6000F000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(MemoryController0, ExceptionVectors, UINT64_C(0x7001C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(MemoryController1, MemoryController0, UINT64_C(0x7001D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(Sdmmc, MemoryController1, UINT64_C(0x700B0000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(Disp1, Sdmmc, UINT64_C(0x54200000), UINT64_C(0x3000), true, ## __VA_ARGS__) \ + HANDLER(Dsi, Disp1, UINT64_C(0x54300000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(MipiCal, Dsi, UINT64_C(0x700E3000), UINT64_C(0x1000), true, ## __VA_ARGS__) + + #define DEFINE_DEVICE_REGION(_NAME_, _PREV_, _ADDRESS_, _SIZE_, _SECURE_) \ + constexpr inline const MemoryRegion MemoryRegionVirtualDevice##_NAME_ = MemoryRegion(MemoryRegionVirtualDevice##_PREV_.GetEndAddress() + 0x1000, _SIZE_); \ + constexpr inline const MemoryRegion MemoryRegionPhysicalDevice##_NAME_ = MemoryRegion(_ADDRESS_, _SIZE_); \ + static_assert(MemoryRegionVirtualDevice.Contains(MemoryRegionVirtualDevice##_NAME_)); \ + static_assert(MemoryRegionPhysical.Contains(MemoryRegionPhysicalDevice##_NAME_)); + + AMS_SECMON_FOREACH_DEVICE_REGION(DEFINE_DEVICE_REGION) + + #undef DEFINE_DEVICE_REGION + + constexpr inline const MemoryRegion MemoryRegionVirtualDeviceFuses = MemoryRegion(MemoryRegionVirtualDeviceFuseKFuse.GetAddress() + 0x800, 0x400); + constexpr inline const MemoryRegion MemoryRegionPhysicalDeviceFuses = MemoryRegion(MemoryRegionPhysicalDeviceFuseKFuse.GetAddress() + 0x800, 0x400); + static_assert(MemoryRegionVirtualDeviceFuseKFuse.Contains(MemoryRegionVirtualDeviceFuses)); + static_assert(MemoryRegionPhysicalDeviceFuseKFuse.Contains(MemoryRegionPhysicalDeviceFuses)); + + constexpr inline const MemoryRegion MemoryRegionVirtualDeviceActivityMonitor = MemoryRegion(MemoryRegionVirtualDeviceSystem.GetAddress() + 0x800, 0x400); + constexpr inline const MemoryRegion MemoryRegionPhysicalDeviceActivityMonitor = MemoryRegion(MemoryRegionPhysicalDeviceSystem.GetAddress() + 0x800, 0x400); + static_assert(MemoryRegionVirtualDeviceSystem.Contains(MemoryRegionVirtualDeviceActivityMonitor)); + static_assert(MemoryRegionPhysicalDeviceSystem.Contains(MemoryRegionPhysicalDeviceActivityMonitor)); + + constexpr inline const MemoryRegion MemoryRegionVirtualDeviceUartA = MemoryRegion(MemoryRegionVirtualDeviceUart.GetAddress() + 0x000, 0x040); + constexpr inline const MemoryRegion MemoryRegionVirtualDeviceUartB = MemoryRegion(MemoryRegionVirtualDeviceUart.GetAddress() + 0x040, 0x040); + constexpr inline const MemoryRegion MemoryRegionVirtualDeviceUartC = MemoryRegion(MemoryRegionVirtualDeviceUart.GetAddress() + 0x200, 0x100); + static_assert(MemoryRegionVirtualDeviceUart.Contains(MemoryRegionVirtualDeviceUartA)); + static_assert(MemoryRegionVirtualDeviceUart.Contains(MemoryRegionVirtualDeviceUartB)); + static_assert(MemoryRegionVirtualDeviceUart.Contains(MemoryRegionVirtualDeviceUartC)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalDeviceUartA = MemoryRegion(MemoryRegionPhysicalDeviceUart.GetAddress() + 0x000, 0x040); + constexpr inline const MemoryRegion MemoryRegionPhysicalDeviceUartB = MemoryRegion(MemoryRegionPhysicalDeviceUart.GetAddress() + 0x040, 0x040); + constexpr inline const MemoryRegion MemoryRegionPhysicalDeviceUartC = MemoryRegion(MemoryRegionPhysicalDeviceUart.GetAddress() + 0x200, 0x100); + static_assert(MemoryRegionPhysicalDeviceUart.Contains(MemoryRegionPhysicalDeviceUartA)); + static_assert(MemoryRegionPhysicalDeviceUart.Contains(MemoryRegionPhysicalDeviceUartB)); + static_assert(MemoryRegionPhysicalDeviceUart.Contains(MemoryRegionPhysicalDeviceUartC)); + + constexpr inline const MemoryRegion MemoryRegionVirtualDevicePmc = MemoryRegion(MemoryRegionVirtualDeviceRtcPmc.GetAddress() + 0x400, 0xC00); + constexpr inline const MemoryRegion MemoryRegionPhysicalDevicePmc = MemoryRegion(MemoryRegionPhysicalDeviceRtcPmc.GetAddress() + 0x400, 0xC00); + static_assert(MemoryRegionVirtualDeviceRtcPmc.Contains(MemoryRegionVirtualDevicePmc)); + static_assert(MemoryRegionPhysicalDeviceRtcPmc.Contains(MemoryRegionPhysicalDevicePmc)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramReadOnlyAlias = MemoryRegion(UINT64_C(0x1F00A0000), MemoryRegionPhysicalTzram.GetSize()); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramReadOnlyAlias = MemoryRegion(MemoryRegionPhysicalTzram.GetAddress(), MemoryRegionPhysicalTzram.GetSize()); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualTzramReadOnlyAlias)); + static_assert(MemoryRegionPhysicalTzram.Contains(MemoryRegionPhysicalTzramReadOnlyAlias)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramProgram(UINT64_C(0x1F00C0000), 0xC000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualTzramProgram)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramProgramExceptionVectors(UINT64_C(0x1F00C0000), 0x800); + static_assert(MemoryRegionVirtualTzramProgram.Contains(MemoryRegionVirtualTzramProgramExceptionVectors)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramMarikoProgram(UINT64_C(0x1F00D0000), 0x20000); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramMarikoProgram(UINT64_C(0x7C020000), 0x20000); + static_assert(MemoryRegionPhysicalTzramMariko.Contains(MemoryRegionPhysicalTzramMarikoProgram)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramMarikoProgramFatalErrorContext(UINT64_C(0x1F00EF000), 0x1000); + static_assert(MemoryRegionVirtualTzramMarikoProgram.Contains(MemoryRegionVirtualTzramMarikoProgramFatalErrorContext)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalIramFatalErrorContext(UINT64_C(0x4003E000), 0x1000); + static_assert(MemoryRegionPhysicalIram.Contains(MemoryRegionPhysicalIramFatalErrorContext)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramMarikoProgramStack(UINT64_C(0x1F00F4000), 0x8000); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramMarikoProgramStack(UINT64_C(0x7C040000), 0x8000); + static_assert(MemoryRegionPhysicalTzramMariko.Contains(MemoryRegionPhysicalTzramMarikoProgramStack)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalMarikoProgramImage(UINT64_C(0x80020000), 0x20000); + static_assert(MemoryRegionDram.Contains(MemoryRegionPhysicalMarikoProgramImage)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramProgramMain(UINT64_C(0x1F00C0800), 0xB800); + static_assert(MemoryRegionVirtualTzramProgram.Contains(MemoryRegionVirtualTzramProgramMain)); + + static_assert(MemoryRegionVirtualTzramProgram.GetSize() == MemoryRegionVirtualTzramProgramExceptionVectors.GetSize() + MemoryRegionVirtualTzramProgramMain.GetSize()); + + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramProgram(UINT64_C(0x7C012000), 0xC000); + static_assert(MemoryRegionPhysicalTzramNonVolatile.Contains(MemoryRegionPhysicalTzramProgram)); + + constexpr inline const Address PhysicalTzramProgramResetVector = MemoryRegionPhysicalTzramProgram.GetAddress() + MemoryRegionVirtualTzramProgramExceptionVectors.GetSize(); + static_assert(static_cast<u32>(PhysicalTzramProgramResetVector) == PhysicalTzramProgramResetVector); + + constexpr uintptr_t GetPhysicalTzramProgramAddress(uintptr_t virtual_address) { + return virtual_address - MemoryRegionVirtualTzramProgram.GetStartAddress() + MemoryRegionPhysicalTzramNonVolatile.GetStartAddress(); + } + + constexpr inline const MemoryRegion MemoryRegionVirtualIramSc7Work = MemoryRegion(UINT64_C(0x1F0120000), MemoryRegionPhysicalTzram.GetSize()); + constexpr inline const MemoryRegion MemoryRegionPhysicalIramSc7Work = MemoryRegion( UINT64_C(0x40020000), MemoryRegionPhysicalTzram.GetSize()); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualIramSc7Work)); + static_assert(MemoryRegionPhysicalIram.Contains(MemoryRegionPhysicalIramSc7Work)); + + constexpr inline const MemoryRegion MemoryRegionVirtualIramSc7Firmware = MemoryRegion(UINT64_C(0x1F0140000), 0x1000); + constexpr inline const MemoryRegion MemoryRegionPhysicalIramSc7Firmware = MemoryRegion( UINT64_C(0x40003000), 0x1000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualIramSc7Firmware)); + static_assert(MemoryRegionPhysicalIram.Contains(MemoryRegionPhysicalIramSc7Firmware)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalIramSecureMonitorDebug(UINT64_C(0x40034000), 0x4000); + static_assert(MemoryRegionPhysicalIram.Contains(MemoryRegionPhysicalIramSecureMonitorDebug)); + + constexpr inline const MemoryRegion MemoryRegionVirtualDebugCode = MemoryRegion(UINT64_C(0x1F0150000), 0x4000); + constexpr inline const MemoryRegion MemoryRegionPhysicalDebugCode = MemoryRegion(UINT64_C(0x40034000), 0x4000); + static_assert(MemoryRegionPhysicalIramSecureMonitorDebug.Contains(MemoryRegionPhysicalDebugCode)); + + constexpr inline const MemoryRegion MemoryRegionVirtualDebug = MemoryRegion(UINT64_C(0x1F0160000), 0x10000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualDebug)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramBootCode = MemoryRegion(UINT64_C(0x1F01C0000), 0x2000); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramBootCode = MemoryRegion( UINT64_C(0x7C010000), 0x2000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualTzramBootCode)); + static_assert(MemoryRegionPhysicalTzramVolatile.Contains(MemoryRegionPhysicalTzramBootCode)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalDramMonitorConfiguration = MemoryRegion( UINT64_C(0x8000F000), 0x1000); + + constexpr inline const MemoryRegion MemoryRegionVirtualDramSecureDataStore = MemoryRegion(UINT64_C(0x1F0100000), 0x10000); + constexpr inline const MemoryRegion MemoryRegionPhysicalDramSecureDataStore = MemoryRegion( UINT64_C(0x80010000), 0x10000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualDramSecureDataStore)); + static_assert(MemoryRegionDram.Contains(MemoryRegionPhysicalDramSecureDataStore)); + + constexpr inline const MemoryRegion MemoryRegionVirtualDramDebugDataStore = MemoryRegion(UINT64_C(0x1F0110000), 0x4000); + constexpr inline const MemoryRegion MemoryRegionPhysicalDramDebugDataStore = MemoryRegion( UINT64_C(0x8000C000), 0x4000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualDramSecureDataStore)); + static_assert(MemoryRegionDram.Contains(MemoryRegionPhysicalDramSecureDataStore)); + + constexpr inline const MemoryRegion MemoryRegionVirtualDramSdmmcMappedData = MemoryRegion(UINT64_C(0x1F0100000), 0xC000); + constexpr inline const MemoryRegion MemoryRegionPhysicalDramSdmmcMappedData = MemoryRegion(UINT64_C(0x80010000), 0xC000); + + constexpr inline const MemoryRegion MemoryRegionVirtualDramDcL0DevicePageTable = MemoryRegion(UINT64_C(0x1F010C000), 0x1000); + constexpr inline const MemoryRegion MemoryRegionPhysicalDramDcL0DevicePageTable = MemoryRegion( UINT64_C(0x8001C000), 0x1000); + + constexpr inline const MemoryRegion MemoryRegionVirtualDramSdmmc1L0DevicePageTable = MemoryRegion(UINT64_C(0x1F010E000), 0x1000); + constexpr inline const MemoryRegion MemoryRegionPhysicalDramSdmmc1L0DevicePageTable = MemoryRegion( UINT64_C(0x8001E000), 0x1000); + + constexpr inline const MemoryRegion MemoryRegionVirtualDramSdmmc1L1DevicePageTable = MemoryRegion(UINT64_C(0x1F010F000), 0x1000); + constexpr inline const MemoryRegion MemoryRegionPhysicalDramSdmmc1L1DevicePageTable = MemoryRegion( UINT64_C(0x8001F000), 0x1000); + + constexpr inline const MemoryRegion MemoryRegionVirtualDramSecureDataStoreTzram = MemoryRegion(UINT64_C(0x1F0100000), 0xE000); + constexpr inline const MemoryRegion MemoryRegionVirtualDramSecureDataStoreWarmbootFirmware = MemoryRegion(UINT64_C(0x1F010E000), 0x17C0); + constexpr inline const MemoryRegion MemoryRegionVirtualDramSecureDataStoreSecurityEngineState = MemoryRegion(UINT64_C(0x1F010F7C0), 0x0840); + static_assert(MemoryRegionVirtualDramSecureDataStore.Contains(MemoryRegionVirtualDramSecureDataStoreTzram)); + static_assert(MemoryRegionVirtualDramSecureDataStore.Contains(MemoryRegionVirtualDramSecureDataStoreWarmbootFirmware)); + static_assert(MemoryRegionVirtualDramSecureDataStore.Contains(MemoryRegionVirtualDramSecureDataStoreSecurityEngineState)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalDramSecureDataStoreTzram = MemoryRegion(UINT64_C(0x80010000), 0xE000); + constexpr inline const MemoryRegion MemoryRegionPhysicalDramSecureDataStoreWarmbootFirmware = MemoryRegion(UINT64_C(0x8001E000), 0x17C0); + constexpr inline const MemoryRegion MemoryRegionPhysicalDramSecureDataStoreSecurityEngineState = MemoryRegion(UINT64_C(0x8001F7C0), 0x0840); + static_assert(MemoryRegionPhysicalDramSecureDataStore.Contains(MemoryRegionPhysicalDramSecureDataStoreTzram)); + static_assert(MemoryRegionPhysicalDramSecureDataStore.Contains(MemoryRegionPhysicalDramSecureDataStoreWarmbootFirmware)); + static_assert(MemoryRegionPhysicalDramSecureDataStore.Contains(MemoryRegionPhysicalDramSecureDataStoreSecurityEngineState)); + + constexpr inline const MemoryRegion MemoryRegionVirtualAtmosphereIramPage = MemoryRegion(UINT64_C(0x1F01F0000), 0x1000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualAtmosphereIramPage)); + + constexpr inline const MemoryRegion MemoryRegionVirtualAtmosphereUserPage = MemoryRegion(UINT64_C(0x1F01F2000), 0x1000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualAtmosphereUserPage)); + + constexpr inline const MemoryRegion MemoryRegionVirtualSmcUserPage = MemoryRegion(UINT64_C(0x1F01F4000), 0x1000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualSmcUserPage)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramVolatileData = MemoryRegion(UINT64_C(0x1F01F6000), 0x1000); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramVolatileData = MemoryRegion( UINT64_C(0x7C010000), 0x1000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualTzramVolatileData)); + static_assert(MemoryRegionPhysicalTzramVolatile.Contains(MemoryRegionPhysicalTzramVolatileData)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramVolatileStack = MemoryRegion(UINT64_C(0x1F01F8000), 0x1000); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramVolatileStack = MemoryRegion( UINT64_C(0x7C011000), 0x1000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualTzramVolatileStack)); + static_assert(MemoryRegionPhysicalTzramVolatile.Contains(MemoryRegionPhysicalTzramVolatileStack)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramConfigurationData = MemoryRegion(UINT64_C(0x1F01FA000), 0x1000); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramConfigurationData = MemoryRegion( UINT64_C(0x7C01E000), 0x1000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualTzramConfigurationData)); + static_assert(MemoryRegionPhysicalTzramNonVolatile.Contains(MemoryRegionPhysicalTzramConfigurationData)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramL1PageTable = MemoryRegion(UINT64_C(0x1F01FCFC0), 0x40); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramL1PageTable = MemoryRegion( UINT64_C(0x7C01EFC0), 0x40); + static_assert(MemoryRegionPhysicalTzramConfigurationData.Contains(MemoryRegionPhysicalTzramL1PageTable)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramL2L3PageTable = MemoryRegion(UINT64_C(0x1F01FE000), 0x1000); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramL2L3PageTable = MemoryRegion( UINT64_C(0x7C01F000), 0x1000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualTzramL2L3PageTable)); + static_assert(MemoryRegionPhysicalTzramNonVolatile.Contains(MemoryRegionPhysicalTzramL2L3PageTable)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramFullProgramImage = MemoryRegion(UINT64_C(0x7C010800), 0xD800); + constexpr inline const MemoryRegion MemoryRegionPhysicalIramBootCodeImage = MemoryRegion(UINT64_C(0x40032000), 0xC000); + + constexpr inline const MemoryRegion MemoryRegionPhysicalIramBootCodeCode = MemoryRegion(UINT64_C(0x40032000), 0x1000); + constexpr inline const MemoryRegion MemoryRegionPhysicalIramBootCodeKeys = MemoryRegion(UINT64_C(0x40033000), 0x1000); + + constexpr inline const MemoryRegion MemoryRegionPhysicalIramWarmbootBin = MemoryRegion(UINT64_C(0x4003E000), 0x17F0); + constexpr inline const MemoryRegion MemoryRegionPhysicalIramBootConfig = MemoryRegion(UINT64_C(0x4003F800), 0x400); + + constexpr inline const MemoryRegion MemoryRegionPhysicalIramRebootStub = MemoryRegion(UINT64_C(0x4003F000), 0x1000); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_monitor_context.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_monitor_context.hpp new file mode 100644 index 00000000..c37b76c8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_monitor_context.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <exosphere/fuse.hpp> +#include <exosphere/uart.hpp> +#include <exosphere/secmon/secmon_emummc_context.hpp> + +namespace ams::secmon { + + enum SecureMonitorConfigurationFlag : u32 { + SecureMonitorConfigurationFlag_None = (0u << 0), + SecureMonitorConfigurationFlag_IsDevelopmentFunctionEnabledForKernel = (1u << 1), + SecureMonitorConfigurationFlag_IsDevelopmentFunctionEnabledForUser = (1u << 2), + SecureMonitorConfigurationFlag_DisableUserModeExceptionHandlers = (1u << 3), + SecureMonitorConfigurationFlag_EnableUserModePerformanceCounterAccess = (1u << 4), + SecureMonitorConfigurationFlag_ShouldUseBlankCalibrationBinary = (1u << 5), + SecureMonitorConfigurationFlag_AllowWritingToCalibrationBinarySysmmc = (1u << 6), + SecureMonitorConfigurationFlag_ForceEnableUsb30 = (1u << 7), + + SecureMonitorConfigurationFlag_Default = SecureMonitorConfigurationFlag_IsDevelopmentFunctionEnabledForKernel, + }; + + struct SecureMonitorStorageConfiguration { + static constexpr u32 Magic = util::FourCC<'E','X','O','0'>::Code; + + u32 magic; + ams::TargetFirmware target_firmware; + u32 flags[2]; + u16 lcd_vendor; + u8 log_port; + u8 log_flags; + u32 log_baud_rate; + u32 reserved1[2]; + EmummcConfiguration emummc_cfg; + + constexpr bool IsValid() const { return this->magic == Magic; } + }; + static_assert(util::is_pod<SecureMonitorStorageConfiguration>::value); + static_assert(sizeof(SecureMonitorStorageConfiguration) == 0x130); + + struct SecureMonitorConfiguration { + ams::TargetFirmware target_firmware; + s32 key_generation; + u8 hardware_type; + u8 soc_type; + u8 hardware_state; + u8 log_port; + u32 flags[2]; + u16 lcd_vendor; + u8 log_flags; + u8 reserved0; + u32 log_baud_rate; + u32 reserved1[(0x80 - 0x1C) / sizeof(u32)]; + + constexpr void CopyFrom(const SecureMonitorStorageConfiguration &storage) { + this->target_firmware = storage.target_firmware; + this->flags[0] = storage.flags[0]; + this->flags[1] = storage.flags[1]; + this->lcd_vendor = storage.lcd_vendor; + this->log_port = storage.log_port; + this->log_flags = storage.log_flags; + this->log_baud_rate = storage.log_baud_rate != 0 ? storage.log_baud_rate : 115200; + } + + void SetFuseInfo() { + this->hardware_type = fuse::GetHardwareType(); + this->soc_type = fuse::GetSocType(); + this->hardware_state = fuse::GetHardwareState(); + } + + constexpr ams::TargetFirmware GetTargetFirmware() const { return this->target_firmware; } + constexpr int GetKeyGeneration() const { return this->key_generation; } + constexpr fuse::HardwareType GetHardwareType() const { return static_cast<fuse::HardwareType>(this->hardware_type); } + constexpr fuse::SocType GetSocType() const { return static_cast<fuse::SocType>(this->soc_type); } + constexpr fuse::HardwareState GetHardwareState() const { return static_cast<fuse::HardwareState>(this->hardware_state); } + constexpr uart::Port GetLogPort() const { return static_cast<uart::Port>(this->log_port); } + constexpr u8 GetLogFlags() const { return this->log_flags; } + + constexpr u16 GetLcdVendor() const { return this->lcd_vendor; } + + constexpr u32 GetLogBaudRate() const { return this->log_baud_rate; } + + constexpr bool IsProduction() const { return this->GetHardwareState() != fuse::HardwareState_Development; } + + constexpr bool IsDevelopmentFunctionEnabledForKernel() const { return (this->flags[0] & SecureMonitorConfigurationFlag_IsDevelopmentFunctionEnabledForKernel) != 0; } + constexpr bool IsDevelopmentFunctionEnabledForUser() const { return (this->flags[0] & SecureMonitorConfigurationFlag_IsDevelopmentFunctionEnabledForUser) != 0; } + constexpr bool DisableUserModeExceptionHandlers() const { return (this->flags[0] & SecureMonitorConfigurationFlag_DisableUserModeExceptionHandlers) != 0; } + constexpr bool EnableUserModePerformanceCounterAccess() const { return (this->flags[0] & SecureMonitorConfigurationFlag_EnableUserModePerformanceCounterAccess) != 0; } + constexpr bool ShouldUseBlankCalibrationBinary() const { return (this->flags[0] & SecureMonitorConfigurationFlag_ShouldUseBlankCalibrationBinary) != 0; } + constexpr bool AllowWritingToCalibrationBinarySysmmc() const { return (this->flags[0] & SecureMonitorConfigurationFlag_AllowWritingToCalibrationBinarySysmmc) != 0; } + constexpr bool IsUsb30ForceEnabled() const { return (this->flags[0] & SecureMonitorConfigurationFlag_ForceEnableUsb30) != 0; } + + constexpr bool IsDevelopmentFunctionEnabled(bool for_kern) const { return for_kern ? this->IsDevelopmentFunctionEnabledForKernel() : this->IsDevelopmentFunctionEnabledForUser(); } + }; + static_assert(util::is_pod<SecureMonitorConfiguration>::value); + static_assert(sizeof(SecureMonitorConfiguration) == 0x80); + + constexpr inline const SecureMonitorConfiguration DefaultSecureMonitorConfiguration = { + .target_firmware = ams::TargetFirmware_Current, + .key_generation = {}, + .hardware_type = {}, + .soc_type = {}, + .hardware_state = {}, + .log_port = uart::Port_ReservedDebug, + .flags = { SecureMonitorConfigurationFlag_Default, SecureMonitorConfigurationFlag_None }, + .lcd_vendor = {}, + .log_flags = {}, + .reserved0 = {}, + .log_baud_rate = 115200, + .reserved1 = {}, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_volatile_context.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_volatile_context.hpp new file mode 100644 index 00000000..b17293f0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/secmon/secmon_volatile_context.hpp @@ -0,0 +1,138 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <exosphere/pkg1.hpp> +#include <exosphere/pkg2.hpp> + +namespace ams::secmon { + + /* The VolatileStack page is reserved entirely for use for core 3 SMC handling. */ + constexpr inline const Address Core3SmcStackAddress = MemoryRegionVirtualTzramVolatileStack.GetAddress() + MemoryRegionVirtualTzramVolatileStack.GetSize(); + + constexpr inline const size_t CoreExceptionStackSize = 0x80; + + /* Volatile keydata that we lose access to after boot. */ + struct VolatileKeys { + u8 boot_config_rsa_modulus[0x100]; + u8 package2_dev_rsa_modulus[0x100]; + u8 package2_prod_rsa_modulus[0x100]; + u8 package2_aes_key[0x10]; + u8 master_key_source[0x10]; + u8 device_master_key_source_kek_source[0x10]; + u8 mariko_dev_master_kek_source[0x10]; + u8 mariko_prod_master_kek_source[0x10]; + u8 dev_master_key_vectors[pkg1::OldMasterKeyCount + 1][0x10]; + u8 prod_master_key_vectors[pkg1::OldMasterKeyCount + 1][0x10]; + u8 device_master_key_source_sources[pkg1::OldDeviceMasterKeyCount][0x10]; + u8 dev_device_master_kek_sources[pkg1::OldDeviceMasterKeyCount][0x10]; + u8 prod_device_master_kek_sources[pkg1::OldDeviceMasterKeyCount][0x10]; + }; + static_assert(util::is_pod<VolatileKeys>::value); + static_assert(sizeof(VolatileKeys) <= 0x1000); + + /* Nintendo uses the bottom 0x740 of this as a stack for warmboot setup, and another 0x740 for the core 0/1/2 SMC stacks. */ + /* This is...wasteful. The warmboot stack is not deep. We will thus save 1K+ of nonvolatile storage by keeping the random cache in here. */ + struct VolatileData { + u8 se_work_block[crypto::AesEncryptor128::BlockSize]; + union { + u8 random_cache[0x400]; + pkg2::Package2Meta pkg2_meta; + }; + u8 reserved_danger_zone[0x30]; /* This memory is "available", but careful consideration must be taken before declaring it used. */ + u8 warmboot_stack[0x380]; + u8 core012_smc_stack[0x6C0]; + u8 core_exception_stacks[3][CoreExceptionStackSize]; + }; + static_assert(util::is_pod<VolatileData>::value); + static_assert(sizeof(VolatileData) == 0x1000); + + ALWAYS_INLINE VolatileData &GetVolatileData() { + return *MemoryRegionVirtualTzramVolatileData.GetPointer<VolatileData>(); + } + + ALWAYS_INLINE u8 *GetRandomBytesCache() { + return GetVolatileData().random_cache; + } + + constexpr ALWAYS_INLINE size_t GetRandomBytesCacheSize() { + return sizeof(VolatileData::random_cache); + } + + ALWAYS_INLINE u8 *GetSecurityEngineEphemeralWorkBlock() { + return GetVolatileData().se_work_block; + } + + namespace boot { + + ALWAYS_INLINE VolatileKeys &GetVolatileKeys() { + return *MemoryRegionPhysicalIramBootCodeKeys.GetPointer<VolatileKeys>(); + } + + ALWAYS_INLINE const u8 *GetBootConfigRsaModulus() { + return GetVolatileKeys().boot_config_rsa_modulus; + } + + ALWAYS_INLINE const u8 *GetPackage2RsaModulus(bool is_prod) { + auto &keys = GetVolatileKeys(); + return is_prod ? keys.package2_prod_rsa_modulus : keys.package2_dev_rsa_modulus; + } + + ALWAYS_INLINE const u8 *GetPackage2AesKey() { + return GetVolatileKeys().package2_aes_key; + } + + ALWAYS_INLINE const u8 *GetMasterKeySource() { + return GetVolatileKeys().master_key_source; + } + + ALWAYS_INLINE const u8 *GetDeviceMasterKeySourceKekSource() { + return GetVolatileKeys().device_master_key_source_kek_source; + } + + ALWAYS_INLINE const u8 *GetMarikoMasterKekSource(bool is_prod) { + auto &keys = GetVolatileKeys(); + return is_prod ? keys.mariko_prod_master_kek_source : keys.mariko_dev_master_kek_source; + } + + ALWAYS_INLINE const u8 *GetMasterKeyVector(bool is_prod, size_t i) { + auto &keys = GetVolatileKeys(); + return is_prod ? keys.prod_master_key_vectors[i] : keys.dev_master_key_vectors[i]; + } + + ALWAYS_INLINE const u8 *GetDeviceMasterKeySourceSource(size_t i) { + return GetVolatileKeys().device_master_key_source_sources[i]; + } + + ALWAYS_INLINE const u8 *GetDeviceMasterKekSource(bool is_prod, size_t i) { + auto &keys = GetVolatileKeys(); + return is_prod ? keys.prod_device_master_kek_sources[i] : keys.dev_device_master_kek_sources[i]; + } + + ALWAYS_INLINE pkg2::Package2Meta &GetEphemeralPackage2Meta() { + return GetVolatileData().pkg2_meta; + } + + } + + constexpr inline const Address WarmbootStackAddress = MemoryRegionVirtualTzramVolatileData.GetAddress() + AMS_OFFSETOF(VolatileData, warmboot_stack) + sizeof(VolatileData::warmboot_stack); + constexpr inline const Address Core012SmcStackAddress = MemoryRegionVirtualTzramVolatileData.GetAddress() + AMS_OFFSETOF(VolatileData, core012_smc_stack) + sizeof(VolatileData::core012_smc_stack); + + constexpr inline const Address Core0ExceptionStackAddress = MemoryRegionVirtualTzramVolatileData.GetAddress() + AMS_OFFSETOF(VolatileData, core_exception_stacks) + CoreExceptionStackSize; + constexpr inline const Address Core1ExceptionStackAddress = Core0ExceptionStackAddress + CoreExceptionStackSize; + constexpr inline const Address Core2ExceptionStackAddress = Core1ExceptionStackAddress + CoreExceptionStackSize; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/tsec.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/tsec.hpp new file mode 100644 index 00000000..49b89667 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/tsec.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::tsec { + + bool RunTsecFirmware(const void *fw, size_t fw_size); + + void Lock(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/uart.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/uart.hpp new file mode 100644 index 00000000..c49a9312 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/uart.hpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::uart { + + enum Port { + Port_A = 0, + Port_B = 1, + Port_C = 2, + + Port_Count = 3, + + Port_ReservedDebug = Port_A, + Port_RightJoyCon = Port_B, + Port_LeftJoyCon = Port_C, + }; + + enum Flags { + Flag_None = (0u << 0), + Flag_Inverted = (1u << 0), + }; + + void SetRegisterAddress(uintptr_t address); + + void Initialize(Port port, int baud_rate, u32 flags); + + void SendText(Port port, const void *data, size_t size); + + void WaitFlush(Port port); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/util.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/util.hpp new file mode 100644 index 00000000..ad916bc2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/util.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::util { + + void SetRegisterAddress(uintptr_t address); + + void ClearMemory(void *ptr, size_t size); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/wdt.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/wdt.hpp new file mode 100644 index 00000000..79a396c2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/include/exosphere/wdt.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::wdt { + + void SetRegisterAddress(uintptr_t address); + void Reboot(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/libexosphere.mk b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/libexosphere.mk new file mode 100644 index 00000000..616e1e53 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/libexosphere.mk @@ -0,0 +1,146 @@ +#--------------------------------------------------------------------------------- +# pull in common atmosphere configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(CURRENT_DIRECTORY)/../config/common.mk + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +PRECOMPILED_HEADERS := + +ifeq ($(strip $(ATMOSPHERE_ARCH_NAME)),arm64) + +ifeq ($(ATMOSPHERE_BUILD_FOR_DEBUGGING),1) +ATMOSPHERE_OPTIMIZATION_FLAG := -Os +else +ATMOSPHERE_OPTIMIZATION_FLAG := -Os +endif + +else + +ifeq ($(ATMOSPHERE_BUILD_FOR_DEBUGGING),1) +ATMOSPHERE_OPTIMIZATION_FLAG := -Os +else +ATMOSPHERE_OPTIMIZATION_FLAG := -O2 +endif + +endif + +DEFINES := $(ATMOSPHERE_DEFINES) -DATMOSPHERE_IS_EXOSPHERE -DAMS_FORCE_DISABLE_DETAILED_ASSERTIONS +SETTINGS := $(ATMOSPHERE_SETTINGS) $(ATMOSPHERE_OPTIMIZATION_FLAG) -Wextra -Werror -fno-non-call-exceptions -Wno-array-bounds -Wno-stringop-overflow -Wno-stringop-overread + +ifeq ($(strip $(ATMOSPHERE_ARCH_NAME)),arm64) +SETTINGS += -mgeneral-regs-only -ffixed-x18 +else ifeq ($(strip $(ATMOSPHERE_ARCH_NAME)),arm) +SETTINGS += -flto +endif + +CFLAGS := $(ATMOSPHERE_CFLAGS) $(SETTINGS) $(DEFINES) $(INCLUDE) +CXXFLAGS := $(CFLAGS) $(ATMOSPHERE_CXXFLAGS) -fno-use-cxa-atexit +ASFLAGS := $(ATMOSPHERE_ASFLAGS) $(SETTINGS) $(DEFINES) $(INCLUDE) + +SOURCES += $(call ALL_SOURCE_DIRS,../libvapours/source) + +LIBS := + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(ATMOSPHERE_LIBRARIES_DIR)/libvapours + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +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) + +#--------------------------------------------------------------------------------- +# 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 GCH_DIRS := $(PRECOMPILED_HEADERS:.hpp=.hpp.gch) +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. + +#--------------------------------------------------------------------------------- + +.PHONY: clean all + +all: $(ATMOSPHERE_LIBRARY_DIR) $(ATMOSPHERE_BUILD_DIR) $(SOURCES) $(INCLUDES) $(GCH_DIRS) $(CPPFILES) $(CFILES) $(SFILES) + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_LIBRARY_DIR)/$(TARGET).a \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +#--------------------------------------------------------------------------------- +clean: + @echo clean $(ATMOSPHERE_BUILD_NAME) ... + @rm -fr $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_OUT_DIR) + @rm -fr $(foreach hdr,$(GCH_DIRS),$(hdr)/$(ATMOSPHERE_GCH_IDENTIFIER)) + @for i in $(GCH_DIRS); do [ -d $$i ] && rmdir $$i 2>/dev/null || true; done + +$(ATMOSPHERE_LIBRARY_DIR) $(ATMOSPHERE_BUILD_DIR) $(GCH_DIRS): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +else + +GCH_FILES := $(foreach hdr,$(PRECOMPILED_HEADERS:.hpp=.hpp.gch),$(CURRENT_DIRECTORY)/$(hdr)/$(ATMOSPHERE_GCH_IDENTIFIER)) +DEPENDS := $(OFILES:.o=.d) $(foreach hdr,$(GCH_FILES),$(notdir $(patsubst %.hpp.gch/,%.d,$(dir $(hdr))))) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT) : $(OFILES) + +$(OFILES) : $(GCH_FILES) + +$(OFILES_SRC) : $(HFILES_BIN) + +libc.o: CFLAGS += -fno-builtin + +ifeq ($(strip $(ATMOSPHERE_ARCH_NAME)),arm) +libc.o: CFLAGS += -fno-lto +util_api.o: CFLAGS += -fno-lto +endif + +#--------------------------------------------------------------------------------- +%_bin.h %.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/actmon/actmon_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/actmon/actmon_api.cpp new file mode 100644 index 00000000..9ddfc4da --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/actmon/actmon_api.cpp @@ -0,0 +1,94 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "actmon_registers.hpp" + +namespace ams::actmon { + + namespace { + + constinit uintptr_t g_register_address = secmon::MemoryRegionPhysicalDeviceActivityMonitor.GetAddress(); + + constinit InterruptHandler g_interrupt_handler = nullptr; + + } + + void SetRegisterAddress(uintptr_t address) { + g_register_address = address; + } + + void HandleInterrupt() { + /* Get the registers. */ + const uintptr_t ACTMON = g_register_address; + + /* Disable the actmon interrupt. */ + reg::Write(ACTMON + ACTMON_COP_CTRL, ACTMON_REG_BITS_ENUM(COP_CTRL_ENB, DISABLE)); + + /* Update the interrupt status. */ + reg::Write(ACTMON + ACTMON_COP_INTR_STATUS, reg::Read(ACTMON + ACTMON_COP_INTR_STATUS)); + + /* Invoke the handler. */ + if (g_interrupt_handler != nullptr) { + g_interrupt_handler(); + g_interrupt_handler = nullptr; + } + } + + void StartMonitoringBpmp(InterruptHandler handler) { + /* Get the registers. */ + const uintptr_t ACTMON = g_register_address; + + /* Configure the activity monitor to poll once per microsecond. */ + reg::Write(ACTMON + ACTMON_GLB_PERIOD_CTRL, ACTMON_REG_BITS_ENUM (GLB_PERIOD_CTRL_SOURCE, USEC), + ACTMON_REG_BITS_VALUE(GLB_PERIOD_CTRL_SAMPLE_PERIOD, 0)); + + /* Configure the activity monitor to generate an interrupt the first time the event occurs. */ + reg::Write(ACTMON + ACTMON_COP_UPPER_WMARK, 0); + + /* Set the interrupt handler. */ + g_interrupt_handler = handler; + + /* Configure the activity monitor to generate events whenever the bpmp is woken up. */ + reg::Write(ACTMON + ACTMON_COP_CTRL, ACTMON_REG_BITS_ENUM (COP_CTRL_ENB, ENABLE), + ACTMON_REG_BITS_ENUM (COP_CTRL_CONSECUTIVE_ABOVE_WMARK_EN, ENABLE), + ACTMON_REG_BITS_ENUM (COP_CTRL_CONSECUTIVE_BELOW_WMARK_EN, DISABLE), + ACTMON_REG_BITS_VALUE(COP_CTRL_ABOVE_WMARK_NUM, 0), + ACTMON_REG_BITS_VALUE(COP_CTRL_BELOW_WMARK_NUM, 0), + ACTMON_REG_BITS_ENUM (COP_CTRL_WHEN_OVERFLOW_EN, DISABLE), + ACTMON_REG_BITS_ENUM (COP_CTRL_AVG_ABOVE_WMARK_EN, DISABLE), + ACTMON_REG_BITS_ENUM (COP_CTRL_AVG_BELOW_WMARK_EN, DISABLE), + ACTMON_REG_BITS_ENUM (COP_CTRL_AT_END_EN, DISABLE), + ACTMON_REG_BITS_ENUM (COP_CTRL_ENB_PERIODIC, ENABLE)); + + /* Read the activity monitor control register to make sure our configuration takes. */ + reg::Read(ACTMON + ACTMON_COP_CTRL); + } + + void StopMonitoringBpmp() { + /* Get the registers. */ + const uintptr_t ACTMON = g_register_address; + + /* Disable the actmon interrupt. */ + reg::Write(ACTMON + ACTMON_COP_CTRL, ACTMON_REG_BITS_ENUM(COP_CTRL_ENB, DISABLE)); + + /* Update the interrupt status. */ + reg::Write(ACTMON + ACTMON_COP_INTR_STATUS, reg::Read(ACTMON + ACTMON_COP_INTR_STATUS)); + + /* Clear the interrupt handler. */ + g_interrupt_handler = nullptr; + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/actmon/actmon_registers.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/actmon/actmon_registers.hpp new file mode 100644 index 00000000..761b4b8a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/actmon/actmon_registers.hpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::actmon { + + #define ACTMON_GLB_PERIOD_CTRL (0x004) + #define ACTMON_COP_CTRL (0x0C0) + #define ACTMON_COP_UPPER_WMARK (0x0C4) + #define ACTMON_COP_INTR_STATUS (0x0E4) + + /* Actmon source enums. */ + #define ACTMON_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (ACTMON, NAME) + #define ACTMON_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (ACTMON, NAME, VALUE) + #define ACTMON_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (ACTMON, NAME, ENUM) + #define ACTMON_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(ACTMON, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + + #define DEFINE_ACTMON_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (ACTMON, NAME, __OFFSET__, __WIDTH__) + #define DEFINE_ACTMON_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (ACTMON, NAME, __OFFSET__, ZERO, ONE) + #define DEFINE_ACTMON_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (ACTMON, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) + #define DEFINE_ACTMON_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(ACTMON, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) + #define DEFINE_ACTMON_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (ACTMON, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + + DEFINE_ACTMON_REG(GLB_PERIOD_CTRL_SAMPLE_PERIOD, 0, 8); + DEFINE_ACTMON_REG_BIT_ENUM(GLB_PERIOD_CTRL_SOURCE, 8, MSEC, USEC); + + DEFINE_ACTMON_REG(COP_CTRL_K_VAL, 10, 3); + DEFINE_ACTMON_REG_BIT_ENUM(COP_CTRL_ENB_PERIODIC, 18, DISABLE, ENABLE); + DEFINE_ACTMON_REG_BIT_ENUM(COP_CTRL_AT_END_EN, 19, DISABLE, ENABLE); + DEFINE_ACTMON_REG_BIT_ENUM(COP_CTRL_AVG_BELOW_WMARK_EN, 20, DISABLE, ENABLE); + DEFINE_ACTMON_REG_BIT_ENUM(COP_CTRL_AVG_ABOVE_WMARK_EN, 21, DISABLE, ENABLE); + DEFINE_ACTMON_REG_BIT_ENUM(COP_CTRL_WHEN_OVERFLOW_EN, 22, DISABLE, ENABLE); + DEFINE_ACTMON_REG(COP_CTRL_BELOW_WMARK_NUM, 23, 3); + DEFINE_ACTMON_REG(COP_CTRL_ABOVE_WMARK_NUM, 26, 3); + DEFINE_ACTMON_REG_BIT_ENUM(COP_CTRL_CONSECUTIVE_BELOW_WMARK_EN, 29, DISABLE, ENABLE); + DEFINE_ACTMON_REG_BIT_ENUM(COP_CTRL_CONSECUTIVE_ABOVE_WMARK_EN, 30, DISABLE, ENABLE); + DEFINE_ACTMON_REG_BIT_ENUM(COP_CTRL_ENB, 31, DISABLE, ENABLE); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/charger/charger_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/charger/charger_api.cpp new file mode 100644 index 00000000..0b5f33aa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/charger/charger_api.cpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::charger { + + namespace { + + /* https://www.ti.com/lit/ds/symlink/bq24193.pdf */ + constexpr inline int I2cAddressBq24193 = 0x6B; + + constexpr inline int Bq24193RegisterInputSourceControl = 0x00; + + /* 8.5.1.1 EN_HIZ */ + enum EnHiZ : u8 { + EnHiZ_Disable = (0u << 7), + EnHiZ_Enable = (1u << 7), + + EnHiZ_Mask = (1u << 7), + }; + + } + + bool IsHiZMode() { + return (i2c::QueryByte(i2c::Port_1, I2cAddressBq24193, Bq24193RegisterInputSourceControl) & EnHiZ_Mask) == EnHiZ_Enable; + } + + void EnterHiZMode() { + u8 ctrl = i2c::QueryByte(i2c::Port_1, I2cAddressBq24193, Bq24193RegisterInputSourceControl); + ctrl &= ~EnHiZ_Mask; + ctrl |= EnHiZ_Enable; + i2c::SendByte(i2c::Port_1, I2cAddressBq24193, Bq24193RegisterInputSourceControl, ctrl); + } + + void ExitHiZMode() { + u8 ctrl = i2c::QueryByte(i2c::Port_1, I2cAddressBq24193, Bq24193RegisterInputSourceControl); + ctrl &= ~EnHiZ_Mask; + ctrl |= EnHiZ_Disable; + i2c::SendByte(i2c::Port_1, I2cAddressBq24193, Bq24193RegisterInputSourceControl, ctrl); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/clkrst/clkrst_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/clkrst/clkrst_api.cpp new file mode 100644 index 00000000..0c05f5ad --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/clkrst/clkrst_api.cpp @@ -0,0 +1,357 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::clkrst { + + namespace { + + constinit uintptr_t g_register_address = secmon::MemoryRegionPhysicalDeviceClkRst.GetAddress(); + + constinit BpmpClockRate g_bpmp_clock_rate = BpmpClockRate_408MHz; + + struct ClockParameters { + uintptr_t reset_offset; + uintptr_t clk_enb_offset; + uintptr_t clk_src_offset; + u8 index; + u8 clk_src; + u8 clk_div; + }; + + void EnableClock(const ClockParameters ¶m) { + /* Hold reset. */ + reg::ReadWrite(g_register_address + param.reset_offset, REG_BITS_VALUE(param.index, 1, 1)); + + /* Disable clock. */ + reg::ReadWrite(g_register_address + param.clk_enb_offset, REG_BITS_VALUE(param.index, 1, 0)); + + /* Set the clock source. */ + if (param.clk_src_offset != 0) { + reg::Write(g_register_address + param.clk_src_offset, (param.clk_src << 29) | (param.clk_div << 0)); + } + + /* Enable clk. */ + reg::ReadWrite(g_register_address + param.clk_enb_offset, REG_BITS_VALUE(param.index, 1, 1)); + + /* Release reset. */ + reg::ReadWrite(g_register_address + param.reset_offset, REG_BITS_VALUE(param.index, 1, 0)); + } + + void DisableClock(const ClockParameters ¶m) { + /* Hold reset. */ + reg::ReadWrite(g_register_address + param.reset_offset, REG_BITS_VALUE(param.index, 1, 1)); + + /* Disable clock. */ + reg::ReadWrite(g_register_address + param.clk_enb_offset, REG_BITS_VALUE(param.index, 1, 0)); + } + + #define DEFINE_CLOCK_PARAMETERS(_VARNAME_, _REG_, _NAME_, _CLK_, _DIV_) \ + constexpr inline const ClockParameters _VARNAME_ = { \ + .reset_offset = CLK_RST_CONTROLLER_RST_DEVICES_##_REG_, \ + .clk_enb_offset = CLK_RST_CONTROLLER_CLK_OUT_ENB_##_REG_, \ + .clk_src_offset = CLK_RST_CONTROLLER_CLK_SOURCE_##_NAME_, \ + .index = CLK_RST_CONTROLLER_CLK_ENB_##_NAME_##_INDEX, \ + .clk_src = CLK_RST_CONTROLLER_CLK_SOURCE_##_NAME_##_##_NAME_##_CLK_SRC_##_CLK_, \ + .clk_div = _DIV_, \ + } + + #define DEFINE_CLOCK_PARAMETERS_WITHOUT_CLKDIV(_VARNAME_, _REG_, _NAME_) \ + constexpr inline const ClockParameters _VARNAME_ = { \ + .reset_offset = CLK_RST_CONTROLLER_RST_DEVICES_##_REG_, \ + .clk_enb_offset = CLK_RST_CONTROLLER_CLK_OUT_ENB_##_REG_, \ + .clk_src_offset = 0, \ + .index = CLK_RST_CONTROLLER_CLK_ENB_##_NAME_##_INDEX, \ + .clk_src = 0, \ + .clk_div = 0, \ + } + + DEFINE_CLOCK_PARAMETERS(UartAClock, L, UARTA, PLLP_OUT0, 0); + DEFINE_CLOCK_PARAMETERS(UartBClock, L, UARTB, PLLP_OUT0, 0); + DEFINE_CLOCK_PARAMETERS(UartCClock, H, UARTC, PLLP_OUT0, 0); + DEFINE_CLOCK_PARAMETERS(I2c1Clock, L, I2C1, CLK_M, 0); + DEFINE_CLOCK_PARAMETERS(I2c5Clock, H, I2C5, CLK_M, 0); + DEFINE_CLOCK_PARAMETERS(SeClock, V, SE, PLLP_OUT0, 0); + DEFINE_CLOCK_PARAMETERS(ActmonClock, V, ACTMON, CLK_M, 0); + + DEFINE_CLOCK_PARAMETERS(CsiteClock, U, CSITE, PLLP_OUT0, 4); + DEFINE_CLOCK_PARAMETERS(Host1xClock, L, HOST1X, PLLP_OUT0, 3); + DEFINE_CLOCK_PARAMETERS(TsecClock, U, TSEC, PLLP_OUT0, 2); + DEFINE_CLOCK_PARAMETERS(Sor1Clock, X, SOR1, PLLP_OUT0, 2); + + DEFINE_CLOCK_PARAMETERS_WITHOUT_CLKDIV(CldvfsClock, W, DVFS); + + DEFINE_CLOCK_PARAMETERS_WITHOUT_CLKDIV(TzramClock, V, TZRAM); + + DEFINE_CLOCK_PARAMETERS_WITHOUT_CLKDIV(SorSafeClock, Y, SOR_SAFE); + DEFINE_CLOCK_PARAMETERS_WITHOUT_CLKDIV(Sor0Clock, X, SOR0); + DEFINE_CLOCK_PARAMETERS_WITHOUT_CLKDIV(KfuseClock, H, KFUSE); + + DEFINE_CLOCK_PARAMETERS_WITHOUT_CLKDIV(Cache2Clock, L, CACHE2); + DEFINE_CLOCK_PARAMETERS_WITHOUT_CLKDIV(Cram2Clock, U, CRAM2); + + constexpr const u32 PllcDivn[] = { + [BpmpClockRate_408MHz] = 0, + [BpmpClockRate_544MHz] = 85, + [BpmpClockRate_576MHz] = 90, + [BpmpClockRate_589MHz] = 92, + }; + + void EnablePllc(BpmpClockRate rate) { + const u32 desired_divn = PllcDivn[rate]; + + /* Check if we're already enabled. */ + const bool is_enabled = reg::HasValue(g_register_address + CLK_RST_CONTROLLER_PLLC_BASE, CLK_RST_REG_BITS_ENUM(PLLC_BASE_PLLC_ENABLE, ENABLE)); + const bool is_good_divn = reg::HasValue(g_register_address + CLK_RST_CONTROLLER_PLLC_BASE, CLK_RST_REG_BITS_VALUE(PLLC_BASE_PLLC_DIVN, desired_divn)); + if (is_enabled && is_good_divn) { + return; + } + + /* Take PLLC out of reset. */ + reg::Write(g_register_address + CLK_RST_CONTROLLER_PLLC_MISC, (reg::Read(g_register_address + CLK_RST_CONTROLLER_PLLC_MISC) & 0xBFF0000F) | (0x80000 << 4)); + reg::SetBits(g_register_address + CLK_RST_CONTROLLER_PLLC_MISC2, 0xF0 << 8); + + /* Disable pll. */ + reg::ReadWrite(g_register_address + CLK_RST_CONTROLLER_PLLC_BASE, CLK_RST_REG_BITS_ENUM(PLLC_BASE_PLLC_ENABLE, DISABLE)); + reg::ClearBits(g_register_address + CLK_RST_CONTROLLER_PLLC_MISC1, (1u << 27)); + util::WaitMicroSeconds(10); + + /* Set dividers. */ + reg::Write(g_register_address + CLK_RST_CONTROLLER_PLLC_BASE, CLK_RST_REG_BITS_VALUE(PLLC_BASE_PLLC_DIVM, 4), + CLK_RST_REG_BITS_VALUE(PLLC_BASE_PLLC_DIVN, desired_divn)); + + /* Enable pll. */ + reg::ReadWrite(g_register_address + CLK_RST_CONTROLLER_PLLC_BASE, CLK_RST_REG_BITS_ENUM(PLLC_BASE_PLLC_ENABLE, ENABLE)); + while (!reg::HasValue(g_register_address + CLK_RST_CONTROLLER_PLLC_BASE, CLK_RST_REG_BITS_ENUM(PLLC_BASE_PLLC_LOCK, LOCK))) { + /* ... */ + } + + /* Disable PLLC_OUT1. */ + reg::Write(g_register_address + CLK_RST_CONTROLLER_PLLC_OUT, CLK_RST_REG_BITS_VALUE(PLLC_OUT_PLLC_OUT1_RATIO, 1)); + + /* Enable PLLC_OUT1. */ + reg::ReadWrite(g_register_address + CLK_RST_CONTROLLER_PLLC_OUT, CLK_RST_REG_BITS_ENUM(PLLC_OUT_PLLC_OUT1_RSTN, RESET_DISABLE), + CLK_RST_REG_BITS_ENUM(PLLC_OUT_PLLC_OUT1_CLKEN, ENABLE)); + util::WaitMicroSeconds(1'000); + } + + void DisablePllc() { + /* Disable PLLC/PLLC_OUT1. */ + reg::ReadWrite(g_register_address + CLK_RST_CONTROLLER_PLLC_OUT, CLK_RST_REG_BITS_ENUM(PLLC_OUT_PLLC_OUT1_RSTN, RESET_ENABLE), + CLK_RST_REG_BITS_ENUM(PLLC_OUT_PLLC_OUT1_CLKEN, DISABLE)); + + reg::ReadWrite(g_register_address + CLK_RST_CONTROLLER_PLLC_BASE, CLK_RST_REG_BITS_ENUM(PLLC_BASE_PLLC_ENABLE, DISABLE)); + reg::ReadWrite(g_register_address + CLK_RST_CONTROLLER_PLLC_BASE, CLK_RST_REG_BITS_ENUM(PLLC_BASE_PLLC_REF_DIS, REF_DISABLE)); + reg::SetBits(g_register_address + CLK_RST_CONTROLLER_PLLC_MISC1, (1u << 27)); + reg::SetBits(g_register_address + CLK_RST_CONTROLLER_PLLC_MISC, (1u << 30)); + util::WaitMicroSeconds(10); + } + + } + + void SetRegisterAddress(uintptr_t address) { + g_register_address = address; + } + + void SetFuseVisibility(bool visible) { + reg::ReadWrite(g_register_address + CLK_RST_CONTROLLER_MISC_CLK_ENB, CLK_RST_REG_BITS_VALUE(MISC_CLK_ENB_CFG_ALL_VISIBLE, visible ? 1 : 0)); + } + + void EnableUartAClock() { + EnableClock(UartAClock); + } + + void EnableUartBClock() { + EnableClock(UartBClock); + } + + void EnableUartCClock() { + EnableClock(UartCClock); + } + + void EnableActmonClock() { + EnableClock(ActmonClock); + } + + void EnableI2c1Clock() { + EnableClock(I2c1Clock); + } + + void EnableI2c5Clock() { + EnableClock(I2c5Clock); + } + + void EnableSeClock() { + EnableClock(SeClock); + if (fuse::GetSocType() == fuse::SocType_Mariko) { + reg::ReadWrite(g_register_address + CLK_RST_CONTROLLER_CLK_SOURCE_SE, CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SE_CLK_LOCK, ENABLE)); + } + } + + void EnableCldvfsClock() { + EnableClock(CldvfsClock); + } + + void EnableCsiteClock() { + EnableClock(CsiteClock); + } + + void EnableTzramClock() { + EnableClock(TzramClock); + } + + void EnableCache2Clock() { + EnableClock(Cache2Clock); + } + + void EnableCram2Clock() { + EnableClock(Cram2Clock); + } + + void EnableHost1xClock() { + EnableClock(Host1xClock); + } + + void EnableTsecClock() { + EnableClock(TsecClock); + } + + void EnableSorSafeClock() { + EnableClock(SorSafeClock); + } + + void EnableSor0Clock() { + EnableClock(Sor0Clock); + } + + void EnableSor1Clock() { + EnableClock(Sor1Clock); + } + + void EnableKfuseClock() { + EnableClock(KfuseClock); + } + + void DisableI2c1Clock() { + DisableClock(I2c1Clock); + } + + void DisableHost1xClock() { + DisableClock(Host1xClock); + } + + void DisableTsecClock() { + DisableClock(TsecClock); + } + + void DisableSorSafeClock() { + DisableClock(SorSafeClock); + } + + void DisableSor0Clock() { + DisableClock(Sor0Clock); + } + + void DisableSor1Clock() { + DisableClock(Sor1Clock); + } + + void DisableKfuseClock() { + DisableClock(KfuseClock); + } + + BpmpClockRate GetBpmpClockRate() { + return g_bpmp_clock_rate; + } + + BpmpClockRate SetBpmpClockRate(BpmpClockRate rate) { + /* Get the current rate. */ + const auto prev_rate = g_bpmp_clock_rate; + + /* Cap our rate. */ + if (rate >= BpmpClockRate_Count) { + rate = BpmpClockRate_589MHz; + } + + /* Change the rate, if we need to. */ + if (rate == prev_rate) { + return prev_rate; + } + + /* Configure the rate. */ + if (rate != BpmpClockRate_408MHz) { + /* If we were previously overclocked, restore to PLLP_OUT. */ + if (prev_rate != BpmpClockRate_408MHz) { + reg::Write(g_register_address + CLK_RST_CONTROLLER_SCLK_BURST_POLICY, CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SYS_STATE, RUN), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_COP_AUTO_SWAKEUP_FROM_FIQ, NOP), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_CPU_AUTO_SWAKEUP_FROM_FIQ, NOP), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_COP_AUTO_SWAKEUP_FROM_IRQ, NOP), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_CPU_AUTO_SWAKEUP_FROM_IRQ, NOP), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_FIQ_SOURCE, PLLP_OUT0), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_IRQ_SOURCE, PLLP_OUT0), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_RUN_SOURCE, PLLP_OUT0), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_IDLE_SOURCE, PLLP_OUT0)); + util::WaitMicroSeconds(1'000); + } + + /* Configure PLLC. */ + EnablePllc(rate); + + /* Set SCLK. */ + reg::Write(g_register_address + CLK_RST_CONTROLLER_CLK_SYSTEM_RATE, CLK_RST_REG_BITS_VALUE(CLK_SYSTEM_RATE_HCLK_DIS, 0), + CLK_RST_REG_BITS_VALUE(CLK_SYSTEM_RATE_AHB_RATE, 0), + CLK_RST_REG_BITS_VALUE(CLK_SYSTEM_RATE_PCLK_DIS, 0), + CLK_RST_REG_BITS_VALUE(CLK_SYSTEM_RATE_APB_RATE, 3)); + + reg::Write(g_register_address + CLK_RST_CONTROLLER_SCLK_BURST_POLICY, CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SYS_STATE, RUN), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_COP_AUTO_SWAKEUP_FROM_FIQ, NOP), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_CPU_AUTO_SWAKEUP_FROM_FIQ, NOP), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_COP_AUTO_SWAKEUP_FROM_IRQ, NOP), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_CPU_AUTO_SWAKEUP_FROM_IRQ, NOP), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_FIQ_SOURCE, PLLP_OUT0), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_IRQ_SOURCE, PLLP_OUT0), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_RUN_SOURCE, PLLC_OUT1), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_IDLE_SOURCE, CLKM)); + } else { + /* Configure to use PLLP_OUT0. */ + reg::Write(g_register_address + CLK_RST_CONTROLLER_SCLK_BURST_POLICY, CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SYS_STATE, RUN), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_COP_AUTO_SWAKEUP_FROM_FIQ, NOP), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_CPU_AUTO_SWAKEUP_FROM_FIQ, NOP), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_COP_AUTO_SWAKEUP_FROM_IRQ, NOP), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_CPU_AUTO_SWAKEUP_FROM_IRQ, NOP), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_FIQ_SOURCE, PLLP_OUT0), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_IRQ_SOURCE, PLLP_OUT0), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_RUN_SOURCE, PLLP_OUT0), + CLK_RST_REG_BITS_ENUM(SCLK_BURST_POLICY_SWAKEUP_IDLE_SOURCE, CLKM)); + util::WaitMicroSeconds(1'000); + + reg::Write(g_register_address + CLK_RST_CONTROLLER_CLK_SYSTEM_RATE, CLK_RST_REG_BITS_VALUE(CLK_SYSTEM_RATE_HCLK_DIS, 0), + CLK_RST_REG_BITS_VALUE(CLK_SYSTEM_RATE_AHB_RATE, 0), + CLK_RST_REG_BITS_VALUE(CLK_SYSTEM_RATE_PCLK_DIS, 0), + CLK_RST_REG_BITS_VALUE(CLK_SYSTEM_RATE_APB_RATE, 2)); + + /* Disable PLLC. */ + DisablePllc(); + } + + /* Set the clock rate. */ + g_bpmp_clock_rate = rate; + + /* Return the previous rate. */ + return prev_rate; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/crypto/crypto_aes_impl_security_engine.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/crypto/crypto_aes_impl_security_engine.cpp new file mode 100644 index 00000000..0cf3f4b4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/crypto/crypto_aes_impl_security_engine.cpp @@ -0,0 +1,95 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::crypto::impl { + + namespace { + + constexpr bool IsSupportedKeySize(size_t size) { + return size == 16 || size == 24 || size == 32; + } + + } + + template<size_t KeySize> + AesImpl<KeySize>::~AesImpl() { + ClearMemory(this, sizeof(*this)); + } + + template<size_t KeySize> + void AesImpl<KeySize>::Initialize(const void *key, size_t key_size, bool is_encrypt) { + static_assert(IsSupportedKeySize(KeySize)); + AMS_ASSERT(key_size == sizeof(int)); + AMS_UNUSED(key_size, is_encrypt); + + /* Set the security engine keyslot. */ + m_slot = *static_cast<const int *>(key); + } + + template<size_t KeySize> + void AesImpl<KeySize>::EncryptBlock(void *dst, size_t dst_size, const void *src, size_t src_size) const { + static_assert(IsSupportedKeySize(KeySize)); + AMS_ASSERT(src_size >= BlockSize); + AMS_ASSERT(dst_size >= BlockSize); + + if constexpr (KeySize == 16) { + /* Aes 128. */ + se::EncryptAes128(dst, dst_size, m_slot, src, src_size); + } else if constexpr (KeySize == 24) { + /* Aes 192. */ + /* TODO: se::EncryptAes192(dst, dst_size, m_slot, src, src_size); */ + AMS_UNUSED(dst, dst_size, src, src_size); + } else if constexpr (KeySize == 32) { + /* Aes 256. */ + /* TODO: se::EncryptAes256(dst, dst_size, m_slot, src, src_size); */ + AMS_UNUSED(dst, dst_size, src, src_size); + } else { + /* Invalid key size. */ + static_assert(!std::is_same<AesImpl<KeySize>, AesImpl<KeySize>>::value); + } + } + + template<size_t KeySize> + void AesImpl<KeySize>::DecryptBlock(void *dst, size_t dst_size, const void *src, size_t src_size) const { + static_assert(IsSupportedKeySize(KeySize)); + AMS_ASSERT(src_size >= BlockSize); + AMS_ASSERT(dst_size >= BlockSize); + + if constexpr (KeySize == 16) { + /* Aes 128. */ + se::DecryptAes128(dst, dst_size, m_slot, src, src_size); + } else if constexpr (KeySize == 24) { + /* Aes 192. */ + /* TODO: se::DecryptAes192(dst, dst_size, m_slot, src, src_size); */ + AMS_UNUSED(dst, dst_size, src, src_size); + } else if constexpr (KeySize == 32) { + /* Aes 256. */ + /* TODO: se::DecryptAes256(dst, dst_size, m_slot, src, src_size); */ + AMS_UNUSED(dst, dst_size, src, src_size); + } else { + /* Invalid key size. */ + static_assert(!std::is_same<AesImpl<KeySize>, AesImpl<KeySize>>::value); + } + } + + + /* Explicitly instantiate the three supported key sizes. */ + template class AesImpl<16>; + template class AesImpl<24>; + template class AesImpl<32>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/flow/flow_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/flow/flow_api.cpp new file mode 100644 index 00000000..e026a9fe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/flow/flow_api.cpp @@ -0,0 +1,83 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::flow { + + namespace { + + struct FlowControllerRegisterOffset { + u16 cpu_csr; + u16 halt_cpu_events; + u16 cc4_core_ctrl; + }; + + constinit uintptr_t g_register_address = secmon::MemoryRegionPhysicalDeviceFlowController.GetAddress(); + + constexpr const FlowControllerRegisterOffset FlowControllerRegisterOffsets[] = { + { FLOW_CTLR_CPU0_CSR, FLOW_CTLR_HALT_CPU0_EVENTS, FLOW_CTLR_CC4_CORE0_CTRL, }, + { FLOW_CTLR_CPU1_CSR, FLOW_CTLR_HALT_CPU1_EVENTS, FLOW_CTLR_CC4_CORE1_CTRL, }, + { FLOW_CTLR_CPU2_CSR, FLOW_CTLR_HALT_CPU2_EVENTS, FLOW_CTLR_CC4_CORE2_CTRL, }, + { FLOW_CTLR_CPU3_CSR, FLOW_CTLR_HALT_CPU3_EVENTS, FLOW_CTLR_CC4_CORE3_CTRL, }, + }; + + constexpr u32 GetHaltCpuEventsValue(bool resume_on_irq) { + if (resume_on_irq) { + return reg::Encode(FLOW_REG_BITS_ENUM(HALT_CPUN_EVENTS_FLOW_MODE, WAITEVENT), + FLOW_REG_BITS_ENUM(HALT_CPUN_EVENTS_LIC_IRQN, ENABLE), + FLOW_REG_BITS_ENUM(HALT_CPUN_EVENTS_LIC_FIQN, ENABLE), + FLOW_REG_BITS_ENUM(HALT_CPUN_EVENTS_GIC_IRQN, ENABLE), + FLOW_REG_BITS_ENUM(HALT_CPUN_EVENTS_GIC_FIQN, ENABLE)); + } else { + return reg::Encode(FLOW_REG_BITS_ENUM(HALT_CPUN_EVENTS_FLOW_MODE, WAITEVENT)); + } + } + + } + + void SetRegisterAddress(uintptr_t address) { + g_register_address = address; + } + + void ResetCpuRegisters(int core) { + AMS_ASSUME(core >= 0); + + const auto &offsets = FlowControllerRegisterOffsets[core]; + reg::Write(g_register_address + offsets.cpu_csr, 0); + reg::Write(g_register_address + offsets.halt_cpu_events, 0); + } + + void SetCpuCsr(int core, u32 enable_ext) { + reg::Write(g_register_address + FlowControllerRegisterOffsets[core].cpu_csr, FLOW_REG_BITS_ENUM (CPUN_CSR_INTR_FLAG, TRUE), + FLOW_REG_BITS_ENUM (CPUN_CSR_EVENT_FLAG, TRUE), + FLOW_REG_BITS_VALUE(CPUN_CSR_ENABLE_EXT, enable_ext), + FLOW_REG_BITS_VALUE(CPUN_CSR_WAIT_WFI_BITMAP, (1u << core)), + FLOW_REG_BITS_ENUM (CPUN_CSR_ENABLE, ENABLE)); + } + + void SetHaltCpuEvents(int core, bool resume_on_irq) { + reg::Write(g_register_address + FlowControllerRegisterOffsets[core].halt_cpu_events, GetHaltCpuEventsValue(resume_on_irq)); + } + + void SetCc4Ctrl(int core, u32 value) { + reg::Write(g_register_address + FlowControllerRegisterOffsets[core].cc4_core_ctrl, value); + } + + void ClearL2FlushControl() { + reg::Write(g_register_address + FLOW_CTLR_L2FLUSH_CONTROL, 0); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/fuse/fuse_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/fuse/fuse_api.cpp new file mode 100644 index 00000000..837ad735 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/fuse/fuse_api.cpp @@ -0,0 +1,521 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "fuse_registers.hpp" + +namespace ams::fuse { + + namespace { + + static constexpr SocType SocType_CommonInternal = static_cast<SocType>(-1); + static_assert(SocType_CommonInternal != SocType_Erista); + static_assert(SocType_CommonInternal != SocType_Mariko); + + constinit SocType g_soc_type = SocType_CommonInternal; + + struct BypassEntry { + u32 offset; + u32 value; + }; + + struct OdmWord2 { + using DeviceUniqueKeyGeneration = util::BitPack32::Field<0, 5, int>; + using Reserved = util::BitPack32::Field<5, 27, int>; + }; + + struct OdmWord4 { + using HardwareState1 = util::BitPack32::Field<0, 2, int>; + using HardwareType1 = util::BitPack32::Field<HardwareState1::Next, 1, int>; + using DramId1 = util::BitPack32::Field<HardwareType1::Next, 5, int>; + using HardwareType2 = util::BitPack32::Field<DramId1::Next, 1, int>; + using HardwareState2 = util::BitPack32::Field<HardwareType2::Next, 1, int>; + using RetailInteractiveDisplayState = util::BitPack32::Field<HardwareState2::Next, 1, int>; + using FormatVersion = util::BitPack32::Field<RetailInteractiveDisplayState::Next, 1, int>; + using DramId2 = util::BitPack32::Field<FormatVersion::Next, 3, int>; + using Reserved = util::BitPack32::Field<DramId2::Next, 1, int>; + using HardwareType3 = util::BitPack32::Field<Reserved::Next, 4, int>; + }; + + struct OdmWord28 { + using Regulator = util::BitPack32::Field<0, 1, int>; + using Reserved = util::BitPack32::Field<1, 31, int>; + }; + + constexpr ALWAYS_INLINE int GetHardwareStateValue(const util::BitPack32 odm_word4) { + constexpr auto HardwareState1Shift = 0; + constexpr auto HardwareState2Shift = OdmWord4::HardwareState1::Count + HardwareState1Shift; + + return (odm_word4.Get<OdmWord4::HardwareState1>() << HardwareState1Shift) | + (odm_word4.Get<OdmWord4::HardwareState2>() << HardwareState2Shift); + } + + constexpr ALWAYS_INLINE int GetHardwareTypeValue(const util::BitPack32 odm_word4) { + constexpr auto HardwareType1Shift = 0; + constexpr auto HardwareType2Shift = OdmWord4::HardwareType1::Count + HardwareType1Shift; + constexpr auto HardwareType3Shift = OdmWord4::HardwareType2::Count + HardwareType2Shift; + + return (odm_word4.Get<OdmWord4::HardwareType1>() << HardwareType1Shift) | + (odm_word4.Get<OdmWord4::HardwareType2>() << HardwareType2Shift) | + (odm_word4.Get<OdmWord4::HardwareType3>() << HardwareType3Shift); + } + + constexpr ALWAYS_INLINE int GetDramIdValue(const util::BitPack32 odm_word4) { + constexpr auto DramId1Shift = 0; + constexpr auto DramId2Shift = OdmWord4::DramId1::Count + DramId1Shift; + + + return (odm_word4.Get<OdmWord4::DramId1>() << DramId1Shift) | + (odm_word4.Get<OdmWord4::DramId2>() << DramId2Shift); + } + + constinit uintptr_t g_register_address = secmon::MemoryRegionPhysicalDeviceFuses.GetAddress(); + + constinit bool g_checked_for_rcm_bug_patch = false; + constinit bool g_has_rcm_bug_patch = false; + + ALWAYS_INLINE volatile FuseRegisterRegion *GetRegisterRegion() { + return reinterpret_cast<volatile FuseRegisterRegion *>(g_register_address); + } + + ALWAYS_INLINE volatile FuseRegisters &GetRegisters() { + return GetRegisterRegion()->fuse; + } + + ALWAYS_INLINE volatile FuseChipRegistersCommon &GetChipRegistersCommon() { + return GetRegisterRegion()->chip_common; + } + + ALWAYS_INLINE volatile FuseChipRegistersErista &GetChipRegistersErista() { + return GetRegisterRegion()->chip_erista; + } + + ALWAYS_INLINE volatile FuseChipRegistersMariko &GetChipRegistersMariko() { + return GetRegisterRegion()->chip_mariko; + } + + bool IsIdle() { + return reg::HasValue(GetRegisters().FUSE_FUSECTRL, FUSE_REG_BITS_ENUM(FUSECTRL_STATE, IDLE)); + } + + void WaitForIdle() { + while (!IsIdle()) { /* ... */ } + } + + u32 GetOdmWordImpl(int index, fuse::SocType soc_type) { + if (index < 8) { + volatile auto &chip = GetChipRegistersCommon(); + return chip.FUSE_RESERVED_ODM_0[index - 0]; + } else if (soc_type == SocType_Mariko) { + volatile auto &chip = GetChipRegistersMariko(); + if (index < 22) { + return chip.FUSE_RESERVED_ODM_8[index - 8]; + } else if (index < 25) { + return chip.FUSE_RESERVED_ODM_22[index - 22]; + } else if (index < 26) { + return chip.FUSE_RESERVED_ODM_25[index - 25]; + } else if (index < 29) { + return chip.FUSE_RESERVED_ODM_26[index - 26]; + } else if (index < 30) { + return chip.FUSE_RESERVED_ODM_29[index - 29]; + } + } + AMS_ABORT("Invalid ODM fuse read"); + } + + u32 GetCommonOdmWord(int index) { + return GetOdmWordImpl(index, SocType_CommonInternal); + } + + bool IsNewFuseFormat() { + /* On mariko, this should always be true. */ + if (GetSocType() != SocType_Erista) { + return true; + } + + /* Require that the format version be non-zero in odm4. */ + if (util::BitPack32{GetCommonOdmWord(4)}.Get<OdmWord4::FormatVersion>() == 0) { + return false; + } + + /* Check that odm word 0/1 are fused with the magic values. */ + constexpr u32 NewFuseFormatMagic0 = 0x8E61ECAE; + constexpr u32 NewFuseFormatMagic1 = 0xF2BA3BB2; + + const u32 w0 = GetCommonOdmWord(0); + const u32 w1 = GetCommonOdmWord(1); + + return w0 == NewFuseFormatMagic0 && w1 == NewFuseFormatMagic1; + } + + constexpr u32 CompressLotCode(u32 lot0) { + constexpr int Radix = 36; + constexpr int Count = 5; + constexpr int Width = 6; + constexpr u32 Mask = (1u << Width) - 1; + + u32 compressed = 0; + + for (int i = Count - 1; i >= 0; --i) { + compressed *= Radix; + compressed += (lot0 >> (i * Width)) & Mask; + } + + return compressed; + } + + constexpr const TargetFirmware FuseVersionIncrementFirmwares[] = { + TargetFirmware_20_0_0, + TargetFirmware_19_0_0, + TargetFirmware_17_0_0, + TargetFirmware_16_0_0, + TargetFirmware_15_0_0, + TargetFirmware_13_2_1, + TargetFirmware_12_0_2, + TargetFirmware_11_0_0, + TargetFirmware_10_0_0, + TargetFirmware_9_1_0, + TargetFirmware_9_0_0, + TargetFirmware_8_1_0, + TargetFirmware_7_0_0, + TargetFirmware_6_2_0, + TargetFirmware_6_0_0, + TargetFirmware_5_0_0, + TargetFirmware_4_0_0, + TargetFirmware_3_0_2, + TargetFirmware_3_0_0, + TargetFirmware_2_0_0, + TargetFirmware_1_0_0, + }; + + constexpr inline int NumFuseIncrements = util::size(FuseVersionIncrementFirmwares); + + constexpr const BypassEntry FuseBypassEntries[] = { + /* Don't configure any fuse bypass entries. */ + }; + + constexpr inline int NumFuseBypassEntries = util::size(FuseBypassEntries); + + /* Verify that the fuse version increment list is sorted. */ + static_assert([] { + for (size_t i = 0; i < util::size(FuseVersionIncrementFirmwares) - 1; ++i) { + if (FuseVersionIncrementFirmwares[i] <= FuseVersionIncrementFirmwares[i + 1]) { + return false; + } + } + return true; + }()); + + constexpr int GetExpectedFuseVersionImpl(TargetFirmware target_fw) { + for (int i = 0; i < NumFuseIncrements; ++i) { + if (target_fw >= FuseVersionIncrementFirmwares[i]) { + return NumFuseIncrements - i; + } + } + return 0; + } + + static_assert(GetExpectedFuseVersionImpl(TargetFirmware_11_0_0) == 14); + static_assert(GetExpectedFuseVersionImpl(TargetFirmware_1_0_0) == 1); + static_assert(GetExpectedFuseVersionImpl(static_cast<TargetFirmware>(0)) == 0); + + } + + void SetRegisterAddress(uintptr_t address) { + g_register_address = address; + } + + void SetWriteSecureOnly() { + reg::Write(GetRegisters().FUSE_PRIVATEKEYDISABLE, FUSE_REG_BITS_ENUM(PRIVATEKEYDISABLE_TZ_STICKY_BIT_VAL, KEY_INVISIBLE)); + } + + void Lockout() { + reg::Write(GetRegisters().FUSE_DISABLEREGPROGRAM, FUSE_REG_BITS_ENUM(DISABLEREGPROGRAM_VAL, ENABLE)); + } + + u32 ReadWord(int address) { + /* Require that the fuse array be idle. */ + AMS_ABORT_UNLESS(IsIdle()); + + /* Get the registers. */ + volatile auto &FUSE = GetRegisters(); + + /* Write the address to read. */ + reg::Write(FUSE.FUSE_FUSEADDR, address); + + /* Set control to read. */ + reg::ReadWrite(FUSE.FUSE_FUSECTRL, FUSE_REG_BITS_ENUM(FUSECTRL_CMD, READ)); + + /* Wait 1 us. */ + util::WaitMicroSeconds(1); + + /* Wait for the array to be idle. */ + WaitForIdle(); + + return reg::Read(FUSE.FUSE_FUSERDATA); + } + + u32 GetOdmWord(int index) { + return GetOdmWordImpl(index, GetSocType()); + } + + void GetEcid(br::BootEcid *out) { + /* Get the registers. */ + volatile auto &chip = GetChipRegistersCommon(); + + /* Read the ecid components. */ + const u32 vendor = reg::Read(chip.FUSE_OPT_VENDOR_CODE) & ((1u << 4) - 1); + const u32 fab = reg::Read(chip.FUSE_OPT_FAB_CODE) & ((1u << 6) - 1); + const u32 lot0 = reg::Read(chip.FUSE_OPT_LOT_CODE_0) /* all 32 bits */ ; + const u32 lot1 = reg::Read(chip.FUSE_OPT_LOT_CODE_1) & ((1u << 28) - 1); + const u32 wafer = reg::Read(chip.FUSE_OPT_WAFER_ID) & ((1u << 6) - 1); + const u32 x_coord = reg::Read(chip.FUSE_OPT_X_COORDINATE) & ((1u << 9) - 1); + const u32 y_coord = reg::Read(chip.FUSE_OPT_Y_COORDINATE) & ((1u << 9) - 1); + const u32 reserved = reg::Read(chip.FUSE_OPT_OPS_RESERVED) & ((1u << 6) - 1); + + /* Clear the output. */ + util::ClearMemory(out, sizeof(*out)); + + /* Copy the component bits. */ + out->ecid[0] = static_cast<u32>((lot1 << 30) | (wafer << 24) | (x_coord << 15) | (y_coord << 6) | (reserved)); + out->ecid[1] = static_cast<u32>((lot0 << 26) | (lot1 >> 2)); + out->ecid[2] = static_cast<u32>((fab << 26) | (lot0 >> 6)); + out->ecid[3] = static_cast<u32>(vendor); + } + + u64 GetDeviceId() { + /* Get the registers. */ + volatile auto &chip = GetChipRegistersCommon(); + + /* Read the device id components. */ + /* NOTE: Device ID is "basically" just an alternate encoding of Ecid. */ + /* It elides lot1 (and compresses lot0), but this is fine because */ + /* lot1 is fixed-value for all fused devices. */ + const u64 fab = reg::Read(chip.FUSE_OPT_FAB_CODE) & ((1u << 6) - 1); + const u32 lot0 = reg::Read(chip.FUSE_OPT_LOT_CODE_0) /* all 32 bits */ ; + const u64 wafer = reg::Read(chip.FUSE_OPT_WAFER_ID) & ((1u << 6) - 1); + const u64 x_coord = reg::Read(chip.FUSE_OPT_X_COORDINATE) & ((1u << 9) - 1); + const u64 y_coord = reg::Read(chip.FUSE_OPT_Y_COORDINATE) & ((1u << 9) - 1); + + /* Compress lot0 down from 32-bits to 26. */ + const u64 clot0 = CompressLotCode(lot0) & ((1u << 26) - 1); + + return (y_coord << 0) | + (x_coord << 9) | + (wafer << 18) | + (clot0 << 24) | + (fab << 50); + } + + DramId GetDramId() { + /* Get the value. */ + return static_cast<DramId>(GetDramIdValue(util::BitPack32{GetCommonOdmWord(4)})); + } + + HardwareType GetHardwareType() { + /* Read the odm word. */ + const util::BitPack32 odm_word4 = { GetCommonOdmWord(4) }; + + /* Get the value. */ + const auto value = GetHardwareTypeValue(odm_word4); + + switch (value) { + case 0x01: return HardwareType_Icosa; + case 0x02: return (true /* TODO: GetSocType() == SocType_Mariko */) ? HardwareType_Calcio : HardwareType_Copper; + case 0x04: return HardwareType_Iowa; + case 0x08: return HardwareType_Hoag; + case 0x10: return HardwareType_Aula; + default: return HardwareType_Undefined; + } + } + + HardwareState GetHardwareState() { + /* Read the odm word. */ + const util::BitPack32 odm_word4 = { GetCommonOdmWord(4) }; + + /* Get the value. */ + const auto value = GetHardwareStateValue(odm_word4); + + switch (value) { + case 3: return HardwareState_Development; + case 4: return HardwareState_Production; + default: return HardwareState_Undefined; + } + } + + PatchVersion GetPatchVersion() { + const auto patch_version = reg::Read(GetChipRegistersCommon().FUSE_SOC_SPEEDO_1_CALIB); + return static_cast<PatchVersion>(static_cast<int>(GetSocType() << 12) | patch_version); + } + + RetailInteractiveDisplayState GetRetailInteractiveDisplayState() { + return static_cast<RetailInteractiveDisplayState>(util::BitPack32{GetCommonOdmWord(4)}.Get<OdmWord4::RetailInteractiveDisplayState>()); + } + + pmic::Regulator GetRegulator() { + if (GetSocType() == SocType_Mariko) { + /* Read the odm word. */ + const util::BitPack32 odm_word28 = { GetOdmWordImpl(28, SocType_Mariko) }; + + return static_cast<pmic::Regulator>(odm_word28.Get<OdmWord28::Regulator>() + 1); + } else /* if (GetSocType() == SocType_Erista) */ { + return pmic::Regulator_Erista_Max77621; + } + } + + int GetDeviceUniqueKeyGeneration() { + if (IsNewFuseFormat()) { + return util::BitPack32{GetCommonOdmWord(2)}.Get<OdmWord2::DeviceUniqueKeyGeneration>(); + } else { + return 0; + } + } + + SocType GetSocType() { + if (AMS_LIKELY(g_soc_type != SocType_CommonInternal)) { + return g_soc_type; + } else { + switch (GetHardwareType()) { + case HardwareType_Icosa: + case HardwareType_Copper: + g_soc_type = SocType_Erista; + break; + case HardwareType_Iowa: + case HardwareType_Hoag: + case HardwareType_Calcio: + case HardwareType_Aula: + g_soc_type = SocType_Mariko; + break; + default: + g_soc_type = SocType_Undefined; + break; + } + + return g_soc_type; + } + } + + int GetExpectedFuseVersion(TargetFirmware target_fw) { + return GetExpectedFuseVersionImpl(target_fw); + } + + int GetFuseVersion() { + return util::PopCount(GetCommonOdmWord(7)); + } + + bool HasRcmVulnerabilityPatch() { + /* Only check for RCM bug patch once, and cache our result. */ + if (!g_checked_for_rcm_bug_patch) { + do { + /* Mariko units are necessarily patched. */ + if (fuse::GetSocType() != SocType_Erista) { + g_has_rcm_bug_patch = true; + break; + } + + /* Some patched units use XUSB in RCM. */ + if (reg::Read(GetChipRegistersCommon().FUSE_RESERVED_SW) & 0x80) { + g_has_rcm_bug_patch = true; + break; + } + + /* Other units have a proper ipatch instead. */ + u32 word_count = reg::Read(GetChipRegistersCommon().FUSE_FIRST_BOOTROM_PATCH_SIZE) & 0x7F; + u32 word_addr = 191; + + while (word_count && !g_has_rcm_bug_patch) { + u32 word0 = ReadWord(word_addr); + u32 ipatch_count = (word0 >> 16) & 0xF; + + for (u32 i = 0; i < ipatch_count && !g_has_rcm_bug_patch; ++i) { + u32 word = ReadWord(word_addr - (i + 1)); + u32 addr = (word >> 16) * 2; + + if (addr == 0x769a) { + g_has_rcm_bug_patch = true; + break; + } + } + + word_addr -= word_count; + word_count = word0 >> 25; + } + } while (0); + + g_checked_for_rcm_bug_patch = true; + } + + return g_has_rcm_bug_patch; + } + + bool IsOdmProductionMode() { + return reg::HasValue(GetChipRegistersCommon().FUSE_SECURITY_MODE, FUSE_REG_BITS_ENUM(SECURITY_MODE_SECURITY_MODE, ENABLED)); + } + + bool GetSecureBootKey(void *dst) { + /* Get the sbk from fuse data. */ + bool valid = false; + for (size_t i = 0; i < 4; ++i) { + const u32 key_word = GetChipRegistersCommon().FUSE_PRIVATE_KEY[i]; + + static_cast<u32 *>(dst)[i] = key_word; + valid |= key_word != 0xFFFFFFFF; + } + + return valid; + } + + void ConfigureFuseBypass() { + /* Make the fuse registers visible. */ + clkrst::SetFuseVisibility(true); + + /* Only perform bypass configuration if fuse programming is allowed. */ + if (!reg::HasValue(GetRegisters().FUSE_DISABLEREGPROGRAM, FUSE_REG_BITS_ENUM(DISABLEREGPROGRAM_VAL, DISABLE))) { + return; + } + + /* Enable software writes to fuses. */ + reg::ReadWrite(GetRegisters().FUSE_WRITE_ACCESS_SW, FUSE_REG_BITS_ENUM(WRITE_ACCESS_SW_CTRL, READWRITE), + FUSE_REG_BITS_ENUM(WRITE_ACCESS_SW_STATUS, WRITE)); + + /* Enable fuse bypass. */ + reg::Write(GetRegisters().FUSE_FUSEBYPASS, FUSE_REG_BITS_ENUM(FUSEBYPASS_VAL, ENABLE)); + + /* Override fuses. */ + for (const auto &entry : FuseBypassEntries) { + reg::Write(g_register_address + entry.offset, entry.value); + } + + /* Disable software writes to fuses. */ + reg::ReadWrite(GetRegisters().FUSE_WRITE_ACCESS_SW, FUSE_REG_BITS_ENUM(WRITE_ACCESS_SW_CTRL, READONLY)); + + /* NOTE: Here, NVidia almost certainly intends to *disable* fuse bypass, but they write enable instead... */ + reg::Write(GetRegisters().FUSE_FUSEBYPASS, FUSE_REG_BITS_ENUM(FUSEBYPASS_VAL, ENABLE)); + + /* NOTE: Here, NVidia intends to disable fuse programming. However, they fuck up -- and *clear* the disable bit. */ + /* It should be noted that this is a sticky bit, and thus software clears have no effect. */ + reg::ReadWrite(GetRegisters().FUSE_DISABLEREGPROGRAM, FUSE_REG_BITS_ENUM(DISABLEREGPROGRAM_VAL, DISABLE)); + + /* Configure FUSE_PRIVATEKEYDISABLE_TZ_STICKY_BIT. */ + constexpr const uintptr_t PMC = secmon::MemoryRegionPhysicalDevicePmc.GetAddress(); + const bool key_invisible = reg::HasValue(PMC + APBDEV_PMC_SECURE_SCRATCH21, FUSE_REG_BITS_ENUM(PRIVATEKEYDISABLE_TZ_STICKY_BIT_VAL, KEY_INVISIBLE)); + + reg::ReadWrite(GetRegisters().FUSE_PRIVATEKEYDISABLE, FUSE_REG_BITS_ENUM_SEL(PRIVATEKEYDISABLE_TZ_STICKY_BIT_VAL, key_invisible, KEY_INVISIBLE, KEY_VISIBLE)); + + /* Write-lock PMC_SECURE_SCRATCH21. */ + reg::ReadWrite(PMC + APBDEV_PMC_SEC_DISABLE2, PMC_REG_BITS_ENUM(SEC_DISABLE2_WRITE21, ON)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/fuse/fuse_registers.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/fuse/fuse_registers.hpp new file mode 100644 index 00000000..82904d3a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/fuse/fuse_registers.hpp @@ -0,0 +1,555 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::fuse { + + struct FuseRegisters { + u32 FUSE_FUSECTRL; + u32 FUSE_FUSEADDR; + u32 FUSE_FUSERDATA; + u32 FUSE_FUSEWDATA; + u32 FUSE_FUSETIME_RD1; + u32 FUSE_FUSETIME_RD2; + u32 FUSE_FUSETIME_PGM1; + u32 FUSE_FUSETIME_PGM2; + u32 FUSE_PRIV2INTFC_START; + u32 FUSE_FUSEBYPASS; + u32 FUSE_PRIVATEKEYDISABLE; + u32 FUSE_DISABLEREGPROGRAM; + u32 FUSE_WRITE_ACCESS_SW; + u32 FUSE_PWR_GOOD_SW; + u32 _0x38; + u32 FUSE_PRIV2RESHIFT; + u32 _0x40[0x3]; + u32 FUSE_FUSETIME_RD3; + u32 _0x50[0xC]; + u32 FUSE_PRIVATE_KEY0_NONZERO; + u32 FUSE_PRIVATE_KEY1_NONZERO; + u32 FUSE_PRIVATE_KEY2_NONZERO; + u32 FUSE_PRIVATE_KEY3_NONZERO; + u32 FUSE_PRIVATE_KEY4_NONZERO; + u32 _0x94; + }; + static_assert(util::is_pod<FuseRegisters>::value); + static_assert(sizeof(FuseRegisters) == 0x98); + + struct FuseChipRegistersCommon { + u32 _0x98[0x1A]; + u32 FUSE_PRODUCTION_MODE; + u32 FUSE_JTAG_SECUREID_VALID; + u32 FUSE_ODM_LOCK; + u32 FUSE_OPT_OPENGL_EN; + u32 FUSE_SKU_INFO; + u32 FUSE_CPU_SPEEDO_0_CALIB; + u32 FUSE_CPU_IDDQ_CALIB; + u32 _0x11C; + u32 _0x120; + u32 _0x124; + u32 FUSE_OPT_FT_REV; + u32 FUSE_CPU_SPEEDO_1_CALIB; + u32 FUSE_CPU_SPEEDO_2_CALIB; + u32 FUSE_SOC_SPEEDO_0_CALIB; + u32 FUSE_SOC_SPEEDO_1_CALIB; + u32 FUSE_SOC_SPEEDO_2_CALIB; + u32 FUSE_SOC_IDDQ_CALIB; + u32 _0x144; + u32 FUSE_FA; + u32 FUSE_RESERVED_PRODUCTION; + u32 FUSE_HDMI_LANE0_CALIB; + u32 FUSE_HDMI_LANE1_CALIB; + u32 FUSE_HDMI_LANE2_CALIB; + u32 FUSE_HDMI_LANE3_CALIB; + u32 FUSE_ENCRYPTION_RATE; + u32 FUSE_PUBLIC_KEY[0x8]; + u32 FUSE_TSENSOR1_CALIB; + u32 FUSE_TSENSOR2_CALIB; + u32 _0x18C; + u32 FUSE_OPT_CP_REV; + u32 FUSE_OPT_PFG; + u32 FUSE_TSENSOR0_CALIB; + u32 FUSE_FIRST_BOOTROM_PATCH_SIZE; + u32 FUSE_SECURITY_MODE; + u32 FUSE_PRIVATE_KEY[0x5]; + u32 FUSE_ARM_JTAG_DIS; + u32 FUSE_BOOT_DEVICE_INFO; + u32 FUSE_RESERVED_SW; + u32 FUSE_OPT_VP9_DISABLE; + u32 FUSE_RESERVED_ODM_0[8 - 0]; + u32 FUSE_OBS_DIS; + u32 _0x1EC; + u32 FUSE_USB_CALIB; + u32 FUSE_SKU_DIRECT_CONFIG; + u32 FUSE_KFUSE_PRIVKEY_CTRL; + u32 FUSE_PACKAGE_INFO; + u32 FUSE_OPT_VENDOR_CODE; + u32 FUSE_OPT_FAB_CODE; + u32 FUSE_OPT_LOT_CODE_0; + u32 FUSE_OPT_LOT_CODE_1; + u32 FUSE_OPT_WAFER_ID; + u32 FUSE_OPT_X_COORDINATE; + u32 FUSE_OPT_Y_COORDINATE; + u32 FUSE_OPT_SEC_DEBUG_EN; + u32 FUSE_OPT_OPS_RESERVED; + u32 _0x224; + u32 FUSE_GPU_IDDQ_CALIB; + u32 FUSE_TSENSOR3_CALIB; + u32 _0x234; + u32 _0x238; + u32 _0x23C; + u32 _0x240; + u32 _0x244; + u32 FUSE_OPT_SAMPLE_TYPE; + u32 FUSE_OPT_SUBREVISION; + u32 FUSE_OPT_SW_RESERVED_0; + u32 FUSE_OPT_SW_RESERVED_1; + u32 FUSE_TSENSOR4_CALIB; + u32 FUSE_TSENSOR5_CALIB; + u32 FUSE_TSENSOR6_CALIB; + u32 FUSE_TSENSOR7_CALIB; + u32 FUSE_OPT_PRIV_SEC_EN; + u32 _0x268; + u32 _0x26C; + u32 _0x270; + u32 _0x274; + u32 _0x278; + u32 FUSE_FUSE2TSEC_DEBUG_DISABLE; + u32 FUSE_TSENSOR_COMMON; + u32 FUSE_OPT_CP_BIN; + u32 FUSE_OPT_GPU_DISABLE; + u32 FUSE_OPT_FT_BIN; + u32 FUSE_OPT_DONE_MAP; + u32 _0x294; + u32 FUSE_APB2JTAG_DISABLE; + u32 FUSE_ODM_INFO; + u32 _0x2A0; + u32 _0x2A4; + u32 FUSE_ARM_CRYPT_DE_FEATURE; + u32 _0x2AC; + u32 _0x2B0; + u32 _0x2B4; + u32 _0x2B8; + u32 _0x2BC; + u32 FUSE_WOA_SKU_FLAG; + u32 FUSE_ECO_RESERVE_1; + u32 FUSE_GCPLEX_CONFIG_FUSE; + u32 FUSE_PRODUCTION_MONTH; + u32 FUSE_RAM_REPAIR_INDICATOR; + u32 FUSE_TSENSOR9_CALIB; + u32 _0x2D8; + u32 FUSE_VMIN_CALIBRATION; + u32 FUSE_AGING_SENSOR_CALIBRATION; + u32 FUSE_DEBUG_AUTHENTICATION; + u32 FUSE_SECURE_PROVISION_INDEX; + u32 FUSE_SECURE_PROVISION_INFO; + u32 FUSE_OPT_GPU_DISABLE_CP1; + u32 FUSE_SPARE_ENDIS; + u32 FUSE_ECO_RESERVE_0; + u32 _0x2FC; + u32 _0x300; + u32 FUSE_RESERVED_CALIB0; + u32 FUSE_RESERVED_CALIB1; + u32 FUSE_OPT_GPU_TPC0_DISABLE; + u32 FUSE_OPT_GPU_TPC0_DISABLE_CP1; + u32 FUSE_OPT_CPU_DISABLE; + u32 FUSE_OPT_CPU_DISABLE_CP1; + u32 FUSE_TSENSOR10_CALIB; + u32 FUSE_TSENSOR10_CALIB_AUX; + u32 _0x324; + u32 _0x328; + u32 _0x32C; + u32 _0x330; + u32 _0x334; + u32 FUSE_OPT_GPU_TPC0_DISABLE_CP2; + u32 FUSE_OPT_GPU_TPC1_DISABLE; + u32 FUSE_OPT_GPU_TPC1_DISABLE_CP1; + u32 FUSE_OPT_GPU_TPC1_DISABLE_CP2; + u32 FUSE_OPT_CPU_DISABLE_CP2; + u32 FUSE_OPT_GPU_DISABLE_CP2; + u32 FUSE_USB_CALIB_EXT; + u32 FUSE_RESERVED_FIELD; + u32 _0x358; + u32 _0x35C; + u32 _0x360; + u32 _0x364; + u32 _0x368; + u32 _0x36C; + u32 _0x370; + u32 _0x374; + u32 _0x378; + u32 FUSE_SPARE_REALIGNMENT_REG; + u32 FUSE_SPARE_BIT[0x20]; + }; + static_assert(util::is_pod<FuseChipRegistersCommon>::value); + static_assert(sizeof(FuseChipRegistersCommon) == 0x400 - 0x98); + + struct FuseChipRegistersErista { + u32 _0x98[0x1A]; + u32 FUSE_PRODUCTION_MODE; + u32 FUSE_JTAG_SECUREID_VALID; + u32 FUSE_ODM_LOCK; + u32 FUSE_OPT_OPENGL_EN; + u32 FUSE_SKU_INFO; + u32 FUSE_CPU_SPEEDO_0_CALIB; + u32 FUSE_CPU_IDDQ_CALIB; + u32 FUSE_DAC_CRT_CALIB; + u32 FUSE_DAC_HDTV_CALIB; + u32 FUSE_DAC_SDTV_CALIB; + u32 FUSE_OPT_FT_REV; + u32 FUSE_CPU_SPEEDO_1_CALIB; + u32 FUSE_CPU_SPEEDO_2_CALIB; + u32 FUSE_SOC_SPEEDO_0_CALIB; + u32 FUSE_SOC_SPEEDO_1_CALIB; + u32 FUSE_SOC_SPEEDO_2_CALIB; + u32 FUSE_SOC_IDDQ_CALIB; + u32 FUSE_RESERVED_PRODUCTION_WP; + u32 FUSE_FA; + u32 FUSE_RESERVED_PRODUCTION; + u32 FUSE_HDMI_LANE0_CALIB; + u32 FUSE_HDMI_LANE1_CALIB; + u32 FUSE_HDMI_LANE2_CALIB; + u32 FUSE_HDMI_LANE3_CALIB; + u32 FUSE_ENCRYPTION_RATE; + u32 FUSE_PUBLIC_KEY[0x8]; + u32 FUSE_TSENSOR1_CALIB; + u32 FUSE_TSENSOR2_CALIB; + u32 FUSE_VSENSOR_CALIB; + u32 FUSE_OPT_CP_REV; + u32 FUSE_OPT_PFG; + u32 FUSE_TSENSOR0_CALIB; + u32 FUSE_FIRST_BOOTROM_PATCH_SIZE; + u32 FUSE_SECURITY_MODE; + u32 FUSE_PRIVATE_KEY[0x5]; + u32 FUSE_ARM_JTAG_DIS; + u32 FUSE_BOOT_DEVICE_INFO; + u32 FUSE_RESERVED_SW; + u32 FUSE_OPT_VP9_DISABLE; + u32 FUSE_RESERVED_ODM_0[8 - 0]; + u32 FUSE_OBS_DIS; + u32 FUSE_NOR_INFO; + u32 FUSE_USB_CALIB; + u32 FUSE_SKU_DIRECT_CONFIG; + u32 FUSE_KFUSE_PRIVKEY_CTRL; + u32 FUSE_PACKAGE_INFO; + u32 FUSE_OPT_VENDOR_CODE; + u32 FUSE_OPT_FAB_CODE; + u32 FUSE_OPT_LOT_CODE_0; + u32 FUSE_OPT_LOT_CODE_1; + u32 FUSE_OPT_WAFER_ID; + u32 FUSE_OPT_X_COORDINATE; + u32 FUSE_OPT_Y_COORDINATE; + u32 FUSE_OPT_SEC_DEBUG_EN; + u32 FUSE_OPT_OPS_RESERVED; + u32 FUSE_SATA_CALIB; + u32 FUSE_GPU_IDDQ_CALIB; + u32 FUSE_TSENSOR3_CALIB; + u32 FUSE_SKU_BOND_OUT_L; + u32 FUSE_SKU_BOND_OUT_H; + u32 FUSE_SKU_BOND_OUT_U; + u32 FUSE_SKU_BOND_OUT_V; + u32 FUSE_SKU_BOND_OUT_W; + u32 FUSE_OPT_SAMPLE_TYPE; + u32 FUSE_OPT_SUBREVISION; + u32 FUSE_OPT_SW_RESERVED_0; + u32 FUSE_OPT_SW_RESERVED_1; + u32 FUSE_TSENSOR4_CALIB; + u32 FUSE_TSENSOR5_CALIB; + u32 FUSE_TSENSOR6_CALIB; + u32 FUSE_TSENSOR7_CALIB; + u32 FUSE_OPT_PRIV_SEC_EN; + u32 FUSE_PKC_DISABLE; + u32 _0x26C; + u32 _0x270; + u32 _0x274; + u32 _0x278; + u32 FUSE_FUSE2TSEC_DEBUG_DISABLE; + u32 FUSE_TSENSOR_COMMON; + u32 FUSE_OPT_CP_BIN; + u32 FUSE_OPT_GPU_DISABLE; + u32 FUSE_OPT_FT_BIN; + u32 FUSE_OPT_DONE_MAP; + u32 _0x294; + u32 FUSE_APB2JTAG_DISABLE; + u32 FUSE_ODM_INFO; + u32 _0x2A0; + u32 _0x2A4; + u32 FUSE_ARM_CRYPT_DE_FEATURE; + u32 _0x2AC; + u32 _0x2B0; + u32 _0x2B4; + u32 _0x2B8; + u32 _0x2BC; + u32 FUSE_WOA_SKU_FLAG; + u32 FUSE_ECO_RESERVE_1; + u32 FUSE_GCPLEX_CONFIG_FUSE; + u32 FUSE_PRODUCTION_MONTH; + u32 FUSE_RAM_REPAIR_INDICATOR; + u32 FUSE_TSENSOR9_CALIB; + u32 _0x2D8; + u32 FUSE_VMIN_CALIBRATION; + u32 FUSE_AGING_SENSOR_CALIBRATION; + u32 FUSE_DEBUG_AUTHENTICATION; + u32 FUSE_SECURE_PROVISION_INDEX; + u32 FUSE_SECURE_PROVISION_INFO; + u32 FUSE_OPT_GPU_DISABLE_CP1; + u32 FUSE_SPARE_ENDIS; + u32 FUSE_ECO_RESERVE_0; + u32 _0x2FC; + u32 _0x300; + u32 FUSE_RESERVED_CALIB0; + u32 FUSE_RESERVED_CALIB1; + u32 FUSE_OPT_GPU_TPC0_DISABLE; + u32 FUSE_OPT_GPU_TPC0_DISABLE_CP1; + u32 FUSE_OPT_CPU_DISABLE; + u32 FUSE_OPT_CPU_DISABLE_CP1; + u32 FUSE_TSENSOR10_CALIB; + u32 FUSE_TSENSOR10_CALIB_AUX; + u32 FUSE_OPT_RAM_SVOP_DP; + u32 FUSE_OPT_RAM_SVOP_PDP; + u32 FUSE_OPT_RAM_SVOP_REG; + u32 FUSE_OPT_RAM_SVOP_SP; + u32 FUSE_OPT_RAM_SVOP_SMPDP; + u32 FUSE_OPT_GPU_TPC0_DISABLE_CP2; + u32 FUSE_OPT_GPU_TPC1_DISABLE; + u32 FUSE_OPT_GPU_TPC1_DISABLE_CP1; + u32 FUSE_OPT_GPU_TPC1_DISABLE_CP2; + u32 FUSE_OPT_CPU_DISABLE_CP2; + u32 FUSE_OPT_GPU_DISABLE_CP2; + u32 FUSE_USB_CALIB_EXT; + u32 FUSE_RESERVED_FIELD; + u32 FUSE_OPT_ECC_EN; + u32 _0x35C; + u32 _0x360; + u32 _0x364; + u32 _0x368; + u32 _0x36C; + u32 _0x370; + u32 _0x374; + u32 _0x378; + u32 FUSE_SPARE_REALIGNMENT_REG; + u32 FUSE_SPARE_BIT[0x20]; + }; + static_assert(util::is_pod<FuseChipRegistersErista>::value); + static_assert(sizeof(FuseChipRegistersErista) == 0x400 - 0x98); + + struct FuseChipRegistersMariko { + u32 FUSE_RESERVED_ODM_8[22 - 8]; + u32 FUSE_KEK[4]; + u32 FUSE_BEK[4]; + u32 _0xF0[4]; + u32 FUSE_PRODUCTION_MODE; + u32 FUSE_JTAG_SECUREID_VALID; + u32 FUSE_ODM_LOCK; + u32 FUSE_OPT_OPENGL_EN; + u32 FUSE_SKU_INFO; + u32 FUSE_CPU_SPEEDO_0_CALIB; + u32 FUSE_CPU_IDDQ_CALIB; + u32 FUSE_RESERVED_ODM_22[25 - 22]; + u32 FUSE_OPT_FT_REV; + u32 FUSE_CPU_SPEEDO_1_CALIB; + u32 FUSE_CPU_SPEEDO_2_CALIB; + u32 FUSE_SOC_SPEEDO_0_CALIB; + u32 FUSE_SOC_SPEEDO_1_CALIB; + u32 FUSE_SOC_SPEEDO_2_CALIB; + u32 FUSE_SOC_IDDQ_CALIB; + u32 FUSE_RESERVED_ODM_25[26 - 25]; + u32 FUSE_FA; + u32 FUSE_RESERVED_PRODUCTION; + u32 FUSE_HDMI_LANE0_CALIB; + u32 FUSE_HDMI_LANE1_CALIB; + u32 FUSE_HDMI_LANE2_CALIB; + u32 FUSE_HDMI_LANE3_CALIB; + u32 FUSE_ENCRYPTION_RATE; + u32 FUSE_PUBLIC_KEY[0x8]; + u32 FUSE_TSENSOR1_CALIB; + u32 FUSE_TSENSOR2_CALIB; + u32 FUSE_OPT_SECURE_SCC_DIS; + u32 FUSE_OPT_CP_REV; + u32 FUSE_OPT_PFG; + u32 FUSE_TSENSOR0_CALIB; + u32 FUSE_FIRST_BOOTROM_PATCH_SIZE; + u32 FUSE_SECURITY_MODE; + u32 FUSE_PRIVATE_KEY[0x5]; + u32 FUSE_ARM_JTAG_DIS; + u32 FUSE_BOOT_DEVICE_INFO; + u32 FUSE_RESERVED_SW; + u32 FUSE_OPT_VP9_DISABLE; + u32 FUSE_RESERVED_ODM_0[8 - 0]; + u32 FUSE_OBS_DIS; + u32 _0x1EC; + u32 FUSE_USB_CALIB; + u32 FUSE_SKU_DIRECT_CONFIG; + u32 FUSE_KFUSE_PRIVKEY_CTRL; + u32 FUSE_PACKAGE_INFO; + u32 FUSE_OPT_VENDOR_CODE; + u32 FUSE_OPT_FAB_CODE; + u32 FUSE_OPT_LOT_CODE_0; + u32 FUSE_OPT_LOT_CODE_1; + u32 FUSE_OPT_WAFER_ID; + u32 FUSE_OPT_X_COORDINATE; + u32 FUSE_OPT_Y_COORDINATE; + u32 FUSE_OPT_SEC_DEBUG_EN; + u32 FUSE_OPT_OPS_RESERVED; + u32 _0x224; + u32 FUSE_GPU_IDDQ_CALIB; + u32 FUSE_TSENSOR3_CALIB; + u32 FUSE_CLOCK_BONDOUT0; + u32 FUSE_CLOCK_BONDOUT1; + u32 FUSE_RESERVED_ODM_26[29 - 26]; + u32 FUSE_OPT_SAMPLE_TYPE; + u32 FUSE_OPT_SUBREVISION; + u32 FUSE_OPT_SW_RESERVED_0; + u32 FUSE_OPT_SW_RESERVED_1; + u32 FUSE_TSENSOR4_CALIB; + u32 FUSE_TSENSOR5_CALIB; + u32 FUSE_TSENSOR6_CALIB; + u32 FUSE_TSENSOR7_CALIB; + u32 FUSE_OPT_PRIV_SEC_EN; + u32 FUSE_BOOT_SECURITY_INFO; + u32 _0x26C; + u32 _0x270; + u32 _0x274; + u32 _0x278; + u32 FUSE_FUSE2TSEC_DEBUG_DISABLE; + u32 FUSE_TSENSOR_COMMON; + u32 FUSE_OPT_CP_BIN; + u32 FUSE_OPT_GPU_DISABLE; + u32 FUSE_OPT_FT_BIN; + u32 FUSE_OPT_DONE_MAP; + u32 FUSE_RESERVED_ODM_29[30 - 29]; + u32 FUSE_APB2JTAG_DISABLE; + u32 FUSE_ODM_INFO; + u32 _0x2A0; + u32 _0x2A4; + u32 FUSE_ARM_CRYPT_DE_FEATURE; + u32 _0x2AC; + u32 _0x2B0; + u32 _0x2B4; + u32 _0x2B8; + u32 _0x2BC; + u32 FUSE_WOA_SKU_FLAG; + u32 FUSE_ECO_RESERVE_1; + u32 FUSE_GCPLEX_CONFIG_FUSE; + u32 FUSE_PRODUCTION_MONTH; + u32 FUSE_RAM_REPAIR_INDICATOR; + u32 FUSE_TSENSOR9_CALIB; + u32 _0x2D8; + u32 FUSE_VMIN_CALIBRATION; + u32 FUSE_AGING_SENSOR_CALIBRATION; + u32 FUSE_DEBUG_AUTHENTICATION; + u32 FUSE_SECURE_PROVISION_INDEX; + u32 FUSE_SECURE_PROVISION_INFO; + u32 FUSE_OPT_GPU_DISABLE_CP1; + u32 FUSE_SPARE_ENDIS; + u32 FUSE_ECO_RESERVE_0; + u32 _0x2FC; + u32 _0x300; + u32 FUSE_RESERVED_CALIB0; + u32 FUSE_RESERVED_CALIB1; + u32 FUSE_OPT_GPU_TPC0_DISABLE; + u32 FUSE_OPT_GPU_TPC0_DISABLE_CP1; + u32 FUSE_OPT_CPU_DISABLE; + u32 FUSE_OPT_CPU_DISABLE_CP1; + u32 FUSE_TSENSOR10_CALIB; + u32 FUSE_TSENSOR10_CALIB_AUX; + u32 _0x324; + u32 _0x328; + u32 _0x32C; + u32 _0x330; + u32 _0x334; + u32 FUSE_OPT_GPU_TPC0_DISABLE_CP2; + u32 FUSE_OPT_GPU_TPC1_DISABLE; + u32 FUSE_OPT_GPU_TPC1_DISABLE_CP1; + u32 FUSE_OPT_GPU_TPC1_DISABLE_CP2; + u32 FUSE_OPT_CPU_DISABLE_CP2; + u32 FUSE_OPT_GPU_DISABLE_CP2; + u32 FUSE_USB_CALIB_EXT; + u32 FUSE_RESERVED_FIELD; + u32 _0x358; + u32 _0x35C; + u32 _0x360; + u32 _0x364; + u32 _0x368; + u32 _0x36C; + u32 _0x370; + u32 _0x374; + u32 _0x378; + u32 FUSE_SPARE_REALIGNMENT_REG; + u32 FUSE_SPARE_BIT[0x20]; + }; + static_assert(util::is_pod<FuseChipRegistersMariko>::value); + static_assert(sizeof(FuseChipRegistersMariko) == 0x400 - 0x98); + + struct FuseRegisterRegion { + FuseRegisters fuse; + union { + FuseChipRegistersCommon chip_common; + FuseChipRegistersErista chip_erista; + FuseChipRegistersMariko chip_mariko; + }; + }; + static_assert(util::is_pod<FuseRegisterRegion>::value); + static_assert(sizeof(FuseRegisterRegion) == secmon::MemoryRegionPhysicalDeviceFuses.GetSize()); + + #define FUSE_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (FUSE, NAME) + #define FUSE_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (FUSE, NAME, VALUE) + #define FUSE_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (FUSE, NAME, ENUM) + #define FUSE_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(FUSE, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + + #define DEFINE_FUSE_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (FUSE, NAME, __OFFSET__, __WIDTH__) + #define DEFINE_FUSE_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (FUSE, NAME, __OFFSET__, ZERO, ONE) + #define DEFINE_FUSE_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (FUSE, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) + #define DEFINE_FUSE_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(FUSE, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) + #define DEFINE_FUSE_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (FUSE, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + + DEFINE_FUSE_REG_TWO_BIT_ENUM(FUSECTRL_CMD, 0, IDLE, READ, WRITE, SENSE_CTRL); + + DEFINE_FUSE_REG(FUSECTRL_STATE, 16, 5); + + enum FUSE_FUSECTRL_STATE { + FUSE_FUSECTRL_STATE_RESET = 0, + FUSE_FUSECTRL_STATE_POST_RESET = 1, + FUSE_FUSECTRL_STATE_LOAD_ROW0 = 2, + FUSE_FUSECTRL_STATE_LOAD_ROW1 = 3, + FUSE_FUSECTRL_STATE_IDLE = 4, + FUSE_FUSECTRL_STATE_READ_SETUP = 5, + FUSE_FUSECTRL_STATE_READ_STROBE = 6, + FUSE_FUSECTRL_STATE_SAMPLE_FUSES = 7, + FUSE_FUSECTRL_STATE_READ_HOLD = 8, + FUSE_FUSECTRL_STATE_FUSE_SRC_SETUP = 9, + FUSE_FUSECTRL_STATE_WRITE_SETUP = 10, + FUSE_FUSECTRL_STATE_WRITE_ADDR_SETUP = 11, + FUSE_FUSECTRL_STATE_WRITE_PROGRAM = 12, + FUSE_FUSECTRL_STATE_WRITE_ADDR_HOLD = 13, + FUSE_FUSECTRL_STATE_FUSE_SRC_HOLD = 14, + FUSE_FUSECTRL_STATE_LOAD_RIR = 15, + FUSE_FUSECTRL_STATE_READ_BEFORE_WRITE_SETUP = 16, + FUSE_FUSECTRL_STATE_READ_DEASSERT_PD = 17, + }; + + DEFINE_FUSE_REG_BIT_ENUM(PRIVATEKEYDISABLE_TZ_STICKY_BIT_VAL, 4, KEY_VISIBLE, KEY_INVISIBLE); + DEFINE_FUSE_REG_BIT_ENUM(PRIVATEKEYDISABLE_PRIVATEKEYDISABLE_VAL_KEY, 0, VISIBLE, INVISIBLE); + + DEFINE_FUSE_REG_BIT_ENUM(FUSEBYPASS_VAL, 0, DISABLE, ENABLE); + + DEFINE_FUSE_REG_BIT_ENUM(DISABLEREGPROGRAM_VAL, 0, DISABLE, ENABLE); + + DEFINE_FUSE_REG_BIT_ENUM(WRITE_ACCESS_SW_CTRL, 0, READWRITE, READONLY); + DEFINE_FUSE_REG_BIT_ENUM(WRITE_ACCESS_SW_STATUS, 16, NOWRITE, WRITE); + + DEFINE_FUSE_REG_BIT_ENUM(SECURITY_MODE_SECURITY_MODE, 0, DISABLED, ENABLED); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/gic/gic_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/gic/gic_api.cpp new file mode 100644 index 00000000..223e7713 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/gic/gic_api.cpp @@ -0,0 +1,225 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::gic { + + namespace { + + struct GicDistributor { + u32 ctlr; + u32 typer; + u32 iidr; + u32 reserved_0x0c; + u32 statusr; + u32 reserved_0x14[3]; + u32 impldef_0x20[8]; + u32 setspi_nsr; + u32 reserved_0x44; + u32 clrspi_nsr; + u32 reserved_0x4c; + u32 setspi_sr; + u32 reserved_0x54; + u32 clrspi_sr; + u32 reserved_0x5c[9]; + u32 igroupr[32]; + u32 isenabler[32]; + u32 icenabler[32]; + u32 ispendr[32]; + u32 icpendr[32]; + u32 isactiver[32]; + u32 icactiver[32]; + union { + u8 bytes[1020]; + u32 words[255]; + } ipriorityr; + u32 _0x7fc; + union { + u8 bytes[1020]; + u32 words[255]; + } itargetsr; + u32 _0xbfc; + u32 icfgr[64]; + u32 igrpmodr[32]; + u32 _0xd80[32]; + u32 nsacr[64]; + u32 sgir; + u32 _0xf04[3]; + u32 cpendsgir[4]; + u32 spendsgir[4]; + u32 reserved_0xf30[52]; + + static constexpr size_t SgirCpuTargetListShift = 16; + + enum SgirTargetListFilter : u32 { + SgirTargetListFilter_CpuTargetList = (0 << 24), + SgirTargetListFilter_Others = (1 << 24), + SgirTargetListFilter_Self = (2 << 24), + SgirTargetListFilter_Reserved = (3 << 24), + }; + }; + static_assert(util::is_pod<GicDistributor>::value); + static_assert(sizeof(GicDistributor) == 0x1000); + static_assert(sizeof(GicDistributor) == secmon::MemoryRegionPhysicalDeviceGicDistributor.GetSize()); + + struct GicCpuInterface { + u32 ctlr; + u32 pmr; + u32 bpr; + u32 iar; + u32 eoir; + u32 rpr; + u32 hppir; + u32 abpr; + u32 aiar; + u32 aeoir; + u32 ahppir; + u32 statusr; + u32 reserved_30[4]; + u32 impldef_40[36]; + u32 apr[4]; + u32 nsapr[4]; + u32 reserved_f0[3]; + u32 iidr; + u32 reserved_100[960]; + u32 dir; + u32 _0x1004[1023]; + }; + static_assert(util::is_pod<GicCpuInterface>::value); + static_assert(sizeof(GicCpuInterface) == 0x2000); + static_assert(sizeof(GicCpuInterface) == secmon::MemoryRegionPhysicalDeviceGicCpuInterface.GetSize()); + + constexpr inline int InterruptWords = InterruptCount / BITSIZEOF(u32); + constexpr inline int SpiIndex = BITSIZEOF(u32); + + constinit uintptr_t g_distributor_address = secmon::MemoryRegionPhysicalDeviceGicDistributor.GetAddress(); + constinit uintptr_t g_cpu_interface_address = secmon::MemoryRegionPhysicalDeviceGicCpuInterface.GetAddress(); + + volatile GicDistributor *GetDistributor() { + return reinterpret_cast<volatile GicDistributor *>(g_distributor_address); + } + + volatile GicCpuInterface *GetCpuInterface() { + return reinterpret_cast<volatile GicCpuInterface *>(g_cpu_interface_address); + } + + void ReadWrite(uintptr_t address, int width, int i, u32 value) { + /* This code will never be invoked with a negative interrupt id. */ + AMS_ASSUME(i >= 0); + + const int scale = BITSIZEOF(u32) / width; + const int word = i / scale; + const int bit = (i % scale) * width; + + const u32 mask = ((1u << width) - 1) << bit; + + const uintptr_t reg_addr = address + sizeof(u32) * word; + const u32 old = reg::Read(reg_addr) & ~mask; + reg::Write(reg_addr, old | ((value << bit) & mask)); + } + + void Write(uintptr_t address, int width, int i, u32 value) { + /* This code will never be invoked with a negative interrupt id. */ + AMS_ASSUME(i >= 0); + + const int scale = BITSIZEOF(u32) / width; + const int word = i / scale; + const int bit = (i % scale) * width; + + reg::Write(address + sizeof(u32) * word, value << bit); + } + + } + + void SetRegisterAddress(uintptr_t distributor_address, uintptr_t cpu_interface_address) { + g_distributor_address = distributor_address; + g_cpu_interface_address = cpu_interface_address; + } + + void InitializeCommon() { + /* Get the gicd registers. */ + auto *gicd = GetDistributor(); + + /* Set IGROUPR for to be FFs. */ + for (int i = SpiIndex / BITSIZEOF(u32); i < InterruptWords; ++i) { + gicd->igroupr[i] = 0xFFFFFFFFu; + } + + /* Set IPRIORITYR for spi interrupts to be 0x80. */ + for (int i = SpiIndex; i < InterruptCount; ++i) { + gicd->ipriorityr.bytes[i] = 0x80; + } + + /* Enable group 0. */ + gicd->ctlr = 1; + } + + void InitializeCoreUnique() { + /* Get the registers. */ + auto *gicd = GetDistributor(); + auto *gicc = GetCpuInterface(); + + /* Set IGROUPR0 to be FFs. */ + gicd->igroupr[0] = 0xFFFFFFFFu; + + /* Set IPRIORITYR for core local interrupts to be 0x80. */ + for (int i = 0; i < SpiIndex; ++i) { + gicd->ipriorityr.bytes[i] = 0x80; + } + + /* Enable group 0 as FIQs. */ + gicc->ctlr = 0x1D9; + + /* Set PMR. */ + gicc->pmr = 0x80; + + /* Set BPR. */ + gicc->bpr = 7; + } + + void SetPriority(int interrupt_id, int priority) { + ReadWrite(g_distributor_address + AMS_OFFSETOF(GicDistributor, ipriorityr), BITSIZEOF(u8), interrupt_id, priority); + } + + void SetInterruptGroup(int interrupt_id, int group) { + ReadWrite(g_distributor_address + AMS_OFFSETOF(GicDistributor, igroupr), 1, interrupt_id, group); + } + + void SetEnable(int interrupt_id, bool enable) { + Write(g_distributor_address + AMS_OFFSETOF(GicDistributor, isenabler), 1, interrupt_id, enable); + } + + void SetSpiTargetCpu(int interrupt_id, u32 cpu_mask) { + ReadWrite(g_distributor_address + AMS_OFFSETOF(GicDistributor, itargetsr), BITSIZEOF(u8), interrupt_id, cpu_mask); + } + + void SetSpiMode(int interrupt_id, InterruptMode mode) { + ReadWrite(g_distributor_address + AMS_OFFSETOF(GicDistributor, icfgr), 2, interrupt_id, static_cast<u32>(mode) << 1); + } + + void SetPending(int interrupt_id) { + Write(g_distributor_address + AMS_OFFSETOF(GicDistributor, ispendr), 1, interrupt_id, 1); + } + + int GetInterruptRequestId() { + return reg::Read(GetCpuInterface()->iar); + } + + void SetEndOfInterrupt(int interrupt_id) { + reg::Write(GetCpuInterface()->eoir, interrupt_id); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/hw/hw_cache.arch.arm.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/hw/hw_cache.arch.arm.cpp new file mode 100644 index 00000000..81042050 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/hw/hw_cache.arch.arm.cpp @@ -0,0 +1,253 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::hw::arch::arm { + +#ifdef __BPMP__ + + namespace { + + constexpr inline uintptr_t AVP_CACHE = AVP_CACHE_ADDR(0); + + ALWAYS_INLINE bool IsLargeBuffer(size_t size) { + /* From TRM: For very large physical buffers or when the full cache needs to be cleared, */ + /* software should simply loop over all lines in all ways and run the *_LINE command on each of them. */ + return size >= DataCacheSize / 4; + } + + ALWAYS_INLINE bool IsCacheEnabled() { + return reg::HasValue(AVP_CACHE + AVP_CACHE_CONFIG, AVP_CACHE_REG_BITS_ENUM(CONFIG_ENABLE_CACHE, TRUE)); + } + + void DoPhyCacheOperation(AVP_CACHE_MAINT_OPCODE op, uintptr_t addr) { + /* Clear maintenance done. */ + reg::Write(AVP_CACHE + AVP_CACHE_INT_CLEAR, AVP_CACHE_REG_BITS_ENUM(INT_CLEAR_MAINTENANCE_DONE, TRUE)); + + /* Write maintenance address. */ + reg::Write(AVP_CACHE + AVP_CACHE_MAINT_0, addr); + + /* Write maintenance request. */ + reg::Write(AVP_CACHE + AVP_CACHE_MAINT_2, AVP_CACHE_REG_BITS_VALUE(MAINT_2_WAY_BITMAP, 0x0), + AVP_CACHE_REG_BITS_VALUE(MAINT_2_OPCODE, op)); + + /* Wait for maintenance to be done. */ + while (!reg::HasValue(AVP_CACHE + AVP_CACHE_INT_RAW_EVENT, AVP_CACHE_REG_BITS_ENUM(INT_RAW_EVENT_MAINTENANCE_DONE, TRUE))) { + /* ... */ + } + + /* Clear raw event. */ + reg::Write(AVP_CACHE + AVP_CACHE_INT_CLEAR, reg::Read(AVP_CACHE + AVP_CACHE_INT_RAW_EVENT)); + } + + void DoEntireCacheOperation(AVP_CACHE_MAINT_OPCODE op) { + /* Clear maintenance done. */ + reg::Write(AVP_CACHE + AVP_CACHE_INT_CLEAR, AVP_CACHE_REG_BITS_ENUM(INT_CLEAR_MAINTENANCE_DONE, TRUE)); + + /* Write maintenance request. */ + reg::Write(AVP_CACHE + AVP_CACHE_MAINT_2, AVP_CACHE_REG_BITS_VALUE(MAINT_2_WAY_BITMAP, 0xF), + AVP_CACHE_REG_BITS_VALUE(MAINT_2_OPCODE, op)); + + /* Wait for maintenance to be done. */ + while (!reg::HasValue(AVP_CACHE + AVP_CACHE_INT_RAW_EVENT, AVP_CACHE_REG_BITS_ENUM(INT_RAW_EVENT_MAINTENANCE_DONE, TRUE))) { + /* ... */ + } + + /* Clear raw event. */ + reg::Write(AVP_CACHE + AVP_CACHE_INT_CLEAR, reg::Read(AVP_CACHE + AVP_CACHE_INT_RAW_EVENT)); + } + + } + + #define REQUIRE_CACHE_ENABLED() \ + do { \ + if (AMS_UNLIKELY(!IsCacheEnabled())) { \ + return; \ + } \ + } while (false) \ + + + #define REQUIRE_CACHE_DISABLED() \ + do { \ + if (AMS_UNLIKELY(IsCacheEnabled())) { \ + return; \ + } \ + } while (false) \ + + void InitializeDataCache() { + REQUIRE_CACHE_DISABLED(); + + /* Issue init mmu command. */ + reg::Write(AVP_CACHE + AVP_CACHE_MMU_CMD, AVP_CACHE_REG_BITS_ENUM(MMU_CMD_CMD, INIT)); + + /* Set mmu fallback entry as RWX, uncached. */ + reg::Write(AVP_CACHE + AVP_CACHE_MMU_FALLBACK_ENTRY, AVP_CACHE_REG_BITS_ENUM(MMU_FALLBACK_ENTRY_WR_ENA, ENABLE), + AVP_CACHE_REG_BITS_ENUM(MMU_FALLBACK_ENTRY_RD_ENA, ENABLE), + AVP_CACHE_REG_BITS_ENUM(MMU_FALLBACK_ENTRY_EXE_ENA, ENABLE), + AVP_CACHE_REG_BITS_ENUM(MMU_FALLBACK_ENTRY_CACHED, DISABLE)); + + /* Set mmu cfg. */ + reg::Write(AVP_CACHE + AVP_CACHE_MMU_CFG, AVP_CACHE_REG_BITS_ENUM(MMU_CFG_CLR_ABORT, NOP), + AVP_CACHE_REG_BITS_ENUM(MMU_CFG_ABORT_MODE, STORE_LAST), + AVP_CACHE_REG_BITS_ENUM(MMU_CFG_SEQ_CHECK_ALL_ENTRIES, DISABLE), + AVP_CACHE_REG_BITS_ENUM(MMU_CFG_TLB_ENA, ENABLE), + AVP_CACHE_REG_BITS_ENUM(MMU_CFG_SEQ_ENA, ENABLE), + AVP_CACHE_REG_BITS_ENUM(MMU_CFG_BLOCK_MAIN_ENTRY_WR, DISABLE)); + + /* Initialize mmu entries. */ + { + /* Clear shadow copy mask. */ + reg::Write(AVP_CACHE + AVP_CACHE_MMU_SHADOW_COPY_MASK_0, 0); + + /* Add DRAM as index 0, RWX/Cached. */ + { + reg::Write(AVP_CACHE + AVP_CACHE_MMU_SHADOW_ENTRY_0_MIN_ADDR, 0x80000000); + reg::Write(AVP_CACHE + AVP_CACHE_MMU_SHADOW_ENTRY_0_MAX_ADDR, util::AlignDown(0xFFFFFFFF, DataCacheLineSize)); + + reg::Write(AVP_CACHE + AVP_CACHE_MMU_SHADOW_ENTRY_0_CFG, AVP_CACHE_REG_BITS_ENUM(SHADOW_ENTRY_CFG_WR_ENA, ENABLE), + AVP_CACHE_REG_BITS_ENUM(SHADOW_ENTRY_CFG_RD_ENA, ENABLE), + AVP_CACHE_REG_BITS_ENUM(SHADOW_ENTRY_CFG_EXE_ENA, ENABLE), + AVP_CACHE_REG_BITS_ENUM(SHADOW_ENTRY_CFG_CACHED, ENABLE)); + + reg::SetBits(AVP_CACHE + AVP_CACHE_MMU_SHADOW_COPY_MASK_0, (1 << 0)); + } + + /* Add IRAM as index 1, RWX/Cached. */ + { + reg::Write(AVP_CACHE + AVP_CACHE_MMU_SHADOW_ENTRY_1_MIN_ADDR, 0x40000000); + reg::Write(AVP_CACHE + AVP_CACHE_MMU_SHADOW_ENTRY_1_MAX_ADDR, util::AlignDown(0x4003FFFF, DataCacheLineSize)); + + reg::Write(AVP_CACHE + AVP_CACHE_MMU_SHADOW_ENTRY_1_CFG, AVP_CACHE_REG_BITS_ENUM(SHADOW_ENTRY_CFG_WR_ENA, ENABLE), + AVP_CACHE_REG_BITS_ENUM(SHADOW_ENTRY_CFG_RD_ENA, ENABLE), + AVP_CACHE_REG_BITS_ENUM(SHADOW_ENTRY_CFG_EXE_ENA, ENABLE), + AVP_CACHE_REG_BITS_ENUM(SHADOW_ENTRY_CFG_CACHED, ENABLE)); + + reg::SetBits(AVP_CACHE + AVP_CACHE_MMU_SHADOW_COPY_MASK_0, (1 << 1)); + } + + /* Issue copy shadow mmu command. */ + reg::Write(AVP_CACHE + AVP_CACHE_MMU_CMD, AVP_CACHE_REG_BITS_ENUM(MMU_CMD_CMD, COPY_SHADOW)); + } + + /* Invalidate entire cache. */ + DoEntireCacheOperation(AVP_CACHE_MAINT_OPCODE_INVALID_WAY); + + /* Enable the cache. */ + reg::Write(AVP_CACHE + AVP_CACHE_CONFIG, AVP_CACHE_REG_BITS_ENUM(CONFIG_ENABLE_CACHE, TRUE), + AVP_CACHE_REG_BITS_ENUM(CONFIG_FORCE_WRITE_THROUGH, TRUE), + AVP_CACHE_REG_BITS_ENUM(CONFIG_MMU_TAG_MODE, PARALLEL), + AVP_CACHE_REG_BITS_ENUM(CONFIG_TAG_CHECK_ABORT_ON_ERROR, TRUE)); + + /* Invalidate entire cache again (WAR for hardware bug). */ + DoEntireCacheOperation(AVP_CACHE_MAINT_OPCODE_INVALID_WAY); + } + + void FinalizeDataCache() { + REQUIRE_CACHE_ENABLED(); + + /* Flush entire data cache. */ + FlushEntireDataCache(); + + /* Disable cache. */ + reg::Write(AVP_CACHE + AVP_CACHE_CONFIG, AVP_CACHE_REG_BITS_ENUM(CONFIG_ENABLE_CACHE, FALSE)); + } + + void InvalidateEntireDataCache() { + REQUIRE_CACHE_ENABLED(); + + DoEntireCacheOperation(AVP_CACHE_MAINT_OPCODE_INVALID_WAY); + } + + void StoreEntireDataCache() { + REQUIRE_CACHE_ENABLED(); + + DoEntireCacheOperation(AVP_CACHE_MAINT_OPCODE_CLEAN_WAY); + } + + void FlushEntireDataCache() { + REQUIRE_CACHE_ENABLED(); + + DoEntireCacheOperation(AVP_CACHE_MAINT_OPCODE_CLEAN_INVALID_WAY); + } + + void InvalidateDataCacheLine(void *ptr) { + /* NOTE: Don't check cache enabled as an optimization, as only direct caller will be InvalidateDataCache(). */ + /* REQUIRE_CACHE_ENABLED(); */ + + DoPhyCacheOperation(AVP_CACHE_MAINT_OPCODE_INVALID_PHY, reinterpret_cast<uintptr_t>(ptr)); + } + + void StoreDataCacheLine(void *ptr) { + /* NOTE: Don't check cache enabled as an optimization, as only direct caller will be FlushDataCache(). */ + /* REQUIRE_CACHE_ENABLED(); */ + + DoPhyCacheOperation(AVP_CACHE_MAINT_OPCODE_CLEAN_PHY, reinterpret_cast<uintptr_t>(ptr)); + } + + void FlushDataCacheLine(void *ptr) { + /* NOTE: Don't check cache enabled as an optimization, as only direct caller will be FlushDataCache(). */ + /* REQUIRE_CACHE_ENABLED(); */ + + DoPhyCacheOperation(AVP_CACHE_MAINT_OPCODE_CLEAN_INVALID_PHY, reinterpret_cast<uintptr_t>(ptr)); + } + + void InvalidateDataCache(void *ptr, size_t size) { + REQUIRE_CACHE_ENABLED(); + + if (IsLargeBuffer(size)) { + InvalidateEntireDataCache(); + } else { + const uintptr_t start = reinterpret_cast<uintptr_t>(ptr); + const uintptr_t end = util::AlignUp(start + size, hw::DataCacheLineSize); + + for (uintptr_t cur = start; cur < end; cur += hw::DataCacheLineSize) { + InvalidateDataCacheLine(reinterpret_cast<void *>(cur)); + } + } + } + + void StoreDataCache(const void *ptr, size_t size) { + REQUIRE_CACHE_ENABLED(); + + if (IsLargeBuffer(size)) { + StoreEntireDataCache(); + } else { + const uintptr_t start = reinterpret_cast<uintptr_t>(ptr); + const uintptr_t end = util::AlignUp(start + size, hw::DataCacheLineSize); + + for (uintptr_t cur = start; cur < end; cur += hw::DataCacheLineSize) { + StoreDataCacheLine(reinterpret_cast<void *>(cur)); + } + } + } + + void FlushDataCache(const void *ptr, size_t size) { + REQUIRE_CACHE_ENABLED(); + + if (IsLargeBuffer(size)) { + FlushEntireDataCache(); + } else { + const uintptr_t start = reinterpret_cast<uintptr_t>(ptr); + const uintptr_t end = util::AlignUp(start + size, hw::DataCacheLineSize); + + for (uintptr_t cur = start; cur < end; cur += hw::DataCacheLineSize) { + FlushDataCacheLine(reinterpret_cast<void *>(cur)); + } + } + } +#endif + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/hw/hw_cache.arch.arm64.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/hw/hw_cache.arch.arm64.cpp new file mode 100644 index 00000000..63cb5837 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/hw/hw_cache.arch.arm64.cpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::hw::arch::arm64 { + + void FlushDataCache(const void *ptr, size_t size) { + const uintptr_t start = reinterpret_cast<uintptr_t>(ptr); + const uintptr_t end = util::AlignUp(start + size, hw::DataCacheLineSize); + + for (uintptr_t cur = start; cur < end; cur += hw::DataCacheLineSize) { + FlushDataCacheLine(reinterpret_cast<void *>(cur)); + } + } + + + + void InvalidateDataCache(const void *ptr, size_t size) { + const uintptr_t start = reinterpret_cast<uintptr_t>(ptr); + const uintptr_t end = util::AlignUp(start + size, hw::DataCacheLineSize); + + for (uintptr_t cur = start; cur < end; cur += hw::DataCacheLineSize) { + InvalidateDataCacheLine(reinterpret_cast<void *>(cur)); + } + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/i2c/i2c_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/i2c/i2c_api.cpp new file mode 100644 index 00000000..0e73d6ae --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/i2c/i2c_api.cpp @@ -0,0 +1,205 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::i2c { + + namespace { + + constexpr inline size_t MaxTransferSize = sizeof(u32); + + constinit std::array<uintptr_t, Port_Count> g_register_addresses = [] { + std::array<uintptr_t, Port_Count> arr = {}; + + arr[Port_1] = secmon::MemoryRegionPhysicalDeviceI2c1.GetAddress(); + arr[Port_5] = secmon::MemoryRegionPhysicalDeviceI2c5.GetAddress(); + + return arr; + }(); + + void LoadConfig(uintptr_t address) { + /* Configure for TIMEOUT and MSTR config load. */ + /* NOTE: Nintendo writes value 1 to reserved bit 5 here. This bit is documented as having no meaning. */ + /* We will reproduce the write just in case it is undocumented. */ + reg::Write(address + I2C_CONFIG_LOAD, I2C_REG_BITS_VALUE(CONFIG_LOAD_RESERVED_BIT_5, 1), + I2C_REG_BITS_ENUM (CONFIG_LOAD_TIMEOUT_CONFIG_LOAD, ENABLE), + I2C_REG_BITS_ENUM (CONFIG_LOAD_SLV_CONFIG_LOAD, DISABLE), + I2C_REG_BITS_ENUM (CONFIG_LOAD_MSTR_CONFIG_LOAD, ENABLE)); + + /* Wait up to 20 microseconds for the master config to be loaded. */ + for (int i = 0; i < 20; ++i) { + if (reg::HasValue(address + I2C_CONFIG_LOAD, I2C_REG_BITS_ENUM(CONFIG_LOAD_MSTR_CONFIG_LOAD, DISABLE))) { + return; + } + util::WaitMicroSeconds(1); + } + } + + void ClearBus(uintptr_t address) { + /* Configure the bus clear register. */ + reg::Write(address + I2C_BUS_CLEAR_CONFIG, I2C_REG_BITS_VALUE(BUS_CLEAR_CONFIG_BC_SCLK_THRESHOLD, 9), + I2C_REG_BITS_ENUM (BUS_CLEAR_CONFIG_BC_STOP_COND, NO_STOP), + I2C_REG_BITS_ENUM (BUS_CLEAR_CONFIG_BC_TERMINATE, IMMEDIATE), + I2C_REG_BITS_ENUM (BUS_CLEAR_CONFIG_BC_ENABLE, ENABLE)); + + /* Load the config. */ + LoadConfig(address); + + /* Wait up to 250us (in 25 us increments) until the bus clear is done. */ + for (int i = 0; i < 10; ++i) { + if (reg::HasValue(address + I2C_INTERRUPT_STATUS_REGISTER, I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_BUS_CLEAR_DONE, SET))) { + break; + } + + util::WaitMicroSeconds(25); + } + + /* Read the bus clear status. */ + reg::Read(address + I2C_BUS_CLEAR_STATUS); + } + + void InitializePort(uintptr_t address) { + /* Calculate the divisor. */ + constexpr int Divisor = util::DivideUp(19200, 8 * 400); + + /* Set the divisor. */ + reg::Write(address + I2C_CLK_DIVISOR_REGISTER, I2C_REG_BITS_VALUE(CLK_DIVISOR_REGISTER_STD_FAST_MODE, Divisor - 1), + I2C_REG_BITS_VALUE(CLK_DIVISOR_REGISTER_HSMODE, 1)); + + /* Clear the bus. */ + ClearBus(address); + + /* Clear the status. */ + reg::Write(address + I2C_INTERRUPT_STATUS_REGISTER, reg::Read(address + I2C_INTERRUPT_STATUS_REGISTER)); + } + + bool Write(uintptr_t base_address, Port port, int address, const void *src, size_t src_size, bool unused) { + AMS_UNUSED(port, unused); + + /* Ensure we don't write too much. */ + u32 data = 0; + if (src_size > MaxTransferSize) { + return false; + } + + /* Copy the data to a transfer word. */ + std::memcpy(std::addressof(data), src, src_size); + + + /* Configure the to write the 7-bit address. */ + reg::Write(base_address + I2C_I2C_CMD_ADDR0, I2C_REG_BITS_VALUE(I2C_CMD_ADDR0_7BIT_ADDR, address), + I2C_REG_BITS_ENUM (I2C_CMD_ADDR0_7BIT_RW, WRITE)); + + /* Configure to write the data. */ + reg::Write(base_address + I2C_I2C_CMD_DATA1, data); + + /* Configure to write the correct amount of data. */ + reg::Write(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM (I2C_CNFG_DEBOUNCE_CNT, DEBOUNCE_4T), + I2C_REG_BITS_ENUM (I2C_CNFG_NEW_MASTER_FSM, ENABLE), + I2C_REG_BITS_ENUM (I2C_CNFG_CMD1, WRITE), + I2C_REG_BITS_VALUE(I2C_CNFG_LENGTH, src_size - 1)); + + /* Load the configuration. */ + LoadConfig(base_address); + + /* Start the command. */ + reg::ReadWrite(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM(I2C_CNFG_SEND, GO)); + + /* Wait for the command to be done. */ + while (!reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_BUSY, NOT_BUSY))) { /* ... */ } + + /* Check if the transfer was successful. */ + return reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_CMD1_STAT, SL1_XFER_SUCCESSFUL)); + } + + bool Read(uintptr_t base_address, Port port, void *dst, size_t dst_size, int address, bool unused) { + AMS_UNUSED(port, unused); + + /* Ensure we don't read too much. */ + if (dst_size > MaxTransferSize) { + return false; + } + + /* Configure the to read the 7-bit address. */ + reg::Write(base_address + I2C_I2C_CMD_ADDR0, I2C_REG_BITS_VALUE(I2C_CMD_ADDR0_7BIT_ADDR, address), + I2C_REG_BITS_ENUM (I2C_CMD_ADDR0_7BIT_RW, READ)); + + /* Configure to read the correct amount of data. */ + reg::Write(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM (I2C_CNFG_DEBOUNCE_CNT, DEBOUNCE_4T), + I2C_REG_BITS_ENUM (I2C_CNFG_NEW_MASTER_FSM, ENABLE), + I2C_REG_BITS_ENUM (I2C_CNFG_CMD1, READ), + I2C_REG_BITS_VALUE(I2C_CNFG_LENGTH, dst_size - 1)); + + /* Load the configuration. */ + LoadConfig(base_address); + + /* Start the command. */ + reg::ReadWrite(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM(I2C_CNFG_SEND, GO)); + + /* Wait for the command to be done. */ + while (!reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_BUSY, NOT_BUSY))) { /* ... */ } + + /* Check that the transfer was successful. */ + if (!reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_CMD1_STAT, SL1_XFER_SUCCESSFUL))) { + return false; + } + + /* Read and copy out the data. */ + u32 data = reg::Read(base_address + I2C_I2C_CMD_DATA1); + std::memcpy(dst, std::addressof(data), dst_size); + return true; + } + + } + + void SetRegisterAddress(Port port, uintptr_t address) { + g_register_addresses[port] = address; + } + + void Initialize(Port port) { + InitializePort(g_register_addresses[port]); + } + + bool Query(void *dst, size_t dst_size, Port port, int address, int r) { + const uintptr_t base_address = g_register_addresses[port]; + + /* Select the register we want to read. */ + bool success = Write(base_address, port, address, std::addressof(r), 1, false); + if (success) { + /* If we successfully selected, read data from the register. */ + success = Read(base_address, port, dst, dst_size, address, true); + } + + return success; + } + + bool Send(Port port, int address, int r, const void *src, size_t src_size) { + const uintptr_t base_address = g_register_addresses[port]; + + /* Create a transfer buffer, make sure we can use it. */ + u8 buffer[MaxTransferSize]; + if (src_size > sizeof(buffer) - 1) { + return false; + } + + /* Copy data into the buffer. */ + buffer[0] = static_cast<u8>(r); + std::memcpy(buffer + 1, src, src_size); + + return Write(base_address, port, address, buffer, src_size + 1, false); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/impl/ams_impl_unexpected_default.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/impl/ams_impl_unexpected_default.cpp new file mode 100644 index 00000000..3b397ad5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/impl/ams_impl_unexpected_default.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::impl { + + NORETURN void UnexpectedDefaultImpl(const char *func, const char *file, int line) { + ::ams::diag::AbortImpl("", func, file, line); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/kfuse/kfuse_registers.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/kfuse/kfuse_registers.hpp new file mode 100644 index 00000000..0ccde59f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/kfuse/kfuse_registers.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +#define KFUSE_STATE (0x080) + +#define KFUSE_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (KFUSE, NAME) +#define KFUSE_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (KFUSE, NAME, VALUE) +#define KFUSE_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (KFUSE, NAME, ENUM) +#define KFUSE_REG_BITS_ENUM_KFUSEL(NAME, __COND__, TRUE_ENUM, FALKFUSE_ENUM) REG_NAMED_BITS_ENUM_KFUSEL(KFUSE, NAME, __COND__, TRUE_ENUM, FALKFUSE_ENUM) + +#define DEFINE_KFUSE_REG(NAME, __OFFKFUSET__, __WIDTH__) REG_DEFINE_NAMED_REG (KFUSE, NAME, __OFFKFUSET__, __WIDTH__) +#define DEFINE_KFUSE_REG_BIT_ENUM(NAME, __OFFKFUSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (KFUSE, NAME, __OFFKFUSET__, ZERO, ONE) +#define DEFINE_KFUSE_REG_TWO_BIT_ENUM(NAME, __OFFKFUSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (KFUSE, NAME, __OFFKFUSET__, ZERO, ONE, TWO, THREE) +#define DEFINE_KFUSE_REG_THREE_BIT_ENUM(NAME, __OFFKFUSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, KFUSEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(KFUSE, NAME, __OFFKFUSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, KFUSEVEN) +#define DEFINE_KFUSE_REG_FOUR_BIT_ENUM(NAME, __OFFKFUSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, KFUSEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (KFUSE, NAME, __OFFKFUSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, KFUSEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + +DEFINE_KFUSE_REG_BIT_ENUM(STATE_DONE, 16, NOT_DONE, DONE); +DEFINE_KFUSE_REG_BIT_ENUM(STATE_CRCPASS, 17, FAIL, PASS); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/libc/libc.c b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/libc/libc.c new file mode 100644 index 00000000..2117f248 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/libc/libc.c @@ -0,0 +1,1141 @@ +/* Note: copied from newlib */ +#ifdef __cplusplus +extern "C" { +#endif + +#include <string.h> +#include <stddef.h> +#include <limits.h> + +/* + * Copyright (C) 2004 CodeSourcery, LLC + * + * Permission to use, copy, modify, and distribute this file + * for any purpose is hereby granted without fee, provided that + * the above copyright notice and this notice appears in all + * copies. + * + * This file is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +/* Handle ELF .{pre_init,init,fini}_array sections. */ +#include <sys/types.h> + +#ifndef HAVE_INITFINI_ARRAY +#define HAVE_INITFINI_ARRAY +#endif + +#undef HAVE_INIT_FINI + +#ifdef HAVE_INITFINI_ARRAY + +/* These magic symbols are provided by the linker. */ +extern void (*__preinit_array_start []) (void) __attribute__((weak)); +extern void (*__preinit_array_end []) (void) __attribute__((weak)); +extern void (*__init_array_start []) (void) __attribute__((weak)); +extern void (*__init_array_end []) (void) __attribute__((weak)); + +#ifdef HAVE_INIT_FINI +extern void _init (void); +#endif + +/* Iterate over all the init routines. */ +void +__libc_init_array (void) +{ + size_t count; + size_t i; + + count = __preinit_array_end - __preinit_array_start; + for (i = 0; i < count; i++) + __preinit_array_start[i] (); + +#ifdef HAVE_INIT_FINI + _init (); +#endif + + count = __init_array_end - __init_array_start; + for (i = 0; i < count; i++) + __init_array_start[i] (); +} +#endif + +#ifdef HAVE_INITFINI_ARRAY +extern void (*__fini_array_start []) (void) __attribute__((weak)); +extern void (*__fini_array_end []) (void) __attribute__((weak)); + +#ifdef HAVE_INIT_FINI +extern void _fini (void); +#endif + +/* Run all the cleanup routines. */ +void +__libc_fini_array (void) +{ + size_t count; + size_t i; + + count = __fini_array_end - __fini_array_start; + for (i = count; i > 0; i--) + __fini_array_start[i-1] (); + +#ifdef HAVE_INIT_FINI + _fini (); +#endif +} +#endif + +/* +FUNCTION + <<memmove>>---move possibly overlapping memory +INDEX + memmove +SYNOPSIS + #include <string.h> + void *memmove(void *<[dst]>, const void *<[src]>, size_t <[length]>); +DESCRIPTION + This function moves <[length]> characters from the block of + memory starting at <<*<[src]>>> to the memory starting at + <<*<[dst]>>>. <<memmove>> reproduces the characters correctly + at <<*<[dst]>>> even if the two areas overlap. +RETURNS + The function returns <[dst]> as passed. +PORTABILITY +<<memmove>> is ANSI C. +<<memmove>> requires no supporting OS subroutines. +QUICKREF + memmove ansi pure +*/ + +/* Nonzero if either X or Y is not aligned on a "long" boundary. */ +#define UNALIGNED(X, Y) \ + (((long)X & (sizeof (long) - 1)) | ((long)Y & (sizeof (long) - 1))) + +/* How many bytes are copied each iteration of the 4X unrolled loop. */ +#define BIGBLOCKSIZE (sizeof (long) << 2) + +/* How many bytes are copied each iteration of the word copy loop. */ +#define LITTLEBLOCKSIZE (sizeof (long)) + +/* Threshhold for punting to the byte copier. */ +#undef TOO_SMALL +#define TOO_SMALL(LEN) ((LEN) < BIGBLOCKSIZE) + +/*SUPPRESS 20*/ +void * +//__inhibit_loop_to_libcall +memmove (void *dst_void, + const void *src_void, + size_t length) +{ +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) + char *dst = dst_void; + const char *src = src_void; + + if (src < dst && dst < src + length) + { + /* Have to copy backwards */ + src += length; + dst += length; + while (length--) + { + *--dst = *--src; + } + } + else + { + while (length--) + { + *dst++ = *src++; + } + } + + return dst_void; +#else + char *dst = dst_void; + const char *src = src_void; + long *aligned_dst; + const long *aligned_src; + + if (src < dst && dst < src + length) + { + /* Destructive overlap...have to copy backwards */ + src += length; + dst += length; + while (length--) + { + *--dst = *--src; + } + } + else + { + /* Use optimizing algorithm for a non-destructive copy to closely + match memcpy. If the size is small or either SRC or DST is unaligned, + then punt into the byte copy loop. This should be rare. */ + if (!TOO_SMALL(length) && !UNALIGNED (src, dst)) + { + aligned_dst = (long*)dst; + aligned_src = (long*)src; + + /* Copy 4X long words at a time if possible. */ + while (length >= BIGBLOCKSIZE) + { + *aligned_dst++ = *aligned_src++; + *aligned_dst++ = *aligned_src++; + *aligned_dst++ = *aligned_src++; + *aligned_dst++ = *aligned_src++; + length -= BIGBLOCKSIZE; + } + + /* Copy one long word at a time if possible. */ + while (length >= LITTLEBLOCKSIZE) + { + *aligned_dst++ = *aligned_src++; + length -= LITTLEBLOCKSIZE; + } + + /* Pick up any residual with a byte copier. */ + dst = (char*)aligned_dst; + src = (char*)aligned_src; + } + + while (length--) + { + *dst++ = *src++; + } + } + + return dst_void; +#endif /* not PREFER_SIZE_OVER_SPEED */ +} + +/* +FUNCTION + <<memcpy>>---copy memory regions +SYNOPSIS + #include <string.h> + void* memcpy(void *restrict <[out]>, const void *restrict <[in]>, + size_t <[n]>); +DESCRIPTION + This function copies <[n]> bytes from the memory region + pointed to by <[in]> to the memory region pointed to by + <[out]>. + If the regions overlap, the behavior is undefined. +RETURNS + <<memcpy>> returns a pointer to the first byte of the <[out]> + region. +PORTABILITY +<<memcpy>> is ANSI C. +<<memcpy>> requires no supporting OS subroutines. +QUICKREF + memcpy ansi pure + */ + +void * +memcpy (void * dst0, + const void * __restrict src0, + size_t len0) +{ +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) + char *dst = (char *) dst0; + char *src = (char *) src0; + + void *save = dst0; + + while (len0--) + { + *dst++ = *src++; + } + + return save; +#else + char *dst = dst0; + const char *src = src0; + long *aligned_dst; + const long *aligned_src; + + /* If the size is small, or either SRC or DST is unaligned, + then punt into the byte copy loop. This should be rare. */ + if (!TOO_SMALL(len0) && !UNALIGNED (src, dst)) + { + aligned_dst = (long*)dst; + aligned_src = (long*)src; + + /* Copy 4X long words at a time if possible. */ + while (len0 >= BIGBLOCKSIZE) + { + *aligned_dst++ = *aligned_src++; + *aligned_dst++ = *aligned_src++; + *aligned_dst++ = *aligned_src++; + *aligned_dst++ = *aligned_src++; + len0 -= BIGBLOCKSIZE; + } + + /* Copy one long word at a time if possible. */ + while (len0 >= LITTLEBLOCKSIZE) + { + *aligned_dst++ = *aligned_src++; + len0 -= LITTLEBLOCKSIZE; + } + + /* Pick up any residual with a byte copier. */ + dst = (char*)aligned_dst; + src = (char*)aligned_src; + } + + while (len0--) + *dst++ = *src++; + + return dst0; +#endif /* not PREFER_SIZE_OVER_SPEED */ +} + +/* +FUNCTION + <<memset>>---set an area of memory +INDEX + memset +SYNOPSIS + #include <string.h> + void *memset(void *<[dst]>, int <[c]>, size_t <[length]>); +DESCRIPTION + This function converts the argument <[c]> into an unsigned + char and fills the first <[length]> characters of the array + pointed to by <[dst]> to the value. +RETURNS + <<memset>> returns the value of <[dst]>. +PORTABILITY +<<memset>> is ANSI C. + <<memset>> requires no supporting OS subroutines. +QUICKREF + memset ansi pure +*/ + +#include <string.h> + +#undef LBLOCKSIZE +#undef UNALIGNED +#undef TOO_SMALL + +#define LBLOCKSIZE (sizeof(long)) +#define UNALIGNED(X) ((long)X & (LBLOCKSIZE - 1)) +#define TOO_SMALL(LEN) ((LEN) < LBLOCKSIZE) + +void * +memset (void *m, + int c, + size_t n) +{ + char *s = (char *) m; + +#if !defined(PREFER_SIZE_OVER_SPEED) && !defined(__OPTIMIZE_SIZE__) + unsigned int i; + unsigned long buffer; + unsigned long *aligned_addr; + unsigned int d = c & 0xff; /* To avoid sign extension, copy C to an + unsigned variable. */ + + while (UNALIGNED (s)) + { + if (n--) + *s++ = (char) c; + else + return m; + } + + if (!TOO_SMALL (n)) + { + /* If we get this far, we know that n is large and s is word-aligned. */ + aligned_addr = (unsigned long *) s; + + /* Store D into each char sized location in BUFFER so that + we can set large blocks quickly. */ + buffer = (d << 8) | d; + buffer |= (buffer << 16); + for (i = 32; i < LBLOCKSIZE * 8; i <<= 1) + buffer = (buffer << i) | buffer; + + /* Unroll the loop. */ + while (n >= LBLOCKSIZE*4) + { + *aligned_addr++ = buffer; + *aligned_addr++ = buffer; + *aligned_addr++ = buffer; + *aligned_addr++ = buffer; + n -= 4*LBLOCKSIZE; + } + + while (n >= LBLOCKSIZE) + { + *aligned_addr++ = buffer; + n -= LBLOCKSIZE; + } + /* Pick up the remainder with a bytewise loop. */ + s = (char*)aligned_addr; + } + +#endif /* not PREFER_SIZE_OVER_SPEED */ + + while (n--) + *s++ = (char) c; + + return m; +} + +/* +FUNCTION + <<memchr>>---find character in memory +INDEX + memchr +SYNOPSIS + #include <string.h> + void *memchr(const void *<[src]>, int <[c]>, size_t <[length]>); +DESCRIPTION + This function searches memory starting at <<*<[src]>>> for the + character <[c]>. The search only ends with the first + occurrence of <[c]>, or after <[length]> characters; in + particular, <<NUL>> does not terminate the search. +RETURNS + If the character <[c]> is found within <[length]> characters + of <<*<[src]>>>, a pointer to the character is returned. If + <[c]> is not found, then <<NULL>> is returned. +PORTABILITY +<<memchr>> is ANSI C. +<<memchr>> requires no supporting OS subroutines. +QUICKREF + memchr ansi pure +*/ + +#undef LBLOCKSIZE +#undef UNALIGNED +#undef TOO_SMALL + + +/* Nonzero if either X or Y is not aligned on a "long" boundary. */ +#define UNALIGNED(X) ((long)X & (sizeof (long) - 1)) + +/* How many bytes are loaded each iteration of the word copy loop. */ +#define LBLOCKSIZE (sizeof (long)) + +/* Threshhold for punting to the bytewise iterator. */ +#define TOO_SMALL(LEN) ((LEN) < LBLOCKSIZE) + +#if LONG_MAX == 2147483647L +#define DETECTNULL(X) (((X) - 0x01010101) & ~(X) & 0x80808080) +#else +#if LONG_MAX == 9223372036854775807L +/* Nonzero if X (a long int) contains a NULL byte. */ +#define DETECTNULL(X) (((X) - 0x0101010101010101) & ~(X) & 0x8080808080808080) +#else +#error long int is not a 32bit or 64bit type. +#endif +#endif + +#ifndef DETECTNULL +#error long int is not a 32bit or 64bit byte +#endif + +/* DETECTCHAR returns nonzero if (long)X contains the byte used + to fill (long)MASK. */ +#define DETECTCHAR(X,MASK) (DETECTNULL(X ^ MASK)) + +void * +memchr (const void *src_void, + int c, + size_t length) +{ + const unsigned char *src = (const unsigned char *) src_void; + unsigned char d = c; + +#if !defined(PREFER_SIZE_OVER_SPEED) && !defined(__OPTIMIZE_SIZE__) + unsigned long *asrc; + unsigned long mask; + unsigned int i; + + while (UNALIGNED (src)) + { + if (!length--) + return NULL; + if (*src == d) + return (void *) src; + src++; + } + + if (!TOO_SMALL (length)) + { + /* If we get this far, we know that length is large and src is + word-aligned. */ + /* The fast code reads the source one word at a time and only + performs the bytewise search on word-sized segments if they + contain the search character, which is detected by XORing + the word-sized segment with a word-sized block of the search + character and then detecting for the presence of NUL in the + result. */ + asrc = (unsigned long *) src; + mask = d << 8 | d; + mask = mask << 16 | mask; + for (i = 32; i < LBLOCKSIZE * 8; i <<= 1) + mask = (mask << i) | mask; + + while (length >= LBLOCKSIZE) + { + if (DETECTCHAR (*asrc, mask)) + break; + length -= LBLOCKSIZE; + asrc++; + } + + /* If there are fewer than LBLOCKSIZE characters left, + then we resort to the bytewise loop. */ + + src = (unsigned char *) asrc; + } + +#endif /* not PREFER_SIZE_OVER_SPEED */ + + while (length--) + { + if (*src == d) + return (void *) src; + src++; + } + + return NULL; +} + +/* +FUNCTION + <<memcmp>>---compare two memory areas +INDEX + memcmp +SYNOPSIS + #include <string.h> + int memcmp(const void *<[s1]>, const void *<[s2]>, size_t <[n]>); +DESCRIPTION + This function compares not more than <[n]> characters of the + object pointed to by <[s1]> with the object pointed to by <[s2]>. +RETURNS + The function returns an integer greater than, equal to or + less than zero according to whether the object pointed to by + <[s1]> is greater than, equal to or less than the object + pointed to by <[s2]>. +PORTABILITY +<<memcmp>> is ANSI C. +<<memcmp>> requires no supporting OS subroutines. +QUICKREF + memcmp ansi pure +*/ + + +#undef LBLOCKSIZE +#undef UNALIGNED +#undef TOO_SMALL + +/* Nonzero if either X or Y is not aligned on a "long" boundary. */ +#define UNALIGNED(X, Y) \ + (((long)X & (sizeof (long) - 1)) | ((long)Y & (sizeof (long) - 1))) + +/* How many bytes are copied each iteration of the word copy loop. */ +#define LBLOCKSIZE (sizeof (long)) + +/* Threshhold for punting to the byte copier. */ +#define TOO_SMALL(LEN) ((LEN) < LBLOCKSIZE) + +int +memcmp (const void *m1, + const void *m2, + size_t n) +{ +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) + unsigned char *s1 = (unsigned char *) m1; + unsigned char *s2 = (unsigned char *) m2; + + while (n--) + { + if (*s1 != *s2) + { + return *s1 - *s2; + } + s1++; + s2++; + } + return 0; +#else + unsigned char *s1 = (unsigned char *) m1; + unsigned char *s2 = (unsigned char *) m2; + unsigned long *a1; + unsigned long *a2; + + /* If the size is too small, or either pointer is unaligned, + then we punt to the byte compare loop. Hopefully this will + not turn up in inner loops. */ + if (!TOO_SMALL(n) && !UNALIGNED(s1,s2)) + { + /* Otherwise, load and compare the blocks of memory one + word at a time. */ + a1 = (unsigned long*) s1; + a2 = (unsigned long*) s2; + while (n >= LBLOCKSIZE) + { + if (*a1 != *a2) + break; + a1++; + a2++; + n -= LBLOCKSIZE; + } + + /* check m mod LBLOCKSIZE remaining characters */ + + s1 = (unsigned char*)a1; + s2 = (unsigned char*)a2; + } + + while (n--) + { + if (*s1 != *s2) + return *s1 - *s2; + s1++; + s2++; + } + + return 0; +#endif /* not PREFER_SIZE_OVER_SPEED */ +} + +/* +FUNCTION + <<strchr>>---search for character in string +INDEX + strchr +SYNOPSIS + #include <string.h> + char * strchr(const char *<[string]>, int <[c]>); +DESCRIPTION + This function finds the first occurence of <[c]> (converted to + a char) in the string pointed to by <[string]> (including the + terminating null character). +RETURNS + Returns a pointer to the located character, or a null pointer + if <[c]> does not occur in <[string]>. +PORTABILITY +<<strchr>> is ANSI C. +<<strchr>> requires no supporting OS subroutines. +QUICKREF + strchr ansi pure +*/ + +#undef LBLOCKSIZE +#undef UNALIGNED +#undef TOO_SMALL + + +/* Nonzero if X is not aligned on a "long" boundary. */ +#define UNALIGNED(X) ((long)X & (sizeof (long) - 1)) + +/* How many bytes are loaded each iteration of the word copy loop. */ +#define LBLOCKSIZE (sizeof (long)) + +char * +strchr (const char *s1, + int i) +{ + const unsigned char *s = (const unsigned char *)s1; + unsigned char c = i; + +#if !defined(PREFER_SIZE_OVER_SPEED) && !defined(__OPTIMIZE_SIZE__) + unsigned long mask,j; + unsigned long *aligned_addr; + + /* Special case for finding 0. */ + if (!c) + { + while (UNALIGNED (s)) + { + if (!*s) + return (char *) s; + s++; + } + /* Operate a word at a time. */ + aligned_addr = (unsigned long *) s; + while (!DETECTNULL (*aligned_addr)) + aligned_addr++; + /* Found the end of string. */ + s = (const unsigned char *) aligned_addr; + while (*s) + s++; + return (char *) s; + } + + /* All other bytes. Align the pointer, then search a long at a time. */ + while (UNALIGNED (s)) + { + if (!*s) + return NULL; + if (*s == c) + return (char *) s; + s++; + } + + mask = c; + for (j = 8; j < LBLOCKSIZE * 8; j <<= 1) + mask = (mask << j) | mask; + + aligned_addr = (unsigned long *) s; + while (!DETECTNULL (*aligned_addr) && !DETECTCHAR (*aligned_addr, mask)) + aligned_addr++; + + /* The block of bytes currently pointed to by aligned_addr + contains either a null or the target char, or both. We + catch it using the bytewise search. */ + + s = (unsigned char *) aligned_addr; + +#endif /* not PREFER_SIZE_OVER_SPEED */ + + while (*s && *s != c) + s++; + if (*s == c) + return (char *)s; + return NULL; +} + +/* +FUNCTION + <<strcmp>>---character string compare + +INDEX + strcmp +SYNOPSIS + #include <string.h> + int strcmp(const char *<[a]>, const char *<[b]>); +DESCRIPTION + <<strcmp>> compares the string at <[a]> to + the string at <[b]>. +RETURNS + If <<*<[a]>>> sorts lexicographically after <<*<[b]>>>, + <<strcmp>> returns a number greater than zero. If the two + strings match, <<strcmp>> returns zero. If <<*<[a]>>> + sorts lexicographically before <<*<[b]>>>, <<strcmp>> returns a + number less than zero. +PORTABILITY +<<strcmp>> is ANSI C. +<<strcmp>> requires no supporting OS subroutines. +QUICKREF + strcmp ansi pure +*/ + +#undef LBLOCKSIZE +#undef UNALIGNED +#undef TOO_SMALL + +/* Nonzero if either X or Y is not aligned on a "long" boundary. */ +#define UNALIGNED(X, Y) \ + (((long)X & (sizeof (long) - 1)) | ((long)Y & (sizeof (long) - 1))) + +int +strcmp (const char *s1, + const char *s2) +{ +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) + while (*s1 != '\0' && *s1 == *s2) + { + s1++; + s2++; + } + + return (*(unsigned char *) s1) - (*(unsigned char *) s2); +#else + unsigned long *a1; + unsigned long *a2; + + /* If s1 or s2 are unaligned, then compare bytes. */ + if (!UNALIGNED (s1, s2)) + { + /* If s1 and s2 are word-aligned, compare them a word at a time. */ + a1 = (unsigned long*)s1; + a2 = (unsigned long*)s2; + while (*a1 == *a2) + { + /* To get here, *a1 == *a2, thus if we find a null in *a1, + then the strings must be equal, so return zero. */ + if (DETECTNULL (*a1)) + return 0; + + a1++; + a2++; + } + + /* A difference was detected in last few bytes of s1, so search bytewise */ + s1 = (char*)a1; + s2 = (char*)a2; + } + + while (*s1 != '\0' && *s1 == *s2) + { + s1++; + s2++; + } + return (*(unsigned char *) s1) - (*(unsigned char *) s2); +#endif /* not PREFER_SIZE_OVER_SPEED */ +} + +/* +FUNCTION + <<strcpy>>---copy string +INDEX + strcpy +SYNOPSIS + #include <string.h> + char *strcpy(char *<[dst]>, const char *<[src]>); +DESCRIPTION + <<strcpy>> copies the string pointed to by <[src]> + (including the terminating null character) to the array + pointed to by <[dst]>. +RETURNS + This function returns the initial value of <[dst]>. +PORTABILITY +<<strcpy>> is ANSI C. +<<strcpy>> requires no supporting OS subroutines. +QUICKREF + strcpy ansi pure +*/ + +/*SUPPRESS 560*/ +/*SUPPRESS 530*/ + +#undef LBLOCKSIZE +#undef UNALIGNED +#undef TOO_SMALL + +/* Nonzero if either X or Y is not aligned on a "long" boundary. */ +#define UNALIGNED(X, Y) \ + (((long)X & (sizeof (long) - 1)) | ((long)Y & (sizeof (long) - 1))) + +char* +strcpy (char *dst0, + const char *src0) +{ +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) + char *s = dst0; + + while ((*dst0++ = *src0++)) + ; + + return s; +#else + char *dst = dst0; + const char *src = src0; + long *aligned_dst; + const long *aligned_src; + + /* If SRC or DEST is unaligned, then copy bytes. */ + if (!UNALIGNED (src, dst)) + { + aligned_dst = (long*)dst; + aligned_src = (long*)src; + + /* SRC and DEST are both "long int" aligned, try to do "long int" + sized copies. */ + while (!DETECTNULL(*aligned_src)) + { + *aligned_dst++ = *aligned_src++; + } + + dst = (char*)aligned_dst; + src = (char*)aligned_src; + } + + while ((*dst++ = *src++)) + ; + return dst0; +#endif /* not PREFER_SIZE_OVER_SPEED */ +} + +/* +FUNCTION + <<strlen>>---character string length +INDEX + strlen +SYNOPSIS + #include <string.h> + size_t strlen(const char *<[str]>); +DESCRIPTION + The <<strlen>> function works out the length of the string + starting at <<*<[str]>>> by counting chararacters until it + reaches a <<NULL>> character. +RETURNS + <<strlen>> returns the character count. +PORTABILITY +<<strlen>> is ANSI C. +<<strlen>> requires no supporting OS subroutines. +QUICKREF + strlen ansi pure +*/ + +#undef LBLOCKSIZE +#undef UNALIGNED +#undef TOO_SMALL + +#define LBLOCKSIZE (sizeof (long)) +#define UNALIGNED(X) ((long)X & (LBLOCKSIZE - 1)) +size_t +strlen (const char *str) +{ + const char *start = str; + +#if !defined(PREFER_SIZE_OVER_SPEED) && !defined(__OPTIMIZE_SIZE__) + unsigned long *aligned_addr; + + /* Align the pointer, so we can search a word at a time. */ + while (UNALIGNED (str)) + { + if (!*str) + return str - start; + str++; + } + + /* If the string is word-aligned, we can check for the presence of + a null in each word-sized block. */ + aligned_addr = (unsigned long *)str; + while (!DETECTNULL (*aligned_addr)) + aligned_addr++; + + /* Once a null is detected, we check each byte in that block for a + precise position of the null. */ + str = (char *) aligned_addr; + +#endif /* not PREFER_SIZE_OVER_SPEED */ + + while (*str) + str++; + return str - start; +} + +/* +FUNCTION + <<strncmp>>---character string compare + +INDEX + strncmp +SYNOPSIS + #include <string.h> + int strncmp(const char *<[a]>, const char * <[b]>, size_t <[length]>); +DESCRIPTION + <<strncmp>> compares up to <[length]> characters + from the string at <[a]> to the string at <[b]>. +RETURNS + If <<*<[a]>>> sorts lexicographically after <<*<[b]>>>, + <<strncmp>> returns a number greater than zero. If the two + strings are equivalent, <<strncmp>> returns zero. If <<*<[a]>>> + sorts lexicographically before <<*<[b]>>>, <<strncmp>> returns a + number less than zero. +PORTABILITY +<<strncmp>> is ANSI C. +<<strncmp>> requires no supporting OS subroutines. +QUICKREF + strncmp ansi pure +*/ + +#undef LBLOCKSIZE +#undef UNALIGNED +#undef TOO_SMALL + +#define UNALIGNED(X, Y) \ + (((long)X & (sizeof (long) - 1)) | ((long)Y & (sizeof (long) - 1))) + +int +strncmp (const char *s1, + const char *s2, + size_t n) +{ +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) + if (n == 0) + return 0; + + while (n-- != 0 && *s1 == *s2) + { + if (n == 0 || *s1 == '\0') + break; + s1++; + s2++; + } + + return (*(unsigned char *) s1) - (*(unsigned char *) s2); +#else + unsigned long *a1; + unsigned long *a2; + + if (n == 0) + return 0; + + /* If s1 or s2 are unaligned, then compare bytes. */ + if (!UNALIGNED (s1, s2)) + { + /* If s1 and s2 are word-aligned, compare them a word at a time. */ + a1 = (unsigned long*)s1; + a2 = (unsigned long*)s2; + while (n >= sizeof (long) && *a1 == *a2) + { + n -= sizeof (long); + + /* If we've run out of bytes or hit a null, return zero + since we already know *a1 == *a2. */ + if (n == 0 || DETECTNULL (*a1)) + return 0; + + a1++; + a2++; + } + + /* A difference was detected in last few bytes of s1, so search bytewise */ + s1 = (char*)a1; + s2 = (char*)a2; + } + + while (n-- > 0 && *s1 == *s2) + { + /* If we've run out of bytes or hit a null, return zero + since we already know *s1 == *s2. */ + if (n == 0 || *s1 == '\0') + return 0; + s1++; + s2++; + } + return (*(unsigned char *) s1) - (*(unsigned char *) s2); +#endif /* not PREFER_SIZE_OVER_SPEED */ +} + +/* +FUNCTION + <<strncpy>>---counted copy string +INDEX + strncpy +SYNOPSIS + #include <string.h> + char *strncpy(char *restrict <[dst]>, const char *restrict <[src]>, + size_t <[length]>); +DESCRIPTION + <<strncpy>> copies not more than <[length]> characters from the + the string pointed to by <[src]> (including the terminating + null character) to the array pointed to by <[dst]>. If the + string pointed to by <[src]> is shorter than <[length]> + characters, null characters are appended to the destination + array until a total of <[length]> characters have been + written. +RETURNS + This function returns the initial value of <[dst]>. +PORTABILITY +<<strncpy>> is ANSI C. +<<strncpy>> requires no supporting OS subroutines. +QUICKREF + strncpy ansi pure +*/ + +/*SUPPRESS 560*/ +/*SUPPRESS 530*/ + +#undef LBLOCKSIZE +#undef UNALIGNED +#undef TOO_SMALL + +#define UNALIGNED(X, Y) \ + (((long)X & (sizeof (long) - 1)) | ((long)Y & (sizeof (long) - 1))) + +#define TOO_SMALL(LEN) ((LEN) < sizeof (long)) + +char * +strncpy (char *__restrict dst0, + const char *__restrict src0, + size_t count) +{ +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) + char *dscan; + const char *sscan; + + dscan = dst0; + sscan = src0; + while (count > 0) + { + --count; + if ((*dscan++ = *sscan++) == '\0') + break; + } + while (count-- > 0) + *dscan++ = '\0'; + + return dst0; +#else + char *dst = dst0; + const char *src = src0; + long *aligned_dst; + const long *aligned_src; + + /* If SRC and DEST is aligned and count large enough, then copy words. */ + if (!UNALIGNED (src, dst) && !TOO_SMALL (count)) + { + aligned_dst = (long*)dst; + aligned_src = (long*)src; + + /* SRC and DEST are both "long int" aligned, try to do "long int" + sized copies. */ + while (count >= sizeof (long int) && !DETECTNULL(*aligned_src)) + { + count -= sizeof (long int); + *aligned_dst++ = *aligned_src++; + } + + dst = (char*)aligned_dst; + src = (char*)aligned_src; + } + + while (count > 0) + { + --count; + if ((*dst++ = *src++) == '\0') + break; + } + + while (count-- > 0) + *dst++ = '\0'; + + return dst0; +#endif /* not PREFER_SIZE_OVER_SPEED */ +} + +/* +FUNCTION + <<strnlen>>---character string length + +INDEX + strnlen +SYNOPSIS + #include <string.h> + size_t strnlen(const char *<[str]>, size_t <[n]>); +DESCRIPTION + The <<strnlen>> function works out the length of the string + starting at <<*<[str]>>> by counting chararacters until it + reaches a NUL character or the maximum: <[n]> number of + characters have been inspected. +RETURNS + <<strnlen>> returns the character count or <[n]>. +PORTABILITY +<<strnlen>> is a GNU extension. +<<strnlen>> requires no supporting OS subroutines. +*/ + +size_t +strnlen (const char *str, + size_t n) +{ + const char *start = str; + + while (n-- > 0 && *str) + str++; + + return str - start; +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/libc/libexo_cxx.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/libc/libexo_cxx.cpp new file mode 100644 index 00000000..8ac82a08 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/libc/libexo_cxx.cpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +#ifdef __cplusplus +extern "C" { +#endif + +/* cxx implementation details to be stubbed here, as needed. */ +void __cxa_pure_virtual() { AMS_ABORT("pure virtual function call"); } + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/log/log_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/log/log_api.cpp new file mode 100644 index 00000000..677d8aab --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/log/log_api.cpp @@ -0,0 +1,116 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::log { + + namespace { + + constexpr inline uart::Port DefaultLogPort = uart::Port_ReservedDebug; + constexpr inline u32 DefaultLogFlags = static_cast<u32>(uart::Flag_None); + constexpr inline int DefaultBaudRate = 115200; + constinit uart::Port g_log_port = DefaultLogPort; + constinit bool g_initialized_uart = false; + + ALWAYS_INLINE void SetupUartClock(uart::Port port) { + /* The debug port must always be set up, for compatibility with official hos. */ + pinmux::SetupUartA(); + clkrst::EnableUartAClock(); + + /* If logging to a joy-con port, configure appropriately. */ + if (port == uart::Port_LeftJoyCon) { + /* Logging to left joy-con (e.g. with Joyless). */ + static_assert(uart::Port_LeftJoyCon == uart::Port_C); + pinmux::SetupUartC(); + clkrst::EnableUartCClock(); + } else if (port == uart::Port_RightJoyCon) { + /* Logging to right joy-con (e.g. with Joyless). */ + static_assert(uart::Port_RightJoyCon == uart::Port_B); + pinmux::SetupUartB(); + clkrst::EnableUartBClock(); + } + } + + } + + void Initialize() { + return Initialize(DefaultLogPort, DefaultBaudRate, DefaultLogFlags); + } + + void Initialize(uart::Port port, u32 baud_rate, u32 flags) { + /* Initialize pinmux and clock for the target uart port. */ + SetupUartClock(port); + + /* Initialize the target uart port. */ + uart::Initialize(port, baud_rate, flags); + + /* Note that we've initialized. */ + g_log_port = port; + g_initialized_uart = true; + } + + void Finalize() { + g_initialized_uart = false; + } + + NOINLINE void VPrintf(const char *fmt, ::std::va_list vl) { + /* TODO: What's a good size for the log buffer? Nintendo uses 0x100, but this seems big. */ + char log_buf[0x80]; + const auto len = util::TVSNPrintf(log_buf, sizeof(log_buf), fmt, vl); + + if (g_initialized_uart) { + uart::SendText(g_log_port, log_buf, len); + } + } + + NOINLINE void Printf(const char *fmt, ...) { + ::std::va_list vl; + va_start(vl, fmt); + VPrintf(fmt, vl); + va_end(vl); + } + + NOINLINE void Dump(const void *src, size_t size) { + const u8 *src_u8 = static_cast<const u8 *>(src); + + for (size_t i = 0; i < size; ++i) { + if ((i % 0x20) == 0x00) { + Printf("%03zx| ", i); + } + Printf("%02x ", src_u8[i]); + if ((i % 0x20) == 0x1F) { + Printf("\n"); + } + } + if ((size % 0x20) != 0) { + Printf("\n"); + } + } + + + void SendText(const void *text, size_t size) { + if (g_initialized_uart) { + uart::SendText(g_log_port, text, size); + } + } + + void Flush() { + if (g_initialized_uart) { + uart::WaitFlush(g_log_port); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pinmux/pinmux_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pinmux/pinmux_api.cpp new file mode 100644 index 00000000..6cda3be6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pinmux/pinmux_api.cpp @@ -0,0 +1,259 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::pinmux { + + namespace { + + constinit uintptr_t g_pinmux_address = secmon::MemoryRegionPhysicalDeviceApbMisc.GetAddress(); + constinit uintptr_t g_gpio_address = secmon::MemoryRegionPhysicalDeviceGpio.GetAddress(); + + void SetupFirstImpl(bool tx_cross_ext_con) { + if (tx_cross_ext_con) { + reg::Write(g_pinmux_address + PINMUX_AUX_UART2_TX, PINMUX_REG_BITS_ENUM(AUX_UART2_PM, UARTB), + PINMUX_REG_BITS_ENUM(AUX_PUPD, NONE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + + reg::Write(g_pinmux_address + PINMUX_AUX_UART3_TX, PINMUX_REG_BITS_ENUM(AUX_UART3_PM, UARTC), + PINMUX_REG_BITS_ENUM(AUX_PUPD, NONE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + + /* Configure GPIO for Uart-B/Uart-C. */ + reg::ReadWrite(g_gpio_address + 0x108, REG_BITS_VALUE(0, 1, 1)); + reg::ReadWrite(g_gpio_address + 0x00C, REG_BITS_VALUE(1, 1, 1)); + reg::ReadWrite(g_gpio_address + 0x118, REG_BITS_VALUE(0, 1, 0)); + reg::ReadWrite(g_gpio_address + 0x01C, REG_BITS_VALUE(1, 1, 0)); + } + + /* Configure PE6/PH6 */ + reg::Write(g_pinmux_address + PINMUX_AUX_GPIO_PE6, PINMUX_REG_BITS_ENUM(AUX_GPIO_PE6_PM, RSVD0), + PINMUX_REG_BITS_ENUM(AUX_PUPD, NONE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + + reg::Write(g_pinmux_address + PINMUX_AUX_GPIO_PH6, PINMUX_REG_BITS_ENUM(AUX_GPIO_PH6_PM, RSVD0), + PINMUX_REG_BITS_ENUM(AUX_PUPD, NONE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + + /* Configure GPIO E6/H6. */ + reg::ReadWrite(g_gpio_address + 0x100, REG_BITS_VALUE(6, 1, 1)); + reg::ReadWrite(g_gpio_address + 0x10C, REG_BITS_VALUE(6, 1, 1)); + reg::ReadWrite(g_gpio_address + 0x110, REG_BITS_VALUE(6, 1, 0)); + reg::ReadWrite(g_gpio_address + 0x11C, REG_BITS_VALUE(6, 1, 0)); + } + + } + + void SetRegisterAddress(uintptr_t pinmux_address, uintptr_t gpio_address) { + g_pinmux_address = pinmux_address; + g_gpio_address = gpio_address; + } + + void SetupFirst(fuse::HardwareType hw_type) { + switch (hw_type) { + case fuse::HardwareType_Icosa: + case fuse::HardwareType_Iowa: + case fuse::HardwareType_Aula: + SetupFirstImpl(true); + break; + case fuse::HardwareType_Hoag: + case fuse::HardwareType_Calcio: + SetupFirstImpl(false); + break; + case fuse::HardwareType_Copper: + case fuse::HardwareType_Undefined: + break; + } + } + + void SetupUartA() { + /* Get the registers. */ + const uintptr_t PINMUX = g_pinmux_address; + + /* Configure Uart-A. */ + reg::Write(PINMUX + PINMUX_AUX_UART1_TX, PINMUX_REG_BITS_ENUM(AUX_UART1_PM, UARTA), + PINMUX_REG_BITS_ENUM(AUX_PUPD, NONE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + + reg::Write(PINMUX + PINMUX_AUX_UART1_RX, PINMUX_REG_BITS_ENUM(AUX_UART1_PM, UARTA), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_UP), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + + reg::Write(PINMUX + PINMUX_AUX_UART1_RTS, PINMUX_REG_BITS_ENUM(AUX_UART1_PM, UARTA), + PINMUX_REG_BITS_ENUM(AUX_PUPD, NONE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + + reg::Write(PINMUX + PINMUX_AUX_UART1_CTS, PINMUX_REG_BITS_ENUM(AUX_UART1_PM, UARTA), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_DOWN), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + } + + void SetupUartB() { + /* Get the registers. */ + const uintptr_t PINMUX = g_pinmux_address; + + /* Configure Uart-B. */ + reg::Write(PINMUX + PINMUX_AUX_UART2_TX, PINMUX_REG_BITS_ENUM(AUX_UART2_PM, UARTB), + PINMUX_REG_BITS_ENUM(AUX_PUPD, NONE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + + reg::Write(PINMUX + PINMUX_AUX_UART2_RX, PINMUX_REG_BITS_ENUM(AUX_UART2_PM, UARTB), + PINMUX_REG_BITS_ENUM(AUX_PUPD, NONE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + + reg::Write(PINMUX + PINMUX_AUX_UART2_RTS, PINMUX_REG_BITS_ENUM(AUX_UART2_PM, UARTB), + PINMUX_REG_BITS_ENUM(AUX_PUPD, NONE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + + reg::Write(PINMUX + PINMUX_AUX_UART2_CTS, PINMUX_REG_BITS_ENUM(AUX_UART2_PM, UARTB), + PINMUX_REG_BITS_ENUM(AUX_PUPD, NONE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + + /* Configure GPIO for Uart-B. */ + reg::ReadWrite(g_gpio_address + 0x108, REG_BITS_VALUE(0, 4, 0)); + } + + void SetupUartC() { + /* Get the registers. */ + const uintptr_t PINMUX = g_pinmux_address; + + /* Configure Uart-C. */ + reg::Write(PINMUX + PINMUX_AUX_UART3_TX, PINMUX_REG_BITS_ENUM(AUX_UART3_PM, UARTC), + PINMUX_REG_BITS_ENUM(AUX_PUPD, NONE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + + reg::Write(PINMUX + PINMUX_AUX_UART3_RX, PINMUX_REG_BITS_ENUM(AUX_UART3_PM, UARTC), + PINMUX_REG_BITS_ENUM(AUX_PUPD, NONE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, TRISTATE), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + + reg::Write(PINMUX + PINMUX_AUX_UART3_RTS, PINMUX_REG_BITS_ENUM(AUX_UART3_PM, UARTC), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_DOWN), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + + reg::Write(PINMUX + PINMUX_AUX_UART3_CTS, PINMUX_REG_BITS_ENUM(AUX_UART3_PM, UARTC), + PINMUX_REG_BITS_ENUM(AUX_PUPD, NONE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, TRISTATE), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + + /* Configure GPIO for Uart-C. */ + reg::ReadWrite(g_gpio_address + 0x118, REG_BITS_VALUE(0, 1, 1)); + reg::Read(g_gpio_address + 0x118); + reg::ReadWrite(g_gpio_address + 0x00C, REG_BITS_VALUE(1, 1, 0)); + reg::Read(g_gpio_address + 0x00C); + } + + void SetupI2c1() { + /* Get the registers. */ + const uintptr_t PINMUX = g_pinmux_address; + + /* Configure I2c1 */ + reg::Write(PINMUX + PINMUX_AUX_GEN1_I2C_SCL, PINMUX_REG_BITS_ENUM(AUX_GEN1_I2C_PM, I2C1), + PINMUX_REG_BITS_ENUM(AUX_PUPD, NONE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + + reg::Write(PINMUX + PINMUX_AUX_GEN1_I2C_SDA, PINMUX_REG_BITS_ENUM(AUX_GEN1_I2C_PM, I2C1), + PINMUX_REG_BITS_ENUM(AUX_PUPD, NONE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + } + + void SetupI2c5() { + /* Get the registers. */ + const uintptr_t PINMUX = g_pinmux_address; + + /* Configure I2c5 */ + reg::Write(PINMUX + PINMUX_AUX_PWR_I2C_SCL, PINMUX_REG_BITS_ENUM(AUX_PWR_I2C_PM, I2CPMU), + PINMUX_REG_BITS_ENUM(AUX_PUPD, NONE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + + reg::Write(PINMUX + PINMUX_AUX_PWR_I2C_SDA, PINMUX_REG_BITS_ENUM(AUX_PWR_I2C_PM, I2CPMU), + PINMUX_REG_BITS_ENUM(AUX_PUPD, NONE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_LOCK, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE)); + } + + void SetupVolumeButton() { + /* Configure VOL_UP/VOL_DOWN */ + reg::ReadWrite(g_gpio_address + 0x50C, REG_BITS_VALUE(6, 1, 1)); + reg::ReadWrite(g_gpio_address + 0x50C, REG_BITS_VALUE(7, 1, 1)); + reg::ReadWrite(g_gpio_address + 0x51C, REG_BITS_VALUE(6, 1, 0)); + reg::ReadWrite(g_gpio_address + 0x51C, REG_BITS_VALUE(7, 1, 0)); + } + + void SetupHomeButton() { + /* Configure BUTTON_HOME */ + reg::ReadWrite(g_gpio_address + 0x600, REG_BITS_VALUE(1, 1, 1)); + reg::ReadWrite(g_gpio_address + 0x610, REG_BITS_VALUE(1, 1, 0)); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pkg1/pkg1_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pkg1/pkg1_api.cpp new file mode 100644 index 00000000..c2d80b69 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pkg1/pkg1_api.cpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::pkg1 { + + namespace { + + bool IsProductionImpl() { + return fuse::GetHardwareState() != fuse::HardwareState_Development; + } + + } + + bool IsProduction() { + return IsProductionImpl(); + } + + bool IsProductionForVersionCheck() { + return IsProductionImpl(); + } + + bool IsProductionForPublicKey() { + return IsProductionImpl(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pmc/pmc_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pmc/pmc_api.cpp new file mode 100644 index 00000000..4178ceac --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pmc/pmc_api.cpp @@ -0,0 +1,301 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::pmc { + + namespace { + + constinit uintptr_t g_register_address = secmon::MemoryRegionPhysicalDevicePmc.GetAddress(); + + constexpr inline u32 WriteMask = 0x1; + constexpr inline u32 ReadMask = 0x2; + + enum class LockMode { + Read, + Write, + ReadWrite, + }; + + template<LockMode Mode> + constexpr inline u32 LockMask = [] { + switch (Mode) { + case LockMode::Read: return ReadMask; + case LockMode::Write: return WriteMask; + case LockMode::ReadWrite: return ReadMask | WriteMask; + default: __builtin_unreachable(); + } + }(); + + constexpr inline size_t NumSecureScratchRegisters = 120; + constexpr inline size_t NumSecureDisableRegisters = 8; + + template<size_t SecureScratch> requires (SecureScratch < NumSecureScratchRegisters) + constexpr inline std::pair<size_t, size_t> DisableRegisterIndex = [] { + if constexpr (SecureScratch < 8) { + return std::pair<size_t, size_t>{0, 4 + 2 * SecureScratch}; + } else { + constexpr size_t Relative = SecureScratch - 8; + return std::pair<size_t, size_t>{1 + (Relative / 16), 2 * (Relative % 16)}; + } + }(); + + struct LockInfo { + size_t scratch; + LockMode mode; + }; + + template<LockInfo Info> + constexpr ALWAYS_INLINE void SetSecureScratchMask(std::array<u32, NumSecureDisableRegisters> &disables) { + constexpr std::pair<size_t, size_t> Location = DisableRegisterIndex<Info.scratch>; + disables[Location.first] |= LockMask<Info.mode> << Location.second; + } + + template<LockInfo... Info> + constexpr ALWAYS_INLINE void SetSecureScratchMasks(std::array<u32, NumSecureDisableRegisters> &disables) { + (SetSecureScratchMask<Info>(disables), ...); + } + + template<size_t... Ix> + constexpr ALWAYS_INLINE void SetSecureScratchReadWriteMasks(std::array<u32, NumSecureDisableRegisters> &disables) { + (SetSecureScratchMask<LockInfo{Ix, LockMode::ReadWrite}>(disables), ...); + } + + template<size_t... Ix> + constexpr ALWAYS_INLINE void SetSecureScratchReadMasks(std::array<u32, NumSecureDisableRegisters> &disables) { + (SetSecureScratchMask<LockInfo{Ix, LockMode::Read}>(disables), ...); + } + + template<size_t... Ix> + constexpr ALWAYS_INLINE void SetSecureScratchWriteMasks(std::array<u32, NumSecureDisableRegisters> &disables) { + (SetSecureScratchMask<LockInfo{Ix, LockMode::Write}>(disables), ...); + } + + template<SecureRegister Register> + constexpr ALWAYS_INLINE std::array<u32, NumSecureDisableRegisters> GetSecureScratchMasks() { + std::array<u32, NumSecureDisableRegisters> disables = {}; + + if constexpr ((Register & SecureRegister_Other) != 0) { + constexpr std::array<u32, NumSecureDisableRegisters> NonOtherDisables = GetSecureScratchMasks<static_cast<SecureRegister>(~SecureRegister_Other)>(); + for (size_t i = 0; i < NumSecureDisableRegisters; i++) { + disables[i] |= ~NonOtherDisables[i]; + } + disables[0] &= 0x007FFFF0; + } + if constexpr ((Register & SecureRegister_DramParameters) != 0) { + SetSecureScratchReadWriteMasks< 8, 9, 10, 11, 12, 13, 14, 15, + 17, 18, 19, 20, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 52, 53, 54, + 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 79, 80, 81, 82, 83, 84, + 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, + 104, 105, 106, 107 + >(disables); + } + if constexpr ((Register & SecureRegister_ResetVector) != 0) { + SetSecureScratchReadWriteMasks<34, 35>(disables); + } + if constexpr ((Register & SecureRegister_Carveout) != 0) { + SetSecureScratchReadWriteMasks<16, 39, 51, 55, 74, 75, 76, 77, 78, 99, 100, 101, 102, 103>(disables); + } + if constexpr ((Register & SecureRegister_CmacWrite) != 0) { + SetSecureScratchWriteMasks<112, 113, 114, 115>(disables); + } + if constexpr ((Register & SecureRegister_CmacRead) != 0) { + SetSecureScratchReadMasks<112, 113, 114, 115>(disables); + } + if constexpr ((Register & SecureRegister_KeySourceWrite) != 0) { + SetSecureScratchWriteMasks<24, 25, 26, 27>(disables); + } + if constexpr ((Register & SecureRegister_KeySourceRead) != 0) { + SetSecureScratchReadMasks<24, 25, 26, 27>(disables); + } + if constexpr ((Register & SecureRegister_Srk) != 0) { + SetSecureScratchReadWriteMasks<4, 5, 6, 7>(disables); + } + + return disables; + } + + /* Validate that the secure scratch masks produced are correct. */ + #include "pmc_secure_scratch_test.inc" + + ALWAYS_INLINE void LockBits(uintptr_t address, u32 mask) { + reg::Write(address, reg::Read(address) | mask); + } + + template<SecureRegister Register> + ALWAYS_INLINE void SetSecureScratchMasks(uintptr_t address) { + constexpr auto Masks = GetSecureScratchMasks<Register>(); + + if constexpr (Masks[0] != 0) { LockBits(address + APBDEV_PMC_SEC_DISABLE , Masks[0]); } + if constexpr (Masks[1] != 0) { LockBits(address + APBDEV_PMC_SEC_DISABLE2, Masks[1]); } + if constexpr (Masks[2] != 0) { LockBits(address + APBDEV_PMC_SEC_DISABLE3, Masks[2]); } + if constexpr (Masks[3] != 0) { LockBits(address + APBDEV_PMC_SEC_DISABLE4, Masks[3]); } + if constexpr (Masks[4] != 0) { LockBits(address + APBDEV_PMC_SEC_DISABLE5, Masks[4]); } + if constexpr (Masks[5] != 0) { LockBits(address + APBDEV_PMC_SEC_DISABLE6, Masks[5]); } + if constexpr (Masks[6] != 0) { LockBits(address + APBDEV_PMC_SEC_DISABLE7, Masks[6]); } + if constexpr (Masks[7] != 0) { LockBits(address + APBDEV_PMC_SEC_DISABLE8, Masks[7]); } + + static_assert(Masks.size() == 8); + } + + template<SecureRegister Register> + ALWAYS_INLINE bool TestSecureScratchMasks(uintptr_t address) { + constexpr auto Masks = GetSecureScratchMasks<Register>(); + + if constexpr (Masks[0] != 0) { if ((reg::Read(address + APBDEV_PMC_SEC_DISABLE ) & Masks[0]) != Masks[0]) { return false; } } + if constexpr (Masks[1] != 0) { if ((reg::Read(address + APBDEV_PMC_SEC_DISABLE2) & Masks[1]) != Masks[1]) { return false; } } + if constexpr (Masks[2] != 0) { if ((reg::Read(address + APBDEV_PMC_SEC_DISABLE3) & Masks[2]) != Masks[2]) { return false; } } + if constexpr (Masks[3] != 0) { if ((reg::Read(address + APBDEV_PMC_SEC_DISABLE4) & Masks[3]) != Masks[3]) { return false; } } + if constexpr (Masks[4] != 0) { if ((reg::Read(address + APBDEV_PMC_SEC_DISABLE5) & Masks[4]) != Masks[4]) { return false; } } + if constexpr (Masks[5] != 0) { if ((reg::Read(address + APBDEV_PMC_SEC_DISABLE6) & Masks[5]) != Masks[5]) { return false; } } + if constexpr (Masks[6] != 0) { if ((reg::Read(address + APBDEV_PMC_SEC_DISABLE7) & Masks[6]) != Masks[6]) { return false; } } + if constexpr (Masks[7] != 0) { if ((reg::Read(address + APBDEV_PMC_SEC_DISABLE8) & Masks[7]) != Masks[7]) { return false; } } + static_assert(Masks.size() == 8); + + return true; + } + + NOINLINE void WriteRandomValueToRegister(uintptr_t offset) { + /* Create an aligned buffer. */ + util::AlignedBuffer<hw::DataCacheLineSize, sizeof(u32)> buf; + + /* Generate random bytes into it. */ + se::GenerateRandomBytes(buf, sizeof(u32)); + + /* Read the random value. */ + const u32 random = *reinterpret_cast<const u32 *>(static_cast<u8 *>(buf)); + + /* Get the address. */ + const uintptr_t address = g_register_address + offset; + + /* Write the value. */ + reg::Write(address, random); + + /* Verify it was written. */ + AMS_ABORT_UNLESS(reg::Read(address) == random); + } + + } + + void SetRegisterAddress(uintptr_t address) { + g_register_address = address; + } + + void InitializeRandomScratch() { + /* Write random data to the scratch that contains the SRK. */ + WriteRandomValueToRegister(APBDEV_PMC_SECURE_SCRATCH4); + WriteRandomValueToRegister(APBDEV_PMC_SECURE_SCRATCH5); + WriteRandomValueToRegister(APBDEV_PMC_SECURE_SCRATCH6); + WriteRandomValueToRegister(APBDEV_PMC_SECURE_SCRATCH7); + + /* Lock the SRK scratch. */ + LockSecureRegister(SecureRegister_Srk); + + /* Write random data to the scratch used for tzram cmac. */ + WriteRandomValueToRegister(APBDEV_PMC_SECURE_SCRATCH112); + WriteRandomValueToRegister(APBDEV_PMC_SECURE_SCRATCH113); + WriteRandomValueToRegister(APBDEV_PMC_SECURE_SCRATCH114); + WriteRandomValueToRegister(APBDEV_PMC_SECURE_SCRATCH115); + + /* Write random data to the scratch used for tzram key source. */ + WriteRandomValueToRegister(APBDEV_PMC_SECURE_SCRATCH24); + WriteRandomValueToRegister(APBDEV_PMC_SECURE_SCRATCH25); + WriteRandomValueToRegister(APBDEV_PMC_SECURE_SCRATCH26); + WriteRandomValueToRegister(APBDEV_PMC_SECURE_SCRATCH27); + + /* Here, Nintendo locks the SRK scratch a second time. */ + /* This may just be "to be sure". */ + LockSecureRegister(SecureRegister_Srk); + } + + void EnableWakeEventDetection() { + /* Get the address. */ + const uintptr_t address = g_register_address; + + /* Wait 75us, then enable event detection, then wait another 75us. */ + util::WaitMicroSeconds(75); + reg::ReadWrite(address + APBDEV_PMC_CNTRL2, PMC_REG_BITS_ENUM(CNTRL2_WAKE_DET_EN, ENABLE)); + util::WaitMicroSeconds(75); + + /* Enable all wake events. */ + reg::Write(address + APBDEV_PMC_WAKE_STATUS, 0xFFFFFFFFu); + reg::Write(address + APBDEV_PMC_WAKE2_STATUS, 0xFFFFFFFFu); + util::WaitMicroSeconds(75); + } + + void ConfigureForSc7Entry() { + /* Get the address. */ + const uintptr_t address = g_register_address; + + /* Configure the bootrom to perform a warmboot. */ + reg::Write(address + APBDEV_PMC_SCRATCH0, 0x1); + + /* Enable the TSC multiplier. */ + reg::ReadWrite(address + APBDEV_PMC_DPD_ENABLE, PMC_REG_BITS_ENUM(DPD_ENABLE_TSC_MULT_EN, ENABLE)); + } + + void LockSecureRegister(SecureRegister reg) { + /* Get the address. */ + const uintptr_t address = g_register_address; + + /* Apply each mask. */ + #define PMC_PROCESS_REG(REG) do { if ((reg & SecureRegister_##REG) != 0) { SetSecureScratchMasks<SecureRegister_##REG>(address); } } while (0) + PMC_PROCESS_REG(Other); + PMC_PROCESS_REG(DramParameters); + PMC_PROCESS_REG(ResetVector); + PMC_PROCESS_REG(Carveout); + PMC_PROCESS_REG(CmacWrite); + PMC_PROCESS_REG(CmacRead); + PMC_PROCESS_REG(KeySourceWrite); + PMC_PROCESS_REG(KeySourceRead); + PMC_PROCESS_REG(Srk); + #undef PMC_PROCESS_REG + + } + + LockState GetSecureRegisterLockState(SecureRegister reg) { + bool all_valid = true; + bool any_valid = false; + + /* Get the address. */ + const uintptr_t address = g_register_address; + + /* Test each mask. */ + #define PMC_PROCESS_REG(REG) do { if ((reg & SecureRegister_##REG) != 0) { const bool test = TestSecureScratchMasks<SecureRegister_##REG>(address); all_valid &= test; any_valid |= test; } } while (0) + PMC_PROCESS_REG(Other); + PMC_PROCESS_REG(DramParameters); + PMC_PROCESS_REG(ResetVector); + PMC_PROCESS_REG(Carveout); + PMC_PROCESS_REG(CmacWrite); + PMC_PROCESS_REG(CmacRead); + PMC_PROCESS_REG(KeySourceWrite); + PMC_PROCESS_REG(KeySourceRead); + PMC_PROCESS_REG(Srk); + #undef PMC_PROCESS_REG + + if (all_valid) { + return LockState::Locked; + } else if (any_valid) { + return LockState::PartiallyLocked; + } else { + return LockState::NotLocked; + } + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pmc/pmc_secure_scratch_test.inc b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pmc/pmc_secure_scratch_test.inc new file mode 100644 index 00000000..f7941b85 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pmc/pmc_secure_scratch_test.inc @@ -0,0 +1,100 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +namespace test { + + constexpr inline auto Other = GetSecureScratchMasks<SecureRegister_Other>(); + static_assert(Other[0] == 0x00700FF0u); + static_assert(Other[1] == 0xFC000000u); + static_assert(Other[2] == 0x3F0FFF00u); + static_assert(Other[3] == 0x00000000u); + static_assert(Other[4] == 0x00000000u); + static_assert(Other[5] == 0x0C000000u); + static_assert(Other[6] == 0x00000000u); + static_assert(Other[7] == 0xFF00FF00u); + + constexpr inline auto DramParameters = GetSecureScratchMasks<SecureRegister_DramParameters>(); + static_assert(DramParameters[0] == 0x00000000u); + static_assert(DramParameters[1] == 0x03FCFFFFu); + static_assert(DramParameters[2] == 0x00000000u); + static_assert(DramParameters[3] == 0x3F3FFFFFu); + static_assert(DramParameters[4] == 0xFFFFFFFFu); + static_assert(DramParameters[5] == 0xF3FFC00Fu); + static_assert(DramParameters[6] == 0x003FFFFFu); + static_assert(DramParameters[7] == 0x000000FFu); + + constexpr inline auto ResetVector = GetSecureScratchMasks<SecureRegister_ResetVector>(); + static_assert(ResetVector[0] == 0x00000000u); + static_assert(ResetVector[1] == 0x00000000u); + static_assert(ResetVector[2] == 0x00F00000u); + static_assert(ResetVector[3] == 0x00000000u); + static_assert(ResetVector[4] == 0x00000000u); + static_assert(ResetVector[5] == 0x00000000u); + static_assert(ResetVector[6] == 0x00000000u); + static_assert(ResetVector[7] == 0x00000000u); + + constexpr inline auto CmacWrite = GetSecureScratchMasks<SecureRegister_CmacWrite>(); + static_assert(CmacWrite[0] == 0x00000000u); + static_assert(CmacWrite[1] == 0x00000000u); + static_assert(CmacWrite[2] == 0x00000000u); + static_assert(CmacWrite[3] == 0x00000000u); + static_assert(CmacWrite[4] == 0x00000000u); + static_assert(CmacWrite[5] == 0x00000000u); + static_assert(CmacWrite[6] == 0x00000000u); + static_assert(CmacWrite[7] == 0x00550000u); + + constexpr inline auto CmacRead = GetSecureScratchMasks<SecureRegister_CmacRead>(); + static_assert(CmacRead[0] == 0x00000000u); + static_assert(CmacRead[1] == 0x00000000u); + static_assert(CmacRead[2] == 0x00000000u); + static_assert(CmacRead[3] == 0x00000000u); + static_assert(CmacRead[4] == 0x00000000u); + static_assert(CmacRead[5] == 0x00000000u); + static_assert(CmacRead[6] == 0x00000000u); + static_assert(CmacRead[7] == 0x00AA0000u); + + constexpr inline auto KeySourceWrite = GetSecureScratchMasks<SecureRegister_KeySourceWrite>(); + static_assert(KeySourceWrite[0] == 0x00000000u); + static_assert(KeySourceWrite[1] == 0x00000000u); + static_assert(KeySourceWrite[2] == 0x00000055u); + static_assert(KeySourceWrite[3] == 0x00000000u); + static_assert(KeySourceWrite[4] == 0x00000000u); + static_assert(KeySourceWrite[5] == 0x00000000u); + static_assert(KeySourceWrite[6] == 0x00000000u); + static_assert(KeySourceWrite[7] == 0x00000000u); + + constexpr inline auto KeySourceRead = GetSecureScratchMasks<SecureRegister_KeySourceRead>(); + static_assert(KeySourceRead[0] == 0x00000000u); + static_assert(KeySourceRead[1] == 0x00000000u); + static_assert(KeySourceRead[2] == 0x000000AAu); + static_assert(KeySourceRead[3] == 0x00000000u); + static_assert(KeySourceRead[4] == 0x00000000u); + static_assert(KeySourceRead[5] == 0x00000000u); + static_assert(KeySourceRead[6] == 0x00000000u); + static_assert(KeySourceRead[7] == 0x00000000u); + + constexpr inline auto Srk = GetSecureScratchMasks<SecureRegister_Srk>(); + static_assert(Srk[0] == 0x000FF000u); + static_assert(Srk[1] == 0x00000000u); + static_assert(Srk[2] == 0x00000000u); + static_assert(Srk[3] == 0x00000000u); + static_assert(Srk[4] == 0x00000000u); + static_assert(Srk[5] == 0x00000000u); + static_assert(Srk[6] == 0x00000000u); + static_assert(Srk[7] == 0x00000000u); + + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pmic/max77620.h b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pmic/max77620.h new file mode 100644 index 00000000..b1335544 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pmic/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_ */ \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pmic/max7762x.h b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pmic/max7762x.h new file mode 100644 index 00000000..1c820251 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pmic/max7762x.h @@ -0,0 +1,109 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _MAX7762X_H_ +#define _MAX7762X_H_ + +/* +* Switch Power domains (max77620): +* Name | Usage | uV step | uV min | uV default | uV max | Init +*-------+---------------+---------+--------+------------+---------+------------------ +* sd0 | core | 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_DISABLE (0 << 7) +#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 + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pmic/pmic_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pmic/pmic_api.cpp new file mode 100644 index 00000000..c1692f39 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/pmic/pmic_api.cpp @@ -0,0 +1,291 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "max77620.h" +#include "max7762x.h" + +namespace ams::pmic { + + namespace { + + constexpr inline int I2cAddressEristaMax77621 = 0x1B; + constexpr inline int I2cAddressMarikoMax77812_A = 0x31; + constexpr inline int I2cAddressMarikoMax77812_B = 0x33; + + constexpr inline int I2cAddressMax77620Pmic = 0x3C; + + /* https://github.com/Atmosphere-NX/Atmosphere/blob/master/emummc/source/power/max77620.h */ + /* https://github.com/Atmosphere-NX/Atmosphere/blob/master/emummc/source/power/max7762x.h */ + /* TODO: Find datasheet, link to it instead. */ + /* NOTE: Tentatively, Max77620 "mostly" matches https://datasheets.maximintegrated.com/en/ds/MAX77863.pdf. */ + /* This does not contain Max77621 documentation, though. */ + constexpr inline int Max77620RegisterCnfgBbc = 0x04; + constexpr inline int Max77620RegisterOnOffStat = 0x15; + constexpr inline int Max77620RegisterSd0 = 0x16; + constexpr inline int Max77620RegisterSd1 = 0x17; + constexpr inline int Max77620RegisterCnfg2Sd = 0x22; + constexpr inline int Max77620RegisterCnfg1Ldo8 = 0x33; + constexpr inline int Max77620RegisterGpio0 = 0x36; + constexpr inline int Max77620RegisterAmeGpio = 0x40; + constexpr inline int Max77620RegisterOnOffCnfg1 = 0x41; + constexpr inline int Max77620RegisterCnfgFps0 = 0x43; + constexpr inline int Max77620RegisterCnfgFps1 = 0x44; + constexpr inline int Max77620RegisterCnfgFps2 = 0x45; + constexpr inline int Max77620RegisterFpsLdo4 = 0x4A; + constexpr inline int Max77620RegisterFpsLdo8 = 0x4E; + constexpr inline int Max77620RegisterFpsSd0 = 0x4F; + constexpr inline int Max77620RegisterFpsSd1 = 0x50; + constexpr inline int Max77620RegisterFpsSd3 = 0x52; + constexpr inline int Max77620RegisterFpsGpio3 = 0x56; + + constexpr inline int Max77621RegisterVOut = 0x00; + constexpr inline int Max77621RegisterVOutDvc = 0x01; + constexpr inline int Max77621RegisterControl1 = 0x02; + constexpr inline int Max77621RegisterControl2 = 0x03; + + + /* https://datasheets.maximintegrated.com/en/ds/MAX77812.pdf */ + constexpr inline int Max77812RegisterEnCtrl = 0x06; + constexpr inline int Max77812RegisterM4VOut = 0x26; + + void Max77620EnableGpio(int gpio) { + u8 val; + + /* Clear the AE for the GPIO */ + if (i2c::Query(std::addressof(val), sizeof(val), i2c::Port_5, I2cAddressMax77620Pmic, Max77620RegisterAmeGpio)) { + val &= ~(1 << gpio); + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, Max77620RegisterAmeGpio, val); + } + + /* Set GPIO_DRV_PUSHPULL (bit 0), GPIO_OUTPUT_VAL_HIGH (bit 3). */ + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, Max77620RegisterGpio0 + gpio, MAX77620_CNFG_GPIO_DRV_PUSHPULL | MAX77620_CNFG_GPIO_OUTPUT_VAL_HIGH); + } + + void SetEnBitErista() { + i2c::SendByte(i2c::Port_5, I2cAddressEristaMax77621, Max77621RegisterVOut, MAX77621_VOUT_ENABLE); + } + + void EnableVddCpuErista() { + /* Enable GPIO 5. */ + /* TODO: What does this control? */ + Max77620EnableGpio(5); + + /* Configure Max77621 control registers. */ + i2c::SendByte(i2c::Port_5, I2cAddressEristaMax77621, Max77621RegisterControl1, MAX77621_AD_ENABLE | MAX77621_NFSR_ENABLE | MAX77621_SNS_ENABLE | MAX77621_RAMP_12mV_PER_US); + i2c::SendByte(i2c::Port_5, I2cAddressEristaMax77621, Max77621RegisterControl2, MAX77621_T_JUNCTION_120 | MAX77621_WDTMR_ENABLE | MAX77621_CKKADV_TRIP_75mV_PER_US| MAX77621_INDUCTOR_NOMINAL); + + /* Configure Max77621 VOut to 0.95v */ + i2c::SendByte(i2c::Port_5, I2cAddressEristaMax77621, Max77621RegisterVOut, MAX77621_VOUT_ENABLE | MAX77621_VOUT_0_95V); + i2c::SendByte(i2c::Port_5, I2cAddressEristaMax77621, Max77621RegisterVOutDvc, MAX77621_VOUT_ENABLE | MAX77621_VOUT_0_95V); + } + + void DisableVddCpuErista() { + /* Disable Max77621 VOut. */ + i2c::SendByte(i2c::Port_5, I2cAddressEristaMax77621, Max77621RegisterVOut, MAX77621_VOUT_DISABLE); + } + + int GetI2cAddressForMarikoMax77812(Regulator regulator) { + switch (regulator) { + case Regulator_Mariko_Max77812_A: return I2cAddressMarikoMax77812_A; + case Regulator_Mariko_Max77812_B: return I2cAddressMarikoMax77812_B; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void SetEnBitMariko(Regulator regulator) { + /* Set EN_M3_LPM to enable BUCK Master 3 low power mode. */ + i2c::SendByte(i2c::Port_5, GetI2cAddressForMarikoMax77812(regulator), Max77812RegisterEnCtrl, 0x40); + } + + void EnableVddCpuMariko(Regulator regulator) { + const int address = GetI2cAddressForMarikoMax77812(regulator); + + /* Set EN_M3_LPM to enable BUCK Master 3 low power mode. */ + u8 ctrl; + if (i2c::Query(std::addressof(ctrl), sizeof(ctrl), i2c::Port_5, address, Max77812RegisterEnCtrl)) { + ctrl |= 0x40; + i2c::SendByte(i2c::Port_5, address, Max77812RegisterEnCtrl, ctrl); + } + + /* Set BUCK Master 4 output voltage to 110. */ + i2c::SendByte(i2c::Port_5, address, Max77812RegisterM4VOut, 110); + } + + void DisableVddCpuMariko(Regulator regulator) { + const int address = GetI2cAddressForMarikoMax77812(regulator); + + /* Clear EN_M3_LPM to disable BUCK Master 3 low power mode. */ + u8 ctrl; + if (i2c::Query(std::addressof(ctrl), sizeof(ctrl), i2c::Port_5, address, Max77812RegisterEnCtrl)) { + ctrl &= ~0x40; + i2c::SendByte(i2c::Port_5, address, Max77812RegisterEnCtrl, ctrl); + } + } + + u8 GetPmicOnOffStat() { + return i2c::QueryByte(i2c::Port_5, I2cAddressMax77620Pmic, Max77620RegisterOnOffStat); + } + + void ShutdownSystemImpl(bool reboot) { + /* Get value, set or clear software reset mask. */ + u8 on_off_2_val = i2c::QueryByte(i2c::Port_5, I2cAddressMax77620Pmic, MAX77620_REG_ONOFFCNFG2); + if (reboot) { + on_off_2_val |= MAX77620_ONOFFCNFG2_SFT_RST_WK; + } else { + on_off_2_val &= ~(MAX77620_ONOFFCNFG2_SFT_RST_WK); + } + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, MAX77620_REG_ONOFFCNFG2, on_off_2_val); + + /* Get value, set software reset mask. */ + u8 on_off_1_val = i2c::QueryByte(i2c::Port_5, I2cAddressMax77620Pmic, MAX77620_REG_ONOFFCNFG1); + on_off_1_val |= MAX77620_ONOFFCNFG1_SFT_RST; + + /* NOTE: Here, userland finalizes the battery on non-Calcio. */ + if (fuse::GetHardwareType() != fuse::HardwareType_Calcio) { + /* ... */ + } + + /* Actually write the value to trigger shutdown/reset. */ + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, MAX77620_REG_ONOFFCNFG1, on_off_1_val); + } + + void SetBackupBatteryConfig() { + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, Max77620RegisterCnfgBbc, 0x40); + } + + void SetForcePowerOffTimeConfig() { + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, Max77620RegisterOnOffCnfg1, 0x58); + } + + void SetFlexiblePowerSequencer() { + /* Configure FPS registers. */ + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, Max77620RegisterCnfgFps0, 0x38); + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, Max77620RegisterCnfgFps1, 0x3A); + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, Max77620RegisterCnfgFps2, 0x38); + + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, Max77620RegisterFpsLdo4, 0x0F); + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, Max77620RegisterFpsLdo8, 0xC7); + + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, Max77620RegisterFpsSd0, 0x4F); + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, Max77620RegisterFpsSd1, 0x29); + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, Max77620RegisterFpsSd3, 0x1B); + + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, Max77620RegisterFpsGpio3, 0x22); + } + + void SetVoltage(int reg, int mv) { + const u8 v = ((mv - 600) * 1000) / 12500; + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, reg, v); + } + + } + + void SetEnBit(Regulator regulator) { + switch (regulator) { + case Regulator_Erista_Max77621: + return SetEnBitErista(); + case Regulator_Mariko_Max77812_A: + case Regulator_Mariko_Max77812_B: + return SetEnBitMariko(regulator); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void EnableVddCpu(Regulator regulator) { + switch (regulator) { + case Regulator_Erista_Max77621: + return EnableVddCpuErista(); + case Regulator_Mariko_Max77812_A: + case Regulator_Mariko_Max77812_B: + return EnableVddCpuMariko(regulator); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void DisableVddCpu(Regulator regulator) { + switch (regulator) { + case Regulator_Erista_Max77621: + return DisableVddCpuErista(); + case Regulator_Mariko_Max77812_A: + case Regulator_Mariko_Max77812_B: + return DisableVddCpuMariko(regulator); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void EnableSleep() { + /* Get the current onoff cfg. */ + u8 cnfg = i2c::QueryByte(i2c::Port_5, I2cAddressMax77620Pmic, Max77620RegisterOnOffCnfg1); + + /* Set SlpEn. */ + cnfg |= MAX77620_ONOFFCNFG1_SLPEN; + + /* Write the new cfg. */ + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, Max77620RegisterOnOffCnfg1, cnfg); + } + + void ShutdownSystem(bool reboot) { + ShutdownSystemImpl(reboot); + + /* Allow up to 5 seconds for shutdown/reboot to take place. */ + util::WaitMicroSeconds(5'000'000ul); + + AMS_ABORT("Shutdown failed"); + } + + void PowerOff() { + ShutdownSystemImpl(false); + } + + bool IsAcOk() { + return (GetPmicOnOffStat() & (1 << 1)) != 0; + } + + bool IsPowerButtonPressed() { + return (GetPmicOnOffStat() & (1 << 2)) != 0; + } + + void SetSystemSetting(fuse::SocType soc_type) { + SetBackupBatteryConfig(); + SetForcePowerOffTimeConfig(); + if (soc_type == fuse::SocType_Erista) { + SetFlexiblePowerSequencer(); + } + } + + void EnableVddCore(fuse::SocType soc_type) { + if (soc_type == fuse::SocType_Erista) { + SetVoltage(Max77620RegisterSd0, 1125); + } else /* if (soc_type == fuse::SocType_Mariko) */ { + SetVoltage(Max77620RegisterSd0, 1050); + } + } + + void EnableLdo8() { + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, Max77620RegisterCnfg1Ldo8, 0xE8); + } + + void EnableVddMemory(fuse::SocType soc_type) { + /* Disable remote sense for Sd1. */ + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Pmic, Max77620RegisterCnfg2Sd, 0x05); + + /* On Erista, set Sd1 voltage. */ + if (soc_type == fuse::SocType_Erista) { + SetVoltage(Max77620RegisterSd1, 1100); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/rtc/max77620-rtc.h b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/rtc/max77620-rtc.h new file mode 100644 index 00000000..2d38487c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/rtc/max77620-rtc.h @@ -0,0 +1,60 @@ +/* + * PMIC Real Time Clock driver for Nintendo Switch's MAX77620-RTC + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _MFD_MAX77620_RTC_H_ +#define _MFD_MAX77620_RTC_H_ + +#define MAX77620_RTC_I2C_ADDR 0x68 + +#define MAX77620_RTC_NR_TIME_REGS 7 + +#define MAX77620_RTC_CONTROLM_REG 0x02 +#define MAX77620_RTC_CONTROL_REG 0x03 +#define MAX77620_RTC_BIN_FORMAT (1 << 0) +#define MAX77620_RTC_24H (1 << 1) + +#define MAX77620_RTC_UPDATE0_REG 0x04 +#define MAX77620_RTC_WRITE_UPDATE (1 << 0) +#define MAX77620_RTC_READ_UPDATE (1 << 4) + +#define MAX77620_RTC_SEC_REG 0x07 +#define MAX77620_RTC_MIN_REG 0x08 +#define MAX77620_RTC_HOUR_REG 0x09 +#define MAX77620_RTC_HOUR_PM_MASK (1 << 6) +#define MAX77620_RTC_WEEKDAY_REG 0x0A +#define MAX77620_RTC_MONTH_REG 0x0B +#define MAX77620_RTC_YEAR_REG 0x0C +#define MAX77620_RTC_DATE_REG 0x0D + +#define MAX77620_ALARM1_SEC_REG 0x0E +#define MAX77620_ALARM1_MIN_REG 0x0F +#define MAX77620_ALARM1_HOUR_REG 0x10 +#define MAX77620_ALARM1_WEEKDAY_REG 0x11 +#define MAX77620_ALARM1_MONTH_REG 0x12 +#define MAX77620_ALARM1_YEAR_REG 0x13 +#define MAX77620_ALARM1_DATE_REG 0x14 +#define MAX77620_ALARM2_SEC_REG 0x15 +#define MAX77620_ALARM2_MIN_REG 0x16 +#define MAX77620_ALARM2_HOUR_REG 0x17 +#define MAX77620_ALARM2_WEEKDAY_REG 0x18 +#define MAX77620_ALARM2_MONTH_REG 0x19 +#define MAX77620_ALARM2_YEAR_REG 0x1A +#define MAX77620_ALARM2_DATE_REG 0x1B +#define MAX77620_RTC_ALARM_EN_MASK (1 << 7) + +#endif /* _MFD_MAX77620_RTC_H_ */ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/rtc/rtc_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/rtc/rtc_api.cpp new file mode 100644 index 00000000..25910797 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/rtc/rtc_api.cpp @@ -0,0 +1,65 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "max77620-rtc.h" + +namespace ams::rtc { + + namespace { + + constexpr inline int I2cAddressMax77620Rtc = 0x68; + + /* TODO: Find datasheet, link to it instead. */ + /* NOTE: Tentatively, Max77620 "mostly" matches https://datasheets.maximintegrated.com/en/ds/MAX77863.pdf. */ + constexpr inline int Max77620RtcRegisterUpdate0 = 0x04; + + constexpr inline int Max77620RtcRegisterAlarmStart = 0x0E; + + constexpr inline int Max77620RtcRegisterAlarm1Sec = 0x0E; + constexpr inline int Max77620RtcRegisterAlarm1Min = 0x0F; + constexpr inline int Max77620RtcRegisterAlarm1Hour = 0x10; + constexpr inline int Max77620RtcRegisterAlarm1Weekday = 0x11; + constexpr inline int Max77620RtcRegisterAlarm1Month = 0x12; + constexpr inline int Max77620RtcRegisterAlarm1Year = 0x13; + constexpr inline int Max77620RtcRegisterAlarm1Date = 0x14; + constexpr inline int Max77620RtcRegisterAlarm2Sec = 0x15; + constexpr inline int Max77620RtcRegisterAlarm2Min = 0x16; + constexpr inline int Max77620RtcRegisterAlarm2Hour = 0x17; + constexpr inline int Max77620RtcRegisterAlarm2Weekday = 0x18; + constexpr inline int Max77620RtcRegisterAlarm2Month = 0x19; + constexpr inline int Max77620RtcRegisterAlarm2Year = 0x1A; + constexpr inline int Max77620RtcRegisterAlarm2Date = 0x1B; + + constexpr inline int Max77620RtcRegisterAlarmLast = 0x1B; + + } + + void StopAlarm() { + /* Begin update. */ + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Rtc, Max77620RtcRegisterUpdate0, MAX77620_RTC_READ_UPDATE); + + /* Clear ALARM_EN for all alarm registers. */ + for (auto reg = Max77620RtcRegisterAlarmStart; reg <= Max77620RtcRegisterAlarmLast; ++reg) { + u8 val = i2c::QueryByte(i2c::Port_5, I2cAddressMax77620Rtc, reg); + val &= ~MAX77620_RTC_ALARM_EN_MASK; + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Rtc, reg, val); + } + + /* End update. */ + i2c::SendByte(i2c::Port_5, I2cAddressMax77620Rtc, Max77620RtcRegisterUpdate0, MAX77620_RTC_WRITE_UPDATE); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_aes.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_aes.cpp new file mode 100644 index 00000000..5e4a8b65 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_aes.cpp @@ -0,0 +1,703 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "se_execute.hpp" + +namespace ams::se { + + namespace { + + constexpr inline int AesKeySizeMax = 256 / BITSIZEOF(u8); + + enum AesMode { + AesMode_Aes128 = ((SE_CONFIG_ENC_MODE_AESMODE_KEY128 << SE_CONFIG_ENC_MODE_OFFSET) | (SE_CONFIG_DEC_MODE_AESMODE_KEY128 << SE_CONFIG_DEC_MODE_OFFSET)) >> SE_CONFIG_DEC_MODE_OFFSET, + AesMode_Aes192 = ((SE_CONFIG_ENC_MODE_AESMODE_KEY192 << SE_CONFIG_ENC_MODE_OFFSET) | (SE_CONFIG_DEC_MODE_AESMODE_KEY192 << SE_CONFIG_DEC_MODE_OFFSET)) >> SE_CONFIG_DEC_MODE_OFFSET, + AesMode_Aes256 = ((SE_CONFIG_ENC_MODE_AESMODE_KEY256 << SE_CONFIG_ENC_MODE_OFFSET) | (SE_CONFIG_DEC_MODE_AESMODE_KEY256 << SE_CONFIG_DEC_MODE_OFFSET)) >> SE_CONFIG_DEC_MODE_OFFSET, + }; + + enum MemoryInterface { + MemoryInterface_Ahb = SE_CRYPTO_CONFIG_MEMIF_AHB, + MemoryInterface_Mc = SE_CRYPTO_CONFIG_MEMIF_MCCIF, + }; + + constexpr inline u32 AesConfigEcb = reg::Encode(SE_REG_BITS_VALUE(CRYPTO_CONFIG_CTR_CNTN, 0), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_KEYSCH_BYPASS, DISABLE), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_IV_SELECT, ORIGINAL), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_VCTRAM_SEL, MEMORY), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_INPUT_SEL, MEMORY), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_XOR_POS, BYPASS), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_HASH_ENB, DISABLE)); + + constexpr inline u32 AesConfigCtr = reg::Encode(SE_REG_BITS_VALUE(CRYPTO_CONFIG_CTR_CNTN, 1), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_KEYSCH_BYPASS, DISABLE), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_IV_SELECT, ORIGINAL), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_VCTRAM_SEL, MEMORY), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_INPUT_SEL, LINEAR_CTR), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_XOR_POS, BOTTOM), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_HASH_ENB, DISABLE)); + + constexpr inline u32 AesConfigCmac = reg::Encode(SE_REG_BITS_VALUE(CRYPTO_CONFIG_CTR_CNTN, 0), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_KEYSCH_BYPASS, DISABLE), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_IV_SELECT, ORIGINAL), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_VCTRAM_SEL, INIT_AESOUT), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_INPUT_SEL, MEMORY), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_XOR_POS, TOP), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_HASH_ENB, ENABLE)); + + constexpr inline u32 AesConfigCbcEncrypt = reg::Encode(SE_REG_BITS_VALUE(CRYPTO_CONFIG_CTR_CNTN, 0), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_KEYSCH_BYPASS, DISABLE), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_IV_SELECT, ORIGINAL), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_VCTRAM_SEL, INIT_AESOUT), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_INPUT_SEL, MEMORY), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_XOR_POS, TOP), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_HASH_ENB, DISABLE)); + + constexpr inline u32 AesConfigCbcDecrypt = reg::Encode(SE_REG_BITS_VALUE(CRYPTO_CONFIG_CTR_CNTN, 0), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_KEYSCH_BYPASS, DISABLE), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_IV_SELECT, ORIGINAL), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_VCTRAM_SEL, INIT_PREV_MEMORY), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_INPUT_SEL, MEMORY), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_XOR_POS, BOTTOM), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_HASH_ENB, DISABLE)); + + void SetConfig(volatile SecurityEngineRegisters *SE, bool encrypt, SE_CONFIG_DST dst) { + reg::Write(SE->SE_CONFIG, SE_REG_BITS_ENUM (CONFIG_ENC_MODE, AESMODE_KEY128), + SE_REG_BITS_ENUM (CONFIG_DEC_MODE, AESMODE_KEY128), + SE_REG_BITS_ENUM_SEL(CONFIG_ENC_ALG, encrypt, AES_ENC, NOP), + SE_REG_BITS_ENUM_SEL(CONFIG_DEC_ALG, encrypt, NOP, AES_DEC), + SE_REG_BITS_VALUE (CONFIG_DST, dst)); + } + + void SetAesConfig(volatile SecurityEngineRegisters *SE, int slot, bool encrypt, u32 config) { + const u32 encoded = reg::Encode(SE_REG_BITS_ENUM (CRYPTO_CONFIG_MEMIF, AHB), + SE_REG_BITS_VALUE (CRYPTO_CONFIG_KEY_INDEX, slot), + SE_REG_BITS_ENUM_SEL(CRYPTO_CONFIG_CORE_SEL, encrypt, ENCRYPT, DECRYPT)); + + reg::Write(SE->SE_CRYPTO_CONFIG, (config | encoded)); + } + + void SetBlockCount(volatile SecurityEngineRegisters *SE, int count) { + reg::Write(SE->SE_CRYPTO_LAST_BLOCK, count - 1); + } + + void UpdateAesMode(volatile SecurityEngineRegisters *SE, AesMode mode) { + reg::ReadWrite(SE->SE_CONFIG, REG_BITS_VALUE(16, 16, mode)); + } + + void UpdateMemoryInterface(volatile SecurityEngineRegisters *SE, MemoryInterface memif) { + reg::ReadWrite(SE->SE_CRYPTO_CONFIG, SE_REG_BITS_VALUE(CRYPTO_CONFIG_MEMIF, memif)); + } + + void SetCounter(volatile SecurityEngineRegisters *SE, const void *ctr) { + const u32 *ctr_32 = reinterpret_cast<const u32 *>(ctr); + + /* Copy the input ctr to the linear CTR registers. */ + reg::Write(SE->SE_CRYPTO_LINEAR_CTR[0], util::LoadLittleEndian(ctr_32 + 0)); + reg::Write(SE->SE_CRYPTO_LINEAR_CTR[1], util::LoadLittleEndian(ctr_32 + 1)); + reg::Write(SE->SE_CRYPTO_LINEAR_CTR[2], util::LoadLittleEndian(ctr_32 + 2)); + reg::Write(SE->SE_CRYPTO_LINEAR_CTR[3], util::LoadLittleEndian(ctr_32 + 3)); + } + + void SetAesKeyIv(volatile SecurityEngineRegisters *SE, int slot, const void *iv, size_t iv_size) { + AMS_ABORT_UNLESS(0 <= slot && slot < AesKeySlotCount); + AMS_ABORT_UNLESS(iv_size <= AesBlockSize); + + /* Set each iv word in order. */ + const u32 *iv_u32 = static_cast<const u32 *>(iv); + const int num_words = iv_size / sizeof(u32); + for (int i = 0; i < num_words; ++i) { + /* Select the keyslot. */ + reg::Write(SE->SE_CRYPTO_KEYTABLE_ADDR, SE_REG_BITS_VALUE(CRYPTO_KEYTABLE_ADDR_KEYIV_KEY_SLOT, slot), + SE_REG_BITS_ENUM (CRYPTO_KEYTABLE_ADDR_KEYIV_KEYIV_SEL, IV), + SE_REG_BITS_ENUM (CRYPTO_KEYTABLE_ADDR_KEYIV_IV_SEL, ORIGINAL_IV), + SE_REG_BITS_VALUE(CRYPTO_KEYTABLE_ADDR_KEYIV_KEY_WORD, i)); + + /* Set the iv word. */ + SE->SE_CRYPTO_KEYTABLE_DATA = *(iv_u32++); + } + } + + void SetEncryptedAesKey(int dst_slot, int kek_slot, const void *key, size_t key_size, AesMode mode) { + AMS_ABORT_UNLESS(key_size <= AesKeySizeMax); + AMS_ABORT_UNLESS(0 <= dst_slot && dst_slot < AesKeySlotCount); + AMS_ABORT_UNLESS(0 <= kek_slot && kek_slot < AesKeySlotCount); + + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Configure for single AES ECB decryption to key table. */ + SetConfig(SE, false, SE_CONFIG_DST_KEYTABLE); + SetAesConfig(SE, kek_slot, false, AesConfigEcb); + UpdateAesMode(SE, mode); + SetBlockCount(SE, 1); + + /* Select the destination keyslot. */ + reg::Write(SE->SE_CRYPTO_KEYTABLE_DST, SE_REG_BITS_VALUE(CRYPTO_KEYTABLE_DST_KEY_INDEX, dst_slot), SE_REG_BITS_ENUM(CRYPTO_KEYTABLE_DST_WORD_QUAD, KEYS_0_3)); + + /* Ensure that the se sees the keydata we want it to. */ + hw::FlushDataCache(key, key_size); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Execute the operation. */ + ExecuteOperation(SE, SE_OPERATION_OP_START, nullptr, 0, key, key_size); + } + + void EncryptAes(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, AesMode mode) { + /* If nothing to decrypt, succeed. */ + if (src_size == 0) { return; } + + /* Validate input. */ + AMS_ABORT_UNLESS(dst_size == AesBlockSize); + AMS_ABORT_UNLESS(src_size == AesBlockSize); + AMS_ABORT_UNLESS(0 <= slot && slot < AesKeySlotCount); + + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Configure for AES-ECB encryption to memory. */ + SetConfig(SE, true, SE_CONFIG_DST_MEMORY); + SetAesConfig(SE, slot, true, AesConfigEcb); + UpdateAesMode(SE, mode); + + /* Execute the operation. */ + ExecuteOperationSingleBlock(SE, dst, dst_size, src, src_size); + } + + void ExpandSubkey(u8 *subkey) { + /* Shift everything left one bit. */ + u8 prev = 0; + for (int i = AesBlockSize - 1; i >= 0; --i) { + const u8 top = (subkey[i] >> 7); + subkey[i] = ((subkey[i] << 1) | prev); + prev = top; + } + + /* And xor with Rb if necessary. */ + if (prev != 0) { + subkey[AesBlockSize - 1] ^= 0x87; + } + } + + void ExpandSubkeyLittleEndian(u8 *subkey) { + /* Shift everything left one bit. */ + u8 prev = 0; + for (size_t i = 0; i < AesBlockSize; ++i) { + const u8 top = (subkey[i] >> 7); + subkey[i] = ((subkey[i] << 1) | prev); + prev = top; + } + + /* And xor with Rb if necessary. */ + if (prev != 0) { + subkey[0] ^= 0x87; + } + } + + void GetCmacResult(volatile SecurityEngineRegisters *SE, void *dst, size_t dst_size) { + const int num_words = dst_size / sizeof(u32); + for (int i = 0; i < num_words; ++i) { + reg::Write(static_cast<u32 *>(dst) + i, reg::Read(SE->SE_HASH_RESULT[i])); + } + } + + void ComputeAesCmac(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, AesMode mode) { + /* Validate input. */ + AMS_ABORT_UNLESS(0 <= slot && slot < AesKeySlotCount); + + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Determine mac extents. */ + const int num_blocks = util::DivideUp(src_size, AesBlockSize); + const size_t last_block_size = (src_size == 0) ? 0 : (src_size - ((num_blocks - 1) * AesBlockSize)); + + /* Create subkey. */ + u8 subkey[AesBlockSize]; + { + /* Encrypt zeroes. */ + std::memset(subkey, 0, sizeof(subkey)); + EncryptAes(subkey, sizeof(subkey), slot, subkey, sizeof(subkey), mode); + + /* Expand. */ + ExpandSubkey(subkey); + + /* Account for last block. */ + if (last_block_size != AesBlockSize) { + ExpandSubkey(subkey); + } + } + + /* Configure for AES-CMAC. */ + SetConfig(SE, true, SE_CONFIG_DST_HASH_REG); + SetAesConfig(SE, slot, true, AesConfigCmac); + UpdateAesMode(SE, mode); + + /* Set the IV to zero. */ + for (int i = 0; i < 4; ++i) { + /* Select the keyslot. */ + reg::Write(SE->SE_CRYPTO_KEYTABLE_ADDR, SE_REG_BITS_VALUE(CRYPTO_KEYTABLE_ADDR_KEYIV_KEY_SLOT, slot), + SE_REG_BITS_ENUM (CRYPTO_KEYTABLE_ADDR_KEYIV_KEYIV_SEL, IV), + SE_REG_BITS_ENUM (CRYPTO_KEYTABLE_ADDR_KEYIV_IV_SEL, ORIGINAL_IV), + SE_REG_BITS_VALUE(CRYPTO_KEYTABLE_ADDR_KEYIV_KEY_WORD, i)); + + /* Set the iv word. */ + SE->SE_CRYPTO_KEYTABLE_DATA = 0; + } + + /* Handle blocks before the last. */ + if (num_blocks > 1) { + SetBlockCount(SE, num_blocks - 1); + ExecuteOperation(SE, SE_OPERATION_OP_START, nullptr, 0, src, src_size); + reg::ReadWrite(SE->SE_CRYPTO_CONFIG, SE_REG_BITS_ENUM(CRYPTO_CONFIG_IV_SELECT, UPDATED)); + } + + /* Handle the last block. */ + { + SetBlockCount(SE, 1); + + /* Create the last block. */ + u8 last_block[AesBlockSize]; + if (last_block_size < sizeof(last_block)) { + std::memset(last_block, 0, sizeof(last_block)); + last_block[last_block_size] = 0x80; + } + std::memcpy(last_block, static_cast<const u8 *>(src) + src_size - last_block_size, last_block_size); + + /* Xor with the subkey. */ + for (size_t i = 0; i < AesBlockSize; ++i) { + last_block[i] ^= subkey[i]; + } + + /* Ensure the SE sees correct data. */ + hw::FlushDataCache(last_block, sizeof(last_block)); + hw::DataSynchronizationBarrierInnerShareable(); + + ExecuteOperation(SE, SE_OPERATION_OP_START, nullptr, 0, last_block, sizeof(last_block)); + } + + /* Get the output. */ + GetCmacResult(SE, dst, dst_size); + } + + void EncryptAesCbc(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, const void *iv, size_t iv_size, AesMode mode) { + /* If nothing to encrypt, succeed. */ + if (src_size == 0) { return; } + + /* Validate input. */ + AMS_ABORT_UNLESS(iv_size == AesBlockSize); + AMS_ABORT_UNLESS(0 <= slot && slot < AesKeySlotCount); + + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Determine extents. */ + const size_t num_blocks = src_size / AesBlockSize; + const size_t aligned_size = num_blocks * AesBlockSize; + AMS_ABORT_UNLESS(src_size == aligned_size); + + /* Configure for aes-cbc encryption. */ + SetConfig(SE, true, SE_CONFIG_DST_MEMORY); + SetAesConfig(SE, slot, true, AesConfigCbcEncrypt); + UpdateAesMode(SE, mode); + + /* Set the iv. */ + SetAesKeyIv(SE, slot, iv, iv_size); + + /* Set the block count. */ + SetBlockCount(SE, num_blocks); + + /* Execute the operation. */ + ExecuteOperation(SE, SE_OPERATION_OP_START, dst, dst_size, src, aligned_size); + } + + void DecryptAesCbc(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, const void *iv, size_t iv_size, AesMode mode) { + /* If nothing to decrypt, succeed. */ + if (src_size == 0) { return; } + + /* Validate input. */ + AMS_ABORT_UNLESS(iv_size == AesBlockSize); + AMS_ABORT_UNLESS(0 <= slot && slot < AesKeySlotCount); + + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Determine extents. */ + const size_t num_blocks = src_size / AesBlockSize; + const size_t aligned_size = num_blocks * AesBlockSize; + AMS_ABORT_UNLESS(src_size == aligned_size); + + /* Configure for aes-cbc encryption. */ + SetConfig(SE, false, SE_CONFIG_DST_MEMORY); + SetAesConfig(SE, slot, false, AesConfigCbcDecrypt); + UpdateAesMode(SE, mode); + + /* Set the iv. */ + SetAesKeyIv(SE, slot, iv, iv_size); + + /* Set the block count. */ + SetBlockCount(SE, num_blocks); + + /* Execute the operation. */ + ExecuteOperation(SE, SE_OPERATION_OP_START, dst, dst_size, src, aligned_size); + } + + void XorWithXtsTweak(void *dst, size_t dst_size, const void *src, size_t src_size, const void *base_tweak) { + /* Copy tweak. */ + u8 tweak[se::AesBlockSize]; + std::memcpy(tweak, base_tweak, sizeof(tweak)); + + /* Perform xor. */ + u8 *dst_u8 = static_cast<u8 *>(dst); + const u8 *src_u8 = static_cast<const u8 *>(src); + + const size_t num_blocks = std::min<size_t>(dst_size, src_size) / sizeof(tweak); + for (size_t i = 0; i < num_blocks; ++i) { + for (size_t j = 0; j < sizeof(tweak); ++j) { + dst_u8[j] = src_u8[j] ^ tweak[j]; + } + + dst_u8 += sizeof(tweak); + src_u8 += sizeof(tweak); + + ExpandSubkeyLittleEndian(tweak); + } + } + + void DecryptAesXts(void *dst, size_t dst_size, int slot_enc, int slot_tweak, const void *src, size_t src_size, size_t sector, AesMode mode) { + /* If nothing to decrypt, succeed. */ + if (src_size == 0) { return; } + + /* Validate input. */ + AMS_ABORT_UNLESS(util::IsAligned(dst_size, AesBlockSize)); + AMS_ABORT_UNLESS(util::IsAligned(src_size, AesBlockSize)); + AMS_ABORT_UNLESS(0 <= slot_enc && slot_enc < AesKeySlotCount); + AMS_ABORT_UNLESS(0 <= slot_tweak && slot_tweak < AesKeySlotCount); + + /* Generate tweak. */ + u32 base_tweak[se::AesBlockSize / sizeof(u32)] = {}; + base_tweak[util::size(base_tweak) - 1] = util::ConvertToBigEndian<u32>(static_cast<u32>(sector)); + if constexpr (sizeof(sector) > sizeof(u32)) { + static_assert(sizeof(sector) <= sizeof(u64)); + base_tweak[util::size(base_tweak) - 2] = util::ConvertToBigEndian<u32>(static_cast<u32>(sector >> BITSIZEOF(u32))); + } + se::EncryptAes128(base_tweak, sizeof(base_tweak), slot_tweak, base_tweak, sizeof(base_tweak)); + + /* Xor all data. */ + XorWithXtsTweak(dst, dst_size, src, src_size, base_tweak); + + /* Ensure the SE sees correct data. */ + hw::FlushDataCache(dst, dst_size); + + /* Decrypt all data. */ + { + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Determine extents. */ + const size_t num_blocks = dst_size / AesBlockSize; + + /* Configure for AES-ECB decryption to memory. */ + SetConfig(SE, false, SE_CONFIG_DST_MEMORY); + SetAesConfig(SE, slot_enc, false, AesConfigEcb); + UpdateAesMode(SE, mode); + + /* Set the block count. */ + SetBlockCount(SE, num_blocks); + + /* Execute the operation. */ + ExecuteOperation(SE, SE_OPERATION_OP_START, dst, dst_size, dst, dst_size); + + /* Ensure the cpu sees correct data. */ + hw::InvalidateDataCache(dst, dst_size); + } + + /* Xor all data. */ + XorWithXtsTweak(dst, dst_size, dst, dst_size, base_tweak); + } + + void ComputeAes128Async(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, DoneHandler handler, u32 config, bool encrypt, volatile SecurityEngineRegisters *SE) { + /* If nothing to decrypt, succeed. */ + if (size == 0) { return; } + + /* Validate input. */ + AMS_ABORT_UNLESS(0 <= slot && slot < AesKeySlotCount); + + /* Configure for the specific operation. */ + SetConfig(SE, encrypt, SE_CONFIG_DST_MEMORY); + SetAesConfig(SE, slot, encrypt, config); + UpdateMemoryInterface(SE, MemoryInterface_Mc); + + /* Configure the number of blocks. */ + const int num_blocks = size / AesBlockSize; + SetBlockCount(SE, num_blocks); + + /* Set the done handler. */ + SetDoneHandler(SE, handler); + + /* Start the raw operation. */ + StartOperationRaw(SE, SE_OPERATION_OP_START, out_ll_address, in_ll_address); + } + + void ClearAesKeySlot(volatile SecurityEngineRegisters *SE, int slot) { + /* Validate the key slot. */ + AMS_ABORT_UNLESS(0 <= slot && slot < AesKeySlotCount); + + for (int i = 0; i < 16; ++i) { + /* Select the keyslot. */ + reg::Write(SE->SE_CRYPTO_KEYTABLE_ADDR, SE_REG_BITS_VALUE(CRYPTO_KEYTABLE_ADDR_KEYIV_KEY_SLOT, slot), SE_REG_BITS_VALUE(CRYPTO_KEYTABLE_ADDR_KEYIV_WORD, i)); + + /* Write the data. */ + SE->SE_CRYPTO_KEYTABLE_DATA = 0; + } + } + + } + + void ClearAesKeySlot(int slot) { + /* Clear the slot in SE1. */ + ClearAesKeySlot(GetRegisters(), slot); + } + + void ClearAesKeySlot2(int slot) { + /* Clear the slot in SE2. */ + ClearAesKeySlot(GetRegisters2(), slot); + } + + void ClearAesKeyIv(int slot) { + /* Validate the key slot. */ + AMS_ABORT_UNLESS(0 <= slot && slot < AesKeySlotCount); + + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Set each iv word in order. */ + for (int i = 0; i < 4; ++i) { + /* Select the keyslot original iv. */ + reg::Write(SE->SE_CRYPTO_KEYTABLE_ADDR, SE_REG_BITS_VALUE(CRYPTO_KEYTABLE_ADDR_KEYIV_KEY_SLOT, slot), + SE_REG_BITS_ENUM (CRYPTO_KEYTABLE_ADDR_KEYIV_KEYIV_SEL, IV), + SE_REG_BITS_ENUM (CRYPTO_KEYTABLE_ADDR_KEYIV_IV_SEL, ORIGINAL_IV), + SE_REG_BITS_VALUE(CRYPTO_KEYTABLE_ADDR_KEYIV_KEY_WORD, i)); + + /* Set the iv word. */ + SE->SE_CRYPTO_KEYTABLE_DATA = 0; + + /* Select the keyslot updated iv. */ + reg::Write(SE->SE_CRYPTO_KEYTABLE_ADDR, SE_REG_BITS_VALUE(CRYPTO_KEYTABLE_ADDR_KEYIV_KEY_SLOT, slot), + SE_REG_BITS_ENUM (CRYPTO_KEYTABLE_ADDR_KEYIV_KEYIV_SEL, IV), + SE_REG_BITS_ENUM (CRYPTO_KEYTABLE_ADDR_KEYIV_IV_SEL, UPDATED_IV), + SE_REG_BITS_VALUE(CRYPTO_KEYTABLE_ADDR_KEYIV_KEY_WORD, i)); + + /* Set the iv word. */ + SE->SE_CRYPTO_KEYTABLE_DATA = 0; + } + } + + void LockAesKeySlot(int slot, u32 flags) { + /* Validate the key slot. */ + AMS_ABORT_UNLESS(0 <= slot && slot < AesKeySlotCount); + + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Set non per-key flags. */ + if ((flags & ~KeySlotLockFlags_PerKey) != 0) { + /* KeySlotLockFlags_DstKeyTableOnly is Mariko-only. */ + if (fuse::GetSocType() == fuse::SocType_Mariko) { + reg::ReadWrite(SE->SE_CRYPTO_KEYTABLE_ACCESS[slot], REG_BITS_VALUE(0, 7, ~flags), REG_BITS_VALUE(7, 1, ((flags & KeySlotLockFlags_DstKeyTableOnly) != 0) ? 1 : 0)); + } else { + reg::ReadWrite(SE->SE_CRYPTO_KEYTABLE_ACCESS[slot], REG_BITS_VALUE(0, 7, ~flags)); + } + } + + /* Set per-key flag. */ + if ((flags & KeySlotLockFlags_PerKey) != 0) { + reg::ReadWrite(SE->SE_CRYPTO_SECURITY_PERKEY, REG_BITS_VALUE(slot, 1, 0)); + } + } + + void SetAesKey(int slot, const void *key, size_t key_size) { + /* Validate the key slot and key size. */ + AMS_ABORT_UNLESS(0 <= slot && slot < AesKeySlotCount); + AMS_ABORT_UNLESS(key_size <= AesKeySizeMax); + + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Set each key word in order. */ + const u32 *key_u32 = static_cast<const u32 *>(key); + const int num_words = key_size / sizeof(u32); + for (int i = 0; i < num_words; ++i) { + /* Select the keyslot. */ + reg::Write(SE->SE_CRYPTO_KEYTABLE_ADDR, SE_REG_BITS_VALUE(CRYPTO_KEYTABLE_ADDR_KEYIV_KEY_SLOT, slot), + SE_REG_BITS_ENUM (CRYPTO_KEYTABLE_ADDR_KEYIV_KEYIV_SEL, KEY), + SE_REG_BITS_VALUE(CRYPTO_KEYTABLE_ADDR_KEYIV_KEY_WORD, i)); + + /* Set the key word. */ + SE->SE_CRYPTO_KEYTABLE_DATA = *(key_u32++); + } + } + + void SetEncryptedAesKey128(int dst_slot, int kek_slot, const void *key, size_t key_size) { + return SetEncryptedAesKey(dst_slot, kek_slot, key, key_size, AesMode_Aes128); + } + + void SetEncryptedAesKey256(int dst_slot, int kek_slot, const void *key, size_t key_size) { + return SetEncryptedAesKey(dst_slot, kek_slot, key, key_size, AesMode_Aes256); + } + + void EncryptAes128(void *dst, size_t dst_size, int slot, const void *src, size_t src_size) { + return EncryptAes(dst, dst_size, slot, src, src_size, AesMode_Aes128); + } + + void DecryptAes128(void *dst, size_t dst_size, int slot, const void *src, size_t src_size) { + /* If nothing to decrypt, succeed. */ + if (src_size == 0) { return; } + + /* Validate input. */ + AMS_ABORT_UNLESS(dst_size == AesBlockSize); + AMS_ABORT_UNLESS(src_size == AesBlockSize); + AMS_ABORT_UNLESS(0 <= slot && slot < AesKeySlotCount); + + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Configure for AES-ECB decryption to memory. */ + SetConfig(SE, false, SE_CONFIG_DST_MEMORY); + SetAesConfig(SE, slot, false, AesConfigEcb); + + ExecuteOperationSingleBlock(SE, dst, dst_size, src, src_size); + } + + void ComputeAes128Ctr(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, const void *iv, size_t iv_size) { + /* If nothing to do, succeed. */ + if (src_size == 0) { return; } + + /* Validate input. */ + AMS_ABORT_UNLESS(iv_size == AesBlockSize); + AMS_ABORT_UNLESS(0 <= slot && slot < AesKeySlotCount); + + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Determine how many full blocks we can operate on. */ + const size_t num_blocks = src_size / AesBlockSize; + const size_t aligned_size = num_blocks * AesBlockSize; + const size_t fractional = src_size - aligned_size; + + /* Here Nintendo writes 1 to SE_SPARE. It's unclear why they do this, but we will do so as well. */ + SE->SE_SPARE = 0x1; + + /* Configure for AES-CTR encryption/decryption to memory. */ + SetConfig(SE, true, SE_CONFIG_DST_MEMORY); + SetAesConfig(SE, slot, true, AesConfigCtr); + + /* Set the counter. */ + SetCounter(SE, iv); + + /* Process as many aligned blocks as we can. */ + if (aligned_size > 0) { + /* Configure the engine to process the right number of blocks. */ + SetBlockCount(SE, num_blocks); + + /* Execute the operation. */ + ExecuteOperation(SE, SE_OPERATION_OP_START, dst, dst_size, src, aligned_size); + + /* Synchronize around this point. */ + hw::DataSynchronizationBarrierInnerShareable(); + } + + /* Process a single block to output. */ + if (fractional > 0 && dst_size > aligned_size) { + const size_t copy_size = std::min(fractional, dst_size - aligned_size); + + ExecuteOperationSingleBlock(SE, static_cast<u8 *>(dst) + aligned_size, copy_size, static_cast<const u8 *>(src) + aligned_size, fractional); + } + } + + void ComputeAes128Cmac(void *dst, size_t dst_size, int slot, const void *src, size_t src_size) { + return ComputeAesCmac(dst, dst_size, slot, src, src_size, AesMode_Aes128); + } + + void ComputeAes256Cmac(void *dst, size_t dst_size, int slot, const void *src, size_t src_size) { + return ComputeAesCmac(dst, dst_size, slot, src, src_size, AesMode_Aes256); + } + + void EncryptAes128Cbc(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, const void *iv, size_t iv_size) { + return EncryptAesCbc(dst, dst_size, slot, src, src_size, iv, iv_size, AesMode_Aes128); + } + + void EncryptAes256Cbc(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, const void *iv, size_t iv_size) { + return EncryptAesCbc(dst, dst_size, slot, src, src_size, iv, iv_size, AesMode_Aes256); + } + + void DecryptAes128Cbc(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, const void *iv, size_t iv_size) { + return DecryptAesCbc(dst, dst_size, slot, src, src_size, iv, iv_size, AesMode_Aes128); + } + + void DecryptAes256Cbc(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, const void *iv, size_t iv_size) { + return DecryptAesCbc(dst, dst_size, slot, src, src_size, iv, iv_size, AesMode_Aes256); + } + + void DecryptAes128Xts(void *dst, size_t dst_size, int slot_enc, int slot_tweak, const void *src, size_t src_size, size_t sector) { + return DecryptAesXts(dst, dst_size, slot_enc, slot_tweak, src, src_size, sector, AesMode_Aes128); + } + + void EncryptAes128CbcAsync(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, const void *iv, size_t iv_size, DoneHandler handler) { + /* Validate the iv. */ + AMS_ABORT_UNLESS(iv_size == AesBlockSize); + + /* Get the registers. */ + volatile auto *SE = GetRegisters(); + + /* Set the iv. */ + SetAesKeyIv(SE, slot, iv, iv_size); + + /* Perform the asynchronous aes operation. */ + ComputeAes128Async(out_ll_address, slot, in_ll_address, size, handler, AesConfigCbcEncrypt, true, SE); + } + + void DecryptAes128CbcAsync(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, const void *iv, size_t iv_size, DoneHandler handler) { + /* Validate the iv. */ + AMS_ABORT_UNLESS(iv_size == AesBlockSize); + + /* Get the registers. */ + volatile auto *SE = GetRegisters(); + + /* Set the iv. */ + SetAesKeyIv(SE, slot, iv, iv_size); + + /* Perform the asynchronous aes operation. */ + ComputeAes128Async(out_ll_address, slot, in_ll_address, size, handler, AesConfigCbcDecrypt, false, SE); + } + + void ComputeAes128CtrAsync(u32 out_ll_address, int slot, u32 in_ll_address, u32 size, const void *iv, size_t iv_size, DoneHandler handler) { + /* Validate the iv. */ + AMS_ABORT_UNLESS(iv_size == AesBlockSize); + + /* Get the registers. */ + volatile auto *SE = GetRegisters(); + + /* Here Nintendo writes 1 to SE_SPARE. It's unclear why they do this, but we will do so as well. */ + SE->SE_SPARE = 0x1; + + /* Set the counter. */ + SetCounter(SE, iv); + + /* Perform the asynchronous aes operation. */ + ComputeAes128Async(out_ll_address, slot, in_ll_address, size, handler, AesConfigCtr, true, SE); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_execute.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_execute.cpp new file mode 100644 index 00000000..1ae0ae7b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_execute.cpp @@ -0,0 +1,181 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "se_execute.hpp" + +namespace ams::se { + + namespace { + + struct LinkedListEntry { + u32 zero; + u32 address; + u32 size; + }; + static_assert(util::is_pod<LinkedListEntry>::value); + static_assert(sizeof(LinkedListEntry) == 0xC); + + uintptr_t GetPhysicalAddress(const void *ptr) { + const uintptr_t virt_address = reinterpret_cast<uintptr_t>(ptr); + + #if defined(ATMOSPHERE_ARCH_ARM64) + u64 phys_address; + __asm__ __volatile__("at s1e3r, %[virt]; mrs %[phys], par_el1" : [phys]"=r"(phys_address) : [virt]"r"(virt_address) : "memory", "cc"); + return (phys_address & 0x0000FFFFFFFFF000ul) | (virt_address & 0x0000000000000FFFul); + #elif defined(ATMOSPHERE_ARCH_ARM) + return virt_address; + #else + #error "Unknown architecture for Tegra Security Engine physical address translation" + #endif + } + + constexpr void SetLinkedListEntry(LinkedListEntry *entry, const void *ptr, size_t size) { + /* Clear the zero field. */ + entry->zero = 0; + + /* Set the address. */ + if (ptr != nullptr) { + entry->address = GetPhysicalAddress(ptr); + entry->size = static_cast<u32>(size); + } else { + entry->address = 0; + entry->size = 0; + } + } + + void StartOperation(volatile SecurityEngineRegisters *SE, SE_OPERATION_OP op) { + /* Write back the current values of the error and interrupt status. */ + reg::Write(SE->SE_ERR_STATUS, reg::Read(SE->SE_ERR_STATUS)); + reg::Write(SE->SE_INT_STATUS, reg::Read(SE->SE_INT_STATUS)); + + /* Write the operation. */ + reg::Write(SE->SE_OPERATION, SE_REG_BITS_VALUE(OPERATION_OP, op)); + } + + void EnsureOperationStarted(volatile SecurityEngineRegisters *SE) { + /* Read the operation register to make sure our write takes. */ + reg::Read(SE->SE_OPERATION); + + hw::DataSynchronizationBarrierInnerShareable(); + } + + void WaitForOperationComplete(volatile SecurityEngineRegisters *SE) { + /* Spin until the operation is done. */ + while (reg::HasValue(SE->SE_INT_STATUS, SE_REG_BITS_ENUM(INT_STATUS_SE_OP_DONE, CLEAR))) { /* ... */ } + + /* Check for operation success. */ + ValidateAesOperationResult(SE); + } + + } + + void ExecuteOperation(volatile SecurityEngineRegisters *SE, SE_OPERATION_OP op, void *dst, size_t dst_size, const void *src, size_t src_size) { + /* Set the linked list entries. */ + LinkedListEntry src_entry; + LinkedListEntry dst_entry; + + SetLinkedListEntry(std::addressof(src_entry), src, src_size); + SetLinkedListEntry(std::addressof(dst_entry), dst, dst_size); + + /* Ensure the linked list entry data is seen correctly. */ + hw::FlushDataCache(std::addressof(src_entry), sizeof(src_entry)); + hw::FlushDataCache(std::addressof(dst_entry), sizeof(dst_entry)); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Configure the linked list addresses. */ + reg::Write(SE->SE_IN_LL_ADDR, static_cast<u32>(GetPhysicalAddress(std::addressof(src_entry)))); + reg::Write(SE->SE_OUT_LL_ADDR, static_cast<u32>(GetPhysicalAddress(std::addressof(dst_entry)))); + + /* Start the operation. */ + StartOperation(SE, op); + + /* Wait for the operation to complete. */ + WaitForOperationComplete(SE); + } + + void ExecuteOperationSingleBlock(volatile SecurityEngineRegisters *SE, void *dst, size_t dst_size, const void *src, size_t src_size) { + /* Validate sizes. */ + AMS_ABORT_UNLESS(dst_size <= AesBlockSize); + AMS_ABORT_UNLESS(src_size <= AesBlockSize); + + /* Set the block count to 1. */ + reg::Write(SE->SE_CRYPTO_LAST_BLOCK, 0); + + /* Create an aligned buffer. */ + util::AlignedBuffer<hw::DataCacheLineSize, AesBlockSize> aligned; + std::memcpy(aligned, src, src_size); + hw::FlushDataCache(aligned, AesBlockSize); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Execute the operation. */ + ExecuteOperation(SE, SE_OPERATION_OP_START, aligned, AesBlockSize, aligned, AesBlockSize); + + /* Ensure that the CPU will see the correct output. */ + hw::DataSynchronizationBarrierInnerShareable(); + hw::FlushDataCache(aligned, AesBlockSize); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Copy the output to the destination. */ + std::memcpy(dst, aligned, dst_size); + } + + void StartInputOperation(volatile SecurityEngineRegisters *SE, const void *src, size_t src_size) { + /* Set the linked list entry. */ + LinkedListEntry src_entry; + SetLinkedListEntry(std::addressof(src_entry), src, src_size); + + /* Ensure the linked list entry data is seen correctly. */ + hw::FlushDataCache(std::addressof(src_entry), sizeof(src_entry)); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Configure the linked list addresses. */ + reg::Write(SE->SE_IN_LL_ADDR, static_cast<u32>(GetPhysicalAddress(std::addressof(src_entry)))); + + /* Start the operation. */ + StartOperation(SE, SE_OPERATION_OP_START); + + /* Ensure the operation is started. */ + EnsureOperationStarted(SE); + } + + void StartOperationRaw(volatile SecurityEngineRegisters *SE, SE_OPERATION_OP op, u32 out_ll_address, u32 in_ll_address) { + /* Configure the linked list addresses. */ + reg::Write(SE->SE_IN_LL_ADDR, in_ll_address); + reg::Write(SE->SE_OUT_LL_ADDR, out_ll_address); + + /* Start the operation. */ + StartOperation(SE, op); + + /* Ensure the operation is started. */ + EnsureOperationStarted(SE); + } + + void ValidateAesOperationResult(volatile SecurityEngineRegisters *SE) { + /* Ensure no error occurred. */ + AMS_ABORT_UNLESS(reg::HasValue(SE->SE_INT_STATUS, SE_REG_BITS_ENUM(INT_STATUS_ERR_STAT, CLEAR))); + + /* Ensure the security engine is idle. */ + AMS_ABORT_UNLESS(reg::HasValue(SE->SE_STATUS, SE_REG_BITS_ENUM(STATUS_STATE, IDLE))); + + /* Ensure there is no error status. */ + AMS_ABORT_UNLESS(reg::Read(SE->SE_ERR_STATUS) == 0); + } + + void ValidateAesOperationResult() { + return ValidateAesOperationResult(GetRegisters()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_execute.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_execute.hpp new file mode 100644 index 00000000..ea8ba906 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_execute.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "se_registers.hpp" + +namespace ams::se { + + volatile SecurityEngineRegisters *GetRegisters(); + volatile SecurityEngineRegisters *GetRegisters2(); + + void ExecuteOperation(volatile SecurityEngineRegisters *SE, SE_OPERATION_OP op, void *dst, size_t dst_size, const void *src, size_t src_size); + void ExecuteOperationSingleBlock(volatile SecurityEngineRegisters *SE, void *dst, size_t dst_size, const void *src, size_t src_size); + + void StartInputOperation(volatile SecurityEngineRegisters *SE, const void *src, size_t src_size); + void StartOperationRaw(volatile SecurityEngineRegisters *SE, SE_OPERATION_OP op, u32 out_ll_address, u32 in_ll_address); + void SetDoneHandler(volatile SecurityEngineRegisters *SE, DoneHandler handler); + + void ValidateAesOperationResult(volatile SecurityEngineRegisters *SE); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_hash.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_hash.cpp new file mode 100644 index 00000000..5c1ea882 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_hash.cpp @@ -0,0 +1,72 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "se_execute.hpp" + +namespace ams::se { + + namespace { + + void SetMessageSize(volatile SecurityEngineRegisters *SE, size_t src_size) { + /* Set the message size. */ + reg::Write(SE->SE_SHA_MSG_LENGTH[0], src_size * BITSIZEOF(u8)); + reg::Write(SE->SE_SHA_MSG_LENGTH[1], 0); + reg::Write(SE->SE_SHA_MSG_LENGTH[2], 0); + reg::Write(SE->SE_SHA_MSG_LENGTH[3], 0); + + /* Set the message remaining size. */ + reg::Write(SE->SE_SHA_MSG_LEFT[0], src_size * BITSIZEOF(u8)); + reg::Write(SE->SE_SHA_MSG_LEFT[1], 0); + reg::Write(SE->SE_SHA_MSG_LEFT[2], 0); + reg::Write(SE->SE_SHA_MSG_LEFT[3], 0); + } + + void GetHashResult(volatile SecurityEngineRegisters *SE, void *dst, size_t dst_size) { + /* Copy out the words. */ + const int num_words = dst_size / sizeof(u32); + for (int i = 0; i < num_words; ++i) { + const u32 word = reg::Read(SE->SE_HASH_RESULT[i]); + util::StoreBigEndian(static_cast<u32 *>(dst) + i, word); + } + } + + } + + void CalculateSha256(Sha256Hash *dst, const void *src, size_t src_size) { + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Configure the engine to perform SHA256 "encryption". */ + reg::Write(SE->SE_CONFIG, SE_REG_BITS_ENUM(CONFIG_ENC_MODE, SHA256), + SE_REG_BITS_ENUM(CONFIG_DEC_MODE, AESMODE_KEY128), + SE_REG_BITS_ENUM(CONFIG_ENC_ALG, SHA), + SE_REG_BITS_ENUM(CONFIG_DEC_ALG, NOP), + SE_REG_BITS_ENUM(CONFIG_DST, HASH_REG)); + + /* Begin a hardware hash operation. */ + reg::Write(SE->SE_SHA_CONFIG, SE_REG_BITS_VALUE(SHA_CONFIG_HW_INIT_HASH, 1)); + + /* Set the message size. */ + SetMessageSize(SE, src_size); + + /* Execute the operation. */ + ExecuteOperation(SE, SE_OPERATION_OP_START, nullptr, 0, src, src_size); + + /* Get the result. */ + GetHashResult(SE, dst, sizeof(*dst)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_management.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_management.cpp new file mode 100644 index 00000000..df36d3d6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_management.cpp @@ -0,0 +1,142 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "se_execute.hpp" + +namespace ams::se { + + namespace { + + constinit uintptr_t g_register_address = secmon::MemoryRegionPhysicalDeviceSecurityEngine.GetAddress(); + constinit uintptr_t g_register2_address = secmon::MemoryRegionPhysicalDeviceSecurityEngine2.GetAddress(); + constinit DoneHandler g_done_handler = nullptr; + + void SetSecure(volatile SecurityEngineRegisters *SE, bool secure) { + /* Set the security software setting. */ + if (secure) { + reg::ReadWrite(SE->SE_SE_SECURITY, SE_REG_BITS_ENUM(SECURITY_SOFT_SETTING, SECURE)); + } else { + reg::ReadWrite(SE->SE_SE_SECURITY, SE_REG_BITS_ENUM(SECURITY_SOFT_SETTING, NONSECURE)); + } + + /* Read the status register to force an update. */ + reg::Read(SE->SE_SE_SECURITY); + } + + } + + volatile SecurityEngineRegisters *GetRegisters() { + return reinterpret_cast<volatile SecurityEngineRegisters *>(g_register_address); + } + + volatile SecurityEngineRegisters *GetRegisters2() { + return reinterpret_cast<volatile SecurityEngineRegisters *>(g_register2_address); + } + + void SetRegisterAddress(uintptr_t address, uintptr_t address2) { + g_register_address = address; + g_register2_address = address2; + } + + void Initialize() { + auto *SE = GetRegisters(); + AMS_ABORT_UNLESS(reg::HasValue(SE->SE_STATUS, SE_REG_BITS_ENUM(STATUS_STATE, IDLE))); + } + + void SetSecure(bool secure) { + /* Set security for SE1. */ + SetSecure(GetRegisters(), secure); + + /* If SE2 is present, set security for SE2. */ + if (fuse::GetSocType() == fuse::SocType_Mariko) { + SetSecure(GetRegisters2(), secure); + } + } + + void SetTzramSecure() { + auto *SE = GetRegisters(); + + /* Set the TZRAM setting to secure. */ + SE->SE_TZRAM_SECURITY = SE_TZRAM_SETTING_SECURE; + } + + void SetPerKeySecure() { + auto *SE = GetRegisters(); + + /* Update PERKEY_SETTING to secure. */ + reg::ReadWrite(SE->SE_SE_SECURITY, SE_REG_BITS_ENUM(SECURITY_PERKEY_SETTING, SECURE)); + } + + + void SetContextSaveSecure() { + /* Context save lock to trustzone secure is only available on mariko. */ + if (fuse::GetSocType() == fuse::SocType_Mariko) { + auto *SE = GetRegisters(); + auto *SE2 = GetRegisters2(); + + reg::ReadWrite(SE->SE_SE_SECURITY, SE_REG_BITS_ENUM(SECURITY_CTX_SAVE_TZ_LOCK, SECURE)); + reg::ReadWrite(SE2->SE_SE_SECURITY, SE_REG_BITS_ENUM(SECURITY_CTX_SAVE_TZ_LOCK, SECURE)); + } + } + + void Lockout() { + auto *SE = GetRegisters(); + + /* Lock access to the AES keyslots. */ + for (int i = 0; i < AesKeySlotCount; ++i) { + SE->SE_CRYPTO_KEYTABLE_ACCESS[i] = 0; + } + + /* Lock access to the RSA keyslots. */ + for (int i = 0; i < RsaKeySlotCount; ++i) { + SE->SE_RSA_KEYTABLE_ACCESS[i] = 0; + } + + /* Set Per Key secure. */ + SetPerKeySecure(); + + /* Configure SE_SECURITY. */ + { + reg::ReadWrite(SE->SE_SE_SECURITY, SE_REG_BITS_ENUM(SECURITY_HARD_SETTING, SECURE), + SE_REG_BITS_ENUM(SECURITY_ENG_DIS, DISABLE), + SE_REG_BITS_ENUM(SECURITY_PERKEY_SETTING, SECURE), + SE_REG_BITS_ENUM(SECURITY_SOFT_SETTING, SECURE)); + } + } + + void HandleInterrupt() { + /* Get the registers. */ + auto *SE = GetRegisters(); + + /* Disable the SE interrupt. */ + reg::Write(SE->SE_INT_ENABLE, 0); + + /* Execute the handler if we have one. */ + if (const auto handler = g_done_handler; handler != nullptr) { + g_done_handler = nullptr; + handler(); + } + } + + void SetDoneHandler(volatile SecurityEngineRegisters *SE, DoneHandler handler) { + /* Set the done handler. */ + g_done_handler = handler; + + /* Configure to trigger an interrupt when done. */ + reg::Write(SE->SE_INT_ENABLE, SE_REG_BITS_ENUM(INT_ENABLE_SE_OP_DONE, ENABLE)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_oaep.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_oaep.cpp new file mode 100644 index 00000000..daea6919 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_oaep.cpp @@ -0,0 +1,122 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "se_execute.hpp" + +namespace ams::se { + + /* NOTE: This implementation is mostly copy/pasted from crypto::impl::RsaOaepImpl. */ + + namespace { + + constexpr inline size_t HashSize = sizeof(Sha256Hash); + + constexpr inline u8 HeadMagic = 0x00; + + void ApplyMGF1(u8 *dst, size_t dst_size, const void *src, size_t src_size) { + /* Check our pre-conditions. */ + AMS_ABORT_UNLESS(src_size <= RsaSize - (1 + HashSize)); + + /* Create a buffer. */ + util::AlignedBuffer<hw::DataCacheLineSize, RsaSize - (1 + HashSize) + sizeof(u32)> buf; + u32 counter = 0; + + while (dst_size > 0) { + /* Setup the current hash buffer. */ + const size_t cur_size = std::min(HashSize, dst_size); + std::memcpy(static_cast<u8 *>(buf), src, src_size); + { + u32 counter_be; + util::StoreBigEndian(std::addressof(counter_be), counter++); + std::memcpy(static_cast<u8 *>(buf) + src_size, std::addressof(counter_be), sizeof(counter_be)); + } + + /* Ensure se sees correct data. */ + hw::FlushDataCache(buf, src_size + sizeof(u32)); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Calculate the hash. */ + Sha256Hash hash; + se::CalculateSha256(std::addressof(hash), buf, src_size + sizeof(u32)); + + /* Mask the current output. */ + const u8 *mask = hash.bytes; + for (size_t i = 0; i < cur_size; ++i) { + *(dst++) ^= *(mask++); + } + + /* Advance. */ + dst_size -= cur_size; + } + } + + } + + size_t DecodeRsaOaepSha256(void *dst, size_t dst_size, void *src, size_t src_size, const void *label_digest, size_t label_digest_size) { + /* Check our preconditions. */ + AMS_ABORT_UNLESS(src_size == RsaSize); + AMS_ABORT_UNLESS(label_digest_size == HashSize); + + /* Get a byte-readable copy of the input. */ + u8 *buf = static_cast<u8 *>(src); + + /* Validate sanity byte. */ + bool is_valid = buf[0] == HeadMagic; + + /* Decrypt seed and masked db. */ + size_t db_len = src_size - HashSize - 1; + u8 *seed = buf + 1; + u8 *db = seed + HashSize; + ApplyMGF1(seed, HashSize, db, db_len); + ApplyMGF1(db, db_len, seed, HashSize); + + /* Check the label digest. */ + is_valid &= crypto::IsSameBytes(label_digest, db, HashSize); + + /* Skip past the label digest. */ + db += HashSize; + db_len -= HashSize; + + /* Verify that DB is of the form 0000...0001 < message > */ + s32 msg_ofs = 0; + { + int looking_for_one = 1; + int invalid_db_padding = 0; + int is_zero; + int is_one; + for (size_t i = 0; i < db_len; /* ... */) { + is_zero = (db[i] == 0); + is_one = (db[i] == 1); + msg_ofs += (looking_for_one & is_one) * (static_cast<s32>(++i)); + looking_for_one &= ~is_one; + invalid_db_padding |= (looking_for_one & ~is_zero); + } + + is_valid &= (invalid_db_padding == 0); + } + + /* If we're invalid, return zero size. */ + const size_t valid_msg_size = db_len - msg_ofs; + const size_t msg_size = std::min(dst_size, static_cast<size_t>(is_valid) * valid_msg_size); + + /* Copy to output. */ + std::memcpy(dst, db + msg_ofs, msg_size); + + /* Return copied size. */ + return msg_size; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_registers.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_registers.hpp new file mode 100644 index 00000000..1cd5e509 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_registers.hpp @@ -0,0 +1,262 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::se { + + struct SecurityEngineRegisters { + u32 SE_SE_SECURITY; + u32 SE_TZRAM_SECURITY; + u32 SE_OPERATION; + u32 SE_INT_ENABLE; + u32 SE_INT_STATUS; + u32 SE_CONFIG; + u32 SE_IN_LL_ADDR; + u32 SE_IN_CUR_BYTE_ADDR; + u32 SE_IN_CUR_LL_ID; + u32 SE_OUT_LL_ADDR; + u32 SE_OUT_CUR_BYTE_ADDR; + u32 SE_OUT_CUR_LL_ID; + u32 SE_HASH_RESULT[0x10]; + u32 SE_CTX_SAVE_CONFIG; + u32 SE_CTX_SAVE_AUTO; + u32 _0x78[0x62]; + u32 SE_SHA_CONFIG; + u32 SE_SHA_MSG_LENGTH[0x4]; + u32 SE_SHA_MSG_LEFT[0x4]; + u32 _0x224[0x17]; + u32 SE_CRYPTO_SECURITY_PERKEY; + u32 SE_CRYPTO_KEYTABLE_ACCESS[0x10]; + u32 _0x2C4[0x10]; + u32 SE_CRYPTO_CONFIG; + u32 SE_CRYPTO_LINEAR_CTR[0x4]; + u32 SE_CRYPTO_LAST_BLOCK; + u32 SE_CRYPTO_KEYTABLE_ADDR; + u32 SE_CRYPTO_KEYTABLE_DATA; + u32 _0x324[0x3]; + u32 SE_CRYPTO_KEYTABLE_DST; + u32 _0x334[0x3]; + u32 SE_RNG_CONFIG; + u32 SE_RNG_SRC_CONFIG; + u32 SE_RNG_RESEED_INTERVAL; + u32 _0x34C[0x2D]; + u32 SE_RSA_CONFIG; + u32 SE_RSA_KEY_SIZE; + u32 SE_RSA_EXP_SIZE; + u32 SE_RSA_SECURITY_PERKEY; + u32 SE_RSA_KEYTABLE_ACCESS[0x2]; + u32 _0x418[0x2]; + u32 SE_RSA_KEYTABLE_ADDR; + u32 SE_RSA_KEYTABLE_DATA; + u32 SE_RSA_OUTPUT[0x40]; + u32 _0x528[0x6]; + u32 SE_TZRAM_OPERATION; + u32 _0x544[0xAF]; + u32 SE_STATUS; + u32 SE_ERR_STATUS; + u32 SE_MISC; + u32 SE_SPARE; + u32 SE_ENTROPY_DEBUG_COUNTER; + u32 _0x814; + u32 _0x818; + u32 _0x81C; + u32 _0x820[0x5F8]; + }; + static_assert(util::is_pod<SecurityEngineRegisters>::value); + static_assert(sizeof(SecurityEngineRegisters) == secmon::MemoryRegionPhysicalDeviceSecurityEngine.GetSize()); + + static_assert(AesKeySlotCount == util::size(SecurityEngineRegisters{}.SE_CRYPTO_KEYTABLE_ACCESS)); + static_assert(RsaKeySlotCount == util::size(SecurityEngineRegisters{}.SE_RSA_KEYTABLE_ACCESS)); + + #define SE_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (SE, NAME) + #define SE_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (SE, NAME, VALUE) + #define SE_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (SE, NAME, ENUM) + #define SE_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(SE, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + + #define DEFINE_SE_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (SE, NAME, __OFFSET__, __WIDTH__) + #define DEFINE_SE_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (SE, NAME, __OFFSET__, ZERO, ONE) + #define DEFINE_SE_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (SE, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) + #define DEFINE_SE_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(SE, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) + #define DEFINE_SE_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (SE, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + + #define DEFINE_SE_REG_BIT_ENUM_WITH_SW_CLEAR(NAME, __OFFSET__) \ + REG_DEFINE_NAMED_REG(SE, NAME, __OFFSET__, 1); \ + \ + enum SE_##NAME { \ + SE_##NAME##_##CLEAR = 0, \ + SE_##NAME##_##ACTIVE = 1, \ + SE_##NAME##_##SW_CLEAR = 1, \ + }; + + /* SE_STATUS. */ + DEFINE_SE_REG_TWO_BIT_ENUM(STATUS_STATE, 0, IDLE, BUSY, WAIT_OUT, WAIT_IN); + DEFINE_SE_REG_BIT_ENUM(STATUS_MEM_INTERFACE, 2, IDLE, BUSY); + + /* SE_SECURITY */ + DEFINE_SE_REG_BIT_ENUM(SECURITY_HARD_SETTING, 0, SECURE, NONSECURE); + DEFINE_SE_REG_BIT_ENUM(SECURITY_ENG_DIS, 1, DISABLE, ENABLE); + DEFINE_SE_REG_BIT_ENUM(SECURITY_PERKEY_SETTING, 2, SECURE, NONSECURE); + DEFINE_SE_REG_BIT_ENUM(SECURITY_CTX_SAVE_TZ_LOCK, 4, SECURE, NONSECURE); + DEFINE_SE_REG_BIT_ENUM(SECURITY_CTX_TZ_LOCK_SOFT, 5, SECURE, NONSECURE); + DEFINE_SE_REG_BIT_ENUM(SECURITY_SOFT_SETTING, 16, SECURE, NONSECURE); + + /* SE_TZRAM_SECURITY */ + DEFINE_SE_REG(TZRAM_SETTING, 0, BITSIZEOF(u32)); + constexpr inline u32 SE_TZRAM_SETTING_SECURE = 0; + + /* SE_TZRAM_OPERATION */ + DEFINE_SE_REG_BIT_ENUM(TZRAM_OPERATION_REQ, 0, IDLE, INITIATE); + DEFINE_SE_REG_BIT_ENUM(TZRAM_OPERATION_MODE, 1, SAVE, RESTORE); + DEFINE_SE_REG_BIT_ENUM(TZRAM_OPERATION_BUSY, 2, NO, YES); + DEFINE_SE_REG(TZRAM_OPERATION_CURR_ADDR, 16, 16); + + /* SE_OPERATION */ + DEFINE_SE_REG_THREE_BIT_ENUM(OPERATION_OP, 0, ABORT, START, RESTART_OUT, CTX_SAVE, RESTART_IN, RESERVED_5, RESERVED_6, RESERVED_7); + + /* SE_INT_ENABLE */ + DEFINE_SE_REG_BIT_ENUM(INT_ENABLE_IN_LL_BUF_RD, 0, DISABLE, ENABLE); + DEFINE_SE_REG_BIT_ENUM(INT_ENABLE_IN_DONE, 1, DISABLE, ENABLE); + DEFINE_SE_REG_BIT_ENUM(INT_ENABLE_OUT_LL_BUF_WR, 2, DISABLE, ENABLE); + DEFINE_SE_REG_BIT_ENUM(INT_ENABLE_OUT_DONE, 3, DISABLE, ENABLE); + DEFINE_SE_REG_BIT_ENUM(INT_ENABLE_SE_OP_DONE, 4, DISABLE, ENABLE); + DEFINE_SE_REG_BIT_ENUM(INT_ENABLE_RESEED_CNTR_EXHAUSTED, 5, DISABLE, ENABLE); + DEFINE_SE_REG_BIT_ENUM(INT_ENABLE_ERR_STAT, 16, DISABLE, ENABLE); + + /* SE_INT_STATUS */ + DEFINE_SE_REG_BIT_ENUM_WITH_SW_CLEAR(INT_STATUS_IN_LL_BUF_RD, 0); + DEFINE_SE_REG_BIT_ENUM_WITH_SW_CLEAR(INT_STATUS_IN_DONE, 1); + DEFINE_SE_REG_BIT_ENUM_WITH_SW_CLEAR(INT_STATUS_OUT_LL_BUF_WR, 2); + DEFINE_SE_REG_BIT_ENUM_WITH_SW_CLEAR(INT_STATUS_OUT_DONE, 3); + DEFINE_SE_REG_BIT_ENUM_WITH_SW_CLEAR(INT_STATUS_SE_OP_DONE, 4); + DEFINE_SE_REG_BIT_ENUM_WITH_SW_CLEAR(INT_STATUS_RESEED_CNTR_EXHAUSTED, 5); + DEFINE_SE_REG_BIT_ENUM_WITH_SW_CLEAR(INT_STATUS_ERR_STAT, 16); + + /* SE_CONFIG */ + DEFINE_SE_REG(CONFIG_DEC_MODE, 16, 8); + DEFINE_SE_REG(CONFIG_ENC_MODE, 24, 8); + + DEFINE_SE_REG_THREE_BIT_ENUM(CONFIG_DST, 2, MEMORY, HASH_REG, KEYTABLE, SRK, RSA_REG, RESERVED5, RESERVED6, RESERVED7); + DEFINE_SE_REG_FOUR_BIT_ENUM(CONFIG_DEC_ALG, 8, NOP, AES_DEC, RESERVED2, RESERVED3, RESERVED4, RESERVED5, RESERVED6, RESERVED7, RESERVED8, RESERVED9, RESERVED10, RESERVED11, RESERVED12, RESERVED13, RESERVED14, RESERVED15); + DEFINE_SE_REG_FOUR_BIT_ENUM(CONFIG_ENC_ALG, 12, NOP, AES_ENC, RNG, SHA, RSA, RESERVED5, RESERVED6, RESERVED7, RESERVED8, RESERVED9, RESERVED10, RESERVED11, RESERVED12, RESERVED13, RESERVED14, RESERVED15); + + enum SE_CONFIG_DEC_MODE { + SE_CONFIG_DEC_MODE_AESMODE_KEY128 = 0, + SE_CONFIG_DEC_MODE_AESMODE_KEY192 = 1, + SE_CONFIG_DEC_MODE_AESMODE_KEY256 = 2, + }; + + enum SE_CONFIG_ENC_MODE { + SE_CONFIG_ENC_MODE_AESMODE_KEY128 = 0, + SE_CONFIG_ENC_MODE_AESMODE_KEY192 = 1, + SE_CONFIG_ENC_MODE_AESMODE_KEY256 = 2, + + SE_CONFIG_ENC_MODE_SHA1 = 1, + SE_CONFIG_ENC_MODE_SHA224 = 4, + SE_CONFIG_ENC_MODE_SHA256 = 5, + SE_CONFIG_ENC_MODE_SHA384 = 6, + SE_CONFIG_ENC_MODE_SHA512 = 7, + }; + + /* SE_CTX_SAVE_CONFIG */ + DEFINE_SE_REG_TWO_BIT_ENUM(CTX_SAVE_CONFIG_AES_WORD_QUAD, 0, KEYS_0_3, KEYS_4_7, ORIGINAL_IVS, UPDATED_IVS); + DEFINE_SE_REG(CTX_SAVE_CONFIG_PKA1_WORD_QUAD_L, 0, 4); + DEFINE_SE_REG(CTX_SAVE_CONFIG_AES_KEY_INDEX, 8, 4); + DEFINE_SE_REG(CTX_SAVE_CONFIG_RSA_WORD_QUAD, 12, 4); + DEFINE_SE_REG(CTX_SAVE_CONFIG_PKA1_WORD_QUAD_H, 12, 4); + DEFINE_SE_REG_TWO_BIT_ENUM(CTX_SAVE_CONFIG_RSA_KEY_INDEX, 16, SLOT0_EXPONENT, SLOT0_MODULUS, SLOT1_EXPONENT, SLOT1_MODULUS); + DEFINE_SE_REG_BIT_ENUM(CTX_SAVE_CONFIG_STICKY_WORD_QUAD, 24, WORDS_0_3, WORDS_4_7); + DEFINE_SE_REG_THREE_BIT_ENUM(CTX_SAVE_CONFIG_SRC, 29, STICKY_BITS, RSA_KEYTABLE, AES_KEYTABLE, PKA1_STICKY_BITS, MEM, RESERVED5, SRK, PKA1_KEYTABLE); + + /* SE_CTX_SAVE_AUTO */ + DEFINE_SE_REG_BIT_ENUM(CTX_SAVE_AUTO_ENABLE, 0, NO, YES); + DEFINE_SE_REG_BIT_ENUM(CTX_SAVE_AUTO_LOCK, 8, NO, YES); + DEFINE_SE_REG(CTX_SAVE_AUTO_CURR_CNT, 16, 10); + + /* SE_SHA_CONFIG */ + DEFINE_SE_REG(SHA_CONFIG_HW_INIT_HASH, 0, 1); + + + /* SE_CRYPTO_KEYTABLE_ADDR */ + DEFINE_SE_REG(CRYPTO_KEYTABLE_ADDR_KEYIV_WORD, 0, 4); + DEFINE_SE_REG(CRYPTO_KEYTABLE_ADDR_KEYIV_IV_WORD, 0, 2); + DEFINE_SE_REG(CRYPTO_KEYTABLE_ADDR_KEYIV_KEY_WORD, 0, 3); + + enum SE_CRYPTO_KEYTABLE_ADDR_KEYIV_WORD { + SE_CRYPTO_KEYTABLE_ADDR_KEYIV_WORD_KEY_0 = 0u, + SE_CRYPTO_KEYTABLE_ADDR_KEYIV_WORD_KEY_1 = 1u, + SE_CRYPTO_KEYTABLE_ADDR_KEYIV_WORD_KEY_2 = 2u, + SE_CRYPTO_KEYTABLE_ADDR_KEYIV_WORD_KEY_3 = 3u, + SE_CRYPTO_KEYTABLE_ADDR_KEYIV_WORD_KEY_4 = 4u, + SE_CRYPTO_KEYTABLE_ADDR_KEYIV_WORD_KEY_5 = 5u, + SE_CRYPTO_KEYTABLE_ADDR_KEYIV_WORD_KEY_6 = 6u, + SE_CRYPTO_KEYTABLE_ADDR_KEYIV_WORD_KEY_7 = 7u, + SE_CRYPTO_KEYTABLE_ADDR_KEYIV_WORD_OIV_0 = 8u, + SE_CRYPTO_KEYTABLE_ADDR_KEYIV_WORD_OIV_1 = 9u, + SE_CRYPTO_KEYTABLE_ADDR_KEYIV_WORD_OIV_2 = 10u, + SE_CRYPTO_KEYTABLE_ADDR_KEYIV_WORD_OIV_3 = 11u, + SE_CRYPTO_KEYTABLE_ADDR_KEYIV_WORD_UIV_0 = 12u, + SE_CRYPTO_KEYTABLE_ADDR_KEYIV_WORD_UIV_1 = 13u, + SE_CRYPTO_KEYTABLE_ADDR_KEYIV_WORD_UIV_2 = 14u, + SE_CRYPTO_KEYTABLE_ADDR_KEYIV_WORD_UIV_3 = 15u, + }; + + DEFINE_SE_REG_BIT_ENUM(CRYPTO_KEYTABLE_ADDR_KEYIV_IV_SEL, 2, ORIGINAL_IV, UPDATED_IV); + DEFINE_SE_REG_BIT_ENUM(CRYPTO_KEYTABLE_ADDR_KEYIV_KEYIV_SEL, 3, KEY, IV); + + DEFINE_SE_REG(CRYPTO_KEYTABLE_ADDR_KEYIV_KEY_SLOT, 4, 4); + + /* SE_RSA_CONFIG */ + DEFINE_SE_REG(RSA_CONFIG_KEY_SLOT, 24, 1); + + /* SE_RSA_KEYTABLE_ADDR */ + DEFINE_SE_REG(RSA_KEYTABLE_ADDR_WORD_ADDR, 0, 6); + DEFINE_SE_REG_BIT_ENUM(RSA_KEYTABLE_ADDR_EXPMOD_SEL, 6, EXPONENT, MODULUS); + DEFINE_SE_REG(RSA_KEYTABLE_ADDR_KEY_SLOT, 7, 1); + DEFINE_SE_REG_BIT_ENUM(RSA_KEYTABLE_ADDR_INPUT_MODE, 8, REGISTER, MEMORY); + + /* SE_RSA_KEYTABLE_ACCESS */ + DEFINE_SE_REG_BIT_ENUM(RSA_KEYTABLE_ACCESS_KEYREAD, 0, DISABLE, ENABLE); + DEFINE_SE_REG_BIT_ENUM(RSA_KEYTABLE_ACCESS_KEYUPDATE, 1, DISABLE, ENABLE); + DEFINE_SE_REG_BIT_ENUM(RSA_KEYTABLE_ACCESS_KEYUSE, 2, DISABLE, ENABLE); + + /* SE_CRYPTO_CONFIG */ + DEFINE_SE_REG_BIT_ENUM(CRYPTO_CONFIG_HASH_ENB, 0, DISABLE, ENABLE); + DEFINE_SE_REG_TWO_BIT_ENUM(CRYPTO_CONFIG_XOR_POS, 1, BYPASS, RESERVED, TOP, BOTTOM); + DEFINE_SE_REG_TWO_BIT_ENUM(CRYPTO_CONFIG_INPUT_SEL, 3, MEMORY, RANDOM, INIT_AESOUT, LINEAR_CTR); + DEFINE_SE_REG_TWO_BIT_ENUM(CRYPTO_CONFIG_VCTRAM_SEL, 5, MEMORY, RESERVED, INIT_AESOUT, INIT_PREV_MEMORY); + DEFINE_SE_REG_BIT_ENUM(CRYPTO_CONFIG_IV_SELECT, 7, ORIGINAL, UPDATED); + DEFINE_SE_REG_BIT_ENUM(CRYPTO_CONFIG_CORE_SEL, 8, DECRYPT, ENCRYPT); + DEFINE_SE_REG_BIT_ENUM(CRYPTO_CONFIG_KEYSCH_BYPASS, 10, DISABLE, ENABLE); + DEFINE_SE_REG(CRYPTO_CONFIG_CTR_CNTN, 11, 8); + DEFINE_SE_REG(CRYPTO_CONFIG_KEY_INDEX, 24, 4); + DEFINE_SE_REG_BIT_ENUM(CRYPTO_CONFIG_MEMIF, 31, AHB, MCCIF); + + /* SE_CRYPTO_KEYTABLE_DST */ + DEFINE_SE_REG_TWO_BIT_ENUM(CRYPTO_KEYTABLE_DST_WORD_QUAD, 0, KEYS_0_3, KEYS_4_7, ORIGINAL_IV, UPDATED_IV); + DEFINE_SE_REG(CRYPTO_KEYTABLE_DST_KEY_INDEX, 8, 4); + + /* SE_RNG_CONFIG */ + DEFINE_SE_REG_TWO_BIT_ENUM(RNG_CONFIG_MODE, 0, NORMAL, FORCE_INSTANTIATION, FORCE_RESEED, RESERVED3); + DEFINE_SE_REG_TWO_BIT_ENUM(RNG_CONFIG_SRC, 2, NONE, ENTROPY, LFSR, RESERVED3); + + /* SE_RNG_SRC_CONFIG */ + DEFINE_SE_REG_BIT_ENUM(RNG_SRC_CONFIG_RO_ENTROPY_SOURCE_LOCK, 0, DISABLE, ENABLE); + DEFINE_SE_REG_BIT_ENUM(RNG_SRC_CONFIG_RO_ENTROPY_SOURCE, 1, DISABLE, ENABLE); + DEFINE_SE_REG_BIT_ENUM(RNG_SRC_CONFIG_HW_DISABLE_CYA, 2, DISABLE, ENABLE); + DEFINE_SE_REG(RNG_SRC_CONFIG_RO_ENTROPY_SUBSAMPLE, 4, 3); + DEFINE_SE_REG(RNG_SRC_CONFIG_RO_ENTROPY_DATA_FLUSH, 8, 1); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_rng.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_rng.cpp new file mode 100644 index 00000000..ad00379e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_rng.cpp @@ -0,0 +1,162 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "se_execute.hpp" + +namespace ams::se { + + namespace { + + constexpr inline int RngReseedInterval = 70001; + + void ConfigRng(volatile SecurityEngineRegisters *SE, SE_CONFIG_DST dst, SE_RNG_CONFIG_MODE mode) { + /* Configure the engine to do RNG encryption. */ + reg::Write(SE->SE_CONFIG, SE_REG_BITS_ENUM (CONFIG_ENC_MODE, AESMODE_KEY128), + SE_REG_BITS_ENUM (CONFIG_DEC_MODE, AESMODE_KEY128), + SE_REG_BITS_ENUM (CONFIG_ENC_ALG, RNG), + SE_REG_BITS_ENUM (CONFIG_DEC_ALG, NOP), + SE_REG_BITS_VALUE(CONFIG_DST, dst)); + + reg::Write(SE->SE_CRYPTO_CONFIG, SE_REG_BITS_ENUM (CRYPTO_CONFIG_MEMIF, AHB), + SE_REG_BITS_VALUE(CRYPTO_CONFIG_CTR_CNTN, 0), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_KEYSCH_BYPASS, DISABLE), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_CORE_SEL, ENCRYPT), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_IV_SELECT, ORIGINAL), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_VCTRAM_SEL, MEMORY), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_INPUT_SEL, RANDOM), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_XOR_POS, BYPASS), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_HASH_ENB, DISABLE)); + + /* Configure the RNG to use Entropy as source. */ + reg::Write(SE->SE_RNG_CONFIG, SE_REG_BITS_ENUM(RNG_CONFIG_SRC, ENTROPY), SE_REG_BITS_VALUE(RNG_CONFIG_MODE, mode)); + } + + void InitializeRandom(volatile SecurityEngineRegisters *SE) { + /* Lock the entropy source. */ + reg::Write(SE->SE_RNG_SRC_CONFIG, SE_REG_BITS_ENUM(RNG_SRC_CONFIG_RO_ENTROPY_SOURCE, ENABLE), + SE_REG_BITS_ENUM(RNG_SRC_CONFIG_RO_ENTROPY_SOURCE_LOCK, ENABLE)); + + /* Set the reseed interval to force a reseed every 70000 blocks. */ + SE->SE_RNG_RESEED_INTERVAL = RngReseedInterval; + + /* Initialize the DRBG. */ + { + u8 dummy_buf[AesBlockSize]; + + /* Configure the engine to force drbg instantiation by writing random to memory. */ + ConfigRng(SE, SE_CONFIG_DST_MEMORY, SE_RNG_CONFIG_MODE_FORCE_INSTANTIATION); + + /* Configure to do a single RNG block operation to trigger DRBG init. */ + SE->SE_CRYPTO_LAST_BLOCK = 0; + + /* Execute the operation. */ + ExecuteOperation(SE, SE_OPERATION_OP_START, dummy_buf, sizeof(dummy_buf), nullptr, 0); + } + } + + void GenerateSrk(volatile SecurityEngineRegisters *SE) { + /* Configure the RNG to output to SRK and force a reseed. */ + ConfigRng(SE, SE_CONFIG_DST_SRK, SE_RNG_CONFIG_MODE_FORCE_RESEED); + + /* Configure a single block operation. */ + SE->SE_CRYPTO_LAST_BLOCK = 0; + + /* Execute the operation. */ + ExecuteOperation(SE, SE_OPERATION_OP_START, nullptr, 0, nullptr, 0); + } + + } + + void InitializeRandom() { + /* Initialize random for SE1. */ + InitializeRandom(GetRegisters()); + + /* If we have SE2, initialize random for SE2. */ + /* NOTE: Nintendo's implementation of this is incorrect. */ + if (fuse::GetSocType() == fuse::SocType_Mariko) { + InitializeRandom(GetRegisters2()); + } + } + + void GenerateRandomBytes(void *dst, size_t size) { + /* If we're not generating any bytes, there's nothing to do. */ + if (size == 0) { + return; + } + + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Determine how many blocks to generate. */ + const size_t num_blocks = size / AesBlockSize; + const size_t aligned_size = num_blocks * AesBlockSize; + const size_t fractional = size - aligned_size; + + /* Configure the RNG to generate random to memory. */ + ConfigRng(SE, SE_CONFIG_DST_MEMORY, SE_RNG_CONFIG_MODE_NORMAL); + + /* Generate as many aligned blocks as we can. */ + if (aligned_size > 0) { + /* Configure the engine to generate the right number of blocks. */ + SE->SE_CRYPTO_LAST_BLOCK = num_blocks - 1; + + /* Execute the operation. */ + ExecuteOperation(SE, SE_OPERATION_OP_START, dst, aligned_size, nullptr, 0); + } + + /* Generate a single block to output. */ + if (fractional > 0) { + ExecuteOperationSingleBlock(SE, static_cast<u8 *>(dst) + aligned_size, fractional, nullptr, 0); + } + } + + void SetRandomKey(int slot) { + /* NOTE: Nintendo does not validate the destination keyslot here. */ + + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Configure the RNG to output to the keytable. */ + ConfigRng(SE, SE_CONFIG_DST_KEYTABLE, SE_RNG_CONFIG_MODE_NORMAL); + + /* Configure the keytable destination to be the low part of the key. */ + reg::Write(SE->SE_CRYPTO_KEYTABLE_DST, SE_REG_BITS_VALUE(CRYPTO_KEYTABLE_DST_KEY_INDEX, slot), SE_REG_BITS_ENUM(CRYPTO_KEYTABLE_DST_WORD_QUAD, KEYS_0_3)); + + /* Configure a single block operation. */ + SE->SE_CRYPTO_LAST_BLOCK = 0; + + /* Execute the operation to generate a random low-part of the key. */ + ExecuteOperation(SE, SE_OPERATION_OP_START, nullptr, 0, nullptr, 0); + + /* Configure the keytable destination to be the high part of the key. */ + reg::Write(SE->SE_CRYPTO_KEYTABLE_DST, SE_REG_BITS_VALUE(CRYPTO_KEYTABLE_DST_KEY_INDEX, slot), SE_REG_BITS_ENUM(CRYPTO_KEYTABLE_DST_WORD_QUAD, KEYS_4_7)); + + /* Execute the operation to generate a random high-part of the key. */ + ExecuteOperation(SE, SE_OPERATION_OP_START, nullptr, 0, nullptr, 0); + } + + void GenerateSrk() { + /* Generate SRK for SE1. */ + GenerateSrk(GetRegisters()); + + /* If we have SE2, generate SRK for SE2. */ + /* NOTE: Nintendo's implementation of this is incorrect. */ + if (fuse::GetSocType() == fuse::SocType_Mariko) { + GenerateSrk(GetRegisters2()); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_rsa.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_rsa.cpp new file mode 100644 index 00000000..b3700078 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_rsa.cpp @@ -0,0 +1,230 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "se_execute.hpp" + +namespace ams::se { + + namespace { + + struct RsaKeyInfo { + int modulus_size_val; + int exponent_size_val; + }; + + constinit RsaKeyInfo g_rsa_key_infos[RsaKeySlotCount] = {}; + + void ClearRsaKeySlot(volatile SecurityEngineRegisters *SE, int slot, SE_RSA_KEYTABLE_ADDR_EXPMOD_SEL expmod) { + constexpr int NumWords = se::RsaSize / sizeof(u32); + for (int i = 0; i < NumWords; ++i) { + /* Select the keyslot word. */ + reg::Write(SE->SE_RSA_KEYTABLE_ADDR, SE_REG_BITS_ENUM (RSA_KEYTABLE_ADDR_INPUT_MODE, REGISTER), + SE_REG_BITS_VALUE(RSA_KEYTABLE_ADDR_KEY_SLOT, slot), + SE_REG_BITS_VALUE(RSA_KEYTABLE_ADDR_EXPMOD_SEL, expmod), + SE_REG_BITS_VALUE(RSA_KEYTABLE_ADDR_WORD_ADDR, i)); + + /* Clear the keyslot word. */ + SE->SE_RSA_KEYTABLE_DATA = 0; + } + } + + void SetRsaKey(volatile SecurityEngineRegisters *SE, int slot, SE_RSA_KEYTABLE_ADDR_EXPMOD_SEL expmod, const void *key, size_t key_size) { + const int num_words = key_size / sizeof(u32); + for (int i = 0; i < num_words; ++i) { + /* Select the keyslot word. */ + reg::Write(SE->SE_RSA_KEYTABLE_ADDR, SE_REG_BITS_ENUM (RSA_KEYTABLE_ADDR_INPUT_MODE, REGISTER), + SE_REG_BITS_VALUE(RSA_KEYTABLE_ADDR_KEY_SLOT, slot), + SE_REG_BITS_VALUE(RSA_KEYTABLE_ADDR_EXPMOD_SEL, expmod), + SE_REG_BITS_VALUE(RSA_KEYTABLE_ADDR_WORD_ADDR, i)); + + /* Get the word. */ + const u32 word = util::LoadBigEndian(static_cast<const u32 *>(key) + (num_words - 1 - i)); + + /* Write the keyslot word. */ + SE->SE_RSA_KEYTABLE_DATA = word; + } + } + + void GetRsaResult(volatile SecurityEngineRegisters *SE, void *dst, size_t size) { + /* Copy out the words. */ + const int num_words = size / sizeof(u32); + for (int i = 0; i < num_words; ++i) { + const u32 word = reg::Read(SE->SE_RSA_OUTPUT[i]); + util::StoreBigEndian(static_cast<u32 *>(dst) + num_words - 1 - i, word); + } + } + + void WaitForInputReadComplete(volatile SecurityEngineRegisters *SE) { + while (reg::HasValue(SE->SE_INT_STATUS, SE_REG_BITS_ENUM(INT_STATUS_IN_DONE, CLEAR))) { /* ... */ } + } + + } + + void ClearRsaKeySlot(int slot) { + /* Validate the key slot. */ + AMS_ABORT_UNLESS(0 <= slot && slot < RsaKeySlotCount); + + /* Clear the info. */ + g_rsa_key_infos[slot] = {}; + + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Clear the modulus. */ + ClearRsaKeySlot(SE, slot, SE_RSA_KEYTABLE_ADDR_EXPMOD_SEL_MODULUS); + + /* Clear the exponent. */ + ClearRsaKeySlot(SE, slot, SE_RSA_KEYTABLE_ADDR_EXPMOD_SEL_EXPONENT); + } + + void LockRsaKeySlot(int slot, u32 flags) { + /* Validate the key slot. */ + AMS_ABORT_UNLESS(0 <= slot && slot < RsaKeySlotCount); + + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Set non per-key flags. */ + if ((flags & ~KeySlotLockFlags_PerKey) != 0) { + /* Pack the flags into the expected format. */ + u32 value = 0; + value |= ((flags & KeySlotLockFlags_KeyRead) == 0) ? (1u << 0) : 0; + value |= ((flags & KeySlotLockFlags_KeyRead) == 0) ? (1u << 1) : 0; + value |= ((flags & KeySlotLockFlags_KeyRead) == 0) ? (1u << 2) : 0; + + reg::Write(SE->SE_RSA_KEYTABLE_ACCESS[slot], SE_REG_BITS_ENUM_SEL(RSA_KEYTABLE_ACCESS_KEYREAD, (flags & KeySlotLockFlags_KeyRead) != 0, DISABLE, ENABLE), + SE_REG_BITS_ENUM_SEL(RSA_KEYTABLE_ACCESS_KEYUPDATE, (flags & KeySlotLockFlags_KeyWrite) != 0, DISABLE, ENABLE), + SE_REG_BITS_ENUM_SEL(RSA_KEYTABLE_ACCESS_KEYUSE, (flags & KeySlotLockFlags_KeyUse) != 0, DISABLE, ENABLE)); + } + + /* Set per-key flag. */ + if ((flags & KeySlotLockFlags_PerKey) != 0) { + reg::ReadWrite(SE->SE_RSA_SECURITY_PERKEY, REG_BITS_VALUE(slot, 1, 0)); + } + } + + void SetRsaKey(int slot, const void *mod, size_t mod_size, const void *exp, size_t exp_size) { + /* Validate the key slot and sizes. */ + AMS_ABORT_UNLESS(0 <= slot && slot < RsaKeySlotCount); + AMS_ABORT_UNLESS(mod_size <= RsaSize); + AMS_ABORT_UNLESS(exp_size <= RsaSize); + + /* Set the sizes in the info. */ + auto &info = g_rsa_key_infos[slot]; + info.modulus_size_val = (mod_size / 64) - 1; + info.exponent_size_val = (exp_size / 4); + + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Set the modulus and exponent. */ + SetRsaKey(SE, slot, SE_RSA_KEYTABLE_ADDR_EXPMOD_SEL_MODULUS, mod, mod_size); + SetRsaKey(SE, slot, SE_RSA_KEYTABLE_ADDR_EXPMOD_SEL_EXPONENT, exp, exp_size); + } + + void ModularExponentiate(void *dst, size_t dst_size, int slot, const void *src, size_t src_size) { + /* Validate the slot and sizes. */ + AMS_ABORT_UNLESS(0 <= slot && slot < RsaKeySlotCount); + AMS_ABORT_UNLESS(src_size <= RsaSize); + AMS_ABORT_UNLESS(dst_size <= RsaSize); + + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Create a work buffer. */ + u8 work[RsaSize]; + util::ClearMemory(work, sizeof(work)); + + /* Copy the input into the work buffer (reversing endianness). */ + const u8 *src_u8 = static_cast<const u8 *>(src); + for (size_t i = 0; i < src_size; ++i) { + work[src_size - 1 - i] = src_u8[i]; + } + + /* Flush the work buffer to ensure the SE sees correct results. */ + hw::FlushDataCache(work, sizeof(work)); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Configure the engine to perform RSA encryption. */ + reg::Write(SE->SE_CONFIG, SE_REG_BITS_ENUM(CONFIG_ENC_MODE, AESMODE_KEY128), + SE_REG_BITS_ENUM(CONFIG_DEC_MODE, AESMODE_KEY128), + SE_REG_BITS_ENUM(CONFIG_ENC_ALG, RSA), + SE_REG_BITS_ENUM(CONFIG_DEC_ALG, NOP), + SE_REG_BITS_ENUM(CONFIG_DST, RSA_REG)); + + /* Configure the engine to use the keyslot and correct modulus/exp sizes. */ + const auto &info = g_rsa_key_infos[slot]; + reg::Write(SE->SE_RSA_CONFIG, SE_REG_BITS_VALUE(RSA_CONFIG_KEY_SLOT, slot)); + reg::Write(SE->SE_RSA_KEY_SIZE, info.modulus_size_val); + reg::Write(SE->SE_RSA_EXP_SIZE, info.exponent_size_val); + + /* Execute the operation. */ + ExecuteOperation(SE, SE_OPERATION_OP_START, nullptr, 0, work, src_size); + + /* Copy out the result. */ + GetRsaResult(SE, dst, dst_size); + } + + void ModularExponentiateAsync(int slot, const void *src, size_t src_size, DoneHandler handler) { + /* Validate the slot and size. */ + AMS_ABORT_UNLESS(0 <= slot && slot < RsaKeySlotCount); + AMS_ABORT_UNLESS(src_size <= RsaSize); + + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Create a work buffer. */ + u8 work[RsaSize]; + util::ClearMemory(work, sizeof(work)); + + /* Copy the input into the work buffer (reversing endianness). */ + const u8 *src_u8 = static_cast<const u8 *>(src); + for (size_t i = 0; i < src_size; ++i) { + work[src_size - 1 - i] = src_u8[i]; + } + + /* Flush the work buffer to ensure the SE sees correct results. */ + hw::FlushDataCache(work, sizeof(work)); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Configure the engine to perform RSA encryption. */ + reg::Write(SE->SE_CONFIG, SE_REG_BITS_ENUM(CONFIG_ENC_MODE, AESMODE_KEY128), + SE_REG_BITS_ENUM(CONFIG_DEC_MODE, AESMODE_KEY128), + SE_REG_BITS_ENUM(CONFIG_ENC_ALG, RSA), + SE_REG_BITS_ENUM(CONFIG_DEC_ALG, NOP), + SE_REG_BITS_ENUM(CONFIG_DST, RSA_REG)); + + /* Configure the engine to use the keyslot and correct modulus/exp sizes. */ + const auto &info = g_rsa_key_infos[slot]; + reg::Write(SE->SE_RSA_CONFIG, SE_REG_BITS_VALUE(RSA_CONFIG_KEY_SLOT, slot)); + reg::Write(SE->SE_RSA_KEY_SIZE, info.modulus_size_val); + reg::Write(SE->SE_RSA_EXP_SIZE, info.exponent_size_val); + + /* Set the done handler. */ + SetDoneHandler(SE, handler); + + /* Trigger the input operation. */ + StartInputOperation(SE, work, src_size); + + /* Wait for input to be read by the se. */ + WaitForInputReadComplete(SE); + } + + void GetRsaResult(void *dst, size_t dst_size) { + GetRsaResult(GetRegisters(), dst, dst_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_suspend.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_suspend.cpp new file mode 100644 index 00000000..6ef4af2a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/se/se_suspend.cpp @@ -0,0 +1,354 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "se_execute.hpp" + +namespace ams::se { + + namespace { + + constexpr inline size_t SE1ContextSaveOperationCount = 133; + constexpr inline size_t SE2ContextSaveOperationCount = 646; + static_assert(((SE1ContextSaveOperationCount - 2) + 1) * se::AesBlockSize == sizeof(se::Context)); + + constinit const u8 FixedPattern[AesBlockSize] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F + }; + + bool TestRegister(volatile u32 &r, u16 v) { + return (static_cast<u16>(reg::Read(r))) == v; + } + + void ExecuteContextSaveOperation(volatile SecurityEngineRegisters *SE, void *dst, size_t dst_size, const void *src, size_t src_size) { + /* Save the output to a temporary buffer. */ + util::AlignedBuffer<hw::DataCacheLineSize, AesBlockSize> temp; + AMS_ABORT_UNLESS(dst_size <= AesBlockSize); + + /* Ensure that the cpu and SE see consistent data. */ + if (src_size > 0) { + hw::FlushDataCache(src, src_size); + hw::DataSynchronizationBarrierInnerShareable(); + } + if (dst_size > 0) { + hw::FlushDataCache(temp, AesBlockSize); + hw::DataSynchronizationBarrierInnerShareable(); + } + + /* Execute the operation. */ + ExecuteOperation(SE, SE_OPERATION_OP_CTX_SAVE, temp, dst_size, src, src_size); + + /* Copy output from the operation, if any. */ + if (dst_size > 0) { + hw::DataSynchronizationBarrierInnerShareable(); + hw::FlushDataCache(temp, AesBlockSize); + hw::DataSynchronizationBarrierInnerShareable(); + + std::memcpy(dst, temp, dst_size); + } + } + + void SaveContextBlock(volatile SecurityEngineRegisters *SE, void *dst) { + /* Configure to encrypt a single block. */ + reg::Write(SE->SE_CRYPTO_LAST_BLOCK, 0); + + /* Execute the operation. */ + ExecuteContextSaveOperation(SE, dst, AesBlockSize, nullptr, 0); + } + + void ConfigureForAutomaticContextSave(volatile SecurityEngineRegisters *SE) { + /* Configure the engine to do RNG encryption. */ + reg::Write(SE->SE_CONFIG, SE_REG_BITS_ENUM(CONFIG_ENC_MODE, AESMODE_KEY128), + SE_REG_BITS_ENUM(CONFIG_DEC_MODE, AESMODE_KEY128), + SE_REG_BITS_ENUM(CONFIG_ENC_ALG, RNG), + SE_REG_BITS_ENUM(CONFIG_DEC_ALG, NOP), + SE_REG_BITS_ENUM(CONFIG_DST, MEMORY)); + + reg::Write(SE->SE_CRYPTO_CONFIG, SE_REG_BITS_ENUM (CRYPTO_CONFIG_MEMIF, AHB), + SE_REG_BITS_VALUE(CRYPTO_CONFIG_CTR_CNTN, 0), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_KEYSCH_BYPASS, DISABLE), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_CORE_SEL, ENCRYPT), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_IV_SELECT, ORIGINAL), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_VCTRAM_SEL, MEMORY), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_INPUT_SEL, RANDOM), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_XOR_POS, BYPASS), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_HASH_ENB, DISABLE)); + } + + void WaitAutomaticContextSaveDone(volatile SecurityEngineRegisters *SE) { + /* Wait for operation. */ + while (!reg::HasValue(SE->SE_INT_STATUS, SE_REG_BITS_ENUM(INT_STATUS_SE_OP_DONE, ACTIVE))) { /* ... */ } + + /* Wait for the engine to be idle. */ + while (!reg::HasValue(SE->SE_STATUS, SE_REG_BITS_ENUM(STATUS_STATE, IDLE))) { /* ... */ } + + /* Wait for the memory interface to be idle. */ + while (!reg::HasValue(SE->SE_STATUS, SE_REG_BITS_ENUM(STATUS_MEM_INTERFACE, IDLE))) { /* ... */ } + } + + void ValidateErrStatus(volatile SecurityEngineRegisters *SE) { + /* Ensure there is no error status. */ + AMS_ABORT_UNLESS(reg::Read(SE->SE_ERR_STATUS) == 0); + + /* Ensure no error occurred. */ + AMS_ABORT_UNLESS(reg::HasValue(SE->SE_INT_STATUS, SE_REG_BITS_ENUM(INT_STATUS_ERR_STAT, CLEAR))); + } + + } + + bool ValidateStickyBits(const StickyBits &bits) { + /* Get the registers. */ + auto *SE = GetRegisters(); + + /* Check SE_SECURITY. */ + if (!TestRegister(SE->SE_SE_SECURITY, bits.se_security)) { return false; } + + /* Check TZRAM_SECURITY. */ + if (!TestRegister(SE->SE_TZRAM_SECURITY, bits.tzram_security)) { return false; } + + /* Check CRYPTO_SECURITY_PERKEY. */ + if (!TestRegister(SE->SE_CRYPTO_SECURITY_PERKEY, bits.crypto_security_perkey)) { return false; } + + /* Check CRYPTO_KEYTABLE_ACCESS. */ + for (int i = 0; i < AesKeySlotCount; ++i) { + if (!TestRegister(SE->SE_CRYPTO_KEYTABLE_ACCESS[i], bits.crypto_keytable_access[i])) { return false; } + } + + /* Test RSA_SECURITY_PERKEY */ + if (!TestRegister(SE->SE_RSA_SECURITY_PERKEY, bits.rsa_security_perkey)) { return false; } + + /* Check RSA_KEYTABLE_ACCESS. */ + for (int i = 0; i < RsaKeySlotCount; ++i) { + if (!TestRegister(SE->SE_RSA_KEYTABLE_ACCESS[i], bits.rsa_keytable_access[i])) { return false; } + } + + /* All sticky bits are valid. */ + return true; + } + + void SaveContext(Context *dst) { + /* Get the registers. */ + auto *SE = GetRegisters(); + + /* Generate a random srk. */ + GenerateSrk(); + + /* Save a randomly-generated block. */ + { + util::AlignedBuffer<hw::DataCacheLineSize, AesBlockSize> random_block; + + /* Flush the region we're about to fill to ensure consistency with the SE. */ + hw::FlushDataCache(random_block, AesBlockSize); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Generate random bytes. */ + GenerateRandomBytes(random_block, AesBlockSize); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Flush to ensure the CPU sees consistent data for the region. */ + hw::FlushDataCache(random_block, AesBlockSize); + hw::DataSynchronizationBarrierInnerShareable(); + + /* Configure to encrypt the random block to memory. */ + reg::Write(SE->SE_CONFIG, SE_REG_BITS_ENUM(CONFIG_ENC_MODE, AESMODE_KEY128), + SE_REG_BITS_ENUM(CONFIG_DEC_MODE, AESMODE_KEY128), + SE_REG_BITS_ENUM(CONFIG_ENC_ALG, AES_ENC), + SE_REG_BITS_ENUM(CONFIG_DEC_ALG, NOP), + SE_REG_BITS_ENUM(CONFIG_DST, MEMORY)); + + /* Configure to context save using memory as source. */ + reg::Write(SE->SE_CTX_SAVE_CONFIG, SE_REG_BITS_ENUM(CTX_SAVE_CONFIG_SRC, MEM)); + + /* Configure to encrypt a single block. */ + reg::Write(SE->SE_CRYPTO_LAST_BLOCK, 0); + + /* Execute the operation. */ + ExecuteContextSaveOperation(SE, dst->random, AesBlockSize, random_block, AesBlockSize); + } + + /* Save the sticky bits. */ + for (size_t i = 0; i < util::size(dst->sticky_bits); ++i) { + /* Configure to encrypt the sticky bits block. */ + reg::Write(SE->SE_CTX_SAVE_CONFIG, SE_REG_BITS_ENUM (CTX_SAVE_CONFIG_SRC, STICKY_BITS), + SE_REG_BITS_VALUE(CTX_SAVE_CONFIG_STICKY_WORD_QUAD, i)); + + /* Save the block. */ + SaveContextBlock(SE, dst->sticky_bits[i]); + } + + /* Save the aes keytable. */ + { + for (size_t key = 0; key < util::size(dst->aes_key); ++key) { + for (auto part = 0; part < AesKeySlotPartCount; ++part) { + /* Configure to encrypt the part of the key. */ + reg::Write(SE->SE_CTX_SAVE_CONFIG, SE_REG_BITS_ENUM (CTX_SAVE_CONFIG_SRC, AES_KEYTABLE), + SE_REG_BITS_VALUE(CTX_SAVE_CONFIG_AES_KEY_INDEX, key), + SE_REG_BITS_VALUE(CTX_SAVE_CONFIG_AES_WORD_QUAD, part)); + + /* Save the block. */ + SaveContextBlock(SE, dst->aes_key[key][part]); + } + } + + for (size_t key = 0; key < util::size(dst->aes_oiv); ++key) { + /* Configure to encrypt the original iv. */ + reg::Write(SE->SE_CTX_SAVE_CONFIG, SE_REG_BITS_ENUM (CTX_SAVE_CONFIG_SRC, AES_KEYTABLE), + SE_REG_BITS_VALUE(CTX_SAVE_CONFIG_AES_KEY_INDEX, key), + SE_REG_BITS_ENUM (CTX_SAVE_CONFIG_AES_WORD_QUAD, ORIGINAL_IVS)); + + /* Save the block. */ + SaveContextBlock(SE, dst->aes_oiv[key]); + } + + for (size_t key = 0; key < util::size(dst->aes_uiv); ++key) { + /* Configure to encrypt the updated iv. */ + reg::Write(SE->SE_CTX_SAVE_CONFIG, SE_REG_BITS_ENUM (CTX_SAVE_CONFIG_SRC, AES_KEYTABLE), + SE_REG_BITS_VALUE(CTX_SAVE_CONFIG_AES_KEY_INDEX, key), + SE_REG_BITS_ENUM (CTX_SAVE_CONFIG_AES_WORD_QUAD, UPDATED_IVS)); + + /* Save the block. */ + SaveContextBlock(SE, dst->aes_uiv[key]); + } + } + + /* Save the rsa keytable. */ + for (size_t key = 0; key < util::size(dst->rsa_key); ++key) { + for (auto part = 0; part < RsaKeySlotPartCount; ++part) { + /* Note that the parts are done in reverse order. */ + const auto part_index = RsaKeySlotPartCount - 1 - part; + + /* Determine a total key index. */ + const auto key_index = key * util::size(dst->rsa_key) + part_index; + + for (size_t block = 0; block < RsaSize / AesBlockSize; ++block) { + /* Configure to encrypt the part of the key. */ + reg::Write(SE->SE_CTX_SAVE_CONFIG, SE_REG_BITS_ENUM (CTX_SAVE_CONFIG_SRC, RSA_KEYTABLE), + SE_REG_BITS_VALUE(CTX_SAVE_CONFIG_RSA_KEY_INDEX, key_index), + SE_REG_BITS_VALUE(CTX_SAVE_CONFIG_RSA_WORD_QUAD, block)); + + /* Save the block. */ + SaveContextBlock(SE, dst->rsa_key[key][part][block]); + } + } + } + + /* Save the fixed pattern. */ + { + /* Configure to context save using memory as source. */ + reg::Write(SE->SE_CTX_SAVE_CONFIG, SE_REG_BITS_ENUM(CTX_SAVE_CONFIG_SRC, MEM)); + + /* Configure to encrypt a single block. */ + reg::Write(SE->SE_CRYPTO_LAST_BLOCK, 0); + + /* Execute the operation. */ + ExecuteContextSaveOperation(SE, dst->fixed_pattern, AesBlockSize, FixedPattern, AesBlockSize); + } + + /* Save the srk. */ + { + /* Configure to context save using srk as source. */ + reg::Write(SE->SE_CTX_SAVE_CONFIG, SE_REG_BITS_ENUM(CTX_SAVE_CONFIG_SRC, SRK)); + + /* Configure to encrypt a single block. */ + reg::Write(SE->SE_CRYPTO_LAST_BLOCK, 0); + + /* Execute the operation. */ + ExecuteContextSaveOperation(SE, nullptr, 0, nullptr, 0); + } + + /* Perform a no-op context save operation. */ + { + /* Configure to perform no-op. */ + reg::Write(SE->SE_CONFIG, SE_REG_BITS_ENUM(CONFIG_ENC_ALG, NOP), + SE_REG_BITS_ENUM(CONFIG_DEC_ALG, NOP)); + + /* Execute the operation. */ + ExecuteContextSaveOperation(SE, nullptr, 0, nullptr, 0); + } + } + + void ConfigureAutomaticContextSave() { + /* Get registers. */ + auto *SE = GetRegisters(); + auto *SE2 = GetRegisters2(); + + /* Automatic context save is supported only on mariko. */ + if (fuse::GetSocType() == fuse::SocType_Mariko) { + /* Configure SE1 to do automatic context save. */ + reg::Write(SE->SE_CTX_SAVE_AUTO, SE_REG_BITS_ENUM(CTX_SAVE_AUTO_ENABLE, YES), + SE_REG_BITS_ENUM(CTX_SAVE_AUTO_LOCK, YES)); + + /* Configure SE2 to do automatic context save. */ + reg::Write(SE2->SE_CTX_SAVE_AUTO, SE_REG_BITS_ENUM(CTX_SAVE_AUTO_ENABLE, YES), + SE_REG_BITS_ENUM(CTX_SAVE_AUTO_LOCK, YES)); + } + } + + void SaveContextAutomatic() { + /* Get registers. */ + auto *SE = GetRegisters(); + auto *SE2 = GetRegisters2(); + + /* Ensure there's no error status before or after we save context. */ + ValidateErrStatus(); + ON_SCOPE_EXIT { ValidateErrStatus(); }; + + /* Perform atomic context save. */ + { + /* Check that context save has not already been performed. */ + AMS_ABORT_UNLESS(reg::HasValue(SE->SE_CTX_SAVE_AUTO, SE_REG_BITS_VALUE(CTX_SAVE_AUTO_CURR_CNT, 0))); + AMS_ABORT_UNLESS(reg::HasValue(SE2->SE_CTX_SAVE_AUTO, SE_REG_BITS_VALUE(CTX_SAVE_AUTO_CURR_CNT, 0))); + + /* Configure SE1 to do context save. */ + ConfigureForAutomaticContextSave(SE); + ConfigureForAutomaticContextSave(SE2); + + /* Start the context save operation. */ + reg::Write(SE->SE_OPERATION, SE_REG_BITS_ENUM(OPERATION_OP, CTX_SAVE)); + reg::Write(SE2->SE_OPERATION, SE_REG_BITS_ENUM(OPERATION_OP, CTX_SAVE)); + + /* Wait for the context save operation to complete. */ + WaitAutomaticContextSaveDone(SE); + WaitAutomaticContextSaveDone(SE2); + + /* Check that the correct sizes were written. */ + AMS_ABORT_UNLESS(reg::HasValue(SE->SE_CTX_SAVE_AUTO, SE_REG_BITS_VALUE(CTX_SAVE_AUTO_CURR_CNT, SE1ContextSaveOperationCount))); + AMS_ABORT_UNLESS(reg::HasValue(SE2->SE_CTX_SAVE_AUTO, SE_REG_BITS_VALUE(CTX_SAVE_AUTO_CURR_CNT, SE2ContextSaveOperationCount))); + } + } + + void SaveTzramAutomatic() { + /* Get registers. */ + auto *SE = GetRegisters(); + + /* Begin save-to-shadow-tzram operation. */ + reg::Write(SE->SE_TZRAM_OPERATION, SE_REG_BITS_ENUM(TZRAM_OPERATION_MODE, SAVE), + SE_REG_BITS_ENUM(TZRAM_OPERATION_REQ, INITIATE)); + + /* Wait for operation to complete. */ + while (reg::HasValue(SE->SE_TZRAM_OPERATION, SE_REG_BITS_ENUM(TZRAM_OPERATION_BUSY, YES))) { /* ... */ } + } + + void ValidateErrStatus() { + /* Ensure SE has no error status. */ + ValidateErrStatus(GetRegisters()); + + /* If on mariko, ensure SE2 has no error status. */ + if (fuse::GetSocType() == fuse::SocType_Mariko) { + ValidateErrStatus(GetRegisters2()); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/tsec/tsec_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/tsec/tsec_api.cpp new file mode 100644 index 00000000..1145f3bd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/tsec/tsec_api.cpp @@ -0,0 +1,176 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "tsec_registers.hpp" +#include "../kfuse/kfuse_registers.hpp" + +namespace ams::tsec { + + namespace { + + constexpr inline const uintptr_t KFUSE = 0x7000FC00; + constexpr inline const uintptr_t TSEC = 0x54500000; + + enum TsecResult : u32 { + TsecResult_Success = 0xB0B0B0B0, + TsecResult_Failure = 0xD0D0D0D0, + }; + + enum TsecMemory { + TsecMemory_Imem, + TsecMemory_Dmem, + }; + + bool WaitForKfuseReady() { + constexpr auto KfuseTimeout = 10 * 1000; /* 10 ms. */ + + const u32 end_time = util::GetMicroSeconds() + KfuseTimeout; + + /* Wait for STATE_DONE. */ + while (!reg::HasValue(KFUSE + KFUSE_STATE, KFUSE_REG_BITS_ENUM(STATE_DONE, DONE))) { + if (util::GetMicroSeconds() >= end_time) { + return false; + } + } + + /* Check for STATE_CRCPASS. */ + return reg::HasValue(KFUSE + KFUSE_STATE, KFUSE_REG_BITS_ENUM(STATE_CRCPASS, PASS)); + } + + void WaitForDmaIdle() { + constexpr auto DmaTimeout = 10 * 1000 * 1000; /* 10 Seconds. */ + + u32 cur_time = util::GetMicroSeconds(); + const u32 end_time = cur_time + DmaTimeout; + + while (cur_time <= end_time) { + if (reg::HasValue(TSEC + TSEC_FALCON_DMATRFCMD, TSEC_REG_BITS_ENUM(FALCON_DMATRFCMD_BUSY, IDLE))) { + return; + } + + cur_time = util::GetMicroSeconds(); + } + + AMS_ABORT("tsec dma timeout"); + } + + void WaitForTsecIdle() { + constexpr auto TsecTimeout = 2 * 1000 * 1000; /* 2 Seconds. */ + + u32 cur_time = util::GetMicroSeconds(); + const u32 end_time = cur_time + TsecTimeout; + + while (cur_time <= end_time) { + if (reg::HasValue(TSEC + TSEC_FALCON_CPUCTL, TSEC_REG_BITS_ENUM(FALCON_CPUCTL_HALTED, TRUE))) { + return; + } + + cur_time = util::GetMicroSeconds(); + } + + AMS_ABORT("tsec timeout"); + } + + void DoDma256(TsecMemory memory, u32 dst_offset, u32 src_offset) { + reg::Write(TSEC + TSEC_FALCON_DMATRFMOFFS, TSEC_REG_BITS_VALUE(FALCON_DMATRFMOFFS_OFFSET, dst_offset)); + reg::Write(TSEC + TSEC_FALCON_DMATRFFBOFFS, src_offset); + + if (memory == TsecMemory_Imem) { + reg::Write(TSEC + TSEC_FALCON_DMATRFCMD, TSEC_REG_BITS_ENUM(FALCON_DMATRFCMD_TO, IMEM), + TSEC_REG_BITS_ENUM(FALCON_DMATRFCMD_SIZE, 4B)); + } else { + reg::Write(TSEC + TSEC_FALCON_DMATRFCMD, TSEC_REG_BITS_ENUM(FALCON_DMATRFCMD_TO, DMEM), + TSEC_REG_BITS_ENUM(FALCON_DMATRFCMD_SIZE, 256B)); + } + + WaitForDmaIdle(); + } + + } + + bool RunTsecFirmware(const void *fw, size_t fw_size) { + /* Enable relevant clocks. */ + clkrst::EnableHost1xClock(); + clkrst::EnableTsecClock(); + clkrst::EnableSorSafeClock(); + clkrst::EnableSor0Clock(); + clkrst::EnableSor1Clock(); + clkrst::EnableKfuseClock(); + + /* Disable clocks once we're done. */ + ON_SCOPE_EXIT { + clkrst::DisableHost1xClock(); + clkrst::DisableTsecClock(); + clkrst::DisableSorSafeClock(); + clkrst::DisableSor0Clock(); + clkrst::DisableSor1Clock(); + clkrst::DisableKfuseClock(); + }; + + /* Wait for kfuse to be ready. */ + if (!WaitForKfuseReady()) { + return false; + } + + /* Configure falcon. */ + reg::Write(TSEC + TSEC_FALCON_DMACTL, 0); + reg::Write(TSEC + TSEC_FALCON_IRQMSET, 0xFFF2); + reg::Write(TSEC + TSEC_FALCON_IRQDEST, 0xFFF0); + reg::Write(TSEC + TSEC_FALCON_ITFEN, 0x3); + + /* Wait for TSEC dma to be idle. */ + WaitForDmaIdle(); + + /* Set the base address for transfers. */ + reg::Write(TSEC + TSEC_FALCON_DMATRFBASE, reinterpret_cast<uintptr_t>(fw) >> 8); + + /* Transfer all data to TSEC imem. */ + for (size_t i = 0; i < fw_size; i += 0x100) { + DoDma256(TsecMemory_Imem, i, i); + } + + /* Write the magic value to host1x syncpoint 160. */ + reg::Write(0x50003300, 0x34C2E1DA); + + /* Execute the firmware. */ + reg::Write(TSEC + TSEC_FALCON_MAILBOX0, 0); + reg::Write(TSEC + TSEC_FALCON_MAILBOX1, 0); + reg::Write(TSEC + TSEC_FALCON_BOOTVEC, 0); + reg::Write(TSEC + TSEC_FALCON_CPUCTL, TSEC_REG_BITS_ENUM(FALCON_CPUCTL_STARTCPU, TRUE)); + + /* Wait for TSEC dma to be idle. */ + WaitForDmaIdle(); + + /* Wait for TSEC to complete. */ + WaitForTsecIdle(); + + /* Clear magic value from host1x syncpoint 160. */ + reg::Write(0x50003300, 0); + + /* Return whether the tsec firmware succeeded. */ + return reg::Read(TSEC + TSEC_FALCON_MAILBOX1) == TsecResult_Success; + } + + void Lock() { + /* Set the tsec host1x syncpoint (160) to be secure. */ + /* TODO: constexpr value. */ + reg::ReadWrite(0x500038F8, REG_BITS_VALUE(0, 1, 0)); + + /* Clear the tsec host1x syncpoint. */ + reg::Write(0x50003300, 0); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/tsec/tsec_registers.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/tsec/tsec_registers.hpp new file mode 100644 index 00000000..326d75fd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/tsec/tsec_registers.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +#define TSEC_FALCON_IRQMSET (0x1010) +#define TSEC_FALCON_IRQDEST (0x101C) +#define TSEC_FALCON_MAILBOX0 (0x1040) +#define TSEC_FALCON_MAILBOX1 (0x1044) +#define TSEC_FALCON_ITFEN (0x1048) +#define TSEC_FALCON_CPUCTL (0x1100) +#define TSEC_FALCON_BOOTVEC (0x1104) +#define TSEC_FALCON_DMACTL (0x110C) +#define TSEC_FALCON_DMATRFBASE (0x1110) +#define TSEC_FALCON_DMATRFMOFFS (0x1114) +#define TSEC_FALCON_DMATRFCMD (0x1118) +#define TSEC_FALCON_DMATRFFBOFFS (0x111C) + +#define TSEC_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (TSEC, NAME) +#define TSEC_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (TSEC, NAME, VALUE) +#define TSEC_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (TSEC, NAME, ENUM) +#define TSEC_REG_BITS_ENUM_TSECL(NAME, __COND__, TRUE_ENUM, FALTSEC_ENUM) REG_NAMED_BITS_ENUM_TSECL(TSEC, NAME, __COND__, TRUE_ENUM, FALTSEC_ENUM) + +#define DEFINE_TSEC_REG(NAME, __OFFTSECT__, __WIDTH__) REG_DEFINE_NAMED_REG (TSEC, NAME, __OFFTSECT__, __WIDTH__) +#define DEFINE_TSEC_REG_BIT_ENUM(NAME, __OFFTSECT__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (TSEC, NAME, __OFFTSECT__, ZERO, ONE) +#define DEFINE_TSEC_REG_TWO_BIT_ENUM(NAME, __OFFTSECT__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (TSEC, NAME, __OFFTSECT__, ZERO, ONE, TWO, THREE) +#define DEFINE_TSEC_REG_THREE_BIT_ENUM(NAME, __OFFTSECT__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, TSECVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(TSEC, NAME, __OFFTSECT__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, TSECVEN) +#define DEFINE_TSEC_REG_FOUR_BIT_ENUM(NAME, __OFFTSECT__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, TSECVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (TSEC, NAME, __OFFTSECT__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, TSECVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + +DEFINE_TSEC_REG_BIT_ENUM(FALCON_CPUCTL_STARTCPU, 1, FALSE, TRUE); +DEFINE_TSEC_REG_BIT_ENUM(FALCON_CPUCTL_HALTED, 4, FALSE, TRUE); + +DEFINE_TSEC_REG(FALCON_DMATRFMOFFS_OFFSET, 0, 16); + +DEFINE_TSEC_REG_BIT_ENUM(FALCON_DMATRFCMD_BUSY, 1, BUSY, IDLE); +DEFINE_TSEC_REG_BIT_ENUM(FALCON_DMATRFCMD_TO, 4, DMEM, IMEM); +DEFINE_TSEC_REG_THREE_BIT_ENUM(FALCON_DMATRFCMD_SIZE, 8, 4B, 8B, 16B, 32B, 64B, 128B, 256B, RSVD7); \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/uart/uart_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/uart/uart_api.cpp new file mode 100644 index 00000000..532c2e6e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/uart/uart_api.cpp @@ -0,0 +1,149 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "uart_registers.hpp" + +namespace ams::uart { + + namespace { + + constexpr inline const u16 UartRegisterOffsets[Port_Count] = { + secmon::MemoryRegionPhysicalDeviceUartA.GetAddress() - secmon::MemoryRegionPhysicalDeviceUart.GetAddress(), + secmon::MemoryRegionPhysicalDeviceUartB.GetAddress() - secmon::MemoryRegionPhysicalDeviceUart.GetAddress(), + secmon::MemoryRegionPhysicalDeviceUartC.GetAddress() - secmon::MemoryRegionPhysicalDeviceUart.GetAddress(), + }; + + constinit uintptr_t g_register_address = secmon::MemoryRegionPhysicalDeviceUart.GetAddress(); + + volatile UartRegisters *GetRegisters(Port port) { + return reinterpret_cast<volatile UartRegisters *>(g_register_address + UartRegisterOffsets[port]); + } + + void WaitSymbols(int baud, u32 num) { + util::WaitMicroSeconds(util::DivideUp(num * 1'000'000, baud)); + } + + void WaitCycles(int baud, u32 num) { + util::WaitMicroSeconds(util::DivideUp(num * 1'000'000, 16 * baud)); + } + + ALWAYS_INLINE void WaitFifoNotFull(volatile UartRegisters *uart) { + while ((uart->lsr & UART_LSR_TX_FIFO_FULL) != 0) { /* ... */ } + } + + ALWAYS_INLINE void WaitFifoNotEmpty(volatile UartRegisters *uart) { + while ((uart->lsr & UART_LSR_RX_FIFO_EMPTY) != 0) { /* ... */ } + } + + void WaitIdle(volatile UartRegisters *uart, u32 vendor_state) { + if (vendor_state & UART_VENDOR_STATE_TX_IDLE) { + while ((uart->lsr & UART_LSR_TMTY) == 0) { /* ... */ } + } + + if (vendor_state & UART_VENDOR_STATE_RX_IDLE) { + while ((uart->lsr & UART_LSR_RDR) != 0) { /* ... */ } + } + } + + constexpr inline u32 LockBit = (1 << 6); + + } + + void SetRegisterAddress(uintptr_t address) { + g_register_address = address; + } + + void Initialize(Port port, int baud_rate, u32 flags) { + /* Get the registers. */ + auto *uart = GetRegisters(port); + + /* Parse flags. */ + const bool inverted = (flags & Flag_Inverted) != 0; + + /* Calculate the baud rate divisor. */ + constexpr u32 UartClock = 408000000; + const u32 divisor = (UartClock + (baud_rate * 16) / 2) / (baud_rate * 16); + + /* Wait for idle state. */ + WaitIdle(uart, UART_VENDOR_STATE_TX_IDLE); + + /* Wait 100 us. */ + util::WaitMicroSeconds(100); + + /* Disable interrupts. */ + uart->lcr = uart->lcr & ~UART_LCR_DLAB; + uart->ier = 0; + uart->mcr = 0; + + /* Setup the uart in FIFO mode. */ + uart->lcr = UART_LCR_DLAB | UART_LCR_WD_LENGTH_8; + uart->dll = static_cast<u8>(divisor); + uart->dlh = static_cast<u8>(divisor >> 8); + uart->lcr = uart->lcr & ~UART_LCR_DLAB; + reg::Read(std::addressof(uart->spr)); + + /* Wait three symbols. */ + WaitSymbols(baud_rate, 3); + + /* Enable FIFO with default settings. */ + uart->fcr = UART_FCR_FCR_EN_FIFO; + uart->irda_csr = inverted ? UART_IRDA_CSR_INVERT_TXD : 0; + reg::Read(std::addressof(uart->spr)); + + /* Wait three cycles. */ + WaitCycles(baud_rate, 3); + + /* Flush the FIFO. */ + WaitIdle(uart, UART_VENDOR_STATE_TX_IDLE); + uart->fcr = uart->fcr | UART_FCR_RX_CLR | UART_FCR_TX_CLR; + WaitCycles(baud_rate, 32); + + /* Wait for idle state. */ + WaitIdle(uart, UART_VENDOR_STATE_TX_IDLE | UART_VENDOR_STATE_RX_IDLE); + + /* Wait 100 us. */ + util::WaitMicroSeconds(100); + } + + void SendText(Port port, const void *data, size_t size) { + /* Get the registers. */ + auto *uart = GetRegisters(port); + + /* Get pointer to data. */ + const u8 *p = static_cast<const u8 *>(data); + + /* Send each byte. */ + for (size_t i = 0; i < size; ++i) { + WaitFifoNotFull(uart); + + if (p[i] == '\n') { + *reinterpret_cast<volatile u8 *>(std::addressof(uart->thr)) = '\r'; + WaitFifoNotFull(uart); + } + + *reinterpret_cast<volatile u8 *>(std::addressof(uart->thr)) = p[i]; + } + } + + void WaitFlush(Port port) { + /* Get the registers. */ + auto *uart = GetRegisters(port); + + /* Wait for idle. */ + WaitIdle(uart, UART_VENDOR_STATE_TX_IDLE); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/uart/uart_registers.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/uart/uart_registers.hpp new file mode 100644 index 00000000..2ea32d77 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/uart/uart_registers.hpp @@ -0,0 +1,180 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::uart { + + struct UartRegisters { + union { + u32 thr; + u32 rbr; + u32 dll; + }; + union { + u32 ier; + u32 dlh; + }; + union { + u32 iir; + u32 fcr; + }; + u32 lcr; + u32 mcr; + u32 lsr; + u32 msr; + u32 spr; + u32 irda_csr; + u32 rx_fifo_cfg; + u32 mie; + u32 vendor_status; + u32 reserved_30; + u32 reserved_34; + u32 reserved_38; + u32 asr; + }; + static_assert(util::is_pod<UartRegisters>::value); + static_assert(sizeof(UartRegisters) == 0x40); + + /* 36.3.12 UART_VENDOR_STATUS_0_0 */ + enum UartVendorStatus { + UART_VENDOR_STATE_TX_IDLE = 1 << 0, + UART_VENDOR_STATE_RX_IDLE = 1 << 1, + + /* This bit is set to 1 when a read is issued to an empty FIFO and gets cleared on register read (sticky bit until read) + 0 = NO_UNDERRUN + 1 = UNDERRUN + */ + UART_VENDOR_STATE_RX_UNDERRUN = 1 << 2, + + /* This bit is set to 1 when write data is issued to the TX FIFO when it is already full and gets cleared on register read (sticky bit until read) + 0 = NO_OVERRUN + 1 = OVERRUN + */ + UART_VENDOR_STATE_TX_OVERRUN = 1 << 3, + + UART_VENDOR_STATE_RX_FIFO_COUNTER = 0b111111 << 16, /* reflects number of current entries in RX FIFO */ + UART_VENDOR_STATE_TX_FIFO_COUNTER = 0b111111 << 24 /* reflects number of current entries in TX FIFO */ + }; + + /* 36.3.6 UART_LSR_0 */ + enum UartLineStatus { + UART_LSR_RDR = 1 << 0, /* Receiver Data Ready */ + UART_LSR_OVRF = 1 << 1, /* Receiver Overrun Error */ + UART_LSR_PERR = 1 << 2, /* Parity Error */ + UART_LSR_FERR = 1 << 3, /* Framing Error */ + UART_LSR_BRK = 1 << 4, /* BREAK condition detected on line */ + UART_LSR_THRE = 1 << 5, /* Transmit Holding Register is Empty -- OK to write data */ + UART_LSR_TMTY = 1 << 6, /* Transmit Shift Register empty status */ + UART_LSR_FIFOE = 1 << 7, /* Receive FIFO Error */ + UART_LSR_TX_FIFO_FULL = 1 << 8, /* Transmitter FIFO full status */ + UART_LSR_RX_FIFO_EMPTY = 1 << 9, /* Receiver FIFO empty status */ + }; + + /* 36.3.4 UART_LCR_0 */ + enum UartLineControl { + UART_LCR_WD_LENGTH_5 = 0, /* word length 5 */ + UART_LCR_WD_LENGTH_6 = 1, /* word length 6 */ + UART_LCR_WD_LENGTH_7 = 2, /* word length 7 */ + UART_LCR_WD_LENGTH_8 = 3, /* word length 8 */ + + /* STOP: + 0 = Transmit 1 stop bit + 1 = Transmit 2 stop bits (receiver always checks for 1 stop bit) + */ + UART_LCR_STOP = 1 << 2, + UART_LCR_PAR = 1 << 3, /* Parity enabled */ + UART_LCR_EVEN = 1 << 4, /* Even parity format. There will always be an even number of 1s in the binary representation (PAR = 1) */ + UART_LCR_SET_P = 1 << 5, /* Set (force) parity to value in LCR[4] */ + UART_LCR_SET_B = 1 << 6, /* Set BREAK condition -- Transmitter sends all zeroes to indicate BREAK */ + UART_LCR_DLAB = 1 << 7, /* Divisor Latch Access Bit (set to allow programming of the DLH, DLM Divisors) */ + }; + + /* 36.3.3 UART_IIR_FCR_0 */ + enum UartFifoControl { + UART_FCR_FCR_EN_FIFO = 1 << 0, /* Enable the transmit and receive FIFOs. This bit should be enabled */ + UART_FCR_RX_CLR = 1 << 1, /* Clears the contents of the receive FIFO and resets its counter logic to 0 (the receive shift register is not cleared or altered). This bit returns to 0 after clearing the FIFOs */ + UART_FCR_TX_CLR = 1 << 2, /* Clears the contents of the transmit FIFO and resets its counter logic to 0 (the transmit shift register is not cleared or altered). This bit returns to 0 after clearing the FIFOs */ + + /* DMA: + 0 = DMA_MODE_0 + 1 = DMA_MODE_1 + */ + UART_FCR_DMA = 1 << 3, + + /* TX_TRIG + 0 = FIFO_COUNT_GREATER_16 + 1 = FIFO_COUNT_GREATER_8 + 2 = FIFO_COUNT_GREATER_4 + 3 = FIFO_COUNT_GREATER_1 + */ + UART_FCR_TX_TRIG = 3 << 4, + UART_FCR_TX_TRIG_FIFO_COUNT_GREATER_16 = 0 << 4, + UART_FCR_TX_TRIG_FIFO_COUNT_GREATER_8 = 1 << 4, + UART_FCR_TX_TRIG_FIFO_COUNT_GREATER_4 = 2 << 4, + UART_FCR_TX_TRIG_FIFO_COUNT_GREATER_1 = 3 << 4, + + /* RX_TRIG + 0 = FIFO_COUNT_GREATER_1 + 1 = FIFO_COUNT_GREATER_4 + 2 = FIFO_COUNT_GREATER_8 + 3 = FIFO_COUNT_GREATER_16 + */ + UART_FCR_RX_TRIG = 3 << 6, + UART_FCR_RX_TRIG_FIFO_COUNT_GREATER_1 = 0 << 6, + UART_FCR_RX_TRIG_FIFO_COUNT_GREATER_4 = 1 << 6, + UART_FCR_RX_TRIG_FIFO_COUNT_GREATER_8 = 2 << 6, + UART_FCR_RX_TRIG_FIFO_COUNT_GREATER_16 = 3 << 6, + }; + + /* 36.3.2 UART_IER_DLAB_0_0 */ + enum UartInterruptEnable { + UART_IER_IE_RHR = 1 << 0, /* Interrupt enable for Received Data Interrupt */ + UART_IER_IE_THR = 1 << 1, /* Interrupt enable for Transmitter Holding Register Empty interrupt */ + UART_IER_IE_RXS = 1 << 2, /* Interrupt enable for Receiver Line Status Interrupt */ + UART_IER_IE_MSI = 1 << 3, /* Interrupt enable for Modem Status Interrupt */ + UART_IER_IE_RX_TIMEOUT = 1 << 4, /* Interrupt enable for RX FIFO timeout */ + UART_IER_IE_EORD = 1 << 5, /* Interrupt enable for Interrupt Enable for End of Received Data */ + }; + + /* 36.3.3 UART_IIR_FCR_0 */ + enum UartInterruptIdentification { + UART_IIR_IS_STA = 1 << 0, /* Interrupt Pending if ZERO */ + UART_IIR_IS_PRI0 = 1 << 1, /* Encoded Interrupt ID Refer to IIR[3:0] table [36.3.3] */ + UART_IIR_IS_PRI1 = 1 << 2, /* Encoded Interrupt ID Refer to IIR[3:0] table */ + UART_IIR_IS_PRI2 = 1 << 3, /* Encoded Interrupt ID Refer to IIR[3:0] table */ + + /* FIFO Mode Status + 0 = 16450 mode (no FIFO) + 1 = 16550 mode (FIFO) + */ + UART_IIR_EN_FIFO = 3 << 6, + UART_IIR_MODE_16450 = 0 << 6, + UART_IIR_MODE_16550 = 1 << 6, + }; + + /* 36.3.9 UART_IRDA_CSR_0 */ + enum UartIrDAPulseCodingCSR { + UART_IRDA_CSR_INVERT_RXD = 1 << 0, + UART_IRDA_CSR_INVERT_TXD = 1 << 1, + UART_IRDA_CSR_INVERT_CTS = 1 << 2, + UART_IRDA_CSR_INVERT_RTS = 1 << 3, + + UART_IRDA_CSR_PWT_A_BAUD_PULSE_3_14 = 0 << 6, + UART_IRDA_CSR_PWT_A_BAUD_PULSE_4_14 = 1 << 6, + UART_IRDA_CSR_SIR_A = 1 << 7, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/util/util_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/util/util_api.cpp new file mode 100644 index 00000000..f9ade342 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/util/util_api.cpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::util { + + namespace { + + constinit uintptr_t g_timer_register_address = secmon::MemoryRegionPhysicalDeviceTimer.GetAddress(); + + ALWAYS_INLINE uintptr_t GetCurrentTimeRegisterAddress() { + return g_timer_register_address + 0x10; + } + + } + + void SetRegisterAddress(uintptr_t address) { + g_timer_register_address = address; + } + + u32 GetMicroSeconds() { + return reg::Read(GetCurrentTimeRegisterAddress()); + } + + void WaitMicroSeconds(int us) { + const u32 start = reg::Read(GetCurrentTimeRegisterAddress()); + u32 cur = start; + while ((cur - start) <= static_cast<u32>(us)) { + cur = reg::Read(GetCurrentTimeRegisterAddress()); + } + } + + void ClearMemory(void *ptr, size_t size) { + std::memset(ptr, 0, size); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/wdt/wdt_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/wdt/wdt_api.cpp new file mode 100644 index 00000000..46465b0b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libexosphere/source/wdt/wdt_api.cpp @@ -0,0 +1,100 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> + +namespace ams::wdt { + + namespace { + + volatile uintptr_t g_register_address = secmon::MemoryRegionPhysicalDeviceTimer.GetAddress(); + + #if defined(ATMOSPHERE_ARCH_ARM64) + NOINLINE void Reboot(uintptr_t registers) { + __asm__ __volatile__( + /* Get the current core. */ + "mrs x12, mpidr_el1\n" + "and x12, x12, #0xFF\n" + + /* Get the offsets of the registers we want to write */ + "mov x10, #0x8\n" + "mov x11, #0x20\n" + "madd x10, x10, x12, %[registers]\n" + "madd x11, x11, x12, %[registers]\n" + "add x10, x10, #0x60\n" + "add x11, x11, #0x100\n" + + /* Write the magic unlock pattern. */ + "mov w9, #0xC45A\n" + "str w9, [x11, #0xC]\n" + + /* Disable the counters. */ + "mov w9, #0x2\n" + "str w9, [x11, #0x8]\n" + + /* Start periodic timer. */ + "mov w9, #0xC0000000\n" + "str w9, [x10]\n" + + /* Set reboot source to the timer we started. */ + "mov w9, #0x8015\n" + "add w9, w9, w12\n" + "str w9, [x11]\n" + + /* Enable the counters. */ + "mov w9, #0x1\n" + "str w9, [x11, #0x8]\n" + + /* Wait forever. */ + "1: b 1b" + : [registers]"=&r"(registers) + : + : "x9", "x10", "x11", "x12", "memory" + ); + } + #elif defined(ATMOSPHERE_ARCH_ARM) + NOINLINE void Reboot(uintptr_t registers) { + /* Write the magic unlock pattern. */ + reg::Write(registers + 0x18C, 0xC45A); + + /* Disable the counters. */ + reg::Write(registers + 0x188, 0x2); + + /* Start periodic timer. */ + reg::Write(registers + 0x080, 0xC0000000); + + /* Set reboot source to the timer we started. */ + reg::Write(registers + 0x180, 0x8019); + + /* Enable the counters. */ + reg::Write(registers + 0x188, 0x1); + + /* Wait forever until the reboot takes. */ + AMS_INFINITE_LOOP(); + } + #endif + + } + + void SetRegisterAddress(uintptr_t address) { + g_register_address = address; + } + + NOINLINE void Reboot() { + const uintptr_t registers = g_register_address; + Reboot(registers); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/Makefile b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/Makefile new file mode 100644 index 00000000..6c5895cf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/Makefile @@ -0,0 +1,42 @@ +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)/libmesosphere.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)/libmesosphere.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,)) +$(eval $(call ATMOSPHERE_ADD_TARGETS, qemu_virt_a57, qemu-virt, 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/libraries/libmesosphere/README.md b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/README.md new file mode 100644 index 00000000..8e7e9a9c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/README.md @@ -0,0 +1,29 @@ +![License](https://img.shields.io/badge/License-GPLv2-blue.svg) + +libmesosphere is a work-in-progress C++ library implementing functionality for the Horizon Kernel. + +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: +* The [yuzu Nintendo Switch emulator](https://github.com/yuzu-emu/yuzu) and the [Ryujinx Team and Contributors](https://github.com/orgs/Ryujinx) are exempt from GPLv2 licensing. They are permitted, each at their individual discretion, to instead license any source code authored for the libmesosphere project as either GPLv2 or later or the [MIT license](https://github.com/Atmosphere-NX/Atmosphere/blob/master/docs/licensing_exemptions/MIT_LICENSE). In doing so, they may alter, supplement, or entirely remove the copyright notice for each file they choose to relicense. Neither the Atmosphère project nor its individual contributors shall assert their moral rights against any of the aforementioned projects. +* [Nintendo](https://github.com/Nintendo) is exempt from GPLv2 licensing and may (at its option) instead license any source code authored for the libmesosphere project under the Zero-Clause BSD license. + +Credits +===== + +libmesosphere is currently being developed and maintained by __SciresM__.<br> + +In addition to those credited in [Atmosphère's credits](https://github.com/Atmosphere-NX/Atmosphere/blob/master/README.md#Credits), we would like to thank for contributing to libmesosphere in some significant way: + +* @[devkitPro](https://github.com/devkitPro) +* @[yellows8](https://github.com/yellows8) +* @[qlutoo](https://github.com/plutooo) +* @[hedgeberg](https://github.com/hedgeberg) +* @[Nintendo](https://github.com/Nintendo) +* @[NVidia](https://github.com/NVidia) +* @[Kaphotics](https://github.com/kwsch) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere.hpp new file mode 100644 index 00000000..b243495f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere.hpp @@ -0,0 +1,96 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +/* All kernel code should have access to libvapours. */ +#include <vapours.hpp> + +/* First, pull in core macros (panic, etc). */ +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_panic.hpp> + +/* Primitive types. */ +#include <mesosphere/kern_k_typed_address.hpp> +#include <mesosphere/kern_initial_process.hpp> +#include <mesosphere/kern_k_exception_context.hpp> + +/* Tracing functionality. */ +#include <mesosphere/kern_k_trace.hpp> + +/* Core pre-initialization includes. */ +#include <mesosphere/kern_select_cpu.hpp> +#include <mesosphere/kern_select_system_control.hpp> +#include <mesosphere/kern_k_target_system.hpp> + +/* Initialization headers. */ +#include <mesosphere/init/kern_init_elf.hpp> +#include <mesosphere/init/kern_init_layout.hpp> +#include <mesosphere/init/kern_init_slab_setup.hpp> +#include <mesosphere/init/kern_init_page_table_select.hpp> +#include <mesosphere/init/kern_init_arguments_select.hpp> +#include <mesosphere/kern_k_memory_layout.hpp> + +/* Core functionality. */ +#include <mesosphere/kern_select_interrupt_manager.hpp> +#include <mesosphere/kern_k_spin_lock.hpp> +#include <mesosphere/kern_k_memory_manager.hpp> +#include <mesosphere/kern_k_interrupt_task_manager.hpp> +#include <mesosphere/kern_k_slab_heap.hpp> +#include <mesosphere/kern_k_light_lock.hpp> +#include <mesosphere/kern_k_dpc_manager.hpp> +#include <mesosphere/kern_kernel.hpp> +#include <mesosphere/kern_k_page_table_manager.hpp> +#include <mesosphere/kern_select_page_table.hpp> +#include <mesosphere/kern_k_dump_object.hpp> + +/* Miscellaneous objects. */ +#include <mesosphere/kern_k_shared_memory_info.hpp> +#include <mesosphere/kern_k_event_info.hpp> + +/* Auto Objects. */ +#include <mesosphere/kern_k_auto_object.hpp> +#include <mesosphere/kern_k_synchronization_object.hpp> +#include <mesosphere/kern_k_readable_event.hpp> +#include <mesosphere/kern_k_handle_table.hpp> +#include <mesosphere/kern_k_event.hpp> +#include <mesosphere/kern_k_interrupt_event.hpp> +#include <mesosphere/kern_k_light_session.hpp> +#include <mesosphere/kern_k_session.hpp> +#include <mesosphere/kern_k_session_request.hpp> +#include <mesosphere/kern_k_port.hpp> +#include <mesosphere/kern_k_shared_memory.hpp> +#include <mesosphere/kern_k_transfer_memory.hpp> +#include <mesosphere/kern_k_code_memory.hpp> +#include <mesosphere/kern_k_device_address_space.hpp> +#include <mesosphere/kern_select_debug.hpp> +#include <mesosphere/kern_k_process.hpp> +#include <mesosphere/kern_k_resource_limit.hpp> +#include <mesosphere/kern_k_io_pool.hpp> + +/* More Miscellaneous objects. */ +#include <mesosphere/kern_k_object_name.hpp> +#include <mesosphere/kern_k_unsafe_memory.hpp> +#include <mesosphere/kern_k_scoped_resource_reservation.hpp> + +/* Supervisor Calls. */ +#include <mesosphere/kern_svc.hpp> + +/* Main functionality. */ +#include <mesosphere/kern_main.hpp> + +/* Deferred includes. */ +#include <mesosphere/kern_k_auto_object_impls.hpp> +#include <mesosphere/kern_k_scheduler_impls.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm/kern_generic_interrupt_controller.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm/kern_generic_interrupt_controller.hpp new file mode 100644 index 00000000..a44c9221 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm/kern_generic_interrupt_controller.hpp @@ -0,0 +1,282 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <mesosphere/kern_select_cpu.hpp> +#include <mesosphere/kern_k_typed_address.hpp> + +namespace ams::kern::arch::arm { + + struct GicDistributor { + u32 ctlr; + u32 typer; + u32 iidr; + u32 reserved_0x0c; + u32 statusr; + u32 reserved_0x14[3]; + u32 impldef_0x20[8]; + u32 setspi_nsr; + u32 reserved_0x44; + u32 clrspi_nsr; + u32 reserved_0x4c; + u32 setspi_sr; + u32 reserved_0x54; + u32 clrspi_sr; + u32 reserved_0x5c[9]; + u32 igroupr[32]; + u32 isenabler[32]; + u32 icenabler[32]; + u32 ispendr[32]; + u32 icpendr[32]; + u32 isactiver[32]; + u32 icactiver[32]; + union { + u8 bytes[1020]; + u32 words[255]; + } ipriorityr; + u32 _0x7fc; + union { + u8 bytes[1020]; + u32 words[255]; + } itargetsr; + u32 _0xbfc; + u32 icfgr[64]; + u32 igrpmodr[32]; + u32 _0xd80[32]; + u32 nsacr[64]; + u32 sgir; + u32 _0xf04[3]; + u32 cpendsgir[4]; + u32 spendsgir[4]; + u32 reserved_0xf30[52]; + + static constexpr size_t SgirCpuTargetListShift = 16; + + enum SgirTargetListFilter : u32 { + SgirTargetListFilter_CpuTargetList = (0 << 24), + SgirTargetListFilter_Others = (1 << 24), + SgirTargetListFilter_Self = (2 << 24), + SgirTargetListFilter_Reserved = (3 << 24), + }; + }; + static_assert(util::is_pod<GicDistributor>::value); + static_assert(sizeof(GicDistributor) == 0x1000); + + struct GicCpuInterface { + u32 ctlr; + u32 pmr; + u32 bpr; + u32 iar; + u32 eoir; + u32 rpr; + u32 hppir; + u32 abpr; + u32 aiar; + u32 aeoir; + u32 ahppir; + u32 statusr; + u32 reserved_30[4]; + u32 impldef_40[36]; + u32 apr[4]; + u32 nsapr[4]; + u32 reserved_f0[3]; + u32 iidr; + u32 reserved_100[960]; + u32 dir; + u32 _0x1004[1023]; + }; + static_assert(util::is_pod<GicCpuInterface>::value); + static_assert(sizeof(GicCpuInterface) == 0x2000); + + struct KInterruptController { + NON_COPYABLE(KInterruptController); + NON_MOVEABLE(KInterruptController); + public: + static constexpr s32 NumSoftwareInterrupts = 16; + static constexpr s32 NumLocalInterrupts = NumSoftwareInterrupts + 16; + static constexpr s32 NumGlobalInterrupts = 988; + static constexpr s32 NumInterrupts = NumLocalInterrupts + NumGlobalInterrupts; + static constexpr s32 NumPriorityLevels = 4; + public: + struct LocalState { + u32 isenabler[NumLocalInterrupts / 32]; + u32 ipriorityr[NumLocalInterrupts / 4]; + u32 itargetsr[NumLocalInterrupts / 4]; + u32 icfgr[NumLocalInterrupts / 16]; + u32 spendsgir[4]; + }; + static_assert(sizeof(LocalState{}.spendsgir) == sizeof(GicDistributor{}.spendsgir)); + + struct GlobalState { + u32 isenabler[NumGlobalInterrupts / 32]; + u32 ipriorityr[NumGlobalInterrupts / 4]; + u32 itargetsr[NumGlobalInterrupts / 4]; + u32 icfgr[NumGlobalInterrupts / 16]; + }; + + enum PriorityLevel : u8 { + PriorityLevel_High = 0, + PriorityLevel_Low = NumPriorityLevels - 1, + + PriorityLevel_Timer = 1, + PriorityLevel_Scheduler = 2, + }; + private: + static constinit inline u32 s_mask[cpu::NumCores]; + private: + volatile GicDistributor *m_gicd; + volatile GicCpuInterface *m_gicc; + public: + constexpr KInterruptController() : m_gicd(nullptr), m_gicc(nullptr) { /* ... */ } + + void Initialize(s32 core_id); + void Finalize(s32 core_id); + + void SaveCoreLocal(LocalState *state) const; + void SaveGlobal(GlobalState *state) const; + void RestoreCoreLocal(const LocalState *state) const; + void RestoreGlobal(const GlobalState *state) const; + public: + u32 GetIrq() const { + return m_gicc->iar; + } + + static constexpr s32 ConvertRawIrq(u32 irq) { + return (irq == 0x3FF) ? -1 : (irq & 0x3FF); + } + + void Enable(s32 irq) const { + m_gicd->isenabler[irq / BITSIZEOF(u32)] = (1u << (irq % BITSIZEOF(u32))); + } + + void Disable(s32 irq) const { + m_gicd->icenabler[irq / BITSIZEOF(u32)] = (1u << (irq % BITSIZEOF(u32))); + } + + void Clear(s32 irq) const { + m_gicd->icpendr[irq / BITSIZEOF(u32)] = (1u << (irq % BITSIZEOF(u32))); + } + + void SetTarget(s32 irq, s32 core_id) const { + m_gicd->itargetsr.bytes[irq] = m_gicd->itargetsr.bytes[irq] | GetGicMask(core_id); + } + + void ClearTarget(s32 irq, s32 core_id) const { + m_gicd->itargetsr.bytes[irq] = m_gicd->itargetsr.bytes[irq] & ~GetGicMask(core_id); + } + + void SetPriorityLevel(s32 irq, s32 level) const { + MESOSPHERE_ASSERT(PriorityLevel_High <= level && level <= PriorityLevel_Low); + m_gicd->ipriorityr.bytes[irq] = ToGicPriorityValue(level); + } + + s32 GetPriorityLevel(s32 irq) const { + return FromGicPriorityValue(m_gicd->ipriorityr.bytes[irq]); + } + + void SetPriorityLevel(s32 level) const { + MESOSPHERE_ASSERT(PriorityLevel_High <= level && level <= PriorityLevel_Low); + m_gicc->pmr = ToGicPriorityValue(level); + } + + void SetEdge(s32 irq) const { + u32 cfg = m_gicd->icfgr[irq / (BITSIZEOF(u32) / 2)]; + cfg &= ~(0x3 << (2 * (irq % (BITSIZEOF(u32) / 2)))); + cfg |= (0x2 << (2 * (irq % (BITSIZEOF(u32) / 2)))); + m_gicd->icfgr[irq / (BITSIZEOF(u32) / 2)] = cfg; + } + + void SetLevel(s32 irq) const { + u32 cfg = m_gicd->icfgr[irq / (BITSIZEOF(u32) / 2)]; + cfg &= ~(0x3 << (2 * (irq % (BITSIZEOF(u32) / 2)))); + cfg |= (0x0 << (2 * (irq % (BITSIZEOF(u32) / 2)))); + m_gicd->icfgr[irq / (BITSIZEOF(u32) / 2)] = cfg; + } + + void SendInterProcessorInterrupt(s32 irq, u64 core_mask) { + MESOSPHERE_ASSERT(IsSoftware(irq)); + m_gicd->sgir = GetCpuTargetListMask(irq, core_mask); + } + + void SendInterProcessorInterrupt(s32 irq) { + MESOSPHERE_ASSERT(IsSoftware(irq)); + m_gicd->sgir = GicDistributor::SgirTargetListFilter_Others | irq; + } + + void EndOfInterrupt(u32 irq) const { + m_gicc->eoir = irq; + } + + bool IsInterruptDefined(s32 irq) const { + const s32 num_interrupts = std::min(32 + 32 * (m_gicd->typer & 0x1F), static_cast<u32>(NumInterrupts)); + return (0 <= irq && irq < num_interrupts); + } + public: + static constexpr ALWAYS_INLINE bool IsSoftware(s32 id) { + MESOSPHERE_ASSERT(0 <= id && id < NumInterrupts); + return id < NumSoftwareInterrupts; + } + + static constexpr ALWAYS_INLINE bool IsLocal(s32 id) { + MESOSPHERE_ASSERT(0 <= id && id < NumInterrupts); + return id < NumLocalInterrupts; + } + + static constexpr ALWAYS_INLINE bool IsGlobal(s32 id) { + MESOSPHERE_ASSERT(0 <= id && id < NumInterrupts); + return NumLocalInterrupts <= id; + } + + static constexpr size_t GetGlobalInterruptIndex(s32 id) { + MESOSPHERE_ASSERT(IsGlobal(id)); + return id - NumLocalInterrupts; + } + + static constexpr size_t GetLocalInterruptIndex(s32 id) { + MESOSPHERE_ASSERT(IsLocal(id)); + return id; + } + private: + static constexpr size_t PriorityShift = BITSIZEOF(u8) - util::CountTrailingZeros(NumPriorityLevels); + static_assert(PriorityShift < BITSIZEOF(u8)); + static_assert(util::IsPowerOfTwo(NumPriorityLevels)); + + static constexpr ALWAYS_INLINE u8 ToGicPriorityValue(s32 level) { + return (level << PriorityShift) | ((1 << PriorityShift) - 1); + } + + static constexpr ALWAYS_INLINE s32 FromGicPriorityValue(u8 priority) { + return (priority >> PriorityShift) & (NumPriorityLevels - 1); + } + + static constexpr ALWAYS_INLINE s32 GetCpuTargetListMask(s32 irq, u64 core_mask) { + MESOSPHERE_ASSERT(IsSoftware(irq)); + MESOSPHERE_ASSERT(core_mask < (1ul << cpu::NumCores)); + return GicDistributor::SgirTargetListFilter_CpuTargetList | irq | (static_cast<u16>(core_mask) << GicDistributor::SgirCpuTargetListShift); + } + + static ALWAYS_INLINE s32 GetGicMask(s32 core_id) { + return s_mask[core_id]; + } + + ALWAYS_INLINE void SetGicMask(s32 core_id) const { + s_mask[core_id] = m_gicd->itargetsr.bytes[0]; + } + + NOINLINE void SetupInterruptLines(s32 core_id) const; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm/kern_k_interrupt_controller.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm/kern_k_interrupt_controller.hpp new file mode 100644 index 00000000..190bb8c2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm/kern_k_interrupt_controller.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <mesosphere/kern_select_cpu.hpp> +#include <mesosphere/kern_k_typed_address.hpp> + +#if 1 + + #include <mesosphere/arch/arm/kern_generic_interrupt_controller.hpp> + +#else + + #error "Unknown board for KInterruptController" + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm/kern_k_memory_region_device_types.inc b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm/kern_k_memory_region_device_types.inc new file mode 100644 index 00000000..f2be3fae --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm/kern_k_memory_region_device_types.inc @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* All architectures must define NumArchitectureDeviceRegions. */ +constexpr inline const auto NumArchitectureDeviceRegions = 3; + +constexpr inline const auto KMemoryRegionType_Uart = KMemoryRegionType_ArchDeviceBase.DeriveSparse(0, NumArchitectureDeviceRegions, 0); +constexpr inline const auto KMemoryRegionType_InterruptCpuInterface = KMemoryRegionType_ArchDeviceBase.DeriveSparse(0, NumArchitectureDeviceRegions, 1).SetAttribute(KMemoryRegionAttr_NoUserMap); +constexpr inline const auto KMemoryRegionType_InterruptDistributor = KMemoryRegionType_ArchDeviceBase.DeriveSparse(0, NumArchitectureDeviceRegions, 2).SetAttribute(KMemoryRegionAttr_NoUserMap); +static_assert(KMemoryRegionType_Uart .GetValue() == (0x1D)); +static_assert(KMemoryRegionType_InterruptCpuInterface.GetValue() == (0x2D | KMemoryRegionAttr_NoUserMap)); +static_assert(KMemoryRegionType_InterruptDistributor .GetValue() == (0x4D | KMemoryRegionAttr_NoUserMap)); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/init/kern_k_init_arguments.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/init/kern_k_init_arguments.hpp new file mode 100644 index 00000000..b8e3a956 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/init/kern_k_init_arguments.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_select_assembly_offsets.h> + +namespace ams::kern::init { + + struct alignas(util::CeilingPowerOfTwo(INIT_ARGUMENTS_SIZE)) KInitArguments { + u64 cpuactlr; + u64 cpuectlr; + u64 sp; + u64 entrypoint; + u64 argument; + }; + static_assert(alignof(KInitArguments) == util::CeilingPowerOfTwo(INIT_ARGUMENTS_SIZE)); + static_assert(sizeof(KInitArguments) == std::max(INIT_ARGUMENTS_SIZE, util::CeilingPowerOfTwo(INIT_ARGUMENTS_SIZE))); + + static_assert(AMS_OFFSETOF(KInitArguments, cpuactlr) == INIT_ARGUMENTS_CPUACTLR); + static_assert(AMS_OFFSETOF(KInitArguments, cpuectlr) == INIT_ARGUMENTS_CPUECTLR); + static_assert(AMS_OFFSETOF(KInitArguments, sp) == INIT_ARGUMENTS_SP); + static_assert(AMS_OFFSETOF(KInitArguments, entrypoint) == INIT_ARGUMENTS_ENTRYPOINT); + static_assert(AMS_OFFSETOF(KInitArguments, argument) == INIT_ARGUMENTS_ARGUMENT); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/init/kern_k_init_page_table.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/init/kern_k_init_page_table.hpp new file mode 100644 index 00000000..b653da7b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/init/kern_k_init_page_table.hpp @@ -0,0 +1,954 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_typed_address.hpp> +#include <mesosphere/kern_select_cpu.hpp> +#include <mesosphere/arch/arm64/kern_k_page_table_entry.hpp> +#include <mesosphere/kern_select_system_control.hpp> + +namespace ams::kern::arch::arm64::init { + + /* NOTE: Nintendo uses virtual functions, rather than a concept + template. */ + template<typename T> + concept IsInitialPageAllocator = requires (T &t, KPhysicalAddress phys_addr, size_t size) { + { t.Allocate(size) } -> std::same_as<KPhysicalAddress>; + { t.Free(phys_addr, size) } -> std::same_as<void>; + }; + + class KInitialPageTable { + private: + KPhysicalAddress m_l1_tables[2]; + u32 m_num_entries[2]; + public: + template<IsInitialPageAllocator PageAllocator> + KInitialPageTable(KVirtualAddress start_address, KVirtualAddress end_address, PageAllocator &allocator) { + /* Set tables. */ + m_l1_tables[0] = AllocateNewPageTable(allocator, 0); + m_l1_tables[1] = AllocateNewPageTable(allocator, 0); + + /* Set counts. */ + m_num_entries[0] = MaxPageTableEntries; + m_num_entries[1] = ((end_address / L1BlockSize) & (MaxPageTableEntries - 1)) - ((start_address / L1BlockSize) & (MaxPageTableEntries - 1)) + 1; + } + + KInitialPageTable() { + /* Set tables. */ + m_l1_tables[0] = util::AlignDown(cpu::GetTtbr0El1(), PageSize); + m_l1_tables[1] = util::AlignDown(cpu::GetTtbr1El1(), PageSize); + + /* Set counts. */ + cpu::TranslationControlRegisterAccessor tcr; + m_num_entries[0] = tcr.GetT0Size() / L1BlockSize; + m_num_entries[1] = tcr.GetT1Size() / L1BlockSize; + + /* Check counts. */ + MESOSPHERE_INIT_ABORT_UNLESS(0 < m_num_entries[0] && m_num_entries[0] <= MaxPageTableEntries); + MESOSPHERE_INIT_ABORT_UNLESS(0 < m_num_entries[1] && m_num_entries[1] <= MaxPageTableEntries); + } + + constexpr ALWAYS_INLINE uintptr_t GetTtbr0L1TableAddress() const { + return GetInteger(m_l1_tables[0]); + } + + constexpr ALWAYS_INLINE uintptr_t GetTtbr1L1TableAddress() const { + return GetInteger(m_l1_tables[1]); + } + private: + constexpr ALWAYS_INLINE L1PageTableEntry *GetL1Entry(KVirtualAddress address, u64 phys_to_virt_offset = 0) const { + const size_t index = (GetInteger(address) >> (BITSIZEOF(address) - 1)) & 1; + L1PageTableEntry *l1_table = reinterpret_cast<L1PageTableEntry *>(GetInteger(m_l1_tables[index]) + phys_to_virt_offset); + return l1_table + ((GetInteger(address) / L1BlockSize) & (m_num_entries[index] - 1)); + } + + static constexpr ALWAYS_INLINE L2PageTableEntry *GetL2Entry(const L1PageTableEntry *entry, KVirtualAddress address, u64 phys_to_virt_offset = 0) { + L2PageTableEntry *l2_table = reinterpret_cast<L2PageTableEntry *>(GetInteger(entry->GetTable()) + phys_to_virt_offset); + return l2_table + ((GetInteger(address) / L2BlockSize) & (MaxPageTableEntries - 1)); + } + + static constexpr ALWAYS_INLINE L3PageTableEntry *GetL3Entry(const L2PageTableEntry *entry, KVirtualAddress address, u64 phys_to_virt_offset = 0) { + L3PageTableEntry *l3_table = reinterpret_cast<L3PageTableEntry *>(GetInteger(entry->GetTable()) + phys_to_virt_offset); + return l3_table + ((GetInteger(address) / L3BlockSize) & (MaxPageTableEntries - 1)); + } + + template<IsInitialPageAllocator PageAllocator> + static ALWAYS_INLINE KPhysicalAddress AllocateNewPageTable(PageAllocator &allocator, u64 phys_to_virt_offset) { + MESOSPHERE_UNUSED(phys_to_virt_offset); + return allocator.Allocate(PageSize); + } + + static ALWAYS_INLINE void ClearNewPageTable(KPhysicalAddress address, u64 phys_to_virt_offset) { + /* Convert to a deferenceable address, and clear. */ + volatile u64 *ptr = reinterpret_cast<volatile u64 *>(GetInteger(address) + phys_to_virt_offset); + for (size_t i = 0; i < PageSize / sizeof(u64); ++i) { + ptr[i] = 0; + } + } + public: + static consteval size_t GetMaximumOverheadSize(size_t size) { + return (util::DivideUp(size, L1BlockSize) + util::DivideUp(size, L2BlockSize)) * PageSize; + } + private: + size_t NOINLINE GetBlockCount(KVirtualAddress virt_addr, size_t size, size_t block_size) { + const KVirtualAddress end_virt_addr = virt_addr + size; + size_t count = 0; + while (virt_addr < end_virt_addr) { + L1PageTableEntry *l1_entry = this->GetL1Entry(virt_addr); + + /* If an L1 block is mapped or we're empty, advance by L1BlockSize. */ + if (l1_entry->IsMappedBlock() || l1_entry->IsMappedEmpty()) { + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(virt_addr), L1BlockSize)); + MESOSPHERE_INIT_ABORT_UNLESS(static_cast<size_t>(end_virt_addr - virt_addr) >= L1BlockSize); + virt_addr += L1BlockSize; + if (l1_entry->IsMappedBlock() && block_size == L1BlockSize) { + count++; + } + continue; + } + + /* Non empty and non-block must be table. */ + MESOSPHERE_INIT_ABORT_UNLESS(l1_entry->IsMappedTable()); + + /* Table, so check if we're mapped in L2. */ + L2PageTableEntry *l2_entry = GetL2Entry(l1_entry, virt_addr); + + if (l2_entry->IsMappedBlock() || l2_entry->IsMappedEmpty()) { + const size_t advance_size = (l2_entry->IsMappedBlock() && l2_entry->IsContiguous()) ? L2ContiguousBlockSize : L2BlockSize; + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(virt_addr), advance_size)); + MESOSPHERE_INIT_ABORT_UNLESS(static_cast<size_t>(end_virt_addr - virt_addr) >= advance_size); + virt_addr += advance_size; + if (l2_entry->IsMappedBlock() && block_size == advance_size) { + count++; + } + continue; + } + + /* Non empty and non-block must be table. */ + MESOSPHERE_INIT_ABORT_UNLESS(l2_entry->IsMappedTable()); + + /* Table, so check if we're mapped in L3. */ + L3PageTableEntry *l3_entry = GetL3Entry(l2_entry, virt_addr); + + /* L3 must be block or empty. */ + MESOSPHERE_INIT_ABORT_UNLESS(l3_entry->IsMappedBlock() || l3_entry->IsMappedEmpty()); + + const size_t advance_size = (l3_entry->IsMappedBlock() && l3_entry->IsContiguous()) ? L3ContiguousBlockSize : L3BlockSize; + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(virt_addr), advance_size)); + MESOSPHERE_INIT_ABORT_UNLESS(static_cast<size_t>(end_virt_addr - virt_addr) >= advance_size); + virt_addr += advance_size; + if (l3_entry->IsMappedBlock() && block_size == advance_size) { + count++; + } + } + return count; + } + + KVirtualAddress NOINLINE GetBlockByIndex(KVirtualAddress virt_addr, size_t size, size_t block_size, size_t index) { + const KVirtualAddress end_virt_addr = virt_addr + size; + size_t count = 0; + while (virt_addr < end_virt_addr) { + L1PageTableEntry *l1_entry = this->GetL1Entry(virt_addr); + + /* If an L1 block is mapped or we're empty, advance by L1BlockSize. */ + if (l1_entry->IsMappedBlock() || l1_entry->IsMappedEmpty()) { + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(virt_addr), L1BlockSize)); + MESOSPHERE_INIT_ABORT_UNLESS(static_cast<size_t>(end_virt_addr - virt_addr) >= L1BlockSize); + if (l1_entry->IsMappedBlock() && block_size == L1BlockSize) { + if ((count++) == index) { + return virt_addr; + } + } + virt_addr += L1BlockSize; + continue; + } + + /* Non empty and non-block must be table. */ + MESOSPHERE_INIT_ABORT_UNLESS(l1_entry->IsMappedTable()); + + /* Table, so check if we're mapped in L2. */ + L2PageTableEntry *l2_entry = GetL2Entry(l1_entry, virt_addr); + + if (l2_entry->IsMappedBlock() || l2_entry->IsMappedEmpty()) { + const size_t advance_size = (l2_entry->IsMappedBlock() && l2_entry->IsContiguous()) ? L2ContiguousBlockSize : L2BlockSize; + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(virt_addr), advance_size)); + MESOSPHERE_INIT_ABORT_UNLESS(static_cast<size_t>(end_virt_addr - virt_addr) >= advance_size); + if (l2_entry->IsMappedBlock() && block_size == advance_size) { + if ((count++) == index) { + return virt_addr; + } + } + virt_addr += advance_size; + continue; + } + + /* Non empty and non-block must be table. */ + MESOSPHERE_INIT_ABORT_UNLESS(l2_entry->IsMappedTable()); + + /* Table, so check if we're mapped in L3. */ + L3PageTableEntry *l3_entry = GetL3Entry(l2_entry, virt_addr); + + /* L3 must be block or empty. */ + MESOSPHERE_INIT_ABORT_UNLESS(l3_entry->IsMappedBlock() || l3_entry->IsMappedEmpty()); + + const size_t advance_size = (l3_entry->IsMappedBlock() && l3_entry->IsContiguous()) ? L3ContiguousBlockSize : L3BlockSize; + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(virt_addr), advance_size)); + MESOSPHERE_INIT_ABORT_UNLESS(static_cast<size_t>(end_virt_addr - virt_addr) >= advance_size); + if (l3_entry->IsMappedBlock() && block_size == advance_size) { + if ((count++) == index) { + return virt_addr; + } + } + virt_addr += advance_size; + } + return Null<KVirtualAddress>; + } + + PageTableEntry *GetMappingEntry(KVirtualAddress virt_addr, size_t block_size) { + L1PageTableEntry *l1_entry = this->GetL1Entry(virt_addr); + + if (l1_entry->IsMappedBlock()) { + MESOSPHERE_INIT_ABORT_UNLESS(block_size == L1BlockSize); + return l1_entry; + } + + MESOSPHERE_INIT_ABORT_UNLESS(l1_entry->IsMappedTable()); + + /* Table, so check if we're mapped in L2. */ + L2PageTableEntry *l2_entry = GetL2Entry(l1_entry, virt_addr); + + if (l2_entry->IsMappedBlock()) { + const size_t real_size = (l2_entry->IsContiguous()) ? L2ContiguousBlockSize : L2BlockSize; + MESOSPHERE_INIT_ABORT_UNLESS(real_size == block_size); + return l2_entry; + } + + MESOSPHERE_INIT_ABORT_UNLESS(l2_entry->IsMappedTable()); + + /* Table, so check if we're mapped in L3. */ + L3PageTableEntry *l3_entry = GetL3Entry(l2_entry, virt_addr); + + /* L3 must be block. */ + MESOSPHERE_INIT_ABORT_UNLESS(l3_entry->IsMappedBlock()); + + const size_t real_size = (l3_entry->IsContiguous()) ? L3ContiguousBlockSize : L3BlockSize; + MESOSPHERE_INIT_ABORT_UNLESS(real_size == block_size); + return l3_entry; + } + + void NOINLINE SwapBlocks(KVirtualAddress src_virt_addr, KVirtualAddress dst_virt_addr, size_t block_size, bool do_copy) { + static_assert(L2ContiguousBlockSize / L2BlockSize == L3ContiguousBlockSize / L3BlockSize); + const bool contig = (block_size == L2ContiguousBlockSize || block_size == L3ContiguousBlockSize); + const size_t num_mappings = contig ? L2ContiguousBlockSize / L2BlockSize : 1; + + /* Unmap the source. */ + PageTableEntry *src_entry = this->GetMappingEntry(src_virt_addr, block_size); + const auto src_saved = *src_entry; + for (size_t i = 0; i < num_mappings; i++) { + src_entry[i] = InvalidPageTableEntry; + } + + /* Unmap the target. */ + PageTableEntry *dst_entry = this->GetMappingEntry(dst_virt_addr, block_size); + const auto dst_saved = *dst_entry; + for (size_t i = 0; i < num_mappings; i++) { + dst_entry[i] = InvalidPageTableEntry; + } + + /* Invalidate the entire tlb. */ + cpu::DataSynchronizationBarrierInnerShareable(); + cpu::InvalidateEntireTlb(); + + /* Copy data, if we should. */ + const u64 negative_block_size_for_mask = static_cast<u64>(-static_cast<s64>(block_size)); + const u64 offset_mask = negative_block_size_for_mask & ((1ul << 48) - 1); + const KVirtualAddress copy_src_addr = KVirtualAddress(src_saved.GetRawAttributesUnsafeForSwap() & offset_mask); + const KVirtualAddress copy_dst_addr = KVirtualAddress(dst_saved.GetRawAttributesUnsafeForSwap() & offset_mask); + if (do_copy) { + u8 tmp[0x100]; + for (size_t ofs = 0; ofs < block_size; ofs += sizeof(tmp)) { + std::memcpy(tmp, GetVoidPointer(copy_src_addr + ofs), sizeof(tmp)); + std::memcpy(GetVoidPointer(copy_src_addr + ofs), GetVoidPointer(copy_dst_addr + ofs), sizeof(tmp)); + std::memcpy(GetVoidPointer(copy_dst_addr + ofs), tmp, sizeof(tmp)); + } + cpu::DataSynchronizationBarrierInnerShareable(); + } + + /* Swap the mappings. */ + const u64 attr_preserve_mask = (block_size - 1) | 0xFFFF000000000000ul; + const size_t shift_for_contig = contig ? 4 : 0; + size_t advanced_size = 0; + const u64 src_attr_val = src_saved.GetRawAttributesUnsafeForSwap() & attr_preserve_mask; + const u64 dst_attr_val = dst_saved.GetRawAttributesUnsafeForSwap() & attr_preserve_mask; + for (size_t i = 0; i < num_mappings; i++) { + reinterpret_cast<u64 *>(src_entry)[i] = GetInteger(copy_dst_addr + (advanced_size >> shift_for_contig)) | src_attr_val; + reinterpret_cast<u64 *>(dst_entry)[i] = GetInteger(copy_src_addr + (advanced_size >> shift_for_contig)) | dst_attr_val; + advanced_size += block_size; + } + + cpu::DataSynchronizationBarrierInnerShareable(); + } + + void NOINLINE PhysicallyRandomize(KVirtualAddress virt_addr, size_t size, size_t block_size, bool do_copy) { + const size_t block_count = this->GetBlockCount(virt_addr, size, block_size); + if (block_count > 1) { + for (size_t cur_block = 0; cur_block < block_count; cur_block++) { + const size_t target_block = KSystemControl::Init::GenerateRandomRange(cur_block, block_count - 1); + if (cur_block != target_block) { + const KVirtualAddress cur_virt_addr = this->GetBlockByIndex(virt_addr, size, block_size, cur_block); + const KVirtualAddress target_virt_addr = this->GetBlockByIndex(virt_addr, size, block_size, target_block); + MESOSPHERE_INIT_ABORT_UNLESS(cur_virt_addr != Null<KVirtualAddress>); + MESOSPHERE_INIT_ABORT_UNLESS(target_virt_addr != Null<KVirtualAddress>); + this->SwapBlocks(cur_virt_addr, target_virt_addr, block_size, do_copy); + } + } + } + } + public: + template<IsInitialPageAllocator PageAllocator> + void NOINLINE Map(KVirtualAddress virt_addr, size_t size, KPhysicalAddress phys_addr, const PageTableEntry &attr, PageAllocator &allocator, u64 phys_to_virt_offset) { + /* Ensure that addresses and sizes are page aligned. */ + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(virt_addr), PageSize)); + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(phys_addr), PageSize)); + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(size, PageSize)); + + /* Iteratively map pages until the requested region is mapped. */ + while (size > 0) { + L1PageTableEntry *l1_entry = this->GetL1Entry(virt_addr, phys_to_virt_offset); + + /* Can we make an L1 block? */ + if (util::IsAligned(GetInteger(virt_addr), L1BlockSize) && util::IsAligned(GetInteger(phys_addr), L1BlockSize) && size >= L1BlockSize) { + *l1_entry = L1PageTableEntry(PageTableEntry::BlockTag{}, phys_addr, attr, PageTableEntry::SoftwareReservedBit_None, false); + + virt_addr += L1BlockSize; + phys_addr += L1BlockSize; + size -= L1BlockSize; + continue; + } + + /* If we don't already have an L2 table, we need to make a new one. */ + if (!l1_entry->IsMappedTable()) { + KPhysicalAddress new_table = AllocateNewPageTable(allocator, phys_to_virt_offset); + cpu::DataSynchronizationBarrierInnerShareable(); + *l1_entry = L1PageTableEntry(PageTableEntry::TableTag{}, new_table, attr.IsPrivilegedExecuteNever()); + } + + L2PageTableEntry *l2_entry = GetL2Entry(l1_entry, virt_addr, phys_to_virt_offset); + + /* Can we make a contiguous L2 block? */ + if (util::IsAligned(GetInteger(virt_addr), L2ContiguousBlockSize) && util::IsAligned(GetInteger(phys_addr), L2ContiguousBlockSize) && size >= L2ContiguousBlockSize) { + for (size_t i = 0; i < L2ContiguousBlockSize / L2BlockSize; i++) { + l2_entry[i] = L2PageTableEntry(PageTableEntry::BlockTag{}, phys_addr, attr, PageTableEntry::SoftwareReservedBit_None, true); + + virt_addr += L2BlockSize; + phys_addr += L2BlockSize; + size -= L2BlockSize; + } + continue; + } + + /* Can we make an L2 block? */ + if (util::IsAligned(GetInteger(virt_addr), L2BlockSize) && util::IsAligned(GetInteger(phys_addr), L2BlockSize) && size >= L2BlockSize) { + *l2_entry = L2PageTableEntry(PageTableEntry::BlockTag{}, phys_addr, attr, PageTableEntry::SoftwareReservedBit_None, false); + + virt_addr += L2BlockSize; + phys_addr += L2BlockSize; + size -= L2BlockSize; + continue; + } + + /* If we don't already have an L3 table, we need to make a new one. */ + if (!l2_entry->IsMappedTable()) { + KPhysicalAddress new_table = AllocateNewPageTable(allocator, phys_to_virt_offset); + cpu::DataSynchronizationBarrierInnerShareable(); + *l2_entry = L2PageTableEntry(PageTableEntry::TableTag{}, new_table, attr.IsPrivilegedExecuteNever()); + } + + L3PageTableEntry *l3_entry = GetL3Entry(l2_entry, virt_addr, phys_to_virt_offset); + + /* Can we make a contiguous L3 block? */ + if (util::IsAligned(GetInteger(virt_addr), L3ContiguousBlockSize) && util::IsAligned(GetInteger(phys_addr), L3ContiguousBlockSize) && size >= L3ContiguousBlockSize) { + for (size_t i = 0; i < L3ContiguousBlockSize / L3BlockSize; i++) { + l3_entry[i] = L3PageTableEntry(PageTableEntry::BlockTag{}, phys_addr, attr, PageTableEntry::SoftwareReservedBit_None, true); + + virt_addr += L3BlockSize; + phys_addr += L3BlockSize; + size -= L3BlockSize; + } + continue; + } + + /* Make an L3 block. */ + *l3_entry = L3PageTableEntry(PageTableEntry::BlockTag{}, phys_addr, attr, PageTableEntry::SoftwareReservedBit_None, false); + virt_addr += L3BlockSize; + phys_addr += L3BlockSize; + size -= L3BlockSize; + } + + /* Ensure data consistency after our mapping is added. */ + cpu::DataSynchronizationBarrierInnerShareable(); + } + + void UnmapTtbr0Entries(u64 phys_to_virt_offset) { + /* Ensure data consistency before we unmap. */ + cpu::DataSynchronizationBarrierInnerShareable(); + + /* Define helper, as we only want to clear non-nGnRE pages. */ + constexpr auto ShouldUnmap = [](const PageTableEntry *entry) ALWAYS_INLINE_LAMBDA -> bool { + return entry->GetPageAttribute() != PageTableEntry::PageAttribute_Device_nGnRE; + }; + + /* Iterate all L1 entries. */ + L1PageTableEntry * const l1_table = reinterpret_cast<L1PageTableEntry *>(GetInteger(m_l1_tables[0]) + phys_to_virt_offset); + for (size_t l1_index = 0; l1_index < m_num_entries[0]; l1_index++) { + /* Get L1 entry. */ + L1PageTableEntry * const l1_entry = l1_table + l1_index; + if (l1_entry->IsMappedBlock()) { + /* Unmap the L1 entry, if we should. */ + if (ShouldUnmap(l1_entry)) { + *static_cast<PageTableEntry *>(l1_entry) = InvalidPageTableEntry; + } + } else if (l1_entry->IsMappedTable()) { + /* Get the L2 table. */ + L2PageTableEntry * const l2_table = reinterpret_cast<L2PageTableEntry *>(GetInteger(l1_entry->GetTable()) + phys_to_virt_offset); + + /* Unmap all L2 entries, as relevant. */ + size_t remaining_l2_entries = 0; + for (size_t l2_index = 0; l2_index < MaxPageTableEntries; ++l2_index) { + /* Get L2 entry. */ + L2PageTableEntry * const l2_entry = l2_table + l2_index; + if (l2_entry->IsMappedBlock()) { + const size_t num_to_clear = (l2_entry->IsContiguous() ? L2ContiguousBlockSize : L2BlockSize) / L2BlockSize; + + if (ShouldUnmap(l2_entry)) { + for (size_t i = 0; i < num_to_clear; ++i) { + static_cast<PageTableEntry *>(l2_entry)[i] = InvalidPageTableEntry; + } + } else { + remaining_l2_entries += num_to_clear; + } + + l2_index = l2_index + num_to_clear - 1; + } else if (l2_entry->IsMappedTable()) { + /* Get the L3 table. */ + L3PageTableEntry * const l3_table = reinterpret_cast<L3PageTableEntry *>(GetInteger(l2_entry->GetTable()) + phys_to_virt_offset); + + /* Unmap all L3 entries, as relevant. */ + size_t remaining_l3_entries = 0; + for (size_t l3_index = 0; l3_index < MaxPageTableEntries; ++l3_index) { + /* Get L3 entry. */ + if (L3PageTableEntry * const l3_entry = l3_table + l3_index; l3_entry->IsMappedBlock()) { + const size_t num_to_clear = (l3_entry->IsContiguous() ? L3ContiguousBlockSize : L3BlockSize) / L3BlockSize; + + if (ShouldUnmap(l3_entry)) { + for (size_t i = 0; i < num_to_clear; ++i) { + static_cast<PageTableEntry *>(l3_entry)[i] = InvalidPageTableEntry; + } + } else { + remaining_l3_entries += num_to_clear; + } + + l3_index = l3_index + num_to_clear - 1; + } + } + + /* If we unmapped all L3 entries, clear the L2 entry. */ + if (remaining_l3_entries == 0) { + *static_cast<PageTableEntry *>(l2_entry) = InvalidPageTableEntry; + + /* Invalidate the entire tlb. */ + cpu::DataSynchronizationBarrierInnerShareable(); + cpu::InvalidateEntireTlb(); + } else { + remaining_l2_entries++; + } + } + } + + /* If we unmapped all L2 entries, clear the L1 entry. */ + if (remaining_l2_entries == 0) { + *static_cast<PageTableEntry *>(l1_entry) = InvalidPageTableEntry; + + /* Invalidate the entire tlb. */ + cpu::DataSynchronizationBarrierInnerShareable(); + cpu::InvalidateEntireTlb(); + } + } + } + + /* Invalidate the entire tlb. */ + cpu::DataSynchronizationBarrierInnerShareable(); + cpu::InvalidateEntireTlb(); + } + + KPhysicalAddress GetPhysicalAddress(KVirtualAddress virt_addr) const { + /* Get the L1 entry. */ + const L1PageTableEntry *l1_entry = this->GetL1Entry(virt_addr); + + if (l1_entry->IsMappedBlock()) { + return l1_entry->GetBlock() + (GetInteger(virt_addr) & (L1BlockSize - 1)); + } + + MESOSPHERE_INIT_ABORT_UNLESS(l1_entry->IsMappedTable()); + + /* Get the L2 entry. */ + const L2PageTableEntry *l2_entry = GetL2Entry(l1_entry, virt_addr); + + if (l2_entry->IsMappedBlock()) { + return l2_entry->GetBlock() + (GetInteger(virt_addr) & (L2BlockSize - 1)); + } + + MESOSPHERE_INIT_ABORT_UNLESS(l2_entry->IsMappedTable()); + + /* Get the L3 entry. */ + const L3PageTableEntry *l3_entry = GetL3Entry(l2_entry, virt_addr); + + MESOSPHERE_INIT_ABORT_UNLESS(l3_entry->IsMappedBlock()); + + return l3_entry->GetBlock() + (GetInteger(virt_addr) & (L3BlockSize - 1)); + } + + KPhysicalAddress GetPhysicalAddressOfRandomizedRange(KVirtualAddress virt_addr, size_t size) const { + /* Define tracking variables for ourselves to use. */ + KPhysicalAddress min_phys_addr = Null<KPhysicalAddress>; + KPhysicalAddress max_phys_addr = Null<KPhysicalAddress>; + + /* Ensure the range we're querying is valid. */ + const KVirtualAddress end_virt_addr = virt_addr + size; + if (virt_addr > end_virt_addr) { + MESOSPHERE_INIT_ABORT_UNLESS(size == 0); + return min_phys_addr; + } + + auto UpdateExtents = [&](const KPhysicalAddress block, size_t block_size) ALWAYS_INLINE_LAMBDA { + /* Ensure that we are allowed to have the block here. */ + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(virt_addr), block_size)); + MESOSPHERE_INIT_ABORT_UNLESS(block_size <= GetInteger(end_virt_addr) - GetInteger(virt_addr)); + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(block), block_size)); + MESOSPHERE_INIT_ABORT_UNLESS(size >= block_size); + + const KPhysicalAddress block_end = block + block_size; + + /* We want to update min phys addr when it's 0 or > block. */ + /* This is equivalent in two's complement to (n - 1) >= block. */ + if ((GetInteger(min_phys_addr) - 1) >= GetInteger(block)) { + min_phys_addr = block; + } + + /* Update max phys addr when it's 0 or < block_end. */ + if (GetInteger(max_phys_addr) < GetInteger(block_end) || GetInteger(max_phys_addr) == 0) { + max_phys_addr = block_end; + } + + /* Traverse onwards. */ + virt_addr += block_size; + }; + + while (virt_addr < end_virt_addr) { + L1PageTableEntry *l1_entry = this->GetL1Entry(virt_addr); + + /* If an L1 block is mapped, update. */ + if (l1_entry->IsMappedBlock()) { + UpdateExtents(l1_entry->GetBlock(), L1BlockSize); + continue; + } + + /* Not a block, so we must have a table. */ + MESOSPHERE_INIT_ABORT_UNLESS(l1_entry->IsMappedTable()); + + L2PageTableEntry *l2_entry = GetL2Entry(l1_entry, virt_addr); + if (l2_entry->IsMappedBlock()) { + UpdateExtents(l2_entry->GetBlock(), l2_entry->IsContiguous() ? L2ContiguousBlockSize : L2BlockSize); + continue; + } + + /* Not a block, so we must have a table. */ + MESOSPHERE_INIT_ABORT_UNLESS(l2_entry->IsMappedTable()); + + /* We must have a mapped l3 entry to inspect. */ + L3PageTableEntry *l3_entry = GetL3Entry(l2_entry, virt_addr); + MESOSPHERE_INIT_ABORT_UNLESS(l3_entry->IsMappedBlock()); + + UpdateExtents(l3_entry->GetBlock(), l3_entry->IsContiguous() ? L3ContiguousBlockSize : L3BlockSize); + } + + /* Ensure we got the right range. */ + MESOSPHERE_INIT_ABORT_UNLESS(GetInteger(max_phys_addr) - GetInteger(min_phys_addr) == size); + + /* Write the address that we found. */ + return min_phys_addr; + } + + bool IsFree(KVirtualAddress virt_addr, size_t size) { + /* Ensure that addresses and sizes are page aligned. */ + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(virt_addr), PageSize)); + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(size, PageSize)); + + const KVirtualAddress end_virt_addr = virt_addr + size; + while (virt_addr < end_virt_addr) { + L1PageTableEntry *l1_entry = this->GetL1Entry(virt_addr); + + /* If an L1 block is mapped, the address isn't free. */ + if (l1_entry->IsMappedBlock()) { + return false; + } + + if (!l1_entry->IsMappedTable()) { + /* Not a table, so just move to check the next region. */ + virt_addr = util::AlignDown(GetInteger(virt_addr) + L1BlockSize, L1BlockSize); + continue; + } + + /* Table, so check if we're mapped in L2. */ + L2PageTableEntry *l2_entry = GetL2Entry(l1_entry, virt_addr); + + if (l2_entry->IsMappedBlock()) { + return false; + } + + if (!l2_entry->IsMappedTable()) { + /* Not a table, so just move to check the next region. */ + virt_addr = util::AlignDown(GetInteger(virt_addr) + L2BlockSize, L2BlockSize); + continue; + } + + /* Table, so check if we're mapped in L3. */ + L3PageTableEntry *l3_entry = GetL3Entry(l2_entry, virt_addr); + + if (l3_entry->IsMappedBlock()) { + return false; + } + + /* Not a block, so move on to check the next page. */ + virt_addr = util::AlignDown(GetInteger(virt_addr) + L3BlockSize, L3BlockSize); + } + return true; + } + + void Reprotect(KVirtualAddress virt_addr, size_t size, const PageTableEntry &attr_before, const PageTableEntry &attr_after) { + /* Ensure that addresses and sizes are page aligned. */ + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(virt_addr), PageSize)); + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(size, PageSize)); + + /* Iteratively reprotect pages until the requested region is reprotected. */ + while (size > 0) { + L1PageTableEntry *l1_entry = this->GetL1Entry(virt_addr); + + /* Check if an L1 block is present. */ + if (l1_entry->IsMappedBlock()) { + /* Ensure that we are allowed to have an L1 block here. */ + const KPhysicalAddress block = l1_entry->GetBlock(); + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(virt_addr), L1BlockSize)); + MESOSPHERE_INIT_ABORT_UNLESS(size >= L1BlockSize); + MESOSPHERE_INIT_ABORT_UNLESS(l1_entry->IsCompatibleWithAttribute(attr_before, PageTableEntry::SoftwareReservedBit_None, false)); + + /* Invalidate the existing L1 block. */ + *static_cast<PageTableEntry *>(l1_entry) = InvalidPageTableEntry; + cpu::DataSynchronizationBarrierInnerShareable(); + cpu::InvalidateEntireTlb(); + + /* Create new L1 block. */ + *l1_entry = L1PageTableEntry(PageTableEntry::BlockTag{}, block, attr_after, PageTableEntry::SoftwareReservedBit_None, false); + + virt_addr += L1BlockSize; + size -= L1BlockSize; + continue; + } + + /* Not a block, so we must be a table. */ + MESOSPHERE_INIT_ABORT_UNLESS(l1_entry->IsMappedTable()); + + L2PageTableEntry *l2_entry = GetL2Entry(l1_entry, virt_addr); + if (l2_entry->IsMappedBlock()) { + const KPhysicalAddress block = l2_entry->GetBlock(); + + if (l2_entry->IsContiguous()) { + /* Ensure that we are allowed to have a contiguous L2 block here. */ + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(virt_addr), L2ContiguousBlockSize)); + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(block), L2ContiguousBlockSize)); + MESOSPHERE_INIT_ABORT_UNLESS(size >= L2ContiguousBlockSize); + + /* Invalidate the existing contiguous L2 block. */ + for (size_t i = 0; i < L2ContiguousBlockSize / L2BlockSize; i++) { + /* Ensure that the entry is valid. */ + MESOSPHERE_INIT_ABORT_UNLESS(l2_entry[i].IsCompatibleWithAttribute(attr_before, PageTableEntry::SoftwareReservedBit_None, true)); + static_cast<PageTableEntry *>(l2_entry)[i] = InvalidPageTableEntry; + } + cpu::DataSynchronizationBarrierInnerShareable(); + cpu::InvalidateEntireTlb(); + + /* Create a new contiguous L2 block. */ + for (size_t i = 0; i < L2ContiguousBlockSize / L2BlockSize; i++) { + l2_entry[i] = L2PageTableEntry(PageTableEntry::BlockTag{}, block + L2BlockSize * i, attr_after, PageTableEntry::SoftwareReservedBit_None, true); + } + + virt_addr += L2ContiguousBlockSize; + size -= L2ContiguousBlockSize; + } else { + /* Ensure that we are allowed to have an L2 block here. */ + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(virt_addr), L2BlockSize)); + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(block), L2BlockSize)); + MESOSPHERE_INIT_ABORT_UNLESS(size >= L2BlockSize); + MESOSPHERE_INIT_ABORT_UNLESS(l2_entry->IsCompatibleWithAttribute(attr_before, PageTableEntry::SoftwareReservedBit_None, false)); + + /* Invalidate the existing L2 block. */ + *static_cast<PageTableEntry *>(l2_entry) = InvalidPageTableEntry; + cpu::DataSynchronizationBarrierInnerShareable(); + cpu::InvalidateEntireTlb(); + + /* Create new L2 block. */ + *l2_entry = L2PageTableEntry(PageTableEntry::BlockTag{}, block, attr_after, PageTableEntry::SoftwareReservedBit_None, false); + + virt_addr += L2BlockSize; + size -= L2BlockSize; + } + + continue; + } + + /* Not a block, so we must be a table. */ + MESOSPHERE_INIT_ABORT_UNLESS(l2_entry->IsMappedTable()); + + /* We must have a mapped l3 entry to reprotect. */ + L3PageTableEntry *l3_entry = GetL3Entry(l2_entry, virt_addr); + MESOSPHERE_INIT_ABORT_UNLESS(l3_entry->IsMappedBlock()); + const KPhysicalAddress block = l3_entry->GetBlock(); + + if (l3_entry->IsContiguous()) { + /* Ensure that we are allowed to have a contiguous L3 block here. */ + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(virt_addr), L3ContiguousBlockSize)); + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(block), L3ContiguousBlockSize)); + MESOSPHERE_INIT_ABORT_UNLESS(size >= L3ContiguousBlockSize); + + /* Invalidate the existing contiguous L3 block. */ + for (size_t i = 0; i < L3ContiguousBlockSize / L3BlockSize; i++) { + /* Ensure that the entry is valid. */ + MESOSPHERE_INIT_ABORT_UNLESS(l3_entry[i].IsCompatibleWithAttribute(attr_before, PageTableEntry::SoftwareReservedBit_None, true)); + static_cast<PageTableEntry *>(l3_entry)[i] = InvalidPageTableEntry; + } + cpu::DataSynchronizationBarrierInnerShareable(); + cpu::InvalidateEntireTlb(); + + /* Create a new contiguous L3 block. */ + for (size_t i = 0; i < L3ContiguousBlockSize / L3BlockSize; i++) { + l3_entry[i] = L3PageTableEntry(PageTableEntry::BlockTag{}, block + L3BlockSize * i, attr_after, PageTableEntry::SoftwareReservedBit_None, true); + } + + virt_addr += L3ContiguousBlockSize; + size -= L3ContiguousBlockSize; + } else { + /* Ensure that we are allowed to have an L3 block here. */ + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(virt_addr), L3BlockSize)); + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(GetInteger(block), L3BlockSize)); + MESOSPHERE_INIT_ABORT_UNLESS(size >= L3BlockSize); + MESOSPHERE_INIT_ABORT_UNLESS(l3_entry->IsCompatibleWithAttribute(attr_before, PageTableEntry::SoftwareReservedBit_None, false)); + + /* Invalidate the existing L3 block. */ + *static_cast<PageTableEntry *>(l3_entry) = InvalidPageTableEntry; + cpu::DataSynchronizationBarrierInnerShareable(); + cpu::InvalidateEntireTlb(); + + /* Create new L3 block. */ + *l3_entry = L3PageTableEntry(PageTableEntry::BlockTag{}, block, attr_after, PageTableEntry::SoftwareReservedBit_None, false); + + virt_addr += L3BlockSize; + size -= L3BlockSize; + } + } + + /* Ensure data consistency after we complete reprotection. */ + cpu::DataSynchronizationBarrierInnerShareable(); + } + + void PhysicallyRandomize(KVirtualAddress virt_addr, size_t size, bool do_copy) { + this->PhysicallyRandomize(virt_addr, size, L1BlockSize, do_copy); + this->PhysicallyRandomize(virt_addr, size, L2ContiguousBlockSize, do_copy); + this->PhysicallyRandomize(virt_addr, size, L2BlockSize, do_copy); + this->PhysicallyRandomize(virt_addr, size, L3ContiguousBlockSize, do_copy); + this->PhysicallyRandomize(virt_addr, size, L3BlockSize, do_copy); + cpu::StoreCacheForInit(GetVoidPointer(virt_addr), size); + } + }; + + class KInitialPageAllocator final { + private: + static constexpr inline size_t FreeUnitSize = BITSIZEOF(u64) * PageSize; + struct FreeListEntry { + FreeListEntry *next; + size_t size; + }; + public: + struct State { + uintptr_t start_address; + uintptr_t end_address; + FreeListEntry *free_head; + }; + private: + State m_state; + public: + constexpr ALWAYS_INLINE KInitialPageAllocator() : m_state{} { /* ... */ } + + ALWAYS_INLINE void Initialize(uintptr_t address) { + m_state.start_address = address; + m_state.end_address = address; + } + + ALWAYS_INLINE void InitializeFromState(const State *state) { + m_state = *state; + } + + ALWAYS_INLINE void GetFinalState(State *out) { + *out = m_state; + m_state = {}; + } + private: + bool CanAllocate(size_t align, size_t size) const { + for (auto *cur = m_state.free_head; cur != nullptr; cur = cur->next) { + const uintptr_t cur_last = reinterpret_cast<uintptr_t>(cur) + cur->size - 1; + const uintptr_t alloc_last = util::AlignUp(reinterpret_cast<uintptr_t>(cur), align) + size - 1; + if (alloc_last <= cur_last) { + return true; + } + } + return false; + } + + bool TryAllocate(uintptr_t address, size_t size) { + /* Try to allocate the region. */ + auto **prev_next = std::addressof(m_state.free_head); + for (auto *cur = m_state.free_head; cur != nullptr; prev_next = std::addressof(cur->next), cur = cur->next) { + const uintptr_t cur_start = reinterpret_cast<uintptr_t>(cur); + const uintptr_t cur_last = cur_start + cur->size - 1; + if (cur_start <= address && address + size - 1 <= cur_last) { + auto *alloc = reinterpret_cast<FreeListEntry *>(address); + + /* Perform fragmentation at front. */ + if (cur != alloc) { + prev_next = std::addressof(cur->next); + *alloc = { + .next = cur->next, + .size = cur_start + cur->size - address, + }; + *cur = { + .next = alloc, + .size = address - cur_start, + }; + } + + /* Perform fragmentation at tail. */ + if (alloc->size != size) { + auto *next = reinterpret_cast<FreeListEntry *>(address + size); + *next = { + .next = alloc->next, + .size = alloc->size - size, + }; + *alloc = { + .next = next, + .size = size, + }; + } + + *prev_next = alloc->next; + return true; + } + } + + return false; + } + public: + KPhysicalAddress Allocate(size_t align, size_t size) { + /* Ensure that the free list is non-empty. */ + while (!this->CanAllocate(align, size)) { + this->Free(m_state.end_address, FreeUnitSize); + m_state.end_address += FreeUnitSize; + } + + /* Allocate a random address. */ + const uintptr_t aligned_start = util::AlignUp(m_state.start_address, align); + const uintptr_t aligned_end = util::AlignDown(m_state.end_address, align); + const size_t ind_max = ((aligned_end - aligned_start) / align) - 1; + while (true) { + if (const uintptr_t random_address = aligned_start + (KSystemControl::Init::GenerateRandomRange(0, ind_max) * align); this->TryAllocate(random_address, size)) { + /* Clear the allocated pages. */ + volatile u64 *ptr = reinterpret_cast<volatile u64 *>(random_address); + for (size_t i = 0; i < size / sizeof(u64); ++i) { + ptr[i] = 0; + } + + return random_address; + } + } + } + + KPhysicalAddress Allocate(size_t size) { + return this->Allocate(size, size); + } + + void Free(KPhysicalAddress phys_addr, size_t size) { + auto **prev_next = std::addressof(m_state.free_head); + auto *new_chunk = reinterpret_cast<FreeListEntry *>(GetInteger(phys_addr)); + if (auto *cur = m_state.free_head; cur != nullptr) { + const uintptr_t new_start = reinterpret_cast<uintptr_t>(new_chunk); + const uintptr_t new_end = GetInteger(phys_addr) + size; + while (true) { + /* Attempt coalescing. */ + const uintptr_t cur_start = reinterpret_cast<uintptr_t>(cur); + const uintptr_t cur_end = cur_start + cur->size; + if (new_start < new_end) { + if (new_end < cur_start) { + *new_chunk = { + .next = cur, + .size = size, + }; + break; + } else if (new_end == cur_start) { + *new_chunk = { + .next = cur->next, + .size = cur->size + size, + }; + break; + } + } else if (cur_end == new_start) { + cur->size += size; + return; + } + + prev_next = std::addressof(cur->next); + if (cur->next != nullptr) { + cur = cur->next; + } else { + *new_chunk = { + .next = nullptr, + .size = size, + }; + cur->next = new_chunk; + return; + } + } + + } else { + *new_chunk = { + .next = nullptr, + .size = size, + }; + } + + *prev_next = new_chunk; + } + }; + static_assert(IsInitialPageAllocator<KInitialPageAllocator>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_assembly_macros.h b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_assembly_macros.h new file mode 100644 index 00000000..8dfafdc7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_assembly_macros.h @@ -0,0 +1,101 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/arch/arm64/kern_assembly_offsets.h> + +#define ENABLE_FPU(tmp) \ + mrs tmp, cpacr_el1; \ + orr tmp, tmp, #0x300000; \ + msr cpacr_el1, tmp; \ + isb; + +#define GET_THREAD_CONTEXT_AND_RESTORE_FPCR_FPSR(ctx, xtmp1, xtmp2, wtmp1, wtmp2) \ + add ctx, sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_THREAD_CONTEXT); \ + ldp wtmp1, wtmp2, [ctx, #(THREAD_CONTEXT_FPCR_FPSR)]; \ + msr fpcr, xtmp1; \ + msr fpsr, xtmp2; + +#define RESTORE_FPU64_CALLEE_SAVE_REGISTERS(ctx) \ + ldp q8, q9, [ctx, #(THREAD_CONTEXT_FPU64_Q8_Q9)]; \ + ldp q10, q11, [ctx, #(THREAD_CONTEXT_FPU64_Q10_Q11)]; \ + ldp q12, q13, [ctx, #(THREAD_CONTEXT_FPU64_Q12_Q13)]; \ + ldp q14, q15, [ctx, #(THREAD_CONTEXT_FPU64_Q14_Q15)]; + +#define RESTORE_FPU64_CALLER_SAVE_REGISTERS(tmp) \ + ldr tmp, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_CALLER_SAVE_FPU_REGISTERS)]; \ + ldp q0, q1, [tmp, #(THREAD_FPU64_CONTEXT_Q0_Q1)]; \ + ldp q2, q3, [tmp, #(THREAD_FPU64_CONTEXT_Q2_Q3)]; \ + ldp q4, q5, [tmp, #(THREAD_FPU64_CONTEXT_Q4_Q5)]; \ + ldp q6, q7, [tmp, #(THREAD_FPU64_CONTEXT_Q6_Q7)]; \ + ldp q16, q17, [tmp, #(THREAD_FPU64_CONTEXT_Q16_Q17)]; \ + ldp q18, q19, [tmp, #(THREAD_FPU64_CONTEXT_Q18_Q19)]; \ + ldp q20, q21, [tmp, #(THREAD_FPU64_CONTEXT_Q20_Q21)]; \ + ldp q22, q23, [tmp, #(THREAD_FPU64_CONTEXT_Q22_Q23)]; \ + ldp q24, q25, [tmp, #(THREAD_FPU64_CONTEXT_Q24_Q25)]; \ + ldp q26, q27, [tmp, #(THREAD_FPU64_CONTEXT_Q26_Q27)]; \ + ldp q28, q29, [tmp, #(THREAD_FPU64_CONTEXT_Q28_Q29)]; \ + ldp q30, q31, [tmp, #(THREAD_FPU64_CONTEXT_Q30_Q31)]; + +#define RESTORE_FPU64_ALL_REGISTERS(ctx, tmp) \ + RESTORE_FPU64_CALLEE_SAVE_REGISTERS(ctx) \ + RESTORE_FPU64_CALLER_SAVE_REGISTERS(tmp) + +#define RESTORE_FPU32_CALLEE_SAVE_REGISTERS(ctx) \ + ldp q4, q5, [ctx, #(THREAD_CONTEXT_FPU32_Q4_Q5)]; \ + ldp q6, q7, [ctx, #(THREAD_CONTEXT_FPU32_Q6_Q7)]; + +#define RESTORE_FPU32_CALLER_SAVE_REGISTERS(tmp) \ + ldr tmp, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_CALLER_SAVE_FPU_REGISTERS)]; \ + ldp q0, q1, [tmp, #(THREAD_FPU32_CONTEXT_Q0_Q1)]; \ + ldp q2, q3, [tmp, #(THREAD_FPU32_CONTEXT_Q2_Q3)]; \ + ldp q8, q9, [tmp, #(THREAD_FPU32_CONTEXT_Q8_Q9)]; \ + ldp q10, q11, [tmp, #(THREAD_FPU32_CONTEXT_Q10_Q11)]; \ + ldp q12, q13, [tmp, #(THREAD_FPU32_CONTEXT_Q12_Q13)]; \ + ldp q14, q15, [tmp, #(THREAD_FPU32_CONTEXT_Q14_Q15)]; + +#define RESTORE_FPU32_ALL_REGISTERS(ctx, tmp) \ + RESTORE_FPU32_CALLEE_SAVE_REGISTERS(ctx) \ + RESTORE_FPU32_CALLER_SAVE_REGISTERS(tmp) + +#define ENABLE_AND_RESTORE_FPU(ctx, xtmp1, xtmp2, wtmp1, wtmp2, label_32, label_done) \ + ENABLE_FPU(xtmp1) \ + GET_THREAD_CONTEXT_AND_RESTORE_FPCR_FPSR(ctx, xtmp1, xtmp2, wtmp1, wtmp2) \ + \ + ldrb wtmp1, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)]; \ + tbz wtmp1, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_64_BIT), label_32##f; \ + \ + RESTORE_FPU64_ALL_REGISTERS(ctx, xtmp1) \ + \ + b label_done##f; \ + \ +label_32: \ + RESTORE_FPU32_ALL_REGISTERS(ctx, xtmp1) \ +label_done: + +#define ENABLE_AND_RESTORE_FPU64(ctx, xtmp1, xtmp2, wtmp1, wtmp2) \ + ENABLE_FPU(xtmp1) \ + GET_THREAD_CONTEXT_AND_RESTORE_FPCR_FPSR(ctx, xtmp1, xtmp2, wtmp1, wtmp2) \ + RESTORE_FPU64_ALL_REGISTERS(ctx, xtmp1) + +#define ENABLE_AND_RESTORE_FPU32(ctx, xtmp1, xtmp2, wtmp1, wtmp2) \ + ENABLE_FPU(xtmp1) \ + GET_THREAD_CONTEXT_AND_RESTORE_FPCR_FPSR(ctx, xtmp1, xtmp2, wtmp1, wtmp2) \ + RESTORE_FPU32_ALL_REGISTERS(ctx, xtmp1) + +#define ERET_WITH_SPECULATION_BARRIER \ + eret; \ + dsb nsh; \ + isb diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_assembly_offsets.h b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_assembly_offsets.h new file mode 100644 index 00000000..46d956e6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_assembly_offsets.h @@ -0,0 +1,263 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_build_config.hpp> + +/* TODO: Different header for this? */ +#define AMS_KERN_NUM_SUPERVISOR_CALLS 0xC0 + +/* ams::kern::KThread, https://github.com/Atmosphere-NX/Atmosphere/blob/master/libraries/libmesosphere/include/mesosphere/kern_k_thread.hpp */ +#define THREAD_KERNEL_STACK_TOP 0x280 + +/* ams::kern::KThread::StackParameters, https://github.com/Atmosphere-NX/Atmosphere/blob/master/libraries/libmesosphere/include/mesosphere/kern_k_thread.hpp */ +#define THREAD_STACK_PARAMETERS_SIZE 0x140 +#define THREAD_STACK_PARAMETERS_SVC_PERMISSION 0x00 +#define THREAD_STACK_PARAMETERS_CALLER_SAVE_FPU_REGISTERS 0x18 +#define THREAD_STACK_PARAMETERS_CUR_THREAD 0x20 +#define THREAD_STACK_PARAMETERS_DISABLE_COUNT 0x28 +#define THREAD_STACK_PARAMETERS_DPC_FLAGS 0x2A +#define THREAD_STACK_PARAMETERS_CURRENT_SVC_ID 0x2B +#define THREAD_STACK_PARAMETERS_RESERVED_2C 0x2C +#define THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS 0x2D +#define THREAD_STACK_PARAMETERS_IS_PINNED 0x2E +#define THREAD_STACK_PARAMETERS_RESERVED_2F 0x2F +#define THREAD_STACK_PARAMETERS_RESERVED_30 0x30 +#define THREAD_STACK_PARAMETERS_THREAD_CONTEXT 0x40 + +#define THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_CALLING_SVC (0) +#define THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_IN_EXCEPTION_HANDLER (1) +#define THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_CONTEXT_RESTORE_NEEDED (2) +#define THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_64_BIT (3) +#define THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_IN_USERMODE_EXCEPTION_HANDLER (4) +#define THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_IN_CACHE_MAINTENANCE_OPERATION (5) +#define THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_IN_TLB_MAINTENANCE_OPERATION (6) + +#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) +#define THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_HARDWARE_SINGLE_STEP (7) +#endif + +#define THREAD_EXCEPTION_FLAG_IS_CALLING_SVC (1 << THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_CALLING_SVC) +#define THREAD_EXCEPTION_FLAG_IS_IN_EXCEPTION_HANDLER (1 << THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_IN_EXCEPTION_HANDLER) +#define THREAD_EXCEPTION_FLAG_IS_FPU_CONTEXT_RESTORE_NEEDED (1 << THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_CONTEXT_RESTORE_NEEDED) +#define THREAD_EXCEPTION_FLAG_IS_FPU_64_BIT (1 << THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_64_BIT) +#define THREAD_EXCEPTION_FLAG_IS_IN_USERMODE_EXCEPTION_HANDLER (1 << THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_IN_USERMODE_EXCEPTION_HANDLER) +#define THREAD_EXCEPTION_FLAG_IS_IN_CACHE_MAINTENANCE_OPERATION (1 << THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_IN_CACHE_MAINTENANCE_OPERATION) +#define THREAD_EXCEPTION_FLAG_IS_IN_TLB_MAINTENANCE_OPERATION (1 << THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_IN_TLB_MAINTENANCE_OPERATION) + +#if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) +#define THREAD_EXCEPTION_FLAG_IS_HARDWARE_SINGLE_STEP (1 << THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_HARDWARE_SINGLE_STEP) +#endif + +/* ams::kern::arch::arm64::KThreadContext, https://github.com/Atmosphere-NX/Atmosphere/blob/master/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_thread_context.hpp */ +#define THREAD_CONTEXT_SIZE 0x100 +#define THREAD_CONTEXT_CPU_REGISTERS 0x000 +#define THREAD_CONTEXT_X19 0x000 +#define THREAD_CONTEXT_X20 0x008 +#define THREAD_CONTEXT_X21 0x010 +#define THREAD_CONTEXT_X22 0x018 +#define THREAD_CONTEXT_X23 0x020 +#define THREAD_CONTEXT_X24 0x028 +#define THREAD_CONTEXT_X25 0x030 +#define THREAD_CONTEXT_X26 0x038 +#define THREAD_CONTEXT_X27 0x040 +#define THREAD_CONTEXT_X28 0x048 +#define THREAD_CONTEXT_X29 0x050 +#define THREAD_CONTEXT_LR 0x058 +#define THREAD_CONTEXT_SP 0x060 +#define THREAD_CONTEXT_FPCR 0x068 +#define THREAD_CONTEXT_FPSR 0x06C +#define THREAD_CONTEXT_FPU_REGISTERS 0x070 +#define THREAD_CONTEXT_LOCKED 0x0F0 + +#define THREAD_CONTEXT_X19_X20 THREAD_CONTEXT_X19 +#define THREAD_CONTEXT_X21_X22 THREAD_CONTEXT_X21 +#define THREAD_CONTEXT_X23_X24 THREAD_CONTEXT_X23 +#define THREAD_CONTEXT_X25_X26 THREAD_CONTEXT_X25 +#define THREAD_CONTEXT_X27_X28 THREAD_CONTEXT_X27 +#define THREAD_CONTEXT_X29_X30 THREAD_CONTEXT_X29 +#define THREAD_CONTEXT_LR_SP THREAD_CONTEXT_LR +#define THREAD_CONTEXT_SP_FPCR_FPSR THREAD_CONTEXT_SP + +#define THREAD_CONTEXT_FPCR_FPSR THREAD_CONTEXT_FPCR + +#define THREAD_CONTEXT_FPU64_Q8 (THREAD_CONTEXT_FPU_REGISTERS + 0x00) +#define THREAD_CONTEXT_FPU64_Q9 (THREAD_CONTEXT_FPU_REGISTERS + 0x10) +#define THREAD_CONTEXT_FPU64_Q10 (THREAD_CONTEXT_FPU_REGISTERS + 0x20) +#define THREAD_CONTEXT_FPU64_Q11 (THREAD_CONTEXT_FPU_REGISTERS + 0x30) +#define THREAD_CONTEXT_FPU64_Q12 (THREAD_CONTEXT_FPU_REGISTERS + 0x40) +#define THREAD_CONTEXT_FPU64_Q13 (THREAD_CONTEXT_FPU_REGISTERS + 0x50) +#define THREAD_CONTEXT_FPU64_Q14 (THREAD_CONTEXT_FPU_REGISTERS + 0x60) +#define THREAD_CONTEXT_FPU64_Q15 (THREAD_CONTEXT_FPU_REGISTERS + 0x70) + +#define THREAD_CONTEXT_FPU64_Q8_Q9 THREAD_CONTEXT_FPU64_Q8 +#define THREAD_CONTEXT_FPU64_Q10_Q11 THREAD_CONTEXT_FPU64_Q10 +#define THREAD_CONTEXT_FPU64_Q12_Q13 THREAD_CONTEXT_FPU64_Q12 +#define THREAD_CONTEXT_FPU64_Q14_Q15 THREAD_CONTEXT_FPU64_Q14 + +#define THREAD_CONTEXT_FPU32_Q4 (THREAD_CONTEXT_FPU_REGISTERS + 0x00) +#define THREAD_CONTEXT_FPU32_Q5 (THREAD_CONTEXT_FPU_REGISTERS + 0x10) +#define THREAD_CONTEXT_FPU32_Q6 (THREAD_CONTEXT_FPU_REGISTERS + 0x20) +#define THREAD_CONTEXT_FPU32_Q7 (THREAD_CONTEXT_FPU_REGISTERS + 0x30) + +#define THREAD_CONTEXT_FPU32_Q4_Q5 THREAD_CONTEXT_FPU32_Q4 +#define THREAD_CONTEXT_FPU32_Q6_Q7 THREAD_CONTEXT_FPU32_Q6 + +#define THREAD_FPU64_CONTEXT_Q0 0x000 +#define THREAD_FPU64_CONTEXT_Q1 0x010 +#define THREAD_FPU64_CONTEXT_Q2 0x020 +#define THREAD_FPU64_CONTEXT_Q3 0x030 +#define THREAD_FPU64_CONTEXT_Q4 0x040 +#define THREAD_FPU64_CONTEXT_Q5 0x050 +#define THREAD_FPU64_CONTEXT_Q6 0x060 +#define THREAD_FPU64_CONTEXT_Q7 0x070 +#define THREAD_FPU64_CONTEXT_Q16 0x080 +#define THREAD_FPU64_CONTEXT_Q17 0x090 +#define THREAD_FPU64_CONTEXT_Q18 0x0A0 +#define THREAD_FPU64_CONTEXT_Q19 0x0B0 +#define THREAD_FPU64_CONTEXT_Q20 0x0C0 +#define THREAD_FPU64_CONTEXT_Q21 0x0D0 +#define THREAD_FPU64_CONTEXT_Q22 0x0E0 +#define THREAD_FPU64_CONTEXT_Q23 0x0F0 +#define THREAD_FPU64_CONTEXT_Q24 0x100 +#define THREAD_FPU64_CONTEXT_Q25 0x110 +#define THREAD_FPU64_CONTEXT_Q26 0x120 +#define THREAD_FPU64_CONTEXT_Q27 0x130 +#define THREAD_FPU64_CONTEXT_Q28 0x140 +#define THREAD_FPU64_CONTEXT_Q29 0x150 +#define THREAD_FPU64_CONTEXT_Q30 0x160 +#define THREAD_FPU64_CONTEXT_Q31 0x170 + +#define THREAD_FPU64_CONTEXT_Q0_Q1 THREAD_FPU64_CONTEXT_Q0 +#define THREAD_FPU64_CONTEXT_Q2_Q3 THREAD_FPU64_CONTEXT_Q2 +#define THREAD_FPU64_CONTEXT_Q4_Q5 THREAD_FPU64_CONTEXT_Q4 +#define THREAD_FPU64_CONTEXT_Q6_Q7 THREAD_FPU64_CONTEXT_Q6 +#define THREAD_FPU64_CONTEXT_Q16_Q17 THREAD_FPU64_CONTEXT_Q16 +#define THREAD_FPU64_CONTEXT_Q18_Q19 THREAD_FPU64_CONTEXT_Q18 +#define THREAD_FPU64_CONTEXT_Q20_Q21 THREAD_FPU64_CONTEXT_Q20 +#define THREAD_FPU64_CONTEXT_Q22_Q23 THREAD_FPU64_CONTEXT_Q22 +#define THREAD_FPU64_CONTEXT_Q24_Q25 THREAD_FPU64_CONTEXT_Q24 +#define THREAD_FPU64_CONTEXT_Q26_Q27 THREAD_FPU64_CONTEXT_Q26 +#define THREAD_FPU64_CONTEXT_Q28_Q29 THREAD_FPU64_CONTEXT_Q28 +#define THREAD_FPU64_CONTEXT_Q30_Q31 THREAD_FPU64_CONTEXT_Q30 + +#define THREAD_FPU32_CONTEXT_Q0 0x000 +#define THREAD_FPU32_CONTEXT_Q1 0x010 +#define THREAD_FPU32_CONTEXT_Q2 0x020 +#define THREAD_FPU32_CONTEXT_Q3 0x030 +#define THREAD_FPU32_CONTEXT_Q8 0x040 +#define THREAD_FPU32_CONTEXT_Q9 0x050 +#define THREAD_FPU32_CONTEXT_Q10 0x060 +#define THREAD_FPU32_CONTEXT_Q11 0x070 +#define THREAD_FPU32_CONTEXT_Q12 0x080 +#define THREAD_FPU32_CONTEXT_Q13 0x090 +#define THREAD_FPU32_CONTEXT_Q14 0x0A0 +#define THREAD_FPU32_CONTEXT_Q15 0x0B0 + +#define THREAD_FPU32_CONTEXT_Q0_Q1 THREAD_FPU32_CONTEXT_Q0 +#define THREAD_FPU32_CONTEXT_Q2_Q3 THREAD_FPU32_CONTEXT_Q2 +#define THREAD_FPU32_CONTEXT_Q8_Q9 THREAD_FPU32_CONTEXT_Q8 +#define THREAD_FPU32_CONTEXT_Q10_Q11 THREAD_FPU32_CONTEXT_Q10 +#define THREAD_FPU32_CONTEXT_Q12_Q13 THREAD_FPU32_CONTEXT_Q12 +#define THREAD_FPU32_CONTEXT_Q14_Q15 THREAD_FPU32_CONTEXT_Q14 + +/* ams::kern::arch::arm64::KExceptionContext, https://github.com/Atmosphere-NX/Atmosphere/blob/master/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_exception_context.hpp */ +#define EXCEPTION_CONTEXT_SIZE 0x120 +#define EXCEPTION_CONTEXT_X0 0x000 +#define EXCEPTION_CONTEXT_X1 0x008 +#define EXCEPTION_CONTEXT_X2 0x010 +#define EXCEPTION_CONTEXT_X3 0x018 +#define EXCEPTION_CONTEXT_X4 0x020 +#define EXCEPTION_CONTEXT_X5 0x028 +#define EXCEPTION_CONTEXT_X6 0x030 +#define EXCEPTION_CONTEXT_X7 0x038 +#define EXCEPTION_CONTEXT_X8 0x040 +#define EXCEPTION_CONTEXT_X9 0x048 +#define EXCEPTION_CONTEXT_X10 0x050 +#define EXCEPTION_CONTEXT_X11 0x058 +#define EXCEPTION_CONTEXT_X12 0x060 +#define EXCEPTION_CONTEXT_X13 0x068 +#define EXCEPTION_CONTEXT_X14 0x070 +#define EXCEPTION_CONTEXT_X15 0x078 +#define EXCEPTION_CONTEXT_X16 0x080 +#define EXCEPTION_CONTEXT_X17 0x088 +#define EXCEPTION_CONTEXT_X18 0x090 +#define EXCEPTION_CONTEXT_X19 0x098 +#define EXCEPTION_CONTEXT_X20 0x0A0 +#define EXCEPTION_CONTEXT_X21 0x0A8 +#define EXCEPTION_CONTEXT_X22 0x0B0 +#define EXCEPTION_CONTEXT_X23 0x0B8 +#define EXCEPTION_CONTEXT_X24 0x0C0 +#define EXCEPTION_CONTEXT_X25 0x0C8 +#define EXCEPTION_CONTEXT_X26 0x0D0 +#define EXCEPTION_CONTEXT_X27 0x0D8 +#define EXCEPTION_CONTEXT_X28 0x0E0 +#define EXCEPTION_CONTEXT_X29 0x0E8 +#define EXCEPTION_CONTEXT_X30 0x0F0 +#define EXCEPTION_CONTEXT_SP 0x0F8 +#define EXCEPTION_CONTEXT_PC 0x100 +#define EXCEPTION_CONTEXT_PSR 0x108 +#define EXCEPTION_CONTEXT_TPIDR 0x110 + +#define EXCEPTION_CONTEXT_X0_X1 EXCEPTION_CONTEXT_X0 +#define EXCEPTION_CONTEXT_X2_X3 EXCEPTION_CONTEXT_X2 +#define EXCEPTION_CONTEXT_X4_X5 EXCEPTION_CONTEXT_X4 +#define EXCEPTION_CONTEXT_X6_X7 EXCEPTION_CONTEXT_X6 +#define EXCEPTION_CONTEXT_X8_X9 EXCEPTION_CONTEXT_X8 +#define EXCEPTION_CONTEXT_X10_X11 EXCEPTION_CONTEXT_X10 +#define EXCEPTION_CONTEXT_X12_X13 EXCEPTION_CONTEXT_X12 +#define EXCEPTION_CONTEXT_X14_X15 EXCEPTION_CONTEXT_X14 +#define EXCEPTION_CONTEXT_X16_X17 EXCEPTION_CONTEXT_X16 +#define EXCEPTION_CONTEXT_X18_X19 EXCEPTION_CONTEXT_X18 +#define EXCEPTION_CONTEXT_X20_X21 EXCEPTION_CONTEXT_X20 +#define EXCEPTION_CONTEXT_X22_X23 EXCEPTION_CONTEXT_X22 +#define EXCEPTION_CONTEXT_X24_X25 EXCEPTION_CONTEXT_X24 +#define EXCEPTION_CONTEXT_X26_X27 EXCEPTION_CONTEXT_X26 +#define EXCEPTION_CONTEXT_X28_X29 EXCEPTION_CONTEXT_X28 +#define EXCEPTION_CONTEXT_X30_SP EXCEPTION_CONTEXT_X30 +#define EXCEPTION_CONTEXT_PC_PSR EXCEPTION_CONTEXT_PC + +#define EXCEPTION_CONTEXT_X9_X10 EXCEPTION_CONTEXT_X9 +#define EXCEPTION_CONTEXT_X19_X20 EXCEPTION_CONTEXT_X19 +#define EXCEPTION_CONTEXT_X21_X22 EXCEPTION_CONTEXT_X21 +#define EXCEPTION_CONTEXT_X23_X24 EXCEPTION_CONTEXT_X23 +#define EXCEPTION_CONTEXT_X25_X26 EXCEPTION_CONTEXT_X25 +#define EXCEPTION_CONTEXT_X27_X28 EXCEPTION_CONTEXT_X27 +#define EXCEPTION_CONTEXT_X29_X30 EXCEPTION_CONTEXT_X29 +#define EXCEPTION_CONTEXT_SP_PC EXCEPTION_CONTEXT_SP +#define EXCEPTION_CONTEXT_PSR_TPIDR EXCEPTION_CONTEXT_PSR + +/* ams::svc::arch::arm64::ThreadLocalRegion, https://github.com/Atmosphere-NX/Atmosphere/blob/master/libraries/libvapours/include/vapours/svc/arch/arm64/svc_thread_local_region.hpp */ +#define THREAD_LOCAL_REGION_MESSAGE_BUFFER 0x000 +#define THREAD_LOCAL_REGION_DISABLE_COUNT 0x100 +#define THREAD_LOCAL_REGION_INTERRUPT_FLAG 0x102 +#define THREAD_LOCAL_REGION_SIZE 0x200 + +/* ams::kern::init::KInitArguments, https://github.com/Atmosphere-NX/Atmosphere/blob/master/libraries/libmesosphere/include/mesosphere/arch/arm64/init/kern_k_init_arguments.hpp */ +#define INIT_ARGUMENTS_SIZE 0x28 +#define INIT_ARGUMENTS_CPUACTLR 0x00 +#define INIT_ARGUMENTS_CPUECTLR 0x08 +#define INIT_ARGUMENTS_SP 0x10 +#define INIT_ARGUMENTS_ENTRYPOINT 0x18 +#define INIT_ARGUMENTS_ARGUMENT 0x20 + +/* ams::kern::KScheduler (::SchedulingState), https://github.com/Atmosphere-NX/Atmosphere/blob/master/libraries/libmesosphere/include/mesosphere/kern_k_scheduler.hpp */ +/* NOTE: Due to constraints on ldarb relative offsets, KSCHEDULER_NEEDS_SCHEDULING cannot trivially be changed, and will require assembly edits. */ +#define KSCHEDULER_NEEDS_SCHEDULING 0x00 +#define KSCHEDULER_INTERRUPT_TASK_RUNNABLE 0x01 +#define KSCHEDULER_HIGHEST_PRIORITY_THREAD 0x18 +#define KSCHEDULER_IDLE_THREAD_STACK 0x20 +#define KSCHEDULER_PREVIOUS_THREAD 0x28 +#define KSCHEDULER_INTERRUPT_TASK_MANAGER 0x30 diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_cpu.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_cpu.hpp new file mode 100644 index 00000000..9f0494ec --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_cpu.hpp @@ -0,0 +1,271 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <mesosphere/arch/arm64/kern_cpu_system_registers.hpp> +#include <mesosphere/kern_select_userspace_memory_access.hpp> + +namespace ams::kern::arch::arm64::cpu { + +#if defined(ATMOSPHERE_CPU_ARM_CORTEX_A57) || defined(ATMOSPHERE_CPU_ARM_CORTEX_A53) + constexpr inline size_t InstructionCacheLineSize = 0x40; + constexpr inline size_t DataCacheLineSize = 0x40; + constexpr inline size_t NumPerformanceCounters = 6; +#else + #error "Unknown CPU for cache line sizes" +#endif + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + constexpr inline size_t NumCores = 4; +#elif defined(ATMOSPHERE_BOARD_QEMU_VIRT) + constexpr inline size_t NumCores = 4; +#else + #error "Unknown Board for cpu::NumCores" +#endif + + constexpr inline u32 El0Aarch64PsrMask = 0xF0000000; + constexpr inline u32 El0Aarch32PsrMask = 0xFE0FFE20; + + /* Initialization. */ + NOINLINE void InitializeInterruptThreads(s32 core_id); + + /* Helpers for managing memory state. */ + ALWAYS_INLINE void DataSynchronizationBarrier() { + __asm__ __volatile__("dsb sy" ::: "memory"); + } + + ALWAYS_INLINE void DataSynchronizationBarrierInnerShareable() { + __asm__ __volatile__("dsb ish" ::: "memory"); + } + + ALWAYS_INLINE void DataSynchronizationBarrierInnerShareableStore() { + __asm__ __volatile__("dsb ishst" ::: "memory"); + } + + ALWAYS_INLINE void DataMemoryBarrier() { + __asm__ __volatile__("dmb sy" ::: "memory"); + } + + ALWAYS_INLINE void DataMemoryBarrierInnerShareable() { + __asm__ __volatile__("dmb ish" ::: "memory"); + } + + ALWAYS_INLINE void DataMemoryBarrierInnerShareableStore() { + __asm__ __volatile__("dmb ishst" ::: "memory"); + } + + ALWAYS_INLINE void InstructionMemoryBarrier() { + __asm__ __volatile__("isb" ::: "memory"); + } + + ALWAYS_INLINE void EnsureInstructionConsistency() { + DataSynchronizationBarrierInnerShareable(); + InstructionMemoryBarrier(); + } + + ALWAYS_INLINE void EnsureInstructionConsistencyFullSystem() { + DataSynchronizationBarrier(); + InstructionMemoryBarrier(); + } + + ALWAYS_INLINE void Yield() { + __asm__ __volatile__("yield" ::: "memory"); + } + + ALWAYS_INLINE void SwitchProcess(u64 ttbr, u32 proc_id) { + SetTtbr0El1(ttbr); + ContextIdRegisterAccessor(0).SetProcId(proc_id).Store(); + InstructionMemoryBarrier(); + } + + /* Performance counter helpers. */ + ALWAYS_INLINE u64 GetCycleCounter() { + return cpu::GetPmcCntrEl0(); + } + + ALWAYS_INLINE u32 GetPerformanceCounter(s32 n) { + u64 counter = 0; + if (n < static_cast<s32>(NumPerformanceCounters)) { + switch (n) { + case 0: + counter = cpu::GetPmevCntr0El0(); + break; + case 1: + counter = cpu::GetPmevCntr1El0(); + break; + case 2: + counter = cpu::GetPmevCntr2El0(); + break; + case 3: + counter = cpu::GetPmevCntr3El0(); + break; + case 4: + counter = cpu::GetPmevCntr4El0(); + break; + case 5: + counter = cpu::GetPmevCntr5El0(); + break; + default: + break; + } + } + return static_cast<u32>(counter); + } + + /* Helper for address access. */ + ALWAYS_INLINE bool GetPhysicalAddressWritable(KPhysicalAddress *out, KVirtualAddress addr, bool privileged = false) { + const uintptr_t va = GetInteger(addr); + + if (privileged) { + __asm__ __volatile__("at s1e1w, %[va]" :: [va]"r"(va) : "memory"); + } else { + __asm__ __volatile__("at s1e0w, %[va]" :: [va]"r"(va) : "memory"); + } + InstructionMemoryBarrier(); + + u64 par = GetParEl1(); + + if (par & 0x1) { + return false; + } + + if (out) { + *out = KPhysicalAddress((par & 0xFFFFFFFFF000ull) | (va & 0xFFFull)); + } + return true; + } + + ALWAYS_INLINE bool GetPhysicalAddressReadable(KPhysicalAddress *out, KVirtualAddress addr, bool privileged = false) { + const uintptr_t va = GetInteger(addr); + + if (privileged) { + __asm__ __volatile__("at s1e1r, %[va]" :: [va]"r"(va) : "memory"); + } else { + __asm__ __volatile__("at s1e0r, %[va]" :: [va]"r"(va) : "memory"); + } + InstructionMemoryBarrier(); + + u64 par = GetParEl1(); + + if (par & 0x1) { + return false; + } + + if (out) { + *out = KPhysicalAddress((par & 0xFFFFFFFFF000ull) | (va & 0xFFFull)); + } + return true; + } + + ALWAYS_INLINE bool CanAccessAtomic(KProcessAddress addr, bool privileged = false) { + const uintptr_t va = GetInteger(addr); + + if (privileged) { + __asm__ __volatile__("at s1e1w, %[va]" :: [va]"r"(va) : "memory"); + } else { + __asm__ __volatile__("at s1e0w, %[va]" :: [va]"r"(va) : "memory"); + } + InstructionMemoryBarrier(); + + u64 par = GetParEl1(); + + if (par & 0x1) { + return false; + } + + return (par >> (BITSIZEOF(par) - BITSIZEOF(u8))) == 0xFF; + } + + ALWAYS_INLINE void StoreDataCacheForInitArguments(const void *addr, size_t size) { + const uintptr_t start = util::AlignDown(reinterpret_cast<uintptr_t>(addr), DataCacheLineSize); + for (size_t stored = 0; stored < size; stored += cpu::DataCacheLineSize) { + __asm__ __volatile__("dc cvac, %[cur]" :: [cur]"r"(start + stored) : "memory"); + } + DataSynchronizationBarrier(); + } + + /* Synchronization helpers. */ + NOINLINE void SynchronizeAllCores(); + void SynchronizeCores(u64 core_mask); + + /* Cache management helpers. */ + void StoreCacheForInit(void *addr, size_t size); + + void FlushEntireDataCache(); + + Result InvalidateDataCache(void *addr, size_t size); + Result StoreDataCache(const void *addr, size_t size); + Result FlushDataCache(const void *addr, size_t size); + + void InvalidateEntireInstructionCache(); + + void ClearPageToZeroImpl(void *); + + ALWAYS_INLINE void ClearPageToZero(void * const page) { + MESOSPHERE_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(page), PageSize)); + MESOSPHERE_ASSERT(page != nullptr); + + ClearPageToZeroImpl(page); + } + + ALWAYS_INLINE void InvalidateTlbByAsid(u32 asid) { + const u64 value = (static_cast<u64>(asid) << 48); + __asm__ __volatile__("tlbi aside1is, %[value]" :: [value]"r"(value) : "memory"); + EnsureInstructionConsistency(); + } + + ALWAYS_INLINE void InvalidateTlbByAsidAndVa(u32 asid, KProcessAddress virt_addr) { + const u64 value = (static_cast<u64>(asid) << 48) | ((GetInteger(virt_addr) >> 12) & 0xFFFFFFFFFFFul); + __asm__ __volatile__("tlbi aside1is, %[value]" :: [value]"r"(value) : "memory"); + EnsureInstructionConsistency(); + } + + ALWAYS_INLINE void InvalidateEntireTlb() { + __asm__ __volatile__("tlbi vmalle1is" ::: "memory"); + EnsureInstructionConsistency(); + } + + ALWAYS_INLINE void InvalidateEntireTlbDataOnly() { + __asm__ __volatile__("tlbi vmalle1is" ::: "memory"); + DataSynchronizationBarrierInnerShareable(); + } + + ALWAYS_INLINE void InvalidateTlbByVaDataOnly(KProcessAddress virt_addr) { + const u64 value = ((GetInteger(virt_addr) >> 12) & 0xFFFFFFFFFFFul); + __asm__ __volatile__("tlbi vaae1is, %[value]" :: [value]"r"(value) : "memory"); + DataSynchronizationBarrierInnerShareable(); + } + + ALWAYS_INLINE uintptr_t GetCurrentThreadPointerValue() { + register uintptr_t x18 asm("x18"); + __asm__ __volatile__("" : [x18]"=r"(x18)); + return x18; + } + + ALWAYS_INLINE void SetCurrentThreadPointerValue(uintptr_t value) { + register uintptr_t x18 asm("x18") = value; + __asm__ __volatile__("":: [x18]"r"(x18)); + } + + ALWAYS_INLINE void SetExceptionThreadStackTop(uintptr_t top) { + cpu::SetCntvCvalEl0(top); + } + + ALWAYS_INLINE void SwitchThreadLocalRegion(uintptr_t tlr) { + cpu::SetTpidrRoEl0(tlr); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_cpu_system_registers.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_cpu_system_registers.hpp new file mode 100644 index 00000000..61e066fe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_cpu_system_registers.hpp @@ -0,0 +1,493 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::kern::arch::arm64::cpu { + + #define MESOSPHERE_CPU_GET_SYSREG(name) \ + ({ \ + u64 temp_value; \ + __asm__ __volatile__("mrs %0, " #name "" : "=&r"(temp_value) :: "memory"); \ + temp_value; \ + }) + + #define MESOSPHERE_CPU_SET_SYSREG(name, value) \ + ({ \ + __asm__ __volatile__("msr " #name ", %0" :: "r"(value) : "memory", "cc"); \ + }) + + #define MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(name, reg_name) \ + ALWAYS_INLINE void Set##name(u64 value) { MESOSPHERE_CPU_SET_SYSREG(reg_name, value); } \ + ALWAYS_INLINE u64 Get##name() { return MESOSPHERE_CPU_GET_SYSREG(reg_name); } + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(Ttbr0El1, ttbr0_el1) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(Ttbr1El1, ttbr1_el1) + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(TcrEl1, tcr_el1) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(MairEl1, mair_el1) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(TpidrEl1, tpidr_el1) + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(VbarEl1, vbar_el1) + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(FarEl1, far_el1) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(ParEl1, par_el1) + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(SctlrEl1, sctlr_el1) + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(CpuActlrEl1, s3_1_c15_c2_0) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(CpuEctlrEl1, s3_1_c15_c2_1) + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(CsselrEl1, csselr_el1) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(CcsidrEl1, ccsidr_el1) + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(OslarEl1, oslar_el1) + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(TpidrEl0, tpidr_el0) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(TpidrRoEl0, tpidrro_el0) + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(ElrEl1, elr_el1) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(EsrEl1, esr_el1) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(SpsrEl1, spsr_el1) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(Afsr0El1, afsr0_el1) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(Afsr1El1, afsr1_el1) + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(MdscrEl1, mdscr_el1) + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(CpacrEl1, cpacr_el1) + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(ContextidrEl1, contextidr_el1) + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(CntkCtlEl1, cntkctl_el1) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(CntpCtlEl0, cntp_ctl_el0) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(CntpCvalEl0, cntp_cval_el0) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(CntvCvalEl0, cntv_cval_el0) + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(Daif, daif) + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(SpEl0, sp_el0) + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(IdAa64Dfr0El1, id_aa64dfr0_el1) + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(PmcrEl0, pmcr_el0) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(PmUserEnrEl0, pmuserenr_el0) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(PmcCntrEl0, pmccntr_el0) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(PmSelrEl0, pmselr_el0) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(PmcCfiltrEl0, pmccfiltr_el0) + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(PmIntEnSetEl1, pmintenset_el1) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(PmCntEnSetEl0, pmcntenset_el0) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(PmOvsSetEl0, pmovsset_el0) + + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(PmIntEnClrEl1, pmintenclr_el1) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(PmCntEnClrEl0, pmcntenclr_el0) + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(PmOvsClrEl0, pmovsclr_el0) + + #define FOR_I_IN_0_TO_30(HANDLER, ...) \ + HANDLER(0, ## __VA_ARGS__) HANDLER(1, ## __VA_ARGS__) HANDLER(2, ## __VA_ARGS__) HANDLER(3, ## __VA_ARGS__) \ + HANDLER(4, ## __VA_ARGS__) HANDLER(5, ## __VA_ARGS__) HANDLER(6, ## __VA_ARGS__) HANDLER(7, ## __VA_ARGS__) \ + HANDLER(8, ## __VA_ARGS__) HANDLER(9, ## __VA_ARGS__) HANDLER(10, ## __VA_ARGS__) HANDLER(11, ## __VA_ARGS__) \ + HANDLER(12, ## __VA_ARGS__) HANDLER(13, ## __VA_ARGS__) HANDLER(14, ## __VA_ARGS__) HANDLER(15, ## __VA_ARGS__) \ + HANDLER(16, ## __VA_ARGS__) HANDLER(17, ## __VA_ARGS__) HANDLER(18, ## __VA_ARGS__) HANDLER(19, ## __VA_ARGS__) \ + HANDLER(20, ## __VA_ARGS__) HANDLER(21, ## __VA_ARGS__) HANDLER(22, ## __VA_ARGS__) HANDLER(23, ## __VA_ARGS__) \ + HANDLER(24, ## __VA_ARGS__) HANDLER(25, ## __VA_ARGS__) HANDLER(26, ## __VA_ARGS__) HANDLER(27, ## __VA_ARGS__) \ + HANDLER(28, ## __VA_ARGS__) HANDLER(29, ## __VA_ARGS__) HANDLER(30, ## __VA_ARGS__) + + #define MESOSPHERE_CPU_DEFINE_PMEV_ACCESSORS(ID, ...) \ + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(PmevCntr##ID##El0, pmevcntr##ID##_el0) \ + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(PmevTyper##ID##El0, pmevtyper##ID##_el0) + + FOR_I_IN_0_TO_30(MESOSPHERE_CPU_DEFINE_PMEV_ACCESSORS) + + #undef MESOSPHERE_CPU_DEFINE_PMEV_ACCESSORS + #undef FOR_I_IN_0_TO_30 + + #define FOR_I_IN_0_TO_15(HANDLER, ...) \ + HANDLER(0, ## __VA_ARGS__) HANDLER(1, ## __VA_ARGS__) HANDLER(2, ## __VA_ARGS__) HANDLER(3, ## __VA_ARGS__) \ + HANDLER(4, ## __VA_ARGS__) HANDLER(5, ## __VA_ARGS__) HANDLER(6, ## __VA_ARGS__) HANDLER(7, ## __VA_ARGS__) \ + HANDLER(8, ## __VA_ARGS__) HANDLER(9, ## __VA_ARGS__) HANDLER(10, ## __VA_ARGS__) HANDLER(11, ## __VA_ARGS__) \ + HANDLER(12, ## __VA_ARGS__) HANDLER(13, ## __VA_ARGS__) HANDLER(14, ## __VA_ARGS__) HANDLER(15, ## __VA_ARGS__) + + #define MESOSPHERE_CPU_DEFINE_DBG_SYSREG_ACCESSORS(ID, ...) \ + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(DbgWcr##ID##El1, dbgwcr##ID##_el1) \ + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(DbgWvr##ID##El1, dbgwvr##ID##_el1) \ + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(DbgBcr##ID##El1, dbgbcr##ID##_el1) \ + MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS(DbgBvr##ID##El1, dbgbvr##ID##_el1) + + FOR_I_IN_0_TO_15(MESOSPHERE_CPU_DEFINE_DBG_SYSREG_ACCESSORS) + + #undef MESOSPHERE_CPU_DEFINE_DBG_SYSREG_ACCESSORS + + /* Base class for register accessors. */ + class GenericRegisterAccessorBase { + NON_COPYABLE(GenericRegisterAccessorBase); + NON_MOVEABLE(GenericRegisterAccessorBase); + private: + u64 m_value; + public: + constexpr ALWAYS_INLINE GenericRegisterAccessorBase(u64 v) : m_value(v) { /* ... */ } + protected: + constexpr ALWAYS_INLINE u64 GetValue() const { + return m_value; + } + + constexpr ALWAYS_INLINE u64 GetBits(size_t offset, size_t count) const { + return (m_value >> offset) & ((1ul << count) - 1); + } + + constexpr ALWAYS_INLINE void SetBits(size_t offset, size_t count, u64 value) { + const u64 mask = ((1ul << count) - 1) << offset; + m_value &= ~mask; + m_value |= (value & (mask >> offset)) << offset; + } + + constexpr ALWAYS_INLINE void SetBitsDirect(size_t offset, size_t count, u64 value) { + const u64 mask = ((1ul << count) - 1) << offset; + m_value &= ~mask; + m_value |= (value & mask); + } + + constexpr ALWAYS_INLINE void SetBit(size_t offset, bool enabled) { + const u64 mask = 1ul << offset; + if (enabled) { + m_value |= mask; + } else { + m_value &= ~mask; + } + } + }; + + template<typename Derived> + class GenericRegisterAccessor : public GenericRegisterAccessorBase { + public: + constexpr ALWAYS_INLINE GenericRegisterAccessor(u64 v) : GenericRegisterAccessorBase(v) { /* ... */ } + protected: + ALWAYS_INLINE void Store() const { + static_cast<const Derived *>(this)->Store(); + } + }; + + #define MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(name) class name##RegisterAccessor : public GenericRegisterAccessor<name##RegisterAccessor> + + #define MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS(accessor, reg_name) \ + ALWAYS_INLINE accessor##RegisterAccessor() : GenericRegisterAccessor(MESOSPHERE_CPU_GET_SYSREG(reg_name)) { /* ... */ } \ + constexpr ALWAYS_INLINE accessor##RegisterAccessor(u64 v) : GenericRegisterAccessor(v) { /* ... */ } \ + \ + ALWAYS_INLINE void Store() { const u64 v = this->GetValue(); MESOSPHERE_CPU_SET_SYSREG(reg_name, v); } + + /* Accessors. */ + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(MemoryAccessIndirection) { + public: + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS(MemoryAccessIndirection, mair_el1) + }; + + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(TranslationControl) { + public: + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS(TranslationControl, tcr_el1) + + constexpr ALWAYS_INLINE size_t GetT0Size() const { + const size_t shift_value = this->GetBits(0, 6); + return size_t(1) << (size_t(64) - shift_value); + } + + constexpr ALWAYS_INLINE size_t GetT1Size() const { + const size_t shift_value = this->GetBits(16, 6); + return size_t(1) << (size_t(64) - shift_value); + } + + constexpr ALWAYS_INLINE bool GetEpd0() const { + return this->GetBits(7, 1) != 0; + } + + constexpr ALWAYS_INLINE decltype(auto) SetEpd0(bool set) { + this->SetBit(7, set); + return *this; + } + }; + + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(ArchitecturalFeatureAccessControl) { + public: + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS(ArchitecturalFeatureAccessControl, cpacr_el1) + + constexpr ALWAYS_INLINE decltype(auto) SetFpEnabled(bool en) { + if (en) { + this->SetBits(20, 2, 0x3); + } else { + this->SetBits(20, 2, 0x0); + } + return *this; + } + + constexpr ALWAYS_INLINE bool IsFpEnabled() { + return this->GetBits(20, 2) != 0; + } + }; + + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(DebugFeature) { + public: + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS(DebugFeature, id_aa64dfr0_el1) + + constexpr ALWAYS_INLINE size_t GetNumWatchpoints() const { + return this->GetBits(20, 4); + } + + constexpr ALWAYS_INLINE size_t GetNumBreakpoints() const { + return this->GetBits(12, 4); + } + + constexpr ALWAYS_INLINE size_t GetNumContextAwareBreakpoints() const { + return this->GetBits(28, 4); + } + }; + + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(MonitorDebugSystemControl) { + public: + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS(MonitorDebugSystemControl, mdscr_el1) + + constexpr ALWAYS_INLINE bool GetMde() const { + return this->GetBits(15, 1) != 0; + } + + constexpr ALWAYS_INLINE size_t GetTdcc() const { + return this->GetBits(12, 1) != 0; + } + + constexpr ALWAYS_INLINE bool GetSoftwareStep() const { + return this->GetBits(0, 1) != 0; + } + + constexpr ALWAYS_INLINE decltype(auto) SetMde(bool set) { + this->SetBit(15, set); + return *this; + } + + constexpr ALWAYS_INLINE decltype(auto) SetTdcc(bool set) { + this->SetBit(12, set); + return *this; + } + + constexpr ALWAYS_INLINE decltype(auto) SetSoftwareStep(bool set) { + this->SetBit(0, set); + return *this; + } + }; + + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(MultiprocessorAffinity) { + public: + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS(MultiprocessorAffinity, mpidr_el1) + + constexpr ALWAYS_INLINE u64 GetAff0() const { + return this->GetBits(0, 8); + } + + constexpr ALWAYS_INLINE u64 GetAff1() const { + return this->GetBits(8, 8); + } + + constexpr ALWAYS_INLINE u64 GetAff2() const { + return this->GetBits(16, 8); + } + + constexpr ALWAYS_INLINE u64 GetAff3() const { + return this->GetBits(32, 8); + } + + constexpr ALWAYS_INLINE u64 GetCpuOnArgument() const { + constexpr u64 Mask = 0x000000FF00FFFF00ul; + return this->GetValue() & Mask; + } + }; + + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(ThreadId) { + public: + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS(ThreadId, tpidr_el1) + }; + + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(OsLockAccess) { + public: + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS(OsLockAccess, oslar_el1) + }; + + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(ContextId) { + public: + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS(ContextId, contextidr_el1) + + constexpr ALWAYS_INLINE decltype(auto) SetProcId(u32 proc_id) { + this->SetBits(0, BITSIZEOF(proc_id), proc_id); + return *this; + } + }; + + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(MainId) { + public: + enum class Implementer { + ArmLimited = 0x41, + }; + enum class PrimaryPartNumber { + CortexA53 = 0xD03, + CortexA57 = 0xD07, + }; + public: + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS(MainId, midr_el1) + public: + constexpr ALWAYS_INLINE Implementer GetImplementer() const { + return static_cast<Implementer>(this->GetBits(24, 8)); + } + + constexpr ALWAYS_INLINE u64 GetVariant() const { + return this->GetBits(20, 4); + } + + constexpr ALWAYS_INLINE u64 GetArchitecture() const { + return this->GetBits(16, 4); + } + + constexpr ALWAYS_INLINE PrimaryPartNumber GetPrimaryPartNumber() const { + return static_cast<PrimaryPartNumber>(this->GetBits(4, 12)); + } + + constexpr ALWAYS_INLINE u64 GetRevision() const { + return this->GetBits(0, 4); + } + }; + + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(SystemControl) { + public: + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS(SystemControl, sctlr_el1) + + constexpr ALWAYS_INLINE decltype(auto) SetWxn(bool en) { + this->SetBit(19, en); + return *this; + } + + constexpr ALWAYS_INLINE bool GetWxn() const { + return this->GetBits(19, 1) != 0; + } + }; + + /* Accessors for timer registers. */ + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(CounterTimerKernelControl) { + public: + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS(CounterTimerKernelControl, cntkctl_el1) + + constexpr ALWAYS_INLINE decltype(auto) SetEl0PctEn(bool en) { + this->SetBit(0, en); + return *this; + } + }; + + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(CounterTimerPhysicalTimerControl) { + public: + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS(CounterTimerPhysicalTimerControl, cntp_ctl_el0) + + constexpr ALWAYS_INLINE decltype(auto) SetEnable(bool en) { + this->SetBit(0, en); + return *this; + } + + constexpr ALWAYS_INLINE decltype(auto) SetIMask(bool en) { + this->SetBit(1, en); + return *this; + } + }; + + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(CounterTimerPhysicalTimerCompareValue) { + public: + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS(CounterTimerPhysicalTimerCompareValue, cntp_cval_el0) + + constexpr ALWAYS_INLINE u64 GetCompareValue() { + return this->GetValue(); + } + + constexpr ALWAYS_INLINE decltype(auto) SetCompareValue(u64 value) { + this->SetBits(0, BITSIZEOF(value), value); + return *this; + } + }; + + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(CounterTimerPhysicalCountValue) { + public: + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS(CounterTimerPhysicalCountValue, cntpct_el0) + + constexpr ALWAYS_INLINE u64 GetCount() { + return this->GetValue(); + } + }; + + /* Accessors for cache registers. */ + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(CacheLineId) { + public: + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS(CacheLineId, clidr_el1) + public: + constexpr ALWAYS_INLINE int GetLevelsOfCoherency() const { + return static_cast<int>(this->GetBits(24, 3)); + } + + constexpr ALWAYS_INLINE int GetLevelsOfUnification() const { + return static_cast<int>(this->GetBits(21, 3)); + } + + /* TODO: Other bitfield accessors? */ + }; + + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(CacheSizeId) { + public: + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS(CacheSizeId, ccsidr_el1) + public: + constexpr ALWAYS_INLINE int GetNumberOfSets() const { + return static_cast<int>(this->GetBits(13, 15)); + } + + constexpr ALWAYS_INLINE int GetAssociativity() const { + return static_cast<int>(this->GetBits(3, 10)); + } + + constexpr ALWAYS_INLINE int GetLineSize() const { + return static_cast<int>(this->GetBits(0, 3)); + } + + /* TODO: Other bitfield accessors? */ + }; + + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS(PerformanceMonitorsControl) { + public: + MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS(PerformanceMonitorsControl, pmcr_el0) + public: + constexpr ALWAYS_INLINE u64 GetN() const { + return this->GetBits(11, 5); + } + + constexpr ALWAYS_INLINE decltype(auto) SetEventCounterReset(bool en) { + this->SetBit(1, en); + return *this; + } + + constexpr ALWAYS_INLINE decltype(auto) SetCycleCounterReset(bool en) { + this->SetBit(2, en); + return *this; + } + + /* TODO: Other bitfield accessors? */ + }; + + #undef FOR_I_IN_0_TO_15 + #undef MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS_FUNCTIONS + #undef MESOSPHERE_CPU_SYSREG_ACCESSOR_CLASS + #undef MESOSPHERE_CPU_DEFINE_SYSREG_ACCESSORS + #undef MESOSPHERE_CPU_GET_SYSREG + #undef MESOSPHERE_CPU_SET_SYSREG + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_debug.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_debug.hpp new file mode 100644 index 00000000..ca6bbc55 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_debug.hpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_select_cpu.hpp> +#include <mesosphere/kern_k_debug_base.hpp> + +namespace ams::kern { + + class KThread; + class KProcess; + +} + +namespace ams::kern::arch::arm64 { + + class KDebug final : public KAutoObjectWithSlabHeapAndContainer<KDebug, KDebugBase> { + MESOSPHERE_AUTOOBJECT_TRAITS(KDebug, KSynchronizationObject); + public: + explicit KDebug() { /* ... */ } + + static void PostDestroy(uintptr_t arg) { MESOSPHERE_UNUSED(arg); /* ... */ } + public: + /* NOTE: These are virtual in Nintendo's kernel. */ + Result GetThreadContextImpl(ams::svc::ThreadContext *out, KThread *thread, u32 context_flags); + Result SetThreadContextImpl(const ams::svc::ThreadContext &ctx, KThread *thread, u32 context_flags); + private: + Result GetFpuContext(ams::svc::ThreadContext *out, KThread *thread, u32 context_flags); + Result SetFpuContext(const ams::svc::ThreadContext &ctx, KThread *thread, u32 context_flags); + public: + static uintptr_t GetProgramCounter(const KThread &thread); + static void SetPreviousProgramCounter(); + + static void PrintRegister(KThread *thread = nullptr); + static void PrintBacktrace(KThread *thread = nullptr); + + static Result BreakIfAttached(ams::svc::BreakReason break_reason, uintptr_t address, size_t size); + static Result SetHardwareBreakPoint(ams::svc::HardwareBreakPointRegisterName name, u64 flags, u64 value); + + static constexpr bool IsBreakInstruction(u32 insn, u32 psr) { + constexpr u32 BreakInstructionAarch64 = 0xE7FFFFFF; + constexpr u32 BreakInstructionAarch32 = 0xE7FFDEFE; + constexpr u32 BreakInstructionThumb32 = 0xB68E; + if ((psr & 0x10) == 0) { + return insn == BreakInstructionAarch64; + } else { + if ((psr & 0x20) == 0) { + return insn == BreakInstructionAarch32; + } else { + return insn == BreakInstructionThumb32; + } + } + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_exception_context.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_exception_context.hpp new file mode 100644 index 00000000..da3ce9cc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_exception_context.hpp @@ -0,0 +1,84 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +namespace ams::kern::arch::arm64 { + + struct KExceptionContext { + u64 x[(30 - 0) + 1]; + u64 sp; + u64 pc; + u32 psr; + u32 write; + u64 tpidr; + u64 reserved; + + constexpr void GetSvcThreadContext(ams::svc::LastThreadContext *out) const { + if ((this->psr & 0x10) == 0) { + /* aarch64 thread. */ + out->fp = this->x[29]; + out->sp = this->sp; + out->lr = this->x[30]; + out->pc = this->pc; + } else { + /* aarch32 thread. */ + out->fp = this->x[11]; + out->sp = this->x[13]; + out->lr = this->x[14]; + out->pc = this->pc; + } + } + }; + static_assert(sizeof(KExceptionContext) == EXCEPTION_CONTEXT_SIZE); + + static_assert(AMS_OFFSETOF(KExceptionContext, x[ 0]) == EXCEPTION_CONTEXT_X0); + static_assert(AMS_OFFSETOF(KExceptionContext, x[ 1]) == EXCEPTION_CONTEXT_X1); + static_assert(AMS_OFFSETOF(KExceptionContext, x[ 2]) == EXCEPTION_CONTEXT_X2); + static_assert(AMS_OFFSETOF(KExceptionContext, x[ 3]) == EXCEPTION_CONTEXT_X3); + static_assert(AMS_OFFSETOF(KExceptionContext, x[ 4]) == EXCEPTION_CONTEXT_X4); + static_assert(AMS_OFFSETOF(KExceptionContext, x[ 5]) == EXCEPTION_CONTEXT_X5); + static_assert(AMS_OFFSETOF(KExceptionContext, x[ 6]) == EXCEPTION_CONTEXT_X6); + static_assert(AMS_OFFSETOF(KExceptionContext, x[ 7]) == EXCEPTION_CONTEXT_X7); + static_assert(AMS_OFFSETOF(KExceptionContext, x[ 8]) == EXCEPTION_CONTEXT_X8); + static_assert(AMS_OFFSETOF(KExceptionContext, x[ 9]) == EXCEPTION_CONTEXT_X9); + static_assert(AMS_OFFSETOF(KExceptionContext, x[10]) == EXCEPTION_CONTEXT_X10); + static_assert(AMS_OFFSETOF(KExceptionContext, x[11]) == EXCEPTION_CONTEXT_X11); + static_assert(AMS_OFFSETOF(KExceptionContext, x[12]) == EXCEPTION_CONTEXT_X12); + static_assert(AMS_OFFSETOF(KExceptionContext, x[13]) == EXCEPTION_CONTEXT_X13); + static_assert(AMS_OFFSETOF(KExceptionContext, x[14]) == EXCEPTION_CONTEXT_X14); + static_assert(AMS_OFFSETOF(KExceptionContext, x[15]) == EXCEPTION_CONTEXT_X15); + static_assert(AMS_OFFSETOF(KExceptionContext, x[16]) == EXCEPTION_CONTEXT_X16); + static_assert(AMS_OFFSETOF(KExceptionContext, x[17]) == EXCEPTION_CONTEXT_X17); + static_assert(AMS_OFFSETOF(KExceptionContext, x[18]) == EXCEPTION_CONTEXT_X18); + static_assert(AMS_OFFSETOF(KExceptionContext, x[19]) == EXCEPTION_CONTEXT_X19); + static_assert(AMS_OFFSETOF(KExceptionContext, x[20]) == EXCEPTION_CONTEXT_X20); + static_assert(AMS_OFFSETOF(KExceptionContext, x[21]) == EXCEPTION_CONTEXT_X21); + static_assert(AMS_OFFSETOF(KExceptionContext, x[22]) == EXCEPTION_CONTEXT_X22); + static_assert(AMS_OFFSETOF(KExceptionContext, x[23]) == EXCEPTION_CONTEXT_X23); + static_assert(AMS_OFFSETOF(KExceptionContext, x[24]) == EXCEPTION_CONTEXT_X24); + static_assert(AMS_OFFSETOF(KExceptionContext, x[25]) == EXCEPTION_CONTEXT_X25); + static_assert(AMS_OFFSETOF(KExceptionContext, x[26]) == EXCEPTION_CONTEXT_X26); + static_assert(AMS_OFFSETOF(KExceptionContext, x[27]) == EXCEPTION_CONTEXT_X27); + static_assert(AMS_OFFSETOF(KExceptionContext, x[28]) == EXCEPTION_CONTEXT_X28); + static_assert(AMS_OFFSETOF(KExceptionContext, x[29]) == EXCEPTION_CONTEXT_X29); + static_assert(AMS_OFFSETOF(KExceptionContext, x[30]) == EXCEPTION_CONTEXT_X30); + static_assert(AMS_OFFSETOF(KExceptionContext, sp) == EXCEPTION_CONTEXT_SP); + static_assert(AMS_OFFSETOF(KExceptionContext, pc) == EXCEPTION_CONTEXT_PC); + static_assert(AMS_OFFSETOF(KExceptionContext, psr) == EXCEPTION_CONTEXT_PSR); + static_assert(AMS_OFFSETOF(KExceptionContext, tpidr) == EXCEPTION_CONTEXT_TPIDR); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_hardware_timer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_hardware_timer.hpp new file mode 100644 index 00000000..9ffe5793 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_hardware_timer.hpp @@ -0,0 +1,95 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_select_cpu.hpp> +#include <mesosphere/kern_k_hardware_timer_base.hpp> + +namespace ams::kern::arch::arm64 { + + class KHardwareTimer : public KInterruptTask, public KHardwareTimerBase { + private: + s64 m_maximum_time; + public: + constexpr KHardwareTimer() : KInterruptTask(), KHardwareTimerBase(), m_maximum_time(std::numeric_limits<s64>::max()) { /* ... */ } + public: + /* Public API. */ + NOINLINE void Initialize(); + NOINLINE void Finalize(); + + static s64 GetTick() { + return GetCount(); + } + + void RegisterAbsoluteTask(KTimerTask *task, s64 task_time) { + KScopedDisableDispatch dd; + KScopedSpinLock lk(this->GetLock()); + + if (this->RegisterAbsoluteTaskImpl(task, task_time)) { + if (task_time <= m_maximum_time) { + SetCompareValue(task_time); + EnableInterrupt(); + } + } + } + private: + /* Hardware register accessors. */ + static ALWAYS_INLINE void InitializeGlobalTimer() { + /* Set kernel control. */ + cpu::CounterTimerKernelControlRegisterAccessor(0).SetEl0PctEn(true).Store(); + + /* Disable the physical timer. */ + cpu::CounterTimerPhysicalTimerControlRegisterAccessor(0).SetEnable(false).SetIMask(false).Store(); + + /* Set the compare value to the maximum. */ + cpu::CounterTimerPhysicalTimerCompareValueRegisterAccessor(0).SetCompareValue(std::numeric_limits<u64>::max()).Store(); + + /* Enable the physical timer, with interrupt masked. */ + cpu::CounterTimerPhysicalTimerControlRegisterAccessor(0).SetEnable(true).SetIMask(true).Store(); + } + + static ALWAYS_INLINE void EnableInterrupt() { + cpu::CounterTimerPhysicalTimerControlRegisterAccessor(0).SetEnable(true).SetIMask(false).Store(); + } + + static ALWAYS_INLINE void DisableInterrupt() { + cpu::CounterTimerPhysicalTimerControlRegisterAccessor(0).SetEnable(true).SetIMask(true).Store(); + } + + static ALWAYS_INLINE void StopTimer() { + /* Set the compare value to the maximum. */ + cpu::CounterTimerPhysicalTimerCompareValueRegisterAccessor(0).SetCompareValue(std::numeric_limits<u64>::max()).Store(); + + /* Disable the physical timer. */ + cpu::CounterTimerPhysicalTimerControlRegisterAccessor(0).SetEnable(false).SetIMask(false).Store(); + } + + static ALWAYS_INLINE s64 GetCount() { + return cpu::CounterTimerPhysicalCountValueRegisterAccessor().GetCount(); + } + + static ALWAYS_INLINE void SetCompareValue(s64 value) { + cpu::CounterTimerPhysicalTimerCompareValueRegisterAccessor(0).SetCompareValue(static_cast<u64>(value)).Store(); + } + public: + virtual KInterruptTask *OnInterrupt(s32 interrupt_id) override { + MESOSPHERE_UNUSED(interrupt_id); + return this; + } + + virtual void DoTask() override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_interrupt_controller.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_interrupt_controller.hpp new file mode 100644 index 00000000..15ac18ea --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_interrupt_controller.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <mesosphere/kern_select_cpu.hpp> +#include <mesosphere/kern_k_typed_address.hpp> + +#if 1 + + #include <mesosphere/arch/arm/kern_generic_interrupt_controller.hpp> + namespace ams::kern::arch::arm64 { + + using ams::kern::arch::arm::GicDistributor; + using ams::kern::arch::arm::GicCpuInterface; + using ams::kern::arch::arm::KInterruptController; + + } + +#else + + #error "Unknown board for KInterruptController" + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_interrupt_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_interrupt_manager.hpp new file mode 100644 index 00000000..2a0c1560 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_interrupt_manager.hpp @@ -0,0 +1,151 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <mesosphere/kern_select_cpu.hpp> +#include <mesosphere/kern_k_spin_lock.hpp> +#include <mesosphere/kern_k_interrupt_task.hpp> +#include <mesosphere/kern_select_interrupt_controller.hpp> + +namespace ams::kern::arch::arm64 { + + class KInterruptManager { + NON_COPYABLE(KInterruptManager); + NON_MOVEABLE(KInterruptManager); + private: + struct KCoreLocalInterruptEntry { + KInterruptHandler *handler; + bool manually_cleared; + bool needs_clear; + u8 priority; + + constexpr KCoreLocalInterruptEntry() + : handler(nullptr), manually_cleared(false), needs_clear(false), priority(KInterruptController::PriorityLevel_Low) + { + /* ... */ + } + }; + + struct KGlobalInterruptEntry { + KInterruptHandler *handler; + bool manually_cleared; + bool needs_clear; + + constexpr KGlobalInterruptEntry() : handler(nullptr), manually_cleared(false), needs_clear(false) { /* ... */ } + }; + private: + KCoreLocalInterruptEntry m_core_local_interrupts[cpu::NumCores][KInterruptController::NumLocalInterrupts]{}; + KInterruptController m_interrupt_controller{}; + KInterruptController::LocalState m_local_states[cpu::NumCores]{}; + bool m_local_state_saved[cpu::NumCores]{}; + mutable KSpinLock m_global_interrupt_lock{}; + KGlobalInterruptEntry m_global_interrupts[KInterruptController::NumGlobalInterrupts]{}; + KInterruptController::GlobalState m_global_state{}; + bool m_global_state_saved{}; + private: + ALWAYS_INLINE KSpinLock &GetGlobalInterruptLock() const { return m_global_interrupt_lock; } + ALWAYS_INLINE KGlobalInterruptEntry &GetGlobalInterruptEntry(s32 irq) { return m_global_interrupts[KInterruptController::GetGlobalInterruptIndex(irq)]; } + ALWAYS_INLINE KCoreLocalInterruptEntry &GetLocalInterruptEntry(s32 irq) { return m_core_local_interrupts[GetCurrentCoreId()][KInterruptController::GetLocalInterruptIndex(irq)]; } + + bool OnHandleInterrupt(); + public: + constexpr KInterruptManager() = default; + + NOINLINE void Initialize(s32 core_id); + NOINLINE void Finalize(s32 core_id); + + NOINLINE void Save(s32 core_id); + NOINLINE void Restore(s32 core_id); + + bool IsInterruptDefined(s32 irq) const { + return m_interrupt_controller.IsInterruptDefined(irq); + } + + bool IsGlobal(s32 irq) const { + return m_interrupt_controller.IsGlobal(irq); + } + + bool IsLocal(s32 irq) const { + return m_interrupt_controller.IsLocal(irq); + } + + NOINLINE Result BindHandler(KInterruptHandler *handler, s32 irq, s32 core_id, s32 priority, bool manual_clear, bool level); + NOINLINE Result UnbindHandler(s32 irq, s32 core); + + NOINLINE Result ClearInterrupt(s32 irq, s32 core_id); + + ALWAYS_INLINE void SendInterProcessorInterrupt(s32 irq, u64 core_mask) { + m_interrupt_controller.SendInterProcessorInterrupt(irq, core_mask); + } + + ALWAYS_INLINE void SendInterProcessorInterrupt(s32 irq) { + m_interrupt_controller.SendInterProcessorInterrupt(irq); + } + + static void HandleInterrupt(bool user_mode); + private: + Result BindGlobal(KInterruptHandler *handler, s32 irq, s32 core_id, s32 priority, bool manual_clear, bool level); + Result BindLocal(KInterruptHandler *handler, s32 irq, s32 priority, bool manual_clear); + Result UnbindGlobal(s32 irq); + Result UnbindLocal(s32 irq); + Result ClearGlobal(s32 irq); + Result ClearLocal(s32 irq); + private: + [[nodiscard]] static ALWAYS_INLINE u32 GetInterruptsEnabledState() { + u64 intr_state; + __asm__ __volatile__("mrs %[intr_state], daif\n" + "ubfx %[intr_state], %[intr_state], #7, #1" + : [intr_state]"=r"(intr_state) + :: "memory"); + return intr_state; + } + public: + static ALWAYS_INLINE void EnableInterrupts() { + __asm__ __volatile__("msr daifclr, #2" ::: "memory"); + } + + static ALWAYS_INLINE void DisableInterrupts() { + __asm__ __volatile__("msr daifset, #2" ::: "memory"); + } + + [[nodiscard]] static ALWAYS_INLINE u32 GetInterruptsEnabledStateAndDisableInterrupts() { + const auto intr_state = GetInterruptsEnabledState(); + DisableInterrupts(); + return intr_state; + } + + [[nodiscard]] static ALWAYS_INLINE u32 GetInterruptsEnabledStateAndEnableInterrupts() { + const auto intr_state = GetInterruptsEnabledState(); + EnableInterrupts(); + return intr_state; + } + + static ALWAYS_INLINE void RestoreInterrupts(u32 intr_state) { + u64 tmp; + __asm__ __volatile__("mrs %[tmp], daif\n" + "bfi %[tmp], %[intr_state], #7, #1\n" + "msr daif, %[tmp]" + : [tmp]"=&r"(tmp) + : [intr_state]"r"(intr_state) + : "memory"); + } + + static ALWAYS_INLINE bool AreInterruptsEnabled() { + return GetInterruptsEnabledState() == 0; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_interrupt_name.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_interrupt_name.hpp new file mode 100644 index 00000000..7bcac7a1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_interrupt_name.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +namespace ams::kern::arch::arm64 { + + namespace interrupt_name { + + enum KInterruptName : s32 { + /* SGIs */ + KInterruptName_ThreadTerminate = 0, + KInterruptName_CacheOperation = 1, + KInterruptName_Scheduler = 2, + KInterruptName_CoreBarrier = 3, + + KInterruptName_PerformanceCounter = 4, + + /* PPIs */ + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + KInterruptName_VirtualMaintenance = 25, + KInterruptName_HypervisorTimer = 26, + KInterruptName_VirtualTimer = 27, + KInterruptName_LegacyNFiq = 28, + KInterruptName_SecurePhysicalTimer = 29, + KInterruptName_NonSecurePhysicalTimer = 30, + KInterruptName_LegacyNIrq = 31, + #elif defined(ATMOSPHERE_BOARD_QEMU_VIRT) + KInterruptName_VirtualTimer = 27, + KInterruptName_SecurePhysicalTimer = 29, + KInterruptName_NonSecurePhysicalTimer = 30, + #endif + + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + KInterruptName_MemoryController = 109, + #endif + }; + + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_memory_region_device_types.inc b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_memory_region_device_types.inc new file mode 100644 index 00000000..f2be3fae --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_memory_region_device_types.inc @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* All architectures must define NumArchitectureDeviceRegions. */ +constexpr inline const auto NumArchitectureDeviceRegions = 3; + +constexpr inline const auto KMemoryRegionType_Uart = KMemoryRegionType_ArchDeviceBase.DeriveSparse(0, NumArchitectureDeviceRegions, 0); +constexpr inline const auto KMemoryRegionType_InterruptCpuInterface = KMemoryRegionType_ArchDeviceBase.DeriveSparse(0, NumArchitectureDeviceRegions, 1).SetAttribute(KMemoryRegionAttr_NoUserMap); +constexpr inline const auto KMemoryRegionType_InterruptDistributor = KMemoryRegionType_ArchDeviceBase.DeriveSparse(0, NumArchitectureDeviceRegions, 2).SetAttribute(KMemoryRegionAttr_NoUserMap); +static_assert(KMemoryRegionType_Uart .GetValue() == (0x1D)); +static_assert(KMemoryRegionType_InterruptCpuInterface.GetValue() == (0x2D | KMemoryRegionAttr_NoUserMap)); +static_assert(KMemoryRegionType_InterruptDistributor .GetValue() == (0x4D | KMemoryRegionAttr_NoUserMap)); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_page_table.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_page_table.hpp new file mode 100644 index 00000000..7bc1a96f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_page_table.hpp @@ -0,0 +1,270 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_select_cpu.hpp> +#include <mesosphere/kern_k_page_table_base.hpp> +#include <mesosphere/kern_k_page_group.hpp> +#include <mesosphere/kern_k_page_table_manager.hpp> + +namespace ams::kern::arch::arm64 { + + class KPageTable final : public KPageTableBase { + NON_COPYABLE(KPageTable); + NON_MOVEABLE(KPageTable); + private: + friend class KPageTableBase; + public: + using TraversalEntry = KPageTableImpl::TraversalEntry; + using TraversalContext = KPageTableImpl::TraversalContext; + + enum BlockType { + BlockType_L3Block, + BlockType_L3ContiguousBlock, + BlockType_L2Block, + +#ifdef ATMOSPHERE_BOARD_NINTENDO_NX + BlockType_L2TegraSmmuBlock, +#endif + + BlockType_L2ContiguousBlock, + BlockType_L1Block, + + BlockType_Count, + }; + static_assert(L3BlockSize == PageSize); + static constexpr size_t ContiguousPageSize = L3ContiguousBlockSize; + +#ifdef ATMOSPHERE_BOARD_NINTENDO_NX + static constexpr size_t L2TegraSmmuBlockSize = 2 * L2BlockSize; +#endif + static constexpr size_t BlockSizes[BlockType_Count] = { + [BlockType_L3Block] = L3BlockSize, + [BlockType_L3ContiguousBlock] = L3ContiguousBlockSize, + [BlockType_L2Block] = L2BlockSize, +#ifdef ATMOSPHERE_BOARD_NINTENDO_NX + [BlockType_L2TegraSmmuBlock] = L2TegraSmmuBlockSize, +#endif + [BlockType_L2ContiguousBlock] = L2ContiguousBlockSize, + [BlockType_L1Block] = L1BlockSize, + }; + + static constexpr BlockType GetMaxBlockType() { + return BlockType_L1Block; + } + + static constexpr size_t GetBlockSize(BlockType type) { + return BlockSizes[type]; + } + + static constexpr BlockType GetBlockType(size_t size) { + switch (size) { + case L3BlockSize: return BlockType_L3Block; + case L3ContiguousBlockSize: return BlockType_L3ContiguousBlock; + case L2BlockSize: return BlockType_L2Block; +#ifdef ATMOSPHERE_BOARD_NINTENDO_NX + case L2TegraSmmuBlockSize: return BlockType_L2TegraSmmuBlock; +#endif + case L2ContiguousBlockSize: return BlockType_L2ContiguousBlock; + case L1BlockSize: return BlockType_L1Block; + MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); + } + } + + static constexpr size_t GetSmallerAlignment(size_t alignment) { + MESOSPHERE_ASSERT(alignment > L3BlockSize); + return KPageTable::GetBlockSize(static_cast<KPageTable::BlockType>(KPageTable::GetBlockType(alignment) - 1)); + } + + static constexpr size_t GetLargerAlignment(size_t alignment) { + MESOSPHERE_ASSERT(alignment < L1BlockSize); + return KPageTable::GetBlockSize(static_cast<KPageTable::BlockType>(KPageTable::GetBlockType(alignment) + 1)); + } + public: + /* TODO: How should this size be determined. Does the KProcess slab count need to go in a header as a define? */ + static constexpr size_t NumTtbr0Entries = 81; + private: + static constinit inline const volatile u64 s_ttbr0_entries[NumTtbr0Entries] = {}; + private: + KPageTableManager *m_manager; + u8 m_asid; + protected: + Result OperateImpl(PageLinkedList *page_list, KProcessAddress virt_addr, size_t num_pages, KPhysicalAddress phys_addr, bool is_pa_valid, const KPageProperties properties, OperationType operation, bool reuse_ll); + Result OperateImpl(PageLinkedList *page_list, KProcessAddress virt_addr, size_t num_pages, const KPageGroup &page_group, const KPageProperties properties, OperationType operation, bool reuse_ll); + void FinalizeUpdateImpl(PageLinkedList *page_list); + + KPageTableManager &GetPageTableManager() const { return *m_manager; } + private: + constexpr PageTableEntry GetEntryTemplate(const KPageProperties properties) const { + /* Check that the property is not kernel execute. */ + MESOSPHERE_ABORT_UNLESS((properties.perm & KMemoryPermission_KernelExecute) == 0); + + /* Set basic attributes. */ + PageTableEntry entry{PageTableEntry::ExtensionFlag_Valid}; + entry.SetPrivilegedExecuteNever(true); + entry.SetAccessFlag(PageTableEntry::AccessFlag_Accessed); + entry.SetShareable(PageTableEntry::Shareable_InnerShareable); + + if (!this->IsKernel()) { + entry.SetGlobal(false); + } + + /* Set page attribute. */ + if (properties.io) { + MESOSPHERE_ABORT_UNLESS(!properties.uncached); + MESOSPHERE_ABORT_UNLESS((properties.perm & KMemoryPermission_UserExecute) == 0); + + entry.SetPageAttribute(PageTableEntry::PageAttribute_Device_nGnRnE) + .SetUserExecuteNever(true); + } else if (properties.uncached) { + MESOSPHERE_ABORT_UNLESS((properties.perm & KMemoryPermission_UserExecute) == 0); + + entry.SetPageAttribute(PageTableEntry::PageAttribute_NormalMemoryNotCacheable) + .SetUserExecuteNever(true); + } else { + entry.SetPageAttribute(PageTableEntry::PageAttribute_NormalMemory); + + if ((properties.perm & KMemoryPermission_UserExecute) != 0) { + /* Check that the permission is either r--/--x or r--/r-x. */ + MESOSPHERE_ABORT_UNLESS((properties.perm & ~ams::svc::MemoryPermission_Read) == (KMemoryPermission_KernelRead | KMemoryPermission_UserExecute)); + } else { + entry.SetUserExecuteNever(true); + } + } + + /* Set AP[1] based on perm. */ + switch (properties.perm & KMemoryPermission_UserReadWrite) { + case KMemoryPermission_UserReadWrite: + case KMemoryPermission_UserRead: + entry.SetUserAccessible(true); + break; + case KMemoryPermission_KernelReadWrite: + case KMemoryPermission_KernelRead: + entry.SetUserAccessible(false); + break; + MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); + } + + /* Set AP[2] based on perm. */ + switch (properties.perm & KMemoryPermission_UserReadWrite) { + case KMemoryPermission_UserReadWrite: + case KMemoryPermission_KernelReadWrite: + entry.SetReadOnly(false); + break; + case KMemoryPermission_KernelRead: + case KMemoryPermission_UserRead: + entry.SetReadOnly(true); + break; + MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); + } + + /* Set the fault bit based on whether the page is mapped. */ + entry.SetMapped((properties.perm & KMemoryPermission_NotMapped) == 0); + + return entry; + } + public: + constexpr explicit KPageTable(util::ConstantInitializeTag) : KPageTableBase(util::ConstantInitialize), m_manager(), m_asid() { /* ... */ } + explicit KPageTable() { /* ... */ } + + static NOINLINE void Initialize(s32 core_id); + + static const volatile u64 &GetTtbr0Entry(size_t index) { return s_ttbr0_entries[index]; } + + static ALWAYS_INLINE u64 GetKernelTtbr0() { + return s_ttbr0_entries[0]; + } + + static ALWAYS_INLINE void ActivateKernel() { + /* Activate, using asid 0 and process id = 0xFFFFFFFF */ + cpu::SwitchProcess(GetKernelTtbr0(), 0xFFFFFFFF); + } + + static ALWAYS_INLINE void ActivateProcess(size_t proc_idx, u32 proc_id) { + cpu::SwitchProcess(s_ttbr0_entries[proc_idx + 1], proc_id); + } + + NOINLINE Result InitializeForKernel(void *table, KVirtualAddress start, KVirtualAddress end); + NOINLINE Result InitializeForProcess(ams::svc::CreateProcessFlag flags, bool from_back, KMemoryManager::Pool pool, KProcessAddress code_address, size_t code_size, KSystemResource *system_resource, KResourceLimit *resource_limit, size_t process_index); + Result Finalize(); + + static void NoteUpdatedCallback(const void *pt) { + /* Note the update. */ + static_cast<const KPageTable *>(pt)->NoteUpdated(); + } + private: + Result Unmap(KProcessAddress virt_addr, size_t num_pages, PageLinkedList *page_list, bool force, bool reuse_ll); + + Result Map(KProcessAddress virt_addr, KPhysicalAddress phys_addr, size_t num_pages, PageTableEntry entry_template, bool disable_head_merge, size_t page_size, PageLinkedList *page_list, bool reuse_ll); + + Result MapContiguous(KProcessAddress virt_addr, KPhysicalAddress phys_addr, size_t num_pages, PageTableEntry entry_template, bool disable_head_merge, PageLinkedList *page_list, bool reuse_ll); + Result MapGroup(KProcessAddress virt_addr, const KPageGroup &pg, size_t num_pages, PageTableEntry entry_template, bool disable_head_merge, bool not_first, PageLinkedList *page_list, bool reuse_ll); + + bool MergePages(TraversalContext *context, PageLinkedList *page_list); + void MergePages(KProcessAddress virt_addr, size_t num_pages, PageLinkedList *page_list); + + Result SeparatePagesImpl(TraversalEntry *entry, TraversalContext *context, KProcessAddress virt_addr, size_t block_size, PageLinkedList *page_list, bool reuse_ll); + Result SeparatePages(KProcessAddress virt_addr, size_t num_pages, PageLinkedList *page_list, bool reuse_ll); + + Result ChangePermissions(KProcessAddress virt_addr, size_t num_pages, PageTableEntry entry_template, DisableMergeAttribute disable_merge_attr, bool refresh_mapping, bool flush_mapping, PageLinkedList *page_list, bool reuse_ll); + + static ALWAYS_INLINE void PteDataMemoryBarrier() { + cpu::DataMemoryBarrierInnerShareableStore(); + } + + static ALWAYS_INLINE void ClearPageTable(KVirtualAddress table) { + cpu::ClearPageToZero(GetVoidPointer(table)); + } + + ALWAYS_INLINE void OnTableUpdated() const { + cpu::InvalidateTlbByAsid(m_asid); + } + + ALWAYS_INLINE void OnKernelTableUpdated() const { + cpu::InvalidateEntireTlbDataOnly(); + } + + ALWAYS_INLINE void OnKernelTableSinglePageUpdated(KProcessAddress virt_addr) const { + cpu::InvalidateTlbByVaDataOnly(virt_addr); + } + + void NoteUpdated() const; + void NoteSingleKernelPageUpdated(KProcessAddress virt_addr) const; + + KVirtualAddress AllocatePageTable(PageLinkedList *page_list, bool reuse_ll) const { + KVirtualAddress table = this->GetPageTableManager().Allocate(); + + if (table == Null<KVirtualAddress>) { + if (reuse_ll && page_list->Peek()) { + table = KVirtualAddress(reinterpret_cast<uintptr_t>(page_list->Pop())); + } else { + return Null<KVirtualAddress>; + } + } + + MESOSPHERE_ASSERT(this->GetPageTableManager().GetRefCount(table) == 0); + + return table; + } + + void FreePageTable(PageLinkedList *page_list, KVirtualAddress table) const { + MESOSPHERE_ASSERT(this->GetPageTableManager().IsInPageTableHeap(table)); + MESOSPHERE_ASSERT(this->GetPageTableManager().GetRefCount(table) == 0); + page_list->Push(table); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_page_table_entry.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_page_table_entry.hpp new file mode 100644 index 00000000..ed32262f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_page_table_entry.hpp @@ -0,0 +1,472 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_select_cpu.hpp> +#include <mesosphere/kern_k_typed_address.hpp> + +namespace ams::kern::arch::arm64 { + + constexpr size_t BlocksPerContiguousBlock = 0x10; + constexpr size_t BlocksPerTable = PageSize / sizeof(u64); + + constexpr size_t L1BlockSize = 1_GB; + constexpr size_t L1ContiguousBlockSize = BlocksPerContiguousBlock * L1BlockSize; + constexpr size_t L2BlockSize = 2_MB; + constexpr size_t L2ContiguousBlockSize = BlocksPerContiguousBlock * L2BlockSize; + constexpr size_t L3BlockSize = PageSize; + constexpr size_t L3ContiguousBlockSize = BlocksPerContiguousBlock * L3BlockSize; + + class PageTableEntry { + public: + struct InvalidTag{}; + struct TableTag{}; + struct BlockTag{}; + struct SeparateContiguousTag{}; + + enum Permission : u64 { + Permission_KernelRWX = ((0ul << 53) | (1ul << 54) | (0ul << 6)), + Permission_KernelRX = ((0ul << 53) | (1ul << 54) | (2ul << 6)), + Permission_KernelR = ((1ul << 53) | (1ul << 54) | (2ul << 6)), + Permission_KernelRW = ((1ul << 53) | (1ul << 54) | (0ul << 6)), + + Permission_UserRX = ((1ul << 53) | (0ul << 54) | (3ul << 6)), + Permission_UserR = ((1ul << 53) | (1ul << 54) | (3ul << 6)), + Permission_UserRW = ((1ul << 53) | (1ul << 54) | (1ul << 6)), + }; + + enum Shareable : u64 { + Shareable_NonShareable = (0 << 8), + Shareable_OuterShareable = (2 << 8), + Shareable_InnerShareable = (3 << 8), + }; + + /* Official attributes are: */ + /* 0x00, 0x04, 0xFF, 0x44. 4-7 are unused. */ + enum PageAttribute : u64 { + PageAttribute_Device_nGnRnE = (0 << 2), + PageAttribute_Device_nGnRE = (1 << 2), + PageAttribute_NormalMemory = (2 << 2), + PageAttribute_NormalMemoryNotCacheable = (3 << 2), + }; + + enum AccessFlag : u64 { + AccessFlag_NotAccessed = (0 << 10), + AccessFlag_Accessed = (1 << 10), + }; + + enum MappingFlag : u64 { + MappingFlag_NotMapped = (0 << 0), + MappingFlag_Mapped = (1 << 0), + }; + + enum SoftwareReservedBit : u8 { + SoftwareReservedBit_None = 0, + SoftwareReservedBit_DisableMergeHead = (1u << 0), + SoftwareReservedBit_DisableMergeHeadAndBody = (1u << 1), + SoftwareReservedBit_DisableMergeHeadTail = (1u << 2), + SoftwareReservedBit_Valid = (1u << 3), + }; + + static constexpr ALWAYS_INLINE std::underlying_type<SoftwareReservedBit>::type EncodeSoftwareReservedBits(bool head, bool head_body, bool tail) { + return (head ? SoftwareReservedBit_DisableMergeHead : SoftwareReservedBit_None) | (head_body ? SoftwareReservedBit_DisableMergeHeadAndBody : SoftwareReservedBit_None) | (tail ? SoftwareReservedBit_DisableMergeHeadTail : SoftwareReservedBit_None); + } + + enum ExtensionFlag : u64 { + ExtensionFlag_DisableMergeHead = (static_cast<u64>(SoftwareReservedBit_DisableMergeHead) << 55), + ExtensionFlag_DisableMergeHeadAndBody = (static_cast<u64>(SoftwareReservedBit_DisableMergeHeadAndBody) << 55), + ExtensionFlag_DisableMergeTail = (static_cast<u64>(SoftwareReservedBit_DisableMergeHeadTail) << 55), + ExtensionFlag_Valid = (static_cast<u64>(SoftwareReservedBit_Valid) << 55), + + ExtensionFlag_ValidAndMapped = (ExtensionFlag_Valid | MappingFlag_Mapped), + ExtensionFlag_TestTableMask = (ExtensionFlag_Valid | (1ul << 1)), + }; + + enum Type : u64 { + Type_None = 0x0, + Type_L1Block = ExtensionFlag_Valid, + Type_L1Table = 0x2, + Type_L2Block = ExtensionFlag_Valid, + Type_L2Table = 0x2, + Type_L3Block = ExtensionFlag_TestTableMask, + }; + + enum ContigType : u64 { + ContigType_NotContiguous = (0x0ul << 52), + ContigType_Contiguous = (0x1ul << 52), + }; + protected: + u64 m_attributes; + public: + /* Take in a raw attribute. */ + constexpr explicit ALWAYS_INLINE PageTableEntry() : m_attributes() { /* ... */ } + constexpr explicit ALWAYS_INLINE PageTableEntry(u64 attr) : m_attributes(attr) { /* ... */ } + + constexpr explicit ALWAYS_INLINE PageTableEntry(InvalidTag) : m_attributes(0) { /* ... */ } + + /* Extend a previous attribute. */ + constexpr explicit ALWAYS_INLINE PageTableEntry(const PageTableEntry &rhs, u64 new_attr) : m_attributes(rhs.m_attributes | new_attr) { /* ... */ } + + /* Construct a new attribute. */ + constexpr explicit ALWAYS_INLINE PageTableEntry(Permission perm, PageAttribute p_a, Shareable share, MappingFlag m) + : m_attributes(static_cast<u64>(perm) | static_cast<u64>(AccessFlag_Accessed) | static_cast<u64>(p_a) | static_cast<u64>(share) | static_cast<u64>(m)) + { + /* ... */ + } + + /* Construct a table. */ + constexpr explicit ALWAYS_INLINE PageTableEntry(TableTag, KPhysicalAddress phys_addr, bool is_kernel, bool pxn, size_t ref_count) + : PageTableEntry(((is_kernel ? 0x3ul : 0) << 60) | (static_cast<u64>(pxn) << 59) | GetInteger(phys_addr) | (ref_count << 2) | 0x3) + { + /* ... */ + } + + /* Construct a block. */ + constexpr explicit ALWAYS_INLINE PageTableEntry(BlockTag, KPhysicalAddress phys_addr, const PageTableEntry &attr, u8 sw_reserved_bits, bool contig, bool page) + : PageTableEntry(attr, (static_cast<u64>(sw_reserved_bits) << 55) | (static_cast<u64>(contig) << 52) | GetInteger(phys_addr) | (page ? ExtensionFlag_TestTableMask : ExtensionFlag_Valid)) + { + /* ... */ + } + constexpr explicit ALWAYS_INLINE PageTableEntry(BlockTag, KPhysicalAddress phys_addr, const PageTableEntry &attr, SeparateContiguousTag) + : PageTableEntry(attr, GetInteger(phys_addr)) + { + /* ... */ + } + protected: + constexpr ALWAYS_INLINE u64 GetBits(size_t offset, size_t count) const { + return (m_attributes >> offset) & ((1ul << count) - 1); + } + + constexpr ALWAYS_INLINE u64 SelectBits(size_t offset, size_t count) const { + return m_attributes & (((1ul << count) - 1) << offset); + } + + constexpr ALWAYS_INLINE void SetBits(size_t offset, size_t count, u64 value) { + const u64 mask = ((1ul << count) - 1) << offset; + m_attributes &= ~mask; + m_attributes |= (value & (mask >> offset)) << offset; + } + + constexpr ALWAYS_INLINE void SetBitsDirect(size_t offset, size_t count, u64 value) { + const u64 mask = ((1ul << count) - 1) << offset; + m_attributes &= ~mask; + m_attributes |= (value & mask); + } + + constexpr ALWAYS_INLINE void SetBit(size_t offset, bool enabled) { + const u64 mask = 1ul << offset; + if (enabled) { + m_attributes |= mask; + } else { + m_attributes &= ~mask; + } + } + public: + constexpr ALWAYS_INLINE u8 GetSoftwareReservedBits() const { return this->GetBits(55, 3); } + constexpr ALWAYS_INLINE bool IsHeadMergeDisabled() const { return (this->GetSoftwareReservedBits() & SoftwareReservedBit_DisableMergeHead) != 0; } + constexpr ALWAYS_INLINE bool IsHeadAndBodyMergeDisabled() const { return (this->GetSoftwareReservedBits() & SoftwareReservedBit_DisableMergeHeadAndBody) != 0; } + constexpr ALWAYS_INLINE bool IsTailMergeDisabled() const { return (this->GetSoftwareReservedBits() & SoftwareReservedBit_DisableMergeHeadTail) != 0; } + constexpr ALWAYS_INLINE bool IsHeadOrHeadAndBodyMergeDisabled() const { return (this->GetSoftwareReservedBits() & (SoftwareReservedBit_DisableMergeHead | SoftwareReservedBit_DisableMergeHeadAndBody)) != 0; } + constexpr ALWAYS_INLINE bool IsUserExecuteNever() const { return this->GetBits(54, 1) != 0; } + constexpr ALWAYS_INLINE bool IsPrivilegedExecuteNever() const { return this->GetBits(53, 1) != 0; } + constexpr ALWAYS_INLINE bool IsContiguous() const { return this->GetBits(52, 1) != 0; } + constexpr ALWAYS_INLINE bool IsGlobal() const { return this->GetBits(11, 1) == 0; } + constexpr ALWAYS_INLINE AccessFlag GetAccessFlag() const { return static_cast<AccessFlag>(this->SelectBits(10, 1)); } + constexpr ALWAYS_INLINE Shareable GetShareable() const { return static_cast<Shareable>(this->SelectBits(8, 2)); } + constexpr ALWAYS_INLINE PageAttribute GetPageAttribute() const { return static_cast<PageAttribute>(this->SelectBits(2, 3)); } + constexpr ALWAYS_INLINE int GetAccessFlagInteger() const { return static_cast<int>(this->GetBits(10, 1)); } + constexpr ALWAYS_INLINE int GetShareableInteger() const { return static_cast<int>(this->GetBits(8, 2)); } + constexpr ALWAYS_INLINE int GetPageAttributeInteger() const { return static_cast<int>(this->GetBits(2, 3)); } + constexpr ALWAYS_INLINE bool IsReadOnly() const { return this->GetBits(7, 1) != 0; } + constexpr ALWAYS_INLINE bool IsUserAccessible() const { return this->GetBits(6, 1) != 0; } + constexpr ALWAYS_INLINE bool IsNonSecure() const { return this->GetBits(5, 1) != 0; } + + constexpr ALWAYS_INLINE u64 GetTestTableMask() const { return (m_attributes & ExtensionFlag_TestTableMask); } + + constexpr ALWAYS_INLINE bool IsBlock() const { return (m_attributes & ExtensionFlag_TestTableMask) == ExtensionFlag_Valid; } + constexpr ALWAYS_INLINE bool IsPage() const { return (m_attributes & ExtensionFlag_TestTableMask) == ExtensionFlag_TestTableMask; } + constexpr ALWAYS_INLINE bool IsTable() const { return (m_attributes & ExtensionFlag_TestTableMask) == 2; } + constexpr ALWAYS_INLINE bool IsEmpty() const { return (m_attributes & ExtensionFlag_TestTableMask) == 0; } + + constexpr ALWAYS_INLINE KPhysicalAddress GetTable() const { return this->SelectBits(12, 36); } + + constexpr ALWAYS_INLINE bool IsMappedBlock() const { return this->GetBits(0, 2) == 1; } + constexpr ALWAYS_INLINE bool IsMappedTable() const { return this->GetBits(0, 2) == 3; } + constexpr ALWAYS_INLINE bool IsMappedEmpty() const { return this->GetBits(0, 2) == 0; } + constexpr ALWAYS_INLINE bool IsMapped() const { return this->GetBits(0, 1) != 0; } + + constexpr ALWAYS_INLINE decltype(auto) SetUserExecuteNever(bool en) { this->SetBit(54, en); return *this; } + constexpr ALWAYS_INLINE decltype(auto) SetPrivilegedExecuteNever(bool en) { this->SetBit(53, en); return *this; } + constexpr ALWAYS_INLINE decltype(auto) SetContiguous(bool en) { this->SetBit(52, en); return *this; } + constexpr ALWAYS_INLINE decltype(auto) SetGlobal(bool en) { this->SetBit(11, !en); return *this; } + constexpr ALWAYS_INLINE decltype(auto) SetAccessFlag(AccessFlag f) { this->SetBitsDirect(10, 1, f); return *this; } + constexpr ALWAYS_INLINE decltype(auto) SetShareable(Shareable s) { this->SetBitsDirect(8, 2, s); return *this; } + constexpr ALWAYS_INLINE decltype(auto) SetReadOnly(bool en) { this->SetBit(7, en); return *this; } + constexpr ALWAYS_INLINE decltype(auto) SetUserAccessible(bool en) { this->SetBit(6, en); return *this; } + constexpr ALWAYS_INLINE decltype(auto) SetPageAttribute(PageAttribute a) { this->SetBitsDirect(2, 3, a); return *this; } + constexpr ALWAYS_INLINE decltype(auto) SetMapped(bool m) { static_assert(static_cast<u64>(MappingFlag_Mapped == (1 << 0))); this->SetBit(0, m); return *this; } + + constexpr ALWAYS_INLINE size_t GetTableReferenceCount() const { return this->GetBits(2, 10); } + constexpr ALWAYS_INLINE decltype(auto) SetTableReferenceCount(size_t num) { this->SetBits(2, 10, num); return *this; } + + constexpr ALWAYS_INLINE decltype(auto) OpenTableReferences(size_t num) { MESOSPHERE_ASSERT(this->GetTableReferenceCount() + num <= BlocksPerTable + 1); return this->SetTableReferenceCount(this->GetTableReferenceCount() + num); } + constexpr ALWAYS_INLINE decltype(auto) CloseTableReferences(size_t num) { MESOSPHERE_ASSERT(this->GetTableReferenceCount() >= num); return this->SetTableReferenceCount(this->GetTableReferenceCount() - num); } + + constexpr ALWAYS_INLINE decltype(auto) SetValid() { MESOSPHERE_ASSERT((m_attributes & ExtensionFlag_Valid) == 0); m_attributes |= ExtensionFlag_Valid; return *this; } + + constexpr ALWAYS_INLINE u64 GetEntryTemplateForMerge() const { + constexpr u64 BaseMask = (0xFFFF000000000FFFul & ~static_cast<u64>((0x1ul << 52) | ExtensionFlag_TestTableMask | ExtensionFlag_DisableMergeHead | ExtensionFlag_DisableMergeHeadAndBody | ExtensionFlag_DisableMergeTail)); + return m_attributes & BaseMask; + } + + constexpr ALWAYS_INLINE bool IsForMerge(u64 attr) const { + constexpr u64 BaseMaskForMerge = ~static_cast<u64>(ExtensionFlag_DisableMergeHead | ExtensionFlag_DisableMergeHeadAndBody | ExtensionFlag_DisableMergeTail); + return (m_attributes & BaseMaskForMerge) == attr; + } + + static constexpr ALWAYS_INLINE u64 GetEntryTemplateForSeparateContiguousMask(size_t idx) { + constexpr u64 BaseMask = (0xFFFF000000000FFFul & ~static_cast<u64>((0x1ul << 52) | ExtensionFlag_DisableMergeHead | ExtensionFlag_DisableMergeHeadAndBody | ExtensionFlag_DisableMergeTail)); + if (idx == 0) { + return BaseMask | ExtensionFlag_DisableMergeHead | ExtensionFlag_DisableMergeHeadAndBody; + } else if (idx < BlocksPerContiguousBlock - 1) { + return BaseMask; + } else { + return BaseMask | ExtensionFlag_DisableMergeTail; + } + } + + constexpr ALWAYS_INLINE u64 GetEntryTemplateForSeparateContiguous(size_t idx) const { + return m_attributes & GetEntryTemplateForSeparateContiguousMask(idx); + } + + static constexpr ALWAYS_INLINE u64 GetEntryTemplateForSeparateMask(size_t idx) { + constexpr u64 BaseMask = (0xFFFF000000000FFFul & ~static_cast<u64>((0x1ul << 52) | ExtensionFlag_TestTableMask | ExtensionFlag_DisableMergeHead | ExtensionFlag_DisableMergeHeadAndBody | ExtensionFlag_DisableMergeTail)); + if (idx == 0) { + return BaseMask | ExtensionFlag_DisableMergeHead | ExtensionFlag_DisableMergeHeadAndBody; + } else if (idx < BlocksPerContiguousBlock) { + return BaseMask | ExtensionFlag_DisableMergeHeadAndBody; + } else if (idx < BlocksPerTable - 1) { + return BaseMask; + } else { + return BaseMask | ExtensionFlag_DisableMergeTail; + } + } + + constexpr ALWAYS_INLINE u64 GetEntryTemplateForSeparate(size_t idx) const { + return m_attributes & GetEntryTemplateForSeparateMask(idx); + } + + constexpr ALWAYS_INLINE u64 GetRawAttributesUnsafe() const { + return m_attributes; + } + + constexpr ALWAYS_INLINE u64 GetRawAttributesUnsafeForSwap() const { + return m_attributes; + } + protected: + constexpr ALWAYS_INLINE u64 GetRawAttributes() const { + return m_attributes; + } + }; + + static_assert(sizeof(PageTableEntry) == sizeof(u64)); + + constexpr inline PageTableEntry InvalidPageTableEntry = PageTableEntry(PageTableEntry::InvalidTag{}); + + constexpr inline size_t MaxPageTableEntries = PageSize / sizeof(PageTableEntry); + + class L1PageTableEntry : public PageTableEntry { + public: + constexpr explicit ALWAYS_INLINE L1PageTableEntry(InvalidTag) : PageTableEntry(InvalidTag{}) { /* ... */ } + + constexpr explicit ALWAYS_INLINE L1PageTableEntry(TableTag, KPhysicalAddress phys_addr, bool pxn) + : PageTableEntry((0x3ul << 60) | (static_cast<u64>(pxn) << 59) | GetInteger(phys_addr) | 0x3) + { + /* ... */ + } + + constexpr explicit ALWAYS_INLINE L1PageTableEntry(TableTag, KPhysicalAddress phys_addr, bool is_kernel, bool pxn) + : PageTableEntry(((is_kernel ? 0x3ul : 0) << 60) | (static_cast<u64>(pxn) << 59) | GetInteger(phys_addr) | 0x3) + { + /* ... */ + } + + constexpr explicit ALWAYS_INLINE L1PageTableEntry(BlockTag, KPhysicalAddress phys_addr, const PageTableEntry &attr, u8 sw_reserved_bits, bool contig) + : PageTableEntry(attr, (static_cast<u64>(sw_reserved_bits) << 55) | (static_cast<u64>(contig) << 52) | GetInteger(phys_addr) | 0x1) + { + /* ... */ + } + + constexpr ALWAYS_INLINE KPhysicalAddress GetBlock() const { + return this->SelectBits(30, 18); + } + + constexpr ALWAYS_INLINE KPhysicalAddress GetTable() const { + return this->SelectBits(12, 36); + } + + constexpr ALWAYS_INLINE bool GetTable(KPhysicalAddress &out) const { + if (this->IsTable()) { + out = this->GetTable(); + return true; + } else { + return false; + } + } + + static constexpr ALWAYS_INLINE u64 GetEntryTemplateForL2BlockMask(size_t idx) { + constexpr u64 BaseMask = (0xFFF0000000000FFFul & ~static_cast<u64>((0x1ul << 52) | ExtensionFlag_TestTableMask | ExtensionFlag_DisableMergeHead | ExtensionFlag_DisableMergeHeadAndBody | ExtensionFlag_DisableMergeTail)); + if (idx == 0) { + return BaseMask | ExtensionFlag_DisableMergeHead | ExtensionFlag_DisableMergeHeadAndBody; + } else if (idx < L2ContiguousBlockSize / L2BlockSize) { + return BaseMask | ExtensionFlag_DisableMergeHeadAndBody; + } else if (idx < (L1BlockSize - L2ContiguousBlockSize) / L2BlockSize) { + return BaseMask; + } else { + return BaseMask | ExtensionFlag_DisableMergeTail; + } + } + + constexpr ALWAYS_INLINE u64 GetEntryTemplateForL2Block(size_t idx) const { + return m_attributes & GetEntryTemplateForL2BlockMask(idx); + } + + constexpr ALWAYS_INLINE bool IsCompatibleWithAttribute(const PageTableEntry &rhs, u8 sw_reserved_bits, bool contig) const { + /* Check whether this has the same permission/etc as the desired attributes. */ + return L1PageTableEntry(BlockTag{}, this->GetBlock(), rhs, sw_reserved_bits, contig).GetRawAttributes() == this->GetRawAttributes(); + } + }; + + class L2PageTableEntry : public PageTableEntry { + public: + constexpr explicit ALWAYS_INLINE L2PageTableEntry(InvalidTag) : PageTableEntry(InvalidTag{}) { /* ... */ } + + constexpr explicit ALWAYS_INLINE L2PageTableEntry(TableTag, KPhysicalAddress phys_addr, bool pxn) + : PageTableEntry((0x3ul << 60) | (static_cast<u64>(pxn) << 59) | GetInteger(phys_addr) | 0x3) + { + /* ... */ + } + + constexpr explicit ALWAYS_INLINE L2PageTableEntry(TableTag, KPhysicalAddress phys_addr, bool is_kernel, bool pxn) + : PageTableEntry(((is_kernel ? 0x3ul : 0) << 60) | (static_cast<u64>(pxn) << 59) | GetInteger(phys_addr) | 0x3) + { + /* ... */ + } + + constexpr explicit ALWAYS_INLINE L2PageTableEntry(BlockTag, KPhysicalAddress phys_addr, const PageTableEntry &attr, u8 sw_reserved_bits, bool contig) + : PageTableEntry(attr, (static_cast<u64>(sw_reserved_bits) << 55) | (static_cast<u64>(contig) << 52) | GetInteger(phys_addr) | 0x1) + { + /* ... */ + } + + constexpr ALWAYS_INLINE KPhysicalAddress GetBlock() const { + return this->SelectBits(21, 27); + } + + constexpr ALWAYS_INLINE KPhysicalAddress GetTable() const { + return this->SelectBits(12, 36); + } + + constexpr ALWAYS_INLINE bool GetTable(KPhysicalAddress &out) const { + if (this->IsTable()) { + out = this->GetTable(); + return true; + } else { + return false; + } + } + + static constexpr ALWAYS_INLINE u64 GetEntryTemplateForL2BlockMask(size_t idx) { + constexpr u64 BaseMask = (0xFFF0000000000FFFul & ~static_cast<u64>((0x1ul << 52) | ExtensionFlag_TestTableMask | ExtensionFlag_DisableMergeHead | ExtensionFlag_DisableMergeHeadAndBody | ExtensionFlag_DisableMergeTail)); + if (idx == 0) { + return BaseMask | ExtensionFlag_DisableMergeHead | ExtensionFlag_DisableMergeHeadAndBody; + } else if (idx < (L2ContiguousBlockSize / L2BlockSize) - 1) { + return BaseMask; + } else { + return BaseMask | ExtensionFlag_DisableMergeTail; + } + } + + constexpr ALWAYS_INLINE u64 GetEntryTemplateForL2Block(size_t idx) const { + return m_attributes & GetEntryTemplateForL2BlockMask(idx); + } + + static constexpr ALWAYS_INLINE u64 GetEntryTemplateForL3BlockMask(size_t idx) { + constexpr u64 BaseMask = (0xFFF0000000000FFFul & ~static_cast<u64>((0x1ul << 52) | ExtensionFlag_TestTableMask | ExtensionFlag_DisableMergeHead | ExtensionFlag_DisableMergeHeadAndBody | ExtensionFlag_DisableMergeTail)); + if (idx == 0) { + return BaseMask | ExtensionFlag_DisableMergeHead | ExtensionFlag_DisableMergeHeadAndBody; + } else if (idx < L3ContiguousBlockSize / L3BlockSize) { + return BaseMask | ExtensionFlag_DisableMergeHeadAndBody; + } else if (idx < (L2BlockSize - L3ContiguousBlockSize) / L3BlockSize) { + return BaseMask; + } else { + return BaseMask | ExtensionFlag_DisableMergeTail; + } + } + + constexpr ALWAYS_INLINE u64 GetEntryTemplateForL3Block(size_t idx) const { + return m_attributes & GetEntryTemplateForL3BlockMask(idx); + } + + constexpr ALWAYS_INLINE bool IsCompatibleWithAttribute(const PageTableEntry &rhs, u8 sw_reserved_bits, bool contig) const { + /* Check whether this has the same permission/etc as the desired attributes. */ + return L2PageTableEntry(BlockTag{}, this->GetBlock(), rhs, sw_reserved_bits, contig).GetRawAttributes() == this->GetRawAttributes(); + } + }; + + class L3PageTableEntry : public PageTableEntry { + public: + constexpr explicit ALWAYS_INLINE L3PageTableEntry(InvalidTag) : PageTableEntry(InvalidTag{}) { /* ... */ } + + constexpr explicit ALWAYS_INLINE L3PageTableEntry(BlockTag, KPhysicalAddress phys_addr, const PageTableEntry &attr, u8 sw_reserved_bits, bool contig) + : PageTableEntry(attr, (static_cast<u64>(sw_reserved_bits) << 55) | (static_cast<u64>(contig) << 52) | GetInteger(phys_addr) | 0x3) + { + /* ... */ + } + + constexpr ALWAYS_INLINE bool IsBlock() const { return (GetRawAttributes() & ExtensionFlag_TestTableMask) == ExtensionFlag_TestTableMask; } + constexpr ALWAYS_INLINE bool IsMappedBlock() const { return this->GetBits(0, 2) == 3; } + + constexpr ALWAYS_INLINE KPhysicalAddress GetBlock() const { + return this->SelectBits(12, 36); + } + + static constexpr ALWAYS_INLINE u64 GetEntryTemplateForL3BlockMask(size_t idx) { + constexpr u64 BaseMask = (0xFFF0000000000FFFul & ~static_cast<u64>((0x1ul << 52) | ExtensionFlag_TestTableMask | ExtensionFlag_DisableMergeHead | ExtensionFlag_DisableMergeHeadAndBody | ExtensionFlag_DisableMergeTail)); + if (idx == 0) { + return BaseMask | ExtensionFlag_DisableMergeHead | ExtensionFlag_DisableMergeHeadAndBody; + } else if (idx < (L3ContiguousBlockSize / L3BlockSize) - 1) { + return BaseMask; + } else { + return BaseMask | ExtensionFlag_DisableMergeTail; + } + } + + constexpr ALWAYS_INLINE u64 GetEntryTemplateForL3Block(size_t idx) const { + return m_attributes & GetEntryTemplateForL3BlockMask(idx); + } + + constexpr ALWAYS_INLINE bool IsCompatibleWithAttribute(const PageTableEntry &rhs, u8 sw_reserved_bits, bool contig) const { + /* Check whether this has the same permission/etc as the desired attributes. */ + return L3PageTableEntry(BlockTag{}, this->GetBlock(), rhs, sw_reserved_bits, contig).GetRawAttributes() == this->GetRawAttributes(); + } + }; + + constexpr inline L1PageTableEntry InvalidL1PageTableEntry = L1PageTableEntry(PageTableEntry::InvalidTag{}); + constexpr inline L2PageTableEntry InvalidL2PageTableEntry = L2PageTableEntry(PageTableEntry::InvalidTag{}); + constexpr inline L3PageTableEntry InvalidL3PageTableEntry = L3PageTableEntry(PageTableEntry::InvalidTag{}); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_page_table_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_page_table_impl.hpp new file mode 100644 index 00000000..b1a97fca --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_page_table_impl.hpp @@ -0,0 +1,161 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_select_cpu.hpp> +#include <mesosphere/kern_k_typed_address.hpp> +#include <mesosphere/kern_k_memory_layout.hpp> +#include <mesosphere/arch/arm64/kern_k_page_table_entry.hpp> + +namespace ams::kern::arch::arm64 { + + class KPageTableImpl { + NON_COPYABLE(KPageTableImpl); + NON_MOVEABLE(KPageTableImpl); + public: + struct TraversalEntry { + KPhysicalAddress phys_addr; + size_t block_size; + u8 sw_reserved_bits; + u8 attr; + + constexpr bool IsHeadMergeDisabled() const { return (this->sw_reserved_bits & PageTableEntry::SoftwareReservedBit_DisableMergeHead) != 0; } + constexpr bool IsHeadAndBodyMergeDisabled() const { return (this->sw_reserved_bits & PageTableEntry::SoftwareReservedBit_DisableMergeHeadAndBody) != 0; } + constexpr bool IsTailMergeDisabled() const { return (this->sw_reserved_bits & PageTableEntry::SoftwareReservedBit_DisableMergeHeadTail) != 0; } + }; + + enum EntryLevel : u32 { + EntryLevel_L3 = 0, + EntryLevel_L2 = 1, + EntryLevel_L1 = 2, + EntryLevel_Count = 3, + }; + + struct TraversalContext { + PageTableEntry *level_entries[EntryLevel_Count]; + EntryLevel level; + bool is_contiguous; + }; + + using EntryUpdatedCallback = void (*)(const void *); + private: + static constexpr size_t PageBits = util::CountTrailingZeros(PageSize); + static constexpr size_t NumLevels = 3; + static constexpr size_t LevelBits = 9; + static_assert(NumLevels > 0); + + template<size_t Offset, size_t Count> + static constexpr ALWAYS_INLINE u64 GetBits(u64 value) { + return (value >> Offset) & ((1ul << Count) - 1); + } + + static constexpr ALWAYS_INLINE u64 GetBits(u64 value, size_t offset, size_t count) { + return (value >> offset) & ((1ul << count) - 1); + } + + template<size_t Offset, size_t Count> + static constexpr ALWAYS_INLINE u64 SelectBits(u64 value) { + return value & (((1ul << Count) - 1) << Offset); + } + + static constexpr ALWAYS_INLINE u64 SelectBits(u64 value, size_t offset, size_t count) { + return value & (((1ul << count) - 1) << offset); + } + + static constexpr ALWAYS_INLINE uintptr_t GetL0Index(KProcessAddress addr) { return GetBits<PageBits + LevelBits * (NumLevels - 0), LevelBits>(GetInteger(addr)); } + static constexpr ALWAYS_INLINE uintptr_t GetL1Index(KProcessAddress addr) { return GetBits<PageBits + LevelBits * (NumLevels - 1), LevelBits>(GetInteger(addr)); } + static constexpr ALWAYS_INLINE uintptr_t GetL2Index(KProcessAddress addr) { return GetBits<PageBits + LevelBits * (NumLevels - 2), LevelBits>(GetInteger(addr)); } + static constexpr ALWAYS_INLINE uintptr_t GetL3Index(KProcessAddress addr) { return GetBits<PageBits + LevelBits * (NumLevels - 3), LevelBits>(GetInteger(addr)); } + + static constexpr ALWAYS_INLINE uintptr_t GetL1Offset(KProcessAddress addr) { return GetBits<0, PageBits + LevelBits * (NumLevels - 1)>(GetInteger(addr)); } + static constexpr ALWAYS_INLINE uintptr_t GetL2Offset(KProcessAddress addr) { return GetBits<0, PageBits + LevelBits * (NumLevels - 2)>(GetInteger(addr)); } + static constexpr ALWAYS_INLINE uintptr_t GetL3Offset(KProcessAddress addr) { return GetBits<0, PageBits + LevelBits * (NumLevels - 3)>(GetInteger(addr)); } + static constexpr ALWAYS_INLINE uintptr_t GetContiguousL1Offset(KProcessAddress addr) { return GetBits<0, PageBits + LevelBits * (NumLevels - 1) + 4>(GetInteger(addr)); } + static constexpr ALWAYS_INLINE uintptr_t GetContiguousL2Offset(KProcessAddress addr) { return GetBits<0, PageBits + LevelBits * (NumLevels - 2) + 4>(GetInteger(addr)); } + static constexpr ALWAYS_INLINE uintptr_t GetContiguousL3Offset(KProcessAddress addr) { return GetBits<0, PageBits + LevelBits * (NumLevels - 3) + 4>(GetInteger(addr)); } + + static constexpr ALWAYS_INLINE uintptr_t GetBlock(const PageTableEntry *pte, EntryLevel level) { return SelectBits(pte->GetRawAttributesUnsafe(), PageBits + LevelBits * level, LevelBits * (NumLevels + 1 - level)); } + static constexpr ALWAYS_INLINE uintptr_t GetOffset(KProcessAddress addr, EntryLevel level) { return GetBits(GetInteger(addr), 0, PageBits + LevelBits * level); } + + static ALWAYS_INLINE KVirtualAddress GetPageTableVirtualAddress(KPhysicalAddress addr) { + return KMemoryLayout::GetLinearVirtualAddress(addr); + } + public: + static constexpr ALWAYS_INLINE uintptr_t GetLevelIndex(KProcessAddress addr, EntryLevel level) { return GetBits(GetInteger(addr), PageBits + LevelBits * level, LevelBits); } + private: + L1PageTableEntry *m_table; + bool m_is_kernel; + u32 m_num_entries; + public: + ALWAYS_INLINE KVirtualAddress GetTableEntry(KVirtualAddress table, size_t index) const { + return table + index * sizeof(PageTableEntry); + } + + ALWAYS_INLINE L1PageTableEntry *GetL1Entry(KProcessAddress address) const { + return GetPointer<L1PageTableEntry>(GetTableEntry(KVirtualAddress(m_table), GetL1Index(address) & (m_num_entries - 1))); + } + + ALWAYS_INLINE L2PageTableEntry *GetL2EntryFromTable(KVirtualAddress table, KProcessAddress address) const { + return GetPointer<L2PageTableEntry>(GetTableEntry(table, GetL2Index(address))); + } + + ALWAYS_INLINE L2PageTableEntry *GetL2Entry(const L1PageTableEntry *entry, KProcessAddress address) const { + return GetL2EntryFromTable(KMemoryLayout::GetLinearVirtualAddress(entry->GetTable()), address); + } + + ALWAYS_INLINE L3PageTableEntry *GetL3EntryFromTable(KVirtualAddress table, KProcessAddress address) const { + return GetPointer<L3PageTableEntry>(GetTableEntry(table, GetL3Index(address))); + } + + ALWAYS_INLINE L3PageTableEntry *GetL3Entry(const L2PageTableEntry *entry, KProcessAddress address) const { + return GetL3EntryFromTable(KMemoryLayout::GetLinearVirtualAddress(entry->GetTable()), address); + } + + static constexpr size_t GetBlockSize(EntryLevel level, bool contiguous = false) { + return 1 << (PageBits + LevelBits * level + 4 * contiguous); + } + public: + constexpr explicit KPageTableImpl(util::ConstantInitializeTag) : m_table(), m_is_kernel(), m_num_entries() { /* ... */ } + + explicit KPageTableImpl() { /* ... */ } + + size_t GetNumL1Entries() const { return m_num_entries; } + + NOINLINE void InitializeForKernel(void *tb, KVirtualAddress start, KVirtualAddress end); + NOINLINE void InitializeForProcess(void *tb, KVirtualAddress start, KVirtualAddress end); + L1PageTableEntry *Finalize(); + + void Dump(uintptr_t start, size_t size) const; + size_t CountPageTables() const; + + bool BeginTraversal(TraversalEntry *out_entry, TraversalContext *out_context, KProcessAddress address) const; + bool ContinueTraversal(TraversalEntry *out_entry, TraversalContext *context) const; + + bool GetPhysicalAddress(KPhysicalAddress *out, KProcessAddress virt_addr) const; + + static bool MergePages(KVirtualAddress *out, TraversalContext *context, EntryUpdatedCallback on_entry_updated, const void *pt); + void SeparatePages(TraversalEntry *entry, TraversalContext *context, KProcessAddress address, PageTableEntry *pte, EntryUpdatedCallback on_entry_updated, const void *pt) const; + + KProcessAddress GetAddressForContext(const TraversalContext *context) const { + KProcessAddress addr = m_is_kernel ? static_cast<uintptr_t>(-GetBlockSize(EntryLevel_L1)) * m_num_entries : 0; + for (u32 level = context->level; level <= EntryLevel_L1; ++level) { + addr += ((reinterpret_cast<uintptr_t>(context->level_entries[level]) / sizeof(PageTableEntry)) & (BlocksPerTable - 1)) << (PageBits + LevelBits * level); + } + return addr; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp new file mode 100644 index 00000000..e18a6fbc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp @@ -0,0 +1,348 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/arch/arm64/kern_k_page_table.hpp> + +namespace ams::kern::arch::arm64 { + + class KProcessPageTable { + private: + KPageTable m_page_table; + public: + void Activate(size_t process_index, u64 id) { + /* Activate the page table with the specified contextidr. */ + m_page_table.ActivateProcess(process_index, id); + } + + Result Initialize(ams::svc::CreateProcessFlag flags, bool from_back, KMemoryManager::Pool pool, KProcessAddress code_address, size_t code_size, KSystemResource *system_resource, KResourceLimit *resource_limit, size_t process_index) { + R_RETURN(m_page_table.InitializeForProcess(flags, from_back, pool, code_address, code_size, system_resource, resource_limit, process_index)); + } + + void Finalize() { m_page_table.Finalize(); } + + ALWAYS_INLINE KScopedLightLock AcquireDeviceMapLock() { + return m_page_table.AcquireDeviceMapLock(); + } + + Result SetMemoryPermission(KProcessAddress addr, size_t size, ams::svc::MemoryPermission perm) { + R_RETURN(m_page_table.SetMemoryPermission(addr, size, perm)); + } + + Result SetProcessMemoryPermission(KProcessAddress addr, size_t size, ams::svc::MemoryPermission perm) { + R_RETURN(m_page_table.SetProcessMemoryPermission(addr, size, perm)); + } + + Result SetMemoryAttribute(KProcessAddress addr, size_t size, u32 mask, u32 attr) { + R_RETURN(m_page_table.SetMemoryAttribute(addr, size, mask, attr)); + } + + Result SetHeapSize(KProcessAddress *out, size_t size) { + R_RETURN(m_page_table.SetHeapSize(out, size)); + } + + Result SetMaxHeapSize(size_t size) { + R_RETURN(m_page_table.SetMaxHeapSize(size)); + } + + Result QueryInfo(KMemoryInfo *out_info, ams::svc::PageInfo *out_page_info, KProcessAddress addr) const { + R_RETURN(m_page_table.QueryInfo(out_info, out_page_info, addr)); + } + + Result QueryPhysicalAddress(ams::svc::PhysicalMemoryInfo *out, KProcessAddress address) const { + R_RETURN(m_page_table.QueryPhysicalAddress(out, address)); + } + + Result QueryStaticMapping(KProcessAddress *out, KPhysicalAddress address, size_t size) const { + R_RETURN(m_page_table.QueryStaticMapping(out, address, size)); + } + + Result QueryIoMapping(KProcessAddress *out, KPhysicalAddress address, size_t size) const { + R_RETURN(m_page_table.QueryIoMapping(out, address, size)); + } + + Result MapMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size) { + R_RETURN(m_page_table.MapMemory(dst_address, src_address, size)); + } + + Result UnmapMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size) { + R_RETURN(m_page_table.UnmapMemory(dst_address, src_address, size)); + } + + Result MapCodeMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size) { + R_RETURN(m_page_table.MapCodeMemory(dst_address, src_address, size)); + } + + Result UnmapCodeMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size) { + R_RETURN(m_page_table.UnmapCodeMemory(dst_address, src_address, size)); + } + + Result MapIo(KPhysicalAddress phys_addr, size_t size, KMemoryPermission perm) { + R_RETURN(m_page_table.MapIo(phys_addr, size, perm)); + } + + Result MapIoRegion(KProcessAddress dst_address, KPhysicalAddress phys_addr, size_t size, ams::svc::MemoryMapping mapping, ams::svc::MemoryPermission perm) { + R_RETURN(m_page_table.MapIoRegion(dst_address, phys_addr, size, mapping, perm)); + } + + Result UnmapIoRegion(KProcessAddress dst_address, KPhysicalAddress phys_addr, size_t size, ams::svc::MemoryMapping mapping) { + R_RETURN(m_page_table.UnmapIoRegion(dst_address, phys_addr, size, mapping)); + } + + Result MapStatic(KPhysicalAddress phys_addr, size_t size, KMemoryPermission perm) { + R_RETURN(m_page_table.MapStatic(phys_addr, size, perm)); + } + + Result MapRegion(KMemoryRegionType region_type, KMemoryPermission perm) { + R_RETURN(m_page_table.MapRegion(region_type, perm)); + } + + Result MapInsecurePhysicalMemory(KProcessAddress address, size_t size) { + R_RETURN(m_page_table.MapInsecurePhysicalMemory(address, size)); + } + + Result UnmapInsecurePhysicalMemory(KProcessAddress address, size_t size) { + R_RETURN(m_page_table.UnmapInsecurePhysicalMemory(address, size)); + } + + Result MapPageGroup(KProcessAddress addr, const KPageGroup &pg, KMemoryState state, KMemoryPermission perm) { + R_RETURN(m_page_table.MapPageGroup(addr, pg, state, perm)); + } + + Result UnmapPageGroup(KProcessAddress address, const KPageGroup &pg, KMemoryState state) { + R_RETURN(m_page_table.UnmapPageGroup(address, pg, state)); + } + + Result MapPages(KProcessAddress *out_addr, size_t num_pages, size_t alignment, KPhysicalAddress phys_addr, KMemoryState state, KMemoryPermission perm) { + R_RETURN(m_page_table.MapPages(out_addr, num_pages, alignment, phys_addr, state, perm)); + } + + Result MapPages(KProcessAddress *out_addr, size_t num_pages, KMemoryState state, KMemoryPermission perm) { + R_RETURN(m_page_table.MapPages(out_addr, num_pages, state, perm)); + } + + Result MapPages(KProcessAddress address, size_t num_pages, KMemoryState state, KMemoryPermission perm) { + R_RETURN(m_page_table.MapPages(address, num_pages, state, perm)); + } + + Result UnmapPages(KProcessAddress addr, size_t num_pages, KMemoryState state) { + R_RETURN(m_page_table.UnmapPages(addr, num_pages, state)); + } + + Result MakeAndOpenPageGroup(KPageGroup *out, KProcessAddress address, size_t num_pages, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr) { + R_RETURN(m_page_table.MakeAndOpenPageGroup(out, address, num_pages, state_mask, state, perm_mask, perm, attr_mask, attr)); + } + + Result InvalidateProcessDataCache(KProcessAddress address, size_t size) { + R_RETURN(m_page_table.InvalidateProcessDataCache(address, size)); + } + + Result InvalidateCurrentProcessDataCache(KProcessAddress address, size_t size) { + R_RETURN(m_page_table.InvalidateCurrentProcessDataCache(address, size)); + } + + Result ReadDebugMemory(void *buffer, KProcessAddress address, size_t size, bool force_debug_prod) { + R_RETURN(m_page_table.ReadDebugMemory(buffer, address, size, force_debug_prod)); + } + + Result ReadDebugIoMemory(void *buffer, KProcessAddress address, size_t size, KMemoryState state) { + R_RETURN(m_page_table.ReadDebugIoMemory(buffer, address, size, state)); + } + + Result WriteDebugMemory(KProcessAddress address, const void *buffer, size_t size) { + R_RETURN(m_page_table.WriteDebugMemory(address, buffer, size)); + } + + Result WriteDebugIoMemory(KProcessAddress address, const void *buffer, size_t size, KMemoryState state) { + R_RETURN(m_page_table.WriteDebugIoMemory(address, buffer, size, state)); + } + + Result LockForMapDeviceAddressSpace(bool *out_is_io, KProcessAddress address, size_t size, KMemoryPermission perm, bool is_aligned, bool check_heap) { + R_RETURN(m_page_table.LockForMapDeviceAddressSpace(out_is_io, address, size, perm, is_aligned, check_heap)); + } + + Result LockForUnmapDeviceAddressSpace(KProcessAddress address, size_t size, bool check_heap) { + R_RETURN(m_page_table.LockForUnmapDeviceAddressSpace(address, size, check_heap)); + } + + Result UnlockForDeviceAddressSpace(KProcessAddress address, size_t size) { + R_RETURN(m_page_table.UnlockForDeviceAddressSpace(address, size)); + } + + Result UnlockForDeviceAddressSpacePartialMap(KProcessAddress address, size_t size) { + R_RETURN(m_page_table.UnlockForDeviceAddressSpacePartialMap(address, size)); + } + + Result OpenMemoryRangeForMapDeviceAddressSpace(KPageTableBase::MemoryRange *out, KProcessAddress address, size_t size, KMemoryPermission perm, bool is_aligned) { + R_RETURN(m_page_table.OpenMemoryRangeForMapDeviceAddressSpace(out, address, size, perm, is_aligned)); + } + + Result OpenMemoryRangeForUnmapDeviceAddressSpace(KPageTableBase::MemoryRange *out, KProcessAddress address, size_t size) { + R_RETURN(m_page_table.OpenMemoryRangeForUnmapDeviceAddressSpace(out, address, size)); + } + + Result LockForIpcUserBuffer(KPhysicalAddress *out, KProcessAddress address, size_t size) { + R_RETURN(m_page_table.LockForIpcUserBuffer(out, address, size)); + } + + Result UnlockForIpcUserBuffer(KProcessAddress address, size_t size) { + R_RETURN(m_page_table.UnlockForIpcUserBuffer(address, size)); + } + + Result LockForTransferMemory(KPageGroup *out, KProcessAddress address, size_t size, KMemoryPermission perm) { + R_RETURN(m_page_table.LockForTransferMemory(out, address, size, perm)); + } + + Result UnlockForTransferMemory(KProcessAddress address, size_t size, const KPageGroup &pg) { + R_RETURN(m_page_table.UnlockForTransferMemory(address, size, pg)); + } + + Result LockForCodeMemory(KPageGroup *out, KProcessAddress address, size_t size) { + R_RETURN(m_page_table.LockForCodeMemory(out, address, size)); + } + + Result UnlockForCodeMemory(KProcessAddress address, size_t size, const KPageGroup &pg) { + R_RETURN(m_page_table.UnlockForCodeMemory(address, size, pg)); + } + + Result OpenMemoryRangeForProcessCacheOperation(KPageTableBase::MemoryRange *out, KProcessAddress address, size_t size) { + R_RETURN(m_page_table.OpenMemoryRangeForProcessCacheOperation(out, address, size)); + } + + Result CopyMemoryFromLinearToUser(KProcessAddress dst_addr, size_t size, KProcessAddress src_addr, u32 src_state_mask, u32 src_state, KMemoryPermission src_test_perm, u32 src_attr_mask, u32 src_attr) { + R_RETURN(m_page_table.CopyMemoryFromLinearToUser(dst_addr, size, src_addr, src_state_mask, src_state, src_test_perm, src_attr_mask, src_attr)); + } + + Result CopyMemoryFromLinearToKernel(KProcessAddress dst_addr, size_t size, KProcessAddress src_addr, u32 src_state_mask, u32 src_state, KMemoryPermission src_test_perm, u32 src_attr_mask, u32 src_attr) { + R_RETURN(m_page_table.CopyMemoryFromLinearToKernel(dst_addr, size, src_addr, src_state_mask, src_state, src_test_perm, src_attr_mask, src_attr)); + } + + Result CopyMemoryFromUserToLinear(KProcessAddress dst_addr, size_t size, u32 dst_state_mask, u32 dst_state, KMemoryPermission dst_test_perm, u32 dst_attr_mask, u32 dst_attr, KProcessAddress src_addr) { + R_RETURN(m_page_table.CopyMemoryFromUserToLinear(dst_addr, size, dst_state_mask, dst_state, dst_test_perm, dst_attr_mask, dst_attr, src_addr)); + } + + Result CopyMemoryFromKernelToLinear(KProcessAddress dst_addr, size_t size, u32 dst_state_mask, u32 dst_state, KMemoryPermission dst_test_perm, u32 dst_attr_mask, u32 dst_attr, KProcessAddress src_addr) { + R_RETURN(m_page_table.CopyMemoryFromKernelToLinear(dst_addr, size, dst_state_mask, dst_state, dst_test_perm, dst_attr_mask, dst_attr, src_addr)); + } + + Result CopyMemoryFromHeapToHeap(KProcessPageTable &dst_page_table, KProcessAddress dst_addr, size_t size, u32 dst_state_mask, u32 dst_state, KMemoryPermission dst_test_perm, u32 dst_attr_mask, u32 dst_attr, KProcessAddress src_addr, u32 src_state_mask, u32 src_state, KMemoryPermission src_test_perm, u32 src_attr_mask, u32 src_attr) { + R_RETURN(m_page_table.CopyMemoryFromHeapToHeap(dst_page_table.m_page_table, dst_addr, size, dst_state_mask, dst_state, dst_test_perm, dst_attr_mask, dst_attr, src_addr, src_state_mask, src_state, src_test_perm, src_attr_mask, src_attr)); + } + + Result CopyMemoryFromHeapToHeapWithoutCheckDestination(KProcessPageTable &dst_page_table, KProcessAddress dst_addr, size_t size, u32 dst_state_mask, u32 dst_state, KMemoryPermission dst_test_perm, u32 dst_attr_mask, u32 dst_attr, KProcessAddress src_addr, u32 src_state_mask, u32 src_state, KMemoryPermission src_test_perm, u32 src_attr_mask, u32 src_attr) { + R_RETURN(m_page_table.CopyMemoryFromHeapToHeapWithoutCheckDestination(dst_page_table.m_page_table, dst_addr, size, dst_state_mask, dst_state, dst_test_perm, dst_attr_mask, dst_attr, src_addr, src_state_mask, src_state, src_test_perm, src_attr_mask, src_attr)); + } + + Result SetupForIpc(KProcessAddress *out_dst_addr, size_t size, KProcessAddress src_addr, KProcessPageTable &src_page_table, KMemoryPermission test_perm, KMemoryState dst_state, bool send) { + R_RETURN(m_page_table.SetupForIpc(out_dst_addr, size, src_addr, src_page_table.m_page_table, test_perm, dst_state, send)); + } + + Result CleanupForIpcServer(KProcessAddress address, size_t size, KMemoryState dst_state) { + R_RETURN(m_page_table.CleanupForIpcServer(address, size, dst_state)); + } + + Result CleanupForIpcClient(KProcessAddress address, size_t size, KMemoryState dst_state) { + R_RETURN(m_page_table.CleanupForIpcClient(address, size, dst_state)); + } + + Result MapPhysicalMemory(KProcessAddress address, size_t size) { + R_RETURN(m_page_table.MapPhysicalMemory(address, size)); + } + + Result UnmapPhysicalMemory(KProcessAddress address, size_t size) { + R_RETURN(m_page_table.UnmapPhysicalMemory(address, size)); + } + + Result MapPhysicalMemoryUnsafe(KProcessAddress address, size_t size) { + R_RETURN(m_page_table.MapPhysicalMemoryUnsafe(address, size)); + } + + Result UnmapPhysicalMemoryUnsafe(KProcessAddress address, size_t size) { + R_RETURN(m_page_table.UnmapPhysicalMemoryUnsafe(address, size)); + } + + Result UnmapProcessMemory(KProcessAddress dst_address, size_t size, KProcessPageTable &src_page_table, KProcessAddress src_address) { + R_RETURN(m_page_table.UnmapProcessMemory(dst_address, size, src_page_table.m_page_table, src_address)); + } + + void DumpMemoryBlocks() const { + return m_page_table.DumpMemoryBlocks(); + } + + void DumpPageTable() const { + return m_page_table.DumpPageTable(); + } + + size_t CountPageTables() const { + return m_page_table.CountPageTables(); + } + + bool GetPhysicalAddress(KPhysicalAddress *out, KProcessAddress address) const { + return m_page_table.GetPhysicalAddress(out, address); + } + + bool Contains(KProcessAddress addr, size_t size) const { return m_page_table.Contains(addr, size); } + + bool IsInAliasRegion(KProcessAddress addr, size_t size) const { return m_page_table.IsInAliasRegion(addr, size); } + bool IsInUnsafeAliasRegion(KProcessAddress addr, size_t size) const { return m_page_table.IsInUnsafeAliasRegion(addr, size); } + + bool CanContain(KProcessAddress addr, size_t size, KMemoryState state) const { return m_page_table.CanContain(addr, size, state); } + bool CanContain(KProcessAddress addr, size_t size, ams::svc::MemoryState state) const { return m_page_table.CanContain(addr, size, state); } + + KProcessAddress GetAddressSpaceStart() const { return m_page_table.GetAddressSpaceStart(); } + KProcessAddress GetHeapRegionStart() const { return m_page_table.GetHeapRegionStart(); } + KProcessAddress GetAliasRegionStart() const { return m_page_table.GetAliasRegionStart(); } + KProcessAddress GetStackRegionStart() const { return m_page_table.GetStackRegionStart(); } + KProcessAddress GetKernelMapRegionStart() const { return m_page_table.GetKernelMapRegionStart(); } + KProcessAddress GetAliasCodeRegionStart() const { return m_page_table.GetAliasCodeRegionStart(); } + + size_t GetAddressSpaceSize() const { return m_page_table.GetAddressSpaceSize(); } + size_t GetHeapRegionSize() const { return m_page_table.GetHeapRegionSize(); } + size_t GetAliasRegionSize() const { return m_page_table.GetAliasRegionSize(); } + size_t GetStackRegionSize() const { return m_page_table.GetStackRegionSize(); } + size_t GetKernelMapRegionSize() const { return m_page_table.GetKernelMapRegionSize(); } + size_t GetAliasCodeRegionSize() const { return m_page_table.GetAliasCodeRegionSize(); } + + size_t GetAliasRegionExtraSize() const { return m_page_table.GetAliasRegionExtraSize(); } + + size_t GetNormalMemorySize() const { return m_page_table.GetNormalMemorySize(); } + + size_t GetCodeSize() const { return m_page_table.GetCodeSize(); } + size_t GetCodeDataSize() const { return m_page_table.GetCodeDataSize(); } + + size_t GetAliasCodeSize() const { return m_page_table.GetAliasCodeSize(); } + size_t GetAliasCodeDataSize() const { return m_page_table.GetAliasCodeDataSize(); } + + u32 GetAllocateOption() const { return m_page_table.GetAllocateOption(); } + + KPhysicalAddress GetHeapPhysicalAddress(KVirtualAddress address) const { + return m_page_table.GetHeapPhysicalAddress(address); + } + + KVirtualAddress GetHeapVirtualAddress(KPhysicalAddress address) const { + return m_page_table.GetHeapVirtualAddress(address); + } + + KBlockInfoManager *GetBlockInfoManager() { + return m_page_table.GetBlockInfoManager(); + } + + KPageTableBase &GetBasePageTable() { + return m_page_table; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_slab_heap_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_slab_heap_impl.hpp new file mode 100644 index 00000000..63c8fe76 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_slab_heap_impl.hpp @@ -0,0 +1,93 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <mesosphere/kern_select_cpu.hpp> +#include <mesosphere/kern_select_interrupt_manager.hpp> + +namespace ams::kern::arch::arm64 { + + template<typename T> + concept SlabHeapNode = requires (T &t) { + { t.next } -> std::convertible_to<T *>; + }; + + ALWAYS_INLINE bool IsSlabAtomicValid() { + /* Without careful consideration, slab heaps atomics are vulnerable to */ + /* the ABA problem, when doing compare and swap of node pointers. */ + /* We resolve this by using the ARM exclusive monitor; we bundle the */ + /* load and store of the relevant values into a single exclusive monitor */ + /* hold, preventing the ABA problem. */ + /* However, our assembly must do both a load and a store under a single */ + /* hold, at different memory addresses. Considering the case where the */ + /* addresses are distinct but resolve to the same cache set (by chance), */ + /* we can note that under a 1-way associative (direct-mapped) cache */ + /* we would have as a guarantee that the second access would evict the */ + /* cache line from the first access, invalidating our exclusive monitor */ + /* hold. Thus, we require that the cache is not 1-way associative, for */ + /* our implementation to be correct. */ + { + /* Disable interrupts. */ + KScopedInterruptDisable di; + + /* Select L1 cache. */ + cpu::SetCsselrEl1(0); + cpu::InstructionMemoryBarrier(); + + /* Check that the L1 cache is not direct-mapped. */ + return cpu::CacheSizeIdRegisterAccessor().GetAssociativity() != 0; + } + } + + template<typename T> requires SlabHeapNode<T> + ALWAYS_INLINE T *AllocateFromSlabAtomic(T **head) { + u32 tmp; + T *node, *next; + + __asm__ __volatile__( + "1:\n" + " ldaxr %[node], [%[head]]\n" + " cbz %[node], 2f\n" + " ldr %[next], [%[node]]\n" + " stlxr %w[tmp], %[next], [%[head]]\n" + " cbnz %w[tmp], 1b\n" + "2:\n" + : [tmp]"=&r"(tmp), [node]"=&r"(node), [next]"=&r"(next), [head]"+&r"(head) + : + : "cc", "memory" + ); + + return node; + } + + template<typename T> requires SlabHeapNode<T> + ALWAYS_INLINE void FreeToSlabAtomic(T **head, T *node) { + u32 tmp; + T *next; + + __asm__ __volatile__( + "1:\n" + " ldaxr %[next], [%[head]]\n" + " str %[next], [%[node]]\n" + " stlxr %w[tmp], %[node], [%[head]]\n" + " cbnz %w[tmp], 1b\n" + : [tmp]"=&r"(tmp), [node]"+&r"(node), [next]"=&r"(next), [head]"+&r"(head) + : + : "cc", "memory" + ); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_spin_lock.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_spin_lock.hpp new file mode 100644 index 00000000..c5a97750 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_spin_lock.hpp @@ -0,0 +1,111 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <mesosphere/kern_select_cpu.hpp> + +namespace ams::kern::arch::arm64 { + + class KNotAlignedSpinLock { + private: + u32 m_packed_tickets; + public: + constexpr KNotAlignedSpinLock() : m_packed_tickets(0) { /* ... */ } + + ALWAYS_INLINE void Lock() { + u32 tmp0, tmp1, tmp2; + + __asm__ __volatile__( + " prfm pstl1keep, %[m_packed_tickets]\n" + "1:\n" + " ldaxr %w[tmp0], %[m_packed_tickets]\n" + " add %w[tmp2], %w[tmp0], #0x10000\n" + " stxr %w[tmp1], %w[tmp2], %[m_packed_tickets]\n" + " cbnz %w[tmp1], 1b\n" + " \n" + " and %w[tmp1], %w[tmp0], #0xFFFF\n" + " cmp %w[tmp1], %w[tmp0], lsr #16\n" + " b.eq 3f\n" + " sevl\n" + "2:\n" + " wfe\n" + " ldaxrh %w[tmp1], %[m_packed_tickets]\n" + " cmp %w[tmp1], %w[tmp0], lsr #16\n" + " b.ne 2b\n" + "3:\n" + : [tmp0]"=&r"(tmp0), [tmp1]"=&r"(tmp1), [tmp2]"=&r"(tmp2), [m_packed_tickets]"+Q"(m_packed_tickets) + : + : "cc", "memory" + ); + } + + ALWAYS_INLINE void Unlock() { + const u32 value = m_packed_tickets + 1; + __asm__ __volatile__( + " stlrh %w[value], %[m_packed_tickets]\n" + : [m_packed_tickets]"+Q"(m_packed_tickets) + : [value]"r"(value) + : "memory" + ); + } + }; + static_assert(sizeof(KNotAlignedSpinLock) == sizeof(u32)); + + class KAlignedSpinLock { + private: + alignas(cpu::DataCacheLineSize) u16 m_current_ticket; + alignas(cpu::DataCacheLineSize) u16 m_next_ticket; + public: + constexpr KAlignedSpinLock() : m_current_ticket(0), m_next_ticket(0) { /* ... */ } + + ALWAYS_INLINE void Lock() { + u32 tmp0, tmp1, got_lock; + + __asm__ __volatile__( + " prfm pstl1keep, %[m_next_ticket]\n" + "1:\n" + " ldxrh %w[tmp0], %[m_next_ticket]\n" + " add %w[tmp1], %w[tmp0], #0x1\n" + " stxrh %w[got_lock], %w[tmp1], %[m_next_ticket]\n" + " cbnz %w[got_lock], 1b\n" + " \n" + " sevl\n" + "2:\n" + " wfe\n" + " ldaxrh %w[tmp1], %[m_current_ticket]\n" + " cmp %w[tmp1], %w[tmp0]\n" + " b.ne 2b\n" + : [tmp0]"=&r"(tmp0), [tmp1]"=&r"(tmp1), [got_lock]"=&r"(got_lock), [m_next_ticket]"+Q"(m_next_ticket) + : [m_current_ticket]"Q"(m_current_ticket) + : "cc", "memory" + ); + } + + ALWAYS_INLINE void Unlock() { + const u32 value = m_current_ticket + 1; + __asm__ __volatile__( + " stlrh %w[value], %[m_current_ticket]\n" + : [m_current_ticket]"+Q"(m_current_ticket) + : [value]"r"(value) + : "memory" + ); + } + }; + static_assert(sizeof(KAlignedSpinLock) == 2 * cpu::DataCacheLineSize); + + using KSpinLock = KNotAlignedSpinLock; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_supervisor_page_table.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_supervisor_page_table.hpp new file mode 100644 index 00000000..0c060228 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_supervisor_page_table.hpp @@ -0,0 +1,75 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_select_cpu.hpp> +#include <mesosphere/arch/arm64/kern_k_page_table.hpp> + +namespace ams::kern::arch::arm64 { + + class KSupervisorPageTable { + private: + KPageTable m_page_table; + public: + constexpr KSupervisorPageTable() : m_page_table(util::ConstantInitialize) { /* ... */ } + + NOINLINE void Initialize(s32 core_id); + + void Activate() { + m_page_table.ActivateKernel(); + } + + void ActivateForInit() { + this->Activate(); + + /* Invalidate entire TLB. */ + cpu::InvalidateEntireTlb(); + } + + Result MapPages(KProcessAddress *out_addr, size_t num_pages, size_t alignment, KPhysicalAddress phys_addr, KProcessAddress region_start, size_t region_num_pages, KMemoryState state, KMemoryPermission perm) { + R_RETURN(m_page_table.MapPages(out_addr, num_pages, alignment, phys_addr, region_start, region_num_pages, state, perm)); + } + + Result UnmapPages(KProcessAddress address, size_t num_pages, KMemoryState state) { + R_RETURN(m_page_table.UnmapPages(address, num_pages, state)); + } + + Result MapPageGroup(KProcessAddress *out_addr, const KPageGroup &pg, KProcessAddress region_start, size_t region_num_pages, KMemoryState state, KMemoryPermission perm) { + R_RETURN(m_page_table.MapPageGroup(out_addr, pg, region_start, region_num_pages, state, perm)); + } + + Result UnmapPageGroup(KProcessAddress address, const KPageGroup &pg, KMemoryState state) { + R_RETURN(m_page_table.UnmapPageGroup(address, pg, state)); + } + + bool GetPhysicalAddress(KPhysicalAddress *out, KProcessAddress address) const { + return m_page_table.GetPhysicalAddress(out, address); + } + + void DumpMemoryBlocks() const { + return m_page_table.DumpMemoryBlocks(); + } + + void DumpPageTable() const { + return m_page_table.DumpPageTable(); + } + + size_t CountPageTables() const { + return m_page_table.CountPageTables(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_thread_context.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_thread_context.hpp new file mode 100644 index 00000000..40cc1f86 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_thread_context.hpp @@ -0,0 +1,316 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_typed_address.hpp> + +namespace ams::kern { + + class KThread; + +} + +namespace ams::kern::arch::arm64 { + + class KThreadContext { + public: + static constexpr size_t NumCalleeSavedRegisters = (29 - 19) + 1; + static constexpr size_t NumCalleeSavedFpuRegisters = 8; + static constexpr size_t NumCallerSavedFpuRegisters = 24; + static constexpr size_t NumFpuRegisters = NumCalleeSavedFpuRegisters + NumCallerSavedFpuRegisters; + public: + union CalleeSaveRegisters { + u64 registers[NumCalleeSavedRegisters]; + struct { + u64 x19; + u64 x20; + u64 x21; + u64 x22; + u64 x23; + u64 x24; + u64 x25; + u64 x26; + u64 x27; + u64 x28; + u64 x29; + }; + }; + + union CalleeSaveFpu64Registers { + u128 v[NumCalleeSavedFpuRegisters]; + struct { + u128 q8; + u128 q9; + u128 q10; + u128 q11; + u128 q12; + u128 q13; + u128 q14; + u128 q15; + }; + }; + + union CalleeSaveFpu32Registers { + u128 v[NumCalleeSavedFpuRegisters / 2]; + struct { + u128 q4; + u128 q5; + u128 q6; + u128 q7; + }; + }; + + union CalleeSaveFpuRegisters { + CalleeSaveFpu64Registers fpu64; + CalleeSaveFpu32Registers fpu32; + }; + + union CallerSaveFpu64Registers { + u128 v[NumCallerSavedFpuRegisters]; + struct { + union { + u128 v0_7[NumCallerSavedFpuRegisters / 3]; + struct { + u128 q0; + u128 q1; + u128 q2; + u128 q3; + u128 q4; + u128 q5; + u128 q6; + u128 q7; + }; + }; + union { + u128 v16_31[2 * NumCallerSavedFpuRegisters / 3]; + struct { + u128 q16; + u128 q17; + u128 q18; + u128 q19; + u128 q20; + u128 q21; + u128 q22; + u128 q23; + u128 q24; + u128 q25; + u128 q26; + u128 q27; + u128 q28; + u128 q29; + u128 q30; + u128 q31; + }; + }; + }; + }; + + union CallerSaveFpu32Registers { + u128 v[NumCallerSavedFpuRegisters / 2]; + struct { + union { + u128 v0_3[(NumCallerSavedFpuRegisters / 3) / 2]; + struct { + u128 q0; + u128 q1; + u128 q2; + u128 q3; + }; + }; + union { + u128 v8_15[(2 * NumCallerSavedFpuRegisters / 3) / 2]; + struct { + u128 q8; + u128 q9; + u128 q10; + u128 q11; + u128 q12; + u128 q13; + u128 q14; + u128 q15; + }; + }; + }; + }; + + union CallerSaveFpuRegisters { + CallerSaveFpu64Registers fpu64; + CallerSaveFpu32Registers fpu32; + }; + private: + CalleeSaveRegisters m_callee_saved; + u64 m_lr; + u64 m_sp; + u32 m_fpcr; + u32 m_fpsr; + alignas(0x10) CalleeSaveFpuRegisters m_callee_saved_fpu; + bool m_locked; + private: + static void RestoreFpuRegisters64(const KThreadContext &); + static void RestoreFpuRegisters32(const KThreadContext &); + public: + constexpr explicit KThreadContext(util::ConstantInitializeTag) : m_callee_saved(), m_lr(), m_sp(), m_fpcr(), m_fpsr(), m_callee_saved_fpu(), m_locked() { /* ... */ } + explicit KThreadContext() { /* ... */ } + + Result Initialize(KVirtualAddress u_pc, KVirtualAddress k_sp, KVirtualAddress u_sp, uintptr_t arg, bool is_user, bool is_64_bit, bool is_main); + + void SetArguments(uintptr_t arg0, uintptr_t arg1); + + static void FpuContextSwitchHandler(KThread *thread); + + u32 GetFpcr() const { return m_fpcr; } + u32 GetFpsr() const { return m_fpsr; } + + void SetFpcr(u32 v) { m_fpcr = v; } + void SetFpsr(u32 v) { m_fpsr = v; } + + void CloneFpuStatus(); + + const auto &GetCalleeSaveFpuRegisters() const { return m_callee_saved_fpu; } + auto &GetCalleeSaveFpuRegisters() { return m_callee_saved_fpu; } + public: + static void OnThreadTerminating(const KThread *thread); + public: + static consteval bool ValidateOffsets(); + + template<typename CallerSave, typename CalleeSave> requires ((std::same_as<CallerSave, CallerSaveFpu64Registers> && std::same_as<CalleeSave, CalleeSaveFpu64Registers>) || (std::same_as<CallerSave, CallerSaveFpu32Registers> && std::same_as<CalleeSave, CalleeSaveFpu32Registers>)) + static void GetFpuRegisters(u128 *out, const CallerSave &caller_save, const CalleeSave &callee_save) { + /* Check that the register counts are correct. */ + constexpr size_t RegisterUnitCount = util::size(CalleeSave{}.v); + static_assert(util::size(CalleeSave{}.v) == 1 * RegisterUnitCount); + static_assert(util::size(CallerSave{}.v) == 3 * RegisterUnitCount); + + /* Copy the low caller-save registers. */ + for (size_t i = 0; i < RegisterUnitCount; ++i) { + *(out++) = caller_save.v[i]; + } + + /* Copy the callee-save registers. */ + for (size_t i = 0; i < RegisterUnitCount; ++i) { + *(out++) = callee_save.v[i]; + } + + /* Copy the remaining caller-save registers. */ + for (size_t i = 0; i < 2 * RegisterUnitCount; ++i) { + *(out++) = caller_save.v[RegisterUnitCount + i]; + } + } + + template<typename CallerSave, typename CalleeSave> requires ((std::same_as<CallerSave, CallerSaveFpu64Registers> && std::same_as<CalleeSave, CalleeSaveFpu64Registers>) || (std::same_as<CallerSave, CallerSaveFpu32Registers> && std::same_as<CalleeSave, CalleeSaveFpu32Registers>)) + static ALWAYS_INLINE void SetFpuRegisters(CallerSave &caller_save, CalleeSave &callee_save, const u128 *v) { + /* Check that the register counts are correct. */ + constexpr size_t RegisterUnitCount = util::size(CalleeSave{}.v); + static_assert(util::size(CalleeSave{}.v) == 1 * RegisterUnitCount); + static_assert(util::size(CallerSave{}.v) == 3 * RegisterUnitCount); + + /* Copy the low caller-save registers. */ + for (size_t i = 0; i < RegisterUnitCount; ++i) { + caller_save.v[i] = *(v++); + } + + /* Copy the callee-save registers. */ + for (size_t i = 0; i < RegisterUnitCount; ++i) { + callee_save.v[i] = *(v++); + } + + /* Copy the remaining caller-save registers. */ + for (size_t i = 0; i < 2 * RegisterUnitCount; ++i) { + caller_save.v[RegisterUnitCount + i] = *(v++); + } + } + }; + + consteval bool KThreadContext::ValidateOffsets() { + static_assert(sizeof(KThreadContext) == THREAD_CONTEXT_SIZE); + + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved.registers) == THREAD_CONTEXT_CPU_REGISTERS); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved.x19) == THREAD_CONTEXT_X19); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved.x20) == THREAD_CONTEXT_X20); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved.x21) == THREAD_CONTEXT_X21); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved.x22) == THREAD_CONTEXT_X22); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved.x23) == THREAD_CONTEXT_X23); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved.x24) == THREAD_CONTEXT_X24); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved.x25) == THREAD_CONTEXT_X25); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved.x26) == THREAD_CONTEXT_X26); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved.x27) == THREAD_CONTEXT_X27); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved.x28) == THREAD_CONTEXT_X28); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved.x29) == THREAD_CONTEXT_X29); + static_assert(AMS_OFFSETOF(KThreadContext, m_lr) == THREAD_CONTEXT_LR); + static_assert(AMS_OFFSETOF(KThreadContext, m_sp) == THREAD_CONTEXT_SP); + static_assert(AMS_OFFSETOF(KThreadContext, m_fpcr) == THREAD_CONTEXT_FPCR); + static_assert(AMS_OFFSETOF(KThreadContext, m_fpsr) == THREAD_CONTEXT_FPSR); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved_fpu) == THREAD_CONTEXT_FPU_REGISTERS); + static_assert(AMS_OFFSETOF(KThreadContext, m_locked) == THREAD_CONTEXT_LOCKED); + + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved_fpu.fpu64.q8 ) == THREAD_CONTEXT_FPU64_Q8 ); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved_fpu.fpu64.q9 ) == THREAD_CONTEXT_FPU64_Q9 ); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved_fpu.fpu64.q10) == THREAD_CONTEXT_FPU64_Q10); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved_fpu.fpu64.q11) == THREAD_CONTEXT_FPU64_Q11); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved_fpu.fpu64.q12) == THREAD_CONTEXT_FPU64_Q12); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved_fpu.fpu64.q13) == THREAD_CONTEXT_FPU64_Q13); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved_fpu.fpu64.q14) == THREAD_CONTEXT_FPU64_Q14); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved_fpu.fpu64.q15) == THREAD_CONTEXT_FPU64_Q15); + + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved_fpu.fpu32.q4 ) == THREAD_CONTEXT_FPU32_Q4 ); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved_fpu.fpu32.q5 ) == THREAD_CONTEXT_FPU32_Q5 ); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved_fpu.fpu32.q6 ) == THREAD_CONTEXT_FPU32_Q6 ); + static_assert(AMS_OFFSETOF(KThreadContext, m_callee_saved_fpu.fpu32.q7 ) == THREAD_CONTEXT_FPU32_Q7 ); + + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q0 ) == THREAD_FPU64_CONTEXT_Q0 ); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q1 ) == THREAD_FPU64_CONTEXT_Q1 ); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q2 ) == THREAD_FPU64_CONTEXT_Q2 ); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q3 ) == THREAD_FPU64_CONTEXT_Q3 ); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q4 ) == THREAD_FPU64_CONTEXT_Q4 ); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q5 ) == THREAD_FPU64_CONTEXT_Q5 ); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q6 ) == THREAD_FPU64_CONTEXT_Q6 ); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q7 ) == THREAD_FPU64_CONTEXT_Q7 ); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q16) == THREAD_FPU64_CONTEXT_Q16); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q17) == THREAD_FPU64_CONTEXT_Q17); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q18) == THREAD_FPU64_CONTEXT_Q18); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q19) == THREAD_FPU64_CONTEXT_Q19); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q20) == THREAD_FPU64_CONTEXT_Q20); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q21) == THREAD_FPU64_CONTEXT_Q21); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q22) == THREAD_FPU64_CONTEXT_Q22); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q23) == THREAD_FPU64_CONTEXT_Q23); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q24) == THREAD_FPU64_CONTEXT_Q24); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q25) == THREAD_FPU64_CONTEXT_Q25); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q26) == THREAD_FPU64_CONTEXT_Q26); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q27) == THREAD_FPU64_CONTEXT_Q27); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q28) == THREAD_FPU64_CONTEXT_Q28); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q29) == THREAD_FPU64_CONTEXT_Q29); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q30) == THREAD_FPU64_CONTEXT_Q30); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu64.q31) == THREAD_FPU64_CONTEXT_Q31); + + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu32.q0 ) == THREAD_FPU32_CONTEXT_Q0 ); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu32.q1 ) == THREAD_FPU32_CONTEXT_Q1 ); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu32.q2 ) == THREAD_FPU32_CONTEXT_Q2 ); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu32.q3 ) == THREAD_FPU32_CONTEXT_Q3 ); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu32.q8 ) == THREAD_FPU32_CONTEXT_Q8 ); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu32.q9 ) == THREAD_FPU32_CONTEXT_Q9 ); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu32.q10) == THREAD_FPU32_CONTEXT_Q10); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu32.q11) == THREAD_FPU32_CONTEXT_Q11); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu32.q12) == THREAD_FPU32_CONTEXT_Q12); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu32.q13) == THREAD_FPU32_CONTEXT_Q13); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu32.q14) == THREAD_FPU32_CONTEXT_Q14); + static_assert(AMS_OFFSETOF(KThreadContext::CallerSaveFpuRegisters, fpu32.q15) == THREAD_FPU32_CONTEXT_Q15); + + return true; + } + static_assert(KThreadContext::ValidateOffsets()); + + void GetUserContext(ams::svc::ThreadContext *out, const KThread *thread); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_secure_monitor_base.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_secure_monitor_base.hpp new file mode 100644 index 00000000..a3e8f080 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_secure_monitor_base.hpp @@ -0,0 +1,78 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_select_cpu.hpp> + +namespace ams::kern::arch::arm64::smc { + + template<int SmcId> + void SecureMonitorCall(u64 *buf) { + /* Load arguments into registers. */ + register u64 x0 asm("x0") = buf[0]; + register u64 x1 asm("x1") = buf[1]; + register u64 x2 asm("x2") = buf[2]; + register u64 x3 asm("x3") = buf[3]; + register u64 x4 asm("x4") = buf[4]; + register u64 x5 asm("x5") = buf[5]; + register u64 x6 asm("x6") = buf[6]; + register u64 x7 asm("x7") = buf[7]; + + /* Backup the current thread pointer. */ + const uintptr_t current_thread_pointer_value = cpu::GetCurrentThreadPointerValue(); + + /* Perform the call. */ + __asm__ __volatile__("smc %c[smc_id]" + : "+r"(x0), "+r"(x1), "+r"(x2), "+r"(x3), "+r"(x4), "+r"(x5), "+r"(x6), "+r"(x7) + : [smc_id]"i"(SmcId) + : "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", "x18", "cc", "memory" + ); + + /* Restore the current thread pointer into X18. */ + cpu::SetCurrentThreadPointerValue(current_thread_pointer_value); + + /* Store arguments to output. */ + buf[0] = x0; + buf[1] = x1; + buf[2] = x2; + buf[3] = x3; + buf[4] = x4; + buf[5] = x5; + buf[6] = x6; + buf[7] = x7; + } + + enum PsciFunction { + PsciFunction_CpuSuspend = 0xC4000001, + PsciFunction_CpuOff = 0x84000002, + PsciFunction_CpuOn = 0xC4000003, + }; + + template<int SmcId> + u64 PsciCall(PsciFunction function, u64 x1 = 0, u64 x2 = 0, u64 x3 = 0, u64 x4 = 0, u64 x5 = 0, u64 x6 = 0, u64 x7 = 0) { + ams::svc::lp64::SecureMonitorArguments args = { { function, x1, x2, x3, x4, x5, x6, x7 } }; + + SecureMonitorCall<SmcId>(args.r); + + return args.r[0]; + } + + template<int SmcId> + u64 CpuOn(u64 core_id, uintptr_t entrypoint, uintptr_t arg) { + return PsciCall<SmcId>(PsciFunction_CpuOn, core_id, entrypoint, arg); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_userspace_memory_access.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_userspace_memory_access.hpp new file mode 100644 index 00000000..17c6c6a4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_userspace_memory_access.hpp @@ -0,0 +1,163 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +namespace ams::kern::arch::arm64 { + + void UserspaceAccessFunctionAreaBegin(); + + class UserspaceAccess { + private: + class Impl { + public: + static bool CopyMemoryFromUser(void *dst, const void *src, size_t size); + static bool CopyMemoryFromUserAligned32Bit(void *dst, const void *src, size_t size); + static bool CopyMemoryFromUserAligned64Bit(void *dst, const void *src, size_t size); + static bool CopyMemoryFromUserSize64Bit(void *dst, const void *src); + static bool CopyMemoryFromUserSize32Bit(void *dst, const void *src); + static bool CopyMemoryFromUserSize32BitWithSupervisorAccess(void *dst, const void *src); + static s32 CopyStringFromUser(void *dst, const void *src, size_t size); + + static bool CopyMemoryToUser(void *dst, const void *src, size_t size); + static bool CopyMemoryToUserAligned32Bit(void *dst, const void *src, size_t size); + static bool CopyMemoryToUserAligned64Bit(void *dst, const void *src, size_t size); + static bool CopyMemoryToUserSize32Bit(void *dst, const void *src); + static s32 CopyStringToUser(void *dst, const void *src, size_t size); + + static bool UpdateLockAtomic(u32 *out, u32 *address, u32 if_zero, u32 new_orr_mask); + static bool UpdateIfEqualAtomic(s32 *out, s32 *address, s32 compare_value, s32 new_value); + static bool DecrementIfLessThanAtomic(s32 *out, s32 *address, s32 compare); + + static bool StoreDataCache(uintptr_t start, uintptr_t end); + static bool FlushDataCache(uintptr_t start, uintptr_t end); + static bool InvalidateDataCache(uintptr_t start, uintptr_t end); + + static bool ReadIoMemory32Bit(void *dst, const void *src, size_t size); + static bool ReadIoMemory16Bit(void *dst, const void *src, size_t size); + static bool ReadIoMemory8Bit(void *dst, const void *src, size_t size); + static bool WriteIoMemory32Bit(void *dst, const void *src, size_t size); + static bool WriteIoMemory16Bit(void *dst, const void *src, size_t size); + static bool WriteIoMemory8Bit(void *dst, const void *src, size_t size); + }; + public: + static bool CopyMemoryFromUser(void *dst, const void *src, size_t size) { + return Impl::CopyMemoryFromUser(dst, src, size); + } + + static bool CopyMemoryFromUserAligned32Bit(void *dst, const void *src, size_t size) { + return Impl::CopyMemoryFromUserAligned32Bit(dst, src, size); + } + + static bool CopyMemoryFromUserAligned64Bit(void *dst, const void *src, size_t size) { + return Impl::CopyMemoryFromUserAligned64Bit(dst, src, size); + } + + static bool CopyMemoryFromUserSize64Bit(void *dst, const void *src) { + return Impl::CopyMemoryFromUserSize64Bit(dst, src); + } + + static bool CopyMemoryFromUserSize32Bit(void *dst, const void *src) { + return Impl::CopyMemoryFromUserSize32Bit(dst, src); + } + + + static bool CopyMemoryFromUserSize32BitWithSupervisorAccess(void *dst, const void *src) { + /* Check that the address is within the valid userspace range. */ + if (const uintptr_t src_uptr = reinterpret_cast<uintptr_t>(src); src_uptr < ams::svc::AddressNullGuard32Size || (src_uptr + sizeof(u32) - 1) >= ams::svc::AddressMemoryRegion39Size) { + return false; + } + + return Impl::CopyMemoryFromUserSize32BitWithSupervisorAccess(dst, src); + } + + static s32 CopyStringFromUser(void *dst, const void *src, size_t size) { + return Impl::CopyStringFromUser(dst, src, size); + } + + static bool CopyMemoryToUser(void *dst, const void *src, size_t size) { + return Impl::CopyMemoryToUser(dst, src, size); + } + + static bool CopyMemoryToUserAligned32Bit(void *dst, const void *src, size_t size) { + return Impl::CopyMemoryToUserAligned32Bit(dst, src, size); + } + + static bool CopyMemoryToUserAligned64Bit(void *dst, const void *src, size_t size) { + return Impl::CopyMemoryToUserAligned64Bit(dst, src, size); + } + + static bool CopyMemoryToUserSize32Bit(void *dst, const void *src) { + return Impl::CopyMemoryToUserSize32Bit(dst, src); + } + + static s32 CopyStringToUser(void *dst, const void *src, size_t size) { + return Impl::CopyStringToUser(dst, src, size); + } + + static bool UpdateLockAtomic(u32 *out, u32 *address, u32 if_zero, u32 new_orr_mask) { + return Impl::UpdateLockAtomic(out, address, if_zero, new_orr_mask); + } + + static bool UpdateIfEqualAtomic(s32 *out, s32 *address, s32 compare_value, s32 new_value) { + return Impl::UpdateIfEqualAtomic(out, address, compare_value, new_value); + } + + static bool DecrementIfLessThanAtomic(s32 *out, s32 *address, s32 compare) { + return Impl::DecrementIfLessThanAtomic(out, address, compare); + } + + static bool StoreDataCache(uintptr_t start, uintptr_t end) { + return Impl::StoreDataCache(start, end); + } + + static bool FlushDataCache(uintptr_t start, uintptr_t end) { + return Impl::FlushDataCache(start, end); + } + + static bool InvalidateDataCache(uintptr_t start, uintptr_t end) { + return Impl::InvalidateDataCache(start, end); + } + + static bool ReadIoMemory32Bit(void *dst, const void *src, size_t size) { + return Impl::ReadIoMemory32Bit(dst, src, size); + } + + static bool ReadIoMemory16Bit(void *dst, const void *src, size_t size) { + return Impl::ReadIoMemory16Bit(dst, src, size); + } + + static bool ReadIoMemory8Bit(void *dst, const void *src, size_t size) { + return Impl::ReadIoMemory8Bit(dst, src, size); + } + + static bool WriteIoMemory32Bit(void *dst, const void *src, size_t size) { + return Impl::WriteIoMemory32Bit(dst, src, size); + } + + static bool WriteIoMemory16Bit(void *dst, const void *src, size_t size) { + return Impl::WriteIoMemory16Bit(dst, src, size); + } + + static bool WriteIoMemory8Bit(void *dst, const void *src, size_t size) { + return Impl::WriteIoMemory8Bit(dst, src, size); + } + }; + + + void UserspaceAccessFunctionAreaEnd(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/generic/kern_k_device_page_table.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/generic/kern_k_device_page_table.hpp new file mode 100644 index 00000000..662708ce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/generic/kern_k_device_page_table.hpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_page_group.hpp> +#include <mesosphere/kern_k_memory_manager.hpp> +#include <mesosphere/kern_select_page_table.hpp> + +namespace ams::kern::board::generic { + + using KDeviceVirtualAddress = u64; + + class KDevicePageTable { + public: + constexpr KDevicePageTable() { /* ... */ } + + Result ALWAYS_INLINE Initialize(u64 space_address, u64 space_size) { + MESOSPHERE_UNUSED(space_address, space_size); + R_THROW(ams::kern::svc::ResultNotImplemented()); + } + + void ALWAYS_INLINE Finalize() { /* ... */ } + + Result ALWAYS_INLINE Attach(ams::svc::DeviceName device_name, u64 space_address, u64 space_size) { + MESOSPHERE_UNUSED(device_name, space_address, space_size); + R_THROW(ams::kern::svc::ResultNotImplemented()); + } + + Result ALWAYS_INLINE Detach(ams::svc::DeviceName device_name) { + MESOSPHERE_UNUSED(device_name); + R_THROW(ams::kern::svc::ResultNotImplemented()); + } + + Result ALWAYS_INLINE Map(KProcessPageTable *page_table, KProcessAddress process_address, size_t size, KDeviceVirtualAddress device_address, ams::svc::MemoryPermission device_perm, bool is_aligned, bool is_io) { + MESOSPHERE_UNUSED(page_table, process_address, size, device_address, device_perm, is_aligned, is_io); + R_THROW(ams::kern::svc::ResultNotImplemented()); + } + + Result ALWAYS_INLINE Unmap(KProcessPageTable *page_table, KProcessAddress process_address, size_t size, KDeviceVirtualAddress device_address) { + MESOSPHERE_UNUSED(page_table, process_address, size, device_address); + R_THROW(ams::kern::svc::ResultNotImplemented()); + } + + void ALWAYS_INLINE Unmap(KDeviceVirtualAddress device_address, size_t size) { + MESOSPHERE_UNUSED(device_address, size); + } + public: + static ALWAYS_INLINE void Initialize() { /* ... */ } + + static ALWAYS_INLINE void Lock() { /* ... */ } + static ALWAYS_INLINE void Unlock() { /* ... */ } + static ALWAYS_INLINE void Sleep() { /* ... */ } + static ALWAYS_INLINE void Wakeup() { /* ... */ } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_cpu_map.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_cpu_map.hpp new file mode 100644 index 00000000..b36621b1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_cpu_map.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +namespace ams::kern::board::nintendo::nx::impl::cpu { + + /* Virtual to Physical core map. */ + constexpr inline const s32 VirtualToPhysicalCoreMap[BITSIZEOF(u64)] = { + 0, 1, 2, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_device_page_table.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_device_page_table.hpp new file mode 100644 index 00000000..5cc4ba00 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_device_page_table.hpp @@ -0,0 +1,100 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_page_group.hpp> +#include <mesosphere/kern_k_memory_manager.hpp> +#include <mesosphere/kern_select_page_table.hpp> + +namespace ams::kern::board::nintendo::nx { + + using KDeviceVirtualAddress = u64; + + class KDevicePageTable { + private: + static constexpr size_t TableCount = 4; + private: + KVirtualAddress m_tables[TableCount]; + u8 m_table_asids[TableCount]; + u64 m_attached_device; + u32 m_attached_value; + u32 m_detached_value; + u32 m_hs_attached_value; + u32 m_hs_detached_value; + private: + static ALWAYS_INLINE bool IsHeapVirtualAddress(KVirtualAddress addr) { + const KMemoryRegion *hint = nullptr; + return KMemoryLayout::IsHeapVirtualAddress(hint, addr); + } + + static ALWAYS_INLINE bool IsHeapPhysicalAddress(KPhysicalAddress addr) { + const KMemoryRegion *hint = nullptr; + return KMemoryLayout::IsHeapPhysicalAddress(hint, addr); + } + + static ALWAYS_INLINE KVirtualAddress GetHeapVirtualAddress(KPhysicalAddress addr) { + return KPageTable::GetHeapVirtualAddress(addr); + } + + static ALWAYS_INLINE KPhysicalAddress GetHeapPhysicalAddress(KVirtualAddress addr) { + return KPageTable::GetHeapPhysicalAddress(addr); + } + + static ALWAYS_INLINE KVirtualAddress GetPageTableVirtualAddress(KPhysicalAddress addr) { + return KPageTable::GetPageTableVirtualAddress(addr); + } + + static ALWAYS_INLINE KPhysicalAddress GetPageTablePhysicalAddress(KVirtualAddress addr) { + return KPageTable::GetPageTablePhysicalAddress(addr); + } + public: + constexpr KDevicePageTable() + : m_tables{Null<KVirtualAddress>, Null<KVirtualAddress>, Null<KVirtualAddress>, Null<KVirtualAddress>}, + m_table_asids(), m_attached_device(), m_attached_value(), m_detached_value(), m_hs_attached_value(), m_hs_detached_value() + { + /* ... */ + } + + Result Initialize(u64 space_address, u64 space_size); + void Finalize(); + + Result Attach(ams::svc::DeviceName device_name, u64 space_address, u64 space_size); + Result Detach(ams::svc::DeviceName device_name); + + Result Map(KProcessPageTable *page_table, KProcessAddress process_address, size_t size, KDeviceVirtualAddress device_address, ams::svc::MemoryPermission device_perm, bool is_aligned, bool is_io); + Result Unmap(KProcessPageTable *page_table, KProcessAddress process_address, size_t size, KDeviceVirtualAddress device_address); + + void Unmap(KDeviceVirtualAddress device_address, size_t size) { + return this->UnmapImpl(device_address, size, false); + } + private: + Result MapDevicePage(KPhysicalAddress phys_addr, u64 size, KDeviceVirtualAddress address, ams::svc::MemoryPermission device_perm); + + Result MapImpl(KProcessPageTable *page_table, KProcessAddress process_address, size_t size, KDeviceVirtualAddress device_address, ams::svc::MemoryPermission device_perm, bool is_aligned); + void UnmapImpl(KDeviceVirtualAddress address, u64 size, bool force); + + bool IsFree(KDeviceVirtualAddress address, u64 size) const; + bool Compare(KProcessPageTable *page_table, KProcessAddress process_address, size_t size, KDeviceVirtualAddress device_address) const; + public: + static void Initialize(); + + static void Lock(); + static void Unlock(); + static void Sleep(); + static void Wakeup(); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_memory_layout.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_memory_layout.hpp new file mode 100644 index 00000000..4e82780b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_memory_layout.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_typed_address.hpp> + +namespace ams::kern { + + constexpr inline KPhysicalAddress MainMemoryAddress = 0x80000000; + + constexpr inline size_t MainMemorySize = 4_GB; + constexpr inline size_t MainMemorySizeMax = 8_GB; + + constexpr inline u32 MinimumMemoryManagerAlignmentShifts[] = { + 0, 0, 0, 0 + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_memory_region_device_types.inc b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_memory_region_device_types.inc new file mode 100644 index 00000000..68be3dcd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_memory_region_device_types.inc @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* All architectures must define NumBoardDeviceRegions. */ +constexpr inline const auto NumBoardDeviceRegions = 6; + /* UNUSED: .Derive(NumBoardDeviceRegions, 0); */ +constexpr inline const auto KMemoryRegionType_MemoryController = KMemoryRegionType_BoardDeviceBase.Derive(NumBoardDeviceRegions, 1).SetAttribute(KMemoryRegionAttr_NoUserMap); +constexpr inline const auto KMemoryRegionType_MemoryController1 = KMemoryRegionType_BoardDeviceBase.Derive(NumBoardDeviceRegions, 2).SetAttribute(KMemoryRegionAttr_NoUserMap); +constexpr inline const auto KMemoryRegionType_MemoryController0 = KMemoryRegionType_BoardDeviceBase.Derive(NumBoardDeviceRegions, 3).SetAttribute(KMemoryRegionAttr_NoUserMap); +constexpr inline const auto KMemoryRegionType_PowerManagementController = KMemoryRegionType_BoardDeviceBase.Derive(NumBoardDeviceRegions, 4).DeriveTransition(); +constexpr inline const auto KMemoryRegionType_LegacyLpsDevices = KMemoryRegionType_BoardDeviceBase.Derive(NumBoardDeviceRegions, 5); +static_assert(KMemoryRegionType_MemoryController .GetValue() == (0x55 | KMemoryRegionAttr_NoUserMap)); +static_assert(KMemoryRegionType_MemoryController1 .GetValue() == (0x65 | KMemoryRegionAttr_NoUserMap)); +static_assert(KMemoryRegionType_MemoryController0 .GetValue() == (0x95 | KMemoryRegionAttr_NoUserMap)); +static_assert(KMemoryRegionType_PowerManagementController.GetValue() == (0x1A5)); + +static_assert(KMemoryRegionType_LegacyLpsDevices.GetValue() == 0xC5); + +constexpr inline const auto NumLegacyLpsDevices = 7; +constexpr inline const auto KMemoryRegionType_LegacyLpsExceptionVectors = KMemoryRegionType_LegacyLpsDevices.Derive(NumLegacyLpsDevices, 0); +constexpr inline const auto KMemoryRegionType_LegacyLpsIram = KMemoryRegionType_LegacyLpsDevices.Derive(NumLegacyLpsDevices, 1); +constexpr inline const auto KMemoryRegionType_LegacyLpsFlowController = KMemoryRegionType_LegacyLpsDevices.Derive(NumLegacyLpsDevices, 2); +constexpr inline const auto KMemoryRegionType_LegacyLpsPrimaryICtlr = KMemoryRegionType_LegacyLpsDevices.Derive(NumLegacyLpsDevices, 3); +constexpr inline const auto KMemoryRegionType_LegacyLpsSemaphore = KMemoryRegionType_LegacyLpsDevices.Derive(NumLegacyLpsDevices, 4); +constexpr inline const auto KMemoryRegionType_LegacyLpsAtomics = KMemoryRegionType_LegacyLpsDevices.Derive(NumLegacyLpsDevices, 5); +constexpr inline const auto KMemoryRegionType_LegacyLpsClkRst = KMemoryRegionType_LegacyLpsDevices.Derive(NumLegacyLpsDevices, 6); +static_assert(KMemoryRegionType_LegacyLpsExceptionVectors.GetValue() == 0x3C5); +static_assert(KMemoryRegionType_LegacyLpsIram .GetValue() == 0x5C5); +static_assert(KMemoryRegionType_LegacyLpsFlowController .GetValue() == 0x6C5); +static_assert(KMemoryRegionType_LegacyLpsPrimaryICtlr .GetValue() == 0x9C5); +static_assert(KMemoryRegionType_LegacyLpsSemaphore .GetValue() == 0xAC5); +static_assert(KMemoryRegionType_LegacyLpsAtomics .GetValue() == 0xCC5); +static_assert(KMemoryRegionType_LegacyLpsClkRst .GetValue() == 0x11C5); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_system_control.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_system_control.hpp new file mode 100644 index 00000000..8e182984 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/nintendo/nx/kern_k_system_control.hpp @@ -0,0 +1,75 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_system_control_base.hpp> + +namespace ams::kern::board::nintendo::nx { + + class KSystemControl : public KSystemControlBase { + public: + /* This can be overridden as needed. */ + static constexpr size_t SecureAppletMemorySize = 4_MB; + public: + class Init : public KSystemControlBase::Init { + private: + friend class KSystemControlBase::Init; + private: + static void CpuOnImpl(u64 core_id, uintptr_t entrypoint, uintptr_t arg); + public: + /* Initialization. */ + static size_t GetRealMemorySize(); + static size_t GetIntendedMemorySize(); + static bool ShouldIncreaseThreadResourceLimit(); + static size_t GetApplicationPoolSize(); + static size_t GetAppletPoolSize(); + static size_t GetMinimumNonSecureSystemPoolSize(); + static u8 GetDebugLogUartPort(); + + /* Randomness. */ + static void GenerateRandom(u64 *dst, size_t count); + static u64 GenerateRandomRange(u64 min, u64 max); + }; + public: + /* Initialization. */ + static NOINLINE void ConfigureKTargetSystem(); + static NOINLINE void InitializePhase1(); + static NOINLINE void InitializePhase2(); + static NOINLINE u32 GetCreateProcessMemoryPool(); + + /* Randomness. */ + static void GenerateRandom(u64 *dst, size_t count); + static u64 GenerateRandomRange(u64 min, u64 max); + static u64 GenerateRandomU64(); + + /* Privileged Access. */ + static void ReadWriteRegisterPrivileged(u32 *out, ams::svc::PhysicalAddress address, u32 mask, u32 value); + static Result ReadWriteRegister(u32 *out, ams::svc::PhysicalAddress address, u32 mask, u32 value); + + /* Power management. */ + static void SleepSystem(); + static NORETURN void StopSystem(void *arg = nullptr); + + /* User access. */ + static void CallSecureMonitorFromUserImpl(ams::svc::lp64::SecureMonitorArguments *args); + + /* Secure Memory. */ + static size_t CalculateRequiredSecureMemorySize(size_t size, u32 pool); + static Result AllocateSecureMemory(KVirtualAddress *out, size_t size, u32 pool); + static void FreeSecureMemory(KVirtualAddress address, size_t size, u32 pool); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/qemu/virt/kern_cpu_map.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/qemu/virt/kern_cpu_map.hpp new file mode 100644 index 00000000..ec333546 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/qemu/virt/kern_cpu_map.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +namespace ams::kern::board::qemu::virt::impl::cpu { + + /* Virtual to Physical core map. */ + constexpr inline const s32 VirtualToPhysicalCoreMap[BITSIZEOF(u64)] = { + 0, 1, 2, 3, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/qemu/virt/kern_k_memory_layout.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/qemu/virt/kern_k_memory_layout.hpp new file mode 100644 index 00000000..b18ec45c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/qemu/virt/kern_k_memory_layout.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_typed_address.hpp> + +namespace ams::kern { + + constexpr inline KPhysicalAddress MainMemoryAddress = 0x40000000; + + constexpr inline size_t MainMemorySize = 4_GB; + constexpr inline size_t MainMemorySizeMax = 8_GB; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/qemu/virt/kern_k_memory_region_device_types.inc b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/qemu/virt/kern_k_memory_region_device_types.inc new file mode 100644 index 00000000..3d64f040 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/qemu/virt/kern_k_memory_region_device_types.inc @@ -0,0 +1,20 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* All architectures must define NumBoardDeviceRegions. */ +constexpr inline const auto NumBoardDeviceRegions = 0; + /* UNUSED: .Derive(NumBoardDeviceRegions, 0); */ + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/qemu/virt/kern_k_system_control.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/qemu/virt/kern_k_system_control.hpp new file mode 100644 index 00000000..78a94fa1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/board/qemu/virt/kern_k_system_control.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_system_control_base.hpp> + +namespace ams::kern::board::qemu::virt { + + class KSystemControl : public KSystemControlBase { + public: + /* User access. */ + static void CallSecureMonitorFromUser(ams::svc::lp64::SecureMonitorArguments *args); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/init/kern_init_arguments_select.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/init/kern_init_arguments_select.hpp new file mode 100644 index 00000000..6892d330 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/init/kern_init_arguments_select.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_k_typed_address.hpp> + +#ifdef ATMOSPHERE_ARCH_ARM64 + #include <mesosphere/arch/arm64/init/kern_k_init_arguments.hpp> +#else + #error "Unknown architecture for KInitArguments" +#endif + +namespace ams::kern::init { + + static_assert(util::IsPowerOfTwo(alignof(KInitArguments)) && util::IsPowerOfTwo(sizeof(KInitArguments))); + + KInitArguments *GetInitArguments(s32 core_id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/init/kern_init_elf.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/init/kern_init_elf.hpp new file mode 100644 index 00000000..ba6eb228 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/init/kern_init_elf.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +#ifdef ATMOSPHERE_ARCH_ARM64 + #include <mesosphere/init/kern_init_elf64.hpp> + + namespace ams::kern::init::Elf { + using namespace ams::kern::init::Elf::Elf64; + + enum RelocationType { + R_ARCHITECTURE_RELATIVE = 0x403, /* Real name R_AARCH64_RELATIVE */ + }; + } +#else + #error "Unknown Architecture" +#endif + +namespace ams::kern::init::Elf { + + /* API to apply relocations or call init array. */ + void ApplyRelocations(uintptr_t base_address, const Dyn *dynamic); + void CallInitArrayFuncs(uintptr_t init_array_start, uintptr_t init_array_end); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/init/kern_init_elf64.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/init/kern_init_elf64.hpp new file mode 100644 index 00000000..76d85842 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/init/kern_init_elf64.hpp @@ -0,0 +1,149 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +/* +From musl include/elf.h + +Copyright © 2005-2014 Rich Felker, et al. + +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. +*/ +#pragma once +#include <vapours.hpp> + +namespace ams::kern::init::Elf::Elf64 { + + /* Type declarations required to perform relocations */ + using Half = u16; + using Word = u32; + using Sword = s32; + using Xword = u64; + using SXword = s64; + + using Addr = u64; + using Off = u64; + + class Dyn { + private: + SXword m_tag; + union { + Xword m_value; + Addr m_ptr; + }; + public: + constexpr ALWAYS_INLINE SXword GetTag() const { + return m_tag; + } + + constexpr ALWAYS_INLINE Xword GetValue() const { + return m_value; + } + + constexpr ALWAYS_INLINE Addr GetPtr() const { + return m_ptr; + } + }; + + class Rel { + private: + Addr m_offset; + Xword m_info; + public: + constexpr ALWAYS_INLINE Addr GetOffset() const { + return m_offset; + } + + constexpr ALWAYS_INLINE Xword GetSym() const { + return m_info >> 32; + } + + constexpr ALWAYS_INLINE Xword GetType() const { + return m_info & 0xFFFFFFFF; + } + }; + + class Rela { + private: + Addr m_offset; + Xword m_info; + SXword m_addend; + public: + constexpr ALWAYS_INLINE Addr GetOffset() const { + return m_offset; + } + + constexpr ALWAYS_INLINE Xword GetSym() const { + return m_info >> 32; + } + + constexpr ALWAYS_INLINE Xword GetType() const { + return m_info & 0xFFFFFFFF; + } + + constexpr ALWAYS_INLINE SXword GetAddend() const { + return m_addend; + } + }; + + class Relr { + private: + Xword m_info; + public: + constexpr ALWAYS_INLINE bool IsLocation() const { + return (m_info & 1) == 0; + } + + constexpr ALWAYS_INLINE Xword GetLocation() const { + return m_info; + } + + constexpr ALWAYS_INLINE Xword GetBitmap() const { + return m_info >> 1; + } + }; + + enum DynamicTag { + DT_NULL = 0, + DT_RELA = 7, + DT_RELAENT = 9, + DT_REL = 17, + DT_RELENT = 19, + + DT_RELRSZ = 35, + DT_RELR = 36, + DT_RELRENT = 37, + + DT_RELACOUNT = 0x6ffffff9, + DT_RELCOUNT = 0x6ffffffa + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/init/kern_init_layout.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/init/kern_init_layout.hpp new file mode 100644 index 00000000..db952edf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/init/kern_init_layout.hpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::kern::init { + + struct KernelLayout { + u32 rx_offset; + u32 rx_end_offset; + u32 ro_offset; + u32 ro_end_offset; + u32 rw_offset; + u32 rw_end_offset; + u32 bss_offset; + u32 bss_end_offset; + u32 resource_offset; + u32 dynamic_offset; + u32 init_array_offset; + u32 init_array_end_offset; + u32 sysreg_offset; + }; + static_assert(util::is_pod<KernelLayout>::value); + static_assert(sizeof(KernelLayout) == 0x34); + + #if defined(ATMOSPHERE_ARCH_ARM64) + struct KernelSystemRegisters { + u64 ttbr0_el1; + u64 ttbr1_el1; + u64 tcr_el1; + u64 mair_el1; + u64 sctlr_el1; + }; + #else + struct KernelSystemRegisters { + }; + #endif + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/init/kern_init_page_table_select.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/init/kern_init_page_table_select.hpp new file mode 100644 index 00000000..f7ae26d0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/init/kern_init_page_table_select.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +#ifdef ATMOSPHERE_ARCH_ARM64 + #include <mesosphere/arch/arm64/init/kern_k_init_page_table.hpp> + + namespace ams::kern::init { + using ams::kern::arch::arm64::PageTableEntry; + using ams::kern::arch::arm64::init::KInitialPageTable; + using ams::kern::arch::arm64::init::KInitialPageAllocator; + } +#else + #error "Unknown architecture for KInitialPageTable" +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/init/kern_init_slab_setup.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/init/kern_init_slab_setup.hpp new file mode 100644 index 00000000..209b8d73 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/init/kern_init_slab_setup.hpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <mesosphere/kern_k_slab_heap.hpp> + +namespace ams::kern::init { + + struct KSlabResourceCounts { + size_t num_KProcess; + size_t num_KThread; + size_t num_KEvent; + size_t num_KInterruptEvent; + size_t num_KPort; + size_t num_KSharedMemory; + size_t num_KTransferMemory; + size_t num_KCodeMemory; + size_t num_KDeviceAddressSpace; + size_t num_KSession; + size_t num_KLightSession; + size_t num_KObjectName; + size_t num_KResourceLimit; + size_t num_KDebug; + size_t num_KIoPool; + size_t num_KIoRegion; + size_t num_KSessionRequestMappings; + }; + + NOINLINE void InitializeSlabResourceCounts(); + const KSlabResourceCounts &GetSlabResourceCounts(); + + size_t CalculateTotalSlabHeapSize(); + NOINLINE void InitializeSlabHeaps(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_build_config.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_build_config.hpp new file mode 100644 index 00000000..6ecd2291 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_build_config.hpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#if defined(AMS_BUILD_FOR_AUDITING) +#define MESOSPHERE_BUILD_FOR_AUDITING +#endif + +#if defined(MESOSPHERE_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING) +#define MESOSPHERE_BUILD_FOR_DEBUGGING +#endif + +#ifdef MESOSPHERE_BUILD_FOR_DEBUGGING +#define MESOSPHERE_ENABLE_ASSERTIONS +#define MESOSPHERE_ENABLE_DEBUG_PRINT +#define MESOSPHERE_ENABLE_KERNEL_STACK_USAGE +#endif + +#if defined(MESOSPHERE_BUILD_FOR_DEBUGGING) +#define MESOSPHERE_NOINLINE_IF_DEBUG NOINLINE +#define MESOSPHERE_ALWAYS_INLINE_IF_RELEASE NOINLINE +#else +#define MESOSPHERE_NOINLINE_IF_DEBUG +#define MESOSPHERE_ALWAYS_INLINE_IF_RELEASE ALWAYS_INLINE +#endif + +//#define MESOSPHERE_BUILD_FOR_TRACING +//#define MESOSPHERE_ENABLE_PERFORMANCE_COUNTER +#define MESOSPHERE_ENABLE_PANIC_REGISTER_DUMP +#define MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP + +/* NOTE: In 16.0.0, Nintendo deleted the creation time field for KProcess, */ +/* but this may be useful for some debugging applications, and so can be. */ +/* re-enabled by toggling this define. */ +//#define MESOSPHERE_ENABLE_PROCESS_CREATION_TIME + +/* NOTE: This enables fast class token storage using a class member. */ +/* This saves a virtual call when doing KAutoObject->DynCast<>(), */ +/* at the cost of storing class tokens inside the class object. */ +/* However, as of (10/16/2021) KAutoObject has an unused class member */ +/* of the right side, and so this does not actually cost any space. */ +#define MESOSPHERE_ENABLE_DEVIRTUALIZED_DYNAMIC_CAST + +/* NOTE: This enables usage of KDebug handles as parameter for svc::GetInfo */ +/* calls which require a process parameter. This enables a debugger to obtain */ +/* address space/layout information, for example. However, it changes abi, and so */ +/* this define allows toggling the extension. */ +#define MESOSPHERE_ENABLE_GET_INFO_OF_DEBUG_PROCESS + +/* NOTE: This uses currently-reserved bits inside the MapRange capability */ +/* in order to support large physical addresses (40-bit instead of 36). */ +/* This is toggleable in order to disable it if N ever uses those bits. */ +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) +//#define MESOSPHERE_ENABLE_LARGE_PHYSICAL_ADDRESS_CAPABILITIES +#else +#define MESOSPHERE_ENABLE_LARGE_PHYSICAL_ADDRESS_CAPABILITIES +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_common.hpp new file mode 100644 index 00000000..bdea4adb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_common.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <mesosphere/kern_build_config.hpp> +#include <mesosphere/svc/kern_svc_results.hpp> +#include <mesosphere/kern_select_assembly_offsets.h> + +namespace ams::kern { + + constexpr size_t PageSize = 4_KB; + +#ifdef ATMOSPHERE_BOARD_NINTENDO_NX + ams::TargetFirmware GetTargetFirmware(); +#else + consteval ALWAYS_INLINE ams::TargetFirmware GetTargetFirmware() { + return ams::TargetFirmware_Current; + } +#endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_debug_log.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_debug_log.hpp new file mode 100644 index 00000000..51b83596 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_debug_log.hpp @@ -0,0 +1,67 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/svc/kern_svc_k_user_pointer.hpp> + +namespace ams::kern { + + class KDebugLog { + private: + static NOINLINE void VSNPrintf(char *dst, const size_t dst_size, const char *format, ::std::va_list vl); + public: + static NOINLINE void Initialize(); + + static NOINLINE void Printf(const char *format, ...) __attribute__((format(printf, 1, 2))); + static NOINLINE void VPrintf(const char *format, ::std::va_list vl); + + static NOINLINE void LogException(const char *str); + + static NOINLINE Result PrintUserString(ams::kern::svc::KUserPointer<const char *> user_str, size_t len); + + /* Functionality for preserving across sleep. */ + static NOINLINE void Save(); + static NOINLINE void Restore(); + }; + +} + +#ifndef MESOSPHERE_DEBUG_LOG_SELECTED + + #ifdef ATMOSPHERE_BOARD_NINTENDO_NX + #define MESOSPHERE_DEBUG_LOG_USE_UART + #elif defined(ATMOSPHERE_BOARD_QEMU_VIRT) + #define MESOSPHERE_DEBUG_LOG_USE_SEMIHOSTING + #else + #error "Unknown board for Default Debug Log Source" + #endif + + #define MESOSPHERE_DEBUG_LOG_SELECTED + +#endif + +#define MESOSPHERE_EXCEPTION_LOG(str) ::ams::kern::KDebugLog::LogException(str) + +#define MESOSPHERE_RELEASE_LOG(fmt, ...) ::ams::kern::KDebugLog::Printf((fmt), ## __VA_ARGS__) +#define MESOSPHERE_RELEASE_VLOG(fmt, vl) ::ams::kern::KDebugLog::VPrintf((fmt), (vl)) + +#ifdef MESOSPHERE_ENABLE_DEBUG_PRINT +#define MESOSPHERE_LOG(fmt, ...) MESOSPHERE_RELEASE_LOG((fmt), ## __VA_ARGS__) +#define MESOSPHERE_VLOG(fmt, vl) MESOSPHERE_RELEASE_VLOG((fmt), (vl)) +#else +#define MESOSPHERE_LOG(fmt, ...) do { MESOSPHERE_UNUSED(fmt); MESOSPHERE_UNUSED(__VA_ARGS__); } while (0) +#define MESOSPHERE_VLOG(fmt, vl) do { MESOSPHERE_UNUSED(fmt); MESOSPHERE_UNUSED(vl); } while (0) +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_initial_process.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_initial_process.hpp new file mode 100644 index 00000000..a7dfeab7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_initial_process.hpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_initial_process_reader.hpp> + +namespace ams::kern { + + constexpr u32 InitialProcessBinaryMagic = util::FourCC<'I','N','I','1'>::Code; + constexpr size_t InitialProcessBinarySizeMax = 12_MB; + + struct InitialProcessBinaryHeader { + u32 magic; + u32 size; + u32 num_processes; + u32 reserved; + }; + + struct InitialProcessBinaryLayout { + uintptr_t address; + uintptr_t _08; + uintptr_t kern_address; + }; + + struct InitialProcessBinaryLayoutWithSize { + InitialProcessBinaryLayout layout; + size_t size; + }; + + KPhysicalAddress GetInitialProcessBinaryPhysicalAddress(); + size_t GetInitialProcessBinarySize(); + void SetInitialProcessBinaryPhysicalAddress(KPhysicalAddress phys_addr, size_t size); + + u64 GetInitialProcessIdMin(); + u64 GetInitialProcessIdMax(); + size_t GetInitialProcessesSecureMemorySize(); + + NOINLINE size_t CopyInitialProcessBinaryToKernelMemory(); + NOINLINE void CreateAndRunInitialProcesses(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_address_arbiter.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_address_arbiter.hpp new file mode 100644 index 00000000..f4ef74e6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_address_arbiter.hpp @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_condition_variable.hpp> + +namespace ams::kern { + + class KAddressArbiter { + public: + using ThreadTree = KConditionVariable::ThreadTree; + private: + ThreadTree m_tree; + public: + constexpr KAddressArbiter() = default; + + Result SignalToAddress(uintptr_t addr, ams::svc::SignalType type, s32 value, s32 count) { + switch (type) { + case ams::svc::SignalType_Signal: + R_RETURN(this->Signal(addr, count)); + case ams::svc::SignalType_SignalAndIncrementIfEqual: + R_RETURN(this->SignalAndIncrementIfEqual(addr, value, count)); + case ams::svc::SignalType_SignalAndModifyByWaitingCountIfEqual: + R_RETURN(this->SignalAndModifyByWaitingCountIfEqual(addr, value, count)); + MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); + } + } + + Result WaitForAddress(uintptr_t addr, ams::svc::ArbitrationType type, s64 value, s64 timeout) { + switch (type) { + case ams::svc::ArbitrationType_WaitIfLessThan: + R_RETURN(this->WaitIfLessThan(addr, static_cast<s32>(value), false, timeout)); + case ams::svc::ArbitrationType_DecrementAndWaitIfLessThan: + R_RETURN(this->WaitIfLessThan(addr, static_cast<s32>(value), true, timeout)); + case ams::svc::ArbitrationType_WaitIfEqual: + R_RETURN(this->WaitIfEqual(addr, static_cast<s32>(value), timeout)); + case ams::svc::ArbitrationType_WaitIfEqual64: + R_RETURN(this->WaitIfEqual64(addr, value, timeout)); + MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); + } + } + private: + Result Signal(uintptr_t addr, s32 count); + Result SignalAndIncrementIfEqual(uintptr_t addr, s32 value, s32 count); + Result SignalAndModifyByWaitingCountIfEqual(uintptr_t addr, s32 value, s32 count); + Result WaitIfLessThan(uintptr_t addr, s32 value, bool decrement, s64 timeout); + Result WaitIfEqual(uintptr_t addr, s32 value, s64 timeout); + Result WaitIfEqual64(uintptr_t addr, s64 value, s64 timeout); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_address_space_info.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_address_space_info.hpp new file mode 100644 index 00000000..775c0054 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_address_space_info.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +namespace ams::kern { + + + + struct KAddressSpaceInfo { + public: + enum Type { + Type_MapSmall = 0, + Type_MapLarge = 1, + Type_Map39Bit = 2, + Type_Heap = 3, + Type_Stack = 4, + Type_Alias = 5, + + Type_Count, + }; + private: + size_t m_bit_width; + size_t m_address; + size_t m_size; + Type m_type; + public: + static uintptr_t GetAddressSpaceStart(ams::svc::CreateProcessFlag flags, Type type, size_t code_size); + static size_t GetAddressSpaceSize(ams::svc::CreateProcessFlag flags, Type type); + + static void SetAddressSpaceSize(size_t width, Type type, size_t size); + + constexpr KAddressSpaceInfo(size_t bw, size_t a, size_t s, Type t) : m_bit_width(bw), m_address(a), m_size(s), m_type(t) { /* ... */ } + + constexpr size_t GetWidth() const { return m_bit_width; } + constexpr size_t GetAddress() const { return m_address; } + constexpr size_t GetSize() const { return m_size; } + constexpr Type GetType() const { return m_type; } + + constexpr void SetSize(size_t size) { m_size = size; } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_affinity_mask.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_affinity_mask.hpp new file mode 100644 index 00000000..9b6d17a1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_affinity_mask.hpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_select_cpu.hpp> + +namespace ams::kern { + + class KAffinityMask { + private: + static constexpr u64 AllowedAffinityMask = (1ul << cpu::NumCores) - 1; + private: + u64 m_mask; + private: + static constexpr ALWAYS_INLINE u64 GetCoreBit(s32 core) { + MESOSPHERE_ASSERT(0 <= core && core < static_cast<s32>(cpu::NumCores)); + return (1ul << core); + } + public: + constexpr ALWAYS_INLINE KAffinityMask() : m_mask(0) { MESOSPHERE_ASSERT_THIS(); } + + constexpr ALWAYS_INLINE u64 GetAffinityMask() const { return m_mask; } + + constexpr ALWAYS_INLINE void SetAffinityMask(u64 new_mask) { + MESOSPHERE_ASSERT((new_mask & ~AllowedAffinityMask) == 0); + m_mask = new_mask; + } + + constexpr ALWAYS_INLINE bool GetAffinity(s32 core) const { + return m_mask & GetCoreBit(core); + } + + constexpr ALWAYS_INLINE void SetAffinity(s32 core, bool set) { + MESOSPHERE_ASSERT(0 <= core && core < static_cast<s32>(cpu::NumCores)); + + if (set) { + m_mask |= GetCoreBit(core); + } else { + m_mask &= ~GetCoreBit(core); + } + } + + constexpr ALWAYS_INLINE void SetAll() { + m_mask = AllowedAffinityMask; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_auto_object.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_auto_object.hpp new file mode 100644 index 00000000..dc99f5ce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_auto_object.hpp @@ -0,0 +1,371 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_typed_address.hpp> +#include <mesosphere/kern_k_class_token.hpp> + +namespace ams::kern { + + class KProcess; + + #if defined(MESOSPHERE_BUILD_FOR_DEBUGGING) || defined(MESOSPHERE_BUILD_FOR_AUDITING) + #define MESOSPHERE_AUTO_OBJECT_TYPENAME_IMPL(CLASS) #CLASS + #else + #define MESOSPHERE_AUTO_OBJECT_TYPENAME_IMPL(CLASS) "" + #endif + + #define MESOSPHERE_AUTOOBJECT_TRAITS(CLASS, BASE_CLASS) \ + NON_COPYABLE(CLASS); \ + NON_MOVEABLE(CLASS); \ + private: \ + friend class ::ams::kern::KClassTokenGenerator; \ + static constexpr inline auto ObjectType = ::ams::kern::KClassTokenGenerator::ObjectType::CLASS; \ + static constexpr inline const char * const TypeName = MESOSPHERE_AUTO_OBJECT_TYPENAME_IMPL(CLASS); \ + static constexpr inline ClassTokenType ClassToken() { return ::ams::kern::ClassToken<CLASS>; } \ + public: \ + using BaseClass = BASE_CLASS; \ + static consteval ALWAYS_INLINE TypeObj GetStaticTypeObj() { \ + constexpr ClassTokenType Token = ClassToken(); \ + return TypeObj(TypeName, Token); \ + } \ + static consteval ALWAYS_INLINE const char *GetStaticTypeName() { return TypeName; } \ + virtual TypeObj GetTypeObj() const { return GetStaticTypeObj(); } \ + virtual const char *GetTypeName() { return GetStaticTypeName(); } \ + private: + + class KAutoObject { + public: + class ReferenceCount { + NON_COPYABLE(ReferenceCount); + NON_MOVEABLE(ReferenceCount); + private: + using Storage = u32; + private: + util::Atomic<Storage> m_value; + public: + ALWAYS_INLINE explicit ReferenceCount() { /* ... */ } + constexpr ALWAYS_INLINE explicit ReferenceCount(Storage v) : m_value(v) { /* ... */ } + + ALWAYS_INLINE void operator=(Storage v) { m_value = v; } + + ALWAYS_INLINE Storage GetValue() const { return m_value.Load(); } + + ALWAYS_INLINE bool Open() { + /* Atomically increment the reference count, only if it's positive. */ + u32 cur = m_value.Load<std::memory_order_relaxed>(); + do { + if (AMS_UNLIKELY(cur == 0)) { + MESOSPHERE_AUDIT(cur != 0); + return false; + } + MESOSPHERE_ABORT_UNLESS(cur < cur + 1); + } while (AMS_UNLIKELY(!m_value.CompareExchangeWeak<std::memory_order_relaxed>(cur, cur + 1))); + + return true; + } + + ALWAYS_INLINE bool Close() { + /* Atomically decrement the reference count, not allowing it to decrement past zero. */ + u32 cur = m_value.Load<std::memory_order_relaxed>(); + do { + MESOSPHERE_ABORT_UNLESS(cur > 0); + } while (AMS_UNLIKELY(!m_value.CompareExchangeWeak<std::memory_order_relaxed>(cur, cur - 1))); + + /* Return whether the object was closed. */ + return cur - 1 == 0; + } + }; + protected: + class TypeObj { + private: + const char *m_name; + ClassTokenType m_class_token; + public: + constexpr explicit TypeObj(const char *n, ClassTokenType tok) : m_name(n), m_class_token(tok) { /* ... */ } + + constexpr ALWAYS_INLINE const char *GetName() const { return m_name; } + constexpr ALWAYS_INLINE ClassTokenType GetClassToken() const { return m_class_token; } + + constexpr ALWAYS_INLINE bool operator==(const TypeObj &rhs) { + return this->GetClassToken() == rhs.GetClassToken(); + } + + constexpr ALWAYS_INLINE bool operator!=(const TypeObj &rhs) { + return this->GetClassToken() != rhs.GetClassToken(); + } + + constexpr ALWAYS_INLINE bool IsDerivedFrom(const TypeObj &rhs) { + return IsClassTokenDerivedFrom(this->GetClassToken(), rhs.GetClassToken()); + } + + static constexpr ALWAYS_INLINE bool IsClassTokenDerivedFrom(ClassTokenType derived, ClassTokenType base) { + return (derived | base) == derived; + } + }; + private: + MESOSPHERE_AUTOOBJECT_TRAITS(KAutoObject, KAutoObject); + private: + KAutoObject *m_next_closed_object; + ReferenceCount m_ref_count; + #if defined(MESOSPHERE_ENABLE_DEVIRTUALIZED_DYNAMIC_CAST) + ClassTokenType m_class_token; + #endif + public: + constexpr ALWAYS_INLINE explicit KAutoObject(util::ConstantInitializeTag) : m_next_closed_object(nullptr), m_ref_count(0) + #if defined(MESOSPHERE_ENABLE_DEVIRTUALIZED_DYNAMIC_CAST) + , m_class_token(0) + #endif + { + MESOSPHERE_ASSERT_THIS(); + } + + ALWAYS_INLINE explicit KAutoObject() : m_ref_count(0) { MESOSPHERE_ASSERT_THIS(); } + + /* Destroy is responsible for destroying the auto object's resources when ref_count hits zero. */ + virtual void Destroy() { MESOSPHERE_ASSERT_THIS(); } + + /* Finalize is responsible for cleaning up resource, but does not destroy the object. */ + /* NOTE: This is a virtual function in official kernel, but because everything which uses it */ + /* is already using CRTP for slab heap, we have devirtualized it for performance gain. */ + /* virtual void Finalize() { MESOSPHERE_ASSERT_THIS(); } */ + + /* NOTE: This is a virtual function which is unused-except-for-debug in Nintendo's kernel. */ + /* virtual KProcess *GetOwner() const { return nullptr; } */ + + u32 GetReferenceCount() const { + return m_ref_count.GetValue(); + } + + ALWAYS_INLINE bool IsDerivedFrom(const TypeObj &rhs) const { + #if defined(MESOSPHERE_ENABLE_DEVIRTUALIZED_DYNAMIC_CAST) + return TypeObj::IsClassTokenDerivedFrom(m_class_token, rhs.GetClassToken()); + #else + return this->GetTypeObj().IsDerivedFrom(rhs); + #endif + } + + ALWAYS_INLINE bool IsDerivedFrom(const KAutoObject &rhs) const { + #if defined(MESOSPHERE_ENABLE_DEVIRTUALIZED_DYNAMIC_CAST) + return TypeObj::IsClassTokenDerivedFrom(m_class_token, rhs.m_class_token); + #else + return this->IsDerivedFrom(rhs.GetTypeObj()); + #endif + } + + template<typename Derived> + ALWAYS_INLINE Derived DynamicCast() { + static_assert(std::is_pointer<Derived>::value); + using DerivedType = typename std::remove_pointer<Derived>::type; + + if (AMS_LIKELY(this->IsDerivedFrom(DerivedType::GetStaticTypeObj()))) { + return static_cast<Derived>(this); + } else { + return nullptr; + } + } + + template<typename Derived> + ALWAYS_INLINE const Derived DynamicCast() const { + static_assert(std::is_pointer<Derived>::value); + using DerivedType = typename std::remove_pointer<Derived>::type; + + if (AMS_LIKELY(this->IsDerivedFrom(DerivedType::GetStaticTypeObj()))) { + return static_cast<Derived>(this); + } else { + return nullptr; + } + } + + MESOSPHERE_ALWAYS_INLINE_IF_RELEASE bool Open() { + MESOSPHERE_ASSERT_THIS(); + + return m_ref_count.Open(); + } + + MESOSPHERE_ALWAYS_INLINE_IF_RELEASE void Close() { + MESOSPHERE_ASSERT_THIS(); + + if (m_ref_count.Close()) { + this->ScheduleDestruction(); + } + } + private: + /* NOTE: This has to be defined *after* KThread is defined. */ + /* Nintendo seems to handle this by defining Open/Close() in a cpp, but we'd like them to remain in headers. */ + /* Implementation for this will be inside kern_k_thread.hpp, so it can be ALWAYS_INLINE. */ + void ScheduleDestruction(); + public: + /* Getter, for KThread. */ + ALWAYS_INLINE KAutoObject *GetNextClosedObject() { return m_next_closed_object; } + public: + template<typename Derived> requires (std::derived_from<Derived, KAutoObject>) + static ALWAYS_INLINE void Create(typename std::type_identity<Derived>::type *obj) { + /* Get auto object pointer. */ + KAutoObject &auto_object = *static_cast<KAutoObject *>(obj); + + /* If we should, set our class token. */ + #if defined(MESOSPHERE_ENABLE_DEVIRTUALIZED_DYNAMIC_CAST) + { + constexpr auto Token = Derived::GetStaticTypeObj().GetClassToken(); + auto_object.m_class_token = Token; + } + #endif + + /* Initialize reference count to 1. */ + auto_object.m_ref_count = 1; + } + }; + + class KAutoObjectWithListBase : public KAutoObject { + private: + void *m_alignment_forcer_unused[0]; + public: + constexpr ALWAYS_INLINE explicit KAutoObjectWithListBase(util::ConstantInitializeTag) : KAutoObject(util::ConstantInitialize), m_alignment_forcer_unused{} { /* ... */ } + + ALWAYS_INLINE explicit KAutoObjectWithListBase() { /* ... */ } + }; + + class KAutoObjectWithList : public KAutoObjectWithListBase { + private: + template<typename> + friend class KAutoObjectWithListContainer; + private: + util::IntrusiveRedBlackTreeNode m_list_node; + public: + constexpr ALWAYS_INLINE KAutoObjectWithList(util::ConstantInitializeTag) : KAutoObjectWithListBase(util::ConstantInitialize), m_list_node(util::ConstantInitialize) { /* ... */ } + ALWAYS_INLINE explicit KAutoObjectWithList() { /* ... */ } + public: + /* NOTE: This is virtual in Nintendo's kernel. */ + u64 GetId() const; + }; + + template<typename T> requires std::derived_from<T, KAutoObject> + class KScopedAutoObject { + NON_COPYABLE(KScopedAutoObject); + private: + template<typename U> requires std::derived_from<U, KAutoObject> + friend class KScopedAutoObject; + private: + T *m_obj; + private: + constexpr ALWAYS_INLINE void Swap(KScopedAutoObject &rhs) { + std::swap(m_obj, rhs.m_obj); + } + public: + constexpr ALWAYS_INLINE KScopedAutoObject(T *o) : m_obj(o) { + if (m_obj != nullptr) { + m_obj->Open(); + } + } + + ALWAYS_INLINE ~KScopedAutoObject() { + if (m_obj != nullptr) { + m_obj->Close(); + } + m_obj = nullptr; + } + + template<typename U> requires (std::derived_from<T, U> || std::derived_from<U, T>) + constexpr KScopedAutoObject(KScopedAutoObject<U> &&rhs) { + if constexpr (std::derived_from<U, T>) { + /* Upcast. */ + m_obj = rhs.m_obj; + rhs.m_obj = nullptr; + } else { + /* Downcast. */ + T *derived = nullptr; + if (rhs.m_obj != nullptr) { + derived = rhs.m_obj->template DynamicCast<T *>(); + if (derived == nullptr) { + rhs.m_obj->Close(); + } + } + + m_obj = derived; + rhs.m_obj = nullptr; + } + } + + constexpr ALWAYS_INLINE KScopedAutoObject<T> &operator=(KScopedAutoObject<T> &&rhs) { + rhs.Swap(*this); + return *this; + } + + constexpr ALWAYS_INLINE T *operator->() { return m_obj; } + constexpr ALWAYS_INLINE T &operator*() { return *m_obj; } + + constexpr ALWAYS_INLINE void Reset(T *o) { + KScopedAutoObject(o).Swap(*this); + } + + constexpr ALWAYS_INLINE T *GetPointerUnsafe() { return m_obj; } + + constexpr ALWAYS_INLINE T *ReleasePointerUnsafe() { T *ret = m_obj; m_obj = nullptr; return ret; } + + constexpr ALWAYS_INLINE bool IsNull() const { return m_obj == nullptr; } + constexpr ALWAYS_INLINE bool IsNotNull() const { return m_obj != nullptr; } + }; + + template<typename T> requires std::derived_from<T, KAutoObject> + class KSharedAutoObject { + private: + T *m_object; + KAutoObject::ReferenceCount m_ref_count; + public: + explicit KSharedAutoObject() : m_object(nullptr) { /* ... */ } + + void Attach(T *obj) { + MESOSPHERE_ASSERT(m_object == nullptr); + + /* Set our object. */ + m_object = obj; + + /* Open reference to our object. */ + m_object->Open(); + + /* Set our reference count. */ + m_ref_count = 1; + } + + bool Open() { + return m_ref_count.Open(); + } + + void Close() { + if (m_ref_count.Close()) { + this->Detach(); + } + } + + ALWAYS_INLINE T *Get() const { + return m_object; + } + private: + void Detach() { + /* Close our object, if we have one. */ + if (T * const object = m_object; AMS_LIKELY(object != nullptr)) { + /* Set our object to a debug sentinel value, which will cause a crash if accessed. */ + m_object = reinterpret_cast<T *>(1); + + /* Close reference to our object. */ + object->Close(); + } + } + }; + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_auto_object_container.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_auto_object_container.hpp new file mode 100644 index 00000000..e7b18cdc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_auto_object_container.hpp @@ -0,0 +1,151 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_auto_object.hpp> +#include <mesosphere/kern_k_light_lock.hpp> + +namespace ams::kern { + + namespace impl { + + template<typename T> + struct GetAutoObjectWithListComparator; + + class KAutoObjectWithListContainerBase { + NON_COPYABLE(KAutoObjectWithListContainerBase); + NON_MOVEABLE(KAutoObjectWithListContainerBase); + protected: + template<typename ListType> + class ListAccessorImpl { + NON_COPYABLE(ListAccessorImpl); + NON_MOVEABLE(ListAccessorImpl); + private: + KScopedLightLock m_lk; + ListType &m_list; + public: + explicit ALWAYS_INLINE ListAccessorImpl(KAutoObjectWithListContainerBase *container, ListType &list) : m_lk(container->m_lock), m_list(list) { /* ... */ } + explicit ALWAYS_INLINE ListAccessorImpl(KAutoObjectWithListContainerBase &container, ListType &list) : m_lk(container.m_lock), m_list(list) { /* ... */ } + + ALWAYS_INLINE ~ListAccessorImpl() { /* ... */ } + + ALWAYS_INLINE typename ListType::iterator begin() const { + return m_list.begin(); + } + + ALWAYS_INLINE typename ListType::iterator end() const { + return m_list.end(); + } + + ALWAYS_INLINE typename ListType::iterator find(typename ListType::const_reference ref) const { + return m_list.find(ref); + } + + ALWAYS_INLINE typename ListType::iterator find_key(typename ListType::const_key_reference ref) const { + return m_list.find_key(ref); + } + }; + + template<typename ListType> + friend class ListAccessorImpl; + private: + KLightLock m_lock; + protected: + constexpr KAutoObjectWithListContainerBase() : m_lock() { /* ... */ } + + ALWAYS_INLINE void InitializeImpl() { MESOSPHERE_ASSERT_THIS(); } + ALWAYS_INLINE void FinalizeImpl() { MESOSPHERE_ASSERT_THIS(); } + + template<typename ListType> + void RegisterImpl(KAutoObjectWithList *obj, ListType &list) { + MESOSPHERE_ASSERT_THIS(); + + KScopedLightLock lk(m_lock); + + list.insert(*obj); + } + + template<typename ListType> + void UnregisterImpl(KAutoObjectWithList *obj, ListType &list) { + MESOSPHERE_ASSERT_THIS(); + + KScopedLightLock lk(m_lock); + + list.erase(list.iterator_to(*obj)); + } + + template<typename U, typename ListType> + size_t GetOwnedCountImpl(const KProcess *owner, ListType &list) { + MESOSPHERE_ASSERT_THIS(); + + KScopedLightLock lk(m_lock); + + size_t count = 0; + + for (const auto &obj : list) { + MESOSPHERE_AUDIT(obj.template DynamicCast<const U *>() != nullptr); + if (static_cast<const U &>(obj).GetOwner() == owner) { + ++count; + } + } + + return count; + } + }; + + struct DummyKAutoObjectWithListComparator { + static NOINLINE int Compare(KAutoObjectWithList &lhs, KAutoObjectWithList &rhs) { + MESOSPHERE_UNUSED(lhs, rhs); + MESOSPHERE_PANIC("DummyKAutoObjectWithListComparator invoked"); + } + }; + + } + + template<typename T> + class KAutoObjectWithListContainer : public impl::KAutoObjectWithListContainerBase { + private: + using Base = impl::KAutoObjectWithListContainerBase; + public: + class ListAccessor; + friend class ListAccessor; + + template<typename Comparator> + using ListType = util::IntrusiveRedBlackTreeMemberTraits<&KAutoObjectWithList::m_list_node>::TreeType<Comparator>; + + using DummyListType = ListType<impl::DummyKAutoObjectWithListComparator>; + private: + DummyListType m_dummy_object_list; + public: + constexpr ALWAYS_INLINE KAutoObjectWithListContainer() : Base(), m_dummy_object_list() { static_assert(std::derived_from<T, KAutoObjectWithList>); } + + ALWAYS_INLINE void Initialize() { return this->InitializeImpl(); } + ALWAYS_INLINE void Finalize() { return this->FinalizeImpl(); } + + void Register(T *obj); + void Unregister(T *obj); + + private: + size_t GetOwnedCountChecked(const KProcess *owner); + public: + template<typename U> requires (std::same_as<U, T> && requires (const U &u) { { u.GetOwner() } -> std::convertible_to<const KProcess *>; }) + ALWAYS_INLINE size_t GetOwnedCount(const KProcess *owner) { + return this->GetOwnedCountChecked(owner); + } + }; + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_auto_object_impls.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_auto_object_impls.hpp new file mode 100644 index 00000000..8000f999 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_auto_object_impls.hpp @@ -0,0 +1,163 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_class_token.hpp> + +namespace ams::kern { + + /* NOTE: This header is included after all other KAutoObjects. */ + namespace impl { + + template<typename T> requires std::derived_from<T, KAutoObject> + consteval bool IsAutoObjectInheritanceValidImpl() { + #define CLASS_TOKEN_HANDLER(CLASSNAME) \ + if constexpr (std::same_as<T, CLASSNAME>) { \ + if (T::GetStaticTypeObj().GetClassToken() != ::ams::kern::ClassToken<CLASSNAME>) { \ + return false; \ + } \ + } else { \ + if (T::GetStaticTypeObj().IsDerivedFrom(CLASSNAME::GetStaticTypeObj()) != std::derived_from<T, CLASSNAME>) { \ + return false; \ + } \ + } + + FOR_EACH_K_CLASS_TOKEN_OBJECT_TYPE(CLASS_TOKEN_HANDLER) + #undef CLASS_TOKEN_HANDLER + + return true; + } + + consteval bool IsEveryAutoObjectInheritanceValid() { + #define CLASS_TOKEN_HANDLER(CLASSNAME) if (!IsAutoObjectInheritanceValidImpl<CLASSNAME>()) { return false; } + FOR_EACH_K_CLASS_TOKEN_OBJECT_TYPE(CLASS_TOKEN_HANDLER) + #undef CLASS_TOKEN_HANDLER + + return true; + } + + static_assert(IsEveryAutoObjectInheritanceValid()); + + template<typename T> + concept IsAutoObjectWithSpecializedGetId = std::derived_from<T, KAutoObjectWithList> && requires (const T &t, const KAutoObjectWithList &l) { + { t.GetIdImpl() } -> std::same_as<decltype(l.GetId())>; + }; + + template<typename T> + struct AutoObjectWithListComparatorImpl { + using RedBlackKeyType = u64; + + static ALWAYS_INLINE RedBlackKeyType GetRedBlackKey(const RedBlackKeyType &v) { return v; } + + static ALWAYS_INLINE RedBlackKeyType GetRedBlackKey(const KAutoObjectWithList &v) { + if constexpr (IsAutoObjectWithSpecializedGetId<T>) { + return static_cast<const T &>(v).GetIdImpl(); + } else { + return reinterpret_cast<u64>(std::addressof(v)); + } + } + + template<typename U> requires (std::same_as<U, KAutoObjectWithList> || std::same_as<U, RedBlackKeyType>) + static ALWAYS_INLINE int Compare(const U &lhs, const KAutoObjectWithList &rhs) { + const u64 lid = GetRedBlackKey(lhs); + const u64 rid = GetRedBlackKey(rhs); + + if (lid < rid) { + return -1; + } else if (lid > rid) { + return 1; + } else { + return 0; + } + } + }; + + template<typename T> + using AutoObjectWithListComparator = AutoObjectWithListComparatorImpl<typename std::conditional<IsAutoObjectWithSpecializedGetId<T>, T, KAutoObjectWithList>::type>; + + template<typename T> + using TrueObjectContainerListType = typename KAutoObjectWithListContainer<T>::ListType<AutoObjectWithListComparator<T>>; + + template<typename T> + ALWAYS_INLINE TrueObjectContainerListType<T> &GetTrueObjectContainerList(typename KAutoObjectWithListContainer<T>::DummyListType &l) { + static_assert(alignof(l) == alignof(impl::TrueObjectContainerListType<T>)); + static_assert(sizeof(l) == sizeof(impl::TrueObjectContainerListType<T>)); + return *reinterpret_cast<TrueObjectContainerListType<T> *>(std::addressof(l)); + } + + } + + inline NOINLINE void KAutoObject::ScheduleDestruction() { + MESOSPHERE_ASSERT_THIS(); + + /* Set our object to destroy. */ + m_next_closed_object = GetCurrentThread().GetClosedObject(); + + /* Set ourselves as the thread's next object to destroy. */ + GetCurrentThread().SetClosedObject(this); + } + + template<typename T> + class KAutoObjectWithListContainer<T>::ListAccessor : public impl::KAutoObjectWithListContainerBase::ListAccessorImpl<impl::TrueObjectContainerListType<T>> { + NON_COPYABLE(ListAccessor); + NON_MOVEABLE(ListAccessor); + private: + using BaseListAccessor = impl::KAutoObjectWithListContainerBase::ListAccessorImpl<impl::TrueObjectContainerListType<T>>; + public: + explicit ALWAYS_INLINE ListAccessor(KAutoObjectWithListContainer *container) : BaseListAccessor(container, impl::GetTrueObjectContainerList<T>(container->m_dummy_object_list)) { /* ... */ } + explicit ALWAYS_INLINE ListAccessor(KAutoObjectWithListContainer &container) : BaseListAccessor(container, impl::GetTrueObjectContainerList<T>(container.m_dummy_object_list)) { /* ... */ } + + ALWAYS_INLINE ~ListAccessor() { /* ... */ } + }; + + + template<typename T> + ALWAYS_INLINE void KAutoObjectWithListContainer<T>::Register(T *obj) { + return this->RegisterImpl(obj, impl::GetTrueObjectContainerList<T>(m_dummy_object_list)); + } + + template<typename T> + ALWAYS_INLINE void KAutoObjectWithListContainer<T>::Unregister(T *obj) { + return this->UnregisterImpl(obj, impl::GetTrueObjectContainerList<T>(m_dummy_object_list)); + } + + template<typename T> + ALWAYS_INLINE size_t KAutoObjectWithListContainer<T>::GetOwnedCountChecked(const KProcess *owner) { + return this->GetOwnedCountImpl<T>(owner, impl::GetTrueObjectContainerList<T>(m_dummy_object_list)); + } + + inline u64 KAutoObjectWithList::GetId() const { + #define CLASS_TOKEN_HANDLER(CLASSNAME) \ + if constexpr (impl::IsAutoObjectWithSpecializedGetId<CLASSNAME>) { \ + if (const CLASSNAME * const derived = this->DynamicCast<const CLASSNAME *>(); derived != nullptr) { \ + return []<typename T>(const T * const t_derived) ALWAYS_INLINE_LAMBDA -> u64 { \ + static_assert(std::same_as<T, CLASSNAME>); \ + if constexpr (impl::IsAutoObjectWithSpecializedGetId<CLASSNAME>) { \ + return impl::AutoObjectWithListComparator<CLASSNAME>::GetRedBlackKey(*t_derived); \ + } else { \ + AMS_ASSUME(false); \ + } \ + }(derived); \ + } \ + } + + FOR_EACH_K_CLASS_TOKEN_OBJECT_TYPE(CLASS_TOKEN_HANDLER) + #undef CLASS_TOKEN_HANDLER + + return impl::AutoObjectWithListComparator<KAutoObjectWithList>::GetRedBlackKey(*this); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_capabilities.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_capabilities.hpp new file mode 100644 index 00000000..2c74b835 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_capabilities.hpp @@ -0,0 +1,272 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_thread.hpp> +#include <mesosphere/kern_select_page_table.hpp> +#include <mesosphere/kern_svc.hpp> + +namespace ams::kern { + + class KCapabilities { + private: + static constexpr size_t InterruptIdCount = 0x400; + + struct InterruptFlagSetTag{}; + using InterruptFlagSet = util::BitFlagSet<InterruptIdCount, InterruptFlagSetTag>; + + enum class CapabilityType : u32 { + CorePriority = (1u << 3) - 1, + SyscallMask = (1u << 4) - 1, + MapRange = (1u << 6) - 1, + MapIoPage = (1u << 7) - 1, + MapRegion = (1u << 10) - 1, + InterruptPair = (1u << 11) - 1, + ProgramType = (1u << 13) - 1, + KernelVersion = (1u << 14) - 1, + HandleTable = (1u << 15) - 1, + DebugFlags = (1u << 16) - 1, + + Invalid = 0u, + Padding = ~0u, + }; + + using RawCapabilityValue = util::BitPack32::Field<0, BITSIZEOF(util::BitPack32), u32>; + + static constexpr CapabilityType GetCapabilityType(const util::BitPack32 cap) { + const u32 value = cap.Get<RawCapabilityValue>(); + return static_cast<CapabilityType>((~value & (value + 1)) - 1); + } + + static constexpr u32 GetCapabilityFlag(CapabilityType type) { + return static_cast<u32>(type) + 1; + } + + template<size_t Index, size_t Count, typename T = u32> + using Field = util::BitPack32::Field<Index, Count, T>; + + #define DEFINE_FIELD(name, prev, ...) using name = Field<prev::Next, __VA_ARGS__> + + template<CapabilityType Type> + static constexpr inline u32 CapabilityFlag = static_cast<u32>(Type) + 1; + + template<CapabilityType Type> + static constexpr inline u32 CapabilityId = util::CountTrailingZeros<u32>(CapabilityFlag<Type>); + + struct CorePriority { + using IdBits = Field<0, CapabilityId<CapabilityType::CorePriority> + 1>; + + DEFINE_FIELD(LowestThreadPriority, IdBits, 6); + DEFINE_FIELD(HighestThreadPriority, LowestThreadPriority, 6); + DEFINE_FIELD(MinimumCoreId, HighestThreadPriority, 8); + DEFINE_FIELD(MaximumCoreId, MinimumCoreId, 8); + }; + + struct SyscallMask { + using IdBits = Field<0, CapabilityId<CapabilityType::SyscallMask> + 1>; + + DEFINE_FIELD(Mask, IdBits, 24); + DEFINE_FIELD(Index, Mask, 3); + }; + + #if defined(MESOSPHERE_ENABLE_LARGE_PHYSICAL_ADDRESS_CAPABILITIES) + static constexpr u64 PhysicalMapAllowedMask = (1ul << 40) - 1; + #else + static constexpr u64 PhysicalMapAllowedMask = (1ul << 36) - 1; + #endif + + struct MapRange { + using IdBits = Field<0, CapabilityId<CapabilityType::MapRange> + 1>; + + DEFINE_FIELD(Address, IdBits, 24); + DEFINE_FIELD(ReadOnly, Address, 1, bool); + }; + + struct MapRangeSize { + using IdBits = Field<0, CapabilityId<CapabilityType::MapRange> + 1>; + + DEFINE_FIELD(Pages, IdBits, 20); + + #if defined(MESOSPHERE_ENABLE_LARGE_PHYSICAL_ADDRESS_CAPABILITIES) + DEFINE_FIELD(AddressHigh, Pages, 4); + DEFINE_FIELD(Normal, AddressHigh, 1, bool); + #else + DEFINE_FIELD(Reserved, Pages, 4); + DEFINE_FIELD(Normal, Reserved, 1, bool); + #endif + }; + + struct MapIoPage { + using IdBits = Field<0, CapabilityId<CapabilityType::MapIoPage> + 1>; + + DEFINE_FIELD(Address, IdBits, 24); + }; + + enum class RegionType : u32 { + NoMapping = 0, + KernelTraceBuffer = 1, + OnMemoryBootImage = 2, + DTB = 3, + }; + + struct MapRegion { + using IdBits = Field<0, CapabilityId<CapabilityType::MapRegion> + 1>; + + DEFINE_FIELD(Region0, IdBits, 6, RegionType); + DEFINE_FIELD(ReadOnly0, Region0, 1, bool); + DEFINE_FIELD(Region1, ReadOnly0, 6, RegionType); + DEFINE_FIELD(ReadOnly1, Region1, 1, bool); + DEFINE_FIELD(Region2, ReadOnly1, 6, RegionType); + DEFINE_FIELD(ReadOnly2, Region2, 1, bool); + }; + + static const u32 PaddingInterruptId = 0x3FF; + static_assert(PaddingInterruptId < InterruptIdCount); + + struct InterruptPair { + using IdBits = Field<0, CapabilityId<CapabilityType::InterruptPair> + 1>; + + DEFINE_FIELD(InterruptId0, IdBits, 10); + DEFINE_FIELD(InterruptId1, InterruptId0, 10); + }; + + + struct ProgramType { + using IdBits = Field<0, CapabilityId<CapabilityType::ProgramType> + 1>; + + DEFINE_FIELD(Type, IdBits, 3); + DEFINE_FIELD(Reserved, Type, 15); + }; + + struct KernelVersion { + using IdBits = Field<0, CapabilityId<CapabilityType::KernelVersion> + 1>; + + DEFINE_FIELD(MinorVersion, IdBits, 4); + DEFINE_FIELD(MajorVersion, MinorVersion, 13); + }; + + struct HandleTable { + using IdBits = Field<0, CapabilityId<CapabilityType::HandleTable> + 1>; + + DEFINE_FIELD(Size, IdBits, 10); + DEFINE_FIELD(Reserved, Size, 6); + }; + + struct DebugFlags { + using IdBits = Field<0, CapabilityId<CapabilityType::DebugFlags> + 1>; + + DEFINE_FIELD(AllowDebug, IdBits, 1, bool); + DEFINE_FIELD(ForceDebugProd, AllowDebug, 1, bool); + DEFINE_FIELD(ForceDebug, ForceDebugProd, 1, bool); + DEFINE_FIELD(Reserved, ForceDebug, 12); + }; + + #undef DEFINE_FIELD + + static constexpr u32 InitializeOnceFlags = CapabilityFlag<CapabilityType::CorePriority> | + CapabilityFlag<CapabilityType::ProgramType> | + CapabilityFlag<CapabilityType::KernelVersion> | + CapabilityFlag<CapabilityType::HandleTable> | + CapabilityFlag<CapabilityType::DebugFlags>; + private: + svc::SvcAccessFlagSet m_svc_access_flags; + InterruptFlagSet m_irq_access_flags; + u64 m_core_mask; + u64 m_phys_core_mask; + u64 m_priority_mask; + util::BitPack32 m_debug_capabilities; + s32 m_handle_table_size; + util::BitPack32 m_intended_kernel_version; + u32 m_program_type; + private: + constexpr bool SetSvcAllowed(u32 id) { + if (AMS_LIKELY(id < static_cast<u32>(m_svc_access_flags.GetCount()))) { + m_svc_access_flags[id] = true; + return true; + } else { + return false; + } + } + + constexpr bool SetInterruptPermitted(u32 id) { + if (AMS_LIKELY(id < static_cast<u32>(m_irq_access_flags.GetCount()))) { + m_irq_access_flags[id] = true; + return true; + } else { + return false; + } + } + + Result SetCorePriorityCapability(const util::BitPack32 cap); + Result SetSyscallMaskCapability(const util::BitPack32 cap, u32 &set_svc); + Result MapRange(const util::BitPack32 cap, const util::BitPack32 size_cap, KProcessPageTable *page_table); + Result MapIoPage(const util::BitPack32 cap, KProcessPageTable *page_table); + Result MapRegion(const util::BitPack32 cap, KProcessPageTable *page_table); + Result SetInterruptPairCapability(const util::BitPack32 cap); + Result SetProgramTypeCapability(const util::BitPack32 cap); + Result SetKernelVersionCapability(const util::BitPack32 cap); + Result SetHandleTableCapability(const util::BitPack32 cap); + Result SetDebugFlagsCapability(const util::BitPack32 cap); + + template<typename F> + static Result ProcessMapRegionCapability(const util::BitPack32 cap, F f); + static Result CheckMapRegion(const util::BitPack32 cap); + + Result SetCapability(const util::BitPack32 cap, u32 &set_flags, u32 &set_svc, KProcessPageTable *page_table); + Result SetCapabilities(const u32 *caps, s32 num_caps, KProcessPageTable *page_table); + Result SetCapabilities(svc::KUserPointer<const u32 *> user_caps, s32 num_caps, KProcessPageTable *page_table); + public: + constexpr explicit KCapabilities(util::ConstantInitializeTag) : m_svc_access_flags{}, m_irq_access_flags{}, m_core_mask{}, m_phys_core_mask{}, m_priority_mask{}, m_debug_capabilities{0}, m_handle_table_size{}, m_intended_kernel_version{}, m_program_type{} { /* ... */ } + KCapabilities() { /* ... */ } + + Result Initialize(const u32 *caps, s32 num_caps, KProcessPageTable *page_table); + Result Initialize(svc::KUserPointer<const u32 *> user_caps, s32 num_caps, KProcessPageTable *page_table); + + static Result CheckCapabilities(svc::KUserPointer<const u32 *> user_caps, s32 num_caps); + + constexpr u64 GetCoreMask() const { return m_core_mask; } + constexpr u64 GetPhysicalCoreMask() const { return m_phys_core_mask; } + constexpr u64 GetPriorityMask() const { return m_priority_mask; } + constexpr s32 GetHandleTableSize() const { return m_handle_table_size; } + + constexpr const svc::SvcAccessFlagSet &GetSvcPermissions() const { return m_svc_access_flags; } + + constexpr bool IsPermittedSvc(svc::SvcId id) const { + return (id < m_svc_access_flags.GetCount()) && m_svc_access_flags[id]; + } + + constexpr bool IsPermittedInterrupt(u32 id) const { + return (id < m_irq_access_flags.GetCount()) && m_irq_access_flags[id]; + } + + constexpr bool IsPermittedDebug() const { + return m_debug_capabilities.Get<DebugFlags::AllowDebug>(); + } + + constexpr bool CanForceDebugProd() const { + return m_debug_capabilities.Get<DebugFlags::ForceDebugProd>(); + } + + constexpr bool CanForceDebug() const { + return m_debug_capabilities.Get<DebugFlags::ForceDebug>(); + } + + constexpr u32 GetIntendedKernelMajorVersion() const { return m_intended_kernel_version.Get<KernelVersion::MajorVersion>(); } + constexpr u32 GetIntendedKernelMinorVersion() const { return m_intended_kernel_version.Get<KernelVersion::MinorVersion>(); } + constexpr u32 GetIntendedKernelVersion() const { return ams::svc::EncodeKernelVersion(this->GetIntendedKernelMajorVersion(), this->GetIntendedKernelMinorVersion()); } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_class_token.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_class_token.hpp new file mode 100644 index 00000000..2dca3e00 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_class_token.hpp @@ -0,0 +1,204 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_typed_address.hpp> + +namespace ams::kern { + + class KAutoObject; + + class KSystemResource; + + #define FOR_EACH_K_CLASS_TOKEN_OBJECT_TYPE(HANDLER) \ + HANDLER(KAutoObject) \ + \ + HANDLER(KSynchronizationObject) \ + HANDLER(KReadableEvent) \ + \ + HANDLER(KInterruptEvent) \ + HANDLER(KDebug) \ + HANDLER(KThread) \ + HANDLER(KServerPort) \ + HANDLER(KServerSession) \ + HANDLER(KClientPort) \ + HANDLER(KClientSession) \ + HANDLER(KProcess) \ + HANDLER(KResourceLimit) \ + HANDLER(KLightSession) \ + HANDLER(KPort) \ + HANDLER(KSession) \ + HANDLER(KSharedMemory) \ + HANDLER(KEvent) \ + HANDLER(KLightClientSession) \ + HANDLER(KLightServerSession) \ + HANDLER(KTransferMemory) \ + HANDLER(KDeviceAddressSpace) \ + HANDLER(KSessionRequest) \ + HANDLER(KCodeMemory) \ + HANDLER(KIoPool) \ + HANDLER(KIoRegion) \ + HANDLER(KSystemResource) + + class KClassTokenGenerator { + public: + using TokenBaseType = u16; + public: + static constexpr size_t BaseClassBits = 8; + static constexpr size_t FinalClassBits = (sizeof(TokenBaseType) * CHAR_BIT) - BaseClassBits; + /* One bit per base class. */ + static constexpr size_t NumBaseClasses = BaseClassBits; + /* Final classes are permutations of three bits. */ + static constexpr size_t NumFinalClasses = [] { + TokenBaseType index = 0; + for (size_t i = 0; i < FinalClassBits; i++) { + for (size_t j = i + 1; j < FinalClassBits; j++) { + for (size_t k = j + 1; k < FinalClassBits; k++) { + index++; + } + } + } + return index; + }(); + private: + template<TokenBaseType Index> + static constexpr inline TokenBaseType BaseClassToken = BIT(Index); + + template<TokenBaseType Index> + static constexpr inline TokenBaseType FinalClassToken = [] { + TokenBaseType index = 0; + for (size_t i = 0; i < FinalClassBits; i++) { + for (size_t j = i + 1; j < FinalClassBits; j++) { + for (size_t k = j + 1; k < FinalClassBits; k++) { + if ((index++) == Index) { + return ((1ul << i) | (1ul << j) | (1ul << k)) << BaseClassBits; + } + } + } + } + __builtin_unreachable(); + }(); + + template<typename T> + static constexpr inline TokenBaseType GetClassToken() { + static_assert(std::is_base_of<KAutoObject, T>::value); + if constexpr (std::is_same<T, KAutoObject>::value) { + static_assert(T::ObjectType == ObjectType::KAutoObject); + return 0; + } else if constexpr (!std::is_final<T>::value && !std::same_as<T, KSystemResource>) { + static_assert(ObjectType::BaseClassesStart <= T::ObjectType && T::ObjectType < ObjectType::BaseClassesEnd); + constexpr auto ClassIndex = static_cast<TokenBaseType>(T::ObjectType) - static_cast<TokenBaseType>(ObjectType::BaseClassesStart); + return BaseClassToken<ClassIndex> | GetClassToken<typename T::BaseClass>(); + } else if constexpr (ObjectType::FinalClassesStart <= T::ObjectType && T::ObjectType < ObjectType::FinalClassesEnd) { + constexpr auto ClassIndex = static_cast<TokenBaseType>(T::ObjectType) - static_cast<TokenBaseType>(ObjectType::FinalClassesStart); + return FinalClassToken<ClassIndex> | GetClassToken<typename T::BaseClass>(); + } else { + static_assert(!std::is_same<T, T>::value, "GetClassToken: Invalid Type"); + } + }; + public: + enum class ObjectType { + KAutoObject, + + BaseClassesStart, + + KSynchronizationObject = BaseClassesStart, + KReadableEvent, + + BaseClassesEnd, + + FinalClassesStart = BaseClassesEnd, + + KInterruptEvent = FinalClassesStart, + KDebug, + KThread, + KServerPort, + KServerSession, + KClientPort, + KClientSession, + KProcess, + KResourceLimit, + KLightSession, + KPort, + KSession, + KSharedMemory, + KEvent, + KLightClientSession, + KLightServerSession, + KTransferMemory, + KDeviceAddressSpace, + KSessionRequest, + KCodeMemory, + KIoPool, + KIoRegion, + + /* NOTE: What occupies these gaps? They can be inferred, but they don't make sense. */ + KAlpha, + KBeta, + + KSystemResource, + + FinalClassesLast, + + FinalClassesEnd = FinalClassesStart + NumFinalClasses, + }; + static_assert(ObjectType::FinalClassesLast <= ObjectType::FinalClassesEnd); + + template<typename T> + static constexpr inline TokenBaseType ClassToken = GetClassToken<T>(); + }; + + using ClassTokenType = KClassTokenGenerator::TokenBaseType; + + template<typename T> + static constexpr inline ClassTokenType ClassToken = KClassTokenGenerator::ClassToken<T>; + + namespace impl { + + consteval bool IsKClassTokenGeneratorForEachMacroValid() { + auto IsObjectTypeIncludedByMacro = [](KClassTokenGenerator::ObjectType object_type) -> bool { + #define CLASS_TOKEN_HANDLER(CLASSNAME) if (object_type == KClassTokenGenerator::ObjectType::CLASSNAME) { return true; } + FOR_EACH_K_CLASS_TOKEN_OBJECT_TYPE(CLASS_TOKEN_HANDLER) + #undef CLASS_TOKEN_HANDLER + return false; + }; + + if (!IsObjectTypeIncludedByMacro(KClassTokenGenerator::ObjectType::KAutoObject)) { + return false; + } + + for (auto base = util::ToUnderlying(KClassTokenGenerator::ObjectType::BaseClassesStart); base < util::ToUnderlying(KClassTokenGenerator::ObjectType::BaseClassesEnd); ++base) { + if (!IsObjectTypeIncludedByMacro(static_cast<KClassTokenGenerator::ObjectType>(base))) { + return false; + } + } + + for (auto fin = util::ToUnderlying(KClassTokenGenerator::ObjectType::FinalClassesStart); fin < util::ToUnderlying(KClassTokenGenerator::ObjectType::FinalClassesLast); ++fin) { + if (const auto o = static_cast<KClassTokenGenerator::ObjectType>(fin); !IsObjectTypeIncludedByMacro(o)) { + if (o != KClassTokenGenerator::ObjectType::KAlpha && o != KClassTokenGenerator::ObjectType::KBeta) { + return false; + } + } + } + + return true; + } + + static_assert(IsKClassTokenGeneratorForEachMacroValid()); + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_client_port.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_client_port.hpp new file mode 100644 index 00000000..daad415f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_client_port.hpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_synchronization_object.hpp> + +namespace ams::kern { + + class KPort; + class KSession; + class KClientSession; + class KLightSession; + class KLightClientSession; + + class KClientPort final : public KSynchronizationObject { + MESOSPHERE_AUTOOBJECT_TRAITS(KClientPort, KSynchronizationObject); + private: + util::Atomic<s32> m_num_sessions; + util::Atomic<s32> m_peak_sessions; + s32 m_max_sessions; + KPort *m_parent; + public: + constexpr explicit KClientPort(util::ConstantInitializeTag) : KSynchronizationObject(util::ConstantInitialize), m_num_sessions(0), m_peak_sessions(0), m_max_sessions(), m_parent() { /* ... */ } + + explicit KClientPort() { /* ... */ } + + void Initialize(KPort *parent, s32 max_sessions); + void OnSessionFinalized(); + void OnServerClosed(); + + constexpr const KPort *GetParent() const { return m_parent; } + + ALWAYS_INLINE s32 GetNumSessions() const { return m_num_sessions.Load(); } + ALWAYS_INLINE s32 GetPeakSessions() const { return m_peak_sessions.Load(); } + ALWAYS_INLINE s32 GetMaxSessions() const { return m_max_sessions; } + + bool IsLight() const; + bool IsServerClosed() const; + + /* Overridden virtual functions. */ + virtual void Destroy() override; + virtual bool IsSignaled() const override; + + Result CreateSession(KClientSession **out); + Result CreateLightSession(KLightClientSession **out); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_client_session.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_client_session.hpp new file mode 100644 index 00000000..e49ab8f0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_client_session.hpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_synchronization_object.hpp> + +namespace ams::kern { + + class KSession; + class KEvent; + + class KClientSession final : public KAutoObject { + MESOSPHERE_AUTOOBJECT_TRAITS(KClientSession, KAutoObject); + private: + KSession *m_parent; + public: + constexpr explicit KClientSession(util::ConstantInitializeTag) : KAutoObject(util::ConstantInitialize), m_parent() { /* ... */ } + explicit KClientSession() { /* ... */ } + + void Initialize(KSession *parent) { + /* Set member variables. */ + m_parent = parent; + } + + virtual void Destroy() override; + + constexpr KSession *GetParent() const { return m_parent; } + + Result SendSyncRequest(uintptr_t address, size_t size); + Result SendAsyncRequest(KEvent *event, uintptr_t address, size_t size); + + void OnServerClosed(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_code_memory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_code_memory.hpp new file mode 100644 index 00000000..daf47e3d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_code_memory.hpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_auto_object.hpp> +#include <mesosphere/kern_slab_helpers.hpp> + +namespace ams::kern { + + class KCodeMemory final : public KAutoObjectWithSlabHeapAndContainer<KCodeMemory, KAutoObjectWithList> { + MESOSPHERE_AUTOOBJECT_TRAITS(KCodeMemory, KAutoObject); + private: + util::TypedStorage<KPageGroup> m_page_group; + KProcess *m_owner; + KProcessAddress m_address; + KLightLock m_lock; + bool m_is_initialized; + bool m_is_owner_mapped; + bool m_is_mapped; + public: + explicit KCodeMemory() : m_owner(nullptr), m_address(Null<KProcessAddress>), m_is_initialized(false), m_is_owner_mapped(false), m_is_mapped(false) { + /* ... */ + } + + Result Initialize(KProcessAddress address, size_t size); + void Finalize(); + + Result Map(KProcessAddress address, size_t size); + Result Unmap(KProcessAddress address, size_t size); + Result MapToOwner(KProcessAddress address, size_t size, ams::svc::MemoryPermission perm); + Result UnmapFromOwner(KProcessAddress address, size_t size); + + bool IsInitialized() const { return m_is_initialized; } + static void PostDestroy(uintptr_t arg) { MESOSPHERE_UNUSED(arg); /* ... */ } + + KProcess *GetOwner() const { return m_owner; } + KProcessAddress GetSourceAddress() { return m_address; } + size_t GetSize() const { return m_is_initialized ? GetReference(m_page_group).GetNumPages() * PageSize : 0; } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_condition_variable.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_condition_variable.hpp new file mode 100644 index 00000000..de0187bf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_condition_variable.hpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_thread.hpp> +#include <mesosphere/kern_k_scheduler.hpp> + +namespace ams::kern { + + class KConditionVariable { + public: + using ThreadTree = typename KThread::ConditionVariableThreadTreeType; + private: + ThreadTree m_tree; + public: + constexpr KConditionVariable() = default; + + /* Arbitration. */ + static Result SignalToAddress(KProcessAddress addr); + static Result WaitForAddress(ams::svc::Handle handle, KProcessAddress addr, u32 value); + + /* Condition variable. */ + void Signal(uintptr_t cv_key, s32 count); + Result Wait(KProcessAddress addr, uintptr_t key, u32 value, s64 timeout); + private: + void SignalImpl(KThread *thread); + }; + + ALWAYS_INLINE void BeforeUpdatePriority(KConditionVariable::ThreadTree *tree, KThread *thread) { + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + tree->erase(tree->iterator_to(*thread)); + } + + ALWAYS_INLINE void AfterUpdatePriority(KConditionVariable::ThreadTree *tree, KThread *thread) { + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + tree->insert(*thread); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_current_context.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_current_context.hpp new file mode 100644 index 00000000..a06cda7a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_current_context.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_select_cpu.hpp> + +namespace ams::kern { + + class KThread; + class KProcess; + class KScheduler; + + ALWAYS_INLINE KThread *GetCurrentThreadPointer() { + return reinterpret_cast<KThread *>(cpu::GetCurrentThreadPointerValue()); + } + + ALWAYS_INLINE KThread &GetCurrentThread() { + return *GetCurrentThreadPointer(); + } + + ALWAYS_INLINE void SetCurrentThread(KThread *new_thread) { + cpu::SetCurrentThreadPointerValue(reinterpret_cast<uintptr_t>(new_thread)); + } + + ALWAYS_INLINE KProcess *GetCurrentProcessPointer(); + ALWAYS_INLINE KProcess &GetCurrentProcess(); + + ALWAYS_INLINE s32 GetCurrentCoreId(); + + ALWAYS_INLINE KScheduler &GetCurrentScheduler(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_debug_base.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_debug_base.hpp new file mode 100644 index 00000000..6978b915 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_debug_base.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_synchronization_object.hpp> +#include <mesosphere/kern_k_process.hpp> +#include <mesosphere/kern_k_event_info.hpp> +#include <mesosphere/kern_k_light_lock.hpp> + +namespace ams::kern { + + class KDebugBase : public KSynchronizationObject { + protected: + using DebugEventList = util::IntrusiveListBaseTraits<KEventInfo>::ListType; + private: + DebugEventList m_event_info_list; + u32 m_continue_flags; + KSharedAutoObject<KProcess> m_process_holder; + KLightLock m_lock; + KProcess::State m_old_process_state; + bool m_is_attached; + bool m_is_force_debug_prod; + public: + explicit KDebugBase() { /* ... */ } + protected: + bool Is64Bit() const; + public: + void Initialize(); + void Finalize(); + + Result Attach(KProcess *process); + Result BreakProcess(); + Result TerminateProcess(); + + Result ContinueDebug(const u32 flags, const u64 *thread_ids, size_t num_thread_ids); + + Result QueryMemoryInfo(ams::svc::MemoryInfo *out_memory_info, ams::svc::PageInfo *out_page_info, KProcessAddress address); + Result ReadMemory(KProcessAddress buffer, KProcessAddress address, size_t size); + Result WriteMemory(KProcessAddress buffer, KProcessAddress address, size_t size); + + Result GetThreadContext(ams::svc::ThreadContext *out, u64 thread_id, u32 context_flags); + Result SetThreadContext(const ams::svc::ThreadContext &ctx, u64 thread_id, u32 context_flags); + + Result GetRunningThreadInfo(ams::svc::LastThreadContext *out_context, u64 *out_thread_id); + + Result GetDebugEventInfo(ams::svc::lp64::DebugEventInfo *out); + Result GetDebugEventInfo(ams::svc::ilp32::DebugEventInfo *out); + + ALWAYS_INLINE bool IsAttached() const { + return m_is_attached; + } + + ALWAYS_INLINE bool IsForceDebugProd() const { + return m_is_force_debug_prod; + } + + ALWAYS_INLINE bool OpenProcess() { + return m_process_holder.Open(); + } + + ALWAYS_INLINE void CloseProcess() { + return m_process_holder.Close(); + } + + ALWAYS_INLINE KProcess *GetProcessUnsafe() const { + return m_process_holder.Get(); + } + private: + void PushDebugEvent(ams::svc::DebugEvent event, const uintptr_t *params, size_t num_params); + void EnqueueDebugEventInfo(KEventInfo *info); + + template<typename T> requires (std::same_as<T, ams::svc::lp64::DebugEventInfo> || std::same_as<T, ams::svc::ilp32::DebugEventInfo>) + Result GetDebugEventInfoImpl(T *out); + public: + virtual bool IsSignaled() const override; + private: + /* NOTE: This is public/virtual override in Nintendo's kernel. */ + void OnFinalizeSynchronizationObject(); + private: + static Result ProcessDebugEvent(ams::svc::DebugEvent event, const uintptr_t *params, size_t num_params); + public: + static Result OnDebugEvent(ams::svc::DebugEvent event, const uintptr_t *params, size_t num_params); + static Result OnExitProcess(KProcess *process); + static Result OnTerminateProcess(KProcess *process); + static Result OnExitThread(KThread *thread); + static KEventInfo *CreateDebugEvent(ams::svc::DebugEvent event, u64 thread_id, const uintptr_t *params, size_t num_params); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_device_address_space.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_device_address_space.hpp new file mode 100644 index 00000000..57766e6a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_device_address_space.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_auto_object.hpp> +#include <mesosphere/kern_slab_helpers.hpp> +#include <mesosphere/kern_select_device_page_table.hpp> + +namespace ams::kern { + + class KDeviceAddressSpace final : public KAutoObjectWithSlabHeapAndContainer<KDeviceAddressSpace, KAutoObjectWithList> { + MESOSPHERE_AUTOOBJECT_TRAITS(KDeviceAddressSpace, KAutoObject); + private: + KLightLock m_lock; + KDevicePageTable m_table; + u64 m_space_address; + u64 m_space_size; + bool m_is_initialized; + public: + explicit KDeviceAddressSpace() : m_is_initialized(false) { /* ... */ } + + Result Initialize(u64 address, u64 size); + void Finalize(); + + bool IsInitialized() const { return m_is_initialized; } + static void PostDestroy(uintptr_t arg) { MESOSPHERE_UNUSED(arg); /* ... */ } + + Result Attach(ams::svc::DeviceName device_name); + Result Detach(ams::svc::DeviceName device_name); + + Result MapByForce(KProcessPageTable *page_table, KProcessAddress process_address, size_t size, u64 device_address, u32 option) { + R_RETURN(this->Map(page_table, process_address, size, device_address, option, false)); + } + + Result MapAligned(KProcessPageTable *page_table, KProcessAddress process_address, size_t size, u64 device_address, u32 option) { + R_RETURN(this->Map(page_table, process_address, size, device_address, option, true)); + } + + Result Unmap(KProcessPageTable *page_table, KProcessAddress process_address, size_t size, u64 device_address); + private: + Result Map(KProcessPageTable *page_table, KProcessAddress process_address, size_t size, u64 device_address, u32 option, bool is_aligned); + public: + static void Initialize(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_dpc_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_dpc_manager.hpp new file mode 100644 index 00000000..97bae668 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_dpc_manager.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_select_cpu.hpp> + +namespace ams::kern { + + class KDpcManager { + private: + static constexpr s32 DpcManagerNormalThreadPriority = 59; + static constexpr s32 DpcManagerPreemptionThreadPriority = 63; + + static_assert(ams::svc::HighestThreadPriority <= DpcManagerNormalThreadPriority && DpcManagerNormalThreadPriority <= ams::svc::LowestThreadPriority); + static_assert(ams::svc::HighestThreadPriority <= DpcManagerPreemptionThreadPriority && DpcManagerPreemptionThreadPriority <= ams::svc::LowestThreadPriority); + private: + static NOINLINE void Initialize(s32 core_id, s32 priority); + public: + static void Initialize() { + const s32 core_id = GetCurrentCoreId(); + if (core_id == static_cast<s32>(cpu::NumCores) - 1) { + Initialize(core_id, DpcManagerPreemptionThreadPriority); + } else { + Initialize(core_id, DpcManagerNormalThreadPriority); + } + } + + static NOINLINE void HandleDpc(); + static void Sync(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_dump_object.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_dump_object.hpp new file mode 100644 index 00000000..8627aad8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_dump_object.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_select_cpu.hpp> + +namespace ams::kern::KDumpObject { + + void DumpThread(); + void DumpThread(u64 thread_id); + + void DumpThreadCallStack(); + void DumpThreadCallStack(u64 thread_id); + + void DumpKernelObject(); + + void DumpHandle(); + void DumpHandle(u64 process_id); + + void DumpKernelMemory(); + void DumpMemory(); + void DumpMemory(u64 process_id); + + void DumpKernelPageTable(); + void DumpPageTable(); + void DumpPageTable(u64 process_id); + + void DumpKernelCpuUtilization(); + void DumpCpuUtilization(); + void DumpCpuUtilization(u64 process_id); + + void DumpProcess(); + void DumpProcess(u64 process_id); + + void DumpPort(); + void DumpPort(u64 process_id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_dynamic_page_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_dynamic_page_manager.hpp new file mode 100644 index 00000000..db12684c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_dynamic_page_manager.hpp @@ -0,0 +1,149 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_spin_lock.hpp> +#include <mesosphere/kern_k_slab_heap.hpp> +#include <mesosphere/kern_k_page_group.hpp> +#include <mesosphere/kern_k_memory_block.hpp> +#include <mesosphere/kern_k_page_bitmap.hpp> +#include <mesosphere/kern_select_interrupt_manager.hpp> + +namespace ams::kern { + + class KDynamicPageManager { + public: + class PageBuffer { + private: + u8 m_buffer[PageSize]; + }; + static_assert(sizeof(PageBuffer) == PageSize); + private: + KSpinLock m_lock; + KPageBitmap m_page_bitmap; + size_t m_used; + size_t m_peak; + size_t m_count; + KVirtualAddress m_address; + KVirtualAddress m_aligned_address; + size_t m_size; + public: + KDynamicPageManager() : m_lock(), m_page_bitmap(), m_used(), m_peak(), m_count(), m_address(Null<KVirtualAddress>), m_aligned_address(Null<KVirtualAddress>), m_size() { /* ... */ } + + Result Initialize(KVirtualAddress memory, size_t size, size_t align) { + /* We need to have positive size. */ + R_UNLESS(size > 0, svc::ResultOutOfMemory()); + + /* Set addresses. */ + m_address = memory; + m_aligned_address = util::AlignDown(GetInteger(memory), align); + + /* Calculate extents. */ + const size_t managed_size = m_address + size - m_aligned_address; + const size_t overhead_size = util::AlignUp(KPageBitmap::CalculateManagementOverheadSize(managed_size / sizeof(PageBuffer)), sizeof(PageBuffer)); + R_UNLESS(overhead_size < size, svc::ResultOutOfMemory()); + + /* Set tracking fields. */ + m_size = util::AlignDown(size - overhead_size, sizeof(PageBuffer)); + m_count = m_size / sizeof(PageBuffer); + + /* Clear the management region. */ + u64 *management_ptr = GetPointer<u64>(m_address + size - overhead_size); + std::memset(management_ptr, 0, overhead_size); + + /* Initialize the bitmap. */ + const size_t allocatable_region_size = (GetInteger(m_address) + size - overhead_size) - GetInteger(m_aligned_address); + MESOSPHERE_ABORT_UNLESS(allocatable_region_size >= sizeof(PageBuffer)); + + m_page_bitmap.Initialize(management_ptr, allocatable_region_size / sizeof(PageBuffer)); + + /* Free the pages to the bitmap. */ + for (size_t i = 0; i < m_count; i++) { + /* Ensure the freed page is all-zero. */ + cpu::ClearPageToZero(GetPointer<PageBuffer>(m_address) + i); + + /* Set the bit for the free page. */ + m_page_bitmap.SetBit((GetInteger(m_address) + (i * sizeof(PageBuffer)) - GetInteger(m_aligned_address)) / sizeof(PageBuffer)); + } + + R_SUCCEED(); + } + + constexpr KVirtualAddress GetAddress() const { return m_address; } + constexpr size_t GetSize() const { return m_size; } + constexpr size_t GetUsed() const { return m_used; } + constexpr size_t GetPeak() const { return m_peak; } + constexpr size_t GetCount() const { return m_count; } + + PageBuffer *Allocate() { + /* Take the lock. */ + KScopedInterruptDisable di; + KScopedSpinLock lk(m_lock); + + /* Find a random free block. */ + ssize_t soffset = m_page_bitmap.FindFreeBlock(true); + if (AMS_UNLIKELY(soffset < 0)) { + return nullptr; + } + + const size_t offset = static_cast<size_t>(soffset); + + /* Update our tracking. */ + m_page_bitmap.ClearBit(offset); + m_peak = std::max(m_peak, (++m_used)); + + return GetPointer<PageBuffer>(m_aligned_address) + offset; + } + + PageBuffer *Allocate(size_t count) { + /* Take the lock. */ + KScopedInterruptDisable di; + KScopedSpinLock lk(m_lock); + + /* Find a random free block. */ + ssize_t soffset = m_page_bitmap.FindFreeRange(count); + if (AMS_UNLIKELY(soffset < 0)) { + return nullptr; + } + + const size_t offset = static_cast<size_t>(soffset); + + /* Update our tracking. */ + m_page_bitmap.ClearRange(offset, count); + m_used += count; + m_peak = std::max(m_peak, m_used); + + return GetPointer<PageBuffer>(m_aligned_address) + offset; + } + + void Free(PageBuffer *pb) { + /* Ensure all pages in the heap are zero. */ + cpu::ClearPageToZero(pb); + + /* Take the lock. */ + KScopedInterruptDisable di; + KScopedSpinLock lk(m_lock); + + /* Set the bit for the free page. */ + size_t offset = (reinterpret_cast<uintptr_t>(pb) - GetInteger(m_aligned_address)) / sizeof(PageBuffer); + m_page_bitmap.SetBit(offset); + + /* Decrement our used count. */ + --m_used; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_dynamic_resource_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_dynamic_resource_manager.hpp new file mode 100644 index 00000000..b829a0c8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_dynamic_resource_manager.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_dynamic_slab_heap.hpp> + +namespace ams::kern { + + template<typename T, bool ClearNode = false> + class KDynamicResourceManager { + NON_COPYABLE(KDynamicResourceManager); + NON_MOVEABLE(KDynamicResourceManager); + public: + using DynamicSlabType = KDynamicSlabHeap<T, ClearNode>; + private: + KDynamicPageManager *m_page_allocator{}; + DynamicSlabType *m_slab_heap{}; + public: + constexpr KDynamicResourceManager() = default; + + constexpr ALWAYS_INLINE KVirtualAddress GetAddress() const { return m_slab_heap->GetAddress(); } + constexpr ALWAYS_INLINE size_t GetSize() const { return m_slab_heap->GetSize(); } + constexpr ALWAYS_INLINE size_t GetUsed() const { return m_slab_heap->GetUsed(); } + constexpr ALWAYS_INLINE size_t GetPeak() const { return m_slab_heap->GetPeak(); } + constexpr ALWAYS_INLINE size_t GetCount() const { return m_slab_heap->GetCount(); } + + ALWAYS_INLINE void Initialize(KDynamicPageManager *page_allocator, DynamicSlabType *slab_heap) { + m_page_allocator = page_allocator; + m_slab_heap = slab_heap; + } + + T *Allocate() const { + return m_slab_heap->Allocate(m_page_allocator); + } + + ALWAYS_INLINE void Free(T *t) const { + m_slab_heap->Free(t); + } + }; + + class KBlockInfoManager : public KDynamicResourceManager<KBlockInfo>{}; + class KMemoryBlockSlabManager : public KDynamicResourceManager<KMemoryBlock>{}; + + using KBlockInfoSlabHeap = typename KBlockInfoManager::DynamicSlabType; + using KMemoryBlockSlabHeap = typename KMemoryBlockSlabManager::DynamicSlabType; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_dynamic_slab_heap.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_dynamic_slab_heap.hpp new file mode 100644 index 00000000..3a462fa5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_dynamic_slab_heap.hpp @@ -0,0 +1,120 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_slab_heap.hpp> +#include <mesosphere/kern_k_page_group.hpp> +#include <mesosphere/kern_k_memory_block.hpp> +#include <mesosphere/kern_k_dynamic_page_manager.hpp> + +namespace ams::kern { + + template<typename T, bool ClearNode = false> + class KDynamicSlabHeap : protected impl::KSlabHeapImpl { + NON_COPYABLE(KDynamicSlabHeap); + NON_MOVEABLE(KDynamicSlabHeap); + private: + using PageBuffer = KDynamicPageManager::PageBuffer; + private: + util::Atomic<size_t> m_used{0}; + util::Atomic<size_t> m_peak{0}; + util::Atomic<size_t> m_count{0}; + KVirtualAddress m_address{Null<KVirtualAddress>}; + size_t m_size{}; + public: + constexpr KDynamicSlabHeap() = default; + + constexpr ALWAYS_INLINE KVirtualAddress GetAddress() const { return m_address; } + constexpr ALWAYS_INLINE size_t GetSize() const { return m_size; } + constexpr ALWAYS_INLINE size_t GetUsed() const { return m_used.Load(); } + constexpr ALWAYS_INLINE size_t GetPeak() const { return m_peak.Load(); } + constexpr ALWAYS_INLINE size_t GetCount() const { return m_count.Load(); } + + constexpr ALWAYS_INLINE bool IsInRange(KVirtualAddress addr) const { + return this->GetAddress() <= addr && addr <= this->GetAddress() + this->GetSize() - 1; + } + + ALWAYS_INLINE void Initialize(KDynamicPageManager *page_allocator, size_t num_objects) { + MESOSPHERE_ASSERT(page_allocator != nullptr); + + /* Initialize members. */ + m_address = page_allocator->GetAddress(); + m_size = page_allocator->GetSize(); + + /* Initialize the base allocator. */ + KSlabHeapImpl::Initialize(); + + /* Allocate until we have the correct number of objects. */ + while (m_count.Load() < num_objects) { + auto *allocated = reinterpret_cast<T *>(page_allocator->Allocate()); + MESOSPHERE_ABORT_UNLESS(allocated != nullptr); + + for (size_t i = 0; i < sizeof(PageBuffer) / sizeof(T); i++) { + KSlabHeapImpl::Free(allocated + i); + } + + m_count += sizeof(PageBuffer) / sizeof(T); + } + } + + ALWAYS_INLINE T *Allocate(KDynamicPageManager *page_allocator) { + T *allocated = static_cast<T *>(KSlabHeapImpl::Allocate()); + + /* If we successfully allocated and we should clear the node, do so. */ + if constexpr (ClearNode) { + if (AMS_LIKELY(allocated != nullptr)) { + reinterpret_cast<KSlabHeapImpl::Node *>(allocated)->next = nullptr; + } + } + + /* If we fail to allocate, try to get a new page from our next allocator. */ + if (AMS_UNLIKELY(allocated == nullptr) ) { + if (page_allocator != nullptr) { + allocated = reinterpret_cast<T *>(page_allocator->Allocate()); + if (allocated != nullptr) { + /* If we succeeded in getting a page, free the rest to our slab. */ + for (size_t i = 1; i < sizeof(PageBuffer) / sizeof(T); i++) { + KSlabHeapImpl::Free(allocated + i); + } + m_count += sizeof(PageBuffer) / sizeof(T); + } + } + } + + if (AMS_LIKELY(allocated != nullptr)) { + /* Construct the object. */ + std::construct_at(allocated); + + /* Update our tracking. */ + const size_t used = ++m_used; + size_t peak = m_peak.Load(); + while (peak < used) { + if (m_peak.CompareExchangeWeak<std::memory_order_relaxed>(peak, used)) { + break; + } + } + } + + return allocated; + } + + ALWAYS_INLINE void Free(T *t) { + KSlabHeapImpl::Free(t); + --m_used; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_event.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_event.hpp new file mode 100644 index 00000000..dd4a950d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_event.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_auto_object.hpp> +#include <mesosphere/kern_slab_helpers.hpp> +#include <mesosphere/kern_k_readable_event.hpp> + +namespace ams::kern { + + class KEvent final : public KAutoObjectWithSlabHeapAndContainer<KEvent, KAutoObjectWithList, true> { + MESOSPHERE_AUTOOBJECT_TRAITS(KEvent, KAutoObject); + private: + KReadableEvent m_readable_event; + KProcess *m_owner; + bool m_initialized; + bool m_readable_event_destroyed; + public: + constexpr explicit KEvent(util::ConstantInitializeTag) + : KAutoObjectWithSlabHeapAndContainer<KEvent, KAutoObjectWithList, true>(util::ConstantInitialize), + m_readable_event(util::ConstantInitialize), m_owner(), m_initialized(), m_readable_event_destroyed() + { + /* ... */ + } + + explicit KEvent() : m_readable_event(), m_owner(), m_initialized(), m_readable_event_destroyed() { /* ... */ } + + void Initialize(); + void Finalize(); + + bool IsInitialized() const { return m_initialized; } + uintptr_t GetPostDestroyArgument() const { return reinterpret_cast<uintptr_t>(m_owner); } + + static void PostDestroy(uintptr_t arg); + + KProcess *GetOwner() const { return m_owner; } + + KReadableEvent &GetReadableEvent() { return m_readable_event; } + + Result Signal(); + Result Clear(); + + ALWAYS_INLINE void OnReadableEventDestroyed() { m_readable_event_destroyed = true; } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_event_info.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_event_info.hpp new file mode 100644 index 00000000..98dbd576 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_event_info.hpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_slab_helpers.hpp> + +namespace ams::kern { + + class KEventInfo : public KSlabAllocated<KEventInfo>, public util::IntrusiveListBaseNode<KEventInfo> { + public: + struct InfoCreateThread { + u32 thread_id; + uintptr_t tls_address; + }; + + struct InfoExitProcess { + ams::svc::ProcessExitReason reason; + }; + + struct InfoExitThread { + ams::svc::ThreadExitReason reason; + }; + + struct InfoException { + ams::svc::DebugException exception_type; + s32 exception_data_count; + uintptr_t exception_address; + uintptr_t exception_data[std::max<size_t>(4, cpu::NumCores)]; + }; + + struct InfoSystemCall { + s64 tick; + s32 id; + }; + public: + ams::svc::DebugEvent event; + u32 thread_id; + u32 flags; + bool is_attached; + bool continue_flag; + bool ignore_continue; + bool close_once; + union { + InfoCreateThread create_thread; + InfoExitProcess exit_process; + InfoExitThread exit_thread; + InfoException exception; + InfoSystemCall system_call; + } info; + KThread *debug_thread; + public: + explicit KEventInfo() : is_attached(), continue_flag(), ignore_continue() { /* ... */ } + ~KEventInfo() { /* ... */ } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_exception_context.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_exception_context.hpp new file mode 100644 index 00000000..bee3d6f0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_exception_context.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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#ifdef ATMOSPHERE_ARCH_ARM64 + #include <mesosphere/arch/arm64/kern_k_exception_context.hpp> + + namespace ams::kern { + using ams::kern::arch::arm64::KExceptionContext; + } +#else + #error "Unknown architecture for KExceptionContext" +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_handle_table.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_handle_table.hpp new file mode 100644 index 00000000..440616aa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_handle_table.hpp @@ -0,0 +1,339 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_auto_object.hpp> +#include <mesosphere/kern_k_spin_lock.hpp> +#include <mesosphere/kern_k_thread.hpp> +#include <mesosphere/kern_k_interrupt_event.hpp> + +namespace ams::kern { + + constexpr ALWAYS_INLINE util::BitPack32 GetHandleBitPack(ams::svc::Handle handle) { + return util::BitPack32{handle}; + } + + class KProcess; + class KThread; + + class KHandleTable { + NON_COPYABLE(KHandleTable); + NON_MOVEABLE(KHandleTable); + public: + static constexpr size_t MaxTableSize = 1024; + private: + using HandleRawValue = util::BitPack32::Field<0, BITSIZEOF(u32), u32>; + using HandleEncoded = util::BitPack32::Field<0, BITSIZEOF(ams::svc::Handle), ams::svc::Handle>; + + using HandleIndex = util::BitPack32::Field<0, 15, u16>; + using HandleLinearId = util::BitPack32::Field<HandleIndex::Next, 15, u16>; + using HandleReserved = util::BitPack32::Field<HandleLinearId::Next, 2, u32>; + + static constexpr u16 MinLinearId = 1; + static constexpr u16 MaxLinearId = util::BitPack32{std::numeric_limits<u32>::max()}.Get<HandleLinearId>(); + + static constexpr ALWAYS_INLINE ams::svc::Handle EncodeHandle(u16 index, u16 linear_id) { + util::BitPack32 pack = {0}; + pack.Set<HandleIndex>(index); + pack.Set<HandleLinearId>(linear_id); + pack.Set<HandleReserved>(0); + return pack.Get<HandleEncoded>(); + } + + union EntryInfo { + u16 linear_id; + s16 next_free_index; + + constexpr ALWAYS_INLINE u16 GetLinearId() const { return linear_id; } + constexpr ALWAYS_INLINE s32 GetNextFreeIndex() const { return next_free_index; } + }; + private: + EntryInfo m_entry_infos[MaxTableSize]; + KAutoObject *m_objects[MaxTableSize]; + mutable KSpinLock m_lock; + s32 m_free_head_index; + u16 m_table_size; + u16 m_max_count; + u16 m_next_linear_id; + u16 m_count; + public: + constexpr explicit KHandleTable(util::ConstantInitializeTag) : m_entry_infos(), m_objects(), m_lock(), m_free_head_index(-1), m_table_size(), m_max_count(), m_next_linear_id(), m_count() { /* ... */ } + + explicit KHandleTable() : m_lock(), m_free_head_index(-1), m_table_size(), m_max_count(), m_next_linear_id(), m_count() { MESOSPHERE_ASSERT_THIS(); } + + MESOSPHERE_NOINLINE_IF_DEBUG Result Initialize(s32 size) { + MESOSPHERE_ASSERT_THIS(); + + /* Check that the table size is valid. */ + R_UNLESS(size <= static_cast<s32>(MaxTableSize), svc::ResultOutOfMemory()); + + /* Lock. */ + KScopedDisableDispatch dd; + KScopedSpinLock lk(m_lock); + + /* Initialize all fields. */ + m_max_count = 0; + m_table_size = (size <= 0) ? MaxTableSize : size; + m_next_linear_id = MinLinearId; + m_count = 0; + m_free_head_index = -1; + + /* Free all entries. */ + for (s32 i = 0; i < static_cast<s32>(m_table_size); ++i) { + m_objects[i] = nullptr; + m_entry_infos[i].next_free_index = i - 1; + m_free_head_index = i; + } + + R_SUCCEED(); + } + + constexpr ALWAYS_INLINE size_t GetTableSize() const { return m_table_size; } + constexpr ALWAYS_INLINE size_t GetCount() const { return m_count; } + constexpr ALWAYS_INLINE size_t GetMaxCount() const { return m_max_count; } + + MESOSPHERE_NOINLINE_IF_DEBUG Result Finalize(); + MESOSPHERE_NOINLINE_IF_DEBUG bool Remove(ams::svc::Handle handle); + + template<typename T = KAutoObject> + ALWAYS_INLINE KScopedAutoObject<T> GetObjectWithoutPseudoHandle(ams::svc::Handle handle) const { + /* Lock and look up in table. */ + KScopedDisableDispatch dd; + KScopedSpinLock lk(m_lock); + + if constexpr (std::is_same<T, KAutoObject>::value) { + return this->GetObjectImpl(handle); + } else { + if (auto *obj = this->GetObjectImpl(handle); AMS_LIKELY(obj != nullptr)) { + return obj->DynamicCast<T*>(); + } else { + return nullptr; + } + } + } + + template<typename T = KAutoObject> + ALWAYS_INLINE KScopedAutoObject<T> GetObject(ams::svc::Handle handle) const { + MESOSPHERE_ASSERT_THIS(); + + /* Handle pseudo-handles. */ + if constexpr (std::derived_from<KProcess, T>) { + if (handle == ams::svc::PseudoHandle::CurrentProcess) { + auto * const cur_process = GetCurrentProcessPointer(); + AMS_ASSUME(cur_process != nullptr); + return cur_process; + } + } else if constexpr (std::derived_from<KThread, T>) { + if (handle == ams::svc::PseudoHandle::CurrentThread) { + auto * const cur_thread = GetCurrentThreadPointer(); + AMS_ASSUME(cur_thread != nullptr); + return cur_thread; + } + } + + return this->template GetObjectWithoutPseudoHandle<T>(handle); + } + + KScopedAutoObject<KAutoObject> GetObjectForIpcWithoutPseudoHandle(ams::svc::Handle handle) const { + /* Lock and look up in table. */ + KScopedDisableDispatch dd; + KScopedSpinLock lk(m_lock); + + KAutoObject *obj = this->GetObjectImpl(handle); + if (AMS_LIKELY(obj != nullptr)) { + if (AMS_UNLIKELY(obj->DynamicCast<KInterruptEvent *>() != nullptr)) { + return nullptr; + } + } + + return obj; + } + + ALWAYS_INLINE KScopedAutoObject<KAutoObject> GetObjectForIpc(ams::svc::Handle handle, KThread *cur_thread) const { + /* Handle pseudo-handles. */ + AMS_ASSUME(cur_thread != nullptr); + if (handle == ams::svc::PseudoHandle::CurrentProcess) { + auto * const cur_process = static_cast<KAutoObject *>(static_cast<void *>(cur_thread->GetOwnerProcess())); + AMS_ASSUME(cur_process != nullptr); + return cur_process; + } + if (handle == ams::svc::PseudoHandle::CurrentThread) { + return static_cast<KAutoObject *>(cur_thread); + } + + return GetObjectForIpcWithoutPseudoHandle(handle); + } + + ALWAYS_INLINE KScopedAutoObject<KAutoObject> GetObjectByIndex(ams::svc::Handle *out_handle, size_t index) const { + MESOSPHERE_ASSERT_THIS(); + KScopedDisableDispatch dd; + KScopedSpinLock lk(m_lock); + + return this->GetObjectByIndexImpl(out_handle, index); + } + + MESOSPHERE_NOINLINE_IF_DEBUG Result Reserve(ams::svc::Handle *out_handle); + MESOSPHERE_NOINLINE_IF_DEBUG void Unreserve(ams::svc::Handle handle); + + MESOSPHERE_NOINLINE_IF_DEBUG Result Add(ams::svc::Handle *out_handle, KAutoObject *obj); + MESOSPHERE_NOINLINE_IF_DEBUG void Register(ams::svc::Handle handle, KAutoObject *obj); + + template<typename T> + ALWAYS_INLINE bool GetMultipleObjects(T **out, const ams::svc::Handle *handles, size_t num_handles) const { + /* Try to convert and open all the handles. */ + size_t num_opened; + { + /* Lock the table. */ + KScopedDisableDispatch dd; + KScopedSpinLock lk(m_lock); + for (num_opened = 0; num_opened < num_handles; num_opened++) { + /* Get the current handle. */ + const auto cur_handle = handles[num_opened]; + + /* Get the object for the current handle. */ + KAutoObject *cur_object = this->GetObjectImpl(cur_handle); + if (AMS_UNLIKELY(cur_object == nullptr)) { + break; + } + + /* Cast the current object to the desired type. */ + T *cur_t = cur_object->DynamicCast<T*>(); + if (AMS_UNLIKELY(cur_t == nullptr)) { + break; + } + + /* Open a reference to the current object. */ + cur_t->Open(); + out[num_opened] = cur_t; + } + } + + /* If we converted every object, succeed. */ + if (AMS_LIKELY(num_opened == num_handles)) { + return true; + } + + /* If we didn't convert entry object, close the ones we opened. */ + for (size_t i = 0; i < num_opened; i++) { + out[i]->Close(); + } + + return false; + } + private: + + constexpr ALWAYS_INLINE s32 AllocateEntry() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(m_count < m_table_size); + + const auto index = m_free_head_index; + + m_free_head_index = m_entry_infos[index].GetNextFreeIndex(); + + m_max_count = std::max(m_max_count, ++m_count); + + return index; + } + + constexpr ALWAYS_INLINE void FreeEntry(s32 index) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(m_count > 0); + + m_objects[index] = nullptr; + m_entry_infos[index].next_free_index = m_free_head_index; + + m_free_head_index = index; + + --m_count; + } + + constexpr ALWAYS_INLINE u16 AllocateLinearId() { + const u16 id = m_next_linear_id++; + if (m_next_linear_id > MaxLinearId) { + m_next_linear_id = MinLinearId; + } + return id; + } + + constexpr ALWAYS_INLINE bool IsValidHandle(ams::svc::Handle handle) const { + MESOSPHERE_ASSERT_THIS(); + + /* Unpack the handle. */ + const auto handle_pack = GetHandleBitPack(handle); + const auto raw_value = handle_pack.Get<HandleRawValue>(); + const auto index = handle_pack.Get<HandleIndex>(); + const auto linear_id = handle_pack.Get<HandleLinearId>(); + const auto reserved = handle_pack.Get<HandleReserved>(); + MESOSPHERE_ASSERT(reserved == 0); + MESOSPHERE_UNUSED(reserved); + + /* Validate our indexing information. */ + if (AMS_UNLIKELY(raw_value == 0)) { + return false; + } + if (AMS_UNLIKELY(linear_id == 0)) { + return false; + } + if (AMS_UNLIKELY(index >= m_table_size)) { + return false; + } + + /* Check that there's an object, and our serial id is correct. */ + if (AMS_UNLIKELY(m_objects[index] == nullptr)) { + return false; + } + if (AMS_UNLIKELY(m_entry_infos[index].GetLinearId() != linear_id)) { + return false; + } + + return true; + } + + constexpr MESOSPHERE_NOINLINE_IF_DEBUG KAutoObject *GetObjectImpl(ams::svc::Handle handle) const { + MESOSPHERE_ASSERT_THIS(); + + /* Handles must not have reserved bits set. */ + const auto handle_pack = GetHandleBitPack(handle); + if (AMS_UNLIKELY(handle_pack.Get<HandleReserved>() != 0)) { + return nullptr; + } + + if (AMS_LIKELY(this->IsValidHandle(handle))) { + return m_objects[handle_pack.Get<HandleIndex>()]; + } else { + return nullptr; + } + } + + constexpr ALWAYS_INLINE KAutoObject *GetObjectByIndexImpl(ams::svc::Handle *out_handle, size_t index) const { + MESOSPHERE_ASSERT_THIS(); + + /* Index must be in bounds. */ + if (AMS_UNLIKELY(index >= m_table_size)) { + return nullptr; + } + + /* Ensure entry has an object. */ + if (KAutoObject *obj = m_objects[index]; obj != nullptr) { + *out_handle = EncodeHandle(index, m_entry_infos[index].GetLinearId()); + return obj; + } else { + return nullptr; + } + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_hardware_timer_base.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_hardware_timer_base.hpp new file mode 100644 index 00000000..ab2ad1c1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_hardware_timer_base.hpp @@ -0,0 +1,95 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_k_spin_lock.hpp> +#include <mesosphere/kern_k_timer_task.hpp> +#include <mesosphere/kern_select_interrupt_manager.hpp> + +namespace ams::kern { + + class KHardwareTimerBase { + private: + using TimerTaskTree = util::IntrusiveRedBlackTreeBaseTraits<KTimerTask>::TreeType<KTimerTask>; + private: + KSpinLock m_lock; + TimerTaskTree m_task_tree; + KTimerTask *m_next_task; + public: + constexpr ALWAYS_INLINE KHardwareTimerBase() : m_lock(), m_task_tree(), m_next_task(nullptr) { /* ... */ } + private: + ALWAYS_INLINE void RemoveTaskFromTree(KTimerTask *task) { + /* Erase from the tree. */ + auto it = m_task_tree.erase(m_task_tree.iterator_to(*task)); + + /* Clear the task's scheduled time. */ + task->SetTime(0); + + /* Update our next task if relevant. */ + if (m_next_task == task) { + m_next_task = (it != m_task_tree.end()) ? std::addressof(*it) : nullptr; + } + } + public: + NOINLINE void CancelTask(KTimerTask *task) { + KScopedDisableDispatch dd; + KScopedSpinLock lk(m_lock); + + if (const s64 task_time = task->GetTime(); task_time > 0) { + this->RemoveTaskFromTree(task); + } + } + protected: + ALWAYS_INLINE KSpinLock &GetLock() { return m_lock; } + + ALWAYS_INLINE s64 DoInterruptTaskImpl(s64 cur_time) { + /* We want to handle all tasks, returning the next time that a task is scheduled. */ + while (true) { + /* Get the next task. If there isn't one, return 0. */ + KTimerTask *task = m_next_task; + if (task == nullptr) { + return 0; + } + + /* If the task needs to be done in the future, do it in the future and not now. */ + if (const s64 task_time = task->GetTime(); task_time > cur_time) { + return task_time; + } + + /* Remove the task from the tree of tasks, and update our next task. */ + this->RemoveTaskFromTree(task); + + /* Handle the task. */ + task->OnTimer(); + } + } + + ALWAYS_INLINE bool RegisterAbsoluteTaskImpl(KTimerTask *task, s64 task_time) { + MESOSPHERE_ASSERT(task_time > 0); + + /* Set the task's time, and insert it into our tree. */ + task->SetTime(task_time); + m_task_tree.insert(*task); + + /* Update our next task if relevant. */ + if (m_next_task != nullptr && m_next_task->GetTime() <= task_time) { + return false; + } + m_next_task = task; + return true; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_initial_process_reader.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_initial_process_reader.hpp new file mode 100644 index 00000000..8d22c3b7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_initial_process_reader.hpp @@ -0,0 +1,140 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_address_space_info.hpp> +#include <mesosphere/kern_select_page_table.hpp> + +namespace ams::kern { + + class KInitialProcessHeader { + private: + static constexpr u32 Magic = util::FourCC<'K','I','P','1'>::Code; + private: + u32 m_magic; + u8 m_name[12]; + u64 m_program_id; + u32 m_version; + u8 m_priority; + u8 m_ideal_core_id; + u8 m_1E; + u8 m_flags; + u32 m_rx_address; + u32 m_rx_size; + u32 m_rx_compressed_size; + u32 m_affinity_mask; + u32 m_ro_address; + u32 m_ro_size; + u32 m_ro_compressed_size; + u32 m_stack_size; + u32 m_rw_address; + u32 m_rw_size; + u32 m_rw_compressed_size; + u32 m_4C; + u32 m_bss_address; + u32 m_bss_size; + u32 m_pad[(0x80 - 0x58) / sizeof(u32)]; + u32 m_capabilities[0x80 / sizeof(u32)]; + public: + constexpr bool IsValid() const { return m_magic == Magic; } + + constexpr void GetName(char *dst, size_t size) const { + std::memset(dst, 0, size); + std::memcpy(dst, m_name, std::min(sizeof(m_name), size)); + } + + constexpr const u32 *GetCapabilities() const { return m_capabilities; } + constexpr size_t GetNumCapabilities() const { return util::size(m_capabilities); } + + constexpr u64 GetProgramId() const { return m_program_id; } + constexpr u32 GetVersion() const { return m_version; } + constexpr u8 GetPriority() const { return m_priority; } + constexpr u8 GetIdealCoreId() const { return m_ideal_core_id; } + + constexpr bool IsRxCompressed() const { return (m_flags & (1 << 0)); } + constexpr bool IsRoCompressed() const { return (m_flags & (1 << 1)); } + constexpr bool IsRwCompressed() const { return (m_flags & (1 << 2)); } + constexpr bool Is64Bit() const { return (m_flags & (1 << 3)); } + constexpr bool Is64BitAddressSpace() const { return (m_flags & (1 << 4)); } + constexpr bool UsesSecureMemory() const { return (m_flags & (1 << 5)); } + constexpr bool IsImmortal() const { return (m_flags & (1 << 6)); } + + constexpr u32 GetRxAddress() const { return m_rx_address; } + constexpr u32 GetRxSize() const { return m_rx_size; } + constexpr u32 GetRxCompressedSize() const { return m_rx_compressed_size; } + constexpr u32 GetRoAddress() const { return m_ro_address; } + constexpr u32 GetRoSize() const { return m_ro_size; } + constexpr u32 GetRoCompressedSize() const { return m_ro_compressed_size; } + constexpr u32 GetRwAddress() const { return m_rw_address; } + constexpr u32 GetRwSize() const { return m_rw_size; } + constexpr u32 GetRwCompressedSize() const { return m_rw_compressed_size; } + constexpr u32 GetBssAddress() const { return m_bss_address; } + constexpr u32 GetBssSize() const { return m_bss_size; } + + constexpr u32 GetAffinityMask() const { return m_affinity_mask; } + constexpr u32 GetStackSize() const { return m_stack_size; } + }; + static_assert(sizeof(KInitialProcessHeader) == 0x100); + + class KInitialProcessReader { + private: + KInitialProcessHeader m_kip_header; + public: + constexpr KInitialProcessReader() : m_kip_header() { /* ... */ } + + constexpr const u32 *GetCapabilities() const { return m_kip_header.GetCapabilities(); } + constexpr size_t GetNumCapabilities() const { return m_kip_header.GetNumCapabilities(); } + + constexpr size_t GetBinarySize() const { + return m_kip_header.GetRxCompressedSize() + m_kip_header.GetRoCompressedSize() + m_kip_header.GetRwCompressedSize(); + } + + constexpr size_t GetSize() const { + if (const size_t bss_size = m_kip_header.GetBssSize(); bss_size != 0) { + return util::AlignUp(m_kip_header.GetBssAddress() + m_kip_header.GetBssSize(), PageSize); + } else { + return util::AlignUp(m_kip_header.GetRwAddress() + m_kip_header.GetRwSize(), PageSize); + } + } + + constexpr u8 GetPriority() const { return m_kip_header.GetPriority(); } + constexpr u8 GetIdealCoreId() const { return m_kip_header.GetIdealCoreId(); } + constexpr u32 GetAffinityMask() const { return m_kip_header.GetAffinityMask(); } + constexpr u32 GetStackSize() const { return m_kip_header.GetStackSize(); } + + constexpr bool Is64Bit() const { return m_kip_header.Is64Bit(); } + constexpr bool Is64BitAddressSpace() const { return m_kip_header.Is64BitAddressSpace(); } + constexpr bool UsesSecureMemory() const { return m_kip_header.UsesSecureMemory(); } + constexpr bool IsImmortal() const { return m_kip_header.IsImmortal(); } + + KVirtualAddress Attach(KVirtualAddress bin) { + /* Copy the header. */ + m_kip_header = *GetPointer<const KInitialProcessHeader>(bin); + + /* Check that it's valid. */ + if (m_kip_header.IsValid()) { + return bin + sizeof(KInitialProcessHeader); + } else { + return Null<KVirtualAddress>; + } + } + + Result MakeCreateProcessParameter(ams::svc::CreateProcessParameter *out, bool enable_aslr) const; + void Load(const KPageGroup &pg, KVirtualAddress data) const; + Result SetMemoryPermissions(KProcessPageTable &page_table, const ams::svc::CreateProcessParameter ¶ms) const; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_interrupt_event.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_interrupt_event.hpp new file mode 100644 index 00000000..ac32cdad --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_interrupt_event.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_auto_object.hpp> +#include <mesosphere/kern_slab_helpers.hpp> +#include <mesosphere/kern_k_readable_event.hpp> +#include <mesosphere/kern_k_interrupt_task.hpp> + +namespace ams::kern { + + class KInterruptEvent final : public KAutoObjectWithSlabHeapAndContainer<KInterruptEvent, KReadableEvent>, public KInterruptTask { + MESOSPHERE_AUTOOBJECT_TRAITS(KInterruptEvent, KReadableEvent); + private: + s32 m_interrupt_id; + s32 m_core_id; + bool m_is_initialized; + public: + constexpr explicit KInterruptEvent(util::ConstantInitializeTag) : KAutoObjectWithSlabHeapAndContainer<KInterruptEvent, KReadableEvent>(util::ConstantInitialize), m_interrupt_id(-1), m_core_id(-1), m_is_initialized(false) { /* ... */ } + + explicit KInterruptEvent() : m_interrupt_id(-1), m_is_initialized(false) { /* ... */ } + + Result Initialize(int32_t interrupt_name, ams::svc::InterruptType type); + void Finalize(); + + Result Reset(); + + Result Clear() { + MESOSPHERE_ASSERT_THIS(); + + /* Try to perform a reset, succeeding unconditionally. */ + this->Reset(); + + R_SUCCEED(); + } + + bool IsInitialized() const { return m_is_initialized; } + + static void PostDestroy(uintptr_t arg) { MESOSPHERE_UNUSED(arg); /* ... */ } + + constexpr s32 GetInterruptId() const { return m_interrupt_id; } + + virtual KInterruptTask *OnInterrupt(s32 interrupt_id) override; + virtual void DoTask() override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_interrupt_task.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_interrupt_task.hpp new file mode 100644 index 00000000..f2a96042 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_interrupt_task.hpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +namespace ams::kern { + + class KInterruptTask; + + class KInterruptHandler { + public: + virtual KInterruptTask *OnInterrupt(s32 interrupt_id) = 0; + }; + + class KInterruptTask : public KInterruptHandler { + private: + KInterruptTask *m_next_task; + public: + constexpr ALWAYS_INLINE KInterruptTask() : m_next_task(nullptr) { /* ... */ } + + constexpr ALWAYS_INLINE KInterruptTask *GetNextTask() const { + return m_next_task; + } + + constexpr ALWAYS_INLINE void SetNextTask(KInterruptTask *t) { + m_next_task = t; + } + + virtual void DoTask() = 0; + }; + + static ALWAYS_INLINE KInterruptTask *GetDummyInterruptTask() { + return reinterpret_cast<KInterruptTask *>(1); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_interrupt_task_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_interrupt_task_manager.hpp new file mode 100644 index 00000000..f13f3256 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_interrupt_task_manager.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_k_interrupt_task.hpp> + +namespace ams::kern { + + class KThread; + + class KInterruptTaskManager { + private: + class TaskQueue { + private: + KInterruptTask *m_head; + KInterruptTask *m_tail; + public: + constexpr ALWAYS_INLINE TaskQueue() : m_head(nullptr), m_tail(nullptr) { /* ... */ } + + constexpr ALWAYS_INLINE KInterruptTask *GetHead() { return m_head; } + constexpr ALWAYS_INLINE bool IsEmpty() const { return m_head == nullptr; } + constexpr ALWAYS_INLINE void Clear() { m_head = nullptr; m_tail = nullptr; } + + void Enqueue(KInterruptTask *task); + void Dequeue(); + }; + private: + TaskQueue m_task_queue; + s64 m_cpu_time; + public: + constexpr KInterruptTaskManager() : m_task_queue(), m_cpu_time(0) { /* ... */ } + + constexpr ALWAYS_INLINE s64 GetCpuTime() const { return m_cpu_time; } + + void EnqueueTask(KInterruptTask *task); + void DoTasks(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_io_pool.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_io_pool.hpp new file mode 100644 index 00000000..e9b84f34 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_io_pool.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_auto_object.hpp> +#include <mesosphere/kern_slab_helpers.hpp> +#include <mesosphere/kern_k_io_region.hpp> + +namespace ams::kern { + + class KIoPool final : public KAutoObjectWithSlabHeapAndContainer<KIoPool, KAutoObjectWithList> { + MESOSPHERE_AUTOOBJECT_TRAITS(KIoPool, KAutoObject); + private: + using IoRegionTree = util::IntrusiveRedBlackTreeBaseTraits<KIoRegion>::TreeType<KIoRegion>; + private: + KLightLock m_lock; + IoRegionTree m_io_region_tree; + ams::svc::IoPoolType m_pool_type; + bool m_is_initialized; + public: + static bool IsValidIoPoolType(ams::svc::IoPoolType pool_type); + public: + explicit KIoPool() : m_is_initialized(false) { + /* ... */ + } + + Result Initialize(ams::svc::IoPoolType pool_type); + void Finalize(); + + bool IsInitialized() const { return m_is_initialized; } + static void PostDestroy(uintptr_t arg) { MESOSPHERE_UNUSED(arg); /* ... */ } + + Result AddIoRegion(KIoRegion *region); + void RemoveIoRegion(KIoRegion *region); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_io_region.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_io_region.hpp new file mode 100644 index 00000000..5402bd70 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_io_region.hpp @@ -0,0 +1,93 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_auto_object.hpp> +#include <mesosphere/kern_slab_helpers.hpp> + +namespace ams::kern { + + class KProcess; + class KIoPool; + + class KIoRegion final : public KAutoObjectWithSlabHeapAndContainer<KIoRegion, KAutoObjectWithList>, public util::IntrusiveRedBlackTreeBaseNode<KIoRegion> { + MESOSPHERE_AUTOOBJECT_TRAITS(KIoRegion, KAutoObject); + private: + friend class KProcess; + friend class KIoPool; + public: + using RedBlackKeyType = KPhysicalAddress; + + static constexpr ALWAYS_INLINE RedBlackKeyType GetRedBlackKey(const RedBlackKeyType &v) { return v; } + static constexpr ALWAYS_INLINE RedBlackKeyType GetRedBlackKey(const KIoRegion &v) { return v.GetAddress(); } + + template<typename T> requires (std::same_as<T, KIoRegion> || std::same_as<T, RedBlackKeyType>) + static constexpr ALWAYS_INLINE int Compare(const T &lhs, const KIoRegion &rhs) { + const RedBlackKeyType lval = GetRedBlackKey(lhs); + const RedBlackKeyType rval = GetRedBlackKey(rhs); + + if (lval < rval) { + return -1; + } else if (lval == rval) { + return 0; + } else { + return 1; + } + } + private: + KLightLock m_lock; + KIoPool *m_pool; + KPhysicalAddress m_physical_address; + size_t m_size; + ams::svc::MemoryMapping m_mapping; + ams::svc::MemoryPermission m_permission; + bool m_is_initialized; + bool m_is_mapped; + util::IntrusiveListNode m_process_list_node; + public: + explicit KIoRegion() : m_pool(nullptr), m_is_initialized(false) { /* ... */ } + + Result Initialize(KIoPool *pool, KPhysicalAddress phys_addr, size_t size, ams::svc::MemoryMapping mapping, ams::svc::MemoryPermission perm); + void Finalize(); + + bool IsInitialized() const { return m_is_initialized; } + static void PostDestroy(uintptr_t arg) { MESOSPHERE_UNUSED(arg); /* ... */ } + + Result Map(KProcessAddress address, size_t size, ams::svc::MemoryPermission map_perm); + Result Unmap(KProcessAddress address, size_t size); + + constexpr bool Overlaps(KPhysicalAddress address, size_t size) const { + return m_physical_address <= (address + size - 1) && address <= (m_physical_address + m_size - 1); + } + + constexpr ALWAYS_INLINE KPhysicalAddress GetAddress() const { return m_physical_address; } + constexpr ALWAYS_INLINE size_t GetSize() const { return m_size; } + + constexpr uintptr_t GetHint() const { + /* TODO: Is this architecture specific? */ + if (m_size >= 2_MB) { + return GetInteger(m_physical_address) & (2_MB - 1); + } else if (m_size >= 64_KB) { + return GetInteger(m_physical_address) & (64_KB - 1); + } else if (m_size >= 4_KB) { + return GetInteger(m_physical_address) & (4_KB - 1); + } else { + return 0; + } + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_light_client_session.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_light_client_session.hpp new file mode 100644 index 00000000..d20d10d6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_light_client_session.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_synchronization_object.hpp> + +namespace ams::kern { + + class KLightSession; + + class KLightClientSession final : public KAutoObject { + MESOSPHERE_AUTOOBJECT_TRAITS(KLightClientSession, KAutoObject); + private: + KLightSession *m_parent; + public: + explicit KLightClientSession() { /* ... */ } + + void Initialize(KLightSession *parent) { + /* Set member variables. */ + m_parent = parent; + } + + virtual void Destroy() override; + + constexpr const KLightSession *GetParent() const { return m_parent; } + + Result SendSyncRequest(u32 *data); + + void OnServerClosed(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_light_condition_variable.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_light_condition_variable.hpp new file mode 100644 index 00000000..3a6d61e2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_light_condition_variable.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_select_cpu.hpp> +#include <mesosphere/kern_k_light_lock.hpp> +#include <mesosphere/kern_k_thread_queue.hpp> +#include <mesosphere/kern_k_scoped_scheduler_lock_and_sleep.hpp> + +namespace ams::kern { + + class KLightConditionVariable { + private: + KThread::WaiterList m_wait_list; + public: + constexpr explicit ALWAYS_INLINE KLightConditionVariable(util::ConstantInitializeTag) : m_wait_list() { /* ... */ } + + explicit ALWAYS_INLINE KLightConditionVariable() { /* ... */ } + public: + void Wait(KLightLock *lock, s64 timeout = -1ll, bool allow_terminating_thread = true); + void Broadcast(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_light_lock.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_light_lock.hpp new file mode 100644 index 00000000..c96f5636 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_light_lock.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_select_cpu.hpp> +#include <mesosphere/kern_k_current_context.hpp> +#include <mesosphere/kern_k_scoped_lock.hpp> + +namespace ams::kern { + + class KLightLock { + private: + util::Atomic<uintptr_t> m_tag; + public: + constexpr ALWAYS_INLINE KLightLock() : m_tag(0) { /* ... */ } + + void Lock() { + MESOSPHERE_ASSERT_THIS(); + + const uintptr_t cur_thread = reinterpret_cast<uintptr_t>(GetCurrentThreadPointer()); + + while (true) { + uintptr_t old_tag = m_tag.Load<std::memory_order_relaxed>(); + + while (!m_tag.CompareExchangeWeak<std::memory_order_acquire>(old_tag, (old_tag == 0) ? cur_thread : (old_tag | 1))) { + /* ... */ + } + + if (old_tag == 0 || this->LockSlowPath(old_tag | 1, cur_thread)) { + break; + } + } + } + + ALWAYS_INLINE void Unlock() { + MESOSPHERE_ASSERT_THIS(); + + const uintptr_t cur_thread = reinterpret_cast<uintptr_t>(GetCurrentThreadPointer()); + + uintptr_t expected = cur_thread; + if (!m_tag.CompareExchangeStrong<std::memory_order_release>(expected, 0)) { + this->UnlockSlowPath(cur_thread); + } + } + + NOINLINE bool LockSlowPath(uintptr_t owner, uintptr_t cur_thread); + NOINLINE void UnlockSlowPath(uintptr_t cur_thread); + + ALWAYS_INLINE bool IsLocked() const { return m_tag.Load() != 0; } + ALWAYS_INLINE bool IsLockedByCurrentThread() const { return (m_tag.Load() | 0x1ul) == (reinterpret_cast<uintptr_t>(GetCurrentThreadPointer()) | 0x1ul); } + }; + + using KScopedLightLock = KScopedLock<KLightLock>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_light_server_session.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_light_server_session.hpp new file mode 100644 index 00000000..b991672e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_light_server_session.hpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_synchronization_object.hpp> +#include <mesosphere/kern_k_thread.hpp> +#include <mesosphere/kern_k_thread_queue.hpp> + +namespace ams::kern { + + class KLightSession; + + class KLightServerSession final : public KAutoObject, public util::IntrusiveListBaseNode<KLightServerSession> { + MESOSPHERE_AUTOOBJECT_TRAITS(KLightServerSession, KAutoObject); + private: + KLightSession *m_parent; + KThread::WaiterList m_request_list; + KThread *m_current_request; + u64 m_server_thread_id; + KThread *m_server_thread; + public: + explicit KLightServerSession() : m_current_request(nullptr), m_server_thread_id(std::numeric_limits<u64>::max()), m_server_thread() { /* ... */ } + + void Initialize(KLightSession *parent) { + /* Set member variables. */ + m_parent = parent; + } + + virtual void Destroy() override; + + constexpr const KLightSession *GetParent() const { return m_parent; } + + Result OnRequest(KThread *request_thread); + Result ReplyAndReceive(u32 *data); + + void OnClientClosed(); + private: + void CleanupRequests(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_light_session.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_light_session.hpp new file mode 100644 index 00000000..22892403 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_light_session.hpp @@ -0,0 +1,73 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_synchronization_object.hpp> +#include <mesosphere/kern_k_light_server_session.hpp> +#include <mesosphere/kern_k_light_client_session.hpp> +#include <mesosphere/kern_slab_helpers.hpp> + +namespace ams::kern { + + class KClientPort; + class KProcess; + + class KLightSession final : public KAutoObjectWithSlabHeapAndContainer<KLightSession, KAutoObjectWithList, true> { + MESOSPHERE_AUTOOBJECT_TRAITS(KLightSession, KAutoObject); + private: + enum class State : u8 { + Invalid = 0, + Normal = 1, + ClientClosed = 2, + ServerClosed = 3, + }; + public: + static constexpr size_t DataSize = sizeof(u32) * 7; + static constexpr u32 ReplyFlag = (1u << (BITSIZEOF(u32) - 1)); + private: + KLightServerSession m_server; + KLightClientSession m_client; + State m_state; + KClientPort *m_port; + uintptr_t m_name; + KProcess *m_process; + bool m_initialized; + public: + explicit KLightSession() : m_state(State::Invalid), m_process(), m_initialized() { /* ... */ } + + void Initialize(KClientPort *client_port, uintptr_t name); + void Finalize(); + + bool IsInitialized() const { return m_initialized; } + uintptr_t GetPostDestroyArgument() const { return reinterpret_cast<uintptr_t>(m_process); } + + static void PostDestroy(uintptr_t arg); + + void OnServerClosed(); + void OnClientClosed(); + + bool IsServerClosed() const { return m_state != State::Normal; } + bool IsClientClosed() const { return m_state != State::Normal; } + + Result OnRequest(KThread *request_thread) { R_RETURN(m_server.OnRequest(request_thread)); } + + KLightClientSession &GetClientSession() { return m_client; } + KLightServerSession &GetServerSession() { return m_server; } + const KLightClientSession &GetClientSession() const { return m_client; } + const KLightServerSession &GetServerSession() const { return m_server; } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_memory_block.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_memory_block.hpp new file mode 100644 index 00000000..57876fbc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_memory_block.hpp @@ -0,0 +1,672 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_typed_address.hpp> +#include <mesosphere/kern_slab_helpers.hpp> + +namespace ams::kern { + + enum KMemoryState : u32 { + KMemoryState_None = 0, + KMemoryState_Mask = 0xFF, + KMemoryState_All = ~KMemoryState_None, + + KMemoryState_FlagCanReprotect = (1 << 8), + KMemoryState_FlagCanDebug = (1 << 9), + KMemoryState_FlagCanUseIpc = (1 << 10), + KMemoryState_FlagCanUseNonDeviceIpc = (1 << 11), + KMemoryState_FlagCanUseNonSecureIpc = (1 << 12), + KMemoryState_FlagMapped = (1 << 13), + KMemoryState_FlagCode = (1 << 14), + KMemoryState_FlagCanAlias = (1 << 15), + KMemoryState_FlagCanCodeAlias = (1 << 16), + KMemoryState_FlagCanTransfer = (1 << 17), + KMemoryState_FlagCanQueryPhysical = (1 << 18), + KMemoryState_FlagCanDeviceMap = (1 << 19), + KMemoryState_FlagCanAlignedDeviceMap = (1 << 20), + KMemoryState_FlagCanIpcUserBuffer = (1 << 21), + KMemoryState_FlagReferenceCounted = (1 << 22), + KMemoryState_FlagCanMapProcess = (1 << 23), + KMemoryState_FlagCanChangeAttribute = (1 << 24), + KMemoryState_FlagCanCodeMemory = (1 << 25), + KMemoryState_FlagLinearMapped = (1 << 26), + KMemoryState_FlagCanPermissionLock = (1 << 27), + + KMemoryState_FlagsData = KMemoryState_FlagCanReprotect | KMemoryState_FlagCanUseIpc | + KMemoryState_FlagCanUseNonDeviceIpc | KMemoryState_FlagCanUseNonSecureIpc | + KMemoryState_FlagMapped | KMemoryState_FlagCanAlias | + KMemoryState_FlagCanTransfer | KMemoryState_FlagCanQueryPhysical | + KMemoryState_FlagCanDeviceMap | KMemoryState_FlagCanAlignedDeviceMap | + KMemoryState_FlagCanIpcUserBuffer | KMemoryState_FlagReferenceCounted | + KMemoryState_FlagCanChangeAttribute | KMemoryState_FlagLinearMapped, + + KMemoryState_FlagsCode = KMemoryState_FlagCanDebug | KMemoryState_FlagCanUseIpc | + KMemoryState_FlagCanUseNonDeviceIpc | KMemoryState_FlagCanUseNonSecureIpc | + KMemoryState_FlagMapped | KMemoryState_FlagCode | + KMemoryState_FlagCanQueryPhysical | KMemoryState_FlagCanDeviceMap | + KMemoryState_FlagCanAlignedDeviceMap | KMemoryState_FlagReferenceCounted | + KMemoryState_FlagLinearMapped, + + KMemoryState_FlagsMisc = KMemoryState_FlagMapped | KMemoryState_FlagReferenceCounted | + KMemoryState_FlagCanQueryPhysical | KMemoryState_FlagCanDeviceMap | + KMemoryState_FlagLinearMapped, + + + KMemoryState_Free = ams::svc::MemoryState_Free, + + KMemoryState_IoMemory = ams::svc::MemoryState_Io | KMemoryState_FlagMapped | KMemoryState_FlagCanDeviceMap | KMemoryState_FlagCanAlignedDeviceMap, + KMemoryState_IoRegister = ams::svc::MemoryState_Io | KMemoryState_FlagCanDeviceMap | KMemoryState_FlagCanAlignedDeviceMap, + + + KMemoryState_Static = ams::svc::MemoryState_Static | KMemoryState_FlagCanQueryPhysical, + KMemoryState_Code = ams::svc::MemoryState_Code | KMemoryState_FlagsCode | KMemoryState_FlagCanMapProcess, + KMemoryState_CodeData = ams::svc::MemoryState_CodeData | KMemoryState_FlagsData | KMemoryState_FlagCanMapProcess | KMemoryState_FlagCanCodeMemory | KMemoryState_FlagCanPermissionLock, + KMemoryState_Normal = ams::svc::MemoryState_Normal | KMemoryState_FlagsData | KMemoryState_FlagCanCodeMemory, + KMemoryState_Shared = ams::svc::MemoryState_Shared | KMemoryState_FlagMapped | KMemoryState_FlagReferenceCounted | KMemoryState_FlagLinearMapped, + + /* KMemoryState_Alias was removed after 1.0.0. */ + + KMemoryState_AliasCode = ams::svc::MemoryState_AliasCode | KMemoryState_FlagsCode | KMemoryState_FlagCanMapProcess | KMemoryState_FlagCanCodeAlias, + KMemoryState_AliasCodeData = ams::svc::MemoryState_AliasCodeData | KMemoryState_FlagsData | KMemoryState_FlagCanMapProcess | KMemoryState_FlagCanCodeAlias | KMemoryState_FlagCanCodeMemory + | KMemoryState_FlagCanPermissionLock, + + KMemoryState_Ipc = ams::svc::MemoryState_Ipc | KMemoryState_FlagsMisc | KMemoryState_FlagCanAlignedDeviceMap + | KMemoryState_FlagCanUseIpc | KMemoryState_FlagCanUseNonSecureIpc | KMemoryState_FlagCanUseNonDeviceIpc, + + KMemoryState_Stack = ams::svc::MemoryState_Stack | KMemoryState_FlagsMisc | KMemoryState_FlagCanAlignedDeviceMap + | KMemoryState_FlagCanUseIpc | KMemoryState_FlagCanUseNonSecureIpc | KMemoryState_FlagCanUseNonDeviceIpc, + + KMemoryState_ThreadLocal = ams::svc::MemoryState_ThreadLocal | KMemoryState_FlagLinearMapped, + + KMemoryState_Transfered = ams::svc::MemoryState_Transfered | KMemoryState_FlagsMisc | KMemoryState_FlagCanAlignedDeviceMap | KMemoryState_FlagCanChangeAttribute + | KMemoryState_FlagCanUseIpc | KMemoryState_FlagCanUseNonSecureIpc | KMemoryState_FlagCanUseNonDeviceIpc, + + KMemoryState_SharedTransfered = ams::svc::MemoryState_SharedTransfered | KMemoryState_FlagsMisc | KMemoryState_FlagCanAlignedDeviceMap + | KMemoryState_FlagCanUseNonSecureIpc | KMemoryState_FlagCanUseNonDeviceIpc, + + KMemoryState_SharedCode = ams::svc::MemoryState_SharedCode | KMemoryState_FlagMapped | KMemoryState_FlagReferenceCounted | KMemoryState_FlagLinearMapped + | KMemoryState_FlagCanUseNonSecureIpc | KMemoryState_FlagCanUseNonDeviceIpc, + + KMemoryState_Inaccessible = ams::svc::MemoryState_Inaccessible, + + KMemoryState_NonSecureIpc = ams::svc::MemoryState_NonSecureIpc | KMemoryState_FlagsMisc | KMemoryState_FlagCanAlignedDeviceMap + | KMemoryState_FlagCanUseNonSecureIpc | KMemoryState_FlagCanUseNonDeviceIpc, + + KMemoryState_NonDeviceIpc = ams::svc::MemoryState_NonDeviceIpc | KMemoryState_FlagsMisc | KMemoryState_FlagCanUseNonDeviceIpc, + + + KMemoryState_Kernel = ams::svc::MemoryState_Kernel, + + KMemoryState_GeneratedCode = ams::svc::MemoryState_GeneratedCode | KMemoryState_FlagMapped | KMemoryState_FlagReferenceCounted | KMemoryState_FlagCanDebug | KMemoryState_FlagLinearMapped, + KMemoryState_CodeOut = ams::svc::MemoryState_CodeOut | KMemoryState_FlagMapped | KMemoryState_FlagReferenceCounted | KMemoryState_FlagLinearMapped, + + KMemoryState_Coverage = ams::svc::MemoryState_Coverage | KMemoryState_FlagMapped, + + KMemoryState_Insecure = ams::svc::MemoryState_Insecure | KMemoryState_FlagMapped | KMemoryState_FlagReferenceCounted | KMemoryState_FlagLinearMapped | KMemoryState_FlagCanChangeAttribute + | KMemoryState_FlagCanDeviceMap | KMemoryState_FlagCanAlignedDeviceMap | KMemoryState_FlagCanQueryPhysical + | KMemoryState_FlagCanUseNonSecureIpc | KMemoryState_FlagCanUseNonDeviceIpc, + }; + +#if 1 + static_assert(KMemoryState_Free == 0x00000000); + static_assert(KMemoryState_IoMemory == 0x00182001); + static_assert(KMemoryState_IoRegister == 0x00180001); + static_assert(KMemoryState_Static == 0x00040002); + static_assert(KMemoryState_Code == 0x04DC7E03); + static_assert(KMemoryState_CodeData == 0x0FFEBD04); + static_assert(KMemoryState_Normal == 0x077EBD05); + static_assert(KMemoryState_Shared == 0x04402006); + + static_assert(KMemoryState_AliasCode == 0x04DD7E08); + static_assert(KMemoryState_AliasCodeData == 0x0FFFBD09); + static_assert(KMemoryState_Ipc == 0x045C3C0A); + static_assert(KMemoryState_Stack == 0x045C3C0B); + static_assert(KMemoryState_ThreadLocal == 0x0400000C); + static_assert(KMemoryState_Transfered == 0x055C3C0D); + static_assert(KMemoryState_SharedTransfered == 0x045C380E); + static_assert(KMemoryState_SharedCode == 0x0440380F); + static_assert(KMemoryState_Inaccessible == 0x00000010); + static_assert(KMemoryState_NonSecureIpc == 0x045C3811); + static_assert(KMemoryState_NonDeviceIpc == 0x044C2812); + static_assert(KMemoryState_Kernel == 0x00000013); + static_assert(KMemoryState_GeneratedCode == 0x04402214); + static_assert(KMemoryState_CodeOut == 0x04402015); + static_assert(KMemoryState_Coverage == 0x00002016); /* TODO: Is this correct? */ + static_assert(KMemoryState_Insecure == 0x055C3817); +#endif + + enum KMemoryPermission : u8 { + KMemoryPermission_None = 0, + KMemoryPermission_All = static_cast<u8>(~KMemoryPermission_None), + + KMemoryPermission_KernelShift = 3, + + KMemoryPermission_KernelRead = ams::svc::MemoryPermission_Read << KMemoryPermission_KernelShift, + KMemoryPermission_KernelWrite = ams::svc::MemoryPermission_Write << KMemoryPermission_KernelShift, + KMemoryPermission_KernelExecute = ams::svc::MemoryPermission_Execute << KMemoryPermission_KernelShift, + + KMemoryPermission_NotMapped = (1 << (2 * KMemoryPermission_KernelShift)), + + KMemoryPermission_KernelReadWrite = KMemoryPermission_KernelRead | KMemoryPermission_KernelWrite, + KMemoryPermission_KernelReadExecute = KMemoryPermission_KernelRead | KMemoryPermission_KernelExecute, + + KMemoryPermission_UserRead = ams::svc::MemoryPermission_Read | KMemoryPermission_KernelRead, + KMemoryPermission_UserWrite = ams::svc::MemoryPermission_Write | KMemoryPermission_KernelWrite, + KMemoryPermission_UserExecute = ams::svc::MemoryPermission_Execute, + + KMemoryPermission_UserReadWrite = KMemoryPermission_UserRead | KMemoryPermission_UserWrite, + KMemoryPermission_UserReadExecute = KMemoryPermission_UserRead | KMemoryPermission_UserExecute, + + KMemoryPermission_UserMask = ams::svc::MemoryPermission_Read | ams::svc::MemoryPermission_Write | ams::svc::MemoryPermission_Execute, + + KMemoryPermission_IpcLockChangeMask = KMemoryPermission_NotMapped | KMemoryPermission_UserReadWrite, + }; + + constexpr KMemoryPermission ConvertToKMemoryPermission(ams::svc::MemoryPermission perm) { + return static_cast<KMemoryPermission>((util::ToUnderlying(perm) & KMemoryPermission_UserMask) | KMemoryPermission_KernelRead | ((util::ToUnderlying(perm) & ams::svc::MemoryPermission_Write) ? KMemoryPermission_KernelWrite : KMemoryPermission_None) | (perm == ams::svc::MemoryPermission_None ? KMemoryPermission_NotMapped : KMemoryPermission_None)); + } + + enum KMemoryAttribute : u8 { + KMemoryAttribute_None = 0x00, + KMemoryAttribute_All = 0xFF, + KMemoryAttribute_UserMask = KMemoryAttribute_All, + + KMemoryAttribute_Locked = ams::svc::MemoryAttribute_Locked, + KMemoryAttribute_IpcLocked = ams::svc::MemoryAttribute_IpcLocked, + KMemoryAttribute_DeviceShared = ams::svc::MemoryAttribute_DeviceShared, + KMemoryAttribute_Uncached = ams::svc::MemoryAttribute_Uncached, + KMemoryAttribute_PermissionLocked = ams::svc::MemoryAttribute_PermissionLocked, + + KMemoryAttribute_SetMask = KMemoryAttribute_Uncached | KMemoryAttribute_PermissionLocked, + }; + + enum KMemoryBlockDisableMergeAttribute : u8 { + KMemoryBlockDisableMergeAttribute_None = 0, + KMemoryBlockDisableMergeAttribute_Normal = (1u << 0), + KMemoryBlockDisableMergeAttribute_DeviceLeft = (1u << 1), + KMemoryBlockDisableMergeAttribute_IpcLeft = (1u << 2), + KMemoryBlockDisableMergeAttribute_Locked = (1u << 3), + /* ... */ + KMemoryBlockDisableMergeAttribute_DeviceRight = (1u << 5), + + KMemoryBlockDisableMergeAttribute_AllLeft = KMemoryBlockDisableMergeAttribute_Normal | KMemoryBlockDisableMergeAttribute_DeviceLeft | KMemoryBlockDisableMergeAttribute_IpcLeft | KMemoryBlockDisableMergeAttribute_Locked, + KMemoryBlockDisableMergeAttribute_AllRight = KMemoryBlockDisableMergeAttribute_DeviceRight, + }; + + struct KMemoryInfo { + uintptr_t m_address; + size_t m_size; + KMemoryState m_state; + u16 m_device_disable_merge_left_count; + u16 m_device_disable_merge_right_count; + u16 m_ipc_lock_count; + u16 m_device_use_count; + u16 m_ipc_disable_merge_count; + KMemoryPermission m_permission; + KMemoryAttribute m_attribute; + KMemoryPermission m_original_permission; + KMemoryBlockDisableMergeAttribute m_disable_merge_attribute; + + constexpr ams::svc::MemoryInfo GetSvcMemoryInfo() const { + return { + .base_address = m_address, + .size = m_size, + .state = static_cast<ams::svc::MemoryState>(m_state & KMemoryState_Mask), + .attribute = static_cast<ams::svc::MemoryAttribute>(m_attribute & KMemoryAttribute_UserMask), + .permission = static_cast<ams::svc::MemoryPermission>(m_permission & KMemoryPermission_UserMask), + .ipc_count = m_ipc_lock_count, + .device_count = m_device_use_count, + .padding = {}, + }; + } + + constexpr uintptr_t GetAddress() const { + return m_address; + } + + constexpr size_t GetSize() const { + return m_size; + } + + constexpr size_t GetNumPages() const { + return this->GetSize() / PageSize; + } + + constexpr uintptr_t GetEndAddress() const { + return this->GetAddress() + this->GetSize(); + } + + constexpr uintptr_t GetLastAddress() const { + return this->GetEndAddress() - 1; + } + + constexpr u16 GetIpcLockCount() const { + return m_ipc_lock_count; + } + + constexpr u16 GetIpcDisableMergeCount() const { + return m_ipc_disable_merge_count; + } + + constexpr KMemoryState GetState() const { + return m_state; + } + + constexpr ams::svc::MemoryState GetSvcState() const { + return static_cast<ams::svc::MemoryState>(m_state & KMemoryState_Mask); + } + + constexpr KMemoryPermission GetPermission() const { + return m_permission; + } + + constexpr KMemoryPermission GetOriginalPermission() const { + return m_original_permission; + } + + constexpr KMemoryAttribute GetAttribute() const { + return m_attribute; + } + + constexpr KMemoryBlockDisableMergeAttribute GetDisableMergeAttribute() const { + return m_disable_merge_attribute; + } + }; + + class KMemoryBlock : public util::IntrusiveRedBlackTreeBaseNode<KMemoryBlock> { + private: + KMemoryPermission m_permission; + KMemoryPermission m_original_permission; + KMemoryAttribute m_attribute; + KMemoryBlockDisableMergeAttribute m_disable_merge_attribute; + KProcessAddress m_address; + u32 m_num_pages; + KMemoryState m_memory_state; + u16 m_ipc_lock_count; + u16 m_ipc_disable_merge_count; + u16 m_device_use_count; + u16 m_device_disable_merge_left_count; + u16 m_device_disable_merge_right_count; + public: + static constexpr ALWAYS_INLINE int Compare(const KMemoryBlock &lhs, const KMemoryBlock &rhs) { + if (lhs.GetAddress() < rhs.GetAddress()) { + return -1; + } else if (lhs.GetAddress() <= rhs.GetLastAddress()) { + return 0; + } else { + return 1; + } + } + public: + constexpr KProcessAddress GetAddress() const { + return m_address; + } + + constexpr size_t GetNumPages() const { + return m_num_pages; + } + + constexpr size_t GetSize() const { + return this->GetNumPages() * PageSize; + } + + constexpr KProcessAddress GetEndAddress() const { + return this->GetAddress() + this->GetSize(); + } + + constexpr KProcessAddress GetLastAddress() const { + return this->GetEndAddress() - 1; + } + + constexpr KMemoryState GetState() const { + return m_memory_state; + } + + constexpr u16 GetIpcLockCount() const { + return m_ipc_lock_count; + } + + constexpr u16 GetIpcDisableMergeCount() const { + return m_ipc_disable_merge_count; + } + + constexpr u16 GetDeviceUseCount() const { + return m_device_use_count; + } + + constexpr KMemoryPermission GetPermission() const { + return m_permission; + } + + constexpr KMemoryPermission GetOriginalPermission() const { + return m_original_permission; + } + + constexpr KMemoryAttribute GetAttribute() const { + return m_attribute; + } + + constexpr KMemoryInfo GetMemoryInfo() const { + return { + .m_address = GetInteger(this->GetAddress()), + .m_size = this->GetSize(), + .m_state = m_memory_state, + .m_device_disable_merge_left_count = m_device_disable_merge_left_count, + .m_device_disable_merge_right_count = m_device_disable_merge_right_count, + .m_ipc_lock_count = m_ipc_lock_count, + .m_device_use_count = m_device_use_count, + .m_ipc_disable_merge_count = m_ipc_disable_merge_count, + .m_permission = m_permission, + .m_attribute = m_attribute, + .m_original_permission = m_original_permission, + .m_disable_merge_attribute = m_disable_merge_attribute, + }; + } + public: + explicit KMemoryBlock() { /* ... */ } + + constexpr KMemoryBlock(util::ConstantInitializeTag, KProcessAddress addr, u32 np, KMemoryState ms, KMemoryPermission p, KMemoryAttribute attr) + : util::IntrusiveRedBlackTreeBaseNode<KMemoryBlock>(util::ConstantInitialize), m_permission(p), m_original_permission(KMemoryPermission_None), + m_attribute(attr), m_disable_merge_attribute(), m_address(addr), m_num_pages(np), m_memory_state(ms), m_ipc_lock_count(0), + m_ipc_disable_merge_count(), m_device_use_count(0), m_device_disable_merge_left_count(), m_device_disable_merge_right_count() + { + /* ... */ + } + + constexpr void Initialize(KProcessAddress addr, u32 np, KMemoryState ms, KMemoryPermission p, KMemoryAttribute attr) { + MESOSPHERE_ASSERT_THIS(); + m_device_disable_merge_left_count = 0; + m_device_disable_merge_right_count = 0; + m_address = addr; + m_num_pages = np; + m_memory_state = ms; + m_ipc_lock_count = 0; + m_device_use_count = 0; + m_permission = p; + m_original_permission = KMemoryPermission_None; + m_attribute = attr; + m_disable_merge_attribute = KMemoryBlockDisableMergeAttribute_None; + } + + constexpr bool HasProperties(KMemoryState s, KMemoryPermission p, KMemoryAttribute a) const { + MESOSPHERE_ASSERT_THIS(); + constexpr auto AttributeIgnoreMask = KMemoryAttribute_IpcLocked | KMemoryAttribute_DeviceShared; + return m_memory_state == s && m_permission == p && (m_attribute | AttributeIgnoreMask) == (a | AttributeIgnoreMask); + } + + constexpr bool HasSameProperties(const KMemoryBlock &rhs) const { + MESOSPHERE_ASSERT_THIS(); + + return m_memory_state == rhs.m_memory_state && + m_permission == rhs.m_permission && + m_original_permission == rhs.m_original_permission && + m_attribute == rhs.m_attribute && + m_ipc_lock_count == rhs.m_ipc_lock_count && + m_device_use_count == rhs.m_device_use_count; + } + + constexpr bool CanMergeWith(const KMemoryBlock &rhs) const { + return this->HasSameProperties(rhs) && + (m_disable_merge_attribute & KMemoryBlockDisableMergeAttribute_AllRight) == 0 && + (rhs.m_disable_merge_attribute & KMemoryBlockDisableMergeAttribute_AllLeft) == 0; + } + + constexpr bool Contains(KProcessAddress addr) const { + MESOSPHERE_ASSERT_THIS(); + + return this->GetAddress() <= addr && addr <= this->GetEndAddress(); + } + + constexpr void Add(const KMemoryBlock &added_block) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(added_block.GetNumPages() > 0); + MESOSPHERE_ASSERT(this->GetAddress() + added_block.GetSize() - 1 < this->GetEndAddress() + added_block.GetSize() - 1); + + m_num_pages += added_block.GetNumPages(); + m_disable_merge_attribute = static_cast<KMemoryBlockDisableMergeAttribute>(m_disable_merge_attribute | added_block.m_disable_merge_attribute); + m_device_disable_merge_right_count = added_block.m_device_disable_merge_right_count; + } + + constexpr void Update(KMemoryState s, KMemoryPermission p, KMemoryAttribute a, bool set_disable_merge_attr, u8 set_mask, u8 clear_mask) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(m_original_permission == KMemoryPermission_None); + MESOSPHERE_ASSERT((m_attribute & KMemoryAttribute_IpcLocked) == 0); + + m_memory_state = s; + m_permission = p; + m_attribute = static_cast<KMemoryAttribute>(a | (m_attribute & (KMemoryAttribute_IpcLocked | KMemoryAttribute_DeviceShared))); + + if (set_disable_merge_attr && set_mask != 0) { + m_disable_merge_attribute = static_cast<KMemoryBlockDisableMergeAttribute>(m_disable_merge_attribute | set_mask); + } + if (clear_mask != 0) { + m_disable_merge_attribute = static_cast<KMemoryBlockDisableMergeAttribute>(m_disable_merge_attribute & ~clear_mask); + } + } + + constexpr void UpdateAttribute(u32 mask, u32 attr) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT((mask & KMemoryAttribute_IpcLocked) == 0); + MESOSPHERE_ASSERT((mask & KMemoryAttribute_DeviceShared) == 0); + + m_attribute = static_cast<KMemoryAttribute>((m_attribute & ~mask) | attr); + } + + constexpr void Split(KMemoryBlock *block, KProcessAddress addr) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(this->GetAddress() < addr); + MESOSPHERE_ASSERT(this->Contains(addr)); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(addr), PageSize)); + + block->m_address = m_address; + block->m_num_pages = (addr - this->GetAddress()) / PageSize; + block->m_memory_state = m_memory_state; + block->m_ipc_lock_count = m_ipc_lock_count; + block->m_device_use_count = m_device_use_count; + block->m_permission = m_permission; + block->m_original_permission = m_original_permission; + block->m_attribute = m_attribute; + block->m_disable_merge_attribute = static_cast<KMemoryBlockDisableMergeAttribute>(m_disable_merge_attribute & KMemoryBlockDisableMergeAttribute_AllLeft); + block->m_ipc_disable_merge_count = m_ipc_disable_merge_count; + block->m_device_disable_merge_left_count = m_device_disable_merge_left_count; + block->m_device_disable_merge_right_count = 0; + + m_address = addr; + m_num_pages -= block->m_num_pages; + + m_ipc_disable_merge_count = 0; + m_device_disable_merge_left_count = 0; + m_disable_merge_attribute = static_cast<KMemoryBlockDisableMergeAttribute>(m_disable_merge_attribute & KMemoryBlockDisableMergeAttribute_AllRight); + } + + constexpr void UpdateDeviceDisableMergeStateForShareLeft(KMemoryPermission new_perm, bool left, bool right) { + /* New permission/right aren't used. */ + MESOSPHERE_UNUSED(new_perm, right); + + if (left) { + m_disable_merge_attribute = static_cast<KMemoryBlockDisableMergeAttribute>(m_disable_merge_attribute | KMemoryBlockDisableMergeAttribute_DeviceLeft); + const u16 new_device_disable_merge_left_count = ++m_device_disable_merge_left_count; + MESOSPHERE_ABORT_UNLESS(new_device_disable_merge_left_count > 0); + } + } + + constexpr void UpdateDeviceDisableMergeStateForShareRight(KMemoryPermission new_perm, bool left, bool right) { + /* New permission/left aren't used. */ + MESOSPHERE_UNUSED(new_perm, left); + + if (right) { + m_disable_merge_attribute = static_cast<KMemoryBlockDisableMergeAttribute>(m_disable_merge_attribute | KMemoryBlockDisableMergeAttribute_DeviceRight); + const u16 new_device_disable_merge_right_count = ++m_device_disable_merge_right_count; + MESOSPHERE_ABORT_UNLESS(new_device_disable_merge_right_count > 0); + } + } + + constexpr void UpdateDeviceDisableMergeStateForShare(KMemoryPermission new_perm, bool left, bool right) { + this->UpdateDeviceDisableMergeStateForShareLeft(new_perm, left, right); + this->UpdateDeviceDisableMergeStateForShareRight(new_perm, left, right); + } + + constexpr void ShareToDevice(KMemoryPermission new_perm, bool left, bool right) { + /* New permission isn't used. */ + MESOSPHERE_UNUSED(new_perm); + + /* We must either be shared or have a zero lock count. */ + MESOSPHERE_ASSERT((m_attribute & KMemoryAttribute_DeviceShared) == KMemoryAttribute_DeviceShared || m_device_use_count == 0); + + /* Share. */ + const u16 new_count = ++m_device_use_count; + MESOSPHERE_ABORT_UNLESS(new_count > 0); + + m_attribute = static_cast<KMemoryAttribute>(m_attribute | KMemoryAttribute_DeviceShared); + + this->UpdateDeviceDisableMergeStateForShare(new_perm, left, right); + } + + constexpr void UpdateDeviceDisableMergeStateForUnshareLeft(KMemoryPermission new_perm, bool left, bool right) { + /* New permission/right aren't used. */ + MESOSPHERE_UNUSED(new_perm, right); + + if (left) { + if (!m_device_disable_merge_left_count) { + return; + } + --m_device_disable_merge_left_count; + } + + m_device_disable_merge_left_count = std::min(m_device_disable_merge_left_count, m_device_use_count); + + if (m_device_disable_merge_left_count == 0) { + m_disable_merge_attribute = static_cast<KMemoryBlockDisableMergeAttribute>(m_disable_merge_attribute & ~KMemoryBlockDisableMergeAttribute_DeviceLeft); + } + } + + constexpr void UpdateDeviceDisableMergeStateForUnshareRight(KMemoryPermission new_perm, bool left, bool right) { + /* New permission/left aren't used. */ + MESOSPHERE_UNUSED(new_perm, left); + + if (right) { + const u16 old_device_disable_merge_right_count = m_device_disable_merge_right_count--; + MESOSPHERE_ASSERT(old_device_disable_merge_right_count > 0); + if (old_device_disable_merge_right_count == 1) { + m_disable_merge_attribute = static_cast<KMemoryBlockDisableMergeAttribute>(m_disable_merge_attribute & ~KMemoryBlockDisableMergeAttribute_DeviceRight); + } + } + } + + constexpr void UpdateDeviceDisableMergeStateForUnshare(KMemoryPermission new_perm, bool left, bool right) { + this->UpdateDeviceDisableMergeStateForUnshareLeft(new_perm, left, right); + this->UpdateDeviceDisableMergeStateForUnshareRight(new_perm, left, right); + } + + constexpr void UnshareToDevice(KMemoryPermission new_perm, bool left, bool right) { + /* New permission isn't used. */ + MESOSPHERE_UNUSED(new_perm); + + /* We must be shared. */ + MESOSPHERE_ASSERT((m_attribute & KMemoryAttribute_DeviceShared) == KMemoryAttribute_DeviceShared); + + /* Unhare. */ + const u16 old_count = m_device_use_count--; + MESOSPHERE_ABORT_UNLESS(old_count > 0); + + if (old_count == 1) { + m_attribute = static_cast<KMemoryAttribute>(m_attribute & ~KMemoryAttribute_DeviceShared); + } + + this->UpdateDeviceDisableMergeStateForUnshare(new_perm, left, right); + } + + constexpr void UnshareToDeviceRight(KMemoryPermission new_perm, bool left, bool right) { + /* New permission isn't used. */ + MESOSPHERE_UNUSED(new_perm); + + /* We must be shared. */ + MESOSPHERE_ASSERT((m_attribute & KMemoryAttribute_DeviceShared) == KMemoryAttribute_DeviceShared); + + /* Unhare. */ + const u16 old_count = m_device_use_count--; + MESOSPHERE_ABORT_UNLESS(old_count > 0); + + if (old_count == 1) { + m_attribute = static_cast<KMemoryAttribute>(m_attribute & ~KMemoryAttribute_DeviceShared); + } + + this->UpdateDeviceDisableMergeStateForUnshareRight(new_perm, left, right); + } + + constexpr void LockForIpc(KMemoryPermission new_perm, bool left, bool right) { + /* We must either be locked or have a zero lock count. */ + MESOSPHERE_ASSERT((m_attribute & KMemoryAttribute_IpcLocked) == KMemoryAttribute_IpcLocked || m_ipc_lock_count == 0); + + /* Lock. */ + const u16 new_lock_count = ++m_ipc_lock_count; + MESOSPHERE_ABORT_UNLESS(new_lock_count > 0); + + /* If this is our first lock, update our permissions. */ + if (new_lock_count == 1) { + MESOSPHERE_ASSERT(m_original_permission == KMemoryPermission_None); + MESOSPHERE_ASSERT((m_permission | new_perm | KMemoryPermission_NotMapped) == (m_permission | KMemoryPermission_NotMapped)); + MESOSPHERE_ASSERT((m_permission & KMemoryPermission_UserExecute) != KMemoryPermission_UserExecute || (new_perm == KMemoryPermission_UserRead)); + m_original_permission = m_permission; + m_permission = static_cast<KMemoryPermission>((new_perm & KMemoryPermission_IpcLockChangeMask) | (m_original_permission & ~KMemoryPermission_IpcLockChangeMask)); + } + m_attribute = static_cast<KMemoryAttribute>(m_attribute | KMemoryAttribute_IpcLocked); + + if (left) { + m_disable_merge_attribute = static_cast<KMemoryBlockDisableMergeAttribute>(m_disable_merge_attribute | KMemoryBlockDisableMergeAttribute_IpcLeft); + const u16 new_ipc_disable_merge_count = ++m_ipc_disable_merge_count; + MESOSPHERE_ABORT_UNLESS(new_ipc_disable_merge_count > 0); + } + MESOSPHERE_UNUSED(right); + } + + constexpr void UnlockForIpc(KMemoryPermission new_perm, bool left, bool right) { + /* New permission isn't used. */ + MESOSPHERE_UNUSED(new_perm); + + /* We must be locked. */ + MESOSPHERE_ASSERT((m_attribute & KMemoryAttribute_IpcLocked) == KMemoryAttribute_IpcLocked); + + /* Unlock. */ + const u16 old_lock_count = m_ipc_lock_count--; + MESOSPHERE_ABORT_UNLESS(old_lock_count > 0); + + /* If this is our last unlock, update our permissions. */ + if (old_lock_count == 1) { + MESOSPHERE_ASSERT(m_original_permission != KMemoryPermission_None); + m_permission = m_original_permission; + m_original_permission = KMemoryPermission_None; + m_attribute = static_cast<KMemoryAttribute>(m_attribute & ~KMemoryAttribute_IpcLocked); + } + + if (left) { + const u16 old_ipc_disable_merge_count = m_ipc_disable_merge_count--; + MESOSPHERE_ASSERT(old_ipc_disable_merge_count > 0); + if (old_ipc_disable_merge_count == 1) { + m_disable_merge_attribute = static_cast<KMemoryBlockDisableMergeAttribute>(m_disable_merge_attribute & ~KMemoryBlockDisableMergeAttribute_IpcLeft); + } + } + MESOSPHERE_UNUSED(right); + } + + constexpr KMemoryBlockDisableMergeAttribute GetDisableMergeAttribute() const { + return m_disable_merge_attribute; + } + }; + static_assert(std::is_trivially_destructible<KMemoryBlock>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_memory_block_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_memory_block_manager.hpp new file mode 100644 index 00000000..f2888ec6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_memory_block_manager.hpp @@ -0,0 +1,138 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_memory_block.hpp> +#include <mesosphere/kern_k_page_table_manager.hpp> + +namespace ams::kern { + + class KMemoryBlockManagerUpdateAllocator { + public: + static constexpr size_t MaxBlocks = 2; + private: + KMemoryBlock *m_blocks[MaxBlocks]; + size_t m_index; + KMemoryBlockSlabManager *m_slab_manager; + private: + ALWAYS_INLINE Result Initialize(size_t num_blocks) { + /* Check num blocks. */ + MESOSPHERE_ASSERT(num_blocks <= MaxBlocks); + + /* Set index. */ + m_index = MaxBlocks - num_blocks; + + /* Allocate the blocks. */ + for (size_t i = 0; i < num_blocks && i < MaxBlocks; ++i) { + m_blocks[m_index + i] = m_slab_manager->Allocate(); + R_UNLESS(m_blocks[m_index + i] != nullptr, svc::ResultOutOfResource()); + } + + R_SUCCEED(); + } + public: + KMemoryBlockManagerUpdateAllocator(Result *out_result, KMemoryBlockSlabManager *sm, size_t num_blocks = MaxBlocks) : m_blocks(), m_index(MaxBlocks), m_slab_manager(sm) { + *out_result = this->Initialize(num_blocks); + } + + ~KMemoryBlockManagerUpdateAllocator() { + for (const auto &block : m_blocks) { + if (block != nullptr) { + m_slab_manager->Free(block); + } + } + } + + KMemoryBlock *Allocate() { + MESOSPHERE_ABORT_UNLESS(m_index < MaxBlocks); + MESOSPHERE_ABORT_UNLESS(m_blocks[m_index] != nullptr); + KMemoryBlock *block = nullptr; + std::swap(block, m_blocks[m_index++]); + return block; + } + + void Free(KMemoryBlock *block) { + MESOSPHERE_ABORT_UNLESS(m_index <= MaxBlocks); + MESOSPHERE_ABORT_UNLESS(block != nullptr); + if (m_index == 0) { + m_slab_manager->Free(block); + } else { + m_blocks[--m_index] = block; + } + } + }; + + class KMemoryBlockManager { + public: + using MemoryBlockTree = util::IntrusiveRedBlackTreeBaseTraits<KMemoryBlock>::TreeType<KMemoryBlock>; + using MemoryBlockLockFunction = void (KMemoryBlock::*)(KMemoryPermission new_perm, bool left, bool right); + using iterator = MemoryBlockTree::iterator; + using const_iterator = MemoryBlockTree::const_iterator; + private: + MemoryBlockTree m_memory_block_tree; + KProcessAddress m_start_address; + KProcessAddress m_end_address; + private: + void CoalesceForUpdate(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages); + public: + constexpr explicit KMemoryBlockManager(util::ConstantInitializeTag) : m_memory_block_tree(), m_start_address(Null<KProcessAddress>), m_end_address(Null<KProcessAddress>) { /* ... */ } + + explicit KMemoryBlockManager() { /* ... */ } + + iterator end() { return m_memory_block_tree.end(); } + const_iterator end() const { return m_memory_block_tree.end(); } + const_iterator cend() const { return m_memory_block_tree.cend(); } + + Result Initialize(KProcessAddress st, KProcessAddress nd, KMemoryBlockSlabManager *slab_manager); + void Finalize(KMemoryBlockSlabManager *slab_manager); + + static bool GetRegionForFindFreeArea(KProcessAddress *out_start, KProcessAddress *out_end, KProcessAddress region_start, size_t region_num_pages, size_t num_pages, size_t alignment, size_t offset, size_t guard_pages); + KProcessAddress FindFreeArea(KProcessAddress region_start, size_t region_num_pages, size_t num_pages, size_t alignment, size_t offset, size_t guard_pages) const; + + void Update(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, KMemoryState state, KMemoryPermission perm, KMemoryAttribute attr, KMemoryBlockDisableMergeAttribute set_disable_attr, KMemoryBlockDisableMergeAttribute clear_disable_attr); + void UpdateLock(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, MemoryBlockLockFunction lock_func, KMemoryPermission perm); + + void UpdateIfMatch(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, KMemoryState test_state, KMemoryPermission test_perm, KMemoryAttribute test_attr, KMemoryState state, KMemoryPermission perm, KMemoryAttribute attr, KMemoryBlockDisableMergeAttribute set_disable_attr, KMemoryBlockDisableMergeAttribute clear_disable_attr); + + void UpdateAttribute(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, u32 mask, u32 attr); + + iterator FindIterator(KProcessAddress address) const { + return m_memory_block_tree.find(KMemoryBlock(util::ConstantInitialize, address, 1, KMemoryState_Free, KMemoryPermission_None, KMemoryAttribute_None)); + } + + const KMemoryBlock *FindBlock(KProcessAddress address) const { + if (const_iterator it = this->FindIterator(address); it != m_memory_block_tree.end()) { + return std::addressof(*it); + } + + return nullptr; + } + + /* Debug. */ + bool CheckState() const; + void DumpBlocks() const; + }; + + class KScopedMemoryBlockManagerAuditor { + private: + KMemoryBlockManager *m_manager; + public: + explicit ALWAYS_INLINE KScopedMemoryBlockManagerAuditor(KMemoryBlockManager *m) : m_manager(m) { MESOSPHERE_AUDIT(m_manager->CheckState()); } + explicit ALWAYS_INLINE KScopedMemoryBlockManagerAuditor(KMemoryBlockManager &m) : KScopedMemoryBlockManagerAuditor(std::addressof(m)) { /* ... */ } + ALWAYS_INLINE ~KScopedMemoryBlockManagerAuditor() { MESOSPHERE_AUDIT(m_manager->CheckState()); } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_memory_layout.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_memory_layout.hpp new file mode 100644 index 00000000..f8ed6ad0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_memory_layout.hpp @@ -0,0 +1,245 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/init/kern_init_page_table_select.hpp> +#include <mesosphere/kern_k_memory_region.hpp> + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + #include <mesosphere/board/nintendo/nx/kern_k_memory_layout.hpp> +#elif defined(ATMOSPHERE_BOARD_QEMU_VIRT) + #include <mesosphere/board/qemu/virt/kern_k_memory_layout.hpp> +#else + #error "Unknown board for KMemoryLayout" +#endif + +namespace ams::kern { + + constexpr size_t KernelAslrAlignment = 2_MB; + constexpr size_t KernelVirtualAddressSpaceWidth = size_t(1ul) << 39ul; + constexpr size_t KernelPhysicalAddressSpaceWidth = size_t(1ul) << 48ul; + + constexpr size_t KernelVirtualAddressSpaceBase = 0ul - KernelVirtualAddressSpaceWidth; + constexpr size_t KernelVirtualAddressSpaceEnd = KernelVirtualAddressSpaceBase + (KernelVirtualAddressSpaceWidth - KernelAslrAlignment); + constexpr size_t KernelVirtualAddressSpaceLast = KernelVirtualAddressSpaceEnd - 1ul; + constexpr size_t KernelVirtualAddressSpaceSize = KernelVirtualAddressSpaceEnd - KernelVirtualAddressSpaceBase; + + constexpr size_t KernelPhysicalAddressSpaceBase = 0ul; + constexpr size_t KernelPhysicalAddressSpaceEnd = KernelPhysicalAddressSpaceBase + KernelPhysicalAddressSpaceWidth; + constexpr size_t KernelPhysicalAddressSpaceLast = KernelPhysicalAddressSpaceEnd - 1ul; + constexpr size_t KernelPhysicalAddressSpaceSize = KernelPhysicalAddressSpaceEnd - KernelPhysicalAddressSpaceBase; + + constexpr size_t KernelPageTableHeapSize = init::KInitialPageTable::GetMaximumOverheadSize(kern::MainMemorySizeMax); + constexpr size_t KernelInitialPageHeapSize = 128_KB; + + constexpr size_t KernelSlabHeapDataSize = 5_MB; + constexpr size_t KernelSlabHeapGapsSizeMax = 2_MB - 64_KB; + constexpr size_t KernelSlabHeapSize = KernelSlabHeapDataSize + KernelSlabHeapGapsSizeMax; + + /* NOTE: This is calculated from KThread slab counts, assuming KThread size <= 0x800. */ + constexpr size_t KernelPageBufferHeapSize = 0x3E0000; + constexpr size_t KernelSlabHeapAdditionalSize = 0x148000; + constexpr size_t KernelPageBufferAdditionalSize = 0x33C000; + + constexpr size_t KernelResourceSize = KernelPageTableHeapSize + KernelInitialPageHeapSize + KernelSlabHeapSize + KernelPageBufferHeapSize; + + class KMemoryLayout { + private: + static constinit inline uintptr_t s_linear_phys_to_virt_diff; + static constinit inline uintptr_t s_linear_virt_to_phys_diff; + static constinit inline KMemoryRegionTree s_virtual_tree; + static constinit inline KMemoryRegionTree s_physical_tree; + static constinit inline KMemoryRegionTree s_virtual_linear_tree; + static constinit inline KMemoryRegionTree s_physical_linear_tree; + private: + template<typename AddressType> requires IsKTypedAddress<AddressType> + static ALWAYS_INLINE bool IsTypedAddress(const KMemoryRegion *®ion, AddressType address, KMemoryRegionTree &tree, KMemoryRegionType type) { + /* Check if the cached region already contains the address. */ + if (region != nullptr && region->Contains(GetInteger(address))) { + return true; + } + + /* Find the containing region, and update the cache. */ + if (const KMemoryRegion *found = tree.Find(GetInteger(address)); found != nullptr && found->IsDerivedFrom(type)) { + region = found; + return true; + } else { + return false; + } + } + + template<typename AddressType> requires IsKTypedAddress<AddressType> + static ALWAYS_INLINE bool IsTypedAddress(const KMemoryRegion *®ion, AddressType address, size_t size, KMemoryRegionTree &tree, KMemoryRegionType type) { + /* Get the end of the checked region. */ + const uintptr_t last_address = GetInteger(address) + size - 1; + + /* Walk the tree to verify the region is correct. */ + const KMemoryRegion *cur = (region != nullptr && region->Contains(GetInteger(address))) ? region : tree.Find(GetInteger(address)); + while (cur != nullptr && cur->IsDerivedFrom(type)) { + if (last_address <= cur->GetLastAddress()) { + region = cur; + return true; + } + + cur = cur->GetNext(); + } + return false; + } + + template<typename AddressType> requires IsKTypedAddress<AddressType> + static ALWAYS_INLINE const KMemoryRegion *Find(AddressType address, const KMemoryRegionTree &tree) { + return tree.Find(GetInteger(address)); + } + + static ALWAYS_INLINE KMemoryRegion &Dereference(KMemoryRegion *region) { + MESOSPHERE_INIT_ABORT_UNLESS(region != nullptr); + return *region; + } + + static ALWAYS_INLINE const KMemoryRegion &Dereference(const KMemoryRegion *region) { + MESOSPHERE_INIT_ABORT_UNLESS(region != nullptr); + return *region; + } + + static ALWAYS_INLINE KVirtualAddress GetStackTopAddress(s32 core_id, KMemoryRegionType type) { + const auto ®ion = Dereference(GetVirtualMemoryRegionTree().FindByTypeAndAttribute(type, static_cast<u32>(core_id))); + MESOSPHERE_INIT_ABORT_UNLESS(region.GetEndAddress() != 0); + return region.GetEndAddress(); + } + public: + static ALWAYS_INLINE KMemoryRegionTree &GetVirtualMemoryRegionTree() { return s_virtual_tree; } + static ALWAYS_INLINE KMemoryRegionTree &GetPhysicalMemoryRegionTree() { return s_physical_tree; } + static ALWAYS_INLINE KMemoryRegionTree &GetVirtualLinearMemoryRegionTree() { return s_virtual_linear_tree; } + static ALWAYS_INLINE KMemoryRegionTree &GetPhysicalLinearMemoryRegionTree() { return s_physical_linear_tree; } + + static ALWAYS_INLINE KVirtualAddress GetLinearVirtualAddress(KPhysicalAddress address) { return GetInteger(address) + s_linear_phys_to_virt_diff; } + static ALWAYS_INLINE KPhysicalAddress GetLinearPhysicalAddress(KVirtualAddress address) { return GetInteger(address) + s_linear_virt_to_phys_diff; } + + static MESOSPHERE_NOINLINE_IF_DEBUG const KMemoryRegion *Find(KVirtualAddress address) { return Find(address, GetVirtualMemoryRegionTree()); } + static MESOSPHERE_NOINLINE_IF_DEBUG const KMemoryRegion *Find(KPhysicalAddress address) { return Find(address, GetPhysicalMemoryRegionTree()); } + + static MESOSPHERE_NOINLINE_IF_DEBUG const KMemoryRegion *FindLinear(KVirtualAddress address) { return Find(address, GetVirtualLinearMemoryRegionTree()); } + static MESOSPHERE_NOINLINE_IF_DEBUG const KMemoryRegion *FindLinear(KPhysicalAddress address) { return Find(address, GetPhysicalLinearMemoryRegionTree()); } + + static MESOSPHERE_NOINLINE_IF_DEBUG KVirtualAddress GetMainStackTopAddress(s32 core_id) { return GetStackTopAddress(core_id, KMemoryRegionType_KernelMiscMainStack); } + static MESOSPHERE_NOINLINE_IF_DEBUG KVirtualAddress GetIdleStackTopAddress(s32 core_id) { return GetStackTopAddress(core_id, KMemoryRegionType_KernelMiscIdleStack); } + static MESOSPHERE_NOINLINE_IF_DEBUG KVirtualAddress GetExceptionStackTopAddress(s32 core_id) { return GetStackTopAddress(core_id, KMemoryRegionType_KernelMiscExceptionStack); } + + static MESOSPHERE_NOINLINE_IF_DEBUG KVirtualAddress GetSlabRegionAddress() { return Dereference(GetVirtualMemoryRegionTree().FindByType(KMemoryRegionType_KernelSlab)).GetAddress(); } + + static MESOSPHERE_NOINLINE_IF_DEBUG const KMemoryRegion &GetDeviceRegion(KMemoryRegionType type) { return Dereference(GetPhysicalMemoryRegionTree().FindFirstDerived(type)); } + static KPhysicalAddress GetDevicePhysicalAddress(KMemoryRegionType type) { return GetDeviceRegion(type).GetAddress(); } + static KVirtualAddress GetDeviceVirtualAddress(KMemoryRegionType type) { return GetDeviceRegion(type).GetPairAddress(); } + + static MESOSPHERE_NOINLINE_IF_DEBUG const KMemoryRegion &GetPoolManagementRegion() { return Dereference(GetVirtualMemoryRegionTree().FindByType(KMemoryRegionType_VirtualDramPoolManagement)); } + static MESOSPHERE_NOINLINE_IF_DEBUG const KMemoryRegion &GetPageTableHeapRegion() { return Dereference(GetVirtualMemoryRegionTree().FindByType(KMemoryRegionType_VirtualDramKernelPtHeap)); } + static MESOSPHERE_NOINLINE_IF_DEBUG const KMemoryRegion &GetKernelStackRegion() { return Dereference(GetVirtualMemoryRegionTree().FindByType(KMemoryRegionType_KernelStack)); } + static MESOSPHERE_NOINLINE_IF_DEBUG const KMemoryRegion &GetTempRegion() { return Dereference(GetVirtualMemoryRegionTree().FindByType(KMemoryRegionType_KernelTemp)); } + static MESOSPHERE_NOINLINE_IF_DEBUG const KMemoryRegion &GetSlabRegion() { return Dereference(GetVirtualMemoryRegionTree().FindByType(KMemoryRegionType_KernelSlab)); } + + static MESOSPHERE_NOINLINE_IF_DEBUG const KMemoryRegion &GetKernelTraceBufferRegion() { return Dereference(GetVirtualLinearMemoryRegionTree().FindByType(KMemoryRegionType_VirtualDramKernelTraceBuffer)); } + + static MESOSPHERE_NOINLINE_IF_DEBUG const KMemoryRegion &GetSecureAppletMemoryRegion() { return Dereference(GetVirtualMemoryRegionTree().FindByType(KMemoryRegionType_VirtualDramKernelSecureAppletMemory)); } + + static MESOSPHERE_NOINLINE_IF_DEBUG const KMemoryRegion &GetVirtualLinearRegion(KVirtualAddress address) { return Dereference(FindLinear(address)); } + static MESOSPHERE_NOINLINE_IF_DEBUG const KMemoryRegion &GetPhysicalLinearRegion(KPhysicalAddress address) { return Dereference(FindLinear(address)); } + + static MESOSPHERE_NOINLINE_IF_DEBUG const KMemoryRegion *GetPhysicalKernelTraceBufferRegion() { return GetPhysicalMemoryRegionTree().FindFirstDerived(KMemoryRegionType_KernelTraceBuffer); } + static MESOSPHERE_NOINLINE_IF_DEBUG const KMemoryRegion *GetPhysicalOnMemoryBootImageRegion() { return GetPhysicalMemoryRegionTree().FindFirstDerived(KMemoryRegionType_OnMemoryBootImage); } + static MESOSPHERE_NOINLINE_IF_DEBUG const KMemoryRegion *GetPhysicalDTBRegion() { return GetPhysicalMemoryRegionTree().FindFirstDerived(KMemoryRegionType_DTB); } + + static MESOSPHERE_NOINLINE_IF_DEBUG bool IsHeapPhysicalAddress(const KMemoryRegion *®ion, KPhysicalAddress address) { return IsTypedAddress(region, address, GetPhysicalLinearMemoryRegionTree(), KMemoryRegionType_DramUserPool); } + static MESOSPHERE_NOINLINE_IF_DEBUG bool IsHeapVirtualAddress(const KMemoryRegion *®ion, KVirtualAddress address) { return IsTypedAddress(region, address, GetVirtualLinearMemoryRegionTree(), KMemoryRegionType_VirtualDramUserPool); } + + static MESOSPHERE_NOINLINE_IF_DEBUG bool IsHeapPhysicalAddress(const KMemoryRegion *®ion, KPhysicalAddress address, size_t size) { return IsTypedAddress(region, address, size, GetPhysicalLinearMemoryRegionTree(), KMemoryRegionType_DramUserPool); } + static MESOSPHERE_NOINLINE_IF_DEBUG bool IsHeapVirtualAddress(const KMemoryRegion *®ion, KVirtualAddress address, size_t size) { return IsTypedAddress(region, address, size, GetVirtualLinearMemoryRegionTree(), KMemoryRegionType_VirtualDramUserPool); } + + static MESOSPHERE_NOINLINE_IF_DEBUG bool IsLinearMappedPhysicalAddress(const KMemoryRegion *®ion, KPhysicalAddress address) { return IsTypedAddress(region, address, GetPhysicalLinearMemoryRegionTree(), static_cast<KMemoryRegionType>(KMemoryRegionAttr_LinearMapped)); } + static MESOSPHERE_NOINLINE_IF_DEBUG bool IsLinearMappedPhysicalAddress(const KMemoryRegion *®ion, KPhysicalAddress address, size_t size) { return IsTypedAddress(region, address, size, GetPhysicalLinearMemoryRegionTree(), static_cast<KMemoryRegionType>(KMemoryRegionAttr_LinearMapped)); } + + static std::tuple<size_t, size_t> GetTotalAndKernelMemorySizes() { + size_t total_size = 0, kernel_size = 0; + for (const auto ®ion : GetPhysicalMemoryRegionTree()) { + if (region.IsDerivedFrom(KMemoryRegionType_Dram)) { + total_size += region.GetSize(); + if (!region.IsDerivedFrom(KMemoryRegionType_DramUserPool)) { + kernel_size += region.GetSize(); + } + } + } + return std::make_tuple(total_size, kernel_size); + } + + static void InitializeLinearMemoryAddresses(u64 phys_to_virt_diff) { + /* Set static differences. */ + s_linear_phys_to_virt_diff = phys_to_virt_diff; + s_linear_virt_to_phys_diff = -phys_to_virt_diff; + } + + static void InitializeLinearMemoryRegionTrees(); + + static size_t GetResourceRegionSizeForInit(bool use_extra_resource); + + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetKernelRegionExtents() { return GetVirtualMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_Kernel); } + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetKernelCodeRegionExtents() { return GetVirtualMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_KernelCode); } + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetKernelStackRegionExtents() { return GetVirtualMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_KernelStack); } + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetKernelMiscRegionExtents() { return GetVirtualMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_KernelMisc); } + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetKernelSlabRegionExtents() { return GetVirtualMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_KernelSlab); } + + + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetLinearRegionPhysicalExtents() { return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionAttr_LinearMapped); } + + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetLinearRegionVirtualExtents() { + const auto physical = GetLinearRegionPhysicalExtents(); + return KMemoryRegion(GetInteger(GetLinearVirtualAddress(physical.GetAddress())), GetInteger(GetLinearVirtualAddress(physical.GetLastAddress())), 0, KMemoryRegionType_None); + } + + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetMainMemoryPhysicalExtents() { return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_Dram); } + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetCarveoutRegionExtents() { return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionAttr_CarveoutProtected); } + + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetKernelRegionPhysicalExtents() { return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_DramKernelBase); } + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetKernelCodeRegionPhysicalExtents() { return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_DramKernelCode); } + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetKernelSlabRegionPhysicalExtents() { return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_DramKernelSlab); } + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetKernelSecureAppletMemoryRegionPhysicalExtents() { return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_DramKernelSecureAppletMemory); } + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetKernelPageTableHeapRegionPhysicalExtents() { return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_DramKernelPtHeap); } + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetKernelInitPageTableRegionPhysicalExtents() { return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_DramKernelInitPt); } + + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetKernelPoolPartitionRegionPhysicalExtents() { return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_DramPoolPartition); } + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetKernelPoolManagementRegionPhysicalExtents() { return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_DramPoolManagement); } + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetKernelSystemPoolRegionPhysicalExtents() { return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_DramSystemPool); } + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetKernelSystemNonSecurePoolRegionPhysicalExtents() { return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_DramSystemNonSecurePool); } + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetKernelAppletPoolRegionPhysicalExtents() { return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_DramAppletPool); } + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetKernelApplicationPoolRegionPhysicalExtents() { return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_DramApplicationPool); } + + static MESOSPHERE_NOINLINE_IF_DEBUG bool HasKernelSystemNonSecurePoolRegion() { return GetPhysicalMemoryRegionTree().FindFirstDerived(KMemoryRegionType_DramSystemNonSecurePool) != nullptr; } + static MESOSPHERE_NOINLINE_IF_DEBUG bool HasKernelAppletPoolRegion() { return GetPhysicalMemoryRegionTree().FindFirstDerived(KMemoryRegionType_DramAppletPool) != nullptr; } + static MESOSPHERE_NOINLINE_IF_DEBUG bool HasKernelApplicationPoolRegion() { return GetPhysicalMemoryRegionTree().FindFirstDerived(KMemoryRegionType_DramApplicationPool) != nullptr; } + + static MESOSPHERE_NOINLINE_IF_DEBUG auto GetKernelTraceBufferRegionPhysicalExtents() { return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_KernelTraceBuffer); } + }; + + namespace init { + + /* These should be generic, regardless of board. */ + void SetupPoolPartitionMemoryRegions(); + + /* These may be implemented in a board-specific manner. */ + void SetupDevicePhysicalMemoryRegions(); + void SetupDramPhysicalMemoryRegions(); + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_memory_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_memory_manager.hpp new file mode 100644 index 00000000..99ef1f6b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_memory_manager.hpp @@ -0,0 +1,329 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_light_lock.hpp> +#include <mesosphere/kern_k_memory_layout.hpp> +#include <mesosphere/kern_k_page_heap.hpp> + +namespace ams::kern { + + class KPageGroup; + + class KMemoryManager { + public: + enum Pool { + Pool_Application = 0, + Pool_Applet = 1, + Pool_System = 2, + Pool_SystemNonSecure = 3, + + Pool_Count, + + Pool_Shift = 4, + Pool_Mask = (0xF << Pool_Shift), + + /* Aliases. */ + Pool_Unsafe = Pool_Application, + Pool_Secure = Pool_System, + }; + + enum Direction { + Direction_FromFront = 0, + Direction_FromBack = 1, + + Direction_Shift = 0, + Direction_Mask = (0xF << Direction_Shift), + }; + + static constexpr size_t MaxManagerCount = 10; + private: + class Impl { + private: + using RefCount = u16; + public: + static size_t CalculateManagementOverheadSize(size_t region_size); + + static constexpr size_t CalculateOptimizedProcessOverheadSize(size_t region_size) { + return (util::AlignUp((region_size / PageSize), BITSIZEOF(u64)) / BITSIZEOF(u64)) * sizeof(u64); + } + private: + KPageHeap m_heap; + RefCount *m_page_reference_counts; + KVirtualAddress m_management_region; + Pool m_pool; + Impl *m_next; + Impl *m_prev; + public: + Impl() : m_heap(), m_page_reference_counts(), m_management_region(Null<KVirtualAddress>), m_pool(), m_next(), m_prev() { /* ... */ } + + size_t Initialize(KPhysicalAddress address, size_t size, KVirtualAddress management, KVirtualAddress management_end, Pool p); + + KPhysicalAddress AllocateBlock(s32 index, bool random) { return m_heap.AllocateBlock(index, random); } + KPhysicalAddress AllocateAligned(s32 index, size_t num_pages, size_t align_pages) { return m_heap.AllocateAligned(index, num_pages, align_pages); } + void Free(KPhysicalAddress addr, size_t num_pages) { m_heap.Free(addr, num_pages); } + + void SetInitialUsedHeapSize(size_t reserved_size) { m_heap.SetInitialUsedSize(reserved_size); } + + void InitializeOptimizedMemory() { std::memset(GetVoidPointer(m_management_region), 0, CalculateOptimizedProcessOverheadSize(m_heap.GetSize())); } + + void TrackUnoptimizedAllocation(KPhysicalAddress block, size_t num_pages); + void TrackOptimizedAllocation(KPhysicalAddress block, size_t num_pages); + + bool ProcessOptimizedAllocation(KPhysicalAddress block, size_t num_pages, u8 fill_pattern); + + constexpr Pool GetPool() const { return m_pool; } + constexpr size_t GetSize() const { return m_heap.GetSize(); } + constexpr KPhysicalAddress GetEndAddress() const { return m_heap.GetEndAddress(); } + + size_t GetFreeSize() const { return m_heap.GetFreeSize(); } + + void DumpFreeList() const { return m_heap.DumpFreeList(); } + + constexpr size_t GetPageOffset(KPhysicalAddress address) const { return m_heap.GetPageOffset(address); } + constexpr size_t GetPageOffsetToEnd(KPhysicalAddress address) const { return m_heap.GetPageOffsetToEnd(address); } + + constexpr void SetNext(Impl *n) { m_next = n; } + constexpr void SetPrev(Impl *n) { m_prev = n; } + constexpr Impl *GetNext() const { return m_next; } + constexpr Impl *GetPrev() const { return m_prev; } + + void OpenFirst(KPhysicalAddress address, size_t num_pages) { + size_t index = this->GetPageOffset(address); + const size_t end = index + num_pages; + while (index < end) { + const RefCount ref_count = (++m_page_reference_counts[index]); + MESOSPHERE_ABORT_UNLESS(ref_count == 1); + + index++; + } + } + + void Open(KPhysicalAddress address, size_t num_pages) { + size_t index = this->GetPageOffset(address); + const size_t end = index + num_pages; + while (index < end) { + const RefCount ref_count = (++m_page_reference_counts[index]); + MESOSPHERE_ABORT_UNLESS(ref_count > 1); + + index++; + } + } + + void Close(KPhysicalAddress address, size_t num_pages) { + size_t index = this->GetPageOffset(address); + const size_t end = index + num_pages; + + size_t free_start = 0; + size_t free_count = 0; + while (index < end) { + MESOSPHERE_ABORT_UNLESS(m_page_reference_counts[index] > 0); + const RefCount ref_count = (--m_page_reference_counts[index]); + + /* Keep track of how many zero refcounts we see in a row, to minimize calls to free. */ + if (ref_count == 0) { + if (free_count > 0) { + free_count++; + } else { + free_start = index; + free_count = 1; + } + } else { + if (free_count > 0) { + this->Free(m_heap.GetAddress() + free_start * PageSize, free_count); + free_count = 0; + } + } + + index++; + } + + if (free_count > 0) { + this->Free(m_heap.GetAddress() + free_start * PageSize, free_count); + } + } + }; + private: + KLightLock m_pool_locks[Pool_Count]; + Impl *m_pool_managers_head[Pool_Count]; + Impl *m_pool_managers_tail[Pool_Count]; + Impl m_managers[MaxManagerCount]; + size_t m_num_managers; + u64 m_optimized_process_ids[Pool_Count]; + bool m_has_optimized_process[Pool_Count]; + s32 m_min_heap_indexes[Pool_Count]; + private: + Impl &GetManager(KPhysicalAddress address) { + return m_managers[KMemoryLayout::GetPhysicalLinearRegion(address).GetAttributes()]; + } + + const Impl &GetManager(KPhysicalAddress address) const { + return m_managers[KMemoryLayout::GetPhysicalLinearRegion(address).GetAttributes()]; + } + + constexpr Impl *GetFirstManager(Pool pool, Direction dir) { + return dir == Direction_FromBack ? m_pool_managers_tail[pool] : m_pool_managers_head[pool]; + } + + constexpr Impl *GetNextManager(Impl *cur, Direction dir) { + if (dir == Direction_FromBack) { + return cur->GetPrev(); + } else { + return cur->GetNext(); + } + } + + Result AllocatePageGroupImpl(KPageGroup *out, size_t num_pages, Pool pool, Direction dir, bool unoptimized, bool random, s32 min_heap_index); + public: + KMemoryManager() + : m_pool_locks(), m_pool_managers_head(), m_pool_managers_tail(), m_managers(), m_num_managers(), m_optimized_process_ids(), m_has_optimized_process(), m_min_heap_indexes() + { + /* ... */ + } + + NOINLINE void Initialize(KVirtualAddress management_region, size_t management_region_size, const u32 *min_align_shifts); + + NOINLINE Result InitializeOptimizedMemory(u64 process_id, Pool pool); + NOINLINE void FinalizeOptimizedMemory(u64 process_id, Pool pool); + + NOINLINE KPhysicalAddress AllocateAndOpenContinuous(size_t num_pages, size_t align_pages, u32 option); + NOINLINE Result AllocateAndOpen(KPageGroup *out, size_t num_pages, size_t align_pages, u32 option); + NOINLINE Result AllocateForProcess(KPageGroup *out, size_t num_pages, u32 option, u64 process_id, u8 fill_pattern); + + Pool GetPool(KPhysicalAddress address) const { + return this->GetManager(address).GetPool(); + } + + void Open(KPhysicalAddress address, size_t num_pages) { + /* Repeatedly open references until we've done so for all pages. */ + while (num_pages) { + auto &manager = this->GetManager(address); + const size_t cur_pages = std::min(num_pages, manager.GetPageOffsetToEnd(address)); + + { + KScopedLightLock lk(m_pool_locks[manager.GetPool()]); + manager.Open(address, cur_pages); + } + + num_pages -= cur_pages; + address += cur_pages * PageSize; + } + } + + void OpenFirst(KPhysicalAddress address, size_t num_pages) { + /* Repeatedly open references until we've done so for all pages. */ + while (num_pages) { + auto &manager = this->GetManager(address); + const size_t cur_pages = std::min(num_pages, manager.GetPageOffsetToEnd(address)); + + { + KScopedLightLock lk(m_pool_locks[manager.GetPool()]); + manager.OpenFirst(address, cur_pages); + } + + num_pages -= cur_pages; + address += cur_pages * PageSize; + } + } + + void Close(KPhysicalAddress address, size_t num_pages) { + /* Repeatedly close references until we've done so for all pages. */ + while (num_pages) { + auto &manager = this->GetManager(address); + const size_t cur_pages = std::min(num_pages, manager.GetPageOffsetToEnd(address)); + + { + KScopedLightLock lk(m_pool_locks[manager.GetPool()]); + manager.Close(address, cur_pages); + } + + num_pages -= cur_pages; + address += cur_pages * PageSize; + } + } + + size_t GetSize() { + size_t total = 0; + for (size_t i = 0; i < m_num_managers; i++) { + total += m_managers[i].GetSize(); + } + return total; + } + + size_t GetSize(Pool pool) { + constexpr Direction GetSizeDirection = Direction_FromFront; + size_t total = 0; + for (auto *manager = this->GetFirstManager(pool, GetSizeDirection); manager != nullptr; manager = this->GetNextManager(manager, GetSizeDirection)) { + total += manager->GetSize(); + } + return total; + } + + size_t GetFreeSize() { + size_t total = 0; + for (size_t i = 0; i < m_num_managers; i++) { + KScopedLightLock lk(m_pool_locks[m_managers[i].GetPool()]); + total += m_managers[i].GetFreeSize(); + } + return total; + } + + size_t GetFreeSize(Pool pool) { + KScopedLightLock lk(m_pool_locks[pool]); + + constexpr Direction GetSizeDirection = Direction_FromFront; + size_t total = 0; + for (auto *manager = this->GetFirstManager(pool, GetSizeDirection); manager != nullptr; manager = this->GetNextManager(manager, GetSizeDirection)) { + total += manager->GetFreeSize(); + } + return total; + } + + void DumpFreeList(Pool pool) { + KScopedLightLock lk(m_pool_locks[pool]); + + constexpr Direction DumpDirection = Direction_FromFront; + for (auto *manager = this->GetFirstManager(pool, DumpDirection); manager != nullptr; manager = this->GetNextManager(manager, DumpDirection)) { + manager->DumpFreeList(); + } + } + + size_t GetMinimumAlignment(Pool pool) { + return KPageHeap::GetBlockSize(m_min_heap_indexes[pool]); + } + public: + static size_t CalculateManagementOverheadSize(size_t region_size) { + return Impl::CalculateManagementOverheadSize(region_size); + } + + static constexpr ALWAYS_INLINE u32 EncodeOption(Pool pool, Direction dir) { + return (pool << Pool_Shift) | (dir << Direction_Shift); + } + + static constexpr ALWAYS_INLINE Pool GetPool(u32 option) { + return static_cast<Pool>((option & Pool_Mask) >> Pool_Shift); + } + + static constexpr ALWAYS_INLINE Direction GetDirection(u32 option) { + return static_cast<Direction>((option & Direction_Mask) >> Direction_Shift); + } + + static constexpr ALWAYS_INLINE std::tuple<Pool, Direction> DecodeOption(u32 option) { + return std::make_tuple(GetPool(option), GetDirection(option)); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_memory_region.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_memory_region.hpp new file mode 100644 index 00000000..a905d8e7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_memory_region.hpp @@ -0,0 +1,314 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_memory_region_type.hpp> + +namespace ams::kern { + + class KMemoryRegionTree; + + class KMemoryRegion : public util::IntrusiveRedBlackTreeBaseNode<KMemoryRegion> { + NON_COPYABLE(KMemoryRegion); + NON_MOVEABLE(KMemoryRegion); + private: + friend class KMemoryRegionTree; + private: + uintptr_t m_address; + uintptr_t m_pair_address; + uintptr_t m_last_address; + u32 m_attributes; + u32 m_type_id; + public: + static constexpr ALWAYS_INLINE int Compare(const KMemoryRegion &lhs, const KMemoryRegion &rhs) { + if (lhs.GetAddress() < rhs.GetAddress()) { + return -1; + } else if (lhs.GetAddress() <= rhs.GetLastAddress()) { + return 0; + } else { + return 1; + } + } + public: + constexpr ALWAYS_INLINE KMemoryRegion() : util::IntrusiveRedBlackTreeBaseNode<KMemoryRegion>(util::ConstantInitialize), m_address(0), m_pair_address(0), m_last_address(0), m_attributes(0), m_type_id(0) { /* ... */ } + + ALWAYS_INLINE KMemoryRegion(uintptr_t a, size_t la, uintptr_t p, u32 r, u32 t) : + m_address(a), m_pair_address(p), m_last_address(la), m_attributes(r), m_type_id(t) + { + /* ... */ + } + ALWAYS_INLINE KMemoryRegion(uintptr_t a, size_t la, u32 r, u32 t) : KMemoryRegion(a, la, std::numeric_limits<uintptr_t>::max(), r, t) { /* ... */ } + private: + constexpr ALWAYS_INLINE void Reset(uintptr_t a, uintptr_t la, uintptr_t p, u32 r, u32 t) { + m_address = a; + m_pair_address = p; + m_last_address = la; + m_attributes = r; + m_type_id = t; + } + public: + constexpr ALWAYS_INLINE uintptr_t GetAddress() const { + return m_address; + } + + constexpr ALWAYS_INLINE uintptr_t GetPairAddress() const { + return m_pair_address; + } + + constexpr ALWAYS_INLINE uintptr_t GetLastAddress() const { + return m_last_address; + } + + constexpr ALWAYS_INLINE uintptr_t GetEndAddress() const { + return this->GetLastAddress() + 1; + } + + constexpr ALWAYS_INLINE size_t GetSize() const { + return this->GetEndAddress() - this->GetAddress(); + } + + constexpr ALWAYS_INLINE u32 GetAttributes() const { + return m_attributes; + } + + constexpr ALWAYS_INLINE u32 GetType() const { + return m_type_id; + } + + constexpr ALWAYS_INLINE void SetType(u32 type) { + MESOSPHERE_INIT_ABORT_UNLESS(this->CanDerive(type)); + m_type_id = type; + } + + constexpr ALWAYS_INLINE bool Contains(uintptr_t address) const { + MESOSPHERE_INIT_ABORT_UNLESS(this->GetEndAddress() != 0); + return this->GetAddress() <= address && address <= this->GetLastAddress(); + } + + constexpr ALWAYS_INLINE bool IsDerivedFrom(u32 type) const { + return (this->GetType() | type) == this->GetType(); + } + + constexpr ALWAYS_INLINE bool HasTypeAttribute(KMemoryRegionAttr attr) const { + return (this->GetType() | attr) == this->GetType(); + } + + constexpr ALWAYS_INLINE bool CanDerive(u32 type) const { + return (this->GetType() | type) == type; + } + + constexpr ALWAYS_INLINE void SetPairAddress(uintptr_t a) { + m_pair_address = a; + } + + constexpr ALWAYS_INLINE void SetTypeAttribute(KMemoryRegionAttr attr) { + m_type_id |= attr; + } + }; + static_assert(std::is_trivially_destructible<KMemoryRegion>::value); + + class KMemoryRegionTree { + public: + struct DerivedRegionExtents { + const KMemoryRegion *first_region; + const KMemoryRegion *last_region; + + constexpr DerivedRegionExtents() : first_region(nullptr), last_region(nullptr) { /* ... */ } + + constexpr ALWAYS_INLINE uintptr_t GetAddress() const { + return this->first_region->GetAddress(); + } + + constexpr ALWAYS_INLINE uintptr_t GetLastAddress() const { + return this->last_region->GetLastAddress(); + } + + constexpr ALWAYS_INLINE uintptr_t GetEndAddress() const { + return this->GetLastAddress() + 1; + } + + constexpr ALWAYS_INLINE size_t GetSize() const { + return this->GetEndAddress() - this->GetAddress(); + } + }; + private: + using TreeType = util::IntrusiveRedBlackTreeBaseTraits<KMemoryRegion>::TreeType<KMemoryRegion>; + public: + using value_type = TreeType::value_type; + using size_type = TreeType::size_type; + using difference_type = TreeType::difference_type; + using pointer = TreeType::pointer; + using const_pointer = TreeType::const_pointer; + using reference = TreeType::reference; + using const_reference = TreeType::const_reference; + using iterator = TreeType::iterator; + using const_iterator = TreeType::const_iterator; + private: + TreeType m_tree; + public: + constexpr ALWAYS_INLINE KMemoryRegionTree() : m_tree() { /* ... */ } + public: + KMemoryRegion *FindModifiable(uintptr_t address) { + if (auto it = this->find(KMemoryRegion(address, address, 0, 0)); it != this->end()) { + return std::addressof(*it); + } else { + return nullptr; + } + } + + const KMemoryRegion *Find(uintptr_t address) const { + if (auto it = this->find(KMemoryRegion(address, address, 0, 0)); it != this->cend()) { + return std::addressof(*it); + } else { + return nullptr; + } + } + + const KMemoryRegion *FindByType(u32 type_id) const { + for (auto it = this->cbegin(); it != this->cend(); ++it) { + if (it->GetType() == type_id) { + return std::addressof(*it); + } + } + return nullptr; + } + + const KMemoryRegion *FindByTypeAndAttribute(u32 type_id, u32 attr) const { + for (auto it = this->cbegin(); it != this->cend(); ++it) { + if (it->GetType() == type_id && it->GetAttributes() == attr) { + return std::addressof(*it); + } + } + return nullptr; + } + + const KMemoryRegion *FindFirstDerived(u32 type_id) const { + for (auto it = this->cbegin(); it != this->cend(); it++) { + if (it->IsDerivedFrom(type_id)) { + return std::addressof(*it); + } + } + return nullptr; + } + + const KMemoryRegion *FindLastDerived(u32 type_id) const { + const KMemoryRegion *region = nullptr; + for (auto it = this->begin(); it != this->end(); it++) { + if (it->IsDerivedFrom(type_id)) { + region = std::addressof(*it); + } + } + return region; + } + + + DerivedRegionExtents GetDerivedRegionExtents(u32 type_id) const { + DerivedRegionExtents extents; + + MESOSPHERE_INIT_ABORT_UNLESS(extents.first_region == nullptr); + MESOSPHERE_INIT_ABORT_UNLESS(extents.last_region == nullptr); + + for (auto it = this->cbegin(); it != this->cend(); it++) { + if (it->IsDerivedFrom(type_id)) { + if (extents.first_region == nullptr) { + extents.first_region = std::addressof(*it); + } + extents.last_region = std::addressof(*it); + } + } + + MESOSPHERE_INIT_ABORT_UNLESS(extents.first_region != nullptr); + MESOSPHERE_INIT_ABORT_UNLESS(extents.last_region != nullptr); + + return extents; + } + public: + NOINLINE void InsertDirectly(uintptr_t address, uintptr_t last_address, u32 attr = 0, u32 type_id = 0); + NOINLINE bool Insert(uintptr_t address, size_t size, u32 type_id, u32 new_attr = 0, u32 old_attr = 0); + public: + /* Iterator accessors. */ + iterator begin() { + return m_tree.begin(); + } + + const_iterator begin() const { + return m_tree.begin(); + } + + iterator end() { + return m_tree.end(); + } + + const_iterator end() const { + return m_tree.end(); + } + + const_iterator cbegin() const { + return this->begin(); + } + + const_iterator cend() const { + return this->end(); + } + + iterator iterator_to(reference ref) { + return m_tree.iterator_to(ref); + } + + const_iterator iterator_to(const_reference ref) const { + return m_tree.iterator_to(ref); + } + + /* Content management. */ + bool empty() const { + return m_tree.empty(); + } + + reference back() { + return m_tree.back(); + } + + const_reference back() const { + return m_tree.back(); + } + + reference front() { + return m_tree.front(); + } + + const_reference front() const { + return m_tree.front(); + } + + /* GCC over-eagerly inlines this operation. */ + NOINLINE iterator insert(reference ref) { + return m_tree.insert(ref); + } + + NOINLINE iterator erase(iterator it) { + return m_tree.erase(it); + } + + iterator find(const_reference ref) const { + return m_tree.find(ref); + } + + iterator nfind(const_reference ref) const { + return m_tree.nfind(ref); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_memory_region_type.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_memory_region_type.hpp new file mode 100644 index 00000000..f90908bf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_memory_region_type.hpp @@ -0,0 +1,350 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +namespace ams::kern { + + enum KMemoryRegionType : u32 {}; + + enum KMemoryRegionAttr : typename std::underlying_type<KMemoryRegionType>::type { + KMemoryRegionAttr_CarveoutProtected = 0x02000000, + KMemoryRegionAttr_Uncached = 0x04000000, + KMemoryRegionAttr_DidKernelMap = 0x08000000, + KMemoryRegionAttr_ShouldKernelMap = 0x10000000, + KMemoryRegionAttr_UserReadOnly = 0x20000000, + KMemoryRegionAttr_NoUserMap = 0x40000000, + KMemoryRegionAttr_LinearMapped = 0x80000000, + }; + + namespace impl { + + consteval size_t BitsForDeriveSparse(size_t n) { + return n + 1; + } + + consteval size_t BitsForDeriveDense(size_t n) { + AMS_ASSUME(n > 0); + + size_t low = 0, high = 1; + for (size_t i = 0; i < n - 1; ++i) { + if ((++low) == high) { + ++high; + low = 0; + } + } + return high + 1; + } + + class KMemoryRegionTypeValue { + private: + using ValueType = typename std::underlying_type<KMemoryRegionType>::type; + private: + ValueType m_value; + size_t m_next_bit; + bool m_finalized; + bool m_sparse_only; + bool m_dense_only; + private: + consteval KMemoryRegionTypeValue(ValueType v) : m_value(v), m_next_bit(0), m_finalized(false), m_sparse_only(false), m_dense_only(false) { /* ... */ } + public: + consteval KMemoryRegionTypeValue() : KMemoryRegionTypeValue(0) { /* ... */ } + + consteval operator KMemoryRegionType() const { return static_cast<KMemoryRegionType>(m_value); } + consteval ValueType GetValue() const { return m_value; } + + consteval const KMemoryRegionTypeValue Finalize() { + AMS_ASSUME(!m_finalized); + + KMemoryRegionTypeValue new_type = *this; + new_type.m_finalized = true; + return new_type; + } + + consteval const KMemoryRegionTypeValue SetSparseOnly() { + AMS_ASSUME(!m_finalized); + AMS_ASSUME(!m_sparse_only); + AMS_ASSUME(!m_dense_only); + + KMemoryRegionTypeValue new_type = *this; + new_type.m_sparse_only = true; + return new_type; + } + + consteval const KMemoryRegionTypeValue SetDenseOnly() { + AMS_ASSUME(!m_finalized); + AMS_ASSUME(!m_sparse_only); + AMS_ASSUME(!m_dense_only); + + KMemoryRegionTypeValue new_type = *this; + new_type.m_dense_only = true; + return new_type; + } + + consteval KMemoryRegionTypeValue SetAttribute(KMemoryRegionAttr attr) { + AMS_ASSUME(!m_finalized); + + KMemoryRegionTypeValue new_type = *this; + new_type.m_value |= attr; + return new_type; + } + + consteval KMemoryRegionTypeValue DeriveInitial(size_t i, size_t next = BITSIZEOF(ValueType)) const { + AMS_ASSUME(!m_finalized); + AMS_ASSUME(!m_value); + AMS_ASSUME(!m_next_bit); + AMS_ASSUME(next > i); + + KMemoryRegionTypeValue new_type = *this; + new_type.m_value = (ValueType{1} << i); + new_type.m_next_bit = next; + return new_type; + } + + consteval KMemoryRegionTypeValue DeriveAttribute(KMemoryRegionAttr attr) const { + AMS_ASSUME(!m_finalized); + + KMemoryRegionTypeValue new_type = *this; + new_type.m_value |= attr; + return new_type; + } + + consteval KMemoryRegionTypeValue DeriveTransition(size_t ofs = 0, size_t adv = 1) const { + AMS_ASSUME(!m_finalized); + AMS_ASSUME(ofs < adv); + AMS_ASSUME(m_next_bit + adv <= BITSIZEOF(ValueType)); + + KMemoryRegionTypeValue new_type = *this; + new_type.m_value |= (ValueType{1} << (m_next_bit + ofs)); + new_type.m_next_bit += adv; + return new_type; + } + + consteval KMemoryRegionTypeValue DeriveSparse(size_t ofs, size_t n, size_t i) const { + AMS_ASSUME(!m_finalized); + AMS_ASSUME(!m_dense_only); + AMS_ASSUME(m_next_bit + ofs + n + 1 <= BITSIZEOF(ValueType)); + AMS_ASSUME(i < n); + + KMemoryRegionTypeValue new_type = *this; + new_type.m_value |= (ValueType{1} << (m_next_bit + ofs)); + new_type.m_value |= (ValueType{1} << (m_next_bit + ofs + 1 + i)); + new_type.m_next_bit += ofs + n + 1; + return new_type; + } + + consteval KMemoryRegionTypeValue Derive(size_t n, size_t i) const { + AMS_ASSUME(!m_finalized); + AMS_ASSUME(!m_sparse_only); + AMS_ASSUME(m_next_bit + BitsForDeriveDense(n) <= BITSIZEOF(ValueType)); + AMS_ASSUME(i < n); + + size_t low = 0, high = 1; + for (size_t j = 0; j < i; ++j) { + if ((++low) == high) { + ++high; + low = 0; + } + } + AMS_ASSUME(high < BitsForDeriveDense(n)); + + + KMemoryRegionTypeValue new_type = *this; + new_type.m_value |= (ValueType{1} << (m_next_bit + low)); + new_type.m_value |= (ValueType{1} << (m_next_bit + high)); + new_type.m_next_bit += BitsForDeriveDense(n); + return new_type; + } + + consteval KMemoryRegionTypeValue Advance(size_t n) const { + AMS_ASSUME(!m_finalized); + AMS_ASSUME(m_next_bit + n <= BITSIZEOF(ValueType)); + + KMemoryRegionTypeValue new_type = *this; + new_type.m_next_bit += n; + return new_type; + } + + constexpr ALWAYS_INLINE bool IsAncestorOf(ValueType v) const { + return (m_value | v) == v; + } + }; + + } + + constexpr inline const auto KMemoryRegionType_None = impl::KMemoryRegionTypeValue(); + + constexpr inline const auto KMemoryRegionType_Kernel = KMemoryRegionType_None.DeriveInitial(0, 2); + constexpr inline const auto KMemoryRegionType_Dram = KMemoryRegionType_None.DeriveInitial(1, 2); + static_assert(KMemoryRegionType_Kernel.GetValue() == 0x1); + static_assert(KMemoryRegionType_Dram .GetValue() == 0x2); + + /* constexpr inline const auto KMemoryRegionType_CoreLocalRegion = KMemoryRegionType_None.DeriveInitial(2).Finalize(); */ + /* static_assert(KMemoryRegionType_CoreLocalRegion.GetValue() == 0x4); */ + + constexpr inline const auto KMemoryRegionType_DramKernelBase = KMemoryRegionType_Dram.DeriveSparse(0, 3, 0).SetAttribute(KMemoryRegionAttr_NoUserMap).SetAttribute(KMemoryRegionAttr_CarveoutProtected); + constexpr inline const auto KMemoryRegionType_DramReservedBase = KMemoryRegionType_Dram.DeriveSparse(0, 3, 1); + constexpr inline const auto KMemoryRegionType_DramHeapBase = KMemoryRegionType_Dram.DeriveSparse(0, 3, 2).SetAttribute(KMemoryRegionAttr_LinearMapped); + static_assert(KMemoryRegionType_DramKernelBase .GetValue() == (0xE | KMemoryRegionAttr_CarveoutProtected | KMemoryRegionAttr_NoUserMap)); + static_assert(KMemoryRegionType_DramReservedBase.GetValue() == (0x16)); + static_assert(KMemoryRegionType_DramHeapBase .GetValue() == (0x26 | KMemoryRegionAttr_LinearMapped)); + + constexpr inline const auto KMemoryRegionType_DramKernelCode = KMemoryRegionType_DramKernelBase.DeriveSparse(0, 4, 0); + constexpr inline const auto KMemoryRegionType_DramKernelSlab = KMemoryRegionType_DramKernelBase.DeriveSparse(0, 4, 1); + constexpr inline const auto KMemoryRegionType_DramKernelPtHeap = KMemoryRegionType_DramKernelBase.DeriveSparse(0, 4, 2).SetAttribute(KMemoryRegionAttr_LinearMapped); + constexpr inline const auto KMemoryRegionType_DramKernelInitPt = KMemoryRegionType_DramKernelBase.DeriveSparse(0, 4, 3).SetAttribute(KMemoryRegionAttr_LinearMapped); + static_assert(KMemoryRegionType_DramKernelCode .GetValue() == (0xCE | KMemoryRegionAttr_CarveoutProtected | KMemoryRegionAttr_NoUserMap)); + static_assert(KMemoryRegionType_DramKernelSlab .GetValue() == (0x14E | KMemoryRegionAttr_CarveoutProtected | KMemoryRegionAttr_NoUserMap)); + static_assert(KMemoryRegionType_DramKernelPtHeap.GetValue() == (0x24E | KMemoryRegionAttr_CarveoutProtected | KMemoryRegionAttr_NoUserMap | KMemoryRegionAttr_LinearMapped)); + static_assert(KMemoryRegionType_DramKernelInitPt.GetValue() == (0x44E | KMemoryRegionAttr_CarveoutProtected | KMemoryRegionAttr_NoUserMap | KMemoryRegionAttr_LinearMapped)); + + constexpr inline const auto KMemoryRegionType_DramKernelSecureAppletMemory = KMemoryRegionType_DramKernelBase.DeriveSparse(1, 3, 0).SetAttribute(KMemoryRegionAttr_LinearMapped); + constexpr inline const auto KMemoryRegionType_DramKernelSecureUnknown = KMemoryRegionType_DramKernelBase.DeriveSparse(1, 3, 1).SetAttribute(KMemoryRegionAttr_LinearMapped); + static_assert(KMemoryRegionType_DramKernelSecureAppletMemory.GetValue() == (0x18E | KMemoryRegionAttr_CarveoutProtected | KMemoryRegionAttr_NoUserMap | KMemoryRegionAttr_LinearMapped)); + static_assert(KMemoryRegionType_DramKernelSecureUnknown.GetValue() == (0x28E | KMemoryRegionAttr_CarveoutProtected | KMemoryRegionAttr_NoUserMap | KMemoryRegionAttr_LinearMapped)); + + constexpr inline const auto KMemoryRegionType_DramReservedEarly = KMemoryRegionType_DramReservedBase.DeriveAttribute(KMemoryRegionAttr_NoUserMap); + static_assert(KMemoryRegionType_DramReservedEarly.GetValue() == (0x16 | KMemoryRegionAttr_NoUserMap)); + + constexpr inline const auto KMemoryRegionType_KernelTraceBuffer = KMemoryRegionType_DramReservedBase.DeriveSparse(0, 3, 0).SetAttribute(KMemoryRegionAttr_LinearMapped).SetAttribute(KMemoryRegionAttr_UserReadOnly); + constexpr inline const auto KMemoryRegionType_OnMemoryBootImage = KMemoryRegionType_DramReservedBase.DeriveSparse(0, 3, 1); + constexpr inline const auto KMemoryRegionType_DTB = KMemoryRegionType_DramReservedBase.DeriveSparse(0, 3, 2); + static_assert(KMemoryRegionType_KernelTraceBuffer.GetValue() == (0xD6 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_UserReadOnly)); + static_assert(KMemoryRegionType_OnMemoryBootImage.GetValue() == 0x156); + static_assert(KMemoryRegionType_DTB.GetValue() == 0x256); + + + constexpr inline const auto KMemoryRegionType_DramPoolPartition = KMemoryRegionType_DramHeapBase.DeriveAttribute(KMemoryRegionAttr_NoUserMap); + static_assert(KMemoryRegionType_DramPoolPartition.GetValue() == (0x26 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap)); + + constexpr inline const auto KMemoryRegionType_DramPoolManagement = KMemoryRegionType_DramPoolPartition.Derive(4, 0).SetAttribute(KMemoryRegionAttr_CarveoutProtected); + /* UNUSED: .Derive(4, 1); */ + /* UNUSED: .Derive(4, 2); */ + constexpr inline const auto KMemoryRegionType_DramUserPool = KMemoryRegionType_DramPoolPartition.Derive(4, 3); + static_assert(KMemoryRegionType_DramPoolManagement.GetValue() == (0xE6 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap | KMemoryRegionAttr_CarveoutProtected)); + static_assert(KMemoryRegionType_DramUserPool .GetValue() == (0x266 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap)); + + constexpr inline const auto KMemoryRegionType_DramApplicationPool = KMemoryRegionType_DramUserPool.Derive(4, 0); + constexpr inline const auto KMemoryRegionType_DramAppletPool = KMemoryRegionType_DramUserPool.Derive(4, 1); + constexpr inline const auto KMemoryRegionType_DramSystemNonSecurePool = KMemoryRegionType_DramUserPool.Derive(4, 2); + constexpr inline const auto KMemoryRegionType_DramSystemPool = KMemoryRegionType_DramUserPool.Derive(4, 3).SetAttribute(KMemoryRegionAttr_CarveoutProtected); + static_assert(KMemoryRegionType_DramApplicationPool .GetValue() == (0xE66 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap)); + static_assert(KMemoryRegionType_DramAppletPool .GetValue() == (0x1666 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap)); + static_assert(KMemoryRegionType_DramSystemNonSecurePool.GetValue() == (0x1A66 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap)); + static_assert(KMemoryRegionType_DramSystemPool .GetValue() == (0x2666 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap | KMemoryRegionAttr_CarveoutProtected)); + + constexpr inline const auto KMemoryRegionType_VirtualDramHeapBase = KMemoryRegionType_Dram.DeriveSparse(1, 4, 0); + constexpr inline const auto KMemoryRegionType_VirtualDramKernelPtHeap = KMemoryRegionType_Dram.DeriveSparse(1, 4, 1); + constexpr inline const auto KMemoryRegionType_VirtualDramKernelTraceBuffer = KMemoryRegionType_Dram.DeriveSparse(1, 4, 2); + static_assert(KMemoryRegionType_VirtualDramHeapBase .GetValue() == 0x1A); + static_assert(KMemoryRegionType_VirtualDramKernelPtHeap .GetValue() == 0x2A); + static_assert(KMemoryRegionType_VirtualDramKernelTraceBuffer.GetValue() == 0x4A); + + /* UNUSED: .DeriveSparse(2, 2, 0); */ + constexpr inline const auto KMemoryRegionType_VirtualDramUnknownDebug = KMemoryRegionType_Dram.Advance(2).Derive(4, 0); + constexpr inline const auto KMemoryRegionType_VirtualDramKernelSecureAppletMemory = KMemoryRegionType_Dram.Advance(2).Derive(4, 1); + /* UNUSED: .Derive(4, 2); */ + constexpr inline const auto KMemoryRegionType_VirtualDramKernelSecureUnknown = KMemoryRegionType_Dram.Advance(2).Derive(4, 3); + static_assert(KMemoryRegionType_VirtualDramUnknownDebug .GetValue() == (0x32)); + static_assert(KMemoryRegionType_VirtualDramKernelSecureAppletMemory.GetValue() == (0x52)); + static_assert(KMemoryRegionType_VirtualDramKernelSecureUnknown .GetValue() == (0x92)); + + + constexpr inline const auto KMemoryRegionType_VirtualDramKernelInitPt = KMemoryRegionType_VirtualDramHeapBase.Derive(4, 0); + constexpr inline const auto KMemoryRegionType_VirtualDramPoolManagement = KMemoryRegionType_VirtualDramHeapBase.Derive(4, 1); + constexpr inline const auto KMemoryRegionType_VirtualDramUserPool = KMemoryRegionType_VirtualDramHeapBase.Derive(4, 2); + /* UNUSED: .Derive(4, 3); */ + static_assert(KMemoryRegionType_VirtualDramKernelInitPt .GetValue() == 0x31A); + static_assert(KMemoryRegionType_VirtualDramPoolManagement.GetValue() == 0x51A); + static_assert(KMemoryRegionType_VirtualDramUserPool .GetValue() == 0x61A); + + constexpr inline const auto KMemoryRegionType_VirtualDramApplicationPool = KMemoryRegionType_VirtualDramUserPool.Derive(4, 0); + constexpr inline const auto KMemoryRegionType_VirtualDramAppletPool = KMemoryRegionType_VirtualDramUserPool.Derive(4, 1); + constexpr inline const auto KMemoryRegionType_VirtualDramSystemNonSecurePool = KMemoryRegionType_VirtualDramUserPool.Derive(4, 2); + constexpr inline const auto KMemoryRegionType_VirtualDramSystemPool = KMemoryRegionType_VirtualDramUserPool.Derive(4, 3); + static_assert(KMemoryRegionType_VirtualDramApplicationPool .GetValue() == 0x361A); + static_assert(KMemoryRegionType_VirtualDramAppletPool .GetValue() == 0x561A); + static_assert(KMemoryRegionType_VirtualDramSystemNonSecurePool.GetValue() == 0x661A); + static_assert(KMemoryRegionType_VirtualDramSystemPool .GetValue() == 0x961A); + + constexpr inline const auto KMemoryRegionType_ArchDeviceBase = KMemoryRegionType_Kernel.DeriveTransition(0, 1).SetSparseOnly(); + constexpr inline const auto KMemoryRegionType_BoardDeviceBase = KMemoryRegionType_Kernel.DeriveTransition(0, 2).SetDenseOnly(); + static_assert(KMemoryRegionType_ArchDeviceBase .GetValue() == 0x5); + static_assert(KMemoryRegionType_BoardDeviceBase.GetValue() == 0x5); + + #if defined(ATMOSPHERE_ARCH_ARM64) + #include <mesosphere/arch/arm64/kern_k_memory_region_device_types.inc> + #elif defined(ATMOSPHERE_ARCH_ARM) + #include <mesosphere/arch/arm/kern_k_memory_region_device_types.inc> + #else + /* Default to no architecture devices. */ + constexpr inline const auto NumArchitectureDeviceRegions = 0; + #endif + static_assert(NumArchitectureDeviceRegions >= 0); + + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + #include <mesosphere/board/nintendo/nx/kern_k_memory_region_device_types.inc> + #else + /* Default to no board devices. */ + constexpr inline const auto NumBoardDeviceRegions = 0; + #endif + static_assert(NumBoardDeviceRegions >= 0); + + constexpr inline const auto KMemoryRegionType_KernelCode = KMemoryRegionType_Kernel.DeriveSparse(1, 4, 0); + constexpr inline const auto KMemoryRegionType_KernelStack = KMemoryRegionType_Kernel.DeriveSparse(1, 4, 1); + constexpr inline const auto KMemoryRegionType_KernelMisc = KMemoryRegionType_Kernel.DeriveSparse(1, 4, 2); + constexpr inline const auto KMemoryRegionType_KernelSlab = KMemoryRegionType_Kernel.DeriveSparse(1, 4, 3); + static_assert(KMemoryRegionType_KernelCode .GetValue() == 0x19); + static_assert(KMemoryRegionType_KernelStack.GetValue() == 0x29); + static_assert(KMemoryRegionType_KernelMisc .GetValue() == 0x49); + static_assert(KMemoryRegionType_KernelSlab .GetValue() == 0x89); + + constexpr inline const auto KMemoryRegionType_KernelMiscDerivedBase = KMemoryRegionType_KernelMisc.DeriveTransition(); + static_assert(KMemoryRegionType_KernelMiscDerivedBase.GetValue() == 0x149); + + /* UNUSED: .Derive(7, 0); */ + constexpr inline const auto KMemoryRegionType_KernelMiscMainStack = KMemoryRegionType_KernelMiscDerivedBase.Derive(7, 1); + constexpr inline const auto KMemoryRegionType_KernelMiscMappedDevice = KMemoryRegionType_KernelMiscDerivedBase.Derive(7, 2); + constexpr inline const auto KMemoryRegionType_KernelMiscExceptionStack = KMemoryRegionType_KernelMiscDerivedBase.Derive(7, 3); + constexpr inline const auto KMemoryRegionType_KernelMiscUnknownDebug = KMemoryRegionType_KernelMiscDerivedBase.Derive(7, 4); + /* UNUSED: .Derive(7, 5); */ + constexpr inline const auto KMemoryRegionType_KernelMiscIdleStack = KMemoryRegionType_KernelMiscDerivedBase.Derive(7, 6); + static_assert(KMemoryRegionType_KernelMiscMainStack .GetValue() == 0xB49); + static_assert(KMemoryRegionType_KernelMiscMappedDevice .GetValue() == 0xD49); + static_assert(KMemoryRegionType_KernelMiscExceptionStack.GetValue() == 0x1349); + static_assert(KMemoryRegionType_KernelMiscUnknownDebug .GetValue() == 0x1549); + static_assert(KMemoryRegionType_KernelMiscIdleStack .GetValue() == 0x2349); + + constexpr inline const auto KMemoryRegionType_KernelTemp = KMemoryRegionType_Kernel.Advance(2).Derive(2, 0); + static_assert(KMemoryRegionType_KernelTemp.GetValue() == 0x31); + + constexpr ALWAYS_INLINE KMemoryRegionType GetTypeForVirtualLinearMapping(u32 type_id) { + if (KMemoryRegionType_DramKernelPtHeap.IsAncestorOf(type_id)) { + return KMemoryRegionType_VirtualDramKernelPtHeap; + } else if (KMemoryRegionType_DramKernelSecureAppletMemory.IsAncestorOf(type_id)) { + return KMemoryRegionType_VirtualDramKernelSecureAppletMemory; + } else if (KMemoryRegionType_DramKernelSecureUnknown.IsAncestorOf(type_id)) { + return KMemoryRegionType_VirtualDramKernelSecureUnknown; + } else if (KMemoryRegionType_KernelTraceBuffer.IsAncestorOf(type_id)) { + return KMemoryRegionType_VirtualDramKernelTraceBuffer; + } else if ((type_id | KMemoryRegionAttr_ShouldKernelMap) == type_id) { + return KMemoryRegionType_VirtualDramUnknownDebug; + } else { + return KMemoryRegionType_Dram; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_object_name.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_object_name.hpp new file mode 100644 index 00000000..bc1b0e37 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_object_name.hpp @@ -0,0 +1,67 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_auto_object.hpp> +#include <mesosphere/kern_k_light_lock.hpp> +#include <mesosphere/kern_slab_helpers.hpp> + +namespace ams::kern { + + class KObjectName : public KSlabAllocated<KObjectName>, public util::IntrusiveListBaseNode<KObjectName> { + public: + static constexpr size_t NameLengthMax = 12; + + using List = util::IntrusiveListBaseTraits<KObjectName>::ListType; + private: + char m_name[NameLengthMax]; + KAutoObject *m_object; + public: + static Result NewFromName(KAutoObject *obj, const char *name); + static Result Delete(KAutoObject *obj, const char *name); + + static KScopedAutoObject<KAutoObject> Find(const char *name); + + template<typename Derived> + static Result Delete(const char *name) { + /* Find the object. */ + KScopedAutoObject obj = Find(name); + R_UNLESS(obj.IsNotNull(), svc::ResultNotFound()); + + /* Cast the object to the desired type. */ + Derived *derived = obj->DynamicCast<Derived *>(); + R_UNLESS(derived != nullptr, svc::ResultNotFound()); + + /* Check that the object is closed. */ + R_UNLESS(derived->IsServerClosed(), svc::ResultInvalidState()); + + R_RETURN(Delete(obj.GetPointerUnsafe(), name)); + } + + template<typename Derived> requires std::derived_from<Derived, KAutoObject> + static KScopedAutoObject<Derived> Find(const char *name) { + return Find(name); + } + private: + static KScopedAutoObject<KAutoObject> FindImpl(const char *name); + + void Initialize(KAutoObject *obj, const char *name); + + bool MatchesName(const char *name) const; + KAutoObject *GetObject() const { return m_object; } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_bitmap.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_bitmap.hpp new file mode 100644 index 00000000..25b039bb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_bitmap.hpp @@ -0,0 +1,331 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_select_system_control.hpp> + +namespace ams::kern { + + class KPageBitmap { + public: + class RandomBitGenerator { + private: + util::TinyMT m_rng; + u32 m_entropy; + u32 m_bits_available; + private: + void RefreshEntropy() { + m_entropy = m_rng.GenerateRandomU32(); + m_bits_available = BITSIZEOF(m_entropy); + } + + bool GenerateRandomBit() { + if (m_bits_available == 0) { + this->RefreshEntropy(); + } + + const bool rnd_bit = (m_entropy & 1) != 0; + m_entropy >>= 1; + --m_bits_available; + return rnd_bit; + } + + u64 GenerateRandomBits(u32 num_bits) { + u64 result = 0; + + /* Iteratively add random bits to our result. */ + while (num_bits > 0) { + /* Ensure we have random bits to take from. */ + if (m_bits_available == 0) { + this->RefreshEntropy(); + } + + /* Determine how many bits to take this round. */ + const auto cur_bits = std::min(num_bits, m_bits_available); + + /* Generate mask for our current bits. */ + const u64 mask = (static_cast<u64>(1) << cur_bits) - 1; + + /* Add bits to output from our entropy. */ + result <<= cur_bits; + result |= (m_entropy & mask); + + /* Remove bits from our entropy. */ + m_entropy >>= cur_bits; + m_bits_available -= cur_bits; + + /* Advance. */ + num_bits -= cur_bits; + } + + return result; + } + public: + RandomBitGenerator() : m_entropy(), m_bits_available() { + m_rng.Initialize(static_cast<u32>(KSystemControl::GenerateRandomU64())); + } + + u64 SelectRandomBit(u64 bitmap) { + u64 selected = 0; + + for (size_t cur_num_bits = BITSIZEOF(bitmap) / 2; cur_num_bits != 0; cur_num_bits /= 2) { + const u64 high = (bitmap >> cur_num_bits); + const u64 low = (bitmap & (~(UINT64_C(0xFFFFFFFFFFFFFFFF) << cur_num_bits))); + + /* Choose high if we have high and (don't have low or select high randomly). */ + if (high && (low == 0 || this->GenerateRandomBit())) { + bitmap = high; + selected += cur_num_bits; + } else { + bitmap = low; + selected += 0; + } + } + + return selected; + } + + u64 GenerateRandom(u64 max) { + /* Determine the number of bits we need. */ + const u64 bits_needed = 1 + (BITSIZEOF(max) - util::CountLeadingZeros(max)); + + /* Generate a random value of the desired bitwidth. */ + const u64 rnd = this->GenerateRandomBits(bits_needed); + + /* Adjust the value to be in range. */ + return rnd - ((rnd / max) * max); + } + }; + public: + static constexpr size_t MaxDepth = 4; + private: + u64 *m_bit_storages[MaxDepth]; + u64 *m_end_storages[MaxDepth]; + RandomBitGenerator m_rng; + size_t m_num_bits; + size_t m_used_depths; + public: + KPageBitmap() : m_bit_storages(), m_end_storages(), m_rng(), m_num_bits(), m_used_depths() { /* ... */ } + + constexpr size_t GetNumBits() const { return m_num_bits; } + constexpr s32 GetHighestDepthIndex() const { return static_cast<s32>(m_used_depths) - 1; } + + u64 *Initialize(u64 *storage, size_t size) { + /* Initially, everything is un-set. */ + m_num_bits = 0; + + /* Calculate the needed bitmap depth. */ + m_used_depths = static_cast<size_t>(GetRequiredDepth(size)); + MESOSPHERE_ASSERT(m_used_depths <= MaxDepth); + + /* Set the bitmap pointers. */ + for (s32 depth = this->GetHighestDepthIndex(); depth >= 0; depth--) { + m_bit_storages[depth] = storage; + size = util::AlignUp(size, BITSIZEOF(u64)) / BITSIZEOF(u64); + storage += size; + m_end_storages[depth] = storage; + } + + return storage; + } + + ssize_t FindFreeBlock(bool random) { + uintptr_t offset = 0; + s32 depth = 0; + + if (random) { + do { + const u64 v = m_bit_storages[depth][offset]; + if (v == 0) { + /* If depth is bigger than zero, then a previous level indicated a block was free. */ + MESOSPHERE_ASSERT(depth == 0); + return -1; + } + offset = offset * BITSIZEOF(u64) + m_rng.SelectRandomBit(v); + ++depth; + } while (depth < static_cast<s32>(m_used_depths)); + } else { + do { + const u64 v = m_bit_storages[depth][offset]; + if (v == 0) { + /* If depth is bigger than zero, then a previous level indicated a block was free. */ + MESOSPHERE_ASSERT(depth == 0); + return -1; + } + offset = offset * BITSIZEOF(u64) + __builtin_ctzll(v); + ++depth; + } while (depth < static_cast<s32>(m_used_depths)); + } + + return static_cast<ssize_t>(offset); + } + + ssize_t FindFreeRange(size_t count) { + /* Check that it is possible to find a range. */ + const u64 * const storage_start = m_bit_storages[m_used_depths - 1]; + const u64 * const storage_end = m_end_storages[m_used_depths - 1]; + + /* If we don't have a storage to iterate (or want more blocks than fit in a single storage), we can't find a free range. */ + if (!(storage_start < storage_end && count <= BITSIZEOF(u64))) { + return -1; + } + + /* Walk the storages to select a random free range. */ + const size_t options_per_storage = std::max<size_t>(BITSIZEOF(u64) / count, 1); + const size_t num_entries = std::max<size_t>(storage_end - storage_start, 1); + + const u64 free_mask = (static_cast<u64>(1) << count) - 1; + + size_t num_valid_options = 0; + ssize_t chosen_offset = -1; + for (size_t storage_index = 0; storage_index < num_entries; ++storage_index) { + u64 storage = storage_start[storage_index]; + for (size_t option = 0; option < options_per_storage; ++option) { + if ((storage & free_mask) == free_mask) { + /* We've found a new valid option. */ + ++num_valid_options; + + /* Select the Kth valid option with probability 1/K. This leads to an overall uniform distribution. */ + if (num_valid_options == 1 || m_rng.GenerateRandom(num_valid_options) == 0) { + /* This is our first option, so select it. */ + chosen_offset = storage_index * BITSIZEOF(u64) + option * count; + } + } + storage >>= count; + } + } + + /* Return the random offset we chose.*/ + return chosen_offset; + } + + void SetBit(size_t offset) { + this->SetBit(this->GetHighestDepthIndex(), offset); + m_num_bits++; + } + + void ClearBit(size_t offset) { + this->ClearBit(this->GetHighestDepthIndex(), offset); + m_num_bits--; + } + + bool ClearRange(size_t offset, size_t count) { + s32 depth = this->GetHighestDepthIndex(); + u64 *bits = m_bit_storages[depth]; + size_t bit_ind = offset / BITSIZEOF(u64); + if (AMS_LIKELY(count < BITSIZEOF(u64))) { + const size_t shift = offset % BITSIZEOF(u64); + MESOSPHERE_ASSERT(shift + count <= BITSIZEOF(u64)); + /* Check that all the bits are set. */ + const u64 mask = ((u64(1) << count) - 1) << shift; + u64 v = bits[bit_ind]; + if ((v & mask) != mask) { + return false; + } + + /* Clear the bits. */ + v &= ~mask; + bits[bit_ind] = v; + if (v == 0) { + this->ClearBit(depth - 1, bit_ind); + } + } else { + MESOSPHERE_ASSERT(offset % BITSIZEOF(u64) == 0); + MESOSPHERE_ASSERT(count % BITSIZEOF(u64) == 0); + /* Check that all the bits are set. */ + size_t remaining = count; + size_t i = 0; + do { + if (bits[bit_ind + i++] != ~u64(0)) { + return false; + } + remaining -= BITSIZEOF(u64); + } while (remaining > 0); + + /* Clear the bits. */ + remaining = count; + i = 0; + do { + bits[bit_ind + i] = 0; + this->ClearBit(depth - 1, bit_ind + i); + i++; + remaining -= BITSIZEOF(u64); + } while (remaining > 0); + } + + m_num_bits -= count; + return true; + } + private: + void SetBit(s32 depth, size_t offset) { + while (depth >= 0) { + size_t ind = offset / BITSIZEOF(u64); + size_t which = offset % BITSIZEOF(u64); + const u64 mask = u64(1) << which; + + u64 *bit = std::addressof(m_bit_storages[depth][ind]); + u64 v = *bit; + MESOSPHERE_ASSERT((v & mask) == 0); + *bit = v | mask; + if (v) { + break; + } + offset = ind; + depth--; + } + } + + void ClearBit(s32 depth, size_t offset) { + while (depth >= 0) { + size_t ind = offset / BITSIZEOF(u64); + size_t which = offset % BITSIZEOF(u64); + const u64 mask = u64(1) << which; + + u64 *bit = std::addressof(m_bit_storages[depth][ind]); + u64 v = *bit; + MESOSPHERE_ASSERT((v & mask) != 0); + v &= ~mask; + *bit = v; + if (v) { + break; + } + offset = ind; + depth--; + } + } + private: + static constexpr s32 GetRequiredDepth(size_t region_size) { + s32 depth = 0; + while (true) { + region_size /= BITSIZEOF(u64); + depth++; + if (region_size == 0) { + return depth; + } + } + } + public: + static constexpr size_t CalculateManagementOverheadSize(size_t region_size) { + size_t overhead_bits = 0; + for (s32 depth = GetRequiredDepth(region_size) - 1; depth >= 0; depth--) { + region_size = util::AlignUp(region_size, BITSIZEOF(u64)) / BITSIZEOF(u64); + overhead_bits += region_size; + } + return overhead_bits * sizeof(u64); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_buffer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_buffer.hpp new file mode 100644 index 00000000..dc80aaec --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_buffer.hpp @@ -0,0 +1,108 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_slab_helpers.hpp> +#include <mesosphere/kern_k_memory_layout.hpp> +#include <mesosphere/kern_k_dynamic_page_manager.hpp> + +namespace ams::kern { + + class KDynamicPageManager; + + class KPageBuffer; + + class KPageBufferSlabHeap : protected impl::KSlabHeapImpl { + public: + static constexpr size_t BufferSize = PageSize; + static constinit inline size_t s_buffer_count = 0; + private: + size_t m_obj_size{}; + public: + constexpr KPageBufferSlabHeap() = default; + + /* See kern_init_slab_setup.cpp for definition. */ + void Initialize(KDynamicPageManager &allocator); + + KPageBuffer *Allocate(); + void Free(KPageBuffer *pb); + }; + + class KPageBuffer { + private: + u8 m_buffer[KPageBufferSlabHeap::BufferSize]; + public: + KPageBuffer() { + std::memset(m_buffer, 0, sizeof(m_buffer)); + } + + ALWAYS_INLINE KPhysicalAddress GetPhysicalAddress() const { + return KMemoryLayout::GetLinearPhysicalAddress(KVirtualAddress(this)); + } + + static ALWAYS_INLINE KPageBuffer *FromPhysicalAddress(KPhysicalAddress phys_addr) { + const KVirtualAddress virt_addr = KMemoryLayout::GetLinearVirtualAddress(phys_addr); + + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(phys_addr), PageSize)); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(virt_addr), PageSize)); + + return GetPointer<KPageBuffer>(virt_addr); + } + private: + static constinit inline KPageBufferSlabHeap s_slab_heap; + public: + static void InitializeSlabHeap(KDynamicPageManager &allocator) { + s_slab_heap.Initialize(allocator); + } + + static KPageBuffer *Allocate() { + return s_slab_heap.Allocate(); + } + + static void Free(KPageBuffer *obj) { + s_slab_heap.Free(obj); + } + + template<size_t ExpectedSize> + static ALWAYS_INLINE KPageBuffer *AllocateChecked() { + /* Check that the allocation is valid. */ + MESOSPHERE_ABORT_UNLESS(sizeof(KPageBuffer) == ExpectedSize); + + return Allocate(); + } + + template<size_t ExpectedSize> + static ALWAYS_INLINE void FreeChecked(KPageBuffer *obj) { + /* Check that the free is valid. */ + MESOSPHERE_ABORT_UNLESS(sizeof(KPageBuffer) == ExpectedSize); + + return Free(obj); + } + }; + static_assert(sizeof(KPageBuffer) == KPageBufferSlabHeap::BufferSize); + + ALWAYS_INLINE KPageBuffer *KPageBufferSlabHeap::Allocate() { + KPageBuffer *pb = static_cast<KPageBuffer *>(KSlabHeapImpl::Allocate()); + if (AMS_LIKELY(pb != nullptr)) { + std::construct_at(pb); + } + return pb; + } + + ALWAYS_INLINE void KPageBufferSlabHeap::Free(KPageBuffer *pb) { + KSlabHeapImpl::Free(pb); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_group.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_group.hpp new file mode 100644 index 00000000..2ab0a467 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_group.hpp @@ -0,0 +1,180 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_typed_address.hpp> +#include <mesosphere/kern_slab_helpers.hpp> + +namespace ams::kern { + + class KBlockInfoManager; + + class KPageGroup; + + class KBlockInfo { + private: + friend class KPageGroup; + private: + KBlockInfo *m_next; + u32 m_page_index; + u32 m_num_pages; + public: + KBlockInfo() : m_next(nullptr) { /* ... */ } + + constexpr ALWAYS_INLINE void Initialize(KPhysicalAddress addr, size_t np) { + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(addr), PageSize)); + MESOSPHERE_ASSERT(static_cast<u32>(np) == np); + + m_page_index = GetInteger(addr) / PageSize; + m_num_pages = np; + } + + constexpr ALWAYS_INLINE KPhysicalAddress GetAddress() const { return m_page_index * PageSize; } + constexpr ALWAYS_INLINE size_t GetNumPages() const { return m_num_pages; } + constexpr ALWAYS_INLINE size_t GetSize() const { return this->GetNumPages() * PageSize; } + constexpr ALWAYS_INLINE KPhysicalAddress GetEndAddress() const { return (m_page_index + m_num_pages) * PageSize; } + constexpr ALWAYS_INLINE KPhysicalAddress GetLastAddress() const { return this->GetEndAddress() - 1; } + + constexpr ALWAYS_INLINE KBlockInfo *GetNext() const { return m_next; } + + constexpr ALWAYS_INLINE bool IsEquivalentTo(const KBlockInfo &rhs) const { + return m_page_index == rhs.m_page_index && m_num_pages == rhs.m_num_pages; + } + + constexpr ALWAYS_INLINE bool operator==(const KBlockInfo &rhs) const { + return this->IsEquivalentTo(rhs); + } + + constexpr ALWAYS_INLINE bool operator!=(const KBlockInfo &rhs) const { + return !(*this == rhs); + } + + constexpr ALWAYS_INLINE bool IsStrictlyBefore(KPhysicalAddress addr) const { + const KPhysicalAddress end = this->GetEndAddress(); + + if (m_page_index != 0 && end == Null<KPhysicalAddress>) { + return false; + } + + return end < addr; + } + + constexpr ALWAYS_INLINE bool operator<(KPhysicalAddress addr) const { + return this->IsStrictlyBefore(addr); + } + + constexpr ALWAYS_INLINE bool TryConcatenate(KPhysicalAddress addr, size_t np) { + if (addr != Null<KPhysicalAddress> && addr == this->GetEndAddress()) { + m_num_pages += np; + return true; + } + return false; + } + private: + constexpr ALWAYS_INLINE void SetNext(KBlockInfo *next) { + m_next = next; + } + }; + static_assert(sizeof(KBlockInfo) <= 0x10); + + class KPageGroup { + public: + class Iterator { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = const KBlockInfo; + using difference_type = std::ptrdiff_t; + using pointer = value_type *; + using reference = value_type &; + private: + pointer m_node; + public: + constexpr explicit ALWAYS_INLINE Iterator(pointer n) : m_node(n) { /* ... */ } + + constexpr ALWAYS_INLINE bool operator==(const Iterator &rhs) const { return m_node == rhs.m_node; } + constexpr ALWAYS_INLINE bool operator!=(const Iterator &rhs) const { return !(*this == rhs); } + + constexpr ALWAYS_INLINE pointer operator->() const { return m_node; } + constexpr ALWAYS_INLINE reference operator*() const { return *m_node; } + + constexpr ALWAYS_INLINE Iterator &operator++() { + m_node = m_node->GetNext(); + return *this; + } + + constexpr ALWAYS_INLINE Iterator operator++(int) { + const Iterator it{*this}; + ++(*this); + return it; + } + }; + private: + KBlockInfo *m_first_block; + KBlockInfo *m_last_block; + KBlockInfoManager *m_manager; + public: + explicit KPageGroup(KBlockInfoManager *m) : m_first_block(), m_last_block(), m_manager(m) { /* ... */ } + ~KPageGroup() { this->Finalize(); } + + void CloseAndReset(); + void Finalize(); + + ALWAYS_INLINE Iterator begin() const { return Iterator{m_first_block}; } + ALWAYS_INLINE Iterator end() const { return Iterator{nullptr}; } + ALWAYS_INLINE bool empty() const { return m_first_block == nullptr; } + + Result AddBlock(KPhysicalAddress addr, size_t num_pages); + void Open() const; + void OpenFirst() const; + void Close() const; + + size_t GetNumPages() const; + + bool IsEquivalentTo(const KPageGroup &rhs) const; + + Result CopyRangeTo(KPageGroup &out, size_t offset, size_t size) const; + + ALWAYS_INLINE bool operator==(const KPageGroup &rhs) const { + return this->IsEquivalentTo(rhs); + } + + ALWAYS_INLINE bool operator!=(const KPageGroup &rhs) const { + return !(*this == rhs); + } + }; + + class KScopedPageGroup { + private: + const KPageGroup *m_pg; + public: + explicit ALWAYS_INLINE KScopedPageGroup(const KPageGroup *gp, bool not_first = true) : m_pg(gp) { + if (m_pg) { + if (not_first) { + m_pg->Open(); + } else { + m_pg->OpenFirst(); + } + } + } + explicit ALWAYS_INLINE KScopedPageGroup(const KPageGroup &gp, bool not_first = true) : KScopedPageGroup(std::addressof(gp), not_first) { /* ... */ } + ALWAYS_INLINE ~KScopedPageGroup() { if (m_pg) { m_pg->Close(); } } + + ALWAYS_INLINE void CancelClose() { + m_pg = nullptr; + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_heap.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_heap.hpp new file mode 100644 index 00000000..d953c481 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_heap.hpp @@ -0,0 +1,188 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_page_bitmap.hpp> + +namespace ams::kern { + + class KPageHeap { + private: + static constexpr inline size_t MemoryBlockPageShifts[] = { 0xC, 0x10, 0x15, 0x16, 0x19, 0x1D, 0x1E }; + static constexpr size_t NumMemoryBlockPageShifts = util::size(MemoryBlockPageShifts); + public: + static constexpr s32 GetAlignedBlockIndex(size_t num_pages, size_t align_pages) { + const size_t target_pages = std::max(num_pages, align_pages); + for (size_t i = 0; i < NumMemoryBlockPageShifts; i++) { + if (target_pages <= (static_cast<size_t>(1) << MemoryBlockPageShifts[i]) / PageSize) { + return static_cast<s32>(i); + } + } + return -1; + } + + static constexpr s32 GetBlockIndex(size_t num_pages) { + for (s32 i = static_cast<s32>(NumMemoryBlockPageShifts) - 1; i >= 0; i--) { + if (num_pages >= (static_cast<size_t>(1) << MemoryBlockPageShifts[i]) / PageSize) { + return i; + } + } + return -1; + } + + static constexpr size_t GetBlockSize(size_t index) { + return static_cast<size_t>(1) << MemoryBlockPageShifts[index]; + } + + static constexpr size_t GetBlockNumPages(size_t index) { + return GetBlockSize(index) / PageSize; + } + private: + class Block { + private: + KPageBitmap m_bitmap; + KPhysicalAddress m_heap_address; + uintptr_t m_end_offset; + size_t m_block_shift; + size_t m_next_block_shift; + public: + Block() : m_bitmap(), m_heap_address(Null<KPhysicalAddress>), m_end_offset(), m_block_shift(), m_next_block_shift() { /* ... */ } + + constexpr size_t GetShift() const { return m_block_shift; } + constexpr size_t GetNextShift() const { return m_next_block_shift; } + constexpr size_t GetSize() const { return u64(1) << this->GetShift(); } + constexpr size_t GetNumPages() const { return this->GetSize() / PageSize; } + constexpr size_t GetNumFreeBlocks() const { return m_bitmap.GetNumBits(); } + constexpr size_t GetNumFreePages() const { return this->GetNumFreeBlocks() * this->GetNumPages(); } + + u64 *Initialize(KPhysicalAddress addr, size_t size, size_t bs, size_t nbs, u64 *bit_storage) { + /* Set shifts. */ + m_block_shift = bs; + m_next_block_shift = nbs; + + /* Align up the address. */ + KPhysicalAddress end = addr + size; + const size_t align = (m_next_block_shift != 0) ? (u64(1) << m_next_block_shift) : (u64(1) << m_block_shift); + addr = util::AlignDown(GetInteger(addr), align); + end = util::AlignUp(GetInteger(end), align); + + m_heap_address = addr; + m_end_offset = (end - addr) / (u64(1) << m_block_shift); + return m_bitmap.Initialize(bit_storage, m_end_offset); + } + + KPhysicalAddress PushBlock(KPhysicalAddress address) { + /* Set the bit for the free block. */ + size_t offset = (address - m_heap_address) >> this->GetShift(); + m_bitmap.SetBit(offset); + + /* If we have a next shift, try to clear the blocks below this one and return the new address. */ + if (this->GetNextShift()) { + const size_t diff = u64(1) << (this->GetNextShift() - this->GetShift()); + offset = util::AlignDown(offset, diff); + if (m_bitmap.ClearRange(offset, diff)) { + return m_heap_address + (offset << this->GetShift()); + } + } + + /* We couldn't coalesce, or we're already as big as possible. */ + return Null<KPhysicalAddress>; + } + + KPhysicalAddress PopBlock(bool random) { + /* Find a free block. */ + ssize_t soffset = m_bitmap.FindFreeBlock(random); + if (soffset < 0) { + return Null<KPhysicalAddress>; + } + const size_t offset = static_cast<size_t>(soffset); + + /* Update our tracking and return it. */ + m_bitmap.ClearBit(offset); + return m_heap_address + (offset << this->GetShift()); + } + public: + static constexpr size_t CalculateManagementOverheadSize(size_t region_size, size_t cur_block_shift, size_t next_block_shift) { + const size_t cur_block_size = (u64(1) << cur_block_shift); + const size_t next_block_size = (u64(1) << next_block_shift); + const size_t align = (next_block_shift != 0) ? next_block_size : cur_block_size; + return KPageBitmap::CalculateManagementOverheadSize((align * 2 + util::AlignUp(region_size, align)) / cur_block_size); + } + }; + private: + KPhysicalAddress m_heap_address; + size_t m_heap_size; + size_t m_initial_used_size; + size_t m_num_blocks; + Block m_blocks[NumMemoryBlockPageShifts]; + KPageBitmap::RandomBitGenerator m_rng; + private: + void Initialize(KPhysicalAddress heap_address, size_t heap_size, KVirtualAddress management_address, size_t management_size, const size_t *block_shifts, size_t num_block_shifts); + size_t GetNumFreePages() const; + + void FreeBlock(KPhysicalAddress block, s32 index); + public: + KPageHeap() : m_heap_address(Null<KPhysicalAddress>), m_heap_size(), m_initial_used_size(), m_num_blocks(), m_blocks(), m_rng() { /* ... */ } + + constexpr KPhysicalAddress GetAddress() const { return m_heap_address; } + constexpr size_t GetSize() const { return m_heap_size; } + constexpr KPhysicalAddress GetEndAddress() const { return this->GetAddress() + this->GetSize(); } + constexpr size_t GetPageOffset(KPhysicalAddress block) const { return (block - this->GetAddress()) / PageSize; } + constexpr size_t GetPageOffsetToEnd(KPhysicalAddress block) const { return (this->GetEndAddress() - block) / PageSize; } + + void Initialize(KPhysicalAddress heap_address, size_t heap_size, KVirtualAddress management_address, size_t management_size) { + return this->Initialize(heap_address, heap_size, management_address, management_size, MemoryBlockPageShifts, NumMemoryBlockPageShifts); + } + + size_t GetFreeSize() const { return this->GetNumFreePages() * PageSize; } + void DumpFreeList() const; + + void SetInitialUsedSize(size_t reserved_size) { + /* Check that the reserved size is valid. */ + const size_t free_size = this->GetNumFreePages() * PageSize; + MESOSPHERE_ABORT_UNLESS(m_heap_size >= free_size + reserved_size); + + /* Set the initial used size. */ + m_initial_used_size = m_heap_size - free_size - reserved_size; + } + + KPhysicalAddress AllocateBlock(s32 index, bool random) { + if (random) { + const size_t block_pages = m_blocks[index].GetNumPages(); + return this->AllocateByRandom(index, block_pages, block_pages); + } else { + return this->AllocateByLinearSearch(index); + } + } + + KPhysicalAddress AllocateAligned(s32 index, size_t num_pages, size_t align_pages) { + /* TODO: linear search support? */ + return this->AllocateByRandom(index, num_pages, align_pages); + } + + void Free(KPhysicalAddress addr, size_t num_pages); + private: + KPhysicalAddress AllocateByLinearSearch(s32 index); + KPhysicalAddress AllocateByRandom(s32 index, size_t num_pages, size_t align_pages); + + static size_t CalculateManagementOverheadSize(size_t region_size, const size_t *block_shifts, size_t num_block_shifts); + public: + static size_t CalculateManagementOverheadSize(size_t region_size) { + return CalculateManagementOverheadSize(region_size, MemoryBlockPageShifts, NumMemoryBlockPageShifts); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp new file mode 100644 index 00000000..7b2c722e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp @@ -0,0 +1,549 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_select_page_table_impl.hpp> +#include <mesosphere/kern_k_light_lock.hpp> +#include <mesosphere/kern_k_page_group.hpp> +#include <mesosphere/kern_k_memory_manager.hpp> +#include <mesosphere/kern_k_memory_layout.hpp> +#include <mesosphere/kern_k_memory_block_manager.hpp> + +namespace ams::kern { + + enum DisableMergeAttribute : u8 { + DisableMergeAttribute_None = (0u << 0), + + DisableMergeAttribute_DisableHead = (1u << 0), + DisableMergeAttribute_DisableHeadAndBody = (1u << 1), + DisableMergeAttribute_EnableHeadAndBody = (1u << 2), + DisableMergeAttribute_DisableTail = (1u << 3), + DisableMergeAttribute_EnableTail = (1u << 4), + DisableMergeAttribute_EnableAndMergeHeadBodyTail = (1u << 5), + + DisableMergeAttribute_EnableHeadBodyTail = DisableMergeAttribute_EnableHeadAndBody | DisableMergeAttribute_EnableTail, + DisableMergeAttribute_DisableHeadBodyTail = DisableMergeAttribute_DisableHeadAndBody | DisableMergeAttribute_DisableTail, + }; + + struct KPageProperties { + KMemoryPermission perm; + bool io; + bool uncached; + DisableMergeAttribute disable_merge_attributes; + }; + static_assert(std::is_trivial<KPageProperties>::value); + static_assert(sizeof(KPageProperties) == sizeof(u32)); + + class KResourceLimit; + class KSystemResource; + + class KPageTableBase { + NON_COPYABLE(KPageTableBase); + NON_MOVEABLE(KPageTableBase); + public: + using TraversalEntry = KPageTableImpl::TraversalEntry; + using TraversalContext = KPageTableImpl::TraversalContext; + + class MemoryRange { + private: + KPhysicalAddress m_address; + size_t m_size; + bool m_heap; + u8 m_attr; + public: + constexpr MemoryRange() : m_address(Null<KPhysicalAddress>), m_size(0), m_heap(false), m_attr(0) { /* ... */ } + + void Set(KPhysicalAddress address, size_t size, bool heap, u8 attr) { + m_address = address; + m_size = size; + m_heap = heap; + m_attr = attr; + } + + constexpr KPhysicalAddress GetAddress() const { return m_address; } + constexpr size_t GetSize() const { return m_size; } + constexpr bool IsHeap() const { return m_heap; } + constexpr u8 GetAttribute() const { return m_attr; } + + void Open(); + void Close(); + }; + protected: + enum MemoryFillValue { + MemoryFillValue_Zero = 0, + MemoryFillValue_Stack = 'X', + MemoryFillValue_Ipc = 'Y', + MemoryFillValue_Heap = 'Z', + }; + + enum RegionType { + RegionType_KernelMap = 0, + RegionType_Stack = 1, + RegionType_Alias = 2, + RegionType_Heap = 3, + + RegionType_Count, + }; + + enum OperationType { + OperationType_Map = 0, + OperationType_MapGroup = 1, + OperationType_MapFirstGroup = 2, + OperationType_Unmap = 3, + OperationType_ChangePermissions = 4, + OperationType_ChangePermissionsAndRefresh = 5, + OperationType_ChangePermissionsAndRefreshAndFlush = 6, + OperationType_Separate = 7, + }; + + static constexpr size_t MaxPhysicalMapAlignment = 1_GB; + static constexpr size_t RegionAlignment = 2_MB; + static_assert(RegionAlignment == KernelAslrAlignment); + + struct PageLinkedList { + private: + struct Node { + Node *m_next; + u8 m_buffer[PageSize - sizeof(Node *)]; + }; + static_assert(util::is_pod<Node>::value); + private: + Node *m_root; + public: + constexpr PageLinkedList() : m_root(nullptr) { /* ... */ } + + void Push(Node *n) { + MESOSPHERE_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(n), PageSize)); + n->m_next = m_root; + m_root = n; + } + + void Push(KVirtualAddress addr) { + this->Push(GetPointer<Node>(addr)); + } + + Node *Peek() const { return m_root; } + + Node *Pop() { + Node * const r = m_root; + + m_root = r->m_next; + r->m_next = nullptr; + + return r; + } + }; + static_assert(std::is_trivially_destructible<PageLinkedList>::value); + + static constexpr u32 DefaultMemoryIgnoreAttr = KMemoryAttribute_IpcLocked | KMemoryAttribute_DeviceShared; + + static constexpr size_t GetAddressSpaceWidth(ams::svc::CreateProcessFlag as_type) { + switch (static_cast<ams::svc::CreateProcessFlag>(as_type & ams::svc::CreateProcessFlag_AddressSpaceMask)) { + case ams::svc::CreateProcessFlag_AddressSpace64Bit: + return 39; + case ams::svc::CreateProcessFlag_AddressSpace64BitDeprecated: + return 36; + case ams::svc::CreateProcessFlag_AddressSpace32Bit: + case ams::svc::CreateProcessFlag_AddressSpace32BitWithoutAlias: + return 32; + MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); + } + } + private: + class KScopedPageTableUpdater { + private: + KPageTableBase *m_pt; + PageLinkedList m_ll; + public: + ALWAYS_INLINE explicit KScopedPageTableUpdater(KPageTableBase *pt) : m_pt(pt), m_ll() { /* ... */ } + ALWAYS_INLINE explicit KScopedPageTableUpdater(KPageTableBase &pt) : KScopedPageTableUpdater(std::addressof(pt)) { /* ... */ } + ALWAYS_INLINE ~KScopedPageTableUpdater() { m_pt->FinalizeUpdate(this->GetPageList()); } + + PageLinkedList *GetPageList() { return std::addressof(m_ll); } + }; + private: + KProcessAddress m_address_space_start; + KProcessAddress m_address_space_end; + KProcessAddress m_region_starts[RegionType_Count]; + KProcessAddress m_region_ends[RegionType_Count]; + KProcessAddress m_current_heap_end; + KProcessAddress m_alias_code_region_start; + KProcessAddress m_alias_code_region_end; + KProcessAddress m_code_region_start; + KProcessAddress m_code_region_end; + size_t m_max_heap_size; + size_t m_mapped_physical_memory_size; + size_t m_mapped_unsafe_physical_memory; + size_t m_mapped_insecure_memory; + size_t m_mapped_ipc_server_memory; + size_t m_alias_region_extra_size; + mutable KLightLock m_general_lock; + mutable KLightLock m_map_physical_memory_lock; + KLightLock m_device_map_lock; + KPageTableImpl m_impl; + KMemoryBlockManager m_memory_block_manager; + u32 m_allocate_option; + u32 m_address_space_width; + bool m_is_kernel; + bool m_enable_aslr; + bool m_enable_device_address_space_merge; + KMemoryBlockSlabManager *m_memory_block_slab_manager; + KBlockInfoManager *m_block_info_manager; + KResourceLimit *m_resource_limit; + const KMemoryRegion *m_cached_physical_linear_region; + const KMemoryRegion *m_cached_physical_heap_region; + MemoryFillValue m_heap_fill_value; + MemoryFillValue m_ipc_fill_value; + MemoryFillValue m_stack_fill_value; + public: + constexpr explicit KPageTableBase(util::ConstantInitializeTag) + : m_address_space_start(Null<KProcessAddress>), m_address_space_end(Null<KProcessAddress>), + m_region_starts{Null<KProcessAddress>, Null<KProcessAddress>, Null<KProcessAddress>, Null<KProcessAddress>}, + m_region_ends{Null<KProcessAddress>, Null<KProcessAddress>, Null<KProcessAddress>, Null<KProcessAddress>}, + m_current_heap_end(Null<KProcessAddress>), m_alias_code_region_start(Null<KProcessAddress>), + m_alias_code_region_end(Null<KProcessAddress>), m_code_region_start(Null<KProcessAddress>), m_code_region_end(Null<KProcessAddress>), + m_max_heap_size(), m_mapped_physical_memory_size(), m_mapped_unsafe_physical_memory(), m_mapped_insecure_memory(), m_mapped_ipc_server_memory(), m_alias_region_extra_size(), + m_general_lock(), m_map_physical_memory_lock(), m_device_map_lock(), m_impl(util::ConstantInitialize), m_memory_block_manager(util::ConstantInitialize), + m_allocate_option(), m_address_space_width(), m_is_kernel(), m_enable_aslr(), m_enable_device_address_space_merge(), + m_memory_block_slab_manager(), m_block_info_manager(), m_resource_limit(), m_cached_physical_linear_region(), m_cached_physical_heap_region(), + m_heap_fill_value(), m_ipc_fill_value(), m_stack_fill_value() + { + /* ... */ + } + + explicit KPageTableBase() { /* ... */ } + + NOINLINE Result InitializeForKernel(bool is_64_bit, void *table, KVirtualAddress start, KVirtualAddress end); + NOINLINE Result InitializeForProcess(ams::svc::CreateProcessFlag flags, bool from_back, KMemoryManager::Pool pool, void *table, KProcessAddress start, KProcessAddress end, KProcessAddress code_address, size_t code_size, KSystemResource *system_resource, KResourceLimit *resource_limit); + + void Finalize(); + + constexpr bool IsKernel() const { return m_is_kernel; } + constexpr bool IsAslrEnabled() const { return m_enable_aslr; } + + constexpr bool Contains(KProcessAddress addr) const { + return m_address_space_start <= addr && addr <= m_address_space_end - 1; + } + + constexpr bool Contains(KProcessAddress addr, size_t size) const { + return m_address_space_start <= addr && addr < addr + size && addr + size - 1 <= m_address_space_end - 1; + } + + constexpr bool IsInAliasRegion(KProcessAddress addr, size_t size) const { + return this->Contains(addr, size) && m_region_starts[RegionType_Alias] <= addr && addr + size - 1 <= m_region_ends[RegionType_Alias] - 1; + } + + bool IsInUnsafeAliasRegion(KProcessAddress addr, size_t size) const { + /* Even though Unsafe physical memory is KMemoryState_Normal, it must be mapped inside the alias code region. */ + return this->CanContain(addr, size, ams::svc::MemoryState_AliasCode); + } + + ALWAYS_INLINE KScopedLightLock AcquireDeviceMapLock() { + return KScopedLightLock(m_device_map_lock); + } + + KProcessAddress GetRegionAddress(ams::svc::MemoryState state) const; + size_t GetRegionSize(ams::svc::MemoryState state) const; + bool CanContain(KProcessAddress addr, size_t size, ams::svc::MemoryState state) const; + + ALWAYS_INLINE KProcessAddress GetRegionAddress(KMemoryState state) const { return this->GetRegionAddress(static_cast<ams::svc::MemoryState>(state & KMemoryState_Mask)); } + ALWAYS_INLINE size_t GetRegionSize(KMemoryState state) const { return this->GetRegionSize(static_cast<ams::svc::MemoryState>(state & KMemoryState_Mask)); } + ALWAYS_INLINE bool CanContain(KProcessAddress addr, size_t size, KMemoryState state) const { return this->CanContain(addr, size, static_cast<ams::svc::MemoryState>(state & KMemoryState_Mask)); } + protected: + /* NOTE: These three functions (Operate, Operate, FinalizeUpdate) are virtual functions */ + /* in Nintendo's kernel. We devirtualize them, since KPageTable is the only derived */ + /* class, and this avoids unnecessary virtual function calls. See "kern_select_page_table.hpp" */ + /* for definition of these functions. */ + Result Operate(PageLinkedList *page_list, KProcessAddress virt_addr, size_t num_pages, KPhysicalAddress phys_addr, bool is_pa_valid, const KPageProperties properties, OperationType operation, bool reuse_ll); + Result Operate(PageLinkedList *page_list, KProcessAddress virt_addr, size_t num_pages, const KPageGroup &page_group, const KPageProperties properties, OperationType operation, bool reuse_ll); + void FinalizeUpdate(PageLinkedList *page_list); + + ALWAYS_INLINE KPageTableImpl &GetImpl() { return m_impl; } + ALWAYS_INLINE const KPageTableImpl &GetImpl() const { return m_impl; } + + ALWAYS_INLINE bool IsLockedByCurrentThread() const { return m_general_lock.IsLockedByCurrentThread(); } + + ALWAYS_INLINE bool IsLinearMappedPhysicalAddress(KPhysicalAddress phys_addr) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + return KMemoryLayout::IsLinearMappedPhysicalAddress(m_cached_physical_linear_region, phys_addr); + } + + ALWAYS_INLINE bool IsLinearMappedPhysicalAddress(KPhysicalAddress phys_addr, size_t size) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + return KMemoryLayout::IsLinearMappedPhysicalAddress(m_cached_physical_linear_region, phys_addr, size); + } + + ALWAYS_INLINE bool IsHeapPhysicalAddress(KPhysicalAddress phys_addr) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + return KMemoryLayout::IsHeapPhysicalAddress(m_cached_physical_heap_region, phys_addr); + } + + ALWAYS_INLINE bool IsHeapPhysicalAddress(KPhysicalAddress phys_addr, size_t size) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + return KMemoryLayout::IsHeapPhysicalAddress(m_cached_physical_heap_region, phys_addr, size); + } + + ALWAYS_INLINE bool IsHeapPhysicalAddressForFinalize(KPhysicalAddress phys_addr) { + MESOSPHERE_ASSERT(!this->IsLockedByCurrentThread()); + + return KMemoryLayout::IsHeapPhysicalAddress(m_cached_physical_heap_region, phys_addr); + } + + ALWAYS_INLINE bool ContainsPages(KProcessAddress addr, size_t num_pages) const { + return (m_address_space_start <= addr) && (num_pages <= (m_address_space_end - m_address_space_start) / PageSize) && (addr + num_pages * PageSize - 1 <= m_address_space_end - 1); + } + private: + constexpr size_t GetNumGuardPages() const { return this->IsKernel() ? 1 : 4; } + ALWAYS_INLINE KProcessAddress FindFreeArea(KProcessAddress region_start, size_t region_num_pages, size_t num_pages, size_t alignment, size_t offset, size_t guard_pages) const; + + Result CheckMemoryStateContiguous(size_t *out_blocks_needed, KProcessAddress addr, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr) const; + Result CheckMemoryStateContiguous(KProcessAddress addr, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr) const { + R_RETURN(this->CheckMemoryStateContiguous(nullptr, addr, size, state_mask, state, perm_mask, perm, attr_mask, attr)); + } + + Result CheckMemoryState(KMemoryBlockManager::const_iterator it, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr) const; + Result CheckMemoryState(KMemoryState *out_state, KMemoryPermission *out_perm, KMemoryAttribute *out_attr, size_t *out_blocks_needed, KMemoryBlockManager::const_iterator it, KProcessAddress last_addr, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr, u32 ignore_attr = DefaultMemoryIgnoreAttr) const; + Result CheckMemoryState(KMemoryState *out_state, KMemoryPermission *out_perm, KMemoryAttribute *out_attr, size_t *out_blocks_needed, KProcessAddress addr, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr, u32 ignore_attr = DefaultMemoryIgnoreAttr) const; + Result CheckMemoryState(size_t *out_blocks_needed, KProcessAddress addr, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr, u32 ignore_attr = DefaultMemoryIgnoreAttr) const { + R_RETURN(this->CheckMemoryState(nullptr, nullptr, nullptr, out_blocks_needed, addr, size, state_mask, state, perm_mask, perm, attr_mask, attr, ignore_attr)); + } + Result CheckMemoryState(KProcessAddress addr, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr, u32 ignore_attr = DefaultMemoryIgnoreAttr) const { + R_RETURN(this->CheckMemoryState(nullptr, addr, size, state_mask, state, perm_mask, perm, attr_mask, attr, ignore_attr)); + } + + bool CanReadWriteDebugMemory(KProcessAddress addr, size_t size, bool force_debug_prod); + + Result LockMemoryAndOpen(KPageGroup *out_pg, KPhysicalAddress *out_paddr, KProcessAddress addr, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr, KMemoryPermission new_perm, u32 lock_attr); + Result UnlockMemory(KProcessAddress addr, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr, KMemoryPermission new_perm, u32 lock_attr, const KPageGroup *pg); + + Result QueryInfoImpl(KMemoryInfo *out_info, ams::svc::PageInfo *out_page, KProcessAddress address) const; + + Result QueryMappingImpl(KProcessAddress *out, KPhysicalAddress address, size_t size, ams::svc::MemoryState state) const; + + Result AllocateAndMapPagesImpl(PageLinkedList *page_list, KProcessAddress address, size_t num_pages, const KPageProperties &properties); + Result MapPageGroupImpl(PageLinkedList *page_list, KProcessAddress address, const KPageGroup &pg, const KPageProperties properties, bool reuse_ll); + + void RemapPageGroup(PageLinkedList *page_list, KProcessAddress address, size_t size, const KPageGroup &pg); + + Result MakePageGroup(KPageGroup &pg, KProcessAddress addr, size_t num_pages); + bool IsValidPageGroup(const KPageGroup &pg, KProcessAddress addr, size_t num_pages); + + Result GetContiguousMemoryRangeWithState(MemoryRange *out, KProcessAddress address, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr); + + NOINLINE Result MapPages(KProcessAddress *out_addr, size_t num_pages, size_t alignment, KPhysicalAddress phys_addr, bool is_pa_valid, KProcessAddress region_start, size_t region_num_pages, KMemoryState state, KMemoryPermission perm); + + Result MapIoImpl(KProcessAddress *out, PageLinkedList *page_list, KPhysicalAddress phys_addr, size_t size, KMemoryState state, KMemoryPermission perm); + Result ReadIoMemoryImpl(void *buffer, KPhysicalAddress phys_addr, size_t size, KMemoryState state); + Result WriteIoMemoryImpl(KPhysicalAddress phys_addr, const void *buffer, size_t size, KMemoryState state); + + Result SetupForIpcClient(PageLinkedList *page_list, size_t *out_blocks_needed, KProcessAddress address, size_t size, KMemoryPermission test_perm, KMemoryState dst_state); + Result SetupForIpcServer(KProcessAddress *out_addr, size_t size, KProcessAddress src_addr, KMemoryPermission test_perm, KMemoryState dst_state, KPageTableBase &src_page_table, bool send); + void CleanupForIpcClientOnServerSetupFailure(PageLinkedList *page_list, KProcessAddress address, size_t size, KMemoryPermission prot_perm); + + size_t GetSize(KMemoryState state) const; + + ALWAYS_INLINE bool GetPhysicalAddressLocked(KPhysicalAddress *out, KProcessAddress virt_addr) const { + /* Validate pre-conditions. */ + MESOSPHERE_AUDIT(this->IsLockedByCurrentThread()); + + return this->GetImpl().GetPhysicalAddress(out, virt_addr); + } + public: + bool GetPhysicalAddress(KPhysicalAddress *out, KProcessAddress virt_addr) const { + /* Validate pre-conditions. */ + MESOSPHERE_AUDIT(!this->IsLockedByCurrentThread()); + + /* Acquire exclusive access to the table while doing address translation. */ + KScopedLightLock lk(m_general_lock); + + return this->GetPhysicalAddressLocked(out, virt_addr); + } + + KBlockInfoManager *GetBlockInfoManager() const { return m_block_info_manager; } + + Result SetMemoryPermission(KProcessAddress addr, size_t size, ams::svc::MemoryPermission perm); + Result SetProcessMemoryPermission(KProcessAddress addr, size_t size, ams::svc::MemoryPermission perm); + Result SetMemoryAttribute(KProcessAddress addr, size_t size, u32 mask, u32 attr); + Result SetHeapSize(KProcessAddress *out, size_t size); + Result SetMaxHeapSize(size_t size); + Result QueryInfo(KMemoryInfo *out_info, ams::svc::PageInfo *out_page_info, KProcessAddress addr) const; + Result QueryPhysicalAddress(ams::svc::PhysicalMemoryInfo *out, KProcessAddress address) const; + Result QueryStaticMapping(KProcessAddress *out, KPhysicalAddress address, size_t size) const { R_RETURN(this->QueryMappingImpl(out, address, size, ams::svc::MemoryState_Static)); } + Result QueryIoMapping(KProcessAddress *out, KPhysicalAddress address, size_t size) const { R_RETURN(this->QueryMappingImpl(out, address, size, ams::svc::MemoryState_Io)); } + Result MapMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size); + Result UnmapMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size); + Result MapCodeMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size); + Result UnmapCodeMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size); + Result MapIo(KPhysicalAddress phys_addr, size_t size, KMemoryPermission perm); + Result MapIoRegion(KProcessAddress dst_address, KPhysicalAddress phys_addr, size_t size, ams::svc::MemoryMapping mapping, ams::svc::MemoryPermission perm); + Result UnmapIoRegion(KProcessAddress dst_address, KPhysicalAddress phys_addr, size_t size, ams::svc::MemoryMapping mapping); + Result MapStatic(KPhysicalAddress phys_addr, size_t size, KMemoryPermission perm); + Result MapRegion(KMemoryRegionType region_type, KMemoryPermission perm); + Result MapInsecurePhysicalMemory(KProcessAddress address, size_t size); + Result UnmapInsecurePhysicalMemory(KProcessAddress address, size_t size); + + Result MapPages(KProcessAddress *out_addr, size_t num_pages, size_t alignment, KPhysicalAddress phys_addr, KProcessAddress region_start, size_t region_num_pages, KMemoryState state, KMemoryPermission perm) { + R_RETURN(this->MapPages(out_addr, num_pages, alignment, phys_addr, true, region_start, region_num_pages, state, perm)); + } + + Result MapPages(KProcessAddress *out_addr, size_t num_pages, size_t alignment, KPhysicalAddress phys_addr, KMemoryState state, KMemoryPermission perm) { + R_RETURN(this->MapPages(out_addr, num_pages, alignment, phys_addr, true, this->GetRegionAddress(state), this->GetRegionSize(state) / PageSize, state, perm)); + } + + Result MapPages(KProcessAddress *out_addr, size_t num_pages, KMemoryState state, KMemoryPermission perm) { + R_RETURN(this->MapPages(out_addr, num_pages, PageSize, Null<KPhysicalAddress>, false, this->GetRegionAddress(state), this->GetRegionSize(state) / PageSize, state, perm)); + } + + Result MapPages(KProcessAddress address, size_t num_pages, KMemoryState state, KMemoryPermission perm); + Result UnmapPages(KProcessAddress address, size_t num_pages, KMemoryState state); + + Result MapPageGroup(KProcessAddress *out_addr, const KPageGroup &pg, KProcessAddress region_start, size_t region_num_pages, KMemoryState state, KMemoryPermission perm); + Result MapPageGroup(KProcessAddress address, const KPageGroup &pg, KMemoryState state, KMemoryPermission perm); + Result UnmapPageGroup(KProcessAddress address, const KPageGroup &pg, KMemoryState state); + + Result MakeAndOpenPageGroup(KPageGroup *out, KProcessAddress address, size_t num_pages, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr); + + Result InvalidateProcessDataCache(KProcessAddress address, size_t size); + Result InvalidateCurrentProcessDataCache(KProcessAddress address, size_t size); + + Result ReadDebugMemory(void *buffer, KProcessAddress address, size_t size, bool force_debug_prod); + Result ReadDebugIoMemory(void *buffer, KProcessAddress address, size_t size, KMemoryState state); + + Result WriteDebugMemory(KProcessAddress address, const void *buffer, size_t size); + Result WriteDebugIoMemory(KProcessAddress address, const void *buffer, size_t size, KMemoryState state); + + Result LockForMapDeviceAddressSpace(bool *out_is_io, KProcessAddress address, size_t size, KMemoryPermission perm, bool is_aligned, bool check_heap); + Result LockForUnmapDeviceAddressSpace(KProcessAddress address, size_t size, bool check_heap); + + Result UnlockForDeviceAddressSpace(KProcessAddress address, size_t size); + Result UnlockForDeviceAddressSpacePartialMap(KProcessAddress address, size_t size); + + Result OpenMemoryRangeForMapDeviceAddressSpace(KPageTableBase::MemoryRange *out, KProcessAddress address, size_t size, KMemoryPermission perm, bool is_aligned); + Result OpenMemoryRangeForUnmapDeviceAddressSpace(MemoryRange *out, KProcessAddress address, size_t size); + + Result LockForIpcUserBuffer(KPhysicalAddress *out, KProcessAddress address, size_t size); + Result UnlockForIpcUserBuffer(KProcessAddress address, size_t size); + + Result LockForTransferMemory(KPageGroup *out, KProcessAddress address, size_t size, KMemoryPermission perm); + Result UnlockForTransferMemory(KProcessAddress address, size_t size, const KPageGroup &pg); + Result LockForCodeMemory(KPageGroup *out, KProcessAddress address, size_t size); + Result UnlockForCodeMemory(KProcessAddress address, size_t size, const KPageGroup &pg); + + Result OpenMemoryRangeForProcessCacheOperation(MemoryRange *out, KProcessAddress address, size_t size); + + Result CopyMemoryFromLinearToUser(KProcessAddress dst_addr, size_t size, KProcessAddress src_addr, u32 src_state_mask, u32 src_state, KMemoryPermission src_test_perm, u32 src_attr_mask, u32 src_attr); + Result CopyMemoryFromLinearToKernel(KProcessAddress dst_addr, size_t size, KProcessAddress src_addr, u32 src_state_mask, u32 src_state, KMemoryPermission src_test_perm, u32 src_attr_mask, u32 src_attr); + Result CopyMemoryFromUserToLinear(KProcessAddress dst_addr, size_t size, u32 dst_state_mask, u32 dst_state, KMemoryPermission dst_test_perm, u32 dst_attr_mask, u32 dst_attr, KProcessAddress src_addr); + Result CopyMemoryFromKernelToLinear(KProcessAddress dst_addr, size_t size, u32 dst_state_mask, u32 dst_state, KMemoryPermission dst_test_perm, u32 dst_attr_mask, u32 dst_attr, KProcessAddress src_addr); + Result CopyMemoryFromHeapToHeap(KPageTableBase &dst_page_table, KProcessAddress dst_addr, size_t size, u32 dst_state_mask, u32 dst_state, KMemoryPermission dst_test_perm, u32 dst_attr_mask, u32 dst_attr, KProcessAddress src_addr, u32 src_state_mask, u32 src_state, KMemoryPermission src_test_perm, u32 src_attr_mask, u32 src_attr); + Result CopyMemoryFromHeapToHeapWithoutCheckDestination(KPageTableBase &dst_page_table, KProcessAddress dst_addr, size_t size, u32 dst_state_mask, u32 dst_state, KMemoryPermission dst_test_perm, u32 dst_attr_mask, u32 dst_attr, KProcessAddress src_addr, u32 src_state_mask, u32 src_state, KMemoryPermission src_test_perm, u32 src_attr_mask, u32 src_attr); + + Result SetupForIpc(KProcessAddress *out_dst_addr, size_t size, KProcessAddress src_addr, KPageTableBase &src_page_table, KMemoryPermission test_perm, KMemoryState dst_state, bool send); + Result CleanupForIpcServer(KProcessAddress address, size_t size, KMemoryState dst_state); + Result CleanupForIpcClient(KProcessAddress address, size_t size, KMemoryState dst_state); + + Result MapPhysicalMemory(KProcessAddress address, size_t size); + Result UnmapPhysicalMemory(KProcessAddress address, size_t size); + + Result MapPhysicalMemoryUnsafe(KProcessAddress address, size_t size); + Result UnmapPhysicalMemoryUnsafe(KProcessAddress address, size_t size); + + Result UnmapProcessMemory(KProcessAddress dst_address, size_t size, KPageTableBase &src_pt, KProcessAddress src_address); + + void DumpMemoryBlocksLocked() const { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + m_memory_block_manager.DumpBlocks(); + } + + void DumpMemoryBlocks() const { + KScopedLightLock lk(m_general_lock); + this->DumpMemoryBlocksLocked(); + } + + void DumpPageTable() const { + KScopedLightLock lk(m_general_lock); + this->GetImpl().Dump(GetInteger(m_address_space_start), m_address_space_end - m_address_space_start); + } + + size_t CountPageTables() const { + KScopedLightLock lk(m_general_lock); + return this->GetImpl().CountPageTables(); + } + public: + KProcessAddress GetAddressSpaceStart() const { return m_address_space_start; } + + KProcessAddress GetHeapRegionStart() const { return m_region_starts[RegionType_Heap]; } + KProcessAddress GetAliasRegionStart() const { return m_region_starts[RegionType_Alias]; } + KProcessAddress GetStackRegionStart() const { return m_region_starts[RegionType_Stack]; } + KProcessAddress GetKernelMapRegionStart() const { return m_region_starts[RegionType_KernelMap]; } + + KProcessAddress GetAliasCodeRegionStart() const { return m_alias_code_region_start; } + + size_t GetAddressSpaceSize() const { return m_address_space_end - m_address_space_start; } + + size_t GetHeapRegionSize() const { return m_region_ends[RegionType_Heap] - m_region_starts[RegionType_Heap]; } + size_t GetAliasRegionSize() const { return m_region_ends[RegionType_Alias] - m_region_starts[RegionType_Alias]; } + size_t GetStackRegionSize() const { return m_region_ends[RegionType_Stack] - m_region_starts[RegionType_Stack]; } + size_t GetKernelMapRegionSize() const { return m_region_ends[RegionType_KernelMap] - m_region_starts[RegionType_KernelMap]; } + + size_t GetAliasCodeRegionSize() const { return m_alias_code_region_end - m_alias_code_region_start; } + + size_t GetAliasRegionExtraSize() const { return m_alias_region_extra_size; } + + size_t GetNormalMemorySize() const { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + return (m_current_heap_end - m_region_starts[RegionType_Heap]) + m_mapped_physical_memory_size; + } + + size_t GetCodeSize() const; + size_t GetCodeDataSize() const; + size_t GetAliasCodeSize() const; + size_t GetAliasCodeDataSize() const; + + u32 GetAllocateOption() const { return m_allocate_option; } + public: + static ALWAYS_INLINE KVirtualAddress GetLinearMappedVirtualAddress(KPhysicalAddress addr) { + return KMemoryLayout::GetLinearVirtualAddress(addr); + } + + static ALWAYS_INLINE KPhysicalAddress GetLinearMappedPhysicalAddress(KVirtualAddress addr) { + return KMemoryLayout::GetLinearPhysicalAddress(addr); + } + + static ALWAYS_INLINE KVirtualAddress GetHeapVirtualAddress(KPhysicalAddress addr) { + return GetLinearMappedVirtualAddress(addr); + } + + static ALWAYS_INLINE KPhysicalAddress GetHeapPhysicalAddress(KVirtualAddress addr) { + return GetLinearMappedPhysicalAddress(addr); + } + + static ALWAYS_INLINE KVirtualAddress GetPageTableVirtualAddress(KPhysicalAddress addr) { + return GetLinearMappedVirtualAddress(addr); + } + + static ALWAYS_INLINE KPhysicalAddress GetPageTablePhysicalAddress(KVirtualAddress addr) { + return GetLinearMappedPhysicalAddress(addr); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_table_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_table_manager.hpp new file mode 100644 index 00000000..bf1546b6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_table_manager.hpp @@ -0,0 +1,67 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_page_table_slab_heap.hpp> +#include <mesosphere/kern_k_dynamic_resource_manager.hpp> + +namespace ams::kern { + + class KPageTableManager : public KDynamicResourceManager<impl::PageTablePage, true> { + public: + using RefCount = KPageTableSlabHeap::RefCount; + static constexpr size_t PageTableSize = KPageTableSlabHeap::PageTableSize; + private: + using BaseHeap = KDynamicResourceManager<impl::PageTablePage, true>; + private: + KPageTableSlabHeap *m_pt_heap; + public: + constexpr explicit KPageTableManager(util::ConstantInitializeTag) : m_pt_heap() { /* ... */ } + explicit KPageTableManager() { /* ... */ } + + ALWAYS_INLINE void Initialize(KDynamicPageManager *page_allocator, KPageTableSlabHeap *pt_heap) { + m_pt_heap = pt_heap; + + static_assert(std::derived_from<KPageTableSlabHeap, DynamicSlabType>); + BaseHeap::Initialize(page_allocator, pt_heap); + } + + KVirtualAddress Allocate() { + return KVirtualAddress(BaseHeap::Allocate()); + } + + void Free(KVirtualAddress addr) { + return BaseHeap::Free(GetPointer<impl::PageTablePage>(addr)); + } + + ALWAYS_INLINE RefCount GetRefCount(KVirtualAddress addr) const { + return m_pt_heap->GetRefCount(addr); + } + + ALWAYS_INLINE void Open(KVirtualAddress addr, int count) { + return m_pt_heap->Open(addr, count); + } + + ALWAYS_INLINE bool Close(KVirtualAddress addr, int count) { + return m_pt_heap->Close(addr, count); + } + + constexpr ALWAYS_INLINE bool IsInPageTableHeap(KVirtualAddress addr) const { + return m_pt_heap->IsInRange(addr); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_table_slab_heap.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_table_slab_heap.hpp new file mode 100644 index 00000000..fae3ad46 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_page_table_slab_heap.hpp @@ -0,0 +1,93 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_slab_helpers.hpp> +#include <mesosphere/kern_k_dynamic_slab_heap.hpp> + +namespace ams::kern { + + namespace impl { + + class PageTablePage { + private: + u8 m_buffer[PageSize]; + public: + ALWAYS_INLINE PageTablePage() { /* Do not initialize anything. */ } + }; + static_assert(sizeof(PageTablePage) == PageSize); + + } + + class KPageTableSlabHeap : public KDynamicSlabHeap<impl::PageTablePage, true> { + public: + using RefCount = u16; + static constexpr size_t PageTableSize = sizeof(impl::PageTablePage); + static_assert(PageTableSize == PageSize); + private: + using BaseHeap = KDynamicSlabHeap<impl::PageTablePage, true>; + private: + RefCount *m_ref_counts{}; + public: + static constexpr ALWAYS_INLINE size_t CalculateReferenceCountSize(size_t size) { + return (size / PageSize) * sizeof(RefCount); + } + public: + constexpr KPageTableSlabHeap() = default; + private: + ALWAYS_INLINE void Initialize(RefCount *rc) { + m_ref_counts = rc; + for (size_t i = 0; i < this->GetSize() / PageSize; i++) { + m_ref_counts[i] = 0; + } + } + + constexpr ALWAYS_INLINE RefCount *GetRefCountPointer(KVirtualAddress addr) const { + return m_ref_counts + ((addr - this->GetAddress()) / PageSize); + } + public: + ALWAYS_INLINE void Initialize(KDynamicPageManager *page_allocator, size_t object_count, RefCount *rc) { + BaseHeap::Initialize(page_allocator, object_count); + this->Initialize(rc); + } + + ALWAYS_INLINE RefCount GetRefCount(KVirtualAddress addr) const { + MESOSPHERE_ASSERT(this->IsInRange(addr)); + return *this->GetRefCountPointer(addr); + } + + ALWAYS_INLINE void Open(KVirtualAddress addr, int count) { + MESOSPHERE_ASSERT(this->IsInRange(addr)); + + *this->GetRefCountPointer(addr) += count; + + MESOSPHERE_ABORT_UNLESS(this->GetRefCount(addr) > 0); + } + + ALWAYS_INLINE bool Close(KVirtualAddress addr, int count) { + MESOSPHERE_ASSERT(this->IsInRange(addr)); + MESOSPHERE_ABORT_UNLESS(this->GetRefCount(addr) >= count); + + *this->GetRefCountPointer(addr) -= count; + return this->GetRefCount(addr) == 0; + } + + constexpr ALWAYS_INLINE bool IsInPageTableHeap(KVirtualAddress addr) const { + return this->IsInRange(addr); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_port.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_port.hpp new file mode 100644 index 00000000..ee545a9f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_port.hpp @@ -0,0 +1,71 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_synchronization_object.hpp> +#include <mesosphere/kern_k_client_port.hpp> +#include <mesosphere/kern_k_server_port.hpp> +#include <mesosphere/kern_slab_helpers.hpp> + +namespace ams::kern { + + class KServerSession; + class KLightServerSession; + + class KPort final : public KAutoObjectWithSlabHeapAndContainer<KPort, KAutoObjectWithList> { + MESOSPHERE_AUTOOBJECT_TRAITS(KPort, KAutoObject); + private: + enum class State : u8 { + Invalid = 0, + Normal = 1, + ClientClosed = 2, + ServerClosed = 3, + }; + private: + KServerPort m_server; + KClientPort m_client; + uintptr_t m_name; + State m_state; + bool m_is_light; + public: + explicit KPort() : m_state(State::Invalid), m_is_light() { /* ... */ } + + static void PostDestroy(uintptr_t arg) { MESOSPHERE_UNUSED(arg); /* ... */ } + + void Initialize(s32 max_sessions, bool is_light, uintptr_t name); + void Finalize() { /* ... */ } + + void OnClientClosed(); + void OnServerClosed(); + + uintptr_t GetName() const { return m_name; } + bool IsLight() const { return m_is_light; } + + bool IsServerClosed() const { + KScopedSchedulerLock sl; + return m_state == State::ServerClosed; + } + + Result EnqueueSession(KServerSession *session); + Result EnqueueSession(KLightServerSession *session); + + KClientPort &GetClientPort() { return m_client; } + KServerPort &GetServerPort() { return m_server; } + const KClientPort &GetClientPort() const { return m_client; } + const KServerPort &GetServerPort() const { return m_server; } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_priority_queue.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_priority_queue.hpp new file mode 100644 index 00000000..87d3fb1b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_priority_queue.hpp @@ -0,0 +1,419 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +namespace ams::kern { + + template<typename T> + concept KPriorityQueueAffinityMask = !std::is_reference<T>::value && requires (T &t) { + { t.GetAffinityMask() } -> std::convertible_to<u64>; + { t.SetAffinityMask(std::declval<u64>()) }; + + { t.GetAffinity(std::declval<int32_t>()) } -> std::same_as<bool>; + { t.SetAffinity(std::declval<int32_t>(), std::declval<bool>()) }; + { t.SetAll() }; + }; + + template<typename T> + concept KPriorityQueueMember = !std::is_reference<T>::value && requires (T &t) { + { typename T::QueueEntry() }; + { (typename T::QueueEntry()).Initialize() }; + { (typename T::QueueEntry()).SetPrev(std::addressof(t)) }; + { (typename T::QueueEntry()).SetNext(std::addressof(t)) }; + { (typename T::QueueEntry()).GetNext() } -> std::same_as<T*>; + { (typename T::QueueEntry()).GetPrev() } -> std::same_as<T*>; + { t.GetPriorityQueueEntry(std::declval<s32>()) } -> std::same_as<typename T::QueueEntry &>; + + { t.GetAffinityMask() }; + { typename std::remove_cvref<decltype(t.GetAffinityMask())>::type() } -> KPriorityQueueAffinityMask; + + { t.GetActiveCore() } -> std::convertible_to<s32>; + { t.GetPriority() } -> std::convertible_to<s32>; + }; + + template<typename Member, size_t _NumCores, int LowestPriority, int HighestPriority> requires KPriorityQueueMember<Member> + class KPriorityQueue { + public: + using AffinityMaskType = typename std::remove_cv<typename std::remove_reference<decltype(std::declval<Member>().GetAffinityMask())>::type>::type; + + static_assert(LowestPriority >= 0); + static_assert(HighestPriority >= 0); + static_assert(LowestPriority >= HighestPriority); + static constexpr size_t NumPriority = LowestPriority - HighestPriority + 1; + static constexpr size_t NumCores = _NumCores; + + static constexpr ALWAYS_INLINE bool IsValidCore(s32 core) { + return 0 <= core && core < static_cast<s32>(NumCores); + } + + static constexpr ALWAYS_INLINE bool IsValidPriority(s32 priority) { + return HighestPriority <= priority && priority <= LowestPriority + 1; + } + private: + using Entry = typename Member::QueueEntry; + public: + class KPerCoreQueue { + private: + Entry m_root[NumCores]; + public: + constexpr ALWAYS_INLINE KPerCoreQueue() : m_root() { + for (size_t i = 0; i < NumCores; i++) { + m_root[i].Initialize(); + } + } + + constexpr ALWAYS_INLINE bool PushBack(s32 core, Member *member) { + /* Get the entry associated with the member. */ + Entry &member_entry = member->GetPriorityQueueEntry(core); + + /* Get the entry associated with the end of the queue. */ + Member *tail = m_root[core].GetPrev(); + Entry &tail_entry = (tail != nullptr) ? tail->GetPriorityQueueEntry(core) : m_root[core]; + + /* Link the entries. */ + member_entry.SetPrev(tail); + member_entry.SetNext(nullptr); + tail_entry.SetNext(member); + m_root[core].SetPrev(member); + + return (tail == nullptr); + } + + constexpr ALWAYS_INLINE bool PushFront(s32 core, Member *member) { + /* Get the entry associated with the member. */ + Entry &member_entry = member->GetPriorityQueueEntry(core); + + /* Get the entry associated with the front of the queue. */ + Member *head = m_root[core].GetNext(); + Entry &head_entry = (head != nullptr) ? head->GetPriorityQueueEntry(core) : m_root[core]; + + /* Link the entries. */ + member_entry.SetPrev(nullptr); + member_entry.SetNext(head); + head_entry.SetPrev(member); + m_root[core].SetNext(member); + + return (head == nullptr); + } + + constexpr ALWAYS_INLINE bool Remove(s32 core, Member *member) { + /* Get the entry associated with the member. */ + Entry &member_entry = member->GetPriorityQueueEntry(core); + + /* Get the entries associated with next and prev. */ + Member *prev = member_entry.GetPrev(); + Member *next = member_entry.GetNext(); + Entry &prev_entry = (prev != nullptr) ? prev->GetPriorityQueueEntry(core) : m_root[core]; + Entry &next_entry = (next != nullptr) ? next->GetPriorityQueueEntry(core) : m_root[core]; + + /* Unlink. */ + prev_entry.SetNext(next); + next_entry.SetPrev(prev); + + return (this->GetFront(core) == nullptr); + } + + constexpr ALWAYS_INLINE Member *GetFront(s32 core) const { + return m_root[core].GetNext(); + } + }; + + class KPriorityQueueImpl { + private: + KPerCoreQueue m_queues[NumPriority]; + util::BitSet64<NumPriority> m_available_priorities[NumCores]; + public: + constexpr ALWAYS_INLINE KPriorityQueueImpl() : m_queues(), m_available_priorities() { /* ... */ } + + constexpr ALWAYS_INLINE void PushBack(s32 priority, s32 core, Member *member) { + MESOSPHERE_ASSERT(IsValidCore(core)); + MESOSPHERE_ASSERT(IsValidPriority(priority)); + + if (AMS_LIKELY(priority <= LowestPriority)) { + if (m_queues[priority].PushBack(core, member)) { + m_available_priorities[core].SetBit(priority); + } + } + } + + constexpr ALWAYS_INLINE void PushFront(s32 priority, s32 core, Member *member) { + MESOSPHERE_ASSERT(IsValidCore(core)); + MESOSPHERE_ASSERT(IsValidPriority(priority)); + + if (AMS_LIKELY(priority <= LowestPriority)) { + if (m_queues[priority].PushFront(core, member)) { + m_available_priorities[core].SetBit(priority); + } + } + } + + constexpr ALWAYS_INLINE void Remove(s32 priority, s32 core, Member *member) { + MESOSPHERE_ASSERT(IsValidCore(core)); + MESOSPHERE_ASSERT(IsValidPriority(priority)); + + if (AMS_LIKELY(priority <= LowestPriority)) { + if (m_queues[priority].Remove(core, member)) { + m_available_priorities[core].ClearBit(priority); + } + } + } + + constexpr ALWAYS_INLINE Member *GetFront(s32 core) const { + MESOSPHERE_ASSERT(IsValidCore(core)); + + const s32 priority = m_available_priorities[core].CountLeadingZero(); + if (AMS_LIKELY(priority <= LowestPriority)) { + return m_queues[priority].GetFront(core); + } else { + return nullptr; + } + } + + constexpr ALWAYS_INLINE Member *GetFront(s32 priority, s32 core) const { + MESOSPHERE_ASSERT(IsValidCore(core)); + MESOSPHERE_ASSERT(IsValidPriority(priority)); + + if (AMS_LIKELY(priority <= LowestPriority)) { + return m_queues[priority].GetFront(core); + } else { + return nullptr; + } + } + + constexpr ALWAYS_INLINE Member *GetNext(s32 core, const Member *member) const { + MESOSPHERE_ASSERT(IsValidCore(core)); + + Member *next = member->GetPriorityQueueEntry(core).GetNext(); + if (next == nullptr) { + const s32 priority = m_available_priorities[core].GetNextSet(member->GetPriority()); + if (AMS_LIKELY(priority <= LowestPriority)) { + next = m_queues[priority].GetFront(core); + } + } + return next; + } + + constexpr ALWAYS_INLINE void MoveToFront(s32 priority, s32 core, Member *member) { + MESOSPHERE_ASSERT(IsValidCore(core)); + MESOSPHERE_ASSERT(IsValidPriority(priority)); + + if (AMS_LIKELY(priority <= LowestPriority)) { + m_queues[priority].Remove(core, member); + m_queues[priority].PushFront(core, member); + } + } + + constexpr ALWAYS_INLINE Member *MoveToBack(s32 priority, s32 core, Member *member) { + MESOSPHERE_ASSERT(IsValidCore(core)); + MESOSPHERE_ASSERT(IsValidPriority(priority)); + + if (AMS_LIKELY(priority <= LowestPriority)) { + m_queues[priority].Remove(core, member); + m_queues[priority].PushBack(core, member); + return m_queues[priority].GetFront(core); + } else { + return nullptr; + } + } + }; + private: + KPriorityQueueImpl m_scheduled_queue; + KPriorityQueueImpl m_suggested_queue; + private: + static constexpr ALWAYS_INLINE void ClearAffinityBit(u64 &affinity, s32 core) { + affinity &= ~(UINT64_C(1) << core); + } + + static constexpr ALWAYS_INLINE s32 GetNextCore(u64 &affinity) { + const s32 core = __builtin_ctzll(static_cast<unsigned long long>(affinity)); + ClearAffinityBit(affinity, core); + return core; + } + + constexpr ALWAYS_INLINE void PushBack(s32 priority, Member *member) { + MESOSPHERE_ASSERT(IsValidPriority(priority)); + + /* Push onto the scheduled queue for its core, if we can. */ + u64 affinity = member->GetAffinityMask().GetAffinityMask(); + if (const s32 core = member->GetActiveCore(); core >= 0) { + m_scheduled_queue.PushBack(priority, core, member); + ClearAffinityBit(affinity, core); + } + + /* And suggest the thread for all other cores. */ + while (affinity) { + m_suggested_queue.PushBack(priority, GetNextCore(affinity), member); + } + } + + constexpr ALWAYS_INLINE void PushFront(s32 priority, Member *member) { + MESOSPHERE_ASSERT(IsValidPriority(priority)); + + /* Push onto the scheduled queue for its core, if we can. */ + u64 affinity = member->GetAffinityMask().GetAffinityMask(); + if (const s32 core = member->GetActiveCore(); core >= 0) { + m_scheduled_queue.PushFront(priority, core, member); + ClearAffinityBit(affinity, core); + } + + /* And suggest the thread for all other cores. */ + /* Note: Nintendo pushes onto the back of the suggested queue, not the front. */ + while (affinity) { + m_suggested_queue.PushBack(priority, GetNextCore(affinity), member); + } + } + + constexpr ALWAYS_INLINE void Remove(s32 priority, Member *member) { + MESOSPHERE_ASSERT(IsValidPriority(priority)); + + /* Remove from the scheduled queue for its core. */ + u64 affinity = member->GetAffinityMask().GetAffinityMask(); + if (const s32 core = member->GetActiveCore(); core >= 0) { + m_scheduled_queue.Remove(priority, core, member); + ClearAffinityBit(affinity, core); + } + + /* Remove from the suggested queue for all other cores. */ + while (affinity) { + m_suggested_queue.Remove(priority, GetNextCore(affinity), member); + } + } + public: + constexpr ALWAYS_INLINE KPriorityQueue() : m_scheduled_queue(), m_suggested_queue() { /* ... */ } + + /* Getters. */ + constexpr ALWAYS_INLINE Member *GetScheduledFront(s32 core) const { + return m_scheduled_queue.GetFront(core); + } + + constexpr ALWAYS_INLINE Member *GetScheduledFront(s32 core, s32 priority) const { + return m_scheduled_queue.GetFront(priority, core); + } + + constexpr ALWAYS_INLINE Member *GetSuggestedFront(s32 core) const { + return m_suggested_queue.GetFront(core); + } + + constexpr ALWAYS_INLINE Member *GetSuggestedFront(s32 core, s32 priority) const { + return m_suggested_queue.GetFront(priority, core); + } + + constexpr ALWAYS_INLINE Member *GetScheduledNext(s32 core, const Member *member) const { + return m_scheduled_queue.GetNext(core, member); + } + + constexpr ALWAYS_INLINE Member *GetSuggestedNext(s32 core, const Member *member) const { + return m_suggested_queue.GetNext(core, member); + } + + constexpr ALWAYS_INLINE Member *GetSamePriorityNext(s32 core, const Member *member) const { + return member->GetPriorityQueueEntry(core).GetNext(); + } + + /* Mutators. */ + constexpr ALWAYS_INLINE void PushBack(Member *member) { + this->PushBack(member->GetPriority(), member); + } + + constexpr ALWAYS_INLINE void Remove(Member *member) { + this->Remove(member->GetPriority(), member); + } + + constexpr ALWAYS_INLINE void MoveToScheduledFront(Member *member) { + m_scheduled_queue.MoveToFront(member->GetPriority(), member->GetActiveCore(), member); + } + + constexpr ALWAYS_INLINE KThread *MoveToScheduledBack(Member *member) { + return m_scheduled_queue.MoveToBack(member->GetPriority(), member->GetActiveCore(), member); + } + + /* First class fancy operations. */ + constexpr ALWAYS_INLINE void ChangePriority(s32 prev_priority, bool is_running, Member *member) { + MESOSPHERE_ASSERT(IsValidPriority(prev_priority)); + + /* Remove the member from the queues. */ + const s32 new_priority = member->GetPriority(); + this->Remove(prev_priority, member); + + /* And enqueue. If the member is running, we want to keep it running. */ + if (is_running) { + this->PushFront(new_priority, member); + } else { + this->PushBack(new_priority, member); + } + } + + constexpr ALWAYS_INLINE void ChangeAffinityMask(s32 prev_core, const AffinityMaskType &prev_affinity, Member *member) { + /* Get the new information. */ + const s32 priority = member->GetPriority(); + const AffinityMaskType &new_affinity = member->GetAffinityMask(); + const s32 new_core = member->GetActiveCore(); + + /* Remove the member from all queues it was in before. */ + for (s32 core = 0; core < static_cast<s32>(NumCores); core++) { + if (prev_affinity.GetAffinity(core)) { + if (core == prev_core) { + m_scheduled_queue.Remove(priority, core, member); + } else { + m_suggested_queue.Remove(priority, core, member); + } + } + } + + /* And add the member to all queues it should be in now. */ + for (s32 core = 0; core < static_cast<s32>(NumCores); core++) { + if (new_affinity.GetAffinity(core)) { + if (core == new_core) { + m_scheduled_queue.PushBack(priority, core, member); + } else { + m_suggested_queue.PushBack(priority, core, member); + } + } + } + } + + constexpr ALWAYS_INLINE void ChangeCore(s32 prev_core, Member *member, bool to_front = false) { + /* Get the new information. */ + const s32 new_core = member->GetActiveCore(); + const s32 priority = member->GetPriority(); + + /* We don't need to do anything if the core is the same. */ + if (prev_core != new_core) { + /* Remove from the scheduled queue for the previous core. */ + if (prev_core >= 0) { + m_scheduled_queue.Remove(priority, prev_core, member); + } + + /* Remove from the suggested queue and add to the scheduled queue for the new core. */ + if (new_core >= 0) { + m_suggested_queue.Remove(priority, new_core, member); + if (to_front) { + m_scheduled_queue.PushFront(priority, new_core, member); + } else { + m_scheduled_queue.PushBack(priority, new_core, member); + } + } + + /* Add to the suggested queue for the previous core. */ + if (prev_core >= 0) { + m_suggested_queue.PushBack(priority, prev_core, member); + } + } + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp new file mode 100644 index 00000000..849f73da --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp @@ -0,0 +1,431 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_select_cpu.hpp> +#include <mesosphere/kern_slab_helpers.hpp> +#include <mesosphere/kern_k_synchronization_object.hpp> +#include <mesosphere/kern_k_handle_table.hpp> +#include <mesosphere/kern_k_thread.hpp> +#include <mesosphere/kern_k_thread_local_page.hpp> +#include <mesosphere/kern_k_shared_memory_info.hpp> +#include <mesosphere/kern_k_io_region.hpp> +#include <mesosphere/kern_k_worker_task.hpp> +#include <mesosphere/kern_select_page_table.hpp> +#include <mesosphere/kern_k_condition_variable.hpp> +#include <mesosphere/kern_k_address_arbiter.hpp> +#include <mesosphere/kern_k_capabilities.hpp> +#include <mesosphere/kern_k_wait_object.hpp> +#include <mesosphere/kern_k_dynamic_resource_manager.hpp> +#include <mesosphere/kern_k_page_table_manager.hpp> +#include <mesosphere/kern_k_system_resource.hpp> + +namespace ams::kern { + + class KProcess final : public KAutoObjectWithSlabHeapAndContainer<KProcess, KWorkerTask> { + MESOSPHERE_AUTOOBJECT_TRAITS(KProcess, KSynchronizationObject); + public: + enum State { + State_Created = ams::svc::ProcessState_Created, + State_CreatedAttached = ams::svc::ProcessState_CreatedAttached, + State_Running = ams::svc::ProcessState_Running, + State_Crashed = ams::svc::ProcessState_Crashed, + State_RunningAttached = ams::svc::ProcessState_RunningAttached, + State_Terminating = ams::svc::ProcessState_Terminating, + State_Terminated = ams::svc::ProcessState_Terminated, + State_DebugBreak = ams::svc::ProcessState_DebugBreak, + }; + + using ThreadList = util::IntrusiveListMemberTraits<&KThread::m_process_list_node>::ListType; + + static constexpr size_t AslrAlignment = KernelAslrAlignment; + private: + using SharedMemoryInfoList = util::IntrusiveListBaseTraits<KSharedMemoryInfo>::ListType; + using IoRegionList = util::IntrusiveListMemberTraits<&KIoRegion::m_process_list_node>::ListType; + using TLPTree = util::IntrusiveRedBlackTreeBaseTraits<KThreadLocalPage>::TreeType<KThreadLocalPage>; + using TLPIterator = TLPTree::iterator; + private: + KProcessPageTable m_page_table; + util::Atomic<size_t> m_used_kernel_memory_size; + TLPTree m_fully_used_tlp_tree; + TLPTree m_partially_used_tlp_tree; + s32 m_ideal_core_id; + void *m_attached_object; + KResourceLimit *m_resource_limit; + KSystemResource *m_system_resource; + size_t m_memory_release_hint; + State m_state; + KLightLock m_state_lock; + KLightLock m_list_lock; + KConditionVariable m_cond_var; + KAddressArbiter m_address_arbiter; + u64 m_entropy[4]; + bool m_is_signaled; + bool m_is_initialized; + bool m_is_application; + bool m_is_default_application_system_resource; + char m_name[13]; + util::Atomic<u16> m_num_running_threads; + u32 m_flags; + KMemoryManager::Pool m_memory_pool; + s64 m_schedule_count; + KCapabilities m_capabilities; + ams::svc::ProgramId m_program_id; + u64 m_process_id; + #if defined(MESOSPHERE_ENABLE_PROCESS_CREATION_TIME) + s64 m_creation_time; + #endif + KProcessAddress m_code_address; + size_t m_code_size; + size_t m_main_thread_stack_size; + size_t m_max_process_memory; + u32 m_version; + KHandleTable m_handle_table; + KProcessAddress m_plr_address; + void *m_plr_heap_address; + KThread *m_exception_thread; + ThreadList m_thread_list; + SharedMemoryInfoList m_shared_memory_list; + IoRegionList m_io_region_list; + bool m_is_suspended; + bool m_is_immortal; + bool m_is_jit_debug; + bool m_is_handle_table_initialized; + ams::svc::DebugEvent m_jit_debug_event_type; + ams::svc::DebugException m_jit_debug_exception_type; + uintptr_t m_jit_debug_params[4]; + u64 m_jit_debug_thread_id; + KWaitObject m_wait_object; + KThread *m_running_threads[cpu::NumCores]; + u64 m_running_thread_idle_counts[cpu::NumCores]; + u64 m_running_thread_switch_counts[cpu::NumCores]; + KThread *m_pinned_threads[cpu::NumCores]; + util::Atomic<s64> m_cpu_time; + util::Atomic<s64> m_num_process_switches; + util::Atomic<s64> m_num_thread_switches; + util::Atomic<s64> m_num_fpu_switches; + util::Atomic<s64> m_num_supervisor_calls; + util::Atomic<s64> m_num_ipc_messages; + util::Atomic<s64> m_num_ipc_replies; + util::Atomic<s64> m_num_ipc_receives; + private: + Result Initialize(const ams::svc::CreateProcessParameter ¶ms); + + Result StartTermination(); + void FinishTermination(); + + ALWAYS_INLINE void PinThread(s32 core_id, KThread *thread) { + MESOSPHERE_ASSERT(0 <= core_id && core_id < static_cast<s32>(cpu::NumCores)); + MESOSPHERE_ASSERT(thread != nullptr); + MESOSPHERE_ASSERT(m_pinned_threads[core_id] == nullptr); + m_pinned_threads[core_id] = thread; + } + + ALWAYS_INLINE void UnpinThread(s32 core_id, KThread *thread) { + MESOSPHERE_UNUSED(thread); + MESOSPHERE_ASSERT(0 <= core_id && core_id < static_cast<s32>(cpu::NumCores)); + MESOSPHERE_ASSERT(thread != nullptr); + MESOSPHERE_ASSERT(m_pinned_threads[core_id] == thread); + m_pinned_threads[core_id] = nullptr; + } + public: + explicit KProcess() : m_is_initialized(false) { /* ... */ } + + Result Initialize(const ams::svc::CreateProcessParameter ¶ms, const KPageGroup &pg, const u32 *caps, s32 num_caps, KResourceLimit *res_limit, KMemoryManager::Pool pool, bool immortal); + Result Initialize(const ams::svc::CreateProcessParameter ¶ms, svc::KUserPointer<const u32 *> caps, s32 num_caps, KResourceLimit *res_limit, KMemoryManager::Pool pool); + void Exit(); + + constexpr const char *GetName() const { return m_name; } + + constexpr ams::svc::ProgramId GetProgramId() const { return m_program_id; } + + constexpr u64 GetProcessId() const { return m_process_id; } + + constexpr State GetState() const { return m_state; } + + constexpr u64 GetCoreMask() const { return m_capabilities.GetCoreMask(); } + constexpr u64 GetPhysicalCoreMask() const { return m_capabilities.GetPhysicalCoreMask(); } + constexpr u64 GetPriorityMask() const { return m_capabilities.GetPriorityMask(); } + + constexpr s32 GetIdealCoreId() const { return m_ideal_core_id; } + constexpr void SetIdealCoreId(s32 core_id) { m_ideal_core_id = core_id; } + + constexpr bool CheckThreadPriority(s32 prio) const { return ((1ul << prio) & this->GetPriorityMask()) != 0; } + + constexpr u32 GetCreateProcessFlags() const { return m_flags; } + + constexpr bool Is64Bit() const { return m_flags & ams::svc::CreateProcessFlag_Is64Bit; } + + constexpr KProcessAddress GetEntryPoint() const { return m_code_address; } + + constexpr size_t GetMainStackSize() const { return m_main_thread_stack_size; } + + constexpr KMemoryManager::Pool GetMemoryPool() const { return m_memory_pool; } + + constexpr u64 GetRandomEntropy(size_t i) const { return m_entropy[i]; } + + constexpr bool IsApplication() const { return m_is_application; } + + constexpr bool IsDefaultApplicationSystemResource() const { return m_is_default_application_system_resource; } + + constexpr bool IsSuspended() const { return m_is_suspended; } + constexpr void SetSuspended(bool suspended) { m_is_suspended = suspended; } + + Result Terminate(); + + constexpr bool IsTerminated() const { + return m_state == State_Terminated; + } + + constexpr bool IsAttachedToDebugger() const { + return m_attached_object != nullptr; + } + + constexpr bool IsPermittedSvc(svc::SvcId svc_id) const { + return m_capabilities.IsPermittedSvc(svc_id); + } + + constexpr bool IsPermittedInterrupt(int32_t interrupt_id) const { + return m_capabilities.IsPermittedInterrupt(interrupt_id); + } + + constexpr bool IsPermittedDebug() const { + return m_capabilities.IsPermittedDebug(); + } + + constexpr bool CanForceDebugProd() const { + return m_capabilities.CanForceDebugProd(); + } + + constexpr bool CanForceDebug() const { + return m_capabilities.CanForceDebug(); + } + + u32 GetAllocateOption() const { return m_page_table.GetAllocateOption(); } + + ThreadList &GetThreadList() { return m_thread_list; } + const ThreadList &GetThreadList() const { return m_thread_list; } + + constexpr void *GetDebugObject() const { return m_attached_object; } + KProcess::State SetDebugObject(void *debug_object); + void ClearDebugObject(KProcess::State state); + + bool EnterJitDebug(ams::svc::DebugEvent event, ams::svc::DebugException exception, uintptr_t param1 = 0, uintptr_t param2 = 0, uintptr_t param3 = 0, uintptr_t param4 = 0); + + KEventInfo *GetJitDebugInfo(); + void ClearJitDebugInfo(); + + bool EnterUserException(); + bool LeaveUserException(); + bool ReleaseUserException(KThread *thread); + + KThread *GetPinnedThread(s32 core_id) const { + MESOSPHERE_ASSERT(0 <= core_id && core_id < static_cast<s32>(cpu::NumCores)); + return m_pinned_threads[core_id]; + } + + const svc::SvcAccessFlagSet &GetSvcPermissions() const { return m_capabilities.GetSvcPermissions(); } + + constexpr KResourceLimit *GetResourceLimit() const { return m_resource_limit; } + + bool ReserveResource(ams::svc::LimitableResource which, s64 value); + bool ReserveResource(ams::svc::LimitableResource which, s64 value, s64 timeout); + void ReleaseResource(ams::svc::LimitableResource which, s64 value); + void ReleaseResource(ams::svc::LimitableResource which, s64 value, s64 hint); + + constexpr KLightLock &GetStateLock() { return m_state_lock; } + constexpr KLightLock &GetListLock() { return m_list_lock; } + + constexpr KProcessPageTable &GetPageTable() { return m_page_table; } + constexpr const KProcessPageTable &GetPageTable() const { return m_page_table; } + + constexpr KHandleTable &GetHandleTable() { return m_handle_table; } + constexpr const KHandleTable &GetHandleTable() const { return m_handle_table; } + + KWaitObject *GetWaitObjectPointer() { return std::addressof(m_wait_object); } + + size_t GetUsedUserPhysicalMemorySize() const; + size_t GetTotalUserPhysicalMemorySize() const; + size_t GetUsedNonSystemUserPhysicalMemorySize() const; + size_t GetTotalNonSystemUserPhysicalMemorySize() const; + + Result AddSharedMemory(KSharedMemory *shmem, KProcessAddress address, size_t size); + void RemoveSharedMemory(KSharedMemory *shmem, KProcessAddress address, size_t size); + + void AddIoRegion(KIoRegion *io_region); + void RemoveIoRegion(KIoRegion *io_region); + + Result CreateThreadLocalRegion(KProcessAddress *out); + Result DeleteThreadLocalRegion(KProcessAddress addr); + void *GetThreadLocalRegionPointer(KProcessAddress addr); + + constexpr KProcessAddress GetProcessLocalRegionAddress() const { return m_plr_address; } + + constexpr void *GetProcessLocalRegionHeapAddress() const { return m_plr_heap_address; } + + KThread *GetExceptionThread() const { return m_exception_thread; } + + void AddCpuTime(s64 diff) { m_cpu_time += diff; } + s64 GetCpuTime() { return m_cpu_time.Load(); } + + constexpr s64 GetScheduledCount() const { return m_schedule_count; } + void IncrementScheduledCount() { ++m_schedule_count; } + + void IncrementRunningThreadCount(); + void DecrementRunningThreadCount(); + + size_t GetRequiredSecureMemorySizeNonDefault() const { + return (!this->IsDefaultApplicationSystemResource() && m_system_resource->IsSecureResource()) ? static_cast<KSecureSystemResource *>(m_system_resource)->CalculateRequiredSecureMemorySize() : 0; + } + + size_t GetRequiredSecureMemorySize() const { + return m_system_resource->IsSecureResource() ? static_cast<KSecureSystemResource *>(m_system_resource)->CalculateRequiredSecureMemorySize() : 0; + } + + size_t GetTotalSystemResourceSize() const { + return (!this->IsDefaultApplicationSystemResource() && m_system_resource->IsSecureResource()) ? static_cast<KSecureSystemResource *>(m_system_resource)->GetSize() : 0; + } + + size_t GetUsedSystemResourceSize() const { + return (!this->IsDefaultApplicationSystemResource() && m_system_resource->IsSecureResource()) ? static_cast<KSecureSystemResource *>(m_system_resource)->GetUsedSize() : 0; + } + + void SetRunningThread(s32 core, KThread *thread, u64 idle_count, u64 switch_count) { + m_running_threads[core] = thread; + m_running_thread_idle_counts[core] = idle_count; + m_running_thread_switch_counts[core] = switch_count; + } + + void ClearRunningThread(KThread *thread) { + for (size_t i = 0; i < util::size(m_running_threads); ++i) { + if (m_running_threads[i] == thread) { + m_running_threads[i] = nullptr; + } + } + } + + const KSystemResource &GetSystemResource() const { return *m_system_resource; } + + const KMemoryBlockSlabManager &GetMemoryBlockSlabManager() const { return m_system_resource->GetMemoryBlockSlabManager(); } + const KBlockInfoManager &GetBlockInfoManager() const { return m_system_resource->GetBlockInfoManager(); } + const KPageTableManager &GetPageTableManager() const { return m_system_resource->GetPageTableManager(); } + + constexpr KThread *GetRunningThread(s32 core) const { return m_running_threads[core]; } + constexpr u64 GetRunningThreadIdleCount(s32 core) const { return m_running_thread_idle_counts[core]; } + constexpr u64 GetRunningThreadSwitchCount(s32 core) const { return m_running_thread_switch_counts[core]; } + + void RegisterThread(KThread *thread); + void UnregisterThread(KThread *thread); + + Result Run(s32 priority, size_t stack_size); + + Result Reset(); + + void SetDebugBreak() { + if (m_state == State_RunningAttached) { + this->ChangeState(State_DebugBreak); + } + } + + void SetAttached() { + if (m_state == State_DebugBreak) { + this->ChangeState(State_RunningAttached); + } + } + + Result SetActivity(ams::svc::ProcessActivity activity); + + void PinCurrentThread(); + void UnpinCurrentThread(); + void UnpinThread(KThread *thread); + + void SignalConditionVariable(uintptr_t cv_key, int32_t count) { + return m_cond_var.Signal(cv_key, count); + } + + Result WaitConditionVariable(KProcessAddress address, uintptr_t cv_key, u32 tag, s64 ns) { + R_RETURN(m_cond_var.Wait(address, cv_key, tag, ns)); + } + + Result SignalAddressArbiter(uintptr_t address, ams::svc::SignalType signal_type, s32 value, s32 count) { + R_RETURN(m_address_arbiter.SignalToAddress(address, signal_type, value, count)); + } + + Result WaitAddressArbiter(uintptr_t address, ams::svc::ArbitrationType arb_type, s64 value, s64 timeout) { + R_RETURN(m_address_arbiter.WaitForAddress(address, arb_type, value, timeout)); + } + + Result GetThreadList(s32 *out_num_threads, ams::kern::svc::KUserPointer<u64 *> out_thread_ids, s32 max_out_count); + + static KProcess *GetProcessFromId(u64 process_id); + static Result GetProcessList(s32 *out_num_processes, ams::kern::svc::KUserPointer<u64 *> out_process_ids, s32 max_out_count); + + static void Switch(KProcess *cur_process, KProcess *next_process) { + MESOSPHERE_UNUSED(cur_process); + + /* Update the current page table. */ + if (next_process) { + next_process->GetPageTable().Activate(next_process->GetSlabIndex(), next_process->GetProcessId()); + } else { + Kernel::GetKernelPageTable().Activate(); + } + } + public: + /* Overridden parent functions. */ + bool IsInitialized() const { return m_is_initialized; } + + static void PostDestroy(uintptr_t arg) { MESOSPHERE_UNUSED(arg); /* ... */ } + + void Finalize(); + + ALWAYS_INLINE u64 GetIdImpl() const { return this->GetProcessId(); } + ALWAYS_INLINE u64 GetId() const { return this->GetIdImpl(); } + + virtual bool IsSignaled() const override { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + return m_is_signaled; + } + + void DoWorkerTaskImpl(); + private: + void ChangeState(State new_state) { + if (m_state != new_state) { + m_state = new_state; + m_is_signaled = true; + this->NotifyAvailable(); + } + } + + ALWAYS_INLINE Result InitializeHandleTable(s32 size) { + /* Try to initialize the handle table. */ + R_TRY(m_handle_table.Initialize(size)); + + /* We succeeded, so note that we did. */ + m_is_handle_table_initialized = true; + R_SUCCEED(); + } + + ALWAYS_INLINE void FinalizeHandleTable() { + /* Finalize the table. */ + m_handle_table.Finalize(); + + /* Note that the table is finalized. */ + m_is_handle_table_initialized = false; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_readable_event.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_readable_event.hpp new file mode 100644 index 00000000..d5888f98 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_readable_event.hpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_synchronization_object.hpp> + +namespace ams::kern { + + class KEvent; + + class KReadableEvent : public KSynchronizationObject { + MESOSPHERE_AUTOOBJECT_TRAITS(KReadableEvent, KSynchronizationObject); + private: + bool m_is_signaled; + KEvent *m_parent; + public: + constexpr explicit KReadableEvent(util::ConstantInitializeTag) : KSynchronizationObject(util::ConstantInitialize), m_is_signaled(), m_parent() { MESOSPHERE_ASSERT_THIS(); } + + explicit KReadableEvent() { /* ... */ } + + void Initialize(KEvent *parent); + + constexpr KEvent *GetParent() const { return m_parent; } + + Result Signal(); + Result Reset(); + + Result Clear() { + MESOSPHERE_ASSERT_THIS(); + + /* Try to perform a reset, succeeding unconditionally. */ + this->Reset(); + + R_SUCCEED(); + } + + virtual bool IsSignaled() const override; + virtual void Destroy() override; + + /* NOTE: This is a virtual function in Nintendo's kernel. */ + /* virtual Result Reset(); */ + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_resource_limit.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_resource_limit.hpp new file mode 100644 index 00000000..5a3426e9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_resource_limit.hpp @@ -0,0 +1,66 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_auto_object.hpp> +#include <mesosphere/kern_slab_helpers.hpp> +#include <mesosphere/kern_k_light_lock.hpp> +#include <mesosphere/kern_k_light_condition_variable.hpp> + +namespace ams::kern { + + class KResourceLimit final : public KAutoObjectWithSlabHeapAndContainer<KResourceLimit, KAutoObjectWithList> { + MESOSPHERE_AUTOOBJECT_TRAITS(KResourceLimit, KAutoObject); + private: + s64 m_limit_values[ams::svc::LimitableResource_Count]; + s64 m_current_values[ams::svc::LimitableResource_Count]; + s64 m_current_hints[ams::svc::LimitableResource_Count]; + s64 m_peak_values[ams::svc::LimitableResource_Count]; + mutable KLightLock m_lock; + s32 m_waiter_count; + KLightConditionVariable m_cond_var; + public: + constexpr explicit ALWAYS_INLINE KResourceLimit(util::ConstantInitializeTag) + : KAutoObjectWithSlabHeapAndContainer<KResourceLimit, KAutoObjectWithList>(util::ConstantInitialize), + m_limit_values(), m_current_values(), m_current_hints(), m_peak_values(), m_lock(), m_waiter_count(), + m_cond_var(util::ConstantInitialize) + { + /* ... */ + } + + explicit ALWAYS_INLINE KResourceLimit() { /* ... */ } + + static void PostDestroy(uintptr_t arg) { MESOSPHERE_UNUSED(arg); /* ... */ } + + void Initialize(); + void Finalize(); + + s64 GetLimitValue(ams::svc::LimitableResource which) const; + s64 GetCurrentValue(ams::svc::LimitableResource which) const; + s64 GetPeakValue(ams::svc::LimitableResource which) const; + s64 GetFreeValue(ams::svc::LimitableResource which) const; + + Result SetLimitValue(ams::svc::LimitableResource which, s64 value); + + void Add(ams::svc::LimitableResource which, s64 value); + + bool Reserve(ams::svc::LimitableResource which, s64 value); + bool Reserve(ams::svc::LimitableResource which, s64 value, s64 timeout); + void Release(ams::svc::LimitableResource which, s64 value); + void Release(ams::svc::LimitableResource which, s64 value, s64 hint); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_scheduler.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_scheduler.hpp new file mode 100644 index 00000000..cc19d8f3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_scheduler.hpp @@ -0,0 +1,225 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_select_cpu.hpp> +#include <mesosphere/kern_k_thread.hpp> +#include <mesosphere/kern_k_priority_queue.hpp> +#include <mesosphere/kern_k_interrupt_task_manager.hpp> +#include <mesosphere/kern_k_scheduler_lock.hpp> + +namespace ams::kern { + + using KSchedulerPriorityQueue = KPriorityQueue<KThread, cpu::NumCores, ams::svc::LowestThreadPriority, ams::svc::HighestThreadPriority>; + static_assert(std::is_same<KSchedulerPriorityQueue::AffinityMaskType, KAffinityMask>::value); + static_assert(KSchedulerPriorityQueue::NumCores == cpu::NumCores); + static_assert(KSchedulerPriorityQueue::NumPriority == BITSIZEOF(u64)); + + class KScopedSchedulerLock; + class KScopedSchedulerLockAndSleep; + + class KScheduler { + NON_COPYABLE(KScheduler); + NON_MOVEABLE(KScheduler); + public: + static constexpr s32 HighestCoreMigrationAllowedPriority = 2; + static_assert(ams::svc::LowestThreadPriority >= HighestCoreMigrationAllowedPriority); + static_assert(ams::svc::HighestThreadPriority <= HighestCoreMigrationAllowedPriority); + + struct SchedulingState { + util::Atomic<bool> needs_scheduling{false}; + bool interrupt_task_runnable{false}; + bool should_count_idle{false}; + u64 idle_count{0}; + u64 switch_count{0}; + KThread *highest_priority_thread{nullptr}; + void *idle_thread_stack{nullptr}; + KThread *prev_thread{nullptr}; + KInterruptTaskManager *interrupt_task_manager{nullptr}; + + constexpr SchedulingState() = default; + }; + private: + friend class KScopedSchedulerLock; + friend class KScopedSchedulerLockAndSleep; + friend class KScopedDisableDispatch; + private: + SchedulingState m_state; + bool m_is_active; + s32 m_core_id; + s64 m_last_context_switch_time; + KThread *m_idle_thread; + util::Atomic<KThread *> m_current_thread; + public: + constexpr KScheduler() : m_state(), m_is_active(false), m_core_id(0), m_last_context_switch_time(0), m_idle_thread(nullptr), m_current_thread(nullptr) { + m_state.needs_scheduling = true; + m_state.interrupt_task_runnable = false; + m_state.should_count_idle = false; + m_state.idle_count = 0; + m_state.switch_count = 0; + m_state.idle_thread_stack = nullptr; + m_state.highest_priority_thread = nullptr; + m_state.prev_thread = nullptr; + m_state.interrupt_task_manager = nullptr; + } + + NOINLINE void Initialize(KThread *idle_thread); + NOINLINE void Activate(); + + ALWAYS_INLINE void SetInterruptTaskRunnable() { + m_state.interrupt_task_runnable = true; + m_state.needs_scheduling = true; + } + + ALWAYS_INLINE void RequestScheduleOnInterrupt() { + m_state.needs_scheduling = true; + + if (CanSchedule()) { + this->ScheduleOnInterrupt(); + } + } + + ALWAYS_INLINE u64 GetIdleCount() const { + return m_state.idle_count; + } + + ALWAYS_INLINE u64 GetSwitchCount() const { + return m_state.switch_count; + } + + ALWAYS_INLINE KThread *GetIdleThread() const { + return m_idle_thread; + } + + ALWAYS_INLINE KThread *GetPreviousThread() const { + return m_state.prev_thread; + } + + ALWAYS_INLINE KThread *GetSchedulerCurrentThread() const { + return m_current_thread.Load(); + } + + ALWAYS_INLINE s64 GetLastContextSwitchTime() const { + return m_last_context_switch_time; + } + private: + /* Static private API. */ + static ALWAYS_INLINE KSchedulerPriorityQueue &GetPriorityQueue() { return s_priority_queue; } + static NOINLINE u64 UpdateHighestPriorityThreadsImpl(); + public: + /* Static public API. */ + static ALWAYS_INLINE bool CanSchedule() { return GetCurrentThread().GetDisableDispatchCount() == 0; } + static ALWAYS_INLINE bool IsSchedulerLockedByCurrentThread() { return s_scheduler_lock.IsLockedByCurrentThread(); } + + static ALWAYS_INLINE bool IsSchedulerUpdateNeeded() { return s_scheduler_update_needed; } + static ALWAYS_INLINE void SetSchedulerUpdateNeeded() { s_scheduler_update_needed = true; } + static ALWAYS_INLINE void ClearSchedulerUpdateNeeded() { s_scheduler_update_needed = false; } + + static ALWAYS_INLINE void DisableScheduling() { + MESOSPHERE_ASSERT(GetCurrentThread().GetDisableDispatchCount() >= 0); + GetCurrentThread().DisableDispatch(); + } + + static NOINLINE void EnableScheduling(u64 cores_needing_scheduling) { + MESOSPHERE_ASSERT(GetCurrentThread().GetDisableDispatchCount() >= 1); + + GetCurrentScheduler().RescheduleOtherCores(cores_needing_scheduling); + + if (GetCurrentThread().GetDisableDispatchCount() > 1) { + GetCurrentThread().EnableDispatch(); + } else { + GetCurrentScheduler().RescheduleCurrentCore(); + } + } + + static ALWAYS_INLINE u64 UpdateHighestPriorityThreads() { + if (IsSchedulerUpdateNeeded()) { + return UpdateHighestPriorityThreadsImpl(); + } else { + return 0; + } + } + + static NOINLINE void ClearPreviousThread(KThread *thread); + + static NOINLINE void OnThreadStateChanged(KThread *thread, KThread::ThreadState old_state); + static NOINLINE void OnThreadPriorityChanged(KThread *thread, s32 old_priority); + static NOINLINE void OnThreadAffinityMaskChanged(KThread *thread, const KAffinityMask &old_affinity, s32 old_core); + + static NOINLINE void RotateScheduledQueue(s32 core_id, s32 priority); + + static NOINLINE void YieldWithoutCoreMigration(); + static NOINLINE void YieldWithCoreMigration(); + static NOINLINE void YieldToAnyThread(); + private: + /* Instanced private API. */ + void ScheduleImpl(); + void SwitchThread(KThread *next_thread); + + ALWAYS_INLINE void Schedule() { + MESOSPHERE_ASSERT(GetCurrentThread().GetDisableDispatchCount() == 1); + MESOSPHERE_ASSERT(m_core_id == GetCurrentCoreId()); + + this->ScheduleImpl(); + } + + ALWAYS_INLINE void ScheduleOnInterrupt() { + GetCurrentThread().DisableDispatch(); + this->Schedule(); + GetCurrentThread().EnableDispatch(); + } + + void RescheduleOtherCores(u64 cores_needing_scheduling); + + ALWAYS_INLINE void RescheduleCurrentCore() { + MESOSPHERE_ASSERT(GetCurrentThread().GetDisableDispatchCount() == 1); + + GetCurrentThread().EnableDispatch(); + + if (m_state.needs_scheduling.Load()) { + /* Disable interrupts, and then check again if rescheduling is needed. */ + KScopedInterruptDisable intr_disable; + + GetCurrentScheduler().RescheduleCurrentCoreImpl(); + } + } + + ALWAYS_INLINE void RescheduleCurrentCoreImpl() { + /* Check that scheduling is needed. */ + if (AMS_LIKELY(m_state.needs_scheduling.Load())) { + GetCurrentThread().DisableDispatch(); + this->Schedule(); + GetCurrentThread().EnableDispatch(); + } + } + + NOINLINE u64 UpdateHighestPriorityThread(KThread *thread); + public: + using LockType = KAbstractSchedulerLock<KScheduler>; + private: + static bool s_scheduler_update_needed; + static KSchedulerPriorityQueue s_priority_queue; + static LockType s_scheduler_lock; + public: + static consteval bool ValidateAssemblyOffsets(); + }; + + class KScopedSchedulerLock : KScopedLock<KScheduler::LockType> { + public: + explicit ALWAYS_INLINE KScopedSchedulerLock() : KScopedLock(KScheduler::s_scheduler_lock) { /* ... */ } + ALWAYS_INLINE ~KScopedSchedulerLock() { /* ... */ } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_scheduler_impls.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_scheduler_impls.hpp new file mode 100644 index 00000000..e7071ab0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_scheduler_impls.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_scheduler.hpp> +#include <mesosphere/kern_select_interrupt_manager.hpp> + +namespace ams::kern { + + /* NOTE: This header is included after all main headers. */ + consteval bool KScheduler::ValidateAssemblyOffsets() { + static_assert(AMS_OFFSETOF(KScheduler, m_state.needs_scheduling) == KSCHEDULER_NEEDS_SCHEDULING); + static_assert(AMS_OFFSETOF(KScheduler, m_state.interrupt_task_runnable) == KSCHEDULER_INTERRUPT_TASK_RUNNABLE); + static_assert(AMS_OFFSETOF(KScheduler, m_state.highest_priority_thread) == KSCHEDULER_HIGHEST_PRIORITY_THREAD); + static_assert(AMS_OFFSETOF(KScheduler, m_state.idle_thread_stack) == KSCHEDULER_IDLE_THREAD_STACK); + static_assert(AMS_OFFSETOF(KScheduler, m_state.prev_thread) == KSCHEDULER_PREVIOUS_THREAD); + static_assert(AMS_OFFSETOF(KScheduler, m_state.interrupt_task_manager) == KSCHEDULER_INTERRUPT_TASK_MANAGER); + + return true; + } + static_assert(KScheduler::ValidateAssemblyOffsets()); + + ALWAYS_INLINE void KScheduler::RescheduleOtherCores(u64 cores_needing_scheduling) { + if (const u64 core_mask = cores_needing_scheduling & ~(1ul << m_core_id); core_mask != 0) { + cpu::DataSynchronizationBarrierInnerShareable(); + Kernel::GetInterruptManager().SendInterProcessorInterrupt(KInterruptName_Scheduler, core_mask); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_scheduler_lock.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_scheduler_lock.hpp new file mode 100644 index 00000000..d96d2e55 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_scheduler_lock.hpp @@ -0,0 +1,95 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_spin_lock.hpp> +#include <mesosphere/kern_k_current_context.hpp> +#include <mesosphere/kern_k_scoped_lock.hpp> + +namespace ams::kern { + + class KThread; + + template<typename T> + concept KSchedulerLockable = !std::is_reference<T>::value && requires(T) { + { T::DisableScheduling() } -> std::same_as<void>; + { T::EnableScheduling(std::declval<u64>()) } -> std::same_as<void>; + { T::UpdateHighestPriorityThreads() } -> std::convertible_to<u64>; + }; + + template<typename SchedulerType> requires KSchedulerLockable<SchedulerType> + class KAbstractSchedulerLock { + private: + KAlignedSpinLock m_spin_lock; + s32 m_lock_count; + KThread *m_owner_thread; + public: + constexpr ALWAYS_INLINE KAbstractSchedulerLock() : m_spin_lock(), m_lock_count(0), m_owner_thread(nullptr) { MESOSPHERE_ASSERT_THIS(); } + + ALWAYS_INLINE bool IsLockedByCurrentThread() const { + MESOSPHERE_ASSERT_THIS(); + + return m_owner_thread == GetCurrentThreadPointer(); + } + + MESOSPHERE_ALWAYS_INLINE_IF_RELEASE void Lock() { + MESOSPHERE_ASSERT_THIS(); + + if (this->IsLockedByCurrentThread()) { + /* If we already own the lock, the lock count should be > 0. */ + /* For debug, ensure this is true. */ + MESOSPHERE_ASSERT(m_lock_count > 0); + } else { + /* Otherwise, we want to disable scheduling and acquire the spinlock. */ + SchedulerType::DisableScheduling(); + m_spin_lock.Lock(); + + /* For debug, ensure that our state is valid. */ + MESOSPHERE_ASSERT(m_lock_count == 0); + MESOSPHERE_ASSERT(m_owner_thread == nullptr); + + /* Take ownership of the lock. */ + m_owner_thread = GetCurrentThreadPointer(); + } + + /* Increment the lock count. */ + m_lock_count++; + } + + MESOSPHERE_ALWAYS_INLINE_IF_RELEASE void Unlock() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + MESOSPHERE_ASSERT(m_lock_count > 0); + + /* Release an instance of the lock. */ + if ((--m_lock_count) == 0) { + /* Perform a memory barrier here. */ + cpu::DataMemoryBarrierInnerShareable(); + + /* We're no longer going to hold the lock. Take note of what cores need scheduling. */ + const u64 cores_needing_scheduling = SchedulerType::UpdateHighestPriorityThreads(); + + /* Note that we no longer hold the lock, and unlock the spinlock. */ + m_owner_thread = nullptr; + m_spin_lock.Unlock(); + + /* Enable scheduling, and perform a rescheduling operation. */ + SchedulerType::EnableScheduling(cores_needing_scheduling); + } + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_scoped_lock.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_scoped_lock.hpp new file mode 100644 index 00000000..d63374c4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_scoped_lock.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +namespace ams::kern { + + template<typename T> + concept KLockable = !std::is_reference<T>::value && requires (T &t) { + { t.Lock() } -> std::same_as<void>; + { t.Unlock() } -> std::same_as<void>; + }; + + template<typename T> requires KLockable<T> + class KScopedLock { + NON_COPYABLE(KScopedLock); + NON_MOVEABLE(KScopedLock); + private: + T &m_lock; + public: + explicit ALWAYS_INLINE KScopedLock(T &l) : m_lock(l) { m_lock.Lock(); } + explicit ALWAYS_INLINE KScopedLock(T *l) : KScopedLock(*l) { /* ... */ } + ALWAYS_INLINE ~KScopedLock() { m_lock.Unlock(); } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_scoped_resource_reservation.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_scoped_resource_reservation.hpp new file mode 100644 index 00000000..7df6d58b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_scoped_resource_reservation.hpp @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_resource_limit.hpp> +#include <mesosphere/kern_k_process.hpp> + +namespace ams::kern { + + class KScopedResourceReservation { + private: + KResourceLimit *m_limit; + s64 m_value; + ams::svc::LimitableResource m_resource; + bool m_succeeded; + public: + ALWAYS_INLINE KScopedResourceReservation(KResourceLimit *l, ams::svc::LimitableResource r, s64 v, s64 timeout) : m_limit(l), m_value(v), m_resource(r) { + if (m_limit && m_value) { + m_succeeded = m_limit->Reserve(m_resource, m_value, timeout); + } else { + m_succeeded = true; + } + } + + ALWAYS_INLINE KScopedResourceReservation(KResourceLimit *l, ams::svc::LimitableResource r, s64 v = 1) : m_limit(l), m_value(v), m_resource(r) { + if (m_limit && m_value) { + m_succeeded = m_limit->Reserve(m_resource, m_value); + } else { + m_succeeded = true; + } + } + + ALWAYS_INLINE KScopedResourceReservation(const KProcess *p, ams::svc::LimitableResource r, s64 v, s64 t) : KScopedResourceReservation(p->GetResourceLimit(), r, v, t) { /* ... */ } + ALWAYS_INLINE KScopedResourceReservation(const KProcess *p, ams::svc::LimitableResource r, s64 v = 1) : KScopedResourceReservation(p->GetResourceLimit(), r, v) { /* ... */ } + + ALWAYS_INLINE ~KScopedResourceReservation() { + if (m_limit && m_value && m_succeeded) { + m_limit->Release(m_resource, m_value); + } + } + + ALWAYS_INLINE void Commit() { + m_limit = nullptr; + } + + ALWAYS_INLINE bool Succeeded() const { + return m_succeeded; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_scoped_scheduler_lock_and_sleep.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_scoped_scheduler_lock_and_sleep.hpp new file mode 100644 index 00000000..c4ffefff --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_scoped_scheduler_lock_and_sleep.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_scheduler.hpp> +#include <mesosphere/kern_select_hardware_timer.hpp> +#include <mesosphere/kern_kernel.hpp> + +namespace ams::kern { + + class KScopedSchedulerLockAndSleep { + private: + s64 m_timeout_tick; + KThread *m_thread; + KHardwareTimer *m_timer; + public: + explicit ALWAYS_INLINE KScopedSchedulerLockAndSleep(KHardwareTimer **out_timer, KThread *t, s64 timeout) : m_timeout_tick(timeout), m_thread(t) { + /* Lock the scheduler. */ + KScheduler::s_scheduler_lock.Lock(); + + /* Set our timer only if the absolute time is positive. */ + m_timer = (m_timeout_tick > 0) ? std::addressof(Kernel::GetHardwareTimer()) : nullptr; + + *out_timer = m_timer; + } + + ~KScopedSchedulerLockAndSleep() { + /* Register the sleep. */ + if (m_timeout_tick > 0) { + m_timer->RegisterAbsoluteTask(m_thread, m_timeout_tick); + } + + /* Unlock the scheduler. */ + KScheduler::s_scheduler_lock.Unlock(); + } + + ALWAYS_INLINE void CancelSleep() { + m_timeout_tick = 0; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_server_port.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_server_port.hpp new file mode 100644 index 00000000..9253ee8a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_server_port.hpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_synchronization_object.hpp> +#include <mesosphere/kern_slab_helpers.hpp> + +namespace ams::kern { + + class KPort; + class KServerSession; + class KLightServerSession; + + class KServerPort final : public KSynchronizationObject { + MESOSPHERE_AUTOOBJECT_TRAITS(KServerPort, KSynchronizationObject); + private: + using SessionList = util::IntrusiveListBaseTraits<KServerSession>::ListType; + using LightSessionList = util::IntrusiveListBaseTraits<KLightServerSession>::ListType; + private: + SessionList m_session_list; + LightSessionList m_light_session_list; + KPort *m_parent; + public: + constexpr explicit KServerPort(util::ConstantInitializeTag) : KSynchronizationObject(util::ConstantInitialize), m_session_list(), m_light_session_list(), m_parent() { /* ... */ } + explicit KServerPort() { /* ... */ } + + void Initialize(KPort *parent); + void EnqueueSession(KServerSession *session); + void EnqueueSession(KLightServerSession *session); + + KServerSession *AcceptSession(); + KLightServerSession *AcceptLightSession(); + + constexpr const KPort *GetParent() const { return m_parent; } + + bool IsLight() const; + + /* Overridden virtual functions. */ + virtual void Destroy() override; + virtual bool IsSignaled() const override; + private: + void CleanupSessions(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_server_session.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_server_session.hpp new file mode 100644 index 00000000..db8de125 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_server_session.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_synchronization_object.hpp> +#include <mesosphere/kern_k_session_request.hpp> +#include <mesosphere/kern_k_light_lock.hpp> + +namespace ams::kern { + + class KSession; + + class KServerSession final : public KSynchronizationObject, public util::IntrusiveListBaseNode<KServerSession> { + MESOSPHERE_AUTOOBJECT_TRAITS(KServerSession, KSynchronizationObject); + private: + using RequestList = util::IntrusiveListBaseTraits<KSessionRequest>::ListType; + private: + KSession *m_parent; + RequestList m_request_list; + KSessionRequest *m_current_request; + KLightLock m_lock; + public: + constexpr explicit KServerSession(util::ConstantInitializeTag) : KSynchronizationObject(util::ConstantInitialize), m_parent(), m_request_list(), m_current_request(), m_lock() { /* ... */ } + explicit KServerSession() : m_current_request(nullptr), m_lock() { /* ... */ } + + virtual void Destroy() override; + + void Initialize(KSession *p) { m_parent = p; } + + constexpr const KSession *GetParent() const { return m_parent; } + + virtual bool IsSignaled() const override; + + Result OnRequest(KSessionRequest *request); + + Result ReceiveRequest(uintptr_t message, uintptr_t buffer_size, KPhysicalAddress message_paddr); + Result SendReply(uintptr_t message, uintptr_t buffer_size, KPhysicalAddress message_paddr); + + void OnClientClosed(); + + void Dump(); + private: + ALWAYS_INLINE bool IsSignaledImpl() const; + void CleanupRequests(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_session.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_session.hpp new file mode 100644 index 00000000..ff571826 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_session.hpp @@ -0,0 +1,88 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_synchronization_object.hpp> +#include <mesosphere/kern_k_server_session.hpp> +#include <mesosphere/kern_k_client_session.hpp> +#include <mesosphere/kern_slab_helpers.hpp> + +namespace ams::kern { + + class KClientPort; + class KProcess; + + class KSession final : public KAutoObjectWithSlabHeapAndContainer<KSession, KAutoObjectWithList, true> { + MESOSPHERE_AUTOOBJECT_TRAITS(KSession, KAutoObject); + private: + enum class State : u8 { + Invalid = 0, + Normal = 1, + ClientClosed = 2, + ServerClosed = 3, + }; + private: + util::Atomic<std::underlying_type<State>::type> m_atomic_state; + bool m_initialized; + KServerSession m_server; + KClientSession m_client; + KClientPort *m_port; + uintptr_t m_name; + KProcess *m_process; + private: + ALWAYS_INLINE void SetState(State state) { + m_atomic_state = static_cast<u8>(state); + } + + ALWAYS_INLINE State GetState() const { + return static_cast<State>(m_atomic_state.Load()); + } + public: + constexpr explicit KSession(util::ConstantInitializeTag) + : KAutoObjectWithSlabHeapAndContainer<KSession, KAutoObjectWithList, true>(util::ConstantInitialize), + m_atomic_state(static_cast<std::underlying_type<State>::type>(State::Invalid)), m_initialized(), + m_server(util::ConstantInitialize), m_client(util::ConstantInitialize), m_port(), m_name(), m_process() + { + /* ... */ + } + + explicit KSession() : m_atomic_state(util::ToUnderlying(State::Invalid)), m_initialized(false), m_process(nullptr) { /* ... */ } + + void Initialize(KClientPort *client_port, uintptr_t name); + void Finalize(); + + bool IsInitialized() const { return m_initialized; } + uintptr_t GetPostDestroyArgument() const { return reinterpret_cast<uintptr_t>(m_process); } + + static void PostDestroy(uintptr_t arg); + + void OnServerClosed(); + void OnClientClosed(); + + bool IsServerClosed() const { return this->GetState() != State::Normal; } + bool IsClientClosed() const { return this->GetState() != State::Normal; } + + Result OnRequest(KSessionRequest *request) { R_RETURN(m_server.OnRequest(request)); } + + KClientSession &GetClientSession() { return m_client; } + KServerSession &GetServerSession() { return m_server; } + const KClientSession &GetClientSession() const { return m_client; } + const KServerSession &GetServerSession() const { return m_server; } + + const KClientPort *GetParent() const { return m_port; } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_session_request.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_session_request.hpp new file mode 100644 index 00000000..752cc917 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_session_request.hpp @@ -0,0 +1,249 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_auto_object.hpp> +#include <mesosphere/kern_slab_helpers.hpp> +#include <mesosphere/kern_k_event.hpp> +#include <mesosphere/kern_k_thread.hpp> +#include <mesosphere/kern_k_process.hpp> +#include <mesosphere/kern_k_memory_block.hpp> + +namespace ams::kern { + + class KSessionRequest final : public KSlabAllocated<KSessionRequest, true>, public KAutoObject, public util::IntrusiveListBaseNode<KSessionRequest> { + MESOSPHERE_AUTOOBJECT_TRAITS(KSessionRequest, KAutoObject); + public: + class SessionMappings { + private: + /* At most 15 buffers of each type (4-bit descriptor counts), for 45 total. */ + static constexpr size_t NumMappings = ((1ul << 4) - 1) * 3; + static constexpr size_t NumStaticMappings = 8; + static constexpr size_t NumDynamicMappings = NumMappings - NumStaticMappings; + + class Mapping { + private: + uintptr_t m_client_address; + uintptr_t m_server_address; + size_t m_size; + KMemoryState m_state; + public: + constexpr void Set(KProcessAddress c, KProcessAddress s, size_t sz, KMemoryState st) { + m_client_address = GetInteger(c); + m_server_address = GetInteger(s); + m_size = sz; + m_state = st; + } + + constexpr ALWAYS_INLINE KProcessAddress GetClientAddress() const { return m_client_address; } + constexpr ALWAYS_INLINE KProcessAddress GetServerAddress() const { return m_server_address; } + constexpr ALWAYS_INLINE size_t GetSize() const { return m_size; } + constexpr ALWAYS_INLINE KMemoryState GetMemoryState() const { return m_state; } + }; + public: + class DynamicMappings : public KSlabAllocated<DynamicMappings, true> { + private: + Mapping m_mappings[NumDynamicMappings]; + public: + constexpr explicit DynamicMappings() : m_mappings() { /* ... */ } + + constexpr ALWAYS_INLINE Mapping &Get(size_t idx) { return m_mappings[idx]; } + constexpr ALWAYS_INLINE const Mapping &Get(size_t idx) const { return m_mappings[idx]; } + }; + static_assert(sizeof(DynamicMappings) == sizeof(Mapping) * NumDynamicMappings); + private: + Mapping m_static_mappings[NumStaticMappings]; + DynamicMappings *m_dynamic_mappings; + u8 m_num_send; + u8 m_num_recv; + u8 m_num_exch; + public: + constexpr explicit SessionMappings(util::ConstantInitializeTag) : m_static_mappings(), m_dynamic_mappings(), m_num_send(), m_num_recv(), m_num_exch() { /* ... */ } + + explicit SessionMappings() : m_dynamic_mappings(nullptr), m_num_send(), m_num_recv(), m_num_exch() { /* ... */ } + + void Initialize() { /* ... */ } + void Finalize(); + + constexpr ALWAYS_INLINE size_t GetSendCount() const { return m_num_send; } + constexpr ALWAYS_INLINE size_t GetReceiveCount() const { return m_num_recv; } + constexpr ALWAYS_INLINE size_t GetExchangeCount() const { return m_num_exch; } + + Result PushSend(KProcessAddress client, KProcessAddress server, size_t size, KMemoryState state); + Result PushReceive(KProcessAddress client, KProcessAddress server, size_t size, KMemoryState state); + Result PushExchange(KProcessAddress client, KProcessAddress server, size_t size, KMemoryState state); + + constexpr ALWAYS_INLINE KProcessAddress GetSendClientAddress(size_t i) const { return GetSendMapping(i).GetClientAddress(); } + constexpr ALWAYS_INLINE KProcessAddress GetSendServerAddress(size_t i) const { return GetSendMapping(i).GetServerAddress(); } + constexpr ALWAYS_INLINE size_t GetSendSize(size_t i) const { return GetSendMapping(i).GetSize(); } + constexpr ALWAYS_INLINE KMemoryState GetSendMemoryState(size_t i) const { return GetSendMapping(i).GetMemoryState(); } + + constexpr ALWAYS_INLINE KProcessAddress GetReceiveClientAddress(size_t i) const { return GetReceiveMapping(i).GetClientAddress(); } + constexpr ALWAYS_INLINE KProcessAddress GetReceiveServerAddress(size_t i) const { return GetReceiveMapping(i).GetServerAddress(); } + constexpr ALWAYS_INLINE size_t GetReceiveSize(size_t i) const { return GetReceiveMapping(i).GetSize(); } + constexpr ALWAYS_INLINE KMemoryState GetReceiveMemoryState(size_t i) const { return GetReceiveMapping(i).GetMemoryState(); } + + constexpr ALWAYS_INLINE KProcessAddress GetExchangeClientAddress(size_t i) const { return GetExchangeMapping(i).GetClientAddress(); } + constexpr ALWAYS_INLINE KProcessAddress GetExchangeServerAddress(size_t i) const { return GetExchangeMapping(i).GetServerAddress(); } + constexpr ALWAYS_INLINE size_t GetExchangeSize(size_t i) const { return GetExchangeMapping(i).GetSize(); } + constexpr ALWAYS_INLINE KMemoryState GetExchangeMemoryState(size_t i) const { return GetExchangeMapping(i).GetMemoryState(); } + private: + Result PushMap(KProcessAddress client, KProcessAddress server, size_t size, KMemoryState state, size_t index); + + constexpr ALWAYS_INLINE const Mapping &GetSendMapping(size_t i) const { + MESOSPHERE_ASSERT(i < m_num_send); + + const size_t index = i; + if (index < NumStaticMappings) { + return m_static_mappings[index]; + } else { + return m_dynamic_mappings->Get(index - NumStaticMappings); + } + } + + constexpr ALWAYS_INLINE const Mapping &GetReceiveMapping(size_t i) const { + MESOSPHERE_ASSERT(i < m_num_recv); + + const size_t index = m_num_send + i; + if (index < NumStaticMappings) { + return m_static_mappings[index]; + } else { + return m_dynamic_mappings->Get(index - NumStaticMappings); + } + } + + constexpr ALWAYS_INLINE const Mapping &GetExchangeMapping(size_t i) const { + MESOSPHERE_ASSERT(i < m_num_exch); + + const size_t index = m_num_send + m_num_recv + i; + if (index < NumStaticMappings) { + return m_static_mappings[index]; + } else { + return m_dynamic_mappings->Get(index - NumStaticMappings); + } + } + }; + private: + SessionMappings m_mappings; + KThread *m_thread; + KProcess *m_server; + KEvent *m_event; + uintptr_t m_address; + size_t m_size; + public: + constexpr explicit KSessionRequest(util::ConstantInitializeTag) : KAutoObject(util::ConstantInitialize), m_mappings(util::ConstantInitialize), m_thread(), m_server(), m_event(), m_address(), m_size() { /* ... */ } + + explicit KSessionRequest() : m_thread(nullptr), m_server(nullptr), m_event(nullptr) { /* ... */ } + + static KSessionRequest *Create() { + KSessionRequest *req = KSessionRequest::Allocate(); + if (AMS_LIKELY(req != nullptr)) { + KAutoObject::Create<KSessionRequest>(req); + } + return req; + } + + static KSessionRequest *CreateFromUnusedSlabMemory() { + KSessionRequest *req = KSessionRequest::AllocateFromUnusedSlabMemory(); + if (AMS_LIKELY(req != nullptr)) { + KAutoObject::Create<KSessionRequest>(req); + } + return req; + } + + virtual void Destroy() override { + this->Finalize(); + KSessionRequest::Free(this); + } + + void Initialize(KEvent *event, uintptr_t address, size_t size) { + m_mappings.Initialize(); + + m_thread = std::addressof(GetCurrentThread()); + m_event = event; + m_address = address; + m_size = size; + + m_thread->Open(); + if (m_event != nullptr) { + m_event->Open(); + } + } + + static void PostDestroy(uintptr_t arg) { MESOSPHERE_UNUSED(arg); /* ... */ } + + constexpr ALWAYS_INLINE KThread *GetThread() const { return m_thread; } + constexpr ALWAYS_INLINE KEvent *GetEvent() const { return m_event; } + constexpr ALWAYS_INLINE uintptr_t GetAddress() const { return m_address; } + constexpr ALWAYS_INLINE size_t GetSize() const { return m_size; } + constexpr ALWAYS_INLINE KProcess *GetServerProcess() const { return m_server; } + + void ALWAYS_INLINE SetServerProcess(KProcess *process) { + m_server = process; + m_server->Open(); + } + + constexpr ALWAYS_INLINE void ClearThread() { m_thread = nullptr; } + constexpr ALWAYS_INLINE void ClearEvent() { m_event = nullptr; } + + constexpr ALWAYS_INLINE size_t GetSendCount() const { return m_mappings.GetSendCount(); } + constexpr ALWAYS_INLINE size_t GetReceiveCount() const { return m_mappings.GetReceiveCount(); } + constexpr ALWAYS_INLINE size_t GetExchangeCount() const { return m_mappings.GetExchangeCount(); } + + ALWAYS_INLINE Result PushSend(KProcessAddress client, KProcessAddress server, size_t size, KMemoryState state) { + R_RETURN(m_mappings.PushSend(client, server, size, state)); + } + + ALWAYS_INLINE Result PushReceive(KProcessAddress client, KProcessAddress server, size_t size, KMemoryState state) { + R_RETURN(m_mappings.PushReceive(client, server, size, state)); + } + + ALWAYS_INLINE Result PushExchange(KProcessAddress client, KProcessAddress server, size_t size, KMemoryState state) { + R_RETURN(m_mappings.PushExchange(client, server, size, state)); + } + + constexpr ALWAYS_INLINE KProcessAddress GetSendClientAddress(size_t i) const { return m_mappings.GetSendClientAddress(i); } + constexpr ALWAYS_INLINE KProcessAddress GetSendServerAddress(size_t i) const { return m_mappings.GetSendServerAddress(i); } + constexpr ALWAYS_INLINE size_t GetSendSize(size_t i) const { return m_mappings.GetSendSize(i); } + constexpr ALWAYS_INLINE KMemoryState GetSendMemoryState(size_t i) const { return m_mappings.GetSendMemoryState(i); } + + constexpr ALWAYS_INLINE KProcessAddress GetReceiveClientAddress(size_t i) const { return m_mappings.GetReceiveClientAddress(i); } + constexpr ALWAYS_INLINE KProcessAddress GetReceiveServerAddress(size_t i) const { return m_mappings.GetReceiveServerAddress(i); } + constexpr ALWAYS_INLINE size_t GetReceiveSize(size_t i) const { return m_mappings.GetReceiveSize(i); } + constexpr ALWAYS_INLINE KMemoryState GetReceiveMemoryState(size_t i) const { return m_mappings.GetReceiveMemoryState(i); } + + constexpr ALWAYS_INLINE KProcessAddress GetExchangeClientAddress(size_t i) const { return m_mappings.GetExchangeClientAddress(i); } + constexpr ALWAYS_INLINE KProcessAddress GetExchangeServerAddress(size_t i) const { return m_mappings.GetExchangeServerAddress(i); } + constexpr ALWAYS_INLINE size_t GetExchangeSize(size_t i) const { return m_mappings.GetExchangeSize(i); } + constexpr ALWAYS_INLINE KMemoryState GetExchangeMemoryState(size_t i) const { return m_mappings.GetExchangeMemoryState(i); } + private: + /* NOTE: This is public and virtual in Nintendo's kernel. */ + void Finalize() { + m_mappings.Finalize(); + + if (m_thread) { + m_thread->Close(); + } + if (m_event) { + m_event->Close(); + } + if (m_server) { + m_server->Close(); + } + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_shared_memory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_shared_memory.hpp new file mode 100644 index 00000000..7c1c51fa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_shared_memory.hpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_auto_object.hpp> +#include <mesosphere/kern_slab_helpers.hpp> +#include <mesosphere/kern_select_page_table.hpp> + +namespace ams::kern { + + class KProcess; + class KResourceLimit; + + class KSharedMemory final : public KAutoObjectWithSlabHeapAndContainer<KSharedMemory, KAutoObjectWithList> { + MESOSPHERE_AUTOOBJECT_TRAITS(KSharedMemory, KAutoObject); + private: + KPageGroup m_page_group; + KResourceLimit *m_resource_limit; + u64 m_owner_process_id; + ams::svc::MemoryPermission m_owner_perm; + ams::svc::MemoryPermission m_remote_perm; + bool m_is_initialized; + public: + explicit KSharedMemory() + : m_page_group(Kernel::GetSystemSystemResource().GetBlockInfoManagerPointer()), m_resource_limit(nullptr), m_owner_process_id(std::numeric_limits<u64>::max()), + m_owner_perm(ams::svc::MemoryPermission_None), m_remote_perm(ams::svc::MemoryPermission_None), m_is_initialized(false) + { + /* ... */ + } + + Result Initialize(KProcess *owner, size_t size, ams::svc::MemoryPermission own_perm, ams::svc::MemoryPermission rem_perm); + void Finalize(); + + bool IsInitialized() const { return m_is_initialized; } + static void PostDestroy(uintptr_t arg) { MESOSPHERE_UNUSED(arg); /* ... */ } + + Result Map(KProcessPageTable *table, KProcessAddress address, size_t size, KProcess *process, ams::svc::MemoryPermission map_perm); + Result Unmap(KProcessPageTable *table, KProcessAddress address, size_t size, KProcess *process); + + u64 GetOwnerProcessId() const { return m_owner_process_id; } + size_t GetSize() const { return m_page_group.GetNumPages() * PageSize; } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_shared_memory_info.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_shared_memory_info.hpp new file mode 100644 index 00000000..c423a205 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_shared_memory_info.hpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_slab_helpers.hpp> + +namespace ams::kern { + + class KSharedMemory; + + class KSharedMemoryInfo : public KSlabAllocated<KSharedMemoryInfo>, public util::IntrusiveListBaseNode<KSharedMemoryInfo> { + private: + KSharedMemory *m_shared_memory; + size_t m_reference_count; + public: + explicit KSharedMemoryInfo() { /* ... */ } + ~KSharedMemoryInfo() { /* ... */ } + + constexpr void Initialize(KSharedMemory *m) { + MESOSPHERE_ASSERT_THIS(); + m_shared_memory = m; + m_reference_count = 0; + } + + constexpr void Open() { + ++m_reference_count; + MESOSPHERE_ASSERT(m_reference_count > 0); + } + + constexpr bool Close() { + MESOSPHERE_ASSERT(m_reference_count > 0); + return (--m_reference_count) == 0; + } + + constexpr KSharedMemory *GetSharedMemory() const { return m_shared_memory; } + constexpr size_t GetReferenceCount() const { return m_reference_count; } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_slab_heap.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_slab_heap.hpp new file mode 100644 index 00000000..bd8157bd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_slab_heap.hpp @@ -0,0 +1,247 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_typed_address.hpp> +#include <mesosphere/kern_k_memory_layout.hpp> + +#if defined(ATMOSPHERE_ARCH_ARM64) + + #include <mesosphere/arch/arm64/kern_k_slab_heap_impl.hpp> + namespace ams::kern { + using ams::kern::arch::arm64::IsSlabAtomicValid; + using ams::kern::arch::arm64::AllocateFromSlabAtomic; + using ams::kern::arch::arm64::FreeToSlabAtomic; + } + +#else + + #error "Unknown architecture for KSlabHeapImpl" + +#endif + + +namespace ams::kern { + + namespace impl { + + class KSlabHeapImpl { + NON_COPYABLE(KSlabHeapImpl); + NON_MOVEABLE(KSlabHeapImpl); + public: + struct Node { + Node *next; + }; + private: + Node *m_head{nullptr}; + public: + constexpr KSlabHeapImpl() = default; + + void Initialize() { + MESOSPHERE_ABORT_UNLESS(m_head == nullptr); + MESOSPHERE_ABORT_UNLESS(IsSlabAtomicValid()); + } + + ALWAYS_INLINE Node *GetHead() const { + return m_head; + } + + ALWAYS_INLINE void *Allocate() { + return AllocateFromSlabAtomic(std::addressof(m_head)); + } + + ALWAYS_INLINE void Free(void *obj) { + return FreeToSlabAtomic(std::addressof(m_head), static_cast<Node *>(obj)); + } + }; + + } + + template<bool SupportDynamicExpansion> + class KSlabHeapBase : protected impl::KSlabHeapImpl { + NON_COPYABLE(KSlabHeapBase); + NON_MOVEABLE(KSlabHeapBase); + private: + size_t m_obj_size{}; + uintptr_t m_peak{}; + uintptr_t m_start{}; + uintptr_t m_end{}; + private: + ALWAYS_INLINE void UpdatePeakImpl(uintptr_t obj) { + const util::AtomicRef<uintptr_t> peak_ref(m_peak); + + const uintptr_t alloc_peak = obj + this->GetObjectSize(); + uintptr_t cur_peak = m_peak; + do { + if (alloc_peak <= cur_peak) { + break; + } + } while (!peak_ref.CompareExchangeStrong(cur_peak, alloc_peak)); + } + public: + constexpr KSlabHeapBase() = default; + + ALWAYS_INLINE bool Contains(uintptr_t address) const { + return m_start <= address && address < m_end; + } + + void Initialize(size_t obj_size, void *memory, size_t memory_size) { + /* Ensure we don't initialize a slab using null memory. */ + MESOSPHERE_ABORT_UNLESS(memory != nullptr); + + /* Set our object size. */ + m_obj_size = obj_size; + + /* Initialize the base allocator. */ + KSlabHeapImpl::Initialize(); + + /* Set our tracking variables. */ + const size_t num_obj = (memory_size / obj_size); + m_start = reinterpret_cast<uintptr_t>(memory); + m_end = m_start + num_obj * obj_size; + m_peak = m_start; + + /* Free the objects. */ + u8 *cur = reinterpret_cast<u8 *>(m_end); + + for (size_t i = 0; i < num_obj; i++) { + cur -= obj_size; + KSlabHeapImpl::Free(cur); + } + } + + ALWAYS_INLINE size_t GetSlabHeapSize() const { + return (m_end - m_start) / this->GetObjectSize(); + } + + ALWAYS_INLINE size_t GetObjectSize() const { + return m_obj_size; + } + + ALWAYS_INLINE void *Allocate() { + void *obj = KSlabHeapImpl::Allocate(); + + /* Track the allocated peak. */ + #if defined(MESOSPHERE_BUILD_FOR_DEBUGGING) + if (AMS_LIKELY(obj != nullptr)) { + if constexpr (SupportDynamicExpansion) { + if (this->Contains(reinterpret_cast<uintptr_t>(obj))) { + this->UpdatePeakImpl(reinterpret_cast<uintptr_t>(obj)); + } else { + this->UpdatePeakImpl(reinterpret_cast<uintptr_t>(m_end) - this->GetObjectSize()); + } + } else { + this->UpdatePeakImpl(reinterpret_cast<uintptr_t>(obj)); + } + } + #endif + + return obj; + } + + ALWAYS_INLINE void Free(void *obj) { + /* Don't allow freeing an object that wasn't allocated from this heap. */ + const bool contained = this->Contains(reinterpret_cast<uintptr_t>(obj)); + if constexpr (SupportDynamicExpansion) { + const bool is_slab = KMemoryLayout::GetSlabRegion().Contains(reinterpret_cast<uintptr_t>(obj)); + MESOSPHERE_ABORT_UNLESS(contained || is_slab); + } else { + MESOSPHERE_ABORT_UNLESS(contained); + } + + KSlabHeapImpl::Free(obj); + } + + ALWAYS_INLINE size_t GetObjectIndex(const void *obj) const { + if constexpr (SupportDynamicExpansion) { + if (!this->Contains(reinterpret_cast<uintptr_t>(obj))) { + return std::numeric_limits<size_t>::max(); + } + } + + return (reinterpret_cast<uintptr_t>(obj) - m_start) / this->GetObjectSize(); + } + + ALWAYS_INLINE size_t GetPeakIndex() const { + return this->GetObjectIndex(reinterpret_cast<const void *>(m_peak)); + } + + ALWAYS_INLINE uintptr_t GetSlabHeapAddress() const { + return m_start; + } + + ALWAYS_INLINE size_t GetNumRemaining() const { + size_t remaining = 0; + + /* Only calculate the number of remaining objects under debug configuration. */ + #if defined(MESOSPHERE_BUILD_FOR_DEBUGGING) + while (true) { + auto *cur = this->GetHead(); + remaining = 0; + + if constexpr (SupportDynamicExpansion) { + const auto &slab_region = KMemoryLayout::GetSlabRegion(); + + while (this->Contains(reinterpret_cast<uintptr_t>(cur)) || slab_region.Contains(reinterpret_cast<uintptr_t>(cur))) { + ++remaining; + cur = cur->next; + } + } else { + while (this->Contains(reinterpret_cast<uintptr_t>(cur))) { + ++remaining; + cur = cur->next; + } + } + + if (cur == nullptr) { + break; + } + } + #endif + + return remaining; + } + }; + + template<typename T, bool SupportDynamicExpansion> + class KSlabHeap : public KSlabHeapBase<SupportDynamicExpansion> { + private: + using BaseHeap = KSlabHeapBase<SupportDynamicExpansion>; + public: + constexpr KSlabHeap() = default; + + void Initialize(void *memory, size_t memory_size) { + BaseHeap::Initialize(sizeof(T), memory, memory_size); + } + + ALWAYS_INLINE T *Allocate() { + T *obj = static_cast<T *>(BaseHeap::Allocate()); + if (AMS_LIKELY(obj != nullptr)) { + std::construct_at(obj); + } + return obj; + } + + ALWAYS_INLINE void Free(T *obj) { + BaseHeap::Free(obj); + } + + ALWAYS_INLINE size_t GetObjectIndex(const T *obj) const { + return BaseHeap::GetObjectIndex(obj); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_spin_lock.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_spin_lock.hpp new file mode 100644 index 00000000..bc7d0525 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_spin_lock.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_scoped_lock.hpp> + +#if defined(ATMOSPHERE_ARCH_ARM64) + + #include <mesosphere/arch/arm64/kern_k_spin_lock.hpp> + namespace ams::kern { + using ams::kern::arch::arm64::KAlignedSpinLock; + using ams::kern::arch::arm64::KNotAlignedSpinLock; + using ams::kern::arch::arm64::KSpinLock; + } + +#else + + #error "Unknown architecture for KInterruptManager" + +#endif + + +namespace ams::kern { + + using KScopedSpinLock = KScopedLock<KSpinLock>; + using KScopedAlignedSpinLock = KScopedLock<KAlignedSpinLock>; + using KScopedNotAlignedSpinLock = KScopedLock<KNotAlignedSpinLock>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_synchronization_object.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_synchronization_object.hpp new file mode 100644 index 00000000..315dde1f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_synchronization_object.hpp @@ -0,0 +1,85 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_k_auto_object.hpp> +#include <mesosphere/kern_slab_helpers.hpp> + +namespace ams::kern { + + class KThread; + + class KSynchronizationObject : public KAutoObjectWithList { + MESOSPHERE_AUTOOBJECT_TRAITS(KSynchronizationObject, KAutoObject); + public: + struct ThreadListNode { + ThreadListNode *next; + KThread *thread; + }; + private: + ThreadListNode *m_thread_list_head; + ThreadListNode *m_thread_list_tail; + protected: + constexpr ALWAYS_INLINE explicit KSynchronizationObject(util::ConstantInitializeTag) : KAutoObjectWithList(util::ConstantInitialize), m_thread_list_head(), m_thread_list_tail() { MESOSPHERE_ASSERT_THIS(); } + ALWAYS_INLINE explicit KSynchronizationObject() : m_thread_list_head(), m_thread_list_tail() { MESOSPHERE_ASSERT_THIS(); } + + /* NOTE: This is a virtual function which is overridden only by KDebugBase in Nintendo's kernel. */ + /* virtual void OnFinalizeSynchronizationObject() { MESOSPHERE_ASSERT_THIS(); } */ + + void NotifyAvailable(Result result); + ALWAYS_INLINE void NotifyAvailable() { + return this->NotifyAvailable(ResultSuccess()); + } + public: + static Result Wait(s32 *out_index, KSynchronizationObject **objects, const s32 num_objects, s64 timeout); + public: + void Finalize(); + virtual bool IsSignaled() const { AMS_INFINITE_LOOP(); } + + void DumpWaiters(); + + ALWAYS_INLINE void LinkNode(ThreadListNode *node) { + /* Link the node to the list. */ + if (m_thread_list_tail == nullptr) { + m_thread_list_head = node; + } else { + m_thread_list_tail->next = node; + } + + m_thread_list_tail = node; + } + + ALWAYS_INLINE void UnlinkNode(ThreadListNode *node) { + /* Unlink the node from the list. */ + ThreadListNode *prev_ptr = reinterpret_cast<ThreadListNode *>(std::addressof(m_thread_list_head)); + ThreadListNode *prev_val = nullptr; + ThreadListNode *prev, *tail_prev; + + do { + prev = prev_ptr; + prev_ptr = prev_ptr->next; + tail_prev = prev_val; + prev_val = prev_ptr; + } while (prev_ptr != node); + + if (m_thread_list_tail == node) { + m_thread_list_tail = tail_prev; + } + + prev->next = node->next; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_system_control_base.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_system_control_base.hpp new file mode 100644 index 00000000..3d7ea531 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_system_control_base.hpp @@ -0,0 +1,130 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_spin_lock.hpp> + +namespace ams::kern { + + struct InitialProcessBinaryLayout; + + namespace init { + + struct KInitArguments; + + } + +} + +namespace ams::kern { + + class KResourceLimit; + + class KSystemControlBase { + public: + /* This can be overridden as needed. */ + static constexpr size_t SecureAppletMemorySize = 0; + protected: + /* Nintendo uses std::mt19937_t for randomness. */ + /* To save space (and because mt19337_t isn't secure anyway), */ + /* We will use TinyMT. */ + static constinit inline bool s_uninitialized_random_generator{true}; + static constinit inline util::TinyMT s_random_generator{util::ConstantInitialize}; + static constinit inline KSpinLock s_random_lock; + public: + class Init { + private: + static void CpuOnImpl(u64 core_id, uintptr_t entrypoint, uintptr_t arg); + public: + /* Initialization. */ + static size_t GetRealMemorySize(); + static size_t GetIntendedMemorySize(); + static KPhysicalAddress GetKernelPhysicalBaseAddress(KPhysicalAddress base_address); + static void GetInitialProcessBinaryLayout(InitialProcessBinaryLayout *out, KPhysicalAddress kern_base_address); + static bool ShouldIncreaseThreadResourceLimit(); + static void TurnOnCpu(u64 core_id, const ams::kern::init::KInitArguments *args); + static size_t GetApplicationPoolSize(); + static size_t GetAppletPoolSize(); + static size_t GetMinimumNonSecureSystemPoolSize(); + static u8 GetDebugLogUartPort(); + + /* Randomness. */ + static void GenerateRandom(u64 *dst, size_t count); + static u64 GenerateRandomRange(u64 min, u64 max); + }; + protected: + static NOINLINE void InitializePhase1Base(u64 seed); + public: + /* Initialization. */ + static NOINLINE void ConfigureKTargetSystem(); + static NOINLINE void InitializePhase1(); + static NOINLINE void InitializePhase2(); + static NOINLINE u32 GetCreateProcessMemoryPool(); + + /* Randomness. */ + static void GenerateRandom(u64 *dst, size_t count); + static u64 GenerateRandomRange(u64 min, u64 max); + static u64 GenerateRandomU64(); + + /* Register access Access. */ + static Result ReadWriteRegister(u32 *out, ams::svc::PhysicalAddress address, u32 mask, u32 value); + static void ReadWriteRegisterPrivileged(u32 *out, ams::svc::PhysicalAddress address, u32 mask, u32 value); + + static u32 ReadRegisterPrivileged(ams::svc::PhysicalAddress address); + static void WriteRegisterPrivileged(ams::svc::PhysicalAddress address, u32 value); + + /* Power management. */ + static void SleepSystem(); + static NORETURN void StopSystem(void *arg = nullptr); + + /* User access. */ + #if defined(ATMOSPHERE_ARCH_ARM64) + static void CallSecureMonitorFromUser(ams::svc::lp64::SecureMonitorArguments *args); + #endif + + /* Secure Memory. */ + static size_t CalculateRequiredSecureMemorySize(size_t size, u32 pool); + static Result AllocateSecureMemory(KVirtualAddress *out, size_t size, u32 pool); + static void FreeSecureMemory(KVirtualAddress address, size_t size, u32 pool); + + /* Insecure Memory. */ + static KResourceLimit *GetInsecureMemoryResourceLimit(); + static u32 GetInsecureMemoryPool(); + protected: + template<typename F> + static ALWAYS_INLINE u64 GenerateUniformRange(u64 min, u64 max, F f) { + /* Handle the case where the difference is too large to represent. */ + if (max == std::numeric_limits<u64>::max() && min == std::numeric_limits<u64>::min()) { + return f(); + } + + /* Iterate until we get a value in range. */ + const u64 range_size = ((max + 1) - min); + const u64 effective_max = (std::numeric_limits<u64>::max() / range_size) * range_size; + while (true) { + if (const u64 rnd = f(); rnd < effective_max) { + return min + (rnd % range_size); + } + } + } + + /* User access. */ + #if defined(ATMOSPHERE_ARCH_ARM64) + static void CallSecureMonitorFromUserImpl(ams::svc::lp64::SecureMonitorArguments *args); + #endif + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_system_resource.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_system_resource.hpp new file mode 100644 index 00000000..628d1724 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_system_resource.hpp @@ -0,0 +1,112 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_auto_object.hpp> +#include <mesosphere/kern_slab_helpers.hpp> +#include <mesosphere/kern_k_memory_manager.hpp> +#include <mesosphere/kern_k_dynamic_resource_manager.hpp> +#include <mesosphere/kern_k_page_table_manager.hpp> +#include <mesosphere/kern_k_resource_limit.hpp> + +namespace ams::kern { + + /* NOTE: Nintendo's implementation does not have the "is_secure_resource" field, and instead uses virtual IsSecureResource(). */ + + class KSystemResource : public KAutoObject { + MESOSPHERE_AUTOOBJECT_TRAITS(KSystemResource, KAutoObject); + private: + KMemoryBlockSlabManager *m_p_memory_block_slab_manager{}; + KBlockInfoManager *m_p_block_info_manager{}; + KPageTableManager *m_p_page_table_manager{}; + bool m_is_secure_resource{false}; + public: + explicit KSystemResource() : KAutoObject() { /* ... */ } + + constexpr explicit KSystemResource(util::ConstantInitializeTag) : KAutoObject(util::ConstantInitialize) { /* ... */ } + protected: + ALWAYS_INLINE void SetSecureResource() { m_is_secure_resource = true; } + public: + virtual void Destroy() override { MESOSPHERE_PANIC("KSystemResource::Destroy() was called"); } + + ALWAYS_INLINE bool IsSecureResource() const { return m_is_secure_resource; } + + void SetManagers(KMemoryBlockSlabManager &mb, KBlockInfoManager &bi, KPageTableManager &pt) { + MESOSPHERE_ASSERT(m_p_memory_block_slab_manager == nullptr); + MESOSPHERE_ASSERT(m_p_block_info_manager == nullptr); + MESOSPHERE_ASSERT(m_p_page_table_manager == nullptr); + + m_p_memory_block_slab_manager = std::addressof(mb); + m_p_block_info_manager = std::addressof(bi); + m_p_page_table_manager = std::addressof(pt); + } + + const KMemoryBlockSlabManager &GetMemoryBlockSlabManager() const { return *m_p_memory_block_slab_manager; } + const KBlockInfoManager &GetBlockInfoManager() const { return *m_p_block_info_manager; } + const KPageTableManager &GetPageTableManager() const { return *m_p_page_table_manager; } + + KMemoryBlockSlabManager &GetMemoryBlockSlabManager() { return *m_p_memory_block_slab_manager; } + KBlockInfoManager &GetBlockInfoManager() { return *m_p_block_info_manager; } + KPageTableManager &GetPageTableManager() { return *m_p_page_table_manager; } + + KMemoryBlockSlabManager *GetMemoryBlockSlabManagerPointer() { return m_p_memory_block_slab_manager; } + KBlockInfoManager *GetBlockInfoManagerPointer() { return m_p_block_info_manager; } + KPageTableManager *GetPageTableManagerPointer() { return m_p_page_table_manager; } + }; + + class KSecureSystemResource final : public KAutoObjectWithSlabHeap<KSecureSystemResource, KSystemResource> { + private: + bool m_is_initialized; + KMemoryManager::Pool m_resource_pool; + KDynamicPageManager m_dynamic_page_manager; + KMemoryBlockSlabManager m_memory_block_slab_manager; + KBlockInfoManager m_block_info_manager; + KPageTableManager m_page_table_manager; + KMemoryBlockSlabHeap m_memory_block_heap; + KBlockInfoSlabHeap m_block_info_heap; + KPageTableSlabHeap m_page_table_heap; + KResourceLimit *m_resource_limit; + KVirtualAddress m_resource_address; + size_t m_resource_size; + public: + explicit KSecureSystemResource() : m_is_initialized(false), m_resource_limit(nullptr) { + /* Mark ourselves as being a secure resource. */ + this->SetSecureResource(); + } + + Result Initialize(size_t size, KResourceLimit *resource_limit, KMemoryManager::Pool pool); + void Finalize(); + + bool IsInitialized() const { return m_is_initialized; } + static void PostDestroy(uintptr_t arg) { MESOSPHERE_UNUSED(arg); /* ... */ } + + ALWAYS_INLINE size_t CalculateRequiredSecureMemorySize() const { + if (m_resource_limit != nullptr) { + return CalculateRequiredSecureMemorySize(m_resource_size, m_resource_pool); + } else { + return 0; + } + } + + ALWAYS_INLINE size_t GetSize() const { return m_resource_size; } + ALWAYS_INLINE size_t GetUsedSize() const { return m_dynamic_page_manager.GetUsed() * PageSize; } + + const KDynamicPageManager &GetDynamicPageManager() const { return m_dynamic_page_manager; } + public: + static size_t CalculateRequiredSecureMemorySize(size_t size, KMemoryManager::Pool pool); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_target_system.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_target_system.hpp new file mode 100644 index 00000000..949fd45b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_target_system.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_select_system_control.hpp> + +namespace ams::kern { + + class KTargetSystem { + private: + friend class KSystemControlBase; + friend class KSystemControl; + private: + struct KTargetSystemData { + bool is_not_debug_mode; + bool disable_debug_logging; + bool disable_user_exception_handlers; + bool disable_debug_memory_fill; + bool disable_user_pmu_access; + bool disable_kernel_debugging; + bool disable_dynamic_resource_limits; + }; + private: + static inline constinit bool s_is_uninitialized = true; + static inline constinit const volatile KTargetSystemData s_data = { + .is_not_debug_mode = false, + .disable_debug_logging = false, + .disable_user_exception_handlers = false, + .disable_debug_memory_fill = false, + .disable_user_pmu_access = false, + .disable_kernel_debugging = false, + .disable_dynamic_resource_limits = true, + }; + private: + static ALWAYS_INLINE void SetInitialized() { s_is_uninitialized = false; } + public: + static ALWAYS_INLINE bool IsDebugMode() { return !(s_is_uninitialized | s_data.is_not_debug_mode); } + static ALWAYS_INLINE bool IsDebugLoggingEnabled() { return !(s_is_uninitialized | s_data.disable_debug_logging); } + static ALWAYS_INLINE bool IsUserExceptionHandlersEnabled() { return !(s_is_uninitialized | s_data.disable_user_exception_handlers); } + static ALWAYS_INLINE bool IsDebugMemoryFillEnabled() { return !(s_is_uninitialized | s_data.disable_debug_memory_fill); } + static ALWAYS_INLINE bool IsUserPmuAccessEnabled() { return !(s_is_uninitialized | s_data.disable_user_pmu_access); } + static ALWAYS_INLINE bool IsKernelDebuggingEnabled() { return !(s_is_uninitialized | s_data.disable_kernel_debugging); } + static ALWAYS_INLINE bool IsDynamicResourceLimitsEnabled() { return !(s_is_uninitialized | s_data.disable_dynamic_resource_limits); } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_thread.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_thread.hpp new file mode 100644 index 00000000..f86457b4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_thread.hpp @@ -0,0 +1,812 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_svc.hpp> +#include <mesosphere/kern_slab_helpers.hpp> +#include <mesosphere/kern_k_synchronization_object.hpp> +#include <mesosphere/kern_k_affinity_mask.hpp> +#include <mesosphere/kern_k_thread_context.hpp> +#include <mesosphere/kern_k_current_context.hpp> +#include <mesosphere/kern_k_timer_task.hpp> +#include <mesosphere/kern_k_worker_task.hpp> + +namespace ams::kern { + + class KThreadQueue; + class KProcess; + class KConditionVariable; + class KAddressArbiter; + + using KThreadFunction = void (*)(uintptr_t); + + class KThread final : public KAutoObjectWithSlabHeapAndContainer<KThread, KWorkerTask>, public util::IntrusiveListBaseNode<KThread>, public KTimerTask { + MESOSPHERE_AUTOOBJECT_TRAITS(KThread, KSynchronizationObject); + private: + friend class KProcess; + friend class KConditionVariable; + friend class KAddressArbiter; + friend class KThreadQueue; + public: + static constexpr s32 MainThreadPriority = 1; + static constexpr s32 IdleThreadPriority = 64; + + enum ThreadType : u32 { + ThreadType_Main = 0, + ThreadType_Kernel = 1, + ThreadType_HighPriority = 2, + ThreadType_User = 3, + }; + + enum SuspendType : u32 { + SuspendType_Process = 0, + SuspendType_Thread = 1, + SuspendType_Debug = 2, + SuspendType_Backtrace = 3, + SuspendType_Init = 4, + + SuspendType_Count, + }; + + enum ThreadState : u16 { + ThreadState_Initialized = 0, + ThreadState_Waiting = 1, + ThreadState_Runnable = 2, + ThreadState_Terminated = 3, + + ThreadState_SuspendShift = 4, + ThreadState_Mask = (1 << ThreadState_SuspendShift) - 1, + + ThreadState_ProcessSuspended = (1 << (SuspendType_Process + ThreadState_SuspendShift)), + ThreadState_ThreadSuspended = (1 << (SuspendType_Thread + ThreadState_SuspendShift)), + ThreadState_DebugSuspended = (1 << (SuspendType_Debug + ThreadState_SuspendShift)), + ThreadState_BacktraceSuspended = (1 << (SuspendType_Backtrace + ThreadState_SuspendShift)), + ThreadState_InitSuspended = (1 << (SuspendType_Init + ThreadState_SuspendShift)), + + ThreadState_SuspendFlagMask = ((1 << SuspendType_Count) - 1) << ThreadState_SuspendShift, + }; + + enum DpcFlag : u32 { + DpcFlag_Terminating = (1 << 0), + DpcFlag_Terminated = (1 << 1), + DpcFlag_PerformDestruction = (1 << 2), + }; + + enum ExceptionFlag : u32 { + ExceptionFlag_IsCallingSvc = (1 << 0), + ExceptionFlag_IsInExceptionHandler = (1 << 1), + ExceptionFlag_IsFpuContextRestoreNeeded = (1 << 2), + ExceptionFlag_IsFpu64Bit = (1 << 3), + ExceptionFlag_IsInUsermodeExceptionHandler = (1 << 4), + ExceptionFlag_IsInCacheMaintenanceOperation = (1 << 5), + ExceptionFlag_IsInTlbMaintenanceOperation = (1 << 6), + #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) + ExceptionFlag_IsHardwareSingleStep = (1 << 7), + #endif + }; + + struct StackParameters { + svc::SvcAccessFlagSet svc_access_flags; + KThreadContext::CallerSaveFpuRegisters *caller_save_fpu_registers; + KThread *cur_thread; + s16 disable_count; + util::Atomic<u8> dpc_flags; + u8 current_svc_id; + u8 reserved_2c; + u8 exception_flags; + bool is_pinned; + u8 reserved_2f; + u8 reserved_30[0x10]; + KThreadContext context; + }; + + static_assert(util::IsAligned(AMS_OFFSETOF(StackParameters, context), 0x10)); + static_assert(sizeof(StackParameters) == THREAD_STACK_PARAMETERS_SIZE); + + static_assert(AMS_OFFSETOF(StackParameters, svc_access_flags) == THREAD_STACK_PARAMETERS_SVC_PERMISSION); + static_assert(AMS_OFFSETOF(StackParameters, caller_save_fpu_registers) == THREAD_STACK_PARAMETERS_CALLER_SAVE_FPU_REGISTERS); + static_assert(AMS_OFFSETOF(StackParameters, cur_thread) == THREAD_STACK_PARAMETERS_CUR_THREAD); + static_assert(AMS_OFFSETOF(StackParameters, disable_count) == THREAD_STACK_PARAMETERS_DISABLE_COUNT); + static_assert(AMS_OFFSETOF(StackParameters, dpc_flags) == THREAD_STACK_PARAMETERS_DPC_FLAGS); + static_assert(AMS_OFFSETOF(StackParameters, current_svc_id) == THREAD_STACK_PARAMETERS_CURRENT_SVC_ID); + static_assert(AMS_OFFSETOF(StackParameters, reserved_2c) == THREAD_STACK_PARAMETERS_RESERVED_2C); + static_assert(AMS_OFFSETOF(StackParameters, exception_flags) == THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS); + static_assert(AMS_OFFSETOF(StackParameters, is_pinned) == THREAD_STACK_PARAMETERS_IS_PINNED); + static_assert(AMS_OFFSETOF(StackParameters, reserved_2f) == THREAD_STACK_PARAMETERS_RESERVED_2F); + static_assert(AMS_OFFSETOF(StackParameters, reserved_30) == THREAD_STACK_PARAMETERS_RESERVED_30); + static_assert(AMS_OFFSETOF(StackParameters, context) == THREAD_STACK_PARAMETERS_THREAD_CONTEXT); + + + static_assert(ExceptionFlag_IsCallingSvc == THREAD_EXCEPTION_FLAG_IS_CALLING_SVC); + static_assert(ExceptionFlag_IsInExceptionHandler == THREAD_EXCEPTION_FLAG_IS_IN_EXCEPTION_HANDLER); + static_assert(ExceptionFlag_IsFpuContextRestoreNeeded == THREAD_EXCEPTION_FLAG_IS_FPU_CONTEXT_RESTORE_NEEDED); + static_assert(ExceptionFlag_IsFpu64Bit == THREAD_EXCEPTION_FLAG_IS_FPU_64_BIT); + static_assert(ExceptionFlag_IsInUsermodeExceptionHandler == THREAD_EXCEPTION_FLAG_IS_IN_USERMODE_EXCEPTION_HANDLER); + static_assert(ExceptionFlag_IsInCacheMaintenanceOperation == THREAD_EXCEPTION_FLAG_IS_IN_CACHE_MAINTENANCE_OPERATION); + static_assert(ExceptionFlag_IsInTlbMaintenanceOperation == THREAD_EXCEPTION_FLAG_IS_IN_TLB_MAINTENANCE_OPERATION); + #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) + static_assert(ExceptionFlag_IsHardwareSingleStep == THREAD_EXCEPTION_FLAG_IS_HARDWARE_SINGLE_STEP); + #endif + + struct QueueEntry { + private: + KThread *m_prev; + KThread *m_next; + public: + constexpr void Initialize() { + m_prev = nullptr; + m_next = nullptr; + } + + constexpr KThread *GetPrev() const { return m_prev; } + constexpr KThread *GetNext() const { return m_next; } + constexpr void SetPrev(KThread *t) { m_prev = t; } + constexpr void SetNext(KThread *t) { m_next = t; } + }; + + using WaiterList = util::IntrusiveListBaseTraits<KThread>::ListType; + private: + static constexpr size_t PriorityInheritanceCountMax = 10; + union SyncObjectBuffer { + KSynchronizationObject *m_sync_objects[ams::svc::ArgumentHandleCountMax]; + ams::svc::Handle m_handles[ams::svc::ArgumentHandleCountMax * (sizeof(KSynchronizationObject *) / sizeof(ams::svc::Handle))]; + + constexpr explicit SyncObjectBuffer(util::ConstantInitializeTag) : m_sync_objects() { /* ... */ } + + explicit SyncObjectBuffer() { /* ... */ } + }; + static_assert(sizeof(SyncObjectBuffer::m_sync_objects) == sizeof(SyncObjectBuffer::m_handles)); + + struct ConditionVariableComparator { + struct RedBlackKeyType { + uintptr_t m_cv_key; + s32 m_priority; + + constexpr ALWAYS_INLINE uintptr_t GetConditionVariableKey() const { + return m_cv_key; + } + + constexpr ALWAYS_INLINE s32 GetPriority() const { + return m_priority; + } + }; + + template<typename T> requires (std::same_as<T, KThread> || std::same_as<T, RedBlackKeyType>) + static constexpr ALWAYS_INLINE int Compare(const T &lhs, const KThread &rhs) { + const uintptr_t l_key = lhs.GetConditionVariableKey(); + const uintptr_t r_key = rhs.GetConditionVariableKey(); + + if (l_key < r_key) { + /* Sort first by key */ + return -1; + } else if (l_key == r_key && lhs.GetPriority() < rhs.GetPriority()) { + /* And then by priority. */ + return -1; + } else { + return 1; + } + } + }; + static_assert(ams::util::HasRedBlackKeyType<ConditionVariableComparator>); + static_assert(std::same_as<ams::util::RedBlackKeyType<ConditionVariableComparator, void>, ConditionVariableComparator::RedBlackKeyType>); + + struct LockWithPriorityInheritanceComparator { + struct RedBlackKeyType { + s32 m_priority; + + constexpr ALWAYS_INLINE s32 GetPriority() const { + return m_priority; + } + }; + + template<typename T> requires (std::same_as<T, KThread> || std::same_as<T, RedBlackKeyType>) + static constexpr ALWAYS_INLINE int Compare(const T &lhs, const KThread &rhs) { + if (lhs.GetPriority() < rhs.GetPriority()) { + /* Sort by priority. */ + return -1; + } else { + return 1; + } + } + }; + static_assert(ams::util::HasRedBlackKeyType<LockWithPriorityInheritanceComparator>); + static_assert(std::same_as<ams::util::RedBlackKeyType<LockWithPriorityInheritanceComparator, void>, LockWithPriorityInheritanceComparator::RedBlackKeyType>); + private: + util::IntrusiveListNode m_process_list_node; + util::IntrusiveRedBlackTreeNode m_condvar_arbiter_tree_node; + s32 m_priority; + + using ConditionVariableThreadTreeTraits = util::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&KThread::m_condvar_arbiter_tree_node>; + using ConditionVariableThreadTree = ConditionVariableThreadTreeTraits::TreeType<ConditionVariableComparator>; + + using LockWithPriorityInheritanceThreadTreeTraits = util::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&KThread::m_condvar_arbiter_tree_node>; + using LockWithPriorityInheritanceThreadTree = ConditionVariableThreadTreeTraits::TreeType<LockWithPriorityInheritanceComparator>; + public: + class LockWithPriorityInheritanceInfo : public KSlabAllocated<LockWithPriorityInheritanceInfo>, public util::IntrusiveListBaseNode<LockWithPriorityInheritanceInfo> { + private: + LockWithPriorityInheritanceThreadTree m_tree; + KProcessAddress m_address_key; + KThread *m_owner; + u32 m_waiter_count; + public: + constexpr LockWithPriorityInheritanceInfo() : m_tree(), m_address_key(Null<KProcessAddress>), m_owner(nullptr), m_waiter_count() { + /* ... */ + } + + static LockWithPriorityInheritanceInfo *Create(KProcessAddress address_key) { + /* Create a new lock info. */ + auto *new_lock = LockWithPriorityInheritanceInfo::Allocate(); + MESOSPHERE_ABORT_UNLESS(new_lock != nullptr); + + /* Set the new lock's address key. */ + new_lock->m_address_key = address_key; + + return new_lock; + } + + void SetOwner(KThread *new_owner) { + /* Set new owner. */ + m_owner = new_owner; + } + + void AddWaiter(KThread *waiter) { + /* Insert the waiter. */ + m_tree.insert(*waiter); + m_waiter_count++; + + waiter->SetWaitingLockInfo(this); + } + + [[nodiscard]] bool RemoveWaiter(KThread *waiter) { + m_tree.erase(m_tree.iterator_to(*waiter)); + + waiter->SetWaitingLockInfo(nullptr); + + return (--m_waiter_count) == 0; + } + + KThread *GetHighestPriorityWaiter() { return std::addressof(m_tree.front()); } + const KThread *GetHighestPriorityWaiter() const { return std::addressof(m_tree.front()); } + + LockWithPriorityInheritanceThreadTree &GetThreadTree() { return m_tree; } + const LockWithPriorityInheritanceThreadTree &GetThreadTree() const { return m_tree; } + + constexpr KProcessAddress GetAddressKey() const { return m_address_key; } + + constexpr KThread *GetOwner() const { return m_owner; } + + constexpr u32 GetWaiterCount() const { return m_waiter_count; } + }; + private: + using LockWithPriorityInheritanceInfoList = util::IntrusiveListBaseTraits<LockWithPriorityInheritanceInfo>::ListType; + + ConditionVariableThreadTree *m_condvar_tree; + uintptr_t m_condvar_key; + alignas(16) KThreadContext::CallerSaveFpuRegisters m_caller_save_fpu_registers; + u64 m_virtual_affinity_mask; + KAffinityMask m_physical_affinity_mask; + u64 m_thread_id; + util::Atomic<s64> m_cpu_time; + KProcessAddress m_address_key; + KProcess *m_parent; + void *m_kernel_stack_top; + u32 *m_light_ipc_data; + KProcessAddress m_tls_address; + void *m_tls_heap_address; + KLightLock m_activity_pause_lock; + SyncObjectBuffer m_sync_object_buffer; + s64 m_schedule_count; + s64 m_last_scheduled_tick; + QueueEntry m_per_core_priority_queue_entry[cpu::NumCores]; + KThreadQueue *m_wait_queue; + LockWithPriorityInheritanceInfoList m_held_lock_info_list; + LockWithPriorityInheritanceInfo *m_waiting_lock_info; + WaiterList m_pinned_waiter_list; + uintptr_t m_debug_params[3]; + KAutoObject *m_closed_object; + u32 m_address_key_value; + u32 m_suspend_request_flags; + u32 m_suspend_allowed_flags; + s32 m_synced_index; + Result m_wait_result; + Result m_debug_exception_result; + s32 m_base_priority; + s32 m_base_priority_on_unpin; + s32 m_physical_ideal_core_id; + s32 m_virtual_ideal_core_id; + s32 m_num_kernel_waiters; + s32 m_current_core_id; + s32 m_core_id; + KAffinityMask m_original_physical_affinity_mask; + s32 m_original_physical_ideal_core_id; + s32 m_num_core_migration_disables; + ThreadState m_thread_state; + util::Atomic<bool> m_termination_requested; + bool m_wait_cancelled; + bool m_cancellable; + bool m_signaled; + bool m_initialized; + bool m_debug_attached; + s8 m_priority_inheritance_count; + bool m_resource_limit_release_hint; + public: + constexpr explicit KThread(util::ConstantInitializeTag) + : KAutoObjectWithSlabHeapAndContainer<KThread, KWorkerTask>(util::ConstantInitialize), KTimerTask(util::ConstantInitialize), + m_process_list_node{}, m_condvar_arbiter_tree_node{util::ConstantInitialize}, m_priority{-1}, m_condvar_tree{}, m_condvar_key{}, + m_caller_save_fpu_registers{}, m_virtual_affinity_mask{}, m_physical_affinity_mask{}, m_thread_id{}, m_cpu_time{0}, m_address_key{Null<KProcessAddress>}, m_parent{}, + m_kernel_stack_top{}, m_light_ipc_data{}, m_tls_address{Null<KProcessAddress>}, m_tls_heap_address{}, m_activity_pause_lock{}, m_sync_object_buffer{util::ConstantInitialize}, + m_schedule_count{}, m_last_scheduled_tick{}, m_per_core_priority_queue_entry{}, m_wait_queue{}, m_held_lock_info_list{}, m_waiting_lock_info{}, + m_pinned_waiter_list{}, m_debug_params{}, m_closed_object{}, m_address_key_value{}, m_suspend_request_flags{}, m_suspend_allowed_flags{}, m_synced_index{}, + m_wait_result{svc::ResultNoSynchronizationObject()}, m_debug_exception_result{ResultSuccess()}, m_base_priority{}, m_base_priority_on_unpin{}, + m_physical_ideal_core_id{}, m_virtual_ideal_core_id{}, m_num_kernel_waiters{}, m_current_core_id{}, m_core_id{}, m_original_physical_affinity_mask{}, + m_original_physical_ideal_core_id{}, m_num_core_migration_disables{}, m_thread_state{}, m_termination_requested{false}, m_wait_cancelled{}, + m_cancellable{}, m_signaled{}, m_initialized{}, m_debug_attached{}, m_priority_inheritance_count{}, m_resource_limit_release_hint{} + { + /* ... */ + } + + explicit KThread() : m_priority(-1), m_condvar_tree(nullptr), m_condvar_key(0), m_parent(nullptr), m_initialized(false) { /* ... */ } + + Result Initialize(KThreadFunction func, uintptr_t arg, void *kern_stack_top, KProcessAddress user_stack_top, s32 prio, s32 virt_core, KProcess *owner, ThreadType type); + private: + static Result InitializeThread(KThread *thread, KThreadFunction func, uintptr_t arg, KProcessAddress user_stack_top, s32 prio, s32 virt_core, KProcess *owner, ThreadType type); + public: + static Result InitializeKernelThread(KThread *thread, KThreadFunction func, uintptr_t arg, s32 prio, s32 virt_core) { + R_RETURN(InitializeThread(thread, func, arg, Null<KProcessAddress>, prio, virt_core, nullptr, ThreadType_Kernel)); + } + + static Result InitializeHighPriorityThread(KThread *thread, KThreadFunction func, uintptr_t arg) { + R_RETURN(InitializeThread(thread, func, arg, Null<KProcessAddress>, 0, GetCurrentCoreId(), nullptr, ThreadType_HighPriority)); + } + + static Result InitializeUserThread(KThread *thread, KThreadFunction func, uintptr_t arg, KProcessAddress user_stack_top, s32 prio, s32 virt_core, KProcess *owner) { + R_RETURN(InitializeThread(thread, func, arg, user_stack_top, prio, virt_core, owner, ThreadType_User)); + } + + static void ResumeThreadsSuspendedForInit(); + private: + ALWAYS_INLINE StackParameters &GetStackParameters() { return *(reinterpret_cast< StackParameters *>(m_kernel_stack_top) - 1); } + ALWAYS_INLINE const StackParameters &GetStackParameters() const { return *(reinterpret_cast<const StackParameters *>(m_kernel_stack_top) - 1); } + public: + ALWAYS_INLINE s16 GetDisableDispatchCount() const { + MESOSPHERE_ASSERT_THIS(); + return this->GetStackParameters().disable_count; + } + + ALWAYS_INLINE void DisableDispatch() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(GetCurrentThread().GetDisableDispatchCount() >= 0); + this->GetStackParameters().disable_count++; + } + + ALWAYS_INLINE void EnableDispatch() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(GetCurrentThread().GetDisableDispatchCount() > 0); + this->GetStackParameters().disable_count--; + } + + void Pin(); + void Unpin(); + + ALWAYS_INLINE void SaveDebugParams(uintptr_t param1, uintptr_t param2, uintptr_t param3) { + m_debug_params[0] = param1; + m_debug_params[1] = param2; + m_debug_params[2] = param3; + } + + ALWAYS_INLINE void RestoreDebugParams(uintptr_t *param1, uintptr_t *param2, uintptr_t *param3) { + *param1 = m_debug_params[0]; + *param2 = m_debug_params[1]; + *param3 = m_debug_params[2]; + } + + NOINLINE void DisableCoreMigration(); + NOINLINE void EnableCoreMigration(); + private: + ALWAYS_INLINE void SetExceptionFlag(ExceptionFlag flag) { + MESOSPHERE_ASSERT_THIS(); + this->GetStackParameters().exception_flags |= flag; + } + + ALWAYS_INLINE void ClearExceptionFlag(ExceptionFlag flag) { + MESOSPHERE_ASSERT_THIS(); + this->GetStackParameters().exception_flags &= ~flag; + } + + ALWAYS_INLINE bool IsExceptionFlagSet(ExceptionFlag flag) const { + MESOSPHERE_ASSERT_THIS(); + return this->GetStackParameters().exception_flags & flag; + } + public: + /* ALWAYS_INLINE void SetCallingSvc() { return this->SetExceptionFlag(ExceptionFlag_IsCallingSvc); } */ + /* ALWAYS_INLINE void ClearCallingSvc() { return this->ClearExceptionFlag(ExceptionFlag_IsCallingSvc); } */ + ALWAYS_INLINE bool IsCallingSvc() const { return this->IsExceptionFlagSet(ExceptionFlag_IsCallingSvc); } + + ALWAYS_INLINE void SetInExceptionHandler() { return this->SetExceptionFlag(ExceptionFlag_IsInExceptionHandler); } + ALWAYS_INLINE void ClearInExceptionHandler() { return this->ClearExceptionFlag(ExceptionFlag_IsInExceptionHandler); } + ALWAYS_INLINE bool IsInExceptionHandler() const { return this->IsExceptionFlagSet(ExceptionFlag_IsInExceptionHandler); } + + /* ALWAYS_INLINE void SetFpuContextRestoreNeeded() { return this->SetExceptionFlag(ExceptionFlag_IsFpuContextRestoreNeeded); } */ + /* ALWAYS_INLINE void ClearFpuContextRestoreNeeded() { return this->ClearExceptionFlag(ExceptionFlag_IsFpuContextRestoreNeeded); } */ + /* ALWAYS_INLINE bool IsFpuContextRestoreNeeded() const { return this->IsExceptionFlagSet(ExceptionFlag_IsFpuContextRestoreNeeded); } */ + + ALWAYS_INLINE void SetFpu64Bit() { return this->SetExceptionFlag(ExceptionFlag_IsFpu64Bit); } + /* ALWAYS_INLINE void ClearFpu64Bit() { return this->ClearExceptionFlag(ExceptionFlag_IsFpu64Bit); } */ + /* ALWAYS_INLINE bool IsFpu64Bit() const { return this->IsExceptionFlagSet(ExceptionFlag_IsFpu64Bit); } */ + + ALWAYS_INLINE void SetInUsermodeExceptionHandler() { return this->SetExceptionFlag(ExceptionFlag_IsInUsermodeExceptionHandler); } + ALWAYS_INLINE void ClearInUsermodeExceptionHandler() { return this->ClearExceptionFlag(ExceptionFlag_IsInUsermodeExceptionHandler); } + ALWAYS_INLINE bool IsInUsermodeExceptionHandler() const { return this->IsExceptionFlagSet(ExceptionFlag_IsInUsermodeExceptionHandler); } + + ALWAYS_INLINE void SetInCacheMaintenanceOperation() { return this->SetExceptionFlag(ExceptionFlag_IsInCacheMaintenanceOperation); } + ALWAYS_INLINE void ClearInCacheMaintenanceOperation() { return this->ClearExceptionFlag(ExceptionFlag_IsInCacheMaintenanceOperation); } + ALWAYS_INLINE bool IsInCacheMaintenanceOperation() const { return this->IsExceptionFlagSet(ExceptionFlag_IsInCacheMaintenanceOperation); } + + ALWAYS_INLINE void SetInTlbMaintenanceOperation() { return this->SetExceptionFlag(ExceptionFlag_IsInTlbMaintenanceOperation); } + ALWAYS_INLINE void ClearInTlbMaintenanceOperation() { return this->ClearExceptionFlag(ExceptionFlag_IsInTlbMaintenanceOperation); } + ALWAYS_INLINE bool IsInTlbMaintenanceOperation() const { return this->IsExceptionFlagSet(ExceptionFlag_IsInTlbMaintenanceOperation); } + + #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) + ALWAYS_INLINE void SetHardwareSingleStep() { return this->SetExceptionFlag(ExceptionFlag_IsHardwareSingleStep); } + ALWAYS_INLINE void ClearHardwareSingleStep() { return this->ClearExceptionFlag(ExceptionFlag_IsHardwareSingleStep); } + ALWAYS_INLINE bool IsHardwareSingleStep() const { return this->IsExceptionFlagSet(ExceptionFlag_IsHardwareSingleStep); } + #endif + + ALWAYS_INLINE u8 GetSvcId() const { + MESOSPHERE_ASSERT_THIS(); + return this->GetStackParameters().current_svc_id; + } + + ALWAYS_INLINE void RegisterDpc(DpcFlag flag) { + this->GetStackParameters().dpc_flags |= flag; + } + + ALWAYS_INLINE void ClearDpc(DpcFlag flag) { + this->GetStackParameters().dpc_flags &= ~flag; + } + + ALWAYS_INLINE u8 GetDpc() const { + return this->GetStackParameters().dpc_flags.Load(); + } + + ALWAYS_INLINE bool HasDpc() const { + MESOSPHERE_ASSERT_THIS(); + return this->GetDpc() != 0; + } + + private: + void SetPinnedSvcPermissions(); + void SetUnpinnedSvcPermissions(); + + void SetUsermodeExceptionSvcPermissions(); + void ClearUsermodeExceptionSvcPermissions(); + private: + void UpdateState(); + + ALWAYS_INLINE void AddHeldLock(LockWithPriorityInheritanceInfo *lock_info); + ALWAYS_INLINE LockWithPriorityInheritanceInfo *FindHeldLock(KProcessAddress address_key); + + ALWAYS_INLINE void AddWaiterImpl(KThread *thread); + ALWAYS_INLINE void RemoveWaiterImpl(KThread *thread); + ALWAYS_INLINE static void RestorePriority(KThread *thread); + + void StartTermination(); + void FinishTermination(); + + void IncreaseBasePriority(s32 priority); + + NOINLINE void SetState(ThreadState state); + public: + constexpr u64 GetThreadId() const { return m_thread_id; } + + const KThreadContext &GetContext() const { return this->GetStackParameters().context; } + KThreadContext &GetContext() { return this->GetStackParameters().context; } + + const auto &GetCallerSaveFpuRegisters() const { return m_caller_save_fpu_registers; } + auto &GetCallerSaveFpuRegisters() { return m_caller_save_fpu_registers; } + + constexpr u64 GetVirtualAffinityMask() const { return m_virtual_affinity_mask; } + constexpr const KAffinityMask &GetAffinityMask() const { return m_physical_affinity_mask; } + + Result GetCoreMask(int32_t *out_ideal_core, u64 *out_affinity_mask); + Result SetCoreMask(int32_t ideal_core, u64 affinity_mask); + + Result GetPhysicalCoreMask(int32_t *out_ideal_core, u64 *out_affinity_mask); + + constexpr ThreadState GetState() const { return static_cast<ThreadState>(m_thread_state & ThreadState_Mask); } + constexpr ThreadState GetRawState() const { return m_thread_state; } + + constexpr uintptr_t GetConditionVariableKey() const { return m_condvar_key; } + constexpr uintptr_t GetAddressArbiterKey() const { return m_condvar_key; } + + constexpr void SetConditionVariable(ConditionVariableThreadTree *tree, KProcessAddress address, uintptr_t cv_key, u32 value) { + MESOSPHERE_ASSERT(m_waiting_lock_info == nullptr); + + m_condvar_tree = tree; + m_condvar_key = cv_key; + m_address_key = address; + m_address_key_value = value; + } + + constexpr void ClearConditionVariable() { + m_condvar_tree = nullptr; + } + + constexpr bool IsWaitingForConditionVariable() const { + return m_condvar_tree != nullptr; + } + + constexpr void SetAddressArbiter(ConditionVariableThreadTree *tree, uintptr_t address) { + MESOSPHERE_ASSERT(m_waiting_lock_info == nullptr); + + m_condvar_tree = tree; + m_condvar_key = address; + } + + constexpr void ClearAddressArbiter() { + m_condvar_tree = nullptr; + } + + constexpr bool IsWaitingForAddressArbiter() const { + return m_condvar_tree != nullptr; + } + + constexpr s32 GetIdealVirtualCore() const { return m_virtual_ideal_core_id; } + constexpr s32 GetIdealPhysicalCore() const { return m_physical_ideal_core_id; } + + constexpr s32 GetActiveCore() const { return m_core_id; } + constexpr void SetActiveCore(s32 core) { m_core_id = core; } + + constexpr ALWAYS_INLINE s32 GetCurrentCore() const { return m_current_core_id; } + constexpr void SetCurrentCore(s32 core) { m_current_core_id = core; } + + constexpr s32 GetPriority() const { return m_priority; } + constexpr void SetPriority(s32 prio) { m_priority = prio; } + + constexpr s32 GetBasePriority() const { return m_base_priority; } + + constexpr QueueEntry &GetPriorityQueueEntry(s32 core) { return m_per_core_priority_queue_entry[core]; } + constexpr const QueueEntry &GetPriorityQueueEntry(s32 core) const { return m_per_core_priority_queue_entry[core]; } + + constexpr ConditionVariableThreadTree *GetConditionVariableTree() const { return m_condvar_tree; } + + constexpr s32 GetNumKernelWaiters() const { return m_num_kernel_waiters; } + + void AddWaiter(KThread *thread); + void RemoveWaiter(KThread *thread); + KThread *RemoveWaiterByKey(bool *out_has_waiters, KProcessAddress key); + + constexpr KProcessAddress GetAddressKey() const { return m_address_key; } + constexpr u32 GetAddressKeyValue() const { return m_address_key_value; } + constexpr void SetAddressKey(KProcessAddress key) { MESOSPHERE_ASSERT(m_waiting_lock_info == nullptr); m_address_key = key; } + constexpr void SetAddressKey(KProcessAddress key, u32 val) { MESOSPHERE_ASSERT(m_waiting_lock_info == nullptr); m_address_key = key; m_address_key_value = val; } + + constexpr void SetWaitingLockInfo(LockWithPriorityInheritanceInfo *lock) { m_waiting_lock_info = lock; } + constexpr LockWithPriorityInheritanceInfo *GetWaitingLockInfo() { return m_waiting_lock_info; } + + constexpr KThread *GetLockOwner() const { return m_waiting_lock_info != nullptr ? m_waiting_lock_info->GetOwner() : nullptr; } + + constexpr void ClearWaitQueue() { m_wait_queue = nullptr; } + + void BeginWait(KThreadQueue *queue); + void NotifyAvailable(KSynchronizationObject *signaled_object, Result wait_result); + void EndWait(Result wait_result); + void CancelWait(Result wait_result, bool cancel_timer_task); + + constexpr void SetSyncedIndex(s32 index) { m_synced_index = index; } + constexpr s32 GetSyncedIndex() const { return m_synced_index; } + + constexpr void SetWaitResult(Result wait_res) { m_wait_result = wait_res; } + constexpr Result GetWaitResult() const { return m_wait_result; } + + constexpr void SetDebugExceptionResult(Result result) { m_debug_exception_result = result; } + + constexpr Result GetDebugExceptionResult() const { return m_debug_exception_result; } + + void WaitCancel(); + + bool IsWaitCancelled() const { return m_wait_cancelled; } + void ClearWaitCancelled() { m_wait_cancelled = false; } + + void ClearCancellable() { m_cancellable = false; } + void SetCancellable() { m_cancellable = true; } + + constexpr u32 *GetLightSessionData() const { return m_light_ipc_data; } + constexpr void SetLightSessionData(u32 *data) { m_light_ipc_data = data; } + + constexpr s64 GetLastScheduledTick() const { return m_last_scheduled_tick; } + constexpr void SetLastScheduledTick(s64 tick) { m_last_scheduled_tick = tick; } + + constexpr s64 GetYieldScheduleCount() const { return m_schedule_count; } + constexpr void SetYieldScheduleCount(s64 count) { m_schedule_count = count; } + + constexpr KProcess *GetOwnerProcess() const { return m_parent; } + constexpr bool IsUserThread() const { return m_parent != nullptr; } + + constexpr KProcessAddress GetThreadLocalRegionAddress() const { return m_tls_address; } + constexpr void *GetThreadLocalRegionHeapAddress() const { return m_tls_heap_address; } + + constexpr KSynchronizationObject **GetSynchronizationObjectBuffer() { return std::addressof(m_sync_object_buffer.m_sync_objects[0]); } + constexpr ams::svc::Handle *GetHandleBuffer() { return std::addressof(m_sync_object_buffer.m_handles[sizeof(m_sync_object_buffer.m_sync_objects) / (sizeof(ams::svc::Handle)) - ams::svc::ArgumentHandleCountMax]); } + + u16 GetUserDisableCount() const { return static_cast<ams::svc::ThreadLocalRegion *>(m_tls_heap_address)->disable_count; } + void SetInterruptFlag() const { static_cast<ams::svc::ThreadLocalRegion *>(m_tls_heap_address)->interrupt_flag = 1; } + void ClearInterruptFlag() const { static_cast<ams::svc::ThreadLocalRegion *>(m_tls_heap_address)->interrupt_flag = 0; } + + bool IsInUserCacheMaintenanceOperation() const { return static_cast<ams::svc::ThreadLocalRegion *>(m_tls_heap_address)->cache_maintenance_flag != 0; } + + ALWAYS_INLINE KAutoObject *GetClosedObject() { return m_closed_object; } + + ALWAYS_INLINE void SetClosedObject(KAutoObject *object) { + MESOSPHERE_ASSERT(object != nullptr); + + /* Set the object to destroy. */ + m_closed_object = object; + + /* Schedule destruction DPC. */ + if ((this->GetStackParameters().dpc_flags.Load<std::memory_order_relaxed>() & DpcFlag_PerformDestruction) == 0) { + this->RegisterDpc(DpcFlag_PerformDestruction); + } + } + + ALWAYS_INLINE void DestroyClosedObjects() { + /* Destroy all objects that have been closed. */ + if (KAutoObject *cur = m_closed_object; cur != nullptr) { + do { + /* Set our closed object as the next to close. */ + m_closed_object = cur->GetNextClosedObject(); + + /* Destroy the current object. */ + cur->Destroy(); + + /* Advance. */ + cur = m_closed_object; + } while (cur != nullptr); + + /* Clear the pending DPC. */ + this->ClearDpc(DpcFlag_PerformDestruction); + } + } + + constexpr void SetDebugAttached() { m_debug_attached = true; } + constexpr bool IsAttachedToDebugger() const { return m_debug_attached; } + + void AddCpuTime(s32 core_id, s64 amount) { + m_cpu_time += amount; + /* TODO: Debug kernels track per-core tick counts. Should we? */ + MESOSPHERE_UNUSED(core_id); + } + + s64 GetCpuTime() const { return m_cpu_time.Load(); } + + s64 GetCpuTime(s32 core_id) const { + MESOSPHERE_ABORT_UNLESS(0 <= core_id && core_id < static_cast<s32>(cpu::NumCores)); + + /* TODO: Debug kernels track per-core tick counts. Should we? */ + return 0; + } + + constexpr u32 GetSuspendFlags() const { return m_suspend_allowed_flags & m_suspend_request_flags; } + constexpr bool IsSuspended() const { return this->GetSuspendFlags() != 0; } + constexpr bool IsSuspendRequested(SuspendType type) const { return (m_suspend_request_flags & (1u << (util::ToUnderlying(ThreadState_SuspendShift) + util::ToUnderlying(type)))) != 0; } + constexpr bool IsSuspendRequested() const { return m_suspend_request_flags != 0; } + void RequestSuspend(SuspendType type); + void Resume(SuspendType type); + void TrySuspend(); + void Continue(); + + Result SetActivity(ams::svc::ThreadActivity activity); + Result GetThreadContext3(ams::svc::ThreadContext *out); + + void ContinueIfHasKernelWaiters() { + if (this->GetNumKernelWaiters() > 0) { + this->Continue(); + } + } + + void SetBasePriority(s32 priority); + Result SetPriorityToIdle(); + + Result Run(); + void Exit(); + + Result Terminate(); + ThreadState RequestTerminate(); + + Result Sleep(s64 timeout); + + ALWAYS_INLINE void *GetStackTop() const { return reinterpret_cast<StackParameters *>(m_kernel_stack_top) - 1; } + ALWAYS_INLINE void *GetKernelStackTop() const { return m_kernel_stack_top; } + + ALWAYS_INLINE bool IsTerminationRequested() const { + return m_termination_requested.Load() || this->GetRawState() == ThreadState_Terminated; + } + + size_t GetKernelStackUsage() const; + + void OnEnterUsermodeException(); + void OnLeaveUsermodeException(); + public: + /* Overridden parent functions. */ + ALWAYS_INLINE u64 GetIdImpl() const { return this->GetThreadId(); } + ALWAYS_INLINE u64 GetId() const { return this->GetIdImpl(); } + + bool IsInitialized() const { return m_initialized; } + uintptr_t GetPostDestroyArgument() const { return reinterpret_cast<uintptr_t>(m_parent) | (m_resource_limit_release_hint ? 1 : 0); } + + static void PostDestroy(uintptr_t arg); + + void Finalize(); + + virtual bool IsSignaled() const override; + void OnTimer(); + void DoWorkerTaskImpl(); + public: + static consteval bool IsKThreadStructurallyValid(); + + static KThread *GetThreadFromId(u64 thread_id); + static Result GetThreadList(s32 *out_num_threads, ams::kern::svc::KUserPointer<u64 *> out_thread_ids, s32 max_out_count); + + using ConditionVariableThreadTreeType = ConditionVariableThreadTree; + + }; + static_assert(alignof(KThread) == 0x10); + + consteval bool KThread::IsKThreadStructurallyValid() { + /* Check that the condition variable tree is valid. */ + static_assert(ConditionVariableThreadTreeTraits::IsValid()); + + /* Check that the assembly offsets are valid. */ + static_assert(AMS_OFFSETOF(KThread, m_kernel_stack_top) == THREAD_KERNEL_STACK_TOP); + + return true; + } + + static_assert(KThread::IsKThreadStructurallyValid()); + + class KScopedDisableDispatch { + public: + explicit ALWAYS_INLINE KScopedDisableDispatch() { + GetCurrentThread().DisableDispatch(); + } + + NOINLINE ~KScopedDisableDispatch(); + }; + + ALWAYS_INLINE KExceptionContext *GetExceptionContext(KThread *thread) { + return reinterpret_cast<KExceptionContext *>(reinterpret_cast<uintptr_t>(thread->GetKernelStackTop()) - sizeof(KThread::StackParameters) - sizeof(KExceptionContext)); + } + + ALWAYS_INLINE const KExceptionContext *GetExceptionContext(const KThread *thread) { + return reinterpret_cast<const KExceptionContext *>(reinterpret_cast<uintptr_t>(thread->GetKernelStackTop()) - sizeof(KThread::StackParameters) - sizeof(KExceptionContext)); + } + + ALWAYS_INLINE KProcess *GetCurrentProcessPointer() { + return GetCurrentThread().GetOwnerProcess(); + } + + ALWAYS_INLINE KProcess &GetCurrentProcess() { + return *GetCurrentProcessPointer(); + } + + ALWAYS_INLINE s32 GetCurrentCoreId() { + return GetCurrentThread().GetCurrentCore(); + } + + ALWAYS_INLINE void KTimerTask::OnTimer() { + static_cast<KThread *>(this)->OnTimer(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_thread_context.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_thread_context.hpp new file mode 100644 index 00000000..81e81b2b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_thread_context.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#ifdef ATMOSPHERE_ARCH_ARM64 + #include <mesosphere/arch/arm64/kern_k_thread_context.hpp> + + namespace ams::kern { + using ams::kern::arch::arm64::KThreadContext; + + using ams::kern::arch::arm64::GetUserContext; + } +#else + #error "Unknown architecture for KThreadContext" +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_thread_local_page.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_thread_local_page.hpp new file mode 100644 index 00000000..280b6590 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_thread_local_page.hpp @@ -0,0 +1,120 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_select_cpu.hpp> +#include <mesosphere/kern_slab_helpers.hpp> +#include <mesosphere/kern_k_page_buffer.hpp> + +namespace ams::kern { + + class KThread; + class KProcess; + + class KThreadLocalPage : public util::IntrusiveRedBlackTreeBaseNode<KThreadLocalPage>, public KSlabAllocated<KThreadLocalPage> { + public: + static constexpr size_t RegionsPerPage = PageSize / ams::svc::ThreadLocalRegionSize; + static_assert(RegionsPerPage > 0); + private: + KProcessAddress m_virt_addr; + KProcess *m_owner; + bool m_is_region_free[RegionsPerPage]; + public: + explicit KThreadLocalPage(KProcessAddress addr) : m_virt_addr(addr), m_owner(nullptr) { + for (size_t i = 0; i < util::size(m_is_region_free); i++) { + m_is_region_free[i] = true; + } + } + + explicit KThreadLocalPage() : KThreadLocalPage(Null<KProcessAddress>) { /* ... */ } + + constexpr ALWAYS_INLINE KProcessAddress GetAddress() const { return m_virt_addr; } + public: + using RedBlackKeyType = KProcessAddress; + + static constexpr ALWAYS_INLINE RedBlackKeyType GetRedBlackKey(const RedBlackKeyType &v) { return v; } + static constexpr ALWAYS_INLINE RedBlackKeyType GetRedBlackKey(const KThreadLocalPage &v) { return v.GetAddress(); } + + template<typename T> requires (std::same_as<T, KThreadLocalPage> || std::same_as<T, RedBlackKeyType>) + static constexpr ALWAYS_INLINE int Compare(const T &lhs, const KThreadLocalPage &rhs) { + const KProcessAddress lval = GetRedBlackKey(lhs); + const KProcessAddress rval = GetRedBlackKey(rhs); + + if (lval < rval) { + return -1; + } else if (lval == rval) { + return 0; + } else { + return 1; + } + } + private: + constexpr ALWAYS_INLINE KProcessAddress GetRegionAddress(size_t i) { + return this->GetAddress() + i * ams::svc::ThreadLocalRegionSize; + } + + constexpr ALWAYS_INLINE bool Contains(KProcessAddress addr) { + return this->GetAddress() <= addr && addr < this->GetAddress() + PageSize; + } + + constexpr ALWAYS_INLINE size_t GetRegionIndex(KProcessAddress addr) { + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(addr), ams::svc::ThreadLocalRegionSize)); + MESOSPHERE_ASSERT(this->Contains(addr)); + return (addr - this->GetAddress()) / ams::svc::ThreadLocalRegionSize; + } + public: + Result Initialize(KProcess *process); + Result Finalize(); + + KProcessAddress Reserve(); + void Release(KProcessAddress addr); + + void *GetPointer() const; + + bool IsAllUsed() const { + for (size_t i = 0; i < RegionsPerPage; i++) { + if (m_is_region_free[i]) { + return false; + } + } + return true; + } + + bool IsAllFree() const { + for (size_t i = 0; i < RegionsPerPage; i++) { + if (!m_is_region_free[i]) { + return false; + } + } + return true; + } + + bool IsAnyUsed() const { + return !this->IsAllFree(); + } + + bool IsAnyFree() const { + return !this->IsAllUsed(); + } + }; + + /* Miscellaneous sanity checking. */ + static_assert(ams::svc::ThreadLocalRegionSize == THREAD_LOCAL_REGION_SIZE); + static_assert(AMS_OFFSETOF(ams::svc::ThreadLocalRegion, message_buffer) == THREAD_LOCAL_REGION_MESSAGE_BUFFER); + static_assert(AMS_OFFSETOF(ams::svc::ThreadLocalRegion, disable_count) == THREAD_LOCAL_REGION_DISABLE_COUNT); + static_assert(AMS_OFFSETOF(ams::svc::ThreadLocalRegion, interrupt_flag) == THREAD_LOCAL_REGION_INTERRUPT_FLAG); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_thread_queue.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_thread_queue.hpp new file mode 100644 index 00000000..c0747fe1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_thread_queue.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_thread.hpp> +#include <mesosphere/kern_select_hardware_timer.hpp> + +namespace ams::kern { + + class KThreadQueue { + private: + KHardwareTimer *m_hardware_timer; + public: + constexpr ALWAYS_INLINE KThreadQueue() : m_hardware_timer(nullptr) { /* ... */ } + + constexpr void SetHardwareTimer(KHardwareTimer *timer) { m_hardware_timer = timer; } + + virtual void NotifyAvailable(KThread *waiting_thread, KSynchronizationObject *signaled_object, Result wait_result); + virtual void EndWait(KThread *waiting_thread, Result wait_result); + virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task); + }; + + class KThreadQueueWithoutEndWait : public KThreadQueue { + public: + constexpr ALWAYS_INLINE KThreadQueueWithoutEndWait() : KThreadQueue() { /* ... */ } + + virtual void EndWait(KThread *waiting_thread, Result wait_result) override final; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_timer_task.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_timer_task.hpp new file mode 100644 index 00000000..e1cc9cb4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_timer_task.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +namespace ams::kern { + + class KTimerTask : public util::IntrusiveRedBlackTreeBaseNode<KTimerTask> { + private: + s64 m_time; + public: + static constexpr ALWAYS_INLINE int Compare(const KTimerTask &lhs, const KTimerTask &rhs) { + if (lhs.GetTime() < rhs.GetTime()) { + return -1; + } else { + return 1; + } + } + public: + constexpr explicit ALWAYS_INLINE KTimerTask(util::ConstantInitializeTag) : util::IntrusiveRedBlackTreeBaseNode<KTimerTask>(util::ConstantInitialize), m_time(0) { /* ... */ } + explicit ALWAYS_INLINE KTimerTask() : m_time(0) { /* ... */ } + + constexpr ALWAYS_INLINE void SetTime(s64 t) { + m_time = t; + } + + constexpr ALWAYS_INLINE s64 GetTime() const { + return m_time; + } + + /* NOTE: This is virtual in Nintendo's kernel. Prior to 13.0.0, KWaitObject was also a TimerTask; this is no longer the case. */ + /* Since this is now KThread exclusive, we have devirtualized (see inline declaration for this inside kern_kthread.hpp). */ + void OnTimer(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_trace.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_trace.hpp new file mode 100644 index 00000000..d8b41497 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_trace.hpp @@ -0,0 +1,115 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_spin_lock.hpp> + +namespace ams::kern { + + #if defined(MESOSPHERE_BUILD_FOR_TRACING) + constexpr inline bool IsKTraceEnabled = true; + #else + constexpr inline bool IsKTraceEnabled = false; + #endif + + constexpr inline size_t KTraceBufferSize = IsKTraceEnabled ? 16_MB : 0; + + static_assert(IsKTraceEnabled || !IsKTraceEnabled); + static_assert((IsKTraceEnabled) ^ (KTraceBufferSize == 0)); + + class KTrace { + public: + enum Type { + Type_ThreadSwitch = 1, + + Type_SvcEntry0 = 3, + Type_SvcEntry1 = 4, + Type_SvcExit0 = 5, + Type_SvcExit1 = 6, + Type_Interrupt = 7, + + Type_ScheduleUpdate = 11, + + Type_CoreMigration = 14, + }; + private: + static bool s_is_active; + public: + static void Initialize(KVirtualAddress address, size_t size); + static void Start(); + static void Stop(); + + static void PushRecord(u8 type, u64 param0 = 0, u64 param1 = 0, u64 param2 = 0, u64 param3 = 0, u64 param4 = 0, u64 param5 = 0); + + static ALWAYS_INLINE bool IsActive() { return s_is_active; } + }; + +} + +#define MESOSPHERE_KTRACE_RESUME() \ + ({ \ + if constexpr (::ams::kern::IsKTraceEnabled) { \ + ::ams::kern::KTrace::Start(); \ + } \ + }) + +#define MESOSPHERE_KTRACE_PAUSE() \ + ({ \ + if constexpr (::ams::kern::IsKTraceEnabled) { \ + ::ams::kern::KTrace::Stop(); \ + } \ + }) + +#define MESOSPHERE_KTRACE_PUSH_RECORD(TYPE, ...) \ + ({ \ + if constexpr (::ams::kern::IsKTraceEnabled) { \ + if (::ams::kern::KTrace::IsActive()) { \ + ::ams::kern::KTrace::PushRecord(TYPE, ## __VA_ARGS__); \ + } \ + } \ + }) + +#define MESOSPHERE_KTRACE_THREAD_SWITCH(NEXT) \ + MESOSPHERE_KTRACE_PUSH_RECORD(::ams::kern::KTrace::Type_ThreadSwitch, (NEXT)->GetId()) + +#define MESOSPHERE_KTRACE_SVC_ENTRY(SVC_ID, PARAM0, PARAM1, PARAM2, PARAM3, PARAM4, PARAM5, PARAM6, PARAM7) \ + ({ \ + if constexpr (::ams::kern::IsKTraceEnabled) { \ + if (::ams::kern::KTrace::IsActive()) { \ + ::ams::kern::KTrace::PushRecord(::ams::kern::KTrace::Type_SvcEntry0, SVC_ID, PARAM0, PARAM1, PARAM2, PARAM3, PARAM4); \ + ::ams::kern::KTrace::PushRecord(::ams::kern::KTrace::Type_SvcEntry1, PARAM5, PARAM6, PARAM7); \ + } \ + } \ + }) + +#define MESOSPHERE_KTRACE_SVC_EXIT(SVC_ID, PARAM0, PARAM1, PARAM2, PARAM3, PARAM4, PARAM5, PARAM6, PARAM7) \ + ({ \ + if constexpr (::ams::kern::IsKTraceEnabled) { \ + if (::ams::kern::KTrace::IsActive()) { \ + ::ams::kern::KTrace::PushRecord(::ams::kern::KTrace::Type_SvcExit0, SVC_ID, PARAM0, PARAM1, PARAM2, PARAM3, PARAM4); \ + ::ams::kern::KTrace::PushRecord(::ams::kern::KTrace::Type_SvcExit1, PARAM5, PARAM6, PARAM7); \ + } \ + } \ + }) + +#define MESOSPHERE_KTRACE_INTERRUPT(ID) \ + MESOSPHERE_KTRACE_PUSH_RECORD(::ams::kern::KTrace::Type_Interrupt, ID) + +#define MESOSPHERE_KTRACE_SCHEDULE_UPDATE(CORE, PREV, NEXT) \ + MESOSPHERE_KTRACE_PUSH_RECORD(::ams::kern::KTrace::Type_ScheduleUpdate, CORE, (PREV)->GetId(), (NEXT)->GetId()) + +#define MESOSPHERE_KTRACE_CORE_MIGRATION(THREAD_ID, PREV, NEXT, REASON) \ + MESOSPHERE_KTRACE_PUSH_RECORD(::ams::kern::KTrace::Type_CoreMigration, THREAD_ID, PREV, NEXT, REASON) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_transfer_memory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_transfer_memory.hpp new file mode 100644 index 00000000..c34ceb8c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_transfer_memory.hpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_auto_object.hpp> +#include <mesosphere/kern_slab_helpers.hpp> + +namespace ams::kern { + + class KTransferMemory final : public KAutoObjectWithSlabHeapAndContainer<KTransferMemory, KAutoObjectWithList> { + MESOSPHERE_AUTOOBJECT_TRAITS(KTransferMemory, KAutoObject); + private: + util::TypedStorage<KPageGroup> m_page_group; + KProcess *m_owner; + KProcessAddress m_address; + KLightLock m_lock; + ams::svc::MemoryPermission m_owner_perm; + bool m_is_initialized; + bool m_is_mapped; + public: + explicit KTransferMemory() : m_owner(nullptr), m_address(Null<KProcessAddress>), m_owner_perm(ams::svc::MemoryPermission_None), m_is_initialized(false), m_is_mapped(false) { + /* ... */ + } + + Result Initialize(KProcessAddress addr, size_t size, ams::svc::MemoryPermission own_perm); + void Finalize(); + + bool IsInitialized() const { return m_is_initialized; } + uintptr_t GetPostDestroyArgument() const { return reinterpret_cast<uintptr_t>(m_owner); } + static void PostDestroy(uintptr_t arg); + + Result Map(KProcessAddress address, size_t size, ams::svc::MemoryPermission map_perm); + Result Unmap(KProcessAddress address, size_t size); + + KProcess *GetOwner() const { return m_owner; } + KProcessAddress GetSourceAddress() { return m_address; } + size_t GetSize() const { return m_is_initialized ? GetReference(m_page_group).GetNumPages() * PageSize : 0; } + + constexpr uintptr_t GetHint() const { + /* Get memory size. */ + const size_t size = this->GetSize(); + + /* TODO: Is this architecture specific? */ + if (size >= 2_MB) { + return GetInteger(m_address) & (2_MB - 1); + } else if (size >= 64_KB) { + return GetInteger(m_address) & (64_KB - 1); + } else if (size >= 4_KB) { + return GetInteger(m_address) & (4_KB - 1); + } else { + return 0; + } + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_typed_address.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_typed_address.hpp new file mode 100644 index 00000000..5f8ba89d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_typed_address.hpp @@ -0,0 +1,269 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +namespace ams::kern { + +#ifndef MESOSPHERE_DISABLE_TYPED_ADDRESSES + + template<bool Virtual, typename T> + class KTypedAddress { + private: + uintptr_t m_address; + public: + /* Constructors. */ + ALWAYS_INLINE KTypedAddress() { /* ... */ } + constexpr ALWAYS_INLINE KTypedAddress(uintptr_t a) : m_address(a) { /* ... */ } + template<typename U> + constexpr ALWAYS_INLINE explicit KTypedAddress(U *ptr) : m_address(reinterpret_cast<uintptr_t>(ptr)) { /* ... */ } + + /* Copy constructor. */ + constexpr ALWAYS_INLINE KTypedAddress(const KTypedAddress &rhs) = default; + + /* Assignment operator. */ + constexpr ALWAYS_INLINE KTypedAddress &operator=(const KTypedAddress &rhs) = default; + + /* Arithmetic operators. */ + template<typename I> + constexpr ALWAYS_INLINE KTypedAddress operator+(I rhs) const { + static_assert(std::is_integral<I>::value); + return m_address + rhs; + } + + template<typename I> + constexpr ALWAYS_INLINE KTypedAddress operator-(I rhs) const { + static_assert(std::is_integral<I>::value); + return m_address - rhs; + } + + constexpr ALWAYS_INLINE ptrdiff_t operator-(KTypedAddress rhs) const { + return m_address - rhs.m_address; + } + + template<typename I> + constexpr ALWAYS_INLINE KTypedAddress operator+=(I rhs) { + static_assert(std::is_integral<I>::value); + m_address += rhs; + return *this; + } + + template<typename I> + constexpr ALWAYS_INLINE KTypedAddress operator-=(I rhs) { + static_assert(std::is_integral<I>::value); + m_address -= rhs; + return *this; + } + + /* Logical operators. */ + constexpr ALWAYS_INLINE uintptr_t operator&(uintptr_t mask) const { + return m_address & mask; + } + + constexpr ALWAYS_INLINE uintptr_t operator|(uintptr_t mask) const { + return m_address | mask; + } + + constexpr ALWAYS_INLINE uintptr_t operator<<(int shift) const { + return m_address << shift; + } + + constexpr ALWAYS_INLINE uintptr_t operator>>(int shift) const { + return m_address >> shift; + } + + template<typename U> + constexpr ALWAYS_INLINE size_t operator/(U size) const { return m_address / size; } + + /* constexpr ALWAYS_INLINE uintptr_t operator%(U align) const { return m_address % align; } */ + + /* Comparison operators. */ + constexpr ALWAYS_INLINE bool operator==(KTypedAddress rhs) const { + return m_address == rhs.m_address; + } + + constexpr ALWAYS_INLINE bool operator!=(KTypedAddress rhs) const { + return m_address != rhs.m_address; + } + + constexpr ALWAYS_INLINE bool operator<(KTypedAddress rhs) const { + return m_address < rhs.m_address; + } + + constexpr ALWAYS_INLINE bool operator<=(KTypedAddress rhs) const { + return m_address <= rhs.m_address; + } + + constexpr ALWAYS_INLINE bool operator>(KTypedAddress rhs) const { + return m_address > rhs.m_address; + } + + constexpr ALWAYS_INLINE bool operator>=(KTypedAddress rhs) const { + return m_address >= rhs.m_address; + } + + /* For convenience, also define comparison operators versus uintptr_t. */ + constexpr ALWAYS_INLINE bool operator==(uintptr_t rhs) const { + return m_address == rhs; + } + + /* Allow getting the address explicitly, for use in accessors. */ + constexpr ALWAYS_INLINE uintptr_t GetValue() const { + return m_address; + } + + }; + + struct KPhysicalAddressTag{}; + struct KVirtualAddressTag{}; + struct KProcessAddressTag{}; + + using KPhysicalAddress = KTypedAddress<false, KPhysicalAddressTag>; + using KVirtualAddress = KTypedAddress<true, KVirtualAddressTag>; + using KProcessAddress = KTypedAddress<true, KProcessAddressTag>; + + /* Define accessors. */ + template<bool Virtual, typename T> + constexpr ALWAYS_INLINE uintptr_t GetInteger(KTypedAddress<Virtual, T> address) { + return address.GetValue(); + } + + template<typename T, typename U> + ALWAYS_INLINE T *GetPointer(KTypedAddress<true, U> address) { + return reinterpret_cast<T *>(address.GetValue()); + } + + template<typename T> + ALWAYS_INLINE void *GetVoidPointer(KTypedAddress<true, T> address) { + return reinterpret_cast<void *>(address.GetValue()); + } + +#else + + /* Plausibly, we may not want compiler overhead from using strongly typed addresses. */ + /* In this case, we should just use uintptr_t. */ + using KPhysicalAddress = uintptr_t; + using KVirtualAddress = uintptr_t; + using KProcessAddress = uintptr_t; + + /* Define accessors. */ + constexpr ALWAYS_INLINE uintptr_t GetInteger(uintptr_t address) { + return address; + } + + template<typename T> + constexpr ALWAYS_INLINE T *GetPointer(uintptr_t address) { + return reinterpret_cast<T *>(address); + } + + template<typename T> + constexpr ALWAYS_INLINE void *GetVoidPointer(uintptr_t address) { + return reinterpret_cast<void *>(address); + } + +#endif + + template<typename T> + concept IsKTypedAddress = std::same_as<T, KPhysicalAddress> || std::same_as<T, KVirtualAddress> || std::same_as<T, KProcessAddress>; + + template<typename T> + constexpr inline T Null = [] { + if constexpr (std::is_same<T, uintptr_t>::value) { + return 0; + } else { + static_assert(std::is_same<T, KPhysicalAddress>::value || + std::is_same<T, KVirtualAddress>::value || + std::is_same<T, KProcessAddress>::value); + return T(0); + } + }(); + + /* Basic type validations. */ + static_assert(sizeof(KPhysicalAddress) == sizeof(uintptr_t)); + static_assert(sizeof(KVirtualAddress) == sizeof(uintptr_t)); + static_assert(sizeof(KProcessAddress) == sizeof(uintptr_t)); + + static_assert(std::is_trivially_copyable<KPhysicalAddress>::value); + static_assert(std::is_trivially_copyable<KVirtualAddress>::value); + static_assert(std::is_trivially_copyable<KProcessAddress>::value); + + static_assert(std::is_trivially_copy_constructible<KPhysicalAddress>::value); + static_assert(std::is_trivially_copy_constructible<KVirtualAddress>::value); + static_assert(std::is_trivially_copy_constructible<KProcessAddress>::value); + + static_assert(std::is_trivially_move_constructible<KPhysicalAddress>::value); + static_assert(std::is_trivially_move_constructible<KVirtualAddress>::value); + static_assert(std::is_trivially_move_constructible<KProcessAddress>::value); + + static_assert(std::is_trivially_copy_assignable<KPhysicalAddress>::value); + static_assert(std::is_trivially_copy_assignable<KVirtualAddress>::value); + static_assert(std::is_trivially_copy_assignable<KProcessAddress>::value); + + static_assert(std::is_trivially_move_assignable<KPhysicalAddress>::value); + static_assert(std::is_trivially_move_assignable<KVirtualAddress>::value); + static_assert(std::is_trivially_move_assignable<KProcessAddress>::value); + + static_assert(std::is_trivially_destructible<KPhysicalAddress>::value); + static_assert(std::is_trivially_destructible<KVirtualAddress>::value); + static_assert(std::is_trivially_destructible<KProcessAddress>::value); + + static_assert(Null<uintptr_t> == 0); + static_assert(Null<KPhysicalAddress> == Null<uintptr_t>); + static_assert(Null<KVirtualAddress> == Null<uintptr_t>); + static_assert(Null<KProcessAddress> == Null<uintptr_t>); + + /* Constructor/assignment validations. */ + static_assert([]{ const KPhysicalAddress a(5); KPhysicalAddress b(a); return b; }() == KPhysicalAddress(5)); + static_assert([]{ const KPhysicalAddress a(5); KPhysicalAddress b(10); b = a; return b; }() == KPhysicalAddress(5)); + + /* Arithmetic validations. */ + static_assert(KPhysicalAddress(10) + 5 == KPhysicalAddress(15)); + static_assert(KPhysicalAddress(10) - 5 == KPhysicalAddress(5)); + static_assert([]{ KPhysicalAddress v(10); v += 5; return v; }() == KPhysicalAddress(15)); + static_assert([]{ KPhysicalAddress v(10); v -= 5; return v; }() == KPhysicalAddress(5)); + + /* Logical validations. */ + static_assert((KPhysicalAddress(0b11111111) >> 1) == 0b01111111); + static_assert((KPhysicalAddress(0b10101010) >> 1) == 0b01010101); + static_assert((KPhysicalAddress(0b11111111) << 1) == 0b111111110); + static_assert((KPhysicalAddress(0b01010101) << 1) == 0b10101010); + static_assert((KPhysicalAddress(0b11111111) & 0b01010101) == 0b01010101); + static_assert((KPhysicalAddress(0b11111111) & 0b10101010) == 0b10101010); + static_assert((KPhysicalAddress(0b01010101) & 0b10101010) == 0b00000000); + static_assert((KPhysicalAddress(0b00000000) | 0b01010101) == 0b01010101); + static_assert((KPhysicalAddress(0b11111111) | 0b01010101) == 0b11111111); + static_assert((KPhysicalAddress(0b10101010) | 0b01010101) == 0b11111111); + + /* Comparisons. */ + static_assert(KPhysicalAddress(0) == KPhysicalAddress(0)); + static_assert(KPhysicalAddress(0) != KPhysicalAddress(1)); + static_assert(KPhysicalAddress(0) < KPhysicalAddress(1)); + static_assert(KPhysicalAddress(0) <= KPhysicalAddress(1)); + static_assert(KPhysicalAddress(1) > KPhysicalAddress(0)); + static_assert(KPhysicalAddress(1) >= KPhysicalAddress(0)); + + static_assert(!(KPhysicalAddress(0) == KPhysicalAddress(1))); + static_assert(!(KPhysicalAddress(0) != KPhysicalAddress(0))); + static_assert(!(KPhysicalAddress(1) < KPhysicalAddress(0))); + static_assert(!(KPhysicalAddress(1) <= KPhysicalAddress(0))); + static_assert(!(KPhysicalAddress(0) > KPhysicalAddress(1))); + static_assert(!(KPhysicalAddress(0) >= KPhysicalAddress(1))); + + /* Accessors. */ + static_assert(15 == GetInteger(KPhysicalAddress(15))); + static_assert(0 == GetInteger(Null<KPhysicalAddress>)); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_unsafe_memory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_unsafe_memory.hpp new file mode 100644 index 00000000..db63c297 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_unsafe_memory.hpp @@ -0,0 +1,78 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_light_lock.hpp> + +namespace ams::kern { + + class KUnsafeMemory { + private: + mutable KLightLock m_lock; + size_t m_limit_size; + size_t m_current_size; + public: + constexpr KUnsafeMemory() : m_lock(), m_limit_size(), m_current_size() { /* ... */ } + + bool TryReserve(size_t size) { + MESOSPHERE_ASSERT_THIS(); + KScopedLightLock lk(m_lock); + + /* Test for overflow. */ + if (m_current_size > m_current_size + size) { + return false; + } + + /* Test for limit allowance. */ + if (m_current_size + size > m_limit_size) { + return false; + } + + /* Reserve the size. */ + m_current_size += size; + return true; + } + + void Release(size_t size) { + MESOSPHERE_ASSERT_THIS(); + KScopedLightLock lk(m_lock); + + MESOSPHERE_ABORT_UNLESS(m_current_size >= size); + m_current_size -= size; + } + + size_t GetLimitSize() const { + MESOSPHERE_ASSERT_THIS(); + KScopedLightLock lk(m_lock); + return m_limit_size; + } + + size_t GetCurrentSize() const { + MESOSPHERE_ASSERT_THIS(); + KScopedLightLock lk(m_lock); + return m_current_size; + } + + Result SetLimitSize(size_t size) { + MESOSPHERE_ASSERT_THIS(); + KScopedLightLock lk(m_lock); + + R_UNLESS(size >= m_current_size, svc::ResultLimitReached()); + m_limit_size = size; + R_SUCCEED(); + } + }; +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_unused_slab_memory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_unused_slab_memory.hpp new file mode 100644 index 00000000..032317ca --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_unused_slab_memory.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_typed_address.hpp> + +namespace ams::kern { + + /* Utilities to allocate/free memory from the "unused" gaps between slab heaps. */ + /* See KTargetSystem::IsDynamicResourceLimitsEnabled() usage for more context. */ + KVirtualAddress AllocateUnusedSlabMemory(size_t size, size_t alignment); + void FreeUnusedSlabMemory(KVirtualAddress address, size_t size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_wait_object.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_wait_object.hpp new file mode 100644 index 00000000..3beabd28 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_wait_object.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_timer_task.hpp> +#include <mesosphere/kern_k_thread.hpp> + +namespace ams::kern { + + class KWaitObject { + private: + KThread::WaiterList m_wait_list; + KThread *m_next_thread; + public: + constexpr KWaitObject() : m_wait_list(), m_next_thread() { /* ... */ } + + Result Synchronize(s64 timeout); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_worker_task.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_worker_task.hpp new file mode 100644 index 00000000..7519e589 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_worker_task.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +namespace ams::kern { + + /* NOTE: We inherit KWorkerTask from KSynchronizationObject in order to devirtualize DoWorkerTask, which is exclusive to KThread/KProcess. */ + /* This should be reverted, if Nintendo adds new types of worker tasks in the future. */ + + /* Nintendo has class KWorkerTask { ... } with identical interface but DoWorkerTask() virtual. */ + + class KWorkerTask : public KSynchronizationObject { + private: + KWorkerTask *m_next_task; + public: + constexpr ALWAYS_INLINE explicit KWorkerTask(util::ConstantInitializeTag) : KSynchronizationObject(util::ConstantInitialize), m_next_task(nullptr) { /* ... */ } + + ALWAYS_INLINE explicit KWorkerTask() : m_next_task(nullptr) { /* ... */ } + + constexpr ALWAYS_INLINE KWorkerTask *GetNextTask() const { return m_next_task; } + constexpr ALWAYS_INLINE void SetNextTask(KWorkerTask *task) { m_next_task = task; } + + void DoWorkerTask(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_worker_task_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_worker_task_manager.hpp new file mode 100644 index 00000000..cb87f22b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_k_worker_task_manager.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_worker_task.hpp> +#include <mesosphere/kern_k_thread.hpp> + +namespace ams::kern { + + class KWorkerTaskManager { + public: + static constexpr s32 ExitWorkerPriority = 11; + + enum WorkerType { + WorkerType_ExitThread, + WorkerType_ExitProcess, + + WorkerType_Count, + }; + private: + KWorkerTask *m_head_task; + KWorkerTask *m_tail_task; + KThread *m_waiting_thread; + private: + static void ThreadFunction(uintptr_t arg); + void ThreadFunctionImpl(); + + KWorkerTask *GetTask(); + void AddTask(KWorkerTask *task); + public: + constexpr KWorkerTaskManager() : m_head_task(), m_tail_task(), m_waiting_thread() { /* ... */ } + + NOINLINE void Initialize(s32 priority); + static void AddTask(WorkerType type, KWorkerTask *task); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_kernel.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_kernel.hpp new file mode 100644 index 00000000..95002f20 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_kernel.hpp @@ -0,0 +1,160 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_typed_address.hpp> +#include <mesosphere/kern_select_cpu.hpp> +#include <mesosphere/kern_k_memory_layout.hpp> +#include <mesosphere/kern_k_memory_manager.hpp> +#include <mesosphere/kern_k_scheduler.hpp> +#include <mesosphere/kern_k_interrupt_task_manager.hpp> +#include <mesosphere/kern_select_interrupt_manager.hpp> +#include <mesosphere/kern_select_hardware_timer.hpp> +#include <mesosphere/kern_k_worker_task_manager.hpp> + +namespace ams::kern { + + class KThread; + class KHardwareTimer; + class KResourceLimit; + class KInterruptManager; + class KInterruptTaskManager; + class KScheduler; + class KMemoryManager; + class KPageTableManager; + class KMemoryBlockSlabManager; + class KBlockInfoManager; + class KUnsafeMemory; + +#if defined(ATMOSPHERE_ARCH_ARM64) + + namespace arch::arm64 { + class KSupervisorPageTable; + } + using ams::kern::arch::arm64::KSupervisorPageTable; + +#else + + #error "Unknown architecture for KSupervisorPageTable forward declare" + +#endif + + class Kernel { + public: + enum class State : u8 { + Invalid = 0, + Initializing = 1, + Initialized = 2, + }; + + static constexpr size_t ApplicationMemoryBlockSlabHeapSize = 20000; + static constexpr size_t SystemMemoryBlockSlabHeapSize = 10000; + static constexpr size_t BlockInfoSlabHeapSize = 4000; + static constexpr size_t ReservedDynamicPageCount = 64; + private: + static State s_state; + static KResourceLimit s_system_resource_limit; + static KMemoryManager s_memory_manager; + static KPageTableSlabHeap s_page_table_heap; + static KMemoryBlockSlabHeap s_app_memory_block_heap; + static KMemoryBlockSlabHeap s_sys_memory_block_heap; + static KBlockInfoSlabHeap s_block_info_heap; + static KPageTableManager s_app_page_table_manager; + static KPageTableManager s_sys_page_table_manager; + static KMemoryBlockSlabManager s_app_memory_block_manager; + static KMemoryBlockSlabManager s_sys_memory_block_manager; + static KBlockInfoManager s_app_block_info_manager; + static KBlockInfoManager s_sys_block_info_manager; + static KSystemResource s_app_system_resource; + static KSystemResource s_sys_system_resource; + static KSupervisorPageTable s_supervisor_page_table; + static KUnsafeMemory s_unsafe_memory; + static KWorkerTaskManager s_worker_task_managers[KWorkerTaskManager::WorkerType_Count]; + static KInterruptManager s_interrupt_manager; + static KScheduler s_schedulers[cpu::NumCores]; + static KInterruptTaskManager s_interrupt_task_managers[cpu::NumCores]; + static KHardwareTimer s_hardware_timers[cpu::NumCores]; + public: + static NOINLINE void InitializeCoreLocalRegion(s32 core_id); + static NOINLINE void InitializeMainAndIdleThreads(s32 core_id); + static NOINLINE void InitializeResourceManagers(KVirtualAddress address, size_t size); + static NOINLINE void PrintLayout(); + + static ALWAYS_INLINE State GetState() { return s_state; } + static ALWAYS_INLINE void SetState(State state) { s_state = state; } + + static KThread &GetMainThread(s32 core_id); + static KThread &GetIdleThread(s32 core_id); + + static ALWAYS_INLINE KScheduler &GetScheduler() { + return s_schedulers[GetCurrentCoreId()]; + } + + static ALWAYS_INLINE KScheduler &GetScheduler(s32 core_id) { + return s_schedulers[core_id]; + } + + static ALWAYS_INLINE KInterruptTaskManager &GetInterruptTaskManager() { + return s_interrupt_task_managers[GetCurrentCoreId()]; + } + + static ALWAYS_INLINE KInterruptManager &GetInterruptManager() { + return s_interrupt_manager; + } + + static ALWAYS_INLINE KHardwareTimer &GetHardwareTimer() { + return s_hardware_timers[GetCurrentCoreId()]; + } + + static ALWAYS_INLINE KHardwareTimer &GetHardwareTimer(s32 core_id) { + return s_hardware_timers[core_id]; + } + + static ALWAYS_INLINE KResourceLimit &GetSystemResourceLimit() { + return s_system_resource_limit; + } + + static ALWAYS_INLINE KMemoryManager &GetMemoryManager() { + return s_memory_manager; + } + + static ALWAYS_INLINE KSystemResource &GetApplicationSystemResource() { + return s_app_system_resource; + } + + static ALWAYS_INLINE KSystemResource &GetSystemSystemResource() { + return s_sys_system_resource; + } + + static ALWAYS_INLINE KSupervisorPageTable &GetKernelPageTable() { + return s_supervisor_page_table; + } + + static ALWAYS_INLINE KUnsafeMemory &GetUnsafeMemory() { + return s_unsafe_memory; + } + + static ALWAYS_INLINE KWorkerTaskManager &GetWorkerTaskManager(KWorkerTaskManager::WorkerType type) { + MESOSPHERE_ASSERT(type <= KWorkerTaskManager::WorkerType_Count); + return s_worker_task_managers[type]; + } + }; + + ALWAYS_INLINE KScheduler &GetCurrentScheduler() { + return Kernel::GetScheduler(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_main.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_main.hpp new file mode 100644 index 00000000..3b45b533 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_main.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +namespace ams::kern { + + NORETURN void HorizonKernelMain(s32 core_id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_panic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_panic.hpp new file mode 100644 index 00000000..047a6576 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_panic.hpp @@ -0,0 +1,108 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_debug_log.hpp> + +namespace ams::kern { + + NORETURN NOINLINE void Panic(const char *file, int line, const char *format, ...) __attribute__((format(printf, 3, 4))); + NORETURN NOINLINE void Panic(); + +} + +namespace ams::diag { + + NORETURN ALWAYS_INLINE void OnAssertionFailure(AssertionType type, const char *expr, const char *func, const char *file, int line) { + #if defined(MESOSPHERE_ENABLE_DEBUG_PRINT) + ::ams::kern::Panic(file, line, "ams::diag::OnAssertionFailure: %d %s:%s", (type == AssertionType_Audit), func, expr); + #else + ::ams::kern::Panic(); + AMS_UNUSED(type, expr, func, file, line); + #endif + } + +} + +#define MESOSPHERE_UNUSED(...) AMS_UNUSED(__VA_ARGS__) + +#ifdef MESOSPHERE_ENABLE_DEBUG_PRINT +#define MESOSPHERE_PANIC(...) do { ::ams::kern::Panic(__FILE__, __LINE__, ## __VA_ARGS__); } while(0) +#else +#define MESOSPHERE_PANIC(...) do { MESOSPHERE_UNUSED(__VA_ARGS__); ::ams::kern::Panic(); } while(0) +#endif + +#ifdef MESOSPHERE_ENABLE_ASSERTIONS +#define MESOSPHERE_ASSERT_IMPL(expr, ...) \ + ({ \ + const bool __tmp_meso_assert_val = (expr); \ + if (AMS_UNLIKELY(!__tmp_meso_assert_val)) { \ + MESOSPHERE_PANIC(__VA_ARGS__); \ + } \ + }) +#elif defined(MESOSPHERE_PRESERVE_ASSERTION_EXPRESSIONS) +#define MESOSPHERE_ASSERT_IMPL(expr, ...) do { static_cast<void>(expr); } while (0) +#else +#define MESOSPHERE_ASSERT_IMPL(expr, ...) static_cast<void>(0) +#endif + +#define MESOSPHERE_ASSERT(expr) MESOSPHERE_ASSERT_IMPL(expr, "Assertion failed: %s\n", #expr) +#define MESOSPHERE_R_ASSERT(expr) MESOSPHERE_ASSERT_IMPL(R_SUCCEEDED(expr), "Result assertion failed: %s\n", #expr) +#define MESOSPHERE_UNREACHABLE_DEFAULT_CASE() default: MESOSPHERE_PANIC("Unreachable default case entered") + +#ifdef MESOSPHERE_ENABLE_THIS_ASSERT +#define MESOSPHERE_ASSERT_THIS() MESOSPHERE_ASSERT(this != nullptr) +#else +#define MESOSPHERE_ASSERT_THIS() +#endif + +#ifdef MESOSPHERE_BUILD_FOR_AUDITING +#define MESOSPHERE_AUDIT(expr) MESOSPHERE_ASSERT(expr) +#elif defined(MESOSPHERE_PRESERVE_AUDIT_EXPRESSIONS) +#define MESOSPHERE_AUDIT(expr) do { static_cast<void>(expr); } while (0) +#else +#define MESOSPHERE_AUDIT(expr) static_cast<void>(0) +#endif + +#define MESOSPHERE_TODO(arg) ({ constexpr const char *__mesosphere_todo = arg; static_cast<void>(__mesosphere_todo); MESOSPHERE_PANIC("TODO (%s): %s\n", __PRETTY_FUNCTION__, __mesosphere_todo); }) +#define MESOSPHERE_UNIMPLEMENTED() MESOSPHERE_PANIC("%s: Unimplemented\n", __PRETTY_FUNCTION__) + +#define MESOSPHERE_ABORT() MESOSPHERE_PANIC("Abort()\n"); +#define MESOSPHERE_INIT_ABORT() AMS_INFINITE_LOOP() + +#define MESOSPHERE_ABORT_UNLESS(expr) \ + ({ \ + const bool _tmp_meso_assert_val = (expr); \ + if (AMS_UNLIKELY(!_tmp_meso_assert_val)) { \ + MESOSPHERE_PANIC("Abort(): %s", #expr); \ + } \ + }) + +#define MESOSPHERE_INIT_ABORT_UNLESS(expr) \ + ({ \ + const bool __tmp_meso_assert_val = (expr); \ + if (AMS_UNLIKELY(!__tmp_meso_assert_val)) { \ + MESOSPHERE_INIT_ABORT(); \ + } \ + }) + +#define MESOSPHERE_R_ABORT_UNLESS(expr) \ + ({ \ + const ::ams::Result _tmp_meso_r_abort_res = static_cast<::ams::Result>((expr)); \ + if (AMS_UNLIKELY((R_FAILED(_tmp_meso_r_abort_res)))) { \ + MESOSPHERE_PANIC("Result Abort(): %s 2%03d-%04d\n", #expr, _tmp_meso_r_abort_res.GetModule(), _tmp_meso_r_abort_res.GetDescription()); \ + } \ + }) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_assembly_macros.h b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_assembly_macros.h new file mode 100644 index 00000000..519863c8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_assembly_macros.h @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#ifdef ATMOSPHERE_ARCH_ARM64 + + #include <mesosphere/arch/arm64/kern_assembly_macros.h> + +#else + + #error "Unknown architecture for CPU" + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_assembly_offsets.h b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_assembly_offsets.h new file mode 100644 index 00000000..5c778364 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_assembly_offsets.h @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#ifdef ATMOSPHERE_ARCH_ARM64 + + #include <mesosphere/arch/arm64/kern_assembly_offsets.h> + +#else + + #error "Unknown architecture for CPU" + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_cpu.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_cpu.hpp new file mode 100644 index 00000000..58945e6f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_cpu.hpp @@ -0,0 +1,85 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +#ifdef ATMOSPHERE_ARCH_ARM64 + #include <mesosphere/arch/arm64/kern_cpu.hpp> + + namespace ams::kern::cpu { + + using namespace ams::kern::arch::arm64::cpu; + + } + +#else + #error "Unknown architecture for CPU" +#endif + +#ifdef ATMOSPHERE_BOARD_NINTENDO_NX + + #include <mesosphere/board/nintendo/nx/kern_cpu_map.hpp> + + namespace ams::kern::cpu { + + using namespace ams::kern::board::nintendo::nx::impl::cpu; + + } + +#elif defined(ATMOSPHERE_BOARD_QEMU_VIRT) + + #include <mesosphere/board/qemu/virt/kern_cpu_map.hpp> + + namespace ams::kern::cpu { + + using namespace ams::kern::board::qemu::virt::impl::cpu; + + } + +#else + #error "Unknown board for CPU Map" +#endif + +namespace ams::kern { + + namespace cpu { + + static constexpr inline size_t NumVirtualCores = BITSIZEOF(u64); + + static constexpr inline u64 VirtualCoreMask = [] { + u64 mask = 0; + for (size_t i = 0; i < NumVirtualCores; ++i) { + mask |= (UINT64_C(1) << i); + } + return mask; + }(); + + static constexpr inline u64 ConvertVirtualCoreMaskToPhysical(u64 v_core_mask) { + u64 p_core_mask = 0; + while (v_core_mask != 0) { + const u64 next = util::CountTrailingZeros(v_core_mask); + v_core_mask &= ~(static_cast<u64>(1) << next); + p_core_mask |= (static_cast<u64>(1) << cpu::VirtualToPhysicalCoreMap[next]); + } + return p_core_mask; + } + + } + + static_assert(cpu::NumCores <= cpu::NumVirtualCores); + static_assert(util::size(cpu::VirtualToPhysicalCoreMap) == cpu::NumVirtualCores); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_debug.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_debug.hpp new file mode 100644 index 00000000..2cbb29b8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_debug.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +#if defined(ATMOSPHERE_ARCH_ARM64) + + #include <mesosphere/arch/arm64/kern_k_debug.hpp> + namespace ams::kern { + using ams::kern::arch::arm64::KDebug; + } + +#else + + #error "Unknown architecture for KDebug" + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_device_page_table.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_device_page_table.hpp new file mode 100644 index 00000000..f74796c2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_device_page_table.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +#ifdef ATMOSPHERE_BOARD_NINTENDO_NX + #include <mesosphere/board/nintendo/nx/kern_k_device_page_table.hpp> + + namespace ams::kern { + using ams::kern::board::nintendo::nx::KDevicePageTable; + } + +#else + #include <mesosphere/board/generic/kern_k_device_page_table.hpp> + + namespace ams::kern { + using ams::kern::board::generic::KDevicePageTable; + } + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_hardware_timer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_hardware_timer.hpp new file mode 100644 index 00000000..778b87b5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_hardware_timer.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +#if defined(ATMOSPHERE_ARCH_ARM64) + + #include <mesosphere/arch/arm64/kern_k_hardware_timer.hpp> + namespace ams::kern { + using ams::kern::arch::arm64::KHardwareTimer; + } + +#else + + #error "Unknown architecture for KHardwareTimer" + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_interrupt_controller.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_interrupt_controller.hpp new file mode 100644 index 00000000..46c0ba2a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_interrupt_controller.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_select_interrupt_name.hpp> + +#if defined(ATMOSPHERE_ARCH_ARM64) + + #include <mesosphere/arch/arm64/kern_k_interrupt_controller.hpp> + namespace ams::kern { + using ams::kern::arch::arm64::KInterruptController; + } + +#elif defined(ATMOSPHERE_ARCH_ARM) + + #include <mesosphere/arch/arm/kern_k_interrupt_controller.hpp> + namespace ams::kern { + using ams::kern::arch::arm::KInterruptController; + } + +#else + + #error "Unknown architecture for KInterruptController" + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_interrupt_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_interrupt_manager.hpp new file mode 100644 index 00000000..047ea960 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_interrupt_manager.hpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_select_interrupt_name.hpp> + +#if defined(ATMOSPHERE_ARCH_ARM64) + + #include <mesosphere/arch/arm64/kern_k_interrupt_manager.hpp> + namespace ams::kern { + using ams::kern::arch::arm64::KInterruptManager; + } + +#else + + #error "Unknown architecture for KInterruptManager" + +#endif + + +namespace ams::kern { + + + /* Enable or disable interrupts for the lifetime of an object. */ + class KScopedInterruptDisable { + NON_COPYABLE(KScopedInterruptDisable); + NON_MOVEABLE(KScopedInterruptDisable); + private: + u32 m_prev_intr_state; + public: + ALWAYS_INLINE KScopedInterruptDisable() : m_prev_intr_state(KInterruptManager::GetInterruptsEnabledStateAndDisableInterrupts()) { /* ... */ } + ALWAYS_INLINE ~KScopedInterruptDisable() { KInterruptManager::RestoreInterrupts(m_prev_intr_state); } + }; + + class KScopedInterruptEnable { + NON_COPYABLE(KScopedInterruptEnable); + NON_MOVEABLE(KScopedInterruptEnable); + private: + u32 m_prev_intr_state; + public: + ALWAYS_INLINE KScopedInterruptEnable() : m_prev_intr_state(KInterruptManager::GetInterruptsEnabledStateAndEnableInterrupts()) { /* ... */ } + ALWAYS_INLINE ~KScopedInterruptEnable() { KInterruptManager::RestoreInterrupts(m_prev_intr_state); } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_interrupt_name.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_interrupt_name.hpp new file mode 100644 index 00000000..70f64b1b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_interrupt_name.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +#if defined(ATMOSPHERE_ARCH_ARM64) + + #include <mesosphere/arch/arm64/kern_k_interrupt_name.hpp> + namespace ams::kern { + using namespace ams::kern::arch::arm64::interrupt_name; + } + +#else + + #error "Unknown architecture for KInterruptName" + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_page_table.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_page_table.hpp new file mode 100644 index 00000000..d6cd1f7b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_page_table.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +#if defined(ATMOSPHERE_ARCH_ARM64) + + #include <mesosphere/arch/arm64/kern_k_page_table.hpp> + #include <mesosphere/arch/arm64/kern_k_supervisor_page_table.hpp> + #include <mesosphere/arch/arm64/kern_k_process_page_table.hpp> + namespace ams::kern { + using ams::kern::arch::arm64::KPageTable; + using ams::kern::arch::arm64::KSupervisorPageTable; + using ams::kern::arch::arm64::KProcessPageTable; + } + +#else + + #error "Unknown architecture for KPageTable" + +#endif + +namespace ams::kern { + + /* NOTE: These three functions (Operate, Operate, FinalizeUpdate) are virtual functions */ + /* in Nintendo's kernel. We devirtualize them, since KPageTable is the only derived */ + /* class, and this avoids unnecessary virtual function calls. */ + static_assert(std::derived_from<KPageTable, KPageTableBase>); + + ALWAYS_INLINE Result KPageTableBase::Operate(PageLinkedList *page_list, KProcessAddress virt_addr, size_t num_pages, KPhysicalAddress phys_addr, bool is_pa_valid, const KPageProperties properties, OperationType operation, bool reuse_ll) { + R_RETURN(static_cast<KPageTable *>(this)->OperateImpl(page_list, virt_addr, num_pages, phys_addr, is_pa_valid, properties, operation, reuse_ll)); + } + + ALWAYS_INLINE Result KPageTableBase::Operate(PageLinkedList *page_list, KProcessAddress virt_addr, size_t num_pages, const KPageGroup &page_group, const KPageProperties properties, OperationType operation, bool reuse_ll) { + R_RETURN(static_cast<KPageTable *>(this)->OperateImpl(page_list, virt_addr, num_pages, page_group, properties, operation, reuse_ll)); + } + + ALWAYS_INLINE void KPageTableBase::FinalizeUpdate(PageLinkedList *page_list) { + static_cast<KPageTable *>(this)->FinalizeUpdateImpl(page_list); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_page_table_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_page_table_impl.hpp new file mode 100644 index 00000000..d0ddf5b9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_page_table_impl.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +#if defined(ATMOSPHERE_ARCH_ARM64) + + #include <mesosphere/arch/arm64/kern_k_page_table_impl.hpp> + namespace ams::kern { + using ams::kern::arch::arm64::KPageTableImpl; + } + +#else + + #error "Unknown architecture for KPageTableImpl" + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_system_control.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_system_control.hpp new file mode 100644 index 00000000..cc0edc55 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_system_control.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_system_control_base.hpp> + +#ifdef ATMOSPHERE_BOARD_NINTENDO_NX + #include <mesosphere/board/nintendo/nx/kern_k_system_control.hpp> + + namespace ams::kern { + using ams::kern::board::nintendo::nx::KSystemControl; + } + +#elif defined(ATMOSPHERE_BOARD_QEMU_VIRT) + #include <mesosphere/board/qemu/virt/kern_k_system_control.hpp> + + namespace ams::kern { + using ams::kern::board::qemu::virt::KSystemControl; + } + +#else + #error "Unknown board for KSystemControl" +#endif + +namespace ams::kern { + + ALWAYS_INLINE u32 KSystemControlBase::ReadRegisterPrivileged(ams::svc::PhysicalAddress address) { + u32 v; + KSystemControl::ReadWriteRegisterPrivileged(std::addressof(v), address, 0x00000000u, 0); + return v; + } + + ALWAYS_INLINE void KSystemControlBase::WriteRegisterPrivileged(ams::svc::PhysicalAddress address, u32 value) { + u32 v; + KSystemControl::ReadWriteRegisterPrivileged(std::addressof(v), address, 0xFFFFFFFFu, value); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_userspace_memory_access.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_userspace_memory_access.hpp new file mode 100644 index 00000000..f772424c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_select_userspace_memory_access.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> + +#ifdef ATMOSPHERE_ARCH_ARM64 + #include <mesosphere/arch/arm64/kern_userspace_memory_access.hpp> + + namespace ams::kern { + + using ams::kern::arch::arm64::UserspaceAccess; + + } + +#else + #error "Unknown architecture for CPU" +#endif + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_slab_helpers.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_slab_helpers.hpp new file mode 100644 index 00000000..6cf90555 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_slab_helpers.hpp @@ -0,0 +1,195 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/kern_common.hpp> +#include <mesosphere/kern_k_auto_object.hpp> +#include <mesosphere/kern_k_slab_heap.hpp> +#include <mesosphere/kern_k_auto_object_container.hpp> +#include <mesosphere/kern_k_unused_slab_memory.hpp> + +namespace ams::kern { + + template<class Derived, bool SupportDynamicExpansion = false> + class KSlabAllocated { + private: + static constinit inline KSlabHeap<Derived, SupportDynamicExpansion> s_slab_heap; + public: + constexpr KSlabAllocated() = default; + + size_t GetSlabIndex() const { + return s_slab_heap.GetIndex(static_cast<const Derived *>(this)); + } + public: + static void InitializeSlabHeap(void *memory, size_t memory_size) { + s_slab_heap.Initialize(memory, memory_size); + } + + static Derived *Allocate() { + return s_slab_heap.Allocate(); + } + + static void Free(Derived *obj) { + s_slab_heap.Free(obj); + } + + template<bool Enable = SupportDynamicExpansion, typename = typename std::enable_if<Enable>::type> + static Derived *AllocateFromUnusedSlabMemory() { + static_assert(Enable == SupportDynamicExpansion); + + Derived * const obj = GetPointer<Derived>(AllocateUnusedSlabMemory(sizeof(Derived), alignof(Derived))); + if (AMS_LIKELY(obj != nullptr)) { + std::construct_at(obj); + } + return obj; + } + + static size_t GetObjectSize() { return s_slab_heap.GetObjectSize(); } + static size_t GetSlabHeapSize() { return s_slab_heap.GetSlabHeapSize(); } + static size_t GetPeakIndex() { return s_slab_heap.GetPeakIndex(); } + static uintptr_t GetSlabHeapAddress() { return s_slab_heap.GetSlabHeapAddress(); } + + static size_t GetNumRemaining() { return s_slab_heap.GetNumRemaining(); } + }; + + template<typename Derived, typename Base, bool SupportDynamicExpansion> requires std::derived_from<Base, KAutoObject> + class KAutoObjectWithSlabHeapBase : public Base { + private: + template<typename, typename, bool> friend class KAutoObjectWithSlabHeap; + template<typename, typename, bool> friend class KAutoObjectWithSlabHeapAndContainer; + private: + static constinit inline KSlabHeap<Derived, SupportDynamicExpansion> s_slab_heap; + private: + static ALWAYS_INLINE Derived *Allocate() { + return s_slab_heap.Allocate(); + } + + static ALWAYS_INLINE void Free(Derived *obj) { + s_slab_heap.Free(obj); + } + private: + static ALWAYS_INLINE bool IsInitialized(const Derived *obj) { + if constexpr (requires { { obj->IsInitialized() } -> std::same_as<bool>; }) { + return obj->IsInitialized(); + } else { + return true; + } + } + + static ALWAYS_INLINE uintptr_t GetPostDestroyArgument(const Derived *obj) { + if constexpr (requires { { obj->GetPostDestroyArgument() } -> std::same_as<uintptr_t>; }) { + return obj->GetPostDestroyArgument(); + } else { + return 0; + } + } + public: + constexpr explicit KAutoObjectWithSlabHeapBase(util::ConstantInitializeTag) : Base(util::ConstantInitialize) { /* ... */ } + + explicit KAutoObjectWithSlabHeapBase() { /* ... */ } + + /* NOTE: IsInitialized() and GetPostDestroyArgument() are virtual functions declared in this class, */ + /* in Nintendo's kernel. We fully devirtualize them, as Destroy() is the only user of them. */ + /* We also devirtualize KAutoObject::Finalize(), which is only used by this function in Nintendo's kernel. */ + virtual void Destroy() override final { + Derived * const derived = static_cast<Derived *>(this); + + if (KAutoObjectWithSlabHeapBase<Derived, Base, SupportDynamicExpansion>::IsInitialized(derived)) { + Derived::PreFinalize(derived); + const uintptr_t arg = KAutoObjectWithSlabHeapBase<Derived, Base, SupportDynamicExpansion>::GetPostDestroyArgument(derived); + derived->Finalize(); + KAutoObjectWithSlabHeapBase<Derived, Base, SupportDynamicExpansion>::Free(derived); + Derived::PostDestroy(arg); + } else { + KAutoObjectWithSlabHeapBase<Derived, Base, SupportDynamicExpansion>::Free(derived); + } + } + + size_t GetSlabIndex() const { + return s_slab_heap.GetObjectIndex(static_cast<const Derived *>(this)); + } + public: + static void InitializeSlabHeap(void *memory, size_t memory_size) { + s_slab_heap.Initialize(memory, memory_size); + } + + static Derived *Create() { + Derived *obj = Allocate(); + if (AMS_LIKELY(obj != nullptr)) { + KAutoObject::Create<Derived>(obj); + } + return obj; + } + + template<bool Enable = SupportDynamicExpansion, typename = typename std::enable_if<Enable>::type> + static Derived *CreateFromUnusedSlabMemory() { + static_assert(Enable == SupportDynamicExpansion); + + Derived * const obj = GetPointer<Derived>(AllocateUnusedSlabMemory(sizeof(Derived), alignof(Derived))); + if (AMS_LIKELY(obj != nullptr)) { + std::construct_at(obj); + KAutoObject::Create<Derived>(obj); + } + return obj; + } + + static size_t GetObjectSize() { return s_slab_heap.GetObjectSize(); } + static size_t GetSlabHeapSize() { return s_slab_heap.GetSlabHeapSize(); } + static size_t GetPeakIndex() { return s_slab_heap.GetPeakIndex(); } + static uintptr_t GetSlabHeapAddress() { return s_slab_heap.GetSlabHeapAddress(); } + + static size_t GetNumRemaining() { return s_slab_heap.GetNumRemaining(); } + }; + + template<typename Derived, typename Base, bool SupportDynamicExpansion = false> + class KAutoObjectWithSlabHeap : public KAutoObjectWithSlabHeapBase<Derived, Base, SupportDynamicExpansion> { + public: + constexpr explicit KAutoObjectWithSlabHeap(util::ConstantInitializeTag) : KAutoObjectWithSlabHeapBase<Derived, Base, SupportDynamicExpansion>(util::ConstantInitialize) { /* ... */ } + + explicit KAutoObjectWithSlabHeap() { /* ... */ } + + static ALWAYS_INLINE void PreFinalize(Derived *) { /* ... */ } + }; + + + template<typename Derived, typename Base, bool SupportDynamicExpansion = false> + class KAutoObjectWithSlabHeapAndContainer : public KAutoObjectWithSlabHeapBase<Derived, Base, SupportDynamicExpansion> { + static_assert(std::derived_from<Base, KAutoObjectWithList>); + private: + static constinit inline KAutoObjectWithListContainer<Derived> s_container; + public: + class ListAccessor : public KAutoObjectWithListContainer<Derived>::ListAccessor { + public: + ALWAYS_INLINE ListAccessor() : KAutoObjectWithListContainer<Derived>::ListAccessor(s_container) { /* ... */ } + ALWAYS_INLINE ~ListAccessor() { /* ... */ } + }; + public: + constexpr explicit KAutoObjectWithSlabHeapAndContainer(util::ConstantInitializeTag) : KAutoObjectWithSlabHeapBase<Derived, Base, SupportDynamicExpansion>(util::ConstantInitialize) { /* ... */ } + + explicit KAutoObjectWithSlabHeapAndContainer() { /* ... */ } + public: + static void InitializeSlabHeap(void *memory, size_t memory_size) { + KAutoObjectWithSlabHeapBase<Derived, Base, SupportDynamicExpansion>::InitializeSlabHeap(memory, memory_size); + s_container.Initialize(); + } + + static void Register(Derived *obj) { + return s_container.Register(obj); + } + + static ALWAYS_INLINE void PreFinalize(Derived *obj) { s_container.Unregister(obj); } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_svc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_svc.hpp new file mode 100644 index 00000000..94640b0b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/kern_svc.hpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere/svc/kern_svc_results.hpp> +#include <mesosphere/svc/kern_svc_k_user_pointer.hpp> +#include <mesosphere/svc/kern_svc_prototypes.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/svc/kern_svc_k_user_pointer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/svc/kern_svc_k_user_pointer.hpp new file mode 100644 index 00000000..ad2c107c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/svc/kern_svc_k_user_pointer.hpp @@ -0,0 +1,203 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <mesosphere/svc/kern_svc_results.hpp> +#include <mesosphere/kern_select_userspace_memory_access.hpp> + +namespace ams::kern::svc { + + namespace impl { + + template<typename T> + concept Pointer = std::is_pointer<T>::value; + + template<typename T> + concept NonConstPointer = Pointer<T> && !std::is_const<typename std::remove_pointer<T>::type>::value; + + template<typename T> + concept ConstPointer = Pointer<T> && std::is_const<typename std::remove_pointer<T>::type>::value; + + template<typename T, size_t N> + concept AlignedNPointer = Pointer<T> && alignof(typename std::remove_pointer<T>::type) >= N && util::IsAligned(sizeof(typename std::remove_pointer<T>::type), N); + + template<typename T> + concept Aligned8Pointer = AlignedNPointer<T, sizeof(u8)>; + + template<typename T> + concept Aligned16Pointer = AlignedNPointer<T, sizeof(u16)> && Aligned8Pointer<T>; + + template<typename T> + concept Aligned32Pointer = AlignedNPointer<T, sizeof(u32)> && Aligned16Pointer<T>; + + template<typename T> + concept Aligned64Pointer = AlignedNPointer<T, sizeof(u64)> && Aligned32Pointer<T>; + + template<typename _T> + class KUserPointerImplTraits; + + template<typename _T> requires Aligned8Pointer<_T> + class KUserPointerImplTraits<_T> { + public: + using T = typename std::remove_const<typename std::remove_pointer<_T>::type>::type; + public: + static ALWAYS_INLINE Result CopyFromUserspace(void *dst, const void *src, size_t size) { + R_UNLESS(UserspaceAccess::CopyMemoryFromUser(dst, src, size), svc::ResultInvalidPointer()); + R_SUCCEED(); + } + + static ALWAYS_INLINE Result CopyToUserspace(void *dst, const void *src, size_t size) { + R_UNLESS(UserspaceAccess::CopyMemoryToUser(dst, src, size), svc::ResultInvalidPointer()); + R_SUCCEED(); + } + }; + + template<typename _T> requires Aligned32Pointer<_T> + class KUserPointerImplTraits<_T> { + public: + using T = typename std::remove_const<typename std::remove_pointer<_T>::type>::type; + public: + static ALWAYS_INLINE Result CopyFromUserspace(void *dst, const void *src, size_t size) { + R_UNLESS(UserspaceAccess::CopyMemoryFromUserAligned32Bit(dst, src, size), svc::ResultInvalidPointer()); + R_SUCCEED(); + } + + static ALWAYS_INLINE Result CopyToUserspace(void *dst, const void *src, size_t size) { + R_UNLESS(UserspaceAccess::CopyMemoryToUserAligned32Bit(dst, src, size), svc::ResultInvalidPointer()); + R_SUCCEED(); + } + }; + + template<typename _T> requires Aligned64Pointer<_T> + class KUserPointerImplTraits<_T> { + public: + using T = typename std::remove_const<typename std::remove_pointer<_T>::type>::type; + public: + static ALWAYS_INLINE Result CopyFromUserspace(void *dst, const void *src, size_t size) { + R_UNLESS(UserspaceAccess::CopyMemoryFromUserAligned64Bit(dst, src, size), svc::ResultInvalidPointer()); + R_SUCCEED(); + } + + static ALWAYS_INLINE Result CopyToUserspace(void *dst, const void *src, size_t size) { + R_UNLESS(UserspaceAccess::CopyMemoryToUserAligned64Bit(dst, src, size), svc::ResultInvalidPointer()); + R_SUCCEED(); + } + }; + + template<typename _T> + class KUserPointerImpl; + + template<typename _T> requires Aligned8Pointer<_T> + class KUserPointerImpl<_T> : impl::KUserPointerTag { + private: + using Traits = KUserPointerImplTraits<_T>; + protected: + using CT = typename std::remove_pointer<_T>::type; + using T = typename std::remove_const<CT>::type; + private: + CT *m_ptr; + private: + ALWAYS_INLINE Result CopyToImpl(void *p, size_t size) const { + R_RETURN(Traits::CopyFromUserspace(p, m_ptr, size)); + } + + ALWAYS_INLINE Result CopyFromImpl(const void *p, size_t size) const { + R_RETURN(Traits::CopyToUserspace(m_ptr, p, size)); + } + protected: + ALWAYS_INLINE Result CopyTo(T *p) const { R_RETURN(this->CopyToImpl(p, sizeof(*p))); } + ALWAYS_INLINE Result CopyFrom(const T *p) const { R_RETURN(this->CopyFromImpl(p, sizeof(*p))); } + + ALWAYS_INLINE Result CopyArrayElementTo(T *p, size_t index) const { R_RETURN(Traits::CopyFromUserspace(p, m_ptr + index, sizeof(*p))); } + ALWAYS_INLINE Result CopyArrayElementFrom(const T *p, size_t index) const { R_RETURN(Traits::CopyToUserspace(m_ptr + index, p, sizeof(*p))); } + + ALWAYS_INLINE Result CopyArrayTo(T *arr, size_t count) const { R_RETURN(this->CopyToImpl(arr, sizeof(*arr) * count)); } + ALWAYS_INLINE Result CopyArrayFrom(const T *arr, size_t count) const { R_RETURN(this->CopyFromImpl(arr, sizeof(*arr) * count)); } + + constexpr ALWAYS_INLINE bool IsNull() const { return m_ptr == nullptr; } + + constexpr ALWAYS_INLINE CT *GetUnsafePointer() const { return m_ptr; } + }; + + template<> + class KUserPointerImpl<const char *> : impl::KUserPointerTag { + private: + using Traits = KUserPointerImplTraits<const char *>; + protected: + using CT = const char; + using T = char; + private: + const char *m_ptr; + protected: + ALWAYS_INLINE Result CopyStringTo(char *dst, size_t size) const { + static_assert(sizeof(char) == 1); + R_UNLESS(UserspaceAccess::CopyStringFromUser(dst, m_ptr, size) > 0, svc::ResultInvalidPointer()); + R_SUCCEED(); + } + + ALWAYS_INLINE Result CopyArrayElementTo(char *dst, size_t index) const { + R_RETURN(Traits::CopyFromUserspace(dst, m_ptr + index, sizeof(*dst))); + } + + constexpr ALWAYS_INLINE bool IsNull() const { return m_ptr == nullptr; } + + constexpr ALWAYS_INLINE const char *GetUnsafePointer() const { return m_ptr; } + }; + + } + + template<typename T> + struct KUserPointer; + + template<typename T> requires impl::ConstPointer<T> + struct KUserPointer<T> : public impl::KUserPointerImpl<T> { + public: + static constexpr bool IsInput = true; + public: + using impl::KUserPointerImpl<T>::CopyTo; + using impl::KUserPointerImpl<T>::CopyArrayElementTo; + using impl::KUserPointerImpl<T>::CopyArrayTo; + using impl::KUserPointerImpl<T>::IsNull; + + using impl::KUserPointerImpl<T>::GetUnsafePointer; + }; + + template<typename T> requires impl::NonConstPointer<T> + struct KUserPointer<T> : public impl::KUserPointerImpl<T> { + public: + static constexpr bool IsInput = false; + public: + using impl::KUserPointerImpl<T>::CopyFrom; + using impl::KUserPointerImpl<T>::CopyArrayElementFrom; + using impl::KUserPointerImpl<T>::CopyArrayFrom; + using impl::KUserPointerImpl<T>::IsNull; + + using impl::KUserPointerImpl<T>::GetUnsafePointer; + }; + + template<> + struct KUserPointer<const char *> : public impl::KUserPointerImpl<const char *> { + public: + static constexpr bool IsInput = true; + public: + using impl::KUserPointerImpl<const char *>::CopyStringTo; + using impl::KUserPointerImpl<const char *>::CopyArrayElementTo; + using impl::KUserPointerImpl<const char *>::IsNull; + + using impl::KUserPointerImpl<const char *>::GetUnsafePointer; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/svc/kern_svc_prototypes.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/svc/kern_svc_prototypes.hpp new file mode 100644 index 00000000..dca425f4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/svc/kern_svc_prototypes.hpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <mesosphere/svc/kern_svc_k_user_pointer.hpp> +#include <mesosphere/svc/kern_svc_results.hpp> + +namespace ams::kern::svc { + + static constexpr size_t NumSupervisorCalls = AMS_KERN_NUM_SUPERVISOR_CALLS; + + #define AMS_KERN_SVC_DECLARE_ENUM_ID(ID, RETURN_TYPE, NAME, ...) \ + SvcId_##NAME = ID, + + enum SvcId { + AMS_SVC_FOREACH_KERN_DEFINITION(AMS_KERN_SVC_DECLARE_ENUM_ID, __invalid) + SvcId_Count = NumSupervisorCalls, + }; + + #undef AMS_KERN_SVC_DECLARE_ENUM_ID + + #define AMS_KERN_SVC_DECLARE_PROTOTYPE_64(ID, RETURN_TYPE, NAME, ...) \ + NOINLINE RETURN_TYPE NAME##64(__VA_ARGS__); + #define AMS_KERN_SVC_DECLARE_PROTOTYPE_64_FROM_32(ID, RETURN_TYPE, NAME, ...) \ + NOINLINE RETURN_TYPE NAME##64From32(__VA_ARGS__); + + AMS_SVC_FOREACH_KERN_DEFINITION(AMS_KERN_SVC_DECLARE_PROTOTYPE_64, lp64) + AMS_SVC_FOREACH_KERN_DEFINITION(AMS_KERN_SVC_DECLARE_PROTOTYPE_64_FROM_32, ilp32) + + /* TODO: Support _32 ABI */ + + #undef AMS_KERN_SVC_DECLARE_PROTOTYPE_64 + #undef AMS_KERN_SVC_DECLARE_PROTOTYPE_64_FROM_32 + + struct SvcAccessFlagSetTag{}; + + using SvcAccessFlagSet = util::BitFlagSet<NumSupervisorCalls, SvcAccessFlagSetTag>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/svc/kern_svc_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/svc/kern_svc_results.hpp new file mode 100644 index 00000000..b8dfee4e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/svc/kern_svc_results.hpp @@ -0,0 +1,78 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::kern::svc { + + /* 7 */ using ::ams::svc::ResultOutOfSessions; + + /* 14 */ using ::ams::svc::ResultInvalidArgument; + + /* 33 */ using ::ams::svc::ResultNotImplemented; + + /* 54 */ using ::ams::svc::ResultStopProcessingException; + + /* 57 */ using ::ams::svc::ResultNoSynchronizationObject; + + /* 59 */ using ::ams::svc::ResultTerminationRequested; + + /* 70 */ using ::ams::svc::ResultNoEvent; + + /* 101 */ using ::ams::svc::ResultInvalidSize; + /* 102 */ using ::ams::svc::ResultInvalidAddress; + /* 103 */ using ::ams::svc::ResultOutOfResource; + /* 104 */ using ::ams::svc::ResultOutOfMemory; + /* 105 */ using ::ams::svc::ResultOutOfHandles; + /* 106 */ using ::ams::svc::ResultInvalidCurrentMemory; + + /* 108 */ using ::ams::svc::ResultInvalidNewMemoryPermission; + + /* 110 */ using ::ams::svc::ResultInvalidMemoryRegion; + + /* 112 */ using ::ams::svc::ResultInvalidPriority; + /* 113 */ using ::ams::svc::ResultInvalidCoreId; + /* 114 */ using ::ams::svc::ResultInvalidHandle; + /* 115 */ using ::ams::svc::ResultInvalidPointer; + /* 116 */ using ::ams::svc::ResultInvalidCombination; + /* 117 */ using ::ams::svc::ResultTimedOut; + /* 118 */ using ::ams::svc::ResultCancelled; + /* 119 */ using ::ams::svc::ResultOutOfRange; + /* 120 */ using ::ams::svc::ResultInvalidEnumValue; + /* 121 */ using ::ams::svc::ResultNotFound; + /* 122 */ using ::ams::svc::ResultBusy; + /* 123 */ using ::ams::svc::ResultSessionClosed; + /* 124 */ using ::ams::svc::ResultNotHandled; + /* 125 */ using ::ams::svc::ResultInvalidState; + /* 126 */ using ::ams::svc::ResultReservedUsed; + /* 127 */ using ::ams::svc::ResultNotSupported; + /* 128 */ using ::ams::svc::ResultDebug; + /* 129 */ using ::ams::svc::ResultNoThread; + /* 130 */ using ::ams::svc::ResultUnknownThread; + /* 131 */ using ::ams::svc::ResultPortClosed; + /* 132 */ using ::ams::svc::ResultLimitReached; + /* 133 */ using ::ams::svc::ResultInvalidMemoryPool; + + /* 258 */ using ::ams::svc::ResultReceiveListBroken; + /* 259 */ using ::ams::svc::ResultOutOfAddressSpace; + /* 260 */ using ::ams::svc::ResultMessageTooLarge; + + /* 517 */ using ::ams::svc::ResultInvalidProcessId; + /* 518 */ using ::ams::svc::ResultInvalidThreadId; + /* 519 */ using ::ams::svc::ResultInvalidId; + /* 520 */ using ::ams::svc::ResultProcessTerminated; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/svc/kern_svc_tables.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/svc/kern_svc_tables.hpp new file mode 100644 index 00000000..d807b120 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/include/mesosphere/svc/kern_svc_tables.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <mesosphere/svc/kern_svc_prototypes.hpp> + +namespace ams::kern::svc { + + using SvcTableEntry = void (*)(); + + /* TODO: 32-bit ABI */ + + extern const std::array<SvcTableEntry, NumSupervisorCalls> SvcTable64From32; + extern const std::array<SvcTableEntry, NumSupervisorCalls> SvcTable64; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/libmesosphere.mk b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/libmesosphere.mk new file mode 100644 index 00000000..2ab310a2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/libmesosphere.mk @@ -0,0 +1,123 @@ +#--------------------------------------------------------------------------------- +# pull in common atmosphere configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(CURRENT_DIRECTORY)/../config/common.mk + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +PRECOMPILED_HEADERS := include/mesosphere.hpp +#PRECOMPILED_HEADERS := + +ifeq ($(ATMOSPHERE_BUILD_FOR_DEBUGGING),1) +ATMOSPHERE_OPTIMIZATION_FLAG := -Os +else +ATMOSPHERE_OPTIMIZATION_FLAG := -O3 +endif + +DEFINES := $(ATMOSPHERE_DEFINES) -DATMOSPHERE_IS_MESOSPHERE +SETTINGS := $(ATMOSPHERE_SETTINGS) $(ATMOSPHERE_OPTIMIZATION_FLAG) -mgeneral-regs-only -ffixed-x18 -Wextra -Werror -fno-non-call-exceptions +CFLAGS := $(ATMOSPHERE_CFLAGS) $(SETTINGS) $(DEFINES) $(INCLUDE) +CXXFLAGS := $(CFLAGS) $(ATMOSPHERE_CXXFLAGS) -fno-use-cxa-atexit +ASFLAGS := $(ATMOSPHERE_ASFLAGS) $(SETTINGS) $(DEFINES) $(INCLUDE) + +SOURCES += $(foreach v,$(call ALL_SOURCE_DIRS,../libvapours/source),$(if $(findstring ../libvapours/source/sdmmc,$v),,$v)) + +LIBS := + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(ATMOSPHERE_LIBRARIES_DIR)/libvapours + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +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) + +#--------------------------------------------------------------------------------- +# 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 GCH_DIRS := $(PRECOMPILED_HEADERS:.hpp=.hpp.gch) +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. + +#--------------------------------------------------------------------------------- + +.PHONY: clean all + +all: $(ATMOSPHERE_LIBRARY_DIR) $(ATMOSPHERE_BUILD_DIR) $(SOURCES) $(INCLUDES) $(GCH_DIRS) $(CPPFILES) $(CFILES) $(SFILES) + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_LIBRARY_DIR)/$(TARGET).a \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +#--------------------------------------------------------------------------------- +clean: + @echo clean $(ATMOSPHERE_BUILD_NAME) ... + @rm -fr $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_OUT_DIR) + @rm -fr $(foreach hdr,$(GCH_DIRS),$(hdr)/$(ATMOSPHERE_GCH_IDENTIFIER)) + @for i in $(GCH_DIRS); do [ -d $$i ] && rmdir $$i 2>/dev/null || true; done + +$(ATMOSPHERE_LIBRARY_DIR) $(ATMOSPHERE_BUILD_DIR) $(GCH_DIRS): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +else + +GCH_FILES := $(foreach hdr,$(PRECOMPILED_HEADERS:.hpp=.hpp.gch),$(CURRENT_DIRECTORY)/$(hdr)/$(ATMOSPHERE_GCH_IDENTIFIER)) +DEPENDS := $(OFILES:.o=.d) $(foreach hdr,$(GCH_FILES),$(notdir $(patsubst %.hpp.gch/,%.d,$(dir $(hdr))))) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT) : $(OFILES) + +$(filter-out kern_svc_tables.o, $(OFILES)) : $(GCH_FILES) + +$(OFILES_SRC) : $(HFILES_BIN) + +kern_libc_generic.o: CFLAGS += -fno-builtin + +#--------------------------------------------------------------------------------- +%_bin.h %.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm/kern_generic_interrupt_controller.inc b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm/kern_generic_interrupt_controller.inc new file mode 100644 index 00000000..e7f2eb4f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm/kern_generic_interrupt_controller.inc @@ -0,0 +1,210 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::arch::arm { + + void KInterruptController::SetupInterruptLines(s32 core_id) const { + const size_t ITLines = (core_id == 0) ? 32 * ((m_gicd->typer & 0x1F) + 1) : NumLocalInterrupts; + + for (size_t i = 0; i < ITLines / 32; i++) { + m_gicd->icenabler[i] = 0xFFFFFFFF; + m_gicd->icpendr[i] = 0xFFFFFFFF; + m_gicd->icactiver[i] = 0xFFFFFFFF; + m_gicd->igroupr[i] = 0; + } + + for (size_t i = 0; i < ITLines; i++) { + m_gicd->ipriorityr.bytes[i] = 0xFF; + m_gicd->itargetsr.bytes[i] = 0x00; + } + + for (size_t i = 0; i < ITLines / 16; i++) { + m_gicd->icfgr[i] = 0x00000000; + } + } + + void KInterruptController::Initialize(s32 core_id) { + /* Setup pointers to ARM mmio. */ + m_gicd = GetPointer<volatile GicDistributor >(KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_InterruptDistributor)); + m_gicc = GetPointer<volatile GicCpuInterface>(KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_InterruptCpuInterface)); + + /* Clear CTLRs. */ + m_gicc->ctlr = 0; + if (core_id == 0) { + m_gicd->ctlr = 0; + } + + m_gicc->pmr = 0; + m_gicc->bpr = 7; + + /* Setup all interrupt lines. */ + SetupInterruptLines(core_id); + + /* Set CTLRs. */ + if (core_id == 0) { + m_gicd->ctlr = 1; + } + m_gicc->ctlr = 1; + + /* Set the mask for this core. */ + SetGicMask(core_id); + + /* Set the priority level. */ + SetPriorityLevel(PriorityLevel_Low); + } + + void KInterruptController::Finalize(s32 core_id) { + /* Clear CTLRs. */ + if (core_id == 0) { + m_gicd->ctlr = 0; + } + m_gicc->ctlr = 0; + + /* Set the priority level. */ + SetPriorityLevel(PriorityLevel_High); + + /* Setup all interrupt lines. */ + SetupInterruptLines(core_id); + + /* Clear pointers, if needed. */ + if (core_id == 0) { + m_gicd = nullptr; + m_gicc = nullptr; + } + } + + void KInterruptController::SaveCoreLocal(LocalState *state) const { + /* Save isenabler. */ + for (size_t i = 0; i < util::size(state->isenabler); ++i) { + constexpr size_t Offset = 0; + state->isenabler[i] = m_gicd->isenabler[i + Offset]; + m_gicd->isenabler[i + Offset] = 0xFFFFFFFF; + } + + /* Save ipriorityr. */ + for (size_t i = 0; i < util::size(state->ipriorityr); ++i) { + constexpr size_t Offset = 0; + state->ipriorityr[i] = m_gicd->ipriorityr.words[i + Offset]; + m_gicd->ipriorityr.words[i + Offset] = 0xFFFFFFFF; + } + + /* Save itargetsr. */ + for (size_t i = 0; i < util::size(state->itargetsr); ++i) { + constexpr size_t Offset = 0; + state->itargetsr[i] = m_gicd->itargetsr.words[i + Offset]; + } + + /* Save icfgr. */ + for (size_t i = 0; i < util::size(state->icfgr); ++i) { + constexpr size_t Offset = 0; + state->icfgr[i] = m_gicd->icfgr[i + Offset]; + } + + /* Save spendsgir. */ + for (size_t i = 0; i < util::size(state->spendsgir); ++i) { + state->spendsgir[i] = m_gicd->spendsgir[i]; + } + } + + void KInterruptController::SaveGlobal(GlobalState *state) const { + /* Save isenabler. */ + for (size_t i = 0; i < util::size(state->isenabler); ++i) { + constexpr size_t Offset = util::size(LocalState{}.isenabler); + state->isenabler[i] = m_gicd->isenabler[i + Offset]; + m_gicd->isenabler[i + Offset] = 0xFFFFFFFF; + } + + /* Save ipriorityr. */ + for (size_t i = 0; i < util::size(state->ipriorityr); ++i) { + constexpr size_t Offset = util::size(LocalState{}.ipriorityr); + state->ipriorityr[i] = m_gicd->ipriorityr.words[i + Offset]; + m_gicd->ipriorityr.words[i + Offset] = 0xFFFFFFFF; + } + + /* Save itargetsr. */ + for (size_t i = 0; i < util::size(state->itargetsr); ++i) { + constexpr size_t Offset = util::size(LocalState{}.itargetsr); + state->itargetsr[i] = m_gicd->itargetsr.words[i + Offset]; + } + + /* Save icfgr. */ + for (size_t i = 0; i < util::size(state->icfgr); ++i) { + constexpr size_t Offset = util::size(LocalState{}.icfgr); + state->icfgr[i] = m_gicd->icfgr[i + Offset]; + } + } + + void KInterruptController::RestoreCoreLocal(const LocalState *state) const { + /* Restore ipriorityr. */ + for (size_t i = 0; i < util::size(state->ipriorityr); ++i) { + constexpr size_t Offset = 0; + m_gicd->ipriorityr.words[i + Offset] = state->ipriorityr[i]; + } + + /* Restore itargetsr. */ + for (size_t i = 0; i < util::size(state->itargetsr); ++i) { + constexpr size_t Offset = 0; + m_gicd->itargetsr.words[i + Offset] = state->itargetsr[i]; + } + + /* Restore icfgr. */ + for (size_t i = 0; i < util::size(state->icfgr); ++i) { + constexpr size_t Offset = 0; + m_gicd->icfgr[i + Offset] = state->icfgr[i]; + } + + /* Restore isenabler. */ + for (size_t i = 0; i < util::size(state->isenabler); ++i) { + constexpr size_t Offset = 0; + m_gicd->icenabler[i + Offset] = 0xFFFFFFFF; + m_gicd->isenabler[i + Offset] = state->isenabler[i]; + } + + /* Restore spendsgir. */ + for (size_t i = 0; i < util::size(state->spendsgir); ++i) { + m_gicd->spendsgir[i] = state->spendsgir[i]; + } + } + + void KInterruptController::RestoreGlobal(const GlobalState *state) const { + /* Restore ipriorityr. */ + for (size_t i = 0; i < util::size(state->ipriorityr); ++i) { + constexpr size_t Offset = util::size(LocalState{}.ipriorityr); + m_gicd->ipriorityr.words[i + Offset] = state->ipriorityr[i]; + } + + /* Restore itargetsr. */ + for (size_t i = 0; i < util::size(state->itargetsr); ++i) { + constexpr size_t Offset = util::size(LocalState{}.itargetsr); + m_gicd->itargetsr.words[i + Offset] = state->itargetsr[i]; + } + + /* Restore icfgr. */ + for (size_t i = 0; i < util::size(state->icfgr); ++i) { + constexpr size_t Offset = util::size(LocalState{}.icfgr); + m_gicd->icfgr[i + Offset] = state->icfgr[i]; + } + + /* Restore isenabler. */ + for (size_t i = 0; i < util::size(state->isenabler); ++i) { + constexpr size_t Offset = util::size(LocalState{}.isenabler); + m_gicd->icenabler[i + Offset] = 0xFFFFFFFF; + m_gicd->isenabler[i + Offset] = state->isenabler[i]; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm/kern_k_interrupt_controller.board.generic.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm/kern_k_interrupt_controller.board.generic.cpp new file mode 100644 index 00000000..de70a3f1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm/kern_k_interrupt_controller.board.generic.cpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +/* Include the common implementation. */ +#include "../arm/kern_generic_interrupt_controller.inc" diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_cpu.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_cpu.cpp new file mode 100644 index 00000000..401a4d4d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_cpu.cpp @@ -0,0 +1,528 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::arch::arm64::cpu { + + /* Declare prototype to be implemented in asm. */ + void SynchronizeAllCoresImpl(s32 *sync_var, s32 num_cores); + + + namespace { + + ALWAYS_INLINE void SetEventLocally() { + __asm__ __volatile__("sevl" ::: "memory"); + } + + ALWAYS_INLINE void WaitForEvent() { + __asm__ __volatile__("wfe" ::: "memory"); + } + + class KScopedCoreMigrationDisable { + public: + ALWAYS_INLINE KScopedCoreMigrationDisable() { GetCurrentThread().DisableCoreMigration(); } + + ALWAYS_INLINE ~KScopedCoreMigrationDisable() { GetCurrentThread().EnableCoreMigration(); } + }; + + class KScopedCacheMaintenance { + private: + bool m_active; + public: + ALWAYS_INLINE KScopedCacheMaintenance() { + __asm__ __volatile__("" ::: "memory"); + if (m_active = !GetCurrentThread().IsInCacheMaintenanceOperation(); m_active) { + GetCurrentThread().SetInCacheMaintenanceOperation(); + } + } + + ALWAYS_INLINE ~KScopedCacheMaintenance() { + if (m_active) { + GetCurrentThread().ClearInCacheMaintenanceOperation(); + } + __asm__ __volatile__("" ::: "memory"); + } + }; + + /* Nintendo registers a handler for a SGI on thread termination, but does not handle anything. */ + /* This is sufficient, because post-interrupt scheduling is all they really intend to occur. */ + class KThreadTerminationInterruptHandler : public KInterruptHandler { + public: + constexpr KThreadTerminationInterruptHandler() : KInterruptHandler() { /* ... */ } + + virtual KInterruptTask *OnInterrupt(s32 interrupt_id) override { + MESOSPHERE_UNUSED(interrupt_id); + return nullptr; + } + }; + + class KPerformanceCounterInterruptHandler : public KInterruptHandler { + private: + static constinit inline KLightLock s_lock; + private: + u64 m_counter; + s32 m_which; + bool m_done; + public: + constexpr KPerformanceCounterInterruptHandler() : KInterruptHandler(), m_counter(), m_which(), m_done() { /* ... */ } + + static KLightLock &GetLock() { return s_lock; } + + void Setup(s32 w) { + m_done = false; + m_which = w; + } + + void Wait() { + while (!m_done) { + cpu::Yield(); + } + } + + u64 GetCounter() const { return m_counter; } + + /* Nintendo misuses this per their own API, but it's functional. */ + virtual KInterruptTask *OnInterrupt(s32 interrupt_id) override { + MESOSPHERE_UNUSED(interrupt_id); + + if (m_which < 0) { + m_counter = cpu::GetCycleCounter(); + } else { + m_counter = cpu::GetPerformanceCounter(m_which); + } + DataMemoryBarrierInnerShareable(); + m_done = true; + return nullptr; + } + }; + + class KCoreBarrierInterruptHandler : public KInterruptHandler { + private: + util::Atomic<u64> m_target_cores; + KLightLock m_lock; + public: + constexpr KCoreBarrierInterruptHandler() : KInterruptHandler(), m_target_cores(0), m_lock() { /* ... */ } + + virtual KInterruptTask *OnInterrupt(s32 interrupt_id) override { + MESOSPHERE_UNUSED(interrupt_id); + m_target_cores &= ~(1ul << GetCurrentCoreId()); + return nullptr; + } + + void SynchronizeCores(u64 core_mask) { + /* Acquire exclusive access to ourselves. */ + KScopedLightLock lk(m_lock); + + /* If necessary, force synchronization with other cores. */ + if (const u64 other_cores_mask = core_mask & ~(1ul << GetCurrentCoreId()); other_cores_mask != 0) { + /* Send an interrupt to the other cores. */ + m_target_cores = other_cores_mask; + cpu::DataSynchronizationBarrierInnerShareable(); + Kernel::GetInterruptManager().SendInterProcessorInterrupt(KInterruptName_CoreBarrier, other_cores_mask); + + /* Wait for all cores to acknowledge. */ + { + u64 v; + __asm__ __volatile__("ldaxr %[v], %[p]\n" + "cbz %[v], 1f\n" + "0:\n" + "wfe\n" + "ldaxr %[v], %[p]\n" + "cbnz %[v], 0b\n" + "1:\n" + : [v]"=&r"(v) + : [p]"Q"(*reinterpret_cast<u64 *>(std::addressof(m_target_cores))) + : "memory"); + } + } + } + }; + + class KCacheHelperInterruptHandler : public KInterruptHandler { + private: + static constexpr s32 ThreadPriority = 8; + public: + enum class Operation { + Idle, + InstructionMemoryBarrier, + StoreDataCache, + FlushDataCache, + }; + private: + KLightLock m_lock; + KLightLock m_cv_lock; + KLightConditionVariable m_cv; + util::Atomic<u64> m_target_cores; + volatile Operation m_operation; + private: + static void ThreadFunction(uintptr_t _this) { + reinterpret_cast<KCacheHelperInterruptHandler *>(_this)->ThreadFunctionImpl(); + } + + void ThreadFunctionImpl() { + const u64 core_mask = (1ul << GetCurrentCoreId()); + while (true) { + /* Wait for a request to come in. */ + { + KScopedLightLock lk(m_cv_lock); + while ((m_target_cores.Load() & core_mask) == 0) { + m_cv.Wait(std::addressof(m_cv_lock)); + } + } + + /* Process the request. */ + this->ProcessOperation(); + + /* Broadcast, if there's nothing pending. */ + { + KScopedLightLock lk(m_cv_lock); + + m_target_cores &= ~core_mask; + if (m_target_cores.Load() == 0) { + m_cv.Broadcast(); + } + } + } + } + + void ProcessOperation(); + public: + constexpr KCacheHelperInterruptHandler() : KInterruptHandler(), m_lock(), m_cv_lock(), m_cv(util::ConstantInitialize), m_target_cores(0), m_operation(Operation::Idle) { /* ... */ } + + void Initialize(s32 core_id) { + /* Reserve a thread from the system limit. */ + MESOSPHERE_ABORT_UNLESS(Kernel::GetSystemResourceLimit().Reserve(ams::svc::LimitableResource_ThreadCountMax, 1)); + + /* Create a new thread. */ + KThread *new_thread = KThread::Create(); + MESOSPHERE_ABORT_UNLESS(new_thread != nullptr); + MESOSPHERE_R_ABORT_UNLESS(KThread::InitializeKernelThread(new_thread, ThreadFunction, reinterpret_cast<uintptr_t>(this), ThreadPriority, core_id)); + + /* Register the new thread. */ + KThread::Register(new_thread); + + /* Run the thread. */ + new_thread->Run(); + } + + virtual KInterruptTask *OnInterrupt(s32 interrupt_id) override { + MESOSPHERE_UNUSED(interrupt_id); + this->ProcessOperation(); + m_target_cores &= ~(1ul << GetCurrentCoreId()); + return nullptr; + } + + void RequestOperation(Operation op) { + KScopedLightLock lk(m_lock); + + /* Create core masks for us to use. */ + constexpr u64 AllCoresMask = (1ul << cpu::NumCores) - 1ul; + const u64 other_cores_mask = AllCoresMask & ~(1ul << GetCurrentCoreId()); + + if ((op == Operation::InstructionMemoryBarrier) || (Kernel::GetState() == Kernel::State::Initializing)) { + /* Check that there's no on-going operation. */ + MESOSPHERE_ABORT_UNLESS(m_operation == Operation::Idle); + MESOSPHERE_ABORT_UNLESS(m_target_cores.Load() == 0); + + /* Set operation. */ + m_operation = op; + + /* For certain operations, we want to send an interrupt. */ + m_target_cores = other_cores_mask; + + const u64 target_mask = m_target_cores.Load(); + + DataSynchronizationBarrierInnerShareable(); + Kernel::GetInterruptManager().SendInterProcessorInterrupt(KInterruptName_CacheOperation, target_mask); + + this->ProcessOperation(); + while (m_target_cores.Load() != 0) { + cpu::Yield(); + } + + /* Go idle again. */ + m_operation = Operation::Idle; + } else { + /* Lock condvar so that we can send and wait for acknowledgement of request. */ + KScopedLightLock cv_lk(m_cv_lock); + + /* Check that there's no on-going operation. */ + MESOSPHERE_ABORT_UNLESS(m_operation == Operation::Idle); + MESOSPHERE_ABORT_UNLESS(m_target_cores.Load() == 0); + + /* Set operation. */ + m_operation = op; + + /* Request all cores. */ + m_target_cores = AllCoresMask; + + /* Use the condvar. */ + m_cv.Broadcast(); + while (m_target_cores.Load() != 0) { + m_cv.Wait(std::addressof(m_cv_lock)); + } + + /* Go idle again. */ + m_operation = Operation::Idle; + } + } + }; + + /* Instances of the interrupt handlers. */ + constinit KThreadTerminationInterruptHandler g_thread_termination_handler; + constinit KCacheHelperInterruptHandler g_cache_operation_handler; + constinit KCoreBarrierInterruptHandler g_core_barrier_handler; + + #if defined(MESOSPHERE_ENABLE_PERFORMANCE_COUNTER) + constinit KPerformanceCounterInterruptHandler g_performance_counter_handler[cpu::NumCores]; + #endif + + /* Expose this as a global, for asm to use. */ + constinit s32 g_all_core_sync_count; + + template<typename F> + ALWAYS_INLINE void PerformCacheOperationBySetWayImpl(int level, F f) { + /* Used in multiple locations. */ + const u64 level_sel_value = static_cast<u64>(level << 1); + + /* Get the cache size id register value with interrupts disabled. */ + u64 ccsidr_value; + { + /* Disable interrupts. */ + KScopedInterruptDisable di; + + /* Configure the cache select register for our level. */ + cpu::SetCsselrEl1(level_sel_value); + + /* Ensure our configuration takes before reading the cache size id register. */ + cpu::InstructionMemoryBarrier(); + + /* Get the cache size id register. */ + ccsidr_value = cpu::GetCcsidrEl1(); + } + + /* Ensure that no memory inconsistencies occur between cache management invocations. */ + cpu::DataSynchronizationBarrier(); + + /* Get cache size id info. */ + CacheSizeIdRegisterAccessor ccsidr_el1(ccsidr_value); + const int num_sets = ccsidr_el1.GetNumberOfSets(); + const int num_ways = ccsidr_el1.GetAssociativity(); + const int line_size = ccsidr_el1.GetLineSize(); + + const u64 way_shift = static_cast<u64>(__builtin_clz(num_ways)); + const u64 set_shift = static_cast<u64>(line_size + 4); + + for (int way = 0; way <= num_ways; way++) { + for (int set = 0; set <= num_sets; set++) { + const u64 way_value = static_cast<u64>(way) << way_shift; + const u64 set_value = static_cast<u64>(set) << set_shift; + f(way_value | set_value | level_sel_value); + } + } + } + + ALWAYS_INLINE void FlushDataCacheLineBySetWayImpl(const u64 sw_value) { + __asm__ __volatile__("dc cisw, %[v]" :: [v]"r"(sw_value) : "memory"); + } + + ALWAYS_INLINE void StoreDataCacheLineBySetWayImpl(const u64 sw_value) { + __asm__ __volatile__("dc csw, %[v]" :: [v]"r"(sw_value) : "memory"); + } + + void StoreDataCacheBySetWay(int level) { + PerformCacheOperationBySetWayImpl(level, StoreDataCacheLineBySetWayImpl); + } + + void FlushDataCacheBySetWay(int level) { + PerformCacheOperationBySetWayImpl(level, FlushDataCacheLineBySetWayImpl); + } + + void KCacheHelperInterruptHandler::ProcessOperation() { + switch (m_operation) { + case Operation::Idle: + break; + case Operation::InstructionMemoryBarrier: + InstructionMemoryBarrier(); + break; + case Operation::StoreDataCache: + StoreDataCacheBySetWay(0); + cpu::DataSynchronizationBarrier(); + break; + case Operation::FlushDataCache: + FlushDataCacheBySetWay(0); + cpu::DataSynchronizationBarrier(); + break; + } + } + + ALWAYS_INLINE Result InvalidateDataCacheRange(uintptr_t start, uintptr_t end) { + MESOSPHERE_ASSERT(util::IsAligned(start, DataCacheLineSize)); + MESOSPHERE_ASSERT(util::IsAligned(end, DataCacheLineSize)); + R_UNLESS(UserspaceAccess::InvalidateDataCache(start, end), svc::ResultInvalidCurrentMemory()); + DataSynchronizationBarrier(); + R_SUCCEED(); + } + + ALWAYS_INLINE Result StoreDataCacheRange(uintptr_t start, uintptr_t end) { + MESOSPHERE_ASSERT(util::IsAligned(start, DataCacheLineSize)); + MESOSPHERE_ASSERT(util::IsAligned(end, DataCacheLineSize)); + R_UNLESS(UserspaceAccess::StoreDataCache(start, end), svc::ResultInvalidCurrentMemory()); + DataSynchronizationBarrier(); + R_SUCCEED(); + } + + ALWAYS_INLINE Result FlushDataCacheRange(uintptr_t start, uintptr_t end) { + MESOSPHERE_ASSERT(util::IsAligned(start, DataCacheLineSize)); + MESOSPHERE_ASSERT(util::IsAligned(end, DataCacheLineSize)); + R_UNLESS(UserspaceAccess::FlushDataCache(start, end), svc::ResultInvalidCurrentMemory()); + DataSynchronizationBarrier(); + R_SUCCEED(); + } + + ALWAYS_INLINE void InvalidateEntireInstructionCacheLocalImpl() { + __asm__ __volatile__("ic iallu" ::: "memory"); + } + + ALWAYS_INLINE void InvalidateEntireInstructionCacheGlobalImpl() { + __asm__ __volatile__("ic ialluis" ::: "memory"); + } + + } + + void SynchronizeCores(u64 core_mask) { + /* Request a core barrier interrupt. */ + g_core_barrier_handler.SynchronizeCores(core_mask); + } + + void StoreCacheForInit(void *addr, size_t size) { + /* Store the data cache for the specified range. */ + const uintptr_t start = util::AlignDown(reinterpret_cast<uintptr_t>(addr), DataCacheLineSize); + const uintptr_t end = start + size; + for (uintptr_t cur = start; cur < end; cur += DataCacheLineSize) { + __asm__ __volatile__("dc cvac, %[cur]" :: [cur]"r"(cur) : "memory"); + } + + /* Data synchronization barrier. */ + DataSynchronizationBarrierInnerShareable(); + + /* Invalidate instruction cache. */ + InvalidateEntireInstructionCacheLocalImpl(); + + /* Ensure local instruction consistency. */ + EnsureInstructionConsistency(); + } + + void FlushEntireDataCache() { + KScopedCoreMigrationDisable dm; + + CacheLineIdRegisterAccessor clidr_el1; + const int levels_of_coherency = clidr_el1.GetLevelsOfCoherency(); + + /* Store cache from L2 up to the level of coherence (if there's an L3 cache or greater). */ + for (int level = 2; level < levels_of_coherency; ++level) { + StoreDataCacheBySetWay(level - 1); + } + + /* Flush cache from the level of coherence down to L2. */ + for (int level = levels_of_coherency; level > 1; --level) { + FlushDataCacheBySetWay(level - 1); + } + + /* Data synchronization barrier for full system. */ + DataSynchronizationBarrier(); + } + + Result InvalidateDataCache(void *addr, size_t size) { + /* Mark ourselves as in a cache maintenance operation, and prevent re-ordering. */ + KScopedCacheMaintenance cm; + + const uintptr_t start = reinterpret_cast<uintptr_t>(addr); + const uintptr_t end = start + size; + uintptr_t aligned_start = util::AlignDown(start, DataCacheLineSize); + uintptr_t aligned_end = util::AlignUp(end, DataCacheLineSize); + + if (aligned_start != start) { + R_TRY(FlushDataCacheRange(aligned_start, aligned_start + DataCacheLineSize)); + aligned_start += DataCacheLineSize; + } + + if (aligned_start < aligned_end && (aligned_end != end)) { + aligned_end -= DataCacheLineSize; + R_TRY(FlushDataCacheRange(aligned_end, aligned_end + DataCacheLineSize)); + } + + if (aligned_start < aligned_end) { + R_TRY(InvalidateDataCacheRange(aligned_start, aligned_end)); + } + + R_SUCCEED(); + } + + Result StoreDataCache(const void *addr, size_t size) { + /* Mark ourselves as in a cache maintenance operation, and prevent re-ordering. */ + KScopedCacheMaintenance cm; + + const uintptr_t start = util::AlignDown(reinterpret_cast<uintptr_t>(addr), DataCacheLineSize); + const uintptr_t end = util::AlignUp( reinterpret_cast<uintptr_t>(addr) + size, DataCacheLineSize); + + R_RETURN(StoreDataCacheRange(start, end)); + } + + Result FlushDataCache(const void *addr, size_t size) { + /* Mark ourselves as in a cache maintenance operation, and prevent re-ordering. */ + KScopedCacheMaintenance cm; + + const uintptr_t start = util::AlignDown(reinterpret_cast<uintptr_t>(addr), DataCacheLineSize); + const uintptr_t end = util::AlignUp( reinterpret_cast<uintptr_t>(addr) + size, DataCacheLineSize); + + R_RETURN(FlushDataCacheRange(start, end)); + } + + void InvalidateEntireInstructionCache() { + KScopedCoreMigrationDisable dm; + + /* Invalidate the instruction cache on all cores. */ + InvalidateEntireInstructionCacheGlobalImpl(); + EnsureInstructionConsistency(); + + /* Request the interrupt helper to perform an instruction memory barrier. */ + g_cache_operation_handler.RequestOperation(KCacheHelperInterruptHandler::Operation::InstructionMemoryBarrier); + } + + void InitializeInterruptThreads(s32 core_id) { + /* Initialize the cache operation handler. */ + g_cache_operation_handler.Initialize(core_id); + + /* Bind all handlers to the relevant interrupts. */ + Kernel::GetInterruptManager().BindHandler(std::addressof(g_cache_operation_handler), KInterruptName_CacheOperation, core_id, KInterruptController::PriorityLevel_High, false, false); + Kernel::GetInterruptManager().BindHandler(std::addressof(g_thread_termination_handler), KInterruptName_ThreadTerminate, core_id, KInterruptController::PriorityLevel_Scheduler, false, false); + Kernel::GetInterruptManager().BindHandler(std::addressof(g_core_barrier_handler), KInterruptName_CoreBarrier, core_id, KInterruptController::PriorityLevel_Scheduler, false, false); + + /* If we should, enable user access to the performance counter registers. */ + if (KTargetSystem::IsUserPmuAccessEnabled()) { SetPmUserEnrEl0(1ul); } + + /* If we should, enable the kernel performance counter interrupt handler. */ + #if defined(MESOSPHERE_ENABLE_PERFORMANCE_COUNTER) + Kernel::GetInterruptManager().BindHandler(std::addressof(g_performance_counter_handler[core_id]), KInterruptName_PerformanceCounter, core_id, KInterruptController::PriorityLevel_Timer, false, false); + #endif + } + + void SynchronizeAllCores() { + SynchronizeAllCoresImpl(&g_all_core_sync_count, static_cast<s32>(cpu::NumCores)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_cpu_asm.s b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_cpu_asm.s new file mode 100644 index 00000000..3c6ec40c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_cpu_asm.s @@ -0,0 +1,198 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* ams::kern::arch::arm64::cpu::SynchronizeAllCoresImpl(int *sync_var, int num_cores) */ +.section .text._ZN3ams4kern4arch5arm643cpu23SynchronizeAllCoresImplEPii, "ax", %progbits +.global _ZN3ams4kern4arch5arm643cpu23SynchronizeAllCoresImplEPii +.type _ZN3ams4kern4arch5arm643cpu23SynchronizeAllCoresImplEPii, %function +_ZN3ams4kern4arch5arm643cpu23SynchronizeAllCoresImplEPii: + /* Loop until the sync var is less than num cores. */ + sevl +1: + wfe + ldaxr w2, [x0] + cmp w2, w1 + b.gt 1b + + /* Increment the sync var. */ +2: + ldaxr w2, [x0] + add w3, w2, #1 + stlxr w4, w3, [x0] + cbnz w4, 2b + + /* Loop until the sync var matches our ticket. */ + add w3, w2, w1 + sevl +3: + wfe + ldaxr w2, [x0] + cmp w2, w3 + b.ne 3b + + /* Check if the ticket is the last. */ + sub w2, w1, #1 + add w2, w2, w1 + cmp w3, w2 + b.eq 5f + + /* Our ticket is not the last one. Increment. */ +4: + ldaxr w2, [x0] + add w3, w2, #1 + stlxr w4, w3, [x0] + cbnz w4, 4b + ret + + /* Our ticket is the last one. */ +5: + stlr wzr, [x0] + ret + +/* ams::kern::arch::arm64::cpu::ClearPageToZeroImpl(void *) */ +.section .text._ZN3ams4kern4arch5arm643cpu19ClearPageToZeroImplEPv, "ax", %progbits +.global _ZN3ams4kern4arch5arm643cpu19ClearPageToZeroImplEPv +.type _ZN3ams4kern4arch5arm643cpu19ClearPageToZeroImplEPv, %function +_ZN3ams4kern4arch5arm643cpu19ClearPageToZeroImplEPv: + /* Efficiently clear the page using dc zva. */ + dc zva, x0 + add x8, x0, #0x040 + dc zva, x8 + add x8, x0, #0x080 + dc zva, x8 + add x8, x0, #0x0c0 + dc zva, x8 + add x8, x0, #0x100 + dc zva, x8 + add x8, x0, #0x140 + dc zva, x8 + add x8, x0, #0x180 + dc zva, x8 + add x8, x0, #0x1c0 + dc zva, x8 + add x8, x0, #0x200 + dc zva, x8 + add x8, x0, #0x240 + dc zva, x8 + add x8, x0, #0x280 + dc zva, x8 + add x8, x0, #0x2c0 + dc zva, x8 + add x8, x0, #0x300 + dc zva, x8 + add x8, x0, #0x340 + dc zva, x8 + add x8, x0, #0x380 + dc zva, x8 + add x8, x0, #0x3c0 + dc zva, x8 + add x8, x0, #0x400 + dc zva, x8 + add x8, x0, #0x440 + dc zva, x8 + add x8, x0, #0x480 + dc zva, x8 + add x8, x0, #0x4c0 + dc zva, x8 + add x8, x0, #0x500 + dc zva, x8 + add x8, x0, #0x540 + dc zva, x8 + add x8, x0, #0x580 + dc zva, x8 + add x8, x0, #0x5c0 + dc zva, x8 + add x8, x0, #0x600 + dc zva, x8 + add x8, x0, #0x640 + dc zva, x8 + add x8, x0, #0x680 + dc zva, x8 + add x8, x0, #0x6c0 + dc zva, x8 + add x8, x0, #0x700 + dc zva, x8 + add x8, x0, #0x740 + dc zva, x8 + add x8, x0, #0x780 + dc zva, x8 + add x8, x0, #0x7c0 + dc zva, x8 + add x8, x0, #0x800 + dc zva, x8 + add x8, x0, #0x840 + dc zva, x8 + add x8, x0, #0x880 + dc zva, x8 + add x8, x0, #0x8c0 + dc zva, x8 + add x8, x0, #0x900 + dc zva, x8 + add x8, x0, #0x940 + dc zva, x8 + add x8, x0, #0x980 + dc zva, x8 + add x8, x0, #0x9c0 + dc zva, x8 + add x8, x0, #0xa00 + dc zva, x8 + add x8, x0, #0xa40 + dc zva, x8 + add x8, x0, #0xa80 + dc zva, x8 + add x8, x0, #0xac0 + dc zva, x8 + add x8, x0, #0xb00 + dc zva, x8 + add x8, x0, #0xb40 + dc zva, x8 + add x8, x0, #0xb80 + dc zva, x8 + add x8, x0, #0xbc0 + dc zva, x8 + add x8, x0, #0xc00 + dc zva, x8 + add x8, x0, #0xc40 + dc zva, x8 + add x8, x0, #0xc80 + dc zva, x8 + add x8, x0, #0xcc0 + dc zva, x8 + add x8, x0, #0xd00 + dc zva, x8 + add x8, x0, #0xd40 + dc zva, x8 + add x8, x0, #0xd80 + dc zva, x8 + add x8, x0, #0xdc0 + dc zva, x8 + add x8, x0, #0xe00 + dc zva, x8 + add x8, x0, #0xe40 + dc zva, x8 + add x8, x0, #0xe80 + dc zva, x8 + add x8, x0, #0xec0 + dc zva, x8 + add x8, x0, #0xf00 + dc zva, x8 + add x8, x0, #0xf40 + dc zva, x8 + add x8, x0, #0xf80 + dc zva, x8 + add x8, x0, #0xfc0 + dc zva, x8 + ret diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_exception_handlers.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_exception_handlers.cpp new file mode 100644 index 00000000..6c21b455 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_exception_handlers.cpp @@ -0,0 +1,656 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + void RestoreContext(uintptr_t sp); + +} + +namespace ams::kern::arch::arm64 { + + namespace { + + enum EsrEc : u32 { + EsrEc_Unknown = 0b000000, + EsrEc_WaitForInterruptOrEvent = 0b000001, + EsrEc_Cp15McrMrc = 0b000011, + EsrEc_Cp15McrrMrrc = 0b000100, + EsrEc_Cp14McrMrc = 0b000101, + EsrEc_FpAccess = 0b000111, + EsrEc_Cp14Mrrc = 0b001100, + EsrEc_BranchTarget = 0b001101, + EsrEc_IllegalExecution = 0b001110, + EsrEc_Svc32 = 0b010001, + EsrEc_Svc64 = 0b010101, + EsrEc_SystemInstruction64 = 0b011000, + EsrEc_SveZen = 0b011001, + EsrEc_PointerAuthInstruction = 0b011100, + EsrEc_InstructionAbortEl0 = 0b100000, + EsrEc_InstructionAbortEl1 = 0b100001, + EsrEc_PcAlignmentFault = 0b100010, + EsrEc_DataAbortEl0 = 0b100100, + EsrEc_DataAbortEl1 = 0b100101, + EsrEc_SpAlignmentFault = 0b100110, + EsrEc_FpException32 = 0b101000, + EsrEc_FpException64 = 0b101100, + EsrEc_SErrorInterrupt = 0b101111, + EsrEc_BreakPointEl0 = 0b110000, + EsrEc_BreakPointEl1 = 0b110001, + EsrEc_SoftwareStepEl0 = 0b110010, + EsrEc_SoftwareStepEl1 = 0b110011, + EsrEc_WatchPointEl0 = 0b110100, + EsrEc_WatchPointEl1 = 0b110101, + EsrEc_BkptInstruction = 0b111000, + EsrEc_BrkInstruction = 0b111100, + }; + + + + u32 GetInstructionDataSupervisorMode(const KExceptionContext *context, u64 esr) { + /* Check for THUMB usermode */ + if ((context->psr & 0x3F) == 0x30) { + u32 insn = *reinterpret_cast<u16 *>(context->pc & ~0x1); + /* Check if the instruction was 32-bit. */ + if ((esr >> 25) & 1) { + insn = (insn << 16) | *reinterpret_cast<u16 *>((context->pc & ~0x1) + sizeof(u16)); + } + return insn; + } else { + /* Not thumb, so just get the instruction. */ + return *reinterpret_cast<u32 *>(context->pc); + } + } + + u32 GetInstructionDataUserMode(const KExceptionContext *context) { + /* Check for THUMB usermode */ + u32 insn = 0; + if ((context->psr & 0x3F) == 0x30) { + u16 insn_high = 0; + if (UserspaceAccess::CopyMemoryFromUser(std::addressof(insn_high), reinterpret_cast<u16 *>(context->pc & ~0x1), sizeof(insn_high))) { + insn = insn_high; + + /* Check if the instruction was a THUMB mode branch prefix. */ + if (((insn >> 11) & 0b11110) == 0b11110) { + u16 insn_low = 0; + if (UserspaceAccess::CopyMemoryFromUser(std::addressof(insn_low), reinterpret_cast<u16 *>((context->pc & ~0x1) + sizeof(u16)), sizeof(insn_low))) { + insn = (static_cast<u32>(insn_high) << 16) | (static_cast<u32>(insn_low) << 0); + } else { + insn = 0; + } + } + } else { + insn = 0; + } + } else { + u32 insn_value = 0; + if (UserspaceAccess::CopyMemoryFromUser(std::addressof(insn_value), reinterpret_cast<u32 *>(context->pc), sizeof(insn_value))) { + insn = insn_value; + } else if (KTargetSystem::IsDebugMode() && (context->pc & 3) == 0 && UserspaceAccess::CopyMemoryFromUserSize32BitWithSupervisorAccess(std::addressof(insn_value), reinterpret_cast<u32 *>(context->pc))) { + insn = insn_value; + } else { + insn = 0; + } + } + return insn; + } + + void HandleUserException(KExceptionContext *context, u64 raw_esr, u64 raw_far, u64 afsr0, u64 afsr1, u32 data) { + /* Pre-process exception registers as needed. */ + u64 esr = raw_esr; + u64 far = raw_far; + const u64 ec = (esr >> 26) & 0x3F; + if (ec == EsrEc_InstructionAbortEl0 || ec == EsrEc_DataAbortEl0) { + /* Adjust registers if a synchronous external abort has occurred with far not valid. */ + /* Mask 0x03F = Low 6 bits IFSC == 0x10: "Synchronous External abort, */ + /* not on translation table walk or hardware update of translation table. */ + /* Mask 0x400 = FnV = "FAR Not Valid" */ + /* TODO: How would we perform this check using named register accesses? */ + if ((esr & 0x43F) == 0x410) { + /* Clear the faulting register on memory tagging exception. */ + far = 0; + } else { + /* If the faulting address is a kernel address, set ISFC = 4. */ + if (far >= ams::svc::AddressMemoryRegion39Size) { + esr = (esr & 0xFFFFFFC0) | 4; + } + } + } + + KProcess &cur_process = GetCurrentProcess(); + bool should_process_user_exception = KTargetSystem::IsUserExceptionHandlersEnabled(); + + /* In the event that we return from this exception, we want SPSR.SS set so that we advance an instruction if single-stepping. */ + #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) + context->psr |= (1ul << 21); + #endif + + /* If we should process the user exception (and it's not a breakpoint), try to enter. */ + const bool is_software_break = (ec == EsrEc_Unknown || ec == EsrEc_IllegalExecution || ec == EsrEc_BkptInstruction || ec == EsrEc_BrkInstruction); + const bool is_breakpoint = (ec == EsrEc_BreakPointEl0 || ec == EsrEc_SoftwareStepEl0 || ec == EsrEc_WatchPointEl0); + if ((should_process_user_exception) && + !(is_software_break && cur_process.IsAttachedToDebugger() && KDebug::IsBreakInstruction(data, context->psr)) && + !(is_breakpoint)) + { + if (cur_process.EnterUserException()) { + /* Fill out the exception info. */ + const bool is_aarch64 = (context->psr & 0x10) == 0; + if (is_aarch64) { + /* 64-bit. */ + ams::svc::aarch64::ExceptionInfo *info = std::addressof(static_cast<ams::svc::aarch64::ProcessLocalRegion *>(cur_process.GetProcessLocalRegionHeapAddress())->exception_info); + + for (size_t i = 0; i < util::size(info->r); ++i) { + info->r[i] = context->x[i]; + } + info->sp = context->sp; + info->lr = context->x[30]; + info->pc = context->pc; + info->pstate = (context->psr & cpu::El0Aarch64PsrMask); + info->afsr0 = afsr0; + info->afsr1 = afsr1; + info->esr = esr; + info->far = far; + } else { + /* 32-bit. */ + ams::svc::aarch32::ExceptionInfo *info = std::addressof(static_cast<ams::svc::aarch32::ProcessLocalRegion *>(cur_process.GetProcessLocalRegionHeapAddress())->exception_info); + + for (size_t i = 0; i < util::size(info->r); ++i) { + info->r[i] = context->x[i]; + } + info->sp = context->x[13]; + info->lr = context->x[14]; + info->pc = context->pc; + info->flags = 1; + + info->status_64.pstate = (context->psr & cpu::El0Aarch32PsrMask); + info->status_64.afsr0 = afsr0; + info->status_64.afsr1 = afsr1; + info->status_64.esr = esr; + info->status_64.far = far; + } + + /* Save the debug parameters to the current thread. */ + GetCurrentThread().SaveDebugParams(raw_far, raw_esr, data); + + /* Get the exception type. */ + u32 type; + switch (ec) { + case EsrEc_Unknown: + case EsrEc_IllegalExecution: + case EsrEc_Cp15McrMrc: + case EsrEc_Cp15McrrMrrc: + case EsrEc_Cp14McrMrc: + case EsrEc_Cp14Mrrc: + case EsrEc_SystemInstruction64: + case EsrEc_BkptInstruction: + case EsrEc_BrkInstruction: + type = ams::svc::ExceptionType_InstructionAbort; + break; + case EsrEc_PcAlignmentFault: + type = ams::svc::ExceptionType_UnalignedInstruction; + break; + case EsrEc_SpAlignmentFault: + type = ams::svc::ExceptionType_UnalignedData; + break; + case EsrEc_Svc32: + case EsrEc_Svc64: + type = ams::svc::ExceptionType_InvalidSystemCall; + break; + case EsrEc_SErrorInterrupt: + type = ams::svc::ExceptionType_MemorySystemError; + break; + case EsrEc_InstructionAbortEl0: + type = ams::svc::ExceptionType_InstructionAbort; + break; + case EsrEc_DataAbortEl0: + /* If esr.IFSC is "Alignment Fault", return UnalignedData instead of DataAbort. */ + if ((esr & 0x3F) == 0b100001) { + type = ams::svc::ExceptionType_UnalignedData; + } else { + type = ams::svc::ExceptionType_DataAbort; + } + break; + default: + type = ams::svc::ExceptionType_DataAbort; + break; + } + + /* We want to enter at the process entrypoint, with x0 = type. */ + context->pc = GetInteger(cur_process.GetEntryPoint()); + context->x[0] = type; + if (is_aarch64) { + context->x[1] = GetInteger(cur_process.GetProcessLocalRegionAddress() + AMS_OFFSETOF(ams::svc::aarch64::ProcessLocalRegion, exception_info)); + + const auto *plr = GetPointer<ams::svc::aarch64::ProcessLocalRegion>(cur_process.GetProcessLocalRegionAddress()); + context->sp = util::AlignDown(reinterpret_cast<uintptr_t>(plr->data) + sizeof(plr->data), 0x10); + context->psr = 0; + } else { + context->x[1] = GetInteger(cur_process.GetProcessLocalRegionAddress() + AMS_OFFSETOF(ams::svc::aarch32::ProcessLocalRegion, exception_info)); + + const auto *plr = GetPointer<ams::svc::aarch32::ProcessLocalRegion>(cur_process.GetProcessLocalRegionAddress()); + context->x[13] = util::AlignDown(reinterpret_cast<uintptr_t>(plr->data) + sizeof(plr->data), 0x08); + context->psr = 0x10; + } + + /* Process that we're entering a usermode exception on the current thread. */ + GetCurrentThread().OnEnterUsermodeException(); + return; + } + } + + /* If we should, clear the thread's state as single-step. */ + #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) + if (AMS_UNLIKELY(GetCurrentThread().IsHardwareSingleStep())) { + GetCurrentThread().ClearHardwareSingleStep(); + cpu::MonitorDebugSystemControlRegisterAccessor().SetSoftwareStep(false).Store(); + cpu::InstructionMemoryBarrier(); + } + #endif + + { + /* Collect additional information based on the ec. */ + uintptr_t params[3] = {}; + switch (ec) { + case EsrEc_Unknown: + case EsrEc_IllegalExecution: + case EsrEc_BkptInstruction: + case EsrEc_BrkInstruction: + { + params[0] = ams::svc::DebugException_UndefinedInstruction; + params[1] = far; + params[2] = data; + } + break; + case EsrEc_PcAlignmentFault: + case EsrEc_SpAlignmentFault: + { + params[0] = ams::svc::DebugException_AlignmentFault; + params[1] = far; + } + break; + case EsrEc_Svc32: + case EsrEc_Svc64: + { + params[0] = ams::svc::DebugException_UndefinedSystemCall; + params[1] = far; + params[2] = (esr & 0xFF); + } + break; + case EsrEc_BreakPointEl0: + case EsrEc_SoftwareStepEl0: + { + params[0] = ams::svc::DebugException_BreakPoint; + params[1] = far; + params[2] = ams::svc::BreakPointType_HardwareInstruction; + } + break; + case EsrEc_WatchPointEl0: + { + params[0] = ams::svc::DebugException_BreakPoint; + params[1] = far; + params[2] = ams::svc::BreakPointType_HardwareData; + } + break; + case EsrEc_SErrorInterrupt: + { + params[0] = ams::svc::DebugException_MemorySystemError; + params[1] = far; + } + break; + case EsrEc_InstructionAbortEl0: + { + params[0] = ams::svc::DebugException_InstructionAbort; + params[1] = far; + } + break; + case EsrEc_DataAbortEl0: + default: + { + params[0] = ams::svc::DebugException_DataAbort; + params[1] = far; + } + break; + } + + /* Process the debug event. */ + Result result = KDebug::OnDebugEvent(ams::svc::DebugEvent_Exception, params, util::size(params)); + + /* If we should stop processing the exception, do so. */ + if (svc::ResultStopProcessingException::Includes(result)) { + return; + } + + #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) + { + if (ec != EsrEc_SoftwareStepEl0) { + /* If the exception wasn't single-step, print details. */ + MESOSPHERE_EXCEPTION_LOG("Exception occurred. "); + + { + /* Print the current thread's registers. */ + KDebug::PrintRegister(); + + /* Print a backtrace. */ + KDebug::PrintBacktrace(); + } + } else { + /* If the exception was single-step and we have no debug object, we should just return. */ + if (AMS_UNLIKELY(!cur_process.IsAttachedToDebugger())) { + return; + } + } + } + #else + { + /* Print that an exception occurred. */ + MESOSPHERE_EXCEPTION_LOG("Exception occurred. "); + + { + /* Print the current thread's registers. */ + KDebug::PrintRegister(); + + /* Print a backtrace. */ + KDebug::PrintBacktrace(); + } + } + #endif + + /* If the SVC is handled, handle it. */ + if (!svc::ResultNotHandled::Includes(result)) { + /* If we successfully enter jit debug, stop processing the exception. */ + if (cur_process.EnterJitDebug(ams::svc::DebugEvent_Exception, static_cast<ams::svc::DebugException>(params[0]), params[1], params[2])) { + return; + } + } + } + + /* Exit the current process. */ + cur_process.Exit(); + } + + } + + /* NOTE: This function is called from ASM. */ + void FpuContextSwitchHandler() { + KThreadContext::FpuContextSwitchHandler(GetCurrentThreadPointer()); + } + + /* NOTE: This function is called from ASM. */ + void ReturnFromException(Result user_result) { + /* Get the current thread. */ + KThread *cur_thread = GetCurrentThreadPointer(); + + /* Get the current exception context. */ + KExceptionContext *e_ctx = GetExceptionContext(cur_thread); + + /* Get the current process. */ + KProcess &cur_process = GetCurrentProcess(); + + /* Read the exception info that userland put in tls. */ + union { + ams::svc::aarch64::ExceptionInfo info64; + ams::svc::aarch32::ExceptionInfo info32; + } info = {}; + + const bool is_aarch64 = (e_ctx->psr & 0x10) == 0; + if (is_aarch64) { + /* We're 64-bit. */ + info.info64 = static_cast<const ams::svc::aarch64::ProcessLocalRegion *>(cur_process.GetProcessLocalRegionHeapAddress())->exception_info; + } else { + /* We're 32-bit. */ + info.info32 = static_cast<const ams::svc::aarch32::ProcessLocalRegion *>(cur_process.GetProcessLocalRegionHeapAddress())->exception_info; + } + + /* Try to leave the user exception. */ + if (cur_process.LeaveUserException()) { + /* Process that we're leaving a usermode exception on the current thread. */ + GetCurrentThread().OnLeaveUsermodeException(); + + /* Copy the user context to the thread context. */ + if (is_aarch64) { + for (size_t i = 0; i < util::size(info.info64.r); ++i) { + e_ctx->x[i] = info.info64.r[i]; + } + e_ctx->x[30] = info.info64.lr; + e_ctx->sp = info.info64.sp; + e_ctx->pc = info.info64.pc; + e_ctx->psr = (info.info64.pstate & cpu::El0Aarch64PsrMask) | (e_ctx->psr & ~cpu::El0Aarch64PsrMask); + } else { + for (size_t i = 0; i < util::size(info.info32.r); ++i) { + e_ctx->x[i] = info.info32.r[i]; + } + e_ctx->x[14] = info.info32.lr; + e_ctx->x[13] = info.info32.sp; + e_ctx->pc = info.info32.pc; + e_ctx->psr = (info.info32.status_64.pstate & cpu::El0Aarch32PsrMask) | (e_ctx->psr & ~cpu::El0Aarch32PsrMask); + } + + /* Note that PC was adjusted. */ + e_ctx->write = 1; + + if (R_SUCCEEDED(user_result)) { + /* If result handling succeeded, just restore the context. */ + svc::RestoreContext(reinterpret_cast<uintptr_t>(e_ctx)); + } else { + /* Restore the debug params for the exception. */ + uintptr_t far, esr, data; + GetCurrentThread().RestoreDebugParams(std::addressof(far), std::addressof(esr), std::addressof(data)); + + /* Pre-process exception registers as needed. */ + const u64 ec = (esr >> 26) & 0x3F; + if (ec == EsrEc_InstructionAbortEl0 || ec == EsrEc_DataAbortEl0) { + /* Adjust registers if a synchronous external abort has occurred with far not valid. */ + /* Mask 0x03F = Low 6 bits IFSC == 0x10: "Synchronous External abort, */ + /* not on translation table walk or hardware update of translation table. */ + /* Mask 0x400 = FnV = "FAR Not Valid" */ + /* TODO: How would we perform this check using named register accesses? */ + if ((esr & 0x43F) == 0x410) { + /* Clear the faulting register on memory tagging exception. */ + far = 0; + } else { + /* If the faulting address is a kernel address, set ISFC = 4. */ + if (far >= ams::svc::AddressMemoryRegion39Size) { + esr = (esr & 0xFFFFFFC0) | 4; + } + } + } + + /* Collect additional information based on the ec. */ + uintptr_t params[3] = {}; + switch (ec) { + case EsrEc_Unknown: + case EsrEc_IllegalExecution: + case EsrEc_BkptInstruction: + case EsrEc_BrkInstruction: + { + params[0] = ams::svc::DebugException_UndefinedInstruction; + params[1] = far; + params[2] = data; + } + break; + case EsrEc_PcAlignmentFault: + case EsrEc_SpAlignmentFault: + { + params[0] = ams::svc::DebugException_AlignmentFault; + params[1] = far; + } + break; + case EsrEc_Svc32: + case EsrEc_Svc64: + { + params[0] = ams::svc::DebugException_UndefinedSystemCall; + params[1] = far; + params[2] = (esr & 0xFF); + } + break; + case EsrEc_SErrorInterrupt: + { + params[0] = ams::svc::DebugException_MemorySystemError; + params[1] = far; + } + break; + case EsrEc_InstructionAbortEl0: + { + params[0] = ams::svc::DebugException_InstructionAbort; + params[1] = far; + } + break; + case EsrEc_DataAbortEl0: + default: + { + params[0] = ams::svc::DebugException_DataAbort; + params[1] = far; + } + break; + } + + /* Process the debug event. */ + Result result = KDebug::OnDebugEvent(ams::svc::DebugEvent_Exception, params, util::size(params)); + + /* If the SVC is handled, handle it. */ + if (!svc::ResultNotHandled::Includes(result)) { + /* If we should stop processing the exception, restore. */ + if (svc::ResultStopProcessingException::Includes(result)) { + svc::RestoreContext(reinterpret_cast<uintptr_t>(e_ctx)); + } + + /* If we successfully enter jit debug, restore. */ + if (cur_process.EnterJitDebug(ams::svc::DebugEvent_Exception, static_cast<ams::svc::DebugException>(params[0]), params[1], params[2])) { + svc::RestoreContext(reinterpret_cast<uintptr_t>(e_ctx)); + } + } + + /* Otherwise, if result debug was returned, restore. */ + if (svc::ResultDebug::Includes(result)) { + svc::RestoreContext(reinterpret_cast<uintptr_t>(e_ctx)); + } + } + } + + /* Print that an exception occurred. */ + MESOSPHERE_EXCEPTION_LOG("Exception occurred. "); + + /* Exit the current process. */ + GetCurrentProcess().Exit(); + } + + /* NOTE: This function is called from ASM. */ + void HandleException(KExceptionContext *context) { + MESOSPHERE_ASSERT(!KInterruptManager::AreInterruptsEnabled()); + + /* Retrieve information about the exception. */ + const bool is_user_mode = (context->psr & 0xF) == 0; + const u64 esr = cpu::GetEsrEl1(); + const u64 afsr0 = cpu::GetAfsr0El1(); + const u64 afsr1 = cpu::GetAfsr1El1(); + u64 far = 0; + u32 data = 0; + + /* Collect far and data based on the ec. */ + switch ((esr >> 26) & 0x3F) { + case EsrEc_Unknown: + case EsrEc_IllegalExecution: + case EsrEc_BkptInstruction: + case EsrEc_BrkInstruction: + far = context->pc; + /* NOTE: Nintendo always calls GetInstructionDataUserMode. */ + if (is_user_mode) { + data = GetInstructionDataUserMode(context); + } else { + data = GetInstructionDataSupervisorMode(context, esr); + } + break; + case EsrEc_Svc32: + if (context->psr & 0x20) { + /* Thumb mode. */ + context->pc -= 2; + } else { + /* ARM mode. */ + context->pc -= 4; + } + far = context->pc; + break; + case EsrEc_Svc64: + context->pc -= 4; + far = context->pc; + break; + case EsrEc_BreakPointEl0: + far = context->pc; + break; + default: + far = cpu::GetFarEl1(); + break; + } + + /* Note that we're in an exception handler. */ + GetCurrentThread().SetInExceptionHandler(); + + /* Verify that spsr's M is allowable (EL0t). */ + { + if (is_user_mode) { + /* If the user disable count is set, we may need to pin the current thread. */ + if (GetCurrentThread().GetUserDisableCount() != 0 && GetCurrentProcess().GetPinnedThread(GetCurrentCoreId()) == nullptr) { + KScopedSchedulerLock lk; + + /* Pin the current thread. */ + GetCurrentProcess().PinCurrentThread(); + + /* Set the interrupt flag for the thread. */ + GetCurrentThread().SetInterruptFlag(); + } + + /* Enable interrupts while we process the usermode exception. */ + { + KScopedInterruptEnable ei; + + /* Terminate the thread, if we should. */ + if (GetCurrentThread().IsTerminationRequested()) { + GetCurrentThread().Exit(); + } + + HandleUserException(context, esr, far, afsr0, afsr1, data); + } + } else { + const s32 core_id = GetCurrentCoreId(); + + MESOSPHERE_LOG("%d: Unhandled Exception in Supervisor Mode\n", core_id); + if (GetCurrentProcessPointer() != nullptr) { + MESOSPHERE_LOG("%d: Current Process = %s\n", core_id, GetCurrentProcess().GetName()); + } + + for (size_t i = 0; i < 31; i++) { + MESOSPHERE_LOG("%d: X[%02zu] = %016lx\n", core_id, i, context->x[i]); + } + MESOSPHERE_LOG("%d: PC = %016lx\n", core_id, context->pc); + MESOSPHERE_LOG("%d: SP = %016lx\n", core_id, context->sp); + + MESOSPHERE_PANIC("Unhandled Exception in Supervisor Mode\n"); + } + + MESOSPHERE_ASSERT(!KInterruptManager::AreInterruptsEnabled()); + + /* Handle any DPC requests. */ + while (GetCurrentThread().HasDpc()) { + KDpcManager::HandleDpc(); + } + } + + /* Note that we're no longer in an exception handler. */ + GetCurrentThread().ClearInExceptionHandler(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_debug.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_debug.cpp new file mode 100644 index 00000000..87d7bf30 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_debug.cpp @@ -0,0 +1,949 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +/* <stratosphere/rocrt/rocrt.hpp> */ +namespace ams::rocrt { + + constexpr inline const u32 ModuleHeaderVersion = util::FourCC<'M','O','D','0'>::Code; + + struct ModuleHeader { + u32 signature; + u32 dynamic_offset; + u32 bss_start_offset; + u32 bss_end_offset; + u32 exception_info_start_offset; + u32 exception_info_end_offset; + u32 module_offset; + }; + + struct ModuleHeaderLocation { + u32 pad; + u32 header_offset; + }; + +} + +namespace ams::kern::arch::arm64 { + + namespace { + + constexpr inline u64 ForbiddenBreakPointFlagsMask = (((1ul << 40) - 1) << 24) | /* Reserved upper bits. */ + (((1ul << 1) - 1) << 23) | /* Match VMID BreakPoint Type. */ + (((1ul << 2) - 1) << 14) | /* Security State Control. */ + (((1ul << 1) - 1) << 13) | /* Hyp Mode Control. */ + (((1ul << 4) - 1) << 9) | /* Reserved middle bits. */ + (((1ul << 2) - 1) << 3) | /* Reserved lower bits. */ + (((1ul << 2) - 1) << 1); /* Privileged Mode Control. */ + + static_assert(ForbiddenBreakPointFlagsMask == 0xFFFFFFFFFF80FE1Eul); + + constexpr inline u64 ForbiddenWatchPointFlagsMask = (((1ul << 32) - 1) << 32) | /* Reserved upper bits. */ + (((1ul << 4) - 1) << 20) | /* WatchPoint Type. */ + (((1ul << 2) - 1) << 14) | /* Security State Control. */ + (((1ul << 1) - 1) << 13) | /* Hyp Mode Control. */ + (((1ul << 2) - 1) << 1); /* Privileged Access Control. */ + + static_assert(ForbiddenWatchPointFlagsMask == 0xFFFFFFFF00F0E006ul); + + } + + uintptr_t KDebug::GetProgramCounter(const KThread &thread) { + return GetExceptionContext(std::addressof(thread))->pc; + } + + void KDebug::SetPreviousProgramCounter() { + /* Get the current thread. */ + KThread *thread = GetCurrentThreadPointer(); + MESOSPHERE_ASSERT(thread->IsCallingSvc()); + + /* Get the exception context. */ + KExceptionContext *e_ctx = GetExceptionContext(thread); + + /* Set the previous pc. */ + if (e_ctx->write == 0) { + /* Subtract from the program counter. */ + if (thread->GetOwnerProcess()->Is64Bit()) { + e_ctx->pc -= sizeof(u32); + } else { + e_ctx->pc -= (e_ctx->psr & 0x20) ? sizeof(u16) : sizeof(u32); + } + + /* Mark that we've set. */ + e_ctx->write = 1; + } + } + + Result KDebug::GetThreadContextImpl(ams::svc::ThreadContext *out, KThread *thread, u32 context_flags) { + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + MESOSPHERE_ASSERT(thread != GetCurrentThreadPointer()); + + /* Get the exception context. */ + const KExceptionContext *e_ctx = GetExceptionContext(thread); + + /* Get whether we're 64-bit. */ + const bool is_64_bit = this->Is64Bit(); + + /* If general registers are requested, get them. */ + if ((context_flags & ams::svc::ThreadContextFlag_General) != 0) { + /* We can always get X0-X7/R0-R7. */ + auto register_count = 8; + if (!thread->IsCallingSvc() || thread->GetSvcId() == svc::SvcId_ReturnFromException) { + if (is_64_bit) { + /* We're not in an SVC, so we can get X0-X29. */ + register_count = 29; + } else { + /* We're 32-bit, so we should get R0-R12. */ + register_count = 13; + } + } + + /* Get the registers. */ + for (auto i = 0; i < register_count; ++i) { + out->r[i] = is_64_bit ? e_ctx->x[i] : static_cast<u32>(e_ctx->x[i]); + } + } + + /* If control flags are requested, get them. */ + if ((context_flags & ams::svc::ThreadContextFlag_Control) != 0) { + if (is_64_bit) { + out->fp = e_ctx->x[29]; + out->lr = e_ctx->x[30]; + out->sp = e_ctx->sp; + out->pc = e_ctx->pc; + out->pstate = (e_ctx->psr & cpu::El0Aarch64PsrMask); + + /* Adjust PC if we should. */ + if (e_ctx->write == 0 && thread->IsCallingSvc()) { + out->pc -= sizeof(u32); + } + + out->tpidr = e_ctx->tpidr; + } else { + out->r[11] = static_cast<u32>(e_ctx->x[11]); + out->r[13] = static_cast<u32>(e_ctx->x[13]); + out->r[14] = static_cast<u32>(e_ctx->x[14]); + out->lr = 0; + out->sp = 0; + out->pc = e_ctx->pc; + out->pstate = (e_ctx->psr & cpu::El0Aarch32PsrMask); + + /* Adjust PC if we should. */ + if (e_ctx->write == 0 && thread->IsCallingSvc()) { + out->pc -= (e_ctx->psr & 0x20) ? sizeof(u16) : sizeof(u32); + } + + out->tpidr = static_cast<u32>(e_ctx->tpidr); + } + } + + /* Get the FPU context. */ + R_RETURN(this->GetFpuContext(out, thread, context_flags)); + } + + Result KDebug::SetThreadContextImpl(const ams::svc::ThreadContext &ctx, KThread *thread, u32 context_flags) { + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + MESOSPHERE_ASSERT(thread != GetCurrentThreadPointer()); + + /* Get the exception context. */ + KExceptionContext *e_ctx = GetExceptionContext(thread); + + /* If general registers are requested, set them. */ + if ((context_flags & ams::svc::ThreadContextFlag_General) != 0) { + if (this->Is64Bit()) { + /* Set X0-X28. */ + for (auto i = 0; i <= 28; ++i) { + e_ctx->x[i] = ctx.r[i]; + } + } else { + /* Set R0-R12. */ + for (auto i = 0; i <= 12; ++i) { + e_ctx->x[i] = static_cast<u32>(ctx.r[i]); + } + } + } + + /* If control flags are requested, set them. */ + if ((context_flags & ams::svc::ThreadContextFlag_Control) != 0) { + /* Mark ourselve as having adjusted pc. */ + e_ctx->write = 1; + + if (this->Is64Bit()) { + e_ctx->x[29] = ctx.fp; + e_ctx->x[30] = ctx.lr; + e_ctx->sp = ctx.sp; + e_ctx->pc = ctx.pc; + e_ctx->psr = ((ctx.pstate & cpu::El0Aarch64PsrMask) | (e_ctx->psr & ~cpu::El0Aarch64PsrMask)); + e_ctx->tpidr = ctx.tpidr; + } else { + e_ctx->x[13] = static_cast<u32>(ctx.r[13]); + e_ctx->x[14] = static_cast<u32>(ctx.r[14]); + e_ctx->x[30] = 0; + e_ctx->sp = 0; + e_ctx->pc = static_cast<u32>(ctx.pc); + e_ctx->psr = ((ctx.pstate & cpu::El0Aarch32PsrMask) | (e_ctx->psr & ~cpu::El0Aarch32PsrMask)); + e_ctx->tpidr = ctx.tpidr; + } + } + + /* Set the FPU context. */ + R_RETURN(this->SetFpuContext(ctx, thread, context_flags)); + } + + Result KDebug::GetFpuContext(ams::svc::ThreadContext *out, KThread *thread, u32 context_flags) { + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + MESOSPHERE_ASSERT(thread != GetCurrentThreadPointer()); + + /* Succeed if there's nothing to do. */ + R_SUCCEED_IF((context_flags & (ams::svc::ThreadContextFlag_Fpu | ams::svc::ThreadContextFlag_FpuControl)) == 0); + + /* Get the thread context. */ + KThreadContext *t_ctx = std::addressof(thread->GetContext()); + + /* Get the FPU control registers, if required. */ + if ((context_flags & ams::svc::ThreadContextFlag_FpuControl) != 0) { + out->fpsr = t_ctx->GetFpsr(); + out->fpcr = t_ctx->GetFpcr(); + } + + /* Get the FPU registers, if required. */ + if ((context_flags & ams::svc::ThreadContextFlag_Fpu) != 0) { + static_assert(util::size(ams::svc::ThreadContext{}.v) == KThreadContext::NumFpuRegisters); + const auto &caller_save = thread->GetCallerSaveFpuRegisters(); + const auto &callee_save = t_ctx->GetCalleeSaveFpuRegisters(); + + if (this->Is64Bit()) { + KThreadContext::GetFpuRegisters(out->v, caller_save.fpu64, callee_save.fpu64); + } else { + KThreadContext::GetFpuRegisters(out->v, caller_save.fpu32, callee_save.fpu32); + for (size_t i = KThreadContext::NumFpuRegisters / 2; i < KThreadContext::NumFpuRegisters; ++i) { + out->v[i] = 0; + } + } + } + + R_SUCCEED(); + } + + Result KDebug::SetFpuContext(const ams::svc::ThreadContext &ctx, KThread *thread, u32 context_flags) { + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + MESOSPHERE_ASSERT(thread != GetCurrentThreadPointer()); + + /* Succeed if there's nothing to do. */ + R_SUCCEED_IF((context_flags & (ams::svc::ThreadContextFlag_Fpu | ams::svc::ThreadContextFlag_FpuControl)) == 0); + + /* Get the thread context. */ + KThreadContext *t_ctx = std::addressof(thread->GetContext()); + + /* Set the FPU control registers, if required. */ + if ((context_flags & ams::svc::ThreadContextFlag_FpuControl) != 0) { + t_ctx->SetFpsr(ctx.fpsr); + t_ctx->SetFpcr(ctx.fpcr); + } + + /* Set the FPU registers, if required. */ + if ((context_flags & ams::svc::ThreadContextFlag_Fpu) != 0) { + static_assert(util::size(ams::svc::ThreadContext{}.v) == KThreadContext::NumFpuRegisters); + auto &caller_save = thread->GetCallerSaveFpuRegisters(); + auto &callee_save = t_ctx->GetCalleeSaveFpuRegisters(); + + if (this->Is64Bit()) { + KThreadContext::SetFpuRegisters(caller_save.fpu64, callee_save.fpu64, ctx.v); + } else { + KThreadContext::SetFpuRegisters(caller_save.fpu32, callee_save.fpu32, ctx.v); + } + } + + R_SUCCEED(); + } + + Result KDebug::BreakIfAttached(ams::svc::BreakReason break_reason, uintptr_t address, size_t size) { + const uintptr_t params[5] = { ams::svc::DebugException_UserBreak, GetProgramCounter(GetCurrentThread()), break_reason, address, size }; + R_RETURN(KDebugBase::OnDebugEvent(ams::svc::DebugEvent_Exception, params, util::size(params))); + } + + #define MESOSPHERE_SET_HW_BREAK_POINT(ID, FLAGS, VALUE) \ + ({ \ + cpu::SetDbgBcr##ID##El1(0); \ + cpu::EnsureInstructionConsistencyFullSystem(); \ + cpu::SetDbgBvr##ID##El1(VALUE); \ + cpu::EnsureInstructionConsistencyFullSystem(); \ + cpu::SetDbgBcr##ID##El1(FLAGS); \ + cpu::EnsureInstructionConsistencyFullSystem(); \ + }) + + #define MESOSPHERE_SET_HW_WATCH_POINT(ID, FLAGS, VALUE) \ + ({ \ + cpu::SetDbgWcr##ID##El1(0); \ + cpu::EnsureInstructionConsistencyFullSystem(); \ + cpu::SetDbgWvr##ID##El1(VALUE); \ + cpu::EnsureInstructionConsistencyFullSystem(); \ + cpu::SetDbgWcr##ID##El1(FLAGS); \ + cpu::EnsureInstructionConsistencyFullSystem(); \ + }) + + Result KDebug::SetHardwareBreakPoint(ams::svc::HardwareBreakPointRegisterName name, u64 flags, u64 value) { + /* Get the debug feature register. */ + cpu::DebugFeatureRegisterAccessor dfr0; + + /* Extract interesting info from the debug feature register. */ + const auto num_bp = dfr0.GetNumBreakpoints(); + const auto num_wp = dfr0.GetNumWatchpoints(); + const auto num_ctx = dfr0.GetNumContextAwareBreakpoints(); + + if (ams::svc::HardwareBreakPointRegisterName_I0 <= name && name <= ams::svc::HardwareBreakPointRegisterName_I15) { + /* Check that the name is a valid instruction breakpoint. */ + R_UNLESS((name - ams::svc::HardwareBreakPointRegisterName_I0) <= num_bp, svc::ResultNotSupported()); + + /* Configure flags/value. */ + if ((flags & 1) != 0) { + /* We're enabling the breakpoint. Check that the flags are allowable. */ + R_UNLESS((flags & ForbiddenBreakPointFlagsMask) == 0, svc::ResultInvalidCombination()); + + /* Require that the breakpoint be linked or match context id. */ + R_UNLESS((flags & ((1ul << 21) | (1ul << 20))) != 0, svc::ResultInvalidCombination()); + + /* If the breakpoint matches context id, we need to get the context id. */ + if ((flags & (1ul << 21)) != 0) { + /* Ensure that the breakpoint is context-aware. */ + R_UNLESS((name - ams::svc::HardwareBreakPointRegisterName_I0) >= (num_bp - num_ctx), svc::ResultNotSupported()); + + /* Check that the breakpoint does not have the mismatch bit. */ + R_UNLESS((flags & (1ul << 22)) == 0, svc::ResultInvalidCombination()); + + /* Get the debug object from the current handle table. */ + KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(static_cast<ams::svc::Handle>(value)); + R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the process from the debug object. */ + R_UNLESS(debug->IsAttached(), svc::ResultProcessTerminated()); + R_UNLESS(debug->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close the process when we're done. */ + ON_SCOPE_EXIT { debug->CloseProcess(); }; + + /* Get the proces. */ + KProcess * const process = debug->GetProcessUnsafe(); + + /* Set the value to be the context id. */ + value = process->GetId() & 0xFFFFFFFF; + } + + /* Set the breakpoint as non-secure EL0-only. */ + flags |= (1ul << 14) | (2ul << 1); + } else { + /* We're disabling the breakpoint. */ + flags = 0; + value = 0; + } + + /* Set the breakpoint. */ + switch (name) { + case ams::svc::HardwareBreakPointRegisterName_I0: MESOSPHERE_SET_HW_BREAK_POINT( 0, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I1: MESOSPHERE_SET_HW_BREAK_POINT( 1, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I2: MESOSPHERE_SET_HW_BREAK_POINT( 2, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I3: MESOSPHERE_SET_HW_BREAK_POINT( 3, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I4: MESOSPHERE_SET_HW_BREAK_POINT( 4, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I5: MESOSPHERE_SET_HW_BREAK_POINT( 5, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I6: MESOSPHERE_SET_HW_BREAK_POINT( 6, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I7: MESOSPHERE_SET_HW_BREAK_POINT( 7, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I8: MESOSPHERE_SET_HW_BREAK_POINT( 8, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I9: MESOSPHERE_SET_HW_BREAK_POINT( 9, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I10: MESOSPHERE_SET_HW_BREAK_POINT(10, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I11: MESOSPHERE_SET_HW_BREAK_POINT(11, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I12: MESOSPHERE_SET_HW_BREAK_POINT(12, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I13: MESOSPHERE_SET_HW_BREAK_POINT(13, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I14: MESOSPHERE_SET_HW_BREAK_POINT(14, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_I15: MESOSPHERE_SET_HW_BREAK_POINT(15, flags, value); break; + default: break; + } + } else if (ams::svc::HardwareBreakPointRegisterName_D0 <= name && name <= ams::svc::HardwareBreakPointRegisterName_D15) { + /* Check that the name is a valid data breakpoint. */ + R_UNLESS((name - ams::svc::HardwareBreakPointRegisterName_D0) <= num_wp, svc::ResultNotSupported()); + + /* Configure flags/value. */ + if ((flags & 1) != 0) { + /* We're enabling the watchpoint. Check that the flags are allowable. */ + R_UNLESS((flags & ForbiddenWatchPointFlagsMask) == 0, svc::ResultInvalidCombination()); + + /* Set the breakpoint as linked non-secure EL0-only. */ + flags |= (1ul << 20) | (1ul << 14) | (2ul << 1); + } else { + /* We're disabling the watchpoint. */ + flags = 0; + value = 0; + } + + /* Set the watchpoint. */ + switch (name) { + case ams::svc::HardwareBreakPointRegisterName_D0: MESOSPHERE_SET_HW_WATCH_POINT( 0, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D1: MESOSPHERE_SET_HW_WATCH_POINT( 1, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D2: MESOSPHERE_SET_HW_WATCH_POINT( 2, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D3: MESOSPHERE_SET_HW_WATCH_POINT( 3, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D4: MESOSPHERE_SET_HW_WATCH_POINT( 4, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D5: MESOSPHERE_SET_HW_WATCH_POINT( 5, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D6: MESOSPHERE_SET_HW_WATCH_POINT( 6, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D7: MESOSPHERE_SET_HW_WATCH_POINT( 7, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D8: MESOSPHERE_SET_HW_WATCH_POINT( 8, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D9: MESOSPHERE_SET_HW_WATCH_POINT( 9, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D10: MESOSPHERE_SET_HW_WATCH_POINT(10, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D11: MESOSPHERE_SET_HW_WATCH_POINT(11, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D12: MESOSPHERE_SET_HW_WATCH_POINT(12, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D13: MESOSPHERE_SET_HW_WATCH_POINT(13, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D14: MESOSPHERE_SET_HW_WATCH_POINT(14, flags, value); break; + case ams::svc::HardwareBreakPointRegisterName_D15: MESOSPHERE_SET_HW_WATCH_POINT(15, flags, value); break; + default: break; + } + } else { + /* Invalid name. */ + R_THROW(svc::ResultInvalidEnumValue()); + } + + R_SUCCEED(); + } + + #undef MESOSPHERE_SET_HW_WATCH_POINT + #undef MESOSPHERE_SET_HW_BREAK_POINT + + void KDebug::PrintRegister(KThread *thread) { + #if defined(MESOSPHERE_BUILD_FOR_DEBUGGING) + { + /* Treat no thread as current thread. */ + if (thread == nullptr) { + thread = GetCurrentThreadPointer(); + } + + /* Get the exception context. */ + KExceptionContext *e_ctx = GetExceptionContext(thread); + + /* Get the owner process. */ + if (auto *process = thread->GetOwnerProcess(); process != nullptr) { + /* Lock the owner process. */ + KScopedLightLock state_lk(process->GetStateLock()); + KScopedLightLock list_lk(process->GetListLock()); + + /* Suspend all the process's threads. */ + { + KScopedSchedulerLock sl; + + auto end = process->GetThreadList().end(); + for (auto it = process->GetThreadList().begin(); it != end; ++it) { + if (std::addressof(*it) != GetCurrentThreadPointer()) { + it->RequestSuspend(KThread::SuspendType_Backtrace); + } + } + } + + /* Print the registers. */ + MESOSPHERE_RELEASE_LOG("Registers\n"); + if ((e_ctx->psr & 0x10) == 0) { + /* 64-bit thread. */ + for (auto i = 0; i < 31; ++i) { + MESOSPHERE_RELEASE_LOG(" X[%2d]: 0x%016lx\n", i, e_ctx->x[i]); + } + MESOSPHERE_RELEASE_LOG(" SP: 0x%016lx\n", e_ctx->sp); + MESOSPHERE_RELEASE_LOG(" PC: 0x%016lx\n", e_ctx->pc - sizeof(u32)); + MESOSPHERE_RELEASE_LOG(" PSR: 0x%08x\n", e_ctx->psr); + MESOSPHERE_RELEASE_LOG(" TPIDR_EL0: 0x%016lx\n", e_ctx->tpidr); + } else { + /* 32-bit thread. */ + for (auto i = 0; i < 13; ++i) { + MESOSPHERE_RELEASE_LOG(" R[%2d]: 0x%08x\n", i, static_cast<u32>(e_ctx->x[i])); + } + MESOSPHERE_RELEASE_LOG(" SP: 0x%08x\n", static_cast<u32>(e_ctx->x[13])); + MESOSPHERE_RELEASE_LOG(" LR: 0x%08x\n", static_cast<u32>(e_ctx->x[14])); + MESOSPHERE_RELEASE_LOG(" PC: 0x%08x\n", static_cast<u32>(e_ctx->pc) - static_cast<u32>((e_ctx->psr & 0x20) ? sizeof(u16) : sizeof(u32))); + MESOSPHERE_RELEASE_LOG(" PSR: 0x%08x\n", e_ctx->psr); + MESOSPHERE_RELEASE_LOG(" TPIDR: 0x%08x\n", static_cast<u32>(e_ctx->tpidr)); + } + + /* Resume the threads that we suspended. */ + { + KScopedSchedulerLock sl; + + auto end = process->GetThreadList().end(); + for (auto it = process->GetThreadList().begin(); it != end; ++it) { + if (std::addressof(*it) != GetCurrentThreadPointer()) { + it->Resume(KThread::SuspendType_Backtrace); + } + } + } + } + } + #else + MESOSPHERE_UNUSED(thread); + #endif + } + + #if defined(MESOSPHERE_BUILD_FOR_DEBUGGING) + namespace { + + bool IsHeapPhysicalAddress(KPhysicalAddress phys_addr) { + const KMemoryRegion *cached = nullptr; + return KMemoryLayout::IsHeapPhysicalAddress(cached, phys_addr); + } + + template<typename T> + bool ReadValue(T *out, KProcess *process, uintptr_t address) { + KPhysicalAddress phys_addr; + KMemoryInfo mem_info; + ams::svc::PageInfo page_info; + + if (!util::IsAligned(address, sizeof(T))) { + return false; + } + if (R_FAILED(process->GetPageTable().QueryInfo(std::addressof(mem_info), std::addressof(page_info), address))) { + return false; + } + if ((mem_info.GetPermission() & KMemoryPermission_UserRead) != KMemoryPermission_UserRead) { + return false; + } + if (!process->GetPageTable().GetPhysicalAddress(std::addressof(phys_addr), address)) { + return false; + } + if (!IsHeapPhysicalAddress(phys_addr)) { + return false; + } + + *out = *GetPointer<T>(process->GetPageTable().GetHeapVirtualAddress(phys_addr)); + return true; + } + + bool GetModuleName(char *dst, size_t dst_size, KProcess *process, uintptr_t base_address) { + /* Locate .rodata. */ + KMemoryInfo mem_info; + ams::svc::PageInfo page_info; + KMemoryState mem_state = KMemoryState_None; + + while (true) { + if (R_FAILED(process->GetPageTable().QueryInfo(std::addressof(mem_info), std::addressof(page_info), base_address))) { + return false; + } + if (mem_state == KMemoryState_None) { + mem_state = mem_info.GetState(); + if (mem_state != KMemoryState_Code && mem_state != KMemoryState_AliasCode) { + return false; + } + } + if (mem_info.GetState() != mem_state) { + return false; + } + if (mem_info.GetPermission() == KMemoryPermission_UserRead) { + break; + } + base_address = mem_info.GetEndAddress(); + } + + /* Check that first value is 0. */ + u32 val; + if (!ReadValue(std::addressof(val), process, base_address)) { + return false; + } + if (val != 0) { + return false; + } + + /* Read the name length. */ + if (!ReadValue(std::addressof(val), process, base_address + sizeof(u32))) { + return false; + } + if (!(0 < val && val < dst_size)) { + return false; + } + const size_t name_len = val; + + /* Read the name, one character at a time. */ + for (size_t i = 0; i < name_len; ++i) { + if (!ReadValue(dst + i, process, base_address + 2 * sizeof(u32) + i)) { + return false; + } + if (!(0 < dst[i] && dst[i] <= 0x7F)) { + return false; + } + } + + /* NULL-terminate. */ + dst[name_len] = 0; + + return true; + } + + void PrintAddress(uintptr_t address) { + MESOSPHERE_RELEASE_LOG(" %p\n", reinterpret_cast<void *>(address)); + } + + void PrintAddressWithModuleName(uintptr_t address, bool has_module_name, const char *module_name, uintptr_t base_address) { + if (has_module_name) { + MESOSPHERE_RELEASE_LOG(" %p [%10s + %8lx]\n", reinterpret_cast<void *>(address), module_name, address - base_address); + } else { + MESOSPHERE_RELEASE_LOG(" %p [%10lx + %8lx]\n", reinterpret_cast<void *>(address), base_address, address - base_address); + } + } + + void PrintAddressWithSymbol(uintptr_t address, bool has_module_name, const char *module_name, uintptr_t base_address, const char *symbol_name, uintptr_t func_address) { + if (has_module_name) { + MESOSPHERE_RELEASE_LOG(" %p [%10s + %8lx] (%s + %lx)\n", reinterpret_cast<void *>(address), module_name, address - base_address, symbol_name, address - func_address); + } else { + MESOSPHERE_RELEASE_LOG(" %p [%10lx + %8lx] (%s + %lx)\n", reinterpret_cast<void *>(address), base_address, address - base_address, symbol_name, address - func_address); + } + } + + void PrintCodeAddress(KProcess *process, uintptr_t address, bool is_lr = true) { + /* Prepare to parse + print the address. */ + uintptr_t test_address = is_lr ? address - sizeof(u32) : address; + uintptr_t base_address = address; + uintptr_t dyn_address = 0; + uintptr_t sym_tab = 0; + uintptr_t str_tab = 0; + size_t num_sym = 0; + + u64 temp_64; + u32 temp_32; + + /* Locate the start of .text. */ + KMemoryInfo mem_info; + ams::svc::PageInfo page_info; + KMemoryState mem_state = KMemoryState_None; + while (true) { + if (R_FAILED(process->GetPageTable().QueryInfo(std::addressof(mem_info), std::addressof(page_info), base_address))) { + return PrintAddress(address); + } + if (mem_state == KMemoryState_None) { + mem_state = mem_info.GetState(); + if (mem_state != KMemoryState_Code && mem_state != KMemoryState_AliasCode) { + return PrintAddress(address); + } + } else if (mem_info.GetState() != mem_state) { + return PrintAddress(address); + } + if (mem_info.GetPermission() != KMemoryPermission_UserReadExecute) { + return PrintAddress(address); + } + base_address = mem_info.GetAddress(); + + if (R_FAILED(process->GetPageTable().QueryInfo(std::addressof(mem_info), std::addressof(page_info), base_address - 1))) { + return PrintAddress(address); + } + if (mem_info.GetState() != mem_state) { + break; + } + if (mem_info.GetPermission() != KMemoryPermission_UserReadExecute) { + break; + } + } + + /* Get the module name. */ + char module_name[0x20]; + const bool has_module_name = GetModuleName(module_name, sizeof(module_name), process, base_address); + + /* If the process is 32-bit, just print the module. */ + if (!process->Is64Bit()) { + return PrintAddressWithModuleName(address, has_module_name, module_name, base_address); + } + + /* Locate .dyn using rocrt::ModuleHeader. */ + { + /* Determine the ModuleHeader offset. */ + u32 mod_offset; + if (!ReadValue(std::addressof(mod_offset), process, base_address + sizeof(u32))) { + return PrintAddressWithModuleName(address, has_module_name, module_name, base_address); + } + + /* Read the signature. */ + constexpr u32 SignatureFieldOffset = AMS_OFFSETOF(rocrt::ModuleHeader, signature); + if (!ReadValue(std::addressof(temp_32), process, base_address + mod_offset + SignatureFieldOffset)) { + return PrintAddressWithModuleName(address, has_module_name, module_name, base_address); + } + + /* Check that the module signature is expected. */ + if (temp_32 != rocrt::ModuleHeaderVersion) { /* MOD0 */ + return PrintAddressWithModuleName(address, has_module_name, module_name, base_address); + } + + /* Determine the dynamic offset. */ + constexpr u32 DynamicFieldOffset = AMS_OFFSETOF(rocrt::ModuleHeader, dynamic_offset); + if (!ReadValue(std::addressof(temp_32), process, base_address + mod_offset + DynamicFieldOffset)) { + return PrintAddressWithModuleName(address, has_module_name, module_name, base_address); + } + + dyn_address = base_address + mod_offset + temp_32; + } + + /* Locate tables inside .dyn. */ + for (size_t ofs = 0; /* ... */; ofs += 0x10) { + /* Read the DynamicTag. */ + if (!ReadValue(std::addressof(temp_64), process, dyn_address + ofs)) { + return PrintAddressWithModuleName(address, has_module_name, module_name, base_address); + } + if (temp_64 == 0) { + /* We're done parsing .dyn. */ + break; + } else if (temp_64 == 4) { + /* We found DT_HASH */ + if (!ReadValue(std::addressof(temp_64), process, dyn_address + ofs + sizeof(u64))) { + return PrintAddressWithModuleName(address, has_module_name, module_name, base_address); + } + /* Read nchain, to get the number of symbols. */ + if (!ReadValue(std::addressof(temp_32), process, base_address + temp_64 + sizeof(u32))) { + return PrintAddressWithModuleName(address, has_module_name, module_name, base_address); + } + + num_sym = temp_32; + } else if (temp_64 == 5) { + /* We found DT_STRTAB */ + if (!ReadValue(std::addressof(temp_64), process, dyn_address + ofs + sizeof(u64))) { + return PrintAddressWithModuleName(address, has_module_name, module_name, base_address); + } + + str_tab = base_address + temp_64; + } else if (temp_64 == 6) { + /* We found DT_SYMTAB */ + if (!ReadValue(std::addressof(temp_64), process, dyn_address + ofs + sizeof(u64))) { + return PrintAddressWithModuleName(address, has_module_name, module_name, base_address); + } + + sym_tab = base_address + temp_64; + } + } + + /* Check that we found all the tables. */ + if (!(sym_tab != 0 && str_tab != 0 && num_sym != 0)) { + return PrintAddressWithModuleName(address, has_module_name, module_name, base_address); + } + + /* Try to locate an appropriate symbol. */ + for (size_t i = 0; i < num_sym; ++i) { + /* Read the symbol from userspace. */ + struct { + u32 st_name; + u8 st_info; + u8 st_other; + u16 st_shndx; + u64 st_value; + u64 st_size; + } sym; + { + u64 x[sizeof(sym) / sizeof(u64)]; + for (size_t j = 0; j < util::size(x); ++j) { + if (!ReadValue(x + j, process, sym_tab + sizeof(sym) * i + sizeof(u64) * j)) { + return PrintAddressWithModuleName(address, has_module_name, module_name, base_address); + } + } + std::memcpy(std::addressof(sym), x, sizeof(sym)); + } + + /* Check the symbol is valid/STT_FUNC. */ + if (sym.st_shndx == 0 || ((sym.st_shndx & 0xFF00) == 0xFF00)) { + continue; + } + if ((sym.st_info & 0xF) != 2) { + continue; + } + + /* Check the address. */ + const uintptr_t func_start = base_address + sym.st_value; + if (func_start <= test_address && test_address < func_start + sym.st_size) { + /* Read the symbol name. */ + const uintptr_t sym_address = str_tab + sym.st_name; + char sym_name[0x80]; + sym_name[util::size(sym_name) - 1] = 0; + for (size_t j = 0; j < util::size(sym_name) - 1; ++j) { + if (!ReadValue(sym_name + j, process, sym_address + j)) { + return PrintAddressWithModuleName(address, has_module_name, module_name, base_address); + } + if (sym_name[j] == 0) { + break; + } + } + + /* Print the symbol. */ + return PrintAddressWithSymbol(address, has_module_name, module_name, base_address, sym_name, func_start); + } + } + + /* Fall back to printing the module. */ + return PrintAddressWithModuleName(address, has_module_name, module_name, base_address); + } + + } + #endif + + void KDebug::PrintBacktrace(KThread *thread) { + #if defined(MESOSPHERE_BUILD_FOR_DEBUGGING) + { + /* Treat no thread as current thread. */ + if (thread == nullptr) { + thread = GetCurrentThreadPointer(); + } + + /* Get the exception context. */ + KExceptionContext *e_ctx = GetExceptionContext(thread); + + /* Get the owner process. */ + if (auto *process = thread->GetOwnerProcess(); process != nullptr) { + /* Lock the owner process. */ + KScopedLightLock state_lk(process->GetStateLock()); + KScopedLightLock list_lk(process->GetListLock()); + + /* Suspend all the process's threads. */ + { + KScopedSchedulerLock sl; + + auto end = process->GetThreadList().end(); + for (auto it = process->GetThreadList().begin(); it != end; ++it) { + if (std::addressof(*it) != GetCurrentThreadPointer()) { + it->RequestSuspend(KThread::SuspendType_Backtrace); + } + } + } + + /* Print the backtrace. */ + MESOSPHERE_RELEASE_LOG("User Backtrace\n"); + if ((e_ctx->psr & 0x10) == 0) { + /* 64-bit thread. */ + PrintCodeAddress(process, e_ctx->pc, false); + PrintCodeAddress(process, e_ctx->x[30]); + + /* Walk the stack frames. */ + uintptr_t fp = static_cast<uintptr_t>(e_ctx->x[29]); + for (auto i = 0; i < 0x20 && fp != 0 && util::IsAligned(fp, 0x10); ++i) { + /* Read the next frame. */ + struct { + u64 fp; + u64 lr; + } stack_frame; + { + KMemoryInfo mem_info; + ams::svc::PageInfo page_info; + KPhysicalAddress phys_addr; + + if (R_FAILED(process->GetPageTable().QueryInfo(std::addressof(mem_info), std::addressof(page_info), fp))) { + break; + } + if ((mem_info.GetState() & KMemoryState_FlagReferenceCounted) == 0) { + break; + } + if ((mem_info.GetAttribute() & KMemoryAttribute_Uncached) != 0) { + break; + } + if ((mem_info.GetPermission() & KMemoryPermission_UserRead) != KMemoryPermission_UserRead) { + break; + } + if (!process->GetPageTable().GetPhysicalAddress(std::addressof(phys_addr), fp)) { + break; + } + if (!IsHeapPhysicalAddress(phys_addr)) { + break; + } + + u64 *frame_ptr = GetPointer<u64>(process->GetPageTable().GetHeapVirtualAddress(phys_addr)); + stack_frame.fp = frame_ptr[0]; + stack_frame.lr = frame_ptr[1]; + } + + /* Print and advance. */ + PrintCodeAddress(process, stack_frame.lr); + fp = stack_frame.fp; + } + } else { + /* 32-bit thread. */ + PrintCodeAddress(process, e_ctx->pc, false); + PrintCodeAddress(process, e_ctx->x[14]); + + /* Walk the stack frames. */ + uintptr_t fp = static_cast<uintptr_t>(e_ctx->x[11]); + for (auto i = 0; i < 0x20 && fp != 0 && util::IsAligned(fp, 4); ++i) { + /* Read the next frame. */ + struct { + u32 fp; + u32 lr; + } stack_frame; + { + KMemoryInfo mem_info; + ams::svc::PageInfo page_info; + KPhysicalAddress phys_addr; + + /* Read FP */ + if (R_FAILED(process->GetPageTable().QueryInfo(std::addressof(mem_info), std::addressof(page_info), fp))) { + break; + } + if ((mem_info.GetState() & KMemoryState_FlagReferenceCounted) == 0) { + break; + } + if ((mem_info.GetAttribute() & KMemoryAttribute_Uncached) != 0) { + break; + } + if ((mem_info.GetPermission() & KMemoryPermission_UserRead) != KMemoryPermission_UserRead) { + break; + } + if (!process->GetPageTable().GetPhysicalAddress(std::addressof(phys_addr), fp)) { + break; + } + if (!IsHeapPhysicalAddress(phys_addr)) { + break; + } + + stack_frame.fp = *GetPointer<u32>(process->GetPageTable().GetHeapVirtualAddress(phys_addr)); + + /* Read LR. */ + uintptr_t lr_ptr = (e_ctx->x[13] <= stack_frame.fp && stack_frame.fp < e_ctx->x[13] + PageSize) ? fp + 4 : fp - 4; + if (R_FAILED(process->GetPageTable().QueryInfo(std::addressof(mem_info), std::addressof(page_info), lr_ptr))) { + break; + } + if ((mem_info.GetState() & KMemoryState_FlagReferenceCounted) == 0) { + break; + } + if ((mem_info.GetAttribute() & KMemoryAttribute_Uncached) != 0) { + break; + } + if ((mem_info.GetPermission() & KMemoryPermission_UserRead) != KMemoryPermission_UserRead) { + break; + } + if (!process->GetPageTable().GetPhysicalAddress(std::addressof(phys_addr), lr_ptr)) { + break; + } + if (!IsHeapPhysicalAddress(phys_addr)) { + break; + } + + stack_frame.lr = *GetPointer<u32>(process->GetPageTable().GetHeapVirtualAddress(phys_addr)); + } + + /* Print and advance. */ + PrintCodeAddress(process, stack_frame.lr); + fp = stack_frame.fp; + } + } + + /* Resume the threads that we suspended. */ + { + KScopedSchedulerLock sl; + + auto end = process->GetThreadList().end(); + for (auto it = process->GetThreadList().begin(); it != end; ++it) { + if (std::addressof(*it) != GetCurrentThreadPointer()) { + it->Resume(KThread::SuspendType_Backtrace); + } + } + } + } + } + #else + MESOSPHERE_UNUSED(thread); + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_hardware_timer.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_hardware_timer.cpp new file mode 100644 index 00000000..57e10774 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_hardware_timer.cpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::arch::arm64 { + + void KHardwareTimer::Initialize() { + /* Setup the global timer for the core. */ + InitializeGlobalTimer(); + + /* Set maximum time. */ + m_maximum_time = static_cast<s64>(std::min<u64>(std::numeric_limits<s64>::max(), cpu::CounterTimerPhysicalTimerCompareValueRegisterAccessor().GetCompareValue())); + + /* Bind the interrupt task for this core. */ + Kernel::GetInterruptManager().BindHandler(this, KInterruptName_NonSecurePhysicalTimer, GetCurrentCoreId(), KInterruptController::PriorityLevel_Timer, true, true); + } + + void KHardwareTimer::Finalize() { + /* Stop the hardware timer. */ + StopTimer(); + } + + void KHardwareTimer::DoTask() { + /* Handle the interrupt. */ + { + KScopedSchedulerLock slk; + KScopedSpinLock lk(this->GetLock()); + + /* Disable the timer interrupt while we handle this. */ + DisableInterrupt(); + if (const s64 next_time = this->DoInterruptTaskImpl(GetTick()); 0 < next_time && next_time <= m_maximum_time) { + /* We have a next time, so we should set the time to interrupt and turn the interrupt on. */ + SetCompareValue(next_time); + EnableInterrupt(); + } + } + + /* Clear the timer interrupt. */ + Kernel::GetInterruptManager().ClearInterrupt(KInterruptName_NonSecurePhysicalTimer, GetCurrentCoreId()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_interrupt_controller.board.generic.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_interrupt_controller.board.generic.cpp new file mode 100644 index 00000000..de70a3f1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_interrupt_controller.board.generic.cpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +/* Include the common implementation. */ +#include "../arm/kern_generic_interrupt_controller.inc" diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_interrupt_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_interrupt_manager.cpp new file mode 100644 index 00000000..e4cf075d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_interrupt_manager.cpp @@ -0,0 +1,389 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::arch::arm64 { + + void KInterruptManager::Initialize(s32 core_id) { + m_interrupt_controller.Initialize(core_id); + } + + void KInterruptManager::Finalize(s32 core_id) { + m_interrupt_controller.Finalize(core_id); + } + + void KInterruptManager::Save(s32 core_id) { + /* Verify core id. */ + MESOSPHERE_ASSERT(core_id == GetCurrentCoreId()); + + /* Ensure all cores get to this point before continuing. */ + cpu::SynchronizeAllCores(); + + /* If on core 0, save the global interrupts. */ + if (core_id == 0) { + MESOSPHERE_ABORT_UNLESS(!m_global_state_saved); + m_interrupt_controller.SaveGlobal(std::addressof(m_global_state)); + m_global_state_saved = true; + } + + /* Ensure all cores get to this point before continuing. */ + cpu::SynchronizeAllCores(); + + /* Save all local interrupts. */ + MESOSPHERE_ABORT_UNLESS(!m_local_state_saved[core_id]); + m_interrupt_controller.SaveCoreLocal(std::addressof(m_local_states[core_id])); + m_local_state_saved[core_id] = true; + + /* Ensure all cores get to this point before continuing. */ + cpu::SynchronizeAllCores(); + + /* Finalize all cores other than core 0. */ + if (core_id != 0) { + this->Finalize(core_id); + } + + /* Ensure all cores get to this point before continuing. */ + cpu::SynchronizeAllCores(); + + /* Finalize core 0. */ + if (core_id == 0) { + this->Finalize(core_id); + } + } + + void KInterruptManager::Restore(s32 core_id) { + /* Verify core id. */ + MESOSPHERE_ASSERT(core_id == GetCurrentCoreId()); + + /* Ensure all cores get to this point before continuing. */ + cpu::SynchronizeAllCores(); + + /* Initialize core 0. */ + if (core_id == 0) { + this->Initialize(core_id); + } + + /* Ensure all cores get to this point before continuing. */ + cpu::SynchronizeAllCores(); + + /* Initialize all cores other than core 0. */ + if (core_id != 0) { + this->Initialize(core_id); + } + + /* Ensure all cores get to this point before continuing. */ + cpu::SynchronizeAllCores(); + + /* Restore all local interrupts. */ + MESOSPHERE_ASSERT(m_local_state_saved[core_id]); + m_interrupt_controller.RestoreCoreLocal(std::addressof(m_local_states[core_id])); + m_local_state_saved[core_id] = false; + + /* Ensure all cores get to this point before continuing. */ + cpu::SynchronizeAllCores(); + + /* If on core 0, restore the global interrupts. */ + if (core_id == 0) { + MESOSPHERE_ASSERT(m_global_state_saved); + m_interrupt_controller.RestoreGlobal(std::addressof(m_global_state)); + m_global_state_saved = false; + } + + /* Ensure all cores get to this point before continuing. */ + cpu::SynchronizeAllCores(); + } + + bool KInterruptManager::OnHandleInterrupt() { + /* Get the interrupt id. */ + const u32 raw_irq = m_interrupt_controller.GetIrq(); + const s32 irq = KInterruptController::ConvertRawIrq(raw_irq); + + /* Trace the interrupt. */ + MESOSPHERE_KTRACE_INTERRUPT(irq); + + /* If the IRQ is spurious, we don't need to reschedule. */ + if (irq < 0) { + return false; + } + + KInterruptTask *task = nullptr; + if (KInterruptController::IsLocal(irq)) { + /* Get local interrupt entry. */ + auto &entry = GetLocalInterruptEntry(irq); + if (entry.handler != nullptr) { + /* Set manual clear needed if relevant. */ + if (entry.manually_cleared) { + m_interrupt_controller.SetPriorityLevel(irq, KInterruptController::PriorityLevel_Low); + entry.needs_clear = true; + } + + /* Set the handler. */ + task = entry.handler->OnInterrupt(irq); + } else { + MESOSPHERE_LOG("Core%d: Unhandled local interrupt %d\n", GetCurrentCoreId(), irq); + } + } else if (KInterruptController::IsGlobal(irq)) { + KScopedSpinLock lk(this->GetGlobalInterruptLock()); + + /* Get global interrupt entry. */ + auto &entry = GetGlobalInterruptEntry(irq); + if (entry.handler != nullptr) { + /* Set manual clear needed if relevant. */ + if (entry.manually_cleared) { + m_interrupt_controller.Disable(irq); + entry.needs_clear = true; + } + + /* Set the handler. */ + task = entry.handler->OnInterrupt(irq); + } else { + MESOSPHERE_LOG("Core%d: Unhandled global interrupt %d\n", GetCurrentCoreId(), irq); + } + } else { + MESOSPHERE_LOG("Invalid interrupt %d\n", irq); + } + + /* Acknowledge the interrupt. */ + m_interrupt_controller.EndOfInterrupt(raw_irq); + + /* If we found no task, then we don't need to reschedule. */ + if (task == nullptr) { + return false; + } + + /* If the task isn't the dummy task, we should add it to the queue. */ + if (task != GetDummyInterruptTask()) { + Kernel::GetInterruptTaskManager().EnqueueTask(task); + } + + return true; + } + + void KInterruptManager::HandleInterrupt(bool user_mode) { + /* On interrupt, call OnHandleInterrupt() to determine if we need rescheduling and handle. */ + const bool needs_scheduling = Kernel::GetInterruptManager().OnHandleInterrupt(); + + /* If we need scheduling, */ + if (needs_scheduling) { + if (user_mode) { + /* If the interrupt occurred in the middle of a userland cache maintenance operation, ensure memory consistency before rescheduling. */ + if (GetCurrentThread().IsInUserCacheMaintenanceOperation()) { + cpu::DataSynchronizationBarrier(); + } + + /* If the user disable count is set, we may need to pin the current thread. */ + if (GetCurrentThread().GetUserDisableCount() != 0 && GetCurrentProcess().GetPinnedThread(GetCurrentCoreId()) == nullptr) { + KScopedSchedulerLock sl; + + /* Pin the current thread. */ + GetCurrentProcess().PinCurrentThread(); + + /* Set the interrupt flag for the thread. */ + GetCurrentThread().SetInterruptFlag(); + + /* Request interrupt scheduling. */ + Kernel::GetScheduler().RequestScheduleOnInterrupt(); + } else { + /* Request interrupt scheduling. */ + Kernel::GetScheduler().RequestScheduleOnInterrupt(); + } + } else { + /* If the interrupt occurred in the middle of a cache maintenance operation, ensure memory consistency before rescheduling. */ + if (GetCurrentThread().IsInCacheMaintenanceOperation()) { + cpu::DataSynchronizationBarrier(); + } else if (GetCurrentThread().IsInTlbMaintenanceOperation()) { + /* Otherwise, if we're in the middle of a tlb maintenance operation, ensure inner shareable memory consistency before rescheduling. */ + cpu::DataSynchronizationBarrierInnerShareable(); + } + + /* Request interrupt scheduling. */ + Kernel::GetScheduler().RequestScheduleOnInterrupt(); + } + } + + /* If user mode, check if the thread needs termination. */ + /* If it does, we can take advantage of this to terminate it. */ + if (user_mode) { + KThread *cur_thread = GetCurrentThreadPointer(); + if (cur_thread->IsTerminationRequested()) { + EnableInterrupts(); + cur_thread->Exit(); + } + } + } + + Result KInterruptManager::BindHandler(KInterruptHandler *handler, s32 irq, s32 core_id, s32 priority, bool manual_clear, bool level) { + MESOSPHERE_UNUSED(core_id); + + R_UNLESS(KInterruptController::IsGlobal(irq) || KInterruptController::IsLocal(irq), svc::ResultOutOfRange()); + + if (KInterruptController::IsGlobal(irq)) { + KScopedInterruptDisable di; + KScopedSpinLock lk(this->GetGlobalInterruptLock()); + R_RETURN(this->BindGlobal(handler, irq, core_id, priority, manual_clear, level)); + } else { + MESOSPHERE_ASSERT(core_id == GetCurrentCoreId()); + + KScopedInterruptDisable di; + R_RETURN(this->BindLocal(handler, irq, priority, manual_clear)); + } + } + + Result KInterruptManager::UnbindHandler(s32 irq, s32 core_id) { + MESOSPHERE_UNUSED(core_id); + + R_UNLESS(KInterruptController::IsGlobal(irq) || KInterruptController::IsLocal(irq), svc::ResultOutOfRange()); + + + if (KInterruptController::IsGlobal(irq)) { + KScopedInterruptDisable di; + + KScopedSpinLock lk(this->GetGlobalInterruptLock()); + R_RETURN(this->UnbindGlobal(irq)); + } else { + MESOSPHERE_ASSERT(core_id == GetCurrentCoreId()); + + KScopedInterruptDisable di; + R_RETURN(this->UnbindLocal(irq)); + } + } + + Result KInterruptManager::ClearInterrupt(s32 irq, s32 core_id) { + MESOSPHERE_UNUSED(core_id); + + R_UNLESS(KInterruptController::IsGlobal(irq) || KInterruptController::IsLocal(irq), svc::ResultOutOfRange()); + + + if (KInterruptController::IsGlobal(irq)) { + KScopedInterruptDisable di; + KScopedSpinLock lk(this->GetGlobalInterruptLock()); + R_RETURN(this->ClearGlobal(irq)); + } else { + MESOSPHERE_ASSERT(core_id == GetCurrentCoreId()); + + KScopedInterruptDisable di; + R_RETURN(this->ClearLocal(irq)); + } + } + + Result KInterruptManager::BindGlobal(KInterruptHandler *handler, s32 irq, s32 core_id, s32 priority, bool manual_clear, bool level) { + /* Ensure the priority level is valid. */ + R_UNLESS(KInterruptController::PriorityLevel_High <= priority, svc::ResultOutOfRange()); + R_UNLESS(priority <= KInterruptController::PriorityLevel_Low, svc::ResultOutOfRange()); + + /* Ensure we aren't already bound. */ + auto &entry = GetGlobalInterruptEntry(irq); + R_UNLESS(entry.handler == nullptr, svc::ResultBusy()); + + /* Set entry fields. */ + entry.needs_clear = false; + entry.manually_cleared = manual_clear; + entry.handler = handler; + + /* Configure the interrupt as level or edge. */ + if (level) { + m_interrupt_controller.SetLevel(irq); + } else { + m_interrupt_controller.SetEdge(irq); + } + + /* Configure the interrupt. */ + m_interrupt_controller.Clear(irq); + m_interrupt_controller.SetTarget(irq, core_id); + m_interrupt_controller.SetPriorityLevel(irq, priority); + m_interrupt_controller.Enable(irq); + + R_SUCCEED(); + } + + Result KInterruptManager::BindLocal(KInterruptHandler *handler, s32 irq, s32 priority, bool manual_clear) { + /* Ensure the priority level is valid. */ + R_UNLESS(KInterruptController::PriorityLevel_High <= priority, svc::ResultOutOfRange()); + R_UNLESS(priority <= KInterruptController::PriorityLevel_Low, svc::ResultOutOfRange()); + + /* Ensure we aren't already bound. */ + auto &entry = this->GetLocalInterruptEntry(irq); + R_UNLESS(entry.handler == nullptr, svc::ResultBusy()); + + /* Set entry fields. */ + entry.needs_clear = false; + entry.manually_cleared = manual_clear; + entry.handler = handler; + entry.priority = static_cast<u8>(priority); + + /* Configure the interrupt. */ + m_interrupt_controller.Clear(irq); + m_interrupt_controller.SetPriorityLevel(irq, priority); + m_interrupt_controller.Enable(irq); + + R_SUCCEED(); + } + + Result KInterruptManager::UnbindGlobal(s32 irq) { + for (size_t core_id = 0; core_id < cpu::NumCores; core_id++) { + m_interrupt_controller.ClearTarget(irq, static_cast<s32>(core_id)); + } + m_interrupt_controller.SetPriorityLevel(irq, KInterruptController::PriorityLevel_Low); + m_interrupt_controller.Disable(irq); + + GetGlobalInterruptEntry(irq).handler = nullptr; + + R_SUCCEED(); + } + + Result KInterruptManager::UnbindLocal(s32 irq) { + auto &entry = this->GetLocalInterruptEntry(irq); + R_UNLESS(entry.handler != nullptr, svc::ResultInvalidState()); + + m_interrupt_controller.SetPriorityLevel(irq, KInterruptController::PriorityLevel_Low); + m_interrupt_controller.Disable(irq); + + entry.handler = nullptr; + + R_SUCCEED(); + } + + Result KInterruptManager::ClearGlobal(s32 irq) { + /* We can't clear an entry with no handler. */ + auto &entry = GetGlobalInterruptEntry(irq); + R_UNLESS(entry.handler != nullptr, svc::ResultInvalidState()); + + /* If auto-cleared, we can succeed immediately. */ + R_SUCCEED_IF(!entry.manually_cleared); + R_SUCCEED_IF(!entry.needs_clear); + + /* Clear and enable. */ + entry.needs_clear = false; + m_interrupt_controller.Enable(irq); + R_SUCCEED(); + } + + Result KInterruptManager::ClearLocal(s32 irq) { + /* We can't clear an entry with no handler. */ + auto &entry = this->GetLocalInterruptEntry(irq); + R_UNLESS(entry.handler != nullptr, svc::ResultInvalidState()); + + /* If auto-cleared, we can succeed immediately. */ + R_SUCCEED_IF(!entry.manually_cleared); + R_SUCCEED_IF(!entry.needs_clear); + + /* Clear and set priority. */ + entry.needs_clear = false; + m_interrupt_controller.SetPriorityLevel(irq, entry.priority); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_page_table.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_page_table.cpp new file mode 100644 index 00000000..abc3b107 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_page_table.cpp @@ -0,0 +1,1044 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::arch::arm64 { + + namespace { + + class AlignedMemoryBlock { + private: + uintptr_t m_before_start; + uintptr_t m_before_end; + uintptr_t m_after_start; + uintptr_t m_after_end; + size_t m_current_alignment; + public: + constexpr AlignedMemoryBlock(uintptr_t start, size_t num_pages, size_t alignment) : m_before_start(0), m_before_end(0), m_after_start(0), m_after_end(0), m_current_alignment(0) { + MESOSPHERE_ASSERT(util::IsAligned(start, PageSize)); + MESOSPHERE_ASSERT(num_pages > 0); + + /* Find an alignment that allows us to divide into at least two regions.*/ + uintptr_t start_page = start / PageSize; + alignment /= PageSize; + while (util::AlignUp(start_page, alignment) >= util::AlignDown(start_page + num_pages, alignment)) { + alignment = KPageTable::GetSmallerAlignment(alignment * PageSize) / PageSize; + } + + m_before_start = start_page; + m_before_end = util::AlignUp(start_page, alignment); + m_after_start = m_before_end; + m_after_end = start_page + num_pages; + m_current_alignment = alignment; + MESOSPHERE_ASSERT(m_current_alignment > 0); + } + + constexpr void SetAlignment(size_t alignment) { + /* We can only ever decrease the granularity. */ + MESOSPHERE_ASSERT(m_current_alignment >= alignment / PageSize); + m_current_alignment = alignment / PageSize; + } + + constexpr size_t GetAlignment() const { + return m_current_alignment * PageSize; + } + + constexpr void FindBlock(uintptr_t &out, size_t &num_pages) { + if ((m_after_end - m_after_start) >= m_current_alignment) { + /* Select aligned memory from after block. */ + const size_t available_pages = util::AlignDown(m_after_end, m_current_alignment) - m_after_start; + if (num_pages == 0 || available_pages < num_pages) { + num_pages = available_pages; + } + out = m_after_start * PageSize; + m_after_start += num_pages; + } else if ((m_before_end - m_before_start) >= m_current_alignment) { + /* Select aligned memory from before block. */ + const size_t available_pages = m_before_end - util::AlignUp(m_before_start, m_current_alignment); + if (num_pages == 0 || available_pages < num_pages) { + num_pages = available_pages; + } + m_before_end -= num_pages; + out = m_before_end * PageSize; + } else { + /* Neither after or before can get an aligned bit of memory. */ + out = 0; + num_pages = 0; + } + } + }; + + constexpr u64 EncodeTtbr(KPhysicalAddress table, u8 asid) { + return (static_cast<u64>(asid) << 48) | (static_cast<u64>(GetInteger(table))); + } + + } + + ALWAYS_INLINE void KPageTable::NoteUpdated() const { + cpu::DataSynchronizationBarrierInnerShareableStore(); + + /* Mark ourselves as in a tlb maintenance operation. */ + GetCurrentThread().SetInTlbMaintenanceOperation(); + ON_SCOPE_EXIT { GetCurrentThread().ClearInTlbMaintenanceOperation(); __asm__ __volatile__("" ::: "memory"); }; + + if (this->IsKernel()) { + this->OnKernelTableUpdated(); + } else { + this->OnTableUpdated(); + } + } + + ALWAYS_INLINE void KPageTable::NoteSingleKernelPageUpdated(KProcessAddress virt_addr) const { + MESOSPHERE_ASSERT(this->IsKernel()); + + cpu::DataSynchronizationBarrierInnerShareableStore(); + + /* Mark ourselves as in a tlb maintenance operation. */ + GetCurrentThread().SetInTlbMaintenanceOperation(); + ON_SCOPE_EXIT { GetCurrentThread().ClearInTlbMaintenanceOperation(); __asm__ __volatile__("" ::: "memory"); }; + + this->OnKernelTableSinglePageUpdated(virt_addr); + } + + + void KPageTable::Initialize(s32 core_id) { + /* Nothing actually needed here. */ + MESOSPHERE_UNUSED(core_id); + } + + Result KPageTable::InitializeForKernel(void *table, KVirtualAddress start, KVirtualAddress end) { + /* Initialize basic fields. */ + m_asid = 0; + m_manager = Kernel::GetSystemSystemResource().GetPageTableManagerPointer(); + + /* Initialize the base page table. */ + MESOSPHERE_R_ABORT_UNLESS(KPageTableBase::InitializeForKernel(true, table, start, end)); + + R_SUCCEED(); + } + + Result KPageTable::InitializeForProcess(ams::svc::CreateProcessFlag flags, bool from_back, KMemoryManager::Pool pool, KProcessAddress code_address, size_t code_size, KSystemResource *system_resource, KResourceLimit *resource_limit, size_t process_index) { + /* Determine our ASID */ + m_asid = process_index + 1; + MESOSPHERE_ABORT_UNLESS(0 < m_asid && m_asid < util::size(s_ttbr0_entries)); + + /* Set our manager. */ + m_manager = system_resource->GetPageTableManagerPointer(); + + /* Get the virtual address of our L1 table. */ + const KPhysicalAddress ttbr0_phys = KPhysicalAddress(s_ttbr0_entries[m_asid] & UINT64_C(0xFFFFFFFFFFFE)); + const KVirtualAddress ttbr0_virt = KMemoryLayout::GetLinearVirtualAddress(ttbr0_phys); + + /* Initialize our base table. */ + const size_t as_width = GetAddressSpaceWidth(flags); + const KProcessAddress as_start = 0; + const KProcessAddress as_end = (1ul << as_width); + R_TRY(KPageTableBase::InitializeForProcess(flags, from_back, pool, GetVoidPointer(ttbr0_virt), as_start, as_end, code_address, code_size, system_resource, resource_limit)); + + /* Note that we've updated the table (since we created it). */ + this->NoteUpdated(); + R_SUCCEED(); + } + + Result KPageTable::Finalize() { + /* Only process tables should be finalized. */ + MESOSPHERE_ASSERT(!this->IsKernel()); + + /* NOTE: Here Nintendo calls an unknown OnFinalize function. */ + /* this->OnFinalize(); */ + + /* Note that we've updated (to ensure we're synchronized). */ + this->NoteUpdated(); + + /* NOTE: Here Nintendo calls a second unknown OnFinalize function. */ + /* this->OnFinalize2(); */ + + /* Free all pages in the table. */ + { + /* Get implementation objects. */ + auto &impl = this->GetImpl(); + auto &mm = Kernel::GetMemoryManager(); + + /* Traverse, freeing all pages. */ + { + /* Begin the traversal. */ + TraversalContext context; + TraversalEntry entry; + + KPhysicalAddress cur_phys_addr = Null<KPhysicalAddress>; + size_t cur_size = 0; + u8 has_attr = 0; + + bool cur_valid = impl.BeginTraversal(std::addressof(entry), std::addressof(context), this->GetAddressSpaceStart()); + while (true) { + if (cur_valid) { + /* Free the actual pages, if there are any. */ + if (IsHeapPhysicalAddressForFinalize(entry.phys_addr)) { + if (cur_size > 0) { + /* NOTE: Nintendo really does check next_entry.attr == (cur_entry.attr != 0)...but attr is always zero as of 18.0.0, and this is "probably" for the new console or debug-only anyway, */ + /* so we'll implement the weird logic verbatim even though it doesn't match the GetContiguousRange logic. */ + if (entry.phys_addr == cur_phys_addr + cur_size && entry.attr == has_attr) { + /* Just extend the block, since we can. */ + cur_size += entry.block_size; + } else { + /* Close the block, and begin tracking anew. */ + mm.Close(cur_phys_addr, cur_size / PageSize); + + cur_phys_addr = entry.phys_addr; + cur_size = entry.block_size; + has_attr = entry.attr != 0; + } + } else { + cur_phys_addr = entry.phys_addr; + cur_size = entry.block_size; + has_attr = entry.attr != 0; + } + } + + /* Clean up the page table entries. */ + bool freeing_table = false; + while (true) { + /* Clear the entries. */ + const size_t num_to_clear = (!freeing_table && context.is_contiguous) ? BlocksPerContiguousBlock : 1; + auto *pte = reinterpret_cast<PageTableEntry *>(context.is_contiguous ? util::AlignDown(reinterpret_cast<uintptr_t>(context.level_entries[context.level]), BlocksPerContiguousBlock * sizeof(PageTableEntry)) : reinterpret_cast<uintptr_t>(context.level_entries[context.level])); + for (size_t i = 0; i < num_to_clear; ++i) { + pte[i] = InvalidPageTableEntry; + } + + /* Remove the entries from the previous table. */ + if (context.level != KPageTableImpl::EntryLevel_L1) { + context.level_entries[context.level + 1]->CloseTableReferences(num_to_clear); + } + + /* If we cleared a table, we need to note that we updated and free the table. */ + if (freeing_table) { + KVirtualAddress table = KVirtualAddress(util::AlignDown(reinterpret_cast<uintptr_t>(context.level_entries[context.level - 1]), PageSize)); + if (table == Null<KVirtualAddress>) { + break; + } + ClearPageTable(table); + this->GetPageTableManager().Free(table); + } + + /* Advance; we're no longer contiguous. */ + context.is_contiguous = false; + context.level_entries[context.level] = pte + num_to_clear - 1; + + /* We may have removed the last entries in a table, in which case we can free and unmap the tables. */ + if (context.level >= KPageTableImpl::EntryLevel_L1 || context.level_entries[context.level + 1]->GetTableReferenceCount() != 0) { + break; + } + + /* Advance; we will not be working with blocks any more. */ + context.level = static_cast<KPageTableImpl::EntryLevel>(util::ToUnderlying(context.level) + 1); + freeing_table = true; + } + } + + /* Continue the traversal. */ + cur_valid = impl.ContinueTraversal(std::addressof(entry), std::addressof(context)); + + if (entry.block_size == 0) { + break; + } + } + + /* Free any remaining pages. */ + if (cur_size > 0) { + mm.Close(cur_phys_addr, cur_size / PageSize); + } + } + + /* Clear the L1 table. */ + { + const KVirtualAddress l1_table = reinterpret_cast<uintptr_t>(impl.Finalize()); + ClearPageTable(l1_table); + } + + /* Perform inherited finalization. */ + KPageTableBase::Finalize(); + } + + R_SUCCEED(); + } + + Result KPageTable::OperateImpl(PageLinkedList *page_list, KProcessAddress virt_addr, size_t num_pages, KPhysicalAddress phys_addr, bool is_pa_valid, const KPageProperties properties, OperationType operation, bool reuse_ll) { + /* Check validity of parameters. */ + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + MESOSPHERE_ASSERT(num_pages > 0); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(virt_addr), PageSize)); + MESOSPHERE_ASSERT(this->ContainsPages(virt_addr, num_pages)); + + if (operation == OperationType_Map) { + MESOSPHERE_ABORT_UNLESS(is_pa_valid); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(phys_addr), PageSize)); + } else { + MESOSPHERE_ABORT_UNLESS(!is_pa_valid); + } + + if (operation == OperationType_Unmap) { + R_RETURN(this->Unmap(virt_addr, num_pages, page_list, false, reuse_ll)); + } else if (operation == OperationType_Separate) { + R_RETURN(this->SeparatePages(virt_addr, num_pages, page_list, reuse_ll)); + } else { + auto entry_template = this->GetEntryTemplate(properties); + + switch (operation) { + case OperationType_Map: + /* If mapping io or uncached pages, ensure that there is no pending reschedule. */ + if (properties.io || properties.uncached) { + KScopedSchedulerLock sl; + } + R_RETURN(this->MapContiguous(virt_addr, phys_addr, num_pages, entry_template, properties.disable_merge_attributes == DisableMergeAttribute_DisableHead, page_list, reuse_ll)); + case OperationType_ChangePermissions: + R_RETURN(this->ChangePermissions(virt_addr, num_pages, entry_template, properties.disable_merge_attributes, false, false, page_list, reuse_ll)); + case OperationType_ChangePermissionsAndRefresh: + R_RETURN(this->ChangePermissions(virt_addr, num_pages, entry_template, properties.disable_merge_attributes, true, false, page_list, reuse_ll)); + case OperationType_ChangePermissionsAndRefreshAndFlush: + R_RETURN(this->ChangePermissions(virt_addr, num_pages, entry_template, properties.disable_merge_attributes, true, true, page_list, reuse_ll)); + MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + Result KPageTable::OperateImpl(PageLinkedList *page_list, KProcessAddress virt_addr, size_t num_pages, const KPageGroup &page_group, const KPageProperties properties, OperationType operation, bool reuse_ll) { + /* Check validity of parameters. */ + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(virt_addr), PageSize)); + MESOSPHERE_ASSERT(num_pages > 0); + MESOSPHERE_ASSERT(num_pages == page_group.GetNumPages()); + + /* Map the page group. */ + auto entry_template = this->GetEntryTemplate(properties); + switch (operation) { + case OperationType_MapGroup: + case OperationType_MapFirstGroup: + /* If mapping io or uncached pages, ensure that there is no pending reschedule. */ + if (properties.io || properties.uncached) { + KScopedSchedulerLock sl; + } + R_RETURN(this->MapGroup(virt_addr, page_group, num_pages, entry_template, properties.disable_merge_attributes == DisableMergeAttribute_DisableHead, operation != OperationType_MapFirstGroup, page_list, reuse_ll)); + MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); + } + } + + Result KPageTable::Unmap(KProcessAddress virt_addr, size_t num_pages, PageLinkedList *page_list, bool force, bool reuse_ll) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + /* Ensure there are no pending data writes. */ + cpu::DataSynchronizationBarrier(); + + auto &impl = this->GetImpl(); + + /* If we're not forcing an unmap, separate pages immediately. */ + if (!force) { + R_TRY(this->SeparatePages(virt_addr, num_pages, page_list, reuse_ll)); + } + + /* Cache initial addresses for use on cleanup. */ + const KProcessAddress orig_virt_addr = virt_addr; + size_t remaining_pages = num_pages; + + /* Ensure that any pages we track close on exit. */ + KPageGroup pages_to_close(this->GetBlockInfoManager()); + ON_SCOPE_EXIT { pages_to_close.CloseAndReset(); }; + + /* Begin traversal. */ + TraversalContext context; + TraversalEntry next_entry; + bool next_valid = impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), virt_addr); + + while (remaining_pages > 0) { + /* Handle the case where we're not valid. */ + if (!next_valid) { + MESOSPHERE_ABORT_UNLESS(force); + const size_t cur_size = std::min(next_entry.block_size - (GetInteger(virt_addr) & (next_entry.block_size - 1)), remaining_pages * PageSize); + remaining_pages -= cur_size / PageSize; + virt_addr += cur_size; + next_valid = impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + continue; + } + + /* Handle the case where the block is bigger than it should be. */ + if (next_entry.block_size > remaining_pages * PageSize) { + MESOSPHERE_ABORT_UNLESS(force); + MESOSPHERE_R_ABORT_UNLESS(this->SeparatePagesImpl(std::addressof(next_entry), std::addressof(context), virt_addr, remaining_pages * PageSize, page_list, reuse_ll)); + } + + /* Check that our state is coherent. */ + MESOSPHERE_ASSERT((next_entry.block_size / PageSize) <= remaining_pages); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(next_entry.phys_addr), next_entry.block_size)); + + /* Unmap the block. */ + bool freeing_table = false; + bool need_recalculate_virt_addr = false; + while (true) { + /* Clear the entries. */ + const size_t num_to_clear = (!freeing_table && context.is_contiguous) ? BlocksPerContiguousBlock : 1; + auto *pte = reinterpret_cast<PageTableEntry *>(context.is_contiguous ? util::AlignDown(reinterpret_cast<uintptr_t>(context.level_entries[context.level]), BlocksPerContiguousBlock * sizeof(PageTableEntry)) : reinterpret_cast<uintptr_t>(context.level_entries[context.level])); + for (size_t i = 0; i < num_to_clear; ++i) { + pte[i] = InvalidPageTableEntry; + } + + /* Remove the entries from the previous table. */ + if (context.level != KPageTableImpl::EntryLevel_L1) { + context.level_entries[context.level + 1]->CloseTableReferences(num_to_clear); + } + + /* If we cleared a table, we need to note that we updated and free the table. */ + if (freeing_table) { + /* If there's no table, we also don't need to do a free. */ + const KVirtualAddress table = KVirtualAddress(util::AlignDown(reinterpret_cast<uintptr_t>(context.level_entries[context.level - 1]), PageSize)); + if (table == Null<KVirtualAddress>) { + break; + } + this->NoteUpdated(); + this->FreePageTable(page_list, table); + need_recalculate_virt_addr = true; + } + + /* Advance; we're no longer contiguous. */ + context.is_contiguous = false; + context.level_entries[context.level] = pte + num_to_clear - 1; + + /* We may have removed the last entries in a table, in which case we can free and unmap the tables. */ + if (context.level >= KPageTableImpl::EntryLevel_L1 || context.level_entries[context.level + 1]->GetTableReferenceCount() != 0) { + break; + } + + /* Advance; we will not be working with blocks any more. */ + context.level = static_cast<KPageTableImpl::EntryLevel>(util::ToUnderlying(context.level) + 1); + freeing_table = true; + } + + /* Close the blocks. */ + if (!force && IsHeapPhysicalAddress(next_entry.phys_addr)) { + const size_t block_num_pages = next_entry.block_size / PageSize; + if (R_FAILED(pages_to_close.AddBlock(next_entry.phys_addr, block_num_pages))) { + this->NoteUpdated(); + Kernel::GetMemoryManager().Close(next_entry.phys_addr, block_num_pages); + pages_to_close.CloseAndReset(); + } + } + + /* Advance. */ + size_t freed_size = next_entry.block_size; + if (need_recalculate_virt_addr) { + /* We advanced more than by the block, so we need to calculate the actual advanced size. */ + const size_t block_size = impl.GetBlockSize(context.level, context.is_contiguous); + const KProcessAddress new_virt_addr = util::AlignDown(GetInteger(impl.GetAddressForContext(std::addressof(context))) + block_size, block_size); + MESOSPHERE_ABORT_UNLESS(new_virt_addr >= virt_addr + next_entry.block_size); + + freed_size = std::min<size_t>(new_virt_addr - virt_addr, remaining_pages * PageSize); + } + + /* We can just advance by the block size. */ + virt_addr += freed_size; + remaining_pages -= freed_size / PageSize; + + next_valid = impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + } + + /* Ensure we remain coherent. */ + if (this->IsKernel() && num_pages == 1) { + this->NoteSingleKernelPageUpdated(orig_virt_addr); + } else { + this->NoteUpdated(); + } + + R_SUCCEED(); + } + + Result KPageTable::Map(KProcessAddress virt_addr, KPhysicalAddress phys_addr, size_t num_pages, PageTableEntry entry_template, bool disable_head_merge, size_t page_size, PageLinkedList *page_list, bool reuse_ll) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(virt_addr), PageSize)); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(phys_addr), PageSize)); + + auto &impl = this->GetImpl(); + u8 sw_reserved_bits = PageTableEntry::EncodeSoftwareReservedBits(disable_head_merge, false, false); + + /* Begin traversal. */ + TraversalContext context; + TraversalEntry entry; + bool valid = impl.BeginTraversal(std::addressof(entry), std::addressof(context), virt_addr); + + /* Iterate, mapping each page. */ + while (num_pages > 0) { + /* If we're mapping at the address, there must be nothing there. */ + MESOSPHERE_ABORT_UNLESS(!valid); + + /* If we fail, clean up any empty tables we may have allocated. */ + ON_RESULT_FAILURE { + /* Remove entries for and free any tables. */ + while (context.level < KPageTableImpl::EntryLevel_L1) { + /* If the higher-level table has entries, we don't need to do a free. */ + if (context.level_entries[context.level + 1]->GetTableReferenceCount() != 0) { + break; + } + + /* If there's no table, we also don't need to do a free. */ + const KVirtualAddress table = KVirtualAddress(util::AlignDown(reinterpret_cast<uintptr_t>(context.level_entries[context.level]), PageSize)); + if (table == Null<KVirtualAddress>) { + break; + } + + /* Clear the entry for the table we're removing. */ + *context.level_entries[context.level + 1] = InvalidPageTableEntry; + + /* Remove the entry for the table one level higher. */ + if (context.level + 1 < KPageTableImpl::EntryLevel_L1) { + context.level_entries[context.level + 2]->CloseTableReferences(1); + } + + /* Advance our level. */ + context.level = static_cast<KPageTableImpl::EntryLevel>(util::ToUnderlying(context.level) + 1); + + /* Note that we performed an update and free the table. */ + this->NoteUpdated(); + this->FreePageTable(page_list, table); + } + }; + + /* If necessary, allocate page tables for the entry. */ + size_t mapping_size = entry.block_size; + while (mapping_size > page_size) { + /* Allocate the table. */ + const auto table = AllocatePageTable(page_list, reuse_ll); + R_UNLESS(table != Null<KVirtualAddress>, svc::ResultOutOfResource()); + + /* Wait for pending stores to complete. */ + cpu::DataSynchronizationBarrierInnerShareableStore(); + + /* Update the block entry to be a table entry. */ + *context.level_entries[context.level] = PageTableEntry(PageTableEntry::TableTag{}, KPageTable::GetPageTablePhysicalAddress(table), this->IsKernel(), true, 0); + + /* Add the entry to the table containing this one. */ + if (context.level != KPageTableImpl::EntryLevel_L1) { + context.level_entries[context.level + 1]->OpenTableReferences(1); + } + + /* Decrease our level. */ + context.level = static_cast<KPageTableImpl::EntryLevel>(util::ToUnderlying(context.level) - 1); + + /* Add our new entry to the context. */ + context.level_entries[context.level] = GetPointer<PageTableEntry>(table) + impl.GetLevelIndex(virt_addr, context.level); + + /* Update our mapping size. */ + mapping_size = impl.GetBlockSize(context.level); + } + + /* Determine how many pages we can set up on this iteration. */ + const size_t block_size = impl.GetBlockSize(context.level); + const size_t max_ptes = (context.level == KPageTableImpl::EntryLevel_L1 ? impl.GetNumL1Entries() : BlocksPerTable) - ((reinterpret_cast<uintptr_t>(context.level_entries[context.level]) / sizeof(PageTableEntry)) & (BlocksPerTable - 1)); + const size_t max_pages = (block_size * max_ptes) / PageSize; + + const size_t cur_pages = std::min(max_pages, num_pages); + + /* Determine the new base attribute. */ + const bool contig = page_size >= BlocksPerContiguousBlock * mapping_size; + + const size_t num_ptes = cur_pages / (block_size / PageSize); + auto *pte = context.level_entries[context.level]; + for (size_t i = 0; i < num_ptes; ++i) { + pte[i] = PageTableEntry(PageTableEntry::BlockTag{}, phys_addr + i * block_size, entry_template, sw_reserved_bits, contig, context.level == KPageTableImpl::EntryLevel_L3); + sw_reserved_bits &= ~(PageTableEntry::SoftwareReservedBit_DisableMergeHead); + } + + /* Add the entries to the table containing this one. */ + if (context.level != KPageTableImpl::EntryLevel_L1) { + context.level_entries[context.level + 1]->OpenTableReferences(num_ptes); + } + + /* Update our context. */ + context.is_contiguous = contig; + context.level_entries[context.level] = pte + num_ptes - (contig ? BlocksPerContiguousBlock : 1); + + /* Advance our addresses. */ + phys_addr += cur_pages * PageSize; + virt_addr += cur_pages * PageSize; + num_pages -= cur_pages; + + /* Continue traversal. */ + valid = impl.ContinueTraversal(std::addressof(entry), std::addressof(context)); + } + + /* We mapped, so wait for our writes to take. */ + cpu::DataSynchronizationBarrierInnerShareableStore(); + + R_SUCCEED(); + + } + + Result KPageTable::MapContiguous(KProcessAddress virt_addr, KPhysicalAddress phys_addr, size_t num_pages, PageTableEntry entry_template, bool disable_head_merge, PageLinkedList *page_list, bool reuse_ll) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + /* Cache initial addresses for use on cleanup. */ + const KProcessAddress orig_virt_addr = virt_addr; + const KPhysicalAddress orig_phys_addr = phys_addr; + + size_t remaining_pages = num_pages; + + /* Map the pages, using a guard to ensure we don't leak. */ + { + ON_RESULT_FAILURE { MESOSPHERE_R_ABORT_UNLESS(this->Unmap(orig_virt_addr, num_pages, page_list, true, true)); }; + + if (num_pages < ContiguousPageSize / PageSize) { + R_TRY(this->Map(virt_addr, phys_addr, num_pages, entry_template, disable_head_merge && virt_addr == orig_virt_addr, L3BlockSize, page_list, reuse_ll)); + remaining_pages -= num_pages; + virt_addr += num_pages * PageSize; + phys_addr += num_pages * PageSize; + } else { + /* Map the fractional part of the pages. */ + size_t alignment; + for (alignment = ContiguousPageSize; (virt_addr & (alignment - 1)) == (phys_addr & (alignment - 1)); alignment = GetLargerAlignment(alignment)) { + /* Check if this would be our last map. */ + const size_t pages_to_map = ((alignment - (virt_addr & (alignment - 1))) & (alignment - 1)) / PageSize; + if (pages_to_map + (alignment / PageSize) > remaining_pages) { + break; + } + + /* Map pages, if we should. */ + if (pages_to_map > 0) { + R_TRY(this->Map(virt_addr, phys_addr, pages_to_map, entry_template, disable_head_merge && virt_addr == orig_virt_addr, GetSmallerAlignment(alignment), page_list, reuse_ll)); + remaining_pages -= pages_to_map; + virt_addr += pages_to_map * PageSize; + phys_addr += pages_to_map * PageSize; + } + + /* Don't go further than L1 block. */ + if (alignment == L1BlockSize) { + break; + } + } + + while (remaining_pages > 0) { + /* Select the next smallest alignment. */ + alignment = GetSmallerAlignment(alignment); + MESOSPHERE_ASSERT((virt_addr & (alignment - 1)) == 0); + MESOSPHERE_ASSERT((phys_addr & (alignment - 1)) == 0); + + /* Map pages, if we should. */ + const size_t pages_to_map = util::AlignDown(remaining_pages, alignment / PageSize); + if (pages_to_map > 0) { + R_TRY(this->Map(virt_addr, phys_addr, pages_to_map, entry_template, disable_head_merge && virt_addr == orig_virt_addr, alignment, page_list, reuse_ll)); + remaining_pages -= pages_to_map; + virt_addr += pages_to_map * PageSize; + phys_addr += pages_to_map * PageSize; + } + } + } + } + + /* Perform what coalescing we can. */ + this->MergePages(orig_virt_addr, num_pages, page_list); + + /* Open references to the pages, if we should. */ + if (IsHeapPhysicalAddress(orig_phys_addr)) { + Kernel::GetMemoryManager().Open(orig_phys_addr, num_pages); + } + + R_SUCCEED(); + } + + Result KPageTable::MapGroup(KProcessAddress virt_addr, const KPageGroup &pg, size_t num_pages, PageTableEntry entry_template, bool disable_head_merge, bool not_first, PageLinkedList *page_list, bool reuse_ll) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + /* We want to maintain a new reference to every page in the group. */ + KScopedPageGroup spg(pg, not_first); + + /* Cache initial address for use on cleanup. */ + const KProcessAddress orig_virt_addr = virt_addr; + + size_t mapped_pages = 0; + + /* Map the pages, using a guard to ensure we don't leak. */ + { + ON_RESULT_FAILURE { MESOSPHERE_R_ABORT_UNLESS(this->Unmap(orig_virt_addr, num_pages, page_list, true, true)); }; + + if (num_pages < ContiguousPageSize / PageSize) { + for (const auto &block : pg) { + const KPhysicalAddress block_phys_addr = block.GetAddress(); + const size_t cur_pages = block.GetNumPages(); + R_TRY(this->Map(virt_addr, block_phys_addr, cur_pages, entry_template, disable_head_merge && virt_addr == orig_virt_addr, L3BlockSize, page_list, reuse_ll)); + + virt_addr += cur_pages * PageSize; + mapped_pages += cur_pages; + } + } else { + /* Create a block representing our virtual space. */ + AlignedMemoryBlock virt_block(GetInteger(virt_addr), num_pages, L1BlockSize); + for (const auto &block : pg) { + /* Create a block representing this physical group, synchronize its alignment to our virtual block. */ + const KPhysicalAddress block_phys_addr = block.GetAddress(); + size_t cur_pages = block.GetNumPages(); + + AlignedMemoryBlock phys_block(GetInteger(block_phys_addr), cur_pages, virt_block.GetAlignment()); + virt_block.SetAlignment(phys_block.GetAlignment()); + + while (cur_pages > 0) { + /* Find a physical region for us to map at. */ + uintptr_t phys_choice = 0; + size_t phys_pages = 0; + phys_block.FindBlock(phys_choice, phys_pages); + + /* If we didn't find a region, try decreasing our alignment. */ + if (phys_pages == 0) { + const size_t next_alignment = KPageTable::GetSmallerAlignment(phys_block.GetAlignment()); + MESOSPHERE_ASSERT(next_alignment >= PageSize); + phys_block.SetAlignment(next_alignment); + virt_block.SetAlignment(next_alignment); + continue; + } + + /* Begin choosing virtual blocks to map at the region we chose. */ + while (phys_pages > 0) { + /* Find a virtual region for us to map at. */ + uintptr_t virt_choice = 0; + size_t virt_pages = phys_pages; + virt_block.FindBlock(virt_choice, virt_pages); + + /* If we didn't find a region, try decreasing our alignment. */ + if (virt_pages == 0) { + const size_t next_alignment = KPageTable::GetSmallerAlignment(virt_block.GetAlignment()); + MESOSPHERE_ASSERT(next_alignment >= PageSize); + phys_block.SetAlignment(next_alignment); + virt_block.SetAlignment(next_alignment); + continue; + } + + /* Map! */ + R_TRY(this->Map(virt_choice, phys_choice, virt_pages, entry_template, disable_head_merge && virt_addr == orig_virt_addr, virt_block.GetAlignment(), page_list, reuse_ll)); + + /* Advance. */ + phys_choice += virt_pages * PageSize; + phys_pages -= virt_pages; + cur_pages -= virt_pages; + mapped_pages += virt_pages; + } + } + } + } + } + MESOSPHERE_ASSERT(mapped_pages == num_pages); + + /* Perform what coalescing we can. */ + this->MergePages(orig_virt_addr, num_pages, page_list); + + /* We succeeded! We want to persist the reference to the pages. */ + spg.CancelClose(); + R_SUCCEED(); + } + + bool KPageTable::MergePages(TraversalContext *context, PageLinkedList *page_list) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + auto &impl = this->GetImpl(); + + /* Iteratively merge, until we can't. */ + bool merged = false; + while (true) { + /* Try to merge. */ + KVirtualAddress freed_table = Null<KVirtualAddress>; + if (!impl.MergePages(std::addressof(freed_table), context, &KPageTable::NoteUpdatedCallback, this)) { + break; + } + + /* Free the page. */ + if (freed_table != Null<KVirtualAddress>) { + ClearPageTable(freed_table); + this->FreePageTable(page_list, freed_table); + } + + /* We performed at least one merge. */ + merged = true; + } + + return merged; + } + + void KPageTable::MergePages(KProcessAddress virt_addr, size_t num_pages, PageLinkedList *page_list) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + auto &impl = this->GetImpl(); + + /* Begin traversal. */ + TraversalContext context; + TraversalEntry entry; + MESOSPHERE_ABORT_UNLESS(impl.BeginTraversal(std::addressof(entry), std::addressof(context), virt_addr)); + + /* Merge start of the range. */ + this->MergePages(std::addressof(context), page_list); + + /* If we have more than one page, do the same for the end of the range. */ + if (num_pages > 1) { + /* Begin traversal for end of range. */ + const size_t size = num_pages * PageSize; + const auto end_page = virt_addr + size; + const auto last_page = end_page - PageSize; + MESOSPHERE_ABORT_UNLESS(impl.BeginTraversal(std::addressof(entry), std::addressof(context), last_page)); + + /* Merge. */ + this->MergePages(std::addressof(context), page_list); + } + } + + Result KPageTable::SeparatePagesImpl(TraversalEntry *entry, TraversalContext *context, KProcessAddress virt_addr, size_t block_size, PageLinkedList *page_list, bool reuse_ll) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + auto &impl = this->GetImpl(); + + /* If at any point we fail, we want to merge. */ + ON_RESULT_FAILURE { this->MergePages(context, page_list); }; + + /* Iterate, separating until our block size is small enough. */ + while (entry->block_size > block_size) { + /* If necessary, allocate a table. */ + KVirtualAddress table = Null<KVirtualAddress>; + if (!context->is_contiguous) { + table = this->AllocatePageTable(page_list, reuse_ll); + R_UNLESS(table != Null<KVirtualAddress>, svc::ResultOutOfResource()); + } + + /* Separate. */ + impl.SeparatePages(entry, context, virt_addr, GetPointer<PageTableEntry>(table), &KPageTable::NoteUpdatedCallback, this); + } + + R_SUCCEED(); + } + + Result KPageTable::SeparatePages(KProcessAddress virt_addr, size_t num_pages, PageLinkedList *page_list, bool reuse_ll) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + auto &impl = this->GetImpl(); + + /* Begin traversal. */ + TraversalContext start_context; + TraversalEntry entry; + MESOSPHERE_ABORT_UNLESS(impl.BeginTraversal(std::addressof(entry), std::addressof(start_context), virt_addr)); + + /* Separate pages at the start of the range. */ + const size_t size = num_pages * PageSize; + R_TRY(this->SeparatePagesImpl(std::addressof(entry), std::addressof(start_context), virt_addr, std::min(util::GetAlignment(GetInteger(virt_addr)), size), page_list, reuse_ll)); + + /* If necessary, separate pages at the end of the range. */ + if (num_pages > 1) { + const auto end_page = virt_addr + size; + const auto last_page = end_page - PageSize; + + /* Begin traversal. */ + TraversalContext end_context; + MESOSPHERE_ABORT_UNLESS(impl.BeginTraversal(std::addressof(entry), std::addressof(end_context), last_page)); + + + ON_RESULT_FAILURE { this->MergePages(std::addressof(start_context), page_list); }; + + R_TRY(this->SeparatePagesImpl(std::addressof(entry), std::addressof(end_context), last_page, std::min(util::GetAlignment(GetInteger(end_page)), size), page_list, reuse_ll)); + } + + R_SUCCEED(); + } + + Result KPageTable::ChangePermissions(KProcessAddress virt_addr, size_t num_pages, PageTableEntry entry_template, DisableMergeAttribute disable_merge_attr, bool refresh_mapping, bool flush_mapping, PageLinkedList *page_list, bool reuse_ll) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + /* Ensure there are no pending data writes. */ + cpu::DataSynchronizationBarrier(); + + /* Separate pages before we change permissions. */ + R_TRY(this->SeparatePages(virt_addr, num_pages, page_list, reuse_ll)); + + /* ===================================================== */ + + /* Define a helper function which will apply our template to entries. */ + + enum ApplyOption : u32 { + ApplyOption_None = 0, + ApplyOption_FlushDataCache = (1u << 0), + ApplyOption_MergeMappings = (1u << 1), + }; + + auto ApplyEntryTemplate = [this, virt_addr, disable_merge_attr, num_pages, page_list](PageTableEntry entry_template, u32 apply_option) -> void { + /* Create work variables for us to use. */ + const KProcessAddress orig_virt_addr = virt_addr; + const KProcessAddress end_virt_addr = orig_virt_addr + (num_pages * PageSize); + KProcessAddress cur_virt_addr = virt_addr; + size_t remaining_pages = num_pages; + + auto &impl = this->GetImpl(); + + /* Parse the disable merge attrs. */ + const bool attr_disable_head = (disable_merge_attr & DisableMergeAttribute_DisableHead) != 0; + const bool attr_disable_head_body = (disable_merge_attr & DisableMergeAttribute_DisableHeadAndBody) != 0; + const bool attr_enable_head_body = (disable_merge_attr & DisableMergeAttribute_EnableHeadAndBody) != 0; + const bool attr_disable_tail = (disable_merge_attr & DisableMergeAttribute_DisableTail) != 0; + const bool attr_enable_tail = (disable_merge_attr & DisableMergeAttribute_EnableTail) != 0; + const bool attr_enable_and_merge = (disable_merge_attr & DisableMergeAttribute_EnableAndMergeHeadBodyTail) != 0; + + /* Begin traversal. */ + TraversalContext context; + TraversalEntry next_entry; + MESOSPHERE_ABORT_UNLESS(impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), cur_virt_addr)); + + /* Continue changing properties until we've changed them for all pages. */ + bool cleared_disable_merge_bits = false; + while (remaining_pages > 0) { + MESOSPHERE_ABORT_UNLESS(util::IsAligned(GetInteger(next_entry.phys_addr), next_entry.block_size)); + MESOSPHERE_ABORT_UNLESS(next_entry.block_size <= remaining_pages * PageSize); + + /* Determine if we're at the start. */ + const bool is_start = (cur_virt_addr == orig_virt_addr); + const bool is_end = ((cur_virt_addr + next_entry.block_size) == end_virt_addr); + + /* Determine the relevant merge attributes. */ + bool disable_head_merge, disable_head_body_merge, disable_tail_merge; + if (next_entry.IsHeadMergeDisabled()) { + disable_head_merge = true; + } else if (attr_disable_head) { + disable_head_merge = is_start; + } else { + disable_head_merge = false; + } + if (is_start) { + if (attr_disable_head_body) { + disable_head_body_merge = true; + } else if (attr_enable_head_body) { + disable_head_body_merge = false; + } else { + disable_head_body_merge = (!attr_enable_and_merge && next_entry.IsHeadAndBodyMergeDisabled()); + } + } else { + disable_head_body_merge = (!attr_enable_and_merge && next_entry.IsHeadAndBodyMergeDisabled()); + cleared_disable_merge_bits |= (attr_enable_and_merge && next_entry.IsHeadAndBodyMergeDisabled()); + } + if (is_end) { + if (attr_disable_tail) { + disable_tail_merge = true; + } else if (attr_enable_tail) { + disable_tail_merge = false; + } else { + disable_tail_merge = (!attr_enable_and_merge && next_entry.IsTailMergeDisabled()); + } + } else { + disable_tail_merge = (!attr_enable_and_merge && next_entry.IsTailMergeDisabled()); + cleared_disable_merge_bits |= (attr_enable_and_merge && next_entry.IsTailMergeDisabled()); + } + + /* Encode the merge disable flags into the software reserved bits. */ + u8 sw_reserved_bits = PageTableEntry::EncodeSoftwareReservedBits(disable_head_merge, disable_head_body_merge, disable_tail_merge); + + /* If we should flush entries, do so. */ + if ((apply_option & ApplyOption_FlushDataCache) != 0) { + if (IsHeapPhysicalAddress(next_entry.phys_addr)) { + cpu::FlushDataCache(GetVoidPointer(GetHeapVirtualAddress(next_entry.phys_addr)), next_entry.block_size); + } + } + + /* Apply the entry template. */ + { + const size_t num_entries = context.is_contiguous ? BlocksPerContiguousBlock : 1; + + auto * const pte = context.level_entries[context.level]; + const size_t block_size = impl.GetBlockSize(context.level); + for (size_t i = 0; i < num_entries; ++i) { + pte[i] = PageTableEntry(PageTableEntry::BlockTag{}, next_entry.phys_addr + i * block_size, entry_template, sw_reserved_bits, context.is_contiguous, context.level == KPageTableImpl::EntryLevel_L3); + sw_reserved_bits &= ~(PageTableEntry::SoftwareReservedBit_DisableMergeHead); + } + } + + /* If our option asks us to, try to merge mappings. */ + bool merge = ((apply_option & ApplyOption_MergeMappings) != 0 || cleared_disable_merge_bits) && next_entry.block_size < L1BlockSize; + if (merge) { + const size_t larger_align = GetLargerAlignment(next_entry.block_size); + if (util::IsAligned(GetInteger(cur_virt_addr) + next_entry.block_size, larger_align)) { + const uintptr_t aligned_start = util::AlignDown(GetInteger(cur_virt_addr), larger_align); + if (orig_virt_addr <= aligned_start && aligned_start + larger_align - 1 < GetInteger(orig_virt_addr) + (num_pages * PageSize) - 1) { + merge = this->MergePages(std::addressof(context), page_list); + } else { + merge = false; + } + } else { + merge = false; + } + } + + /* If we merged, correct the traversal to a sane state. */ + if (merge) { + /* NOTE: Begin a new traversal, now that we've merged. */ + MESOSPHERE_ABORT_UNLESS(impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), cur_virt_addr)); + + /* The actual size needs to not take into account the portion of the block before our virtual address. */ + const size_t actual_size = next_entry.block_size - (GetInteger(next_entry.phys_addr) & (next_entry.block_size - 1)); + remaining_pages -= std::min(remaining_pages, actual_size / PageSize); + cur_virt_addr += actual_size; + } else { + /* If we didn't merge, just advance. */ + remaining_pages -= next_entry.block_size / PageSize; + cur_virt_addr += next_entry.block_size; + } + + /* Continue our traversal. */ + if (remaining_pages == 0) { + break; + } + MESOSPHERE_ABORT_UNLESS(impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context))); + } + }; + + + /* ===================================================== */ + + /* If we don't need to refresh the pages, we can just apply the mappings. */ + if (!refresh_mapping) { + ApplyEntryTemplate(entry_template, ApplyOption_None); + this->NoteUpdated(); + } else { + /* We need to refresh the mappings. */ + /* First, apply the changes without the mapped bit. This will cause all entries to page fault if accessed. */ + { + PageTableEntry unmapped_template = entry_template; + unmapped_template.SetMapped(false); + ApplyEntryTemplate(unmapped_template, ApplyOption_MergeMappings); + this->NoteUpdated(); + } + + /* Next, take and immediately release the scheduler lock. This will force a reschedule. */ + { + KScopedSchedulerLock sl; + } + + /* Finally, apply the changes as directed, flushing the mappings before they're applied (if we should). */ + ApplyEntryTemplate(entry_template, flush_mapping ? ApplyOption_FlushDataCache : ApplyOption_None); + + /* Wait for pending stores to complete. */ + cpu::DataSynchronizationBarrierInnerShareableStore(); + } + + /* We've succeeded, now perform what coalescing we can. */ + this->MergePages(virt_addr, num_pages, page_list); + + R_SUCCEED(); + } + + void KPageTable::FinalizeUpdateImpl(PageLinkedList *page_list) { + while (page_list->Peek()) { + KVirtualAddress page = KVirtualAddress(page_list->Pop()); + MESOSPHERE_ASSERT(this->GetPageTableManager().IsInPageTableHeap(page)); + MESOSPHERE_ASSERT(this->GetPageTableManager().GetRefCount(page) == 0); + this->GetPageTableManager().Free(page); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_page_table_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_page_table_impl.cpp new file mode 100644 index 00000000..5ce64e3f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_page_table_impl.cpp @@ -0,0 +1,540 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::arch::arm64 { + + void KPageTableImpl::InitializeForKernel(void *tb, KVirtualAddress start, KVirtualAddress end) { + m_table = static_cast<L1PageTableEntry *>(tb); + m_is_kernel = true; + m_num_entries = util::AlignUp(end - start, L1BlockSize) / L1BlockSize; + + /* Page table entries created by KInitialPageTable need to be iterated and modified to ensure KPageTable invariants. */ + PageTableEntry *level_entries[EntryLevel_Count] = { nullptr, nullptr, m_table }; + u32 level = EntryLevel_L1; + while (level != EntryLevel_L1 || (level_entries[EntryLevel_L1] - static_cast<PageTableEntry *>(m_table)) < m_num_entries) { + /* Get the pte; it must never have the validity-extension flag set. */ + auto *pte = level_entries[level]; + MESOSPHERE_ASSERT((pte->GetSoftwareReservedBits() & PageTableEntry::SoftwareReservedBit_Valid) == 0); + + /* While we're a table, recurse, fixing up the reference counts. */ + while (level > EntryLevel_L3 && pte->IsMappedTable()) { + /* Count how many references are in the table. */ + auto *table = GetPointer<PageTableEntry>(GetPageTableVirtualAddress(pte->GetTable())); + + size_t ref_count = 0; + for (size_t i = 0; i < BlocksPerTable; ++i) { + if (table[i].IsMapped()) { + ++ref_count; + } + } + + /* Set the reference count for our new page, adding one additional uncloseable reference; kernel pages must never be unreferenced. */ + pte->SetTableReferenceCount(ref_count + 1).SetValid(); + + /* Iterate downwards. */ + level -= 1; + level_entries[level] = table; + pte = level_entries[level]; + + /* Check that the entry isn't unexpected. */ + MESOSPHERE_ASSERT((pte->GetSoftwareReservedBits() & PageTableEntry::SoftwareReservedBit_Valid) == 0); + } + + /* We're dealing with some block. If it's mapped, set it valid. */ + if (pte->IsMapped()) { + pte->SetValid(); + } + + /* Advance. */ + while (true) { + /* Advance to the next entry at the current level. */ + if (!util::IsAligned(reinterpret_cast<uintptr_t>(++level_entries[level]), PageSize)) { + break; + } + + /* If we're at the end of a level, advance upwards. */ + level_entries[level++] = nullptr; + + if (level > EntryLevel_L1) { + return; + } + } + } + } + + void KPageTableImpl::InitializeForProcess(void *tb, KVirtualAddress start, KVirtualAddress end) { + m_table = static_cast<L1PageTableEntry *>(tb); + m_is_kernel = false; + m_num_entries = util::AlignUp(end - start, L1BlockSize) / L1BlockSize; + } + + L1PageTableEntry *KPageTableImpl::Finalize() { + return m_table; + } + + bool KPageTableImpl::BeginTraversal(TraversalEntry *out_entry, TraversalContext *out_context, KProcessAddress address) const { + /* Setup invalid defaults. */ + *out_entry = {}; + *out_context = {}; + + /* Validate that we can read the actual entry. */ + const size_t l0_index = GetL0Index(address); + const size_t l1_index = GetL1Index(address); + if (m_is_kernel) { + /* Kernel entries must be accessed via TTBR1. */ + if ((l0_index != MaxPageTableEntries - 1) || (l1_index < MaxPageTableEntries - m_num_entries)) { + return false; + } + } else { + /* User entries must be accessed with TTBR0. */ + if ((l0_index != 0) || l1_index >= m_num_entries) { + return false; + } + } + + /* Get the L1 entry, and check if it's a table. */ + out_context->level_entries[EntryLevel_L1] = this->GetL1Entry(address); + if (out_context->level_entries[EntryLevel_L1]->IsMappedTable()) { + /* Get the L2 entry, and check if it's a table. */ + out_context->level_entries[EntryLevel_L2] = this->GetL2EntryFromTable(GetPageTableVirtualAddress(out_context->level_entries[EntryLevel_L1]->GetTable()), address); + if (out_context->level_entries[EntryLevel_L2]->IsMappedTable()) { + /* Get the L3 entry. */ + out_context->level_entries[EntryLevel_L3] = this->GetL3EntryFromTable(GetPageTableVirtualAddress(out_context->level_entries[EntryLevel_L2]->GetTable()), address); + + /* It's either a page or not. */ + out_context->level = EntryLevel_L3; + } else { + /* Not a L2 table, so possibly an L2 block. */ + out_context->level = EntryLevel_L2; + } + } else { + /* Not a L1 table, so possibly an L1 block. */ + out_context->level = EntryLevel_L1; + } + + /* Determine other fields. */ + const auto *pte = out_context->level_entries[out_context->level]; + + out_context->is_contiguous = pte->IsContiguous(); + + out_entry->sw_reserved_bits = pte->GetSoftwareReservedBits(); + out_entry->attr = 0; + out_entry->phys_addr = this->GetBlock(pte, out_context->level) + this->GetOffset(address, out_context->level); + out_entry->block_size = static_cast<size_t>(1) << (PageBits + LevelBits * out_context->level + 4 * out_context->is_contiguous); + + return out_context->level == EntryLevel_L3 ? pte->IsPage() : pte->IsBlock(); + } + + bool KPageTableImpl::ContinueTraversal(TraversalEntry *out_entry, TraversalContext *context) const { + /* Advance entry. */ + auto *cur_pte = context->level_entries[context->level]; + auto *next_pte = reinterpret_cast<PageTableEntry *>(context->is_contiguous ? util::AlignDown(reinterpret_cast<uintptr_t>(cur_pte), BlocksPerContiguousBlock * sizeof(PageTableEntry)) + BlocksPerContiguousBlock * sizeof(PageTableEntry) : reinterpret_cast<uintptr_t>(cur_pte) + sizeof(PageTableEntry)); + + /* Set the pte. */ + context->level_entries[context->level] = next_pte; + + /* Advance appropriately. */ + while (context->level < EntryLevel_L1 && util::IsAligned(reinterpret_cast<uintptr_t>(context->level_entries[context->level]), PageSize)) { + /* Advance the above table by one entry. */ + context->level_entries[context->level + 1]++; + context->level = static_cast<EntryLevel>(util::ToUnderlying(context->level) + 1); + } + + /* Check if we've hit the end of the L1 table. */ + if (context->level == EntryLevel_L1) { + if (context->level_entries[EntryLevel_L1] - static_cast<const PageTableEntry *>(m_table) >= m_num_entries) { + *context = {}; + *out_entry = {}; + return false; + } + } + + /* We may have advanced to a new table, and if we have we should descend. */ + while (context->level > EntryLevel_L3 && context->level_entries[context->level]->IsMappedTable()) { + context->level_entries[context->level - 1] = GetPointer<PageTableEntry>(GetPageTableVirtualAddress(context->level_entries[context->level]->GetTable())); + context->level = static_cast<EntryLevel>(util::ToUnderlying(context->level) - 1); + } + + const auto *pte = context->level_entries[context->level]; + + context->is_contiguous = pte->IsContiguous(); + + out_entry->sw_reserved_bits = pte->GetSoftwareReservedBits(); + out_entry->attr = 0; + out_entry->phys_addr = this->GetBlock(pte, context->level); + out_entry->block_size = static_cast<size_t>(1) << (PageBits + LevelBits * context->level + 4 * context->is_contiguous); + return context->level == EntryLevel_L3 ? pte->IsPage() : pte->IsBlock(); + } + + bool KPageTableImpl::GetPhysicalAddress(KPhysicalAddress *out, KProcessAddress address) const { + /* Validate that we can read the actual entry. */ + const size_t l0_index = GetL0Index(address); + const size_t l1_index = GetL1Index(address); + if (m_is_kernel) { + /* Kernel entries must be accessed via TTBR1. */ + if ((l0_index != MaxPageTableEntries - 1) || (l1_index < MaxPageTableEntries - m_num_entries)) { + return false; + } + } else { + /* User entries must be accessed with TTBR0. */ + if ((l0_index != 0) || l1_index >= m_num_entries) { + return false; + } + } + + /* Get the L1 entry, and check if it's a table. */ + const PageTableEntry *pte = this->GetL1Entry(address); + EntryLevel level = EntryLevel_L1; + if (pte->IsMappedTable()) { + /* Get the L2 entry, and check if it's a table. */ + pte = this->GetL2EntryFromTable(GetPageTableVirtualAddress(pte->GetTable()), address); + level = EntryLevel_L2; + if (pte->IsMappedTable()) { + pte = this->GetL3EntryFromTable(GetPageTableVirtualAddress(pte->GetTable()), address); + level = EntryLevel_L3; + } + } + + const bool is_block = level == EntryLevel_L3 ? pte->IsPage() : pte->IsBlock(); + if (is_block) { + *out = this->GetBlock(pte, level) + this->GetOffset(address, level); + } else { + *out = Null<KPhysicalAddress>; + } + + return is_block; + } + + bool KPageTableImpl::MergePages(KVirtualAddress *out, TraversalContext *context, EntryUpdatedCallback on_entry_updated, const void *pt) { + /* We want to upgrade the pages by one step. */ + if (context->is_contiguous) { + /* We can't merge an L1 table. */ + if (context->level == EntryLevel_L1) { + return false; + } + + /* We want to upgrade a contiguous mapping in a table to a block. */ + PageTableEntry *pte = reinterpret_cast<PageTableEntry *>(util::AlignDown(reinterpret_cast<uintptr_t>(context->level_entries[context->level]), BlocksPerTable * sizeof(PageTableEntry))); + const KPhysicalAddress phys_addr = util::AlignDown(GetBlock(pte, context->level), GetBlockSize(static_cast<EntryLevel>(context->level + 1), false)); + + /* First, check that all entries are valid for us to merge. */ + const u64 entry_template = pte->GetEntryTemplateForMerge(); + for (size_t i = 0; i < BlocksPerTable; ++i) { + if (!pte[i].IsForMerge(entry_template | GetInteger(phys_addr + (i << (PageBits + LevelBits * context->level))) | PageTableEntry::ContigType_Contiguous | pte->GetTestTableMask())) { + return false; + } + if (i > 0 && pte[i].IsHeadOrHeadAndBodyMergeDisabled()) { + return false; + } + if (i < BlocksPerTable - 1 && pte[i].IsTailMergeDisabled()) { + return false; + } + } + + /* The entries are valid for us to merge, so merge them. */ + const auto *head_pte = pte; + const auto *tail_pte = pte + BlocksPerTable - 1; + const auto sw_reserved_bits = PageTableEntry::EncodeSoftwareReservedBits(head_pte->IsHeadMergeDisabled(), head_pte->IsHeadAndBodyMergeDisabled(), tail_pte->IsTailMergeDisabled()); + + *context->level_entries[context->level + 1] = PageTableEntry(PageTableEntry::BlockTag{}, phys_addr, PageTableEntry(entry_template), sw_reserved_bits, false, false); + on_entry_updated(pt); + + /* Update our context. */ + context->is_contiguous = false; + context->level = static_cast<EntryLevel>(util::ToUnderlying(context->level) + 1); + + /* Set the output to the table we just freed. */ + *out = KVirtualAddress(pte); + } else { + /* We want to upgrade a non-contiguous mapping to a contiguous mapping. */ + PageTableEntry *pte = reinterpret_cast<PageTableEntry *>(util::AlignDown(reinterpret_cast<uintptr_t>(context->level_entries[context->level]), BlocksPerContiguousBlock * sizeof(PageTableEntry))); + const KPhysicalAddress phys_addr = util::AlignDown(GetBlock(pte, context->level), GetBlockSize(context->level, true)); + + /* First, check that all entries are valid for us to merge. */ + const u64 entry_template = pte->GetEntryTemplateForMerge(); + for (size_t i = 0; i < BlocksPerContiguousBlock; ++i) { + if (!pte[i].IsForMerge(entry_template | GetInteger(phys_addr + (i << (PageBits + LevelBits * context->level))) | pte->GetTestTableMask())) { + return false; + } + if (i > 0 && pte[i].IsHeadOrHeadAndBodyMergeDisabled()) { + return false; + } + if (i < BlocksPerContiguousBlock - 1 && pte[i].IsTailMergeDisabled()) { + return false; + } + } + + /* The entries are valid for us to merge, so merge them. */ + const auto *head_pte = pte; + const auto *tail_pte = pte + BlocksPerContiguousBlock - 1; + const auto sw_reserved_bits = PageTableEntry::EncodeSoftwareReservedBits(head_pte->IsHeadMergeDisabled(), head_pte->IsHeadAndBodyMergeDisabled(), tail_pte->IsTailMergeDisabled()); + + for (size_t i = 0; i < BlocksPerContiguousBlock; ++i) { + pte[i] = PageTableEntry(PageTableEntry::BlockTag{}, phys_addr + (i << (PageBits + LevelBits * context->level)), PageTableEntry(entry_template), sw_reserved_bits, true, context->level == EntryLevel_L3); + } + on_entry_updated(pt); + + /* Update our context. */ + context->level_entries[context->level] = pte; + context->is_contiguous = true; + } + + return true; + } + + void KPageTableImpl::SeparatePages(TraversalEntry *entry, TraversalContext *context, KProcessAddress address, PageTableEntry *pte, EntryUpdatedCallback on_entry_updated, const void *pt) const { + /* We want to downgrade the pages by one step. */ + if (context->is_contiguous) { + /* We want to downgrade a contiguous mapping to a non-contiguous mapping. */ + pte = reinterpret_cast<PageTableEntry *>(util::AlignDown(reinterpret_cast<uintptr_t>(context->level_entries[context->level]), BlocksPerContiguousBlock * sizeof(PageTableEntry))); + + auto * const first = pte; + const KPhysicalAddress block = this->GetBlock(first, context->level); + for (size_t i = 0; i < BlocksPerContiguousBlock; ++i) { + pte[i] = PageTableEntry(PageTableEntry::BlockTag{}, block + (i << (PageBits + LevelBits * context->level)), PageTableEntry(first->GetEntryTemplateForSeparateContiguous(i)), PageTableEntry::SeparateContiguousTag{}); + } + on_entry_updated(pt); + + context->is_contiguous = false; + + context->level_entries[context->level] = pte + (this->GetLevelIndex(address, context->level) & (BlocksPerContiguousBlock - 1)); + } else { + /* We want to downgrade a block into a table. */ + auto * const first = context->level_entries[context->level]; + const KPhysicalAddress block = this->GetBlock(first, context->level); + for (size_t i = 0; i < BlocksPerTable; ++i) { + pte[i] = PageTableEntry(PageTableEntry::BlockTag{}, block + (i << (PageBits + LevelBits * (context->level - 1))), PageTableEntry(first->GetEntryTemplateForSeparate(i)), PageTableEntry::SoftwareReservedBit_None, true, context->level - 1 == EntryLevel_L3); + } + + context->is_contiguous = true; + context->level = static_cast<EntryLevel>(util::ToUnderlying(context->level) - 1); + + /* Wait for pending stores to complete. */ + cpu::DataSynchronizationBarrierInnerShareableStore(); + + /* Update the block entry to be a table entry. */ + *context->level_entries[context->level + 1] = PageTableEntry(PageTableEntry::TableTag{}, KPageTable::GetPageTablePhysicalAddress(KVirtualAddress(pte)), m_is_kernel, true, BlocksPerTable); + on_entry_updated(pt); + + context->level_entries[context->level] = pte + this->GetLevelIndex(address, context->level); + } + + entry->sw_reserved_bits = context->level_entries[context->level]->GetSoftwareReservedBits(); + entry->attr = 0; + entry->phys_addr = this->GetBlock(context->level_entries[context->level], context->level) + this->GetOffset(address, context->level); + entry->block_size = static_cast<size_t>(1) << (PageBits + LevelBits * context->level + 4 * context->is_contiguous); + } + + void KPageTableImpl::Dump(uintptr_t start, size_t size) const { + /* If zero size, there's nothing to dump. */ + if (size == 0) { + return; + } + + /* Define extents. */ + const uintptr_t end = start + size; + const uintptr_t last = end - 1; + + /* Define tracking variables. */ + bool unmapped = false; + uintptr_t unmapped_start = 0; + + /* Walk the table. */ + uintptr_t cur = start; + while (cur < end) { + /* Validate that we can read the actual entry. */ + const size_t l0_index = GetL0Index(cur); + const size_t l1_index = GetL1Index(cur); + if (m_is_kernel) { + /* Kernel entries must be accessed via TTBR1. */ + if ((l0_index != MaxPageTableEntries - 1) || (l1_index < MaxPageTableEntries - m_num_entries)) { + return; + } + } else { + /* User entries must be accessed with TTBR0. */ + if ((l0_index != 0) || l1_index >= m_num_entries) { + return; + } + } + + /* Try to get from l1 table. */ + const L1PageTableEntry *l1_entry = this->GetL1Entry(cur); + if (l1_entry->IsBlock()) { + /* Update. */ + cur = util::AlignDown(cur, L1BlockSize); + if (unmapped) { + unmapped = false; + MESOSPHERE_RELEASE_LOG("%016lx - %016lx: not mapped\n", unmapped_start, cur - 1); + } + + /* Print. */ + MESOSPHERE_RELEASE_LOG("%016lx: %016lx PA=%p SZ=1G Mapped=%d UXN=%d PXN=%d Cont=%d nG=%d AF=%d SH=%x RO=%d UA=%d NS=%d AttrIndx=%d NoMerge=%d,%d,%d\n", cur, + *reinterpret_cast<const u64 *>(l1_entry), + reinterpret_cast<void *>(GetInteger(l1_entry->GetBlock())), + l1_entry->IsMapped(), + l1_entry->IsUserExecuteNever(), + l1_entry->IsPrivilegedExecuteNever(), + l1_entry->IsContiguous(), + !l1_entry->IsGlobal(), + static_cast<int>(l1_entry->GetAccessFlagInteger()), + static_cast<unsigned int>(l1_entry->GetShareableInteger()), + l1_entry->IsReadOnly(), + l1_entry->IsUserAccessible(), + l1_entry->IsNonSecure(), + static_cast<int>(l1_entry->GetPageAttributeInteger()), + l1_entry->IsHeadMergeDisabled(), + l1_entry->IsHeadAndBodyMergeDisabled(), + l1_entry->IsTailMergeDisabled()); + + /* Advance. */ + cur += L1BlockSize; + continue; + } else if (!l1_entry->IsTable()) { + /* Update. */ + cur = util::AlignDown(cur, L1BlockSize); + if (!unmapped) { + unmapped_start = cur; + unmapped = true; + } + + /* Advance. */ + cur += L1BlockSize; + continue; + } + + /* Try to get from l2 table. */ + const L2PageTableEntry *l2_entry = this->GetL2Entry(l1_entry, cur); + if (l2_entry->IsBlock()) { + /* Update. */ + cur = util::AlignDown(cur, L2BlockSize); + if (unmapped) { + unmapped = false; + MESOSPHERE_RELEASE_LOG("%016lx - %016lx: not mapped\n", unmapped_start, cur - 1); + } + + /* Print. */ + MESOSPHERE_RELEASE_LOG("%016lx: %016lx PA=%p SZ=2M Mapped=%d UXN=%d PXN=%d Cont=%d nG=%d AF=%d SH=%x RO=%d UA=%d NS=%d AttrIndx=%d NoMerge=%d,%d,%d\n", cur, + *reinterpret_cast<const u64 *>(l2_entry), + reinterpret_cast<void *>(GetInteger(l2_entry->GetBlock())), + l2_entry->IsMapped(), + l2_entry->IsUserExecuteNever(), + l2_entry->IsPrivilegedExecuteNever(), + l2_entry->IsContiguous(), + !l2_entry->IsGlobal(), + static_cast<int>(l2_entry->GetAccessFlagInteger()), + static_cast<unsigned int>(l2_entry->GetShareableInteger()), + l2_entry->IsReadOnly(), + l2_entry->IsUserAccessible(), + l2_entry->IsNonSecure(), + static_cast<int>(l2_entry->GetPageAttributeInteger()), + l2_entry->IsHeadMergeDisabled(), + l2_entry->IsHeadAndBodyMergeDisabled(), + l2_entry->IsTailMergeDisabled()); + + /* Advance. */ + cur += L2BlockSize; + continue; + } else if (!l2_entry->IsTable()) { + /* Update. */ + cur = util::AlignDown(cur, L2BlockSize); + if (!unmapped) { + unmapped_start = cur; + unmapped = true; + } + + /* Advance. */ + cur += L2BlockSize; + continue; + } + + /* Try to get from l3 table. */ + const L3PageTableEntry *l3_entry = this->GetL3Entry(l2_entry, cur); + if (l3_entry->IsBlock()) { + /* Update. */ + cur = util::AlignDown(cur, L3BlockSize); + if (unmapped) { + unmapped = false; + MESOSPHERE_RELEASE_LOG("%016lx - %016lx: not mapped\n", unmapped_start, cur - 1); + } + + /* Print. */ + MESOSPHERE_RELEASE_LOG("%016lx: %016lx PA=%p SZ=4K Mapped=%d UXN=%d PXN=%d Cont=%d nG=%d AF=%d SH=%x RO=%d UA=%d NS=%d AttrIndx=%d NoMerge=%d,%d,%d\n", cur, + *reinterpret_cast<const u64 *>(l3_entry), + reinterpret_cast<void *>(GetInteger(l3_entry->GetBlock())), + l3_entry->IsMapped(), + l3_entry->IsUserExecuteNever(), + l3_entry->IsPrivilegedExecuteNever(), + l3_entry->IsContiguous(), + !l3_entry->IsGlobal(), + static_cast<int>(l3_entry->GetAccessFlagInteger()), + static_cast<unsigned int>(l3_entry->GetShareableInteger()), + l3_entry->IsReadOnly(), + l3_entry->IsUserAccessible(), + l3_entry->IsNonSecure(), + static_cast<int>(l3_entry->GetPageAttributeInteger()), + l3_entry->IsHeadMergeDisabled(), + l3_entry->IsHeadAndBodyMergeDisabled(), + l3_entry->IsTailMergeDisabled()); + + /* Advance. */ + cur += L3BlockSize; + continue; + } else { + /* Update. */ + cur = util::AlignDown(cur, L3BlockSize); + if (!unmapped) { + unmapped_start = cur; + unmapped = true; + } + + /* Advance. */ + cur += L3BlockSize; + continue; + } + } + + /* Print the last unmapped range if necessary. */ + if (unmapped) { + MESOSPHERE_RELEASE_LOG("%016lx - %016lx: not mapped\n", unmapped_start, last); + } + } + + size_t KPageTableImpl::CountPageTables() const { + size_t num_tables = 0; + + #if defined(MESOSPHERE_BUILD_FOR_DEBUGGING) + { + ++num_tables; + for (size_t l1_index = 0; l1_index < m_num_entries; ++l1_index) { + auto &l1_entry = m_table[l1_index]; + if (l1_entry.IsTable()) { + ++num_tables; + for (size_t l2_index = 0; l2_index < MaxPageTableEntries; ++l2_index) { + auto *l2_entry = GetPointer<L2PageTableEntry>(GetTableEntry(KMemoryLayout::GetLinearVirtualAddress(l1_entry.GetTable()), l2_index)); + if (l2_entry->IsTable()) { + ++num_tables; + } + } + } + } + } + #endif + + return num_tables; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_supervisor_page_table.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_supervisor_page_table.cpp new file mode 100644 index 00000000..5f5112f8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_supervisor_page_table.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::arch::arm64 { + + void KSupervisorPageTable::Initialize(s32 core_id) { + /* Verify that sctlr_el1 has the wxn bit set. */ + MESOSPHERE_ABORT_UNLESS(cpu::SystemControlRegisterAccessor().GetWxn()); + + /* Invalidate the entire TLB. */ + cpu::InvalidateEntireTlb(); + + /* If core 0, initialize our base page table. */ + if (core_id == 0) { + /* TODO: constexpr defines. */ + const u64 ttbr1 = cpu::GetTtbr1El1() & 0xFFFFFFFFFFFFul; + const u64 kernel_vaddr_start = 0xFFFFFF8000000000ul; + const u64 kernel_vaddr_end = 0xFFFFFFFFFFE00000ul; + void *table = GetVoidPointer(KPageTableBase::GetLinearMappedVirtualAddress(ttbr1)); + m_page_table.InitializeForKernel(table, kernel_vaddr_start, kernel_vaddr_end); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_thread_context.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_thread_context.cpp new file mode 100644 index 00000000..76e91cff --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_k_thread_context.cpp @@ -0,0 +1,283 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::arch::arm64 { + + /* These are implemented elsewhere (asm). */ + void UserModeThreadStarter(); + void SupervisorModeThreadStarter(); + + void InvokeSupervisorModeThread(uintptr_t argument, uintptr_t entrypoint) { + /* Invoke the function. */ + using SupervisorModeFunctionType = void (*)(uintptr_t); + reinterpret_cast<SupervisorModeFunctionType>(entrypoint)(argument); + + /* Wait forever. */ + AMS_INFINITE_LOOP(); + } + + void OnThreadStart() { + MESOSPHERE_ASSERT(!KInterruptManager::AreInterruptsEnabled()); + /* Send KDebug event for this thread's creation. */ + { + KScopedInterruptEnable ei; + + const uintptr_t params[2] = { GetCurrentThread().GetId(), GetInteger(GetCurrentThread().GetThreadLocalRegionAddress()) }; + KDebug::OnDebugEvent(ams::svc::DebugEvent_CreateThread, params, util::size(params)); + } + + /* Handle any pending dpc. */ + while (GetCurrentThread().HasDpc()) { + KDpcManager::HandleDpc(); + } + + /* Clear our status as in an exception handler */ + GetCurrentThread().ClearInExceptionHandler(); + } + + namespace { + + ALWAYS_INLINE bool IsFpuEnabled() { + return cpu::ArchitecturalFeatureAccessControlRegisterAccessor().IsFpEnabled(); + } + + ALWAYS_INLINE void EnableFpu() { + cpu::ArchitecturalFeatureAccessControlRegisterAccessor().SetFpEnabled(true).Store(); + cpu::InstructionMemoryBarrier(); + } + + uintptr_t SetupStackForUserModeThreadStarter(KVirtualAddress pc, KVirtualAddress k_sp, KVirtualAddress u_sp, uintptr_t arg, const bool is_64_bit) { + /* NOTE: Stack layout on entry looks like following: */ + /* SP */ + /* | */ + /* v */ + /* | KExceptionContext (size 0x120) | KThread::StackParameters (size 0x130) | */ + KExceptionContext *ctx = GetPointer<KExceptionContext>(k_sp) - 1; + + /* Clear context. */ + std::memset(ctx, 0, sizeof(*ctx)); + + /* Set PC and argument. */ + ctx->pc = GetInteger(pc) & ~(UINT64_C(1)); + ctx->x[0] = arg; + + /* Set PSR. */ + if (is_64_bit) { + ctx->psr = 0; + } else { + constexpr u64 PsrArmValue = 0x00; + constexpr u64 PsrThumbValue = 0x20; + ctx->psr = ((pc & 1) == 0 ? PsrArmValue : PsrThumbValue) | (0x10); + MESOSPHERE_LOG("Creating User 32-Thread, %016lx\n", GetInteger(pc)); + } + + /* Set CFI-value. */ + if (is_64_bit) { + ctx->x[18] = KSystemControl::GenerateRandomU64() | 1; + } + + /* Set stack pointer. */ + if (is_64_bit) { + ctx->sp = GetInteger(u_sp); + } else { + ctx->x[13] = GetInteger(u_sp); + } + + return reinterpret_cast<uintptr_t>(ctx); + } + + uintptr_t SetupStackForSupervisorModeThreadStarter(KVirtualAddress pc, KVirtualAddress sp, uintptr_t arg) { + /* NOTE: Stack layout on entry looks like following: */ + /* SP */ + /* | */ + /* v */ + /* | u64 argument | u64 entrypoint | KThread::StackParameters (size 0x140) | */ + static_assert(sizeof(KThread::StackParameters) == 0x140); + + u64 *stack = GetPointer<u64>(sp); + *(--stack) = GetInteger(pc); + *(--stack) = arg; + return reinterpret_cast<uintptr_t>(stack); + } + + } + + Result KThreadContext::Initialize(KVirtualAddress u_pc, KVirtualAddress k_sp, KVirtualAddress u_sp, uintptr_t arg, bool is_user, bool is_64_bit, bool is_main) { + MESOSPHERE_ASSERT(k_sp != Null<KVirtualAddress>); + + /* Ensure that the stack pointers are aligned. */ + k_sp = util::AlignDown(GetInteger(k_sp), 16); + u_sp = util::AlignDown(GetInteger(u_sp), 16); + + /* Determine LR and SP. */ + if (is_user) { + /* Usermode thread. */ + m_lr = reinterpret_cast<uintptr_t>(::ams::kern::arch::arm64::UserModeThreadStarter); + m_sp = SetupStackForUserModeThreadStarter(u_pc, k_sp, u_sp, arg, is_64_bit); + } else { + /* Kernel thread. */ + MESOSPHERE_ASSERT(is_64_bit); + + if (is_main) { + /* Main thread. */ + m_lr = GetInteger(u_pc); + m_sp = GetInteger(k_sp); + } else { + /* Generic Kernel thread. */ + m_lr = reinterpret_cast<uintptr_t>(::ams::kern::arch::arm64::SupervisorModeThreadStarter); + m_sp = SetupStackForSupervisorModeThreadStarter(u_pc, k_sp, arg); + } + } + + /* Clear callee-saved registers. */ + for (size_t i = 0; i < util::size(m_callee_saved.registers); i++) { + m_callee_saved.registers[i] = 0; + } + + /* Clear FPU state. */ + m_fpcr = 0; + m_fpsr = 0; + for (size_t i = 0; i < util::size(m_callee_saved_fpu.fpu64.v); ++i) { + m_callee_saved_fpu.fpu64.v[i] = 0; + } + + /* Lock the context, if we're a main thread. */ + m_locked = is_main; + + R_SUCCEED(); + } + + void KThreadContext::SetArguments(uintptr_t arg0, uintptr_t arg1) { + u64 *stack = reinterpret_cast<u64 *>(m_sp); + stack[0] = arg0; + stack[1] = arg1; + } + + void KThreadContext::CloneFpuStatus() { + u64 pcr, psr; + cpu::InstructionMemoryBarrier(); + + KScopedInterruptDisable di; + + if (IsFpuEnabled()) { + __asm__ __volatile__("mrs %[pcr], fpcr" : [pcr]"=r"(pcr) :: "memory"); + __asm__ __volatile__("mrs %[psr], fpsr" : [psr]"=r"(psr) :: "memory"); + } else { + pcr = GetCurrentThread().GetContext().GetFpcr(); + psr = GetCurrentThread().GetContext().GetFpsr(); + } + + this->SetFpcr(pcr); + this->SetFpsr(psr); + } + + void GetUserContext(ams::svc::ThreadContext *out, const KThread *thread) { + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + MESOSPHERE_ASSERT(thread->IsSuspended()); + MESOSPHERE_ASSERT(thread->GetOwnerProcess() != nullptr); + + /* Get the contexts. */ + const KExceptionContext *e_ctx = GetExceptionContext(thread); + const KThreadContext *t_ctx = std::addressof(thread->GetContext()); + + if (thread->GetOwnerProcess()->Is64Bit()) { + /* Set special registers. */ + out->fp = e_ctx->x[29]; + out->lr = e_ctx->x[30]; + out->sp = e_ctx->sp; + out->pc = e_ctx->pc; + out->pstate = e_ctx->psr & cpu::El0Aarch64PsrMask; + + /* Get the thread's general purpose registers. */ + if (thread->IsCallingSvc()) { + for (size_t i = 19; i < 29; ++i) { + out->r[i] = e_ctx->x[i]; + } + if (e_ctx->write == 0) { + out->pc -= sizeof(u32); + } + } else { + for (size_t i = 0; i < 29; ++i) { + out->r[i] = e_ctx->x[i]; + } + } + + /* Copy tpidr. */ + out->tpidr = e_ctx->tpidr; + + /* Copy fpu registers. */ + static_assert(util::size(ams::svc::ThreadContext{}.v) == KThreadContext::NumFpuRegisters); + static_assert(KThreadContext::NumCallerSavedFpuRegisters == KThreadContext::NumCalleeSavedFpuRegisters * 3); + static_assert(KThreadContext::NumFpuRegisters == KThreadContext::NumCallerSavedFpuRegisters + KThreadContext::NumCalleeSavedFpuRegisters); + const auto &caller_save_fpu = thread->GetCallerSaveFpuRegisters().fpu64; + const auto &callee_save_fpu = t_ctx->GetCalleeSaveFpuRegisters().fpu64; + + if (!thread->IsCallingSvc() || thread->IsInUsermodeExceptionHandler()) { + KThreadContext::GetFpuRegisters(out->v, caller_save_fpu, callee_save_fpu); + } else { + for (size_t i = 0; i < KThreadContext::NumCalleeSavedFpuRegisters; ++i) { + out->v[(KThreadContext::NumCallerSavedFpuRegisters / 3) + i] = caller_save_fpu.v[i]; + } + } + } else { + /* Set special registers. */ + out->pc = static_cast<u32>(e_ctx->pc); + out->pstate = e_ctx->psr & cpu::El0Aarch32PsrMask; + + /* Get the thread's general purpose registers. */ + for (size_t i = 0; i < 15; ++i) { + out->r[i] = static_cast<u32>(e_ctx->x[i]); + } + + /* Adjust PC, if the thread is calling svc. */ + if (thread->IsCallingSvc()) { + if (e_ctx->write == 0) { + /* Adjust by 2 if thumb mode, 4 if arm mode. */ + out->pc -= ((e_ctx->psr & 0x20) == 0) ? sizeof(u32) : sizeof(u16); + } + } + + /* Copy tpidr. */ + out->tpidr = static_cast<u32>(e_ctx->tpidr); + + /* Copy fpu registers. */ + static_assert(util::size(ams::svc::ThreadContext{}.v) == KThreadContext::NumFpuRegisters); + static_assert(KThreadContext::NumCallerSavedFpuRegisters == KThreadContext::NumCalleeSavedFpuRegisters * 3); + static_assert(KThreadContext::NumFpuRegisters == KThreadContext::NumCallerSavedFpuRegisters + KThreadContext::NumCalleeSavedFpuRegisters); + const auto &caller_save_fpu = thread->GetCallerSaveFpuRegisters().fpu32; + const auto &callee_save_fpu = t_ctx->GetCalleeSaveFpuRegisters().fpu32; + + if (!thread->IsCallingSvc() || thread->IsInUsermodeExceptionHandler()) { + KThreadContext::GetFpuRegisters(out->v, caller_save_fpu, callee_save_fpu); + } else { + for (size_t i = 0; i < KThreadContext::NumCalleeSavedFpuRegisters / 2; ++i) { + out->v[((KThreadContext::NumCallerSavedFpuRegisters / 3) / 2) + i] = caller_save_fpu.v[i]; + } + } + } + + /* Copy fpcr/fpsr. */ + out->fpcr = t_ctx->GetFpcr(); + out->fpsr = t_ctx->GetFpsr(); + } + + void KThreadContext::OnThreadTerminating(const KThread *thread) { + MESOSPHERE_UNUSED(thread); + /* ... */ + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_panic_asm.s b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_panic_asm.s new file mode 100644 index 00000000..cc9ec078 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_panic_asm.s @@ -0,0 +1,89 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere/kern_build_config.hpp> +#include <mesosphere/kern_select_assembly_offsets.h> + +#if defined(MESOSPHERE_ENABLE_PANIC_REGISTER_DUMP) + +#define MESOSPHERE_GENERATE_PANIC_EXCEPTION_CONTEXT \ + /* Save x0/x1 to stack. */ \ + stp x0, x1, [sp, #-16]!; \ + \ + /* Get the exception context for the core. */ \ + adr x0, _ZN3ams4kern26g_panic_exception_contextsE; \ + mrs x1, mpidr_el1; \ + and x1, x1, #0xFF; \ + lsl x1, x1, #0x8; \ + add x0, x0, x1; \ + lsr x1, x1, #0x3; \ + add x0, x0, x1; \ + \ + /* Save x0/x1/sp to the context. */ \ + ldr x1, [sp, #(8 * 0)]; \ + str x1, [x0, #(EXCEPTION_CONTEXT_X0)]; \ + ldr x1, [sp, #(8 * 1)]; \ + str x1, [x0, #(EXCEPTION_CONTEXT_X1)]; \ + \ + /* Save all other registers to the context. */ \ + stp x2, x3, [x0, #(EXCEPTION_CONTEXT_X2_X3)]; \ + stp x4, x5, [x0, #(EXCEPTION_CONTEXT_X4_X5)]; \ + stp x6, x7, [x0, #(EXCEPTION_CONTEXT_X6_X7)]; \ + stp x8, x9, [x0, #(EXCEPTION_CONTEXT_X8_X9)]; \ + stp x10, x11, [x0, #(EXCEPTION_CONTEXT_X10_X11)]; \ + stp x12, x13, [x0, #(EXCEPTION_CONTEXT_X12_X13)]; \ + stp x14, x15, [x0, #(EXCEPTION_CONTEXT_X14_X15)]; \ + stp x16, x17, [x0, #(EXCEPTION_CONTEXT_X16_X17)]; \ + stp x18, x19, [x0, #(EXCEPTION_CONTEXT_X18_X19)]; \ + stp x20, x21, [x0, #(EXCEPTION_CONTEXT_X20_X21)]; \ + stp x22, x23, [x0, #(EXCEPTION_CONTEXT_X22_X23)]; \ + stp x24, x25, [x0, #(EXCEPTION_CONTEXT_X24_X25)]; \ + stp x26, x27, [x0, #(EXCEPTION_CONTEXT_X26_X27)]; \ + stp x28, x29, [x0, #(EXCEPTION_CONTEXT_X28_X29)]; \ + \ + add x1, sp, #16; \ + stp x30, x1, [x0, #(EXCEPTION_CONTEXT_X30_SP)]; \ + \ + /* Restore x0/x1. */ \ + ldp x0, x1, [sp], #16; + +#else + +#define MESOSPHERE_GENERATE_PANIC_EXCEPTION_CONTEXT + +#endif + +/* ams::kern::Panic(const char *file, int line, const char *format, ...) */ +.section .text._ZN3ams4kern5PanicEPKciS2_z, "ax", %progbits +.global _ZN3ams4kern5PanicEPKciS2_z +.type _ZN3ams4kern5PanicEPKciS2_z, %function +_ZN3ams4kern5PanicEPKciS2_z: + /* Generate the exception context. */ + MESOSPHERE_GENERATE_PANIC_EXCEPTION_CONTEXT + + /* Jump to the architecturally-common implementation function. */ + b _ZN3ams4kern9PanicImplEPKciS2_z + + +/* ams::kern::Panic() */ +.section .text._ZN3ams4kern5PanicEv, "ax", %progbits +.global _ZN3ams4kern5PanicEv +.type _ZN3ams4kern5PanicEv, %function +_ZN3ams4kern5PanicEv: + /* Generate the exception context. */ + MESOSPHERE_GENERATE_PANIC_EXCEPTION_CONTEXT + + /* Jump to the architecturally-common implementation function. */ + b _ZN3ams4kern9PanicImplEv diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_userspace_memory_access_asm.s b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_userspace_memory_access_asm.s new file mode 100644 index 00000000..1d9d2c5d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/kern_userspace_memory_access_asm.s @@ -0,0 +1,800 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* ams::kern::arch::arm64::UserspaceAccessFunctionAreaBegin() */ +.section .text._ZN3ams4kern4arch5arm6432UserspaceAccessFunctionAreaBeginEv, "ax", %progbits +.global _ZN3ams4kern4arch5arm6432UserspaceAccessFunctionAreaBeginEv +.type _ZN3ams4kern4arch5arm6432UserspaceAccessFunctionAreaBeginEv, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6432UserspaceAccessFunctionAreaBeginEv: +/* NOTE: This is not a real function, and only exists as a label for safety. */ + +/* ================ All Userspace Access Functions after this line. ================ */ + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryFromUser(void *dst, const void *src, size_t size) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18CopyMemoryFromUserEPvPKvm, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18CopyMemoryFromUserEPvPKvm +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18CopyMemoryFromUserEPvPKvm, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18CopyMemoryFromUserEPvPKvm: + /* Check if there's anything to copy. */ + cmp x2, #0 + b.eq 2f + + /* Keep track of the last address. */ + add x3, x1, x2 + +1: /* We're copying memory byte-by-byte. */ + ldtrb w2, [x1] + strb w2, [x0], #1 + add x1, x1, #1 + cmp x1, x3 + b.ne 1b + +2: /* We're done. */ + mov x0, #1 + ret + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryFromUserAligned32Bit(void *dst, const void *src, size_t size) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned32BitEPvPKvm, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned32BitEPvPKvm +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned32BitEPvPKvm, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned32BitEPvPKvm: + /* Check if there are 0x40 bytes to copy */ + cmp x2, #0x3F + b.ls 1f + ldtr x4, [x1, #0x00] + ldtr x5, [x1, #0x08] + ldtr x6, [x1, #0x10] + ldtr x7, [x1, #0x18] + ldtr x8, [x1, #0x20] + ldtr x9, [x1, #0x28] + ldtr x10, [x1, #0x30] + ldtr x11, [x1, #0x38] + stp x4, x5, [x0, #0x00] + stp x6, x7, [x0, #0x10] + stp x8, x9, [x0, #0x20] + stp x10, x11, [x0, #0x30] + add x0, x0, #0x40 + add x1, x1, #0x40 + sub x2, x2, #0x40 + b _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned32BitEPvPKvm + +1: /* We have less than 0x40 bytes to copy. */ + cmp x2, #0 + b.eq 2f + ldtr w4, [x1] + str w4, [x0], #4 + add x1, x1, #4 + sub x2, x2, #4 + b 1b + +2: /* We're done. */ + mov x0, #1 + ret + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryFromUserAligned64Bit(void *dst, const void *src, size_t size) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned64BitEPvPKvm, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned64BitEPvPKvm +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned64BitEPvPKvm, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned64BitEPvPKvm: + /* Check if there are 0x40 bytes to copy */ + cmp x2, #0x3F + b.ls 1f + ldtr x4, [x1, #0x00] + ldtr x5, [x1, #0x08] + ldtr x6, [x1, #0x10] + ldtr x7, [x1, #0x18] + ldtr x8, [x1, #0x20] + ldtr x9, [x1, #0x28] + ldtr x10, [x1, #0x30] + ldtr x11, [x1, #0x38] + stp x4, x5, [x0, #0x00] + stp x6, x7, [x0, #0x10] + stp x8, x9, [x0, #0x20] + stp x10, x11, [x0, #0x30] + add x0, x0, #0x40 + add x1, x1, #0x40 + sub x2, x2, #0x40 + b _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl30CopyMemoryFromUserAligned64BitEPvPKvm + +1: /* We have less than 0x40 bytes to copy. */ + cmp x2, #0 + b.eq 2f + ldtr x4, [x1] + str x4, [x0], #8 + add x1, x1, #8 + sub x2, x2, #8 + b 1b + +2: /* We're done. */ + mov x0, #1 + ret + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryFromUserSize64Bit(void *dst, const void *src) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl27CopyMemoryFromUserSize64BitEPvPKv, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl27CopyMemoryFromUserSize64BitEPvPKv +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl27CopyMemoryFromUserSize64BitEPvPKv, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl27CopyMemoryFromUserSize64BitEPvPKv: + /* Just load and store a u64. */ + ldtr x2, [x1] + str x2, [x0] + + /* We're done. */ + mov x0, #1 + ret + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryFromUserSize32Bit(void *dst, const void *src) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl27CopyMemoryFromUserSize32BitEPvPKv, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl27CopyMemoryFromUserSize32BitEPvPKv +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl27CopyMemoryFromUserSize32BitEPvPKv, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl27CopyMemoryFromUserSize32BitEPvPKv: + /* Just load and store a u32. */ + ldtr w2, [x1] + str w2, [x0] + + /* We're done. */ + mov x0, #1 + ret + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryFromUserSize32BitWithSupervisorAccess(void *dst, const void *src) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl47CopyMemoryFromUserSize32BitWithSupervisorAccessEPvPKv, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl47CopyMemoryFromUserSize32BitWithSupervisorAccessEPvPKv +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl47CopyMemoryFromUserSize32BitWithSupervisorAccessEPvPKv, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl47CopyMemoryFromUserSize32BitWithSupervisorAccessEPvPKv: + /* Just load and store a u32. */ + /* NOTE: This is done with supervisor access permissions. */ + ldr w2, [x1] + str w2, [x0] + + /* We're done. */ + mov x0, #1 + ret + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyStringFromUser(void *dst, const void *src, size_t size) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18CopyStringFromUserEPvPKvm, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18CopyStringFromUserEPvPKvm +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18CopyStringFromUserEPvPKvm, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18CopyStringFromUserEPvPKvm: + /* Check if there's anything to copy. */ + cmp x2, #0 + b.eq 3f + + /* Keep track of the start address and last address. */ + mov x4, x1 + add x3, x1, x2 + +1: /* We're copying memory byte-by-byte. */ + ldtrb w2, [x1] + strb w2, [x0], #1 + add x1, x1, #1 + + /* If we read a null terminator, we're done. */ + cmp w2, #0 + b.eq 2f + + /* Check if we're done. */ + cmp x1, x3 + b.ne 1b + +2: /* We're done, and we copied some amount of data from the string. */ + sub x0, x1, x4 + ret + +3: /* We're done, and there was no string data. */ + mov x0, #0 + ret + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryToUser(void *dst, const void *src, size_t size) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16CopyMemoryToUserEPvPKvm, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16CopyMemoryToUserEPvPKvm +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16CopyMemoryToUserEPvPKvm, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16CopyMemoryToUserEPvPKvm: + /* Check if there's anything to copy. */ + cmp x2, #0 + b.eq 2f + + /* Keep track of the last address. */ + add x3, x1, x2 + +1: /* We're copying memory byte-by-byte. */ + ldrb w2, [x1], #1 + sttrb w2, [x0] + add x0, x0, #1 + cmp x1, x3 + b.ne 1b + +2: /* We're done. */ + mov x0, #1 + ret + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryToUserAligned32Bit(void *dst, const void *src, size_t size) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned32BitEPvPKvm, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned32BitEPvPKvm +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned32BitEPvPKvm, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned32BitEPvPKvm: + /* Check if there are 0x40 bytes to copy */ + cmp x2, #0x3F + b.ls 1f + ldp x4, x5, [x1, #0x00] + ldp x6, x7, [x1, #0x10] + ldp x8, x9, [x1, #0x20] + ldp x10, x11, [x1, #0x30] + sttr x4, [x0, #0x00] + sttr x5, [x0, #0x08] + sttr x6, [x0, #0x10] + sttr x7, [x0, #0x18] + sttr x8, [x0, #0x20] + sttr x9, [x0, #0x28] + sttr x10, [x0, #0x30] + sttr x11, [x0, #0x38] + add x0, x0, #0x40 + add x1, x1, #0x40 + sub x2, x2, #0x40 + b _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned32BitEPvPKvm + +1: /* We have less than 0x40 bytes to copy. */ + cmp x2, #0 + b.eq 2f + ldr w4, [x1], #4 + sttr w4, [x0] + add x0, x0, #4 + sub x2, x2, #4 + b 1b + +2: /* We're done. */ + mov x0, #1 + ret + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryToUserAligned64Bit(void *dst, const void *src, size_t size) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned64BitEPvPKvm, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned64BitEPvPKvm +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned64BitEPvPKvm, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned64BitEPvPKvm: + /* Check if there are 0x40 bytes to copy */ + cmp x2, #0x3F + b.ls 1f + ldp x4, x5, [x1, #0x00] + ldp x6, x7, [x1, #0x10] + ldp x8, x9, [x1, #0x20] + ldp x10, x11, [x1, #0x30] + sttr x4, [x0, #0x00] + sttr x5, [x0, #0x08] + sttr x6, [x0, #0x10] + sttr x7, [x0, #0x18] + sttr x8, [x0, #0x20] + sttr x9, [x0, #0x28] + sttr x10, [x0, #0x30] + sttr x11, [x0, #0x38] + add x0, x0, #0x40 + add x1, x1, #0x40 + sub x2, x2, #0x40 + b _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl28CopyMemoryToUserAligned64BitEPvPKvm + +1: /* We have less than 0x40 bytes to copy. */ + cmp x2, #0 + b.eq 2f + ldr x4, [x1], #8 + sttr x4, [x0] + add x0, x0, #8 + sub x2, x2, #8 + b 1b + +2: /* We're done. */ + mov x0, #1 + ret + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyMemoryToUserSize32Bit(void *dst, const void *src) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl25CopyMemoryToUserSize32BitEPvPKv, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl25CopyMemoryToUserSize32BitEPvPKv +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl25CopyMemoryToUserSize32BitEPvPKv, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl25CopyMemoryToUserSize32BitEPvPKv: + /* Just load and store a u32. */ + ldr w2, [x1] + sttr w2, [x0] + + /* We're done. */ + mov x0, #1 + ret + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::CopyStringToUser(void *dst, const void *src, size_t size) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16CopyStringToUserEPvPKvm, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16CopyStringToUserEPvPKvm +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16CopyStringToUserEPvPKvm, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16CopyStringToUserEPvPKvm: + /* Check if there's anything to copy. */ + cmp x2, #0 + b.eq 3f + + /* Keep track of the start address and last address. */ + mov x4, x1 + add x3, x1, x2 + +1: /* We're copying memory byte-by-byte. */ + ldrb w2, [x1], #1 + sttrb w2, [x0] + add x0, x0, #1 + + /* If we read a null terminator, we're done. */ + cmp w2, #0 + b.eq 2f + + /* Check if we're done. */ + cmp x1, x3 + b.ne 1b + +2: /* We're done, and we copied some amount of data from the string. */ + sub x0, x1, x4 + ret + +3: /* We're done, and there was no string data. */ + mov x0, #0 + ret + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::UpdateLockAtomic(u32 *out, u32 *address, u32 if_zero, u32 new_orr_mask) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16UpdateLockAtomicEPjS5_jj, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16UpdateLockAtomicEPjS5_jj +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16UpdateLockAtomicEPjS5_jj, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16UpdateLockAtomicEPjS5_jj: + /* Load the value from the address. */ + ldaxr w4, [x1] + + /* Orr in the new mask. */ + orr w5, w4, w3 + + /* If the value is zero, use the if_zero value, otherwise use the newly orr'd value. */ + cmp w4, wzr + csel w5, w2, w5, eq + + /* Try to store. */ + stlxr w6, w5, [x1] + + /* If we failed to store, try again. */ + cbnz w6, _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16UpdateLockAtomicEPjS5_jj + + /* We're done. */ + str w4, [x0] + mov x0, #1 + ret + + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::UpdateIfEqualAtomic(s32 *out, s32 *address, s32 compare_value, s32 new_value) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl19UpdateIfEqualAtomicEPiS5_ii, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl19UpdateIfEqualAtomicEPiS5_ii +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl19UpdateIfEqualAtomicEPiS5_ii, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl19UpdateIfEqualAtomicEPiS5_ii: + /* Load the value from the address. */ + ldaxr w4, [x1] + + /* Compare it to the desired one. */ + cmp w4, w2 + + /* If equal, we want to try to write the new value. */ + b.eq 1f + + /* Otherwise, clear our exclusive hold and finish. */ + clrex + b 2f + +1: /* Try to store. */ + stlxr w5, w3, [x1] + + /* If we failed to store, try again. */ + cbnz w5, _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl19UpdateIfEqualAtomicEPiS5_ii + +2: /* We're done. */ + str w4, [x0] + mov x0, #1 + ret + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::DecrementIfLessThanAtomic(s32 *out, s32 *address, s32 compare) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl25DecrementIfLessThanAtomicEPiS5_i, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl25DecrementIfLessThanAtomicEPiS5_i +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl25DecrementIfLessThanAtomicEPiS5_i, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl25DecrementIfLessThanAtomicEPiS5_i: + /* Load the value from the address. */ + ldaxr w3, [x1] + + /* Compare it to the desired one. */ + cmp w3, w2 + + /* If less than, we want to try to decrement. */ + b.lt 1f + + /* Otherwise, clear our exclusive hold and finish. */ + clrex + b 2f + +1: /* Decrement and try to store. */ + sub w4, w3, #1 + stlxr w5, w4, [x1] + + /* If we failed to store, try again. */ + cbnz w5, _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl25DecrementIfLessThanAtomicEPiS5_i + +2: /* We're done. */ + str w3, [x0] + mov x0, #1 + ret + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::StoreDataCache(uintptr_t start, uintptr_t end) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl14StoreDataCacheEmm, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl14StoreDataCacheEmm +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl14StoreDataCacheEmm, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl14StoreDataCacheEmm: + /* Check if we have any work to do. */ + cmp x1, x0 + b.eq 2f + +1: /* Loop, storing each cache line. */ + dc cvac, x0 + add x0, x0, #0x40 + cmp x1, x0 + b.ne 1b + +2: /* We're done! */ + mov x0, #1 + ret + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::FlushDataCache(uintptr_t start, uintptr_t end) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl14FlushDataCacheEmm, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl14FlushDataCacheEmm +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl14FlushDataCacheEmm, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl14FlushDataCacheEmm: + /* Check if we have any work to do. */ + cmp x1, x0 + b.eq 2f + +1: /* Loop, flushing each cache line. */ + dc civac, x0 + add x0, x0, #0x40 + cmp x1, x0 + b.ne 1b + +2: /* We're done! */ + mov x0, #1 + ret + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::InvalidateDataCache(uintptr_t start, uintptr_t end) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl19InvalidateDataCacheEmm, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl19InvalidateDataCacheEmm +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl19InvalidateDataCacheEmm, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl19InvalidateDataCacheEmm: + /* Check if we have any work to do. */ + cmp x1, x0 + b.eq 2f + +1: /* Loop, invalidating each cache line. */ + dc ivac, x0 + add x0, x0, #0x40 + cmp x1, x0 + b.ne 1b + +2: /* We're done! */ + mov x0, #1 + ret + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::ReadIoMemory32Bit(void *dst, const void *src, size_t size) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17ReadIoMemory32BitEPvPKvm, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17ReadIoMemory32BitEPvPKvm +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17ReadIoMemory32BitEPvPKvm, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17ReadIoMemory32BitEPvPKvm: + /* Check if we have any work to do. */ + cmp x2, #0 + b.eq 3f + + /* Save variables in temporary registers. */ + mov x4, x0 + mov x5, x1 + mov x6, x2 + add x7, x5, x6 + + /* Save our return address. */ + mov x8, x30 + + /* Prepare return address for read failure. */ + adr x10, 4f + +1: /* Set our return address so that on read failure we continue as though we read -1. */ + mov x30, x10 + + /* Read the word from io. */ + ldtr w9, [x5] + dsb sy + nop + +2: /* Restore our return address. */ + mov x30, x8 + + /* Write the value we read. */ + sttr w9, [x4] + + /* Advance. */ + add x4, x4, #4 + add x5, x5, #4 + cmp x5, x7 + b.ne 1b + +3: /* We're done! */ + mov x0, #1 + ret + +4: /* We failed to read a value, so continue as though we read -1. */ + mov w9, #0xFFFFFFFF + b 2b + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::ReadIoMemory16Bit(void *dst, const void *src, size_t size) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17ReadIoMemory16BitEPvPKvm, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17ReadIoMemory16BitEPvPKvm +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17ReadIoMemory16BitEPvPKvm, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17ReadIoMemory16BitEPvPKvm: + /* Check if we have any work to do. */ + cmp x2, #0 + b.eq 3f + + /* Save variables in temporary registers. */ + mov x4, x0 + mov x5, x1 + mov x6, x2 + add x7, x5, x6 + + /* Save our return address. */ + mov x8, x30 + + /* Prepare return address for read failure. */ + adr x10, 4f + +1: /* Set our return address so that on read failure we continue as though we read -1. */ + mov x30, x10 + + /* Read the word from io. */ + ldtrh w9, [x5] + dsb sy + nop + +2: /* Restore our return address. */ + mov x30, x8 + + /* Write the value we read. */ + sttrh w9, [x4] + + /* Advance. */ + add x4, x4, #2 + add x5, x5, #2 + cmp x5, x7 + b.ne 1b + +3: /* We're done! */ + mov x0, #1 + ret + +4: /* We failed to read a value, so continue as though we read -1. */ + mov w9, #0xFFFFFFFF + b 2b + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::ReadIoMemory8Bit(void *dst, const void *src, size_t size) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16ReadIoMemory8BitEPvPKvm, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16ReadIoMemory8BitEPvPKvm +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16ReadIoMemory8BitEPvPKvm, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl16ReadIoMemory8BitEPvPKvm: + /* Check if we have any work to do. */ + cmp x2, #0 + b.eq 3f + + /* Save variables in temporary registers. */ + mov x4, x0 + mov x5, x1 + mov x6, x2 + add x7, x5, x6 + + /* Save our return address. */ + mov x8, x30 + + /* Prepare return address for read failure. */ + adr x10, 4f + +1: /* Set our return address so that on read failure we continue as though we read -1. */ + mov x30, x10 + + /* Read the word from io. */ + ldtrb w9, [x5] + dsb sy + nop + +2: /* Restore our return address. */ + mov x30, x8 + + /* Write the value we read. */ + sttrb w9, [x4] + + /* Advance. */ + add x4, x4, #1 + add x5, x5, #1 + cmp x5, x7 + b.ne 1b + +3: /* We're done! */ + mov x0, #1 + ret + +4: /* We failed to read a value, so continue as though we read -1. */ + mov w9, #0xFFFFFFFF + b 2b + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::WriteIoMemory32Bit(void *dst, const void *src, size_t size) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18WriteIoMemory32BitEPvPKvm, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18WriteIoMemory32BitEPvPKvm +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18WriteIoMemory32BitEPvPKvm, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18WriteIoMemory32BitEPvPKvm: + /* Check if we have any work to do. */ + cmp x2, #0 + b.eq 3f + + /* Save variables in temporary registers. */ + mov x4, x0 + mov x5, x1 + mov x6, x2 + add x7, x5, x6 + + /* Save our return address. */ + mov x8, x30 + + /* Prepare return address for failure. */ + adr x10, 2f + +1: /* Read the word from normal memory. */ + ldtr w9, [x5] + + /* Set our return address so that on failure we continue. */ + mov x30, x10 + + /* Write the word to io. */ + sttr w9, [x5] + dsb sy + +2: /* Continue. */ + mov x30, x8 + + /* Advance. */ + add x4, x4, #4 + add x5, x5, #4 + cmp x5, x7 + b.ne 1b + +3: /* We're done! */ + mov x0, #1 + ret + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::WriteIoMemory16Bit(void *dst, const void *src, size_t size) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18WriteIoMemory16BitEPvPKvm, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18WriteIoMemory16BitEPvPKvm +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18WriteIoMemory16BitEPvPKvm, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl18WriteIoMemory16BitEPvPKvm: + /* Check if we have any work to do. */ + cmp x2, #0 + b.eq 3f + + /* Save variables in temporary registers. */ + mov x4, x0 + mov x5, x1 + mov x6, x2 + add x7, x5, x6 + + /* Save our return address. */ + mov x8, x30 + + /* Prepare return address for failure. */ + adr x10, 2f + +1: /* Read the word from normal memory. */ + ldtrh w9, [x5] + + /* Set our return address so that on failure we continue. */ + mov x30, x10 + + /* Write the word to io. */ + sttrh w9, [x5] + dsb sy + +2: /* Continue. */ + mov x30, x8 + + /* Advance. */ + add x4, x4, #2 + add x5, x5, #2 + cmp x5, x7 + b.ne 1b + +3: /* We're done! */ + mov x0, #1 + ret + +/* ams::kern::arch::arm64::UserspaceAccess::Impl::WriteIoMemory8Bit(void *dst, const void *src, size_t size) */ +.section .text._ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17WriteIoMemory8BitEPvPKvm, "ax", %progbits +.global _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17WriteIoMemory8BitEPvPKvm +.type _ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17WriteIoMemory8BitEPvPKvm, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6415UserspaceAccess4Impl17WriteIoMemory8BitEPvPKvm: + /* Check if we have any work to do. */ + cmp x2, #0 + b.eq 3f + + /* Save variables in temporary registers. */ + mov x4, x0 + mov x5, x1 + mov x6, x2 + add x7, x5, x6 + + /* Save our return address. */ + mov x8, x30 + + /* Prepare return address for failure. */ + adr x10, 2f + +1: /* Read the word from normal memory. */ + ldtrb w9, [x5] + + /* Set our return address so that on failure we continue. */ + mov x30, x10 + + /* Write the word to io. */ + sttrb w9, [x5] + dsb sy + +2: /* Continue. */ + mov x30, x8 + + /* Advance. */ + add x4, x4, #1 + add x5, x5, #1 + cmp x5, x7 + b.ne 1b + +3: /* We're done! */ + mov x0, #1 + ret + +/* ================ All Userspace Access Functions before this line. ================ */ + +/* ams::kern::arch::arm64::UserspaceAccessFunctionAreaEnd() */ +.section .text._ZN3ams4kern4arch5arm6430UserspaceAccessFunctionAreaEndEv, "ax", %progbits +.global _ZN3ams4kern4arch5arm6430UserspaceAccessFunctionAreaEndEv +.type _ZN3ams4kern4arch5arm6430UserspaceAccessFunctionAreaEndEv, %function +.balign 0x10 +_ZN3ams4kern4arch5arm6430UserspaceAccessFunctionAreaEndEv: +/* NOTE: This is not a real function, and only exists as a label for safety. */ \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_address_arbiter_asm.s b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_address_arbiter_asm.s new file mode 100644 index 00000000..f82db445 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_address_arbiter_asm.s @@ -0,0 +1,67 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* ams::kern::svc::CallWaitForAddress64From32() */ +.section .text._ZN3ams4kern3svc26CallWaitForAddress64From32Ev, "ax", %progbits +.global _ZN3ams4kern3svc26CallWaitForAddress64From32Ev +.type _ZN3ams4kern3svc26CallWaitForAddress64From32Ev, %function +_ZN3ams4kern3svc26CallWaitForAddress64From32Ev: + /* Save LR + callee-save registers. */ + str x30, [sp, #-16]! + stp x6, x7, [sp, #-16]! + + /* Gather the arguments into correct registers. */ + /* NOTE: This has to be manually implemented via asm, */ + /* in order to avoid breaking ABI with pre-19.0.0. */ + orr x2, x2, x5, lsl#32 + orr x3, x3, x4, lsl#32 + + /* Invoke the svc handler. */ + bl _ZN3ams4kern3svc22WaitForAddress64From32ENS_3svc7AddressENS2_15ArbitrationTypeEll + + /* Clean up registers. */ + mov x1, xzr + mov x2, xzr + mov x3, xzr + mov x4, xzr + mov x5, xzr + + ldp x6, x7, [sp], #0x10 + ldr x30, [sp], #0x10 + ret + +/* ams::kern::svc::CallWaitForAddress64() */ +.section .text._ZN3ams4kern3svc20CallWaitForAddress64Ev, "ax", %progbits +.global _ZN3ams4kern3svc20CallWaitForAddress64Ev +.type _ZN3ams4kern3svc20CallWaitForAddress64Ev, %function +_ZN3ams4kern3svc20CallWaitForAddress64Ev: + /* Save LR + FP. */ + stp x29, x30, [sp, #-16]! + + /* Invoke the svc handler. */ + bl _ZN3ams4kern3svc22WaitForAddress64From32ENS_3svc7AddressENS2_15ArbitrationTypeEll + + /* Clean up registers. */ + mov x1, xzr + mov x2, xzr + mov x3, xzr + mov x4, xzr + mov x5, xzr + mov x6, xzr + mov x7, xzr + + ldp x29, x30, [sp], #0x10 + ret diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_call_secure_monitor_asm.s b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_call_secure_monitor_asm.s new file mode 100644 index 00000000..698759db --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_call_secure_monitor_asm.s @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* ams::kern::svc::CallCallSecureMonitor64From32() */ +.section .text._ZN3ams4kern3svc29CallCallSecureMonitor64From32Ev, "ax", %progbits +.global _ZN3ams4kern3svc29CallCallSecureMonitor64From32Ev +.type _ZN3ams4kern3svc29CallCallSecureMonitor64From32Ev, %function +_ZN3ams4kern3svc29CallCallSecureMonitor64From32Ev: + /* Secure Monitor 64-from-32 ABI is not supported. */ + mov x0, xzr + mov x1, xzr + mov x2, xzr + mov x3, xzr + mov x4, xzr + mov x5, xzr + mov x6, xzr + mov x7, xzr + + ret \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_exception_asm.s b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_exception_asm.s new file mode 100644 index 00000000..c3ee6a07 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_exception_asm.s @@ -0,0 +1,133 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere/kern_select_assembly_macros.h> + +/* ams::kern::svc::CallReturnFromException64(Result result) */ +.section .text._ZN3ams4kern3svc25CallReturnFromException64Ev, "ax", %progbits +.global _ZN3ams4kern3svc25CallReturnFromException64Ev +.type _ZN3ams4kern3svc25CallReturnFromException64Ev, %function +_ZN3ams4kern3svc25CallReturnFromException64Ev: + /* Save registers the SVC entry handler didn't. */ + stp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)] + stp x14, x15, [sp, #(EXCEPTION_CONTEXT_X14_X15)] + stp x16, x17, [sp, #(EXCEPTION_CONTEXT_X16_X17)] + str x19, [sp, #(EXCEPTION_CONTEXT_X19)] + stp x20, x21, [sp, #(EXCEPTION_CONTEXT_X20_X21)] + stp x22, x23, [sp, #(EXCEPTION_CONTEXT_X22_X23)] + stp x24, x25, [sp, #(EXCEPTION_CONTEXT_X24_X25)] + stp x26, x27, [sp, #(EXCEPTION_CONTEXT_X26_X27)] + stp x28, x29, [sp, #(EXCEPTION_CONTEXT_X28_X29)] + + /* Call ams::kern::arch::arm64::ReturnFromException(result). */ + bl _ZN3ams4kern4arch5arm6419ReturnFromExceptionENS_6ResultE + +0: /* We should never reach this point. */ + b 0b + +/* ams::kern::svc::CallReturnFromException64From32(Result result) */ +.section .text._ZN3ams4kern3svc31CallReturnFromException64From32Ev, "ax", %progbits +.global _ZN3ams4kern3svc31CallReturnFromException64From32Ev +.type _ZN3ams4kern3svc31CallReturnFromException64From32Ev, %function +_ZN3ams4kern3svc31CallReturnFromException64From32Ev: + /* Save registers the SVC entry handler didn't. */ + /* ... */ + + /* Call ams::kern::arch::arm64::ReturnFromException(result). */ + bl _ZN3ams4kern4arch5arm6419ReturnFromExceptionENS_6ResultE + +0: /* We should never reach this point. */ + b 0b + + +/* ams::kern::svc::RestoreContext(uintptr_t sp) */ +.section .text._ZN3ams4kern3svc14RestoreContextEm, "ax", %progbits +.global _ZN3ams4kern3svc14RestoreContextEm +.type _ZN3ams4kern3svc14RestoreContextEm, %function +_ZN3ams4kern3svc14RestoreContextEm: + /* Set the stack pointer, set daif. */ + mov sp, x0 + msr daifset, #2 + +0: /* We should handle DPC. */ + /* Check the dpc flags. */ + ldrb w8, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_DPC_FLAGS)] + cbz w8, 1f + + /* We have DPC to do! */ + /* Save registers and call ams::kern::KDpcManager::HandleDpc(). */ + sub sp, sp, #0x40 + stp x0, x1, [sp, #(8 * 0)] + stp x2, x3, [sp, #(8 * 2)] + stp x4, x5, [sp, #(8 * 4)] + stp x6, x7, [sp, #(8 * 6)] + bl _ZN3ams4kern11KDpcManager9HandleDpcEv + ldp x0, x1, [sp, #(8 * 0)] + ldp x2, x3, [sp, #(8 * 2)] + ldp x4, x5, [sp, #(8 * 4)] + ldp x6, x7, [sp, #(8 * 6)] + add sp, sp, #0x40 + b 0b + +1: /* We're done with DPC, and should return from the svc. */ + + /* Get our exception flags. */ + ldrb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + + /* Clear in-svc, in-user-exception, and needs-fpu-restore flags. */ + and w10, w9, #(~(THREAD_EXCEPTION_FLAG_IS_FPU_CONTEXT_RESTORE_NEEDED)) + and w10, w10, #(~(THREAD_EXCEPTION_FLAG_IS_CALLING_SVC)) + and w10, w10, #(~(THREAD_EXCEPTION_FLAG_IS_IN_USERMODE_EXCEPTION_HANDLER)) + strb w10, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + + /* If we don't need to restore the fpu, skip restoring it. */ + tbz w9, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_CONTEXT_RESTORE_NEEDED), 3f + + /* Enable and restore the fpu. */ + ENABLE_AND_RESTORE_FPU(x10, x8, x9, w8, w9, 2, 3) + + /* Restore registers. */ + ldp x30, x8, [sp, #(EXCEPTION_CONTEXT_X30_SP)] + ldp x9, x10, [sp, #(EXCEPTION_CONTEXT_PC_PSR)] + ldr x11, [sp, #(EXCEPTION_CONTEXT_TPIDR)] + + #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) + /* Since we're returning from an exception, set SPSR.SS so that we advance an instruction if single-stepping. */ + orr x10, x10, #(1 << 21) + #endif + + msr sp_el0, x8 + msr elr_el1, x9 + msr spsr_el1, x10 + msr tpidr_el0, x11 + ldp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + ldp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + ldp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)] + ldp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)] + ldp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)] + ldp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)] + ldp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)] + ldp x14, x15, [sp, #(EXCEPTION_CONTEXT_X14_X15)] + ldp x16, x17, [sp, #(EXCEPTION_CONTEXT_X16_X17)] + ldp x18, x19, [sp, #(EXCEPTION_CONTEXT_X18_X19)] + ldp x20, x21, [sp, #(EXCEPTION_CONTEXT_X20_X21)] + ldp x22, x23, [sp, #(EXCEPTION_CONTEXT_X22_X23)] + ldp x24, x25, [sp, #(EXCEPTION_CONTEXT_X24_X25)] + ldp x26, x27, [sp, #(EXCEPTION_CONTEXT_X26_X27)] + ldp x28, x29, [sp, #(EXCEPTION_CONTEXT_X28_X29)] + + /* Return. */ + add sp, sp, #(EXCEPTION_CONTEXT_SIZE) + ERET_WITH_SPECULATION_BARRIER diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_handlers.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_handlers.cpp new file mode 100644 index 00000000..6986c50f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_handlers.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + void TraceSvcEntry(const u64 *data) { + MESOSPHERE_KTRACE_SVC_ENTRY(GetCurrentThread().GetSvcId(), data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]); + } + + void TraceSvcExit(const u64 *data) { + MESOSPHERE_KTRACE_SVC_EXIT(GetCurrentThread().GetSvcId(), data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_handlers_asm.s b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_handlers_asm.s new file mode 100644 index 00000000..a3ee735a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_handlers_asm.s @@ -0,0 +1,552 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere/kern_build_config.hpp> +#include <mesosphere/kern_select_assembly_macros.h> + +/* ams::kern::arch::arm64::SvcHandler64() */ +.section .text._ZN3ams4kern4arch5arm6412SvcHandler64Ev, "ax", %progbits +.global _ZN3ams4kern4arch5arm6412SvcHandler64Ev +.type _ZN3ams4kern4arch5arm6412SvcHandler64Ev, %function +_ZN3ams4kern4arch5arm6412SvcHandler64Ev: + /* Create a KExceptionContext for the exception. */ + sub sp, sp, #(EXCEPTION_CONTEXT_SIZE) + + /* Save registers needed for ReturnFromException */ + stp x9, x10, [sp, #(EXCEPTION_CONTEXT_X9_X10)] + str x11, [sp, #(EXCEPTION_CONTEXT_X11)] + str x18, [sp, #(EXCEPTION_CONTEXT_X18)] + + mrs x8, sp_el0 + mrs x9, elr_el1 + mrs x10, spsr_el1 + mrs x11, tpidr_el0 + ldr x18, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_CUR_THREAD)] + + /* Save callee-saved registers. */ + stp x19, x20, [sp, #(EXCEPTION_CONTEXT_X19_X20)] + stp x21, x22, [sp, #(EXCEPTION_CONTEXT_X21_X22)] + stp x23, x24, [sp, #(EXCEPTION_CONTEXT_X23_X24)] + stp x25, x26, [sp, #(EXCEPTION_CONTEXT_X25_X26)] + stp x27, x28, [sp, #(EXCEPTION_CONTEXT_X27_X28)] + + /* Save miscellaneous registers. */ + stp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + stp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + stp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)] + stp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)] + stp x29, x30, [sp, #(EXCEPTION_CONTEXT_X29_X30)] + stp x8, x9, [sp, #(EXCEPTION_CONTEXT_SP_PC)] + stp x10, x11, [sp, #(EXCEPTION_CONTEXT_PSR_TPIDR)] + + /* Check if the SVC index is out of range. */ + mrs x8, esr_el1 + and x8, x8, #0xFF + cmp x8, #(AMS_KERN_NUM_SUPERVISOR_CALLS) + b.ge 3f + + /* Check the specific SVC permission bit for allowal. */ + mov x9, sp + add x9, x9, x8, lsr#3 + ldrb w9, [x9, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_SVC_PERMISSION)] + and x10, x8, #0x7 + lsr x10, x9, x10 + tst x10, #1 + b.eq 3f + + /* Check if our disable count allows us to call SVCs. */ + mrs x10, tpidrro_el0 + add x10, x10, #(THREAD_LOCAL_REGION_DISABLE_COUNT) + ldtrh w10, [x10] + cbz w10, 1f + + /* It might not, so check the stack params to see if we must not allow the SVC. */ + ldrb w10, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_IS_PINNED)] + cbz w10, 3f + +1: /* We can call the SVC. */ + adr x10, _ZN3ams4kern3svc10SvcTable64E + ldr x11, [x10, x8, lsl#3] + cbz x11, 3f + + /* Note that we're calling the SVC. */ + ldrb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + orr w9, w9, #(THREAD_EXCEPTION_FLAG_IS_CALLING_SVC) + strb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + strb w8, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_CURRENT_SVC_ID)] + + /* If we should, trace the svc entry. */ +#if defined(MESOSPHERE_BUILD_FOR_TRACING) + sub sp, sp, #0x50 + stp x0, x1, [sp, #(8 * 0)] + stp x2, x3, [sp, #(8 * 2)] + stp x4, x5, [sp, #(8 * 4)] + stp x6, x7, [sp, #(8 * 6)] + str x11, [sp, #(8 * 8)] + mov x0, sp + bl _ZN3ams4kern3svc13TraceSvcEntryEPKm + ldp x0, x1, [sp, #(8 * 0)] + ldp x2, x3, [sp, #(8 * 2)] + ldp x4, x5, [sp, #(8 * 4)] + ldp x6, x7, [sp, #(8 * 6)] + ldr x11, [sp, #(8 * 8)] + add sp, sp, #0x50 +#endif + + /* Invoke the SVC handler. */ + msr daifclr, #2 + blr x11 + msr daifset, #2 + +2: /* We completed the SVC, and we should handle DPC. */ + /* Check the dpc flags. */ + ldrb w8, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_DPC_FLAGS)] + cbz w8, 5f + + /* We have DPC to do! */ + /* Save registers and call ams::kern::KDpcManager::HandleDpc(). */ + sub sp, sp, #0x40 + stp x0, x1, [sp, #(8 * 0)] + stp x2, x3, [sp, #(8 * 2)] + stp x4, x5, [sp, #(8 * 4)] + stp x6, x7, [sp, #(8 * 6)] + bl _ZN3ams4kern11KDpcManager9HandleDpcEv + ldp x0, x1, [sp, #(8 * 0)] + ldp x2, x3, [sp, #(8 * 2)] + ldp x4, x5, [sp, #(8 * 4)] + ldp x6, x7, [sp, #(8 * 6)] + add sp, sp, #0x40 + b 2b + +3: /* Invalid SVC. */ + /* Setup the context to call into HandleException. */ + stp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + stp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + stp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)] + stp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)] + stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X8_X9)] + stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X10_X11)] + stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X12_X13)] + stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X14_X15)] + stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X16_X17)] + str x19, [sp, #(EXCEPTION_CONTEXT_X19)] + stp x20, x21, [sp, #(EXCEPTION_CONTEXT_X20_X21)] + stp x22, x23, [sp, #(EXCEPTION_CONTEXT_X22_X23)] + stp x24, x25, [sp, #(EXCEPTION_CONTEXT_X24_X25)] + stp x26, x27, [sp, #(EXCEPTION_CONTEXT_X26_X27)] + stp x28, x29, [sp, #(EXCEPTION_CONTEXT_X28_X29)] + + /* Call ams::kern::arch::arm64::HandleException(ams::kern::arch::arm64::KExceptionContext *) */ + mov x0, sp + bl _ZN3ams4kern4arch5arm6415HandleExceptionEPNS2_17KExceptionContextE + + /* If we don't need to restore the fpu, skip restoring it. */ + ldrb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + tbz w9, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_CONTEXT_RESTORE_NEEDED), 4f + + /* Clear the needs-fpu-restore flag. */ + and w9, w9, #(~THREAD_EXCEPTION_FLAG_IS_FPU_CONTEXT_RESTORE_NEEDED) + strb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + + /* Enable and restore the fpu. */ + ENABLE_AND_RESTORE_FPU64(x10, x8, x9, w8, w9) + +4: /* Restore registers. */ + ldp x30, x8, [sp, #(EXCEPTION_CONTEXT_X30_SP)] + ldp x9, x10, [sp, #(EXCEPTION_CONTEXT_PC_PSR)] + ldr x11, [sp, #(EXCEPTION_CONTEXT_TPIDR)] + + #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) + /* Since we're returning from an SVC, make sure SPSR.SS is cleared so that if we're single-stepping we break instantly on the instruction after the SVC. */ + bic x10, x10, #(1 << 21) + #endif + + msr sp_el0, x8 + msr elr_el1, x9 + msr spsr_el1, x10 + msr tpidr_el0, x11 + ldp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + ldp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + ldp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)] + ldp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)] + ldp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)] + ldp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)] + ldp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)] + ldp x14, x15, [sp, #(EXCEPTION_CONTEXT_X14_X15)] + ldp x16, x17, [sp, #(EXCEPTION_CONTEXT_X16_X17)] + ldp x18, x19, [sp, #(EXCEPTION_CONTEXT_X18_X19)] + ldp x20, x21, [sp, #(EXCEPTION_CONTEXT_X20_X21)] + ldp x22, x23, [sp, #(EXCEPTION_CONTEXT_X22_X23)] + ldp x24, x25, [sp, #(EXCEPTION_CONTEXT_X24_X25)] + ldp x26, x27, [sp, #(EXCEPTION_CONTEXT_X26_X27)] + ldp x28, x29, [sp, #(EXCEPTION_CONTEXT_X28_X29)] + + /* Return. */ + add sp, sp, #(EXCEPTION_CONTEXT_SIZE) + ERET_WITH_SPECULATION_BARRIER + +5: /* Return from SVC. */ + + /* If we should, trace the svc exit. */ +#if defined(MESOSPHERE_BUILD_FOR_TRACING) + sub sp, sp, #0x40 + stp x0, x1, [sp, #(8 * 0)] + stp x2, x3, [sp, #(8 * 2)] + stp x4, x5, [sp, #(8 * 4)] + stp x6, x7, [sp, #(8 * 6)] + mov x0, sp + bl _ZN3ams4kern3svc12TraceSvcExitEPKm + ldp x0, x1, [sp, #(8 * 0)] + ldp x2, x3, [sp, #(8 * 2)] + ldp x4, x5, [sp, #(8 * 4)] + ldp x6, x7, [sp, #(8 * 6)] + add sp, sp, #0x40 +#endif + + /* Get our exception flags. */ + ldrb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + + /* Clear in-svc and needs-fpu-restore flags. */ + and w8, w9, #(~(THREAD_EXCEPTION_FLAG_IS_FPU_CONTEXT_RESTORE_NEEDED)) + and w8, w8, #(~(THREAD_EXCEPTION_FLAG_IS_CALLING_SVC)) + strb w8, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + + /* If we don't need to restore the fpu, skip restoring it. */ + tbz w9, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_CONTEXT_RESTORE_NEEDED), 7f + + /* If we need to restore the fpu, check if we need to do a full restore. */ + tbnz w9, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_IN_USERMODE_EXCEPTION_HANDLER), 6f + + /* Enable the fpu. */ + ENABLE_FPU(x8) + + /* Get the thread context and restore fpsr/fpcr. */ + GET_THREAD_CONTEXT_AND_RESTORE_FPCR_FPSR(x10, x8, x9, w8, w9) + + /* Restore callee-saved registers to 64-bit fpu. */ + RESTORE_FPU64_CALLEE_SAVE_REGISTERS(x10) + + /* Clear caller-saved registers to 64-bit fpu. */ + movi v0.2d, #0 + movi v1.2d, #0 + movi v2.2d, #0 + movi v3.2d, #0 + movi v4.2d, #0 + movi v5.2d, #0 + movi v6.2d, #0 + movi v7.2d, #0 + movi v16.2d, #0 + movi v17.2d, #0 + movi v18.2d, #0 + movi v19.2d, #0 + movi v20.2d, #0 + movi v21.2d, #0 + movi v22.2d, #0 + movi v23.2d, #0 + movi v24.2d, #0 + movi v25.2d, #0 + movi v26.2d, #0 + movi v27.2d, #0 + movi v28.2d, #0 + movi v29.2d, #0 + movi v30.2d, #0 + movi v31.2d, #0 + b 7f + +6: /* We need to do a full fpu restore. */ + ENABLE_AND_RESTORE_FPU64(x10, x8, x9, w8, w9) + +7: /* Restore registers. */ + ldp x30, x8, [sp, #(EXCEPTION_CONTEXT_X30_SP)] + ldp x9, x10, [sp, #(EXCEPTION_CONTEXT_PC_PSR)] + ldr x11, [sp, #(EXCEPTION_CONTEXT_TPIDR)] + ldr x18, [sp, #(EXCEPTION_CONTEXT_X18)] + + #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) + /* Since we're returning from an SVC, make sure SPSR.SS is cleared so that if we're single-stepping we break instantly on the instruction after the SVC. */ + bic x10, x10, #(1 << 21) + #endif + + msr sp_el0, x8 + msr elr_el1, x9 + msr spsr_el1, x10 + msr tpidr_el0, x11 + + /* Clear registers. */ + mov x8, xzr + mov x9, xzr + mov x10, xzr + mov x11, xzr + mov x12, xzr + mov x13, xzr + mov x14, xzr + mov x15, xzr + mov x16, xzr + mov x17, xzr + + /* Return. */ + add sp, sp, #(EXCEPTION_CONTEXT_SIZE) + ERET_WITH_SPECULATION_BARRIER + +/* ams::kern::arch::arm64::SvcHandler32() */ +.section .text._ZN3ams4kern4arch5arm6412SvcHandler32Ev, "ax", %progbits +.global _ZN3ams4kern4arch5arm6412SvcHandler32Ev +.type _ZN3ams4kern4arch5arm6412SvcHandler32Ev, %function +_ZN3ams4kern4arch5arm6412SvcHandler32Ev: + /* Ensure that our registers are 32-bit. */ + mov w0, w0 + mov w1, w1 + mov w2, w2 + mov w3, w3 + mov w4, w4 + mov w5, w5 + mov w6, w6 + mov w7, w7 + + /* Create a KExceptionContext for the exception. */ + sub sp, sp, #(EXCEPTION_CONTEXT_SIZE) + + /* Save system registers */ + mrs x17, elr_el1 + mrs x20, spsr_el1 + mrs x19, tpidr_el0 + ldr x18, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_CUR_THREAD)] + stp x17, x20, [sp, #(EXCEPTION_CONTEXT_PC_PSR)] + str x19, [sp, #(EXCEPTION_CONTEXT_TPIDR)] + + /* Save registers. */ + stp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + stp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + stp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)] + stp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)] + stp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)] + stp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)] + stp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)] + stp x14, xzr, [sp, #(EXCEPTION_CONTEXT_X14_X15)] + + /* Check if the SVC index is out of range. */ + mrs x8, esr_el1 + and x8, x8, #0xFF + cmp x8, #(AMS_KERN_NUM_SUPERVISOR_CALLS) + b.ge 3f + + /* Check the specific SVC permission bit for allowal. */ + mov x9, sp + add x9, x9, x8, lsr#3 + ldrb w9, [x9, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_SVC_PERMISSION)] + and x10, x8, #0x7 + lsr x10, x9, x10 + tst x10, #1 + b.eq 3f + + /* Check if our disable count allows us to call SVCs. */ + mrs x10, tpidrro_el0 + add x10, x10, #(THREAD_LOCAL_REGION_DISABLE_COUNT) + ldtrh w10, [x10] + cbz w10, 1f + + /* It might not, so check the stack params to see if we must not allow the SVC. */ + ldrb w10, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_IS_PINNED)] + cbz w10, 3f + +1: /* We can call the SVC. */ + adr x10, _ZN3ams4kern3svc16SvcTable64From32E + ldr x11, [x10, x8, lsl#3] + cbz x11, 3f + + + /* Note that we're calling the SVC. */ + ldrb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + orr w9, w9, #(THREAD_EXCEPTION_FLAG_IS_CALLING_SVC) + strb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + strb w8, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_CURRENT_SVC_ID)] + + /* If we should, trace the svc entry. */ +#if defined(MESOSPHERE_BUILD_FOR_TRACING) + sub sp, sp, #0x50 + stp x0, x1, [sp, #(8 * 0)] + stp x2, x3, [sp, #(8 * 2)] + stp x4, x5, [sp, #(8 * 4)] + stp x6, x7, [sp, #(8 * 6)] + str x11, [sp, #(8 * 8)] + mov x0, sp + bl _ZN3ams4kern3svc13TraceSvcEntryEPKm + ldp x0, x1, [sp, #(8 * 0)] + ldp x2, x3, [sp, #(8 * 2)] + ldp x4, x5, [sp, #(8 * 4)] + ldp x6, x7, [sp, #(8 * 6)] + ldr x11, [sp, #(8 * 8)] + add sp, sp, #0x50 +#endif + + /* Invoke the SVC handler. */ + msr daifclr, #2 + blr x11 + msr daifset, #2 + +2: /* We completed the SVC, and we should handle DPC. */ + /* Check the dpc flags. */ + ldrb w8, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_DPC_FLAGS)] + cbz w8, 5f + + /* We have DPC to do! */ + /* Save registers and call ams::kern::KDpcManager::HandleDpc(). */ + sub sp, sp, #0x20 + stp w0, w1, [sp, #(4 * 0)] + stp w2, w3, [sp, #(4 * 2)] + stp w4, w5, [sp, #(4 * 4)] + stp w6, w7, [sp, #(4 * 6)] + bl _ZN3ams4kern11KDpcManager9HandleDpcEv + ldp w0, w1, [sp, #(4 * 0)] + ldp w2, w3, [sp, #(4 * 2)] + ldp w4, w5, [sp, #(4 * 4)] + ldp w6, w7, [sp, #(4 * 6)] + add sp, sp, #0x20 + b 2b + +3: /* Invalid SVC. */ + /* Setup the context to call into HandleException. */ + stp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + stp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + stp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)] + stp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)] + stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X16_X17)] + stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X18_X19)] + stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X20_X21)] + stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X22_X23)] + stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X24_X25)] + stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X26_X27)] + stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X28_X29)] + stp xzr, xzr, [sp, #(EXCEPTION_CONTEXT_X30_SP)] + + /* Call ams::kern::arch::arm64::HandleException(ams::kern::arch::arm64::KExceptionContext *) */ + mov x0, sp + bl _ZN3ams4kern4arch5arm6415HandleExceptionEPNS2_17KExceptionContextE + + /* If we don't need to restore the fpu, skip restoring it. */ + ldrb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + tbz w9, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_CONTEXT_RESTORE_NEEDED), 4f + + /* Clear the needs-fpu-restore flag. */ + and w9, w9, #(~THREAD_EXCEPTION_FLAG_IS_FPU_CONTEXT_RESTORE_NEEDED) + strb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + + /* Enable and restore the fpu. */ + ENABLE_AND_RESTORE_FPU32(x10, x8, x9, w8, w9) + +4: /* Restore registers. */ + ldp x9, x10, [sp, #(EXCEPTION_CONTEXT_PC_PSR)] + ldr x11, [sp, #(EXCEPTION_CONTEXT_TPIDR)] + + #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) + /* Since we're returning from an SVC, make sure SPSR.SS is cleared so that if we're single-stepping we break instantly on the instruction after the SVC. */ + bic x10, x10, #(1 << 21) + #endif + + msr elr_el1, x9 + msr spsr_el1, x10 + msr tpidr_el0, x11 + ldp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + ldp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + ldp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)] + ldp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)] + ldp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)] + ldp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)] + ldp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)] + ldp x14, x15, [sp, #(EXCEPTION_CONTEXT_X14_X15)] + + /* Return. */ + add sp, sp, #(EXCEPTION_CONTEXT_SIZE) + ERET_WITH_SPECULATION_BARRIER + +5: /* Return from SVC. */ + + /* If we should, trace the svc exit. */ +#if defined(MESOSPHERE_BUILD_FOR_TRACING) + sub sp, sp, #0x40 + stp x0, x1, [sp, #(8 * 0)] + stp x2, x3, [sp, #(8 * 2)] + stp x4, x5, [sp, #(8 * 4)] + stp x6, x7, [sp, #(8 * 6)] + mov x0, sp + bl _ZN3ams4kern3svc12TraceSvcExitEPKm + ldp x0, x1, [sp, #(8 * 0)] + ldp x2, x3, [sp, #(8 * 2)] + ldp x4, x5, [sp, #(8 * 4)] + ldp x6, x7, [sp, #(8 * 6)] + add sp, sp, #0x40 +#endif + + /* Get our exception flags. */ + ldrb w9, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + + /* Clear in-svc and needs-fpu-restore flags. */ + and w8, w9, #(~(THREAD_EXCEPTION_FLAG_IS_FPU_CONTEXT_RESTORE_NEEDED)) + and w8, w8, #(~(THREAD_EXCEPTION_FLAG_IS_CALLING_SVC)) + strb w8, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + + /* If we don't need to restore the fpu, skip restoring it. */ + tbz w9, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_CONTEXT_RESTORE_NEEDED), 7f + + /* If we need to restore the fpu, check if we need to do a full restore. */ + tbnz w9, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_IN_USERMODE_EXCEPTION_HANDLER), 6f + + /* Enable the fpu. */ + ENABLE_FPU(x8) + + /* Get the thread context and restore fpsr/fpcr. */ + GET_THREAD_CONTEXT_AND_RESTORE_FPCR_FPSR(x10, x8, x9, w8, w9) + + /* Restore callee-saved registers to 32-bit fpu. */ + RESTORE_FPU32_CALLEE_SAVE_REGISTERS(x10) + + /* Clear caller-saved registers to 32-bit fpu. */ + movi v0.2d, #0 + movi v1.2d, #0 + movi v2.2d, #0 + movi v3.2d, #0 + movi v8.2d, #0 + movi v9.2d, #0 + movi v10.2d, #0 + movi v11.2d, #0 + movi v12.2d, #0 + movi v13.2d, #0 + movi v14.2d, #0 + movi v15.2d, #0 + b 7f + +6: /* We need to do a full fpu restore. */ + ENABLE_AND_RESTORE_FPU32(x10, x8, x9, w8, w9) + +7: /* Restore registers. */ + ldp x9, x10, [sp, #(EXCEPTION_CONTEXT_PC_PSR)] + ldr x11, [sp, #(EXCEPTION_CONTEXT_TPIDR)] + + #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) + /* Since we're returning from an SVC, make sure SPSR.SS is cleared so that if we're single-stepping we break instantly on the instruction after the SVC. */ + bic x10, x10, #(1 << 21) + #endif + + msr elr_el1, x9 + msr spsr_el1, x10 + msr tpidr_el0, x11 + ldp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)] + ldp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)] + ldp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)] + ldp x14, xzr, [sp, #(EXCEPTION_CONTEXT_X14_X15)] + + /* Return. */ + add sp, sp, #(EXCEPTION_CONTEXT_SIZE) + ERET_WITH_SPECULATION_BARRIER diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_light_ipc_asm.s b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_light_ipc_asm.s new file mode 100644 index 00000000..3c05d40e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_light_ipc_asm.s @@ -0,0 +1,139 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* ams::kern::svc::CallSendSyncRequestLight64() */ +.section .text._ZN3ams4kern3svc26CallSendSyncRequestLight64Ev, "ax", %progbits +.global _ZN3ams4kern3svc26CallSendSyncRequestLight64Ev +.type _ZN3ams4kern3svc26CallSendSyncRequestLight64Ev, %function +_ZN3ams4kern3svc26CallSendSyncRequestLight64Ev: + /* Allocate space for the light ipc data. */ + sub sp, sp, #(4 * 8) + + /* Store the light ipc data. */ + stp w1, w2, [sp, #(4 * 0)] + stp w3, w4, [sp, #(4 * 2)] + stp w5, w6, [sp, #(4 * 4)] + str w7, [sp, #(4 * 6)] + + /* Invoke the svc handler. */ + mov x1, sp + stp x29, x30, [sp, #-16]! + bl _ZN3ams4kern3svc22SendSyncRequestLight64EjPj + ldp x29, x30, [sp], #16 + + /* Load the light ipc data. */ + ldp w1, w2, [sp, #(4 * 0)] + ldp w3, w4, [sp, #(4 * 2)] + ldp w5, w6, [sp, #(4 * 4)] + ldr w7, [sp, #(4 * 6)] + + /* Free the stack space for the light ipc data. */ + add sp, sp, #(4 * 8) + + ret + +/* ams::kern::svc::CallSendSyncRequestLight64From32() */ +.section .text._ZN3ams4kern3svc32CallSendSyncRequestLight64From32Ev, "ax", %progbits +.global _ZN3ams4kern3svc32CallSendSyncRequestLight64From32Ev +.type _ZN3ams4kern3svc32CallSendSyncRequestLight64From32Ev, %function +_ZN3ams4kern3svc32CallSendSyncRequestLight64From32Ev: + /* Allocate space for the light ipc data. */ + sub sp, sp, #(4 * 8) + + /* Store the light ipc data. */ + stp w1, w2, [sp, #(4 * 0)] + stp w3, w4, [sp, #(4 * 2)] + stp w5, w6, [sp, #(4 * 4)] + str w7, [sp, #(4 * 6)] + + /* Invoke the svc handler. */ + mov x1, sp + stp x29, x30, [sp, #-16]! + bl _ZN3ams4kern3svc28SendSyncRequestLight64From32EjPj + ldp x29, x30, [sp], #16 + + /* Load the light ipc data. */ + ldp w1, w2, [sp, #(4 * 0)] + ldp w3, w4, [sp, #(4 * 2)] + ldp w5, w6, [sp, #(4 * 4)] + ldr w7, [sp, #(4 * 6)] + + /* Free the stack space for the light ipc data. */ + add sp, sp, #(4 * 8) + + ret + +/* ams::kern::svc::CallReplyAndReceiveLight64() */ +.section .text._ZN3ams4kern3svc26CallReplyAndReceiveLight64Ev, "ax", %progbits +.global _ZN3ams4kern3svc26CallReplyAndReceiveLight64Ev +.type _ZN3ams4kern3svc26CallReplyAndReceiveLight64Ev, %function +_ZN3ams4kern3svc26CallReplyAndReceiveLight64Ev: + /* Allocate space for the light ipc data. */ + sub sp, sp, #(4 * 8) + + /* Store the light ipc data. */ + stp w1, w2, [sp, #(4 * 0)] + stp w3, w4, [sp, #(4 * 2)] + stp w5, w6, [sp, #(4 * 4)] + str w7, [sp, #(4 * 6)] + + /* Invoke the svc handler. */ + mov x1, sp + stp x29, x30, [sp, #-16]! + bl _ZN3ams4kern3svc22ReplyAndReceiveLight64EjPj + ldp x29, x30, [sp], #16 + + /* Load the light ipc data. */ + ldp w1, w2, [sp, #(4 * 0)] + ldp w3, w4, [sp, #(4 * 2)] + ldp w5, w6, [sp, #(4 * 4)] + ldr w7, [sp, #(4 * 6)] + + /* Free the stack space for the light ipc data. */ + add sp, sp, #(4 * 8) + + ret + +/* ams::kern::svc::CallReplyAndReceiveLight64From32() */ +.section .text._ZN3ams4kern3svc32CallReplyAndReceiveLight64From32Ev, "ax", %progbits +.global _ZN3ams4kern3svc32CallReplyAndReceiveLight64From32Ev +.type _ZN3ams4kern3svc32CallReplyAndReceiveLight64From32Ev, %function +_ZN3ams4kern3svc32CallReplyAndReceiveLight64From32Ev: + /* Allocate space for the light ipc data. */ + sub sp, sp, #(4 * 8) + + /* Store the light ipc data. */ + stp w1, w2, [sp, #(4 * 0)] + stp w3, w4, [sp, #(4 * 2)] + stp w5, w6, [sp, #(4 * 4)] + str w7, [sp, #(4 * 6)] + + /* Invoke the svc handler. */ + mov x1, sp + stp x29, x30, [sp, #-16]! + bl _ZN3ams4kern3svc28ReplyAndReceiveLight64From32EjPj + ldp x29, x30, [sp], #16 + + /* Load the light ipc data. */ + ldp w1, w2, [sp, #(4 * 0)] + ldp w3, w4, [sp, #(4 * 2)] + ldp w5, w6, [sp, #(4 * 4)] + ldr w7, [sp, #(4 * 6)] + + /* Free the stack space for the light ipc data. */ + add sp, sp, #(4 * 8) + + ret diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_tables.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_tables.cpp new file mode 100644 index 00000000..cfc0cb7c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/arch/arm64/svc/kern_svc_tables.cpp @@ -0,0 +1,182 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#ifdef MESOSPHERE_USE_STUBBED_SVC_TABLES +#include <mesosphere/kern_debug_log.hpp> +#endif + +#include <mesosphere/svc/kern_svc_tables.hpp> +#include <vapours/svc/svc_codegen.hpp> + +namespace ams::kern::svc { + + /* Declare special prototypes for the light ipc handlers. */ + void CallSendSyncRequestLight64(); + void CallSendSyncRequestLight64From32(); + + void CallReplyAndReceiveLight64(); + void CallReplyAndReceiveLight64From32(); + + /* Declare special prototypes for ReturnFromException. */ + void CallReturnFromException64(); + void CallReturnFromException64From32(); + + /* Declare special prototype for (unsupported) CallCallSecureMonitor64From32. */ + void CallCallSecureMonitor64From32(); + + /* Declare special prototypes for WaitForAddress. */ + void CallWaitForAddress64(); + void CallWaitForAddress64From32(); + + namespace { + + #ifndef MESOSPHERE_USE_STUBBED_SVC_TABLES + #define DECLARE_SVC_STRUCT(ID, RETURN_TYPE, NAME, ...) \ + class NAME { \ + private: \ + using Impl = ::ams::svc::codegen::KernelSvcWrapper<::ams::kern::svc::NAME##64, ::ams::kern::svc::NAME##64From32>; \ + public: \ + static NOINLINE void Call64() { return Impl::Call64(); } \ + static NOINLINE void Call64From32() { return Impl::Call64From32(); } \ + }; + #else + #define DECLARE_SVC_STRUCT(ID, RETURN_TYPE, NAME, ...) \ + class NAME { \ + public: \ + static NOINLINE void Call64() { MESOSPHERE_PANIC("Stubbed Svc"#NAME"64 was called"); } \ + static NOINLINE void Call64From32() { MESOSPHERE_PANIC("Stubbed Svc"#NAME"64From32 was called"); } \ + }; + #endif + + + /* Set omit-frame-pointer to prevent GCC from emitting MOV X29, SP instructions. */ + #pragma GCC push_options + #pragma GCC optimize ("-O3") + #pragma GCC optimize ("omit-frame-pointer") + + AMS_SVC_FOREACH_KERN_DEFINITION(DECLARE_SVC_STRUCT, _) + + #pragma GCC pop_options + + constexpr const std::array<SvcTableEntry, NumSupervisorCalls> SvcTable64From32Impl = [] { + std::array<SvcTableEntry, NumSupervisorCalls> table = {}; + + #define AMS_KERN_SVC_SET_TABLE_ENTRY(ID, RETURN_TYPE, NAME, ...) \ + if (table[ID] == nullptr) { table[ID] = NAME::Call64From32; } + AMS_SVC_FOREACH_KERN_DEFINITION(AMS_KERN_SVC_SET_TABLE_ENTRY, _) + #undef AMS_KERN_SVC_SET_TABLE_ENTRY + + table[svc::SvcId_SendSyncRequestLight] = CallSendSyncRequestLight64From32; + table[svc::SvcId_ReplyAndReceiveLight] = CallReplyAndReceiveLight64From32; + + table[svc::SvcId_ReturnFromException] = CallReturnFromException64From32; + + table[svc::SvcId_CallSecureMonitor] = CallCallSecureMonitor64From32; + + table[svc::SvcId_WaitForAddress] = CallWaitForAddress64From32; + + return table; + }(); + + constexpr const std::array<SvcTableEntry, NumSupervisorCalls> SvcTable64Impl = [] { + std::array<SvcTableEntry, NumSupervisorCalls> table = {}; + + #define AMS_KERN_SVC_SET_TABLE_ENTRY(ID, RETURN_TYPE, NAME, ...) \ + if (table[ID] == nullptr) { table[ID] = NAME::Call64; } + AMS_SVC_FOREACH_KERN_DEFINITION(AMS_KERN_SVC_SET_TABLE_ENTRY, _) + #undef AMS_KERN_SVC_SET_TABLE_ENTRY + + table[svc::SvcId_SendSyncRequestLight] = CallSendSyncRequestLight64; + table[svc::SvcId_ReplyAndReceiveLight] = CallReplyAndReceiveLight64; + + table[svc::SvcId_ReturnFromException] = CallReturnFromException64; + + table[svc::SvcId_WaitForAddress] = CallWaitForAddress64; + + return table; + }(); + + constexpr bool IsValidSvcTable(const std::array<SvcTableEntry, NumSupervisorCalls> &table) { + for (size_t i = 0; i < NumSupervisorCalls; i++) { + if (table[i] != nullptr) { + return true; + } + } + + return false; + } + + static_assert(IsValidSvcTable(SvcTable64Impl)); + static_assert(IsValidSvcTable(SvcTable64From32Impl)); + + } + + constinit const std::array<SvcTableEntry, NumSupervisorCalls> SvcTable64 = SvcTable64Impl; + + constinit const std::array<SvcTableEntry, NumSupervisorCalls> SvcTable64From32 = SvcTable64From32Impl; + + void PatchSvcTableEntry(const SvcTableEntry *table, u32 id, SvcTableEntry entry); + + namespace { + + /* NOTE: Although the SVC tables are constants, our global constructor will run before .rodata is protected R--. */ + class SvcTablePatcher { + private: + using SvcTable = std::array<SvcTableEntry, NumSupervisorCalls>; + private: + static SvcTablePatcher s_instance; + private: + ALWAYS_INLINE const SvcTableEntry *GetTableData(const SvcTable *table) { + if (table != nullptr) { + return table->data(); + } else { + return nullptr; + } + } + + NOINLINE void PatchTables(const SvcTableEntry *table_64, const SvcTableEntry *table_64_from_32) { + /* Get the target firmware. */ + const auto target_fw = kern::GetTargetFirmware(); + + /* 10.0.0 broke the ABI for QueryIoMapping, and renamed it to QueryMemoryMapping. */ + if (target_fw < TargetFirmware_10_0_0) { + if (table_64) { ::ams::kern::svc::PatchSvcTableEntry(table_64, svc::SvcId_QueryMemoryMapping, LegacyQueryIoMapping::Call64); } + if (table_64_from_32) { ::ams::kern::svc::PatchSvcTableEntry(table_64_from_32, svc::SvcId_QueryMemoryMapping, LegacyQueryIoMapping::Call64From32); } + } + + /* 6.0.0 broke the ABI for GetFutureThreadInfo, and renamed it to GetDebugFutureThreadInfo. */ + if (target_fw < TargetFirmware_6_0_0) { + static_assert(svc::SvcId_GetDebugFutureThreadInfo == svc::SvcId_LegacyGetFutureThreadInfo); + if (table_64) { ::ams::kern::svc::PatchSvcTableEntry(table_64, svc::SvcId_GetDebugFutureThreadInfo, LegacyGetFutureThreadInfo::Call64); } + if (table_64_from_32) { ::ams::kern::svc::PatchSvcTableEntry(table_64_from_32, svc::SvcId_GetDebugFutureThreadInfo, LegacyGetFutureThreadInfo::Call64From32); } + } + + /* 3.0.0 broke the ABI for ContinueDebugEvent. */ + if (target_fw < TargetFirmware_3_0_0) { + if (table_64) { ::ams::kern::svc::PatchSvcTableEntry(table_64, svc::SvcId_ContinueDebugEvent, LegacyContinueDebugEvent::Call64); } + if (table_64_from_32) { ::ams::kern::svc::PatchSvcTableEntry(table_64_from_32, svc::SvcId_ContinueDebugEvent, LegacyContinueDebugEvent::Call64From32); } + } + } + public: + SvcTablePatcher(const SvcTable *table_64, const SvcTable *table_64_from_32) { + PatchTables(GetTableData(table_64), GetTableData(table_64_from_32)); + } + }; + + SvcTablePatcher SvcTablePatcher::s_instance(std::addressof(SvcTable64), std::addressof(SvcTable64From32)); + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_atomics_registers.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_atomics_registers.hpp new file mode 100644 index 00000000..5f29af2a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_atomics_registers.hpp @@ -0,0 +1,21 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#define ATOMICS_AP0_TRIGGER 0x000 +#define ATOMICS_AP0_RESULT(id) (0xc00 + id * 4) + +#define TRIGGER_CMD_GET 4 diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_bpmp_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_bpmp_api.hpp new file mode 100644 index 00000000..d78223ad --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_bpmp_api.hpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere.hpp> + +/* Message Flags */ +#define BPMP_MSG_DO_ACK (1 << 0) +#define BPMP_MSG_RING_DOORBELL (1 << 1) + +/* Messages */ +#define MRQ_PING 0 +#define MRQ_ENABLE_SUSPEND 17 +#define MRQ_CPU_PMIC_SELECT 28 + +/* BPMP Power states. */ +#define TEGRA_BPMP_PM_CC1 9 +#define TEGRA_BPMP_PM_CC4 12 +#define TEGRA_BPMP_PM_CC6 14 +#define TEGRA_BPMP_PM_CC7 15 +#define TEGRA_BPMP_PM_SC1 17 +#define TEGRA_BPMP_PM_SC2 18 +#define TEGRA_BPMP_PM_SC3 19 +#define TEGRA_BPMP_PM_SC4 20 +#define TEGRA_BPMP_PM_SC7 23 + +/* Channel states. */ +#define CH_MASK(ch) (0x3u << ((ch) * 2)) +#define SL_SIGL(ch) (0x0u << ((ch) * 2)) +#define SL_QUED(ch) (0x1u << ((ch) * 2)) +#define MA_FREE(ch) (0x2u << ((ch) * 2)) +#define MA_ACKD(ch) (0x3u << ((ch) * 2)) + +constexpr inline int MessageSize = 0x80; +constexpr inline int MessageDataSizeMax = 0x78; + +struct MailboxData { + s32 code; + s32 flags; + u8 data[MessageDataSizeMax]; +}; + +static_assert(ams::util::is_pod<MailboxData>::value); +static_assert(sizeof(MailboxData) == MessageSize); + +struct ChannelData { + MailboxData *ib; + MailboxData *ob; +}; + + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_ictlr_registers.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_ictlr_registers.hpp new file mode 100644 index 00000000..b073b39b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_ictlr_registers.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#define ICTLR_REG_BASE(irq) ((((irq) - 32) >> 5) * 0x100) +#define ICTLR_FIR_SET(irq) (ICTLR_REG_BASE(irq) + 0x18) +#define ICTLR_FIR_CLR(irq) (ICTLR_REG_BASE(irq) + 0x1c) +#define FIR_BIT(irq) (1 << ((irq) & 0x1f)) + +#define INT_GIC_BASE (0) +#define INT_PRI_BASE (INT_GIC_BASE + 32) +#define INT_SHR_SEM_OUTBOX_IBF (INT_PRI_BASE + 6) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_k_device_page_table.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_k_device_page_table.cpp new file mode 100644 index 00000000..292c2575 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_k_device_page_table.cpp @@ -0,0 +1,1447 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +#if defined(MESOSPHERE_BUILD_FOR_DEBUGGING) || defined(MESOSPHERE_BUILD_FOR_AUDITING) +#define MESOSPHERE_ENABLE_MEMORY_CONTROLLER_INTERRUPT +#endif + +namespace ams::kern::board::nintendo::nx { + + namespace { + + /* Definitions. */ + constexpr size_t PageDirectorySize = KPageTableManager::PageTableSize; + constexpr size_t PageTableSize = KPageTableManager::PageTableSize; + static_assert(PageDirectorySize == PageSize); + + 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 == 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 size_t DeviceAsidRegisterOffsets[] = { + [ams::svc::DeviceName_Afi] = MC_SMMU_AFI_ASID, + [ams::svc::DeviceName_Avpc] = MC_SMMU_AVPC_ASID, + [ams::svc::DeviceName_Dc] = MC_SMMU_DC_ASID, + [ams::svc::DeviceName_Dcb] = MC_SMMU_DCB_ASID, + [ams::svc::DeviceName_Hc] = MC_SMMU_HC_ASID, + [ams::svc::DeviceName_Hda] = MC_SMMU_HDA_ASID, + [ams::svc::DeviceName_Isp2] = MC_SMMU_ISP2_ASID, + [ams::svc::DeviceName_MsencNvenc] = MC_SMMU_MSENC_NVENC_ASID, + [ams::svc::DeviceName_Nv] = MC_SMMU_NV_ASID, + [ams::svc::DeviceName_Nv2] = MC_SMMU_NV2_ASID, + [ams::svc::DeviceName_Ppcs] = MC_SMMU_PPCS_ASID, + [ams::svc::DeviceName_Sata] = MC_SMMU_SATA_ASID, + [ams::svc::DeviceName_Vi] = MC_SMMU_VI_ASID, + [ams::svc::DeviceName_Vic] = MC_SMMU_VIC_ASID, + [ams::svc::DeviceName_XusbHost] = MC_SMMU_XUSB_HOST_ASID, + [ams::svc::DeviceName_XusbDev] = MC_SMMU_XUSB_DEV_ASID, + [ams::svc::DeviceName_Tsec] = MC_SMMU_TSEC_ASID, + [ams::svc::DeviceName_Ppcs1] = MC_SMMU_PPCS1_ASID, + [ams::svc::DeviceName_Dc1] = MC_SMMU_DC1_ASID, + [ams::svc::DeviceName_Sdmmc1a] = MC_SMMU_SDMMC1A_ASID, + [ams::svc::DeviceName_Sdmmc2a] = MC_SMMU_SDMMC2A_ASID, + [ams::svc::DeviceName_Sdmmc3a] = MC_SMMU_SDMMC3A_ASID, + [ams::svc::DeviceName_Sdmmc4a] = MC_SMMU_SDMMC4A_ASID, + [ams::svc::DeviceName_Isp2b] = MC_SMMU_ISP2B_ASID, + [ams::svc::DeviceName_Gpu] = MC_SMMU_GPU_ASID, + [ams::svc::DeviceName_Gpub] = MC_SMMU_GPUB_ASID, + [ams::svc::DeviceName_Ppcs2] = MC_SMMU_PPCS2_ASID, + [ams::svc::DeviceName_Nvdec] = MC_SMMU_NVDEC_ASID, + [ams::svc::DeviceName_Ape] = MC_SMMU_APE_ASID, + [ams::svc::DeviceName_Se] = MC_SMMU_SE_ASID, + [ams::svc::DeviceName_Nvjpg] = MC_SMMU_NVJPG_ASID, + [ams::svc::DeviceName_Hc1] = MC_SMMU_HC1_ASID, + [ams::svc::DeviceName_Se1] = MC_SMMU_SE1_ASID, + [ams::svc::DeviceName_Axiap] = MC_SMMU_AXIAP_ASID, + [ams::svc::DeviceName_Etr] = MC_SMMU_ETR_ASID, + [ams::svc::DeviceName_Tsecb] = MC_SMMU_TSECB_ASID, + [ams::svc::DeviceName_Tsec1] = MC_SMMU_TSEC1_ASID, + [ams::svc::DeviceName_Tsecb1] = MC_SMMU_TSECB1_ASID, + [ams::svc::DeviceName_Nvdec1] = MC_SMMU_NVDEC1_ASID, + }; + static_assert(util::size(DeviceAsidRegisterOffsets) == ams::svc::DeviceName_Count); + constexpr bool DeviceAsidRegistersValid = [] { + for (size_t i = 0; i < ams::svc::DeviceName_Count; i++) { + if (DeviceAsidRegisterOffsets[i] == 0 || !util::IsAligned(DeviceAsidRegisterOffsets[i], sizeof(u32))) { + return false; + } + } + return true; + }(); + + static_assert(DeviceAsidRegistersValid); + + constexpr ALWAYS_INLINE int GetDeviceAsidRegisterOffset(ams::svc::DeviceName dev) { + if (dev < ams::svc::DeviceName_Count) { + return DeviceAsidRegisterOffsets[dev]; + } else { + return -1; + } + } + + constexpr ams::svc::DeviceName HsDevices[] = { + ams::svc::DeviceName_Afi, + ams::svc::DeviceName_Dc, + ams::svc::DeviceName_Dcb, + ams::svc::DeviceName_Hda, + ams::svc::DeviceName_Isp2, + ams::svc::DeviceName_Sata, + ams::svc::DeviceName_Vi, + ams::svc::DeviceName_XusbHost, + ams::svc::DeviceName_XusbDev, + ams::svc::DeviceName_Tsec, + ams::svc::DeviceName_Dc1, + ams::svc::DeviceName_Sdmmc1a, + ams::svc::DeviceName_Sdmmc2a, + ams::svc::DeviceName_Sdmmc3a, + ams::svc::DeviceName_Sdmmc4a, + ams::svc::DeviceName_Isp2b, + ams::svc::DeviceName_Gpu, + ams::svc::DeviceName_Gpub, + ams::svc::DeviceName_Axiap, + ams::svc::DeviceName_Etr, + ams::svc::DeviceName_Tsecb, + ams::svc::DeviceName_Tsec1, + ams::svc::DeviceName_Tsecb1, + }; + constexpr size_t NumHsDevices = util::size(HsDevices); + + constexpr u64 HsDeviceMask = [] { + u64 mask = 0; + for (size_t i = 0; i < NumHsDevices; i++) { + mask |= 1ul << HsDevices[i]; + } + return mask; + }(); + + constexpr ALWAYS_INLINE bool IsHsSupported(ams::svc::DeviceName dv) { + return (HsDeviceMask & (1ul << dv)) != 0; + } + + constexpr ALWAYS_INLINE bool IsValidPhysicalAddress(KPhysicalAddress addr) { + return (static_cast<u64>(GetInteger(addr)) & ~PhysicalAddressMask) == 0; + } + + constexpr struct { u64 start; u64 end; } SmmuSupportedRanges[] = { + [ams::svc::DeviceName_Afi] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Avpc] = { 0x00000000ul, 0x0FFFFFFFFul }, + [ams::svc::DeviceName_Dc] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Dcb] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Hc] = { 0x00000000ul, 0x0FFFFFFFFul }, + [ams::svc::DeviceName_Hda] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Isp2] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_MsencNvenc] = { 0x00000000ul, 0x0FFFFFFFFul }, + [ams::svc::DeviceName_Nv] = { 0x00000000ul, 0x0FFFFFFFFul }, + [ams::svc::DeviceName_Nv2] = { 0x00000000ul, 0x0FFFFFFFFul }, + [ams::svc::DeviceName_Ppcs] = { 0x00000000ul, 0x0FFFFFFFFul }, + [ams::svc::DeviceName_Sata] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Vi] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Vic] = { 0x00000000ul, 0x0FFFFFFFFul }, + [ams::svc::DeviceName_XusbHost] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_XusbDev] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Tsec] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Ppcs1] = { 0x00000000ul, 0x0FFFFFFFFul }, + [ams::svc::DeviceName_Dc1] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Sdmmc1a] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Sdmmc2a] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Sdmmc3a] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Sdmmc4a] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Isp2b] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Gpu] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Gpub] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Ppcs2] = { 0x00000000ul, 0x0FFFFFFFFul }, + [ams::svc::DeviceName_Nvdec] = { 0x00000000ul, 0x0FFFFFFFFul }, + [ams::svc::DeviceName_Ape] = { 0x00000000ul, 0x0FFFFFFFFul }, + [ams::svc::DeviceName_Se] = { 0x00000000ul, 0x0FFFFFFFFul }, + [ams::svc::DeviceName_Nvjpg] = { 0x00000000ul, 0x0FFFFFFFFul }, + [ams::svc::DeviceName_Hc1] = { 0x00000000ul, 0x0FFFFFFFFul }, + [ams::svc::DeviceName_Se1] = { 0x00000000ul, 0x0FFFFFFFFul }, + [ams::svc::DeviceName_Axiap] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Etr] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Tsecb] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Tsec1] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Tsecb1] = { 0x00000000ul, 0x3FFFFFFFFul }, + [ams::svc::DeviceName_Nvdec1] = { 0x00000000ul, 0x0FFFFFFFFul }, + }; + static_assert(util::size(SmmuSupportedRanges) == ams::svc::DeviceName_Count); + + constexpr bool IsAttachable(ams::svc::DeviceName device_name, u64 space_address, u64 space_size) { + if (0 <= device_name && device_name < ams::svc::DeviceName_Count) { + const auto &range = SmmuSupportedRanges[device_name]; + return range.start <= space_address && (space_address + space_size - 1) <= range.end; + } + return false; + } + + /* 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)); + } + + template<Bit... Bits> + constexpr ALWAYS_INLINE u32 SelectBits() const { + constexpr u32 Mask = ((1u << Bits) | ...); + return m_value & Mask; + } + + 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, KPhysicalAddress addr, bool t) { + return EncodeBit(Bit_Readable, r) | EncodeBit(Bit_Writeable, w) | EncodeBit(Bit_NonSecure, ns) | EncodeBit(Bit_Table, t) | static_cast<u32>(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(KPhysicalAddress 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->SelectBits<Bit_Readable, Bit_Writeable>(); } + + constexpr ALWAYS_INLINE u32 GetAttributes() const { return this->SelectBits<Bit_Readable, Bit_Writeable, Bit_NonSecure>(); } + + constexpr ALWAYS_INLINE KPhysicalAddress GetPhysicalAddress() const { return (static_cast<u64>(m_value) << DevicePageBits) & PhysicalAddressMask; } + + + ALWAYS_INLINE void InvalidateAttributes() { this->SetValue(m_value & ~(0xCu << 28)); } + 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, KPhysicalAddress addr) { + MESOSPHERE_ASSERT(IsValidPhysicalAddress(addr)); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(addr), DevicePageSize)); + this->SetValue(EncodeValue(r, w, ns, addr, true)); + } + + ALWAYS_INLINE void SetLargePage(bool r, bool w, bool ns, KPhysicalAddress addr) { + MESOSPHERE_ASSERT(IsValidPhysicalAddress(addr)); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(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, KPhysicalAddress addr) { + MESOSPHERE_ASSERT(IsValidPhysicalAddress(addr)); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(addr), DevicePageSize)); + this->SetValue(EncodeValue(r, w, ns, addr, true)); + } + }; + + class KDeviceAsidManager { + private: + using WordType = u32; + static constexpr u8 ReservedAsids[] = { 0, 1, 2, 3 }; + static constexpr size_t NumReservedAsids = util::size(ReservedAsids); + static constexpr size_t BitsPerWord = BITSIZEOF(WordType); + static constexpr size_t NumWords = AsidCount / BitsPerWord; + static constexpr WordType FullWord = ~WordType(0u); + private: + WordType m_state[NumWords]; + KLightLock m_lock; + private: + constexpr void ReserveImpl(u8 asid) { + m_state[asid / BitsPerWord] |= (1u << (asid % BitsPerWord)); + } + + constexpr void ReleaseImpl(u8 asid) { + m_state[asid / BitsPerWord] &= ~(1u << (asid % BitsPerWord)); + } + + static constexpr ALWAYS_INLINE WordType ClearLeadingZero(WordType value) { + return __builtin_clzll(value) - (BITSIZEOF(unsigned long long) - BITSIZEOF(WordType)); + } + public: + constexpr KDeviceAsidManager() : m_state(), m_lock() { + for (size_t i = 0; i < NumReservedAsids; i++) { + this->ReserveImpl(ReservedAsids[i]); + } + } + + Result Reserve(u8 *out, size_t num_desired) { + KScopedLightLock lk(m_lock); + MESOSPHERE_ASSERT(num_desired > 0); + + size_t num_reserved = 0; + for (size_t i = 0; i < NumWords; i++) { + while (m_state[i] != FullWord) { + const WordType clear_bit = (m_state[i] + 1) ^ (m_state[i]); + m_state[i] |= clear_bit; + out[num_reserved++] = static_cast<u8>(BitsPerWord * i + BitsPerWord - 1 - ClearLeadingZero(clear_bit)); + R_SUCCEED_IF(num_reserved == num_desired); + } + } + + /* We failed, so free what we reserved. */ + for (size_t i = 0; i < num_reserved; i++) { + this->ReleaseImpl(out[i]); + } + R_THROW(svc::ResultOutOfResource()); + } + + void Release(u8 asid) { + KScopedLightLock lk(m_lock); + this->ReleaseImpl(asid); + } + }; + + /* Globals. */ + constinit KLightLock g_lock; + constinit u8 g_reserved_asid; + constinit KPhysicalAddress g_memory_controller_address{Null<KPhysicalAddress>}; + constinit KPhysicalAddress g_reserved_table_phys_addr{Null<KPhysicalAddress>}; + constinit KDeviceAsidManager g_asid_manager; + constinit u32 g_saved_page_tables[AsidCount]; + constinit u32 g_saved_asid_registers[ams::svc::DeviceName_Count]; + + /* Memory controller access functionality. */ + void WriteMcRegister(size_t offset, u32 value) { + KSystemControl::WriteRegisterPrivileged(GetInteger(g_memory_controller_address) + offset, value); + } + + u32 ReadMcRegister(size_t offset) { + return KSystemControl::ReadRegisterPrivileged(GetInteger(g_memory_controller_address) + offset); + } + + /* Memory controller interrupt functionality. */ + + constexpr const char * const MemoryControllerClientNames[138] = { + [ 0] = "csr_ptcr (ptc)", + [ 1] = "csr_display0a (dc)", + [ 2] = "csr_display0ab (dcb)", + [ 3] = "csr_display0b (dc)", + [ 4] = "csr_display0bb (dcb)", + [ 5] = "csr_display0c (dc)", + [ 6] = "csr_display0cb (dcb)", + [ 7] = "Unknown Client", + [ 8] = "Unknown Client", + [ 9] = "Unknown Client", + [ 10] = "Unknown Client", + [ 11] = "Unknown Client", + [ 12] = "Unknown Client", + [ 13] = "Unknown Client", + [ 14] = "csr_afir (afi)", + [ 15] = "csr_avpcarm7r (avpc)", + [ 16] = "csr_displayhc (dc)", + [ 17] = "csr_displayhcb (dcb)", + [ 18] = "Unknown Client", + [ 19] = "Unknown Client", + [ 20] = "Unknown Client", + [ 21] = "csr_hdar (hda)", + [ 22] = "csr_host1xdmar (hc)", + [ 23] = "csr_host1xr (hc)", + [ 24] = "Unknown Client", + [ 25] = "Unknown Client", + [ 26] = "Unknown Client", + [ 27] = "Unknown Client", + [ 28] = "csr_nvencsrd (nvenc)", + [ 29] = "csr_ppcsahbdmar (ppcs)", + [ 30] = "csr_ppcsahbslvr (ppcs)", + [ 31] = "csr_satar (sata)", + [ 32] = "Unknown Client", + [ 33] = "Unknown Client", + [ 34] = "Unknown Client", + [ 35] = "Unknown Client", + [ 36] = "Unknown Client", + [ 37] = "Unknown Client", + [ 38] = "Unknown Client", + [ 39] = "csr_mpcorer (cpu)", + [ 40] = "Unknown Client", + [ 41] = "Unknown Client", + [ 42] = "Unknown Client", + [ 43] = "csw_nvencswr (nvenc)", + [ 44] = "Unknown Client", + [ 45] = "Unknown Client", + [ 46] = "Unknown Client", + [ 47] = "Unknown Client", + [ 48] = "Unknown Client", + [ 49] = "csw_afiw (afi)", + [ 50] = "csw_avpcarm7w (avpc)", + [ 51] = "Unknown Client", + [ 52] = "Unknown Client", + [ 53] = "csw_hdaw (hda)", + [ 54] = "csw_host1xw (hc)", + [ 55] = "Unknown Client", + [ 56] = "Unknown Client", + [ 57] = "csw_mpcorew (cpu)", + [ 58] = "Unknown Client", + [ 59] = "csw_ppcsahbdmaw (ppcs)", + [ 60] = "csw_ppcsahbslvw (ppcs)", + [ 61] = "csw_sataw (sata)", + [ 62] = "Unknown Client", + [ 63] = "Unknown Client", + [ 64] = "Unknown Client", + [ 65] = "Unknown Client", + [ 66] = "Unknown Client", + [ 67] = "Unknown Client", + [ 68] = "csr_ispra (isp2)", + [ 69] = "Unknown Client", + [ 70] = "csw_ispwa (isp2)", + [ 71] = "csw_ispwb (isp2)", + [ 72] = "Unknown Client", + [ 73] = "Unknown Client", + [ 74] = "csr_xusb_hostr (xusb_host)", + [ 75] = "csw_xusb_hostw (xusb_host)", + [ 76] = "csr_xusb_devr (xusb_dev)", + [ 77] = "csw_xusb_devw (xusb_dev)", + [ 78] = "csr_isprab (isp2b)", + [ 79] = "Unknown Client", + [ 80] = "csw_ispwab (isp2b)", + [ 81] = "csw_ispwbb (isp2b)", + [ 82] = "Unknown Client", + [ 83] = "Unknown Client", + [ 84] = "csr_tsecsrd (tsec)", + [ 85] = "csw_tsecswr (tsec)", + [ 86] = "csr_a9avpscr (a9avp)", + [ 87] = "csw_a9avpscw (a9avp)", + [ 88] = "csr_gpusrd (gpu)", + [ 89] = "csw_gpuswr (gpu)", + [ 90] = "csr_displayt (dc)", + [ 91] = "Unknown Client", + [ 92] = "Unknown Client", + [ 93] = "Unknown Client", + [ 94] = "Unknown Client", + [ 95] = "Unknown Client", + [ 96] = "csr_sdmmcra (sdmmc1a)", + [ 97] = "csr_sdmmcraa (sdmmc2a)", + [ 98] = "csr_sdmmcr (sdmmc3a)", + [ 99] = "csr_sdmmcrab (sdmmc4a)", + [100] = "csw_sdmmcwa (sdmmc1a)", + [101] = "csw_sdmmcwaa (sdmmc2a)", + [102] = "csw_sdmmcw (sdmmc3a)", + [103] = "csw_sdmmcwab (sdmmc4a)", + [104] = "Unknown Client", + [105] = "Unknown Client", + [106] = "Unknown Client", + [107] = "Unknown Client", + [108] = "csr_vicsrd (vic)", + [109] = "csw_vicswr (vic)", + [110] = "Unknown Client", + [111] = "Unknown Client", + [112] = "Unknown Client", + [113] = "Unknown Client", + [114] = "csw_viw (vi)", + [115] = "csr_displayd (dc)", + [116] = "Unknown Client", + [117] = "Unknown Client", + [118] = "Unknown Client", + [119] = "Unknown Client", + [120] = "csr_nvdecsrd (nvdec)", + [121] = "csw_nvdecswr (nvdec)", + [122] = "csr_aper (ape)", + [123] = "csw_apew (ape)", + [124] = "Unknown Client", + [125] = "Unknown Client", + [126] = "csr_nvjpgsrd (nvjpg)", + [127] = "csw_nvjpgswr (nvjpg)", + [128] = "csr_sesrd (se)", + [129] = "csw_seswr (se)", + [130] = "csr_axiapr (axiap)", + [131] = "csw_axiapw (axiap)", + [132] = "csr_etrr (etr)", + [133] = "csw_etrw (etr)", + [134] = "csr_tsecsrdb (tsecb)", + [135] = "csw_tsecswrb (tsecb)", + [136] = "csr_gpusrd2 (gpu)", + [137] = "csw_gpuswr2 (gpu)", + }; + + constexpr const char * GetMemoryControllerClientName(size_t i) { + if (i < util::size(MemoryControllerClientNames)) { + return MemoryControllerClientNames[i]; + } + return "Unknown Client"; + } + + constexpr const char * const MemoryControllerErrorTypes[8] = { + "RSVD", + "Unknown", + "DECERR_EMEM", + "SECURITY_TRUSTZONE", + "SECURITY_CARVEOUT", + "Unknown", + "INVALID_SMMU_PAGE", + "Unknown", + }; + + class KMemoryControllerInterruptTask : public KInterruptTask { + public: + constexpr KMemoryControllerInterruptTask() : KInterruptTask() { /* ... */ } + + virtual KInterruptTask *OnInterrupt(s32 interrupt_id) override { + MESOSPHERE_UNUSED(interrupt_id); + return this; + } + + virtual void DoTask() override { + #if defined(MESOSPHERE_ENABLE_MEMORY_CONTROLLER_INTERRUPT) + { + /* Clear the interrupt when we're done. */ + ON_SCOPE_EXIT { Kernel::GetInterruptManager().ClearInterrupt(KInterruptName_MemoryController, GetCurrentCoreId()); }; + + /* Get and clear the interrupt status. */ + u32 int_status, err_status, err_adr; + { + int_status = ReadMcRegister(MC_INTSTATUS); + err_status = ReadMcRegister(MC_ERR_STATUS); + err_adr = ReadMcRegister(MC_ERR_ADR); + + WriteMcRegister(MC_INTSTATUS, int_status); + } + + /* Print the interrupt. */ + { + constexpr auto GetBits = [](u32 value, size_t ofs, size_t count) ALWAYS_INLINE_LAMBDA { + return (value >> ofs) & ((1u << count) - 1); + }; + + constexpr auto GetBit = [GetBits](u32 value, size_t ofs) ALWAYS_INLINE_LAMBDA { + return (value >> ofs) & 1u; + }; + + MESOSPHERE_RELEASE_LOG("sMMU error interrupt\n"); + MESOSPHERE_RELEASE_LOG(" MC_INTSTATUS=%08x\n", int_status); + MESOSPHERE_RELEASE_LOG(" DECERR_GENERALIZED_CARVEOUT=%d\n", GetBit(int_status, 17)); + MESOSPHERE_RELEASE_LOG(" DECERR_MTS=%d\n", GetBit(int_status, 16)); + MESOSPHERE_RELEASE_LOG(" SECERR_SEC=%d\n", GetBit(int_status, 13)); + MESOSPHERE_RELEASE_LOG(" DECERR_VPR=%d\n", GetBit(int_status, 12)); + MESOSPHERE_RELEASE_LOG(" INVALID_APB_ASID_UPDATE=%d\n", GetBit(int_status, 11)); + MESOSPHERE_RELEASE_LOG(" INVALID_SMMU_PAGE=%d\n", GetBit(int_status, 10)); + MESOSPHERE_RELEASE_LOG(" ARBITRATION_EMEM=%d\n", GetBit(int_status, 9)); + MESOSPHERE_RELEASE_LOG(" SECURITY_VIOLATION=%d\n", GetBit(int_status, 8)); + MESOSPHERE_RELEASE_LOG(" DECERR_EMEM=%d\n", GetBit(int_status, 6)); + MESOSPHERE_RELEASE_LOG(" MC_ERRSTATUS=%08x\n", err_status); + MESOSPHERE_RELEASE_LOG(" ERR_TYPE=%d (%s)\n", GetBits(err_status, 28, 3), MemoryControllerErrorTypes[GetBits(err_status, 28, 3)]); + MESOSPHERE_RELEASE_LOG(" ERR_INVALID_SMMU_PAGE_READABLE=%d\n", GetBit (err_status, 27)); + MESOSPHERE_RELEASE_LOG(" ERR_INVALID_SMMU_PAGE_WRITABLE=%d\n", GetBit (err_status, 26)); + MESOSPHERE_RELEASE_LOG(" ERR_INVALID_SMMU_NONSECURE=%d\n", GetBit (err_status, 25)); + MESOSPHERE_RELEASE_LOG(" ERR_ADR_HI=%x\n", GetBits(err_status, 20, 2)); + MESOSPHERE_RELEASE_LOG(" ERR_SWAP=%d\n", GetBit (err_status, 18)); + MESOSPHERE_RELEASE_LOG(" ERR_SECURITY=%d %s\n", GetBit (err_status, 17), GetBit(err_status, 17) ? "SECURE" : "NONSECURE"); + MESOSPHERE_RELEASE_LOG(" ERR_RW=%d %s\n", GetBit (err_status, 16), GetBit(err_status, 16) ? "WRITE" : "READ"); + MESOSPHERE_RELEASE_LOG(" ERR_ADR1=%x\n", GetBits(err_status, 12, 3)); + MESOSPHERE_RELEASE_LOG(" ERR_ID=%d %s\n", GetBits(err_status, 0, 8), GetMemoryControllerClientName(GetBits(err_status, 0, 8))); + MESOSPHERE_RELEASE_LOG(" MC_ERRADR=%08x\n", err_adr); + MESOSPHERE_RELEASE_LOG(" ERR_ADR=%lx\n", (static_cast<u64>(GetBits(err_status, 20, 2)) << 32) | static_cast<u64>(err_adr)); + MESOSPHERE_RELEASE_LOG("\n"); + } + } + #endif + } + }; + + /* Interrupt task global. */ + constinit KMemoryControllerInterruptTask g_mc_interrupt_task; + + /* Memory controller utilities. */ + ALWAYS_INLINE void SmmuSynchronizationBarrier() { + ReadMcRegister(MC_SMMU_CONFIG); + } + + ALWAYS_INLINE void InvalidatePtc() { + WriteMcRegister(MC_SMMU_PTC_FLUSH_0, 0); + } + + ALWAYS_INLINE void InvalidatePtc(KPhysicalAddress address) { + WriteMcRegister(MC_SMMU_PTC_FLUSH_1, (static_cast<u64>(GetInteger(address)) >> 32)); + WriteMcRegister(MC_SMMU_PTC_FLUSH_0, (GetInteger(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, KDeviceVirtualAddress address, TlbFlushVaMatch match) { + return ((match_asid ? 1u : 0u) << 31) | ((asid & 0x7F) << 24) | (((address & 0xFFC00000u) >> DevicePageBits)) | (match); + } + + ALWAYS_INLINE void InvalidateTlb() { + return WriteMcRegister(MC_SMMU_TLB_FLUSH, EncodeTlbFlushValue(false, 0, 0, TlbFlushVaMatch_All)); + } + + ALWAYS_INLINE void InvalidateTlb(u8 asid) { + return WriteMcRegister(MC_SMMU_TLB_FLUSH, EncodeTlbFlushValue(true, asid, 0, TlbFlushVaMatch_All)); + } + + ALWAYS_INLINE void InvalidateTlbSection(u8 asid, KDeviceVirtualAddress address) { + return WriteMcRegister(MC_SMMU_TLB_FLUSH, EncodeTlbFlushValue(true, asid, address, TlbFlushVaMatch_Section)); + } + + void SetTable(u8 asid, KPhysicalAddress address) { + /* Write the table address. */ + { + KScopedLightLock lk(g_lock); + + WriteMcRegister(MC_SMMU_PTB_ASID, asid); + WriteMcRegister(MC_SMMU_PTB_DATA, EntryBase::EncodePtbDataValue(address)); + + SmmuSynchronizationBarrier(); + } + + /* Ensure consistency. */ + InvalidatePtc(); + InvalidateTlb(asid); + SmmuSynchronizationBarrier(); + } + + } + + void KDevicePageTable::Initialize() { + /* Set the memory controller register address. */ + g_memory_controller_address = KMemoryLayout::GetDevicePhysicalAddress(KMemoryRegionType_MemoryController); + + /* Allocate a page to use as a reserved/no device table. */ + auto &ptm = Kernel::GetSystemSystemResource().GetPageTableManager(); + + const KVirtualAddress table_virt_addr = ptm.Allocate(); + MESOSPHERE_ABORT_UNLESS(table_virt_addr != Null<KVirtualAddress>); + const KPhysicalAddress table_phys_addr = GetPageTablePhysicalAddress(table_virt_addr); + MESOSPHERE_ASSERT(IsValidPhysicalAddress(table_phys_addr)); + ptm.Open(table_virt_addr, 1); + + /* Save the page. Note that it is a pre-condition that the page is cleared, when allocated from the system page table manager. */ + /* NOTE: Nintendo does not check the result of StoreDataCache. */ + cpu::StoreDataCache(GetVoidPointer(table_virt_addr), PageDirectorySize); + g_reserved_table_phys_addr = table_phys_addr; + + /* Reserve an asid to correspond to no device. */ + MESOSPHERE_R_ABORT_UNLESS(g_asid_manager.Reserve(std::addressof(g_reserved_asid), 1)); + + /* Set all asids to the reserved table. */ + static_assert(AsidCount <= std::numeric_limits<u8>::max()); + for (size_t i = 0; i < AsidCount; i++) { + SetTable(static_cast<u8>(i), g_reserved_table_phys_addr); + } + + /* Set all devices to the reserved asid. */ + for (size_t i = 0; i < ams::svc::DeviceName_Count; i++) { + u32 value = 0x80000000u; + if (IsHsSupported(static_cast<ams::svc::DeviceName>(i))) { + for (size_t t = 0; t < TableCount; t++) { + value |= (g_reserved_asid << (BITSIZEOF(u8) * t)); + } + } else { + value |= g_reserved_asid; + } + + WriteMcRegister(GetDeviceAsidRegisterOffset(static_cast<ams::svc::DeviceName>(i)), value); + SmmuSynchronizationBarrier(); + } + + /* Ensure consistency. */ + InvalidatePtc(); + InvalidateTlb(); + SmmuSynchronizationBarrier(); + + /* Clear int status. */ + WriteMcRegister(MC_INTSTATUS, ReadMcRegister(MC_INTSTATUS)); + + /* If we're setting an interrupt handler, unmask all interrupts. */ + #if defined(MESOSPHERE_ENABLE_MEMORY_CONTROLLER_INTERRUPT) + { + WriteMcRegister(MC_INTMASK, 0x33D40); + } + #endif + + /* Enable the SMMU */ + WriteMcRegister(MC_SMMU_CONFIG, 1); + SmmuSynchronizationBarrier(); + + /* Install interrupt handler. */ + #if defined(MESOSPHERE_ENABLE_MEMORY_CONTROLLER_INTERRUPT) + { + Kernel::GetInterruptManager().BindHandler(std::addressof(g_mc_interrupt_task), KInterruptName_MemoryController, GetCurrentCoreId(), KInterruptController::PriorityLevel_High, true, true); + } + #endif + } + + void KDevicePageTable::Lock() { + g_lock.Lock(); + } + + void KDevicePageTable::Unlock() { + g_lock.Unlock(); + } + + void KDevicePageTable::Sleep() { + /* Save all page tables. */ + for (size_t i = 0; i < AsidCount; ++i) { + WriteMcRegister(MC_SMMU_PTB_ASID, i); + SmmuSynchronizationBarrier(); + g_saved_page_tables[i] = ReadMcRegister(MC_SMMU_PTB_DATA); + } + + /* Save all asid registers. */ + for (size_t i = 0; i < ams::svc::DeviceName_Count; ++i) { + g_saved_asid_registers[i] = ReadMcRegister(GetDeviceAsidRegisterOffset(static_cast<ams::svc::DeviceName>(i))); + } + } + + void KDevicePageTable::Wakeup() { + /* Synchronize. */ + InvalidatePtc(); + InvalidateTlb(); + SmmuSynchronizationBarrier(); + + /* Disable the SMMU */ + WriteMcRegister(MC_SMMU_CONFIG, 0); + + /* Restore the page tables. */ + for (size_t i = 0; i < AsidCount; ++i) { + WriteMcRegister(MC_SMMU_PTB_ASID, i); + SmmuSynchronizationBarrier(); + WriteMcRegister(MC_SMMU_PTB_DATA, g_saved_page_tables[i]); + } + SmmuSynchronizationBarrier(); + + /* Restore the asid registers. */ + for (size_t i = 0; i < ams::svc::DeviceName_Count; ++i) { + WriteMcRegister(GetDeviceAsidRegisterOffset(static_cast<ams::svc::DeviceName>(i)), g_saved_asid_registers[i]); + SmmuSynchronizationBarrier(); + } + + /* Synchronize. */ + InvalidatePtc(); + InvalidateTlb(); + SmmuSynchronizationBarrier(); + + /* Enable the SMMU */ + WriteMcRegister(MC_SMMU_CONFIG, 1); + SmmuSynchronizationBarrier(); + } + + /* Member functions. */ + + Result KDevicePageTable::Initialize(u64 space_address, u64 space_size) { + /* Ensure space is valid. */ + R_UNLESS(((space_address + space_size - 1) & ~DeviceVirtualAddressMask) == 0, svc::ResultInvalidMemoryRegion()); + + /* Determine extents. */ + const size_t start_index = space_address / DeviceRegionSize; + const size_t end_index = (space_address + space_size - 1) / DeviceRegionSize; + + /* Get the page table manager. */ + auto &ptm = Kernel::GetSystemSystemResource().GetPageTableManager(); + + /* Clear the tables. */ + static_assert(TableCount == (1ul << DeviceVirtualAddressBits) / DeviceRegionSize); + for (size_t i = 0; i < TableCount; ++i) { + m_tables[i] = Null<KVirtualAddress>; + } + + /* Ensure that we clean up the tables on failure. */ + ON_RESULT_FAILURE { + for (size_t i = start_index; i <= end_index; ++i) { + if (m_tables[i] != Null<KVirtualAddress> && ptm.Close(m_tables[i], 1)) { + ptm.Free(m_tables[i]); + } + } + }; + + /* Allocate a table for all required indices. */ + for (size_t i = start_index; i <= end_index; ++i) { + const KVirtualAddress table_vaddr = ptm.Allocate(); + R_UNLESS(table_vaddr != Null<KVirtualAddress>, svc::ResultOutOfMemory()); + + MESOSPHERE_ASSERT(IsValidPhysicalAddress(GetPageTablePhysicalAddress(table_vaddr))); + + ptm.Open(table_vaddr, 1); + cpu::StoreDataCache(GetVoidPointer(table_vaddr), PageDirectorySize); + m_tables[i] = table_vaddr; + } + + /* Clear asids. */ + for (size_t i = 0; i < TableCount; ++i) { + m_table_asids[i] = g_reserved_asid; + } + + /* Reserve asids for the tables. */ + R_TRY(g_asid_manager.Reserve(std::addressof(m_table_asids[start_index]), end_index - start_index + 1)); + + /* Associate tables with asids. */ + for (size_t i = start_index; i <= end_index; ++i) { + SetTable(m_table_asids[i], GetPageTablePhysicalAddress(m_tables[i])); + } + + /* Set member variables. */ + m_attached_device = 0; + m_attached_value = (1u << 31) | m_table_asids[0]; + m_detached_value = (1u << 31) | g_reserved_asid; + + m_hs_attached_value = (1u << 31); + m_hs_detached_value = (1u << 31); + for (size_t i = 0; i < TableCount; ++i) { + m_hs_attached_value |= (m_table_asids[i] << (i * BITSIZEOF(u8))); + m_hs_detached_value |= (g_reserved_asid << (i * BITSIZEOF(u8))); + } + + /* We succeeded. */ + R_SUCCEED(); + } + + void KDevicePageTable::Finalize() { + /* Get the page table manager. */ + auto &ptm = Kernel::GetSystemSystemResource().GetPageTableManager(); + + /* Detach from all devices. */ + { + KScopedLightLock lk(g_lock); + for (size_t i = 0; i < ams::svc::DeviceName_Count; ++i) { + const auto device_name = static_cast<ams::svc::DeviceName>(i); + if ((m_attached_device & (1ul << device_name)) != 0) { + WriteMcRegister(GetDeviceAsidRegisterOffset(device_name), IsHsSupported(device_name) ? m_hs_detached_value : m_detached_value); + SmmuSynchronizationBarrier(); + } + } + } + + /* Forcibly unmap all pages. */ + this->UnmapImpl(0, (1ul << DeviceVirtualAddressBits), false); + + /* Release all asids. */ + for (size_t i = 0; i < TableCount; ++i) { + if (m_table_asids[i] != g_reserved_asid) { + /* Set the table to the reserved table. */ + SetTable(m_table_asids[i], g_reserved_table_phys_addr); + + /* Close the table. */ + const KVirtualAddress table_vaddr = m_tables[i]; + MESOSPHERE_ASSERT(ptm.GetRefCount(table_vaddr) == 1); + MESOSPHERE_ABORT_UNLESS(ptm.Close(table_vaddr, 1)); + + /* Free the table. */ + ptm.Free(table_vaddr); + + /* Release the asid. */ + g_asid_manager.Release(m_table_asids[i]); + } + } + } + + Result KDevicePageTable::Attach(ams::svc::DeviceName device_name, u64 space_address, u64 space_size) { + /* Validate the device name. */ + R_UNLESS(0 <= device_name, svc::ResultNotFound()); + R_UNLESS(device_name < ams::svc::DeviceName_Count, svc::ResultNotFound()); + + /* Check that the device isn't already attached. */ + R_UNLESS((m_attached_device & (1ul << device_name)) == 0, svc::ResultBusy()); + + /* Validate that the space is allowed for the device. */ + const size_t end_index = (space_address + space_size - 1) / DeviceRegionSize; + R_UNLESS(end_index == 0 || IsHsSupported(device_name), svc::ResultInvalidCombination()); + + /* Validate that the device can be attached. */ + R_UNLESS(IsAttachable(device_name, space_address, space_size), svc::ResultInvalidCombination()); + + /* Get the device asid register offset. */ + const int reg_offset = GetDeviceAsidRegisterOffset(device_name); + R_UNLESS(reg_offset >= 0, svc::ResultNotFound()); + + /* Determine the old/new values. */ + const u32 old_val = IsHsSupported(device_name) ? m_hs_detached_value : m_detached_value; + const u32 new_val = IsHsSupported(device_name) ? m_hs_attached_value : m_attached_value; + + /* Attach the device. */ + { + KScopedLightLock lk(g_lock); + + /* Validate that the device is unclaimed. */ + R_UNLESS((ReadMcRegister(reg_offset) | (1u << 31)) == (old_val | (1u << 31)), svc::ResultBusy()); + + /* Claim the device. */ + WriteMcRegister(reg_offset, new_val); + SmmuSynchronizationBarrier(); + + /* Ensure that we claimed it successfully. */ + if (ReadMcRegister(reg_offset) != new_val) { + WriteMcRegister(reg_offset, old_val); + SmmuSynchronizationBarrier(); + + R_THROW(svc::ResultNotFound()); + } + } + + /* Mark the device as attached. */ + m_attached_device |= (1ul << device_name); + + R_SUCCEED(); + } + + Result KDevicePageTable::Detach(ams::svc::DeviceName device_name) { + /* Validate the device name. */ + R_UNLESS(0 <= device_name, svc::ResultNotFound()); + R_UNLESS(device_name < ams::svc::DeviceName_Count, svc::ResultNotFound()); + + /* Check that the device is already attached. */ + R_UNLESS((m_attached_device & (1ul << device_name)) != 0, svc::ResultInvalidState()); + + /* Get the device asid register offset. */ + const int reg_offset = GetDeviceAsidRegisterOffset(device_name); + R_UNLESS(reg_offset >= 0, svc::ResultNotFound()); + + /* Determine the old/new values. */ + const u32 old_val = IsHsSupported(device_name) ? m_hs_attached_value : m_attached_value; + const u32 new_val = IsHsSupported(device_name) ? m_hs_detached_value : m_detached_value; + + /* When not building for debug, the old value might be unused. */ + AMS_UNUSED(old_val); + + /* Detach the device. */ + { + KScopedLightLock lk(g_lock); + + /* Check that the device is attached. */ + MESOSPHERE_ASSERT(ReadMcRegister(reg_offset) == old_val); + + /* Release the device. */ + WriteMcRegister(reg_offset, new_val); + SmmuSynchronizationBarrier(); + + /* Check that the device was released. */ + MESOSPHERE_ASSERT((ReadMcRegister(reg_offset) | (1u << 31)) == (new_val | 1u << 31)); + } + + /* Mark the device as detached. */ + m_attached_device &= ~(1ul << device_name); + + R_SUCCEED(); + } + + bool KDevicePageTable::IsFree(KDeviceVirtualAddress address, u64 size) const { + MESOSPHERE_ASSERT((address & ~DeviceVirtualAddressMask) == 0); + MESOSPHERE_ASSERT(((address + size - 1) & ~DeviceVirtualAddressMask) == 0); + + /* Walk the directory, looking for entries. */ + u64 remaining = size; + while (remaining > 0) { + const size_t l0_index = (address / DeviceRegionSize); + const size_t l1_index = (address % DeviceRegionSize) / DeviceLargePageSize; + const size_t l2_index = (address % DeviceLargePageSize) / DevicePageSize; + + const PageDirectoryEntry *l1 = GetPointer<PageDirectoryEntry>(m_tables[l0_index]); + if (l1 == nullptr || !l1[l1_index].IsValid()) { + const size_t remaining_in_entry = (PageTableSize / sizeof(PageTableEntry)) - l2_index; + const size_t map_count = std::min<size_t>(remaining_in_entry, remaining / DevicePageSize); + + address += DevicePageSize * map_count; + remaining -= DevicePageSize * map_count; + } else if (l1[l1_index].IsTable()) { + const PageTableEntry *l2 = GetPointer<PageTableEntry>(GetPageTableVirtualAddress(l1[l1_index].GetPhysicalAddress())); + + const size_t remaining_in_entry = (PageTableSize / sizeof(PageTableEntry)) - l2_index; + const size_t map_count = std::min<size_t>(remaining_in_entry, remaining / DevicePageSize); + + for (size_t i = 0; i < map_count; ++i) { + if (l2[l2_index + i].IsValid()) { + return false; + } + } + + address += DevicePageSize * map_count; + remaining -= DevicePageSize * map_count; + } else { + /* If we have an entry, we're not free. */ + return false; + } + } + + return true; + } + + Result KDevicePageTable::MapDevicePage(KPhysicalAddress phys_addr, u64 size, KDeviceVirtualAddress address, ams::svc::MemoryPermission device_perm) { + /* Ensure that the physical address is valid. */ + R_UNLESS(IsValidPhysicalAddress(static_cast<u64>(GetInteger(phys_addr)) + size - 1), svc::ResultInvalidCurrentMemory()); + MESOSPHERE_ASSERT((address & ~DeviceVirtualAddressMask) == 0); + MESOSPHERE_ASSERT(((address + size - 1) & ~DeviceVirtualAddressMask) == 0); + + /* Get the memory manager and page table manager. */ + KMemoryManager &mm = Kernel::GetMemoryManager(); + KPageTableManager &ptm = Kernel::GetSystemSystemResource().GetPageTableManager(); + + /* Cache permissions. */ + const bool read = (device_perm & ams::svc::MemoryPermission_Read) != 0; + const bool write = (device_perm & ams::svc::MemoryPermission_Write) != 0; + + /* Walk the directory. */ + u64 remaining = size; + while (remaining > 0) { + const size_t l0_index = (address / DeviceRegionSize); + const size_t l1_index = (address % DeviceRegionSize) / DeviceLargePageSize; + const size_t l2_index = (address % DeviceLargePageSize) / DevicePageSize; + + /* Get and validate l1. */ + PageDirectoryEntry *l1 = GetPointer<PageDirectoryEntry>(m_tables[l0_index]); + MESOSPHERE_ASSERT(l1 != nullptr); + + /* Setup an l1 table/entry, if needed. */ + if (!l1[l1_index].IsTable()) { + /* Check that an entry doesn't already exist. */ + MESOSPHERE_ASSERT(!l1[l1_index].IsValid()); + + /* If we can make an l1 entry, do so. */ + if (l2_index == 0 && util::IsAligned(GetInteger(phys_addr), DeviceLargePageSize) && remaining >= DeviceLargePageSize) { + /* Set the large page. */ + l1[l1_index].SetLargePage(read, write, true, phys_addr); + cpu::StoreDataCache(std::addressof(l1[l1_index]), sizeof(PageDirectoryEntry)); + + /* Synchronize. */ + InvalidatePtc(GetPageTablePhysicalAddress(KVirtualAddress(std::addressof(l1[l1_index])))); + InvalidateTlbSection(m_table_asids[l0_index], address); + SmmuSynchronizationBarrier(); + + /* Open references to the pages. */ + mm.Open(phys_addr, DeviceLargePageSize / PageSize); + + /* Advance. */ + phys_addr += DeviceLargePageSize; + address += DeviceLargePageSize; + remaining -= DeviceLargePageSize; + continue; + } else { + /* Make an l1 table. */ + const KVirtualAddress table_vaddr = ptm.Allocate(); + R_UNLESS(table_vaddr != Null<KVirtualAddress>, svc::ResultOutOfMemory()); + MESOSPHERE_ASSERT(IsValidPhysicalAddress(GetPageTablePhysicalAddress(table_vaddr))); + cpu::StoreDataCache(GetVoidPointer(table_vaddr), PageTableSize); + + /* Set the l1 table. */ + l1[l1_index].SetTable(true, true, true, GetPageTablePhysicalAddress(table_vaddr)); + cpu::StoreDataCache(std::addressof(l1[l1_index]), sizeof(PageDirectoryEntry)); + + /* Synchronize. */ + InvalidatePtc(GetPageTablePhysicalAddress(KVirtualAddress(std::addressof(l1[l1_index])))); + InvalidateTlbSection(m_table_asids[l0_index], address); + SmmuSynchronizationBarrier(); + } + } + + /* If we get to this point, l1 must be a table. */ + MESOSPHERE_ASSERT(l1[l1_index].IsTable()); + + /* Map l2 entries. */ + { + PageTableEntry *l2 = GetPointer<PageTableEntry>(GetPageTableVirtualAddress(l1[l1_index].GetPhysicalAddress())); + + const size_t remaining_in_entry = (PageTableSize / sizeof(PageTableEntry)) - l2_index; + const size_t map_count = std::min<size_t>(remaining_in_entry, remaining / DevicePageSize); + + /* Set the entries. */ + for (size_t i = 0; i < map_count; ++i) { + MESOSPHERE_ASSERT(!l2[l2_index + i].IsValid()); + l2[l2_index + i].SetPage(read, write, true, phys_addr + DevicePageSize * i); + + /* Add a reference to the l2 page (from the l2 entry page). */ + ptm.Open(KVirtualAddress(l2), 1); + } + cpu::StoreDataCache(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(GetPageTablePhysicalAddress(KVirtualAddress(std::addressof(l2[i])))); + } + + /* Synchronize. */ + InvalidateTlbSection(m_table_asids[l0_index], address); + SmmuSynchronizationBarrier(); + + /* Open references to the pages. */ + mm.Open(phys_addr, (map_count * DevicePageSize) / PageSize); + + /* Advance. */ + phys_addr += map_count * DevicePageSize; + address += map_count * DevicePageSize; + remaining -= map_count * DevicePageSize; + } + } + + R_SUCCEED(); + } + + Result KDevicePageTable::MapImpl(KProcessPageTable *page_table, KProcessAddress process_address, size_t size, KDeviceVirtualAddress device_address, ams::svc::MemoryPermission device_perm, bool is_aligned) { + + /* Ensure that the region we're mapping to is free. */ + R_UNLESS(this->IsFree(device_address, size), svc::ResultInvalidCurrentMemory()); + + /* Ensure that if we fail, we unmap anything we mapped. */ + ON_RESULT_FAILURE { this->UnmapImpl(device_address, size, false); }; + + /* Iterate, mapping device pages. */ + KDeviceVirtualAddress cur_addr = device_address; + size_t mapped_size = 0; + while (mapped_size < size) { + /* Map the next contiguous range. */ + size_t cur_size; + { + /* Get the current contiguous range. */ + KPageTableBase::MemoryRange contig_range; + R_TRY(page_table->OpenMemoryRangeForMapDeviceAddressSpace(std::addressof(contig_range), process_address + mapped_size, size - mapped_size, ConvertToKMemoryPermission(device_perm), is_aligned)); + + /* Ensure we close the range when we're done. */ + ON_SCOPE_EXIT { contig_range.Close(); }; + + /* Get the current size. */ + cur_size = contig_range.GetSize(); + + /* Map the device page. */ + R_TRY(this->MapDevicePage(contig_range.GetAddress(), cur_size, cur_addr, device_perm)); + } + + /* Advance. */ + cur_addr += cur_size; + mapped_size += cur_size; + } + + R_SUCCEED(); + } + + void KDevicePageTable::UnmapImpl(KDeviceVirtualAddress address, u64 size, bool force) { + MESOSPHERE_UNUSED(force); + MESOSPHERE_ASSERT((address & ~DeviceVirtualAddressMask) == 0); + MESOSPHERE_ASSERT(((address + size - 1) & ~DeviceVirtualAddressMask) == 0); + + /* Get the memory manager and page table manager. */ + KMemoryManager &mm = Kernel::GetMemoryManager(); + KPageTableManager &ptm = Kernel::GetSystemSystemResource().GetPageTableManager(); + + /* Make a page group for the pages we're closing. */ + KPageGroup pg(Kernel::GetSystemSystemResource().GetBlockInfoManagerPointer()); + + /* Walk the directory. */ + u64 remaining = size; + while (remaining > 0) { + const size_t l0_index = (address / DeviceRegionSize); + const size_t l1_index = (address % DeviceRegionSize) / DeviceLargePageSize; + const size_t l2_index = (address % DeviceLargePageSize) / DevicePageSize; + + /* Get and validate l1. */ + PageDirectoryEntry *l1 = GetPointer<PageDirectoryEntry>(m_tables[l0_index]); + + /* Check if there's nothing mapped at l1. */ + if (l1 == nullptr || !l1[l1_index].IsValid()) { + const size_t remaining_in_entry = (PageTableSize / sizeof(PageTableEntry)) - l2_index; + const size_t map_count = std::min<size_t>(remaining_in_entry, remaining / DevicePageSize); + + /* Advance. */ + address += map_count * DevicePageSize; + remaining -= map_count * DevicePageSize; + } else if (l1[l1_index].IsTable()) { + /* Dealing with an l1 table. */ + PageTableEntry *l2 = GetPointer<PageTableEntry>(GetPageTableVirtualAddress(l1[l1_index].GetPhysicalAddress())); + + const size_t remaining_in_entry = (PageTableSize / sizeof(PageTableEntry)) - l2_index; + const size_t map_count = std::min<size_t>(remaining_in_entry, remaining / DevicePageSize); + size_t num_closed = 0; + + /* Invalidate the attributes of all entries. */ + for (size_t i = 0; i < map_count; ++i) { + if (l2[l2_index + i].IsValid()) { + l2[l2_index + i].InvalidateAttributes(); + ++num_closed; + } + } + cpu::StoreDataCache(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(GetPageTablePhysicalAddress(KVirtualAddress(std::addressof(l2[i])))); + } + SmmuSynchronizationBarrier(); + + /* Close the memory manager's references to the pages. */ + { + KPhysicalAddress contig_phys_addr = Null<KPhysicalAddress>; + size_t contig_count = 0; + for (size_t i = 0; i < map_count; ++i) { + /* Get the physical address. */ + const KPhysicalAddress phys_addr = l2[l2_index + i].GetPhysicalAddress(); + MESOSPHERE_ASSERT(phys_addr == Null<KPhysicalAddress> || IsHeapPhysicalAddress(phys_addr)); + + /* Fully invalidate the entry. */ + l2[l2_index + i].Invalidate(); + + if (contig_count == 0) { + /* Ensure that our address/count is valid. */ + contig_phys_addr = phys_addr; + contig_count = contig_phys_addr != Null<KPhysicalAddress> ? 1 : 0; + } else if (phys_addr == Null<KPhysicalAddress> || phys_addr != (contig_phys_addr + (contig_count * DevicePageSize))) { + /* If we're no longer contiguous, close the range we've been building. */ + mm.Close(contig_phys_addr, (contig_count * DevicePageSize) / PageSize); + + contig_phys_addr = phys_addr; + contig_count = contig_phys_addr != Null<KPhysicalAddress> ? 1 : 0; + } else { + ++contig_count; + } + } + + if (contig_count > 0) { + mm.Close(contig_phys_addr, (contig_count * DevicePageSize) / PageSize); + } + } + + /* Close the pages. */ + if (ptm.Close(KVirtualAddress(l2), num_closed)) { + /* Invalidate the l1 entry. */ + l1[l1_index].Invalidate(); + cpu::StoreDataCache(std::addressof(l1[l1_index]), sizeof(PageDirectoryEntry)); + + /* Synchronize. */ + InvalidatePtc(GetPageTablePhysicalAddress(KVirtualAddress(std::addressof(l1[l1_index])))); + SmmuSynchronizationBarrier(); + + /* Free the l2 page. */ + ptm.Free(KVirtualAddress(l2)); + } + + /* Advance. */ + address += map_count * DevicePageSize; + remaining -= map_count * DevicePageSize; + } else { + /* Dealing with an l1 entry. */ + MESOSPHERE_ASSERT(l2_index == 0); + + /* Get the physical address. */ + const KPhysicalAddress phys_addr = l1[l1_index].GetPhysicalAddress(); + MESOSPHERE_ASSERT(IsHeapPhysicalAddress(phys_addr)); + + /* Invalidate the entry. */ + l1[l1_index].Invalidate(); + cpu::StoreDataCache(std::addressof(l1[l1_index]), sizeof(PageDirectoryEntry)); + + /* Synchronize. */ + InvalidatePtc(GetPageTablePhysicalAddress(KVirtualAddress(std::addressof(l1[l1_index])))); + InvalidateTlbSection(m_table_asids[l0_index], address); + SmmuSynchronizationBarrier(); + + /* Close references. */ + mm.Close(phys_addr, DeviceLargePageSize / PageSize); + + /* Advance. */ + address += DeviceLargePageSize; + remaining -= DeviceLargePageSize; + } + } + } + + bool KDevicePageTable::Compare(KProcessPageTable *page_table, KProcessAddress process_address, size_t size, KDeviceVirtualAddress device_address) const { + MESOSPHERE_ASSERT((device_address & ~DeviceVirtualAddressMask) == 0); + MESOSPHERE_ASSERT(((device_address + size - 1) & ~DeviceVirtualAddressMask) == 0); + + /* We need to traverse the ranges that make up our mapping, to make sure they're all good. Start by getting a contiguous range. */ + KPageTableBase::MemoryRange contig_range; + if (R_FAILED(page_table->OpenMemoryRangeForUnmapDeviceAddressSpace(std::addressof(contig_range), process_address, size))) { + return false; + } + + /* Ensure that we close the range when we're done. */ + bool range_open = true; + ON_SCOPE_EXIT { if (range_open) { contig_range.Close(); } }; + + /* Walk the directory. */ + KProcessAddress cur_process_address = process_address; + size_t remaining_size = size; + KPhysicalAddress cur_phys_address = contig_range.GetAddress(); + size_t remaining_in_range = contig_range.GetSize(); + bool first = true; + u32 first_attr = 0; + while (remaining_size > 0) { + /* Convert the device address to a series of indices. */ + const size_t l0_index = (device_address / DeviceRegionSize); + const size_t l1_index = (device_address % DeviceRegionSize) / DeviceLargePageSize; + const size_t l2_index = (device_address % DeviceLargePageSize) / DevicePageSize; + + /* Get and validate l1. */ + const PageDirectoryEntry *l1 = GetPointer<PageDirectoryEntry>(m_tables[l0_index]); + if (!(l1 != nullptr && l1[l1_index].IsValid())) { + return false; + } + + if (l1[l1_index].IsTable()) { + /* We're acting on an l2 entry. */ + const PageTableEntry *l2 = GetPointer<PageTableEntry>(GetPageTableVirtualAddress(l1[l1_index].GetPhysicalAddress())); + + /* Determine the number of pages to check. */ + const size_t remaining_in_entry = (PageTableSize / sizeof(PageTableEntry)) - l2_index; + const size_t map_count = std::min<size_t>(remaining_in_entry, remaining_size / DevicePageSize); + + /* Check each page. */ + for (size_t i = 0; i < map_count; ++i) { + /* Ensure the l2 entry is valid. */ + if (!l2[l2_index + i].IsValid()) { + return false; + } + + /* Check that the attributes match the first attributes we encountered. */ + const u32 cur_attr = l2[l2_index + i].GetAttributes(); + if (!first && cur_attr != first_attr) { + return false; + } + + /* If there's nothing remaining in the range, refresh the range. */ + if (remaining_in_range == 0) { + contig_range.Close(); + + range_open = false; + if (R_FAILED(page_table->OpenMemoryRangeForUnmapDeviceAddressSpace(std::addressof(contig_range), cur_process_address, remaining_size))) { + return false; + } + range_open = true; + + cur_phys_address = contig_range.GetAddress(); + remaining_in_range = contig_range.GetSize(); + } + + /* Check that the physical address is expected. */ + if (l2[l2_index + i].GetPhysicalAddress() != cur_phys_address) { + return false; + } + + /* Advance. */ + cur_phys_address += DevicePageSize; + cur_process_address += DevicePageSize; + remaining_size -= DevicePageSize; + remaining_in_range -= DevicePageSize; + + first = false; + first_attr = cur_attr; + } + + /* Advance the device address. */ + device_address += map_count * DevicePageSize; + } else { + /* We're acting on an l1 entry. */ + if (!(l2_index == 0 && remaining_size >= DeviceLargePageSize)) { + return false; + } + + /* Check that the attributes match the first attributes we encountered. */ + const u32 cur_attr = l1[l1_index].GetAttributes(); + if (!first && cur_attr != first_attr) { + return false; + } + + /* If there's nothing remaining in the range, refresh the range. */ + if (remaining_in_range == 0) { + contig_range.Close(); + + range_open = false; + if (R_FAILED(page_table->OpenMemoryRangeForUnmapDeviceAddressSpace(std::addressof(contig_range), cur_process_address, remaining_size))) { + return false; + } + range_open = true; + + cur_phys_address = contig_range.GetAddress(); + remaining_in_range = contig_range.GetSize(); + } + + /* Check that the physical address is expected, and there's enough in the range. */ + if (remaining_in_range < DeviceLargePageSize || l1[l1_index].GetPhysicalAddress() != cur_phys_address) { + return false; + } + + /* Advance. */ + cur_phys_address += DeviceLargePageSize; + cur_process_address += DeviceLargePageSize; + remaining_size -= DeviceLargePageSize; + remaining_in_range -= DeviceLargePageSize; + + first = false; + first_attr = cur_attr; + + /* Advance the device address. */ + device_address += DeviceLargePageSize; + } + } + + /* The range is valid! */ + return true; + } + + Result KDevicePageTable::Map(KProcessPageTable *page_table, KProcessAddress process_address, size_t size, KDeviceVirtualAddress device_address, ams::svc::MemoryPermission device_perm, bool is_aligned, bool is_io) { + /* Validate address/size. */ + MESOSPHERE_ASSERT((device_address & ~DeviceVirtualAddressMask) == 0); + MESOSPHERE_ASSERT(((device_address + size - 1) & ~DeviceVirtualAddressMask) == 0); + + /* IO is not supported on NX board. */ + MESOSPHERE_ASSERT(!is_io); + MESOSPHERE_UNUSED(is_io); + + /* Map the pages. */ + R_RETURN(this->MapImpl(page_table, process_address, size, device_address, device_perm, is_aligned)); + } + + Result KDevicePageTable::Unmap(KProcessPageTable *page_table, KProcessAddress process_address, size_t size, KDeviceVirtualAddress device_address) { + /* Validate address/size. */ + MESOSPHERE_ASSERT((device_address & ~DeviceVirtualAddressMask) == 0); + MESOSPHERE_ASSERT(((device_address + size - 1) & ~DeviceVirtualAddressMask) == 0); + + /* Ensure the page group is correct. */ + R_UNLESS(this->Compare(page_table, process_address, size, device_address), svc::ResultInvalidCurrentMemory()); + + /* Unmap the pages. */ + this->UnmapImpl(device_address, size, false); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_k_io_pool.board.nintendo_nx.inc b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_k_io_pool.board.nintendo_nx.inc new file mode 100644 index 00000000..e109c5d6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_k_io_pool.board.nintendo_nx.inc @@ -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 <http://www.gnu.org/licenses/>. + */ + +constexpr IoRegionExtents g_io_region_extents[4] = { + { KPhysicalAddress(0x12000000), 224_MB }, /* PCIE_A2 */ + { Null<KPhysicalAddress>, 0 }, + { Null<KPhysicalAddress>, 0 }, + { Null<KPhysicalAddress>, 0 }, +}; + +constexpr bool IsValidIoPoolTypeImpl(ams::svc::IoPoolType pool_type) { + return pool_type == ams::svc::IoPoolType_PcieA2; +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_k_sleep_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_k_sleep_manager.cpp new file mode 100644 index 00000000..59e7770e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_k_sleep_manager.cpp @@ -0,0 +1,639 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> +#include "kern_k_sleep_manager.hpp" +#include "kern_secure_monitor.hpp" +#include "kern_lps_driver.hpp" + +namespace ams::kern::init { + + void StartOtherCore(const ams::kern::init::KInitArguments *init_args); + +} + +namespace ams::kern::board::nintendo::nx { + + namespace { + + /* Struct representing registers saved on wake/sleep. */ + class SavedSystemRegisters { + private: + u64 elr_el1; + u64 sp_el0; + u64 spsr_el1; + u64 daif; + u64 cpacr_el1; + u64 vbar_el1; + u64 csselr_el1; + u64 cntp_ctl_el0; + u64 cntp_cval_el0; + u64 cntkctl_el1; + u64 tpidr_el0; + u64 tpidrro_el0; + u64 mdscr_el1; + u64 contextidr_el1; + u64 dbgwcrN_el1[16]; + u64 dbgwvrN_el1[16]; + u64 dbgbcrN_el1[16]; + u64 dbgbvrN_el1[16]; + u64 pmccfiltr_el0; + u64 pmccntr_el0; + u64 pmcntenset_el0; + u64 pmcr_el0; + u64 pmevcntrN_el0[31]; + u64 pmevtyperN_el0[31]; + u64 pmintenset_el1; + u64 pmovsset_el0; + u64 pmselr_el0; + u64 pmuserenr_el0; + public: + void Save(); + void Restore() const; + }; + + constexpr s32 SleepManagerThreadPriority = 2; + + /* Globals for sleep/wake. */ + constinit u64 g_sleep_target_cores; + constinit KLightLock g_request_lock; + constinit KLightLock g_cv_lock; + constinit KLightConditionVariable g_cv{util::ConstantInitialize}; + alignas(1_KB) constinit u64 g_sleep_buffers[cpu::NumCores][1_KB / sizeof(u64)]; + constinit ams::kern::init::KInitArguments g_sleep_init_arguments[cpu::NumCores]; + constinit SavedSystemRegisters g_sleep_system_registers[cpu::NumCores] = {}; + + void WaitOtherCpuPowerOff() { + constexpr u64 PmcPhysicalAddress = 0x7000E400; + constexpr u32 PWRGATE_STATUS_CE123_MASK = ((1u << 3) - 1) << 9; + + u32 value; + do { + bool res = smc::ReadWriteRegister(std::addressof(value), PmcPhysicalAddress + APBDEV_PMC_PWRGATE_STATUS, 0, 0); + MESOSPHERE_ASSERT(res); + MESOSPHERE_UNUSED(res); + } while ((value & PWRGATE_STATUS_CE123_MASK) != 0); + } + + void SavedSystemRegisters::Save() { + /* Save system registers. */ + this->tpidr_el0 = cpu::GetTpidrEl0(); + this->elr_el1 = cpu::GetElrEl1(); + this->sp_el0 = cpu::GetSpEl0(); + this->spsr_el1 = cpu::GetSpsrEl1(); + this->daif = cpu::GetDaif(); + this->cpacr_el1 = cpu::GetCpacrEl1(); + this->vbar_el1 = cpu::GetVbarEl1(); + this->csselr_el1 = cpu::GetCsselrEl1(); + this->cntp_ctl_el0 = cpu::GetCntpCtlEl0(); + this->cntp_cval_el0 = cpu::GetCntpCvalEl0(); + this->cntkctl_el1 = cpu::GetCntkCtlEl1(); + this->tpidrro_el0 = cpu::GetTpidrRoEl0(); + + /* Save pmu registers. */ + { + /* Get and clear pmcr_el0 */ + this->pmcr_el0 = cpu::GetPmcrEl0(); + cpu::SetPmcrEl0(0); + cpu::EnsureInstructionConsistency(); + + /* Save other pmu registers. */ + this->pmuserenr_el0 = cpu::GetPmUserEnrEl0(); + this->pmselr_el0 = cpu::GetPmSelrEl0(); + this->pmccfiltr_el0 = cpu::GetPmcCfiltrEl0(); + this->pmcntenset_el0 = cpu::GetPmCntEnSetEl0(); + this->pmintenset_el1 = cpu::GetPmIntEnSetEl1(); + this->pmovsset_el0 = cpu::GetPmOvsSetEl0(); + this->pmccntr_el0 = cpu::GetPmcCntrEl0(); + + switch (cpu::PerformanceMonitorsControlRegisterAccessor(this->pmcr_el0).GetN()) { + #define HANDLE_PMU_CASE(N) \ + case (N+1): \ + this->pmevcntrN_el0 [ N ] = cpu::GetPmevCntr##N##El0(); \ + this->pmevtyperN_el0[ N ] = cpu::GetPmevTyper##N##El0(); \ + [[fallthrough]] + + HANDLE_PMU_CASE(30); + HANDLE_PMU_CASE(29); + HANDLE_PMU_CASE(28); + HANDLE_PMU_CASE(27); + HANDLE_PMU_CASE(26); + HANDLE_PMU_CASE(25); + HANDLE_PMU_CASE(24); + HANDLE_PMU_CASE(23); + HANDLE_PMU_CASE(22); + HANDLE_PMU_CASE(21); + HANDLE_PMU_CASE(20); + HANDLE_PMU_CASE(19); + HANDLE_PMU_CASE(18); + HANDLE_PMU_CASE(17); + HANDLE_PMU_CASE(16); + HANDLE_PMU_CASE(15); + HANDLE_PMU_CASE(14); + HANDLE_PMU_CASE(13); + HANDLE_PMU_CASE(12); + HANDLE_PMU_CASE(11); + HANDLE_PMU_CASE(10); + HANDLE_PMU_CASE( 9); + HANDLE_PMU_CASE( 8); + HANDLE_PMU_CASE( 7); + HANDLE_PMU_CASE( 6); + HANDLE_PMU_CASE( 5); + HANDLE_PMU_CASE( 4); + HANDLE_PMU_CASE( 3); + HANDLE_PMU_CASE( 2); + HANDLE_PMU_CASE( 1); + HANDLE_PMU_CASE( 0); + + #undef HANDLE_PMU_CASE + case 0: + default: + break; + } + } + + /* Save debug registers. */ + const u64 dfr0 = cpu::GetIdAa64Dfr0El1(); + + this->mdscr_el1 = cpu::GetMdscrEl1(); + this->contextidr_el1 = cpu::GetContextidrEl1(); + + /* Save watchpoints. */ + switch (cpu::DebugFeatureRegisterAccessor(dfr0).GetNumWatchpoints()) { + #define HANDLE_DBG_CASE(N) \ + case N: \ + this->dbgwcrN_el1[ N ] = cpu::GetDbgWcr##N##El1(); \ + this->dbgwvrN_el1[ N ] = cpu::GetDbgWvr##N##El1(); \ + [[fallthrough]] + + HANDLE_DBG_CASE(15); + HANDLE_DBG_CASE(14); + HANDLE_DBG_CASE(13); + HANDLE_DBG_CASE(12); + HANDLE_DBG_CASE(11); + HANDLE_DBG_CASE(10); + HANDLE_DBG_CASE( 9); + HANDLE_DBG_CASE( 8); + HANDLE_DBG_CASE( 7); + HANDLE_DBG_CASE( 6); + HANDLE_DBG_CASE( 5); + HANDLE_DBG_CASE( 4); + HANDLE_DBG_CASE( 3); + HANDLE_DBG_CASE( 2); + + #undef HANDLE_DBG_CASE + + case 1: + this->dbgwcrN_el1[1] = cpu::GetDbgWcr1El1(); + this->dbgwvrN_el1[1] = cpu::GetDbgWvr1El1(); + this->dbgwcrN_el1[0] = cpu::GetDbgWcr0El1(); + this->dbgwvrN_el1[0] = cpu::GetDbgWvr0El1(); + [[fallthrough]]; + default: + break; + } + + /* Save breakpoints. */ + switch (cpu::DebugFeatureRegisterAccessor(dfr0).GetNumBreakpoints()) { + #define HANDLE_DBG_CASE(N) \ + case N: \ + this->dbgbcrN_el1[ N ] = cpu::GetDbgBcr##N##El1(); \ + this->dbgbvrN_el1[ N ] = cpu::GetDbgBvr##N##El1(); \ + [[fallthrough]] + + HANDLE_DBG_CASE(15); + HANDLE_DBG_CASE(14); + HANDLE_DBG_CASE(13); + HANDLE_DBG_CASE(12); + HANDLE_DBG_CASE(11); + HANDLE_DBG_CASE(10); + HANDLE_DBG_CASE( 9); + HANDLE_DBG_CASE( 8); + HANDLE_DBG_CASE( 7); + HANDLE_DBG_CASE( 6); + HANDLE_DBG_CASE( 5); + HANDLE_DBG_CASE( 4); + HANDLE_DBG_CASE( 3); + HANDLE_DBG_CASE( 2); + + #undef HANDLE_DBG_CASE + + case 1: + this->dbgbcrN_el1[1] = cpu::GetDbgBcr1El1(); + this->dbgbvrN_el1[1] = cpu::GetDbgBvr1El1(); + [[fallthrough]]; + default: + break; + } + + this->dbgbcrN_el1[0] = cpu::GetDbgBcr0El1(); + this->dbgbvrN_el1[0] = cpu::GetDbgBvr0El1(); + cpu::EnsureInstructionConsistency(); + + /* Clear mdscr_el1. */ + cpu::SetMdscrEl1(0); + cpu::EnsureInstructionConsistency(); + } + + void SavedSystemRegisters::Restore() const { + /* Restore debug registers. */ + const u64 dfr0 = cpu::GetIdAa64Dfr0El1(); + cpu::EnsureInstructionConsistency(); + + cpu::SetMdscrEl1(0); + cpu::EnsureInstructionConsistency(); + + cpu::SetOslarEl1(0); + cpu::EnsureInstructionConsistency(); + + /* Restore watchpoints. */ + switch (cpu::DebugFeatureRegisterAccessor(dfr0).GetNumWatchpoints()) { + #define HANDLE_DBG_CASE(N) \ + case N: \ + cpu::SetDbgWcr##N##El1(this->dbgwcrN_el1[ N ]); \ + cpu::SetDbgWvr##N##El1(this->dbgwvrN_el1[ N ]); \ + [[fallthrough]] + + HANDLE_DBG_CASE(15); + HANDLE_DBG_CASE(14); + HANDLE_DBG_CASE(13); + HANDLE_DBG_CASE(12); + HANDLE_DBG_CASE(11); + HANDLE_DBG_CASE(10); + HANDLE_DBG_CASE( 9); + HANDLE_DBG_CASE( 8); + HANDLE_DBG_CASE( 7); + HANDLE_DBG_CASE( 6); + HANDLE_DBG_CASE( 5); + HANDLE_DBG_CASE( 4); + HANDLE_DBG_CASE( 3); + HANDLE_DBG_CASE( 2); + + #undef HANDLE_DBG_CASE + + case 1: + cpu::SetDbgWcr1El1(this->dbgwcrN_el1[1]); + cpu::SetDbgWvr1El1(this->dbgwvrN_el1[1]); + cpu::SetDbgWcr0El1(this->dbgwcrN_el1[0]); + cpu::SetDbgWvr0El1(this->dbgwvrN_el1[0]); + [[fallthrough]]; + default: + break; + } + + /* Restore breakpoints. */ + switch (cpu::DebugFeatureRegisterAccessor(dfr0).GetNumBreakpoints()) { + #define HANDLE_DBG_CASE(N) \ + case N: \ + cpu::SetDbgBcr##N##El1(this->dbgbcrN_el1[ N ]); \ + cpu::SetDbgBvr##N##El1(this->dbgbvrN_el1[ N ]); \ + [[fallthrough]] + + HANDLE_DBG_CASE(15); + HANDLE_DBG_CASE(14); + HANDLE_DBG_CASE(13); + HANDLE_DBG_CASE(12); + HANDLE_DBG_CASE(11); + HANDLE_DBG_CASE(10); + HANDLE_DBG_CASE( 9); + HANDLE_DBG_CASE( 8); + HANDLE_DBG_CASE( 7); + HANDLE_DBG_CASE( 6); + HANDLE_DBG_CASE( 5); + HANDLE_DBG_CASE( 4); + HANDLE_DBG_CASE( 3); + HANDLE_DBG_CASE( 2); + + #undef HANDLE_DBG_CASE + + case 1: + cpu::SetDbgBcr1El1(this->dbgbcrN_el1[1]); + cpu::SetDbgBvr1El1(this->dbgbvrN_el1[1]); + [[fallthrough]]; + default: + break; + } + + cpu::SetDbgBcr0El1(this->dbgbcrN_el1[0]); + cpu::SetDbgBvr0El1(this->dbgbvrN_el1[0]); + cpu::EnsureInstructionConsistency(); + + cpu::SetContextidrEl1(this->contextidr_el1); + cpu::EnsureInstructionConsistency(); + + cpu::SetMdscrEl1(this->mdscr_el1); + cpu::EnsureInstructionConsistency(); + + /* Restore pmu registers. */ + cpu::SetPmUserEnrEl0(0); + cpu::PerformanceMonitorsControlRegisterAccessor(0).SetEventCounterReset(true).SetCycleCounterReset(true).Store(); + cpu::EnsureInstructionConsistency(); + + cpu::SetPmOvsClrEl0(static_cast<u64>(static_cast<u32>(~u32()))); + cpu::SetPmIntEnClrEl1(static_cast<u64>(static_cast<u32>(~u32()))); + cpu::SetPmCntEnClrEl0(static_cast<u64>(static_cast<u32>(~u32()))); + + switch (cpu::PerformanceMonitorsControlRegisterAccessor(this->pmcr_el0).GetN()) { + #define HANDLE_PMU_CASE(N) \ + case (N+1): \ + cpu::SetPmevCntr##N##El0 (this->pmevcntrN_el0 [ N ]); \ + cpu::SetPmevTyper##N##El0(this->pmevtyperN_el0[ N ]); \ + [[fallthrough]] + + HANDLE_PMU_CASE(30); + HANDLE_PMU_CASE(29); + HANDLE_PMU_CASE(28); + HANDLE_PMU_CASE(27); + HANDLE_PMU_CASE(26); + HANDLE_PMU_CASE(25); + HANDLE_PMU_CASE(24); + HANDLE_PMU_CASE(23); + HANDLE_PMU_CASE(22); + HANDLE_PMU_CASE(21); + HANDLE_PMU_CASE(20); + HANDLE_PMU_CASE(19); + HANDLE_PMU_CASE(18); + HANDLE_PMU_CASE(17); + HANDLE_PMU_CASE(16); + HANDLE_PMU_CASE(15); + HANDLE_PMU_CASE(14); + HANDLE_PMU_CASE(13); + HANDLE_PMU_CASE(12); + HANDLE_PMU_CASE(11); + HANDLE_PMU_CASE(10); + HANDLE_PMU_CASE( 9); + HANDLE_PMU_CASE( 8); + HANDLE_PMU_CASE( 7); + HANDLE_PMU_CASE( 6); + HANDLE_PMU_CASE( 5); + HANDLE_PMU_CASE( 4); + HANDLE_PMU_CASE( 3); + HANDLE_PMU_CASE( 2); + HANDLE_PMU_CASE( 1); + HANDLE_PMU_CASE( 0); + + #undef HANDLE_PMU_CASE + case 0: + default: + break; + } + + cpu::SetPmUserEnrEl0 (this->pmuserenr_el0); + cpu::SetPmSelrEl0 (this->pmselr_el0); + cpu::SetPmcCfiltrEl0 (this->pmccfiltr_el0); + cpu::SetPmCntEnSetEl0(this->pmcntenset_el0); + cpu::SetPmIntEnSetEl1(this->pmintenset_el1); + cpu::SetPmOvsSetEl0 (this->pmovsset_el0); + cpu::SetPmcCntrEl0 (this->pmccntr_el0); + cpu::EnsureInstructionConsistency(); + + cpu::SetPmcrEl0(this->pmcr_el0); + cpu::EnsureInstructionConsistency(); + + /* Restore system registers. */ + cpu::SetTtbr0El1 (KPageTable::GetKernelTtbr0()); + cpu::SetTpidrEl0 (this->tpidr_el0); + cpu::SetElrEl1 (this->elr_el1); + cpu::SetSpEl0 (this->sp_el0); + cpu::SetSpsrEl1 (this->spsr_el1); + cpu::SetDaif (this->daif); + cpu::SetCpacrEl1 (this->cpacr_el1); + cpu::SetVbarEl1 (this->vbar_el1); + cpu::SetCsselrEl1 (this->csselr_el1); + cpu::SetCntpCtlEl0 (this->cntp_ctl_el0); + cpu::SetCntpCvalEl0(this->cntp_cval_el0); + cpu::SetCntkCtlEl1 (this->cntkctl_el1); + cpu::SetTpidrRoEl0 (this->tpidrro_el0); + cpu::EnsureInstructionConsistency(); + + /* Invalidate the entire tlb. */ + cpu::InvalidateEntireTlb(); + } + + } + + void KSleepManager::Initialize() { + /* Create a sleep manager thread for each core. */ + for (size_t core_id = 0; core_id < cpu::NumCores; core_id++) { + /* Reserve a thread from the system limit. */ + MESOSPHERE_ABORT_UNLESS(Kernel::GetSystemResourceLimit().Reserve(ams::svc::LimitableResource_ThreadCountMax, 1)); + + /* Create a new thread. */ + KThread *new_thread = KThread::Create(); + MESOSPHERE_ABORT_UNLESS(new_thread != nullptr); + + /* Launch the new thread. */ + MESOSPHERE_R_ABORT_UNLESS(KThread::InitializeKernelThread(new_thread, KSleepManager::ProcessRequests, reinterpret_cast<uintptr_t>(g_sleep_buffers[core_id]), SleepManagerThreadPriority, static_cast<s32>(core_id))); + + /* Register the new thread. */ + KThread::Register(new_thread); + + /* Run the thread. */ + new_thread->Run(); + } + } + + void KSleepManager::SleepSystem() { + /* Ensure device mappings are not modified during sleep. */ + KDevicePageTable::Lock(); + ON_SCOPE_EXIT { KDevicePageTable::Unlock(); }; + + /* Request that the system sleep. */ + { + KScopedLightLock lk(g_request_lock); + + /* Signal the manager to sleep on all cores. */ + { + KScopedLightLock lk(g_cv_lock); + MESOSPHERE_ABORT_UNLESS(g_sleep_target_cores == 0); + + g_sleep_target_cores = (1ul << cpu::NumCores) - 1; + g_cv.Broadcast(); + + while (g_sleep_target_cores != 0) { + g_cv.Wait(std::addressof(g_cv_lock)); + } + } + } + } + + void KSleepManager::ProcessRequests(uintptr_t sleep_buffer) { + const auto target_fw = GetTargetFirmware(); + const s32 core_id = GetCurrentCoreId(); + + ams::kern::init::KInitArguments * const init_args = g_sleep_init_arguments + core_id; + KPhysicalAddress start_core_phys_addr = Null<KPhysicalAddress>; + KPhysicalAddress init_args_phys_addr = Null<KPhysicalAddress>; + + /* Get the physical addresses we'll need. */ + { + MESOSPHERE_ABORT_UNLESS(Kernel::GetKernelPageTable().GetPhysicalAddress(std::addressof(start_core_phys_addr), KProcessAddress(&::ams::kern::init::StartOtherCore))); + MESOSPHERE_ABORT_UNLESS(Kernel::GetKernelPageTable().GetPhysicalAddress(std::addressof(init_args_phys_addr), KProcessAddress(init_args))); + } + + const u64 target_core_mask = (1ul << core_id); + + const bool use_legacy_lps_driver = target_fw < TargetFirmware_2_0_0; + + /* Loop, processing sleep when requested. */ + while (true) { + /* Wait for a request. */ + { + KScopedLightLock lk(g_cv_lock); + while ((g_sleep_target_cores & target_core_mask) == 0) { + g_cv.Wait(std::addressof(g_cv_lock)); + } + } + + /* If on core 0, ensure the legacy lps driver is initialized. */ + if (use_legacy_lps_driver && core_id == 0) { + lps::Initialize(); + } + + /* Perform Sleep/Wake sequence. */ + { + /* Disable interrupts. */ + KScopedInterruptDisable di; + + /* Save the system registers for the current core. */ + g_sleep_system_registers[core_id].Save(); + + /* Invalidate the entire tlb. */ + cpu::InvalidateEntireTlb(); + + /* Ensure that all cores get to this point before continuing. */ + cpu::SynchronizeAllCores(); + + /* If on core 0, put the device page tables to sleep. */ + if (core_id == 0) { + KDevicePageTable::Sleep(); + } + + /* Ensure that all cores get to this point before continuing. */ + cpu::SynchronizeAllCores(); + + /* Wait 100us before continuing. */ + { + const s64 timeout = KHardwareTimer::GetTick() + ams::svc::Tick(TimeSpan::FromMicroSeconds(100)); + while (KHardwareTimer::GetTick() < timeout) { + __asm__ __volatile__("" ::: "memory"); + } + } + + /* Save the interrupt manager's state. */ + Kernel::GetInterruptManager().Save(core_id); + + /* Setup the initial arguments. */ + { + /* Determine whether we're running on a cortex-a53 or a-57. */ + cpu::MainIdRegisterAccessor midr_el1; + const auto implementer = midr_el1.GetImplementer(); + const auto primary_part = midr_el1.GetPrimaryPartNumber(); + const bool needs_cpu_ctlr = (implementer == cpu::MainIdRegisterAccessor::Implementer::ArmLimited) && (primary_part == cpu::MainIdRegisterAccessor::PrimaryPartNumber::CortexA57 || primary_part == cpu::MainIdRegisterAccessor::PrimaryPartNumber::CortexA53); + + init_args->cpuactlr = needs_cpu_ctlr ? cpu::GetCpuActlrEl1() : 0; + init_args->cpuectlr = needs_cpu_ctlr ? cpu::GetCpuEctlrEl1() : 0; + init_args->sp = 0; + init_args->entrypoint = reinterpret_cast<uintptr_t>(::ams::kern::board::nintendo::nx::KSleepManager::ResumeEntry); + init_args->argument = sleep_buffer; + } + + /* Ensure that all cores get to this point before continuing. */ + cpu::SynchronizeAllCores(); + + /* Log that the core is going to sleep. */ + MESOSPHERE_LOG("Core[%d]: Going to sleep, buffer = %010lx\n", core_id, sleep_buffer); + + /* If we're on a core other than zero, we can just invoke the sleep handler. */ + if (core_id != 0) { + CpuSleepHandler(sleep_buffer, GetInteger(start_core_phys_addr), GetInteger(init_args_phys_addr)); + } else { + /* Wait for all other cores to be powered off. */ + WaitOtherCpuPowerOff(); + + /* If we're using the legacy lps driver, enable suspend. */ + if (use_legacy_lps_driver) { + MESOSPHERE_R_ABORT_UNLESS(lps::EnableSuspend(true)); + } + + /* Log that we're about to enter SC7. */ + MESOSPHERE_LOG("Entering SC7\n"); + + /* Save the debug log state. */ + KDebugLog::Save(); + + /* Invoke the sleep handler. */ + if (!use_legacy_lps_driver) { + /* When not using the legacy driver, invoke directly. */ + CpuSleepHandler(sleep_buffer, GetInteger(start_core_phys_addr), GetInteger(init_args_phys_addr)); + } else { + lps::InvokeCpuSleepHandler(sleep_buffer, GetInteger(start_core_phys_addr), GetInteger(init_args_phys_addr)); + } + + /* Restore the debug log state. */ + KDebugLog::Restore(); + + /* Log that we're about to exit SC7. */ + MESOSPHERE_LOG("Exiting SC7\n"); + + /* Wake up the other cores. */ + cpu::MultiprocessorAffinityRegisterAccessor mpidr; + const auto arg = mpidr.GetCpuOnArgument(); + for (s32 i = 1; i < static_cast<s32>(cpu::NumCores); ++i) { + KSystemControl::Init::TurnOnCpu(arg | i, g_sleep_init_arguments + i); + } + } + + /* Log that the core is waking from sleep. */ + MESOSPHERE_LOG("Core[%d]: Woke from sleep.\n", core_id); + + /* Ensure that all cores get to this point before continuing. */ + cpu::SynchronizeAllCores(); + + /* Restore the interrupt manager's state. */ + Kernel::GetInterruptManager().Restore(core_id); + + /* Ensure that all cores get to this point before continuing. */ + cpu::SynchronizeAllCores(); + + /* If on core 0, wake up the device page tables. */ + if (core_id == 0) { + KDevicePageTable::Wakeup(); + + /* If we're using the legacy driver, resume the bpmp firmware. */ + if (use_legacy_lps_driver) { + lps::ResumeBpmpFirmware(); + } + } + + /* Ensure that all cores get to this point before continuing. */ + cpu::SynchronizeAllCores(); + + /* Restore the system registers for the current core. */ + g_sleep_system_registers[core_id].Restore(); + } + + /* Signal request completed. */ + { + KScopedLightLock lk(g_cv_lock); + g_sleep_target_cores &= ~target_core_mask; + if (g_sleep_target_cores == 0) { + g_cv.Broadcast(); + } + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_k_sleep_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_k_sleep_manager.hpp new file mode 100644 index 00000000..e5d3add1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_k_sleep_manager.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere.hpp> + +namespace ams::kern::board::nintendo::nx { + + class KSleepManager { + private: + static void ResumeEntry(uintptr_t arg); + + static void ProcessRequests(uintptr_t buffer); + public: + static void Initialize(); + static void SleepSystem(); + public: + static void CpuSleepHandler(uintptr_t arg, uintptr_t entry, uintptr_t entry_args); + }; + + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_k_sleep_manager_asm.s b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_k_sleep_manager_asm.s new file mode 100644 index 00000000..1da1ac14 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_k_sleep_manager_asm.s @@ -0,0 +1,158 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* 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 + +#define LOAD_IMMEDIATE_32(reg, val) \ + mov reg, #(((val) >> 0x00) & 0xFFFF); \ + movk reg, #(((val) >> 0x10) & 0xFFFF), lsl#16 + +/* ams::kern::board::nintendo::nx::KSleepManager::CpuSleepHandler(uintptr_t arg, uintptr_t entry, uintptr_t entry_arg) */ +.section .sleep._ZN3ams4kern5board8nintendo2nx13KSleepManager15CpuSleepHandlerEmmm, "ax", %progbits +.global _ZN3ams4kern5board8nintendo2nx13KSleepManager15CpuSleepHandlerEmmm +.type _ZN3ams4kern5board8nintendo2nx13KSleepManager15CpuSleepHandlerEmmm, %function +_ZN3ams4kern5board8nintendo2nx13KSleepManager15CpuSleepHandlerEmmm: + /* Save arguments. */ + mov x16, x1 + mov x17, x2 + + /* Enable access to FPU registers. */ + mrs x1, cpacr_el1 + orr x1, x1, #0x100000 + msr cpacr_el1, x1 + dsb sy + isb + + /* Save callee-save registers. */ + stp x18, x19, [x0], #0x10 + stp x20, x21, [x0], #0x10 + stp x22, x23, [x0], #0x10 + stp x24, x25, [x0], #0x10 + stp x26, x27, [x0], #0x10 + stp x28, x29, [x0], #0x10 + stp x30, xzr, [x0], #0x10 + + /* Save stack pointer. */ + mov x1, sp + str x1, [x0], #8 + + /* Save fpcr/fpsr. */ + mrs x1, fpcr + mrs x2, fpsr + stp w1, w2, [x0], #8 + + /* Save the floating point registers. */ + stp q0, q1, [x0], #0x20 + stp q2, q3, [x0], #0x20 + stp q4, q5, [x0], #0x20 + stp q6, q7, [x0], #0x20 + stp q8, q9, [x0], #0x20 + stp q10, q11, [x0], #0x20 + stp q12, q13, [x0], #0x20 + stp q14, q15, [x0], #0x20 + stp q16, q17, [x0], #0x20 + stp q28, q19, [x0], #0x20 + stp q20, q21, [x0], #0x20 + stp q22, q23, [x0], #0x20 + stp q24, q25, [x0], #0x20 + stp q26, q27, [x0], #0x20 + stp q28, q29, [x0], #0x20 + stp q30, q31, [x0], #0x20 + + /* Save tpidr/cntv_cval_el0. */ + mrs x1, tpidr_el1 + mrs x2, cntv_cval_el0 + stp x1, x2, [x0], #0x10 + + /* Get the current core id. */ + mrs x0, mpidr_el1 + and x0, x0, #0xFF + + /* If we're on core 0, suspend. */ + cbz x0, 1f + + /* Otherwise, power off. */ + LOAD_IMMEDIATE_32(x0, 0x84000002) + smc #1 +0: b 0b + +1: /* Suspend. */ + LOAD_IMMEDIATE_32(x0, 0xC4000001) + LOAD_IMMEDIATE_32(x1, 0x0201001B) + mov x2, x16 + mov x3, x17 + smc #1 +0: b 0b + +/* ams::kern::board::nintendo::nx::KSleepManager::ResumeEntry(uintptr_t arg) */ +.section .sleep._ZN3ams4kern5board8nintendo2nx13KSleepManager11ResumeEntryEm, "ax", %progbits +.global _ZN3ams4kern5board8nintendo2nx13KSleepManager11ResumeEntryEm +.type _ZN3ams4kern5board8nintendo2nx13KSleepManager11ResumeEntryEm, %function +_ZN3ams4kern5board8nintendo2nx13KSleepManager11ResumeEntryEm: + /* Enable access to FPU registers. */ + mrs x1, cpacr_el1 + orr x1, x1, #0x100000 + msr cpacr_el1, x1 + dsb sy + isb + + /* Restore callee-save registers. */ + ldp x18, x19, [x0], #0x10 + ldp x20, x21, [x0], #0x10 + ldp x22, x23, [x0], #0x10 + ldp x24, x25, [x0], #0x10 + ldp x26, x27, [x0], #0x10 + ldp x28, x29, [x0], #0x10 + ldp x30, xzr, [x0], #0x10 + + /* Restore stack pointer. */ + ldr x1, [x0], #8 + mov sp, x1 + + /* Restore fpcr/fpsr. */ + ldp w1, w2, [x0], #8 + msr fpcr, x1 + msr fpsr, x2 + + /* Restore the floating point registers. */ + ldp q0, q1, [x0], #0x20 + ldp q2, q3, [x0], #0x20 + ldp q4, q5, [x0], #0x20 + ldp q6, q7, [x0], #0x20 + ldp q8, q9, [x0], #0x20 + ldp q10, q11, [x0], #0x20 + ldp q12, q13, [x0], #0x20 + ldp q14, q15, [x0], #0x20 + ldp q16, q17, [x0], #0x20 + ldp q28, q19, [x0], #0x20 + ldp q20, q21, [x0], #0x20 + ldp q22, q23, [x0], #0x20 + ldp q24, q25, [x0], #0x20 + ldp q26, q27, [x0], #0x20 + ldp q28, q29, [x0], #0x20 + ldp q30, q31, [x0], #0x20 + + /* Restore tpidr/cntv_cval_el0. */ + ldp x1, x2, [x0], #0x10 + msr tpidr_el1, x1 + msr cntv_cval_el0, x2 + dsb sy + isb + + /* Return. */ + ret diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_k_system_control.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_k_system_control.cpp new file mode 100644 index 00000000..58e86f15 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_k_system_control.cpp @@ -0,0 +1,715 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> +#include "kern_secure_monitor.hpp" +#include "kern_k_sleep_manager.hpp" + +namespace ams::kern::board::nintendo::nx { + + namespace { + + constexpr size_t SecureAlignment = 128_KB; + + constexpr size_t SecureSizeMax = util::AlignDown(512_MB - 1, SecureAlignment); + + /* Global variables for panic. */ + constinit const volatile bool g_call_smc_on_panic = false; + + /* Global variables for secure memory. */ + constinit KSpinLock g_secure_applet_lock; + constinit bool g_secure_applet_memory_used = false; + constinit KVirtualAddress g_secure_applet_memory_address = Null<KVirtualAddress>; + + constinit KSpinLock g_secure_region_lock; + constinit bool g_secure_region_used = false; + constinit KPhysicalAddress g_secure_region_phys_addr = Null<KPhysicalAddress>; + constinit size_t g_secure_region_size = 0; + + ALWAYS_INLINE util::BitPack32 GetKernelConfigurationForInit() { + u64 value = 0; + smc::init::GetConfig(&value, 1, smc::ConfigItem::KernelConfiguration); + return util::BitPack32{static_cast<u32>(value)}; + } + + ALWAYS_INLINE u32 GetMemoryModeForInit() { + u64 value = 0; + smc::init::GetConfig(&value, 1, smc::ConfigItem::MemoryMode); + return static_cast<u32>(value); + } + + ALWAYS_INLINE smc::MemoryArrangement GetMemoryArrangeForInit() { + switch(GetMemoryModeForInit() & 0x3F) { + case 0x01: + default: + return smc::MemoryArrangement_4GB; + case 0x02: + return smc::MemoryArrangement_4GBForAppletDev; + case 0x03: + return smc::MemoryArrangement_4GBForSystemDev; + case 0x11: + return smc::MemoryArrangement_6GB; + case 0x12: + return smc::MemoryArrangement_6GBForAppletDev; + case 0x21: + return smc::MemoryArrangement_8GB; + } + } + + ALWAYS_INLINE u64 GenerateRandomU64ForInit() { + u64 value; + smc::init::GenerateRandomBytes(std::addressof(value), sizeof(value)); + return value; + } + + ALWAYS_INLINE u64 GenerateRandomU64FromSmc() { + u64 value; + smc::GenerateRandomBytes(std::addressof(value), sizeof(value)); + return value; + } + + ALWAYS_INLINE u64 GetConfigU64(smc::ConfigItem which) { + u64 value; + smc::GetConfig(&value, 1, which); + return value; + } + + ALWAYS_INLINE u32 GetConfigU32(smc::ConfigItem which) { + return static_cast<u32>(GetConfigU64(which)); + } + + ALWAYS_INLINE bool GetConfigBool(smc::ConfigItem which) { + return GetConfigU64(which) != 0; + } + + ALWAYS_INLINE bool CheckRegisterAllowedTable(const u8 *table, const size_t offset) { + return (table[(offset / sizeof(u32)) / BITSIZEOF(u8)] & (1u << ((offset / sizeof(u32)) % BITSIZEOF(u8)))) != 0; + } + + /* TODO: Generate this from a list of register names (see similar logic in exosphere)? */ + constexpr inline const u8 McKernelRegisterWhitelist[(PageSize / sizeof(u32)) / BITSIZEOF(u8)] = { + 0x9F, 0x31, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xC0, 0x73, 0x3E, 0x6F, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xE4, 0xFF, 0xFF, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + /* TODO: Generate this from a list of register names (see similar logic in exosphere)? */ + constexpr inline const u8 McUserRegisterWhitelist[(PageSize / sizeof(u32)) / BITSIZEOF(u8)] = { + 0x00, 0x00, 0x20, 0x00, 0xF0, 0xFF, 0xF7, 0x01, + 0xCD, 0xFE, 0xC0, 0xFE, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6E, + 0x30, 0x05, 0x06, 0xB0, 0x71, 0xC8, 0x43, 0x04, + 0x80, 0xFF, 0x08, 0x80, 0x03, 0x38, 0x8E, 0x1F, + 0xC8, 0xFF, 0xFF, 0x00, 0x0E, 0x00, 0x00, 0x00, + 0xF0, 0x1F, 0x00, 0x30, 0xF0, 0x03, 0x03, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0xFE, 0x0F, + 0x01, 0x00, 0x80, 0x00, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + bool IsRegisterAccessibleToPrivileged(ams::svc::PhysicalAddress address) { + /* Find the region for the address. */ + const KMemoryRegion *region = KMemoryLayout::Find(KPhysicalAddress(address)); + if (AMS_LIKELY(region != nullptr)) { + if (AMS_LIKELY(region->IsDerivedFrom(KMemoryRegionType_MemoryController))) { + /* Check the region is valid. */ + MESOSPHERE_ABORT_UNLESS(region->GetEndAddress() != 0); + + /* Get the offset within the region. */ + const size_t offset = address - region->GetAddress(); + MESOSPHERE_ABORT_UNLESS(offset < region->GetSize()); + + /* Check the whitelist. */ + if (AMS_LIKELY(CheckRegisterAllowedTable(McKernelRegisterWhitelist, offset))) { + return true; + } + } + } + + return false; + } + + bool IsRegisterAccessibleToUser(ams::svc::PhysicalAddress address) { + /* Find the region for the address. */ + const KMemoryRegion *region = KMemoryLayout::Find(KPhysicalAddress(address)); + if (AMS_LIKELY(region != nullptr)) { + /* The PMC is always allowed. */ + if (region->IsDerivedFrom(KMemoryRegionType_PowerManagementController)) { + return true; + } + + /* Memory controller is allowed if the register is whitelisted. */ + if (region->IsDerivedFrom(KMemoryRegionType_MemoryController ) || + region->IsDerivedFrom(KMemoryRegionType_MemoryController0) || + region->IsDerivedFrom(KMemoryRegionType_MemoryController1)) + { + /* Check the region is valid. */ + MESOSPHERE_ABORT_UNLESS(region->GetEndAddress() != 0); + + /* Get the offset within the region. */ + const size_t offset = address - region->GetAddress(); + MESOSPHERE_ABORT_UNLESS(offset < region->GetSize()); + + /* Check the whitelist. */ + if (AMS_LIKELY(CheckRegisterAllowedTable(McUserRegisterWhitelist, offset))) { + return true; + } + } + } + + return false; + } + + bool SetSecureRegion(KPhysicalAddress phys_addr, size_t size) { + /* Ensure size is valid. */ + if (size > SecureSizeMax) { + return false; + } + + /* Ensure address and size are aligned. */ + if (!util::IsAligned(GetInteger(phys_addr), SecureAlignment)) { + return false; + } + if (!util::IsAligned(size, SecureAlignment)) { + return false; + } + + /* Disable interrupts and acquire the secure region lock. */ + KScopedInterruptDisable di; + KScopedSpinLock lk(g_secure_region_lock); + + /* If size is non-zero, we're allocating the secure region. Otherwise, we're freeing it. */ + if (size != 0) { + /* Verify that the secure region is free. */ + if (g_secure_region_used) { + return false; + } + + /* Set the secure region. */ + g_secure_region_used = true; + g_secure_region_phys_addr = phys_addr; + g_secure_region_size = size; + } else { + /* Verify that the secure region is in use. */ + if (!g_secure_region_used) { + return false; + } + + /* Verify that the address being freed is the secure region. */ + if (phys_addr != g_secure_region_phys_addr) { + return false; + } + + /* Clear the secure region. */ + g_secure_region_used = false; + g_secure_region_phys_addr = Null<KPhysicalAddress>; + g_secure_region_size = 0; + } + + /* Configure the carveout with the secure monitor. */ + smc::ConfigureCarveout(1, GetInteger(phys_addr), size); + + return true; + } + + Result AllocateSecureMemoryForApplet(KVirtualAddress *out, size_t size) { + /* Verify that the size is valid. */ + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size <= KSystemControl::SecureAppletMemorySize, svc::ResultOutOfMemory()); + + /* Disable interrupts and acquire the secure applet lock. */ + KScopedInterruptDisable di; + KScopedSpinLock lk(g_secure_applet_lock); + + /* Check that memory is reserved for secure applet use. */ + MESOSPHERE_ABORT_UNLESS(g_secure_applet_memory_address != Null<KVirtualAddress>); + + /* Verify that the secure applet memory isn't already being used. */ + R_UNLESS(!g_secure_applet_memory_used, svc::ResultOutOfMemory()); + + /* Return the secure applet memory. */ + g_secure_applet_memory_used = true; + *out = g_secure_applet_memory_address; + + R_SUCCEED(); + } + + void FreeSecureMemoryForApplet(KVirtualAddress address, size_t size) { + /* Disable interrupts and acquire the secure applet lock. */ + KScopedInterruptDisable di; + KScopedSpinLock lk(g_secure_applet_lock); + + /* Verify that the memory being freed is correct. */ + MESOSPHERE_ABORT_UNLESS(address == g_secure_applet_memory_address); + MESOSPHERE_ABORT_UNLESS(size <= KSystemControl::SecureAppletMemorySize); + MESOSPHERE_ABORT_UNLESS(util::IsAligned(size, PageSize)); + MESOSPHERE_ABORT_UNLESS(g_secure_applet_memory_used); + + /* Release the secure applet memory. */ + g_secure_applet_memory_used = false; + } + + u32 GetVersionIdentifier() { + u32 value = 0; + value |= static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MICRO) << 0; + value |= static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MINOR) << 8; + value |= static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MAJOR) << 16; + value |= static_cast<u64>('M') << 24; + return value; + } + + } + + /* Initialization. */ + size_t KSystemControl::Init::GetRealMemorySize() { + /* TODO: Move this into a header for the MC in general. */ + constexpr u32 MemoryControllerConfigurationRegister = 0x70019050; + u32 config_value; + smc::init::ReadWriteRegister(std::addressof(config_value), MemoryControllerConfigurationRegister, 0, 0); + return static_cast<size_t>(config_value & 0x3FFF) << 20; + } + + size_t KSystemControl::Init::GetIntendedMemorySize() { + switch (GetKernelConfigurationForInit().Get<smc::KernelConfiguration::MemorySize>()) { + case smc::MemorySize_4GB: + default: /* All invalid modes should go to 4GB. */ + return 4_GB; + case smc::MemorySize_6GB: + return 6_GB; + case smc::MemorySize_8GB: + return 8_GB; + } + } + + bool KSystemControl::Init::ShouldIncreaseThreadResourceLimit() { + return GetKernelConfigurationForInit().Get<smc::KernelConfiguration::IncreaseThreadResourceLimit>(); + } + + size_t KSystemControl::Init::GetApplicationPoolSize() { + /* Get the base pool size. */ + const size_t base_pool_size = []() ALWAYS_INLINE_LAMBDA -> size_t { + switch (GetMemoryArrangeForInit()) { + case smc::MemoryArrangement_4GB: + default: + return 3285_MB; + case smc::MemoryArrangement_4GBForAppletDev: + return 2048_MB; + case smc::MemoryArrangement_4GBForSystemDev: + return 3285_MB; + case smc::MemoryArrangement_6GB: + return 4916_MB; + case smc::MemoryArrangement_6GBForAppletDev: + return 3285_MB; + case smc::MemoryArrangement_8GB: + return 6964_MB; + } + }(); + + /* Return (possibly) adjusted size. */ + return base_pool_size; + } + + size_t KSystemControl::Init::GetAppletPoolSize() { + /* Get the base pool size. */ + const size_t base_pool_size = []() ALWAYS_INLINE_LAMBDA -> size_t { + switch (GetMemoryArrangeForInit()) { + case smc::MemoryArrangement_4GB: + default: + return 507_MB; + case smc::MemoryArrangement_4GBForAppletDev: + return 1554_MB; + case smc::MemoryArrangement_4GBForSystemDev: + return 448_MB; + case smc::MemoryArrangement_6GB: + return 562_MB; + case smc::MemoryArrangement_6GBForAppletDev: + return 2193_MB; + case smc::MemoryArrangement_8GB: + return 562_MB; + } + }(); + + /* Return (possibly) adjusted size. */ + /* NOTE: On 20.0.0+ the browser requires much more memory in the applet pool in order to function. */ + /* Thus, we have to reduce our extra system memory size by 26 MB to compensate. */ + const size_t ExtraSystemMemoryForAtmosphere = kern::GetTargetFirmware() >= ams::TargetFirmware_20_0_0 ? 14_MB : 40_MB; + return base_pool_size - ExtraSystemMemoryForAtmosphere - KTraceBufferSize; + } + + size_t KSystemControl::Init::GetMinimumNonSecureSystemPoolSize() { + /* Verify that our minimum is at least as large as Nintendo's. */ + constexpr size_t MinimumSizeWithFatal = ::ams::svc::RequiredNonSecureSystemMemorySizeWithFatal; + static_assert(MinimumSizeWithFatal >= 0x2C04000); + + constexpr size_t MinimumSizeWithoutFatal = ::ams::svc::RequiredNonSecureSystemMemorySize; + static_assert(MinimumSizeWithoutFatal >= 0x2A00000); + + /* Include fatal in non-seure size on 16.0.0+. */ + return kern::GetTargetFirmware() >= ams::TargetFirmware_16_0_0 ? MinimumSizeWithFatal : MinimumSizeWithoutFatal; + } + + u8 KSystemControl::Init::GetDebugLogUartPort() { + /* Get the log configuration. */ + u64 value = 0; + smc::init::GetConfig(std::addressof(value), 1, smc::ConfigItem::ExosphereLogConfiguration); + + /* Extract the port. */ + return static_cast<u8>((value >> 32) & 0xFF); + } + + void KSystemControl::Init::CpuOnImpl(u64 core_id, uintptr_t entrypoint, uintptr_t arg) { + MESOSPHERE_INIT_ABORT_UNLESS((::ams::kern::arch::arm64::smc::CpuOn<smc::SmcId_Supervisor>(core_id, entrypoint, arg)) == 0); + } + + /* Randomness for Initialization. */ + void KSystemControl::Init::GenerateRandom(u64 *dst, size_t count) { + MESOSPHERE_INIT_ABORT_UNLESS(count <= 7); + smc::init::GenerateRandomBytes(dst, count * sizeof(u64)); + } + + u64 KSystemControl::Init::GenerateRandomRange(u64 min, u64 max) { + return KSystemControlBase::GenerateUniformRange(min, max, GenerateRandomU64ForInit); + } + + /* System Initialization. */ + void KSystemControl::ConfigureKTargetSystem() { + /* Configure KTargetSystem. */ + volatile auto *ts = const_cast<volatile KTargetSystem::KTargetSystemData *>(std::addressof(KTargetSystem::s_data)); + { + /* Set whether we're in debug mode. */ + { + ts->is_not_debug_mode = !GetConfigBool(smc::ConfigItem::IsDebugMode); + + /* If we're not in debug mode, we don't want to initialize uart logging. */ + ts->disable_debug_logging = ts->is_not_debug_mode; + } + + /* Set Kernel Configuration. */ + { + const auto kernel_config = util::BitPack32{GetConfigU32(smc::ConfigItem::KernelConfiguration)}; + + ts->disable_debug_memory_fill = !kernel_config.Get<smc::KernelConfiguration::DebugFillMemory>(); + ts->disable_user_exception_handlers = !kernel_config.Get<smc::KernelConfiguration::EnableUserExceptionHandlers>(); + ts->disable_dynamic_resource_limits = kernel_config.Get<smc::KernelConfiguration::DisableDynamicResourceLimits>(); + ts->disable_user_pmu_access = !kernel_config.Get<smc::KernelConfiguration::EnableUserPmuAccess>(); + + /* Configure call smc on panic. */ + *const_cast<volatile bool *>(std::addressof(g_call_smc_on_panic)) = kernel_config.Get<smc::KernelConfiguration::UseSecureMonitorPanicCall>(); + } + + /* Set Kernel Debugging. */ + { + /* NOTE: This is used to restrict access to SvcKernelDebug/SvcChangeKernelTraceState. */ + /* Mesosphere may wish to not require this, as we'd ideally keep ProgramVerification enabled for userland. */ + ts->disable_kernel_debugging = !GetConfigBool(smc::ConfigItem::DisableProgramVerification); + } + } + } + + void KSystemControl::InitializePhase1() { + /* Enable KTargetSystem. */ + KTargetSystem::SetInitialized(); + + /* Check KTargetSystem was configured correctly. */ + { + /* Check IsDebugMode. */ + { + MESOSPHERE_ABORT_UNLESS(KTargetSystem::IsDebugMode() == GetConfigBool(smc::ConfigItem::IsDebugMode)); + MESOSPHERE_ABORT_UNLESS(KTargetSystem::IsDebugLoggingEnabled() == GetConfigBool(smc::ConfigItem::IsDebugMode)); + } + + /* Check Kernel Configuration. */ + { + const auto kernel_config = util::BitPack32{GetConfigU32(smc::ConfigItem::KernelConfiguration)}; + + MESOSPHERE_ABORT_UNLESS(KTargetSystem::IsDebugMemoryFillEnabled() == kernel_config.Get<smc::KernelConfiguration::DebugFillMemory>()); + MESOSPHERE_ABORT_UNLESS(KTargetSystem::IsUserExceptionHandlersEnabled() == kernel_config.Get<smc::KernelConfiguration::EnableUserExceptionHandlers>()); + MESOSPHERE_ABORT_UNLESS(KTargetSystem::IsDynamicResourceLimitsEnabled() == !kernel_config.Get<smc::KernelConfiguration::DisableDynamicResourceLimits>()); + MESOSPHERE_ABORT_UNLESS(KTargetSystem::IsUserPmuAccessEnabled() == kernel_config.Get<smc::KernelConfiguration::EnableUserPmuAccess>()); + + MESOSPHERE_ABORT_UNLESS(g_call_smc_on_panic == kernel_config.Get<smc::KernelConfiguration::UseSecureMonitorPanicCall>()); + } + + /* Check Kernel Debugging. */ + { + MESOSPHERE_ABORT_UNLESS(KTargetSystem::IsKernelDebuggingEnabled() == GetConfigBool(smc::ConfigItem::DisableProgramVerification)); + } + } + + /* Initialize random and resource limit. */ + { + u64 seed; + smc::GenerateRandomBytes(std::addressof(seed), sizeof(seed)); + KSystemControlBase::InitializePhase1Base(seed); + } + + /* Configure the Kernel Carveout region. */ + { + const auto carveout = KMemoryLayout::GetCarveoutRegionExtents(); + MESOSPHERE_ABORT_UNLESS(carveout.GetEndAddress() != 0); + + smc::ConfigureCarveout(0, carveout.GetAddress(), carveout.GetSize()); + } + } + + void KSystemControl::InitializePhase2() { + /* Initialize the sleep manager. */ + KSleepManager::Initialize(); + + /* Get the secure applet memory. */ + const auto &secure_applet_memory = KMemoryLayout::GetSecureAppletMemoryRegion(); + MESOSPHERE_INIT_ABORT_UNLESS(secure_applet_memory.GetSize() == SecureAppletMemorySize); + + g_secure_applet_memory_address = secure_applet_memory.GetAddress(); + + /* Initialize KTrace (and potentially other init). */ + KSystemControlBase::InitializePhase2(); + } + + u32 KSystemControl::GetCreateProcessMemoryPool() { + return KMemoryManager::Pool_Unsafe; + } + + /* Privileged Access. */ + void KSystemControl::ReadWriteRegisterPrivileged(u32 *out, ams::svc::PhysicalAddress address, u32 mask, u32 value) { + MESOSPHERE_ABORT_UNLESS(util::IsAligned(address, sizeof(u32))); + MESOSPHERE_ABORT_UNLESS(IsRegisterAccessibleToPrivileged(address)); + MESOSPHERE_ABORT_UNLESS(smc::ReadWriteRegister(out, address, mask, value)); + } + + Result KSystemControl::ReadWriteRegister(u32 *out, ams::svc::PhysicalAddress address, u32 mask, u32 value) { + R_UNLESS(AMS_LIKELY(util::IsAligned(address, sizeof(u32))), svc::ResultInvalidAddress()); + R_UNLESS(AMS_LIKELY(IsRegisterAccessibleToUser(address)), svc::ResultInvalidAddress()); + R_UNLESS(AMS_LIKELY(smc::ReadWriteRegister(out, address, mask, value)), svc::ResultInvalidAddress()); + R_SUCCEED(); + } + + /* Randomness. */ + void KSystemControl::GenerateRandom(u64 *dst, size_t count) { + MESOSPHERE_INIT_ABORT_UNLESS(count <= 7); + smc::GenerateRandomBytes(dst, count * sizeof(u64)); + } + + u64 KSystemControl::GenerateRandomRange(u64 min, u64 max) { + KScopedInterruptDisable intr_disable; + KScopedSpinLock lk(s_random_lock); + + + if (AMS_LIKELY(!s_uninitialized_random_generator)) { + return KSystemControlBase::GenerateUniformRange(min, max, []() ALWAYS_INLINE_LAMBDA -> u64 { return s_random_generator.GenerateRandomU64(); }); + } else { + return KSystemControlBase::GenerateUniformRange(min, max, GenerateRandomU64FromSmc); + } + } + + u64 KSystemControl::GenerateRandomU64() { + KScopedInterruptDisable intr_disable; + KScopedSpinLock lk(s_random_lock); + + if (AMS_LIKELY(!s_uninitialized_random_generator)) { + return s_random_generator.GenerateRandomU64(); + } else { + return GenerateRandomU64FromSmc(); + } + } + + void KSystemControl::SleepSystem() { + MESOSPHERE_LOG("SleepSystem() was called\n"); + KSleepManager::SleepSystem(); + } + + void KSystemControl::StopSystem(void *arg) { + if (arg != nullptr) { + /* Get the address of the legacy IRAM region. */ + const KVirtualAddress iram_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsIram) + 64_KB; + constexpr size_t RebootPayloadSize = 0x24000; + + /* NOTE: Atmosphere extension; if we received an exception context from Panic(), */ + /* generate a fatal error report using it. */ + const KExceptionContext *e_ctx = static_cast<const KExceptionContext *>(arg); + auto *f_ctx = GetPointer<::ams::impl::FatalErrorContext>(iram_address + 0x2E000); + + /* Clear the fatal context. */ + std::memset(f_ctx, 0xCC, sizeof(*f_ctx)); + + /* Set metadata. */ + f_ctx->magic = ::ams::impl::FatalErrorContext::Magic; + f_ctx->error_desc = ::ams::impl::FatalErrorContext::KernelPanicDesc; + f_ctx->program_id = (static_cast<u64>(util::FourCC<'M', 'E', 'S', 'O'>::Code) << 0) | (static_cast<u64>(util::FourCC<'S', 'P', 'H', 'R'>::Code) << 32); + + /* Set identifier. */ + f_ctx->report_identifier = KHardwareTimer::GetTick(); + + /* Set module base. */ + f_ctx->module_base = KMemoryLayout::GetKernelCodeRegionExtents().GetAddress(); + + /* Set afsr1. */ + f_ctx->afsr0 = GetVersionIdentifier(); + f_ctx->afsr1 = static_cast<u32>(kern::GetTargetFirmware()); + + /* Set efsr/far. */ + f_ctx->far = cpu::GetFarEl1(); + f_ctx->esr = cpu::GetEsrEl1(); + + /* Copy registers. */ + for (size_t i = 0; i < util::size(e_ctx->x); ++i) { + f_ctx->gprs[i] = e_ctx->x[i]; + } + f_ctx->sp = e_ctx->sp; + f_ctx->pc = cpu::GetElrEl1(); + + /* Dump stack trace. */ + { + uintptr_t fp = e_ctx->x[29]; + for (f_ctx->stack_trace_size = 0; f_ctx->stack_trace_size < ::ams::impl::FatalErrorContext::MaxStackTrace && fp != 0 && util::IsAligned(fp, 0x10) && cpu::GetPhysicalAddressWritable(nullptr, fp, true); ++(f_ctx->stack_trace_size)) { + struct { + uintptr_t fp; + uintptr_t lr; + } *stack_frame = reinterpret_cast<decltype(stack_frame)>(fp); + + f_ctx->stack_trace[f_ctx->stack_trace_size] = stack_frame->lr; + fp = stack_frame->fp; + } + } + + /* Dump stack. */ + { + uintptr_t sp = e_ctx->sp; + for (f_ctx->stack_dump_size = 0; f_ctx->stack_dump_size < ::ams::impl::FatalErrorContext::MaxStackDumpSize && cpu::GetPhysicalAddressWritable(nullptr, sp + f_ctx->stack_dump_size, true); f_ctx->stack_dump_size += sizeof(u64)) { + *reinterpret_cast<u64 *>(f_ctx->stack_dump + f_ctx->stack_dump_size) = *reinterpret_cast<u64 *>(sp + f_ctx->stack_dump_size); + } + } + + /* Try to get a payload address. */ + const KMemoryRegion *cached_region = nullptr; + u64 reboot_payload_paddr = 0; + if (smc::TryGetConfig(std::addressof(reboot_payload_paddr), 1, smc::ConfigItem::ExospherePayloadAddress) && KMemoryLayout::IsLinearMappedPhysicalAddress(cached_region, reboot_payload_paddr, RebootPayloadSize)) { + /* If we have a payload, reboot to it. */ + const KVirtualAddress reboot_payload = KMemoryLayout::GetLinearVirtualAddress(KPhysicalAddress(reboot_payload_paddr)); + + /* Clear IRAM. */ + std::memset(GetVoidPointer(iram_address), 0xCC, RebootPayloadSize); + + /* Copy the payload to iram. */ + for (size_t i = 0; i < RebootPayloadSize / sizeof(u32); ++i) { + GetPointer<volatile u32>(iram_address)[i] = GetPointer<volatile u32>(reboot_payload)[i]; + } + } + smc::SetConfig(smc::ConfigItem::ExosphereNeedsReboot, smc::UserRebootType_ToFatalError); + } + + if (g_call_smc_on_panic) { + /* If we should, instruct the secure monitor to display a panic screen. */ + smc::ShowError(0xF00); + } + + AMS_INFINITE_LOOP(); + } + + /* User access. */ + void KSystemControl::CallSecureMonitorFromUserImpl(ams::svc::lp64::SecureMonitorArguments *args) { + /* Invoke the secure monitor. */ + return smc::CallSecureMonitorFromUser(args); + } + + /* Secure Memory. */ + size_t KSystemControl::CalculateRequiredSecureMemorySize(size_t size, u32 pool) { + if (pool == KMemoryManager::Pool_Applet) { + return 0; + } else { + return KSystemControlBase::CalculateRequiredSecureMemorySize(size, pool); + } + } + + Result KSystemControl::AllocateSecureMemory(KVirtualAddress *out, size_t size, u32 pool) { + /* Applet secure memory is handled separately. */ + if (pool == KMemoryManager::Pool_Applet) { + R_RETURN(AllocateSecureMemoryForApplet(out, size)); + } + + /* Ensure the size is aligned. */ + const size_t alignment = (pool == KMemoryManager::Pool_System ? PageSize : SecureAlignment); + R_UNLESS(util::IsAligned(size, alignment), svc::ResultInvalidSize()); + + /* Allocate the memory. */ + const size_t num_pages = size / PageSize; + const KPhysicalAddress paddr = Kernel::GetMemoryManager().AllocateAndOpenContinuous(num_pages, alignment / PageSize, KMemoryManager::EncodeOption(static_cast<KMemoryManager::Pool>(pool), KMemoryManager::Direction_FromFront)); + R_UNLESS(paddr != Null<KPhysicalAddress>, svc::ResultOutOfMemory()); + + /* Ensure we don't leak references to the memory on error. */ + ON_RESULT_FAILURE { Kernel::GetMemoryManager().Close(paddr, num_pages); }; + + /* If the memory isn't already secure, set it as secure. */ + if (pool != KMemoryManager::Pool_System) { + /* Set the secure region. */ + R_UNLESS(SetSecureRegion(paddr, size), svc::ResultOutOfMemory()); + } + + /* We succeeded. */ + *out = KPageTable::GetHeapVirtualAddress(paddr); + R_SUCCEED(); + } + + void KSystemControl::FreeSecureMemory(KVirtualAddress address, size_t size, u32 pool) { + /* Applet secure memory is handled separately. */ + if (pool == KMemoryManager::Pool_Applet) { + return FreeSecureMemoryForApplet(address, size); + } + + /* Ensure the size is aligned. */ + const size_t alignment = (pool == KMemoryManager::Pool_System ? PageSize : SecureAlignment); + MESOSPHERE_ABORT_UNLESS(util::IsAligned(GetInteger(address), alignment)); + MESOSPHERE_ABORT_UNLESS(util::IsAligned(size, alignment)); + + /* If the memory isn't secure system, reset the secure region. */ + if (pool != KMemoryManager::Pool_System) { + /* Check that the size being freed is the current secure region size. */ + MESOSPHERE_ABORT_UNLESS(g_secure_region_size == size); + + /* Get the physical address. */ + const KPhysicalAddress paddr = KPageTable::GetHeapPhysicalAddress(address); + MESOSPHERE_ABORT_UNLESS(paddr != Null<KPhysicalAddress>); + + /* Check that the memory being freed is the current secure region. */ + MESOSPHERE_ABORT_UNLESS(paddr == g_secure_region_phys_addr); + + /* Free the secure region. */ + MESOSPHERE_ABORT_UNLESS(SetSecureRegion(paddr, 0)); + } + + /* Close the secure region's pages. */ + Kernel::GetMemoryManager().Close(KPageTable::GetHeapPhysicalAddress(address), size / PageSize); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_lps_driver.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_lps_driver.cpp new file mode 100644 index 00000000..a1f1cf2f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_lps_driver.cpp @@ -0,0 +1,463 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> +#include "kern_lps_driver.hpp" +#include "kern_k_sleep_manager.hpp" + +#include "kern_bpmp_api.hpp" +#include "kern_atomics_registers.hpp" +#include "kern_ictlr_registers.hpp" +#include "kern_sema_registers.hpp" + +namespace ams::kern::board::nintendo::nx::lps { + + namespace { + + constexpr inline int ChannelCount = 12; + + constexpr inline TimeSpan ChannelTimeout = TimeSpan::FromSeconds(1); + + constinit bool g_lps_init_done = false; + constinit bool g_bpmp_connected = false; + constinit bool g_bpmp_mail_initialized = false; + + constinit KSpinLock g_bpmp_mrq_lock; + + constinit KVirtualAddress g_evp_address = Null<KVirtualAddress>; + constinit KVirtualAddress g_flow_address = Null<KVirtualAddress>; + constinit KVirtualAddress g_prictlr_address = Null<KVirtualAddress>; + constinit KVirtualAddress g_sema_address = Null<KVirtualAddress>; + constinit KVirtualAddress g_atomics_address = Null<KVirtualAddress>; + constinit KVirtualAddress g_clkrst_address = Null<KVirtualAddress>; + constinit KVirtualAddress g_pmc_address = Null<KVirtualAddress>; + + constinit ChannelData g_channel_area[ChannelCount] = {}; + + constinit u32 g_csite_clk_source = 0; + + ALWAYS_INLINE u32 Read(KVirtualAddress address) { + return *GetPointer<volatile u32>(address); + } + + ALWAYS_INLINE void Write(KVirtualAddress address, u32 value) { + *GetPointer<volatile u32>(address) = value; + } + + void InitializeDeviceVirtualAddresses() { + /* Retrieve randomized mappings. */ + g_evp_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsExceptionVectors); + g_flow_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsFlowController); + g_prictlr_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsPrimaryICtlr); + g_sema_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsSemaphore); + g_atomics_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsAtomics); + g_clkrst_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsClkRst); + g_pmc_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_PowerManagementController); + } + + /* NOTE: linux "do_cc4_init" */ + void ConfigureCc3AndCc4() { + /* Configure CC4/CC3 as enabled with time threshold as 2 microseconds. */ + Write(g_flow_address + FLOW_CTLR_CC4_HVC_CONTROL, (0x2 << 3) | 0x1); + + /* Configure Retention with threshold 2 microseconds. */ + Write(g_flow_address + FLOW_CTLR_CC4_RETENTION_CONTROL, (0x2 << 3)); + + /* Configure CC3/CC3 retry threshold as 2 microseconds. */ + Write(g_flow_address + FLOW_CTLR_CC4_HVC_RETRY, (0x2 << 3)); + + /* Read the retry register to ensure writes take. */ + Read(g_flow_address + FLOW_CTLR_CC4_HVC_RETRY); + } + + constexpr bool IsValidMessageDataSize(int size) { + return 0 <= size && size < MessageDataSizeMax; + } + + /* NOTE: linux "bpmp_valid_txfer" */ + constexpr bool IsTransferValid(const void *ob, int ob_size, void *ib, int ib_size) { + return IsValidMessageDataSize(ob_size) && IsValidMessageDataSize(ib_size) && (ob_size == 0 || ob != nullptr) && (ib_size == 0 || ib != nullptr); + } + + /* NOTE: linux "bpmp_ob_channel" */ + int BpmpGetOutboundChannel() { + return GetCurrentCoreId(); + } + + /* NOTE: linux "bpmp_ch_sta" */ + u32 BpmpGetChannelState(int channel) { + cpu::DataSynchronizationBarrier(); + return Read(g_sema_address + RES_SEMA_SHRD_SMP_STA) & CH_MASK(channel); + } + + /* NOTE: linux "bpmp_master_free" */ + bool BpmpIsMasterFree(int channel) { + return BpmpGetChannelState(channel) == MA_FREE(channel); + } + + /* NOTE: linux "bpmp_master_acked" */ + bool BpmpIsMasterAcked(int channel) { + return BpmpGetChannelState(channel) == MA_ACKD(channel); + } + + /* NOTE: linux "bpmp_signal_slave" */ + void BpmpSignalSlave(int channel) { + Write(g_sema_address + RES_SEMA_SHRD_SMP_CLR, CH_MASK(channel)); + cpu::DataSynchronizationBarrier(); + } + + /* NOTE: linux "bpmp_free_master" */ + void BpmpFreeMaster(int channel) { + /* Transition state from ack'd to free. */ + Write(g_sema_address + RES_SEMA_SHRD_SMP_CLR, ((MA_ACKD(channel)) ^ (MA_FREE(channel)))); + cpu::DataSynchronizationBarrier(); + } + + /* NOTE: linux "bpmp_ring_doorbell" */ + void BpmpRingDoorbell() { + Write(g_prictlr_address + ICTLR_FIR_SET(INT_SHR_SEM_OUTBOX_IBF), FIR_BIT(INT_SHR_SEM_OUTBOX_IBF)); + cpu::DataSynchronizationBarrier(); + } + + /* NOTE: linux "bpmp_wait_master_free" */ + int BpmpWaitMasterFree(int channel) { + /* Check if the master is already freed. */ + if (BpmpIsMasterFree(channel)) { + return 0; + } + + /* Spin-poll for the master to be freed until timeout occurs. */ + const auto start_tick = KHardwareTimer::GetTick(); + const auto timeout = ams::svc::Tick(ChannelTimeout); + do { + if (BpmpIsMasterFree(channel)) { + return 0; + } + } while ((KHardwareTimer::GetTick() - start_tick) < timeout); + + /* The master didn't become free. */ + return -1; + } + + /* NOTE: linux "bpmp_wait_ack" */ + int BpmpWaitAck(int channel) { + /* Check if the master is already ACK'd. */ + if (BpmpIsMasterAcked(channel)) { + return 0; + } + + /* Spin-poll for the master to be ACK'd until timeout occurs. */ + const auto start_tick = KHardwareTimer::GetTick(); + const auto timeout = ams::svc::Tick(ChannelTimeout); + do { + if (BpmpIsMasterAcked(channel)) { + return 0; + } + } while ((KHardwareTimer::GetTick() - start_tick) < timeout); + + /* The master didn't get ACK'd. */ + return -1; + } + + /* NOTE: linux "bpmp_write_ch" */ + int BpmpWriteChannel(int channel, int mrq, int flags, const void *data, size_t data_size) { + /* Wait to be able to master the mailbox. */ + if (int res = BpmpWaitMasterFree(channel); res != 0) { + return res; + } + + /* Prepare the message. */ + MailboxData *mb = g_channel_area[channel].ob; + mb->code = mrq; + mb->flags = flags; + if (data != nullptr) { + std::memcpy(mb->data, data, data_size); + } + + /* Signal to slave that message is available. */ + BpmpSignalSlave(channel); + + return 0; + } + + /* NOTE: linux "__bpmp_read_ch" */ + int BpmpReadChannel(int channel, void *data, size_t data_size) { + /* Get the message. */ + MailboxData *mb = g_channel_area[channel].ib; + + /* Copy any return data. */ + if (data != nullptr) { + std::memcpy(data, mb->data, data_size); + } + + /* Free the channel. */ + BpmpFreeMaster(channel); + + /* Return result. */ + return mb->code; + } + + /* NOTE: linux "tegra_bpmp_send_receive_atomic" or "tegra_bpmp_send_receive". */ + int BpmpSendAndReceive(int mrq, const void *ob, int ob_size, void *ib, int ib_size) { + /* Validate that the data transfer is valid. */ + if (!IsTransferValid(ob, ob_size, ib, ib_size)) { + return -1; + } + + /* Validate that the bpmp is connected. */ + if (!g_bpmp_connected) { + return -1; + } + + /* Disable interrupts. */ + KScopedInterruptDisable di; + + /* Acquire exclusive access to send mrqs. */ + KScopedSpinLock lk(g_bpmp_mrq_lock); + + /* Send the message. */ + int channel = BpmpGetOutboundChannel(); + if (int res = BpmpWriteChannel(channel, mrq, BPMP_MSG_DO_ACK, ob, ob_size); res != 0) { + return res; + } + + /* Send "doorbell" irq to the bpmp firmware. */ + BpmpRingDoorbell(); + + /* Wait for the bpmp firmware to acknowledge our request. */ + if (int res = BpmpWaitAck(channel); res != 0) { + return res; + } + + /* Read the data the bpmp sent back. */ + return BpmpReadChannel(channel, ib, ib_size); + } + + /* NOTE: linux "tegra_bpmp_send" */ + int BpmpSend(int mrq, const void *ob, int ob_size) { + /* Validate that the data transfer is valid. */ + if (!IsTransferValid(ob, ob_size, nullptr, 0)) { + return -1; + } + + /* Validate that the bpmp is connected. */ + if (!g_bpmp_connected) { + return -1; + } + + /* Disable interrupts. */ + KScopedInterruptDisable di; + + /* Acquire exclusive access to send mrqs. */ + KScopedSpinLock lk(g_bpmp_mrq_lock); + + /* Send the message. */ + int channel = BpmpGetOutboundChannel(); + if (int res = BpmpWriteChannel(channel, mrq, 0, ob, ob_size); res != 0) { + return res; + } + + /* Send "doorbell" irq to the bpmp firmware. */ + BpmpRingDoorbell(); + + return 0; + } + + /* NOTE: modified linux "tegra_bpmp_enable_suspend" */ + int BpmpEnableSuspend(int mode, int flags) { + /* Prepare data for bpmp. */ + const s32 data[] = { mode, flags }; + + /* Send the data. */ + return BpmpSend(MRQ_ENABLE_SUSPEND, data, sizeof(data)); + } + + /* NOTE: linux "__bpmp_connect" */ + int ConnectToBpmp() { + /* Check if we've already connected. */ + if (g_bpmp_connected) { + return 0; + } + + /* Verify that the resource semaphore state is set. */ + if (Read(g_sema_address + RES_SEMA_SHRD_SMP_STA) == 0) { + return -1; + } + + /* Get the channels, which the bpmp firmware has configured in advance. */ + { + const KVirtualAddress iram_virt_addr = KMemoryLayout::GetDeviceVirtualAddress (KMemoryRegionType_LegacyLpsIram); + const KPhysicalAddress iram_phys_addr = KMemoryLayout::GetDevicePhysicalAddress(KMemoryRegionType_LegacyLpsIram); + + for (auto i = 0; i < ChannelCount; ++i) { + /* Trigger a get command for the desired channel. */ + Write(g_atomics_address + ATOMICS_AP0_TRIGGER, TRIGGER_CMD_GET | (i << 16)); + + /* Retrieve the channel phys-addr-in-iram, and convert it to a kernel address. */ + auto *ch = GetPointer<MailboxData>(iram_virt_addr + (Read(g_atomics_address + ATOMICS_AP0_RESULT(i)) - GetInteger(iram_phys_addr))); + + /* Verify the channel isn't null. */ + /* NOTE: This is an utterly nonsense check, as this would require the bpmp firmware to specify */ + /* a phys-to-virt diff as an address. On 1.0.0, which had no ASLR, this was 0x8028C000. */ + /* However, Nintendo has the check, and we'll preserve it to be faithful. */ + if (ch == nullptr) { + return -1; + } + + /* Set the channel in the channel area. */ + g_channel_area[i].ib = ch; + g_channel_area[i].ob = ch; + } + } + + /* Mark driver as connected to bpmp. */ + g_bpmp_connected = true; + + return 0; + } + + /* NOTE: Modified linux "bpmp_mail_init" */ + int InitializeBpmpMail() { + /* Check if we've already initialized. */ + if (g_bpmp_mail_initialized) { + return 0; + } + + /* Mark function as having been called. */ + g_bpmp_mail_initialized = true; + + /* Forward declare result/reply variables. */ + int res, request = 0, reply = 0; + + /* Try to connect to the bpmp. */ + if (res = ConnectToBpmp(); res != 0) { + MESOSPHERE_LOG("bpmp: connect error returns %d\n", res); + return res; + } + + /* Ensure that we can successfully ping the bpmp. */ + request = 1; + if (res = BpmpSendAndReceive(MRQ_PING, std::addressof(request), sizeof(request), std::addressof(reply), sizeof(reply)); res != 0) { + MESOSPHERE_LOG("bpmp: MRQ_PING error returns %d with reply %d\n", res, reply); + return res; + } + + /* Configure the PMIC. */ + request = 1; + if (res = BpmpSendAndReceive(MRQ_CPU_PMIC_SELECT, std::addressof(request), sizeof(request), std::addressof(reply), sizeof(reply)); res != 0) { + MESOSPHERE_LOG("bpmp: MRQ_CPU_PMIC_SELECT for MAX77621 error returns %d with reply %d\n", res, reply); + return res; + } + + return 0; + } + + } + + void Initialize() { + if (!g_lps_init_done) { + /* Get the addresses of the devices the driver needs. */ + InitializeDeviceVirtualAddresses(); + + /* Configure CC3/CC4. */ + ConfigureCc3AndCc4(); + + /* Initialize ccplex <-> bpmp mail. */ + /* NOTE: Nintendo does not check that this call succeeds. */ + InitializeBpmpMail(); + + g_lps_init_done = true; + } + } + + Result EnableSuspend(bool enable) { + /* If we're not on core 0, there's nothing to do. */ + R_SUCCEED_IF(GetCurrentCoreId() != 0); + + /* If we're not enabling suspend, there's nothing to do. */ + R_SUCCEED_IF(!enable); + + /* Instruct BPMP to enable suspend-to-sc7. */ + R_UNLESS(BpmpEnableSuspend(TEGRA_BPMP_PM_SC7, 0) == 0, svc::ResultInvalidState()); + + R_SUCCEED(); + } + + void InvokeCpuSleepHandler(uintptr_t arg, uintptr_t entry, uintptr_t entry_arg) { + /* Verify that we're allowed to perform suspension. */ + MESOSPHERE_ABORT_UNLESS(g_lps_init_done); + MESOSPHERE_ABORT_UNLESS(GetCurrentCoreId() == 0); + + /* Save the CSITE clock source. */ + g_csite_clk_source = Read(g_clkrst_address + CLK_RST_CONTROLLER_CLK_SOURCE_CSITE); + + /* Configure CSITE clock source as CLK_M. */ + Write(g_clkrst_address + CLK_RST_CONTROLLER_CLK_SOURCE_CSITE, (0x6 << 29)); + + /* Clear the top bit of PMC_SCRATCH4. */ + Write(g_pmc_address + APBDEV_PMC_SCRATCH4, Read(g_pmc_address + APBDEV_PMC_SCRATCH4) & 0x7FFFFFFF); + + /* Write 1 to PMC_SCRATCH0. This will cause the bootrom to use the warmboot code-path. */ + Write(g_pmc_address + APBDEV_PMC_SCRATCH0, 1); + + /* Read PMC_SCRATCH0 to be sure our write takes. */ + Read(g_pmc_address + APBDEV_PMC_SCRATCH0); + + /* Invoke the sleep hander. */ + KSleepManager::CpuSleepHandler(arg, entry, entry_arg); + + /* Disable deep power down. */ + Write(g_pmc_address + APBDEV_PMC_DPD_ENABLE, 0); + + /* Restore the saved CSITE clock source. */ + Write(g_clkrst_address + CLK_RST_CONTROLLER_CLK_SOURCE_CSITE, g_csite_clk_source); + + /* Read the CSITE clock source to ensure our configuration takes. */ + Read(g_clkrst_address + CLK_RST_CONTROLLER_CLK_SOURCE_CSITE); + + /* Configure CC3/CC4. */ + ConfigureCc3AndCc4(); + } + + void ResumeBpmpFirmware() { + /* Halt the bpmp. */ + Write(g_flow_address + FLOW_CTLR_HALT_COP_EVENTS, (0x2 << 29)); + + /* Hold the bpmp in reset. */ + Write(g_clkrst_address + CLK_RST_CONTROLLER_RST_DEV_L_SET, 0x2); + + /* Read the saved bpmp entrypoint, and write it to the relevant exception vector. */ + const u32 bpmp_entry = Read(g_pmc_address + APBDEV_PMC_SCRATCH39); + Write(g_evp_address + EVP_COP_RESET_VECTOR, bpmp_entry); + + /* Verify that we can read back the address we wrote. */ + while (Read(g_evp_address + EVP_COP_RESET_VECTOR) != bpmp_entry) { + /* ... */ + } + + /* Spin for 40 ticks, to give enough time for the bpmp to be reset. */ + const auto start_tick = KHardwareTimer::GetTick(); + do { + __asm__ __volatile__("" ::: "memory"); + } while ((KHardwareTimer::GetTick() - start_tick) < 40); + + /* Take the bpmp out of reset. */ + Write(g_clkrst_address + CLK_RST_CONTROLLER_RST_DEV_L_CLR, 0x2); + + /* Resume the bpmp. */ + Write(g_flow_address + FLOW_CTLR_HALT_COP_EVENTS, (0x0 << 29)); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_lps_driver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_lps_driver.hpp new file mode 100644 index 00000000..0c69d5da --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_lps_driver.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere.hpp> + +namespace ams::kern::board::nintendo::nx { + + namespace lps { + + void Initialize(); + Result EnableSuspend(bool enable); + void InvokeCpuSleepHandler(uintptr_t arg, uintptr_t entry, uintptr_t entry_arg); + void ResumeBpmpFirmware(); + + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_secure_monitor.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_secure_monitor.cpp new file mode 100644 index 00000000..d55eb2c7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_secure_monitor.cpp @@ -0,0 +1,237 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> +#include "kern_secure_monitor.hpp" + +namespace ams::kern::board::nintendo::nx::smc { + + namespace { + + enum UserFunctionId : u32 { + UserFunctionId_SetConfig = 0xC3000401, + UserFunctionId_GetConfigUser = 0xC3000002, + UserFunctionId_GetResult = 0xC3000003, + UserFunctionId_GetResultData = 0xC3000404, + UserFunctionId_ModularExponentiate = 0xC3000E05, + UserFunctionId_GenerateRandomBytes = 0xC3000006, + UserFunctionId_GenerateAesKek = 0xC3000007, + UserFunctionId_LoadAesKey = 0xC3000008, + UserFunctionId_ComputeAes = 0xC3000009, + UserFunctionId_GenerateSpecificAesKey = 0xC300000A, + UserFunctionId_ComputeCmac = 0xC300040B, + UserFunctionId_ReencryptDeviceUniqueData = 0xC300D60C, + UserFunctionId_DecryptDeviceUniqueData = 0xC300100D, + UserFunctionId_ModularExponentiateByStorageKey = 0xC300060F, + UserFunctionId_PrepareEsDeviceUniqueKey = 0xC3000610, + UserFunctionId_LoadPreparedAesKey = 0xC3000011, + UserFunctionId_PrepareEsCommonTitleKey = 0xC3000012, + }; + + enum FunctionId : u32 { + FunctionId_GetConfig = 0xC3000004, + FunctionId_GenerateRandomBytes = 0xC3000005, + FunctionId_ShowError = 0xC3000006, + FunctionId_ConfigureCarveout = 0xC3000007, + FunctionId_ReadWriteRegister = 0xC3000008, + + /* NOTE: Atmosphere extension for mesosphere. This ID is subject to change at any time. */ + FunctionId_SetConfig = 0xC3000409, + }; + + constexpr size_t GenerateRandomBytesSizeMax = sizeof(::ams::svc::lp64::SecureMonitorArguments) - sizeof(::ams::svc::lp64::SecureMonitorArguments{}.r[0]); + + /* Global lock for generate random bytes. */ + constinit KSpinLock g_generate_random_lock; + + bool TryGetConfigImpl(u64 *out, size_t num_qwords, ConfigItem config_item) { + /* Create the arguments .*/ + ams::svc::lp64::SecureMonitorArguments args = { { FunctionId_GetConfig, static_cast<u32>(config_item) } }; + + /* Call into the secure monitor. */ + ::ams::kern::arch::arm64::smc::SecureMonitorCall<SmcId_Supervisor>(args.r); + + /* If successful, copy the output. */ + const bool success = static_cast<SmcResult>(args.r[0]) == SmcResult::Success; + if (AMS_LIKELY(success)) { + for (size_t i = 0; i < num_qwords && i < 7; i++) { + out[i] = args.r[1 + i]; + } + } + + return success; + } + + bool SetConfigImpl(ConfigItem config_item, u64 value) { + /* Create the arguments .*/ + ams::svc::lp64::SecureMonitorArguments args = { { FunctionId_SetConfig, static_cast<u32>(config_item), 0, value } }; + + /* Call into the secure monitor. */ + ::ams::kern::arch::arm64::smc::SecureMonitorCall<SmcId_Supervisor>(args.r); + + /* Return whether the call was successful. */ + return static_cast<SmcResult>(args.r[0]) == SmcResult::Success; + } + + bool ReadWriteRegisterImpl(u32 *out, u64 address, u32 mask, u32 value) { + /* Create the arguments .*/ + ams::svc::lp64::SecureMonitorArguments args = { { FunctionId_ReadWriteRegister, address, mask, value } }; + + /* Call into the secure monitor. */ + ::ams::kern::arch::arm64::smc::SecureMonitorCall<SmcId_Supervisor>(args.r); + + /* Unconditionally write the output. */ + *out = static_cast<u32>(args.r[1]); + + /* Return whether the call was successful. */ + return static_cast<SmcResult>(args.r[0]) == SmcResult::Success; + } + + bool GenerateRandomBytesImpl(void *dst, size_t size) { + /* Create the arguments. */ + ams::svc::lp64::SecureMonitorArguments args = { { FunctionId_GenerateRandomBytes, size } }; + + /* Call into the secure monitor. */ + ::ams::kern::arch::arm64::smc::SecureMonitorCall<SmcId_Supervisor>(args.r); + + /* If successful, copy the output. */ + const bool success = static_cast<SmcResult>(args.r[0]) == SmcResult::Success; + if (AMS_LIKELY(success)) { + std::memcpy(dst, std::addressof(args.r[1]), size); + } + + return success; + } + + bool ConfigureCarveoutImpl(size_t which, uintptr_t address, size_t size) { + /* Create the arguments .*/ + ams::svc::lp64::SecureMonitorArguments args = { { FunctionId_ConfigureCarveout, static_cast<u64>(which), static_cast<u64>(address), static_cast<u64>(size) } }; + + /* Call into the secure monitor. */ + ::ams::kern::arch::arm64::smc::SecureMonitorCall<SmcId_Supervisor>(args.r); + + /* Return whether the call was successful. */ + return static_cast<SmcResult>(args.r[0]) == SmcResult::Success; + } + + bool ShowErrorImpl(u32 color) { + /* Create the arguments .*/ + ams::svc::lp64::SecureMonitorArguments args = { { FunctionId_ShowError, color } }; + + /* Call into the secure monitor. */ + ::ams::kern::arch::arm64::smc::SecureMonitorCall<SmcId_Supervisor>(args.r); + + /* Return whether the call was successful. */ + return static_cast<SmcResult>(args.r[0]) == SmcResult::Success; + } + + void CallSecureMonitorFromUserImpl(ams::svc::lp64::SecureMonitorArguments *args) { + /* Call into the secure monitor. */ + ::ams::kern::arch::arm64::smc::SecureMonitorCall<SmcId_User>(args->r); + } + + } + + /* SMC functionality needed for init. */ + namespace init { + + void GetConfig(u64 *out, size_t num_qwords, ConfigItem config_item) { + /* Ensure we successfully get the config. */ + MESOSPHERE_INIT_ABORT_UNLESS(TryGetConfigImpl(out, num_qwords, config_item)); + } + + void GenerateRandomBytes(void *dst, size_t size) { + /* Check that the size is valid. */ + MESOSPHERE_INIT_ABORT_UNLESS(0 < size && size <= GenerateRandomBytesSizeMax); + + /* Ensure we successfully generate the random bytes. */ + MESOSPHERE_INIT_ABORT_UNLESS(GenerateRandomBytesImpl(dst, size)); + } + + void ReadWriteRegister(u32 *out, u64 address, u32 mask, u32 value) { + /* Ensure we successfully access the register. */ + MESOSPHERE_INIT_ABORT_UNLESS(ReadWriteRegisterImpl(out, address, mask, value)); + } + + } + + bool TryGetConfig(u64 *out, size_t num_qwords, ConfigItem config_item) { + /* Disable interrupts. */ + KScopedInterruptDisable di; + + /* Get the config. */ + return TryGetConfigImpl(out, num_qwords, config_item); + } + + void GetConfig(u64 *out, size_t num_qwords, ConfigItem config_item) { + /* Ensure we successfully get the config. */ + MESOSPHERE_ABORT_UNLESS(TryGetConfig(out, num_qwords, config_item)); + } + + bool SetConfig(ConfigItem config_item, u64 value) { + /* Disable interrupts. */ + KScopedInterruptDisable di; + + /* Set the config. */ + return SetConfigImpl(config_item, value); + } + + bool ReadWriteRegister(u32 *out, ams::svc::PhysicalAddress address, u32 mask, u32 value) { + /* Disable interrupts. */ + KScopedInterruptDisable di; + + /* Access the register. */ + return ReadWriteRegisterImpl(out, address, mask, value); + } + + void ConfigureCarveout(size_t which, uintptr_t address, size_t size) { + /* Disable interrupts. */ + KScopedInterruptDisable di; + + /* Ensure that we successfully configure the carveout. */ + MESOSPHERE_ABORT_UNLESS(ConfigureCarveoutImpl(which, address, size)); + } + + void GenerateRandomBytes(void *dst, size_t size) { + /* Check that the size is valid. */ + MESOSPHERE_ABORT_UNLESS(0 < size && size <= GenerateRandomBytesSizeMax); + + /* Disable interrupts. */ + KScopedInterruptDisable di; + + /* Acquire the exclusive right to generate random bytes. */ + KScopedSpinLock lk(g_generate_random_lock); + + /* Ensure we successfully generate the random bytes. */ + MESOSPHERE_ABORT_UNLESS(GenerateRandomBytesImpl(dst, size)); + } + + void ShowError(u32 color) { + /* Disable interrupts. */ + KScopedInterruptDisable di; + + /* Ensure we successfully show the error. */ + MESOSPHERE_ABORT_UNLESS(ShowErrorImpl(color)); + } + + void CallSecureMonitorFromUser(ams::svc::lp64::SecureMonitorArguments *args) { + /* Disable interrupts. */ + KScopedInterruptDisable di; + + /* Perform the call. */ + CallSecureMonitorFromUserImpl(args); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_secure_monitor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_secure_monitor.hpp new file mode 100644 index 00000000..6ee325aa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_secure_monitor.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere.hpp> +#include <mesosphere/arch/arm64/kern_secure_monitor_base.hpp> + +namespace ams::kern::board::nintendo::nx::smc { + + /* Types. */ + enum SmcId { + SmcId_User = 0, + SmcId_Supervisor = 1, + }; + + enum MemorySize { + MemorySize_4GB = 0, + MemorySize_6GB = 1, + MemorySize_8GB = 2, + }; + + enum MemoryArrangement { + MemoryArrangement_4GB = 0, + MemoryArrangement_4GBForAppletDev = 1, + MemoryArrangement_4GBForSystemDev = 2, + MemoryArrangement_6GB = 3, + MemoryArrangement_6GBForAppletDev = 4, + MemoryArrangement_8GB = 5, + }; + + enum class ConfigItem : u32 { + /* Standard config items. */ + DisableProgramVerification = 1, + DramId = 2, + SecurityEngineIrqNumber = 3, + Version = 4, + HardwareType = 5, + IsRetail = 6, + IsRecoveryBoot = 7, + DeviceId = 8, + BootReason = 9, + MemoryMode = 10, + IsDebugMode = 11, + KernelConfiguration = 12, + IsChargerHiZModeEnabled = 13, + IsQuest = 14, + RegulatorType = 15, + DeviceUniqueKeyGeneration = 16, + Package2Hash = 17, + + /* Extension config items for exosphere. */ + ExosphereApiVersion = 65000, + ExosphereNeedsReboot = 65001, + ExosphereNeedsShutdown = 65002, + ExosphereGitCommitHash = 65003, + ExosphereHasRcmBugPatch = 65004, + ExosphereBlankProdInfo = 65005, + ExosphereAllowCalWrites = 65006, + ExosphereEmummcType = 65007, + ExospherePayloadAddress = 65008, + ExosphereLogConfiguration = 65009, + ExosphereForceEnableUsb30 = 65010, + ExosphereSupportedHosVersion = 65011, + }; + + enum class SmcResult { + Success = 0, + NotImplemented = 1, + InvalidArgument = 2, + InProgress = 3, + NoAsyncOperation = 4, + InvalidAsyncOperation = 5, + NotPermitted = 6, + }; + + struct KernelConfiguration { + using DebugFillMemory = util::BitPack32::Field<0, 1, bool>; + using EnableUserExceptionHandlers = util::BitPack32::Field<DebugFillMemory::Next, 1, bool>; + using EnableUserPmuAccess = util::BitPack32::Field<EnableUserExceptionHandlers::Next, 1, bool>; + using IncreaseThreadResourceLimit = util::BitPack32::Field<EnableUserPmuAccess::Next, 1, bool>; + using DisableDynamicResourceLimits = util::BitPack32::Field<IncreaseThreadResourceLimit::Next, 1, bool>; + using Reserved5 = util::BitPack32::Field<DisableDynamicResourceLimits::Next, 3, u32>; + using UseSecureMonitorPanicCall = util::BitPack32::Field<Reserved5::Next, 1, bool>; + using Reserved9 = util::BitPack32::Field<UseSecureMonitorPanicCall::Next, 7, u32>; + using MemorySize = util::BitPack32::Field<Reserved9::Next, 2, smc::MemorySize>; + }; + + enum UserRebootType { + UserRebootType_None = 0, + UserRebootType_ToRcm = 1, + UserRebootType_ToPayload = 2, + UserRebootType_ToFatalError = 3, + }; + + void GenerateRandomBytes(void *dst, size_t size); + bool TryGetConfig(u64 *out, size_t num_qwords, ConfigItem config_item); + void GetConfig(u64 *out, size_t num_qwords, ConfigItem config_item); + bool ReadWriteRegister(u32 *out, ams::svc::PhysicalAddress address, u32 mask, u32 value); + void ConfigureCarveout(size_t which, uintptr_t address, size_t size); + + bool SetConfig(ConfigItem config_item, u64 value); + + void ShowError(u32 color); + + void CallSecureMonitorFromUser(ams::svc::lp64::SecureMonitorArguments *args); + + namespace init { + + void GetConfig(u64 *out, size_t num_qwords, ConfigItem config_item); + void GenerateRandomBytes(void *dst, size_t size); + void ReadWriteRegister(u32 *out, u64 address, u32 mask, u32 value); + + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_sema_registers.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_sema_registers.hpp new file mode 100644 index 00000000..fd59a49c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/nintendo/nx/kern_sema_registers.hpp @@ -0,0 +1,20 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#define RES_SEMA_SHRD_SMP_STA 0x000 +#define RES_SEMA_SHRD_SMP_SET 0x004 +#define RES_SEMA_SHRD_SMP_CLR 0x008 diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/qemu/virt/kern_k_system_control.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/qemu/virt/kern_k_system_control.cpp new file mode 100644 index 00000000..59972ef6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/qemu/virt/kern_k_system_control.cpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> +#include "kern_secure_monitor.hpp" + +namespace ams::kern::board::qemu::virt { + + /* User access. */ + void KSystemControl::CallSecureMonitorFromUser(ams::svc::lp64::SecureMonitorArguments *args) { + /* Invoke the secure monitor. */ + return smc::CallSecureMonitorFromUser(args); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/qemu/virt/kern_secure_monitor.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/qemu/virt/kern_secure_monitor.cpp new file mode 100644 index 00000000..a97fd53b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/qemu/virt/kern_secure_monitor.cpp @@ -0,0 +1,71 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> +#include "kern_secure_monitor.hpp" + +namespace ams::kern::board::qemu::virt::smc { + + namespace { + + enum UserFunctionId : u32 { + UserFunctionId_SetConfig = 0xC3000401, + UserFunctionId_GetConfig = 0xC3000002, + UserFunctionId_GetResult = 0xC3000003, + UserFunctionId_GetResultData = 0xC3000404, + UserFunctionId_ModularExponentiate = 0xC3000E05, + UserFunctionId_GenerateRandomBytes = 0xC3000006, + UserFunctionId_GenerateAesKek = 0xC3000007, + UserFunctionId_LoadAesKey = 0xC3000008, + UserFunctionId_ComputeAes = 0xC3000009, + UserFunctionId_GenerateSpecificAesKey = 0xC300000A, + UserFunctionId_ComputeCmac = 0xC300040B, + UserFunctionId_ReencryptDeviceUniqueData = 0xC300D60C, + UserFunctionId_DecryptDeviceUniqueData = 0xC300100D, + UserFunctionId_ModularExponentiateByStorageKey = 0xC300060F, + UserFunctionId_PrepareEsDeviceUniqueKey = 0xC3000610, + UserFunctionId_LoadPreparedAesKey = 0xC3000011, + UserFunctionId_PrepareEsCommonTitleKey = 0xC3000012, + }; + + } + + void CallSecureMonitorFromUser(ams::svc::lp64::SecureMonitorArguments *args) { + MESOSPHERE_LOG("Received SMC [%p %p %p %p %p %p %p %p] from %s\n", reinterpret_cast<void *>(args->r[0]), reinterpret_cast<void *>(args->r[1]), reinterpret_cast<void *>(args->r[2]), reinterpret_cast<void *>(args->r[3]), reinterpret_cast<void *>(args->r[4]), reinterpret_cast<void *>(args->r[5]), reinterpret_cast<void *>(args->r[6]), reinterpret_cast<void *>(args->r[7]), GetCurrentProcess().GetName()); + + switch (args->r[0]) { + case UserFunctionId_GetConfig: + { + switch (static_cast<ConfigItem>(args->r[1])) { + case ConfigItem::ExosphereApiVersion: + args->r[1] = (static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MAJOR & 0xFF) << 56) | + (static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MINOR & 0xFF) << 48) | + (static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MICRO & 0xFF) << 40) | + (static_cast<u64>(13) << 32) | + (static_cast<u64>(GetTargetFirmware()) << 0); + break; + default: + MESOSPHERE_PANIC("Unhandled GetConfig\n"); + } + + args->r[0] = static_cast<u64>(SmcResult::Success); + } + break; + default: + MESOSPHERE_PANIC("Unhandled SMC [%p %p %p %p %p %p %p %p]", reinterpret_cast<void *>(args->r[0]), reinterpret_cast<void *>(args->r[1]), reinterpret_cast<void *>(args->r[2]), reinterpret_cast<void *>(args->r[3]), reinterpret_cast<void *>(args->r[4]), reinterpret_cast<void *>(args->r[5]), reinterpret_cast<void *>(args->r[6]), reinterpret_cast<void *>(args->r[7])); + } + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/qemu/virt/kern_secure_monitor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/qemu/virt/kern_secure_monitor.hpp new file mode 100644 index 00000000..8aeaec11 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/board/qemu/virt/kern_secure_monitor.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere.hpp> + +namespace ams::kern::board::qemu::virt::smc { + + enum class ConfigItem : u32 { + /* Standard config items. */ + DisableProgramVerification = 1, + DramId = 2, + SecurityEngineIrqNumber = 3, + Version = 4, + HardwareType = 5, + IsRetail = 6, + IsRecoveryBoot = 7, + DeviceId = 8, + BootReason = 9, + MemoryMode = 10, + IsDebugMode = 11, + KernelConfiguration = 12, + IsChargerHiZModeEnabled = 13, + IsQuest = 14, + RegulatorType = 15, + DeviceUniqueKeyGeneration = 16, + Package2Hash = 17, + + /* Extension config items for exosphere. */ + ExosphereApiVersion = 65000, + ExosphereNeedsReboot = 65001, + ExosphereNeedsShutdown = 65002, + ExosphereGitCommitHash = 65003, + ExosphereHasRcmBugPatch = 65004, + ExosphereBlankProdInfo = 65005, + ExosphereAllowCalWrites = 65006, + ExosphereEmummcType = 65007, + ExospherePayloadAddress = 65008, + ExosphereLogConfiguration = 65009, + ExosphereForceEnableUsb30 = 65010, + ExosphereSupportedHosVersion = 65011, + }; + + enum class SmcResult { + Success = 0, + NotImplemented = 1, + InvalidArgument = 2, + InProgress = 3, + NoAsyncOperation = 4, + InvalidAsyncOperation = 5, + NotPermitted = 6, + }; + + void CallSecureMonitorFromUser(ams::svc::lp64::SecureMonitorArguments *args); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/init/kern_init_elf.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/init/kern_init_elf.cpp new file mode 100644 index 00000000..a9ef3666 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/init/kern_init_elf.cpp @@ -0,0 +1,143 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::init::Elf { + + /* API to apply relocations or call init array. */ + void ApplyRelocations(uintptr_t base_address, const Dyn *dynamic) { + uintptr_t dyn_rel = 0; + uintptr_t dyn_rela = 0; + uintptr_t dyn_relr = 0; + uintptr_t rel_count = 0; + uintptr_t rela_count = 0; + uintptr_t relr_sz = 0; + uintptr_t rel_ent = 0; + uintptr_t rela_ent = 0; + uintptr_t relr_ent = 0; + + /* Iterate over all tags, identifying important extents. */ + for (const Dyn *cur_entry = dynamic; cur_entry->GetTag() != DT_NULL; cur_entry++) { + switch (cur_entry->GetTag()) { + case DT_REL: + dyn_rel = base_address + cur_entry->GetPtr(); + break; + case DT_RELA: + dyn_rela = base_address + cur_entry->GetPtr(); + break; + case DT_RELR: + dyn_relr = base_address + cur_entry->GetPtr(); + break; + case DT_RELENT: + rel_ent = cur_entry->GetValue(); + break; + case DT_RELAENT: + rela_ent = cur_entry->GetValue(); + break; + case DT_RELRENT: + relr_ent = cur_entry->GetValue(); + break; + case DT_RELCOUNT: + rel_count = cur_entry->GetValue(); + break; + case DT_RELACOUNT: + rela_count = cur_entry->GetValue(); + break; + case DT_RELRSZ: + relr_sz = cur_entry->GetValue(); + break; + } + } + + /* Apply all Rel relocations */ + if (rel_count > 0) { + /* Check that the rel relocations are applyable. */ + MESOSPHERE_INIT_ABORT_UNLESS(dyn_rel != 0); + MESOSPHERE_INIT_ABORT_UNLESS(rel_ent == sizeof(Elf::Rel)); + + for (size_t i = 0; i < rel_count; ++i) { + const auto &rel = reinterpret_cast<const Elf::Rel *>(dyn_rel)[i]; + + /* Only allow architecture-specific relocations. */ + while (rel.GetType() != R_ARCHITECTURE_RELATIVE) { /* ... */ } + + /* Apply the relocation. */ + Elf::Addr *target_address = reinterpret_cast<Elf::Addr *>(base_address + rel.GetOffset()); + *target_address += base_address; + } + } + + /* Apply all Rela relocations. */ + if (rela_count > 0) { + /* Check that the rela relocations are applyable. */ + MESOSPHERE_INIT_ABORT_UNLESS(dyn_rela != 0); + MESOSPHERE_INIT_ABORT_UNLESS(rela_ent == sizeof(Elf::Rela)); + + for (size_t i = 0; i < rela_count; ++i) { + const auto &rela = reinterpret_cast<const Elf::Rela *>(dyn_rela)[i]; + + /* Only allow architecture-specific relocations. */ + while (rela.GetType() != R_ARCHITECTURE_RELATIVE) { /* ... */ } + + /* Apply the relocation. */ + Elf::Addr *target_address = reinterpret_cast<Elf::Addr *>(base_address + rela.GetOffset()); + *target_address = base_address + rela.GetAddend(); + } + } + + /* Apply all Relr relocations. */ + if (relr_sz >= sizeof(Elf::Relr)) { + /* Check that the relr relocations are applyable. */ + MESOSPHERE_INIT_ABORT_UNLESS(dyn_relr != 0); + MESOSPHERE_INIT_ABORT_UNLESS(relr_ent == sizeof(Elf::Relr)); + + const size_t relr_count = relr_sz / sizeof(Elf::Relr); + + Elf::Addr *where = nullptr; + for (size_t i = 0; i < relr_count; ++i) { + const auto &relr = reinterpret_cast<const Elf::Relr *>(dyn_relr)[i]; + + if (relr.IsLocation()) { + /* Update location. */ + where = reinterpret_cast<Elf::Addr *>(base_address + relr.GetLocation()); + + /* Apply the relocation. */ + *(where++) += base_address; + } else { + /* Get the bitmap. */ + u64 bitmap = relr.GetBitmap(); + + /* Apply all relocations. */ + while (bitmap != 0) { + const u64 next = util::CountTrailingZeros(bitmap); + bitmap &= ~(static_cast<u64>(1) << next); + where[next] += base_address; + } + + /* Advance. */ + where += BITSIZEOF(bitmap) - 1; + } + } + } + } + + void CallInitArrayFuncs(uintptr_t init_array_start, uintptr_t init_array_end) { + for (uintptr_t cur_entry = init_array_start; cur_entry < init_array_end; cur_entry += sizeof(void *)) { + (*(void (**)())(cur_entry))(); + } + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/init/kern_init_slab_setup.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/init/kern_init_slab_setup.cpp new file mode 100644 index 00000000..b82e78f8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/init/kern_init_slab_setup.cpp @@ -0,0 +1,270 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::init { + + /* For macro convenience. */ + using KSessionRequestMappings = KSessionRequest::SessionMappings::DynamicMappings; + using KThreadLockInfo = KThread::LockWithPriorityInheritanceInfo; + + #define SLAB_COUNT(CLASS) g_slab_resource_counts.num_##CLASS + + #define FOREACH_SLAB_TYPE(HANDLER, ...) \ + HANDLER(KProcess, (SLAB_COUNT(KProcess)), ## __VA_ARGS__) \ + HANDLER(KThread, (SLAB_COUNT(KThread)), ## __VA_ARGS__) \ + HANDLER(KEvent, (SLAB_COUNT(KEvent)), ## __VA_ARGS__) \ + HANDLER(KInterruptEvent, (SLAB_COUNT(KInterruptEvent)), ## __VA_ARGS__) \ + HANDLER(KPort, (SLAB_COUNT(KPort)), ## __VA_ARGS__) \ + HANDLER(KSharedMemory, (SLAB_COUNT(KSharedMemory)), ## __VA_ARGS__) \ + HANDLER(KSharedMemoryInfo, (SLAB_COUNT(KSharedMemory) * 8), ## __VA_ARGS__) \ + HANDLER(KTransferMemory, (SLAB_COUNT(KTransferMemory)), ## __VA_ARGS__) \ + HANDLER(KCodeMemory, (SLAB_COUNT(KCodeMemory)), ## __VA_ARGS__) \ + HANDLER(KDeviceAddressSpace, (SLAB_COUNT(KDeviceAddressSpace)), ## __VA_ARGS__) \ + HANDLER(KSession, (SLAB_COUNT(KSession)), ## __VA_ARGS__) \ + HANDLER(KSessionRequest, (SLAB_COUNT(KSession) * 2), ## __VA_ARGS__) \ + HANDLER(KLightSession, (SLAB_COUNT(KLightSession)), ## __VA_ARGS__) \ + HANDLER(KThreadLocalPage, (SLAB_COUNT(KProcess) + (SLAB_COUNT(KProcess) + SLAB_COUNT(KThread)) / 8), ## __VA_ARGS__) \ + HANDLER(KObjectName, (SLAB_COUNT(KObjectName)), ## __VA_ARGS__) \ + HANDLER(KResourceLimit, (SLAB_COUNT(KResourceLimit)), ## __VA_ARGS__) \ + HANDLER(KEventInfo, (SLAB_COUNT(KThread) + SLAB_COUNT(KDebug)), ## __VA_ARGS__) \ + HANDLER(KDebug, (SLAB_COUNT(KDebug)), ## __VA_ARGS__) \ + HANDLER(KIoPool, (SLAB_COUNT(KIoPool)), ## __VA_ARGS__) \ + HANDLER(KIoRegion, (SLAB_COUNT(KIoRegion)), ## __VA_ARGS__) \ + HANDLER(KSessionRequestMappings, (SLAB_COUNT(KSessionRequestMappings)), ## __VA_ARGS__) \ + HANDLER(KSecureSystemResource, (SLAB_COUNT(KProcess)), ## __VA_ARGS__) \ + HANDLER(KThreadLockInfo, (SLAB_COUNT(KThread)), ## __VA_ARGS__) + + namespace { + + + #define DEFINE_SLAB_TYPE_ENUM_MEMBER(NAME, COUNT, ...) KSlabType_##NAME, + + enum KSlabType : u32 { + FOREACH_SLAB_TYPE(DEFINE_SLAB_TYPE_ENUM_MEMBER) + KSlabType_Count, + }; + + #undef DEFINE_SLAB_TYPE_ENUM_MEMBER + + /* Constexpr counts. */ + constexpr size_t SlabCountKProcess = 80; + constexpr size_t SlabCountKThread = 800; + constexpr size_t SlabCountKEvent = 900; + constexpr size_t SlabCountKInterruptEvent = 100; + constexpr size_t SlabCountKPort = 384; + constexpr size_t SlabCountKSharedMemory = 80; + constexpr size_t SlabCountKTransferMemory = 200; + constexpr size_t SlabCountKCodeMemory = 10; + constexpr size_t SlabCountKDeviceAddressSpace = 300; + constexpr size_t SlabCountKSession = 1133; + constexpr size_t SlabCountKLightSession = 100; + constexpr size_t SlabCountKObjectName = 7; + constexpr size_t SlabCountKResourceLimit = 5; + constexpr size_t SlabCountKDebug = cpu::NumCores; + constexpr size_t SlabCountKIoPool = 1; + constexpr size_t SlabCountKIoRegion = 6; + constexpr size_t SlabcountKSessionRequestMappings = 40; + + constexpr size_t SlabCountExtraKThread = (1024 + 256 + 256) - SlabCountKThread; + + namespace test { + + constexpr size_t RequiredSizeForExtraThreadCount = SlabCountExtraKThread * (sizeof(KThread) + (sizeof(KThreadLocalPage) / 8) + sizeof(KEventInfo)); + static_assert(RequiredSizeForExtraThreadCount <= KernelSlabHeapAdditionalSize); + + static_assert(KernelPageBufferHeapSize == 2 * PageSize + (SlabCountKProcess + SlabCountKThread + (SlabCountKProcess + SlabCountKThread) / 8) * PageSize); + static_assert(KernelPageBufferAdditionalSize == (SlabCountExtraKThread + (SlabCountExtraKThread / 8)) * PageSize); + + } + + /* Global to hold our resource counts. */ + constinit KSlabResourceCounts g_slab_resource_counts = { + .num_KProcess = SlabCountKProcess, + .num_KThread = SlabCountKThread, + .num_KEvent = SlabCountKEvent, + .num_KInterruptEvent = SlabCountKInterruptEvent, + .num_KPort = SlabCountKPort, + .num_KSharedMemory = SlabCountKSharedMemory, + .num_KTransferMemory = SlabCountKTransferMemory, + .num_KCodeMemory = SlabCountKCodeMemory, + .num_KDeviceAddressSpace = SlabCountKDeviceAddressSpace, + .num_KSession = SlabCountKSession, + .num_KLightSession = SlabCountKLightSession, + .num_KObjectName = SlabCountKObjectName, + .num_KResourceLimit = SlabCountKResourceLimit, + .num_KDebug = SlabCountKDebug, + .num_KIoPool = SlabCountKIoPool, + .num_KIoRegion = SlabCountKIoRegion, + .num_KSessionRequestMappings = SlabcountKSessionRequestMappings, + }; + + template<typename T> + NOINLINE KVirtualAddress InitializeSlabHeap(KVirtualAddress address, size_t num_objects) { + const size_t size = util::AlignUp(sizeof(T) * num_objects, alignof(void *)); + KVirtualAddress start = util::AlignUp(GetInteger(address), alignof(T)); + + if (size > 0) { + const KMemoryRegion *region = KMemoryLayout::Find(start + size - 1); + MESOSPHERE_ABORT_UNLESS(region != nullptr); + MESOSPHERE_ABORT_UNLESS(region->IsDerivedFrom(KMemoryRegionType_KernelSlab)); + T::InitializeSlabHeap(GetVoidPointer(start), size); + } + + return start + size; + } + + } + + + const KSlabResourceCounts &GetSlabResourceCounts() { + return g_slab_resource_counts; + } + + void InitializeSlabResourceCounts() { + /* Note: Nintendo initializes all fields here, but we initialize all constants at compile-time. */ + if (KSystemControl::Init::ShouldIncreaseThreadResourceLimit()) { + g_slab_resource_counts.num_KThread += SlabCountExtraKThread; + } + } + + size_t CalculateSlabHeapGapSize() { + constexpr size_t KernelSlabHeapGapSize = 2_MB - 356_KB; + static_assert(KernelSlabHeapGapSize <= KernelSlabHeapGapsSizeMax); + return KernelSlabHeapGapSize; + } + + size_t CalculateTotalSlabHeapSize() { + size_t size = 0; + + #define ADD_SLAB_SIZE(NAME, COUNT, ...) ({ \ + size += alignof(NAME); \ + size += util::AlignUp(sizeof(NAME) * (COUNT), alignof(void *)); \ + }); + + /* Add the size required for each slab. */ + FOREACH_SLAB_TYPE(ADD_SLAB_SIZE) + + #undef ADD_SLAB_SIZE + + /* Add the reserved size. */ + size += CalculateSlabHeapGapSize(); + + return size; + } + + void InitializeSlabHeaps() { + /* Get the slab region, since that's where we'll be working. */ + const KMemoryRegion &slab_region = KMemoryLayout::GetSlabRegion(); + KVirtualAddress address = slab_region.GetAddress(); + + /* Clear the slab region. */ + std::memset(GetVoidPointer(address), 0, slab_region.GetSize()); + + /* Initialize slab type array to be in sorted order. */ + KSlabType slab_types[KSlabType_Count]; + for (size_t i = 0; i < util::size(slab_types); i++) { slab_types[i] = static_cast<KSlabType>(i); } + + /* N shuffles the slab type array with the following simple algorithm. */ + for (size_t i = 0; i < util::size(slab_types); i++) { + const size_t rnd = KSystemControl::GenerateRandomRange(0, util::size(slab_types) - 1); + std::swap(slab_types[i], slab_types[rnd]); + } + + /* Create an array to represent the gaps between the slabs. */ + const size_t total_gap_size = CalculateSlabHeapGapSize(); + size_t slab_gaps[util::size(slab_types)]; + for (size_t i = 0; i < util::size(slab_gaps); i++) { + /* Note: This is an off-by-one error from Nintendo's intention, because GenerateRandomRange is inclusive. */ + /* However, Nintendo also has the off-by-one error, and it's "harmless", so we will include it ourselves. */ + slab_gaps[i] = KSystemControl::GenerateRandomRange(0, total_gap_size); + } + + /* Sort the array, so that we can treat differences between values as offsets to the starts of slabs. */ + for (size_t i = 1; i < util::size(slab_gaps); i++) { + for (size_t j = i; j > 0 && slab_gaps[j-1] > slab_gaps[j]; j--) { + std::swap(slab_gaps[j], slab_gaps[j-1]); + } + } + + /* Track the gaps, so that we can free them to the unused slab tree. */ + KVirtualAddress gap_start = address; + size_t gap_size = 0; + + for (size_t i = 0; i < util::size(slab_types); i++) { + /* Add the random gap to the address. */ + const auto cur_gap = (i == 0) ? slab_gaps[0] : slab_gaps[i] - slab_gaps[i - 1]; + address += cur_gap; + gap_size += cur_gap; + + #define INITIALIZE_SLAB_HEAP(NAME, COUNT, ...) \ + case KSlabType_##NAME: \ + if (COUNT > 0) { \ + address = InitializeSlabHeap<NAME>(address, COUNT); \ + } \ + break; + + /* Initialize the slabheap. */ + switch (slab_types[i]) { + /* For each of the slab types, we want to initialize that heap. */ + FOREACH_SLAB_TYPE(INITIALIZE_SLAB_HEAP) + /* If we somehow get an invalid type, abort. */ + MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); + } + + /* If we've hit the end of a gap, free it. */ + if (gap_start + gap_size != address) { + FreeUnusedSlabMemory(gap_start, gap_size); + gap_start = address; + gap_size = 0; + } + } + + /* Free the end of the slab region. */ + FreeUnusedSlabMemory(gap_start, gap_size + (slab_region.GetEndAddress() - GetInteger(address))); + } + +} + +namespace ams::kern { + + void KPageBufferSlabHeap::Initialize(KDynamicPageManager &allocator) { + /* Get slab resource counts. */ + const auto &counts = init::GetSlabResourceCounts(); + + /* If size is correct, account for thread local pages. */ + if (BufferSize == PageSize) { + s_buffer_count += counts.num_KProcess + counts.num_KThread + (counts.num_KProcess + counts.num_KThread) / 8; + } + + /* Set our object size. */ + m_obj_size = BufferSize; + + /* Initialize the base allocator. */ + KSlabHeapImpl::Initialize(); + + /* Allocate the desired page count. */ + for (size_t i = 0; i < s_buffer_count; ++i) { + /* Allocate an appropriate buffer. */ + auto * const pb = (BufferSize <= PageSize) ? allocator.Allocate() : allocator.Allocate(BufferSize / PageSize); + MESOSPHERE_ABORT_UNLESS(pb != nullptr); + + /* Free to our slab. */ + KSlabHeapImpl::Free(pb); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_debug_log.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_debug_log.cpp new file mode 100644 index 00000000..89893795 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_debug_log.cpp @@ -0,0 +1,197 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> +#include "kern_debug_log_impl.hpp" + +namespace ams::kern { + + namespace { + + constinit KSpinLock g_debug_log_lock; + constinit bool g_initialized_impl; + + /* NOTE: Nintendo's print buffer is size 0x100. */ + constinit char g_print_buffer[0x400]; + + void PutString(const char *str) { + /* Only print if the implementation is initialized. */ + if (AMS_UNLIKELY(!g_initialized_impl)) { + return; + } + + #if defined(MESOSPHERE_DEBUG_LOG_USE_SEMIHOSTING) + KDebugLogImpl::PutStringBySemihosting(str); + #else + while (*str) { + /* Get a character. */ + const char c = *(str++); + + /* Print the character. */ + if (c == '\n') { + KDebugLogImpl::PutChar('\r'); + } + KDebugLogImpl::PutChar(c); + } + + KDebugLogImpl::Flush(); + #endif + } + + #if defined(MESOSPHERE_ENABLE_DEBUG_PRINT) + + Result PutUserString(ams::kern::svc::KUserPointer<const char *> user_str, size_t len) { + /* Only print if the implementation is initialized. */ + if (!g_initialized_impl) { + R_SUCCEED(); + } + + #if defined(MESOSPHERE_DEBUG_LOG_USE_SEMIHOSTING) + /* TODO: should we do this properly? */ + KDebugLogImpl::PutStringBySemihosting(user_str.GetUnsafePointer()); + MESOSPHERE_UNUSED(len); + #else + for (size_t i = 0; i < len; ++i) { + /* Get a character. */ + char c; + R_TRY(user_str.CopyArrayElementTo(std::addressof(c), i)); + + /* Print the character. */ + if (c == '\n') { + KDebugLogImpl::PutChar('\r'); + } + KDebugLogImpl::PutChar(c); + } + + KDebugLogImpl::Flush(); + #endif + + R_SUCCEED(); + } + + #endif + + ALWAYS_INLINE void FormatU64(char * const dst, u64 value) { + /* Adjust, so that we can print the value backwards. */ + char *cur = dst + 2 * sizeof(value); + + /* Format the value in (as %016lx) */ + while (cur > dst) { + /* Extract the digit. */ + const auto digit = value & 0xF; + value >>= 4; + + *(--cur) = (digit <= 9) ? ('0' + digit) : ('a' + digit - 10); + } + } + + } + + void KDebugLog::Initialize() { + if (KTargetSystem::IsDebugLoggingEnabled()) { + KScopedInterruptDisable di; + KScopedSpinLock lk(g_debug_log_lock); + + if (!g_initialized_impl) { + g_initialized_impl = KDebugLogImpl::Initialize(); + } + } + } + + void KDebugLog::Printf(const char *format, ...) { + if (KTargetSystem::IsDebugLoggingEnabled()) { + ::std::va_list vl; + va_start(vl, format); + VPrintf(format, vl); + va_end(vl); + } + } + + void KDebugLog::VPrintf(const char *format, ::std::va_list vl) { + if (KTargetSystem::IsDebugLoggingEnabled()) { + KScopedInterruptDisable di; + KScopedSpinLock lk(g_debug_log_lock); + + VSNPrintf(g_print_buffer, util::size(g_print_buffer), format, vl); + PutString(g_print_buffer); + } + } + + void KDebugLog::VSNPrintf(char *dst, const size_t dst_size, const char *format, ::std::va_list vl) { + ::ams::util::TVSNPrintf(dst, dst_size, format, vl); + } + + void KDebugLog::LogException(const char *str) { + if (KTargetSystem::IsDebugLoggingEnabled()) { + /* Get the current program ID. */ + /* NOTE: Nintendo does this after printing the string, */ + /* but it seems wise to avoid holding the lock/disabling interrupts */ + /* for longer than is strictly necessary. */ + char suffix[18]; + if (const auto *cur_process = GetCurrentProcessPointer(); AMS_LIKELY(cur_process != nullptr)) { + FormatU64(suffix, cur_process->GetProgramId()); + suffix[16] = '\n'; + suffix[17] = '\x00'; + } else { + suffix[0] = '\n'; + suffix[1] = '\x00'; + } + + KScopedInterruptDisable di; + KScopedSpinLock lk(g_debug_log_lock); + + /* Log the string. */ + PutString(str); + + /* Log the program id (and newline) suffix. */ + PutString(suffix); + } + } + + Result KDebugLog::PrintUserString(ams::kern::svc::KUserPointer<const char *> user_str, size_t len) { + /* If printing is enabled, print the user string. */ + #if defined(MESOSPHERE_ENABLE_DEBUG_PRINT) + if (KTargetSystem::IsDebugLoggingEnabled()) { + KScopedInterruptDisable di; + KScopedSpinLock lk(g_debug_log_lock); + + R_TRY(PutUserString(user_str, len)); + } + #else + MESOSPHERE_UNUSED(user_str, len); + #endif + + R_SUCCEED(); + } + + void KDebugLog::Save() { + if (KTargetSystem::IsDebugLoggingEnabled()) { + KScopedInterruptDisable di; + KScopedSpinLock lk(g_debug_log_lock); + + KDebugLogImpl::Save(); + } + } + + void KDebugLog::Restore() { + if (KTargetSystem::IsDebugLoggingEnabled()) { + KScopedInterruptDisable di; + KScopedSpinLock lk(g_debug_log_lock); + + KDebugLogImpl::Restore(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_debug_log_impl.arch.arm64.s b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_debug_log_impl.arch.arm64.s new file mode 100644 index 00000000..e7d4423e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_debug_log_impl.arch.arm64.s @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* ams::kern::KDebugLogImpl::PutStringBySemihosting(const char *str) */ +.section .text._ZN3ams4kern13KDebugLogImpl22PutStringBySemihostingEPKc, "ax", %progbits +.global _ZN3ams4kern13KDebugLogImpl22PutStringBySemihostingEPKc +.type _ZN3ams4kern13KDebugLogImpl22PutStringBySemihostingEPKc, %function +.balign 0x10 +_ZN3ams4kern13KDebugLogImpl22PutStringBySemihostingEPKc: + mov x1, x0 + mov x0, #0x4 + hlt #0xF000 + ret + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_debug_log_impl.board.nintendo_nx.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_debug_log_impl.board.nintendo_nx.cpp new file mode 100644 index 00000000..d5b210af --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_debug_log_impl.board.nintendo_nx.cpp @@ -0,0 +1,182 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> +#include "kern_debug_log_impl.hpp" + +namespace ams::kern { + +#if defined(MESOSPHERE_DEBUG_LOG_USE_UART) + + namespace { + + constexpr bool DoSaveAndRestore = false; + + enum UartRegister { + UartRegister_THR = 0, + UartRegister_IER = 1, + UartRegister_FCR = 2, + UartRegister_LCR = 3, + + UartRegister_LSR = 5, + + UartRegister_IRDA_CSR = 8, + + UartRegister_DLL = 0, + UartRegister_DLH = 1, + }; + + KVirtualAddress g_uart_address = 0; + + [[maybe_unused]] constinit u32 g_saved_registers[5]; + + ALWAYS_INLINE u32 ReadUartRegister(UartRegister which) { + return GetPointer<volatile u32>(g_uart_address)[which]; + } + + ALWAYS_INLINE void WriteUartRegister(UartRegister which, u32 value) { + GetPointer<volatile u32>(g_uart_address)[which] = value; + } + + } + + bool KDebugLogImpl::Initialize() { + /* Get the uart memory region. */ + const KMemoryRegion *uart_region = KMemoryLayout::GetPhysicalMemoryRegionTree().FindFirstDerived(KMemoryRegionType_Uart); + if (uart_region == nullptr) { + return false; + } + + /* Set the uart register base address. */ + g_uart_address = uart_region->GetPairAddress(); + if (g_uart_address == Null<KVirtualAddress>) { + return false; + } + + /* NOTE: We assume here that UART init/config has been done by the Secure Monitor. */ + /* As such, we only need to disable interrupts. */ + WriteUartRegister(UartRegister_IER, 0x00); + + return true; + } + + void KDebugLogImpl::PutChar(char c) { + while (ReadUartRegister(UartRegister_LSR) & 0x100) { + /* While the FIFO is full, yield. */ + cpu::Yield(); + } + WriteUartRegister(UartRegister_THR, c); + cpu::DataSynchronizationBarrier(); + } + + void KDebugLogImpl::Flush() { + while ((ReadUartRegister(UartRegister_LSR) & 0x40) == 0) { + /* Wait for the TMTY bit to be one (transmit empty). */ + } + } + + void KDebugLogImpl::Save() { + if constexpr (DoSaveAndRestore) { + /* Save LCR, IER, FCR. */ + g_saved_registers[0] = ReadUartRegister(UartRegister_LCR); + g_saved_registers[1] = ReadUartRegister(UartRegister_IER); + g_saved_registers[2] = ReadUartRegister(UartRegister_FCR); + + /* Set Divisor Latch Access bit, to allow access to DLL/DLH */ + WriteUartRegister(UartRegister_LCR, 0x80); + ReadUartRegister(UartRegister_LCR); + + /* Save DLL/DLH. */ + g_saved_registers[3] = ReadUartRegister(UartRegister_DLL); + g_saved_registers[4] = ReadUartRegister(UartRegister_DLH); + + /* Restore Divisor Latch Access bit. */ + WriteUartRegister(UartRegister_LCR, g_saved_registers[0]); + ReadUartRegister(UartRegister_LCR); + } + } + + void KDebugLogImpl::Restore() { + if constexpr (DoSaveAndRestore) { + /* Set Divisor Latch Access bit, to allow access to DLL/DLH */ + WriteUartRegister(UartRegister_LCR, 0x80); + ReadUartRegister(UartRegister_LCR); + + /* Restore DLL/DLH. */ + WriteUartRegister(UartRegister_DLL, g_saved_registers[3]); + WriteUartRegister(UartRegister_DLH, g_saved_registers[4]); + ReadUartRegister(UartRegister_DLH); + + /* Restore Divisor Latch Access bit. */ + WriteUartRegister(UartRegister_LCR, g_saved_registers[0]); + ReadUartRegister(UartRegister_LCR); + + /* Restore IER and FCR. */ + WriteUartRegister(UartRegister_IER, g_saved_registers[1]); + WriteUartRegister(UartRegister_FCR, g_saved_registers[2] | 2); + WriteUartRegister(UartRegister_IRDA_CSR, 0x02); + ReadUartRegister(UartRegister_FCR); + } + } + +#elif defined(MESOSPHERE_DEBUG_LOG_USE_IRAM_RINGBUFFER) + + namespace { + + constinit KVirtualAddress g_debug_iram_address = 0; + + constexpr size_t RingBufferSize = 0x5000; + constinit uintptr_t g_offset = 0; + + constinit u8 g_saved_buffer[RingBufferSize]; + + } + + bool KDebugLogImpl::Initialize() { + /* Set the base address. */ + g_debug_iram_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsIram) + 0x38000; + + std::memset(GetVoidPointer(g_debug_iram_address), 0xFF, RingBufferSize); + + return true; + } + + void KDebugLogImpl::PutChar(char c) { + GetPointer<char>(g_debug_iram_address)[g_offset++] = c; + + if (g_offset == RingBufferSize) { + g_offset = 0; + } + } + + void KDebugLogImpl::Flush() { + /* ... */ + } + + void KDebugLogImpl::Save() { + std::memcpy(g_saved_buffer, GetVoidPointer(g_debug_iram_address), RingBufferSize); + } + + void KDebugLogImpl::Restore() { + std::memcpy(GetVoidPointer(g_debug_iram_address), g_saved_buffer, RingBufferSize); + } + +#else + + #error "Unknown Debug UART device!" + +#endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_debug_log_impl.board.qemu_virt.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_debug_log_impl.board.qemu_virt.cpp new file mode 100644 index 00000000..a71b904d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_debug_log_impl.board.qemu_virt.cpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> +#include "kern_debug_log_impl.hpp" + +namespace ams::kern { + +#if defined(MESOSPHERE_DEBUG_LOG_USE_SEMIHOSTING) + + bool KDebugLogImpl::Initialize() { + return true; + } + + void KDebugLogImpl::PutChar(char c) { + /* TODO */ + AMS_UNUSED(c); + } + + void KDebugLogImpl::Flush() { + /* ... */ + } + + void KDebugLogImpl::Save() { + /* ... */ + } + + void KDebugLogImpl::Restore() { + /* ... */ + } + +#else + + #error "Unknown Debug device!" + +#endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_debug_log_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_debug_log_impl.hpp new file mode 100644 index 00000000..a34591db --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_debug_log_impl.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere.hpp> + +namespace ams::kern { + + class KDebugLogImpl { + public: + static NOINLINE bool Initialize(); + static NOINLINE void PutStringBySemihosting(const char *s); + static NOINLINE void PutChar(char c); + static NOINLINE void Flush(); + + /* Functionality for preserving across sleep. */ + static NOINLINE void Save(); + static NOINLINE void Restore(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_initial_process.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_initial_process.cpp new file mode 100644 index 00000000..ab38d577 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_initial_process.cpp @@ -0,0 +1,324 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + struct InitialProcessInfo { + KProcess *process; + size_t stack_size; + s32 priority; + }; + + constinit KPhysicalAddress g_initial_process_binary_phys_addr = Null<KPhysicalAddress>; + constinit KVirtualAddress g_initial_process_binary_address = Null<KVirtualAddress>; + constinit size_t g_initial_process_binary_size = 0; + constinit InitialProcessBinaryHeader g_initial_process_binary_header = {}; + constinit size_t g_initial_process_secure_memory_size = 0; + constinit u64 g_initial_process_id_min = std::numeric_limits<u64>::max(); + constinit u64 g_initial_process_id_max = std::numeric_limits<u64>::min(); + + void LoadInitialProcessBinaryHeader() { + if (g_initial_process_binary_header.magic != InitialProcessBinaryMagic) { + /* Get the virtual address. */ + MESOSPHERE_INIT_ABORT_UNLESS(g_initial_process_binary_phys_addr != Null<KPhysicalAddress>); + const KVirtualAddress virt_addr = KMemoryLayout::GetLinearVirtualAddress(g_initial_process_binary_phys_addr); + + /* Copy and validate the header. */ + g_initial_process_binary_header = *GetPointer<InitialProcessBinaryHeader>(virt_addr); + MESOSPHERE_ABORT_UNLESS(g_initial_process_binary_header.magic == InitialProcessBinaryMagic); + MESOSPHERE_ABORT_UNLESS(g_initial_process_binary_header.num_processes <= init::GetSlabResourceCounts().num_KProcess); + + /* Set the image address. */ + g_initial_process_binary_address = virt_addr; + + /* Process/calculate the secure memory size. */ + KVirtualAddress current = g_initial_process_binary_address + sizeof(InitialProcessBinaryHeader); + const KVirtualAddress end = g_initial_process_binary_address + g_initial_process_binary_header.size; + const size_t num_processes = g_initial_process_binary_header.num_processes; + for (size_t i = 0; i < num_processes; ++i) { + /* Validate that we can read the current KIP. */ + MESOSPHERE_ABORT_UNLESS(current <= end - sizeof(KInitialProcessHeader)); + + /* Attach to the current KIP. */ + KInitialProcessReader reader; + KVirtualAddress data = reader.Attach(current); + MESOSPHERE_ABORT_UNLESS(data != Null<KVirtualAddress>); + + /* If the process uses secure memory, account for that. */ + if (reader.UsesSecureMemory()) { + g_initial_process_secure_memory_size += reader.GetSize() + util::AlignUp(reader.GetStackSize(), PageSize); + } + + /* Advance to the next KIP. */ + current = data + reader.GetBinarySize(); + } + } + } + + void CreateProcesses(InitialProcessInfo *infos) { + /* Determine process image extents. */ + KVirtualAddress current = g_initial_process_binary_address + sizeof(InitialProcessBinaryHeader); + KVirtualAddress end = g_initial_process_binary_address + g_initial_process_binary_header.size; + + /* Decide on pools to use. */ + const auto unsafe_pool = static_cast<KMemoryManager::Pool>(KSystemControl::GetCreateProcessMemoryPool()); + const auto secure_pool = (GetTargetFirmware() >= TargetFirmware_2_0_0) ? KMemoryManager::Pool_Secure : unsafe_pool; + + const size_t num_processes = g_initial_process_binary_header.num_processes; + for (size_t i = 0; i < num_processes; ++i) { + /* Validate that we can read the current KIP header. */ + MESOSPHERE_ABORT_UNLESS(current <= end - sizeof(KInitialProcessHeader)); + + /* Attach to the current kip. */ + KInitialProcessReader reader; + KVirtualAddress data = reader.Attach(current); + MESOSPHERE_ABORT_UNLESS(data != Null<KVirtualAddress>); + + /* Ensure that the remainder of our parse is page aligned. */ + if (!util::IsAligned(GetInteger(data), PageSize)) { + const KVirtualAddress aligned_data = util::AlignDown(GetInteger(data), PageSize); + std::memmove(GetVoidPointer(aligned_data), GetVoidPointer(data), end - data); + + data = aligned_data; + end -= (data - aligned_data); + } + + /* If we crossed a page boundary, free the pages we're done using. */ + if (KVirtualAddress aligned_current = util::AlignDown(GetInteger(current), PageSize); aligned_current != data) { + const size_t freed_size = data - aligned_current; + Kernel::GetMemoryManager().Close(KMemoryLayout::GetLinearPhysicalAddress(aligned_current), freed_size / PageSize); + Kernel::GetSystemResourceLimit().Release(ams::svc::LimitableResource_PhysicalMemoryMax, freed_size); + } + + /* Parse process parameters. */ + ams::svc::CreateProcessParameter params; + MESOSPHERE_R_ABORT_UNLESS(reader.MakeCreateProcessParameter(std::addressof(params), true)); + + /* Get the binary size for the kip. */ + const size_t binary_size = reader.GetBinarySize(); + const size_t binary_pages = binary_size / PageSize; + + /* Get the pool for both the current (compressed) image, and the decompressed process. */ + const auto src_pool = Kernel::GetMemoryManager().GetPool(KMemoryLayout::GetLinearPhysicalAddress(data)); + const auto dst_pool = reader.UsesSecureMemory() ? secure_pool : unsafe_pool; + + /* Determine the process size, and how much memory isn't already reserved. */ + const size_t process_size = params.code_num_pages * PageSize; + const size_t unreserved_size = process_size - (src_pool == dst_pool ? util::AlignDown(binary_size, PageSize) : 0); + + /* Reserve however much memory we need to reserve. */ + MESOSPHERE_ABORT_UNLESS(Kernel::GetSystemResourceLimit().Reserve(ams::svc::LimitableResource_PhysicalMemoryMax, unreserved_size)); + + /* Create the process. */ + KProcess *new_process = nullptr; + { + /* Make page groups to represent the data. */ + KPageGroup pg(Kernel::GetSystemSystemResource().GetBlockInfoManagerPointer()); + KPageGroup workaround_pg(Kernel::GetSystemSystemResource().GetBlockInfoManagerPointer()); + + /* Populate the page group to represent the data. */ + { + /* Allocate the previously unreserved pages. */ + KPageGroup unreserve_pg(Kernel::GetSystemSystemResource().GetBlockInfoManagerPointer()); + MESOSPHERE_R_ABORT_UNLESS(Kernel::GetMemoryManager().AllocateAndOpen(std::addressof(unreserve_pg), unreserved_size / PageSize, 1, KMemoryManager::EncodeOption(dst_pool, KMemoryManager::Direction_FromFront))); + + /* Add the previously reserved pages. */ + if (src_pool == dst_pool && binary_pages != 0) { + /* NOTE: Nintendo does not check the result of this operation. */ + pg.AddBlock(KMemoryLayout::GetLinearPhysicalAddress(data), binary_pages); + } + + /* Add the previously unreserved pages. */ + for (const auto &block : unreserve_pg) { + /* NOTE: Nintendo does not check the result of this operation. */ + pg.AddBlock(block.GetAddress(), block.GetNumPages()); + } + } + MESOSPHERE_ABORT_UNLESS(pg.GetNumPages() == static_cast<size_t>(params.code_num_pages)); + + /* Ensure that we do not leak pages. */ + KPageGroup *process_pg = std::addressof(pg); + ON_SCOPE_EXIT { process_pg->Close(); }; + + /* Load the process. */ + reader.Load(pg, data); + + /* If necessary, close/release the aligned part of the data we just loaded. */ + if (const size_t aligned_bin_size = util::AlignDown(binary_size, PageSize); aligned_bin_size != 0 && src_pool != dst_pool) { + Kernel::GetMemoryManager().Close(KMemoryLayout::GetLinearPhysicalAddress(data), aligned_bin_size / PageSize); + Kernel::GetSystemResourceLimit().Release(ams::svc::LimitableResource_PhysicalMemoryMax, aligned_bin_size); + } + + /* Create a KProcess object. */ + new_process = KProcess::Create(); + MESOSPHERE_ABORT_UNLESS(new_process != nullptr); + + /* Ensure the page group is usable for the process. */ + /* If the pool is the same, we need to use the workaround page group. */ + if (src_pool == dst_pool) { + /* Allocate a new, usable group for the process. */ + MESOSPHERE_R_ABORT_UNLESS(Kernel::GetMemoryManager().AllocateAndOpen(std::addressof(workaround_pg), static_cast<size_t>(params.code_num_pages), 1, KMemoryManager::EncodeOption(dst_pool, KMemoryManager::Direction_FromFront))); + + /* Copy data from the working page group to the usable one. */ + auto work_it = pg.begin(); + MESOSPHERE_ABORT_UNLESS(work_it != pg.end()); + { + auto work_address = work_it->GetAddress(); + auto work_remaining = work_it->GetNumPages(); + for (const auto &block : workaround_pg) { + auto block_address = block.GetAddress(); + auto block_remaining = block.GetNumPages(); + while (block_remaining > 0) { + if (work_remaining == 0) { + ++work_it; + work_address = work_it->GetAddress(); + work_remaining = work_it->GetNumPages(); + } + + const size_t cur_pages = std::min(block_remaining, work_remaining); + const size_t cur_size = cur_pages * PageSize; + std::memcpy(GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(block_address)), GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(work_address)), cur_size); + + block_address += cur_size; + work_address += cur_size; + + block_remaining -= cur_pages; + work_remaining -= cur_pages; + } + } + + ++work_it; + } + MESOSPHERE_ABORT_UNLESS(work_it == pg.end()); + + /* We want to use the new page group. */ + process_pg = std::addressof(workaround_pg); + pg.Close(); + } + + /* Initialize the process. */ + MESOSPHERE_R_ABORT_UNLESS(new_process->Initialize(params, *process_pg, reader.GetCapabilities(), reader.GetNumCapabilities(), std::addressof(Kernel::GetSystemResourceLimit()), dst_pool, reader.IsImmortal())); + } + + /* Set the process's memory permissions. */ + MESOSPHERE_R_ABORT_UNLESS(reader.SetMemoryPermissions(new_process->GetPageTable(), params)); + + /* Register the process. */ + KProcess::Register(new_process); + + /* Set the ideal core id. */ + new_process->SetIdealCoreId(reader.GetIdealCoreId()); + + /* Save the process info. */ + infos[i].process = new_process; + infos[i].stack_size = reader.GetStackSize(); + infos[i].priority = reader.GetPriority(); + + /* Advance the reader. */ + current = data + binary_size; + } + + /* Release remaining memory used by the image. */ + { + const size_t remaining_size = util::AlignUp(GetInteger(g_initial_process_binary_address) + g_initial_process_binary_header.size, PageSize) - util::AlignDown(GetInteger(current), PageSize); + const size_t remaining_pages = remaining_size / PageSize; + Kernel::GetMemoryManager().Close(KMemoryLayout::GetLinearPhysicalAddress(util::AlignDown(GetInteger(current), PageSize)), remaining_pages); + Kernel::GetSystemResourceLimit().Release(ams::svc::LimitableResource_PhysicalMemoryMax, remaining_size); + } + } + + } + + void SetInitialProcessBinaryPhysicalAddress(KPhysicalAddress phys_addr, size_t size) { + MESOSPHERE_INIT_ABORT_UNLESS(g_initial_process_binary_phys_addr == Null<KPhysicalAddress>); + + g_initial_process_binary_phys_addr = phys_addr; + g_initial_process_binary_size = size; + } + + KPhysicalAddress GetInitialProcessBinaryPhysicalAddress() { + MESOSPHERE_INIT_ABORT_UNLESS(g_initial_process_binary_phys_addr != Null<KPhysicalAddress>); + + return g_initial_process_binary_phys_addr; + } + + size_t GetInitialProcessBinarySize() { + MESOSPHERE_INIT_ABORT_UNLESS(g_initial_process_binary_phys_addr != Null<KPhysicalAddress>); + + return g_initial_process_binary_size; + } + + u64 GetInitialProcessIdMin() { + return g_initial_process_id_min; + } + + u64 GetInitialProcessIdMax() { + return g_initial_process_id_max; + } + + size_t GetInitialProcessesSecureMemorySize() { + LoadInitialProcessBinaryHeader(); + + return g_initial_process_secure_memory_size; + } + + size_t CopyInitialProcessBinaryToKernelMemory() { + LoadInitialProcessBinaryHeader(); + + if (g_initial_process_binary_header.num_processes > 0) { + /* Ensure that we have a non-zero size. */ + const size_t expected_size = g_initial_process_binary_size; + MESOSPHERE_INIT_ABORT_UNLESS(expected_size != 0); + + /* Ensure that the size we need to reserve is as we expect it to be. */ + const size_t total_size = util::AlignUp(g_initial_process_binary_header.size, PageSize); + MESOSPHERE_ABORT_UNLESS(total_size == expected_size); + MESOSPHERE_ABORT_UNLESS(total_size <= InitialProcessBinarySizeMax); + + /* Reserve pages for the initial process binary from the system resource limit. */ + MESOSPHERE_ABORT_UNLESS(Kernel::GetSystemResourceLimit().Reserve(ams::svc::LimitableResource_PhysicalMemoryMax, total_size)); + + return total_size; + } else { + return 0; + } + } + + void CreateAndRunInitialProcesses() { + /* Allocate space for the processes. */ + InitialProcessInfo *infos = static_cast<InitialProcessInfo *>(__builtin_alloca(sizeof(InitialProcessInfo) * g_initial_process_binary_header.num_processes)); + + /* Create the processes. */ + CreateProcesses(infos); + + /* Determine the initial process id range. */ + for (size_t i = 0; i < g_initial_process_binary_header.num_processes; i++) { + const auto pid = infos[i].process->GetId(); + g_initial_process_id_min = std::min(g_initial_process_id_min, pid); + g_initial_process_id_max = std::max(g_initial_process_id_max, pid); + } + + /* Run the processes. */ + for (size_t i = 0; i < g_initial_process_binary_header.num_processes; i++) { + MESOSPHERE_R_ABORT_UNLESS(infos[i].process->Run(infos[i].priority, infos[i].stack_size)); + infos[i].process->Close(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_address_arbiter.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_address_arbiter.cpp new file mode 100644 index 00000000..66c0c164 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_address_arbiter.cpp @@ -0,0 +1,333 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + ALWAYS_INLINE bool ReadFromUser(s32 *out, KProcessAddress address) { + return UserspaceAccess::CopyMemoryFromUserSize32Bit(out, GetVoidPointer(address)); + } + + ALWAYS_INLINE bool ReadFromUser(s64 *out, KProcessAddress address) { + return UserspaceAccess::CopyMemoryFromUserSize64Bit(out, GetVoidPointer(address)); + } + + ALWAYS_INLINE bool DecrementIfLessThan(s32 *out, KProcessAddress address, s32 value) { + /* NOTE: If scheduler lock is not held here, interrupt disable is required. */ + /* KScopedInterruptDisable di; */ + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + if (!cpu::CanAccessAtomic(address)) { + return false; + } + + return UserspaceAccess::DecrementIfLessThanAtomic(out, GetPointer<s32>(address), value); + } + + ALWAYS_INLINE bool UpdateIfEqual(s32 *out, KProcessAddress address, s32 value, s32 new_value) { + /* NOTE: If scheduler lock is not held here, interrupt disable is required. */ + /* KScopedInterruptDisable di; */ + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + if (!cpu::CanAccessAtomic(address)) { + return false; + } + + return UserspaceAccess::UpdateIfEqualAtomic(out, GetPointer<s32>(address), value, new_value); + } + + class ThreadQueueImplForKAddressArbiter final : public KThreadQueue { + private: + KAddressArbiter::ThreadTree *m_tree; + public: + constexpr ThreadQueueImplForKAddressArbiter(KAddressArbiter::ThreadTree *t) : KThreadQueue(), m_tree(t) { /* ... */ } + + virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override { + /* If the thread is waiting on an address arbiter, remove it from the tree. */ + if (waiting_thread->IsWaitingForAddressArbiter()) { + m_tree->erase(m_tree->iterator_to(*waiting_thread)); + waiting_thread->ClearAddressArbiter(); + } + + /* Invoke the base cancel wait handler. */ + KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + } + }; + + } + + Result KAddressArbiter::Signal(uintptr_t addr, s32 count) { + /* Perform signaling. */ + s32 num_waiters = 0; + { + KScopedSchedulerLock sl; + + auto it = m_tree.nfind_key({ addr, -1 }); + while ((it != m_tree.end()) && (count <= 0 || num_waiters < count) && (it->GetAddressArbiterKey() == addr)) { + /* End the thread's wait. */ + KThread *target_thread = std::addressof(*it); + target_thread->EndWait(ResultSuccess()); + + MESOSPHERE_ASSERT(target_thread->IsWaitingForAddressArbiter()); + target_thread->ClearAddressArbiter(); + + it = m_tree.erase(it); + ++num_waiters; + } + } + R_SUCCEED(); + } + + Result KAddressArbiter::SignalAndIncrementIfEqual(uintptr_t addr, s32 value, s32 count) { + /* Perform signaling. */ + s32 num_waiters = 0; + { + KScopedSchedulerLock sl; + + /* Check the userspace value. */ + s32 user_value; + R_UNLESS(UpdateIfEqual(std::addressof(user_value), addr, value, value + 1), svc::ResultInvalidCurrentMemory()); + R_UNLESS(user_value == value, svc::ResultInvalidState()); + + auto it = m_tree.nfind_key({ addr, -1 }); + while ((it != m_tree.end()) && (count <= 0 || num_waiters < count) && (it->GetAddressArbiterKey() == addr)) { + /* End the thread's wait. */ + KThread *target_thread = std::addressof(*it); + target_thread->EndWait(ResultSuccess()); + + MESOSPHERE_ASSERT(target_thread->IsWaitingForAddressArbiter()); + target_thread->ClearAddressArbiter(); + + it = m_tree.erase(it); + ++num_waiters; + } + } + R_SUCCEED(); + } + + Result KAddressArbiter::SignalAndModifyByWaitingCountIfEqual(uintptr_t addr, s32 value, s32 count) { + /* Perform signaling. */ + s32 num_waiters = 0; + { + KScopedSchedulerLock sl; + + auto it = m_tree.nfind_key({ addr, -1 }); + /* Determine the updated value. */ + s32 new_value; + if (count <= 0) { + if ((it != m_tree.end()) && (it->GetAddressArbiterKey() == addr)) { + new_value = value - 1; + } else { + new_value = value + 1; + } + } else { + if ((it != m_tree.end()) && (it->GetAddressArbiterKey() == addr)) { + auto tmp_it = it; + s32 tmp_num_waiters = 0; + while ((++tmp_it != m_tree.end()) && (tmp_it->GetAddressArbiterKey() == addr)) { + if ((++tmp_num_waiters) >= count) { + break; + } + } + + if (tmp_num_waiters < count) { + new_value = value - 1; + } else { + new_value = value; + } + } else { + new_value = value + 1; + } + } + + /* Check the userspace value. */ + s32 user_value; + bool succeeded; + if (value != new_value) { + succeeded = UpdateIfEqual(std::addressof(user_value), addr, value, new_value); + } else { + succeeded = ReadFromUser(std::addressof(user_value), addr); + } + + R_UNLESS(succeeded, svc::ResultInvalidCurrentMemory()); + R_UNLESS(user_value == value, svc::ResultInvalidState()); + + while ((it != m_tree.end()) && (count <= 0 || num_waiters < count) && (it->GetAddressArbiterKey() == addr)) { + /* End the thread's wait. */ + KThread *target_thread = std::addressof(*it); + target_thread->EndWait(ResultSuccess()); + + MESOSPHERE_ASSERT(target_thread->IsWaitingForAddressArbiter()); + target_thread->ClearAddressArbiter(); + + it = m_tree.erase(it); + ++num_waiters; + } + } + R_SUCCEED(); + } + + Result KAddressArbiter::WaitIfLessThan(uintptr_t addr, s32 value, bool decrement, s64 timeout) { + /* Prepare to wait. */ + KThread *cur_thread = GetCurrentThreadPointer(); + KHardwareTimer *timer; + ThreadQueueImplForKAddressArbiter wait_queue(std::addressof(m_tree)); + + { + KScopedSchedulerLockAndSleep slp(std::addressof(timer), cur_thread, timeout); + + /* Check that the thread isn't terminating. */ + if (cur_thread->IsTerminationRequested()) { + slp.CancelSleep(); + R_THROW(svc::ResultTerminationRequested()); + } + + /* Read the value from userspace. */ + s32 user_value; + bool succeeded; + if (decrement) { + succeeded = DecrementIfLessThan(std::addressof(user_value), addr, value); + } else { + succeeded = ReadFromUser(std::addressof(user_value), addr); + } + + if (!succeeded) { + slp.CancelSleep(); + R_THROW(svc::ResultInvalidCurrentMemory()); + } + + /* Check that the value is less than the specified one. */ + if (user_value >= value) { + slp.CancelSleep(); + R_THROW(svc::ResultInvalidState()); + } + + /* Check that the timeout is non-zero. */ + if (timeout == 0) { + slp.CancelSleep(); + R_THROW(svc::ResultTimedOut()); + } + + /* Set the arbiter. */ + cur_thread->SetAddressArbiter(std::addressof(m_tree), addr); + m_tree.insert(*cur_thread); + + /* Wait for the thread to finish. */ + wait_queue.SetHardwareTimer(timer); + cur_thread->BeginWait(std::addressof(wait_queue)); + } + + /* Get the wait result. */ + R_RETURN(cur_thread->GetWaitResult()); + } + + Result KAddressArbiter::WaitIfEqual(uintptr_t addr, s32 value, s64 timeout) { + /* Prepare to wait. */ + KThread *cur_thread = GetCurrentThreadPointer(); + KHardwareTimer *timer; + ThreadQueueImplForKAddressArbiter wait_queue(std::addressof(m_tree)); + + { + KScopedSchedulerLockAndSleep slp(std::addressof(timer), cur_thread, timeout); + + /* Check that the thread isn't terminating. */ + if (cur_thread->IsTerminationRequested()) { + slp.CancelSleep(); + R_THROW(svc::ResultTerminationRequested()); + } + + /* Read the value from userspace. */ + s32 user_value; + if (!ReadFromUser(std::addressof(user_value), addr)) { + slp.CancelSleep(); + R_THROW(svc::ResultInvalidCurrentMemory()); + } + + /* Check that the value is equal. */ + if (value != user_value) { + slp.CancelSleep(); + R_THROW(svc::ResultInvalidState()); + } + + /* Check that the timeout is non-zero. */ + if (timeout == 0) { + slp.CancelSleep(); + R_THROW(svc::ResultTimedOut()); + } + + /* Set the arbiter. */ + cur_thread->SetAddressArbiter(std::addressof(m_tree), addr); + m_tree.insert(*cur_thread); + + /* Wait for the thread to finish. */ + wait_queue.SetHardwareTimer(timer); + cur_thread->BeginWait(std::addressof(wait_queue)); + } + + /* Get the wait result. */ + R_RETURN(cur_thread->GetWaitResult()); + } + + Result KAddressArbiter::WaitIfEqual64(uintptr_t addr, s64 value, s64 timeout) { + /* Prepare to wait. */ + KThread *cur_thread = GetCurrentThreadPointer(); + KHardwareTimer *timer; + ThreadQueueImplForKAddressArbiter wait_queue(std::addressof(m_tree)); + + { + KScopedSchedulerLockAndSleep slp(std::addressof(timer), cur_thread, timeout); + + /* Check that the thread isn't terminating. */ + if (cur_thread->IsTerminationRequested()) { + slp.CancelSleep(); + R_THROW(svc::ResultTerminationRequested()); + } + + /* Read the value from userspace. */ + s64 user_value; + if (!ReadFromUser(std::addressof(user_value), addr)) { + slp.CancelSleep(); + R_THROW(svc::ResultInvalidCurrentMemory()); + } + + /* Check that the value is equal. */ + if (value != user_value) { + slp.CancelSleep(); + R_THROW(svc::ResultInvalidState()); + } + + /* Check that the timeout is non-zero. */ + if (timeout == 0) { + slp.CancelSleep(); + R_THROW(svc::ResultTimedOut()); + } + + /* Set the arbiter. */ + cur_thread->SetAddressArbiter(std::addressof(m_tree), addr); + m_tree.insert(*cur_thread); + + /* Wait for the thread to finish. */ + wait_queue.SetHardwareTimer(timer); + cur_thread->BeginWait(std::addressof(wait_queue)); + } + + /* Get the wait result. */ + R_RETURN(cur_thread->GetWaitResult()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_address_space_info.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_address_space_info.cpp new file mode 100644 index 00000000..3aa24c5d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_address_space_info.cpp @@ -0,0 +1,108 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + constexpr uintptr_t Invalid = std::numeric_limits<uintptr_t>::max(); + + constinit KAddressSpaceInfo AddressSpaceInfos[] = { + { 32, ams::svc::AddressSmallMap32Start, ams::svc::AddressSmallMap32Size, KAddressSpaceInfo::Type_MapSmall, }, + { 32, ams::svc::AddressLargeMap32Start, ams::svc::AddressLargeMap32Size, KAddressSpaceInfo::Type_MapLarge, }, + { 32, Invalid, ams::svc::AddressMemoryRegionHeap32Size, KAddressSpaceInfo::Type_Heap, }, + { 32, Invalid, ams::svc::AddressMemoryRegionAlias32Size, KAddressSpaceInfo::Type_Alias, }, + { 36, ams::svc::AddressSmallMap36Start, ams::svc::AddressSmallMap36Size, KAddressSpaceInfo::Type_MapSmall, }, + { 36, ams::svc::AddressLargeMap36Start, ams::svc::AddressLargeMap36Size, KAddressSpaceInfo::Type_MapLarge, }, + { 36, Invalid, ams::svc::AddressMemoryRegionHeap36Size, KAddressSpaceInfo::Type_Heap, }, + { 36, Invalid, ams::svc::AddressMemoryRegionAlias36Size, KAddressSpaceInfo::Type_Alias, }, + { 39, ams::svc::AddressMap39Start, ams::svc::AddressMap39Size, KAddressSpaceInfo::Type_Map39Bit, }, + { 39, Invalid, ams::svc::AddressMemoryRegionSmall39Size, KAddressSpaceInfo::Type_MapSmall, }, + { 39, Invalid, ams::svc::AddressMemoryRegionHeap39Size, KAddressSpaceInfo::Type_Heap, }, + { 39, Invalid, ams::svc::AddressMemoryRegionAlias39Size, KAddressSpaceInfo::Type_Alias, }, + { 39, Invalid, ams::svc::AddressMemoryRegionStack39Size, KAddressSpaceInfo::Type_Stack, }, + }; + + constexpr u8 FlagsToAddressSpaceWidthTable[4] = { + 32, 36, 32, 39 + }; + + constexpr size_t GetAddressSpaceWidth(ams::svc::CreateProcessFlag flags) { + /* Convert the input flags to an array index. */ + const size_t idx = (flags & ams::svc::CreateProcessFlag_AddressSpaceMask) >> ams::svc::CreateProcessFlag_AddressSpaceShift; + MESOSPHERE_ABORT_UNLESS(idx < sizeof(FlagsToAddressSpaceWidthTable)); + + /* Return the width. */ + return FlagsToAddressSpaceWidthTable[idx]; + } + + static_assert(GetAddressSpaceWidth(ams::svc::CreateProcessFlag_AddressSpace32Bit) == 32); + static_assert(GetAddressSpaceWidth(ams::svc::CreateProcessFlag_AddressSpace64BitDeprecated) == 36); + static_assert(GetAddressSpaceWidth(ams::svc::CreateProcessFlag_AddressSpace32BitWithoutAlias) == 32); + static_assert(GetAddressSpaceWidth(ams::svc::CreateProcessFlag_AddressSpace64Bit) == 39); + + KAddressSpaceInfo &GetAddressSpaceInfo(size_t width, KAddressSpaceInfo::Type type) { + for (auto &info : AddressSpaceInfos) { + if (info.GetWidth() == width && info.GetType() == type) { + return info; + } + } + MESOSPHERE_PANIC("Could not find AddressSpaceInfo"); + } + + } + + uintptr_t KAddressSpaceInfo::GetAddressSpaceStart(ams::svc::CreateProcessFlag flags, KAddressSpaceInfo::Type type, size_t code_size) { + MESOSPHERE_UNUSED(code_size); + return GetAddressSpaceInfo(GetAddressSpaceWidth(flags), type).GetAddress(); + } + + size_t KAddressSpaceInfo::GetAddressSpaceSize(ams::svc::CreateProcessFlag flags, KAddressSpaceInfo::Type type) { + /* Extract the address space from the create process flags. */ + const auto as_flags = (flags & ams::svc::CreateProcessFlag_AddressSpaceMask); + + /* Get the address space width. */ + const auto as_width = GetAddressSpaceWidth(flags); + + /* Get the size. */ + size_t as_size = GetAddressSpaceInfo(as_width, type).GetSize(); + + /* If we're getting size for 32-bit without alias, adjust the sizes accordingly. */ + if (as_flags == ams::svc::CreateProcessFlag_AddressSpace32BitWithoutAlias) { + switch (type) { + /* The heap space receives space that would otherwise go to the alias space. */ + case KAddressSpaceInfo::Type_Heap: + as_size += GetAddressSpaceInfo(as_width, KAddressSpaceInfo::Type_Alias).GetSize(); + break; + /* The alias space doesn't exist. */ + case KAddressSpaceInfo::Type_Alias: + as_size = 0; + break; + /* Nothing to do by default. */ + default: + break; + } + } + + return as_size; + } + + void KAddressSpaceInfo::SetAddressSpaceSize(size_t width, Type type, size_t size) { + GetAddressSpaceInfo(width, type).SetSize(size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_capabilities.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_capabilities.cpp new file mode 100644 index 00000000..ba035926 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_capabilities.cpp @@ -0,0 +1,376 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + Result KCapabilities::Initialize(const u32 *caps, s32 num_caps, KProcessPageTable *page_table) { + /* We're initializing an initial process. */ + m_svc_access_flags.Reset(); + m_irq_access_flags.Reset(); + m_debug_capabilities = {0}; + m_handle_table_size = 0; + m_intended_kernel_version = {0}; + m_program_type = 0; + + /* Initial processes may run on all cores. */ + constexpr u64 VirtMask = cpu::VirtualCoreMask; + constexpr u64 PhysMask = cpu::ConvertVirtualCoreMaskToPhysical(VirtMask); + + m_core_mask = VirtMask; + m_phys_core_mask = PhysMask; + + /* Initial processes may use any user priority they like. */ + m_priority_mask = ~0xFul; + + /* Here, Nintendo sets the kernel version to the current kernel version. */ + /* We will follow suit and set the version to the highest supported kernel version. */ + m_intended_kernel_version.Set<KernelVersion::MajorVersion>(ams::svc::SupportedKernelMajorVersion); + m_intended_kernel_version.Set<KernelVersion::MinorVersion>(ams::svc::SupportedKernelMinorVersion); + + /* Parse the capabilities array. */ + R_RETURN(this->SetCapabilities(caps, num_caps, page_table)); + } + + Result KCapabilities::Initialize(svc::KUserPointer<const u32 *> user_caps, s32 num_caps, KProcessPageTable *page_table) { + /* We're initializing a user process. */ + m_svc_access_flags.Reset(); + m_irq_access_flags.Reset(); + m_debug_capabilities = {0}; + m_handle_table_size = 0; + m_intended_kernel_version = {0}; + m_program_type = 0; + + /* User processes must specify what cores/priorities they can use. */ + m_core_mask = 0; + m_priority_mask = 0; + + /* Parse the user capabilities array. */ + R_RETURN(this->SetCapabilities(user_caps, num_caps, page_table)); + } + + Result KCapabilities::SetCorePriorityCapability(const util::BitPack32 cap) { + /* We can't set core/priority if we've already set them. */ + R_UNLESS(m_core_mask == 0, svc::ResultInvalidArgument()); + R_UNLESS(m_priority_mask == 0, svc::ResultInvalidArgument()); + + /* Validate the core/priority. */ + const auto min_core = cap.Get<CorePriority::MinimumCoreId>(); + const auto max_core = cap.Get<CorePriority::MaximumCoreId>(); + const auto max_prio = cap.Get<CorePriority::LowestThreadPriority>(); + const auto min_prio = cap.Get<CorePriority::HighestThreadPriority>(); + + R_UNLESS(min_core <= max_core, svc::ResultInvalidCombination()); + R_UNLESS(min_prio <= max_prio, svc::ResultInvalidCombination()); + R_UNLESS(max_core < cpu::NumVirtualCores, svc::ResultInvalidCoreId()); + + MESOSPHERE_ASSERT(max_prio < BITSIZEOF(u64)); + + /* Set core mask. */ + for (auto core_id = min_core; core_id <= max_core; core_id++) { + m_core_mask |= (1ul << core_id); + } + MESOSPHERE_ASSERT((m_core_mask & cpu::VirtualCoreMask) == m_core_mask); + + /* Set physical core mask. */ + m_phys_core_mask = cpu::ConvertVirtualCoreMaskToPhysical(m_core_mask); + + /* Set priority mask. */ + for (auto prio = min_prio; prio <= max_prio; prio++) { + m_priority_mask |= (1ul << prio); + } + + /* We must have some core/priority we can use. */ + R_UNLESS(m_core_mask != 0, svc::ResultInvalidArgument()); + R_UNLESS(m_priority_mask != 0, svc::ResultInvalidArgument()); + + /* Processes must not have access to kernel thread priorities. */ + R_UNLESS((m_priority_mask & 0xF) == 0, svc::ResultInvalidArgument()); + + R_SUCCEED(); + } + + Result KCapabilities::SetSyscallMaskCapability(const util::BitPack32 cap, u32 &set_svc) { + /* Validate the index. */ + const auto mask = cap.Get<SyscallMask::Mask>(); + const auto index = cap.Get<SyscallMask::Index>(); + + const u32 index_flag = (1u << index); + R_UNLESS((set_svc & index_flag) == 0, svc::ResultInvalidCombination()); + set_svc |= index_flag; + + /* Set SVCs. */ + for (size_t i = 0; i < SyscallMask::Mask::Count; i++) { + const u32 svc_id = SyscallMask::Mask::Count * index + i; + if (mask & (1u << i)) { + R_UNLESS(this->SetSvcAllowed(svc_id), svc::ResultOutOfRange()); + } + } + + R_SUCCEED(); + } + + Result KCapabilities::MapRange(const util::BitPack32 cap, const util::BitPack32 size_cap, KProcessPageTable *page_table) { + /* Get/validate address/size */ + #if defined(MESOSPHERE_ENABLE_LARGE_PHYSICAL_ADDRESS_CAPABILITIES) + const u64 phys_addr = static_cast<u64>(cap.Get<MapRange::Address>() | (size_cap.Get<MapRangeSize::AddressHigh>() << MapRange::Address::Count)) * PageSize; + #else + const u64 phys_addr = static_cast<u64>(cap.Get<MapRange::Address>()) * PageSize; + + /* Validate reserved bits are unused. */ + R_UNLESS(size_cap.Get<MapRangeSize::Reserved>() == 0, svc::ResultOutOfRange()); + #endif + const size_t num_pages = size_cap.Get<MapRangeSize::Pages>(); + const size_t size = num_pages * PageSize; + R_UNLESS(phys_addr == GetInteger(KPhysicalAddress(phys_addr)), svc::ResultInvalidAddress()); + R_UNLESS(num_pages != 0, svc::ResultInvalidSize()); + R_UNLESS(phys_addr < phys_addr + size, svc::ResultInvalidAddress()); + R_UNLESS(((phys_addr + size - 1) & ~PhysicalMapAllowedMask) == 0, svc::ResultInvalidAddress()); + + /* Do the mapping. */ + const KMemoryPermission perm = cap.Get<MapRange::ReadOnly>() ? KMemoryPermission_UserRead : KMemoryPermission_UserReadWrite; + if (size_cap.Get<MapRangeSize::Normal>()) { + R_RETURN(page_table->MapStatic(phys_addr, size, perm)); + } else { + R_RETURN(page_table->MapIo(phys_addr, size, perm)); + } + } + + Result KCapabilities::MapIoPage(const util::BitPack32 cap, KProcessPageTable *page_table) { + /* Get/validate address/size */ + const u64 phys_addr = cap.Get<MapIoPage::Address>() * PageSize; + const size_t num_pages = 1; + const size_t size = num_pages * PageSize; + R_UNLESS(phys_addr == GetInteger(KPhysicalAddress(phys_addr)), svc::ResultInvalidAddress()); + R_UNLESS(num_pages != 0, svc::ResultInvalidSize()); + R_UNLESS(phys_addr < phys_addr + size, svc::ResultInvalidAddress()); + R_UNLESS(((phys_addr + size - 1) & ~PhysicalMapAllowedMask) == 0, svc::ResultInvalidAddress()); + + /* Do the mapping. */ + R_RETURN(page_table->MapIo(phys_addr, size, KMemoryPermission_UserReadWrite)); + } + + template<typename F> + ALWAYS_INLINE Result KCapabilities::ProcessMapRegionCapability(const util::BitPack32 cap, F f) { + /* Define the allowed memory regions. */ + constexpr const KMemoryRegionType MemoryRegions[] = { + KMemoryRegionType_None, + KMemoryRegionType_KernelTraceBuffer, + KMemoryRegionType_OnMemoryBootImage, + KMemoryRegionType_DTB, + }; + + /* Extract regions/read only. */ + const RegionType types[3] = { cap.Get<MapRegion::Region0>(), cap.Get<MapRegion::Region1>(), cap.Get<MapRegion::Region2>(), }; + const bool ro[3] = { cap.Get<MapRegion::ReadOnly0>(), cap.Get<MapRegion::ReadOnly1>(), cap.Get<MapRegion::ReadOnly2>(), }; + + for (size_t i = 0; i < util::size(types); i++) { + const auto type = types[i]; + const auto perm = ro[i] ? KMemoryPermission_UserRead : KMemoryPermission_UserReadWrite; + switch (type) { + case RegionType::NoMapping: + break; + case RegionType::KernelTraceBuffer: + /* NOTE: This does not match official, but is used to make pre-processing hbl capabilities in userland unnecessary. */ + /* If ktrace isn't enabled, allow ktrace to succeed without mapping anything. */ + if constexpr (!ams::kern::IsKTraceEnabled) { + break; + } + case RegionType::OnMemoryBootImage: + case RegionType::DTB: + R_TRY(f(MemoryRegions[static_cast<u32>(type)], perm)); + break; + default: + R_THROW(svc::ResultNotFound()); + } + } + + R_SUCCEED(); + } + + Result KCapabilities::MapRegion(const util::BitPack32 cap, KProcessPageTable *page_table) { + /* Map each region into the process's page table. */ + R_RETURN(ProcessMapRegionCapability(cap, [page_table] ALWAYS_INLINE_LAMBDA (KMemoryRegionType region_type, KMemoryPermission perm) -> Result { + R_RETURN(page_table->MapRegion(region_type, perm)); + })); + } + + Result KCapabilities::CheckMapRegion(const util::BitPack32 cap) { + /* Check that each region has a physical backing store. */ + R_RETURN(ProcessMapRegionCapability(cap, [] ALWAYS_INLINE_LAMBDA (KMemoryRegionType region_type, KMemoryPermission perm) -> Result { + MESOSPHERE_UNUSED(perm); + R_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().FindFirstDerived(region_type) != nullptr, svc::ResultOutOfRange()); + R_SUCCEED(); + })); + } + + Result KCapabilities::SetInterruptPairCapability(const util::BitPack32 cap) { + /* Extract interrupts. */ + const u32 ids[2] = { cap.Get<InterruptPair::InterruptId0>(), cap.Get<InterruptPair::InterruptId1>(), }; + + for (size_t i = 0; i < util::size(ids); i++) { + if (ids[i] != PaddingInterruptId) { + R_UNLESS(Kernel::GetInterruptManager().IsInterruptDefined(ids[i]), svc::ResultOutOfRange()); + R_UNLESS(this->SetInterruptPermitted(ids[i]), svc::ResultOutOfRange()); + } + } + + R_SUCCEED(); + } + + Result KCapabilities::SetProgramTypeCapability(const util::BitPack32 cap) { + /* Validate. */ + R_UNLESS(cap.Get<ProgramType::Reserved>() == 0, svc::ResultReservedUsed()); + + m_program_type = cap.Get<ProgramType::Type>(); + R_SUCCEED(); + } + + Result KCapabilities::SetKernelVersionCapability(const util::BitPack32 cap) { + /* Ensure we haven't set our version before. */ + R_UNLESS(m_intended_kernel_version.Get<KernelVersion::MajorVersion>() == 0, svc::ResultInvalidArgument()); + + /* Set, ensure that we set a valid version. */ + m_intended_kernel_version = cap; + R_UNLESS(m_intended_kernel_version.Get<KernelVersion::MajorVersion>() != 0, svc::ResultInvalidArgument()); + + R_SUCCEED(); + } + + Result KCapabilities::SetHandleTableCapability(const util::BitPack32 cap) { + /* Validate. */ + R_UNLESS(cap.Get<HandleTable::Reserved>() == 0, svc::ResultReservedUsed()); + + m_handle_table_size = cap.Get<HandleTable::Size>(); + R_SUCCEED(); + } + + Result KCapabilities::SetDebugFlagsCapability(const util::BitPack32 cap) { + /* Validate. */ + R_UNLESS(cap.Get<DebugFlags::Reserved>() == 0, svc::ResultReservedUsed()); + + u32 total = 0; + if (cap.Get<DebugFlags::AllowDebug>()) { ++total; } + if (cap.Get<DebugFlags::ForceDebugProd>()) { ++total; } + if (cap.Get<DebugFlags::ForceDebug>()) { ++total; } + R_UNLESS(total <= 1, svc::ResultInvalidCombination()); + + m_debug_capabilities.Set<DebugFlags::AllowDebug>(cap.Get<DebugFlags::AllowDebug>()); + m_debug_capabilities.Set<DebugFlags::ForceDebugProd>(cap.Get<DebugFlags::ForceDebugProd>()); + m_debug_capabilities.Set<DebugFlags::ForceDebug>(cap.Get<DebugFlags::ForceDebug>()); + R_SUCCEED(); + } + + Result KCapabilities::SetCapability(const util::BitPack32 cap, u32 &set_flags, u32 &set_svc, KProcessPageTable *page_table) { + /* Validate this is a capability we can act on. */ + const auto type = GetCapabilityType(cap); + R_UNLESS(type != CapabilityType::Invalid, svc::ResultInvalidArgument()); + + /* If the type is padding, we have no work to do. */ + R_SUCCEED_IF(type == CapabilityType::Padding); + + /* Check that we haven't already processed this capability. */ + const auto flag = GetCapabilityFlag(type); + R_UNLESS(((set_flags & InitializeOnceFlags) & flag) == 0, svc::ResultInvalidCombination()); + set_flags |= flag; + + /* Process the capability. */ + switch (type) { + case CapabilityType::CorePriority: R_RETURN(this->SetCorePriorityCapability(cap)); + case CapabilityType::SyscallMask: R_RETURN(this->SetSyscallMaskCapability(cap, set_svc)); + case CapabilityType::MapIoPage: R_RETURN(this->MapIoPage(cap, page_table)); + case CapabilityType::MapRegion: R_RETURN(this->MapRegion(cap, page_table)); + case CapabilityType::InterruptPair: R_RETURN(this->SetInterruptPairCapability(cap)); + case CapabilityType::ProgramType: R_RETURN(this->SetProgramTypeCapability(cap)); + case CapabilityType::KernelVersion: R_RETURN(this->SetKernelVersionCapability(cap)); + case CapabilityType::HandleTable: R_RETURN(this->SetHandleTableCapability(cap)); + case CapabilityType::DebugFlags: R_RETURN(this->SetDebugFlagsCapability(cap)); + default: R_THROW(svc::ResultInvalidArgument()); + } + } + + Result KCapabilities::SetCapabilities(const u32 *caps, s32 num_caps, KProcessPageTable *page_table) { + u32 set_flags = 0, set_svc = 0; + + for (s32 i = 0; i < num_caps; i++) { + const util::BitPack32 cap = { caps[i] }; + if (GetCapabilityType(cap) == CapabilityType::MapRange) { + /* Check that the pair cap exists. */ + R_UNLESS((++i) < num_caps, svc::ResultInvalidCombination()); + + /* Check the pair cap is a map range cap. */ + const util::BitPack32 size_cap = { caps[i] }; + R_UNLESS(GetCapabilityType(size_cap) == CapabilityType::MapRange, svc::ResultInvalidCombination()); + + /* Map the range. */ + R_TRY(this->MapRange(cap, size_cap, page_table)); + } else { + R_TRY(this->SetCapability(cap, set_flags, set_svc, page_table)); + } + } + + R_SUCCEED(); + } + + Result KCapabilities::SetCapabilities(svc::KUserPointer<const u32 *> user_caps, s32 num_caps, KProcessPageTable *page_table) { + u32 set_flags = 0, set_svc = 0; + + for (s32 i = 0; i < num_caps; i++) { + /* Read the cap from userspace. */ + u32 cap0; + R_TRY(user_caps.CopyArrayElementTo(std::addressof(cap0), i)); + + const util::BitPack32 cap = { cap0 }; + if (GetCapabilityType(cap) == CapabilityType::MapRange) { + /* Check that the pair cap exists. */ + R_UNLESS((++i) < num_caps, svc::ResultInvalidCombination()); + + /* Read the second cap from userspace. */ + u32 cap1; + R_TRY(user_caps.CopyArrayElementTo(std::addressof(cap1), i)); + + /* Check the pair cap is a map range cap. */ + const util::BitPack32 size_cap = { cap1 }; + R_UNLESS(GetCapabilityType(size_cap) == CapabilityType::MapRange, svc::ResultInvalidCombination()); + + /* Map the range. */ + R_TRY(this->MapRange(cap, size_cap, page_table)); + } else { + R_TRY(this->SetCapability(cap, set_flags, set_svc, page_table)); + } + } + + R_SUCCEED(); + } + + Result KCapabilities::CheckCapabilities(svc::KUserPointer<const u32 *> user_caps, s32 num_caps) { + for (s32 i = 0; i < num_caps; ++i) { + /* Read the cap from userspace. */ + u32 cap0; + R_TRY(user_caps.CopyArrayElementTo(std::addressof(cap0), i)); + + /* Check the capability refers to a valid region. */ + + const util::BitPack32 cap = { cap0 }; + if (GetCapabilityType(cap) == CapabilityType::MapRegion) { + R_TRY(CheckMapRegion(cap)); + } + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_class_token.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_class_token.cpp new file mode 100644 index 00000000..c7d7ea67 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_class_token.cpp @@ -0,0 +1,119 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + /* Ensure that we generate correct class tokens for all types. */ + + /* Ensure that the absolute token values are correct. */ + static_assert(ClassToken<KAutoObject> == 0b00000000'00000000); + static_assert(ClassToken<KSynchronizationObject> == 0b00000000'00000001); + static_assert(ClassToken<KReadableEvent> == 0b00000000'00000011); + static_assert(ClassToken<KInterruptEvent> == 0b00000111'00000011); + static_assert(ClassToken<KDebug> == 0b00001011'00000001); + static_assert(ClassToken<KThread> == 0b00010011'00000001); + static_assert(ClassToken<KServerPort> == 0b00100011'00000001); + static_assert(ClassToken<KServerSession> == 0b01000011'00000001); + static_assert(ClassToken<KClientPort> == 0b10000011'00000001); + static_assert(ClassToken<KClientSession> == 0b00001101'00000000); + static_assert(ClassToken<KProcess> == 0b00010101'00000001); + static_assert(ClassToken<KResourceLimit> == 0b00100101'00000000); + static_assert(ClassToken<KLightSession> == 0b01000101'00000000); + static_assert(ClassToken<KPort> == 0b10000101'00000000); + static_assert(ClassToken<KSession> == 0b00011001'00000000); + static_assert(ClassToken<KSharedMemory> == 0b00101001'00000000); + static_assert(ClassToken<KEvent> == 0b01001001'00000000); + static_assert(ClassToken<KLightClientSession> == 0b10001001'00000000); + static_assert(ClassToken<KLightServerSession> == 0b00110001'00000000); + static_assert(ClassToken<KTransferMemory> == 0b01010001'00000000); + static_assert(ClassToken<KDeviceAddressSpace> == 0b10010001'00000000); + static_assert(ClassToken<KSessionRequest> == 0b01100001'00000000); + static_assert(ClassToken<KCodeMemory> == 0b10100001'00000000); + static_assert(ClassToken<KIoPool> == 0b11000001'00000000); + static_assert(ClassToken<KIoRegion> == 0b00001110'00000000); + /* 0b00010110'00000000 */ + /* 0b00100110'00000000 */ + static_assert(ClassToken<KSystemResource> == 0b01000110'00000000); + + + /* Ensure that the token hierarchy is correct. */ + + /* Base classes */ + static_assert(ClassToken<KAutoObject> == (0b00000000)); + static_assert(ClassToken<KSynchronizationObject> == (0b00000001 | ClassToken<KAutoObject>)); + static_assert(ClassToken<KReadableEvent> == (0b00000010 | ClassToken<KSynchronizationObject>)); + + /* Final classes */ + static_assert(ClassToken<KInterruptEvent> == ((0b00000111 << 8) | ClassToken<KReadableEvent>)); + static_assert(ClassToken<KDebug> == ((0b00001011 << 8) | ClassToken<KSynchronizationObject>)); + static_assert(ClassToken<KThread> == ((0b00010011 << 8) | ClassToken<KSynchronizationObject>)); + static_assert(ClassToken<KServerPort> == ((0b00100011 << 8) | ClassToken<KSynchronizationObject>)); + static_assert(ClassToken<KServerSession> == ((0b01000011 << 8) | ClassToken<KSynchronizationObject>)); + static_assert(ClassToken<KClientPort> == ((0b10000011 << 8) | ClassToken<KSynchronizationObject>)); + static_assert(ClassToken<KClientSession> == ((0b00001101 << 8) | ClassToken<KAutoObject>)); + static_assert(ClassToken<KProcess> == ((0b00010101 << 8) | ClassToken<KSynchronizationObject>)); + static_assert(ClassToken<KResourceLimit> == ((0b00100101 << 8) | ClassToken<KAutoObject>)); + static_assert(ClassToken<KLightSession> == ((0b01000101 << 8) | ClassToken<KAutoObject>)); + static_assert(ClassToken<KPort> == ((0b10000101 << 8) | ClassToken<KAutoObject>)); + static_assert(ClassToken<KSession> == ((0b00011001 << 8) | ClassToken<KAutoObject>)); + static_assert(ClassToken<KSharedMemory> == ((0b00101001 << 8) | ClassToken<KAutoObject>)); + static_assert(ClassToken<KEvent> == ((0b01001001 << 8) | ClassToken<KAutoObject>)); + static_assert(ClassToken<KLightClientSession> == ((0b10001001 << 8) | ClassToken<KAutoObject>)); + static_assert(ClassToken<KLightServerSession> == ((0b00110001 << 8) | ClassToken<KAutoObject>)); + static_assert(ClassToken<KTransferMemory> == ((0b01010001 << 8) | ClassToken<KAutoObject>)); + static_assert(ClassToken<KDeviceAddressSpace> == ((0b10010001 << 8) | ClassToken<KAutoObject>)); + static_assert(ClassToken<KSessionRequest> == ((0b01100001 << 8) | ClassToken<KAutoObject>)); + static_assert(ClassToken<KCodeMemory> == ((0b10100001 << 8) | ClassToken<KAutoObject>)); + static_assert(ClassToken<KIoPool> == ((0b11000001 << 8) | ClassToken<KAutoObject>)); + static_assert(ClassToken<KIoRegion> == ((0b00001110 << 8) | ClassToken<KAutoObject>)); + + static_assert(ClassToken<KSystemResource> == ((0b01000110 << 8) | ClassToken<KAutoObject>)); + + /* Ensure that the token hierarchy reflects the class hierarchy. */ + + /* Base classes. */ + static_assert(!std::is_final<KSynchronizationObject>::value && std::is_base_of<KAutoObject, KSynchronizationObject>::value); + static_assert(!std::is_final<KReadableEvent>::value && std::is_base_of<KSynchronizationObject, KReadableEvent>::value); + + /* Final classes */ + static_assert(std::is_final<KInterruptEvent>::value && std::is_base_of<KReadableEvent, KInterruptEvent>::value); + static_assert(std::is_final<KDebug>::value && std::is_base_of<KSynchronizationObject, KDebug>::value); + static_assert(std::is_final<KThread>::value && std::is_base_of<KSynchronizationObject, KThread>::value); + static_assert(std::is_final<KServerPort>::value && std::is_base_of<KSynchronizationObject, KServerPort>::value); + static_assert(std::is_final<KServerSession>::value && std::is_base_of<KSynchronizationObject, KServerSession>::value); + static_assert(std::is_final<KClientPort>::value && std::is_base_of<KSynchronizationObject, KClientPort>::value); + static_assert(std::is_final<KClientSession>::value && std::is_base_of<KAutoObject, KClientSession>::value); + static_assert(std::is_final<KProcess>::value && std::is_base_of<KSynchronizationObject, KProcess>::value); + static_assert(std::is_final<KResourceLimit>::value && std::is_base_of<KAutoObject, KResourceLimit>::value); + static_assert(std::is_final<KLightSession>::value && std::is_base_of<KAutoObject, KLightSession>::value); + static_assert(std::is_final<KPort>::value && std::is_base_of<KAutoObject, KPort>::value); + static_assert(std::is_final<KSession>::value && std::is_base_of<KAutoObject, KSession>::value); + static_assert(std::is_final<KSharedMemory>::value && std::is_base_of<KAutoObject, KSharedMemory>::value); + static_assert(std::is_final<KEvent>::value && std::is_base_of<KAutoObject, KEvent>::value); + static_assert(std::is_final<KLightClientSession>::value && std::is_base_of<KAutoObject, KLightClientSession>::value); + static_assert(std::is_final<KLightServerSession>::value && std::is_base_of<KAutoObject, KLightServerSession>::value); + static_assert(std::is_final<KTransferMemory>::value && std::is_base_of<KAutoObject, KTransferMemory>::value); + static_assert(std::is_final<KDeviceAddressSpace>::value && std::is_base_of<KAutoObject, KDeviceAddressSpace>::value); + static_assert(std::is_final<KSessionRequest>::value && std::is_base_of<KAutoObject, KSessionRequest>::value); + static_assert(std::is_final<KCodeMemory>::value && std::is_base_of<KAutoObject, KCodeMemory>::value); + static_assert(std::is_final<KIoPool>::value && std::is_base_of<KAutoObject, KIoPool>::value); + static_assert(std::is_final<KIoRegion>::value && std::is_base_of<KAutoObject, KIoRegion>::value); + + static_assert(std::is_base_of<KAutoObject, KSystemResource>::value); + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_client_port.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_client_port.cpp new file mode 100644 index 00000000..71ca3d7d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_client_port.cpp @@ -0,0 +1,218 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + void KClientPort::Initialize(KPort *parent, s32 max_sessions) { + /* Set member variables. */ + m_num_sessions = 0; + m_peak_sessions = 0; + m_parent = parent; + m_max_sessions = max_sessions; + } + + void KClientPort::OnSessionFinalized() { + KScopedSchedulerLock sl; + + if (const auto prev = m_num_sessions--; prev == m_max_sessions) { + this->NotifyAvailable(); + } + } + + void KClientPort::OnServerClosed() { + MESOSPHERE_ASSERT_THIS(); + } + + bool KClientPort::IsLight() const { + return this->GetParent()->IsLight(); + } + + bool KClientPort::IsServerClosed() const { + return this->GetParent()->IsServerClosed(); + } + + void KClientPort::Destroy() { + /* Note with our parent that we're closed. */ + m_parent->OnClientClosed(); + + /* Close our reference to our parent. */ + m_parent->Close(); + } + + bool KClientPort::IsSignaled() const { + MESOSPHERE_ASSERT_THIS(); + return m_num_sessions.Load() < m_max_sessions; + } + + Result KClientPort::CreateSession(KClientSession **out) { + MESOSPHERE_ASSERT_THIS(); + + /* Declare the session we're going to allocate. */ + KSession *session; + + /* Reserve a new session from the resource limit. */ + KScopedResourceReservation session_reservation(GetCurrentProcessPointer(), ams::svc::LimitableResource_SessionCountMax); + if (session_reservation.Succeeded()) { + /* Allocate a session normally. */ + session = KSession::Create(); + } else { + /* We couldn't reserve a session. Check that we support dynamically expanding the resource limit. */ + R_UNLESS(GetCurrentProcess().GetResourceLimit() == std::addressof(Kernel::GetSystemResourceLimit()), svc::ResultLimitReached()); + R_UNLESS(KTargetSystem::IsDynamicResourceLimitsEnabled(), svc::ResultLimitReached()); + + /* Try to allocate a session from unused slab memory. */ + session = KSession::CreateFromUnusedSlabMemory(); + R_UNLESS(session != nullptr, svc::ResultLimitReached()); + ON_RESULT_FAILURE { session->Close(); }; + + /* We want to add two KSessionRequests to the heap, to prevent request exhaustion. */ + for (size_t i = 0; i < 2; ++i) { + KSessionRequest *request = KSessionRequest::CreateFromUnusedSlabMemory(); + R_UNLESS(request != nullptr, svc::ResultLimitReached()); + + request->Close(); + } + + /* We successfully allocated a session, so add the object we allocated to the resource limit. */ + Kernel::GetSystemResourceLimit().Add(ams::svc::LimitableResource_SessionCountMax, 1); + } + + /* Check that we successfully created a session. */ + R_UNLESS(session != nullptr, svc::ResultOutOfResource()); + + /* Update the session counts. */ + { + ON_RESULT_FAILURE { session->Close(); }; + + /* Atomically increment the number of sessions. */ + s32 new_sessions; + { + const auto max = m_max_sessions; + auto cur_sessions = m_num_sessions.Load(); + do { + R_UNLESS(cur_sessions < max, svc::ResultOutOfSessions()); + new_sessions = cur_sessions + 1; + } while (!m_num_sessions.CompareExchangeWeak<std::memory_order_relaxed>(cur_sessions, new_sessions)); + } + + /* Atomically update the peak session tracking. */ + { + auto peak = m_peak_sessions.Load(); + do { + if (peak >= new_sessions) { + break; + } + } while (!m_peak_sessions.CompareExchangeWeak<std::memory_order_relaxed>(peak, new_sessions)); + } + } + + /* Initialize the session. */ + session->Initialize(this, m_parent->GetName()); + + /* Commit the session reservation. */ + session_reservation.Commit(); + + /* Register the session. */ + KSession::Register(session); + ON_RESULT_FAILURE { + session->GetClientSession().Close(); + session->GetServerSession().Close(); + }; + + /* Enqueue the session with our parent. */ + R_TRY(m_parent->EnqueueSession(std::addressof(session->GetServerSession()))); + + /* We succeeded, so set the output. */ + *out = std::addressof(session->GetClientSession()); + R_SUCCEED(); + } + + Result KClientPort::CreateLightSession(KLightClientSession **out) { + MESOSPHERE_ASSERT_THIS(); + + /* Declare the session we're going to allocate. */ + KLightSession *session; + + /* Reserve a new session from the resource limit. */ + KScopedResourceReservation session_reservation(GetCurrentProcessPointer(), ams::svc::LimitableResource_SessionCountMax); + if (session_reservation.Succeeded()) { + /* Allocate a session normally. */ + session = KLightSession::Create(); + } else { + /* We couldn't reserve a session. Check that we support dynamically expanding the resource limit. */ + R_UNLESS(GetCurrentProcess().GetResourceLimit() == std::addressof(Kernel::GetSystemResourceLimit()), svc::ResultLimitReached()); + R_UNLESS(KTargetSystem::IsDynamicResourceLimitsEnabled(), svc::ResultLimitReached()); + + /* Try to allocate a session from unused slab memory. */ + session = KLightSession::CreateFromUnusedSlabMemory(); + R_UNLESS(session != nullptr, svc::ResultLimitReached()); + + /* We successfully allocated a session, so add the object we allocated to the resource limit. */ + Kernel::GetSystemResourceLimit().Add(ams::svc::LimitableResource_SessionCountMax, 1); + } + + /* Check that we successfully created a session. */ + R_UNLESS(session != nullptr, svc::ResultOutOfResource()); + + /* Update the session counts. */ + { + ON_RESULT_FAILURE { session->Close(); }; + + /* Atomically increment the number of sessions. */ + s32 new_sessions; + { + const auto max = m_max_sessions; + auto cur_sessions = m_num_sessions.Load(); + do { + R_UNLESS(cur_sessions < max, svc::ResultOutOfSessions()); + new_sessions = cur_sessions + 1; + } while (!m_num_sessions.CompareExchangeWeak<std::memory_order_relaxed>(cur_sessions, new_sessions)); + } + + /* Atomically update the peak session tracking. */ + { + auto peak = m_peak_sessions.Load(); + do { + if (peak >= new_sessions) { + break; + } + } while (!m_peak_sessions.CompareExchangeWeak<std::memory_order_relaxed>(peak, new_sessions)); + } + } + + /* Initialize the session. */ + session->Initialize(this, m_parent->GetName()); + + /* Commit the session reservation. */ + session_reservation.Commit(); + + /* Register the session. */ + KLightSession::Register(session); + ON_RESULT_FAILURE { + session->GetClientSession().Close(); + session->GetServerSession().Close(); + }; + + /* Enqueue the session with our parent. */ + R_TRY(m_parent->EnqueueSession(std::addressof(session->GetServerSession()))); + + /* We succeeded, so set the output. */ + *out = std::addressof(session->GetClientSession()); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_client_session.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_client_session.cpp new file mode 100644 index 00000000..510ff7d4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_client_session.cpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + void KClientSession::Destroy() { + MESOSPHERE_ASSERT_THIS(); + + m_parent->OnClientClosed(); + m_parent->Close(); + } + + void KClientSession::OnServerClosed() { + MESOSPHERE_ASSERT_THIS(); + } + + Result KClientSession::SendSyncRequest(uintptr_t address, size_t size) { + MESOSPHERE_ASSERT_THIS(); + + /* Create a session request. */ + KSessionRequest *request = KSessionRequest::Create(); + R_UNLESS(request != nullptr, svc::ResultOutOfResource()); + ON_SCOPE_EXIT { request->Close(); }; + + /* Initialize the request. */ + request->Initialize(nullptr, address, size); + + /* Send the request. */ + R_RETURN(m_parent->OnRequest(request)); + } + + Result KClientSession::SendAsyncRequest(KEvent *event, uintptr_t address, size_t size) { + MESOSPHERE_ASSERT_THIS(); + + /* Create a session request. */ + KSessionRequest *request = KSessionRequest::Create(); + R_UNLESS(request != nullptr, svc::ResultOutOfResource()); + ON_SCOPE_EXIT { request->Close(); }; + + /* Initialize the request. */ + request->Initialize(event, address, size); + + /* Send the request. */ + R_RETURN(m_parent->OnRequest(request)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_code_memory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_code_memory.cpp new file mode 100644 index 00000000..6a5a30ff --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_code_memory.cpp @@ -0,0 +1,160 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + Result KCodeMemory::Initialize(KProcessAddress addr, size_t size) { + MESOSPHERE_ASSERT_THIS(); + + /* Set members. */ + m_owner = GetCurrentProcessPointer(); + + /* Get the owner page table. */ + auto &page_table = m_owner->GetPageTable(); + + /* Construct the page group, guarding to make sure our state is valid on exit. */ + auto pg_guard = util::ConstructAtGuarded(m_page_group, page_table.GetBlockInfoManager()); + + /* Lock the memory. */ + R_TRY(page_table.LockForCodeMemory(GetPointer(m_page_group), addr, size)); + + /* Clear the memory. */ + for (const auto &block : GetReference(m_page_group)) { + /* Clear and store cache. */ + void * const block_address = GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(block.GetAddress())); + std::memset(block_address, 0xFF, block.GetSize()); + cpu::StoreDataCache(block_address, block.GetSize()); + } + + /* Set remaining tracking members. */ + m_owner->Open(); + m_address = addr; + m_is_initialized = true; + m_is_owner_mapped = false; + m_is_mapped = false; + + /* We succeeded. */ + pg_guard.Cancel(); + R_SUCCEED(); + } + + void KCodeMemory::Finalize() { + MESOSPHERE_ASSERT_THIS(); + + /* Unlock. */ + if (!m_is_mapped && !m_is_owner_mapped) { + const size_t size = GetReference(m_page_group).GetNumPages() * PageSize; + MESOSPHERE_R_ABORT_UNLESS(m_owner->GetPageTable().UnlockForCodeMemory(m_address, size, GetReference(m_page_group))); + } + + /* Close the page group. */ + GetReference(m_page_group).Close(); + GetReference(m_page_group).Finalize(); + + /* Close our reference to our owner. */ + m_owner->Close(); + } + + Result KCodeMemory::Map(KProcessAddress address, size_t size) { + MESOSPHERE_ASSERT_THIS(); + + /* Validate the size. */ + R_UNLESS(GetReference(m_page_group).GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize()); + + /* Lock ourselves. */ + KScopedLightLock lk(m_lock); + + /* Ensure we're not already mapped. */ + R_UNLESS(!m_is_mapped, svc::ResultInvalidState()); + + /* Map the memory. */ + R_TRY(GetCurrentProcess().GetPageTable().MapPageGroup(address, GetReference(m_page_group), KMemoryState_CodeOut, KMemoryPermission_UserReadWrite)); + + /* Mark ourselves as mapped. */ + m_is_mapped = true; + + R_SUCCEED(); + } + + Result KCodeMemory::Unmap(KProcessAddress address, size_t size) { + MESOSPHERE_ASSERT_THIS(); + + /* Validate the size. */ + R_UNLESS(GetReference(m_page_group).GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize()); + + /* Lock ourselves. */ + KScopedLightLock lk(m_lock); + + /* Unmap the memory. */ + R_TRY(GetCurrentProcess().GetPageTable().UnmapPageGroup(address, GetReference(m_page_group), KMemoryState_CodeOut)); + + /* Mark ourselves as unmapped. */ + MESOSPHERE_ASSERT(m_is_mapped); + m_is_mapped = false; + + R_SUCCEED(); + } + + Result KCodeMemory::MapToOwner(KProcessAddress address, size_t size, ams::svc::MemoryPermission perm) { + MESOSPHERE_ASSERT_THIS(); + + /* Validate the size. */ + R_UNLESS(GetReference(m_page_group).GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize()); + + /* Lock ourselves. */ + KScopedLightLock lk(m_lock); + + /* Ensure we're not already mapped. */ + R_UNLESS(!m_is_owner_mapped, svc::ResultInvalidState()); + + /* Convert the memory permission. */ + KMemoryPermission k_perm; + switch (perm) { + case ams::svc::MemoryPermission_Read: k_perm = KMemoryPermission_UserRead; break; + case ams::svc::MemoryPermission_ReadExecute: k_perm = KMemoryPermission_UserReadExecute; break; + MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); + } + + /* Map the memory. */ + R_TRY(m_owner->GetPageTable().MapPageGroup(address, GetReference(m_page_group), KMemoryState_GeneratedCode, k_perm)); + + /* Mark ourselves as mapped. */ + m_is_owner_mapped = true; + + R_SUCCEED(); + } + + Result KCodeMemory::UnmapFromOwner(KProcessAddress address, size_t size) { + MESOSPHERE_ASSERT_THIS(); + + /* Validate the size. */ + R_UNLESS(GetReference(m_page_group).GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize()); + + /* Lock ourselves. */ + KScopedLightLock lk(m_lock); + + /* Unmap the memory. */ + R_TRY(m_owner->GetPageTable().UnmapPageGroup(address, GetReference(m_page_group), KMemoryState_GeneratedCode)); + + /* Mark ourselves as unmapped. */ + MESOSPHERE_ASSERT(m_is_owner_mapped); + m_is_owner_mapped = false; + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_condition_variable.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_condition_variable.cpp new file mode 100644 index 00000000..14b25d62 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_condition_variable.cpp @@ -0,0 +1,283 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + ALWAYS_INLINE bool ReadFromUser(u32 *out, KProcessAddress address) { + return UserspaceAccess::CopyMemoryFromUserSize32Bit(out, GetVoidPointer(address)); + } + + ALWAYS_INLINE bool WriteToUser(KProcessAddress address, const u32 *p) { + return UserspaceAccess::CopyMemoryToUserSize32Bit(GetVoidPointer(address), p); + } + + ALWAYS_INLINE bool UpdateLockAtomic(u32 *out, KProcessAddress address, u32 if_zero, u32 new_orr_mask) { + return UserspaceAccess::UpdateLockAtomic(out, GetPointer<u32>(address), if_zero, new_orr_mask); + } + + class ThreadQueueImplForKConditionVariableWaitForAddress final : public KThreadQueue { + public: + constexpr ThreadQueueImplForKConditionVariableWaitForAddress() : KThreadQueue() { /* ... */ } + + virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override { + /* Remove the thread as a waiter from its owner. */ + waiting_thread->GetLockOwner()->RemoveWaiter(waiting_thread); + + /* Invoke the base cancel wait handler. */ + KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + } + }; + + class ThreadQueueImplForKConditionVariableWaitConditionVariable final : public KThreadQueue { + private: + KConditionVariable::ThreadTree *m_tree; + public: + constexpr ThreadQueueImplForKConditionVariableWaitConditionVariable(KConditionVariable::ThreadTree *t) : KThreadQueue(), m_tree(t) { /* ... */ } + + virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override { + /* Remove the thread as a waiter from its owner. */ + if (KThread *owner = waiting_thread->GetLockOwner(); owner != nullptr) { + owner->RemoveWaiter(waiting_thread); + } + + /* If the thread is waiting on a condvar, remove it from the tree. */ + if (waiting_thread->IsWaitingForConditionVariable()) { + m_tree->erase(m_tree->iterator_to(*waiting_thread)); + waiting_thread->ClearConditionVariable(); + } + + /* Invoke the base cancel wait handler. */ + KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + } + }; + + } + + Result KConditionVariable::SignalToAddress(KProcessAddress addr) { + KThread *owner_thread = GetCurrentThreadPointer(); + + /* Signal the address. */ + { + KScopedSchedulerLock sl; + + /* Remove waiter thread. */ + bool has_waiters; + KThread * const next_owner_thread = owner_thread->RemoveWaiterByKey(std::addressof(has_waiters), addr); + + /* Determine the next tag. */ + u32 next_value = 0; + if (next_owner_thread != nullptr) { + next_value = next_owner_thread->GetAddressKeyValue(); + if (has_waiters) { + next_value |= ams::svc::HandleWaitMask; + } + } + + /* Synchronize memory before proceeding. */ + cpu::DataMemoryBarrierInnerShareable(); + + /* Write the value to userspace. */ + Result result; + if (AMS_LIKELY(WriteToUser(addr, std::addressof(next_value)))) { + result = ResultSuccess(); + } else { + result = svc::ResultInvalidCurrentMemory(); + } + + /* If necessary, signal the next owner thread. */ + if (next_owner_thread != nullptr) { + next_owner_thread->EndWait(result); + } + + R_RETURN(result); + } + } + + Result KConditionVariable::WaitForAddress(ams::svc::Handle handle, KProcessAddress addr, u32 value) { + KThread *cur_thread = GetCurrentThreadPointer(); + ThreadQueueImplForKConditionVariableWaitForAddress wait_queue; + + /* Wait for the address. */ + KThread *owner_thread; + { + KScopedSchedulerLock sl; + + /* Check if the thread should terminate. */ + R_UNLESS(!cur_thread->IsTerminationRequested(), svc::ResultTerminationRequested()); + + /* Read the tag from userspace. */ + u32 test_tag; + R_UNLESS(ReadFromUser(std::addressof(test_tag), addr), svc::ResultInvalidCurrentMemory()); + + /* If the tag isn't the handle (with wait mask), we're done. */ + R_SUCCEED_IF(test_tag != (handle | ams::svc::HandleWaitMask)); + + /* Get the lock owner thread. */ + owner_thread = GetCurrentProcess().GetHandleTable().GetObjectWithoutPseudoHandle<KThread>(handle).ReleasePointerUnsafe(); + R_UNLESS(owner_thread != nullptr, svc::ResultInvalidHandle()); + + /* Update the lock. */ + cur_thread->SetAddressKey(addr, value); + owner_thread->AddWaiter(cur_thread); + + /* Begin waiting. */ + cur_thread->BeginWait(std::addressof(wait_queue)); + } + + /* Close our reference to the owner thread, now that the wait is over. */ + owner_thread->Close(); + + /* Get the wait result. */ + R_RETURN(cur_thread->GetWaitResult()); + } + + void KConditionVariable::SignalImpl(KThread *thread) { + /* Check pre-conditions. */ + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + /* Update the tag. */ + KProcessAddress address = thread->GetAddressKey(); + u32 own_tag = thread->GetAddressKeyValue(); + + u32 prev_tag; + bool can_access; + { + /* NOTE: If scheduler lock is not held here, interrupt disable is required. */ + /* KScopedInterruptDisable di; */ + + can_access = cpu::CanAccessAtomic(address); + if (AMS_LIKELY(can_access)) { + can_access = UpdateLockAtomic(std::addressof(prev_tag), address, own_tag, ams::svc::HandleWaitMask); + } + } + + if (AMS_LIKELY(can_access)) { + if (prev_tag == ams::svc::InvalidHandle) { + /* If nobody held the lock previously, we're all good. */ + thread->EndWait(ResultSuccess()); + } else { + /* Get the previous owner. */ + KThread *owner_thread = GetCurrentProcess().GetHandleTable().GetObjectWithoutPseudoHandle<KThread>(static_cast<ams::svc::Handle>(prev_tag & ~ams::svc::HandleWaitMask)) + .ReleasePointerUnsafe(); + if (AMS_LIKELY(owner_thread != nullptr)) { + /* Add the thread as a waiter on the owner. */ + owner_thread->AddWaiter(thread); + owner_thread->Close(); + } else { + /* The lock was tagged with a thread that doesn't exist. */ + thread->EndWait(svc::ResultInvalidState()); + } + } + } else { + /* If the address wasn't accessible, note so. */ + thread->EndWait(svc::ResultInvalidCurrentMemory()); + } + } + + void KConditionVariable::Signal(uintptr_t cv_key, s32 count) { + /* Perform signaling. */ + int num_waiters = 0; + { + KScopedSchedulerLock sl; + + auto it = m_tree.nfind_key({ cv_key, -1 }); + while ((it != m_tree.end()) && (count <= 0 || num_waiters < count) && (it->GetConditionVariableKey() == cv_key)) { + KThread *target_thread = std::addressof(*it); + + it = m_tree.erase(it); + target_thread->ClearConditionVariable(); + + this->SignalImpl(target_thread); + + ++num_waiters; + } + + /* If we have no waiters, clear the has waiter flag. */ + if (it == m_tree.end() || it->GetConditionVariableKey() != cv_key) { + const u32 has_waiter_flag = 0; + WriteToUser(cv_key, std::addressof(has_waiter_flag)); + } + } + } + + Result KConditionVariable::Wait(KProcessAddress addr, uintptr_t key, u32 value, s64 timeout) { + /* Prepare to wait. */ + KThread *cur_thread = GetCurrentThreadPointer(); + KHardwareTimer *timer; + ThreadQueueImplForKConditionVariableWaitConditionVariable wait_queue(std::addressof(m_tree)); + + { + KScopedSchedulerLockAndSleep slp(std::addressof(timer), cur_thread, timeout); + + /* Check that the thread isn't terminating. */ + if (cur_thread->IsTerminationRequested()) { + slp.CancelSleep(); + R_THROW(svc::ResultTerminationRequested()); + } + + /* Update the value and process for the next owner. */ + { + /* Remove waiter thread. */ + bool has_waiters; + KThread *next_owner_thread = cur_thread->RemoveWaiterByKey(std::addressof(has_waiters), GetInteger(addr)); + + /* Update for the next owner thread. */ + u32 next_value = 0; + if (next_owner_thread != nullptr) { + /* Get the next tag value. */ + next_value = next_owner_thread->GetAddressKeyValue(); + if (has_waiters) { + next_value |= ams::svc::HandleWaitMask; + } + + /* Wake up the next owner. */ + next_owner_thread->EndWait(ResultSuccess()); + } + + /* Write to the cv key. */ + { + const u32 has_waiter_flag = 1; + WriteToUser(key, std::addressof(has_waiter_flag)); + cpu::DataMemoryBarrierInnerShareable(); + } + + /* Write the value to userspace. */ + if (!WriteToUser(addr, std::addressof(next_value))) { + slp.CancelSleep(); + R_THROW(svc::ResultInvalidCurrentMemory()); + } + } + + /* If timeout is zero, time out. */ + R_UNLESS(timeout != 0, svc::ResultTimedOut()); + + /* Update condition variable tracking. */ + cur_thread->SetConditionVariable(std::addressof(m_tree), addr, key, value); + m_tree.insert(*cur_thread); + + /* Begin waiting. */ + wait_queue.SetHardwareTimer(timer); + cur_thread->BeginWait(std::addressof(wait_queue)); + } + + /* Get the wait result. */ + R_RETURN(cur_thread->GetWaitResult()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_debug_base.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_debug_base.cpp new file mode 100644 index 00000000..635673cd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_debug_base.cpp @@ -0,0 +1,1187 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + ALWAYS_INLINE KDebugBase *GetDebugObject(KProcess *process) { + return static_cast<KDebugBase *>(process->GetDebugObject()); + } + + } + + void KDebugBase::Initialize() { + /* Clear the continue flags. */ + m_continue_flags = 0; + m_is_force_debug_prod = GetCurrentProcess().CanForceDebugProd(); + } + + bool KDebugBase::Is64Bit() const { + MESOSPHERE_ASSERT(m_lock.IsLockedByCurrentThread()); + MESOSPHERE_ASSERT(m_is_attached); + + KProcess * const process = this->GetProcessUnsafe(); + MESOSPHERE_ASSERT(process != nullptr); + return process->Is64Bit(); + } + + + Result KDebugBase::QueryMemoryInfo(ams::svc::MemoryInfo *out_memory_info, ams::svc::PageInfo *out_page_info, KProcessAddress address) { + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + + /* Lock ourselves. */ + KScopedLightLock lk(m_lock); + + /* Check that we're still attached now that we're locked. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Get the process pointer. */ + KProcess * const process = this->GetProcessUnsafe(); + + /* Check that the process isn't terminated. */ + R_UNLESS(!process->IsTerminated(), svc::ResultProcessTerminated()); + + /* Query the mapping's info. */ + KMemoryInfo info; + R_TRY(process->GetPageTable().QueryInfo(std::addressof(info), out_page_info, address)); + + /* Write output. */ + *out_memory_info = info.GetSvcMemoryInfo(); + + R_SUCCEED(); + } + + Result KDebugBase::ReadMemory(KProcessAddress buffer, KProcessAddress address, size_t size) { + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + + /* Lock ourselves. */ + KScopedLightLock lk(m_lock); + + /* Check that we're still attached now that we're locked. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Get the process pointer. */ + KProcess * const process = this->GetProcessUnsafe(); + + /* Check that the process isn't terminated. */ + R_UNLESS(!process->IsTerminated(), svc::ResultProcessTerminated()); + + /* Get the page tables. */ + KProcessPageTable &debugger_pt = GetCurrentProcess().GetPageTable(); + KProcessPageTable &target_pt = process->GetPageTable(); + + /* Verify that the regions are in range. */ + R_UNLESS(target_pt.Contains(address, size), svc::ResultInvalidCurrentMemory()); + R_UNLESS(debugger_pt.Contains(buffer, size), svc::ResultInvalidCurrentMemory()); + + /* Iterate over the target process's memory blocks. */ + KProcessAddress cur_address = address; + size_t remaining = size; + while (remaining > 0) { + /* Get the current memory info. */ + KMemoryInfo info; + ams::svc::PageInfo pi; + R_TRY(target_pt.QueryInfo(std::addressof(info), std::addressof(pi), cur_address)); + + /* Check that the memory is accessible. */ + R_UNLESS(info.GetState() != static_cast<KMemoryState>(ams::svc::MemoryState_Inaccessible), svc::ResultInvalidAddress()); + + /* Get the current size. */ + const size_t cur_size = std::min(remaining, info.GetEndAddress() - GetInteger(cur_address)); + + /* Read the memory. */ + if (info.GetSvcState() != ams::svc::MemoryState_Io) { + /* The memory is normal memory. */ + R_TRY(target_pt.ReadDebugMemory(GetVoidPointer(buffer), cur_address, cur_size, this->IsForceDebugProd())); + } else { + /* Only allow IO memory to be read if not force debug prod. */ + R_UNLESS(!this->IsForceDebugProd(), svc::ResultInvalidCurrentMemory()); + + /* The memory is IO memory. */ + R_TRY(target_pt.ReadDebugIoMemory(GetVoidPointer(buffer), cur_address, cur_size, info.GetState())); + } + + /* Advance. */ + buffer += cur_size; + cur_address += cur_size; + remaining -= cur_size; + } + + R_SUCCEED(); + } + + Result KDebugBase::WriteMemory(KProcessAddress buffer, KProcessAddress address, size_t size) { + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + + /* Lock ourselves. */ + KScopedLightLock lk(m_lock); + + /* Check that we're still attached now that we're locked. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Get the process pointer. */ + KProcess * const process = this->GetProcessUnsafe(); + + /* Check that the process isn't terminated. */ + R_UNLESS(!process->IsTerminated(), svc::ResultProcessTerminated()); + + /* Get the page tables. */ + KProcessPageTable &debugger_pt = GetCurrentProcess().GetPageTable(); + KProcessPageTable &target_pt = process->GetPageTable(); + + /* Verify that the regions are in range. */ + R_UNLESS(target_pt.Contains(address, size), svc::ResultInvalidCurrentMemory()); + R_UNLESS(debugger_pt.Contains(buffer, size), svc::ResultInvalidCurrentMemory()); + + /* Iterate over the target process's memory blocks. */ + KProcessAddress cur_address = address; + size_t remaining = size; + while (remaining > 0) { + /* Get the current memory info. */ + KMemoryInfo info; + ams::svc::PageInfo pi; + R_TRY(target_pt.QueryInfo(std::addressof(info), std::addressof(pi), cur_address)); + + /* Check that the memory is accessible. */ + R_UNLESS(info.GetState() != static_cast<KMemoryState>(ams::svc::MemoryState_Inaccessible), svc::ResultInvalidAddress()); + + /* Get the current size. */ + const size_t cur_size = std::min(remaining, info.GetEndAddress() - GetInteger(cur_address)); + + /* Read the memory. */ + if (info.GetSvcState() != ams::svc::MemoryState_Io) { + /* The memory is normal memory. */ + R_TRY(target_pt.WriteDebugMemory(cur_address, GetVoidPointer(buffer), cur_size)); + } else { + /* The memory is IO memory. */ + R_TRY(target_pt.WriteDebugIoMemory(cur_address, GetVoidPointer(buffer), cur_size, info.GetState())); + } + + /* Advance. */ + buffer += cur_size; + cur_address += cur_size; + remaining -= cur_size; + } + + R_SUCCEED(); + } + + Result KDebugBase::GetRunningThreadInfo(ams::svc::LastThreadContext *out_context, u64 *out_thread_id) { + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + + /* Get the process pointer. */ + KProcess * const process = this->GetProcessUnsafe(); + + /* Get the thread info. */ + { + KScopedSchedulerLock sl; + + /* Get the running thread. */ + const s32 core_id = GetCurrentCoreId(); + KThread *thread = process->GetRunningThread(core_id); + + /* We want to check that the thread is actually running. */ + /* If it is, then the scheduler will have just switched from the thread to the current thread. */ + /* This implies exactly one switch will have taken place, and the current thread will be on the current core. */ + const auto &scheduler = Kernel::GetScheduler(core_id); + if (!(thread != nullptr && thread->GetActiveCore() == core_id && process->GetRunningThreadSwitchCount(core_id) + 1 == scheduler.GetSwitchCount())) { + /* The most recent thread switch was from a thread other than the expected one to the current one. */ + /* We want to use the appropriate result to inform userland about what thread we switched from. */ + if (scheduler.GetIdleCount() + 1 == scheduler.GetSwitchCount()) { + /* We switched from the idle thread. */ + R_THROW(svc::ResultNoThread()); + } else { + /* We switched from some other unknown thread. */ + R_THROW(svc::ResultUnknownThread()); + } + } + + /* Get the thread's exception context. */ + GetExceptionContext(thread)->GetSvcThreadContext(out_context); + + /* Get the thread's id. */ + *out_thread_id = thread->GetId(); + } + + R_SUCCEED(); + } + + Result KDebugBase::Attach(KProcess *target) { + /* Check that the process isn't null. */ + MESOSPHERE_ASSERT(target != nullptr); + + /* Clear ourselves as unattached. */ + m_is_attached = false; + + /* Attach to the process. */ + { + /* Lock both ourselves, the target process, and the scheduler. */ + KScopedLightLock state_lk(target->GetStateLock()); + KScopedLightLock list_lk(target->GetListLock()); + KScopedLightLock this_lk(m_lock); + KScopedSchedulerLock sl; + + /* Check that the process isn't already being debugged. */ + R_UNLESS(!target->IsAttachedToDebugger(), svc::ResultBusy()); + + { + /* Ensure the process is in a state that allows for debugging. */ + const KProcess::State state = target->GetState(); + switch (state) { + case KProcess::State_Created: + case KProcess::State_Running: + /* Created and running processes can only be debugged if the debugger is not ForceDebugProd. */ + R_UNLESS(!this->IsForceDebugProd(), svc::ResultInvalidState()); + break; + case KProcess::State_Crashed: + break; + case KProcess::State_CreatedAttached: + case KProcess::State_RunningAttached: + case KProcess::State_DebugBreak: + R_THROW(svc::ResultBusy()); + case KProcess::State_Terminating: + case KProcess::State_Terminated: + R_THROW(svc::ResultProcessTerminated()); + MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); + } + + /* Attach to the target. */ + m_process_holder.Attach(target); + m_is_attached = true; + + /* Set ourselves as the process's attached object. */ + m_old_process_state = target->SetDebugObject(this); + + /* Send an event for our attaching to the process. */ + this->PushDebugEvent(ams::svc::DebugEvent_CreateProcess, nullptr, 0); + + /* Send events for attaching to each thread in the process. */ + { + auto end = target->GetThreadList().end(); + for (auto it = target->GetThreadList().begin(); it != end; ++it) { + /* Request that we suspend the thread. */ + it->RequestSuspend(KThread::SuspendType_Debug); + + /* If the thread is in a state for us to do so, generate the event. */ + if (const auto thread_state = it->GetState(); thread_state == KThread::ThreadState_Runnable || thread_state == KThread::ThreadState_Waiting) { + /* Mark the thread as attached to. */ + it->SetDebugAttached(); + + /* Send the event. */ + const uintptr_t params[2] = { it->GetId(), GetInteger(it->GetThreadLocalRegionAddress()) }; + this->PushDebugEvent(ams::svc::DebugEvent_CreateThread, params, util::size(params)); + } + } + } + + /* Send the process's jit debug info, if relevant. */ + if (KEventInfo *jit_info = target->GetJitDebugInfo(); jit_info != nullptr) { + this->EnqueueDebugEventInfo(jit_info); + } + + /* Send an exception event to represent our attaching. */ + const uintptr_t params[1] = { static_cast<uintptr_t>(ams::svc::DebugException_DebuggerAttached) }; + this->PushDebugEvent(ams::svc::DebugEvent_Exception, params, util::size(params)); + + /* Signal. */ + this->NotifyAvailable(); + } + } + + R_SUCCEED(); + } + + Result KDebugBase::BreakProcess() { + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + + /* Get the process pointer. */ + KProcess * const target = this->GetProcessUnsafe(); + + /* Lock both ourselves, the target process, and the scheduler. */ + KScopedLightLock state_lk(target->GetStateLock()); + KScopedLightLock list_lk(target->GetListLock()); + KScopedLightLock this_lk(m_lock); + KScopedSchedulerLock sl; + + /* Check that we're still attached now that we're locked. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Check that the process isn't terminated. */ + R_UNLESS(!target->IsTerminated(), svc::ResultProcessTerminated()); + + /* Get the currently active threads. */ + constexpr u64 ThreadIdNoThread = -1ll; + constexpr u64 ThreadIdUnknownThread = -2ll; + uintptr_t debug_info_params[1 + cpu::NumCores] = { static_cast<uintptr_t>(ams::svc::DebugException_DebuggerBreak), }; + for (size_t i = 0; i < cpu::NumCores; ++i) { + /* Get the currently running thread. */ + KThread *thread = target->GetRunningThread(i); + + /* Check that the thread's idle count is correct. */ + if (target->GetRunningThreadIdleCount(i) == Kernel::GetScheduler(i).GetIdleCount()) { + if (thread != nullptr && static_cast<size_t>(thread->GetActiveCore()) == i) { + debug_info_params[1 + i] = thread->GetId(); + } else { + /* We found an unknown thread. */ + debug_info_params[1 + i] = ThreadIdUnknownThread; + } + } else { + /* We didn't find a thread. */ + debug_info_params[1 + i] = ThreadIdNoThread; + } + } + + /* Suspend all the threads in the process. */ + { + auto end = target->GetThreadList().end(); + for (auto it = target->GetThreadList().begin(); it != end; ++it) { + /* Request that we suspend the thread. */ + it->RequestSuspend(KThread::SuspendType_Debug); + } + } + + /* Send an exception event to represent our breaking the process. */ + this->PushDebugEvent(ams::svc::DebugEvent_Exception, debug_info_params, util::size(debug_info_params)); + + /* Signal. */ + this->NotifyAvailable(); + + /* Set the process as breaked. */ + target->SetDebugBreak(); + + R_SUCCEED(); + } + + Result KDebugBase::TerminateProcess() { + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), ResultSuccess()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), ResultSuccess()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + + /* Get the process pointer. */ + KProcess * const target = this->GetProcessUnsafe(); + + /* Terminate the process. */ + target->Terminate(); + + R_SUCCEED(); + } + + Result KDebugBase::GetThreadContext(ams::svc::ThreadContext *out, u64 thread_id, u32 context_flags) { + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + + /* Lock ourselves. */ + KScopedLightLock lk(m_lock); + + /* Check that we're still attached now that we're locked. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Get the process pointer. */ + KProcess * const process = this->GetProcessUnsafe(); + + /* Get the thread from its id. */ + KThread *thread = KThread::GetThreadFromId(thread_id); + R_UNLESS(thread != nullptr, svc::ResultInvalidId()); + ON_SCOPE_EXIT { thread->Close(); }; + + /* Verify that the thread is owned by our process. */ + R_UNLESS(process == thread->GetOwnerProcess(), svc::ResultInvalidId()); + + /* Verify that the thread isn't terminated. */ + R_UNLESS(thread->GetState() != KThread::ThreadState_Terminated, svc::ResultTerminationRequested()); + + /* Check that the thread is not the current one. */ + /* NOTE: Nintendo does not check this, and thus the following loop will deadlock. */ + R_UNLESS(thread != GetCurrentThreadPointer(), svc::ResultInvalidId()); + + /* Try to get the thread context until the thread isn't current on any core. */ + while (true) { + KScopedSchedulerLock sl; + + /* The thread needs to be requested for debug suspension. */ + R_UNLESS(thread->IsSuspendRequested(KThread::SuspendType_Debug), svc::ResultInvalidState()); + + /* If the thread's raw state isn't runnable, check if it's current on some core. */ + if (thread->GetRawState() != KThread::ThreadState_Runnable) { + bool current = false; + for (auto i = 0; i < static_cast<s32>(cpu::NumCores); ++i) { + if (thread == Kernel::GetScheduler(i).GetSchedulerCurrentThread()) { + current = true; + break; + } + } + + /* If the thread is current, retry until it isn't. */ + if (current) { + continue; + } + } + + /* Get the thread context. */ + static_assert(std::derived_from<KDebug, KDebugBase>); + R_RETURN(static_cast<KDebug *>(this)->GetThreadContextImpl(out, thread, context_flags)); + } + } + + Result KDebugBase::SetThreadContext(const ams::svc::ThreadContext &ctx, u64 thread_id, u32 context_flags) { + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + + /* Lock ourselves. */ + KScopedLightLock lk(m_lock); + + /* Check that we're still attached now that we're locked. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Get the process pointer. */ + KProcess * const process = this->GetProcessUnsafe(); + + /* Get the thread from its id. */ + KThread *thread = KThread::GetThreadFromId(thread_id); + R_UNLESS(thread != nullptr, svc::ResultInvalidId()); + ON_SCOPE_EXIT { thread->Close(); }; + + /* Verify that the thread is owned by our process. */ + R_UNLESS(process == thread->GetOwnerProcess(), svc::ResultInvalidId()); + + /* Verify that the thread isn't terminated. */ + R_UNLESS(thread->GetState() != KThread::ThreadState_Terminated, svc::ResultTerminationRequested()); + + /* Check that the thread is not the current one. */ + /* NOTE: Nintendo does not check this, and thus the following loop will deadlock. */ + R_UNLESS(thread != GetCurrentThreadPointer(), svc::ResultInvalidId()); + + /* Try to get the thread context until the thread isn't current on any core. */ + while (true) { + KScopedSchedulerLock sl; + + /* The thread needs to be requested for debug suspension. */ + R_UNLESS(thread->IsSuspendRequested(KThread::SuspendType_Debug), svc::ResultInvalidState()); + + /* If the thread's raw state isn't runnable, check if it's current on some core. */ + if (thread->GetRawState() != KThread::ThreadState_Runnable) { + bool current = false; + for (auto i = 0; i < static_cast<s32>(cpu::NumCores); ++i) { + if (thread == Kernel::GetScheduler(i).GetSchedulerCurrentThread()) { + current = true; + break; + } + } + + /* If the thread is current, retry until it isn't. */ + if (current) { + continue; + } + } + + /* Update thread single-step state. */ + #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) + { + if ((context_flags & ams::svc::ThreadContextFlag_SetSingleStep) != 0) { + /* Set single step. */ + thread->SetHardwareSingleStep(); + + /* If no other thread flags are present, we're done. */ + R_SUCCEED_IF((context_flags & ~ams::svc::ThreadContextFlag_SetSingleStep) == 0); + } else if ((context_flags & ams::svc::ThreadContextFlag_ClearSingleStep) != 0) { + /* Clear single step. */ + thread->ClearHardwareSingleStep(); + + /* If no other thread flags are present, we're done. */ + R_SUCCEED_IF((context_flags & ~ams::svc::ThreadContextFlag_ClearSingleStep) == 0); + } + } + #endif + + /* Verify that the thread's svc state is valid. */ + if (thread->IsCallingSvc()) { + const u8 svc_id = thread->GetSvcId(); + + const bool is_valid_svc = svc_id == svc::SvcId_Break || + svc_id == svc::SvcId_ReturnFromException; + + R_UNLESS(is_valid_svc, svc::ResultInvalidState()); + } + + /* Set the thread context. */ + static_assert(std::derived_from<KDebug, KDebugBase>); + R_RETURN(static_cast<KDebug *>(this)->SetThreadContextImpl(ctx, thread, context_flags)); + } + } + + + Result KDebugBase::ContinueDebug(const u32 flags, const u64 *thread_ids, size_t num_thread_ids) { + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + + /* Get the process pointer. */ + KProcess * const target = this->GetProcessUnsafe(); + + /* Lock both ourselves, the target process, and the scheduler. */ + KScopedLightLock state_lk(target->GetStateLock()); + KScopedLightLock list_lk(target->GetListLock()); + KScopedLightLock this_lk(m_lock); + KScopedSchedulerLock sl; + + /* Check that we're still attached now that we're locked. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Check that the process isn't terminated. */ + R_UNLESS(!target->IsTerminated(), svc::ResultProcessTerminated()); + + /* Check that we have no pending events. */ + R_UNLESS(m_event_info_list.empty(), svc::ResultBusy()); + + /* Clear the target's JIT debug info. */ + target->ClearJitDebugInfo(); + + /* Set our continue flags. */ + m_continue_flags = flags; + + /* Iterate over threads, continuing them as we should. */ + bool has_debug_break_thread = false; + { + /* Parse our flags. */ + const bool exception_handled = (m_continue_flags & ams::svc::ContinueFlag_ExceptionHandled) != 0; + const bool continue_all = (m_continue_flags & ams::svc::ContinueFlag_ContinueAll) != 0; + const bool continue_others = (m_continue_flags & ams::svc::ContinueFlag_ContinueOthers) != 0; + + /* Update each thread. */ + auto end = target->GetThreadList().end(); + for (auto it = target->GetThreadList().begin(); it != end; ++it) { + /* Determine if we should continue the thread. */ + bool should_continue; + { + if (continue_all) { + /* Continue all threads. */ + should_continue = true; + } else if (continue_others) { + /* Continue the thread if it doesn't match one of our target ids. */ + const u64 thread_id = it->GetId(); + should_continue = true; + for (size_t i = 0; i < num_thread_ids; ++i) { + if (thread_ids[i] == thread_id) { + should_continue = false; + break; + } + } + } else { + /* Continue the thread if it matches one of our target ids. */ + const u64 thread_id = it->GetId(); + should_continue = false; + for (size_t i = 0; i < num_thread_ids; ++i) { + if (thread_ids[i] == thread_id) { + should_continue = true; + break; + } + } + } + } + + /* Continue the thread if we should. */ + if (should_continue) { + if (exception_handled) { + it->SetDebugExceptionResult(svc::ResultStopProcessingException()); + } + it->Resume(KThread::SuspendType_Debug); + } + + /* If the thread has debug suspend requested, note so. */ + if (it->IsSuspendRequested(KThread::SuspendType_Debug)) { + has_debug_break_thread = true; + } + } + } + + /* Set the process's state. */ + if (has_debug_break_thread) { + target->SetDebugBreak(); + } else { + target->SetAttached(); + } + + R_SUCCEED(); + } + + KEventInfo *KDebugBase::CreateDebugEvent(ams::svc::DebugEvent event, u64 cur_thread_id, const uintptr_t *params, size_t num_params) { + /* Allocate a new event. */ + KEventInfo *info = KEventInfo::Allocate(); + + /* Populate the event info. */ + if (info != nullptr) { + /* Set common fields. */ + info->event = event; + info->thread_id = 0; + info->flags = ams::svc::DebugEventFlag_Stopped; + + /* Set event specific fields. */ + switch (event) { + case ams::svc::DebugEvent_CreateProcess: + { + /* Check parameters. */ + MESOSPHERE_ASSERT(params == nullptr); + MESOSPHERE_ASSERT(num_params == 0); + } + break; + case ams::svc::DebugEvent_CreateThread: + { + /* Check parameters. */ + MESOSPHERE_ASSERT(params != nullptr); + MESOSPHERE_ASSERT(num_params == 2); + + /* Set the thread id. */ + info->thread_id = params[0]; + + /* Set the thread creation info. */ + info->info.create_thread.thread_id = params[0]; + info->info.create_thread.tls_address = params[1]; + } + break; + case ams::svc::DebugEvent_ExitProcess: + { + /* Check parameters. */ + MESOSPHERE_ASSERT(params != nullptr); + MESOSPHERE_ASSERT(num_params == 1); + + /* Set the exit reason. */ + info->info.exit_process.reason = static_cast<ams::svc::ProcessExitReason>(params[0]); + + /* Clear the thread id and flags. */ + info->thread_id = 0; + info->flags = 0; + } + break; + case ams::svc::DebugEvent_ExitThread: + { + /* Check parameters. */ + MESOSPHERE_ASSERT(params != nullptr); + MESOSPHERE_ASSERT(num_params == 2); + + /* Set the thread id. */ + info->thread_id = params[0]; + + /* Set the exit reason. */ + info->info.exit_thread.reason = static_cast<ams::svc::ThreadExitReason>(params[1]); + } + break; + case ams::svc::DebugEvent_Exception: + { + /* Check parameters. */ + MESOSPHERE_ASSERT(params != nullptr); + MESOSPHERE_ASSERT(num_params >= 1); + + /* Set the thread id. */ + info->thread_id = cur_thread_id; + + /* Set the exception type, and clear the count. */ + info->info.exception.exception_type = static_cast<ams::svc::DebugException>(params[0]); + info->info.exception.exception_data_count = 0; + switch (static_cast<ams::svc::DebugException>(params[0])) { + case ams::svc::DebugException_UndefinedInstruction: + case ams::svc::DebugException_BreakPoint: + case ams::svc::DebugException_UndefinedSystemCall: + { + MESOSPHERE_ASSERT(num_params >= 3); + + info->info.exception.exception_address = params[1]; + + info->info.exception.exception_data_count = 1; + info->info.exception.exception_data[0] = params[2]; + } + break; + case ams::svc::DebugException_DebuggerAttached: + { + info->thread_id = 0; + + info->info.exception.exception_address = 0; + } + break; + case ams::svc::DebugException_UserBreak: + { + MESOSPHERE_ASSERT(num_params >= 2); + + info->info.exception.exception_address = params[1]; + + info->info.exception.exception_data_count = 0; + for (size_t i = 2; i < num_params; ++i) { + info->info.exception.exception_data[info->info.exception.exception_data_count++] = params[i]; + } + } + break; + case ams::svc::DebugException_DebuggerBreak: + { + info->thread_id = 0; + + info->info.exception.exception_address = 0; + + info->info.exception.exception_data_count = 0; + for (size_t i = 1; i < num_params; ++i) { + info->info.exception.exception_data[info->info.exception.exception_data_count++] = params[i]; + } + } + break; + case ams::svc::DebugException_MemorySystemError: + { + info->info.exception.exception_address = 0; + } + break; + case ams::svc::DebugException_InstructionAbort: + case ams::svc::DebugException_DataAbort: + case ams::svc::DebugException_AlignmentFault: + default: + { + MESOSPHERE_ASSERT(num_params >= 2); + + info->info.exception.exception_address = params[1]; + } + break; + } + } + break; + } + } + + return info; + } + + void KDebugBase::PushDebugEvent(ams::svc::DebugEvent event, const uintptr_t *params, size_t num_params) { + /* Create and enqueue and event. */ + if (KEventInfo *new_info = CreateDebugEvent(event, GetCurrentThread().GetId(), params, num_params); new_info != nullptr) { + this->EnqueueDebugEventInfo(new_info); + } + } + + void KDebugBase::EnqueueDebugEventInfo(KEventInfo *info) { + /* Lock the scheduler. */ + KScopedSchedulerLock sl; + + /* Push the event to the back of the list. */ + m_event_info_list.push_back(*info); + } + + + template<typename T> requires (std::same_as<T, ams::svc::lp64::DebugEventInfo> || std::same_as<T, ams::svc::ilp32::DebugEventInfo>) + Result KDebugBase::GetDebugEventInfoImpl(T *out) { + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + + /* Get the process pointer. */ + KProcess * const process = this->GetProcessUnsafe(); + + /* Pop an event info from our queue. */ + KEventInfo *info = nullptr; + { + KScopedSchedulerLock sl; + + /* Check that we have an event to dequeue. */ + R_UNLESS(!m_event_info_list.empty(), svc::ResultNoEvent()); + + /* Pop the event from the front of the queue. */ + info = std::addressof(m_event_info_list.front()); + m_event_info_list.pop_front(); + } + MESOSPHERE_ASSERT(info != nullptr); + + /* Free the event info once we're done with it. */ + ON_SCOPE_EXIT { KEventInfo::Free(info); }; + + /* Set common fields. */ + out->type = info->event; + out->thread_id = info->thread_id; + out->flags = info->flags; + + /* Set event specific fields. */ + switch (info->event) { + case ams::svc::DebugEvent_CreateProcess: + { + out->info.create_process.program_id = process->GetProgramId(); + out->info.create_process.process_id = process->GetId(); + out->info.create_process.flags = process->GetCreateProcessFlags(); + out->info.create_process.user_exception_context_address = GetInteger(process->GetProcessLocalRegionAddress()); + + std::memcpy(out->info.create_process.name, process->GetName(), sizeof(out->info.create_process.name)); + } + break; + case ams::svc::DebugEvent_CreateThread: + { + out->info.create_thread.thread_id = info->info.create_thread.thread_id; + out->info.create_thread.tls_address = info->info.create_thread.tls_address; + } + break; + case ams::svc::DebugEvent_ExitProcess: + { + out->info.exit_process.reason = info->info.exit_process.reason; + } + break; + case ams::svc::DebugEvent_ExitThread: + { + out->info.exit_thread.reason = info->info.exit_thread.reason; + } + break; + case ams::svc::DebugEvent_Exception: + { + out->info.exception.type = info->info.exception.exception_type; + out->info.exception.address = info->info.exception.exception_address; + + switch (info->info.exception.exception_type) { + case ams::svc::DebugException_UndefinedInstruction: + { + MESOSPHERE_ASSERT(info->info.exception.exception_data_count == 1); + /* Only save the instruction if the caller is not force debug prod. */ + if (this->IsForceDebugProd()) { + out->info.exception.specific.undefined_instruction.insn = 0; + } else { + out->info.exception.specific.undefined_instruction.insn = info->info.exception.exception_data[0]; + } + } + break; + case ams::svc::DebugException_BreakPoint: + { + MESOSPHERE_ASSERT(info->info.exception.exception_data_count == 1); + out->info.exception.specific.break_point.type = static_cast<ams::svc::BreakPointType>(info->info.exception.exception_data[0]); + out->info.exception.specific.break_point.address = 0; + } + break; + case ams::svc::DebugException_UserBreak: + { + MESOSPHERE_ASSERT(info->info.exception.exception_data_count == 3); + out->info.exception.specific.user_break.break_reason = static_cast<ams::svc::BreakReason>(info->info.exception.exception_data[0]); + out->info.exception.specific.user_break.address = info->info.exception.exception_data[1]; + out->info.exception.specific.user_break.size = info->info.exception.exception_data[2]; + } + break; + case ams::svc::DebugException_DebuggerBreak: + { + /* TODO: How does this work with non-4 cpu count? */ + static_assert(cpu::NumCores <= 4); + + MESOSPHERE_ASSERT(info->info.exception.exception_data_count == cpu::NumCores); + out->info.exception.specific.debugger_break.active_thread_ids[0] = info->info.exception.exception_data[0]; + out->info.exception.specific.debugger_break.active_thread_ids[1] = info->info.exception.exception_data[1]; + out->info.exception.specific.debugger_break.active_thread_ids[2] = info->info.exception.exception_data[2]; + out->info.exception.specific.debugger_break.active_thread_ids[3] = info->info.exception.exception_data[3]; + } + break; + case ams::svc::DebugException_UndefinedSystemCall: + { + MESOSPHERE_ASSERT(info->info.exception.exception_data_count == 1); + out->info.exception.specific.undefined_system_call.id = info->info.exception.exception_data[0]; + } + break; + default: + { + /* ... */ + } + break; + } + } + break; + } + + R_SUCCEED(); + } + + Result KDebugBase::GetDebugEventInfo(ams::svc::lp64::DebugEventInfo *out) { + R_RETURN(this->GetDebugEventInfoImpl(out)); + } + + Result KDebugBase::GetDebugEventInfo(ams::svc::ilp32::DebugEventInfo *out) { + R_RETURN(this->GetDebugEventInfoImpl(out)); + } + + void KDebugBase::Finalize() { + /* Perform base finalization. */ + KSynchronizationObject::Finalize(); + + /* Perform post-synchronization finalization. */ + this->OnFinalizeSynchronizationObject(); + } + + void KDebugBase::OnFinalizeSynchronizationObject() { + /* Detach from our process, if we have one. */ + if (this->IsAttached() && this->OpenProcess()) { + /* Close the process when we're done with it. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + + /* Get the process pointer. */ + KProcess * const process = this->GetProcessUnsafe(); + + /* Lock both ourselves and the target process. */ + KScopedLightLock state_lk(process->GetStateLock()); + KScopedLightLock list_lk(process->GetListLock()); + KScopedLightLock this_lk(m_lock); + + /* Check that we're still attached. */ + if (m_is_attached) { + KScopedSchedulerLock sl; + + /* Detach ourselves from the process. */ + process->ClearDebugObject(m_old_process_state); + + /* Release all threads. */ + const bool resume = (process->GetState() != KProcess::State_Crashed); + { + auto end = process->GetThreadList().end(); + for (auto it = process->GetThreadList().begin(); it != end; ++it) { + #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) + /* Clear the thread's single-step state. */ + it->ClearHardwareSingleStep(); + #endif + + if (resume) { + /* If the process isn't crashed, resume threads. */ + it->Resume(KThread::SuspendType_Debug); + } else { + /* Otherwise, suspend them. */ + it->RequestSuspend(KThread::SuspendType_Debug); + } + } + } + + /* Note we're now unattached. */ + m_is_attached = false; + + /* Close the initial reference opened to our process. */ + this->CloseProcess(); + } + } + + /* Free any pending events. */ + { + KScopedSchedulerLock sl; + + while (!m_event_info_list.empty()) { + KEventInfo *info = std::addressof(m_event_info_list.front()); + m_event_info_list.pop_front(); + KEventInfo::Free(info); + } + } + } + + bool KDebugBase::IsSignaled() const { + bool empty; + { + KScopedSchedulerLock sl; + + empty = m_event_info_list.empty(); + } + + return !empty || !m_is_attached || this->GetProcessUnsafe()->IsTerminated(); + } + + Result KDebugBase::ProcessDebugEvent(ams::svc::DebugEvent event, const uintptr_t *params, size_t num_params) { + /* Get the current process. */ + KProcess *process = GetCurrentProcessPointer(); + + /* If the event is CreateThread and we've already attached, there's nothing to do. */ + if (event == ams::svc::DebugEvent_CreateThread) { + R_SUCCEED_IF(GetCurrentThread().IsAttachedToDebugger()); + } + + while (true) { + /* Lock the process and the scheduler. */ + KScopedLightLock state_lk(process->GetStateLock()); + KScopedLightLock list_lk(process->GetListLock()); + KScopedSchedulerLock sl; + + /* If the current thread is terminating, we can't process an event. */ + R_SUCCEED_IF(GetCurrentThread().IsTerminationRequested()); + + /* Get the debug object. If we have none, there's nothing to process. */ + KDebugBase *debug = GetDebugObject(process); + R_SUCCEED_IF(debug == nullptr); + + /* If the event is an exception and we don't have exception events enabled, we can't handle the event. */ + if (event == ams::svc::DebugEvent_Exception && (debug->m_continue_flags & ams::svc::ContinueFlag_EnableExceptionEvent) == 0) { + GetCurrentThread().SetDebugExceptionResult(ResultSuccess()); + R_THROW(svc::ResultNotHandled()); + } + + /* If the current thread is suspended, retry. */ + if (GetCurrentThread().IsSuspended()) { + continue; + } + + /* Suspend all the process's threads. */ + { + auto end = process->GetThreadList().end(); + for (auto it = process->GetThreadList().begin(); it != end; ++it) { + it->RequestSuspend(KThread::SuspendType_Debug); + } + } + + /* Push the event. */ + debug->PushDebugEvent(event, params, num_params); + debug->NotifyAvailable(); + + /* Set the process as breaked. */ + process->SetDebugBreak(); + + /* If the event is an exception, set the result and clear single step. */ + if (event == ams::svc::DebugEvent_Exception) { + GetCurrentThread().SetDebugExceptionResult(ResultSuccess()); + } + + /* Exit our retry loop. */ + break; + } + + /* If the event is an exception, get the exception result. */ + if (event == ams::svc::DebugEvent_Exception) { + /* Lock the scheduler. */ + KScopedSchedulerLock sl; + + /* If the thread is terminating, we can't process the exception. */ + R_UNLESS(!GetCurrentThread().IsTerminationRequested(), svc::ResultStopProcessingException()); + + /* Get the debug object. */ + if (KDebugBase *debug = GetDebugObject(process); debug != nullptr) { + /* If we have one, check the debug exception. */ + R_RETURN(GetCurrentThread().GetDebugExceptionResult()); + } else { + /* We don't have a debug object, so stop processing the exception. */ + R_THROW(svc::ResultStopProcessingException()); + } + } + + R_SUCCEED(); + } + + Result KDebugBase::OnDebugEvent(ams::svc::DebugEvent event, const uintptr_t *params, size_t num_params) { + if (KProcess *process = GetCurrentProcessPointer(); process != nullptr && process->IsAttachedToDebugger()) { + R_RETURN(ProcessDebugEvent(event, params, num_params)); + } + R_SUCCEED(); + } + + Result KDebugBase::OnExitProcess(KProcess *process) { + MESOSPHERE_ASSERT(process != nullptr); + + /* Check if we're attached to a debugger. */ + if (process->IsAttachedToDebugger()) { + /* If we are, lock the scheduler. */ + KScopedSchedulerLock sl; + + /* Push the event. */ + if (KDebugBase *debug = GetDebugObject(process); debug != nullptr) { + const uintptr_t params[1] = { static_cast<uintptr_t>(ams::svc::ProcessExitReason_ExitProcess) }; + debug->PushDebugEvent(ams::svc::DebugEvent_ExitProcess, params, util::size(params)); + debug->NotifyAvailable(); + } + } + + R_SUCCEED(); + } + + Result KDebugBase::OnTerminateProcess(KProcess *process) { + MESOSPHERE_ASSERT(process != nullptr); + + /* Check if we're attached to a debugger. */ + if (process->IsAttachedToDebugger()) { + /* If we are, lock the scheduler. */ + KScopedSchedulerLock sl; + + /* Push the event. */ + if (KDebugBase *debug = GetDebugObject(process); debug != nullptr) { + const uintptr_t params[1] = { static_cast<uintptr_t>(ams::svc::ProcessExitReason_TerminateProcess) }; + debug->PushDebugEvent(ams::svc::DebugEvent_ExitProcess, params, util::size(params)); + debug->NotifyAvailable(); + } + } + + R_SUCCEED(); + } + + Result KDebugBase::OnExitThread(KThread *thread) { + MESOSPHERE_ASSERT(thread != nullptr); + + /* Check if we're attached to a debugger. */ + if (KProcess *process = thread->GetOwnerProcess(); process != nullptr && process->IsAttachedToDebugger()) { + /* If we are, submit the event. */ + const uintptr_t params[2] = { thread->GetId(), static_cast<uintptr_t>(thread->IsTerminationRequested() ? ams::svc::ThreadExitReason_TerminateThread : ams::svc::ThreadExitReason_ExitThread) }; + R_TRY(OnDebugEvent(ams::svc::DebugEvent_ExitThread, params, util::size(params))); + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_device_address_space.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_device_address_space.cpp new file mode 100644 index 00000000..c275dc9c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_device_address_space.cpp @@ -0,0 +1,142 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + /* Static initializer. */ + void KDeviceAddressSpace::Initialize() { + /* This just forwards to the device page table manager. */ + KDevicePageTable::Initialize(); + } + + /* Member functions. */ + Result KDeviceAddressSpace::Initialize(u64 address, u64 size) { + MESOSPHERE_ASSERT_THIS(); + + /* Initialize the device page table. */ + R_TRY(m_table.Initialize(address, size)); + + /* Set member variables. */ + m_space_address = address; + m_space_size = size; + m_is_initialized = true; + + R_SUCCEED(); + } + + void KDeviceAddressSpace::Finalize() { + MESOSPHERE_ASSERT_THIS(); + + /* Finalize the table. */ + m_table.Finalize(); + } + + Result KDeviceAddressSpace::Attach(ams::svc::DeviceName device_name) { + /* Lock the address space. */ + KScopedLightLock lk(m_lock); + + /* Attach. */ + R_RETURN(m_table.Attach(device_name, m_space_address, m_space_size)); + } + + Result KDeviceAddressSpace::Detach(ams::svc::DeviceName device_name) { + /* Lock the address space. */ + KScopedLightLock lk(m_lock); + + /* Detach. */ + R_RETURN(m_table.Detach(device_name)); + } + + Result KDeviceAddressSpace::Map(KProcessPageTable *page_table, KProcessAddress process_address, size_t size, u64 device_address, u32 option, bool is_aligned) { + /* Check that the address falls within the space. */ + R_UNLESS((m_space_address <= device_address && device_address + size - 1 <= m_space_address + m_space_size - 1), svc::ResultInvalidCurrentMemory()); + + /* Decode the option. */ + const util::BitPack32 option_pack = { option }; + const auto device_perm = option_pack.Get<ams::svc::MapDeviceAddressSpaceOption::Permission>(); + const auto flags = option_pack.Get<ams::svc::MapDeviceAddressSpaceOption::Flags>(); + const auto reserved = option_pack.Get<ams::svc::MapDeviceAddressSpaceOption::Reserved>(); + + /* Validate the option. */ + /* TODO: It is likely that this check for flags == none is only on NX board. */ + R_UNLESS(flags == ams::svc::MapDeviceAddressSpaceFlag_None, svc::ResultInvalidEnumValue()); + R_UNLESS(reserved == 0, svc::ResultInvalidEnumValue()); + + /* Lock the address space. */ + KScopedLightLock lk(m_lock); + + /* Lock the page table to prevent concurrent device mapping operations. */ + KScopedLightLock pt_lk = page_table->AcquireDeviceMapLock(); + + /* Lock the pages. */ + bool is_io{}; + R_TRY(page_table->LockForMapDeviceAddressSpace(std::addressof(is_io), process_address, size, ConvertToKMemoryPermission(device_perm), is_aligned, true)); + + /* Ensure that if we fail, we don't keep unmapped pages locked. */ + ON_RESULT_FAILURE { MESOSPHERE_R_ABORT_UNLESS(page_table->UnlockForDeviceAddressSpace(process_address, size)); }; + + /* Check that the io status is allowable. */ + if (is_io) { + R_UNLESS((flags & ams::svc::MapDeviceAddressSpaceFlag_NotIoRegister) == 0, svc::ResultInvalidCombination()); + } + + /* Map the pages. */ + { + /* Perform the mapping. */ + R_TRY(m_table.Map(page_table, process_address, size, device_address, device_perm, is_aligned, is_io)); + + /* Ensure that we unmap the pages if we fail to update the protections. */ + /* NOTE: Nintendo does not check the result of this unmap call. */ + ON_RESULT_FAILURE { m_table.Unmap(device_address, size); }; + + /* Update the protections in accordance with how much we mapped. */ + R_TRY(page_table->UnlockForDeviceAddressSpacePartialMap(process_address, size)); + } + + /* We succeeded. */ + R_SUCCEED(); + } + + Result KDeviceAddressSpace::Unmap(KProcessPageTable *page_table, KProcessAddress process_address, size_t size, u64 device_address) { + /* Check that the address falls within the space. */ + R_UNLESS((m_space_address <= device_address && device_address + size - 1 <= m_space_address + m_space_size - 1), svc::ResultInvalidCurrentMemory()); + + /* Lock the address space. */ + KScopedLightLock lk(m_lock); + + /* Lock the page table to prevent concurrent device mapping operations. */ + KScopedLightLock pt_lk = page_table->AcquireDeviceMapLock(); + + /* Lock the pages. */ + R_TRY(page_table->LockForUnmapDeviceAddressSpace(process_address, size, true)); + + /* Unmap the pages. */ + { + /* If we fail to unmap, we want to do a partial unlock. */ + ON_RESULT_FAILURE { MESOSPHERE_R_ABORT_UNLESS(page_table->UnlockForDeviceAddressSpacePartialMap(process_address, size)); }; + + /* Perform the unmap. */ + R_TRY(m_table.Unmap(page_table, process_address, size, device_address)); + } + + /* Unlock the pages. */ + MESOSPHERE_R_ABORT_UNLESS(page_table->UnlockForDeviceAddressSpace(process_address, size)); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_dpc_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_dpc_manager.cpp new file mode 100644 index 00000000..9d2218e2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_dpc_manager.cpp @@ -0,0 +1,200 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + class KDpcTask { + private: + static constinit inline KLightLock s_req_lock; + static constinit inline KLightLock s_lock; + static constinit inline KLightConditionVariable s_cond_var{util::ConstantInitialize}; + static constinit inline u64 s_core_mask; + static constinit inline KDpcTask *s_task; + private: + static bool HasRequest(s32 core_id) { + return (s_core_mask & (1ull << core_id)) != 0; + } + + static void SetRequest(s32 core_id) { + s_core_mask |= (1ull << core_id); + } + + static void ClearRequest(s32 core_id) { + s_core_mask &= ~(1ull << core_id); + } + public: + virtual void DoTask() { /* ... */ } + + static void Request(KDpcTask *task) { + KScopedLightLock rlk(s_req_lock); + + /* Acquire the requested task. */ + MESOSPHERE_ABORT_UNLESS(s_task == nullptr); + s_task = task; + { + KScopedLightLock lk(s_lock); + MESOSPHERE_ABORT_UNLESS(s_core_mask == 0); + + for (auto core = 0; core < static_cast<s32>(cpu::NumCores); ++core) { + SetRequest(core); + } + + s_cond_var.Broadcast(); + + while (s_core_mask != 0) { + s_cond_var.Wait(std::addressof(s_lock), -1ll); + } + } + s_task = nullptr; + } + + static void WaitForRequest() { + /* Wait for a request to come in. */ + const auto core_id = GetCurrentCoreId(); + KScopedLightLock lk(s_lock); + while (!HasRequest(core_id)) { + s_cond_var.Wait(std::addressof(s_lock), -1ll); + } + } + + static bool TimedWaitForRequest(s64 timeout) { + /* Wait for a request to come in. */ + const auto core_id = GetCurrentCoreId(); + KScopedLightLock lk(s_lock); + while (!HasRequest(core_id)) { + s_cond_var.Wait(std::addressof(s_lock), timeout); + if (KHardwareTimer::GetTick() >= timeout) { + return false; + } + } + return true; + } + + static void HandleRequest() { + /* Perform the request. */ + s_task->DoTask(); + + /* Clear the request. */ + const auto core_id = GetCurrentCoreId(); + KScopedLightLock lk(s_lock); + ClearRequest(core_id); + if (s_core_mask == 0) { + s_cond_var.Broadcast(); + } + } + + }; + + /* Convenience definitions. */ + constexpr s32 DpcManagerThreadPriority = 3; + constexpr s64 DpcManagerTimeout = ams::svc::Tick(TimeSpan::FromMilliSeconds(10)); + + /* Globals. */ + s64 g_preemption_priorities[cpu::NumCores]; + + /* Manager thread functions. */ + void DpcManagerNormalThreadFunction(uintptr_t arg) { + /* Input argument goes unused. */ + MESOSPHERE_UNUSED(arg); + + /* Forever wait and service requests. */ + while (true) { + KDpcTask::WaitForRequest(); + KDpcTask::HandleRequest(); + } + } + + void DpcManagerPreemptionThreadFunction(uintptr_t arg) { + /* Input argument goes unused. */ + MESOSPHERE_UNUSED(arg); + + /* Forever wait and service requests, rotating the scheduled queue every 10 ms. */ + s64 timeout = KHardwareTimer::GetTick() + DpcManagerTimeout; + while (true) { + if (KDpcTask::TimedWaitForRequest(timeout)) { + KDpcTask::HandleRequest(); + } else { + /* Rotate the scheduler queue for each core. */ + KScopedSchedulerLock lk; + + for (size_t core_id = 0; core_id < cpu::NumCores; core_id++) { + if (const s32 priority = g_preemption_priorities[core_id]; priority > DpcManagerThreadPriority) { + KScheduler::RotateScheduledQueue(static_cast<s32>(core_id), priority); + } + } + + /* Update our next timeout. */ + timeout = KHardwareTimer::GetTick() + DpcManagerTimeout; + } + } + } + + } + + void KDpcManager::Initialize(s32 core_id, s32 priority) { + /* Reserve a thread from the system limit. */ + MESOSPHERE_ABORT_UNLESS(Kernel::GetSystemResourceLimit().Reserve(ams::svc::LimitableResource_ThreadCountMax, 1)); + + /* Create a new thread. */ + KThread *new_thread = KThread::Create(); + MESOSPHERE_ABORT_UNLESS(new_thread != nullptr); + + /* Launch the new thread. */ + g_preemption_priorities[core_id] = priority; + if (core_id == cpu::NumCores - 1) { + MESOSPHERE_R_ABORT_UNLESS(KThread::InitializeKernelThread(new_thread, DpcManagerPreemptionThreadFunction, 0, DpcManagerThreadPriority, core_id)); + } else { + MESOSPHERE_R_ABORT_UNLESS(KThread::InitializeKernelThread(new_thread, DpcManagerNormalThreadFunction, 0, DpcManagerThreadPriority, core_id)); + } + + /* Register the new thread. */ + KThread::Register(new_thread); + + /* Run the thread. */ + new_thread->Run(); + } + + void KDpcManager::HandleDpc() { + MESOSPHERE_ASSERT(!KInterruptManager::AreInterruptsEnabled()); + MESOSPHERE_ASSERT(!KScheduler::IsSchedulerLockedByCurrentThread()); + + /* Get reference to the current thread. */ + KThread &cur_thread = GetCurrentThread(); + + /* Enable interrupts, temporarily. */ + KScopedInterruptEnable ei; + + /* If the thread is scheduled for termination, exit the thread. */ + if (cur_thread.IsTerminationRequested()) { + cur_thread.Exit(); + __builtin_unreachable(); + } + + /* We may also need to destroy any closed objects. */ + cur_thread.DestroyClosedObjects(); + } + + void KDpcManager::Sync() { + MESOSPHERE_ASSERT(!KScheduler::IsSchedulerLockedByCurrentThread()); + + KDpcTask dummy_task; + KDpcTask::Request(std::addressof(dummy_task)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_dump_object.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_dump_object.cpp new file mode 100644 index 00000000..0a3e2995 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_dump_object.cpp @@ -0,0 +1,915 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::KDumpObject { + + namespace { + + constexpr const char * const ThreadStates[] = { + [KThread::ThreadState_Initialized] = "Initialized", + [KThread::ThreadState_Waiting] = "Waiting", + [KThread::ThreadState_Runnable] = "Runnable", + [KThread::ThreadState_Terminated] = "Terminated", + }; + + void DumpThread(KThread *thread) { + if (KProcess *process = thread->GetOwnerProcess(); process != nullptr) { + MESOSPHERE_RELEASE_LOG("Thread ID=%5lu pid=%3lu %-11s Pri=%2d %-11s KernelStack=%4zu/%4zu Run=%d Ideal=%d (%d) Affinity=%016lx (%016lx)\n", + thread->GetId(), process->GetId(), process->GetName(), thread->GetPriority(), ThreadStates[thread->GetState()], + thread->GetKernelStackUsage(), PageSize, thread->GetActiveCore(), thread->GetIdealVirtualCore(), thread->GetIdealPhysicalCore(), + thread->GetVirtualAffinityMask(), thread->GetAffinityMask().GetAffinityMask()); + + MESOSPHERE_RELEASE_LOG(" State: 0x%04x Suspend: 0x%04x Dpc: 0x%x\n", thread->GetRawState(), thread->GetSuspendFlags(), thread->GetDpc()); + + MESOSPHERE_RELEASE_LOG(" TLS: %p (%p)\n", GetVoidPointer(thread->GetThreadLocalRegionAddress()), thread->GetThreadLocalRegionHeapAddress()); + } else { + MESOSPHERE_RELEASE_LOG("Thread ID=%5lu pid=%3d %-11s Pri=%2d %-11s KernelStack=%4zu/%4zu Run=%d Ideal=%d (%d) Affinity=%016lx (%016lx)\n", + thread->GetId(), -1, "(kernel)", thread->GetPriority(), ThreadStates[thread->GetState()], + thread->GetKernelStackUsage(), PageSize, thread->GetActiveCore(), thread->GetIdealVirtualCore(), thread->GetIdealPhysicalCore(), + thread->GetVirtualAffinityMask(), thread->GetAffinityMask().GetAffinityMask()); + } + } + + void DumpThreadCallStack(KThread *thread) { + if (KProcess *process = thread->GetOwnerProcess(); process != nullptr) { + MESOSPHERE_RELEASE_LOG("Thread ID=%5lu pid=%3lu %-11s Pri=%2d %-11s KernelStack=%4zu/%4zu\n", + thread->GetId(), process->GetId(), process->GetName(), thread->GetPriority(), ThreadStates[thread->GetState()], thread->GetKernelStackUsage(), PageSize); + + KDebug::PrintRegister(thread); + KDebug::PrintBacktrace(thread); + } else { + MESOSPHERE_RELEASE_LOG("Thread ID=%5lu pid=%3d %-11s Pri=%2d %-11s KernelStack=%4zu/%4zu\n", + thread->GetId(), -1, "(kernel)", thread->GetPriority(), ThreadStates[thread->GetState()], thread->GetKernelStackUsage(), PageSize); + } + } + + void DumpHandle(const KProcess::ListAccessor &accessor, KProcess *process) { + MESOSPHERE_RELEASE_LOG("Process ID=%lu (%s)\n", process->GetId(), process->GetName()); + + const auto end = accessor.end(); + const auto &handle_table = process->GetHandleTable(); + const size_t max_handles = handle_table.GetTableSize(); + for (size_t i = 0; i < max_handles; ++i) { + /* Get the object + handle. */ + ams::svc::Handle handle = ams::svc::InvalidHandle; + KScopedAutoObject obj = handle_table.GetObjectByIndex(std::addressof(handle), i); + if (obj.IsNotNull()) { + if (auto *target = obj->DynamicCast<KServerSession *>(); target != nullptr) { + MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s Client=%p\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName(), std::addressof(target->GetParent()->GetClientSession())); + target->Dump(); + } else if (auto *target = obj->DynamicCast<KClientSession *>(); target != nullptr) { + MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s Server=%p\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName(), std::addressof(target->GetParent()->GetServerSession())); + } else if (auto *target = obj->DynamicCast<KThread *>(); target != nullptr) { + KProcess *target_owner = target->GetOwnerProcess(); + const s32 owner_pid = target_owner != nullptr ? static_cast<s32>(target_owner->GetId()) : -1; + MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s ID=%d PID=%d\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName(), static_cast<s32>(target->GetId()), owner_pid); + } else if (auto *target = obj->DynamicCast<KProcess *>(); target != nullptr) { + MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s ID=%d\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName(), static_cast<s32>(target->GetId())); + } else if (auto *target = obj->DynamicCast<KSharedMemory *>(); target != nullptr) { + /* Find the owner. */ + KProcess *target_owner = nullptr; + for (auto it = accessor.begin(); it != end; ++it) { + if (static_cast<KProcess *>(std::addressof(*it))->GetId() == target->GetOwnerProcessId()) { + target_owner = static_cast<KProcess *>(std::addressof(*it)); + break; + } + } + + MESOSPHERE_ASSERT(target_owner != nullptr); + MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s Size=%zu KB OwnerPID=%d (%s)\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName(), target->GetSize() / 1_KB, static_cast<s32>(target_owner->GetId()), target_owner->GetName()); + } else if (auto *target = obj->DynamicCast<KTransferMemory *>(); target != nullptr) { + KProcess *target_owner = target->GetOwner(); + MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s OwnerPID=%d (%s) OwnerAddress=%lx Size=%zu KB\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName(), static_cast<s32>(target_owner->GetId()), target_owner->GetName(), GetInteger(target->GetSourceAddress()), target->GetSize() / 1_KB); + } else if (auto *target = obj->DynamicCast<KCodeMemory *>(); target != nullptr) { + KProcess *target_owner = target->GetOwner(); + MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s OwnerPID=%d (%s) OwnerAddress=%lx Size=%zu KB\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName(), static_cast<s32>(target_owner->GetId()), target_owner->GetName(), GetInteger(target->GetSourceAddress()), target->GetSize() / 1_KB); + } else if (auto *target = obj->DynamicCast<KInterruptEvent *>(); target != nullptr) { + MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s irq=%d\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName(), target->GetInterruptId()); + } else if (auto *target = obj->DynamicCast<KEvent *>(); target != nullptr) { + MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName()); + } else if (auto *target = obj->DynamicCast<KReadableEvent *>(); target != nullptr) { + if (KEvent *event = target->GetParent(); event != nullptr) { + MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s Parent=%p\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName(), event); + } else { + MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName()); + } + } else { + MESOSPHERE_RELEASE_LOG("Handle %08x Obj=%p Ref=%d Type=%s\n", handle, obj.GetPointerUnsafe(), obj->GetReferenceCount() - 1, obj->GetTypeName()); + } + + if (auto *sync = obj->DynamicCast<KSynchronizationObject *>(); sync != nullptr) { + sync->DumpWaiters(); + } + } + } + + MESOSPHERE_RELEASE_LOG("%zu(max %zu)/%zu used.\n", handle_table.GetCount(), max_handles, handle_table.GetTableSize()); + MESOSPHERE_RELEASE_LOG("\n\n"); + } + + void DumpMemory(KProcess *process) { + const auto process_id = process->GetId(); + MESOSPHERE_RELEASE_LOG("Process ID=%3lu (%s)\n", process_id, process->GetName()); + + /* Dump the memory blocks. */ + process->GetPageTable().DumpMemoryBlocks(); + + /* Collect information about memory totals. */ + const size_t code = process->GetPageTable().GetCodeSize(); + const size_t code_data = process->GetPageTable().GetCodeDataSize(); + const size_t alias_code = process->GetPageTable().GetAliasCodeSize(); + const size_t alias_code_data = process->GetPageTable().GetAliasCodeDataSize(); + const size_t normal = process->GetPageTable().GetNormalMemorySize(); + const size_t main_stack = process->GetMainStackSize(); + + size_t shared = 0; + { + KSharedMemory::ListAccessor accessor; + const auto end = accessor.end(); + for (auto it = accessor.begin(); it != end; ++it) { + KSharedMemory *shared_mem = static_cast<KSharedMemory *>(std::addressof(*it)); + if (shared_mem->GetOwnerProcessId() == process_id) { + shared += shared_mem->GetSize(); + } + } + } + + /* Dump the totals. */ + MESOSPHERE_RELEASE_LOG("---\n"); + MESOSPHERE_RELEASE_LOG("Code %8zu KB\n", code / 1_KB); + MESOSPHERE_RELEASE_LOG("CodeData %8zu KB\n", code_data / 1_KB); + MESOSPHERE_RELEASE_LOG("AliasCode %8zu KB\n", alias_code / 1_KB); + MESOSPHERE_RELEASE_LOG("AliasCodeData %8zu KB\n", alias_code_data / 1_KB); + MESOSPHERE_RELEASE_LOG("Heap %8zu KB\n", normal / 1_KB); + MESOSPHERE_RELEASE_LOG("SharedMemory %8zu KB\n", shared / 1_KB); + MESOSPHERE_RELEASE_LOG("InitialStack %8zu KB\n", main_stack / 1_KB); + MESOSPHERE_RELEASE_LOG("---\n"); + MESOSPHERE_RELEASE_LOG("TOTAL %8zu KB\n", (code + code_data + alias_code + alias_code_data + normal + main_stack + shared) / 1_KB); + MESOSPHERE_RELEASE_LOG("\n\n"); + } + + void DumpPageTable(KProcess *process) { + MESOSPHERE_RELEASE_LOG("Process ID=%3lu (%s)\n", process->GetId(), process->GetName()); + process->GetPageTable().DumpPageTable(); + MESOSPHERE_RELEASE_LOG("\n\n"); + } + + void DumpProcess(KProcess *process) { + MESOSPHERE_RELEASE_LOG("Process ID=%3lu index=%3zu State=%d (%s)\n", process->GetId(), process->GetSlabIndex(), process->GetState(), process->GetName()); + } + + void DumpPort(const KProcess::ListAccessor &accessor, KProcess *process) { + MESOSPHERE_RELEASE_LOG("Dump Port Process ID=%lu (%s)\n", process->GetId(), process->GetName()); + + const auto end = accessor.end(); + const auto &handle_table = process->GetHandleTable(); + const size_t max_handles = handle_table.GetTableSize(); + for (size_t i = 0; i < max_handles; ++i) { + /* Get the object + handle. */ + ams::svc::Handle handle = ams::svc::InvalidHandle; + KScopedAutoObject obj = handle_table.GetObjectByIndex(std::addressof(handle), i); + if (obj.IsNull()) { + continue; + } + + /* Process the object as a port. */ + if (auto *server = obj->DynamicCast<KServerPort *>(); server != nullptr) { + const KClientPort *client = std::addressof(server->GetParent()->GetClientPort()); + const uintptr_t port_name = server->GetParent()->GetName(); + + /* Get the port name. */ + char name[9] = {}; + { + /* Find the client port process. */ + KProcess *client_port_process = nullptr; + ON_SCOPE_EXIT { if (client_port_process != nullptr) { client_port_process->Close(); } }; + + { + for (auto it = accessor.begin(); it != end && client_port_process == nullptr; ++it) { + KProcess *cur = static_cast<KProcess *>(std::addressof(*it)); + for (size_t j = 0; j < cur->GetHandleTable().GetTableSize(); ++j) { + ams::svc::Handle cur_h = ams::svc::InvalidHandle; + KScopedAutoObject cur_o = cur->GetHandleTable().GetObjectByIndex(std::addressof(cur_h), j); + if (cur_o.IsNotNull()) { + if (cur_o.GetPointerUnsafe() == client) { + client_port_process = cur; + client_port_process->Open(); + break; + } + } + } + } + } + + /* Read the port name. */ + if (client_port_process != nullptr) { + if (R_FAILED(client_port_process->GetPageTable().CopyMemoryFromLinearToKernel(KProcessAddress(name), 8, port_name, KMemoryState_None, KMemoryState_None, KMemoryPermission_UserRead, KMemoryAttribute_None, KMemoryAttribute_None))) { + std::memset(name, 0, sizeof(name)); + } + for (size_t i = 0; i < 8 && name[i] != 0; i++) { + if (name[i] > 0x7F) { + std::memset(name, 0, sizeof(name)); + break; + } + } + } + } + + MESOSPHERE_RELEASE_LOG("%-9s: Handle %08x Obj=%p Cur=%3d Peak=%3d Max=%3d\n", name, handle, obj.GetPointerUnsafe(), client->GetNumSessions(), client->GetPeakSessions(), client->GetMaxSessions()); + + /* Identify any sessions. */ + { + for (auto it = accessor.begin(); it != end; ++it) { + KProcess *cur = static_cast<KProcess *>(std::addressof(*it)); + for (size_t j = 0; j < cur->GetHandleTable().GetTableSize(); ++j) { + ams::svc::Handle cur_h = ams::svc::InvalidHandle; + KScopedAutoObject cur_o = cur->GetHandleTable().GetObjectByIndex(std::addressof(cur_h), j); + if (cur_o.IsNull()) { + continue; + } + if (auto *session = cur_o->DynamicCast<KClientSession *>(); session != nullptr && session->GetParent()->GetParent() == client) { + MESOSPHERE_RELEASE_LOG(" Client %p Server %p %-12s: PID=%3lu\n", session, std::addressof(session->GetParent()->GetServerSession()), cur->GetName(), cur->GetId()); + } + } + } + } + } + } + } + + ALWAYS_INLINE s64 GetTickOrdered() { + __asm__ __volatile__("" ::: "memory"); + const s64 tick = KHardwareTimer::GetTick(); + __asm__ __volatile__("" ::: "memory"); + return tick; + } + + } + + void DumpThread() { + MESOSPHERE_RELEASE_LOG("Dump Thread\n"); + + { + /* Lock the list. */ + KThread::ListAccessor accessor; + const auto end = accessor.end(); + + /* Dump each thread. */ + for (auto it = accessor.begin(); it != end; ++it) { + DumpThread(static_cast<KThread *>(std::addressof(*it))); + } + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + + void DumpThread(u64 thread_id) { + MESOSPHERE_RELEASE_LOG("Dump Thread\n"); + + { + /* Find and dump the target thread. */ + if (KThread *thread = KThread::GetThreadFromId(thread_id); thread != nullptr) { + ON_SCOPE_EXIT { thread->Close(); }; + DumpThread(thread); + } + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + + void DumpThreadCallStack() { + MESOSPHERE_RELEASE_LOG("Dump Thread\n"); + + { + /* Lock the list. */ + KThread::ListAccessor accessor; + const auto end = accessor.end(); + + /* Dump each thread. */ + for (auto it = accessor.begin(); it != end; ++it) { + DumpThreadCallStack(static_cast<KThread *>(std::addressof(*it))); + } + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + + void DumpThreadCallStack(u64 thread_id) { + MESOSPHERE_RELEASE_LOG("Dump Thread\n"); + + { + /* Find and dump the target thread. */ + if (KThread *thread = KThread::GetThreadFromId(thread_id); thread != nullptr) { + ON_SCOPE_EXIT { thread->Close(); }; + DumpThreadCallStack(thread); + } + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + + void DumpKernelObject() { + MESOSPHERE_LOG("Dump Kernel Object\n"); + + { + /* Static slab heaps. */ + { + #define DUMP_KSLABOBJ(__OBJECT__) \ + MESOSPHERE_RELEASE_LOG(#__OBJECT__ "\n"); \ + MESOSPHERE_RELEASE_LOG(" Cur=%3zu Peak=%3zu Max=%3zu\n", __OBJECT__::GetSlabHeapSize() - __OBJECT__::GetNumRemaining(), __OBJECT__::GetPeakIndex(), __OBJECT__::GetSlabHeapSize()) + + DUMP_KSLABOBJ(KEvent); + DUMP_KSLABOBJ(KInterruptEvent); + DUMP_KSLABOBJ(KProcess); + DUMP_KSLABOBJ(KThread); + DUMP_KSLABOBJ(KPort); + DUMP_KSLABOBJ(KSharedMemory); + DUMP_KSLABOBJ(KTransferMemory); + DUMP_KSLABOBJ(KDeviceAddressSpace); + DUMP_KSLABOBJ(KDebug); + DUMP_KSLABOBJ(KSession); + DUMP_KSLABOBJ(KLightSession); + DUMP_KSLABOBJ(KThreadLocalPage); + DUMP_KSLABOBJ(KObjectName); + DUMP_KSLABOBJ(KEventInfo); + DUMP_KSLABOBJ(KSessionRequest); + DUMP_KSLABOBJ(KResourceLimit); + DUMP_KSLABOBJ(KIoPool); + DUMP_KSLABOBJ(KIoRegion); + + #undef DUMP_KSLABOBJ + + } + MESOSPHERE_RELEASE_LOG("\n"); + + /* Dynamic slab heaps. */ + { + /* Memory block slabs. */ + { + MESOSPHERE_RELEASE_LOG("App Memory Block\n"); + auto &app = Kernel::GetApplicationSystemResource().GetMemoryBlockSlabManager(); + MESOSPHERE_RELEASE_LOG(" Cur=%6zu Peak=%6zu Max=%6zu\n", app.GetUsed(), app.GetPeak(), app.GetCount()); + MESOSPHERE_RELEASE_LOG("Sys Memory Block\n"); + auto &sys = Kernel::GetSystemSystemResource().GetMemoryBlockSlabManager(); + MESOSPHERE_RELEASE_LOG(" Cur=%6zu Peak=%6zu Max=%6zu\n", sys.GetUsed(), sys.GetPeak(), sys.GetCount()); + } + + /* KBlockInfo slab. */ + { + MESOSPHERE_RELEASE_LOG("KBlockInfo\n"); + auto &manager = Kernel::GetSystemSystemResource().GetBlockInfoManager(); + MESOSPHERE_RELEASE_LOG(" Cur=%6zu Peak=%6zu Max=%6zu\n", manager.GetUsed(), manager.GetPeak(), manager.GetCount()); + } + + /* Page Table slab. */ + { + MESOSPHERE_RELEASE_LOG("Page Table\n"); + auto &manager = Kernel::GetSystemSystemResource().GetPageTableManager(); + MESOSPHERE_RELEASE_LOG(" Cur=%6zu Peak=%6zu Max=%6zu\n", manager.GetUsed(), manager.GetPeak(), manager.GetCount()); + } + } + MESOSPHERE_RELEASE_LOG("\n"); + + /* Process resources. */ + { + KProcess::ListAccessor accessor; + + size_t process_pts = 0; + + const auto end = accessor.end(); + for (auto it = accessor.begin(); it != end; ++it) { + KProcess *process = static_cast<KProcess *>(std::addressof(*it)); + + /* Count the number of threads. */ + int threads = 0; + { + KThread::ListAccessor thr_accessor; + const auto thr_end = thr_accessor.end(); + for (auto thr_it = thr_accessor.begin(); thr_it != thr_end; ++thr_it) { + KThread *thread = static_cast<KThread *>(std::addressof(*thr_it)); + if (thread->GetOwnerProcess() == process) { + ++threads; + } + } + } + + /* Count the number of events. */ + int events = 0; + { + KEvent::ListAccessor ev_accessor; + const auto ev_end = ev_accessor.end(); + for (auto ev_it = ev_accessor.begin(); ev_it != ev_end; ++ev_it) { + KEvent *event = static_cast<KEvent *>(std::addressof(*ev_it)); + if (event->GetOwner() == process) { + ++events; + } + } + } + + size_t pts = process->GetPageTable().CountPageTables(); + process_pts += pts; + + MESOSPHERE_RELEASE_LOG("%-12s: PID=%3lu Thread %4d / Event %4d / PageTable %5zu\n", process->GetName(), process->GetId(), threads, events, pts); + if (const auto &system_resource = process->GetSystemResource(); system_resource.IsSecureResource()) { + const auto &secure_resource = static_cast<const KSecureSystemResource &>(system_resource); + + MESOSPHERE_RELEASE_LOG(" System Resource\n"); + MESOSPHERE_RELEASE_LOG(" Cur=%6zu Peak=%6zu Max=%6zu\n", secure_resource.GetDynamicPageManager().GetUsed(), secure_resource.GetDynamicPageManager().GetPeak(), secure_resource.GetDynamicPageManager().GetCount()); + MESOSPHERE_RELEASE_LOG(" Memory Block\n"); + MESOSPHERE_RELEASE_LOG(" Cur=%6zu Peak=%6zu Max=%6zu\n", secure_resource.GetMemoryBlockSlabManager().GetUsed(), secure_resource.GetMemoryBlockSlabManager().GetPeak(), secure_resource.GetMemoryBlockSlabManager().GetCount()); + MESOSPHERE_RELEASE_LOG(" Page Table\n"); + MESOSPHERE_RELEASE_LOG(" Cur=%6zu Peak=%6zu Max=%6zu\n", secure_resource.GetPageTableManager().GetUsed(), secure_resource.GetPageTableManager().GetPeak(), secure_resource.GetPageTableManager().GetCount()); + MESOSPHERE_RELEASE_LOG(" Block Info\n"); + MESOSPHERE_RELEASE_LOG(" Cur=%6zu Peak=%6zu Max=%6zu\n", secure_resource.GetBlockInfoManager().GetUsed(), secure_resource.GetBlockInfoManager().GetPeak(), secure_resource.GetBlockInfoManager().GetCount()); + } + } + + MESOSPHERE_RELEASE_LOG("Process Page Table %zu\n", process_pts); + MESOSPHERE_RELEASE_LOG("Kernel Page Table %zu\n", Kernel::GetKernelPageTable().CountPageTables()); + } + MESOSPHERE_RELEASE_LOG("\n"); + + /* Resource limits. */ + { + auto &sys_rl = Kernel::GetSystemResourceLimit(); + + u64 cur = sys_rl.GetCurrentValue(ams::svc::LimitableResource_PhysicalMemoryMax); + u64 lim = sys_rl.GetLimitValue(ams::svc::LimitableResource_PhysicalMemoryMax); + MESOSPHERE_RELEASE_LOG("System ResourceLimit PhysicalMemory 0x%01x_%08x / 0x%01x_%08x\n", static_cast<u32>(cur >> 32), static_cast<u32>(cur), static_cast<u32>(lim >> 32), static_cast<u32>(lim)); + + cur = sys_rl.GetCurrentValue(ams::svc::LimitableResource_ThreadCountMax); + lim = sys_rl.GetLimitValue(ams::svc::LimitableResource_ThreadCountMax); + MESOSPHERE_RELEASE_LOG("System ResourceLimit Thread %4lu / %4lu\n", cur, lim); + + cur = sys_rl.GetCurrentValue(ams::svc::LimitableResource_EventCountMax); + lim = sys_rl.GetLimitValue(ams::svc::LimitableResource_EventCountMax); + MESOSPHERE_RELEASE_LOG("System ResourceLimit Event %4lu / %4lu\n", cur, lim); + + cur = sys_rl.GetCurrentValue(ams::svc::LimitableResource_TransferMemoryCountMax); + lim = sys_rl.GetLimitValue(ams::svc::LimitableResource_TransferMemoryCountMax); + MESOSPHERE_RELEASE_LOG("System ResourceLimit TransferMemory %4lu / %4lu\n", cur, lim); + + cur = sys_rl.GetCurrentValue(ams::svc::LimitableResource_SessionCountMax); + lim = sys_rl.GetLimitValue(ams::svc::LimitableResource_SessionCountMax); + MESOSPHERE_RELEASE_LOG("System ResourceLimit Session %4lu / %4lu\n", cur, lim); + + { + KResourceLimit::ListAccessor accessor; + + const auto end = accessor.end(); + for (auto it = accessor.begin(); it != end; ++it) { + KResourceLimit *rl = static_cast<KResourceLimit *>(std::addressof(*it)); + cur = rl->GetCurrentValue(ams::svc::LimitableResource_PhysicalMemoryMax); + lim = rl->GetLimitValue(ams::svc::LimitableResource_PhysicalMemoryMax); + MESOSPHERE_RELEASE_LOG("ResourceLimit %zu PhysicalMemory 0x%01x_%08x / 0x%01x_%08x\n", rl->GetSlabIndex(), static_cast<u32>(cur >> 32), static_cast<u32>(cur), static_cast<u32>(lim >> 32), static_cast<u32>(lim)); + } + } + } + MESOSPHERE_RELEASE_LOG("\n"); + + /* Memory Manager. */ + { + auto &mm = Kernel::GetMemoryManager(); + u64 max = mm.GetSize(); + u64 cur = max - mm.GetFreeSize(); + MESOSPHERE_RELEASE_LOG("Kernel Heap Size 0x%01x_%08x / 0x%01x_%08x\n", static_cast<u32>(cur >> 32), static_cast<u32>(cur), static_cast<u32>(max >> 32), static_cast<u32>(max)); + MESOSPHERE_RELEASE_LOG("\n"); + + max = mm.GetSize(KMemoryManager::Pool_Application); + cur = max - mm.GetFreeSize(KMemoryManager::Pool_Application); + MESOSPHERE_RELEASE_LOG("Application 0x%01x_%08x / 0x%01x_%08x\n", static_cast<u32>(cur >> 32), static_cast<u32>(cur), static_cast<u32>(max >> 32), static_cast<u32>(max)); + mm.DumpFreeList(KMemoryManager::Pool_Application); + MESOSPHERE_RELEASE_LOG("\n"); + + max = mm.GetSize(KMemoryManager::Pool_Applet); + cur = max - mm.GetFreeSize(KMemoryManager::Pool_Applet); + MESOSPHERE_RELEASE_LOG("Applet 0x%01x_%08x / 0x%01x_%08x\n", static_cast<u32>(cur >> 32), static_cast<u32>(cur), static_cast<u32>(max >> 32), static_cast<u32>(max)); + mm.DumpFreeList(KMemoryManager::Pool_Applet); + MESOSPHERE_RELEASE_LOG("\n"); + + max = mm.GetSize(KMemoryManager::Pool_System); + cur = max - mm.GetFreeSize(KMemoryManager::Pool_System); + MESOSPHERE_RELEASE_LOG("System 0x%01x_%08x / 0x%01x_%08x\n", static_cast<u32>(cur >> 32), static_cast<u32>(cur), static_cast<u32>(max >> 32), static_cast<u32>(max)); + mm.DumpFreeList(KMemoryManager::Pool_System); + MESOSPHERE_RELEASE_LOG("\n"); + + max = mm.GetSize(KMemoryManager::Pool_SystemNonSecure); + cur = max - mm.GetFreeSize(KMemoryManager::Pool_SystemNonSecure); + MESOSPHERE_RELEASE_LOG("SystemNonSecure 0x%01x_%08x / 0x%01x_%08x\n", static_cast<u32>(cur >> 32), static_cast<u32>(cur), static_cast<u32>(max >> 32), static_cast<u32>(max)); + mm.DumpFreeList(KMemoryManager::Pool_SystemNonSecure); + MESOSPHERE_RELEASE_LOG("\n"); + } + MESOSPHERE_RELEASE_LOG("\n"); + + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + + void DumpHandle() { + MESOSPHERE_RELEASE_LOG("Dump Handle\n"); + + { + /* Lock the list. */ + KProcess::ListAccessor accessor; + const auto end = accessor.end(); + + /* Dump each process. */ + for (auto it = accessor.begin(); it != end; ++it) { + DumpHandle(accessor, static_cast<KProcess *>(std::addressof(*it))); + } + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + + void DumpHandle(u64 process_id) { + MESOSPHERE_RELEASE_LOG("Dump Handle\n"); + + { + /* Find and dump the target process. */ + if (KProcess *process = KProcess::GetProcessFromId(process_id); process != nullptr) { + ON_SCOPE_EXIT { process->Close(); }; + + /* Lock the list. */ + KProcess::ListAccessor accessor; + DumpHandle(accessor, process); + } + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + + void DumpKernelMemory() { + MESOSPHERE_RELEASE_LOG("Dump Kernel Memory Info\n"); + + { + Kernel::GetKernelPageTable().DumpMemoryBlocks(); + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + + void DumpMemory() { + MESOSPHERE_RELEASE_LOG("Dump Memory Info\n"); + + { + /* Lock the list. */ + KProcess::ListAccessor accessor; + const auto end = accessor.end(); + + /* Dump each process. */ + for (auto it = accessor.begin(); it != end; ++it) { + DumpMemory(static_cast<KProcess *>(std::addressof(*it))); + } + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + + void DumpMemory(u64 process_id) { + MESOSPHERE_RELEASE_LOG("Dump Memory Info\n"); + + { + /* Find and dump the target process. */ + if (KProcess *process = KProcess::GetProcessFromId(process_id); process != nullptr) { + ON_SCOPE_EXIT { process->Close(); }; + DumpMemory(process); + } + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + + void DumpProcess() { + MESOSPHERE_RELEASE_LOG("Dump Process\n"); + + { + /* Lock the list. */ + KProcess::ListAccessor accessor; + const auto end = accessor.end(); + + /* Dump each process. */ + for (auto it = accessor.begin(); it != end; ++it) { + DumpProcess(static_cast<KProcess *>(std::addressof(*it))); + } + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + + void DumpKernelPageTable() { + MESOSPHERE_RELEASE_LOG("Dump Kernel PageTable\n"); + + { + Kernel::GetKernelPageTable().DumpPageTable(); + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + + void DumpPageTable() { + MESOSPHERE_RELEASE_LOG("Dump Process\n"); + + { + /* Lock the list. */ + KProcess::ListAccessor accessor; + const auto end = accessor.end(); + + /* Dump each process. */ + for (auto it = accessor.begin(); it != end; ++it) { + DumpPageTable(static_cast<KProcess *>(std::addressof(*it))); + } + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + + void DumpPageTable(u64 process_id) { + MESOSPHERE_RELEASE_LOG("Dump PageTable\n"); + + { + /* Find and dump the target process. */ + if (KProcess *process = KProcess::GetProcessFromId(process_id); process != nullptr) { + ON_SCOPE_EXIT { process->Close(); }; + DumpPageTable(process); + } + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + + void DumpKernelCpuUtilization() { + MESOSPHERE_RELEASE_LOG("Dump Kernel Cpu Utilization\n"); + + constexpr size_t MaxObjects = 64; + { + /* Create tracking arrays. */ + KAutoObject *objects[MaxObjects]; + u32 cpu_time[MaxObjects]; + + s64 start_tick; + size_t i, n; + KDpcManager::Sync(); + { + /* Lock the thread list. */ + KThread::ListAccessor accessor; + + /* Begin tracking. */ + start_tick = GetTickOrdered(); + + /* Iterate, finding kernel threads. */ + const auto end = accessor.end(); + i = 0; + for (auto it = accessor.begin(); it != end; ++it) { + KThread *thread = static_cast<KThread *>(std::addressof(*it)); + if (KProcess *process = thread->GetOwnerProcess(); process == nullptr) { + if (AMS_LIKELY(i < MaxObjects)) { + if (AMS_LIKELY(thread->Open())) { + cpu_time[i] = thread->GetCpuTime(); + objects[i] = thread; + ++i; + } + } + } + } + + /* Keep track of how many kernel threads we found. */ + n = i; + } + + /* Wait one second. */ + const s64 timeout = KHardwareTimer::GetTick() + ams::svc::Tick(TimeSpan::FromSeconds(1)); + GetCurrentThread().Sleep(timeout); + KDpcManager::Sync(); + + /* Update our metrics. */ + for (i = 0; i < n; ++i) { + KThread *thread = static_cast<KThread *>(objects[i]); + cpu_time[i] = thread->GetCpuTime() - cpu_time[i]; + } + + /* End tracking. */ + const s64 end_tick = GetTickOrdered(); + + /* Log thread utilization. */ + for (i = 0; i < n; ++i) { + KThread *thread = static_cast<KThread *>(objects[i]); + const s64 t = static_cast<u64>(cpu_time[i]) * 1000 / (end_tick - start_tick); + + MESOSPHERE_RELEASE_LOG("tid=%3lu (kernel) %3lu.%lu%% pri=%2d af=%lx\n", thread->GetId(), t / 10, t % 10, thread->GetPriority(), thread->GetAffinityMask().GetAffinityMask()); + } + + /* Close all objects. */ + for (i = 0; i < n; ++i) { + objects[i]->Close(); + } + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + + void DumpCpuUtilization() { + MESOSPHERE_RELEASE_LOG("Dump Cpu Utilization\n"); + + /* NOTE: Nintendo uses 0x40 as maximum here, but the KProcess slabheap has 0x50 entries. */ + /* We have the stack space, so there's no reason not to allow logging all processes. */ + constexpr size_t MaxObjects = 0x50; + { + /* Create tracking arrays. */ + KAutoObject *objects[MaxObjects]; + u32 cpu_time[MaxObjects]; + + s64 start_tick; + size_t i, n; + KDpcManager::Sync(); + { + /* Lock the process list. */ + KProcess::ListAccessor accessor; + + /* Begin tracking. */ + start_tick = GetTickOrdered(); + + /* Iterate, finding processes. */ + const auto end = accessor.end(); + i = 0; + for (auto it = accessor.begin(); it != end; ++it) { + KProcess *process = static_cast<KProcess *>(std::addressof(*it)); + if (AMS_LIKELY(i < MaxObjects)) { + if (AMS_LIKELY(process->Open())) { + cpu_time[i] = process->GetCpuTime(); + objects[i] = process; + ++i; + } + } + } + + /* Keep track of how many processes we found. */ + n = i; + } + + /* Wait one second. */ + const s64 timeout = KHardwareTimer::GetTick() + ams::svc::Tick(TimeSpan::FromSeconds(1)); + GetCurrentThread().Sleep(timeout); + KDpcManager::Sync(); + + /* Update our metrics. */ + for (i = 0; i < n; ++i) { + KProcess *process = static_cast<KProcess *>(objects[i]); + cpu_time[i] = process->GetCpuTime() - cpu_time[i]; + } + + /* End tracking. */ + const s64 end_tick = GetTickOrdered(); + + /* Log process utilization. */ + for (i = 0; i < n; ++i) { + KProcess *process = static_cast<KProcess *>(objects[i]); + const s64 t = static_cast<u64>(cpu_time[i]) * 1000 / (end_tick - start_tick); + + MESOSPHERE_RELEASE_LOG("pid=%3lu %-11s %3lu.%lu%%\n", process->GetId(), process->GetName(), t / 10, t % 10); + } + + /* Close all objects. */ + for (i = 0; i < n; ++i) { + objects[i]->Close(); + } + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + + void DumpCpuUtilization(u64 process_id) { + MESOSPHERE_RELEASE_LOG("Dump Cpu Utilization\n"); + + constexpr size_t MaxObjects = 64; + { + /* Create tracking arrays. */ + KAutoObject *objects[MaxObjects]; + u32 cpu_time[MaxObjects]; + + s64 start_tick; + size_t i, n; + KDpcManager::Sync(); + { + /* Lock the thread list. */ + KThread::ListAccessor accessor; + + /* Begin tracking. */ + start_tick = GetTickOrdered(); + + /* Iterate, finding process threads. */ + const auto end = accessor.end(); + i = 0; + for (auto it = accessor.begin(); it != end; ++it) { + KThread *thread = static_cast<KThread *>(std::addressof(*it)); + if (KProcess *process = thread->GetOwnerProcess(); process != nullptr && process->GetId() == process_id) { + if (AMS_LIKELY(i < MaxObjects)) { + if (AMS_LIKELY(thread->Open())) { + cpu_time[i] = thread->GetCpuTime(); + objects[i] = thread; + ++i; + } + } + } + } + + /* Keep track of how many process threads we found. */ + n = i; + } + + /* Wait one second. */ + const s64 timeout = KHardwareTimer::GetTick() + ams::svc::Tick(TimeSpan::FromSeconds(1)); + GetCurrentThread().Sleep(timeout); + KDpcManager::Sync(); + + /* Update our metrics. */ + for (i = 0; i < n; ++i) { + KThread *thread = static_cast<KThread *>(objects[i]); + cpu_time[i] = thread->GetCpuTime() - cpu_time[i]; + } + + /* End tracking. */ + const s64 end_tick = GetTickOrdered(); + + /* Log thread utilization. */ + for (i = 0; i < n; ++i) { + KThread *thread = static_cast<KThread *>(objects[i]); + KProcess *process = thread->GetOwnerProcess(); + const s64 t = static_cast<u64>(cpu_time[i]) * 1000 / (end_tick - start_tick); + + MESOSPHERE_RELEASE_LOG("tid=%3lu pid=%3lu %-11s %3lu.%lu%% pri=%2d af=%lx\n", thread->GetId(), process->GetId(), process->GetName(), t / 10, t % 10, thread->GetPriority(), thread->GetAffinityMask().GetAffinityMask()); + } + + /* Close all objects. */ + for (i = 0; i < n; ++i) { + objects[i]->Close(); + } + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + + void DumpProcess(u64 process_id) { + MESOSPHERE_RELEASE_LOG("Dump Process\n"); + + { + /* Find and dump the target process. */ + if (KProcess *process = KProcess::GetProcessFromId(process_id); process != nullptr) { + ON_SCOPE_EXIT { process->Close(); }; + DumpProcess(process); + } + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + + void DumpPort() { + MESOSPHERE_RELEASE_LOG("Dump Port\n"); + + { + /* Lock the list. */ + KProcess::ListAccessor accessor; + const auto end = accessor.end(); + + /* Dump each process. */ + for (auto it = accessor.begin(); it != end; ++it) { + DumpPort(accessor, static_cast<KProcess *>(std::addressof(*it))); + } + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + + void DumpPort(u64 process_id) { + MESOSPHERE_RELEASE_LOG("Dump Port\n"); + + { + /* Find and dump the target process. */ + if (KProcess *process = KProcess::GetProcessFromId(process_id); process != nullptr) { + ON_SCOPE_EXIT { process->Close(); }; + + /* Lock the list. */ + KProcess::ListAccessor accessor; + DumpPort(accessor, process); + } + } + + MESOSPHERE_RELEASE_LOG("\n"); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_event.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_event.cpp new file mode 100644 index 00000000..5400557e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_event.cpp @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + void KEvent::Initialize() { + MESOSPHERE_ASSERT_THIS(); + + /* Create our readable event. */ + KAutoObject::Create<KReadableEvent>(std::addressof(m_readable_event)); + + /* Initialize our readable event. */ + m_readable_event.Initialize(this); + + /* Set our owner process. */ + m_owner = GetCurrentProcessPointer(); + m_owner->Open(); + + /* Mark initialized. */ + m_initialized = true; + } + + void KEvent::Finalize() { + MESOSPHERE_ASSERT_THIS(); + } + + Result KEvent::Signal() { + KScopedSchedulerLock sl; + + R_SUCCEED_IF(m_readable_event_destroyed); + + R_RETURN(m_readable_event.Signal()); + } + + Result KEvent::Clear() { + KScopedSchedulerLock sl; + + R_SUCCEED_IF(m_readable_event_destroyed); + + R_RETURN(m_readable_event.Clear()); + } + + void KEvent::PostDestroy(uintptr_t arg) { + /* Release the event count resource the owner process holds. */ + KProcess *owner = reinterpret_cast<KProcess *>(arg); + owner->ReleaseResource(ams::svc::LimitableResource_EventCountMax, 1); + owner->Close(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_handle_table.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_handle_table.cpp new file mode 100644 index 00000000..421df928 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_handle_table.cpp @@ -0,0 +1,159 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + Result KHandleTable::Finalize() { + MESOSPHERE_ASSERT_THIS(); + + /* Get the table and clear our record of it. */ + u16 saved_table_size = 0; + { + KScopedDisableDispatch dd; + KScopedSpinLock lk(m_lock); + + std::swap(m_table_size, saved_table_size); + } + + /* Close and free all entries. */ + for (size_t i = 0; i < saved_table_size; i++) { + if (KAutoObject *obj = m_objects[i]; obj != nullptr) { + obj->Close(); + } + } + + R_SUCCEED(); + } + + bool KHandleTable::Remove(ams::svc::Handle handle) { + MESOSPHERE_ASSERT_THIS(); + + /* Don't allow removal of a pseudo-handle. */ + if (AMS_UNLIKELY(ams::svc::IsPseudoHandle(handle))) { + return false; + } + + /* Handles must not have reserved bits set. */ + const auto handle_pack = GetHandleBitPack(handle); + if (AMS_UNLIKELY(handle_pack.Get<HandleReserved>() != 0)) { + return false; + } + + /* Find the object and free the entry. */ + KAutoObject *obj = nullptr; + { + KScopedDisableDispatch dd; + KScopedSpinLock lk(m_lock); + + if (AMS_LIKELY(this->IsValidHandle(handle))) { + const auto index = handle_pack.Get<HandleIndex>(); + + obj = m_objects[index]; + this->FreeEntry(index); + } else { + return false; + } + } + + /* Close the object. */ + obj->Close(); + return true; + } + + Result KHandleTable::Add(ams::svc::Handle *out_handle, KAutoObject *obj) { + MESOSPHERE_ASSERT_THIS(); + KScopedDisableDispatch dd; + KScopedSpinLock lk(m_lock); + + /* Never exceed our capacity. */ + R_UNLESS(m_count < m_table_size, svc::ResultOutOfHandles()); + + /* Allocate entry, set output handle. */ + { + const auto linear_id = this->AllocateLinearId(); + const auto index = this->AllocateEntry(); + + m_entry_infos[index].linear_id = linear_id; + m_objects[index] = obj; + + obj->Open(); + + *out_handle = EncodeHandle(index, linear_id); + } + + R_SUCCEED(); + } + + Result KHandleTable::Reserve(ams::svc::Handle *out_handle) { + MESOSPHERE_ASSERT_THIS(); + KScopedDisableDispatch dd; + KScopedSpinLock lk(m_lock); + + /* Never exceed our capacity. */ + R_UNLESS(m_count < m_table_size, svc::ResultOutOfHandles()); + + *out_handle = EncodeHandle(this->AllocateEntry(), this->AllocateLinearId()); + R_SUCCEED(); + } + + void KHandleTable::Unreserve(ams::svc::Handle handle) { + MESOSPHERE_ASSERT_THIS(); + KScopedDisableDispatch dd; + KScopedSpinLock lk(m_lock); + + /* Unpack the handle. */ + const auto handle_pack = GetHandleBitPack(handle); + const auto index = handle_pack.Get<HandleIndex>(); + const auto linear_id = handle_pack.Get<HandleLinearId>(); + const auto reserved = handle_pack.Get<HandleReserved>(); + MESOSPHERE_ASSERT(reserved == 0); + MESOSPHERE_ASSERT(linear_id != 0); + MESOSPHERE_UNUSED(linear_id, reserved); + + if (AMS_LIKELY(index < m_table_size)) { + /* NOTE: This code does not check the linear id. */ + MESOSPHERE_ASSERT(m_objects[index] == nullptr); + this->FreeEntry(index); + } + } + + void KHandleTable::Register(ams::svc::Handle handle, KAutoObject *obj) { + MESOSPHERE_ASSERT_THIS(); + KScopedDisableDispatch dd; + KScopedSpinLock lk(m_lock); + + /* Unpack the handle. */ + const auto handle_pack = GetHandleBitPack(handle); + const auto index = handle_pack.Get<HandleIndex>(); + const auto linear_id = handle_pack.Get<HandleLinearId>(); + const auto reserved = handle_pack.Get<HandleReserved>(); + MESOSPHERE_ASSERT(reserved == 0); + MESOSPHERE_ASSERT(linear_id != 0); + MESOSPHERE_UNUSED(reserved); + + if (AMS_LIKELY(index < m_table_size)) { + /* Set the entry. */ + MESOSPHERE_ASSERT(m_objects[index] == nullptr); + + m_entry_infos[index].linear_id = linear_id; + m_objects[index] = obj; + + obj->Open(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_initial_process_reader.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_initial_process_reader.cpp new file mode 100644 index 00000000..5d9164ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_initial_process_reader.cpp @@ -0,0 +1,331 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + struct BlzSegmentFlags { + using Offset = util::BitPack16::Field<0, 12, u32>; + using Size = util::BitPack16::Field<Offset::Next, 4, u32>; + }; + + NOINLINE void BlzUncompress(void *_end) { + /* Parse the footer, endian agnostic. */ + static_assert(sizeof(u32) == 4); + static_assert(sizeof(u16) == 2); + static_assert(sizeof(u8) == 1); + + u8 *end = static_cast<u8 *>(_end); + const u32 total_size = (end[-12] << 0) | (end[-11] << 8) | (end[-10] << 16) | (end[- 9] << 24); + const u32 footer_size = (end[- 8] << 0) | (end[- 7] << 8) | (end[- 6] << 16) | (end[- 5] << 24); + const u32 additional_size = (end[- 4] << 0) | (end[- 3] << 8) | (end[- 2] << 16) | (end[- 1] << 24); + + /* Prepare to decompress. */ + u8 *cmp_start = end - total_size; + u32 cmp_ofs = total_size - footer_size; + u32 out_ofs = total_size + additional_size; + + /* Decompress. */ + while (out_ofs) { + u8 control = cmp_start[--cmp_ofs]; + + /* Each bit in the control byte is a flag indicating compressed or not compressed. */ + for (size_t i = 0; i < 8 && out_ofs; ++i, control <<= 1) { + if (control & 0x80) { + /* NOTE: Nintendo does not check if it's possible to decompress. */ + /* As such, we will leave the following as a debug assertion, and not a release assertion. */ + MESOSPHERE_AUDIT(cmp_ofs >= sizeof(u16)); + cmp_ofs -= sizeof(u16); + + /* Extract segment bounds. */ + const util::BitPack16 seg_flags{static_cast<u16>((cmp_start[cmp_ofs] << 0) | (cmp_start[cmp_ofs + 1] << 8))}; + const u32 seg_ofs = seg_flags.Get<BlzSegmentFlags::Offset>() + 3; + const u32 seg_size = std::min(seg_flags.Get<BlzSegmentFlags::Size>() + 3, out_ofs); + MESOSPHERE_AUDIT(out_ofs + seg_ofs <= total_size + additional_size); + + /* Copy the data. */ + out_ofs -= seg_size; + for (size_t j = 0; j < seg_size; j++) { + cmp_start[out_ofs + j] = cmp_start[out_ofs + seg_ofs + j]; + } + } else { + /* NOTE: Nintendo does not check if it's possible to copy. */ + /* As such, we will leave the following as a debug assertion, and not a release assertion. */ + MESOSPHERE_AUDIT(cmp_ofs >= sizeof(u8)); + cmp_start[--out_ofs] = cmp_start[--cmp_ofs]; + } + } + } + } + + NOINLINE void LoadInitialProcessSegment(const KPageGroup &pg, size_t seg_offset, size_t seg_size, size_t binary_size, KVirtualAddress data, bool compressed) { + /* Save the original binary extents, for later use. */ + const KPhysicalAddress binary_phys = KMemoryLayout::GetLinearPhysicalAddress(data); + + /* Create a page group representing the segment. */ + KPageGroup segment_pg(Kernel::GetSystemSystemResource().GetBlockInfoManagerPointer()); + MESOSPHERE_R_ABORT_UNLESS(pg.CopyRangeTo(segment_pg, seg_offset, util::AlignUp(seg_size, PageSize))); + + /* Setup the new page group's memory so that we can load the segment. */ + { + KVirtualAddress last_block = Null<KVirtualAddress>; + KVirtualAddress last_data = Null<KVirtualAddress>; + size_t last_copy_size = 0; + size_t last_clear_size = 0; + size_t remaining_copy_size = binary_size; + for (const auto &block : segment_pg) { + /* Get the current block extents. */ + const auto block_addr = block.GetAddress(); + const size_t block_size = block.GetSize(); + if (remaining_copy_size > 0) { + /* Determine if we need to copy anything. */ + const size_t cur_size = std::min<size_t>(block_size, remaining_copy_size); + + /* NOTE: The first block may potentially overlap the binary we want to copy to. */ + /* Consider e.g. the case where the overall compressed image has size 0x40000, seg_offset is 0x30000, and binary_size is > 0x20000. */ + /* Suppose too that data points, say, 0x18000 into the compressed image. */ + /* Suppose finally that we simply naively copy in order. */ + /* The first iteration of this loop will perform an 0x10000 copy from image+0x18000 to image + 0x30000 (as there is no overlap). */ + /* The second iteration will perform a copy from image+0x28000 to <allocated pages>. */ + /* However, the first copy will have trashed the data in the second copy. */ + /* Thus, we must copy the first block after-the-fact to avoid potentially trashing data in the overlap case. */ + /* It is guaranteed by pre-condition that only the very first block can overlap with the physical binary, so we can simply memmove it at the end. */ + if (last_block != Null<KVirtualAddress>) { + /* This is guaranteed by pre-condition, but for ease of debugging, check for no overlap. */ + MESOSPHERE_ASSERT(!util::HasOverlap(GetInteger(binary_phys), binary_size, GetInteger(block_addr), cur_size)); + MESOSPHERE_UNUSED(binary_phys); + + /* We need to copy. */ + std::memcpy(GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(block_addr)), GetVoidPointer(data), cur_size); + + /* If we need to, clear past where we're copying. */ + if (cur_size != block_size) { + std::memset(GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(block_addr + cur_size)), 0, block_size - cur_size); + } + + /* Advance. */ + remaining_copy_size -= cur_size; + data += cur_size; + } else { + /* Save the first block, which may potentially overlap, so that we can copy it later. */ + last_block = KMemoryLayout::GetLinearVirtualAddress(block_addr); + last_data = data; + last_copy_size = cur_size; + last_clear_size = block_size - cur_size; + + /* Advance. */ + remaining_copy_size -= cur_size; + data += cur_size; + } + } else { + /* We don't have data to copy, so we should just clear the pages. */ + std::memset(GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(block_addr)), 0, block_size); + } + } + + /* Handle a last block. */ + if (last_copy_size != 0) { + if (last_block != last_data) { + std::memmove(GetVoidPointer(last_block), GetVoidPointer(last_data), last_copy_size); + } + if (last_clear_size != 0) { + std::memset(GetVoidPointer(last_block + last_copy_size), 0, last_clear_size); + } + } + } + + /* If compressed, uncompress the data. */ + if (compressed) { + /* Get the temporary region. */ + const auto &temp_region = KMemoryLayout::GetTempRegion(); + MESOSPHERE_ABORT_UNLESS(temp_region.GetEndAddress() != 0); + + /* Map the process's memory into the temporary region. */ + KProcessAddress temp_address = Null<KProcessAddress>; + MESOSPHERE_R_ABORT_UNLESS(Kernel::GetKernelPageTable().MapPageGroup(std::addressof(temp_address), segment_pg, temp_region.GetAddress(), temp_region.GetSize() / PageSize, KMemoryState_Kernel, KMemoryPermission_KernelReadWrite)); + ON_SCOPE_EXIT { MESOSPHERE_R_ABORT_UNLESS(Kernel::GetKernelPageTable().UnmapPageGroup(temp_address, segment_pg, KMemoryState_Kernel)); }; + + /* Uncompress the data. */ + BlzUncompress(GetVoidPointer(temp_address + binary_size)); + } + } + + } + + Result KInitialProcessReader::MakeCreateProcessParameter(ams::svc::CreateProcessParameter *out, bool enable_aslr) const { + /* Get and validate addresses/sizes. */ + const uintptr_t rx_address = m_kip_header.GetRxAddress(); + const size_t rx_size = m_kip_header.GetRxSize(); + const uintptr_t ro_address = m_kip_header.GetRoAddress(); + const size_t ro_size = m_kip_header.GetRoSize(); + const uintptr_t rw_address = m_kip_header.GetRwAddress(); + const size_t rw_size = m_kip_header.GetRwSize(); + const uintptr_t bss_address = m_kip_header.GetBssAddress(); + const size_t bss_size = m_kip_header.GetBssSize(); + R_UNLESS(util::IsAligned(rx_address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(ro_address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(rw_address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(rx_address <= rx_address + util::AlignUp(rx_size, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(ro_address <= ro_address + util::AlignUp(ro_size, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(rw_address <= rw_address + util::AlignUp(rw_size, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(bss_address <= bss_address + util::AlignUp(bss_size, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(rx_address + util::AlignUp(rx_size, PageSize) <= ro_address, svc::ResultInvalidAddress()); + R_UNLESS(ro_address + util::AlignUp(ro_size, PageSize) <= rw_address, svc::ResultInvalidAddress()); + R_UNLESS(rw_address + rw_size <= bss_address, svc::ResultInvalidAddress()); + + /* Validate the address space. */ + if (this->Is64BitAddressSpace()) { + R_UNLESS(this->Is64Bit(), svc::ResultInvalidCombination()); + } + + const uintptr_t start_address = rx_address; + const uintptr_t end_address = bss_size > 0 ? bss_address + bss_size : rw_address + rw_size; + MESOSPHERE_ABORT_UNLESS(start_address == 0); + + /* Default fields in parameter to zero. */ + *out = {}; + + /* Set fields in parameter. */ + out->code_address = 0; + out->code_num_pages = util::AlignUp(end_address - start_address, PageSize) / PageSize; + out->program_id = m_kip_header.GetProgramId(); + out->version = m_kip_header.GetVersion(); + out->flags = 0; + out->reslimit = ams::svc::InvalidHandle; + out->system_resource_num_pages = 0; + + /* Copy name field. */ + m_kip_header.GetName(out->name, sizeof(out->name)); + + /* Apply other flags. */ + if (this->Is64Bit()) { + out->flags |= ams::svc::CreateProcessFlag_Is64Bit; + } + if (this->Is64BitAddressSpace()) { + out->flags |= (GetTargetFirmware() >= TargetFirmware_2_0_0) ? ams::svc::CreateProcessFlag_AddressSpace64Bit : ams::svc::CreateProcessFlag_AddressSpace64BitDeprecated; + } else { + out->flags |= ams::svc::CreateProcessFlag_AddressSpace32Bit; + } + if (enable_aslr) { + out->flags |= ams::svc::CreateProcessFlag_EnableAslr; + } + + /* All initial processes should disable device address space merge. */ + out->flags |= ams::svc::CreateProcessFlag_DisableDeviceAddressSpaceMerge; + + /* Set and check code address. */ + /* NOTE: Even though Nintendo passes a size to GetAddressSpaceStart at other call sites, they pass */ + /* a number of pages here. Even though this is presumably only used for debug assertions, this is */ + /* almost certainly a bug. */ + using ASType = KAddressSpaceInfo::Type; + const ASType as_type = this->Is64BitAddressSpace() ? ((GetTargetFirmware() >= TargetFirmware_2_0_0) ? KAddressSpaceInfo::Type_Map39Bit : KAddressSpaceInfo::Type_MapSmall) : KAddressSpaceInfo::Type_MapSmall; + const uintptr_t map_start = KAddressSpaceInfo::GetAddressSpaceStart(static_cast<ams::svc::CreateProcessFlag>(out->flags), as_type, out->code_num_pages); + const size_t map_size = KAddressSpaceInfo::GetAddressSpaceSize(static_cast<ams::svc::CreateProcessFlag>(out->flags), as_type); + const uintptr_t map_end = map_start + map_size; + out->code_address = map_start + start_address; + MESOSPHERE_ABORT_UNLESS((out->code_address / PageSize) + out->code_num_pages <= (map_end / PageSize)); + + /* Apply ASLR, if needed. */ + if (enable_aslr) { + const size_t choices = (map_end / KernelAslrAlignment) - (util::AlignUp(out->code_address + out->code_num_pages * PageSize, KernelAslrAlignment) / KernelAslrAlignment); + out->code_address += KSystemControl::GenerateRandomRange(0, choices) * KernelAslrAlignment; + } + + R_SUCCEED(); + } + + void KInitialProcessReader::Load(const KPageGroup &pg, KVirtualAddress data) const { + /* Prepare to layout the data. */ + const KVirtualAddress rx_data = data; + const KVirtualAddress ro_data = rx_data + m_kip_header.GetRxCompressedSize(); + const KVirtualAddress rw_data = ro_data + m_kip_header.GetRoCompressedSize(); + const size_t rx_size = m_kip_header.GetRxSize(); + const size_t ro_size = m_kip_header.GetRoSize(); + const size_t rw_size = m_kip_header.GetRwSize(); + + /* If necessary, setup bss. */ + if (const size_t bss_size = m_kip_header.GetBssSize(); bss_size > 0) { + /* Determine how many additional pages are needed for bss. */ + const u64 rw_end = util::AlignUp<u64>(m_kip_header.GetRwAddress() + m_kip_header.GetRwSize(), PageSize); + const u64 bss_end = util::AlignUp<u64>(m_kip_header.GetBssAddress() + m_kip_header.GetBssSize(), PageSize); + if (rw_end != bss_end) { + /* Find the pages corresponding to bss. */ + size_t cur_offset = 0; + size_t remaining_size = bss_end - rw_end; + size_t bss_offset = rw_end - m_kip_header.GetRxAddress(); + for (auto it = pg.begin(); it != pg.end() && remaining_size > 0; ++it) { + /* Get the current size. */ + const size_t cur_size = it->GetSize(); + + /* Determine if the offset is in range. */ + const size_t rel_diff = bss_offset - cur_offset; + const bool is_before = cur_offset <= bss_offset; + cur_offset += cur_size; + if (is_before && bss_offset < cur_offset) { + /* It is, so clear the bss range. */ + const size_t block_size = std::min<size_t>(cur_size - rel_diff, remaining_size); + std::memset(GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(it->GetAddress() + rel_diff)), 0, block_size); + + /* Advance. */ + cur_offset = bss_offset + block_size; + remaining_size -= block_size; + bss_offset += block_size; + } + } + } + } + + /* Load .rwdata. */ + LoadInitialProcessSegment(pg, m_kip_header.GetRwAddress() - m_kip_header.GetRxAddress(), rw_size, m_kip_header.GetRwCompressedSize(), rw_data, m_kip_header.IsRwCompressed()); + + /* Load .rodata. */ + LoadInitialProcessSegment(pg, m_kip_header.GetRoAddress() - m_kip_header.GetRxAddress(), ro_size, m_kip_header.GetRoCompressedSize(), ro_data, m_kip_header.IsRoCompressed()); + + /* Load .text. */ + LoadInitialProcessSegment(pg, m_kip_header.GetRxAddress() - m_kip_header.GetRxAddress(), rx_size, m_kip_header.GetRxCompressedSize(), rx_data, m_kip_header.IsRxCompressed()); + } + + Result KInitialProcessReader::SetMemoryPermissions(KProcessPageTable &page_table, const ams::svc::CreateProcessParameter ¶ms) const { + const size_t rx_size = m_kip_header.GetRxSize(); + const size_t ro_size = m_kip_header.GetRoSize(); + const size_t rw_size = m_kip_header.GetRwSize(); + const size_t bss_size = m_kip_header.GetBssSize(); + + /* Set R-X pages. */ + if (rx_size) { + const uintptr_t start = m_kip_header.GetRxAddress() + params.code_address; + R_TRY(page_table.SetProcessMemoryPermission(start, util::AlignUp(rx_size, PageSize), ams::svc::MemoryPermission_ReadExecute)); + } + + /* Set R-- pages. */ + if (ro_size) { + const uintptr_t start = m_kip_header.GetRoAddress() + params.code_address; + R_TRY(page_table.SetProcessMemoryPermission(start, util::AlignUp(ro_size, PageSize), ams::svc::MemoryPermission_Read)); + } + + /* Set RW- pages. */ + if (rw_size || bss_size) { + const uintptr_t start = (rw_size ? m_kip_header.GetRwAddress() : m_kip_header.GetBssAddress()) + params.code_address; + const uintptr_t end = (bss_size ? m_kip_header.GetBssAddress() + bss_size : m_kip_header.GetRwAddress() + rw_size) + params.code_address; + R_TRY(page_table.SetProcessMemoryPermission(start, util::AlignUp(end - start, PageSize), ams::svc::MemoryPermission_ReadWrite)); + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_interrupt_event.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_interrupt_event.cpp new file mode 100644 index 00000000..60e0755c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_interrupt_event.cpp @@ -0,0 +1,87 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + Result KInterruptEvent::Initialize(int32_t interrupt_name, ams::svc::InterruptType type) { + MESOSPHERE_ASSERT_THIS(); + + /* Verify the interrupt is defined and global. */ + R_UNLESS(Kernel::GetInterruptManager().IsInterruptDefined(interrupt_name), svc::ResultOutOfRange()); + R_UNLESS(Kernel::GetInterruptManager().IsGlobal(interrupt_name), svc::ResultOutOfRange()); + + /* Set interrupt id. */ + m_interrupt_id = interrupt_name; + + /* Set core id. */ + m_core_id = GetCurrentCoreId(); + + /* Initialize readable event base. */ + KReadableEvent::Initialize(nullptr); + + /* Bind ourselves as the handler for our interrupt id. */ + R_TRY(Kernel::GetInterruptManager().BindHandler(this, m_interrupt_id, m_core_id, KInterruptController::PriorityLevel_High, true, type == ams::svc::InterruptType_Level)); + + /* Mark initialized. */ + m_is_initialized = true; + R_SUCCEED(); + } + + void KInterruptEvent::Finalize() { + MESOSPHERE_ASSERT_THIS(); + + /* Unbind ourselves as the handler for our interrupt id. */ + Kernel::GetInterruptManager().UnbindHandler(m_interrupt_id, m_core_id); + + /* Synchronize the unbind on all cores, before proceeding. */ + KDpcManager::Sync(); + + /* Perform inherited finalization. */ + KReadableEvent::Finalize(); + } + + Result KInterruptEvent::Reset() { + MESOSPHERE_ASSERT_THIS(); + + /* Lock the scheduler. */ + KScopedSchedulerLock sl; + + /* Clear the event. */ + R_TRY(KReadableEvent::Reset()); + + /* Clear the interrupt. */ + Kernel::GetInterruptManager().ClearInterrupt(m_interrupt_id, m_core_id); + + R_SUCCEED(); + } + + KInterruptTask *KInterruptEvent::OnInterrupt(s32 interrupt_id) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_UNUSED(interrupt_id); + return this; + } + + void KInterruptEvent::DoTask() { + MESOSPHERE_ASSERT_THIS(); + + /* Lock the scheduler. */ + KScopedSchedulerLock sl; + + /* Signal. */ + this->Signal(); + } +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_interrupt_task_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_interrupt_task_manager.cpp new file mode 100644 index 00000000..fbacf61e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_interrupt_task_manager.cpp @@ -0,0 +1,90 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + void KInterruptTaskManager::TaskQueue::Enqueue(KInterruptTask *task) { + MESOSPHERE_ASSERT(task != m_head); + MESOSPHERE_ASSERT(task != m_tail); + MESOSPHERE_AUDIT(task->GetNextTask() == nullptr); + + /* Insert the task into the queue. */ + if (m_tail != nullptr) { + m_tail->SetNextTask(task); + } else { + m_head = task; + } + + m_tail = task; + + /* Set the next task for auditing. */ + #if defined (MESOSPHERE_BUILD_FOR_AUDITING) + task->SetNextTask(GetDummyInterruptTask()); + #endif + } + + void KInterruptTaskManager::TaskQueue::Dequeue() { + MESOSPHERE_ASSERT(m_head != nullptr); + MESOSPHERE_ASSERT(m_tail != nullptr); + MESOSPHERE_AUDIT(m_tail->GetNextTask() == GetDummyInterruptTask()); + + /* Pop the task from the front of the queue. */ + KInterruptTask *old_head = m_head; + + if (m_head == m_tail) { + m_head = nullptr; + m_tail = nullptr; + } else { + m_head = m_head->GetNextTask(); + } + + #if defined (MESOSPHERE_BUILD_FOR_AUDITING) + old_head->SetNextTask(nullptr); + #else + AMS_UNUSED(old_head); + #endif + } + + void KInterruptTaskManager::EnqueueTask(KInterruptTask *task) { + MESOSPHERE_ASSERT(!KInterruptManager::AreInterruptsEnabled()); + + /* Enqueue the task and signal the scheduler. */ + m_task_queue.Enqueue(task); + Kernel::GetScheduler().SetInterruptTaskRunnable(); + } + + void KInterruptTaskManager::DoTasks() { + /* Execute pending tasks. */ + const s64 start_time = KHardwareTimer::GetTick(); + for (KInterruptTask *task = m_task_queue.GetHead(); task != nullptr; task = m_task_queue.GetHead()) { + /* Dequeue the task. */ + m_task_queue.Dequeue(); + + /* Do the task with interrupts temporarily enabled. */ + { + KScopedInterruptEnable ei; + + task->DoTask(); + } + } + const s64 end_time = KHardwareTimer::GetTick(); + + /* Increment the time we've spent executing. */ + m_cpu_time += end_time - start_time; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_io_pool.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_io_pool.cpp new file mode 100644 index 00000000..869d8aea --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_io_pool.cpp @@ -0,0 +1,140 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + constinit KLightLock g_io_pool_lock; + constinit bool g_pool_used[ams::svc::IoPoolType_Count]; + + struct IoRegionExtents { + KPhysicalAddress address; + size_t size; + }; + + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + + #include "board/nintendo/nx/kern_k_io_pool.board.nintendo_nx.inc" + + #elif defined(AMS_SVC_IO_POOL_NOT_SUPPORTED) + + #include "kern_k_io_pool.unsupported.inc" + + #else + + #error "Unknown context for IoPoolType!" + + #endif + + constexpr bool IsValidIoRegionImpl(ams::svc::IoPoolType pool_type, KPhysicalAddress address, size_t size) { + /* NOTE: It seems likely this depends on pool type, but this isn't confirmable as of now. */ + MESOSPHERE_UNUSED(pool_type); + + /* Check if the address/size falls within any allowable extents. */ + for (const auto &extents : g_io_region_extents) { + if (extents.size != 0 && extents.address <= address && address + size - 1 <= extents.address + extents.size - 1) { + return true; + } + } + + return false; + } + + } + + bool KIoPool::IsValidIoPoolType(ams::svc::IoPoolType pool_type) { + return IsValidIoPoolTypeImpl(pool_type); + } + + Result KIoPool::Initialize(ams::svc::IoPoolType pool_type) { + MESOSPHERE_ASSERT_THIS(); + + /* Register the pool type. */ + { + /* Lock the pool used table. */ + KScopedLightLock lk(g_io_pool_lock); + + /* Check that the pool isn't already used. */ + R_UNLESS(!g_pool_used[pool_type], svc::ResultBusy()); + + /* Set the pool as used. */ + g_pool_used[pool_type] = true; + } + + /* Set our fields. */ + m_pool_type = pool_type; + m_is_initialized = true; + + R_SUCCEED(); + } + + void KIoPool::Finalize() { + MESOSPHERE_ASSERT_THIS(); + + /* Lock the pool used table. */ + KScopedLightLock lk(g_io_pool_lock); + + /* Check that the pool is used. */ + MESOSPHERE_ASSERT(g_pool_used[m_pool_type]); + + /* Set the pool as unused. */ + g_pool_used[m_pool_type] = false; + } + + Result KIoPool::AddIoRegion(KIoRegion *new_region) { + MESOSPHERE_ASSERT_THIS(); + + /* Check that the region is allowed. */ + R_UNLESS(IsValidIoRegionImpl(m_pool_type, new_region->GetAddress(), new_region->GetSize()), svc::ResultInvalidMemoryRegion()); + + /* Lock ourselves. */ + KScopedLightLock lk(m_lock); + + /* Check that the desired range isn't already in our pool. */ + { + /* Get the lowest region with address >= the new region that's already in our tree. */ + auto lowest_after = m_io_region_tree.nfind_key(new_region->GetAddress()); + if (lowest_after != m_io_region_tree.end()) { + R_UNLESS(!lowest_after->Overlaps(new_region->GetAddress(), new_region->GetSize()), svc::ResultBusy()); + } + + /* There is no region with address >= the new region already in our tree, but we also need to check */ + /* for a region with address < the new region already in our tree. */ + if (lowest_after != m_io_region_tree.begin()) { + auto highest_before = --lowest_after; + R_UNLESS(!highest_before->Overlaps(new_region->GetAddress(), new_region->GetSize()), svc::ResultBusy()); + } + } + + /* Add the region to our pool. */ + m_io_region_tree.insert(*new_region); + + R_SUCCEED(); + } + + void KIoPool::RemoveIoRegion(KIoRegion *region) { + MESOSPHERE_ASSERT_THIS(); + + /* Lock ourselves. */ + KScopedLightLock lk(m_lock); + + /* Remove the region from our tree. */ + m_io_region_tree.erase(m_io_region_tree.iterator_to(*region)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_io_pool.unsupported.inc b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_io_pool.unsupported.inc new file mode 100644 index 00000000..816f5b9e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_io_pool.unsupported.inc @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +constexpr IoRegionExtents g_io_region_extents[4] = { + { Null<KPhysicalAddress>, 0 }, + { Null<KPhysicalAddress>, 0 }, + { Null<KPhysicalAddress>, 0 }, + { Null<KPhysicalAddress>, 0 }, +}; + +constexpr bool IsValidIoPoolTypeImpl(ams::svc::IoPoolType pool_type) { + MESOSPHERE_UNUSED(pool_type); + return false; +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_io_region.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_io_region.cpp new file mode 100644 index 00000000..f27d17d1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_io_region.cpp @@ -0,0 +1,99 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + Result KIoRegion::Initialize(KIoPool *pool, KPhysicalAddress phys_addr, size_t size, ams::svc::MemoryMapping mapping, ams::svc::MemoryPermission perm) { + MESOSPHERE_ASSERT_THIS(); + + /* Set fields. */ + m_physical_address = phys_addr; + m_size = size; + m_mapping = mapping; + m_permission = perm; + m_pool = pool; + m_is_mapped = false; + + /* Add ourselves to our pool. */ + R_TRY(m_pool->AddIoRegion(this)); + + /* Open a reference to our pool. */ + m_pool->Open(); + + /* Mark ourselves as initialized. */ + m_is_initialized = true; + + R_SUCCEED(); + } + + void KIoRegion::Finalize() { + /* Remove ourselves from our pool. */ + m_pool->RemoveIoRegion(this); + + /* Close our reference to our pool. */ + m_pool->Close(); + } + + Result KIoRegion::Map(KProcessAddress address, size_t size, ams::svc::MemoryPermission map_perm) { + MESOSPHERE_ASSERT_THIS(); + + /* Check that the desired perm is allowable. */ + R_UNLESS((m_permission | map_perm) == m_permission, svc::ResultInvalidNewMemoryPermission()); + + /* Check that the size is correct. */ + R_UNLESS(size == m_size, svc::ResultInvalidSize()); + + /* Lock ourselves. */ + KScopedLightLock lk(m_lock); + + /* Check that we're not already mapped. */ + R_UNLESS(!m_is_mapped, svc::ResultInvalidState()); + + /* Map ourselves. */ + R_TRY(GetCurrentProcess().GetPageTable().MapIoRegion(address, m_physical_address, size, m_mapping, map_perm)); + + /* Add ourselves to the current process. */ + GetCurrentProcess().AddIoRegion(this); + + /* Note that we're mapped. */ + m_is_mapped = true; + + R_SUCCEED(); + } + + Result KIoRegion::Unmap(KProcessAddress address, size_t size) { + MESOSPHERE_ASSERT_THIS(); + + /* Check that the size is correct. */ + R_UNLESS(size == m_size, svc::ResultInvalidSize()); + + /* Lock ourselves. */ + KScopedLightLock lk(m_lock); + + /* Unmap ourselves. */ + R_TRY(GetCurrentProcess().GetPageTable().UnmapIoRegion(address, m_physical_address, size, m_mapping)); + + /* Remove ourselves from the current process. */ + GetCurrentProcess().RemoveIoRegion(this); + + /* Note that we're unmapped. */ + m_is_mapped = false; + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_light_client_session.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_light_client_session.cpp new file mode 100644 index 00000000..c4c12899 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_light_client_session.cpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + void KLightClientSession::Destroy() { + MESOSPHERE_ASSERT_THIS(); + + m_parent->OnClientClosed(); + } + + void KLightClientSession::OnServerClosed() { + MESOSPHERE_ASSERT_THIS(); + } + + Result KLightClientSession::SendSyncRequest(u32 *data) { + MESOSPHERE_ASSERT_THIS(); + + /* Get the request thread. */ + KThread *cur_thread = GetCurrentThreadPointer(); + + /* Set the light data. */ + cur_thread->SetLightSessionData(data); + + /* Send the request. */ + R_RETURN(m_parent->OnRequest(cur_thread)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_light_condition_variable.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_light_condition_variable.cpp new file mode 100644 index 00000000..25b2b0b9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_light_condition_variable.cpp @@ -0,0 +1,84 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + class ThreadQueueImplForKLightConditionVariable final : public KThreadQueue { + private: + KThread::WaiterList *m_wait_list; + bool m_allow_terminating_thread; + public: + constexpr ThreadQueueImplForKLightConditionVariable(KThread::WaiterList *wl, bool term) : KThreadQueue(), m_wait_list(wl), m_allow_terminating_thread(term) { /* ... */ } + + virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override { + /* Only process waits if we're allowed to. */ + if (svc::ResultTerminationRequested::Includes(wait_result) && m_allow_terminating_thread) { + return; + } + + /* Remove the thread from the waiting thread from the light condition variable. */ + m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread)); + + /* Invoke the base cancel wait handler. */ + KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + } + }; + + } + + void KLightConditionVariable::Wait(KLightLock *lock, s64 timeout, bool allow_terminating_thread) { + /* Create thread queue. */ + KThread *owner = GetCurrentThreadPointer(); + KHardwareTimer *timer; + + ThreadQueueImplForKLightConditionVariable wait_queue(std::addressof(m_wait_list), allow_terminating_thread); + + /* Sleep the thread. */ + { + KScopedSchedulerLockAndSleep lk(std::addressof(timer), owner, timeout); + + if (!allow_terminating_thread && owner->IsTerminationRequested()) { + lk.CancelSleep(); + return; + } + + lock->Unlock(); + + /* Add the thread to the queue. */ + m_wait_list.push_back(*owner); + + /* Begin waiting. */ + wait_queue.SetHardwareTimer(timer); + owner->BeginWait(std::addressof(wait_queue)); + } + + /* Re-acquire the lock. */ + lock->Lock(); + } + + void KLightConditionVariable::Broadcast() { + KScopedSchedulerLock lk; + + /* Signal all threads. */ + for (auto it = m_wait_list.begin(); it != m_wait_list.end(); it = m_wait_list.erase(it)) { + it->EndWait(ResultSuccess()); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_light_lock.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_light_lock.cpp new file mode 100644 index 00000000..f9d928b5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_light_lock.cpp @@ -0,0 +1,96 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + class ThreadQueueImplForKLightLock final : public KThreadQueue { + public: + constexpr ThreadQueueImplForKLightLock() : KThreadQueue() { /* ... */ } + + virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override { + /* Do nothing, waiting to acquire a light lock cannot be canceled. */ + MESOSPHERE_UNUSED(waiting_thread, wait_result, cancel_timer_task); + } + }; + + } + + bool KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) { + KThread *cur_thread = reinterpret_cast<KThread *>(_cur_thread); + ThreadQueueImplForKLightLock wait_queue; + + /* Pend the current thread waiting on the owner thread. */ + { + KScopedSchedulerLock sl; + + /* Ensure we actually have locking to do. */ + if (m_tag.Load<std::memory_order_relaxed>() != _owner) { + return false; + } + + /* Add the current thread as a waiter on the owner. */ + KThread *owner_thread = reinterpret_cast<KThread *>(_owner & ~1ul); + cur_thread->SetAddressKey(reinterpret_cast<uintptr_t>(std::addressof(m_tag))); + owner_thread->AddWaiter(cur_thread); + + /* Begin waiting to hold the lock. */ + cur_thread->BeginWait(std::addressof(wait_queue)); + + if (owner_thread->IsSuspended()) { + owner_thread->ContinueIfHasKernelWaiters(); + } + } + + return true; + } + + void KLightLock::UnlockSlowPath(uintptr_t _cur_thread) { + KThread *owner_thread = reinterpret_cast<KThread *>(_cur_thread); + + /* Unlock. */ + { + KScopedSchedulerLock sl; + + /* Get the next owner. */ + bool has_waiters; + KThread *next_owner = owner_thread->RemoveWaiterByKey(std::addressof(has_waiters), reinterpret_cast<uintptr_t>(std::addressof(m_tag))); + + /* Pass the lock to the next owner. */ + uintptr_t next_tag = 0; + if (next_owner != nullptr) { + next_tag = reinterpret_cast<uintptr_t>(next_owner) | static_cast<uintptr_t>(has_waiters); + + next_owner->EndWait(ResultSuccess()); + + if (next_owner->IsSuspended()) { + next_owner->ContinueIfHasKernelWaiters(); + } + } + + /* We may have unsuspended in the process of acquiring the lock, so we'll re-suspend now if so. */ + if (owner_thread->IsSuspended()) { + owner_thread->TrySuspend(); + } + + /* Write the new tag value. */ + m_tag.Store<std::memory_order_release>(next_tag); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_light_server_session.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_light_server_session.cpp new file mode 100644 index 00000000..7c83f3b8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_light_server_session.cpp @@ -0,0 +1,250 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + constexpr u64 InvalidThreadId = -1ull; + + class ThreadQueueImplForKLightServerSessionRequest final : public KThreadQueue { + private: + KThread::WaiterList *m_wait_list; + public: + constexpr ThreadQueueImplForKLightServerSessionRequest(KThread::WaiterList *wl) : KThreadQueue(), m_wait_list(wl) { /* ... */ } + + virtual void EndWait(KThread *waiting_thread, Result wait_result) override { + /* Remove the thread from our wait list. */ + m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread)); + + /* Invoke the base end wait handler. */ + KThreadQueue::EndWait(waiting_thread, wait_result); + } + + virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override { + /* Remove the thread from our wait list. */ + m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread)); + + /* Invoke the base cancel wait handler. */ + KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + } + }; + + class ThreadQueueImplForKLightServerSessionReceive final : public KThreadQueue { + private: + KThread **m_server_thread; + public: + constexpr ThreadQueueImplForKLightServerSessionReceive(KThread **st) : KThreadQueue(), m_server_thread(st) { /* ... */ } + + virtual void EndWait(KThread *waiting_thread, Result wait_result) override { + /* Clear the server thread. */ + *m_server_thread = nullptr; + + /* Set the waiting thread as not cancelable. */ + waiting_thread->ClearCancellable(); + + /* Invoke the base end wait handler. */ + KThreadQueue::EndWait(waiting_thread, wait_result); + } + + virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override { + /* Clear the server thread. */ + *m_server_thread = nullptr; + + /* Set the waiting thread as not cancelable. */ + waiting_thread->ClearCancellable(); + + /* Invoke the base cancel wait handler. */ + KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + } + }; + + } + + void KLightServerSession::Destroy() { + MESOSPHERE_ASSERT_THIS(); + + this->CleanupRequests(); + + m_parent->OnServerClosed(); + } + + void KLightServerSession::OnClientClosed() { + MESOSPHERE_ASSERT_THIS(); + + this->CleanupRequests(); + } + + Result KLightServerSession::OnRequest(KThread *request_thread) { + MESOSPHERE_ASSERT_THIS(); + + ThreadQueueImplForKLightServerSessionRequest wait_queue(std::addressof(m_request_list)); + + /* Send the request. */ + { + /* Lock the scheduler. */ + KScopedSchedulerLock sl; + + /* Check that the server isn't closed. */ + R_UNLESS(!m_parent->IsServerClosed(), svc::ResultSessionClosed()); + + /* Check that the request thread isn't terminating. */ + R_UNLESS(!request_thread->IsTerminationRequested(), svc::ResultTerminationRequested()); + + /* Add the request thread to our list. */ + m_request_list.push_back(*request_thread); + + /* Begin waiting on the request. */ + request_thread->BeginWait(std::addressof(wait_queue)); + + /* If we have a server thread, end its wait. */ + if (m_server_thread != nullptr) { + m_server_thread->EndWait(ResultSuccess()); + } + } + + /* NOTE: Nintendo returns GetCurrentThread().GetWaitResult() here. */ + /* This is technically incorrect, although it doesn't cause problems in practice */ + /* because this is only ever called with request_thread = GetCurrentThreadPointer(). */ + R_RETURN(request_thread->GetWaitResult()); + } + + Result KLightServerSession::ReplyAndReceive(u32 *data) { + MESOSPHERE_ASSERT_THIS(); + + /* Set the server context. */ + GetCurrentThread().SetLightSessionData(data); + + /* Reply, if we need to. */ + if (data[0] & KLightSession::ReplyFlag) { + KScopedSchedulerLock sl; + + /* Check that we're open. */ + R_UNLESS(!m_parent->IsClientClosed(), svc::ResultSessionClosed()); + R_UNLESS(!m_parent->IsServerClosed(), svc::ResultSessionClosed()); + + /* Check that we have a request to reply to. */ + R_UNLESS(m_current_request != nullptr, svc::ResultInvalidState()); + + /* Check that the server thread id is correct. */ + R_UNLESS(m_server_thread_id == GetCurrentThread().GetId(), svc::ResultInvalidState()); + + /* If we can reply, do so. */ + if (!m_current_request->IsTerminationRequested()) { + std::memcpy(m_current_request->GetLightSessionData(), GetCurrentThread().GetLightSessionData(), KLightSession::DataSize); + m_current_request->EndWait(ResultSuccess()); + } + + /* Close our current request. */ + m_current_request->Close(); + + /* Clear our current request. */ + m_current_request = nullptr; + m_server_thread_id = InvalidThreadId; + } + + /* Close any pending objects before we wait. */ + GetCurrentThread().DestroyClosedObjects(); + + /* Create the wait queue for our receive. */ + ThreadQueueImplForKLightServerSessionReceive wait_queue(std::addressof(m_server_thread)); + + /* Receive. */ + while (true) { + /* Try to receive a request. */ + { + KScopedSchedulerLock sl; + + /* Check that we aren't already receiving. */ + R_UNLESS(m_server_thread == nullptr, svc::ResultInvalidState()); + R_UNLESS(m_server_thread_id == InvalidThreadId, svc::ResultInvalidState()); + + /* Check that we're open. */ + R_UNLESS(!m_parent->IsClientClosed(), svc::ResultSessionClosed()); + R_UNLESS(!m_parent->IsServerClosed(), svc::ResultSessionClosed()); + + /* Check that we're not terminating. */ + R_UNLESS(!GetCurrentThread().IsTerminationRequested(), svc::ResultTerminationRequested()); + + /* If we have a request available, use it. */ + if (auto head = m_request_list.begin(); head != m_request_list.end()) { + /* Set our current request. */ + m_current_request = std::addressof(*head); + m_current_request->Open(); + + /* Set our server thread id. */ + m_server_thread_id = GetCurrentThread().GetId(); + + /* Copy the client request data. */ + std::memcpy(GetCurrentThread().GetLightSessionData(), m_current_request->GetLightSessionData(), KLightSession::DataSize); + + /* We successfully received. */ + R_SUCCEED(); + } + + /* We need to wait for a request to come in. */ + + /* Check if we were cancelled. */ + if (GetCurrentThread().IsWaitCancelled()) { + GetCurrentThread().ClearWaitCancelled(); + R_THROW(svc::ResultCancelled()); + } + + /* Mark ourselves as cancellable. */ + GetCurrentThread().SetCancellable(); + + /* Wait for a request to come in. */ + m_server_thread = GetCurrentThreadPointer(); + GetCurrentThread().BeginWait(std::addressof(wait_queue)); + } + + /* We waited to receive a request; if our wait failed, return the failing result. */ + R_TRY(GetCurrentThread().GetWaitResult()); + } + } + + void KLightServerSession::CleanupRequests() { + /* Cleanup all pending requests. */ + { + KScopedSchedulerLock sl; + + /* Handle the current request. */ + if (m_current_request != nullptr) { + /* Reply to the current request. */ + if (!m_current_request->IsTerminationRequested()) { + m_current_request->EndWait(svc::ResultSessionClosed()); + } + + /* Clear our current request. */ + m_current_request->Close(); + m_current_request = nullptr; + m_server_thread_id = InvalidThreadId; + } + + /* Reply to all other requests. */ + for (auto &thread : m_request_list) { + thread.EndWait(svc::ResultSessionClosed()); + } + + /* Wait up our server thread, if we have one. */ + if (m_server_thread != nullptr) { + m_server_thread->EndWait(svc::ResultSessionClosed()); + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_light_session.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_light_session.cpp new file mode 100644 index 00000000..e0128b1a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_light_session.cpp @@ -0,0 +1,91 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + void KLightSession::Initialize(KClientPort *client_port, uintptr_t name) { + MESOSPHERE_ASSERT_THIS(); + + /* Increment reference count. */ + /* Because reference count is one on creation, this will result */ + /* in a reference count of two. Thus, when both server and client are closed */ + /* this object will be destroyed. */ + this->Open(); + + /* Create our sub sessions. */ + KAutoObject::Create<KLightServerSession>(std::addressof(m_server)); + KAutoObject::Create<KLightClientSession>(std::addressof(m_client)); + + /* Initialize our sub sessions. */ + m_server.Initialize(this); + m_client.Initialize(this); + + /* Set state and name. */ + m_state = State::Normal; + m_name = name; + + /* Set our owner process. */ + m_process = GetCurrentProcessPointer(); + m_process->Open(); + + /* Set our port. */ + m_port = client_port; + if (m_port != nullptr) { + m_port->Open(); + } + + /* Mark initialized. */ + m_initialized = true; + } + + void KLightSession::Finalize() { + if (m_port != nullptr) { + m_port->OnSessionFinalized(); + m_port->Close(); + } + } + + void KLightSession::OnServerClosed() { + MESOSPHERE_ASSERT_THIS(); + + if (m_state == State::Normal) { + m_state = State::ServerClosed; + m_client.OnServerClosed(); + } + + this->Close(); + } + + void KLightSession::OnClientClosed() { + MESOSPHERE_ASSERT_THIS(); + + if (m_state == State::Normal) { + m_state = State::ClientClosed; + m_server.OnClientClosed(); + } + + this->Close(); + } + + void KLightSession::PostDestroy(uintptr_t arg) { + /* Release the session count resource the owner process holds. */ + KProcess *owner = reinterpret_cast<KProcess *>(arg); + owner->ReleaseResource(ams::svc::LimitableResource_SessionCountMax, 1); + owner->Close(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_memory_block_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_memory_block_manager.cpp new file mode 100644 index 00000000..639c047d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_memory_block_manager.cpp @@ -0,0 +1,485 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + constexpr const std::pair<KMemoryState, const char *> MemoryStateNames[] = { + {KMemoryState_Free , "----- Free -----"}, + {KMemoryState_IoMemory , "IoMemory "}, + {KMemoryState_IoRegister , "IoRegister "}, + {KMemoryState_Static , "Static "}, + {KMemoryState_Code , "Code "}, + {KMemoryState_CodeData , "CodeData "}, + {KMemoryState_Normal , "Normal "}, + {KMemoryState_Shared , "Shared "}, + {KMemoryState_AliasCode , "AliasCode "}, + {KMemoryState_AliasCodeData , "AliasCodeData "}, + {KMemoryState_Ipc , "Ipc "}, + {KMemoryState_Stack , "Stack "}, + {KMemoryState_ThreadLocal , "ThreadLocal "}, + {KMemoryState_Transfered , "Transfered "}, + {KMemoryState_SharedTransfered , "SharedTransfered"}, + {KMemoryState_SharedCode , "SharedCode "}, + {KMemoryState_Inaccessible , "Inaccessible "}, + {KMemoryState_NonSecureIpc , "NonSecureIpc "}, + {KMemoryState_NonDeviceIpc , "NonDeviceIpc "}, + {KMemoryState_Kernel , "Kernel "}, + {KMemoryState_GeneratedCode , "GeneratedCode "}, + {KMemoryState_CodeOut , "CodeOut "}, + {KMemoryState_Coverage , "Coverage "}, + }; + + constexpr const char *GetMemoryStateName(KMemoryState state) { + for (size_t i = 0; i < util::size(MemoryStateNames); i++) { + if (std::get<0>(MemoryStateNames[i]) == state) { + return std::get<1>(MemoryStateNames[i]); + } + } + return "Unknown "; + } + + constexpr const char *GetMemoryPermissionString(const KMemoryBlock &block) { + if (block.GetState() == KMemoryState_Free) { + return " "; + } else { + switch (block.GetPermission()) { + case KMemoryPermission_UserReadExecute: + return "r-x"; + case KMemoryPermission_UserRead: + return "r--"; + case KMemoryPermission_UserReadWrite: + return "rw-"; + default: + return "---"; + } + } + } + + void DumpMemoryBlock(const KMemoryBlock &block) { + const char *state = GetMemoryStateName(block.GetState()); + const char *perm = GetMemoryPermissionString(block); + const uintptr_t start = GetInteger(block.GetAddress()); + const uintptr_t end = GetInteger(block.GetLastAddress()); + const size_t kb = block.GetSize() / 1_KB; + + const char l = (block.GetAttribute() & KMemoryAttribute_Locked) ? 'L' : '-'; + const char i = (block.GetAttribute() & KMemoryAttribute_IpcLocked) ? 'I' : '-'; + const char d = (block.GetAttribute() & KMemoryAttribute_DeviceShared) ? 'D' : '-'; + const char u = (block.GetAttribute() & KMemoryAttribute_Uncached) ? 'U' : '-'; + + MESOSPHERE_LOG("0x%10lx - 0x%10lx (%9zu KB) %s %s %c%c%c%c [%d, %d]\n", start, end, kb, perm, state, l, i, d, u, block.GetIpcLockCount(), block.GetDeviceUseCount()); + } + + } + + Result KMemoryBlockManager::Initialize(KProcessAddress st, KProcessAddress nd, KMemoryBlockSlabManager *slab_manager) { + /* Allocate a block to encapsulate the address space, insert it into the tree. */ + KMemoryBlock *start_block = slab_manager->Allocate(); + R_UNLESS(start_block != nullptr, svc::ResultOutOfResource()); + + /* Set our start and end. */ + m_start_address = st; + m_end_address = nd; + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(m_start_address), PageSize)); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(m_end_address), PageSize)); + + /* Initialize and insert the block. */ + start_block->Initialize(m_start_address, (m_end_address - m_start_address) / PageSize, KMemoryState_Free, KMemoryPermission_None, KMemoryAttribute_None); + m_memory_block_tree.insert(*start_block); + + R_SUCCEED(); + } + + void KMemoryBlockManager::Finalize(KMemoryBlockSlabManager *slab_manager) { + /* Erase every block until we have none left. */ + auto it = m_memory_block_tree.begin(); + while (it != m_memory_block_tree.end()) { + KMemoryBlock *block = std::addressof(*it); + it = m_memory_block_tree.erase(it); + slab_manager->Free(block); + } + + MESOSPHERE_ASSERT(m_memory_block_tree.empty()); + } + + bool KMemoryBlockManager::GetRegionForFindFreeArea(KProcessAddress *out_start, KProcessAddress *out_end, KProcessAddress region_start, size_t region_num_pages, size_t num_pages, size_t alignment, size_t offset, size_t guard_pages) { + /* Check that there's room for the pages in the specified region. */ + if (num_pages + 2 * guard_pages > region_num_pages) { + return false; + } + + /* Determine the aligned start of the guarded region. */ + const KProcessAddress guarded_start = region_start + guard_pages * PageSize; + const KProcessAddress aligned_guarded_start = util::AlignDown(GetInteger(guarded_start), alignment); + KProcessAddress aligned_guarded_start_with_offset = aligned_guarded_start + offset; + if (guarded_start > aligned_guarded_start_with_offset) { + if (!util::CanAddWithoutOverflow<uintptr_t>(GetInteger(aligned_guarded_start), alignment)) { + return false; + } + aligned_guarded_start_with_offset += alignment; + } + + /* Determine the aligned end of the guarded region. */ + const KProcessAddress guarded_end = region_start + ((region_num_pages - (num_pages + guard_pages)) * PageSize); + const KProcessAddress aligned_guarded_end = util::AlignDown(GetInteger(guarded_end), alignment); + KProcessAddress aligned_guarded_end_with_offset = aligned_guarded_end + offset; + if (aligned_guarded_end_with_offset > guarded_end) { + if (aligned_guarded_end < alignment) { + return false; + } + aligned_guarded_end_with_offset -= alignment; + } + + /* Check that the extents are valid. */ + if (aligned_guarded_end_with_offset < aligned_guarded_start_with_offset) { + return false; + } + + /* Set the output extents. */ + *out_start = aligned_guarded_start_with_offset; + *out_end = aligned_guarded_end_with_offset; + return true; + } + + KProcessAddress KMemoryBlockManager::FindFreeArea(KProcessAddress region_start, size_t region_num_pages, size_t num_pages, size_t alignment, size_t offset, size_t guard_pages) const { + /* Determine the range to search in. */ + KProcessAddress search_start = Null<KProcessAddress>; + KProcessAddress search_end = Null<KProcessAddress>; + if (this->GetRegionForFindFreeArea(std::addressof(search_start), std::addressof(search_end), region_start, region_num_pages, num_pages, alignment, offset, guard_pages)) { + /* Iterate over blocks in the search space, looking for a suitable one. */ + for (const_iterator it = this->FindIterator(search_start); it != m_memory_block_tree.cend(); it++) { + /* If our block is past the end of our search space, we're done. */ + if (search_end < it->GetAddress()) { + break; + } + + /* We only want to consider free blocks. */ + if (it->GetState() != KMemoryState_Free) { + continue; + } + + /* Determine the candidate range. */ + KProcessAddress candidate_start = Null<KProcessAddress>; + KProcessAddress candidate_end = Null<KProcessAddress>; + if (!this->GetRegionForFindFreeArea(std::addressof(candidate_start), std::addressof(candidate_end), it->GetAddress(), it->GetNumPages(), num_pages, alignment, offset, guard_pages)) { + continue; + } + + /* Try the suggested candidate (coercing into the search region if needed). */ + KProcessAddress candidate = candidate_start; + if (candidate < search_start) { + candidate = search_start; + } + + /* Check if the candidate is valid. */ + if (candidate <= search_end && candidate <= candidate_end) { + return candidate; + } + } + } + + return Null<KProcessAddress>; + } + + void KMemoryBlockManager::CoalesceForUpdate(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages) { + /* Find the iterator now that we've updated. */ + iterator it = this->FindIterator(address); + if (address != m_start_address) { + it--; + } + + /* Coalesce blocks that we can. */ + while (true) { + iterator prev = it++; + if (it == m_memory_block_tree.end()) { + break; + } + + if (prev->CanMergeWith(*it)) { + KMemoryBlock *block = std::addressof(*it); + m_memory_block_tree.erase(it); + prev->Add(*block); + allocator->Free(block); + it = prev; + } + + if (address + num_pages * PageSize < it->GetEndAddress()) { + break; + } + } + } + + void KMemoryBlockManager::Update(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, KMemoryState state, KMemoryPermission perm, KMemoryAttribute attr, KMemoryBlockDisableMergeAttribute set_disable_attr, KMemoryBlockDisableMergeAttribute clear_disable_attr) { + /* Ensure for auditing that we never end up with an invalid tree. */ + KScopedMemoryBlockManagerAuditor auditor(this); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(address), PageSize)); + MESOSPHERE_ASSERT((attr & (KMemoryAttribute_IpcLocked | KMemoryAttribute_DeviceShared)) == 0); + + KProcessAddress cur_address = address; + size_t remaining_pages = num_pages; + iterator it = this->FindIterator(address); + + while (remaining_pages > 0) { + const size_t remaining_size = remaining_pages * PageSize; + if (it->HasProperties(state, perm, attr)) { + /* If we already have the right properties, just advance. */ + if (cur_address + remaining_size < it->GetEndAddress()) { + remaining_pages = 0; + cur_address += remaining_size; + } else { + remaining_pages = (cur_address + remaining_size - it->GetEndAddress()) / PageSize; + cur_address = it->GetEndAddress(); + } + } else { + /* If we need to, create a new block before and insert it. */ + if (it->GetAddress() != GetInteger(cur_address)) { + KMemoryBlock *new_block = allocator->Allocate(); + + it->Split(new_block, cur_address); + it = m_memory_block_tree.insert(*new_block); + it++; + + cur_address = it->GetAddress(); + } + + /* If we need to, create a new block after and insert it. */ + if (it->GetSize() > remaining_size) { + KMemoryBlock *new_block = allocator->Allocate(); + + it->Split(new_block, cur_address + remaining_size); + it = m_memory_block_tree.insert(*new_block); + } + + /* Update block state. */ + it->Update(state, perm, attr, it->GetAddress() == address, set_disable_attr, clear_disable_attr); + cur_address += it->GetSize(); + remaining_pages -= it->GetNumPages(); + } + it++; + } + + this->CoalesceForUpdate(allocator, address, num_pages); + } + + void KMemoryBlockManager::UpdateIfMatch(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, KMemoryState test_state, KMemoryPermission test_perm, KMemoryAttribute test_attr, KMemoryState state, KMemoryPermission perm, KMemoryAttribute attr, KMemoryBlockDisableMergeAttribute set_disable_attr, KMemoryBlockDisableMergeAttribute clear_disable_attr) { + /* Ensure for auditing that we never end up with an invalid tree. */ + KScopedMemoryBlockManagerAuditor auditor(this); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(address), PageSize)); + MESOSPHERE_ASSERT((attr & (KMemoryAttribute_IpcLocked | KMemoryAttribute_DeviceShared)) == 0); + + KProcessAddress cur_address = address; + size_t remaining_pages = num_pages; + iterator it = this->FindIterator(address); + + while (remaining_pages > 0) { + const size_t remaining_size = remaining_pages * PageSize; + if (it->HasProperties(test_state, test_perm, test_attr) && !it->HasProperties(state, perm, attr)) { + /* If we need to, create a new block before and insert it. */ + if (it->GetAddress() != GetInteger(cur_address)) { + KMemoryBlock *new_block = allocator->Allocate(); + + it->Split(new_block, cur_address); + it = m_memory_block_tree.insert(*new_block); + it++; + + cur_address = it->GetAddress(); + } + + /* If we need to, create a new block after and insert it. */ + if (it->GetSize() > remaining_size) { + KMemoryBlock *new_block = allocator->Allocate(); + + it->Split(new_block, cur_address + remaining_size); + it = m_memory_block_tree.insert(*new_block); + } + + /* Update block state. */ + it->Update(state, perm, attr, it->GetAddress() == address, set_disable_attr, clear_disable_attr); + cur_address += it->GetSize(); + remaining_pages -= it->GetNumPages(); + } else { + /* If we already have the right properties, just advance. */ + if (cur_address + remaining_size < it->GetEndAddress()) { + remaining_pages = 0; + cur_address += remaining_size; + } else { + remaining_pages = (cur_address + remaining_size - it->GetEndAddress()) / PageSize; + cur_address = it->GetEndAddress(); + } + } + it++; + } + + this->CoalesceForUpdate(allocator, address, num_pages); + } + + void KMemoryBlockManager::UpdateLock(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, MemoryBlockLockFunction lock_func, KMemoryPermission perm) { + /* Ensure for auditing that we never end up with an invalid tree. */ + KScopedMemoryBlockManagerAuditor auditor(this); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(address), PageSize)); + + KProcessAddress cur_address = address; + size_t remaining_pages = num_pages; + iterator it = this->FindIterator(address); + + const KProcessAddress end_address = address + (num_pages * PageSize); + + while (remaining_pages > 0) { + const size_t remaining_size = remaining_pages * PageSize; + + /* If we need to, create a new block before and insert it. */ + if (it->GetAddress() != cur_address) { + KMemoryBlock *new_block = allocator->Allocate(); + + it->Split(new_block, cur_address); + it = m_memory_block_tree.insert(*new_block); + it++; + + cur_address = it->GetAddress(); + } + + if (it->GetSize() > remaining_size) { + /* If we need to, create a new block after and insert it. */ + KMemoryBlock *new_block = allocator->Allocate(); + + it->Split(new_block, cur_address + remaining_size); + it = m_memory_block_tree.insert(*new_block); + } + + /* Call the locked update function. */ + (std::addressof(*it)->*lock_func)(perm, it->GetAddress() == address, it->GetEndAddress() == end_address); + cur_address += it->GetSize(); + remaining_pages -= it->GetNumPages(); + it++; + } + + this->CoalesceForUpdate(allocator, address, num_pages); + } + + void KMemoryBlockManager::UpdateAttribute(KMemoryBlockManagerUpdateAllocator *allocator, KProcessAddress address, size_t num_pages, u32 mask, u32 attr) { + /* Ensure for auditing that we never end up with an invalid tree. */ + KScopedMemoryBlockManagerAuditor auditor(this); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(address), PageSize)); + + KProcessAddress cur_address = address; + size_t remaining_pages = num_pages; + iterator it = this->FindIterator(address); + + while (remaining_pages > 0) { + const size_t remaining_size = remaining_pages * PageSize; + + if ((it->GetAttribute() & mask) != attr) { + /* If we need to, create a new block before and insert it. */ + if (it->GetAddress() != GetInteger(cur_address)) { + KMemoryBlock *new_block = allocator->Allocate(); + + it->Split(new_block, cur_address); + it = m_memory_block_tree.insert(*new_block); + it++; + + cur_address = it->GetAddress(); + } + + /* If we need to, create a new block after and insert it. */ + if (it->GetSize() > remaining_size) { + KMemoryBlock *new_block = allocator->Allocate(); + + it->Split(new_block, cur_address + remaining_size); + it = m_memory_block_tree.insert(*new_block); + } + + /* Update block state. */ + it->UpdateAttribute(mask, attr); + cur_address += it->GetSize(); + remaining_pages -= it->GetNumPages(); + } else { + /* If we already have the right attributes, just advance. */ + if (cur_address + remaining_size < it->GetEndAddress()) { + remaining_pages = 0; + cur_address += remaining_size; + } else { + remaining_pages = (cur_address + remaining_size - it->GetEndAddress()) / PageSize; + cur_address = it->GetEndAddress(); + } + } + it++; + } + + this->CoalesceForUpdate(allocator, address, num_pages); + } + + /* Debug. */ + bool KMemoryBlockManager::CheckState() const { + /* If we fail, we should dump blocks. */ + auto dump_guard = SCOPE_GUARD { this->DumpBlocks(); }; + + /* Loop over every block, ensuring that we are sorted and coalesced. */ + auto it = m_memory_block_tree.cbegin(); + auto prev = it++; + while (it != m_memory_block_tree.cend()) { + + /* Sequential blocks which can be merged should be merged. */ + if (prev->CanMergeWith(*it)) { + return false; + } + + /* Sequential blocks should be sequential. */ + if (prev->GetEndAddress() != it->GetAddress()) { + return false; + } + + /* If the block is ipc locked, it must have a count. */ + if ((it->GetAttribute() & KMemoryAttribute_IpcLocked) != 0 && it->GetIpcLockCount() == 0) { + return false; + } + + /* If the block is device shared, it must have a count. */ + if ((it->GetAttribute() & KMemoryAttribute_DeviceShared) != 0 && it->GetDeviceUseCount() == 0) { + return false; + } + + /* Advance the iterator. */ + prev = it++; + } + + /* Our loop will miss checking the last block, potentially, so check it. */ + if (prev != m_memory_block_tree.cend()) { + /* If the block is ipc locked, it must have a count. */ + if ((prev->GetAttribute() & KMemoryAttribute_IpcLocked) != 0 && prev->GetIpcLockCount() == 0) { + return false; + } + + /* If the block is device shared, it must have a count. */ + if ((prev->GetAttribute() & KMemoryAttribute_DeviceShared) != 0 && prev->GetDeviceUseCount() == 0) { + return false; + } + } + + /* We're valid, so no need to print. */ + dump_guard.Cancel(); + return true; + } + + void KMemoryBlockManager::DumpBlocks() const { + /* Dump each block. */ + for (const auto &block : m_memory_block_tree) { + DumpMemoryBlock(block); + } + } +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_memory_layout.board.nintendo_nx.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_memory_layout.board.nintendo_nx.cpp new file mode 100644 index 00000000..b2d4a09b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_memory_layout.board.nintendo_nx.cpp @@ -0,0 +1,272 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + constexpr size_t ReservedEarlyDramSize = 0x60000; + + constexpr size_t CarveoutAlignment = 0x20000; + constexpr size_t CarveoutSizeMax = 512_MB - CarveoutAlignment; + + template<typename... T> requires (std::same_as<T, KMemoryRegionAttr> && ...) + constexpr ALWAYS_INLINE KMemoryRegionType GetMemoryRegionType(KMemoryRegionType base, T... attr) { + return util::FromUnderlying<KMemoryRegionType>(util::ToUnderlying(base) | (util::ToUnderlying<T>(attr) | ...)); + } + + ALWAYS_INLINE bool SetupUartPhysicalMemoryRegion() { + #if defined(MESOSPHERE_DEBUG_LOG_USE_UART) + switch (KSystemControl::Init::GetDebugLogUartPort()) { + case 0: return KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x70006000, 0x40, GetMemoryRegionType(KMemoryRegionType_Uart, KMemoryRegionAttr_ShouldKernelMap)); + case 1: return KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x70006040, 0x40, GetMemoryRegionType(KMemoryRegionType_Uart, KMemoryRegionAttr_ShouldKernelMap)); + case 2: return KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x70006200, 0x100, GetMemoryRegionType(KMemoryRegionType_Uart, KMemoryRegionAttr_ShouldKernelMap)); + case 3: return KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x70006300, 0x100, GetMemoryRegionType(KMemoryRegionType_Uart, KMemoryRegionAttr_ShouldKernelMap)); + default: return false; + } + #elif defined(MESOSPHERE_DEBUG_LOG_USE_IRAM_RINGBUFFER) + return true; + #else + #error "Unknown Debug UART device!" + #endif + } + + ALWAYS_INLINE bool SetupPowerManagementControllerMemoryRegion() { + /* For backwards compatibility, the PMC must remain mappable on < 2.0.0. */ + const KMemoryRegionAttr rtc_restrict_attr = GetTargetFirmware() >= TargetFirmware_2_0_0 ? KMemoryRegionAttr_NoUserMap : static_cast<KMemoryRegionAttr>(0); + const KMemoryRegionAttr pmc_restrict_attr = GetTargetFirmware() >= TargetFirmware_2_0_0 ? KMemoryRegionAttr_NoUserMap : KMemoryRegionAttr_ShouldKernelMap; + + return KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x7000E000, 0x400, GetMemoryRegionType(KMemoryRegionType_None, rtc_restrict_attr)) && + KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x7000E400, 0xC00, GetMemoryRegionType(KMemoryRegionType_PowerManagementController, pmc_restrict_attr)); + } + + void InsertPoolPartitionRegionIntoBothTrees(size_t start, size_t size, KMemoryRegionType phys_type, KMemoryRegionType virt_type, u32 &cur_attr) { + const u32 attr = cur_attr++; + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(start, size, phys_type, attr)); + const KMemoryRegion *phys = KMemoryLayout::GetPhysicalMemoryRegionTree().FindByTypeAndAttribute(phys_type, attr); + MESOSPHERE_INIT_ABORT_UNLESS(phys != nullptr); + MESOSPHERE_INIT_ABORT_UNLESS(phys->GetEndAddress() != 0); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetVirtualMemoryRegionTree().Insert(phys->GetPairAddress(), size, virt_type, attr)); + } + + } + + namespace init { + + void SetupDevicePhysicalMemoryRegions() { + /* TODO: Give these constexpr defines somewhere? */ + MESOSPHERE_INIT_ABORT_UNLESS(SetupUartPhysicalMemoryRegion()); + MESOSPHERE_INIT_ABORT_UNLESS(SetupPowerManagementControllerMemoryRegion()); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x70019000, 0x1000, GetMemoryRegionType(KMemoryRegionType_MemoryController, KMemoryRegionAttr_NoUserMap))); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x7001C000, 0x1000, GetMemoryRegionType(KMemoryRegionType_MemoryController0, KMemoryRegionAttr_NoUserMap))); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x7001D000, 0x1000, GetMemoryRegionType(KMemoryRegionType_MemoryController1, KMemoryRegionAttr_NoUserMap))); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x50040000, 0x1000, GetMemoryRegionType(KMemoryRegionType_None, KMemoryRegionAttr_NoUserMap))); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x50041000, 0x1000, GetMemoryRegionType(KMemoryRegionType_InterruptDistributor, KMemoryRegionAttr_ShouldKernelMap))); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x50042000, 0x1000, GetMemoryRegionType(KMemoryRegionType_InterruptCpuInterface, KMemoryRegionAttr_ShouldKernelMap))); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x50043000, 0x1D000, GetMemoryRegionType(KMemoryRegionType_None, KMemoryRegionAttr_NoUserMap))); + + /* Map IRAM unconditionally, to support debug-logging-to-iram build config. */ + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x40000000, 0x40000, GetMemoryRegionType(KMemoryRegionType_LegacyLpsIram, KMemoryRegionAttr_ShouldKernelMap))); + + if (GetTargetFirmware() >= TargetFirmware_2_0_0) { + /* Prevent mapping the bpmp exception vectors or the ipatch region. */ + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x6000F000, 0x1000, GetMemoryRegionType(KMemoryRegionType_None, KMemoryRegionAttr_NoUserMap))); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x6001DC00, 0x400, GetMemoryRegionType(KMemoryRegionType_None, KMemoryRegionAttr_NoUserMap))); + } else { + /* Map devices required for legacy lps driver. */ + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x6000F000, 0x1000, GetMemoryRegionType(KMemoryRegionType_LegacyLpsExceptionVectors, KMemoryRegionAttr_ShouldKernelMap))); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x60007000, 0x1000, GetMemoryRegionType(KMemoryRegionType_LegacyLpsFlowController, KMemoryRegionAttr_ShouldKernelMap))); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x60004000, 0x1000, GetMemoryRegionType(KMemoryRegionType_LegacyLpsPrimaryICtlr, KMemoryRegionAttr_ShouldKernelMap))); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x60001000, 0x1000, GetMemoryRegionType(KMemoryRegionType_LegacyLpsSemaphore, KMemoryRegionAttr_ShouldKernelMap))); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x70016000, 0x1000, GetMemoryRegionType(KMemoryRegionType_LegacyLpsAtomics, KMemoryRegionAttr_ShouldKernelMap))); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x60006000, 0x1000, GetMemoryRegionType(KMemoryRegionType_LegacyLpsClkRst, KMemoryRegionAttr_ShouldKernelMap))); + } + } + + void SetupDramPhysicalMemoryRegions() { + const size_t intended_memory_size = KSystemControl::Init::GetIntendedMemorySize(); + const KPhysicalAddress physical_memory_base_address = KSystemControl::Init::GetKernelPhysicalBaseAddress(ams::kern::MainMemoryAddress); + + /* Insert blocks into the tree. */ + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(GetInteger(physical_memory_base_address), intended_memory_size, KMemoryRegionType_Dram)); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(GetInteger(physical_memory_base_address), ReservedEarlyDramSize, KMemoryRegionType_DramReservedEarly)); + } + + void SetupPoolPartitionMemoryRegions() { + /* Start by identifying the extents of the DRAM memory region. */ + const auto dram_extents = KMemoryLayout::GetMainMemoryPhysicalExtents(); + MESOSPHERE_INIT_ABORT_UNLESS(dram_extents.GetEndAddress() != 0); + + /* Find the pool partitions region. */ + const KMemoryRegion *pool_partitions_region = KMemoryLayout::GetPhysicalMemoryRegionTree().FindByTypeAndAttribute(KMemoryRegionType_DramPoolPartition, 0); + MESOSPHERE_INIT_ABORT_UNLESS(pool_partitions_region != nullptr); + + const uintptr_t pool_partitions_start = pool_partitions_region->GetAddress(); + + /* Determine the end of the pool region. */ + const uintptr_t pool_end = pool_partitions_region->GetEndAddress(); + MESOSPHERE_INIT_ABORT_UNLESS(pool_end == dram_extents.GetEndAddress()); + + /* Find the start of the kernel DRAM region. */ + const KMemoryRegion *kernel_dram_region = KMemoryLayout::GetPhysicalMemoryRegionTree().FindFirstDerived(KMemoryRegionType_DramKernelBase); + MESOSPHERE_INIT_ABORT_UNLESS(kernel_dram_region != nullptr); + + const uintptr_t kernel_dram_start = kernel_dram_region->GetAddress(); + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(kernel_dram_start, CarveoutAlignment)); + + /* Setup the pool partition layouts. */ + if (GetTargetFirmware() >= TargetFirmware_5_0_0) { + /* On 5.0.0+, setup modern 4-pool-partition layout. */ + + /* Get Application and Applet pool sizes. */ + const size_t application_pool_size = KSystemControl::Init::GetApplicationPoolSize(); + const size_t applet_pool_size = KSystemControl::Init::GetAppletPoolSize(); + const size_t unsafe_system_pool_min_size = KSystemControl::Init::GetMinimumNonSecureSystemPoolSize(); + + /* Decide on starting addresses for our pools. */ + const uintptr_t application_pool_start = pool_end - application_pool_size; + const uintptr_t applet_pool_start = application_pool_start - applet_pool_size; + const uintptr_t unsafe_system_pool_start = std::min(kernel_dram_start + CarveoutSizeMax, util::AlignDown(applet_pool_start - unsafe_system_pool_min_size, CarveoutAlignment)); + const size_t unsafe_system_pool_size = applet_pool_start - unsafe_system_pool_start; + + /* We want to arrange application pool depending on where the middle of dram is. */ + const uintptr_t dram_midpoint = (dram_extents.GetAddress() + dram_extents.GetEndAddress()) / 2; + u32 cur_pool_attr = 0; + size_t total_overhead_size = 0; + if (dram_extents.GetEndAddress() <= dram_midpoint || dram_midpoint <= application_pool_start) { + InsertPoolPartitionRegionIntoBothTrees(application_pool_start, application_pool_size, KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, cur_pool_attr); + total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(application_pool_size); + } else { + const size_t first_application_pool_size = dram_midpoint - application_pool_start; + const size_t second_application_pool_size = application_pool_start + application_pool_size - dram_midpoint; + InsertPoolPartitionRegionIntoBothTrees(application_pool_start, first_application_pool_size, KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, cur_pool_attr); + InsertPoolPartitionRegionIntoBothTrees(dram_midpoint, second_application_pool_size, KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, cur_pool_attr); + total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(first_application_pool_size); + total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(second_application_pool_size); + } + + /* Insert the applet pool. */ + InsertPoolPartitionRegionIntoBothTrees(applet_pool_start, applet_pool_size, KMemoryRegionType_DramAppletPool, KMemoryRegionType_VirtualDramAppletPool, cur_pool_attr); + total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(applet_pool_size); + + /* Insert the nonsecure system pool. */ + InsertPoolPartitionRegionIntoBothTrees(unsafe_system_pool_start, unsafe_system_pool_size, KMemoryRegionType_DramSystemNonSecurePool, KMemoryRegionType_VirtualDramSystemNonSecurePool, cur_pool_attr); + total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(unsafe_system_pool_size); + + /* Determine final total overhead size. */ + total_overhead_size += KMemoryManager::CalculateManagementOverheadSize((unsafe_system_pool_start - pool_partitions_start) - total_overhead_size); + + /* NOTE: Nintendo's kernel has layout [System, Management] but we have [Management, System]. This ensures the four UserPool regions are contiguous. */ + + /* Insert the system pool. */ + const uintptr_t system_pool_start = pool_partitions_start + total_overhead_size; + const size_t system_pool_size = unsafe_system_pool_start - system_pool_start; + InsertPoolPartitionRegionIntoBothTrees(system_pool_start, system_pool_size, KMemoryRegionType_DramSystemPool, KMemoryRegionType_VirtualDramSystemPool, cur_pool_attr); + + /* Insert the pool management region. */ + const uintptr_t pool_management_start = pool_partitions_start; + const size_t pool_management_size = total_overhead_size; + u32 pool_management_attr = 0; + InsertPoolPartitionRegionIntoBothTrees(pool_management_start, pool_management_size, KMemoryRegionType_DramPoolManagement, KMemoryRegionType_VirtualDramPoolManagement, pool_management_attr); + } else { + /* On < 5.0.0, setup a legacy 2-pool layout for backwards compatibility. */ + + static_assert(KMemoryManager::Pool_Count == 4); + static_assert(KMemoryManager::Pool_Unsafe == KMemoryManager::Pool_Application); + static_assert(KMemoryManager::Pool_Secure == KMemoryManager::Pool_System); + + /* Get Secure pool size. */ + const size_t secure_pool_size = [](auto target_firmware) ALWAYS_INLINE_LAMBDA -> size_t { + constexpr size_t LegacySecureKernelSize = 8_MB; /* KPageBuffer pages, other small kernel allocations. */ + constexpr size_t LegacySecureMiscSize = 1_MB; /* Miscellaneous pages for secure process mapping. */ + constexpr size_t LegacySecureHeapSize = 24_MB; /* Heap pages for secure process mapping (fs). */ + constexpr size_t LegacySecureEsSize = 1_MB + 232_KB; /* Size for additional secure process (es, 4.0.0+). */ + + /* The baseline size for the secure region is enough to cover any allocations the kernel might make. */ + size_t size = LegacySecureKernelSize; + + /* If on 2.0.0+, initial processes will fall within the secure region. */ + if (target_firmware >= TargetFirmware_2_0_0) { + /* Account for memory used directly for the processes. */ + size += GetInitialProcessesSecureMemorySize(); + + /* Account for heap and transient memory used by the processes. */ + size += LegacySecureHeapSize + LegacySecureMiscSize; + } + + /* If on 4.0.0+, any process may use secure memory via a create process flag. */ + /* In process this is used for es alone, and the secure pool's size should be */ + /* increased to accommodate es's binary. */ + if (target_firmware >= TargetFirmware_4_0_0) { + size += LegacySecureEsSize; + } + + return size; + }(GetTargetFirmware()); + + /* Calculate the overhead for the secure and (defunct) applet/non-secure-system pools. */ + size_t total_overhead_size = KMemoryManager::CalculateManagementOverheadSize(secure_pool_size); + + /* Calculate the overhead for (an amount larger than) the unsafe pool. */ + const size_t approximate_total_overhead_size = total_overhead_size + KMemoryManager::CalculateManagementOverheadSize((pool_end - pool_partitions_start) - secure_pool_size - total_overhead_size) + 2 * PageSize; + + /* Determine the start of the unsafe region. */ + const uintptr_t unsafe_memory_start = util::AlignUp(pool_partitions_start + secure_pool_size + approximate_total_overhead_size, CarveoutAlignment); + + /* Determine the start of the pool regions. */ + const uintptr_t application_pool_start = unsafe_memory_start; + + /* Determine the pool sizes. */ + const size_t application_pool_size = pool_end - application_pool_start; + + /* We want to arrange application pool depending on where the middle of dram is. */ + const uintptr_t dram_midpoint = (dram_extents.GetAddress() + dram_extents.GetEndAddress()) / 2; + u32 cur_pool_attr = 0; + if (dram_extents.GetEndAddress() <= dram_midpoint || dram_midpoint <= application_pool_start) { + InsertPoolPartitionRegionIntoBothTrees(application_pool_start, application_pool_size, KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, cur_pool_attr); + total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(application_pool_size); + } else { + const size_t first_application_pool_size = dram_midpoint - application_pool_start; + const size_t second_application_pool_size = application_pool_start + application_pool_size - dram_midpoint; + InsertPoolPartitionRegionIntoBothTrees(application_pool_start, first_application_pool_size, KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, cur_pool_attr); + InsertPoolPartitionRegionIntoBothTrees(dram_midpoint, second_application_pool_size, KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, cur_pool_attr); + total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(first_application_pool_size); + total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(second_application_pool_size); + } + + /* Validate the true overhead size. */ + MESOSPHERE_INIT_ABORT_UNLESS(total_overhead_size <= approximate_total_overhead_size); + + /* NOTE: Nintendo's kernel has layout [System, Management] but we have [Management, System]. This ensures the UserPool regions are contiguous. */ + + /* Insert the secure pool. */ + const uintptr_t secure_pool_start = unsafe_memory_start - secure_pool_size; + InsertPoolPartitionRegionIntoBothTrees(secure_pool_start, secure_pool_size, KMemoryRegionType_DramSystemPool, KMemoryRegionType_VirtualDramSystemPool, cur_pool_attr); + + /* Insert the pool management region. */ + const uintptr_t pool_management_start = pool_partitions_start; + const size_t pool_management_size = secure_pool_start - pool_management_start; + MESOSPHERE_INIT_ABORT_UNLESS(total_overhead_size <= pool_management_size); + + u32 pool_management_attr = 0; + InsertPoolPartitionRegionIntoBothTrees(pool_management_start, pool_management_size, KMemoryRegionType_DramPoolManagement, KMemoryRegionType_VirtualDramPoolManagement, pool_management_attr); + } + } + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_memory_layout.board.qemu_virt.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_memory_layout.board.qemu_virt.cpp new file mode 100644 index 00000000..5637a771 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_memory_layout.board.qemu_virt.cpp @@ -0,0 +1,139 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + constexpr size_t ReservedEarlyDramSize = 0x00080000; + + template<typename... T> requires (std::same_as<T, KMemoryRegionAttr> && ...) + constexpr ALWAYS_INLINE KMemoryRegionType GetMemoryRegionType(KMemoryRegionType base, T... attr) { + return util::FromUnderlying<KMemoryRegionType>(util::ToUnderlying(base) | (util::ToUnderlying<T>(attr) | ...)); + } + + void InsertPoolPartitionRegionIntoBothTrees(size_t start, size_t size, KMemoryRegionType phys_type, KMemoryRegionType virt_type, u32 &cur_attr) { + const u32 attr = cur_attr++; + + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(start, size, phys_type, attr)); + const KMemoryRegion *phys = KMemoryLayout::GetPhysicalMemoryRegionTree().FindByTypeAndAttribute(phys_type, attr); + MESOSPHERE_INIT_ABORT_UNLESS(phys != nullptr); + MESOSPHERE_INIT_ABORT_UNLESS(phys->GetEndAddress() != 0); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetVirtualMemoryRegionTree().Insert(phys->GetPairAddress(), size, virt_type, attr)); + } + + } + + namespace init { + + void SetupDevicePhysicalMemoryRegions() { + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x08000000, 0x10000, GetMemoryRegionType(KMemoryRegionType_InterruptDistributor, KMemoryRegionAttr_ShouldKernelMap))); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(0x08010000, 0x10000, GetMemoryRegionType(KMemoryRegionType_InterruptCpuInterface, KMemoryRegionAttr_ShouldKernelMap))); + } + + void SetupDramPhysicalMemoryRegions() { + const size_t intended_memory_size = KSystemControl::Init::GetIntendedMemorySize(); + const KPhysicalAddress physical_memory_base_address = KSystemControl::Init::GetKernelPhysicalBaseAddress(ams::kern::MainMemoryAddress); + + /* Insert blocks into the tree. */ + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(GetInteger(physical_memory_base_address), intended_memory_size, KMemoryRegionType_Dram)); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(GetInteger(physical_memory_base_address), ReservedEarlyDramSize, KMemoryRegionType_DramReservedEarly)); + } + + void SetupPoolPartitionMemoryRegions() { + /* Start by identifying the extents of the DRAM memory region. */ + const auto dram_extents = KMemoryLayout::GetMainMemoryPhysicalExtents(); + MESOSPHERE_INIT_ABORT_UNLESS(dram_extents.GetEndAddress() != 0); + + /* Find the pool partitions region. */ + const KMemoryRegion *pool_partitions_region = KMemoryLayout::GetPhysicalMemoryRegionTree().FindByTypeAndAttribute(KMemoryRegionType_DramPoolPartition, 0); + MESOSPHERE_INIT_ABORT_UNLESS(pool_partitions_region != nullptr); + + const uintptr_t pool_partitions_start = pool_partitions_region->GetAddress(); + + /* Determine the end of the pool region. */ + const uintptr_t pool_end = pool_partitions_region->GetEndAddress(); + MESOSPHERE_INIT_ABORT_UNLESS(pool_end == dram_extents.GetEndAddress()); + + /* Find the start of the kernel DRAM region. */ + const KMemoryRegion *kernel_dram_region = KMemoryLayout::GetPhysicalMemoryRegionTree().FindFirstDerived(KMemoryRegionType_DramKernelBase); + MESOSPHERE_INIT_ABORT_UNLESS(kernel_dram_region != nullptr); + + /* Setup the pool partition layouts. */ + /* Get Application and Applet pool sizes. */ + const size_t application_pool_size = KSystemControl::Init::GetApplicationPoolSize(); + const size_t applet_pool_size = KSystemControl::Init::GetAppletPoolSize(); + const size_t unsafe_system_pool_min_size = KSystemControl::Init::GetMinimumNonSecureSystemPoolSize(); + + /* Decide on starting addresses for our pools. */ + const uintptr_t application_pool_start = pool_end - application_pool_size; + const uintptr_t applet_pool_start = application_pool_start - applet_pool_size; + const uintptr_t unsafe_system_pool_start = util::AlignDown(applet_pool_start - unsafe_system_pool_min_size, PageSize); + const size_t unsafe_system_pool_size = applet_pool_start - unsafe_system_pool_start; + + /* We want to arrange application pool depending on where the middle of dram is. */ + const uintptr_t dram_midpoint = (dram_extents.GetAddress() + dram_extents.GetEndAddress()) / 2; + u32 cur_pool_attr = 0; + size_t total_overhead_size = 0; + + /* Insert the application pool. */ + if (application_pool_size > 0) { + if (dram_extents.GetEndAddress() <= dram_midpoint || dram_midpoint <= application_pool_start) { + InsertPoolPartitionRegionIntoBothTrees(application_pool_start, application_pool_size, KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, cur_pool_attr); + total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(application_pool_size); + } else { + const size_t first_application_pool_size = dram_midpoint - application_pool_start; + const size_t second_application_pool_size = application_pool_start + application_pool_size - dram_midpoint; + InsertPoolPartitionRegionIntoBothTrees(application_pool_start, first_application_pool_size, KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, cur_pool_attr); + InsertPoolPartitionRegionIntoBothTrees(dram_midpoint, second_application_pool_size, KMemoryRegionType_DramApplicationPool, KMemoryRegionType_VirtualDramApplicationPool, cur_pool_attr); + total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(first_application_pool_size); + total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(second_application_pool_size); + } + } + + /* Insert the applet pool. */ + if (applet_pool_size > 0) { + InsertPoolPartitionRegionIntoBothTrees(applet_pool_start, applet_pool_size, KMemoryRegionType_DramAppletPool, KMemoryRegionType_VirtualDramAppletPool, cur_pool_attr); + total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(applet_pool_size); + } + + /* Insert the nonsecure system pool. */ + if (unsafe_system_pool_size > 0) { + InsertPoolPartitionRegionIntoBothTrees(unsafe_system_pool_start, unsafe_system_pool_size, KMemoryRegionType_DramSystemNonSecurePool, KMemoryRegionType_VirtualDramSystemNonSecurePool, cur_pool_attr); + total_overhead_size += KMemoryManager::CalculateManagementOverheadSize(unsafe_system_pool_size); + } + + /* Determine final total overhead size. */ + total_overhead_size += KMemoryManager::CalculateManagementOverheadSize((unsafe_system_pool_start - pool_partitions_start) - total_overhead_size); + + /* NOTE: Nintendo's kernel has layout [System, Management] but we have [Management, System]. This ensures the four UserPool regions are contiguous. */ + + /* Insert the system pool. */ + const uintptr_t system_pool_start = pool_partitions_start + total_overhead_size; + const size_t system_pool_size = unsafe_system_pool_start - system_pool_start; + InsertPoolPartitionRegionIntoBothTrees(system_pool_start, system_pool_size, KMemoryRegionType_DramSystemPool, KMemoryRegionType_VirtualDramSystemPool, cur_pool_attr); + + /* Insert the pool management region. */ + const uintptr_t pool_management_start = pool_partitions_start; + const size_t pool_management_size = total_overhead_size; + u32 pool_management_attr = 0; + InsertPoolPartitionRegionIntoBothTrees(pool_management_start, pool_management_size, KMemoryRegionType_DramPoolManagement, KMemoryRegionType_VirtualDramPoolManagement, pool_management_attr); + } + + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_memory_layout.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_memory_layout.cpp new file mode 100644 index 00000000..8161b17e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_memory_layout.cpp @@ -0,0 +1,133 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + class KMemoryRegionAllocator { + NON_COPYABLE(KMemoryRegionAllocator); + NON_MOVEABLE(KMemoryRegionAllocator); + public: + static constexpr size_t MaxMemoryRegions = 200; + private: + KMemoryRegion m_region_heap[MaxMemoryRegions]; + size_t m_num_regions; + public: + constexpr ALWAYS_INLINE KMemoryRegionAllocator() : m_region_heap(), m_num_regions() { /* ... */ } + public: + template<typename... Args> + ALWAYS_INLINE KMemoryRegion *Allocate(Args&&... args) { + /* Ensure we stay within the bounds of our heap. */ + MESOSPHERE_INIT_ABORT_UNLESS(m_num_regions < MaxMemoryRegions); + + /* Create the new region. */ + KMemoryRegion *region = std::addressof(m_region_heap[m_num_regions++]); + std::construct_at(region, std::forward<Args>(args)...); + + return region; + } + }; + + constinit KMemoryRegionAllocator g_memory_region_allocator; + + template<typename... Args> + ALWAYS_INLINE KMemoryRegion *AllocateRegion(Args&&... args) { + return g_memory_region_allocator.Allocate(std::forward<Args>(args)...); + } + + + } + + void KMemoryRegionTree::InsertDirectly(uintptr_t address, uintptr_t last_address, u32 attr, u32 type_id) { + this->insert(*AllocateRegion(address, last_address, attr, type_id)); + } + + bool KMemoryRegionTree::Insert(uintptr_t address, size_t size, u32 type_id, u32 new_attr, u32 old_attr) { + /* Locate the memory region that contains the address. */ + KMemoryRegion *found = this->FindModifiable(address); + + /* We require that the old attr is correct. */ + if (found->GetAttributes() != old_attr) { + return false; + } + + /* We further require that the region can be split from the old region. */ + const uintptr_t inserted_region_end = address + size; + const uintptr_t inserted_region_last = inserted_region_end - 1; + if (found->GetLastAddress() < inserted_region_last) { + return false; + } + + /* Further, we require that the type id is a valid transformation. */ + if (!found->CanDerive(type_id)) { + return false; + } + + /* Cache information from the region before we remove it. */ + const uintptr_t old_address = found->GetAddress(); + const uintptr_t old_last = found->GetLastAddress(); + const uintptr_t old_pair = found->GetPairAddress(); + const u32 old_type = found->GetType(); + + /* Erase the existing region from the tree. */ + this->erase(this->iterator_to(*found)); + + /* Insert the new region into the tree. */ + if (old_address == address) { + /* Reuse the old object for the new region, if we can. */ + found->Reset(address, inserted_region_last, old_pair, new_attr, type_id); + this->insert(*found); + } else { + /* If we can't re-use, adjust the old region. */ + found->Reset(old_address, address - 1, old_pair, old_attr, old_type); + this->insert(*found); + + /* Insert a new region for the split. */ + const uintptr_t new_pair = (old_pair != std::numeric_limits<uintptr_t>::max()) ? old_pair + (address - old_address) : old_pair; + this->insert(*AllocateRegion(address, inserted_region_last, new_pair, new_attr, type_id)); + } + + /* If we need to insert a region after the region, do so. */ + if (old_last != inserted_region_last) { + const uintptr_t after_pair = (old_pair != std::numeric_limits<uintptr_t>::max()) ? old_pair + (inserted_region_end - old_address) : old_pair; + this->insert(*AllocateRegion(inserted_region_end, old_last, after_pair, old_attr, old_type)); + } + + return true; + } + + void KMemoryLayout::InitializeLinearMemoryRegionTrees() { + /* Initialize linear trees. */ + for (auto ®ion : GetPhysicalMemoryRegionTree()) { + if (region.HasTypeAttribute(KMemoryRegionAttr_LinearMapped)) { + GetPhysicalLinearMemoryRegionTree().InsertDirectly(region.GetAddress(), region.GetLastAddress(), region.GetAttributes(), region.GetType()); + } + } + + for (auto ®ion : GetVirtualMemoryRegionTree()) { + if (region.IsDerivedFrom(KMemoryRegionType_Dram)) { + GetVirtualLinearMemoryRegionTree().InsertDirectly(region.GetAddress(), region.GetLastAddress(), region.GetAttributes(), region.GetType()); + } + } + } + + size_t KMemoryLayout::GetResourceRegionSizeForInit(bool use_extra_resource) { + return KernelResourceSize + KSystemControl::SecureAppletMemorySize + (use_extra_resource ? KernelSlabHeapAdditionalSize + KernelPageBufferAdditionalSize : 0); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_memory_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_memory_manager.cpp new file mode 100644 index 00000000..aa5f5bdc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_memory_manager.cpp @@ -0,0 +1,523 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + constexpr KMemoryManager::Pool GetPoolFromMemoryRegionType(u32 type) { + if ((type | KMemoryRegionType_DramApplicationPool) == type) { + return KMemoryManager::Pool_Application; + } else if ((type | KMemoryRegionType_DramAppletPool) == type) { + return KMemoryManager::Pool_Applet; + } else if ((type | KMemoryRegionType_DramSystemPool) == type) { + return KMemoryManager::Pool_System; + } else if ((type | KMemoryRegionType_DramSystemNonSecurePool) == type) { + return KMemoryManager::Pool_SystemNonSecure; + } else { + MESOSPHERE_PANIC("InvalidMemoryRegionType for conversion to Pool"); + } + } + + } + + void KMemoryManager::Initialize(KVirtualAddress management_region, size_t management_region_size, const u32 *min_align_shifts) { + /* Clear the management region to zero. */ + const KVirtualAddress management_region_end = management_region + management_region_size; + std::memset(GetVoidPointer(management_region), 0, management_region_size); + + /* Reset our manager count. */ + m_num_managers = 0; + + /* Traverse the virtual memory layout tree, initializing each manager as appropriate. */ + while (m_num_managers != MaxManagerCount) { + /* Locate the region that should initialize the current manager. */ + KPhysicalAddress region_address = Null<KPhysicalAddress>; + size_t region_size = 0; + Pool region_pool = Pool_Count; + for (const auto &it : KMemoryLayout::GetPhysicalMemoryRegionTree()) { + /* We only care about regions that we need to create managers for. */ + if (!it.IsDerivedFrom(KMemoryRegionType_DramUserPool)) { + continue; + } + + /* We want to initialize the managers in order. */ + if (it.GetAttributes() != m_num_managers) { + continue; + } + + const KPhysicalAddress cur_start = it.GetAddress(); + const KPhysicalAddress cur_end = it.GetEndAddress(); + + /* Validate the region. */ + MESOSPHERE_ABORT_UNLESS(cur_end != Null<KPhysicalAddress>); + MESOSPHERE_ASSERT(cur_start != Null<KPhysicalAddress>); + MESOSPHERE_ASSERT(it.GetSize() > 0); + + /* Update the region's extents. */ + if (region_address == Null<KPhysicalAddress>) { + region_address = cur_start; + region_size = it.GetSize(); + region_pool = GetPoolFromMemoryRegionType(it.GetType()); + } else { + MESOSPHERE_ASSERT(cur_start == region_address + region_size); + + /* Update the size. */ + region_size = cur_end - region_address; + MESOSPHERE_ABORT_UNLESS(GetPoolFromMemoryRegionType(it.GetType()) == region_pool); + } + } + + /* If we didn't find a region, we're done. */ + if (region_size == 0) { + break; + } + + /* Initialize a new manager for the region. */ + Impl *manager = std::addressof(m_managers[m_num_managers++]); + MESOSPHERE_ABORT_UNLESS(m_num_managers <= util::size(m_managers)); + + const size_t cur_size = manager->Initialize(region_address, region_size, management_region, management_region_end, region_pool); + management_region += cur_size; + MESOSPHERE_ABORT_UNLESS(management_region <= management_region_end); + + /* Insert the manager into the pool list. */ + if (m_pool_managers_tail[region_pool] == nullptr) { + m_pool_managers_head[region_pool] = manager; + } else { + m_pool_managers_tail[region_pool]->SetNext(manager); + manager->SetPrev(m_pool_managers_tail[region_pool]); + } + m_pool_managers_tail[region_pool] = manager; + } + + /* Free each region to its corresponding heap. */ + size_t reserved_sizes[MaxManagerCount] = {}; + const KPhysicalAddress ini_start = GetInitialProcessBinaryPhysicalAddress(); + const size_t ini_size = GetInitialProcessBinarySize(); + const KPhysicalAddress ini_end = ini_start + ini_size; + const KPhysicalAddress ini_last = ini_end - 1; + for (const auto &it : KMemoryLayout::GetPhysicalMemoryRegionTree()) { + if (it.IsDerivedFrom(KMemoryRegionType_DramUserPool)) { + /* Get the manager for the region. */ + auto &manager = m_managers[it.GetAttributes()]; + + const KPhysicalAddress cur_start = it.GetAddress(); + const KPhysicalAddress cur_last = it.GetLastAddress(); + const KPhysicalAddress cur_end = it.GetEndAddress(); + + if (cur_start <= ini_start && ini_last <= cur_last) { + /* Free memory before the ini to the heap. */ + if (cur_start != ini_start) { + manager.Free(cur_start, (ini_start - cur_start) / PageSize); + } + + /* Open/reserve the ini memory. */ + manager.OpenFirst(ini_start, ini_size / PageSize); + reserved_sizes[it.GetAttributes()] += ini_size; + + /* Free memory after the ini to the heap. */ + if (ini_last != cur_last) { + MESOSPHERE_ABORT_UNLESS(cur_end != Null<KPhysicalAddress>); + manager.Free(ini_end, (cur_end - ini_end) / PageSize); + } + } else { + /* Ensure there's no partial overlap with the ini image. */ + if (cur_start <= ini_last) { + MESOSPHERE_ABORT_UNLESS(cur_last < ini_start); + } else { + /* Otherwise, check the region for general validity. */ + MESOSPHERE_ABORT_UNLESS(cur_end != Null<KPhysicalAddress>); + } + + /* Free the memory to the heap. */ + manager.Free(cur_start, it.GetSize() / PageSize); + } + } + } + + /* Update the used size for all managers. */ + for (size_t i = 0; i < m_num_managers; ++i) { + m_managers[i].SetInitialUsedHeapSize(reserved_sizes[i]); + } + + /* Determine the min heap size for all pools. */ + for (size_t i = 0; i < Pool_Count; ++i) { + /* Determine the min alignment for the pool in pages. */ + const size_t min_align_pages = 1 << min_align_shifts[i]; + + /* Determine a heap index. */ + if (const auto heap_index = KPageHeap::GetAlignedBlockIndex(min_align_pages, min_align_pages); heap_index >= 0) { + m_min_heap_indexes[i] = heap_index; + } + } + } + + Result KMemoryManager::InitializeOptimizedMemory(u64 process_id, Pool pool) { + /* Lock the pool. */ + KScopedLightLock lk(m_pool_locks[pool]); + + /* Check that we don't already have an optimized process. */ + R_UNLESS(!m_has_optimized_process[pool], svc::ResultBusy()); + + /* Set the optimized process id. */ + m_optimized_process_ids[pool] = process_id; + m_has_optimized_process[pool] = true; + + /* Clear the management area for the optimized process. */ + for (auto *manager = this->GetFirstManager(pool, Direction_FromFront); manager != nullptr; manager = this->GetNextManager(manager, Direction_FromFront)) { + manager->InitializeOptimizedMemory(); + } + + R_SUCCEED(); + } + + void KMemoryManager::FinalizeOptimizedMemory(u64 process_id, Pool pool) { + /* Lock the pool. */ + KScopedLightLock lk(m_pool_locks[pool]); + + /* If the process was optimized, clear it. */ + if (m_has_optimized_process[pool] && m_optimized_process_ids[pool] == process_id) { + m_has_optimized_process[pool] = false; + } + } + + + KPhysicalAddress KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, size_t align_pages, u32 option) { + /* Early return if we're allocating no pages. */ + if (num_pages == 0) { + return Null<KPhysicalAddress>; + } + + /* Determine the pool and direction we're allocating from. */ + const auto [pool, dir] = DecodeOption(option); + + /* Check that we're allocating a correctly aligned number of pages. */ + const size_t min_align_pages = KPageHeap::GetBlockNumPages(m_min_heap_indexes[pool]); + if (!util::IsAligned(num_pages, min_align_pages)) { + return Null<KPhysicalAddress>; + } + + /* Update our alignment. */ + align_pages = std::max(align_pages, min_align_pages); + + /* Lock the pool that we're allocating from. */ + KScopedLightLock lk(m_pool_locks[pool]); + + /* Choose a heap based on our page size request. */ + const s32 heap_index = KPageHeap::GetAlignedBlockIndex(num_pages, align_pages); + + /* Loop, trying to iterate from each block. */ + Impl *chosen_manager = nullptr; + KPhysicalAddress allocated_block = Null<KPhysicalAddress>; + for (chosen_manager = this->GetFirstManager(pool, dir); chosen_manager != nullptr; chosen_manager = this->GetNextManager(chosen_manager, dir)) { + allocated_block = chosen_manager->AllocateAligned(heap_index, num_pages, align_pages); + if (allocated_block != Null<KPhysicalAddress>) { + break; + } + } + + /* If we failed to allocate, quit now. */ + if (allocated_block == Null<KPhysicalAddress>) { + return Null<KPhysicalAddress>; + } + + /* Maintain the optimized memory bitmap, if we should. */ + if (m_has_optimized_process[pool]) { + chosen_manager->TrackUnoptimizedAllocation(allocated_block, num_pages); + } + + /* Open the first reference to the pages. */ + chosen_manager->OpenFirst(allocated_block, num_pages); + + return allocated_block; + } + + Result KMemoryManager::AllocatePageGroupImpl(KPageGroup *out, size_t num_pages, Pool pool, Direction dir, bool unoptimized, bool random, s32 min_heap_index) { + /* Check that we're allocating a correctly aligned number of pages. */ + const size_t min_align_pages = KPageHeap::GetBlockNumPages(m_min_heap_indexes[pool]); + R_UNLESS(util::IsAligned(num_pages, min_align_pages), svc::ResultInvalidSize()); + + /* Adjust our min heap index to the pool minimum if needed. */ + min_heap_index = std::max(min_heap_index, m_min_heap_indexes[pool]); + + /* Choose a heap based on our page size request. */ + const s32 heap_index = KPageHeap::GetBlockIndex(num_pages); + R_UNLESS(0 <= heap_index, svc::ResultOutOfMemory()); + + /* Ensure that we don't leave anything un-freed. */ + ON_RESULT_FAILURE { + for (const auto &it : *out) { + auto &manager = this->GetManager(it.GetAddress()); + const size_t num_pages = std::min(it.GetNumPages(), (manager.GetEndAddress() - it.GetAddress()) / PageSize); + manager.Free(it.GetAddress(), num_pages); + } + out->Finalize(); + }; + + /* Keep allocating until we've allocated all our pages. */ + for (s32 index = heap_index; index >= min_heap_index && num_pages > 0; index--) { + const size_t pages_per_alloc = KPageHeap::GetBlockNumPages(index); + for (Impl *cur_manager = this->GetFirstManager(pool, dir); cur_manager != nullptr; cur_manager = this->GetNextManager(cur_manager, dir)) { + while (num_pages >= pages_per_alloc) { + /* Allocate a block. */ + KPhysicalAddress allocated_block = cur_manager->AllocateBlock(index, random); + if (allocated_block == Null<KPhysicalAddress>) { + break; + } + + /* Ensure we don't leak the block if we fail. */ + ON_RESULT_FAILURE { cur_manager->Free(allocated_block, pages_per_alloc); }; + + /* Add the block to our group. */ + R_TRY(out->AddBlock(allocated_block, pages_per_alloc)); + + /* Maintain the optimized memory bitmap, if we should. */ + if (unoptimized) { + cur_manager->TrackUnoptimizedAllocation(allocated_block, pages_per_alloc); + } + + num_pages -= pages_per_alloc; + } + } + } + + /* Only succeed if we allocated as many pages as we wanted. */ + R_UNLESS(num_pages == 0, svc::ResultOutOfMemory()); + + /* We succeeded! */ + R_SUCCEED(); + } + + Result KMemoryManager::AllocateAndOpen(KPageGroup *out, size_t num_pages, size_t align_pages, u32 option) { + MESOSPHERE_ASSERT(out != nullptr); + MESOSPHERE_ASSERT(out->GetNumPages() == 0); + + /* Early return if we're allocating no pages. */ + R_SUCCEED_IF(num_pages == 0); + + /* Lock the pool that we're allocating from. */ + const auto [pool, dir] = DecodeOption(option); + KScopedLightLock lk(m_pool_locks[pool]); + + /* Choose a heap based on our alignment size request. */ + const s32 heap_index = KPageHeap::GetAlignedBlockIndex(align_pages, align_pages); + + /* Allocate the page group. */ + R_TRY(this->AllocatePageGroupImpl(out, num_pages, pool, dir, m_has_optimized_process[pool], true, heap_index)); + + /* Open the first reference to the pages. */ + for (const auto &block : *out) { + KPhysicalAddress cur_address = block.GetAddress(); + size_t remaining_pages = block.GetNumPages(); + while (remaining_pages > 0) { + /* Get the manager for the current address. */ + auto &manager = this->GetManager(cur_address); + + /* Process part or all of the block. */ + const size_t cur_pages = std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address)); + manager.OpenFirst(cur_address, cur_pages); + + /* Advance. */ + cur_address += cur_pages * PageSize; + remaining_pages -= cur_pages; + } + } + + R_SUCCEED(); + } + + Result KMemoryManager::AllocateForProcess(KPageGroup *out, size_t num_pages, u32 option, u64 process_id, u8 fill_pattern) { + MESOSPHERE_ASSERT(out != nullptr); + MESOSPHERE_ASSERT(out->GetNumPages() == 0); + + /* Decode the option. */ + const auto [pool, dir] = DecodeOption(option); + + /* Allocate the memory. */ + bool optimized; + { + /* Lock the pool that we're allocating from. */ + KScopedLightLock lk(m_pool_locks[pool]); + + /* Check if we have an optimized process. */ + const bool has_optimized = m_has_optimized_process[pool]; + const bool is_optimized = m_optimized_process_ids[pool] == process_id; + + /* Always use the minimum alignment size. */ + const s32 heap_index = 0; + + /* Allocate the page group. */ + R_TRY(this->AllocatePageGroupImpl(out, num_pages, pool, dir, has_optimized && !is_optimized, false, heap_index)); + + /* Set whether we should optimize. */ + optimized = has_optimized && is_optimized; + } + + /* Perform optimized memory tracking, if we should. */ + if (optimized) { + /* Iterate over the allocated blocks. */ + for (const auto &block : *out) { + /* Get the block extents. */ + const KPhysicalAddress block_address = block.GetAddress(); + const size_t block_pages = block.GetNumPages(); + + /* If it has no pages, we don't need to do anything. */ + if (block_pages == 0) { + continue; + } + + /* Fill all the pages that we need to fill. */ + bool any_new = false; + { + KPhysicalAddress cur_address = block_address; + size_t remaining_pages = block_pages; + while (remaining_pages > 0) { + /* Get the manager for the current address. */ + auto &manager = this->GetManager(cur_address); + + /* Process part or all of the block. */ + const size_t cur_pages = std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address)); + any_new = manager.ProcessOptimizedAllocation(cur_address, cur_pages, fill_pattern); + + /* Advance. */ + cur_address += cur_pages * PageSize; + remaining_pages -= cur_pages; + } + } + + /* If there are new pages, update tracking for the allocation. */ + if (any_new) { + /* Update tracking for the allocation. */ + KPhysicalAddress cur_address = block_address; + size_t remaining_pages = block_pages; + while (remaining_pages > 0) { + /* Get the manager for the current address. */ + auto &manager = this->GetManager(cur_address); + + /* Lock the pool for the manager. */ + KScopedLightLock lk(m_pool_locks[manager.GetPool()]); + + /* Track some or all of the current pages. */ + const size_t cur_pages = std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address)); + manager.TrackOptimizedAllocation(cur_address, cur_pages); + + /* Advance. */ + cur_address += cur_pages * PageSize; + remaining_pages -= cur_pages; + } + } + } + } else { + /* Set all the allocated memory. */ + for (const auto &block : *out) { + std::memset(GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(block.GetAddress())), fill_pattern, block.GetSize()); + } + } + + R_SUCCEED(); + } + + size_t KMemoryManager::Impl::Initialize(KPhysicalAddress address, size_t size, KVirtualAddress management, KVirtualAddress management_end, Pool p) { + /* Calculate management sizes. */ + const size_t ref_count_size = (size / PageSize) * sizeof(u16); + const size_t optimize_map_size = CalculateOptimizedProcessOverheadSize(size); + const size_t manager_size = util::AlignUp(optimize_map_size + ref_count_size, PageSize); + const size_t page_heap_size = KPageHeap::CalculateManagementOverheadSize(size); + const size_t total_management_size = manager_size + page_heap_size; + MESOSPHERE_ABORT_UNLESS(manager_size <= total_management_size); + MESOSPHERE_ABORT_UNLESS(management + total_management_size <= management_end); + MESOSPHERE_ABORT_UNLESS(util::IsAligned(total_management_size, PageSize)); + + /* Setup region. */ + m_pool = p; + m_management_region = management; + m_page_reference_counts = GetPointer<RefCount>(management + optimize_map_size); + MESOSPHERE_ABORT_UNLESS(util::IsAligned(GetInteger(m_management_region), PageSize)); + + /* Initialize the manager's KPageHeap. */ + m_heap.Initialize(address, size, management + manager_size, page_heap_size); + + return total_management_size; + } + + void KMemoryManager::Impl::TrackUnoptimizedAllocation(KPhysicalAddress block, size_t num_pages) { + /* Get the range we're tracking. */ + size_t offset = this->GetPageOffset(block); + const size_t last = offset + num_pages - 1; + + /* Track. */ + u64 *optimize_map = GetPointer<u64>(m_management_region); + while (offset <= last) { + /* Mark the page as not being optimized-allocated. */ + optimize_map[offset / BITSIZEOF(u64)] &= ~(u64(1) << (offset % BITSIZEOF(u64))); + + offset++; + } + } + + void KMemoryManager::Impl::TrackOptimizedAllocation(KPhysicalAddress block, size_t num_pages) { + /* Get the range we're tracking. */ + size_t offset = this->GetPageOffset(block); + const size_t last = offset + num_pages - 1; + + /* Track. */ + u64 *optimize_map = GetPointer<u64>(m_management_region); + while (offset <= last) { + /* Mark the page as being optimized-allocated. */ + optimize_map[offset / BITSIZEOF(u64)] |= (u64(1) << (offset % BITSIZEOF(u64))); + + offset++; + } + } + + bool KMemoryManager::Impl::ProcessOptimizedAllocation(KPhysicalAddress block, size_t num_pages, u8 fill_pattern) { + /* We want to return whether any pages were newly allocated. */ + bool any_new = false; + + /* Get the range we're processing. */ + size_t offset = this->GetPageOffset(block); + const size_t last = offset + num_pages - 1; + + /* Process. */ + u64 *optimize_map = GetPointer<u64>(m_management_region); + while (offset <= last) { + /* Check if the page has been optimized-allocated before. */ + if ((optimize_map[offset / BITSIZEOF(u64)] & (u64(1) << (offset % BITSIZEOF(u64)))) == 0) { + /* If not, it's new. */ + any_new = true; + + /* Fill the page. */ + std::memset(GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(m_heap.GetAddress()) + offset * PageSize), fill_pattern, PageSize); + } + + offset++; + } + + /* Return the number of pages we processed. */ + return any_new; + } + + size_t KMemoryManager::Impl::CalculateManagementOverheadSize(size_t region_size) { + const size_t ref_count_size = (region_size / PageSize) * sizeof(u16); + const size_t optimize_map_size = (util::AlignUp((region_size / PageSize), BITSIZEOF(u64)) / BITSIZEOF(u64)) * sizeof(u64); + const size_t manager_meta_size = util::AlignUp(optimize_map_size + ref_count_size, PageSize); + const size_t page_heap_size = KPageHeap::CalculateManagementOverheadSize(region_size); + return manager_meta_size + page_heap_size; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_object_name.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_object_name.cpp new file mode 100644 index 00000000..775807cf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_object_name.cpp @@ -0,0 +1,106 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + constinit KLightLock g_object_list_lock; + constinit KObjectName::List g_object_list; + + } + + void KObjectName::Initialize(KAutoObject *obj, const char *name) { + /* Set member variables. */ + m_object = obj; + std::strncpy(m_name, name, sizeof(m_name)); + m_name[sizeof(m_name) - 1] = '\x00'; + + /* Open a reference to the object we hold. */ + m_object->Open(); + } + + bool KObjectName::MatchesName(const char *name) const { + return std::strncmp(m_name, name, sizeof(m_name)) == 0; + } + + Result KObjectName::NewFromName(KAutoObject *obj, const char *name) { + /* Create a new object name. */ + KObjectName *new_name = KObjectName::Allocate(); + R_UNLESS(new_name != nullptr, svc::ResultOutOfResource()); + + /* Initialize the new name. */ + new_name->Initialize(obj, name); + + /* Check if there's an existing name. */ + { + /* Ensure we have exclusive access to the global list. */ + KScopedLightLock lk(g_object_list_lock); + + /* If the object doesn't exist, put it into the list. */ + KScopedAutoObject existing_object = FindImpl(name); + if (existing_object.IsNull()) { + g_object_list.push_back(*new_name); + R_SUCCEED(); + } + } + + /* The object already exists, which is an error condition. Perform cleanup. */ + obj->Close(); + KObjectName::Free(new_name); + R_THROW(svc::ResultInvalidState()); + } + + Result KObjectName::Delete(KAutoObject *obj, const char *compare_name) { + /* Ensure we have exclusive access to the global list. */ + KScopedLightLock lk(g_object_list_lock); + + /* Find a matching entry in the list, and delete it. */ + for (auto &name : g_object_list) { + if (name.MatchesName(compare_name) && obj == name.GetObject()) { + /* We found a match, clean up its resources. */ + obj->Close(); + g_object_list.erase(g_object_list.iterator_to(name)); + KObjectName::Free(std::addressof(name)); + R_SUCCEED(); + } + } + + /* We didn't find the object in the list. */ + R_THROW(svc::ResultNotFound()); + } + + KScopedAutoObject<KAutoObject> KObjectName::Find(const char *name) { + /* Ensure we have exclusive access to the global list. */ + KScopedLightLock lk(g_object_list_lock); + + return FindImpl(name); + } + + KScopedAutoObject<KAutoObject> KObjectName::FindImpl(const char *compare_name) { + /* Try to find a matching object in the global list. */ + for (const auto &name : g_object_list) { + if (name.MatchesName(compare_name)) { + return name.GetObject(); + } + } + + /* There's no matching entry in the list. */ + return nullptr; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_page_group.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_page_group.cpp new file mode 100644 index 00000000..a51e0a1f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_page_group.cpp @@ -0,0 +1,181 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + void KPageGroup::Finalize() { + KBlockInfo *cur = m_first_block; + while (cur != nullptr) { + KBlockInfo *next = cur->GetNext(); + m_manager->Free(cur); + cur = next; + } + + m_first_block = nullptr; + m_last_block = nullptr; + } + + void KPageGroup::CloseAndReset() { + auto &mm = Kernel::GetMemoryManager(); + + KBlockInfo *cur = m_first_block; + while (cur != nullptr) { + KBlockInfo *next = cur->GetNext(); + mm.Close(cur->GetAddress(), cur->GetNumPages()); + m_manager->Free(cur); + cur = next; + } + + m_first_block = nullptr; + m_last_block = nullptr; + } + + size_t KPageGroup::GetNumPages() const { + size_t num_pages = 0; + + for (const auto &it : *this) { + num_pages += it.GetNumPages(); + } + + return num_pages; + } + + Result KPageGroup::AddBlock(KPhysicalAddress addr, size_t num_pages) { + /* Succeed immediately if we're adding no pages. */ + R_SUCCEED_IF(num_pages == 0); + + /* Check for overflow. */ + MESOSPHERE_ASSERT(addr < addr + num_pages * PageSize); + + /* Try to just append to the last block. */ + if (m_last_block != nullptr) { + R_SUCCEED_IF(m_last_block->TryConcatenate(addr, num_pages)); + } + + /* Allocate a new block. */ + KBlockInfo *new_block = m_manager->Allocate(); + R_UNLESS(new_block != nullptr, svc::ResultOutOfResource()); + + /* Initialize the block. */ + new_block->Initialize(addr, num_pages); + + /* Add the block to our list. */ + if (m_last_block != nullptr) { + m_last_block->SetNext(new_block); + } else { + m_first_block = new_block; + } + m_last_block = new_block; + + R_SUCCEED(); + } + + Result KPageGroup::CopyRangeTo(KPageGroup &out, size_t range_offset, size_t range_size) const { + /* Get the previous last block for the group. */ + KBlockInfo * const out_last = out.m_last_block; + const auto out_last_addr = out_last != nullptr ? out_last->GetAddress() : Null<KPhysicalAddress>; + const auto out_last_np = out_last != nullptr ? out_last->GetNumPages() : 0; + + /* Ensure we cleanup the group on failure. */ + ON_RESULT_FAILURE { + KBlockInfo *cur = out_last != nullptr ? out_last->GetNext() : out.m_first_block; + while (cur != nullptr) { + KBlockInfo *next = cur->GetNext(); + out.m_manager->Free(cur); + cur = next; + } + + if (out_last != nullptr) { + out_last->Initialize(out_last_addr, out_last_np); + out_last->SetNext(nullptr); + } else { + out.m_first_block = nullptr; + } + out.m_last_block = out_last; + }; + + /* Find the pages within the requested range. */ + size_t cur_offset = 0, remaining_size = range_size; + for (auto it = this->begin(); it != this->end() && remaining_size > 0; ++it) { + /* Get the current size. */ + const size_t cur_size = it->GetSize(); + + /* Determine if the offset is in range. */ + const size_t rel_diff = range_offset - cur_offset; + const bool is_before = cur_offset <= range_offset; + cur_offset += cur_size; + if (is_before && range_offset < cur_offset) { + /* It is, so add the block. */ + const size_t block_size = std::min<size_t>(cur_size - rel_diff, remaining_size); + R_TRY(out.AddBlock(it->GetAddress() + rel_diff, block_size / PageSize)); + + /* Advance. */ + cur_offset = range_offset + block_size; + remaining_size -= block_size; + range_offset += block_size; + } + } + + /* Check that we successfully copied the range. */ + MESOSPHERE_ABORT_UNLESS(remaining_size == 0); + + R_SUCCEED(); + } + + void KPageGroup::Open() const { + auto &mm = Kernel::GetMemoryManager(); + + for (const auto &it : *this) { + mm.Open(it.GetAddress(), it.GetNumPages()); + } + } + + void KPageGroup::OpenFirst() const { + auto &mm = Kernel::GetMemoryManager(); + + for (const auto &it : *this) { + mm.OpenFirst(it.GetAddress(), it.GetNumPages()); + } + } + + void KPageGroup::Close() const { + auto &mm = Kernel::GetMemoryManager(); + + for (const auto &it : *this) { + mm.Close(it.GetAddress(), it.GetNumPages()); + } + } + + bool KPageGroup::IsEquivalentTo(const KPageGroup &rhs) const { + auto lit = this->begin(); + auto rit = rhs.begin(); + auto lend = this->end(); + auto rend = rhs.end(); + + while (lit != lend && rit != rend) { + if (*lit != *rit) { + return false; + } + + ++lit; + ++rit; + } + + return lit == lend && rit == rend; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_page_heap.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_page_heap.cpp new file mode 100644 index 00000000..2974fd1f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_page_heap.cpp @@ -0,0 +1,238 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + void KPageHeap::Initialize(KPhysicalAddress address, size_t size, KVirtualAddress management_address, size_t management_size, const size_t *block_shifts, size_t num_block_shifts) { + /* Check our assumptions. */ + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(address), PageSize)); + MESOSPHERE_ASSERT(util::IsAligned(size, PageSize)); + MESOSPHERE_ASSERT(0 < num_block_shifts && num_block_shifts <= NumMemoryBlockPageShifts); + const KVirtualAddress management_end = management_address + management_size; + + /* Set our members. */ + m_heap_address = address; + m_heap_size = size; + m_num_blocks = num_block_shifts; + + /* Setup bitmaps. */ + u64 *cur_bitmap_storage = GetPointer<u64>(management_address); + for (size_t i = 0; i < num_block_shifts; i++) { + const size_t cur_block_shift = block_shifts[i]; + const size_t next_block_shift = (i != num_block_shifts - 1) ? block_shifts[i + 1] : 0; + cur_bitmap_storage = m_blocks[i].Initialize(m_heap_address, m_heap_size, cur_block_shift, next_block_shift, cur_bitmap_storage); + } + + /* Ensure we didn't overextend our bounds. */ + MESOSPHERE_ABORT_UNLESS(KVirtualAddress(cur_bitmap_storage) <= management_end); + } + + size_t KPageHeap::GetNumFreePages() const { + size_t num_free = 0; + + for (size_t i = 0; i < m_num_blocks; i++) { + num_free += m_blocks[i].GetNumFreePages(); + } + + return num_free; + } + + KPhysicalAddress KPageHeap::AllocateByLinearSearch(s32 index) { + const size_t needed_size = m_blocks[index].GetSize(); + + for (s32 i = index; i < static_cast<s32>(m_num_blocks); i++) { + if (const KPhysicalAddress addr = m_blocks[i].PopBlock(false); addr != Null<KPhysicalAddress>) { + if (const size_t allocated_size = m_blocks[i].GetSize(); allocated_size > needed_size) { + this->Free(addr + needed_size, (allocated_size - needed_size) / PageSize); + } + return addr; + } + } + + return Null<KPhysicalAddress>; + } + + KPhysicalAddress KPageHeap::AllocateByRandom(s32 index, size_t num_pages, size_t align_pages) { + /* Get the size and required alignment. */ + const size_t needed_size = num_pages * PageSize; + const size_t align_size = align_pages * PageSize; + + /* Determine meta-alignment of our desired alignment size. */ + const size_t align_shift = util::CountTrailingZeros(align_size); + + /* Decide on a block to allocate from. */ + constexpr size_t MinimumPossibleAlignmentsForRandomAllocation = 4; + { + /* By default, we'll want to look at all blocks larger than our current one. */ + s32 max_blocks = static_cast<s32>(m_num_blocks); + + /* Determine the maximum block we should try to allocate from. */ + size_t possible_alignments = 0; + for (s32 i = index; i < max_blocks; ++i) { + /* Add the possible alignments from blocks at the current size. */ + possible_alignments += (1 + ((m_blocks[i].GetSize() - needed_size) >> align_shift)) * m_blocks[i].GetNumFreeBlocks(); + + /* If there are enough possible alignments, we don't need to look at larger blocks. */ + if (possible_alignments >= MinimumPossibleAlignmentsForRandomAllocation) { + max_blocks = i + 1; + break; + } + } + + /* If we have any possible alignments which require a larger block, we need to pick one. */ + if (possible_alignments > 0 && index + 1 < max_blocks) { + /* Select a random alignment from the possibilities. */ + const size_t rnd = m_rng.GenerateRandom(possible_alignments); + + /* Determine which block corresponds to the random alignment we chose. */ + possible_alignments = 0; + for (s32 i = index; i < max_blocks; ++i) { + /* Add the possible alignments from blocks at the current size. */ + possible_alignments += (1 + ((m_blocks[i].GetSize() - needed_size) >> align_shift)) * m_blocks[i].GetNumFreeBlocks(); + + /* If the current block gets us to our random choice, use the current block. */ + if (rnd < possible_alignments) { + index = i; + break; + } + } + } + } + + /* Pop a block from the index we selected. */ + if (KPhysicalAddress addr = m_blocks[index].PopBlock(true); addr != Null<KPhysicalAddress>) { + /* Determine how much size we have left over. */ + if (const size_t leftover_size = m_blocks[index].GetSize() - needed_size; leftover_size > 0) { + /* Determine how many valid alignments we can have. */ + const size_t possible_alignments = 1 + (leftover_size >> align_shift); + + /* Select a random valid alignment. */ + const size_t random_offset = m_rng.GenerateRandom(possible_alignments) << align_shift; + + /* Free memory before the random offset. */ + if (random_offset != 0) { + this->Free(addr, random_offset / PageSize); + } + + /* Advance our block by the random offset. */ + addr += random_offset; + + /* Free memory after our allocated block. */ + if (random_offset != leftover_size) { + this->Free(addr + needed_size, (leftover_size - random_offset) / PageSize); + } + } + + /* Return the block we allocated. */ + return addr; + } + + return Null<KPhysicalAddress>; + } + + void KPageHeap::FreeBlock(KPhysicalAddress block, s32 index) { + do { + block = m_blocks[index++].PushBlock(block); + } while (block != Null<KPhysicalAddress>); + } + + void KPageHeap::Free(KPhysicalAddress addr, size_t num_pages) { + /* Freeing no pages is a no-op. */ + if (num_pages == 0) { + return; + } + + /* Find the largest block size that we can free, and free as many as possible. */ + s32 big_index = static_cast<s32>(m_num_blocks) - 1; + const KPhysicalAddress start = addr; + const KPhysicalAddress end = addr + num_pages * PageSize; + KPhysicalAddress before_start = start; + KPhysicalAddress before_end = start; + KPhysicalAddress after_start = end; + KPhysicalAddress after_end = end; + while (big_index >= 0) { + const size_t block_size = m_blocks[big_index].GetSize(); + const KPhysicalAddress big_start = util::AlignUp(GetInteger(start), block_size); + const KPhysicalAddress big_end = util::AlignDown(GetInteger(end), block_size); + if (big_start < big_end) { + /* Free as many big blocks as we can. */ + for (auto block = big_start; block < big_end; block += block_size) { + this->FreeBlock(block, big_index); + } + before_end = big_start; + after_start = big_end; + break; + } + big_index--; + } + MESOSPHERE_ASSERT(big_index >= 0); + + /* Free space before the big blocks. */ + for (s32 i = big_index - 1; i >= 0; i--) { + const size_t block_size = m_blocks[i].GetSize(); + while (before_start + block_size <= before_end) { + before_end -= block_size; + this->FreeBlock(before_end, i); + } + } + + /* Free space after the big blocks. */ + for (s32 i = big_index - 1; i >= 0; i--) { + const size_t block_size = m_blocks[i].GetSize(); + while (after_start + block_size <= after_end) { + this->FreeBlock(after_start, i); + after_start += block_size; + } + } + } + + size_t KPageHeap::CalculateManagementOverheadSize(size_t region_size, const size_t *block_shifts, size_t num_block_shifts) { + size_t overhead_size = 0; + for (size_t i = 0; i < num_block_shifts; i++) { + const size_t cur_block_shift = block_shifts[i]; + const size_t next_block_shift = (i != num_block_shifts - 1) ? block_shifts[i + 1] : 0; + overhead_size += KPageHeap::Block::CalculateManagementOverheadSize(region_size, cur_block_shift, next_block_shift); + } + return util::AlignUp(overhead_size, PageSize); + } + + void KPageHeap::DumpFreeList() const { + MESOSPHERE_RELEASE_LOG("KPageHeap::DumpFreeList %p\n", this); + + for (size_t i = 0; i < m_num_blocks; ++i) { + const size_t block_size = m_blocks[i].GetSize(); + const char *suffix; + size_t size; + if (block_size >= 1_GB) { + suffix = "GiB"; + size = block_size / 1_GB; + } else if (block_size >= 1_MB) { + suffix = "MiB"; + size = block_size / 1_MB; + } else if (block_size >= 1_KB) { + suffix = "KiB"; + size = block_size / 1_KB; + } else { + suffix = "B"; + size = block_size; + } + + MESOSPHERE_RELEASE_LOG(" %4zu %s block x %zu\n", size, suffix, m_blocks[i].GetNumFreeBlocks()); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_page_table_base.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_page_table_base.cpp new file mode 100644 index 00000000..ad42a62a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_page_table_base.cpp @@ -0,0 +1,5021 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> +#include <mesosphere/kern_select_page_table.hpp> + +namespace ams::kern { + + namespace { + + class KScopedLightLockPair { + NON_COPYABLE(KScopedLightLockPair); + NON_MOVEABLE(KScopedLightLockPair); + private: + KLightLock *m_lower; + KLightLock *m_upper; + public: + ALWAYS_INLINE KScopedLightLockPair(KLightLock &lhs, KLightLock &rhs) { + /* Ensure our locks are in a consistent order. */ + if (std::addressof(lhs) <= std::addressof(rhs)) { + m_lower = std::addressof(lhs); + m_upper = std::addressof(rhs); + } else { + m_lower = std::addressof(rhs); + m_upper = std::addressof(lhs); + } + + /* Acquire both locks. */ + m_lower->Lock(); + if (m_lower != m_upper) { + m_upper->Lock(); + } + } + + ~KScopedLightLockPair() { + /* Unlock the upper lock. */ + if (m_upper != nullptr && m_upper != m_lower) { + m_upper->Unlock(); + } + + /* Unlock the lower lock. */ + if (m_lower != nullptr) { + m_lower->Unlock(); + } + } + public: + /* Utility. */ + ALWAYS_INLINE void TryUnlockHalf(KLightLock &lock) { + /* Only allow unlocking if the lock is half the pair. */ + if (m_lower != m_upper) { + /* We want to be sure the lock is one we own. */ + if (m_lower == std::addressof(lock)) { + lock.Unlock(); + m_lower = nullptr; + } else if (m_upper == std::addressof(lock)) { + lock.Unlock(); + m_upper = nullptr; + } + } + } + }; + + } + + void KPageTableBase::MemoryRange::Open() { + /* If the range contains heap pages, open them. */ + if (this->IsHeap()) { + Kernel::GetMemoryManager().Open(this->GetAddress(), this->GetSize() / PageSize); + } + } + + void KPageTableBase::MemoryRange::Close() { + /* If the range contains heap pages, close them. */ + if (this->IsHeap()) { + Kernel::GetMemoryManager().Close(this->GetAddress(), this->GetSize() / PageSize); + } + } + + Result KPageTableBase::InitializeForKernel(bool is_64_bit, void *table, KVirtualAddress start, KVirtualAddress end) { + /* Initialize our members. */ + m_address_space_width = (is_64_bit) ? BITSIZEOF(u64) : BITSIZEOF(u32); + m_address_space_start = KProcessAddress(GetInteger(start)); + m_address_space_end = KProcessAddress(GetInteger(end)); + m_is_kernel = true; + m_enable_aslr = true; + m_enable_device_address_space_merge = false; + + for (auto i = 0; i < RegionType_Count; ++i) { + m_region_starts[i] = 0; + m_region_ends[i] = 0; + } + + m_current_heap_end = 0; + m_alias_code_region_start = 0; + m_alias_code_region_end = 0; + m_code_region_start = 0; + m_code_region_end = 0; + m_max_heap_size = 0; + m_mapped_physical_memory_size = 0; + m_mapped_unsafe_physical_memory = 0; + m_mapped_insecure_memory = 0; + m_mapped_ipc_server_memory = 0; + m_alias_region_extra_size = 0; + + m_memory_block_slab_manager = Kernel::GetSystemSystemResource().GetMemoryBlockSlabManagerPointer(); + m_block_info_manager = Kernel::GetSystemSystemResource().GetBlockInfoManagerPointer(); + m_resource_limit = std::addressof(Kernel::GetSystemResourceLimit()); + + m_allocate_option = KMemoryManager::EncodeOption(KMemoryManager::Pool_System, KMemoryManager::Direction_FromFront); + m_heap_fill_value = MemoryFillValue_Zero; + m_ipc_fill_value = MemoryFillValue_Zero; + m_stack_fill_value = MemoryFillValue_Zero; + + m_cached_physical_linear_region = nullptr; + m_cached_physical_heap_region = nullptr; + + /* Initialize our implementation. */ + m_impl.InitializeForKernel(table, start, end); + + /* Initialize our memory block manager. */ + R_RETURN(m_memory_block_manager.Initialize(m_address_space_start, m_address_space_end, m_memory_block_slab_manager)); + } + + Result KPageTableBase::InitializeForProcess(ams::svc::CreateProcessFlag flags, bool from_back, KMemoryManager::Pool pool, void *table, KProcessAddress start, KProcessAddress end, KProcessAddress code_address, size_t code_size, KSystemResource *system_resource, KResourceLimit *resource_limit) { + /* Validate the region. */ + MESOSPHERE_ABORT_UNLESS(start <= code_address); + MESOSPHERE_ABORT_UNLESS(code_address < code_address + code_size); + MESOSPHERE_ABORT_UNLESS(code_address + code_size - 1 <= end - 1); + + /* Define helpers. */ + auto GetSpaceStart = [&](KAddressSpaceInfo::Type type) ALWAYS_INLINE_LAMBDA { + return KAddressSpaceInfo::GetAddressSpaceStart(flags, type, code_size); + }; + auto GetSpaceSize = [&](KAddressSpaceInfo::Type type) ALWAYS_INLINE_LAMBDA { + return KAddressSpaceInfo::GetAddressSpaceSize(flags, type); + }; + + /* Default to zero alias region extra size. */ + m_alias_region_extra_size = 0; + + /* Set our width and heap/alias sizes. */ + m_address_space_width = GetAddressSpaceWidth(flags); + size_t alias_region_size = GetSpaceSize(KAddressSpaceInfo::Type_Alias); + size_t heap_region_size = GetSpaceSize(KAddressSpaceInfo::Type_Heap); + + /* Set code regions and determine remaining sizes. */ + KProcessAddress process_code_start; + KProcessAddress process_code_end; + size_t stack_region_size; + size_t kernel_map_region_size; + KProcessAddress before_process_code_start, after_process_code_start; + size_t before_process_code_size, after_process_code_size; + if (m_address_space_width == 39) { + stack_region_size = GetSpaceSize(KAddressSpaceInfo::Type_Stack); + kernel_map_region_size = GetSpaceSize(KAddressSpaceInfo::Type_MapSmall); + + m_code_region_start = GetSpaceStart(KAddressSpaceInfo::Type_Map39Bit); + m_code_region_end = m_code_region_start + GetSpaceSize(KAddressSpaceInfo::Type_Map39Bit); + m_alias_code_region_start = m_code_region_start; + m_alias_code_region_end = m_code_region_end; + + process_code_start = util::AlignDown(GetInteger(code_address), RegionAlignment); + process_code_end = util::AlignUp(GetInteger(code_address) + code_size, RegionAlignment); + + before_process_code_start = m_code_region_start; + before_process_code_size = process_code_start - before_process_code_start; + after_process_code_start = process_code_end; + after_process_code_size = m_code_region_end - process_code_end; + + /* If we have a 39-bit address space and should, enable extra size to the alias region. */ + if (flags & ams::svc::CreateProcessFlag_EnableAliasRegionExtraSize) { + /* Extra size is 1/8th of the address space. */ + m_alias_region_extra_size = (static_cast<size_t>(1) << m_address_space_width) / 8; + + alias_region_size += m_alias_region_extra_size; + } + } else { + stack_region_size = 0; + kernel_map_region_size = 0; + + m_code_region_start = GetSpaceStart(KAddressSpaceInfo::Type_MapSmall); + m_code_region_end = m_code_region_start + GetSpaceSize(KAddressSpaceInfo::Type_MapSmall); + m_alias_code_region_start = m_code_region_start; + m_alias_code_region_end = GetSpaceStart(KAddressSpaceInfo::Type_MapLarge) + GetSpaceSize(KAddressSpaceInfo::Type_MapLarge); + m_region_starts[RegionType_Stack] = m_code_region_start; + m_region_ends[RegionType_Stack] = m_code_region_end; + m_region_starts[RegionType_KernelMap] = m_code_region_start; + m_region_ends[RegionType_KernelMap] = m_code_region_end; + + process_code_start = m_code_region_start; + process_code_end = m_code_region_end; + + before_process_code_start = m_code_region_start; + before_process_code_size = 0; + after_process_code_start = GetSpaceStart(KAddressSpaceInfo::Type_MapLarge); + after_process_code_size = GetSpaceSize(KAddressSpaceInfo::Type_MapLarge); + } + + /* Set other basic fields. */ + m_enable_aslr = (flags & ams::svc::CreateProcessFlag_EnableAslr) != 0; + m_enable_device_address_space_merge = (flags & ams::svc::CreateProcessFlag_DisableDeviceAddressSpaceMerge) == 0; + m_address_space_start = start; + m_address_space_end = end; + m_is_kernel = false; + m_memory_block_slab_manager = system_resource->GetMemoryBlockSlabManagerPointer(); + m_block_info_manager = system_resource->GetBlockInfoManagerPointer(); + m_resource_limit = resource_limit; + + /* Set up our undetermined regions. */ + { + /* Declare helper structure for layout process. */ + struct RegionLayoutInfo { + size_t size; + RegionType type; + s32 alloc_index; /* 0 for before process code, 1 for after process code */ + }; + + /* Create region layout info array, and add regions to it. */ + RegionLayoutInfo region_layouts[RegionType_Count] = {}; + size_t num_regions = 0; + + if (kernel_map_region_size > 0) { region_layouts[num_regions++] = { .size = kernel_map_region_size, .type = RegionType_KernelMap, .alloc_index = 0, }; } + if (stack_region_size > 0) { region_layouts[num_regions++] = { .size = stack_region_size, .type = RegionType_Stack, .alloc_index = 0, }; } + + region_layouts[num_regions++] = { .size = alias_region_size, .type = RegionType_Alias, .alloc_index = 0, }; + region_layouts[num_regions++] = { .size = heap_region_size, .type = RegionType_Heap, .alloc_index = 0, }; + + /* Selection-sort the regions by size largest-to-smallest. */ + for (size_t i = 0; i < num_regions - 1; ++i) { + for (size_t j = i + 1; j < num_regions; ++j) { + if (region_layouts[i].size < region_layouts[j].size) { + std::swap(region_layouts[i], region_layouts[j]); + } + } + } + + /* Layout the regions. */ + constexpr auto AllocIndexCount = 2; + KProcessAddress alloc_starts[AllocIndexCount] = { before_process_code_start, after_process_code_start }; + size_t alloc_sizes[AllocIndexCount] = { before_process_code_size, after_process_code_size }; + size_t alloc_counts[AllocIndexCount] = {}; + for (size_t i = 0; i < num_regions; ++i) { + /* Get reference to the current region. */ + auto &cur_region = region_layouts[i]; + + /* Determine where the current region should go. */ + cur_region.alloc_index = alloc_sizes[1] >= alloc_sizes[0] ? 1 : 0; + ++alloc_counts[cur_region.alloc_index]; + + /* Check that the current region can fit. */ + R_UNLESS(alloc_sizes[cur_region.alloc_index] >= cur_region.size, svc::ResultOutOfMemory()); + + /* Update our remaining size tracking. */ + alloc_sizes[cur_region.alloc_index] -= cur_region.size; + } + + /* Selection sort the regions to coalesce them by alloc index. */ + for (size_t i = 0; i < num_regions - 1; ++i) { + for (size_t j = i + 1; j < num_regions; ++j) { + if (region_layouts[i].alloc_index > region_layouts[j].alloc_index) { + std::swap(region_layouts[i], region_layouts[j]); + } + } + } + + /* Layout the regions for each alloc index. */ + for (auto cur_alloc_index = 0; cur_alloc_index < AllocIndexCount; ++cur_alloc_index) { + /* If there are no regions to place, continue. */ + const size_t cur_alloc_count = alloc_counts[cur_alloc_index]; + if (cur_alloc_count == 0) { + continue; + } + + /* Determine the starting region index for the current alloc index. */ + size_t cur_region_index = 0; + for (size_t i = 0; i < num_regions; ++i) { + if (region_layouts[i].alloc_index == cur_alloc_index) { + cur_region_index = i; + break; + } + } + + /* If aslr is enabled, randomize the current region order. Otherwise, sort by type. */ + if (m_enable_aslr) { + for (size_t i = 0; i < cur_alloc_count - 1; ++i) { + std::swap(region_layouts[cur_region_index + i], region_layouts[cur_region_index + KSystemControl::GenerateRandomRange(i, cur_alloc_count - 1)]); + } + } else { + for (size_t i = 0; i < cur_alloc_count - 1; ++i) { + for (size_t j = i + 1; j < cur_alloc_count; ++j) { + if (region_layouts[cur_region_index + i].type > region_layouts[cur_region_index + j].type) { + std::swap(region_layouts[cur_region_index + i], region_layouts[cur_region_index + j]); + } + } + } + } + + /* Determine aslr offsets for the current space. */ + size_t aslr_offsets[RegionType_Count] = {}; + if (m_enable_aslr) { + /* Generate the aslr offsets. */ + for (size_t i = 0; i < cur_alloc_count; ++i) { + aslr_offsets[i] = KSystemControl::GenerateRandomRange(0, alloc_sizes[cur_alloc_index] / RegionAlignment) * RegionAlignment; + } + + /* Sort the aslr offsets. */ + for (size_t i = 0; i < cur_alloc_count - 1; ++i) { + for (size_t j = i + 1; j < cur_alloc_count; ++j) { + if (aslr_offsets[i] > aslr_offsets[j]) { + std::swap(aslr_offsets[i], aslr_offsets[j]); + } + } + } + } + + /* Calculate final region positions. */ + KProcessAddress prev_region_end = alloc_starts[cur_alloc_index]; + size_t prev_aslr_offset = 0; + for (size_t i = 0; i < cur_alloc_count; ++i) { + /* Get the current region. */ + auto &cur_region = region_layouts[cur_region_index + i]; + + /* Set the current region start/end. */ + m_region_starts[cur_region.type] = (aslr_offsets[i] - prev_aslr_offset) + GetInteger(prev_region_end); + m_region_ends[cur_region.type] = m_region_starts[cur_region.type] + cur_region.size; + + /* Update tracking variables. */ + prev_region_end = m_region_ends[cur_region.type]; + prev_aslr_offset = aslr_offsets[i]; + } + } + + /* Declare helpers to check that regions are inside our address space. */ + const KProcessAddress process_code_last = process_code_end - 1; + auto IsInAddressSpace = [&](KProcessAddress addr) ALWAYS_INLINE_LAMBDA { return m_address_space_start <= addr && addr <= m_address_space_end; }; + + /* Ensure that the KernelMap region is valid. */ + for (size_t k = 0; k < num_regions; ++k) { + if (const auto &kmap_region = region_layouts[k]; kmap_region.type == RegionType_KernelMap) { + /* If there's no kmap region, we have nothing to check. */ + if (kmap_region.size == 0) { + break; + } + + /* Check that the kmap region is within our address space. */ + MESOSPHERE_ABORT_UNLESS(IsInAddressSpace(m_region_starts[RegionType_KernelMap])); + MESOSPHERE_ABORT_UNLESS(IsInAddressSpace(m_region_ends[RegionType_KernelMap])); + + /* Check for overlap with process code. */ + const KProcessAddress kmap_start = m_region_starts[RegionType_KernelMap]; + const KProcessAddress kmap_last = m_region_ends[RegionType_KernelMap] - 1; + MESOSPHERE_ABORT_UNLESS(kernel_map_region_size == 0 || kmap_last < process_code_start || process_code_last < kmap_start); + + /* Check for overlap with stack. */ + for (size_t s = 0; s < num_regions; ++s) { + if (const auto &stack_region = region_layouts[s]; stack_region.type == RegionType_Stack) { + if (stack_region.size != 0) { + const KProcessAddress stack_start = m_region_starts[RegionType_Stack]; + const KProcessAddress stack_last = m_region_ends[RegionType_Stack] - 1; + MESOSPHERE_ABORT_UNLESS((kernel_map_region_size == 0 && stack_region_size == 0) || kmap_last < stack_start || stack_last < kmap_start); + } + break; + } + } + + /* Check for overlap with alias. */ + for (size_t a = 0; a < num_regions; ++a) { + if (const auto &alias_region = region_layouts[a]; alias_region.type == RegionType_Alias) { + if (alias_region.size != 0) { + const KProcessAddress alias_start = m_region_starts[RegionType_Alias]; + const KProcessAddress alias_last = m_region_ends[RegionType_Alias] - 1; + MESOSPHERE_ABORT_UNLESS(kmap_last < alias_start || alias_last < kmap_start); + } + break; + } + } + + /* Check for overlap with heap. */ + for (size_t h = 0; h < num_regions; ++h) { + if (const auto &heap_region = region_layouts[h]; heap_region.type == RegionType_Heap) { + if (heap_region.size != 0) { + const KProcessAddress heap_start = m_region_starts[RegionType_Heap]; + const KProcessAddress heap_last = m_region_ends[RegionType_Heap] - 1; + MESOSPHERE_ABORT_UNLESS(kmap_last < heap_start || heap_last < kmap_start); + } + break; + } + } + } + } + + /* Check that the Stack region is valid. */ + for (size_t s = 0; s < num_regions; ++s) { + if (const auto &stack_region = region_layouts[s]; stack_region.type == RegionType_Stack) { + /* If there's no stack region, we have nothing to check. */ + if (stack_region.size == 0) { + break; + } + + /* Check that the stack region is within our address space. */ + MESOSPHERE_ABORT_UNLESS(IsInAddressSpace(m_region_starts[RegionType_Stack])); + MESOSPHERE_ABORT_UNLESS(IsInAddressSpace(m_region_ends[RegionType_Stack])); + + /* Check for overlap with process code. */ + const KProcessAddress stack_start = m_region_starts[RegionType_Stack]; + const KProcessAddress stack_last = m_region_ends[RegionType_Stack] - 1; + MESOSPHERE_ABORT_UNLESS(stack_region_size == 0 || stack_last < process_code_start || process_code_last < stack_start); + + /* Check for overlap with alias. */ + for (size_t a = 0; a < num_regions; ++a) { + if (const auto &alias_region = region_layouts[a]; alias_region.type == RegionType_Alias) { + if (alias_region.size != 0) { + const KProcessAddress alias_start = m_region_starts[RegionType_Alias]; + const KProcessAddress alias_last = m_region_ends[RegionType_Alias] - 1; + MESOSPHERE_ABORT_UNLESS(stack_last < alias_start || alias_last < stack_start); + } + break; + } + } + + /* Check for overlap with heap. */ + for (size_t h = 0; h < num_regions; ++h) { + if (const auto &heap_region = region_layouts[h]; heap_region.type == RegionType_Heap) { + if (heap_region.size != 0) { + const KProcessAddress heap_start = m_region_starts[RegionType_Heap]; + const KProcessAddress heap_last = m_region_ends[RegionType_Heap] - 1; + MESOSPHERE_ABORT_UNLESS(stack_last < heap_start || heap_last < stack_start); + } + break; + } + } + } + } + + /* Check that the Alias region is valid. */ + for (size_t a = 0; a < num_regions; ++a) { + if (const auto &alias_region = region_layouts[a]; alias_region.type == RegionType_Alias) { + /* If there's no alias region, we have nothing to check. */ + if (alias_region.size == 0) { + break; + } + + /* Check that the alias region is within our address space. */ + MESOSPHERE_ABORT_UNLESS(IsInAddressSpace(m_region_starts[RegionType_Alias])); + MESOSPHERE_ABORT_UNLESS(IsInAddressSpace(m_region_ends[RegionType_Alias])); + + /* Check for overlap with process code. */ + const KProcessAddress alias_start = m_region_starts[RegionType_Alias]; + const KProcessAddress alias_last = m_region_ends[RegionType_Alias] - 1; + MESOSPHERE_ABORT_UNLESS(alias_last < process_code_start || process_code_last < alias_start); + + /* Check for overlap with heap. */ + for (size_t h = 0; h < num_regions; ++h) { + if (const auto &heap_region = region_layouts[h]; heap_region.type == RegionType_Heap) { + if (heap_region.size != 0) { + const KProcessAddress heap_start = m_region_starts[RegionType_Heap]; + const KProcessAddress heap_last = m_region_ends[RegionType_Heap] - 1; + MESOSPHERE_ABORT_UNLESS(alias_last < heap_start || heap_last < alias_start); + } + break; + } + } + } + } + + /* Check that the Heap region is valid. */ + for (size_t h = 0; h < num_regions; ++h) { + if (const auto &heap_region = region_layouts[h]; heap_region.type == RegionType_Heap) { + /* If there's no heap region, we have nothing to check. */ + if (heap_region.size == 0) { + break; + } + + /* Check that the heap region is within our address space. */ + MESOSPHERE_ABORT_UNLESS(IsInAddressSpace(m_region_starts[RegionType_Heap])); + MESOSPHERE_ABORT_UNLESS(IsInAddressSpace(m_region_ends[RegionType_Heap])); + + /* Check for overlap with process code. */ + const KProcessAddress heap_start = m_region_starts[RegionType_Heap]; + const KProcessAddress heap_last = m_region_ends[RegionType_Heap] - 1; + MESOSPHERE_ABORT_UNLESS(heap_last < process_code_start || process_code_last < heap_start); + } + } + } + + /* Set heap and fill members. */ + m_current_heap_end = m_region_starts[RegionType_Heap]; + m_max_heap_size = 0; + m_mapped_physical_memory_size = 0; + m_mapped_unsafe_physical_memory = 0; + m_mapped_insecure_memory = 0; + m_mapped_ipc_server_memory = 0; + + const bool fill_memory = KTargetSystem::IsDebugMemoryFillEnabled(); + m_heap_fill_value = fill_memory ? MemoryFillValue_Heap : MemoryFillValue_Zero; + m_ipc_fill_value = fill_memory ? MemoryFillValue_Ipc : MemoryFillValue_Zero; + m_stack_fill_value = fill_memory ? MemoryFillValue_Stack : MemoryFillValue_Zero; + + /* Set allocation option. */ + m_allocate_option = KMemoryManager::EncodeOption(pool, from_back ? KMemoryManager::Direction_FromBack : KMemoryManager::Direction_FromFront); + + /* Initialize our implementation. */ + m_impl.InitializeForProcess(table, GetInteger(start), GetInteger(end)); + + /* Initialize our memory block manager. */ + R_RETURN(m_memory_block_manager.Initialize(m_address_space_start, m_address_space_end, m_memory_block_slab_manager)); + } + + + void KPageTableBase::Finalize() { + /* Finalize memory blocks. */ + m_memory_block_manager.Finalize(m_memory_block_slab_manager); + + /* Free any unsafe mapped memory. */ + if (m_mapped_unsafe_physical_memory) { + Kernel::GetUnsafeMemory().Release(m_mapped_unsafe_physical_memory); + } + + /* Release any insecure mapped memory. */ + if (m_mapped_insecure_memory) { + if (auto * const insecure_resource_limit = KSystemControl::GetInsecureMemoryResourceLimit(); insecure_resource_limit != nullptr) { + insecure_resource_limit->Release(ams::svc::LimitableResource_PhysicalMemoryMax, m_mapped_insecure_memory); + } + } + + /* Release any ipc server memory. */ + if (m_mapped_ipc_server_memory) { + m_resource_limit->Release(ams::svc::LimitableResource_PhysicalMemoryMax, m_mapped_ipc_server_memory); + } + + /* Invalidate the entire instruction cache. */ + cpu::InvalidateEntireInstructionCache(); + } + + KProcessAddress KPageTableBase::GetRegionAddress(ams::svc::MemoryState state) const { + switch (state) { + case ams::svc::MemoryState_Free: + case ams::svc::MemoryState_Kernel: + return m_address_space_start; + case ams::svc::MemoryState_Normal: + return m_region_starts[RegionType_Heap]; + case ams::svc::MemoryState_Ipc: + case ams::svc::MemoryState_NonSecureIpc: + case ams::svc::MemoryState_NonDeviceIpc: + return m_region_starts[RegionType_Alias]; + case ams::svc::MemoryState_Stack: + return m_region_starts[RegionType_Stack]; + case ams::svc::MemoryState_Static: + case ams::svc::MemoryState_ThreadLocal: + return m_region_starts[RegionType_KernelMap]; + case ams::svc::MemoryState_Io: + case ams::svc::MemoryState_Shared: + case ams::svc::MemoryState_AliasCode: + case ams::svc::MemoryState_AliasCodeData: + case ams::svc::MemoryState_Transfered: + case ams::svc::MemoryState_SharedTransfered: + case ams::svc::MemoryState_SharedCode: + case ams::svc::MemoryState_GeneratedCode: + case ams::svc::MemoryState_CodeOut: + case ams::svc::MemoryState_Coverage: + case ams::svc::MemoryState_Insecure: + return m_alias_code_region_start; + case ams::svc::MemoryState_Code: + case ams::svc::MemoryState_CodeData: + return m_code_region_start; + MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); + } + } + + size_t KPageTableBase::GetRegionSize(ams::svc::MemoryState state) const { + switch (state) { + case ams::svc::MemoryState_Free: + case ams::svc::MemoryState_Kernel: + return m_address_space_end - m_address_space_start; + case ams::svc::MemoryState_Normal: + return m_region_ends[RegionType_Heap] - m_region_starts[RegionType_Heap]; + case ams::svc::MemoryState_Ipc: + case ams::svc::MemoryState_NonSecureIpc: + case ams::svc::MemoryState_NonDeviceIpc: + return m_region_ends[RegionType_Alias] - m_region_starts[RegionType_Alias]; + case ams::svc::MemoryState_Stack: + return m_region_ends[RegionType_Stack] - m_region_starts[RegionType_Stack]; + case ams::svc::MemoryState_Static: + case ams::svc::MemoryState_ThreadLocal: + return m_region_ends[RegionType_KernelMap] - m_region_starts[RegionType_KernelMap]; + case ams::svc::MemoryState_Io: + case ams::svc::MemoryState_Shared: + case ams::svc::MemoryState_AliasCode: + case ams::svc::MemoryState_AliasCodeData: + case ams::svc::MemoryState_Transfered: + case ams::svc::MemoryState_SharedTransfered: + case ams::svc::MemoryState_SharedCode: + case ams::svc::MemoryState_GeneratedCode: + case ams::svc::MemoryState_CodeOut: + case ams::svc::MemoryState_Coverage: + case ams::svc::MemoryState_Insecure: + return m_alias_code_region_end - m_alias_code_region_start; + case ams::svc::MemoryState_Code: + case ams::svc::MemoryState_CodeData: + return m_code_region_end - m_code_region_start; + MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); + } + } + + bool KPageTableBase::CanContain(KProcessAddress addr, size_t size, ams::svc::MemoryState state) const { + const KProcessAddress end = addr + size; + const KProcessAddress last = end - 1; + + const KProcessAddress region_start = this->GetRegionAddress(state); + const size_t region_size = this->GetRegionSize(state); + + const bool is_in_region = region_start <= addr && addr < end && last <= region_start + region_size - 1; + const bool is_in_heap = !(end <= m_region_starts[RegionType_Heap] || m_region_ends[RegionType_Heap] <= addr || m_region_starts[RegionType_Heap] == m_region_ends[RegionType_Heap]); + const bool is_in_alias = !(end <= m_region_starts[RegionType_Alias] || m_region_ends[RegionType_Alias] <= addr || m_region_starts[RegionType_Alias] == m_region_ends[RegionType_Alias]); + switch (state) { + case ams::svc::MemoryState_Free: + case ams::svc::MemoryState_Kernel: + return is_in_region; + case ams::svc::MemoryState_Io: + case ams::svc::MemoryState_Static: + case ams::svc::MemoryState_Code: + case ams::svc::MemoryState_CodeData: + case ams::svc::MemoryState_Shared: + case ams::svc::MemoryState_AliasCode: + case ams::svc::MemoryState_AliasCodeData: + case ams::svc::MemoryState_Stack: + case ams::svc::MemoryState_ThreadLocal: + case ams::svc::MemoryState_Transfered: + case ams::svc::MemoryState_SharedTransfered: + case ams::svc::MemoryState_SharedCode: + case ams::svc::MemoryState_GeneratedCode: + case ams::svc::MemoryState_CodeOut: + case ams::svc::MemoryState_Coverage: + case ams::svc::MemoryState_Insecure: + return is_in_region && !is_in_heap && !is_in_alias; + case ams::svc::MemoryState_Normal: + MESOSPHERE_ASSERT(is_in_heap); + return is_in_region && !is_in_alias; + case ams::svc::MemoryState_Ipc: + case ams::svc::MemoryState_NonSecureIpc: + case ams::svc::MemoryState_NonDeviceIpc: + MESOSPHERE_ASSERT(is_in_alias); + return is_in_region && !is_in_heap; + default: + return false; + } + } + + Result KPageTableBase::CheckMemoryState(KMemoryBlockManager::const_iterator it, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr) const { + /* Validate the states match expectation. */ + R_UNLESS((it->GetState() & state_mask) == state, svc::ResultInvalidCurrentMemory()); + R_UNLESS((it->GetPermission() & perm_mask) == perm, svc::ResultInvalidCurrentMemory()); + R_UNLESS((it->GetAttribute() & attr_mask) == attr, svc::ResultInvalidCurrentMemory()); + + R_SUCCEED(); + } + + Result KPageTableBase::CheckMemoryStateContiguous(size_t *out_blocks_needed, KProcessAddress addr, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr) const { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + /* Get information about the first block. */ + const KProcessAddress last_addr = addr + size - 1; + KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(addr); + + /* If the start address isn't aligned, we need a block. */ + const size_t blocks_for_start_align = (util::AlignDown(GetInteger(addr), PageSize) != it->GetAddress()) ? 1 : 0; + + while (true) { + /* Validate against the provided masks. */ + R_TRY(this->CheckMemoryState(it, state_mask, state, perm_mask, perm, attr_mask, attr)); + + /* Break once we're done. */ + if (last_addr <= it->GetLastAddress()) { + break; + } + + /* Advance our iterator. */ + it++; + MESOSPHERE_ASSERT(it != m_memory_block_manager.cend()); + } + + /* If the end address isn't aligned, we need a block. */ + const size_t blocks_for_end_align = (util::AlignUp(GetInteger(addr) + size, PageSize) != it->GetEndAddress()) ? 1 : 0; + + if (out_blocks_needed != nullptr) { + *out_blocks_needed = blocks_for_start_align + blocks_for_end_align; + } + + R_SUCCEED(); + } + + Result KPageTableBase::CheckMemoryState(KMemoryState *out_state, KMemoryPermission *out_perm, KMemoryAttribute *out_attr, size_t *out_blocks_needed, KMemoryBlockManager::const_iterator it, KProcessAddress last_addr, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr, u32 ignore_attr) const { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + /* Validate all blocks in the range have correct state. */ + const KMemoryState first_state = it->GetState(); + const KMemoryPermission first_perm = it->GetPermission(); + const KMemoryAttribute first_attr = it->GetAttribute(); + while (true) { + /* Validate the current block. */ + R_UNLESS(it->GetState() == first_state, svc::ResultInvalidCurrentMemory()); + R_UNLESS(it->GetPermission() == first_perm, svc::ResultInvalidCurrentMemory()); + R_UNLESS((it->GetAttribute() | ignore_attr) == (first_attr | ignore_attr), svc::ResultInvalidCurrentMemory()); + + /* Validate against the provided masks. */ + R_TRY(this->CheckMemoryState(it, state_mask, state, perm_mask, perm, attr_mask, attr)); + + /* Break once we're done. */ + if (last_addr <= it->GetLastAddress()) { + break; + } + + /* Advance our iterator. */ + it++; + MESOSPHERE_ASSERT(it != m_memory_block_manager.cend()); + } + + /* Write output state. */ + if (out_state != nullptr) { + *out_state = first_state; + } + if (out_perm != nullptr) { + *out_perm = first_perm; + } + if (out_attr != nullptr) { + *out_attr = static_cast<KMemoryAttribute>(first_attr & ~ignore_attr); + } + + /* If the end address isn't aligned, we need a block. */ + if (out_blocks_needed != nullptr) { + const size_t blocks_for_end_align = (util::AlignDown(GetInteger(last_addr), PageSize) + PageSize != it->GetEndAddress()) ? 1 : 0; + *out_blocks_needed = blocks_for_end_align; + } + + R_SUCCEED(); + } + + Result KPageTableBase::CheckMemoryState(KMemoryState *out_state, KMemoryPermission *out_perm, KMemoryAttribute *out_attr, size_t *out_blocks_needed, KProcessAddress addr, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr, u32 ignore_attr) const { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + /* Check memory state. */ + const KProcessAddress last_addr = addr + size - 1; + KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(addr); + R_TRY(this->CheckMemoryState(out_state, out_perm, out_attr, out_blocks_needed, it, last_addr, state_mask, state, perm_mask, perm, attr_mask, attr, ignore_attr)); + + /* If the start address isn't aligned, we need a block. */ + if (out_blocks_needed != nullptr && util::AlignDown(GetInteger(addr), PageSize) != it->GetAddress()) { + ++(*out_blocks_needed); + } + + R_SUCCEED(); + } + + Result KPageTableBase::LockMemoryAndOpen(KPageGroup *out_pg, KPhysicalAddress *out_paddr, KProcessAddress addr, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr, KMemoryPermission new_perm, u32 lock_attr) { + /* Validate basic preconditions. */ + MESOSPHERE_ASSERT((lock_attr & attr) == 0); + MESOSPHERE_ASSERT((lock_attr & (KMemoryAttribute_IpcLocked | KMemoryAttribute_DeviceShared)) == 0); + + /* Validate the lock request. */ + const size_t num_pages = size / PageSize; + R_UNLESS(this->Contains(addr, size), svc::ResultInvalidCurrentMemory()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check that the output page group is empty, if it exists. */ + if (out_pg) { + MESOSPHERE_ASSERT(out_pg->GetNumPages() == 0); + } + + /* Check the state. */ + KMemoryState old_state; + KMemoryPermission old_perm; + KMemoryAttribute old_attr; + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm), std::addressof(old_attr), std::addressof(num_allocator_blocks), addr, size, state_mask | KMemoryState_FlagReferenceCounted, state | KMemoryState_FlagReferenceCounted, perm_mask, perm, attr_mask, attr)); + + /* Get the physical address, if we're supposed to. */ + if (out_paddr != nullptr) { + MESOSPHERE_ABORT_UNLESS(this->GetPhysicalAddressLocked(out_paddr, addr)); + } + + /* Make the page group, if we're supposed to. */ + if (out_pg != nullptr) { + R_TRY(this->MakePageGroup(*out_pg, addr, num_pages)); + } + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* Decide on new perm and attr. */ + new_perm = (new_perm != KMemoryPermission_None) ? new_perm : old_perm; + KMemoryAttribute new_attr = static_cast<KMemoryAttribute>(old_attr | lock_attr); + + /* Update permission, if we need to. */ + if (new_perm != old_perm) { + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + const KPageProperties properties = { new_perm, false, (old_attr & KMemoryAttribute_Uncached) != 0, DisableMergeAttribute_DisableHeadBodyTail }; + R_TRY(this->Operate(updater.GetPageList(), addr, num_pages, Null<KPhysicalAddress>, false, properties, OperationType_ChangePermissions, false)); + } + + /* Apply the memory block updates. */ + m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, new_perm, new_attr, KMemoryBlockDisableMergeAttribute_Locked, KMemoryBlockDisableMergeAttribute_None); + + /* If we have an output group, open. */ + if (out_pg) { + out_pg->Open(); + } + + R_SUCCEED(); + } + + Result KPageTableBase::UnlockMemory(KProcessAddress addr, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr, KMemoryPermission new_perm, u32 lock_attr, const KPageGroup *pg) { + /* Validate basic preconditions. */ + MESOSPHERE_ASSERT((attr_mask & lock_attr) == lock_attr); + MESOSPHERE_ASSERT((attr & lock_attr) == lock_attr); + + /* Validate the unlock request. */ + const size_t num_pages = size / PageSize; + R_UNLESS(this->Contains(addr, size), svc::ResultInvalidCurrentMemory()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check the state. */ + KMemoryState old_state; + KMemoryPermission old_perm; + KMemoryAttribute old_attr; + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm), std::addressof(old_attr), std::addressof(num_allocator_blocks), addr, size, state_mask | KMemoryState_FlagReferenceCounted, state | KMemoryState_FlagReferenceCounted, perm_mask, perm, attr_mask, attr)); + + /* Check the page group. */ + if (pg != nullptr) { + R_UNLESS(this->IsValidPageGroup(*pg, addr, num_pages), svc::ResultInvalidMemoryRegion()); + } + + /* Decide on new perm and attr. */ + new_perm = (new_perm != KMemoryPermission_None) ? new_perm : old_perm; + KMemoryAttribute new_attr = static_cast<KMemoryAttribute>(old_attr & ~lock_attr); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* Update permission, if we need to. */ + if (new_perm != old_perm) { + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + const KPageProperties properties = { new_perm, false, (old_attr & KMemoryAttribute_Uncached) != 0, DisableMergeAttribute_EnableAndMergeHeadBodyTail }; + R_TRY(this->Operate(updater.GetPageList(), addr, num_pages, Null<KPhysicalAddress>, false, properties, OperationType_ChangePermissions, false)); + } + + /* Apply the memory block updates. */ + m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, new_perm, new_attr, KMemoryBlockDisableMergeAttribute_None, KMemoryBlockDisableMergeAttribute_Locked); + + R_SUCCEED(); + } + + Result KPageTableBase::QueryInfoImpl(KMemoryInfo *out_info, ams::svc::PageInfo *out_page, KProcessAddress address) const { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + MESOSPHERE_ASSERT(out_info != nullptr); + MESOSPHERE_ASSERT(out_page != nullptr); + + const KMemoryBlock *block = m_memory_block_manager.FindBlock(address); + R_UNLESS(block != nullptr, svc::ResultInvalidCurrentMemory()); + + *out_info = block->GetMemoryInfo(); + out_page->flags = 0; + R_SUCCEED(); + } + + Result KPageTableBase::QueryMappingImpl(KProcessAddress *out, KPhysicalAddress address, size_t size, ams::svc::MemoryState state) const { + MESOSPHERE_ASSERT(!this->IsLockedByCurrentThread()); + MESOSPHERE_ASSERT(out != nullptr); + + const KProcessAddress region_start = this->GetRegionAddress(state); + const size_t region_size = this->GetRegionSize(state); + + /* Check that the address/size are potentially valid. */ + R_UNLESS((address < address + size), svc::ResultNotFound()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + auto &impl = this->GetImpl(); + + /* Begin traversal. */ + TraversalContext context; + TraversalEntry cur_entry = { .phys_addr = Null<KPhysicalAddress>, .block_size = 0, .sw_reserved_bits = 0, .attr = 0 }; + bool cur_valid = false; + TraversalEntry next_entry; + bool next_valid; + size_t tot_size = 0; + + next_valid = impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), region_start); + next_entry.block_size = (next_entry.block_size - (GetInteger(region_start) & (next_entry.block_size - 1))); + + /* Iterate, looking for entry. */ + while (true) { + if ((!next_valid && !cur_valid) || (next_valid && cur_valid && next_entry.phys_addr == cur_entry.phys_addr + cur_entry.block_size)) { + cur_entry.block_size += next_entry.block_size; + } else { + if (cur_valid && cur_entry.phys_addr <= address && address + size <= cur_entry.phys_addr + cur_entry.block_size) { + /* Check if this region is valid. */ + const KProcessAddress mapped_address = (region_start + tot_size) + (address - cur_entry.phys_addr); + if (R_SUCCEEDED(this->CheckMemoryState(mapped_address, size, KMemoryState_Mask, static_cast<KMemoryState>(util::ToUnderlying(state)), KMemoryPermission_UserRead, KMemoryPermission_UserRead, KMemoryAttribute_None, KMemoryAttribute_None))) { + /* It is! */ + *out = mapped_address; + R_SUCCEED(); + } + } + + /* Update tracking variables. */ + tot_size += cur_entry.block_size; + cur_entry = next_entry; + cur_valid = next_valid; + } + + if (cur_entry.block_size + tot_size >= region_size) { + break; + } + + next_valid = impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + } + + /* Check the last entry. */ + R_UNLESS(cur_valid, svc::ResultNotFound()); + R_UNLESS(cur_entry.phys_addr <= address, svc::ResultNotFound()); + R_UNLESS(address + size <= cur_entry.phys_addr + cur_entry.block_size, svc::ResultNotFound()); + + /* Check if the last region is valid. */ + const KProcessAddress mapped_address = (region_start + tot_size) + (address - cur_entry.phys_addr); + R_TRY_CATCH(this->CheckMemoryState(mapped_address, size, KMemoryState_All, state, KMemoryPermission_UserRead, KMemoryPermission_UserRead, KMemoryAttribute_None, KMemoryAttribute_None)) { + R_CONVERT_ALL(svc::ResultNotFound()); + } R_END_TRY_CATCH; + + /* We found the region. */ + *out = mapped_address; + R_SUCCEED(); + } + + Result KPageTableBase::MapMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size) { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Validate that the source address's state is valid. */ + KMemoryState src_state; + size_t num_src_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(src_state), nullptr, nullptr, std::addressof(num_src_allocator_blocks), src_address, size, KMemoryState_FlagCanAlias, KMemoryState_FlagCanAlias, KMemoryPermission_All, KMemoryPermission_UserReadWrite, KMemoryAttribute_All, KMemoryAttribute_None)); + + /* Validate that the dst address's state is valid. */ + size_t num_dst_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(num_dst_allocator_blocks), dst_address, size, KMemoryState_All, KMemoryState_Free, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryAttribute_None)); + + /* Create an update allocator for the source. */ + Result src_allocator_result; + KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result), m_memory_block_slab_manager, num_src_allocator_blocks); + R_TRY(src_allocator_result); + + /* Create an update allocator for the destination. */ + Result dst_allocator_result; + KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result), m_memory_block_slab_manager, num_dst_allocator_blocks); + R_TRY(dst_allocator_result); + + /* Map the memory. */ + { + /* Determine the number of pages being operated on. */ + const size_t num_pages = size / PageSize; + + /* Create page groups for the memory being unmapped. */ + KPageGroup pg(m_block_info_manager); + + /* Create the page group representing the source. */ + R_TRY(this->MakePageGroup(pg, src_address, num_pages)); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Reprotect the source as kernel-read/not mapped. */ + const KMemoryPermission new_src_perm = static_cast<KMemoryPermission>(KMemoryPermission_KernelRead | KMemoryPermission_NotMapped); + const KMemoryAttribute new_src_attr = KMemoryAttribute_Locked; + const KPageProperties src_properties = { new_src_perm, false, false, DisableMergeAttribute_DisableHeadBodyTail }; + R_TRY(this->Operate(updater.GetPageList(), src_address, num_pages, Null<KPhysicalAddress>, false, src_properties, OperationType_ChangePermissions, false)); + + /* Ensure that we unprotect the source pages on failure. */ + ON_RESULT_FAILURE { + const KPageProperties unprotect_properties = { KMemoryPermission_UserReadWrite, false, false, DisableMergeAttribute_EnableHeadBodyTail }; + MESOSPHERE_R_ABORT_UNLESS(this->Operate(updater.GetPageList(), src_address, num_pages, Null<KPhysicalAddress>, false, unprotect_properties, OperationType_ChangePermissions, true)); + }; + + /* Map the alias pages. */ + const KPageProperties dst_map_properties = { KMemoryPermission_UserReadWrite, false, false, DisableMergeAttribute_DisableHead }; + R_TRY(this->MapPageGroupImpl(updater.GetPageList(), dst_address, pg, dst_map_properties, false)); + + /* Apply the memory block updates. */ + m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, src_state, new_src_perm, new_src_attr, KMemoryBlockDisableMergeAttribute_Locked, KMemoryBlockDisableMergeAttribute_None); + m_memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages, KMemoryState_Stack, KMemoryPermission_UserReadWrite, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_Normal, KMemoryBlockDisableMergeAttribute_None); + } + + R_SUCCEED(); + } + + Result KPageTableBase::UnmapMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size) { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Validate that the source address's state is valid. */ + KMemoryState src_state; + size_t num_src_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(src_state), nullptr, nullptr, std::addressof(num_src_allocator_blocks), src_address, size, KMemoryState_FlagCanAlias, KMemoryState_FlagCanAlias, KMemoryPermission_All, KMemoryPermission_NotMapped | KMemoryPermission_KernelRead, KMemoryAttribute_All, KMemoryAttribute_Locked)); + + /* Validate that the dst address's state is valid. */ + KMemoryPermission dst_perm; + size_t num_dst_allocator_blocks; + R_TRY(this->CheckMemoryState(nullptr, std::addressof(dst_perm), nullptr, std::addressof(num_dst_allocator_blocks), dst_address, size, KMemoryState_All, KMemoryState_Stack, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_All, KMemoryAttribute_None)); + + /* Create an update allocator for the source. */ + Result src_allocator_result; + KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result), m_memory_block_slab_manager, num_src_allocator_blocks); + R_TRY(src_allocator_result); + + /* Create an update allocator for the destination. */ + Result dst_allocator_result; + KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result), m_memory_block_slab_manager, num_dst_allocator_blocks); + R_TRY(dst_allocator_result); + + /* Unmap the memory. */ + { + /* Determine the number of pages being operated on. */ + const size_t num_pages = size / PageSize; + + /* Create page groups for the memory being unmapped. */ + KPageGroup pg(m_block_info_manager); + + /* Create the page group representing the destination. */ + R_TRY(this->MakePageGroup(pg, dst_address, num_pages)); + + /* Ensure the page group is the valid for the source. */ + R_UNLESS(this->IsValidPageGroup(pg, src_address, num_pages), svc::ResultInvalidMemoryRegion()); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Unmap the aliased copy of the pages. */ + const KPageProperties dst_unmap_properties = { KMemoryPermission_None, false, false, DisableMergeAttribute_None }; + R_TRY(this->Operate(updater.GetPageList(), dst_address, num_pages, Null<KPhysicalAddress>, false, dst_unmap_properties, OperationType_Unmap, false)); + + /* Ensure that we re-map the aliased pages on failure. */ + ON_RESULT_FAILURE { + this->RemapPageGroup(updater.GetPageList(), dst_address, size, pg); + }; + + /* Try to set the permissions for the source pages back to what they should be. */ + const KPageProperties src_properties = { KMemoryPermission_UserReadWrite, false, false, DisableMergeAttribute_EnableAndMergeHeadBodyTail }; + R_TRY(this->Operate(updater.GetPageList(), src_address, num_pages, Null<KPhysicalAddress>, false, src_properties, OperationType_ChangePermissions, false)); + + /* Apply the memory block updates. */ + m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, src_state, KMemoryPermission_UserReadWrite, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_None, KMemoryBlockDisableMergeAttribute_Locked); + m_memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages, KMemoryState_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_None, KMemoryBlockDisableMergeAttribute_Normal); + } + + R_SUCCEED(); + } + + Result KPageTableBase::MapCodeMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size) { + /* Validate the mapping request. */ + R_UNLESS(this->CanContain(dst_address, size, KMemoryState_AliasCode), svc::ResultInvalidMemoryRegion()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Verify that the source memory is normal heap. */ + KMemoryState src_state; + KMemoryPermission src_perm; + size_t num_src_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(src_state), std::addressof(src_perm), nullptr, std::addressof(num_src_allocator_blocks), src_address, size, KMemoryState_All, KMemoryState_Normal, KMemoryPermission_All, KMemoryPermission_UserReadWrite, KMemoryAttribute_All, KMemoryAttribute_None)); + + /* Verify that the destination memory is unmapped. */ + size_t num_dst_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(num_dst_allocator_blocks), dst_address, size, KMemoryState_All, KMemoryState_Free, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryAttribute_None)); + + /* Create an update allocator for the source. */ + Result src_allocator_result; + KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result), m_memory_block_slab_manager, num_src_allocator_blocks); + R_TRY(src_allocator_result); + + /* Create an update allocator for the destination. */ + Result dst_allocator_result; + KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result), m_memory_block_slab_manager, num_dst_allocator_blocks); + R_TRY(dst_allocator_result); + + /* Map the code memory. */ + { + /* Determine the number of pages being operated on. */ + const size_t num_pages = size / PageSize; + + /* Create page groups for the memory being unmapped. */ + KPageGroup pg(m_block_info_manager); + + /* Create the page group representing the source. */ + R_TRY(this->MakePageGroup(pg, src_address, num_pages)); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Reprotect the source as kernel-read/not mapped. */ + const KMemoryPermission new_perm = static_cast<KMemoryPermission>(KMemoryPermission_KernelRead | KMemoryPermission_NotMapped); + const KPageProperties src_properties = { new_perm, false, false, DisableMergeAttribute_DisableHeadBodyTail }; + R_TRY(this->Operate(updater.GetPageList(), src_address, num_pages, Null<KPhysicalAddress>, false, src_properties, OperationType_ChangePermissions, false)); + + /* Ensure that we unprotect the source pages on failure. */ + ON_RESULT_FAILURE { + const KPageProperties unprotect_properties = { src_perm, false, false, DisableMergeAttribute_EnableHeadBodyTail }; + MESOSPHERE_R_ABORT_UNLESS(this->Operate(updater.GetPageList(), src_address, num_pages, Null<KPhysicalAddress>, false, unprotect_properties, OperationType_ChangePermissions, true)); + }; + + /* Map the alias pages. */ + const KPageProperties dst_properties = { new_perm, false, false, DisableMergeAttribute_DisableHead }; + R_TRY(this->MapPageGroupImpl(updater.GetPageList(), dst_address, pg, dst_properties, false)); + + /* Apply the memory block updates. */ + m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, src_state, new_perm, KMemoryAttribute_Locked, KMemoryBlockDisableMergeAttribute_Locked, KMemoryBlockDisableMergeAttribute_None); + m_memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages, KMemoryState_AliasCode, new_perm, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_Normal, KMemoryBlockDisableMergeAttribute_None); + } + + R_SUCCEED(); + } + + Result KPageTableBase::UnmapCodeMemory(KProcessAddress dst_address, KProcessAddress src_address, size_t size) { + /* Validate the mapping request. */ + R_UNLESS(this->CanContain(dst_address, size, KMemoryState_AliasCode), svc::ResultInvalidMemoryRegion()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Verify that the source memory is locked normal heap. */ + size_t num_src_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(num_src_allocator_blocks), src_address, size, KMemoryState_All, KMemoryState_Normal, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_All, KMemoryAttribute_Locked)); + + /* Verify that the destination memory is aliasable code. */ + size_t num_dst_allocator_blocks; + R_TRY(this->CheckMemoryStateContiguous(std::addressof(num_dst_allocator_blocks), dst_address, size, KMemoryState_FlagCanCodeAlias, KMemoryState_FlagCanCodeAlias, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_All & ~KMemoryAttribute_PermissionLocked, KMemoryAttribute_None)); + + /* Determine whether any pages being unmapped are code. */ + bool any_code_pages = false; + { + KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(dst_address); + while (true) { + /* Check if the memory has code flag. */ + if ((it->GetState() & KMemoryState_FlagCode) != 0) { + any_code_pages = true; + break; + } + + /* Check if we're done. */ + if (dst_address + size - 1 <= it->GetLastAddress()) { + break; + } + + /* Advance. */ + ++it; + } + } + + /* Ensure that we maintain the instruction cache. */ + bool reprotected_pages = false; + ON_SCOPE_EXIT { + if (reprotected_pages && any_code_pages) { + cpu::InvalidateEntireInstructionCache(); + } + }; + + /* Unmap. */ + { + /* Determine the number of pages being operated on. */ + const size_t num_pages = size / PageSize; + + /* Create page groups for the memory being unmapped. */ + KPageGroup pg(m_block_info_manager); + + /* Create the page group representing the destination. */ + R_TRY(this->MakePageGroup(pg, dst_address, num_pages)); + + /* Verify that the page group contains the same pages as the source. */ + R_UNLESS(this->IsValidPageGroup(pg, src_address, num_pages), svc::ResultInvalidMemoryRegion()); + + /* Create an update allocator for the source. */ + Result src_allocator_result; + KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result), m_memory_block_slab_manager, num_src_allocator_blocks); + R_TRY(src_allocator_result); + + /* Create an update allocator for the destination. */ + Result dst_allocator_result; + KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result), m_memory_block_slab_manager, num_dst_allocator_blocks); + R_TRY(dst_allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Unmap the aliased copy of the pages. */ + const KPageProperties dst_unmap_properties = { KMemoryPermission_None, false, false, DisableMergeAttribute_None }; + R_TRY(this->Operate(updater.GetPageList(), dst_address, num_pages, Null<KPhysicalAddress>, false, dst_unmap_properties, OperationType_Unmap, false)); + + /* Ensure that we re-map the aliased pages on failure. */ + ON_RESULT_FAILURE { + this->RemapPageGroup(updater.GetPageList(), dst_address, size, pg); + }; + + /* Try to set the permissions for the source pages back to what they should be. */ + const KPageProperties src_properties = { KMemoryPermission_UserReadWrite, false, false, DisableMergeAttribute_EnableAndMergeHeadBodyTail }; + R_TRY(this->Operate(updater.GetPageList(), src_address, num_pages, Null<KPhysicalAddress>, false, src_properties, OperationType_ChangePermissions, false)); + + /* Apply the memory block updates. */ + m_memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages, KMemoryState_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_None, KMemoryBlockDisableMergeAttribute_Normal); + m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, KMemoryState_Normal, KMemoryPermission_UserReadWrite, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_None, KMemoryBlockDisableMergeAttribute_Locked); + + /* Note that we reprotected pages. */ + reprotected_pages = true; + } + + R_SUCCEED(); + } + + Result KPageTableBase::MapInsecurePhysicalMemory(KProcessAddress address, size_t size) { + /* Get the insecure memory resource limit and pool. */ + auto * const insecure_resource_limit = KSystemControl::GetInsecureMemoryResourceLimit(); + const auto insecure_pool = static_cast<KMemoryManager::Pool>(KSystemControl::GetInsecureMemoryPool()); + + /* Reserve the insecure memory. */ + /* NOTE: ResultOutOfMemory is returned here instead of the usual LimitReached. */ + KScopedResourceReservation memory_reservation(insecure_resource_limit, ams::svc::LimitableResource_PhysicalMemoryMax, size); + R_UNLESS(memory_reservation.Succeeded(), svc::ResultOutOfMemory()); + + /* Allocate pages for the insecure memory. */ + KPageGroup pg(m_block_info_manager); + R_TRY(Kernel::GetMemoryManager().AllocateAndOpen(std::addressof(pg), size / PageSize, 1, KMemoryManager::EncodeOption(insecure_pool, KMemoryManager::Direction_FromFront))); + + /* Close the opened pages when we're done with them. */ + /* If the mapping succeeds, each page will gain an extra reference, otherwise they will be freed automatically. */ + ON_SCOPE_EXIT { pg.Close(); }; + + /* Clear all the newly allocated pages. */ + for (const auto &it : pg) { + std::memset(GetVoidPointer(GetHeapVirtualAddress(it.GetAddress())), m_heap_fill_value, it.GetSize()); + } + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Validate that the address's state is valid. */ + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size, KMemoryState_All, KMemoryState_Free, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryAttribute_None)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Map the pages. */ + const size_t num_pages = size / PageSize; + const KPageProperties map_properties = { KMemoryPermission_UserReadWrite, false, false, DisableMergeAttribute_DisableHead }; + R_TRY(this->Operate(updater.GetPageList(), address, num_pages, pg, map_properties, OperationType_MapGroup, false)); + + /* Apply the memory block update. */ + m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState_Insecure, KMemoryPermission_UserReadWrite, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_Normal, KMemoryBlockDisableMergeAttribute_None); + + /* Update our mapped insecure size. */ + m_mapped_insecure_memory += size; + + /* Commit the memory reservation. */ + memory_reservation.Commit(); + + /* We succeeded. */ + R_SUCCEED(); + } + + Result KPageTableBase::UnmapInsecurePhysicalMemory(KProcessAddress address, size_t size) { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check the memory state. */ + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size, KMemoryState_All, KMemoryState_Insecure, KMemoryPermission_All, KMemoryPermission_UserReadWrite, KMemoryAttribute_All, KMemoryAttribute_None)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Unmap the memory. */ + const size_t num_pages = size / PageSize; + const KPageProperties unmap_properties = { KMemoryPermission_None, false, false, DisableMergeAttribute_None }; + R_TRY(this->Operate(updater.GetPageList(), address, num_pages, Null<KPhysicalAddress>, false, unmap_properties, OperationType_Unmap, false)); + + /* Apply the memory block update. */ + m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState_Free, KMemoryPermission_None, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_None, KMemoryBlockDisableMergeAttribute_Normal); + + /* Update our mapped insecure size. */ + m_mapped_insecure_memory -= size; + + /* Release the insecure memory from the insecure limit. */ + if (auto * const insecure_resource_limit = KSystemControl::GetInsecureMemoryResourceLimit(); insecure_resource_limit != nullptr) { + insecure_resource_limit->Release(ams::svc::LimitableResource_PhysicalMemoryMax, size); + } + + R_SUCCEED(); + } + + KProcessAddress KPageTableBase::FindFreeArea(KProcessAddress region_start, size_t region_num_pages, size_t num_pages, size_t alignment, size_t offset, size_t guard_pages) const { + KProcessAddress address = Null<KProcessAddress>; + + KProcessAddress search_start = Null<KProcessAddress>; + KProcessAddress search_end = Null<KProcessAddress>; + if (m_memory_block_manager.GetRegionForFindFreeArea(std::addressof(search_start), std::addressof(search_end), region_start, region_num_pages, num_pages, alignment, offset, guard_pages)) { + if (this->IsAslrEnabled()) { + /* Try to directly find a free area up to 8 times. */ + for (size_t i = 0; i < 8; i++) { + const size_t random_offset = KSystemControl::GenerateRandomRange(0, (search_end - search_start) / alignment) * alignment; + const KProcessAddress candidate = search_start + random_offset; + + KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(candidate); + MESOSPHERE_ABORT_UNLESS(it != m_memory_block_manager.end()); + + if (it->GetState() != KMemoryState_Free) { continue; } + if (!(it->GetAddress() + guard_pages * PageSize <= GetInteger(candidate))) { continue; } + if (!(candidate + (num_pages + guard_pages) * PageSize - 1 <= it->GetLastAddress())) { continue; } + + address = candidate; + break; + } + + /* Fall back to finding the first free area with a random offset. */ + if (address == Null<KProcessAddress>) { + /* NOTE: Nintendo does not account for guard pages here. */ + /* This may theoretically cause an offset to be chosen that cannot be mapped. */ + /* We will account for guard pages. */ + const size_t offset_blocks = KSystemControl::GenerateRandomRange(0, (search_end - search_start) / alignment); + const auto region_end = region_start + region_num_pages * PageSize; + address = m_memory_block_manager.FindFreeArea(search_start + offset_blocks * alignment, (region_end - (search_start + offset_blocks * alignment)) / PageSize, num_pages, alignment, offset, guard_pages); + } + } + + /* Find the first free area. */ + if (address == Null<KProcessAddress>) { + address = m_memory_block_manager.FindFreeArea(region_start, region_num_pages, num_pages, alignment, offset, guard_pages); + } + } + + return address; + } + + size_t KPageTableBase::GetSize(KMemoryState state) const { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Iterate, counting blocks with the desired state. */ + size_t total_size = 0; + for (KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(m_address_space_start); it != m_memory_block_manager.end(); ++it) { + if (it->GetState() == state) { + total_size += it->GetSize(); + } + } + + return total_size; + } + + size_t KPageTableBase::GetCodeSize() const { + return this->GetSize(KMemoryState_Code); + } + + size_t KPageTableBase::GetCodeDataSize() const { + return this->GetSize(KMemoryState_CodeData); + } + + size_t KPageTableBase::GetAliasCodeSize() const { + return this->GetSize(KMemoryState_AliasCode); + } + + size_t KPageTableBase::GetAliasCodeDataSize() const { + return this->GetSize(KMemoryState_AliasCodeData); + } + + Result KPageTableBase::AllocateAndMapPagesImpl(PageLinkedList *page_list, KProcessAddress address, size_t num_pages, const KPageProperties &properties) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + /* Create a page group to hold the pages we allocate. */ + KPageGroup pg(m_block_info_manager); + + /* Allocate the pages. */ + R_TRY(Kernel::GetMemoryManager().AllocateAndOpen(std::addressof(pg), num_pages, 1, m_allocate_option)); + + /* Ensure that the page group is closed when we're done working with it. */ + ON_SCOPE_EXIT { pg.Close(); }; + + /* Clear all pages. */ + for (const auto &it : pg) { + std::memset(GetVoidPointer(GetHeapVirtualAddress(it.GetAddress())), m_heap_fill_value, it.GetSize()); + } + + /* Map the pages. */ + R_RETURN(this->Operate(page_list, address, num_pages, pg, properties, OperationType_MapGroup, false)); + } + + Result KPageTableBase::MapPageGroupImpl(PageLinkedList *page_list, KProcessAddress address, const KPageGroup &pg, const KPageProperties properties, bool reuse_ll) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + /* Note the current address, so that we can iterate. */ + const KProcessAddress start_address = address; + KProcessAddress cur_address = address; + + /* Ensure that we clean up on failure. */ + ON_RESULT_FAILURE { + MESOSPHERE_ABORT_UNLESS(!reuse_ll); + if (cur_address != start_address) { + const KPageProperties unmap_properties = { KMemoryPermission_None, false, false, DisableMergeAttribute_None }; + MESOSPHERE_R_ABORT_UNLESS(this->Operate(page_list, start_address, (cur_address - start_address) / PageSize, Null<KPhysicalAddress>, false, unmap_properties, OperationType_Unmap, true)); + } + }; + + /* Iterate, mapping all pages in the group. */ + for (const auto &block : pg) { + /* Map and advance. */ + const KPageProperties cur_properties = (cur_address == start_address) ? properties : KPageProperties{ properties.perm, properties.io, properties.uncached, DisableMergeAttribute_None }; + R_TRY(this->Operate(page_list, cur_address, block.GetNumPages(), block.GetAddress(), true, cur_properties, OperationType_Map, reuse_ll)); + cur_address += block.GetSize(); + } + + /* We succeeded! */ + R_SUCCEED(); + } + + void KPageTableBase::RemapPageGroup(PageLinkedList *page_list, KProcessAddress address, size_t size, const KPageGroup &pg) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + /* Note the current address, so that we can iterate. */ + const KProcessAddress start_address = address; + const KProcessAddress last_address = start_address + size - 1; + const KProcessAddress end_address = last_address + 1; + + /* Iterate over the memory. */ + auto pg_it = pg.begin(); + MESOSPHERE_ABORT_UNLESS(pg_it != pg.end()); + + KPhysicalAddress pg_phys_addr = pg_it->GetAddress(); + size_t pg_pages = pg_it->GetNumPages(); + + auto it = m_memory_block_manager.FindIterator(start_address); + while (true) { + /* Check that the iterator is valid. */ + MESOSPHERE_ASSERT(it != m_memory_block_manager.end()); + + /* Determine the range to map. */ + KProcessAddress map_address = std::max(GetInteger(it->GetAddress()), GetInteger(start_address)); + const KProcessAddress map_end_address = std::min(GetInteger(it->GetEndAddress()), GetInteger(end_address)); + MESOSPHERE_ABORT_UNLESS(map_end_address != map_address); + + /* Determine if we should disable head merge. */ + const bool disable_head_merge = it->GetAddress() >= GetInteger(start_address) && (it->GetDisableMergeAttribute() & KMemoryBlockDisableMergeAttribute_Normal) != 0; + const KPageProperties map_properties = { it->GetPermission(), false, false, disable_head_merge ? DisableMergeAttribute_DisableHead : DisableMergeAttribute_None }; + + /* While we have pages to map, map them. */ + size_t map_pages = (map_end_address - map_address) / PageSize; + while (map_pages > 0) { + /* Check if we're at the end of the physical block. */ + if (pg_pages == 0) { + /* Ensure there are more pages to map. */ + MESOSPHERE_ABORT_UNLESS(pg_it != pg.end()); + + /* Advance our physical block. */ + ++pg_it; + pg_phys_addr = pg_it->GetAddress(); + pg_pages = pg_it->GetNumPages(); + } + + /* Map whatever we can. */ + const size_t cur_pages = std::min(pg_pages, map_pages); + MESOSPHERE_R_ABORT_UNLESS(this->Operate(page_list, map_address, map_pages, pg_phys_addr, true, map_properties, OperationType_Map, true)); + + /* Advance. */ + map_address += cur_pages * PageSize; + map_pages -= cur_pages; + + pg_phys_addr += cur_pages * PageSize; + pg_pages -= cur_pages; + } + + /* Check if we're done. */ + if (last_address <= it->GetLastAddress()) { + break; + } + + /* Advance. */ + ++it; + } + + /* Check that we re-mapped precisely the page group. */ + MESOSPHERE_ABORT_UNLESS((++pg_it) == pg.end()); + } + + Result KPageTableBase::MakePageGroup(KPageGroup &pg, KProcessAddress addr, size_t num_pages) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + const size_t size = num_pages * PageSize; + + /* We're making a new group, not adding to an existing one. */ + R_UNLESS(pg.empty(), svc::ResultInvalidCurrentMemory()); + + auto &impl = this->GetImpl(); + + /* Begin traversal. */ + TraversalContext context; + TraversalEntry next_entry; + R_UNLESS(impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), addr), svc::ResultInvalidCurrentMemory()); + + /* Prepare tracking variables. */ + KPhysicalAddress cur_addr = next_entry.phys_addr; + size_t cur_size = next_entry.block_size - (GetInteger(cur_addr) & (next_entry.block_size - 1)); + size_t tot_size = cur_size; + + /* Iterate, adding to group as we go. */ + while (tot_size < size) { + R_UNLESS(impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)), svc::ResultInvalidCurrentMemory()); + + if (next_entry.phys_addr != (cur_addr + cur_size)) { + const size_t cur_pages = cur_size / PageSize; + + R_UNLESS(IsHeapPhysicalAddress(cur_addr), svc::ResultInvalidCurrentMemory()); + R_TRY(pg.AddBlock(cur_addr, cur_pages)); + + cur_addr = next_entry.phys_addr; + cur_size = next_entry.block_size; + } else { + cur_size += next_entry.block_size; + } + + tot_size += next_entry.block_size; + } + + /* Ensure we add the right amount for the last block. */ + if (tot_size > size) { + cur_size -= (tot_size - size); + } + + /* add the last block. */ + const size_t cur_pages = cur_size / PageSize; + R_UNLESS(IsHeapPhysicalAddress(cur_addr), svc::ResultInvalidCurrentMemory()); + R_TRY(pg.AddBlock(cur_addr, cur_pages)); + + R_SUCCEED(); + } + + bool KPageTableBase::IsValidPageGroup(const KPageGroup &pg, KProcessAddress addr, size_t num_pages) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + const size_t size = num_pages * PageSize; + + /* Empty groups are necessarily invalid. */ + if (pg.empty()) { + return false; + } + + auto &impl = this->GetImpl(); + + /* We're going to validate that the group we'd expect is the group we see. */ + auto cur_it = pg.begin(); + KPhysicalAddress cur_block_address = cur_it->GetAddress(); + size_t cur_block_pages = cur_it->GetNumPages(); + + auto UpdateCurrentIterator = [&]() ALWAYS_INLINE_LAMBDA { + if (cur_block_pages == 0) { + if ((++cur_it) == pg.end()) { + return false; + } + + cur_block_address = cur_it->GetAddress(); + cur_block_pages = cur_it->GetNumPages(); + } + return true; + }; + + /* Begin traversal. */ + TraversalContext context; + TraversalEntry next_entry; + if (!impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), addr)) { + return false; + } + + /* Prepare tracking variables. */ + KPhysicalAddress cur_addr = next_entry.phys_addr; + size_t cur_size = next_entry.block_size - (GetInteger(cur_addr) & (next_entry.block_size - 1)); + size_t tot_size = cur_size; + + /* Iterate, comparing expected to actual. */ + while (tot_size < size) { + if (!impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context))) { + return false; + } + + if (next_entry.phys_addr != (cur_addr + cur_size)) { + const size_t cur_pages = cur_size / PageSize; + + if (!IsHeapPhysicalAddress(cur_addr)) { + return false; + } + + if (!UpdateCurrentIterator()) { + return false; + } + + if (cur_block_address != cur_addr || cur_block_pages < cur_pages) { + return false; + } + + cur_block_address += cur_size; + cur_block_pages -= cur_pages; + cur_addr = next_entry.phys_addr; + cur_size = next_entry.block_size; + } else { + cur_size += next_entry.block_size; + } + + tot_size += next_entry.block_size; + } + + /* Ensure we compare the right amount for the last block. */ + if (tot_size > size) { + cur_size -= (tot_size - size); + } + + if (!IsHeapPhysicalAddress(cur_addr)) { + return false; + } + + if (!UpdateCurrentIterator()) { + return false; + } + + return cur_block_address == cur_addr && cur_block_pages == (cur_size / PageSize); + } + + Result KPageTableBase::GetContiguousMemoryRangeWithState(MemoryRange *out, KProcessAddress address, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + auto &impl = this->GetImpl(); + + /* Begin a traversal. */ + TraversalContext context; + TraversalEntry cur_entry = { .phys_addr = Null<KPhysicalAddress>, .block_size = 0, .sw_reserved_bits = 0, .attr = 0 }; + R_UNLESS(impl.BeginTraversal(std::addressof(cur_entry), std::addressof(context), address), svc::ResultInvalidCurrentMemory()); + + /* Traverse until we have enough size or we aren't contiguous any more. */ + const KPhysicalAddress phys_address = cur_entry.phys_addr; + const u8 entry_attr = cur_entry.attr; + size_t contig_size; + for (contig_size = cur_entry.block_size - (GetInteger(phys_address) & (cur_entry.block_size - 1)); contig_size < size; contig_size += cur_entry.block_size) { + if (!impl.ContinueTraversal(std::addressof(cur_entry), std::addressof(context))) { + break; + } + if (cur_entry.phys_addr != phys_address + contig_size) { + break; + } + if (cur_entry.attr != entry_attr) { + break; + } + } + + /* Take the minimum size for our region. */ + size = std::min(size, contig_size); + + /* Check that the memory is contiguous (modulo the reference count bit). */ + const u32 test_state_mask = state_mask | KMemoryState_FlagReferenceCounted; + const bool is_heap = R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, test_state_mask, state | KMemoryState_FlagReferenceCounted, perm_mask, perm, attr_mask, attr)); + if (!is_heap) { + R_TRY(this->CheckMemoryStateContiguous(address, size, test_state_mask, state, perm_mask, perm, attr_mask, attr)); + } + + /* The memory is contiguous, so set the output range. */ + out->Set(phys_address, size, is_heap, attr); + R_SUCCEED(); + } + + Result KPageTableBase::SetMemoryPermission(KProcessAddress addr, size_t size, ams::svc::MemoryPermission svc_perm) { + const size_t num_pages = size / PageSize; + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Verify we can change the memory permission. */ + KMemoryState old_state; + KMemoryPermission old_perm; + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm), nullptr, std::addressof(num_allocator_blocks), addr, size, KMemoryState_FlagCanReprotect, KMemoryState_FlagCanReprotect, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_All, KMemoryAttribute_None)); + + /* Determine new perm. */ + const KMemoryPermission new_perm = ConvertToKMemoryPermission(svc_perm); + R_SUCCEED_IF(old_perm == new_perm); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Perform mapping operation. */ + const KPageProperties properties = { new_perm, false, false, DisableMergeAttribute_None }; + R_TRY(this->Operate(updater.GetPageList(), addr, num_pages, Null<KPhysicalAddress>, false, properties, OperationType_ChangePermissions, false)); + + /* Update the blocks. */ + m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, old_state, new_perm, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_None, KMemoryBlockDisableMergeAttribute_None); + + R_SUCCEED(); + } + + Result KPageTableBase::SetProcessMemoryPermission(KProcessAddress addr, size_t size, ams::svc::MemoryPermission svc_perm) { + const size_t num_pages = size / PageSize; + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Verify we can change the memory permission. */ + KMemoryState old_state; + KMemoryPermission old_perm; + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm), nullptr, std::addressof(num_allocator_blocks), addr, size, KMemoryState_FlagCode, KMemoryState_FlagCode, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_All, KMemoryAttribute_None)); + + /* Make a new page group for the region. */ + KPageGroup pg(m_block_info_manager); + + /* Determine new perm/state. */ + const KMemoryPermission new_perm = ConvertToKMemoryPermission(svc_perm); + KMemoryState new_state = old_state; + const bool is_w = (new_perm & KMemoryPermission_UserWrite) == KMemoryPermission_UserWrite; + const bool is_x = (new_perm & KMemoryPermission_UserExecute) == KMemoryPermission_UserExecute; + const bool was_x = (old_perm & KMemoryPermission_UserExecute) == KMemoryPermission_UserExecute; + MESOSPHERE_ASSERT(!(is_w && is_x)); + + if (is_w) { + switch (old_state) { + case KMemoryState_Code: new_state = KMemoryState_CodeData; break; + case KMemoryState_AliasCode: new_state = KMemoryState_AliasCodeData; break; + MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); + } + } + + /* Create a page group, if we're setting execute permissions. */ + if (is_x) { + R_TRY(this->MakePageGroup(pg, GetInteger(addr), num_pages)); + } + + /* Succeed if there's nothing to do. */ + R_SUCCEED_IF(old_perm == new_perm && old_state == new_state); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* If we're creating an executable mapping, take and immediately release the scheduler lock. This will force a reschedule. */ + if (is_x) { + KScopedSchedulerLock sl; + } + + /* Ensure cache coherency, if we're setting pages as executable. */ + if (is_x) { + for (const auto &block : pg) { + cpu::StoreDataCache(GetVoidPointer(GetHeapVirtualAddress(block.GetAddress())), block.GetSize()); + } + cpu::InvalidateEntireInstructionCache(); + } + + /* Perform mapping operation. */ + const KPageProperties properties = { new_perm, false, false, DisableMergeAttribute_None }; + const auto operation = was_x ? OperationType_ChangePermissionsAndRefresh : OperationType_ChangePermissions; + R_TRY(this->Operate(updater.GetPageList(), addr, num_pages, Null<KPhysicalAddress>, false, properties, operation, false)); + + /* Update the blocks. */ + m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, new_state, new_perm, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_None, KMemoryBlockDisableMergeAttribute_None); + + /* Ensure cache coherency, if we're setting pages as executable. */ + if (was_x) { + cpu::InvalidateEntireInstructionCache(); + } + + R_SUCCEED(); + } + + Result KPageTableBase::SetMemoryAttribute(KProcessAddress addr, size_t size, u32 mask, u32 attr) { + const size_t num_pages = size / PageSize; + MESOSPHERE_ASSERT((mask | KMemoryAttribute_SetMask) == KMemoryAttribute_SetMask); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Verify we can change the memory attribute. */ + KMemoryState old_state; + KMemoryPermission old_perm; + KMemoryAttribute old_attr; + size_t num_allocator_blocks; + constexpr u32 AttributeTestMask = ~(KMemoryAttribute_SetMask | KMemoryAttribute_DeviceShared); + const u32 state_test_mask = ((mask & KMemoryAttribute_Uncached) ? static_cast<u32>(KMemoryState_FlagCanChangeAttribute) : 0) | ((mask & KMemoryAttribute_PermissionLocked) ? static_cast<u32>(KMemoryState_FlagCanPermissionLock) : 0); + R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm), std::addressof(old_attr), std::addressof(num_allocator_blocks), + addr, size, + state_test_mask, state_test_mask, + KMemoryPermission_None, KMemoryPermission_None, + AttributeTestMask, KMemoryAttribute_None, ~AttributeTestMask)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* If we need to, perform a change attribute operation. */ + if ((mask & KMemoryAttribute_Uncached) != 0) { + /* Determine the new attribute. */ + const KMemoryAttribute new_attr = static_cast<KMemoryAttribute>(((old_attr & ~mask) | (attr & mask))); + + /* Perform operation. */ + const KPageProperties properties = { old_perm, false, (new_attr & KMemoryAttribute_Uncached) != 0, DisableMergeAttribute_None }; + R_TRY(this->Operate(updater.GetPageList(), addr, num_pages, Null<KPhysicalAddress>, false, properties, OperationType_ChangePermissionsAndRefreshAndFlush, false)); + } + + /* Update the blocks. */ + m_memory_block_manager.UpdateAttribute(std::addressof(allocator), addr, num_pages, mask, attr); + + R_SUCCEED(); + } + + Result KPageTableBase::SetHeapSize(KProcessAddress *out, size_t size) { + /* Lock the physical memory mutex. */ + KScopedLightLock map_phys_mem_lk(m_map_physical_memory_lock); + + /* Try to perform a reduction in heap, instead of an extension. */ + KProcessAddress cur_address; + size_t allocation_size; + { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Validate that setting heap size is possible at all. */ + R_UNLESS(!m_is_kernel, svc::ResultOutOfMemory()); + R_UNLESS(size <= static_cast<size_t>(m_region_ends[RegionType_Heap] - m_region_starts[RegionType_Heap]), svc::ResultOutOfMemory()); + R_UNLESS(size <= m_max_heap_size, svc::ResultOutOfMemory()); + + if (size < static_cast<size_t>(m_current_heap_end - m_region_starts[RegionType_Heap])) { + /* The size being requested is less than the current size, so we need to free the end of the heap. */ + + /* Validate memory state. */ + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), + m_region_starts[RegionType_Heap] + size, (m_current_heap_end - m_region_starts[RegionType_Heap]) - size, + KMemoryState_All, KMemoryState_Normal, + KMemoryPermission_All, KMemoryPermission_UserReadWrite, + KMemoryAttribute_All, KMemoryAttribute_None)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Unmap the end of the heap. */ + const size_t num_pages = ((m_current_heap_end - m_region_starts[RegionType_Heap]) - size) / PageSize; + const KPageProperties unmap_properties = { KMemoryPermission_None, false, false, DisableMergeAttribute_None }; + R_TRY(this->Operate(updater.GetPageList(), m_region_starts[RegionType_Heap] + size, num_pages, Null<KPhysicalAddress>, false, unmap_properties, OperationType_Unmap, false)); + + /* Release the memory from the resource limit. */ + m_resource_limit->Release(ams::svc::LimitableResource_PhysicalMemoryMax, num_pages * PageSize); + + /* Apply the memory block update. */ + m_memory_block_manager.Update(std::addressof(allocator), m_region_starts[RegionType_Heap] + size, num_pages, KMemoryState_Free, KMemoryPermission_None, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_None, size == 0 ? KMemoryBlockDisableMergeAttribute_Normal : KMemoryBlockDisableMergeAttribute_None); + + /* Update the current heap end. */ + m_current_heap_end = m_region_starts[RegionType_Heap] + size; + + /* Set the output. */ + *out = m_region_starts[RegionType_Heap]; + R_SUCCEED(); + } else if (size == static_cast<size_t>(m_current_heap_end - m_region_starts[RegionType_Heap])) { + /* The size requested is exactly the current size. */ + *out = m_region_starts[RegionType_Heap]; + R_SUCCEED(); + } else { + /* We have to allocate memory. Determine how much to allocate and where while the table is locked. */ + cur_address = m_current_heap_end; + allocation_size = size - (m_current_heap_end - m_region_starts[RegionType_Heap]); + } + } + + /* Reserve memory for the heap extension. */ + KScopedResourceReservation memory_reservation(m_resource_limit, ams::svc::LimitableResource_PhysicalMemoryMax, allocation_size); + R_UNLESS(memory_reservation.Succeeded(), svc::ResultLimitReached()); + + /* Allocate pages for the heap extension. */ + KPageGroup pg(m_block_info_manager); + R_TRY(Kernel::GetMemoryManager().AllocateAndOpen(std::addressof(pg), allocation_size / PageSize, 1, m_allocate_option)); + + /* Close the opened pages when we're done with them. */ + /* If the mapping succeeds, each page will gain an extra reference, otherwise they will be freed automatically. */ + ON_SCOPE_EXIT { pg.Close(); }; + + /* Clear all the newly allocated pages. */ + for (const auto &it : pg) { + std::memset(GetVoidPointer(GetHeapVirtualAddress(it.GetAddress())), m_heap_fill_value, it.GetSize()); + } + + /* Map the pages. */ + { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Ensure that the heap hasn't changed since we began executing. */ + MESOSPHERE_ABORT_UNLESS(cur_address == m_current_heap_end); + + /* Check the memory state. */ + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), m_current_heap_end, allocation_size, KMemoryState_All, KMemoryState_Free, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryAttribute_None)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Map the pages. */ + const size_t num_pages = allocation_size / PageSize; + const KPageProperties map_properties = { KMemoryPermission_UserReadWrite, false, false, (m_current_heap_end == m_region_starts[RegionType_Heap]) ? DisableMergeAttribute_DisableHead : DisableMergeAttribute_None }; + R_TRY(this->Operate(updater.GetPageList(), m_current_heap_end, num_pages, pg, map_properties, OperationType_MapGroup, false)); + + /* We succeeded, so commit our memory reservation. */ + memory_reservation.Commit(); + + /* Apply the memory block update. */ + m_memory_block_manager.Update(std::addressof(allocator), m_current_heap_end, num_pages, KMemoryState_Normal, KMemoryPermission_UserReadWrite, KMemoryAttribute_None, m_region_starts[RegionType_Heap] == m_current_heap_end ? KMemoryBlockDisableMergeAttribute_Normal : KMemoryBlockDisableMergeAttribute_None, KMemoryBlockDisableMergeAttribute_None); + + /* Update the current heap end. */ + m_current_heap_end = m_region_starts[RegionType_Heap] + size; + + /* Set the output. */ + *out = m_region_starts[RegionType_Heap]; + R_SUCCEED(); + } + } + + Result KPageTableBase::SetMaxHeapSize(size_t size) { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Only process page tables are allowed to set heap size. */ + MESOSPHERE_ASSERT(!this->IsKernel()); + + m_max_heap_size = size; + + R_SUCCEED(); + } + + Result KPageTableBase::QueryInfo(KMemoryInfo *out_info, ams::svc::PageInfo *out_page_info, KProcessAddress addr) const { + /* If the address is invalid, create a fake block. */ + if (!this->Contains(addr, 1)) { + *out_info = { + .m_address = GetInteger(m_address_space_end), + .m_size = 0 - GetInteger(m_address_space_end), + .m_state = static_cast<KMemoryState>(ams::svc::MemoryState_Inaccessible), + .m_device_disable_merge_left_count = 0, + .m_device_disable_merge_right_count = 0, + .m_ipc_lock_count = 0, + .m_device_use_count = 0, + .m_ipc_disable_merge_count = 0, + .m_permission = KMemoryPermission_None, + .m_attribute = KMemoryAttribute_None, + .m_original_permission = KMemoryPermission_None, + .m_disable_merge_attribute = KMemoryBlockDisableMergeAttribute_None, + }; + out_page_info->flags = 0; + + R_SUCCEED(); + } + + /* Otherwise, lock the table and query. */ + KScopedLightLock lk(m_general_lock); + R_RETURN(this->QueryInfoImpl(out_info, out_page_info, addr)); + } + + Result KPageTableBase::QueryPhysicalAddress(ams::svc::PhysicalMemoryInfo *out, KProcessAddress address) const { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Align the address down to page size. */ + address = util::AlignDown(GetInteger(address), PageSize); + + /* Verify that we can query the address. */ + KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(address); + R_UNLESS(it != m_memory_block_manager.end(), svc::ResultInvalidCurrentMemory()); + + /* Check the memory state. */ + R_TRY(this->CheckMemoryState(it, KMemoryState_FlagCanQueryPhysical, KMemoryState_FlagCanQueryPhysical, KMemoryPermission_UserReadExecute, KMemoryPermission_UserRead, KMemoryAttribute_None, KMemoryAttribute_None)); + + /* Prepare to traverse. */ + KPhysicalAddress phys_addr; + size_t phys_size; + + KProcessAddress virt_addr = it->GetAddress(); + KProcessAddress end_addr = it->GetEndAddress(); + + /* Perform traversal. */ + { + /* Begin traversal. */ + TraversalContext context; + TraversalEntry next_entry; + bool traverse_valid = m_impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), virt_addr); + R_UNLESS(traverse_valid, svc::ResultInvalidCurrentMemory()); + + /* Set tracking variables. */ + phys_addr = next_entry.phys_addr; + phys_size = next_entry.block_size - (GetInteger(phys_addr) & (next_entry.block_size - 1)); + + /* Iterate. */ + while (true) { + /* Continue the traversal. */ + traverse_valid = m_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + if (!traverse_valid) { + break; + } + + if (next_entry.phys_addr != (phys_addr + phys_size)) { + /* Check if we're done. */ + if (virt_addr <= address && address <= virt_addr + phys_size - 1) { + break; + } + + /* Advance. */ + phys_addr = next_entry.phys_addr; + virt_addr += next_entry.block_size; + phys_size = next_entry.block_size - (GetInteger(phys_addr) & (next_entry.block_size - 1)); + } else { + phys_size += next_entry.block_size; + } + + /* Check if we're done. */ + if (end_addr < virt_addr + phys_size) { + break; + } + } + MESOSPHERE_ASSERT(virt_addr <= address && address <= virt_addr + phys_size - 1); + + /* Ensure we use the right size. */ + if (end_addr < virt_addr + phys_size) { + phys_size = end_addr - virt_addr; + } + } + + /* Set the output. */ + out->physical_address = GetInteger(phys_addr); + out->virtual_address = GetInteger(virt_addr); + out->size = phys_size; + R_SUCCEED(); + } + + Result KPageTableBase::MapIoImpl(KProcessAddress *out, PageLinkedList *page_list, KPhysicalAddress phys_addr, size_t size, KMemoryState state, KMemoryPermission perm) { + /* Check pre-conditions. */ + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(phys_addr), PageSize)); + MESOSPHERE_ASSERT(util::IsAligned(size, PageSize)); + MESOSPHERE_ASSERT(size > 0); + + R_UNLESS(phys_addr < phys_addr + size, svc::ResultInvalidAddress()); + const size_t num_pages = size / PageSize; + const KPhysicalAddress last = phys_addr + size - 1; + + /* Get region extents. */ + const KProcessAddress region_start = m_region_starts[RegionType_KernelMap]; + const size_t region_size = m_region_ends[RegionType_KernelMap] - m_region_starts[RegionType_KernelMap]; + const size_t region_num_pages = region_size / PageSize; + + MESOSPHERE_ASSERT(this->CanContain(region_start, region_size, state)); + + /* Locate the memory region. */ + const KMemoryRegion *region = KMemoryLayout::Find(phys_addr); + R_UNLESS(region != nullptr, svc::ResultInvalidAddress()); + + MESOSPHERE_ASSERT(region->Contains(GetInteger(phys_addr))); + + /* Ensure that the region is mappable. */ + const bool is_rw = perm == KMemoryPermission_UserReadWrite; + while (true) { + /* Check that the region exists. */ + R_UNLESS(region != nullptr, svc::ResultInvalidAddress()); + + /* Check the region attributes. */ + R_UNLESS(!region->IsDerivedFrom(KMemoryRegionType_Dram), svc::ResultInvalidAddress()); + R_UNLESS(!region->HasTypeAttribute(KMemoryRegionAttr_UserReadOnly) || !is_rw, svc::ResultInvalidAddress()); + R_UNLESS(!region->HasTypeAttribute(KMemoryRegionAttr_NoUserMap), svc::ResultInvalidAddress()); + + /* Check if we're done. */ + if (GetInteger(last) <= region->GetLastAddress()) { + break; + } + + /* Advance. */ + region = region->GetNext(); + }; + + /* Select an address to map at. */ + KProcessAddress addr = Null<KProcessAddress>; + for (s32 block_type = KPageTable::GetMaxBlockType(); block_type >= 0; block_type--) { + const size_t alignment = KPageTable::GetBlockSize(static_cast<KPageTable::BlockType>(block_type)); + + const KPhysicalAddress aligned_phys = util::AlignUp(GetInteger(phys_addr), alignment) + alignment - 1; + if (aligned_phys <= phys_addr) { + continue; + } + + const KPhysicalAddress last_aligned_paddr = util::AlignDown(GetInteger(last) + 1, alignment) - 1; + if (!(last_aligned_paddr <= last && aligned_phys <= last_aligned_paddr)) { + continue; + } + + addr = this->FindFreeArea(region_start, region_num_pages, num_pages, alignment, 0, this->GetNumGuardPages()); + if (addr != Null<KProcessAddress>) { + break; + } + } + R_UNLESS(addr != Null<KProcessAddress>, svc::ResultOutOfMemory()); + + /* Check that we can map IO here. */ + MESOSPHERE_ASSERT(this->CanContain(addr, size, state)); + MESOSPHERE_R_ASSERT(this->CheckMemoryState(addr, size, KMemoryState_All, KMemoryState_Free, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryAttribute_None)); + + /* Perform mapping operation. */ + const KPageProperties properties = { perm, state == KMemoryState_IoRegister, false, DisableMergeAttribute_DisableHead }; + R_TRY(this->Operate(page_list, addr, num_pages, phys_addr, true, properties, OperationType_Map, false)); + + /* Set the output address. */ + *out = addr; + + R_SUCCEED(); + } + + Result KPageTableBase::MapIo(KPhysicalAddress phys_addr, size_t size, KMemoryPermission perm) { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Map the io memory. */ + KProcessAddress addr; + R_TRY(this->MapIoImpl(std::addressof(addr), updater.GetPageList(), phys_addr, size, KMemoryState_IoRegister, perm)); + + /* Update the blocks. */ + m_memory_block_manager.Update(std::addressof(allocator), addr, size / PageSize, KMemoryState_IoRegister, perm, KMemoryAttribute_Locked, KMemoryBlockDisableMergeAttribute_Normal, KMemoryBlockDisableMergeAttribute_None); + + /* We successfully mapped the pages. */ + R_SUCCEED(); + } + + Result KPageTableBase::MapIoRegion(KProcessAddress dst_address, KPhysicalAddress phys_addr, size_t size, ams::svc::MemoryMapping mapping, ams::svc::MemoryPermission svc_perm) { + const size_t num_pages = size / PageSize; + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Validate the memory state. */ + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), dst_address, size, KMemoryState_All, KMemoryState_None, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryAttribute_None)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Perform mapping operation. */ + const KMemoryPermission perm = ConvertToKMemoryPermission(svc_perm); + const KPageProperties properties = { perm, mapping == ams::svc::MemoryMapping_IoRegister, mapping == ams::svc::MemoryMapping_Uncached, DisableMergeAttribute_DisableHead }; + R_TRY(this->Operate(updater.GetPageList(), dst_address, num_pages, phys_addr, true, properties, OperationType_Map, false)); + + /* Update the blocks. */ + const auto state = mapping == ams::svc::MemoryMapping_Memory ? KMemoryState_IoMemory : KMemoryState_IoRegister; + m_memory_block_manager.Update(std::addressof(allocator), dst_address, num_pages, state, perm, KMemoryAttribute_Locked, KMemoryBlockDisableMergeAttribute_Normal, KMemoryBlockDisableMergeAttribute_None); + + /* We successfully mapped the pages. */ + R_SUCCEED(); + } + + Result KPageTableBase::UnmapIoRegion(KProcessAddress dst_address, KPhysicalAddress phys_addr, size_t size, ams::svc::MemoryMapping mapping) { + const size_t num_pages = size / PageSize; + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Validate the memory state. */ + KMemoryState old_state; + KMemoryPermission old_perm; + KMemoryAttribute old_attr; + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm), std::addressof(old_attr), std::addressof(num_allocator_blocks), + dst_address, size, + KMemoryState_All, mapping == ams::svc::MemoryMapping_Memory ? KMemoryState_IoMemory : KMemoryState_IoRegister, + KMemoryPermission_None, KMemoryPermission_None, + KMemoryAttribute_All, KMemoryAttribute_Locked)); + + /* Validate that the region being unmapped corresponds to the physical range described. */ + { + /* Get the impl. */ + auto &impl = this->GetImpl(); + + /* Begin traversal. */ + TraversalContext context; + TraversalEntry next_entry; + MESOSPHERE_ABORT_UNLESS(impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), dst_address)); + + /* Check that the physical region matches. */ + R_UNLESS(next_entry.phys_addr == phys_addr, svc::ResultInvalidMemoryRegion()); + + /* Iterate. */ + for (size_t checked_size = next_entry.block_size - (GetInteger(phys_addr) & (next_entry.block_size - 1)); checked_size < size; checked_size += next_entry.block_size) { + /* Continue the traversal. */ + MESOSPHERE_ABORT_UNLESS(impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context))); + + /* Check that the physical region matches. */ + R_UNLESS(next_entry.phys_addr == phys_addr + checked_size, svc::ResultInvalidMemoryRegion()); + } + } + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* If the region being unmapped is Memory, synchronize. */ + if (mapping == ams::svc::MemoryMapping_Memory) { + /* Change the region to be uncached. */ + const KPageProperties properties = { old_perm, false, true, DisableMergeAttribute_None }; + MESOSPHERE_R_ABORT_UNLESS(this->Operate(updater.GetPageList(), dst_address, num_pages, Null<KPhysicalAddress>, false, properties, OperationType_ChangePermissionsAndRefresh, false)); + + /* Temporarily unlock ourselves, so that other operations can occur while we flush the region. */ + m_general_lock.Unlock(); + ON_SCOPE_EXIT { m_general_lock.Lock(); }; + + /* Flush the region. */ + MESOSPHERE_R_ABORT_UNLESS(cpu::FlushDataCache(GetVoidPointer(dst_address), size)); + } + + /* Perform the unmap. */ + const KPageProperties unmap_properties = { KMemoryPermission_None, false, false, DisableMergeAttribute_None }; + MESOSPHERE_R_ABORT_UNLESS(this->Operate(updater.GetPageList(), dst_address, num_pages, Null<KPhysicalAddress>, false, unmap_properties, OperationType_Unmap, false)); + + /* Update the blocks. */ + m_memory_block_manager.Update(std::addressof(allocator), dst_address, num_pages, KMemoryState_Free, KMemoryPermission_None, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_None, KMemoryBlockDisableMergeAttribute_Normal); + + R_SUCCEED(); + } + + Result KPageTableBase::MapStatic(KPhysicalAddress phys_addr, size_t size, KMemoryPermission perm) { + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(phys_addr), PageSize)); + MESOSPHERE_ASSERT(util::IsAligned(size, PageSize)); + MESOSPHERE_ASSERT(size > 0); + R_UNLESS(phys_addr < phys_addr + size, svc::ResultInvalidAddress()); + const size_t num_pages = size / PageSize; + const KPhysicalAddress last = phys_addr + size - 1; + + /* Get region extents. */ + const KProcessAddress region_start = this->GetRegionAddress(KMemoryState_Static); + const size_t region_size = this->GetRegionSize(KMemoryState_Static); + const size_t region_num_pages = region_size / PageSize; + + /* Locate the memory region. */ + const KMemoryRegion *region = KMemoryLayout::Find(phys_addr); + R_UNLESS(region != nullptr, svc::ResultInvalidAddress()); + + MESOSPHERE_ASSERT(region->Contains(GetInteger(phys_addr))); + R_UNLESS(GetInteger(last) <= region->GetLastAddress(), svc::ResultInvalidAddress()); + + /* Check the region attributes. */ + const bool is_rw = perm == KMemoryPermission_UserReadWrite; + R_UNLESS( region->IsDerivedFrom(KMemoryRegionType_Dram), svc::ResultInvalidAddress()); + R_UNLESS(!region->HasTypeAttribute(KMemoryRegionAttr_NoUserMap), svc::ResultInvalidAddress()); + R_UNLESS(!region->HasTypeAttribute(KMemoryRegionAttr_UserReadOnly) || !is_rw, svc::ResultInvalidAddress()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Select an address to map at. */ + KProcessAddress addr = Null<KProcessAddress>; + for (s32 block_type = KPageTable::GetMaxBlockType(); block_type >= 0; block_type--) { + const size_t alignment = KPageTable::GetBlockSize(static_cast<KPageTable::BlockType>(block_type)); + + const KPhysicalAddress aligned_phys = util::AlignUp(GetInteger(phys_addr), alignment) + alignment - 1; + if (aligned_phys <= phys_addr) { + continue; + } + + const KPhysicalAddress last_aligned_paddr = util::AlignDown(GetInteger(last) + 1, alignment) - 1; + if (!(last_aligned_paddr <= last && aligned_phys <= last_aligned_paddr)) { + continue; + } + + addr = this->FindFreeArea(region_start, region_num_pages, num_pages, alignment, 0, this->GetNumGuardPages()); + if (addr != Null<KProcessAddress>) { + break; + } + } + R_UNLESS(addr != Null<KProcessAddress>, svc::ResultOutOfMemory()); + + /* Check that we can map static here. */ + MESOSPHERE_ASSERT(this->CanContain(addr, size, KMemoryState_Static)); + MESOSPHERE_R_ASSERT(this->CheckMemoryState(addr, size, KMemoryState_All, KMemoryState_Free, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryAttribute_None)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Perform mapping operation. */ + const KPageProperties properties = { perm, false, false, DisableMergeAttribute_DisableHead }; + R_TRY(this->Operate(updater.GetPageList(), addr, num_pages, phys_addr, true, properties, OperationType_Map, false)); + + /* Update the blocks. */ + m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, KMemoryState_Static, perm, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_Normal, KMemoryBlockDisableMergeAttribute_None); + + /* We successfully mapped the pages. */ + R_SUCCEED(); + } + + Result KPageTableBase::MapRegion(KMemoryRegionType region_type, KMemoryPermission perm) { + /* Get the memory region. */ + const KMemoryRegion *region = KMemoryLayout::GetPhysicalMemoryRegionTree().FindFirstDerived(region_type); + R_UNLESS(region != nullptr, svc::ResultOutOfRange()); + + /* Check that the region is valid. */ + MESOSPHERE_ABORT_UNLESS(region->GetEndAddress() != 0); + + /* Map the region. */ + R_TRY_CATCH(this->MapStatic(region->GetAddress(), region->GetSize(), perm)) { + R_CONVERT(svc::ResultInvalidAddress, svc::ResultOutOfRange()) + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + Result KPageTableBase::MapPages(KProcessAddress *out_addr, size_t num_pages, size_t alignment, KPhysicalAddress phys_addr, bool is_pa_valid, KProcessAddress region_start, size_t region_num_pages, KMemoryState state, KMemoryPermission perm) { + MESOSPHERE_ASSERT(util::IsAligned(alignment, PageSize) && alignment >= PageSize); + + /* Ensure this is a valid map request. */ + R_UNLESS(this->CanContain(region_start, region_num_pages * PageSize, state), svc::ResultInvalidCurrentMemory()); + R_UNLESS(num_pages < region_num_pages, svc::ResultOutOfMemory()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Find a random address to map at. */ + KProcessAddress addr = this->FindFreeArea(region_start, region_num_pages, num_pages, alignment, 0, this->GetNumGuardPages()); + R_UNLESS(addr != Null<KProcessAddress>, svc::ResultOutOfMemory()); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(addr), alignment)); + MESOSPHERE_ASSERT(this->CanContain(addr, num_pages * PageSize, state)); + MESOSPHERE_R_ASSERT(this->CheckMemoryState(addr, num_pages * PageSize, KMemoryState_All, KMemoryState_Free, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryAttribute_None)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Perform mapping operation. */ + const KPageProperties properties = { perm, false, false, DisableMergeAttribute_DisableHead }; + if (is_pa_valid) { + R_TRY(this->Operate(updater.GetPageList(), addr, num_pages, phys_addr, true, properties, OperationType_Map, false)); + } else { + R_TRY(this->AllocateAndMapPagesImpl(updater.GetPageList(), addr, num_pages, properties)); + } + + /* Update the blocks. */ + m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_Normal, KMemoryBlockDisableMergeAttribute_None); + + /* We successfully mapped the pages. */ + *out_addr = addr; + R_SUCCEED(); + } + + Result KPageTableBase::MapPages(KProcessAddress address, size_t num_pages, KMemoryState state, KMemoryPermission perm) { + /* Check that the map is in range. */ + const size_t size = num_pages * PageSize; + R_UNLESS(this->CanContain(address, size, state), svc::ResultInvalidCurrentMemory()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check the memory state. */ + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size, KMemoryState_All, KMemoryState_Free, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryAttribute_None)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Map the pages. */ + const KPageProperties properties = { perm, false, false, DisableMergeAttribute_DisableHead }; + R_TRY(this->AllocateAndMapPagesImpl(updater.GetPageList(), address, num_pages, properties)); + + /* Update the blocks. */ + m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, state, perm, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_Normal, KMemoryBlockDisableMergeAttribute_None); + + R_SUCCEED(); + } + + Result KPageTableBase::UnmapPages(KProcessAddress address, size_t num_pages, KMemoryState state) { + /* Check that the unmap is in range. */ + const size_t size = num_pages * PageSize; + R_UNLESS(this->Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check the memory state. */ + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size, KMemoryState_All, state, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_All, KMemoryAttribute_None)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Perform the unmap. */ + const KPageProperties unmap_properties = { KMemoryPermission_None, false, false, DisableMergeAttribute_None }; + R_TRY(this->Operate(updater.GetPageList(), address, num_pages, Null<KPhysicalAddress>, false, unmap_properties, OperationType_Unmap, false)); + + /* Update the blocks. */ + m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState_Free, KMemoryPermission_None, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_None, KMemoryBlockDisableMergeAttribute_Normal); + + R_SUCCEED(); + } + + Result KPageTableBase::MapPageGroup(KProcessAddress *out_addr, const KPageGroup &pg, KProcessAddress region_start, size_t region_num_pages, KMemoryState state, KMemoryPermission perm) { + MESOSPHERE_ASSERT(!this->IsLockedByCurrentThread()); + + /* Ensure this is a valid map request. */ + const size_t num_pages = pg.GetNumPages(); + R_UNLESS(this->CanContain(region_start, region_num_pages * PageSize, state), svc::ResultInvalidCurrentMemory()); + R_UNLESS(num_pages < region_num_pages, svc::ResultOutOfMemory()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Find a random address to map at. */ + KProcessAddress addr = this->FindFreeArea(region_start, region_num_pages, num_pages, PageSize, 0, this->GetNumGuardPages()); + R_UNLESS(addr != Null<KProcessAddress>, svc::ResultOutOfMemory()); + MESOSPHERE_ASSERT(this->CanContain(addr, num_pages * PageSize, state)); + MESOSPHERE_R_ASSERT(this->CheckMemoryState(addr, num_pages * PageSize, KMemoryState_All, KMemoryState_Free, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryAttribute_None)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Perform mapping operation. */ + const KPageProperties properties = { perm, false, false, DisableMergeAttribute_DisableHead }; + R_TRY(this->MapPageGroupImpl(updater.GetPageList(), addr, pg, properties, false)); + + /* Update the blocks. */ + m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_Normal, KMemoryBlockDisableMergeAttribute_None); + + /* We successfully mapped the pages. */ + *out_addr = addr; + R_SUCCEED(); + } + + Result KPageTableBase::MapPageGroup(KProcessAddress addr, const KPageGroup &pg, KMemoryState state, KMemoryPermission perm) { + MESOSPHERE_ASSERT(!this->IsLockedByCurrentThread()); + + /* Ensure this is a valid map request. */ + const size_t num_pages = pg.GetNumPages(); + const size_t size = num_pages * PageSize; + R_UNLESS(this->CanContain(addr, size, state), svc::ResultInvalidCurrentMemory()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check if state allows us to map. */ + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), addr, size, KMemoryState_All, KMemoryState_Free, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryAttribute_None)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Ensure cache coherency, if we're mapping executable pages. */ + if ((perm & KMemoryPermission_UserExecute) == KMemoryPermission_UserExecute) { + cpu::InvalidateEntireInstructionCache(); + } + + /* Perform mapping operation. */ + const KPageProperties properties = { perm, false, false, DisableMergeAttribute_DisableHead }; + R_TRY(this->MapPageGroupImpl(updater.GetPageList(), addr, pg, properties, false)); + + /* Update the blocks. */ + m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_Normal, KMemoryBlockDisableMergeAttribute_None); + + /* We successfully mapped the pages. */ + R_SUCCEED(); + } + + Result KPageTableBase::UnmapPageGroup(KProcessAddress address, const KPageGroup &pg, KMemoryState state) { + MESOSPHERE_ASSERT(!this->IsLockedByCurrentThread()); + + /* Ensure this is a valid unmap request. */ + const size_t num_pages = pg.GetNumPages(); + const size_t size = num_pages * PageSize; + R_UNLESS(this->CanContain(address, size, state), svc::ResultInvalidCurrentMemory()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check if state allows us to unmap. */ + KMemoryPermission old_perm; + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(nullptr, std::addressof(old_perm), nullptr, std::addressof(num_allocator_blocks), address, size, KMemoryState_All, state, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_All, KMemoryAttribute_None)); + + /* Check that the page group is valid. */ + R_UNLESS(this->IsValidPageGroup(pg, address, num_pages), svc::ResultInvalidCurrentMemory()); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Perform unmapping operation. */ + const KPageProperties properties = { KMemoryPermission_None, false, false, DisableMergeAttribute_None }; + R_TRY(this->Operate(updater.GetPageList(), address, num_pages, Null<KPhysicalAddress>, false, properties, OperationType_Unmap, false)); + + /* Ensure cache coherency, if we're mapping executable pages. */ + if ((old_perm & KMemoryPermission_UserExecute) == KMemoryPermission_UserExecute) { + cpu::InvalidateEntireInstructionCache(); + } + + /* Update the blocks. */ + m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState_Free, KMemoryPermission_None, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_None, KMemoryBlockDisableMergeAttribute_Normal); + + R_SUCCEED(); + } + + Result KPageTableBase::MakeAndOpenPageGroup(KPageGroup *out, KProcessAddress address, size_t num_pages, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr) { + /* Ensure that the page group isn't null. */ + MESOSPHERE_ASSERT(out != nullptr); + + /* Make sure that the region we're mapping is valid for the table. */ + const size_t size = num_pages * PageSize; + R_UNLESS(this->Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check if state allows us to create the group. */ + R_TRY(this->CheckMemoryState(address, size, state_mask | KMemoryState_FlagReferenceCounted, state | KMemoryState_FlagReferenceCounted, perm_mask, perm, attr_mask, attr)); + + /* Create a new page group for the region. */ + R_TRY(this->MakePageGroup(*out, address, num_pages)); + + /* Open a new reference to the pages in the group. */ + out->Open(); + + R_SUCCEED(); + } + + Result KPageTableBase::InvalidateProcessDataCache(KProcessAddress address, size_t size) { + /* Check that the region is in range. */ + R_UNLESS(this->Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check the memory state. */ + R_TRY(this->CheckMemoryStateContiguous(address, size, KMemoryState_FlagReferenceCounted, KMemoryState_FlagReferenceCounted, KMemoryPermission_UserReadWrite, KMemoryPermission_UserReadWrite, KMemoryAttribute_Uncached, KMemoryAttribute_None)); + + /* Get the impl. */ + auto &impl = this->GetImpl(); + + /* Begin traversal. */ + TraversalContext context; + TraversalEntry next_entry; + bool traverse_valid = impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), address); + R_UNLESS(traverse_valid, svc::ResultInvalidCurrentMemory()); + + /* Prepare tracking variables. */ + KPhysicalAddress cur_addr = next_entry.phys_addr; + size_t cur_size = next_entry.block_size - (GetInteger(cur_addr) & (next_entry.block_size - 1)); + size_t tot_size = cur_size; + + /* Iterate. */ + while (tot_size < size) { + /* Continue the traversal. */ + traverse_valid = impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + R_UNLESS(traverse_valid, svc::ResultInvalidCurrentMemory()); + + if (next_entry.phys_addr != (cur_addr + cur_size)) { + /* Check that the pages are linearly mapped. */ + R_UNLESS(IsLinearMappedPhysicalAddress(cur_addr), svc::ResultInvalidCurrentMemory()); + + /* Invalidate the block. */ + if (cur_size > 0) { + /* NOTE: Nintendo does not check the result of invalidation. */ + cpu::InvalidateDataCache(GetVoidPointer(GetLinearMappedVirtualAddress(cur_addr)), cur_size); + } + + /* Advance. */ + cur_addr = next_entry.phys_addr; + cur_size = next_entry.block_size; + } else { + cur_size += next_entry.block_size; + } + + tot_size += next_entry.block_size; + } + + /* Ensure we use the right size for the last block. */ + if (tot_size > size) { + cur_size -= (tot_size - size); + } + + /* Check that the last block is linearly mapped. */ + R_UNLESS(IsLinearMappedPhysicalAddress(cur_addr), svc::ResultInvalidCurrentMemory()); + + /* Invalidate the last block. */ + if (cur_size > 0) { + /* NOTE: Nintendo does not check the result of invalidation. */ + cpu::InvalidateDataCache(GetVoidPointer(GetLinearMappedVirtualAddress(cur_addr)), cur_size); + } + + R_SUCCEED(); + } + + Result KPageTableBase::InvalidateCurrentProcessDataCache(KProcessAddress address, size_t size) { + /* Check pre-condition: this is being called on the current process. */ + MESOSPHERE_ASSERT(this == std::addressof(GetCurrentProcess().GetPageTable().GetBasePageTable())); + + /* Check that the region is in range. */ + R_UNLESS(this->Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check the memory state. */ + R_TRY(this->CheckMemoryStateContiguous(address, size, KMemoryState_FlagReferenceCounted, KMemoryState_FlagReferenceCounted, KMemoryPermission_UserReadWrite, KMemoryPermission_UserReadWrite, KMemoryAttribute_Uncached, KMemoryAttribute_None)); + + /* Invalidate the data cache. */ + R_RETURN(cpu::InvalidateDataCache(GetVoidPointer(address), size)); + } + + bool KPageTableBase::CanReadWriteDebugMemory(KProcessAddress address, size_t size, bool force_debug_prod) { + /* Check pre-conditions. */ + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + /* If the memory is debuggable and user-readable, we can perform the access. */ + if (R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_FlagCanDebug, KMemoryState_FlagCanDebug, KMemoryPermission_NotMapped | KMemoryPermission_UserRead, KMemoryPermission_UserRead, KMemoryAttribute_None, KMemoryAttribute_None))) { + return true; + } + + /* If we're in debug mode, and the process isn't force debug prod, check if the memory is debuggable and kernel-readable and user-executable. */ + if (KTargetSystem::IsDebugMode() && !force_debug_prod) { + if (R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_FlagCanDebug, KMemoryState_FlagCanDebug, KMemoryPermission_KernelRead | KMemoryPermission_UserExecute, KMemoryPermission_KernelRead | KMemoryPermission_UserExecute, KMemoryAttribute_None, KMemoryAttribute_None))) { + return true; + } + } + + /* If neither of the above checks passed, we can't access the memory. */ + return false; + } + + Result KPageTableBase::ReadDebugMemory(void *buffer, KProcessAddress address, size_t size, bool force_debug_prod) { + /* Lightly validate the region is in range. */ + R_UNLESS(this->Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Require that the memory either be user-readable-and-mapped or debug-accessible. */ + const bool can_read = R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_None, KMemoryState_None, KMemoryPermission_NotMapped | KMemoryPermission_UserRead, KMemoryPermission_UserRead, KMemoryAttribute_None, KMemoryAttribute_None)); + if (!can_read) { + R_UNLESS(this->CanReadWriteDebugMemory(address, size, force_debug_prod), svc::ResultInvalidCurrentMemory()); + } + + /* Get the impl. */ + auto &impl = this->GetImpl(); + + /* Begin traversal. */ + TraversalContext context; + TraversalEntry next_entry; + bool traverse_valid = impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), address); + R_UNLESS(traverse_valid, svc::ResultInvalidCurrentMemory()); + + /* Prepare tracking variables. */ + KPhysicalAddress cur_addr = next_entry.phys_addr; + size_t cur_size = next_entry.block_size - (GetInteger(cur_addr) & (next_entry.block_size - 1)); + size_t tot_size = cur_size; + + auto PerformCopy = [&]() ALWAYS_INLINE_LAMBDA -> Result { + /* Ensure the address is linear mapped. */ + R_UNLESS(IsLinearMappedPhysicalAddress(cur_addr), svc::ResultInvalidCurrentMemory()); + + /* Copy as much aligned data as we can. */ + if (cur_size >= sizeof(u32)) { + const size_t copy_size = util::AlignDown(cur_size, sizeof(u32)); + const void * copy_src = GetVoidPointer(GetLinearMappedVirtualAddress(cur_addr)); + cpu::FlushDataCache(copy_src, copy_size); + R_UNLESS(UserspaceAccess::CopyMemoryToUserAligned32Bit(buffer, copy_src, copy_size), svc::ResultInvalidPointer()); + buffer = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(buffer) + copy_size); + cur_addr += copy_size; + cur_size -= copy_size; + } + + /* Copy remaining data. */ + if (cur_size > 0) { + const void * copy_src = GetVoidPointer(GetLinearMappedVirtualAddress(cur_addr)); + cpu::FlushDataCache(copy_src, cur_size); + R_UNLESS(UserspaceAccess::CopyMemoryToUser(buffer, copy_src, cur_size), svc::ResultInvalidPointer()); + } + + R_SUCCEED(); + }; + + /* Iterate. */ + while (tot_size < size) { + /* Continue the traversal. */ + traverse_valid = impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + MESOSPHERE_ASSERT(traverse_valid); + + if (next_entry.phys_addr != (cur_addr + cur_size)) { + /* Perform copy. */ + R_TRY(PerformCopy()); + + /* Advance. */ + buffer = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(buffer) + cur_size); + + cur_addr = next_entry.phys_addr; + cur_size = next_entry.block_size; + } else { + cur_size += next_entry.block_size; + } + + tot_size += next_entry.block_size; + } + + /* Ensure we use the right size for the last block. */ + if (tot_size > size) { + cur_size -= (tot_size - size); + } + + /* Perform copy for the last block. */ + R_TRY(PerformCopy()); + + R_SUCCEED(); + } + + Result KPageTableBase::WriteDebugMemory(KProcessAddress address, const void *buffer, size_t size) { + /* Lightly validate the region is in range. */ + R_UNLESS(this->Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Require that the memory either be user-writable-and-mapped or debug-accessible. */ + const bool can_write = R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_None, KMemoryState_None, KMemoryPermission_NotMapped | KMemoryPermission_UserReadWrite, KMemoryPermission_UserReadWrite, KMemoryAttribute_None, KMemoryAttribute_None)); + if (!can_write) { + R_UNLESS(this->CanReadWriteDebugMemory(address, size, false), svc::ResultInvalidCurrentMemory()); + } + + /* Get the impl. */ + auto &impl = this->GetImpl(); + + /* Begin traversal. */ + TraversalContext context; + TraversalEntry next_entry; + bool traverse_valid = impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), address); + R_UNLESS(traverse_valid, svc::ResultInvalidCurrentMemory()); + + /* Prepare tracking variables. */ + KPhysicalAddress cur_addr = next_entry.phys_addr; + size_t cur_size = next_entry.block_size - (GetInteger(cur_addr) & (next_entry.block_size - 1)); + size_t tot_size = cur_size; + + auto PerformCopy = [&]() ALWAYS_INLINE_LAMBDA -> Result { + /* Ensure the address is linear mapped. */ + R_UNLESS(IsLinearMappedPhysicalAddress(cur_addr), svc::ResultInvalidCurrentMemory()); + + /* Copy as much aligned data as we can. */ + if (cur_size >= sizeof(u32)) { + const size_t copy_size = util::AlignDown(cur_size, sizeof(u32)); + R_UNLESS(UserspaceAccess::CopyMemoryFromUserAligned32Bit(GetVoidPointer(GetLinearMappedVirtualAddress(cur_addr)), buffer, copy_size), svc::ResultInvalidCurrentMemory()); + cpu::StoreDataCache(GetVoidPointer(GetLinearMappedVirtualAddress(cur_addr)), copy_size); + + buffer = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(buffer) + copy_size); + cur_addr += copy_size; + cur_size -= copy_size; + } + + /* Copy remaining data. */ + if (cur_size > 0) { + R_UNLESS(UserspaceAccess::CopyMemoryFromUser(GetVoidPointer(GetLinearMappedVirtualAddress(cur_addr)), buffer, cur_size), svc::ResultInvalidCurrentMemory()); + cpu::StoreDataCache(GetVoidPointer(GetLinearMappedVirtualAddress(cur_addr)), cur_size); + } + + R_SUCCEED(); + }; + + /* Iterate. */ + while (tot_size < size) { + /* Continue the traversal. */ + traverse_valid = impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + MESOSPHERE_ASSERT(traverse_valid); + + if (next_entry.phys_addr != (cur_addr + cur_size)) { + /* Perform copy. */ + R_TRY(PerformCopy()); + + /* Advance. */ + buffer = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(buffer) + cur_size); + + cur_addr = next_entry.phys_addr; + cur_size = next_entry.block_size; + } else { + cur_size += next_entry.block_size; + } + + tot_size += next_entry.block_size; + } + + /* Ensure we use the right size for the last block. */ + if (tot_size > size) { + cur_size -= (tot_size - size); + } + + /* Perform copy for the last block. */ + R_TRY(PerformCopy()); + + /* Invalidate the entire instruction cache, as this svc allows modifying executable pages. */ + cpu::InvalidateEntireInstructionCache(); + + R_SUCCEED(); + } + + Result KPageTableBase::ReadIoMemoryImpl(void *buffer, KPhysicalAddress phys_addr, size_t size, KMemoryState state) { + /* Check pre-conditions. */ + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + /* Determine the mapping extents. */ + const KPhysicalAddress map_start = util::AlignDown(GetInteger(phys_addr), PageSize); + const KPhysicalAddress map_end = util::AlignUp(GetInteger(phys_addr) + size, PageSize); + const size_t map_size = map_end - map_start; + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Temporarily map the io memory. */ + KProcessAddress io_addr; + R_TRY(this->MapIoImpl(std::addressof(io_addr), updater.GetPageList(), map_start, map_size, state, KMemoryPermission_UserRead)); + + /* Ensure we unmap the io memory when we're done with it. */ + ON_SCOPE_EXIT { + const KPageProperties unmap_properties = { KMemoryPermission_None, false, false, DisableMergeAttribute_None }; + MESOSPHERE_R_ABORT_UNLESS(this->Operate(updater.GetPageList(), io_addr, map_size / PageSize, Null<KPhysicalAddress>, false, unmap_properties, OperationType_Unmap, true)); + }; + + /* Read the memory. */ + const KProcessAddress read_addr = io_addr + (GetInteger(phys_addr) & (PageSize - 1)); + switch ((GetInteger(read_addr) | size) & 3) { + case 0: + { + R_UNLESS(UserspaceAccess::ReadIoMemory32Bit(buffer, GetVoidPointer(read_addr), size), svc::ResultInvalidPointer()); + } + break; + case 2: + { + R_UNLESS(UserspaceAccess::ReadIoMemory16Bit(buffer, GetVoidPointer(read_addr), size), svc::ResultInvalidPointer()); + } + break; + default: + { + R_UNLESS(UserspaceAccess::ReadIoMemory8Bit(buffer, GetVoidPointer(read_addr), size), svc::ResultInvalidPointer()); + } + break; + } + + R_SUCCEED(); + } + + Result KPageTableBase::WriteIoMemoryImpl(KPhysicalAddress phys_addr, const void *buffer, size_t size, KMemoryState state) { + /* Check pre-conditions. */ + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + /* Determine the mapping extents. */ + const KPhysicalAddress map_start = util::AlignDown(GetInteger(phys_addr), PageSize); + const KPhysicalAddress map_end = util::AlignUp(GetInteger(phys_addr) + size, PageSize); + const size_t map_size = map_end - map_start; + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Temporarily map the io memory. */ + KProcessAddress io_addr; + R_TRY(this->MapIoImpl(std::addressof(io_addr), updater.GetPageList(), map_start, map_size, state, KMemoryPermission_UserReadWrite)); + + /* Ensure we unmap the io memory when we're done with it. */ + ON_SCOPE_EXIT { + const KPageProperties unmap_properties = { KMemoryPermission_None, false, false, DisableMergeAttribute_None }; + MESOSPHERE_R_ABORT_UNLESS(this->Operate(updater.GetPageList(), io_addr, map_size / PageSize, Null<KPhysicalAddress>, false, unmap_properties, OperationType_Unmap, true)); + }; + + /* Read the memory. */ + const KProcessAddress write_addr = io_addr + (GetInteger(phys_addr) & (PageSize - 1)); + switch ((GetInteger(write_addr) | size) & 3) { + case 0: + { + R_UNLESS(UserspaceAccess::WriteIoMemory32Bit(GetVoidPointer(write_addr), buffer, size), svc::ResultInvalidPointer()); + } + break; + case 2: + { + R_UNLESS(UserspaceAccess::WriteIoMemory16Bit(GetVoidPointer(write_addr), buffer, size), svc::ResultInvalidPointer()); + } + break; + default: + { + R_UNLESS(UserspaceAccess::WriteIoMemory8Bit(GetVoidPointer(write_addr), buffer, size), svc::ResultInvalidPointer()); + } + break; + } + + R_SUCCEED(); + } + + Result KPageTableBase::ReadDebugIoMemory(void *buffer, KProcessAddress address, size_t size, KMemoryState state) { + /* Lightly validate the range before doing anything else. */ + R_UNLESS(this->Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* We need to lock both this table, and the current process's table, so set up some aliases. */ + KPageTableBase &src_page_table = *this; + KPageTableBase &dst_page_table = GetCurrentProcess().GetPageTable().GetBasePageTable(); + + /* Acquire the table locks. */ + KScopedLightLockPair lk(src_page_table.m_general_lock, dst_page_table.m_general_lock); + + /* Check that the desired range is readable io memory. */ + R_TRY(this->CheckMemoryStateContiguous(address, size, KMemoryState_All, state, KMemoryPermission_UserRead, KMemoryPermission_UserRead, KMemoryAttribute_None, KMemoryAttribute_None)); + + /* Read the memory. */ + u8 *dst = static_cast<u8 *>(buffer); + const KProcessAddress last_address = address + size - 1; + while (address <= last_address) { + /* Get the current physical address. */ + KPhysicalAddress phys_addr; + MESOSPHERE_ABORT_UNLESS(src_page_table.GetPhysicalAddressLocked(std::addressof(phys_addr), address)); + + /* Determine the current read size. */ + const size_t cur_size = std::min<size_t>(last_address - address + 1, PageSize - (GetInteger(address) & (PageSize - 1))); + + /* Read. */ + R_TRY(dst_page_table.ReadIoMemoryImpl(dst, phys_addr, cur_size, state)); + + /* Advance. */ + address += cur_size; + dst += cur_size; + } + + R_SUCCEED(); + } + + Result KPageTableBase::WriteDebugIoMemory(KProcessAddress address, const void *buffer, size_t size, KMemoryState state) { + /* Lightly validate the range before doing anything else. */ + R_UNLESS(this->Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* We need to lock both this table, and the current process's table, so set up some aliases. */ + KPageTableBase &src_page_table = *this; + KPageTableBase &dst_page_table = GetCurrentProcess().GetPageTable().GetBasePageTable(); + + /* Acquire the table locks. */ + KScopedLightLockPair lk(src_page_table.m_general_lock, dst_page_table.m_general_lock); + + /* Check that the desired range is writable io memory. */ + R_TRY(this->CheckMemoryStateContiguous(address, size, KMemoryState_All, state, KMemoryPermission_UserReadWrite, KMemoryPermission_UserReadWrite, KMemoryAttribute_None, KMemoryAttribute_None)); + + /* Read the memory. */ + const u8 *src = static_cast<const u8 *>(buffer); + const KProcessAddress last_address = address + size - 1; + while (address <= last_address) { + /* Get the current physical address. */ + KPhysicalAddress phys_addr; + MESOSPHERE_ABORT_UNLESS(src_page_table.GetPhysicalAddressLocked(std::addressof(phys_addr), address)); + + /* Determine the current read size. */ + const size_t cur_size = std::min<size_t>(last_address - address + 1, PageSize - (GetInteger(address) & (PageSize - 1))); + + /* Read. */ + R_TRY(dst_page_table.WriteIoMemoryImpl(phys_addr, src, cur_size, state)); + + /* Advance. */ + address += cur_size; + src += cur_size; + } + + R_SUCCEED(); + } + + Result KPageTableBase::LockForMapDeviceAddressSpace(bool *out_is_io, KProcessAddress address, size_t size, KMemoryPermission perm, bool is_aligned, bool check_heap) { + /* Lightly validate the range before doing anything else. */ + const size_t num_pages = size / PageSize; + R_UNLESS(this->Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check the memory state. */ + const u32 test_state = (is_aligned ? KMemoryState_FlagCanAlignedDeviceMap : KMemoryState_FlagCanDeviceMap) | (check_heap ? KMemoryState_FlagReferenceCounted : KMemoryState_None); + size_t num_allocator_blocks; + KMemoryState old_state; + R_TRY(this->CheckMemoryState(std::addressof(old_state), nullptr, nullptr, std::addressof(num_allocator_blocks), address, size, test_state, test_state, perm, perm, KMemoryAttribute_IpcLocked | KMemoryAttribute_Locked, KMemoryAttribute_None, KMemoryAttribute_DeviceShared)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* Update the memory blocks. */ + m_memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages, &KMemoryBlock::ShareToDevice, KMemoryPermission_None); + + /* Set whether the locked memory was io. */ + *out_is_io = static_cast<ams::svc::MemoryState>(old_state & KMemoryState_Mask) == ams::svc::MemoryState_Io; + + R_SUCCEED(); + } + + Result KPageTableBase::LockForUnmapDeviceAddressSpace(KProcessAddress address, size_t size, bool check_heap) { + /* Lightly validate the range before doing anything else. */ + const size_t num_pages = size / PageSize; + R_UNLESS(this->Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check the memory state. */ + const u32 test_state = KMemoryState_FlagCanDeviceMap | (check_heap ? KMemoryState_FlagReferenceCounted : KMemoryState_None); + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryStateContiguous(std::addressof(num_allocator_blocks), + address, size, + test_state, test_state, + KMemoryPermission_None, KMemoryPermission_None, + KMemoryAttribute_DeviceShared | KMemoryAttribute_Locked, KMemoryAttribute_DeviceShared)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* Update the memory blocks. */ + const KMemoryBlockManager::MemoryBlockLockFunction lock_func = m_enable_device_address_space_merge ? &KMemoryBlock::UpdateDeviceDisableMergeStateForShare : &KMemoryBlock::UpdateDeviceDisableMergeStateForShareRight; + m_memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages, lock_func, KMemoryPermission_None); + + R_SUCCEED(); + } + + Result KPageTableBase::UnlockForDeviceAddressSpace(KProcessAddress address, size_t size) { + /* Lightly validate the range before doing anything else. */ + const size_t num_pages = size / PageSize; + R_UNLESS(this->Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check the memory state. */ + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryStateContiguous(std::addressof(num_allocator_blocks), + address, size, + KMemoryState_FlagCanDeviceMap, KMemoryState_FlagCanDeviceMap, + KMemoryPermission_None, KMemoryPermission_None, + KMemoryAttribute_DeviceShared | KMemoryAttribute_Locked, KMemoryAttribute_DeviceShared)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* Update the memory blocks. */ + m_memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages, &KMemoryBlock::UnshareToDevice, KMemoryPermission_None); + + R_SUCCEED(); + } + + Result KPageTableBase::UnlockForDeviceAddressSpacePartialMap(KProcessAddress address, size_t size) { + /* Lightly validate the range before doing anything else. */ + const size_t num_pages = size / PageSize; + R_UNLESS(this->Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check memory state. */ + size_t allocator_num_blocks = 0; + R_TRY(this->CheckMemoryStateContiguous(std::addressof(allocator_num_blocks), + address, size, + KMemoryState_FlagCanDeviceMap, KMemoryState_FlagCanDeviceMap, + KMemoryPermission_None, KMemoryPermission_None, + KMemoryAttribute_DeviceShared | KMemoryAttribute_Locked, KMemoryAttribute_DeviceShared)); + + /* Create an update allocator for the region. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, allocator_num_blocks); + R_TRY(allocator_result); + + /* Update the memory blocks. */ + m_memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages, m_enable_device_address_space_merge ? &KMemoryBlock::UpdateDeviceDisableMergeStateForUnshare : &KMemoryBlock::UpdateDeviceDisableMergeStateForUnshareRight, KMemoryPermission_None); + + R_SUCCEED(); + } + + Result KPageTableBase::OpenMemoryRangeForMapDeviceAddressSpace(KPageTableBase::MemoryRange *out, KProcessAddress address, size_t size, KMemoryPermission perm, bool is_aligned) { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Get the range. */ + const u32 test_state = (is_aligned ? KMemoryState_FlagCanAlignedDeviceMap : KMemoryState_FlagCanDeviceMap); + R_TRY(this->GetContiguousMemoryRangeWithState(out, + address, size, + test_state, test_state, + perm, perm, + KMemoryAttribute_IpcLocked | KMemoryAttribute_Locked, KMemoryAttribute_None)); + + /* We got the range, so open it. */ + out->Open(); + + R_SUCCEED(); + } + + Result KPageTableBase::OpenMemoryRangeForUnmapDeviceAddressSpace(MemoryRange *out, KProcessAddress address, size_t size) { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Get the range. */ + R_TRY(this->GetContiguousMemoryRangeWithState(out, + address, size, + KMemoryState_FlagCanDeviceMap, KMemoryState_FlagCanDeviceMap, + KMemoryPermission_None, KMemoryPermission_None, + KMemoryAttribute_DeviceShared | KMemoryAttribute_Locked, KMemoryAttribute_DeviceShared)); + + /* We got the range, so open it. */ + out->Open(); + + R_SUCCEED(); + } + + Result KPageTableBase::LockForIpcUserBuffer(KPhysicalAddress *out, KProcessAddress address, size_t size) { + R_RETURN(this->LockMemoryAndOpen(nullptr, out, address, size, + KMemoryState_FlagCanIpcUserBuffer, KMemoryState_FlagCanIpcUserBuffer, + KMemoryPermission_All, KMemoryPermission_UserReadWrite, + KMemoryAttribute_All, KMemoryAttribute_None, + static_cast<KMemoryPermission>(KMemoryPermission_NotMapped | KMemoryPermission_KernelReadWrite), + KMemoryAttribute_Locked)); + } + + Result KPageTableBase::UnlockForIpcUserBuffer(KProcessAddress address, size_t size) { + R_RETURN(this->UnlockMemory(address, size, + KMemoryState_FlagCanIpcUserBuffer, KMemoryState_FlagCanIpcUserBuffer, + KMemoryPermission_None, KMemoryPermission_None, + KMemoryAttribute_All, KMemoryAttribute_Locked, + KMemoryPermission_UserReadWrite, + KMemoryAttribute_Locked, nullptr)); + } + + Result KPageTableBase::LockForTransferMemory(KPageGroup *out, KProcessAddress address, size_t size, KMemoryPermission perm) { + R_RETURN(this->LockMemoryAndOpen(out, nullptr, address, size, + KMemoryState_FlagCanTransfer, KMemoryState_FlagCanTransfer, + KMemoryPermission_All, KMemoryPermission_UserReadWrite, + KMemoryAttribute_All, KMemoryAttribute_None, + perm, + KMemoryAttribute_Locked)); + } + + Result KPageTableBase::UnlockForTransferMemory(KProcessAddress address, size_t size, const KPageGroup &pg) { + R_RETURN(this->UnlockMemory(address, size, + KMemoryState_FlagCanTransfer, KMemoryState_FlagCanTransfer, + KMemoryPermission_None, KMemoryPermission_None, + KMemoryAttribute_All, KMemoryAttribute_Locked, + KMemoryPermission_UserReadWrite, + KMemoryAttribute_Locked, std::addressof(pg))); + } + + Result KPageTableBase::LockForCodeMemory(KPageGroup *out, KProcessAddress address, size_t size) { + R_RETURN(this->LockMemoryAndOpen(out, nullptr, address, size, + KMemoryState_FlagCanCodeMemory, KMemoryState_FlagCanCodeMemory, + KMemoryPermission_All, KMemoryPermission_UserReadWrite, + KMemoryAttribute_All, KMemoryAttribute_None, + static_cast<KMemoryPermission>(KMemoryPermission_NotMapped | KMemoryPermission_KernelReadWrite), + KMemoryAttribute_Locked)); + } + + Result KPageTableBase::UnlockForCodeMemory(KProcessAddress address, size_t size, const KPageGroup &pg) { + R_RETURN(this->UnlockMemory(address, size, + KMemoryState_FlagCanCodeMemory, KMemoryState_FlagCanCodeMemory, + KMemoryPermission_None, KMemoryPermission_None, + KMemoryAttribute_All, KMemoryAttribute_Locked, + KMemoryPermission_UserReadWrite, + KMemoryAttribute_Locked, std::addressof(pg))); + } + + Result KPageTableBase::OpenMemoryRangeForProcessCacheOperation(MemoryRange *out, KProcessAddress address, size_t size) { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Get the range. */ + R_TRY(this->GetContiguousMemoryRangeWithState(out, + address, size, + KMemoryState_FlagReferenceCounted, KMemoryState_FlagReferenceCounted, + KMemoryPermission_UserRead, KMemoryPermission_UserRead, + KMemoryAttribute_Uncached, KMemoryAttribute_None)); + + /* We got the range, so open it. */ + out->Open(); + + R_SUCCEED(); + } + + Result KPageTableBase::CopyMemoryFromLinearToUser(KProcessAddress dst_addr, size_t size, KProcessAddress src_addr, u32 src_state_mask, u32 src_state, KMemoryPermission src_test_perm, u32 src_attr_mask, u32 src_attr) { + /* Lightly validate the range before doing anything else. */ + R_UNLESS(this->Contains(src_addr, size), svc::ResultInvalidCurrentMemory()); + + /* Copy the memory. */ + { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check memory state. */ + R_TRY(this->CheckMemoryStateContiguous(src_addr, size, src_state_mask, src_state, src_test_perm, src_test_perm, src_attr_mask | KMemoryAttribute_Uncached, src_attr)); + + auto &impl = this->GetImpl(); + + /* Begin traversal. */ + TraversalContext context; + TraversalEntry next_entry; + bool traverse_valid = impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), src_addr); + MESOSPHERE_ABORT_UNLESS(traverse_valid); + + /* Prepare tracking variables. */ + KPhysicalAddress cur_addr = next_entry.phys_addr; + size_t cur_size = next_entry.block_size - (GetInteger(cur_addr) & (next_entry.block_size - 1)); + size_t tot_size = cur_size; + + auto PerformCopy = [&]() ALWAYS_INLINE_LAMBDA -> Result { + /* Ensure the address is linear mapped. */ + R_UNLESS(IsLinearMappedPhysicalAddress(cur_addr), svc::ResultInvalidCurrentMemory()); + + /* Copy as much aligned data as we can. */ + if (cur_size >= sizeof(u32)) { + const size_t copy_size = util::AlignDown(cur_size, sizeof(u32)); + R_UNLESS(UserspaceAccess::CopyMemoryToUserAligned32Bit(GetVoidPointer(dst_addr), GetVoidPointer(GetLinearMappedVirtualAddress(cur_addr)), copy_size), svc::ResultInvalidCurrentMemory()); + dst_addr += copy_size; + cur_addr += copy_size; + cur_size -= copy_size; + } + + /* Copy remaining data. */ + if (cur_size > 0) { + R_UNLESS(UserspaceAccess::CopyMemoryToUser(GetVoidPointer(dst_addr), GetVoidPointer(GetLinearMappedVirtualAddress(cur_addr)), cur_size), svc::ResultInvalidCurrentMemory()); + } + + R_SUCCEED(); + }; + + /* Iterate. */ + while (tot_size < size) { + /* Continue the traversal. */ + traverse_valid = impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + MESOSPHERE_ASSERT(traverse_valid); + + if (next_entry.phys_addr != (cur_addr + cur_size)) { + /* Perform copy. */ + R_TRY(PerformCopy()); + + /* Advance. */ + dst_addr += cur_size; + + cur_addr = next_entry.phys_addr; + cur_size = next_entry.block_size; + } else { + cur_size += next_entry.block_size; + } + + tot_size += next_entry.block_size; + } + + /* Ensure we use the right size for the last block. */ + if (tot_size > size) { + cur_size -= (tot_size - size); + } + + /* Perform copy for the last block. */ + R_TRY(PerformCopy()); + } + + R_SUCCEED(); + } + + Result KPageTableBase::CopyMemoryFromLinearToKernel(KProcessAddress dst_addr, size_t size, KProcessAddress src_addr, u32 src_state_mask, u32 src_state, KMemoryPermission src_test_perm, u32 src_attr_mask, u32 src_attr) { + /* Lightly validate the range before doing anything else. */ + R_UNLESS(this->Contains(src_addr, size), svc::ResultInvalidCurrentMemory()); + + /* Copy the memory. */ + { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check memory state. */ + R_TRY(this->CheckMemoryStateContiguous(src_addr, size, src_state_mask, src_state, src_test_perm, src_test_perm, src_attr_mask | KMemoryAttribute_Uncached, src_attr)); + + auto &impl = this->GetImpl(); + + /* Begin traversal. */ + TraversalContext context; + TraversalEntry next_entry; + bool traverse_valid = impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), src_addr); + MESOSPHERE_ABORT_UNLESS(traverse_valid); + + /* Prepare tracking variables. */ + KPhysicalAddress cur_addr = next_entry.phys_addr; + size_t cur_size = next_entry.block_size - (GetInteger(cur_addr) & (next_entry.block_size - 1)); + size_t tot_size = cur_size; + + auto PerformCopy = [&]() ALWAYS_INLINE_LAMBDA -> Result { + /* Ensure the address is linear mapped. */ + R_UNLESS(IsLinearMappedPhysicalAddress(cur_addr), svc::ResultInvalidCurrentMemory()); + + /* Copy the data. */ + std::memcpy(GetVoidPointer(dst_addr), GetVoidPointer(GetLinearMappedVirtualAddress(cur_addr)), cur_size); + + R_SUCCEED(); + }; + + /* Iterate. */ + while (tot_size < size) { + /* Continue the traversal. */ + traverse_valid = impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + MESOSPHERE_ASSERT(traverse_valid); + + if (next_entry.phys_addr != (cur_addr + cur_size)) { + /* Perform copy. */ + R_TRY(PerformCopy()); + + /* Advance. */ + dst_addr += cur_size; + + cur_addr = next_entry.phys_addr; + cur_size = next_entry.block_size; + } else { + cur_size += next_entry.block_size; + } + + tot_size += next_entry.block_size; + } + + /* Ensure we use the right size for the last block. */ + if (tot_size > size) { + cur_size -= (tot_size - size); + } + + /* Perform copy for the last block. */ + R_TRY(PerformCopy()); + } + + R_SUCCEED(); + } + + Result KPageTableBase::CopyMemoryFromUserToLinear(KProcessAddress dst_addr, size_t size, u32 dst_state_mask, u32 dst_state, KMemoryPermission dst_test_perm, u32 dst_attr_mask, u32 dst_attr, KProcessAddress src_addr) { + /* Lightly validate the range before doing anything else. */ + R_UNLESS(this->Contains(dst_addr, size), svc::ResultInvalidCurrentMemory()); + + /* Copy the memory. */ + { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check memory state. */ + R_TRY(this->CheckMemoryStateContiguous(dst_addr, size, dst_state_mask, dst_state, dst_test_perm, dst_test_perm, dst_attr_mask | KMemoryAttribute_Uncached, dst_attr)); + + auto &impl = this->GetImpl(); + + /* Begin traversal. */ + TraversalContext context; + TraversalEntry next_entry; + bool traverse_valid = impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), dst_addr); + MESOSPHERE_ABORT_UNLESS(traverse_valid); + + /* Prepare tracking variables. */ + KPhysicalAddress cur_addr = next_entry.phys_addr; + size_t cur_size = next_entry.block_size - (GetInteger(cur_addr) & (next_entry.block_size - 1)); + size_t tot_size = cur_size; + + auto PerformCopy = [&]() ALWAYS_INLINE_LAMBDA -> Result { + /* Ensure the address is linear mapped. */ + R_UNLESS(IsLinearMappedPhysicalAddress(cur_addr), svc::ResultInvalidCurrentMemory()); + + /* Copy as much aligned data as we can. */ + if (cur_size >= sizeof(u32)) { + const size_t copy_size = util::AlignDown(cur_size, sizeof(u32)); + R_UNLESS(UserspaceAccess::CopyMemoryFromUserAligned32Bit(GetVoidPointer(GetLinearMappedVirtualAddress(cur_addr)), GetVoidPointer(src_addr), copy_size), svc::ResultInvalidCurrentMemory()); + src_addr += copy_size; + cur_addr += copy_size; + cur_size -= copy_size; + } + + /* Copy remaining data. */ + if (cur_size > 0) { + R_UNLESS(UserspaceAccess::CopyMemoryFromUser(GetVoidPointer(GetLinearMappedVirtualAddress(cur_addr)), GetVoidPointer(src_addr), cur_size), svc::ResultInvalidCurrentMemory()); + } + + R_SUCCEED(); + }; + + /* Iterate. */ + while (tot_size < size) { + /* Continue the traversal. */ + traverse_valid = impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + MESOSPHERE_ASSERT(traverse_valid); + + if (next_entry.phys_addr != (cur_addr + cur_size)) { + /* Perform copy. */ + R_TRY(PerformCopy()); + + /* Advance. */ + src_addr += cur_size; + + cur_addr = next_entry.phys_addr; + cur_size = next_entry.block_size; + } else { + cur_size += next_entry.block_size; + } + + tot_size += next_entry.block_size; + } + + /* Ensure we use the right size for the last block. */ + if (tot_size > size) { + cur_size -= (tot_size - size); + } + + /* Perform copy for the last block. */ + R_TRY(PerformCopy()); + } + + R_SUCCEED(); + } + + Result KPageTableBase::CopyMemoryFromKernelToLinear(KProcessAddress dst_addr, size_t size, u32 dst_state_mask, u32 dst_state, KMemoryPermission dst_test_perm, u32 dst_attr_mask, u32 dst_attr, KProcessAddress src_addr) { + /* Lightly validate the range before doing anything else. */ + R_UNLESS(this->Contains(dst_addr, size), svc::ResultInvalidCurrentMemory()); + + /* Copy the memory. */ + { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check memory state. */ + R_TRY(this->CheckMemoryStateContiguous(dst_addr, size, dst_state_mask, dst_state, dst_test_perm, dst_test_perm, dst_attr_mask | KMemoryAttribute_Uncached, dst_attr)); + + auto &impl = this->GetImpl(); + + /* Begin traversal. */ + TraversalContext context; + TraversalEntry next_entry; + bool traverse_valid = impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), dst_addr); + MESOSPHERE_ABORT_UNLESS(traverse_valid); + + /* Prepare tracking variables. */ + KPhysicalAddress cur_addr = next_entry.phys_addr; + size_t cur_size = next_entry.block_size - (GetInteger(cur_addr) & (next_entry.block_size - 1)); + size_t tot_size = cur_size; + + auto PerformCopy = [&]() ALWAYS_INLINE_LAMBDA -> Result { + /* Ensure the address is linear mapped. */ + R_UNLESS(IsLinearMappedPhysicalAddress(cur_addr), svc::ResultInvalidCurrentMemory()); + + /* Copy the data. */ + std::memcpy(GetVoidPointer(GetLinearMappedVirtualAddress(cur_addr)), GetVoidPointer(src_addr), cur_size); + + R_SUCCEED(); + }; + + /* Iterate. */ + while (tot_size < size) { + /* Continue the traversal. */ + traverse_valid = impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + MESOSPHERE_ASSERT(traverse_valid); + + if (next_entry.phys_addr != (cur_addr + cur_size)) { + /* Perform copy. */ + R_TRY(PerformCopy()); + + /* Advance. */ + src_addr += cur_size; + + cur_addr = next_entry.phys_addr; + cur_size = next_entry.block_size; + } else { + cur_size += next_entry.block_size; + } + + tot_size += next_entry.block_size; + } + + /* Ensure we use the right size for the last block. */ + if (tot_size > size) { + cur_size -= (tot_size - size); + } + + /* Perform copy for the last block. */ + R_TRY(PerformCopy()); + } + + R_SUCCEED(); + } + + Result KPageTableBase::CopyMemoryFromHeapToHeap(KPageTableBase &dst_page_table, KProcessAddress dst_addr, size_t size, u32 dst_state_mask, u32 dst_state, KMemoryPermission dst_test_perm, u32 dst_attr_mask, u32 dst_attr, KProcessAddress src_addr, u32 src_state_mask, u32 src_state, KMemoryPermission src_test_perm, u32 src_attr_mask, u32 src_attr) { + /* For convenience, alias this. */ + KPageTableBase &src_page_table = *this; + + /* Lightly validate the ranges before doing anything else. */ + R_UNLESS(src_page_table.Contains(src_addr, size), svc::ResultInvalidCurrentMemory()); + R_UNLESS(dst_page_table.Contains(dst_addr, size), svc::ResultInvalidCurrentMemory()); + + /* Copy the memory. */ + { + /* Acquire the table locks. */ + KScopedLightLockPair lk(src_page_table.m_general_lock, dst_page_table.m_general_lock); + + /* Check memory state. */ + R_TRY(src_page_table.CheckMemoryStateContiguous(src_addr, size, src_state_mask, src_state, src_test_perm, src_test_perm, src_attr_mask | KMemoryAttribute_Uncached, src_attr)); + R_TRY(dst_page_table.CheckMemoryStateContiguous(dst_addr, size, dst_state_mask, dst_state, dst_test_perm, dst_test_perm, dst_attr_mask | KMemoryAttribute_Uncached, dst_attr)); + + /* Get implementations. */ + auto &src_impl = src_page_table.GetImpl(); + auto &dst_impl = dst_page_table.GetImpl(); + + /* Prepare for traversal. */ + TraversalContext src_context; + TraversalContext dst_context; + TraversalEntry src_next_entry; + TraversalEntry dst_next_entry; + bool traverse_valid; + + /* Begin traversal. */ + traverse_valid = src_impl.BeginTraversal(std::addressof(src_next_entry), std::addressof(src_context), src_addr); + MESOSPHERE_ABORT_UNLESS(traverse_valid); + traverse_valid = dst_impl.BeginTraversal(std::addressof(dst_next_entry), std::addressof(dst_context), dst_addr); + MESOSPHERE_ABORT_UNLESS(traverse_valid); + + /* Prepare tracking variables. */ + KPhysicalAddress cur_src_block_addr = src_next_entry.phys_addr; + KPhysicalAddress cur_dst_block_addr = dst_next_entry.phys_addr; + size_t cur_src_size = src_next_entry.block_size - (GetInteger(cur_src_block_addr) & (src_next_entry.block_size - 1)); + size_t cur_dst_size = dst_next_entry.block_size - (GetInteger(cur_dst_block_addr) & (dst_next_entry.block_size - 1)); + + /* Adjust the initial block sizes. */ + src_next_entry.block_size = cur_src_size; + dst_next_entry.block_size = cur_dst_size; + + /* Before we get any crazier, succeed if there's nothing to do. */ + R_SUCCEED_IF(size == 0); + + /* We're going to manage dual traversal via an offset against the total size. */ + KPhysicalAddress cur_src_addr = cur_src_block_addr; + KPhysicalAddress cur_dst_addr = cur_dst_block_addr; + size_t cur_min_size = std::min<size_t>(cur_src_size, cur_dst_size); + + /* Iterate. */ + size_t ofs = 0; + while (ofs < size) { + /* Determine how much we can copy this iteration. */ + const size_t cur_copy_size = std::min<size_t>(cur_min_size, size - ofs); + + /* If we need to advance the traversals, do so. */ + bool updated_src = false, updated_dst = false, skip_copy = false; + if (ofs + cur_copy_size != size) { + if (cur_src_addr + cur_min_size == cur_src_block_addr + cur_src_size) { + /* Continue the src traversal. */ + traverse_valid = src_impl.ContinueTraversal(std::addressof(src_next_entry), std::addressof(src_context)); + MESOSPHERE_ASSERT(traverse_valid); + + /* Update source. */ + updated_src = cur_src_addr + cur_min_size != GetInteger(src_next_entry.phys_addr); + } + + if (cur_dst_addr + cur_min_size == dst_next_entry.phys_addr + dst_next_entry.block_size) { + /* Continue the dst traversal. */ + traverse_valid = dst_impl.ContinueTraversal(std::addressof(dst_next_entry), std::addressof(dst_context)); + MESOSPHERE_ASSERT(traverse_valid); + + /* Update destination. */ + updated_dst = cur_dst_addr + cur_min_size != GetInteger(dst_next_entry.phys_addr); + } + + /* If we didn't update either of source/destination, skip the copy this iteration. */ + if (!updated_src && !updated_dst) { + skip_copy = true; + + /* Update the source block address. */ + cur_src_block_addr = src_next_entry.phys_addr; + } + } + + /* Do the copy, unless we're skipping it. */ + if (!skip_copy) { + /* We need both ends of the copy to be heap blocks. */ + R_UNLESS(IsHeapPhysicalAddress(cur_src_addr), svc::ResultInvalidCurrentMemory()); + R_UNLESS(IsHeapPhysicalAddress(cur_dst_addr), svc::ResultInvalidCurrentMemory()); + + /* Copy the data. */ + std::memcpy(GetVoidPointer(GetHeapVirtualAddress(cur_dst_addr)), GetVoidPointer(GetHeapVirtualAddress(cur_src_addr)), cur_copy_size); + + /* Update. */ + cur_src_block_addr = src_next_entry.phys_addr; + cur_src_addr = updated_src ? cur_src_block_addr : cur_src_addr + cur_copy_size; + cur_dst_block_addr = dst_next_entry.phys_addr; + cur_dst_addr = updated_dst ? cur_dst_block_addr : cur_dst_addr + cur_copy_size; + + /* Advance offset. */ + ofs += cur_copy_size; + } + + /* Update min size. */ + cur_src_size = src_next_entry.block_size; + cur_dst_size = dst_next_entry.block_size; + cur_min_size = std::min<size_t>(cur_src_block_addr - cur_src_addr + cur_src_size, cur_dst_block_addr - cur_dst_addr + cur_dst_size); + } + } + + R_SUCCEED(); + } + + Result KPageTableBase::CopyMemoryFromHeapToHeapWithoutCheckDestination(KPageTableBase &dst_page_table, KProcessAddress dst_addr, size_t size, u32 dst_state_mask, u32 dst_state, KMemoryPermission dst_test_perm, u32 dst_attr_mask, u32 dst_attr, KProcessAddress src_addr, u32 src_state_mask, u32 src_state, KMemoryPermission src_test_perm, u32 src_attr_mask, u32 src_attr) { + /* For convenience, alias this. */ + KPageTableBase &src_page_table = *this; + + /* Lightly validate the ranges before doing anything else. */ + R_UNLESS(src_page_table.Contains(src_addr, size), svc::ResultInvalidCurrentMemory()); + R_UNLESS(dst_page_table.Contains(dst_addr, size), svc::ResultInvalidCurrentMemory()); + + /* Copy the memory. */ + { + /* Acquire the table locks. */ + KScopedLightLockPair lk(src_page_table.m_general_lock, dst_page_table.m_general_lock); + + /* Check memory state for source. */ + R_TRY(src_page_table.CheckMemoryStateContiguous(src_addr, size, src_state_mask, src_state, src_test_perm, src_test_perm, src_attr_mask | KMemoryAttribute_Uncached, src_attr)); + + /* Destination state is intentionally unchecked. */ + MESOSPHERE_UNUSED(dst_state_mask, dst_state, dst_test_perm, dst_attr_mask, dst_attr); + + /* Get implementations. */ + auto &src_impl = src_page_table.GetImpl(); + auto &dst_impl = dst_page_table.GetImpl(); + + /* Prepare for traversal. */ + TraversalContext src_context; + TraversalContext dst_context; + TraversalEntry src_next_entry; + TraversalEntry dst_next_entry; + bool traverse_valid; + + /* Begin traversal. */ + traverse_valid = src_impl.BeginTraversal(std::addressof(src_next_entry), std::addressof(src_context), src_addr); + MESOSPHERE_ABORT_UNLESS(traverse_valid); + traverse_valid = dst_impl.BeginTraversal(std::addressof(dst_next_entry), std::addressof(dst_context), dst_addr); + MESOSPHERE_ABORT_UNLESS(traverse_valid); + + /* Prepare tracking variables. */ + KPhysicalAddress cur_src_block_addr = src_next_entry.phys_addr; + KPhysicalAddress cur_dst_block_addr = dst_next_entry.phys_addr; + size_t cur_src_size = src_next_entry.block_size - (GetInteger(cur_src_block_addr) & (src_next_entry.block_size - 1)); + size_t cur_dst_size = dst_next_entry.block_size - (GetInteger(cur_dst_block_addr) & (dst_next_entry.block_size - 1)); + + /* Adjust the initial block sizes. */ + src_next_entry.block_size = cur_src_size; + dst_next_entry.block_size = cur_dst_size; + + /* Before we get any crazier, succeed if there's nothing to do. */ + R_SUCCEED_IF(size == 0); + + /* We're going to manage dual traversal via an offset against the total size. */ + KPhysicalAddress cur_src_addr = cur_src_block_addr; + KPhysicalAddress cur_dst_addr = cur_dst_block_addr; + size_t cur_min_size = std::min<size_t>(cur_src_size, cur_dst_size); + + /* Iterate. */ + size_t ofs = 0; + while (ofs < size) { + /* Determine how much we can copy this iteration. */ + const size_t cur_copy_size = std::min<size_t>(cur_min_size, size - ofs); + + /* If we need to advance the traversals, do so. */ + bool updated_src = false, updated_dst = false, skip_copy = false; + if (ofs + cur_copy_size != size) { + if (cur_src_addr + cur_min_size == cur_src_block_addr + cur_src_size) { + /* Continue the src traversal. */ + traverse_valid = src_impl.ContinueTraversal(std::addressof(src_next_entry), std::addressof(src_context)); + MESOSPHERE_ASSERT(traverse_valid); + + /* Update source. */ + updated_src = cur_src_addr + cur_min_size != GetInteger(src_next_entry.phys_addr); + } + + if (cur_dst_addr + cur_min_size == dst_next_entry.phys_addr + dst_next_entry.block_size) { + /* Continue the dst traversal. */ + traverse_valid = dst_impl.ContinueTraversal(std::addressof(dst_next_entry), std::addressof(dst_context)); + MESOSPHERE_ASSERT(traverse_valid); + + /* Update destination. */ + updated_dst = cur_dst_addr + cur_min_size != GetInteger(dst_next_entry.phys_addr); + } + + /* If we didn't update either of source/destination, skip the copy this iteration. */ + if (!updated_src && !updated_dst) { + skip_copy = true; + + /* Update the source block address. */ + cur_src_block_addr = src_next_entry.phys_addr; + } + } + + /* Do the copy, unless we're skipping it. */ + if (!skip_copy) { + /* We need both ends of the copy to be heap blocks. */ + R_UNLESS(IsHeapPhysicalAddress(cur_src_addr), svc::ResultInvalidCurrentMemory()); + R_UNLESS(IsHeapPhysicalAddress(cur_dst_addr), svc::ResultInvalidCurrentMemory()); + + /* Copy the data. */ + std::memcpy(GetVoidPointer(GetHeapVirtualAddress(cur_dst_addr)), GetVoidPointer(GetHeapVirtualAddress(cur_src_addr)), cur_copy_size); + + /* Update. */ + cur_src_block_addr = src_next_entry.phys_addr; + cur_src_addr = updated_src ? cur_src_block_addr : cur_src_addr + cur_copy_size; + cur_dst_block_addr = dst_next_entry.phys_addr; + cur_dst_addr = updated_dst ? cur_dst_block_addr : cur_dst_addr + cur_copy_size; + + /* Advance offset. */ + ofs += cur_copy_size; + } + + /* Update min size. */ + cur_src_size = src_next_entry.block_size; + cur_dst_size = dst_next_entry.block_size; + cur_min_size = std::min<size_t>(cur_src_block_addr - cur_src_addr + cur_src_size, cur_dst_block_addr - cur_dst_addr + cur_dst_size); + } + } + + R_SUCCEED(); + } + + #pragma GCC push_options + #pragma GCC optimize ("-O3") + + Result KPageTableBase::SetupForIpcClient(PageLinkedList *page_list, size_t *out_blocks_needed, KProcessAddress address, size_t size, KMemoryPermission test_perm, KMemoryState dst_state) { + /* Validate pre-conditions. */ + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + MESOSPHERE_ASSERT(test_perm == KMemoryPermission_UserReadWrite || test_perm == KMemoryPermission_UserRead); + + /* Check that the address is in range. */ + R_UNLESS(this->Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Get the source permission. */ + const auto src_perm = static_cast<KMemoryPermission>((test_perm == KMemoryPermission_UserReadWrite) ? (KMemoryPermission_KernelReadWrite | KMemoryPermission_NotMapped) : KMemoryPermission_UserRead); + + /* Get aligned extents. */ + const KProcessAddress aligned_src_start = util::AlignDown(GetInteger(address), PageSize); + const KProcessAddress aligned_src_end = util::AlignUp(GetInteger(address) + size, PageSize); + const KProcessAddress mapping_src_start = util::AlignUp(GetInteger(address), PageSize); + const KProcessAddress mapping_src_end = util::AlignDown(GetInteger(address) + size, PageSize); + + const auto aligned_src_last = GetInteger(aligned_src_end) - 1; + const auto mapping_src_last = GetInteger(mapping_src_end) - 1; + + /* Get the test state and attribute mask. */ + u32 test_state; + u32 test_attr_mask; + switch (dst_state) { + case KMemoryState_Ipc: + test_state = KMemoryState_FlagCanUseIpc; + test_attr_mask = KMemoryAttribute_All & (~(KMemoryAttribute_PermissionLocked | KMemoryAttribute_IpcLocked)); + break; + case KMemoryState_NonSecureIpc: + test_state = KMemoryState_FlagCanUseNonSecureIpc; + test_attr_mask = KMemoryAttribute_All & (~(KMemoryAttribute_PermissionLocked | KMemoryAttribute_DeviceShared | KMemoryAttribute_IpcLocked)); + break; + case KMemoryState_NonDeviceIpc: + test_state = KMemoryState_FlagCanUseNonDeviceIpc; + test_attr_mask = KMemoryAttribute_All & (~(KMemoryAttribute_PermissionLocked | KMemoryAttribute_DeviceShared | KMemoryAttribute_IpcLocked)); + break; + default: + R_THROW(svc::ResultInvalidCombination()); + } + + /* Ensure that on failure, we roll back appropriately. */ + size_t mapped_size = 0; + ON_RESULT_FAILURE { + if (mapped_size > 0) { + this->CleanupForIpcClientOnServerSetupFailure(page_list, mapping_src_start, mapped_size, src_perm); + } + }; + + size_t blocks_needed = 0; + + /* Iterate, mapping as needed. */ + KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(aligned_src_start); + while (true) { + /* Validate the current block. */ + R_TRY(this->CheckMemoryState(it, test_state, test_state, test_perm, test_perm, test_attr_mask, KMemoryAttribute_None)); + + if (mapping_src_start < mapping_src_end && GetInteger(mapping_src_start) < GetInteger(it->GetEndAddress()) && GetInteger(it->GetAddress()) < GetInteger(mapping_src_end)) { + const auto cur_start = it->GetAddress() >= GetInteger(mapping_src_start) ? GetInteger(it->GetAddress()) : GetInteger(mapping_src_start); + const auto cur_end = mapping_src_last >= GetInteger(it->GetLastAddress()) ? GetInteger(it->GetEndAddress()) : GetInteger(mapping_src_end); + const size_t cur_size = cur_end - cur_start; + + if (GetInteger(it->GetAddress()) < GetInteger(mapping_src_start)) { + ++blocks_needed; + } + if (mapping_src_last < GetInteger(it->GetLastAddress())) { + ++blocks_needed; + } + + /* Set the permissions on the block, if we need to. */ + if ((it->GetPermission() & KMemoryPermission_IpcLockChangeMask) != src_perm) { + const DisableMergeAttribute head_body_attr = (GetInteger(mapping_src_start) >= GetInteger(it->GetAddress())) ? DisableMergeAttribute_DisableHeadAndBody : DisableMergeAttribute_None; + const DisableMergeAttribute tail_attr = (cur_end == GetInteger(mapping_src_end)) ? DisableMergeAttribute_DisableTail : DisableMergeAttribute_None; + const KPageProperties properties = { src_perm, false, false, static_cast<DisableMergeAttribute>(head_body_attr | tail_attr) }; + R_TRY(this->Operate(page_list, cur_start, cur_size / PageSize, Null<KPhysicalAddress>, false, properties, OperationType_ChangePermissions, false)); + } + + /* Note that we mapped this part. */ + mapped_size += cur_size; + } + + /* If the block is at the end, we're done. */ + if (aligned_src_last <= GetInteger(it->GetLastAddress())) { + break; + } + + /* Advance. */ + ++it; + MESOSPHERE_ABORT_UNLESS(it != m_memory_block_manager.end()); + } + + if (out_blocks_needed != nullptr) { + MESOSPHERE_ASSERT(blocks_needed <= KMemoryBlockManagerUpdateAllocator::MaxBlocks); + *out_blocks_needed = blocks_needed; + } + + R_SUCCEED(); + } + + Result KPageTableBase::SetupForIpcServer(KProcessAddress *out_addr, size_t size, KProcessAddress src_addr, KMemoryPermission test_perm, KMemoryState dst_state, KPageTableBase &src_page_table, bool send) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + MESOSPHERE_ASSERT(src_page_table.IsLockedByCurrentThread()); + + /* Check that we can theoretically map. */ + const KProcessAddress region_start = m_region_starts[RegionType_Alias]; + const size_t region_size = m_region_ends[RegionType_Alias] - m_region_starts[RegionType_Alias]; + R_UNLESS(size < region_size, svc::ResultOutOfAddressSpace()); + + /* Get aligned source extents. */ + const KProcessAddress src_start = src_addr; + const KProcessAddress src_end = src_addr + size; + const KProcessAddress aligned_src_start = util::AlignDown(GetInteger(src_start), PageSize); + const KProcessAddress aligned_src_end = util::AlignUp(GetInteger(src_start) + size, PageSize); + const KProcessAddress mapping_src_start = util::AlignUp(GetInteger(src_start), PageSize); + const KProcessAddress mapping_src_end = util::AlignDown(GetInteger(src_start) + size, PageSize); + const size_t aligned_src_size = aligned_src_end - aligned_src_start; + const size_t mapping_src_size = (mapping_src_start < mapping_src_end) ? (mapping_src_end - mapping_src_start) : 0; + + /* Select a random address to map at. */ + KProcessAddress dst_addr = Null<KProcessAddress>; + for (s32 block_type = KPageTable::GetMaxBlockType(); block_type >= 0; block_type--) { + const size_t alignment = KPageTable::GetBlockSize(static_cast<KPageTable::BlockType>(block_type)); + const size_t offset = GetInteger(aligned_src_start) & (alignment - 1); + + dst_addr = this->FindFreeArea(region_start, region_size / PageSize, aligned_src_size / PageSize, alignment, offset, this->GetNumGuardPages()); + if (dst_addr != Null<KProcessAddress>) { + break; + } + } + R_UNLESS(dst_addr != Null<KProcessAddress>, svc::ResultOutOfAddressSpace()); + + /* Check that we can perform the operation we're about to perform. */ + MESOSPHERE_ASSERT(this->CanContain(dst_addr, aligned_src_size, dst_state)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Reserve space for any partial pages we allocate. */ + const size_t unmapped_size = aligned_src_size - mapping_src_size; + KScopedResourceReservation memory_reservation(m_resource_limit, ams::svc::LimitableResource_PhysicalMemoryMax, unmapped_size); + R_UNLESS(memory_reservation.Succeeded(), svc::ResultLimitReached()); + + /* Ensure that we manage page references correctly. */ + KPhysicalAddress start_partial_page = Null<KPhysicalAddress>; + KPhysicalAddress end_partial_page = Null<KPhysicalAddress>; + KProcessAddress cur_mapped_addr = dst_addr; + + /* If the partial pages are mapped, an extra reference will have been opened. Otherwise, they'll free on scope exit. */ + ON_SCOPE_EXIT { + if (start_partial_page != Null<KPhysicalAddress>) { + Kernel::GetMemoryManager().Close(start_partial_page, 1); + } + if (end_partial_page != Null<KPhysicalAddress>) { + Kernel::GetMemoryManager().Close(end_partial_page, 1); + } + }; + + ON_RESULT_FAILURE { + if (cur_mapped_addr != dst_addr) { + const KPageProperties unmap_properties = { KMemoryPermission_None, false, false, DisableMergeAttribute_None }; + MESOSPHERE_R_ABORT_UNLESS(this->Operate(updater.GetPageList(), dst_addr, (cur_mapped_addr - dst_addr) / PageSize, Null<KPhysicalAddress>, false, unmap_properties, OperationType_Unmap, true)); + } + }; + + /* Allocate the start page as needed. */ + if (aligned_src_start < mapping_src_start) { + start_partial_page = Kernel::GetMemoryManager().AllocateAndOpenContinuous(1, 1, m_allocate_option); + R_UNLESS(start_partial_page != Null<KPhysicalAddress>, svc::ResultOutOfMemory()); + } + + /* Allocate the end page as needed. */ + if (mapping_src_end < aligned_src_end && (aligned_src_start < mapping_src_end || aligned_src_start == mapping_src_start)) { + end_partial_page = Kernel::GetMemoryManager().AllocateAndOpenContinuous(1, 1, m_allocate_option); + R_UNLESS(end_partial_page != Null<KPhysicalAddress>, svc::ResultOutOfMemory()); + } + + /* Get the implementation. */ + auto &src_impl = src_page_table.GetImpl(); + + /* Get the fill value for partial pages. */ + const auto fill_val = m_ipc_fill_value; + + /* Begin traversal. */ + TraversalContext context; + TraversalEntry next_entry; + bool traverse_valid = src_impl.BeginTraversal(std::addressof(next_entry), std::addressof(context), aligned_src_start); + MESOSPHERE_ASSERT(traverse_valid); + MESOSPHERE_UNUSED(traverse_valid); + + /* Prepare tracking variables. */ + KPhysicalAddress cur_block_addr = next_entry.phys_addr; + size_t cur_block_size = next_entry.block_size - (GetInteger(cur_block_addr) & (next_entry.block_size - 1)); + size_t tot_block_size = cur_block_size; + + /* Map the start page, if we have one. */ + if (start_partial_page != Null<KPhysicalAddress>) { + /* Ensure the page holds correct data. */ + const KVirtualAddress start_partial_virt = GetHeapVirtualAddress(start_partial_page); + if (send) { + const size_t partial_offset = src_start - aligned_src_start; + size_t copy_size, clear_size; + if (src_end < mapping_src_start) { + copy_size = size; + clear_size = mapping_src_start - src_end; + } else { + copy_size = mapping_src_start - src_start; + clear_size = 0; + } + + std::memset(GetVoidPointer(start_partial_virt), fill_val, partial_offset); + std::memcpy(GetVoidPointer(start_partial_virt + partial_offset), GetVoidPointer(GetHeapVirtualAddress(cur_block_addr) + partial_offset), copy_size); + if (clear_size > 0) { + std::memset(GetVoidPointer(start_partial_virt + partial_offset + copy_size), fill_val, clear_size); + } + } else { + std::memset(GetVoidPointer(start_partial_virt), fill_val, PageSize); + } + + /* Map the page. */ + const KPageProperties start_map_properties = { test_perm, false, false, DisableMergeAttribute_DisableHead }; + R_TRY(this->Operate(updater.GetPageList(), cur_mapped_addr, 1, start_partial_page, true, start_map_properties, OperationType_Map, false)); + + /* Update tracking extents. */ + cur_mapped_addr += PageSize; + cur_block_addr += PageSize; + cur_block_size -= PageSize; + + /* If the block's size was one page, we may need to continue traversal. */ + if (cur_block_size == 0 && aligned_src_size > PageSize) { + traverse_valid = src_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + MESOSPHERE_ASSERT(traverse_valid); + + cur_block_addr = next_entry.phys_addr; + cur_block_size = next_entry.block_size; + tot_block_size += next_entry.block_size; + } + } + + /* Map the remaining pages. */ + while (aligned_src_start + tot_block_size < mapping_src_end) { + /* Continue the traversal. */ + traverse_valid = src_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + MESOSPHERE_ASSERT(traverse_valid); + + /* Process the block. */ + if (next_entry.phys_addr != cur_block_addr + cur_block_size) { + /* Map the block we've been processing so far. */ + const KPageProperties map_properties = { test_perm, false, false, (cur_mapped_addr == dst_addr) ? DisableMergeAttribute_DisableHead : DisableMergeAttribute_None }; + R_TRY(this->Operate(updater.GetPageList(), cur_mapped_addr, cur_block_size / PageSize, cur_block_addr, true, map_properties, OperationType_Map, false)); + + /* Update tracking extents. */ + cur_mapped_addr += cur_block_size; + cur_block_addr = next_entry.phys_addr; + cur_block_size = next_entry.block_size; + } else { + cur_block_size += next_entry.block_size; + } + tot_block_size += next_entry.block_size; + } + + /* Handle the last direct-mapped page. */ + if (const KProcessAddress mapped_block_end = aligned_src_start + tot_block_size - cur_block_size; mapped_block_end < mapping_src_end) { + const size_t last_block_size = mapping_src_end - mapped_block_end; + + /* Map the last block. */ + const KPageProperties map_properties = { test_perm, false, false, (cur_mapped_addr == dst_addr) ? DisableMergeAttribute_DisableHead : DisableMergeAttribute_None }; + R_TRY(this->Operate(updater.GetPageList(), cur_mapped_addr, last_block_size / PageSize, cur_block_addr, true, map_properties, OperationType_Map, false)); + + /* Update tracking extents. */ + cur_mapped_addr += last_block_size; + cur_block_addr += last_block_size; + if (mapped_block_end + cur_block_size < aligned_src_end && cur_block_size == last_block_size) { + traverse_valid = src_impl.ContinueTraversal(std::addressof(next_entry), std::addressof(context)); + MESOSPHERE_ASSERT(traverse_valid); + + cur_block_addr = next_entry.phys_addr; + } + } + + /* Map the end page, if we have one. */ + if (end_partial_page != Null<KPhysicalAddress>) { + /* Ensure the page holds correct data. */ + const KVirtualAddress end_partial_virt = GetHeapVirtualAddress(end_partial_page); + if (send) { + const size_t copy_size = src_end - mapping_src_end; + std::memcpy(GetVoidPointer(end_partial_virt), GetVoidPointer(GetHeapVirtualAddress(cur_block_addr)), copy_size); + std::memset(GetVoidPointer(end_partial_virt + copy_size), fill_val, PageSize - copy_size); + } else { + std::memset(GetVoidPointer(end_partial_virt), fill_val, PageSize); + } + + /* Map the page. */ + const KPageProperties map_properties = { test_perm, false, false, (cur_mapped_addr == dst_addr) ? DisableMergeAttribute_DisableHead : DisableMergeAttribute_None }; + R_TRY(this->Operate(updater.GetPageList(), cur_mapped_addr, 1, end_partial_page, true, map_properties, OperationType_Map, false)); + } + + /* Update memory blocks to reflect our changes */ + m_memory_block_manager.Update(std::addressof(allocator), dst_addr, aligned_src_size / PageSize, dst_state, test_perm, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_Normal, KMemoryBlockDisableMergeAttribute_None); + + /* Set the output address. */ + *out_addr = dst_addr + (src_start - aligned_src_start); + + /* We succeeded. */ + memory_reservation.Commit(); + R_SUCCEED(); + } + + Result KPageTableBase::SetupForIpc(KProcessAddress *out_dst_addr, size_t size, KProcessAddress src_addr, KPageTableBase &src_page_table, KMemoryPermission test_perm, KMemoryState dst_state, bool send) { + /* For convenience, alias this. */ + KPageTableBase &dst_page_table = *this; + + /* Acquire the table locks. */ + KScopedLightLockPair lk(src_page_table.m_general_lock, dst_page_table.m_general_lock); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(std::addressof(src_page_table)); + + /* Perform client setup. */ + size_t num_allocator_blocks; + R_TRY(src_page_table.SetupForIpcClient(updater.GetPageList(), std::addressof(num_allocator_blocks), src_addr, size, test_perm, dst_state)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), src_page_table.m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* Get the mapped extents. */ + const KProcessAddress src_map_start = util::AlignUp(GetInteger(src_addr), PageSize); + const KProcessAddress src_map_end = util::AlignDown(GetInteger(src_addr) + size, PageSize); + const size_t src_map_size = src_map_end - src_map_start; + + /* Ensure that we clean up appropriately if we fail after this. */ + const auto src_perm = static_cast<KMemoryPermission>((test_perm == KMemoryPermission_UserReadWrite) ? (KMemoryPermission_KernelReadWrite | KMemoryPermission_NotMapped) : KMemoryPermission_UserRead); + ON_RESULT_FAILURE { + if (src_map_end > src_map_start) { + src_page_table.CleanupForIpcClientOnServerSetupFailure(updater.GetPageList(), src_map_start, src_map_size, src_perm); + } + }; + + /* Perform server setup. */ + R_TRY(dst_page_table.SetupForIpcServer(out_dst_addr, size, src_addr, test_perm, dst_state, src_page_table, send)); + + /* If anything was mapped, ipc-lock the pages. */ + if (src_map_start < src_map_end) { + /* Get the source permission. */ + src_page_table.m_memory_block_manager.UpdateLock(std::addressof(allocator), src_map_start, (src_map_end - src_map_start) / PageSize, &KMemoryBlock::LockForIpc, src_perm); + } + + R_SUCCEED(); + } + + Result KPageTableBase::CleanupForIpcServer(KProcessAddress address, size_t size, KMemoryState dst_state) { + /* Validate the address. */ + R_UNLESS(this->Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Validate the memory state. */ + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size, KMemoryState_All, dst_state, KMemoryPermission_UserRead, KMemoryPermission_UserRead, KMemoryAttribute_All, KMemoryAttribute_None)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Get aligned extents. */ + const KProcessAddress aligned_start = util::AlignDown(GetInteger(address), PageSize); + const KProcessAddress aligned_end = util::AlignUp(GetInteger(address) + size, PageSize); + const size_t aligned_size = aligned_end - aligned_start; + const size_t aligned_num_pages = aligned_size / PageSize; + + /* Unmap the pages. */ + const KPageProperties unmap_properties = { KMemoryPermission_None, false, false, DisableMergeAttribute_None }; + R_TRY(this->Operate(updater.GetPageList(), aligned_start, aligned_num_pages, Null<KPhysicalAddress>, false, unmap_properties, OperationType_Unmap, false)); + + /* Update memory blocks. */ + m_memory_block_manager.Update(std::addressof(allocator), aligned_start, aligned_num_pages, KMemoryState_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_None, KMemoryBlockDisableMergeAttribute_Normal); + + /* Release from the resource limit as relevant. */ + const KProcessAddress mapping_start = util::AlignUp(GetInteger(address), PageSize); + const KProcessAddress mapping_end = util::AlignDown(GetInteger(address) + size, PageSize); + const size_t mapping_size = (mapping_start < mapping_end) ? mapping_end - mapping_start : 0; + m_resource_limit->Release(ams::svc::LimitableResource_PhysicalMemoryMax, aligned_size - mapping_size); + + R_SUCCEED(); + } + + Result KPageTableBase::CleanupForIpcClient(KProcessAddress address, size_t size, KMemoryState dst_state) { + /* Validate the address. */ + R_UNLESS(this->Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Get aligned source extents. */ + const KProcessAddress mapping_start = util::AlignUp(GetInteger(address), PageSize); + const KProcessAddress mapping_end = util::AlignDown(GetInteger(address) + size, PageSize); + const KProcessAddress mapping_last = mapping_end - 1; + const size_t mapping_size = (mapping_start < mapping_end) ? (mapping_end - mapping_start) : 0; + + /* If nothing was mapped, we're actually done immediately. */ + R_SUCCEED_IF(mapping_size == 0); + + /* Get the test state and attribute mask. */ + u32 test_state; + u32 test_attr_mask; + switch (dst_state) { + case KMemoryState_Ipc: + test_state = KMemoryState_FlagCanUseIpc; + test_attr_mask = KMemoryAttribute_Uncached | KMemoryAttribute_DeviceShared | KMemoryAttribute_Locked; + break; + case KMemoryState_NonSecureIpc: + test_state = KMemoryState_FlagCanUseNonSecureIpc; + test_attr_mask = KMemoryAttribute_Uncached | KMemoryAttribute_Locked; + break; + case KMemoryState_NonDeviceIpc: + test_state = KMemoryState_FlagCanUseNonDeviceIpc; + test_attr_mask = KMemoryAttribute_Uncached | KMemoryAttribute_Locked; + break; + default: + R_THROW(svc::ResultInvalidCombination()); + } + + /* Lock the table. */ + /* NOTE: Nintendo does this *after* creating the updater below, but this does not follow convention elsewhere in KPageTableBase. */ + KScopedLightLock lk(m_general_lock); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Ensure that on failure, we roll back appropriately. */ + size_t mapped_size = 0; + ON_RESULT_FAILURE { + if (mapped_size > 0) { + /* Determine where the mapping ends. */ + const auto mapped_end = GetInteger(mapping_start) + mapped_size; + const auto mapped_last = mapped_end - 1; + + /* Get current and next iterators. */ + KMemoryBlockManager::const_iterator cur_it = m_memory_block_manager.FindIterator(mapping_start); + KMemoryBlockManager::const_iterator next_it = cur_it; + ++next_it; + + /* Create tracking variables. */ + KProcessAddress cur_address = cur_it->GetAddress(); + size_t cur_size = cur_it->GetSize(); + bool cur_perm_eq = cur_it->GetPermission() == cur_it->GetOriginalPermission(); + bool cur_needs_set_perm = !cur_perm_eq && cur_it->GetIpcLockCount() == 1; + bool first = cur_it->GetIpcDisableMergeCount() == 1 && (cur_it->GetDisableMergeAttribute() & KMemoryBlockDisableMergeAttribute_Locked) == 0; + + while ((GetInteger(cur_address) + cur_size - 1) < mapped_last) { + /* Check that we have a next block. */ + MESOSPHERE_ABORT_UNLESS(next_it != m_memory_block_manager.end()); + + /* Check if we can consolidate the next block's permission set with the current one. */ + const bool next_perm_eq = next_it->GetPermission() == next_it->GetOriginalPermission(); + const bool next_needs_set_perm = !next_perm_eq && next_it->GetIpcLockCount() == 1; + if (cur_perm_eq == next_perm_eq && cur_needs_set_perm == next_needs_set_perm && cur_it->GetOriginalPermission() == next_it->GetOriginalPermission()) { + /* We can consolidate the reprotection for the current and next block into a single call. */ + cur_size += next_it->GetSize(); + } else { + /* We have to operate on the current block. */ + if ((cur_needs_set_perm || first) && !cur_perm_eq) { + const KPageProperties properties = { cur_it->GetPermission(), false, false, first ? DisableMergeAttribute_EnableAndMergeHeadBodyTail : DisableMergeAttribute_None }; + MESOSPHERE_R_ABORT_UNLESS(this->Operate(updater.GetPageList(), cur_address, cur_size / PageSize, Null<KPhysicalAddress>, false, properties, OperationType_ChangePermissions, true)); + } + + /* Advance. */ + cur_address = next_it->GetAddress(); + cur_size = next_it->GetSize(); + first = false; + } + + /* Advance. */ + cur_perm_eq = next_perm_eq; + cur_needs_set_perm = next_needs_set_perm; + + cur_it = next_it++; + } + + /* Process the last block. */ + if ((first || cur_needs_set_perm) && !cur_perm_eq) { + const KPageProperties properties = { cur_it->GetPermission(), false, false, first ? DisableMergeAttribute_EnableAndMergeHeadBodyTail : DisableMergeAttribute_None }; + MESOSPHERE_R_ABORT_UNLESS(this->Operate(updater.GetPageList(), cur_address, cur_size / PageSize, Null<KPhysicalAddress>, false, properties, OperationType_ChangePermissions, true)); + } + } + }; + + /* Iterate, reprotecting as needed. */ + { + /* Get current and next iterators. */ + KMemoryBlockManager::const_iterator cur_it = m_memory_block_manager.FindIterator(mapping_start); + KMemoryBlockManager::const_iterator next_it = cur_it; + ++next_it; + + /* Validate the current block. */ + MESOSPHERE_R_ABORT_UNLESS(this->CheckMemoryState(cur_it, test_state, test_state, KMemoryPermission_None, KMemoryPermission_None, test_attr_mask | KMemoryAttribute_IpcLocked, KMemoryAttribute_IpcLocked)); + + /* Create tracking variables. */ + KProcessAddress cur_address = cur_it->GetAddress(); + size_t cur_size = cur_it->GetSize(); + bool cur_perm_eq = cur_it->GetPermission() == cur_it->GetOriginalPermission(); + bool cur_needs_set_perm = !cur_perm_eq && cur_it->GetIpcLockCount() == 1; + bool first = cur_it->GetIpcDisableMergeCount() == 1 && (cur_it->GetDisableMergeAttribute() & KMemoryBlockDisableMergeAttribute_Locked) == 0; + + while ((cur_address + cur_size - 1) < mapping_last) { + /* Check that we have a next block. */ + MESOSPHERE_ABORT_UNLESS(next_it != m_memory_block_manager.end()); + + /* Validate the next block. */ + MESOSPHERE_R_ABORT_UNLESS(this->CheckMemoryState(next_it, test_state, test_state, KMemoryPermission_None, KMemoryPermission_None, test_attr_mask | KMemoryAttribute_IpcLocked, KMemoryAttribute_IpcLocked)); + + /* Check if we can consolidate the next block's permission set with the current one. */ + const bool next_perm_eq = next_it->GetPermission() == next_it->GetOriginalPermission(); + const bool next_needs_set_perm = !next_perm_eq && next_it->GetIpcLockCount() == 1; + if (cur_perm_eq == next_perm_eq && cur_needs_set_perm == next_needs_set_perm && cur_it->GetOriginalPermission() == next_it->GetOriginalPermission()) { + /* We can consolidate the reprotection for the current and next block into a single call. */ + cur_size += next_it->GetSize(); + } else { + /* We have to operate on the current block. */ + if ((cur_needs_set_perm || first) && !cur_perm_eq) { + const KPageProperties properties = { cur_needs_set_perm ? cur_it->GetOriginalPermission() : cur_it->GetPermission(), false, false, first ? DisableMergeAttribute_EnableHeadAndBody : DisableMergeAttribute_None }; + R_TRY(this->Operate(updater.GetPageList(), cur_address, cur_size / PageSize, Null<KPhysicalAddress>, false, properties, OperationType_ChangePermissions, false)); + } + + /* Mark that we mapped the block. */ + mapped_size += cur_size; + + /* Advance. */ + cur_address = next_it->GetAddress(); + cur_size = next_it->GetSize(); + first = false; + } + + /* Advance. */ + cur_perm_eq = next_perm_eq; + cur_needs_set_perm = next_needs_set_perm; + + cur_it = next_it++; + } + + /* Process the last block. */ + const auto lock_count = cur_it->GetIpcLockCount() + (next_it != m_memory_block_manager.end() ? (next_it->GetIpcDisableMergeCount() - next_it->GetIpcLockCount()) : 0); + if ((first || cur_needs_set_perm || (lock_count == 1)) && !cur_perm_eq) { + const DisableMergeAttribute head_body_attr = first ? DisableMergeAttribute_EnableHeadAndBody : DisableMergeAttribute_None; + const DisableMergeAttribute tail_attr = lock_count == 1 ? DisableMergeAttribute_EnableTail : DisableMergeAttribute_None; + const KPageProperties properties = { cur_needs_set_perm ? cur_it->GetOriginalPermission() : cur_it->GetPermission(), false, false, static_cast<DisableMergeAttribute>(head_body_attr | tail_attr) }; + R_TRY(this->Operate(updater.GetPageList(), cur_address, cur_size / PageSize, Null<KPhysicalAddress>, false, properties, OperationType_ChangePermissions, false)); + } + } + + /* Create an update allocator. */ + /* NOTE: Guaranteed zero blocks needed here. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, 0); + R_TRY(allocator_result); + + /* Unlock the pages. */ + m_memory_block_manager.UpdateLock(std::addressof(allocator), mapping_start, mapping_size / PageSize, &KMemoryBlock::UnlockForIpc, KMemoryPermission_None); + + R_SUCCEED(); + } + + void KPageTableBase::CleanupForIpcClientOnServerSetupFailure(PageLinkedList *page_list, KProcessAddress address, size_t size, KMemoryPermission prot_perm) { + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + MESOSPHERE_ASSERT(util::IsAligned(GetInteger(address), PageSize)); + MESOSPHERE_ASSERT(util::IsAligned(size, PageSize)); + + /* Get the mapped extents. */ + const KProcessAddress src_map_start = address; + const KProcessAddress src_map_end = address + size; + const KProcessAddress src_map_last = src_map_end - 1; + + /* This function is only invoked when there's something to do. */ + MESOSPHERE_ASSERT(src_map_end > src_map_start); + + /* Iterate over blocks, fixing permissions. */ + KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(address); + while (true) { + const auto cur_start = it->GetAddress() >= GetInteger(src_map_start) ? it->GetAddress() : GetInteger(src_map_start); + const auto cur_end = src_map_last <= it->GetLastAddress() ? src_map_end : it->GetEndAddress(); + + /* If we can, fix the protections on the block. */ + if ((it->GetIpcLockCount() == 0 && (it->GetPermission() & KMemoryPermission_IpcLockChangeMask) != prot_perm) || + (it->GetIpcLockCount() != 0 && (it->GetOriginalPermission() & KMemoryPermission_IpcLockChangeMask) != prot_perm)) + { + /* Check if we actually need to fix the protections on the block. */ + if (cur_end == src_map_end || it->GetAddress() <= GetInteger(src_map_start) || (it->GetPermission() & KMemoryPermission_IpcLockChangeMask) != prot_perm) { + const bool start_nc = (it->GetAddress() == GetInteger(src_map_start)) ? ((it->GetDisableMergeAttribute() & (KMemoryBlockDisableMergeAttribute_Locked | KMemoryBlockDisableMergeAttribute_IpcLeft)) == 0) : it->GetAddress() <= GetInteger(src_map_start); + + const DisableMergeAttribute head_body_attr = start_nc ? DisableMergeAttribute_EnableHeadAndBody : DisableMergeAttribute_None; + DisableMergeAttribute tail_attr; + if (cur_end == src_map_end && it->GetEndAddress() == src_map_end) { + auto next_it = it; + ++next_it; + + const auto lock_count = it->GetIpcLockCount() + (next_it != m_memory_block_manager.end() ? (next_it->GetIpcDisableMergeCount() - next_it->GetIpcLockCount()) : 0); + tail_attr = lock_count == 0 ? DisableMergeAttribute_EnableTail : DisableMergeAttribute_None; + } else { + tail_attr = DisableMergeAttribute_None; + } + + const KPageProperties properties = { it->GetPermission(), false, false, static_cast<DisableMergeAttribute>(head_body_attr | tail_attr) }; + MESOSPHERE_R_ABORT_UNLESS(this->Operate(page_list, cur_start, (cur_end - cur_start) / PageSize, Null<KPhysicalAddress>, false, properties, OperationType_ChangePermissions, true)); + } + } + + /* If we're past the end of the region, we're done. */ + if (src_map_last <= it->GetLastAddress()) { + break; + } + + /* Advance. */ + ++it; + MESOSPHERE_ABORT_UNLESS(it != m_memory_block_manager.end()); + } + } + + #pragma GCC pop_options + + Result KPageTableBase::MapPhysicalMemory(KProcessAddress address, size_t size) { + /* Lock the physical memory lock. */ + KScopedLightLock phys_lk(m_map_physical_memory_lock); + + /* Calculate the last address for convenience. */ + const KProcessAddress last_address = address + size - 1; + + /* Define iteration variables. */ + KProcessAddress cur_address; + size_t mapped_size; + + /* The entire mapping process can be retried. */ + while (true) { + /* Check if the memory is already mapped. */ + { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Iterate over the memory. */ + cur_address = address; + mapped_size = 0; + + auto it = m_memory_block_manager.FindIterator(cur_address); + while (true) { + /* Check that the iterator is valid. */ + MESOSPHERE_ASSERT(it != m_memory_block_manager.end()); + + /* Check if we're done. */ + if (last_address <= it->GetLastAddress()) { + if (it->GetState() != KMemoryState_Free) { + mapped_size += (last_address + 1 - cur_address); + } + break; + } + + /* Track the memory if it's mapped. */ + if (it->GetState() != KMemoryState_Free) { + mapped_size += it->GetEndAddress() - cur_address; + } + + /* Advance. */ + cur_address = it->GetEndAddress(); + ++it; + } + + /* If the size mapped is the size requested, we've nothing to do. */ + R_SUCCEED_IF(size == mapped_size); + } + + /* Allocate and map the memory. */ + { + /* Reserve the memory from the process resource limit. */ + KScopedResourceReservation memory_reservation(m_resource_limit, ams::svc::LimitableResource_PhysicalMemoryMax, size - mapped_size); + R_UNLESS(memory_reservation.Succeeded(), svc::ResultLimitReached()); + + /* Allocate pages for the new memory. */ + KPageGroup pg(m_block_info_manager); + R_TRY(Kernel::GetMemoryManager().AllocateForProcess(std::addressof(pg), (size - mapped_size) / PageSize, m_allocate_option, GetCurrentProcess().GetId(), m_heap_fill_value)); + + /* If we fail in the next bit (or retry), we need to cleanup the pages. */ + auto pg_guard = SCOPE_GUARD { + pg.OpenFirst(); + pg.Close(); + }; + + /* Map the memory. */ + { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + size_t num_allocator_blocks = 0; + + /* Verify that nobody has mapped memory since we first checked. */ + { + /* Iterate over the memory. */ + size_t checked_mapped_size = 0; + cur_address = address; + + auto it = m_memory_block_manager.FindIterator(cur_address); + while (true) { + /* Check that the iterator is valid. */ + MESOSPHERE_ASSERT(it != m_memory_block_manager.end()); + + const bool is_free = it->GetState() == KMemoryState_Free; + if (is_free) { + if (it->GetAddress() < GetInteger(address)) { + ++num_allocator_blocks; + } + if (last_address < it->GetLastAddress()) { + ++num_allocator_blocks; + } + } + + /* Check if we're done. */ + if (last_address <= it->GetLastAddress()) { + if (!is_free) { + checked_mapped_size += (last_address + 1 - cur_address); + } + break; + } + + /* Track the memory if it's mapped. */ + if (!is_free) { + checked_mapped_size += it->GetEndAddress() - cur_address; + } + + /* Advance. */ + cur_address = it->GetEndAddress(); + ++it; + } + + /* If the size now isn't what it was before, somebody mapped or unmapped concurrently. */ + /* If this happened, retry. */ + if (mapped_size != checked_mapped_size) { + continue; + } + } + + /* Create an update allocator. */ + MESOSPHERE_ASSERT(num_allocator_blocks <= KMemoryBlockManagerUpdateAllocator::MaxBlocks); + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Prepare to iterate over the memory. */ + auto pg_it = pg.begin(); + KPhysicalAddress pg_phys_addr = pg_it->GetAddress(); + size_t pg_pages = pg_it->GetNumPages(); + + /* Reset the current tracking address, and make sure we clean up on failure. */ + pg_guard.Cancel(); + cur_address = address; + ON_RESULT_FAILURE { + if (cur_address > address) { + const KProcessAddress last_unmap_address = cur_address - 1; + + /* Iterate, unmapping the pages. */ + cur_address = address; + + auto it = m_memory_block_manager.FindIterator(cur_address); + while (true) { + /* Check that the iterator is valid. */ + MESOSPHERE_ASSERT(it != m_memory_block_manager.end()); + + /* If the memory state is free, we mapped it and need to unmap it. */ + if (it->GetState() == KMemoryState_Free) { + /* Determine the range to unmap. */ + const KPageProperties unmap_properties = { KMemoryPermission_None, false, false, DisableMergeAttribute_None }; + const size_t cur_pages = std::min(it->GetEndAddress() - cur_address, last_unmap_address + 1 - cur_address) / PageSize; + + /* Unmap. */ + MESOSPHERE_R_ABORT_UNLESS(this->Operate(updater.GetPageList(), cur_address, cur_pages, Null<KPhysicalAddress>, false, unmap_properties, OperationType_Unmap, true)); + } + + /* Check if we're done. */ + if (last_unmap_address <= it->GetLastAddress()) { + break; + } + + /* Advance. */ + cur_address = it->GetEndAddress(); + ++it; + } + } + + /* Release any remaining unmapped memory. */ + Kernel::GetMemoryManager().OpenFirst(pg_phys_addr, pg_pages); + Kernel::GetMemoryManager().Close(pg_phys_addr, pg_pages); + for (++pg_it; pg_it != pg.end(); ++pg_it) { + Kernel::GetMemoryManager().OpenFirst(pg_it->GetAddress(), pg_it->GetNumPages()); + Kernel::GetMemoryManager().Close(pg_it->GetAddress(), pg_it->GetNumPages()); + } + }; + + auto it = m_memory_block_manager.FindIterator(cur_address); + while (true) { + /* Check that the iterator is valid. */ + MESOSPHERE_ASSERT(it != m_memory_block_manager.end()); + + /* If it's unmapped, we need to map it. */ + if (it->GetState() == KMemoryState_Free) { + /* Determine the range to map. */ + const KPageProperties map_properties = { KMemoryPermission_UserReadWrite, false, false, cur_address == this->GetAliasRegionStart() ? DisableMergeAttribute_DisableHead : DisableMergeAttribute_None }; + size_t map_pages = std::min(it->GetEndAddress() - cur_address, last_address + 1 - cur_address) / PageSize; + + /* While we have pages to map, map them. */ + { + /* Create a page group for the current mapping range. */ + KPageGroup cur_pg(m_block_info_manager); + { + ON_RESULT_FAILURE { + cur_pg.OpenFirst(); + cur_pg.Close(); + }; + + size_t remain_pages = map_pages; + while (remain_pages > 0) { + /* Check if we're at the end of the physical block. */ + if (pg_pages == 0) { + /* Ensure there are more pages to map. */ + MESOSPHERE_ASSERT(pg_it != pg.end()); + + /* Advance our physical block. */ + ++pg_it; + pg_phys_addr = pg_it->GetAddress(); + pg_pages = pg_it->GetNumPages(); + } + + /* Add whatever we can to the current block. */ + const size_t cur_pages = std::min(pg_pages, remain_pages); + R_TRY(cur_pg.AddBlock(pg_phys_addr + ((pg_pages - cur_pages) * PageSize), cur_pages)); + + /* Advance. */ + remain_pages -= cur_pages; + pg_pages -= cur_pages; + } + } + + /* Map the pages. */ + R_TRY(this->Operate(updater.GetPageList(), cur_address, map_pages, cur_pg, map_properties, OperationType_MapFirstGroup, false)); + } + } + + /* Check if we're done. */ + if (last_address <= it->GetLastAddress()) { + break; + } + + /* Advance. */ + cur_address = it->GetEndAddress(); + ++it; + } + + /* We succeeded, so commit the memory reservation. */ + memory_reservation.Commit(); + + /* Increase our tracked mapped size. */ + m_mapped_physical_memory_size += (size - mapped_size); + + /* Update the relevant memory blocks. */ + m_memory_block_manager.UpdateIfMatch(std::addressof(allocator), address, size / PageSize, + KMemoryState_Free, KMemoryPermission_None, KMemoryAttribute_None, + KMemoryState_Normal, KMemoryPermission_UserReadWrite, KMemoryAttribute_None, + address == this->GetAliasRegionStart() ? KMemoryBlockDisableMergeAttribute_Normal : KMemoryBlockDisableMergeAttribute_None, + KMemoryBlockDisableMergeAttribute_None); + + R_SUCCEED(); + } + } + } + } + + Result KPageTableBase::UnmapPhysicalMemory(KProcessAddress address, size_t size) { + /* Lock the physical memory lock. */ + KScopedLightLock phys_lk(m_map_physical_memory_lock); + + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Calculate the last address for convenience. */ + const KProcessAddress last_address = address + size - 1; + + /* Define iteration variables. */ + KProcessAddress map_start_address = Null<KProcessAddress>; + KProcessAddress map_last_address = Null<KProcessAddress>; + + KProcessAddress cur_address; + size_t mapped_size; + size_t num_allocator_blocks = 0; + + /* Check if the memory is mapped. */ + { + /* Iterate over the memory. */ + cur_address = address; + mapped_size = 0; + + auto it = m_memory_block_manager.FindIterator(cur_address); + while (true) { + /* Check that the iterator is valid. */ + MESOSPHERE_ASSERT(it != m_memory_block_manager.end()); + + /* Verify the memory's state. */ + const bool is_normal = it->GetState() == KMemoryState_Normal && it->GetAttribute() == 0; + const bool is_free = it->GetState() == KMemoryState_Free; + R_UNLESS(is_normal || is_free, svc::ResultInvalidCurrentMemory()); + + if (is_normal) { + R_UNLESS(it->GetAttribute() == KMemoryAttribute_None, svc::ResultInvalidCurrentMemory()); + + if (map_start_address == Null<KProcessAddress>) { + map_start_address = cur_address; + } + map_last_address = (last_address >= it->GetLastAddress()) ? it->GetLastAddress() : last_address; + + if (it->GetAddress() < GetInteger(address)) { + ++num_allocator_blocks; + } + if (last_address < it->GetLastAddress()) { + ++num_allocator_blocks; + } + + mapped_size += (map_last_address + 1 - cur_address); + } + + /* Check if we're done. */ + if (last_address <= it->GetLastAddress()) { + break; + } + + /* Advance. */ + cur_address = it->GetEndAddress(); + ++it; + } + + /* If there's nothing mapped, we've nothing to do. */ + R_SUCCEED_IF(mapped_size == 0); + } + + /* Create an update allocator. */ + MESOSPHERE_ASSERT(num_allocator_blocks <= KMemoryBlockManagerUpdateAllocator::MaxBlocks); + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Separate the mapping. */ + const KPageProperties sep_properties = { KMemoryPermission_None, false, false, DisableMergeAttribute_None }; + R_TRY(this->Operate(updater.GetPageList(), map_start_address, (map_last_address + 1 - map_start_address) / PageSize, Null<KPhysicalAddress>, false, sep_properties, OperationType_Separate, false)); + + /* Reset the current tracking address, and make sure we clean up on failure. */ + cur_address = address; + + /* Iterate over the memory, unmapping as we go. */ + auto it = m_memory_block_manager.FindIterator(cur_address); + + const auto clear_merge_attr = (it->GetState() == KMemoryState_Normal && it->GetAddress() == this->GetAliasRegionStart() && it->GetAddress() == address) ? KMemoryBlockDisableMergeAttribute_Normal : KMemoryBlockDisableMergeAttribute_None; + + while (true) { + /* Check that the iterator is valid. */ + MESOSPHERE_ASSERT(it != m_memory_block_manager.end()); + + /* If the memory state is normal, we need to unmap it. */ + if (it->GetState() == KMemoryState_Normal) { + /* Determine the range to unmap. */ + const KPageProperties unmap_properties = { KMemoryPermission_None, false, false, DisableMergeAttribute_None }; + const size_t cur_pages = std::min(it->GetEndAddress() - cur_address, last_address + 1 - cur_address) / PageSize; + + /* Unmap. */ + MESOSPHERE_R_ABORT_UNLESS(this->Operate(updater.GetPageList(), cur_address, cur_pages, Null<KPhysicalAddress>, false, unmap_properties, OperationType_Unmap, false)); + } + + /* Check if we're done. */ + if (last_address <= it->GetLastAddress()) { + break; + } + + /* Advance. */ + cur_address = it->GetEndAddress(); + ++it; + } + + /* Release the memory resource. */ + m_mapped_physical_memory_size -= mapped_size; + m_resource_limit->Release(ams::svc::LimitableResource_PhysicalMemoryMax, mapped_size); + + /* Update memory blocks. */ + m_memory_block_manager.Update(std::addressof(allocator), address, size / PageSize, KMemoryState_Free, KMemoryPermission_None, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_None, clear_merge_attr); + + /* We succeeded. */ + R_SUCCEED(); + } + + Result KPageTableBase::MapPhysicalMemoryUnsafe(KProcessAddress address, size_t size) { + /* Try to reserve the unsafe memory. */ + R_UNLESS(Kernel::GetUnsafeMemory().TryReserve(size), svc::ResultLimitReached()); + + /* Ensure we release our reservation on failure. */ + ON_RESULT_FAILURE { Kernel::GetUnsafeMemory().Release(size); }; + + /* Create a page group for the new memory. */ + KPageGroup pg(m_block_info_manager); + + /* Allocate the new memory. */ + const size_t num_pages = size / PageSize; + R_TRY(Kernel::GetMemoryManager().AllocateAndOpen(std::addressof(pg), num_pages, 1, KMemoryManager::EncodeOption(KMemoryManager::Pool_Unsafe, KMemoryManager::Direction_FromFront))); + + /* Close the page group when we're done with it. */ + ON_SCOPE_EXIT { pg.Close(); }; + + /* Clear the new memory. */ + for (const auto &block : pg) { + std::memset(GetVoidPointer(GetHeapVirtualAddress(block.GetAddress())), m_heap_fill_value, block.GetSize()); + } + + /* Map the new memory. */ + { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check the memory state. */ + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size, KMemoryState_All, KMemoryState_Free, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryAttribute_None)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Map the pages. */ + const KPageProperties map_properties = { KMemoryPermission_UserReadWrite, false, false, DisableMergeAttribute_DisableHead }; + R_TRY(this->Operate(updater.GetPageList(), address, num_pages, pg, map_properties, OperationType_MapGroup, false)); + + /* Apply the memory block update. */ + m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState_Normal, KMemoryPermission_UserReadWrite, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_Normal, KMemoryBlockDisableMergeAttribute_None); + + /* Update our mapped unsafe size. */ + m_mapped_unsafe_physical_memory += size; + + /* We succeeded. */ + R_SUCCEED(); + } + } + + Result KPageTableBase::UnmapPhysicalMemoryUnsafe(KProcessAddress address, size_t size) { + /* Lock the table. */ + KScopedLightLock lk(m_general_lock); + + /* Check whether we can unmap this much unsafe physical memory. */ + R_UNLESS(size <= m_mapped_unsafe_physical_memory, svc::ResultInvalidCurrentMemory()); + + /* Check the memory state. */ + size_t num_allocator_blocks; + R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size, KMemoryState_All, KMemoryState_Normal, KMemoryPermission_All, KMemoryPermission_UserReadWrite, KMemoryAttribute_All, KMemoryAttribute_None)); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Unmap the memory. */ + const size_t num_pages = size / PageSize; + const KPageProperties unmap_properties = { KMemoryPermission_None, false, false, DisableMergeAttribute_None }; + R_TRY(this->Operate(updater.GetPageList(), address, num_pages, Null<KPhysicalAddress>, false, unmap_properties, OperationType_Unmap, false)); + + /* Apply the memory block update. */ + m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState_Free, KMemoryPermission_None, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_None, KMemoryBlockDisableMergeAttribute_Normal); + + /* Release the unsafe memory from the limit. */ + Kernel::GetUnsafeMemory().Release(size); + + /* Update our mapped unsafe size. */ + m_mapped_unsafe_physical_memory -= size; + + R_SUCCEED(); + } + + Result KPageTableBase::UnmapProcessMemory(KProcessAddress dst_address, size_t size, KPageTableBase &src_page_table, KProcessAddress src_address) { + /* We need to lock both this table, and the current process's table, so set up an alias. */ + KPageTableBase &dst_page_table = *this; + + /* Acquire the table locks. */ + KScopedLightLockPair lk(src_page_table.m_general_lock, dst_page_table.m_general_lock); + + /* Check that the memory is mapped in the destination process. */ + size_t num_allocator_blocks; + R_TRY(dst_page_table.CheckMemoryState(std::addressof(num_allocator_blocks), dst_address, size, KMemoryState_All, KMemoryState_SharedCode, KMemoryPermission_UserReadWrite, KMemoryPermission_UserReadWrite, KMemoryAttribute_All, KMemoryAttribute_None)); + + /* Check that the memory is mapped in the source process. */ + R_TRY(src_page_table.CheckMemoryState(src_address, size, KMemoryState_FlagCanMapProcess, KMemoryState_FlagCanMapProcess, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_All, KMemoryAttribute_None)); + + /* Validate that the memory ranges are compatible. */ + { + /* Define a helper type. */ + struct ContiguousRangeInfo { + public: + KPageTableBase &m_pt; + TraversalContext m_context; + TraversalEntry m_entry; + KPhysicalAddress m_phys_addr; + size_t m_cur_size; + size_t m_remaining_size; + public: + ContiguousRangeInfo(KPageTableBase &pt, KProcessAddress address, size_t size) : m_pt(pt), m_remaining_size(size) { + /* Begin a traversal. */ + MESOSPHERE_ABORT_UNLESS(m_pt.GetImpl().BeginTraversal(std::addressof(m_entry), std::addressof(m_context), address)); + + /* Setup tracking fields. */ + m_phys_addr = m_entry.phys_addr; + m_cur_size = std::min<size_t>(m_remaining_size, m_entry.block_size - (GetInteger(m_phys_addr) & (m_entry.block_size - 1))); + + /* Consume the whole contiguous block. */ + this->DetermineContiguousBlockExtents(); + } + + void ContinueTraversal() { + /* Update our remaining size. */ + m_remaining_size = m_remaining_size - m_cur_size; + + /* Update our tracking fields. */ + if (m_remaining_size > 0) { + m_phys_addr = m_entry.phys_addr; + m_cur_size = std::min<size_t>(m_remaining_size, m_entry.block_size); + + /* Consume the whole contiguous block. */ + this->DetermineContiguousBlockExtents(); + } + } + private: + void DetermineContiguousBlockExtents() { + /* Continue traversing until we're not contiguous, or we have enough. */ + while (m_cur_size < m_remaining_size) { + MESOSPHERE_ABORT_UNLESS(m_pt.GetImpl().ContinueTraversal(std::addressof(m_entry), std::addressof(m_context))); + + /* If we're not contiguous, we're done. */ + if (m_entry.phys_addr != m_phys_addr + m_cur_size) { + break; + } + + /* Update our current size. */ + m_cur_size = std::min(m_remaining_size, m_cur_size + m_entry.block_size); + } + } + }; + + /* Create ranges for both tables. */ + ContiguousRangeInfo src_range(src_page_table, src_address, size); + ContiguousRangeInfo dst_range(dst_page_table, dst_address, size); + + /* Validate the ranges. */ + while (src_range.m_remaining_size > 0 && dst_range.m_remaining_size > 0) { + R_UNLESS(src_range.m_phys_addr == dst_range.m_phys_addr, svc::ResultInvalidMemoryRegion()); + R_UNLESS(src_range.m_cur_size == dst_range.m_cur_size, svc::ResultInvalidMemoryRegion()); + + src_range.ContinueTraversal(); + dst_range.ContinueTraversal(); + } + } + + /* We no longer need to hold our lock on the source page table. */ + lk.TryUnlockHalf(src_page_table.m_general_lock); + + /* Create an update allocator. */ + Result allocator_result; + KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), m_memory_block_slab_manager, num_allocator_blocks); + R_TRY(allocator_result); + + /* We're going to perform an update, so create a helper. */ + KScopedPageTableUpdater updater(this); + + /* Unmap the memory. */ + const size_t num_pages = size / PageSize; + const KPageProperties unmap_properties = { KMemoryPermission_None, false, false, DisableMergeAttribute_None }; + R_TRY(this->Operate(updater.GetPageList(), dst_address, num_pages, Null<KPhysicalAddress>, false, unmap_properties, OperationType_Unmap, false)); + + /* Apply the memory block update. */ + m_memory_block_manager.Update(std::addressof(allocator), dst_address, num_pages, KMemoryState_Free, KMemoryPermission_None, KMemoryAttribute_None, KMemoryBlockDisableMergeAttribute_None, KMemoryBlockDisableMergeAttribute_Normal); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_port.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_port.cpp new file mode 100644 index 00000000..0e207f45 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_port.cpp @@ -0,0 +1,74 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + void KPort::Initialize(s32 max_sessions, bool is_light, uintptr_t name) { + /* Open a new reference count to the initialized port. */ + this->Open(); + + /* Create and initialize our server/client pair. */ + KAutoObject::Create<KServerPort>(std::addressof(m_server)); + KAutoObject::Create<KClientPort>(std::addressof(m_client)); + m_server.Initialize(this); + m_client.Initialize(this, max_sessions); + + /* Set our member variables. */ + m_is_light = is_light; + m_name = name; + m_state = State::Normal; + } + + void KPort::OnClientClosed() { + MESOSPHERE_ASSERT_THIS(); + + KScopedSchedulerLock sl; + + if (m_state == State::Normal) { + m_state = State::ClientClosed; + } + } + + void KPort::OnServerClosed() { + MESOSPHERE_ASSERT_THIS(); + + KScopedSchedulerLock sl; + + if (m_state == State::Normal) { + m_state = State::ServerClosed; + } + } + + Result KPort::EnqueueSession(KServerSession *session) { + KScopedSchedulerLock sl; + + R_UNLESS(m_state == State::Normal, svc::ResultPortClosed()); + + m_server.EnqueueSession(session); + R_SUCCEED(); + } + + Result KPort::EnqueueSession(KLightServerSession *session) { + KScopedSchedulerLock sl; + + R_UNLESS(m_state == State::Normal, svc::ResultPortClosed()); + + m_server.EnqueueSession(session); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_process.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_process.cpp new file mode 100644 index 00000000..08a79f5c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_process.cpp @@ -0,0 +1,1328 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + constexpr u64 InitialProcessIdMin = 1; + constexpr u64 InitialProcessIdMax = 0x50; + + constexpr u64 ProcessIdMin = InitialProcessIdMax + 1; + constexpr u64 ProcessIdMax = std::numeric_limits<u64>::max(); + + constinit util::Atomic<u64> g_initial_process_id = InitialProcessIdMin; + constinit util::Atomic<u64> g_process_id = ProcessIdMin; + + Result TerminateChildren(KProcess *process, const KThread *thread_to_not_terminate) { + /* Request that all children threads terminate. */ + { + KScopedLightLock proc_lk(process->GetListLock()); + KScopedSchedulerLock sl; + + if (thread_to_not_terminate != nullptr && process->GetPinnedThread(GetCurrentCoreId()) == thread_to_not_terminate) { + /* NOTE: Here Nintendo unpins the current thread instead of the thread_to_not_terminate. */ + /* This is valid because the only caller which uses non-nullptr as argument uses GetCurrentThreadPointer(), */ + /* but it's still notable because it seems incorrect at first glance. */ + process->UnpinCurrentThread(); + } + + auto &thread_list = process->GetThreadList(); + for (auto it = thread_list.begin(); it != thread_list.end(); ++it) { + if (KThread *thread = std::addressof(*it); thread != thread_to_not_terminate) { + if (thread->GetState() != KThread::ThreadState_Terminated) { + thread->RequestTerminate(); + } + } + } + } + + /* Wait for all children threads to terminate. */ + while (true) { + /* Get the next child. */ + KThread *cur_child = nullptr; + { + KScopedLightLock proc_lk(process->GetListLock()); + + auto &thread_list = process->GetThreadList(); + for (auto it = thread_list.begin(); it != thread_list.end(); ++it) { + if (KThread *thread = std::addressof(*it); thread != thread_to_not_terminate) { + if (thread->GetState() != KThread::ThreadState_Terminated) { + if (AMS_LIKELY(thread->Open())) { + cur_child = thread; + break; + } + } + } + } + } + + /* If we didn't find any non-terminated children, we're done. */ + if (cur_child == nullptr) { + break; + } + + /* Terminate and close the thread. */ + ON_SCOPE_EXIT { cur_child->Close(); }; + + if (const Result terminate_result = cur_child->Terminate(); svc::ResultTerminationRequested::Includes(terminate_result)) { + R_THROW(terminate_result); + } + } + + R_SUCCEED(); + } + + class ThreadQueueImplForKProcessEnterUserException final : public KThreadQueue { + private: + KThread **m_exception_thread; + public: + constexpr ThreadQueueImplForKProcessEnterUserException(KThread **t) : KThreadQueue(), m_exception_thread(t) { /* ... */ } + + virtual void EndWait(KThread *waiting_thread, Result wait_result) override { + /* Set the exception thread. */ + *m_exception_thread = waiting_thread; + + /* Invoke the base end wait handler. */ + KThreadQueue::EndWait(waiting_thread, wait_result); + } + + virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override { + /* Remove the thread as a waiter on its mutex owner. */ + waiting_thread->GetLockOwner()->RemoveWaiter(waiting_thread); + + /* Invoke the base cancel wait handler. */ + KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + } + }; + + } + + void KProcess::Finalize() { + /* Delete the process local region. */ + this->DeleteThreadLocalRegion(m_plr_address); + + /* Get the used memory size. */ + const size_t used_memory_size = this->GetUsedNonSystemUserPhysicalMemorySize(); + + /* Finalize the page table. */ + m_page_table.Finalize(); + + /* Finish using our system resource. */ + { + if (m_system_resource->IsSecureResource()) { + /* Finalize optimized memory. If memory wasn't optimized, this is a no-op. */ + Kernel::GetMemoryManager().FinalizeOptimizedMemory(this->GetId(), m_memory_pool); + } + + m_system_resource->Close(); + } + + /* Free all shared memory infos. */ + { + auto it = m_shared_memory_list.begin(); + while (it != m_shared_memory_list.end()) { + KSharedMemoryInfo *info = std::addressof(*it); + KSharedMemory *shmem = info->GetSharedMemory(); + + while (!info->Close()) { + shmem->Close(); + } + shmem->Close(); + + it = m_shared_memory_list.erase(it); + KSharedMemoryInfo::Free(info); + } + } + + /* Close all references to our io regions. */ + { + auto it = m_io_region_list.begin(); + while (it != m_io_region_list.end()) { + KIoRegion *io_region = std::addressof(*it); + it = m_io_region_list.erase(it); + + io_region->Close(); + } + } + + /* Our thread local page list must be empty at this point. */ + MESOSPHERE_ABORT_UNLESS(m_partially_used_tlp_tree.empty()); + MESOSPHERE_ABORT_UNLESS(m_fully_used_tlp_tree.empty()); + + /* Release memory to the resource limit. */ + if (m_resource_limit != nullptr) { + MESOSPHERE_ABORT_UNLESS(used_memory_size >= m_memory_release_hint); + m_resource_limit->Release(ams::svc::LimitableResource_PhysicalMemoryMax, used_memory_size, used_memory_size - m_memory_release_hint); + m_resource_limit->Close(); + } + + /* Log that we finalized for debug. */ + MESOSPHERE_LOG("KProcess::Finalize() pid=%ld name=%-12s\n", m_process_id, m_name); + + /* Perform inherited finalization. */ + KSynchronizationObject::Finalize(); + } + + Result KProcess::Initialize(const ams::svc::CreateProcessParameter ¶ms) { + /* Validate that the intended kernel version is high enough for us to support. */ + R_UNLESS(m_capabilities.GetIntendedKernelVersion() >= ams::svc::RequiredKernelVersion, svc::ResultInvalidCombination()); + + /* Validate that the intended kernel version isn't too high for us to support. */ + R_UNLESS(m_capabilities.GetIntendedKernelVersion() <= ams::svc::SupportedKernelVersion, svc::ResultInvalidCombination()); + + /* Create and clear the process local region. */ + R_TRY(this->CreateThreadLocalRegion(std::addressof(m_plr_address))); + m_plr_heap_address = this->GetThreadLocalRegionPointer(m_plr_address); + std::memset(m_plr_heap_address, 0, ams::svc::ThreadLocalRegionSize); + + /* Copy in the name from parameters. */ + static_assert(sizeof(params.name) < sizeof(m_name)); + std::memcpy(m_name, params.name, sizeof(params.name)); + m_name[sizeof(params.name)] = 0; + + /* Set misc fields. */ + m_state = State_Created; + m_main_thread_stack_size = 0; + m_used_kernel_memory_size = 0; + m_ideal_core_id = 0; + m_flags = params.flags; + m_version = params.version; + m_program_id = params.program_id; + m_code_address = params.code_address; + m_code_size = params.code_num_pages * PageSize; + m_is_application = (params.flags & ams::svc::CreateProcessFlag_IsApplication); + m_is_jit_debug = false; + + #if defined(MESOSPHERE_ENABLE_PROCESS_CREATION_TIME) + m_creation_time = KHardwareTimer::GetTick(); + #endif + + /* Set thread fields. */ + for (size_t i = 0; i < cpu::NumCores; i++) { + m_running_threads[i] = nullptr; + m_pinned_threads[i] = nullptr; + m_running_thread_idle_counts[i] = 0; + m_running_thread_switch_counts[i] = 0; + } + + /* Set max memory. */ + m_max_process_memory = KAddressSpaceInfo::GetAddressSpaceSize(static_cast<ams::svc::CreateProcessFlag>(m_flags), KAddressSpaceInfo::Type_Heap); + + /* Generate random entropy. */ + KSystemControl::GenerateRandom(m_entropy, util::size(m_entropy)); + + /* Clear remaining fields. */ + m_num_running_threads = 0; + m_num_process_switches = 0; + m_num_thread_switches = 0; + m_num_fpu_switches = 0; + m_num_supervisor_calls = 0; + m_num_ipc_messages = 0; + + m_is_signaled = false; + m_attached_object = nullptr; + m_exception_thread = nullptr; + m_is_suspended = false; + m_memory_release_hint = 0; + m_schedule_count = 0; + m_is_handle_table_initialized = false; + + /* We're initialized! */ + m_is_initialized = true; + + R_SUCCEED(); + } + + Result KProcess::Initialize(const ams::svc::CreateProcessParameter ¶ms, const KPageGroup &pg, const u32 *caps, s32 num_caps, KResourceLimit *res_limit, KMemoryManager::Pool pool, bool immortal) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(res_limit != nullptr); + MESOSPHERE_ABORT_UNLESS((params.code_num_pages * PageSize) / PageSize == static_cast<size_t>(params.code_num_pages)); + + /* Set members. */ + m_memory_pool = pool; + m_resource_limit = res_limit; + m_is_default_application_system_resource = false; + m_is_immortal = immortal; + + /* Setup our system resource. */ + if (const size_t system_resource_num_pages = params.system_resource_num_pages; system_resource_num_pages != 0) { + /* Create a secure system resource. */ + KSecureSystemResource *secure_resource = KSecureSystemResource::Create(); + R_UNLESS(secure_resource != nullptr, svc::ResultOutOfResource()); + + ON_RESULT_FAILURE { secure_resource->Close(); }; + + /* Initialize the secure resource. */ + R_TRY(secure_resource->Initialize(system_resource_num_pages * PageSize, m_resource_limit, m_memory_pool)); + + /* Set our system resource. */ + m_system_resource = secure_resource; + } else { + /* Use the system-wide system resource. */ + const bool is_app = (params.flags & ams::svc::CreateProcessFlag_IsApplication); + m_system_resource = std::addressof(is_app ? Kernel::GetApplicationSystemResource() : Kernel::GetSystemSystemResource()); + + m_is_default_application_system_resource = is_app; + + /* Open reference to the system resource. */ + m_system_resource->Open(); + } + + /* Ensure we clean up our secure resource, if we fail. */ + ON_RESULT_FAILURE { m_system_resource->Close(); }; + + /* Setup page table. */ + { + const bool from_back = (params.flags & ams::svc::CreateProcessFlag_EnableAslr) == 0; + R_TRY(m_page_table.Initialize(static_cast<ams::svc::CreateProcessFlag>(params.flags), from_back, pool, params.code_address, params.code_num_pages * PageSize, m_system_resource, res_limit, this->GetSlabIndex())); + } + ON_RESULT_FAILURE_2 { m_page_table.Finalize(); }; + + /* Ensure we can insert the code region. */ + R_UNLESS(m_page_table.CanContain(params.code_address, params.code_num_pages * PageSize, KMemoryState_Code), svc::ResultInvalidMemoryRegion()); + + /* Map the code region. */ + R_TRY(m_page_table.MapPageGroup(params.code_address, pg, KMemoryState_Code, KMemoryPermission_KernelRead)); + + /* Initialize capabilities. */ + R_TRY(m_capabilities.Initialize(caps, num_caps, std::addressof(m_page_table))); + + /* Initialize the process id. */ + m_process_id = g_initial_process_id++; + MESOSPHERE_ABORT_UNLESS(InitialProcessIdMin <= m_process_id); + MESOSPHERE_ABORT_UNLESS(m_process_id <= InitialProcessIdMax); + + /* Initialize the rest of the process. */ + R_TRY(this->Initialize(params)); + + /* Open a reference to the resource limit. */ + m_resource_limit->Open(); + + /* We succeeded! */ + R_SUCCEED(); + } + + Result KProcess::Initialize(const ams::svc::CreateProcessParameter ¶ms, svc::KUserPointer<const u32 *> user_caps, s32 num_caps, KResourceLimit *res_limit, KMemoryManager::Pool pool) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(res_limit != nullptr); + + /* Set pool and resource limit. */ + m_memory_pool = pool; + m_resource_limit = res_limit; + m_is_default_application_system_resource = false; + m_is_immortal = false; + + /* Get the memory sizes. */ + const size_t code_num_pages = params.code_num_pages; + const size_t system_resource_num_pages = params.system_resource_num_pages; + const size_t code_size = code_num_pages * PageSize; + const size_t system_resource_size = system_resource_num_pages * PageSize; + + /* Reserve memory for our code resource. */ + KScopedResourceReservation memory_reservation(this, ams::svc::LimitableResource_PhysicalMemoryMax, code_size); + R_UNLESS(memory_reservation.Succeeded(), svc::ResultLimitReached()); + + /* Setup our system resource. */ + if (system_resource_num_pages != 0) { + /* Create a secure system resource. */ + KSecureSystemResource *secure_resource = KSecureSystemResource::Create(); + R_UNLESS(secure_resource != nullptr, svc::ResultOutOfResource()); + + ON_RESULT_FAILURE { secure_resource->Close(); }; + + /* Initialize the secure resource. */ + R_TRY(secure_resource->Initialize(system_resource_size, m_resource_limit, m_memory_pool)); + + /* Set our system resource. */ + m_system_resource = secure_resource; + + } else { + /* Use the system-wide system resource. */ + const bool is_app = (params.flags & ams::svc::CreateProcessFlag_IsApplication); + m_system_resource = std::addressof(is_app ? Kernel::GetApplicationSystemResource() : Kernel::GetSystemSystemResource()); + + m_is_default_application_system_resource = is_app; + + /* Open reference to the system resource. */ + m_system_resource->Open(); + } + + /* Ensure we clean up our secure resource, if we fail. */ + ON_RESULT_FAILURE { m_system_resource->Close(); }; + + /* Setup page table. */ + { + const bool from_back = (params.flags & ams::svc::CreateProcessFlag_EnableAslr) == 0; + R_TRY(m_page_table.Initialize(static_cast<ams::svc::CreateProcessFlag>(params.flags), from_back, pool, params.code_address, code_size, m_system_resource, res_limit, this->GetSlabIndex())); + } + ON_RESULT_FAILURE_2 { m_page_table.Finalize(); }; + + /* Ensure we can insert the code region. */ + R_UNLESS(m_page_table.CanContain(params.code_address, code_size, KMemoryState_Code), svc::ResultInvalidMemoryRegion()); + + /* Map the code region. */ + R_TRY(m_page_table.MapPages(params.code_address, code_num_pages, KMemoryState_Code, static_cast<KMemoryPermission>(KMemoryPermission_KernelRead | KMemoryPermission_NotMapped))); + + /* Initialize capabilities. */ + R_TRY(m_capabilities.Initialize(user_caps, num_caps, std::addressof(m_page_table))); + + /* Initialize the process id. */ + m_process_id = g_process_id++; + MESOSPHERE_ABORT_UNLESS(ProcessIdMin <= m_process_id); + MESOSPHERE_ABORT_UNLESS(m_process_id <= ProcessIdMax); + + /* If we should optimize memory allocations, do so. */ + if (m_system_resource->IsSecureResource() && (params.flags & ams::svc::CreateProcessFlag_OptimizeMemoryAllocation) != 0) { + R_TRY(Kernel::GetMemoryManager().InitializeOptimizedMemory(m_process_id, pool)); + } + + /* Initialize the rest of the process. */ + R_TRY(this->Initialize(params)); + + /* Open a reference to the resource limit. */ + m_resource_limit->Open(); + + /* We succeeded, so commit our memory reservation. */ + memory_reservation.Commit(); + R_SUCCEED(); + } + + void KProcess::DoWorkerTaskImpl() { + /* Terminate child threads. */ + TerminateChildren(this, nullptr); + + /* Finalize the handle table, if we're not immortal. */ + if (!m_is_immortal && m_is_handle_table_initialized) { + this->FinalizeHandleTable(); + } + + /* Call the debug callback. */ + KDebug::OnExitProcess(this); + + /* Finish termination. */ + this->FinishTermination(); + } + + Result KProcess::StartTermination() { + /* Finalize the handle table when we're done, if the process isn't immortal. */ + ON_SCOPE_EXIT { + if (!m_is_immortal) { + this->FinalizeHandleTable(); + } + }; + + /* Terminate child threads other than the current one. */ + R_RETURN(TerminateChildren(this, GetCurrentThreadPointer())); + } + + void KProcess::FinishTermination() { + /* Only allow termination to occur if the process isn't immortal. */ + if (!m_is_immortal) { + /* Release resource limit hint. */ + if (m_resource_limit != nullptr) { + m_memory_release_hint = this->GetUsedNonSystemUserPhysicalMemorySize(); + m_resource_limit->Release(ams::svc::LimitableResource_PhysicalMemoryMax, 0, m_memory_release_hint); + } + + /* Change state. */ + { + KScopedSchedulerLock sl; + this->ChangeState(State_Terminated); + } + + /* Close. */ + this->Close(); + } + } + + void KProcess::Exit() { + MESOSPHERE_ASSERT_THIS(); + + /* Determine whether we need to start terminating. */ + bool needs_terminate = false; + { + KScopedLightLock lk(m_state_lock); + KScopedSchedulerLock sl; + + MESOSPHERE_ASSERT(m_state != State_Created); + MESOSPHERE_ASSERT(m_state != State_CreatedAttached); + MESOSPHERE_ASSERT(m_state != State_Crashed); + MESOSPHERE_ASSERT(m_state != State_Terminated); + if (m_state == State_Running || m_state == State_RunningAttached || m_state == State_DebugBreak) { + this->ChangeState(State_Terminating); + needs_terminate = true; + } + } + + /* If we need to start termination, do so. */ + if (needs_terminate) { + this->StartTermination(); + + /* Note for debug that we're exiting the process. */ + MESOSPHERE_LOG("KProcess::Exit() pid=%ld name=%-12s\n", m_process_id, m_name); + + /* Register the process as a work task. */ + KWorkerTaskManager::AddTask(KWorkerTaskManager::WorkerType_ExitProcess, this); + } + + /* Exit the current thread. */ + GetCurrentThread().Exit(); + MESOSPHERE_PANIC("Thread survived call to exit"); + } + + Result KProcess::Terminate() { + MESOSPHERE_ASSERT_THIS(); + + /* Determine whether we need to start terminating */ + bool needs_terminate = false; + { + KScopedLightLock lk(m_state_lock); + + /* Check whether we're allowed to terminate. */ + R_UNLESS(m_state != State_Created, svc::ResultInvalidState()); + R_UNLESS(m_state != State_CreatedAttached, svc::ResultInvalidState()); + + KScopedSchedulerLock sl; + + if (m_state == State_Running || m_state == State_RunningAttached || m_state == State_Crashed || m_state == State_DebugBreak) { + this->ChangeState(State_Terminating); + needs_terminate = true; + } + } + + /* If we need to terminate, do so. */ + if (needs_terminate) { + /* Start termination. */ + if (R_SUCCEEDED(this->StartTermination())) { + /* Note for debug that we're terminating the process. */ + MESOSPHERE_LOG("KProcess::Terminate() OK pid=%ld name=%-12s\n", m_process_id, m_name); + + /* Call the debug callback. */ + KDebug::OnTerminateProcess(this); + + /* Finish termination. */ + this->FinishTermination(); + } else { + /* Note for debug that we're terminating the process. */ + MESOSPHERE_LOG("KProcess::Terminate() FAIL pid=%ld name=%-12s\n", m_process_id, m_name); + + /* Register the process as a work task. */ + KWorkerTaskManager::AddTask(KWorkerTaskManager::WorkerType_ExitProcess, this); + } + } + + R_SUCCEED(); + } + + Result KProcess::AddSharedMemory(KSharedMemory *shmem, KProcessAddress address, size_t size) { + /* Lock ourselves, to prevent concurrent access. */ + KScopedLightLock lk(m_state_lock); + + /* Address and size parameters aren't used. */ + MESOSPHERE_UNUSED(address, size); + + /* Try to find an existing info for the memory. */ + KSharedMemoryInfo *info = nullptr; + for (auto it = m_shared_memory_list.begin(); it != m_shared_memory_list.end(); ++it) { + if (it->GetSharedMemory() == shmem) { + info = std::addressof(*it); + break; + } + } + + /* If we didn't find an info, create one. */ + if (info == nullptr) { + /* Allocate a new info. */ + info = KSharedMemoryInfo::Allocate(); + R_UNLESS(info != nullptr, svc::ResultOutOfResource()); + + /* Initialize the info and add it to our list. */ + info->Initialize(shmem); + m_shared_memory_list.push_back(*info); + } + + /* Open a reference to the shared memory and its info. */ + shmem->Open(); + info->Open(); + + R_SUCCEED(); + } + + void KProcess::RemoveSharedMemory(KSharedMemory *shmem, KProcessAddress address, size_t size) { + /* Lock ourselves, to prevent concurrent access. */ + KScopedLightLock lk(m_state_lock); + + /* Address and size parameters aren't used. */ + MESOSPHERE_UNUSED(address, size); + + /* Find an existing info for the memory. */ + KSharedMemoryInfo *info = nullptr; + auto it = m_shared_memory_list.begin(); + for (/* ... */; it != m_shared_memory_list.end(); ++it) { + if (it->GetSharedMemory() == shmem) { + info = std::addressof(*it); + break; + } + } + MESOSPHERE_ABORT_UNLESS(info != nullptr); + + /* Close a reference to the info and its memory. */ + if (info->Close()) { + m_shared_memory_list.erase(it); + KSharedMemoryInfo::Free(info); + } + + shmem->Close(); + } + + void KProcess::AddIoRegion(KIoRegion *io_region) { + /* Lock ourselves, to prevent concurrent access. */ + KScopedLightLock lk(m_state_lock); + + /* Open a reference to the region. */ + io_region->Open(); + + /* Add the region to our list. */ + m_io_region_list.push_back(*io_region); + + } + + void KProcess::RemoveIoRegion(KIoRegion *io_region) { + /* Remove the region from our list. */ + { + /* Lock ourselves, to prevent concurrent access. */ + KScopedLightLock lk(m_state_lock); + + /* Remove the region from our list. */ + m_io_region_list.erase(m_io_region_list.iterator_to(*io_region)); + } + + /* Close our reference to the io region. */ + io_region->Close(); + } + + Result KProcess::CreateThreadLocalRegion(KProcessAddress *out) { + KThreadLocalPage *tlp = nullptr; + KProcessAddress tlr = Null<KProcessAddress>; + + /* See if we can get a region from a partially used TLP. */ + { + KScopedSchedulerLock sl; + + if (auto it = m_partially_used_tlp_tree.begin(); it != m_partially_used_tlp_tree.end()) { + tlr = it->Reserve(); + MESOSPHERE_ABORT_UNLESS(tlr != Null<KProcessAddress>); + + if (it->IsAllUsed()) { + tlp = std::addressof(*it); + m_partially_used_tlp_tree.erase(it); + m_fully_used_tlp_tree.insert(*tlp); + } + + *out = tlr; + R_SUCCEED(); + } + } + + /* Allocate a new page. */ + tlp = KThreadLocalPage::Allocate(); + R_UNLESS(tlp != nullptr, svc::ResultOutOfMemory()); + ON_RESULT_FAILURE { KThreadLocalPage::Free(tlp); }; + + /* Initialize the new page. */ + R_TRY(tlp->Initialize(this)); + + /* Reserve a TLR. */ + tlr = tlp->Reserve(); + MESOSPHERE_ABORT_UNLESS(tlr != Null<KProcessAddress>); + + /* Insert into our tree. */ + { + KScopedSchedulerLock sl; + if (tlp->IsAllUsed()) { + m_fully_used_tlp_tree.insert(*tlp); + } else { + m_partially_used_tlp_tree.insert(*tlp); + } + } + + /* We succeeded! */ + *out = tlr; + R_SUCCEED(); + } + + Result KProcess::DeleteThreadLocalRegion(KProcessAddress addr) { + KThreadLocalPage *page_to_free = nullptr; + + /* Release the region. */ + { + KScopedSchedulerLock sl; + + /* Try to find the page in the partially used list. */ + auto it = m_partially_used_tlp_tree.find_key(util::AlignDown(GetInteger(addr), PageSize)); + if (it == m_partially_used_tlp_tree.end()) { + /* If we don't find it, it has to be in the fully used list. */ + it = m_fully_used_tlp_tree.find_key(util::AlignDown(GetInteger(addr), PageSize)); + R_UNLESS(it != m_fully_used_tlp_tree.end(), svc::ResultInvalidAddress()); + + /* Release the region. */ + it->Release(addr); + + /* Move the page out of the fully used list. */ + KThreadLocalPage *tlp = std::addressof(*it); + m_fully_used_tlp_tree.erase(it); + if (tlp->IsAllFree()) { + page_to_free = tlp; + } else { + m_partially_used_tlp_tree.insert(*tlp); + } + } else { + /* Release the region. */ + it->Release(addr); + + /* Handle the all-free case. */ + KThreadLocalPage *tlp = std::addressof(*it); + if (tlp->IsAllFree()) { + m_partially_used_tlp_tree.erase(it); + page_to_free = tlp; + } + } + } + + /* If we should free the page it was in, do so. */ + if (page_to_free != nullptr) { + page_to_free->Finalize(); + + KThreadLocalPage::Free(page_to_free); + } + + R_SUCCEED(); + } + + void *KProcess::GetThreadLocalRegionPointer(KProcessAddress addr) { + KThreadLocalPage *tlp = nullptr; + { + KScopedSchedulerLock sl; + if (auto it = m_partially_used_tlp_tree.find_key(util::AlignDown(GetInteger(addr), PageSize)); it != m_partially_used_tlp_tree.end()) { + tlp = std::addressof(*it); + } else if (auto it = m_fully_used_tlp_tree.find_key(util::AlignDown(GetInteger(addr), PageSize)); it != m_fully_used_tlp_tree.end()) { + tlp = std::addressof(*it); + } else { + return nullptr; + } + } + return static_cast<u8 *>(tlp->GetPointer()) + (GetInteger(addr) & (PageSize - 1)); + } + + bool KProcess::ReserveResource(ams::svc::LimitableResource which, s64 value) { + if (KResourceLimit *rl = this->GetResourceLimit(); rl != nullptr) { + return rl->Reserve(which, value); + } else { + return true; + } + } + + bool KProcess::ReserveResource(ams::svc::LimitableResource which, s64 value, s64 timeout) { + if (KResourceLimit *rl = this->GetResourceLimit(); rl != nullptr) { + return rl->Reserve(which, value, timeout); + } else { + return true; + } + } + + void KProcess::ReleaseResource(ams::svc::LimitableResource which, s64 value) { + if (KResourceLimit *rl = this->GetResourceLimit(); rl != nullptr) { + rl->Release(which, value); + } + } + + void KProcess::ReleaseResource(ams::svc::LimitableResource which, s64 value, s64 hint) { + if (KResourceLimit *rl = this->GetResourceLimit(); rl != nullptr) { + rl->Release(which, value, hint); + } + } + + void KProcess::IncrementRunningThreadCount() { + MESOSPHERE_ASSERT(m_num_running_threads.Load() >= 0); + + ++m_num_running_threads; + } + + void KProcess::DecrementRunningThreadCount() { + MESOSPHERE_ASSERT(m_num_running_threads.Load() > 0); + + if (const auto prev = m_num_running_threads--; prev == 1) { + this->Terminate(); + } + } + + bool KProcess::EnterUserException() { + /* Get the current thread. */ + KThread *cur_thread = GetCurrentThreadPointer(); + MESOSPHERE_ASSERT(this == cur_thread->GetOwnerProcess()); + + /* Check that we haven't already claimed the exception thread. */ + if (m_exception_thread == cur_thread) { + return false; + } + + /* Create the wait queue we'll be using. */ + ThreadQueueImplForKProcessEnterUserException wait_queue(std::addressof(m_exception_thread)); + + /* Claim the exception thread. */ + { + /* Lock the scheduler. */ + KScopedSchedulerLock sl; + + /* Check that we're not terminating. */ + if (cur_thread->IsTerminationRequested()) { + return false; + } + + /* If we don't have an exception thread, we can just claim it directly. */ + if (m_exception_thread == nullptr) { + m_exception_thread = cur_thread; + KScheduler::SetSchedulerUpdateNeeded(); + return true; + } + + /* Otherwise, we need to wait until we don't have an exception thread. */ + + /* Add the current thread as a waiter on the current exception thread. */ + cur_thread->SetAddressKey(reinterpret_cast<uintptr_t>(std::addressof(m_exception_thread)) | 1); + m_exception_thread->AddWaiter(cur_thread); + + /* Wait to claim the exception thread. */ + cur_thread->BeginWait(std::addressof(wait_queue)); + } + + /* If our wait didn't end due to thread termination, we succeeded. */ + return !svc::ResultTerminationRequested::Includes(cur_thread->GetWaitResult()); + } + + bool KProcess::LeaveUserException() { + return this->ReleaseUserException(GetCurrentThreadPointer()); + } + + bool KProcess::ReleaseUserException(KThread *thread) { + KScopedSchedulerLock sl; + + if (m_exception_thread == thread) { + m_exception_thread = nullptr; + + /* Remove waiter thread. */ + bool has_waiters; + if (KThread *next = thread->RemoveWaiterByKey(std::addressof(has_waiters), reinterpret_cast<uintptr_t>(std::addressof(m_exception_thread)) | 1); next != nullptr) { + next->EndWait(ResultSuccess()); + } + + KScheduler::SetSchedulerUpdateNeeded(); + + return true; + } else { + return false; + } + } + + void KProcess::RegisterThread(KThread *thread) { + KScopedLightLock lk(m_list_lock); + + m_thread_list.push_back(*thread); + } + + void KProcess::UnregisterThread(KThread *thread) { + KScopedLightLock lk(m_list_lock); + + m_thread_list.erase(m_thread_list.iterator_to(*thread)); + } + + size_t KProcess::GetUsedUserPhysicalMemorySize() const { + const size_t norm_size = m_page_table.GetNormalMemorySize(); + const size_t other_size = m_code_size + m_main_thread_stack_size; + const size_t sec_size = this->GetRequiredSecureMemorySizeNonDefault(); + + return norm_size + other_size + sec_size; + } + + size_t KProcess::GetTotalUserPhysicalMemorySize() const { + /* Get the amount of free and used size. */ + const size_t free_size = m_resource_limit->GetFreeValue(ams::svc::LimitableResource_PhysicalMemoryMax); + const size_t max_size = m_max_process_memory; + + /* Determine used size. */ + /* NOTE: This does *not* check this->IsDefaultApplicationSystemResource(), unlike GetUsedUserPhysicalMemorySize(). */ + const size_t norm_size = m_page_table.GetNormalMemorySize(); + const size_t other_size = m_code_size + m_main_thread_stack_size; + const size_t sec_size = this->GetRequiredSecureMemorySize(); + const size_t used_size = norm_size + other_size + sec_size; + + /* NOTE: These function calls will recalculate, introducing a race...it is unclear why Nintendo does it this way. */ + if (used_size + free_size > max_size) { + return max_size; + } else { + return free_size + this->GetUsedUserPhysicalMemorySize(); + } + } + + size_t KProcess::GetUsedNonSystemUserPhysicalMemorySize() const { + const size_t norm_size = m_page_table.GetNormalMemorySize(); + const size_t other_size = m_code_size + m_main_thread_stack_size; + + return norm_size + other_size; + } + + size_t KProcess::GetTotalNonSystemUserPhysicalMemorySize() const { + /* Get the amount of free and used size. */ + const size_t free_size = m_resource_limit->GetFreeValue(ams::svc::LimitableResource_PhysicalMemoryMax); + const size_t max_size = m_max_process_memory; + + /* Determine used size. */ + /* NOTE: This does *not* check this->IsDefaultApplicationSystemResource(), unlike GetUsedUserPhysicalMemorySize(). */ + const size_t norm_size = m_page_table.GetNormalMemorySize(); + const size_t other_size = m_code_size + m_main_thread_stack_size; + const size_t sec_size = this->GetRequiredSecureMemorySize(); + const size_t used_size = norm_size + other_size + sec_size; + + /* NOTE: These function calls will recalculate, introducing a race...it is unclear why Nintendo does it this way. */ + if (used_size + free_size > max_size) { + return max_size - this->GetRequiredSecureMemorySizeNonDefault(); + } else { + return free_size + this->GetUsedNonSystemUserPhysicalMemorySize(); + } + } + + Result KProcess::Run(s32 priority, size_t stack_size) { + MESOSPHERE_ASSERT_THIS(); + + /* Lock ourselves, to prevent concurrent access. */ + KScopedLightLock lk(m_state_lock); + + /* Validate that we're in a state where we can initialize. */ + const auto state = m_state; + R_UNLESS(state == State_Created || state == State_CreatedAttached, svc::ResultInvalidState()); + + /* Place a tentative reservation of a thread for this process. */ + KScopedResourceReservation thread_reservation(this, ams::svc::LimitableResource_ThreadCountMax); + R_UNLESS(thread_reservation.Succeeded(), svc::ResultLimitReached()); + + /* Ensure that we haven't already allocated stack. */ + MESOSPHERE_ABORT_UNLESS(m_main_thread_stack_size == 0); + + /* Ensure that we're allocating a valid stack. */ + R_UNLESS(stack_size + m_code_size <= m_max_process_memory, svc::ResultOutOfMemory()); + R_UNLESS(stack_size + m_code_size >= m_code_size, svc::ResultOutOfMemory()); + + /* Place a tentative reservation of memory for our new stack. */ + KScopedResourceReservation mem_reservation(this, ams::svc::LimitableResource_PhysicalMemoryMax, stack_size); + R_UNLESS(mem_reservation.Succeeded(), svc::ResultLimitReached()); + + /* Allocate and map our stack. */ + KProcessAddress stack_top = Null<KProcessAddress>; + if (stack_size) { + KProcessAddress stack_bottom; + R_TRY(m_page_table.MapPages(std::addressof(stack_bottom), stack_size / PageSize, KMemoryState_Stack, KMemoryPermission_UserReadWrite)); + + stack_top = stack_bottom + stack_size; + m_main_thread_stack_size = stack_size; + } + + /* Ensure our stack is safe to clean up on exit. */ + ON_RESULT_FAILURE { + if (m_main_thread_stack_size) { + MESOSPHERE_R_ABORT_UNLESS(m_page_table.UnmapPages(stack_top - m_main_thread_stack_size, m_main_thread_stack_size / PageSize, KMemoryState_Stack)); + m_main_thread_stack_size = 0; + } + }; + + /* Set our maximum heap size. */ + R_TRY(m_page_table.SetMaxHeapSize(m_max_process_memory - (m_main_thread_stack_size + m_code_size))); + + /* Initialize our handle table. */ + R_TRY(this->InitializeHandleTable(m_capabilities.GetHandleTableSize())); + ON_RESULT_FAILURE_2 { this->FinalizeHandleTable(); }; + + /* Create a new thread for the process. */ + KThread *main_thread = KThread::Create(); + R_UNLESS(main_thread != nullptr, svc::ResultOutOfResource()); + ON_SCOPE_EXIT { main_thread->Close(); }; + + /* Initialize the thread. */ + R_TRY(KThread::InitializeUserThread(main_thread, reinterpret_cast<KThreadFunction>(GetVoidPointer(this->GetEntryPoint())), 0, stack_top, priority, m_ideal_core_id, this)); + + /* Register the thread, and commit our reservation. */ + KThread::Register(main_thread); + thread_reservation.Commit(); + + /* Add the thread to our handle table. */ + ams::svc::Handle thread_handle; + R_TRY(m_handle_table.Add(std::addressof(thread_handle), main_thread)); + + /* Set the thread arguments. */ + main_thread->GetContext().SetArguments(0, thread_handle); + + /* Update our state. */ + this->ChangeState((state == State_Created) ? State_Running : State_RunningAttached); + ON_RESULT_FAILURE_2 { this->ChangeState(state); }; + + /* Run our thread. */ + R_TRY(main_thread->Run()); + + /* Open a reference to represent that we're running. */ + this->Open(); + + /* We succeeded! Commit our memory reservation. */ + mem_reservation.Commit(); + + /* Note for debug that we're running a new process. */ + MESOSPHERE_LOG("KProcess::Run() pid=%ld name=%-12s thread=%ld affinity=0x%lx ideal_core=%d active_core=%d\n", m_process_id, m_name, main_thread->GetId(), main_thread->GetVirtualAffinityMask(), main_thread->GetIdealVirtualCore(), main_thread->GetActiveCore()); + + R_SUCCEED(); + } + + Result KProcess::Reset() { + MESOSPHERE_ASSERT_THIS(); + + /* Lock the process and the scheduler. */ + KScopedLightLock lk(m_state_lock); + KScopedSchedulerLock sl; + + /* Validate that we're in a state that we can reset. */ + R_UNLESS(m_state != State_Terminated, svc::ResultInvalidState()); + R_UNLESS(m_is_signaled, svc::ResultInvalidState()); + + /* Clear signaled. */ + m_is_signaled = false; + R_SUCCEED(); + } + + Result KProcess::SetActivity(ams::svc::ProcessActivity activity) { + /* Lock ourselves and the scheduler. */ + KScopedLightLock lk(m_state_lock); + KScopedLightLock list_lk(m_list_lock); + KScopedSchedulerLock sl; + + /* Validate our state. */ + R_UNLESS(m_state != State_Terminating, svc::ResultInvalidState()); + R_UNLESS(m_state != State_Terminated, svc::ResultInvalidState()); + + /* Either pause or resume. */ + if (activity == ams::svc::ProcessActivity_Paused) { + /* Verify that we're not suspended. */ + R_UNLESS(!m_is_suspended, svc::ResultInvalidState()); + + /* Suspend all threads. */ + auto end = this->GetThreadList().end(); + for (auto it = this->GetThreadList().begin(); it != end; ++it) { + it->RequestSuspend(KThread::SuspendType_Process); + } + + /* Set ourselves as suspended. */ + this->SetSuspended(true); + } else { + MESOSPHERE_ASSERT(activity == ams::svc::ProcessActivity_Runnable); + + /* Verify that we're suspended. */ + R_UNLESS(m_is_suspended, svc::ResultInvalidState()); + + /* Resume all threads. */ + auto end = this->GetThreadList().end(); + for (auto it = this->GetThreadList().begin(); it != end; ++it) { + it->Resume(KThread::SuspendType_Process); + } + + /* Set ourselves as resumed. */ + this->SetSuspended(false); + } + + R_SUCCEED(); + } + + void KProcess::PinCurrentThread() { + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + /* Get the current thread. */ + const s32 core_id = GetCurrentCoreId(); + KThread *cur_thread = GetCurrentThreadPointer(); + + /* If the thread isn't terminated, pin it. */ + if (!cur_thread->IsTerminationRequested()) { + /* Pin it. */ + this->PinThread(core_id, cur_thread); + cur_thread->Pin(); + + /* An update is needed. */ + KScheduler::SetSchedulerUpdateNeeded(); + } + } + + void KProcess::UnpinCurrentThread() { + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + /* Get the current thread. */ + const s32 core_id = GetCurrentCoreId(); + KThread *cur_thread = GetCurrentThreadPointer(); + + /* Unpin it. */ + cur_thread->Unpin(); + this->UnpinThread(core_id, cur_thread); + + /* An update is needed. */ + KScheduler::SetSchedulerUpdateNeeded(); + } + + void KProcess::UnpinThread(KThread *thread) { + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + /* Get the thread's core id. */ + const auto core_id = thread->GetActiveCore(); + + /* Unpin it. */ + this->UnpinThread(core_id, thread); + thread->Unpin(); + + /* An update is needed. */ + KScheduler::SetSchedulerUpdateNeeded(); + } + + Result KProcess::GetThreadList(s32 *out_num_threads, ams::kern::svc::KUserPointer<u64 *> out_thread_ids, s32 max_out_count) { + /* Lock the list. */ + KScopedLightLock lk(m_list_lock); + + /* Iterate over the list. */ + s32 count = 0; + auto end = this->GetThreadList().end(); + for (auto it = this->GetThreadList().begin(); it != end; ++it) { + /* If we're within array bounds, write the id. */ + if (count < max_out_count) { + /* Get the thread id. */ + KThread *thread = std::addressof(*it); + const u64 id = thread->GetId(); + + /* Copy the id to userland. */ + R_TRY(out_thread_ids.CopyArrayElementFrom(std::addressof(id), count)); + } + + /* Increment the count. */ + ++count; + } + + /* We successfully iterated the list. */ + *out_num_threads = count; + R_SUCCEED(); + } + + KProcess::State KProcess::SetDebugObject(void *debug_object) { + /* Attaching should only happen to non-null objects while the scheduler is locked. */ + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + MESOSPHERE_ASSERT(debug_object != nullptr); + + /* Cache our state to return it to the debug object. */ + const auto old_state = m_state; + + /* Set the object. */ + m_attached_object = debug_object; + + /* Check that our state is valid for attach. */ + MESOSPHERE_ASSERT(m_state == State_Created || m_state == State_Running || m_state == State_Crashed); + + /* Update our state. */ + if (m_state != State_DebugBreak) { + if (m_state == State_Created) { + this->ChangeState(State_CreatedAttached); + } else { + this->ChangeState(State_DebugBreak); + } + } + + return old_state; + } + + void KProcess::ClearDebugObject(KProcess::State old_state) { + /* Detaching from process should only happen while the scheduler is locked. */ + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + /* Clear the attached object. */ + m_attached_object = nullptr; + + /* Validate that the process is in an attached state. */ + MESOSPHERE_ASSERT(m_state == State_CreatedAttached || m_state == State_RunningAttached || m_state == State_DebugBreak || m_state == State_Terminating || m_state == State_Terminated); + + /* Change the state appropriately. */ + if (m_state == State_CreatedAttached) { + this->ChangeState(State_Created); + } else if (m_state == State_RunningAttached || m_state == State_DebugBreak) { + /* Disallow transition back to created from running. */ + if (old_state == State_Created) { + old_state = State_Running; + } + this->ChangeState(old_state); + } + } + + bool KProcess::EnterJitDebug(ams::svc::DebugEvent event, ams::svc::DebugException exception, uintptr_t param1, uintptr_t param2, uintptr_t param3, uintptr_t param4) { + /* Check that we're the current process. */ + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(this == GetCurrentProcessPointer()); + + /* If we aren't allowed to enter jit debug, don't. */ + if ((m_flags & ams::svc::CreateProcessFlag_EnableDebug) == 0) { + return false; + } + + /* We're the current process, so we should be some kind of running. */ + MESOSPHERE_ASSERT(m_state != State_Created); + MESOSPHERE_ASSERT(m_state != State_CreatedAttached); + MESOSPHERE_ASSERT(m_state != State_Terminated); + + /* Try to enter JIT debug. */ + while (true) { + /* Lock ourselves and the scheduler. */ + KScopedLightLock lk(m_state_lock); + KScopedLightLock list_lk(m_list_lock); + KScopedSchedulerLock sl; + + /* If we're attached to a debugger, we're necessarily in debug. */ + if (this->IsAttachedToDebugger()) { + return true; + } + + /* If the current thread is terminating, we can't enter debug. */ + if (GetCurrentThread().IsTerminationRequested()) { + return false; + } + + /* We're not attached to debugger, so check that. */ + MESOSPHERE_ASSERT(m_state != State_RunningAttached); + MESOSPHERE_ASSERT(m_state != State_DebugBreak); + + /* If we're terminating, we can't enter debug. */ + if (m_state != State_Running && m_state != State_Crashed) { + MESOSPHERE_ASSERT(m_state == State_Terminating); + return false; + } + + /* If the current thread is suspended, retry. */ + if (GetCurrentThread().IsSuspended()) { + continue; + } + + /* Suspend all our threads. */ + { + auto end = this->GetThreadList().end(); + for (auto it = this->GetThreadList().begin(); it != end; ++it) { + it->RequestSuspend(KThread::SuspendType_Debug); + } + } + + /* Change our state to crashed. */ + this->ChangeState(State_Crashed); + + /* Enter jit debug. */ + m_is_jit_debug = true; + m_jit_debug_event_type = event; + m_jit_debug_exception_type = exception; + m_jit_debug_params[0] = param1; + m_jit_debug_params[1] = param2; + m_jit_debug_params[2] = param3; + m_jit_debug_params[3] = param4; + m_jit_debug_thread_id = GetCurrentThread().GetId(); + + /* Exit our retry loop. */ + break; + } + + /* Check if our state indicates we're in jit debug. */ + { + KScopedSchedulerLock sl; + + if (m_state == State_Running || m_state == State_RunningAttached || m_state == State_Crashed || m_state == State_DebugBreak) { + return true; + } + } + + return false; + } + + KEventInfo *KProcess::GetJitDebugInfo() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + if (m_is_jit_debug) { + const uintptr_t params[5] = { m_jit_debug_exception_type, m_jit_debug_params[0], m_jit_debug_params[1], m_jit_debug_params[2], m_jit_debug_params[3] }; + return KDebugBase::CreateDebugEvent(m_jit_debug_event_type, m_jit_debug_thread_id, params, util::size(params)); + } else { + return nullptr; + } + } + + void KProcess::ClearJitDebugInfo() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + m_is_jit_debug = false; + } + + KProcess *KProcess::GetProcessFromId(u64 process_id) { + /* Lock the list. */ + KProcess::ListAccessor accessor; + const auto end = accessor.end(); + + /* Iterate over the list. */ + for (auto it = accessor.begin(); it != end; ++it) { + /* Get the process. */ + KProcess *process = static_cast<KProcess *>(std::addressof(*it)); + + if (process->GetId() == process_id) { + if (AMS_LIKELY(process->Open())) { + return process; + } + } + } + + /* We failed to find the process. */ + return nullptr; + } + + Result KProcess::GetProcessList(s32 *out_num_processes, ams::kern::svc::KUserPointer<u64 *> out_process_ids, s32 max_out_count) { + /* Lock the list. */ + KProcess::ListAccessor accessor; + const auto end = accessor.end(); + + /* Iterate over the list. */ + s32 count = 0; + for (auto it = accessor.begin(); it != end; ++it) { + /* If we're within array bounds, write the id. */ + if (count < max_out_count) { + /* Get the process id. */ + KProcess *process = static_cast<KProcess *>(std::addressof(*it)); + const u64 id = process->GetId(); + + /* Copy the id to userland. */ + R_TRY(out_process_ids.CopyArrayElementFrom(std::addressof(id), count)); + } + + /* Increment the count. */ + ++count; + } + + /* We successfully iterated the list. */ + *out_num_processes = count; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_readable_event.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_readable_event.cpp new file mode 100644 index 00000000..375e536f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_readable_event.cpp @@ -0,0 +1,73 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + void KReadableEvent::Initialize(KEvent *parent) { + MESOSPHERE_ASSERT_THIS(); + m_is_signaled = false; + m_parent = parent; + + if (m_parent != nullptr) { + m_parent->Open(); + } + } + + bool KReadableEvent::IsSignaled() const { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + return m_is_signaled; + } + + void KReadableEvent::Destroy() { + MESOSPHERE_ASSERT_THIS(); + if (m_parent) { + { + KScopedSchedulerLock sl; + m_parent->OnReadableEventDestroyed(); + } + + m_parent->Close(); + } + } + + Result KReadableEvent::Signal() { + MESOSPHERE_ASSERT_THIS(); + + KScopedSchedulerLock lk; + + if (!m_is_signaled) { + m_is_signaled = true; + this->NotifyAvailable(); + } + + R_SUCCEED(); + } + + Result KReadableEvent::Reset() { + MESOSPHERE_ASSERT_THIS(); + + KScopedSchedulerLock lk; + + R_UNLESS(m_is_signaled, svc::ResultInvalidState()); + + m_is_signaled = false; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_resource_limit.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_resource_limit.cpp new file mode 100644 index 00000000..6ed10a7a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_resource_limit.cpp @@ -0,0 +1,216 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + constexpr s64 DefaultTimeout = ams::svc::Tick(TimeSpan::FromSeconds(10)); + + } + + void KResourceLimit::Initialize() { + m_waiter_count = 0; + std::memset(m_limit_values, 0, sizeof(m_limit_values)); + std::memset(m_current_values, 0, sizeof(m_current_values)); + std::memset(m_current_hints, 0, sizeof(m_current_hints)); + std::memset(m_peak_values, 0, sizeof(m_peak_values)); + } + + void KResourceLimit::Finalize() { + /* ... */ + } + + s64 KResourceLimit::GetLimitValue(ams::svc::LimitableResource which) const { + MESOSPHERE_ASSERT_THIS(); + + s64 value; + { + KScopedLightLock lk(m_lock); + value = m_limit_values[which]; + MESOSPHERE_ASSERT(value >= 0); + MESOSPHERE_ASSERT(m_current_values[which] <= m_peak_values[which]); + MESOSPHERE_ASSERT(m_peak_values[which] <= m_limit_values[which]); + MESOSPHERE_ASSERT(m_current_hints[which] <= m_current_values[which]); + } + + return value; + } + + s64 KResourceLimit::GetCurrentValue(ams::svc::LimitableResource which) const { + MESOSPHERE_ASSERT_THIS(); + + s64 value; + { + KScopedLightLock lk(m_lock); + value = m_current_values[which]; + MESOSPHERE_ASSERT(value >= 0); + MESOSPHERE_ASSERT(m_current_values[which] <= m_peak_values[which]); + MESOSPHERE_ASSERT(m_peak_values[which] <= m_limit_values[which]); + MESOSPHERE_ASSERT(m_current_hints[which] <= m_current_values[which]); + } + + return value; + } + + s64 KResourceLimit::GetPeakValue(ams::svc::LimitableResource which) const { + MESOSPHERE_ASSERT_THIS(); + + s64 value; + { + KScopedLightLock lk(m_lock); + value = m_peak_values[which]; + MESOSPHERE_ASSERT(value >= 0); + MESOSPHERE_ASSERT(m_current_values[which] <= m_peak_values[which]); + MESOSPHERE_ASSERT(m_peak_values[which] <= m_limit_values[which]); + MESOSPHERE_ASSERT(m_current_hints[which] <= m_current_values[which]); + } + + return value; + } + + s64 KResourceLimit::GetFreeValue(ams::svc::LimitableResource which) const { + MESOSPHERE_ASSERT_THIS(); + + s64 value; + { + KScopedLightLock lk(m_lock); + MESOSPHERE_ASSERT(m_current_values[which] >= 0); + MESOSPHERE_ASSERT(m_current_values[which] <= m_peak_values[which]); + MESOSPHERE_ASSERT(m_peak_values[which] <= m_limit_values[which]); + MESOSPHERE_ASSERT(m_current_hints[which] <= m_current_values[which]); + value = m_limit_values[which] - m_current_values[which]; + } + + return value; + } + + Result KResourceLimit::SetLimitValue(ams::svc::LimitableResource which, s64 value) { + MESOSPHERE_ASSERT_THIS(); + + KScopedLightLock lk(m_lock); + R_UNLESS(m_current_values[which] <= value, svc::ResultInvalidState()); + + m_limit_values[which] = value; + m_peak_values[which] = m_current_values[which]; + + R_SUCCEED(); + } + + void KResourceLimit::Add(ams::svc::LimitableResource which, s64 value) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KTargetSystem::IsDynamicResourceLimitsEnabled()); + + KScopedLightLock lk(m_lock); + + /* Check that this is a true increase. */ + MESOSPHERE_ABORT_UNLESS(value > 0); + + /* Check that we can perform an increase. */ + MESOSPHERE_ABORT_UNLESS(m_current_values[which] <= m_peak_values[which]); + MESOSPHERE_ABORT_UNLESS(m_peak_values[which] <= m_limit_values[which]); + MESOSPHERE_ABORT_UNLESS(m_current_hints[which] <= m_current_values[which]); + + /* Check that the increase doesn't cause an overflow. */ + const auto increased_limit = m_limit_values[which] + value; + const auto increased_current = m_current_values[which] + value; + const auto increased_hint = m_current_hints[which] + value; + MESOSPHERE_ABORT_UNLESS(m_limit_values[which] < increased_limit); + MESOSPHERE_ABORT_UNLESS(m_current_values[which] < increased_current); + MESOSPHERE_ABORT_UNLESS(m_current_hints[which] < increased_hint); + + /* Add the value. */ + m_limit_values[which] = increased_limit; + m_current_values[which] = increased_current; + m_current_hints[which] = increased_hint; + + /* Update our peak. */ + m_peak_values[which] = std::max(m_peak_values[which], increased_current); + } + + bool KResourceLimit::Reserve(ams::svc::LimitableResource which, s64 value) { + return this->Reserve(which, value, KHardwareTimer::GetTick() + DefaultTimeout); + } + + bool KResourceLimit::Reserve(ams::svc::LimitableResource which, s64 value, s64 timeout) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(value >= 0); + + KScopedLightLock lk(m_lock); + + MESOSPHERE_ASSERT(m_current_hints[which] <= m_current_values[which]); + if (m_current_hints[which] >= m_limit_values[which]) { + return false; + } + + /* Loop until we reserve or run out of time. */ + while (true) { + MESOSPHERE_ASSERT(m_current_values[which] <= m_limit_values[which]); + MESOSPHERE_ASSERT(m_current_hints[which] <= m_current_values[which]); + + /* If we would overflow, don't allow to succeed. */ + if (m_current_values[which] + value <= m_current_values[which]) { + break; + } + + if (m_current_values[which] + value <= m_limit_values[which]) { + m_current_values[which] += value; + m_current_hints[which] += value; + m_peak_values[which] = std::max(m_peak_values[which], m_current_values[which]); + return true; + } + + if (m_current_hints[which] + value <= m_limit_values[which] && (timeout < 0 || KHardwareTimer::GetTick() < timeout)) { + m_waiter_count++; + m_cond_var.Wait(std::addressof(m_lock), timeout, false); + m_waiter_count--; + + if (GetCurrentThread().IsTerminationRequested()) { + return false; + } + } else { + break; + } + } + + return false; + } + + void KResourceLimit::Release(ams::svc::LimitableResource which, s64 value) { + this->Release(which, value, value); + } + + void KResourceLimit::Release(ams::svc::LimitableResource which, s64 value, s64 hint) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(value >= 0); + MESOSPHERE_ASSERT(hint >= 0); + + KScopedLightLock lk(m_lock); + MESOSPHERE_ASSERT(m_current_values[which] <= m_limit_values[which]); + MESOSPHERE_ASSERT(m_current_hints[which] <= m_current_values[which]); + MESOSPHERE_ASSERT(value <= m_current_values[which]); + MESOSPHERE_ASSERT(hint <= m_current_hints[which]); + + m_current_values[which] -= value; + m_current_hints[which] -= hint; + + if (m_waiter_count != 0) { + m_cond_var.Broadcast(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_scheduler.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_scheduler.cpp new file mode 100644 index 00000000..34fc4aea --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_scheduler.cpp @@ -0,0 +1,605 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +#pragma GCC push_options +#pragma GCC optimize ("-O3") + +namespace ams::kern { + + bool KScheduler::s_scheduler_update_needed; + KScheduler::LockType KScheduler::s_scheduler_lock; + KSchedulerPriorityQueue KScheduler::s_priority_queue; + + namespace { + + class KSchedulerInterruptHandler : public KInterruptHandler { + public: + constexpr KSchedulerInterruptHandler() : KInterruptHandler() { /* ... */ } + + virtual KInterruptTask *OnInterrupt(s32 interrupt_id) override { + MESOSPHERE_UNUSED(interrupt_id); + return GetDummyInterruptTask(); + } + }; + + ALWAYS_INLINE void IncrementScheduledCount(KThread *thread) { + if (KProcess *parent = thread->GetOwnerProcess(); parent != nullptr) { + parent->IncrementScheduledCount(); + } + } + + KSchedulerInterruptHandler g_scheduler_interrupt_handler; + + ALWAYS_INLINE auto *GetSchedulerInterruptHandler() { + return std::addressof(g_scheduler_interrupt_handler); + } + + } + + void KScheduler::Initialize(KThread *idle_thread) { + /* Set core ID/idle thread/interrupt task manager. */ + m_core_id = GetCurrentCoreId(); + m_idle_thread = idle_thread; + m_state.idle_thread_stack = m_idle_thread->GetStackTop(); + m_state.interrupt_task_manager = std::addressof(Kernel::GetInterruptTaskManager()); + + /* Insert the main thread into the priority queue. */ + { + KScopedSchedulerLock lk; + GetPriorityQueue().PushBack(GetCurrentThreadPointer()); + SetSchedulerUpdateNeeded(); + } + + /* Bind interrupt handler. */ + Kernel::GetInterruptManager().BindHandler(GetSchedulerInterruptHandler(), KInterruptName_Scheduler, m_core_id, KInterruptController::PriorityLevel_Scheduler, false, false); + + /* Set the current thread. */ + m_current_thread = GetCurrentThreadPointer(); + } + + void KScheduler::Activate() { + MESOSPHERE_ASSERT(GetCurrentThread().GetDisableDispatchCount() == 1); + + m_state.should_count_idle = KTargetSystem::IsDebugMode(); + m_is_active = true; + RescheduleCurrentCore(); + } + + u64 KScheduler::UpdateHighestPriorityThread(KThread *highest_thread) { + if (KThread *prev_highest_thread = m_state.highest_priority_thread; AMS_LIKELY(prev_highest_thread != highest_thread)) { + if (AMS_LIKELY(prev_highest_thread != nullptr)) { + IncrementScheduledCount(prev_highest_thread); + prev_highest_thread->SetLastScheduledTick(KHardwareTimer::GetTick()); + } + if (m_state.should_count_idle) { + if (AMS_LIKELY(highest_thread != nullptr)) { + if (KProcess *process = highest_thread->GetOwnerProcess(); process != nullptr) { + /* Set running thread (and increment switch count). */ + process->SetRunningThread(m_core_id, highest_thread, m_state.idle_count, ++m_state.switch_count); + } + } else { + /* Set idle count and switch count to switch count + 1. */ + m_state.idle_count = ++m_state.switch_count; + } + } + + MESOSPHERE_KTRACE_SCHEDULE_UPDATE(m_core_id, (prev_highest_thread != nullptr ? prev_highest_thread : m_idle_thread), (highest_thread != nullptr ? highest_thread : m_idle_thread)); + + m_state.highest_priority_thread = highest_thread; + m_state.needs_scheduling = true; + return (1ul << m_core_id); + } else { + return 0; + } + } + + u64 KScheduler::UpdateHighestPriorityThreadsImpl() { + MESOSPHERE_ASSERT(IsSchedulerLockedByCurrentThread()); + + /* Clear that we need to update. */ + ClearSchedulerUpdateNeeded(); + + u64 cores_needing_scheduling = 0, idle_cores = 0; + KThread *top_threads[cpu::NumCores]; + auto &priority_queue = GetPriorityQueue(); + + /* We want to go over all cores, finding the highest priority thread and determining if scheduling is needed for that core. */ + for (size_t core_id = 0; core_id < cpu::NumCores; core_id++) { + KThread *top_thread = priority_queue.GetScheduledFront(core_id); + if (top_thread != nullptr) { + /* We need to check if the thread's process has a pinned thread. */ + if (KProcess *parent = top_thread->GetOwnerProcess(); parent != nullptr) { + /* Check that there's a pinned thread other than the current top thread. */ + if (KThread *pinned = parent->GetPinnedThread(core_id); pinned != nullptr && pinned != top_thread) { + /* We need to prefer threads with kernel waiters to the pinned thread. */ + if (top_thread->GetNumKernelWaiters() == 0 && top_thread != parent->GetExceptionThread()) { + /* If the pinned thread is runnable, use it. */ + if (pinned->GetRawState() == KThread::ThreadState_Runnable) { + top_thread = pinned; + } else { + top_thread = nullptr; + } + } + } + } + } else { + idle_cores |= (1ul << core_id); + } + + top_threads[core_id] = top_thread; + cores_needing_scheduling |= Kernel::GetScheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]); + } + + /* Idle cores are bad. We're going to try to migrate threads to each idle core in turn. */ + while (idle_cores != 0) { + s32 core_id = __builtin_ctzll(idle_cores); + + if (KThread *suggested = priority_queue.GetSuggestedFront(core_id); suggested != nullptr) { + s32 migration_candidates[cpu::NumCores]; + size_t num_candidates = 0; + + /* While we have a suggested thread, try to migrate it! */ + while (suggested != nullptr) { + /* Check if the suggested thread is the top thread on its core. */ + const s32 suggested_core = suggested->GetActiveCore(); + if (KThread *top_thread = (suggested_core >= 0) ? top_threads[suggested_core] : nullptr; top_thread != suggested) { + /* Make sure we're not dealing with threads too high priority for migration. */ + if (top_thread != nullptr && top_thread->GetPriority() < HighestCoreMigrationAllowedPriority) { + break; + } + + /* The suggested thread isn't bound to its core, so we can migrate it! */ + suggested->SetActiveCore(core_id); + priority_queue.ChangeCore(suggested_core, suggested); + MESOSPHERE_KTRACE_CORE_MIGRATION(suggested->GetId(), suggested_core, core_id, 1); + top_threads[core_id] = suggested; + cores_needing_scheduling |= Kernel::GetScheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]); + break; + } + + /* Note this core as a candidate for migration. */ + MESOSPHERE_ASSERT(num_candidates < cpu::NumCores); + migration_candidates[num_candidates++] = suggested_core; + suggested = priority_queue.GetSuggestedNext(core_id, suggested); + } + + /* If suggested is nullptr, we failed to migrate a specific thread. So let's try all our candidate cores' top threads. */ + if (suggested == nullptr) { + for (size_t i = 0; i < num_candidates; i++) { + /* Check if there's some other thread that can run on the candidate core. */ + const s32 candidate_core = migration_candidates[i]; + suggested = top_threads[candidate_core]; + if (KThread *next_on_candidate_core = priority_queue.GetScheduledNext(candidate_core, suggested); next_on_candidate_core != nullptr) { + /* The candidate core can run some other thread! We'll migrate its current top thread to us. */ + top_threads[candidate_core] = next_on_candidate_core; + cores_needing_scheduling |= Kernel::GetScheduler(candidate_core).UpdateHighestPriorityThread(top_threads[candidate_core]); + + /* Perform the migration. */ + suggested->SetActiveCore(core_id); + priority_queue.ChangeCore(candidate_core, suggested); + MESOSPHERE_KTRACE_CORE_MIGRATION(suggested->GetId(), candidate_core, core_id, 2); + top_threads[core_id] = suggested; + cores_needing_scheduling |= Kernel::GetScheduler(core_id).UpdateHighestPriorityThread(top_threads[core_id]); + break; + } + } + } + } + + idle_cores &= ~(1ul << core_id); + } + + return cores_needing_scheduling; + } + + void KScheduler::SwitchThread(KThread *next_thread) { + KProcess * const cur_process = GetCurrentProcessPointer(); + KThread * const cur_thread = GetCurrentThreadPointer(); + + /* We never want to schedule a null thread, so use the idle thread if we don't have a next. */ + if (next_thread == nullptr) { + next_thread = m_idle_thread; + } + + if (next_thread->GetCurrentCore() != m_core_id) { + next_thread->SetCurrentCore(m_core_id); + } + + /* If we're not actually switching thread, there's nothing to do. */ + if (next_thread == cur_thread) { + return; + } + + /* Next thread is now known not to be nullptr, and must not be dispatchable. */ + MESOSPHERE_ASSERT(next_thread->GetDisableDispatchCount() == 1); + + /* Update the CPU time tracking variables. */ + const s64 prev_tick = m_last_context_switch_time; + const s64 cur_tick = KHardwareTimer::GetTick(); + const s64 tick_diff = cur_tick - prev_tick; + cur_thread->AddCpuTime(m_core_id, tick_diff); + if (cur_process != nullptr) { + cur_process->AddCpuTime(tick_diff); + } + m_last_context_switch_time = cur_tick; + + /* Update our previous thread. */ + if (cur_process != nullptr) { + /* NOTE: Combining this into AMS_LIKELY(!... && ...) triggers an internal compiler error: Segmentation fault in GCC 9.2.0. */ + if (AMS_LIKELY(!cur_thread->IsTerminationRequested()) && AMS_LIKELY(cur_thread->GetActiveCore() == m_core_id)) { + m_state.prev_thread = cur_thread; + } else { + m_state.prev_thread = nullptr; + } + } + + MESOSPHERE_KTRACE_THREAD_SWITCH(next_thread); + + #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) + /* Ensure the single-step bit in mdscr reflects the correct single-step state for the new thread. */ + /* NOTE: Per ARM docs, changing the single-step bit requires a "context synchronization event" to */ + /* be sure that our new configuration takes. However, there are three types of synchronization event: */ + /* Taking an exception, returning from an exception, and ISB. The single-step bit change only matters */ + /* in EL0...which implies a return-from-exception has occurred since we set the bit. Thus, forcing */ + /* an ISB is unnecessary, and we can modify the register safely and be confident it will affect the next */ + /* userland instruction executed. */ + cpu::MonitorDebugSystemControlRegisterAccessor().SetSoftwareStep(next_thread->IsHardwareSingleStep()).Store(); + #endif + + /* Switch the current process, if we're switching processes. */ + if (KProcess *next_process = next_thread->GetOwnerProcess(); next_process != cur_process) { + KProcess::Switch(cur_process, next_process); + } + + /* Set the new thread. */ + SetCurrentThread(next_thread); + m_current_thread = next_thread; + + /* Set the new Thread Local region. */ + cpu::SwitchThreadLocalRegion(GetInteger(next_thread->GetThreadLocalRegionAddress())); + } + + void KScheduler::ClearPreviousThread(KThread *thread) { + MESOSPHERE_ASSERT(IsSchedulerLockedByCurrentThread()); + for (size_t i = 0; i < cpu::NumCores; ++i) { + /* Get an atomic reference to the core scheduler's previous thread. */ + const util::AtomicRef<KThread *> prev_thread(Kernel::GetScheduler(static_cast<s32>(i)).m_state.prev_thread); + + /* Atomically clear the previous thread if it's our target. */ + KThread *compare = thread; + prev_thread.CompareExchangeStrong(compare, nullptr); + } + } + + void KScheduler::OnThreadStateChanged(KThread *thread, KThread::ThreadState old_state) { + MESOSPHERE_ASSERT(IsSchedulerLockedByCurrentThread()); + + /* Check if the state has changed, because if it hasn't there's nothing to do. */ + const KThread::ThreadState cur_state = thread->GetRawState(); + if (cur_state == old_state) { + return; + } + + /* Update the priority queues. */ + if (old_state == KThread::ThreadState_Runnable) { + /* If we were previously runnable, then we're not runnable now, and we should remove. */ + GetPriorityQueue().Remove(thread); + IncrementScheduledCount(thread); + SetSchedulerUpdateNeeded(); + } else if (cur_state == KThread::ThreadState_Runnable) { + /* If we're now runnable, then we weren't previously, and we should add. */ + GetPriorityQueue().PushBack(thread); + IncrementScheduledCount(thread); + SetSchedulerUpdateNeeded(); + } + } + + void KScheduler::OnThreadPriorityChanged(KThread *thread, s32 old_priority) { + MESOSPHERE_ASSERT(IsSchedulerLockedByCurrentThread()); + + /* If the thread is runnable, we want to change its priority in the queue. */ + if (thread->GetRawState() == KThread::ThreadState_Runnable) { + GetPriorityQueue().ChangePriority(old_priority, thread == GetCurrentThreadPointer(), thread); + IncrementScheduledCount(thread); + SetSchedulerUpdateNeeded(); + } + } + + void KScheduler::OnThreadAffinityMaskChanged(KThread *thread, const KAffinityMask &old_affinity, s32 old_core) { + MESOSPHERE_ASSERT(IsSchedulerLockedByCurrentThread()); + + /* If the thread is runnable, we want to change its affinity in the queue. */ + if (thread->GetRawState() == KThread::ThreadState_Runnable) { + GetPriorityQueue().ChangeAffinityMask(old_core, old_affinity, thread); + IncrementScheduledCount(thread); + SetSchedulerUpdateNeeded(); + } + } + + void KScheduler::RotateScheduledQueue(s32 core_id, s32 priority) { + MESOSPHERE_ASSERT(IsSchedulerLockedByCurrentThread()); + + /* Get a reference to the priority queue. */ + auto &priority_queue = GetPriorityQueue(); + + /* Rotate the front of the queue to the end. */ + KThread *top_thread = priority_queue.GetScheduledFront(core_id, priority); + KThread *next_thread = nullptr; + if (top_thread != nullptr) { + next_thread = priority_queue.MoveToScheduledBack(top_thread); + if (next_thread != top_thread) { + IncrementScheduledCount(top_thread); + IncrementScheduledCount(next_thread); + } + } + + /* While we have a suggested thread, try to migrate it! */ + { + KThread *suggested = priority_queue.GetSuggestedFront(core_id, priority); + while (suggested != nullptr) { + /* Check if the suggested thread is the top thread on its core. */ + const s32 suggested_core = suggested->GetActiveCore(); + if (KThread *top_on_suggested_core = (suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core) : nullptr; top_on_suggested_core != suggested) { + /* If the next thread is a new thread that has been waiting longer than our suggestion, we prefer it to our suggestion. */ + if (top_thread != next_thread && next_thread != nullptr && next_thread->GetLastScheduledTick() < suggested->GetLastScheduledTick()) { + suggested = nullptr; + break; + } + + /* If we're allowed to do a migration, do one. */ + /* NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the suggestion to the front of the queue. */ + if (top_on_suggested_core == nullptr || top_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) { + suggested->SetActiveCore(core_id); + priority_queue.ChangeCore(suggested_core, suggested, true); + IncrementScheduledCount(suggested); + break; + } + } + + /* Get the next suggestion. */ + suggested = priority_queue.GetSamePriorityNext(core_id, suggested); + } + } + + /* Now that we might have migrated a thread with the same priority, check if we can do better. */ + { + KThread *best_thread = priority_queue.GetScheduledFront(core_id); + if (best_thread == GetCurrentThreadPointer()) { + best_thread = priority_queue.GetScheduledNext(core_id, best_thread); + } + + /* If the best thread we can choose has a priority the same or worse than ours, try to migrate a higher priority thread. */ + if (best_thread != nullptr && best_thread->GetPriority() >= priority) { + KThread *suggested = priority_queue.GetSuggestedFront(core_id); + while (suggested != nullptr) { + /* If the suggestion's priority is the same as ours, don't bother. */ + if (suggested->GetPriority() >= best_thread->GetPriority()) { + break; + } + + /* Check if the suggested thread is the top thread on its core. */ + const s32 suggested_core = suggested->GetActiveCore(); + if (KThread *top_on_suggested_core = (suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core) : nullptr; top_on_suggested_core != suggested) { + /* If we're allowed to do a migration, do one. */ + /* NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the suggestion to the front of the queue. */ + if (top_on_suggested_core == nullptr || top_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) { + suggested->SetActiveCore(core_id); + priority_queue.ChangeCore(suggested_core, suggested, true); + IncrementScheduledCount(suggested); + break; + } + } + + /* Get the next suggestion. */ + suggested = priority_queue.GetSuggestedNext(core_id, suggested); + } + } + } + + /* After a rotation, we need a scheduler update. */ + SetSchedulerUpdateNeeded(); + } + + void KScheduler::YieldWithoutCoreMigration() { + /* Validate preconditions. */ + MESOSPHERE_ASSERT(CanSchedule()); + MESOSPHERE_ASSERT(GetCurrentProcessPointer() != nullptr); + + /* Get the current thread and process. */ + KThread &cur_thread = GetCurrentThread(); + KProcess &cur_process = GetCurrentProcess(); + + /* If the thread's yield count matches, there's nothing for us to do. */ + if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) { + return; + } + + /* Get a reference to the priority queue. */ + auto &priority_queue = GetPriorityQueue(); + + /* Perform the yield. */ + { + KScopedSchedulerLock sl; + + const auto cur_state = cur_thread.GetRawState(); + if (cur_state == KThread::ThreadState_Runnable) { + /* Put the current thread at the back of the queue. */ + KThread *next_thread = priority_queue.MoveToScheduledBack(std::addressof(cur_thread)); + IncrementScheduledCount(std::addressof(cur_thread)); + + /* If the next thread is different, we have an update to perform. */ + if (next_thread != std::addressof(cur_thread)) { + SetSchedulerUpdateNeeded(); + } else { + /* Otherwise, set the thread's yield count so that we won't waste work until the process is scheduled again. */ + cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount()); + } + } + } + } + + void KScheduler::YieldWithCoreMigration() { + /* Validate preconditions. */ + MESOSPHERE_ASSERT(CanSchedule()); + MESOSPHERE_ASSERT(GetCurrentProcessPointer() != nullptr); + + /* Get the current thread and process. */ + KThread &cur_thread = GetCurrentThread(); + KProcess &cur_process = GetCurrentProcess(); + + /* If the thread's yield count matches, there's nothing for us to do. */ + if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) { + return; + } + + /* Get a reference to the priority queue. */ + auto &priority_queue = GetPriorityQueue(); + + /* Perform the yield. */ + { + KScopedSchedulerLock sl; + + const auto cur_state = cur_thread.GetRawState(); + if (cur_state == KThread::ThreadState_Runnable) { + /* Get the current active core. */ + const s32 core_id = cur_thread.GetActiveCore(); + + /* Put the current thread at the back of the queue. */ + KThread *next_thread = priority_queue.MoveToScheduledBack(std::addressof(cur_thread)); + IncrementScheduledCount(std::addressof(cur_thread)); + + /* While we have a suggested thread, try to migrate it! */ + bool recheck = false; + KThread *suggested = priority_queue.GetSuggestedFront(core_id); + while (suggested != nullptr) { + /* Check if the suggested thread is the thread running on its core. */ + const s32 suggested_core = suggested->GetActiveCore(); + + if (KThread *running_on_suggested_core = (suggested_core >= 0) ? Kernel::GetScheduler(suggested_core).m_state.highest_priority_thread : nullptr; running_on_suggested_core != suggested) { + /* If the current thread's priority is higher than our suggestion's we prefer the next thread to the suggestion. */ + /* We also prefer the next thread when the current thread's priority is equal to the suggestions, but the next thread has been waiting longer. */ + if ((suggested->GetPriority() > cur_thread.GetPriority()) || + (suggested->GetPriority() == cur_thread.GetPriority() && next_thread != std::addressof(cur_thread) && next_thread->GetLastScheduledTick() < suggested->GetLastScheduledTick())) + { + suggested = nullptr; + break; + } + + /* If we're allowed to do a migration, do one. */ + /* NOTE: Unlike migrations in UpdateHighestPriorityThread, this moves the suggestion to the front of the queue. */ + if (running_on_suggested_core == nullptr || running_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) { + suggested->SetActiveCore(core_id); + priority_queue.ChangeCore(suggested_core, suggested, true); + MESOSPHERE_KTRACE_CORE_MIGRATION(suggested->GetId(), suggested_core, core_id, 3); + IncrementScheduledCount(suggested); + break; + } else { + /* We couldn't perform a migration, but we should check again on a future yield. */ + recheck = true; + } + } + + /* Get the next suggestion. */ + suggested = priority_queue.GetSuggestedNext(core_id, suggested); + } + + + /* If we still have a suggestion or the next thread is different, we have an update to perform. */ + if (suggested != nullptr || next_thread != std::addressof(cur_thread)) { + SetSchedulerUpdateNeeded(); + } else if (!recheck) { + /* Otherwise if we don't need to re-check, set the thread's yield count so that we won't waste work until the process is scheduled again. */ + cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount()); + } + } + } + } + + void KScheduler::YieldToAnyThread() { + /* Validate preconditions. */ + MESOSPHERE_ASSERT(CanSchedule()); + MESOSPHERE_ASSERT(GetCurrentProcessPointer() != nullptr); + + /* Get the current thread and process. */ + KThread &cur_thread = GetCurrentThread(); + KProcess &cur_process = GetCurrentProcess(); + + /* If the thread's yield count matches, there's nothing for us to do. */ + if (cur_thread.GetYieldScheduleCount() == cur_process.GetScheduledCount()) { + return; + } + + /* Get a reference to the priority queue. */ + auto &priority_queue = GetPriorityQueue(); + + /* Perform the yield. */ + { + KScopedSchedulerLock sl; + + const auto cur_state = cur_thread.GetRawState(); + if (cur_state == KThread::ThreadState_Runnable) { + /* Get the current active core. */ + const s32 core_id = cur_thread.GetActiveCore(); + + /* Migrate the current thread to core -1. */ + cur_thread.SetActiveCore(-1); + priority_queue.ChangeCore(core_id, std::addressof(cur_thread)); + MESOSPHERE_KTRACE_CORE_MIGRATION(cur_thread.GetId(), core_id, -1, 4); + IncrementScheduledCount(std::addressof(cur_thread)); + + /* If there's nothing scheduled, we can try to perform a migration. */ + if (priority_queue.GetScheduledFront(core_id) == nullptr) { + /* While we have a suggested thread, try to migrate it! */ + KThread *suggested = priority_queue.GetSuggestedFront(core_id); + while (suggested != nullptr) { + /* Check if the suggested thread is the top thread on its core. */ + const s32 suggested_core = suggested->GetActiveCore(); + if (KThread *top_on_suggested_core = (suggested_core >= 0) ? priority_queue.GetScheduledFront(suggested_core) : nullptr; top_on_suggested_core != suggested) { + /* If we're allowed to do a migration, do one. */ + if (top_on_suggested_core == nullptr || top_on_suggested_core->GetPriority() >= HighestCoreMigrationAllowedPriority) { + suggested->SetActiveCore(core_id); + priority_queue.ChangeCore(suggested_core, suggested); + MESOSPHERE_KTRACE_CORE_MIGRATION(suggested->GetId(), suggested_core, core_id, 5); + IncrementScheduledCount(suggested); + } + + /* Regardless of whether we migrated, we had a candidate, so we're done. */ + break; + } + + /* Get the next suggestion. */ + suggested = priority_queue.GetSuggestedNext(core_id, suggested); + } + + /* If the suggestion is different from the current thread, we need to perform an update. */ + if (suggested != std::addressof(cur_thread)) { + SetSchedulerUpdateNeeded(); + } else { + /* Otherwise, set the thread's yield count so that we won't waste work until the process is scheduled again. */ + cur_thread.SetYieldScheduleCount(cur_process.GetScheduledCount()); + } + } else { + /* Otherwise, we have an update to perform. */ + SetSchedulerUpdateNeeded(); + } + } + } + } + +} + +#pragma GCC pop_options diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_scoped_disable_dispatch.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_scoped_disable_dispatch.cpp new file mode 100644 index 00000000..eb673d91 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_scoped_disable_dispatch.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + KScopedDisableDispatch::~KScopedDisableDispatch() { + if (GetCurrentThread().GetDisableDispatchCount() <= 1) { + Kernel::GetScheduler().RescheduleCurrentCore(); + } else { + GetCurrentThread().EnableDispatch(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_server_port.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_server_port.cpp new file mode 100644 index 00000000..82223bfe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_server_port.cpp @@ -0,0 +1,156 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + void KServerPort::Initialize(KPort *parent) { + /* Set member variables. */ + m_parent = parent; + } + + bool KServerPort::IsLight() const { + return this->GetParent()->IsLight(); + } + + void KServerPort::CleanupSessions() { + /* Ensure our preconditions are met. */ + if (this->IsLight()) { + MESOSPHERE_ASSERT(m_session_list.empty()); + } else { + MESOSPHERE_ASSERT(m_light_session_list.empty()); + } + + /* Cleanup the session list. */ + while (true) { + /* Get the last session in the list. */ + KServerSession *session = nullptr; + { + KScopedSchedulerLock sl; + if (!m_session_list.empty()) { + session = std::addressof(m_session_list.front()); + m_session_list.pop_front(); + } + } + + /* Close the session. */ + if (session != nullptr) { + session->Close(); + } else { + break; + } + } + + /* Cleanup the light session list. */ + while (true) { + /* Get the last session in the list. */ + KLightServerSession *session = nullptr; + { + KScopedSchedulerLock sl; + if (!m_light_session_list.empty()) { + session = std::addressof(m_light_session_list.front()); + m_light_session_list.pop_front(); + } + } + + /* Close the session. */ + if (session != nullptr) { + session->Close(); + } else { + break; + } + } + } + + void KServerPort::Destroy() { + /* Note with our parent that we're closed. */ + m_parent->OnServerClosed(); + + /* Perform necessary cleanup of our session lists. */ + this->CleanupSessions(); + + /* Close our reference to our parent. */ + m_parent->Close(); + } + + bool KServerPort::IsSignaled() const { + MESOSPHERE_ASSERT_THIS(); + if (this->IsLight()) { + return !m_light_session_list.empty(); + } else { + return !m_session_list.empty(); + } + } + + void KServerPort::EnqueueSession(KServerSession *session) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(!this->IsLight()); + + KScopedSchedulerLock sl; + + /* Add the session to our queue. */ + m_session_list.push_back(*session); + if (m_session_list.size() == 1) { + this->NotifyAvailable(); + } + } + + void KServerPort::EnqueueSession(KLightServerSession *session) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(this->IsLight()); + + KScopedSchedulerLock sl; + + /* Add the session to our queue. */ + m_light_session_list.push_back(*session); + if (m_light_session_list.size() == 1) { + this->NotifyAvailable(); + } + } + + KServerSession *KServerPort::AcceptSession() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(!this->IsLight()); + + KScopedSchedulerLock sl; + + /* Return the first session in the list. */ + if (m_session_list.empty()) { + return nullptr; + } + + KServerSession *session = std::addressof(m_session_list.front()); + m_session_list.pop_front(); + return session; + } + + KLightServerSession *KServerPort::AcceptLightSession() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(this->IsLight()); + + KScopedSchedulerLock sl; + + /* Return the first session in the list. */ + if (m_light_session_list.empty()) { + return nullptr; + } + + KLightServerSession *session = std::addressof(m_light_session_list.front()); + m_light_session_list.pop_front(); + return session; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_server_session.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_server_session.cpp new file mode 100644 index 00000000..7c8c4e91 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_server_session.cpp @@ -0,0 +1,1431 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +#pragma GCC push_options +#pragma GCC optimize ("-O3") + +namespace ams::kern { + + namespace ipc { + + using MessageBuffer = ams::svc::ipc::MessageBuffer; + + } + + namespace { + + constexpr inline size_t PointerTransferBufferAlignment = 0x10; + + class ThreadQueueImplForKServerSessionRequest final : public KThreadQueue { /* ... */ }; + + class ReceiveList { + private: + u32 m_data[ipc::MessageBuffer::MessageHeader::ReceiveListCountType_CountMax * ipc::MessageBuffer::ReceiveListEntry::GetDataSize() / sizeof(u32)]; + s32 m_recv_list_count; + uintptr_t m_msg_buffer_end; + uintptr_t m_msg_buffer_space_end; + public: + static constexpr ALWAYS_INLINE int GetEntryCount(const ipc::MessageBuffer::MessageHeader &header) { + const auto count = header.GetReceiveListCount(); + switch (count) { + case ipc::MessageBuffer::MessageHeader::ReceiveListCountType_None: + return 0; + case ipc::MessageBuffer::MessageHeader::ReceiveListCountType_ToMessageBuffer: + return 0; + case ipc::MessageBuffer::MessageHeader::ReceiveListCountType_ToSingleBuffer: + return 1; + default: + return count - ipc::MessageBuffer::MessageHeader::ReceiveListCountType_CountOffset; + } + } + public: + ReceiveList(const u32 *dst_msg, uintptr_t dst_address, const KProcessPageTable &dst_page_table, const ipc::MessageBuffer::MessageHeader &dst_header, const ipc::MessageBuffer::SpecialHeader &dst_special_header, size_t msg_size, size_t out_offset, s32 dst_recv_list_idx, bool is_tls) { + m_recv_list_count = dst_header.GetReceiveListCount(); + m_msg_buffer_end = dst_address + sizeof(u32) * out_offset; + m_msg_buffer_space_end = dst_address + msg_size; + + /* NOTE: Nintendo calculates the receive list index here using the special header. */ + /* We pre-calculate it in the caller, and pass it as a parameter. */ + MESOSPHERE_UNUSED(dst_special_header); + + const u32 *recv_list = dst_msg + dst_recv_list_idx; + const auto entry_count = GetEntryCount(dst_header); + + if (is_tls) { + __builtin_memcpy(m_data, recv_list, entry_count * ipc::MessageBuffer::ReceiveListEntry::GetDataSize()); + } else { + uintptr_t page_addr = util::AlignDown(dst_address, PageSize); + uintptr_t cur_addr = dst_address + dst_recv_list_idx * sizeof(u32); + for (size_t i = 0; i < entry_count * ipc::MessageBuffer::ReceiveListEntry::GetDataSize() / sizeof(u32); ++i) { + if (page_addr != util::AlignDown(cur_addr, PageSize)) { + KPhysicalAddress phys_addr; + dst_page_table.GetPhysicalAddress(std::addressof(phys_addr), KProcessAddress(cur_addr)); + + recv_list = GetPointer<u32>(KPageTable::GetHeapVirtualAddress(phys_addr)); + page_addr = util::AlignDown(cur_addr, PageSize); + } + m_data[i] = *(recv_list++); + cur_addr += sizeof(u32); + } + } + } + + constexpr ALWAYS_INLINE bool IsIndex() const { + return m_recv_list_count > ipc::MessageBuffer::MessageHeader::ReceiveListCountType_CountOffset; + } + + constexpr ALWAYS_INLINE bool IsToMessageBuffer() const { + return m_recv_list_count == ipc::MessageBuffer::MessageHeader::ReceiveListCountType_ToMessageBuffer; + } + + void GetBuffer(uintptr_t &out, size_t size, int &key) const { + switch (m_recv_list_count) { + case ipc::MessageBuffer::MessageHeader::ReceiveListCountType_None: + { + out = 0; + } + break; + case ipc::MessageBuffer::MessageHeader::ReceiveListCountType_ToMessageBuffer: + { + const uintptr_t buf = util::AlignUp(m_msg_buffer_end + key, PointerTransferBufferAlignment); + + if ((buf < buf + size) && (buf + size <= m_msg_buffer_space_end)) { + out = buf; + key = buf + size - m_msg_buffer_end; + } else { + out = 0; + } + } + break; + case ipc::MessageBuffer::MessageHeader::ReceiveListCountType_ToSingleBuffer: + { + const ipc::MessageBuffer::ReceiveListEntry entry(m_data[0], m_data[1]); + const uintptr_t buf = util::AlignUp(entry.GetAddress() + key, PointerTransferBufferAlignment); + + const uintptr_t entry_addr = entry.GetAddress(); + const size_t entry_size = entry.GetSize(); + + if ((buf < buf + size) && (entry_addr < entry_addr + entry_size) && (buf + size <= entry_addr + entry_size)) { + out = buf; + key = buf + size - entry_addr; + } else { + out = 0; + } + } + break; + default: + { + if (key < m_recv_list_count - ipc::MessageBuffer::MessageHeader::ReceiveListCountType_CountOffset) { + const ipc::MessageBuffer::ReceiveListEntry entry(m_data[2 * key + 0], m_data[2 * key + 1]); + + const uintptr_t entry_addr = entry.GetAddress(); + const size_t entry_size = entry.GetSize(); + + if ((entry_addr < entry_addr + entry_size) && (entry_size >= size)) { + out = entry_addr; + } + } else { + out = 0; + } + } + break; + } + } + }; + + template<bool MoveHandleAllowed> + ALWAYS_INLINE Result ProcessMessageSpecialData(int &offset, KProcess &dst_process, KProcess &src_process, KThread &src_thread, const ipc::MessageBuffer &dst_msg, const ipc::MessageBuffer &src_msg, const ipc::MessageBuffer::SpecialHeader &src_special_header) { + /* Copy the special header to the destination. */ + offset = dst_msg.Set(src_special_header); + + /* Copy the process ID. */ + if (src_special_header.GetHasProcessId()) { + /* NOTE: Atmosphere extends the official kernel here to enable the mitm api. */ + /* If building the kernel without this support, just set the following to false. */ + constexpr bool EnableProcessIdPassthroughForAtmosphere = true; + + if constexpr (EnableProcessIdPassthroughForAtmosphere) { + constexpr u64 PassthroughProcessIdMask = UINT64_C(0xFFFF000000000000); + constexpr u64 PassthroughProcessIdValue = UINT64_C(0xFFFE000000000000); + static_assert((PassthroughProcessIdMask & PassthroughProcessIdValue) == PassthroughProcessIdValue); + + const u64 src_process_id_value = src_msg.GetProcessId(offset); + const bool is_passthrough = (src_process_id_value & PassthroughProcessIdMask) == PassthroughProcessIdValue; + + offset = dst_msg.SetProcessId(offset, is_passthrough ? (src_process_id_value & ~PassthroughProcessIdMask) : src_process.GetId()); + } else { + offset = dst_msg.SetProcessId(offset, src_process.GetId()); + } + } + + /* Prepare to process handles. */ + auto &dst_handle_table = dst_process.GetHandleTable(); + auto &src_handle_table = src_process.GetHandleTable(); + Result result = ResultSuccess(); + + /* Process copy handles. */ + for (auto i = 0; i < src_special_header.GetCopyHandleCount(); ++i) { + /* Get the handles. */ + const ams::svc::Handle src_handle = src_msg.GetHandle(offset); + ams::svc::Handle dst_handle = ams::svc::InvalidHandle; + + /* If we're in a success state, try to move the handle to the new table. */ + if (R_SUCCEEDED(result) && src_handle != ams::svc::InvalidHandle) { + KScopedAutoObject obj = src_handle_table.GetObjectForIpc(src_handle, std::addressof(src_thread)); + if (obj.IsNotNull()) { + Result add_result = dst_handle_table.Add(std::addressof(dst_handle), obj.GetPointerUnsafe()); + if (R_FAILED(add_result)) { + result = add_result; + dst_handle = ams::svc::InvalidHandle; + } + } else { + result = svc::ResultInvalidHandle(); + } + } + + /* Set the handle. */ + offset = dst_msg.SetHandle(offset, dst_handle); + } + + /* Process move handles. */ + if constexpr (MoveHandleAllowed) { + for (auto i = 0; i < src_special_header.GetMoveHandleCount(); ++i) { + /* Get the handles. */ + const ams::svc::Handle src_handle = src_msg.GetHandle(offset); + ams::svc::Handle dst_handle = ams::svc::InvalidHandle; + + /* Whether or not we've succeeded, we need to remove the handles from the source table. */ + if (src_handle != ams::svc::InvalidHandle) { + if (R_SUCCEEDED(result)) { + KScopedAutoObject obj = src_handle_table.GetObjectForIpcWithoutPseudoHandle(src_handle); + if (obj.IsNotNull()) { + Result add_result = dst_handle_table.Add(std::addressof(dst_handle), obj.GetPointerUnsafe()); + + src_handle_table.Remove(src_handle); + + if (R_FAILED(add_result)) { + result = add_result; + dst_handle = ams::svc::InvalidHandle; + } + } else { + result = svc::ResultInvalidHandle(); + } + } else { + src_handle_table.Remove(src_handle); + } + } + + /* Set the handle. */ + offset = dst_msg.SetHandle(offset, dst_handle); + } + } + + R_RETURN(result); + } + + ALWAYS_INLINE Result ProcessReceiveMessagePointerDescriptors(int &offset, int &pointer_key, KProcessPageTable &dst_page_table, KProcessPageTable &src_page_table, const ipc::MessageBuffer &dst_msg, const ipc::MessageBuffer &src_msg, const ReceiveList &dst_recv_list, bool dst_user) { + /* Get the offset at the start of processing. */ + const int cur_offset = offset; + + /* Get the pointer desc. */ + ipc::MessageBuffer::PointerDescriptor src_desc(src_msg, cur_offset); + offset += ipc::MessageBuffer::PointerDescriptor::GetDataSize() / sizeof(u32); + + /* Extract address/size. */ + const uintptr_t src_pointer = src_desc.GetAddress(); + const size_t recv_size = src_desc.GetSize(); + uintptr_t recv_pointer = 0; + + /* Process the buffer, if it has a size. */ + if (recv_size > 0) { + /* If using indexing, set index. */ + if (dst_recv_list.IsIndex()) { + pointer_key = src_desc.GetIndex(); + } + + /* Get the buffer. */ + dst_recv_list.GetBuffer(recv_pointer, recv_size, pointer_key); + R_UNLESS(recv_pointer != 0, svc::ResultOutOfResource()); + + /* Perform the pointer data copy. */ + if (dst_user) { + R_TRY(src_page_table.CopyMemoryFromHeapToHeapWithoutCheckDestination(dst_page_table, recv_pointer, recv_size, + KMemoryState_FlagReferenceCounted, KMemoryState_FlagReferenceCounted, + static_cast<KMemoryPermission>(KMemoryPermission_NotMapped | KMemoryPermission_KernelReadWrite), + KMemoryAttribute_Uncached | KMemoryAttribute_Locked, KMemoryAttribute_Locked, + src_pointer, + KMemoryState_FlagLinearMapped, KMemoryState_FlagLinearMapped, + KMemoryPermission_UserRead, + KMemoryAttribute_Uncached, KMemoryAttribute_None)); + } else { + R_TRY(src_page_table.CopyMemoryFromLinearToUser(recv_pointer, recv_size, src_pointer, + KMemoryState_FlagLinearMapped, KMemoryState_FlagLinearMapped, + KMemoryPermission_UserRead, + KMemoryAttribute_Uncached, KMemoryAttribute_None)); + } + } + + /* Set the output descriptor. */ + dst_msg.Set(cur_offset, ipc::MessageBuffer::PointerDescriptor(reinterpret_cast<void *>(recv_pointer), recv_size, src_desc.GetIndex())); + + R_SUCCEED(); + } + + constexpr ALWAYS_INLINE Result GetMapAliasMemoryState(KMemoryState &out, ipc::MessageBuffer::MapAliasDescriptor::Attribute attr) { + switch (attr) { + case ipc::MessageBuffer::MapAliasDescriptor::Attribute_Ipc: out = KMemoryState_Ipc; break; + case ipc::MessageBuffer::MapAliasDescriptor::Attribute_NonSecureIpc: out = KMemoryState_NonSecureIpc; break; + case ipc::MessageBuffer::MapAliasDescriptor::Attribute_NonDeviceIpc: out = KMemoryState_NonDeviceIpc; break; + default: R_THROW(svc::ResultInvalidCombination()); + } + + R_SUCCEED(); + } + + constexpr ALWAYS_INLINE Result GetMapAliasTestStateAndAttributeMask(u32 &out_state, u32 &out_attr_mask, KMemoryState state) { + switch (state) { + case KMemoryState_Ipc: + out_state = KMemoryState_FlagCanUseIpc; + out_attr_mask = KMemoryAttribute_Uncached | KMemoryAttribute_DeviceShared | KMemoryAttribute_Locked; + break; + case KMemoryState_NonSecureIpc: + out_state = KMemoryState_FlagCanUseNonSecureIpc; + out_attr_mask = KMemoryAttribute_Uncached | KMemoryAttribute_Locked; + break; + case KMemoryState_NonDeviceIpc: + out_state = KMemoryState_FlagCanUseNonDeviceIpc; + out_attr_mask = KMemoryAttribute_Uncached | KMemoryAttribute_Locked; + break; + default: + R_THROW(svc::ResultInvalidCombination()); + } + + R_SUCCEED(); + } + + ALWAYS_INLINE void CleanupSpecialData(KProcess &dst_process, u32 *dst_msg_ptr, size_t dst_buffer_size) { + /* Parse the message. */ + const ipc::MessageBuffer dst_msg(dst_msg_ptr, dst_buffer_size); + const ipc::MessageBuffer::MessageHeader dst_header(dst_msg); + const ipc::MessageBuffer::SpecialHeader dst_special_header(dst_msg, dst_header); + + /* Check that the size is big enough. */ + if (ipc::MessageBuffer::GetMessageBufferSize(dst_header, dst_special_header) > dst_buffer_size) { + return; + } + + /* Set the special header. */ + int offset = dst_msg.Set(dst_special_header); + + /* Clear the process id, if needed. */ + if (dst_special_header.GetHasProcessId()) { + offset = dst_msg.SetProcessId(offset, 0); + } + + /* Clear handles, as relevant. */ + auto &dst_handle_table = dst_process.GetHandleTable(); + for (auto i = 0; i < (dst_special_header.GetCopyHandleCount() + dst_special_header.GetMoveHandleCount()); ++i) { + const ams::svc::Handle handle = dst_msg.GetHandle(offset); + + if (handle != ams::svc::InvalidHandle) { + dst_handle_table.Remove(handle); + } + + offset = dst_msg.SetHandle(offset, ams::svc::InvalidHandle); + } + } + + ALWAYS_INLINE Result CleanupServerHandles(uintptr_t message, size_t buffer_size, KPhysicalAddress message_paddr) { + /* Server is assumed to be current thread. */ + const KThread &thread = GetCurrentThread(); + + /* Get the linear message pointer. */ + u32 *msg_ptr; + if (message) { + msg_ptr = GetPointer<u32>(KPageTable::GetHeapVirtualAddress(message_paddr)); + } else { + msg_ptr = static_cast<ams::svc::ThreadLocalRegion *>(thread.GetThreadLocalRegionHeapAddress())->message_buffer; + buffer_size = sizeof(ams::svc::ThreadLocalRegion{}.message_buffer); + message = GetInteger(thread.GetThreadLocalRegionAddress()); + } + + /* Parse the message. */ + const ipc::MessageBuffer msg(msg_ptr, buffer_size); + const ipc::MessageBuffer::MessageHeader header(msg); + const ipc::MessageBuffer::SpecialHeader special_header(msg, header); + + /* Check that the size is big enough. */ + R_UNLESS(ipc::MessageBuffer::GetMessageBufferSize(header, special_header) <= buffer_size, svc::ResultInvalidCombination()); + + /* If there's a special header, there may be move handles we need to close. */ + if (header.GetHasSpecialHeader()) { + /* Determine the offset to the start of handles. */ + auto offset = msg.GetSpecialDataIndex(header, special_header); + if (special_header.GetHasProcessId()) { + offset += sizeof(u64) / sizeof(u32); + } + if (auto copy_count = special_header.GetCopyHandleCount(); copy_count > 0) { + offset += (sizeof(ams::svc::Handle) * copy_count) / sizeof(u32); + } + + /* Get the handle table. */ + auto &handle_table = thread.GetOwnerProcess()->GetHandleTable(); + + /* Close the handles. */ + for (auto i = 0; i < special_header.GetMoveHandleCount(); ++i) { + handle_table.Remove(msg.GetHandle(offset)); + offset += sizeof(ams::svc::Handle) / sizeof(u32); + } + } + + R_SUCCEED(); + } + + ALWAYS_INLINE Result CleanupServerMap(KSessionRequest *request, KProcess *server_process) { + /* If there's no server process, there's nothing to clean up. */ + R_SUCCEED_IF(server_process == nullptr); + + /* Get the page table. */ + auto &server_page_table = server_process->GetPageTable(); + + /* Cleanup Send mappings. */ + for (size_t i = 0; i < request->GetSendCount(); ++i) { + R_TRY(server_page_table.CleanupForIpcServer(request->GetSendServerAddress(i), request->GetSendSize(i), request->GetSendMemoryState(i))); + } + + /* Cleanup Receive mappings. */ + for (size_t i = 0; i < request->GetReceiveCount(); ++i) { + R_TRY(server_page_table.CleanupForIpcServer(request->GetReceiveServerAddress(i), request->GetReceiveSize(i), request->GetReceiveMemoryState(i))); + } + + /* Cleanup Exchange mappings. */ + for (size_t i = 0; i < request->GetExchangeCount(); ++i) { + R_TRY(server_page_table.CleanupForIpcServer(request->GetExchangeServerAddress(i), request->GetExchangeSize(i), request->GetExchangeMemoryState(i))); + } + + R_SUCCEED(); + } + + ALWAYS_INLINE Result CleanupClientMap(KSessionRequest *request, KProcessPageTable *client_page_table) { + /* If there's no client page table, there's nothing to clean up. */ + R_SUCCEED_IF(client_page_table == nullptr); + + /* Cleanup Send mappings. */ + for (size_t i = 0; i < request->GetSendCount(); ++i) { + R_TRY(client_page_table->CleanupForIpcClient(request->GetSendClientAddress(i), request->GetSendSize(i), request->GetSendMemoryState(i))); + } + + /* Cleanup Receive mappings. */ + for (size_t i = 0; i < request->GetReceiveCount(); ++i) { + R_TRY(client_page_table->CleanupForIpcClient(request->GetReceiveClientAddress(i), request->GetReceiveSize(i), request->GetReceiveMemoryState(i))); + } + + /* Cleanup Exchange mappings. */ + for (size_t i = 0; i < request->GetExchangeCount(); ++i) { + R_TRY(client_page_table->CleanupForIpcClient(request->GetExchangeClientAddress(i), request->GetExchangeSize(i), request->GetExchangeMemoryState(i))); + } + + R_SUCCEED(); + } + + ALWAYS_INLINE Result CleanupMap(KSessionRequest *request, KProcess *server_process, KProcessPageTable *client_page_table) { + /* Cleanup the server map. */ + R_TRY(CleanupServerMap(request, server_process)); + + /* Cleanup the client map. */ + R_TRY(CleanupClientMap(request, client_page_table)); + + R_SUCCEED(); + } + + ALWAYS_INLINE Result ProcessReceiveMessageMapAliasDescriptors(int &offset, KProcessPageTable &dst_page_table, KProcessPageTable &src_page_table, const ipc::MessageBuffer &dst_msg, const ipc::MessageBuffer &src_msg, KSessionRequest *request, KMemoryPermission perm, bool send) { + /* Get the offset at the start of processing. */ + const int cur_offset = offset; + + /* Get the map alias descriptor. */ + ipc::MessageBuffer::MapAliasDescriptor src_desc(src_msg, cur_offset); + offset += ipc::MessageBuffer::MapAliasDescriptor::GetDataSize() / sizeof(u32); + + /* Extract address/size. */ + const KProcessAddress src_address = src_desc.GetAddress(); + const size_t size = src_desc.GetSize(); + KProcessAddress dst_address = 0; + + /* Determine the result memory state. */ + KMemoryState dst_state; + R_TRY(GetMapAliasMemoryState(dst_state, src_desc.GetAttribute())); + + /* Process the buffer, if it has a size. */ + if (size > 0) { + /* Set up the source pages for ipc. */ + R_TRY(dst_page_table.SetupForIpc(std::addressof(dst_address), size, src_address, src_page_table, perm, dst_state, send)); + + /* Ensure that we clean up on failure. */ + ON_RESULT_FAILURE { + dst_page_table.CleanupForIpcServer(dst_address, size, dst_state); + src_page_table.CleanupForIpcClient(src_address, size, dst_state); + }; + + /* Push the appropriate mapping. */ + if (perm == KMemoryPermission_UserRead) { + R_TRY(request->PushSend(src_address, dst_address, size, dst_state)); + } else if (send) { + R_TRY(request->PushExchange(src_address, dst_address, size, dst_state)); + } else { + R_TRY(request->PushReceive(src_address, dst_address, size, dst_state)); + } + } + + /* Set the output descriptor. */ + dst_msg.Set(cur_offset, ipc::MessageBuffer::MapAliasDescriptor(GetVoidPointer(dst_address), size, src_desc.GetAttribute())); + + R_SUCCEED(); + } + + ALWAYS_INLINE Result ReceiveMessage(bool &recv_list_broken, uintptr_t dst_message_buffer, size_t dst_buffer_size, KPhysicalAddress dst_message_paddr, KThread &src_thread, uintptr_t src_message_buffer, size_t src_buffer_size, KServerSession *session, KSessionRequest *request) { + /* Prepare variables for receive. */ + const KThread &dst_thread = GetCurrentThread(); + KProcess &dst_process = *(dst_thread.GetOwnerProcess()); + KProcess &src_process = *(src_thread.GetOwnerProcess()); + auto &dst_page_table = dst_process.GetPageTable(); + auto &src_page_table = src_process.GetPageTable(); + + /* NOTE: Session is used only for debugging, and so may go unused. */ + MESOSPHERE_UNUSED(session); + + /* The receive list is initially not broken. */ + recv_list_broken = false; + + /* Set the server process for the request. */ + request->SetServerProcess(std::addressof(dst_process)); + + /* Determine the message buffers. */ + u32 *dst_msg_ptr, *src_msg_ptr; + bool dst_user, src_user; + + if (dst_message_buffer) { + dst_msg_ptr = GetPointer<u32>(KPageTable::GetHeapVirtualAddress(dst_message_paddr)); + dst_user = true; + } else { + dst_msg_ptr = static_cast<ams::svc::ThreadLocalRegion *>(dst_thread.GetThreadLocalRegionHeapAddress())->message_buffer; + dst_buffer_size = sizeof(ams::svc::ThreadLocalRegion{}.message_buffer); + dst_message_buffer = GetInteger(dst_thread.GetThreadLocalRegionAddress()); + dst_user = false; + } + + if (src_message_buffer) { + /* NOTE: Nintendo does not check the result of this GetPhysicalAddress call. */ + KPhysicalAddress src_message_paddr; + src_page_table.GetPhysicalAddress(std::addressof(src_message_paddr), src_message_buffer); + + src_msg_ptr = GetPointer<u32>(KPageTable::GetHeapVirtualAddress(src_message_paddr)); + src_user = true; + } else { + src_msg_ptr = static_cast<ams::svc::ThreadLocalRegion *>(src_thread.GetThreadLocalRegionHeapAddress())->message_buffer; + src_buffer_size = sizeof(ams::svc::ThreadLocalRegion{}.message_buffer); + src_message_buffer = GetInteger(src_thread.GetThreadLocalRegionAddress()); + src_user = false; + } + + /* Parse the headers. */ + const ipc::MessageBuffer dst_msg(dst_msg_ptr, dst_buffer_size); + const ipc::MessageBuffer src_msg(src_msg_ptr, src_buffer_size); + const ipc::MessageBuffer::MessageHeader dst_header(dst_msg); + const ipc::MessageBuffer::MessageHeader src_header(src_msg); + const ipc::MessageBuffer::SpecialHeader dst_special_header(dst_msg, dst_header); + const ipc::MessageBuffer::SpecialHeader src_special_header(src_msg, src_header); + + /* Get the end of the source message. */ + const size_t src_end_offset = ipc::MessageBuffer::GetRawDataIndex(src_header, src_special_header) + src_header.GetRawCount(); + + /* Ensure that the headers fit. */ + R_UNLESS(ipc::MessageBuffer::GetMessageBufferSize(dst_header, dst_special_header) <= dst_buffer_size, svc::ResultInvalidCombination()); + R_UNLESS(ipc::MessageBuffer::GetMessageBufferSize(src_header, src_special_header) <= src_buffer_size, svc::ResultInvalidCombination()); + + /* Ensure the receive list offset is after the end of raw data. */ + if (dst_header.GetReceiveListOffset()) { + R_UNLESS(dst_header.GetReceiveListOffset() >= ipc::MessageBuffer::GetRawDataIndex(dst_header, dst_special_header) + dst_header.GetRawCount(), svc::ResultInvalidCombination()); + } + + /* Ensure that the destination buffer is big enough to receive the source. */ + R_UNLESS(dst_buffer_size >= src_end_offset * sizeof(u32), svc::ResultMessageTooLarge()); + + /* Get the receive list. */ + const s32 dst_recv_list_idx = ipc::MessageBuffer::GetReceiveListIndex(dst_header, dst_special_header); + ReceiveList dst_recv_list(dst_msg_ptr, dst_message_buffer, dst_page_table, dst_header, dst_special_header, dst_buffer_size, src_end_offset, dst_recv_list_idx, !dst_user); + + /* Ensure that the source special header isn't invalid. */ + const bool src_has_special_header = src_header.GetHasSpecialHeader(); + if (src_has_special_header) { + /* Sending move handles from client -> server is not allowed. */ + R_UNLESS(src_special_header.GetMoveHandleCount() == 0, svc::ResultInvalidCombination()); + } + + /* Prepare for further processing. */ + int pointer_key = 0; + int offset = dst_msg.Set(src_header); + + /* Set up a guard to make sure that we end up in a clean state on error. */ + ON_RESULT_FAILURE { + /* Cleanup mappings. */ + CleanupMap(request, std::addressof(dst_process), std::addressof(src_page_table)); + + /* Cleanup special data. */ + if (src_header.GetHasSpecialHeader()) { + CleanupSpecialData(dst_process, dst_msg_ptr, dst_buffer_size); + } + + /* Cleanup the header if the receive list isn't broken. */ + if (!recv_list_broken) { + dst_msg.Set(dst_header); + if (dst_header.GetHasSpecialHeader()) { + dst_msg.Set(dst_special_header); + } + } + }; + + /* Process any special data. */ + if (src_header.GetHasSpecialHeader()) { + /* After we process, make sure we track whether the receive list is broken. */ + ON_SCOPE_EXIT { if (offset > dst_recv_list_idx) { recv_list_broken = true; } }; + + /* Process special data. */ + R_TRY(ProcessMessageSpecialData<false>(offset, dst_process, src_process, src_thread, dst_msg, src_msg, src_special_header)); + } + + /* Process any pointer buffers. */ + for (auto i = 0; i < src_header.GetPointerCount(); ++i) { + /* After we process, make sure we track whether the receive list is broken. */ + ON_SCOPE_EXIT { if (offset > dst_recv_list_idx) { recv_list_broken = true; } }; + + R_TRY(ProcessReceiveMessagePointerDescriptors(offset, pointer_key, dst_page_table, src_page_table, dst_msg, src_msg, dst_recv_list, dst_user && dst_header.GetReceiveListCount() == ipc::MessageBuffer::MessageHeader::ReceiveListCountType_ToMessageBuffer)); + } + + /* Process any map alias buffers. */ + for (auto i = 0; i < src_header.GetMapAliasCount(); ++i) { + /* After we process, make sure we track whether the receive list is broken. */ + ON_SCOPE_EXIT { if (offset > dst_recv_list_idx) { recv_list_broken = true; } }; + + /* We process in order send, recv, exch. Buffers after send (recv/exch) are ReadWrite. */ + const KMemoryPermission perm = (i >= src_header.GetSendCount()) ? KMemoryPermission_UserReadWrite : KMemoryPermission_UserRead; + + /* Buffer is send if it is send or exch. */ + const bool send = (i < src_header.GetSendCount()) || (i >= src_header.GetSendCount() + src_header.GetReceiveCount()); + + R_TRY(ProcessReceiveMessageMapAliasDescriptors(offset, dst_page_table, src_page_table, dst_msg, src_msg, request, perm, send)); + } + + /* Process any raw data. */ + if (const auto raw_count = src_header.GetRawCount(); raw_count != 0) { + /* After we process, make sure we track whether the receive list is broken. */ + ON_SCOPE_EXIT { if (offset + raw_count > dst_recv_list_idx) { recv_list_broken = true; } }; + + /* Get the offset and size. */ + const size_t offset_words = offset * sizeof(u32); + const size_t raw_size = raw_count * sizeof(u32); + + /* Fast case is TLS -> TLS, do raw memcpy if we can. */ + if (!dst_user && !src_user) { + std::memcpy(dst_msg_ptr + offset, src_msg_ptr + offset, raw_size); + } else if (dst_user) { + /* Determine how much fast size we can copy. */ + const size_t max_fast_size = std::min<size_t>(offset_words + raw_size, PageSize); + const size_t fast_size = max_fast_size - offset_words; + + /* Determine source state; if user buffer, we require heap, and otherwise only linear mapped (to enable tls use). */ + const auto src_state = src_user ? KMemoryState_FlagReferenceCounted : KMemoryState_FlagLinearMapped; + + /* Determine the source permission. User buffer should be unmapped + read, TLS should be user readable. */ + const KMemoryPermission src_perm = static_cast<KMemoryPermission>(src_user ? (KMemoryPermission_NotMapped | KMemoryPermission_KernelRead) : KMemoryPermission_UserRead); + + /* Perform the fast part of the copy. */ + R_TRY(src_page_table.CopyMemoryFromLinearToKernel(reinterpret_cast<uintptr_t>(dst_msg_ptr) + offset_words, fast_size, src_message_buffer + offset_words, + src_state, src_state, + src_perm, + KMemoryAttribute_Uncached, KMemoryAttribute_None)); + + /* If the fast part of the copy didn't get everything, perform the slow part of the copy. */ + if (fast_size < raw_size) { + R_TRY(src_page_table.CopyMemoryFromHeapToHeap(dst_page_table, dst_message_buffer + max_fast_size, raw_size - fast_size, + KMemoryState_FlagReferenceCounted, KMemoryState_FlagReferenceCounted, + static_cast<KMemoryPermission>(KMemoryPermission_NotMapped | KMemoryPermission_KernelReadWrite), + KMemoryAttribute_Uncached | KMemoryAttribute_Locked, KMemoryAttribute_Locked, + src_message_buffer + max_fast_size, + src_state, src_state, + src_perm, + KMemoryAttribute_Uncached, KMemoryAttribute_None)); + } + } else /* if (src_user) */ { + /* The source is a user buffer, so it should be unmapped + readable. */ + constexpr KMemoryPermission SourcePermission = static_cast<KMemoryPermission>(KMemoryPermission_NotMapped | KMemoryPermission_KernelRead); + + /* Copy the memory. */ + R_TRY(src_page_table.CopyMemoryFromLinearToUser(dst_message_buffer + offset_words, raw_size, src_message_buffer + offset_words, + KMemoryState_FlagReferenceCounted, KMemoryState_FlagReferenceCounted, + SourcePermission, + KMemoryAttribute_Uncached, KMemoryAttribute_None)); + } + } + + /* We succeeded! */ + R_SUCCEED(); + } + + ALWAYS_INLINE Result ProcessSendMessageReceiveMapping(KProcessPageTable &dst_page_table, KProcessAddress client_address, KProcessAddress server_address, size_t size, KMemoryState src_state) { + /* If the size is zero, there's nothing to process. */ + R_SUCCEED_IF(size == 0); + + /* Get the memory state and attribute mask to test. */ + u32 test_state; + u32 test_attr_mask; + R_TRY(GetMapAliasTestStateAndAttributeMask(test_state, test_attr_mask, src_state)); + + /* Determine buffer extents. */ + KProcessAddress aligned_dst_start = util::AlignDown(GetInteger(client_address), PageSize); + KProcessAddress aligned_dst_end = util::AlignUp(GetInteger(client_address) + size, PageSize); + KProcessAddress mapping_dst_start = util::AlignUp(GetInteger(client_address), PageSize); + KProcessAddress mapping_dst_end = util::AlignDown(GetInteger(client_address) + size, PageSize); + + KProcessAddress mapping_src_end = util::AlignDown(GetInteger(server_address) + size, PageSize); + + /* If the start of the buffer is unaligned, handle that. */ + if (aligned_dst_start != mapping_dst_start) { + MESOSPHERE_ASSERT(client_address < mapping_dst_start); + const size_t copy_size = std::min<size_t>(size, mapping_dst_start - client_address); + R_TRY(dst_page_table.CopyMemoryFromUserToLinear(client_address, copy_size, + test_state, test_state, + KMemoryPermission_UserReadWrite, + test_attr_mask, KMemoryAttribute_None, + server_address)); + } + + /* If the end of the buffer is unaligned, handle that. */ + if (mapping_dst_end < aligned_dst_end && (aligned_dst_start == mapping_dst_start || aligned_dst_start < mapping_dst_end)) { + const size_t copy_size = client_address + size - mapping_dst_end; + R_TRY(dst_page_table.CopyMemoryFromUserToLinear(mapping_dst_end, copy_size, + test_state, test_state, + KMemoryPermission_UserReadWrite, + test_attr_mask, KMemoryAttribute_None, + mapping_src_end)); + } + + R_SUCCEED(); + } + + ALWAYS_INLINE Result ProcessSendMessagePointerDescriptors(int &offset, int &pointer_key, KProcessPageTable &dst_page_table, const ipc::MessageBuffer &dst_msg, const ipc::MessageBuffer &src_msg, const ReceiveList &dst_recv_list, bool dst_user) { + /* Get the offset at the start of processing. */ + const int cur_offset = offset; + + /* Get the pointer desc. */ + ipc::MessageBuffer::PointerDescriptor src_desc(src_msg, cur_offset); + offset += ipc::MessageBuffer::PointerDescriptor::GetDataSize() / sizeof(u32); + + /* Extract address/size. */ + const uintptr_t src_pointer = src_desc.GetAddress(); + const size_t recv_size = src_desc.GetSize(); + uintptr_t recv_pointer = 0; + + /* Process the buffer, if it has a size. */ + if (recv_size > 0) { + /* If using indexing, set index. */ + if (dst_recv_list.IsIndex()) { + pointer_key = src_desc.GetIndex(); + } + + /* Get the buffer. */ + dst_recv_list.GetBuffer(recv_pointer, recv_size, pointer_key); + R_UNLESS(recv_pointer != 0, svc::ResultOutOfResource()); + + /* Perform the pointer data copy. */ + const bool dst_heap = dst_user && dst_recv_list.IsToMessageBuffer(); + const auto dst_state = dst_heap ? KMemoryState_FlagReferenceCounted : KMemoryState_FlagLinearMapped; + const KMemoryPermission dst_perm = static_cast<KMemoryPermission>(dst_heap ? (KMemoryPermission_NotMapped | KMemoryPermission_KernelReadWrite) : KMemoryPermission_UserReadWrite); + R_TRY(dst_page_table.CopyMemoryFromUserToLinear(recv_pointer, recv_size, + dst_state, dst_state, + dst_perm, + KMemoryAttribute_Uncached, KMemoryAttribute_None, + src_pointer)); + } + + /* Set the output descriptor. */ + dst_msg.Set(cur_offset, ipc::MessageBuffer::PointerDescriptor(reinterpret_cast<void *>(recv_pointer), recv_size, src_desc.GetIndex())); + + R_SUCCEED(); + } + + ALWAYS_INLINE Result SendMessage(uintptr_t src_message_buffer, size_t src_buffer_size, KPhysicalAddress src_message_paddr, KThread &dst_thread, uintptr_t dst_message_buffer, size_t dst_buffer_size, KServerSession *session, KSessionRequest *request) { + /* Prepare variables for send. */ + KThread &src_thread = GetCurrentThread(); + KProcess &dst_process = *(dst_thread.GetOwnerProcess()); + KProcess &src_process = *(src_thread.GetOwnerProcess()); + auto &dst_page_table = dst_process.GetPageTable(); + auto &src_page_table = src_process.GetPageTable(); + + /* NOTE: Session is used only for debugging, and so may go unused. */ + MESOSPHERE_UNUSED(session); + + /* NOTE: Source page table is not used, and so may go unused. */ + MESOSPHERE_UNUSED(src_page_table); + + /* Determine the message buffers. */ + u32 *dst_msg_ptr, *src_msg_ptr; + bool dst_user, src_user; + + if (dst_message_buffer) { + /* NOTE: Nintendo does not check the result of this GetPhysicalAddress call. */ + KPhysicalAddress dst_message_paddr; + dst_page_table.GetPhysicalAddress(std::addressof(dst_message_paddr), dst_message_buffer); + + dst_msg_ptr = GetPointer<u32>(KPageTable::GetHeapVirtualAddress(dst_message_paddr)); + dst_user = true; + } else { + dst_msg_ptr = static_cast<ams::svc::ThreadLocalRegion *>(dst_thread.GetThreadLocalRegionHeapAddress())->message_buffer; + dst_buffer_size = sizeof(ams::svc::ThreadLocalRegion{}.message_buffer); + dst_message_buffer = GetInteger(dst_thread.GetThreadLocalRegionAddress()); + dst_user = false; + } + + if (src_message_buffer) { + src_msg_ptr = GetPointer<u32>(KPageTable::GetHeapVirtualAddress(src_message_paddr)); + src_user = true; + } else { + src_msg_ptr = static_cast<ams::svc::ThreadLocalRegion *>(src_thread.GetThreadLocalRegionHeapAddress())->message_buffer; + src_buffer_size = sizeof(ams::svc::ThreadLocalRegion{}.message_buffer); + src_message_buffer = GetInteger(src_thread.GetThreadLocalRegionAddress()); + src_user = false; + } + + /* Parse the headers. */ + const ipc::MessageBuffer dst_msg(dst_msg_ptr, dst_buffer_size); + const ipc::MessageBuffer src_msg(src_msg_ptr, src_buffer_size); + const ipc::MessageBuffer::MessageHeader dst_header(dst_msg); + const ipc::MessageBuffer::MessageHeader src_header(src_msg); + const ipc::MessageBuffer::SpecialHeader dst_special_header(dst_msg, dst_header); + const ipc::MessageBuffer::SpecialHeader src_special_header(src_msg, src_header); + + /* Get the end of the source message. */ + const size_t src_end_offset = ipc::MessageBuffer::GetRawDataIndex(src_header, src_special_header) + src_header.GetRawCount(); + + /* Declare variables for processing. */ + int offset = 0; + int pointer_key = 0; + bool processed_special_data = false; + + /* Send the message. */ + { + /* Make sure that we end up in a clean state on error. */ + ON_RESULT_FAILURE { + /* Cleanup special data. */ + if (processed_special_data) { + if (src_header.GetHasSpecialHeader()) { + CleanupSpecialData(dst_process, dst_msg_ptr, dst_buffer_size); + } + } else { + CleanupServerHandles(src_user ? src_message_buffer : 0, src_buffer_size, src_message_paddr); + } + + /* Cleanup mappings. */ + CleanupMap(request, std::addressof(src_process), std::addressof(dst_page_table)); + }; + + /* Ensure that the headers fit. */ + R_UNLESS(ipc::MessageBuffer::GetMessageBufferSize(src_header, src_special_header) <= src_buffer_size, svc::ResultInvalidCombination()); + R_UNLESS(ipc::MessageBuffer::GetMessageBufferSize(dst_header, dst_special_header) <= dst_buffer_size, svc::ResultInvalidCombination()); + + /* Ensure the receive list offset is after the end of raw data. */ + if (dst_header.GetReceiveListOffset()) { + R_UNLESS(dst_header.GetReceiveListOffset() >= ipc::MessageBuffer::GetRawDataIndex(dst_header, dst_special_header) + dst_header.GetRawCount(), svc::ResultInvalidCombination()); + } + + /* Ensure that the destination buffer is big enough to receive the source. */ + R_UNLESS(dst_buffer_size >= src_end_offset * sizeof(u32), svc::ResultMessageTooLarge()); + + /* Replies must have no buffers. */ + R_UNLESS(src_header.GetSendCount() == 0, svc::ResultInvalidCombination()); + R_UNLESS(src_header.GetReceiveCount() == 0, svc::ResultInvalidCombination()); + R_UNLESS(src_header.GetExchangeCount() == 0, svc::ResultInvalidCombination()); + + /* Get the receive list. */ + const s32 dst_recv_list_idx = ipc::MessageBuffer::GetReceiveListIndex(dst_header, dst_special_header); + ReceiveList dst_recv_list(dst_msg_ptr, dst_message_buffer, dst_page_table, dst_header, dst_special_header, dst_buffer_size, src_end_offset, dst_recv_list_idx, !dst_user); + + /* Handle any receive buffers. */ + for (size_t i = 0; i < request->GetReceiveCount(); ++i) { + R_TRY(ProcessSendMessageReceiveMapping(dst_page_table, request->GetReceiveClientAddress(i), request->GetReceiveServerAddress(i), request->GetReceiveSize(i), request->GetReceiveMemoryState(i))); + } + + /* Handle any exchange buffers. */ + for (size_t i = 0; i < request->GetExchangeCount(); ++i) { + R_TRY(ProcessSendMessageReceiveMapping(dst_page_table, request->GetExchangeClientAddress(i), request->GetExchangeServerAddress(i), request->GetExchangeSize(i), request->GetExchangeMemoryState(i))); + } + + /* Set the header. */ + offset = dst_msg.Set(src_header); + + /* Process any special data. */ + MESOSPHERE_ASSERT(GetCurrentThreadPointer() == std::addressof(src_thread)); + processed_special_data = true; + if (src_header.GetHasSpecialHeader()) { + R_TRY(ProcessMessageSpecialData<true>(offset, dst_process, src_process, src_thread, dst_msg, src_msg, src_special_header)); + } + + /* Process any pointer buffers. */ + for (auto i = 0; i < src_header.GetPointerCount(); ++i) { + R_TRY(ProcessSendMessagePointerDescriptors(offset, pointer_key, dst_page_table, dst_msg, src_msg, dst_recv_list, dst_user && dst_header.GetReceiveListCount() == ipc::MessageBuffer::MessageHeader::ReceiveListCountType_ToMessageBuffer)); + } + + /* Clear any map alias buffers. */ + for (auto i = 0; i < src_header.GetMapAliasCount(); ++i) { + offset = dst_msg.Set(offset, ipc::MessageBuffer::MapAliasDescriptor()); + } + + /* Process any raw data. */ + if (const auto raw_count = src_header.GetRawCount(); raw_count != 0) { + /* Get the offset and size. */ + const size_t offset_words = offset * sizeof(u32); + const size_t raw_size = raw_count * sizeof(u32); + + /* Fast case is TLS -> TLS, do raw memcpy if we can. */ + if (!dst_user && !src_user) { + std::memcpy(dst_msg_ptr + offset, src_msg_ptr + offset, raw_size); + } else if (src_user) { + /* Determine how much fast size we can copy. */ + const size_t max_fast_size = std::min<size_t>(offset_words + raw_size, PageSize); + const size_t fast_size = max_fast_size - offset_words; + + /* Determine dst state; if user buffer, we require heap, and otherwise only linear mapped (to enable tls use). */ + const auto dst_state = dst_user ? KMemoryState_FlagReferenceCounted : KMemoryState_FlagLinearMapped; + + /* Determine the dst permission. User buffer should be unmapped + read, TLS should be user readable. */ + const KMemoryPermission dst_perm = static_cast<KMemoryPermission>(dst_user ? (KMemoryPermission_NotMapped | KMemoryPermission_KernelReadWrite) : KMemoryPermission_UserReadWrite); + + /* Perform the fast part of the copy. */ + R_TRY(dst_page_table.CopyMemoryFromKernelToLinear(dst_message_buffer + offset_words, fast_size, + dst_state, dst_state, + dst_perm, + KMemoryAttribute_Uncached, KMemoryAttribute_None, + reinterpret_cast<uintptr_t>(src_msg_ptr) + offset_words)); + + /* If the fast part of the copy didn't get everything, perform the slow part of the copy. */ + if (fast_size < raw_size) { + R_TRY(dst_page_table.CopyMemoryFromHeapToHeap(dst_page_table, dst_message_buffer + max_fast_size, raw_size - fast_size, + dst_state, dst_state, + dst_perm, + KMemoryAttribute_Uncached, KMemoryAttribute_None, + src_message_buffer + max_fast_size, + KMemoryState_FlagReferenceCounted, KMemoryState_FlagReferenceCounted, + static_cast<KMemoryPermission>(KMemoryPermission_NotMapped | KMemoryPermission_KernelRead), + KMemoryAttribute_Uncached | KMemoryAttribute_Locked, KMemoryAttribute_Locked)); + } + } else /* if (dst_user) */ { + /* The destination is a user buffer, so it should be unmapped + readable. */ + constexpr KMemoryPermission DestinationPermission = static_cast<KMemoryPermission>(KMemoryPermission_NotMapped | KMemoryPermission_KernelReadWrite); + + /* Copy the memory. */ + R_TRY(dst_page_table.CopyMemoryFromUserToLinear(dst_message_buffer + offset_words, raw_size, + KMemoryState_FlagReferenceCounted, KMemoryState_FlagReferenceCounted, + DestinationPermission, + KMemoryAttribute_Uncached, KMemoryAttribute_None, + src_message_buffer + offset_words)); + } + } + } + + /* Perform (and validate) any remaining cleanup. */ + R_RETURN(CleanupMap(request, std::addressof(src_process), std::addressof(dst_page_table))); + } + + ALWAYS_INLINE void ReplyAsyncError(KProcess *to_process, uintptr_t to_msg_buf, size_t to_msg_buf_size, Result result) { + /* Convert the buffer to a physical address. */ + KPhysicalAddress phys_addr; + to_process->GetPageTable().GetPhysicalAddress(std::addressof(phys_addr), KProcessAddress(to_msg_buf)); + + /* Convert the physical address to a linear pointer. */ + u32 *to_msg = GetPointer<u32>(KPageTable::GetHeapVirtualAddress(phys_addr)); + + /* Set the error. */ + ipc::MessageBuffer msg(to_msg, to_msg_buf_size); + msg.SetAsyncResult(result); + } + + } + + void KServerSession::Destroy() { + MESOSPHERE_ASSERT_THIS(); + + m_parent->OnServerClosed(); + + this->CleanupRequests(); + + m_parent->Close(); + } + + Result KServerSession::ReceiveRequest(uintptr_t server_message, uintptr_t server_buffer_size, KPhysicalAddress server_message_paddr) { + MESOSPHERE_ASSERT_THIS(); + + /* Lock the session. */ + KScopedLightLock lk(m_lock); + + /* Get the request and client thread. */ + KSessionRequest *request; + KThread *client_thread; + { + KScopedSchedulerLock sl; + + /* Ensure that we can service the request. */ + R_UNLESS(!m_parent->IsClientClosed(), svc::ResultSessionClosed()); + + /* Ensure we aren't already servicing a request. */ + R_UNLESS(m_current_request == nullptr, svc::ResultNotFound()); + + /* Ensure we have a request to service. */ + R_UNLESS(!m_request_list.empty(), svc::ResultNotFound()); + + /* Pop the first request from the list. */ + request = std::addressof(m_request_list.front()); + m_request_list.pop_front(); + + /* Get the thread for the request. */ + client_thread = request->GetThread(); + R_UNLESS(client_thread != nullptr, svc::ResultSessionClosed()); + + /* Open the client thread. */ + client_thread->Open(); + } + ON_SCOPE_EXIT { client_thread->Close(); }; + + /* Set the request as our current. */ + m_current_request = request; + + /* Get the client address. */ + uintptr_t client_message = request->GetAddress(); + size_t client_buffer_size = request->GetSize(); + bool recv_list_broken = false; + + /* Receive the message. */ + Result result = ReceiveMessage(recv_list_broken, server_message, server_buffer_size, server_message_paddr, *client_thread, client_message, client_buffer_size, this, request); + + /* Handle cleanup on receive failure. */ + if (R_FAILED(result)) { + /* Cache the result to return it to the client. */ + const Result result_for_client = result; + + /* Clear the current request. */ + { + KScopedSchedulerLock sl; + MESOSPHERE_ASSERT(m_current_request == request); + m_current_request = nullptr; + if (!m_request_list.empty()) { + this->NotifyAvailable(); + } + } + + /* Reply to the client. */ + { + /* After we reply, close our reference to the request. */ + ON_SCOPE_EXIT { request->Close(); }; + + /* Get the event to check whether the request is async. */ + if (KEvent *event = request->GetEvent(); event != nullptr) { + /* The client sent an async request. */ + KProcess *client = client_thread->GetOwnerProcess(); + auto &client_pt = client->GetPageTable(); + + /* Send the async result. */ + if (R_FAILED(result_for_client)) { + ReplyAsyncError(client, client_message, client_buffer_size, result_for_client); + } + + /* Unlock the client buffer. */ + /* NOTE: Nintendo does not check the result of this. */ + client_pt.UnlockForIpcUserBuffer(client_message, client_buffer_size); + + /* Signal the event. */ + event->Signal(); + } else { + /* End the client thread's wait. */ + KScopedSchedulerLock sl; + + if (!client_thread->IsTerminationRequested()) { + client_thread->EndWait(result_for_client); + } + } + } + + /* Set the server result. */ + if (recv_list_broken) { + result = svc::ResultReceiveListBroken(); + } else { + result = svc::ResultNotFound(); + } + } + + R_RETURN(result); + } + + Result KServerSession::SendReply(uintptr_t server_message, uintptr_t server_buffer_size, KPhysicalAddress server_message_paddr) { + MESOSPHERE_ASSERT_THIS(); + + /* Lock the session. */ + KScopedLightLock lk(m_lock); + + /* Get the request. */ + KSessionRequest *request; + { + KScopedSchedulerLock sl; + + /* Get the current request. */ + request = m_current_request; + R_UNLESS(request != nullptr, svc::ResultInvalidState()); + + /* Clear the current request, since we're processing it. */ + m_current_request = nullptr; + if (!m_request_list.empty()) { + this->NotifyAvailable(); + } + } + + /* Close reference to the request once we're done processing it. */ + ON_SCOPE_EXIT { request->Close(); }; + + /* Extract relevant information from the request. */ + const uintptr_t client_message = request->GetAddress(); + const size_t client_buffer_size = request->GetSize(); + KThread *client_thread = request->GetThread(); + KEvent *event = request->GetEvent(); + + /* Check whether we're closed. */ + const bool closed = (client_thread == nullptr || m_parent->IsClientClosed()); + + Result result; + if (!closed) { + /* If we're not closed, send the reply. */ + result = SendMessage(server_message, server_buffer_size, server_message_paddr, *client_thread, client_message, client_buffer_size, this, request); + } else { + /* Otherwise, we'll need to do some cleanup. */ + KProcess *server_process = request->GetServerProcess(); + KProcess *client_process = (client_thread != nullptr) ? client_thread->GetOwnerProcess() : nullptr; + KProcessPageTable *client_page_table = (client_process != nullptr) ? std::addressof(client_process->GetPageTable()) : nullptr; + + /* Cleanup server handles. */ + result = CleanupServerHandles(server_message, server_buffer_size, server_message_paddr); + + /* Cleanup mappings. */ + Result cleanup_map_result = CleanupMap(request, server_process, client_page_table); + + /* If we successfully cleaned up handles, use the map cleanup result as our result. */ + if (R_SUCCEEDED(result)) { + result = cleanup_map_result; + } + } + + /* Select a result for the client. */ + Result client_result = result; + if (closed && R_SUCCEEDED(result)) { + result = svc::ResultSessionClosed(); + client_result = svc::ResultSessionClosed(); + } else { + result = ResultSuccess(); + } + + /* If there's a client thread, update it. */ + if (client_thread != nullptr) { + if (event != nullptr) { + /* Get the client process/page table. */ + KProcess *client_process = client_thread->GetOwnerProcess(); + KProcessPageTable *client_page_table = std::addressof(client_process->GetPageTable()); + + /* If we need to, reply with an async error. */ + if (R_FAILED(client_result)) { + ReplyAsyncError(client_process, client_message, client_buffer_size, client_result); + } + + /* Unlock the client buffer. */ + /* NOTE: Nintendo does not check the result of this. */ + client_page_table->UnlockForIpcUserBuffer(client_message, client_buffer_size); + + /* Signal the event. */ + event->Signal(); + } else { + /* End the client thread's wait. */ + KScopedSchedulerLock sl; + + if (!client_thread->IsTerminationRequested()) { + client_thread->EndWait(client_result); + } + } + } + + R_RETURN(result); + } + + Result KServerSession::OnRequest(KSessionRequest *request) { + MESOSPHERE_ASSERT_THIS(); + + /* Create the wait queue. */ + ThreadQueueImplForKServerSessionRequest wait_queue; + + /* Handle the request. */ + { + /* Lock the scheduler. */ + KScopedSchedulerLock sl; + + /* Ensure that we can handle new requests. */ + R_UNLESS(!m_parent->IsServerClosed(), svc::ResultSessionClosed()); + + /* Check that we're not terminating. */ + R_UNLESS(!GetCurrentThread().IsTerminationRequested(), svc::ResultTerminationRequested()); + + /* Get whether we're empty. */ + const bool was_empty = m_request_list.empty(); + + /* Add the request to the list. */ + request->Open(); + m_request_list.push_back(*request); + + /* If we were empty, signal. */ + if (was_empty) { + this->NotifyAvailable(); + } + + /* If we have a request, this is asynchronous, and we don't need to wait. */ + R_SUCCEED_IF(request->GetEvent() != nullptr); + + /* This is a synchronous request, so we should wait for our request to complete. */ + GetCurrentThread().BeginWait(std::addressof(wait_queue)); + } + + R_RETURN(GetCurrentThread().GetWaitResult()); + } + + bool KServerSession::IsSignaledImpl() const { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + /* If the client is closed, we're always signaled. */ + if (m_parent->IsClientClosed()) { + return true; + } + + /* Otherwise, we're signaled if we have a request and aren't handling one. */ + return !m_request_list.empty() && m_current_request == nullptr; + } + + bool KServerSession::IsSignaled() const { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + return this->IsSignaledImpl(); + } + + void KServerSession::CleanupRequests() { + MESOSPHERE_ASSERT_THIS(); + + KScopedLightLock lk(m_lock); + + /* Clean up any pending requests. */ + while (true) { + /* Get the next request. */ + KSessionRequest *request = nullptr; + { + KScopedSchedulerLock sl; + + if (m_current_request) { + /* Choose the current request if we have one. */ + request = m_current_request; + m_current_request = nullptr; + } else if (!m_request_list.empty()) { + /* Pop the request from the front of the list. */ + request = std::addressof(m_request_list.front()); + m_request_list.pop_front(); + } + } + + /* If there's no request, we're done. */ + if (request == nullptr) { + break; + } + + /* Close a reference to the request once it's cleaned up. */ + ON_SCOPE_EXIT { request->Close(); }; + + /* Extract relevant information from the request. */ + const uintptr_t client_message = request->GetAddress(); + const size_t client_buffer_size = request->GetSize(); + KThread *client_thread = request->GetThread(); + KEvent *event = request->GetEvent(); + + KProcess *server_process = request->GetServerProcess(); + KProcess *client_process = (client_thread != nullptr) ? client_thread->GetOwnerProcess() : nullptr; + KProcessPageTable *client_page_table = (client_process != nullptr) ? std::addressof(client_process->GetPageTable()) : nullptr; + + /* Cleanup the mappings. */ + Result result = CleanupMap(request, server_process, client_page_table); + + /* If there's a client thread, update it. */ + if (client_thread != nullptr) { + if (event != nullptr) { + /* We need to reply async. */ + ReplyAsyncError(client_process, client_message, client_buffer_size, (R_SUCCEEDED(result) ? svc::ResultSessionClosed() : result)); + + /* Unlock the client buffer. */ + /* NOTE: Nintendo does not check the result of this. */ + client_page_table->UnlockForIpcUserBuffer(client_message, client_buffer_size); + + /* Signal the event. */ + event->Signal(); + } else { + /* End the client thread's wait. */ + KScopedSchedulerLock sl; + + if (!client_thread->IsTerminationRequested()) { + client_thread->EndWait(R_SUCCEEDED(result) ? svc::ResultSessionClosed() : result); + } + } + } + } + } + + void KServerSession::OnClientClosed() { + MESOSPHERE_ASSERT_THIS(); + + KScopedLightLock lk(m_lock); + + /* Handle any pending requests. */ + KSessionRequest *prev_request = nullptr; + while (true) { + /* Declare variables for processing the request. */ + KSessionRequest *request = nullptr; + KEvent *event = nullptr; + KThread *thread = nullptr; + bool cur_request = false; + bool terminate = false; + + /* Get the next request. */ + { + KScopedSchedulerLock sl; + + if (m_current_request != nullptr && m_current_request != prev_request) { + /* Set the request, open a reference as we process it. */ + request = m_current_request; + request->Open(); + cur_request = true; + + /* Get thread and event for the request. */ + thread = request->GetThread(); + event = request->GetEvent(); + + /* If the thread is terminating, handle that. */ + if (thread->IsTerminationRequested()) { + request->ClearThread(); + request->ClearEvent(); + terminate = true; + } + + prev_request = request; + } else if (!m_request_list.empty()) { + /* Pop the request from the front of the list. */ + request = std::addressof(m_request_list.front()); + m_request_list.pop_front(); + + /* Get thread and event for the request. */ + thread = request->GetThread(); + event = request->GetEvent(); + } + } + + /* If there are no requests, we're done. */ + if (request == nullptr) { + break; + } + + /* All requests must have threads. */ + MESOSPHERE_ASSERT(thread != nullptr); + + /* Ensure that we close the request when done. */ + ON_SCOPE_EXIT { request->Close(); }; + + /* If we're terminating, close a reference to the thread and event. */ + if (terminate) { + thread->Close(); + if (event != nullptr) { + event->Close(); + } + } + + /* If we need to, reply. */ + if (event != nullptr && !cur_request) { + /* There must be no mappings. */ + MESOSPHERE_ASSERT(request->GetSendCount() == 0); + MESOSPHERE_ASSERT(request->GetReceiveCount() == 0); + MESOSPHERE_ASSERT(request->GetExchangeCount() == 0); + + /* Get the process and page table. */ + KProcess *client_process = thread->GetOwnerProcess(); + auto &client_pt = client_process->GetPageTable(); + + /* Reply to the request. */ + ReplyAsyncError(client_process, request->GetAddress(), request->GetSize(), svc::ResultSessionClosed()); + + /* Unlock the buffer. */ + /* NOTE: Nintendo does not check the result of this. */ + client_pt.UnlockForIpcUserBuffer(request->GetAddress(), request->GetSize()); + + /* Signal the event. */ + event->Signal(); + } + } + + /* Notify. */ + this->NotifyAvailable(svc::ResultSessionClosed()); + } + + void KServerSession::Dump() { + MESOSPHERE_ASSERT_THIS(); + + KScopedLightLock lk(m_lock); + { + KScopedSchedulerLock sl; + MESOSPHERE_RELEASE_LOG("Dump Session %p\n", this); + + /* Dump current request. */ + bool has_request = false; + if (m_current_request != nullptr) { + KThread *thread = m_current_request->GetThread(); + const s32 thread_id = thread != nullptr ? static_cast<s32>(thread->GetId()) : -1; + MESOSPHERE_RELEASE_LOG(" CurrentReq %p Thread=%p ID=%d\n", m_current_request, thread, thread_id); + has_request = true; + } + + /* Dump all rqeuests in list. */ + for (auto it = m_request_list.begin(); it != m_request_list.end(); ++it) { + KThread *thread = it->GetThread(); + const s32 thread_id = thread != nullptr ? static_cast<s32>(thread->GetId()) : -1; + MESOSPHERE_RELEASE_LOG(" Req %p Thread=%p ID=%d\n", m_current_request, thread, thread_id); + has_request = true; + } + + /* If we didn't have any requests, print so. */ + if (!has_request) { + MESOSPHERE_RELEASE_LOG(" None\n"); + } + } + } + +} + +#pragma GCC pop_options diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_session.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_session.cpp new file mode 100644 index 00000000..fe8c572f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_session.cpp @@ -0,0 +1,87 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + void KSession::Initialize(KClientPort *client_port, uintptr_t name) { + MESOSPHERE_ASSERT_THIS(); + + /* Increment reference count. */ + /* Because reference count is one on creation, this will result */ + /* in a reference count of two. Thus, when both server and client are closed */ + /* this object will be destroyed. */ + this->Open(); + + /* Create our sub sessions. */ + KAutoObject::Create<KServerSession>(std::addressof(m_server)); + KAutoObject::Create<KClientSession>(std::addressof(m_client)); + + /* Initialize our sub sessions. */ + m_server.Initialize(this); + m_client.Initialize(this); + + /* Set state and name. */ + this->SetState(State::Normal); + m_name = name; + + /* Set our owner process. */ + m_process = GetCurrentProcessPointer(); + m_process->Open(); + + /* Set our port. */ + m_port = client_port; + if (m_port != nullptr) { + m_port->Open(); + } + + /* Mark initialized. */ + m_initialized = true; + } + + void KSession::Finalize() { + if (m_port != nullptr) { + m_port->OnSessionFinalized(); + m_port->Close(); + } + } + + void KSession::OnServerClosed() { + MESOSPHERE_ASSERT_THIS(); + + if (this->GetState() == State::Normal) { + this->SetState(State::ServerClosed); + m_client.OnServerClosed(); + } + } + + void KSession::OnClientClosed() { + MESOSPHERE_ASSERT_THIS(); + + if (this->GetState() == State::Normal) { + this->SetState(State::ClientClosed); + m_server.OnClientClosed(); + } + } + + void KSession::PostDestroy(uintptr_t arg) { + /* Release the session count resource the owner process holds. */ + KProcess *owner = reinterpret_cast<KProcess *>(arg); + owner->ReleaseResource(ams::svc::LimitableResource_SessionCountMax, 1); + owner->Close(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_session_request.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_session_request.cpp new file mode 100644 index 00000000..deb1619d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_session_request.cpp @@ -0,0 +1,66 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + Result KSessionRequest::SessionMappings::PushMap(KProcessAddress client, KProcessAddress server, size_t size, KMemoryState state, size_t index) { + /* At most 15 buffers of each type (4-bit descriptor counts). */ + MESOSPHERE_ASSERT(index < NumMappings); + + /* Get the mapping. */ + Mapping *mapping; + if (index < NumStaticMappings) { + mapping = std::addressof(m_static_mappings[index]); + } else { + /* Allocate dynamic mappings as necessary. */ + if (m_dynamic_mappings == nullptr) { + m_dynamic_mappings = DynamicMappings::Allocate(); + R_UNLESS(m_dynamic_mappings != nullptr, svc::ResultOutOfMemory()); + } + + mapping = std::addressof(m_dynamic_mappings->Get(index - NumStaticMappings)); + } + + /* Set the mapping. */ + mapping->Set(client, server, size, state); + + R_SUCCEED(); + } + + Result KSessionRequest::SessionMappings::PushSend(KProcessAddress client, KProcessAddress server, size_t size, KMemoryState state) { + MESOSPHERE_ASSERT(m_num_recv == 0); + MESOSPHERE_ASSERT(m_num_exch == 0); + R_RETURN(this->PushMap(client, server, size, state, m_num_send++)); + } + + Result KSessionRequest::SessionMappings::PushReceive(KProcessAddress client, KProcessAddress server, size_t size, KMemoryState state) { + MESOSPHERE_ASSERT(m_num_exch == 0); + R_RETURN(this->PushMap(client, server, size, state, m_num_send + m_num_recv++)); + } + + Result KSessionRequest::SessionMappings::PushExchange(KProcessAddress client, KProcessAddress server, size_t size, KMemoryState state) { + R_RETURN(this->PushMap(client, server, size, state, m_num_send + m_num_recv + m_num_exch++)); + } + + void KSessionRequest::SessionMappings::Finalize() { + if (m_dynamic_mappings) { + DynamicMappings::Free(m_dynamic_mappings); + m_dynamic_mappings = nullptr; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_shared_memory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_shared_memory.cpp new file mode 100644 index 00000000..95d0dc40 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_shared_memory.cpp @@ -0,0 +1,105 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + Result KSharedMemory::Initialize(KProcess *owner, size_t size, ams::svc::MemoryPermission own_perm, ams::svc::MemoryPermission rem_perm) { + MESOSPHERE_ASSERT_THIS(); + + /* Set members. */ + m_owner_process_id = owner->GetId(); + m_owner_perm = own_perm; + m_remote_perm = rem_perm; + + /* Get the number of pages. */ + const size_t num_pages = util::DivideUp(size, PageSize); + MESOSPHERE_ASSERT(num_pages > 0); + + /* Get the resource limit. */ + KResourceLimit *reslimit = owner->GetResourceLimit(); + + /* Reserve memory for ourselves. */ + KScopedResourceReservation memory_reservation(reslimit, ams::svc::LimitableResource_PhysicalMemoryMax, size); + R_UNLESS(memory_reservation.Succeeded(), svc::ResultLimitReached()); + + /* Allocate the memory. */ + R_TRY(Kernel::GetMemoryManager().AllocateAndOpen(std::addressof(m_page_group), num_pages, 1, owner->GetAllocateOption())); + + /* Commit our reservation. */ + memory_reservation.Commit(); + + /* Set our resource limit. */ + m_resource_limit = reslimit; + m_resource_limit->Open(); + + /* Mark initialized. */ + m_is_initialized = true; + + /* Clear all pages in the memory. */ + for (const auto &block : m_page_group) { + std::memset(GetVoidPointer(KMemoryLayout::GetLinearVirtualAddress(block.GetAddress())), 0, block.GetSize()); + } + + R_SUCCEED(); + } + + void KSharedMemory::Finalize() { + MESOSPHERE_ASSERT_THIS(); + + /* Get the number of pages. */ + const size_t num_pages = m_page_group.GetNumPages(); + const size_t size = num_pages * PageSize; + + /* Close and finalize the page group. */ + m_page_group.Close(); + m_page_group.Finalize(); + + /* Release the memory reservation. */ + m_resource_limit->Release(ams::svc::LimitableResource_PhysicalMemoryMax, size); + m_resource_limit->Close(); + } + + Result KSharedMemory::Map(KProcessPageTable *table, KProcessAddress address, size_t size, KProcess *process, ams::svc::MemoryPermission map_perm) { + MESOSPHERE_ASSERT_THIS(); + + /* Validate the size. */ + R_UNLESS(m_page_group.GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize()); + + /* Validate the permission. */ + const ams::svc::MemoryPermission test_perm = (process->GetId() == m_owner_process_id) ? m_owner_perm : m_remote_perm; + if (test_perm == ams::svc::MemoryPermission_DontCare) { + MESOSPHERE_ASSERT(map_perm == ams::svc::MemoryPermission_Read || map_perm == ams::svc::MemoryPermission_ReadWrite); + } else { + R_UNLESS(map_perm == test_perm, svc::ResultInvalidNewMemoryPermission()); + } + + /* Map the memory. */ + R_RETURN(table->MapPageGroup(address, m_page_group, KMemoryState_Shared, ConvertToKMemoryPermission(map_perm))); + } + + Result KSharedMemory::Unmap(KProcessPageTable *table, KProcessAddress address, size_t size, KProcess *process) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_UNUSED(process); + + /* Validate the size. */ + R_UNLESS(m_page_group.GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize()); + + /* Unmap the memory. */ + R_RETURN(table->UnmapPageGroup(address, m_page_group, KMemoryState_Shared)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_synchronization_object.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_synchronization_object.cpp new file mode 100644 index 00000000..e1969b9c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_synchronization_object.cpp @@ -0,0 +1,203 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + class ThreadQueueImplForKSynchronizationObjectWait final : public KThreadQueueWithoutEndWait { + private: + using ThreadListNode = KSynchronizationObject::ThreadListNode; + private: + KSynchronizationObject **m_objects; + ThreadListNode *m_nodes; + s32 m_count; + public: + constexpr ThreadQueueImplForKSynchronizationObjectWait(KSynchronizationObject **o, ThreadListNode *n, s32 c) : m_objects(o), m_nodes(n), m_count(c) { /* ... */ } + + virtual void NotifyAvailable(KThread *waiting_thread, KSynchronizationObject *signaled_object, Result wait_result) override { + /* Determine the sync index, and unlink all nodes. */ + s32 sync_index = -1; + for (auto i = 0; i < m_count; ++i) { + /* Check if this is the signaled object. */ + if (m_objects[i] == signaled_object && sync_index == -1) { + sync_index = i; + } + + /* Unlink the current node from the current object. */ + m_objects[i]->UnlinkNode(std::addressof(m_nodes[i])); + } + + /* Set the waiting thread's sync index. */ + waiting_thread->SetSyncedIndex(sync_index); + + /* Set the waiting thread as not cancellable. */ + waiting_thread->ClearCancellable(); + + /* Invoke the base end wait handler. */ + KThreadQueue::EndWait(waiting_thread, wait_result); + } + + virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override { + /* Remove all nodes from our list. */ + for (auto i = 0; i < m_count; ++i) { + m_objects[i]->UnlinkNode(std::addressof(m_nodes[i])); + } + + /* Set the waiting thread as not cancellable. */ + waiting_thread->ClearCancellable(); + + /* Invoke the base cancel wait handler. */ + KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + } + }; + + } + + void KSynchronizationObject::Finalize() { + MESOSPHERE_ASSERT_THIS(); + + /* If auditing, ensure that the object has no waiters. */ + #if defined(MESOSPHERE_BUILD_FOR_AUDITING) + { + KScopedSchedulerLock sl; + + for (auto *cur_node = m_thread_list_head; cur_node != nullptr; cur_node = cur_node->next) { + KThread *thread = cur_node->thread; + MESOSPHERE_LOG("KSynchronizationObject::Finalize(%p) with %p (id=%ld) waiting.\n", this, thread, thread->GetId()); + } + } + #endif + + /* NOTE: In Nintendo's kernel, the following is virtual and called here. */ + /* this->OnFinalizeSynchronizationObject(); */ + } + + Result KSynchronizationObject::Wait(s32 *out_index, KSynchronizationObject **objects, const s32 num_objects, s64 timeout) { + /* Allocate space on stack for thread nodes. */ + ThreadListNode *thread_nodes = static_cast<ThreadListNode *>(__builtin_alloca(sizeof(ThreadListNode) * num_objects)); + + /* Prepare for wait. */ + KThread *thread = GetCurrentThreadPointer(); + KHardwareTimer *timer; + ThreadQueueImplForKSynchronizationObjectWait wait_queue(objects, thread_nodes, num_objects); + + { + /* Setup the scheduling lock and sleep. */ + KScopedSchedulerLockAndSleep slp(std::addressof(timer), thread, timeout); + + /* Check if the thread should terminate. */ + if (thread->IsTerminationRequested()) { + slp.CancelSleep(); + R_THROW(svc::ResultTerminationRequested()); + } + + /* Check if any of the objects are already signaled. */ + for (auto i = 0; i < num_objects; ++i) { + MESOSPHERE_ASSERT(objects[i] != nullptr); + + if (objects[i]->IsSignaled()) { + *out_index = i; + slp.CancelSleep(); + R_SUCCEED(); + } + } + + /* Check if the timeout is zero. */ + if (timeout == 0) { + slp.CancelSleep(); + R_THROW(svc::ResultTimedOut()); + } + + /* Check if waiting was canceled. */ + if (thread->IsWaitCancelled()) { + slp.CancelSleep(); + thread->ClearWaitCancelled(); + R_THROW(svc::ResultCancelled()); + } + + /* Add the waiters. */ + for (auto i = 0; i < num_objects; ++i) { + thread_nodes[i].thread = thread; + thread_nodes[i].next = nullptr; + + objects[i]->LinkNode(std::addressof(thread_nodes[i])); + } + + /* Mark the thread as cancellable. */ + thread->SetCancellable(); + + /* Clear the thread's synced index. */ + thread->SetSyncedIndex(-1); + + /* Wait for an object to be signaled. */ + wait_queue.SetHardwareTimer(timer); + thread->BeginWait(std::addressof(wait_queue)); + } + + /* Set the output index. */ + *out_index = thread->GetSyncedIndex(); + + /* Get the wait result. */ + R_RETURN(thread->GetWaitResult()); + } + + void KSynchronizationObject::NotifyAvailable(Result result) { + MESOSPHERE_ASSERT_THIS(); + + KScopedSchedulerLock sl; + + /* If we're not signaled, we've nothing to notify. */ + if (!this->IsSignaled()) { + return; + } + + /* Iterate over each thread. */ + for (auto *cur_node = m_thread_list_head; cur_node != nullptr; cur_node = cur_node->next) { + cur_node->thread->NotifyAvailable(this, result); + } + } + + void KSynchronizationObject::DumpWaiters() { + MESOSPHERE_ASSERT_THIS(); + + /* If debugging, dump the list of waiters. */ + #if defined(MESOSPHERE_BUILD_FOR_DEBUGGING) + { + KScopedSchedulerLock sl; + + MESOSPHERE_RELEASE_LOG("Threads waiting on %p:\n", this); + + for (auto *cur_node = m_thread_list_head; cur_node != nullptr; cur_node = cur_node->next) { + KThread *thread = cur_node->thread; + + if (KProcess *process = thread->GetOwnerProcess(); process != nullptr) { + MESOSPHERE_RELEASE_LOG(" %p tid=%ld pid=%ld (%s)\n", thread, thread->GetId(), process->GetId(), process->GetName()); + } else { + MESOSPHERE_RELEASE_LOG(" %p tid=%ld (Kernel)\n", thread, thread->GetId()); + } + } + + /* If we didn't have any waiters, print so. */ + if (m_thread_list_head == nullptr) { + MESOSPHERE_RELEASE_LOG(" None\n"); + } + } + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_system_control_base.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_system_control_base.cpp new file mode 100644 index 00000000..dece074e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_system_control_base.cpp @@ -0,0 +1,330 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> +#if defined(ATMOSPHERE_ARCH_ARM64) +#include <mesosphere/arch/arm64/kern_secure_monitor_base.hpp> +#endif + +namespace ams::kern { + + namespace init { + + /* TODO: Is this function name architecture specific? */ + void StartOtherCore(const ams::kern::init::KInitArguments *init_args); + + } + + /* Initialization. */ + size_t KSystemControlBase::Init::GetRealMemorySize() { + return ams::kern::MainMemorySize; + } + + size_t KSystemControlBase::Init::GetIntendedMemorySize() { + return ams::kern::MainMemorySize; + } + + KPhysicalAddress KSystemControlBase::Init::GetKernelPhysicalBaseAddress(KPhysicalAddress base_address) { + const size_t real_dram_size = KSystemControl::Init::GetRealMemorySize(); + const size_t intended_dram_size = KSystemControl::Init::GetIntendedMemorySize(); + if (intended_dram_size * 2 <= real_dram_size) { + return base_address; + } else { + return base_address + ((real_dram_size - intended_dram_size) / 2); + } + } + + void KSystemControlBase::Init::GetInitialProcessBinaryLayout(InitialProcessBinaryLayout *out, KPhysicalAddress kern_base_address) { + *out = { + .address = GetInteger(KSystemControl::Init::GetKernelPhysicalBaseAddress(ams::kern::MainMemoryAddress)) + KSystemControl::Init::GetIntendedMemorySize() - InitialProcessBinarySizeMax, + ._08 = 0, + .kern_address = GetInteger(kern_base_address), + }; + } + + + bool KSystemControlBase::Init::ShouldIncreaseThreadResourceLimit() { + return true; + } + + + size_t KSystemControlBase::Init::GetApplicationPoolSize() { + return 0; + } + + size_t KSystemControlBase::Init::GetAppletPoolSize() { + return 0; + } + + size_t KSystemControlBase::Init::GetMinimumNonSecureSystemPoolSize() { + return 0; + } + + u8 KSystemControlBase::Init::GetDebugLogUartPort() { + return 0; + } + + void KSystemControlBase::Init::CpuOnImpl(u64 core_id, uintptr_t entrypoint, uintptr_t arg) { + #if defined(ATMOSPHERE_ARCH_ARM64) + MESOSPHERE_INIT_ABORT_UNLESS((::ams::kern::arch::arm64::smc::CpuOn<0>(core_id, entrypoint, arg)) == 0); + #else + AMS_INFINITE_LOOP(); + #endif + } + + void KSystemControlBase::Init::TurnOnCpu(u64 core_id, const ams::kern::init::KInitArguments *args) { + /* Get entrypoint. */ + KPhysicalAddress entrypoint = Null<KPhysicalAddress>; + while (!cpu::GetPhysicalAddressReadable(std::addressof(entrypoint), reinterpret_cast<uintptr_t>(::ams::kern::init::StartOtherCore), true)) { /* ... */ } + + /* Get arguments. */ + KPhysicalAddress args_addr = Null<KPhysicalAddress>; + while (!cpu::GetPhysicalAddressReadable(std::addressof(args_addr), reinterpret_cast<uintptr_t>(args), true)) { /* ... */ } + + /* Ensure cache is correct for the initial arguments. */ + cpu::StoreDataCacheForInitArguments(args, sizeof(*args)); + + /* Turn on the cpu. */ + KSystemControl::Init::CpuOnImpl(core_id, GetInteger(entrypoint), GetInteger(args_addr)); + } + + /* Randomness for Initialization. */ + void KSystemControlBase::Init::GenerateRandom(u64 *dst, size_t count) { + if (AMS_UNLIKELY(s_uninitialized_random_generator)) { + const u64 seed = KHardwareTimer::GetTick(); + s_random_generator.Initialize(reinterpret_cast<const u32*>(std::addressof(seed)), sizeof(seed) / sizeof(u32)); + s_uninitialized_random_generator = false; + } + + for (size_t i = 0; i < count; ++i) { + dst[i] = s_random_generator.GenerateRandomU64(); + } + } + + u64 KSystemControlBase::Init::GenerateRandomRange(u64 min, u64 max) { + if (AMS_UNLIKELY(s_uninitialized_random_generator)) { + const u64 seed = KHardwareTimer::GetTick(); + s_random_generator.Initialize(reinterpret_cast<const u32*>(std::addressof(seed)), sizeof(seed) / sizeof(u32)); + s_uninitialized_random_generator = false; + } + + return KSystemControlBase::GenerateUniformRange(min, max, []() ALWAYS_INLINE_LAMBDA -> u64 { return s_random_generator.GenerateRandomU64(); }); + } + + /* System Initialization. */ + void KSystemControlBase::ConfigureKTargetSystem() { + /* By default, use the default config set in the KTargetSystem header. */ + } + + void KSystemControlBase::InitializePhase1() { + /* Enable KTargetSystem. */ + { + KTargetSystem::SetInitialized(); + } + + /* Initialize random and resource limit. */ + KSystemControlBase::InitializePhase1Base(KHardwareTimer::GetTick()); + } + + void KSystemControlBase::InitializePhase1Base(u64 seed) { + /* Initialize the rng, if we somehow haven't already. */ + if (AMS_UNLIKELY(s_uninitialized_random_generator)) { + s_random_generator.Initialize(reinterpret_cast<const u32*>(std::addressof(seed)), sizeof(seed) / sizeof(u32)); + s_uninitialized_random_generator = false; + } + + /* Initialize debug logging. */ + KDebugLog::Initialize(); + + /* System ResourceLimit initialization. */ + { + /* Construct the resource limit object. */ + KResourceLimit &sys_res_limit = Kernel::GetSystemResourceLimit(); + KAutoObject::Create<KResourceLimit>(std::addressof(sys_res_limit)); + sys_res_limit.Initialize(); + + /* Set the initial limits. */ + const auto [total_memory_size, kernel_memory_size] = KMemoryLayout::GetTotalAndKernelMemorySizes(); + + /* Update 39-bit address space infos. */ + { + /* Heap should be equal to the total memory size, minimum 8 GB, maximum 32 GB. */ + /* Alias should be equal to 8 * heap size, maximum 128 GB. */ + const size_t heap_size = std::max(std::min(util::AlignUp(total_memory_size, 1_GB), 32_GB), 8_GB); + const size_t alias_size = std::min(heap_size * 8, 128_GB); + + /* Set the address space sizes. */ + KAddressSpaceInfo::SetAddressSpaceSize(39, KAddressSpaceInfo::Type_Heap, heap_size); + KAddressSpaceInfo::SetAddressSpaceSize(39, KAddressSpaceInfo::Type_Alias, alias_size); + } + + const auto &slab_counts = init::GetSlabResourceCounts(); + MESOSPHERE_R_ABORT_UNLESS(sys_res_limit.SetLimitValue(ams::svc::LimitableResource_PhysicalMemoryMax, total_memory_size)); + MESOSPHERE_R_ABORT_UNLESS(sys_res_limit.SetLimitValue(ams::svc::LimitableResource_ThreadCountMax, slab_counts.num_KThread)); + MESOSPHERE_R_ABORT_UNLESS(sys_res_limit.SetLimitValue(ams::svc::LimitableResource_EventCountMax, slab_counts.num_KEvent)); + MESOSPHERE_R_ABORT_UNLESS(sys_res_limit.SetLimitValue(ams::svc::LimitableResource_TransferMemoryCountMax, slab_counts.num_KTransferMemory)); + MESOSPHERE_R_ABORT_UNLESS(sys_res_limit.SetLimitValue(ams::svc::LimitableResource_SessionCountMax, slab_counts.num_KSession)); + + /* Reserve system memory. */ + MESOSPHERE_ABORT_UNLESS(sys_res_limit.Reserve(ams::svc::LimitableResource_PhysicalMemoryMax, kernel_memory_size)); + } + } + + void KSystemControlBase::InitializePhase2() { + /* Initialize KTrace. */ + if constexpr (IsKTraceEnabled) { + const auto &ktrace = KMemoryLayout::GetKernelTraceBufferRegion(); + KTrace::Initialize(ktrace.GetAddress(), ktrace.GetSize()); + } + } + + u32 KSystemControlBase::GetCreateProcessMemoryPool() { + return KMemoryManager::Pool_System; + } + + /* Privileged Access. */ + void KSystemControlBase::ReadWriteRegisterPrivileged(u32 *out, ams::svc::PhysicalAddress address, u32 mask, u32 value) { + /* TODO */ + MESOSPHERE_UNUSED(out, address, mask, value); + MESOSPHERE_UNIMPLEMENTED(); + } + + Result KSystemControlBase::ReadWriteRegister(u32 *out, ams::svc::PhysicalAddress address, u32 mask, u32 value) { + MESOSPHERE_UNUSED(out, address, mask, value); + R_THROW(svc::ResultNotImplemented()); + } + + /* Randomness. */ + void KSystemControlBase::GenerateRandom(u64 *dst, size_t count) { + KScopedInterruptDisable intr_disable; + KScopedSpinLock lk(s_random_lock); + + for (size_t i = 0; i < count; ++i) { + dst[i] = s_random_generator.GenerateRandomU64(); + } + } + + u64 KSystemControlBase::GenerateRandomRange(u64 min, u64 max) { + KScopedInterruptDisable intr_disable; + KScopedSpinLock lk(s_random_lock); + + return KSystemControlBase::GenerateUniformRange(min, max, []() ALWAYS_INLINE_LAMBDA -> u64 { return s_random_generator.GenerateRandomU64(); }); + } + + u64 KSystemControlBase::GenerateRandomU64() { + KScopedInterruptDisable intr_disable; + KScopedSpinLock lk(s_random_lock); + + return s_random_generator.GenerateRandomU64(); + } + + void KSystemControlBase::SleepSystem() { + MESOSPHERE_LOG("SleepSystem() was called\n"); + } + + void KSystemControlBase::StopSystem(void *) { + MESOSPHERE_LOG("KSystemControlBase::StopSystem\n"); + AMS_INFINITE_LOOP(); + } + + /* User access. */ + #if defined(ATMOSPHERE_ARCH_ARM64) + void KSystemControlBase::CallSecureMonitorFromUser(ams::svc::lp64::SecureMonitorArguments *args) { + /* Get the function id for the current call. */ + u64 function_id = args->r[0]; + + /* We'll need to map in pages if arguments are pointers. Prepare page groups to do so. */ + auto &page_table = GetCurrentProcess().GetPageTable(); + auto *bim = page_table.GetBlockInfoManager(); + + constexpr size_t MaxMappedRegisters = 7; + std::array<KPageGroup, MaxMappedRegisters> page_groups = { KPageGroup(bim), KPageGroup(bim), KPageGroup(bim), KPageGroup(bim), KPageGroup(bim), KPageGroup(bim), KPageGroup(bim), }; + + for (size_t i = 0; i < MaxMappedRegisters; i++) { + const size_t reg_id = i + 1; + if (function_id & (1ul << (8 + reg_id))) { + /* Create and open a new page group for the address. */ + KVirtualAddress virt_addr = args->r[reg_id]; + + if (R_SUCCEEDED(page_table.MakeAndOpenPageGroup(std::addressof(page_groups[i]), util::AlignDown(GetInteger(virt_addr), PageSize), 1, KMemoryState_None, KMemoryState_None, KMemoryPermission_UserReadWrite, KMemoryPermission_UserReadWrite, KMemoryAttribute_None, KMemoryAttribute_None))) { + /* Translate the virtual address to a physical address. */ + const auto it = page_groups[i].begin(); + MESOSPHERE_ASSERT(it != page_groups[i].end()); + MESOSPHERE_ASSERT(it->GetNumPages() == 1); + + args->r[reg_id] = GetInteger(it->GetAddress()) | (GetInteger(virt_addr) & (PageSize - 1)); + } else { + /* If we couldn't map, we should clear the address. */ + args->r[reg_id] = 0; + } + } + } + + /* Invoke the secure monitor. */ + KSystemControl::CallSecureMonitorFromUserImpl(args); + + /* Make sure that we close any pages that we opened. */ + for (size_t i = 0; i < MaxMappedRegisters; i++) { + page_groups[i].Close(); + } + } + + void KSystemControlBase::CallSecureMonitorFromUserImpl(ams::svc::lp64::SecureMonitorArguments *args) { + /* By default, we don't actually support secure monitor, so just set args to a failure code. */ + args->r[0] = 1; + } + #endif + + /* Secure Memory. */ + size_t KSystemControlBase::CalculateRequiredSecureMemorySize(size_t size, u32 pool) { + MESOSPHERE_UNUSED(pool); + return size; + } + + Result KSystemControlBase::AllocateSecureMemory(KVirtualAddress *out, size_t size, u32 pool) { + /* Ensure the size is aligned. */ + constexpr size_t Alignment = PageSize; + R_UNLESS(util::IsAligned(size, Alignment), svc::ResultInvalidSize()); + + /* Allocate the memory. */ + const size_t num_pages = size / PageSize; + const KPhysicalAddress paddr = Kernel::GetMemoryManager().AllocateAndOpenContinuous(num_pages, Alignment / PageSize, KMemoryManager::EncodeOption(static_cast<KMemoryManager::Pool>(pool), KMemoryManager::Direction_FromFront)); + R_UNLESS(paddr != Null<KPhysicalAddress>, svc::ResultOutOfMemory()); + + *out = KPageTable::GetHeapVirtualAddress(paddr); + R_SUCCEED(); + } + + void KSystemControlBase::FreeSecureMemory(KVirtualAddress address, size_t size, u32 pool) { + /* Ensure the size is aligned. */ + constexpr size_t Alignment = PageSize; + MESOSPHERE_UNUSED(pool); + MESOSPHERE_ABORT_UNLESS(util::IsAligned(GetInteger(address), Alignment)); + MESOSPHERE_ABORT_UNLESS(util::IsAligned(size, Alignment)); + + /* Close the secure region's pages. */ + Kernel::GetMemoryManager().Close(KPageTable::GetHeapPhysicalAddress(address), size / PageSize); + } + + /* Insecure Memory. */ + KResourceLimit *KSystemControlBase::GetInsecureMemoryResourceLimit() { + return std::addressof(Kernel::GetSystemResourceLimit()); + } + + u32 KSystemControlBase::GetInsecureMemoryPool() { + return KMemoryManager::Pool_SystemNonSecure; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_system_resource.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_system_resource.cpp new file mode 100644 index 00000000..fc487625 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_system_resource.cpp @@ -0,0 +1,95 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + Result KSecureSystemResource::Initialize(size_t size, KResourceLimit *resource_limit, KMemoryManager::Pool pool) { + /* Set members. */ + m_resource_limit = resource_limit; + m_resource_size = size; + m_resource_pool = pool; + + /* Determine required size for our secure resource. */ + const size_t secure_size = this->CalculateRequiredSecureMemorySize(); + + /* Reserve memory for our secure resource. */ + KScopedResourceReservation memory_reservation(m_resource_limit, ams::svc::LimitableResource_PhysicalMemoryMax, secure_size); + R_UNLESS(memory_reservation.Succeeded(), svc::ResultLimitReached()); + + /* Allocate secure memory. */ + R_TRY(KSystemControl::AllocateSecureMemory(std::addressof(m_resource_address), m_resource_size, m_resource_pool)); + MESOSPHERE_ASSERT(m_resource_address != Null<KVirtualAddress>); + + /* Ensure we clean up the secure memory, if we fail past this point. */ + ON_RESULT_FAILURE { KSystemControl::FreeSecureMemory(m_resource_address, m_resource_size, m_resource_pool); }; + + /* Check that our allocation is bigger than the reference counts needed for it. */ + const size_t rc_size = util::AlignUp(KPageTableSlabHeap::CalculateReferenceCountSize(m_resource_size), PageSize); + R_UNLESS(m_resource_size > rc_size, svc::ResultOutOfMemory()); + + /* Initialize slab heaps. */ + m_dynamic_page_manager.Initialize(m_resource_address + rc_size, m_resource_size - rc_size, PageSize); + m_page_table_heap.Initialize(std::addressof(m_dynamic_page_manager), 0, GetPointer<KPageTableManager::RefCount>(m_resource_address)); + m_memory_block_heap.Initialize(std::addressof(m_dynamic_page_manager), 0); + m_block_info_heap.Initialize(std::addressof(m_dynamic_page_manager), 0); + + /* Initialize managers. */ + m_page_table_manager.Initialize(std::addressof(m_dynamic_page_manager), std::addressof(m_page_table_heap)); + m_memory_block_slab_manager.Initialize(std::addressof(m_dynamic_page_manager), std::addressof(m_memory_block_heap)); + m_block_info_manager.Initialize(std::addressof(m_dynamic_page_manager), std::addressof(m_block_info_heap)); + + /* Set our managers. */ + this->SetManagers(m_memory_block_slab_manager, m_block_info_manager, m_page_table_manager); + + /* Commit the memory reservation. */ + memory_reservation.Commit(); + + /* Open reference to our resource limit. */ + if (m_resource_limit != nullptr) { + m_resource_limit->Open(); + } + + /* Set ourselves as initialized. */ + m_is_initialized = true; + + R_SUCCEED(); + } + + void KSecureSystemResource::Finalize() { + /* Check that we have no outstanding allocations. */ + MESOSPHERE_ABORT_UNLESS(m_memory_block_slab_manager.GetUsed() == 0); + MESOSPHERE_ABORT_UNLESS(m_block_info_manager.GetUsed() == 0); + MESOSPHERE_ABORT_UNLESS(m_page_table_manager.GetUsed() == 0); + + /* Free our secure memory. */ + KSystemControl::FreeSecureMemory(m_resource_address, m_resource_size, m_resource_pool); + + /* Clean up our resource usage. */ + if (m_resource_limit != nullptr) { + /* Release the memory reservation. */ + m_resource_limit->Release(ams::svc::LimitableResource_PhysicalMemoryMax, this->CalculateRequiredSecureMemorySize()); + + /* Close reference to our resource limit. */ + m_resource_limit->Close(); + } + } + + size_t KSecureSystemResource::CalculateRequiredSecureMemorySize(size_t size, KMemoryManager::Pool pool) { + return KSystemControl::CalculateRequiredSecureMemorySize(size, pool); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_thread.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_thread.cpp new file mode 100644 index 00000000..74b39acd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_thread.cpp @@ -0,0 +1,1540 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + constexpr inline s32 TerminatingThreadPriority = ams::svc::SystemThreadPriorityHighest - 1; + + constinit util::Atomic<u64> g_thread_id = 0; + + constexpr ALWAYS_INLINE bool IsKernelAddressKey(KProcessAddress key) { + const uintptr_t key_uptr = GetInteger(key); + return KernelVirtualAddressSpaceBase <= key_uptr && key_uptr <= KernelVirtualAddressSpaceLast && (key_uptr & 1) == 0; + } + + void InitializeKernelStack(uintptr_t stack_top) { + #if defined(MESOSPHERE_ENABLE_KERNEL_STACK_USAGE) + const uintptr_t stack_bottom = stack_top - PageSize; + std::memset(reinterpret_cast<void *>(stack_bottom), 0xCC, PageSize - sizeof(KThread::StackParameters)); + #else + MESOSPHERE_UNUSED(stack_top); + #endif + } + + void CleanupKernelStack(uintptr_t stack_top) { + const uintptr_t stack_bottom = stack_top - PageSize; + + KPhysicalAddress stack_paddr = Null<KPhysicalAddress>; + MESOSPHERE_ABORT_UNLESS(Kernel::GetKernelPageTable().GetPhysicalAddress(std::addressof(stack_paddr), stack_bottom)); + + MESOSPHERE_R_ABORT_UNLESS(Kernel::GetKernelPageTable().UnmapPages(stack_bottom, 1, KMemoryState_Kernel)); + + /* Free the stack page. */ + KPageBuffer::FreeChecked<PageSize>(KPageBuffer::FromPhysicalAddress(stack_paddr)); + } + + class ThreadQueueImplForKThreadSleep final : public KThreadQueueWithoutEndWait { /* ... */ }; + + class ThreadQueueImplForKThreadSetProperty final : public KThreadQueue { + private: + KThread::WaiterList *m_wait_list; + public: + constexpr ThreadQueueImplForKThreadSetProperty(KThread::WaiterList *wl) : m_wait_list(wl) { /* ... */ } + + virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override { + /* Remove the thread from the wait list. */ + m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread)); + + /* Invoke the base cancel wait handler. */ + KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + } + }; + + } + + ALWAYS_INLINE void KThread::SetPinnedSvcPermissions() { + /* Get our stack parameters. */ + auto &sp = this->GetStackParameters(); + + /* Get our parent's svc permissions. */ + MESOSPHERE_ASSERT(m_parent != nullptr); + const auto &svc_permissions = m_parent->GetSvcPermissions(); + + /* Get whether we have access to return from exception. */ + const bool return_from_exception = sp.svc_access_flags[svc::SvcId_ReturnFromException]; + + /* Clear all permissions. */ + sp.svc_access_flags.Reset(); + + /* Set SynchronizePreemptionState if allowed. */ + if (svc_permissions[svc::SvcId_SynchronizePreemptionState]) { + sp.svc_access_flags[svc::SvcId_SynchronizePreemptionState] = true; + } + + /* If we previously had ReturnFromException, potentially grant it and GetInfo. */ + if (return_from_exception) { + /* Set ReturnFromException (guaranteed allowed, if we're here). */ + sp.svc_access_flags[svc::SvcId_ReturnFromException] = true; + + /* Set GetInfo if allowed. */ + if (svc_permissions[svc::SvcId_GetInfo]) { + sp.svc_access_flags[svc::SvcId_GetInfo] = true; + } + } + } + + ALWAYS_INLINE void KThread::SetUnpinnedSvcPermissions() { + /* Get our stack parameters. */ + auto &sp = this->GetStackParameters(); + + /* Get our parent's svc permissions. */ + MESOSPHERE_ASSERT(m_parent != nullptr); + const auto &svc_permissions = m_parent->GetSvcPermissions(); + + /* Get whether we have access to return from exception. */ + const bool return_from_exception = sp.svc_access_flags[svc::SvcId_ReturnFromException]; + + /* Copy permissions. */ + sp.svc_access_flags = svc_permissions; + + /* Clear specific SVCs based on our state. */ + sp.svc_access_flags[svc::SvcId_SynchronizePreemptionState] = false; + + if (!return_from_exception) { + sp.svc_access_flags[svc::SvcId_ReturnFromException] = false; + } + } + + ALWAYS_INLINE void KThread::SetUsermodeExceptionSvcPermissions() { + /* Get our stack parameters. */ + auto &sp = this->GetStackParameters(); + + /* Get our parent's svc permissions. */ + MESOSPHERE_ASSERT(m_parent != nullptr); + const auto &svc_permissions = m_parent->GetSvcPermissions(); + + /* Set ReturnFromException if allowed. */ + if (svc_permissions[svc::SvcId_ReturnFromException]) { + sp.svc_access_flags[svc::SvcId_ReturnFromException] = true; + } + + /* Set GetInfo if allowed. */ + if (svc_permissions[svc::SvcId_GetInfo]) { + sp.svc_access_flags[svc::SvcId_GetInfo] = true; + } + } + + ALWAYS_INLINE void KThread::ClearUsermodeExceptionSvcPermissions() { + /* Get our stack parameters. */ + auto &sp = this->GetStackParameters(); + + /* Clear ReturnFromException. */ + sp.svc_access_flags[svc::SvcId_ReturnFromException] = false; + + /* If pinned, clear GetInfo. */ + if (sp.is_pinned) { + sp.svc_access_flags[svc::SvcId_GetInfo] = false; + } + } + + Result KThread::Initialize(KThreadFunction func, uintptr_t arg, void *kern_stack_top, KProcessAddress user_stack_top, s32 prio, s32 virt_core, KProcess *owner, ThreadType type) { + /* Assert parameters are valid. */ + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(kern_stack_top != nullptr); + MESOSPHERE_ASSERT((type == ThreadType_Main) || (ams::svc::HighestThreadPriority <= prio && prio <= ams::svc::LowestThreadPriority)); + MESOSPHERE_ASSERT((owner != nullptr) || (type != ThreadType_User)); + MESOSPHERE_ASSERT(0 <= virt_core && virt_core < static_cast<s32>(BITSIZEOF(u64))); + + /* Convert the virtual core to a physical core. */ + const s32 phys_core = cpu::VirtualToPhysicalCoreMap[virt_core]; + MESOSPHERE_ASSERT(0 <= phys_core && phys_core < static_cast<s32>(cpu::NumCores)); + + /* First, clear the TLS address. */ + m_tls_address = Null<KProcessAddress>; + + const uintptr_t kern_stack_top_address = reinterpret_cast<uintptr_t>(kern_stack_top); + MESOSPHERE_UNUSED(kern_stack_top_address); + + /* Next, assert things based on the type. */ + switch (type) { + case ThreadType_Main: + { + MESOSPHERE_ASSERT(arg == 0); + } + [[fallthrough]]; + case ThreadType_HighPriority: + if (type != ThreadType_Main) { + MESOSPHERE_ASSERT(phys_core == GetCurrentCoreId()); + } + [[fallthrough]]; + case ThreadType_Kernel: + { + MESOSPHERE_ASSERT(user_stack_top == 0); + MESOSPHERE_ASSERT(util::IsAligned(kern_stack_top_address, PageSize)); + } + [[fallthrough]]; + case ThreadType_User: + { + MESOSPHERE_ASSERT(((owner == nullptr) || (owner->GetCoreMask() | (1ul << virt_core)) == owner->GetCoreMask())); + MESOSPHERE_ASSERT(((owner == nullptr) || (owner->GetPriorityMask() | (1ul << prio)) == owner->GetPriorityMask())); + } + break; + default: + MESOSPHERE_PANIC("KThread::Initialize: Unknown ThreadType %u", static_cast<u32>(type)); + break; + } + + /* Set the ideal core ID and affinity mask. */ + m_virtual_ideal_core_id = virt_core; + m_physical_ideal_core_id = phys_core; + m_virtual_affinity_mask = (static_cast<u64>(1) << virt_core); + m_physical_affinity_mask.SetAffinity(phys_core, true); + + /* Set the thread state. */ + m_thread_state = (type == ThreadType_Main) ? ThreadState_Runnable : ThreadState_Initialized; + + /* Set TLS address and TLS heap address. */ + /* NOTE: Nintendo wrote TLS address above already, but official code really does write tls address twice. */ + m_tls_address = 0; + m_tls_heap_address = 0; + + /* Set parent and condvar tree. */ + m_parent = nullptr; + m_condvar_tree = nullptr; + m_condvar_key = 0; + + /* Set sync booleans. */ + m_signaled = false; + m_termination_requested = false; + m_wait_cancelled = false; + m_cancellable = false; + + /* Set core ID and wait result. */ + m_core_id = phys_core; + m_wait_result = svc::ResultNoSynchronizationObject(); + + /* Set the stack top. */ + m_kernel_stack_top = kern_stack_top; + + /* Set priorities. */ + m_priority = prio; + m_base_priority = prio; + + /* Initialize wait queue/sync index. */ + m_synced_index = -1; + m_wait_queue = nullptr; + + /* Set suspend flags. */ + m_suspend_request_flags = 0; + m_suspend_allowed_flags = ThreadState_SuspendFlagMask; + + /* We're neither debug attached, nor are we nesting our priority inheritance. */ + m_debug_attached = false; + m_priority_inheritance_count = 0; + + /* We haven't been scheduled, and we have done no light IPC. */ + m_schedule_count = -1; + m_last_scheduled_tick = 0; + m_light_ipc_data = nullptr; + + /* We're not waiting for a lock, and we haven't disabled migration. */ + m_waiting_lock_info = nullptr; + m_num_core_migration_disables = 0; + + /* We have no waiters, and no closed objects. */ + m_num_kernel_waiters = 0; + m_closed_object = nullptr; + + /* Set our current core id. */ + m_current_core_id = phys_core; + + /* We haven't released our resource limit hint, and we've spent no time on the cpu. */ + m_resource_limit_release_hint = false; + m_cpu_time = 0; + + /* Setup our kernel stack. */ + if (type != ThreadType_Main) { + InitializeKernelStack(reinterpret_cast<uintptr_t>(kern_stack_top)); + } + + /* Clear our stack parameters. */ + std::memset(static_cast<void *>(std::addressof(this->GetStackParameters())), 0, sizeof(StackParameters)); + + /* Setup the TLS, if needed. */ + if (type == ThreadType_User) { + R_TRY(owner->CreateThreadLocalRegion(std::addressof(m_tls_address))); + m_tls_heap_address = owner->GetThreadLocalRegionPointer(m_tls_address); + std::memset(m_tls_heap_address, 0, ams::svc::ThreadLocalRegionSize); + } + + /* Set parent, if relevant. */ + if (owner != nullptr) { + m_parent = owner; + m_parent->Open(); + } + + /* Initialize thread context. */ + constexpr bool IsDefault64Bit = sizeof(uintptr_t) == sizeof(u64); + const bool is_64_bit = m_parent ? m_parent->Is64Bit() : IsDefault64Bit; + const bool is_user = (type == ThreadType_User); + const bool is_main = (type == ThreadType_Main); + this->GetContext().Initialize(reinterpret_cast<uintptr_t>(func), reinterpret_cast<uintptr_t>(this->GetStackTop()), GetInteger(user_stack_top), arg, is_user, is_64_bit, is_main); + + /* Setup the stack parameters. */ + StackParameters &sp = this->GetStackParameters(); + if (m_parent != nullptr) { + this->SetUnpinnedSvcPermissions(); + this->ClearUsermodeExceptionSvcPermissions(); + } + sp.caller_save_fpu_registers = std::addressof(m_caller_save_fpu_registers); + sp.cur_thread = this; + sp.disable_count = 1; + this->SetInExceptionHandler(); + + if (m_parent != nullptr && is_64_bit) { + this->SetFpu64Bit(); + } + + /* Set thread ID. */ + m_thread_id = g_thread_id++; + + /* We initialized! */ + m_initialized = true; + + /* Register ourselves with our parent process. */ + if (m_parent != nullptr) { + m_parent->RegisterThread(this); + if (m_parent->IsSuspended()) { + this->RequestSuspend(SuspendType_Process); + } + } + + R_SUCCEED(); + } + + Result KThread::InitializeThread(KThread *thread, KThreadFunction func, uintptr_t arg, KProcessAddress user_stack_top, s32 prio, s32 core, KProcess *owner, ThreadType type) { + /* Get stack region for the thread. */ + const auto &stack_region = KMemoryLayout::GetKernelStackRegion(); + MESOSPHERE_ABORT_UNLESS(stack_region.GetEndAddress() != 0); + + /* Allocate a page to use as the thread. */ + KPageBuffer *page = KPageBuffer::AllocateChecked<PageSize>(); + R_UNLESS(page != nullptr, svc::ResultOutOfResource()); + + /* Map the stack page. */ + KProcessAddress stack_top = Null<KProcessAddress>; + { + /* If we fail to map, avoid leaking the page. */ + ON_RESULT_FAILURE { KPageBuffer::Free(page); }; + + /* Perform the mapping. */ + KProcessAddress stack_bottom = Null<KProcessAddress>; + R_TRY(Kernel::GetKernelPageTable().MapPages(std::addressof(stack_bottom), 1, PageSize, page->GetPhysicalAddress(), stack_region.GetAddress(), + stack_region.GetSize() / PageSize, KMemoryState_Kernel, KMemoryPermission_KernelReadWrite)); + + /* Calculate top of the stack. */ + stack_top = stack_bottom + PageSize; + } + + /* If we fail, cleanup the stack we mapped. */ + ON_RESULT_FAILURE { CleanupKernelStack(GetInteger(stack_top)); }; + + /* Initialize the thread. */ + R_RETURN(thread->Initialize(func, arg, GetVoidPointer(stack_top), user_stack_top, prio, core, owner, type)); + } + + void KThread::PostDestroy(uintptr_t arg) { + KProcess *owner = reinterpret_cast<KProcess *>(arg & ~1ul); + const bool resource_limit_release_hint = (arg & 1); + const s64 hint_value = (resource_limit_release_hint ? 0 : 1); + if (owner != nullptr) { + owner->ReleaseResource(ams::svc::LimitableResource_ThreadCountMax, 1, hint_value); + owner->Close(); + } else { + Kernel::GetSystemResourceLimit().Release(ams::svc::LimitableResource_ThreadCountMax, 1, hint_value); + } + } + + void KThread::ResumeThreadsSuspendedForInit() { + KThread::ListAccessor list_accessor; + { + KScopedSchedulerLock sl; + + for (auto &thread : list_accessor) { + static_cast<KThread &>(thread).Resume(SuspendType_Init); + } + } + } + + void KThread::Finalize() { + MESOSPHERE_ASSERT_THIS(); + + /* If the thread has an owner process, unregister it. */ + if (m_parent != nullptr) { + m_parent->UnregisterThread(this); + } + + /* If the thread has a local region, delete it. */ + if (m_tls_address != Null<KProcessAddress>) { + MESOSPHERE_R_ABORT_UNLESS(m_parent->DeleteThreadLocalRegion(m_tls_address)); + } + + /* Release any waiters. */ + { + MESOSPHERE_ASSERT(m_waiting_lock_info == nullptr); + KScopedSchedulerLock sl; + + /* Check that we have no kernel waiters. */ + MESOSPHERE_ABORT_UNLESS(m_num_kernel_waiters == 0); + + auto it = m_held_lock_info_list.begin(); + while (it != m_held_lock_info_list.end()) { + /* Get the lock info. */ + auto * const lock_info = std::addressof(*it); + + /* The lock shouldn't have a kernel waiter. */ + MESOSPHERE_ASSERT(!IsKernelAddressKey(lock_info->GetAddressKey())); + + /* Remove all waiters. */ + while (lock_info->GetWaiterCount() != 0) { + /* Get the front waiter. */ + KThread * const waiter = lock_info->GetHighestPriorityWaiter(); + + /* Remove it from the lock. */ + if (lock_info->RemoveWaiter(waiter)) { + MESOSPHERE_ASSERT(lock_info->GetWaiterCount() == 0); + } + + /* Cancel the thread's wait. */ + waiter->CancelWait(svc::ResultInvalidState(), true); + } + + /* Remove the held lock from our list. */ + it = m_held_lock_info_list.erase(it); + + /* Free the lock info. */ + LockWithPriorityInheritanceInfo::Free(lock_info); + } + } + + /* Cleanup the kernel stack. */ + if (m_kernel_stack_top != nullptr) { + CleanupKernelStack(reinterpret_cast<uintptr_t>(m_kernel_stack_top)); + } + + /* Perform inherited finalization. */ + KSynchronizationObject::Finalize(); + } + + bool KThread::IsSignaled() const { + return m_signaled; + } + + void KThread::OnTimer() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + /* If we're waiting, cancel the wait. */ + if (this->GetState() == ThreadState_Waiting) { + m_wait_queue->CancelWait(this, svc::ResultTimedOut(), false); + } + } + + void KThread::StartTermination() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + /* Release user exception and unpin, if relevant. */ + if (m_parent != nullptr) { + m_parent->ReleaseUserException(this); + if (m_parent->GetPinnedThread(GetCurrentCoreId()) == this) { + m_parent->UnpinCurrentThread(); + } + } + + /* Set state to terminated. */ + this->SetState(KThread::ThreadState_Terminated); + + /* Clear the thread's status as running in parent. */ + if (m_parent != nullptr) { + m_parent->ClearRunningThread(this); + } + + /* Call the on thread termination handler. */ + KThreadContext::OnThreadTerminating(this); + + /* Clear previous thread in KScheduler. */ + KScheduler::ClearPreviousThread(this); + + /* Register terminated dpc flag. */ + this->RegisterDpc(DpcFlag_Terminated); + } + + void KThread::FinishTermination() { + MESOSPHERE_ASSERT_THIS(); + + /* Ensure that the thread is not executing on any core. */ + if (m_parent != nullptr) { + /* Wait for the thread to not be current on any core. */ + for (size_t i = 0; i < cpu::NumCores; ++i) { + KThread *core_thread; + do { + core_thread = Kernel::GetScheduler(i).GetSchedulerCurrentThread(); + } while (core_thread == this); + } + + /* Ensure that all cores are synchronized at this point. */ + cpu::SynchronizeCores(m_parent->GetPhysicalCoreMask()); + } + + /* Acquire the scheduler lock. */ + KScopedSchedulerLock sl; + + /* Signal. */ + m_signaled = true; + KSynchronizationObject::NotifyAvailable(); + + /* Close the thread. */ + this->Close(); + } + + void KThread::DoWorkerTaskImpl() { + /* Finish the termination that was begun by Exit(). */ + this->FinishTermination(); + } + + void KThread::OnEnterUsermodeException() { + this->SetUsermodeExceptionSvcPermissions(); + this->SetInUsermodeExceptionHandler(); + } + + void KThread::OnLeaveUsermodeException() { + this->ClearUsermodeExceptionSvcPermissions(); + + /* NOTE: InUsermodeExceptionHandler will be cleared by RestoreContext. */ + } + + void KThread::Pin() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + /* Set ourselves as pinned. */ + this->GetStackParameters().is_pinned = true; + + /* Disable core migration. */ + MESOSPHERE_ASSERT(m_num_core_migration_disables == 0); + { + ++m_num_core_migration_disables; + + /* Save our ideal state to restore when we're unpinned. */ + m_original_physical_ideal_core_id = m_physical_ideal_core_id; + m_original_physical_affinity_mask = m_physical_affinity_mask; + + /* Bind ourselves to this core. */ + const s32 active_core = this->GetActiveCore(); + const s32 current_core = GetCurrentCoreId(); + + this->SetActiveCore(current_core); + m_physical_ideal_core_id = current_core; + m_physical_affinity_mask.SetAffinityMask(1ul << current_core); + + if (active_core != current_core || m_physical_affinity_mask.GetAffinityMask() != m_original_physical_affinity_mask.GetAffinityMask()) { + KScheduler::OnThreadAffinityMaskChanged(this, m_original_physical_affinity_mask, active_core); + } + + /* Set base priority-on-unpin. */ + const s32 old_base_priority = m_base_priority; + m_base_priority_on_unpin = old_base_priority; + + /* Set base priority to higher than any possible process priority. */ + m_base_priority = std::min<s32>(old_base_priority, __builtin_ctzll(this->GetOwnerProcess()->GetPriorityMask()) - 1); + RestorePriority(this); + } + + /* Disallow performing thread suspension. */ + { + /* Update our allow flags. */ + m_suspend_allowed_flags &= ~(1 << (util::ToUnderlying(SuspendType_Thread) + util::ToUnderlying(ThreadState_SuspendShift))); + + /* Update our state. */ + this->UpdateState(); + } + + /* Update our SVC access permissions. */ + this->SetPinnedSvcPermissions(); + } + + void KThread::Unpin() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + /* Set ourselves as unpinned. */ + this->GetStackParameters().is_pinned = false; + + /* Enable core migration. */ + MESOSPHERE_ASSERT(m_num_core_migration_disables == 1); + { + --m_num_core_migration_disables; + + /* Restore our original state. */ + const KAffinityMask old_mask = m_physical_affinity_mask; + + m_physical_ideal_core_id = m_original_physical_ideal_core_id; + m_physical_affinity_mask = m_original_physical_affinity_mask; + + if (m_physical_affinity_mask.GetAffinityMask() != old_mask.GetAffinityMask()) { + const s32 active_core = this->GetActiveCore(); + + if (!m_physical_affinity_mask.GetAffinity(active_core)) { + if (m_physical_ideal_core_id >= 0) { + this->SetActiveCore(m_physical_ideal_core_id); + } else { + this->SetActiveCore(BITSIZEOF(unsigned long long) - 1 - __builtin_clzll(m_physical_affinity_mask.GetAffinityMask())); + } + } + KScheduler::OnThreadAffinityMaskChanged(this, old_mask, active_core); + } + + m_base_priority = m_base_priority_on_unpin; + RestorePriority(this); + } + + /* Allow performing thread suspension (if termination hasn't been requested). */ + if (!this->IsTerminationRequested()) { + /* Update our allow flags. */ + m_suspend_allowed_flags |= (1 << (util::ToUnderlying(SuspendType_Thread) + util::ToUnderlying(ThreadState_SuspendShift))); + + /* Update our state. */ + this->UpdateState(); + + /* Update our SVC access permissions. */ + MESOSPHERE_ASSERT(m_parent != nullptr); + this->SetUnpinnedSvcPermissions(); + } + + /* Resume any threads that began waiting on us while we were pinned. */ + for (auto it = m_pinned_waiter_list.begin(); it != m_pinned_waiter_list.end(); it = m_pinned_waiter_list.erase(it)) { + it->EndWait(ResultSuccess()); + } + } + + void KThread::DisableCoreMigration() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(this == GetCurrentThreadPointer()); + + KScopedSchedulerLock sl; + MESOSPHERE_ASSERT(m_num_core_migration_disables >= 0); + if ((m_num_core_migration_disables++) == 0) { + /* Save our ideal state to restore when we can migrate again. */ + m_original_physical_ideal_core_id = m_physical_ideal_core_id; + m_original_physical_affinity_mask = m_physical_affinity_mask; + + /* Bind ourselves to this core. */ + const s32 active_core = this->GetActiveCore(); + m_physical_ideal_core_id = active_core; + m_physical_affinity_mask.SetAffinityMask(1ul << active_core); + + if (m_physical_affinity_mask.GetAffinityMask() != m_original_physical_affinity_mask.GetAffinityMask()) { + KScheduler::OnThreadAffinityMaskChanged(this, m_original_physical_affinity_mask, active_core); + } + } + } + + void KThread::EnableCoreMigration() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(this == GetCurrentThreadPointer()); + + KScopedSchedulerLock sl; + MESOSPHERE_ASSERT(m_num_core_migration_disables > 0); + if ((--m_num_core_migration_disables) == 0) { + const KAffinityMask old_mask = m_physical_affinity_mask; + + /* Restore our ideals. */ + m_physical_ideal_core_id = m_original_physical_ideal_core_id; + m_physical_affinity_mask = m_original_physical_affinity_mask; + + if (m_physical_affinity_mask.GetAffinityMask() != old_mask.GetAffinityMask()) { + const s32 active_core = this->GetActiveCore(); + + if (!m_physical_affinity_mask.GetAffinity(active_core)) { + if (m_physical_ideal_core_id >= 0) { + this->SetActiveCore(m_physical_ideal_core_id); + } else { + this->SetActiveCore(BITSIZEOF(unsigned long long) - 1 - __builtin_clzll(m_physical_affinity_mask.GetAffinityMask())); + } + } + KScheduler::OnThreadAffinityMaskChanged(this, old_mask, active_core); + } + } + } + + Result KThread::GetCoreMask(int32_t *out_ideal_core, u64 *out_affinity_mask) { + MESOSPHERE_ASSERT_THIS(); + { + KScopedSchedulerLock sl; + + /* Get the virtual mask. */ + *out_ideal_core = m_virtual_ideal_core_id; + *out_affinity_mask = m_virtual_affinity_mask; + } + + R_SUCCEED(); + } + + Result KThread::GetPhysicalCoreMask(int32_t *out_ideal_core, u64 *out_affinity_mask) { + MESOSPHERE_ASSERT_THIS(); + { + KScopedSchedulerLock sl; + MESOSPHERE_ASSERT(m_num_core_migration_disables >= 0); + + /* Select between core mask and original core mask. */ + if (m_num_core_migration_disables == 0) { + *out_ideal_core = m_physical_ideal_core_id; + *out_affinity_mask = m_physical_affinity_mask.GetAffinityMask(); + } else { + *out_ideal_core = m_original_physical_ideal_core_id; + *out_affinity_mask = m_original_physical_affinity_mask.GetAffinityMask(); + } + } + + R_SUCCEED(); + } + + Result KThread::SetCoreMask(int32_t core_id, u64 v_affinity_mask) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(m_parent != nullptr); + MESOSPHERE_ASSERT(v_affinity_mask != 0); + KScopedLightLock lk(m_activity_pause_lock); + + /* Set the core mask. */ + u64 p_affinity_mask = 0; + { + KScopedSchedulerLock sl; + MESOSPHERE_ASSERT(m_num_core_migration_disables >= 0); + + /* If we're updating, set our ideal virtual core. */ + if (core_id != ams::svc::IdealCoreNoUpdate) { + m_virtual_ideal_core_id = core_id; + } else { + /* Preserve our ideal core id. */ + core_id = m_virtual_ideal_core_id; + R_UNLESS(((1ul << core_id) & v_affinity_mask) != 0, svc::ResultInvalidCombination()); + } + + /* Set our affinity mask. */ + m_virtual_affinity_mask = v_affinity_mask; + + /* Translate the virtual core to a physical core. */ + if (core_id >= 0) { + core_id = cpu::VirtualToPhysicalCoreMap[core_id]; + } + + /* Translate the virtual affinity mask to a physical one. */ + p_affinity_mask = cpu::ConvertVirtualCoreMaskToPhysical(v_affinity_mask); + + /* If we haven't disabled migration, perform an affinity change. */ + if (m_num_core_migration_disables == 0) { + const KAffinityMask old_mask = m_physical_affinity_mask; + + /* Set our new ideals. */ + m_physical_ideal_core_id = core_id; + m_physical_affinity_mask.SetAffinityMask(p_affinity_mask); + + if (m_physical_affinity_mask.GetAffinityMask() != old_mask.GetAffinityMask()) { + const s32 active_core = this->GetActiveCore(); + + if (active_core >= 0 && !m_physical_affinity_mask.GetAffinity(active_core)) { + const s32 new_core = m_physical_ideal_core_id >= 0 ? m_physical_ideal_core_id : BITSIZEOF(unsigned long long) - 1 - __builtin_clzll(m_physical_affinity_mask.GetAffinityMask()); + this->SetActiveCore(new_core); + } + KScheduler::OnThreadAffinityMaskChanged(this, old_mask, active_core); + } + } else { + /* Otherwise, we edit the original affinity for restoration later. */ + m_original_physical_ideal_core_id = core_id; + m_original_physical_affinity_mask.SetAffinityMask(p_affinity_mask); + } + } + + /* Update the pinned waiter list. */ + ThreadQueueImplForKThreadSetProperty wait_queue(std::addressof(m_pinned_waiter_list)); + { + bool retry_update; + do { + /* Lock the scheduler. */ + KScopedSchedulerLock sl; + + /* Don't do any further management if our termination has been requested. */ + R_SUCCEED_IF(this->IsTerminationRequested()); + + /* By default, we won't need to retry. */ + retry_update = false; + + /* Check if the thread is currently running. */ + bool thread_is_current = false; + s32 thread_core; + for (thread_core = 0; thread_core < static_cast<s32>(cpu::NumCores); ++thread_core) { + if (Kernel::GetScheduler(thread_core).GetSchedulerCurrentThread() == this) { + thread_is_current = true; + break; + } + } + + /* If the thread is currently running, check whether it's no longer allowed under the new mask. */ + if (thread_is_current && ((1ul << thread_core) & p_affinity_mask) == 0) { + /* If the thread is pinned, we want to wait until it's not pinned. */ + if (this->GetStackParameters().is_pinned) { + /* Verify that the current thread isn't terminating. */ + R_UNLESS(!GetCurrentThread().IsTerminationRequested(), svc::ResultTerminationRequested()); + + /* Wait until the thread isn't pinned any more. */ + m_pinned_waiter_list.push_back(GetCurrentThread()); + GetCurrentThread().BeginWait(std::addressof(wait_queue)); + } else { + /* If the thread isn't pinned, release the scheduler lock and retry until it's not current. */ + retry_update = true; + } + } + } while (retry_update); + } + + R_SUCCEED(); + } + + void KThread::SetBasePriority(s32 priority) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(ams::svc::HighestThreadPriority <= priority && priority <= ams::svc::LowestThreadPriority); + + KScopedSchedulerLock sl; + + /* Determine the priority value to use. */ + const s32 target_priority = m_termination_requested.Load() && priority >= TerminatingThreadPriority ? TerminatingThreadPriority : priority; + + /* Change our base priority. */ + if (this->GetStackParameters().is_pinned) { + m_base_priority_on_unpin = target_priority; + } else { + m_base_priority = target_priority; + } + + /* Perform a priority restoration. */ + RestorePriority(this); + } + + void KThread::IncreaseBasePriority(s32 priority) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(ams::svc::HighestThreadPriority <= priority && priority <= ams::svc::LowestThreadPriority); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + MESOSPHERE_ASSERT(!this->GetStackParameters().is_pinned); + + /* Set our base priority. */ + if (m_base_priority > priority) { + m_base_priority = priority; + + /* Perform a priority restoration. */ + RestorePriority(this); + } + } + + Result KThread::SetPriorityToIdle() { + MESOSPHERE_ASSERT_THIS(); + + KScopedSchedulerLock sl; + + /* Change both our priorities to the idle thread priority. */ + const s32 old_priority = m_priority; + m_priority = IdleThreadPriority; + m_base_priority = IdleThreadPriority; + KScheduler::OnThreadPriorityChanged(this, old_priority); + + R_SUCCEED(); + } + + void KThread::RequestSuspend(SuspendType type) { + MESOSPHERE_ASSERT_THIS(); + + KScopedSchedulerLock lk; + + /* Note the request in our flags. */ + m_suspend_request_flags |= (1u << (util::ToUnderlying(ThreadState_SuspendShift) + util::ToUnderlying(type))); + + /* Try to perform the suspend. */ + this->TrySuspend(); + } + + void KThread::Resume(SuspendType type) { + MESOSPHERE_ASSERT_THIS(); + + KScopedSchedulerLock sl; + + /* Clear the request in our flags. */ + m_suspend_request_flags &= ~(1u << (util::ToUnderlying(ThreadState_SuspendShift) + util::ToUnderlying(type))); + + /* Update our state. */ + this->UpdateState(); + } + + void KThread::WaitCancel() { + MESOSPHERE_ASSERT_THIS(); + + KScopedSchedulerLock sl; + + /* Check if we're waiting and cancellable. */ + if (this->GetState() == ThreadState_Waiting && m_cancellable) { + m_wait_cancelled = false; + m_wait_queue->CancelWait(this, svc::ResultCancelled(), true); + } else { + /* Otherwise, note that we cancelled a wait. */ + m_wait_cancelled = true; + } + } + + void KThread::TrySuspend() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + MESOSPHERE_ASSERT(this->IsSuspendRequested()); + + /* Ensure that we have no waiters. */ + if (this->GetNumKernelWaiters() > 0) { + return; + } + MESOSPHERE_ABORT_UNLESS(this->GetNumKernelWaiters() == 0); + + /* Perform the suspend. */ + this->UpdateState(); + } + + void KThread::UpdateState() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + /* Set our suspend flags in state. */ + const auto old_state = m_thread_state; + const auto new_state = static_cast<ThreadState>(this->GetSuspendFlags() | (old_state & ThreadState_Mask)); + m_thread_state = new_state; + + /* Note the state change in scheduler. */ + if (new_state != old_state) { + KScheduler::OnThreadStateChanged(this, old_state); + } + } + + void KThread::Continue() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + /* Clear our suspend flags in state. */ + const auto old_state = m_thread_state; + m_thread_state = static_cast<ThreadState>(old_state & ThreadState_Mask); + + /* Note the state change in scheduler. */ + KScheduler::OnThreadStateChanged(this, old_state); + } + + size_t KThread::GetKernelStackUsage() const { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(m_kernel_stack_top != nullptr); + + #if defined(MESOSPHERE_ENABLE_KERNEL_STACK_USAGE) + const u8 *stack = static_cast<const u8 *>(m_kernel_stack_top) - PageSize; + + size_t i; + for (i = 0; i < PageSize; ++i) { + if (stack[i] != 0xCC) { + break; + } + } + + return PageSize - i; + #else + return 0; + #endif + } + + Result KThread::SetActivity(ams::svc::ThreadActivity activity) { + /* Lock ourselves. */ + KScopedLightLock lk(m_activity_pause_lock); + + /* Set the activity. */ + { + /* Lock the scheduler. */ + KScopedSchedulerLock sl; + + /* Verify our state. */ + const auto cur_state = this->GetState(); + R_UNLESS((cur_state == ThreadState_Waiting || cur_state == ThreadState_Runnable), svc::ResultInvalidState()); + + /* Either pause or resume. */ + if (activity == ams::svc::ThreadActivity_Paused) { + /* Verify that we're not suspended. */ + R_UNLESS(!this->IsSuspendRequested(SuspendType_Thread), svc::ResultInvalidState()); + + /* Suspend. */ + this->RequestSuspend(SuspendType_Thread); + } else { + MESOSPHERE_ASSERT(activity == ams::svc::ThreadActivity_Runnable); + + /* Verify that we're suspended. */ + R_UNLESS(this->IsSuspendRequested(SuspendType_Thread), svc::ResultInvalidState()); + + /* Resume. */ + this->Resume(SuspendType_Thread); + } + } + + /* If the thread is now paused, update the pinned waiter list. */ + if (activity == ams::svc::ThreadActivity_Paused) { + ThreadQueueImplForKThreadSetProperty wait_queue(std::addressof(m_pinned_waiter_list)); + + bool thread_is_current; + do { + /* Lock the scheduler. */ + KScopedSchedulerLock sl; + + /* Don't do any further management if our termination has been requested. */ + R_SUCCEED_IF(this->IsTerminationRequested()); + + /* By default, treat the thread as not current. */ + thread_is_current = false; + + /* Check whether the thread is pinned. */ + if (this->GetStackParameters().is_pinned) { + /* Verify that the current thread isn't terminating. */ + R_UNLESS(!GetCurrentThread().IsTerminationRequested(), svc::ResultTerminationRequested()); + + /* Wait until the thread isn't pinned any more. */ + m_pinned_waiter_list.push_back(GetCurrentThread()); + GetCurrentThread().BeginWait(std::addressof(wait_queue)); + } else { + /* Check if the thread is currently running. */ + /* If it is, we'll need to retry. */ + for (auto i = 0; i < static_cast<s32>(cpu::NumCores); ++i) { + if (Kernel::GetScheduler(i).GetSchedulerCurrentThread() == this) { + thread_is_current = true; + break; + } + } + } + } while (thread_is_current); + } + + R_SUCCEED(); + } + + Result KThread::GetThreadContext3(ams::svc::ThreadContext *out) { + /* Lock ourselves. */ + KScopedLightLock lk(m_activity_pause_lock); + + /* Get the context. */ + { + /* Lock the scheduler. */ + KScopedSchedulerLock sl; + + /* Verify that we're suspended. */ + R_UNLESS(this->IsSuspendRequested(SuspendType_Thread), svc::ResultInvalidState()); + + /* If we're not terminating, get the thread's user context. */ + if (!this->IsTerminationRequested()) { + GetUserContext(out, this); + } + } + + R_SUCCEED(); + } + + void KThread::AddHeldLock(LockWithPriorityInheritanceInfo *lock_info) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + /* Set ourselves as the lock's owner. */ + lock_info->SetOwner(this); + + /* Add the lock to our held list. */ + m_held_lock_info_list.push_front(*lock_info); + } + + KThread::LockWithPriorityInheritanceInfo *KThread::FindHeldLock(KProcessAddress address_key) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + /* Try to find an existing held lock. */ + for (auto &held_lock : m_held_lock_info_list) { + if (held_lock.GetAddressKey() == address_key) { + return std::addressof(held_lock); + } + } + + return nullptr; + } + + + void KThread::AddWaiterImpl(KThread *thread) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + MESOSPHERE_ASSERT(thread->GetConditionVariableTree() == nullptr); + + /* Get the thread's address key. */ + const auto address_key = thread->GetAddressKey(); + + /* Keep track of how many kernel waiters we have. */ + if (IsKernelAddressKey(address_key)) { + MESOSPHERE_ABORT_UNLESS((m_num_kernel_waiters++) >= 0); + KScheduler::SetSchedulerUpdateNeeded(); + } + + /* Get the relevant lock info. */ + auto *lock_info = this->FindHeldLock(address_key); + if (lock_info == nullptr) { + /* Create a new lock for the address key. */ + lock_info = LockWithPriorityInheritanceInfo::Create(address_key); + + /* Add the new lock to our list. */ + this->AddHeldLock(lock_info); + } + + /* Add the thread as waiter to the lock info. */ + lock_info->AddWaiter(thread); + } + + void KThread::RemoveWaiterImpl(KThread *thread) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + /* Keep track of how many kernel waiters we have. */ + if (IsKernelAddressKey(thread->GetAddressKey())) { + MESOSPHERE_ABORT_UNLESS((m_num_kernel_waiters--) > 0); + KScheduler::SetSchedulerUpdateNeeded(); + } + + /* Get the info for the lock the thread is waiting on. */ + auto *lock_info = thread->GetWaitingLockInfo(); + MESOSPHERE_ASSERT(lock_info->GetOwner() == this); + + /* Remove the waiter. */ + if (lock_info->RemoveWaiter(thread)) { + m_held_lock_info_list.erase(m_held_lock_info_list.iterator_to(*lock_info)); + LockWithPriorityInheritanceInfo::Free(lock_info); + } + } + + void KThread::RestorePriority(KThread *thread) { + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + while (thread != nullptr) { + /* We want to inherit priority where possible. */ + s32 new_priority = thread->GetBasePriority(); + for (const auto &held_lock : thread->m_held_lock_info_list) { + new_priority = std::min(new_priority, held_lock.GetHighestPriorityWaiter()->GetPriority()); + } + + /* If the priority we would inherit is not different from ours, don't do anything. */ + if (new_priority == thread->GetPriority()) { + return; + } + + /* Get the owner of whatever lock this thread is waiting on. */ + KThread * const lock_owner = thread->GetLockOwner(); + + /* If the thread is waiting on some lock, remove it as a waiter to prevent violating red black tree invariants. */ + if (lock_owner != nullptr) { + lock_owner->RemoveWaiterImpl(thread); + } + + /* Ensure we don't violate condition variable red black tree invariants. */ + if (auto *cv_tree = thread->GetConditionVariableTree(); cv_tree != nullptr) { + BeforeUpdatePriority(cv_tree, thread); + } + + /* Change the priority. */ + const s32 old_priority = thread->GetPriority(); + thread->SetPriority(new_priority); + + /* Restore the condition variable, if relevant. */ + if (auto *cv_tree = thread->GetConditionVariableTree(); cv_tree != nullptr) { + AfterUpdatePriority(cv_tree, thread); + } + + /* If we removed the thread from some lock's waiting list, add it back. */ + if (lock_owner != nullptr) { + lock_owner->AddWaiterImpl(thread); + } + + /* Update the scheduler. */ + KScheduler::OnThreadPriorityChanged(thread, old_priority); + + /* Continue inheriting priority. */ + thread = lock_owner; + } + } + + void KThread::AddWaiter(KThread *thread) { + MESOSPHERE_ASSERT_THIS(); + + this->AddWaiterImpl(thread); + + /* If the thread has a higher priority than us, we should inherit. */ + if (thread->GetPriority() < this->GetPriority()) { + RestorePriority(this); + } + } + + void KThread::RemoveWaiter(KThread *thread) { + MESOSPHERE_ASSERT_THIS(); + this->RemoveWaiterImpl(thread); + + /* If our priority is the same as the thread's (and we've inherited), we may need to restore to lower priority. */ + if (this->GetPriority() == thread->GetPriority() && this->GetPriority() < this->GetBasePriority()) { + RestorePriority(this); + } + } + + KThread *KThread::RemoveWaiterByKey(bool *out_has_waiters, KProcessAddress key) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + /* Get the relevant lock info. */ + auto *lock_info = this->FindHeldLock(key); + if (lock_info == nullptr) { + *out_has_waiters = false; + return nullptr; + } + + /* Remove the lock info from our held list. */ + m_held_lock_info_list.erase(m_held_lock_info_list.iterator_to(*lock_info)); + + /* Keep track of how many kernel waiters we have. */ + if (IsKernelAddressKey(lock_info->GetAddressKey())) { + m_num_kernel_waiters -= lock_info->GetWaiterCount(); + MESOSPHERE_ABORT_UNLESS(m_num_kernel_waiters >= 0); + KScheduler::SetSchedulerUpdateNeeded(); + } + + MESOSPHERE_ASSERT(lock_info->GetWaiterCount() > 0); + + /* Remove the highest priority waiter from the lock to be the next owner. */ + KThread *next_lock_owner = lock_info->GetHighestPriorityWaiter(); + if (lock_info->RemoveWaiter(next_lock_owner)) { + /* The new owner was the only waiter. */ + *out_has_waiters = false; + + /* Free the lock info, since it has no waiters. */ + LockWithPriorityInheritanceInfo::Free(lock_info); + } else { + /* There are additional waiters on the lock. */ + *out_has_waiters = true; + + /* Add the lock to the new owner's held list. */ + next_lock_owner->AddHeldLock(lock_info); + + /* Keep track of any kernel waiters for the new owner. */ + if (IsKernelAddressKey(lock_info->GetAddressKey())) { + next_lock_owner->m_num_kernel_waiters += lock_info->GetWaiterCount(); + MESOSPHERE_ABORT_UNLESS(next_lock_owner->m_num_kernel_waiters > 0); + + /* NOTE: No need to set scheduler update needed, because we will have already done so when removing earlier. */ + } + } + + /* If our priority is the same as the next owner's (and we've inherited), we may need to restore to lower priority. */ + if (this->GetPriority() == next_lock_owner->GetPriority() && this->GetPriority() < this->GetBasePriority()) { + RestorePriority(this); + /* NOTE: No need to restore priority on the next lock owner, because it was already the highest priority waiter on the lock. */ + } + + /* Return the next lock owner. */ + return next_lock_owner; + } + + Result KThread::Run() { + MESOSPHERE_ASSERT_THIS(); + + /* If the kernel hasn't finished initializing, then we should suspend. */ + if (Kernel::GetState() != Kernel::State::Initialized) { + this->RequestSuspend(SuspendType_Init); + } + while (true) { + KScopedSchedulerLock lk; + + /* If either this thread or the current thread are requesting termination, note it. */ + R_UNLESS(!this->IsTerminationRequested(), svc::ResultTerminationRequested()); + R_UNLESS(!GetCurrentThread().IsTerminationRequested(), svc::ResultTerminationRequested()); + + /* Ensure our thread state is correct. */ + R_UNLESS(this->GetState() == ThreadState_Initialized, svc::ResultInvalidState()); + + /* If the current thread has been asked to suspend, suspend it and retry. */ + if (GetCurrentThread().IsSuspended()) { + GetCurrentThread().UpdateState(); + continue; + } + + /* If we're not a kernel thread and we've been asked to suspend, suspend ourselves. */ + if (KProcess *parent = this->GetOwnerProcess(); parent != nullptr) { + if (this->IsSuspended()) { + this->UpdateState(); + } + parent->IncrementRunningThreadCount(); + } + + /* Open a reference, now that we're running. */ + this->Open(); + + /* Set our state and finish. */ + this->SetState(KThread::ThreadState_Runnable); + R_SUCCEED(); + } + } + + void KThread::Exit() { + MESOSPHERE_ASSERT_THIS(); + + MESOSPHERE_ASSERT(this == GetCurrentThreadPointer()); + + /* Call the debug callback. */ + KDebug::OnExitThread(this); + + /* Release the thread resource hint, running thread count from parent. */ + if (m_parent != nullptr) { + m_parent->ReleaseResource(ams::svc::LimitableResource_ThreadCountMax, 0, 1); + m_resource_limit_release_hint = true; + m_parent->DecrementRunningThreadCount(); + } + + /* Destroy any dependent objects. */ + this->DestroyClosedObjects(); + + /* Perform termination. */ + { + KScopedSchedulerLock sl; + + /* Disallow all suspension. */ + m_suspend_allowed_flags = 0; + this->UpdateState(); + + /* Start termination. */ + this->StartTermination(); + + /* Register the thread as a work task. */ + KWorkerTaskManager::AddTask(KWorkerTaskManager::WorkerType_ExitThread, this); + } + + MESOSPHERE_PANIC("KThread::Exit() would return"); + } + + Result KThread::Terminate() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(this != GetCurrentThreadPointer()); + + /* Request the thread terminate if it hasn't already. */ + if (const auto new_state = this->RequestTerminate(); new_state != ThreadState_Terminated) { + /* If the thread isn't terminated, wait for it to terminate. */ + s32 index; + KSynchronizationObject *objects[] = { this }; + R_TRY(KSynchronizationObject::Wait(std::addressof(index), objects, 1, ams::svc::WaitInfinite)); + } + + R_SUCCEED(); + } + + KThread::ThreadState KThread::RequestTerminate() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(this != GetCurrentThreadPointer()); + + KScopedSchedulerLock sl; + + /* Determine if this is the first termination request. */ + const bool first_request = [&]() ALWAYS_INLINE_LAMBDA -> bool { + /* Perform an atomic compare-and-swap from false to true. */ + bool expected = false; + return m_termination_requested.CompareExchangeStrong(expected, true); + }(); + + /* If this is the first request, start termination procedure. */ + if (first_request) { + /* If the thread is in initialized state, just change state to terminated. */ + if (this->GetState() == ThreadState_Initialized) { + m_thread_state = ThreadState_Terminated; + return ThreadState_Terminated; + } + + /* Register the terminating dpc. */ + this->RegisterDpc(DpcFlag_Terminating); + + /* If the thread is pinned, unpin it. */ + if (this->GetStackParameters().is_pinned) { + this->GetOwnerProcess()->UnpinThread(this); + } + + /* If the thread is suspended, continue it. */ + if (this->IsSuspended()) { + m_suspend_allowed_flags = 0; + this->UpdateState(); + } + + /* Change the thread's priority to be higher than any system thread's. */ + this->IncreaseBasePriority(TerminatingThreadPriority); + + /* If the thread is runnable, send a termination interrupt to other cores. */ + if (this->GetState() == ThreadState_Runnable) { + if (const u64 core_mask = m_physical_affinity_mask.GetAffinityMask() & ~(1ul << GetCurrentCoreId()); core_mask != 0) { + cpu::DataSynchronizationBarrierInnerShareable(); + Kernel::GetInterruptManager().SendInterProcessorInterrupt(KInterruptName_ThreadTerminate, core_mask); + } + } + + /* Wake up the thread. */ + if (this->GetState() == ThreadState_Waiting) { + m_wait_queue->CancelWait(this, svc::ResultTerminationRequested(), true); + } + } + + return this->GetState(); + } + + Result KThread::Sleep(s64 timeout) { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(!KScheduler::IsSchedulerLockedByCurrentThread()); + MESOSPHERE_ASSERT(this == GetCurrentThreadPointer()); + MESOSPHERE_ASSERT(timeout > 0); + + ThreadQueueImplForKThreadSleep wait_queue; + KHardwareTimer *timer; + { + /* Setup the scheduling lock and sleep. */ + KScopedSchedulerLockAndSleep slp(std::addressof(timer), this, timeout); + + /* Check if the thread should terminate. */ + if (this->IsTerminationRequested()) { + slp.CancelSleep(); + R_THROW(svc::ResultTerminationRequested()); + } + + /* Wait for the sleep to end. */ + wait_queue.SetHardwareTimer(timer); + this->BeginWait(std::addressof(wait_queue)); + } + + R_SUCCEED(); + } + + void KThread::BeginWait(KThreadQueue *queue) { + /* Set our state as waiting. */ + this->SetState(ThreadState_Waiting); + + /* Set our wait queue. */ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdangling-pointer" + m_wait_queue = queue; + #pragma GCC diagnostic pop + } + + void KThread::NotifyAvailable(KSynchronizationObject *signaled_object, Result wait_result) { + MESOSPHERE_ASSERT_THIS(); + + /* Lock the scheduler. */ + KScopedSchedulerLock sl; + + /* If we're waiting, notify our queue that we're available. */ + if (this->GetState() == ThreadState_Waiting) { + m_wait_queue->NotifyAvailable(this, signaled_object, wait_result); + } + } + + void KThread::EndWait(Result wait_result) { + MESOSPHERE_ASSERT_THIS(); + + /* Lock the scheduler. */ + KScopedSchedulerLock sl; + + /* If we're waiting, notify our queue that we're available. */ + if (this->GetState() == ThreadState_Waiting) { + m_wait_queue->EndWait(this, wait_result); + } + } + + void KThread::CancelWait(Result wait_result, bool cancel_timer_task) { + MESOSPHERE_ASSERT_THIS(); + + /* Lock the scheduler. */ + KScopedSchedulerLock sl; + + /* If we're waiting, notify our queue that we're available. */ + if (this->GetState() == ThreadState_Waiting) { + m_wait_queue->CancelWait(this, wait_result, cancel_timer_task); + } + } + + void KThread::SetState(ThreadState state) { + MESOSPHERE_ASSERT_THIS(); + + KScopedSchedulerLock sl; + + const ThreadState old_state = m_thread_state; + m_thread_state = static_cast<ThreadState>((old_state & ~ThreadState_Mask) | (state & ThreadState_Mask)); + if (m_thread_state != old_state) { + KScheduler::OnThreadStateChanged(this, old_state); + } + } + + KThread *KThread::GetThreadFromId(u64 thread_id) { + /* Lock the list. */ + KThread::ListAccessor accessor; + const auto end = accessor.end(); + + /* Find the object with the right id. */ + if (const auto it = accessor.find_key(thread_id); it != end) { + /* Try to open the thread. */ + if (KThread *thread = static_cast<KThread *>(std::addressof(*it)); AMS_LIKELY(thread->Open())) { + MESOSPHERE_ASSERT(thread->GetId() == thread_id); + return thread; + } + } + + /* We failed to find or couldn't open the thread. */ + return nullptr; + } + + Result KThread::GetThreadList(s32 *out_num_threads, ams::kern::svc::KUserPointer<u64 *> out_thread_ids, s32 max_out_count) { + /* Lock the list. */ + KThread::ListAccessor accessor; + const auto end = accessor.end(); + + /* Iterate over the list. */ + s32 count = 0; + for (auto it = accessor.begin(); it != end; ++it) { + /* If we're within array bounds, write the id. */ + if (count < max_out_count) { + /* Get the thread id. */ + KThread *thread = static_cast<KThread *>(std::addressof(*it)); + const u64 id = thread->GetId(); + + /* Copy the id to userland. */ + R_TRY(out_thread_ids.CopyArrayElementFrom(std::addressof(id), count)); + } + + /* Increment the count. */ + ++count; + } + + /* We successfully iterated the list. */ + *out_num_threads = count; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_thread_local_page.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_thread_local_page.cpp new file mode 100644 index 00000000..e0358cc1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_thread_local_page.cpp @@ -0,0 +1,77 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + Result KThreadLocalPage::Initialize(KProcess *process) { + MESOSPHERE_ASSERT_THIS(); + + /* Set that this process owns us. */ + m_owner = process; + + /* Allocate a new page. */ + KPageBuffer *page_buf = KPageBuffer::AllocateChecked<PageSize>(); + R_UNLESS(page_buf != nullptr, svc::ResultOutOfMemory()); + ON_RESULT_FAILURE { KPageBuffer::Free(page_buf); }; + + /* Map the address in. */ + R_RETURN(m_owner->GetPageTable().MapPages(std::addressof(m_virt_addr), 1, PageSize, page_buf->GetPhysicalAddress(), KMemoryState_ThreadLocal, KMemoryPermission_UserReadWrite)); + } + + Result KThreadLocalPage::Finalize() { + MESOSPHERE_ASSERT_THIS(); + + /* Get the physical address of the page. */ + KPhysicalAddress phys_addr = Null<KPhysicalAddress>; + MESOSPHERE_ABORT_UNLESS(m_owner->GetPageTable().GetPhysicalAddress(std::addressof(phys_addr), this->GetAddress())); + + /* Unmap the page. */ + R_TRY(m_owner->GetPageTable().UnmapPages(this->GetAddress(), 1, KMemoryState_ThreadLocal)); + + /* Free the page. */ + KPageBuffer::FreeChecked<PageSize>(KPageBuffer::FromPhysicalAddress(phys_addr)); + R_SUCCEED(); + } + + KProcessAddress KThreadLocalPage::Reserve() { + MESOSPHERE_ASSERT_THIS(); + + for (size_t i = 0; i < util::size(m_is_region_free); i++) { + if (m_is_region_free[i]) { + m_is_region_free[i] = false; + return this->GetRegionAddress(i); + } + } + + return Null<KProcessAddress>; + } + + void KThreadLocalPage::Release(KProcessAddress addr) { + MESOSPHERE_ASSERT_THIS(); + + m_is_region_free[this->GetRegionIndex(addr)] = true; + } + + void *KThreadLocalPage::GetPointer() const { + MESOSPHERE_ASSERT_THIS(); + + KPhysicalAddress phys_addr; + MESOSPHERE_ABORT_UNLESS(m_owner->GetPageTable().GetPhysicalAddress(std::addressof(phys_addr), this->GetAddress())); + return static_cast<void *>(KPageBuffer::FromPhysicalAddress(phys_addr)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_thread_queue.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_thread_queue.cpp new file mode 100644 index 00000000..ea07fa46 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_thread_queue.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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + void KThreadQueue::NotifyAvailable(KThread *waiting_thread, KSynchronizationObject *signaled_object, Result wait_result) { + MESOSPHERE_UNUSED(waiting_thread, signaled_object, wait_result); + MESOSPHERE_PANIC("KThreadQueue::NotifyAvailable\n"); + } + + void KThreadQueue::EndWait(KThread *waiting_thread, Result wait_result) { + /* Set the thread's wait result. */ + waiting_thread->SetWaitResult(wait_result); + + /* Set the thread as runnable. */ + waiting_thread->SetState(KThread::ThreadState_Runnable); + + /* Clear the thread's wait queue. */ + waiting_thread->ClearWaitQueue(); + + /* Cancel the thread task. */ + if (m_hardware_timer != nullptr) { + m_hardware_timer->CancelTask(waiting_thread); + } + } + + void KThreadQueue::CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) { + /* Set the thread's wait result. */ + waiting_thread->SetWaitResult(wait_result); + + /* Set the thread as runnable. */ + waiting_thread->SetState(KThread::ThreadState_Runnable); + + /* Clear the thread's wait queue. */ + waiting_thread->ClearWaitQueue(); + + /* Cancel the thread task. */ + if (cancel_timer_task && m_hardware_timer != nullptr) { + m_hardware_timer->CancelTask(waiting_thread); + } + } + + void KThreadQueueWithoutEndWait::EndWait(KThread *waiting_thread, Result wait_result) { + MESOSPHERE_UNUSED(waiting_thread, wait_result); + MESOSPHERE_PANIC("KThreadQueueWithoutEndWait::EndWait\n"); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_trace.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_trace.cpp new file mode 100644 index 00000000..073c803c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_trace.cpp @@ -0,0 +1,150 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + /* Static initializations. */ + constinit bool KTrace::s_is_active = false; + + namespace { + + constinit KSpinLock g_ktrace_lock; + constinit KVirtualAddress g_ktrace_buffer_address = Null<KVirtualAddress>; + constinit size_t g_ktrace_buffer_size = 0; + constinit u64 g_type_filter = 0; + + struct KTraceHeader { + u32 magic; + u32 offset; + u32 index; + u32 count; + + static constexpr u32 Magic = util::FourCC<'K','T','R','0'>::Code; + }; + static_assert(util::is_pod<KTraceHeader>::value); + + struct KTraceRecord { + u8 core_id; + u8 type; + u16 process_id; + u32 thread_id; + u64 tick; + u64 data[6]; + }; + static_assert(util::is_pod<KTraceRecord>::value); + static_assert(sizeof(KTraceRecord) == 0x40); + + ALWAYS_INLINE bool IsTypeFiltered(u8 type) { + return (g_type_filter & (UINT64_C(1) << (type & (BITSIZEOF(u64) - 1)))) != 0; + } + + } + + void KTrace::Initialize(KVirtualAddress address, size_t size) { + /* Only perform tracing when on development hardware. */ + if (KTargetSystem::IsDebugMode()) { + const size_t offset = util::AlignUp(sizeof(KTraceHeader), sizeof(KTraceRecord)); + if (offset < size) { + /* Clear the trace buffer. */ + std::memset(GetVoidPointer(address), 0, size); + + /* Initialize the KTrace header. */ + KTraceHeader *header = GetPointer<KTraceHeader>(address); + header->magic = KTraceHeader::Magic; + header->offset = offset; + header->index = 0; + header->count = (size - offset) / sizeof(KTraceRecord); + + /* Set the global data. */ + g_ktrace_buffer_address = address; + g_ktrace_buffer_size = size; + + /* Set the filters to defaults. */ + g_type_filter = ~(UINT64_C(0)); + } + } + } + + void KTrace::Start() { + if (g_ktrace_buffer_address != Null<KVirtualAddress>) { + /* Get exclusive access to the trace buffer. */ + KScopedInterruptDisable di; + KScopedSpinLock lk(g_ktrace_lock); + + /* Reset the header. */ + KTraceHeader *header = GetPointer<KTraceHeader>(g_ktrace_buffer_address); + header->index = 0; + + /* Reset the records. */ + KTraceRecord *records = GetPointer<KTraceRecord>(g_ktrace_buffer_address + header->offset); + std::memset(records, 0, sizeof(*records) * header->count); + + /* Note that we're active. */ + s_is_active = true; + } + } + + void KTrace::Stop() { + if (g_ktrace_buffer_address != Null<KVirtualAddress>) { + /* Get exclusive access to the trace buffer. */ + KScopedInterruptDisable di; + KScopedSpinLock lk(g_ktrace_lock); + + /* Note that we're paused. */ + s_is_active = false; + } + } + + void KTrace::PushRecord(u8 type, u64 param0, u64 param1, u64 param2, u64 param3, u64 param4, u64 param5) { + /* Get exclusive access to the trace buffer. */ + KScopedInterruptDisable di; + KScopedSpinLock lk(g_ktrace_lock); + + /* Check whether we should push the record to the trace buffer. */ + if (s_is_active && IsTypeFiltered(type)) { + /* Get the current thread and process. */ + KThread &cur_thread = GetCurrentThread(); + KProcess *cur_process = GetCurrentProcessPointer(); + + /* Get the current record index from the header. */ + KTraceHeader *header = GetPointer<KTraceHeader>(g_ktrace_buffer_address); + u32 index = header->index; + + /* Get the current record. */ + KTraceRecord *record = GetPointer<KTraceRecord>(g_ktrace_buffer_address + header->offset + index * sizeof(KTraceRecord)); + + /* Set the record's data. */ + *record = { + .core_id = static_cast<u8>(GetCurrentCoreId()), + .type = type, + .process_id = static_cast<u16>(cur_process != nullptr ? cur_process->GetId() : ~0), + .thread_id = static_cast<u32>(cur_thread.GetId()), + .tick = static_cast<u64>(KHardwareTimer::GetTick()), + .data = { param0, param1, param2, param3, param4, param5 }, + }; + + /* Advance the current index. */ + if ((++index) >= header->count) { + index = 0; + } + + /* Set the next index. */ + header->index = index; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_transfer_memory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_transfer_memory.cpp new file mode 100644 index 00000000..588510ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_transfer_memory.cpp @@ -0,0 +1,112 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + Result KTransferMemory::Initialize(KProcessAddress addr, size_t size, ams::svc::MemoryPermission own_perm) { + MESOSPHERE_ASSERT_THIS(); + + /* Set members. */ + m_owner = GetCurrentProcessPointer(); + + /* Get the owner page table. */ + auto &page_table = m_owner->GetPageTable(); + + /* Construct the page group, guarding to make sure our state is valid on exit. */ + auto pg_guard = util::ConstructAtGuarded(m_page_group, page_table.GetBlockInfoManager()); + + /* Lock the memory. */ + R_TRY(page_table.LockForTransferMemory(GetPointer(m_page_group), addr, size, ConvertToKMemoryPermission(own_perm))); + + /* Set remaining tracking members. */ + m_owner->Open(); + m_owner_perm = own_perm; + m_address = addr; + m_is_initialized = true; + m_is_mapped = false; + + /* We succeeded. */ + pg_guard.Cancel(); + R_SUCCEED(); + } + + void KTransferMemory::Finalize() { + MESOSPHERE_ASSERT_THIS(); + + /* Unlock. */ + if (!m_is_mapped) { + const size_t size = GetReference(m_page_group).GetNumPages() * PageSize; + MESOSPHERE_R_ABORT_UNLESS(m_owner->GetPageTable().UnlockForTransferMemory(m_address, size, GetReference(m_page_group))); + } + + /* Close the page group. */ + GetReference(m_page_group).Close(); + GetReference(m_page_group).Finalize(); + } + + void KTransferMemory::PostDestroy(uintptr_t arg) { + KProcess *owner = reinterpret_cast<KProcess *>(arg); + owner->ReleaseResource(ams::svc::LimitableResource_TransferMemoryCountMax, 1); + owner->Close(); + } + + Result KTransferMemory::Map(KProcessAddress address, size_t size, ams::svc::MemoryPermission map_perm) { + MESOSPHERE_ASSERT_THIS(); + + /* Validate the size. */ + R_UNLESS(GetReference(m_page_group).GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize()); + + /* Validate the permission. */ + R_UNLESS(m_owner_perm == map_perm, svc::ResultInvalidState()); + + /* Lock ourselves. */ + KScopedLightLock lk(m_lock); + + /* Ensure we're not already mapped. */ + R_UNLESS(!m_is_mapped, svc::ResultInvalidState()); + + /* Map the memory. */ + const KMemoryState state = (m_owner_perm == ams::svc::MemoryPermission_None) ? KMemoryState_Transfered : KMemoryState_SharedTransfered; + R_TRY(GetCurrentProcess().GetPageTable().MapPageGroup(address, GetReference(m_page_group), state, KMemoryPermission_UserReadWrite)); + + /* Mark ourselves as mapped. */ + m_is_mapped = true; + + R_SUCCEED(); + } + + Result KTransferMemory::Unmap(KProcessAddress address, size_t size) { + MESOSPHERE_ASSERT_THIS(); + + /* Validate the size. */ + R_UNLESS(GetReference(m_page_group).GetNumPages() == util::DivideUp(size, PageSize), svc::ResultInvalidSize()); + + /* Lock ourselves. */ + KScopedLightLock lk(m_lock); + + /* Unmap the memory. */ + const KMemoryState state = (m_owner_perm == ams::svc::MemoryPermission_None) ? KMemoryState_Transfered : KMemoryState_SharedTransfered; + R_TRY(GetCurrentProcess().GetPageTable().UnmapPageGroup(address, GetReference(m_page_group), state)); + + /* Mark ourselves as unmapped. */ + MESOSPHERE_ASSERT(m_is_mapped); + m_is_mapped = false; + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_unused_slab_memory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_unused_slab_memory.cpp new file mode 100644 index 00000000..7da5d059 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_unused_slab_memory.cpp @@ -0,0 +1,159 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + class KUnusedSlabMemory : public util::IntrusiveRedBlackTreeBaseNode<KUnusedSlabMemory> { + NON_COPYABLE(KUnusedSlabMemory); + NON_MOVEABLE(KUnusedSlabMemory); + private: + size_t m_size; + public: + struct RedBlackKeyType { + size_t m_size; + + constexpr ALWAYS_INLINE size_t GetSize() const { + return m_size; + } + }; + + template<typename T> requires (std::same_as<T, KUnusedSlabMemory> || std::same_as<T, RedBlackKeyType>) + static constexpr ALWAYS_INLINE int Compare(const T &lhs, const KUnusedSlabMemory &rhs) { + if (lhs.GetSize() < rhs.GetSize()) { + return -1; + } else { + return 1; + } + } + public: + KUnusedSlabMemory(size_t size) : m_size(size) { /* ... */ } + + constexpr ALWAYS_INLINE KVirtualAddress GetAddress() const { return reinterpret_cast<uintptr_t>(this); } + constexpr ALWAYS_INLINE size_t GetSize() const { return m_size; } + + }; + static_assert(std::is_trivially_destructible<KUnusedSlabMemory>::value); + + using KUnusedSlabMemoryTree = util::IntrusiveRedBlackTreeBaseTraits<KUnusedSlabMemory>::TreeType<KUnusedSlabMemory>; + + constinit KLightLock g_unused_slab_memory_lock; + constinit KUnusedSlabMemoryTree g_unused_slab_memory_tree; + + } + + KVirtualAddress AllocateUnusedSlabMemory(size_t size, size_t alignment) { + /* Acquire exclusive access to the memory tree. */ + KScopedLightLock lk(g_unused_slab_memory_lock); + + /* Adjust size and alignment. */ + size = std::max(size, sizeof(KUnusedSlabMemory)); + alignment = std::max(alignment, alignof(KUnusedSlabMemory)); + + /* Find the smallest block which fits our allocation. */ + KUnusedSlabMemory *best_fit = std::addressof(*g_unused_slab_memory_tree.nfind_key({ size - 1 })); + + /* Ensure that the chunk is valid. */ + size_t prefix_waste; + KVirtualAddress alloc_start; + KVirtualAddress alloc_last; + KVirtualAddress alloc_end; + KVirtualAddress chunk_last; + KVirtualAddress chunk_end; + while (true) { + /* Check that we still have a chunk satisfying our size requirement. */ + if (AMS_UNLIKELY(best_fit == nullptr)) { + return Null<KVirtualAddress>; + } + + /* Determine where the actual allocation would start. */ + alloc_start = util::AlignUp(GetInteger(best_fit->GetAddress()), alignment); + if (AMS_LIKELY(alloc_start >= best_fit->GetAddress())) { + prefix_waste = alloc_start - best_fit->GetAddress(); + alloc_end = alloc_start + size; + alloc_last = alloc_end - 1; + + /* Check that the allocation remains in bounds. */ + if (alloc_start <= alloc_last) { + chunk_end = best_fit->GetAddress() + best_fit->GetSize(); + chunk_last = chunk_end - 1; + if (AMS_LIKELY(alloc_last <= chunk_last)) { + break; + } + } + } + + /* Check the next smallest block. */ + best_fit = best_fit->GetNext(); + } + + /* Remove the chunk we selected from the tree. */ + g_unused_slab_memory_tree.erase(g_unused_slab_memory_tree.iterator_to(*best_fit)); + std::destroy_at(best_fit); + + /* If there's enough prefix waste due to alignment for a new chunk, insert it into the tree. */ + if (prefix_waste >= sizeof(KUnusedSlabMemory)) { + std::construct_at(best_fit, prefix_waste); + g_unused_slab_memory_tree.insert(*best_fit); + } + + /* If there's enough suffix waste after the allocation for a new chunk, insert it into the tree. */ + if (alloc_last < alloc_end + sizeof(KUnusedSlabMemory) - 1 && alloc_end + sizeof(KUnusedSlabMemory) - 1 <= chunk_last) { + KUnusedSlabMemory *suffix_chunk = GetPointer<KUnusedSlabMemory>(alloc_end); + std::construct_at(suffix_chunk, chunk_end - alloc_end); + g_unused_slab_memory_tree.insert(*suffix_chunk); + } + + /* Return the allocated memory. */ + return alloc_start; + } + + void FreeUnusedSlabMemory(KVirtualAddress address, size_t size) { + /* NOTE: This is called only during initialization, so we don't need exclusive access. */ + /* Nintendo doesn't acquire the lock here, either. */ + + /* Check that there's anything at all for us to free. */ + if (AMS_UNLIKELY(size == 0)) { + return; + } + + /* Determine the start of the block. */ + const KVirtualAddress block_start = util::AlignUp(GetInteger(address), alignof(KUnusedSlabMemory)); + + /* Check that there's space for a KUnusedSlabMemory to exist. */ + if (AMS_UNLIKELY(std::numeric_limits<uintptr_t>::max() - sizeof(KUnusedSlabMemory) < GetInteger(block_start))) { + return; + } + + /* Determine the end of the block region. */ + const KVirtualAddress block_end = util::AlignDown(GetInteger(address) + size, alignof(KUnusedSlabMemory)); + + /* Check that the block remains within bounds. */ + if (AMS_UNLIKELY(block_start + sizeof(KUnusedSlabMemory) - 1 > block_end - 1)){ + return; + } + + /* Create the block. */ + KUnusedSlabMemory *block = GetPointer<KUnusedSlabMemory>(block_start); + std::construct_at(block, GetInteger(block_end) - GetInteger(block_start)); + + /* Insert the block into the tree. */ + g_unused_slab_memory_tree.insert(*block); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_wait_object.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_wait_object.cpp new file mode 100644 index 00000000..78b26680 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_wait_object.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + class ThreadQueueImplForKWaitObjectSynchronize final : public KThreadQueueWithoutEndWait { + private: + KThread::WaiterList *m_wait_list; + KThread **m_thread; + public: + constexpr ThreadQueueImplForKWaitObjectSynchronize(KThread::WaiterList *wl, KThread **t) : KThreadQueueWithoutEndWait(), m_wait_list(wl), m_thread(t) { /* ... */ } + + virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override { + /* Remove the thread from the wait list. */ + m_wait_list->erase(m_wait_list->iterator_to(*waiting_thread)); + + /* If the result was a timeout and the thread is our wait object thread, cancel recursively. */ + if (svc::ResultTimedOut::Includes(wait_result) && waiting_thread == *m_thread) { + for (auto &thread : *m_wait_list) { + thread.CancelWait(svc::ResultTimedOut(), false); + } + } + + /* If the thread is our wait object thread, clear it. */ + if (*m_thread == waiting_thread) { + *m_thread = nullptr; + } + + /* Invoke the base cancel wait handler. */ + KThreadQueue::CancelWait(waiting_thread, wait_result, cancel_timer_task); + } + }; + + } + + Result KWaitObject::Synchronize(s64 timeout) { + /* Perform the wait. */ + KHardwareTimer *timer; + KThread *cur_thread = GetCurrentThreadPointer(); + ThreadQueueImplForKWaitObjectSynchronize wait_queue(std::addressof(m_wait_list), std::addressof(m_next_thread)); + + { + KScopedSchedulerLockAndSleep slp(std::addressof(timer), cur_thread, timeout); + + /* Check that the thread isn't terminating. */ + if (cur_thread->IsTerminationRequested()) { + slp.CancelSleep(); + R_THROW(svc::ResultTerminationRequested()); + } + + /* Handle the case where timeout is non-negative/infinite. */ + if (timeout >= 0) { + /* Check if we're already waiting. */ + if (m_next_thread != nullptr) { + slp.CancelSleep(); + R_THROW(svc::ResultBusy()); + } + + /* If timeout is zero, handle the special case by canceling all waiting threads. */ + if (timeout == 0) { + for (auto &thread : m_wait_list) { + thread.CancelWait(svc::ResultTimedOut(), false); + } + + slp.CancelSleep(); + R_SUCCEED(); + } + } + + /* If the timeout isn't infinite, register it as our next timeout. */ + if (timeout > 0) { + wait_queue.SetHardwareTimer(timer); + m_next_thread = cur_thread; + } + + /* Add the current thread to our wait list. */ + m_wait_list.push_back(*cur_thread); + + /* Wait until the timeout occurs. */ + cur_thread->BeginWait(std::addressof(wait_queue)); + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_worker_task_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_worker_task_manager.cpp new file mode 100644 index 00000000..f639e776 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_k_worker_task_manager.cpp @@ -0,0 +1,151 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + class ThreadQueueImplForKWorkerTaskManager final : public KThreadQueue { + private: + KThread **m_waiting_thread; + public: + constexpr ThreadQueueImplForKWorkerTaskManager(KThread **t) : KThreadQueue(), m_waiting_thread(t) { /* ... */ } + + virtual void EndWait(KThread *waiting_thread, Result wait_result) override { + /* Clear our waiting thread. */ + *m_waiting_thread = nullptr; + + /* Invoke the base end wait handler. */ + KThreadQueue::EndWait(waiting_thread, wait_result); + } + + virtual void CancelWait(KThread *waiting_thread, Result wait_result, bool cancel_timer_task) override { + MESOSPHERE_UNUSED(waiting_thread, wait_result, cancel_timer_task); + MESOSPHERE_PANIC("ThreadQueueImplForKWorkerTaskManager::CancelWait\n"); + } + }; + + } + + void KWorkerTask::DoWorkerTask() { + if (auto * const thread = this->DynamicCast<KThread *>(); thread != nullptr) { + return thread->DoWorkerTaskImpl(); + } else { + auto * const process = this->DynamicCast<KProcess *>(); + MESOSPHERE_ABORT_UNLESS(process != nullptr); + + return process->DoWorkerTaskImpl(); + } + } + + void KWorkerTaskManager::Initialize(s32 priority) { + /* Reserve a thread from the system limit. */ + MESOSPHERE_ABORT_UNLESS(Kernel::GetSystemResourceLimit().Reserve(ams::svc::LimitableResource_ThreadCountMax, 1)); + + /* Create a new thread. */ + KThread *thread = KThread::Create(); + MESOSPHERE_ABORT_UNLESS(thread != nullptr); + + /* Launch the new thread. */ + MESOSPHERE_R_ABORT_UNLESS(KThread::InitializeKernelThread(thread, ThreadFunction, reinterpret_cast<uintptr_t>(this), priority, cpu::NumCores - 1)); + + /* Register the new thread. */ + KThread::Register(thread); + + /* Run the thread. */ + thread->Run(); + } + + void KWorkerTaskManager::AddTask(WorkerType type, KWorkerTask *task) { + MESOSPHERE_ASSERT(type <= WorkerType_Count); + Kernel::GetWorkerTaskManager(type).AddTask(task); + } + + void KWorkerTaskManager::ThreadFunction(uintptr_t arg) { + reinterpret_cast<KWorkerTaskManager *>(arg)->ThreadFunctionImpl(); + } + + void KWorkerTaskManager::ThreadFunctionImpl() { + /* Create wait queue. */ + ThreadQueueImplForKWorkerTaskManager wait_queue(std::addressof(m_waiting_thread)); + + while (true) { + KWorkerTask *task; + + /* Get a worker task. */ + { + KScopedSchedulerLock sl; + + task = this->GetTask(); + + if (task == nullptr) { + /* Wait to have a task. */ + m_waiting_thread = GetCurrentThreadPointer(); + GetCurrentThread().BeginWait(std::addressof(wait_queue)); + continue; + } + } + + /* Do the task. */ + task->DoWorkerTask(); + + /* Destroy any objects we may need to close. */ + GetCurrentThread().DestroyClosedObjects(); + } + } + + KWorkerTask *KWorkerTaskManager::GetTask() { + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + KWorkerTask *next = m_head_task; + + if (next != nullptr) { + /* Advance the list. */ + if (m_head_task == m_tail_task) { + m_head_task = nullptr; + m_tail_task = nullptr; + } else { + m_head_task = m_head_task->GetNextTask(); + } + + /* Clear the next task's next. */ + next->SetNextTask(nullptr); + } + + return next; + } + + void KWorkerTaskManager::AddTask(KWorkerTask *task) { + KScopedSchedulerLock sl; + MESOSPHERE_ASSERT(task->GetNextTask() == nullptr); + + /* Insert the task. */ + if (m_tail_task) { + m_tail_task->SetNextTask(task); + m_tail_task = task; + } else { + m_head_task = task; + m_tail_task = task; + + /* Make ourselves active if we need to. */ + if (m_waiting_thread != nullptr) { + m_waiting_thread->EndWait(ResultSuccess()); + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_kernel.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_kernel.cpp new file mode 100644 index 00000000..9660d906 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_kernel.cpp @@ -0,0 +1,181 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + KDynamicPageManager g_resource_manager_page_manager; + + template<typename T> + ALWAYS_INLINE void PrintMemoryRegion(const char *prefix, const T &extents) { + static_assert(std::is_same<decltype(extents.GetAddress()), uintptr_t>::value); + static_assert(std::is_same<decltype(extents.GetLastAddress()), uintptr_t>::value); + if constexpr (std::is_same<uintptr_t, unsigned int>::value) { + MESOSPHERE_LOG("%-24s0x%08x - 0x%08x\n", prefix, extents.GetAddress(), extents.GetLastAddress()); + } else if constexpr (std::is_same<uintptr_t, unsigned long>::value) { + MESOSPHERE_LOG("%-24s0x%016lx - 0x%016lx\n", prefix, extents.GetAddress(), extents.GetLastAddress()); + } else if constexpr (std::is_same<uintptr_t, unsigned long long>::value) { + MESOSPHERE_LOG("%-24s0x%016llx - 0x%016llx\n", prefix, extents.GetAddress(), extents.GetLastAddress()); + } else { + static_assert(!std::is_same<T, T>::value, "Unknown uintptr_t width!"); + } + } + + } + + void Kernel::InitializeCoreLocalRegion(s32 core_id) { + /* The core local region no longer exists, so just clear the current thread. */ + AMS_UNUSED(core_id); + SetCurrentThread(nullptr); + } + + void Kernel::InitializeMainAndIdleThreads(s32 core_id) { + /* This function wants to setup the main thread and the idle thread. */ + KThread *main_thread = std::addressof(Kernel::GetMainThread(core_id)); + void *main_thread_stack = GetVoidPointer(KMemoryLayout::GetMainStackTopAddress(core_id)); + KThread *idle_thread = std::addressof(Kernel::GetIdleThread(core_id)); + void *idle_thread_stack = GetVoidPointer(KMemoryLayout::GetIdleStackTopAddress(core_id)); + KAutoObject::Create<KThread>(main_thread); + KAutoObject::Create<KThread>(idle_thread); + main_thread->Initialize(nullptr, 0, main_thread_stack, 0, KThread::MainThreadPriority, core_id, nullptr, KThread::ThreadType_Main); + idle_thread->Initialize(nullptr, 0, idle_thread_stack, 0, KThread::IdleThreadPriority, core_id, nullptr, KThread::ThreadType_Main); + + /* Set the current thread to be the main thread, and we have no processes running yet. */ + SetCurrentThread(main_thread); + + /* Initialize the interrupt manager, hardware timer, and scheduler */ + GetInterruptManager().Initialize(core_id); + GetHardwareTimer().Initialize(); + GetScheduler().Initialize(idle_thread); + } + + void Kernel::InitializeResourceManagers(KVirtualAddress address, size_t size) { + /* Ensure that the buffer is suitable for our use. */ + MESOSPHERE_ABORT_UNLESS(util::IsAligned(GetInteger(address), PageSize)); + MESOSPHERE_ABORT_UNLESS(util::IsAligned(size, PageSize)); + + /* Ensure that we have space for our reference counts. */ + const size_t rc_size = util::AlignUp(KPageTableSlabHeap::CalculateReferenceCountSize(size), PageSize); + MESOSPHERE_ABORT_UNLESS(rc_size < size); + size -= rc_size; + + /* Determine dynamic page managers. */ + KDynamicPageManager * const app_dynamic_page_manager = nullptr; + KDynamicPageManager * const sys_dynamic_page_manager = KTargetSystem::IsDynamicResourceLimitsEnabled() ? std::addressof(g_resource_manager_page_manager) : nullptr; + + /* Initialize the resource managers' shared page manager. */ + g_resource_manager_page_manager.Initialize(address, size, std::max<size_t>(PageSize, KPageBufferSlabHeap::BufferSize)); + + /* Initialize the KPageBuffer slab heap. */ + KPageBuffer::InitializeSlabHeap(g_resource_manager_page_manager); + + /* Initialize the block info heap. */ + s_block_info_heap.Initialize(std::addressof(g_resource_manager_page_manager), BlockInfoSlabHeapSize); + + /* Initialize the system slab managers. */ + s_sys_block_info_manager.Initialize(sys_dynamic_page_manager, std::addressof(s_block_info_heap)); + s_sys_memory_block_heap.Initialize(std::addressof(g_resource_manager_page_manager), SystemMemoryBlockSlabHeapSize); + s_sys_memory_block_manager.Initialize(sys_dynamic_page_manager, std::addressof(s_sys_memory_block_heap)); + + /* Initialize the application slab managers. */ + s_app_block_info_manager.Initialize(app_dynamic_page_manager, std::addressof(s_block_info_heap)); + s_app_memory_block_heap.Initialize(std::addressof(g_resource_manager_page_manager), ApplicationMemoryBlockSlabHeapSize); + s_app_memory_block_manager.Initialize(app_dynamic_page_manager, std::addressof(s_app_memory_block_heap)); + + /* Reserve all but a fixed number of remaining pages for the page table heap. */ + const size_t num_pt_pages = g_resource_manager_page_manager.GetCount() - g_resource_manager_page_manager.GetUsed() - ReservedDynamicPageCount; + s_page_table_heap.Initialize(std::addressof(g_resource_manager_page_manager), num_pt_pages, GetPointer<KPageTableManager::RefCount>(address + size)); + + /* Create and initialize the system system resource. */ + s_sys_page_table_manager.Initialize(sys_dynamic_page_manager, std::addressof(s_page_table_heap)); + KAutoObject::Create<KSystemResource>(std::addressof(s_sys_system_resource)); + s_sys_system_resource.SetManagers(s_sys_memory_block_manager, s_sys_block_info_manager, s_sys_page_table_manager); + + /* Create and initialize the application system resource. */ + s_app_page_table_manager.Initialize(app_dynamic_page_manager, std::addressof(s_page_table_heap)); + KAutoObject::Create<KSystemResource>(std::addressof(s_app_system_resource)); + s_app_system_resource.SetManagers(s_app_memory_block_manager, s_app_block_info_manager, s_app_page_table_manager); + + /* Check that we have the correct number of dynamic pages available. */ + MESOSPHERE_ABORT_UNLESS(g_resource_manager_page_manager.GetCount() - g_resource_manager_page_manager.GetUsed() == ReservedDynamicPageCount); + } + + void Kernel::PrintLayout() { + const auto target_fw = kern::GetTargetFirmware(); + + /* Print out the kernel version. */ + MESOSPHERE_LOG("Horizon Kernel (Mesosphere)\n"); + MESOSPHERE_LOG("Built: %s %s\n", __DATE__, __TIME__); + MESOSPHERE_LOG("Atmosphere version: %d.%d.%d-%s\n", ATMOSPHERE_RELEASE_VERSION, ATMOSPHERE_GIT_REVISION); + MESOSPHERE_LOG("Target Firmware: %d.%d.%d\n", (target_fw >> 24) & 0xFF, (target_fw >> 16) & 0xFF, (target_fw >> 8) & 0xFF); + MESOSPHERE_LOG("Supported OS version: %d.%d.%d\n", ATMOSPHERE_SUPPORTED_HOS_VERSION_MAJOR, ATMOSPHERE_SUPPORTED_HOS_VERSION_MINOR, ATMOSPHERE_SUPPORTED_HOS_VERSION_MICRO); + MESOSPHERE_LOG("\n"); + + /* Print relative memory usage. */ + const auto [total, kernel] = KMemoryLayout::GetTotalAndKernelMemorySizes(); + MESOSPHERE_LOG("Kernel Memory Usage: %zu/%zu MB\n", util::AlignUp(kernel, 1_MB) / 1_MB, util::AlignUp(total, 1_MB) / 1_MB); + MESOSPHERE_LOG("\n"); + + /* Print out important memory layout regions. */ + MESOSPHERE_LOG("Virtual Memory Layout\n"); + PrintMemoryRegion(" KernelRegion", KMemoryLayout::GetKernelRegionExtents()); + PrintMemoryRegion(" Code", KMemoryLayout::GetKernelCodeRegionExtents()); + PrintMemoryRegion(" Stack", KMemoryLayout::GetKernelStackRegionExtents()); + PrintMemoryRegion(" Misc", KMemoryLayout::GetKernelMiscRegionExtents()); + PrintMemoryRegion(" Slab", KMemoryLayout::GetKernelSlabRegionExtents()); + PrintMemoryRegion(" LinearRegion", KMemoryLayout::GetLinearRegionVirtualExtents()); + MESOSPHERE_LOG("\n"); + + MESOSPHERE_LOG("Physical Memory Layout\n"); + PrintMemoryRegion(" LinearRegion", KMemoryLayout::GetLinearRegionPhysicalExtents()); + PrintMemoryRegion(" CarveoutRegion", KMemoryLayout::GetCarveoutRegionExtents()); + MESOSPHERE_LOG("\n"); + PrintMemoryRegion(" KernelRegion", KMemoryLayout::GetKernelRegionPhysicalExtents()); + PrintMemoryRegion(" Code", KMemoryLayout::GetKernelCodeRegionPhysicalExtents()); + PrintMemoryRegion(" Slab", KMemoryLayout::GetKernelSlabRegionPhysicalExtents()); + if constexpr (KSystemControl::SecureAppletMemorySize > 0) { + PrintMemoryRegion(" SecureApplet", KMemoryLayout::GetKernelSecureAppletMemoryRegionPhysicalExtents()); + } + PrintMemoryRegion(" PageTableHeap", KMemoryLayout::GetKernelPageTableHeapRegionPhysicalExtents()); + PrintMemoryRegion(" InitPageTable", KMemoryLayout::GetKernelInitPageTableRegionPhysicalExtents()); + if constexpr (IsKTraceEnabled) { + MESOSPHERE_LOG(" DebugRegion\n"); + PrintMemoryRegion(" Trace Buffer", KMemoryLayout::GetKernelTraceBufferRegionPhysicalExtents()); + } + PrintMemoryRegion(" MemoryPoolRegion", KMemoryLayout::GetKernelPoolPartitionRegionPhysicalExtents()); + if (GetTargetFirmware() >= TargetFirmware_5_0_0) { + PrintMemoryRegion(" Management", KMemoryLayout::GetKernelPoolManagementRegionPhysicalExtents()); + PrintMemoryRegion(" System", KMemoryLayout::GetKernelSystemPoolRegionPhysicalExtents()); + if (KMemoryLayout::HasKernelSystemNonSecurePoolRegion()) { + PrintMemoryRegion(" SystemUnsafe", KMemoryLayout::GetKernelSystemNonSecurePoolRegionPhysicalExtents()); + } + if (KMemoryLayout::HasKernelAppletPoolRegion()) { + PrintMemoryRegion(" Applet", KMemoryLayout::GetKernelAppletPoolRegionPhysicalExtents()); + } + if (KMemoryLayout::HasKernelApplicationPoolRegion()) { + PrintMemoryRegion(" Application", KMemoryLayout::GetKernelApplicationPoolRegionPhysicalExtents()); + } + } else { + PrintMemoryRegion(" Management", KMemoryLayout::GetKernelPoolManagementRegionPhysicalExtents()); + PrintMemoryRegion(" Secure", KMemoryLayout::GetKernelSystemPoolRegionPhysicalExtents()); + PrintMemoryRegion(" Unsafe", KMemoryLayout::GetKernelApplicationPoolRegionPhysicalExtents()); + } + MESOSPHERE_LOG("\n"); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_main.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_main.cpp new file mode 100644 index 00000000..160bb495 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_main.cpp @@ -0,0 +1,162 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + namespace { + + template<typename F> + ALWAYS_INLINE void DoOnEachCoreInOrder(s32 core_id, F f) { + cpu::SynchronizeAllCores(); + for (size_t i = 0; i < cpu::NumCores; i++) { + if (static_cast<s32>(i) == core_id) { + f(); + } + cpu::SynchronizeAllCores(); + } + } + + } + + NORETURN void HorizonKernelMain(s32 core_id) { + /* Setup the Core Local Region, and note that we're initializing. */ + Kernel::InitializeCoreLocalRegion(core_id); + Kernel::SetState(Kernel::State::Initializing); + + /* Ensure that all cores get to this point before proceeding. */ + cpu::SynchronizeAllCores(); + + /* Initialize the main and idle thread for each core. */ + DoOnEachCoreInOrder(core_id, [=]() ALWAYS_INLINE_LAMBDA { + Kernel::InitializeMainAndIdleThreads(core_id); + }); + + if (core_id == 0) { + /* Initialize the carveout and the system resource limit. */ + KSystemControl::InitializePhase1(); + + /* Synchronize all cores before proceeding, to ensure access to the global rng is consistent. */ + cpu::SynchronizeAllCores(); + + /* Initialize the memory manager and the KPageBuffer slabheap. */ + { + const auto &management_region = KMemoryLayout::GetPoolManagementRegion(); + MESOSPHERE_ABORT_UNLESS(management_region.GetEndAddress() != 0); + static_assert(util::size(MinimumMemoryManagerAlignmentShifts) == KMemoryManager::Pool_Count); + + Kernel::GetMemoryManager().Initialize(management_region.GetAddress(), management_region.GetSize(), MinimumMemoryManagerAlignmentShifts); + } + + /* Copy the Initial Process Binary to safe memory. */ + CopyInitialProcessBinaryToKernelMemory(); + + /* Print out information about the kernel. */ + Kernel::PrintLayout(); + + /* Initialize the KObject Slab Heaps. */ + init::InitializeSlabHeaps(); + + /* Initialize the Dynamic Slab Heaps. */ + { + const auto &pt_heap_region = KMemoryLayout::GetPageTableHeapRegion(); + MESOSPHERE_ABORT_UNLESS(pt_heap_region.GetEndAddress() != 0); + + Kernel::InitializeResourceManagers(pt_heap_region.GetAddress(), pt_heap_region.GetSize()); + } + } else { + /* Synchronize all cores before proceeding, to ensure access to the global rng is consistent. */ + cpu::SynchronizeAllCores(); + } + + /* Initialize the supervisor page table for each core. */ + DoOnEachCoreInOrder(core_id, [=]() ALWAYS_INLINE_LAMBDA { + KPageTable::Initialize(core_id); + Kernel::GetKernelPageTable().Initialize(core_id); + }); + + /* Activate the supervisor page table for each core. */ + DoOnEachCoreInOrder(core_id, [=]() ALWAYS_INLINE_LAMBDA { + Kernel::GetKernelPageTable().ActivateForInit(); + }); + + /* NOTE: Kernel calls on each core a nullsub here on retail kernel. */ + + /* Register the main/idle threads and initialize the interrupt task manager. */ + DoOnEachCoreInOrder(core_id, [=]() ALWAYS_INLINE_LAMBDA { + KThread::Register(std::addressof(Kernel::GetMainThread(core_id))); + KThread::Register(std::addressof(Kernel::GetIdleThread(core_id))); + }); + + /* Activate the scheduler and enable interrupts. */ + DoOnEachCoreInOrder(core_id, [=]() ALWAYS_INLINE_LAMBDA { + Kernel::GetScheduler().Activate(); + KInterruptManager::EnableInterrupts(); + }); + + /* Initialize cpu interrupt threads. */ + cpu::InitializeInterruptThreads(core_id); + + /* Initialize the DPC manager. */ + KDpcManager::Initialize(); + cpu::SynchronizeAllCores(); + + /* Perform more core-0 specific initialization. */ + if (core_id == 0) { + /* Initialize the exit worker managers, so that threads and processes may exit cleanly. */ + Kernel::GetWorkerTaskManager(KWorkerTaskManager::WorkerType_ExitThread).Initialize(KWorkerTaskManager::ExitWorkerPriority); + Kernel::GetWorkerTaskManager(KWorkerTaskManager::WorkerType_ExitProcess).Initialize(KWorkerTaskManager::ExitWorkerPriority); + + /* Setup so that we may sleep later, and reserve memory for secure applets. */ + KSystemControl::InitializePhase2(); + + /* Initialize the SMMU. */ + KDeviceAddressSpace::Initialize(); + + /* Load the initial processes. */ + CreateAndRunInitialProcesses(); + + /* We're done initializing! */ + Kernel::SetState(Kernel::State::Initialized); + + /* Resume all threads suspended while we initialized. */ + KThread::ResumeThreadsSuspendedForInit(); + + /* Validate that all reserved dram blocks are valid. */ + for (const auto ®ion : KMemoryLayout::GetPhysicalMemoryRegionTree()) { + if (region.IsDerivedFrom(KMemoryRegionType_DramReservedBase)) { + MESOSPHERE_ABORT_UNLESS(region.GetEndAddress() != 0); + } + } + } + cpu::SynchronizeAllCores(); + + /* Set the current thread priority to idle. */ + GetCurrentThread().SetPriorityToIdle(); + + /* Exit the main thread. */ + { + auto &main_thread = Kernel::GetMainThread(core_id); + main_thread.Open(); + main_thread.Exit(); + } + + /* Main() is done, and we should never get to this point. */ + MESOSPHERE_PANIC("Main Thread continued after exit."); + AMS_INFINITE_LOOP(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_panic.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_panic.cpp new file mode 100644 index 00000000..be6d24b7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/kern_panic.cpp @@ -0,0 +1,164 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +extern "C" void _start(); + +namespace ams::result::impl { + + NORETURN void OnResultAssertion(Result result) { + MESOSPHERE_PANIC("OnResultAssertion(2%03d-%04d)", result.GetModule(), result.GetDescription()); + } + +} + +namespace ams::kern { + + /* NOTE: This is not exposed via a header, but is referenced via assembly. */ + /* NOTE: Nintendo does not save register contents on panic; we use this */ + /* to generate an atmosphere fatal report on panic. */ + constinit KExceptionContext g_panic_exception_contexts[cpu::NumCores]; + + namespace { + + constexpr std::array<s32, cpu::NumCores> NegativeArray = [] { + std::array<s32, cpu::NumCores> arr = {}; + for (size_t i = 0; i < arr.size(); i++) { + arr[i] = -1; + } + return arr; + }(); + + constinit util::Atomic<s32> g_next_ticket = 0; + constinit util::Atomic<s32> g_current_ticket = 0; + + constinit std::array<s32, cpu::NumCores> g_core_tickets = NegativeArray; + + s32 GetCoreTicket() { + const s32 core_id = GetCurrentCoreId(); + if (g_core_tickets[core_id] == -1) { + g_core_tickets[core_id] = 2 * (g_next_ticket++); + } + return g_core_tickets[core_id]; + } + + void WaitCoreTicket() { + const s32 expected = GetCoreTicket(); + const s32 desired = expected + 1; + s32 compare = g_current_ticket.Load<std::memory_order_relaxed>(); + do { + if (compare == desired) { + break; + } + compare = expected; + } while (!g_current_ticket.CompareExchangeWeak(compare, desired)); + } + + void ReleaseCoreTicket() { + const s32 expected = GetCoreTicket() + 1; + const s32 desired = expected + 1; + + s32 compare = expected; + g_current_ticket.CompareExchangeStrong(compare, desired); + } + + ALWAYS_INLINE KExceptionContext *GetPanicExceptionContext(int core_id) { + #if defined(MESOSPHERE_ENABLE_PANIC_REGISTER_DUMP) + return std::addressof(g_panic_exception_contexts[core_id]); + #else + return nullptr; + #endif + } + + [[gnu::unused]] void PrintCurrentState() { + /* Wait for it to be our turn to print. */ + WaitCoreTicket(); + + /* Get the current exception context. */ + const s32 core_id = GetCurrentCoreId(); + const auto *core_ctx = GetPanicExceptionContext(core_id); + + /* Print the state. */ + MESOSPHERE_RELEASE_LOG("Core[%d] Current State:\n", core_id); + + /* Print kernel state. */ + #ifdef ATMOSPHERE_ARCH_ARM64 + MESOSPHERE_RELEASE_LOG("Kernel Registers:\n"); + for (size_t i = 0; i < 31; i++) { + MESOSPHERE_RELEASE_LOG(" X[%02zu] = %016lx\n", i, core_ctx->x[i]); + } + MESOSPHERE_RELEASE_LOG(" SP = %016lx\n", core_ctx->sp); + + /* Print kernel backtrace. */ + MESOSPHERE_RELEASE_LOG("Kernel Backtrace:\n"); + uintptr_t fp = core_ctx != nullptr ? core_ctx->x[29] : reinterpret_cast<uintptr_t>(__builtin_frame_address(0)); + for (size_t i = 0; i < 32 && fp && util::IsAligned(fp, 0x10) && cpu::GetPhysicalAddressWritable(nullptr, fp, true); i++) { + struct { + uintptr_t fp; + uintptr_t lr; + } *stack_frame = reinterpret_cast<decltype(stack_frame)>(fp); + MESOSPHERE_RELEASE_LOG(" [%02zx]: %p\n", i, reinterpret_cast<void *>(stack_frame->lr)); + fp = stack_frame->fp; + } + #endif + + /* Print registers and user backtrace. */ + KDebug::PrintRegister(); + KDebug::PrintBacktrace(); + + MESOSPHERE_RELEASE_LOG("\n"); + + /* Allow the next core to print. */ + ReleaseCoreTicket(); + } + + NORETURN void StopSystem() { + #ifdef MESOSPHERE_BUILD_FOR_DEBUGGING + /* Print the current core. */ + PrintCurrentState(); + #endif + + KSystemControl::StopSystem(GetPanicExceptionContext(GetCurrentCoreId())); + } + + } + + NORETURN WEAK_SYMBOL void PanicImpl(const char *file, int line, const char *format, ...) { + #ifdef MESOSPHERE_BUILD_FOR_DEBUGGING + /* Wait for it to be our turn to print. */ + WaitCoreTicket(); + + ::std::va_list vl; + va_start(vl, format); + MESOSPHERE_RELEASE_LOG("Core[%d]: Kernel Panic at %s:%d\n", GetCurrentCoreId(), file, line); + if (KProcess *cur_process = GetCurrentProcessPointer(); cur_process != nullptr) { + MESOSPHERE_RELEASE_LOG("Core[%d]: Current Process: %s\n", GetCurrentCoreId(), cur_process->GetName()); + } + MESOSPHERE_RELEASE_VLOG(format, vl); + MESOSPHERE_RELEASE_LOG("\n"); + va_end(vl); + #else + MESOSPHERE_UNUSED(file, line, format); + #endif + + StopSystem(); + } + + NORETURN WEAK_SYMBOL void PanicImpl() { + StopSystem(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/libc/kern_cxx.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/libc/kern_cxx.cpp new file mode 100644 index 00000000..3c3e9b05 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/libc/kern_cxx.cpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +#ifdef __cplusplus +extern "C" { +#endif + +/* cxx implementation details to be stubbed here, as needed. */ +void __cxa_pure_virtual() { MESOSPHERE_PANIC("pure virtual function call"); } + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_activity.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_activity.cpp new file mode 100644 index 00000000..a9ebafe3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_activity.cpp @@ -0,0 +1,101 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + constexpr bool IsValidThreadActivity(ams::svc::ThreadActivity thread_activity) { + switch (thread_activity) { + case ams::svc::ThreadActivity_Runnable: + case ams::svc::ThreadActivity_Paused: + return true; + default: + return false; + } + } + + constexpr bool IsValidProcessActivity(ams::svc::ProcessActivity process_activity) { + switch (process_activity) { + case ams::svc::ProcessActivity_Runnable: + case ams::svc::ProcessActivity_Paused: + return true; + default: + return false; + } + } + + Result SetThreadActivity(ams::svc::Handle thread_handle, ams::svc::ThreadActivity thread_activity) { + /* Validate the activity. */ + R_UNLESS(IsValidThreadActivity(thread_activity), svc::ResultInvalidEnumValue()); + + /* Get the thread from its handle. */ + KScopedAutoObject thread = GetCurrentProcess().GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), svc::ResultInvalidHandle()); + + /* Check that the activity is being set on a non-current thread for the current process. */ + R_UNLESS(thread->GetOwnerProcess() == GetCurrentProcessPointer(), svc::ResultInvalidHandle()); + R_UNLESS(thread.GetPointerUnsafe() != GetCurrentThreadPointer(), svc::ResultBusy()); + + /* Set the activity. */ + R_TRY(thread->SetActivity(thread_activity)); + + R_SUCCEED(); + } + + Result SetProcessActivity(ams::svc::Handle process_handle, ams::svc::ProcessActivity process_activity) { + /* Validate the activity. */ + R_UNLESS(IsValidProcessActivity(process_activity), svc::ResultInvalidEnumValue()); + + /* Get the process from its handle. */ + KScopedAutoObject process = GetCurrentProcess().GetHandleTable().GetObject<KProcess>(process_handle); + R_UNLESS(process.IsNotNull(), svc::ResultInvalidHandle()); + + /* Check that the activity isn't being set on the current process. */ + R_UNLESS(process.GetPointerUnsafe() != GetCurrentProcessPointer(), svc::ResultBusy()); + + /* Set the activity. */ + R_TRY(process->SetActivity(process_activity)); + + R_SUCCEED(); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result SetThreadActivity64(ams::svc::Handle thread_handle, ams::svc::ThreadActivity thread_activity) { + R_RETURN(SetThreadActivity(thread_handle, thread_activity)); + } + + Result SetProcessActivity64(ams::svc::Handle process_handle, ams::svc::ProcessActivity process_activity) { + R_RETURN(SetProcessActivity(process_handle, process_activity)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result SetThreadActivity64From32(ams::svc::Handle thread_handle, ams::svc::ThreadActivity thread_activity) { + R_RETURN(SetThreadActivity(thread_handle, thread_activity)); + } + + Result SetProcessActivity64From32(ams::svc::Handle process_handle, ams::svc::ProcessActivity process_activity) { + R_RETURN(SetProcessActivity(process_handle, process_activity)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_address_arbiter.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_address_arbiter.cpp new file mode 100644 index 00000000..2b495ad4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_address_arbiter.cpp @@ -0,0 +1,111 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + constexpr bool IsKernelAddress(uintptr_t address) { + return KernelVirtualAddressSpaceBase <= address && address < KernelVirtualAddressSpaceEnd; + } + + constexpr bool IsValidSignalType(ams::svc::SignalType type) { + switch (type) { + case ams::svc::SignalType_Signal: + case ams::svc::SignalType_SignalAndIncrementIfEqual: + case ams::svc::SignalType_SignalAndModifyByWaitingCountIfEqual: + return true; + default: + return false; + } + } + + constexpr bool IsValidArbitrationType(ams::svc::ArbitrationType type) { + switch (type) { + case ams::svc::ArbitrationType_WaitIfLessThan: + case ams::svc::ArbitrationType_DecrementAndWaitIfLessThan: + case ams::svc::ArbitrationType_WaitIfEqual: + case ams::svc::ArbitrationType_WaitIfEqual64: + return true; + default: + return false; + } + } + + Result WaitForAddress(uintptr_t address, ams::svc::ArbitrationType arb_type, int64_t value, int64_t timeout_ns) { + /* Validate input. */ + R_UNLESS(AMS_LIKELY(!IsKernelAddress(address)), svc::ResultInvalidCurrentMemory()); + if (arb_type == ams::svc::ArbitrationType_WaitIfEqual64) { + R_UNLESS(util::IsAligned(address, sizeof(int64_t)), svc::ResultInvalidAddress()); + } else { + R_UNLESS(util::IsAligned(address, sizeof(int32_t)), svc::ResultInvalidAddress()); + } + R_UNLESS(IsValidArbitrationType(arb_type), svc::ResultInvalidEnumValue()); + + /* Convert timeout from nanoseconds to ticks. */ + s64 timeout; + if (timeout_ns > 0) { + const ams::svc::Tick offset_tick(TimeSpan::FromNanoSeconds(timeout_ns)); + if (AMS_LIKELY(offset_tick > 0)) { + timeout = KHardwareTimer::GetTick() + offset_tick + 2; + if (AMS_UNLIKELY(timeout <= 0)) { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = timeout_ns; + } + + R_RETURN(GetCurrentProcess().WaitAddressArbiter(address, arb_type, value, timeout)); + } + + Result SignalToAddress(uintptr_t address, ams::svc::SignalType signal_type, int32_t value, int32_t count) { + /* Validate input. */ + R_UNLESS(AMS_LIKELY(!IsKernelAddress(address)), svc::ResultInvalidCurrentMemory()); + R_UNLESS(util::IsAligned(address, sizeof(int32_t)), svc::ResultInvalidAddress()); + R_UNLESS(IsValidSignalType(signal_type), svc::ResultInvalidEnumValue()); + + R_RETURN(GetCurrentProcess().SignalAddressArbiter(address, signal_type, value, count)); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result WaitForAddress64(ams::svc::Address address, ams::svc::ArbitrationType arb_type, int64_t value, int64_t timeout_ns) { + R_RETURN(WaitForAddress(address, arb_type, value, timeout_ns)); + } + + Result SignalToAddress64(ams::svc::Address address, ams::svc::SignalType signal_type, int32_t value, int32_t count) { + R_RETURN(SignalToAddress(address, signal_type, value, count)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result WaitForAddress64From32(ams::svc::Address address, ams::svc::ArbitrationType arb_type, int64_t value, int64_t timeout_ns) { + R_RETURN(WaitForAddress(address, arb_type, value, timeout_ns)); + } + + Result SignalToAddress64From32(ams::svc::Address address, ams::svc::SignalType signal_type, int32_t value, int32_t count) { + R_RETURN(SignalToAddress(address, signal_type, value, count)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_address_translation.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_address_translation.cpp new file mode 100644 index 00000000..e93fbfd5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_address_translation.cpp @@ -0,0 +1,164 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + Result QueryPhysicalAddress(ams::svc::PhysicalMemoryInfo *out_info, uintptr_t address) { + /* NOTE: In 10.0.0, Nintendo stubbed this SVC. Should we do so? */ + /* R_UNLESS(GetTargetFirmware() < TargetFirmware_10_0_0, svc::ResultInvalidCurrentMemory()); */ + + /* Get reference to page table. */ + auto &pt = GetCurrentProcess().GetPageTable(); + + /* Check that the address is valid. */ + R_UNLESS(pt.Contains(address, 1), svc::ResultInvalidCurrentMemory()); + + /* Query the physical mapping. */ + R_TRY(pt.QueryPhysicalAddress(out_info, address)); + + R_SUCCEED(); + } + + Result QueryMemoryMapping(uintptr_t *out_address, size_t *out_size, uint64_t phys_addr, size_t size) { + /* Declare variables we'll populate. */ + KProcessAddress found_address = Null<KProcessAddress>; + size_t found_size = 0; + + /* Get reference to page table. */ + auto &pt = GetCurrentProcess().GetPageTable(); + + /* Check whether the address is aligned. */ + const bool aligned = util::IsAligned(phys_addr, PageSize); + + auto QueryMappingFromPageTable = [&](uint64_t phys_addr, size_t size) ALWAYS_INLINE_LAMBDA -> Result { + /* The size must be non-zero. */ + R_UNLESS(size > 0, svc::ResultInvalidSize()); + + /* The request must not overflow. */ + R_UNLESS((phys_addr < phys_addr + size), svc::ResultNotFound()); + + /* Query the mapping. */ + R_TRY_CATCH(pt.QueryIoMapping(std::addressof(found_address), phys_addr, size)) { + R_CATCH(svc::ResultNotFound) { + /* If we failed to find an io mapping, check if the address is a static mapping. */ + R_TRY(pt.QueryStaticMapping(std::addressof(found_address), phys_addr, size)); + } + } R_END_TRY_CATCH; + + /* Use the size as the found size. */ + found_size = size; + + R_SUCCEED(); + }; + + if (aligned) { + /* Query the input. */ + R_TRY(QueryMappingFromPageTable(phys_addr, size)); + } else { + if (kern::GetTargetFirmware() < TargetFirmware_8_0_0 && phys_addr >= PageSize) { + /* Query the aligned-down page. */ + const size_t offset = phys_addr & (PageSize - 1); + R_TRY(QueryMappingFromPageTable(phys_addr - offset, size + offset)); + + /* Adjust the output address. */ + found_address += offset; + } else { + /* Newer kernel only allows unaligned addresses when they're special enum members. */ + R_UNLESS(phys_addr < PageSize, svc::ResultNotFound()); + + /* Try to find the memory region. */ + const KMemoryRegion * const region = [] ALWAYS_INLINE_LAMBDA (ams::svc::MemoryRegionType type) -> const KMemoryRegion * { + switch (type) { + case ams::svc::MemoryRegionType_KernelTraceBuffer: return KMemoryLayout::GetPhysicalKernelTraceBufferRegion(); + case ams::svc::MemoryRegionType_OnMemoryBootImage: return KMemoryLayout::GetPhysicalOnMemoryBootImageRegion(); + case ams::svc::MemoryRegionType_DTB: return KMemoryLayout::GetPhysicalDTBRegion(); + default: return nullptr; + } + }(static_cast<ams::svc::MemoryRegionType>(phys_addr)); + + /* Ensure that we found the region. */ + R_UNLESS(region != nullptr, svc::ResultNotFound()); + + /* Chcek that the region is valid. */ + MESOSPHERE_ABORT_UNLESS(region->GetEndAddress() != 0); + + R_TRY(pt.QueryStaticMapping(std::addressof(found_address), region->GetAddress(), region->GetSize())); + found_size = region->GetSize(); + } + } + + /* We succeeded. */ + MESOSPHERE_ASSERT(found_address != Null<KProcessAddress>); + MESOSPHERE_ASSERT(found_size != 0); + if (out_address != nullptr) { + *out_address = GetInteger(found_address); + } + if (out_size != nullptr) { + *out_size = found_size; + } + R_SUCCEED(); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result QueryPhysicalAddress64(ams::svc::lp64::PhysicalMemoryInfo *out_info, ams::svc::Address address) { + R_RETURN(QueryPhysicalAddress(out_info, address)); + } + + Result QueryMemoryMapping64(ams::svc::Address *out_address, ams::svc::Size *out_size, ams::svc::PhysicalAddress physical_address, ams::svc::Size size) { + static_assert(sizeof(*out_address) == sizeof(uintptr_t)); + static_assert(sizeof(*out_size) == sizeof(size_t)); + R_RETURN(QueryMemoryMapping(reinterpret_cast<uintptr_t *>(out_address), reinterpret_cast<size_t *>(out_size), physical_address, size)); + } + + Result LegacyQueryIoMapping64(ams::svc::Address *out_address, ams::svc::PhysicalAddress physical_address, ams::svc::Size size) { + static_assert(sizeof(*out_address) == sizeof(uintptr_t)); + R_RETURN(QueryMemoryMapping(reinterpret_cast<uintptr_t *>(out_address), nullptr, physical_address, size)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result QueryPhysicalAddress64From32(ams::svc::ilp32::PhysicalMemoryInfo *out_info, ams::svc::Address address) { + ams::svc::PhysicalMemoryInfo info = {}; + R_TRY(QueryPhysicalAddress(std::addressof(info), address)); + + *out_info = { + .physical_address = info.physical_address, + .virtual_address = static_cast<u32>(info.virtual_address), + .size = static_cast<u32>(info.size), + }; + R_SUCCEED(); + } + + Result QueryMemoryMapping64From32(ams::svc::Address *out_address, ams::svc::Size *out_size, ams::svc::PhysicalAddress physical_address, ams::svc::Size size) { + static_assert(sizeof(*out_address) == sizeof(uintptr_t)); + static_assert(sizeof(*out_size) == sizeof(size_t)); + R_RETURN(QueryMemoryMapping(reinterpret_cast<uintptr_t *>(out_address), reinterpret_cast<size_t *>(out_size), physical_address, size)); + } + + Result LegacyQueryIoMapping64From32(ams::svc::Address *out_address, ams::svc::PhysicalAddress physical_address, ams::svc::Size size) { + static_assert(sizeof(*out_address) == sizeof(uintptr_t)); + R_RETURN(QueryMemoryMapping(reinterpret_cast<uintptr_t *>(out_address), nullptr, physical_address, size)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_cache.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_cache.cpp new file mode 100644 index 00000000..c6ffb5e4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_cache.cpp @@ -0,0 +1,214 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + class CacheOperation { + public: + virtual void Operate(void *address, size_t size) const = 0; + }; + + Result DoProcessCacheOperation(const CacheOperation &operation, KProcessPageTable &page_table, uintptr_t address, size_t size) { + /* Determine aligned extents. */ + const uintptr_t aligned_start = util::AlignDown(address, PageSize); + const uintptr_t aligned_end = util::AlignUp(address + size, PageSize); + + /* Iterate over and operate on contiguous ranges. */ + uintptr_t cur_address = aligned_start; + size_t remaining = size; + while (remaining > 0) { + /* Get a contiguous range to operate on. */ + KPageTableBase::MemoryRange contig_range; + R_TRY(page_table.OpenMemoryRangeForProcessCacheOperation(std::addressof(contig_range), cur_address, aligned_end - cur_address)); + + /* Close the range when we're done operating on it. */ + ON_SCOPE_EXIT { contig_range.Close(); }; + + /* Adjust to remain within range. */ + KVirtualAddress operate_address = KMemoryLayout::GetLinearVirtualAddress(contig_range.GetAddress()); + size_t operate_size = contig_range.GetSize(); + if (cur_address < address) { + operate_address += (address - cur_address); + operate_size -= (address - cur_address); + } + if (operate_size > remaining) { + operate_size = remaining; + } + + /* Operate. */ + operation.Operate(GetVoidPointer(operate_address), operate_size); + + /* Advance. */ + cur_address += contig_range.GetSize(); + remaining -= operate_size; + } + MESOSPHERE_ASSERT(remaining == 0); + + R_SUCCEED(); + } + + void FlushEntireDataCache() { + /* Flushing cache takes up to 1ms, so determine our minimum end tick. */ + const s64 timeout = KHardwareTimer::GetTick() + ams::svc::Tick(TimeSpan::FromMilliSeconds(1)); + + /* Flush the entire data cache. */ + cpu::FlushEntireDataCache(); + + /* Wait for 1ms to have passed. */ + while (KHardwareTimer::GetTick() < timeout) { + cpu::Yield(); + } + } + + Result FlushDataCache(uintptr_t address, size_t size) { + /* Succeed if there's nothing to do. */ + R_SUCCEED_IF(size == 0); + + /* Validate that the region is within range. */ + R_UNLESS(GetCurrentProcess().GetPageTable().Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Flush the cache. */ + R_TRY(cpu::FlushDataCache(reinterpret_cast<void *>(address), size)); + + R_SUCCEED(); + } + + Result InvalidateProcessDataCache(ams::svc::Handle process_handle, uint64_t address, uint64_t size) { + /* Validate address/size. */ + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS(address == static_cast<uintptr_t>(address), svc::ResultInvalidCurrentMemory()); + R_UNLESS(size == static_cast<size_t>(size), svc::ResultInvalidCurrentMemory()); + + /* Get the process from its handle. */ + KScopedAutoObject process = GetCurrentProcess().GetHandleTable().GetObject<KProcess>(process_handle); + R_UNLESS(process.IsNotNull(), svc::ResultInvalidHandle()); + + /* Invalidate the cache. */ + if (process.GetPointerUnsafe() == GetCurrentProcessPointer()) { + R_TRY(process->GetPageTable().InvalidateCurrentProcessDataCache(address, size)); + } else { + R_TRY(process->GetPageTable().InvalidateProcessDataCache(address, size)); + } + + R_SUCCEED(); + } + + Result StoreProcessDataCache(ams::svc::Handle process_handle, uint64_t address, uint64_t size) { + /* Validate address/size. */ + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS(address == static_cast<uintptr_t>(address), svc::ResultInvalidCurrentMemory()); + R_UNLESS(size == static_cast<size_t>(size), svc::ResultInvalidCurrentMemory()); + + /* Get the process from its handle. */ + KScopedAutoObject process = GetCurrentProcess().GetHandleTable().GetObject<KProcess>(process_handle); + R_UNLESS(process.IsNotNull(), svc::ResultInvalidHandle()); + + /* Verify the region is within range. */ + auto &page_table = process->GetPageTable(); + R_UNLESS(page_table.Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Perform the operation. */ + if (process.GetPointerUnsafe() == GetCurrentProcessPointer()) { + R_RETURN(cpu::StoreDataCache(reinterpret_cast<void *>(address), size)); + } else { + class StoreCacheOperation : public CacheOperation { + public: + virtual void Operate(void *address, size_t size) const override { cpu::StoreDataCache(address, size); } + } operation; + + R_RETURN(DoProcessCacheOperation(operation, page_table, address, size)); + } + } + + Result FlushProcessDataCache(ams::svc::Handle process_handle, uint64_t address, uint64_t size) { + /* Validate address/size. */ + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS(address == static_cast<uintptr_t>(address), svc::ResultInvalidCurrentMemory()); + R_UNLESS(size == static_cast<size_t>(size), svc::ResultInvalidCurrentMemory()); + + /* Get the process from its handle. */ + KScopedAutoObject process = GetCurrentProcess().GetHandleTable().GetObject<KProcess>(process_handle); + R_UNLESS(process.IsNotNull(), svc::ResultInvalidHandle()); + + /* Verify the region is within range. */ + auto &page_table = process->GetPageTable(); + R_UNLESS(page_table.Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Perform the operation. */ + if (process.GetPointerUnsafe() == GetCurrentProcessPointer()) { + R_RETURN(cpu::FlushDataCache(reinterpret_cast<void *>(address), size)); + } else { + class FlushCacheOperation : public CacheOperation { + public: + virtual void Operate(void *address, size_t size) const override { cpu::FlushDataCache(address, size); } + } operation; + + R_RETURN(DoProcessCacheOperation(operation, page_table, address, size)); + } + } + + } + + /* ============================= 64 ABI ============================= */ + + void FlushEntireDataCache64() { + return FlushEntireDataCache(); + } + + Result FlushDataCache64(ams::svc::Address address, ams::svc::Size size) { + R_RETURN(FlushDataCache(address, size)); + } + + Result InvalidateProcessDataCache64(ams::svc::Handle process_handle, uint64_t address, uint64_t size) { + R_RETURN(InvalidateProcessDataCache(process_handle, address, size)); + } + + Result StoreProcessDataCache64(ams::svc::Handle process_handle, uint64_t address, uint64_t size) { + R_RETURN(StoreProcessDataCache(process_handle, address, size)); + } + + Result FlushProcessDataCache64(ams::svc::Handle process_handle, uint64_t address, uint64_t size) { + R_RETURN(FlushProcessDataCache(process_handle, address, size)); + } + + /* ============================= 64From32 ABI ============================= */ + + void FlushEntireDataCache64From32() { + return FlushEntireDataCache(); + } + + Result FlushDataCache64From32(ams::svc::Address address, ams::svc::Size size) { + R_RETURN(FlushDataCache(address, size)); + } + + Result InvalidateProcessDataCache64From32(ams::svc::Handle process_handle, uint64_t address, uint64_t size) { + R_RETURN(InvalidateProcessDataCache(process_handle, address, size)); + } + + Result StoreProcessDataCache64From32(ams::svc::Handle process_handle, uint64_t address, uint64_t size) { + R_RETURN(StoreProcessDataCache(process_handle, address, size)); + } + + Result FlushProcessDataCache64From32(ams::svc::Handle process_handle, uint64_t address, uint64_t size) { + R_RETURN(FlushProcessDataCache(process_handle, address, size)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_code_memory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_code_memory.cpp new file mode 100644 index 00000000..e400032a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_code_memory.cpp @@ -0,0 +1,163 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + constexpr bool IsValidMapCodeMemoryPermission(ams::svc::MemoryPermission perm) { + return perm == ams::svc::MemoryPermission_ReadWrite; + } + + constexpr bool IsValidMapToOwnerCodeMemoryPermission(ams::svc::MemoryPermission perm) { + return perm == ams::svc::MemoryPermission_Read || perm == ams::svc::MemoryPermission_ReadExecute; + } + + constexpr bool IsValidUnmapCodeMemoryPermission(ams::svc::MemoryPermission perm) { + return perm == ams::svc::MemoryPermission_None; + } + + constexpr bool IsValidUnmapFromOwnerCodeMemoryPermission(ams::svc::MemoryPermission perm) { + return perm == ams::svc::MemoryPermission_None; + } + + Result CreateCodeMemory(ams::svc::Handle *out, uintptr_t address, size_t size) { + /* Validate address / size. */ + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + + /* Create the code memory. */ + KCodeMemory *code_mem = KCodeMemory::Create(); + R_UNLESS(code_mem != nullptr, svc::ResultOutOfResource()); + ON_SCOPE_EXIT { code_mem->Close(); }; + + /* Verify that the region is in range. */ + R_UNLESS(GetCurrentProcess().GetPageTable().Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Initialize the code memory. */ + R_TRY(code_mem->Initialize(address, size)); + + /* Register the code memory. */ + KCodeMemory::Register(code_mem); + + /* Add the code memory to the handle table. */ + R_TRY(GetCurrentProcess().GetHandleTable().Add(out, code_mem)); + + R_SUCCEED(); + } + + Result ControlCodeMemory(ams::svc::Handle code_memory_handle, ams::svc::CodeMemoryOperation operation, uint64_t address, uint64_t size, ams::svc::MemoryPermission perm) { + /* Validate the address / size. */ + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + R_UNLESS(address == static_cast<uintptr_t>(address), svc::ResultInvalidCurrentMemory()); + R_UNLESS(size == static_cast<size_t>(size), svc::ResultInvalidCurrentMemory()); + + /* Get the code memory from its handle. */ + KScopedAutoObject code_mem = GetCurrentProcess().GetHandleTable().GetObject<KCodeMemory>(code_memory_handle); + R_UNLESS(code_mem.IsNotNull(), svc::ResultInvalidHandle()); + + /* NOTE: Here, Atmosphere extends the SVC to allow code memory operations on one's own process. */ + /* This enables homebrew usage of these SVCs for JIT. */ + /* R_UNLESS(code_mem->GetOwner() != GetCurrentProcessPointer(), svc::ResultInvalidHandle()); */ + + /* Perform the operation. */ + switch (operation) { + case ams::svc::CodeMemoryOperation_Map: + { + /* Check that the region is in range. */ + R_UNLESS(GetCurrentProcess().GetPageTable().CanContain(address, size, KMemoryState_CodeOut), svc::ResultInvalidMemoryRegion()); + + /* Check the memory permission. */ + R_UNLESS(IsValidMapCodeMemoryPermission(perm), svc::ResultInvalidNewMemoryPermission()); + + /* Map the memory. */ + R_TRY(code_mem->Map(address, size)); + } + break; + case ams::svc::CodeMemoryOperation_Unmap: + { + /* Check that the region is in range. */ + R_UNLESS(GetCurrentProcess().GetPageTable().CanContain(address, size, KMemoryState_CodeOut), svc::ResultInvalidMemoryRegion()); + + /* Check the memory permission. */ + R_UNLESS(IsValidUnmapCodeMemoryPermission(perm), svc::ResultInvalidNewMemoryPermission()); + + /* Unmap the memory. */ + R_TRY(code_mem->Unmap(address, size)); + } + break; + case ams::svc::CodeMemoryOperation_MapToOwner: + { + /* Check that the region is in range. */ + R_UNLESS(code_mem->GetOwner()->GetPageTable().CanContain(address, size, KMemoryState_GeneratedCode), svc::ResultInvalidMemoryRegion()); + + /* Check the memory permission. */ + R_UNLESS(IsValidMapToOwnerCodeMemoryPermission(perm), svc::ResultInvalidNewMemoryPermission()); + + /* Map the memory to its owner. */ + R_TRY(code_mem->MapToOwner(address, size, perm)); + } + break; + case ams::svc::CodeMemoryOperation_UnmapFromOwner: + { + /* Check that the region is in range. */ + R_UNLESS(code_mem->GetOwner()->GetPageTable().CanContain(address, size, KMemoryState_GeneratedCode), svc::ResultInvalidMemoryRegion()); + + /* Check the memory permission. */ + R_UNLESS(IsValidUnmapFromOwnerCodeMemoryPermission(perm), svc::ResultInvalidNewMemoryPermission()); + + /* Unmap the memory from its owner. */ + R_TRY(code_mem->UnmapFromOwner(address, size)); + } + break; + default: + R_THROW(svc::ResultInvalidEnumValue()); + } + + R_SUCCEED(); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result CreateCodeMemory64(ams::svc::Handle *out_handle, ams::svc::Address address, ams::svc::Size size) { + R_RETURN(CreateCodeMemory(out_handle, address, size)); + } + + Result ControlCodeMemory64(ams::svc::Handle code_memory_handle, ams::svc::CodeMemoryOperation operation, uint64_t address, uint64_t size, ams::svc::MemoryPermission perm) { + R_RETURN(ControlCodeMemory(code_memory_handle, operation, address, size, perm)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result CreateCodeMemory64From32(ams::svc::Handle *out_handle, ams::svc::Address address, ams::svc::Size size) { + R_RETURN(CreateCodeMemory(out_handle, address, size)); + } + + Result ControlCodeMemory64From32(ams::svc::Handle code_memory_handle, ams::svc::CodeMemoryOperation operation, uint64_t address, uint64_t size, ams::svc::MemoryPermission perm) { + R_RETURN(ControlCodeMemory(code_memory_handle, operation, address, size, perm)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_condition_variable.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_condition_variable.cpp new file mode 100644 index 00000000..2d3a031e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_condition_variable.cpp @@ -0,0 +1,80 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + constexpr bool IsKernelAddress(uintptr_t address) { + return KernelVirtualAddressSpaceBase <= address && address < KernelVirtualAddressSpaceEnd; + } + + Result WaitProcessWideKeyAtomic(uintptr_t address, uintptr_t cv_key, uint32_t tag, int64_t timeout_ns) { + /* Validate input. */ + R_UNLESS(AMS_LIKELY(!IsKernelAddress(address)), svc::ResultInvalidCurrentMemory()); + R_UNLESS(util::IsAligned(address, sizeof(int32_t)), svc::ResultInvalidAddress()); + + /* Convert timeout from nanoseconds to ticks. */ + s64 timeout; + if (timeout_ns > 0) { + const ams::svc::Tick offset_tick(TimeSpan::FromNanoSeconds(timeout_ns)); + if (AMS_LIKELY(offset_tick > 0)) { + timeout = KHardwareTimer::GetTick() + offset_tick + 2; + if (AMS_UNLIKELY(timeout <= 0)) { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = timeout_ns; + } + + /* Wait on the condition variable. */ + R_RETURN(GetCurrentProcess().WaitConditionVariable(address, util::AlignDown(cv_key, sizeof(u32)), tag, timeout)); + } + + void SignalProcessWideKey(uintptr_t cv_key, int32_t count) { + /* Signal the condition variable. */ + return GetCurrentProcess().SignalConditionVariable(util::AlignDown(cv_key, sizeof(u32)), count); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result WaitProcessWideKeyAtomic64(ams::svc::Address address, ams::svc::Address cv_key, uint32_t tag, int64_t timeout_ns) { + R_RETURN(WaitProcessWideKeyAtomic(address, cv_key, tag, timeout_ns)); + } + + void SignalProcessWideKey64(ams::svc::Address cv_key, int32_t count) { + return SignalProcessWideKey(cv_key, count); + } + + /* ============================= 64From32 ABI ============================= */ + + Result WaitProcessWideKeyAtomic64From32(ams::svc::Address address, ams::svc::Address cv_key, uint32_t tag, int64_t timeout_ns) { + R_RETURN(WaitProcessWideKeyAtomic(address, cv_key, tag, timeout_ns)); + } + + void SignalProcessWideKey64From32(ams::svc::Address cv_key, int32_t count) { + return SignalProcessWideKey(cv_key, count); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_debug.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_debug.cpp new file mode 100644 index 00000000..986654ce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_debug.cpp @@ -0,0 +1,549 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + constexpr inline int32_t MaximumDebuggableThreadCount = 0x60; + + Result DebugActiveProcess(ams::svc::Handle *out_handle, uint64_t process_id) { + /* Check that the SVC can be used. */ + R_UNLESS(KTargetSystem::IsDebugMode() || GetCurrentProcess().CanForceDebugProd(), svc::ResultNotImplemented()); + + /* Get the process from its id. */ + KProcess *process = KProcess::GetProcessFromId(process_id); + R_UNLESS(process != nullptr, svc::ResultInvalidProcessId()); + + /* Close the reference we opened to the process on scope exit. */ + ON_SCOPE_EXIT { process->Close(); }; + + /* Check that the debugging is allowed. */ + const bool allowable = process->IsPermittedDebug() || GetCurrentProcess().CanForceDebug() || GetCurrentProcess().CanForceDebugProd(); + R_UNLESS(allowable, svc::ResultInvalidState()); + + /* Disallow debugging one's own processs, to prevent softlocks. */ + R_UNLESS(process != GetCurrentProcessPointer(), svc::ResultInvalidState()); + + /* Get the current handle table. */ + auto &handle_table = GetCurrentProcess().GetHandleTable(); + + /* Create a new debug object. */ + KDebug *debug = KDebug::Create(); + R_UNLESS(debug != nullptr, svc::ResultOutOfResource()); + ON_SCOPE_EXIT { debug->Close(); }; + + /* Initialize the debug object. */ + debug->Initialize(); + + /* Register the debug object. */ + KDebug::Register(debug); + + /* Try to attach to the target process. */ + R_TRY(debug->Attach(process)); + + /* Add the new debug object to the handle table. */ + R_TRY(handle_table.Add(out_handle, debug)); + + R_SUCCEED(); + } + + Result BreakDebugProcess(ams::svc::Handle debug_handle) { + /* Only allow invoking the svc on development hardware. */ + R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNotImplemented()); + + /* Get the debug object. */ + KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(debug_handle); + R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); + + /* Break the process. */ + R_TRY(debug->BreakProcess()); + + R_SUCCEED(); + } + + Result TerminateDebugProcess(ams::svc::Handle debug_handle) { + /* Only allow invoking the svc on development hardware. */ + R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNotImplemented()); + + /* Get the debug object. */ + KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(debug_handle); + R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); + + /* Terminate the process. */ + R_TRY(debug->TerminateProcess()); + + R_SUCCEED(); + } + + template<typename EventInfoType> + Result GetDebugEvent(KUserPointer<EventInfoType *> out_info, ams::svc::Handle debug_handle) { + /* Only allow invoking the svc on development hardware or if force debug prod. */ + R_UNLESS(KTargetSystem::IsDebugMode() || GetCurrentProcess().CanForceDebugProd(), svc::ResultNotImplemented()); + + /* Get the debug object. */ + KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(debug_handle); + R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); + + /* Create and clear a new event info. */ + EventInfoType info; + std::memset(std::addressof(info), 0, sizeof(info)); + + /* Get the next info from the debug object. */ + R_TRY(debug->GetDebugEventInfo(std::addressof(info))); + + /* Copy the info out to the user. */ + R_TRY(out_info.CopyFrom(std::addressof(info))); + + R_SUCCEED(); + } + + Result ContinueDebugEventImpl(ams::svc::Handle debug_handle, uint32_t flags, const uint64_t *thread_ids, int32_t num_thread_ids) { + /* Get the debug object. */ + KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(debug_handle); + R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); + + /* Continue the event. */ + R_TRY(debug->ContinueDebug(flags, thread_ids, num_thread_ids)); + + R_SUCCEED(); + } + + Result ContinueDebugEvent(ams::svc::Handle debug_handle, uint32_t flags, KUserPointer<const uint64_t *> user_thread_ids, int32_t num_thread_ids) { + /* Only allow invoking the svc on development hardware. */ + R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNotImplemented()); + + /* Verify that the flags are valid. */ + R_UNLESS((flags | ams::svc::ContinueFlag_AllMask) == ams::svc::ContinueFlag_AllMask, svc::ResultInvalidEnumValue()); + + /* Verify that continue all and continue others flags are exclusive. */ + constexpr u32 AllAndOthersMask = ams::svc::ContinueFlag_ContinueAll | ams::svc::ContinueFlag_ContinueOthers; + R_UNLESS((flags & AllAndOthersMask) != AllAndOthersMask, svc::ResultInvalidEnumValue()); + + /* Verify that the number of thread ids is valid. */ + R_UNLESS((0 <= num_thread_ids && num_thread_ids <= MaximumDebuggableThreadCount), svc::ResultOutOfRange()); + + /* Copy the threads from userspace. */ + uint64_t thread_ids[MaximumDebuggableThreadCount]; + if (num_thread_ids > 0) { + R_TRY(user_thread_ids.CopyArrayTo(thread_ids, num_thread_ids)); + } + + /* Continue the event. */ + R_TRY(ContinueDebugEventImpl(debug_handle, flags, thread_ids, num_thread_ids)); + + R_SUCCEED(); + } + + Result LegacyContinueDebugEvent(ams::svc::Handle debug_handle, uint32_t flags, uint64_t thread_id) { + /* Only allow invoking the svc on development hardware. */ + R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNotImplemented()); + + /* Verify that the flags are valid. */ + R_UNLESS((flags | ams::svc::ContinueFlag_AllMask) == ams::svc::ContinueFlag_AllMask, svc::ResultInvalidEnumValue()); + + /* Verify that continue all and continue others flags are exclusive. */ + constexpr u32 AllAndOthersMask = ams::svc::ContinueFlag_ContinueAll | ams::svc::ContinueFlag_ContinueOthers; + R_UNLESS((flags & AllAndOthersMask) != AllAndOthersMask, svc::ResultInvalidEnumValue()); + + /* Continue the event. */ + R_TRY(ContinueDebugEventImpl(debug_handle, flags, std::addressof(thread_id), 1)); + + R_SUCCEED(); + } + + Result GetDebugThreadContext(KUserPointer<ams::svc::ThreadContext *> out_context, ams::svc::Handle debug_handle, uint64_t thread_id, uint32_t context_flags) { + /* Only allow invoking the svc on development hardware or if force debug prod. */ + R_UNLESS(KTargetSystem::IsDebugMode() || GetCurrentProcess().CanForceDebugProd(), svc::ResultNotImplemented()); + + /* Validate the context flags. */ + R_UNLESS((context_flags | ams::svc::ThreadContextFlag_All) == ams::svc::ThreadContextFlag_All, svc::ResultInvalidEnumValue()); + + /* Get the debug object. */ + KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(debug_handle); + R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the thread context. */ + ams::svc::ThreadContext context = {}; + R_TRY(debug->GetThreadContext(std::addressof(context), thread_id, context_flags)); + + /* Copy the context to userspace. */ + R_TRY(out_context.CopyFrom(std::addressof(context))); + + R_SUCCEED(); + } + + Result SetDebugThreadContext(ams::svc::Handle debug_handle, uint64_t thread_id, KUserPointer<const ams::svc::ThreadContext *> user_context, uint32_t context_flags) { + /* Only allow invoking the svc on development hardware. */ + R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNotImplemented()); + + /* Validate the context flags. */ + #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) + { + /* Check that the flags are a subset of the allowable. */ + constexpr u32 AllFlagsMask = ams::svc::ThreadContextFlag_All | ams::svc::ThreadContextFlag_SetSingleStep | ams::svc::ThreadContextFlag_ClearSingleStep; + R_UNLESS((context_flags | AllFlagsMask) == AllFlagsMask, svc::ResultInvalidEnumValue()); + + /* Check that thread isn't both setting and clearing single step. */ + const bool set_ss = (context_flags & ams::svc::ThreadContextFlag_SetSingleStep) != 0; + const bool clear_ss = (context_flags & ams::svc::ThreadContextFlag_ClearSingleStep) != 0; + + R_UNLESS(!(set_ss && clear_ss), svc::ResultInvalidEnumValue()); + } + #else + { + /* Check that the flags are a subset of the allowable. */ + R_UNLESS((context_flags | ams::svc::ThreadContextFlag_All) == ams::svc::ThreadContextFlag_All, svc::ResultInvalidEnumValue()); + } + #endif + + /* Copy the thread context from userspace. */ + ams::svc::ThreadContext context; + R_TRY(user_context.CopyTo(std::addressof(context))); + + /* Get the debug object. */ + KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(debug_handle); + R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); + + /* Set the thread context. */ + R_TRY(debug->SetThreadContext(context, thread_id, context_flags)); + + R_SUCCEED(); + } + + Result QueryDebugProcessMemory(ams::svc::MemoryInfo *out_memory_info, ams::svc::PageInfo *out_page_info, ams::svc::Handle debug_handle, uintptr_t address) { + /* Only allow invoking the svc on development hardware or if force debug prod. */ + R_UNLESS(KTargetSystem::IsDebugMode() || GetCurrentProcess().CanForceDebugProd(), svc::ResultNotImplemented()); + + /* Get the debug object. */ + KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(debug_handle); + R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); + + /* Query the mapping's info. */ + R_TRY(debug->QueryMemoryInfo(out_memory_info, out_page_info, address)); + + R_SUCCEED(); + } + + template<typename T> + Result QueryDebugProcessMemory(KUserPointer<T *> out_memory_info, ams::svc::PageInfo *out_page_info, ams::svc::Handle debug_handle, uint64_t address) { + /* Get an ams::svc::MemoryInfo for the region. */ + ams::svc::MemoryInfo info = {}; + R_TRY(QueryDebugProcessMemory(std::addressof(info), out_page_info, debug_handle, address)); + + /* Copy the info to userspace. */ + if constexpr (std::same_as<T, ams::svc::MemoryInfo>) { + R_TRY(out_memory_info.CopyFrom(std::addressof(info))); + } else { + /* Convert the info. */ + T converted_info = {}; + static_assert(std::same_as<decltype(T{}.base_address), decltype(ams::svc::MemoryInfo{}.base_address)>); + static_assert(std::same_as<decltype(T{}.size), decltype(ams::svc::MemoryInfo{}.size)>); + + converted_info.base_address = info.base_address; + converted_info.size = info.size; + converted_info.state = info.state; + converted_info.attribute = info.attribute; + converted_info.permission = info.permission; + converted_info.ipc_count = info.ipc_count; + converted_info.device_count = info.device_count; + + /* Copy it. */ + R_TRY(out_memory_info.CopyFrom(std::addressof(converted_info))); + } + + R_SUCCEED(); + } + + Result ReadDebugProcessMemory(uintptr_t buffer, ams::svc::Handle debug_handle, uintptr_t address, size_t size) { + /* Only allow invoking the svc on development hardware or if force debug prod. */ + R_UNLESS(KTargetSystem::IsDebugMode() || GetCurrentProcess().CanForceDebugProd(), svc::ResultNotImplemented()); + + /* Validate address / size. */ + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + R_UNLESS((buffer < buffer + size), svc::ResultInvalidCurrentMemory()); + + /* Get the debug object. */ + KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(debug_handle); + R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); + + /* Read the memory. */ + R_TRY(debug->ReadMemory(buffer, address, size)); + + R_SUCCEED(); + } + + Result WriteDebugProcessMemory(ams::svc::Handle debug_handle, uintptr_t buffer, uintptr_t address, size_t size) { + /* Only allow invoking the svc on development hardware. */ + R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNotImplemented()); + + /* Validate address / size. */ + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + R_UNLESS((buffer < buffer + size), svc::ResultInvalidCurrentMemory()); + + /* Get the debug object. */ + KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(debug_handle); + R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); + + /* Write the memory. */ + R_TRY(debug->WriteMemory(buffer, address, size)); + + R_SUCCEED(); + } + + Result SetHardwareBreakPoint(ams::svc::HardwareBreakPointRegisterName name, uint64_t flags, uint64_t value) { + /* Only allow invoking the svc on development hardware. */ + R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNotImplemented()); + + /* Set the breakpoint. */ + R_TRY(KDebug::SetHardwareBreakPoint(name, flags, value)); + + R_SUCCEED(); + } + + Result GetDebugThreadParam(uint64_t *out_64, uint32_t *out_32, ams::svc::Handle debug_handle, uint64_t thread_id, ams::svc::DebugThreadParam param) { + /* Only allow invoking the svc on development hardware or if force debug prod. */ + R_UNLESS(KTargetSystem::IsDebugMode() || GetCurrentProcess().CanForceDebugProd(), svc::ResultNotImplemented()); + + /* Get the debug object. */ + KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(debug_handle); + R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the thread from its id. */ + KThread *thread = KThread::GetThreadFromId(thread_id); + R_UNLESS(thread != nullptr, svc::ResultInvalidThreadId()); + ON_SCOPE_EXIT { thread->Close(); }; + + /* Get the process from the debug object. */ + R_UNLESS(debug->IsAttached(), svc::ResultProcessTerminated()); + R_UNLESS(debug->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close the process when we're done. */ + ON_SCOPE_EXIT { debug->CloseProcess(); }; + + /* Get the proces. */ + KProcess * const process = debug->GetProcessUnsafe(); + + /* Verify that the process is the thread's parent. */ + R_UNLESS(process == thread->GetOwnerProcess(), svc::ResultInvalidThreadId()); + + /* Get the parameter. */ + switch (param) { + case ams::svc::DebugThreadParam_Priority: + { + /* Get the priority. */ + *out_32 = thread->GetPriority(); + } + break; + case ams::svc::DebugThreadParam_State: + { + /* Get the thread state and suspend status. */ + KThread::ThreadState state; + bool suspended_user; + bool suspended_debug; + { + KScopedSchedulerLock sl; + + state = thread->GetState(); + suspended_user = thread->IsSuspendRequested(KThread::SuspendType_Thread); + suspended_debug = thread->IsSuspendRequested(KThread::SuspendType_Debug); + } + + /* Set the suspend flags. */ + *out_32 = 0; + if (suspended_user) { + *out_32 |= ams::svc::ThreadSuspend_User; + } + if (suspended_debug) { + *out_32 |= ams::svc::ThreadSuspend_Debug; + } + + /* Set the state. */ + switch (state) { + case KThread::ThreadState_Initialized: + { + *out_64 = ams::svc::ThreadState_Initializing; + } + break; + case KThread::ThreadState_Waiting: + { + *out_64 = ams::svc::ThreadState_Waiting; + } + break; + case KThread::ThreadState_Runnable: + { + *out_64 = ams::svc::ThreadState_Running; + } + break; + case KThread::ThreadState_Terminated: + { + *out_64 = ams::svc::ThreadState_Terminated; + } + break; + default: + R_THROW(svc::ResultInvalidState()); + } + } + break; + case ams::svc::DebugThreadParam_IdealCore: + { + /* Get the ideal core. */ + s32 core_id; + u64 affinity_mask; + thread->GetPhysicalCoreMask(std::addressof(core_id), std::addressof(affinity_mask)); + + *out_32 = core_id; + } + break; + case ams::svc::DebugThreadParam_CurrentCore: + { + /* Get the current core. */ + *out_32 = thread->GetActiveCore(); + } + break; + case ams::svc::DebugThreadParam_AffinityMask: + { + /* Get the affinity mask. */ + s32 core_id; + u64 affinity_mask; + thread->GetPhysicalCoreMask(std::addressof(core_id), std::addressof(affinity_mask)); + + *out_32 = affinity_mask; + } + break; + default: + R_THROW(ams::svc::ResultInvalidEnumValue()); + } + + R_SUCCEED(); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result DebugActiveProcess64(ams::svc::Handle *out_handle, uint64_t process_id) { + R_RETURN(DebugActiveProcess(out_handle, process_id)); + } + + Result BreakDebugProcess64(ams::svc::Handle debug_handle) { + R_RETURN(BreakDebugProcess(debug_handle)); + } + + Result TerminateDebugProcess64(ams::svc::Handle debug_handle) { + R_RETURN(TerminateDebugProcess(debug_handle)); + } + + Result GetDebugEvent64(KUserPointer<ams::svc::lp64::DebugEventInfo *> out_info, ams::svc::Handle debug_handle) { + R_RETURN(GetDebugEvent(out_info, debug_handle)); + } + + Result ContinueDebugEvent64(ams::svc::Handle debug_handle, uint32_t flags, KUserPointer<const uint64_t *> thread_ids, int32_t num_thread_ids) { + R_RETURN(ContinueDebugEvent(debug_handle, flags, thread_ids, num_thread_ids)); + } + + Result LegacyContinueDebugEvent64(ams::svc::Handle debug_handle, uint32_t flags, uint64_t thread_id) { + R_RETURN(LegacyContinueDebugEvent(debug_handle, flags, thread_id)); + } + + Result GetDebugThreadContext64(KUserPointer<ams::svc::ThreadContext *> out_context, ams::svc::Handle debug_handle, uint64_t thread_id, uint32_t context_flags) { + R_RETURN(GetDebugThreadContext(out_context, debug_handle, thread_id, context_flags)); + } + + Result SetDebugThreadContext64(ams::svc::Handle debug_handle, uint64_t thread_id, KUserPointer<const ams::svc::ThreadContext *> context, uint32_t context_flags) { + R_RETURN(SetDebugThreadContext(debug_handle, thread_id, context, context_flags)); + } + + Result QueryDebugProcessMemory64(KUserPointer<ams::svc::lp64::MemoryInfo *> out_memory_info, ams::svc::PageInfo *out_page_info, ams::svc::Handle debug_handle, ams::svc::Address address) { + R_RETURN(QueryDebugProcessMemory(out_memory_info, out_page_info, debug_handle, address)); + } + + Result ReadDebugProcessMemory64(ams::svc::Address buffer, ams::svc::Handle debug_handle, ams::svc::Address address, ams::svc::Size size) { + R_RETURN(ReadDebugProcessMemory(buffer, debug_handle, address, size)); + } + + Result WriteDebugProcessMemory64(ams::svc::Handle debug_handle, ams::svc::Address buffer, ams::svc::Address address, ams::svc::Size size) { + R_RETURN(WriteDebugProcessMemory(debug_handle, buffer, address, size)); + } + + Result SetHardwareBreakPoint64(ams::svc::HardwareBreakPointRegisterName name, uint64_t flags, uint64_t value) { + R_RETURN(SetHardwareBreakPoint(name, flags, value)); + } + + Result GetDebugThreadParam64(uint64_t *out_64, uint32_t *out_32, ams::svc::Handle debug_handle, uint64_t thread_id, ams::svc::DebugThreadParam param) { + R_RETURN(GetDebugThreadParam(out_64, out_32, debug_handle, thread_id, param)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result DebugActiveProcess64From32(ams::svc::Handle *out_handle, uint64_t process_id) { + R_RETURN(DebugActiveProcess(out_handle, process_id)); + } + + Result BreakDebugProcess64From32(ams::svc::Handle debug_handle) { + R_RETURN(BreakDebugProcess(debug_handle)); + } + + Result TerminateDebugProcess64From32(ams::svc::Handle debug_handle) { + R_RETURN(TerminateDebugProcess(debug_handle)); + } + + Result GetDebugEvent64From32(KUserPointer<ams::svc::ilp32::DebugEventInfo *> out_info, ams::svc::Handle debug_handle) { + R_RETURN(GetDebugEvent(out_info, debug_handle)); + } + + Result ContinueDebugEvent64From32(ams::svc::Handle debug_handle, uint32_t flags, KUserPointer<const uint64_t *> thread_ids, int32_t num_thread_ids) { + R_RETURN(ContinueDebugEvent(debug_handle, flags, thread_ids, num_thread_ids)); + } + + Result LegacyContinueDebugEvent64From32(ams::svc::Handle debug_handle, uint32_t flags, uint64_t thread_id) { + R_RETURN(LegacyContinueDebugEvent(debug_handle, flags, thread_id)); + } + + Result GetDebugThreadContext64From32(KUserPointer<ams::svc::ThreadContext *> out_context, ams::svc::Handle debug_handle, uint64_t thread_id, uint32_t context_flags) { + R_RETURN(GetDebugThreadContext(out_context, debug_handle, thread_id, context_flags)); + } + + Result SetDebugThreadContext64From32(ams::svc::Handle debug_handle, uint64_t thread_id, KUserPointer<const ams::svc::ThreadContext *> context, uint32_t context_flags) { + R_RETURN(SetDebugThreadContext(debug_handle, thread_id, context, context_flags)); + } + + Result QueryDebugProcessMemory64From32(KUserPointer<ams::svc::ilp32::MemoryInfo *> out_memory_info, ams::svc::PageInfo *out_page_info, ams::svc::Handle debug_handle, ams::svc::Address address) { + R_RETURN(QueryDebugProcessMemory(out_memory_info, out_page_info, debug_handle, address)); + } + + Result ReadDebugProcessMemory64From32(ams::svc::Address buffer, ams::svc::Handle debug_handle, ams::svc::Address address, ams::svc::Size size) { + R_RETURN(ReadDebugProcessMemory(buffer, debug_handle, address, size)); + } + + Result WriteDebugProcessMemory64From32(ams::svc::Handle debug_handle, ams::svc::Address buffer, ams::svc::Address address, ams::svc::Size size) { + R_RETURN(WriteDebugProcessMemory(debug_handle, buffer, address, size)); + } + + Result SetHardwareBreakPoint64From32(ams::svc::HardwareBreakPointRegisterName name, uint64_t flags, uint64_t value) { + R_RETURN(SetHardwareBreakPoint(name, flags, value)); + } + + Result GetDebugThreadParam64From32(uint64_t *out_64, uint32_t *out_32, ams::svc::Handle debug_handle, uint64_t thread_id, ams::svc::DebugThreadParam param) { + R_RETURN(GetDebugThreadParam(out_64, out_32, debug_handle, thread_id, param)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_debug_string.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_debug_string.cpp new file mode 100644 index 00000000..c36c93ff --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_debug_string.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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + Result OutputDebugString(KUserPointer<const char *> debug_str, size_t len) { + /* Succeed immediately if there's nothing to output. */ + R_SUCCEED_IF(len == 0); + + /* Ensure that the data being output is in range. */ + R_UNLESS(GetCurrentProcess().GetPageTable().Contains(KProcessAddress(debug_str.GetUnsafePointer()), len), svc::ResultInvalidCurrentMemory()); + + /* Output the string. */ + R_RETURN(KDebugLog::PrintUserString(debug_str, len)); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result OutputDebugString64(KUserPointer<const char *> debug_str, ams::svc::Size len) { + R_RETURN(OutputDebugString(debug_str, len)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result OutputDebugString64From32(KUserPointer<const char *> debug_str, ams::svc::Size len) { + R_RETURN(OutputDebugString(debug_str, len)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_device_address_space.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_device_address_space.cpp new file mode 100644 index 00000000..55f3c2ac --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_device_address_space.cpp @@ -0,0 +1,229 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + constexpr inline u64 DeviceAddressSpaceAlignMask = (1ul << 22) - 1; + + constexpr bool IsProcessAndDeviceAligned(uint64_t process_address, uint64_t device_address) { + return (process_address & DeviceAddressSpaceAlignMask) == (device_address & DeviceAddressSpaceAlignMask); + } + + Result CreateDeviceAddressSpace(ams::svc::Handle *out, uint64_t das_address, uint64_t das_size) { + /* Validate input. */ + R_UNLESS(util::IsAligned(das_address, PageSize), svc::ResultInvalidMemoryRegion()); + R_UNLESS(util::IsAligned(das_size, PageSize), svc::ResultInvalidMemoryRegion()); + R_UNLESS(das_size > 0, svc::ResultInvalidMemoryRegion()); + R_UNLESS((das_address < das_address + das_size), svc::ResultInvalidMemoryRegion()); + + /* Create the device address space. */ + KDeviceAddressSpace *das = KDeviceAddressSpace::Create(); + R_UNLESS(das != nullptr, svc::ResultOutOfResource()); + ON_SCOPE_EXIT { das->Close(); }; + + /* Initialize the device address space. */ + R_TRY(das->Initialize(das_address, das_size)); + + /* Register the device address space. */ + KDeviceAddressSpace::Register(das); + + /* Add to the handle table. */ + R_TRY(GetCurrentProcess().GetHandleTable().Add(out, das)); + + R_SUCCEED(); + } + + Result AttachDeviceAddressSpace(ams::svc::DeviceName device_name, ams::svc::Handle das_handle) { + /* Get the device address space. */ + KScopedAutoObject das = GetCurrentProcess().GetHandleTable().GetObject<KDeviceAddressSpace>(das_handle); + R_UNLESS(das.IsNotNull(), svc::ResultInvalidHandle()); + + /* Attach. */ + R_RETURN(das->Attach(device_name)); + } + + Result DetachDeviceAddressSpace(ams::svc::DeviceName device_name, ams::svc::Handle das_handle) { + /* Get the device address space. */ + KScopedAutoObject das = GetCurrentProcess().GetHandleTable().GetObject<KDeviceAddressSpace>(das_handle); + R_UNLESS(das.IsNotNull(), svc::ResultInvalidHandle()); + + /* Detach. */ + R_RETURN(das->Detach(device_name)); + } + + constexpr bool IsValidDeviceMemoryPermission(ams::svc::MemoryPermission device_perm) { + switch (device_perm) { + case ams::svc::MemoryPermission_Read: + case ams::svc::MemoryPermission_Write: + case ams::svc::MemoryPermission_ReadWrite: + return true; + default: + return false; + } + } + + Result MapDeviceAddressSpaceByForce(ams::svc::Handle das_handle, ams::svc::Handle process_handle, uint64_t process_address, size_t size, uint64_t device_address, u32 option) { + /* Decode the option. */ + const util::BitPack32 option_pack = { option }; + const auto device_perm = option_pack.Get<ams::svc::MapDeviceAddressSpaceOption::Permission>(); + const auto reserved = option_pack.Get<ams::svc::MapDeviceAddressSpaceOption::Reserved>(); + + /* Validate input. */ + R_UNLESS(util::IsAligned(process_address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(device_address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((process_address < process_address + size), svc::ResultInvalidCurrentMemory()); + R_UNLESS((device_address < device_address + size), svc::ResultInvalidMemoryRegion()); + R_UNLESS((process_address == static_cast<uintptr_t>(process_address)), svc::ResultInvalidCurrentMemory()); + R_UNLESS(IsValidDeviceMemoryPermission(device_perm), svc::ResultInvalidNewMemoryPermission()); + R_UNLESS(reserved == 0, svc::ResultInvalidEnumValue()); + + /* Get the device address space. */ + KScopedAutoObject das = GetCurrentProcess().GetHandleTable().GetObject<KDeviceAddressSpace>(das_handle); + R_UNLESS(das.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the process. */ + KScopedAutoObject process = GetCurrentProcess().GetHandleTable().GetObject<KProcess>(process_handle); + R_UNLESS(process.IsNotNull(), svc::ResultInvalidHandle()); + + /* Validate that the process address is within range. */ + auto &page_table = process->GetPageTable(); + R_UNLESS(page_table.Contains(process_address, size), svc::ResultInvalidCurrentMemory()); + + /* Map. */ + R_RETURN(das->MapByForce(std::addressof(page_table), KProcessAddress(process_address), size, device_address, option)); + } + + Result MapDeviceAddressSpaceAligned(ams::svc::Handle das_handle, ams::svc::Handle process_handle, uint64_t process_address, size_t size, uint64_t device_address, u32 option) { + /* Decode the option. */ + const util::BitPack32 option_pack = { option }; + const auto device_perm = option_pack.Get<ams::svc::MapDeviceAddressSpaceOption::Permission>(); + const auto reserved = option_pack.Get<ams::svc::MapDeviceAddressSpaceOption::Reserved>(); + + /* Validate input. */ + R_UNLESS(util::IsAligned(process_address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(device_address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(IsProcessAndDeviceAligned(process_address, device_address), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((process_address < process_address + size), svc::ResultInvalidCurrentMemory()); + R_UNLESS((device_address < device_address + size), svc::ResultInvalidMemoryRegion()); + R_UNLESS((process_address == static_cast<uintptr_t>(process_address)), svc::ResultInvalidCurrentMemory()); + R_UNLESS(IsValidDeviceMemoryPermission(device_perm), svc::ResultInvalidNewMemoryPermission()); + R_UNLESS(reserved == 0, svc::ResultInvalidEnumValue()); + + /* Get the device address space. */ + KScopedAutoObject das = GetCurrentProcess().GetHandleTable().GetObject<KDeviceAddressSpace>(das_handle); + R_UNLESS(das.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the process. */ + KScopedAutoObject process = GetCurrentProcess().GetHandleTable().GetObject<KProcess>(process_handle); + R_UNLESS(process.IsNotNull(), svc::ResultInvalidHandle()); + + /* Validate that the process address is within range. */ + auto &page_table = process->GetPageTable(); + R_UNLESS(page_table.Contains(process_address, size), svc::ResultInvalidCurrentMemory()); + + /* Map. */ + R_RETURN(das->MapAligned(std::addressof(page_table), KProcessAddress(process_address), size, device_address, option)); + } + + Result UnmapDeviceAddressSpace(ams::svc::Handle das_handle, ams::svc::Handle process_handle, uint64_t process_address, size_t size, uint64_t device_address) { + /* Validate input. */ + R_UNLESS(util::IsAligned(process_address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(device_address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((process_address < process_address + size), svc::ResultInvalidCurrentMemory()); + R_UNLESS((device_address < device_address + size), svc::ResultInvalidMemoryRegion()); + R_UNLESS((process_address == static_cast<uintptr_t>(process_address)), svc::ResultInvalidCurrentMemory()); + + /* Get the device address space. */ + KScopedAutoObject das = GetCurrentProcess().GetHandleTable().GetObject<KDeviceAddressSpace>(das_handle); + R_UNLESS(das.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the process. */ + KScopedAutoObject process = GetCurrentProcess().GetHandleTable().GetObject<KProcess>(process_handle); + R_UNLESS(process.IsNotNull(), svc::ResultInvalidHandle()); + + /* Validate that the process address is within range. */ + auto &page_table = process->GetPageTable(); + R_UNLESS(page_table.Contains(process_address, size), svc::ResultInvalidCurrentMemory()); + + R_RETURN(das->Unmap(std::addressof(page_table), KProcessAddress(process_address), size, device_address)); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result CreateDeviceAddressSpace64(ams::svc::Handle *out_handle, uint64_t das_address, uint64_t das_size) { + R_RETURN(CreateDeviceAddressSpace(out_handle, das_address, das_size)); + } + + Result AttachDeviceAddressSpace64(ams::svc::DeviceName device_name, ams::svc::Handle das_handle) { + R_RETURN(AttachDeviceAddressSpace(device_name, das_handle)); + } + + Result DetachDeviceAddressSpace64(ams::svc::DeviceName device_name, ams::svc::Handle das_handle) { + R_RETURN(DetachDeviceAddressSpace(device_name, das_handle)); + } + + Result MapDeviceAddressSpaceByForce64(ams::svc::Handle das_handle, ams::svc::Handle process_handle, uint64_t process_address, ams::svc::Size size, uint64_t device_address, u32 option) { + R_RETURN(MapDeviceAddressSpaceByForce(das_handle, process_handle, process_address, size, device_address, option)); + } + + Result MapDeviceAddressSpaceAligned64(ams::svc::Handle das_handle, ams::svc::Handle process_handle, uint64_t process_address, ams::svc::Size size, uint64_t device_address, u32 option) { + R_RETURN(MapDeviceAddressSpaceAligned(das_handle, process_handle, process_address, size, device_address, option)); + } + + Result UnmapDeviceAddressSpace64(ams::svc::Handle das_handle, ams::svc::Handle process_handle, uint64_t process_address, ams::svc::Size size, uint64_t device_address) { + R_RETURN(UnmapDeviceAddressSpace(das_handle, process_handle, process_address, size, device_address)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result CreateDeviceAddressSpace64From32(ams::svc::Handle *out_handle, uint64_t das_address, uint64_t das_size) { + R_RETURN(CreateDeviceAddressSpace(out_handle, das_address, das_size)); + } + + Result AttachDeviceAddressSpace64From32(ams::svc::DeviceName device_name, ams::svc::Handle das_handle) { + R_RETURN(AttachDeviceAddressSpace(device_name, das_handle)); + } + + Result DetachDeviceAddressSpace64From32(ams::svc::DeviceName device_name, ams::svc::Handle das_handle) { + R_RETURN(DetachDeviceAddressSpace(device_name, das_handle)); + } + + Result MapDeviceAddressSpaceByForce64From32(ams::svc::Handle das_handle, ams::svc::Handle process_handle, uint64_t process_address, ams::svc::Size size, uint64_t device_address, u32 option) { + R_RETURN(MapDeviceAddressSpaceByForce(das_handle, process_handle, process_address, size, device_address, option)); + } + + Result MapDeviceAddressSpaceAligned64From32(ams::svc::Handle das_handle, ams::svc::Handle process_handle, uint64_t process_address, ams::svc::Size size, uint64_t device_address, u32 option) { + R_RETURN(MapDeviceAddressSpaceAligned(das_handle, process_handle, process_address, size, device_address, option)); + } + + Result UnmapDeviceAddressSpace64From32(ams::svc::Handle das_handle, ams::svc::Handle process_handle, uint64_t process_address, ams::svc::Size size, uint64_t device_address) { + R_RETURN(UnmapDeviceAddressSpace(das_handle, process_handle, process_address, size, device_address)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_event.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_event.cpp new file mode 100644 index 00000000..571347be --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_event.cpp @@ -0,0 +1,146 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + Result SignalEvent(ams::svc::Handle event_handle) { + /* Get the current handle table. */ + auto &handle_table = GetCurrentProcess().GetHandleTable(); + + /* Get the writable event. */ + KScopedAutoObject event = handle_table.GetObject<KEvent>(event_handle); + R_UNLESS(event.IsNotNull(), svc::ResultInvalidHandle()); + + R_RETURN(event->Signal()); + } + + Result ClearEvent(ams::svc::Handle event_handle) { + /* Get the current handle table. */ + auto &handle_table = GetCurrentProcess().GetHandleTable(); + + /* Try to clear the writable event. */ + { + KScopedAutoObject event = handle_table.GetObject<KEvent>(event_handle); + if (event.IsNotNull()) { + R_RETURN(event->Clear()); + } + } + + /* Try to clear the readable event. */ + { + KScopedAutoObject readable_event = handle_table.GetObject<KReadableEvent>(event_handle); + if (readable_event.IsNotNull()) { + if (auto * const interrupt_event = readable_event->DynamicCast<KInterruptEvent *>(); interrupt_event != nullptr) { + R_RETURN(interrupt_event->Clear()); + } else { + R_RETURN(readable_event->Clear()); + } + } + } + + R_THROW(svc::ResultInvalidHandle()); + } + + Result CreateEvent(ams::svc::Handle *out_write, ams::svc::Handle *out_read) { + /* Get the current process and handle table. */ + auto &process = GetCurrentProcess(); + auto &handle_table = process.GetHandleTable(); + + /* Declare the event we're going to allocate. */ + KEvent *event; + + /* Reserve a new event from the process resource limit. */ + KScopedResourceReservation event_reservation(std::addressof(process), ams::svc::LimitableResource_EventCountMax); + if (event_reservation.Succeeded()) { + /* Allocate an event normally. */ + event = KEvent::Create(); + } else { + /* We couldn't reserve an event. Check that we support dynamically expanding the resource limit. */ + R_UNLESS(process.GetResourceLimit() == std::addressof(Kernel::GetSystemResourceLimit()), svc::ResultLimitReached()); + R_UNLESS(KTargetSystem::IsDynamicResourceLimitsEnabled(), svc::ResultLimitReached()); + + /* Try to allocate an event from unused slab memory. */ + event = KEvent::CreateFromUnusedSlabMemory(); + R_UNLESS(event != nullptr, svc::ResultLimitReached()); + + /* We successfully allocated an event, so add the object we allocated to the resource limit. */ + Kernel::GetSystemResourceLimit().Add(ams::svc::LimitableResource_EventCountMax, 1); + } + + /* Check that we successfully created an event. */ + R_UNLESS(event != nullptr, svc::ResultOutOfResource()); + + /* Initialize the event. */ + event->Initialize(); + + /* Commit the event reservation. */ + event_reservation.Commit(); + + /* Ensure that we clean up the event (and its only references are handle table) on function end. */ + ON_SCOPE_EXIT { + event->GetReadableEvent().Close(); + event->Close(); + }; + + /* Register the event. */ + KEvent::Register(event); + + /* Add the event to the handle table. */ + R_TRY(handle_table.Add(out_write, event)); + + /* Ensure that we maintaing a clean handle state on exit. */ + ON_RESULT_FAILURE { handle_table.Remove(*out_write); }; + + /* Add the readable event to the handle table. */ + R_RETURN(handle_table.Add(out_read, std::addressof(event->GetReadableEvent()))); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result SignalEvent64(ams::svc::Handle event_handle) { + R_RETURN(SignalEvent(event_handle)); + } + + Result ClearEvent64(ams::svc::Handle event_handle) { + R_RETURN(ClearEvent(event_handle)); + } + + Result CreateEvent64(ams::svc::Handle *out_write_handle, ams::svc::Handle *out_read_handle) { + R_RETURN(CreateEvent(out_write_handle, out_read_handle)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result SignalEvent64From32(ams::svc::Handle event_handle) { + R_RETURN(SignalEvent(event_handle)); + } + + Result ClearEvent64From32(ams::svc::Handle event_handle) { + R_RETURN(ClearEvent(event_handle)); + } + + Result CreateEvent64From32(ams::svc::Handle *out_write_handle, ams::svc::Handle *out_read_handle) { + R_RETURN(CreateEvent(out_write_handle, out_read_handle)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_exception.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_exception.cpp new file mode 100644 index 00000000..66764645 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_exception.cpp @@ -0,0 +1,90 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + #if defined(MESOSPHERE_BUILD_FOR_DEBUGGING) + void PrintBreak(ams::svc::BreakReason break_reason) { + /* Print that break was called. */ + MESOSPHERE_RELEASE_LOG("%s: svc::Break(%d) was called, pid=%ld, tid=%ld\n", GetCurrentProcess().GetName(), static_cast<s32>(break_reason), GetCurrentProcess().GetId(), GetCurrentThread().GetId()); + + /* Print the current thread's registers. */ + KDebug::PrintRegister(); + + /* Print a backtrace. */ + KDebug::PrintBacktrace(); + } + #endif + + void Break(ams::svc::BreakReason break_reason, uintptr_t address, size_t size) { + /* Determine whether the break is only a notification. */ + const bool is_notification = (break_reason & ams::svc::BreakReason_NotificationOnlyFlag) != 0; + + /* If the break isn't a notification, print it. */ + if (!is_notification) { + #if defined(MESOSPHERE_BUILD_FOR_DEBUGGING) + PrintBreak(break_reason); + #endif + } + + /* If the current process is attached to debugger, try to notify it. */ + if (GetCurrentProcess().IsAttachedToDebugger()) { + if (R_SUCCEEDED(KDebug::BreakIfAttached(break_reason, address, size))) { + /* If we attached, set the pc to the instruction before the current one and return. */ + KDebug::SetPreviousProgramCounter(); + return; + } + } + + /* If the break is only a notification, we're done. */ + if (is_notification) { + return; + } + + /* Print that break was called. */ + MESOSPHERE_EXCEPTION_LOG("Break() called. "); + + /* Try to enter JIT debug state. */ + if (GetCurrentProcess().EnterJitDebug(ams::svc::DebugEvent_Exception, ams::svc::DebugException_UserBreak, KDebug::GetProgramCounter(GetCurrentThread()), break_reason, address, size)) { + /* We entered JIT debug, so set the pc to the instruction before the current one and return. */ + KDebug::SetPreviousProgramCounter(); + return; + } + + /* Exit the current process. */ + GetCurrentProcess().Exit(); + } + + } + + /* ============================= 64 ABI ============================= */ + + void Break64(ams::svc::BreakReason break_reason, ams::svc::Address arg, ams::svc::Size size) { + return Break(break_reason, arg, size); + } + + /* ============================= 64From32 ABI ============================= */ + + void Break64From32(ams::svc::BreakReason break_reason, ams::svc::Address arg, ams::svc::Size size) { + return Break(break_reason, arg, size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_info.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_info.cpp new file mode 100644 index 00000000..ec41ef59 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_info.cpp @@ -0,0 +1,471 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + Result GetInitialProcessIdRange(u64 *out, ams::svc::InitialProcessIdRangeInfo info) { + switch (info) { + case ams::svc::InitialProcessIdRangeInfo_Minimum: + MESOSPHERE_ABORT_UNLESS(GetInitialProcessIdMin() <= GetInitialProcessIdMax()); + *out = GetInitialProcessIdMin(); + break; + case ams::svc::InitialProcessIdRangeInfo_Maximum: + MESOSPHERE_ABORT_UNLESS(GetInitialProcessIdMin() <= GetInitialProcessIdMax()); + *out = GetInitialProcessIdMax(); + break; + default: + R_THROW(svc::ResultInvalidCombination()); + } + + R_SUCCEED(); + } + + Result GetInfoImpl(u64 *out, ams::svc::InfoType info_type, KProcess *process) { + switch (info_type) { + case ams::svc::InfoType_CoreMask: + *out = process->GetCoreMask(); + break; + case ams::svc::InfoType_PriorityMask: + *out = process->GetPriorityMask(); + break; + case ams::svc::InfoType_AliasRegionAddress: + *out = GetInteger(process->GetPageTable().GetAliasRegionStart()); + break; + case ams::svc::InfoType_AliasRegionSize: + *out = process->GetPageTable().GetAliasRegionSize(); + break; + case ams::svc::InfoType_HeapRegionAddress: + *out = GetInteger(process->GetPageTable().GetHeapRegionStart()); + break; + case ams::svc::InfoType_HeapRegionSize: + *out = process->GetPageTable().GetHeapRegionSize(); + break; + case ams::svc::InfoType_TotalMemorySize: + *out = process->GetTotalUserPhysicalMemorySize(); + break; + case ams::svc::InfoType_UsedMemorySize: + *out = process->GetUsedUserPhysicalMemorySize(); + break; + case ams::svc::InfoType_AslrRegionAddress: + *out = GetInteger(process->GetPageTable().GetAliasCodeRegionStart()); + break; + case ams::svc::InfoType_AslrRegionSize: + *out = process->GetPageTable().GetAliasCodeRegionSize(); + break; + case ams::svc::InfoType_StackRegionAddress: + *out = GetInteger(process->GetPageTable().GetStackRegionStart()); + break; + case ams::svc::InfoType_StackRegionSize: + *out = process->GetPageTable().GetStackRegionSize(); + break; + case ams::svc::InfoType_SystemResourceSizeTotal: + *out = process->GetTotalSystemResourceSize(); + break; + case ams::svc::InfoType_SystemResourceSizeUsed: + *out = process->GetUsedSystemResourceSize(); + break; + case ams::svc::InfoType_ProgramId: + *out = process->GetProgramId(); + break; + case ams::svc::InfoType_UserExceptionContextAddress: + *out = GetInteger(process->GetProcessLocalRegionAddress()); + break; + case ams::svc::InfoType_TotalNonSystemMemorySize: + *out = process->GetTotalNonSystemUserPhysicalMemorySize(); + break; + case ams::svc::InfoType_UsedNonSystemMemorySize: + *out = process->GetUsedNonSystemUserPhysicalMemorySize(); + break; + case ams::svc::InfoType_IsApplication: + *out = process->IsApplication(); + break; + case ams::svc::InfoType_FreeThreadCount: + if (KResourceLimit *resource_limit = process->GetResourceLimit(); resource_limit != nullptr) { + const auto current_value = resource_limit->GetCurrentValue(ams::svc::LimitableResource_ThreadCountMax); + const auto limit_value = resource_limit->GetLimitValue(ams::svc::LimitableResource_ThreadCountMax); + *out = limit_value - current_value; + } else { + *out = 0; + } + break; + case ams::svc::InfoType_AliasRegionExtraSize: + *out = process->GetPageTable().GetAliasRegionExtraSize(); + break; + MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); + } + + R_SUCCEED(); + } + + Result GetInfo(u64 *out, ams::svc::InfoType info_type, ams::svc::Handle handle, u64 info_subtype) { + switch (info_type) { + case ams::svc::InfoType_CoreMask: + case ams::svc::InfoType_PriorityMask: + case ams::svc::InfoType_AliasRegionAddress: + case ams::svc::InfoType_AliasRegionSize: + case ams::svc::InfoType_HeapRegionAddress: + case ams::svc::InfoType_HeapRegionSize: + case ams::svc::InfoType_TotalMemorySize: + case ams::svc::InfoType_UsedMemorySize: + case ams::svc::InfoType_AslrRegionAddress: + case ams::svc::InfoType_AslrRegionSize: + case ams::svc::InfoType_StackRegionAddress: + case ams::svc::InfoType_StackRegionSize: + case ams::svc::InfoType_SystemResourceSizeTotal: + case ams::svc::InfoType_SystemResourceSizeUsed: + case ams::svc::InfoType_ProgramId: + case ams::svc::InfoType_UserExceptionContextAddress: + case ams::svc::InfoType_TotalNonSystemMemorySize: + case ams::svc::InfoType_UsedNonSystemMemorySize: + case ams::svc::InfoType_IsApplication: + case ams::svc::InfoType_FreeThreadCount: + case ams::svc::InfoType_AliasRegionExtraSize: + { + /* These info types don't support non-zero subtypes. */ + R_UNLESS(info_subtype == 0, svc::ResultInvalidCombination()); + + /* Get the process from its handle. */ + KScopedAutoObject process = GetCurrentProcess().GetHandleTable().GetObject<KProcess>(handle); + + #if defined(MESOSPHERE_ENABLE_GET_INFO_OF_DEBUG_PROCESS) + /* If we the process is valid, use it. */ + if (process.IsNotNull()) { + R_RETURN(GetInfoImpl(out, info_type, process.GetPointerUnsafe())); + } + + /* Otherwise, as a mesosphere extension check if we were passed a usable KDebug. */ + KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(handle); + R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the process from the debug object. */ + /* TODO: ResultInvalidHandle()? */ + R_UNLESS(debug->IsAttached(), svc::ResultProcessTerminated()); + R_UNLESS(debug->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close the process when we're done. */ + ON_SCOPE_EXIT { debug->CloseProcess(); }; + + /* Return the info. */ + R_RETURN(GetInfoImpl(out, info_type, debug->GetProcessUnsafe())); + #else + /* Verify that the process is valid. */ + R_UNLESS(process.IsNotNull(), svc::ResultInvalidHandle()); + + /* Return the relevant info. */ + R_RETURN(GetInfoImpl(out, info_type, process.GetPointerUnsafe())); + #endif + } + break; + case ams::svc::InfoType_DebuggerAttached: + { + /* Verify the input handle is invalid. */ + R_UNLESS(handle == ams::svc::InvalidHandle, svc::ResultInvalidHandle()); + + /* Verify the sub-type is valid. */ + R_UNLESS(info_subtype == 0, svc::ResultInvalidCombination()); + + /* Get whether debugger is attached. */ + *out = GetCurrentProcess().GetDebugObject() != nullptr; + } + break; + case ams::svc::InfoType_ResourceLimit: + { + /* Verify the input handle is invalid. */ + R_UNLESS(handle == ams::svc::InvalidHandle, svc::ResultInvalidHandle()); + + /* Verify the sub-type is valid. */ + R_UNLESS(info_subtype == 0, svc::ResultInvalidCombination()); + + /* Get the handle table and resource limit. */ + KHandleTable &handle_table = GetCurrentProcess().GetHandleTable(); + KResourceLimit *resource_limit = GetCurrentProcess().GetResourceLimit(); + + if (resource_limit != nullptr) { + /* Get a new handle for the resource limit. */ + ams::svc::Handle tmp; + R_TRY(handle_table.Add(std::addressof(tmp), resource_limit)); + + /* Set the output. */ + *out = tmp; + } else { + /* Set the output. */ + *out = ams::svc::InvalidHandle; + } + } + break; + case ams::svc::InfoType_IdleTickCount: + { + /* Verify the input handle is invalid. */ + R_UNLESS(handle == ams::svc::InvalidHandle, svc::ResultInvalidHandle()); + + /* Disable dispatch while we get the tick count. */ + KScopedDisableDispatch dd; + + /* Verify the requested core is valid. */ + const bool core_valid = (info_subtype == static_cast<u64>(-1ul)) || (info_subtype == static_cast<u64>(GetCurrentCoreId())); + R_UNLESS(core_valid, svc::ResultInvalidCombination()); + + /* Get the idle tick count. */ + *out = Kernel::GetScheduler().GetIdleThread()->GetCpuTime() - Kernel::GetInterruptTaskManager().GetCpuTime(); + } + break; + case ams::svc::InfoType_RandomEntropy: + { + /* Verify the input handle is invalid. */ + R_UNLESS(handle == ams::svc::InvalidHandle, svc::ResultInvalidHandle()); + + /* Verify the requested entropy is valid. */ + R_UNLESS(info_subtype < 4, svc::ResultInvalidCombination()); + + /* Get the entropy. */ + *out = GetCurrentProcess().GetRandomEntropy(info_subtype); + } + break; + case ams::svc::InfoType_InitialProcessIdRange: + { + /* NOTE: This info type was added in 4.0.0, and removed in 5.0.0. */ + R_UNLESS(GetTargetFirmware() < TargetFirmware_5_0_0, svc::ResultInvalidEnumValue()); + + /* Verify the input handle is invalid. */ + R_UNLESS(handle == ams::svc::InvalidHandle, svc::ResultInvalidHandle()); + + /* Get the process id range. */ + R_TRY(GetInitialProcessIdRange(out, static_cast<ams::svc::InitialProcessIdRangeInfo>(info_subtype))); + } + break; + case ams::svc::InfoType_ThreadTickCount: + { + /* Verify the requested core is valid. */ + const bool core_valid = (info_subtype == static_cast<u64>(-1ul)) || (info_subtype < cpu::NumVirtualCores); + R_UNLESS(core_valid, svc::ResultInvalidCombination()); + + /* Get the thread from its handle. */ + KScopedAutoObject thread = GetCurrentProcess().GetHandleTable().GetObject<KThread>(handle); + R_UNLESS(thread.IsNotNull(), svc::ResultInvalidHandle()); + + /* Disable dispatch while we get the tick count. */ + KScopedDisableDispatch dd; + + /* Determine the tick count. */ + s64 tick_count; + if (info_subtype == static_cast<u64>(-1ul)) { + tick_count = thread->GetCpuTime(); + if (GetCurrentThreadPointer() == thread.GetPointerUnsafe()) { + const s64 cur_tick = KHardwareTimer::GetTick(); + const s64 prev_switch = Kernel::GetScheduler().GetLastContextSwitchTime(); + tick_count += (cur_tick - prev_switch); + } + } else { + const s32 phys_core = cpu::VirtualToPhysicalCoreMap[info_subtype]; + MESOSPHERE_ABORT_UNLESS(phys_core < static_cast<s32>(cpu::NumCores)); + + tick_count = thread->GetCpuTime(phys_core); + if (GetCurrentThreadPointer() == thread.GetPointerUnsafe() && phys_core == GetCurrentCoreId()) { + const s64 cur_tick = KHardwareTimer::GetTick(); + const s64 prev_switch = Kernel::GetScheduler().GetLastContextSwitchTime(); + tick_count += (cur_tick - prev_switch); + } + } + + /* Set the output. */ + *out = tick_count; + } + break; + case ams::svc::InfoType_IsSvcPermitted: + { + /* Verify the input handle is invalid. */ + R_UNLESS(handle == ams::svc::InvalidHandle, svc::ResultInvalidHandle()); + + /* Verify the sub-type is valid. */ + R_UNLESS(info_subtype == svc::SvcId_SynchronizePreemptionState, svc::ResultInvalidCombination()); + + /* Get whether the svc is permitted. */ + *out = GetCurrentProcess().IsPermittedSvc(static_cast<svc::SvcId>(info_subtype)); + } + break; + case ams::svc::InfoType_IoRegionHint: + { + /* Verify the sub-type is valid. */ + R_UNLESS(info_subtype == 0, svc::ResultInvalidCombination()); + + /* Get the io region from its handle. */ + KScopedAutoObject io_region = GetCurrentProcess().GetHandleTable().GetObject<KIoRegion>(handle); + R_UNLESS(io_region.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the io region's address hint. */ + *out = io_region->GetHint(); + } + break; + case ams::svc::InfoType_TransferMemoryHint: + { + /* Verify the sub-type is valid. */ + R_UNLESS(info_subtype == 0, svc::ResultInvalidCombination()); + + /* Get the transfer memory from its handle. */ + KScopedAutoObject transfer_memory = GetCurrentProcess().GetHandleTable().GetObject<KTransferMemory>(handle); + R_UNLESS(transfer_memory.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the transfer memory's address hint. */ + *out = transfer_memory->GetHint(); + } + break; + case ams::svc::InfoType_MesosphereMeta: + { + /* Verify the handle is invalid. */ + R_UNLESS(handle == ams::svc::InvalidHandle, svc::ResultInvalidHandle()); + + switch (static_cast<ams::svc::MesosphereMetaInfo>(info_subtype)) { + case ams::svc::MesosphereMetaInfo_KernelVersion: + { + /* Return the supported kernel version. */ + *out = ams::svc::SupportedKernelVersion; + } + break; + case ams::svc::MesosphereMetaInfo_IsKTraceEnabled: + { + /* Return whether the kernel supports tracing. */ + constexpr u64 KTraceValue = ams::kern::IsKTraceEnabled ? 1 : 0; + *out = KTraceValue; + } + break; + case ams::svc::MesosphereMetaInfo_IsSingleStepEnabled: + { + /* Return whether the kernel supports hardware single step. */ + #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) + *out = 1; + #else + *out = 0; + #endif + } + break; + default: + R_THROW(svc::ResultInvalidCombination()); + } + } + break; + case ams::svc::InfoType_MesosphereCurrentProcess: + { + /* Verify the input handle is invalid. */ + R_UNLESS(handle == ams::svc::InvalidHandle, svc::ResultInvalidHandle()); + + /* Verify the sub-type is valid. */ + R_UNLESS(info_subtype == 0, svc::ResultInvalidCombination()); + + /* Get the handle table. */ + KHandleTable &handle_table = GetCurrentProcess().GetHandleTable(); + + /* Get a new handle for the current process. */ + ams::svc::Handle tmp; + R_TRY(handle_table.Add(std::addressof(tmp), GetCurrentProcessPointer())); + + /* Set the output. */ + *out = tmp; + } + break; + default: + { + /* For debug, log the invalid info call. */ + MESOSPHERE_LOG("GetInfo(%p, %u, %08x, %lu) was called\n", out, static_cast<u32>(info_type), static_cast<u32>(handle), info_subtype); + } + R_THROW(svc::ResultInvalidEnumValue()); + } + + R_SUCCEED(); + } + + constexpr bool IsValidMemoryPool(u64 pool) { + switch (static_cast<KMemoryManager::Pool>(pool)) { + case KMemoryManager::Pool_Application: + case KMemoryManager::Pool_Applet: + case KMemoryManager::Pool_System: + case KMemoryManager::Pool_SystemNonSecure: + return true; + default: + return false; + } + } + + Result GetSystemInfo(u64 *out, ams::svc::SystemInfoType info_type, ams::svc::Handle handle, u64 info_subtype) { + switch (info_type) { + case ams::svc::SystemInfoType_TotalPhysicalMemorySize: + case ams::svc::SystemInfoType_UsedPhysicalMemorySize: + { + /* Verify the input handle is invalid. */ + R_UNLESS(handle == ams::svc::InvalidHandle, svc::ResultInvalidHandle()); + + /* Verify the sub-type is valid. */ + R_UNLESS(IsValidMemoryPool(info_subtype), svc::ResultInvalidCombination()); + + /* Convert to pool. */ + const auto pool = static_cast<KMemoryManager::Pool>(info_subtype); + + /* Get the memory size. */ + auto &mm = Kernel::GetMemoryManager(); + switch (info_type) { + case ams::svc::SystemInfoType_TotalPhysicalMemorySize: + *out = mm.GetSize(pool); + break; + case ams::svc::SystemInfoType_UsedPhysicalMemorySize: + *out = mm.GetSize(pool) - mm.GetFreeSize(pool); + break; + MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); + } + } + break; + case ams::svc::SystemInfoType_InitialProcessIdRange: + { + /* Verify the handle is invalid. */ + R_UNLESS(handle == ams::svc::InvalidHandle, svc::ResultInvalidHandle()); + + /* Get the process id range. */ + R_TRY(GetInitialProcessIdRange(out, static_cast<ams::svc::InitialProcessIdRangeInfo>(info_subtype))); + } + break; + default: + R_THROW(svc::ResultInvalidEnumValue()); + } + + R_SUCCEED(); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result GetInfo64(uint64_t *out, ams::svc::InfoType info_type, ams::svc::Handle handle, uint64_t info_subtype) { + R_RETURN(GetInfo(out, info_type, handle, info_subtype)); + } + + Result GetSystemInfo64(uint64_t *out, ams::svc::SystemInfoType info_type, ams::svc::Handle handle, uint64_t info_subtype) { + R_RETURN(GetSystemInfo(out, info_type, handle, info_subtype)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result GetInfo64From32(uint64_t *out, ams::svc::InfoType info_type, ams::svc::Handle handle, uint64_t info_subtype) { + R_RETURN(GetInfo(out, info_type, handle, info_subtype)); + } + + Result GetSystemInfo64From32(uint64_t *out, ams::svc::SystemInfoType info_type, ams::svc::Handle handle, uint64_t info_subtype) { + R_RETURN(GetSystemInfo(out, info_type, handle, info_subtype)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_insecure_memory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_insecure_memory.cpp new file mode 100644 index 00000000..c0262b5c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_insecure_memory.cpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + Result MapInsecurePhysicalMemory(uintptr_t address, size_t size) { + /* Validate the address/size. */ + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + + /* Verify that the mapping is in range. */ + auto &pt = GetCurrentProcess().GetPageTable(); + R_UNLESS(GetCurrentProcess().GetPageTable().CanContain(address, size, KMemoryState_Insecure), svc::ResultInvalidMemoryRegion()); + + /* Map the insecure memory. */ + R_RETURN(pt.MapInsecurePhysicalMemory(address, size)); + } + + Result UnmapInsecurePhysicalMemory(uintptr_t address, size_t size) { + /* Validate the address/size. */ + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + + /* Verify that the mapping is in range. */ + auto &pt = GetCurrentProcess().GetPageTable(); + R_UNLESS(GetCurrentProcess().GetPageTable().CanContain(address, size, KMemoryState_Insecure), svc::ResultInvalidMemoryRegion()); + + /* Map the insecure memory. */ + R_RETURN(pt.UnmapInsecurePhysicalMemory(address, size)); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result MapInsecurePhysicalMemory64(ams::svc::Address address, ams::svc::Size size) { + R_RETURN(MapInsecurePhysicalMemory(address, size)); + } + + Result UnmapInsecurePhysicalMemory64(ams::svc::Address address, ams::svc::Size size) { + R_RETURN(UnmapInsecurePhysicalMemory(address, size)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result MapInsecurePhysicalMemory64From32(ams::svc::Address address, ams::svc::Size size) { + R_RETURN(MapInsecurePhysicalMemory(address, size)); + } + + Result UnmapInsecurePhysicalMemory64From32(ams::svc::Address address, ams::svc::Size size) { + R_RETURN(UnmapInsecurePhysicalMemory(address, size)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_interrupt_event.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_interrupt_event.cpp new file mode 100644 index 00000000..0c867db0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_interrupt_event.cpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + constexpr bool IsValidInterruptType(ams::svc::InterruptType type) { + switch (type) { + case ams::svc::InterruptType_Edge: + case ams::svc::InterruptType_Level: + return true; + default: + return false; + } + } + + Result CreateInterruptEvent(ams::svc::Handle *out, int32_t interrupt_id, ams::svc::InterruptType type) { + /* Validate the type. */ + R_UNLESS(IsValidInterruptType(type), svc::ResultInvalidEnumValue()); + + /* Check whether the interrupt is allowed. */ + auto &process = GetCurrentProcess(); + R_UNLESS(process.IsPermittedInterrupt(interrupt_id), svc::ResultNotFound()); + + /* Get the current handle table. */ + auto &handle_table = process.GetHandleTable(); + + /* Create the interrupt event. */ + KInterruptEvent *event = KInterruptEvent::Create(); + R_UNLESS(event != nullptr, svc::ResultOutOfResource()); + ON_SCOPE_EXIT { event->Close(); }; + + /* Initialize the event. */ + R_TRY(event->Initialize(interrupt_id, type)); + + /* Register the event. */ + KInterruptEvent::Register(event); + + /* Add the event to the handle table. */ + R_TRY(handle_table.Add(out, event)); + + R_SUCCEED(); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result CreateInterruptEvent64(ams::svc::Handle *out_read_handle, int32_t interrupt_id, ams::svc::InterruptType interrupt_type) { + R_RETURN(CreateInterruptEvent(out_read_handle, interrupt_id, interrupt_type)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result CreateInterruptEvent64From32(ams::svc::Handle *out_read_handle, int32_t interrupt_id, ams::svc::InterruptType interrupt_type) { + R_RETURN(CreateInterruptEvent(out_read_handle, interrupt_id, interrupt_type)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_io_pool.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_io_pool.cpp new file mode 100644 index 00000000..a5d132e6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_io_pool.cpp @@ -0,0 +1,214 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + #if defined(AMS_SVC_IO_POOL_NOT_SUPPORTED) + constexpr bool IsIoPoolApiSupported = false; + #else + constexpr bool IsIoPoolApiSupported = true; + #endif + + [[maybe_unused]] constexpr bool IsValidIoRegionMapping(ams::svc::MemoryMapping mapping) { + switch (mapping) { + case ams::svc::MemoryMapping_IoRegister: + case ams::svc::MemoryMapping_Uncached: + case ams::svc::MemoryMapping_Memory: + return true; + default: + return false; + } + } + + [[maybe_unused]] constexpr bool IsValidIoRegionPermission(ams::svc::MemoryPermission perm) { + switch (perm) { + case ams::svc::MemoryPermission_Read: + case ams::svc::MemoryPermission_ReadWrite: + return true; + default: + return false; + } + } + + Result CreateIoPool(ams::svc::Handle *out, ams::svc::IoPoolType pool_type) { + if constexpr (IsIoPoolApiSupported) { + /* Validate that we're allowed to create a pool for the given type. */ + R_UNLESS(KIoPool::IsValidIoPoolType(pool_type), svc::ResultNotFound()); + + /* Create the io pool. */ + KIoPool *io_pool = KIoPool::Create(); + R_UNLESS(io_pool != nullptr, svc::ResultOutOfResource()); + + /* Ensure the only reference is in the handle table when we're done. */ + ON_SCOPE_EXIT { io_pool->Close(); }; + + /* Initialize the io pool. */ + R_TRY(io_pool->Initialize(pool_type)); + + /* Register the io pool. */ + KIoPool::Register(io_pool); + + /* Add the io pool to the handle table. */ + R_TRY(GetCurrentProcess().GetHandleTable().Add(out, io_pool)); + + R_SUCCEED(); + } else { + MESOSPHERE_UNUSED(out, pool_type); + R_THROW(svc::ResultNotImplemented()); + } + } + + Result CreateIoRegion(ams::svc::Handle *out, ams::svc::Handle io_pool_handle, uint64_t phys_addr, size_t size, ams::svc::MemoryMapping mapping, ams::svc::MemoryPermission perm) { + if constexpr (IsIoPoolApiSupported) { + /* Validate the address/size. */ + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(util::IsAligned(phys_addr, PageSize), svc::ResultInvalidAddress()); + R_UNLESS((phys_addr < phys_addr + size), svc::ResultInvalidMemoryRegion()); + + /* Validate the mapping/permissions. */ + R_UNLESS(IsValidIoRegionMapping(mapping), svc::ResultInvalidEnumValue()); + R_UNLESS(IsValidIoRegionPermission(perm), svc::ResultInvalidEnumValue()); + + /* Get the current handle table. */ + auto &handle_table = GetCurrentProcess().GetHandleTable(); + + /* Get the io pool. */ + KScopedAutoObject io_pool = handle_table.GetObject<KIoPool>(io_pool_handle); + R_UNLESS(io_pool.IsNotNull(), svc::ResultInvalidHandle()); + + /* Create the io region. */ + KIoRegion *io_region = KIoRegion::Create(); + R_UNLESS(io_region != nullptr, svc::ResultOutOfResource()); + + /* Ensure the only reference is in the handle table when we're done. */ + ON_SCOPE_EXIT { io_region->Close(); }; + + /* Initialize the io region. */ + R_TRY(io_region->Initialize(io_pool.GetPointerUnsafe(), phys_addr, size, mapping, perm)); + + /* Register the io region. */ + KIoRegion::Register(io_region); + + /* Add the io region to the handle table. */ + R_TRY(handle_table.Add(out, io_region)); + + R_SUCCEED(); + } else { + MESOSPHERE_UNUSED(out, io_pool_handle, phys_addr, size, mapping, perm); + R_THROW(svc::ResultNotImplemented()); + } + } + + Result MapIoRegion(ams::svc::Handle io_region_handle, uintptr_t address, size_t size, ams::svc::MemoryPermission map_perm) { + if constexpr (IsIoPoolApiSupported) { + /* Validate the address/size. */ + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + + /* Verify that the mapping is in range. */ + R_UNLESS(GetCurrentProcess().GetPageTable().CanContain(address, size, ams::svc::MemoryState_Io), svc::ResultInvalidMemoryRegion()); + + /* Validate the map permission. */ + R_UNLESS(IsValidIoRegionPermission(map_perm), svc::ResultInvalidNewMemoryPermission()); + + /* Get the io region. */ + KScopedAutoObject io_region = GetCurrentProcess().GetHandleTable().GetObject<KIoRegion>(io_region_handle); + R_UNLESS(io_region.IsNotNull(), svc::ResultInvalidHandle()); + + /* Map the io region. */ + R_TRY(io_region->Map(address, size, map_perm)); + + /* We succeeded. */ + R_SUCCEED(); + } else { + MESOSPHERE_UNUSED(io_region_handle, address, size, map_perm); + R_THROW(svc::ResultNotImplemented()); + } + } + + Result UnmapIoRegion(ams::svc::Handle io_region_handle, uintptr_t address, size_t size) { + if constexpr (IsIoPoolApiSupported) { + /* Validate the address/size. */ + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + + /* Verify that the mapping is in range. */ + R_UNLESS(GetCurrentProcess().GetPageTable().CanContain(address, size, ams::svc::MemoryState_Io), svc::ResultInvalidMemoryRegion()); + + /* Get the io region. */ + KScopedAutoObject io_region = GetCurrentProcess().GetHandleTable().GetObject<KIoRegion>(io_region_handle); + R_UNLESS(io_region.IsNotNull(), svc::ResultInvalidHandle()); + + /* Unmap the io region. */ + R_TRY(io_region->Unmap(address, size)); + + /* We succeeded. */ + R_SUCCEED(); + } else { + MESOSPHERE_UNUSED(io_region_handle, address, size); + R_THROW(svc::ResultNotImplemented()); + } + } + + } + + /* ============================= 64 ABI ============================= */ + + Result CreateIoPool64(ams::svc::Handle *out_handle, ams::svc::IoPoolType pool_type) { + R_RETURN(CreateIoPool(out_handle, pool_type)); + } + + Result CreateIoRegion64(ams::svc::Handle *out_handle, ams::svc::Handle io_pool, ams::svc::PhysicalAddress physical_address, ams::svc::Size size, ams::svc::MemoryMapping mapping, ams::svc::MemoryPermission perm) { + R_RETURN(CreateIoRegion(out_handle, io_pool, physical_address, size, mapping, perm)); + } + + Result MapIoRegion64(ams::svc::Handle io_region, ams::svc::Address address, ams::svc::Size size, ams::svc::MemoryPermission perm) { + R_RETURN(MapIoRegion(io_region, address, size, perm)); + } + + Result UnmapIoRegion64(ams::svc::Handle io_region, ams::svc::Address address, ams::svc::Size size) { + R_RETURN(UnmapIoRegion(io_region, address, size)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result CreateIoPool64From32(ams::svc::Handle *out_handle, ams::svc::IoPoolType pool_type) { + R_RETURN(CreateIoPool(out_handle, pool_type)); + } + + Result CreateIoRegion64From32(ams::svc::Handle *out_handle, ams::svc::Handle io_pool, ams::svc::PhysicalAddress physical_address, ams::svc::Size size, ams::svc::MemoryMapping mapping, ams::svc::MemoryPermission perm) { + R_RETURN(CreateIoRegion(out_handle, io_pool, physical_address, size, mapping, perm)); + } + + Result MapIoRegion64From32(ams::svc::Handle io_region, ams::svc::Address address, ams::svc::Size size, ams::svc::MemoryPermission perm) { + R_RETURN(MapIoRegion(io_region, address, size, perm)); + } + + Result UnmapIoRegion64From32(ams::svc::Handle io_region, ams::svc::Address address, ams::svc::Size size) { + R_RETURN(UnmapIoRegion(io_region, address, size)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_ipc.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_ipc.cpp new file mode 100644 index 00000000..a67b581c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_ipc.cpp @@ -0,0 +1,310 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +#pragma GCC push_options +#pragma GCC optimize ("-O3") + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + ALWAYS_INLINE Result SendSyncRequestImpl(uintptr_t message, size_t buffer_size, ams::svc::Handle session_handle) { + /* Get the client session. */ + KScopedAutoObject session = GetCurrentProcess().GetHandleTable().GetObject<KClientSession>(session_handle); + R_UNLESS(session.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the parent, and persist a reference to it until we're done. */ + KScopedAutoObject parent = session->GetParent(); + MESOSPHERE_ASSERT(parent.IsNotNull()); + + /* Send the request. */ + R_RETURN(session->SendSyncRequest(message, buffer_size)); + } + + ALWAYS_INLINE Result ReplyAndReceiveImpl(int32_t *out_index, uintptr_t message, size_t buffer_size, KPhysicalAddress message_paddr, KSynchronizationObject **objs, int32_t num_objects, ams::svc::Handle reply_target, int64_t timeout_ns) { + /* Reply to the target, if one is specified. */ + if (reply_target != ams::svc::InvalidHandle) { + KScopedAutoObject session = GetCurrentProcess().GetHandleTable().GetObject<KServerSession>(reply_target); + R_UNLESS(session.IsNotNull(), svc::ResultInvalidHandle()); + + /* If we fail to reply, we want to set the output index to -1. */ + ON_RESULT_FAILURE { *out_index = -1; }; + + /* Send the reply. */ + R_TRY(session->SendReply(message, buffer_size, message_paddr)); + } + + /* Receive a message. */ + { + /* Convert the timeout from nanoseconds to ticks. */ + /* NOTE: Nintendo does not use this conversion logic in WaitSynchronization... */ + s64 timeout; + if (timeout_ns > 0) { + const ams::svc::Tick offset_tick(TimeSpan::FromNanoSeconds(timeout_ns)); + if (AMS_LIKELY(offset_tick > 0)) { + timeout = KHardwareTimer::GetTick() + offset_tick + 2; + if (AMS_UNLIKELY(timeout <= 0)) { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = timeout_ns; + } + + /* Wait for a message. */ + while (true) { + /* Close any pending objects before we wait. */ + GetCurrentThread().DestroyClosedObjects(); + + /* Wait for an object. */ + s32 index; + Result result = KSynchronizationObject::Wait(std::addressof(index), objs, num_objects, timeout); + if (svc::ResultTimedOut::Includes(result)) { + R_THROW(result); + } + + /* Receive the request. */ + if (R_SUCCEEDED(result)) { + KServerSession *session = objs[index]->DynamicCast<KServerSession *>(); + if (session != nullptr) { + result = session->ReceiveRequest(message, buffer_size, message_paddr); + if (svc::ResultNotFound::Includes(result)) { + continue; + } + } + } + + *out_index = index; + R_RETURN(result); + } + } + } + + ALWAYS_INLINE Result ReplyAndReceiveImpl(int32_t *out_index, uintptr_t message, size_t buffer_size, KPhysicalAddress message_paddr, KUserPointer<const ams::svc::Handle *> user_handles, int32_t num_handles, ams::svc::Handle reply_target, int64_t timeout_ns) { + /* Ensure number of handles is valid. */ + R_UNLESS(0 <= num_handles && num_handles <= ams::svc::ArgumentHandleCountMax, svc::ResultOutOfRange()); + + /* Get the synchronization context. */ + auto &handle_table = GetCurrentProcess().GetHandleTable(); + KSynchronizationObject **objs = GetCurrentThread().GetSynchronizationObjectBuffer(); + ams::svc::Handle *handles = GetCurrentThread().GetHandleBuffer(); + + /* Copy user handles. */ + if (num_handles > 0) { + /* Ensure that we can try to get the handles. */ + R_UNLESS(GetCurrentProcess().GetPageTable().Contains(KProcessAddress(user_handles.GetUnsafePointer()), num_handles * sizeof(ams::svc::Handle)), svc::ResultInvalidPointer()); + + /* Get the handles. */ + R_TRY(user_handles.CopyArrayTo(handles, num_handles)); + + /* Convert the handles to objects. */ + R_UNLESS(handle_table.GetMultipleObjects<KSynchronizationObject>(objs, handles, num_handles), svc::ResultInvalidHandle()); + } + + /* Ensure handles are closed when we're done. */ + ON_SCOPE_EXIT { + for (auto i = 0; i < num_handles; ++i) { + objs[i]->Close(); + } + }; + + R_RETURN(ReplyAndReceiveImpl(out_index, message, buffer_size, message_paddr, objs, num_handles, reply_target, timeout_ns)); + } + + ALWAYS_INLINE Result SendSyncRequest(ams::svc::Handle session_handle) { + R_RETURN(SendSyncRequestImpl(0, 0, session_handle)); + } + + ALWAYS_INLINE Result SendSyncRequestWithUserBuffer(uintptr_t message, size_t buffer_size, ams::svc::Handle session_handle) { + /* Validate that the message buffer is page aligned and does not overflow. */ + R_UNLESS(util::IsAligned(message, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(buffer_size > 0, svc::ResultInvalidSize()); + R_UNLESS(util::IsAligned(buffer_size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(message < message + buffer_size, svc::ResultInvalidCurrentMemory()); + + /* Get the process page table. */ + auto &page_table = GetCurrentProcess().GetPageTable(); + + /* Lock the message buffer. */ + R_TRY(page_table.LockForIpcUserBuffer(nullptr, message, buffer_size)); + + { + /* If we fail to send the message, unlock the message buffer. */ + ON_RESULT_FAILURE { page_table.UnlockForIpcUserBuffer(message, buffer_size); }; + + /* Send the request. */ + MESOSPHERE_ASSERT(message != 0); + R_TRY(SendSyncRequestImpl(message, buffer_size, session_handle)); + } + + /* We successfully processed, so try to unlock the message buffer. */ + R_RETURN(page_table.UnlockForIpcUserBuffer(message, buffer_size)); + } + + ALWAYS_INLINE Result SendAsyncRequestWithUserBufferImpl(ams::svc::Handle *out_event_handle, uintptr_t message, size_t buffer_size, ams::svc::Handle session_handle) { + /* Get the process and handle table. */ + auto &process = GetCurrentProcess(); + auto &handle_table = process.GetHandleTable(); + + /* Reserve a new event from the process resource limit. */ + KScopedResourceReservation event_reservation(std::addressof(process), ams::svc::LimitableResource_EventCountMax); + R_UNLESS(event_reservation.Succeeded(), svc::ResultLimitReached()); + + /* Get the client session. */ + KScopedAutoObject session = GetCurrentProcess().GetHandleTable().GetObject<KClientSession>(session_handle); + R_UNLESS(session.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the parent, and persist a reference to it until we're done. */ + KScopedAutoObject parent = session->GetParent(); + MESOSPHERE_ASSERT(parent.IsNotNull()); + + /* Create a new event. */ + KEvent *event = KEvent::Create(); + R_UNLESS(event != nullptr, svc::ResultOutOfResource()); + + /* Initialize the event. */ + event->Initialize(); + + /* Commit our reservation. */ + event_reservation.Commit(); + + /* At end of scope, kill the standing event references. */ + ON_SCOPE_EXIT { + event->GetReadableEvent().Close(); + event->Close(); + }; + + /* Register the event. */ + KEvent::Register(event); + + /* Add the readable event to the handle table. */ + R_TRY(handle_table.Add(out_event_handle, std::addressof(event->GetReadableEvent()))); + + /* Ensure that if we fail to send the request, we close the readable handle. */ + ON_RESULT_FAILURE { handle_table.Remove(*out_event_handle); }; + + /* Send the async request. */ + R_RETURN(session->SendAsyncRequest(event, message, buffer_size)); + } + + ALWAYS_INLINE Result SendAsyncRequestWithUserBuffer(ams::svc::Handle *out_event_handle, uintptr_t message, size_t buffer_size, ams::svc::Handle session_handle) { + /* Validate that the message buffer is page aligned and does not overflow. */ + R_UNLESS(util::IsAligned(message, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(buffer_size > 0, svc::ResultInvalidSize()); + R_UNLESS(util::IsAligned(buffer_size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(message < message + buffer_size, svc::ResultInvalidCurrentMemory()); + + /* Get the process page table. */ + auto &page_table = GetCurrentProcess().GetPageTable(); + + /* Lock the message buffer. */ + R_TRY(page_table.LockForIpcUserBuffer(nullptr, message, buffer_size)); + + /* Ensure that if we fail and aren't terminating that we unlock the user buffer. */ + ON_RESULT_FAILURE_BESIDES(svc::ResultTerminationRequested) { + page_table.UnlockForIpcUserBuffer(message, buffer_size); + }; + + /* Send the request. */ + MESOSPHERE_ASSERT(message != 0); + R_RETURN(SendAsyncRequestWithUserBufferImpl(out_event_handle, message, buffer_size, session_handle)); + } + + ALWAYS_INLINE Result ReplyAndReceive(int32_t *out_index, KUserPointer<const ams::svc::Handle *> handles, int32_t num_handles, ams::svc::Handle reply_target, int64_t timeout_ns) { + R_RETURN(ReplyAndReceiveImpl(out_index, 0, 0, Null<KPhysicalAddress>, handles, num_handles, reply_target, timeout_ns)); + } + + ALWAYS_INLINE Result ReplyAndReceiveWithUserBuffer(int32_t *out_index, uintptr_t message, size_t buffer_size, KUserPointer<const ams::svc::Handle *> handles, int32_t num_handles, ams::svc::Handle reply_target, int64_t timeout_ns) { + /* Validate that the message buffer is page aligned and does not overflow. */ + R_UNLESS(util::IsAligned(message, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(buffer_size > 0, svc::ResultInvalidSize()); + R_UNLESS(util::IsAligned(buffer_size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(message < message + buffer_size, svc::ResultInvalidCurrentMemory()); + + /* Get the process page table. */ + auto &page_table = GetCurrentProcess().GetPageTable(); + + /* Lock the message buffer, getting its physical address. */ + KPhysicalAddress message_paddr; + R_TRY(page_table.LockForIpcUserBuffer(std::addressof(message_paddr), message, buffer_size)); + + { + /* If we fail to send the message, unlock the message buffer. */ + ON_RESULT_FAILURE { page_table.UnlockForIpcUserBuffer(message, buffer_size); }; + + /* Reply/Receive the request. */ + MESOSPHERE_ASSERT(message != 0); + R_TRY(ReplyAndReceiveImpl(out_index, message, buffer_size, message_paddr, handles, num_handles, reply_target, timeout_ns)); + } + + /* We successfully processed, so try to unlock the message buffer. */ + R_RETURN(page_table.UnlockForIpcUserBuffer(message, buffer_size)); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result SendSyncRequest64(ams::svc::Handle session_handle) { + R_RETURN(SendSyncRequest(session_handle)); + } + + Result SendSyncRequestWithUserBuffer64(ams::svc::Address message_buffer, ams::svc::Size message_buffer_size, ams::svc::Handle session_handle) { + R_RETURN(SendSyncRequestWithUserBuffer(message_buffer, message_buffer_size, session_handle)); + } + + Result SendAsyncRequestWithUserBuffer64(ams::svc::Handle *out_event_handle, ams::svc::Address message_buffer, ams::svc::Size message_buffer_size, ams::svc::Handle session_handle) { + R_RETURN(SendAsyncRequestWithUserBuffer(out_event_handle, message_buffer, message_buffer_size, session_handle)); + } + + Result ReplyAndReceive64(int32_t *out_index, KUserPointer<const ams::svc::Handle *> handles, int32_t num_handles, ams::svc::Handle reply_target, int64_t timeout_ns) { + R_RETURN(ReplyAndReceive(out_index, handles, num_handles, reply_target, timeout_ns)); + } + + Result ReplyAndReceiveWithUserBuffer64(int32_t *out_index, ams::svc::Address message_buffer, ams::svc::Size message_buffer_size, KUserPointer<const ams::svc::Handle *> handles, int32_t num_handles, ams::svc::Handle reply_target, int64_t timeout_ns) { + R_RETURN(ReplyAndReceiveWithUserBuffer(out_index, message_buffer, message_buffer_size, handles, num_handles, reply_target, timeout_ns)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result SendSyncRequest64From32(ams::svc::Handle session_handle) { + R_RETURN(SendSyncRequest(session_handle)); + } + + Result SendSyncRequestWithUserBuffer64From32(ams::svc::Address message_buffer, ams::svc::Size message_buffer_size, ams::svc::Handle session_handle) { + R_RETURN(SendSyncRequestWithUserBuffer(message_buffer, message_buffer_size, session_handle)); + } + + Result SendAsyncRequestWithUserBuffer64From32(ams::svc::Handle *out_event_handle, ams::svc::Address message_buffer, ams::svc::Size message_buffer_size, ams::svc::Handle session_handle) { + R_RETURN(SendAsyncRequestWithUserBuffer(out_event_handle, message_buffer, message_buffer_size, session_handle)); + } + + Result ReplyAndReceive64From32(int32_t *out_index, KUserPointer<const ams::svc::Handle *> handles, int32_t num_handles, ams::svc::Handle reply_target, int64_t timeout_ns) { + R_RETURN(ReplyAndReceive(out_index, handles, num_handles, reply_target, timeout_ns)); + } + + Result ReplyAndReceiveWithUserBuffer64From32(int32_t *out_index, ams::svc::Address message_buffer, ams::svc::Size message_buffer_size, KUserPointer<const ams::svc::Handle *> handles, int32_t num_handles, ams::svc::Handle reply_target, int64_t timeout_ns) { + R_RETURN(ReplyAndReceiveWithUserBuffer(out_index, message_buffer, message_buffer_size, handles, num_handles, reply_target, timeout_ns)); + } + +} + +#pragma GCC pop_options diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_kernel_debug.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_kernel_debug.cpp new file mode 100644 index 00000000..c53537a4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_kernel_debug.cpp @@ -0,0 +1,174 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + void KernelDebug(ams::svc::KernelDebugType kern_debug_type, uint64_t arg0, uint64_t arg1, uint64_t arg2) { + MESOSPHERE_UNUSED(kern_debug_type, arg0, arg1, arg2); + + #ifdef MESOSPHERE_BUILD_FOR_DEBUGGING + { + switch (kern_debug_type) { + case ams::svc::KernelDebugType_Thread: + if (arg0 == static_cast<u64>(-1)) { + KDumpObject::DumpThread(); + } else { + KDumpObject::DumpThread(arg0); + } + break; + case ams::svc::KernelDebugType_ThreadCallStack: + if (arg0 == static_cast<u64>(-1)) { + KDumpObject::DumpThreadCallStack(); + } else { + KDumpObject::DumpThreadCallStack(arg0); + } + break; + case ams::svc::KernelDebugType_KernelObject: + KDumpObject::DumpKernelObject(); + break; + case ams::svc::KernelDebugType_Handle: + if (arg0 == static_cast<u64>(-1)) { + KDumpObject::DumpHandle(); + } else { + KDumpObject::DumpHandle(arg0); + } + break; + case ams::svc::KernelDebugType_Memory: + if (arg0 == static_cast<u64>(-2)) { + KDumpObject::DumpKernelMemory(); + } else if (arg0 == static_cast<u64>(-1)) { + KDumpObject::DumpMemory(); + } else { + KDumpObject::DumpMemory(arg0); + } + break; + case ams::svc::KernelDebugType_PageTable: + if (arg0 == static_cast<u64>(-2)) { + KDumpObject::DumpKernelPageTable(); + } else if (arg0 == static_cast<u64>(-1)) { + KDumpObject::DumpPageTable(); + } else { + KDumpObject::DumpPageTable(arg0); + } + break; + case ams::svc::KernelDebugType_CpuUtilization: + { + const auto old_prio = GetCurrentThread().GetBasePriority(); + GetCurrentThread().SetBasePriority(3); + + if (arg0 == static_cast<u64>(-2)) { + KDumpObject::DumpKernelCpuUtilization(); + } else if (arg0 == static_cast<u64>(-1)) { + KDumpObject::DumpCpuUtilization(); + } else { + KDumpObject::DumpCpuUtilization(arg0); + } + + GetCurrentThread().SetBasePriority(old_prio); + } + break; + case ams::svc::KernelDebugType_Process: + if (arg0 == static_cast<u64>(-1)) { + KDumpObject::DumpProcess(); + } else { + KDumpObject::DumpProcess(arg0); + } + break; + case ams::svc::KernelDebugType_SuspendProcess: + if (KProcess *process = KProcess::GetProcessFromId(arg0); process != nullptr) { + ON_SCOPE_EXIT { process->Close(); }; + + if (R_SUCCEEDED(process->SetActivity(ams::svc::ProcessActivity_Paused))) { + MESOSPHERE_RELEASE_LOG("Suspend Process ID=%3lu\n", process->GetId()); + } + } + break; + case ams::svc::KernelDebugType_ResumeProcess: + if (KProcess *process = KProcess::GetProcessFromId(arg0); process != nullptr) { + ON_SCOPE_EXIT { process->Close(); }; + + if (R_SUCCEEDED(process->SetActivity(ams::svc::ProcessActivity_Runnable))) { + MESOSPHERE_RELEASE_LOG("Resume Process ID=%3lu\n", process->GetId()); + } + } + break; + case ams::svc::KernelDebugType_Port: + if (arg0 == static_cast<u64>(-1)) { + KDumpObject::DumpPort(); + } else { + KDumpObject::DumpPort(arg0); + } + break; + default: + break; + } + } + #endif + } + + void ChangeKernelTraceState(ams::svc::KernelTraceState kern_trace_state) { + #ifdef MESOSPHERE_BUILD_FOR_DEBUGGING + { + switch (kern_trace_state) { + case ams::svc::KernelTraceState_Enabled: + { + MESOSPHERE_KTRACE_RESUME(); + } + break; + case ams::svc::KernelTraceState_Disabled: + { + MESOSPHERE_KTRACE_PAUSE(); + } + break; + default: + break; + } + } + #else + { + MESOSPHERE_UNUSED(kern_trace_state); + } + #endif + } + + } + + /* ============================= 64 ABI ============================= */ + + void KernelDebug64(ams::svc::KernelDebugType kern_debug_type, uint64_t arg0, uint64_t arg1, uint64_t arg2) { + return KernelDebug(kern_debug_type, arg0, arg1, arg2); + } + + void ChangeKernelTraceState64(ams::svc::KernelTraceState kern_trace_state) { + return ChangeKernelTraceState(kern_trace_state); + } + + /* ============================= 64From32 ABI ============================= */ + + void KernelDebug64From32(ams::svc::KernelDebugType kern_debug_type, uint64_t arg0, uint64_t arg1, uint64_t arg2) { + return KernelDebug(kern_debug_type, arg0, arg1, arg2); + } + + void ChangeKernelTraceState64From32(ams::svc::KernelTraceState kern_trace_state) { + return ChangeKernelTraceState(kern_trace_state); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_light_ipc.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_light_ipc.cpp new file mode 100644 index 00000000..71ba011a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_light_ipc.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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + ALWAYS_INLINE Result SendSyncRequestLight(ams::svc::Handle session_handle, u32 *args) { + /* Get the light client session from its handle. */ + KScopedAutoObject session = GetCurrentProcess().GetHandleTable().GetObject<KLightClientSession>(session_handle); + R_UNLESS(session.IsNotNull(), svc::ResultInvalidHandle()); + + /* Send the request. */ + R_TRY(session->SendSyncRequest(args)); + + R_SUCCEED(); + } + + ALWAYS_INLINE Result ReplyAndReceiveLight(ams::svc::Handle session_handle, u32 *args) { + /* Get the light server session from its handle. */ + KScopedAutoObject session = GetCurrentProcess().GetHandleTable().GetObject<KLightServerSession>(session_handle); + R_UNLESS(session.IsNotNull(), svc::ResultInvalidHandle()); + + /* Handle the request. */ + R_TRY(session->ReplyAndReceive(args)); + + R_SUCCEED(); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result SendSyncRequestLight64(ams::svc::Handle session_handle, u32 *args) { + R_RETURN(SendSyncRequestLight(session_handle, args)); + } + + Result ReplyAndReceiveLight64(ams::svc::Handle session_handle, u32 *args) { + R_RETURN(ReplyAndReceiveLight(session_handle, args)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result SendSyncRequestLight64From32(ams::svc::Handle session_handle, u32 *args) { + R_RETURN(SendSyncRequestLight(session_handle, args)); + } + + Result ReplyAndReceiveLight64From32(ams::svc::Handle session_handle, u32 *args) { + R_RETURN(ReplyAndReceiveLight(session_handle, args)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_lock.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_lock.cpp new file mode 100644 index 00000000..be19bc0e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_lock.cpp @@ -0,0 +1,66 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + constexpr bool IsKernelAddress(uintptr_t address) { + return KernelVirtualAddressSpaceBase <= address && address < KernelVirtualAddressSpaceEnd; + } + + Result ArbitrateLock(ams::svc::Handle thread_handle, uintptr_t address, uint32_t tag) { + /* Validate the input address. */ + R_UNLESS(!IsKernelAddress(address), svc::ResultInvalidCurrentMemory()); + R_UNLESS(util::IsAligned(address, sizeof(u32)), svc::ResultInvalidAddress()); + + R_RETURN(KConditionVariable::WaitForAddress(thread_handle, address, tag)); + } + + Result ArbitrateUnlock(uintptr_t address) { + /* Validate the input address. */ + R_UNLESS(!IsKernelAddress(address), svc::ResultInvalidCurrentMemory()); + R_UNLESS(util::IsAligned(address, sizeof(u32)), svc::ResultInvalidAddress()); + + R_RETURN(KConditionVariable::SignalToAddress(address)); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result ArbitrateLock64(ams::svc::Handle thread_handle, ams::svc::Address address, uint32_t tag) { + R_RETURN(ArbitrateLock(thread_handle, address, tag)); + } + + Result ArbitrateUnlock64(ams::svc::Address address) { + R_RETURN(ArbitrateUnlock(address)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result ArbitrateLock64From32(ams::svc::Handle thread_handle, ams::svc::Address address, uint32_t tag) { + R_RETURN(ArbitrateLock(thread_handle, address, tag)); + } + + Result ArbitrateUnlock64From32(ams::svc::Address address) { + R_RETURN(ArbitrateUnlock(address)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_memory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_memory.cpp new file mode 100644 index 00000000..95173a0d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_memory.cpp @@ -0,0 +1,162 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + constexpr bool IsValidSetMemoryPermission(ams::svc::MemoryPermission perm) { + switch (perm) { + case ams::svc::MemoryPermission_None: + case ams::svc::MemoryPermission_Read: + case ams::svc::MemoryPermission_ReadWrite: + return true; + default: + return false; + } + } + + Result SetMemoryPermission(uintptr_t address, size_t size, ams::svc::MemoryPermission perm) { + /* Validate address / size. */ + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + + /* Validate the permission. */ + R_UNLESS(IsValidSetMemoryPermission(perm), svc::ResultInvalidNewMemoryPermission()); + + /* Validate that the region is in range for the current process. */ + auto &page_table = GetCurrentProcess().GetPageTable(); + R_UNLESS(page_table.Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Set the memory attribute. */ + R_RETURN(page_table.SetMemoryPermission(address, size, perm)); + } + + Result SetMemoryAttribute(uintptr_t address, size_t size, uint32_t mask, uint32_t attr) { + /* Validate address / size. */ + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + + /* Validate the attribute and mask. */ + constexpr u32 SupportedMask = ams::svc::MemoryAttribute_Uncached | ams::svc::MemoryAttribute_PermissionLocked; + R_UNLESS((mask | attr) == mask, svc::ResultInvalidCombination()); + R_UNLESS((mask | attr | SupportedMask) == SupportedMask, svc::ResultInvalidCombination()); + + /* Check that permission locked is either being set or not masked. */ + R_UNLESS((mask & ams::svc::MemoryAttribute_PermissionLocked) == (attr & ams::svc::MemoryAttribute_PermissionLocked), svc::ResultInvalidCombination()); + + /* Validate that the region is in range for the current process. */ + auto &page_table = GetCurrentProcess().GetPageTable(); + R_UNLESS(page_table.Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Set the memory attribute. */ + R_RETURN(page_table.SetMemoryAttribute(address, size, mask, attr)); + } + + Result MapMemory(uintptr_t dst_address, uintptr_t src_address, size_t size) { + /* Validate that addresses are page aligned. */ + R_UNLESS(util::IsAligned(dst_address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(src_address, PageSize), svc::ResultInvalidAddress()); + + /* Validate that size is positive and page aligned. */ + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + + /* Ensure that neither mapping overflows. */ + R_UNLESS(src_address < src_address + size, svc::ResultInvalidCurrentMemory()); + R_UNLESS(dst_address < dst_address + size, svc::ResultInvalidCurrentMemory()); + + /* Get the page table we're operating on. */ + auto &page_table = GetCurrentProcess().GetPageTable(); + + /* Ensure that the memory we're mapping is in range. */ + R_UNLESS(page_table.Contains(src_address, size), svc::ResultInvalidCurrentMemory()); + R_UNLESS(page_table.CanContain(dst_address, size, KMemoryState_Stack), svc::ResultInvalidMemoryRegion()); + + /* Map the memory. */ + R_RETURN(page_table.MapMemory(dst_address, src_address, size)); + } + + Result UnmapMemory(uintptr_t dst_address, uintptr_t src_address, size_t size) { + /* Validate that addresses are page aligned. */ + R_UNLESS(util::IsAligned(dst_address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(src_address, PageSize), svc::ResultInvalidAddress()); + + /* Validate that size is positive and page aligned. */ + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + + /* Ensure that neither mapping overflows. */ + R_UNLESS(src_address < src_address + size, svc::ResultInvalidCurrentMemory()); + R_UNLESS(dst_address < dst_address + size, svc::ResultInvalidCurrentMemory()); + + /* Get the page table we're operating on. */ + auto &page_table = GetCurrentProcess().GetPageTable(); + + /* Ensure that the memory we're unmapping is in range. */ + R_UNLESS(page_table.Contains(src_address, size), svc::ResultInvalidCurrentMemory()); + R_UNLESS(page_table.CanContain(dst_address, size, KMemoryState_Stack), svc::ResultInvalidMemoryRegion()); + + /* Unmap the memory. */ + R_RETURN(page_table.UnmapMemory(dst_address, src_address, size)); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result SetMemoryPermission64(ams::svc::Address address, ams::svc::Size size, ams::svc::MemoryPermission perm) { + R_RETURN(SetMemoryPermission(address, size, perm)); + } + + Result SetMemoryAttribute64(ams::svc::Address address, ams::svc::Size size, uint32_t mask, uint32_t attr) { + R_RETURN(SetMemoryAttribute(address, size, mask, attr)); + } + + Result MapMemory64(ams::svc::Address dst_address, ams::svc::Address src_address, ams::svc::Size size) { + R_RETURN(MapMemory(dst_address, src_address, size)); + } + + Result UnmapMemory64(ams::svc::Address dst_address, ams::svc::Address src_address, ams::svc::Size size) { + R_RETURN(UnmapMemory(dst_address, src_address, size)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result SetMemoryPermission64From32(ams::svc::Address address, ams::svc::Size size, ams::svc::MemoryPermission perm) { + R_RETURN(SetMemoryPermission(address, size, perm)); + } + + Result SetMemoryAttribute64From32(ams::svc::Address address, ams::svc::Size size, uint32_t mask, uint32_t attr) { + R_RETURN(SetMemoryAttribute(address, size, mask, attr)); + } + + Result MapMemory64From32(ams::svc::Address dst_address, ams::svc::Address src_address, ams::svc::Size size) { + R_RETURN(MapMemory(dst_address, src_address, size)); + } + + Result UnmapMemory64From32(ams::svc::Address dst_address, ams::svc::Address src_address, ams::svc::Size size) { + R_RETURN(UnmapMemory(dst_address, src_address, size)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_physical_memory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_physical_memory.cpp new file mode 100644 index 00000000..e2237877 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_physical_memory.cpp @@ -0,0 +1,188 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + Result SetHeapSize(uintptr_t *out_address, size_t size) { + /* Validate size. */ + R_UNLESS(util::IsAligned(size, ams::svc::HeapSizeAlignment), svc::ResultInvalidSize()); + R_UNLESS(size < ams::kern::MainMemorySizeMax, svc::ResultInvalidSize()); + + /* Set the heap size. */ + KProcessAddress address = Null<KProcessAddress>; + R_TRY(GetCurrentProcess().GetPageTable().SetHeapSize(std::addressof(address), size)); + + /* Set the output. */ + *out_address = GetInteger(address); + R_SUCCEED(); + } + + Result SetUnsafeLimit(size_t limit) { + /* Ensure the size is aligned. */ + R_UNLESS(util::IsAligned(limit, PageSize), svc::ResultInvalidSize()); + + /* Ensure that the size is not bigger than we can accommodate. */ + R_UNLESS(limit <= Kernel::GetMemoryManager().GetSize(KMemoryManager::Pool_Unsafe), svc::ResultOutOfRange()); + + /* Set the size. */ + R_RETURN(Kernel::GetUnsafeMemory().SetLimitSize(limit)); + } + + Result MapPhysicalMemory(uintptr_t address, size_t size) { + /* Validate address / size. */ + const size_t min_alignment = Kernel::GetMemoryManager().GetMinimumAlignment(GetCurrentProcess().GetMemoryPool()); + R_UNLESS(util::IsAligned(address, min_alignment), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, min_alignment), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidMemoryRegion()); + + /* Verify that the process has system resource. */ + auto &process = GetCurrentProcess(); + R_UNLESS(process.GetTotalSystemResourceSize() > 0, svc::ResultInvalidState()); + + /* Verify that the region is in range. */ + auto &page_table = process.GetPageTable(); + R_UNLESS(page_table.IsInAliasRegion(address, size), svc::ResultInvalidMemoryRegion()); + + /* Map the memory. */ + R_TRY(page_table.MapPhysicalMemory(address, size)); + + R_SUCCEED(); + } + + Result UnmapPhysicalMemory(uintptr_t address, size_t size) { + /* Validate address / size. */ + const size_t min_alignment = Kernel::GetMemoryManager().GetMinimumAlignment(GetCurrentProcess().GetMemoryPool()); + R_UNLESS(util::IsAligned(address, min_alignment), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, min_alignment), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidMemoryRegion()); + + /* Verify that the process has system resource. */ + auto &process = GetCurrentProcess(); + R_UNLESS(process.GetTotalSystemResourceSize() > 0, svc::ResultInvalidState()); + + /* Verify that the region is in range. */ + auto &page_table = process.GetPageTable(); + R_UNLESS(page_table.IsInAliasRegion(address, size), svc::ResultInvalidMemoryRegion()); + + /* Unmap the memory. */ + R_TRY(page_table.UnmapPhysicalMemory(address, size)); + + R_SUCCEED(); + } + + Result MapPhysicalMemoryUnsafe(uintptr_t address, size_t size) { + /* Validate address / size. */ + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + + /* Verify that the region is in range. */ + auto &process = GetCurrentProcess(); + auto &page_table = process.GetPageTable(); + R_UNLESS(page_table.IsInUnsafeAliasRegion(address, size), svc::ResultInvalidMemoryRegion()); + + /* Verify that the process isn't already using the unsafe pool. */ + R_UNLESS(process.GetMemoryPool() != KMemoryManager::Pool_Unsafe, svc::ResultInvalidMemoryPool()); + + /* Map the memory. */ + R_TRY(page_table.MapPhysicalMemoryUnsafe(address, size)); + + R_SUCCEED(); + } + + Result UnmapPhysicalMemoryUnsafe(uintptr_t address, size_t size) { + /* Validate address / size. */ + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + + /* Verify that the region is in range. */ + auto &process = GetCurrentProcess(); + auto &page_table = process.GetPageTable(); + R_UNLESS(page_table.IsInUnsafeAliasRegion(address, size), svc::ResultInvalidMemoryRegion()); + + /* Unmap the memory. */ + R_TRY(page_table.UnmapPhysicalMemoryUnsafe(address, size)); + + R_SUCCEED(); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result SetHeapSize64(ams::svc::Address *out_address, ams::svc::Size size) { + static_assert(sizeof(*out_address) == sizeof(uintptr_t)); + R_RETURN(SetHeapSize(reinterpret_cast<uintptr_t *>(out_address), size)); + } + + Result MapPhysicalMemory64(ams::svc::Address address, ams::svc::Size size) { + R_RETURN(MapPhysicalMemory(address, size)); + } + + Result UnmapPhysicalMemory64(ams::svc::Address address, ams::svc::Size size) { + R_RETURN(UnmapPhysicalMemory(address, size)); + } + + Result MapPhysicalMemoryUnsafe64(ams::svc::Address address, ams::svc::Size size) { + R_RETURN(MapPhysicalMemoryUnsafe(address, size)); + } + + Result UnmapPhysicalMemoryUnsafe64(ams::svc::Address address, ams::svc::Size size) { + R_RETURN(UnmapPhysicalMemoryUnsafe(address, size)); + } + + Result SetUnsafeLimit64(ams::svc::Size limit) { + R_RETURN(SetUnsafeLimit(limit)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result SetHeapSize64From32(ams::svc::Address *out_address, ams::svc::Size size) { + static_assert(sizeof(*out_address) == sizeof(uintptr_t)); + R_RETURN(SetHeapSize(reinterpret_cast<uintptr_t *>(out_address), size)); + } + + Result MapPhysicalMemory64From32(ams::svc::Address address, ams::svc::Size size) { + R_RETURN(MapPhysicalMemory(address, size)); + } + + Result UnmapPhysicalMemory64From32(ams::svc::Address address, ams::svc::Size size) { + R_RETURN(UnmapPhysicalMemory(address, size)); + } + + Result MapPhysicalMemoryUnsafe64From32(ams::svc::Address address, ams::svc::Size size) { + R_RETURN(MapPhysicalMemoryUnsafe(address, size)); + } + + Result UnmapPhysicalMemoryUnsafe64From32(ams::svc::Address address, ams::svc::Size size) { + R_RETURN(UnmapPhysicalMemoryUnsafe(address, size)); + } + + Result SetUnsafeLimit64From32(ams::svc::Size limit) { + R_RETURN(SetUnsafeLimit(limit)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_port.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_port.cpp new file mode 100644 index 00000000..1ac5803d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_port.cpp @@ -0,0 +1,205 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + Result ManageNamedPort(ams::svc::Handle *out_server_handle, KUserPointer<const char *> user_name, s32 max_sessions) { + /* Copy the provided name from user memory to kernel memory. */ + char name[KObjectName::NameLengthMax] = {}; + R_TRY(user_name.CopyStringTo(name, sizeof(name))); + + /* Validate that sessions and name are valid. */ + R_UNLESS(max_sessions >= 0, svc::ResultOutOfRange()); + R_UNLESS(name[sizeof(name) - 1] == '\x00', svc::ResultOutOfRange()); + + if (max_sessions > 0) { + /* Get the current handle table. */ + auto &handle_table = GetCurrentProcess().GetHandleTable(); + + /* Create a new port. */ + KPort *port = KPort::Create(); + R_UNLESS(port != nullptr, svc::ResultOutOfResource()); + + /* Initialize the new port. */ + port->Initialize(max_sessions, false, 0); + + /* Register the port. */ + KPort::Register(port); + + /* Ensure that our only reference to the port is in the handle table when we're done. */ + ON_SCOPE_EXIT { + port->GetClientPort().Close(); + port->GetServerPort().Close(); + }; + + /* Register the handle in the table. */ + R_TRY(handle_table.Add(out_server_handle, std::addressof(port->GetServerPort()))); + ON_RESULT_FAILURE { handle_table.Remove(*out_server_handle); }; + + /* Create a new object name. */ + R_TRY(KObjectName::NewFromName(std::addressof(port->GetClientPort()), name)); + } else /* if (max_sessions == 0) */ { + /* Ensure that this else case is correct. */ + MESOSPHERE_AUDIT(max_sessions == 0); + + /* If we're closing, there's no server handle. */ + *out_server_handle = ams::svc::InvalidHandle; + + /* Delete the object. */ + R_TRY(KObjectName::Delete<KClientPort>(name)); + } + + R_SUCCEED(); + } + + Result CreatePort(ams::svc::Handle *out_server, ams::svc::Handle *out_client, int32_t max_sessions, bool is_light, uintptr_t name) { + /* Ensure max sessions is valid. */ + R_UNLESS(max_sessions > 0, svc::ResultOutOfRange()); + + /* Get the current handle table. */ + auto &handle_table = GetCurrentProcess().GetHandleTable(); + + /* Create a new port. */ + KPort *port = KPort::Create(); + R_UNLESS(port != nullptr, svc::ResultOutOfResource()); + + /* Initialize the port. */ + port->Initialize(max_sessions, is_light, name); + + /* Ensure that we clean up the port (and its only references are handle table) on function end. */ + ON_SCOPE_EXIT { + port->GetServerPort().Close(); + port->GetClientPort().Close(); + }; + + /* Register the port. */ + KPort::Register(port); + + /* Add the client to the handle table. */ + R_TRY(handle_table.Add(out_client, std::addressof(port->GetClientPort()))); + + /* Ensure that we maintain a clean handle state on exit. */ + ON_RESULT_FAILURE { handle_table.Remove(*out_client); }; + + /* Add the server to the handle table. */ + R_RETURN(handle_table.Add(out_server, std::addressof(port->GetServerPort()))); + } + + Result ConnectToNamedPort(ams::svc::Handle *out, KUserPointer<const char *> user_name) { + /* Copy the provided name from user memory to kernel memory. */ + char name[KObjectName::NameLengthMax] = {}; + R_TRY(user_name.CopyStringTo(name, sizeof(name))); + + /* Validate that name is valid. */ + R_UNLESS(name[sizeof(name) - 1] == '\x00', svc::ResultOutOfRange()); + + /* Get the current handle table. */ + auto &handle_table = GetCurrentProcess().GetHandleTable(); + + /* Find the client port. */ + auto port = KObjectName::Find<KClientPort>(name); + R_UNLESS(port.IsNotNull(), svc::ResultNotFound()); + + /* Reserve a handle for the port. */ + /* NOTE: Nintendo really does write directly to the output handle here. */ + R_TRY(handle_table.Reserve(out)); + ON_RESULT_FAILURE { handle_table.Unreserve(*out); }; + + /* Create a session. */ + KClientSession *session; + R_TRY(port->CreateSession(std::addressof(session))); + + /* Register the session in the table, close the extra reference. */ + handle_table.Register(*out, session); + session->Close(); + + /* We succeeded. */ + R_SUCCEED(); + } + + Result ConnectToPort(ams::svc::Handle *out, ams::svc::Handle port) { + /* Get the current handle table. */ + auto &handle_table = GetCurrentProcess().GetHandleTable(); + + /* Get the client port. */ + KScopedAutoObject client_port = handle_table.GetObject<KClientPort>(port); + R_UNLESS(client_port.IsNotNull(), svc::ResultInvalidHandle()); + + /* Reserve a handle for the port. */ + /* NOTE: Nintendo really does write directly to the output handle here. */ + R_TRY(handle_table.Reserve(out)); + ON_RESULT_FAILURE { handle_table.Unreserve(*out); }; + + /* Create the session. */ + KAutoObject *session; + if (client_port->IsLight()) { + R_TRY(client_port->CreateLightSession(reinterpret_cast<KLightClientSession **>(std::addressof(session)))); + } else { + R_TRY(client_port->CreateSession(reinterpret_cast<KClientSession **>(std::addressof(session)))); + } + + /* Register the session. */ + handle_table.Register(*out, session); + session->Close(); + + /* We succeeded. */ + R_SUCCEED(); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result ConnectToNamedPort64(ams::svc::Handle *out_handle, KUserPointer<const char *> name) { + R_RETURN(ConnectToNamedPort(out_handle, name)); + } + + Result CreatePort64(ams::svc::Handle *out_server_handle, ams::svc::Handle *out_client_handle, int32_t max_sessions, bool is_light, ams::svc::Address name) { + R_RETURN(CreatePort(out_server_handle, out_client_handle, max_sessions, is_light, name)); + } + + Result ManageNamedPort64(ams::svc::Handle *out_server_handle, KUserPointer<const char *> name, int32_t max_sessions) { + R_RETURN(ManageNamedPort(out_server_handle, name, max_sessions)); + } + + Result ConnectToPort64(ams::svc::Handle *out_handle, ams::svc::Handle port) { + R_RETURN(ConnectToPort(out_handle, port)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result ConnectToNamedPort64From32(ams::svc::Handle *out_handle, KUserPointer<const char *> name) { + R_RETURN(ConnectToNamedPort(out_handle, name)); + } + + Result CreatePort64From32(ams::svc::Handle *out_server_handle, ams::svc::Handle *out_client_handle, int32_t max_sessions, bool is_light, ams::svc::Address name) { + R_RETURN(CreatePort(out_server_handle, out_client_handle, max_sessions, is_light, name)); + } + + Result ManageNamedPort64From32(ams::svc::Handle *out_server_handle, KUserPointer<const char *> name, int32_t max_sessions) { + R_RETURN(ManageNamedPort(out_server_handle, name, max_sessions)); + } + + Result ConnectToPort64From32(ams::svc::Handle *out_handle, ams::svc::Handle port) { + R_RETURN(ConnectToPort(out_handle, port)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_power_management.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_power_management.cpp new file mode 100644 index 00000000..83a95c75 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_power_management.cpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + void SleepSystem() { + return KSystemControl::SleepSystem(); + } + + } + + /* ============================= 64 ABI ============================= */ + + void SleepSystem64() { + return SleepSystem(); + } + + /* ============================= 64From32 ABI ============================= */ + + void SleepSystem64From32() { + return SleepSystem(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_process.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_process.cpp new file mode 100644 index 00000000..6e516a51 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_process.cpp @@ -0,0 +1,445 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + constexpr bool IsValidVirtualCoreId(int32_t core_id) { + return (0 <= core_id && core_id < static_cast<int32_t>(cpu::NumVirtualCores)); + } + + void ExitProcess() { + GetCurrentProcess().Exit(); + MESOSPHERE_PANIC("Process survived call to exit"); + } + + Result GetProcessId(u64 *out_process_id, ams::svc::Handle handle) { + /* Get the object from the handle table. */ + KScopedAutoObject obj = GetCurrentProcess().GetHandleTable().GetObject<KAutoObject>(handle); + R_UNLESS(obj.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the process from the object. */ + if (KProcess *process = obj->DynamicCast<KProcess *>(); process != nullptr) { + /* The object is a process, so we can use it directly. */ + + /* Make sure the target process exists. */ + R_UNLESS(process != nullptr, svc::ResultInvalidHandle()); + + /* Get the process id. */ + *out_process_id = process->GetId(); + } else if (KThread *t = obj->DynamicCast<KThread *>(); t != nullptr) { + /* The object is a thread, so we want to use its parent. */ + KProcess *process = t->GetOwnerProcess(); + + /* Make sure the target process exists. */ + R_UNLESS(process != nullptr, svc::ResultInvalidHandle()); + + /* Get the process id. */ + *out_process_id = process->GetId(); + } else if (KDebug *d = obj->DynamicCast<KDebug *>(); d != nullptr) { + /* The object is a debug, so we want to use the process it's attached to. */ + + /* Make sure the target process exists. */ + R_UNLESS(d->IsAttached(), svc::ResultInvalidHandle()); + R_UNLESS(d->OpenProcess(), svc::ResultInvalidHandle()); + ON_SCOPE_EXIT { d->CloseProcess(); }; + + /* Get the process id. */ + *out_process_id = d->GetProcessUnsafe()->GetProcessId(); + } else { + R_THROW(svc::ResultInvalidHandle()); + } + + R_SUCCEED(); + } + + Result GetProcessList(int32_t *out_num_processes, KUserPointer<uint64_t *> out_process_ids, int32_t max_out_count) { + /* Only allow invoking the svc on development hardware. */ + R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNotImplemented()); + + /* Validate that the out count is valid. */ + R_UNLESS((0 <= max_out_count && max_out_count <= static_cast<int32_t>(std::numeric_limits<int32_t>::max() / sizeof(u64))), svc::ResultOutOfRange()); + + /* Validate that the pointer is in range. */ + if (max_out_count > 0) { + R_UNLESS(GetCurrentProcess().GetPageTable().Contains(KProcessAddress(out_process_ids.GetUnsafePointer()), max_out_count * sizeof(u64)), svc::ResultInvalidCurrentMemory()); + } + + /* Get the process list. */ + R_RETURN(KProcess::GetProcessList(out_num_processes, out_process_ids, max_out_count)); + } + + Result CreateProcess(ams::svc::Handle *out, const ams::svc::CreateProcessParameter ¶ms, KUserPointer<const uint32_t *> user_caps, int32_t num_caps) { + /* Validate the capabilities pointer. */ + R_UNLESS(num_caps >= 0, svc::ResultInvalidPointer()); + if (num_caps > 0) { + /* Check for overflow. */ + R_UNLESS(((num_caps * sizeof(u32)) / sizeof(u32)) == static_cast<size_t>(num_caps), svc::ResultInvalidPointer()); + + /* Validate that the pointer is in range. */ + R_UNLESS(GetCurrentProcess().GetPageTable().Contains(KProcessAddress(user_caps.GetUnsafePointer()), num_caps * sizeof(u32)), svc::ResultInvalidPointer()); + } + + /* Validate that the parameter flags are valid. */ + R_UNLESS((params.flags & ~ams::svc::CreateProcessFlag_All) == 0, svc::ResultInvalidEnumValue()); + + /* Validate that 64-bit process is okay. */ + const bool is_64_bit = (params.flags & ams::svc::CreateProcessFlag_Is64Bit) != 0; + if constexpr (sizeof(void *) < sizeof(u64)) { + R_UNLESS(!is_64_bit, svc::ResultInvalidCombination()); + } + + /* Decide on an address space map region. */ + uintptr_t map_start, map_end; + size_t map_size; + const size_t code_size = params.code_num_pages * PageSize; + switch (params.flags & ams::svc::CreateProcessFlag_AddressSpaceMask) { + case ams::svc::CreateProcessFlag_AddressSpace32Bit: + case ams::svc::CreateProcessFlag_AddressSpace32BitWithoutAlias: + { + map_start = KAddressSpaceInfo::GetAddressSpaceStart(static_cast<ams::svc::CreateProcessFlag>(params.flags), KAddressSpaceInfo::Type_MapSmall, code_size); + map_size = KAddressSpaceInfo::GetAddressSpaceSize(static_cast<ams::svc::CreateProcessFlag>(params.flags), KAddressSpaceInfo::Type_MapSmall); + map_end = map_start + map_size; + } + break; + case ams::svc::CreateProcessFlag_AddressSpace64BitDeprecated: + { + /* 64-bit address space requires 64-bit process. */ + R_UNLESS(is_64_bit, svc::ResultInvalidCombination()); + + map_start = KAddressSpaceInfo::GetAddressSpaceStart(static_cast<ams::svc::CreateProcessFlag>(params.flags), KAddressSpaceInfo::Type_MapSmall, code_size); + map_size = KAddressSpaceInfo::GetAddressSpaceSize(static_cast<ams::svc::CreateProcessFlag>(params.flags), KAddressSpaceInfo::Type_MapSmall); + map_end = map_start + map_size; + } + break; + case ams::svc::CreateProcessFlag_AddressSpace64Bit: + { + /* 64-bit address space requires 64-bit process. */ + R_UNLESS(is_64_bit, svc::ResultInvalidCombination()); + + map_start = KAddressSpaceInfo::GetAddressSpaceStart(static_cast<ams::svc::CreateProcessFlag>(params.flags), KAddressSpaceInfo::Type_Map39Bit, code_size); + map_end = map_start + KAddressSpaceInfo::GetAddressSpaceSize(static_cast<ams::svc::CreateProcessFlag>(params.flags), KAddressSpaceInfo::Type_Map39Bit); + + map_size = KAddressSpaceInfo::GetAddressSpaceSize(static_cast<ams::svc::CreateProcessFlag>(params.flags), KAddressSpaceInfo::Type_Heap); + } + break; + default: + R_THROW(svc::ResultInvalidEnumValue()); + } + + /* Validate the pool partition. */ + if (GetTargetFirmware() >= TargetFirmware_5_0_0) { + switch (params.flags & ams::svc::CreateProcessFlag_PoolPartitionMask) { + case ams::svc::CreateProcessFlag_PoolPartitionApplication: + case ams::svc::CreateProcessFlag_PoolPartitionApplet: + case ams::svc::CreateProcessFlag_PoolPartitionSystem: + case ams::svc::CreateProcessFlag_PoolPartitionSystemNonSecure: + break; + default: + R_THROW(svc::ResultInvalidEnumValue()); + } + } + + /* Check that the code address is aligned. */ + R_UNLESS(util::IsAligned(params.code_address, KProcess::AslrAlignment), svc::ResultInvalidAddress()); + + /* Check that the number of code pages is >= 0. */ + R_UNLESS(params.code_num_pages >= 0, svc::ResultInvalidSize()); + + /* Check that the number of extra resource pages is >= 0. */ + R_UNLESS(params.system_resource_num_pages >= 0, svc::ResultInvalidSize()); + + /* Validate that the alias region extra size is allowed, if enabled. */ + if (params.flags & ams::svc::CreateProcessFlag_EnableAliasRegionExtraSize) { + /* Check that we have a 64-bit address space. */ + R_UNLESS((params.flags & ams::svc::CreateProcessFlag_AddressSpaceMask) == ams::svc::CreateProcessFlag_AddressSpace64Bit, svc::ResultInvalidState()); + + /* Check that the system resource page count is non-zero. */ + R_UNLESS(params.system_resource_num_pages > 0, svc::ResultInvalidState()); + + /* Check that debug mode is enabled. */ + R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultInvalidState()); + } + + /* Convert to sizes. */ + const size_t code_num_pages = params.code_num_pages; + const size_t system_resource_num_pages = params.system_resource_num_pages; + const size_t total_pages = code_num_pages + system_resource_num_pages; + const size_t system_resource_size = system_resource_num_pages * PageSize; + const size_t total_size = code_size + system_resource_size; + + /* Check for overflow. */ + R_UNLESS((code_size / PageSize) == code_num_pages, svc::ResultInvalidSize()); + R_UNLESS((system_resource_size / PageSize) == system_resource_num_pages, svc::ResultInvalidSize()); + R_UNLESS((code_num_pages + system_resource_num_pages) >= code_num_pages, svc::ResultOutOfMemory()); + R_UNLESS((total_size / PageSize) == total_pages, svc::ResultInvalidSize()); + + /* Check that the number of pages is valid. */ + R_UNLESS(code_num_pages < (map_size / PageSize), svc::ResultInvalidMemoryRegion()); + + /* Validate that the code falls within the map reigon. */ + R_UNLESS(map_start <= params.code_address, svc::ResultInvalidMemoryRegion()); + R_UNLESS(params.code_address < params.code_address + code_size, svc::ResultInvalidMemoryRegion()); + R_UNLESS(params.code_address + code_size - 1 <= map_end - 1, svc::ResultInvalidMemoryRegion()); + + /* Check that the number of pages is valid for the kernel address space. */ + R_UNLESS(code_num_pages < (kern::MainMemorySizeMax / PageSize), svc::ResultOutOfMemory()); + R_UNLESS(system_resource_num_pages < (kern::MainMemorySizeMax / PageSize), svc::ResultOutOfMemory()); + R_UNLESS(total_pages < (kern::MainMemorySizeMax / PageSize), svc::ResultOutOfMemory()); + + /* Check that optimized memory allocation is used only for applications. */ + const bool optimize_allocs = (params.flags & ams::svc::CreateProcessFlag_OptimizeMemoryAllocation) != 0; + const bool is_application = (params.flags & ams::svc::CreateProcessFlag_IsApplication) != 0; + R_UNLESS(!optimize_allocs || is_application, svc::ResultBusy()); + + /* Check that the user-provided capabilities are accessible and refer to valid regions. */ + R_TRY(KCapabilities::CheckCapabilities(user_caps, num_caps)); + + /* Get the current handle table. */ + auto &handle_table = GetCurrentProcess().GetHandleTable(); + + /* Create the new process. */ + KProcess *process = KProcess::Create(); + R_UNLESS(process != nullptr, svc::ResultOutOfResource()); + + /* Ensure that the only reference to the process is in the handle table when we're done. */ + ON_SCOPE_EXIT { process->Close(); }; + + /* Get the resource limit from the handle. */ + KScopedAutoObject resource_limit = handle_table.GetObject<KResourceLimit>(params.reslimit); + R_UNLESS(resource_limit.IsNotNull() || params.reslimit == ams::svc::InvalidHandle, svc::ResultInvalidHandle()); + + /* Decide on a resource limit for the process. */ + KResourceLimit *process_resource_limit = resource_limit.IsNotNull() ? resource_limit.GetPointerUnsafe() : std::addressof(Kernel::GetSystemResourceLimit()); + + /* Get the pool for the process. */ + const auto pool = [](u32 flags) ALWAYS_INLINE_LAMBDA -> KMemoryManager::Pool { + if (GetTargetFirmware() >= TargetFirmware_5_0_0) { + switch (flags & ams::svc::CreateProcessFlag_PoolPartitionMask) { + case ams::svc::CreateProcessFlag_PoolPartitionApplication: + return KMemoryManager::Pool_Application; + case ams::svc::CreateProcessFlag_PoolPartitionApplet: + return KMemoryManager::Pool_Applet; + case ams::svc::CreateProcessFlag_PoolPartitionSystem: + return KMemoryManager::Pool_System; + case ams::svc::CreateProcessFlag_PoolPartitionSystemNonSecure: + default: + return KMemoryManager::Pool_SystemNonSecure; + } + } else if (GetTargetFirmware() >= TargetFirmware_4_0_0) { + if ((flags & ams::svc::CreateProcessFlag_DeprecatedUseSecureMemory) != 0) { + return KMemoryManager::Pool_Secure; + } else { + return static_cast<KMemoryManager::Pool>(KSystemControl::GetCreateProcessMemoryPool()); + } + } else { + return static_cast<KMemoryManager::Pool>(KSystemControl::GetCreateProcessMemoryPool()); + } + }(params.flags); + + /* Initialize the process. */ + R_TRY(process->Initialize(params, user_caps, num_caps, process_resource_limit, pool)); + + /* Register the process. */ + KProcess::Register(process); + + /* Add the process to the handle table. */ + R_TRY(handle_table.Add(out, process)); + + R_SUCCEED(); + } + + template<typename T> + Result CreateProcess(ams::svc::Handle *out, KUserPointer<const T *> user_parameters, KUserPointer<const uint32_t *> user_caps, int32_t num_caps) { + /* Read the parameters from user space. */ + T params; + R_TRY(user_parameters.CopyTo(std::addressof(params))); + + /* Invoke the implementation. */ + if constexpr (std::same_as<T, ams::svc::CreateProcessParameter>) { + R_RETURN(CreateProcess(out, params, user_caps, num_caps)); + } else { + /* Convert the parameters. */ + ams::svc::CreateProcessParameter converted_params; + static_assert(sizeof(T{}.name) == sizeof(ams::svc::CreateProcessParameter{}.name)); + + std::memcpy(converted_params.name, params.name, sizeof(converted_params.name)); + converted_params.version = params.version; + converted_params.program_id = params.program_id; + converted_params.code_address = params.code_address; + converted_params.code_num_pages = params.code_num_pages; + converted_params.flags = params.flags; + converted_params.reslimit = params.reslimit; + converted_params.system_resource_num_pages = params.system_resource_num_pages; + + /* Invoke. */ + R_RETURN(CreateProcess(out, converted_params, user_caps, num_caps)); + } + } + + Result StartProcess(ams::svc::Handle process_handle, int32_t priority, int32_t core_id, uint64_t main_thread_stack_size) { + /* Validate stack size. */ + const uint64_t aligned_stack_size = util::AlignUp(main_thread_stack_size, Kernel::GetMemoryManager().GetMinimumAlignment(GetCurrentProcess().GetMemoryPool())); + R_UNLESS(aligned_stack_size >= main_thread_stack_size, svc::ResultOutOfMemory()); + R_UNLESS(aligned_stack_size == static_cast<size_t>(aligned_stack_size), svc::ResultOutOfMemory()); + + /* Get the target process. */ + KScopedAutoObject process = GetCurrentProcess().GetHandleTable().GetObject<KProcess>(process_handle); + R_UNLESS(process.IsNotNull(), svc::ResultInvalidHandle()); + + /* Validate the core id. */ + R_UNLESS(IsValidVirtualCoreId(core_id), svc::ResultInvalidCoreId()); + R_UNLESS(((1ul << core_id) & process->GetCoreMask()) != 0, svc::ResultInvalidCoreId()); + + /* Validate the priority. */ + R_UNLESS(ams::svc::HighestThreadPriority <= priority && priority <= ams::svc::LowestThreadPriority, svc::ResultInvalidPriority()); + R_UNLESS(process->CheckThreadPriority(priority), svc::ResultInvalidPriority()); + + /* Set the process's ideal processor. */ + process->SetIdealCoreId(core_id); + + /* Run the process. */ + R_RETURN(process->Run(priority, static_cast<size_t>(aligned_stack_size))); + } + + Result TerminateProcess(ams::svc::Handle process_handle) { + /* Get the target process. */ + KProcess *process = GetCurrentProcess().GetHandleTable().GetObject<KProcess>(process_handle).ReleasePointerUnsafe(); + R_UNLESS(process != nullptr, svc::ResultInvalidHandle()); + + if (process != GetCurrentProcessPointer()) { + /* We're terminating another process. Close our reference after terminating the process. */ + ON_SCOPE_EXIT { process->Close(); }; + + /* Terminate the process. */ + R_TRY(process->Terminate()); + } else { + /* We're terminating ourselves. Close our reference immediately. */ + process->Close(); + + /* Exit. */ + ExitProcess(); + } + + R_SUCCEED(); + } + + Result GetProcessInfo(int64_t *out, ams::svc::Handle process_handle, ams::svc::ProcessInfoType info_type) { + /* Get the target process. */ + KScopedAutoObject process = GetCurrentProcess().GetHandleTable().GetObject<KProcess>(process_handle); + R_UNLESS(process.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the info. */ + switch (info_type) { + case ams::svc::ProcessInfoType_ProcessState: + { + /* Get the process's state. */ + KProcess::State state; + { + KScopedLightLock proc_lk(process->GetStateLock()); + KScopedSchedulerLock sl; + + state = process->GetState(); + } + + /* Convert to svc state. */ + switch (state) { + case KProcess::State_Created: *out = ams::svc::ProcessState_Created; break; + case KProcess::State_CreatedAttached: *out = ams::svc::ProcessState_CreatedAttached; break; + case KProcess::State_Running: *out = ams::svc::ProcessState_Running; break; + case KProcess::State_Crashed: *out = ams::svc::ProcessState_Crashed; break; + case KProcess::State_RunningAttached: *out = ams::svc::ProcessState_RunningAttached; break; + case KProcess::State_Terminating: *out = ams::svc::ProcessState_Terminating; break; + case KProcess::State_Terminated: *out = ams::svc::ProcessState_Terminated; break; + case KProcess::State_DebugBreak: *out = ams::svc::ProcessState_DebugBreak; break; + MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); + } + } + break; + default: + R_THROW(svc::ResultInvalidEnumValue()); + } + + R_SUCCEED(); + } + + } + + /* ============================= 64 ABI ============================= */ + + void ExitProcess64() { + return ExitProcess(); + } + + Result GetProcessId64(uint64_t *out_process_id, ams::svc::Handle process_handle) { + R_RETURN(GetProcessId(out_process_id, process_handle)); + } + + Result GetProcessList64(int32_t *out_num_processes, KUserPointer<uint64_t *> out_process_ids, int32_t max_out_count) { + R_RETURN(GetProcessList(out_num_processes, out_process_ids, max_out_count)); + } + + Result CreateProcess64(ams::svc::Handle *out_handle, KUserPointer<const ams::svc::lp64::CreateProcessParameter *> parameters, KUserPointer<const uint32_t *> caps, int32_t num_caps) { + R_RETURN(CreateProcess(out_handle, parameters, caps, num_caps)); + } + + Result StartProcess64(ams::svc::Handle process_handle, int32_t priority, int32_t core_id, uint64_t main_thread_stack_size) { + R_RETURN(StartProcess(process_handle, priority, core_id, main_thread_stack_size)); + } + + Result TerminateProcess64(ams::svc::Handle process_handle) { + R_RETURN(TerminateProcess(process_handle)); + } + + Result GetProcessInfo64(int64_t *out_info, ams::svc::Handle process_handle, ams::svc::ProcessInfoType info_type) { + R_RETURN(GetProcessInfo(out_info, process_handle, info_type)); + } + + /* ============================= 64From32 ABI ============================= */ + + void ExitProcess64From32() { + return ExitProcess(); + } + + Result GetProcessId64From32(uint64_t *out_process_id, ams::svc::Handle process_handle) { + R_RETURN(GetProcessId(out_process_id, process_handle)); + } + + Result GetProcessList64From32(int32_t *out_num_processes, KUserPointer<uint64_t *> out_process_ids, int32_t max_out_count) { + R_RETURN(GetProcessList(out_num_processes, out_process_ids, max_out_count)); + } + + Result CreateProcess64From32(ams::svc::Handle *out_handle, KUserPointer<const ams::svc::ilp32::CreateProcessParameter *> parameters, KUserPointer<const uint32_t *> caps, int32_t num_caps) { + R_RETURN(CreateProcess(out_handle, parameters, caps, num_caps)); + } + + Result StartProcess64From32(ams::svc::Handle process_handle, int32_t priority, int32_t core_id, uint64_t main_thread_stack_size) { + R_RETURN(StartProcess(process_handle, priority, core_id, main_thread_stack_size)); + } + + Result TerminateProcess64From32(ams::svc::Handle process_handle) { + R_RETURN(TerminateProcess(process_handle)); + } + + Result GetProcessInfo64From32(int64_t *out_info, ams::svc::Handle process_handle, ams::svc::ProcessInfoType info_type) { + R_RETURN(GetProcessInfo(out_info, process_handle, info_type)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_process_memory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_process_memory.cpp new file mode 100644 index 00000000..41420ca0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_process_memory.cpp @@ -0,0 +1,232 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + constexpr bool IsValidProcessMemoryPermission(ams::svc::MemoryPermission perm) { + switch (perm) { + case ams::svc::MemoryPermission_None: + case ams::svc::MemoryPermission_Read: + case ams::svc::MemoryPermission_ReadWrite: + case ams::svc::MemoryPermission_ReadExecute: + case ams::svc::MemoryPermission_Execute: + return true; + default: + return false; + } + } + + Result SetProcessMemoryPermission(ams::svc::Handle process_handle, uint64_t address, uint64_t size, ams::svc::MemoryPermission perm) { + /* Validate the address/size. */ + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + R_UNLESS(address == static_cast<uintptr_t>(address), svc::ResultInvalidCurrentMemory()); + R_UNLESS(size == static_cast<size_t>(size), svc::ResultInvalidCurrentMemory()); + + /* Validate the memory permission. */ + R_UNLESS(IsValidProcessMemoryPermission(perm), svc::ResultInvalidNewMemoryPermission()); + + /* Get the process from its handle. */ + KScopedAutoObject process = GetCurrentProcess().GetHandleTable().GetObject<KProcess>(process_handle); + R_UNLESS(process.IsNotNull(), svc::ResultInvalidHandle()); + + /* Validate that the address is in range. */ + auto &page_table = process->GetPageTable(); + R_UNLESS(page_table.Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Set the memory permission. */ + R_RETURN(page_table.SetProcessMemoryPermission(address, size, perm)); + } + + Result MapProcessMemory(uintptr_t dst_address, ams::svc::Handle process_handle, uint64_t src_address, size_t size) { + /* Validate the address/size. */ + R_UNLESS(util::IsAligned(dst_address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(src_address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((dst_address < dst_address + size), svc::ResultInvalidCurrentMemory()); + R_UNLESS((src_address < src_address + size), svc::ResultInvalidCurrentMemory()); + R_UNLESS(src_address == static_cast<uintptr_t>(src_address), svc::ResultInvalidCurrentMemory()); + + /* Get the processes. */ + KProcess *dst_process = GetCurrentProcessPointer(); + KScopedAutoObject src_process = dst_process->GetHandleTable().GetObjectWithoutPseudoHandle<KProcess>(process_handle); + R_UNLESS(src_process.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the page tables. */ + auto &dst_pt = dst_process->GetPageTable(); + auto &src_pt = src_process->GetPageTable(); + + /* Validate that the mapping is in range. */ + R_UNLESS(src_pt.Contains(src_address, size), svc::ResultInvalidCurrentMemory()); + R_UNLESS(dst_pt.CanContain(dst_address, size, KMemoryState_SharedCode), svc::ResultInvalidMemoryRegion()); + + /* Create a new page group. */ + KPageGroup pg(dst_pt.GetBlockInfoManager()); + + /* Make the page group. */ + R_TRY(src_pt.MakeAndOpenPageGroup(std::addressof(pg), + src_address, size / PageSize, + KMemoryState_FlagCanMapProcess, KMemoryState_FlagCanMapProcess, + KMemoryPermission_None, KMemoryPermission_None, + KMemoryAttribute_All, KMemoryAttribute_None)); + + /* Close the page group when we're done. */ + ON_SCOPE_EXIT { pg.Close(); }; + + /* Map the group. */ + R_TRY(dst_pt.MapPageGroup(dst_address, pg, KMemoryState_SharedCode, KMemoryPermission_UserReadWrite)); + + R_SUCCEED(); + } + + Result UnmapProcessMemory(uintptr_t dst_address, ams::svc::Handle process_handle, uint64_t src_address, size_t size) { + /* Validate the address/size. */ + R_UNLESS(util::IsAligned(dst_address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(src_address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((dst_address < dst_address + size), svc::ResultInvalidCurrentMemory()); + R_UNLESS((src_address < src_address + size), svc::ResultInvalidCurrentMemory()); + R_UNLESS(src_address == static_cast<uintptr_t>(src_address), svc::ResultInvalidCurrentMemory()); + + /* Get the processes. */ + KProcess *dst_process = GetCurrentProcessPointer(); + KScopedAutoObject src_process = dst_process->GetHandleTable().GetObjectWithoutPseudoHandle<KProcess>(process_handle); + R_UNLESS(src_process.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the page tables. */ + auto &dst_pt = dst_process->GetPageTable(); + auto &src_pt = src_process->GetPageTable(); + + /* Validate that the mapping is in range. */ + R_UNLESS(src_pt.Contains(src_address, size), svc::ResultInvalidCurrentMemory()); + R_UNLESS(dst_pt.CanContain(dst_address, size, KMemoryState_SharedCode), svc::ResultInvalidMemoryRegion()); + + /* Unmap the memory. */ + R_TRY(dst_pt.UnmapProcessMemory(dst_address, size, src_pt, src_address)); + + R_SUCCEED(); + } + + Result MapProcessCodeMemory(ams::svc::Handle process_handle, uint64_t dst_address, uint64_t src_address, uint64_t size) { + /* Validate the address/size. */ + R_UNLESS(util::IsAligned(dst_address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(src_address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((dst_address < dst_address + size), svc::ResultInvalidCurrentMemory()); + R_UNLESS((src_address < src_address + size), svc::ResultInvalidCurrentMemory()); + R_UNLESS(src_address == static_cast<uintptr_t>(src_address), svc::ResultInvalidCurrentMemory()); + R_UNLESS(dst_address == static_cast<uintptr_t>(dst_address), svc::ResultInvalidCurrentMemory()); + R_UNLESS(size == static_cast<size_t>(size), svc::ResultInvalidCurrentMemory()); + + /* Get the process from its handle. */ + KScopedAutoObject process = GetCurrentProcess().GetHandleTable().GetObjectWithoutPseudoHandle<KProcess>(process_handle); + R_UNLESS(process.IsNotNull(), svc::ResultInvalidHandle()); + + /* Validate that the mapping is in range. */ + auto &page_table = process->GetPageTable(); + R_UNLESS(page_table.Contains(src_address, size), svc::ResultInvalidCurrentMemory()); + R_UNLESS(page_table.CanContain(dst_address, size, KMemoryState_AliasCode), svc::ResultInvalidCurrentMemory()); + + /* Map the memory. */ + R_TRY(page_table.MapCodeMemory(dst_address, src_address, size)); + + R_SUCCEED(); + } + + Result UnmapProcessCodeMemory(ams::svc::Handle process_handle, uint64_t dst_address, uint64_t src_address, uint64_t size) { + /* Validate the address/size. */ + R_UNLESS(util::IsAligned(dst_address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(src_address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((dst_address < dst_address + size), svc::ResultInvalidCurrentMemory()); + R_UNLESS((src_address < src_address + size), svc::ResultInvalidCurrentMemory()); + R_UNLESS(src_address == static_cast<uintptr_t>(src_address), svc::ResultInvalidCurrentMemory()); + R_UNLESS(dst_address == static_cast<uintptr_t>(dst_address), svc::ResultInvalidCurrentMemory()); + R_UNLESS(size == static_cast<size_t>(size), svc::ResultInvalidCurrentMemory()); + + /* Get the process from its handle. */ + KScopedAutoObject process = GetCurrentProcess().GetHandleTable().GetObjectWithoutPseudoHandle<KProcess>(process_handle); + R_UNLESS(process.IsNotNull(), svc::ResultInvalidHandle()); + + /* Validate that the mapping is in range. */ + auto &page_table = process->GetPageTable(); + R_UNLESS(page_table.Contains(src_address, size), svc::ResultInvalidCurrentMemory()); + R_UNLESS(page_table.CanContain(dst_address, size, KMemoryState_AliasCode), svc::ResultInvalidCurrentMemory()); + + /* Unmap the memory. */ + R_TRY(page_table.UnmapCodeMemory(dst_address, src_address, size)); + + R_SUCCEED(); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result SetProcessMemoryPermission64(ams::svc::Handle process_handle, uint64_t address, uint64_t size, ams::svc::MemoryPermission perm) { + R_RETURN(SetProcessMemoryPermission(process_handle, address, size, perm)); + } + + Result MapProcessMemory64(ams::svc::Address dst_address, ams::svc::Handle process_handle, uint64_t src_address, ams::svc::Size size) { + R_RETURN(MapProcessMemory(dst_address, process_handle, src_address, size)); + } + + Result UnmapProcessMemory64(ams::svc::Address dst_address, ams::svc::Handle process_handle, uint64_t src_address, ams::svc::Size size) { + R_RETURN(UnmapProcessMemory(dst_address, process_handle, src_address, size)); + } + + Result MapProcessCodeMemory64(ams::svc::Handle process_handle, uint64_t dst_address, uint64_t src_address, uint64_t size) { + R_RETURN(MapProcessCodeMemory(process_handle, dst_address, src_address, size)); + } + + Result UnmapProcessCodeMemory64(ams::svc::Handle process_handle, uint64_t dst_address, uint64_t src_address, uint64_t size) { + R_RETURN(UnmapProcessCodeMemory(process_handle, dst_address, src_address, size)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result SetProcessMemoryPermission64From32(ams::svc::Handle process_handle, uint64_t address, uint64_t size, ams::svc::MemoryPermission perm) { + R_RETURN(SetProcessMemoryPermission(process_handle, address, size, perm)); + } + + Result MapProcessMemory64From32(ams::svc::Address dst_address, ams::svc::Handle process_handle, uint64_t src_address, ams::svc::Size size) { + R_RETURN(MapProcessMemory(dst_address, process_handle, src_address, size)); + } + + Result UnmapProcessMemory64From32(ams::svc::Address dst_address, ams::svc::Handle process_handle, uint64_t src_address, ams::svc::Size size) { + R_RETURN(UnmapProcessMemory(dst_address, process_handle, src_address, size)); + } + + Result MapProcessCodeMemory64From32(ams::svc::Handle process_handle, uint64_t dst_address, uint64_t src_address, uint64_t size) { + R_RETURN(MapProcessCodeMemory(process_handle, dst_address, src_address, size)); + } + + Result UnmapProcessCodeMemory64From32(ams::svc::Handle process_handle, uint64_t dst_address, uint64_t src_address, uint64_t size) { + R_RETURN(UnmapProcessCodeMemory(process_handle, dst_address, src_address, size)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_processor.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_processor.cpp new file mode 100644 index 00000000..55d850cb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_processor.cpp @@ -0,0 +1,66 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + int32_t GetCurrentProcessorNumber() { + /* Setup variables to track affinity information. */ + s32 current_phys_core; + u64 v_affinity_mask = 0; + + /* Forever try to get the affinity. */ + while (true) { + /* Update affinity information if we've run out. */ + while (v_affinity_mask == 0) { + current_phys_core = GetCurrentCoreId(); + v_affinity_mask = GetCurrentThread().GetVirtualAffinityMask(); + if ((v_affinity_mask & (1ul << current_phys_core)) != 0) { + return current_phys_core; + } + } + + /* Check the next virtual bit. */ + do { + const s32 next_virt_core = static_cast<s32>(__builtin_ctzll(v_affinity_mask)); + if (current_phys_core == cpu::VirtualToPhysicalCoreMap[next_virt_core]) { + return next_virt_core; + } + + v_affinity_mask &= ~(1ul << next_virt_core); + } while (v_affinity_mask != 0); + } + } + + } + + /* ============================= 64 ABI ============================= */ + + int32_t GetCurrentProcessorNumber64() { + return GetCurrentProcessorNumber(); + } + + /* ============================= 64From32 ABI ============================= */ + + int32_t GetCurrentProcessorNumber64From32() { + return GetCurrentProcessorNumber(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_query_memory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_query_memory.cpp new file mode 100644 index 00000000..55513074 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_query_memory.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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + Result QueryProcessMemory(ams::svc::MemoryInfo *out_memory_info, ams::svc::PageInfo *out_page_info, ams::svc::Handle process_handle, uintptr_t address) { + /* Get the process. */ + KScopedAutoObject process = GetCurrentProcess().GetHandleTable().GetObject<KProcess>(process_handle); + R_UNLESS(process.IsNotNull(), svc::ResultInvalidHandle()); + + /* Query the mapping's info. */ + KMemoryInfo info; + R_TRY(process->GetPageTable().QueryInfo(std::addressof(info), out_page_info, address)); + + /* Write output. */ + *out_memory_info = info.GetSvcMemoryInfo(); + R_SUCCEED(); + } + + template<typename T> + Result QueryProcessMemory(KUserPointer<T *> out_memory_info, ams::svc::PageInfo *out_page_info, ams::svc::Handle process_handle, uint64_t address) { + /* Get an ams::svc::MemoryInfo for the region. */ + ams::svc::MemoryInfo info = {}; + R_TRY(QueryProcessMemory(std::addressof(info), out_page_info, process_handle, address)); + + /* Copy the info to userspace. */ + if constexpr (std::same_as<T, ams::svc::MemoryInfo>) { + R_TRY(out_memory_info.CopyFrom(std::addressof(info))); + } else { + /* Convert the info. */ + T converted_info = {}; + static_assert(std::same_as<decltype(T{}.base_address), decltype(ams::svc::MemoryInfo{}.base_address)>); + static_assert(std::same_as<decltype(T{}.size), decltype(ams::svc::MemoryInfo{}.size)>); + + converted_info.base_address = info.base_address; + converted_info.size = info.size; + converted_info.state = info.state; + converted_info.attribute = info.attribute; + converted_info.permission = info.permission; + converted_info.ipc_count = info.ipc_count; + converted_info.device_count = info.device_count; + + /* Copy it. */ + R_TRY(out_memory_info.CopyFrom(std::addressof(converted_info))); + } + + R_SUCCEED(); + } + + + template<typename T> + Result QueryMemory(KUserPointer<T *> out_memory_info, ams::svc::PageInfo *out_page_info, uintptr_t address) { + /* Query memory is just QueryProcessMemory on the current process. */ + R_RETURN(QueryProcessMemory(out_memory_info, out_page_info, ams::svc::PseudoHandle::CurrentProcess, address)); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result QueryMemory64(KUserPointer<ams::svc::lp64::MemoryInfo *> out_memory_info, ams::svc::PageInfo *out_page_info, ams::svc::Address address) { + R_RETURN(QueryMemory(out_memory_info, out_page_info, address)); + } + + Result QueryProcessMemory64(KUserPointer<ams::svc::lp64::MemoryInfo *> out_memory_info, ams::svc::PageInfo *out_page_info, ams::svc::Handle process_handle, uint64_t address) { + R_RETURN(QueryProcessMemory(out_memory_info, out_page_info, process_handle, address)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result QueryMemory64From32(KUserPointer<ams::svc::ilp32::MemoryInfo *> out_memory_info, ams::svc::PageInfo *out_page_info, ams::svc::Address address) { + R_RETURN(QueryMemory(out_memory_info, out_page_info, address)); + } + + Result QueryProcessMemory64From32(KUserPointer<ams::svc::ilp32::MemoryInfo *> out_memory_info, ams::svc::PageInfo *out_page_info, ams::svc::Handle process_handle, uint64_t address) { + R_RETURN(QueryProcessMemory(out_memory_info, out_page_info, process_handle, address)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_register.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_register.cpp new file mode 100644 index 00000000..f8ab7c0d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_register.cpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + Result ReadWriteRegister(uint32_t *out, ams::svc::PhysicalAddress address, uint32_t mask, uint32_t value) { + /* Clear the output unconditionally. */ + *out = 0; + + /* Read/write the register. */ + R_RETURN(KSystemControl::ReadWriteRegister(out, address, mask, value)); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result ReadWriteRegister64(uint32_t *out_value, ams::svc::PhysicalAddress address, uint32_t mask, uint32_t value) { + R_RETURN(ReadWriteRegister(out_value, address, mask, value)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result ReadWriteRegister64From32(uint32_t *out_value, ams::svc::PhysicalAddress address, uint32_t mask, uint32_t value) { + R_RETURN(ReadWriteRegister(out_value, address, mask, value)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_resource_limit.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_resource_limit.cpp new file mode 100644 index 00000000..cecb37dc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_resource_limit.cpp @@ -0,0 +1,150 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + constexpr bool IsValidLimitableResource(ams::svc::LimitableResource which) { + return which < ams::svc::LimitableResource_Count; + } + + Result GetResourceLimitLimitValue(int64_t *out_limit_value, ams::svc::Handle resource_limit_handle, ams::svc::LimitableResource which) { + /* Validate the resource. */ + R_UNLESS(IsValidLimitableResource(which), svc::ResultInvalidEnumValue()); + + /* Get the resource limit. */ + KScopedAutoObject resource_limit = GetCurrentProcess().GetHandleTable().GetObject<KResourceLimit>(resource_limit_handle); + R_UNLESS(resource_limit.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the limit value. */ + *out_limit_value = resource_limit->GetLimitValue(which); + + R_SUCCEED(); + } + + Result GetResourceLimitCurrentValue(int64_t *out_current_value, ams::svc::Handle resource_limit_handle, ams::svc::LimitableResource which) { + /* Validate the resource. */ + R_UNLESS(IsValidLimitableResource(which), svc::ResultInvalidEnumValue()); + + /* Get the resource limit. */ + KScopedAutoObject resource_limit = GetCurrentProcess().GetHandleTable().GetObject<KResourceLimit>(resource_limit_handle); + R_UNLESS(resource_limit.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the current value. */ + *out_current_value = resource_limit->GetCurrentValue(which); + + R_SUCCEED(); + } + + Result GetResourceLimitPeakValue(int64_t *out_peak_value, ams::svc::Handle resource_limit_handle, ams::svc::LimitableResource which) { + /* Validate the resource. */ + R_UNLESS(IsValidLimitableResource(which), svc::ResultInvalidEnumValue()); + + /* Get the resource limit. */ + KScopedAutoObject resource_limit = GetCurrentProcess().GetHandleTable().GetObject<KResourceLimit>(resource_limit_handle); + R_UNLESS(resource_limit.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the peak value. */ + *out_peak_value = resource_limit->GetPeakValue(which); + + R_SUCCEED(); + } + + Result CreateResourceLimit(ams::svc::Handle *out_handle) { + /* Create a new resource limit. */ + KResourceLimit *resource_limit = KResourceLimit::Create(); + R_UNLESS(resource_limit != nullptr, svc::ResultOutOfResource()); + + /* Ensure we don't leak a reference to the limit. */ + ON_SCOPE_EXIT { resource_limit->Close(); }; + + /* Initialize the resource limit. */ + resource_limit->Initialize(); + + /* Register the limit. */ + KResourceLimit::Register(resource_limit); + + /* Add the limit to the handle table. */ + R_TRY(GetCurrentProcess().GetHandleTable().Add(out_handle, resource_limit)); + + R_SUCCEED(); + } + + Result SetResourceLimitLimitValue(ams::svc::Handle resource_limit_handle, ams::svc::LimitableResource which, int64_t limit_value) { + /* Validate the resource. */ + R_UNLESS(IsValidLimitableResource(which), svc::ResultInvalidEnumValue()); + + /* Get the resource limit. */ + KScopedAutoObject resource_limit = GetCurrentProcess().GetHandleTable().GetObject<KResourceLimit>(resource_limit_handle); + R_UNLESS(resource_limit.IsNotNull(), svc::ResultInvalidHandle()); + + /* Set the limit value. */ + R_TRY(resource_limit->SetLimitValue(which, limit_value)); + + R_SUCCEED(); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result GetResourceLimitLimitValue64(int64_t *out_limit_value, ams::svc::Handle resource_limit_handle, ams::svc::LimitableResource which) { + R_RETURN(GetResourceLimitLimitValue(out_limit_value, resource_limit_handle, which)); + } + + Result GetResourceLimitCurrentValue64(int64_t *out_current_value, ams::svc::Handle resource_limit_handle, ams::svc::LimitableResource which) { + R_RETURN(GetResourceLimitCurrentValue(out_current_value, resource_limit_handle, which)); + } + + Result GetResourceLimitPeakValue64(int64_t *out_peak_value, ams::svc::Handle resource_limit_handle, ams::svc::LimitableResource which) { + R_RETURN(GetResourceLimitPeakValue(out_peak_value, resource_limit_handle, which)); + } + + Result CreateResourceLimit64(ams::svc::Handle *out_handle) { + R_RETURN(CreateResourceLimit(out_handle)); + } + + Result SetResourceLimitLimitValue64(ams::svc::Handle resource_limit_handle, ams::svc::LimitableResource which, int64_t limit_value) { + R_RETURN(SetResourceLimitLimitValue(resource_limit_handle, which, limit_value)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result GetResourceLimitLimitValue64From32(int64_t *out_limit_value, ams::svc::Handle resource_limit_handle, ams::svc::LimitableResource which) { + R_RETURN(GetResourceLimitLimitValue(out_limit_value, resource_limit_handle, which)); + } + + Result GetResourceLimitCurrentValue64From32(int64_t *out_current_value, ams::svc::Handle resource_limit_handle, ams::svc::LimitableResource which) { + R_RETURN(GetResourceLimitCurrentValue(out_current_value, resource_limit_handle, which)); + } + + Result GetResourceLimitPeakValue64From32(int64_t *out_peak_value, ams::svc::Handle resource_limit_handle, ams::svc::LimitableResource which) { + R_RETURN(GetResourceLimitPeakValue(out_peak_value, resource_limit_handle, which)); + } + + Result CreateResourceLimit64From32(ams::svc::Handle *out_handle) { + R_RETURN(CreateResourceLimit(out_handle)); + } + + Result SetResourceLimitLimitValue64From32(ams::svc::Handle resource_limit_handle, ams::svc::LimitableResource which, int64_t limit_value) { + R_RETURN(SetResourceLimitLimitValue(resource_limit_handle, which, limit_value)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_secure_monitor_call.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_secure_monitor_call.cpp new file mode 100644 index 00000000..9300a576 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_secure_monitor_call.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + + + } + + /* ============================= 64 ABI ============================= */ + + void CallSecureMonitor64(ams::svc::lp64::SecureMonitorArguments *args) { + KSystemControl::CallSecureMonitorFromUser(args); + } + + /* ============================= 64From32 ABI ============================= */ + + /* CallSecureMonitor64From32 is not supported. */ + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_session.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_session.cpp new file mode 100644 index 00000000..507a4a8c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_session.cpp @@ -0,0 +1,151 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + template<typename T> + Result CreateSession(ams::svc::Handle *out_server, ams::svc::Handle *out_client, uintptr_t name) { + /* Get the current process and handle table. */ + auto &process = GetCurrentProcess(); + auto &handle_table = process.GetHandleTable(); + + /* Declare the session we're going to allocate. */ + T *session; + + /* Reserve a new session from the process resource limit. */ + KScopedResourceReservation session_reservation(std::addressof(process), ams::svc::LimitableResource_SessionCountMax); + if (session_reservation.Succeeded()) { + /* Allocate a session normally. */ + session = T::Create(); + } else { + /* We couldn't reserve a session. Check that we support dynamically expanding the resource limit. */ + R_UNLESS(process.GetResourceLimit() == std::addressof(Kernel::GetSystemResourceLimit()), svc::ResultLimitReached()); + R_UNLESS(KTargetSystem::IsDynamicResourceLimitsEnabled(), svc::ResultLimitReached()); + + /* Try to allocate a session from unused slab memory. */ + session = T::CreateFromUnusedSlabMemory(); + R_UNLESS(session != nullptr, svc::ResultLimitReached()); + ON_RESULT_FAILURE { session->Close(); }; + + /* If we're creating a KSession, we want to add two KSessionRequests to the heap, to prevent request exhaustion. */ + /* NOTE: Nintendo checks if session->DynamicCast<KSession *>() != nullptr, but there's no reason to not do this statically. */ + if constexpr (std::same_as<T, KSession>) { + for (size_t i = 0; i < 2; ++i) { + KSessionRequest *request = KSessionRequest::CreateFromUnusedSlabMemory(); + R_UNLESS(request != nullptr, svc::ResultLimitReached()); + + request->Close(); + } + } + + /* We successfully allocated a session, so add the object we allocated to the resource limit. */ + Kernel::GetSystemResourceLimit().Add(ams::svc::LimitableResource_SessionCountMax, 1); + } + + /* Check that we successfully created a session. */ + R_UNLESS(session != nullptr, svc::ResultOutOfResource()); + + /* Initialize the session. */ + session->Initialize(nullptr, name); + + /* Commit the session reservation. */ + session_reservation.Commit(); + + /* Ensure that we clean up the session (and its only references are handle table) on function end. */ + ON_SCOPE_EXIT { + session->GetClientSession().Close(); + session->GetServerSession().Close(); + }; + + /* Register the session. */ + T::Register(session); + + /* Add the server session to the handle table. */ + R_TRY(handle_table.Add(out_server, std::addressof(session->GetServerSession()))); + + /* Ensure that we maintaing a clean handle state on exit. */ + ON_RESULT_FAILURE { handle_table.Remove(*out_server); }; + + /* Add the client session to the handle table. */ + R_RETURN(handle_table.Add(out_client, std::addressof(session->GetClientSession()))); + } + + Result CreateSession(ams::svc::Handle *out_server, ams::svc::Handle *out_client, bool is_light, uintptr_t name) { + if (is_light) { + R_RETURN(CreateSession<KLightSession>(out_server, out_client, name)); + } else { + R_RETURN(CreateSession<KSession>(out_server, out_client, name)); + } + } + + Result AcceptSession(ams::svc::Handle *out, ams::svc::Handle port_handle) { + /* Get the current handle table. */ + auto &handle_table = GetCurrentProcess().GetHandleTable(); + + /* Get the server port. */ + KScopedAutoObject port = handle_table.GetObject<KServerPort>(port_handle); + R_UNLESS(port.IsNotNull(), svc::ResultInvalidHandle()); + + /* Reserve an entry for the new session. */ + R_TRY(handle_table.Reserve(out)); + ON_RESULT_FAILURE { handle_table.Unreserve(*out); }; + + /* Accept the session. */ + KAutoObject *session; + if (port->IsLight()) { + session = port->AcceptLightSession(); + } else { + session = port->AcceptSession(); + } + + /* Ensure we accepted successfully. */ + R_UNLESS(session != nullptr, svc::ResultNotFound()); + + /* Register the session. */ + handle_table.Register(*out, session); + session->Close(); + + R_SUCCEED(); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result CreateSession64(ams::svc::Handle *out_server_session_handle, ams::svc::Handle *out_client_session_handle, bool is_light, ams::svc::Address name) { + R_RETURN(CreateSession(out_server_session_handle, out_client_session_handle, is_light, name)); + } + + Result AcceptSession64(ams::svc::Handle *out_handle, ams::svc::Handle port) { + R_RETURN(AcceptSession(out_handle, port)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result CreateSession64From32(ams::svc::Handle *out_server_session_handle, ams::svc::Handle *out_client_session_handle, bool is_light, ams::svc::Address name) { + R_RETURN(CreateSession(out_server_session_handle, out_client_session_handle, is_light, name)); + } + + Result AcceptSession64From32(ams::svc::Handle *out_handle, ams::svc::Handle port) { + R_RETURN(AcceptSession(out_handle, port)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_shared_memory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_shared_memory.cpp new file mode 100644 index 00000000..87e03074 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_shared_memory.cpp @@ -0,0 +1,154 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + constexpr bool IsValidSharedMemoryPermission(ams::svc::MemoryPermission perm) { + switch (perm) { + case ams::svc::MemoryPermission_Read: + case ams::svc::MemoryPermission_ReadWrite: + return true; + default: + return false; + } + } + + constexpr bool IsValidRemoteSharedMemoryPermission(ams::svc::MemoryPermission perm) { + return IsValidSharedMemoryPermission(perm) || perm == ams::svc::MemoryPermission_DontCare; + } + + Result MapSharedMemory(ams::svc::Handle shmem_handle, uintptr_t address, size_t size, ams::svc::MemoryPermission map_perm) { + /* Validate the address/size. */ + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + + /* Validate the permission. */ + R_UNLESS(IsValidSharedMemoryPermission(map_perm), svc::ResultInvalidNewMemoryPermission()); + + /* Get the current process. */ + auto &process = GetCurrentProcess(); + auto &page_table = process.GetPageTable(); + + /* Get the shared memory. */ + KScopedAutoObject shmem = process.GetHandleTable().GetObject<KSharedMemory>(shmem_handle); + R_UNLESS(shmem.IsNotNull(), svc::ResultInvalidHandle()); + + /* Verify that the mapping is in range. */ + R_UNLESS(page_table.CanContain(address, size, KMemoryState_Shared), svc::ResultInvalidMemoryRegion()); + + /* Add the shared memory to the process. */ + R_TRY(process.AddSharedMemory(shmem.GetPointerUnsafe(), address, size)); + + /* Ensure that we clean up the shared memory if we fail to map it. */ + ON_RESULT_FAILURE { process.RemoveSharedMemory(shmem.GetPointerUnsafe(), address, size); }; + + /* Map the shared memory. */ + R_RETURN(shmem->Map(std::addressof(page_table), address, size, std::addressof(process), map_perm)); + } + + Result UnmapSharedMemory(ams::svc::Handle shmem_handle, uintptr_t address, size_t size) { + /* Validate the address/size. */ + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + + /* Get the current process. */ + auto &process = GetCurrentProcess(); + auto &page_table = process.GetPageTable(); + + /* Get the shared memory. */ + KScopedAutoObject shmem = process.GetHandleTable().GetObject<KSharedMemory>(shmem_handle); + R_UNLESS(shmem.IsNotNull(), svc::ResultInvalidHandle()); + + /* Verify that the mapping is in range. */ + R_UNLESS(page_table.CanContain(address, size, KMemoryState_Shared), svc::ResultInvalidMemoryRegion()); + + /* Unmap the shared memory. */ + R_TRY(shmem->Unmap(std::addressof(page_table), address, size, std::addressof(process))); + + /* Remove the shared memory from the process. */ + process.RemoveSharedMemory(shmem.GetPointerUnsafe(), address, size); + + R_SUCCEED(); + } + + Result CreateSharedMemory(ams::svc::Handle *out, size_t size, ams::svc::MemoryPermission owner_perm, ams::svc::MemoryPermission remote_perm) { + /* Validate the size. */ + R_UNLESS(0 < size && size < kern::MainMemorySizeMax, svc::ResultInvalidSize()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + + /* Validate the permissions. */ + R_UNLESS(IsValidSharedMemoryPermission(owner_perm), svc::ResultInvalidNewMemoryPermission()); + R_UNLESS(IsValidRemoteSharedMemoryPermission(remote_perm), svc::ResultInvalidNewMemoryPermission()); + + /* Create the shared memory. */ + KSharedMemory *shmem = KSharedMemory::Create(); + R_UNLESS(shmem != nullptr, svc::ResultOutOfResource()); + + /* Ensure the only reference is in the handle table when we're done. */ + ON_SCOPE_EXIT { shmem->Close(); }; + + /* Initialize the shared memory. */ + R_TRY(shmem->Initialize(GetCurrentProcessPointer(), size, owner_perm, remote_perm)); + + /* Register the shared memory. */ + KSharedMemory::Register(shmem); + + /* Add the shared memory to the handle table. */ + R_TRY(GetCurrentProcess().GetHandleTable().Add(out, shmem)); + + R_SUCCEED(); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result MapSharedMemory64(ams::svc::Handle shmem_handle, ams::svc::Address address, ams::svc::Size size, ams::svc::MemoryPermission map_perm) { + R_RETURN(MapSharedMemory(shmem_handle, address, size, map_perm)); + } + + Result UnmapSharedMemory64(ams::svc::Handle shmem_handle, ams::svc::Address address, ams::svc::Size size) { + R_RETURN(UnmapSharedMemory(shmem_handle, address, size)); + } + + Result CreateSharedMemory64(ams::svc::Handle *out_handle, ams::svc::Size size, ams::svc::MemoryPermission owner_perm, ams::svc::MemoryPermission remote_perm) { + R_RETURN(CreateSharedMemory(out_handle, size, owner_perm, remote_perm)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result MapSharedMemory64From32(ams::svc::Handle shmem_handle, ams::svc::Address address, ams::svc::Size size, ams::svc::MemoryPermission map_perm) { + R_RETURN(MapSharedMemory(shmem_handle, address, size, map_perm)); + } + + Result UnmapSharedMemory64From32(ams::svc::Handle shmem_handle, ams::svc::Address address, ams::svc::Size size) { + R_RETURN(UnmapSharedMemory(shmem_handle, address, size)); + } + + Result CreateSharedMemory64From32(ams::svc::Handle *out_handle, ams::svc::Size size, ams::svc::MemoryPermission owner_perm, ams::svc::MemoryPermission remote_perm) { + R_RETURN(CreateSharedMemory(out_handle, size, owner_perm, remote_perm)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_synchronization.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_synchronization.cpp new file mode 100644 index 00000000..20e025bb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_synchronization.cpp @@ -0,0 +1,180 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + Result CloseHandle(ams::svc::Handle handle) { + /* Remove the handle. */ + R_UNLESS(GetCurrentProcess().GetHandleTable().Remove(handle), svc::ResultInvalidHandle()); + R_SUCCEED(); + } + + Result ResetSignal(ams::svc::Handle handle) { + /* Get the current handle table. */ + auto &handle_table = GetCurrentProcess().GetHandleTable(); + + /* Try to reset as readable event. */ + { + KScopedAutoObject readable_event = handle_table.GetObject<KReadableEvent>(handle); + if (readable_event.IsNotNull()) { + if (auto * const interrupt_event = readable_event->DynamicCast<KInterruptEvent *>(); interrupt_event != nullptr) { + R_RETURN(interrupt_event->Reset()); + } else { + R_RETURN(readable_event->Reset()); + } + } + } + + /* Try to reset as process. */ + { + KScopedAutoObject process = handle_table.GetObject<KProcess>(handle); + if (process.IsNotNull()) { + R_RETURN(process->Reset()); + } + } + + R_THROW(svc::ResultInvalidHandle()); + } + + Result WaitSynchronizationImpl(int32_t *out_index, KSynchronizationObject **objs, int32_t num_handles, int64_t timeout_ns) { + /* Convert the timeout from nanoseconds to ticks. */ + s64 timeout; + if (timeout_ns > 0) { + u64 ticks = KHardwareTimer::GetTick(); + ticks += ams::svc::Tick(TimeSpan::FromNanoSeconds(timeout_ns)); + ticks += 2; + + timeout = ticks; + } else { + timeout = timeout_ns; + } + + R_RETURN(KSynchronizationObject::Wait(out_index, objs, num_handles, timeout)); + } + + Result WaitSynchronization(int32_t *out_index, KUserPointer<const ams::svc::Handle *> user_handles, int32_t num_handles, int64_t timeout_ns) { + /* Ensure number of handles is valid. */ + R_UNLESS(0 <= num_handles && num_handles <= ams::svc::ArgumentHandleCountMax, svc::ResultOutOfRange()); + + /* Get the synchronization context. */ + auto &handle_table = GetCurrentProcess().GetHandleTable(); + KSynchronizationObject **objs = GetCurrentThread().GetSynchronizationObjectBuffer(); + ams::svc::Handle *handles = GetCurrentThread().GetHandleBuffer(); + + /* Copy user handles. */ + if (num_handles > 0) { + /* Ensure that we can try to get the handles. */ + R_UNLESS(GetCurrentProcess().GetPageTable().Contains(KProcessAddress(user_handles.GetUnsafePointer()), num_handles * sizeof(ams::svc::Handle)), svc::ResultInvalidPointer()); + + /* Get the handles. */ + R_TRY(user_handles.CopyArrayTo(handles, num_handles)); + + /* Convert the handles to objects. */ + R_UNLESS(handle_table.GetMultipleObjects<KSynchronizationObject>(objs, handles, num_handles), svc::ResultInvalidHandle()); + } + + /* Ensure handles are closed when we're done. */ + ON_SCOPE_EXIT { + for (auto i = 0; i < num_handles; ++i) { + objs[i]->Close(); + } + }; + + /* Wait on the objects. */ + R_TRY_CATCH(WaitSynchronizationImpl(out_index, objs, num_handles, timeout_ns)) { + R_CONVERT(svc::ResultSessionClosed, ResultSuccess()) + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + Result CancelSynchronization(ams::svc::Handle handle) { + /* Get the thread from its handle. */ + KScopedAutoObject thread = GetCurrentProcess().GetHandleTable().GetObject<KThread>(handle); + R_UNLESS(thread.IsNotNull(), svc::ResultInvalidHandle()); + + /* Cancel the thread's wait. */ + thread->WaitCancel(); + R_SUCCEED(); + } + + void SynchronizePreemptionState() { + /* Lock the scheduler. */ + KScopedSchedulerLock sl; + + /* If the current thread is pinned, unpin it. */ + KProcess *cur_process = GetCurrentProcessPointer(); + if (cur_process->GetPinnedThread(GetCurrentCoreId()) == GetCurrentThreadPointer()) { + /* Clear the current thread's interrupt flag. */ + GetCurrentThread().ClearInterruptFlag(); + + /* Unpin the current thread. */ + cur_process->UnpinCurrentThread(); + } + } + + } + + /* ============================= 64 ABI ============================= */ + + Result CloseHandle64(ams::svc::Handle handle) { + R_RETURN(CloseHandle(handle)); + } + + Result ResetSignal64(ams::svc::Handle handle) { + R_RETURN(ResetSignal(handle)); + } + + Result WaitSynchronization64(int32_t *out_index, KUserPointer<const ams::svc::Handle *> handles, int32_t num_handles, int64_t timeout_ns) { + R_RETURN(WaitSynchronization(out_index, handles, num_handles, timeout_ns)); + } + + Result CancelSynchronization64(ams::svc::Handle handle) { + R_RETURN(CancelSynchronization(handle)); + } + + void SynchronizePreemptionState64() { + return SynchronizePreemptionState(); + } + + /* ============================= 64From32 ABI ============================= */ + + Result CloseHandle64From32(ams::svc::Handle handle) { + R_RETURN(CloseHandle(handle)); + } + + Result ResetSignal64From32(ams::svc::Handle handle) { + R_RETURN(ResetSignal(handle)); + } + + Result WaitSynchronization64From32(int32_t *out_index, KUserPointer<const ams::svc::Handle *> handles, int32_t num_handles, int64_t timeout_ns) { + R_RETURN(WaitSynchronization(out_index, handles, num_handles, timeout_ns)); + } + + Result CancelSynchronization64From32(ams::svc::Handle handle) { + R_RETURN(CancelSynchronization(handle)); + } + + void SynchronizePreemptionState64From32() { + return SynchronizePreemptionState(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_thread.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_thread.cpp new file mode 100644 index 00000000..6f74dd14 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_thread.cpp @@ -0,0 +1,359 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + constexpr bool IsValidVirtualCoreId(int32_t core_id) { + return (0 <= core_id && core_id < static_cast<int32_t>(cpu::NumVirtualCores)); + } + + Result CreateThread(ams::svc::Handle *out, ams::svc::ThreadFunc f, uintptr_t arg, uintptr_t stack_bottom, int32_t priority, int32_t core_id) { + /* Adjust core id, if it's the default magic. */ + KProcess &process = GetCurrentProcess(); + if (core_id == ams::svc::IdealCoreUseProcessValue) { + core_id = process.GetIdealCoreId(); + } + + /* Validate arguments. */ + R_UNLESS(IsValidVirtualCoreId(core_id), svc::ResultInvalidCoreId()); + R_UNLESS(((1ul << core_id) & process.GetCoreMask()) != 0, svc::ResultInvalidCoreId()); + + R_UNLESS(ams::svc::HighestThreadPriority <= priority && priority <= ams::svc::LowestThreadPriority, svc::ResultInvalidPriority()); + R_UNLESS(process.CheckThreadPriority(priority), svc::ResultInvalidPriority()); + + /* Reserve a new thread from the process resource limit (waiting up to 100ms). */ + KScopedResourceReservation thread_reservation(std::addressof(process), ams::svc::LimitableResource_ThreadCountMax, 1, KHardwareTimer::GetTick() + ams::svc::Tick(TimeSpan::FromMilliSeconds(100))); + R_UNLESS(thread_reservation.Succeeded(), svc::ResultLimitReached()); + + /* Create the thread. */ + KThread *thread = KThread::Create(); + R_UNLESS(thread != nullptr, svc::ResultOutOfResource()); + ON_SCOPE_EXIT { thread->Close(); }; + + /* Initialize the thread. */ + { + KScopedLightLock lk(process.GetStateLock()); + R_TRY(KThread::InitializeUserThread(thread, reinterpret_cast<KThreadFunction>(static_cast<uintptr_t>(f)), arg, stack_bottom, priority, core_id, std::addressof(process))); + } + + /* Commit the thread reservation. */ + thread_reservation.Commit(); + + /* Clone the current fpu status to the new thread. */ + thread->GetContext().CloneFpuStatus(); + + /* Register the new thread. */ + KThread::Register(thread); + + /* Add the thread to the handle table. */ + R_TRY(process.GetHandleTable().Add(out, thread)); + + R_SUCCEED(); + } + + Result StartThread(ams::svc::Handle thread_handle) { + /* Get the thread from its handle. */ + KScopedAutoObject thread = GetCurrentProcess().GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), svc::ResultInvalidHandle()); + + /* Try to start the thread. */ + R_RETURN(thread->Run()); + } + + void ExitThread() { + GetCurrentThread().Exit(); + MESOSPHERE_PANIC("Thread survived call to exit"); + } + + void SleepThread(int64_t ns) { + /* When the input tick is positive, sleep. */ + if (AMS_LIKELY(ns > 0)) { + /* Convert the timeout from nanoseconds to ticks. */ + /* NOTE: Nintendo does not use this conversion logic in WaitSynchronization... */ + s64 timeout; + + const ams::svc::Tick offset_tick(TimeSpan::FromNanoSeconds(ns)); + if (AMS_LIKELY(offset_tick > 0)) { + timeout = KHardwareTimer::GetTick() + offset_tick + 2; + if (AMS_UNLIKELY(timeout <= 0)) { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = std::numeric_limits<s64>::max(); + } + + /* Sleep. */ + /* NOTE: Nintendo does not check the result of this sleep. */ + GetCurrentThread().Sleep(timeout); + } else if (ns == ams::svc::YieldType_WithoutCoreMigration) { + KScheduler::YieldWithoutCoreMigration(); + } else if (ns == ams::svc::YieldType_WithCoreMigration) { + KScheduler::YieldWithCoreMigration(); + } else if (ns == ams::svc::YieldType_ToAnyThread) { + KScheduler::YieldToAnyThread(); + } else { + /* Nintendo does nothing at all if an otherwise invalid value is passed. */ + } + } + + Result GetThreadPriority(int32_t *out_priority, ams::svc::Handle thread_handle) { + /* Get the thread from its handle. */ + KScopedAutoObject thread = GetCurrentProcess().GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the thread's priority. */ + *out_priority = thread->GetPriority(); + R_SUCCEED(); + } + + Result SetThreadPriority(ams::svc::Handle thread_handle, int32_t priority) { + /* Get the current process. */ + KProcess &process = GetCurrentProcess(); + + /* Get the thread from its handle. */ + KScopedAutoObject thread = process.GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), svc::ResultInvalidHandle()); + + /* Validate the thread is owned by the current process. */ + R_UNLESS(thread->GetOwnerProcess() == GetCurrentProcessPointer(), svc::ResultInvalidHandle()); + + /* Validate the priority. */ + R_UNLESS(ams::svc::HighestThreadPriority <= priority && priority <= ams::svc::LowestThreadPriority, svc::ResultInvalidPriority()); + R_UNLESS(process.CheckThreadPriority(priority), svc::ResultInvalidPriority()); + + /* Set the thread priority. */ + thread->SetBasePriority(priority); + R_SUCCEED(); + } + + Result GetThreadCoreMask(int32_t *out_core_id, uint64_t *out_affinity_mask, ams::svc::Handle thread_handle) { + /* Get the thread from its handle. */ + KScopedAutoObject thread = GetCurrentProcess().GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the core mask. */ + R_TRY(thread->GetCoreMask(out_core_id, out_affinity_mask)); + + R_SUCCEED(); + } + + Result SetThreadCoreMask(ams::svc::Handle thread_handle, int32_t core_id, uint64_t affinity_mask) { + /* Get the thread from its handle. */ + KScopedAutoObject thread = GetCurrentProcess().GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), svc::ResultInvalidHandle()); + + /* Validate the thread is owned by the current process. */ + R_UNLESS(thread->GetOwnerProcess() == GetCurrentProcessPointer(), svc::ResultInvalidHandle()); + + /* Determine the core id/affinity mask. */ + if (core_id == ams::svc::IdealCoreUseProcessValue) { + core_id = GetCurrentProcess().GetIdealCoreId(); + affinity_mask = (1ul << core_id); + } else { + /* Validate the affinity mask. */ + const u64 process_core_mask = GetCurrentProcess().GetCoreMask(); + R_UNLESS((affinity_mask | process_core_mask) == process_core_mask, svc::ResultInvalidCoreId()); + R_UNLESS(affinity_mask != 0, svc::ResultInvalidCombination()); + + /* Validate the core id. */ + if (IsValidVirtualCoreId(core_id)) { + R_UNLESS(((1ul << core_id) & affinity_mask) != 0, svc::ResultInvalidCombination()); + } else { + R_UNLESS(core_id == ams::svc::IdealCoreNoUpdate || core_id == ams::svc::IdealCoreDontCare, svc::ResultInvalidCoreId()); + } + } + + /* Set the core mask. */ + R_TRY(thread->SetCoreMask(core_id, affinity_mask)); + + R_SUCCEED(); + } + + Result GetThreadId(uint64_t *out_thread_id, ams::svc::Handle thread_handle) { + /* Get the thread from its handle. */ + KScopedAutoObject thread = GetCurrentProcess().GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), svc::ResultInvalidHandle()); + + /* Get the thread's id. */ + *out_thread_id = thread->GetId(); + R_SUCCEED(); + } + + Result GetThreadContext3(KUserPointer<ams::svc::ThreadContext *> out_context, ams::svc::Handle thread_handle) { + /* Get the thread from its handle. */ + KScopedAutoObject thread = GetCurrentProcess().GetHandleTable().GetObject<KThread>(thread_handle); + R_UNLESS(thread.IsNotNull(), svc::ResultInvalidHandle()); + + /* Require the handle be to a non-current thread in the current process. */ + R_UNLESS(thread->GetOwnerProcess() == GetCurrentProcessPointer(), svc::ResultInvalidHandle()); + R_UNLESS(thread.GetPointerUnsafe() != GetCurrentThreadPointer(), svc::ResultBusy()); + + /* Get the thread context. */ + ams::svc::ThreadContext context = {}; + R_TRY(thread->GetThreadContext3(std::addressof(context))); + + /* Copy the thread context to user space. */ + R_TRY(out_context.CopyFrom(std::addressof(context))); + + R_SUCCEED(); + } + + Result GetThreadList(int32_t *out_num_threads, KUserPointer<uint64_t *> out_thread_ids, int32_t max_out_count, ams::svc::Handle debug_handle) { + /* Only allow invoking the svc on development hardware. */ + R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNotImplemented()); + + /* Validate that the out count is valid. */ + R_UNLESS((0 <= max_out_count && max_out_count <= static_cast<int32_t>(std::numeric_limits<int32_t>::max() / sizeof(u64))), svc::ResultOutOfRange()); + + /* Validate that the pointer is in range. */ + if (max_out_count > 0) { + R_UNLESS(GetCurrentProcess().GetPageTable().Contains(KProcessAddress(out_thread_ids.GetUnsafePointer()), max_out_count * sizeof(u64)), svc::ResultInvalidCurrentMemory()); + } + + /* Get the handle table. */ + auto &handle_table = GetCurrentProcess().GetHandleTable(); + + /* Try to get as a debug object. */ + KScopedAutoObject debug = handle_table.GetObject<KDebug>(debug_handle); + if (debug.IsNotNull()) { + /* Check that the debug object has a process. */ + R_UNLESS(debug->IsAttached(), svc::ResultProcessTerminated()); + R_UNLESS(debug->OpenProcess(), svc::ResultProcessTerminated()); + ON_SCOPE_EXIT { debug->CloseProcess(); }; + + /* Get the thread list. */ + R_TRY(debug->GetProcessUnsafe()->GetThreadList(out_num_threads, out_thread_ids, max_out_count)); + } else { + /* Only allow getting as a process (or global) if the caller does not have ForceDebugProd. */ + R_UNLESS(!GetCurrentProcess().CanForceDebugProd(), svc::ResultInvalidHandle()); + + /* Try to get as a process. */ + KScopedAutoObject process = handle_table.GetObjectWithoutPseudoHandle<KProcess>(debug_handle); + if (process.IsNotNull()) { + /* Get the thread list. */ + R_TRY(process->GetThreadList(out_num_threads, out_thread_ids, max_out_count)); + } else { + /* If the object is not a process, the caller may want the global thread list. */ + R_UNLESS(debug_handle == ams::svc::InvalidHandle, svc::ResultInvalidHandle()); + + /* If passed invalid handle, we should return the global thread list. */ + R_TRY(KThread::GetThreadList(out_num_threads, out_thread_ids, max_out_count)); + } + } + + R_SUCCEED(); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result CreateThread64(ams::svc::Handle *out_handle, ams::svc::ThreadFunc func, ams::svc::Address arg, ams::svc::Address stack_bottom, int32_t priority, int32_t core_id) { + R_RETURN(CreateThread(out_handle, func, arg, stack_bottom, priority, core_id)); + } + + Result StartThread64(ams::svc::Handle thread_handle) { + R_RETURN(StartThread(thread_handle)); + } + + void ExitThread64() { + return ExitThread(); + } + + void SleepThread64(int64_t ns) { + return SleepThread(ns); + } + + Result GetThreadPriority64(int32_t *out_priority, ams::svc::Handle thread_handle) { + R_RETURN(GetThreadPriority(out_priority, thread_handle)); + } + + Result SetThreadPriority64(ams::svc::Handle thread_handle, int32_t priority) { + R_RETURN(SetThreadPriority(thread_handle, priority)); + } + + Result GetThreadCoreMask64(int32_t *out_core_id, uint64_t *out_affinity_mask, ams::svc::Handle thread_handle) { + R_RETURN(GetThreadCoreMask(out_core_id, out_affinity_mask, thread_handle)); + } + + Result SetThreadCoreMask64(ams::svc::Handle thread_handle, int32_t core_id, uint64_t affinity_mask) { + R_RETURN(SetThreadCoreMask(thread_handle, core_id, affinity_mask)); + } + + Result GetThreadId64(uint64_t *out_thread_id, ams::svc::Handle thread_handle) { + R_RETURN(GetThreadId(out_thread_id, thread_handle)); + } + + Result GetThreadContext364(KUserPointer<ams::svc::ThreadContext *> out_context, ams::svc::Handle thread_handle) { + R_RETURN(GetThreadContext3(out_context, thread_handle)); + } + + Result GetThreadList64(int32_t *out_num_threads, KUserPointer<uint64_t *> out_thread_ids, int32_t max_out_count, ams::svc::Handle debug_handle) { + R_RETURN(GetThreadList(out_num_threads, out_thread_ids, max_out_count, debug_handle)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result CreateThread64From32(ams::svc::Handle *out_handle, ams::svc::ThreadFunc func, ams::svc::Address arg, ams::svc::Address stack_bottom, int32_t priority, int32_t core_id) { + R_RETURN(CreateThread(out_handle, func, arg, stack_bottom, priority, core_id)); + } + + Result StartThread64From32(ams::svc::Handle thread_handle) { + R_RETURN(StartThread(thread_handle)); + } + + void ExitThread64From32() { + return ExitThread(); + } + + void SleepThread64From32(int64_t ns) { + return SleepThread(ns); + } + + Result GetThreadPriority64From32(int32_t *out_priority, ams::svc::Handle thread_handle) { + R_RETURN(GetThreadPriority(out_priority, thread_handle)); + } + + Result SetThreadPriority64From32(ams::svc::Handle thread_handle, int32_t priority) { + R_RETURN(SetThreadPriority(thread_handle, priority)); + } + + Result GetThreadCoreMask64From32(int32_t *out_core_id, uint64_t *out_affinity_mask, ams::svc::Handle thread_handle) { + R_RETURN(GetThreadCoreMask(out_core_id, out_affinity_mask, thread_handle)); + } + + Result SetThreadCoreMask64From32(ams::svc::Handle thread_handle, int32_t core_id, uint64_t affinity_mask) { + R_RETURN(SetThreadCoreMask(thread_handle, core_id, affinity_mask)); + } + + Result GetThreadId64From32(uint64_t *out_thread_id, ams::svc::Handle thread_handle) { + R_RETURN(GetThreadId(out_thread_id, thread_handle)); + } + + Result GetThreadContext364From32(KUserPointer<ams::svc::ThreadContext *> out_context, ams::svc::Handle thread_handle) { + R_RETURN(GetThreadContext3(out_context, thread_handle)); + } + + Result GetThreadList64From32(int32_t *out_num_threads, KUserPointer<uint64_t *> out_thread_ids, int32_t max_out_count, ams::svc::Handle debug_handle) { + R_RETURN(GetThreadList(out_num_threads, out_thread_ids, max_out_count, debug_handle)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_thread_profiler.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_thread_profiler.cpp new file mode 100644 index 00000000..02264e42 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_thread_profiler.cpp @@ -0,0 +1,176 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + Result GetLastThreadInfoImpl(ams::svc::LastThreadContext *out_context, uintptr_t *out_tls_address, uint32_t *out_flags) { + /* Disable interrupts. */ + KScopedInterruptDisable di; + + /* Get the previous thread. */ + KThread *prev_thread = Kernel::GetScheduler().GetPreviousThread(); + R_UNLESS(prev_thread != nullptr, svc::ResultNoThread()); + + /* Verify the last thread was owned by the current process. */ + R_UNLESS(prev_thread->GetOwnerProcess() == GetCurrentProcessPointer(), svc::ResultUnknownThread()); + + /* Clear the output flags. */ + *out_flags = 0; + + /* Get the thread's exception context. */ + GetExceptionContext(prev_thread)->GetSvcThreadContext(out_context); + + /* Get the tls address. */ + *out_tls_address = GetInteger(prev_thread->GetThreadLocalRegionAddress()); + + /* Set the syscall flag if appropriate. */ + if (prev_thread->IsCallingSvc()) { + *out_flags |= ams::svc::LastThreadInfoFlag_ThreadInSystemCall; + } + + R_SUCCEED(); + } + + Result SynchronizeCurrentProcessToFutureTime(int64_t ns) { + /* Get the wait object. */ + KWaitObject *wait_object = GetCurrentProcess().GetWaitObjectPointer(); + + /* Convert the timeout from nanoseconds to ticks. */ + s64 timeout; + if (ns > 0) { + u64 ticks = KHardwareTimer::GetTick(); + ticks += ams::svc::Tick(TimeSpan::FromNanoSeconds(ns)); + ticks += 2; + + timeout = ticks; + } else { + timeout = ns; + } + + /* Synchronize to the desired time. */ + R_TRY(wait_object->Synchronize(timeout)); + + R_SUCCEED(); + } + + Result GetDebugFutureThreadInfo(ams::svc::LastThreadContext *out_context, uint64_t *out_thread_id, ams::svc::Handle debug_handle, int64_t ns) { + /* Only allow invoking the svc on development hardware. */ + R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNoThread()); + + /* Get the debug object. */ + KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(debug_handle); + R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); + + /* Synchronize the current process to the desired time. */ + R_TRY(SynchronizeCurrentProcessToFutureTime(ns)); + + /* Get the running thread info. */ + R_TRY(debug->GetRunningThreadInfo(out_context, out_thread_id)); + + R_SUCCEED(); + } + + Result LegacyGetFutureThreadInfo(ams::svc::LastThreadContext *out_context, uintptr_t *out_tls_address, uint32_t *out_flags, int64_t ns) { + /* Only allow invoking the svc on development hardware. */ + R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNoThread()); + + /* Synchronize the current process to the desired time. */ + R_TRY(SynchronizeCurrentProcessToFutureTime(ns)); + + /* Get the thread info. */ + R_TRY(GetLastThreadInfoImpl(out_context, out_tls_address, out_flags)); + + R_SUCCEED(); + } + + Result GetLastThreadInfo(ams::svc::LastThreadContext *out_context, uintptr_t *out_tls_address, uint32_t *out_flags) { + /* Only allow invoking the svc on development hardware. */ + R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNoThread()); + + /* Get the thread info. */ + R_TRY(GetLastThreadInfoImpl(out_context, out_tls_address, out_flags)); + + R_SUCCEED(); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result GetDebugFutureThreadInfo64(ams::svc::lp64::LastThreadContext *out_context, uint64_t *out_thread_id, ams::svc::Handle debug_handle, int64_t ns) { + R_RETURN(GetDebugFutureThreadInfo(out_context, out_thread_id, debug_handle, ns)); + } + + Result LegacyGetFutureThreadInfo64(ams::svc::lp64::LastThreadContext *out_context, ams::svc::Address *out_tls_address, uint32_t *out_flags, int64_t ns) { + R_RETURN(LegacyGetFutureThreadInfo(out_context, reinterpret_cast<uintptr_t *>(out_tls_address), out_flags, ns)); + } + + Result GetLastThreadInfo64(ams::svc::lp64::LastThreadContext *out_context, ams::svc::Address *out_tls_address, uint32_t *out_flags) { + static_assert(sizeof(*out_tls_address) == sizeof(uintptr_t)); + R_RETURN(GetLastThreadInfo(out_context, reinterpret_cast<uintptr_t *>(out_tls_address), out_flags)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result GetDebugFutureThreadInfo64From32(ams::svc::ilp32::LastThreadContext *out_context, uint64_t *out_thread_id, ams::svc::Handle debug_handle, int64_t ns) { + ams::svc::LastThreadContext context = {}; + R_TRY(GetDebugFutureThreadInfo(std::addressof(context), out_thread_id, debug_handle, ns)); + + *out_context = { + .fp = static_cast<u32>(context.fp), + .sp = static_cast<u32>(context.sp), + .lr = static_cast<u32>(context.lr), + .pc = static_cast<u32>(context.pc), + }; + R_SUCCEED(); + } + + Result LegacyGetFutureThreadInfo64From32(ams::svc::ilp32::LastThreadContext *out_context, ams::svc::Address *out_tls_address, uint32_t *out_flags, int64_t ns) { + static_assert(sizeof(*out_tls_address) == sizeof(uintptr_t)); + + ams::svc::LastThreadContext context = {}; + R_TRY(LegacyGetFutureThreadInfo(std::addressof(context), reinterpret_cast<uintptr_t *>(out_tls_address), out_flags, ns)); + + *out_context = { + .fp = static_cast<u32>(context.fp), + .sp = static_cast<u32>(context.sp), + .lr = static_cast<u32>(context.lr), + .pc = static_cast<u32>(context.pc), + }; + R_SUCCEED(); + } + + Result GetLastThreadInfo64From32(ams::svc::ilp32::LastThreadContext *out_context, ams::svc::Address *out_tls_address, uint32_t *out_flags) { + static_assert(sizeof(*out_tls_address) == sizeof(uintptr_t)); + + ams::svc::LastThreadContext context = {}; + R_TRY(GetLastThreadInfo(std::addressof(context), reinterpret_cast<uintptr_t *>(out_tls_address), out_flags)); + + *out_context = { + .fp = static_cast<u32>(context.fp), + .sp = static_cast<u32>(context.sp), + .lr = static_cast<u32>(context.lr), + .pc = static_cast<u32>(context.pc), + }; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_tick.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_tick.cpp new file mode 100644 index 00000000..ae635719 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_tick.cpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + int64_t GetSystemTick() { + return KHardwareTimer::GetTick(); + } + + } + + /* ============================= 64 ABI ============================= */ + + int64_t GetSystemTick64() { + return GetSystemTick(); + } + + /* ============================= 64From32 ABI ============================= */ + + int64_t GetSystemTick64From32() { + return GetSystemTick(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_transfer_memory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_transfer_memory.cpp new file mode 100644 index 00000000..99f8b474 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libmesosphere/source/svc/kern_svc_transfer_memory.cpp @@ -0,0 +1,152 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern::svc { + + /* ============================= Common ============================= */ + + namespace { + + constexpr bool IsValidTransferMemoryPermission(ams::svc::MemoryPermission perm) { + switch (perm) { + case ams::svc::MemoryPermission_None: + case ams::svc::MemoryPermission_Read: + case ams::svc::MemoryPermission_ReadWrite: + return true; + default: + return false; + } + } + + Result MapTransferMemory(ams::svc::Handle trmem_handle, uintptr_t address, size_t size, ams::svc::MemoryPermission map_perm) { + /* Validate the address/size. */ + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + + /* Validate the permission. */ + R_UNLESS(IsValidTransferMemoryPermission(map_perm), svc::ResultInvalidState()); + + /* Get the transfer memory. */ + KScopedAutoObject trmem = GetCurrentProcess().GetHandleTable().GetObject<KTransferMemory>(trmem_handle); + R_UNLESS(trmem.IsNotNull(), svc::ResultInvalidHandle()); + + /* Verify that the mapping is in range. */ + R_UNLESS(GetCurrentProcess().GetPageTable().CanContain(address, size, KMemoryState_Transfered), svc::ResultInvalidMemoryRegion()); + + /* Map the transfer memory. */ + R_TRY(trmem->Map(address, size, map_perm)); + + /* We succeeded. */ + R_SUCCEED(); + } + + Result UnmapTransferMemory(ams::svc::Handle trmem_handle, uintptr_t address, size_t size) { + /* Validate the address/size. */ + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + + /* Get the transfer memory. */ + KScopedAutoObject trmem = GetCurrentProcess().GetHandleTable().GetObject<KTransferMemory>(trmem_handle); + R_UNLESS(trmem.IsNotNull(), svc::ResultInvalidHandle()); + + /* Verify that the mapping is in range. */ + R_UNLESS(GetCurrentProcess().GetPageTable().CanContain(address, size, KMemoryState_Transfered), svc::ResultInvalidMemoryRegion()); + + /* Unmap the transfer memory. */ + R_TRY(trmem->Unmap(address, size)); + + R_SUCCEED(); + } + + Result CreateTransferMemory(ams::svc::Handle *out, uintptr_t address, size_t size, ams::svc::MemoryPermission map_perm) { + /* Validate the size. */ + R_UNLESS(util::IsAligned(address, PageSize), svc::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, PageSize), svc::ResultInvalidSize()); + R_UNLESS(size > 0, svc::ResultInvalidSize()); + R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); + + /* Validate the permissions. */ + R_UNLESS(IsValidTransferMemoryPermission(map_perm), svc::ResultInvalidNewMemoryPermission()); + + /* Get the current process and handle table. */ + auto &process = GetCurrentProcess(); + auto &handle_table = process.GetHandleTable(); + + /* Reserve a new transfer memory from the process resource limit. */ + KScopedResourceReservation trmem_reservation(std::addressof(process), ams::svc::LimitableResource_TransferMemoryCountMax); + R_UNLESS(trmem_reservation.Succeeded(), svc::ResultLimitReached()); + + /* Create the transfer memory. */ + KTransferMemory *trmem = KTransferMemory::Create(); + R_UNLESS(trmem != nullptr, svc::ResultOutOfResource()); + + /* Ensure the only reference is in the handle table when we're done. */ + ON_SCOPE_EXIT { trmem->Close(); }; + + /* Ensure that the region is in range. */ + R_UNLESS(process.GetPageTable().Contains(address, size), svc::ResultInvalidCurrentMemory()); + + /* Initialize the transfer memory. */ + R_TRY(trmem->Initialize(address, size, map_perm)); + + /* Commit the reservation. */ + trmem_reservation.Commit(); + + /* Register the transfer memory. */ + KTransferMemory::Register(trmem); + + /* Add the transfer memory to the handle table. */ + R_TRY(handle_table.Add(out, trmem)); + + R_SUCCEED(); + } + + } + + /* ============================= 64 ABI ============================= */ + + Result MapTransferMemory64(ams::svc::Handle trmem_handle, ams::svc::Address address, ams::svc::Size size, ams::svc::MemoryPermission owner_perm) { + R_RETURN(MapTransferMemory(trmem_handle, address, size, owner_perm)); + } + + Result UnmapTransferMemory64(ams::svc::Handle trmem_handle, ams::svc::Address address, ams::svc::Size size) { + R_RETURN(UnmapTransferMemory(trmem_handle, address, size)); + } + + Result CreateTransferMemory64(ams::svc::Handle *out_handle, ams::svc::Address address, ams::svc::Size size, ams::svc::MemoryPermission map_perm) { + R_RETURN(CreateTransferMemory(out_handle, address, size, map_perm)); + } + + /* ============================= 64From32 ABI ============================= */ + + Result MapTransferMemory64From32(ams::svc::Handle trmem_handle, ams::svc::Address address, ams::svc::Size size, ams::svc::MemoryPermission owner_perm) { + R_RETURN(MapTransferMemory(trmem_handle, address, size, owner_perm)); + } + + Result UnmapTransferMemory64From32(ams::svc::Handle trmem_handle, ams::svc::Address address, ams::svc::Size size) { + R_RETURN(UnmapTransferMemory(trmem_handle, address, size)); + } + + Result CreateTransferMemory64From32(ams::svc::Handle *out_handle, ams::svc::Address address, ams::svc::Size size, ams::svc::MemoryPermission map_perm) { + R_RETURN(CreateTransferMemory(out_handle, address, size, map_perm)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/Makefile b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/Makefile new file mode 100644 index 00000000..7eead0b1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/Makefile @@ -0,0 +1,50 @@ +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)/libstratosphere.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)/libstratosphere.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, $(strip $2)release, $(strip $3), $(strip $4), \ + ATMOSPHERE_BUILD_SETTINGS="$(strip $5)" $(strip $6) \ +)) + +$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_debug, $(strip $2)debug, $(strip $3), $(strip $4), \ + ATMOSPHERE_BUILD_SETTINGS="$(strip $5) -DAMS_BUILD_FOR_DEBUGGING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 $(strip $6) \ +)) + +$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_audit, $(strip $2)audit, $(strip $3), $(strip $4), \ + ATMOSPHERE_BUILD_SETTINGS="$(strip $5) -DAMS_BUILD_FOR_AUDITING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 ATMOSPHERE_BUILD_FOR_AUDITING=1 $(strip $6) \ +)) + +endef + +$(eval $(call ATMOSPHERE_ADD_TARGETS, nx, , nx-hac-001, arm-cortex-a57,,)) + +$(eval $(call ATMOSPHERE_ADD_TARGETS, win_x64, , generic_windows, generic_x64,,)) + +$(eval $(call ATMOSPHERE_ADD_TARGETS, linux_x64, , generic_linux, generic_x64,,)) +$(eval $(call ATMOSPHERE_ADD_TARGETS, linux_x64_clang, clang_, generic_linux, generic_x64,, ATMOSPHERE_COMPILER_NAME="clang")) +$(eval $(call ATMOSPHERE_ADD_TARGETS, linux_arm64_clang, clang_, generic_linux, generic_arm64,, ATMOSPHERE_COMPILER_NAME="clang")) + +$(eval $(call ATMOSPHERE_ADD_TARGETS, macos_x64, , generic_macos, generic_x64,,)) +$(eval $(call ATMOSPHERE_ADD_TARGETS, macos_arm64, , generic_macos, generic_arm64,,)) + +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/libraries/libstratosphere/README.md b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/README.md new file mode 100644 index 00000000..d13d14fc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/README.md @@ -0,0 +1,33 @@ +![License](https://img.shields.io/badge/License-GPLv2-blue.svg) + +libstratosphere is a work-in-progress C++ library for development of system modules for the Nintendo Switch. + +It is built around extending [libnx](https://github.com/switchbrew/libnx). + +It also provides bindings for custom extensions to Horizon OS implemented by [Atmosphère](https://github.com/Atmosphere-NX). + +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: +* The [yuzu Nintendo Switch emulator](https://github.com/yuzu-emu/yuzu) and the [Ryujinx Team and Contributors](https://github.com/orgs/Ryujinx) are exempt from GPLv2 licensing. They are permitted, each at their individual discretion, to instead license any source code authored for the libstratosphere project as either GPLv2 or later or the [MIT license](https://github.com/Atmosphere-NX/Atmosphere/blob/master/docs/licensing_exemptions/MIT_LICENSE). In doing so, they may alter, supplement, or entirely remove the copyright notice for each file they choose to relicense. Neither the Atmosphère project nor its individual contributors shall assert their moral rights against any of the aforementioned projects. +* [Nintendo](https://github.com/Nintendo) is exempt from GPLv2 licensing and may (at its option) instead license any source code authored for the libstratosphere project under the Zero-Clause BSD license. + +Credits +===== + +libstratosphere is currently being developed and maintained by __SciresM__.<br> + +In addition to those credited in [Atmosphère's credits](https://github.com/Atmosphere-NX/Atmosphere/blob/master/README.md#Credits), we would like to thank for contributing to libstratosphere in some significant way: + +* __hthh__ +* __fincs__ +* __lioncash__ +* __misson20000__ +* __neobrain__ +* __yellows8__ +* @[Nintendo](https://github.com/Nintendo) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/discard-ehframe.ld b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/discard-ehframe.ld new file mode 100644 index 00000000..b3b067e0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/discard-ehframe.ld @@ -0,0 +1,7 @@ +SECTIONS +{ + /* Discard .eh_frame section */ + /DISCARD/ : { *(.group .comment .note .interp) + EXCLUDE_FILE(*crtbegin.o) *(.eh_frame_hdr .eh_frame) + } +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere.hpp new file mode 100644 index 00000000..92ac1def --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere.hpp @@ -0,0 +1,112 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +/* Ensure that on windows we use lean-windows headers. */ +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +/* libvapours (pulls in util, svc, results). */ +#include <vapours.hpp> + +/* Libstratosphere diagnostics. */ +#include <stratosphere/diag.hpp> + +/* Libstratosphere definitions. */ +#include <stratosphere/ams/impl/ams_system_thread_definitions.hpp> + +/* Libstratosphere-only utility. */ +#include <stratosphere/util.hpp> + +/* Sadly required shims. */ +#include <stratosphere/svc/svc_stratosphere_shims.hpp> + +/* Critical modules with no dependencies. */ +#include <stratosphere/ams.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/dd.hpp> +#include <stratosphere/lmem.hpp> +#include <stratosphere/mem.hpp> + +/* Pull in all ID definitions from NCM. */ +#include <stratosphere/ncm/ncm_ids.hpp> + +/* At this point, just include the rest alphabetically. */ +/* TODO: Figure out optimal order. */ +#include <stratosphere/boot2.hpp> +#include <stratosphere/cal.hpp> +#include <stratosphere/capsrv.hpp> +#include <stratosphere/cfg.hpp> +#include <stratosphere/clkrst.hpp> +#include <stratosphere/cs.hpp> +#include <stratosphere/ddsf.hpp> +#include <stratosphere/dmnt.hpp> +#include <stratosphere/erpt.hpp> +#include <stratosphere/err.hpp> +#include <stratosphere/fatal.hpp> +#include <stratosphere/gc.hpp> +#include <stratosphere/gpio.hpp> +#include <stratosphere/hid.hpp> +#include <stratosphere/hos.hpp> +#include <stratosphere/htc.hpp> +#include <stratosphere/htcfs.hpp> +#include <stratosphere/htclow.hpp> +#include <stratosphere/htcs.hpp> +#include <stratosphere/i2c.hpp> +#include <stratosphere/init.hpp> +#include <stratosphere/kvdb.hpp> +#include <stratosphere/ldr.hpp> +#include <stratosphere/lr.hpp> +#include <stratosphere/lm.hpp> +#include <stratosphere/mitm.hpp> +#include <stratosphere/ncm.hpp> +#include <stratosphere/nim.hpp> +#include <stratosphere/ns.hpp> +#include <stratosphere/nsd.hpp> +#include <stratosphere/osdbg.hpp> +#include <stratosphere/patcher.hpp> +#include <stratosphere/pcv.hpp> +#include <stratosphere/pgl.hpp> +#include <stratosphere/pinmux.hpp> +#include <stratosphere/powctl.hpp> +#include <stratosphere/psc.hpp> +#include <stratosphere/pm.hpp> +#include <stratosphere/pwm.hpp> +#include <stratosphere/regulator.hpp> +#include <stratosphere/ro.hpp> +#include <stratosphere/settings.hpp> +#include <stratosphere/scs.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/sm.hpp> +#include <stratosphere/socket.hpp> +#include <stratosphere/spl.hpp> +#include <stratosphere/sprofile.hpp> +#include <stratosphere/time.hpp> +#include <stratosphere/tipc.hpp> +#include <stratosphere/tma.hpp> +#include <stratosphere/updater.hpp> +#include <stratosphere/usb.hpp> +#include <stratosphere/wec.hpp> + +/* Include FS last. */ +#include <stratosphere/fs.hpp> +#include <stratosphere/fssrv.hpp> +#include <stratosphere/fssystem.hpp> + +/* External modules that we're including. */ +#include <stratosphere/rapidjson.hpp> \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ams.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ams.hpp new file mode 100644 index 00000000..0187cf07 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ams.hpp @@ -0,0 +1,22 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/ams/ams_types.hpp> +#include <stratosphere/ams/ams_exosphere_api.hpp> +#include <stratosphere/ams/ams_emummc_api.hpp> +#include <stratosphere/ams/ams_environment.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ams/ams_emummc_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ams/ams_emummc_api.hpp new file mode 100644 index 00000000..96ee2a61 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ams/ams_emummc_api.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/ams/ams_types.hpp> + +namespace ams::emummc { + + /* Get whether emummc is active. */ + bool IsActive(); + + /* Get the active emummc id. */ + u32 GetActiveId(); + + /* Get Nintendo redirection path. */ + const char *GetNintendoDirPath(); + + /* Get Emummc folderpath, NULL if not file-based. */ + const char *GetFilePath(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ams/ams_environment.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ams/ams_environment.hpp new file mode 100644 index 00000000..71a5f886 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ams/ams_environment.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/ams/ams_types.hpp> + +namespace ams { + + /* Will be called by libstratosphere on crash. */ + #if defined(ATMOSPHERE_OS_HORIZON) + void CrashHandler(ThreadExceptionDump *ctx); + #endif + + /* API for boot sysmodule. */ + void InitializeForBoot(); + void SetInitialRebootPayload(const void *src, size_t src_size); + + void *Malloc(size_t size); + void Free(void *ptr); + + void *MallocForRapidJson(size_t size); + void *ReallocForRapidJson(void *ptr, size_t size); + void FreeForRapidJson(void *ptr); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ams/ams_exosphere_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ams/ams_exosphere_api.hpp new file mode 100644 index 00000000..2712b2b5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ams/ams_exosphere_api.hpp @@ -0,0 +1,56 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/ams/ams_types.hpp> + +namespace ams::exosphere { + + ApiInfo GetApiInfo(); + + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + void ForceRebootToRcm(); + void ForceRebootToIramPayload(); + void ForceRebootToFatalError(); + void ForceRebootByPmic(); + void ForceShutdown(); + + bool IsRcmBugPatched(); + + bool ShouldBlankProdInfo(); + bool ShouldAllowWritesToProdInfo(); + + u64 GetDeviceId(); + + void CopyToIram(uintptr_t iram_dst, const void *dram_src, size_t size); + void CopyFromIram(void *dram_dst, uintptr_t iram_src, size_t size); + #endif + +} + +namespace ams { + + /* Version checking utility. */ + inline void CheckApiVersion() { + const u32 runtime_version = exosphere::GetApiInfo().GetVersion(); + const u32 build_version = exosphere::GetVersion(ATMOSPHERE_RELEASE_VERSION); + + if (runtime_version < build_version) { + R_ABORT_UNLESS(exosphere::ResultVersionMismatch()); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ams/ams_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ams/ams_types.hpp new file mode 100644 index 00000000..af56fe74 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ams/ams_types.hpp @@ -0,0 +1,84 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf/sf_buffer_tags.hpp> +#include <stratosphere/hos.hpp> + +namespace ams::exosphere { + + using TargetFirmware = ams::TargetFirmware; + + constexpr ALWAYS_INLINE u32 GetVersion(u32 major, u32 minor, u32 micro) { + return (major << 16) | (minor << 8) | (micro); + } + + struct ApiInfo { + using TargetFirmwareVersion = util::BitPack64::Field<0, 32, TargetFirmware>; + using MasterKeyRevision = util::BitPack64::Field<TargetFirmwareVersion::Next, 8, u32>; + using MicroVersion = util::BitPack64::Field<MasterKeyRevision::Next, 8, u32>; + using MinorVersion = util::BitPack64::Field<MicroVersion::Next, 8, u32>; + using MajorVersion = util::BitPack64::Field<MinorVersion::Next, 8, u32>; + + util::BitPack64 value; + + constexpr ALWAYS_INLINE u32 GetVersion() const { + return ::ams::exosphere::GetVersion(this->GetMajorVersion(), this->GetMinorVersion(), this->GetMicroVersion()); + } + + constexpr ALWAYS_INLINE u32 GetMajorVersion() const { + return this->value.Get<MajorVersion>(); + } + + constexpr ALWAYS_INLINE u32 GetMinorVersion() const { + return this->value.Get<MinorVersion>(); + } + + constexpr ALWAYS_INLINE u32 GetMicroVersion() const { + return this->value.Get<MicroVersion>(); + } + + constexpr ALWAYS_INLINE TargetFirmware GetTargetFirmware() const { + return this->value.Get<TargetFirmwareVersion>(); + } + + constexpr ALWAYS_INLINE u32 GetMasterKeyRevision() const { + return this->value.Get<MasterKeyRevision>(); + } + }; + +} + +namespace ams { + + struct FatalErrorContext : ::ams::impl::FatalErrorContext, sf::LargeData, sf::PrefersMapAliasTransferMode {}; + + static_assert(sizeof(FatalErrorContext) == sizeof(::ams::impl::FatalErrorContext)); + +#ifdef ATMOSPHERE_GIT_BRANCH + NX_CONSTEXPR const char *GetGitBranch() { + return ATMOSPHERE_GIT_BRANCH; + } +#endif + +#ifdef ATMOSPHERE_GIT_REVISION + NX_CONSTEXPR const char *GetGitRevision() { + return ATMOSPHERE_GIT_REVISION; + } +#endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp new file mode 100644 index 00000000..ca1c5cb5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp @@ -0,0 +1,185 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::impl { + + struct SystemThreadDefinition { + s32 priority; + const char *name; + }; + + #define AMS_DEFINE_SYSTEM_THREAD(__AMS_THREAD_PRIORITY__, __AMS_MODULE__, __AMS_THREAD_NAME__) \ + constexpr inline const ::ams::impl::SystemThreadDefinition SystemThreadDefinition_##__AMS_MODULE__##_##__AMS_THREAD_NAME__ = { __AMS_THREAD_PRIORITY__, "ams." # __AMS_MODULE__ "." #__AMS_THREAD_NAME__ } + + /* sm. */ + AMS_DEFINE_SYSTEM_THREAD(-1, sm, Main); + AMS_DEFINE_SYSTEM_THREAD(-1, sm, DispatcherThread); + + /* spl. */ + AMS_DEFINE_SYSTEM_THREAD(-1, spl, Main); + + /* Loader. */ + AMS_DEFINE_SYSTEM_THREAD(21, ldr, Main); + + /* Process Manager. */ + AMS_DEFINE_SYSTEM_THREAD(21, pm, Main); + AMS_DEFINE_SYSTEM_THREAD(21, pm, ProcessTrack); + + + /* NCM. */ + AMS_DEFINE_SYSTEM_THREAD(21, ncm, MainWaitThreads); + AMS_DEFINE_SYSTEM_THREAD(21, ncm, ContentManagerServerIpcSession); + AMS_DEFINE_SYSTEM_THREAD(21, ncm, LocationResolverServerIpcSession); + + /* FS. */ + AMS_DEFINE_SYSTEM_THREAD(11, sdmmc, DeviceDetector); + AMS_DEFINE_SYSTEM_THREAD(16, fs, WorkerThreadPool); + AMS_DEFINE_SYSTEM_THREAD(17, fs, Main); + AMS_DEFINE_SYSTEM_THREAD(17, fs, WorkerRealTimeAccess); + AMS_DEFINE_SYSTEM_THREAD(18, fs, WorkerNormalPriorityAccess); + AMS_DEFINE_SYSTEM_THREAD(19, fs, WorkerLowPriorityAccess); + AMS_DEFINE_SYSTEM_THREAD(30, fs, WorkerBackgroundAccess); + AMS_DEFINE_SYSTEM_THREAD(30, fs, PatrolReader); + + /* Boot. */ + AMS_DEFINE_SYSTEM_THREAD(-1, boot, Main); + + /* Mitm. */ + AMS_DEFINE_SYSTEM_THREAD(-7, mitm, InitializeThread); + AMS_DEFINE_SYSTEM_THREAD(-1, mitm_sf, QueryServerProcessThread); + AMS_DEFINE_SYSTEM_THREAD(16, mitm_fs, RomFileSystemInitializeThread); + AMS_DEFINE_SYSTEM_THREAD(16, mitm_fs, RomFileSystemFinalizeThread); + AMS_DEFINE_SYSTEM_THREAD(21, mitm, DebugThrowThread); + AMS_DEFINE_SYSTEM_THREAD(21, mitm_sysupdater, IpcServer); + AMS_DEFINE_SYSTEM_THREAD(21, mitm_sysupdater, AsyncPrepareSdCardUpdateTask); + + /* boot2. */ + AMS_DEFINE_SYSTEM_THREAD(20, boot2, Main); + + /* LogManager. */ + AMS_DEFINE_SYSTEM_THREAD(10, LogManager, MainThread); + AMS_DEFINE_SYSTEM_THREAD(10, lm, IpcServer); + AMS_DEFINE_SYSTEM_THREAD(10, lm, Flush); + AMS_DEFINE_SYSTEM_THREAD(10, lm, HtcsConnection); + + /* dmnt. */ + AMS_DEFINE_SYSTEM_THREAD(-3, dmnt, MultiCoreEventManager); + AMS_DEFINE_SYSTEM_THREAD(-1, dmnt, CheatDebugEvents); + AMS_DEFINE_SYSTEM_THREAD(-1, dmnt, MultiCoreBP); + AMS_DEFINE_SYSTEM_THREAD(11, dmnt, Main); + AMS_DEFINE_SYSTEM_THREAD(11, dmnt, Ipc); + AMS_DEFINE_SYSTEM_THREAD(11, dmnt, CheatDetect); + AMS_DEFINE_SYSTEM_THREAD(20, dmnt, CheatVirtualMachine); + + /* fatal */ + AMS_DEFINE_SYSTEM_THREAD(-13, fatal, Main); + AMS_DEFINE_SYSTEM_THREAD(-13, fatalsrv, FatalTaskThread); + AMS_DEFINE_SYSTEM_THREAD( 9, fatalsrv, IpcDispatcher); + + /* creport. */ + AMS_DEFINE_SYSTEM_THREAD(16, creport, Main); + + /* ro. */ + AMS_DEFINE_SYSTEM_THREAD(21, ro, Main); + + /* gpio. */ + AMS_DEFINE_SYSTEM_THREAD(-12, gpio, InterruptHandler); + + /* bpc. */ + AMS_DEFINE_SYSTEM_THREAD(4, bpc, IpcServer); + + /* powctl. */ + AMS_DEFINE_SYSTEM_THREAD(9, powctl, InterruptHandler); + + /* hid. */ + AMS_DEFINE_SYSTEM_THREAD(-10, hid, IpcServer); + + /* ns.*/ + AMS_DEFINE_SYSTEM_THREAD(21, ns, ApplicationManagerIpcSession); + AMS_DEFINE_SYSTEM_THREAD(21, nssrv, AsyncPrepareCardUpdateTask); + + /* settings. */ + AMS_DEFINE_SYSTEM_THREAD(21, settings, Main); + AMS_DEFINE_SYSTEM_THREAD(21, settings, IpcServer); + AMS_DEFINE_SYSTEM_THREAD(21, settings, LazyWriter); + + /* erpt. */ + AMS_DEFINE_SYSTEM_THREAD(21, erpt, Main); + AMS_DEFINE_SYSTEM_THREAD(21, erpt, IpcServer); + + /* socket. */ + AMS_DEFINE_SYSTEM_THREAD(29, socket, ResolverIpcServer); + + /* jpegdec. */ + AMS_DEFINE_SYSTEM_THREAD(21, jpegdec, Main); + + /* pgl. */ + AMS_DEFINE_SYSTEM_THREAD(21, pgl, Main); + AMS_DEFINE_SYSTEM_THREAD(21, pgl, ProcessControlTask); + + /* htc. */ + AMS_DEFINE_SYSTEM_THREAD(10, htc, Main); + AMS_DEFINE_SYSTEM_THREAD(10, htc, HtcIpc); + AMS_DEFINE_SYSTEM_THREAD(10, htc, HtcsIpc); + AMS_DEFINE_SYSTEM_THREAD(10, htc, HtcsMonitor); + AMS_DEFINE_SYSTEM_THREAD(10, htc, HtcfsIpc); + AMS_DEFINE_SYSTEM_THREAD(10, htc, HtcfsMonitor); + AMS_DEFINE_SYSTEM_THREAD(10, htc, HtclowDiscovery); + AMS_DEFINE_SYSTEM_THREAD(10, htc, HtclowTcpServer); + AMS_DEFINE_SYSTEM_THREAD(10, htc, HtclowUsbIndication); + AMS_DEFINE_SYSTEM_THREAD(10, htc, HtclowListen); + AMS_DEFINE_SYSTEM_THREAD(10, htc, HtclowObserver); + AMS_DEFINE_SYSTEM_THREAD(10, htc, HtclowSend); + AMS_DEFINE_SYSTEM_THREAD(10, htc, HtclowReceive); + AMS_DEFINE_SYSTEM_THREAD(10, htc, Htcmisc); + AMS_DEFINE_SYSTEM_THREAD(10, htc, HtcmiscReceive); + AMS_DEFINE_SYSTEM_THREAD(10, htc, HtcmiscSend); + AMS_DEFINE_SYSTEM_THREAD(10, htc, HtcObserver); + + AMS_DEFINE_SYSTEM_THREAD(10, tma, BridgePcieDriver); + + /* cs/scs. */ + AMS_DEFINE_SYSTEM_THREAD(20, cs, Main); + AMS_DEFINE_SYSTEM_THREAD(20, cs, HidctlService); + AMS_DEFINE_SYSTEM_THREAD(20, cs, HidctlLegacyServer); + AMS_DEFINE_SYSTEM_THREAD(20, cs, AudioServer); + AMS_DEFINE_SYSTEM_THREAD(10, cs, GrcVideoSender); + AMS_DEFINE_SYSTEM_THREAD(10, cs, GrcVideoReader); + AMS_DEFINE_SYSTEM_THREAD(10, cs, GrcAudioSender); + AMS_DEFINE_SYSTEM_THREAD(10, cs, GrcAudioReader); + + AMS_DEFINE_SYSTEM_THREAD(21, scs, ShellServer); + AMS_DEFINE_SYSTEM_THREAD(21, scs, ShellEventHandler); + + /* DevServer/TioServer. */ + AMS_DEFINE_SYSTEM_THREAD(21, TioServer, Main); + AMS_DEFINE_SYSTEM_THREAD(21, TioServer, FileServerHtcsServer); + AMS_DEFINE_SYSTEM_THREAD(21, TioServer, SdCardObserver); + + AMS_DEFINE_SYSTEM_THREAD(16, memlet, Main); + + /* ServiceProfile */ + AMS_DEFINE_SYSTEM_THREAD(-1, sprofile, IpcServer); + + + #undef AMS_DEFINE_SYSTEM_THREAD + +} + +#define AMS_GET_SYSTEM_THREAD_PRIORITY(__AMS_MODULE__, __AMS_THREAD_NAME__) ( ::ams::impl::SystemThreadDefinition_##__AMS_MODULE__##_##__AMS_THREAD_NAME__ ).priority +#define AMS_GET_SYSTEM_THREAD_NAME(__AMS_MODULE__, __AMS_THREAD_NAME__) ( ::ams::impl::SystemThreadDefinition_##__AMS_MODULE__##_##__AMS_THREAD_NAME__ ).name diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/boot2.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/boot2.hpp new file mode 100644 index 00000000..e43c0d0b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/boot2.hpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/boot2/boot2_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/boot2/boot2_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/boot2/boot2_api.hpp new file mode 100644 index 00000000..5eeba5d6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/boot2/boot2_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::boot2 { + + /* Boot2 API. */ + + /* Normally invoked by PM. */ + void LaunchPreSdCardBootProgramsAndBoot2(); + + /* Normally invoked by boot2. */ + void LaunchPostSdCardBootPrograms(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cal.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cal.hpp new file mode 100644 index 00000000..6caef121 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cal.hpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/cal/cal_battery_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cal/cal_battery_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cal/cal_battery_api.hpp new file mode 100644 index 00000000..9a67d940 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cal/cal_battery_api.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::cal { + + Result GetBatteryVersion(u8 *out); + Result GetBatteryVendor(size_t *out_vendor_size, void *dst, size_t dst_size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/capsrv.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/capsrv.hpp new file mode 100644 index 00000000..437ef531 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/capsrv.hpp @@ -0,0 +1,22 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/capsrv/capsrv_screen_shot_decode_option.hpp> +#include <stratosphere/capsrv/server/capsrv_server_config.hpp> +#include <stratosphere/capsrv/server/capsrv_server_decoder_api.hpp> +#include <stratosphere/capsrv/capsrv_screen_shot_control_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/capsrv/capsrv_screen_shot_control_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/capsrv/capsrv_screen_shot_control_api.hpp new file mode 100644 index 00000000..696406ac --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/capsrv/capsrv_screen_shot_control_api.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/vi/vi_layer_stack.hpp> + +namespace ams::capsrv { + + constexpr inline s32 DefaultCaptureTimeoutMilliSeconds = 100; + + Result InitializeScreenShotControl(); + void FinalizeScreenShotControl(); + + Result OpenRawScreenShotReadStreamForDevelop(size_t *out_data_size, s32 *out_width, s32 *out_height, vi::LayerStack layer_stack, TimeSpan timeout); + Result ReadRawScreenShotReadStreamForDevelop(size_t *out_read_size, void *dst, size_t dst_size, std::ptrdiff_t offset); + void CloseRawScreenShotReadStreamForDevelop(); + + Result CaptureJpegScreenshot(u64 *out_size, void *dst, size_t dst_size, vi::LayerStack layer_stack, TimeSpan timeout); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/capsrv/capsrv_screen_shot_decode_option.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/capsrv/capsrv_screen_shot_decode_option.hpp new file mode 100644 index 00000000..6102811c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/capsrv/capsrv_screen_shot_decode_option.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::capsrv { + + enum ScreenShotDecoderFlag : u64 { + ScreenShotDecoderFlag_None = (0 << 0), + ScreenShotDecoderFlag_EnableFancyUpsampling = (1 << 0), + ScreenShotDecoderFlag_EnableBlockSmoothing = (1 << 1), + }; + + using ScreenShotJpegDecoderFlagType = typename std::underlying_type<ScreenShotDecoderFlag>::type; + + struct ScreenShotDecodeOption { + ScreenShotJpegDecoderFlagType flags; + u8 reserved[0x20 - sizeof(ScreenShotJpegDecoderFlagType)]; + + static constexpr ScreenShotDecodeOption GetDefaultOption() { + return ScreenShotDecodeOption{}; + } + + constexpr bool HasJpegDecoderFlag(ScreenShotJpegDecoderFlagType flag) const { + return (this->flags & flag) != 0; + } + }; + static_assert(sizeof(ScreenShotDecodeOption) == 0x20); + static_assert(util::is_pod<ScreenShotDecodeOption>::value); + + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(sizeof(ScreenShotDecodeOption) == sizeof(::CapsScreenShotDecodeOption)); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/capsrv/server/capsrv_server_config.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/capsrv/server/capsrv_server_config.hpp new file mode 100644 index 00000000..d34210bc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/capsrv/server/capsrv_server_config.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::capsrv::server { + + constexpr inline int ScreenShotWidth = 1280; + constexpr inline int ScreenShotHeight = 720; + + constexpr inline int MovieWidth = 1280; + constexpr inline int MovieHeight = 720; + + constexpr inline size_t SoftwareJpegDecoderWorkMemorySize = 16_KB; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/capsrv/server/capsrv_server_decoder_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/capsrv/server/capsrv_server_decoder_api.hpp new file mode 100644 index 00000000..135aac04 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/capsrv/server/capsrv_server_decoder_api.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::capsrv::server { + + Result InitializeForDecoderServer(); + void FinalizeForDecoderServer(); + + void DecoderControlServerThreadFunction(void *); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cfg.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cfg.hpp new file mode 100644 index 00000000..3f9f5db4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cfg.hpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/cfg/cfg_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cfg/cfg_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cfg/cfg_api.hpp new file mode 100644 index 00000000..c2e03a51 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cfg/cfg_api.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/cfg/cfg_types.hpp> +#include <stratosphere/cfg/cfg_locale_types.hpp> +#include <stratosphere/sm/sm_types.hpp> + +namespace ams::cfg { + + /* SD card configuration. */ + bool IsSdCardRequiredServicesReady(); + void WaitSdCardRequiredServicesReady(); + bool IsSdCardInitialized(); + void WaitSdCardInitialized(); + + /* Override key utilities. */ + OverrideStatus CaptureOverrideStatus(ncm::ProgramId program_id); + + /* Locale utilities. */ + OverrideLocale GetOverrideLocale(ncm::ProgramId program_id); + + /* Flag utilities. */ + bool HasFlag(const sm::MitmProcessInfo &process_info, const char *flag); + bool HasContentSpecificFlag(ncm::ProgramId program_id, const char *flag); + bool HasGlobalFlag(const char *flag); + Result DeleteGlobalFlag(const char *flag); + + /* HBL Configuration utilities. */ + bool HasHblFlag(const char *flag); + const char *GetHblPath(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cfg/cfg_locale_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cfg/cfg_locale_types.hpp new file mode 100644 index 00000000..aa24ab52 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cfg/cfg_locale_types.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/cfg/cfg_types.hpp> +#include <stratosphere/settings/settings_types.hpp> + +namespace ams::cfg { + + struct OverrideLocale { + settings::LanguageCode language_code; + settings::RegionCode region_code; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cfg/cfg_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cfg/cfg_types.hpp new file mode 100644 index 00000000..dd898a96 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cfg/cfg_types.hpp @@ -0,0 +1,79 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> + +namespace ams::cfg { + + namespace impl { + + enum OverrideStatusFlag : u64 { + OverrideStatusFlag_Hbl = (1u << 0), + OverrideStatusFlag_ProgramSpecific = (1u << 1), + OverrideStatusFlag_CheatEnabled = (1u << 2), + + OverrideStatusFlag_AddressSpaceShift = 3, + OverrideStatusFlag_AddressSpaceMask = ((1u << 2) - 1) << OverrideStatusFlag_AddressSpaceShift, + + #if defined(ATMOSPHERE_OS_HORIZON) + OverrideStatusFlag_AddressSpace32Bit = (svc::CreateProcessFlag_AddressSpace32Bit >> svc::CreateProcessFlag_AddressSpaceShift) << OverrideStatusFlag_AddressSpaceShift, + OverrideStatusFlag_AddressSpace64BitDeprecated = (svc::CreateProcessFlag_AddressSpace64BitDeprecated >> svc::CreateProcessFlag_AddressSpaceShift) << OverrideStatusFlag_AddressSpaceShift, + OverrideStatusFlag_AddressSpace32BitWithoutAlias = (svc::CreateProcessFlag_AddressSpace32BitWithoutAlias >> svc::CreateProcessFlag_AddressSpaceShift) << OverrideStatusFlag_AddressSpaceShift, + OverrideStatusFlag_AddressSpace64Bit = (svc::CreateProcessFlag_AddressSpace64Bit >> svc::CreateProcessFlag_AddressSpaceShift) << OverrideStatusFlag_AddressSpaceShift, + #endif + }; + + } + + struct OverrideStatus { + u64 keys_held; + u64 flags; + + constexpr inline u64 GetKeysHeld() const { return this->keys_held; } + + #define DEFINE_FLAG_ACCESSORS(flag) \ + constexpr inline bool Is##flag() const { return this->flags & impl::OverrideStatusFlag_##flag; } \ + constexpr inline void Set##flag() { this->flags |= impl::OverrideStatusFlag_##flag; } \ + constexpr inline void Clear##flag() { this->flags &= ~u64(impl::OverrideStatusFlag_##flag); } + + DEFINE_FLAG_ACCESSORS(Hbl) + DEFINE_FLAG_ACCESSORS(ProgramSpecific) + DEFINE_FLAG_ACCESSORS(CheatEnabled) + + #undef DEFINE_FLAG_ACCESSORS + + constexpr inline u64 GetOverrideAddressSpaceFlags() const { return this->flags & impl::OverrideStatusFlag_AddressSpaceMask; } + constexpr inline bool HasOverrideAddressSpace() const { return this->IsHbl() && this->GetOverrideAddressSpaceFlags() != 0; } + }; + + static_assert(sizeof(OverrideStatus) == 0x10, "sizeof(OverrideStatus)"); + static_assert(util::is_pod<OverrideStatus>::value, "util::is_pod<OverrideStatus>::value"); + + constexpr inline bool operator==(const OverrideStatus &lhs, const OverrideStatus &rhs) { + if (std::is_constant_evaluated()) { + return lhs.keys_held == rhs.keys_held && lhs.flags == rhs.flags; + } else { + return std::memcmp(std::addressof(lhs), std::addressof(rhs), sizeof(lhs)) == 0; + } + } + + constexpr inline bool operator!=(const OverrideStatus &lhs, const OverrideStatus &rhs) { + return !(lhs == rhs); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/clkrst.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/clkrst.hpp new file mode 100644 index 00000000..76692b5a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/clkrst.hpp @@ -0,0 +1,21 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/clkrst/clkrst_types.hpp> +#include <stratosphere/clkrst/clkrst_api.hpp> +#include <stratosphere/clkrst/clkrst_session_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/clkrst/clkrst_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/clkrst/clkrst_api.hpp new file mode 100644 index 00000000..e3a240f0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/clkrst/clkrst_api.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/clkrst/clkrst_types.hpp> + +namespace ams::clkrst { + + void Initialize(); + void Finalize(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/clkrst/clkrst_session_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/clkrst/clkrst_session_api.hpp new file mode 100644 index 00000000..74bc449a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/clkrst/clkrst_session_api.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/clkrst/clkrst_types.hpp> + +namespace ams::clkrst { + + struct ClkRstSession { + void *_session; + }; + + Result OpenSession(ClkRstSession *out, DeviceCode device_code); + void CloseSession(ClkRstSession *session); + + void SetResetAsserted(ClkRstSession *session); + void SetResetDeasserted(ClkRstSession *session); + + void SetClockRate(ClkRstSession *session, u32 hz); + + void SetClockDisabled(ClkRstSession *session); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/clkrst/clkrst_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/clkrst/clkrst_types.hpp new file mode 100644 index 00000000..53b4d992 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/clkrst/clkrst_types.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::clkrst { + + /* ... */ + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cs.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cs.hpp new file mode 100644 index 00000000..7bf5d870 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cs.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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <stratosphere/cs/cs_audio_server.hpp> +#include <stratosphere/cs/cs_hid_server.hpp> +#include <stratosphere/cs/cs_remote_video_server.hpp> +#include <stratosphere/cs/cs_target_io_server.hpp> + +#include <stratosphere/cs/cs_command_processor.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cs/cs_audio_server.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cs/cs_audio_server.hpp new file mode 100644 index 00000000..a4352056 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cs/cs_audio_server.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::cs { + + void InitializeAudioServer(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cs/cs_command_processor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cs/cs_command_processor.hpp new file mode 100644 index 00000000..a609f70b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cs/cs_command_processor.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/scs/scs_command_processor.hpp> +#include <stratosphere/vi/vi_layer_stack.hpp> + +namespace ams::cs { + + using CommandHeader = scs::CommandHeader; + using ResponseHeader = scs::ResponseHeader; + + class CommandProcessor : public scs::CommandProcessor { + public: + virtual bool ProcessCommand(const CommandHeader &header, const u8 *body, s32 socket) override; + private: + void TakeScreenShot(const CommandHeader &header, s32 socket, vi::LayerStack layer_stack); + private: + static void SendFirmwareVersion(s32 socket, const CommandHeader &header); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cs/cs_hid_server.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cs/cs_hid_server.hpp new file mode 100644 index 00000000..bb7a528d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cs/cs_hid_server.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::cs { + + void InitializeHidServer(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cs/cs_remote_video_server.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cs/cs_remote_video_server.hpp new file mode 100644 index 00000000..f518bc0c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cs/cs_remote_video_server.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::cs { + + void InitializeRemoteVideoServer(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cs/cs_target_io_server.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cs/cs_target_io_server.hpp new file mode 100644 index 00000000..2ab2a0ac --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/cs/cs_target_io_server.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::cs { + + void InitializeTargetIoServer(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd.hpp new file mode 100644 index 00000000..a1eb4686 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd.hpp @@ -0,0 +1,22 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/dd/dd_types.hpp> +#include <stratosphere/dd/dd_device_address_space.hpp> +#include <stratosphere/dd/dd_io_mappings.hpp> +#include <stratosphere/dd/dd_process_handle.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_device_address_space.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_device_address_space.hpp new file mode 100644 index 00000000..77cd14cf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_device_address_space.hpp @@ -0,0 +1,21 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +#include <stratosphere/dd/dd_device_address_space_common.hpp> +#include <stratosphere/dd/dd_device_address_space_types.hpp> +#include <stratosphere/dd/dd_device_address_space_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_device_address_space_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_device_address_space_api.hpp new file mode 100644 index 00000000..814c66c0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_device_address_space_api.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/dd/dd_types.hpp> +#include <stratosphere/dd/dd_device_address_space_common.hpp> +#include <stratosphere/dd/dd_device_address_space_types.hpp> + +namespace ams::dd { + + Result CreateDeviceAddressSpace(DeviceAddressSpaceType *das, u64 address, u64 size); + Result CreateDeviceAddressSpace(DeviceAddressSpaceType *das, u64 size); + void DestroyDeviceAddressSpace(DeviceAddressSpaceType *das); + + void AttachDeviceAddressSpaceHandle(DeviceAddressSpaceType *das, DeviceAddressSpaceHandle handle, bool managed); + + DeviceAddressSpaceHandle GetDeviceAddressSpaceHandle(DeviceAddressSpaceType *das); + + Result MapDeviceAddressSpaceAligned(DeviceAddressSpaceType *das, ProcessHandle process_handle, u64 process_address, size_t size, DeviceVirtualAddress device_address, MemoryPermission device_perm); + Result MapDeviceAddressSpaceNotAligned(DeviceAddressSpaceType *das, ProcessHandle process_handle, u64 process_address, size_t size, DeviceVirtualAddress device_address, MemoryPermission device_perm); + void UnmapDeviceAddressSpace(DeviceAddressSpaceType *das, ProcessHandle process_handle, u64 process_address, size_t size, DeviceVirtualAddress device_address); + + Result AttachDeviceAddressSpace(DeviceAddressSpaceType *das, DeviceName device_name); + void DetachDeviceAddressSpace(DeviceAddressSpaceType *das, DeviceName device_name); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_device_address_space_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_device_address_space_common.hpp new file mode 100644 index 00000000..69332a81 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_device_address_space_common.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/dd/dd_types.hpp> + +namespace ams::dd { + + #if defined(ATMOSPHERE_OS_HORIZON) + using DeviceName = ::ams::svc::DeviceName; + using enum ::ams::svc::DeviceName; + #else + enum DeviceName { }; + #endif + + constexpr inline u64 DeviceAddressSpaceMemoryRegionAlignment = 4_KB; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_device_address_space_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_device_address_space_types.hpp new file mode 100644 index 00000000..40887f18 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_device_address_space_types.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/dd/dd_types.hpp> + +namespace ams::dd { + + using DeviceVirtualAddress = u64; + + using DeviceAddressSpaceHandle = os::NativeHandle; + + struct DeviceAddressSpaceType { + enum State { + State_NotInitialized = 0, + State_Initialized = 1, + }; + DeviceAddressSpaceHandle device_handle; + u8 state; + bool is_handle_managed; + }; + static_assert(std::is_trivial<DeviceAddressSpaceType>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_io_mappings.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_io_mappings.hpp new file mode 100644 index 00000000..41767506 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_io_mappings.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::dd { + + /* Convenience Helper. */ + inline uintptr_t GetIoMapping(dd::PhysicalAddress phys_addr, size_t size) { + const uintptr_t io_mapping = dd::QueryIoMapping(phys_addr, size); + AMS_ABORT_UNLESS(io_mapping); + return io_mapping; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_process_handle.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_process_handle.hpp new file mode 100644 index 00000000..cb197b01 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_process_handle.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/dd/dd_types.hpp> + +namespace ams::dd { + + ProcessHandle GetCurrentProcessHandle(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_types.hpp new file mode 100644 index 00000000..b1507485 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dd/dd_types.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> + +namespace ams::dd { + + using ProcessHandle = os::NativeHandle; + + using MemoryPermission = os::MemoryPermission; + using enum os::MemoryPermission; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf.hpp new file mode 100644 index 00000000..89bddbab --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <stratosphere/ddsf/ddsf_types.hpp> +#include <stratosphere/ddsf/ddsf_i_castable.hpp> +#include <stratosphere/ddsf/ddsf_i_session.hpp> +#include <stratosphere/ddsf/ddsf_i_device.hpp> +#include <stratosphere/ddsf/ddsf_i_driver.hpp> +#include <stratosphere/ddsf/ddsf_device_code_entry.hpp> +#include <stratosphere/ddsf/ddsf_device_code_entry_manager.hpp> +#include <stratosphere/ddsf/ddsf_i_event_handler.hpp> +#include <stratosphere/ddsf/ddsf_event_handler_manager.hpp> +#include <stratosphere/ddsf/ddsf_memory_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_device_code_entry.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_device_code_entry.hpp new file mode 100644 index 00000000..df82b644 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_device_code_entry.hpp @@ -0,0 +1,109 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::ddsf { + + class IDevice; + + class DeviceCodeEntry { + NON_COPYABLE(DeviceCodeEntry); + NON_MOVEABLE(DeviceCodeEntry); + private: + ams::DeviceCode m_device_code = ams::InvalidDeviceCode; + IDevice *m_device = nullptr; + public: + constexpr DeviceCodeEntry(ams::DeviceCode dc, IDevice *dev) : m_device_code(dc), m_device(dev) { + AMS_ASSERT(dev != nullptr); + } + + constexpr ams::DeviceCode GetDeviceCode() const { + return m_device_code; + } + + constexpr IDevice &GetDevice() { + return *m_device; + } + + constexpr const IDevice &GetDevice() const { + return *m_device; + } + }; + + class DeviceCodeEntryHolder { + NON_COPYABLE(DeviceCodeEntryHolder); + NON_MOVEABLE(DeviceCodeEntryHolder); + private: + util::IntrusiveListNode m_list_node; + util::TypedStorage<DeviceCodeEntry> m_entry_storage; + bool m_is_constructed; + public: + using ListTraits = util::IntrusiveListMemberTraits<&DeviceCodeEntryHolder::m_list_node>; + using List = typename ListTraits::ListType; + friend class util::IntrusiveList<DeviceCodeEntryHolder, util::IntrusiveListMemberTraits<&DeviceCodeEntryHolder::m_list_node>>; + public: + DeviceCodeEntryHolder() : m_list_node(), m_entry_storage(), m_is_constructed(false) { + /* ... */ + } + + ~DeviceCodeEntryHolder() { + if (this->IsConstructed()) { + this->Destroy(); + } + } + + void AddTo(List &list) { + list.push_back(*this); + } + + void RemoveFrom(List list) { + list.erase(list.iterator_to(*this)); + } + + bool IsLinkedToList() const { + return m_list_node.IsLinked(); + } + + DeviceCodeEntry &Construct(DeviceCode dc, IDevice *dev) { + AMS_ASSERT(!this->IsConstructed()); + DeviceCodeEntry *entry = util::ConstructAt(m_entry_storage, dc, dev); + m_is_constructed = true; + return *entry; + } + + bool IsConstructed() const { + return m_is_constructed; + } + + void Destroy() { + AMS_ASSERT(this->IsConstructed()); + util::DestroyAt(m_entry_storage); + m_is_constructed = false; + } + + DeviceCodeEntry &Get() { + AMS_ASSERT(this->IsConstructed()); + return GetReference(m_entry_storage); + } + + const DeviceCodeEntry &Get() const { + AMS_ASSERT(this->IsConstructed()); + return GetReference(m_entry_storage); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_device_code_entry_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_device_code_entry_manager.hpp new file mode 100644 index 00000000..204f952c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_device_code_entry_manager.hpp @@ -0,0 +1,84 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/ddsf/ddsf_types.hpp> +#include <stratosphere/ddsf/impl/ddsf_for_each.hpp> +#include <stratosphere/ddsf/ddsf_device_code_entry.hpp> + +namespace ams::ddsf { + + class IDevice; + + class DeviceCodeEntryManager { + private: + ams::MemoryResource *m_memory_resource; + ddsf::DeviceCodeEntryHolder::List m_entry_list; + mutable os::SdkMutex m_entry_list_lock; + private: + void DestroyAllEntries() { + auto it = m_entry_list.begin(); + while (it != m_entry_list.end()) { + ddsf::DeviceCodeEntryHolder *entry = std::addressof(*it); + it = m_entry_list.erase(it); + + AMS_ASSERT(entry->IsConstructed()); + if (entry->IsConstructed()) { + entry->Destroy(); + } + + m_memory_resource->Deallocate(entry, sizeof(*entry)); + } + } + public: + DeviceCodeEntryManager(ams::MemoryResource *mr) : m_memory_resource(mr), m_entry_list(), m_entry_list_lock() { /* ... */ } + + ~DeviceCodeEntryManager() { + this->DestroyAllEntries(); + } + + void Reset() { + std::scoped_lock lk(m_entry_list_lock); + this->DestroyAllEntries(); + } + + Result Add(DeviceCode device_code, IDevice *device); + bool Remove(DeviceCode device_code); + + Result FindDeviceCodeEntry(DeviceCodeEntry **out, DeviceCode device_code); + Result FindDeviceCodeEntry(const DeviceCodeEntry **out, DeviceCode device_code) const; + + Result FindDevice(IDevice **out, DeviceCode device_code); + Result FindDevice(const IDevice **out, DeviceCode device_code) const; + + template<typename F> + int ForEachEntry(F f) { + return impl::ForEach(m_entry_list_lock, m_entry_list, [&](DeviceCodeEntryHolder &holder) -> bool { + AMS_ASSERT(holder.IsConstructed()); + return f(holder.Get()); + }); + } + + template<typename F> + int ForEachEntry(F f) const { + return impl::ForEach(m_entry_list_lock, m_entry_list, [&](const DeviceCodeEntryHolder &holder) -> bool { + AMS_ASSERT(holder.IsConstructed()); + return f(holder.Get()); + }); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_event_handler_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_event_handler_manager.hpp new file mode 100644 index 00000000..6ecac998 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_event_handler_manager.hpp @@ -0,0 +1,80 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/ddsf/ddsf_i_event_handler.hpp> + +namespace ams::ddsf { + + class EventHandlerManager; + + class EventHandlerManager { + NON_COPYABLE(EventHandlerManager); + NON_MOVEABLE(EventHandlerManager); + private: + struct LoopControlCommandParameters; + private: + bool m_is_initialized; + bool m_is_looping; + os::SdkConditionVariable m_is_looping_cv; + os::MultiWaitType m_multi_wait; + os::ThreadType *m_loop_thread; + os::Event m_loop_control_event; + os::MultiWaitHolderType m_loop_control_event_holder; + LoopControlCommandParameters *m_loop_control_command_params; + os::LightEvent m_loop_control_command_done_event; + os::SdkMutex m_loop_control_lock; + private: + void ProcessControlCommand(LoopControlCommandParameters *params); + void ProcessControlCommandImpl(LoopControlCommandParameters *params); + public: + EventHandlerManager() + : m_is_initialized(false), m_is_looping(false), m_is_looping_cv(), m_multi_wait(), + m_loop_thread(), m_loop_control_event(os::EventClearMode_AutoClear), m_loop_control_event_holder(), + m_loop_control_command_params(), m_loop_control_command_done_event(os::EventClearMode_AutoClear), + m_loop_control_lock() + { + this->Initialize(); + } + + ~EventHandlerManager() { + if (m_is_looping) { + AMS_ASSERT(!this->IsRunningOnLoopThread()); + this->RequestStop(); + } + if (m_is_initialized) { + this->Finalize(); + } + } + + bool IsRunningOnLoopThread() const { return m_loop_thread == os::GetCurrentThread(); } + bool IsLooping() const { return m_is_looping; } + + void Initialize(); + void Finalize(); + + void RegisterHandler(IEventHandler *handler); + void UnregisterHandler(IEventHandler *handler); + + void WaitLoopEnter(); + void WaitLoopExit(); + void RequestStop(); + + void LoopAuto(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_i_castable.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_i_castable.hpp new file mode 100644 index 00000000..cc97e37a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_i_castable.hpp @@ -0,0 +1,88 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/ddsf/ddsf_types.hpp> +#include <stratosphere/ddsf/impl/ddsf_type_tag.hpp> + +namespace ams::ddsf { + + #if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING) + + #define AMS_DDSF_CASTABLE_TRAITS(__CLASS__, __BASE__) \ + static_assert(std::convertible_to<__CLASS__ *, __BASE__ *>); \ + public: \ + static constexpr inline ::ams::ddsf::impl::TypeTag s_ams_ddsf_castable_type_tag{#__CLASS__, __BASE__::s_ams_ddsf_castable_type_tag}; \ + constexpr virtual const ::ams::ddsf::impl::TypeTag &GetTypeTag() const override { return s_ams_ddsf_castable_type_tag; } + + #else + + #define AMS_DDSF_CASTABLE_TRAITS(__CLASS__, __BASE__) \ + static_assert(std::convertible_to<__CLASS__ *, __BASE__ *>); \ + public: \ + static constexpr inline ::ams::ddsf::impl::TypeTag s_ams_ddsf_castable_type_tag{__BASE__::s_ams_ddsf_castable_type_tag}; \ + constexpr virtual const ::ams::ddsf::impl::TypeTag &GetTypeTag() const override { return s_ams_ddsf_castable_type_tag; } + + #endif + + class ICastable { + private: + constexpr virtual const impl::TypeTag &GetTypeTag() const = 0; + + template<typename T> + constexpr ALWAYS_INLINE void AssertCastableTo() const { + AMS_ASSERT(this->IsCastableTo<T>()); + } + public: + template<typename T> + constexpr bool IsCastableTo() const { + return this->GetTypeTag().DerivesFrom(T::s_ams_ddsf_castable_type_tag); + } + + template<typename T> + constexpr T &SafeCastTo() { + this->AssertCastableTo<T>(); + return static_cast<T &>(*this); + } + + template<typename T> + constexpr const T &SafeCastTo() const { + this->AssertCastableTo<T>(); + return static_cast<const T &>(*this); + } + + template<typename T> + constexpr T *SafeCastToPointer() { + this->AssertCastableTo<T>(); + return static_cast<T *>(this); + } + + template<typename T> + constexpr const T *SafeCastToPointer() const { + this->AssertCastableTo<T>(); + return static_cast<const T *>(this); + } + + #if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING) + + constexpr const char *GetClassName() const { + return this->GetTypeTag().GetClassName(); + } + + #endif + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_i_device.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_i_device.hpp new file mode 100644 index 00000000..045aeaea --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_i_device.hpp @@ -0,0 +1,139 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_sdk_mutex.hpp> +#include <stratosphere/ddsf/ddsf_types.hpp> +#include <stratosphere/ddsf/impl/ddsf_for_each.hpp> +#include <stratosphere/ddsf/ddsf_i_castable.hpp> +#include <stratosphere/ddsf/ddsf_i_session.hpp> + +namespace ams::ddsf { + + class IDriver; + + class IDevice : public ICastable { + friend Result OpenSession(IDevice *device, ISession *session, AccessMode mode); + friend void CloseSession(ISession *session); + friend class IDriver; + public: + AMS_DDSF_CASTABLE_ROOT_TRAITS(ams::ddsf::IDevice); + private: + util::IntrusiveListNode m_list_node; + IDriver *m_driver; + ISession::List m_session_list; + mutable os::SdkMutex m_session_list_lock; + bool m_is_exclusive_write; + public: + using ListTraits = util::IntrusiveListMemberTraits<&IDevice::m_list_node>; + using List = typename ListTraits::ListType; + friend class util::IntrusiveList<IDevice, util::IntrusiveListMemberTraits<&IDevice::m_list_node>>; + private: + Result AttachSession(ISession *session) { + AMS_ASSERT(session != nullptr); + std::scoped_lock lk(m_session_list_lock); + + /* Check if we're allowed to attach the session. */ + if (m_is_exclusive_write && session->CheckExclusiveWrite()) { + for (const auto &attached : m_session_list) { + R_UNLESS(!attached.CheckAccess(AccessMode_Write), ddsf::ResultAccessModeDenied()); + } + } + + /* Attach the session. */ + m_session_list.push_back(*session); + R_SUCCEED(); + } + + void DetachSession(ISession *session) { + AMS_ASSERT(session != nullptr); + std::scoped_lock lk(m_session_list_lock); + m_session_list.erase(m_session_list.iterator_to(*session)); + } + + void AttachDriver(IDriver *drv) { + AMS_ASSERT(drv != nullptr); + AMS_ASSERT(!this->IsDriverAttached()); + m_driver = drv; + AMS_ASSERT(this->IsDriverAttached()); + } + + void DetachDriver() { + AMS_ASSERT(this->IsDriverAttached()); + m_driver = nullptr; + AMS_ASSERT(!this->IsDriverAttached()); + } + public: + IDevice(bool exclusive_write) : m_list_node(), m_driver(nullptr), m_session_list(), m_session_list_lock(), m_is_exclusive_write(exclusive_write) { + m_session_list.clear(); + } + protected: + virtual ~IDevice() { + m_session_list.clear(); + } + public: + void AddTo(List &list) { + list.push_back(*this); + } + + void RemoveFrom(List list) { + list.erase(list.iterator_to(*this)); + } + + bool IsLinkedToList() const { + return m_list_node.IsLinked(); + } + + IDriver &GetDriver() { + AMS_ASSERT(this->IsDriverAttached()); + return *m_driver; + } + + const IDriver &GetDriver() const { + AMS_ASSERT(this->IsDriverAttached()); + return *m_driver; + } + + bool IsDriverAttached() const { + return m_driver != nullptr; + } + + template<typename F> + Result ForEachSession(F f, bool return_on_fail) { + R_RETURN(impl::ForEach(m_session_list_lock, m_session_list, f, return_on_fail)); + } + + template<typename F> + Result ForEachSession(F f, bool return_on_fail) const { + R_RETURN(impl::ForEach(m_session_list_lock, m_session_list, f, return_on_fail)); + } + + template<typename F> + int ForEachSession(F f) { + return impl::ForEach(m_session_list_lock, m_session_list, f); + } + + template<typename F> + int ForEachSession(F f) const { + return impl::ForEach(m_session_list_lock, m_session_list, f); + } + + bool HasAnyOpenSession() const { + return !m_session_list.empty(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_i_driver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_i_driver.hpp new file mode 100644 index 00000000..a48085d6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_i_driver.hpp @@ -0,0 +1,98 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_sdk_mutex.hpp> +#include <stratosphere/ddsf/ddsf_types.hpp> +#include <stratosphere/ddsf/impl/ddsf_for_each.hpp> +#include <stratosphere/ddsf/ddsf_i_castable.hpp> +#include <stratosphere/ddsf/ddsf_i_device.hpp> + +namespace ams::ddsf { + + class IDriver : public ICastable { + public: + AMS_DDSF_CASTABLE_ROOT_TRAITS(ams::ddsf::IDriver); + private: + util::IntrusiveListNode m_list_node; + IDevice::List m_device_list; + mutable os::SdkMutex m_device_list_lock; + public: + using ListTraits = util::IntrusiveListMemberTraits<&IDriver::m_list_node>; + using List = typename ListTraits::ListType; + friend class util::IntrusiveList<IDriver, util::IntrusiveListMemberTraits<&IDriver::m_list_node>>; + private: + public: + IDriver() : m_list_node(), m_device_list(), m_device_list_lock() { + m_device_list.clear(); + } + protected: + virtual ~IDriver() { + m_device_list.clear(); + } + public: + void AddTo(List &list) { + list.push_back(*this); + } + + void RemoveFrom(List list) { + list.erase(list.iterator_to(*this)); + } + + bool IsLinkedToList() const { + return m_list_node.IsLinked(); + } + + bool HasAnyDevice() const { + return !m_device_list.empty(); + } + + void RegisterDevice(IDevice *dev) { + AMS_ASSERT(dev != nullptr); + std::scoped_lock lk(m_device_list_lock); + dev->AttachDriver(this); + m_device_list.push_back(*dev); + } + + void UnregisterDevice(IDevice *dev) { + AMS_ASSERT(dev != nullptr); + std::scoped_lock lk(m_device_list_lock); + m_device_list.erase(m_device_list.iterator_to(*dev)); + dev->DetachDriver(); + } + + template<typename F> + Result ForEachDevice(F f, bool return_on_fail) { + R_RETURN(impl::ForEach(m_device_list_lock, m_device_list, f, return_on_fail)); + } + + template<typename F> + Result ForEachDevice(F f, bool return_on_fail) const { + R_RETURN(impl::ForEach(m_device_list_lock, m_device_list, f, return_on_fail)); + } + + template<typename F> + int ForEachDevice(F f) { + return impl::ForEach(m_device_list_lock, m_device_list, f); + } + + template<typename F> + int ForEachDevice(F f) const { + return impl::ForEach(m_device_list_lock, m_device_list, f); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_i_event_handler.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_i_event_handler.hpp new file mode 100644 index 00000000..e9d9634a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_i_event_handler.hpp @@ -0,0 +1,92 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> + +namespace ams::ddsf { + + class EventHandlerManager; + + class IEventHandler { + NON_COPYABLE(IEventHandler); + NON_MOVEABLE(IEventHandler); + friend class EventHandlerManager; + private: + os::MultiWaitHolderType m_holder; + uintptr_t m_user_data; + bool m_is_initialized; + bool m_is_registered; + private: + void Link(os::MultiWaitType *multi_wait) { + AMS_ASSERT(this->IsInitialized()); + AMS_ASSERT(!this->IsRegistered()); + AMS_ASSERT(multi_wait != nullptr); + os::LinkMultiWaitHolder(multi_wait, std::addressof(m_holder)); + } + + void Unlink() { + AMS_ASSERT(this->IsInitialized()); + AMS_ASSERT(this->IsRegistered()); + os::UnlinkMultiWaitHolder(std::addressof(m_holder)); + } + + static IEventHandler &ToEventHandler(os::MultiWaitHolderType *holder) { + AMS_ASSERT(holder != nullptr); + auto &event_handler = *reinterpret_cast<IEventHandler *>(os::GetMultiWaitHolderUserData(holder)); + AMS_ASSERT(event_handler.IsInitialized()); + return event_handler; + } + public: + IEventHandler() : m_holder(), m_user_data(0), m_is_initialized(false), m_is_registered(false) { /* ... */ } + + virtual ~IEventHandler() { + if (this->IsRegistered()) { + this->Unlink(); + } + if (this->IsInitialized()) { + this->Finalize(); + } + } + + bool IsInitialized() const { return m_is_initialized; } + bool IsRegistered() const { return m_is_registered; } + + uintptr_t GetUserData() const { return m_user_data; } + void SetUserData(uintptr_t d) { m_user_data = d; } + + template<typename T> + void Initialize(T *object) { + AMS_ASSERT(object != nullptr); + AMS_ASSERT(!this->IsInitialized()); + os::InitializeMultiWaitHolder(std::addressof(m_holder), object); + os::SetMultiWaitHolderUserData(std::addressof(m_holder), reinterpret_cast<uintptr_t>(this)); + m_is_initialized = true; + m_is_registered = false; + } + + void Finalize() { + AMS_ASSERT(this->IsInitialized()); + AMS_ASSERT(!this->IsRegistered()); + os::FinalizeMultiWaitHolder(std::addressof(m_holder)); + m_is_initialized = false; + m_is_registered = false; + } + protected: + virtual void HandleEvent() = 0; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_i_session.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_i_session.hpp new file mode 100644 index 00000000..a103aae8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_i_session.hpp @@ -0,0 +1,99 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/ddsf/ddsf_types.hpp> +#include <stratosphere/ddsf/impl/ddsf_for_each.hpp> +#include <stratosphere/ddsf/ddsf_i_castable.hpp> + +namespace ams::ddsf { + + class ISession; + class IDevice; + + Result OpenSession(IDevice *device, ISession *session, AccessMode access_mode); + void CloseSession(ISession *session); + + class ISession : public ICastable { + friend Result OpenSession(IDevice *device, ISession *session, AccessMode mode); + friend void CloseSession(ISession *session); + public: + AMS_DDSF_CASTABLE_ROOT_TRAITS(ams::ddsf::IDevice); + private: + util::IntrusiveListNode m_list_node; + IDevice *m_device; + AccessMode m_access_mode; + public: + using ListTraits = util::IntrusiveListMemberTraits<&ISession::m_list_node>; + using List = typename ListTraits::ListType; + friend class util::IntrusiveList<ISession, util::IntrusiveListMemberTraits<&ISession::m_list_node>>; + private: + void AttachDevice(IDevice *dev, AccessMode mode) { + AMS_ASSERT(dev != nullptr); + AMS_ASSERT(!this->IsOpen()); + m_device = dev; + m_access_mode = mode; + AMS_ASSERT(this->IsOpen()); + } + + void DetachDevice() { + /* AMS_ASSERT(this->IsOpen()); */ + m_device = nullptr; + m_access_mode = AccessMode_None; + AMS_ASSERT(!this->IsOpen()); + } + public: + ISession() : m_list_node(), m_device(nullptr), m_access_mode() { /* ... */ } + protected: + virtual ~ISession() { this->DetachDevice(); AMS_ASSERT(!this->IsOpen()); } + public: + void AddTo(List &list) { + list.push_back(*this); + } + + void RemoveFrom(List list) { + list.erase(list.iterator_to(*this)); + } + + bool IsLinkedToList() const { + return m_list_node.IsLinked(); + } + + IDevice &GetDevice() { + AMS_ASSERT(this->IsOpen()); + return *m_device; + } + + const IDevice &GetDevice() const { + AMS_ASSERT(this->IsOpen()); + return *m_device; + } + + bool IsOpen() const { + return m_device != nullptr; + } + + bool CheckAccess(AccessMode mode) const { + AMS_ASSERT(this->IsOpen()); + return ((~m_access_mode) & mode) == 0; + } + + bool CheckExclusiveWrite() const { + return this->CheckAccess(AccessMode_Write) && !this->CheckAccess(AccessMode_Shared); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_memory_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_memory_api.hpp new file mode 100644 index 00000000..4a82afec --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_memory_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/ddsf/ddsf_device_code_entry.hpp> + +namespace ams::ddsf { + + void SetMemoryResource(ams::MemoryResource *mr); + ams::MemoryResource *GetMemoryResource(); + + static constexpr size_t DeviceCodeEntryHolderSize = sizeof(DeviceCodeEntryHolder); + + void SetDeviceCodeEntryHolderMemoryResource(ams::MemoryResource *mr); + ams::MemoryResource *GetDeviceCodeEntryHolderMemoryResource(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_types.hpp new file mode 100644 index 00000000..25a30bfb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_types.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::ddsf { + + enum AccessMode { + AccessMode_None = (0u << 0), + AccessMode_Read = (1u << 0), + AccessMode_Write = (1u << 1), + AccessMode_Shared = (1u << 2), + + AccessMode_ReadWrite = AccessMode_Read | AccessMode_Write, + AccessMode_WriteShared = AccessMode_Write | AccessMode_Shared, + AccessMode_ReadWriteShared = AccessMode_Read | AccessMode_WriteShared, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/impl/ddsf_for_each.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/impl/ddsf_for_each.hpp new file mode 100644 index 00000000..1978d334 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/impl/ddsf_for_each.hpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::ddsf::impl { + + template<typename Lock, typename List, typename F> + inline Result ForEach(Lock &lock, List &list, F f, bool return_on_fail) { + std::scoped_lock lk(lock); + + Result result = ResultSuccess(); + for (auto && it : list) { + if (const auto cur_result = f(std::addressof(it)); R_FAILED(cur_result)) { + if (return_on_fail) { + R_RETURN(cur_result); + } else if (R_SUCCEEDED(result)) { + result = cur_result; + } + } + } + R_RETURN(result); + } + + template<typename List, typename F, typename Lock> + inline int ForEach(Lock &lock, List &list, F f) { + std::scoped_lock lk(lock); + + int success_count = 0; + for (auto && it : list) { + if (!f(it)) { + return success_count; + } + ++success_count; + } + return success_count; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/impl/ddsf_type_tag.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/impl/ddsf_type_tag.hpp new file mode 100644 index 00000000..b521f385 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ddsf/impl/ddsf_type_tag.hpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/ddsf/ddsf_types.hpp> + +namespace ams::ddsf::impl { + + #if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING) + + #define AMS_DDSF_CASTABLE_ROOT_TRAITS(__CLASS__) \ + public: \ + static constexpr inline ::ams::ddsf::impl::TypeTag s_ams_ddsf_castable_type_tag{#__CLASS__}; \ + constexpr virtual const ::ams::ddsf::impl::TypeTag &GetTypeTag() const override { return s_ams_ddsf_castable_type_tag; } + + #else + + #define AMS_DDSF_CASTABLE_ROOT_TRAITS(__CLASS__) \ + public: \ + static constexpr inline ::ams::ddsf::impl::TypeTag s_ams_ddsf_castable_type_tag{}; \ + constexpr virtual const ::ams::ddsf::impl::TypeTag &GetTypeTag() const override { return s_ams_ddsf_castable_type_tag; } + + #endif + + class TypeTag { + private: + const char * const m_class_name; + const TypeTag * const m_base; + public: + #if !(defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING)) + constexpr TypeTag() : m_class_name(nullptr), m_base(nullptr) { /* ... */} + constexpr TypeTag(const TypeTag &b) : m_class_name(nullptr), m_base(std::addressof(b)) { if (!std::is_constant_evaluated()) { AMS_ASSERT(this != m_base); } } + + constexpr TypeTag(const char *c) : m_class_name(nullptr), m_base(nullptr) { AMS_UNUSED(c); } + constexpr TypeTag(const char *c, const TypeTag &b) : m_class_name(nullptr), m_base(std::addressof(b)) { if (!std::is_constant_evaluated()) { AMS_UNUSED(c); AMS_ASSERT(this != m_base); } } + #else + constexpr TypeTag(const char *c) : m_class_name(c), m_base(nullptr) { /* ... */ } + constexpr TypeTag(const char *c, const TypeTag &b) : m_class_name(c), m_base(std::addressof(b)) { if (!std::is_constant_evaluated()) { AMS_ASSERT(this != m_base); } } + #endif + + constexpr const char * GetClassName() const { return m_class_name; } + + constexpr bool Is(const TypeTag &rhs) const { return this == std::addressof(rhs); } + + constexpr bool DerivesFrom(const TypeTag &rhs) const { + const TypeTag * cur = this; + while (cur != nullptr) { + if (cur == std::addressof(rhs)) { + return true; + } + cur = cur->m_base; + } + return false; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag.hpp new file mode 100644 index 00000000..a8f9c0e2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag.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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <stratosphere/diag/diag_log_types.hpp> +#include <stratosphere/diag/diag_log_observer.hpp> +#include <stratosphere/diag/impl/diag_impl_log.hpp> +#include <stratosphere/diag/diag_log.hpp> +#include <stratosphere/diag/diag_sdk_log.hpp> +#include <stratosphere/diag/diag_abort_observer.hpp> +#include <stratosphere/diag/diag_assertion_failure_handler.hpp> + +#include <stratosphere/diag/impl/diag_utf8_util.hpp> + +#include <stratosphere/diag/diag_backtrace.hpp> +#include <stratosphere/diag/diag_symbol.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_abort_observer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_abort_observer.hpp new file mode 100644 index 00000000..52a4605b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_abort_observer.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/diag/diag_log_types.hpp> + +namespace ams::diag { + + using AbortObserver = void (*)(const AbortInfo &); + + struct AbortObserverHolder { + AbortObserver observer; + AbortObserverHolder *next; + bool is_registered; + }; + + void InitializeAbortObserverHolder(AbortObserverHolder *holder, AbortObserver observer); + + void RegisterAbortObserver(AbortObserverHolder *holder); + void UnregisterAbortObserver(AbortObserverHolder *holder); + void EnableDefaultAbortObserver(bool en); + + struct SdkAbortInfo { + AbortInfo abort_info; + Result result; + const ::ams::os::UserExceptionInfo *exc_info; + }; + + using SdkAbortObserver = void (*)(const SdkAbortInfo &); + + struct SdkAbortObserverHolder { + SdkAbortObserver observer; + SdkAbortObserverHolder *next; + bool is_registered; + }; + + void InitializeSdkAbortObserverHolder(SdkAbortObserverHolder *holder, SdkAbortObserver observer); + + void RegisterSdkAbortObserver(SdkAbortObserverHolder *holder); + void UnregisterSdkAbortObserver(SdkAbortObserverHolder *holder); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_assertion_failure_handler.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_assertion_failure_handler.hpp new file mode 100644 index 00000000..b1466a18 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_assertion_failure_handler.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/diag/diag_log_types.hpp> + +namespace ams::diag { + + enum AssertionFailureOperation { + AssertionFailureOperation_Abort, + AssertionFailureOperation_Continue, + }; + + using AssertionFailureHandler = AssertionFailureOperation (*)(const AssertionInfo &info); + + void SetAssertionFailureHandler(AssertionFailureHandler handler); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_backtrace.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_backtrace.hpp new file mode 100644 index 00000000..de8bbe89 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_backtrace.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) + #include <stratosphere/diag/impl/diag_backtrace_impl.os.horizon.hpp> +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include <stratosphere/diag/impl/diag_backtrace_impl.os.windows.hpp> +#elif defined(ATMOSPHERE_OS_LINUX) + #include <stratosphere/diag/impl/diag_backtrace_impl.os.linux.hpp> +#elif defined(ATMOSPHERE_OS_MACOS) + #include <stratosphere/diag/impl/diag_backtrace_impl.os.macos.hpp> +#else + #error "Unknown OS for diag::Backtrace" +#endif + +namespace ams::diag { + + size_t GetBacktrace(uintptr_t *out, size_t out_size); + + #if defined(ATMOSPHERE_OS_HORIZON) + size_t GetBacktace(uintptr_t *out, size_t out_size, uintptr_t fp, uintptr_t sp, uintptr_t pc); + #endif + + class Backtrace { + private: + impl::Backtrace m_impl; + public: + NOINLINE Backtrace() { + m_impl.Initialize(); + m_impl.Step(); + } + + #if defined(ATMOSPHERE_OS_HORIZON) + Backtrace(uintptr_t fp, uintptr_t sp, uintptr_t pc) { + m_impl.Initialize(fp, sp, pc); + } + #endif + + bool Step() { return m_impl.Step(); } + + uintptr_t GetStackPointer() const { return m_impl.GetStackPointer(); } + uintptr_t GetReturnAddress() const { return m_impl.GetReturnAddress(); } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_log.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_log.hpp new file mode 100644 index 00000000..d51b986f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_log.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/diag/impl/diag_impl_structured_log.hpp> + +#if defined(AMS_IMPL_ENABLE_LOG) + +#define AMS_LOG(...) AMS_IMPL_STRUCTURED_LOG_IMPL("", ::ams::diag::LogSeverity_Info, 0, __VA_ARGS__) +#define AMS_VLOG(_FMT_, _VL_) AMS_IMPL_STRUCTURED_VLOG_IMPL("", ::ams::diag::LogSeverity_Info, 0, _FMT_, _VL_) +#define AMS_PUT(_MSG_, _ML_) AMS_IMPL_STRUCTURED_PUT_IMPL("", ::ams::diag::LogSeverity_Info, 0, _MSG_, _ML_) + +#define AMS_STRUCTURED_LOG(_MOD_, _SEV_, _VER_, ...) AMS_IMPL_STRUCTURED_LOG_IMPL(_MOD_, _SEV_, _VER_, __VA_ARGS__) +#define AMS_STRUCTURED_VLOG(_MOD_, _SEV_, _VER_, _FMT_, _VL_) AMS_IMPL_STRUCTURED_VLOG_IMPL(_MOD_, _SEV_, _VER_, _FMT_, _VL_) +#define AMS_STRUCTURED_PUT(_MOD_, _SEV_, _VER_, _MSG_, _ML_) AMS_IMPL_STRUCTURED_PUT_IMPL(_MOD_, _SEV_, _VER_, _MSG_, _ML_) + +#else + +#define AMS_LOG(...) do { /* ... */ } while (false) +#define AMS_VLOG(_FMT_, _VL_) do { /* ... */ } while (false) +#define AMS_PUT(_MSG_, _ML_) do { /* ... */ } while (false) + +#define AMS_STRUCTURED_LOG(_MOD_, _SEV_, _VER_, ...) do { /* ... */ } while (false) +#define AMS_STRUCTURED_VLOG(_MOD_, _SEV_, _VER_, _FMT_, _VL_) do { /* ... */ } while (false) +#define AMS_STRUCTURED_PUT(_MOD_, _SEV_, _VER_, _MSG_, _ML_) do { /* ... */ } while (false) + +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_log_observer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_log_observer.hpp new file mode 100644 index 00000000..26e344f6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_log_observer.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/diag/diag_log_types.hpp> + +namespace ams::diag { + + using LogObserver = void (*)(const LogMetaData &meta, const LogBody &body, void *arg); + + struct LogObserverHolder { + LogObserver log_observer; + LogObserverHolder *next; + bool is_registered; + void *arg; + }; + + constexpr inline void InitializeLogObserverHolder(LogObserverHolder *holder, LogObserver observer, void *arg) { + holder->log_observer = observer; + holder->next = nullptr; + holder->is_registered = false; + holder->arg = arg; + } + + void RegisterLogObserver(LogObserverHolder *holder); + void UnregisterLogObserver(LogObserverHolder *holder); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_log_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_log_types.hpp new file mode 100644 index 00000000..61c9d202 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_log_types.hpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::diag { + + enum LogSeverity { + LogSeverity_Trace = 0, + LogSeverity_Info = 1, + LogSeverity_Warn = 2, + LogSeverity_Error = 3, + LogSeverity_Fatal = 4, + }; + + struct SourceInfo { + int line_number; + const char *file_name; + const char *function_name; + }; + + struct LogMetaData { + SourceInfo source_info; + const char *module_name; + LogSeverity severity; + int verbosity; + bool use_default_locale_charset; + void *additional_data; + size_t additional_data_size; + }; + + struct LogBody { + const char *message; + size_t message_size; + bool is_head; + bool is_tail; + }; + + struct LogMessage { + const char *fmt; + std::va_list *vl; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_sdk_log.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_sdk_log.hpp new file mode 100644 index 00000000..fa3233b5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_sdk_log.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/diag/impl/diag_impl_structured_log.hpp> + +#if defined(AMS_IMPL_ENABLE_SDK_LOG) + +#define AMS_SDK_LOG(...) AMS_IMPL_STRUCTURED_LOG_IMPL("$", ::ams::diag::LogSeverity_Info, 0, __VA_ARGS__) +#define AMS_SDK_VLOG(_FMT_, _VL_) AMS_IMPL_STRUCTURED_VLOG_IMPL("$", ::ams::diag::LogSeverity_Info, 0, _FMT_, _VL_) +#define AMS_SDK_PUT(_MSG_, _ML_) AMS_IMPL_STRUCTURED_PUT_IMPL("$", ::ams::diag::LogSeverity_Info, 0, _MSG_, _ML_) + +#else + +#define AMS_SDK_LOG(...) do { /* ... */ } while (false) +#define AMS_SDK_VLOG(_FMT_, _VL_) do { /* ... */ } while (false) +#define AMS_SDK_PUT(_MSG_, _ML_) do { /* ... */ } while (false) + +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_symbol.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_symbol.hpp new file mode 100644 index 00000000..d4354094 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/diag_symbol.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +namespace ams::diag { + + uintptr_t GetSymbolName(char *dst, size_t dst_size, uintptr_t address); + size_t GetSymbolSize(uintptr_t address); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_backtrace_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_backtrace_impl.os.horizon.hpp new file mode 100644 index 00000000..1500b147 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_backtrace_impl.os.horizon.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::diag::impl { + + class Backtrace { + private: + static constexpr size_t MemoryInfoBufferSize = 5; + public: + struct StackInfo { + uintptr_t stack_top; + uintptr_t stack_bottom; + }; + private: + s64 m_memory_info_buffer[MemoryInfoBufferSize]{}; + StackInfo *m_current_stack_info = nullptr; + StackInfo m_exception_stack_info{}; + StackInfo m_normal_stack_info{}; + uintptr_t m_fp = 0; + uintptr_t m_prev_fp = 0; + uintptr_t m_lr = 0; + public: + Backtrace() = default; + + void Initialize(); + void Initialize(uintptr_t fp, uintptr_t sp, uintptr_t pc); + + bool Step(); + uintptr_t GetStackPointer() const; + uintptr_t GetReturnAddress() const; + private: + void SetStackInfo(uintptr_t fp, uintptr_t sp); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_backtrace_impl.os.linux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_backtrace_impl.os.linux.hpp new file mode 100644 index 00000000..23e1e40a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_backtrace_impl.os.linux.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::diag::impl { + + class Backtrace { + private: + static constexpr size_t BacktraceEntryCountMax = 0x80; + private: + void *m_backtrace_addresses[BacktraceEntryCountMax]; + size_t m_index = 0; + size_t m_size = 0; + public: + Backtrace() = default; + + void Initialize(); + + bool Step(); + uintptr_t GetStackPointer() const; + uintptr_t GetReturnAddress() const; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_backtrace_impl.os.macos.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_backtrace_impl.os.macos.hpp new file mode 100644 index 00000000..23e1e40a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_backtrace_impl.os.macos.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::diag::impl { + + class Backtrace { + private: + static constexpr size_t BacktraceEntryCountMax = 0x80; + private: + void *m_backtrace_addresses[BacktraceEntryCountMax]; + size_t m_index = 0; + size_t m_size = 0; + public: + Backtrace() = default; + + void Initialize(); + + bool Step(); + uintptr_t GetStackPointer() const; + uintptr_t GetReturnAddress() const; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_backtrace_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_backtrace_impl.os.windows.hpp new file mode 100644 index 00000000..23e1e40a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_backtrace_impl.os.windows.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::diag::impl { + + class Backtrace { + private: + static constexpr size_t BacktraceEntryCountMax = 0x80; + private: + void *m_backtrace_addresses[BacktraceEntryCountMax]; + size_t m_index = 0; + size_t m_size = 0; + public: + Backtrace() = default; + + void Initialize(); + + bool Step(); + uintptr_t GetStackPointer() const; + uintptr_t GetReturnAddress() const; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_build_config.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_build_config.hpp new file mode 100644 index 00000000..c1b91792 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_build_config.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +#if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING) + #define AMS_IMPL_ENABLE_SDK_LOG +#else + //#define AMS_IMPL_ENABLE_SDK_LOG +#endif + +#if defined(AMS_ENABLE_LOG) + #define AMS_IMPL_ENABLE_LOG + + #if defined(AMS_DISABLE_LOG) + #error "Incoherent log configuration" + #endif +#elif defined(AMS_DISABLE_LOG) + +#elif defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING) + #define AMS_IMPL_ENABLE_LOG +#else + //#define AMS_IMPL_ENABLE_LOG +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_log.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_log.hpp new file mode 100644 index 00000000..eee203cb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_log.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/diag/diag_log_types.hpp> + +namespace ams::diag::impl { + + void LogImpl(const LogMetaData &meta, const char *fmt, ...) __attribute__((format(printf, 2, 3))); + void VLogImpl(const LogMetaData &meta, const char *fmt, std::va_list vl); + void PutImpl(const LogMetaData &meta, const char *msg, size_t msg_size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_structured_log.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_structured_log.hpp new file mode 100644 index 00000000..d78ea314 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_structured_log.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/diag/impl/diag_impl_build_config.hpp> +#include <stratosphere/diag/impl/diag_impl_log.hpp> + +#if defined(AMS_IMPL_ENABLE_LOG) || defined(AMS_IMPL_ENABLE_SDK_LOG) + +#define AMS_IMPL_LOG_META_DATA(_MOD_, _SEV_, _VER_) \ + (::ams::diag::LogMetaData { \ + { __LINE__, __FILE__, AMS_CURRENT_FUNCTION_NAME }, \ + _MOD_, \ + _SEV_, \ + _VER_, \ + false, \ + nullptr, \ + 0, \ + }) + +#define AMS_IMPL_STRUCTURED_LOG_IMPL(_MOD_, _SEV_, _VER_, ...) ::ams::diag::impl::LogImpl(AMS_IMPL_LOG_META_DATA(_MOD_, _SEV_, _VER_), __VA_ARGS__) +#define AMS_IMPL_STRUCTURED_VLOG_IMPL(_MOD_, _SEV_, _VER_, _FMT_, _VL_) ::ams::diag::impl::VLogImpl(AMS_IMPL_LOG_META_DATA(_MOD_, _SEV_, _VER_), _FMT_, _VL_) +#define AMS_IMPL_STRUCTURED_PUT_IMPL(_MOD_, _SEV_, _VER_, _MSG_, _ML_) ::ams::diag::impl::PutImpl(AMS_IMPL_LOG_META_DATA(_MOD_, _SEV_, _VER_), _MSG_, _ML_) + +#else + +#define AMS_IMPL_STRUCTURED_LOG_IMPL(_MOD_, _SEV_, _VER_, ...) do { /* ... */ } while (false) +#define AMS_IMPL_STRUCTURED_VLOG_IMPL(_MOD_, _SEV_, _VER_, _FMT_, _VL_) do { /* ... */ } while (false) +#define AMS_IMPL_STRUCTURED_PUT_IMPL(_MOD_, _SEV_, _VER_, _MSG_, _ML_) do { /* ... */ } while (false) + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_structured_sdk_log.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_structured_sdk_log.hpp new file mode 100644 index 00000000..510d650b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_impl_structured_sdk_log.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/diag/impl/diag_impl_build_config.hpp> +#include <stratosphere/diag/impl/diag_impl_structured_log.hpp> + +#if defined(AMS_IMPL_ENABLE_SDK_LOG) + +#define AMS_IMPL_STRUCTURED_SDK_LOG(_MOD_, _SEV_, _VER_, ...) AMS_IMPL_STRUCTURED_LOG_IMPL("$" #_MOD_ , ::ams::diag::LogSeverity_##_SEV_, _VER_, __VA_ARGS__) +#define AMS_IMPL_STRUCTURED_SDK_VLOG(_MOD_, _SEV_, _VER_, _FMT_, _VL_) AMS_IMPL_STRUCTURED_VLOG_IMPL("$" #_MOD_ , ::ams::diag::LogSeverity_##_SEV_, _VER_, _FMT_, _VL_) +#define AMS_IMPL_STRUCTURED_SDK_PUT(_MOD_, _SEV_, _VER_, _MSG_, _ML_) AMS_IMPL_STRUCTURED_PUT_IMPL("$" #_MOD_ , ::ams::diag::LogSeverity_##_SEV_, _VER_, _MSG_, _ML_) + +#else + +#define AMS_IMPL_STRUCTURED_SDK_LOG(_MOD_, _SEV_, _VER_, ...) do { /* ... */ } while (false) +#define AMS_IMPL_STRUCTURED_SDK_VLOG(_MOD_, _SEV_, _VER_, _FMT_, _VL_) do { /* ... */ } while (false) +#define AMS_IMPL_STRUCTURED_SDK_PUT(_MOD_, _SEV_, _VER_, _MSG_, _ML_) do { /* ... */ } while (false) + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_utf8_util.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_utf8_util.hpp new file mode 100644 index 00000000..d9a307de --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/diag/impl/diag_utf8_util.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::diag::impl { + + int GetValidSizeAsUtf8String(const char *str, size_t len); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dmnt.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dmnt.hpp new file mode 100644 index 00000000..9d9d9ff8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dmnt.hpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/dmnt/dmnt_cheat_types.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dmnt/dmnt_cheat_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dmnt/dmnt_cheat_types.hpp new file mode 100644 index 00000000..d1cd4da2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/dmnt/dmnt_cheat_types.hpp @@ -0,0 +1,66 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> +#include <stratosphere/sf/sf_buffer_tags.hpp> + +namespace ams::dmnt::cheat { + + struct CheatProcessMetadata { + struct MemoryRegionExtents { + u64 base; + u64 size; + }; + + os::ProcessId process_id; + ncm::ProgramId program_id; + MemoryRegionExtents main_nso_extents; + MemoryRegionExtents heap_extents; + MemoryRegionExtents alias_extents; + MemoryRegionExtents aslr_extents; + u8 main_nso_module_id[0x20]; + }; + + static_assert(util::is_pod<CheatProcessMetadata>::value && sizeof(CheatProcessMetadata) == 0x70, "CheatProcessMetadata definition!"); + + struct CheatDefinition : sf::LargeData, sf::PrefersMapAliasTransferMode { + char readable_name[0x40]; + uint32_t num_opcodes; + uint32_t opcodes[0x100]; + }; + + struct CheatEntry : sf::LargeData, sf::PrefersMapAliasTransferMode { + bool enabled; + uint32_t cheat_id; + CheatDefinition definition; + }; + + static_assert(util::is_pod<CheatDefinition>::value, "CheatDefinition"); + static_assert(util::is_pod<CheatEntry>::value, "CheatEntry"); + + struct FrozenAddressValue { + u64 value; + u8 width; + }; + + struct FrozenAddressEntry { + u64 address; + FrozenAddressValue value; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt.hpp new file mode 100644 index 00000000..dd2c9b5f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/erpt/erpt_ids.autogen.hpp> +#include <stratosphere/erpt/erpt_types.hpp> +#include <stratosphere/erpt/erpt_multiple_category_context.hpp> +#include <stratosphere/erpt/sf/erpt_sf_i_context.hpp> +#include <stratosphere/erpt/sf/erpt_sf_i_session.hpp> +#include <stratosphere/erpt/srv/erpt_srv_types.hpp> +#include <stratosphere/erpt/srv/erpt_srv_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/erpt_ids.autogen.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/erpt_ids.autogen.hpp new file mode 100644 index 00000000..a6a6d5f5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/erpt_ids.autogen.hpp @@ -0,0 +1,964 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +/* NOTE: This file is auto-generated. */ +/* Do not make edits to this file by hand. */ + +#define AMS_ERPT_FOREACH_FIELD_TYPE(HANDLER) \ + HANDLER(FieldType_NumericU64, 0 ) \ + HANDLER(FieldType_NumericU32, 1 ) \ + HANDLER(FieldType_NumericI64, 2 ) \ + HANDLER(FieldType_NumericI32, 3 ) \ + HANDLER(FieldType_String, 4 ) \ + HANDLER(FieldType_U8Array, 5 ) \ + HANDLER(FieldType_U32Array, 6 ) \ + HANDLER(FieldType_U64Array, 7 ) \ + HANDLER(FieldType_I32Array, 8 ) \ + HANDLER(FieldType_I64Array, 9 ) \ + HANDLER(FieldType_Bool, 10) \ + HANDLER(FieldType_NumericU16, 11) \ + HANDLER(FieldType_NumericU8, 12) \ + HANDLER(FieldType_NumericI16, 13) \ + HANDLER(FieldType_NumericI8, 14) \ + HANDLER(FieldType_I8Array, 15) + +#define AMS_ERPT_FOREACH_CATEGORY(HANDLER) \ + HANDLER(Test, 0 ) \ + HANDLER(ErrorInfo, 1 ) \ + HANDLER(ConnectionStatusInfo, 2 ) \ + HANDLER(NetworkInfo, 3 ) \ + HANDLER(NXMacAddressInfo, 4 ) \ + HANDLER(StealthNetworkInfo, 5 ) \ + HANDLER(LimitHighCapacityInfo, 6 ) \ + HANDLER(NATTypeInfo, 7 ) \ + HANDLER(WirelessAPMacAddressInfo, 8 ) \ + HANDLER(GlobalIPAddressInfo, 9 ) \ + HANDLER(EnableWirelessInterfaceInfo, 10 ) \ + HANDLER(EnableWifiInfo, 11 ) \ + HANDLER(EnableBluetoothInfo, 12 ) \ + HANDLER(EnableNFCInfo, 13 ) \ + HANDLER(NintendoZoneSSIDListVersionInfo, 14 ) \ + HANDLER(LANAdapterMacAddressInfo, 15 ) \ + HANDLER(ApplicationInfo, 16 ) \ + HANDLER(OccurrenceInfo, 17 ) \ + HANDLER(ProductModelInfo, 18 ) \ + HANDLER(CurrentLanguageInfo, 19 ) \ + HANDLER(UseNetworkTimeProtocolInfo, 20 ) \ + HANDLER(TimeZoneInfo, 21 ) \ + HANDLER(ControllerFirmwareInfo, 22 ) \ + HANDLER(VideoOutputInfo, 23 ) \ + HANDLER(NANDFreeSpaceInfo, 24 ) \ + HANDLER(SDCardFreeSpaceInfo, 25 ) \ + HANDLER(ScreenBrightnessInfo, 26 ) \ + HANDLER(AudioFormatInfo, 27 ) \ + HANDLER(MuteOnHeadsetUnpluggedInfo, 28 ) \ + HANDLER(NumUserRegisteredInfo, 29 ) \ + HANDLER(DataDeletionInfo, 30 ) \ + HANDLER(ControllerVibrationInfo, 31 ) \ + HANDLER(LockScreenInfo, 32 ) \ + HANDLER(InternalBatteryLotNumberInfo, 33 ) \ + HANDLER(LeftControllerSerialNumberInfo, 34 ) \ + HANDLER(RightControllerSerialNumberInfo, 35 ) \ + HANDLER(NotificationInfo, 36 ) \ + HANDLER(TVInfo, 37 ) \ + HANDLER(SleepInfo, 38 ) \ + HANDLER(ConnectionInfo, 39 ) \ + HANDLER(NetworkErrorInfo, 40 ) \ + HANDLER(FileAccessPathInfo, 41 ) \ + HANDLER(GameCardCIDInfo, 42 ) \ + HANDLER(NANDCIDInfoDeprecated, 43 ) \ + HANDLER(MicroSDCIDInfoDeprecated, 44 ) \ + HANDLER(NANDSpeedModeInfo, 45 ) \ + HANDLER(MicroSDSpeedModeInfo, 46 ) \ + HANDLER(GameCardSpeedModeInfo, 47 ) \ + HANDLER(UserAccountInternalIDInfo, 48 ) \ + HANDLER(NetworkServiceAccountInternalIDInfo, 49 ) \ + HANDLER(NintendoAccountInternalIDInfo, 50 ) \ + HANDLER(USB3AvailableInfo, 51 ) \ + HANDLER(CallStackInfo, 52 ) \ + HANDLER(SystemStartupLogInfo, 53 ) \ + HANDLER(RegionSettingInfo, 54 ) \ + HANDLER(NintendoZoneConnectedInfo, 55 ) \ + HANDLER(ForceSleepInfo, 56 ) \ + HANDLER(ChargerInfo, 57 ) \ + HANDLER(RadioStrengthInfo, 58 ) \ + HANDLER(ErrorInfoAuto, 59 ) \ + HANDLER(AccessPointInfo, 60 ) \ + HANDLER(ErrorInfoDefaults, 61 ) \ + HANDLER(SystemPowerStateInfo, 62 ) \ + HANDLER(PerformanceInfo, 63 ) \ + HANDLER(ThrottlingInfo, 64 ) \ + HANDLER(GameCardErrorInfo, 65 ) \ + HANDLER(EdidInfo, 66 ) \ + HANDLER(ThermalInfo, 67 ) \ + HANDLER(CradleFirmwareInfo, 68 ) \ + HANDLER(RunningApplicationInfo, 69 ) \ + HANDLER(RunningAppletInfo, 70 ) \ + HANDLER(FocusedAppletHistoryInfo, 71 ) \ + HANDLER(CompositorInfo, 72 ) \ + HANDLER(BatteryChargeInfo, 73 ) \ + HANDLER(NANDExtendedCsdDeprecated, 74 ) \ + HANDLER(NANDPatrolInfo, 75 ) \ + HANDLER(NANDErrorInfo, 76 ) \ + HANDLER(NANDDriverLog, 77 ) \ + HANDLER(SdCardSizeSpec, 78 ) \ + HANDLER(SdCardErrorInfo, 79 ) \ + HANDLER(SdCardDriverLog , 80 ) \ + HANDLER(FsProxyErrorInfo, 81 ) \ + HANDLER(SystemAppletSceneInfo, 82 ) \ + HANDLER(VideoInfo, 83 ) \ + HANDLER(GpuErrorInfo, 84 ) \ + HANDLER(PowerClockInfo, 85 ) \ + HANDLER(AdspErrorInfo, 86 ) \ + HANDLER(NvDispDeviceInfo, 87 ) \ + HANDLER(NvDispDcWindowInfo, 88 ) \ + HANDLER(NvDispDpModeInfo, 89 ) \ + HANDLER(NvDispDpLinkSpec, 90 ) \ + HANDLER(NvDispDpLinkStatus, 91 ) \ + HANDLER(NvDispDpHdcpInfo, 92 ) \ + HANDLER(NvDispDpAuxCecInfo, 93 ) \ + HANDLER(NvDispDcInfo, 94 ) \ + HANDLER(NvDispDsiInfo, 95 ) \ + HANDLER(NvDispErrIDInfo, 96 ) \ + HANDLER(SdCardMountInfo, 97 ) \ + HANDLER(RetailInteractiveDisplayInfo, 98 ) \ + HANDLER(CompositorStateInfo, 99 ) \ + HANDLER(CompositorLayerInfo, 100 ) \ + HANDLER(CompositorDisplayInfo, 101 ) \ + HANDLER(CompositorHWCInfo, 102 ) \ + HANDLER(MonitorCapability, 103 ) \ + HANDLER(ErrorReportSharePermissionInfo, 104 ) \ + HANDLER(MultimediaInfo, 105 ) \ + HANDLER(ConnectedControllerInfo, 106 ) \ + HANDLER(FsMemoryInfo, 107 ) \ + HANDLER(UserClockContextInfo, 108 ) \ + HANDLER(NetworkClockContextInfo, 109 ) \ + HANDLER(AcpGeneralSettingsInfo, 110 ) \ + HANDLER(AcpPlayLogSettingsInfo, 111 ) \ + HANDLER(AcpAocSettingsInfo, 112 ) \ + HANDLER(AcpBcatSettingsInfo, 113 ) \ + HANDLER(AcpStorageSettingsInfo, 114 ) \ + HANDLER(AcpRatingSettingsInfo, 115 ) \ + HANDLER(MonitorSettings, 116 ) \ + HANDLER(RebootlessSystemUpdateVersionInfo, 117 ) \ + HANDLER(NifmConnectionTestInfo, 118 ) \ + HANDLER(PcieLoggedStateInfo, 119 ) \ + HANDLER(NetworkSecurityCertificateInfo, 120 ) \ + HANDLER(AcpNeighborDetectionInfo, 121 ) \ + HANDLER(GpuCrashInfo, 122 ) \ + HANDLER(UsbStateInfo, 123 ) \ + HANDLER(NvHostErrInfo, 124 ) \ + HANDLER(RunningUlaInfo, 125 ) \ + HANDLER(InternalPanelInfo, 126 ) \ + HANDLER(ResourceLimitLimitInfo, 127 ) \ + HANDLER(ResourceLimitPeakInfo, 128 ) \ + HANDLER(TouchScreenInfo, 129 ) \ + HANDLER(AcpUserAccountSettingsInfo, 130 ) \ + HANDLER(AudioDeviceInfo, 131 ) \ + HANDLER(AbnormalWakeInfo, 132 ) \ + HANDLER(ServiceProfileInfo, 133 ) \ + HANDLER(BluetoothAudioInfo, 134 ) \ + HANDLER(BluetoothPairingCountInfo, 135 ) \ + HANDLER(FsProxyErrorInfo2, 136 ) \ + HANDLER(BuiltInWirelessOUIInfo, 137 ) \ + HANDLER(WirelessAPOUIInfo, 138 ) \ + HANDLER(EthernetAdapterOUIInfo, 139 ) \ + HANDLER(NANDTypeInfoDeprecated, 140 ) \ + HANDLER(MicroSDTypeInfo, 141 ) \ + HANDLER(AttachmentFileInfo, 142 ) \ + HANDLER(WlanInfo, 143 ) \ + HANDLER(HalfAwakeStateInfo, 144 ) \ + HANDLER(PctlSettingInfo, 145 ) \ + HANDLER(GameCardLogInfo, 146 ) \ + HANDLER(WlanIoctlErrorInfo, 147 ) \ + HANDLER(SdCardActivationInfo, 148 ) \ + HANDLER(GameCardDetailedErrorInfo, 149 ) \ + HANDLER(TestNx, 1000) \ + HANDLER(NANDTypeInfo, 1001) \ + HANDLER(NANDExtendedCsd, 1002) \ + +#define AMS_ERPT_FOREACH_FIELD(HANDLER) \ + HANDLER(TestU64, 0, Test, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(TestU32, 1, Test, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(TestI64, 2, Test, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(TestI32, 3, Test, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(TestString, 4, Test, FieldType_String, FieldFlag_None ) \ + HANDLER(TestU8Array, 5, Test, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(TestU32Array, 6, Test, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(TestU64Array, 7, Test, FieldType_U64Array, FieldFlag_None ) \ + HANDLER(TestI32Array, 8, Test, FieldType_I32Array, FieldFlag_None ) \ + HANDLER(TestI64Array, 9, Test, FieldType_I64Array, FieldFlag_None ) \ + HANDLER(ErrorCode, 10, ErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(ErrorDescription, 11, ErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(OccurrenceTimestamp, 12, ErrorInfoAuto, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(ReportIdentifier, 13, ErrorInfoAuto, FieldType_String, FieldFlag_None ) \ + HANDLER(ConnectionStatus, 14, ConnectionStatusInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(AccessPointSSID, 15, AccessPointInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(AccessPointSecurityType, 16, AccessPointInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(RadioStrength, 17, RadioStrengthInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NXMacAddress, 18, NXMacAddressInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(IPAddressAcquisitionMethod, 19, NetworkInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(CurrentIPAddress, 20, NetworkInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(SubnetMask, 21, NetworkInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(GatewayIPAddress, 22, NetworkInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(DNSType, 23, NetworkInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(PriorityDNSIPAddress, 24, NetworkInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(AlternateDNSIPAddress, 25, NetworkInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(UseProxyFlag, 26, NetworkInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(ProxyIPAddress, 27, NetworkInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(ProxyPort, 28, NetworkInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(ProxyAutoAuthenticateFlag, 29, NetworkInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(MTU, 30, NetworkInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(ConnectAutomaticallyFlag, 31, NetworkInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(UseStealthNetworkFlag, 32, StealthNetworkInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(LimitHighCapacityFlag, 33, LimitHighCapacityInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NATType, 34, NATTypeInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(WirelessAPMacAddress, 35, WirelessAPMacAddressInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(GlobalIPAddress, 36, GlobalIPAddressInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(EnableWirelessInterfaceFlag, 37, EnableWirelessInterfaceInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(EnableWifiFlag, 38, EnableWifiInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(EnableBluetoothFlag, 39, EnableBluetoothInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(EnableNFCFlag, 40, EnableNFCInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NintendoZoneSSIDListVersion, 41, NintendoZoneSSIDListVersionInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(LANAdapterMacAddress, 42, LANAdapterMacAddressInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(ApplicationID, 43, ApplicationInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(ApplicationTitle, 44, ApplicationInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(ApplicationVersion, 45, ApplicationInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(ApplicationStorageLocation, 46, ApplicationInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(DownloadContentType, 47, OccurrenceInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(InstallContentType, 48, OccurrenceInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(ConsoleStartingUpFlag, 49, OccurrenceInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(SystemStartingUpFlag, 50, OccurrenceInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(ConsoleFirstInitFlag, 51, OccurrenceInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(HomeMenuScreenDisplayedFlag, 52, OccurrenceInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(DataManagementScreenDisplayedFlag, 53, OccurrenceInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(ConnectionTestingFlag, 54, OccurrenceInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(ApplicationRunningFlag, 55, OccurrenceInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(DataCorruptionDetectedFlag, 56, OccurrenceInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(ProductModel, 57, ProductModelInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(CurrentLanguage, 58, CurrentLanguageInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(UseNetworkTimeProtocolFlag, 59, UseNetworkTimeProtocolInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(TimeZone, 60, TimeZoneInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(ControllerFirmware, 61, ControllerFirmwareInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(VideoOutputSetting, 62, VideoOutputInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(NANDFreeSpace, 63, NANDFreeSpaceInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(SDCardFreeSpace, 64, SDCardFreeSpaceInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(SerialNumber, 65, ErrorInfoAuto, FieldType_String, FieldFlag_None ) \ + HANDLER(OsVersion, 66, ErrorInfoAuto, FieldType_String, FieldFlag_None ) \ + HANDLER(ScreenBrightnessAutoAdjustFlag, 67, ScreenBrightnessInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(HdmiAudioOutputMode, 68, AudioFormatInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(SpeakerAudioOutputMode, 69, AudioFormatInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(HeadphoneAudioOutputMode, 70, AudioFormatInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(MuteOnHeadsetUnpluggedFlag, 71, MuteOnHeadsetUnpluggedInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NumUserRegistered, 72, NumUserRegisteredInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(StorageAutoOrganizeFlag, 73, DataDeletionInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(ControllerVibrationVolume, 74, ControllerVibrationInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(LockScreenFlag, 75, LockScreenInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(InternalBatteryLotNumber, 76, InternalBatteryLotNumberInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(LeftControllerSerialNumber, 77, LeftControllerSerialNumberInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(RightControllerSerialNumber, 78, RightControllerSerialNumberInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(NotifyInGameDownloadCompletionFlag, 79, NotificationInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NotificationSoundFlag, 80, NotificationInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(TVResolutionSetting, 81, TVInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(RGBRangeSetting, 82, TVInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(ReduceScreenBurnFlag, 83, TVInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(TVAllowsCecFlag, 84, TVInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(HandheldModeTimeToScreenSleep, 85, SleepInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(ConsoleModeTimeToScreenSleep, 86, SleepInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(StopAutoSleepDuringContentPlayFlag, 87, SleepInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(LastConnectionTestDownloadSpeed, 88, ConnectionInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(LastConnectionTestUploadSpeed, 89, ConnectionInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(DEPRECATED_ServerFQDN, 90, NetworkErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(HTTPRequestContents, 91, NetworkErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(HTTPRequestResponseContents, 92, NetworkErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(EdgeServerIPAddress, 93, NetworkErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(CDNContentPath, 94, NetworkErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(FileAccessPath, 95, FileAccessPathInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(GameCardCID, 96, GameCardCIDInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(NANDCIDDeprecated, 97, NANDCIDInfoDeprecated, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(MicroSDCIDDeprecated, 98, MicroSDCIDInfoDeprecated, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(NANDSpeedMode, 99, NANDSpeedModeInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(MicroSDSpeedMode, 100, MicroSDSpeedModeInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(GameCardSpeedMode, 101, GameCardSpeedModeInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(UserAccountInternalID, 102, UserAccountInternalIDInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(NetworkServiceAccountInternalID, 103, NetworkServiceAccountInternalIDInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(NintendoAccountInternalID, 104, NintendoAccountInternalIDInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(USB3AvailableFlag, 105, USB3AvailableInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(CallStack, 106, CallStackInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(SystemStartupLog, 107, SystemStartupLogInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(RegionSetting, 108, RegionSettingInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(NintendoZoneConnectedFlag, 109, NintendoZoneConnectedInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(ForcedSleepHighTemperatureReading, 110, ForceSleepInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(ForcedSleepFanSpeedReading, 111, ForceSleepInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(ForcedSleepHWInfo, 112, ForceSleepInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(AbnormalPowerStateInfo, 113, ChargerInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(ScreenBrightnessLevel, 114, ScreenBrightnessInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(ProgramId, 115, ErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(AbortFlag, 116, ErrorInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(ReportVisibilityFlag, 117, ErrorInfoAuto, FieldType_Bool, FieldFlag_None ) \ + HANDLER(FatalFlag, 118, ErrorInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(OccurrenceTimestampNet, 119, ErrorInfoAuto, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(ResultBacktrace, 120, ErrorInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(GeneralRegisterAarch32, 121, ErrorInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(StackBacktrace32, 122, ErrorInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(ExceptionInfoAarch32, 123, ErrorInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(GeneralRegisterAarch64, 124, ErrorInfo, FieldType_U64Array, FieldFlag_None ) \ + HANDLER(ExceptionInfoAarch64, 125, ErrorInfo, FieldType_U64Array, FieldFlag_None ) \ + HANDLER(StackBacktrace64, 126, ErrorInfo, FieldType_U64Array, FieldFlag_None ) \ + HANDLER(RegisterSetFlag32, 127, ErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(RegisterSetFlag64, 128, ErrorInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(ProgramMappedAddr32, 129, ErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(ProgramMappedAddr64, 130, ErrorInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(AbortType, 131, ErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(PrivateOsVersion, 132, ErrorInfoAuto, FieldType_String, FieldFlag_None ) \ + HANDLER(CurrentSystemPowerState, 133, SystemPowerStateInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(PreviousSystemPowerState, 134, SystemPowerStateInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(DestinationSystemPowerState, 135, SystemPowerStateInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(PscTransitionCurrentState, 136, ErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(PscTransitionPreviousState, 137, ErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(PscInitializedList, 138, ErrorInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(PscCurrentPmStateList, 139, ErrorInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(PscNextPmStateList, 140, ErrorInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(PerformanceMode, 141, PerformanceInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(PerformanceConfiguration, 142, PerformanceInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(Throttled, 143, ThrottlingInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(ThrottlingDuration, 144, ThrottlingInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(ThrottlingTimestamp, 145, ThrottlingInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(GameCardCrcErrorCount, 146, GameCardErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GameCardAsicCrcErrorCount, 147, GameCardErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GameCardRefreshCount, 148, GameCardErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GameCardReadRetryCount, 149, GameCardErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(EdidBlock, 150, EdidInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(EdidExtensionBlock, 151, EdidInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(CreateProcessFailureFlag, 152, ErrorInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(TemperaturePcb, 153, ThermalInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(TemperatureSoc, 154, ThermalInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(CurrentFanDuty, 155, ThermalInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(LastDvfsThresholdTrippedDeprecated, 156, ThermalInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(CradlePdcHFwVersion, 157, CradleFirmwareInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(CradlePdcAFwVersion, 158, CradleFirmwareInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(CradleMcuFwVersion, 159, CradleFirmwareInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(CradleDp2HdmiFwVersion, 160, CradleFirmwareInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(RunningApplicationId, 161, RunningApplicationInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(RunningApplicationTitle, 162, RunningApplicationInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(RunningApplicationVersion, 163, RunningApplicationInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(RunningApplicationStorageLocation, 164, RunningApplicationInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(RunningAppletList, 165, RunningAppletInfo, FieldType_U64Array, FieldFlag_None ) \ + HANDLER(FocusedAppletHistory, 166, FocusedAppletHistoryInfo, FieldType_U64Array, FieldFlag_None ) \ + HANDLER(CompositorState, 167, CompositorStateInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(CompositorLayerState, 168, CompositorLayerInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(CompositorDisplayState, 169, CompositorDisplayInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(CompositorHWCState, 170, CompositorHWCInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(InputCurrentLimit, 171, BatteryChargeInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(BoostModeCurrentLimitDeprecated, 172, BatteryChargeInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(FastChargeCurrentLimit, 173, BatteryChargeInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(ChargeVoltageLimit, 174, BatteryChargeInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(ChargeConfigurationDeprecated, 175, BatteryChargeInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(HizModeDeprecated, 176, BatteryChargeInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(ChargeEnabled, 177, BatteryChargeInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(PowerSupplyPathDeprecated, 178, BatteryChargeInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(BatteryTemperature, 179, BatteryChargeInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(BatteryChargePercent, 180, BatteryChargeInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(BatteryChargeVoltage, 181, BatteryChargeInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(BatteryAge, 182, BatteryChargeInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(PowerRole, 183, BatteryChargeInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(PowerSupplyType, 184, BatteryChargeInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(PowerSupplyVoltage, 185, BatteryChargeInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(PowerSupplyCurrent, 186, BatteryChargeInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(FastBatteryChargingEnabled, 187, BatteryChargeInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(ControllerPowerSupplyAcquiredDeprecated, 188, BatteryChargeInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(OtgRequestedDeprecated, 189, BatteryChargeInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NANDPreEolInfoDeprecated, 190, NANDExtendedCsdDeprecated, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NANDDeviceLifeTimeEstTypADeprecated, 191, NANDExtendedCsdDeprecated, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NANDDeviceLifeTimeEstTypBDeprecated, 192, NANDExtendedCsdDeprecated, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NANDPatrolCount, 193, NANDPatrolInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NANDNumActivationFailures, 194, NANDErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NANDNumActivationErrorCorrections, 195, NANDErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NANDNumReadWriteFailures, 196, NANDErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NANDNumReadWriteErrorCorrections, 197, NANDErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NANDErrorLog, 198, NANDDriverLog, FieldType_String, FieldFlag_None ) \ + HANDLER(SdCardUserAreaSize, 199, SdCardSizeSpec, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(SdCardProtectedAreaSize, 200, SdCardSizeSpec, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(SdCardNumActivationFailures, 201, SdCardErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(SdCardNumActivationErrorCorrections, 202, SdCardErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(SdCardNumReadWriteFailures, 203, SdCardErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(SdCardNumReadWriteErrorCorrections, 204, SdCardErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(SdCardErrorLog, 205, SdCardDriverLog , FieldType_String, FieldFlag_None ) \ + HANDLER(EncryptionKey, 206, ErrorInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(EncryptedExceptionInfo, 207, ErrorInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(GameCardTimeoutRetryErrorCount, 208, GameCardErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(FsRemountForDataCorruptCount, 209, FsProxyErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(FsRemountForDataCorruptRetryOutCount, 210, FsProxyErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GameCardInsertionCount, 211, GameCardErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GameCardRemovalCount, 212, GameCardErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GameCardAsicInitializeCount, 213, GameCardErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(TestU16, 214, Test, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(TestU8, 215, Test, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(TestI16, 216, Test, FieldType_NumericI16, FieldFlag_None ) \ + HANDLER(TestI8, 217, Test, FieldType_NumericI8, FieldFlag_None ) \ + HANDLER(SystemAppletScene, 218, SystemAppletSceneInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(CodecType, 219, VideoInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(DecodeBuffers, 220, VideoInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(FrameWidth, 221, VideoInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(FrameHeight, 222, VideoInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(ColorPrimaries, 223, VideoInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(TransferCharacteristics, 224, VideoInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(MatrixCoefficients, 225, VideoInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(DisplayWidth, 226, VideoInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(DisplayHeight, 227, VideoInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(DARWidth, 228, VideoInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(DARHeight, 229, VideoInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(ColorFormat, 230, VideoInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(ColorSpace, 231, VideoInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(SurfaceLayout, 232, VideoInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(BitStream, 233, VideoInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(VideoDecState, 234, VideoInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(GpuErrorChannelId, 235, GpuErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GpuErrorAruId, 236, GpuErrorInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(GpuErrorType, 237, GpuErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GpuErrorFaultInfo, 238, GpuErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GpuErrorWriteAccess, 239, GpuErrorInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(GpuErrorFaultAddress, 240, GpuErrorInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(GpuErrorFaultUnit, 241, GpuErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GpuErrorFaultType, 242, GpuErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GpuErrorHwContextPointer, 243, GpuErrorInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(GpuErrorContextStatus, 244, GpuErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GpuErrorPbdmaIntr, 245, GpuErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GpuErrorPbdmaErrorType, 246, GpuErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GpuErrorPbdmaHeaderShadow, 247, GpuErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GpuErrorPbdmaHeader, 248, GpuErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GpuErrorPbdmaGpShadow0, 249, GpuErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GpuErrorPbdmaGpShadow1, 250, GpuErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(AccessPointChannel, 251, AccessPointInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(ThreadName, 252, ErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(AdspExceptionRegistersDeprecated, 253, AdspErrorInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(AdspExceptionSpsrDeprecated, 254, AdspErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(AdspExceptionProgramCounter, 255, AdspErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(AdspExceptionLinkRegister, 256, AdspErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(AdspExceptionStackPointer, 257, AdspErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(AdspExceptionArmModeRegistersDeprecated, 258, AdspErrorInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(AdspExceptionStackAddressDeprecated, 259, AdspErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(AdspExceptionStackDumpDeprecated, 260, AdspErrorInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(AdspExceptionReasonDeprecated, 261, AdspErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(OscillatorClockDeprecated, 262, PowerClockInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(CpuDvfsTableClocksDeprecated, 263, PowerClockInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(CpuDvfsTableVoltagesDeprecated, 264, PowerClockInfo, FieldType_I32Array, FieldFlag_None ) \ + HANDLER(GpuDvfsTableClocksDeprecated, 265, PowerClockInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(GpuDvfsTableVoltagesDeprecated, 266, PowerClockInfo, FieldType_I32Array, FieldFlag_None ) \ + HANDLER(EmcDvfsTableClocksDeprecated, 267, PowerClockInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(EmcDvfsTableVoltagesDeprecated, 268, PowerClockInfo, FieldType_I32Array, FieldFlag_None ) \ + HANDLER(ModuleClockFrequencies, 269, PowerClockInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(ModuleClockEnableFlagsDeprecated, 270, PowerClockInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(ModulePowerEnableFlagsDeprecated, 271, PowerClockInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(ModuleResetAssertFlags, 272, PowerClockInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(ModuleMinimumVoltageClockRates, 273, PowerClockInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(PowerDomainEnableFlagsDeprecated, 274, PowerClockInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(PowerDomainVoltagesDeprecated, 275, PowerClockInfo, FieldType_I32Array, FieldFlag_None ) \ + HANDLER(AccessPointRssi, 276, RadioStrengthInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(FuseInfoDeprecated, 277, PowerClockInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(VideoLog, 278, VideoInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(GameCardDeviceId, 279, GameCardCIDInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(GameCardAsicReinitializeCount, 280, GameCardErrorInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(GameCardAsicReinitializeFailureCount, 281, GameCardErrorInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(GameCardAsicReinitializeFailureDetail, 282, GameCardErrorInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(GameCardRefreshSuccessCount, 283, GameCardErrorInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(GameCardAwakenCount, 284, GameCardErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GameCardAwakenFailureCount, 285, GameCardErrorInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(GameCardReadCountFromInsert, 286, GameCardErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GameCardReadCountFromAwaken, 287, GameCardErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GameCardLastReadErrorPageAddress, 288, GameCardErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GameCardLastReadErrorPageCount, 289, GameCardErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(AppletManagerContextTrace, 290, ErrorInfo, FieldType_I32Array, FieldFlag_None ) \ + HANDLER(NvDispIsRegistered, 291, NvDispDeviceInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispIsSuspend, 292, NvDispDeviceInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDC0SurfaceNum, 293, NvDispDeviceInfo, FieldType_I32Array, FieldFlag_None ) \ + HANDLER(NvDispDC1SurfaceNum, 294, NvDispDeviceInfo, FieldType_I32Array, FieldFlag_None ) \ + HANDLER(NvDispWindowSrcRectX, 295, NvDispDcWindowInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispWindowSrcRectY, 296, NvDispDcWindowInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispWindowSrcRectWidth, 297, NvDispDcWindowInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispWindowSrcRectHeight, 298, NvDispDcWindowInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispWindowDstRectX, 299, NvDispDcWindowInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispWindowDstRectY, 300, NvDispDcWindowInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispWindowDstRectWidth, 301, NvDispDcWindowInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispWindowDstRectHeight, 302, NvDispDcWindowInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispWindowIndex, 303, NvDispDcWindowInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispWindowBlendOperation, 304, NvDispDcWindowInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispWindowAlphaOperation, 305, NvDispDcWindowInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispWindowDepth, 306, NvDispDcWindowInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispWindowAlpha, 307, NvDispDcWindowInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(NvDispWindowHFilter, 308, NvDispDcWindowInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispWindowVFilter, 309, NvDispDcWindowInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispWindowOptions, 310, NvDispDcWindowInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispWindowSyncPointId, 311, NvDispDcWindowInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDPSorPower, 312, NvDispDpModeInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDPClkType, 313, NvDispDpModeInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(NvDispDPEnable, 314, NvDispDpModeInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDPState, 315, NvDispDpModeInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDPEdid, 316, NvDispDpModeInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(NvDispDPEdidSize, 317, NvDispDpModeInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDPEdidExtSize, 318, NvDispDpModeInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDPFakeMode, 319, NvDispDpModeInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDPModeNumber, 320, NvDispDpModeInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDPPlugInOut, 321, NvDispDpModeInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDPAuxIntHandler, 322, NvDispDpModeInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDPForceMaxLinkBW, 323, NvDispDpModeInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDPIsConnected, 324, NvDispDpModeInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDPLinkValid, 325, NvDispDpLinkSpec, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDPLinkMaxBW, 326, NvDispDpLinkSpec, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(NvDispDPLinkMaxLaneCount, 327, NvDispDpLinkSpec, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(NvDispDPLinkDownSpread, 328, NvDispDpLinkSpec, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDPLinkSupportEnhancedFraming, 329, NvDispDpLinkSpec, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDPLinkBpp, 330, NvDispDpLinkSpec, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDPLinkScaramberCap, 331, NvDispDpLinkSpec, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDPLinkBW, 332, NvDispDpLinkStatus, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(NvDispDPLinkLaneCount, 333, NvDispDpLinkStatus, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(NvDispDPLinkEnhancedFraming, 334, NvDispDpLinkStatus, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDPLinkScrambleEnable, 335, NvDispDpLinkStatus, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDPLinkActivePolarity, 336, NvDispDpLinkStatus, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDPLinkActiveCount, 337, NvDispDpLinkStatus, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDPLinkTUSize, 338, NvDispDpLinkStatus, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDPLinkActiveFrac, 339, NvDispDpLinkStatus, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDPLinkWatermark, 340, NvDispDpLinkStatus, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDPLinkHBlank, 341, NvDispDpLinkStatus, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDPLinkVBlank, 342, NvDispDpLinkStatus, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDPLinkOnlyEnhancedFraming, 343, NvDispDpLinkStatus, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDPLinkOnlyEdpCap, 344, NvDispDpLinkStatus, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDPLinkSupportFastLT, 345, NvDispDpLinkStatus, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDPLinkLTDataValid, 346, NvDispDpLinkStatus, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDPLinkTsp3Support, 347, NvDispDpLinkStatus, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDPLinkAuxInterval, 348, NvDispDpLinkStatus, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(NvDispHdcpCreated, 349, NvDispDpHdcpInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispHdcpUserRequest, 350, NvDispDpHdcpInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispHdcpPlugged, 351, NvDispDpHdcpInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispHdcpState, 352, NvDispDpHdcpInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispHdcpFailCount, 353, NvDispDpHdcpInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(NvDispHdcpHdcp22, 354, NvDispDpHdcpInfo, FieldType_NumericI8, FieldFlag_None ) \ + HANDLER(NvDispHdcpMaxRetry, 355, NvDispDpHdcpInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(NvDispHdcpHpd, 356, NvDispDpHdcpInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(NvDispHdcpRepeater, 357, NvDispDpHdcpInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(NvDispCecRxBuf, 358, NvDispDpAuxCecInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(NvDispCecRxLength, 359, NvDispDpAuxCecInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(NvDispCecTxBuf, 360, NvDispDpAuxCecInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(NvDispCecTxLength, 361, NvDispDpAuxCecInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(NvDispCecTxRet, 362, NvDispDpAuxCecInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(NvDispCecState, 363, NvDispDpAuxCecInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispCecTxInfo, 364, NvDispDpAuxCecInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(NvDispCecRxInfo, 365, NvDispDpAuxCecInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(NvDispDCIndex, 366, NvDispDcInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDCInitialize, 367, NvDispDcInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDCClock, 368, NvDispDcInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDCFrequency, 369, NvDispDcInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDCFailed, 370, NvDispDcInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDCModeWidth, 371, NvDispDcInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(NvDispDCModeHeight, 372, NvDispDcInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(NvDispDCModeBpp, 373, NvDispDcInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDCPanelFrequency, 374, NvDispDcInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDCWinDirty, 375, NvDispDcInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDCWinEnable, 376, NvDispDcInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDCVrr, 377, NvDispDcInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDCPanelInitialize, 378, NvDispDcInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDsiDataFormat, 379, NvDispDsiInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDsiVideoMode, 380, NvDispDsiInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDsiRefreshRate, 381, NvDispDsiInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDsiLpCmdModeFrequency, 382, NvDispDsiInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDsiHsCmdModeFrequency, 383, NvDispDsiInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDsiPanelResetTimeout, 384, NvDispDsiInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDsiPhyFrequency, 385, NvDispDsiInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDsiFrequency, 386, NvDispDsiInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDsiInstance, 387, NvDispDsiInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDcDsiHostCtrlEnable, 388, NvDispDsiInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDcDsiInit, 389, NvDispDsiInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDcDsiEnable, 390, NvDispDsiInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDcDsiHsMode, 391, NvDispDsiInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDcDsiVendorId, 392, NvDispDsiInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(NvDispDcDsiLcdVendorNum, 393, NvDispDsiInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(NvDispDcDsiHsClockControl, 394, NvDispDsiInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDcDsiEnableHsClockInLpMode, 395, NvDispDsiInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDcDsiTeFrameUpdate, 396, NvDispDsiInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispDcDsiGangedType, 397, NvDispDsiInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispDcDsiHbpInPktSeq, 398, NvDispDsiInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NvDispErrID, 399, NvDispErrIDInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispErrData0, 400, NvDispErrIDInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvDispErrData1, 401, NvDispErrIDInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(SdCardMountStatus, 402, SdCardMountInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(SdCardMountUnexpectedResult, 403, SdCardMountInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(NANDTotalSize, 404, NANDFreeSpaceInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(SdCardTotalSize, 405, SDCardFreeSpaceInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(ElapsedTimeSinceInitialLaunch, 406, ErrorInfoAuto, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(ElapsedTimeSincePowerOn, 407, ErrorInfoAuto, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(ElapsedTimeSinceLastAwake, 408, ErrorInfoAuto, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(OccurrenceTick, 409, ErrorInfoAuto, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(RetailInteractiveDisplayFlag, 410, RetailInteractiveDisplayInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(FatFsError, 411, FsProxyErrorInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(FatFsExtraError, 412, FsProxyErrorInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(FatFsErrorDrive, 413, FsProxyErrorInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(FatFsErrorName, 414, FsProxyErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(MonitorManufactureCode, 415, MonitorCapability, FieldType_String, FieldFlag_None ) \ + HANDLER(MonitorProductCode, 416, MonitorCapability, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(MonitorSerialNumber, 417, MonitorCapability, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(MonitorManufactureYear, 418, MonitorCapability, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(PhysicalAddress, 419, MonitorCapability, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(Is4k60Hz, 420, MonitorCapability, FieldType_Bool, FieldFlag_None ) \ + HANDLER(Is4k30Hz, 421, MonitorCapability, FieldType_Bool, FieldFlag_None ) \ + HANDLER(Is1080P60Hz, 422, MonitorCapability, FieldType_Bool, FieldFlag_None ) \ + HANDLER(Is720P60Hz, 423, MonitorCapability, FieldType_Bool, FieldFlag_None ) \ + HANDLER(PcmChannelMax, 424, MonitorCapability, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(CrashReportHash, 425, ErrorInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(ErrorReportSharePermission, 426, ErrorReportSharePermissionInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(VideoCodecTypeEnum, 427, MultimediaInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(VideoBitRate, 428, MultimediaInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(VideoFrameRate, 429, MultimediaInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(VideoWidth, 430, MultimediaInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(VideoHeight, 431, MultimediaInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(AudioCodecTypeEnum, 432, MultimediaInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(AudioSampleRate, 433, MultimediaInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(AudioChannelCount, 434, MultimediaInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(AudioBitRate, 435, MultimediaInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(MultimediaContainerType, 436, MultimediaInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(MultimediaProfileType, 437, MultimediaInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(MultimediaLevelType, 438, MultimediaInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(MultimediaCacheSizeEnum, 439, MultimediaInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(MultimediaErrorStatusEnum, 440, MultimediaInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(MultimediaErrorLog, 441, MultimediaInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(ServerFqdn, 442, ErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(ServerIpAddress, 443, ErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(TestStringEncrypt, 444, Test, FieldType_String, FieldFlag_Encrypt) \ + HANDLER(TestU8ArrayEncrypt, 445, Test, FieldType_U8Array, FieldFlag_Encrypt) \ + HANDLER(TestU32ArrayEncrypt, 446, Test, FieldType_U32Array, FieldFlag_Encrypt) \ + HANDLER(TestU64ArrayEncrypt, 447, Test, FieldType_U64Array, FieldFlag_Encrypt) \ + HANDLER(TestI32ArrayEncrypt, 448, Test, FieldType_I32Array, FieldFlag_Encrypt) \ + HANDLER(TestI64ArrayEncrypt, 449, Test, FieldType_I64Array, FieldFlag_Encrypt) \ + HANDLER(CipherKey, 450, ErrorInfoAuto, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(FileSystemPath, 451, ErrorInfo, FieldType_String, FieldFlag_Encrypt) \ + HANDLER(WebMediaPlayerOpenUrl, 452, ErrorInfo, FieldType_String, FieldFlag_Encrypt) \ + HANDLER(WebMediaPlayerLastSocketErrors, 453, ErrorInfo, FieldType_I32Array, FieldFlag_None ) \ + HANDLER(UnknownControllerCount, 454, ConnectedControllerInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(AttachedControllerCount, 455, ConnectedControllerInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(BluetoothControllerCount, 456, ConnectedControllerInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(UsbControllerCount, 457, ConnectedControllerInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(ControllerTypeList, 458, ConnectedControllerInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(ControllerInterfaceList, 459, ConnectedControllerInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(ControllerStyleList, 460, ConnectedControllerInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(FsPooledBufferPeakFreeSize, 461, FsMemoryInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(FsPooledBufferRetriedCount, 462, FsMemoryInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(FsPooledBufferReduceAllocationCount, 463, FsMemoryInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(FsBufferManagerPeakFreeSize, 464, FsMemoryInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(FsBufferManagerRetriedCount, 465, FsMemoryInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(FsExpHeapPeakFreeSize, 466, FsMemoryInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(FsBufferPoolPeakFreeSize, 467, FsMemoryInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(FsPatrolReadAllocateBufferSuccessCount, 468, FsMemoryInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(FsPatrolReadAllocateBufferFailureCount, 469, FsMemoryInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(SteadyClockInternalOffset, 470, ErrorInfoAuto, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(SteadyClockCurrentTimePointValue, 471, ErrorInfoAuto, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(UserClockContextOffset, 472, UserClockContextInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(UserClockContextTimeStampValue, 473, UserClockContextInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(NetworkClockContextOffset, 474, NetworkClockContextInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(NetworkClockContextTimeStampValue, 475, NetworkClockContextInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(SystemAbortFlag, 476, ErrorInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(ApplicationAbortFlag, 477, ErrorInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(NifmErrorCode, 478, ConnectionStatusInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(LcsApplicationId, 479, ErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(LcsContentMetaKeyIdList, 480, ErrorInfo, FieldType_U64Array, FieldFlag_None ) \ + HANDLER(LcsContentMetaKeyVersionList, 481, ErrorInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(LcsContentMetaKeyTypeList, 482, ErrorInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(LcsContentMetaKeyInstallTypeList, 483, ErrorInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(LcsSenderFlag, 484, ErrorInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(LcsApplicationRequestFlag, 485, ErrorInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(LcsHasExFatDriverFlag, 486, ErrorInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(LcsIpAddress, 487, ErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(AcpStartupUserAccount, 488, AcpUserAccountSettingsInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(AcpAocRegistrationType, 489, AcpAocSettingsInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(AcpAttributeFlag, 490, AcpGeneralSettingsInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(AcpSupportedLanguageFlag, 491, AcpGeneralSettingsInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(AcpParentalControlFlag, 492, AcpGeneralSettingsInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(AcpScreenShot, 493, AcpGeneralSettingsInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(AcpVideoCapture, 494, AcpGeneralSettingsInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(AcpDataLossConfirmation, 495, AcpGeneralSettingsInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(AcpPlayLogPolicy, 496, AcpPlayLogSettingsInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(AcpPresenceGroupId, 497, AcpGeneralSettingsInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(AcpRatingAge, 498, AcpRatingSettingsInfo, FieldType_I8Array, FieldFlag_None ) \ + HANDLER(AcpAocBaseId, 499, AcpAocSettingsInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(AcpSaveDataOwnerId, 500, AcpStorageSettingsInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(AcpUserAccountSaveDataSize, 501, AcpStorageSettingsInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(AcpUserAccountSaveDataJournalSize, 502, AcpStorageSettingsInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(AcpDeviceSaveDataSize, 503, AcpStorageSettingsInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(AcpDeviceSaveDataJournalSize, 504, AcpStorageSettingsInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(AcpBcatDeliveryCacheStorageSize, 505, AcpBcatSettingsInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(AcpApplicationErrorCodeCategory, 506, AcpGeneralSettingsInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(AcpLocalCommunicationId, 507, AcpGeneralSettingsInfo, FieldType_U64Array, FieldFlag_None ) \ + HANDLER(AcpLogoType, 508, AcpGeneralSettingsInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(AcpLogoHandling, 509, AcpGeneralSettingsInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(AcpRuntimeAocInstall, 510, AcpAocSettingsInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(AcpCrashReport, 511, AcpGeneralSettingsInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(AcpHdcp, 512, AcpGeneralSettingsInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(AcpSeedForPseudoDeviceId, 513, AcpGeneralSettingsInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(AcpBcatPassphrase, 514, AcpBcatSettingsInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(AcpUserAccountSaveDataSizeMax, 515, AcpStorageSettingsInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(AcpUserAccountSaveDataJournalSizeMax, 516, AcpStorageSettingsInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(AcpDeviceSaveDataSizeMax, 517, AcpStorageSettingsInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(AcpDeviceSaveDataJournalSizeMax, 518, AcpStorageSettingsInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(AcpTemporaryStorageSize, 519, AcpStorageSettingsInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(AcpCacheStorageSize, 520, AcpStorageSettingsInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(AcpCacheStorageJournalSize, 521, AcpStorageSettingsInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(AcpCacheStorageDataAndJournalSizeMax, 522, AcpStorageSettingsInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(AcpCacheStorageIndexMax, 523, AcpStorageSettingsInfo, FieldType_NumericI16, FieldFlag_None ) \ + HANDLER(AcpPlayLogQueryableApplicationId, 524, AcpPlayLogSettingsInfo, FieldType_U64Array, FieldFlag_None ) \ + HANDLER(AcpPlayLogQueryCapability, 525, AcpPlayLogSettingsInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(AcpRepairFlag, 526, AcpGeneralSettingsInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(RunningApplicationPatchStorageLocation, 527, RunningApplicationInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(RunningApplicationVersionNumber, 528, RunningApplicationInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(FsRecoveredByInvalidateCacheCount, 529, FsProxyErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(FsSaveDataIndexCount, 530, FsProxyErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(FsBufferManagerPeakTotalAllocatableSize, 531, FsMemoryInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(MonitorCurrentWidth, 532, MonitorSettings, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(MonitorCurrentHeight, 533, MonitorSettings, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(MonitorCurrentRefreshRate, 534, MonitorSettings, FieldType_String, FieldFlag_None ) \ + HANDLER(RebootlessSystemUpdateVersion, 535, RebootlessSystemUpdateVersionInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(EncryptedExceptionInfo1, 536, ErrorInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(EncryptedExceptionInfo2, 537, ErrorInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(EncryptedExceptionInfo3, 538, ErrorInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(EncryptedDyingMessage, 539, ErrorInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(DramIdDeprecated, 540, PowerClockInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NifmConnectionTestRedirectUrl, 541, NifmConnectionTestInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(AcpRequiredNetworkServiceLicenseOnLaunchFlag, 542, AcpUserAccountSettingsInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(PciePort0Flags, 543, PcieLoggedStateInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(PciePort0Speed, 544, PcieLoggedStateInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(PciePort0ResetTimeInUs, 545, PcieLoggedStateInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(PciePort0IrqCount, 546, PcieLoggedStateInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(PciePort0Statistics, 547, PcieLoggedStateInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(PciePort1Flags, 548, PcieLoggedStateInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(PciePort1Speed, 549, PcieLoggedStateInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(PciePort1ResetTimeInUs, 550, PcieLoggedStateInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(PciePort1IrqCount, 551, PcieLoggedStateInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(PciePort1Statistics, 552, PcieLoggedStateInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(PcieFunction0VendorId, 553, PcieLoggedStateInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(PcieFunction0DeviceId, 554, PcieLoggedStateInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(PcieFunction0PmState, 555, PcieLoggedStateInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(PcieFunction0IsAcquired, 556, PcieLoggedStateInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(PcieFunction1VendorId, 557, PcieLoggedStateInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(PcieFunction1DeviceId, 558, PcieLoggedStateInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(PcieFunction1PmState, 559, PcieLoggedStateInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(PcieFunction1IsAcquired, 560, PcieLoggedStateInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(PcieGlobalRootComplexStatistics, 561, PcieLoggedStateInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(PciePllResistorCalibrationValue, 562, PcieLoggedStateInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(CertificateRequestedHostName, 563, NetworkSecurityCertificateInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(CertificateCommonName, 564, NetworkSecurityCertificateInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(CertificateSANCount, 565, NetworkSecurityCertificateInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(CertificateSANs, 566, NetworkSecurityCertificateInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(FsBufferPoolMaxAllocateSize, 567, FsMemoryInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(CertificateIssuerName, 568, NetworkSecurityCertificateInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(ApplicationAliveTime, 569, ErrorInfoAuto, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(ApplicationInFocusTime, 570, ErrorInfoAuto, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(ApplicationOutOfFocusTime, 571, ErrorInfoAuto, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(ApplicationBackgroundFocusTime, 572, ErrorInfoAuto, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(AcpUserAccountSwitchLock, 573, AcpUserAccountSettingsInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(USB3HostAvailableFlag, 574, USB3AvailableInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(USB3DeviceAvailableFlag, 575, USB3AvailableInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(AcpNeighborDetectionClientConfigurationSendDataId, 576, AcpNeighborDetectionInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(AcpNeighborDetectionClientConfigurationReceivableDataIds, 577, AcpNeighborDetectionInfo, FieldType_U64Array, FieldFlag_None ) \ + HANDLER(AcpStartupUserAccountOptionFlag, 578, AcpUserAccountSettingsInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(ServerErrorCode, 579, ErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(AppletManagerMetaLogTrace, 580, ErrorInfo, FieldType_U64Array, FieldFlag_None ) \ + HANDLER(ServerCertificateSerialNumber, 581, NetworkSecurityCertificateInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(ServerCertificatePublicKeyAlgorithm, 582, NetworkSecurityCertificateInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(ServerCertificateSignatureAlgorithm, 583, NetworkSecurityCertificateInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(ServerCertificateNotBefore, 584, NetworkSecurityCertificateInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(ServerCertificateNotAfter, 585, NetworkSecurityCertificateInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(CertificateAlgorithmInfoBits, 586, NetworkSecurityCertificateInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(TlsConnectionPeerIpAddress, 587, NetworkSecurityCertificateInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(TlsConnectionLastHandshakeState, 588, NetworkSecurityCertificateInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(TlsConnectionInfoBits, 589, NetworkSecurityCertificateInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(SslStateBits, 590, NetworkSecurityCertificateInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(SslProcessInfoBits, 591, NetworkSecurityCertificateInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(SslProcessHeapSize, 592, NetworkSecurityCertificateInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(SslBaseErrorCode, 593, NetworkSecurityCertificateInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(GpuCrashDumpSize, 594, GpuCrashInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GpuCrashDump, 595, GpuCrashInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(RunningApplicationProgramIndex, 596, RunningApplicationInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(UsbTopology, 597, UsbStateInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(AkamaiReferenceId, 598, ErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(NvHostErrID, 599, NvHostErrInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NvHostErrDataArrayU32, 600, NvHostErrInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(HasSyslogFlag, 601, ErrorInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(AcpRuntimeParameterDelivery, 602, AcpGeneralSettingsInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(PlatformRegion, 603, RegionSettingInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(RunningUlaApplicationId, 604, RunningUlaInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(RunningUlaAppletId, 605, RunningUlaInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(RunningUlaVersion, 606, RunningUlaInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(RunningUlaApplicationStorageLocation, 607, RunningUlaInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(RunningUlaPatchStorageLocation, 608, RunningUlaInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(NANDTotalSizeOfSystem, 609, NANDFreeSpaceInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(NANDFreeSpaceOfSystem, 610, NANDFreeSpaceInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(AccessPointSSIDAsHex, 611, AccessPointInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(PanelVendorId, 612, InternalPanelInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(PanelRevisionId, 613, InternalPanelInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(PanelModelId, 614, InternalPanelInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(ErrorContext, 615, ErrorInfoAuto, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(ErrorContextSize, 616, ErrorInfoAuto, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(ErrorContextTotalSize, 617, ErrorInfoAuto, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(SystemPhysicalMemoryLimit, 618, ResourceLimitLimitInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(SystemThreadCountLimit, 619, ResourceLimitLimitInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(SystemEventCountLimit, 620, ResourceLimitLimitInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(SystemTransferMemoryCountLimit, 621, ResourceLimitLimitInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(SystemSessionCountLimit, 622, ResourceLimitLimitInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(SystemPhysicalMemoryPeak, 623, ResourceLimitPeakInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(SystemThreadCountPeak, 624, ResourceLimitPeakInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(SystemEventCountPeak, 625, ResourceLimitPeakInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(SystemTransferMemoryCountPeak, 626, ResourceLimitPeakInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(SystemSessionCountPeak, 627, ResourceLimitPeakInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(GpuCrashHash, 628, GpuCrashInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(TouchScreenPanelGpioValue, 629, TouchScreenInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(BrowserCertificateHostName, 630, ErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(BrowserCertificateCommonName, 631, ErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(BrowserCertificateOrganizationalUnitName, 632, ErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(FsPooledBufferFailedIdealAllocationCountOnAsyncAccess, 633, FsMemoryInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(AudioOutputTarget, 634, AudioDeviceInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(AudioOutputChannelCount, 635, AudioDeviceInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(AppletTotalActiveTime, 636, ErrorInfoAuto, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(WakeCount, 637, AbnormalWakeInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(PredominantWakeReason, 638, AbnormalWakeInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(EdidExtensionBlock2, 639, EdidInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(EdidExtensionBlock3, 640, EdidInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(LumenRequestId, 641, ErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(LlnwLlid, 642, ErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(SupportingLimitedApplicationLicenses, 643, RunningApplicationInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(RuntimeLimitedApplicationLicenseUpgrade, 644, RunningApplicationInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(ServiceProfileRevisionKey, 645, ServiceProfileInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(BluetoothAudioConnectionCount, 646, BluetoothAudioInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(BluetoothHidPairingInfoCountDeprecated, 647, BluetoothPairingCountInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(BluetoothAudioPairingInfoCountDeprecated, 648, BluetoothPairingCountInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(BluetoothLePairingInfoCountDeprecated, 649, BluetoothPairingCountInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(FatFsBisSystemFilePeakOpenCount, 650, FsProxyErrorInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(FatFsBisSystemDirectoryPeakOpenCount, 651, FsProxyErrorInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(FatFsBisUserFilePeakOpenCount, 652, FsProxyErrorInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(FatFsBisUserDirectoryPeakOpenCount, 653, FsProxyErrorInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(FatFsSdCardFilePeakOpenCount, 654, FsProxyErrorInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(FatFsSdCardDirectoryPeakOpenCount, 655, FsProxyErrorInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(SslAlertInfo, 656, NetworkSecurityCertificateInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(SslVersionInfo, 657, NetworkSecurityCertificateInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(FatFsBisSystemUniqueFileEntryPeakOpenCount, 658, FsProxyErrorInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(FatFsBisSystemUniqueDirectoryEntryPeakOpenCount, 659, FsProxyErrorInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(FatFsBisUserUniqueFileEntryPeakOpenCount, 660, FsProxyErrorInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(FatFsBisUserUniqueDirectoryEntryPeakOpenCount, 661, FsProxyErrorInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(FatFsSdCardUniqueFileEntryPeakOpenCount, 662, FsProxyErrorInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(FatFsSdCardUniqueDirectoryEntryPeakOpenCount, 663, FsProxyErrorInfo, FieldType_NumericU16, FieldFlag_None ) \ + HANDLER(ServerErrorIsRetryable, 664, ErrorInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(FsDeepRetryStartCount, 665, FsProxyErrorInfo2, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(FsUnrecoverableByGameCardAccessFailedCount, 666, FsProxyErrorInfo2, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(BuiltInWirelessOUI, 667, BuiltInWirelessOUIInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(WirelessAPOUI, 668, WirelessAPOUIInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(EthernetAdapterOUI, 669, EthernetAdapterOUIInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(FatFsBisSystemFatSafeControlResult, 670, FsProxyErrorInfo2, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(FatFsBisSystemFatErrorNumber, 671, FsProxyErrorInfo2, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(FatFsBisSystemFatSafeErrorNumber, 672, FsProxyErrorInfo2, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(FatFsBisUserFatSafeControlResult, 673, FsProxyErrorInfo2, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(FatFsBisUserFatErrorNumber, 674, FsProxyErrorInfo2, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(FatFsBisUserFatSafeErrorNumber, 675, FsProxyErrorInfo2, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(GpuCrashDump2, 676, GpuCrashInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(NANDTypeDeprecated, 677, NANDTypeInfoDeprecated, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(MicroSDType, 678, MicroSDTypeInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(GameCardLastDeactivateReasonResult, 679, GameCardErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GameCardLastDeactivateReason, 680, GameCardErrorInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(InvalidErrorCode, 681, ErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(AppletId, 682, ApplicationInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(PrevReportIdentifier, 683, ErrorInfoAuto, FieldType_String, FieldFlag_None ) \ + HANDLER(SyslogStartupTimeBase, 684, ErrorInfoAuto, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(NxdmpIsAttached, 685, AttachmentFileInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(ScreenshotIsAttached, 686, AttachmentFileInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(SyslogIsAttached, 687, AttachmentFileInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(SaveSyslogResult, 688, AttachmentFileInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(EncryptionKeyGeneration, 689, ErrorInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(FsBufferManagerNonBlockingRetriedCount, 690, FsMemoryInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(FsPooledBufferNonBlockingRetriedCount, 691, FsMemoryInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(LastConnectionTestDownloadSpeed64, 692, ConnectionInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(LastConnectionTestUploadSpeed64, 693, ConnectionInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(EncryptionKeyV1, 694, ErrorInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(GpuCrashDumpAttachmentId, 695, GpuCrashInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(GpuCrashDumpIsAttached, 696, AttachmentFileInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(CallerIdentifier, 697, ErrorInfo, FieldType_NumericU64, FieldFlag_None ) \ + HANDLER(WlanMainState, 698, WlanInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(WlanSubState, 699, WlanInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(AdspSyslogIsAttached, 702, AttachmentFileInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(LastHalfAwakeTime, 703, HalfAwakeStateInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(LastHalfAwakeTimeAfterBackgroundTaskDone, 704, HalfAwakeStateInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(LastHalfAwakeTimeAfterStateUnlocked, 705, HalfAwakeStateInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(LastHalfAwakePowerStateMessage, 706, HalfAwakeStateInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(FastlyRequestId, 707, ErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(CloudflareCfRay, 708, ErrorInfo, FieldType_String, FieldFlag_None ) \ + HANDLER(WlanCommandEventHistory, 709, WlanInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(FsSaveDataAttributeCheckFailureCount, 710, FsProxyErrorInfo2, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(PctlIsRestrictionEnabled, 711, PctlSettingInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(PctlIsPairingActive, 712, PctlSettingInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(PctlSafetyLevel, 713, PctlSettingInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(PctlRatingAge, 714, PctlSettingInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(PctlRatingOrganization, 715, PctlSettingInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(PctlIsSnsPostRestricted, 716, PctlSettingInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(PctlIsFreeCommunicationRestrictedByDefault, 717, PctlSettingInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(PctlIsStereoVisionRestricted, 718, PctlSettingInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(PctlRestrictedFreeCommunicationApplicationIdList, 719, PctlSettingInfo, FieldType_U64Array, FieldFlag_None ) \ + HANDLER(PctlExemptApplicationIdList, 720, PctlSettingInfo, FieldType_U64Array, FieldFlag_None ) \ + HANDLER(GameCardLogEncryptionKeyIndex, 721, GameCardLogInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GameCardLogEncryptedKey, 722, GameCardLogInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(GameCardAsicHandlerLogLength, 723, GameCardLogInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GameCardWorkerLogLength, 724, GameCardLogInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GameCardAsicHandlerLogTimeStamp, 725, GameCardLogInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(GameCardWorkerLogTimeStamp, 726, GameCardLogInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(GameCardEncryptedAsicHandlerLog, 727, GameCardLogInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(GameCardEncryptedWorkerLog, 728, GameCardLogInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(WlanIoctlErrno, 729, ErrorInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(FsSaveDataCertificateVerificationFailureCount, 730, FsProxyErrorInfo2, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(SdCardActivationMilliSeconds, 731, SdCardActivationInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GameCardLastAwakenFailureResult, 732, GameCardDetailedErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(GameCardInsertedTimestamp, 733, GameCardDetailedErrorInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(GameCardPreviousInsertedTimestamp, 734, GameCardDetailedErrorInfo, FieldType_NumericI64, FieldFlag_None ) \ + HANDLER(WlanChipResetTriggered, 735, WlanInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(TestStringNx, 1000, TestNx, FieldType_String, FieldFlag_None ) \ + HANDLER(BoostModeCurrentLimit, 1001, BatteryChargeInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(ChargeConfiguration, 1002, BatteryChargeInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(HizMode, 1003, BatteryChargeInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(PowerSupplyPath, 1004, BatteryChargeInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(ControllerPowerSupplyAcquired, 1005, BatteryChargeInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(OtgRequested, 1006, BatteryChargeInfo, FieldType_Bool, FieldFlag_None ) \ + HANDLER(AdspExceptionRegisters, 1007, AdspErrorInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(AdspExceptionSpsr, 1008, AdspErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(AdspExceptionArmModeRegisters, 1009, AdspErrorInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(AdspExceptionStackAddress, 1010, AdspErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(AdspExceptionStackDump, 1011, AdspErrorInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(AdspExceptionReason, 1012, AdspErrorInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(CpuDvfsTableClocks, 1013, PowerClockInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(CpuDvfsTableVoltages, 1014, PowerClockInfo, FieldType_I32Array, FieldFlag_None ) \ + HANDLER(GpuDvfsTableClocks, 1015, PowerClockInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(GpuDvfsTableVoltages, 1016, PowerClockInfo, FieldType_I32Array, FieldFlag_None ) \ + HANDLER(EmcDvfsTableClocks, 1017, PowerClockInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(EmcDvfsTableVoltages, 1018, PowerClockInfo, FieldType_I32Array, FieldFlag_None ) \ + HANDLER(PowerDomainEnableFlags, 1019, PowerClockInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(PowerDomainVoltages, 1020, PowerClockInfo, FieldType_I32Array, FieldFlag_None ) \ + HANDLER(FuseInfo, 1021, PowerClockInfo, FieldType_U32Array, FieldFlag_None ) \ + HANDLER(NANDType, 1022, NANDTypeInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(BluetoothHidPairingInfoCount, 1023, BluetoothPairingCountInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(BluetoothAudioPairingInfoCount, 1024, BluetoothPairingCountInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(BluetoothLePairingInfoCount, 1025, BluetoothPairingCountInfo, FieldType_NumericU8, FieldFlag_None ) \ + HANDLER(NANDPreEolInfo, 1026, NANDExtendedCsd, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NANDDeviceLifeTimeEstTypA, 1027, NANDExtendedCsd, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(NANDDeviceLifeTimeEstTypB, 1028, NANDExtendedCsd, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(OscillatorClock, 1029, PowerClockInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(DramId, 1030, PowerClockInfo, FieldType_NumericU32, FieldFlag_None ) \ + HANDLER(LastDvfsThresholdTripped, 1031, ThermalInfo, FieldType_NumericI32, FieldFlag_None ) \ + HANDLER(ModuleClockEnableFlags, 1032, PowerClockInfo, FieldType_U8Array, FieldFlag_None ) \ + HANDLER(ModulePowerEnableFlags, 1033, PowerClockInfo, FieldType_U8Array, FieldFlag_None ) \ + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/erpt_multiple_category_context.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/erpt_multiple_category_context.hpp new file mode 100644 index 00000000..c6930623 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/erpt_multiple_category_context.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/erpt/erpt_types.hpp> +#include <stratosphere/sf/sf_buffer_tags.hpp> + +namespace ams::erpt { + + constexpr inline u32 CategoriesPerMultipleCategoryContext = 0x10; + constexpr inline u32 FieldsPerMultipleCategoryContext = CategoriesPerMultipleCategoryContext * 4; + + struct MultipleCategoryContextEntry : public sf::LargeData, public sf::PrefersMapAliasTransferMode { + u32 version; + u32 category_count; + CategoryId categories[CategoriesPerMultipleCategoryContext]; + u32 field_counts[CategoriesPerMultipleCategoryContext]; + u32 array_buf_counts[CategoriesPerMultipleCategoryContext]; + FieldEntry fields[FieldsPerMultipleCategoryContext]; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/erpt_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/erpt_types.hpp new file mode 100644 index 00000000..3d31b2c4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/erpt_types.hpp @@ -0,0 +1,240 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/time/time_posix_time.hpp> +#include <stratosphere/erpt/erpt_ids.autogen.hpp> + +namespace ams::erpt { + + #define GENERATE_ENUM(NAME, ID, ...) NAME = ID, + + enum FieldType { + AMS_ERPT_FOREACH_FIELD_TYPE(GENERATE_ENUM) + FieldType_Count, + }; + + #undef GENERATE_ENUM + + #define GENERATE_ENUM(NAME, ID, ...) CategoryId_##NAME = ID, + + enum CategoryId { + AMS_ERPT_FOREACH_CATEGORY(GENERATE_ENUM) + }; + + #undef GENERATE_ENUM + + #define GENERATE_ENUM(NAME, ID, ...) FieldId_##NAME = ID, + + enum FieldId { + AMS_ERPT_FOREACH_FIELD(GENERATE_ENUM) + }; + + #undef GENERATE_ENUM + + constexpr inline u32 ArrayBufferSizeDefault = 0x100; + constexpr inline u32 ArrayBufferSizeMax = 96_KB; + constexpr inline u32 ArrayFieldSizeMax = 16_KB - 1; + + enum ReportType { + ReportType_Start = 0, + + ReportType_Visible = ReportType_Start, + ReportType_Invisible = 1, + + ReportType_End = 2, + + ReportType_Count = ReportType_End, + + ReportType_Any = ReportType_Count, + }; + + constexpr inline u32 ReportCountMax = 50; + constexpr inline u32 AttachmentsPerReportMax = 5; + constexpr inline u32 AttachmentCountMax = ReportCountMax * AttachmentsPerReportMax; + + constexpr inline u32 ReportMetaDataSize = 0x20; + struct ReportMetaData { + u8 user_data[ReportMetaDataSize]; + }; + + + constexpr inline u32 ReportIdSize = 20; + struct ReportId { + union { + u8 id[ReportIdSize]; + util::Uuid uuid; + #pragma pack(push, 1) + struct { + u32 time_low; + u16 time_mid; + u16 time_high_and_version; + u8 clock_high; + u8 clock_low; + u64 node; + } uuid_data; + #pragma pack(pop) + }; + }; + static_assert(sizeof(ReportId) == ReportIdSize); + + inline bool operator==(const ReportId &lhs, const ReportId &rhs) { + return std::memcmp(lhs.id, rhs.id, sizeof(lhs.uuid)) == 0; + } + + inline bool operator!=(const ReportId &lhs, const ReportId &rhs) { + return !(lhs == rhs); + } + + struct ReportFlag { + using Transmitted = util::BitFlagSet<BITSIZEOF(u32), ReportFlag>::Flag<0>; + using HasAttachment = util::BitFlagSet<BITSIZEOF(u32), ReportFlag>::Flag<1>; + }; + + using ReportFlagSet = util::BitFlagSet<BITSIZEOF(u32), ReportFlag>; + static_assert(util::is_pod<ReportFlagSet>::value); + static_assert(sizeof(ReportFlagSet) == sizeof(u32)); + + struct CreateReportOptionFlag { + using SubmitFsInfo = util::BitFlagSet<BITSIZEOF(u32), CreateReportOptionFlag>::Flag<0>; + }; + + using CreateReportOptionFlagSet = util::BitFlagSet<BITSIZEOF(u32), CreateReportOptionFlag>; + static_assert(util::is_pod<CreateReportOptionFlagSet>::value); + static_assert(sizeof(CreateReportOptionFlagSet) == sizeof(u32)); + + struct ReportInfo { + ReportType type; + ReportId id; + ReportMetaData meta_data; + ReportFlagSet flags; + time::PosixTime timestamp_user; + time::PosixTime timestamp_network; + s64 report_size; + u64 reserved[3]; + }; + + struct ReportList { + u32 report_count; + ReportInfo reports[ReportCountMax]; + }; + + constexpr inline u32 AttachmentIdSize = 20; + struct AttachmentId { + union { + u8 id[AttachmentIdSize]; + util::Uuid uuid; + }; + }; + static_assert(sizeof(AttachmentId) == AttachmentIdSize); + + inline bool operator==(const AttachmentId &lhs, const AttachmentId &rhs) { + return std::memcmp(lhs.id, rhs.id, sizeof(lhs.uuid)) == 0; + } + + inline bool operator!=(const AttachmentId &lhs, const AttachmentId &rhs) { + return !(lhs == rhs); + } + + struct AttachmentFlag { + using HasOwner = util::BitFlagSet<BITSIZEOF(u32), AttachmentFlag>::Flag<1>; + }; + + using AttachmentFlagSet = util::BitFlagSet<BITSIZEOF(u32), AttachmentFlag>; + static_assert(util::is_pod<AttachmentFlagSet>::value); + static_assert(sizeof(AttachmentFlagSet) == sizeof(u32)); + + constexpr inline u32 AttachmentNameSizeMax = 0x20; + struct AttachmentInfo { + ReportId owner_report_id; + AttachmentId attachment_id; + AttachmentFlagSet flags; + s64 attachment_size; + char attachment_name[AttachmentNameSizeMax]; + }; + + struct AttachmentList { + u32 attachment_count; + AttachmentInfo attachments[AttachmentsPerReportMax]; + }; + + constexpr inline u32 AttachmentSizeMax = 512_KB; + + struct FieldEntry { + FieldId id; + FieldType type; + union { + u64 value_u64; + u32 value_u32; + u16 value_u16; + u8 value_u8; + s64 value_i64; + s32 value_i32; + s16 value_i16; + s8 value_i8; + bool value_bool; + struct { + u32 start_idx; + u32 size; + } value_array; + }; + }; + + constexpr inline u32 FieldsPerContext = 20; + struct ContextEntry { + u32 version; + u32 field_count; + CategoryId category; + FieldEntry fields[FieldsPerContext]; + u8 *array_buffer; + u32 array_free_count; + u32 array_buffer_size; + }; + + struct StorageUsageStatistics { + util::Uuid journal_uuid; + u32 used_storage_size; + s64 max_report_size; + u32 report_count[ReportType_Count]; + u32 transmitted_count[ReportType_Count]; + u32 untransmitted_count[ReportType_Count]; + }; + + /* https://github.com/msgpack/msgpack/blob/master/spec.md#overview */ + enum class ValueTypeTag { + FixMap = 0x80, + FixArray = 0x90, + FixStr = 0xA0, + False = 0xC2, + True = 0xC3, + Bin8 = 0xC4, + Bin16 = 0xC5, + U8 = 0xCC, + U16 = 0xCD, + U32 = 0xCE, + U64 = 0xCF, + I8 = 0xD0, + I16 = 0xD1, + I32 = 0xD2, + I64 = 0xD3, + Str8 = 0xD9, + Str16 = 0xDA, + Array16 = 0xDC, + Map16 = 0xDE, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/sf/erpt_sf_i_attachment.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/sf/erpt_sf_i_attachment.hpp new file mode 100644 index 00000000..2aafa3fe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/sf/erpt_sf_i_attachment.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/erpt/erpt_types.hpp> + +#define AMS_ERPT_I_ATTACHMENT_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, Open, (const erpt::AttachmentId &attachment_id), (attachment_id)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, Read, (ams::sf::Out<u32> out_count, const ams::sf::OutBuffer &out_buffer), (out_count, out_buffer)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, SetFlags, (erpt::AttachmentFlagSet flags), (flags)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, GetFlags, (ams::sf::Out<erpt::AttachmentFlagSet> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, Close, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, GetSize, (ams::sf::Out<s64> out), (out)) + +AMS_SF_DEFINE_INTERFACE(ams::erpt::sf, IAttachment, AMS_ERPT_I_ATTACHMENT_INTERFACE_INFO, 0x10FC4A69) \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/sf/erpt_sf_i_context.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/sf/erpt_sf_i_context.hpp new file mode 100644 index 00000000..a4fcc4dc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/sf/erpt_sf_i_context.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/erpt/erpt_types.hpp> +#include <stratosphere/erpt/erpt_multiple_category_context.hpp> +#include <stratosphere/time/time_steady_clock_time_point.hpp> + +#define AMS_ERPT_I_CONTEXT_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, SubmitContext, (const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &str_buffer), (ctx_buffer, str_buffer)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, CreateReportV0, (erpt::ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &str_buffer, const ams::sf::InBuffer &meta_buffer), (report_type, ctx_buffer, str_buffer, meta_buffer)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, SetInitialLaunchSettingsCompletionTime, (const time::SteadyClockTimePoint &time_point), (time_point), hos::Version_3_0_0) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, ClearInitialLaunchSettingsCompletionTime, (), (), hos::Version_3_0_0) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, UpdatePowerOnTime, (), (), hos::Version_3_0_0) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, UpdateAwakeTime, (), (), hos::Version_3_0_0, hos::Version_12_0_0) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, SubmitMultipleCategoryContext, (const erpt::MultipleCategoryContextEntry &ctx_entry, const ams::sf::InBuffer &str_buffer), (ctx_entry, str_buffer), hos::Version_5_0_0, hos::Version_12_0_0) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, UpdateApplicationLaunchTime, (), (), hos::Version_6_0_0) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, ClearApplicationLaunchTime, (), (), hos::Version_6_0_0) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, SubmitAttachment, (ams::sf::Out<erpt::AttachmentId> out, const ams::sf::InBuffer &attachment_name, const ams::sf::InBuffer &attachment_data), (out, attachment_name, attachment_data), hos::Version_8_0_0) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, CreateReportWithAttachmentsDeprecated, (erpt::ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &str_buffer, const ams::sf::InBuffer &attachment_ids_buffer), (report_type, ctx_buffer, str_buffer, attachment_ids_buffer), hos::Version_8_0_0, hos::Version_10_2_0) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, CreateReportWithAttachmentsDeprecated2, (erpt::ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &str_buffer, const ams::sf::InBuffer &attachment_ids_buffer, Result result), (report_type, ctx_buffer, str_buffer, attachment_ids_buffer, result), hos::Version_11_0_0, hos::Version_16_1_0) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, CreateReportWithAttachments, (erpt::ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &str_buffer, const ams::sf::InBuffer &attachment_ids_buffer, Result result, erpt::CreateReportOptionFlagSet flags), (report_type, ctx_buffer, str_buffer, attachment_ids_buffer, result, flags), hos::Version_17_0_0) \ + AMS_SF_METHOD_INFO(C, H, 11, Result, CreateReportV1, (erpt::ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &str_buffer, const ams::sf::InBuffer &meta_buffer, Result result), (report_type, ctx_buffer, str_buffer, meta_buffer, result), hos::Version_11_0_0) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, CreateReport, (erpt::ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &str_buffer, const ams::sf::InBuffer &meta_buffer, Result result, erpt::CreateReportOptionFlagSet flags), (report_type, ctx_buffer, str_buffer, meta_buffer, result, flags), hos::Version_17_0_0) \ + AMS_SF_METHOD_INFO(C, H, 13, Result, SubmitAttachmentWithLz4Compression, (ams::sf::Out<erpt::AttachmentId> out, const ams::sf::InBuffer &attachment_name, const ams::sf::InBuffer &attachment_data), (out, attachment_name, attachment_data), hos::Version_20_0_0) \ + AMS_SF_METHOD_INFO(C, H, 14, Result, CreateReportWithSpecifiedReprotId, (erpt::ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &str_buffer, const ams::sf::InBuffer &meta_buffer, const ams::sf::InBuffer &attachment_ids_buffer, Result result, erpt::CreateReportOptionFlagSet flags, const erpt::ReportId &report_id), (report_type, ctx_buffer, str_buffer, meta_buffer, attachment_ids_buffer, result, flags, report_id), hos::Version_20_0_0) \ + AMS_SF_METHOD_INFO(C, H, 20, Result, RegisterRunningApplet, (ncm::ProgramId program_id), (program_id), hos::Version_12_0_0) \ + AMS_SF_METHOD_INFO(C, H, 21, Result, UnregisterRunningApplet, (ncm::ProgramId program_id), (program_id), hos::Version_12_0_0) \ + AMS_SF_METHOD_INFO(C, H, 22, Result, UpdateAppletSuspendedDuration, (ncm::ProgramId program_id, TimeSpanType duration), (program_id, duration), hos::Version_12_0_0) \ + AMS_SF_METHOD_INFO(C, H, 30, Result, InvalidateForcedShutdownDetection, (), (), hos::Version_12_0_0) + + +AMS_SF_DEFINE_INTERFACE(ams::erpt::sf, IContext, AMS_ERPT_I_CONTEXT_INTERFACE_INFO, 0xDD41DD03) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/sf/erpt_sf_i_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/sf/erpt_sf_i_manager.hpp new file mode 100644 index 00000000..e75bfb34 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/sf/erpt_sf_i_manager.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/erpt/erpt_types.hpp> + +#define AMS_ERPT_I_MANAGER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GetReportList, (const ams::sf::OutBuffer &out_list, erpt::ReportType type_filter), (out_list, type_filter)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, GetEvent, (ams::sf::OutCopyHandle out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, CleanupReports, (), (), hos::Version_4_0_0) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, DeleteReport, (const erpt::ReportId &report_id), (report_id), hos::Version_5_0_0) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, GetStorageUsageStatistics, (ams::sf::Out<erpt::StorageUsageStatistics> out), (out), hos::Version_5_0_0) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, GetAttachmentListDeprecated, (const ams::sf::OutBuffer &out_buf, const erpt::ReportId &report_id), (out_buf, report_id), hos::Version_8_0_0, hos::Version_19_0_1) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, GetAttachmentList, (ams::sf::Out<u32> out_count, const ams::sf::OutBuffer &out_buf, const erpt::ReportId &report_id), (out_count, out_buf, report_id), hos::Version_20_0_0) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, GetReportSizeMax, (ams::sf::Out<u32> out), (out), hos::Version_20_0_0) + + + +AMS_SF_DEFINE_INTERFACE(ams::erpt::sf, IManager, AMS_ERPT_I_MANAGER_INTERFACE_INFO, 0x5CFCC43F) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/sf/erpt_sf_i_report.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/sf/erpt_sf_i_report.hpp new file mode 100644 index 00000000..4b695255 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/sf/erpt_sf_i_report.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/erpt/erpt_types.hpp> + +#define AMS_ERPT_I_REPORT_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, Open, (const erpt::ReportId &report_id), (report_id)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, Read, (ams::sf::Out<u32> out_count, const ams::sf::OutBuffer &out_buffer), (out_count, out_buffer)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, SetFlags, (erpt::ReportFlagSet flags), (flags)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, GetFlags, (ams::sf::Out<erpt::ReportFlagSet> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, Close, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, GetSize, (ams::sf::Out<s64> out), (out)) + +AMS_SF_DEFINE_INTERFACE(ams::erpt::sf, IReport, AMS_ERPT_I_REPORT_INTERFACE_INFO, 0xE4CD5A61) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/sf/erpt_sf_i_session.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/sf/erpt_sf_i_session.hpp new file mode 100644 index 00000000..6bb17988 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/sf/erpt_sf_i_session.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/erpt/erpt_types.hpp> +#include <stratosphere/erpt/sf/erpt_sf_i_report.hpp> +#include <stratosphere/erpt/sf/erpt_sf_i_manager.hpp> +#include <stratosphere/erpt/sf/erpt_sf_i_attachment.hpp> + +#define AMS_ERPT_I_SESSION_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, OpenReport, (ams::sf::Out<ams::sf::SharedPointer<erpt::sf::IReport>> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, OpenManager, (ams::sf::Out<ams::sf::SharedPointer<erpt::sf::IManager>> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, OpenAttachment, (ams::sf::Out<ams::sf::SharedPointer<erpt::sf::IAttachment>> out), (out), hos::Version_8_0_0) + +AMS_SF_DEFINE_INTERFACE(ams::erpt::sf, ISession, AMS_ERPT_I_SESSION_INTERFACE_INFO, 0x00395188) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/srv/erpt_srv_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/srv/erpt_srv_api.hpp new file mode 100644 index 00000000..1feb10ee --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/srv/erpt_srv_api.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::erpt::srv { + + Result Initialize(u8 *mem, size_t mem_size); + Result InitializeAndStartService(); + + Result SetSerialNumberAndOsVersion(const char *sn, u32 sn_len, const char *os, u32 os_len, const char *os_priv, u32 os_priv_len); + Result SetProductModel(const char *model, u32 model_len); + Result SetRegionSetting(const char *region, u32 region_len); + + /* Atmosphere extension. */ + Result SetRedirectNewReportsToSdCard(bool redirect); + Result SetEnabledAutomaticReportCleanup(bool redirect); + + void Wait(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/srv/erpt_srv_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/srv/erpt_srv_types.hpp new file mode 100644 index 00000000..348f3cb5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/erpt/srv/erpt_srv_types.hpp @@ -0,0 +1,158 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/erpt/erpt_ids.autogen.hpp> + +namespace ams::erpt::srv { + + constexpr inline const char ReportOnSdStoragePath[] = "ersd"; + constexpr inline const char ReportOnSdStorageRootDirectoryPath[] = "ersd:/"; + + constexpr inline const char ReportStoragePath[] = "save"; + constexpr inline const char JournalFileName[] = "save:/journal"; + constexpr inline const char ForcedShutdownContextFileName[] = "save:/forced-shutdown"; + + constexpr size_t ReportFileNameLength = 64; + constexpr size_t AttachmentFileNameLength = 64; + constexpr size_t MaxFieldStringSize = 64; + + struct ReportFileName { + char name[ReportFileNameLength]; + }; + + struct AttachmentFileName { + char name[AttachmentFileNameLength]; + }; + + enum FieldFlag : u8 { + FieldFlag_None = 0, + FieldFlag_Encrypt = 1, + }; + + #define STRINGIZE_HANDLER(NAME, ...) #NAME, + constexpr inline const char * const FieldString[] = { + AMS_ERPT_FOREACH_FIELD(STRINGIZE_HANDLER) + }; + + constexpr inline const char * const CategoryString[] = { + AMS_ERPT_FOREACH_CATEGORY(STRINGIZE_HANDLER) + }; + + constexpr inline const char * const TypeString[] = { + AMS_ERPT_FOREACH_FIELD_TYPE(STRINGIZE_HANDLER) + }; + #undef STRINGIZE_HANDLER + + #define GET_FIELD_CATEGORY(FIELD, ID, CATEGORY, TYPE, FLAG) CategoryId_##CATEGORY, + constexpr inline const CategoryId FieldIndexToCategoryMap[] = { + AMS_ERPT_FOREACH_FIELD(GET_FIELD_CATEGORY) + }; + #undef GET_FIELD_CATEGORY + + #define GET_FIELD_TYPE(FIELD, ID, CATEGORY, TYPE, FLAG) TYPE, + constexpr inline const FieldType FieldIndexToTypeMap[] = { + AMS_ERPT_FOREACH_FIELD(GET_FIELD_TYPE) + }; + #undef GET_FIELD_TYPE + + #define GET_FIELD_FLAG(FIELD, ID, CATEGORY, TYPE, FLAG) FLAG, + constexpr inline const FieldFlag FieldIndexToFlagMap[] = { + AMS_ERPT_FOREACH_FIELD(GET_FIELD_FLAG) + }; + #undef GET_FIELD_FLAG + + #define GET_FIELD_ID(FIELD, ...) FieldId_##FIELD, + constexpr inline const FieldId FieldIndexToFieldIdMap[] = { + AMS_ERPT_FOREACH_FIELD(GET_FIELD_ID) + }; + #undef GET_FIELD_ID + + #define GET_CATEGORY_ID(CATEGORY, ...) CategoryId_##CATEGORY, + constexpr inline const CategoryId CategoryIndexToCategoryIdMap[] = { + AMS_ERPT_FOREACH_CATEGORY(GET_CATEGORY_ID) + }; + #undef GET_CATEGORY_ID + + constexpr util::optional<size_t> FindFieldIndex(FieldId id) { + if (std::is_constant_evaluated()) { + for (size_t i = 0; i < util::size(FieldIndexToFieldIdMap); ++i) { + if (FieldIndexToFieldIdMap[i] == id) { + return i; + } + } + + return util::nullopt; + } else { + if (const auto it = std::lower_bound(std::begin(FieldIndexToFieldIdMap), std::end(FieldIndexToFieldIdMap), id); it != std::end(FieldIndexToFieldIdMap) && *it == id) { + return std::distance(FieldIndexToFieldIdMap, it); + } else { + return util::nullopt; + } + } + } + + constexpr util::optional<size_t> FindCategoryIndex(CategoryId id) { + if (std::is_constant_evaluated()) { + for (size_t i = 0; i < util::size(CategoryIndexToCategoryIdMap); ++i) { + if (CategoryIndexToCategoryIdMap[i] == id) { + return i; + } + } + + return util::nullopt; + } else { + if (const auto it = std::lower_bound(std::begin(CategoryIndexToCategoryIdMap), std::end(CategoryIndexToCategoryIdMap), id); it != std::end(CategoryIndexToCategoryIdMap) && *it == id) { + return std::distance(CategoryIndexToCategoryIdMap, it); + } else { + return util::nullopt; + } + } + } + + constexpr inline CategoryId ConvertFieldToCategory(FieldId id) { + const auto index = FindFieldIndex(id); + AMS_ASSERT(index.has_value()); + return FieldIndexToCategoryMap[index.value()]; + } + + constexpr inline FieldType ConvertFieldToType(FieldId id) { + const auto index = FindFieldIndex(id); + AMS_ASSERT(index.has_value()); + return FieldIndexToTypeMap[index.value()]; + } + + constexpr inline FieldFlag ConvertFieldToFlag(FieldId id) { + const auto index = FindFieldIndex(id); + AMS_ASSERT(index.has_value()); + return FieldIndexToFlagMap[index.value()]; + } + + constexpr inline ReportFlagSet MakeNoReportFlags() { + return util::MakeBitFlagSet<32, ReportFlag>(); + } + + constexpr inline CreateReportOptionFlagSet MakeNoCreateReportOptionFlags() { + return util::MakeBitFlagSet<32, CreateReportOptionFlag>(); + } + + constexpr inline AttachmentFlagSet MakeNoAttachmentFlags() { + return util::MakeBitFlagSet<32, AttachmentFlag>(); + } + + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/err.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/err.hpp new file mode 100644 index 00000000..ae8a5eeb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/err.hpp @@ -0,0 +1,21 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/err/err_types.hpp> +#include <stratosphere/err/err_error_context.hpp> +#include <stratosphere/err/err_system_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/err/err_error_context.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/err/err_error_context.hpp new file mode 100644 index 00000000..031662a5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/err/err_error_context.hpp @@ -0,0 +1,93 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf/sf_buffer_tags.hpp> + +namespace ams::err { + + enum class ErrorContextType : u8 { + None = 0, + Http = 1, + FileSystem = 2, + WebMediaPlayer = 3, + LocalContentShare = 4, + }; + + struct PaddingErrorContext { + u8 padding[0x200 - 8]; + }; + + struct ErrorContext : public sf::LargeData, public sf::PrefersMapAliasTransferMode { + ErrorContextType type; + u8 reserved[7]; + + union { + PaddingErrorContext padding; + }; + }; + static_assert(sizeof(ErrorContext) == 0x200); + static_assert(util::is_pod<ErrorContext>::value); + + struct ContextDescriptor { + int value; + + constexpr ALWAYS_INLINE bool operator==(const ContextDescriptor &rhs) const { return this->value == rhs.value; } + constexpr ALWAYS_INLINE bool operator!=(const ContextDescriptor &rhs) const { return this->value != rhs.value; } + constexpr ALWAYS_INLINE bool operator< (const ContextDescriptor &rhs) const { return this->value < rhs.value; } + constexpr ALWAYS_INLINE bool operator<=(const ContextDescriptor &rhs) const { return this->value <= rhs.value; } + constexpr ALWAYS_INLINE bool operator> (const ContextDescriptor &rhs) const { return this->value > rhs.value; } + constexpr ALWAYS_INLINE bool operator>=(const ContextDescriptor &rhs) const { return this->value >= rhs.value; } + }; + + constexpr inline const ContextDescriptor InvalidContextDescriptor{ -1 }; + + namespace impl { + + constexpr inline const ContextDescriptor ContextDescriptorMin{ 0x001 }; + constexpr inline const ContextDescriptor ContextDescriptorMax{ 0x1FF }; + + } + + constexpr Result MakeResultWithContextDescriptor(Result result, ContextDescriptor descriptor) { + /* Check pre-conditions. */ + AMS_ASSERT(R_FAILED(result)); + AMS_ASSERT(descriptor != InvalidContextDescriptor); + AMS_ASSERT(impl::ContextDescriptorMin <= descriptor && descriptor <= impl::ContextDescriptorMax); + + return result::impl::ResultInternalAccessor::MergeReserved(result, descriptor.value | 0x200); + } + + constexpr ContextDescriptor GetContextDescriptorFromResult(Result result) { + /* Check pre-conditions. */ + AMS_ASSERT(R_FAILED(result)); + + /* Get reserved bits. */ + const auto reserved = result::impl::ResultInternalAccessor::GetReserved(result); + if ((reserved & 0x200) != 0x200) { + return InvalidContextDescriptor; + } + + /* Check the descriptor value. */ + const ContextDescriptor descriptor{static_cast<decltype(ContextDescriptor{}.value)>(reserved & ~0x200)}; + if (!(impl::ContextDescriptorMin <= descriptor && descriptor <= impl::ContextDescriptorMax)) { + return InvalidContextDescriptor; + } + + return descriptor; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/err/err_system_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/err/err_system_api.hpp new file mode 100644 index 00000000..3c79999b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/err/err_system_api.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/err/err_types.hpp> + +namespace ams::err { + + ErrorCode ConvertResultToErrorCode(const Result &result); + void GetErrorCodeString(char *dst, size_t dst_size, ErrorCode error_code); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/err/err_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/err/err_types.hpp new file mode 100644 index 00000000..9d403dca --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/err/err_types.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::err { + + using ErrorCodeCategory = u32; + using ErrorCodeNumber = u32; + + struct ErrorCode { + static constexpr auto StringLengthMax = 15; + + ErrorCodeCategory category; + ErrorCodeNumber number; + + constexpr ALWAYS_INLINE bool IsValid() const { return this->category > 0; } + }; + + constexpr inline ErrorCode InvalidErrorCode = { + .category = 0, + .number = 0, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fat.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fat.hpp new file mode 100644 index 00000000..995267a2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fat.hpp @@ -0,0 +1,17 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fat/fat_file_system.hpp> \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fat/fat_file_system.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fat/fat_file_system.hpp new file mode 100644 index 00000000..46212a10 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fat/fat_file_system.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fat { + + constexpr inline size_t FatErrorNameMaxLength = 0x10; + + struct FatError { + int error; + int extra_error; + int drive_id; + char name[FatErrorNameMaxLength]; + u8 reserved[4]; + }; + static_assert(sizeof(FatError) == 0x20); + static_assert(util::is_pod<FatError>::value); + + struct FatReportInfo1 { + u16 open_file_peak_count; + u16 open_directory_peak_count; + }; + static_assert(sizeof(FatReportInfo1) == 4); + static_assert(util::is_pod<FatReportInfo1>::value); + + struct FatReportInfo2 { + u16 open_unique_file_entry_peak_count; + u16 open_unique_directory_entry_peak_count; + }; + static_assert(sizeof(FatReportInfo2) == 4); + static_assert(util::is_pod<FatReportInfo2>::value); + + struct FatSafeInfo { + u32 result; + u32 error_number; + u32 safe_error_number; + }; + static_assert(sizeof(FatSafeInfo) == 12); + static_assert(util::is_pod<FatSafeInfo>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fatal.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fatal.hpp new file mode 100644 index 00000000..3f1fd37c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fatal.hpp @@ -0,0 +1,22 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/fatal/fatal_types.hpp> + +#include <stratosphere/fatal/impl/fatal_i_service.hpp> +#include <stratosphere/fatal/impl/fatal_i_private_service.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fatal/fatal_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fatal/fatal_types.hpp new file mode 100644 index 00000000..1df55cb1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fatal/fatal_types.hpp @@ -0,0 +1,503 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> +#include <stratosphere/sf/sf_buffer_tags.hpp> + +namespace ams::fatal { + + enum FatalPolicy : u32 { + FatalPolicy_ErrorReportAndErrorScreen = 0, + FatalPolicy_ErrorReport = 1, + FatalPolicy_ErrorScreen = 2 + }; + + #if defined(ATMOSPHERE_ARCH_ARM64) + namespace aarch64 { + + enum RegisterName { + RegisterName_X0 = 0, + RegisterName_X1 = 1, + RegisterName_X2 = 2, + RegisterName_X3 = 3, + RegisterName_X4 = 4, + RegisterName_X5 = 5, + RegisterName_X6 = 6, + RegisterName_X7 = 7, + RegisterName_X8 = 8, + RegisterName_X9 = 9, + RegisterName_X10 = 10, + RegisterName_X11 = 11, + RegisterName_X12 = 12, + RegisterName_X13 = 13, + RegisterName_X14 = 14, + RegisterName_X15 = 15, + RegisterName_X16 = 16, + RegisterName_X17 = 17, + RegisterName_X18 = 18, + RegisterName_X19 = 19, + RegisterName_X20 = 20, + RegisterName_X21 = 21, + RegisterName_X22 = 22, + RegisterName_X23 = 23, + RegisterName_X24 = 24, + RegisterName_X25 = 25, + RegisterName_X26 = 26, + RegisterName_X27 = 27, + RegisterName_X28 = 28, + RegisterName_FP = 29, + RegisterName_LR = 30, + + RegisterName_SP = 31, + RegisterName_PC = 32, + + RegisterName_GeneralPurposeCount, + + RegisterName_PState = 33, + RegisterName_Afsr0 = 34, + RegisterName_Afsr1 = 35, + RegisterName_Esr = 36, + RegisterName_Far = 37, + + RegisterName_Count, + }; + + struct CpuContext { + using RegisterType = u64; + static constexpr size_t MaxStackTraceDepth = 0x20; + + static constexpr const char *RegisterNameStrings[RegisterName_Count] = { + "X0", + "X1", + "X2", + "X3", + "X4", + "X5", + "X6", + "X7", + "X8", + "X9", + "X10", + "X11", + "X12", + "X13", + "X14", + "X15", + "X16", + "X17", + "X18", + "X19", + "X20", + "X21", + "X22", + "X23", + "X24", + "X25", + "X26", + "X27", + "X28", + "FP", + "LR", + "SP", + "PC", + "PState", + "Afsr0", + "Afsr1", + "Esr", + "Far", + }; + + /* Registers, exception context. N left names for these fields in fatal .rodata. */ + union { + struct { + union { + RegisterType x[RegisterName_GeneralPurposeCount]; + struct { + RegisterType _x[RegisterName_FP]; + RegisterType fp; + RegisterType lr; + RegisterType sp; + RegisterType pc; + }; + }; + RegisterType pstate; + RegisterType afsr0; + RegisterType afsr1; + RegisterType esr; + RegisterType far; + }; + RegisterType registers[RegisterName_Count]; + }; + + /* Misc. */ + RegisterType stack_trace[MaxStackTraceDepth]; + RegisterType base_address; + RegisterType register_set_flags; + u32 stack_trace_size; + + void ClearState() { + std::memset(this, 0, sizeof(*this)); + } + + void SetProgramIdForAtmosphere(ncm::ProgramId program_id) { + /* Right now, we mux program ID in through afsr when creport. */ + /* TODO: Better way to do this? */ + this->afsr0 = static_cast<RegisterType>(program_id.value); + } + + ncm::ProgramId GetProgramIdForAtmosphere() const { + return ncm::ProgramId{this->afsr0}; + } + + void SetRegisterValue(RegisterName name, RegisterType value) { + this->registers[name] = value; + this->register_set_flags |= (RegisterType(1) << name); + } + + bool HasRegisterValue(RegisterName name) const { + return this->register_set_flags & (RegisterType(1) << name); + } + + void SetBaseAddress(RegisterType base_addr) { + this->base_address = base_addr; + } + }; + + } + #endif + + #if defined(ATMOSPHERE_ARCH_ARM64) || defined(ATMOSPHERE_ARCH_ARM) + namespace aarch32 { + + enum RegisterName { + RegisterName_R0 = 0, + RegisterName_R1 = 1, + RegisterName_R2 = 2, + RegisterName_R3 = 3, + RegisterName_R4 = 4, + RegisterName_R5 = 5, + RegisterName_R6 = 6, + RegisterName_R7 = 7, + RegisterName_R8 = 8, + RegisterName_R9 = 9, + RegisterName_R10 = 10, + RegisterName_FP = 11, + RegisterName_IP = 12, + RegisterName_LR = 13, + RegisterName_SP = 14, + RegisterName_PC = 15, + + RegisterName_GeneralPurposeCount, + + RegisterName_PState = 16, + RegisterName_Afsr0 = 17, + RegisterName_Afsr1 = 18, + RegisterName_Esr = 29, + RegisterName_Far = 20, + + RegisterName_Count, + }; + + struct CpuContext { + using RegisterType = u32; + static constexpr size_t MaxStackTraceDepth = 0x20; + + static constexpr const char *RegisterNameStrings[RegisterName_Count] = { + "R0", + "R1", + "R2", + "R3", + "R4", + "R5", + "R6", + "R7", + "R8", + "R9", + "R10", + "FP", + "IP", + "LR", + "SP", + "PC", + "PState", + "Afsr0", + "Afsr1", + "Esr", + "Far", + }; + + /* Registers, exception context. N left names for these fields in fatal .rodata. */ + union { + struct { + union { + RegisterType r[RegisterName_GeneralPurposeCount]; + struct { + RegisterType _x[RegisterName_FP]; + RegisterType fp; + RegisterType ip; + RegisterType lr; + RegisterType sp; + RegisterType pc; + }; + }; + RegisterType pstate; + RegisterType afsr0; + RegisterType afsr1; + RegisterType esr; + RegisterType far; + }; + RegisterType registers[RegisterName_Count]; + }; + + /* Misc. Yes, stack_trace_size is really laid out differently than aarch64... */ + RegisterType stack_trace[MaxStackTraceDepth]; + u32 stack_trace_size; + RegisterType base_address; + RegisterType register_set_flags; + + void ClearState() { + std::memset(this, 0, sizeof(*this)); + } + + void SetProgramIdForAtmosphere(ncm::ProgramId program_id) { + /* Right now, we mux program ID in through afsr when creport. */ + /* TODO: Better way to do this? */ + this->afsr0 = static_cast<RegisterType>(static_cast<u64>(program_id.value) >> 0); + this->afsr1 = static_cast<RegisterType>(static_cast<u64>(program_id.value) >> 32); + } + + ncm::ProgramId GetProgramIdForAtmosphere() const { + return ncm::ProgramId{(static_cast<u64>(this->afsr1) << 32ul) | (static_cast<u64>(this->afsr0) << 0ul)}; + } + + void SetRegisterValue(RegisterName name, RegisterType value) { + this->registers[name] = value; + this->register_set_flags |= (RegisterType(1) << name); + } + + bool HasRegisterValue(RegisterName name) const { + return this->register_set_flags & (RegisterType(1) << name); + } + + void SetBaseAddress(RegisterType base_addr) { + this->base_address = base_addr; + } + }; + + } + #endif + + #if defined(ATMOSPHERE_ARCH_X64) + namespace x64 { + + enum RegisterName { + /* TODO */ + RegisterName_Count, + }; + + struct CpuContext { + using RegisterType = u64; + static constexpr size_t MaxStackTraceDepth = 0x20; + + static constexpr const char *RegisterNameStrings[RegisterName_Count] = { + /* TODO */ + }; + + /* Registers, exception context. N left names for these fields in fatal .rodata. */ + union { + // struct { + // /* TODO */ + // }; + RegisterType registers[RegisterName_Count]; + }; + + /* Misc. */ + RegisterType stack_trace[MaxStackTraceDepth]; + RegisterType base_address; + RegisterType register_set_flags; + u32 stack_trace_size; + + void ClearState() { + std::memset(this, 0, sizeof(*this)); + } + + void SetProgramIdForAtmosphere(ncm::ProgramId program_id) { + AMS_UNUSED(program_id); + AMS_ABORT("TODO"); + } + + ncm::ProgramId GetProgramIdForAtmosphere() const { + AMS_ABORT("TODO"); + } + + void SetRegisterValue(RegisterName name, RegisterType value) { + this->registers[name] = value; + this->register_set_flags |= (RegisterType(1) << name); + } + + bool HasRegisterValue(RegisterName name) const { + return this->register_set_flags & (RegisterType(1) << name); + } + + void SetBaseAddress(RegisterType base_addr) { + this->base_address = base_addr; + } + }; + + } + #endif + + #if defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + namespace x86 { + + enum RegisterName { + /* TODO */ + RegisterName_Count, + }; + + struct CpuContext { + using RegisterType = u32; + static constexpr size_t MaxStackTraceDepth = 0x20; + + static constexpr const char *RegisterNameStrings[RegisterName_Count] = { + /* TODO */ + }; + + /* Registers, exception context. N left names for these fields in fatal .rodata. */ + union { + // struct { + // /* TODO */ + // }; + RegisterType registers[RegisterName_Count]; + }; + + /* Misc. Yes, stack_trace_size is really laid out differently than aarch64... */ + RegisterType stack_trace[MaxStackTraceDepth]; + u32 stack_trace_size; + RegisterType base_address; + RegisterType register_set_flags; + + void ClearState() { + std::memset(this, 0, sizeof(*this)); + } + + void SetProgramIdForAtmosphere(ncm::ProgramId program_id) { + AMS_UNUSED(program_id); + AMS_ABORT("TODO"); + } + + ncm::ProgramId GetProgramIdForAtmosphere() const { + AMS_ABORT("TODO"); + } + + void SetRegisterValue(RegisterName name, RegisterType value) { + this->registers[name] = value; + this->register_set_flags |= (RegisterType(1) << name); + } + + bool HasRegisterValue(RegisterName name) const { + return this->register_set_flags & (RegisterType(1) << name); + } + + void SetBaseAddress(RegisterType base_addr) { + this->base_address = base_addr; + } + }; + + } + #endif + + struct CpuContext : sf::LargeData, sf::PrefersMapAliasTransferMode { + enum Architecture { + #if defined(ATMOSPHERE_ARCH_ARM64) + Architecture_Aarch64, + #endif + #if defined(ATMOSPHERE_ARCH_ARM64) || defined(ATMOSPHERE_ARCH_ARM) + Architecture_Aarch32, + #endif + #if defined(ATMOSPHERE_X64) + Architecture_X64, + #endif + #if defined(ATMOSPHERE_X64) || defined(ATMOSPHERE_ARCH_X86) + Architecture_X86, + #endif + }; + + union { + #if defined(ATMOSPHERE_ARCH_ARM64) + aarch64::CpuContext aarch64_ctx; + #endif + #if defined(ATMOSPHERE_ARCH_ARM64) || defined(ATMOSPHERE_ARCH_ARM) + aarch32::CpuContext aarch32_ctx; + #endif + #if defined(ATMOSPHERE_X64) + aarch64::CpuContext x64_ctx; + #endif + #if defined(ATMOSPHERE_X64) || defined(ATMOSPHERE_ARCH_X86) + aarch64::CpuContext x86_ctx; + #endif + u8 raw_storage[0x248]; + }; + + Architecture architecture; + u32 type; + + void ClearState() { + std::memset(this, 0, sizeof(*this)); + } + }; + + #if defined(ATMOSPHERE_ARCH_ARM64) + static_assert(util::is_pod<aarch64::CpuContext>::value && sizeof(aarch64::CpuContext) == 0x248, "aarch64::CpuContext definition!"); + #endif + #if defined(ATMOSPHERE_ARCH_ARM64) || defined(ATMOSPHERE_ARCH_ARM) + static_assert(util::is_pod<aarch32::CpuContext>::value && sizeof(aarch32::CpuContext) == 0xE0, "aarch32::CpuContext definition!"); + #endif + static_assert(util::is_pod<CpuContext>::value && sizeof(CpuContext) == 0x250, "CpuContext definition!"); + + namespace srv { + + struct ThrowContext { + Result result; + FatalPolicy policy; + ncm::ProgramId program_id; + ncm::ProgramId throw_program_id; + char proc_name[0xD]; + bool is_creport; + CpuContext cpu_ctx; + bool generate_error_report; + os::Event *erpt_event; + os::Event *battery_event; + size_t stack_dump_size; + u64 stack_dump_base; + u8 stack_dump[0x100]; + u64 tls_address; + u8 tls_dump[0x100]; + + ThrowContext(os::Event *erpt, os::Event *bat) + : result(ResultSuccess()), policy(), program_id(), throw_program_id(), proc_name(), is_creport(), cpu_ctx(), generate_error_report(), + erpt_event(erpt), battery_event(bat), + stack_dump_size(), stack_dump_base(), stack_dump(), tls_address(), tls_dump() + { + /* ... */ + } + }; + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fatal/impl/fatal_i_private_service.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fatal/impl/fatal_i_private_service.hpp new file mode 100644 index 00000000..c98f2d6e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fatal/impl/fatal_i_private_service.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/fatal/fatal_types.hpp> +#include <stratosphere/sf.hpp> + +#define AMS_FATAL_I_PRIVATE_SERVICE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GetFatalEvent, (sf::OutCopyHandle out_h), (out_h)) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, GetFatalContext, (sf::Out<Result> out_error, sf::Out<ncm::ProgramId> out_program_id, sf::Out<fatal::FatalPolicy> out_policy, sf::Out<fatal::CpuContext> out_ctx), (out_error, out_program_id, out_policy, out_ctx)) + +AMS_SF_DEFINE_INTERFACE(ams::fatal::impl, IPrivateService, AMS_FATAL_I_PRIVATE_SERVICE_INTERFACE_INFO, 0x6C3C9791) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fatal/impl/fatal_i_service.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fatal/impl/fatal_i_service.hpp new file mode 100644 index 00000000..afa10984 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fatal/impl/fatal_i_service.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/fatal/fatal_types.hpp> +#include <stratosphere/sf.hpp> + +#define AMS_FATAL_I_SERVICE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, ThrowFatal, (Result error, const sf::ClientProcessId &client_pid), (error, client_pid)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, ThrowFatalWithPolicy, (Result error, const sf::ClientProcessId &client_pid, fatal::FatalPolicy policy), (error, client_pid, policy)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, ThrowFatalWithCpuContext, (Result error, const sf::ClientProcessId &client_pid, fatal::FatalPolicy policy, const fatal::CpuContext &cpu_ctx), (error, client_pid, policy, cpu_ctx)) + +AMS_SF_DEFINE_INTERFACE(ams::fatal::impl, IService, AMS_FATAL_I_SERVICE_INTERFACE_INFO, 0x91328766) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs.hpp new file mode 100644 index 00000000..9c341405 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs.hpp @@ -0,0 +1,72 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/impl/fs_result_utils.hpp> +#include <stratosphere/fs/fs_context.hpp> +#include <stratosphere/fs/fs_result_config.hpp> +#include <stratosphere/fs/fs_storage_type.hpp> +#include <stratosphere/fs/fs_priority.hpp> +#include <stratosphere/fs/impl/fs_priority_utils.hpp> +#include <stratosphere/fs/fs_access_log.hpp> +#include <stratosphere/fs/fsa/fs_ifile.hpp> +#include <stratosphere/fs/fsa/fs_idirectory.hpp> +#include <stratosphere/fs/fsa/fs_ifilesystem.hpp> +#include <stratosphere/fs/impl/fs_filesystem_proxy_type.hpp> +#include <stratosphere/fs/fsa/fs_registrar.hpp> +#include <stratosphere/fs/fs_remote_filesystem.hpp> +#include <stratosphere/fs/fs_read_only_filesystem.hpp> +#include <stratosphere/fs/fs_istorage.hpp> +#include <stratosphere/fs/fs_i_event_notifier.hpp> +#include <stratosphere/fs/fs_substorage.hpp> +#include <stratosphere/fs/fs_memory_storage.hpp> +#include <stratosphere/fs/fs_remote_storage.hpp> +#include <stratosphere/fs/common/fs_file_storage.hpp> +#include <stratosphere/fs/fs_query_range.hpp> +#include <stratosphere/fs/fs_speed_emulation.hpp> +#include <stratosphere/fs/impl/fs_common_mount_name.hpp> +#include <stratosphere/fs/fs_mount.hpp> +#include <stratosphere/fs/fs_path_utility.hpp> +#include <stratosphere/fs/fs_path.hpp> +#include <stratosphere/fs/common/fs_directory_path_parser.hpp> +#include <stratosphere/fs/fs_filesystem_utils.hpp> +#include <stratosphere/fs/fs_romfs_filesystem.hpp> +#include <stratosphere/fs/impl/fs_data.hpp> +#include <stratosphere/fs/fs_application.hpp> +#include <stratosphere/fs/fs_bis.hpp> +#include <stratosphere/fs/fs_code.hpp> +#include <stratosphere/fs/fs_content.hpp> +#include <stratosphere/fs/fs_content_storage.hpp> +#include <stratosphere/fs/fs_error_info.hpp> +#include <stratosphere/fs/fs_game_card.hpp> +#include <stratosphere/fs/fs_host.hpp> +#include <stratosphere/fs/fs_image_directory.hpp> +#include <stratosphere/fs/fs_mmc.hpp> +#include <stratosphere/fs/fs_save_data_types.hpp> +#include <stratosphere/fs/fs_save_data_management.hpp> +#include <stratosphere/fs/fs_save_data_transaction.hpp> +#include <stratosphere/fs/fs_device_save_data.hpp> +#include <stratosphere/fs/fs_system_save_data.hpp> +#include <stratosphere/fs/fs_sd_card.hpp> +#include <stratosphere/fs/fs_signed_system_partition.hpp> +#include <stratosphere/fs/fs_system_data.hpp> +#include <stratosphere/fs/fs_program_index_map_info.hpp> +#include <stratosphere/fs/fs_program_id.hpp> +#include <stratosphere/fs/impl/fs_access_log_impl.hpp> +#include <stratosphere/fs/impl/fs_hash_generator_factory_selector.hpp> +#include <stratosphere/fs/impl/fs_storage_service_object_adapter.hpp> +#include <stratosphere/fs/fs_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/common/fs_dbm_hierarchical_rom_file_table.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/common/fs_dbm_hierarchical_rom_file_table.hpp new file mode 100644 index 00000000..16f50396 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/common/fs_dbm_hierarchical_rom_file_table.hpp @@ -0,0 +1,191 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/common/fs_dbm_rom_types.hpp> +#include <stratosphere/fs/common/fs_dbm_rom_path_tool.hpp> +#include <stratosphere/fs/common/fs_dbm_rom_key_value_storage.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: 14.3.0.0 */ + class HierarchicalRomFileTable { + public: + using Position = u32; + using StorageSizeType = u32; + + struct FindPosition { + Position next_dir; + Position next_file; + }; + static_assert(util::is_pod<FindPosition>::value); + + using FileInfo = RomFileInfo; + + static constexpr RomFileId PositionToFileId(Position pos) { + return static_cast<RomFileId>(pos); + } + + static constexpr Position FileIdToPosition(RomFileId id) { + return static_cast<Position>(id); + } + private: + static constexpr inline Position InvalidPosition = ~Position(); + static constexpr inline Position RootPosition = 0; + static constexpr inline size_t ReservedDirectoryCount = 1; + + static constexpr RomDirectoryId PositionToDirectoryId(Position pos) { + return static_cast<RomDirectoryId>(pos); + } + + static constexpr Position DirectoryIdToPosition(RomDirectoryId id) { + return static_cast<Position>(id); + } + + static_assert(std::is_same<RomDirectoryId, RomFileId>::value); + + struct RomDirectoryEntry { + Position next; + Position dir; + Position file; + }; + static_assert(util::is_pod<RomDirectoryEntry>::value); + + struct RomFileEntry { + Position next; + FileInfo info; + }; + static_assert(util::is_pod<RomFileEntry>::value); + + static constexpr inline u32 MaxKeyLength = RomPathTool::MaxPathLength; + + template<typename ImplKeyType, typename ClientKeyType, typename ValueType> + class EntryMapTable : public KeyValueRomStorageTemplate<ImplKeyType, ValueType, MaxKeyLength> { + public: + using ImplKey = ImplKeyType; + using ClientKey = ClientKeyType; + using Value = ValueType; + using Position = HierarchicalRomFileTable::Position; + using Base = KeyValueRomStorageTemplate<ImplKeyType, ValueType, MaxKeyLength>; + public: + Result Add(Position *out, const ClientKeyType &key, const Value &value) { + R_RETURN(Base::AddInternal(out, key.key, key.Hash(), key.name.begin(), key.name.length(), value)); + } + + Result Get(Position *out_pos, Value *out_val, const ClientKeyType &key) { + R_RETURN(Base::GetInternal(out_pos, out_val, key.key, key.Hash(), key.name.begin(), key.name.length())); + } + + Result GetByPosition(ImplKey *out_key, Value *out_val, Position pos) { + R_RETURN(Base::GetByPosition(out_key, out_val, pos)); + } + + Result GetByPosition(ImplKey *out_key, Value *out_val, void *out_aux, size_t *out_aux_size, Position pos) { + R_RETURN(Base::GetByPosition(out_key, out_val, out_aux, out_aux_size, pos)); + } + + Result SetByPosition(Position pos, const Value &value) { + R_RETURN(Base::SetByPosition(pos, value)); + } + }; + + struct RomEntryKey { + Position parent; + + bool IsEqual(const RomEntryKey &rhs, const void *aux_lhs, size_t aux_lhs_size, const void *aux_rhs, size_t aux_rhs_size) const { + if (this->parent != rhs.parent) { + return false; + } + if (aux_lhs_size != aux_rhs_size) { + return false; + } + return RomPathTool::IsEqualPath(reinterpret_cast<const RomPathChar *>(aux_lhs), reinterpret_cast<const RomPathChar *>(aux_rhs), aux_lhs_size / sizeof(RomPathChar)); + } + }; + static_assert(util::is_pod<RomEntryKey>::value); + + struct EntryKey { + RomEntryKey key; + RomPathTool::RomEntryName name; + + constexpr u32 Hash() const { + u32 hash = this->key.parent ^ 123456789; + const RomPathChar * cur = this->name.begin(); + const RomPathChar * const end = this->name.end(); + while (cur < end) { + const u32 c = static_cast<u32>(static_cast<std::make_unsigned<RomPathChar>::type>(*(cur++))); + hash = ((hash >> 5) | (hash << 27)) ^ c; + } + return hash; + } + }; + + using DirectoryEntryMapTable = EntryMapTable<RomEntryKey, EntryKey, RomDirectoryEntry>; + using FileEntryMapTable = EntryMapTable<RomEntryKey, EntryKey, RomFileEntry>; + private: + DirectoryEntryMapTable m_dir_table; + FileEntryMapTable m_file_table; + public: + static s64 QueryDirectoryEntryBucketStorageSize(StorageSizeType count); + static s64 QueryDirectoryEntrySize(StorageSizeType aux_size); + static s64 QueryFileEntryBucketStorageSize(StorageSizeType count); + static s64 QueryFileEntrySize(StorageSizeType aux_size); + + static Result Format(SubStorage dir_bucket, SubStorage file_bucket); + public: + HierarchicalRomFileTable(); + + Result Initialize(SubStorage dir_bucket, SubStorage dir_entry, SubStorage file_bucket, SubStorage file_entry); + void Finalize(); + + Result CreateRootDirectory(); + Result CreateDirectory(RomDirectoryId *out, const RomPathChar *path); + Result CreateFile(RomFileId *out, const RomPathChar *path, const FileInfo &info); + Result ConvertPathToDirectoryId(RomDirectoryId *out, const RomPathChar *path); + Result ConvertPathToFileId(RomFileId *out, const RomPathChar *path); + + Result OpenFile(FileInfo *out, const RomPathChar *path); + Result OpenFile(FileInfo *out, RomFileId id); + + Result FindOpen(FindPosition *out, const RomPathChar *path); + Result FindOpen(FindPosition *out, RomDirectoryId id); + + Result FindNextDirectory(RomPathChar *out, FindPosition *find, size_t length); + Result FindNextFile(RomPathChar *out, FindPosition *find, size_t length); + + Result QueryRomFileSystemSize(s64 *out_dir_entry_size, s64 *out_file_entry_size); + private: + Result GetParent(Position *out_pos, EntryKey *out_dir_key, RomDirectoryEntry *out_dir_entry, Position pos, RomPathTool::RomEntryName name, const RomPathChar *path); + + Result FindParentDirectoryRecursive(Position *out_pos, EntryKey *out_dir_key, RomDirectoryEntry *out_dir_entry, RomPathTool::PathParser *parser, const RomPathChar *path); + + Result FindPathRecursive(EntryKey *out_key, RomDirectoryEntry *out_dir_entry, bool is_dir, const RomPathChar *path); + Result FindDirectoryRecursive(EntryKey *out_key, RomDirectoryEntry *out_dir_entry, const RomPathChar *path); + Result FindFileRecursive(EntryKey *out_key, RomDirectoryEntry *out_dir_entry, const RomPathChar *path); + + Result CheckSameEntryExists(const EntryKey &key, Result if_exists); + + Result GetDirectoryEntry(Position *out_pos, RomDirectoryEntry *out_entry, const EntryKey &key); + Result GetDirectoryEntry(RomDirectoryEntry *out_entry, RomDirectoryId id); + + Result GetFileEntry(Position *out_pos, RomFileEntry *out_entry, const EntryKey &key); + Result GetFileEntry(RomFileEntry *out_entry, RomFileId id); + + Result OpenFile(FileInfo *out, const EntryKey &key); + + Result FindOpen(FindPosition *out, const EntryKey &key); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/common/fs_dbm_rom_key_value_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/common/fs_dbm_rom_key_value_storage.hpp new file mode 100644 index 00000000..87a571da --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/common/fs_dbm_rom_key_value_storage.hpp @@ -0,0 +1,304 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/common/fs_dbm_rom_types.hpp> +#include <stratosphere/fs/fs_substorage.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: 14.3.0.0 */ + template<typename KeyType, typename ValueType, size_t MaxAuxiliarySize> + class KeyValueRomStorageTemplate { + public: + using Key = KeyType; + using Value = ValueType; + using Position = u32; + using BucketIndex = s64; + + using StorageSizeType = u32; + + struct FindIndex { + BucketIndex ind; + Position pos; + }; + static_assert(util::is_pod<FindIndex>::value); + private: + static constexpr inline Position InvalidPosition = ~Position(); + + struct Element { + Key key; + Value value; + Position next; + StorageSizeType size; + }; + static_assert(util::is_pod<Element>::value); + private: + s64 m_bucket_count; + SubStorage m_bucket_storage; + SubStorage m_kv_storage; + s64 m_total_entry_size; + u32 m_entry_count; + public: + static constexpr s64 QueryBucketStorageSize(s64 num) { + return num * sizeof(Position); + } + + static constexpr s64 QueryBucketCount(StorageSizeType size) { + return size / sizeof(Position); + } + + static constexpr size_t QueryEntrySize(StorageSizeType aux_size) { + return util::AlignUp<size_t>(sizeof(Element) + aux_size, alignof(Element)); + } + + static Result Format(SubStorage bucket, StorageSizeType count) { + const Position pos = InvalidPosition; + for (auto i = 0u; i < count; i++) { + R_TRY(bucket.Write(i * sizeof(pos), std::addressof(pos), sizeof(pos))); + } + R_SUCCEED(); + } + public: + constexpr KeyValueRomStorageTemplate() : m_bucket_count(), m_bucket_storage(), m_kv_storage(), m_total_entry_size(), m_entry_count() { /* ... */ } + + Result Initialize(const SubStorage &bucket, s64 count, const SubStorage &kv) { + AMS_ASSERT(count > 0); + m_bucket_storage = bucket; + m_bucket_count = count; + m_kv_storage = kv; + R_SUCCEED(); + } + + void Finalize() { + m_bucket_storage = SubStorage(); + m_bucket_count = 0; + m_kv_storage = SubStorage(); + } + + s64 GetTotalEntrySize() const { + return m_total_entry_size; + } + protected: + Result AddInternal(Position *out, const Key &key, u32 hash_key, const void *aux, size_t aux_size, const Value &value) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(aux != nullptr || aux_size == 0); + AMS_ASSERT(m_bucket_count > 0); + + { + Position pos, prev_pos; + Element elem; + + const Result find_res = this->FindInternal(std::addressof(pos), std::addressof(prev_pos), std::addressof(elem), key, hash_key, aux, aux_size); + R_UNLESS(R_FAILED(find_res), fs::ResultDbmAlreadyExists()); + R_UNLESS(fs::ResultDbmKeyNotFound::Includes(find_res), find_res); + } + + Position pos; + R_TRY(this->AllocateEntry(std::addressof(pos), static_cast<StorageSizeType>(aux_size))); + + Position next_pos; + R_TRY(this->LinkEntry(std::addressof(next_pos), pos, hash_key)); + + const Element elem = { key, value, next_pos, static_cast<StorageSizeType>(aux_size) }; + R_TRY(this->WriteKeyValue(std::addressof(elem), pos, aux, aux_size)); + + *out = pos; + m_entry_count++; + + R_SUCCEED(); + } + + Result GetInternal(Position *out_pos, Value *out_val, const Key &key, u32 hash_key, const void *aux, size_t aux_size) { + AMS_ASSERT(out_pos != nullptr); + AMS_ASSERT(out_val != nullptr); + AMS_ASSERT(aux != nullptr); + + Position pos, prev_pos; + Element elem; + R_TRY(this->FindInternal(std::addressof(pos), std::addressof(prev_pos), std::addressof(elem), key, hash_key, aux, aux_size)); + + *out_pos = pos; + *out_val = elem.value; + R_SUCCEED(); + } + + Result GetByPosition(Key *out_key, Value *out_val, Position pos) { + AMS_ASSERT(out_key != nullptr); + AMS_ASSERT(out_val != nullptr); + + Element elem; + R_TRY(this->ReadKeyValue(std::addressof(elem), pos)); + + *out_key = elem.key; + *out_val = elem.value; + R_SUCCEED(); + } + + Result GetByPosition(Key *out_key, Value *out_val, void *out_aux, size_t *out_aux_size, Position pos) { + AMS_ASSERT(out_key != nullptr); + AMS_ASSERT(out_val != nullptr); + AMS_ASSERT(out_aux != nullptr); + AMS_ASSERT(out_aux_size != nullptr); + + Element elem; + R_TRY(this->ReadKeyValue(std::addressof(elem), out_aux, out_aux_size, pos)); + + *out_key = elem.key; + *out_val = elem.value; + R_SUCCEED(); + } + + Result SetByPosition(Position pos, const Value &value) { + Element elem; + R_TRY(this->ReadKeyValue(std::addressof(elem), pos)); + elem.value = value; + R_RETURN(this->WriteKeyValue(std::addressof(elem), pos, nullptr, 0)); + } + private: + BucketIndex HashToBucket(u32 hash_key) const { + return hash_key % m_bucket_count; + } + + Result FindInternal(Position *out_pos, Position *out_prev, Element *out_elem, const Key &key, u32 hash_key, const void *aux, size_t aux_size) { + AMS_ASSERT(out_pos != nullptr); + AMS_ASSERT(out_prev != nullptr); + AMS_ASSERT(out_elem != nullptr); + AMS_ASSERT(aux != nullptr || aux_size == 0); + AMS_ASSERT(m_bucket_count > 0); + + *out_pos = 0; + *out_prev = 0; + + const BucketIndex ind = HashToBucket(hash_key); + + Position cur; + R_TRY(this->ReadBucket(std::addressof(cur), ind)); + + s64 kv_size; + R_TRY(m_kv_storage.GetSize(std::addressof(kv_size))); + AMS_ASSERT(cur == InvalidPosition || cur < kv_size); + + R_UNLESS(cur != InvalidPosition, fs::ResultDbmKeyNotFound()); + + auto buf = ::ams::fs::impl::MakeUnique<u8[]>(MaxAuxiliarySize); + R_UNLESS(buf != nullptr, fs::ResultAllocationMemoryFailedMakeUnique()); + + while (true) { + size_t cur_aux_size; + R_TRY(this->ReadKeyValue(out_elem, buf.get(), std::addressof(cur_aux_size), cur)); + + if (key.IsEqual(out_elem->key, aux, aux_size, buf.get(), cur_aux_size)) { + *out_pos = cur; + R_SUCCEED(); + } + + *out_prev = cur; + cur = out_elem->next; + R_UNLESS(cur != InvalidPosition, fs::ResultDbmKeyNotFound()); + } + } + + Result AllocateEntry(Position *out, StorageSizeType aux_size) { + AMS_ASSERT(out != nullptr); + + s64 kv_size; + R_TRY(m_kv_storage.GetSize(std::addressof(kv_size))); + const size_t end_pos = m_total_entry_size + sizeof(Element) + static_cast<size_t>(aux_size); + R_UNLESS(end_pos <= static_cast<size_t>(kv_size), fs::ResultDbmKeyFull()); + + *out = static_cast<Position>(m_total_entry_size); + + m_total_entry_size = util::AlignUp<s64>(static_cast<s64>(end_pos), alignof(Position)); + R_SUCCEED(); + } + + Result LinkEntry(Position *out, Position pos, u32 hash_key) { + AMS_ASSERT(out != nullptr); + + const BucketIndex ind = HashToBucket(hash_key); + + Position next; + R_TRY(this->ReadBucket(std::addressof(next), ind)); + + s64 kv_size; + R_TRY(m_kv_storage.GetSize(std::addressof(kv_size))); + AMS_ASSERT(next == InvalidPosition || next < kv_size); + + R_TRY(this->WriteBucket(pos, ind)); + + *out = next; + R_SUCCEED(); + } + + Result ReadBucket(Position *out, BucketIndex ind) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(ind < m_bucket_count); + + const s64 offset = ind * sizeof(Position); + R_RETURN(m_bucket_storage.Read(offset, out, sizeof(*out))); + } + + Result WriteBucket(Position pos, BucketIndex ind) { + AMS_ASSERT(ind < m_bucket_count); + + const s64 offset = ind * sizeof(Position); + R_RETURN(m_bucket_storage.Write(offset, std::addressof(pos), sizeof(pos))); + } + + Result ReadKeyValue(Element *out, Position pos) { + AMS_ASSERT(out != nullptr); + + s64 kv_size; + R_TRY(m_kv_storage.GetSize(std::addressof(kv_size))); + AMS_ASSERT(pos < kv_size); + + R_RETURN(m_kv_storage.Read(pos, out, sizeof(*out))); + } + + Result ReadKeyValue(Element *out, void *out_aux, size_t *out_aux_size, Position pos) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(out_aux != nullptr); + AMS_ASSERT(out_aux_size != nullptr); + + R_TRY(this->ReadKeyValue(out, pos)); + + *out_aux_size = out->size; + if (out->size > 0) { + R_TRY(m_kv_storage.Read(pos + sizeof(*out), out_aux, out->size)); + } + + R_SUCCEED(); + } + + Result WriteKeyValue(const Element *elem, Position pos, const void *aux, size_t aux_size) { + AMS_ASSERT(elem != nullptr); + AMS_ASSERT(aux != nullptr); + + s64 kv_size; + R_TRY(m_kv_storage.GetSize(std::addressof(kv_size))); + AMS_ASSERT(pos < kv_size); + + R_TRY(m_kv_storage.Write(pos, elem, sizeof(*elem))); + + if (aux != nullptr && aux_size > 0) { + R_TRY(m_kv_storage.Write(pos + sizeof(*elem), aux, aux_size)); + } + + R_SUCCEED(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/common/fs_dbm_rom_path_tool.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/common/fs_dbm_rom_path_tool.hpp new file mode 100644 index 00000000..0fc1bc8e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/common/fs_dbm_rom_path_tool.hpp @@ -0,0 +1,125 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/common/fs_dbm_rom_types.hpp> + +namespace ams::fs::RomPathTool { + + /* ACCURATE_TO_VERSION: 14.3.0.0 */ + + constexpr inline u32 MaxPathLength = 0x300; + + constexpr ALWAYS_INLINE bool IsSeparator(RomPathChar c) { + return c == RomStringTraits::DirectorySeparator; + } + + constexpr ALWAYS_INLINE bool IsNullTerminator(RomPathChar c) { + return c == RomStringTraits::NullTerminator; + } + + constexpr ALWAYS_INLINE bool IsDot(RomPathChar c) { + return c == RomStringTraits::Dot; + } + + class RomEntryName { + private: + const RomPathChar *m_path; + size_t m_length; + public: + constexpr RomEntryName() : m_path(nullptr), m_length(0) { + /* ... */ + } + + constexpr void Initialize(const RomPathChar *p, size_t len) { + m_path = p; + m_length = len; + } + + constexpr bool IsCurrentDirectory() const { + return m_length == 1 && IsDot(m_path[0]); + } + + constexpr bool IsParentDirectory() const { + return m_length == 2 && IsDot(m_path[0]) && IsDot(m_path[1]); + } + + constexpr bool IsRootDirectory() const { + return m_length == 0; + } + + constexpr const RomPathChar *begin() const { + return m_path; + } + + constexpr const RomPathChar *end() const { + return m_path + m_length; + } + + constexpr size_t length() const { + return m_length; + } + }; + + constexpr inline bool IsCurrentDirectory(const RomPathChar *p, size_t length) { + AMS_ASSERT(p != nullptr); + return length == 1 && IsDot(p[0]); + } + + constexpr inline bool IsCurrentDirectory(const RomPathChar *p) { + AMS_ASSERT(p != nullptr); + return IsDot(p[0]) && IsNullTerminator(p[1]); + } + + constexpr inline bool IsParentDirectory(const RomPathChar *p) { + AMS_ASSERT(p != nullptr); + return IsDot(p[0]) && IsDot(p[1]) && IsNullTerminator(p[2]); + } + + constexpr inline bool IsParentDirectory(const RomPathChar *p, size_t length) { + AMS_ASSERT(p != nullptr); + return length == 2 && IsDot(p[0]) && IsDot(p[1]); + } + + constexpr inline bool IsEqualPath(const RomPathChar *lhs, const RomPathChar *rhs, size_t length) { + AMS_ASSERT(lhs != nullptr); + AMS_ASSERT(rhs != nullptr); + return std::strncmp(lhs, rhs, length) == 0; + } + + Result GetParentDirectoryName(RomEntryName *out, const RomEntryName &cur, const RomPathChar *p); + + class PathParser { + private: + const RomPathChar *m_prev_path_start; + const RomPathChar *m_prev_path_end; + const RomPathChar *m_next_path; + bool m_finished; + public: + constexpr PathParser() : m_prev_path_start(), m_prev_path_end(), m_next_path(), m_finished() { /* ... */ } + + Result Initialize(const RomPathChar *path); + void Finalize(); + + bool IsParseFinished() const; + bool IsDirectoryPath() const; + + Result GetAsDirectoryName(RomEntryName *out) const; + Result GetAsFileName(RomEntryName *out) const; + + Result GetNextDirectoryName(RomEntryName *out); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/common/fs_dbm_rom_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/common/fs_dbm_rom_types.hpp new file mode 100644 index 00000000..eda18f8d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/common/fs_dbm_rom_types.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: 14.3.0.0 */ + using RomPathChar = char; + using RomFileId = u32; + using RomDirectoryId = u32; + + struct RomFileSystemInformation { + s64 size; + s64 directory_bucket_offset; + s64 directory_bucket_size; + s64 directory_entry_offset; + s64 directory_entry_size; + s64 file_bucket_offset; + s64 file_bucket_size; + s64 file_entry_offset; + s64 file_entry_size; + s64 body_offset; + }; + static_assert(util::is_pod<RomFileSystemInformation>::value); + static_assert(sizeof(RomFileSystemInformation) == 0x50); + + struct RomFileInfo { + Int64 offset; + Int64 size; + }; + static_assert(util::is_pod<RomFileInfo>::value); + + namespace RomStringTraits { + + constexpr inline char DirectorySeparator = '/'; + constexpr inline char NullTerminator = '\x00'; + constexpr inline char Dot = '.'; + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/common/fs_directory_path_parser.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/common/fs_directory_path_parser.hpp new file mode 100644 index 00000000..5476cd92 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/common/fs_directory_path_parser.hpp @@ -0,0 +1,124 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_path.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class DirectoryPathParser { + NON_COPYABLE(DirectoryPathParser); + NON_MOVEABLE(DirectoryPathParser); + private: + char *m_buffer; + char m_replaced_char; + s32 m_position; + fs::Path m_current_path; + public: + DirectoryPathParser() : m_buffer(nullptr), m_replaced_char(StringTraits::NullTerminator), m_position(0), m_current_path() { /* ... */ } + + const fs::Path &GetCurrentPath() const { + return m_current_path; + } + + Result Initialize(fs::Path *path) { + /* Declare a default buffer, in case the path has no write buffer. */ + static constinit char EmptyBuffer[1] = ""; + + /* Get a usable buffer. */ + char *buf = path->GetWriteBufferLength() > 0 ? path->GetWriteBuffer() : EmptyBuffer; + + /* Get the windows skip length. */ + const auto windows_skip_len = fs::GetWindowsSkipLength(buf); + + /* Set our buffer. */ + m_buffer = buf + windows_skip_len; + + /* Set up our initial state. */ + if (windows_skip_len) { + R_TRY(m_current_path.InitializeWithNormalization(buf, windows_skip_len + 1)); + } else { + if (char * const first = this->ReadNextImpl(); first != nullptr) { + R_TRY(m_current_path.InitializeWithNormalization(first)); + } + } + + R_SUCCEED(); + } + + Result ReadNext(bool *out_finished) { + /* Default to not finished. */ + *out_finished = false; + + /* Get the next child component. */ + if (auto * const child = this->ReadNextImpl(); child != nullptr) { + /* Append the child component to our current path. */ + R_TRY(m_current_path.AppendChild(child)); + } else { + /* We have no child component, so we're finished. */ + *out_finished = true; + } + + R_SUCCEED(); + } + private: + char *ReadNextImpl() { + /* Check that we have characters to read. */ + if (m_position < 0 || m_buffer[0] == StringTraits::NullTerminator) { + return nullptr; + } + + /* If we have a replaced character, restore it. */ + if (m_replaced_char != StringTraits::NullTerminator) { + m_buffer[m_position] = m_replaced_char; + if (m_replaced_char == StringTraits::DirectorySeparator) { + ++m_position; + } + } + + /* If we're at the start of a root-relative path, begin by returning the root. */ + if (m_position == 0 && m_buffer[0] == StringTraits::DirectorySeparator) { + m_replaced_char = m_buffer[1]; + m_buffer[1] = StringTraits::NullTerminator; + m_position = 1; + return m_buffer; + } + + /* Otherwise, find the end of the next path component. */ + s32 i; + for (i = m_position; m_buffer[i] != StringTraits::DirectorySeparator; ++i) { + if (m_buffer[i] == StringTraits::NullTerminator) { + char * const ret = (i != m_position) ? m_buffer + m_position : nullptr; + m_position = -1; + return ret; + } + } + + /* Sanity check that we're not ending on a separator. */ + AMS_ASSERT(m_buffer[i + 1] != StringTraits::NullTerminator); + + char * const ret = m_buffer + m_position; + + m_replaced_char = StringTraits::DirectorySeparator; + m_buffer[i] = 0; + m_position = i; + + return ret; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/common/fs_file_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/common/fs_file_storage.hpp new file mode 100644 index 00000000..d42d30b9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/common/fs_file_storage.hpp @@ -0,0 +1,118 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_istorage.hpp> +#include <stratosphere/fs/fsa/fs_ifile.hpp> +#include <stratosphere/fs/fsa/fs_ifilesystem.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + class FileStorage : public IStorage, public impl::Newable { + NON_COPYABLE(FileStorage); + NON_MOVEABLE(FileStorage); + private: + static constexpr s64 InvalidSize = -1; + private: + std::unique_ptr<fsa::IFile> m_unique_file; + std::shared_ptr<fsa::IFile> m_shared_file; + fsa::IFile *m_base_file; + s64 m_size; + public: + FileStorage(fsa::IFile *f) : m_unique_file(f), m_size(InvalidSize) { + m_base_file = m_unique_file.get(); + } + + FileStorage(std::unique_ptr<fsa::IFile> f) : m_unique_file(std::move(f)), m_size(InvalidSize) { + m_base_file = m_unique_file.get(); + } + + FileStorage(std::shared_ptr<fsa::IFile> f) : m_shared_file(f), m_size(InvalidSize) { + m_base_file = m_shared_file.get(); + } + + virtual ~FileStorage() { /* ... */ } + private: + Result UpdateSize(); + protected: + constexpr FileStorage() : m_unique_file(), m_shared_file(), m_base_file(nullptr), m_size(InvalidSize) { /* ... */ } + + void SetFile(fs::fsa::IFile *file) { + AMS_ASSERT(file != nullptr); + AMS_ASSERT(m_base_file == nullptr); + m_base_file = file; + } + + void SetFile(std::unique_ptr<fs::fsa::IFile> &&file) { + AMS_ASSERT(file != nullptr); + AMS_ASSERT(m_base_file == nullptr); + AMS_ASSERT(m_unique_file == nullptr); + + m_unique_file = std::move(file); + m_base_file = m_unique_file.get(); + } + public: + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + virtual Result Flush() override; + virtual Result GetSize(s64 *out_size) override; + virtual Result SetSize(s64 size) override; + virtual Result OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + }; + + /* ACCURATE_TO_VERSION: Unknown */ + class FileStorageBasedFileSystem : public FileStorage { + NON_COPYABLE(FileStorageBasedFileSystem); + NON_MOVEABLE(FileStorageBasedFileSystem); + private: + std::shared_ptr<fs::fsa::IFileSystem> m_base_file_system; + public: + constexpr FileStorageBasedFileSystem() : FileStorage(), m_base_file_system(nullptr) { /* ... */ } + + Result Initialize(std::shared_ptr<fs::fsa::IFileSystem> base_file_system, const fs::Path &path, fs::OpenMode mode); + }; + + class FileHandleStorage : public IStorage, public impl::Newable { + private: + static constexpr s64 InvalidSize = -1; + private: + FileHandle m_handle; + bool m_close_file; + s64 m_size; + os::SdkMutex m_mutex; + public: + constexpr explicit FileHandleStorage(FileHandle handle, bool close_file) : m_handle(handle), m_close_file(close_file), m_size(InvalidSize), m_mutex() { /* ... */ } + constexpr explicit FileHandleStorage(FileHandle handle) : FileHandleStorage(handle, false) { /* ... */ } + + virtual ~FileHandleStorage() override { + if (m_close_file) { + CloseFile(m_handle); + } + } + protected: + Result UpdateSize(); + public: + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + virtual Result Flush() override; + virtual Result GetSize(s64 *out_size) override; + virtual Result SetSize(s64 size) override; + virtual Result OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_access_log.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_access_log.hpp new file mode 100644 index 00000000..cb1b1061 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_access_log.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + + enum AccessLogMode : u32 { + AccessLogMode_None = 0, + AccessLogMode_Log = 1, + AccessLogMode_SdCard = 2, + }; + + Result GetGlobalAccessLogMode(u32 *out); + Result SetGlobalAccessLogMode(u32 mode); + + void SetLocalAccessLog(bool enabled); + void SetLocalSystemAccessLogForDebug(bool enabled); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_api.hpp new file mode 100644 index 00000000..96370748 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_api.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + + void InitializeForSystem(); + void InitializeWithMultiSessionForSystem(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_application.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_application.hpp new file mode 100644 index 00000000..3301435c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_application.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + + Result MountApplicationPackage(const char *name, const char *common_path); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_bis.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_bis.hpp new file mode 100644 index 00000000..4bdf4cf4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_bis.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_istorage.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + + enum class BisPartitionId { + /* Boot0 */ + BootPartition1Root = 0, + + /* Boot1 */ + BootPartition2Root = 10, + + /* Non-Boot */ + UserDataRoot = 20, + BootConfigAndPackage2Part1 = 21, + BootConfigAndPackage2Part2 = 22, + BootConfigAndPackage2Part3 = 23, + BootConfigAndPackage2Part4 = 24, + BootConfigAndPackage2Part5 = 25, + BootConfigAndPackage2Part6 = 26, + CalibrationBinary = 27, + CalibrationFile = 28, + SafeMode = 29, + User = 30, + System = 31, + SystemProperEncryption = 32, + SystemProperPartition = 33, + SignedSystemPartitionOnSafeMode = 34, + DeviceTreeBlob = 35, + System0 = 36, + }; + + const char *GetBisMountName(BisPartitionId id); + + Result MountBis(BisPartitionId id, const char *root_path); + Result MountBis(const char *name, BisPartitionId id); + + void SetBisRootForHost(BisPartitionId id, const char *root_path); + + Result OpenBisPartition(std::unique_ptr<fs::IStorage> *out, BisPartitionId id); + + Result InvalidateBisCache(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_code.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_code.hpp new file mode 100644 index 00000000..1bc47d4b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_code.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> +#include <stratosphere/fs/fs_code_verification_data.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: 16.2.0.0 */ + Result MountCode(CodeVerificationData *out, const char *name, const char *path, fs::ContentAttributes attr, ncm::ProgramId program_id, ncm::StorageId storage_id); + + Result MountCodeForAtmosphereWithRedirection(CodeVerificationData *out, const char *name, const char *path, fs::ContentAttributes attr, ncm::ProgramId program_id, ncm::StorageId storage_id, bool is_hbl, bool is_specific); + Result MountCodeForAtmosphere(CodeVerificationData *out, const char *name, const char *path, fs::ContentAttributes attr, ncm::ProgramId program_id, ncm::StorageId storage_id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_code_verification_data.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_code_verification_data.hpp new file mode 100644 index 00000000..e687cc95 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_code_verification_data.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf/sf_buffer_tags.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + struct CodeVerificationData : public ams::sf::LargeData { + u8 signature[crypto::Rsa2048PssSha256Verifier::SignatureSize]; + u8 target_hash[crypto::Rsa2048PssSha256Verifier::HashSize]; + bool has_data; + u8 reserved[3]; + }; + static_assert(sizeof(CodeVerificationData) == crypto::Rsa2048PssSha256Verifier::SignatureSize + crypto::Rsa2048PssSha256Verifier::HashSize + 4); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_common.hpp new file mode 100644 index 00000000..3419faac --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_common.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> +#include <stratosphere/sf.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + struct Int64 { + u32 low; + u32 high; + + constexpr ALWAYS_INLINE void Set(s64 v) { + this->low = static_cast<u32>((v & static_cast<u64>(0x00000000FFFFFFFFul)) >> 0); + this->high = static_cast<u32>((v & static_cast<u64>(0xFFFFFFFF00000000ul)) >> 32); + } + + constexpr ALWAYS_INLINE s64 Get() const { + return (static_cast<s64>(this->high) << 32) | (static_cast<s64>(this->low)); + } + + constexpr ALWAYS_INLINE Int64 &operator=(s64 v) { + this->Set(v); + return *this; + } + + constexpr ALWAYS_INLINE operator s64() const { + return this->Get(); + } + }; + static_assert(util::is_pod<Int64>::value); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_content.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_content.hpp new file mode 100644 index 00000000..b379c139 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_content.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + enum ContentType { + ContentType_Meta = 0, + ContentType_Control = 1, + ContentType_Manual = 2, + ContentType_Logo = 3, + ContentType_Data = 4, + }; + + Result MountContent(const char *name, const char *path, fs::ContentAttributes attr, ContentType content_type); + Result MountContent(const char *name, const char *path, fs::ContentAttributes attr, ncm::ProgramId id, ContentType content_type); + Result MountContent(const char *name, const char *path, fs::ContentAttributes attr, ncm::DataId id, ContentType content_type); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_content_attributes.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_content_attributes.hpp new file mode 100644 index 00000000..a8025d18 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_content_attributes.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + enum ContentAttributes : u8 { + ContentAttributes_None = 0x0, + ContentAttributes_All = 0xF, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_content_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_content_storage.hpp new file mode 100644 index 00000000..d2c378a5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_content_storage.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_content_storage_id.hpp> + +namespace ams::fs { + + constexpr inline const char * const ContentStorageDirectoryName = "Contents"; + + const char *GetContentStorageMountName(ContentStorageId id); + + Result MountContentStorage(ContentStorageId id); + Result MountContentStorage(const char *name, ContentStorageId id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_content_storage_id.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_content_storage_id.hpp new file mode 100644 index 00000000..9a439087 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_content_storage_id.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: 16.2.0.0 */ + enum class ContentStorageId : u32 { + System = 0, + User = 1, + SdCard = 2, + System0 = 3, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_context.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_context.hpp new file mode 100644 index 00000000..d1b5454b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_context.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fs { + + enum class AbortSpecifier { + Default, + Abort, + Return, + }; + + using ResultHandler = AbortSpecifier (*)(Result); + + class FsContext { + private: + ResultHandler m_handler; + public: + constexpr explicit FsContext(ResultHandler h) : m_handler(h) { /* ... */ } + + constexpr void SetHandler(ResultHandler h) { m_handler = h; } + + constexpr AbortSpecifier HandleResult(Result result) const { return m_handler(result); } + }; + + void SetDefaultFsContextResultHandler(const ResultHandler handler); + + const FsContext *GetCurrentThreadFsContext(); + void SetCurrentThreadFsContext(const FsContext *context); + + class ScopedFsContext { + private: + const FsContext * const m_prev_context; + public: + ALWAYS_INLINE ScopedFsContext(const FsContext &ctx) : m_prev_context(GetCurrentThreadFsContext()) { + SetCurrentThreadFsContext(std::addressof(ctx)); + } + + ALWAYS_INLINE ~ScopedFsContext() { + SetCurrentThreadFsContext(m_prev_context); + } + }; + + class ScopedAutoAbortDisabler { + private: + const FsContext * const m_prev_context; + public: + ScopedAutoAbortDisabler(); + ALWAYS_INLINE ~ScopedAutoAbortDisabler() { + SetCurrentThreadFsContext(m_prev_context); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_device_save_data.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_device_save_data.hpp new file mode 100644 index 00000000..7dcf5764 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_device_save_data.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_save_data_types.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + Result MountDeviceSaveData(const char *name); + Result MountDeviceSaveData(const char *name, const ncm::ApplicationId application_id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_directory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_directory.hpp new file mode 100644 index 00000000..d51ea3c0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_directory.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + constexpr inline size_t EntryNameLengthMax = 0x300; + + struct DirectoryEntry { + char name[EntryNameLengthMax + 1]; + char pad[3]; + s8 type; + u8 pad2[3]; + s64 file_size; + }; + + struct DirectoryHandle { + void *handle; + }; + + Result ReadDirectory(s64 *out_count, DirectoryEntry *out_entries, DirectoryHandle handle, s64 max_entries); + Result GetDirectoryEntryCount(s64 *out, DirectoryHandle handle); + void CloseDirectory(DirectoryHandle handle); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_error_info.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_error_info.hpp new file mode 100644 index 00000000..06044631 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_error_info.hpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fat/fat_file_system.hpp> + +namespace ams::fs { + + struct StorageErrorInfo { + u32 num_activation_failures; + u32 num_activation_error_corrections; + u32 num_read_write_failures; + u32 num_read_write_error_corrections; + }; + static_assert(sizeof(StorageErrorInfo) == 0x10); + static_assert(util::is_pod<StorageErrorInfo>::value); + + struct FileSystemProxyErrorInfo { + u32 rom_fs_remount_for_data_corruption_count; + u32 rom_fs_unrecoverable_data_corruption_by_remount_count; + fat::FatError fat_fs_error; + u32 rom_fs_recovered_by_invalidate_cache_count; + u32 save_data_index_count; + fat::FatReportInfo1 bis_system_fat_report_info_1; + fat::FatReportInfo1 bis_user_fat_report_info_1; + fat::FatReportInfo1 sd_card_fat_report_info_1; + fat::FatReportInfo2 bis_system_fat_report_info_2; + fat::FatReportInfo2 bis_user_fat_report_info_2; + fat::FatReportInfo2 sd_card_fat_report_info_2; + u32 rom_fs_deep_retry_start_count; + u32 rom_fs_unrecoverable_by_game_card_access_failed_count; + fat::FatSafeInfo bis_system_fat_safe_info; + fat::FatSafeInfo bis_user_fat_safe_info; + + u8 reserved[0x18]; + }; + static_assert(sizeof(FileSystemProxyErrorInfo) == 0x80); + static_assert(util::is_pod<FileSystemProxyErrorInfo>::value); + + Result GetAndClearMmcErrorInfo(StorageErrorInfo *out_sei, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size); + Result GetAndClearSdCardErrorInfo(StorageErrorInfo *out_sei, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size); + + Result GetAndClearFileSystemProxyErrorInfo(FileSystemProxyErrorInfo *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_file.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_file.hpp new file mode 100644 index 00000000..6cd22902 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_file.hpp @@ -0,0 +1,87 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs { + + struct ReadOption { + u32 _value; + + static const ReadOption None; + }; + + enum ReadOptionFlag : u32 { + ReadOptionFlag_None = (0 << 0), + }; + + inline constexpr const ReadOption ReadOption::None = {ReadOptionFlag_None}; + + inline constexpr bool operator==(const ReadOption &lhs, const ReadOption &rhs) { + return lhs._value == rhs._value; + } + + inline constexpr bool operator!=(const ReadOption &lhs, const ReadOption &rhs) { + return !(lhs == rhs); + } + + static_assert(util::is_pod<ReadOption>::value && sizeof(ReadOption) == sizeof(u32)); + + enum WriteOptionFlag : u32 { + WriteOptionFlag_None = (0 << 0), + WriteOptionFlag_Flush = (1 << 0), + }; + + struct WriteOption { + u32 _value; + + constexpr inline bool HasFlushFlag() const { + return _value & WriteOptionFlag_Flush; + } + + static const WriteOption None; + static const WriteOption Flush; + }; + + inline constexpr const WriteOption WriteOption::None = {WriteOptionFlag_None}; + inline constexpr const WriteOption WriteOption::Flush = {WriteOptionFlag_Flush}; + + inline constexpr bool operator==(const WriteOption &lhs, const WriteOption &rhs) { + return lhs._value == rhs._value; + } + + inline constexpr bool operator!=(const WriteOption &lhs, const WriteOption &rhs) { + return !(lhs == rhs); + } + + static_assert(util::is_pod<WriteOption>::value && sizeof(WriteOption) == sizeof(u32)); + + struct FileHandle { + void *handle; + }; + + Result ReadFile(FileHandle handle, s64 offset, void *buffer, size_t size, const fs::ReadOption &option); + Result ReadFile(FileHandle handle, s64 offset, void *buffer, size_t size); + Result ReadFile(size_t *out, FileHandle handle, s64 offset, void *buffer, size_t size, const fs::ReadOption &option); + Result ReadFile(size_t *out, FileHandle handle, s64 offset, void *buffer, size_t size); + Result GetFileSize(s64 *out, FileHandle handle); + Result FlushFile(FileHandle handle); + Result WriteFile(FileHandle handle, s64 offset, const void *buffer, size_t size, const fs::WriteOption &option); + Result SetFileSize(FileHandle handle, s64 size); + int GetFileOpenMode(FileHandle handle); + void CloseFile(FileHandle handle); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_filesystem.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_filesystem.hpp new file mode 100644 index 00000000..1ca745fd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_filesystem.hpp @@ -0,0 +1,78 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs { + + namespace fsa { + + class IFileSystem; + + } + + enum OpenMode { + OpenMode_Read = (1 << 0), + OpenMode_Write = (1 << 1), + OpenMode_AllowAppend = (1 << 2), + + OpenMode_ReadWrite = (OpenMode_Read | OpenMode_Write), + OpenMode_All = (OpenMode_ReadWrite | OpenMode_AllowAppend), + }; + + enum OpenDirectoryMode { + OpenDirectoryMode_Directory = (1 << 0), + OpenDirectoryMode_File = (1 << 1), + + OpenDirectoryMode_All = (OpenDirectoryMode_Directory | OpenDirectoryMode_File), + + /* TODO: Separate enum, like N? */ + OpenDirectoryMode_NotRequireFileSize = (1 << 31), + }; + + enum DirectoryEntryType { + DirectoryEntryType_Directory = 0, + DirectoryEntryType_File = 1, + }; + + enum CreateOption { + CreateOption_None = (0 << 0), + CreateOption_BigFile = (1 << 0), + }; + + struct FileHandle; + struct DirectoryHandle; + + Result CreateFile(const char *path, s64 size); + Result CreateFile(const char* path, s64 size, int option); + Result DeleteFile(const char *path); + Result CreateDirectory(const char *path); + Result DeleteDirectory(const char *path); + Result DeleteDirectoryRecursively(const char *path); + Result RenameFile(const char *old_path, const char *new_path); + Result RenameDirectory(const char *old_path, const char *new_path); + Result GetEntryType(DirectoryEntryType *out, const char *path); + Result OpenFile(FileHandle *out_file, const char *path, int mode); + Result OpenDirectory(DirectoryHandle *out_dir, const char *path, int mode); + Result CleanDirectoryRecursively(const char *path); + Result GetFreeSpaceSize(s64 *out, const char *path); + Result GetTotalSpaceSize(s64 *out, const char *path); + + Result SetConcatenationFileAttribute(const char *path); + + Result OpenFile(FileHandle *out, std::unique_ptr<fsa::IFile> &&file, int mode); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_filesystem_for_debug.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_filesystem_for_debug.hpp new file mode 100644 index 00000000..0a1efc57 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_filesystem_for_debug.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_filesystem.hpp> +#include <stratosphere/time/time_posix_time.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + struct FileTimeStamp { + time::PosixTime create; + time::PosixTime modify; + time::PosixTime access; + bool is_local_time; + char pad[7]; + }; + static_assert(util::is_pod<FileTimeStamp>::value && sizeof(FileTimeStamp) == 0x20); + + struct FileTimeStampRaw { + s64 create; + s64 modify; + s64 access; + bool is_local_time; + char pad[7]; + }; + static_assert(util::is_pod<FileTimeStampRaw>::value && sizeof(FileTimeStampRaw) == 0x20); + + static_assert(__builtin_offsetof(FileTimeStampRaw, create) == __builtin_offsetof(FileTimeStampRaw, create)); + static_assert(__builtin_offsetof(FileTimeStampRaw, modify) == __builtin_offsetof(FileTimeStampRaw, modify)); + static_assert(__builtin_offsetof(FileTimeStampRaw, access) == __builtin_offsetof(FileTimeStampRaw, access)); + static_assert(__builtin_offsetof(FileTimeStampRaw, is_local_time) == __builtin_offsetof(FileTimeStampRaw, is_local_time)); + static_assert(__builtin_offsetof(FileTimeStampRaw, pad) == __builtin_offsetof(FileTimeStampRaw, pad)); + + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(sizeof(FileTimeStampRaw) == sizeof(::FsTimeStampRaw)); + #endif + + namespace impl { + + Result GetFileTimeStampRawForDebug(FileTimeStampRaw *out, const char *path); + + } + + Result GetFileTimeStamp(FileTimeStamp *out, const char *path); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_filesystem_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_filesystem_utils.hpp new file mode 100644 index 00000000..860d9c99 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_filesystem_utils.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_filesystem.hpp> +#include <stratosphere/fs/fs_filesystem_for_debug.hpp> + +namespace ams::fs { + + /* Common utilities. */ + Result EnsureDirectory(const char *path); + Result EnsureParentDirectory(const char *path); + + Result HasFile(bool *out, const char *path); + Result HasDirectory(bool *out, const char *path); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_game_card.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_game_card.hpp new file mode 100644 index 00000000..ccfc0fc8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_game_card.hpp @@ -0,0 +1,93 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + constexpr inline size_t GameCardCidSize = 0x10; + constexpr inline size_t GameCardDeviceIdSize = 0x10; + + enum class GameCardPartition { + Update = 0, + Normal = 1, + Secure = 2, + Logo = 3, + }; + + enum class GameCardPartitionRaw { + NormalReadable, + SecureReadable, + RootWriteable, + }; + + enum GameCardAttribute : u8 { + GameCardAttribute_AutoBootFlag = (1 << 0), + GameCardAttribute_HistoryEraseFlag = (1 << 1), + GameCardAttribute_RepairToolFlag = (1 << 2), + GameCardAttribute_DifferentRegionCupToTerraDeviceFlag = (1 << 3), + GameCardAttribute_DifferentRegionCupToGlobalDeviceFlag = (1 << 4), + + GameCardAttribute_HasCa10CertificateFlag = (1 << 7), + }; + + enum class GameCardCompatibilityType : u8 { + Normal = 0, + Terra = 1, + }; + + struct GameCardErrorReportInfo { + u16 game_card_crc_error_num; + u16 reserved1; + u16 asic_crc_error_num; + u16 reserved2; + u16 refresh_num; + u16 reserved3; + u16 retry_limit_out_num; + u16 timeout_retry_num; + u16 asic_reinitialize_failure_detail; + u16 insertion_count; + u16 removal_count; + u16 asic_reinitialize_num; + u32 initialize_count; + u16 asic_reinitialize_failure_num; + u16 awaken_failure_num; + u16 reserved4; + u16 refresh_succeeded_count; + u32 last_read_error_page_address; + u32 last_read_error_page_count; + u32 awaken_count; + u32 read_count_from_insert; + u32 read_count_from_awaken; + u8 reserved5[8]; + }; + static_assert(util::is_pod<GameCardErrorReportInfo>::value); + static_assert(sizeof(GameCardErrorReportInfo) == 0x40); + + using GameCardHandle = u32; + + Result GetGameCardHandle(GameCardHandle *out); + Result MountGameCardPartition(const char *name, GameCardHandle handle, GameCardPartition partition); + + Result GetGameCardCid(void *dst, size_t size); + Result GetGameCardDeviceId(void *dst, size_t size); + + Result GetGameCardErrorReportInfo(GameCardErrorReportInfo *out); + + bool IsGameCardInserted(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_host.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_host.hpp new file mode 100644 index 00000000..264cf84f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_host.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> +#include <stratosphere/fs/fs_code_verification_data.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + enum MountHostOptionFlag : u32 { + MountHostOptionFlag_None = (0 << 0), + MountHostOptionFlag_PseudoCaseSensitive = (1 << 0), + }; + + struct MountHostOption { + u32 _value; + + constexpr inline bool HasPseudoCaseSensitiveFlag() const { + return _value & MountHostOptionFlag_PseudoCaseSensitive; + } + + static const MountHostOption None; + static const MountHostOption PseudoCaseSensitive; + }; + + inline constexpr const MountHostOption MountHostOption::None = {MountHostOptionFlag_None}; + inline constexpr const MountHostOption MountHostOption::PseudoCaseSensitive = {MountHostOptionFlag_PseudoCaseSensitive}; + + inline constexpr bool operator==(const MountHostOption &lhs, const MountHostOption &rhs) { + return lhs._value == rhs._value; + } + + inline constexpr bool operator!=(const MountHostOption &lhs, const MountHostOption &rhs) { + return !(lhs == rhs); + } + + Result MountHost(const char *name, const char *root_path); + Result MountHost(const char *name, const char *root_path, const MountHostOption &option); + + Result MountHostRoot(); + Result MountHostRoot(const MountHostOption &option); + + void UnmountHostRoot(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_i_buffer_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_i_buffer_manager.hpp new file mode 100644 index 00000000..a7a3b814 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_i_buffer_manager.hpp @@ -0,0 +1,122 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fs { + + class IBufferManager { + public: + class BufferAttribute { + private: + s32 m_level; + public: + constexpr BufferAttribute() : m_level(0) { /* ... */ } + constexpr explicit BufferAttribute(s32 l) : m_level(l) { /* ... */ } + + constexpr s32 GetLevel() const { return m_level; } + }; + + using CacheHandle = u64; + + static constexpr s32 BufferLevelMin = 0; + + using MemoryRange = std::pair<uintptr_t, size_t>; + + static constexpr ALWAYS_INLINE MemoryRange MakeMemoryRange(uintptr_t address, size_t size) { return MemoryRange(address, size); } + public: + virtual ~IBufferManager() { /* ... */ } + + ALWAYS_INLINE const MemoryRange AllocateBuffer(size_t size, const BufferAttribute &attr) { + return this->DoAllocateBuffer(size, attr); + } + + ALWAYS_INLINE const MemoryRange AllocateBuffer(size_t size) { + return this->DoAllocateBuffer(size, BufferAttribute()); + } + + ALWAYS_INLINE void DeallocateBuffer(uintptr_t address, size_t size) { + return this->DoDeallocateBuffer(address, size); + } + + ALWAYS_INLINE void DeallocateBuffer(const MemoryRange &memory_range) { + return this->DoDeallocateBuffer(memory_range.first, memory_range.second); + } + + ALWAYS_INLINE CacheHandle RegisterCache(uintptr_t address, size_t size, const BufferAttribute &attr) { + return this->DoRegisterCache(address, size, attr); + } + + ALWAYS_INLINE CacheHandle RegisterCache(const MemoryRange &memory_range, const BufferAttribute &attr) { + return this->DoRegisterCache(memory_range.first, memory_range.second, attr); + } + + ALWAYS_INLINE const std::pair<uintptr_t, size_t> AcquireCache(CacheHandle handle) { + return this->DoAcquireCache(handle); + } + + ALWAYS_INLINE size_t GetTotalSize() const { + return this->DoGetTotalSize(); + } + + ALWAYS_INLINE size_t GetFreeSize() const { + return this->DoGetFreeSize(); + } + + ALWAYS_INLINE size_t GetTotalAllocatableSize() const { + return this->DoGetTotalAllocatableSize(); + } + + ALWAYS_INLINE size_t GetFreeSizePeak() const { + return this->DoGetFreeSizePeak(); + } + + ALWAYS_INLINE size_t GetTotalAllocatableSizePeak() const { + return this->DoGetTotalAllocatableSizePeak(); + } + + ALWAYS_INLINE size_t GetRetriedCount() const { + return this->DoGetRetriedCount(); + } + + ALWAYS_INLINE void ClearPeak() { + return this->DoClearPeak(); + } + protected: + virtual const MemoryRange DoAllocateBuffer(size_t size, const BufferAttribute &attr) = 0; + + virtual void DoDeallocateBuffer(uintptr_t address, size_t size) = 0; + + virtual CacheHandle DoRegisterCache(uintptr_t address, size_t size, const BufferAttribute &attr) = 0; + + virtual const MemoryRange DoAcquireCache(CacheHandle handle) = 0; + + virtual size_t DoGetTotalSize() const = 0; + + virtual size_t DoGetFreeSize() const = 0; + + virtual size_t DoGetTotalAllocatableSize() const = 0; + + virtual size_t DoGetFreeSizePeak() const = 0; + + virtual size_t DoGetTotalAllocatableSizePeak() const = 0; + + virtual size_t DoGetRetriedCount() const = 0; + + virtual void DoClearPeak() = 0; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_i_event_notifier.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_i_event_notifier.hpp new file mode 100644 index 00000000..6dbba305 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_i_event_notifier.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + class IEventNotifier { + public: + virtual ~IEventNotifier() { /* ... */ } + + Result BindEvent(os::SystemEventType *out, os::EventClearMode clear_mode) { + AMS_ASSERT(out != nullptr); + R_RETURN(this->DoBindEvent(out, clear_mode)); + } + private: + virtual Result DoBindEvent(os::SystemEventType *out, os::EventClearMode clear_mode) = 0; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_image_directory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_image_directory.hpp new file mode 100644 index 00000000..e64cc271 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_image_directory.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + enum class ImageDirectoryId { + Nand = 0, + SdCard = 1, + }; + + Result MountImageDirectory(const char *name, ImageDirectoryId id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_istorage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_istorage.hpp new file mode 100644 index 00000000..f8267497 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_istorage.hpp @@ -0,0 +1,129 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_file.hpp> +#include <stratosphere/fs/fs_operate_range.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: 14.3.0.0 */ + class IStorage { + public: + virtual ~IStorage() { /* ... */ } + + virtual Result Read(s64 offset, void *buffer, size_t size) = 0; + + virtual Result Write(s64 offset, const void *buffer, size_t size) = 0; + + virtual Result Flush() = 0; + + virtual Result SetSize(s64 size) = 0; + + virtual Result GetSize(s64 *out) = 0; + + virtual Result OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) = 0; + + virtual Result OperateRange(OperationId op_id, s64 offset, s64 size) { + R_RETURN(this->OperateRange(nullptr, 0, op_id, offset, size, nullptr, 0)); + } + public: + static inline Result CheckAccessRange(s64 offset, s64 size, s64 total_size) { + R_UNLESS(offset >= 0, fs::ResultInvalidOffset()); + R_UNLESS(size >= 0, fs::ResultInvalidSize()); + R_UNLESS(util::CanAddWithoutOverflow<s64>(offset, size), fs::ResultOutOfRange()); + R_UNLESS(offset + size <= total_size, fs::ResultOutOfRange()); + R_SUCCEED(); + } + + static ALWAYS_INLINE Result CheckAccessRange(s64 offset, size_t size, s64 total_size) { + R_RETURN(CheckAccessRange(offset, static_cast<s64>(size), total_size)); + } + + static inline Result CheckOffsetAndSize(s64 offset, s64 size) { + R_UNLESS(offset >= 0, fs::ResultInvalidOffset()); + R_UNLESS(size >= 0, fs::ResultInvalidSize()); + R_UNLESS(util::CanAddWithoutOverflow<s64>(offset, size), fs::ResultOutOfRange()); + R_SUCCEED(); + } + + static ALWAYS_INLINE Result CheckOffsetAndSize(s64 offset, size_t size) { + R_RETURN(CheckOffsetAndSize(offset, static_cast<s64>(size))); + } + + static inline Result CheckOffsetAndSizeWithResult(s64 offset, s64 size, Result fail_result) { + R_TRY_CATCH(CheckOffsetAndSize(offset, size)) { + R_CONVERT_ALL(fail_result); + } R_END_TRY_CATCH; + R_SUCCEED(); + } + + static ALWAYS_INLINE Result CheckOffsetAndSizeWithResult(s64 offset, size_t size, Result fail_result) { + R_RETURN(CheckOffsetAndSizeWithResult(offset, static_cast<s64>(size), fail_result)); + } + }; + + class ReadOnlyStorageAdapter : public IStorage { + private: + std::shared_ptr<IStorage> m_shared_storage; + std::unique_ptr<IStorage> m_unique_storage; + IStorage *m_storage; + public: + explicit ReadOnlyStorageAdapter(IStorage *s) : m_unique_storage(s) { + m_storage = m_unique_storage.get(); + } + explicit ReadOnlyStorageAdapter(std::shared_ptr<IStorage> s) : m_shared_storage(s) { + m_storage = m_shared_storage.get(); + } + explicit ReadOnlyStorageAdapter(std::unique_ptr<IStorage> s) : m_unique_storage(std::move(s)) { + m_storage = m_unique_storage.get(); + } + + virtual ~ReadOnlyStorageAdapter() { /* ... */ } + public: + virtual Result Read(s64 offset, void *buffer, size_t size) override { + R_RETURN(m_storage->Read(offset, buffer, size)); + } + + virtual Result Flush() override { + R_RETURN(m_storage->Flush()); + } + + virtual Result GetSize(s64 *out) override { + R_RETURN(m_storage->GetSize(out)); + } + + virtual Result OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + R_RETURN(m_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + /* TODO: Better result? Is it possible to get a more specific one? */ + AMS_UNUSED(offset, buffer, size); + R_THROW(fs::ResultUnsupportedOperation()); + } + + virtual Result SetSize(s64 size) override { + /* TODO: Better result? Is it possible to get a more specific one? */ + AMS_UNUSED(size); + R_THROW(fs::ResultUnsupportedOperation()); + } + }; + + template<typename T> + concept PointerToStorage = ::ams::util::RawOrSmartPointerTo<T, ::ams::fs::IStorage>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_memory_management.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_memory_management.hpp new file mode 100644 index 00000000..2162fb3e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_memory_management.hpp @@ -0,0 +1,165 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + using AllocateFunction = void *(*)(size_t); + using DeallocateFunction = void (*)(void *, size_t); + + void SetAllocator(AllocateFunction allocator, DeallocateFunction deallocator); + + namespace impl { + + class Newable; + + void *Allocate(size_t size); + void Deallocate(void *ptr, size_t size); + + void LockAllocatorMutex(); + void UnlockAllocatorMutex(); + + void *AllocateUnsafe(size_t size); + void DeallocateUnsafe(void *ptr, size_t size); + + class AllocatorImpl { + public: + static ALWAYS_INLINE void *Allocate(size_t size) { return ::ams::fs::impl::Allocate(size); } + static ALWAYS_INLINE void *AllocateUnsafe(size_t size) { return ::ams::fs::impl::AllocateUnsafe(size); } + + static ALWAYS_INLINE void Deallocate(void *ptr, size_t size) { return ::ams::fs::impl::Deallocate(ptr, size); } + static ALWAYS_INLINE void DeallocateUnsafe(void *ptr, size_t size) { return ::ams::fs::impl::DeallocateUnsafe(ptr, size); } + + static ALWAYS_INLINE void LockAllocatorMutex() { return ::ams::fs::impl::LockAllocatorMutex(); } + static ALWAYS_INLINE void UnlockAllocatorMutex() { return ::ams::fs::impl::UnlockAllocatorMutex(); } + }; + + template<typename T, typename Impl, bool AllocateWhileLocked> + class AllocatorTemplate : public std::allocator<T> { + public: + template<typename U> + struct rebind { + using other = AllocatorTemplate<U, Impl, AllocateWhileLocked>; + }; + private: + bool m_allocation_failed; + private: + static ALWAYS_INLINE T *AllocateImpl(::std::size_t n) { + if constexpr (AllocateWhileLocked) { + auto * const p = Impl::AllocateUnsafe(sizeof(T) * n); + Impl::UnlockAllocatorMutex(); + return static_cast<T *>(p); + } else { + return static_cast<T *>(Impl::Allocate(sizeof(T) * n)); + } + } + public: + AllocatorTemplate() : m_allocation_failed(false) { /* ... */ } + + template<typename U> + AllocatorTemplate(const AllocatorTemplate<U, Impl, AllocateWhileLocked> &rhs) : m_allocation_failed(rhs.IsAllocationFailed()) { /* ... */ } + + bool IsAllocationFailed() const { return m_allocation_failed; } + + [[nodiscard]] T *allocate(::std::size_t n) { + auto * const p = AllocateImpl(n); + if (AMS_UNLIKELY(p == nullptr) && n) { + m_allocation_failed = true; + } + return p; + } + + void deallocate(T *p, ::std::size_t n) { + Impl::Deallocate(p, sizeof(T) * n); + } + }; + + template<typename T, typename Impl> + using AllocatorTemplateForAllocateShared = AllocatorTemplate<T, Impl, true>; + + template<typename T, template<typename, typename> class AllocatorTemplateT, typename Impl, typename... Args> + std::shared_ptr<T> AllocateSharedImpl(Args &&... args) { + /* Try to allocate. */ + { + /* Acquire exclusive access to the allocator. */ + Impl::LockAllocatorMutex(); + + /* Check that we can allocate memory (using overestimate of 0x80 + sizeof(T)). */ + if (auto * const p = Impl::AllocateUnsafe(0x80 + sizeof(T)); AMS_LIKELY(p != nullptr)) { + /* Free the memory we allocated. */ + Impl::DeallocateUnsafe(p, 0x80 + sizeof(T)); + + /* Get allocator type. */ + using AllocatorType = AllocatorTemplateT<T, Impl>; + + /* Allocate the shared pointer. */ + return std::allocate_shared<T>(AllocatorType{}, std::forward<Args>(args)...); + } else { + /* We can't allocate. */ + Impl::UnlockAllocatorMutex(); + } + } + + /* We failed. */ + return nullptr; + } + + class Deleter { + private: + size_t m_size; + public: + Deleter() : m_size() { /* ... */ } + explicit Deleter(size_t sz) : m_size(sz) { /* ... */ } + + void operator()(void *ptr) const { + ::ams::fs::impl::Deallocate(ptr, m_size); + } + }; + + template<typename T> + auto MakeUnique() { + /* Check that we're not using MakeUnique unnecessarily. */ + static_assert(!std::derived_from<T, ::ams::fs::impl::Newable>); + + return std::unique_ptr<T, Deleter>(static_cast<T *>(::ams::fs::impl::Allocate(sizeof(T))), Deleter(sizeof(T))); + } + + template<typename ArrayT> + auto MakeUnique(size_t size) { + using T = typename std::remove_extent<ArrayT>::type; + + static_assert(util::is_pod<ArrayT>::value); + static_assert(std::is_array<ArrayT>::value); + + /* Check that we're not using MakeUnique unnecessarily. */ + static_assert(!std::derived_from<T, ::ams::fs::impl::Newable>); + + using ReturnType = std::unique_ptr<ArrayT, Deleter>; + + const size_t alloc_size = sizeof(T) * size; + return ReturnType(static_cast<T *>(::ams::fs::impl::Allocate(alloc_size)), Deleter(alloc_size)); + } + + } + + template<typename T, typename... Args> + std::shared_ptr<T> AllocateShared(Args &&... args) { + return ::ams::fs::impl::AllocateSharedImpl<T, ::ams::fs::impl::AllocatorTemplateForAllocateShared, ::ams::fs::impl::AllocatorImpl>(std::forward<Args>(args)...); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_memory_report_info.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_memory_report_info.hpp new file mode 100644 index 00000000..3f72e819 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_memory_report_info.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs { + + struct MemoryReportInfo { + u64 pooled_buffer_peak_free_size; + u64 pooled_buffer_retried_count; + u64 pooled_buffer_reduce_allocation_count; + u64 buffer_manager_peak_free_size; + u64 buffer_manager_retried_count; + u64 exp_heap_peak_free_size; + u64 buffer_pool_peak_free_size; + u64 patrol_read_allocate_buffer_success_count; + u64 patrol_read_allocate_buffer_failure_count; + u64 buffer_manager_peak_total_allocatable_size; + u64 buffer_pool_max_allocate_size; + u64 pooled_buffer_failed_ideal_allocation_count_on_async_access; + + u8 reserved[0x20]; + }; + static_assert(sizeof(MemoryReportInfo) == 0x80); + static_assert(util::is_pod<MemoryReportInfo>::value); + + Result GetAndClearMemoryReportInfo(MemoryReportInfo *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_memory_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_memory_storage.hpp new file mode 100644 index 00000000..8f17a047 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_memory_storage.hpp @@ -0,0 +1,88 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/impl/fs_newable.hpp> +#include <stratosphere/fs/fs_istorage.hpp> +#include <stratosphere/fs/fs_query_range.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + class MemoryStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + private: + u8 * const m_buf; + const s64 m_size; + public: + MemoryStorage(void *b, s64 sz) : m_buf(static_cast<u8 *>(b)), m_size(sz) { /* .. */ } + public: + virtual Result Read(s64 offset, void *buffer, size_t size) override { + /* Succeed immediately on zero-sized read. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + R_TRY(IStorage::CheckAccessRange(offset, size, m_size)); + + /* Copy from memory. */ + std::memcpy(buffer, m_buf + offset, size); + R_SUCCEED(); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + /* Succeed immediately on zero-sized write. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + R_TRY(IStorage::CheckAccessRange(offset, size, m_size)); + + /* Copy to memory. */ + std::memcpy(m_buf + offset, buffer, size); + R_SUCCEED(); + } + + virtual Result Flush() override { + R_SUCCEED(); + } + + virtual Result GetSize(s64 *out) override { + *out = m_size; + R_SUCCEED(); + } + + virtual Result SetSize(s64 size) override { + AMS_UNUSED(size); + R_THROW(fs::ResultUnsupportedSetSizeForMemoryStorage()); + } + + virtual Result OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + AMS_UNUSED(offset, size, src, src_size); + + switch (op_id) { + case OperationId::Invalidate: + R_SUCCEED(); + case OperationId::QueryRange: + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(QueryRangeInfo), fs::ResultInvalidSize()); + reinterpret_cast<QueryRangeInfo *>(dst)->Clear(); + R_SUCCEED(); + default: + R_THROW(fs::ResultUnsupportedOperateRangeForMemoryStorage()); + } + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_mmc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_mmc.hpp new file mode 100644 index 00000000..b954378b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_mmc.hpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs { + + constexpr inline size_t MmcCidSize = 0x10; + + constexpr inline size_t MmcExtendedCsdSize = 0x200; + + constexpr inline int MmcExtendedCsdOffsetReEolInfo = 267; + constexpr inline int MmcExtendedCsdOffsetDeviceLifeTimeEstTypA = 268; + constexpr inline int MmcExtendedCsdOffsetDeviceLifeTimeEstTypB = 269; + + enum MmcSpeedMode { + MmcSpeedMode_Identification = 0, + MmcSpeedMode_LegacySpeed = 1, + MmcSpeedMode_HighSpeed = 2, + MmcSpeedMode_Hs200 = 3, + MmcSpeedMode_Hs400 = 4, + MmcSpeedMode_Unknown = 5, + }; + + enum class MmcPartition { + UserData = 0, + BootPartition1 = 1, + BootPartition2 = 2, + }; + + Result GetMmcCid(void *dst, size_t size); + + inline void ClearMmcCidSerialNumber(u8 *cid) { + /* Clear the serial number from the cid. */ + std::memset(cid + 1, 0, 4); + } + + Result GetMmcSpeedMode(MmcSpeedMode *out); + + Result GetMmcPatrolCount(u32 *out); + + Result GetMmcExtendedCsd(void *dst, size_t size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_mount.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_mount.hpp new file mode 100644 index 00000000..3e6232b2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_mount.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + constexpr inline size_t MountNameLengthMax = 15; + + Result ConvertToFsCommonPath(char *dst, size_t dst_size, const char *src); + + void Unmount(const char *mount_name); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_operate_range.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_operate_range.hpp new file mode 100644 index 00000000..ad63e5a2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_operate_range.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + enum class OperationId : s64 { + FillZero = 0, + DestroySignature = 1, + Invalidate = 2, + QueryRange = 3, + QueryUnpreparedRange = 4, + QueryLazyLoadCompletionRate = 5, + SetLazyLoadPriority = 6, + + ReadLazyLoadFileForciblyForDebug = 10001, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_path.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_path.hpp new file mode 100644 index 00000000..c2bb12cd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_path.hpp @@ -0,0 +1,675 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_memory_management.hpp> +#include <stratosphere/fs/impl/fs_common_mount_name.hpp> +#include <stratosphere/fs/fs_path_utility.hpp> + +namespace ams::fs { + + class DirectoryPathParser; + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + /* NOTE: Intentional inaccuracy in custom WriteBuffer class, to save 0x10 bytes (0x28 -> 0x18) over Nintendo's implementation. */ + class Path { + NON_COPYABLE(Path); + NON_MOVEABLE(Path); + private: + static constexpr const char *EmptyPath = ""; + static constexpr size_t WriteBufferAlignmentLength = 8; + private: + friend class DirectoryPathParser; + public: + class WriteBuffer { + NON_COPYABLE(WriteBuffer); + private: + char *m_buffer; + size_t m_length_and_is_normalized; + public: + constexpr WriteBuffer() : m_buffer(nullptr), m_length_and_is_normalized(0) { /* ... */ } + + constexpr ~WriteBuffer() { + if (m_buffer != nullptr) { + ::ams::fs::impl::Deallocate(m_buffer, this->GetLength()); + this->ResetBuffer(); + } + } + + constexpr WriteBuffer(WriteBuffer &&rhs) : m_buffer(rhs.m_buffer), m_length_and_is_normalized(rhs.m_length_and_is_normalized) { + rhs.ResetBuffer(); + } + + constexpr WriteBuffer &operator=(WriteBuffer &&rhs) { + if (m_buffer != nullptr) { + ::ams::fs::impl::Deallocate(m_buffer, this->GetLength()); + } + + m_buffer = rhs.m_buffer; + m_length_and_is_normalized = rhs.m_length_and_is_normalized; + + rhs.ResetBuffer(); + + return *this; + } + + std::unique_ptr<char[], ::ams::fs::impl::Deleter> ReleaseBuffer() { + auto released = std::unique_ptr<char[], ::ams::fs::impl::Deleter>(m_buffer, ::ams::fs::impl::Deleter(this->GetLength())); + this->ResetBuffer(); + return released; + } + + constexpr ALWAYS_INLINE void ResetBuffer() { + m_buffer = nullptr; + this->SetLength(0); + } + + constexpr ALWAYS_INLINE char *Get() const { + return m_buffer; + } + + constexpr ALWAYS_INLINE size_t GetLength() const { + return m_length_and_is_normalized >> 1; + } + + constexpr ALWAYS_INLINE bool IsNormalized() const { + return static_cast<bool>(m_length_and_is_normalized & 1); + } + + constexpr ALWAYS_INLINE void SetNormalized() { + m_length_and_is_normalized |= static_cast<size_t>(1); + } + + constexpr ALWAYS_INLINE void SetNotNormalized() { + m_length_and_is_normalized &= ~static_cast<size_t>(1); + } + private: + constexpr ALWAYS_INLINE WriteBuffer(char *buffer, size_t length) : m_buffer(buffer), m_length_and_is_normalized(0) { + this->SetLength(length); + } + public: + static WriteBuffer Make(size_t length) { + if (void *alloc = ::ams::fs::impl::Allocate(length); alloc != nullptr) { + return WriteBuffer(static_cast<char *>(alloc), length); + } else { + return WriteBuffer(); + } + } + private: + + constexpr ALWAYS_INLINE void SetLength(size_t size) { + m_length_and_is_normalized = (m_length_and_is_normalized & 1) | (size << 1); + } + }; + private: + const char *m_str; + WriteBuffer m_write_buffer; + public: + constexpr Path() : m_str(EmptyPath), m_write_buffer() { + /* ... */ + } + + constexpr Path(const char *s, util::ConstantInitializeTag) : m_str(s), m_write_buffer() { + m_write_buffer.SetNormalized(); + } + + constexpr ~Path() { /* ... */ } + + std::unique_ptr<char[], ::ams::fs::impl::Deleter> ReleaseBuffer() { + /* Check pre-conditions. */ + AMS_ASSERT(m_write_buffer.Get() != nullptr); + + /* Reset. */ + m_str = EmptyPath; + + /* Release our write buffer. */ + return m_write_buffer.ReleaseBuffer(); + } + + constexpr Result SetShallowBuffer(const char *buffer) { + /* Check pre-conditions. */ + AMS_ASSERT(m_write_buffer.GetLength() == 0); + + /* Check the buffer is valid. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Set buffer. */ + this->SetReadOnlyBuffer(buffer); + + /* Note that we're normalized. */ + this->SetNormalized(); + + R_SUCCEED(); + } + + constexpr const char *GetString() const { + /* Check pre-conditions. */ + AMS_ASSERT(this->IsNormalized()); + + return m_str; + } + + constexpr size_t GetLength() const { + if (std::is_constant_evaluated()) { + return util::Strlen(this->GetString()); + } else { + return std::strlen(this->GetString()); + } + } + + constexpr bool IsEmpty() const { + return *m_str == '\x00'; + } + + constexpr bool IsMatchHead(const char *p, size_t len) const { + return util::Strncmp(this->GetString(), p, len) == 0; + } + + Result Initialize(const Path &rhs) { + /* Check the other path is normalized. */ + const bool normalized = rhs.IsNormalized(); + R_UNLESS(normalized, fs::ResultNotNormalized()); + + /* Allocate buffer for our path. */ + const auto len = rhs.GetLength(); + R_TRY(this->Preallocate(len + 1)); + + /* Copy the path. */ + const size_t copied = util::Strlcpy<char>(m_write_buffer.Get(), rhs.GetString(), len + 1); + R_UNLESS(copied == len, fs::ResultUnexpectedInPathA()); + + /* Set normalized. */ + this->SetNormalized(); + R_SUCCEED(); + } + + Result Initialize(const char *path, size_t len) { + /* Check the path is valid. */ + R_UNLESS(path != nullptr, fs::ResultNullptrArgument()); + + /* Initialize. */ + R_TRY(this->InitializeImpl(path, len)); + + /* Set not normalized. */ + this->SetNotNormalized(); + + R_SUCCEED(); + } + + Result Initialize(const char *path) { + /* Check the path is valid. */ + R_UNLESS(path != nullptr, fs::ResultNullptrArgument()); + + R_RETURN(this->Initialize(path, std::strlen(path))); + } + + Result InitializeWithFormat(const char *fmt, ...) __attribute__((format (printf, 2, 3))) { + /* Check the format string is valid. */ + R_UNLESS(fmt != nullptr, fs::ResultNullptrArgument()); + + /* Create the va_list for formatting. */ + std::va_list vl; + va_start(vl, fmt); + + /* Determine how big the string will be. */ + char dummy; + const auto len = util::VSNPrintf(std::addressof(dummy), 0, fmt, vl); + + /* Allocate buffer for our path. */ + R_TRY(this->Preallocate(len + 1)); + + /* Format our path into our new buffer. */ + const auto real_len = util::VSNPrintf(m_write_buffer.Get(), m_write_buffer.GetLength(), fmt, vl); + AMS_ASSERT(real_len == len); + AMS_UNUSED(real_len); + + /* Finish the va_list. */ + va_end(vl); + + /* Set not normalized. */ + this->SetNotNormalized(); + + R_SUCCEED(); + } + + Result InitializeWithReplaceBackslash(const char *path) { + /* Check the path is valid. */ + R_UNLESS(path != nullptr, fs::ResultNullptrArgument()); + + /* Initialize. */ + R_TRY(this->InitializeImpl(path, std::strlen(path))); + + /* Replace slashes as desired. */ + if (const auto write_buffer_length = m_write_buffer.GetLength(); write_buffer_length > 1) { + fs::Replace(m_write_buffer.Get(), write_buffer_length - 1, '\\', '/'); + } + + /* Set not normalized. */ + this->SetNotNormalized(); + + R_SUCCEED(); + } + + Result InitializeWithReplaceForwardSlashes(const char *path) { + /* Check the path is valid. */ + R_UNLESS(path != nullptr, fs::ResultNullptrArgument()); + + /* Initialize. */ + R_TRY(this->InitializeImpl(path, std::strlen(path))); + + /* Replace slashes as desired. */ + if (m_write_buffer.GetLength() > 1) { + if (auto *p = m_write_buffer.Get(); p[0] == '/' && p[1] == '/') { + p[0] = '\\'; + p[1] = '\\'; + } + } + + /* Set not normalized. */ + this->SetNotNormalized(); + + R_SUCCEED(); + } + + Result InitializeWithReplaceUnc(const char *path) { + /* Check the path is valid. */ + R_UNLESS(path != nullptr, fs::ResultNullptrArgument()); + + /* Initialize. */ + R_TRY(this->InitializeImpl(path, std::strlen(path))); + + /* Set not normalized. */ + this->SetNotNormalized(); + + /* Replace unc as desired. */ + if (m_str[0]) { + auto *p = m_write_buffer.Get(); + + /* Replace :/// -> \\ as needed. */ + if (auto *sep = std::strstr(p, ":///"); sep != nullptr) { + sep[0] = '\\'; + sep[1] = '\\'; + } + + /* Edit path prefix. */ + if (!util::Strncmp(p, AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME AMS_FS_IMPL_MOUNT_NAME_DELIMITER "/", AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME_LEN + AMS_FS_IMPL_MOUNT_NAME_DELIMITER_LEN + 1)) { + static_assert((AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME AMS_FS_IMPL_MOUNT_NAME_DELIMITER)[AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME_LEN + AMS_FS_IMPL_MOUNT_NAME_DELIMITER_LEN - 1] == '/'); + + p[AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME_LEN + AMS_FS_IMPL_MOUNT_NAME_DELIMITER_LEN - 1] = '\\'; + p[AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME_LEN + AMS_FS_IMPL_MOUNT_NAME_DELIMITER_LEN - 0] = '\\'; + } + + if (p[0] == '/' && p[1] == '/') { + p[0] = '\\'; + p[1] = '\\'; + } + } + + R_SUCCEED(); + } + + Result InitializeWithNormalization(const char *path, size_t size) { + /* Check the path is valid. */ + R_UNLESS(path != nullptr, fs::ResultNullptrArgument()); + + /* Initialize. */ + R_TRY(this->InitializeImpl(path, size)); + + /* Set not normalized. */ + this->SetNotNormalized(); + + /* Perform normalization. */ + fs::PathFlags path_flags; + if (fs::IsPathRelative(m_str)) { + path_flags.AllowRelativePath(); + } else if (fs::IsWindowsPath(m_str, true)) { + path_flags.AllowWindowsPath(); + } else { + /* NOTE: In this case, Nintendo checks is normalized, then sets is normalized, then returns success. */ + /* This seems like a bug. */ + size_t dummy; + bool normalized; + R_TRY(PathFormatter::IsNormalized(std::addressof(normalized), std::addressof(dummy), m_str)); + + this->SetNormalized(); + R_SUCCEED(); + } + + /* Normalize. */ + R_TRY(this->Normalize(path_flags)); + + this->SetNormalized(); + R_SUCCEED(); + } + + Result InitializeWithNormalization(const char *path) { + /* Check the path is valid. */ + R_UNLESS(path != nullptr, fs::ResultNullptrArgument()); + + R_RETURN(this->InitializeWithNormalization(path, std::strlen(path))); + } + + Result InitializeAsEmpty() { + /* Clear our buffer. */ + this->ClearBuffer(); + + /* Set normalized. */ + this->SetNormalized(); + + R_SUCCEED(); + } + + Result AppendChild(const char *child) { + /* Check the path is valid. */ + R_UNLESS(child != nullptr, fs::ResultNullptrArgument()); + + /* Basic checks. If we hvea a path and the child is empty, we have nothing to do. */ + const char *c = child; + if (m_str[0]) { + /* Skip an early separator. */ + if (*c == '/') { + ++c; + } + + R_SUCCEED_IF(*c == '\x00'); + } + + /* If we don't have a string, we can just initialize. */ + auto cur_len = std::strlen(m_str); + if (cur_len == 0) { + R_RETURN(this->Initialize(child)); + } + + /* Remove a trailing separator. */ + if (m_str[cur_len - 1] == '/' || m_str[cur_len - 1] == '\\') { + --cur_len; + } + + /* Get the child path's length. */ + auto child_len = std::strlen(c); + + /* Reset our write buffer. */ + WriteBuffer old_write_buffer; + if (m_write_buffer.Get() != nullptr) { + old_write_buffer = std::move(m_write_buffer); + this->ClearBuffer(); + } + + /* Pre-allocate the new buffer. */ + R_TRY(this->Preallocate(cur_len + 1 + child_len + 1)); + + /* Get our write buffer. */ + auto *dst = m_write_buffer.Get(); + if (old_write_buffer.Get() != nullptr && cur_len > 0) { + util::Strlcpy<char>(dst, old_write_buffer.Get(), cur_len + 1); + } + + /* Add separator. */ + dst[cur_len] = '/'; + + /* Copy the child path. */ + const size_t copied = util::Strlcpy<char>(dst + cur_len + 1, c, child_len + 1); + R_UNLESS(copied == child_len, fs::ResultUnexpectedInPathA()); + + R_SUCCEED(); + } + + Result AppendChild(const Path &rhs) { + R_RETURN(this->AppendChild(rhs.GetString())); + } + + Result Combine(const Path &parent, const Path &child) { + /* Get the lengths. */ + const auto p_len = parent.GetLength(); + const auto c_len = child.GetLength(); + + /* Allocate our buffer. */ + R_TRY(this->Preallocate(p_len + c_len + 1)); + + /* Initialize as parent. */ + R_TRY(this->Initialize(parent)); + + /* If we're empty, we can just initialize as child. */ + if (this->IsEmpty()) { + R_TRY(this->Initialize(child)); + } else { + /* Otherwise, we should append the child. */ + R_TRY(this->AppendChild(child)); + } + + R_SUCCEED(); + } + + Result RemoveChild() { + /* If we don't have a write-buffer, ensure that we have one. */ + if (m_write_buffer.Get() == nullptr) { + if (const auto len = std::strlen(m_str); len > 0) { + R_TRY(this->Preallocate(len)); + util::Strlcpy<char>(m_write_buffer.Get(), m_str, len + 1); + } + } + + /* Check that it's possible for us to remove a child. */ + auto *p = m_write_buffer.Get(); + s32 len = std::strlen(p); + R_UNLESS(len != 1 || (p[0] != '/' && p[0] != '.'), fs::ResultNotImplemented()); + + /* Handle a trailing separator. */ + if (len > 0 && (p[len - 1] == '\\' || p[len - 1] == '/')) { + --len; + } + + /* Remove the child path segment. */ + while ((--len) >= 0 && p[len]) { + if (p[len] == '/' || p[len] == '\\') { + if (len > 0) { + p[len] = 0; + } else { + p[1] = 0; + len = 1; + } + break; + } + } + + /* Check that length remains > 0. */ + R_UNLESS(len > 0, fs::ResultNotImplemented()); + + R_SUCCEED(); + } + + Result Normalize(const PathFlags &flags) { + /* If we're already normalized, nothing to do. */ + R_SUCCEED_IF(this->IsNormalized()); + + /* Check if we're normalized. */ + bool normalized; + size_t dummy; + R_TRY(PathFormatter::IsNormalized(std::addressof(normalized), std::addressof(dummy), m_str, flags)); + + /* If we're not normalized, normalize. */ + if (!normalized) { + /* Determine necessary buffer length. */ + auto len = m_write_buffer.GetLength(); + if (flags.IsRelativePathAllowed() && fs::IsPathRelative(m_str)) { + len += 2; + } + if (flags.IsWindowsPathAllowed() && fs::IsWindowsPath(m_str, true)) { + len += 1; + } + + /* Allocate a new buffer. */ + const size_t size = util::AlignUp(len, WriteBufferAlignmentLength); + auto buf = WriteBuffer::Make(size); + R_UNLESS(buf.Get() != nullptr, fs::ResultAllocationMemoryFailedMakeUnique()); + + /* Normalize into it. */ + R_TRY(PathFormatter::Normalize(buf.Get(), size, m_write_buffer.Get(), m_write_buffer.GetLength(), flags)); + + /* Set the normalized buffer as our buffer. */ + this->SetModifiableBuffer(std::move(buf)); + } + + /* Set normalized. */ + this->SetNormalized(); + R_SUCCEED(); + } + private: + void ClearBuffer() { + m_write_buffer.ResetBuffer(); + m_str = EmptyPath; + } + + void SetModifiableBuffer(WriteBuffer &&buffer) { + /* Check pre-conditions. */ + AMS_ASSERT(buffer.Get() != nullptr); + AMS_ASSERT(buffer.GetLength() > 0); + AMS_ASSERT(util::IsAligned(buffer.GetLength(), WriteBufferAlignmentLength)); + + /* Get whether we're normalized. */ + if (m_write_buffer.IsNormalized()) { + buffer.SetNormalized(); + } else { + buffer.SetNotNormalized(); + } + + /* Set write buffer. */ + m_write_buffer = std::move(buffer); + m_str = m_write_buffer.Get(); + } + + constexpr void SetReadOnlyBuffer(const char *buffer) { + m_str = buffer; + m_write_buffer.ResetBuffer(); + } + + Result Preallocate(size_t length) { + /* Allocate additional space, if needed. */ + if (length > m_write_buffer.GetLength()) { + /* Allocate buffer. */ + const size_t size = util::AlignUp(length, WriteBufferAlignmentLength); + auto buf = WriteBuffer::Make(size); + R_UNLESS(buf.Get() != nullptr, fs::ResultAllocationMemoryFailedMakeUnique()); + + /* Set write buffer. */ + this->SetModifiableBuffer(std::move(buf)); + } + + R_SUCCEED(); + } + + Result InitializeImpl(const char *path, size_t size) { + if (size > 0 && path[0]) { + /* Pre allocate a buffer for the path. */ + R_TRY(this->Preallocate(size + 1)); + + /* Copy the path. */ + const size_t copied = util::Strlcpy<char>(m_write_buffer.Get(), path, size + 1); + R_UNLESS(copied >= size, fs::ResultUnexpectedInPathA()); + } else { + /* We can just clear the buffer. */ + this->ClearBuffer(); + } + + R_SUCCEED(); + } + + constexpr char *GetWriteBuffer() { + AMS_ASSERT(m_write_buffer.Get() != nullptr); + return m_write_buffer.Get(); + } + + constexpr ALWAYS_INLINE size_t GetWriteBufferLength() const { + return m_write_buffer.GetLength(); + } + + constexpr ALWAYS_INLINE bool IsNormalized() const { return m_write_buffer.IsNormalized(); } + + constexpr ALWAYS_INLINE void SetNormalized() { m_write_buffer.SetNormalized(); } + + constexpr ALWAYS_INLINE void SetNotNormalized() { m_write_buffer.SetNotNormalized(); } + public: + ALWAYS_INLINE bool operator==(const fs::Path &rhs) const { return std::strcmp(this->GetString(), rhs.GetString()) == 0; } + ALWAYS_INLINE bool operator!=(const fs::Path &rhs) const { return !(*this == rhs); } + ALWAYS_INLINE bool operator==(const char *p) const { return std::strcmp(this->GetString(), p) == 0; } + ALWAYS_INLINE bool operator!=(const char *p) const { return !(*this == p); } + }; + + consteval fs::Path MakeConstantPath(const char *s) { return fs::Path(s, util::ConstantInitializeTag{}); } + + inline Result SetUpFixedPath(fs::Path *out, const char *s) { + /* Verify the path is normalized. */ + bool normalized; + size_t dummy; + R_TRY(PathNormalizer::IsNormalized(std::addressof(normalized), std::addressof(dummy), s)); + + R_UNLESS(normalized, fs::ResultInvalidPathFormat()); + + /* Set the fixed path. */ + R_RETURN(out->SetShallowBuffer(s)); + } + + inline Result SetUpFixedPathSingleEntry(fs::Path *out, char *buf, size_t buf_size, const char *e) { + /* Print the path into the buffer. */ + const size_t len = util::TSNPrintf(buf, buf_size, "/%s", e); + R_UNLESS(len < buf_size, fs::ResultInvalidArgument()); + + /* Set up the path. */ + R_RETURN(SetUpFixedPath(out, buf)); + } + + inline Result SetUpFixedPathDoubleEntry(fs::Path *out, char *buf, size_t buf_size, const char *e, const char *e2) { + /* Print the path into the buffer. */ + const size_t len = util::TSNPrintf(buf, buf_size, "/%s/%s", e, e2); + R_UNLESS(len < buf_size, fs::ResultInvalidArgument()); + + /* Set up the path. */ + R_RETURN(SetUpFixedPath(out, buf)); + } + + inline Result SetUpFixedPathSaveId(fs::Path *out, char *buf, size_t buf_size, u64 id) { + /* Print the path into the buffer. */ + const size_t len = util::TSNPrintf(buf, buf_size, "/%016" PRIx64 "", id); + R_UNLESS(len < buf_size, fs::ResultInvalidArgument()); + + /* Set up the path. */ + R_RETURN(SetUpFixedPath(out, buf)); + } + + inline Result SetUpFixedPathSaveMetaName(fs::Path *out, char *buf, size_t buf_size, u32 type) { + /* Print the path into the buffer. */ + const size_t len = util::TSNPrintf(buf, buf_size, "/%08" PRIx32 ".meta", type); + R_UNLESS(len < buf_size, fs::ResultInvalidArgument()); + + /* Set up the path. */ + R_RETURN(SetUpFixedPath(out, buf)); + } + + inline Result SetUpFixedPathSaveMetaDir(fs::Path *out, char *buf, size_t buf_size, u64 id) { + /* Print the path into the buffer. */ + const size_t len = util::TSNPrintf(buf, buf_size, "/saveMeta/%016" PRIx64 "", id); + R_UNLESS(len < buf_size, fs::ResultInvalidArgument()); + + /* Set up the path. */ + R_RETURN(SetUpFixedPath(out, buf)); + } + + constexpr inline bool IsWindowsDriveRootPath(const fs::Path &path) { + const char * const str = path.GetString(); + return fs::IsWindowsDrive(str) && (str[2] == StringTraits::DirectorySeparator || str[2] == StringTraits::AlternateDirectorySeparator) && str[3] == StringTraits::NullTerminator; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_path_utility.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_path_utility.hpp new file mode 100644 index 00000000..a3025a5e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_path_utility.hpp @@ -0,0 +1,1211 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_path.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + namespace StringTraits { + + constexpr inline char DirectorySeparator = '/'; + constexpr inline char DriveSeparator = ':'; + constexpr inline char Dot = '.'; + constexpr inline char NullTerminator = '\x00'; + + constexpr inline char AlternateDirectorySeparator = '\\'; + + constexpr inline const char InvalidCharacters[6] = { ':', '*', '?', '<', '>', '|' }; + constexpr inline const char InvalidCharactersForHostName[6] = { ':', '*', '<', '>', '|', '$' }; + constexpr inline const char InvalidCharactersForMountName[5] = { '*', '?', '<', '>', '|' }; + + namespace impl { + + template<const char *InvalidCharacterSet, size_t NumInvalidCharacters> + consteval u64 MakeInvalidCharacterMask(size_t n) { + u64 mask = 0; + for (size_t i = 0; i < NumInvalidCharacters; ++i) { + if ((static_cast<u64>(InvalidCharacterSet[i]) >> 6) == n) { + mask |= static_cast<u64>(1) << (static_cast<u64>(InvalidCharacterSet[i]) & 0x3F); + } + } + return mask; + } + + template<const char *InvalidCharacterSet, size_t NumInvalidCharacters> + constexpr ALWAYS_INLINE bool IsInvalidCharacterImpl(char c) { + constexpr u64 Masks[4] = { + MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(0), + MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(1), + MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(2), + MakeInvalidCharacterMask<InvalidCharacterSet, NumInvalidCharacters>(3) + }; + + return (Masks[static_cast<u64>(c) >> 6] & (static_cast<u64>(1) << (static_cast<u64>(c) & 0x3F))) != 0; + } + + } + + constexpr ALWAYS_INLINE bool IsInvalidCharacter(char c) { return impl::IsInvalidCharacterImpl<InvalidCharacters, util::size(InvalidCharacters)>(c); } + constexpr ALWAYS_INLINE bool IsInvalidCharacterForHostName(char c) { return impl::IsInvalidCharacterImpl<InvalidCharactersForHostName, util::size(InvalidCharactersForHostName)>(c); } + constexpr ALWAYS_INLINE bool IsInvalidCharacterForMountName(char c) { return impl::IsInvalidCharacterImpl<InvalidCharactersForMountName, util::size(InvalidCharactersForMountName)>(c); } + + } + + constexpr inline size_t WindowsDriveLength = 2; + constexpr inline size_t UncPathPrefixLength = 2; + constexpr inline size_t DosDevicePathPrefixLength = 4; + + class PathFlags { + private: + static constexpr u32 WindowsPathFlag = (1 << 0); + static constexpr u32 RelativePathFlag = (1 << 1); + static constexpr u32 EmptyPathFlag = (1 << 2); + static constexpr u32 MountNameFlag = (1 << 3); + static constexpr u32 BackslashFlag = (1 << 4); + static constexpr u32 AllCharactersFlag = (1 << 5); + private: + u32 m_value; + public: + constexpr ALWAYS_INLINE PathFlags() : m_value(0) { /* ... */ } + + #define DECLARE_PATH_FLAG_HANDLER(__WHICH__) \ + constexpr ALWAYS_INLINE bool Is ## __WHICH__ ##Allowed() const { return (m_value & __WHICH__ ## Flag) != 0; } \ + constexpr ALWAYS_INLINE void Allow ## __WHICH__ () { m_value |= __WHICH__ ## Flag; } + + DECLARE_PATH_FLAG_HANDLER(WindowsPath) + DECLARE_PATH_FLAG_HANDLER(RelativePath) + DECLARE_PATH_FLAG_HANDLER(EmptyPath) + DECLARE_PATH_FLAG_HANDLER(MountName) + DECLARE_PATH_FLAG_HANDLER(Backslash) + DECLARE_PATH_FLAG_HANDLER(AllCharacters) + + #undef DECLARE_PATH_FLAG_HANDLER + }; + + template<typename T> requires (std::same_as<T, char> || std::same_as<T, wchar_t>) + constexpr inline bool IsDosDevicePath(const T *path) { + AMS_ASSERT(path != nullptr); + + using namespace StringTraits; + + return path[0] == AlternateDirectorySeparator && path[1] == AlternateDirectorySeparator && (path[2] == Dot || path[2] == '?') && (path[3] == DirectorySeparator || path[3] == AlternateDirectorySeparator); + } + + template<typename T> requires (std::same_as<T, char> || std::same_as<T, wchar_t>) + constexpr inline bool IsUncPath(const T *path, bool allow_forward_slash = true, bool allow_back_slash = true) { + AMS_ASSERT(path != nullptr); + + using namespace StringTraits; + + return (allow_forward_slash && path[0] == DirectorySeparator && path[1] == DirectorySeparator) || (allow_back_slash && path[0] == AlternateDirectorySeparator && path[1] == AlternateDirectorySeparator); + } + + constexpr inline bool IsWindowsDrive(const char *path) { + AMS_ASSERT(path != nullptr); + + return (('a' <= path[0] && path[0] <= 'z') || ('A' <= path[0] && path[0] <= 'Z')) && path[1] == StringTraits::DriveSeparator; + } + + constexpr inline bool IsWindowsPath(const char *path, bool allow_forward_slash_unc) { + return IsWindowsDrive(path) || IsDosDevicePath(path) || IsUncPath(path, allow_forward_slash_unc, true); + } + + constexpr inline int GetWindowsSkipLength(const char *path) { + if (IsDosDevicePath(path)) { + return DosDevicePathPrefixLength; + } else if (IsWindowsDrive(path)) { + return WindowsDriveLength; + } else if (IsUncPath(path)) { + return UncPathPrefixLength; + } else { + return 0; + } + } + + constexpr inline bool IsPathAbsolute(const char *path) { + return IsWindowsPath(path, false) || path[0] == StringTraits::DirectorySeparator; + } + + constexpr inline bool IsPathRelative(const char *path) { + return path[0] && !IsPathAbsolute(path); + } + + constexpr inline bool IsCurrentDirectory(const char *path) { + return path[0] == StringTraits::Dot && (path[1] == StringTraits::NullTerminator || path[1] == StringTraits::DirectorySeparator); + } + + constexpr inline bool IsParentDirectory(const char *path) { + return path[0] == StringTraits::Dot && path[1] == StringTraits::Dot && (path[2] == StringTraits::NullTerminator || path[2] == StringTraits::DirectorySeparator); + } + + constexpr inline bool IsPathStartWithCurrentDirectory(const char *path) { + return IsCurrentDirectory(path) || IsParentDirectory(path); + } + + constexpr inline bool IsSubPath(const char *lhs, const char *rhs) { + /* Check pre-conditions. */ + AMS_ASSERT(lhs != nullptr); + AMS_ASSERT(rhs != nullptr); + + /* Import StringTraits names for current scope. */ + using namespace StringTraits; + + /* Special case certain paths. */ + if (IsUncPath(lhs) && !IsUncPath(rhs)) { + return false; + } + if (!IsUncPath(lhs) && IsUncPath(rhs)) { + return false; + } + + if (lhs[0] == DirectorySeparator && lhs[1] == NullTerminator && rhs[0] == DirectorySeparator && rhs[1] != NullTerminator) { + return true; + } + if (rhs[0] == DirectorySeparator && rhs[1] == NullTerminator && lhs[0] == DirectorySeparator && lhs[1] != NullTerminator) { + return true; + } + + /* Check subpath. */ + for (size_t i = 0; /* ... */; ++i) { + if (lhs[i] == NullTerminator) { + return rhs[i] == DirectorySeparator; + } else if (rhs[i] == NullTerminator) { + return lhs[i] == DirectorySeparator; + } else if (lhs[i] != rhs[i]) { + return false; + } + } + } + + /* Path utilities. */ + constexpr inline void Replace(char *dst, size_t dst_size, char old_char, char new_char) { + AMS_ASSERT(dst != nullptr); + for (char *cur = dst; cur < dst + dst_size && *cur; ++cur) { + if (*cur == old_char) { + *cur = new_char; + } + } + } + + constexpr inline Result CheckUtf8(const char *s) { + /* Check pre-conditions. */ + AMS_ASSERT(s != nullptr); + + /* Iterate, checking for utf8-validity. */ + while (*s) { + char utf8_buf[4] = {}; + + const auto pick_res = util::PickOutCharacterFromUtf8String(utf8_buf, std::addressof(s)); + R_UNLESS(pick_res == util::CharacterEncodingResult_Success, fs::ResultInvalidPathFormat()); + + u32 dummy; + const auto cvt_res = util::ConvertCharacterUtf8ToUtf32(std::addressof(dummy), utf8_buf); + R_UNLESS(cvt_res == util::CharacterEncodingResult_Success, fs::ResultInvalidPathFormat()); + } + + R_SUCCEED(); + } + + /* Path formatting. */ + class PathNormalizer { + private: + enum class PathState { + Start, + Normal, + FirstSeparator, + Separator, + CurrentDir, + ParentDir, + }; + private: + static constexpr void ReplaceParentDirectoryPath(char *dst, const char *src) { + /* Use StringTraits names for remainder of scope. */ + using namespace StringTraits; + + /* Start with a dir-separator. */ + dst[0] = DirectorySeparator; + + auto i = 1; + while (src[i] != NullTerminator) { + if ((src[i - 1] == DirectorySeparator || src[i - 1] == AlternateDirectorySeparator) && src[i + 0] == Dot && src[i + 1] == Dot && (src[i + 2] == DirectorySeparator || src[i + 2] == AlternateDirectorySeparator)) { + dst[i - 1] = DirectorySeparator; + dst[i + 0] = Dot; + dst[i + 1] = Dot; + dst[i + 2] = DirectorySeparator; + i += 3; + } else { + if (src[i - 1] == AlternateDirectorySeparator && src[i + 0] == Dot && src[i + 1] == Dot && src[i + 2] == NullTerminator) { + dst[i - 1] = DirectorySeparator; + dst[i + 0] = Dot; + dst[i + 1] = Dot; + i += 2; + break; + } + + dst[i] = src[i]; + ++i; + } + } + + dst[i] = StringTraits::NullTerminator; + } + public: + static constexpr bool IsParentDirectoryPathReplacementNeeded(const char *path) { + /* Use StringTraits names for remainder of scope. */ + using namespace StringTraits; + + if (path[0] != DirectorySeparator && path[0] != AlternateDirectorySeparator) { + return false; + } + + /* Check to find a parent reference using alternate separators. */ + if (path[0] != NullTerminator && path[1] != NullTerminator && path[2] != NullTerminator) { + size_t i; + for (i = 0; path[i + 3] != NullTerminator; ++path) { + if (path[i + 1] != Dot || path[i + 2] != Dot) { + continue; + } + + const char c0 = path[i + 0]; + const char c3 = path[i + 3]; + + if (c0 == AlternateDirectorySeparator && (c3 == DirectorySeparator || c3 == AlternateDirectorySeparator || c3 == NullTerminator)) { + return true; + } + + if (c3 == AlternateDirectorySeparator && (c0 == DirectorySeparator || c0 == AlternateDirectorySeparator)) { + return true; + } + } + + if (path[i + 0] == AlternateDirectorySeparator && path[i + 1] == Dot && path[i + 2] == Dot /* && path[i + 3] == NullTerminator */) { + return true; + } + } + + return false; + } + + static constexpr Result IsNormalized(bool *out, size_t *out_len, const char *path, bool allow_all_characters = false) { + /* Use StringTraits names for remainder of scope. */ + using namespace StringTraits; + + /* Parse the path. */ + auto state = PathState::Start; + size_t len = 0; + while (path[len] != NullTerminator) { + /* Get the current character. */ + const char c = path[len++]; + + /* Check the current character is valid. */ + if (!allow_all_characters && state != PathState::Start) { + R_UNLESS(!IsInvalidCharacter(c), fs::ResultInvalidCharacter()); + } + + /* Process depending on current state. */ + switch (state) { + /* Import the PathState enums for convenience. */ + using enum PathState; + + case Start: + R_UNLESS(c == DirectorySeparator, fs::ResultInvalidPathFormat()); + state = FirstSeparator; + break; + case Normal: + if (c == DirectorySeparator) { + state = Separator; + } + break; + case FirstSeparator: + case Separator: + if (c == DirectorySeparator) { + *out = false; + R_SUCCEED(); + } + + if (c == Dot) { + state = CurrentDir; + } else { + state = Normal; + } + break; + case CurrentDir: + if (c == DirectorySeparator) { + *out = false; + R_SUCCEED(); + } + + if (c == Dot) { + state = ParentDir; + } else { + state = Normal; + } + break; + case ParentDir: + if (c == DirectorySeparator) { + *out = false; + R_SUCCEED(); + } + + state = Normal; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + /* Check the final state. */ + switch (state) { + /* Import the PathState enums for convenience. */ + using enum PathState; + case Start: + R_THROW(fs::ResultInvalidPathFormat()); + case Normal: + case FirstSeparator: + *out = true; + break; + case Separator: + case CurrentDir: + case ParentDir: + *out = false; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Set the output length. */ + *out_len = len; + R_SUCCEED(); + } + + static constexpr Result Normalize(char *dst, size_t *out_len, const char *path, size_t max_out_size, bool is_windows_path, bool is_drive_relative_path, bool allow_all_characters = false) { + /* Use StringTraits names for remainder of scope. */ + using namespace StringTraits; + + /* Prepare to iterate. */ + const char *cur_path = path; + size_t total_len = 0; + + /* If path begins with a separator, check that we're not drive relative. */ + if (cur_path[0] != DirectorySeparator) { + R_UNLESS(is_drive_relative_path, fs::ResultInvalidPathFormat()); + + dst[total_len++] = DirectorySeparator; + } + + /* We're going to need to do path replacement, potentially. */ + char *replacement_path = nullptr; + size_t replacement_path_size = 0; + ON_SCOPE_EXIT { + if (replacement_path != nullptr) { + if (std::is_constant_evaluated()) { + delete[] replacement_path; + } else { + ::ams::fs::impl::Deallocate(replacement_path, replacement_path_size); + } + } + }; + + /* Perform path replacement, if necessary. */ + if (IsParentDirectoryPathReplacementNeeded(cur_path)) { + if (std::is_constant_evaluated()) { + replacement_path_size = fs::EntryNameLengthMax + 1; + replacement_path = new char[replacement_path_size]; + } else { + replacement_path_size = fs::EntryNameLengthMax + 1; + replacement_path = static_cast<char *>(::ams::fs::impl::Allocate(replacement_path_size)); + } + + ReplaceParentDirectoryPath(replacement_path, cur_path); + + cur_path = replacement_path; + } + + /* Iterate, normalizing path components. */ + bool skip_next_sep = false; + size_t i = 0; + + while (cur_path[i] != NullTerminator) { + /* Process a directory separator, if we run into one. */ + if (cur_path[i] == DirectorySeparator) { + /* Swallow separators. */ + do { ++i; } while (cur_path[i] == DirectorySeparator); + + /* Check if we hit end of string. */ + if (cur_path[i] == NullTerminator) { + break; + } + + /* If we aren't skipping the separator, write it, checking that we remain in bounds. */ + if (!skip_next_sep) { + if (total_len + 1 == max_out_size) { + dst[total_len] = NullTerminator; + *out_len = total_len; + R_THROW(fs::ResultTooLongPath()); + } + + dst[total_len++] = DirectorySeparator; + } + + /* Don't skip the next separator. */ + skip_next_sep = false; + } + + /* Get the length of the current directory component. */ + size_t dir_len = 0; + while (cur_path[i + dir_len] != DirectorySeparator && cur_path[i + dir_len] != NullTerminator) { + /* Check for validity. */ + if (!allow_all_characters) { + R_UNLESS(!IsInvalidCharacter(cur_path[i + dir_len]), fs::ResultInvalidCharacter()); + } + + ++dir_len; + } + + /* Handle the current dir component. */ + if (IsCurrentDirectory(cur_path + i)) { + skip_next_sep = true; + } else if (IsParentDirectory(cur_path + i)) { + /* We should have just written a separator. */ + AMS_ASSERT(dst[total_len - 1] == DirectorySeparator); + + /* We should have started with a separator, for non-windows paths. */ + if (!is_windows_path) { + AMS_ASSERT(dst[0] == DirectorySeparator); + } + + /* Remove the previous component. */ + if (total_len == 1) { + R_UNLESS(is_windows_path, fs::ResultDirectoryUnobtainable()); + + --total_len; + } else { + total_len -= 2; + + do { + if (dst[total_len] == DirectorySeparator) { + break; + } + } while ((--total_len) != 0); + } + + /* We should be pointing to a directory separator, for non-windows paths. */ + if (!is_windows_path) { + AMS_ASSERT(dst[total_len] == DirectorySeparator); + } + + /* We should remain in bounds. */ + AMS_ASSERT(total_len < max_out_size); + } else { + /* Copy, possibly truncating. */ + if (total_len + dir_len + 1 > max_out_size) { + const size_t copy_len = max_out_size - (total_len + 1); + + for (size_t j = 0; j < copy_len; ++j) { + dst[total_len++] = cur_path[i + j]; + } + + dst[total_len] = NullTerminator; + *out_len = total_len; + R_THROW(fs::ResultTooLongPath()); + } + + for (size_t j = 0; j < dir_len; ++j) { + dst[total_len++] = cur_path[i + j]; + } + } + + /* Advance past the current directory component. */ + i += dir_len; + } + + if (skip_next_sep) { + --total_len; + } + + if (total_len == 0 && max_out_size != 0) { + total_len = 1; + dst[0] = DirectorySeparator; + } + + /* NOTE: Probable nintendo bug, as max_out_size must be at least total_len + 1 for the null terminator. */ + R_UNLESS(max_out_size >= total_len - 1, fs::ResultTooLongPath()); + + dst[total_len] = NullTerminator; + + /* Check that the result path is normalized. */ + bool is_normalized; + size_t dummy; + R_TRY(IsNormalized(std::addressof(is_normalized), std::addressof(dummy), dst, allow_all_characters)); + + /* Assert that the result path is normalized. */ + AMS_ASSERT(is_normalized); + + /* Set the output length. */ + *out_len = total_len; + R_SUCCEED(); + } + }; + + class PathFormatter { + private: + static constexpr ALWAYS_INLINE Result CheckSharedName(const char *name, size_t len) { + /* Use StringTraits names for remainder of scope. */ + using namespace StringTraits; + + if (len == 1) { + R_UNLESS(name[0] != Dot, fs::ResultInvalidPathFormat()); + } else if (len == 2) { + R_UNLESS(name[0] != Dot || name[1] != Dot, fs::ResultInvalidPathFormat()); + } + + for (size_t i = 0; i < len; ++i) { + R_UNLESS(!IsInvalidCharacter(name[i]), fs::ResultInvalidCharacter()); + } + + R_SUCCEED(); + } + + static constexpr ALWAYS_INLINE Result CheckHostName(const char *name, size_t len) { + /* Use StringTraits names for remainder of scope. */ + using namespace StringTraits; + + if (len == 2) { + R_UNLESS(name[0] != Dot || name[1] != Dot, fs::ResultInvalidPathFormat()); + } + + for (size_t i = 0; i < len; ++i) { + R_UNLESS(!IsInvalidCharacterForHostName(name[i]), fs::ResultInvalidCharacter()); + } + + R_SUCCEED(); + } + + static constexpr Result CheckInvalidBackslash(bool *out_contains_backslash, const char *path, bool allow_backslash) { + /* Use StringTraits names for remainder of scope. */ + using namespace StringTraits; + + /* Default to no backslashes, so we can just write if we see one. */ + *out_contains_backslash = false; + + while (*path != NullTerminator) { + if (*(path++) == AlternateDirectorySeparator) { + *out_contains_backslash = true; + + R_UNLESS(allow_backslash, fs::ResultInvalidCharacter()); + } + } + + R_SUCCEED(); + } + public: + static constexpr ALWAYS_INLINE Result CheckPathFormat(const char *path, const PathFlags &flags) { + bool normalized; + size_t len; + R_RETURN(IsNormalized(std::addressof(normalized), std::addressof(len), path, flags)); + } + + static constexpr ALWAYS_INLINE Result SkipMountName(const char **out, size_t *out_len, const char *path) { + R_RETURN(ParseMountName(out, out_len, nullptr, 0, path)); + } + + static constexpr Result ParseMountName(const char **out, size_t *out_len, char *out_mount_name, size_t out_mount_name_buffer_size, const char *path) { + /* Check pre-conditions. */ + AMS_ASSERT(path != nullptr); + AMS_ASSERT(out_len != nullptr); + AMS_ASSERT(out != nullptr); + AMS_ASSERT((out_mount_name == nullptr) == (out_mount_name_buffer_size == 0)); + + /* Use StringTraits names for remainder of scope. */ + using namespace StringTraits; + + /* Determine max mount length. */ + const auto max_mount_len = out_mount_name_buffer_size == 0 ? MountNameLengthMax + 1 : std::min(MountNameLengthMax + 1, out_mount_name_buffer_size); + + /* Parse the path until we see a drive separator. */ + size_t mount_len = 0; + for (/* ... */; mount_len < max_mount_len && path[mount_len]; ++mount_len) { + const char c = path[mount_len]; + + /* If we see a drive separator, advance, then we're done with the pre-drive separator part of the mount. */ + if (c == DriveSeparator) { + ++mount_len; + break; + } + + /* If we see a directory separator, we're not in a mount name. */ + if (c == DirectorySeparator || c == AlternateDirectorySeparator) { + *out = path; + *out_len = 0; + R_SUCCEED(); + } + } + + + /* Check to be sure we're actually looking at a mount name. */ + if (mount_len <= 2 || path[mount_len - 1] != DriveSeparator) { + *out = path; + *out_len = 0; + R_SUCCEED(); + } + + /* Check that all characters in the mount name are allowable. */ + for (size_t i = 0; i < mount_len; ++i) { + R_UNLESS(!IsInvalidCharacterForMountName(path[i]), fs::ResultInvalidCharacter()); + } + + /* Copy out the mount name. */ + if (out_mount_name_buffer_size > 0) { + R_UNLESS(mount_len < out_mount_name_buffer_size, fs::ResultTooLongPath()); + + for (size_t i = 0; i < mount_len; ++i) { + out_mount_name[i] = path[i]; + } + out_mount_name[mount_len] = NullTerminator; + } + + /* Set the output. */ + *out = path + mount_len; + *out_len = mount_len; + R_SUCCEED(); + } + + static constexpr ALWAYS_INLINE Result SkipRelativeDotPath(const char **out, size_t *out_len, const char *path) { + R_RETURN(ParseRelativeDotPath(out, out_len, nullptr, 0, path)); + } + + static constexpr Result ParseRelativeDotPath(const char **out, size_t *out_len, char *out_relative, size_t out_relative_buffer_size, const char *path) { + /* Check pre-conditions. */ + AMS_ASSERT(path != nullptr); + AMS_ASSERT(out_len != nullptr); + AMS_ASSERT(out != nullptr); + AMS_ASSERT((out_relative == nullptr) == (out_relative_buffer_size == 0)); + + /* Use StringTraits names for remainder of scope. */ + using namespace StringTraits; + + /* Initialize the output buffer, if we have one. */ + if (out_relative_buffer_size > 0) { + out_relative[0] = NullTerminator; + } + + /* Check if the path is relative. */ + if (path[0] == Dot && (path[1] == NullTerminator || path[1] == DirectorySeparator || path[1] == AlternateDirectorySeparator)) { + if (out_relative_buffer_size > 0) { + R_UNLESS(out_relative_buffer_size >= 2, fs::ResultTooLongPath()); + + out_relative[0] = Dot; + out_relative[1] = NullTerminator; + } + + *out = path + 1; + *out_len = 1; + R_SUCCEED(); + } + + /* Ensure the path isn't a parent directory. */ + R_UNLESS(!(path[0] == Dot && path[1] == Dot), fs::ResultDirectoryUnobtainable()); + + /* There was no relative dot path. */ + *out = path; + *out_len = 0; + R_SUCCEED(); + } + + static constexpr Result SkipWindowsPath(const char **out, size_t *out_len, bool *out_normalized, const char *path, bool has_mount_name) { + /* We're normalized if and only if the parsing doesn't throw ResultNotNormalized(). */ + *out_normalized = true; + + R_TRY_CATCH(ParseWindowsPath(out, out_len, nullptr, 0, path, has_mount_name)) { + R_CATCH(fs::ResultNotNormalized) { *out_normalized = false; } + } R_END_TRY_CATCH; + ON_RESULT_INCLUDED(fs::ResultNotNormalized) { *out_normalized = false; }; + + R_SUCCEED(); + } + + static constexpr Result ParseWindowsPath(const char **out, size_t *out_len, char *out_win, size_t out_win_buffer_size, const char *path, bool has_mount_name) { + /* Check pre-conditions. */ + AMS_ASSERT(path != nullptr); + AMS_ASSERT(out_len != nullptr); + AMS_ASSERT(out != nullptr); + AMS_ASSERT((out_win == nullptr) == (out_win_buffer_size == 0)); + + /* Use StringTraits names for remainder of scope. */ + using namespace StringTraits; + + /* Initialize the output buffer, if we have one. */ + if (out_win_buffer_size > 0) { + out_win[0] = NullTerminator; + } + + /* Handle path start. */ + const char *cur_path = path; + if (has_mount_name && path[0] == DirectorySeparator) { + if (path[1] == AlternateDirectorySeparator && path[2] == AlternateDirectorySeparator) { + R_UNLESS(out_win_buffer_size > 0, fs::ResultNotNormalized()); + + ++cur_path; + } else if (IsWindowsDrive(path + 1)) { + R_UNLESS(out_win_buffer_size > 0, fs::ResultNotNormalized()); + + ++cur_path; + } + } + + /* Handle windows drive. */ + if (IsWindowsDrive(cur_path)) { + /* Parse up to separator. */ + size_t win_path_len = WindowsDriveLength; + for (/* ... */; cur_path[win_path_len] != NullTerminator; ++win_path_len) { + R_UNLESS(!IsInvalidCharacter(cur_path[win_path_len]), fs::ResultInvalidCharacter()); + + if (cur_path[win_path_len] == DirectorySeparator || cur_path[win_path_len] == AlternateDirectorySeparator) { + break; + } + } + + /* Ensure that we're normalized, if we're required to be. */ + if (out_win_buffer_size == 0) { + for (size_t i = 0; i < win_path_len; ++i) { + R_UNLESS(cur_path[i] != AlternateDirectorySeparator, fs::ResultNotNormalized()); + } + } else { + /* Ensure we can copy into the normalized buffer. */ + R_UNLESS(win_path_len < out_win_buffer_size, fs::ResultTooLongPath()); + + for (size_t i = 0; i < win_path_len; ++i) { + out_win[i] = cur_path[i]; + } + out_win[win_path_len] = NullTerminator; + + fs::Replace(out_win, win_path_len, AlternateDirectorySeparator, DirectorySeparator); + } + + *out = cur_path + win_path_len; + *out_len = win_path_len; + R_SUCCEED(); + } + + /* Handle DOS device. */ + if (IsDosDevicePath(cur_path)) { + size_t dos_prefix_len = DosDevicePathPrefixLength; + + if (IsWindowsDrive(cur_path + dos_prefix_len)) { + dos_prefix_len += WindowsDriveLength; + } else { + --dos_prefix_len; + } + + if (out_win_buffer_size > 0) { + /* Ensure we can copy into the normalized buffer. */ + R_UNLESS(dos_prefix_len < out_win_buffer_size, fs::ResultTooLongPath()); + + for (size_t i = 0; i < dos_prefix_len; ++i) { + out_win[i] = cur_path[i]; + } + out_win[dos_prefix_len] = NullTerminator; + + fs::Replace(out_win, dos_prefix_len, DirectorySeparator, AlternateDirectorySeparator); + } + + *out = cur_path + dos_prefix_len; + *out_len = dos_prefix_len; + R_SUCCEED(); + } + + /* Handle UNC path. */ + if (IsUncPath(cur_path, false, true)) { + const char *final_path = cur_path; + + R_UNLESS(cur_path[UncPathPrefixLength] != DirectorySeparator, fs::ResultInvalidPathFormat()); + R_UNLESS(cur_path[UncPathPrefixLength] != AlternateDirectorySeparator, fs::ResultInvalidPathFormat()); + + size_t cur_component_offset = 0; + size_t pos = UncPathPrefixLength; + for (/* ... */; cur_path[pos] != NullTerminator; ++pos) { + if (cur_path[pos] == DirectorySeparator || cur_path[pos] == AlternateDirectorySeparator) { + if (cur_component_offset != 0) { + R_TRY(CheckSharedName(cur_path + cur_component_offset, pos - cur_component_offset)); + + final_path = cur_path + pos; + break; + } + + R_UNLESS(cur_path[pos + 1] != DirectorySeparator, fs::ResultInvalidPathFormat()); + R_UNLESS(cur_path[pos + 1] != AlternateDirectorySeparator, fs::ResultInvalidPathFormat()); + + R_TRY(CheckHostName(cur_path + 2, pos - 2)); + + cur_component_offset = pos + 1; + } + } + + R_UNLESS(cur_component_offset != pos, fs::ResultInvalidPathFormat()); + + if (cur_component_offset != 0 && final_path == cur_path) { + R_TRY(CheckSharedName(cur_path + cur_component_offset, pos - cur_component_offset)); + + final_path = cur_path + pos; + } + + size_t unc_prefix_len = final_path - cur_path; + + /* Ensure that we're normalized, if we're required to be. */ + if (out_win_buffer_size == 0) { + for (size_t i = 0; i < unc_prefix_len; ++i) { + R_UNLESS(cur_path[i] != DirectorySeparator, fs::ResultNotNormalized()); + } + } else { + /* Ensure we can copy into the normalized buffer. */ + R_UNLESS(unc_prefix_len < out_win_buffer_size, fs::ResultTooLongPath()); + + for (size_t i = 0; i < unc_prefix_len; ++i) { + out_win[i] = cur_path[i]; + } + out_win[unc_prefix_len] = NullTerminator; + + fs::Replace(out_win, unc_prefix_len, DirectorySeparator, AlternateDirectorySeparator); + } + + *out = cur_path + unc_prefix_len; + *out_len = unc_prefix_len; + R_SUCCEED(); + } + + /* There's no windows path to parse. */ + *out = path; + *out_len = 0; + R_SUCCEED(); + } + + static constexpr Result IsNormalized(bool *out, size_t *out_len, const char *path, const PathFlags &flags = {}) { + /* Ensure nothing is null. */ + R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(out_len != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(path != nullptr, fs::ResultNullptrArgument()); + + /* Verify that the path is valid utf-8. */ + R_TRY(fs::CheckUtf8(path)); + + /* Use StringTraits names for remainder of scope. */ + using namespace StringTraits; + + /* Handle the case where the path is empty. */ + if (path[0] == NullTerminator) { + R_UNLESS(flags.IsEmptyPathAllowed(), fs::ResultInvalidPathFormat()); + + *out = true; + *out_len = 0; + R_SUCCEED(); + } + + /* All normalized paths start with a directory separator...unless they're windows paths, relative paths, or have mount names. */ + if (path[0] != DirectorySeparator) { + R_UNLESS(flags.IsWindowsPathAllowed() || flags.IsRelativePathAllowed() || flags.IsMountNameAllowed(), fs::ResultInvalidPathFormat()); + } + + /* Check that the path is allowed to be a windows path, if it is. */ + if (fs::IsWindowsPath(path, false)) { + R_UNLESS(flags.IsWindowsPathAllowed(), fs::ResultInvalidPathFormat()); + } + + /* Skip past the mount name, if one is present. */ + size_t total_len = 0; + size_t mount_name_len = 0; + R_TRY(SkipMountName(std::addressof(path), std::addressof(mount_name_len), path)); + + /* If we had a mount name, check that that was allowed. */ + if (mount_name_len > 0) { + R_UNLESS(flags.IsMountNameAllowed(), fs::ResultInvalidPathFormat()); + + total_len += mount_name_len; + } + + /* Check that the path starts as a normalized path should. */ + if (path[0] != DirectorySeparator && !IsPathStartWithCurrentDirectory(path) && !IsWindowsPath(path, false)) { + R_UNLESS(flags.IsRelativePathAllowed(), fs::ResultInvalidPathFormat()); + R_UNLESS(!IsInvalidCharacter(path[0]), fs::ResultInvalidPathFormat()); + + *out = false; + R_SUCCEED(); + } + + /* Process relative path. */ + size_t relative_len = 0; + R_TRY(SkipRelativeDotPath(std::addressof(path), std::addressof(relative_len), path)); + + /* If we have a relative path, check that was allowed. */ + if (relative_len > 0) { + R_UNLESS(flags.IsRelativePathAllowed(), fs::ResultInvalidPathFormat()); + + total_len += relative_len; + + if (path[0] == NullTerminator) { + *out = true; + *out_len = total_len; + R_SUCCEED(); + } + } + + /* Process windows path. */ + size_t windows_len = 0; + bool normalized_win = false; + R_TRY(SkipWindowsPath(std::addressof(path), std::addressof(windows_len), std::addressof(normalized_win), path, mount_name_len > 0)); + + /* If the windows path wasn't normalized, we're not normalized. */ + if (!normalized_win) { + R_UNLESS(flags.IsWindowsPathAllowed(), fs::ResultInvalidPathFormat()); + + *out = false; + R_SUCCEED(); + } + + /* If we had a windows path, check that was allowed. */ + if (windows_len > 0) { + R_UNLESS(flags.IsWindowsPathAllowed(), fs::ResultInvalidPathFormat()); + + total_len += windows_len; + + /* We can't have both a relative path and a windows path. */ + R_UNLESS(relative_len == 0, fs::ResultInvalidPathFormat()); + + /* A path ending in a windows path isn't normalized. */ + if (path[0] == NullTerminator) { + *out = false; + R_SUCCEED(); + } + + /* Check that there are no windows directory separators in the path. */ + for (size_t i = 0; path[i] != NullTerminator; ++i) { + if (path[i] == AlternateDirectorySeparator) { + *out = false; + R_SUCCEED(); + } + } + } + + /* Check that parent directory replacement is not needed if backslashes are allowed. */ + if (flags.IsBackslashAllowed() && PathNormalizer::IsParentDirectoryPathReplacementNeeded(path)) { + *out = false; + R_SUCCEED(); + } + + /* Check that the backslash state is valid. */ + bool is_backslash_contained = false; + R_TRY(CheckInvalidBackslash(std::addressof(is_backslash_contained), path, flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed())); + + /* Check that backslashes are contained only if allowed. */ + if (is_backslash_contained && !flags.IsBackslashAllowed()) { + *out = false; + R_SUCCEED(); + } + + /* Check that the final result path is normalized. */ + size_t normal_len = 0; + R_TRY(PathNormalizer::IsNormalized(out, std::addressof(normal_len), path, flags.IsAllCharactersAllowed())); + + /* Add the normal length. */ + total_len += normal_len; + + /* Set the output length. */ + *out_len = total_len; + R_SUCCEED(); + } + + static constexpr Result Normalize(char *dst, size_t dst_size, const char *path, size_t path_len, const PathFlags &flags) { + /* Use StringTraits names for remainder of scope. */ + using namespace StringTraits; + + /* Prepare to iterate. */ + const char *src = path; + size_t cur_pos = 0; + bool is_windows_path = false; + + /* Check if the path is empty. */ + if (src[0] == NullTerminator) { + if (dst_size != 0) { + dst[0] = NullTerminator; + } + + R_UNLESS(flags.IsEmptyPathAllowed(), fs::ResultInvalidPathFormat()); + + R_SUCCEED(); + } + + /* Handle a mount name. */ + size_t mount_name_len = 0; + if (flags.IsMountNameAllowed()) { + R_TRY(ParseMountName(std::addressof(src), std::addressof(mount_name_len), dst + cur_pos, dst_size - cur_pos, src)); + + cur_pos += mount_name_len; + } + + /* Handle a drive-relative prefix. */ + bool is_drive_relative = false; + if (src[0] != DirectorySeparator && !IsPathStartWithCurrentDirectory(src) && !IsWindowsPath(src, false)) { + R_UNLESS(flags.IsRelativePathAllowed(), fs::ResultInvalidPathFormat()); + R_UNLESS(!IsInvalidCharacter(src[0]), fs::ResultInvalidPathFormat()); + + dst[cur_pos++] = Dot; + is_drive_relative = true; + } + + size_t relative_len = 0; + if (flags.IsRelativePathAllowed()) { + R_UNLESS(cur_pos < dst_size, fs::ResultTooLongPath()); + + R_TRY(ParseRelativeDotPath(std::addressof(src), std::addressof(relative_len), dst + cur_pos, dst_size - cur_pos, src)); + + cur_pos += relative_len; + + if (src[0] == NullTerminator) { + R_UNLESS(cur_pos < dst_size, fs::ResultTooLongPath()); + + dst[cur_pos] = NullTerminator; + R_SUCCEED(); + } + } + + /* Handle a windows path. */ + if (flags.IsWindowsPathAllowed()) { + const char * const orig = src; + + R_UNLESS(cur_pos < dst_size, fs::ResultTooLongPath()); + + size_t windows_len = 0; + R_TRY(ParseWindowsPath(std::addressof(src), std::addressof(windows_len), dst + cur_pos, dst_size - cur_pos, src, mount_name_len != 0)); + + cur_pos += windows_len; + + if (src[0] == NullTerminator) { + /* NOTE: Bug in original code here repeated, should be checking cur_pos + 2. */ + R_UNLESS(cur_pos + 1 < dst_size, fs::ResultTooLongPath()); + + dst[cur_pos + 0] = DirectorySeparator; + dst[cur_pos + 1] = NullTerminator; + R_SUCCEED(); + } + + if ((src - orig) > 0) { + is_windows_path = true; + } + } + + /* Check for invalid backslash. */ + bool backslash_contained = false; + R_TRY(CheckInvalidBackslash(std::addressof(backslash_contained), src, flags.IsWindowsPathAllowed() || flags.IsBackslashAllowed())); + + /* Handle backslash replacement as necessary. */ + if (backslash_contained && flags.IsWindowsPathAllowed()) { + /* Create a temporary buffer holding a slash-replaced version of the path. */ + /* NOTE: Nintendo unnecessarily allocates and replaces here a fully copy of the path, despite having skipped some of it already. */ + const size_t replaced_src_len = path_len - (src - path); + + char *replaced_src = nullptr; + ON_SCOPE_EXIT { + if (replaced_src != nullptr) { + if (std::is_constant_evaluated()) { + delete[] replaced_src; + } else { + ::ams::fs::impl::Deallocate(replaced_src, replaced_src_len); + } + } + }; + + if (std::is_constant_evaluated()) { + replaced_src = new char[replaced_src_len]; + } else { + replaced_src = static_cast<char *>(::ams::fs::impl::Allocate(replaced_src_len)); + } + + util::Strlcpy<char>(replaced_src, src, replaced_src_len); + + fs::Replace(replaced_src, replaced_src_len, AlternateDirectorySeparator, DirectorySeparator); + + size_t dummy; + R_TRY(PathNormalizer::Normalize(dst + cur_pos, std::addressof(dummy), replaced_src, dst_size - cur_pos, is_windows_path, is_drive_relative, flags.IsAllCharactersAllowed())); + } else { + /* We can just do normalization. */ + size_t dummy; + R_TRY(PathNormalizer::Normalize(dst + cur_pos, std::addressof(dummy), src, dst_size - cur_pos, is_windows_path, is_drive_relative, flags.IsAllCharactersAllowed())); + } + + R_SUCCEED(); + } + }; + + inline Result ConvertToFspPath(fssrv::sf::FspPath *out, const char *src) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(src != nullptr); + + /* Copy the path. */ + const size_t len = util::Strlcpy<char>(out->str, src, sizeof(out->str)); + R_UNLESS(len < sizeof(out->str), fs::ResultTooLongPath()); + + /* Skip mount name. */ + const char *path_split_mount_name; + size_t skip_len; + R_TRY(PathFormatter::SkipMountName(std::addressof(path_split_mount_name), std::addressof(skip_len), src)); + + /* Perform further validation. */ + if (fs::IsWindowsPath(path_split_mount_name, true)) { + if ((skip_len == 0 || !util::Strncmp<char>(src, AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME ":", AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME_LEN + 1)) && fs::IsUncPath(out->str + skip_len, true, false)) { + out->str[skip_len + 0] = '\\'; + out->str[skip_len + 1] = '\\'; + } + } else { + fs::Replace(out->str, sizeof(out->str) - 1, '\\', '/'); + } + + R_SUCCEED(); + } + + Result FormatToFspPath(fssrv::sf::FspPath *out, const char *fmt, ...) __attribute__((format(printf, 2, 3))); + + inline Result FormatToFspPath(fssrv::sf::FspPath *out, const char *fmt, ...) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(fmt != nullptr); + + /* Format the path. */ + std::va_list vl; + va_start(vl, fmt); + const size_t len = util::VSNPrintf(out->str, sizeof(out->str), fmt, vl); + va_end(vl); + + R_UNLESS(len < sizeof(out->str), fs::ResultTooLongPath()); + + /* Skip mount name. */ + const char *path_split_mount_name; + size_t skip_len; + R_TRY(PathFormatter::SkipMountName(std::addressof(path_split_mount_name), std::addressof(skip_len), out->str)); + + /* Perform further validation. */ + if (fs::IsWindowsPath(path_split_mount_name, true)) { + if ((skip_len == 0 || !util::Strncmp<char>(out->str, AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME ":", AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME_LEN + 1)) && fs::IsUncPath(out->str + skip_len, true, false)) { + out->str[skip_len + 0] = '\\'; + out->str[skip_len + 1] = '\\'; + } + } else { + fs::Replace(out->str, sizeof(out->str) - 1, '\\', '/'); + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_priority.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_priority.hpp new file mode 100644 index 00000000..d168ed64 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_priority.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + enum Priority { + Priority_Realtime = 0, + Priority_Normal = 1, + Priority_Low = 2, + }; + + enum PriorityRaw { + PriorityRaw_Realtime = 0, + PriorityRaw_Normal = 1, + PriorityRaw_Low = 2, + PriorityRaw_Background = 3, + }; + + Priority GetPriorityOnCurrentThread(); + Priority GetPriority(os::ThreadType *thread); + PriorityRaw GetPriorityRawOnCurrentThread(); + PriorityRaw GetPriorityRaw(os::ThreadType *thread); + + void SetPriorityOnCurrentThread(Priority prio); + void SetPriority(os::ThreadType *thread, Priority prio); + void SetPriorityRawOnCurrentThread(PriorityRaw prio); + void SetPriorityRaw(os::ThreadType *thread, PriorityRaw prio); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_program_id.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_program_id.hpp new file mode 100644 index 00000000..e2f00000 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_program_id.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fs_content_attributes.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: 17.5.0.0 */ + Result GetProgramId(ncm::ProgramId *out, const char *path, fs::ContentAttributes attr); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_program_index_map_info.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_program_index_map_info.hpp new file mode 100644 index 00000000..c46d5f64 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_program_index_map_info.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + struct ProgramIndexMapInfo { + ncm::ProgramId program_id; + ncm::ProgramId base_program_id; + u8 program_index; + u8 pad[0xF]; + }; + static_assert(util::is_pod<ProgramIndexMapInfo>::value); + static_assert(sizeof(ProgramIndexMapInfo) == 0x20); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_query_range.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_query_range.hpp new file mode 100644 index 00000000..b9fd3979 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_query_range.hpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_file.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + struct QueryRangeInfo { + s32 aes_ctr_key_type; + s32 speed_emulation_type; + u8 reserved[0x38]; + + void Clear() { + this->aes_ctr_key_type = 0; + this->speed_emulation_type = 0; + std::memset(this->reserved, 0, sizeof(this->reserved)); + } + + void Merge(const QueryRangeInfo &rhs) { + this->aes_ctr_key_type |= rhs.aes_ctr_key_type; + this->speed_emulation_type |= rhs.speed_emulation_type; + } + }; + + static_assert(util::is_pod<QueryRangeInfo>::value); + static_assert(sizeof(QueryRangeInfo) == 0x40); + + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(sizeof(QueryRangeInfo) == sizeof(::FsRangeInfo)); + #endif + + using FileQueryRangeInfo = QueryRangeInfo; + using StorageQueryRangeInfo = QueryRangeInfo; + + Result QueryRange(QueryRangeInfo *out, FileHandle handle, s64 offset, s64 size); + + enum class AesCtrKeyTypeFlag : s32 { + InternalKeyForSoftwareAes = (1 << 0), + InternalKeyForHardwareAes = (1 << 1), + ExternalKeyForHardwareAes = (1 << 2), + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_read_only_filesystem.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_read_only_filesystem.hpp new file mode 100644 index 00000000..9b874333 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_read_only_filesystem.hpp @@ -0,0 +1,176 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> +#include <stratosphere/fs/fsa/fs_ifile.hpp> +#include <stratosphere/fs/fsa/fs_idirectory.hpp> +#include <stratosphere/fs/fsa/fs_ifilesystem.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + + namespace { + + class ReadOnlyFile : public fsa::IFile, public impl::Newable { + NON_COPYABLE(ReadOnlyFile); + NON_MOVEABLE(ReadOnlyFile); + private: + std::unique_ptr<fsa::IFile> m_base_file; + public: + explicit ReadOnlyFile(std::unique_ptr<fsa::IFile> &&f) : m_base_file(std::move(f)) { AMS_ASSERT(m_base_file != nullptr); } + virtual ~ReadOnlyFile() { /* ... */ } + private: + virtual Result DoRead(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override final { + R_RETURN(m_base_file->Read(out, offset, buffer, size, option)); + } + + virtual Result DoGetSize(s64 *out) override final { + R_RETURN(m_base_file->GetSize(out)); + } + + virtual Result DoFlush() override final { + R_SUCCEED(); + } + + virtual Result DoWrite(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override final { + bool need_append; + R_TRY(this->DryWrite(std::addressof(need_append), offset, size, option, fs::OpenMode_Read)); + + AMS_ASSERT(!need_append); + + AMS_UNUSED(buffer); + R_THROW(fs::ResultUnsupportedWriteForReadOnlyFile()); + } + + virtual Result DoSetSize(s64 size) override final { + R_TRY(this->DrySetSize(size, fs::OpenMode_Read)); + R_THROW(fs::ResultUnsupportedWriteForReadOnlyFile()); + } + + virtual Result DoOperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override final { + switch (op_id) { + case OperationId::Invalidate: + case OperationId::QueryRange: + R_RETURN(m_base_file->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + default: + R_THROW(fs::ResultUnsupportedOperateRangeForReadOnlyFile()); + } + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override { + return m_base_file->GetDomainObjectId(); + } + }; + + } + + template<typename T> + class ReadOnlyFileSystemTemplate : public fsa::IFileSystem, public impl::Newable { + NON_COPYABLE(ReadOnlyFileSystemTemplate); + NON_MOVEABLE(ReadOnlyFileSystemTemplate); + private: + T m_base_fs; + public: + explicit ReadOnlyFileSystemTemplate(T &&fs) : m_base_fs(std::move(fs)) { /* ... */ } + virtual ~ReadOnlyFileSystemTemplate() { /* ... */ } + private: + virtual Result DoOpenFile(std::unique_ptr<fsa::IFile> *out_file, const fs::Path &path, OpenMode mode) override final { + /* Only allow opening files with mode = read. */ + R_UNLESS((mode & fs::OpenMode_All) == fs::OpenMode_Read, fs::ResultInvalidOpenMode()); + + std::unique_ptr<fsa::IFile> base_file; + R_TRY(m_base_fs->OpenFile(std::addressof(base_file), path, mode)); + + auto read_only_file = std::make_unique<ReadOnlyFile>(std::move(base_file)); + R_UNLESS(read_only_file != nullptr, fs::ResultAllocationMemoryFailedInReadOnlyFileSystemA()); + + *out_file = std::move(read_only_file); + R_SUCCEED(); + } + + virtual Result DoOpenDirectory(std::unique_ptr<fsa::IDirectory> *out_dir, const fs::Path &path, OpenDirectoryMode mode) override final { + R_RETURN(m_base_fs->OpenDirectory(out_dir, path, mode)); + } + + virtual Result DoGetEntryType(DirectoryEntryType *out, const fs::Path &path) override final { + R_RETURN(m_base_fs->GetEntryType(out, path)); + } + + virtual Result DoCommit() override final { + R_SUCCEED(); + } + + virtual Result DoCreateFile(const fs::Path &path, s64 size, int flags) override final { + AMS_UNUSED(path, size, flags); + R_THROW(fs::ResultUnsupportedWriteForReadOnlyFileSystem()); + } + + virtual Result DoDeleteFile(const fs::Path &path) override final { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForReadOnlyFileSystem()); + } + + virtual Result DoCreateDirectory(const fs::Path &path) override final { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForReadOnlyFileSystem()); + } + + virtual Result DoDeleteDirectory(const fs::Path &path) override final { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForReadOnlyFileSystem()); + } + + virtual Result DoDeleteDirectoryRecursively(const fs::Path &path) override final { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForReadOnlyFileSystem()); + } + + virtual Result DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) override final { + AMS_UNUSED(old_path, new_path); + R_THROW(fs::ResultUnsupportedWriteForReadOnlyFileSystem()); + } + + virtual Result DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) override final { + AMS_UNUSED(old_path, new_path); + R_THROW(fs::ResultUnsupportedWriteForReadOnlyFileSystem()); + } + + virtual Result DoCleanDirectoryRecursively(const fs::Path &path) override final { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForReadOnlyFileSystem()); + } + + virtual Result DoGetFreeSpaceSize(s64 *out, const fs::Path &path) override final { + R_RETURN(m_base_fs->GetFreeSpaceSize(out, path)); + } + + virtual Result DoGetTotalSpaceSize(s64 *out, const fs::Path &path) override final { + AMS_UNUSED(out, path); + R_THROW(fs::ResultUnsupportedGetTotalSpaceSizeForReadOnlyFileSystem()); + } + + virtual Result DoCommitProvisionally(s64 counter) override final { + AMS_UNUSED(counter); + R_THROW(fs::ResultUnsupportedCommitProvisionallyForReadOnlyFileSystem()); + } + }; + + using ReadOnlyFileSystem = ReadOnlyFileSystemTemplate<std::unique_ptr<::ams::fs::fsa::IFileSystem>>; + using ReadOnlyFileSystemShared = ReadOnlyFileSystemTemplate<std::shared_ptr<::ams::fs::fsa::IFileSystem>>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_remote_filesystem.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_remote_filesystem.hpp new file mode 100644 index 00000000..9e665992 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_remote_filesystem.hpp @@ -0,0 +1,238 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> +#include <stratosphere/fs/fsa/fs_ifile.hpp> +#include <stratosphere/fs/fsa/fs_idirectory.hpp> +#include <stratosphere/fs/fsa/fs_ifilesystem.hpp> +#include <stratosphere/fs/fs_query_range.hpp> + +namespace ams::fs { + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemoteFile : public fsa::IFile, public impl::Newable { + NON_COPYABLE(RemoteFile); + NON_MOVEABLE(RemoteFile); + private: + ::FsFile m_base_file; + public: + RemoteFile(const ::FsFile &f) : m_base_file(f) { /* ... */ } + + virtual ~RemoteFile() { fsFileClose(std::addressof(m_base_file)); } + public: + virtual Result DoRead(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override final { + R_RETURN(fsFileRead(std::addressof(m_base_file), offset, buffer, size, option._value, out)); + } + + virtual Result DoGetSize(s64 *out) override final { + R_RETURN(fsFileGetSize(std::addressof(m_base_file), out)); + } + + virtual Result DoFlush() override final { + R_RETURN(fsFileFlush(std::addressof(m_base_file))); + } + + virtual Result DoWrite(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override final { + R_RETURN(fsFileWrite(std::addressof(m_base_file), offset, buffer, size, option._value)); + } + + virtual Result DoSetSize(s64 size) override final { + R_RETURN(fsFileSetSize(std::addressof(m_base_file), size)); + } + + virtual Result DoOperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override final { + AMS_UNUSED(src, src_size); + + R_UNLESS(op_id == OperationId::QueryRange, fs::ResultUnsupportedOperateRangeForFileServiceObjectAdapter()); + R_UNLESS(dst_size == sizeof(FileQueryRangeInfo), fs::ResultInvalidSize()); + + R_RETURN(fsFileOperateRange(std::addressof(m_base_file), static_cast<::FsOperationId>(op_id), offset, size, reinterpret_cast<::FsRangeInfo *>(dst))); + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override final { + return sf::cmif::DomainObjectId{serviceGetObjectId(const_cast<::Service *>(std::addressof(m_base_file.s)))}; + } + }; + + class RemoteDirectory : public fsa::IDirectory, public impl::Newable { + NON_COPYABLE(RemoteDirectory); + NON_MOVEABLE(RemoteDirectory); + private: + ::FsDir m_base_dir; + public: + RemoteDirectory(const ::FsDir &d) : m_base_dir(d) { /* ... */ } + + virtual ~RemoteDirectory() { fsDirClose(std::addressof(m_base_dir)); } + public: + virtual Result DoRead(s64 *out_count, DirectoryEntry *out_entries, s64 max_entries) override final { + static_assert(sizeof(*out_entries) == sizeof(::FsDirectoryEntry)); + R_RETURN(fsDirRead(std::addressof(m_base_dir), out_count, max_entries, reinterpret_cast<::FsDirectoryEntry *>(out_entries))); + } + + virtual Result DoGetEntryCount(s64 *out) override final { + R_RETURN(fsDirGetEntryCount(std::addressof(m_base_dir), out)); + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override final { + return sf::cmif::DomainObjectId{serviceGetObjectId(const_cast<::Service *>(std::addressof(m_base_dir.s)))}; + } + }; + + class RemoteFileSystem : public fsa::IFileSystem, public impl::Newable { + NON_COPYABLE(RemoteFileSystem); + NON_MOVEABLE(RemoteFileSystem); + private: + ::FsFileSystem m_base_fs; + public: + RemoteFileSystem(const ::FsFileSystem &fs) : m_base_fs(fs) { /* ... */ } + + virtual ~RemoteFileSystem() { fsFsClose(std::addressof(m_base_fs)); } + private: + Result GetPathForServiceObject(fssrv::sf::Path *out_path, const fs::Path &path) { + /* Copy, ensuring length is in bounds. */ + const size_t len = util::Strlcpy<char>(out_path->str, path.GetString(), sizeof(out_path->str)); + R_UNLESS(len < sizeof(out_path->str), fs::ResultTooLongPath()); + + /* Replace directory separators. */ + /* TODO: Is this still necessary? We originally had it to not break things on low firmware. */ + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + fs::Replace(out_path->str, sizeof(out_path->str) - 1, '\\', '/'); + #endif + + R_SUCCEED(); + } + public: + virtual Result DoCreateFile(const fs::Path &path, s64 size, int flags) override final { + fssrv::sf::Path sf_path; + R_TRY(GetPathForServiceObject(std::addressof(sf_path), path)); + R_RETURN(fsFsCreateFile(std::addressof(m_base_fs), sf_path.str, size, flags)); + } + + virtual Result DoDeleteFile(const fs::Path &path) override final { + fssrv::sf::Path sf_path; + R_TRY(GetPathForServiceObject(std::addressof(sf_path), path)); + R_RETURN(fsFsDeleteFile(std::addressof(m_base_fs), sf_path.str)); + } + + virtual Result DoCreateDirectory(const fs::Path &path) override final { + fssrv::sf::Path sf_path; + R_TRY(GetPathForServiceObject(std::addressof(sf_path), path)); + R_RETURN(fsFsCreateDirectory(std::addressof(m_base_fs), sf_path.str)); + } + + virtual Result DoDeleteDirectory(const fs::Path &path) override final { + fssrv::sf::Path sf_path; + R_TRY(GetPathForServiceObject(std::addressof(sf_path), path)); + R_RETURN(fsFsDeleteDirectory(std::addressof(m_base_fs), sf_path.str)); + } + + virtual Result DoDeleteDirectoryRecursively(const fs::Path &path) override final { + fssrv::sf::Path sf_path; + R_TRY(GetPathForServiceObject(std::addressof(sf_path), path)); + R_RETURN(fsFsDeleteDirectoryRecursively(std::addressof(m_base_fs), sf_path.str)); + } + + virtual Result DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) override final { + fssrv::sf::Path old_sf_path; + fssrv::sf::Path new_sf_path; + R_TRY(GetPathForServiceObject(std::addressof(old_sf_path), old_path)); + R_TRY(GetPathForServiceObject(std::addressof(new_sf_path), new_path)); + R_RETURN(fsFsRenameFile(std::addressof(m_base_fs), old_sf_path.str, new_sf_path.str)); + } + + virtual Result DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) override final { + fssrv::sf::Path old_sf_path; + fssrv::sf::Path new_sf_path; + R_TRY(GetPathForServiceObject(std::addressof(old_sf_path), old_path)); + R_TRY(GetPathForServiceObject(std::addressof(new_sf_path), new_path)); + R_RETURN(fsFsRenameDirectory(std::addressof(m_base_fs), old_sf_path.str, new_sf_path.str)); + } + + virtual Result DoGetEntryType(DirectoryEntryType *out, const fs::Path &path) override final { + fssrv::sf::Path sf_path; + R_TRY(GetPathForServiceObject(std::addressof(sf_path), path)); + + static_assert(sizeof(::FsDirEntryType) == sizeof(DirectoryEntryType)); + R_RETURN(fsFsGetEntryType(std::addressof(m_base_fs), sf_path.str, reinterpret_cast<::FsDirEntryType *>(out))); + } + + virtual Result DoOpenFile(std::unique_ptr<fsa::IFile> *out_file, const fs::Path &path, OpenMode mode) override final { + fssrv::sf::Path sf_path; + R_TRY(GetPathForServiceObject(std::addressof(sf_path), path)); + + FsFile f; + R_TRY(fsFsOpenFile(std::addressof(m_base_fs), sf_path.str, mode, std::addressof(f))); + + auto file = std::make_unique<RemoteFile>(f); + R_UNLESS(file != nullptr, fs::ResultAllocationMemoryFailedNew()); + + *out_file = std::move(file); + R_SUCCEED(); + } + + virtual Result DoOpenDirectory(std::unique_ptr<fsa::IDirectory> *out_dir, const fs::Path &path, OpenDirectoryMode mode) override final { + fssrv::sf::Path sf_path; + R_TRY(GetPathForServiceObject(std::addressof(sf_path), path)); + + FsDir d; + R_TRY(fsFsOpenDirectory(std::addressof(m_base_fs), sf_path.str, mode, std::addressof(d))); + + auto dir = std::make_unique<RemoteDirectory>(d); + R_UNLESS(dir != nullptr, fs::ResultAllocationMemoryFailedNew()); + + *out_dir = std::move(dir); + R_SUCCEED(); + } + + virtual Result DoCommit() override final { + R_RETURN(fsFsCommit(std::addressof(m_base_fs))); + } + + virtual Result DoGetFreeSpaceSize(s64 *out, const fs::Path &path) override final { + fssrv::sf::Path sf_path; + R_TRY(GetPathForServiceObject(std::addressof(sf_path), path)); + R_RETURN(fsFsGetFreeSpace(std::addressof(m_base_fs), sf_path.str, out)); + } + + virtual Result DoGetTotalSpaceSize(s64 *out, const fs::Path &path) override final { + fssrv::sf::Path sf_path; + R_TRY(GetPathForServiceObject(std::addressof(sf_path), path)); + R_RETURN(fsFsGetTotalSpace(std::addressof(m_base_fs), sf_path.str, out)); + } + + virtual Result DoCleanDirectoryRecursively(const fs::Path &path) override final { + fssrv::sf::Path sf_path; + R_TRY(GetPathForServiceObject(std::addressof(sf_path), path)); + R_RETURN(fsFsCleanDirectoryRecursively(std::addressof(m_base_fs), sf_path.str)); + } + + virtual Result DoGetFileTimeStampRaw(FileTimeStampRaw *out, const fs::Path &path) override final { + fssrv::sf::Path sf_path; + R_TRY(GetPathForServiceObject(std::addressof(sf_path), path)); + static_assert(sizeof(FileTimeStampRaw) == sizeof(::FsTimeStampRaw)); + R_RETURN(fsFsGetFileTimeStampRaw(std::addressof(m_base_fs), sf_path.str, reinterpret_cast<::FsTimeStampRaw *>(out))); + } + + virtual Result DoQueryEntry(char *dst, size_t dst_size, const char *src, size_t src_size, fsa::QueryId query, const fs::Path &path) override final { + fssrv::sf::Path sf_path; + R_TRY(GetPathForServiceObject(std::addressof(sf_path), path)); + R_RETURN(fsFsQueryEntry(std::addressof(m_base_fs), dst, dst_size, src, src_size, sf_path.str, static_cast<FsFileSystemQueryId>(query))); + } + }; + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_remote_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_remote_storage.hpp new file mode 100644 index 00000000..17028116 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_remote_storage.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_istorage.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> + +namespace ams::fs { + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemoteStorage : public IStorage, public impl::Newable { + NON_COPYABLE(RemoteStorage); + NON_MOVEABLE(RemoteStorage); + private: + ::FsStorage m_base_storage; + public: + RemoteStorage(::FsStorage &s) : m_base_storage(s) { /* ... */} + + virtual ~RemoteStorage() { fsStorageClose(std::addressof(m_base_storage)); } + public: + virtual Result Read(s64 offset, void *buffer, size_t size) override { + R_RETURN(fsStorageRead(std::addressof(m_base_storage), offset, buffer, size)); + }; + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + R_RETURN(fsStorageWrite(std::addressof(m_base_storage), offset, buffer, size)); + }; + + virtual Result Flush() override { + R_RETURN(fsStorageFlush(std::addressof(m_base_storage))); + }; + + virtual Result GetSize(s64 *out_size) override { + R_RETURN(fsStorageGetSize(std::addressof(m_base_storage), out_size)); + }; + + virtual Result SetSize(s64 size) override { + R_RETURN(fsStorageSetSize(std::addressof(m_base_storage), size)); + }; + + virtual Result OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + /* TODO: How to deal with this? */ + AMS_UNUSED(dst, dst_size, op_id, offset, size, src, src_size); + R_THROW(fs::ResultUnsupportedOperation()); + }; + }; + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_result_config.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_result_config.hpp new file mode 100644 index 00000000..38f3c4e3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_result_config.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + void SetEnabledAutoAbort(bool enabled); + void SetResultHandledByApplication(bool application); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_rights_id.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_rights_id.hpp new file mode 100644 index 00000000..43fc53ce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_rights_id.hpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fs_content_attributes.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + union RightsId { + u8 data[0x10]; + u64 data64[2]; + }; + static_assert(sizeof(RightsId) == 0x10); + static_assert(util::is_pod<RightsId>::value); + + inline bool operator==(const RightsId &lhs, const RightsId &rhs) { + return std::memcmp(std::addressof(lhs), std::addressof(rhs), sizeof(RightsId)) == 0; + } + + inline bool operator!=(const RightsId &lhs, const RightsId &rhs) { + return !(lhs == rhs); + } + + inline bool operator<(const RightsId &lhs, const RightsId &rhs) { + return std::memcmp(std::addressof(lhs), std::addressof(rhs), sizeof(RightsId)) < 0; + } + + constexpr inline RightsId InvalidRightsId = {}; + + /* Rights ID API */ + Result GetRightsId(RightsId *out, const char *path, fs::ContentAttributes attr); + Result GetRightsId(RightsId *out, u8 *out_key_generation, const char *path, fs::ContentAttributes attr); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_romfs_filesystem.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_romfs_filesystem.hpp new file mode 100644 index 00000000..b836f17f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_romfs_filesystem.hpp @@ -0,0 +1,75 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> +#include <stratosphere/fs/fsa/fs_ifile.hpp> +#include <stratosphere/fs/fsa/fs_idirectory.hpp> +#include <stratosphere/fs/fsa/fs_ifilesystem.hpp> +#include <stratosphere/fs/common/fs_dbm_hierarchical_rom_file_table.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + class RomFsFileSystem : public fsa::IFileSystem, public impl::Newable { + NON_COPYABLE(RomFsFileSystem); + public: + using RomFileTable = HierarchicalRomFileTable; + private: + RomFileTable m_rom_file_table; + IStorage *m_base_storage; + std::unique_ptr<IStorage> m_unique_storage; + std::unique_ptr<IStorage> m_dir_bucket_storage; + std::unique_ptr<IStorage> m_dir_entry_storage; + std::unique_ptr<IStorage> m_file_bucket_storage; + std::unique_ptr<IStorage> m_file_entry_storage; + s64 m_entry_size; + private: + Result GetFileInfo(RomFileTable::FileInfo *out, const char *path); + public: + static Result GetRequiredWorkingMemorySize(size_t *out, IStorage *storage); + public: + RomFsFileSystem(); + virtual ~RomFsFileSystem() override; + + Result Initialize(IStorage *base, void *work, size_t work_size, bool use_cache); + Result Initialize(std::unique_ptr<IStorage>&& base, void *work, size_t work_size, bool use_cache); + + IStorage *GetBaseStorage(); + RomFileTable *GetRomFileTable(); + Result GetFileBaseOffset(s64 *out, const char *path); + public: + virtual Result DoCreateFile(const fs::Path &path, s64 size, int flags) override; + virtual Result DoDeleteFile(const fs::Path &path) override; + virtual Result DoCreateDirectory(const fs::Path &path) override; + virtual Result DoDeleteDirectory(const fs::Path &path) override; + virtual Result DoDeleteDirectoryRecursively(const fs::Path &path) override; + virtual Result DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) override; + virtual Result DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) override; + virtual Result DoGetEntryType(fs::DirectoryEntryType *out, const fs::Path &path) override; + virtual Result DoOpenFile(std::unique_ptr<fs::fsa::IFile> *out_file, const fs::Path &path, fs::OpenMode mode) override; + virtual Result DoOpenDirectory(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const fs::Path &path, fs::OpenDirectoryMode mode) override; + virtual Result DoCommit() override; + virtual Result DoGetFreeSpaceSize(s64 *out, const fs::Path &path) override; + virtual Result DoGetTotalSpaceSize(s64 *out, const fs::Path &path) override; + virtual Result DoCleanDirectoryRecursively(const fs::Path &path) override; + + /* These aren't accessible as commands. */ + virtual Result DoCommitProvisionally(s64 counter) override; + virtual Result DoRollback() override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_management.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_management.hpp new file mode 100644 index 00000000..9f2b2bba --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_management.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_save_data_types.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + Result DeleteSaveData(SaveDataId id); + Result DeleteSaveData(SaveDataSpaceId space_id, SaveDataId id); + + Result GetSaveDataFlags(u32 *out, SaveDataId id); + Result GetSaveDataFlags(u32 *out, SaveDataSpaceId space_id, SaveDataId id); + Result SetSaveDataFlags(SaveDataId id, SaveDataSpaceId space_id, u32 flags); + + Result GetSaveDataAvailableSize(s64 *out, SaveDataId id); + Result GetSaveDataJournalSize(s64 *out, SaveDataId id); + + Result ExtendSaveData(SaveDataSpaceId space_id, SaveDataId id, s64 available_size, s64 journal_size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_transaction.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_transaction.hpp new file mode 100644 index 00000000..7e3f4800 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_transaction.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_save_data_types.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + Result CommitSaveData(const char *path); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_types.hpp new file mode 100644 index 00000000..d12ba1e3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_types.hpp @@ -0,0 +1,184 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + using SaveDataId = u64; + using SystemSaveDataId = u64; + using SystemBcatSaveDataId = SystemSaveDataId; + + enum class SaveDataSpaceId : u8 { + System = 0, + User = 1, + SdSystem = 2, + Temporary = 3, + SdUser = 4, + + ProperSystem = 100, + SafeMode = 101, + }; + + enum class SaveDataType : u8 { + System = 0, + Account = 1, + Bcat = 2, + Device = 3, + Temporary = 4, + Cache = 5, + SystemBcat = 6, + }; + + enum class SaveDataRank : u8 { + Primary = 0, + Secondary = 1, + }; + + struct UserId { + u64 data[2]; + }; + static_assert(util::is_pod<UserId>::value); + + constexpr inline bool operator<(const UserId &lhs, const UserId &rhs) { + if (lhs.data[0] < rhs.data[0]) { + return true; + } else if (lhs.data[0] == rhs.data[0] && lhs.data[1] < rhs.data[1]) { + return true; + } else { + return false; + } + } + + constexpr inline bool operator==(const UserId &lhs, const UserId &rhs) { + return lhs.data[0] == rhs.data[0] && lhs.data[1] == rhs.data[1]; + } + + constexpr inline bool operator!=(const UserId &lhs, const UserId &rhs) { + return !(lhs == rhs); + } + + constexpr inline SystemSaveDataId InvalidSystemSaveDataId = 0; + constexpr inline UserId InvalidUserId = {}; + + enum SaveDataFlags : u32 { + SaveDataFlags_None = (0 << 0), + SaveDataFlags_KeepAfterResettingSystemSaveData = (1 << 0), + SaveDataFlags_KeepAfterRefurbishment = (1 << 1), + SaveDataFlags_KeepAfterResettingSystemSaveDataWithoutUserSaveData = (1 << 2), + SaveDataFlags_NeedsSecureDelete = (1 << 3), + }; + + enum class SaveDataMetaType : u8 { + None = 0, + Thumbnail = 1, + ExtensionContext = 2, + }; + + struct SaveDataMetaInfo { + u32 size; + SaveDataMetaType type; + u8 reserved[0xB]; + }; + static_assert(util::is_pod<SaveDataMetaInfo>::value); + static_assert(sizeof(SaveDataMetaInfo) == 0x10); + + struct SaveDataCreationInfo { + s64 size; + s64 journal_size; + s64 block_size; + u64 owner_id; + u32 flags; + SaveDataSpaceId space_id; + bool pseudo; + u8 reserved[0x1A]; + }; + static_assert(util::is_pod<SaveDataCreationInfo>::value); + static_assert(sizeof(SaveDataCreationInfo) == 0x40); + + struct SaveDataAttribute { + ncm::ProgramId program_id; + UserId user_id; + SystemSaveDataId system_save_data_id; + SaveDataType type; + SaveDataRank rank; + u16 index; + u8 reserved[0x1C]; + + static constexpr SaveDataAttribute Make(ncm::ProgramId program_id, SaveDataType type, UserId user_id, SystemSaveDataId system_save_data_id, u16 index, SaveDataRank rank) { + return { + .program_id = program_id, + .user_id = user_id, + .system_save_data_id = system_save_data_id, + .type = type, + .rank = rank, + .index = index, + }; + } + + static constexpr SaveDataAttribute Make(ncm::ProgramId program_id, SaveDataType type, UserId user_id, SystemSaveDataId system_save_data_id, u16 index) { + return Make(program_id, type, user_id, system_save_data_id, index, SaveDataRank::Primary); + } + + static constexpr SaveDataAttribute Make(ncm::ProgramId program_id, SaveDataType type, UserId user_id, SystemSaveDataId system_save_data_id) { + return Make(program_id, type, user_id, system_save_data_id, 0, SaveDataRank::Primary); + } + }; + static_assert(sizeof(SaveDataAttribute) == 0x40); + static_assert(std::is_trivially_destructible<SaveDataAttribute>::value); + + constexpr inline bool operator<(const SaveDataAttribute &lhs, const SaveDataAttribute &rhs) { + return std::tie(lhs.program_id, lhs.user_id, lhs.system_save_data_id, lhs.index, lhs.rank) < + std::tie(rhs.program_id, rhs.user_id, rhs.system_save_data_id, rhs.index, rhs.rank); + } + + constexpr inline bool operator==(const SaveDataAttribute &lhs, const SaveDataAttribute &rhs) { + return std::tie(lhs.program_id, lhs.user_id, lhs.system_save_data_id, lhs.type, lhs.rank, lhs.index) == + std::tie(rhs.program_id, rhs.user_id, rhs.system_save_data_id, rhs.type, rhs.rank, rhs.index); + } + + constexpr inline bool operator!=(const SaveDataAttribute &lhs, const SaveDataAttribute &rhs) { + return !(lhs == rhs); + } + + constexpr inline size_t DefaultSaveDataBlockSize = 16_KB; + + struct SaveDataExtraData { + SaveDataAttribute attr; + u64 owner_id; + s64 timestamp; + u32 flags; + u8 pad[4]; + s64 available_size; + s64 journal_size; + s64 commit_id; + u8 unused[0x190]; + }; + static_assert(sizeof(SaveDataExtraData) == 0x200); + static_assert(util::is_pod<SaveDataExtraData>::value); + + struct HashSalt { + static constexpr size_t Size = 32; + + u8 value[Size]; + }; + static_assert(util::is_pod<HashSalt>::value); + static_assert(sizeof(HashSalt) == HashSalt::Size); + + using SaveDataHashSalt = util::optional<HashSalt>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_sd_card.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_sd_card.hpp new file mode 100644 index 00000000..b72d9070 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_sd_card.hpp @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + class IEventNotifier; + + constexpr inline size_t SdCardCidSize = 0x10; + + enum SdCardSpeedMode { + SdCardSpeedMode_Identification = 0, + SdCardSpeedMode_DefaultSpeed = 1, + SdCardSpeedMode_HighSpeed = 2, + SdCardSpeedMode_Sdr12 = 3, + SdCardSpeedMode_Sdr25 = 4, + SdCardSpeedMode_Sdr50 = 5, + SdCardSpeedMode_Sdr104 = 6, + SdCardSpeedMode_Ddr50 = 7, + SdCardSpeedMode_Unknown = 8, + }; + + struct EncryptionSeed { + char value[0x10]; + }; + static_assert(util::is_pod<EncryptionSeed>::value); + static_assert(sizeof(EncryptionSeed) == 0x10); + + Result GetSdCardCid(void *dst, size_t size); + + inline void ClearSdCardCidSerialNumber(u8 *cid) { + /* Clear the serial number from the cid. */ + std::memset(cid + 2, 0, 4); + } + + Result GetSdCardUserAreaSize(s64 *out); + Result GetSdCardProtectedAreaSize(s64 *out); + + Result GetSdCardSpeedMode(SdCardSpeedMode *out); + + Result MountSdCard(const char *name); + + Result MountSdCardErrorReportDirectoryForAtmosphere(const char *name); + + Result OpenSdCardDetectionEventNotifier(std::unique_ptr<IEventNotifier> *out); + + bool IsSdCardInserted(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_signed_system_partition.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_signed_system_partition.hpp new file mode 100644 index 00000000..ca34d74c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_signed_system_partition.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + bool IsSignedSystemPartitionOnSdCardValid(const char *system_root_path); + bool IsSignedSystemPartitionOnSdCardValidDeprecated(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_speed_emulation.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_speed_emulation.hpp new file mode 100644 index 00000000..bcfabe5f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_speed_emulation.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + enum class SpeedEmulationMode { + None = 0, + Faster = 1, + Slower = 2, + Random = 3, + }; + + /* TODO */ + /* Result SetSpeedEmulationMode(SpeedEmulationMode mode); */ + /* Result GetSpeedEmulationMode(SpeedEmulationMode *out); */ + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_storage_type.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_storage_type.hpp new file mode 100644 index 00000000..6522aa6c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_storage_type.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + enum StorageType : s32 { + StorageType_SaveData = 0, + StorageType_RomFs = 1, + StorageType_Authoring = 2, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_substorage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_substorage.hpp new file mode 100644 index 00000000..7953040a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_substorage.hpp @@ -0,0 +1,155 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/impl/fs_newable.hpp> +#include <stratosphere/fs/fs_istorage.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + class SubStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + private: + std::shared_ptr<IStorage> m_shared_base_storage; + fs::IStorage *m_base_storage; + s64 m_offset; + s64 m_size; + bool m_resizable; + private: + constexpr bool IsValid() const { + return m_base_storage != nullptr; + } + public: + SubStorage() : m_shared_base_storage(), m_base_storage(nullptr), m_offset(0), m_size(0), m_resizable(false) { /* ... */ } + + SubStorage(const SubStorage &rhs) : m_shared_base_storage(rhs.m_shared_base_storage), m_base_storage(rhs.m_base_storage), m_offset(rhs.m_offset), m_size(rhs.m_size), m_resizable(rhs.m_resizable) { /* ... */} + SubStorage &operator=(const SubStorage &rhs) { + if (this != std::addressof(rhs)) { + m_shared_base_storage = rhs.m_shared_base_storage; + m_base_storage = rhs.m_base_storage; + m_offset = rhs.m_offset; + m_size = rhs.m_size; + m_resizable = rhs.m_resizable; + } + return *this; + } + + SubStorage(IStorage *storage, s64 o, s64 sz) : m_shared_base_storage(), m_base_storage(storage), m_offset(o), m_size(sz), m_resizable(false) { + AMS_ABORT_UNLESS(this->IsValid()); + AMS_ABORT_UNLESS(m_offset >= 0); + AMS_ABORT_UNLESS(m_size >= 0); + } + + SubStorage(std::shared_ptr<IStorage> storage, s64 o, s64 sz) : m_shared_base_storage(storage), m_base_storage(storage.get()), m_offset(o), m_size(sz), m_resizable(false) { + AMS_ABORT_UNLESS(this->IsValid()); + AMS_ABORT_UNLESS(m_offset >= 0); + AMS_ABORT_UNLESS(m_size >= 0); + } + + SubStorage(SubStorage *sub, s64 o, s64 sz) : m_shared_base_storage(sub->m_shared_base_storage), m_base_storage(sub->m_base_storage), m_offset(o + sub->m_offset), m_size(sz), m_resizable(false) { + AMS_ABORT_UNLESS(this->IsValid()); + AMS_ABORT_UNLESS(m_offset >= 0); + AMS_ABORT_UNLESS(m_size >= 0); + AMS_ABORT_UNLESS(sub->m_size >= o + sz); + } + + ALWAYS_INLINE ::ams::fs::IStorage *operator->() { + return this; + } + public: + void SetResizable(bool rsz) { + m_resizable = rsz; + } + public: + virtual Result Read(s64 offset, void *buffer, size_t size) override { + /* Ensure we're initialized. */ + R_UNLESS(this->IsValid(), fs::ResultNotInitialized()); + + /* Succeed immediately on zero-sized operation. */ + R_SUCCEED_IF(size == 0); + + + /* Validate arguments and read. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + R_TRY(IStorage::CheckAccessRange(offset, size, m_size)); + R_RETURN(m_base_storage->Read(m_offset + offset, buffer, size)); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override{ + /* Ensure we're initialized. */ + R_UNLESS(this->IsValid(), fs::ResultNotInitialized()); + + /* Succeed immediately on zero-sized operation. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments and write. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + R_TRY(IStorage::CheckAccessRange(offset, size, m_size)); + R_RETURN(m_base_storage->Write(m_offset + offset, buffer, size)); + } + + virtual Result Flush() override { + R_UNLESS(this->IsValid(), fs::ResultNotInitialized()); + R_RETURN(m_base_storage->Flush()); + } + + virtual Result SetSize(s64 size) override { + /* Ensure we're initialized and validate arguments. */ + R_UNLESS(this->IsValid(), fs::ResultNotInitialized()); + R_UNLESS(m_resizable, fs::ResultUnsupportedSetSizeForNotResizableSubStorage()); + + R_TRY(IStorage::CheckOffsetAndSize(m_offset, size)); + + /* Ensure that we're allowed to set size. */ + s64 cur_size; + R_TRY(m_base_storage->GetSize(std::addressof(cur_size))); + R_UNLESS(cur_size == m_offset + m_size, fs::ResultUnsupportedSetSizeForResizableSubStorage()); + + /* Set the size. */ + R_TRY(m_base_storage->SetSize(m_offset + size)); + + m_size = size; + R_SUCCEED(); + } + + virtual Result GetSize(s64 *out) override { + /* Ensure we're initialized. */ + R_UNLESS(this->IsValid(), fs::ResultNotInitialized()); + + *out = m_size; + R_SUCCEED(); + } + + virtual Result OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + /* Ensure we're initialized. */ + R_UNLESS(this->IsValid(), fs::ResultNotInitialized()); + + /* If we're not invalidating, sanity check arguments. */ + if (op_id != fs::OperationId::Invalidate) { + /* Succeed immediately on zero-sized operation other than invalidate. */ + R_SUCCEED_IF(size == 0); + + /* Check access extents. */ + R_TRY(IStorage::CheckOffsetAndSize(offset, size)); + } + + /* Perform the operation. */ + R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, m_offset + offset, size, src, src_size)); + } + + using IStorage::OperateRange; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_system_data.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_system_data.hpp new file mode 100644 index 00000000..7a980e1f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_system_data.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + Result QueryMountSystemDataCacheSize(size_t *out, ncm::SystemDataId data_id); + + Result MountSystemData(const char *name, ncm::SystemDataId data_id); + Result MountSystemData(const char *name, ncm::SystemDataId data_id, void *cache_buffer, size_t cache_size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_system_save_data.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_system_save_data.hpp new file mode 100644 index 00000000..99aaef8f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fs_system_save_data.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_save_data_types.hpp> + +namespace ams::fs { + + /* ACCURATE_TO_VERSION: Unknown */ + void DisableAutoSaveDataCreation(); + + Result CreateSystemSaveData(SystemSaveDataId save_id, s64 size, s64 journal_size, u32 flags); + Result CreateSystemSaveData(SystemSaveDataId save_id, u64 owner_id, s64 size, s64 journal_size, u32 flags); + Result CreateSystemSaveData(SaveDataSpaceId space_id, SystemSaveDataId save_id, u64 owner_id, s64 size, s64 journal_size, u32 flags); + + Result CreateSystemSaveData(SystemSaveDataId save_id, UserId user_id, s64 size, s64 journal_size, u32 flags); + Result CreateSystemSaveData(SystemSaveDataId save_id, UserId user_id, u64 owner_id, s64 size, s64 journal_size, u32 flags); + Result CreateSystemSaveData(SaveDataSpaceId space_id, SystemSaveDataId save_id, UserId user_id, u64 owner_id, s64 size, s64 journal_size, u32 flags); + + Result MountSystemSaveData(const char *name, SystemSaveDataId id); + Result MountSystemSaveData(const char *name, SaveDataSpaceId space_id, SystemSaveDataId id); + + Result MountSystemSaveData(const char *name, SystemSaveDataId id, UserId user_id); + Result MountSystemSaveData(const char *name, SaveDataSpaceId space_id, SystemSaveDataId id, UserId user_id); + + Result DeleteSystemSaveData(SaveDataSpaceId space_id, SystemSaveDataId id, UserId user_id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_idirectory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_idirectory.hpp new file mode 100644 index 00000000..8550b43a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_idirectory.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_directory.hpp> + +namespace ams::fs::fsa { + + /* ACCURATE_TO_VERSION: Unknown */ + class IDirectory { + public: + virtual ~IDirectory() { /* ... */ } + + Result Read(s64 *out_count, DirectoryEntry *out_entries, s64 max_entries) { + R_UNLESS(out_count != nullptr, fs::ResultNullptrArgument()); + if (max_entries == 0) { + *out_count = 0; + R_SUCCEED(); + } + R_UNLESS(out_entries != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(max_entries > 0, fs::ResultInvalidArgument()); + R_RETURN(this->DoRead(out_count, out_entries, max_entries)); + } + + Result GetEntryCount(s64 *out) { + R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + R_RETURN(this->DoGetEntryCount(out)); + } + public: + /* TODO: This is a hack to allow the mitm API to work. Find a better way? */ + virtual sf::cmif::DomainObjectId GetDomainObjectId() const = 0; + protected: + /* ...? */ + private: + virtual Result DoRead(s64 *out_count, DirectoryEntry *out_entries, s64 max_entries) = 0; + virtual Result DoGetEntryCount(s64 *out) = 0; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifile.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifile.hpp new file mode 100644 index 00000000..68775180 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifile.hpp @@ -0,0 +1,144 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_file.hpp> +#include <stratosphere/fs/fs_filesystem.hpp> +#include <stratosphere/fs/fs_operate_range.hpp> + +namespace ams::fs::fsa { + + class IFile { + public: + virtual ~IFile() { /* ... */ } + + Result Read(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) { + /* Check that we have an output pointer. */ + R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + + /* If we have nothing to read, just succeed. */ + if (size == 0) { + *out = 0; + R_SUCCEED(); + } + + /* Check that the read is valid. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(offset >= 0, fs::ResultOutOfRange()); + R_UNLESS(util::IsIntValueRepresentable<s64>(size), fs::ResultOutOfRange()); + R_UNLESS(util::CanAddWithoutOverflow<s64>(offset, size), fs::ResultOutOfRange()); + + /* Do the read. */ + R_RETURN(this->DoRead(out, offset, buffer, size, option)); + } + + ALWAYS_INLINE Result Read(size_t *out, s64 offset, void *buffer, size_t size) { R_RETURN(this->Read(out, offset, buffer, size, ReadOption::None)); } + + Result GetSize(s64 *out) { + R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + R_RETURN(this->DoGetSize(out)); + } + + Result Flush() { + R_RETURN(this->DoFlush()); + } + + Result Write(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) { + /* Handle the zero-size case. */ + if (size == 0) { + if (option.HasFlushFlag()) { + R_TRY(this->Flush()); + } + R_SUCCEED(); + } + + /* Check the write is valid. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(offset >= 0, fs::ResultOutOfRange()); + R_UNLESS(util::IsIntValueRepresentable<s64>(size), fs::ResultOutOfRange()); + R_UNLESS(util::CanAddWithoutOverflow<s64>(offset, size), fs::ResultOutOfRange()); + + R_RETURN(this->DoWrite(offset, buffer, size, option)); + } + + Result SetSize(s64 size) { + R_UNLESS(size >= 0, fs::ResultOutOfRange()); + R_RETURN(this->DoSetSize(size)); + } + + Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + R_RETURN(this->DoOperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + } + + Result OperateRange(fs::OperationId op_id, s64 offset, s64 size) { + R_RETURN(this->DoOperateRange(nullptr, 0, op_id, offset, size, nullptr, 0)); + } + public: + /* TODO: This is a hack to allow the mitm API to work. Find a better way? */ + virtual sf::cmif::DomainObjectId GetDomainObjectId() const = 0; + protected: + Result DryRead(size_t *out, s64 offset, size_t size, const fs::ReadOption &option, OpenMode open_mode) { + AMS_UNUSED(option); + + /* Check that we can read. */ + R_UNLESS((open_mode & OpenMode_Read) != 0, fs::ResultReadNotPermitted()); + + /* Get the file size, and validate our offset. */ + s64 file_size = 0; + R_TRY(this->DoGetSize(std::addressof(file_size))); + R_UNLESS(offset <= file_size, fs::ResultOutOfRange()); + + *out = static_cast<size_t>(std::min(file_size - offset, static_cast<s64>(size))); + R_SUCCEED(); + } + + Result DrySetSize(s64 size, fs::OpenMode open_mode) { + AMS_UNUSED(size); + + /* Check that we can write. */ + R_UNLESS((open_mode & OpenMode_Write) != 0, fs::ResultWriteNotPermitted()); + R_SUCCEED(); + } + + Result DryWrite(bool *out_append, s64 offset, size_t size, const fs::WriteOption &option, fs::OpenMode open_mode) { + AMS_UNUSED(option); + + /* Check that we can write. */ + R_UNLESS((open_mode & OpenMode_Write) != 0, fs::ResultWriteNotPermitted()); + + /* Get the file size. */ + s64 file_size = 0; + R_TRY(this->DoGetSize(&file_size)); + + /* Determine if we need to append. */ + *out_append = false; + if (file_size < offset + static_cast<s64>(size)) { + R_UNLESS((open_mode & OpenMode_AllowAppend) != 0, fs::ResultFileExtensionWithoutOpenModeAllowAppend()); + *out_append = true; + } + + R_SUCCEED(); + } + private: + virtual Result DoRead(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) = 0; + virtual Result DoGetSize(s64 *out) = 0; + virtual Result DoFlush() = 0; + virtual Result DoWrite(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) = 0; + virtual Result DoSetSize(s64 size) = 0; + virtual Result DoOperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) = 0; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifilesystem.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifilesystem.hpp new file mode 100644 index 00000000..d72670e4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_ifilesystem.hpp @@ -0,0 +1,187 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_filesystem.hpp> +#include <stratosphere/fs/fs_filesystem_for_debug.hpp> +#include <stratosphere/fs/fs_path.hpp> + +namespace ams::fs::fsa { + + /* ACCURATE_TO_VERSION: Unknown */ + class IFile; + class IDirectory; + + enum class QueryId { + SetConcatenationFileAttribute = 0, + UpdateMac = 1, + IsSignedSystemPartitionOnSdCardValid = 2, + QueryUnpreparedFileInformation = 3, + }; + + class IFileSystem { + public: + virtual ~IFileSystem() { /* ... */ } + + Result CreateFile(const fs::Path &path, s64 size, int option) { + R_UNLESS(size >= 0, fs::ResultOutOfRange()); + R_RETURN(this->DoCreateFile(path, size, option)); + } + + Result CreateFile(const fs::Path &path, s64 size) { + R_RETURN(this->CreateFile(path, size, fs::CreateOption_None)); + } + + Result DeleteFile(const fs::Path &path) { + R_RETURN(this->DoDeleteFile(path)); + } + + Result CreateDirectory(const fs::Path &path) { + R_RETURN(this->DoCreateDirectory(path)); + } + + Result DeleteDirectory(const fs::Path &path) { + R_RETURN(this->DoDeleteDirectory(path)); + } + + Result DeleteDirectoryRecursively(const fs::Path &path) { + R_RETURN(this->DoDeleteDirectoryRecursively(path)); + } + + Result RenameFile(const fs::Path &old_path, const fs::Path &new_path) { + R_RETURN(this->DoRenameFile(old_path, new_path)); + } + + Result RenameDirectory(const fs::Path &old_path, const fs::Path &new_path) { + R_RETURN(this->DoRenameDirectory(old_path, new_path)); + } + + Result GetEntryType(DirectoryEntryType *out, const fs::Path &path) { + R_RETURN(this->DoGetEntryType(out, path)); + } + + Result OpenFile(std::unique_ptr<IFile> *out_file, const fs::Path &path, OpenMode mode) { + R_UNLESS(out_file != nullptr, fs::ResultNullptrArgument()); + R_UNLESS((mode & OpenMode_ReadWrite) != 0, fs::ResultInvalidOpenMode()); + R_UNLESS((mode & ~OpenMode_All) == 0, fs::ResultInvalidOpenMode()); + R_RETURN(this->DoOpenFile(out_file, path, mode)); + } + + Result OpenDirectory(std::unique_ptr<IDirectory> *out_dir, const fs::Path &path, OpenDirectoryMode mode) { + R_UNLESS(out_dir != nullptr, fs::ResultNullptrArgument()); + R_UNLESS((mode & OpenDirectoryMode_All) != 0, fs::ResultInvalidOpenMode()); + R_UNLESS((mode & ~(OpenDirectoryMode_All | OpenDirectoryMode_NotRequireFileSize)) == 0, fs::ResultInvalidOpenMode()); + R_RETURN(this->DoOpenDirectory(out_dir, path, mode)); + } + + Result Commit() { + R_RETURN(this->DoCommit()); + } + + Result GetFreeSpaceSize(s64 *out, const fs::Path &path) { + R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + R_RETURN(this->DoGetFreeSpaceSize(out, path)); + } + + Result GetTotalSpaceSize(s64 *out, const fs::Path &path) { + R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + R_RETURN(this->DoGetTotalSpaceSize(out, path)); + } + + Result CleanDirectoryRecursively(const fs::Path &path) { + R_RETURN(this->DoCleanDirectoryRecursively(path)); + } + + Result GetFileTimeStampRaw(FileTimeStampRaw *out, const fs::Path &path) { + R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + R_RETURN(this->DoGetFileTimeStampRaw(out, path)); + } + + Result QueryEntry(char *dst, size_t dst_size, const char *src, size_t src_size, QueryId query, const fs::Path &path) { + R_RETURN(this->DoQueryEntry(dst, dst_size, src, src_size, query, path)); + } + + /* These aren't accessible as commands. */ + + Result CommitProvisionally(s64 counter) { + R_RETURN(this->DoCommitProvisionally(counter)); + } + + Result Rollback() { + R_RETURN(this->DoRollback()); + } + + Result Flush() { + R_RETURN(this->DoFlush()); + } + + protected: + /* ...? */ + private: + virtual Result DoCreateFile(const fs::Path &path, s64 size, int flags) = 0; + virtual Result DoDeleteFile(const fs::Path &path) = 0; + virtual Result DoCreateDirectory(const fs::Path &path) = 0; + virtual Result DoDeleteDirectory(const fs::Path &path) = 0; + virtual Result DoDeleteDirectoryRecursively(const fs::Path &path) = 0; + virtual Result DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) = 0; + virtual Result DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) = 0; + virtual Result DoGetEntryType(fs::DirectoryEntryType *out, const fs::Path &path) = 0; + virtual Result DoOpenFile(std::unique_ptr<fs::fsa::IFile> *out_file, const fs::Path &path, fs::OpenMode mode) = 0; + virtual Result DoOpenDirectory(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const fs::Path &path, fs::OpenDirectoryMode mode) = 0; + virtual Result DoCommit() = 0; + + virtual Result DoGetFreeSpaceSize(s64 *out, const fs::Path &path) { + AMS_UNUSED(out, path); + R_THROW(fs::ResultNotImplemented()); + } + + virtual Result DoGetTotalSpaceSize(s64 *out, const fs::Path &path) { + AMS_UNUSED(out, path); + R_THROW(fs::ResultNotImplemented()); + } + + virtual Result DoCleanDirectoryRecursively(const fs::Path &path) = 0; + + virtual Result DoGetFileTimeStampRaw(fs::FileTimeStampRaw *out, const fs::Path &path) { + AMS_UNUSED(out, path); + R_THROW(fs::ResultNotImplemented()); + } + + virtual Result DoQueryEntry(char *dst, size_t dst_size, const char *src, size_t src_size, fs::fsa::QueryId query, const fs::Path &path) { + AMS_UNUSED(dst, dst_size, src, src_size, query, path); + R_THROW(fs::ResultNotImplemented()); + } + + /* These aren't accessible as commands. */ + virtual Result DoCommitProvisionally(s64 counter) { + AMS_UNUSED(counter); + R_THROW(fs::ResultNotImplemented()); + } + + virtual Result DoRollback() { + R_THROW(fs::ResultNotImplemented()); + } + + virtual Result DoFlush() { + R_THROW(fs::ResultNotImplemented()); + } + }; + + template<typename T> + concept PointerToFileSystem = ::ams::util::RawOrSmartPointerTo<T, ::ams::fs::fsa::IFileSystem>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_registrar.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_registrar.hpp new file mode 100644 index 00000000..92473d78 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/fsa/fs_registrar.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs::fsa { + + /* ACCURATE_TO_VERSION: Unknown */ + class ICommonMountNameGenerator { + public: + virtual ~ICommonMountNameGenerator() { /* ... */ } + virtual Result GenerateCommonMountName(char *name, size_t name_size) = 0; + }; + + class IFileSystem; + + Result Register(const char *name, std::unique_ptr<IFileSystem> &&fs); + Result Register(const char *name, std::unique_ptr<IFileSystem> &&fs, std::unique_ptr<ICommonMountNameGenerator> &&generator); + Result Register(const char *name, std::unique_ptr<IFileSystem> &&fs, std::unique_ptr<ICommonMountNameGenerator> &&generator, bool use_data_cache, bool use_path_cache, bool multi_commit_supported); + + void Unregister(const char *name); +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_access_log_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_access_log_impl.hpp new file mode 100644 index 00000000..c0f0f654 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_access_log_impl.hpp @@ -0,0 +1,341 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fs_access_log.hpp> +#include <stratosphere/fs/fs_directory.hpp> +#include <stratosphere/fs/fs_file.hpp> +#include <stratosphere/fs/fs_priority.hpp> +#include <stratosphere/os/os_tick.hpp> + +namespace ams::fs::impl { + + /* ACCURATE_TO_VERSION: Unknown */ + enum AccessLogTarget : u32 { + AccessLogTarget_None = (0 << 0), + AccessLogTarget_Application = (1 << 0), + AccessLogTarget_System = (1 << 1), + }; + + struct IdentifyAccessLogHandle { + void *handle; + public: + static constexpr IdentifyAccessLogHandle MakeHandle(void *h) { + return IdentifyAccessLogHandle{h}; + } + }; + + bool IsEnabledAccessLog(u32 target); + bool IsEnabledAccessLog(); + + bool IsEnabledHandleAccessLog(fs::FileHandle handle); + bool IsEnabledHandleAccessLog(fs::DirectoryHandle handle); + bool IsEnabledHandleAccessLog(fs::impl::IdentifyAccessLogHandle handle); + bool IsEnabledHandleAccessLog(const void *handle); + + bool IsEnabledFileSystemAccessorAccessLog(const char *mount_name); + void EnableFileSystemAccessorAccessLog(const char *mount_name); + + using AccessLogPrinterCallback = int (*)(char *buffer, size_t buffer_size); + void RegisterStartAccessLogPrinterCallback(AccessLogPrinterCallback callback); + + void OutputAccessLog(Result result, os::Tick start, os::Tick end, const char *name, fs::FileHandle handle, const char *fmt, ...) __attribute__((format (printf, 6, 7))); + void OutputAccessLog(Result result, os::Tick start, os::Tick end, const char *name, fs::DirectoryHandle handle, const char *fmt, ...) __attribute__((format (printf, 6, 7))); + void OutputAccessLog(Result result, os::Tick start, os::Tick end, const char *name, fs::impl::IdentifyAccessLogHandle handle, const char *fmt, ...) __attribute__((format (printf, 6, 7))); + void OutputAccessLog(Result result, os::Tick start, os::Tick end, const char *name, const void *handle, const char *fmt, ...) __attribute__((format (printf, 6, 7))); + void OutputAccessLog(Result result, fs::Priority priority, os::Tick start, os::Tick end, const char *name, const void *handle, const char *fmt, ...) __attribute__((format (printf, 7, 8))); + void OutputAccessLog(Result result, fs::PriorityRaw priority_raw, os::Tick start, os::Tick end, const char *name, const void *handle, const char *fmt, ...) __attribute__((format (printf, 7, 8))); + + void OutputAccessLogToOnlySdCard(const char *fmt, ...) __attribute__((format (printf, 1, 2))); + + void OutputAccessLogUnlessResultSuccess(Result result, os::Tick start, os::Tick end, const char *name, fs::FileHandle handle, const char *fmt, ...) __attribute__((format (printf, 6, 7))); + void OutputAccessLogUnlessResultSuccess(Result result, os::Tick start, os::Tick end, const char *name, fs::DirectoryHandle handle, const char *fmt, ...) __attribute__((format (printf, 6, 7))); + void OutputAccessLogUnlessResultSuccess(Result result, os::Tick start, os::Tick end, const char *name, const void *handle, const char *fmt, ...) __attribute__((format (printf, 6, 7))); + + class IdString { + private: + char m_buffer[0x20]; + private: + const char *ToValueString(int id); + public: + template<typename T> + const char *ToString(T id); + }; + + template<typename T> requires (requires { T{}; }) + inline T DereferenceOutValue(T *out_value, Result result) { + if (R_SUCCEEDED(result) && out_value != nullptr) { + return *out_value; + } else { + return T{}; + } + } + + static_assert(sizeof(size_t) == sizeof(u64)); + +} + +/* Access log result name. */ +#define AMS_FS_IMPL_ACCESS_LOG_RESULT_NAME __tmp_ams_fs_access_log_result +/* Access log utils. */ +#define AMS_FS_IMPL_ACCESS_LOG_DEREFERENCE_OUT_VALUE(__VALUE__) ::ams::fs::impl::DereferenceOutValue(__VALUE__, AMS_FS_IMPL_ACCESS_LOG_RESULT_NAME) + +/* Access log components. */ +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_SIZE ", size: %" PRId64 "" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_READ_SIZE ", read_size: %" PRIuZ "" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_QUERY_SIZE ", read_size: %" PRIuZ "" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_OFFSET_AND_SIZE ", offset: %" PRId64 ", size: %" PRIuZ "" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_THREAD_ID ", thread_id: %" PRIu64 "" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT ", name: \"%s\"" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_ENTRY_COUNT ", entry_count: %" PRId64 "" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_ENTRY_BUFFER_COUNT ", entry_buffer_count: %" PRId64 "" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_OPEN_MODE ", open_mode: 0x%" PRIX32 "" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH ", path: \"%s\"" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH_AND_SIZE ", path: \"%s\", size: %" PRId64 "" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH_AND_OPEN_MODE ", path: \"%s\", open_mode: 0x%" PRIX32 "" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_RENAME ", path: \"%s\", new_path: \"%s\"" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_DIRECTORY_ENTRY_TYPE ", entry_type: %s" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_CONTENT_TYPE ", content_type: %s" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_HOST_OPTION ", mount_host_option: %s" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_ROOT_PATH ", root_path: %s" + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_APPLICATION_ID ", applicationid: 0x%" PRIx64 "" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_BIS_PARTITION_ID ", bispartitionid: %s" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_CONTENT_STORAGE_ID ", contentstorageid: %s" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_SYSTEM_DATA_ID ", systemdataid: 0x%" PRIx64 "" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_DATA_ID ", dataid: 0x%" PRIx64 "" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_GAME_CARD_HANDLE ", gamecard_handle: 0x%" PRIX32 "" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_GAME_CARD_PARTITION ", gamecard_partition: %s" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_IMAGE_DIRECTORY_ID ", imagedirectoryid: %s" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_PROGRAM_ID ", programid: 0x%" PRIx64 "" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_ID ", savedataid: 0x%" PRIx64 "" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_SPACE_ID ", savedataspaceid: %s" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_OWNER_ID ", save_data_owner_id: 0x%" PRIx64 "" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_USER_ID ", userid: 0x%016" PRIx64 "%016" PRIx64 "" + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_FLAGS ", save_data_flags: 0x%08" PRIX32 "" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_JOURNAL_SIZE ", save_data_journal_size: %" PRId64 "" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_SIZE ", save_data_size: %" PRId64 "" + +/* Access log formats. */ +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_NONE "" + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_READ_FILE(__OUT_READ_SIZE__, __OFFSET__, __SIZE__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_OFFSET_AND_SIZE AMS_FS_IMPL_ACCESS_LOG_FORMAT_READ_SIZE, __OFFSET__, __SIZE__, AMS_FS_IMPL_ACCESS_LOG_DEREFERENCE_OUT_VALUE(__OUT_READ_SIZE__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_WRITE_FILE_WITH_NO_OPTION AMS_FS_IMPL_ACCESS_LOG_FORMAT_OFFSET_AND_SIZE +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_WRITE_FILE_WITH_FLUSH_OPTION AMS_FS_IMPL_ACCESS_LOG_FORMAT_WRITE_FILE_WITH_NO_OPTION ", write_option: Flush" +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_WRITE_FILE(__OPTION__) ((__OPTION__).HasFlushFlag() ? AMS_FS_IMPL_ACCESS_LOG_FORMAT_WRITE_FILE_WITH_FLUSH_OPTION : AMS_FS_IMPL_ACCESS_LOG_FORMAT_WRITE_FILE_WITH_NO_OPTION) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_GET_FILE_SIZE(__OUT_SIZE__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_SIZE, AMS_FS_IMPL_ACCESS_LOG_DEREFERENCE_OUT_VALUE(__OUT_SIZE__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_READ_DIRECTORY(__OUT_ENTRY_COUNT__, __ENTRY_BUFFER_COUNT__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_ENTRY_BUFFER_COUNT AMS_FS_IMPL_ACCESS_LOG_FORMAT_ENTRY_COUNT, __ENTRY_BUFFER_COUNT__, AMS_FS_IMPL_ACCESS_LOG_DEREFERENCE_OUT_VALUE(__OUT_ENTRY_COUNT__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_GET_DIRECTORY_ENTRY_COUNT(__OUT_ENTRY_COUNT__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_ENTRY_COUNT, AMS_FS_IMPL_ACCESS_LOG_DEREFERENCE_OUT_VALUE(__OUT_ENTRY_COUNT__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_GET_ENTRY_TYPE(__OUT_ENTRY_TYPE__, __PATH__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH AMS_FS_IMPL_ACCESS_LOG_FORMAT_DIRECTORY_ENTRY_TYPE, __PATH__, ::ams::fs::impl::IdString().ToString(AMS_FS_IMPL_ACCESS_LOG_DEREFERENCE_OUT_VALUE(__OUT_ENTRY_TYPE__)) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_GET_SPACE_SIZE(__OUT_SIZE__, __NAME__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT AMS_FS_IMPL_ACCESS_LOG_FORMAT_SIZE, __NAME__, AMS_FS_IMPL_ACCESS_LOG_DEREFERENCE_OUT_VALUE(__OUT_SIZE__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_APPLICATION_PACKAGE(__NAME__, __PATH__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH, (__NAME__), (__PATH__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_BIS(__NAME__, __ID__, __PATH__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT AMS_FS_IMPL_ACCESS_LOG_FORMAT_BIS_PARTITION_ID AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH, (__NAME__), ::ams::fs::impl::IdString().ToString(__ID__), (__PATH__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_CODE(__NAME__, __PATH__, __ID__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH AMS_FS_IMPL_ACCESS_LOG_FORMAT_PROGRAM_ID, (__NAME__), (__PATH__), (__ID__).value + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_CONTENT_PATH(__NAME__, __PATH__, __TYPE__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH AMS_FS_IMPL_ACCESS_LOG_FORMAT_CONTENT_TYPE, (__NAME__), (__PATH__), ::ams::fs::impl::IdString().ToString(__TYPE__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_CONTENT_PROGRAM_ID(__NAME__, __ID__, __TYPE__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT AMS_FS_IMPL_ACCESS_LOG_FORMAT_PROGRAM_ID AMS_FS_IMPL_ACCESS_LOG_FORMAT_CONTENT_TYPE, (__NAME__), (__ID__), ::ams::fs::impl::IdString().ToString(__TYPE__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_CONTENT_PATH_AND_PROGRAM_ID(__NAME__, __PATH__, __ID__, __TYPE__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH AMS_FS_IMPL_ACCESS_LOG_FORMAT_PROGRAM_ID AMS_FS_IMPL_ACCESS_LOG_FORMAT_CONTENT_TYPE, (__NAME__), (__PATH__), (__ID__).value, ::ams::fs::impl::IdString().ToString(__TYPE__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_CONTENT_PATH_AND_DATA_ID(__NAME__, __PATH__, __ID__, __TYPE__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH AMS_FS_IMPL_ACCESS_LOG_FORMAT_DATA_ID AMS_FS_IMPL_ACCESS_LOG_FORMAT_CONTENT_TYPE, (__NAME__), (__PATH__), (__ID__).value, ::ams::fs::impl::IdString().ToString(__TYPE__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_CONTENT_STORAGE(__NAME__, __ID__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT AMS_FS_IMPL_ACCESS_LOG_FORMAT_CONTENT_STORAGE_ID, (__NAME__), ::ams::fs::impl::IdString().ToString(__ID__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_DEVICE_SAVE_DATA_APPLICATION_ID(__NAME__, __ID__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT AMS_FS_IMPL_ACCESS_LOG_FORMAT_APPLICATION_ID, (__NAME__), (__ID__).value + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_GAME_CARD_PARTITION(__NAME__, __GCHANDLE__, __PARTITION__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT AMS_FS_IMPL_ACCESS_LOG_FORMAT_GAME_CARD_HANDLE AMS_FS_IMPL_ACCESS_LOG_FORMAT_GAME_CARD_PARTITION, (__NAME__), __GCHANDLE__, ::ams::fs::impl::IdString().ToString(__PARTITION__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_HOST_ROOT() \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT, (AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_HOST_ROOT_WITH_OPTION(__OPTION__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_HOST_OPTION, (AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME), ::ams::fs::impl::IdString().ToString(__OPTION__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_HOST(__NAME__, __ROOT_PATH__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT AMS_FS_IMPL_ACCESS_LOG_FORMAT_ROOT_PATH, (__NAME__), (__ROOT_PATH__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_HOST_WITH_OPTION(__NAME__, __ROOT_PATH__, __OPTION__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT AMS_FS_IMPL_ACCESS_LOG_FORMAT_ROOT_PATH AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_HOST_OPTION, (__NAME__), (__ROOT_PATH__), ::ams::fs::impl::IdString().ToString(__OPTION__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_IMAGE_DIRECTORY(__NAME__, __ID__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT AMS_FS_IMPL_ACCESS_LOG_FORMAT_IMAGE_DIRECTORY_ID, (__NAME__), ::ams::fs::impl::IdString().ToString(__ID__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_SYSTEM_DATA(__NAME__, __ID__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT AMS_FS_IMPL_ACCESS_LOG_FORMAT_SYSTEM_DATA_ID, (__NAME__), (__ID__).value + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_SYSTEM_SAVE_DATA(__NAME__, __SPACE__, __SAVE__, __USER__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_SPACE_ID AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_ID AMS_FS_IMPL_ACCESS_LOG_FORMAT_USER_ID, \ + (__NAME__), ::ams::fs::impl::IdString().ToString(__SPACE__), (__SAVE__), (__USER__).data[0], (__USER__).data[1] + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_CREATE_SYSTEM_SAVE_DATA(__SPACE__, __SAVE__, __USER__, __OWNER__, __SIZE__, __JOURNAL_SIZE__, __FLAGS__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_SPACE_ID AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_ID AMS_FS_IMPL_ACCESS_LOG_FORMAT_USER_ID AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_OWNER_ID AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_SIZE AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_JOURNAL_SIZE AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_FLAGS, \ + ::ams::fs::impl::IdString().ToString(__SPACE__), (__SAVE__), (__USER__).data[0], (__USER__).data[1], (__OWNER__), (__SIZE__), (__JOURNAL_SIZE__), (__FLAGS__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_DELETE_SAVE_DATA(__SPACE__, __SAVE__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_SPACE_ID AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_ID, \ + ::ams::fs::impl::IdString().ToString(__SPACE__), (__SAVE__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_DELETE_SYSTEM_SAVE_DATA(__SPACE__, __SAVE__, __USER__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_SPACE_ID AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_ID AMS_FS_IMPL_ACCESS_LOG_FORMAT_USER_ID, \ + ::ams::fs::impl::IdString().ToString(__SPACE__), (__SAVE__), (__USER__).data[0], (__USER__).data[1] + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_EXTEND_SAVE_DATA(__SPACE__, __SAVE__, __SIZE__, __JOURNAL_SIZE__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_SPACE_ID AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_ID AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_SIZE AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_JOURNAL_SIZE, \ + ::ams::fs::impl::IdString().ToString(__SPACE__), (__SAVE__), (__SIZE__), (__JOURNAL_SIZE__) + +#define AMS_FS_IMPL_ACCESS_LOG_FORMAT_QUERY_MOUNT_SYSTEM_DATA_CACHE_SIZE(__ID__, __SIZE__) \ + AMS_FS_IMPL_ACCESS_LOG_FORMAT_SYSTEM_DATA_ID AMS_FS_IMPL_ACCESS_LOG_FORMAT_QUERY_SIZE, (__ID__).value, AMS_FS_IMPL_ACCESS_LOG_DEREFERENCE_OUT_VALUE(__SIZE__) + +/* Access log invocation lambdas. */ +#define AMS_FS_IMPL_ACCESS_LOG_IMPL(__EXPR__, __HANDLE__, __ENABLED__, __NAME__, ...) \ + [&](const char *__fs_func_name_) -> Result { \ + if (!(__ENABLED__)) { \ + R_RETURN(__EXPR__); \ + } else { \ + const ::ams::os::Tick __fs_start_tick = ::ams::os::GetSystemTick(); \ + const auto AMS_FS_IMPL_ACCESS_LOG_RESULT_NAME = (__EXPR__); \ + const ::ams::os::Tick __fs_end_tick = ::ams::os::GetSystemTick(); \ + ::ams::fs::impl::OutputAccessLog(AMS_FS_IMPL_ACCESS_LOG_RESULT_NAME, __fs_start_tick, __fs_end_tick, __fs_func_name_, __HANDLE__, __VA_ARGS__); \ + R_RETURN( AMS_FS_IMPL_ACCESS_LOG_RESULT_NAME ); \ + } \ + }(__NAME__) + +#define AMS_FS_IMPL_ACCESS_LOG_WITH_PRIORITY_IMPL(__EXPR__, __PRIORITY__, __HANDLE__, __ENABLED__, __NAME__, ...) \ + [&](const char *__fs_func_name_) -> Result { \ + if (!(__ENABLED__)) { \ + R_RETURN(__EXPR__); \ + } else { \ + const ::ams::os::Tick __fs_start_tick = ::ams::os::GetSystemTick(); \ + const auto AMS_FS_IMPL_ACCESS_LOG_RESULT_NAME = (__EXPR__); \ + const ::ams::os::Tick __fs_end_tick = ::ams::os::GetSystemTick(); \ + ::ams::fs::impl::OutputAccessLog(AMS_FS_IMPL_ACCESS_LOG_RESULT_NAME, __PRIORITY__, __fs_start_tick, __fs_end_tick, __fs_func_name_, __HANDLE__, __VA_ARGS__); \ + R_RETURN( AMS_FS_IMPL_ACCESS_LOG_RESULT_NAME ); \ + } \ + }(__NAME__) + +#define AMS_FS_IMPL_ACCESS_LOG_EXPLICIT_IMPL(__RESULT__, __START__, __END__, __HANDLE__, __ENABLED__, __NAME__, ...) \ + [&](const char *__fs_func_name_) -> Result { \ + if (!(__ENABLED__)) { \ + R_RETURN(__RESULT__); \ + } else { \ + const auto AMS_FS_IMPL_ACCESS_LOG_RESULT_NAME = (__RESULT__); \ + ::ams::fs::impl::OutputAccessLog(AMS_FS_IMPL_ACCESS_LOG_RESULT_NAME, __START__, __END__, __fs_func_name_, __HANDLE__, __VA_ARGS__); \ + R_RETURN( AMS_FS_IMPL_ACCESS_LOG_RESULT_NAME ); \ + } \ + }(__NAME__) + +#define AMS_FS_IMPL_ACCESS_LOG_UNLESS_R_SUCCEEDED_IMPL(__EXPR__, __ENABLED__, __NAME__, ...) \ + [&](const char *__fs_func_name_) -> Result { \ + if (!(__ENABLED__)) { \ + R_RETURN(__EXPR__); \ + } else { \ + const ::ams::os::Tick __fs_start_tick = ::ams::os::GetSystemTick(); \ + const auto AMS_FS_IMPL_ACCESS_LOG_RESULT_NAME = (__EXPR__); \ + const ::ams::os::Tick __fs_end_tick = ::ams::os::GetSystemTick(); \ + ::ams::fs::impl::OutputAccessLogUnlessResultSuccess(AMS_FS_IMPL_ACCESS_LOG_RESULT_NAME, __fs_start_tick, __fs_end_tick, __fs_func_name_, nullptr, __VA_ARGS__); \ + R_RETURN( AMS_FS_IMPL_ACCESS_LOG_RESULT_NAME ); \ + } \ + }(__NAME__) + +/* Access log api. */ +#define AMS_FS_IMPL_ACCESS_LOG(__EXPR__, __HANDLE__, ...) \ + AMS_FS_IMPL_ACCESS_LOG_IMPL((__EXPR__), __HANDLE__, ::ams::fs::impl::IsEnabledAccessLog() && ::ams::fs::impl::IsEnabledHandleAccessLog(__HANDLE__), AMS_CURRENT_FUNCTION_NAME, __VA_ARGS__) + +#define AMS_FS_IMPL_ACCESS_LOG_SYSTEM(__EXPR__, __HANDLE__, ...) \ + AMS_FS_IMPL_ACCESS_LOG_IMPL((__EXPR__), __HANDLE__, ::ams::fs::impl::IsEnabledAccessLog(::ams::fs::impl::AccessLogTarget_System) && ::ams::fs::impl::IsEnabledHandleAccessLog(__HANDLE__), AMS_CURRENT_FUNCTION_NAME, __VA_ARGS__) + +#define AMS_FS_IMPL_ACCESS_LOG_WITH_NAME(__EXPR__, __HANDLE__, __NAME__, ...) \ + AMS_FS_IMPL_ACCESS_LOG_IMPL((__EXPR__), __HANDLE__, ::ams::fs::impl::IsEnabledAccessLog() && ::ams::fs::impl::IsEnabledHandleAccessLog(__HANDLE__), __NAME__, __VA_ARGS__) + +#define AMS_FS_IMPL_ACCESS_LOG_EXPLICIT(__RESULT__, __START__, __END__, __HANDLE__, __NAME__, ...) \ + AMS_FS_IMPL_ACCESS_LOG_EXPLICIT_IMPL((__RESULT__), __START__, __END__, __HANDLE__, ::ams::fs::impl::IsEnabledAccessLog() && ::ams::fs::impl::IsEnabledHandleAccessLog(__HANDLE__), __NAME__, __VA_ARGS__) + +#define AMS_FS_IMPL_ACCESS_LOG_UNLESS_R_SUCCEEDED(__EXPR__, ...) \ + AMS_FS_IMPL_ACCESS_LOG_UNLESS_R_SUCCEEDED_IMPL((__EXPR__), ::ams::fs::impl::IsEnabledAccessLog(), AMS_CURRENT_FUNCTION_NAME, __VA_ARGS__) + +/* FS Accessor logging. */ +#define AMS_FS_IMPL_ACCESS_LOG_FS_ACCESSOR_ENABLE_IMPL(__NAME__, __ENABLED__) \ + do { \ + if (static_cast<bool>(__ENABLED__)) { \ + ::ams::fs::impl::EnableFileSystemAccessorAccessLog((__NAME__)); \ + } \ + } while (false) + +#define AMS_FS_IMPL_ACCESS_LOG_FS_ACCESSOR_ENABLE(__NAME__) \ + AMS_FS_IMPL_ACCESS_LOG_FS_ACCESSOR_ENABLE_IMPL((__NAME__), ::ams::fs::impl::IsEnabledAccessLog(::ams::fs::impl::AccessLogTarget_Application)) + +// DEBUG +#define AMS_FS_FORCE_ENABLE_SYSTEM_MOUNT_ACCESS_LOG + +/* System access log api. */ +#if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_FS_FORCE_ENABLE_SYSTEM_MOUNT_ACCESS_LOG) + +#define AMS_FS_IMPL_ACCESS_LOG_SYSTEM_MOUNT(__EXPR__, __NAME__, ...) \ + AMS_FS_IMPL_ACCESS_LOG_IMPL((__EXPR__), nullptr, ::ams::fs::impl::IsEnabledAccessLog(::ams::fs::impl::AccessLogTarget_System), AMS_CURRENT_FUNCTION_NAME, __VA_ARGS__) + +#define AMS_FS_IMPL_ACCESS_LOG_SYSTEM_FS_ACCESSOR_ENABLE(__NAME__) \ + AMS_FS_IMPL_ACCESS_LOG_FS_ACCESSOR_ENABLE_IMPL((__NAME__), ::ams::fs::impl::IsEnabledAccessLog(::ams::fs::impl::AccessLogTarget_System)) + +#else + +#define AMS_FS_IMPL_ACCESS_LOG_SYSTEM_MOUNT(__EXPR__, __NAME__, ...) (__EXPR__) + +#define AMS_FS_IMPL_ACCESS_LOG_SYSTEM_FS_ACCESSOR_ENABLE(__NAME__) static_cast<void>(0) + +#endif + +/* Specific utilities. */ +#define AMS_FS_IMPL_ACCESS_LOG_FILESYSTEM(__EXPR__, __HANDLE__, __FILESYSTEM__, ...) \ + AMS_FS_IMPL_ACCESS_LOG_IMPL((__EXPR__), __HANDLE__, ::ams::fs::impl::IsEnabledAccessLog() && (__FILESYSTEM__)->IsEnabledAccessLog(), AMS_CURRENT_FUNCTION_NAME, __VA_ARGS__) + +#define AMS_FS_IMPL_ACCESS_LOG_FILESYSTEM_WITH_NAME(__EXPR__, __HANDLE__, __FILESYSTEM__, __NAME__, ...) \ + AMS_FS_IMPL_ACCESS_LOG_IMPL((__EXPR__), __HANDLE__, ::ams::fs::impl::IsEnabledAccessLog() && (__FILESYSTEM__)->IsEnabledAccessLog(), __NAME__, __VA_ARGS__) + +#define AMS_FS_IMPL_ACCESS_LOG_MOUNT(__EXPR__, __NAME__, ...) \ + AMS_FS_IMPL_ACCESS_LOG_IMPL((__EXPR__), nullptr, ::ams::fs::impl::IsEnabledAccessLog(::ams::fs::impl::AccessLogTarget_Application), AMS_CURRENT_FUNCTION_NAME, __VA_ARGS__) + +#define AMS_FS_IMPL_ACCESS_LOG_MOUNT_UNLESS_R_SUCCEEDED(__EXPR__, __NAME__, ...) \ + AMS_FS_IMPL_ACCESS_LOG_UNLESS_R_SUCCEEDED_IMPL((__EXPR__), ::ams::fs::impl::IsEnabledAccessLog(::ams::fs::impl::AccessLogTarget_Application), AMS_CURRENT_FUNCTION_NAME, __VA_ARGS__) + +#define AMS_FS_IMPL_ACCESS_LOG_UNMOUNT(__EXPR__, __MOUNT_NAME__, ...) \ + AMS_FS_IMPL_ACCESS_LOG_IMPL((__EXPR__), nullptr, ::ams::fs::impl::IsEnabledAccessLog() && ::ams::fs::impl::IsEnabledFileSystemAccessorAccessLog(__MOUNT_NAME__), AMS_CURRENT_FUNCTION_NAME, __VA_ARGS__) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_common_mount_name.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_common_mount_name.hpp new file mode 100644 index 00000000..02fdf3fc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_common_mount_name.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +namespace ams::fs::impl { + + /* ACCURATE_TO_VERSION: Unknown */ + + /* Delimiting of mount names. */ + constexpr inline const char ReservedMountNamePrefixCharacter = '@'; + + #define AMS_FS_IMPL_MOUNT_NAME_DELIMITER ":/" + #define AMS_FS_IMPL_MOUNT_NAME_DELIMITER_LEN 2 + constexpr inline const char * const MountNameDelimiter = AMS_FS_IMPL_MOUNT_NAME_DELIMITER; + + /* Filesystem names. */ + #define AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME "@Host" + #define AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME_LEN 5 + + constexpr inline const char * const HostRootFileSystemMountName = AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME; + + constexpr inline const char * const SdCardFileSystemMountName = "@Sdcard"; + constexpr inline const char * const GameCardFileSystemMountName = "@Gc"; + + constexpr inline size_t GameCardFileSystemMountNameSuffixLength = 1; + + constexpr inline const char * const GameCardFileSystemMountNameUpdateSuffix = "U"; + constexpr inline const char * const GameCardFileSystemMountNameNormalSuffix = "N"; + constexpr inline const char * const GameCardFileSystemMountNameSecureSuffix = "S"; + + /* Built-in storage names. */ + constexpr inline const char * const BisCalibrationFilePartitionMountName = "@CalibFile"; + constexpr inline const char * const BisSafeModePartitionMountName = "@Safe"; + constexpr inline const char * const BisUserPartitionMountName = "@User"; + constexpr inline const char * const BisSystemPartitionMountName = "@System"; + + /* Content storage names. */ + constexpr inline const char * const ContentStorageSystemMountName = "@SystemContent"; + constexpr inline const char * const ContentStorageUserMountName = "@UserContent"; + constexpr inline const char * const ContentStorageSdCardMountName = "@SdCardContent"; + + + /* Registered update partition. */ + constexpr inline const char * const RegisteredUpdatePartitionMountName = "@RegUpdate"; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_data.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_data.hpp new file mode 100644 index 00000000..d0cbd461 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_data.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> + +namespace ams::fs::impl { + + /* ACCURATE_TO_VERSION: Unknown */ + Result QueryMountDataCacheSize(size_t *out, ncm::DataId data_id, ncm::StorageId storage_id); + + Result MountData(const char *name, ncm::DataId data_id, ncm::StorageId storage_id); + Result MountData(const char *name, ncm::DataId data_id, ncm::StorageId storage_id, void *cache_buffer, size_t cache_size); + Result MountData(const char *name, ncm::DataId data_id, ncm::StorageId storage_id, void *cache_buffer, size_t cache_size, bool use_data_cache, bool use_path_cache); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_filesystem_proxy_type.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_filesystem_proxy_type.hpp new file mode 100644 index 00000000..473bf2b9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_filesystem_proxy_type.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fs::impl { + + /* ACCURATE_TO_VERSION: Unknown */ + enum FileSystemProxyType { + FileSystemProxyType_Code = 0, + FileSystemProxyType_Rom = 1, + FileSystemProxyType_Logo = 2, + FileSystemProxyType_Control = 3, + FileSystemProxyType_Manual = 4, + FileSystemProxyType_Meta = 5, + FileSystemProxyType_Data = 6, + FileSystemProxyType_Package = 7, + FileSystemProxyType_UpdatePartition = 8, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_fs_inline_context_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_fs_inline_context_utils.hpp new file mode 100644 index 00000000..bfd7cc76 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_fs_inline_context_utils.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/sf/sf_fs_inline_context.hpp> + +namespace ams::fs::impl { + + /* ACCURATE_TO_VERSION: Unknown */ + constexpr inline u8 TlsIoPriorityMask = 0x7; + constexpr inline u8 TlsIoRecursiveCallMask = 0x8; + + struct TlsIoValueForInheritance { + u8 _tls_value; + }; + + inline void SetCurrentRequestRecursive() { + os::ThreadType * const cur_thread = os::GetCurrentThread(); + sf::SetFsInlineContext(cur_thread, TlsIoRecursiveCallMask | sf::GetFsInlineContext(cur_thread)); + } + + inline bool IsCurrentRequestRecursive() { + return (sf::GetFsInlineContext(os::GetCurrentThread()) & TlsIoRecursiveCallMask) != 0; + } + + inline TlsIoValueForInheritance GetTlsIoValueForInheritance() { + return TlsIoValueForInheritance { sf::GetFsInlineContext(os::GetCurrentThread()) }; + } + + inline void SetTlsIoValueForInheritance(TlsIoValueForInheritance tls_io) { + sf::SetFsInlineContext(os::GetCurrentThread(), tls_io._tls_value); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_hash_generator_factory_selector.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_hash_generator_factory_selector.hpp new file mode 100644 index 00000000..dc119036 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_hash_generator_factory_selector.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fssystem/fssystem_i_hash_256_generator.hpp> + +namespace ams::fs::impl { + + /* ACCURATE_TO_VERSION: Unknown */ + fssystem::IHash256GeneratorFactorySelector *GetNcaHashGeneratorFactorySelector(); + fssystem::IHash256GeneratorFactorySelector *GetSaveDataHashGeneratorFactorySelector(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_newable.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_newable.hpp new file mode 100644 index 00000000..a97922cf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_newable.hpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_memory_management.hpp> + +namespace ams::fs::impl { + + /* ACCURATE_TO_VERSION: Unknown */ + class Newable { + public: + static ALWAYS_INLINE void *operator new(size_t size) noexcept { + return ::ams::fs::impl::Allocate(size); + } + + static ALWAYS_INLINE void *operator new(size_t size, Newable *placement) noexcept { + AMS_UNUSED(size); + return placement; + } + + static ALWAYS_INLINE void *operator new[](size_t size) noexcept { + return ::ams::fs::impl::Allocate(size); + } + + static ALWAYS_INLINE void operator delete(void *ptr, size_t size) noexcept { + return ::ams::fs::impl::Deallocate(ptr, size); + } + + static ALWAYS_INLINE void operator delete[](void *ptr, size_t size) noexcept { + return ::ams::fs::impl::Deallocate(ptr, size); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_priority_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_priority_utils.hpp new file mode 100644 index 00000000..82b7f666 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_priority_utils.hpp @@ -0,0 +1,71 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_priority.hpp> +#include <stratosphere/fs/impl/fs_fs_inline_context_utils.hpp> + +namespace ams::fs::impl { + + /* ACCURATE_TO_VERSION: Unknown */ + enum TlsIoPriority : u8 { + TlsIoPriority_Normal = 0, + TlsIoPriority_Realtime = 1, + TlsIoPriority_Low = 2, + TlsIoPriority_Background = 3, + }; + + #if defined(ATMOSPHERE_OS_HORIZON) + /* Ensure that TlsIo priority matches libnx priority. */ + static_assert(TlsIoPriority_Normal == static_cast<TlsIoPriority>(::FsPriority_Normal)); + static_assert(TlsIoPriority_Realtime == static_cast<TlsIoPriority>(::FsPriority_Realtime)); + static_assert(TlsIoPriority_Low == static_cast<TlsIoPriority>(::FsPriority_Low)); + static_assert(TlsIoPriority_Background == static_cast<TlsIoPriority>(::FsPriority_Background)); + #endif + + constexpr inline Result ConvertFsPriorityToTlsIoPriority(u8 *out, PriorityRaw priority) { + AMS_ASSERT(out != nullptr); + + switch (priority) { + case PriorityRaw_Normal: *out = TlsIoPriority_Normal; break; + case PriorityRaw_Realtime: *out = TlsIoPriority_Realtime; break; + case PriorityRaw_Low: *out = TlsIoPriority_Low; break; + case PriorityRaw_Background: *out = TlsIoPriority_Background; break; + default: R_THROW(fs::ResultInvalidArgument()); + } + + R_SUCCEED(); + } + + constexpr inline Result ConvertTlsIoPriorityToFsPriority(PriorityRaw *out, u8 tls_io) { + AMS_ASSERT(out != nullptr); + + switch (static_cast<TlsIoPriority>(tls_io)) { + case TlsIoPriority_Normal: *out = PriorityRaw_Normal; break; + case TlsIoPriority_Realtime: *out = PriorityRaw_Realtime; break; + case TlsIoPriority_Low: *out = PriorityRaw_Low; break; + case TlsIoPriority_Background: *out = PriorityRaw_Background; break; + default: R_THROW(fs::ResultInvalidArgument()); + } + + R_SUCCEED(); + } + + inline u8 GetTlsIoPriority(os::ThreadType *thread) { + return sf::GetFsInlineContext(thread) & TlsIoPriorityMask; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_result_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_result_utils.hpp new file mode 100644 index 00000000..2ef4fb25 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_result_utils.hpp @@ -0,0 +1,85 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fs::impl { + + /* ACCURATE_TO_VERSION: Unknown */ + bool IsAbortNeeded(Result result); + void LogErrorMessage(Result result, const char *function); + +} + +#define AMS_FS_R_CHECK_ABORT_IMPL(__RESULT__, __FORCE__) \ + ({ \ + if (::ams::fs::impl::IsAbortNeeded(__RESULT__) || (__FORCE__)) { \ + ::ams::fs::impl::LogErrorMessage(__RESULT__, AMS_CURRENT_FUNCTION_NAME); \ + R_ABORT_UNLESS(__RESULT__); \ + } \ + }) + +#define AMS_FS_R_TRY(__RESULT__) \ + ({ \ + const ::ams::Result __tmp_fs_result = (__RESULT__); \ + AMS_FS_R_CHECK_ABORT_IMPL(__tmp_fs_result, false); \ + R_TRY(__tmp_fs_result); \ + }) + +#define AMS_FS_R_ABORT_UNLESS(__RESULT__) \ + ({ \ + const ::ams::Result __tmp_fs_result = (__RESULT__); \ + AMS_FS_R_CHECK_ABORT_IMPL(__tmp_fs_result, true); \ + }) + +#define AMS_FS_ABORT_UNLESS_WITH_RESULT(__EXPR__, __RESULT__) \ + ({ \ + if (!(__EXPR__)) { \ + AMS_FS_R_ABORT_UNLESS((__RESULT__)); \ + } \ + }) + +#define AMS_FS_R_THROW(__RESULT__) \ + ({ \ + const ::ams::Result __tmp_fs_result = (__RESULT__); \ + AMS_FS_R_CHECK_ABORT_IMPL(__tmp_fs_result, false); \ + R_THROW(__tmp_fs_result); \ + }) + +#define AMS_FS_R_UNLESS(__EXPR__, __RESULT__) \ + ({ \ + if (!(__EXPR__)) { \ + AMS_FS_R_THROW((__RESULT__)); \ + } \ + }) + +#define AMS_FS_R_TRY_CATCH(__EXPR__) R_TRY_CATCH(__EXPR__) + +#define AMS_FS_R_CATCH(...) R_CATCH(__VA_ARGS__) + +#define AMS_FS_R_END_TRY_CATCH \ + else if (R_FAILED(R_CURRENT_RESULT)) { \ + AMS_FS_R_THROW(R_CURRENT_RESULT); \ + } \ + } \ + }) + +#define AMS_FS_R_END_TRY_CATCH_WITH_ABORT_UNLESS \ + else { \ + AMS_FS_R_ABORT_UNLESS(R_CURRENT_RESULT); \ + } \ + } \ + }) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_service_name.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_service_name.hpp new file mode 100644 index 00000000..3dccb28d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_service_name.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sm/sm_types.hpp> + +namespace ams::fs::impl { + + constexpr inline const sm::ServiceName FileSystemProxyServiceName = sm::ServiceName::Encode("fsp-srv"); + constexpr inline const sm::ServiceName ProgramRegistryServiceName = sm::ServiceName::Encode("fsp-pr"); + constexpr inline const sm::ServiceName FileSystemProxyForLoaderServiceName = sm::ServiceName::Encode("fsp-ldr"); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_storage_service_object_adapter.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_storage_service_object_adapter.hpp new file mode 100644 index 00000000..66d95124 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fs/impl/fs_storage_service_object_adapter.hpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_istorage.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> + +namespace ams::fs::impl { + + template<typename StorageInterface> + class StorageServiceObjectAdapter : public ::ams::fs::impl::Newable, public ::ams::fs::IStorage { + NON_COPYABLE(StorageServiceObjectAdapter); + NON_MOVEABLE(StorageServiceObjectAdapter); + private: + sf::SharedPointer<StorageInterface> m_x; + public: + explicit StorageServiceObjectAdapter(sf::SharedPointer<StorageInterface> &&o) : m_x(o) { /* ... */} + virtual ~StorageServiceObjectAdapter() { /* ... */ } + public: + virtual Result Read(s64 offset, void *buffer, size_t size) override final { + R_RETURN(m_x->Read(offset, sf::OutNonSecureBuffer(buffer, size), static_cast<s64>(size))); + } + + virtual Result GetSize(s64 *out) override final { + R_RETURN(m_x->GetSize(out)); + } + + virtual Result Flush() override final { + R_RETURN(m_x->Flush()); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override final { + R_RETURN(m_x->Write(offset, sf::InNonSecureBuffer(buffer, size), static_cast<s64>(size))); + } + + virtual Result SetSize(s64 size) override final { + R_RETURN(m_x->SetSize(size)); + } + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override final { + AMS_UNUSED(src, src_size); + switch (op_id) { + case OperationId::Invalidate: + { + fs::QueryRangeInfo dummy_range_info; + R_RETURN(m_x->OperateRange(std::addressof(dummy_range_info), static_cast<s32>(op_id), offset, size)); + } + case OperationId::QueryRange: + { + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize()); + + R_RETURN(m_x->OperateRange(reinterpret_cast<fs::QueryRangeInfo *>(dst), static_cast<s32>(op_id), offset, size)); + } + default: + { + R_THROW(fs::ResultUnsupportedOperateRangeForStorageServiceObjectAdapter()); + } + } + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv.hpp new file mode 100644 index 00000000..d5477f68 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/fssrv/sf/fssrv_sf_path.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_ifile.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_i_event_notifier.hpp> +#include <stratosphere/fssrv/impl/fssrv_impl_program_index_map_info_manager.hpp> +#include <stratosphere/fssrv/impl/fssrv_access_control.hpp> +#include <stratosphere/fssrv/fssrv_nca_crypto_configuration.hpp> +#include <stratosphere/fssrv/fssrv_memory_resource_from_standard_allocator.hpp> +#include <stratosphere/fssrv/fssrv_memory_resource_from_exp_heap.hpp> +#include <stratosphere/fssrv/fssrv_i_file_system_creator.hpp> +#include <stratosphere/fssrv/fscreator/fssrv_partition_file_system_creator.hpp> +#include <stratosphere/fssrv/fscreator/fssrv_rom_file_system_creator.hpp> +#include <stratosphere/fssrv/fscreator/fssrv_storage_on_nca_creator.hpp> +#include <stratosphere/fssrv/fscreator/fssrv_local_file_system_creator.hpp> +#include <stratosphere/fssrv/fscreator/fssrv_subdirectory_file_system_creator.hpp> +#include <stratosphere/fssrv/fscreator/fssrv_storage_on_nca_creator.hpp> +#include <stratosphere/fssrv/fssrv_file_system_proxy_server_session_resource_manager.hpp> +#include <stratosphere/fssrv/impl/fssrv_file_system_proxy_service_object.hpp> +#include <stratosphere/fssrv/fssrv_file_system_proxy_api.hpp> +#include <stratosphere/fssrv/fssrv_program_registry_impl.hpp> +#include <stratosphere/fssrv/fssrv_program_registry_service.hpp> +#include <stratosphere/fssrv/fssrv_nca_file_system_service_impl.hpp> +#include <stratosphere/fssrv/impl/fssrv_external_key_manager.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_local_file_system_creator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_local_file_system_creator.hpp new file mode 100644 index 00000000..0cdc82e1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_local_file_system_creator.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fssrv/fssrv_i_file_system_creator.hpp> + +namespace ams::fssrv::fscreator { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class LocalFileSystemCreator final : public ILocalFileSystemCreator { + NON_COPYABLE(LocalFileSystemCreator); + NON_MOVEABLE(LocalFileSystemCreator); + private: + bool m_is_development; + public: + explicit LocalFileSystemCreator(bool dev) : m_is_development(dev) { /* ... */ } + + virtual Result Create(std::shared_ptr<fs::fsa::IFileSystem> *out, const fs::Path &path, bool case_sensitive, bool ensure_root, Result on_path_not_found) override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_partition_file_system_creator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_partition_file_system_creator.hpp new file mode 100644 index 00000000..a8b20336 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_partition_file_system_creator.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fssrv/fssrv_i_file_system_creator.hpp> + +namespace ams::fssrv::fscreator { + + /* ACCURATE_TO_VERSION: Unknown */ + class PartitionFileSystemCreator : public IPartitionFileSystemCreator { + NON_COPYABLE(PartitionFileSystemCreator); + NON_MOVEABLE(PartitionFileSystemCreator); + public: + PartitionFileSystemCreator() { /* ... */ } + + virtual Result Create(std::shared_ptr<fs::fsa::IFileSystem> *out, std::shared_ptr<fs::IStorage> storage) override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_rom_file_system_creator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_rom_file_system_creator.hpp new file mode 100644 index 00000000..a1263585 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_rom_file_system_creator.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fssrv/fssrv_i_file_system_creator.hpp> + +namespace ams::fssrv::fscreator { + + /* ACCURATE_TO_VERSION: Unknown */ + class RomFileSystemCreator : public IRomFileSystemCreator { + NON_COPYABLE(RomFileSystemCreator); + NON_MOVEABLE(RomFileSystemCreator); + private: + MemoryResource *m_allocator; + public: + explicit RomFileSystemCreator(MemoryResource *mr) : m_allocator(mr) { /* ... */ } + + virtual Result Create(std::shared_ptr<fs::fsa::IFileSystem> *out, std::shared_ptr<fs::IStorage> storage) override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_storage_on_nca_creator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_storage_on_nca_creator.hpp new file mode 100644 index 00000000..a4bdb41e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_storage_on_nca_creator.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fssrv/fssrv_i_file_system_creator.hpp> +#include <stratosphere/fs/fs_i_buffer_manager.hpp> +#include <stratosphere/fssystem/fssystem_i_hash_256_generator.hpp> + +namespace ams::fssystem { + + struct NcaCryptoConfiguration; + struct NcaCompressionConfiguration; + +} + +namespace ams::fssrv::fscreator { + + class StorageOnNcaCreator : public IStorageOnNcaCreator { + NON_COPYABLE(StorageOnNcaCreator); + NON_MOVEABLE(StorageOnNcaCreator); + private: + MemoryResource *m_allocator; + const fssystem::NcaCryptoConfiguration &m_nca_crypto_cfg; + const fssystem::NcaCompressionConfiguration &m_nca_compression_cfg; + fs::IBufferManager * const m_buffer_manager; + fssystem::IHash256GeneratorFactorySelector * const m_hash_generator_factory_selector; + public: + explicit StorageOnNcaCreator(MemoryResource *mr, const fssystem::NcaCryptoConfiguration &cfg, const fssystem::NcaCompressionConfiguration &c_cfg, fs::IBufferManager *bm, fssystem::IHash256GeneratorFactorySelector *hgfs) + : m_allocator(mr), m_nca_crypto_cfg(cfg), m_nca_compression_cfg(c_cfg), m_buffer_manager(bm), m_hash_generator_factory_selector(hgfs) + { + /* ... */ + } + + virtual Result Create(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::IAsynchronousAccessSplitter> *out_splitter, fssystem::NcaFsHeaderReader *out_header_reader, std::shared_ptr<fssystem::NcaReader> nca_reader, s32 index) override; + virtual Result CreateWithPatch(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::IAsynchronousAccessSplitter> *out_splitter, fssystem::NcaFsHeaderReader *out_header_reader, std::shared_ptr<fssystem::NcaReader> original_nca_reader, std::shared_ptr<fssystem::NcaReader> current_nca_reader, s32 index) override; + virtual Result CreateNcaReader(std::shared_ptr<fssystem::NcaReader> *out, std::shared_ptr<fs::IStorage> storage) override; + + #if !defined(ATMOSPHERE_BOARD_NINTENDO_NX) + Result CreateWithContext(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::IAsynchronousAccessSplitter> *out_splitter, fssystem::NcaFsHeaderReader *out_header_reader, void *ctx, std::shared_ptr<fssystem::NcaReader> nca_reader, s32 index); + Result CreateWithPatchWithContext(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::IAsynchronousAccessSplitter> *out_splitter, fssystem::NcaFsHeaderReader *out_header_reader, void *ctx, std::shared_ptr<fssystem::NcaReader> original_nca_reader, std::shared_ptr<fssystem::NcaReader> current_nca_reader, s32 index); + + Result CreateByRawStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::IAsynchronousAccessSplitter> *out_splitter, const fssystem::NcaFsHeaderReader *header_reader, std::shared_ptr<fs::IStorage> raw_storage, void *ctx, std::shared_ptr<fssystem::NcaReader> nca_reader); + #endif + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_subdirectory_file_system_creator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_subdirectory_file_system_creator.hpp new file mode 100644 index 00000000..d6900b1e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_subdirectory_file_system_creator.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fssrv/fssrv_i_file_system_creator.hpp> + +namespace ams::fssrv::fscreator { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class SubDirectoryFileSystemCreator final : public ISubDirectoryFileSystemCreator { + NON_COPYABLE(SubDirectoryFileSystemCreator); + NON_MOVEABLE(SubDirectoryFileSystemCreator); + public: + explicit SubDirectoryFileSystemCreator() { /* ... */ } + + virtual Result Create(std::shared_ptr<fs::fsa::IFileSystem> *out, std::shared_ptr<fs::fsa::IFileSystem> base_fs, const fs::Path &path) override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_file_system_proxy_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_file_system_proxy_api.hpp new file mode 100644 index 00000000..af222eda --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_file_system_proxy_api.hpp @@ -0,0 +1,66 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fssrv::fscreator { + + struct FileSystemCreatorInterfaces; + +} + +namespace ams::fssrv { + + class BaseStorageServiceImpl; + class BaseFileSystemServiceImpl; + class NcaFileSystemServiceImpl; + class SaveDataFileSystemServiceImpl; + class AccessFailureManagementServiceImpl; + class TimeServiceImpl; + class StatusReportServiceImpl; + class ProgramRegistryServiceImpl; + class AccessLogServiceImpl; + class DebugConfigurationServiceImpl; + + /* ACCURATE_TO_VERSION: Unknown */ + struct FileSystemProxyConfiguration { + fscreator::FileSystemCreatorInterfaces *m_fs_creator_interfaces; + BaseStorageServiceImpl *m_base_storage_service_impl; + BaseFileSystemServiceImpl *m_base_file_system_service_impl; + NcaFileSystemServiceImpl *m_nca_file_system_service_impl; + SaveDataFileSystemServiceImpl *m_save_data_file_system_service_impl; + AccessFailureManagementServiceImpl *m_access_failure_management_service_impl; + TimeServiceImpl *m_time_service_impl; + StatusReportServiceImpl *m_status_report_service_impl; + ProgramRegistryServiceImpl *m_program_registry_service_impl; + AccessLogServiceImpl *m_access_log_service_impl; + DebugConfigurationServiceImpl *m_debug_configuration_service_impl; + }; + + struct InternalProgramIdRangeForSpeedEmulation { + u64 program_id_value_min; + u64 program_id_value_max; + }; + +} + +namespace ams::fssrv { + + void InitializeForFileSystemProxy(const FileSystemProxyConfiguration &config); + + void InitializeFileSystemProxyServer(int threads); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_file_system_proxy_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_file_system_proxy_impl.hpp new file mode 100644 index 00000000..7a2778a9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_file_system_proxy_impl.hpp @@ -0,0 +1,200 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_i_file_system_proxy.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_i_program_registry.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_i_file_system_proxy_for_loader.hpp> + +namespace ams::fssrv { + + namespace impl { + + class FileSystemProxyCoreImpl; + + } + + class NcaFileSystemService; + class SaveDataFileSystemService; + + /* ACCURATE_TO_VERSION: Unknown */ + class FileSystemProxyImpl { + NON_COPYABLE(FileSystemProxyImpl); + NON_MOVEABLE(FileSystemProxyImpl); + private: + impl::FileSystemProxyCoreImpl *m_impl; + std::shared_ptr<NcaFileSystemService> m_nca_service; + std::shared_ptr<SaveDataFileSystemService> m_save_data_service; + u64 m_process_id; + public: + FileSystemProxyImpl(); + ~FileSystemProxyImpl(); + + /* TODO */ + public: + /* fsp-srv */ + Result OpenFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, u32 type); + Result SetCurrentProcess(const ams::sf::ClientProcessId &client_pid); + Result OpenDataFileSystemByCurrentProcess(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out); + Result OpenFileSystemWithPatch(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, ncm::ProgramId program_id, u32 type); + Result OpenFileSystemWithIdObsolete(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, u64 program_id, u32 type); + Result OpenDataFileSystemByProgramId(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, ncm::ProgramId program_id); + Result OpenFileSystemWithId(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, fs::ContentAttributes attr, u64 program_id, u32 type); + Result OpenBisFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, u32 id); + Result OpenBisStorage(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, u32 id); + Result InvalidateBisCache(); + Result OpenHostFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path); + Result OpenSdCardFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out); + Result FormatSdCardFileSystem(); + Result DeleteSaveDataFileSystem(u64 save_data_id); + Result CreateSaveDataFileSystem(const fs::SaveDataAttribute &attribute, const fs::SaveDataCreationInfo &creation_info, const fs::SaveDataMetaInfo &meta_info); + Result CreateSaveDataFileSystemBySystemSaveDataId(const fs::SaveDataAttribute &attribute, const fs::SaveDataCreationInfo &creation_info); + Result RegisterSaveDataFileSystemAtomicDeletion(const ams::sf::InBuffer &save_data_ids); + Result DeleteSaveDataFileSystemBySaveDataSpaceId(u8 indexer_space_id, u64 save_data_id); + Result FormatSdCardDryRun(); + Result IsExFatSupported(ams::sf::Out<bool> out); + Result DeleteSaveDataFileSystemBySaveDataAttribute(u8 space_id, const fs::SaveDataAttribute &attribute); + Result OpenGameCardStorage(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, u32 handle, u32 partition); + Result OpenGameCardFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u32 handle, u32 partition); + Result ExtendSaveDataFileSystem(u8 space_id, u64 save_data_id, s64 available_size, s64 journal_size); + Result DeleteCacheStorage(u16 index); + Result GetCacheStorageSize(ams::sf::Out<s64> out_size, ams::sf::Out<s64> out_journal_size, u16 index); + Result CreateSaveDataFileSystemWithHashSalt(const fs::SaveDataAttribute &attribute, const fs::SaveDataCreationInfo &creation_info, const fs::SaveDataMetaInfo &meta_info, const fs::HashSalt &salt); + Result OpenHostFileSystemWithOption(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, u32 option); + Result OpenSaveDataFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u8 space_id, const fs::SaveDataAttribute &attribute); + Result OpenSaveDataFileSystemBySystemSaveDataId(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u8 space_id, const fs::SaveDataAttribute &attribute); + Result OpenReadOnlySaveDataFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u8 space_id, const fs::SaveDataAttribute &attribute); + Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(const ams::sf::OutBuffer &buffer, u8 space_id, u64 save_data_id); + Result ReadSaveDataFileSystemExtraData(const ams::sf::OutBuffer &buffer, u64 save_data_id); + Result WriteSaveDataFileSystemExtraData(u64 save_data_id, u8 space_id, const ams::sf::InBuffer &buffer); + /* ... */ + Result OpenImageDirectoryFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u32 id); + /* ... */ + Result OpenContentStorageFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u32 id); + /* ... */ + Result OpenDataStorageByCurrentProcess(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out); + Result OpenDataStorageByProgramId(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, ncm::ProgramId program_id); + Result OpenDataStorageByDataId(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, ncm::DataId data_id, u8 storage_id); + Result OpenPatchDataStorageByCurrentProcess(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out); + Result OpenDataFileSystemWithProgramIndex(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u8 index); + Result OpenDataStorageWithProgramIndex(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, u8 index); + Result OpenDataStorageByPathObsolete(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, const fssrv::sf::FspPath &path, u32 type); + Result OpenDataStorageByPath(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, const fssrv::sf::FspPath &path, fs::ContentAttributes attr, u32 type); + Result OpenDeviceOperator(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IDeviceOperator>> out); + Result OpenSdCardDetectionEventNotifier(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IEventNotifier>> out); + Result OpenGameCardDetectionEventNotifier(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IEventNotifier>> out); + Result OpenSystemDataUpdateEventNotifier(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IEventNotifier>> out); + Result NotifySystemDataUpdateEvent(); + /* ... */ + Result SetCurrentPosixTime(s64 posix_time); + /* ... */ + Result GetRightsId(ams::sf::Out<fs::RightsId> out, ncm::ProgramId program_id, ncm::StorageId storage_id); + Result RegisterExternalKey(const fs::RightsId &rights_id, const spl::AccessKey &access_key); + Result UnregisterAllExternalKey(); + Result GetProgramId(ams::sf::Out<ncm::ProgramId> out, const fssrv::sf::FspPath &path, fs::ContentAttributes attr); + Result GetRightsIdByPath(ams::sf::Out<fs::RightsId> out, const fssrv::sf::FspPath &path); + Result GetRightsIdAndKeyGenerationByPathObsolete(ams::sf::Out<fs::RightsId> out, ams::sf::Out<u8> out_key_generation, const fssrv::sf::FspPath &path); + Result GetRightsIdAndKeyGenerationByPath(ams::sf::Out<fs::RightsId> out, ams::sf::Out<u8> out_key_generation, const fssrv::sf::FspPath &path, fs::ContentAttributes attr); + Result SetCurrentPosixTimeWithTimeDifference(s64 posix_time, s32 time_difference); + Result GetFreeSpaceSizeForSaveData(ams::sf::Out<s64> out, u8 space_id); + Result VerifySaveDataFileSystemBySaveDataSpaceId(); + Result CorruptSaveDataFileSystemBySaveDataSpaceId(); + Result QuerySaveDataInternalStorageTotalSize(); + Result GetSaveDataCommitId(); + Result UnregisterExternalKey(const fs::RightsId &rights_id); + Result SetSdCardEncryptionSeed(const fs::EncryptionSeed &seed); + Result SetSdCardAccessibility(bool accessible); + Result IsSdCardAccessible(ams::sf::Out<bool> out); + Result IsSignedSystemPartitionOnSdCardValid(ams::sf::Out<bool> out); + Result OpenAccessFailureDetectionEventNotifier(); + /* ... */ + Result GetAndClearErrorInfo(ams::sf::Out<fs::FileSystemProxyErrorInfo> out); + Result RegisterProgramIndexMapInfo(const ams::sf::InBuffer &buffer, s32 count); + Result SetBisRootForHost(u32 id, const fssrv::sf::FspPath &path); + Result SetSaveDataSize(s64 size, s64 journal_size); + Result SetSaveDataRootPath(const fssrv::sf::FspPath &path); + Result DisableAutoSaveDataCreation(); + Result SetGlobalAccessLogMode(u32 mode); + Result GetGlobalAccessLogMode(ams::sf::Out<u32> out); + Result OutputAccessLogToSdCard(const ams::sf::InBuffer &buf); + Result RegisterUpdatePartition(); + Result OpenRegisteredUpdatePartition(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out); + Result GetAndClearMemoryReportInfo(ams::sf::Out<fs::MemoryReportInfo> out); + /* ... */ + Result GetProgramIndexForAccessLog(ams::sf::Out<u32> out_idx, ams::sf::Out<u32> out_count); + Result GetFsStackUsage(ams::sf::Out<u32> out, u32 type); + Result UnsetSaveDataRootPath(); + Result OutputMultiProgramTagAccessLog(); + Result FlushAccessLogOnSdCard(); + Result OutputApplicationInfoAccessLog(); + Result RegisterDebugConfiguration(u32 key, s64 value); + Result UnregisterDebugConfiguration(u32 key); + Result OverrideSaveDataTransferTokenSignVerificationKey(const ams::sf::InBuffer &buf); + Result CorruptSaveDataFileSystemByOffset(u8 space_id, u64 save_data_id, s64 offset); + /* ... */ + public: + /* fsp-ldr */ + Result OpenCodeFileSystemDeprecated(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, const fssrv::sf::Path &path, ncm::ProgramId program_id); + Result OpenCodeFileSystemDeprecated2(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, ams::sf::Out<fs::CodeVerificationData> out_verif, const fssrv::sf::Path &path, ncm::ProgramId program_id); + Result OpenCodeFileSystemDeprecated3(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, ams::sf::Out<fs::CodeVerificationData> out_verif, const fssrv::sf::Path &path, fs::ContentAttributes attr, ncm::ProgramId program_id); + Result OpenCodeFileSystemDeprecated4(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, const ams::sf::OutBuffer &out_verif, const fssrv::sf::Path &path, fs::ContentAttributes attr, ncm::ProgramId program_id); + Result OpenCodeFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, const ams::sf::OutBuffer &out_verif, fs::ContentAttributes attr, ncm::ProgramId program_id, ncm::StorageId storage_id); + Result IsArchivedProgram(ams::sf::Out<bool> out, u64 process_id); + }; + static_assert(sf::IsIFileSystemProxy<FileSystemProxyImpl>); + static_assert(sf::IsIFileSystemProxyForLoader<FileSystemProxyImpl>); + + /* ACCURATE_TO_VERSION: Unknown */ + class InvalidFileSystemProxyImplForLoader { + public: + Result OpenCodeFileSystemDeprecated(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, const fssrv::sf::Path &path, ncm::ProgramId program_id) { + AMS_UNUSED(out_fs, path, program_id); + R_THROW(fs::ResultPortAcceptableCountLimited()); + } + + Result OpenCodeFileSystemDeprecated2(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, ams::sf::Out<fs::CodeVerificationData> out_verif, const fssrv::sf::Path &path, ncm::ProgramId program_id) { + AMS_UNUSED(out_fs, out_verif, path, program_id); + R_THROW(fs::ResultPortAcceptableCountLimited()); + } + + Result OpenCodeFileSystemDeprecated3(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, ams::sf::Out<fs::CodeVerificationData> out_verif, const fssrv::sf::Path &path, fs::ContentAttributes attr, ncm::ProgramId program_id) { + AMS_UNUSED(out_fs, out_verif, path, attr, program_id); + R_THROW(fs::ResultPortAcceptableCountLimited()); + } + + Result OpenCodeFileSystemDeprecated4(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, const ams::sf::OutBuffer &out_verif, const fssrv::sf::Path &path, fs::ContentAttributes attr, ncm::ProgramId program_id) { + AMS_UNUSED(out_fs, out_verif, path, attr, program_id); + R_THROW(fs::ResultPortAcceptableCountLimited()); + } + + Result OpenCodeFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, const ams::sf::OutBuffer &out_verif, fs::ContentAttributes attr, ncm::ProgramId program_id, ncm::StorageId storage_id) { + AMS_UNUSED(out_fs, out_verif, attr, program_id, storage_id); + R_THROW(fs::ResultPortAcceptableCountLimited()); + } + + Result IsArchivedProgram(ams::sf::Out<bool> out, u64 process_id) { + AMS_UNUSED(out, process_id); + R_THROW(fs::ResultPortAcceptableCountLimited()); + } + + Result SetCurrentProcess(const ams::sf::ClientProcessId &client_pid) { + AMS_UNUSED(client_pid); + R_THROW(fs::ResultPortAcceptableCountLimited()); + } + }; + static_assert(sf::IsIFileSystemProxyForLoader<FileSystemProxyImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_file_system_proxy_server_session_resource_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_file_system_proxy_server_session_resource_manager.hpp new file mode 100644 index 00000000..76b5cac3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_file_system_proxy_server_session_resource_manager.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fssrv { + + enum class FileSystemProxyServerSessionType : s32 { + Any = 0, + Realtime = 1, + Background = 2, + Other = 3, + }; + + constexpr inline auto FileSystemProxyServerActiveSessionCount = 5; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_i_file_system_creator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_i_file_system_creator.hpp new file mode 100644 index 00000000..467eb43f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_i_file_system_creator.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> + +namespace ams::fs { + + class IStorage; + enum class BisPartitionId; + + class Path; + + namespace fsa { + + class IFileSystem; + + } + +} + +namespace ams::fssystem { + + class NcaReader; + class NcaFsHeaderReader; + + class IAsynchronousAccessSplitter; + + namespace save { + + /* TODO */ + + } + +} + +namespace ams::fssrv::fscreator { + + /* ACCURATE_TO_VERSION: Unknown */ + class IRomFileSystemCreator { + public: + virtual ~IRomFileSystemCreator() { /* ... */ } + virtual Result Create(std::shared_ptr<fs::fsa::IFileSystem> *out, std::shared_ptr<fs::IStorage> storage) = 0; + }; + + /* ACCURATE_TO_VERSION: Unknown */ + class IPartitionFileSystemCreator { + public: + virtual ~IPartitionFileSystemCreator() { /* ... */ } + virtual Result Create(std::shared_ptr<fs::fsa::IFileSystem> *out, std::shared_ptr<fs::IStorage> storage) = 0; + }; + + /* ACCURATE_TO_VERSION: Unknown */ + class IStorageOnNcaCreator { + public: + virtual ~IStorageOnNcaCreator() { /* ... */ } + virtual Result Create(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::IAsynchronousAccessSplitter> *out_splitter, fssystem::NcaFsHeaderReader *out_header_reader, std::shared_ptr<fssystem::NcaReader> nca_reader, s32 index) = 0; + virtual Result CreateWithPatch(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::IAsynchronousAccessSplitter> *out_splitter, fssystem::NcaFsHeaderReader *out_header_reader, std::shared_ptr<fssystem::NcaReader> original_nca_reader, std::shared_ptr<fssystem::NcaReader> current_nca_reader, s32 index) = 0; + virtual Result CreateNcaReader(std::shared_ptr<fssystem::NcaReader> *out, std::shared_ptr<fs::IStorage> storage) = 0; + }; + + /* ACCURATE_TO_VERSION: Unknown */ + class ILocalFileSystemCreator { + public: + virtual Result Create(std::shared_ptr<fs::fsa::IFileSystem> *out, const fs::Path &path, bool case_sensitive, bool ensure_root, Result on_path_not_found) = 0; + public: + Result Create(std::shared_ptr<fs::fsa::IFileSystem> *out, const fs::Path &path, bool case_sensitive) { + R_RETURN(this->Create(out, path, case_sensitive, false, ResultSuccess())); + } + }; + + /* ACCURATE_TO_VERSION: Unknown */ + class ISubDirectoryFileSystemCreator { + public: + virtual Result Create(std::shared_ptr<fs::fsa::IFileSystem> *out, std::shared_ptr<fs::fsa::IFileSystem> base_fs, const fs::Path &path) = 0; + }; + + /* ACCURATE_TO_VERSION: Unknown */ + struct FileSystemCreatorInterfaces { + ILocalFileSystemCreator *local_fs_creator; + ISubDirectoryFileSystemCreator *subdir_fs_creator; + /* TODO: These don't exist any more, and should be refactored out. */ + IRomFileSystemCreator *rom_fs_creator; + IPartitionFileSystemCreator *partition_fs_creator; + IStorageOnNcaCreator *storage_on_nca_creator; + /* TODO: More creators. */ + }; + static_assert(util::is_pod<FileSystemCreatorInterfaces>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_interface_adapters.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_interface_adapters.hpp new file mode 100644 index 00000000..3f94f0ed --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_interface_adapters.hpp @@ -0,0 +1,18 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fssrv/interface_adapters/fssrv_storage_interface_adapter.hpp> +#include <stratosphere/fssrv/interface_adapters/fssrv_filesystem_interface_adapter.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_memory_resource_from_exp_heap.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_memory_resource_from_exp_heap.hpp new file mode 100644 index 00000000..99eaed8b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_memory_resource_from_exp_heap.hpp @@ -0,0 +1,79 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/lmem/lmem_exp_heap.hpp> + +namespace ams::fssrv { + + /* ACCURATE_TO_VERSION: Unknown */ + class MemoryResourceFromExpHeap : public ams::MemoryResource { + private: + lmem::HeapHandle m_heap_handle; + public: + constexpr explicit MemoryResourceFromExpHeap(lmem::HeapHandle handle) : m_heap_handle(handle) { /* ... */ } + protected: + virtual void *AllocateImpl(size_t size, size_t align) override { + return lmem::AllocateFromExpHeap(m_heap_handle, size, static_cast<s32>(align)); + } + + virtual void DeallocateImpl(void *p, size_t size, size_t align) override { + AMS_UNUSED(size, align); + return lmem::FreeToExpHeap(m_heap_handle, p); + } + + virtual bool IsEqualImpl(const MemoryResource &rhs) const override { + AMS_UNUSED(rhs); + return false; + } + }; + + /* ACCURATE_TO_VERSION: Unknown */ + class PeakCheckableMemoryResourceFromExpHeap : public ams::MemoryResource { + private: + lmem::HeapHandle m_heap_handle; + os::SdkMutex m_mutex; + size_t m_peak_free_size; + size_t m_current_free_size; + public: + constexpr explicit PeakCheckableMemoryResourceFromExpHeap(size_t heap_size) : m_heap_handle(nullptr), m_mutex(), m_peak_free_size(heap_size), m_current_free_size(heap_size) { /* ... */ } + + void SetHeapHandle(lmem::HeapHandle handle) { + m_heap_handle = handle; + } + + size_t GetPeakFreeSize() const { return m_peak_free_size; } + size_t GetCurrentFreeSize() const { return m_current_free_size; } + + void ClearPeak() { m_peak_free_size = m_current_free_size; } + + std::scoped_lock<os::SdkMutex> GetScopedLock() { + return std::scoped_lock(m_mutex); + } + + void OnAllocate(void *p, size_t size); + void OnDeallocate(void *p, size_t size); + protected: + virtual void *AllocateImpl(size_t size, size_t align) override; + virtual void DeallocateImpl(void *p, size_t size, size_t align) override; + virtual bool IsEqualImpl(const MemoryResource &rhs) const override { + AMS_UNUSED(rhs); + return false; + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_memory_resource_from_standard_allocator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_memory_resource_from_standard_allocator.hpp new file mode 100644 index 00000000..f5cdc875 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_memory_resource_from_standard_allocator.hpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> + +namespace ams::mem { + + class StandardAllocator; + +} + +namespace ams::fssrv { + + /* ACCURATE_TO_VERSION: Unknown */ + class MemoryResourceFromStandardAllocator : public ams::MemoryResource { + private: + mem::StandardAllocator *m_allocator; + os::SdkMutex m_mutex; + size_t m_peak_free_size; + size_t m_current_free_size; + size_t m_peak_allocated_size; + public: + explicit MemoryResourceFromStandardAllocator(mem::StandardAllocator *allocator); + constexpr virtual ~MemoryResourceFromStandardAllocator() = default; + public: + size_t GetPeakFreeSize() const { return m_peak_free_size; } + size_t GetCurrentFreeSize() const { return m_current_free_size; } + size_t GetPeakAllocatedSize() const { return m_peak_allocated_size; } + + void ClearPeak(); + protected: + virtual void *AllocateImpl(size_t size, size_t align) override; + virtual void DeallocateImpl(void *p, size_t size, size_t align) override; + virtual bool IsEqualImpl(const MemoryResource &rhs) const override { + AMS_UNUSED(rhs); + return false; + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_nca_crypto_configuration.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_nca_crypto_configuration.hpp new file mode 100644 index 00000000..26da821d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_nca_crypto_configuration.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fssystem/fssystem_nca_file_system_driver.hpp> + +namespace ams::fssrv { + + const ::ams::fssystem::NcaCryptoConfiguration *GetDefaultNcaCryptoConfiguration(bool prod); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_nca_file_system_service_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_nca_file_system_service_impl.hpp new file mode 100644 index 00000000..3e74a07b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_nca_file_system_service_impl.hpp @@ -0,0 +1,89 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fssystem/fssystem_pimpl.hpp> +#include <stratosphere/fssrv/fssrv_file_system_proxy_api.hpp> + +namespace ams::fssrv { + + class BaseFileSystemServiceImpl; + class ProgramRegistryServiceImpl; + class AccessFailureManagementServiceImpl; + + + namespace fscreator { + + class LocalFileSystemCreator; + class TargetManagerFileSystemCreator; + class PartitionFileSystemCreator; + class IRomFileSystemCreator; + class StorageOnNcaCreator; + class SubDirectoryFileSystemCreator; + class EncryptedFileSystemCreator; + class INspRootFileSystemCreator; + + } + + namespace impl { + + class UpdatePartitionPath; + class ExternalKeyManager; + class LocationResolverSet; + class SystemDataUpdateEventManager; + + } + + /* ACCURATE_TO_VERSION: Unknown */ + class NcaFileSystemServiceImpl { + public: + struct Configuration { + BaseFileSystemServiceImpl *base_fs_service; + fscreator::LocalFileSystemCreator *local_fs_creator; + fscreator::TargetManagerFileSystemCreator *tm_fs_creator; + fscreator::PartitionFileSystemCreator *partition_fs_creator; + fscreator::IRomFileSystemCreator *rom_fs_creator; + fscreator::StorageOnNcaCreator *storage_on_nca_creator; + fscreator::SubDirectoryFileSystemCreator *subdir_fs_creator; + fscreator::EncryptedFileSystemCreator *encrypted_fs_creator; + fscreator::INspRootFileSystemCreator *nsp_root_fs_creator; + ProgramRegistryServiceImpl *program_registry_service; + AccessFailureManagementServiceImpl *access_failure_management_service; + InternalProgramIdRangeForSpeedEmulation program_id_range_for_speed_emulation; + }; + private: + struct MountInfo { + bool is_game_card; + u32 gc_handle; + bool is_host_fs; + bool can_mount_nca; + }; + private: + Configuration m_config; + fssystem::Pimpl<impl::UpdatePartitionPath, 0x350> m_update_partition_path; + fssystem::Pimpl<impl::ExternalKeyManager, 0x50> m_external_key_manager; + fssystem::Pimpl<impl::UpdatePartitionPath, 0xA8> m_location_resolver_set; + fssystem::Pimpl<impl::SystemDataUpdateEventManager, 0x48> m_system_data_update_event_manager; + fs::EncryptionSeed m_encryption_seed; + int m_romfs_remount_for_data_corruption_count; + int m_romfs_unrecoverable_data_corruption_by_remount_count; + int m_romfs_recovered_by_invalidate_cache_count; + os::SdkMutex m_romfs_count_mutex; + public: + NcaFileSystemServiceImpl(const Configuration &cfg) : m_config(cfg) { /* ... */ } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_program_registry_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_program_registry_impl.hpp new file mode 100644 index 00000000..a5d209cf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_program_registry_impl.hpp @@ -0,0 +1,74 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_i_program_registry.hpp> + +namespace ams::fssrv { + + class ProgramRegistryServiceImpl; + + namespace impl { + + class ProgramInfo; + + } + + /* ACCURATE_TO_VERSION: Unknown */ + class ProgramRegistryImpl { + NON_COPYABLE(ProgramRegistryImpl); + NON_MOVEABLE(ProgramRegistryImpl); + private: + u64 m_process_id; + public: + ProgramRegistryImpl(); + ~ProgramRegistryImpl(); + public: + static void Initialize(ProgramRegistryServiceImpl *service); + public: + Result RegisterProgram(u64 process_id, u64 program_id, u8 storage_id, const ams::sf::InBuffer &data, s64 data_size, const ams::sf::InBuffer &desc, s64 desc_size); + Result UnregisterProgram(u64 process_id); + Result SetCurrentProcess(const ams::sf::ClientProcessId &client_pid); + Result SetEnabledProgramVerification(bool en); + }; + static_assert(sf::IsIProgramRegistry<ProgramRegistryImpl>); + + /* ACCURATE_TO_VERSION: Unknown */ + class InvalidProgramRegistryImpl { + public: + Result RegisterProgram(u64 process_id, u64 program_id, u8 storage_id, const ams::sf::InBuffer &data, s64 data_size, const ams::sf::InBuffer &desc, s64 desc_size) { + AMS_UNUSED(process_id, program_id, storage_id, data, data_size, desc, desc_size); + R_THROW(fs::ResultPortAcceptableCountLimited()); + } + + Result UnregisterProgram(u64 process_id) { + AMS_UNUSED(process_id); + R_THROW(fs::ResultPortAcceptableCountLimited()); + } + + Result SetCurrentProcess(const ams::sf::ClientProcessId &client_pid) { + AMS_UNUSED(client_pid); + R_THROW(fs::ResultPortAcceptableCountLimited()); + } + + Result SetEnabledProgramVerification(bool en) { + AMS_UNUSED(en); + R_THROW(fs::ResultPortAcceptableCountLimited()); + } + }; + static_assert(sf::IsIProgramRegistry<InvalidProgramRegistryImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_program_registry_service.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_program_registry_service.hpp new file mode 100644 index 00000000..ae8ca684 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_program_registry_service.hpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fssystem/fssystem_pimpl.hpp> + +namespace ams::fssrv { + + namespace impl { + + class ProgramInfo; + class ProgramRegistryManager; + class ProgramIndexMapInfoManager; + + } + + /* ACCURATE_TO_VERSION: Unknown */ + class ProgramRegistryServiceImpl { + public: + struct Configuration { + /* ... */ + }; + private: + Configuration m_config; + fssystem::Pimpl<impl::ProgramRegistryManager, 0x40> m_registry_manager; + fssystem::Pimpl<impl::ProgramIndexMapInfoManager, 0x50> m_index_map_info_manager; + public: + ProgramRegistryServiceImpl(const Configuration &cfg) : m_config(cfg) { /* ... */ } + + Result RegisterProgramInfo(u64 process_id, u64 program_id, u8 storage_id, const void *data, s64 data_size, const void *desc, s64 desc_size); + Result UnregisterProgramInfo(u64 process_id); + + Result ResetProgramIndexMapInfo(const fs::ProgramIndexMapInfo *infos, int count); + + Result GetProgramInfo(std::shared_ptr<impl::ProgramInfo> *out, u64 process_id); + Result GetProgramInfoByProgramId(std::shared_ptr<impl::ProgramInfo> *out, u64 program_id); + + size_t GetProgramIndexMapInfoCount(); + util::optional<fs::ProgramIndexMapInfo> GetProgramIndexMapInfo(const ncm::ProgramId &program_id); + + ncm::ProgramId GetProgramIdByIndex(const ncm::ProgramId &program_id, u8 index); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/impl/fssrv_access_control.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/impl/fssrv_access_control.hpp new file mode 100644 index 00000000..2e6a4735 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/impl/fssrv_access_control.hpp @@ -0,0 +1,253 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> +#include <stratosphere/fssrv/impl/fssrv_access_control_bits.hpp> + +namespace ams::fssrv { + + bool IsDebugFlagEnabled(); + void SetDebugFlagEnabled(bool en); + +} + +namespace ams::fssrv::impl { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + struct Accessibility { + u8 value; + + constexpr bool CanRead() const { return value & (1 << 0); } + constexpr bool CanWrite() const { return value & (1 << 1); } + + static constexpr Accessibility MakeAccessibility(bool read, bool write) { + return { static_cast<u8>(read * (1 << 0) + write * (1 << 1)) }; + } + }; + static_assert(std::is_trivial<Accessibility>::value); + + class ContentOwnerInfo : public util::IntrusiveListBaseNode<ContentOwnerInfo>, public ::ams::fs::impl::Newable { + private: + u64 m_id; + public: + ContentOwnerInfo(u64 id) : m_id(id) { /* ... */ } + + u64 GetId() const { return m_id; } + }; + + class SaveDataOwnerInfo : public util::IntrusiveListBaseNode<SaveDataOwnerInfo>, public ::ams::fs::impl::Newable { + private: + u64 m_id; + Accessibility m_accessibility; + public: + SaveDataOwnerInfo(u64 id, Accessibility access) : m_id(id), m_accessibility(access) { /* ... */ } + + u64 GetId() const { return m_id; } + Accessibility GetAccessibility() const { return m_accessibility; } + }; + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class AccessControl { + public: + enum class AccessibilityType : u32 { + MountLogo, + MountContentMeta, + MountContentControl, + MountContentManual, + MountContentData, + MountApplicationPackage, + MountSaveDataStorage, + MountContentStorage, + MountImageAndVideoStorage, + MountCloudBackupWorkStorage, + MountCustomStorage, + MountBisCalibrationFile, + MountBisSafeMode, + MountBisUser, + MountBisSystem, + MountBisSystemProperEncryption, + MountBisSystemProperPartition, + MountSdCard, + MountGameCard, + MountDeviceSaveData, + MountSystemSaveData, + MountOthersSaveData, + MountOthersSystemSaveData, + OpenBisPartitionBootPartition1Root, + OpenBisPartitionBootPartition2Root, + OpenBisPartitionUserDataRoot, + OpenBisPartitionBootConfigAndPackage2Part1, + OpenBisPartitionBootConfigAndPackage2Part2, + OpenBisPartitionBootConfigAndPackage2Part3, + OpenBisPartitionBootConfigAndPackage2Part4, + OpenBisPartitionBootConfigAndPackage2Part5, + OpenBisPartitionBootConfigAndPackage2Part6, + OpenBisPartitionCalibrationBinary, + OpenBisPartitionCalibrationFile, + OpenBisPartitionSafeMode, + OpenBisPartitionUser, + OpenBisPartitionSystem, + OpenBisPartitionSystemProperEncryption, + OpenBisPartitionSystemProperPartition, + OpenSdCardStorage, + OpenGameCardStorage, + MountSystemDataPrivate, + MountHost, + MountRegisteredUpdatePartition, + MountSaveDataInternalStorage, + MountTemporaryDirectory, + MountAllBaseFileSystem, + NotMount, + + Count, + }; + + enum class OperationType : u32 { + InvalidateBisCache, + EraseMmc, + GetGameCardDeviceCertificate, + GetGameCardIdSet, + FinalizeGameCardDriver, + GetGameCardAsicInfo, + CreateSaveData, + DeleteSaveData, + CreateSystemSaveData, + CreateOthersSystemSaveData, + DeleteSystemSaveData, + OpenSaveDataInfoReader, + OpenSaveDataInfoReaderForSystem, + OpenSaveDataInfoReaderForInternal, + OpenSaveDataMetaFile, + SetCurrentPosixTime, + ReadSaveDataFileSystemExtraData, + SetGlobalAccessLogMode, + SetSpeedEmulationMode, + Debug, + FillBis, + CorruptSaveData, + CorruptSystemSaveData, + VerifySaveData, + DebugSaveData, + FormatSdCard, + GetRightsId, + RegisterExternalKey, + SetEncryptionSeed, + WriteSaveDataFileSystemExtraDataTimeStamp, + WriteSaveDataFileSystemExtraDataFlags, + WriteSaveDataFileSystemExtraDataCommitId, + WriteSaveDataFileSystemExtraDataAll, + ExtendSaveData, + ExtendSystemSaveData, + ExtendOthersSystemSaveData, + RegisterUpdatePartition, + OpenSaveDataTransferManager, + OpenSaveDataTransferManagerVersion2, + OpenSaveDataTransferManagerForSaveDataRepair, + OpenSaveDataTransferManagerForSaveDataRepairTool, + OpenSaveDataTransferProhibiter, + OpenSaveDataMover, + OpenBisWiper, + ListAccessibleSaveDataOwnerId, + ControlMmcPatrol, + OverrideSaveDataTransferTokenSignVerificationKey, + OpenSdCardDetectionEventNotifier, + OpenGameCardDetectionEventNotifier, + OpenSystemDataUpdateEventNotifier, + NotifySystemDataUpdateEvent, + OpenAccessFailureDetectionEventNotifier, + GetAccessFailureDetectionEvent, + IsAccessFailureDetected, + ResolveAccessFailure, + AbandonAccessFailure, + QuerySaveDataInternalStorageTotalSize, + GetSaveDataCommitId, + SetSdCardAccessibility, + SimulateDevice, + CreateSaveDataWithHashSalt, + RegisterProgramIndexMapInfo, + ChallengeCardExistence, + CreateOwnSaveData, + DeleteOwnSaveData, + ReadOwnSaveDataFileSystemExtraData, + ExtendOwnSaveData, + OpenOwnSaveDataTransferProhibiter, + FindOwnSaveDataWithFilter, + OpenSaveDataTransferManagerForRepair, + SetDebugConfiguration, + OpenDataStorageByPath, + + Count, + }; + + AMS_PRAGMA_BEGIN_PACK(4) + struct AccessControlDataHeader { + u8 version; + u8 reserved[3]; + u64 flag_bits; + u32 content_owner_infos_offset; + u32 content_owner_infos_size; + u32 save_data_owner_infos_offset; + u32 save_data_owner_infos_size; + }; + + struct AccessControlDescriptor { + u8 version; + u8 content_owner_id_count; + u8 save_data_owner_id_count; + u8 reserved; + u64 flag_bits; + u64 content_owner_id_min; + u64 content_owner_id_max; + u64 save_data_owner_id_min; + u64 save_data_owner_id_max; + /* ... */ + }; + AMS_PRAGMA_END_PACK() + + static_assert(util::is_pod<AccessControlDataHeader>::value); + static_assert(util::is_pod<AccessControlDescriptor>::value); + + static constexpr u64 AllFlagBitsMask = ~static_cast<u64>(0); + static constexpr u64 DebugFlagDisableMask = AllFlagBitsMask & ~util::ToUnderlying(AccessControlBits::Bits::Debug); + private: + using ContentOwnerInfoList = util::IntrusiveListBaseTraits<ContentOwnerInfo>::ListType; + using SaveDataOwnerInfoList = util::IntrusiveListBaseTraits<SaveDataOwnerInfo>::ListType; + private: + util::optional<AccessControlBits> m_flag_bits; + ContentOwnerInfoList m_content_owner_infos; + SaveDataOwnerInfoList m_save_data_owner_infos; + public: + AccessControl(const void *data, s64 data_size, const void *desc, s64 desc_size); + AccessControl(const void *data, s64 data_size, const void *desc, s64 desc_size, u64 flag_mask); + ~AccessControl(); + public: + bool HasContentOwnerId(u64 owner_id) const; + Accessibility GetAccessibilitySaveDataOwnedBy(u64 owner_id) const; + + void ListContentOwnerId(s32 *out_count, u64 *out_owner_ids, s32 offset, s32 count) const; + void ListSaveDataOwnedId(s32 *out_count, ncm::ApplicationId *out_owner_ids, s32 offset, s32 count) const; + + Accessibility GetAccessibilityFor(AccessibilityType type) const; + bool CanCall(OperationType type) const; + public: + u64 GetRawFlagBits() const { + return m_flag_bits.value().GetValue(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/impl/fssrv_access_control_bits.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/impl/fssrv_access_control_bits.hpp new file mode 100644 index 00000000..7f6b8068 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/impl/fssrv_access_control_bits.hpp @@ -0,0 +1,254 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> +#include <stratosphere/fssrv/impl/fssrv_access_control_bits.hpp> + +namespace ams::fssrv::impl { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + #define AMS_FSSRV_FOR_EACH_ACCESS_CONTROL_CAPABILITY(HANDLER, _NS_) \ + HANDLER(CanAbandonAccessFailure, _NS_::AccessFailureResolution) \ + HANDLER(CanChallengeCardExistence, _NS_::GameCard) \ + HANDLER(CanControlMmcPatrol, _NS_::None) \ + HANDLER(CanCorruptSaveData, _NS_::Debug, _NS_::CorruptSaveData) \ + HANDLER(CanCorruptSystemSaveData, _NS_::CorruptSaveData, _NS_::SaveDataManagement, _NS_::SaveDataBackUp) \ + HANDLER(CanCreateOthersSystemSaveData, _NS_::SaveDataBackUp) \ + HANDLER(CanCreateOwnSaveData, _NS_::CreateOwnSaveData) \ + HANDLER(CanCreateSaveData, _NS_::CreateSaveData, _NS_::SaveDataBackUp) \ + HANDLER(CanCreateSaveDataWithHashSalt, _NS_::None) \ + HANDLER(CanCreateSystemSaveData, _NS_::SaveDataBackUp, _NS_::SystemSaveData) \ + HANDLER(CanDebugSaveData, _NS_::Debug, _NS_::SaveDataForDebug) \ + HANDLER(CanDeleteOwnSaveData, _NS_::CreateOwnSaveData) \ + HANDLER(CanDeleteSaveData, _NS_::SaveDataManagement, _NS_::SaveDataBackUp) \ + HANDLER(CanDeleteSystemSaveData, _NS_::SystemSaveDataManagement, _NS_::SaveDataBackUp, _NS_::SystemSaveData) \ + HANDLER(CanEraseMmc, _NS_::BisAllRaw) \ + HANDLER(CanExtendOthersSystemSaveData, _NS_::SaveDataBackUp) \ + HANDLER(CanExtendOwnSaveData, _NS_::CreateOwnSaveData) \ + HANDLER(CanExtendSaveData, _NS_::CreateSaveData, _NS_::SaveDataBackUp) \ + HANDLER(CanExtendSystemSaveData, _NS_::SaveDataBackUp, _NS_::SystemSaveData) \ + HANDLER(CanFillBis, _NS_::Debug, _NS_::FillBis) \ + HANDLER(CanFinalizeGameCardDriver, _NS_::GameCardPrivate) \ + HANDLER(CanFindOwnSaveDataWithFilter, _NS_::CreateOwnSaveData) \ + HANDLER(CanFormatSdCard, _NS_::FormatSdCard) \ + HANDLER(CanGetAccessFailureDetectionEvent, _NS_::AccessFailureResolution) \ + HANDLER(CanGetGameCardAsicInfo, _NS_::GameCardPrivate) \ + HANDLER(CanGetGameCardDeviceCertificate, _NS_::GameCard) \ + HANDLER(CanGetGameCardIdSet, _NS_::GameCard) \ + HANDLER(CanGetRightsId, _NS_::GetRightsId) \ + HANDLER(CanGetSaveDataCommitId, _NS_::SaveDataTransferVersion2, _NS_::SaveDataBackUp) \ + HANDLER(CanInvalidateBisCache, _NS_::BisAllRaw) \ + HANDLER(CanIsAccessFailureDetected, _NS_::AccessFailureResolution) \ + HANDLER(CanListAccessibleSaveDataOwnerId, _NS_::SaveDataTransferVersion2, _NS_::SaveDataTransfer, _NS_::CreateSaveData) \ + HANDLER(CanMountAllBaseFileSystemRead, _NS_::None) \ + HANDLER(CanMountAllBaseFileSystemWrite, _NS_::None) \ + HANDLER(CanMountApplicationPackageRead, _NS_::ContentManager, _NS_::ApplicationInfo) \ + HANDLER(CanMountBisCalibrationFileRead, _NS_::BisAllRaw, _NS_::Calibration) \ + HANDLER(CanMountBisCalibrationFileWrite, _NS_::BisAllRaw, _NS_::Calibration) \ + HANDLER(CanMountBisSafeModeRead, _NS_::BisAllRaw) \ + HANDLER(CanMountBisSafeModeWrite, _NS_::BisAllRaw) \ + HANDLER(CanMountBisSystemProperEncryptionRead, _NS_::BisAllRaw) \ + HANDLER(CanMountBisSystemProperEncryptionWrite, _NS_::BisAllRaw) \ + HANDLER(CanMountBisSystemProperPartitionRead, _NS_::BisFileSystem, _NS_::BisAllRaw) \ + HANDLER(CanMountBisSystemProperPartitionWrite, _NS_::BisFileSystem, _NS_::BisAllRaw) \ + HANDLER(CanMountBisSystemRead, _NS_::BisFileSystem, _NS_::BisAllRaw) \ + HANDLER(CanMountBisSystemWrite, _NS_::BisFileSystem, _NS_::BisAllRaw) \ + HANDLER(CanMountBisUserRead, _NS_::BisFileSystem, _NS_::BisAllRaw) \ + HANDLER(CanMountBisUserWrite, _NS_::BisFileSystem, _NS_::BisAllRaw) \ + HANDLER(CanMountCloudBackupWorkStorageRead, _NS_::SaveDataTransferVersion2) \ + HANDLER(CanMountCloudBackupWorkStorageWrite, _NS_::SaveDataTransferVersion2) \ + HANDLER(CanMountContentControlRead, _NS_::ContentManager, _NS_::ApplicationInfo) \ + HANDLER(CanMountContentDataRead, _NS_::ContentManager, _NS_::ApplicationInfo) \ + HANDLER(CanMountContentManualRead, _NS_::ContentManager, _NS_::ApplicationInfo) \ + HANDLER(CanMountContentMetaRead, _NS_::ContentManager, _NS_::ApplicationInfo) \ + HANDLER(CanMountContentStorageRead, _NS_::ContentManager) \ + HANDLER(CanMountContentStorageWrite, _NS_::ContentManager) \ + HANDLER(CanMountCustomStorage0Read, _NS_::None) \ + HANDLER(CanMountCustomStorage0Write, _NS_::None) \ + HANDLER(CanMountDeviceSaveDataRead, _NS_::DeviceSaveData, _NS_::SaveDataBackUp) \ + HANDLER(CanMountDeviceSaveDataWrite, _NS_::DeviceSaveData, _NS_::SaveDataBackUp) \ + HANDLER(CanMountGameCardRead, _NS_::GameCard) \ + HANDLER(CanMountHostRead, _NS_::Debug, _NS_::Host) \ + HANDLER(CanMountHostWrite, _NS_::Debug, _NS_::Host) \ + HANDLER(CanMountImageAndVideoStorageRead, _NS_::ImageManager) \ + HANDLER(CanMountImageAndVideoStorageWrite, _NS_::ImageManager) \ + HANDLER(CanMountLogoRead, _NS_::ContentManager, _NS_::ApplicationInfo) \ + HANDLER(CanMountOthersSaveDataRead, _NS_::SaveDataBackUp) \ + HANDLER(CanMountOthersSaveDataWrite, _NS_::SaveDataBackUp) \ + HANDLER(CanMountOthersSystemSaveDataRead, _NS_::SaveDataBackUp) \ + HANDLER(CanMountOthersSystemSaveDataWrite, _NS_::SaveDataBackUp) \ + HANDLER(CanMountRegisteredUpdatePartitionRead, _NS_::SystemUpdate) \ + HANDLER(CanMountSaveDataStorageRead, _NS_::None) \ + HANDLER(CanMountSaveDataStorageWrite, _NS_::None) \ + HANDLER(CanMountSdCardRead, _NS_::Debug, _NS_::SdCard) \ + HANDLER(CanMountSdCardWrite, _NS_::Debug, _NS_::SdCard) \ + HANDLER(CanMountSystemDataPrivateRead, _NS_::SystemData, _NS_::SystemSaveData) \ + HANDLER(CanMountSystemSaveDataRead, _NS_::SaveDataBackUp, _NS_::SystemSaveData) \ + HANDLER(CanMountSystemSaveDataWrite, _NS_::SaveDataBackUp, _NS_::SystemSaveData) \ + HANDLER(CanMountTemporaryDirectoryRead, _NS_::Debug) \ + HANDLER(CanMountTemporaryDirectoryWrite, _NS_::Debug) \ + HANDLER(CanNotifySystemDataUpdateEvent, _NS_::SystemUpdate) \ + HANDLER(CanOpenAccessFailureDetectionEventNotifier, _NS_::AccessFailureResolution) \ + HANDLER(CanOpenBisPartitionBootConfigAndPackage2Part1Read, _NS_::SystemUpdate, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionBootConfigAndPackage2Part1Write, _NS_::SystemUpdate, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionBootConfigAndPackage2Part2Read, _NS_::SystemUpdate, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionBootConfigAndPackage2Part2Write, _NS_::SystemUpdate, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionBootConfigAndPackage2Part3Read, _NS_::SystemUpdate, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionBootConfigAndPackage2Part3Write, _NS_::SystemUpdate, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionBootConfigAndPackage2Part4Read, _NS_::SystemUpdate, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionBootConfigAndPackage2Part4Write, _NS_::SystemUpdate, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionBootConfigAndPackage2Part5Read, _NS_::SystemUpdate, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionBootConfigAndPackage2Part5Write, _NS_::SystemUpdate, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionBootConfigAndPackage2Part6Read, _NS_::SystemUpdate, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionBootConfigAndPackage2Part6Write, _NS_::SystemUpdate, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionBootPartition1RootRead, _NS_::SystemUpdate, _NS_::BisAllRaw, _NS_::BootModeControl) \ + HANDLER(CanOpenBisPartitionBootPartition1RootWrite, _NS_::SystemUpdate, _NS_::BisAllRaw, _NS_::BootModeControl) \ + HANDLER(CanOpenBisPartitionBootPartition2RootRead, _NS_::SystemUpdate, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionBootPartition2RootWrite, _NS_::SystemUpdate, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionCalibrationBinaryRead, _NS_::BisAllRaw, _NS_::Calibration) \ + HANDLER(CanOpenBisPartitionCalibrationBinaryWrite, _NS_::BisAllRaw, _NS_::Calibration) \ + HANDLER(CanOpenBisPartitionCalibrationFileRead, _NS_::BisAllRaw, _NS_::Calibration) \ + HANDLER(CanOpenBisPartitionCalibrationFileWrite, _NS_::BisAllRaw, _NS_::Calibration) \ + HANDLER(CanOpenBisPartitionSafeModeRead, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionSafeModeWrite, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionSystemProperEncryptionRead, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionSystemProperEncryptionWrite, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionSystemProperPartitionRead, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionSystemProperPartitionWrite, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionSystemRead, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionSystemWrite, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionUserDataRootRead, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionUserDataRootWrite, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionUserRead, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisPartitionUserWrite, _NS_::BisAllRaw) \ + HANDLER(CanOpenBisWiper, _NS_::ContentManager) \ + HANDLER(CanOpenDataStorageByPath, _NS_::None) \ + HANDLER(CanOpenGameCardDetectionEventNotifier, _NS_::DeviceDetection, _NS_::GameCardRaw, _NS_::GameCard) \ + HANDLER(CanOpenGameCardStorageRead, _NS_::GameCardRaw) \ + HANDLER(CanOpenGameCardStorageWrite, _NS_::GameCardRaw) \ + HANDLER(CanOpenOwnSaveDataTransferProhibiter, _NS_::CreateOwnSaveData) \ + HANDLER(CanOpenSaveDataInfoReader, _NS_::SaveDataManagement, _NS_::SaveDataBackUp) \ + HANDLER(CanOpenSaveDataInfoReaderForInternal, _NS_::SaveDataManagement) \ + HANDLER(CanOpenSaveDataInfoReaderForSystem, _NS_::SystemSaveDataManagement, _NS_::SaveDataBackUp) \ + HANDLER(CanOpenSaveDataInternalStorageRead, _NS_::None) \ + HANDLER(CanOpenSaveDataInternalStorageWrite, _NS_::None) \ + HANDLER(CanOpenSaveDataMetaFile, _NS_::SaveDataMeta) \ + HANDLER(CanOpenSaveDataMover, _NS_::MoveCacheStorage) \ + HANDLER(CanOpenSaveDataTransferManager, _NS_::SaveDataTransfer) \ + HANDLER(CanOpenSaveDataTransferManagerForRepair, _NS_::SaveDataBackUp) \ + HANDLER(CanOpenSaveDataTransferManagerForSaveDataRepair, _NS_::SaveDataTransferVersion2) \ + HANDLER(CanOpenSaveDataTransferManagerForSaveDataRepairTool, _NS_::None) \ + HANDLER(CanOpenSaveDataTransferManagerVersion2, _NS_::SaveDataTransferVersion2) \ + HANDLER(CanOpenSaveDataTransferProhibiter, _NS_::SaveDataTransferVersion2, _NS_::CreateSaveData) \ + HANDLER(CanOpenSdCardDetectionEventNotifier, _NS_::DeviceDetection, _NS_::SdCard) \ + HANDLER(CanOpenSdCardStorageRead, _NS_::Debug, _NS_::SdCard) \ + HANDLER(CanOpenSdCardStorageWrite, _NS_::Debug, _NS_::SdCard) \ + HANDLER(CanOpenSystemDataUpdateEventNotifier, _NS_::SystemData, _NS_::SystemSaveData) \ + HANDLER(CanOverrideSaveDataTransferTokenSignVerificationKey, _NS_::None) \ + HANDLER(CanQuerySaveDataInternalStorageTotalSize, _NS_::SaveDataTransfer) \ + HANDLER(CanReadOwnSaveDataFileSystemExtraData, _NS_::CreateOwnSaveData) \ + HANDLER(CanReadSaveDataFileSystemExtraData, _NS_::SystemSaveDataManagement, _NS_::SaveDataManagement, _NS_::SaveDataBackUp) \ + HANDLER(CanRegisterExternalKey, _NS_::RegisterExternalKey) \ + HANDLER(CanRegisterProgramIndexMapInfo, _NS_::RegisterProgramIndexMapInfo) \ + HANDLER(CanRegisterUpdatePartition, _NS_::RegisterUpdatePartition) \ + HANDLER(CanResolveAccessFailure, _NS_::AccessFailureResolution) \ + HANDLER(CanSetCurrentPosixTime, _NS_::SetTime) \ + HANDLER(CanSetDebugConfiguration, _NS_::None) \ + HANDLER(CanSetEncryptionSeed, _NS_::ContentManager) \ + HANDLER(CanSetGlobalAccessLogMode, _NS_::SettingsControl) \ + HANDLER(CanSetSdCardAccessibility, _NS_::SdCard) \ + HANDLER(CanSetSpeedEmulationMode, _NS_::SettingsControl) \ + HANDLER(CanSimulateDevice, _NS_::Debug) \ + HANDLER(CanVerifySaveData, _NS_::SaveDataManagement, _NS_::SaveDataBackUp) \ + HANDLER(CanWriteSaveDataFileSystemExtraDataAll, _NS_::None) \ + HANDLER(CanWriteSaveDataFileSystemExtraDataCommitId, _NS_::SaveDataBackUp) \ + HANDLER(CanWriteSaveDataFileSystemExtraDataFlags, _NS_::SaveDataTransferVersion2, _NS_::SystemSaveDataManagement, _NS_::SaveDataBackUp) \ + HANDLER(CanWriteSaveDataFileSystemExtraDataTimeStamp, _NS_::SaveDataBackUp) + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class AccessControlBits { + public: + enum class Bits : u64 { + None = 0, + + ApplicationInfo = UINT64_C(1) << 0, + BootModeControl = UINT64_C(1) << 1, + Calibration = UINT64_C(1) << 2, + SystemSaveData = UINT64_C(1) << 3, + GameCard = UINT64_C(1) << 4, + SaveDataBackUp = UINT64_C(1) << 5, + SaveDataManagement = UINT64_C(1) << 6, + BisAllRaw = UINT64_C(1) << 7, + GameCardRaw = UINT64_C(1) << 8, + GameCardPrivate = UINT64_C(1) << 9, + SetTime = UINT64_C(1) << 10, + ContentManager = UINT64_C(1) << 11, + ImageManager = UINT64_C(1) << 12, + CreateSaveData = UINT64_C(1) << 13, + SystemSaveDataManagement = UINT64_C(1) << 14, + BisFileSystem = UINT64_C(1) << 15, + SystemUpdate = UINT64_C(1) << 16, + SaveDataMeta = UINT64_C(1) << 17, + DeviceSaveData = UINT64_C(1) << 18, + SettingsControl = UINT64_C(1) << 19, + SystemData = UINT64_C(1) << 20, + SdCard = UINT64_C(1) << 21, + Host = UINT64_C(1) << 22, + FillBis = UINT64_C(1) << 23, + CorruptSaveData = UINT64_C(1) << 24, + SaveDataForDebug = UINT64_C(1) << 25, + FormatSdCard = UINT64_C(1) << 26, + GetRightsId = UINT64_C(1) << 27, + RegisterExternalKey = UINT64_C(1) << 28, + RegisterUpdatePartition = UINT64_C(1) << 29, + SaveDataTransfer = UINT64_C(1) << 30, + DeviceDetection = UINT64_C(1) << 31, + AccessFailureResolution = UINT64_C(1) << 32, + SaveDataTransferVersion2 = UINT64_C(1) << 33, + RegisterProgramIndexMapInfo = UINT64_C(1) << 34, + CreateOwnSaveData = UINT64_C(1) << 35, + MoveCacheStorage = UINT64_C(1) << 36, + + Debug = UINT64_C(1) << 62, + FullPermission = UINT64_C(1) << 63 + }; + private: + static constexpr u64 CombineBits(Bits b) { + return util::ToUnderlying(b); + } + + template<typename... Args> + static constexpr u64 CombineBits(Bits b, Args... args) { + return CombineBits(b) | CombineBits(args...); + } + private: + const u64 m_value; + public: + constexpr AccessControlBits(u64 v) : m_value(v) { /* ... */ } + + constexpr u64 GetValue() const { return m_value; } + + #define DEFINE_ACCESS_GETTER(name, ...) \ + constexpr bool name() const { constexpr u64 Mask = CombineBits(Bits::FullPermission, ## __VA_ARGS__); return (m_value & Mask); } + + AMS_FSSRV_FOR_EACH_ACCESS_CONTROL_CAPABILITY(DEFINE_ACCESS_GETTER, Bits) + + #undef DEFINE_ACCESS_GETTER + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/impl/fssrv_external_key_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/impl/fssrv_external_key_manager.hpp new file mode 100644 index 00000000..ef652358 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/impl/fssrv_external_key_manager.hpp @@ -0,0 +1,151 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> +#include <stratosphere/fs/fs_rights_id.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> +#include <stratosphere/spl/spl_types.hpp> + +namespace ams::fssrv::impl { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class ExternalKeyEntry : public util::IntrusiveListBaseNode<ExternalKeyEntry>, public ::ams::fs::impl::Newable { + private: + fs::RightsId m_rights_id; + spl::AccessKey m_access_key; + public: + ExternalKeyEntry(const fs::RightsId &rights_id, const spl::AccessKey &access_key) : m_rights_id(rights_id), m_access_key(access_key) { + /* ... */ + } + + bool Contains(const fs::RightsId &rights_id) const { + return crypto::IsSameBytes(std::addressof(m_rights_id), std::addressof(rights_id), sizeof(m_rights_id)); + } + + bool Contains(const void *key, size_t key_size) const { + AMS_ASSERT(key_size == sizeof(spl::AccessKey)); + AMS_UNUSED(key_size); + + return crypto::IsSameBytes(std::addressof(m_access_key), key, sizeof(m_access_key)); + } + + void CopyAccessKey(spl::AccessKey *out) const { + AMS_ASSERT(out != nullptr); + std::memcpy(out, std::addressof(m_access_key), sizeof(m_access_key)); + } + }; + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class ExternalKeyManager { + NON_COPYABLE(ExternalKeyManager); + NON_MOVEABLE(ExternalKeyManager); + private: + using ExternalKeyList = util::IntrusiveListBaseTraits<ExternalKeyEntry>::ListType; + private: + ExternalKeyList m_key_list; + os::SdkMutex m_mutex; + public: + constexpr ExternalKeyManager() : m_key_list(), m_mutex() { /* ... */ } + + Result Register(const fs::RightsId &rights_id, const spl::AccessKey &access_key) { + /* Acquire exclusive access to the key list */ + std::scoped_lock lk(m_mutex); + + /* Try to find an existing entry. */ + spl::AccessKey existing; + if (R_SUCCEEDED(this->FindCore(std::addressof(existing), rights_id))) { + /* Check the key matches what was previously registered. */ + R_UNLESS(crypto::IsSameBytes(std::addressof(existing), std::addressof(access_key), sizeof(access_key)), fs::ResultNcaExternalKeyInconsistent()); + } else { + /* Make a new entry. */ + auto *entry = new ExternalKeyEntry(rights_id, access_key); + R_UNLESS(entry != nullptr, fs::ResultAllocationMemoryFailed()); + + /* Add the entry to our list. */ + m_key_list.push_back(*entry); + } + + R_SUCCEED(); + } + + Result Unregister(const fs::RightsId &rights_id) { + /* Acquire exclusive access to the key list */ + std::scoped_lock lk(m_mutex); + + /* Find a matching entry. */ + for (auto it = m_key_list.begin(); it != m_key_list.end(); ++it) { + if (it->Contains(rights_id)) { + auto *entry = std::addressof(*it); + m_key_list.erase(it); + delete entry; + break; + } + } + + /* Always succeed. */ + R_SUCCEED(); + } + + Result UnregisterAll() { + /* Acquire exclusive access to the key list */ + std::scoped_lock lk(m_mutex); + + /* Remove all entries until our list is empty. */ + while (!m_key_list.empty()) { + auto *entry = std::addressof(*m_key_list.begin()); + m_key_list.erase(m_key_list.iterator_to(*entry)); + delete entry; + } + + R_SUCCEED(); + } + + bool IsAvailableAccessKey(const void *key, size_t key_size) { + /* Acquire exclusive access to the key list */ + std::scoped_lock lk(m_mutex); + + /* Check if any entry contains the key. */ + for (const auto &entry : m_key_list) { + if (entry.Contains(key, key_size)) { + return true; + } + } + + return false; + } + + Result Find(spl::AccessKey *out, const fs::RightsId &rights_id) { + /* Acquire exclusive access to the key list */ + std::scoped_lock lk(m_mutex); + + /* Try to find an entry with the desired rights id. */ + R_RETURN(this->FindCore(out, rights_id)); + } + private: + Result FindCore(spl::AccessKey *out, const fs::RightsId &rights_id) { + for (const auto &entry : m_key_list) { + if (entry.Contains(rights_id)) { + entry.CopyAccessKey(out); + R_SUCCEED(); + } + } + + R_THROW(fs::ResultNcaExternalKeyUnregistered()); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/impl/fssrv_file_system_proxy_service_object.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/impl/fssrv_file_system_proxy_service_object.hpp new file mode 100644 index 00000000..a99b121a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/impl/fssrv_file_system_proxy_service_object.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_i_file_system_proxy.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_i_program_registry.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_i_file_system_proxy_for_loader.hpp> +#include <stratosphere/fssrv/fssrv_file_system_proxy_impl.hpp> + +namespace ams::fssrv::impl { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + ams::sf::EmplacedRef<fssrv::sf::IFileSystemProxy, fssrv::FileSystemProxyImpl> GetFileSystemProxyServiceObject(); + + ams::sf::SharedPointer<fssrv::sf::IProgramRegistry> GetProgramRegistryServiceObject(); + ams::sf::SharedPointer<fssrv::sf::IProgramRegistry> GetInvalidProgramRegistryServiceObject(); + + ams::sf::SharedPointer<fssrv::sf::IFileSystemProxyForLoader> GetFileSystemProxyForLoaderServiceObject(); + ams::sf::SharedPointer<fssrv::sf::IFileSystemProxyForLoader> GetInvalidFileSystemProxyForLoaderServiceObject(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/impl/fssrv_impl_program_index_map_info_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/impl/fssrv_impl_program_index_map_info_manager.hpp new file mode 100644 index 00000000..539a79e8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/impl/fssrv_impl_program_index_map_info_manager.hpp @@ -0,0 +1,163 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> +#include <stratosphere/fs/fs_program_index_map_info.hpp> +#include <stratosphere/fssystem/fssystem_pimpl.hpp> + +namespace ams::fssrv::impl { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + struct ProgramIndexMapInfoEntry : public ::ams::util::IntrusiveListBaseNode<ProgramIndexMapInfoEntry>, public ::ams::fs::impl::Newable { + ncm::ProgramId program_id; + ncm::ProgramId base_program_id; + u8 program_index; + }; + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class ProgramIndexMapInfoManager { + NON_COPYABLE(ProgramIndexMapInfoManager); + NON_MOVEABLE(ProgramIndexMapInfoManager); + private: + using ProgramIndexMapInfoList = util::IntrusiveListBaseTraits<ProgramIndexMapInfoEntry>::ListType; + private: + ProgramIndexMapInfoList m_list; + mutable os::SdkMutex m_mutex; + public: + constexpr ProgramIndexMapInfoManager() : m_list(), m_mutex() { /* ... */ } + + void Clear() { + /* Acquire exclusive access to the map. */ + std::scoped_lock lk(m_mutex); + + /* Actually clear. */ + this->ClearImpl(); + } + + size_t GetProgramCount() const { + /* Acquire exclusive access to the map. */ + std::scoped_lock lk(m_mutex); + + /* Get the size. */ + return m_list.size(); + } + + util::optional<fs::ProgramIndexMapInfo> Get(const ncm::ProgramId &program_id) const { + /* Acquire exclusive access to the map. */ + std::scoped_lock lk(m_mutex); + + /* Get the entry from the map. */ + return this->GetImpl([&] (const ProgramIndexMapInfoEntry &entry) { + return entry.program_id == program_id; + }); + } + + ncm::ProgramId GetProgramId(const ncm::ProgramId &program_id, u8 program_index) const { + /* Acquire exclusive access to the map. */ + std::scoped_lock lk(m_mutex); + + /* Get the program info for the desired program id. */ + const auto base_info = this->GetImpl([&] (const ProgramIndexMapInfoEntry &entry) { + return entry.program_id == program_id; + }); + + /* Check that an entry exists for the program id. */ + if (!base_info.has_value()) { + return ncm::InvalidProgramId; + } + + /* Get a program info which matches the same base program with the desired index. */ + const auto target_info = this->GetImpl([&] (const ProgramIndexMapInfoEntry &entry) { + return entry.base_program_id == base_info->base_program_id && entry.program_index == program_index; + }); + + /* Return the desired program id. */ + if (target_info.has_value()) { + return target_info->program_id; + } else { + return ncm::InvalidProgramId; + } + } + + Result Reset(const fs::ProgramIndexMapInfo *infos, int count) { + /* Acquire exclusive access to the map. */ + std::scoped_lock lk(m_mutex); + + /* Clear the map, and ensure we remain clear if we fail after this point. */ + this->ClearImpl(); + ON_RESULT_FAILURE { this->ClearImpl(); }; + + /* Add each info to the list. */ + for (int i = 0; i < count; ++i) { + /* Allocate new entry. */ + auto *entry = new ProgramIndexMapInfoEntry; + R_UNLESS(entry != nullptr, fs::ResultAllocationMemoryFailedNew()); + + /* Copy over the info. */ + entry->program_id = infos[i].program_id; + entry->base_program_id = infos[i].base_program_id; + entry->program_index = infos[i].program_index; + + /* Add to the list. */ + m_list.push_back(*entry); + } + + /* We successfully imported the map. */ + R_SUCCEED(); + } + private: + void ClearImpl() { + /* Delete all entries. */ + while (!m_list.empty()) { + /* Get the first entry. */ + ProgramIndexMapInfoEntry *front = std::addressof(*m_list.begin()); + + /* Erase it from the list. */ + m_list.erase(m_list.iterator_to(*front)); + + /* Delete the entry. */ + delete front; + } + } + + template<typename F> + util::optional<fs::ProgramIndexMapInfo> GetImpl(F f) const { + /* Try to find an entry matching the predicate. */ + util::optional<fs::ProgramIndexMapInfo> match = util::nullopt; + + for (const auto &entry : m_list) { + /* If the predicate matches, we want to return the relevant info. */ + if (f(entry)) { + match.emplace(); + + match->program_id = entry.program_id; + match->base_program_id = entry.base_program_id; + match->program_index = entry.program_index; + + break; + } + } + + return match; + } + }; + + +} + +AMS_FSSYSTEM_ENABLE_PIMPL(::ams::fssrv::impl::ProgramIndexMapInfoManager) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/interface_adapters/fssrv_filesystem_interface_adapter.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/interface_adapters/fssrv_filesystem_interface_adapter.hpp new file mode 100644 index 00000000..d3f6560c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/interface_adapters/fssrv_filesystem_interface_adapter.hpp @@ -0,0 +1,265 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_file.hpp> +#include <stratosphere/fs/fs_directory.hpp> +#include <stratosphere/fs/fs_filesystem.hpp> +#include <stratosphere/fs/fs_query_range.hpp> +#include <stratosphere/fssystem/fssystem_utility.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_path.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_ifile.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_idirectory.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_ifilesystem.hpp> + +namespace ams::fs::fsa { + + class IFile; + class IDirectory; + class IFileSystem; + +} + +namespace ams::fssrv::impl { + + class FileSystemInterfaceAdapter; + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class FileInterfaceAdapter { + NON_COPYABLE(FileInterfaceAdapter); + NON_MOVEABLE(FileInterfaceAdapter); + private: + ams::sf::SharedPointer<FileSystemInterfaceAdapter> m_parent_filesystem; + std::unique_ptr<fs::fsa::IFile> m_base_file; + bool m_allow_all_operations; + public: + FileInterfaceAdapter(std::unique_ptr<fs::fsa::IFile> &&file, FileSystemInterfaceAdapter *parent, bool allow_all); + private: + void InvalidateCache(); + public: + /* Command API. */ + Result Read(ams::sf::Out<s64> out, s64 offset, const ams::sf::OutNonSecureBuffer &buffer, s64 size, fs::ReadOption option); + Result Write(s64 offset, const ams::sf::InNonSecureBuffer &buffer, s64 size, fs::WriteOption option); + Result Flush(); + Result SetSize(s64 size); + Result GetSize(ams::sf::Out<s64> out); + Result OperateRange(ams::sf::Out<fs::FileQueryRangeInfo> out, s32 op_id, s64 offset, s64 size); + Result OperateRangeWithBuffer(const ams::sf::OutNonSecureBuffer &out_buf, const ams::sf::InNonSecureBuffer &in_buf, s32 op_id, s64 offset, s64 size); + }; + static_assert(fssrv::sf::IsIFile<FileInterfaceAdapter>); + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class DirectoryInterfaceAdapter { + NON_COPYABLE(DirectoryInterfaceAdapter); + NON_MOVEABLE(DirectoryInterfaceAdapter); + private: + ams::sf::SharedPointer<FileSystemInterfaceAdapter> m_parent_filesystem; + std::unique_ptr<fs::fsa::IDirectory> m_base_dir; + bool m_allow_all_operations; + public: + DirectoryInterfaceAdapter(std::unique_ptr<fs::fsa::IDirectory> &&dir, FileSystemInterfaceAdapter *parent, bool allow_all); + public: + /* Command API */ + Result Read(ams::sf::Out<s64> out, const ams::sf::OutBuffer &out_entries); + Result GetEntryCount(ams::sf::Out<s64> out); + }; + static_assert(fssrv::sf::IsIDirectory<DirectoryInterfaceAdapter>); + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class FileSystemInterfaceAdapter : public ams::sf::ISharedObject { + NON_COPYABLE(FileSystemInterfaceAdapter); + NON_MOVEABLE(FileSystemInterfaceAdapter); + private: + std::shared_ptr<fs::fsa::IFileSystem> m_base_fs; + fs::PathFlags m_path_flags; + bool m_allow_all_operations; + bool m_is_mitm_interface; + public: + FileSystemInterfaceAdapter(std::shared_ptr<fs::fsa::IFileSystem> &&fs, const fs::PathFlags &flags, bool allow_all, bool is_mitm_interface = false) + : m_base_fs(std::move(fs)), m_path_flags(flags), m_allow_all_operations(allow_all), m_is_mitm_interface(is_mitm_interface) + { + /* ... */ + } + + FileSystemInterfaceAdapter(std::shared_ptr<fs::fsa::IFileSystem> &&fs, bool allow_all, bool is_mitm_interface = false) + : m_base_fs(std::move(fs)), m_path_flags(), m_allow_all_operations(allow_all), m_is_mitm_interface(is_mitm_interface) + { + /* ... */ + } + private: + Result SetUpPath(fs::Path *out, const fssrv::sf::Path &sf_path); + public: + /* Command API. */ + Result CreateFile(const fssrv::sf::Path &path, s64 size, s32 option); + Result DeleteFile(const fssrv::sf::Path &path); + Result CreateDirectory(const fssrv::sf::Path &path); + Result DeleteDirectory(const fssrv::sf::Path &path); + Result DeleteDirectoryRecursively(const fssrv::sf::Path &path); + Result RenameFile(const fssrv::sf::Path &old_path, const fssrv::sf::Path &new_path); + Result RenameDirectory(const fssrv::sf::Path &old_path, const fssrv::sf::Path &new_path); + Result GetEntryType(ams::sf::Out<u32> out, const fssrv::sf::Path &path); + Result OpenFile(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFile>> out, const fssrv::sf::Path &path, u32 mode); + Result OpenDirectory(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IDirectory>> out, const fssrv::sf::Path &path, u32 mode); + Result Commit(); + Result GetFreeSpaceSize(ams::sf::Out<s64> out, const fssrv::sf::Path &path); + Result GetTotalSpaceSize(ams::sf::Out<s64> out, const fssrv::sf::Path &path); + + Result CleanDirectoryRecursively(const fssrv::sf::Path &path); + Result GetFileTimeStampRaw(ams::sf::Out<fs::FileTimeStampRaw> out, const fssrv::sf::Path &path); + + Result QueryEntry(const ams::sf::OutNonSecureBuffer &out_buf, const ams::sf::InNonSecureBuffer &in_buf, s32 query_id, const fssrv::sf::Path &path); + }; + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemoteFile { + NON_COPYABLE(RemoteFile); + NON_MOVEABLE(RemoteFile); + private: + ::FsFile m_base_file; + public: + RemoteFile(::FsFile &s) : m_base_file(s) { /* ... */} + + virtual ~RemoteFile() { fsFileClose(std::addressof(m_base_file)); } + public: + Result Read(ams::sf::Out<s64> out, s64 offset, const ams::sf::OutNonSecureBuffer &buffer, s64 size, fs::ReadOption option) { + R_RETURN(fsFileRead(std::addressof(m_base_file), offset, buffer.GetPointer(), size, option._value, reinterpret_cast<u64 *>(out.GetPointer()))); + } + + Result Write(s64 offset, const ams::sf::InNonSecureBuffer &buffer, s64 size, fs::WriteOption option) { + R_RETURN(fsFileWrite(std::addressof(m_base_file), offset, buffer.GetPointer(), size, option._value)); + } + + Result Flush(){ + R_RETURN(fsFileFlush(std::addressof(m_base_file))); + } + + Result SetSize(s64 size) { + R_RETURN(fsFileSetSize(std::addressof(m_base_file), size)); + } + + Result GetSize(ams::sf::Out<s64> out) { + R_RETURN(fsFileGetSize(std::addressof(m_base_file), out.GetPointer())); + } + + Result OperateRange(ams::sf::Out<fs::FileQueryRangeInfo> out, s32 op_id, s64 offset, s64 size) { + static_assert(sizeof(::FsRangeInfo) == sizeof(fs::FileQueryRangeInfo)); + R_RETURN(fsFileOperateRange(std::addressof(m_base_file), static_cast<::FsOperationId>(op_id), offset, size, reinterpret_cast<::FsRangeInfo *>(out.GetPointer()))); + } + + Result OperateRangeWithBuffer(const ams::sf::OutNonSecureBuffer &out_buf, const ams::sf::InNonSecureBuffer &in_buf, s32 op_id, s64 offset, s64 size) { + AMS_UNUSED(out_buf, in_buf, op_id, offset, size); + AMS_ABORT("TODO"); + } + }; + static_assert(fssrv::sf::IsIFile<RemoteFile>); + + class RemoteDirectory { + NON_COPYABLE(RemoteDirectory); + NON_MOVEABLE(RemoteDirectory); + private: + ::FsDir m_base_dir; + public: + RemoteDirectory(::FsDir &s) : m_base_dir(s) { /* ... */} + + virtual ~RemoteDirectory() { fsDirClose(std::addressof(m_base_dir)); } + public: + Result Read(ams::sf::Out<s64> out, const ams::sf::OutBuffer &out_entries) { + static_assert(sizeof(::FsDirectoryEntry) == sizeof(fs::DirectoryEntry)); + R_RETURN(fsDirRead(std::addressof(m_base_dir), out.GetPointer(), out_entries.GetSize() / sizeof(fs::DirectoryEntry), reinterpret_cast<::FsDirectoryEntry *>(out_entries.GetPointer()))); + } + + Result GetEntryCount(ams::sf::Out<s64> out) { + R_RETURN(fsDirGetEntryCount(std::addressof(m_base_dir), out.GetPointer())); + } + }; + static_assert(fssrv::sf::IsIDirectory<RemoteDirectory>); + + class RemoteFileSystem { + NON_COPYABLE(RemoteFileSystem); + NON_MOVEABLE(RemoteFileSystem); + private: + ::FsFileSystem m_base_fs; + public: + RemoteFileSystem(::FsFileSystem &s) : m_base_fs(s) { /* ... */} + + virtual ~RemoteFileSystem() { fsFsClose(std::addressof(m_base_fs)); } + public: + /* Command API. */ + Result CreateFile(const fssrv::sf::Path &path, s64 size, s32 option) { + R_RETURN(fsFsCreateFile(std::addressof(m_base_fs), path.str, size, option)); + } + + Result DeleteFile(const fssrv::sf::Path &path) { + R_RETURN(fsFsDeleteFile(std::addressof(m_base_fs), path.str)); + } + + Result CreateDirectory(const fssrv::sf::Path &path) { + R_RETURN(fsFsCreateDirectory(std::addressof(m_base_fs), path.str)); + } + + Result DeleteDirectory(const fssrv::sf::Path &path) { + R_RETURN(fsFsDeleteDirectory(std::addressof(m_base_fs), path.str)); + } + + Result DeleteDirectoryRecursively(const fssrv::sf::Path &path) { + R_RETURN(fsFsDeleteDirectoryRecursively(std::addressof(m_base_fs), path.str)); + } + + Result RenameFile(const fssrv::sf::Path &old_path, const fssrv::sf::Path &new_path) { + R_RETURN(fsFsRenameFile(std::addressof(m_base_fs), old_path.str, new_path.str)); + } + + Result RenameDirectory(const fssrv::sf::Path &old_path, const fssrv::sf::Path &new_path) { + R_RETURN(fsFsRenameDirectory(std::addressof(m_base_fs), old_path.str, new_path.str)); + } + + Result GetEntryType(ams::sf::Out<u32> out, const fssrv::sf::Path &path) { + static_assert(sizeof(::FsDirEntryType) == sizeof(u32)); + R_RETURN(fsFsGetEntryType(std::addressof(m_base_fs), path.str, reinterpret_cast<::FsDirEntryType *>(out.GetPointer()))); + } + + Result Commit() { + R_RETURN(fsFsCommit(std::addressof(m_base_fs))); + } + + Result GetFreeSpaceSize(ams::sf::Out<s64> out, const fssrv::sf::Path &path) { + R_RETURN(fsFsGetFreeSpace(std::addressof(m_base_fs), path.str, out.GetPointer())); + } + + Result GetTotalSpaceSize(ams::sf::Out<s64> out, const fssrv::sf::Path &path) { + R_RETURN(fsFsGetTotalSpace(std::addressof(m_base_fs), path.str, out.GetPointer())); + } + + Result CleanDirectoryRecursively(const fssrv::sf::Path &path) { + R_RETURN(fsFsCleanDirectoryRecursively(std::addressof(m_base_fs), path.str)); + } + + Result GetFileTimeStampRaw(ams::sf::Out<fs::FileTimeStampRaw> out, const fssrv::sf::Path &path) { + static_assert(sizeof(fs::FileTimeStampRaw) == sizeof(::FsTimeStampRaw)); + R_RETURN(fsFsGetFileTimeStampRaw(std::addressof(m_base_fs), path.str, reinterpret_cast<::FsTimeStampRaw *>(out.GetPointer()))); + } + + Result QueryEntry(const ams::sf::OutNonSecureBuffer &out_buf, const ams::sf::InNonSecureBuffer &in_buf, s32 query_id, const fssrv::sf::Path &path) { + R_RETURN(fsFsQueryEntry(std::addressof(m_base_fs), out_buf.GetPointer(), out_buf.GetSize(), in_buf.GetPointer(), in_buf.GetSize(), path.str, static_cast<FsFileSystemQueryId>(query_id))); + } + + Result OpenFile(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFile>> out, const fssrv::sf::Path &path, u32 mode); + Result OpenDirectory(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IDirectory>> out, const fssrv::sf::Path &path, u32 mode); + }; + static_assert(fssrv::sf::IsIFileSystem<RemoteFileSystem>); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/interface_adapters/fssrv_storage_interface_adapter.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/interface_adapters/fssrv_storage_interface_adapter.hpp new file mode 100644 index 00000000..1c08fb51 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/interface_adapters/fssrv_storage_interface_adapter.hpp @@ -0,0 +1,87 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_query_range.hpp> +#include <stratosphere/fssystem/fssystem_utility.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_istorage.hpp> + +namespace ams::fs { + + class IStorage; + +} + +namespace ams::fssrv::impl { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class StorageInterfaceAdapter { + NON_COPYABLE(StorageInterfaceAdapter); + private: + std::shared_ptr<fs::IStorage> m_base_storage; + public: + explicit StorageInterfaceAdapter(std::shared_ptr<fs::IStorage> &&storage) : m_base_storage(std::move(storage)) { /* ... */ } + public: + /* Command API. */ + Result Read(s64 offset, const ams::sf::OutNonSecureBuffer &buffer, s64 size); + Result Write(s64 offset, const ams::sf::InNonSecureBuffer &buffer, s64 size); + Result Flush(); + Result SetSize(s64 size); + Result GetSize(ams::sf::Out<s64> out); + Result OperateRange(ams::sf::Out<fs::StorageQueryRangeInfo> out, s32 op_id, s64 offset, s64 size); + }; + static_assert(fssrv::sf::IsIStorage<StorageInterfaceAdapter>); + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemoteStorage { + NON_COPYABLE(RemoteStorage); + NON_MOVEABLE(RemoteStorage); + private: + ::FsStorage m_base_storage; + public: + RemoteStorage(::FsStorage &s) : m_base_storage(s) { /* ... */} + + virtual ~RemoteStorage() { fsStorageClose(std::addressof(m_base_storage)); } + public: + Result Read(s64 offset, const ams::sf::OutNonSecureBuffer &buffer, s64 size) { + R_RETURN(fsStorageRead(std::addressof(m_base_storage), offset, buffer.GetPointer(), size)); + } + + Result Write(s64 offset, const ams::sf::InNonSecureBuffer &buffer, s64 size) { + R_RETURN(fsStorageWrite(std::addressof(m_base_storage), offset, buffer.GetPointer(), size)); + } + + Result Flush(){ + R_RETURN(fsStorageFlush(std::addressof(m_base_storage))); + } + + Result SetSize(s64 size) { + R_RETURN(fsStorageSetSize(std::addressof(m_base_storage), size)); + } + + Result GetSize(ams::sf::Out<s64> out) { + R_RETURN(fsStorageGetSize(std::addressof(m_base_storage), out.GetPointer())); + } + + Result OperateRange(ams::sf::Out<fs::StorageQueryRangeInfo> out, s32 op_id, s64 offset, s64 size) { + static_assert(sizeof(::FsRangeInfo) == sizeof(fs::StorageQueryRangeInfo)); + R_RETURN(fsStorageOperateRange(std::addressof(m_base_storage), static_cast<::FsOperationId>(op_id), offset, size, reinterpret_cast<::FsRangeInfo *>(out.GetPointer()))); + } + }; + static_assert(fssrv::sf::IsIStorage<RemoteStorage>); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_i_device_operator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_i_device_operator.hpp new file mode 100644 index 00000000..c251223c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_i_device_operator.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/fs/fs_error_info.hpp> +#include <stratosphere/fs/fs_game_card.hpp> + +/* TODO */ +/* ACCURATE_TO_VERSION: 13.4.0.0 */ +#define AMS_FSSRV_I_DEVICE_OPERATOR_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, IsSdCardInserted, (ams::sf::Out<bool> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, GetSdCardSpeedMode, (ams::sf::Out<s64> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, GetSdCardCid, (ams::sf::OutBuffer out, s64 size), (out, size)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, GetSdCardUserAreaSize, (ams::sf::Out<s64> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, GetSdCardProtectedAreaSize, (ams::sf::Out<s64> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, GetAndClearSdCardErrorInfo, (ams::sf::Out<fs::StorageErrorInfo> out_sei, ams::sf::Out<s64> out_size, ams::sf::OutBuffer out_buf, s64 size), (out_sei, out_size, out_buf, size)) \ + AMS_SF_METHOD_INFO(C, H, 100, Result, GetMmcCid, (ams::sf::OutBuffer out, s64 size), (out, size)) \ + AMS_SF_METHOD_INFO(C, H, 101, Result, GetMmcSpeedMode, (ams::sf::Out<s64> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 112, Result, GetMmcPatrolCount, (ams::sf::Out<u32> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 113, Result, GetAndClearMmcErrorInfo, (ams::sf::Out<fs::StorageErrorInfo> out_sei, ams::sf::Out<s64> out_size, ams::sf::OutBuffer out_buf, s64 size), (out_sei, out_size, out_buf, size)) \ + AMS_SF_METHOD_INFO(C, H, 114, Result, GetMmcExtendedCsd, (ams::sf::OutBuffer out, s64 size), (out, size)) \ + AMS_SF_METHOD_INFO(C, H, 200, Result, IsGameCardInserted, (ams::sf::Out<bool> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 202, Result, GetGameCardHandle, (ams::sf::Out<u32> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 208, Result, GetGameCardIdSet, (ams::sf::OutBuffer out, s64 size), (out, size)) \ + AMS_SF_METHOD_INFO(C, H, 217, Result, GetGameCardErrorReportInfo, (ams::sf::Out<fs::GameCardErrorReportInfo> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 218, Result, GetGameCardDeviceId, (ams::sf::OutBuffer out, s64 size), (out, size)) + +AMS_SF_DEFINE_INTERFACE(ams::fssrv::sf, IDeviceOperator, AMS_FSSRV_I_DEVICE_OPERATOR_INTERFACE_INFO, 0x1484E21C) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_i_event_notifier.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_i_event_notifier.hpp new file mode 100644 index 00000000..275f940d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_i_event_notifier.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> + +/* ACCURATE_TO_VERSION: 13.4.0.0 */ +#define AMS_FSSRV_I_EVENT_NOTIFIER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GetEventHandle, (ams::sf::OutCopyHandle out), (out)) + +AMS_SF_DEFINE_INTERFACE(ams::fssrv::sf, IEventNotifier, AMS_FSSRV_I_EVENT_NOTIFIER_INTERFACE_INFO, 0xA7E3A62C) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_i_file_system_proxy.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_i_file_system_proxy.hpp new file mode 100644 index 00000000..5ccee0cd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_i_file_system_proxy.hpp @@ -0,0 +1,156 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_ifilesystem.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_istorage.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_i_device_operator.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_i_event_notifier.hpp> +#include <stratosphere/fs/fs_error_info.hpp> +#include <stratosphere/fs/fs_memory_report_info.hpp> + +/* ACCURATE_TO_VERSION: 13.4.0.0 */ +#define AMS_FSSRV_I_FILE_SYSTEM_PROXY_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, OpenFileSystem, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, u32 type), (out, path, type), hos::Version_Min, hos::Version_1_0_0) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, SetCurrentProcess, (const ams::sf::ClientProcessId &client_pid), (client_pid)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, OpenDataFileSystemByCurrentProcess, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, OpenFileSystemWithPatch, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, ncm::ProgramId program_id, u32 type), (out, program_id, type), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, OpenFileSystemWithIdObsolete, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, u64 program_id, u32 type), (out, path, program_id, type), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, OpenDataFileSystemByProgramId, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, ncm::ProgramId program_id), (out, program_id), hos::Version_3_0_0) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, OpenFileSystemWithId, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, fs::ContentAttributes attr, u64 program_id, u32 type), (out, path, attr, program_id, type), hos::Version_16_0_0) \ + AMS_SF_METHOD_INFO(C, H, 11, Result, OpenBisFileSystem, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, u32 id), (out, path, id)) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, OpenBisStorage, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, u32 id), (out, id)) \ + AMS_SF_METHOD_INFO(C, H, 13, Result, InvalidateBisCache, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 17, Result, OpenHostFileSystem, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path), (out, path)) \ + AMS_SF_METHOD_INFO(C, H, 18, Result, OpenSdCardFileSystem, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 19, Result, FormatSdCardFileSystem, (), (), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 21, Result, DeleteSaveDataFileSystem, (u64 save_data_id), (save_data_id)) \ + AMS_SF_METHOD_INFO(C, H, 22, Result, CreateSaveDataFileSystem, (const fs::SaveDataAttribute &attribute, const fs::SaveDataCreationInfo &creation_info, const fs::SaveDataMetaInfo &meta_info), (attribute, creation_info, meta_info)) \ + AMS_SF_METHOD_INFO(C, H, 23, Result, CreateSaveDataFileSystemBySystemSaveDataId, (const fs::SaveDataAttribute &attribute, const fs::SaveDataCreationInfo &creation_info), (attribute, creation_info)) \ + AMS_SF_METHOD_INFO(C, H, 24, Result, RegisterSaveDataFileSystemAtomicDeletion, (const ams::sf::InBuffer &save_data_ids), (save_data_ids)) \ + AMS_SF_METHOD_INFO(C, H, 25, Result, DeleteSaveDataFileSystemBySaveDataSpaceId, (u8 indexer_space_id, u64 save_data_id), (indexer_space_id, save_data_id), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 26, Result, FormatSdCardDryRun, (), (), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 27, Result, IsExFatSupported, (ams::sf::Out<bool> out), (out), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 28, Result, DeleteSaveDataFileSystemBySaveDataAttribute, (u8 space_id, const fs::SaveDataAttribute &attribute), (space_id, attribute), hos::Version_4_0_0) \ + AMS_SF_METHOD_INFO(C, H, 30, Result, OpenGameCardStorage, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, u32 handle, u32 partition), (out, handle, partition)) \ + AMS_SF_METHOD_INFO(C, H, 31, Result, OpenGameCardFileSystem, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u32 handle, u32 partition), (out, handle, partition)) \ + AMS_SF_METHOD_INFO(C, H, 32, Result, ExtendSaveDataFileSystem, (u8 space_id, u64 save_data_id, s64 available_size, s64 journal_size), (space_id, save_data_id, available_size, journal_size), hos::Version_3_0_0) \ + AMS_SF_METHOD_INFO(C, H, 33, Result, DeleteCacheStorage, (u16 index), (index), hos::Version_5_0_0) \ + AMS_SF_METHOD_INFO(C, H, 34, Result, GetCacheStorageSize, (ams::sf::Out<s64> out_size, ams::sf::Out<s64> out_journal_size, u16 index), (out_size, out_journal_size, index), hos::Version_5_0_0) \ + AMS_SF_METHOD_INFO(C, H, 35, Result, CreateSaveDataFileSystemWithHashSalt, (const fs::SaveDataAttribute &attribute, const fs::SaveDataCreationInfo &creation_info, const fs::SaveDataMetaInfo &meta_info, const fs::HashSalt &salt), (attribute, creation_info, meta_info, salt), hos::Version_6_0_0) \ + AMS_SF_METHOD_INFO(C, H, 36, Result, OpenHostFileSystemWithOption, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, u32 option), (out, path, option), hos::Version_9_0_0) \ + AMS_SF_METHOD_INFO(C, H, 51, Result, OpenSaveDataFileSystem, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u8 space_id, const fs::SaveDataAttribute &attribute), (out, space_id, attribute)) \ + AMS_SF_METHOD_INFO(C, H, 52, Result, OpenSaveDataFileSystemBySystemSaveDataId, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u8 space_id, const fs::SaveDataAttribute &attribute), (out, space_id, attribute)) \ + AMS_SF_METHOD_INFO(C, H, 53, Result, OpenReadOnlySaveDataFileSystem, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u8 space_id, const fs::SaveDataAttribute &attribute), (out, space_id, attribute), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 57, Result, ReadSaveDataFileSystemExtraDataBySaveDataSpaceId, (const ams::sf::OutBuffer &buffer, u8 space_id, u64 save_data_id), (buffer, space_id, save_data_id), hos::Version_3_0_0) \ + AMS_SF_METHOD_INFO(C, H, 58, Result, ReadSaveDataFileSystemExtraData, (const ams::sf::OutBuffer &buffer, u64 save_data_id), (buffer, save_data_id)) \ + AMS_SF_METHOD_INFO(C, H, 59, Result, WriteSaveDataFileSystemExtraData, (u64 save_data_id, u8 space_id, const ams::sf::InBuffer &buffer), (save_data_id, space_id, buffer), hos::Version_2_0_0) \ + /* AMS_SF_METHOD_INFO(C, H, 60, Result, OpenSaveDataInfoReader, (), ()) */ \ + /* AMS_SF_METHOD_INFO(C, H, 61, Result, OpenSaveDataInfoReaderBySaveDataSpaceId, (), ()) */ \ + /* AMS_SF_METHOD_INFO(C, H, 62, Result, OpenSaveDataInfoReaderOnlyCacheStorage, (), (), hos::Version_5_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 64, Result, OpenSaveDataInternalStorageFileSystem, (), (), hos::Version_5_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 65, Result, UpdateSaveDataMacForDebug, (), (), hos::Version_5_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 66, Result, WriteSaveDataFileSystemExtraDataWithMask, (), (), hos::Version_5_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 67, Result, FindSaveDataWithFilter, (), (), hos::Version_6_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 68, Result, OpenSaveDataInfoReaderWithFilter, (), (), hos::Version_6_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 69, Result, ReadSaveDataFileSystemExtraDataBySaveDataAttribute, (), (), hos::Version_8_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 70, Result, WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute, (), (), hos::Version_8_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 71, Result, ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute, (), (), hos::Version_10_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 80, Result, OpenSaveDataMetaFile, (), ()) */ \ + /* AMS_SF_METHOD_INFO(C, H, 81, Result, OpenSaveDataTransferManager, (), (), hos::Version_4_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 82, Result, OpenSaveDataTransferManagerVersion2, (), (), hos::Version_5_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 83, Result, OpenSaveDataTransferProhibiter, (), (), hos::Version_6_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 84, Result, ListAccessibleSaveDataOwnerId, (), (), hos::Version_6_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 85, Result, OpenSaveDataTransferManagerForSaveDataRepair, (), (), hos::Version_9_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 86, Result, OpenSaveDataMover, (), (), hos::Version_10_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 87, Result, OpenSaveDataTransferManagerForRepair, (), (), hos::Version_11_0_0) */ \ + AMS_SF_METHOD_INFO(C, H, 100, Result, OpenImageDirectoryFileSystem, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u32 id), (out, id)) \ + /* AMS_SF_METHOD_INFO(C, H, 101, Result, OpenBaseFileSystem, (), (), hos::Version_11_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 102, Result, FormatBaseFileSystem, (), (), hos::Version_12_0_0) */ \ + AMS_SF_METHOD_INFO(C, H, 110, Result, OpenContentStorageFileSystem, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u32 id), (out, id)) \ + /* AMS_SF_METHOD_INFO(C, H, 120, Result, OpenCloudBackupWorkStorageFileSystem, (), (), hos::Version_6_0_0, hos::Version_9_2_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 130, Result, OpenCustomStorageFileSystem, (), (), hos::Version_7_0_0) */ \ + AMS_SF_METHOD_INFO(C, H, 200, Result, OpenDataStorageByCurrentProcess, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 201, Result, OpenDataStorageByProgramId, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, ncm::ProgramId program_id), (out, program_id), hos::Version_3_0_0) \ + AMS_SF_METHOD_INFO(C, H, 202, Result, OpenDataStorageByDataId, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, ncm::DataId data_id, u8 storage_id), (out, data_id, storage_id)) \ + AMS_SF_METHOD_INFO(C, H, 203, Result, OpenPatchDataStorageByCurrentProcess, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 204, Result, OpenDataFileSystemWithProgramIndex, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u8 index), (out, index), hos::Version_7_0_0) \ + AMS_SF_METHOD_INFO(C, H, 205, Result, OpenDataStorageWithProgramIndex, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, u8 index), (out, index), hos::Version_7_0_0) \ + AMS_SF_METHOD_INFO(C, H, 206, Result, OpenDataStorageByPathObsolete, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, const fssrv::sf::FspPath &path, u32 type), (out, path, type), hos::Version_13_0_0, hos::Version_15_0_1) \ + AMS_SF_METHOD_INFO(C, H, 206, Result, OpenDataStorageByPath, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, const fssrv::sf::FspPath &path, fs::ContentAttributes attr, u32 type), (out, path, attr, type), hos::Version_16_0_0) \ + AMS_SF_METHOD_INFO(C, H, 400, Result, OpenDeviceOperator, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IDeviceOperator>> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 500, Result, OpenSdCardDetectionEventNotifier, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IEventNotifier>> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 501, Result, OpenGameCardDetectionEventNotifier, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IEventNotifier>> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 510, Result, OpenSystemDataUpdateEventNotifier, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IEventNotifier>> out), (out), hos::Version_5_0_0) \ + AMS_SF_METHOD_INFO(C, H, 511, Result, NotifySystemDataUpdateEvent, (), (), hos::Version_5_0_0) \ + /* AMS_SF_METHOD_INFO(C, H, 520, Result, SimulateDeviceDetectionEvent, (), (), hos::Version_6_0_0) */ \ + AMS_SF_METHOD_INFO(C, H, 600, Result, SetCurrentPosixTime, (s64 posix_time), (posix_time), hos::Version_Min, hos::Version_3_0_2) \ + /* AMS_SF_METHOD_INFO(C, H, 601, Result, QuerySaveDataTotalSize, (), ()) */ \ + /* AMS_SF_METHOD_INFO(C, H, 602, Result, VerifySaveDataFileSystem, (), ()) */ \ + /* AMS_SF_METHOD_INFO(C, H, 603, Result, CorruptSaveDataFileSystem, (), ()) */ \ + /* AMS_SF_METHOD_INFO(C, H, 604, Result, CreatePaddingFile, (), ()) */ \ + /* AMS_SF_METHOD_INFO(C, H, 605, Result, DeleteAllPaddingFiles, (), ()) */ \ + AMS_SF_METHOD_INFO(C, H, 606, Result, GetRightsId, (ams::sf::Out<fs::RightsId> out, ncm::ProgramId program_id, ncm::StorageId storage_id), (out, program_id, storage_id), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 607, Result, RegisterExternalKey, (const fs::RightsId &rights_id, const spl::AccessKey &access_key), (rights_id, access_key), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 608, Result, UnregisterAllExternalKey, (), (), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 609, Result, GetRightsIdByPath, (ams::sf::Out<fs::RightsId> out, const fssrv::sf::FspPath &path), (out, path), hos::Version_2_0_0, hos::Version_15_0_1) \ + AMS_SF_METHOD_INFO(C, H, 610, Result, GetRightsIdAndKeyGenerationByPathObsolete, (ams::sf::Out<fs::RightsId> out, ams::sf::Out<u8> out_key_generation, const fssrv::sf::FspPath &path), (out, out_key_generation, path), hos::Version_3_0_0) \ + AMS_SF_METHOD_INFO(C, H, 610, Result, GetRightsIdAndKeyGenerationByPath, (ams::sf::Out<fs::RightsId> out, ams::sf::Out<u8> out_key_generation, const fssrv::sf::FspPath &path, fs::ContentAttributes attr), (out, out_key_generation, path, attr), hos::Version_16_0_0) \ + AMS_SF_METHOD_INFO(C, H, 611, Result, SetCurrentPosixTimeWithTimeDifference, (s64 posix_time, s32 time_difference), (posix_time, time_difference), hos::Version_4_0_0) \ + AMS_SF_METHOD_INFO(C, H, 612, Result, GetFreeSpaceSizeForSaveData, (ams::sf::Out<s64> out, u8 space_id), (out, space_id), hos::Version_4_0_0) \ + AMS_SF_METHOD_INFO(C, H, 613, Result, VerifySaveDataFileSystemBySaveDataSpaceId, (), (), hos::Version_4_0_0) \ + AMS_SF_METHOD_INFO(C, H, 614, Result, CorruptSaveDataFileSystemBySaveDataSpaceId, (), (), hos::Version_4_0_0) \ + AMS_SF_METHOD_INFO(C, H, 615, Result, QuerySaveDataInternalStorageTotalSize, (), (), hos::Version_5_0_0) \ + AMS_SF_METHOD_INFO(C, H, 616, Result, GetSaveDataCommitId, (), (), hos::Version_6_0_0) \ + AMS_SF_METHOD_INFO(C, H, 617, Result, UnregisterExternalKey, (const fs::RightsId &rights_id), (rights_id), hos::Version_7_0_0) \ + AMS_SF_METHOD_INFO(C, H, 618, Result, GetProgramId, (ams::sf::Out<ncm::ProgramId> out, const fssrv::sf::FspPath &path, fs::ContentAttributes attr), (out, path, attr), hos::Version_17_0_0) \ + AMS_SF_METHOD_INFO(C, H, 620, Result, SetSdCardEncryptionSeed, (const fs::EncryptionSeed &seed), (seed), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 630, Result, SetSdCardAccessibility, (bool accessible), (accessible), hos::Version_4_0_0) \ + AMS_SF_METHOD_INFO(C, H, 631, Result, IsSdCardAccessible, (ams::sf::Out<bool> out), (out), hos::Version_4_0_0) \ + AMS_SF_METHOD_INFO(C, H, 640, Result, IsSignedSystemPartitionOnSdCardValid, (ams::sf::Out<bool> out), (out), hos::Version_4_0_0, hos::Version_7_0_1) \ + AMS_SF_METHOD_INFO(C, H, 700, Result, OpenAccessFailureDetectionEventNotifier, (), (), hos::Version_5_0_0) \ + /* AMS_SF_METHOD_INFO(C, H, 701, Result, GetAccessFailureDetectionEvent, (), (), hos::Version_5_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 702, Result, IsAccessFailureDetected, (), (), hos::Version_5_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 710, Result, ResolveAccessFailure, (), (), hos::Version_5_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 720, Result, AbandonAccessFailure, (), (), hos::Version_5_0_0) */ \ + AMS_SF_METHOD_INFO(C, H, 800, Result, GetAndClearErrorInfo, (ams::sf::Out<fs::FileSystemProxyErrorInfo> out), (out), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 810, Result, RegisterProgramIndexMapInfo, (const ams::sf::InBuffer &buffer, s32 count), (buffer, count), hos::Version_7_0_0) \ + AMS_SF_METHOD_INFO(C, H, 1000, Result, SetBisRootForHost, (u32 id, const fssrv::sf::FspPath &path), (id, path), hos::Version_Min, hos::Version_9_2_0) \ + AMS_SF_METHOD_INFO(C, H, 1001, Result, SetSaveDataSize, (s64 size, s64 journal_size), (size, journal_size)) \ + AMS_SF_METHOD_INFO(C, H, 1002, Result, SetSaveDataRootPath, (const fssrv::sf::FspPath &path), (path)) \ + AMS_SF_METHOD_INFO(C, H, 1003, Result, DisableAutoSaveDataCreation, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 1004, Result, SetGlobalAccessLogMode, (u32 mode), (mode)) \ + AMS_SF_METHOD_INFO(C, H, 1005, Result, GetGlobalAccessLogMode, (ams::sf::Out<u32> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 1006, Result, OutputAccessLogToSdCard, (const ams::sf::InBuffer &buf), (buf)) \ + AMS_SF_METHOD_INFO(C, H, 1007, Result, RegisterUpdatePartition, (), (), hos::Version_4_0_0) \ + AMS_SF_METHOD_INFO(C, H, 1008, Result, OpenRegisteredUpdatePartition, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out), (out), hos::Version_4_0_0) \ + AMS_SF_METHOD_INFO(C, H, 1009, Result, GetAndClearMemoryReportInfo, (ams::sf::Out<fs::MemoryReportInfo> out), (out), hos::Version_4_0_0) \ + /* AMS_SF_METHOD_INFO(C, H, 1010, Result, SetDataStorageRedirectTarget, (), (), hos::Version_5_1_0, hos::Version_6_2_0) */ \ + AMS_SF_METHOD_INFO(C, H, 1011, Result, GetProgramIndexForAccessLog, (ams::sf::Out<u32> out_idx, ams::sf::Out<u32> out_count), (out_idx, out_count), hos::Version_7_0_0) \ + AMS_SF_METHOD_INFO(C, H, 1012, Result, GetFsStackUsage, (ams::sf::Out<u32> out, u32 type), (out, type), hos::Version_9_0_0) \ + AMS_SF_METHOD_INFO(C, H, 1013, Result, UnsetSaveDataRootPath, (), (), hos::Version_9_0_0) \ + AMS_SF_METHOD_INFO(C, H, 1014, Result, OutputMultiProgramTagAccessLog, (), (), hos::Version_10_0_0, hos::Version_10_2_0) \ + AMS_SF_METHOD_INFO(C, H, 1016, Result, FlushAccessLogOnSdCard, (), (), hos::Version_11_0_0) \ + AMS_SF_METHOD_INFO(C, H, 1017, Result, OutputApplicationInfoAccessLog, (), (), hos::Version_11_0_0) \ + AMS_SF_METHOD_INFO(C, H, 1018, Result, RegisterDebugConfiguration, (u32 key, s64 value), (key, value), hos::Version_13_0_0) \ + AMS_SF_METHOD_INFO(C, H, 1019, Result, UnregisterDebugConfiguration, (u32 key), (key), hos::Version_13_0_0) \ + AMS_SF_METHOD_INFO(C, H, 1100, Result, OverrideSaveDataTransferTokenSignVerificationKey, (const ams::sf::InBuffer &buf), (buf), hos::Version_4_0_0) \ + AMS_SF_METHOD_INFO(C, H, 1110, Result, CorruptSaveDataFileSystemByOffset, (u8 space_id, u64 save_data_id, s64 offset), (space_id, save_data_id, offset), hos::Version_6_0_0) \ + /* AMS_SF_METHOD_INFO(C, H, 1200, Result, OpenMultiCommitManager, (), (), hos::Version_6_0_0) */ \ + /* AMS_SF_METHOD_INFO(C, H, 1300, Result, OpenBisWiper, (), (), hos::Version_10_0_0) */ + +AMS_SF_DEFINE_INTERFACE(ams::fssrv::sf, IFileSystemProxy, AMS_FSSRV_I_FILE_SYSTEM_PROXY_INTERFACE_INFO, 0x7DF34ED2) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_i_file_system_proxy_for_loader.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_i_file_system_proxy_for_loader.hpp new file mode 100644 index 00000000..0e55e256 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_i_file_system_proxy_for_loader.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_ifilesystem.hpp> +#include <stratosphere/fs/fs_code_verification_data.hpp> +#include <stratosphere/fs/fs_content_attributes.hpp> + +/* ACCURATE_TO_VERSION: 17.5.0.0 */ +#define AMS_FSSRV_I_FILE_SYSTEM_PROXY_FOR_LOADER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, OpenCodeFileSystemDeprecated, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, const fssrv::sf::Path &path, ncm::ProgramId program_id), (out_fs, path, program_id), hos::Version_Min, hos::Version_9_2_0) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, OpenCodeFileSystemDeprecated2, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, ams::sf::Out<fs::CodeVerificationData> out_verif, const fssrv::sf::Path &path, ncm::ProgramId program_id), (out_fs, out_verif, path, program_id), hos::Version_10_0_0, hos::Version_15_0_1) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, OpenCodeFileSystemDeprecated3, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, ams::sf::Out<fs::CodeVerificationData> out_verif, const fssrv::sf::Path &path, fs::ContentAttributes attr, ncm::ProgramId program_id), (out_fs, out_verif, path, attr, program_id), hos::Version_16_0_0, hos::Version_16_1_0) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, OpenCodeFileSystemDeprecated4, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, const ams::sf::OutBuffer &out_verif, const fssrv::sf::Path &path, fs::ContentAttributes attr, ncm::ProgramId program_id), (out_fs, out_verif, path, attr, program_id), hos::Version_17_0_0, hos::Version_19_0_1) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, OpenCodeFileSystem, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, const ams::sf::OutBuffer &out_verif, fs::ContentAttributes attr, ncm::ProgramId program_id, ncm::StorageId storage_id), (out_fs, out_verif, attr, program_id, storage_id), hos::Version_20_0_0) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, IsArchivedProgram, (ams::sf::Out<bool> out, u64 process_id), (out, process_id)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, SetCurrentProcess, (const ams::sf::ClientProcessId &client_pid), (client_pid), hos::Version_4_0_0) + +AMS_SF_DEFINE_INTERFACE(ams::fssrv::sf, IFileSystemProxyForLoader, AMS_FSSRV_I_FILE_SYSTEM_PROXY_FOR_LOADER_INTERFACE_INFO, 0xDC92EE15) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_i_program_registry.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_i_program_registry.hpp new file mode 100644 index 00000000..3520a934 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_i_program_registry.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/fs/fs_code.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_ifilesystem.hpp> + +/* ACCURATE_TO_VERSION: 13.4.0.0 */ +#define AMS_FSSRV_I_PROGRAM_REGISTRY_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, RegisterProgram, (u64 process_id, u64 program_id, u8 storage_id, const ams::sf::InBuffer &data, s64 data_size, const ams::sf::InBuffer &desc, s64 desc_size), (process_id, program_id, storage_id, data, data_size, desc, desc_size)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, UnregisterProgram, (u64 process_id), (process_id)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, SetCurrentProcess, (const ams::sf::ClientProcessId &client_pid), (client_pid)) \ + AMS_SF_METHOD_INFO(C, H, 256, Result, SetEnabledProgramVerification, (bool en), (en)) + +AMS_SF_DEFINE_INTERFACE(ams::fssrv::sf, IProgramRegistry, AMS_FSSRV_I_PROGRAM_REGISTRY_INTERFACE_INFO, 0xDA73738C) + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_idirectory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_idirectory.hpp new file mode 100644 index 00000000..9d598263 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_idirectory.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/fs/fs_directory.hpp> + +/* ACCURATE_TO_VERSION: 13.4.0.0 */ +#define AMS_FSSRV_I_DIRECTORY_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, Read, (ams::sf::Out<s64> out, const ams::sf::OutBuffer &out_entries), (out, out_entries)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, GetEntryCount, (ams::sf::Out<s64> out), (out)) + +AMS_SF_DEFINE_INTERFACE(ams::fssrv::sf, IDirectory, AMS_FSSRV_I_DIRECTORY_INTERFACE_INFO, 0xB4953DB6) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_ifile.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_ifile.hpp new file mode 100644 index 00000000..dca1ba7f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_ifile.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/fs/fs_file.hpp> +#include <stratosphere/fs/fs_query_range.hpp> + +/* ACCURATE_TO_VERSION: 13.4.0.0 */ +#define AMS_FSSRV_I_FILE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, Read, (ams::sf::Out<s64> out, s64 offset, const ams::sf::OutNonSecureBuffer &buffer, s64 size, ams::fs::ReadOption option), (out, offset, buffer, size, option)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, Write, (s64 offset, const ams::sf::InNonSecureBuffer &buffer, s64 size, ams::fs::WriteOption option), (offset, buffer, size, option)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, Flush, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, SetSize, (s64 size), (size)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, GetSize, (ams::sf::Out<s64> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, OperateRange, (ams::sf::Out<ams::fs::FileQueryRangeInfo> out, s32 op_id, s64 offset, s64 size), (out, op_id, offset, size), hos::Version_4_0_0) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, OperateRangeWithBuffer, (const ams::sf::OutNonSecureBuffer &out_buf, const ams::sf::InNonSecureBuffer &in_buf, s32 op_id, s64 offset, s64 size), (out_buf, in_buf, op_id, offset, size), hos::Version_12_0_0) + +AMS_SF_DEFINE_INTERFACE(ams::fssrv::sf, IFile, AMS_FSSRV_I_FILE_INTERFACE_INFO, 0xF3716DA1) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_ifilesystem.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_ifilesystem.hpp new file mode 100644 index 00000000..7e9cceda --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_ifilesystem.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/fs/fs_filesystem.hpp> +#include <stratosphere/fs/fs_filesystem_for_debug.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_path.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_ifile.hpp> +#include <stratosphere/fssrv/sf/fssrv_sf_idirectory.hpp> + +/* ACCURATE_TO_VERSION: 13.4.0.0 */ +#define AMS_FSSRV_I_FILESYSTEM_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, CreateFile, (const ams::fssrv::sf::Path &path, s64 size, s32 option), (path, size, option)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, DeleteFile, (const ams::fssrv::sf::Path &path), (path)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, CreateDirectory, (const ams::fssrv::sf::Path &path), (path)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, DeleteDirectory, (const ams::fssrv::sf::Path &path), (path)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, DeleteDirectoryRecursively, (const ams::fssrv::sf::Path &path), (path)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, RenameFile, (const ams::fssrv::sf::Path &old_path, const ams::fssrv::sf::Path &new_path), (old_path, new_path)) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, RenameDirectory, (const ams::fssrv::sf::Path &old_path, const ams::fssrv::sf::Path &new_path), (old_path, new_path)) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, GetEntryType, (ams::sf::Out<u32> out, const ams::fssrv::sf::Path &path), (out, path)) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, OpenFile, (ams::sf::Out<ams::sf::SharedPointer<ams::fssrv::sf::IFile>> out, const ams::fssrv::sf::Path &path, u32 mode), (out, path, mode)) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, OpenDirectory, (ams::sf::Out<ams::sf::SharedPointer<ams::fssrv::sf::IDirectory>> out, const ams::fssrv::sf::Path &path, u32 mode), (out, path, mode)) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, Commit, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 11, Result, GetFreeSpaceSize, (ams::sf::Out<s64> out, const ams::fssrv::sf::Path &path), (out, path)) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, GetTotalSpaceSize, (ams::sf::Out<s64> out, const ams::fssrv::sf::Path &path), (out, path)) \ + AMS_SF_METHOD_INFO(C, H, 13, Result, CleanDirectoryRecursively, (const ams::fssrv::sf::Path &path), (path), hos::Version_3_0_0) \ + AMS_SF_METHOD_INFO(C, H, 14, Result, GetFileTimeStampRaw, (ams::sf::Out<ams::fs::FileTimeStampRaw> out, const ams::fssrv::sf::Path &path), (out, path), hos::Version_3_0_0) \ + AMS_SF_METHOD_INFO(C, H, 15, Result, QueryEntry, (const ams::sf::OutNonSecureBuffer &out_buf, const ams::sf::InNonSecureBuffer &in_buf, s32 query_id, const ams::fssrv::sf::Path &path), (out_buf, in_buf, query_id, path), hos::Version_4_0_0) + +AMS_SF_DEFINE_INTERFACE(ams::fssrv::sf, IFileSystem, AMS_FSSRV_I_FILESYSTEM_INTERFACE_INFO, 0xD4EA59E7) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_istorage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_istorage.hpp new file mode 100644 index 00000000..6cec83eb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_istorage.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/fs/fs_file.hpp> +#include <stratosphere/fs/fs_query_range.hpp> + +/* ACCURATE_TO_VERSION: 13.4.0.0 */ +#define AMS_FSSRV_I_STORAGE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, Read, (s64 offset, const ams::sf::OutNonSecureBuffer &buffer, s64 size), (offset, buffer, size)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, Write, (s64 offset, const ams::sf::InNonSecureBuffer &buffer, s64 size), (offset, buffer, size)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, Flush, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, SetSize, (s64 size), (size)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, GetSize, (ams::sf::Out<s64> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, OperateRange, (ams::sf::Out<ams::fs::StorageQueryRangeInfo> out, s32 op_id, s64 offset, s64 size), (out, op_id, offset, size), hos::Version_4_0_0) + +AMS_SF_DEFINE_INTERFACE(ams::fssrv::sf, IStorage, AMS_FSSRV_I_STORAGE_INTERFACE_INFO, 0xC4D2CAEB) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_path.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_path.hpp new file mode 100644 index 00000000..7c099c63 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssrv/sf/fssrv_sf_path.hpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_directory.hpp> +#include <stratosphere/sf/sf_buffer_tags.hpp> + +namespace ams::fssrv::sf { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + struct Path : public ams::sf::LargeData { + char str[fs::EntryNameLengthMax + 1]; + + static constexpr Path Encode(const char *p) { + Path path = {}; + for (size_t i = 0; i < sizeof(path) - 1; i++) { + path.str[i] = p[i]; + if (p[i] == '\x00') { + break; + } + } + return path; + } + + static constexpr size_t GetPathLength(const Path &path) { + size_t len = 0; + for (size_t i = 0; i < sizeof(path) - 1 && path.str[i] != '\x00'; i++) { + len++; + } + return len; + } + }; + static_assert(util::is_pod<Path>::value); + + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(sizeof(Path) == FS_MAX_PATH); + #endif + + using FspPath = Path; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem.hpp new file mode 100644 index 00000000..9121b0e7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem.hpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fssystem/fssystem_allocator_utility.hpp> +#include <stratosphere/fssystem/fssystem_utility.hpp> +#include <stratosphere/fssystem/fssystem_bitmap_utils.hpp> +#include <stratosphere/fssystem/fssystem_speed_emulation_configuration.hpp> +#include <stratosphere/fssystem/fssystem_external_code.hpp> +#include <stratosphere/fssystem/fssystem_forwarding_file_system.hpp> +#include <stratosphere/fssystem/fssystem_partition_file_system.hpp> +#include <stratosphere/fssystem/fssystem_partition_file_system_meta.hpp> +#include <stratosphere/fssystem/fssystem_thread_priority_changer.hpp> +#include <stratosphere/fssystem/fssystem_aes_ctr_storage.hpp> +#include <stratosphere/fssystem/fssystem_aes_xts_storage.hpp> +#include <stratosphere/fssystem/fssystem_subdirectory_filesystem.hpp> +#include <stratosphere/fssystem/fssystem_directory_redirection_filesystem.hpp> +#include <stratosphere/fssystem/fssystem_directory_savedata_filesystem.hpp> +#include <stratosphere/fssystem/fssystem_romfs_file_system.hpp> +#include <stratosphere/fssystem/fssystem_bucket_tree.hpp> +#include <stratosphere/fssystem/fssystem_bucket_tree_template_impl.hpp> +#include <stratosphere/fssystem/fssystem_indirect_storage.hpp> +#include <stratosphere/fssystem/fssystem_indirect_storage_template_impl.hpp> +#include <stratosphere/fssystem/fssystem_sparse_storage.hpp> +#include <stratosphere/fssystem/fssystem_nca_header.hpp> +#include <stratosphere/fssystem/fssystem_nca_file_system_driver.hpp> +#include <stratosphere/fssystem/fssystem_crypto_configuration.hpp> +#include <stratosphere/fssystem/fssystem_compression_configuration.hpp> +#include <stratosphere/fssystem/fssystem_aes_ctr_counter_extended_storage.hpp> +#include <stratosphere/fssystem/fssystem_aes_ctr_storage_external.hpp> +#include <stratosphere/fssystem/fssystem_aes_xts_storage_external.hpp> +#include <stratosphere/fssystem/fssystem_switch_storage.hpp> +#include <stratosphere/fssystem/buffers/fssystem_buffer_manager_utils.hpp> +#include <stratosphere/fssystem/buffers/fssystem_file_system_buffer_manager.hpp> +#include <stratosphere/fssystem/fssystem_pooled_buffer.hpp> +#include <stratosphere/fssystem/fssystem_service_context.hpp> +#include <stratosphere/fssystem/fssystem_alignment_matching_storage_impl.hpp> +#include <stratosphere/fssystem/fssystem_alignment_matching_storage.hpp> +#include <stratosphere/fssystem/fssystem_compressed_storage.hpp> +#include <stratosphere/fssystem/fssystem_buffered_storage.hpp> +#include <stratosphere/fssystem/fssystem_hierarchical_integrity_verification_storage.hpp> +#include <stratosphere/fssystem/fssystem_integrity_romfs_storage.hpp> +#include <stratosphere/fssystem/fssystem_sha_hash_generator.hpp> +#include <stratosphere/fssystem/fssystem_local_file_system.hpp> +#include <stratosphere/fssystem/fssystem_file_system_proxy_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_buffer_manager_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_buffer_manager_utils.hpp new file mode 100644 index 00000000..eaafa007 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_buffer_manager_utils.hpp @@ -0,0 +1,131 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/fs/fs_i_buffer_manager.hpp> + +namespace ams::fssystem::buffers { + + namespace impl { + + constexpr inline auto RetryWait = TimeSpan::FromMilliSeconds(10); + + } + + /* ACCURATE_TO_VERSION: Unknown */ + template<typename F, typename OnFailure> + Result DoContinuouslyUntilBufferIsAllocated(F f, OnFailure on_failure, const char *function_name) { + constexpr auto BufferAllocationRetryLogCountMax = 10; + constexpr auto BufferAllocationRetryLogInterval = 100; + for (auto count = 1; true; count++) { + R_TRY_CATCH(f()) { + R_CATCH(fs::ResultBufferAllocationFailed) { + if ((1 <= count && count <= BufferAllocationRetryLogCountMax) || ((count % BufferAllocationRetryLogInterval) == 0)) { + /* TODO: Log */ + AMS_UNUSED(function_name); + } + R_TRY(on_failure()); + + os::SleepThread(impl::RetryWait); + + continue; + } + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + } + + template<typename F> + Result DoContinuouslyUntilBufferIsAllocated(F f, const char *function_name) { + R_TRY(DoContinuouslyUntilBufferIsAllocated(f, []() ALWAYS_INLINE_LAMBDA { R_SUCCEED(); }, function_name)); + R_SUCCEED(); + } + + /* ACCURATE_TO_VERSION: Unknown */ + class BufferManagerContext { + private: + bool m_needs_blocking; + public: + constexpr BufferManagerContext() : m_needs_blocking(false) { /* ... */ } + public: + bool IsNeedBlocking() const { return m_needs_blocking; } + + void SetNeedBlocking(bool need) { m_needs_blocking = need; } + }; + + void RegisterBufferManagerContext(const BufferManagerContext *context); + BufferManagerContext *GetBufferManagerContext(); + void EnableBlockingBufferManagerAllocation(); + + /* ACCURATE_TO_VERSION: Unknown */ + class ScopedBufferManagerContextRegistration { + private: + BufferManagerContext m_cur_context; + const BufferManagerContext *m_old_context; + public: + ALWAYS_INLINE explicit ScopedBufferManagerContextRegistration() { + m_old_context = GetBufferManagerContext(); + if (m_old_context != nullptr) { + m_cur_context = *m_old_context; + } + RegisterBufferManagerContext(std::addressof(m_cur_context)); + } + + ALWAYS_INLINE ~ScopedBufferManagerContextRegistration() { + RegisterBufferManagerContext(m_old_context); + } + }; + + /* ACCURATE_TO_VERSION: Unknown */ + template<typename IsValidBufferFunction> + Result AllocateBufferUsingBufferManagerContext(fs::IBufferManager::MemoryRange *out, fs::IBufferManager *buffer_manager, size_t size, const fs::IBufferManager::BufferAttribute attribute, IsValidBufferFunction is_valid_buffer, const char *func_name) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(buffer_manager != nullptr); + AMS_ASSERT(func_name != nullptr); + + /* Clear the output. */ + *out = fs::IBufferManager::MakeMemoryRange(0, 0); + + /* Get the context. */ + auto context = GetBufferManagerContext(); + + auto AllocateBufferImpl = [=]() -> Result { + auto buffer = buffer_manager->AllocateBuffer(size, attribute); + if (!is_valid_buffer(buffer)) { + if (buffer.first != 0) { + buffer_manager->DeallocateBuffer(buffer.first, buffer.second); + } + R_THROW(fs::ResultBufferAllocationFailed()); + } + *out = buffer; + R_SUCCEED(); + }; + + if (context == nullptr || !context->IsNeedBlocking()) { + /* If there's no context (or we don't need to block), just allocate the buffer. */ + R_TRY(AllocateBufferImpl()); + } else { + /* Otherwise, try to allocate repeatedly. */ + R_TRY(DoContinuouslyUntilBufferIsAllocated(AllocateBufferImpl, func_name)); + } + + AMS_ASSERT(out->first != 0); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_file_system_buddy_heap.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_file_system_buddy_heap.hpp new file mode 100644 index 00000000..f1b6e5f2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_file_system_buddy_heap.hpp @@ -0,0 +1,193 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> +#include <stratosphere/fs/fs_i_buffer_manager.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: Unknown */ + class FileSystemBuddyHeap { + NON_COPYABLE(FileSystemBuddyHeap); + NON_MOVEABLE(FileSystemBuddyHeap); + public: + static constexpr size_t BufferAlignment = sizeof(void *); + static constexpr size_t BlockSizeMin = 2 * sizeof(void *); + static constexpr s32 OrderUpperLimit = BITSIZEOF(s32) - 1; + private: + class PageList; + + struct PageEntry { PageEntry *next; }; + static_assert(util::is_pod<PageEntry>::value); + static_assert(sizeof(PageEntry) <= BlockSizeMin); + + class PageList : public ::ams::fs::impl::Newable { + NON_COPYABLE(PageList); + NON_MOVEABLE(PageList); + private: + PageEntry *m_first_page_entry; + PageEntry *m_last_page_entry; + s32 m_entry_count; + public: + constexpr PageList() : m_first_page_entry(), m_last_page_entry(), m_entry_count() { /* ... */ } + + constexpr bool IsEmpty() const { return m_entry_count == 0; } + constexpr s32 GetSize() const { return m_entry_count; } + + constexpr const PageEntry *GetFront() const { return m_first_page_entry; } + public: + PageEntry *PopFront(); + void PushBack(PageEntry *page_entry); + bool Remove(PageEntry *page_entry); + }; + private: + size_t m_block_size; + s32 m_order_max; + uintptr_t m_heap_start; + size_t m_heap_size; + + PageList *m_free_lists; + size_t m_total_free_size; + PageList *m_external_free_lists; + std::unique_ptr<PageList[]> m_internal_free_lists; + public: + static constexpr s32 GetBlockCountFromOrder(s32 order) { + AMS_ASSERT(0 <= order); + AMS_ASSERT(order < OrderUpperLimit); + return (1 << order); + } + + static constexpr size_t QueryWorkBufferSize(s32 order_max) { + AMS_ASSERT(0 < order_max && order_max < OrderUpperLimit); + return util::AlignUp(sizeof(PageList) * (order_max + 1) + alignof(PageList), alignof(u64)); + } + + static constexpr s32 QueryOrderMax(size_t size, size_t block_size) { + AMS_ASSERT(size >= block_size); + AMS_ASSERT(block_size >= BlockSizeMin); + AMS_ASSERT(util::IsPowerOfTwo(block_size)); + const auto block_count = static_cast<s32>(util::AlignUp(size, block_size) / block_size); + for (auto order = 1; true; order++) { + if (block_count <= GetBlockCountFromOrder(order)) { + return order; + } + } + } + + public: + constexpr FileSystemBuddyHeap() : m_block_size(), m_order_max(), m_heap_start(), m_heap_size(), m_free_lists(), m_total_free_size(), m_external_free_lists(), m_internal_free_lists() { /* ... */ } + + Result Initialize(uintptr_t address, size_t size, size_t block_size, s32 order_max); + + Result Initialize(uintptr_t address, size_t size, size_t block_size) { + R_RETURN(this->Initialize(address, size, block_size, QueryOrderMax(size, block_size))); + } + + Result Initialize(uintptr_t address, size_t size, size_t block_size, s32 order_max, void *work, size_t work_size) { + AMS_ASSERT(work_size >= QueryWorkBufferSize(order_max)); + AMS_UNUSED(work_size); + + const auto aligned_work = util::AlignUp(reinterpret_cast<uintptr_t>(work), alignof(PageList)); + m_external_free_lists = reinterpret_cast<PageList *>(aligned_work); + R_RETURN(this->Initialize(address, size, block_size, order_max)); + } + + Result Initialize(uintptr_t address, size_t size, size_t block_size, void *work, size_t work_size) { + R_RETURN(this->Initialize(address, size, block_size, QueryOrderMax(size, block_size), work, work_size)); + } + + void Finalize(); + + void *AllocateByOrder(s32 order); + void Free(void *ptr, s32 order); + + size_t GetTotalFreeSize() const; + size_t GetAllocatableSizeMax() const; + void Dump() const; + + s32 GetOrderFromBytes(size_t size) const { + AMS_ASSERT(m_free_lists != nullptr); + return this->GetOrderFromBlockCount(this->GetBlockCountFromSize(size)); + } + + size_t GetBytesFromOrder(s32 order) const { + AMS_ASSERT(m_free_lists != nullptr); + AMS_ASSERT(0 <= order); + AMS_ASSERT(order <= this->GetOrderMax()); + return (this->GetBlockSize() << order); + } + + s32 GetOrderMax() const { + AMS_ASSERT(m_free_lists != nullptr); + return m_order_max; + } + + size_t GetBlockSize() const { + AMS_ASSERT(m_free_lists != nullptr); + return m_block_size; + } + + s32 GetPageBlockCountMax() const { + AMS_ASSERT(m_free_lists != nullptr); + return 1 << this->GetOrderMax(); + } + + size_t GetPageSizeMax() const { + AMS_ASSERT(m_free_lists != nullptr); + return this->GetPageBlockCountMax() * this->GetBlockSize(); + } + private: + void DivideBuddies(PageEntry *page_entry, s32 required_order, s32 chosen_order); + void JoinBuddies(PageEntry *page_entry, s32 order); + PageEntry *GetBuddy(PageEntry *page_entry, s32 order); + PageEntry *GetFreePageEntry(s32 order); + + s32 GetOrderFromBlockCount(s32 block_count) const; + + s32 GetBlockCountFromSize(size_t size) const { + const size_t bsize = this->GetBlockSize(); + return static_cast<s32>(util::AlignUp(size, bsize) / bsize); + } + + uintptr_t GetAddressFromPageEntry(const PageEntry &page_entry) const { + const uintptr_t address = reinterpret_cast<uintptr_t>(std::addressof(page_entry)); + AMS_ASSERT(m_heap_start <= address); + AMS_ASSERT(address < m_heap_start + m_heap_size); + AMS_ASSERT(util::IsAligned(address - m_heap_start, this->GetBlockSize())); + return address; + } + + PageEntry *GetPageEntryFromAddress(uintptr_t address) const { + AMS_ASSERT(m_heap_start <= address); + AMS_ASSERT(address < m_heap_start + m_heap_size); + return reinterpret_cast<PageEntry *>(m_heap_start + util::AlignDown(address - m_heap_start, this->GetBlockSize())); + } + + s32 GetIndexFromPageEntry(const PageEntry &page_entry) const { + const uintptr_t address = reinterpret_cast<uintptr_t>(std::addressof(page_entry)); + AMS_ASSERT(m_heap_start <= address); + AMS_ASSERT(address < m_heap_start + m_heap_size); + AMS_ASSERT(util::IsAligned(address - m_heap_start, this->GetBlockSize())); + return static_cast<s32>((address - m_heap_start) / this->GetBlockSize()); + } + + bool IsAlignedToOrder(const PageEntry *page_entry, s32 order) const { + return util::IsAligned(GetIndexFromPageEntry(*page_entry), GetBlockCountFromOrder(order)); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_file_system_buffer_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_file_system_buffer_manager.hpp new file mode 100644 index 00000000..1d81ca07 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_file_system_buffer_manager.hpp @@ -0,0 +1,307 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/lmem.hpp> +#include <stratosphere/fs/fs_memory_management.hpp> +#include <stratosphere/fs/fs_i_buffer_manager.hpp> +#include <stratosphere/fssystem/buffers/fssystem_file_system_buddy_heap.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: Unknown */ + class FileSystemBufferManager : public fs::IBufferManager { + NON_COPYABLE(FileSystemBufferManager); + NON_MOVEABLE(FileSystemBufferManager); + public: + using BuddyHeap = FileSystemBuddyHeap; + private: + class CacheHandleTable { + NON_COPYABLE(CacheHandleTable); + NON_MOVEABLE(CacheHandleTable); + private: + class Entry { + private: + CacheHandle m_handle; + uintptr_t m_address; + size_t m_size; + BufferAttribute m_attr; + public: + constexpr void Initialize(CacheHandle h, uintptr_t a, size_t sz, BufferAttribute t) { + m_handle = h; + m_address = a; + m_size = sz; + m_attr = t; + } + + constexpr CacheHandle GetHandle() const { + return m_handle; + } + + constexpr uintptr_t GetAddress() const { + return m_address; + } + + constexpr size_t GetSize() const { + return m_size; + } + + constexpr BufferAttribute GetBufferAttribute() const { + return m_attr; + } + }; + + class AttrInfo : public util::IntrusiveListBaseNode<AttrInfo>, public ::ams::fs::impl::Newable { + NON_COPYABLE(AttrInfo); + NON_MOVEABLE(AttrInfo); + private: + s32 m_level; + s32 m_cache_count; + size_t m_cache_size; + public: + constexpr AttrInfo(s32 l, s32 cc, size_t cs) : m_level(l), m_cache_count(cc), m_cache_size(cs) { + /* ... */ + } + + constexpr s32 GetLevel() const { + return m_level; + } + + constexpr s32 GetCacheCount() const { + return m_cache_count; + } + + constexpr void IncrementCacheCount() { + ++m_cache_count; + } + + constexpr void DecrementCacheCount() { + --m_cache_count; + } + + constexpr size_t GetCacheSize() const { + return m_cache_size; + } + + constexpr void AddCacheSize(size_t diff) { + m_cache_size += diff; + } + + constexpr void SubtractCacheSize(size_t diff) { + AMS_ASSERT(m_cache_size >= diff); + m_cache_size -= diff; + } + + using Newable::operator new; + using Newable::operator delete; + + static ALWAYS_INLINE void *operator new(size_t, void *p) noexcept { return p; } + static ALWAYS_INLINE void operator delete(void *, size_t, void*) noexcept { /* ... */ } + }; + + using AttrListTraits = util::IntrusiveListBaseTraits<AttrInfo>; + using AttrList = typename AttrListTraits::ListType; + private: + std::unique_ptr<char[], ::ams::fs::impl::Deleter> m_internal_entry_buffer; + char *m_external_entry_buffer; + size_t m_entry_buffer_size; + Entry *m_entries; + s32 m_entry_count; + s32 m_entry_count_max; + AttrList m_attr_list; + char *m_external_attr_info_buffer; + s32 m_external_attr_info_count; + s32 m_cache_count_min; + size_t m_cache_size_min; + size_t m_total_cache_size; + CacheHandle m_current_handle; + public: + static constexpr size_t QueryWorkBufferSize(s32 max_cache_count) { + AMS_ASSERT(max_cache_count > 0); + const auto entry_size = sizeof(Entry) * max_cache_count; + const auto attr_list_size = sizeof(AttrInfo) * 0x100; + return util::AlignUp(entry_size + attr_list_size + alignof(Entry) + alignof(AttrInfo), 8); + } + public: + CacheHandleTable() : m_internal_entry_buffer(), m_external_entry_buffer(), m_entry_buffer_size(), m_entries(), m_entry_count(), m_entry_count_max(), m_attr_list(), m_external_attr_info_buffer(), m_external_attr_info_count(), m_cache_count_min(), m_cache_size_min(), m_total_cache_size(), m_current_handle() { + /* ... */ + } + + ~CacheHandleTable() { + this->Finalize(); + } + + Result Initialize(s32 max_cache_count); + Result Initialize(s32 max_cache_count, void *work, size_t work_size) { + const auto aligned_entry_buf = util::AlignUp(reinterpret_cast<uintptr_t>(work), alignof(Entry)); + m_external_entry_buffer = reinterpret_cast<char *>(aligned_entry_buf); + m_entry_buffer_size = sizeof(Entry) * max_cache_count; + + const auto aligned_attr_info_buf = util::AlignUp(reinterpret_cast<uintptr_t>(m_external_entry_buffer + m_entry_buffer_size), alignof(AttrInfo)); + const auto work_end = reinterpret_cast<uintptr_t>(work) + work_size; + m_external_attr_info_buffer = reinterpret_cast<char *>(aligned_attr_info_buf); + m_external_attr_info_count = static_cast<s32>((work_end - aligned_attr_info_buf) / sizeof(AttrInfo)); + + R_SUCCEED(); + } + + void Finalize(); + + bool Register(CacheHandle *out, uintptr_t address, size_t size, const BufferAttribute &attr); + bool Unregister(uintptr_t *out_address, size_t *out_size, CacheHandle handle); + + + bool UnregisterOldest(uintptr_t *out_address, size_t *out_size, const BufferAttribute &attr, size_t required_size = 0); + + CacheHandle PublishCacheHandle(); + + size_t GetTotalCacheSize() const; + private: + void UnregisterCore(uintptr_t *out_address, size_t *out_size, Entry *entry); + + Entry *AcquireEntry(uintptr_t address, size_t size, const BufferAttribute &attr); + + void ReleaseEntry(Entry *entry); + + AttrInfo *FindAttrInfo(const BufferAttribute &attr); + + s32 GetCacheCountMin(const BufferAttribute &attr) { + AMS_UNUSED(attr); + return m_cache_count_min; + } + + size_t GetCacheSizeMin(const BufferAttribute &attr) { + AMS_UNUSED(attr); + return m_cache_size_min; + } + }; + private: + BuddyHeap m_buddy_heap; + CacheHandleTable m_cache_handle_table; + size_t m_total_size; + size_t m_peak_free_size; + size_t m_peak_total_allocatable_size; + size_t m_retried_count; + mutable os::SdkMutex m_mutex; + public: + static constexpr size_t QueryWorkBufferSize(s32 max_cache_count, s32 max_order) { + const auto buddy_size = FileSystemBuddyHeap::QueryWorkBufferSize(max_order); + const auto table_size = CacheHandleTable::QueryWorkBufferSize(max_cache_count); + return buddy_size + table_size; + } + public: + FileSystemBufferManager() : m_total_size(), m_peak_free_size(), m_peak_total_allocatable_size(), m_retried_count(), m_mutex() { /* ... */ } + + virtual ~FileSystemBufferManager() { /* ... */ } + + Result Initialize(s32 max_cache_count, uintptr_t address, size_t buffer_size, size_t block_size) { + AMS_ASSERT(buffer_size > 0); + R_TRY(m_cache_handle_table.Initialize(max_cache_count)); + R_TRY(m_buddy_heap.Initialize(address, buffer_size, block_size)); + + m_total_size = m_buddy_heap.GetTotalFreeSize(); + m_peak_free_size = m_total_size; + m_peak_total_allocatable_size = m_total_size; + + R_SUCCEED(); + } + + Result Initialize(s32 max_cache_count, uintptr_t address, size_t buffer_size, size_t block_size, s32 max_order) { + AMS_ASSERT(buffer_size > 0); + R_TRY(m_cache_handle_table.Initialize(max_cache_count)); + R_TRY(m_buddy_heap.Initialize(address, buffer_size, block_size, max_order)); + + m_total_size = m_buddy_heap.GetTotalFreeSize(); + m_peak_free_size = m_total_size; + m_peak_total_allocatable_size = m_total_size; + + R_SUCCEED(); + } + + Result Initialize(s32 max_cache_count, uintptr_t address, size_t buffer_size, size_t block_size, void *work, size_t work_size) { + const auto table_size = CacheHandleTable::QueryWorkBufferSize(max_cache_count); + const auto buddy_size = work_size - table_size; + AMS_ASSERT(work_size > table_size); + const auto table_buffer = static_cast<char *>(work); + const auto buddy_buffer = table_buffer + table_size; + + R_TRY(m_cache_handle_table.Initialize(max_cache_count, table_buffer, table_size)); + R_TRY(m_buddy_heap.Initialize(address, buffer_size, block_size, buddy_buffer, buddy_size)); + + m_total_size = m_buddy_heap.GetTotalFreeSize(); + m_peak_free_size = m_total_size; + m_peak_total_allocatable_size = m_total_size; + + R_SUCCEED(); + } + + Result Initialize(s32 max_cache_count, uintptr_t address, size_t buffer_size, size_t block_size, s32 max_order, void *work, size_t work_size) { + const auto table_size = CacheHandleTable::QueryWorkBufferSize(max_cache_count); + const auto buddy_size = work_size - table_size; + AMS_ASSERT(work_size > table_size); + const auto table_buffer = static_cast<char *>(work); + const auto buddy_buffer = table_buffer + table_size; + + R_TRY(m_cache_handle_table.Initialize(max_cache_count, table_buffer, table_size)); + R_TRY(m_buddy_heap.Initialize(address, buffer_size, block_size, max_order, buddy_buffer, buddy_size)); + + m_total_size = m_buddy_heap.GetTotalFreeSize(); + m_peak_free_size = m_total_size; + m_peak_total_allocatable_size = m_total_size; + + R_SUCCEED(); + } + + void Finalize() { + m_buddy_heap.Finalize(); + m_cache_handle_table.Finalize(); + } + private: + virtual const fs::IBufferManager::MemoryRange DoAllocateBuffer(size_t size, const BufferAttribute &attr) override; + + virtual void DoDeallocateBuffer(uintptr_t address, size_t size) override; + + virtual CacheHandle DoRegisterCache(uintptr_t address, size_t size, const BufferAttribute &attr) override; + + virtual const fs::IBufferManager::MemoryRange DoAcquireCache(CacheHandle handle) override; + + virtual size_t DoGetTotalSize() const override; + + virtual size_t DoGetFreeSize() const override; + + virtual size_t DoGetTotalAllocatableSize() const override; + + virtual size_t DoGetFreeSizePeak() const override; + + virtual size_t DoGetTotalAllocatableSizePeak() const override; + + virtual size_t DoGetRetriedCount() const override; + + virtual void DoClearPeak() override; + private: + const fs::IBufferManager::MemoryRange AllocateBufferImpl(size_t size, const BufferAttribute &attr); + void DeallocateBufferImpl(uintptr_t address, size_t size); + CacheHandle RegisterCacheImpl(uintptr_t address, size_t size, const BufferAttribute &attr); + const fs::IBufferManager::MemoryRange AcquireCacheImpl(CacheHandle handle); + size_t GetFreeSizeImpl() const; + size_t GetTotalAllocatableSizeImpl() const; + size_t GetFreeSizePeakImpl() const; + size_t GetTotalAllocatableSizePeakImpl() const; + size_t GetRetriedCountImpl() const; + void ClearPeakImpl(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_counter_extended_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_counter_extended_storage.hpp new file mode 100644 index 00000000..19dbe5c5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_counter_extended_storage.hpp @@ -0,0 +1,132 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fssystem/fssystem_aes_ctr_storage.hpp> +#include <stratosphere/fssystem/fssystem_bucket_tree.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: Unknown */ + class AesCtrCounterExtendedStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(AesCtrCounterExtendedStorage); + NON_MOVEABLE(AesCtrCounterExtendedStorage); + public: + static constexpr size_t BlockSize = crypto::Aes128CtrEncryptor::BlockSize; + static constexpr size_t KeySize = crypto::Aes128CtrEncryptor::KeySize; + static constexpr size_t IvSize = crypto::Aes128CtrEncryptor::IvSize; + static constexpr size_t NodeSize = 16_KB; + + using IAllocator = BucketTree::IAllocator; + + using DecryptFunction = void(*)(void *dst, size_t dst_size, u8 index, u8 gen, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + + class IDecryptor { + public: + virtual ~IDecryptor() { /* ... */ } + virtual void Decrypt(void *buf, size_t buf_size, const void *enc_key, size_t enc_key_size, void *iv, size_t iv_size) = 0; + virtual bool HasExternalDecryptionKey() const = 0; + }; + + struct Entry { + enum class Encryption : u8 { + Encrypted = 0, + NotEncrypted = 1, + }; + + u8 offset[sizeof(s64)]; + Encryption encryption_value; + u8 reserved[3]; + s32 generation; + + void SetOffset(s64 value) { + std::memcpy(this->offset, std::addressof(value), sizeof(s64)); + } + + s64 GetOffset() const { + s64 value; + std::memcpy(std::addressof(value), this->offset, sizeof(s64)); + return value; + } + }; + static_assert(sizeof(Entry) == 0x10); + static_assert(alignof(Entry) == 4); + static_assert(util::is_pod<Entry>::value); + public: + static constexpr s64 QueryHeaderStorageSize() { + return BucketTree::QueryHeaderStorageSize(); + } + + static constexpr s64 QueryNodeStorageSize(s32 entry_count) { + return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); + } + + static constexpr s64 QueryEntryStorageSize(s32 entry_count) { + return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); + } + + static Result CreateExternalDecryptor(std::unique_ptr<IDecryptor> *out, DecryptFunction func, s32 key_index, s32 key_generation); + static Result CreateSoftwareDecryptor(std::unique_ptr<IDecryptor> *out); + private: + BucketTree m_table; + fs::SubStorage m_data_storage; + u8 m_key[KeySize]; + u32 m_secure_value; + s64 m_counter_offset; + std::unique_ptr<IDecryptor> m_decryptor; + public: + AesCtrCounterExtendedStorage() : m_table(), m_data_storage(), m_secure_value(), m_counter_offset(), m_decryptor() { /* ... */ } + virtual ~AesCtrCounterExtendedStorage() { this->Finalize(); } + + Result Initialize(IAllocator *allocator, const void *key, size_t key_size, u32 secure_value, s64 counter_offset, fs::SubStorage data_storage, fs::SubStorage node_storage, fs::SubStorage entry_storage, s32 entry_count, std::unique_ptr<IDecryptor> &&decryptor); + void Finalize(); + + bool IsInitialized() const { return m_table.IsInitialized(); } + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + + virtual Result GetSize(s64 *out) override { + AMS_ASSERT(out != nullptr); + + BucketTree::Offsets offsets; + R_TRY(m_table.GetOffsets(std::addressof(offsets))); + + *out = offsets.end_offset; + + R_SUCCEED(); + } + + virtual Result Flush() override { + R_SUCCEED(); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + AMS_UNUSED(offset, buffer, size); + R_THROW(fs::ResultUnsupportedWriteForAesCtrCounterExtendedStorage()); + } + + virtual Result SetSize(s64 size) override { + AMS_UNUSED(size); + R_THROW(fs::ResultUnsupportedSetSizeForAesCtrCounterExtendedStorage()); + } + + Result GetEntryList(Entry *out_entries, s32 *out_entry_count, s32 entry_count, s64 offset, s64 size); + private: + Result Initialize(IAllocator *allocator, const void *key, size_t key_size, u32 secure_value, fs::SubStorage data_storage, fs::SubStorage table_storage); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_storage.hpp new file mode 100644 index 00000000..1c779ff1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_storage.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fs_istorage.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: Unknown */ + template<fs::PointerToStorage BasePointer> + class AesCtrStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(AesCtrStorage); + NON_MOVEABLE(AesCtrStorage); + public: + static constexpr size_t BlockSize = crypto::Aes128CtrEncryptor::BlockSize; + static constexpr size_t KeySize = crypto::Aes128CtrEncryptor::KeySize; + static constexpr size_t IvSize = crypto::Aes128CtrEncryptor::IvSize; + private: + BasePointer m_base_storage; + char m_key[KeySize]; + char m_iv[IvSize]; + public: + static void MakeIv(void *dst, size_t dst_size, u64 upper, s64 offset); + public: + AesCtrStorage(BasePointer base, const void *key, size_t key_size, const void *iv, size_t iv_size); + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + + virtual Result Flush() override; + + virtual Result SetSize(s64 size) override; + virtual Result GetSize(s64 *out) override; + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + }; + + using AesCtrStorageByPointer = AesCtrStorage<fs::IStorage *>; + using AesCtrStorageBySharedPointer = AesCtrStorage<std::shared_ptr<fs::IStorage>>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_storage_external.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_storage_external.hpp new file mode 100644 index 00000000..54c8e487 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_storage_external.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fs_istorage.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> +#include <stratosphere/fssystem/fssystem_nca_file_system_driver.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 14.3.0.0 */ + class AesCtrStorageExternal : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(AesCtrStorageExternal); + NON_MOVEABLE(AesCtrStorageExternal); + public: + static constexpr size_t BlockSize = crypto::Aes128CtrEncryptor::BlockSize; + static constexpr size_t KeySize = crypto::Aes128CtrEncryptor::KeySize; + static constexpr size_t IvSize = crypto::Aes128CtrEncryptor::IvSize; + private: + std::shared_ptr<fs::IStorage> m_base_storage; + u8 m_iv[IvSize]; + DecryptAesCtrFunction m_decrypt_function; + s32 m_key_index; + s32 m_key_generation; + u8 m_encrypted_key[KeySize]; + public: + AesCtrStorageExternal(std::shared_ptr<fs::IStorage> bs, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, DecryptAesCtrFunction df, s32 kidx, s32 kgen); + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + virtual Result GetSize(s64 *out) override; + virtual Result Flush() override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + virtual Result SetSize(s64 size) override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_xts_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_xts_storage.hpp new file mode 100644 index 00000000..211bd823 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_xts_storage.hpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fs_istorage.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> +#include <stratosphere/os.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: Unknown */ + template<fs::PointerToStorage BasePointer> + class AesXtsStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(AesXtsStorage); + NON_MOVEABLE(AesXtsStorage); + public: + static constexpr size_t AesBlockSize = crypto::Aes128XtsEncryptor::BlockSize; + static constexpr size_t KeySize = crypto::Aes128XtsEncryptor::KeySize; + static constexpr size_t IvSize = crypto::Aes128XtsEncryptor::IvSize; + private: + BasePointer m_base_storage; + char m_key[2][KeySize]; + char m_iv[IvSize]; + const size_t m_block_size; + os::SdkMutex m_mutex; + public: + static void MakeAesXtsIv(void *dst, size_t dst_size, s64 offset, size_t block_size); + public: + AesXtsStorage(BasePointer base, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, size_t block_size); + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + + virtual Result Flush() override; + + virtual Result SetSize(s64 size) override; + virtual Result GetSize(s64 *out) override; + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + }; + + using AesXtsStorageByPointer = AesXtsStorage<fs::IStorage *>; + using AesXtsStorageBySharedPointer = AesXtsStorage<std::shared_ptr<fs::IStorage>>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_xts_storage_external.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_xts_storage_external.hpp new file mode 100644 index 00000000..3760c965 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_xts_storage_external.hpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fs_istorage.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> +#include <stratosphere/fssystem/fssystem_nca_file_system_driver.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 14.3.0.0 */ + template<fs::PointerToStorage BasePointer> + class AesXtsStorageExternal : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(AesXtsStorageExternal); + NON_MOVEABLE(AesXtsStorageExternal); + public: + static constexpr size_t AesBlockSize = crypto::Aes128XtsEncryptor::BlockSize; + static constexpr size_t KeySize = crypto::Aes128XtsEncryptor::KeySize; + static constexpr size_t IvSize = crypto::Aes128XtsEncryptor::IvSize; + private: + BasePointer m_base_storage; + char m_key[2][KeySize]; + char m_iv[IvSize]; + const size_t m_block_size; + CryptAesXtsFunction m_encrypt_function; + CryptAesXtsFunction m_decrypt_function; + public: + AesXtsStorageExternal(BasePointer bs, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, size_t block_size, CryptAesXtsFunction ef, CryptAesXtsFunction df); + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + virtual Result GetSize(s64 *out) override; + virtual Result Flush() override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + virtual Result SetSize(s64 size) override; + }; + + using AesXtsStorageExternalByPointer = AesXtsStorageExternal<fs::IStorage *>; + using AesXtsStorageExternalBySharedPointer = AesXtsStorageExternal<std::shared_ptr<fs::IStorage>>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_alignment_matching_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_alignment_matching_storage.hpp new file mode 100644 index 00000000..236add1f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_alignment_matching_storage.hpp @@ -0,0 +1,324 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fs_istorage.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> +#include <stratosphere/fssystem/fssystem_alignment_matching_storage_impl.hpp> +#include <stratosphere/fssystem/fssystem_pooled_buffer.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: Unknown */ + template<size_t _DataAlign, size_t _BufferAlign> + class AlignmentMatchingStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(AlignmentMatchingStorage); + NON_MOVEABLE(AlignmentMatchingStorage); + public: + static constexpr size_t DataAlign = _DataAlign; + static constexpr size_t BufferAlign = _BufferAlign; + + static constexpr size_t DataAlignMax = 0x200; + static_assert(DataAlign <= DataAlignMax); + static_assert(util::IsPowerOfTwo(DataAlign)); + static_assert(util::IsPowerOfTwo(BufferAlign)); + private: + std::shared_ptr<fs::IStorage> m_shared_base_storage; + fs::IStorage * const m_base_storage; + s64 m_base_storage_size; + bool m_is_base_storage_size_dirty; + public: + explicit AlignmentMatchingStorage(fs::IStorage *bs) : m_base_storage(bs), m_is_base_storage_size_dirty(true) { + /* ... */ + } + + explicit AlignmentMatchingStorage(std::shared_ptr<fs::IStorage> bs) : m_shared_base_storage(std::move(bs)), m_base_storage(m_shared_base_storage.get()), m_is_base_storage_size_dirty(true) { + /* ... */ + } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + /* Allocate a work buffer on stack. */ + __attribute__((aligned(DataAlignMax))) char work_buf[DataAlign]; + static_assert(util::IsAligned(alignof(work_buf), BufferAlign)); + + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + s64 bs_size = 0; + R_TRY(this->GetSize(std::addressof(bs_size))); + R_TRY(fs::IStorage::CheckAccessRange(offset, size, bs_size)); + + R_RETURN(AlignmentMatchingStorageImpl::Read(m_base_storage, work_buf, sizeof(work_buf), DataAlign, BufferAlign, offset, static_cast<char *>(buffer), size)); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + /* Allocate a work buffer on stack. */ + __attribute__((aligned(DataAlignMax))) char work_buf[DataAlign]; + static_assert(util::IsAligned(alignof(work_buf), BufferAlign)); + + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + s64 bs_size = 0; + R_TRY(this->GetSize(std::addressof(bs_size))); + R_TRY(fs::IStorage::CheckAccessRange(offset, size, bs_size)); + + R_RETURN(AlignmentMatchingStorageImpl::Write(m_base_storage, work_buf, sizeof(work_buf), DataAlign, BufferAlign, offset, static_cast<const char *>(buffer), size)); + } + + virtual Result Flush() override { + R_RETURN(m_base_storage->Flush()); + } + + virtual Result SetSize(s64 size) override { + ON_SCOPE_EXIT { m_is_base_storage_size_dirty = true; }; + R_RETURN(m_base_storage->SetSize(util::AlignUp(size, DataAlign))); + } + + virtual Result GetSize(s64 *out) override { + AMS_ASSERT(out != nullptr); + + if (m_is_base_storage_size_dirty) { + s64 size; + R_TRY(m_base_storage->GetSize(std::addressof(size))); + + m_base_storage_size = size; + m_is_base_storage_size_dirty = false; + } + + *out = m_base_storage_size; + R_SUCCEED(); + } + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + if (op_id == fs::OperationId::Invalidate) { + R_RETURN(m_base_storage->OperateRange(fs::OperationId::Invalidate, offset, size)); + } else { + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Get the base storage size. */ + s64 bs_size = 0; + R_TRY(this->GetSize(std::addressof(bs_size))); + R_TRY(fs::IStorage::CheckOffsetAndSize(offset, size)); + + /* Operate on the base storage. */ + const auto valid_size = std::min(size, bs_size - offset); + const auto aligned_offset = util::AlignDown(offset, DataAlign); + const auto aligned_offset_end = util::AlignUp(offset + valid_size, DataAlign); + const auto aligned_size = aligned_offset_end - aligned_offset; + + R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, aligned_offset, aligned_size, src, src_size)); + } + } + }; + + /* ACCURATE_TO_VERSION: Unknown */ + template<fs::PointerToStorage BasePointer, size_t _BufferAlign> + class AlignmentMatchingStoragePooledBuffer : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(AlignmentMatchingStoragePooledBuffer); + NON_MOVEABLE(AlignmentMatchingStoragePooledBuffer); + public: + static constexpr size_t BufferAlign = _BufferAlign; + + static_assert(util::IsPowerOfTwo(BufferAlign)); + private: + BasePointer m_base_storage; + s64 m_base_storage_size; + size_t m_data_align; + bool m_is_base_storage_size_dirty; + public: + explicit AlignmentMatchingStoragePooledBuffer(BasePointer bs, size_t da) : m_base_storage(std::move(bs)), m_data_align(da), m_is_base_storage_size_dirty(true) { + AMS_ASSERT(util::IsPowerOfTwo(da)); + } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + s64 bs_size = 0; + R_TRY(this->GetSize(std::addressof(bs_size))); + R_TRY(fs::IStorage::CheckAccessRange(offset, size, bs_size)); + + /* Allocate a pooled buffer. */ + PooledBuffer pooled_buffer; + pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align); + + R_RETURN(AlignmentMatchingStorageImpl::Read(m_base_storage, pooled_buffer.GetBuffer(), pooled_buffer.GetSize(), m_data_align, BufferAlign, offset, static_cast<char *>(buffer), size)); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + s64 bs_size = 0; + R_TRY(this->GetSize(std::addressof(bs_size))); + R_TRY(fs::IStorage::CheckAccessRange(offset, size, bs_size)); + + /* Allocate a pooled buffer. */ + PooledBuffer pooled_buffer; + pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align); + + R_RETURN(AlignmentMatchingStorageImpl::Write(m_base_storage, pooled_buffer.GetBuffer(), pooled_buffer.GetSize(), m_data_align, BufferAlign, offset, static_cast<const char *>(buffer), size)); + } + + virtual Result Flush() override { + R_RETURN(m_base_storage->Flush()); + } + + virtual Result SetSize(s64 size) override { + ON_SCOPE_EXIT { m_is_base_storage_size_dirty = true; }; + R_RETURN(m_base_storage->SetSize(util::AlignUp(size, m_data_align))); + } + + virtual Result GetSize(s64 *out) override { + AMS_ASSERT(out != nullptr); + + if (m_is_base_storage_size_dirty) { + s64 size; + R_TRY(m_base_storage->GetSize(std::addressof(size))); + + m_base_storage_size = size; + m_is_base_storage_size_dirty = false; + } + + *out = m_base_storage_size; + R_SUCCEED(); + } + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + if (op_id == fs::OperationId::Invalidate) { + R_RETURN(m_base_storage->OperateRange(fs::OperationId::Invalidate, offset, size)); + } else { + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Get the base storage size. */ + s64 bs_size = 0; + R_TRY(this->GetSize(std::addressof(bs_size))); + R_TRY(fs::IStorage::CheckOffsetAndSize(offset, size)); + + /* Operate on the base storage. */ + const auto valid_size = std::min(size, bs_size - offset); + const auto aligned_offset = util::AlignDown(offset, m_data_align); + const auto aligned_offset_end = util::AlignUp(offset + valid_size, m_data_align); + const auto aligned_size = aligned_offset_end - aligned_offset; + + R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, aligned_offset, aligned_size, src, src_size)); + } + } + }; + + /* ACCURATE_TO_VERSION: Unknown */ + template<size_t _BufferAlign> + class AlignmentMatchingStorageInBulkRead : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(AlignmentMatchingStorageInBulkRead); + NON_MOVEABLE(AlignmentMatchingStorageInBulkRead); + public: + static constexpr size_t BufferAlign = _BufferAlign; + + static_assert(util::IsPowerOfTwo(BufferAlign)); + private: + std::shared_ptr<fs::IStorage> m_shared_base_storage; + fs::IStorage * const m_base_storage; + s64 m_base_storage_size; + size_t m_data_align; + public: + explicit AlignmentMatchingStorageInBulkRead(fs::IStorage *bs, size_t da) : m_shared_base_storage(), m_base_storage(bs), m_base_storage_size(-1), m_data_align(da) { + AMS_ASSERT(util::IsPowerOfTwo(m_data_align)); + } + + explicit AlignmentMatchingStorageInBulkRead(std::shared_ptr<fs::IStorage> bs, size_t da) : m_shared_base_storage(bs), m_base_storage(m_shared_base_storage.get()), m_base_storage_size(-1), m_data_align(da) { + AMS_ASSERT(util::IsPowerOfTwo(da)); + } + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + s64 bs_size = 0; + R_TRY(this->GetSize(std::addressof(bs_size))); + R_TRY(fs::IStorage::CheckAccessRange(offset, size, bs_size)); + + /* Allocate a pooled buffer. */ + PooledBuffer pooled_buffer(m_data_align, m_data_align); + R_RETURN(AlignmentMatchingStorageImpl::Write(m_base_storage, pooled_buffer.GetBuffer(), pooled_buffer.GetSize(), m_data_align, BufferAlign, offset, static_cast<const char *>(buffer), size)); + } + + virtual Result Flush() override { + R_RETURN(m_base_storage->Flush()); + } + + virtual Result SetSize(s64 size) override { + ON_SCOPE_EXIT { m_base_storage_size = -1; }; + R_RETURN(m_base_storage->SetSize(util::AlignUp(size, m_data_align))); + } + + virtual Result GetSize(s64 *out) override { + AMS_ASSERT(out != nullptr); + + if (m_base_storage_size < 0) { + s64 size; + R_TRY(m_base_storage->GetSize(std::addressof(size))); + + m_base_storage_size = size; + } + + *out = m_base_storage_size; + R_SUCCEED(); + } + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + if (op_id == fs::OperationId::Invalidate) { + R_RETURN(m_base_storage->OperateRange(fs::OperationId::Invalidate, offset, size)); + } else { + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Get the base storage size. */ + s64 bs_size = 0; + R_TRY(this->GetSize(std::addressof(bs_size))); + R_TRY(fs::IStorage::CheckOffsetAndSize(offset, size)); + + /* Operate on the base storage. */ + const auto valid_size = std::min(size, bs_size - offset); + const auto aligned_offset = util::AlignDown(offset, m_data_align); + const auto aligned_offset_end = util::AlignUp(offset + valid_size, m_data_align); + const auto aligned_size = aligned_offset_end - aligned_offset; + + R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, aligned_offset, aligned_size, src, src_size)); + } + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_alignment_matching_storage_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_alignment_matching_storage_impl.hpp new file mode 100644 index 00000000..ace8966a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_alignment_matching_storage_impl.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fs_istorage.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: Unknown */ + class AlignmentMatchingStorageImpl { + public: + static Result Read(fs::IStorage *base_storage, char *work_buf, size_t work_buf_size, size_t data_alignment, size_t buffer_alignment, s64 offset, char *buffer, size_t size); + static Result Write(fs::IStorage *base_storage, char *work_buf, size_t work_buf_size, size_t data_alignment, size_t buffer_alignment, s64 offset, const char *buffer, size_t size); + + static Result Read(std::shared_ptr<fs::IStorage> &base_storage, char *work_buf, size_t work_buf_size, size_t data_alignment, size_t buffer_alignment, s64 offset, char *buffer, size_t size) { + R_RETURN(Read(base_storage.get(), work_buf, work_buf_size, data_alignment, buffer_alignment, offset, buffer, size)); + } + + static Result Write(std::shared_ptr<fs::IStorage> &base_storage, char *work_buf, size_t work_buf_size, size_t data_alignment, size_t buffer_alignment, s64 offset, const char *buffer, size_t size) { + R_RETURN(Write(base_storage.get(), work_buf, work_buf_size, data_alignment, buffer_alignment, offset, buffer, size)); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_allocator_utility.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_allocator_utility.hpp new file mode 100644 index 00000000..297b320e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_allocator_utility.hpp @@ -0,0 +1,116 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +/* Forward declare ams::fs allocate shared. */ +namespace ams::fs::impl { + + template<typename T, template<typename, typename> class AllocatorTemplateT, typename Impl, typename... Args> + std::shared_ptr<T> AllocateSharedImpl(Args &&... args); + +} + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: Unknown */ + using AllocateFunction = void *(*)(size_t size); + using DeallocateFunction = void (*)(void *ptr, size_t size); + + void InitializeAllocator(AllocateFunction allocate_func, DeallocateFunction deallocate_func); + void InitializeAllocatorForSystem(AllocateFunction allocate_func, DeallocateFunction deallocate_func); + + void *Allocate(size_t size); + void Deallocate(void *ptr, size_t size); + + namespace impl { + + template<bool ForSystem> + class AllocatorFunctionSet { + public: + static void *Allocate(size_t size); + static void *AllocateUnsafe(size_t size); + + static void Deallocate(void *ptr, size_t size); + static void DeallocateUnsafe(void *ptr, size_t size); + + static void LockAllocatorMutex(); + static void UnlockAllocatorMutex(); + }; + + using AllocatorFunctionSetForNormal = AllocatorFunctionSet<false>; + using AllocatorFunctionSetForSystem = AllocatorFunctionSet<true>; + + template<typename T, typename Impl, bool RequireNonNull, bool AllocateWhileLocked> + class AllocatorTemplate : public std::allocator<T> { + public: + template<typename U> + struct rebind { + using other = AllocatorTemplate<U, Impl, RequireNonNull, AllocateWhileLocked>; + }; + private: + static ALWAYS_INLINE T *AllocateImpl(::std::size_t n) { + if constexpr (AllocateWhileLocked) { + auto * const p = Impl::AllocateUnsafe(sizeof(T) * n); + Impl::UnlockAllocatorMutex(); + return static_cast<T *>(p); + } else { + return static_cast<T *>(Impl::Allocate(sizeof(T) * n)); + } + } + public: + AllocatorTemplate() { /* ... */ } + + template<typename U> + AllocatorTemplate(const AllocatorTemplate<U, Impl, RequireNonNull, AllocateWhileLocked> &) { /* ... */ } + + [[nodiscard]] T *allocate(::std::size_t n) { + auto * const p = AllocateImpl(n); + if constexpr (RequireNonNull) { + AMS_ABORT_UNLESS(p != nullptr); + } + return p; + } + + void deallocate(T *p, ::std::size_t n) { + Impl::Deallocate(p, sizeof(T) * n); + } + }; + + template<typename T, typename Impl> + using AllocatorTemplateForAllocateShared = AllocatorTemplate<T, Impl, true, true>; + + } + + template<typename T, typename... Args> + std::shared_ptr<T> AllocateShared(Args &&... args) { + return ::ams::fs::impl::AllocateSharedImpl<T, impl::AllocatorTemplateForAllocateShared, impl::AllocatorFunctionSetForNormal>(std::forward<Args>(args)...); + } + + template<typename TImpl, typename ErrorResult, typename TIntf, typename... Args> + Result AllocateSharedForSystem(std::shared_ptr<TIntf> *out, Args &&... args) { + /* Allocate the object. */ + auto p = ::ams::fs::impl::AllocateSharedImpl<TImpl, impl::AllocatorTemplateForAllocateShared, impl::AllocatorFunctionSetForSystem>(std::forward<Args>(args)...); + + /* Check that allocation succeeded. */ + R_UNLESS(p != nullptr, ErrorResult()); + + /* Return the allocated object. */ + *out = std::move(p); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_asynchronous_access.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_asynchronous_access.hpp new file mode 100644 index 00000000..8294fb3e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_asynchronous_access.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class IAsynchronousAccessSplitter { + public: + static IAsynchronousAccessSplitter *GetDefaultAsynchronousAccessSplitter(); + public: + constexpr IAsynchronousAccessSplitter() = default; + constexpr virtual ~IAsynchronousAccessSplitter() { /* ... */ } + public: + Result QueryNextOffset(s64 *out, s64 start_offset, s64 end_offset, s64 access_size, s64 alignment_size); + public: + virtual Result QueryAppropriateOffset(s64 *out, s64 offset, s64 access_size, s64 alignment_size) = 0; + virtual Result QueryInvocationCount(s64 *out, s64 start_offset, s64 end_offset, s64 access_size, s64 alignment_size) { AMS_UNUSED(out, start_offset, end_offset, access_size, alignment_size); AMS_ABORT("TODO"); } + }; + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class DefaultAsynchronousAccessSplitter final : public IAsynchronousAccessSplitter { + public: + constexpr DefaultAsynchronousAccessSplitter() = default; + public: + virtual Result QueryAppropriateOffset(s64 *out, s64 offset, s64 access_size, s64 alignment_size) override { + /* Align the access. */ + *out = util::AlignDown(offset + access_size, alignment_size); + R_SUCCEED(); + } + + virtual Result QueryInvocationCount(s64 *out, s64 start_offset, s64 end_offset, s64 access_size, s64 alignment_size) override { + /* Determine aligned access count. */ + *out = util::DivideUp(end_offset - util::AlignDown(start_offset, alignment_size), access_size); + R_SUCCEED(); + } + }; + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + inline IAsynchronousAccessSplitter *IAsynchronousAccessSplitter::GetDefaultAsynchronousAccessSplitter() { + static constinit DefaultAsynchronousAccessSplitter s_default_access_splitter; + return std::addressof(s_default_access_splitter); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bitmap_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bitmap_utils.hpp new file mode 100644 index 00000000..8cfddb87 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bitmap_utils.hpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + + constexpr inline s32 CountLeadingZeros(u32 val) { + return util::CountLeadingZeros(val); + } + + constexpr inline s32 CountLeadingOnes(u32 val) { + return CountLeadingZeros(~val); + } + + inline u32 ReadU32(const u8 *buf, size_t index) { + u32 val; + std::memcpy(std::addressof(val), buf + index, sizeof(u32)); + return val; + } + + inline void WriteU32(u8 *buf, size_t index, u32 val) { + std::memcpy(buf + index, std::addressof(val), sizeof(u32)); + } + + constexpr inline bool IsPowerOfTwo(s32 val) { + return util::IsPowerOfTwo(val); + } + + constexpr inline u32 ILog2(u32 val) { + AMS_ASSERT(val > 0); + return (BITSIZEOF(u32) - 1 - util::CountLeadingZeros<u32>(val)); + } + + constexpr inline u32 CeilingPowerOfTwo(u32 val) { + if (val == 0) { + return 1; + } + + return util::CeilingPowerOfTwo<u32>(val); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_block_cache_buffered_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_block_cache_buffered_storage.hpp new file mode 100644 index 00000000..f447d0b0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_block_cache_buffered_storage.hpp @@ -0,0 +1,180 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/fs/fs_storage_type.hpp> +#include <stratosphere/fs/fs_istorage.hpp> +#include <stratosphere/fs/fs_memory_management.hpp> +#include <stratosphere/fssystem/buffers/fssystem_file_system_buffer_manager.hpp> +#include <stratosphere/fssystem/impl/fssystem_block_cache_manager.hpp> + +namespace ams::fssystem { + + constexpr inline size_t IntegrityMinLayerCount = 2; + constexpr inline size_t IntegrityMaxLayerCount = 7; + constexpr inline size_t IntegrityLayerCountSave = 5; + constexpr inline size_t IntegrityLayerCountSaveDataMeta = 4; + + struct FileSystemBufferManagerSet { + fs::IBufferManager *buffers[IntegrityMaxLayerCount]; + }; + static_assert(util::is_pod<FileSystemBufferManagerSet>::value); + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class BlockCacheBufferedStorage : public ::ams::fs::IStorage { + NON_COPYABLE(BlockCacheBufferedStorage); + NON_MOVEABLE(BlockCacheBufferedStorage); + public: + static constexpr size_t DefaultMaxCacheEntryCount = 24; + private: + using MemoryRange = fs::IBufferManager::MemoryRange; + + struct AccessRange { + s64 offset; + size_t size; + + s64 GetEndOffset() const { + return this->offset + this->size; + } + + bool IsIncluded(s64 ofs) const { + return this->offset <= ofs && ofs < this->GetEndOffset(); + } + }; + static_assert(util::is_pod<AccessRange>::value); + + struct CacheEntry { + AccessRange range; + bool is_valid; + bool is_write_back; + bool is_cached; + bool is_flushing; + u16 lru_counter; + fs::IBufferManager::CacheHandle handle; + uintptr_t memory_address; + size_t memory_size; + + void Invalidate() { + this->is_write_back = false; + this->is_flushing = false; + } + + bool IsAllocated() const { + return this->is_valid && (this->is_write_back ? this->memory_address != 0 : this->handle != 0); + } + + bool IsWriteBack() const { + return this->is_write_back; + } + }; + static_assert(util::is_pod<CacheEntry>::value); + + using BlockCacheManager = ::ams::fssystem::impl::BlockCacheManager<CacheEntry, fs::IBufferManager>; + using CacheIndex = BlockCacheManager::CacheIndex; + + enum Flag : s32 { + Flag_KeepBurstMode = (1 << 8), + Flag_RealData = (1 << 10), + }; + private: + os::SdkRecursiveMutex *m_mutex; + IStorage *m_data_storage; + Result m_last_result; + s64 m_data_size; + size_t m_verification_block_size; + size_t m_verification_block_shift; + s32 m_flags; + s32 m_buffer_level; + BlockCacheManager m_block_cache_manager; + bool m_is_writable; + public: + BlockCacheBufferedStorage(); + virtual ~BlockCacheBufferedStorage() override; + + Result Initialize(fs::IBufferManager *bm, os::SdkRecursiveMutex *mtx, IStorage *data, s64 data_size, size_t verif_block_size, s32 max_cache_entries, bool is_real_data, s8 buffer_level, bool is_keep_burst_mode, bool is_writable); + void Finalize(); + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + + virtual Result SetSize(s64) override { R_THROW(fs::ResultUnsupportedSetSizeForBlockCacheBufferedStorage()); } + virtual Result GetSize(s64 *out) override; + + virtual Result Flush() override; + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + using IStorage::OperateRange; + + Result Commit(); + Result OnRollback(); + + bool IsEnabledKeepBurstMode() const { + return (m_flags & Flag_KeepBurstMode) != 0; + } + + bool IsRealDataCache() const { + return (m_flags & Flag_RealData) != 0; + } + + void SetKeepBurstMode(bool en) { + if (en) { + m_flags |= Flag_KeepBurstMode; + } else { + m_flags &= ~Flag_KeepBurstMode; + } + } + + void SetRealDataCache(bool en) { + if (en) { + m_flags |= Flag_RealData; + } else { + m_flags &= ~Flag_RealData; + } + } + private: + Result FillZeroImpl(s64 offset, s64 size); + Result DestroySignatureImpl(s64 offset, s64 size); + Result InvalidateImpl(); + Result QueryRangeImpl(void *dst, size_t dst_size, s64 offset, s64 size); + + Result GetAssociateBuffer(MemoryRange *out_range, CacheEntry *out_entry, s64 offset, size_t ideal_size, bool is_allocate_for_write); + + Result StoreOrDestroyBuffer(CacheIndex *out, const MemoryRange &range, CacheEntry *entry); + + Result StoreOrDestroyBuffer(const MemoryRange &range, CacheEntry *entry) { + AMS_ASSERT(entry != nullptr); + + CacheIndex dummy; + R_RETURN(this->StoreOrDestroyBuffer(std::addressof(dummy), range, entry)); + } + + Result FlushCacheEntry(CacheIndex index, bool invalidate); + Result FlushRangeCacheEntries(s64 offset, s64 size, bool invalidate); + + Result FlushAllCacheEntries(); + Result InvalidateAllCacheEntries(); + Result ControlDirtiness(); + + Result UpdateLastResult(Result result); + + Result ReadHeadCache(MemoryRange *out_range, CacheEntry *out_entry, bool *out_cache_needed, s64 *offset, s64 *aligned_offset, s64 aligned_offset_end, char **buffer, size_t *size); + Result ReadTailCache(MemoryRange *out_range, CacheEntry *out_entry, bool *out_cache_needed, s64 offset, s64 aligned_offset, s64 *aligned_offset_end, char *buffer, size_t *size); + + Result BulkRead(s64 offset, void *buffer, size_t size, MemoryRange *range_head, MemoryRange *range_tail, CacheEntry *entry_head, CacheEntry *entry_tail, bool head_cache_needed, bool tail_cache_needed); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bucket_tree.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bucket_tree.hpp new file mode 100644 index 00000000..5f43ca42 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bucket_tree.hpp @@ -0,0 +1,355 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fs_substorage.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: Unknown */ + class BucketTree { + NON_COPYABLE(BucketTree); + NON_MOVEABLE(BucketTree); + public: + static constexpr u32 Magic = util::FourCC<'B','K','T','R'>::Code; + static constexpr u32 Version = 1; + + static constexpr size_t NodeSizeMin = 1_KB; + static constexpr size_t NodeSizeMax = 512_KB; + public: + class Visitor; + + struct Header { + u32 magic; + u32 version; + s32 entry_count; + s32 reserved; + + void Format(s32 entry_count); + Result Verify() const; + }; + static_assert(util::is_pod<Header>::value); + static_assert(sizeof(Header) == 0x10); + + struct NodeHeader { + s32 index; + s32 count; + s64 offset; + + Result Verify(s32 node_index, size_t node_size, size_t entry_size) const; + }; + static_assert(util::is_pod<NodeHeader>::value); + static_assert(sizeof(NodeHeader) == 0x10); + + struct Offsets { + s64 start_offset; + s64 end_offset; + + constexpr bool IsInclude(s64 offset) const { + return this->start_offset <= offset && offset < this->end_offset; + } + + constexpr bool IsInclude(s64 offset, s64 size) const { + return size > 0 && this->start_offset <= offset && size <= (this->end_offset - offset); + } + }; + static_assert(util::is_pod<Offsets>::value); + static_assert(sizeof(Offsets) == 0x10); + + struct OffsetCache { + Offsets offsets; + os::SdkMutex mutex; + bool is_initialized; + + constexpr OffsetCache() : offsets{ -1, -1 }, mutex(), is_initialized(false) { /* ... */ } + }; + + class ContinuousReadingInfo { + private: + size_t m_read_size; + s32 m_skip_count; + bool m_done; + public: + constexpr ContinuousReadingInfo() : m_read_size(), m_skip_count(), m_done() { /* ... */ } + + constexpr void Reset() { m_read_size = 0; m_skip_count = 0; m_done = false; } + + constexpr void SetSkipCount(s32 count) { AMS_ASSERT(count >= 0); m_skip_count = count; } + constexpr s32 GetSkipCount() const { return m_skip_count; } + constexpr bool CheckNeedScan() { return (--m_skip_count) <= 0; } + + constexpr void Done() { m_read_size = 0; m_done = true; } + constexpr bool IsDone() const { return m_done; } + + constexpr void SetReadSize(size_t size) { m_read_size = size; } + constexpr size_t GetReadSize() const { return m_read_size; } + constexpr bool CanDo() const { return m_read_size > 0; } + }; + + using IAllocator = MemoryResource; + private: + class NodeBuffer { + NON_COPYABLE(NodeBuffer); + private: + IAllocator *m_allocator; + void *m_header; + public: + NodeBuffer() : m_allocator(), m_header() { /* ... */ } + + ~NodeBuffer() { + AMS_ASSERT(m_header == nullptr); + } + + NodeBuffer(NodeBuffer &&rhs) : m_allocator(rhs.m_allocator), m_header(rhs.m_header) { + rhs.m_allocator = nullptr; + rhs.m_header = nullptr; + } + + NodeBuffer &operator=(NodeBuffer &&rhs) { + if (this != std::addressof(rhs)) { + AMS_ASSERT(m_header == nullptr); + + m_allocator = rhs.m_allocator; + m_header = rhs.m_header; + + rhs.m_allocator = nullptr; + rhs.m_header = nullptr; + } + return *this; + } + + bool Allocate(IAllocator *allocator, size_t node_size) { + AMS_ASSERT(m_header == nullptr); + + m_allocator = allocator; + m_header = allocator->Allocate(node_size, sizeof(s64)); + + AMS_ASSERT(util::IsAligned(m_header, sizeof(s64))); + + return m_header != nullptr; + } + + void Free(size_t node_size) { + if (m_header) { + m_allocator->Deallocate(m_header, node_size); + m_header = nullptr; + } + m_allocator = nullptr; + } + + void FillZero(size_t node_size) const { + if (m_header) { + std::memset(m_header, 0, node_size); + } + } + + NodeHeader *Get() const { + return reinterpret_cast<NodeHeader *>(m_header); + } + + NodeHeader *operator->() const { return this->Get(); } + + template<typename T> + T *Get() const { + static_assert(util::is_pod<T>::value); + static_assert(sizeof(T) == sizeof(NodeHeader)); + return reinterpret_cast<T *>(m_header); + } + + IAllocator *GetAllocator() const { + return m_allocator; + } + }; + private: + static constexpr s32 GetEntryCount(size_t node_size, size_t entry_size) { + return static_cast<s32>((node_size - sizeof(NodeHeader)) / entry_size); + } + + static constexpr s32 GetOffsetCount(size_t node_size) { + return static_cast<s32>((node_size - sizeof(NodeHeader)) / sizeof(s64)); + } + + static constexpr s32 GetEntrySetCount(size_t node_size, size_t entry_size, s32 entry_count) { + const s32 entry_count_per_node = GetEntryCount(node_size, entry_size); + return util::DivideUp(entry_count, entry_count_per_node); + } + + static constexpr s32 GetNodeL2Count(size_t node_size, size_t entry_size, s32 entry_count) { + const s32 offset_count_per_node = GetOffsetCount(node_size); + const s32 entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count); + + if (entry_set_count <= offset_count_per_node) { + return 0; + } + + const s32 node_l2_count = util::DivideUp(entry_set_count, offset_count_per_node); + AMS_ABORT_UNLESS(node_l2_count <= offset_count_per_node); + + return util::DivideUp(entry_set_count - (offset_count_per_node - (node_l2_count - 1)), offset_count_per_node); + } + public: + static constexpr s64 QueryHeaderStorageSize() { return sizeof(Header); } + + static constexpr s64 QueryNodeStorageSize(size_t node_size, size_t entry_size, s32 entry_count) { + AMS_ASSERT(entry_size >= sizeof(s64)); + AMS_ASSERT(node_size >= entry_size + sizeof(NodeHeader)); + AMS_ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); + AMS_ASSERT(util::IsPowerOfTwo(node_size)); + AMS_ASSERT(entry_count >= 0); + + if (entry_count <= 0) { + return 0; + } + return (1 + GetNodeL2Count(node_size, entry_size, entry_count)) * static_cast<s64>(node_size); + } + + static constexpr s64 QueryEntryStorageSize(size_t node_size, size_t entry_size, s32 entry_count) { + AMS_ASSERT(entry_size >= sizeof(s64)); + AMS_ASSERT(node_size >= entry_size + sizeof(NodeHeader)); + AMS_ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); + AMS_ASSERT(util::IsPowerOfTwo(node_size)); + AMS_ASSERT(entry_count >= 0); + + if (entry_count <= 0) { + return 0; + } + return GetEntrySetCount(node_size, entry_size, entry_count) * static_cast<s64>(node_size); + } + private: + mutable fs::SubStorage m_node_storage; + mutable fs::SubStorage m_entry_storage; + NodeBuffer m_node_l1; + size_t m_node_size; + size_t m_entry_size; + s32 m_entry_count; + s32 m_offset_count; + s32 m_entry_set_count; + OffsetCache m_offset_cache; + public: + BucketTree() : m_node_storage(), m_entry_storage(), m_node_l1(), m_node_size(), m_entry_size(), m_entry_count(), m_offset_count(), m_entry_set_count(), m_offset_cache() { /* ... */ } + ~BucketTree() { this->Finalize(); } + + Result Initialize(IAllocator *allocator, fs::SubStorage node_storage, fs::SubStorage entry_storage, size_t node_size, size_t entry_size, s32 entry_count); + void Initialize(size_t node_size, s64 end_offset); + void Finalize(); + + bool IsInitialized() const { return m_node_size > 0; } + bool IsEmpty() const { return m_entry_size == 0; } + + Result Find(Visitor *visitor, s64 virtual_address); + Result InvalidateCache(); + + s32 GetEntryCount() const { return m_entry_count; } + IAllocator *GetAllocator() const { return m_node_l1.GetAllocator(); } + + Result GetOffsets(Offsets *out) { + /* Ensure we have an offset cache. */ + R_TRY(this->EnsureOffsetCache()); + + /* Set the output. */ + *out = m_offset_cache.offsets; + R_SUCCEED(); + } + private: + template<typename EntryType> + struct ContinuousReadingParam { + s64 offset; + size_t size; + NodeHeader entry_set; + s32 entry_index; + Offsets offsets; + EntryType entry; + }; + private: + template<typename EntryType> + Result ScanContinuousReading(ContinuousReadingInfo *out_info, const ContinuousReadingParam<EntryType> ¶m) const; + + bool IsExistL2() const { return m_offset_count < m_entry_set_count; } + bool IsExistOffsetL2OnL1() const { return this->IsExistL2() && m_node_l1->count < m_offset_count; } + + s64 GetEntrySetIndex(s32 node_index, s32 offset_index) const { + return (m_offset_count - m_node_l1->count) + (m_offset_count * node_index) + offset_index; + } + + Result EnsureOffsetCache(); + }; + + /* ACCURATE_TO_VERSION: Unknown */ + class BucketTree::Visitor { + NON_COPYABLE(Visitor); + NON_MOVEABLE(Visitor); + private: + friend class BucketTree; + + union EntrySetHeader { + NodeHeader header; + struct Info { + s32 index; + s32 count; + s64 end; + s64 start; + } info; + static_assert(util::is_pod<Info>::value); + }; + static_assert(util::is_pod<EntrySetHeader>::value); + private: + const BucketTree *m_tree; + BucketTree::Offsets m_offsets; + void *m_entry; + s32 m_entry_index; + s32 m_entry_set_count; + EntrySetHeader m_entry_set; + public: + constexpr Visitor() : m_tree(), m_entry(), m_entry_index(-1), m_entry_set_count(), m_entry_set{} { /* ... */ } + ~Visitor() { + if (m_entry != nullptr) { + m_tree->GetAllocator()->Deallocate(m_entry, m_tree->m_entry_size); + m_tree = nullptr; + m_entry = nullptr; + } + } + + bool IsValid() const { return m_entry_index >= 0; } + bool CanMoveNext() const { return this->IsValid() && (m_entry_index + 1 < m_entry_set.info.count || m_entry_set.info.index + 1 < m_entry_set_count); } + bool CanMovePrevious() const { return this->IsValid() && (m_entry_index > 0 || m_entry_set.info.index > 0); } + + Result MoveNext(); + Result MovePrevious(); + + template<typename EntryType> + Result ScanContinuousReading(ContinuousReadingInfo *out_info, s64 offset, size_t size) const; + + const void *Get() const { AMS_ASSERT(this->IsValid()); return m_entry; } + + template<typename T> + const T *Get() const { AMS_ASSERT(this->IsValid()); return reinterpret_cast<const T *>(m_entry); } + + const BucketTree *GetTree() const { return m_tree; } + private: + Result Initialize(const BucketTree *tree, const BucketTree::Offsets &offsets); + + Result Find(s64 virtual_address); + + Result FindEntrySet(s32 *out_index, s64 virtual_address, s32 node_index); + Result FindEntrySetWithBuffer(s32 *out_index, s64 virtual_address, s32 node_index, char *buffer); + Result FindEntrySetWithoutBuffer(s32 *out_index, s64 virtual_address, s32 node_index); + + Result FindEntry(s64 virtual_address, s32 entry_set_index); + Result FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, char *buffer); + Result FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bucket_tree_template_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bucket_tree_template_impl.hpp new file mode 100644 index 00000000..3ae0bd73 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bucket_tree_template_impl.hpp @@ -0,0 +1,172 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fssystem/fssystem_pooled_buffer.hpp> +#include <stratosphere/fssystem/fssystem_bucket_tree.hpp> +#include <stratosphere/fssystem/fssystem_bucket_tree_utils.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + template<typename EntryType> + Result BucketTree::ScanContinuousReading(ContinuousReadingInfo *out_info, const ContinuousReadingParam<EntryType> ¶m) const { + static_assert(util::is_pod<ContinuousReadingParam<EntryType>>::value); + + /* Validate our preconditions. */ + AMS_ASSERT(this->IsInitialized()); + AMS_ASSERT(out_info != nullptr); + AMS_ASSERT(m_entry_size == sizeof(EntryType)); + + /* Reset the output. */ + out_info->Reset(); + + /* If there's nothing to read, we're done. */ + R_SUCCEED_IF(param.size == 0); + + /* If we're reading a fragment, we're done. */ + R_SUCCEED_IF(param.entry.IsFragment()); + + /* Validate the first entry. */ + auto entry = param.entry; + auto cur_offset = param.offset; + R_UNLESS(entry.GetVirtualOffset() <= cur_offset, fs::ResultOutOfRange()); + + /* Create a pooled buffer for our scan. */ + PooledBuffer pool(m_node_size, 1); + char *buffer = nullptr; + + s64 entry_storage_size; + R_TRY(m_entry_storage.GetSize(std::addressof(entry_storage_size))); + + /* Read the node. */ + if (m_node_size <= pool.GetSize()) { + buffer = pool.GetBuffer(); + const auto ofs = param.entry_set.index * static_cast<s64>(m_node_size); + R_UNLESS(m_node_size + ofs <= static_cast<size_t>(entry_storage_size), fs::ResultInvalidBucketTreeNodeEntryCount()); + + R_TRY(m_entry_storage.Read(ofs, buffer, m_node_size)); + } + + /* Calculate extents. */ + const auto end_offset = cur_offset + static_cast<s64>(param.size); + s64 phys_offset = entry.GetPhysicalOffset(); + + /* Start merge tracking. */ + s64 merge_size = 0; + s64 readable_size = 0; + bool merged = false; + + /* Iterate. */ + auto entry_index = param.entry_index; + for (const auto entry_count = param.entry_set.count; entry_index < entry_count; ++entry_index) { + /* If we're past the end, we're done. */ + if (end_offset <= cur_offset) { + break; + } + + /* Validate the entry offset. */ + const auto entry_offset = entry.GetVirtualOffset(); + R_UNLESS(entry_offset <= cur_offset, fs::ResultInvalidIndirectEntryOffset()); + + /* Get the next entry. */ + EntryType next_entry = {}; + s64 next_entry_offset; + + if (entry_index + 1 < entry_count) { + if (buffer != nullptr) { + const auto ofs = impl::GetBucketTreeEntryOffset(0, m_entry_size, entry_index + 1); + std::memcpy(std::addressof(next_entry), buffer + ofs, m_entry_size); + } else { + const auto ofs = impl::GetBucketTreeEntryOffset(param.entry_set.index, m_node_size, m_entry_size, entry_index + 1); + R_TRY(m_entry_storage.Read(ofs, std::addressof(next_entry), m_entry_size)); + } + + next_entry_offset = next_entry.GetVirtualOffset(); + R_UNLESS(param.offsets.IsInclude(next_entry_offset), fs::ResultInvalidIndirectEntryOffset()); + } else { + next_entry_offset = param.entry_set.offset; + } + + /* Validate the next entry offset. */ + R_UNLESS(cur_offset < next_entry_offset, fs::ResultInvalidIndirectEntryOffset()); + + /* Determine the much data there is. */ + const auto data_size = next_entry_offset - cur_offset; + AMS_ASSERT(data_size > 0); + + /* Determine how much data we should read. */ + const auto remaining_size = end_offset - cur_offset; + const size_t read_size = static_cast<size_t>(std::min(data_size, remaining_size)); + AMS_ASSERT(read_size <= param.size); + + /* Update our merge tracking. */ + if (entry.IsFragment()) { + /* If we can't merge, stop looping. */ + if (EntryType::FragmentSizeMax <= read_size || remaining_size <= data_size) { + break; + } + + /* Otherwise, add the current size to the merge size. */ + merge_size += read_size; + } else { + /* If we can't merge, stop looping. */ + if (phys_offset != entry.GetPhysicalOffset()) { + break; + } + + /* Add the size to the readable amount. */ + readable_size += merge_size + read_size; + AMS_ASSERT(readable_size <= static_cast<s64>(param.size)); + + /* Update whether we've merged. */ + merged |= merge_size > 0; + merge_size = 0; + } + + /* Advance. */ + cur_offset += read_size; + AMS_ASSERT(cur_offset <= end_offset); + + phys_offset += next_entry_offset - entry_offset; + entry = next_entry; + } + + /* If we merged, set our readable size. */ + if (merged) { + out_info->SetReadSize(static_cast<size_t>(readable_size)); + } + out_info->SetSkipCount(entry_index - param.entry_index); + + R_SUCCEED(); + } + + template<typename EntryType> + Result BucketTree::Visitor::ScanContinuousReading(ContinuousReadingInfo *out_info, s64 offset, size_t size) const { + static_assert(util::is_pod<EntryType>::value); + AMS_ASSERT(this->IsValid()); + + /* Create our parameters. */ + ContinuousReadingParam<EntryType> param = { + offset, size, m_entry_set.header, m_entry_index + }; + std::memcpy(std::addressof(param.offsets), std::addressof(m_offsets), sizeof(BucketTree::Offsets)); + std::memcpy(std::addressof(param.entry), m_entry, sizeof(EntryType)); + + /* Scan. */ + R_RETURN(m_tree->ScanContinuousReading<EntryType>(out_info, param)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bucket_tree_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bucket_tree_utils.hpp new file mode 100644 index 00000000..16681d95 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bucket_tree_utils.hpp @@ -0,0 +1,87 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fssystem/fssystem_bucket_tree.hpp> + +namespace ams::fssystem::impl { + + /* ACCURATE_TO_VERSION: Unknown */ + class SafeValue { + public: + static ALWAYS_INLINE s64 GetInt64(const void *ptr) { + s64 value; + std::memcpy(std::addressof(value), ptr, sizeof(s64)); + return value; + } + + static ALWAYS_INLINE s64 GetInt64(const s64 *ptr) { + return GetInt64(static_cast<const void *>(ptr)); + } + + static ALWAYS_INLINE s64 GetInt64(const s64 &v) { + return GetInt64(std::addressof(v)); + } + + static ALWAYS_INLINE void SetInt64(void *dst, const void *src) { + std::memcpy(dst, src, sizeof(s64)); + } + + static ALWAYS_INLINE void SetInt64(void *dst, const s64 *src) { + return SetInt64(dst, static_cast<const void *>(src)); + } + + static ALWAYS_INLINE void SetInt64(void *dst, const s64 &v) { + return SetInt64(dst, std::addressof(v)); + } + }; + + /* ACCURATE_TO_VERSION: Unknown */ + template<typename IteratorType> + struct BucketTreeNode { + using Header = BucketTree::NodeHeader; + + Header header; + + s32 GetCount() const { return this->header.count; } + + void *GetArray() { return std::addressof(this->header) + 1; } + template<typename T> T *GetArray() { return reinterpret_cast<T *>(this->GetArray()); } + const void *GetArray() const { return std::addressof(this->header) + 1; } + template<typename T> const T *GetArray() const { return reinterpret_cast<const T *>(this->GetArray()); } + + s64 GetBeginOffset() const { return *this->GetArray<s64>(); } + s64 GetEndOffset() const { return this->header.offset; } + + IteratorType GetBegin() { return IteratorType(this->GetArray<s64>()); } + IteratorType GetEnd() { return IteratorType(this->GetArray<s64>()) + this->header.count; } + IteratorType GetBegin() const { return IteratorType(this->GetArray<s64>()); } + IteratorType GetEnd() const { return IteratorType(this->GetArray<s64>()) + this->header.count; } + + IteratorType GetBegin(size_t entry_size) { return IteratorType(this->GetArray(), entry_size); } + IteratorType GetEnd(size_t entry_size) { return IteratorType(this->GetArray(), entry_size) + this->header.count; } + IteratorType GetBegin(size_t entry_size) const { return IteratorType(this->GetArray(), entry_size); } + IteratorType GetEnd(size_t entry_size) const { return IteratorType(this->GetArray(), entry_size) + this->header.count; } + }; + + constexpr inline s64 GetBucketTreeEntryOffset(s64 entry_set_offset, size_t entry_size, s32 entry_index) { + return entry_set_offset + sizeof(BucketTree::NodeHeader) + entry_index * static_cast<s64>(entry_size); + } + + constexpr inline s64 GetBucketTreeEntryOffset(s32 entry_set_index, size_t node_size, size_t entry_size, s32 entry_index) { + return GetBucketTreeEntryOffset(entry_set_index * static_cast<s64>(node_size), entry_size, entry_index); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_buffered_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_buffered_storage.hpp new file mode 100644 index 00000000..5603864a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_buffered_storage.hpp @@ -0,0 +1,81 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/fs/fs_istorage.hpp> +#include <stratosphere/fs/fs_substorage.hpp> +#include <stratosphere/fs/fs_i_buffer_manager.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: Unknown */ + class BufferedStorage : public ::ams::fs::IStorage { + NON_COPYABLE(BufferedStorage); + NON_MOVEABLE(BufferedStorage); + private: + class Cache; + class UniqueCache; + class SharedCache; + private: + fs::SubStorage m_base_storage; + fs::IBufferManager *m_buffer_manager; + size_t m_block_size; + s64 m_base_storage_size; + std::unique_ptr<Cache[]> m_caches; + s32 m_cache_count; + Cache *m_next_acquire_cache; + Cache *m_next_fetch_cache; + os::SdkMutex m_mutex; + bool m_bulk_read_enabled; + public: + BufferedStorage(); + virtual ~BufferedStorage(); + + Result Initialize(fs::SubStorage base_storage, fs::IBufferManager *buffer_manager, size_t block_size, s32 buffer_count); + void Finalize(); + + bool IsInitialized() const { return m_caches != nullptr; } + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + + virtual Result GetSize(s64 *out) override; + virtual Result SetSize(s64 size) override; + + virtual Result Flush() override; + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + using IStorage::OperateRange; + + void InvalidateCaches(); + + fs::IBufferManager *GetBufferManager() const { return m_buffer_manager; } + + void EnableBulkRead() { m_bulk_read_enabled = true; } + private: + Result PrepareAllocation(); + Result ControlDirtiness(); + Result ReadCore(s64 offset, void *buffer, size_t size); + + bool ReadHeadCache(s64 *offset, void *buffer, size_t *size, s64 *buffer_offset); + bool ReadTailCache(s64 offset, void *buffer, size_t *size, s64 buffer_offset); + + Result BulkRead(s64 offset, void *buffer, size_t size, bool head_cache_needed, bool tail_cache_needed); + + Result WriteCore(s64 offset, const void *buffer, size_t size); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_compressed_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_compressed_storage.hpp new file mode 100644 index 00000000..0b28f2c5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_compressed_storage.hpp @@ -0,0 +1,1454 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fssystem/fssystem_asynchronous_access.hpp> +#include <stratosphere/fssystem/fssystem_bucket_tree.hpp> +#include <stratosphere/fssystem/fssystem_compression_common.hpp> +#include <stratosphere/fs/fs_i_buffer_manager.hpp> +#include <stratosphere/fssystem/impl/fssystem_block_cache_manager.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class CompressedStorage : public ::ams::fs::IStorage, public ::ams::fssystem::IAsynchronousAccessSplitter, public ::ams::fs::impl::Newable { + NON_COPYABLE(CompressedStorage); + NON_MOVEABLE(CompressedStorage); + public: + static constexpr size_t NodeSize = 16_KB; + + using IAllocator = BucketTree::IAllocator; + + struct Entry { + s64 virt_offset; + s64 phys_offset; + CompressionType compression_type; + s32 phys_size; + + s64 GetPhysicalSize() const { + return this->phys_size; + } + }; + static_assert(util::is_pod<Entry>::value); + static_assert(sizeof(Entry) == 0x18); + public: + static constexpr s64 QueryNodeStorageSize(s32 entry_count) { + return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); + } + + static constexpr s64 QueryEntryStorageSize(s32 entry_count) { + return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); + } + private: + class CompressedStorageCore { + NON_COPYABLE(CompressedStorageCore); + NON_MOVEABLE(CompressedStorageCore); + private: + size_t m_block_size_max; + size_t m_continuous_reading_size_max; + BucketTree m_table; + fs::SubStorage m_data_storage; + GetDecompressorFunction m_get_decompressor_function; + public: + CompressedStorageCore() : m_table(), m_data_storage() { /* ... */ } + + ~CompressedStorageCore() { + this->Finalize(); + } + public: + Result Initialize(MemoryResource *bktr_allocator, fs::SubStorage data_storage, fs::SubStorage node_storage, fs::SubStorage entry_storage, s32 bktr_entry_count, size_t block_size_max, size_t continuous_reading_size_max, GetDecompressorFunction get_decompressor) { + /* Check pre-conditions. */ + AMS_ASSERT(bktr_allocator != nullptr); + AMS_ASSERT(0 < block_size_max); + AMS_ASSERT(block_size_max <= continuous_reading_size_max); + AMS_ASSERT(get_decompressor != nullptr); + + /* Initialize our entry table. */ + R_TRY(m_table.Initialize(bktr_allocator, node_storage, entry_storage, NodeSize, sizeof(Entry), bktr_entry_count)); + + /* Set our other fields. */ + m_block_size_max = block_size_max; + m_continuous_reading_size_max = continuous_reading_size_max; + m_data_storage = data_storage; + m_get_decompressor_function = get_decompressor; + + R_SUCCEED(); + } + + void Finalize() { + if (this->IsInitialized()) { + m_table.Finalize(); + m_data_storage = fs::SubStorage(); + } + } + + fs::IStorage *GetDataStorage() { return std::addressof(m_data_storage); } + + Result GetDataStorageSize(s64 *out) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + + /* Get size. */ + R_RETURN(m_data_storage.GetSize(out)); + } + + BucketTree &GetEntryTable() { return m_table; } + + Result GetEntryList(Entry *out_entries, s32 *out_read_count, s32 max_entry_count, s64 offset, s64 size) { + /* Check pre-conditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(size >= 0); + AMS_ASSERT(this->IsInitialized()); + + /* Check that we can output the count. */ + R_UNLESS(out_read_count != nullptr, fs::ResultNullptrArgument()); + + /* Check that we have anything to read at all. */ + R_SUCCEED_IF(size == 0); + + /* Check that either we have a buffer, or this is to determine how many we need. */ + if (max_entry_count != 0) { + R_UNLESS(out_entries != nullptr, fs::ResultNullptrArgument()); + } + + /* Get the table offsets. */ + BucketTree::Offsets table_offsets; + R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); + + /* Validate arguments. */ + R_UNLESS(table_offsets.IsInclude(offset, size), fs::ResultOutOfRange()); + + /* Find the offset in our tree. */ + BucketTree::Visitor visitor; + R_TRY(m_table.Find(std::addressof(visitor), offset)); + { + const auto entry_offset = visitor.Get<Entry>()->virt_offset; + R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), fs::ResultUnexpectedInCompressedStorageA()); + } + + /* Get the entries. */ + const auto end_offset = offset + size; + s32 read_count = 0; + while (visitor.Get<Entry>()->virt_offset < end_offset) { + /* If we should be setting the output, do so. */ + if (max_entry_count != 0) { + /* Ensure we only read as many entries as we can. */ + if (read_count >= max_entry_count) { + break; + } + + /* Set the current output entry. */ + out_entries[read_count] = *visitor.Get<Entry>(); + } + + /* Increase the read count. */ + ++read_count; + + /* If we're at the end, we're done. */ + if (!visitor.CanMoveNext()) { + break; + } + + /* Move to the next entry. */ + R_TRY(visitor.MoveNext()); + } + + /* Set the output read count. */ + *out_read_count = read_count; + R_SUCCEED(); + } + + Result GetSize(s64 *out) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + + /* Get our table offsets. */ + BucketTree::Offsets offsets; + R_TRY(m_table.GetOffsets(std::addressof(offsets))); + + /* Set the output. */ + *out = offsets.end_offset; + R_SUCCEED(); + } + + Result Invalidate() { + /* Invalidate our entry table. */ + R_TRY(m_table.InvalidateCache()); + + /* Invalidate our data storage. */ + R_TRY(m_data_storage.OperateRange(fs::OperationId::Invalidate, 0, std::numeric_limits<s64>::max())); + + R_SUCCEED(); + } + + Result OperatePerEntry(s64 offset, s64 size, auto f) { + /* Check pre-conditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(size >= 0); + AMS_ASSERT(this->IsInitialized()); + + /* Succeed if there's nothing to operate on. */ + R_SUCCEED_IF(size == 0); + + /* Get the table offsets. */ + BucketTree::Offsets table_offsets; + R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); + + /* Validate arguments. */ + R_UNLESS(table_offsets.IsInclude(offset, size), fs::ResultOutOfRange()); + + /* Find the offset in our tree. */ + BucketTree::Visitor visitor; + R_TRY(m_table.Find(std::addressof(visitor), offset)); + { + const auto entry_offset = visitor.Get<Entry>()->virt_offset; + R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), fs::ResultUnexpectedInCompressedStorageA()); + } + + /* Prepare to operate in chunks. */ + auto cur_offset = offset; + const auto end_offset = offset + static_cast<s64>(size); + + while (cur_offset < end_offset) { + /* Get the current entry. */ + const auto cur_entry = *visitor.Get<Entry>(); + + /* Get and validate the entry's offset. */ + const auto cur_entry_offset = cur_entry.virt_offset; + R_UNLESS(cur_entry_offset <= cur_offset, fs::ResultUnexpectedInCompressedStorageA()); + + /* Get and validate the next entry offset. */ + s64 next_entry_offset; + if (visitor.CanMoveNext()) { + R_TRY(visitor.MoveNext()); + next_entry_offset = visitor.Get<Entry>()->virt_offset; + R_UNLESS(table_offsets.IsInclude(next_entry_offset), fs::ResultUnexpectedInCompressedStorageA()); + } else { + next_entry_offset = table_offsets.end_offset; + } + R_UNLESS(cur_offset < next_entry_offset, fs::ResultUnexpectedInCompressedStorageA()); + + /* Get the offset of the entry in the data we read. */ + const auto data_offset = cur_offset - cur_entry_offset; + const auto data_size = (next_entry_offset - cur_entry_offset); + AMS_ASSERT(data_size > 0); + + /* Determine how much is left. */ + const auto remaining_size = end_offset - cur_offset; + const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset); + AMS_ASSERT(cur_size <= size); + + /* Get the data storage size. */ + s64 storage_size = 0; + R_TRY(m_data_storage.GetSize(std::addressof(storage_size))); + + /* Check that our read remains naively physically in bounds. */ + R_UNLESS(0 <= cur_entry.phys_offset && cur_entry.phys_offset <= storage_size, fs::ResultUnexpectedInCompressedStorageC()); + + /* If we have any compression, verify that we remain physically in bounds. */ + if (cur_entry.compression_type != CompressionType_None) { + R_UNLESS(cur_entry.phys_offset + cur_entry.GetPhysicalSize() <= storage_size, fs::ResultUnexpectedInCompressedStorageC()); + } + + /* Check that block alignment requirements are met. */ + if (CompressionTypeUtility::IsBlockAlignmentRequired(cur_entry.compression_type)) { + R_UNLESS(util::IsAligned(cur_entry.phys_offset, CompressionBlockAlignment), fs::ResultUnexpectedInCompressedStorageA()); + } + + /* Invoke the operator. */ + bool is_continuous = true; + R_TRY(f(std::addressof(is_continuous), cur_entry, data_size, data_offset, cur_size)); + + /* If not continuous, we're done. */ + if (!is_continuous) { + break; + } + + /* Advance. */ + cur_offset += cur_size; + } + + R_SUCCEED(); + } + + Result OperateRange(s64 offset, s64 size, auto f) { + /* Get the table offsets. */ + BucketTree::Offsets table_offsets; + R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); + + /* Validate arguments. */ + R_UNLESS(table_offsets.IsInclude(offset, size), fs::ResultOutOfRange()); + + /* If our table is empty, we have nothing to operate on. */ + R_SUCCEED_IF(m_table.IsEmpty()); + + /* Operate on the range. */ + s64 required_access_physical_offset = 0; + s64 required_access_physical_size = 0; + R_TRY(this->OperatePerEntry(offset, size, [&] (bool *out_continuous, const Entry &entry, s64 virtual_data_size, s64 data_offset, s64 read_size) -> Result { + AMS_UNUSED(virtual_data_size); + + /* Determine the physical extents. */ + s64 physical_offset, physical_size; + if (CompressionTypeUtility::IsRandomAccessible(entry.compression_type)) { + physical_offset = entry.phys_offset + data_offset; + physical_size = read_size; + } else { + physical_offset = entry.phys_offset; + physical_size = entry.GetPhysicalSize(); + } + + /* If we have a pending data storage operation, perform it if we have to. */ + const s64 required_access_physical_end = required_access_physical_offset + required_access_physical_size; + if (required_access_physical_size > 0) { + /* Check that we can can coalesce this operation with the previous one; if we can't, we need to perform it. */ + if (!(required_access_physical_end <= physical_offset && physical_offset <= util::AlignUp(required_access_physical_end, CompressionBlockAlignment))) { + R_TRY(f(required_access_physical_offset, required_access_physical_size)); + + required_access_physical_size = 0; + } + } + + /* If we need to access the data storage, update our storage access parameters. */ + if (CompressionTypeUtility::IsDataStorageAccessRequired(entry.compression_type)) { + /* Update the required access parameters. */ + if (required_access_physical_size > 0) { + required_access_physical_size += physical_size + (physical_offset - required_access_physical_end); + } else { + required_access_physical_offset = physical_offset; + required_access_physical_size = physical_size; + } + } else { + /* Verify that we're allowed to be operating on the non-data-storage-access type. */ + R_UNLESS(entry.compression_type == CompressionType_Zeros, fs::ResultUnexpectedInCompressedStorageB()); + } + + /* We're always continuous. */ + *out_continuous = true; + R_SUCCEED(); + })); + + /* If we have a pending operation, perform it. */ + if (required_access_physical_size > 0) { + R_TRY(f(required_access_physical_offset, required_access_physical_size)); + } + + R_SUCCEED(); + } + + Result QueryAppropriateOffsetForAsynchronousAccess(s64 *out, s64 offset, s64 access_size, s64 alignment_size) { + /* Check pre-conditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(this->IsInitialized()); + + /* Check that we can write to the output. */ + R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + + /* Get the table offsets. */ + BucketTree::Offsets table_offsets; + R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); + + /* Validate arguments. */ + R_UNLESS(table_offsets.IsInclude(offset, 1), fs::ResultOutOfRange()); + + /* Operate on the range. */ + s64 required_access_physical_offset = 0; + s64 required_access_physical_size = 0; + s64 required_access_physical_end = 0; + + s64 appropriate_virtual_offset = offset; + R_TRY(this->OperatePerEntry(offset, table_offsets.end_offset - offset, [&] (bool *out_continuous, const Entry &entry, s64 virtual_data_size, s64 data_offset, s64 read_size) -> Result { + AMS_UNUSED(virtual_data_size); + + /* Determine the physical extents. */ + s64 physical_offset, physical_size; + if (CompressionTypeUtility::IsRandomAccessible(entry.compression_type)) { + physical_offset = entry.phys_offset + data_offset; + physical_size = read_size; + } else { + physical_offset = entry.phys_offset; + physical_size = entry.GetPhysicalSize(); + } + + /* If we don't need to access the data storage, update our storage access parameters simply. */ + if (!CompressionTypeUtility::IsDataStorageAccessRequired(entry.compression_type)) { + /* Verify that we're allowed to be operating on the non-data-storage-access type. */ + R_UNLESS(entry.compression_type == CompressionType_Zeros, fs::ResultUnexpectedInCompressedStorageB()); + + /* No access is required, so we can advance the offset freely. */ + appropriate_virtual_offset += read_size; + + /* A read to zeros is always continuous. */ + *out_continuous = true; + R_SUCCEED(); + } + + /* Update the required access parameters. */ + if (required_access_physical_size > 0) { + /* Check that we can can coalesce this operation with the previous one; if we can't, we need to account for the gap. */ + if ((required_access_physical_end <= physical_offset && physical_offset <= util::AlignUp(required_access_physical_end, CompressionBlockAlignment))) { + const s64 gap_size = physical_offset - required_access_physical_end; + + if (required_access_physical_size + gap_size > access_size) { + *out_continuous = false; + R_SUCCEED(); + } + + required_access_physical_size += gap_size; + } + } else { + required_access_physical_offset = physical_offset; + } + + /* If we're within the access bounds, we want to continue on. */ + if (physical_size + required_access_physical_size <= access_size) { + required_access_physical_size += physical_size; + required_access_physical_end = physical_offset + physical_size; + + appropriate_virtual_offset += read_size; + *out_continuous = true; + R_SUCCEED(); + } + + /* We're no longer within the access bounds, so we won't be continuous. */ + *out_continuous = false; + + /* Ensure we account for block alignment. */ + if (CompressionTypeUtility::IsBlockAlignmentRequired(entry.compression_type)) { + if (appropriate_virtual_offset == offset) { + appropriate_virtual_offset += read_size; + access_size = std::max<s64>(access_size, read_size); + } + } else { + /* Get the default splitter. */ + auto * const default_splitter = fssystem::IAsynchronousAccessSplitter::GetDefaultAsynchronousAccessSplitter(); + + /* Query for an appropriate offset. */ + s64 appropriate_physical_offset = 0; + R_TRY(default_splitter->QueryAppropriateOffset(std::addressof(appropriate_physical_offset), physical_offset, access_size - required_access_physical_size, alignment_size)); + + /* Use it, if we should. */ + if (const auto gap_size = appropriate_physical_offset - physical_offset; gap_size > 0) { + appropriate_virtual_offset += gap_size; + required_access_physical_size += gap_size; + } + } + + R_SUCCEED(); + })); + + /* Check that the offset is actually appropriate. */ + AMS_ASSERT(offset <= appropriate_virtual_offset && appropriate_virtual_offset <= table_offsets.end_offset); + AMS_ASSERT(0 <= required_access_physical_size && required_access_physical_size <= access_size); + + /* Set the output. */ + *out = appropriate_virtual_offset; + R_SUCCEED(); + } + + Result QueryRange(void *dst, size_t dst_size, s64 offset, s64 size) { + /* Check arguments. */ + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidArgument()); + + /* If we have nothing to query, succeed immediately. */ + R_SUCCEED_IF(size <= 0); + + /* Operate on the range. */ + fs::QueryRangeInfo full_info; + full_info.Clear(); + + R_TRY(this->OperateRange(offset, size, [&](s64 offset, s64 size) -> Result { + /* Operate on our data storage. */ + fs::QueryRangeInfo cur_info; + R_TRY(m_data_storage.OperateRange(std::addressof(cur_info), sizeof(cur_info), fs::OperationId::QueryRange, offset, size, nullptr, 0)); + + /* Merge the info. */ + full_info.Merge(cur_info); + R_SUCCEED(); + })); + + R_SUCCEED(); + } + public: + using ReadImplFunction = util::IFunction<Result(void *, size_t)>; + using ReadFunction = util::IFunction<Result(size_t, const ReadImplFunction &)>; + public: + Result Read(s64 offset, s64 size, const ReadFunction &read_func) { + /* Check pre-conditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(this->IsInitialized()); + + /* Succeed immediately, if we have nothing to read. */ + R_SUCCEED_IF(size == 0); + + /* Declare read lambda. */ + constexpr int EntriesCountMax = 0x80; + struct Entries { + CompressionType compression_type; + u32 gap_from_prev; + u32 physical_size; + u32 virtual_size; + }; + Entries entries[EntriesCountMax]; + s32 entry_count = 0; + Entry prev_entry = { .virt_offset = -1, }; + bool will_allocate_pooled_buffer = false; + s64 required_access_physical_offset = 0; + s64 required_access_physical_size = 0; + + auto PerformRequiredRead = [&]() -> Result { + /* If there are no entries, we have nothing to do. */ + R_SUCCEED_IF(entry_count == 0); + + /* Get the remaining size in a convenient form. */ + const size_t total_required_size = static_cast<size_t>(required_access_physical_size); + + /* Perform the read based on whether we need to allocate a buffer. */ + if (will_allocate_pooled_buffer) { + /* Allocate a pooled buffer. */ + fssystem::PooledBuffer pooled_buffer; + if (pooled_buffer.GetAllocatableSizeMax() >= total_required_size) { + pooled_buffer.Allocate(total_required_size, m_block_size_max); + } else { + pooled_buffer.AllocateParticularlyLarge(std::min<size_t>(total_required_size, PooledBuffer::GetAllocatableParticularlyLargeSizeMax()), m_block_size_max); + } + + /* Read each of the entries. */ + for (s32 entry_idx = 0; entry_idx < entry_count; ++entry_idx) { + /* Determine the current read size. */ + bool will_use_pooled_buffer = false; + const size_t cur_read_size = [&] () ALWAYS_INLINE_LAMBDA -> size_t { + if (const size_t target_entry_size = static_cast<size_t>(entries[entry_idx].physical_size) + static_cast<size_t>(entries[entry_idx].gap_from_prev); target_entry_size <= pooled_buffer.GetSize()) { + /* We'll be using the pooled buffer. */ + will_use_pooled_buffer = true; + + /* Determine how much we can read. */ + const size_t max_size = std::min<size_t>(required_access_physical_size, pooled_buffer.GetSize()); + + size_t read_size = 0; + for (auto n = entry_idx; n < entry_count; ++n) { + const size_t cur_entry_size = static_cast<size_t>(entries[n].physical_size) + static_cast<size_t>(entries[n].gap_from_prev); + if (read_size + cur_entry_size > max_size) { + break; + } + + read_size += cur_entry_size; + } + + return read_size; + } else { + /* If we don't fit, we must be uncompressed. */ + AMS_ASSERT(entries[entry_idx].compression_type == CompressionType_None); + + /* We can perform the whole of an uncompressed read directly. */ + return entries[entry_idx].virtual_size; + } + }(); + + /* Perform the read based on whether or not we'll use the pooled buffer. */ + if (will_use_pooled_buffer) { + /* Read the compressed data into the pooled buffer. */ + auto * const buffer = pooled_buffer.GetBuffer(); + R_TRY(m_data_storage.Read(required_access_physical_offset, buffer, cur_read_size)); + + /* Temporarily increase our thread priority, while we decompress the data. */ + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + /* Decompress the data. */ + size_t buffer_offset; + for (buffer_offset = 0; entry_idx < entry_count && ((static_cast<size_t>(entries[entry_idx].physical_size) + static_cast<size_t>(entries[entry_idx].gap_from_prev)) == 0 || buffer_offset < cur_read_size); buffer_offset += entries[entry_idx++].physical_size) { + /* Advance by the relevant gap. */ + buffer_offset += entries[entry_idx].gap_from_prev; + + const auto compression_type = entries[entry_idx].compression_type; + switch (compression_type) { + case CompressionType_None: + { + /* Check that we can remain within bounds. */ + AMS_ASSERT(buffer_offset + entries[entry_idx].virtual_size <= cur_read_size); + + /* Perform no decompression. */ + R_TRY(read_func(entries[entry_idx].virtual_size, util::MakeIFunction([&] (void *dst, size_t dst_size) -> Result { + /* Check that the size is valid. */ + AMS_ASSERT(dst_size == entries[entry_idx].virtual_size); + AMS_UNUSED(dst_size); + + /* We have no compression, so just copy the data out. */ + std::memcpy(dst, buffer + buffer_offset, entries[entry_idx].virtual_size); + R_SUCCEED(); + }))); + } + break; + case CompressionType_Zeros: + { + /* Check that we can remain within bounds. */ + AMS_ASSERT(buffer_offset <= cur_read_size); + + /* Zero the memory. */ + R_TRY(read_func(entries[entry_idx].virtual_size, util::MakeIFunction([&] (void *dst, size_t dst_size) -> Result { + /* Check that the size is valid. */ + AMS_ASSERT(dst_size == entries[entry_idx].virtual_size); + AMS_UNUSED(dst_size); + + /* The data is zeroes, so zero the buffer. */ + std::memset(dst, 0, entries[entry_idx].virtual_size); + R_SUCCEED(); + }))); + } + break; + default: + { + /* Check that we can remain within bounds. */ + AMS_ASSERT(buffer_offset + entries[entry_idx].physical_size <= cur_read_size); + + /* Get the decompressor. */ + const auto decompressor = this->GetDecompressor(compression_type); + R_UNLESS(decompressor != nullptr, fs::ResultUnexpectedInCompressedStorageB()); + + /* Decompress the data. */ + R_TRY(read_func(entries[entry_idx].virtual_size, util::MakeIFunction([&] (void *dst, size_t dst_size) -> Result { + /* Check that the size is valid. */ + AMS_ASSERT(dst_size == entries[entry_idx].virtual_size); + AMS_UNUSED(dst_size); + + /* Perform the decompression. */ + R_RETURN(decompressor(dst, entries[entry_idx].virtual_size, buffer + buffer_offset, entries[entry_idx].physical_size)); + }))); + } + break; + } + } + + /* Check that we processed the correct amount of data. */ + AMS_ASSERT(buffer_offset == cur_read_size); + } else { + /* Account for the gap from the previous entry. */ + required_access_physical_offset += entries[entry_idx].gap_from_prev; + required_access_physical_size -= entries[entry_idx].gap_from_prev; + + /* We don't need the buffer (as the data is uncompressed), so just execute the read. */ + R_TRY(read_func(cur_read_size, util::MakeIFunction([&] (void *dst, size_t dst_size) -> Result { + /* Check that the size is valid. */ + AMS_ASSERT(dst_size == cur_read_size); + AMS_UNUSED(dst_size); + + /* Perform the read. */ + R_RETURN(m_data_storage.Read(required_access_physical_offset, dst, cur_read_size)); + }))); + } + + /* Advance on. */ + required_access_physical_offset += cur_read_size; + required_access_physical_size -= cur_read_size; + } + + /* Verify that we have nothing remaining to read. */ + AMS_ASSERT(required_access_physical_size == 0); + + R_SUCCEED(); + } else { + /* We don't need a buffer, so just execute the read. */ + R_TRY(read_func(total_required_size, util::MakeIFunction([&] (void *dst, size_t dst_size) -> Result { + /* Check that the size is valid. */ + AMS_ASSERT(dst_size == total_required_size); + AMS_UNUSED(dst_size); + + /* Perform the read. */ + R_RETURN(m_data_storage.Read(required_access_physical_offset, dst, total_required_size)); + }))); + } + + R_SUCCEED(); + }; + + R_TRY(this->OperatePerEntry(offset, size, [&] (bool *out_continuous, const Entry &entry, s64 virtual_data_size, s64 data_offset, s64 read_size) -> Result { + /* Determine the physical extents. */ + s64 physical_offset, physical_size; + if (CompressionTypeUtility::IsRandomAccessible(entry.compression_type)) { + physical_offset = entry.phys_offset + data_offset; + physical_size = read_size; + } else { + physical_offset = entry.phys_offset; + physical_size = entry.GetPhysicalSize(); + } + + /* If we have a pending data storage operation, perform it if we have to. */ + const s64 required_access_physical_end = required_access_physical_offset + required_access_physical_size; + if (required_access_physical_size > 0) { + const bool required_by_gap = !(required_access_physical_end <= physical_offset && physical_offset <= util::AlignUp(required_access_physical_end, CompressionBlockAlignment)); + const bool required_by_continuous_size = ((physical_size + physical_offset) - required_access_physical_end) + required_access_physical_size > static_cast<s64>(m_continuous_reading_size_max); + const bool required_by_entry_count = entry_count == EntriesCountMax; + if (required_by_gap || required_by_continuous_size || required_by_entry_count) { + /* Check that our planned access is sane. */ + AMS_ASSERT(!will_allocate_pooled_buffer || required_access_physical_size <= static_cast<s64>(m_continuous_reading_size_max)); + + /* Perform the required read. */ + R_TRY(PerformRequiredRead()); + + /* Reset our requirements. */ + prev_entry.virt_offset = -1; + required_access_physical_size = 0; + entry_count = 0; + will_allocate_pooled_buffer = false; + } + } + + /* Sanity check that we're within bounds on entries. */ + AMS_ASSERT(entry_count < EntriesCountMax); + + /* Determine if a buffer allocation is needed. */ + if (entry.compression_type != CompressionType_None || (prev_entry.virt_offset >= 0 && entry.virt_offset - prev_entry.virt_offset != entry.phys_offset - prev_entry.phys_offset)) { + will_allocate_pooled_buffer = true; + } + + /* If we need to access the data storage, update our required access parameters. */ + if (CompressionTypeUtility::IsDataStorageAccessRequired(entry.compression_type)) { + /* If the data is compressed, ensure the access is sane. */ + if (entry.compression_type != CompressionType_None) { + R_UNLESS(data_offset == 0, fs::ResultInvalidOffset()); + R_UNLESS(virtual_data_size == read_size, fs::ResultInvalidSize()); + R_UNLESS(entry.GetPhysicalSize() <= static_cast<s64>(m_block_size_max), fs::ResultUnexpectedInCompressedStorageD()); + } + + /* Update the required access parameters. */ + s64 gap_from_prev; + if (required_access_physical_size > 0) { + gap_from_prev = physical_offset - required_access_physical_end; + } else { + gap_from_prev = 0; + required_access_physical_offset = physical_offset; + } + required_access_physical_size += physical_size + gap_from_prev; + + /* Create an entry to access the data storage. */ + entries[entry_count++] = { + .compression_type = entry.compression_type, + .gap_from_prev = static_cast<u32>(gap_from_prev), + .physical_size = static_cast<u32>(physical_size), + .virtual_size = static_cast<u32>(read_size), + }; + } else { + /* Verify that we're allowed to be operating on the non-data-storage-access type. */ + R_UNLESS(entry.compression_type == CompressionType_Zeros, fs::ResultUnexpectedInCompressedStorageB()); + + /* If we have entries, create a fake entry for the zero region. */ + if (entry_count != 0) { + /* We need to have a physical size. */ + R_UNLESS(entry.GetPhysicalSize() != 0, fs::ResultUnexpectedInCompressedStorageD()); + + /* Create a fake entry. */ + entries[entry_count++] = { + .compression_type = CompressionType_Zeros, + .gap_from_prev = 0, + .physical_size = 0, + .virtual_size = static_cast<u32>(read_size), + }; + } else { + /* We have no entries, so we can just perform the read. */ + R_TRY(read_func(static_cast<size_t>(read_size), util::MakeIFunction([&] (void *dst, size_t dst_size) -> Result { + /* Check the space we should zero is correct. */ + AMS_ASSERT(dst_size == static_cast<size_t>(read_size)); + AMS_UNUSED(dst_size); + + /* Zero the memory. */ + std::memset(dst, 0, read_size); + R_SUCCEED(); + }))); + } + } + + + /* Set the previous entry. */ + prev_entry = entry; + + /* We're continuous. */ + *out_continuous = true; + R_SUCCEED(); + })); + + /* If we still have a pending access, perform it. */ + if (required_access_physical_size != 0) { + R_TRY(PerformRequiredRead()); + } + + R_SUCCEED(); + } + private: + DecompressorFunction GetDecompressor(CompressionType type) const { + /* Check that we can get a decompressor for the type. */ + if (CompressionTypeUtility::IsUnknownType(type)) { + return nullptr; + } + + /* Get the decompressor. */ + return m_get_decompressor_function(type); + } + + bool IsInitialized() const { + return m_table.IsInitialized(); + } + }; + + class CacheManager { + NON_COPYABLE(CacheManager); + NON_MOVEABLE(CacheManager); + private: + struct Range { + s64 offset; + size_t size; + + s64 GetEndOffset() const { + return this->offset + this->size; + } + + bool IsIncluded(s64 ofs) const { + return this->offset <= ofs && ofs < this->GetEndOffset(); + } + }; + static_assert(util::is_pod<Range>::value); + + struct CacheEntry { + Range range; + fs::IBufferManager::CacheHandle handle; + uintptr_t memory_address; + u32 memory_size; + bool is_valid; + bool is_cached; + u16 lru_counter; + + void Invalidate() { + /* ... */ + } + + bool IsAllocated() const { + return this->is_valid && this->handle != 0; + } + + bool IsIncluded(s64 offset) const { + return this->is_valid && this->range.IsIncluded(offset); + } + + bool IsWriteBack() const { + return false; + } + }; + static_assert(util::is_pod<CacheEntry>::value); + + struct AccessRange { + s64 virtual_offset; + s64 virtual_size; + u32 physical_size; + bool is_block_alignment_required; + + s64 GetEndVirtualOffset() const { + return this->virtual_offset + this->virtual_size; + } + }; + static_assert(util::is_pod<AccessRange>::value); + + using BlockCacheManager = ::ams::fssystem::impl::BlockCacheManager<CacheEntry, fs::IBufferManager>; + using CacheIndex = BlockCacheManager::CacheIndex; + private: + size_t m_cache_size_unk_0; + size_t m_cache_size_unk_1; + os::SdkMutex m_mutex; + BlockCacheManager m_block_cache_manager; + s64 m_storage_size = 0; + public: + CacheManager() = default; + + ~CacheManager() { this->Finalize(); } + public: + Result Initialize(fs::IBufferManager *cache_allocator, s64 storage_size, size_t cache_size_0, size_t cache_size_1, size_t max_cache_entries) { + /* Initialize our block cache manager. */ + R_TRY(m_block_cache_manager.Initialize(cache_allocator, max_cache_entries)); + + /* Set our fields. */ + m_cache_size_unk_0 = cache_size_0; + m_cache_size_unk_1 = cache_size_1; + m_storage_size = storage_size; + + R_SUCCEED(); + } + + void Finalize() { + /* If necessary, invalidate anything we have cached. */ + if (m_block_cache_manager.IsInitialized()) { + this->Invalidate(); + } + + /* Finalize our block cache manager. */ + m_block_cache_manager.Finalize(); + } + + void Invalidate() { + /* Acquire exclusive access to our manager. */ + std::scoped_lock lk(m_mutex); + + /* Invalidate all entries. */ + return m_block_cache_manager.Invalidate(); + } + + Result Read(CompressedStorageCore &core, s64 offset, void *buffer, size_t size) { + /* If we have nothing to read, succeed. */ + R_SUCCEED_IF(size == 0); + + /* Check that we have a buffer to read into. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Check that the read is in bounds. */ + R_UNLESS(offset <= m_storage_size, fs::ResultInvalidOffset()); + + /* Determine how much we can read. */ + const size_t read_size = std::min<size_t>(size, m_storage_size - offset); + + /* Create head/tail ranges. */ + AccessRange head_range = {}; + AccessRange tail_range = {}; + bool is_tail_set = false; + + /* Operate to determine the head range. */ + R_TRY(core.OperatePerEntry(offset, 1, [&] (bool *out_continuous, const Entry &entry, s64 virtual_data_size, s64 data_offset, s64 data_read_size) -> Result { + AMS_UNUSED(data_offset, data_read_size); + + /* Set the head range. */ + head_range = { + .virtual_offset = entry.virt_offset, + .virtual_size = virtual_data_size, + .physical_size = static_cast<u32>(entry.phys_size), + .is_block_alignment_required = CompressionTypeUtility::IsBlockAlignmentRequired(entry.compression_type), + }; + + /* If required, set the tail range. */ + if (static_cast<s64>(offset + read_size) <= entry.virt_offset + virtual_data_size) { + tail_range = { + .virtual_offset = entry.virt_offset, + .virtual_size = virtual_data_size, + .physical_size = static_cast<u32>(entry.phys_size), + .is_block_alignment_required = CompressionTypeUtility::IsBlockAlignmentRequired(entry.compression_type), + }; + is_tail_set = true; + } + + /* We only want to determine the head range, so we're not continuous. */ + *out_continuous = false; + R_SUCCEED(); + })); + + /* If necessary, determine the tail range. */ + if (!is_tail_set) { + R_TRY(core.OperatePerEntry(offset + read_size - 1, 1, [&] (bool *out_continuous, const Entry &entry, s64 virtual_data_size, s64 data_offset, s64 data_read_size) -> Result { + AMS_UNUSED(data_offset, data_read_size); + + /* Set the tail range. */ + tail_range = { + .virtual_offset = entry.virt_offset, + .virtual_size = virtual_data_size, + .physical_size = static_cast<u32>(entry.phys_size), + .is_block_alignment_required = CompressionTypeUtility::IsBlockAlignmentRequired(entry.compression_type), + }; + + /* We only want to determine the tail range, so we're not continuous. */ + *out_continuous = false; + R_SUCCEED(); + })); + } + + /* Begin performing the accesses. */ + s64 cur_offset = offset; + size_t cur_size = read_size; + char *cur_dst = static_cast<char *>(buffer); + + /* If we can use the head/tail cache, do so. */ + if (m_block_cache_manager.GetCount() > 0) { + /* Read the head cache. */ + R_TRY(this->ReadHeadCache(core, cur_offset, cur_dst, cur_size, head_range, tail_range)); + + /* If we're now done, succeed. */ + R_SUCCEED_IF(cur_size == 0); + + /* Read the tail cache. */ + R_TRY(this->ReadTailCache(core, cur_offset, cur_dst, cur_size, head_range, tail_range)); + + /* If we're now done, succeed. */ + R_SUCCEED_IF(cur_size == 0); + } + + /* Determine our alignment. */ + const bool head_unaligned = head_range.is_block_alignment_required && (cur_offset != head_range.virtual_offset || static_cast<s64>(cur_size) < head_range.virtual_size); + const bool tail_unaligned = [&] () ALWAYS_INLINE_LAMBDA -> bool { + if (tail_range.is_block_alignment_required) { + if (static_cast<s64>(cur_size + cur_offset) == tail_range.GetEndVirtualOffset()) { + return false; + } else if (!head_unaligned) { + return true; + } else { + return head_range.GetEndVirtualOffset() < static_cast<s64>(cur_size + cur_offset); + } + } else { + return false; + } + }(); + + /* Determine start/end offsets. */ + const s64 start_offset = head_range.is_block_alignment_required ? head_range.virtual_offset : cur_offset; + const s64 end_offset = tail_range.is_block_alignment_required ? tail_range.GetEndVirtualOffset() : cur_offset + cur_size; + + /* Perform the read. */ + bool is_burst_reading = false; + R_TRY(core.Read(start_offset, end_offset - start_offset, util::MakeIFunction([&] (size_t size_buffer_required, const CompressedStorageCore::ReadImplFunction &read_impl) -> Result { + /* Determine whether we're burst reading. */ + const AccessRange *unaligned_range = nullptr; + if (!is_burst_reading) { + /* Check whether we're using head, tail, or none as unaligned. */ + if (head_unaligned && head_range.virtual_offset <= cur_offset && cur_offset < head_range.GetEndVirtualOffset()) { + unaligned_range = std::addressof(head_range); + } else if (tail_unaligned && tail_range.virtual_offset <= cur_offset && cur_offset < tail_range.GetEndVirtualOffset()) { + unaligned_range = std::addressof(tail_range); + } else { + is_burst_reading = true; + } + } + AMS_ASSERT((is_burst_reading ^ (unaligned_range != nullptr))); + + /* Perform reading by burst, or not. */ + if (is_burst_reading) { + /* Check that the access is valid for burst reading. */ + AMS_ASSERT(size_buffer_required <= cur_size); + + /* Perform the read. */ + R_TRY(read_impl(cur_dst, size_buffer_required)); + + /* Advance. */ + cur_dst += size_buffer_required; + cur_offset += size_buffer_required; + cur_size -= size_buffer_required; + + /* Determine whether we're going to continue burst reading. */ + const s64 offset_aligned = tail_unaligned ? tail_range.virtual_offset : end_offset; + AMS_ASSERT(cur_offset <= offset_aligned); + + if (offset_aligned <= cur_offset) { + is_burst_reading = false; + } + } else { + /* We're not burst reading, so we have some unaligned range. */ + AMS_ASSERT(unaligned_range != nullptr); + + /* Check that the size is correct. */ + AMS_ASSERT(size_buffer_required == static_cast<size_t>(unaligned_range->virtual_size)); + + /* Get a pooled buffer for our read. */ + fssystem::PooledBuffer pooled_buffer; + pooled_buffer.Allocate(size_buffer_required, size_buffer_required); + + /* Perform read. */ + R_TRY(read_impl(pooled_buffer.GetBuffer(), size_buffer_required)); + + /* Copy the data we read to the destination. */ + const size_t skip_size = cur_offset - unaligned_range->virtual_offset; + const size_t copy_size = std::min<size_t>(cur_size, unaligned_range->GetEndVirtualOffset() - cur_offset); + + std::memcpy(cur_dst, pooled_buffer.GetBuffer() + skip_size, copy_size); + + /* Advance. */ + cur_dst += copy_size; + cur_offset += copy_size; + cur_size -= copy_size; + + /* If we should, cache what we read. */ + if (m_block_cache_manager.GetCount() > 0 && unaligned_range->physical_size > m_cache_size_unk_1) { + CacheEntry entry; + for (s64 ofs = unaligned_range->virtual_offset; ofs < unaligned_range->GetEndVirtualOffset(); ofs += entry.range.size) { + /* Find or allocate buffer. */ + fs::IBufferManager::MemoryRange memory_range; + R_TRY(this->FindOrAllocateBuffer(std::addressof(memory_range), std::addressof(entry), ofs, unaligned_range->GetEndVirtualOffset() - ofs)); + + /* If not cached, cache the data. */ + if (!entry.is_cached) { + std::memcpy(reinterpret_cast<void *>(memory_range.first), pooled_buffer.GetBuffer() + (ofs - unaligned_range->virtual_offset), entry.range.size); + entry.is_cached = true; + } + + /* Store the associated buffer. */ + this->StoreAssociateBuffer(memory_range, entry); + } + } + } + + R_SUCCEED(); + }))); + + R_SUCCEED(); + } + private: + Result FindBuffer(fs::IBufferManager::MemoryRange *out, CacheEntry *out_entry, s64 offset) { + /* Check pre-conditions. */ + AMS_ASSERT(m_block_cache_manager.IsInitialized()); + AMS_ASSERT(out != nullptr); + AMS_ASSERT(out_entry != nullptr); + + /* Acquire exclusive access to our entries. */ + std::scoped_lock lk(m_mutex); + + /* Find the buffer. */ + R_RETURN(this->FindBufferImpl(out, out_entry, offset)); + } + + Result FindBufferImpl(fs::IBufferManager::MemoryRange *out, CacheEntry *out_entry, s64 offset) { + /* Check pre-conditions. */ + AMS_ASSERT(m_mutex.IsLockedByCurrentThread()); + + /* Get our block cache count */ + const auto count = m_block_cache_manager.GetCount(); + + /* Try to find the buffer. */ + CacheIndex index; + for (index = 0; index < count; ++index) { + if (const auto &buffer = m_block_cache_manager[index]; buffer.IsAllocated() && buffer.IsIncluded(offset)) { + break; + } + } + + /* Set the output. */ + if (index != count) { + /* Acquire the entry. */ + m_block_cache_manager.AcquireCacheEntry(out_entry, out, index); + if (out->first == 0) { + *out = {}; + *out_entry = {}; + } + } else { + *out = {}; + *out_entry = {}; + } + + R_SUCCEED(); + } + + Result FindOrAllocateBuffer(fs::IBufferManager::MemoryRange *out, CacheEntry *out_entry, s64 offset, size_t max_range_size) { + /* Check pre-conditions. */ + AMS_ASSERT(m_block_cache_manager.IsInitialized()); + AMS_ASSERT(out != nullptr); + AMS_ASSERT(out_entry != nullptr); + + /* Acquire exclusive access to our block cache manager. */ + std::scoped_lock lk(m_mutex); + + /* Try to find the buffer. */ + R_TRY(this->FindBufferImpl(out, out_entry, offset)); + + /* Determine the range size. */ + const size_t range_size = std::min<size_t>(max_range_size, m_cache_size_unk_0); + + /* If necessary, allocate. */ + if (out->first == 0) { + R_TRY(fssystem::buffers::AllocateBufferUsingBufferManagerContext(out, m_block_cache_manager.GetAllocator(), range_size, fs::IBufferManager::BufferAttribute(0x20), [] (const fs::IBufferManager::MemoryRange &buffer) -> bool { + return buffer.first != 0; + }, AMS_CURRENT_FUNCTION_NAME)); + + /* Set the entry for the allocated buffer. */ + out_entry->is_valid = out->first != 0; + out_entry->is_cached = false; + out_entry->handle = 0; + out_entry->memory_address = 0; + out_entry->memory_size = 0; + out_entry->range.offset = offset; + out_entry->range.size = range_size; + out_entry->lru_counter = 0; + } + + /* Check that the result is valid. */ + AMS_ASSERT(out_entry->range.size <= out->second); + + R_SUCCEED(); + } + + Result ReadHeadCache(CompressedStorageCore &core, s64 &offset, char *&buffer, size_t &size, AccessRange &head_range, const AccessRange &tail_range) { + /* Check pre-conditions. */ + AMS_ASSERT(buffer != nullptr); + + /* Read until we're done with the head cache */ + while (head_range.virtual_size > 0 && head_range.virtual_offset < tail_range.GetEndVirtualOffset()) { + /* Cache the access extents. */ + s64 access_offset = offset; + char *access_buf = buffer; + size_t access_size = size; + + /* Determine the current access extents. */ + s64 cur_offset = head_range.virtual_offset + util::AlignDown<s64>(access_offset - head_range.virtual_offset, m_cache_size_unk_0); + while (cur_offset < head_range.GetEndVirtualOffset() && cur_offset < static_cast<s64>(offset + size)) { + /* Find the relevant entry. */ + fs::IBufferManager::MemoryRange memory_range = {}; + CacheEntry entry = {}; + R_TRY(this->FindBuffer(std::addressof(memory_range), std::addressof(entry), cur_offset)); + + /* If the entry isn't cached, we're done. */ + R_SUCCEED_IF(!entry.is_cached); + + /* Otherwise, copy the cacheed data. */ + const size_t copy_size = std::min<size_t>(access_size, entry.range.GetEndOffset() - access_offset); + + std::memcpy(access_buf, reinterpret_cast<const void *>(memory_range.first + access_offset - entry.range.offset), copy_size); + + /* Advance. */ + access_buf += copy_size; + access_offset += copy_size; + access_size -= copy_size; + + cur_offset += entry.range.size; + + /* Store the associated buffer. */ + this->StoreAssociateBuffer(memory_range, entry); + } + + /* Update the output extents. */ + buffer = access_buf; + offset = access_offset; + size = access_size; + + /* Determine the new head range. */ + AccessRange new_head_range = { + .virtual_offset = head_range.GetEndVirtualOffset(), + .virtual_size = 0, + .physical_size = 0, + .is_block_alignment_required = true, + }; + if (head_range.GetEndVirtualOffset() == tail_range.virtual_offset) { + /* We can use the tail range directly. */ + new_head_range.virtual_size = tail_range.virtual_size; + new_head_range.physical_size = tail_range.physical_size; + new_head_range.is_block_alignment_required = tail_range.is_block_alignment_required; + } else if (head_range.GetEndVirtualOffset() < tail_range.GetEndVirtualOffset()) { + /* We need to find the new head range. */ + R_TRY(core.OperatePerEntry(new_head_range.virtual_offset, 1, [&] (bool *out_continuous, const Entry &entry, s64 virtual_data_size, s64 data_offset, s64 data_read_size) -> Result { + AMS_UNUSED(data_offset, data_read_size); + + /* If we can, use the current entry. */ + if (entry.virt_offset < tail_range.GetEndVirtualOffset()) { + new_head_range = { + .virtual_offset = entry.virt_offset, + .virtual_size = virtual_data_size, + .physical_size = static_cast<u32>(entry.phys_size), + .is_block_alignment_required = CompressionTypeUtility::IsBlockAlignmentRequired(entry.compression_type), + }; + } + + /* We only want to determine the new head range, so we're not continuous. */ + *out_continuous = false; + R_SUCCEED(); + })); + } + + /* Update the head range. */ + head_range = new_head_range; + } + + R_SUCCEED(); + } + + Result ReadTailCache(CompressedStorageCore &core, s64 offset, char *buffer, size_t &size, const AccessRange &head_range, AccessRange &tail_range) { + /* Check pre-conditions. */ + AMS_ASSERT(buffer != nullptr); + + /* Read until we're done with the tail cache */ + while (tail_range.virtual_offset >= offset) { + /* Loop reading, while we can. */ + const s64 dst_end_offset = offset + size; + s64 cur_offset = tail_range.virtual_offset; + while (cur_offset < dst_end_offset) { + /* Find the relevant entry. */ + fs::IBufferManager::MemoryRange memory_range = {}; + CacheEntry entry = {}; + R_TRY(this->FindBuffer(std::addressof(memory_range), std::addressof(entry), cur_offset)); + + /* If the entry isn't cached, we're done. */ + R_SUCCEED_IF(!entry.is_cached); + + /* Sanity check our current access. */ + AMS_ASSERT(offset <= entry.range.offset); + + /* Copy the cacheed data. */ + const s64 cur_end_offset = std::min<s64>(dst_end_offset, entry.range.GetEndOffset()); + + std::memcpy(buffer + entry.range.offset - offset, reinterpret_cast<const void *>(memory_range.first), cur_end_offset - entry.range.offset); + + /* Advance. */ + cur_offset += entry.range.size; + + /* Store the associated buffer. */ + this->StoreAssociateBuffer(memory_range, entry); + } + + /* Update the output extents. */ + size -= std::min<s64>(dst_end_offset, tail_range.GetEndVirtualOffset()) - tail_range.virtual_offset; + + /* Update the tail range. */ + bool new_tail_found = false; + if (tail_range.virtual_offset - 1 >= 0) { + /* We need to find the new tail range. */ + R_TRY(core.OperatePerEntry(tail_range.virtual_offset - 1, 1, [&] (bool *out_continuous, const Entry &entry, s64 virtual_data_size, s64 data_offset, s64 data_read_size) -> Result { + AMS_UNUSED(data_offset, data_read_size); + + /* If we can, use the current entry. */ + if (head_range.virtual_offset != entry.virt_offset) { + tail_range = { + .virtual_offset = entry.virt_offset, + .virtual_size = virtual_data_size, + .physical_size = static_cast<u32>(entry.phys_size), + .is_block_alignment_required = CompressionTypeUtility::IsBlockAlignmentRequired(entry.compression_type), + }; + + new_tail_found = true; + } + + /* We only want to determine the new head range, so we're not continuous. */ + *out_continuous = false; + R_SUCCEED(); + })); + } + + /* If we didn't find a new tail, write a default (and we're done). */ + if (!new_tail_found) { + tail_range = { + .virtual_offset = tail_range.virtual_offset, + .virtual_size = 0, + .physical_size = 0, + .is_block_alignment_required = true, + }; + break; + } + } + + R_SUCCEED(); + } + + void StoreAssociateBuffer(const fs::IBufferManager::MemoryRange &memory_range, const CacheEntry &entry) { + /* Check pre-conditions. */ + AMS_ASSERT(m_block_cache_manager.GetCount() > 0); + + /* Acquire exclusive access to our manager. */ + std::scoped_lock lk(m_mutex); + + /* Get empty cache index. */ + CacheIndex empty_index, lru_index; + m_block_cache_manager.GetEmptyCacheEntryIndex(std::addressof(empty_index), std::addressof(lru_index)); + + /* If nothing is empty, invalidate the least recently used entry. */ + if (empty_index == BlockCacheManager::InvalidCacheIndex) { + m_block_cache_manager.InvalidateCacheEntry(lru_index); + empty_index = lru_index; + } + + /* Set the entry. */ + m_block_cache_manager.SetCacheEntry(empty_index, entry, memory_range); + } + }; + private: + CompressedStorageCore m_core; + CacheManager m_cache_manager; + public: + CompressedStorage() = default; + virtual ~CompressedStorage() { this->Finalize(); } + + Result Initialize(MemoryResource *bktr_allocator, fs::IBufferManager *cache_allocator, fs::SubStorage data_storage, fs::SubStorage node_storage, fs::SubStorage entry_storage, s32 bktr_entry_count, size_t block_size_max, size_t continuous_reading_size_max, GetDecompressorFunction get_decompressor, size_t cache_size_0, size_t cache_size_1, s32 max_cache_entries) { + /* Initialize our core. */ + R_TRY(m_core.Initialize(bktr_allocator, data_storage, node_storage, entry_storage, bktr_entry_count, block_size_max, continuous_reading_size_max, get_decompressor)); + + /* Get our core size. */ + s64 core_size = 0; + R_TRY(m_core.GetSize(std::addressof(core_size))); + + /* Initialize our cache manager. */ + R_TRY(m_cache_manager.Initialize(cache_allocator, core_size, cache_size_0, cache_size_1, max_cache_entries)); + + R_SUCCEED(); + } + + void Finalize() { + m_cache_manager.Finalize(); + m_core.Finalize(); + } + + fs::IStorage *GetDataStorage() { + return m_core.GetDataStorage(); + } + + Result GetDataStorageSize(s64 *out) { + R_RETURN(m_core.GetDataStorageSize(out)); + } + + Result GetEntryList(Entry *out_entries, s32 *out_read_count, s32 max_entry_count, s64 offset, s64 size) { + R_RETURN(m_core.GetEntryList(out_entries, out_read_count, max_entry_count, offset, size)); + } + + fssystem::BucketTree &GetEntryTable() { + return m_core.GetEntryTable(); + } + public: + virtual Result QueryAppropriateOffset(s64 *out, s64 offset, s64 access_size, s64 alignment_size) override { + R_RETURN(m_core.QueryAppropriateOffsetForAsynchronousAccess(out, offset, access_size, alignment_size)); + } + public: + virtual Result Read(s64 offset, void *buffer, size_t size) override { + R_RETURN(m_cache_manager.Read(m_core, offset, buffer, size)); + } + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + AMS_UNUSED(src, src_size); + + /* Check pre-conditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(size >= 0); + + /* Perform the operation. */ + switch (op_id) { + case fs::OperationId::Invalidate: + m_cache_manager.Invalidate(); + R_TRY(m_core.Invalidate()); + break; + case fs::OperationId::QueryRange: + R_TRY(m_core.QueryRange(dst, dst_size, offset, size)); + break; + default: + R_THROW(fs::ResultUnsupportedOperateRangeForCompressedStorage()); + } + + R_SUCCEED(); + } + + virtual Result GetSize(s64 *out) override { + R_RETURN(m_core.GetSize(out)); + } + + virtual Result Flush() override { + R_SUCCEED(); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + AMS_UNUSED(offset, buffer, size); + R_THROW(fs::ResultUnsupportedWriteForCompressedStorage()); + } + + virtual Result SetSize(s64 size) override { + AMS_UNUSED(size); + /* NOTE: Is Nintendo returning the wrong result here? */ + R_THROW(fs::ResultUnsupportedSetSizeForIndirectStorage()); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_compression_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_compression_common.hpp new file mode 100644 index 00000000..14e84b91 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_compression_common.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + enum CompressionType : u8 { + CompressionType_None = 0, + CompressionType_Zeros = 1, + CompressionType_2 = 2, + CompressionType_Lz4 = 3, + CompressionType_Unknown = 4, + }; + + using DecompressorFunction = Result (*)(void *, size_t, const void *, size_t); + using GetDecompressorFunction = DecompressorFunction (*)(CompressionType); + + constexpr s64 CompressionBlockAlignment = 0x10; + + namespace CompressionTypeUtility { + + constexpr bool IsBlockAlignmentRequired(CompressionType type) { + return type != CompressionType_None && type != CompressionType_Zeros; + } + + constexpr bool IsDataStorageAccessRequired(CompressionType type) { + return type != CompressionType_Zeros; + } + + constexpr bool IsRandomAccessible(CompressionType type) { + return type == CompressionType_None; + } + + constexpr bool IsUnknownType(CompressionType type) { + return type >= CompressionType_Unknown; + } + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_compression_configuration.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_compression_configuration.hpp new file mode 100644 index 00000000..ff90c47d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_compression_configuration.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fssystem/fssystem_nca_file_system_driver.hpp> + +namespace ams::fssystem { + + const ::ams::fssystem::NcaCompressionConfiguration *GetNcaCompressionConfiguration(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_crypto_configuration.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_crypto_configuration.hpp new file mode 100644 index 00000000..17dfb656 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_crypto_configuration.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fssystem/fssystem_nca_file_system_driver.hpp> +#include <stratosphere/ncm/ncm_content_meta_platform.hpp> + +namespace ams::fssystem { + + const ::ams::fssystem::NcaCryptoConfiguration *GetNcaCryptoConfiguration(bool prod); + + void SetUpKekAccessKeys(bool prod); + + void InvalidateHardwareAesKey(); + + bool IsValidSignatureKeyGeneration(ncm::ContentMetaPlatform platform, size_t key_generation); + + const u8 *GetAcidSignatureKeyModulus(ncm::ContentMetaPlatform platform, bool prod, size_t key_generation, bool unk_unused); + size_t GetAcidSignatureKeyModulusSize(ncm::ContentMetaPlatform platform, bool unk_unused); + + const u8 *GetAcidSignatureKeyPublicExponent(); + + constexpr inline size_t AcidSignatureKeyPublicExponentSize = NcaCryptoConfiguration::Rsa2048KeyPublicExponentSize; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_directory_redirection_filesystem.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_directory_redirection_filesystem.hpp new file mode 100644 index 00000000..24c48d0c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_directory_redirection_filesystem.hpp @@ -0,0 +1,179 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fsa/fs_ifile.hpp> +#include <stratosphere/fs/fsa/fs_idirectory.hpp> +#include <stratosphere/fs/fsa/fs_ifilesystem.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> + +namespace ams::fssystem { + + class DirectoryRedirectionFileSystem : public fs::fsa::IFileSystem, public fs::impl::Newable { + NON_COPYABLE(DirectoryRedirectionFileSystem); + private: + std::unique_ptr<fs::fsa::IFileSystem> m_base_fs; + fs::Path m_before_dir; + fs::Path m_after_dir; + public: + DirectoryRedirectionFileSystem(std::unique_ptr<fs::fsa::IFileSystem> fs) : m_base_fs(std::move(fs)), m_before_dir(), m_after_dir() { + /* ... */ + } + + Result InitializeWithFixedPath(const char *before, const char *after) { + R_TRY(fs::SetUpFixedPath(std::addressof(m_before_dir), before)); + R_TRY(fs::SetUpFixedPath(std::addressof(m_after_dir), after)); + R_SUCCEED(); + } + private: + Result ResolveFullPath(fs::Path *out, const fs::Path &path) { + if (path.IsMatchHead(m_before_dir.GetString(), m_before_dir.GetLength())) { + R_TRY(out->Initialize(m_after_dir)); + R_TRY(out->AppendChild(path.GetString() + m_before_dir.GetLength())); + } else { + R_TRY(out->Initialize(path)); + } + R_SUCCEED(); + } + public: + virtual Result DoCreateFile(const fs::Path &path, s64 size, int option) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->CreateFile(full_path, size, option)); + } + + virtual Result DoDeleteFile(const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->DeleteFile(full_path)); + } + + virtual Result DoCreateDirectory(const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->CreateDirectory(full_path)); + } + + virtual Result DoDeleteDirectory(const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->DeleteDirectory(full_path)); + } + + virtual Result DoDeleteDirectoryRecursively(const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->DeleteDirectoryRecursively(full_path)); + } + + virtual Result DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) override { + fs::Path old_full_path; + fs::Path new_full_path; + R_TRY(this->ResolveFullPath(std::addressof(old_full_path), old_path)); + R_TRY(this->ResolveFullPath(std::addressof(new_full_path), new_path)); + + R_RETURN(m_base_fs->RenameFile(old_full_path, new_full_path)); + } + + virtual Result DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) override { + fs::Path old_full_path; + fs::Path new_full_path; + R_TRY(this->ResolveFullPath(std::addressof(old_full_path), old_path)); + R_TRY(this->ResolveFullPath(std::addressof(new_full_path), new_path)); + + R_RETURN(m_base_fs->RenameDirectory(old_full_path, new_full_path)); + } + + virtual Result DoGetEntryType(fs::DirectoryEntryType *out, const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->GetEntryType(out, full_path)); + } + + virtual Result DoOpenFile(std::unique_ptr<fs::fsa::IFile> *out_file, const fs::Path &path, fs::OpenMode mode) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->OpenFile(out_file, full_path, mode)); + } + + virtual Result DoOpenDirectory(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const fs::Path &path, fs::OpenDirectoryMode mode) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->OpenDirectory(out_dir, full_path, mode)); + } + + virtual Result DoCommit() override { + R_RETURN(m_base_fs->Commit()); + } + + virtual Result DoGetFreeSpaceSize(s64 *out, const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->GetFreeSpaceSize(out, full_path)); + } + + virtual Result DoGetTotalSpaceSize(s64 *out, const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->GetTotalSpaceSize(out, full_path)); + } + + virtual Result DoCleanDirectoryRecursively(const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->CleanDirectoryRecursively(full_path)); + } + + virtual Result DoGetFileTimeStampRaw(fs::FileTimeStampRaw *out, const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->GetFileTimeStampRaw(out, full_path)); + } + + virtual Result DoQueryEntry(char *dst, size_t dst_size, const char *src, size_t src_size, fs::fsa::QueryId query, const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->QueryEntry(dst, dst_size, src, src_size, query, full_path)); + } + + /* These aren't accessible as commands. */ + virtual Result DoCommitProvisionally(s64 counter) override { + R_RETURN(m_base_fs->CommitProvisionally(counter)); + } + + virtual Result DoRollback() override { + R_RETURN(m_base_fs->Rollback()); + } + + virtual Result DoFlush() override { + R_RETURN(m_base_fs->Flush()); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_directory_savedata_filesystem.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_directory_savedata_filesystem.hpp new file mode 100644 index 00000000..531c2b12 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_directory_savedata_filesystem.hpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fsa/fs_ifile.hpp> +#include <stratosphere/fs/fsa/fs_idirectory.hpp> +#include <stratosphere/fs/fsa/fs_ifilesystem.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class DirectorySaveDataFileSystem : public fs::fsa::IFileSystem, public fs::impl::Newable { + NON_COPYABLE(DirectorySaveDataFileSystem); + private: + std::unique_ptr<fs::fsa::IFileSystem> m_unique_fs; + fs::fsa::IFileSystem * const m_base_fs; + os::SdkMutex m_accessor_mutex = {}; + s32 m_open_writable_files = 0; + bool m_is_journaling_supported = false; + bool m_is_multi_commit_supported = false; + bool m_is_journaling_enabled = false; + + /* Extension member to ensure proper savedata locking. */ + std::unique_ptr<fs::fsa::IFile> m_lock_file = nullptr; + public: + DirectorySaveDataFileSystem(std::unique_ptr<fs::fsa::IFileSystem> fs) : m_unique_fs(std::move(fs)), m_base_fs(m_unique_fs.get()) { /* ... */ } + DirectorySaveDataFileSystem(fs::fsa::IFileSystem *fs) : m_unique_fs(), m_base_fs(fs) { /* ... */ } + Result Initialize(bool journaling_supported, bool multi_commit_enabled, bool journaling_enabled); + private: + Result SynchronizeDirectory(const fs::Path &dst, const fs::Path &src); + Result ResolvePath(fs::Path *out, const fs::Path &path); + Result AcquireLockFile(); + public: + void DecrementWriteOpenFileCount(); + public: + virtual Result DoCreateFile(const fs::Path &path, s64 size, int option) override; + virtual Result DoDeleteFile(const fs::Path &path) override; + virtual Result DoCreateDirectory(const fs::Path &path) override; + virtual Result DoDeleteDirectory(const fs::Path &path) override; + virtual Result DoDeleteDirectoryRecursively(const fs::Path &path) override; + virtual Result DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) override; + virtual Result DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) override; + virtual Result DoGetEntryType(fs::DirectoryEntryType *out, const fs::Path &path) override; + virtual Result DoOpenFile(std::unique_ptr<fs::fsa::IFile> *out_file, const fs::Path &path, fs::OpenMode mode) override; + virtual Result DoOpenDirectory(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const fs::Path &path, fs::OpenDirectoryMode mode) override; + virtual Result DoCommit() override; + virtual Result DoGetFreeSpaceSize(s64 *out, const fs::Path &path) override; + virtual Result DoGetTotalSpaceSize(s64 *out, const fs::Path &path) override; + virtual Result DoCleanDirectoryRecursively(const fs::Path &path) override; + + /* These aren't accessible as commands. */ + virtual Result DoCommitProvisionally(s64 counter) override; + virtual Result DoRollback() override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_external_code.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_external_code.hpp new file mode 100644 index 00000000..fe6313e4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_external_code.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> +#include <stratosphere/fs/fsa/fs_ifilesystem.hpp> + +namespace ams::fssystem { + + fs::fsa::IFileSystem *GetExternalCodeFileSystem(ncm::ProgramId program_id); + + Result CreateExternalCode(os::NativeHandle *out, ncm::ProgramId program_id); + void DestroyExternalCode(ncm::ProgramId program_id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_file_system_proxy_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_file_system_proxy_api.hpp new file mode 100644 index 00000000..63e30645 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_file_system_proxy_api.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fssrv::fscreator { + + struct FileSystemCreatorInterfaces; + +} + +namespace ams::fssystem { + + /* TODO: This is kind of really a fs process function/tied into fs main. */ + /* This should be re-examined when FS is reimplemented. */ + void InitializeForFileSystemProxy(); + + void InitializeForAtmosphereMitm(); + + const ::ams::fssrv::fscreator::FileSystemCreatorInterfaces *GetFileSystemCreatorInterfaces(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_forwarding_file_system.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_forwarding_file_system.hpp new file mode 100644 index 00000000..8aa33f61 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_forwarding_file_system.hpp @@ -0,0 +1,178 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> +#include <stratosphere/fs/fsa/fs_ifile.hpp> +#include <stratosphere/fs/fsa/fs_idirectory.hpp> +#include <stratosphere/fs/fsa/fs_ifilesystem.hpp> +#include <stratosphere/fs/fs_query_range.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class ForwardingFile final : public ::ams::fs::fsa::IFile, public ::ams::fs::impl::Newable { + NON_COPYABLE(ForwardingFile); + NON_MOVEABLE(ForwardingFile); + private: + std::unique_ptr<fs::fsa::IFile> m_base_file; + public: + ForwardingFile(std::unique_ptr<fs::fsa::IFile> f) : m_base_file(std::move(f)) { /* ... */ } + + virtual ~ForwardingFile() { /* ... */ } + public: + virtual Result DoRead(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override final { + R_RETURN(m_base_file->Read(out, offset, buffer, size, option)); + } + + virtual Result DoGetSize(s64 *out) override final { + R_RETURN(m_base_file->GetSize(out)); + } + + virtual Result DoFlush() override final { + R_RETURN(m_base_file->Flush()); + } + + virtual Result DoWrite(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override final { + R_RETURN(m_base_file->Write(offset, buffer, size, option)); + } + + virtual Result DoSetSize(s64 size) override final { + R_RETURN(m_base_file->SetSize(size)); + } + + virtual Result DoOperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override final { + R_RETURN(m_base_file->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override final { + return m_base_file->GetDomainObjectId(); + } + }; + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class ForwardingDirectory final : public ::ams::fs::fsa::IDirectory, public ::ams::fs::impl::Newable { + NON_COPYABLE(ForwardingDirectory); + NON_MOVEABLE(ForwardingDirectory); + private: + std::unique_ptr<fs::fsa::IDirectory> m_base_dir; + public: + ForwardingDirectory(std::unique_ptr<fs::fsa::IDirectory> d) : m_base_dir(std::move(d)) { /* ... */ } + + virtual ~ForwardingDirectory() { /* ... */ } + public: + virtual Result DoRead(s64 *out_count, fs::DirectoryEntry *out_entries, s64 max_entries) override final { + R_RETURN(m_base_dir->Read(out_count, out_entries, max_entries)); + } + + virtual Result DoGetEntryCount(s64 *out) override final { + R_RETURN(m_base_dir->GetEntryCount(out)); + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override final { + return m_base_dir->GetDomainObjectId(); + } + }; + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class ForwardingFileSystem final : public ::ams::fs::fsa::IFileSystem, public ::ams::fs::impl::Newable { + NON_COPYABLE(ForwardingFileSystem); + NON_MOVEABLE(ForwardingFileSystem); + private: + std::shared_ptr<fs::fsa::IFileSystem> m_base_fs; + public: + ForwardingFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs) : m_base_fs(std::move(fs)) { /* ... */ } + + virtual ~ForwardingFileSystem() { /* ... */ } + public: + virtual Result DoCreateFile(const fs::Path &path, s64 size, int flags) override final { + R_RETURN(m_base_fs->CreateFile(path, size, flags)); + } + + virtual Result DoDeleteFile(const fs::Path &path) override final { + R_RETURN(m_base_fs->DeleteFile(path)); + } + + virtual Result DoCreateDirectory(const fs::Path &path) override final { + R_RETURN(m_base_fs->CreateDirectory(path)); + } + + virtual Result DoDeleteDirectory(const fs::Path &path) override final { + R_RETURN(m_base_fs->DeleteDirectory(path)); + } + + virtual Result DoDeleteDirectoryRecursively(const fs::Path &path) override final { + R_RETURN(m_base_fs->DeleteDirectoryRecursively(path)); + } + + virtual Result DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) override final { + R_RETURN(m_base_fs->RenameFile(old_path, new_path)); + } + + virtual Result DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) override final { + R_RETURN(m_base_fs->RenameDirectory(old_path, new_path)); + } + + virtual Result DoGetEntryType(fs::DirectoryEntryType *out, const fs::Path &path) override final { + R_RETURN(m_base_fs->GetEntryType(out, path)); + } + + virtual Result DoOpenFile(std::unique_ptr<fs::fsa::IFile> *out_file, const fs::Path &path, fs::OpenMode mode) override final { + R_RETURN(m_base_fs->OpenFile(out_file, path, mode)); + } + + virtual Result DoOpenDirectory(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const fs::Path &path, fs::OpenDirectoryMode mode) override final { + R_RETURN(m_base_fs->OpenDirectory(out_dir, path, mode)); + } + + virtual Result DoCommit() override final { + R_RETURN(m_base_fs->Commit()); + } + + virtual Result DoGetFreeSpaceSize(s64 *out, const fs::Path &path) override final { + R_RETURN(m_base_fs->GetFreeSpaceSize(out, path)); + } + + virtual Result DoGetTotalSpaceSize(s64 *out, const fs::Path &path) override final { + R_RETURN(m_base_fs->GetTotalSpaceSize(out, path)); + } + + virtual Result DoCleanDirectoryRecursively(const fs::Path &path) override final { + R_RETURN(m_base_fs->CleanDirectoryRecursively(path)); + } + + virtual Result DoGetFileTimeStampRaw(fs::FileTimeStampRaw *out, const fs::Path &path) override final { + R_RETURN(m_base_fs->GetFileTimeStampRaw(out, path)); + } + + virtual Result DoQueryEntry(char *dst, size_t dst_size, const char *src, size_t src_size, fs::fsa::QueryId query, const fs::Path &path) override final { + R_RETURN(m_base_fs->QueryEntry(dst, dst_size, src, src_size, query, path)); + } + + virtual Result DoCommitProvisionally(s64 counter) override final { + R_RETURN(m_base_fs->CommitProvisionally(counter)); + } + + virtual Result DoRollback() override final { + R_RETURN(m_base_fs->Rollback()); + } + + virtual Result DoFlush() override final { + R_RETURN(m_base_fs->Flush()); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_hierarchical_integrity_verification_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_hierarchical_integrity_verification_storage.hpp new file mode 100644 index 00000000..b867b8a0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_hierarchical_integrity_verification_storage.hpp @@ -0,0 +1,211 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/fs/fs_istorage.hpp> +#include <stratosphere/fs/fs_substorage.hpp> +#include <stratosphere/fs/fs_storage_type.hpp> +#include <stratosphere/fssystem/fssystem_integrity_verification_storage.hpp> +#include <stratosphere/fssystem/fssystem_block_cache_buffered_storage.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 14.3.0.0 */ + + struct HierarchicalIntegrityVerificationLevelInformation { + fs::Int64 offset; + fs::Int64 size; + s32 block_order; + u8 reserved[4]; + }; + static_assert(util::is_pod<HierarchicalIntegrityVerificationLevelInformation>::value); + static_assert(sizeof(HierarchicalIntegrityVerificationLevelInformation) == 0x18); + static_assert(alignof(HierarchicalIntegrityVerificationLevelInformation) == 0x4); + + struct HierarchicalIntegrityVerificationInformation { + u32 max_layers; + HierarchicalIntegrityVerificationLevelInformation info[IntegrityMaxLayerCount - 1]; + fs::HashSalt seed; + + s64 GetLayeredHashSize() const { + return this->info[this->max_layers - 2].offset; + } + + s64 GetDataOffset() const { + return this->info[this->max_layers - 2].offset; + } + + s64 GetDataSize() const { + return this->info[this->max_layers - 2].size; + } + }; + static_assert(util::is_pod<HierarchicalIntegrityVerificationInformation>::value); + + struct HierarchicalIntegrityVerificationMetaInformation { + u32 magic; + u32 version; + u32 master_hash_size; + HierarchicalIntegrityVerificationInformation level_hash_info; + + /* TODO: Format */ + }; + static_assert(util::is_pod<HierarchicalIntegrityVerificationMetaInformation>::value); + + struct HierarchicalIntegrityVerificationSizeSet { + s64 control_size; + s64 master_hash_size; + s64 layered_hash_sizes[IntegrityMaxLayerCount - 2]; + }; + static_assert(util::is_pod<HierarchicalIntegrityVerificationSizeSet>::value); + + class HierarchicalIntegrityVerificationStorageControlArea { + NON_COPYABLE(HierarchicalIntegrityVerificationStorageControlArea); + NON_MOVEABLE(HierarchicalIntegrityVerificationStorageControlArea); + public: + static constexpr size_t HashSize = crypto::Sha256Generator::HashSize; + + struct InputParam { + size_t level_block_size[IntegrityMaxLayerCount - 1]; + }; + static_assert(util::is_pod<InputParam>::value); + private: + fs::SubStorage m_storage; + HierarchicalIntegrityVerificationMetaInformation m_meta; + public: + static Result QuerySize(HierarchicalIntegrityVerificationSizeSet *out, const InputParam &input_param, s32 layer_count, s64 data_size); + /* TODO Format */ + static Result Expand(fs::SubStorage meta_storage, const HierarchicalIntegrityVerificationMetaInformation &meta); + public: + HierarchicalIntegrityVerificationStorageControlArea() { /* ... */ } + + Result Initialize(fs::SubStorage meta_storage); + void Finalize(); + + u32 GetMasterHashSize() const { return m_meta.master_hash_size; } + void GetLevelHashInfo(HierarchicalIntegrityVerificationInformation *out) { + AMS_ASSERT(out != nullptr); + *out = m_meta.level_hash_info; + } + }; + + class HierarchicalIntegrityVerificationStorage : public ::ams::fs::IStorage { + NON_COPYABLE(HierarchicalIntegrityVerificationStorage); + NON_MOVEABLE(HierarchicalIntegrityVerificationStorage); + private: + friend struct HierarchicalIntegrityVerificationMetaInformation; + protected: + static constexpr s64 HashSize = crypto::Sha256Generator::HashSize; + static constexpr size_t MaxLayers = IntegrityMaxLayerCount; + public: + using GenerateRandomFunction = void (*)(void *dst, size_t size); + + class HierarchicalStorageInformation { + public: + enum { + MasterStorage = 0, + Layer1Storage = 1, + Layer2Storage = 2, + Layer3Storage = 3, + Layer4Storage = 4, + Layer5Storage = 5, + DataStorage = 6, + }; + private: + fs::SubStorage m_storages[DataStorage + 1]; + public: + void SetMasterHashStorage(fs::SubStorage s) { m_storages[MasterStorage] = s; } + void SetLayer1HashStorage(fs::SubStorage s) { m_storages[Layer1Storage] = s; } + void SetLayer2HashStorage(fs::SubStorage s) { m_storages[Layer2Storage] = s; } + void SetLayer3HashStorage(fs::SubStorage s) { m_storages[Layer3Storage] = s; } + void SetLayer4HashStorage(fs::SubStorage s) { m_storages[Layer4Storage] = s; } + void SetLayer5HashStorage(fs::SubStorage s) { m_storages[Layer5Storage] = s; } + void SetDataStorage(fs::SubStorage s) { m_storages[DataStorage] = s; } + + fs::SubStorage &operator[](s32 index) { + AMS_ASSERT(MasterStorage <= index && index <= DataStorage); + return m_storages[index]; + } + }; + private: + static GenerateRandomFunction s_generate_random; + + static void SetGenerateRandomFunction(GenerateRandomFunction func) { + s_generate_random = func; + } + private: + FileSystemBufferManagerSet *m_buffers; + os::SdkRecursiveMutex *m_mutex; + IntegrityVerificationStorage m_verify_storages[MaxLayers - 1]; + BlockCacheBufferedStorage m_buffer_storages[MaxLayers - 1]; + os::Semaphore *m_read_semaphore; + os::Semaphore *m_write_semaphore; + s64 m_data_size; + s32 m_max_layers; + public: + HierarchicalIntegrityVerificationStorage() : m_buffers(nullptr), m_mutex(nullptr), m_data_size(-1) { /* ... */ } + virtual ~HierarchicalIntegrityVerificationStorage() override { this->Finalize(); } + + Result Initialize(const HierarchicalIntegrityVerificationInformation &info, HierarchicalStorageInformation storage, FileSystemBufferManagerSet *bufs, IHash256GeneratorFactory *hgf, bool hash_salt_enabled, os::SdkRecursiveMutex *mtx, os::Semaphore *read_sema, os::Semaphore *write_sema, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, bool is_writable, bool allow_cleared_blocks); + Result Initialize(const HierarchicalIntegrityVerificationInformation &info, HierarchicalStorageInformation storage, FileSystemBufferManagerSet *bufs, IHash256GeneratorFactory *hgf, bool hash_salt_enabled, os::SdkRecursiveMutex *mtx, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, bool is_writable, bool allow_cleared_blocks) { + R_RETURN(this->Initialize(info, storage, bufs, hgf, hash_salt_enabled, mtx, nullptr, nullptr, max_data_cache_entries, max_hash_cache_entries, buffer_level, is_writable, allow_cleared_blocks)); + } + + void Finalize(); + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + + virtual Result SetSize(s64 size) override { AMS_UNUSED(size); R_THROW(fs::ResultUnsupportedSetSizeForHierarchicalIntegrityVerificationStorage()); } + virtual Result GetSize(s64 *out) override; + + virtual Result Flush() override; + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + using IStorage::OperateRange; + + Result Commit(); + Result OnRollback(); + + bool IsInitialized() const { + return m_data_size >= 0; + } + + FileSystemBufferManagerSet *GetBuffers() { + return m_buffers; + } + + void GetParameters(HierarchicalIntegrityVerificationStorageControlArea::InputParam *out) const { + AMS_ASSERT(out != nullptr); + for (auto level = 0; level <= m_max_layers - 2; ++level) { + out->level_block_size[level] = static_cast<size_t>(m_verify_storages[level].GetBlockSize()); + } + } + + s64 GetL1HashVerificationBlockSize() const { + return m_verify_storages[m_max_layers - 2].GetBlockSize(); + } + + fs::SubStorage GetL1HashStorage() { + return fs::SubStorage(std::addressof(m_buffer_storages[m_max_layers - 3]), 0, util::DivideUp(m_data_size, this->GetL1HashVerificationBlockSize())); + } + public: + static constexpr s8 GetDefaultDataCacheBufferLevel(u32 max_layers) { + return 16 + max_layers - 2; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_i_hash_256_generator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_i_hash_256_generator.hpp new file mode 100644 index 00000000..46c0bd8c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_i_hash_256_generator.hpp @@ -0,0 +1,91 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fssystem { + + enum HashAlgorithmType : u8 { + HashAlgorithmType_Sha2 = 0, + HashAlgorithmType_Sha3 = 1, + }; + + /* ACCURATE_TO_VERSION: 14.3.0.0 */ + class IHash256Generator { + public: + static constexpr size_t HashSize = 256 / BITSIZEOF(u8); + public: + constexpr IHash256Generator() = default; + virtual constexpr ~IHash256Generator() { /* ... */ } + public: + void Initialize() { + return this->DoInitialize(); + } + + void Update(const void *data, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(data != nullptr); + + return this->DoUpdate(data, size); + } + + void GetHash(void *dst, size_t dst_size) { + /* Check pre-conditions. */ + AMS_ASSERT(dst_size == HashSize); + + return this->DoGetHash(dst, dst_size); + } + protected: + virtual void DoInitialize() = 0; + virtual void DoUpdate(const void *data, size_t size) = 0; + virtual void DoGetHash(void *dst, size_t dst_size) = 0; + }; + + /* ACCURATE_TO_VERSION: 14.3.0.0 */ + class IHash256GeneratorFactory { + public: + constexpr IHash256GeneratorFactory() = default; + virtual constexpr ~IHash256GeneratorFactory() { /* ... */ } + + Result Create(std::unique_ptr<IHash256Generator> *out) { + return this->DoCreate(out); + } + + void GenerateHash(void *dst, size_t dst_size, const void *src, size_t src_size) { + /* Check pre-conditions. */ + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(src != nullptr); + AMS_ASSERT(dst_size == IHash256Generator::HashSize); + + return this->DoGenerateHash(dst, dst_size, src, src_size); + } + protected: + virtual Result DoCreate(std::unique_ptr<IHash256Generator> *out) = 0; + virtual void DoGenerateHash(void *dst, size_t dst_size, const void *src, size_t src_size) = 0; + }; + + /* ACCURATE_TO_VERSION: 14.3.0.0 */ + class IHash256GeneratorFactorySelector { + public: + constexpr IHash256GeneratorFactorySelector() = default; + virtual constexpr ~IHash256GeneratorFactorySelector() { /* ... */ } + + IHash256GeneratorFactory *GetFactory(HashAlgorithmType alg) { return this->DoGetFactory(alg); } + protected: + virtual IHash256GeneratorFactory *DoGetFactory(HashAlgorithmType alg) = 0; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_indirect_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_indirect_storage.hpp new file mode 100644 index 00000000..3c232293 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_indirect_storage.hpp @@ -0,0 +1,170 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fssystem/fssystem_bucket_tree.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: Unknown */ + class IndirectStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(IndirectStorage); + NON_MOVEABLE(IndirectStorage); + public: + static constexpr s32 StorageCount = 2; + static constexpr size_t NodeSize = 16_KB; + + using IAllocator = MemoryResource; + + struct Entry { + u8 virt_offset[sizeof(s64)]; + u8 phys_offset[sizeof(s64)]; + s32 storage_index; + + void SetVirtualOffset(const s64 &ofs) { + std::memcpy(this->virt_offset, std::addressof(ofs), sizeof(s64)); + } + + s64 GetVirtualOffset() const { + s64 offset; + std::memcpy(std::addressof(offset), this->virt_offset, sizeof(s64)); + return offset; + } + + void SetPhysicalOffset(const s64 &ofs) { + std::memcpy(this->phys_offset, std::addressof(ofs), sizeof(s64)); + } + + s64 GetPhysicalOffset() const { + s64 offset; + std::memcpy(std::addressof(offset), this->phys_offset, sizeof(s64)); + return offset; + } + }; + static_assert(util::is_pod<Entry>::value); + static_assert(sizeof(Entry) == 0x14); + + struct EntryData { + s64 virt_offset; + s64 phys_offset; + s32 storage_index; + + void Set(const Entry &entry) { + this->virt_offset = entry.GetVirtualOffset(); + this->phys_offset = entry.GetPhysicalOffset(); + this->storage_index = entry.storage_index; + } + }; + static_assert(util::is_pod<EntryData>::value); + private: + struct ContinuousReadingEntry { + static constexpr size_t FragmentSizeMax = 4_KB; + + IndirectStorage::Entry entry; + + s64 GetVirtualOffset() const { + return this->entry.GetVirtualOffset(); + } + + s64 GetPhysicalOffset() const { + return this->entry.GetPhysicalOffset(); + } + + bool IsFragment() const { + return this->entry.storage_index != 0; + } + }; + static_assert(util::is_pod<ContinuousReadingEntry>::value); + public: + static constexpr s64 QueryHeaderStorageSize() { + return BucketTree::QueryHeaderStorageSize(); + } + + static constexpr s64 QueryNodeStorageSize(s32 entry_count) { + return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); + } + + static constexpr s64 QueryEntryStorageSize(s32 entry_count) { + return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); + } + private: + BucketTree m_table; + fs::SubStorage m_data_storage[StorageCount]; + public: + IndirectStorage() : m_table(), m_data_storage() { /* ... */ } + virtual ~IndirectStorage() { this->Finalize(); } + + Result Initialize(IAllocator *allocator, fs::SubStorage table_storage); + void Finalize(); + + bool IsInitialized() const { return m_table.IsInitialized(); } + + Result Initialize(IAllocator *allocator, fs::SubStorage node_storage, fs::SubStorage entry_storage, s32 entry_count) { + R_RETURN(m_table.Initialize(allocator, node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count)); + } + + void SetStorage(s32 idx, fs::SubStorage storage) { + AMS_ASSERT(0 <= idx && idx < StorageCount); + m_data_storage[idx] = storage; + } + + template<typename T> + void SetStorage(s32 idx, T storage, s64 offset, s64 size) { + AMS_ASSERT(0 <= idx && idx < StorageCount); + m_data_storage[idx] = fs::SubStorage(storage, offset, size); + } + + Result GetEntryList(Entry *out_entries, s32 *out_entry_count, s32 entry_count, s64 offset, s64 size); + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + + virtual Result GetSize(s64 *out) override { + AMS_ASSERT(out != nullptr); + + BucketTree::Offsets offsets; + R_TRY(m_table.GetOffsets(std::addressof(offsets))); + + *out = offsets.end_offset; + R_SUCCEED(); + } + + virtual Result Flush() override { + R_SUCCEED(); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + AMS_UNUSED(offset, buffer, size); + R_THROW(fs::ResultUnsupportedWriteForIndirectStorage()); + } + + virtual Result SetSize(s64 size) override { + AMS_UNUSED(size); + R_THROW(fs::ResultUnsupportedSetSizeForIndirectStorage()); + } + protected: + BucketTree &GetEntryTable() { return m_table; } + + fs::SubStorage &GetDataStorage(s32 index) { + AMS_ASSERT(0 <= index && index < StorageCount); + return m_data_storage[index]; + } + + template<bool ContinuousCheck, bool RangeCheck, typename F> + Result OperatePerEntry(s64 offset, s64 size, F func); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_indirect_storage_template_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_indirect_storage_template_impl.hpp new file mode 100644 index 00000000..9eb25d7b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_indirect_storage_template_impl.hpp @@ -0,0 +1,150 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fssystem/fssystem_indirect_storage.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + template<bool ContinuousCheck, bool RangeCheck, typename F> + Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) { + /* Validate preconditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(size >= 0); + AMS_ASSERT(this->IsInitialized()); + + /* Succeed if there's nothing to operate on. */ + R_SUCCEED_IF(size == 0); + + /* Get the table offsets. */ + BucketTree::Offsets table_offsets; + R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); + + /* Validate arguments. */ + R_UNLESS(table_offsets.IsInclude(offset, size), fs::ResultOutOfRange()); + + /* Find the offset in our tree. */ + BucketTree::Visitor visitor; + R_TRY(m_table.Find(std::addressof(visitor), offset)); + { + const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset(); + R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), fs::ResultInvalidIndirectEntryOffset()); + } + + /* Prepare to operate in chunks. */ + auto cur_offset = offset; + const auto end_offset = offset + static_cast<s64>(size); + BucketTree::ContinuousReadingInfo cr_info; + + while (cur_offset < end_offset) { + /* Get the current entry. */ + const auto cur_entry = *visitor.Get<Entry>(); + + /* Get and validate the entry's offset. */ + const auto cur_entry_offset = cur_entry.GetVirtualOffset(); + R_UNLESS(cur_entry_offset <= cur_offset, fs::ResultInvalidIndirectEntryOffset()); + + /* Validate the storage index. */ + R_UNLESS(0 <= cur_entry.storage_index && cur_entry.storage_index < StorageCount, fs::ResultInvalidIndirectEntryStorageIndex()); + + /* If we need to check the continuous info, do so. */ + if constexpr (ContinuousCheck) { + /* Scan, if we need to. */ + if (cr_info.CheckNeedScan()) { + R_TRY(visitor.ScanContinuousReading<ContinuousReadingEntry>(std::addressof(cr_info), cur_offset, static_cast<size_t>(end_offset - cur_offset))); + } + + /* Process a base storage entry. */ + if (cr_info.CanDo()) { + /* Ensure that we can process. */ + R_UNLESS(cur_entry.storage_index == 0, fs::ResultInvalidIndirectEntryStorageIndex()); + + + /* Ensure that we remain within range. */ + const auto data_offset = cur_offset - cur_entry_offset; + const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset(); + const auto cur_size = static_cast<s64>(cr_info.GetReadSize()); + + /* If we should, verify the range. */ + if constexpr (RangeCheck) { + /* Get the current data storage's size. */ + s64 cur_data_storage_size; + R_TRY(m_data_storage[0].GetSize(std::addressof(cur_data_storage_size))); + + R_UNLESS(0 <= cur_entry_phys_offset && cur_entry_phys_offset <= cur_data_storage_size, fs::ResultInvalidIndirectEntryOffset()); + R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <= cur_data_storage_size, fs::ResultInvalidIndirectStorageSize()); + } + + /* Operate. */ + R_TRY(func(std::addressof(m_data_storage[0]), cur_entry_phys_offset + data_offset, cur_offset, cur_size)); + + /* Mark as done. */ + cr_info.Done(); + } + } + + /* Get and validate the next entry offset. */ + s64 next_entry_offset; + if (visitor.CanMoveNext()) { + R_TRY(visitor.MoveNext()); + next_entry_offset = visitor.Get<Entry>()->GetVirtualOffset(); + R_UNLESS(table_offsets.IsInclude(next_entry_offset), fs::ResultInvalidIndirectEntryOffset()); + } else { + next_entry_offset = table_offsets.end_offset; + } + R_UNLESS(cur_offset < next_entry_offset, fs::ResultInvalidIndirectEntryOffset()); + + /* Get the offset of the entry in the data we read. */ + const auto data_offset = cur_offset - cur_entry_offset; + const auto data_size = (next_entry_offset - cur_entry_offset); + AMS_ASSERT(data_size > 0); + + /* Determine how much is left. */ + const auto remaining_size = end_offset - cur_offset; + const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset); + AMS_ASSERT(cur_size <= size); + + /* Operate, if we need to. */ + bool needs_operate; + if constexpr (!ContinuousCheck) { + needs_operate = true; + } else { + needs_operate = !cr_info.IsDone() || cur_entry.storage_index != 0; + } + + if (needs_operate) { + const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset(); + + if constexpr (RangeCheck) { + /* Get the current data storage's size. */ + s64 cur_data_storage_size; + R_TRY(m_data_storage[cur_entry.storage_index].GetSize(std::addressof(cur_data_storage_size))); + + /* Ensure that we remain within range. */ + R_UNLESS(0 <= cur_entry_phys_offset && cur_entry_phys_offset <= cur_data_storage_size, fs::ResultIndirectStorageCorrupted()); + R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <= cur_data_storage_size, fs::ResultIndirectStorageCorrupted()); + } + + R_TRY(func(std::addressof(m_data_storage[cur_entry.storage_index]), cur_entry_phys_offset + data_offset, cur_offset, cur_size)); + } + + cur_offset += cur_size; + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_integrity_romfs_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_integrity_romfs_storage.hpp new file mode 100644 index 00000000..3bc66310 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_integrity_romfs_storage.hpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fs_istorage.hpp> +#include <stratosphere/fs/fs_memory_storage.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> +#include <stratosphere/fssystem/fssystem_nca_header.hpp> +#include <stratosphere/fssystem/fssystem_hierarchical_integrity_verification_storage.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: Unknown */ + + constexpr inline size_t IntegrityLayerCountRomFs = 7; + constexpr inline size_t IntegrityHashLayerBlockSize = 16_KB; + + class IntegrityRomFsStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + private: + HierarchicalIntegrityVerificationStorage m_integrity_storage; + FileSystemBufferManagerSet m_buffers; + os::SdkRecursiveMutex m_mutex; + Hash m_master_hash; + std::unique_ptr<fs::MemoryStorage> m_master_hash_storage; + public: + IntegrityRomFsStorage() : m_mutex() { /* ... */ } + virtual ~IntegrityRomFsStorage() override { this->Finalize(); } + + Result Initialize(HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, fs::IBufferManager *bm, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, IHash256GeneratorFactory *hgf); + void Finalize(); + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + R_RETURN(m_integrity_storage.Read(offset, buffer, size)); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + R_RETURN(m_integrity_storage.Write(offset, buffer, size)); + } + + virtual Result SetSize(s64 size) override { AMS_UNUSED(size); R_THROW(fs::ResultUnsupportedSetSizeForIntegrityRomFsStorage()); } + + virtual Result GetSize(s64 *out) override { + R_RETURN(m_integrity_storage.GetSize(out)); + } + + virtual Result Flush() override { + R_RETURN(m_integrity_storage.Flush()); + } + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + R_RETURN(m_integrity_storage.OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + } + + Result Commit() { + R_RETURN(m_integrity_storage.Commit()); + } + + FileSystemBufferManagerSet *GetBuffers() { + return m_integrity_storage.GetBuffers(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_integrity_verification_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_integrity_verification_storage.hpp new file mode 100644 index 00000000..fdab3bd3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_integrity_verification_storage.hpp @@ -0,0 +1,96 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/fs/fs_istorage.hpp> +#include <stratosphere/fs/fs_substorage.hpp> +#include <stratosphere/fs/fs_storage_type.hpp> +#include <stratosphere/fssystem/fssystem_block_cache_buffered_storage.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 14.3.0.0 */ + + class IntegrityVerificationStorage : public ::ams::fs::IStorage { + NON_COPYABLE(IntegrityVerificationStorage); + NON_MOVEABLE(IntegrityVerificationStorage); + public: + static constexpr s64 HashSize = crypto::Sha256Generator::HashSize; + + struct BlockHash { + u8 hash[HashSize]; + }; + static_assert(util::is_pod<BlockHash>::value); + private: + fs::SubStorage m_hash_storage; + fs::SubStorage m_data_storage; + s64 m_verification_block_size; + s64 m_verification_block_order; + s64 m_upper_layer_verification_block_size; + s64 m_upper_layer_verification_block_order; + fs::IBufferManager *m_buffer_manager; + util::optional<fs::HashSalt> m_salt; + bool m_is_real_data; + fssystem::IHash256GeneratorFactory *m_hash_generator_factory; + bool m_is_writable; + bool m_allow_cleared_blocks; + public: + IntegrityVerificationStorage() : m_verification_block_size(0), m_verification_block_order(0), m_upper_layer_verification_block_size(0), m_upper_layer_verification_block_order(0), m_buffer_manager(nullptr), m_salt(util::nullopt) { /* ... */ } + virtual ~IntegrityVerificationStorage() override { this->Finalize(); } + + void Initialize(fs::SubStorage hs, fs::SubStorage ds, s64 verif_block_size, s64 upper_layer_verif_block_size, fs::IBufferManager *bm, fssystem::IHash256GeneratorFactory *hgf, const util::optional<fs::HashSalt> &salt, bool is_real_data, bool is_writable, bool allow_cleared_blocks); + void Finalize(); + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + + virtual Result SetSize(s64 size) override { AMS_UNUSED(size); R_THROW(fs::ResultUnsupportedSetSizeForIntegrityVerificationStorage()); } + virtual Result GetSize(s64 *out) override; + + virtual Result Flush() override; + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + using IStorage::OperateRange; + + void CalcBlockHash(BlockHash *out, const void *buffer, std::unique_ptr<fssystem::IHash256Generator> &generator) const { + return this->CalcBlockHash(out, buffer, static_cast<size_t>(m_verification_block_size), generator); + } + + s64 GetBlockSize() const { + return m_verification_block_size; + } + private: + Result ReadBlockSignature(void *dst, size_t dst_size, s64 offset, size_t size); + Result WriteBlockSignature(const void *src, size_t src_size, s64 offset, size_t size); + Result VerifyHash(const void *buf, BlockHash *hash, std::unique_ptr<fssystem::IHash256Generator> &generator); + + void CalcBlockHash(BlockHash *out, const void *buffer, size_t block_size, std::unique_ptr<fssystem::IHash256Generator> &generator) const; + + Result IsCleared(bool *is_cleared, const BlockHash &hash); + private: + static void SetValidationBit(BlockHash *hash) { + AMS_ASSERT(hash != nullptr); + hash->hash[HashSize - 1] |= 0x80; + } + + static bool IsValidationBit(const BlockHash *hash) { + AMS_ASSERT(hash != nullptr); + return (hash->hash[HashSize - 1] & 0x80) != 0; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_local_file_system.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_local_file_system.hpp new file mode 100644 index 00000000..b698f4ec --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_local_file_system.hpp @@ -0,0 +1,85 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fsa/fs_ifilesystem.hpp> +#include <stratosphere/fs/fs_memory_management.hpp> +#include <stratosphere/fs/fs_path.hpp> + +namespace ams::fssystem { + + /* TODO: Put this in its own header? */ + enum PathCaseSensitiveMode { + PathCaseSensitiveMode_CaseInsensitive = 0, + PathCaseSensitiveMode_CaseSensitive = 1, + }; + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + class LocalFileSystem : public fs::fsa::IFileSystem, public fs::impl::Newable { + NON_COPYABLE(LocalFileSystem); + NON_MOVEABLE(LocalFileSystem); + private: + #if defined(ATMOSPHERE_OS_WINDOWS) + using NativeCharacterType = wchar_t; + #else + using NativeCharacterType = char; + #endif + + using NativePathBuffer = std::unique_ptr<NativeCharacterType[], ::ams::fs::impl::Deleter>; + private: + fs::Path m_root_path; + fssystem::PathCaseSensitiveMode m_case_sensitive_mode; + NativePathBuffer m_native_path_buffer; + int m_native_path_length; + bool m_use_posix_time; + public: + LocalFileSystem(bool posix_time = true) : m_root_path(), m_native_path_buffer(), m_native_path_length(0), m_use_posix_time(posix_time) { + /* ... */ + } + + Result Initialize(const fs::Path &root_path, fssystem::PathCaseSensitiveMode case_sensitive_mode); + + Result GetCaseSensitivePath(int *out_size, char *dst, size_t dst_size, const char *path, const char *work_path); + private: + Result CheckPathCaseSensitively(const NativeCharacterType *path, const NativeCharacterType *root_path, NativeCharacterType *cs_buf, size_t cs_size, bool check_case_sensitivity); + Result ResolveFullPath(NativePathBuffer *out, const fs::Path &path, int max_len, int min_len, bool check_case_sensitivity); + public: + virtual Result DoCreateFile(const fs::Path &path, s64 size, int flags) override; + virtual Result DoDeleteFile(const fs::Path &path) override; + virtual Result DoCreateDirectory(const fs::Path &path) override; + virtual Result DoDeleteDirectory(const fs::Path &path) override; + virtual Result DoDeleteDirectoryRecursively(const fs::Path &path) override; + virtual Result DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) override; + virtual Result DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) override; + virtual Result DoGetEntryType(fs::DirectoryEntryType *out, const fs::Path &path) override; + virtual Result DoOpenFile(std::unique_ptr<fs::fsa::IFile> *out_file, const fs::Path &path, fs::OpenMode mode) override; + virtual Result DoOpenDirectory(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const fs::Path &path, fs::OpenDirectoryMode mode) override; + virtual Result DoCommit() override; + virtual Result DoGetFreeSpaceSize(s64 *out, const fs::Path &path) override; + virtual Result DoGetTotalSpaceSize(s64 *out, const fs::Path &path) override; + virtual Result DoCleanDirectoryRecursively(const fs::Path &path) override; + virtual Result DoGetFileTimeStampRaw(fs::FileTimeStampRaw *out, const fs::Path &path) override; + virtual Result DoQueryEntry(char *dst, size_t dst_size, const char *src, size_t src_size, fs::fsa::QueryId query, const fs::Path &path) override; + + /* These aren't accessible as commands. */ + virtual Result DoCommitProvisionally(s64 counter) override; + virtual Result DoRollback() override; + public: + Result DoGetDiskFreeSpace(s64 *out_free, s64 *out_total, s64 *out_total_free, const fs::Path &path); + Result DeleteDirectoryRecursivelyInternal(const NativeCharacterType *path, bool delete_top); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_file_system_driver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_file_system_driver.hpp new file mode 100644 index 00000000..f35d3330 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_file_system_driver.hpp @@ -0,0 +1,343 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> +#include <stratosphere/fs/fs_istorage.hpp> +#include <stratosphere/fssystem/fssystem_compression_common.hpp> +#include <stratosphere/fssystem/fssystem_i_hash_256_generator.hpp> +#include <stratosphere/fssystem/fssystem_asynchronous_access.hpp> +#include <stratosphere/fssystem/fssystem_nca_header.hpp> +#include <stratosphere/fs/fs_i_buffer_manager.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 14.3.0.0 */ + + class CompressedStorage; + class AesCtrCounterExtendedStorage; + class IndirectStorage; + class SparseStorage; + + struct NcaCryptoConfiguration; + + using KeyGenerationFunction = void (*)(void *dst_key, size_t dst_key_size, const void *src_key, size_t src_key_size, s32 key_type); + using DecryptAesCtrFunction = void (*)(void *dst, size_t dst_size, u8 key_index, u8 key_generation, const void *src_key, size_t src_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + + using CryptAesXtsFunction = Result (*)(void *dst, size_t dst_size, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + using VerifySign1Function = bool (*)(const void *sig, size_t sig_size, const void *data, size_t data_size, u8 generation); + + struct NcaCryptoConfiguration { + static constexpr size_t Rsa2048KeyModulusSize = crypto::Rsa2048PssSha256Verifier::ModulusSize; + static constexpr size_t Rsa2048KeyPublicExponentSize = crypto::Rsa2048PssSha256Verifier::MaximumExponentSize; + static constexpr size_t Rsa2048KeyPrivateExponentSize = Rsa2048KeyModulusSize; + + static constexpr size_t Aes128KeySize = crypto::AesEncryptor128::KeySize; + + static constexpr size_t Header1SignatureKeyGenerationMax = 1; + + static constexpr s32 KeyAreaEncryptionKeyIndexCount = 3; + static constexpr s32 HeaderEncryptionKeyCount = 2; + + static constexpr u8 KeyAreaEncryptionKeyIndexZeroKey = 0xFF; + + static constexpr size_t KeyGenerationMax = 32; + + const u8 *header_1_sign_key_moduli[Header1SignatureKeyGenerationMax + 1]; + u8 header_1_sign_key_public_exponent[Rsa2048KeyPublicExponentSize]; + u8 key_area_encryption_key_source[KeyAreaEncryptionKeyIndexCount][Aes128KeySize]; + u8 header_encryption_key_source[Aes128KeySize]; + u8 header_encrypted_encryption_keys[HeaderEncryptionKeyCount][Aes128KeySize]; + KeyGenerationFunction generate_key; + CryptAesXtsFunction decrypt_aes_xts_external; + CryptAesXtsFunction encrypt_aes_xts_external; + DecryptAesCtrFunction decrypt_aes_ctr; + DecryptAesCtrFunction decrypt_aes_ctr_external; + VerifySign1Function verify_sign1; + bool is_plaintext_header_available; + bool is_available_sw_key; + + #if !defined(ATMOSPHERE_BOARD_NINTENDO_NX) + bool is_unsigned_header_available_for_host_tool; + #endif + }; + static_assert(util::is_pod<NcaCryptoConfiguration>::value); + + struct NcaCompressionConfiguration { + GetDecompressorFunction get_decompressor; + }; + static_assert(util::is_pod<NcaCompressionConfiguration>::value); + + constexpr inline s32 KeyAreaEncryptionKeyCount = NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * NcaCryptoConfiguration::KeyGenerationMax; + + enum class KeyType : s32 { + ZeroKey = -2, + InvalidKey = -1, + NcaHeaderKey1 = KeyAreaEncryptionKeyCount + 0, + NcaHeaderKey2 = KeyAreaEncryptionKeyCount + 1, + NcaExternalKey = KeyAreaEncryptionKeyCount + 2, + SaveDataDeviceUniqueMac = KeyAreaEncryptionKeyCount + 3, + SaveDataSeedUniqueMac = KeyAreaEncryptionKeyCount + 4, + SaveDataTransferMac = KeyAreaEncryptionKeyCount + 5, + }; + + constexpr inline bool IsInvalidKeyTypeValue(s32 key_type) { + return key_type < 0; + } + + constexpr inline s32 GetKeyTypeValue(u8 key_index, u8 key_generation) { + if (key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey) { + return util::ToUnderlying(KeyType::ZeroKey); + } + + if (key_index >= NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount) { + return util::ToUnderlying(KeyType::InvalidKey); + } + + return NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * key_generation + key_index; + } + + class NcaReader : public ::ams::fs::impl::Newable { + NON_COPYABLE(NcaReader); + NON_MOVEABLE(NcaReader); + private: + NcaHeader m_header; + u8 m_decryption_keys[NcaHeader::DecryptionKey_Count][NcaCryptoConfiguration::Aes128KeySize]; + std::shared_ptr<fs::IStorage> m_body_storage; + std::unique_ptr<fs::IStorage> m_header_storage; + u8 m_external_decryption_key[NcaCryptoConfiguration::Aes128KeySize]; + DecryptAesCtrFunction m_decrypt_aes_ctr; + DecryptAesCtrFunction m_decrypt_aes_ctr_external; + bool m_is_software_aes_prioritized; + bool m_is_available_sw_key; + NcaHeader::EncryptionType m_header_encryption_type; + bool m_is_header_sign1_signature_valid; + GetDecompressorFunction m_get_decompressor; + IHash256GeneratorFactorySelector *m_hash_generator_factory_selector; + public: + NcaReader(); + ~NcaReader(); + + Result Initialize(std::shared_ptr<fs::IStorage> base_storage, const NcaCryptoConfiguration &crypto_cfg, const NcaCompressionConfiguration &compression_cfg, IHash256GeneratorFactorySelector *hgf_selector); + + std::shared_ptr<fs::IStorage> GetSharedBodyStorage(); + u32 GetMagic() const; + NcaHeader::DistributionType GetDistributionType() const; + NcaHeader::ContentType GetContentType() const; + u8 GetHeaderSign1KeyGeneration() const; + u8 GetKeyGeneration() const; + u8 GetKeyIndex() const; + u64 GetContentSize() const; + u64 GetProgramId() const; + u32 GetContentIndex() const; + u32 GetSdkAddonVersion() const; + void GetRightsId(u8 *dst, size_t dst_size) const; + bool HasFsInfo(s32 index) const; + s32 GetFsCount() const; + const Hash &GetFsHeaderHash(s32 index) const; + void GetFsHeaderHash(Hash *dst, s32 index) const; + void GetFsInfo(NcaHeader::FsInfo *dst, s32 index) const; + u64 GetFsOffset(s32 index) const; + u64 GetFsEndOffset(s32 index) const; + u64 GetFsSize(s32 index) const; + void GetEncryptedKey(void *dst, size_t size) const; + const void *GetDecryptionKey(s32 index) const; + bool HasValidInternalKey() const; + bool HasInternalDecryptionKeyForAesHw() const; + bool IsSoftwareAesPrioritized() const; + void PrioritizeSoftwareAes(); + bool IsAvailableSwKey() const; + bool HasExternalDecryptionKey() const; + const void *GetExternalDecryptionKey() const; + void SetExternalDecryptionKey(const void *src, size_t size); + void GetRawData(void *dst, size_t dst_size) const; + DecryptAesCtrFunction GetExternalDecryptAesCtrFunction() const; + DecryptAesCtrFunction GetExternalDecryptAesCtrFunctionForExternalKey() const; + NcaHeader::EncryptionType GetEncryptionType() const; + Result ReadHeader(NcaFsHeader *dst, s32 index) const; + + GetDecompressorFunction GetDecompressor() const; + IHash256GeneratorFactorySelector *GetHashGeneratorFactorySelector() const; + + bool GetHeaderSign1Valid() const; + + void GetHeaderSign2(void *dst, size_t size) const; + void GetHeaderSign2TargetHash(void *dst, size_t size) const; + }; + + class NcaFsHeaderReader : public ::ams::fs::impl::Newable { + NON_COPYABLE(NcaFsHeaderReader); + NON_MOVEABLE(NcaFsHeaderReader); + private: + NcaFsHeader m_data; + s32 m_fs_index; + public: + NcaFsHeaderReader() : m_fs_index(-1) { + std::memset(std::addressof(m_data), 0, sizeof(m_data)); + } + + Result Initialize(const NcaReader &reader, s32 index); + bool IsInitialized() const { return m_fs_index >= 0; } + + // NcaFsHeader &GetData() { return m_data; } + // const NcaFsHeader &GetData() const { return m_data; } + + void GetRawData(void *dst, size_t dst_size) const; + + NcaFsHeader::HashData &GetHashData(); + const NcaFsHeader::HashData &GetHashData() const; + u16 GetVersion() const; + s32 GetFsIndex() const; + NcaFsHeader::FsType GetFsType() const; + NcaFsHeader::HashType GetHashType() const; + NcaFsHeader::EncryptionType GetEncryptionType() const; + NcaPatchInfo &GetPatchInfo(); + const NcaPatchInfo &GetPatchInfo() const; + const NcaAesCtrUpperIv GetAesCtrUpperIv() const; + + bool IsSkipLayerHashEncryption() const; + Result GetHashTargetOffset(s64 *out) const; + + bool ExistsSparseLayer() const; + NcaSparseInfo &GetSparseInfo(); + const NcaSparseInfo &GetSparseInfo() const; + + bool ExistsCompressionLayer() const; + NcaCompressionInfo &GetCompressionInfo(); + const NcaCompressionInfo &GetCompressionInfo() const; + + bool ExistsPatchMetaHashLayer() const; + NcaMetaDataHashDataInfo &GetPatchMetaDataHashDataInfo(); + const NcaMetaDataHashDataInfo &GetPatchMetaDataHashDataInfo() const; + NcaFsHeader::MetaDataHashType GetPatchMetaHashType() const; + + bool ExistsSparseMetaHashLayer() const; + NcaMetaDataHashDataInfo &GetSparseMetaDataHashDataInfo(); + const NcaMetaDataHashDataInfo &GetSparseMetaDataHashDataInfo() const; + NcaFsHeader::MetaDataHashType GetSparseMetaHashType() const; + }; + + class NcaFileSystemDriver : public ::ams::fs::impl::Newable { + NON_COPYABLE(NcaFileSystemDriver); + NON_MOVEABLE(NcaFileSystemDriver); + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + private: + #else + public: + #endif + struct StorageContext { + bool open_raw_storage; + std::shared_ptr<fs::IStorage> body_substorage; + std::shared_ptr<fssystem::SparseStorage> current_sparse_storage; + std::shared_ptr<fs::IStorage> sparse_storage_meta_storage; + std::shared_ptr<fssystem::SparseStorage> original_sparse_storage; + void *external_current_sparse_storage; /* TODO: Add real type? */ + void *external_original_sparse_storage; /* TODO: Add real type? */ + std::shared_ptr<fs::IStorage> aes_ctr_ex_storage_meta_storage; + std::shared_ptr<fs::IStorage> aes_ctr_ex_storage_data_storage; + std::shared_ptr<fssystem::AesCtrCounterExtendedStorage> aes_ctr_ex_storage; + std::shared_ptr<fs::IStorage> indirect_storage_meta_storage; + std::shared_ptr<fssystem::IndirectStorage> indirect_storage; + std::shared_ptr<fs::IStorage> fs_data_storage; + std::shared_ptr<fs::IStorage> compressed_storage_meta_storage; + std::shared_ptr<fssystem::CompressedStorage> compressed_storage; + + std::shared_ptr<fs::IStorage> patch_layer_info_storage; + std::shared_ptr<fs::IStorage> sparse_layer_info_storage; + + /* For tools. */ + std::shared_ptr<fs::IStorage> external_original_storage; + }; + private: + enum AlignmentStorageRequirement { + /* TODO */ + AlignmentStorageRequirement_CacheBlockSize = 0, + AlignmentStorageRequirement_None = 1, + }; + private: + std::shared_ptr<NcaReader> m_original_reader; + std::shared_ptr<NcaReader> m_reader; + MemoryResource * const m_allocator; + fs::IBufferManager * const m_buffer_manager; + fssystem::IHash256GeneratorFactorySelector * const m_hash_generator_factory_selector; + public: + static Result SetupFsHeaderReader(NcaFsHeaderReader *out, const NcaReader &reader, s32 fs_index); + public: + NcaFileSystemDriver(std::shared_ptr<NcaReader> reader, MemoryResource *allocator, fs::IBufferManager *buffer_manager, IHash256GeneratorFactorySelector *hgf_selector) : m_original_reader(), m_reader(reader), m_allocator(allocator), m_buffer_manager(buffer_manager), m_hash_generator_factory_selector(hgf_selector) { + AMS_ASSERT(m_reader != nullptr); + AMS_ASSERT(m_hash_generator_factory_selector != nullptr); + } + + NcaFileSystemDriver(std::shared_ptr<NcaReader> original_reader, std::shared_ptr<NcaReader> reader, MemoryResource *allocator, fs::IBufferManager *buffer_manager, IHash256GeneratorFactorySelector *hgf_selector) : m_original_reader(original_reader), m_reader(reader), m_allocator(allocator), m_buffer_manager(buffer_manager), m_hash_generator_factory_selector(hgf_selector) { + AMS_ASSERT(m_reader != nullptr); + AMS_ASSERT(m_hash_generator_factory_selector != nullptr); + } + + Result OpenStorageWithContext(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<IAsynchronousAccessSplitter> *out_splitter, NcaFsHeaderReader *out_header_reader, s32 fs_index, StorageContext *ctx); + + Result OpenStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<IAsynchronousAccessSplitter> *out_splitter, NcaFsHeaderReader *out_header_reader, s32 fs_index) { + /* Create a storage context. */ + StorageContext ctx{}; + + /* Open the storage. */ + R_RETURN(OpenStorageWithContext(out, out_splitter, out_header_reader, fs_index, std::addressof(ctx))); + } + + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + private: + #else + public: + #endif + Result CreateStorageByRawStorage(std::shared_ptr<fs::IStorage> *out, const NcaFsHeaderReader *header_reader, std::shared_ptr<fs::IStorage> raw_storage, StorageContext *ctx); + private: + Result OpenStorageImpl(std::shared_ptr<fs::IStorage> *out, NcaFsHeaderReader *out_header_reader, s32 fs_index, StorageContext *ctx); + + Result OpenIndirectableStorageAsOriginal(std::shared_ptr<fs::IStorage> *out, const NcaFsHeaderReader *header_reader, StorageContext *ctx); + + Result CreateBodySubStorage(std::shared_ptr<fs::IStorage> *out, s64 offset, s64 size); + + Result CreateAesCtrStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, AlignmentStorageRequirement alignment_storage_requirement); + Result CreateAesXtsStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, s64 offset); + + Result CreateSparseStorageMetaStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info); + Result CreateSparseStorageCore(std::shared_ptr<fssystem::SparseStorage> *out, std::shared_ptr<fs::IStorage> base_storage, s64 base_size, std::shared_ptr<fs::IStorage> meta_storage, const NcaSparseInfo &sparse_info, bool external_info); + Result CreateSparseStorage(std::shared_ptr<fs::IStorage> *out, s64 *out_fs_data_offset, std::shared_ptr<fssystem::SparseStorage> *out_sparse_storage, std::shared_ptr<fs::IStorage> *out_meta_storage, s32 index, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info); + + Result CreateSparseStorageMetaStorageWithVerification(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> *out_verification, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, IHash256GeneratorFactory *hgf); + Result CreateSparseStorageWithVerification(std::shared_ptr<fs::IStorage> *out, s64 *out_fs_data_offset, std::shared_ptr<fssystem::SparseStorage> *out_sparse_storage, std::shared_ptr<fs::IStorage> *out_meta_storage, std::shared_ptr<fs::IStorage> *out_verification, s32 index, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, NcaFsHeader::MetaDataHashType meta_data_hash_type); + + Result CreateAesCtrExStorageMetaStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, s64 offset, NcaFsHeader::EncryptionType encryption_type, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info); + Result CreateAesCtrExStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::AesCtrCounterExtendedStorage> *out_ext, std::shared_ptr<fs::IStorage> base_storage, std::shared_ptr<fs::IStorage> meta_storage, s64 counter_offset, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info); + + Result CreateIndirectStorageMetaStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, const NcaPatchInfo &patch_info); + Result CreateIndirectStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::IndirectStorage> *out_ind, std::shared_ptr<fs::IStorage> base_storage, std::shared_ptr<fs::IStorage> original_data_storage, std::shared_ptr<fs::IStorage> meta_storage, const NcaPatchInfo &patch_info); + + Result CreatePatchMetaStorage(std::shared_ptr<fs::IStorage> *out_aes_ctr_ex_meta, std::shared_ptr<fs::IStorage> *out_indirect_meta, std::shared_ptr<fs::IStorage> *out_verification, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, IHash256GeneratorFactory *hgf); + + Result CreateSha256Storage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, const NcaFsHeader::HashData::HierarchicalSha256Data &sha256_data, IHash256GeneratorFactory *hgf); + + Result CreateIntegrityVerificationStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, const NcaFsHeader::HashData::IntegrityMetaInfo &meta_info, IHash256GeneratorFactory *hgf); + Result CreateIntegrityVerificationStorageForMeta(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> *out_verification, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, IHash256GeneratorFactory *hgf); + Result CreateIntegrityVerificationStorageImpl(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, const NcaFsHeader::HashData::IntegrityMetaInfo &meta_info, s64 layer_info_offset, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, IHash256GeneratorFactory *hgf); + + Result CreateRegionSwitchStorage(std::shared_ptr<fs::IStorage> *out, const NcaFsHeaderReader *header_reader, std::shared_ptr<fs::IStorage> inside_storage, std::shared_ptr<fs::IStorage> outside_storage); + + Result CreateCompressedStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::CompressedStorage> *out_cmp, std::shared_ptr<fs::IStorage> *out_meta, std::shared_ptr<fs::IStorage> base_storage, const NcaCompressionInfo &compression_info); + public: + Result CreateCompressedStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::CompressedStorage> *out_cmp, std::shared_ptr<fs::IStorage> *out_meta, std::shared_ptr<fs::IStorage> base_storage, const NcaCompressionInfo &compression_info, GetDecompressorFunction get_decompressor, MemoryResource *allocator, fs::IBufferManager *buffer_manager); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_header.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_header.hpp new file mode 100644 index 00000000..23ece3e1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_header.hpp @@ -0,0 +1,330 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fssystem/fssystem_i_hash_256_generator.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 14.3.0.0 */ + + struct Hash { + static constexpr size_t Size = IHash256Generator::HashSize; + u8 value[Size]; + }; + static_assert(sizeof(Hash) == Hash::Size); + static_assert(util::is_pod<Hash>::value); + + using NcaDigest = Hash; + + struct NcaHeader { + enum class ContentType : u8 { + Program = 0, + Meta = 1, + Control = 2, + Manual = 3, + Data = 4, + PublicData = 5, + + Start = Program, + End = PublicData, + }; + + enum class DistributionType : u8 { + Download = 0, + GameCard = 1, + + Start = Download, + End = GameCard, + }; + + enum class EncryptionType : u8 { + Auto = 0, + None = 1, + }; + + enum DecryptionKey { + DecryptionKey_AesXts = 0, + DecryptionKey_AesXts1 = DecryptionKey_AesXts, + DecryptionKey_AesXts2 = 1, + DecryptionKey_AesCtr = 2, + DecryptionKey_AesCtrEx = 3, + DecryptionKey_AesCtrHw = 4, + DecryptionKey_Count, + }; + + struct FsInfo { + u32 start_sector; + u32 end_sector; + u32 hash_sectors; + u32 reserved; + }; + static_assert(sizeof(FsInfo) == 0x10); + static_assert(util::is_pod<FsInfo>::value); + + static constexpr u32 Magic0 = util::FourCC<'N','C','A','0'>::Code; + static constexpr u32 Magic1 = util::FourCC<'N','C','A','1'>::Code; + static constexpr u32 Magic2 = util::FourCC<'N','C','A','2'>::Code; + static constexpr u32 Magic3 = util::FourCC<'N','C','A','3'>::Code; + + static constexpr u32 Magic = Magic3; + + static constexpr size_t Size = 1_KB; + static constexpr s32 FsCountMax = 4; + static constexpr size_t HeaderSignCount = 2; + static constexpr size_t HeaderSignSize = 0x100; + static constexpr size_t EncryptedKeyAreaSize = 0x100; + static constexpr size_t SectorSize = 0x200; + static constexpr size_t SectorShift = 9; + static constexpr size_t RightsIdSize = 0x10; + static constexpr size_t XtsBlockSize = 0x200; + static constexpr size_t CtrBlockSize = 0x10; + + static_assert(SectorSize == (1 << SectorShift)); + + /* Data members. */ + u8 header_sign_1[HeaderSignSize]; + u8 header_sign_2[HeaderSignSize]; + u32 magic; + DistributionType distribution_type; + ContentType content_type; + u8 key_generation; + u8 key_index; + u64 content_size; + u64 program_id; + u32 content_index; + u32 sdk_addon_version; + u8 key_generation_2; + u8 header1_signature_key_generation; + u8 reserved_222[2]; + u32 reserved_224[3]; + u8 rights_id[RightsIdSize]; + FsInfo fs_info[FsCountMax]; + Hash fs_header_hash[FsCountMax]; + u8 encrypted_key_area[EncryptedKeyAreaSize]; + + static constexpr u64 SectorToByte(u32 sector) { + return static_cast<u64>(sector) << SectorShift; + } + + static constexpr u32 ByteToSector(u64 byte) { + return static_cast<u32>(byte >> SectorShift); + } + + u8 GetProperKeyGeneration() const; + }; + static_assert(sizeof(NcaHeader) == NcaHeader::Size); + static_assert(util::is_pod<NcaHeader>::value); + + struct NcaBucketInfo { + static constexpr size_t HeaderSize = 0x10; + fs::Int64 offset; + fs::Int64 size; + u8 header[HeaderSize]; + }; + static_assert(util::is_pod<NcaBucketInfo>::value); + + struct NcaPatchInfo { + static constexpr size_t Size = 0x40; + static constexpr size_t Offset = 0x100; + + fs::Int64 indirect_offset; + fs::Int64 indirect_size; + u8 indirect_header[NcaBucketInfo::HeaderSize]; + fs::Int64 aes_ctr_ex_offset; + fs::Int64 aes_ctr_ex_size; + u8 aes_ctr_ex_header[NcaBucketInfo::HeaderSize]; + + bool HasIndirectTable() const; + bool HasAesCtrExTable() const; + }; + static_assert(util::is_pod<NcaPatchInfo>::value); + + union NcaAesCtrUpperIv { + u64 value; + struct { + u32 generation; + u32 secure_value; + } part; + }; + static_assert(util::is_pod<NcaAesCtrUpperIv>::value); + + struct NcaSparseInfo { + NcaBucketInfo bucket; + fs::Int64 physical_offset; + u16 generation; + u8 reserved[6]; + + s64 GetPhysicalSize() const { + return this->bucket.offset + this->bucket.size; + } + + u32 GetGeneration() const { + return static_cast<u32>(this->generation) << 16; + } + + const NcaAesCtrUpperIv MakeAesCtrUpperIv(NcaAesCtrUpperIv upper_iv) const { + NcaAesCtrUpperIv sparse_upper_iv = upper_iv; + sparse_upper_iv.part.generation = this->GetGeneration(); + return sparse_upper_iv; + } + }; + static_assert(util::is_pod<NcaSparseInfo>::value); + + struct NcaCompressionInfo { + NcaBucketInfo bucket; + u8 reserved[8]; + }; + static_assert(util::is_pod<NcaCompressionInfo>::value); + + struct NcaMetaDataHashDataInfo { + fs::Int64 offset; + fs::Int64 size; + Hash hash; + }; + static_assert(util::is_pod<NcaMetaDataHashDataInfo>::value); + + struct NcaFsHeader { + static constexpr size_t Size = 0x200; + static constexpr size_t HashDataOffset = 0x8; + + struct Region { + fs::Int64 offset; + fs::Int64 size; + }; + static_assert(util::is_pod<Region>::value); + + enum class FsType : u8 { + RomFs = 0, + PartitionFs = 1, + }; + + enum class EncryptionType : u8 { + Auto = 0, + None = 1, + AesXts = 2, + AesCtr = 3, + AesCtrEx = 4, + AesCtrSkipLayerHash = 5, + AesCtrExSkipLayerHash = 6, + }; + + enum class HashType : u8 { + Auto = 0, + None = 1, + HierarchicalSha256Hash = 2, + HierarchicalIntegrityHash = 3, + AutoSha3 = 4, + HierarchicalSha3256Hash = 5, + HierarchicalIntegritySha3Hash = 6, + }; + + enum class MetaDataHashType : u8 { + None = 0, + HierarchicalIntegrity = 1, + }; + + union HashData { + struct HierarchicalSha256Data { + static constexpr size_t HashLayerCountMax = 5; + static const size_t MasterHashOffset; + + Hash fs_data_master_hash; + s32 hash_block_size; + s32 hash_layer_count; + Region hash_layer_region[HashLayerCountMax]; + } hierarchical_sha256_data; + static_assert(util::is_pod<HierarchicalSha256Data>::value); + + struct IntegrityMetaInfo { + static const size_t MasterHashOffset; + + u32 magic; + u32 version; + u32 master_hash_size; + + struct LevelHashInfo { + u32 max_layers; + + struct HierarchicalIntegrityVerificationLevelInformation { + static constexpr size_t IntegrityMaxLayerCount = 7; + fs::Int64 offset; + fs::Int64 size; + s32 block_order; + u8 reserved[4]; + } info[HierarchicalIntegrityVerificationLevelInformation::IntegrityMaxLayerCount - 1]; + + struct SignatureSalt { + static constexpr size_t Size = 0x20; + u8 value[Size]; + } seed; + } level_hash_info; + + Hash master_hash; + } integrity_meta_info; + static_assert(util::is_pod<IntegrityMetaInfo>::value); + + u8 padding[NcaPatchInfo::Offset - HashDataOffset]; + }; + + u16 version; + FsType fs_type; + HashType hash_type; + EncryptionType encryption_type; + MetaDataHashType meta_data_hash_type; + u8 reserved[2]; + HashData hash_data; + NcaPatchInfo patch_info; + NcaAesCtrUpperIv aes_ctr_upper_iv; + NcaSparseInfo sparse_info; + NcaCompressionInfo compression_info; + NcaMetaDataHashDataInfo meta_data_hash_data_info; + u8 pad[0x30]; + + bool IsSkipLayerHashEncryption() const { + return this->encryption_type == EncryptionType::AesCtrSkipLayerHash || this->encryption_type == EncryptionType::AesCtrExSkipLayerHash; + } + + Result GetHashTargetOffset(s64 *out) const { + switch (this->hash_type) { + case HashType::HierarchicalIntegrityHash: + case HashType::HierarchicalIntegritySha3Hash: + *out = this->hash_data.integrity_meta_info.level_hash_info.info[this->hash_data.integrity_meta_info.level_hash_info.max_layers - 2].offset; + R_SUCCEED(); + case HashType::HierarchicalSha256Hash: + case HashType::HierarchicalSha3256Hash: + *out = this->hash_data.hierarchical_sha256_data.hash_layer_region[this->hash_data.hierarchical_sha256_data.hash_layer_count - 1].offset; + R_SUCCEED(); + default: + R_THROW(fs::ResultInvalidNcaFsHeader()); + } + } + }; + static_assert(sizeof(NcaFsHeader) == NcaFsHeader::Size); + static_assert(util::is_pod<NcaFsHeader>::value); + static_assert(AMS_OFFSETOF(NcaFsHeader, patch_info) == NcaPatchInfo::Offset); + + inline constexpr const size_t NcaFsHeader::HashData::HierarchicalSha256Data::MasterHashOffset = AMS_OFFSETOF(NcaFsHeader, hash_data.hierarchical_sha256_data.fs_data_master_hash); + inline constexpr const size_t NcaFsHeader::HashData::IntegrityMetaInfo::MasterHashOffset = AMS_OFFSETOF(NcaFsHeader, hash_data.integrity_meta_info.master_hash); + + struct NcaMetaDataHashData { + s64 layer_info_offset; + NcaFsHeader::HashData::IntegrityMetaInfo integrity_meta_info; + }; + static_assert(sizeof(NcaMetaDataHashData) == sizeof(NcaFsHeader::HashData::IntegrityMetaInfo) + sizeof(s64)); + static_assert(util::is_pod<NcaMetaDataHashData>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system.hpp new file mode 100644 index 00000000..8fbcbf7c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system.hpp @@ -0,0 +1,74 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fssystem/fssystem_partition_file_system_meta.hpp> +#include <stratosphere/fs/fsa/fs_ifile.hpp> +#include <stratosphere/fs/fsa/fs_idirectory.hpp> +#include <stratosphere/fs/fsa/fs_ifilesystem.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: Unknown */ + + template<typename MetaType> + class PartitionFileSystemCore : public fs::impl::Newable, public fs::fsa::IFileSystem { + NON_COPYABLE(PartitionFileSystemCore); + NON_MOVEABLE(PartitionFileSystemCore); + private: + class PartitionFile; + class PartitionDirectory; + private: + fs::IStorage *m_base_storage; + MetaType *m_meta_data; + bool m_initialized; + size_t m_meta_data_size; + std::unique_ptr<MetaType> m_unique_meta_data; + std::shared_ptr<fs::IStorage> m_shared_storage; + private: + Result Initialize(fs::IStorage *base_storage, MemoryResource *allocator); + public: + PartitionFileSystemCore(); + virtual ~PartitionFileSystemCore() override; + + Result Initialize(std::unique_ptr<MetaType> &&meta_data, std::shared_ptr<fs::IStorage> base_storage); + Result Initialize(MetaType *meta_data, std::shared_ptr<fs::IStorage> base_storage); + Result Initialize(fs::IStorage *base_storage); + Result Initialize(std::shared_ptr<fs::IStorage> base_storage); + Result Initialize(std::shared_ptr<fs::IStorage> base_storage, MemoryResource *allocator); + + Result GetFileBaseOffset(s64 *out_offset, const char *path); + + virtual Result DoCreateFile(const fs::Path &path, s64 size, int option) override; + virtual Result DoDeleteFile(const fs::Path &path) override; + virtual Result DoCreateDirectory(const fs::Path &path) override; + virtual Result DoDeleteDirectory(const fs::Path &path) override; + virtual Result DoDeleteDirectoryRecursively(const fs::Path &path) override; + virtual Result DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) override; + virtual Result DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) override; + virtual Result DoGetEntryType(fs::DirectoryEntryType *out, const fs::Path &path) override; + virtual Result DoOpenFile(std::unique_ptr<fs::fsa::IFile> *out_file, const fs::Path &path, fs::OpenMode mode) override; + virtual Result DoOpenDirectory(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const fs::Path &path, fs::OpenDirectoryMode mode) override; + virtual Result DoCommit() override; + virtual Result DoCleanDirectoryRecursively(const fs::Path &path) override; + + /* These aren't accessible as commands. */ + virtual Result DoCommitProvisionally(s64 counter) override; + }; + + using PartitionFileSystem = PartitionFileSystemCore<PartitionFileSystemMeta>; + using Sha256PartitionFileSystem = PartitionFileSystemCore<Sha256PartitionFileSystemMeta>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system_meta.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system_meta.hpp new file mode 100644 index 00000000..e58481db --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_partition_file_system_meta.hpp @@ -0,0 +1,115 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: Unknown */ + + namespace impl { + + struct PartitionFileSystemFormat { + #pragma pack(push, 1) + struct PartitionEntry { + u64 offset; + u64 size; + u32 name_offset; + u32 reserved; + }; + static_assert(util::is_pod<PartitionEntry>::value); + #pragma pack(pop) + + static constexpr const char VersionSignature[] = { 'P', 'F', 'S', '0' }; + + static constexpr size_t EntryNameLengthMax = ::ams::fs::EntryNameLengthMax; + static constexpr size_t FileDataAlignmentSize = 0x20; + + using ResultSignatureVerificationFailed = fs::ResultPartitionSignatureVerificationFailed; + }; + + struct Sha256PartitionFileSystemFormat { + static constexpr size_t HashSize = ::ams::crypto::Sha256Generator::HashSize; + + #pragma pack(push, 1) + struct PartitionEntry { + u64 offset; + u64 size; + u32 name_offset; + u32 hash_target_size; + u64 hash_target_offset; + char hash[HashSize]; + }; + static_assert(util::is_pod<PartitionEntry>::value); + #pragma pack(pop) + + static constexpr const char VersionSignature[] = { 'H', 'F', 'S', '0' }; + + static constexpr size_t EntryNameLengthMax = ::ams::fs::EntryNameLengthMax; + static constexpr size_t FileDataAlignmentSize = 0x200; + + using ResultSignatureVerificationFailed = fs::ResultSha256PartitionSignatureVerificationFailed; + }; + + } + + template<typename Format> + class PartitionFileSystemMetaCore : public fs::impl::Newable { + public: + static constexpr size_t EntryNameLengthMax = Format::EntryNameLengthMax; + static constexpr size_t FileDataAlignmentSize = Format::FileDataAlignmentSize; + + /* Forward declare header. */ + struct PartitionFileSystemHeader; + + using PartitionEntry = typename Format::PartitionEntry; + protected: + bool m_initialized; + PartitionFileSystemHeader *m_header; + PartitionEntry *m_entries; + char *m_name_table; + size_t m_meta_data_size; + MemoryResource *m_allocator; + char *m_buffer; + public: + PartitionFileSystemMetaCore() : m_initialized(false), m_allocator(nullptr), m_buffer(nullptr) { /* ... */ } + ~PartitionFileSystemMetaCore(); + + Result Initialize(fs::IStorage *storage, MemoryResource *allocator); + Result Initialize(fs::IStorage *storage, void *header, size_t header_size); + + const PartitionEntry *GetEntry(s32 index) const; + s32 GetEntryCount() const; + s32 GetEntryIndex(const char *name) const; + const char *GetEntryName(s32 index) const; + size_t GetHeaderSize() const; + size_t GetMetaDataSize() const; + public: + static Result QueryMetaDataSize(size_t *out_size, fs::IStorage *storage); + protected: + void DeallocateBuffer(); + }; + + using PartitionFileSystemMeta = PartitionFileSystemMetaCore<impl::PartitionFileSystemFormat>; + + class Sha256PartitionFileSystemMeta : public PartitionFileSystemMetaCore<impl::Sha256PartitionFileSystemFormat> { + public: + using PartitionFileSystemMetaCore<impl::Sha256PartitionFileSystemFormat>::Initialize; + Result Initialize(fs::IStorage *base_storage, MemoryResource *allocator, const void *hash, size_t hash_size, util::optional<u8> suffix = util::nullopt); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_pimpl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_pimpl.hpp new file mode 100644 index 00000000..ac43688f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_pimpl.hpp @@ -0,0 +1,72 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + + namespace impl { + + template<typename T, size_t Size> + struct PimplHelper { + static void Construct(void *); + static void Destroy(void *); + }; + + } + + template<typename T, size_t Size> + class Pimpl { + private: + #if defined(ATMOSPHERE_OS_HORIZON) || defined(ATMOSPHERE_OS_WINDOWS) || defined(ATMOSPHERE_OS_LINUX) + static constexpr size_t ExtraSizeToEnsureCompatibility = 0; + #elif defined(ATMOSPHERE_OS_MACOS) + static constexpr size_t ExtraSizeToEnsureCompatibility = 0x20; + #endif + + static constexpr size_t StorageSize = Size + ExtraSizeToEnsureCompatibility; + private: + alignas(0x10) u8 m_storage[StorageSize]; + public: + ALWAYS_INLINE Pimpl() { impl::PimplHelper<T, StorageSize>::Construct(m_storage); } + ALWAYS_INLINE ~Pimpl() { impl::PimplHelper<T, StorageSize>::Destroy(m_storage); } + + ALWAYS_INLINE T *Get() { return reinterpret_cast<T *>(m_storage + 0); } + ALWAYS_INLINE T *operator->() { return reinterpret_cast<T *>(m_storage + 0); } + }; + + #define AMS_FSSYSTEM_ENABLE_PIMPL(_CLASSNAME_) \ + namespace ams::fssystem::impl { \ + \ + template<size_t Size> \ + struct PimplHelper<_CLASSNAME_, Size> { \ + static ALWAYS_INLINE void Construct(void *p) { \ + static_assert(sizeof(_CLASSNAME_) <= Size); \ + static_assert(alignof(_CLASSNAME_) <= 0x10); \ + \ + std::construct_at(static_cast<_CLASSNAME_ *>(p)); \ + } \ + \ + static ALWAYS_INLINE void Destroy(void *p) { \ + std::destroy_at(static_cast<_CLASSNAME_ *>(p)); \ + } \ + }; \ + \ + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_pooled_buffer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_pooled_buffer.hpp new file mode 100644 index 00000000..b5194a36 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_pooled_buffer.hpp @@ -0,0 +1,114 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: Unknown */ + + constexpr inline size_t BufferPoolAlignment = 4_KB; + constexpr inline size_t BufferPoolWorkSize = 320; + + class PooledBuffer { + NON_COPYABLE(PooledBuffer); + private: + char *m_buffer; + size_t m_size; + private: + static size_t GetAllocatableSizeMaxCore(bool large); + public: + static size_t GetAllocatableSizeMax() { return GetAllocatableSizeMaxCore(false); } + static size_t GetAllocatableParticularlyLargeSizeMax() { return GetAllocatableSizeMaxCore(true); } + private: + void Swap(PooledBuffer &rhs) { + std::swap(m_buffer, rhs.m_buffer); + std::swap(m_size, rhs.m_size); + } + public: + /* Constructor/Destructor. */ + constexpr PooledBuffer() : m_buffer(), m_size() { /* ... */ } + + PooledBuffer(size_t ideal_size, size_t required_size) : m_buffer(), m_size() { + this->Allocate(ideal_size, required_size); + } + + ~PooledBuffer() { + this->Deallocate(); + } + + /* Move and assignment. */ + explicit PooledBuffer(PooledBuffer &&rhs) : m_buffer(rhs.m_buffer), m_size(rhs.m_size) { + rhs.m_buffer = nullptr; + rhs.m_size = 0; + } + + PooledBuffer &operator=(PooledBuffer &&rhs) { + PooledBuffer(std::move(rhs)).Swap(*this); + return *this; + } + + /* Allocation API. */ + void Allocate(size_t ideal_size, size_t required_size) { + return this->AllocateCore(ideal_size, required_size, false); + } + + void AllocateParticularlyLarge(size_t ideal_size, size_t required_size) { + return this->AllocateCore(ideal_size, required_size, true); + } + + void Shrink(size_t ideal_size); + + void Deallocate() { + /* Shrink the buffer to empty. */ + this->Shrink(0); + AMS_ASSERT(m_buffer == nullptr); + } + + char *GetBuffer() const { + AMS_ASSERT(m_buffer != nullptr); + return m_buffer; + } + + size_t GetSize() const { + AMS_ASSERT(m_buffer != nullptr); + return m_size; + } + private: + void AllocateCore(size_t ideal_size, size_t required_size, bool large); + }; + + Result InitializeBufferPool(char *buffer, size_t size); + Result InitializeBufferPool(char *buffer, size_t size, char *work, size_t work_size); + + bool IsPooledBuffer(const void *buffer); + + size_t GetPooledBufferRetriedCount(); + size_t GetPooledBufferReduceAllocationCount(); + size_t GetPooledBufferFreeSizePeak(); + + void ClearPooledBufferPeak(); + + void RegisterAdditionalDeviceAddress(uintptr_t address, size_t size); + void UnregisterAdditionalDeviceAddress(uintptr_t address); + bool IsAdditionalDeviceAddress(const void *ptr); + + inline bool IsDeviceAddress(const void *buffer) { + return IsPooledBuffer(buffer) || IsAdditionalDeviceAddress(buffer); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_romfs_file_system.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_romfs_file_system.hpp new file mode 100644 index 00000000..47ff7552 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_romfs_file_system.hpp @@ -0,0 +1,80 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fsa/fs_ifilesystem.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> +#include <stratosphere/fs/common/fs_dbm_hierarchical_rom_file_table.hpp> +#include <stratosphere/fs/fs_istorage.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: Unknown */ + + class RomFsFileSystem : public fs::fsa::IFileSystem, public fs::impl::Newable { + NON_COPYABLE(RomFsFileSystem); + public: + using RomFileTable = fs::HierarchicalRomFileTable; + private: + RomFileTable m_rom_file_table; + fs::IStorage *m_base_storage; + std::shared_ptr<fs::IStorage> m_shared_storage; + std::unique_ptr<fs::IStorage> m_dir_bucket_storage; + std::unique_ptr<fs::IStorage> m_dir_entry_storage; + std::unique_ptr<fs::IStorage> m_file_bucket_storage; + std::unique_ptr<fs::IStorage> m_file_entry_storage; + s64 m_entry_size; + private: + Result GetFileInfo(RomFileTable::FileInfo *out, const char *path); + Result GetFileInfo(RomFileTable::FileInfo *out, const fs::Path &path) { + R_RETURN(this->GetFileInfo(out, path.GetString())); + } + + Result CheckPathFormat(const fs::Path &path) const { + R_RETURN(fs::PathFormatter::CheckPathFormat(path.GetString(), fs::PathFlags{})); + } + public: + static Result GetRequiredWorkingMemorySize(size_t *out, fs::IStorage *storage); + public: + RomFsFileSystem(); + virtual ~RomFsFileSystem() override; + + Result Initialize(fs::IStorage *base, void *work, size_t work_size, bool use_cache); + Result Initialize(std::shared_ptr<fs::IStorage> base, void *work, size_t work_size, bool use_cache); + + fs::IStorage *GetBaseStorage(); + RomFileTable *GetRomFileTable(); + Result GetFileBaseOffset(s64 *out, const fs::Path &path); + public: + virtual Result DoCreateFile(const fs::Path &path, s64 size, int flags) override; + virtual Result DoDeleteFile(const fs::Path &path) override; + virtual Result DoCreateDirectory(const fs::Path &path) override; + virtual Result DoDeleteDirectory(const fs::Path &path) override; + virtual Result DoDeleteDirectoryRecursively(const fs::Path &path) override; + virtual Result DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) override; + virtual Result DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) override; + virtual Result DoGetEntryType(fs::DirectoryEntryType *out, const fs::Path &path) override; + virtual Result DoOpenFile(std::unique_ptr<fs::fsa::IFile> *out_file, const fs::Path &path, fs::OpenMode mode) override; + virtual Result DoOpenDirectory(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const fs::Path &path, fs::OpenDirectoryMode mode) override; + virtual Result DoCommit() override; + virtual Result DoGetFreeSpaceSize(s64 *out, const fs::Path &path) override; + virtual Result DoCleanDirectoryRecursively(const fs::Path &path) override; + + /* These aren't accessible as commands. */ + virtual Result DoCommitProvisionally(s64 counter) override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_service_context.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_service_context.hpp new file mode 100644 index 00000000..fcf5bb42 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_service_context.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fs_priority.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + + class ServiceContext { + private: + struct DeferredProcessContextForDeviceError { + u64 process_id; + bool is_process_deferred; + bool is_invoke_deferral_requested; + }; + + struct DeferredProcessContextForPriority { + int session_type; + bool is_process_deferred; + bool is_acquired; + }; + private: + fs::PriorityRaw m_priority; + DeferredProcessContextForDeviceError m_deferred_process_context_for_device_error; + DeferredProcessContextForPriority m_deferred_process_context_for_priority; + int m_storage_flag; + void *m_request_hook_context; + bool m_enable_count_failed_ideal_pooled_buffer_allocations; + public: + ServiceContext() : m_priority(fs::PriorityRaw_Normal), m_storage_flag(0), m_request_hook_context(nullptr), m_enable_count_failed_ideal_pooled_buffer_allocations(false) { + /* ... */ + } + }; + + void RegisterServiceContext(ServiceContext *context); + void UnregisterServiceContext(); + + ServiceContext *GetServiceContext(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_sha_hash_generator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_sha_hash_generator.hpp new file mode 100644 index 00000000..36ff4eb6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_sha_hash_generator.hpp @@ -0,0 +1,113 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fssystem/fssystem_i_hash_256_generator.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 14.3.0.0 */ + + namespace impl { + + template<typename Traits> + class ShaHashGenerator final : public ::ams::fssystem::IHash256Generator, public ::ams::fs::impl::Newable { + static_assert(Traits::Generator::HashSize == IHash256Generator::HashSize); + NON_COPYABLE(ShaHashGenerator); + NON_MOVEABLE(ShaHashGenerator); + private: + using Generator = typename Traits::Generator; + private: + Generator m_generator; + public: + ShaHashGenerator() = default; + protected: + virtual void DoInitialize() override { + m_generator.Initialize(); + } + + virtual void DoUpdate(const void *data, size_t size) override { + m_generator.Update(data, size); + } + + virtual void DoGetHash(void *dst, size_t dst_size) override { + m_generator.GetHash(dst, dst_size); + } + }; + + template<typename Traits> + class ShaHashGeneratorFactory final : public IHash256GeneratorFactory, public ::ams::fs::impl::Newable { + static_assert(Traits::Generator::HashSize == IHash256Generator::HashSize); + NON_COPYABLE(ShaHashGeneratorFactory); + NON_MOVEABLE(ShaHashGeneratorFactory); + public: + constexpr ShaHashGeneratorFactory() = default; + protected: + virtual Result DoCreate(std::unique_ptr<IHash256Generator> *out) override { + auto generator = std::unique_ptr<IHash256Generator>(new ShaHashGenerator<Traits>()); + R_UNLESS(generator != nullptr, fs::ResultAllocationMemoryFailedNew()); + + *out = std::move(generator); + R_SUCCEED(); + } + + virtual void DoGenerateHash(void *dst, size_t dst_size, const void *src, size_t src_size) override { + Traits::Generate(dst, dst_size, src, src_size); + } + }; + + struct Sha256Traits { + using Generator = crypto::Sha256Generator; + + static ALWAYS_INLINE void Generate(void *dst, size_t dst_size, const void *src, size_t src_size) { + return crypto::GenerateSha256(dst, dst_size, src, src_size); + } + }; + + struct Sha3256Traits { + using Generator = crypto::Sha3256Generator; + + static ALWAYS_INLINE void Generate(void *dst, size_t dst_size, const void *src, size_t src_size) { + return crypto::GenerateSha3256(dst, dst_size, src, src_size); + } + }; + } + + using Sha256HashGenerator = impl::ShaHashGenerator<impl::Sha256Traits>; + using Sha256HashGeneratorFactory = impl::ShaHashGeneratorFactory<impl::Sha256Traits>; + + using Sha3256HashGenerator = impl::ShaHashGenerator<impl::Sha3256Traits>; + using Sha3256HashGeneratorFactory = impl::ShaHashGeneratorFactory<impl::Sha3256Traits>; + + class ShaHashGeneratorFactorySelector final : public IHash256GeneratorFactorySelector, public ::ams::fs::impl::Newable { + NON_COPYABLE(ShaHashGeneratorFactorySelector); + NON_MOVEABLE(ShaHashGeneratorFactorySelector); + private: + Sha256HashGeneratorFactory m_sha256_factory; + Sha3256HashGeneratorFactory m_sha3_256_factory; + public: + constexpr ShaHashGeneratorFactorySelector() = default; + protected: + virtual IHash256GeneratorFactory *DoGetFactory(HashAlgorithmType alg) override { + switch (alg) { + case HashAlgorithmType_Sha2: return std::addressof(m_sha256_factory); + case HashAlgorithmType_Sha3: return std::addressof(m_sha3_256_factory); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_sparse_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_sparse_storage.hpp new file mode 100644 index 00000000..1a1c3f1a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_sparse_storage.hpp @@ -0,0 +1,104 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fssystem/fssystem_indirect_storage.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: Unknown */ + + class SparseStorage : public IndirectStorage { + NON_COPYABLE(SparseStorage); + NON_MOVEABLE(SparseStorage); + private: + class ZeroStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + public: + ZeroStorage() { /* ... */ } + virtual ~ZeroStorage() { /* ... */ } + public: + virtual Result Read(s64 offset, void *buffer, size_t size) override { + AMS_ASSERT(offset >= 0); + AMS_ASSERT(buffer != nullptr || size == 0); + AMS_UNUSED(offset); + + if (size > 0) { + std::memset(buffer, 0, size); + } + R_SUCCEED(); + } + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + AMS_UNUSED(dst, dst_size, op_id, offset, size, src, src_size); + R_SUCCEED(); + } + + virtual Result GetSize(s64 *out) override { + AMS_ASSERT(out != nullptr); + *out = std::numeric_limits<s64>::max(); + R_SUCCEED(); + } + + virtual Result Flush() override { + R_SUCCEED(); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + AMS_UNUSED(offset, buffer, size); + R_THROW(fs::ResultUnsupportedWriteForZeroStorage()); + } + + virtual Result SetSize(s64 size) override { + AMS_UNUSED(size); + R_THROW(fs::ResultUnsupportedSetSizeForZeroStorage()); + } + }; + private: + ZeroStorage m_zero_storage; + public: + SparseStorage() : IndirectStorage(), m_zero_storage() { /* ... */ } + virtual ~SparseStorage() { /* ... */ } + + using IndirectStorage::Initialize; + + void Initialize(s64 end_offset) { + this->GetEntryTable().Initialize(NodeSize, end_offset); + this->SetZeroStorage(); + } + + void SetDataStorage(fs::SubStorage storage) { + AMS_ASSERT(this->IsInitialized()); + + this->SetStorage(0, storage); + this->SetZeroStorage(); + } + + template<typename T> + void SetDataStorage(T storage, s64 offset, s64 size) { + AMS_ASSERT(this->IsInitialized()); + + this->SetStorage(0, storage, offset, size); + this->SetZeroStorage(); + } + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + private: + void SetZeroStorage() { + return this->SetStorage(1, std::addressof(m_zero_storage), 0, std::numeric_limits<s64>::max()); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_speed_emulation_configuration.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_speed_emulation_configuration.hpp new file mode 100644 index 00000000..79b32cb1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_speed_emulation_configuration.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fs_speed_emulation.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: Unknown */ + + class SpeedEmulationConfiguration { + public: + static void SetSpeedEmulationMode(::ams::fs::SpeedEmulationMode mode); + static ::ams::fs::SpeedEmulationMode GetSpeedEmulationMode(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_subdirectory_filesystem.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_subdirectory_filesystem.hpp new file mode 100644 index 00000000..41e6a161 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_subdirectory_filesystem.hpp @@ -0,0 +1,177 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fsa/fs_ifile.hpp> +#include <stratosphere/fs/fsa/fs_idirectory.hpp> +#include <stratosphere/fs/fsa/fs_ifilesystem.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + + class SubDirectoryFileSystem : public fs::fsa::IFileSystem, public fs::impl::Newable { + NON_COPYABLE(SubDirectoryFileSystem); + private: + std::shared_ptr<fs::fsa::IFileSystem> m_shared_fs; + fs::fsa::IFileSystem * const m_base_fs; + fs::Path m_root_path; + public: + SubDirectoryFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs) : m_shared_fs(std::move(fs)), m_base_fs(m_shared_fs.get()), m_root_path() { + /* ... */ + } + + SubDirectoryFileSystem(fs::fsa::IFileSystem *fs) : m_shared_fs(), m_base_fs(fs), m_root_path() { + /* ... */ + } + + Result Initialize(const fs::Path &path) { + R_RETURN(m_root_path.Initialize(path)); + } + private: + Result ResolveFullPath(fs::Path *out, const fs::Path &path) { + R_RETURN(out->Combine(m_root_path, path)); + } + public: + virtual Result DoCreateFile(const fs::Path &path, s64 size, int option) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->CreateFile(full_path, size, option)); + } + + virtual Result DoDeleteFile(const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->DeleteFile(full_path)); + } + + virtual Result DoCreateDirectory(const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->CreateDirectory(full_path)); + } + + virtual Result DoDeleteDirectory(const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->DeleteDirectory(full_path)); + } + + virtual Result DoDeleteDirectoryRecursively(const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->DeleteDirectoryRecursively(full_path)); + } + + virtual Result DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) override { + fs::Path old_full_path; + fs::Path new_full_path; + R_TRY(this->ResolveFullPath(std::addressof(old_full_path), old_path)); + R_TRY(this->ResolveFullPath(std::addressof(new_full_path), new_path)); + + R_RETURN(m_base_fs->RenameFile(old_full_path, new_full_path)); + } + + virtual Result DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) override { + fs::Path old_full_path; + fs::Path new_full_path; + R_TRY(this->ResolveFullPath(std::addressof(old_full_path), old_path)); + R_TRY(this->ResolveFullPath(std::addressof(new_full_path), new_path)); + + R_RETURN(m_base_fs->RenameDirectory(old_full_path, new_full_path)); + } + + virtual Result DoGetEntryType(fs::DirectoryEntryType *out, const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->GetEntryType(out, full_path)); + } + + virtual Result DoOpenFile(std::unique_ptr<fs::fsa::IFile> *out_file, const fs::Path &path, fs::OpenMode mode) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->OpenFile(out_file, full_path, mode)); + } + + virtual Result DoOpenDirectory(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const fs::Path &path, fs::OpenDirectoryMode mode) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->OpenDirectory(out_dir, full_path, mode)); + } + + virtual Result DoCommit() override { + R_RETURN(m_base_fs->Commit()); + } + + virtual Result DoGetFreeSpaceSize(s64 *out, const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->GetFreeSpaceSize(out, full_path)); + } + + virtual Result DoGetTotalSpaceSize(s64 *out, const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->GetTotalSpaceSize(out, full_path)); + } + + virtual Result DoCleanDirectoryRecursively(const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->CleanDirectoryRecursively(full_path)); + } + + virtual Result DoGetFileTimeStampRaw(fs::FileTimeStampRaw *out, const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->GetFileTimeStampRaw(out, full_path)); + } + + virtual Result DoQueryEntry(char *dst, size_t dst_size, const char *src, size_t src_size, fs::fsa::QueryId query, const fs::Path &path) override { + fs::Path full_path; + R_TRY(this->ResolveFullPath(std::addressof(full_path), path)); + + R_RETURN(m_base_fs->QueryEntry(dst, dst_size, src, src_size, query, full_path)); + } + + /* These aren't accessible as commands. */ + virtual Result DoCommitProvisionally(s64 counter) override { + R_RETURN(m_base_fs->CommitProvisionally(counter)); + } + + virtual Result DoRollback() override { + R_RETURN(m_base_fs->Rollback()); + } + + virtual Result DoFlush() override { + R_RETURN(m_base_fs->Flush()); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_switch_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_switch_storage.hpp new file mode 100644 index 00000000..88f3e4ae --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_switch_storage.hpp @@ -0,0 +1,218 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fs_istorage.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 14.3.0.0 */ + template<typename F> + class SwitchStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(SwitchStorage); + NON_MOVEABLE(SwitchStorage); + private: + std::shared_ptr<fs::IStorage> m_true_storage; + std::shared_ptr<fs::IStorage> m_false_storage; + F m_truth_function; + private: + ALWAYS_INLINE std::shared_ptr<fs::IStorage> &SelectStorage() { + return m_truth_function() ? m_true_storage : m_false_storage; + } + public: + SwitchStorage(std::shared_ptr<fs::IStorage> t, std::shared_ptr<fs::IStorage> f, F func) : m_true_storage(std::move(t)), m_false_storage(std::move(f)), m_truth_function(func) { /* ... */ } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + R_RETURN(this->SelectStorage()->Read(offset, buffer, size)); + } + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + switch (op_id) { + case fs::OperationId::Invalidate: + { + R_TRY(m_true_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + R_TRY(m_false_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + R_SUCCEED(); + } + case fs::OperationId::QueryRange: + { + R_TRY(this->SelectStorage()->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + R_SUCCEED(); + } + default: + R_THROW(fs::ResultUnsupportedOperateRangeForSwitchStorage()); + } + } + + virtual Result GetSize(s64 *out) override { + R_RETURN(this->SelectStorage()->GetSize(out)); + } + + virtual Result Flush() override { + R_RETURN(this->SelectStorage()->Flush()); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + R_RETURN(this->SelectStorage()->Write(offset, buffer, size)); + } + + virtual Result SetSize(s64 size) override { + R_RETURN(this->SelectStorage()->SetSize(size)); + } + }; + + class RegionSwitchStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(RegionSwitchStorage); + NON_MOVEABLE(RegionSwitchStorage); + public: + struct Region { + s64 offset; + s64 size; + }; + private: + std::shared_ptr<fs::IStorage> m_inside_region_storage; + std::shared_ptr<fs::IStorage> m_outside_region_storage; + Region m_region; + public: + RegionSwitchStorage(std::shared_ptr<fs::IStorage> &&i, std::shared_ptr<fs::IStorage> &&o, Region r) : m_inside_region_storage(std::move(i)), m_outside_region_storage(std::move(o)), m_region(r) { + /* ... */ + } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + /* Process until we're done. */ + size_t processed = 0; + while (processed < size) { + /* Process on the appropriate storage. */ + s64 cur_size = 0; + if (this->CheckRegions(std::addressof(cur_size), offset + processed, size - processed)) { + R_TRY(m_inside_region_storage->Read(offset + processed, static_cast<u8 *>(buffer) + processed, cur_size)); + } else { + R_TRY(m_outside_region_storage->Read(offset + processed, static_cast<u8 *>(buffer) + processed, cur_size)); + } + + /* Advance. */ + processed += cur_size; + } + + R_SUCCEED(); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + /* Process until we're done. */ + size_t processed = 0; + while (processed < size) { + /* Process on the appropriate storage. */ + s64 cur_size = 0; + if (this->CheckRegions(std::addressof(cur_size), offset + processed, size - processed)) { + R_TRY(m_inside_region_storage->Write(offset + processed, static_cast<const u8 *>(buffer) + processed, cur_size)); + } else { + R_TRY(m_outside_region_storage->Write(offset + processed, static_cast<const u8 *>(buffer) + processed, cur_size)); + } + + /* Advance. */ + processed += cur_size; + } + + R_SUCCEED(); + } + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + switch (op_id) { + case fs::OperationId::Invalidate: + { + R_TRY(m_inside_region_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + R_TRY(m_outside_region_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + R_SUCCEED(); + } + case fs::OperationId::QueryRange: + { + /* Create a new info. */ + fs::QueryRangeInfo merged_info; + merged_info.Clear(); + + /* Process until we're done. */ + s64 processed = 0; + while (processed < size) { + /* Process on the appropriate storage. */ + s64 cur_size = 0; + fs::QueryRangeInfo cur_info; + if (this->CheckRegions(std::addressof(cur_size), offset + processed, size - processed)) { + R_TRY(m_inside_region_storage->OperateRange(std::addressof(cur_info), sizeof(cur_info), op_id, offset + processed, cur_size, src, src_size)); + } else { + R_TRY(m_outside_region_storage->OperateRange(std::addressof(cur_info), sizeof(cur_info), op_id, offset + processed, cur_size, src, src_size)); + } + + /* Merge the current info. */ + merged_info.Merge(cur_info); + + /* Advance. */ + processed += cur_size; + } + + /* Write the merged info. */ + *reinterpret_cast<fs::QueryRangeInfo *>(dst) = merged_info; + R_SUCCEED(); + } + default: + R_THROW(fs::ResultUnsupportedOperateRangeForRegionSwitchStorage()); + } + } + + virtual Result GetSize(s64 *out) override { + R_RETURN(m_inside_region_storage->GetSize(out)); + } + + virtual Result Flush() override { + /* Flush both storages. */ + R_TRY(m_inside_region_storage->Flush()); + R_TRY(m_outside_region_storage->Flush()); + R_SUCCEED(); + } + + virtual Result SetSize(s64 size) override { + /* Set size for both storages. */ + R_TRY(m_inside_region_storage->SetSize(size)); + R_TRY(m_outside_region_storage->SetSize(size)); + R_SUCCEED(); + } + private: + bool CheckRegions(s64 *out_current_size, s64 offset, s64 size) const { + /* Check if our region contains the access. */ + if (m_region.offset <= offset) { + if (offset < m_region.offset + m_region.size) { + if (m_region.offset + m_region.size <= offset + size) { + *out_current_size = m_region.offset + m_region.size - offset; + } else { + *out_current_size = size; + } + return true; + } else { + *out_current_size = size; + return false; + } + } else { + if (m_region.offset <= offset + size) { + *out_current_size = m_region.offset - offset; + } else { + *out_current_size = size; + } + return false; + } + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_thread_priority_changer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_thread_priority_changer.hpp new file mode 100644 index 00000000..c0958415 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_thread_priority_changer.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: Unknown */ + + class ScopedThreadPriorityChanger { + public: + enum class Mode { + Absolute, + Relative, + }; + private: + os::ThreadType *m_thread; + s32 m_priority; + public: + ALWAYS_INLINE explicit ScopedThreadPriorityChanger(s32 priority, Mode mode) : m_thread(os::GetCurrentThread()), m_priority(0) { + const auto result_priority = std::min((mode == Mode::Relative) ? os::GetThreadPriority(m_thread) + priority : priority, os::LowestSystemThreadPriority); + m_priority = os::ChangeThreadPriority(m_thread, result_priority); + } + + ALWAYS_INLINE ~ScopedThreadPriorityChanger() { + os::ChangeThreadPriority(m_thread, m_priority); + } + }; + + class ScopedThreadPriorityChangerByAccessPriority { + public: + enum class AccessMode { + Read, + Write, + }; + private: + static s32 GetThreadPriorityByAccessPriority(AccessMode mode); + private: + ScopedThreadPriorityChanger m_scoped_changer; + public: + ALWAYS_INLINE explicit ScopedThreadPriorityChangerByAccessPriority(AccessMode mode) : m_scoped_changer(GetThreadPriorityByAccessPriority(mode), ScopedThreadPriorityChanger::Mode::Absolute) { + /* ... */ + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_utility.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_utility.hpp new file mode 100644 index 00000000..c236736a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_utility.hpp @@ -0,0 +1,252 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_file.hpp> +#include <stratosphere/fs/fs_directory.hpp> +#include <stratosphere/fs/fs_filesystem.hpp> +#include <stratosphere/fs/fs_path.hpp> + +namespace ams::fssystem { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + + namespace impl { + + template<typename F> + concept IterateDirectoryHandler = requires (F f, const fs::Path &path, const fs::DirectoryEntry &entry) { + { f(path, entry) } -> std::convertible_to<::ams::Result>; + }; + + /* Iteration. */ + template<IterateDirectoryHandler OnEnterDir, IterateDirectoryHandler OnExitDir, IterateDirectoryHandler OnFile> + Result IterateDirectoryRecursivelyImpl(fs::fsa::IFileSystem *fs, fs::Path &work_path, fs::DirectoryEntry *dir_ent, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) { + /* Open the directory. */ + std::unique_ptr<fs::fsa::IDirectory> dir; + R_TRY(fs->OpenDirectory(std::addressof(dir), work_path, fs::OpenDirectoryMode_All)); + + /* Read and handle entries. */ + while (true) { + /* Read a single entry. */ + s64 read_count = 0; + R_TRY(dir->Read(std::addressof(read_count), dir_ent, 1)); + + /* If we're out of entries, we're done. */ + if (read_count == 0) { + break; + } + + /* Append child path. */ + R_TRY(work_path.AppendChild(dir_ent->name)); + { + if (dir_ent->type == fs::DirectoryEntryType_Directory) { + /* Enter directory. */ + R_TRY(on_enter_dir(work_path, *dir_ent)); + + /* Recurse. */ + R_TRY(IterateDirectoryRecursivelyImpl(fs, work_path, dir_ent, on_enter_dir, on_exit_dir, on_file)); + + /* Exit directory. */ + R_TRY(on_exit_dir(work_path, *dir_ent)); + } else { + /* Call file handler. */ + R_TRY(on_file(work_path, *dir_ent)); + } + } + R_TRY(work_path.RemoveChild()); + } + + R_SUCCEED(); + } + + /* TODO: Cleanup. */ + + } + + /* Iteration API */ + template<impl::IterateDirectoryHandler OnEnterDir, impl::IterateDirectoryHandler OnExitDir, impl::IterateDirectoryHandler OnFile> + Result IterateDirectoryRecursively(fs::fsa::IFileSystem *fs, const fs::Path &root_path, fs::DirectoryEntry *dir_ent_buf, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) { + /* Create work path from the root path. */ + fs::Path work_path; + R_TRY(work_path.Initialize(root_path)); + + R_RETURN(impl::IterateDirectoryRecursivelyImpl(fs, work_path, dir_ent_buf, on_enter_dir, on_exit_dir, on_file)); + } + + template<impl::IterateDirectoryHandler OnEnterDir, impl::IterateDirectoryHandler OnExitDir, impl::IterateDirectoryHandler OnFile> + Result IterateDirectoryRecursively(fs::fsa::IFileSystem *fs, const fs::Path &root_path, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) { + fs::DirectoryEntry dir_entry = {}; + R_RETURN(IterateDirectoryRecursively(fs, root_path, std::addressof(dir_entry), on_enter_dir, on_exit_dir, on_file)); + } + + template<impl::IterateDirectoryHandler OnEnterDir, impl::IterateDirectoryHandler OnExitDir, impl::IterateDirectoryHandler OnFile> + Result IterateDirectoryRecursively(fs::fsa::IFileSystem *fs, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) { + R_RETURN(IterateDirectoryRecursively(fs, fs::MakeConstantPath("/"), on_enter_dir, on_exit_dir, on_file)); + } + + /* TODO: Cleanup API */ + + /* Copy API. */ + Result CopyFile(fs::fsa::IFileSystem *dst_fs, fs::fsa::IFileSystem *src_fs, const fs::Path &dst_path, const fs::Path &src_path, void *work_buf, size_t work_buf_size); + + ALWAYS_INLINE Result CopyFile(fs::fsa::IFileSystem *fs, const fs::Path &dst_path, const fs::Path &src_path, void *work_buf, size_t work_buf_size) { + R_RETURN(CopyFile(fs, fs, dst_path, src_path, work_buf, work_buf_size)); + } + + Result CopyDirectoryRecursively(fs::fsa::IFileSystem *dst_fs, fs::fsa::IFileSystem *src_fs, const fs::Path &dst_path, const fs::Path &src_path, fs::DirectoryEntry *entry, void *work_buf, size_t work_buf_size); + + ALWAYS_INLINE Result CopyDirectoryRecursively(fs::fsa::IFileSystem *fs, const fs::Path &dst_path, const fs::Path &src_path, fs::DirectoryEntry *entry, void *work_buf, size_t work_buf_size) { + R_RETURN(CopyDirectoryRecursively(fs, fs, dst_path, src_path, entry, work_buf, work_buf_size)); + } + + /* Locking utilities. */ + class SemaphoreAdaptor : public os::Semaphore { + public: + SemaphoreAdaptor(int c, int mc) : os::Semaphore(c, mc) { /* ... */ } + + bool TryLock(int *out_acquired, int count) { + AMS_ASSERT(count > 0); + + for (auto i = 0; i < count; ++i) { + if (!this->TryAcquire()) { + *out_acquired = i; + return false; + } + } + + *out_acquired = count; + return true; + } + + void Unlock(int count) { + if (count > 0) { + this->Release(count); + } + } + + bool try_lock() { + return this->TryAcquire(); + } + + void unlock() { + this->Release(); + } + }; + + Result TryAcquireCountSemaphore(util::unique_lock<SemaphoreAdaptor> *out, SemaphoreAdaptor *adaptor); + + class IUniqueLock { + NON_COPYABLE(IUniqueLock); + NON_MOVEABLE(IUniqueLock); + public: + virtual ~IUniqueLock() { /* ... */ } + }; + + template<typename T> + class UniqueLockWithPin final : public IUniqueLock, public ::ams::fs::impl::Newable { + private: + util::unique_lock<SemaphoreAdaptor> m_lock; + T m_pinned_object; + public: + UniqueLockWithPin(util::unique_lock<SemaphoreAdaptor> lock, T obj) : m_lock(std::move(lock)), m_pinned_object(std::move(obj)) { /* ... */ } + + virtual ~UniqueLockWithPin() override { + m_lock = {}; + } + }; + + template<typename T> + class MultiLockWithPin final : public IUniqueLock, public ::ams::fs::impl::Newable { + private: + T m_pinned_object; + SemaphoreAdaptor *m_semaphore_adaptor; + int m_lock_count; + public: + MultiLockWithPin(T obj, SemaphoreAdaptor *adaptor) : m_pinned_object(std::move(obj)), m_semaphore_adaptor(adaptor), m_lock_count(0) { + /* ... */ + } + + virtual ~MultiLockWithPin() override { + if (m_lock_count > 0) { + m_semaphore_adaptor->Unlock(m_lock_count); + } + } + + Result Lock(int count) { + AMS_ASSERT(m_lock_count == 0); + + R_UNLESS(m_semaphore_adaptor->TryLock(std::addressof(m_lock_count), count), fs::ResultOpenCountLimit()); + + R_SUCCEED(); + } + }; + + template<typename T> + Result MakeUniqueLockWithPin(std::unique_ptr<IUniqueLock> *out, SemaphoreAdaptor *adaptor, T obj) { + /* Create the semaphore unique lock. */ + util::unique_lock<SemaphoreAdaptor> sema_lock; + R_TRY(TryAcquireCountSemaphore(std::addressof(sema_lock), adaptor)); + + /* Create the output unique lock. */ + auto result_lock = std::unique_ptr<UniqueLockWithPin<T>>(new UniqueLockWithPin<T>(std::move(sema_lock), std::move(obj))); + R_UNLESS(result_lock != nullptr, fs::ResultAllocationMemoryFailedNew()); + + /* Set the output. */ + *out = std::move(result_lock); + R_SUCCEED(); + } + + template<typename T> + Result MakeUniqueLockWithPin(std::unique_ptr<IUniqueLock> *out, SemaphoreAdaptor *adaptor, int count, T obj) { + /* Create the output unique lock. */ + auto result_lock = std::unique_ptr<MultiLockWithPin<T>>(new MultiLockWithPin<T>(std::move(obj), adaptor)); + R_UNLESS(result_lock != nullptr, fs::ResultAllocationMemoryFailedNew()); + + /* Acquire the output lock. */ + R_TRY(result_lock->Lock(count)); + + /* Set the output. */ + *out = std::move(result_lock); + R_SUCCEED(); + } + + /* Other utility. */ + Result HasFile(bool *out, fs::fsa::IFileSystem *fs, const fs::Path &path); + Result HasDirectory(bool *out, fs::fsa::IFileSystem *fs, const fs::Path &path); + + Result EnsureDirectory(fs::fsa::IFileSystem *fs, const fs::Path &path); + + template<s64 RetryMilliSeconds = 100, s32 MaxTryCount = 10> + ALWAYS_INLINE Result RetryFinitelyForTargetLocked(auto f) { + /* Retry sleeping between retries. */ + constexpr TimeSpan RetryWaitTime = TimeSpan::FromMilliSeconds(RetryMilliSeconds); + + Result result = f(); + for (int i = 0; i < MaxTryCount && fs::ResultTargetLocked::Includes(result); ++i) { + os::SleepThread(RetryWaitTime); + result = f(); + } + + R_RETURN(result); + } + + ALWAYS_INLINE Result RetryToAvoidTargetLocked(auto f) { + R_RETURN((RetryFinitelyForTargetLocked<2, 25>(f))); + } + + void AddCounter(void *counter, size_t counter_size, u64 value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/impl/fssystem_block_cache_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/impl/fssystem_block_cache_manager.hpp new file mode 100644 index 00000000..790a8e34 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/impl/fssystem_block_cache_manager.hpp @@ -0,0 +1,279 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/impl/fs_newable.hpp> + +namespace ams::fssystem::impl { + + /* ACCURATE_TO_VERSION: 13.4.0.0 */ + template<typename CacheEntryType, typename AllocatorType> + class BlockCacheManager { + NON_COPYABLE(BlockCacheManager); + NON_MOVEABLE(BlockCacheManager); + public: + using MemoryRange = typename AllocatorType::MemoryRange; + using CacheIndex = s32; + + using BufferAttribute = typename AllocatorType::BufferAttribute; + + static constexpr CacheIndex InvalidCacheIndex = -1; + + using CacheEntry = CacheEntryType; + static_assert(util::is_pod<CacheEntry>::value); + private: + AllocatorType *m_allocator = nullptr; + std::unique_ptr<CacheEntry[], ::ams::fs::impl::Deleter> m_entries{}; + s32 m_max_cache_entry_count = 0; + public: + constexpr BlockCacheManager() = default; + public: + Result Initialize(AllocatorType *allocator, s32 max_entries) { + /* Check pre-conditions. */ + AMS_ASSERT(m_allocator == nullptr); + AMS_ASSERT(m_entries == nullptr); + AMS_ASSERT(allocator != nullptr); + + /* Setup our entries buffer, if necessary. */ + if (max_entries > 0) { + /* Create the entries. */ + m_entries = fs::impl::MakeUnique<CacheEntry[]>(static_cast<size_t>(max_entries)); + R_UNLESS(m_entries != nullptr, fs::ResultAllocationMemoryFailedMakeUnique()); + + /* Clear the entries. */ + std::memset(m_entries.get(), 0, sizeof(CacheEntry) * max_entries); + } + + /* Set fields. */ + m_allocator = allocator; + m_max_cache_entry_count = max_entries; + + R_SUCCEED(); + } + + void Finalize() { + /* Reset all fields. */ + m_entries.reset(nullptr); + m_allocator = nullptr; + m_max_cache_entry_count = 0; + } + + bool IsInitialized() const { + return m_allocator != nullptr; + } + + AllocatorType *GetAllocator() { return m_allocator; } + s32 GetCount() const { return m_max_cache_entry_count; } + + void AcquireCacheEntry(CacheEntry *out_entry, MemoryRange *out_range, CacheIndex index) { + /* Check pre-conditions. */ + AMS_ASSERT(this->IsInitialized()); + AMS_ASSERT(index < this->GetCount()); + + /* Get the entry. */ + auto &entry = m_entries[index]; + + /* Set the out range. */ + if (entry.IsWriteBack()) { + *out_range = AllocatorType::MakeMemoryRange(entry.memory_address, entry.memory_size); + } else { + *out_range = m_allocator->AcquireCache(entry.handle); + } + + /* Set the out entry. */ + *out_entry = entry; + + /* Sanity check. */ + AMS_ASSERT(out_entry->is_valid); + AMS_ASSERT(out_entry->is_cached); + + /* Clear our local entry. */ + entry.is_valid = false; + entry.handle = 0; + entry.memory_address = 0; + entry.memory_size = 0; + entry.lru_counter = 0; + + /* Update the out entry. */ + out_entry->is_valid = true; + out_entry->handle = 0; + out_entry->memory_address = 0; + out_entry->memory_size = 0; + out_entry->lru_counter = 0; + } + + bool ExistsRedundantCacheEntry(const CacheEntry &entry) const { + /* Check pre-conditions. */ + AMS_ASSERT(this->IsInitialized()); + + /* Iterate over all entries, checking if any contain our extents. */ + for (auto i = 0; i < this->GetCount(); ++i) { + if (const auto &cur_entry = m_entries[i]; cur_entry.IsAllocated()) { + if (cur_entry.range.offset < entry.range.GetEndOffset() && entry.range.offset < cur_entry.range.GetEndOffset()) { + return true; + } + } + } + + return false; + } + + void GetEmptyCacheEntryIndex(CacheIndex *out_empty, CacheIndex *out_lru) { + /* Find empty and lru indices. */ + CacheIndex empty = InvalidCacheIndex, lru = InvalidCacheIndex; + for (auto i = 0; i < this->GetCount(); ++i) { + if (auto &entry = m_entries[i]; entry.is_valid) { + /* Get/Update the lru counter. */ + if (entry.lru_counter != std::numeric_limits<decltype(entry.lru_counter)>::max()) { + ++entry.lru_counter; + } + + /* Update the lru index. */ + if (lru == InvalidCacheIndex || m_entries[lru].lru_counter < entry.lru_counter) { + lru = i; + } + } else { + /* The entry is invalid, so we can update the empty index. */ + if (empty == InvalidCacheIndex) { + empty = i; + } + } + } + + /* Set the output. */ + *out_empty = empty; + *out_lru = lru; + } + + void Invalidate() { + /* Check pre-conditions. */ + AMS_ASSERT(this->IsInitialized()); + + /* Invalidate all entries. */ + for (auto i = 0; i < this->GetCount(); ++i) { + if (m_entries[i].is_valid) { + this->InvalidateCacheEntry(i); + } + } + } + + void InvalidateCacheEntry(CacheIndex index) { + /* Check pre-conditions. */ + AMS_ASSERT(this->IsInitialized()); + AMS_ASSERT(index < this->GetCount()); + + /* Get the entry. */ + auto &entry = m_entries[index]; + AMS_ASSERT(entry.is_valid); + + /* If necessary, perform write-back. */ + if (entry.IsWriteBack()) { + AMS_ASSERT(entry.memory_address != 0 && entry.handle == 0); + m_allocator->DeallocateBuffer(AllocatorType::MakeMemoryRange(entry.memory_address, entry.memory_size)); + } else { + AMS_ASSERT(entry.memory_address == 0 && entry.handle != 0); + + if (const auto memory_range = m_allocator->AcquireCache(entry.handle); memory_range.first) { + m_allocator->DeallocateBuffer(memory_range); + } + } + + /* Set entry as invalid. */ + entry.is_valid = false; + entry.Invalidate(); + } + + void RegisterCacheEntry(CacheIndex index, const MemoryRange &memory_range, const BufferAttribute &attribute) { + /* Check pre-conditions. */ + AMS_ASSERT(this->IsInitialized()); + + /* Register the entry. */ + if (auto &entry = m_entries[index]; entry.IsWriteBack()) { + entry.handle = 0; + entry.memory_address = memory_range.first; + entry.memory_size = memory_range.second; + } else { + entry.handle = m_allocator->RegisterCache(memory_range, attribute); + entry.memory_address = 0; + entry.memory_size = 0; + } + } + + void ReleaseCacheEntry(CacheEntry *entry, const MemoryRange &memory_range) { + /* Check pre-conditions. */ + AMS_ASSERT(this->IsInitialized()); + + /* Release the entry. */ + m_allocator->DeallocateBuffer(memory_range); + entry->is_valid = false; + entry->is_cached = false; + } + + void ReleaseCacheEntry(CacheIndex index, const MemoryRange &memory_range) { + return this->ReleaseCacheEntry(std::addressof(m_entries[index]), memory_range); + } + + bool SetCacheEntry(CacheIndex index, const CacheEntry &entry, const MemoryRange &memory_range, const BufferAttribute &attr) { + /* Check pre-conditions. */ + AMS_ASSERT(this->IsInitialized()); + AMS_ASSERT(0 <= index && index < this->GetCount()); + + /* Write the entry. */ + m_entries[index] = entry; + + /* Sanity check. */ + AMS_ASSERT(entry.is_valid); + AMS_ASSERT(entry.is_cached); + AMS_ASSERT(entry.handle == 0); + AMS_ASSERT(entry.memory_address == 0); + + /* Register or release. */ + if (this->ExistsRedundantCacheEntry(entry)) { + this->ReleaseCacheEntry(index, memory_range); + return false; + } else { + this->RegisterCacheEntry(index, memory_range, attr); + return true; + } + } + + bool SetCacheEntry(CacheIndex index, const CacheEntry &entry, const MemoryRange &memory_range) { + const BufferAttribute attr{}; + return this->SetCacheEntry(index, entry, memory_range, attr); + } + + void SetFlushing(CacheIndex index, bool en) { + if constexpr (requires { m_entries[index].is_flushing; }) { + m_entries[index].is_flushing = en; + } + } + + void SetWriteBack(CacheIndex index, bool en) { + if constexpr (requires { m_entries[index].is_write_back; }) { + m_entries[index].is_write_back = en; + } + } + + const CacheEntry &operator[](CacheIndex index) const { + /* Check pre-conditions. */ + AMS_ASSERT(this->IsInitialized()); + AMS_ASSERT(0 <= index && index < this->GetCount()); + + return m_entries[index]; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_i_save_file.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_i_save_file.hpp new file mode 100644 index 00000000..bc2d21f6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_i_save_file.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fssystem::save { + + /* TODO */ + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_i_save_file_system_driver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_i_save_file_system_driver.hpp new file mode 100644 index 00000000..bc2d21f6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_i_save_file_system_driver.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::fssystem::save { + + /* TODO */ + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gc.hpp new file mode 100644 index 00000000..80007a74 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gc.hpp @@ -0,0 +1,21 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/gc/impl/gc_types.hpp> +#include <stratosphere/gc/impl/gc_gc_crypto.hpp> +#include <stratosphere/gc/impl/gc_embedded_data_holder.hpp> +#include <stratosphere/gc/gc.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gc/gc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gc/gc.hpp new file mode 100644 index 00000000..797bb8bb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gc/gc.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/gc/impl/gc_types.hpp> + +namespace ams::gc { + + struct GameCardIdSet { + gc::impl::CardId1 id1; + gc::impl::CardId2 id2; + gc::impl::CardId3 id3; + }; + static_assert(util::is_pod<GameCardIdSet>::value); + static_assert(sizeof(GameCardIdSet) == 0xC); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gc/impl/gc_embedded_data_holder.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gc/impl/gc_embedded_data_holder.hpp new file mode 100644 index 00000000..30201f3d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gc/impl/gc_embedded_data_holder.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/gc/impl/gc_gc_crypto.hpp> + +namespace ams::gc::impl { + + class EmbeddedDataHolder { + NON_COPYABLE(EmbeddedDataHolder); + NON_MOVEABLE(EmbeddedDataHolder); + friend class GcCrypto; + private: + struct ConcatenatedGcLibraryEmbeddedKeys { + u8 enc_hmac_key_for_cv[GcCrypto::GcHmacKeyLength]; + u8 enc_hmac_key_for_key_and_iv[GcCrypto::GcHmacKeyLength]; + u8 enc_cv_constant_value[GcCrypto::GcCvConstLength]; + u8 enc_rsa_oaep_label_hash[GcCrypto::GcSha256HashLength]; + }; + static_assert(util::is_pod<ConcatenatedGcLibraryEmbeddedKeys>::value); + static_assert(sizeof(ConcatenatedGcLibraryEmbeddedKeys) == 0x70); + private: + static bool s_is_dev; + static const void *s_ca_public_exponent; + static const void *s_ca1_modulus; + static const void *s_ca9_modulus; + static const void *s_ca10_modulus; + static const void *s_ca10_certificate_modulus; + static const void *s_card_header_key; + private: + static constinit inline u8 s_titlekey_keks[GcCrypto::GcTitleKeyKekIndexMax][GcCrypto::GcAesKeyLength] = {}; + public: + static Result SetLibraryEmbeddedKeys(bool is_dev = GcCrypto::CheckDevelopmentSpl()); + + static void SetLibraryTitleKeyKek(size_t kek_index, const void *kek, size_t kek_size) { + AMS_ASSERT(kek_index < GcCrypto::GcTitleKeyKekIndexMax); + AMS_ASSERT(kek_size == GcCrypto::GcAesKeyLength); + AMS_UNUSED(kek_size); + + std::memcpy(s_titlekey_keks[kek_index], kek, sizeof(s_titlekey_keks[kek_index])); + } + private: + static Result DecryptoEmbeddedKeys(ConcatenatedGcLibraryEmbeddedKeys *out, size_t out_size, bool is_dev = GcCrypto::CheckDevelopmentSpl()); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gc/impl/gc_gc_crypto.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gc/impl/gc_gc_crypto.hpp new file mode 100644 index 00000000..9072e77e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gc/impl/gc_gc_crypto.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/gc/impl/gc_types.hpp> + +namespace ams::gc::impl { + + class GcCrypto { + NON_COPYABLE(GcCrypto); + NON_MOVEABLE(GcCrypto); + public: + static constexpr size_t GcRsaKeyLength = crypto::Rsa2048PssSha256Verifier::ModulusSize; + static constexpr size_t GcRsaPublicExponentLength = 3; + static constexpr size_t GcAesKeyLength = crypto::AesEncryptor128::KeySize; + static constexpr size_t GcAesCbcIvLength = crypto::Aes128CbcEncryptor::IvSize; + static constexpr size_t GcHmacKeyLength = 0x20; + static constexpr size_t GcCvConstLength = 0x10; + static constexpr size_t GcTitleKeyKekIndexMax = 0x10; + static constexpr size_t GcSha256HashLength = crypto::Sha256Generator::HashSize; + public: + static bool CheckDevelopmentSpl(); + static Result DecryptAesKeySpl(void *dst, size_t dst_size, const void *src, size_t src_size, s32 generation, u32 option); + + static Result VerifyCardHeader(const void *header_buffer, size_t header_size, const void *modulus, size_t modulus_size); + + static Result EncryptCardHeader(void *header, size_t header_size); + static Result DecryptCardHeader(void *header, size_t header_size); + + static Result VerifyT1CardCertificate(const void *cert_buffer, size_t cert_size); + + static Result VerifyCa10Certificate(const void *cert_buffer, size_t cert_size); + + static Result DecryptCardInitialData(void *dst, size_t dst_size, const void *initial_data, size_t data_size, size_t kek_index); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gc/impl/gc_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gc/impl/gc_types.hpp new file mode 100644 index 00000000..a6b1043f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gc/impl/gc_types.hpp @@ -0,0 +1,213 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::gc::impl { + + struct CardInitialDataPayload { + u8 package_id[8]; + u8 reserved_8[8]; + u8 auth_data[0x10]; + u8 auth_mac[0x10]; + u8 auth_nonce[0xC]; + }; + static_assert(util::is_pod<CardInitialDataPayload>::value); + static_assert(sizeof(CardInitialDataPayload) == 0x3C); + + struct CardInitialData { + CardInitialDataPayload payload; + u8 padding[0x200 - sizeof(CardInitialDataPayload)]; + }; + static_assert(util::is_pod<CardInitialData>::value); + static_assert(sizeof(CardInitialData) == 0x200); + + enum FwVersion : u8 { + FwVersion_ForDev = 0, + FwVersion_1_0_0 = 1, + FwVersion_4_0_0 = 2, + FwVersion_9_0_0 = 3, + FwVersion_11_0_0 = 4, + FwVersion_12_0_0 = 5, + }; + + enum KekIndex : u8 { + KekIndex_Version0 = 0, + KekIndex_VersionForDev = 1, + }; + + struct CardHeaderKeyIndex { + using KekIndex = util::BitPack8::Field<0, 4, gc::impl::KekIndex>; + using TitleKeyDecIndex = util::BitPack8::Field<KekIndex::Next, 4, u8>; + + static_assert(TitleKeyDecIndex::Next == BITSIZEOF(u8)); + }; + + struct CardHeaderEncryptedData { + u32 fw_version[2]; + u32 acc_ctrl_1; + u32 wait_1_time_read; + u32 wait_2_time_read; + u32 wait_1_time_write; + u32 wait_2_time_write; + u32 fw_mode; + u32 cup_version; + u8 compatibility_type; + u8 reserved_25; + u8 reserved_26; + u8 reserved_27; + u8 upp_hash[8]; + u64 cup_id; + u8 reserved_38[0x38]; + }; + static_assert(util::is_pod<CardHeaderEncryptedData>::value); + static_assert(sizeof(CardHeaderEncryptedData) == 0x70); + + enum MakerCodeForCardId1 : u8 { + MakerCodeForCardId1_MegaChips = 0xC2, + MakerCodeForCardId1_Lapis = 0xAE, + }; + + enum MemoryCapacity : u8 { + MemoryCapacity_1GB = 0xFA, + MemoryCapacity_2GB = 0xF8, + MemoryCapacity_4GB = 0xF0, + MemoryCapacity_8GB = 0xE0, + MemoryCapacity_16GB = 0xE1, + MemoryCapacity_32GB = 0xE2, + }; + + enum MemoryType : u8 { + MemoryType_T1RomFast = 0x01, + MemoryType_T2RomFast = 0x02, + MemoryType_T1NandFast = 0x09, + MemoryType_T2NandFast = 0x0A, + MemoryType_T1RomLate = 0x21, + MemoryType_T2RomLate = 0x22, + MemoryType_T1NandLate = 0x29, + MemoryType_T2NandLate = 0x2A, + }; + + enum CardSecurityNumber : u8 { + CardSecurityNumber_0 = 0x00, + CardSecurityNumber_1 = 0x01, + CardSecurityNumber_2 = 0x02, + CardSecurityNumber_3 = 0x03, + CardSecurityNumber_4 = 0x04, + }; + + enum CardType : u8 { + CardType_Rom = 0x00, + CardType_Writable_Dev_T1 = 0x01, + CardType_Writable_Prod_T1 = 0x02, + CardType_Writable_Dev_T2 = 0x03, + CardType_Writable_Prod_T2 = 0x04, + }; + + enum AccessControl1ClockRate : u32 { + AccessControl1ClockRate_25MHz = 0x00A10011, + AccessControl1ClockRate_50MHz = 0x00A10010, + }; + + enum SelSec : u8 { + SelSec_T1 = 1, + SelSec_T2 = 2, + }; + + struct CardId1 { + MakerCodeForCardId1 maker_code; + MemoryCapacity memory_capacity; + u8 reserved; + MemoryType memory_type; + }; + + struct CardId2 { + CardSecurityNumber card_security_number; + CardType card_type; + u8 reserved[2]; + }; + + struct CardId3 { + u8 reserved[4]; + }; + + struct CardHeader { + static constexpr u32 Magic = util::FourCC<'H','E','A','D'>::Code; + + u32 magic; + u32 rom_area_start_page; + u32 backup_area_start_page; + util::BitPack8 key_index; + u8 rom_size; + u8 version; + u8 flags; + u8 package_id[8]; + u32 valid_data_end_page; + u8 reserved_11C[4]; + u8 iv[crypto::Aes128CbcDecryptor::IvSize]; + u64 partition_fs_header_address; + u64 partition_fs_header_size; + u8 partition_fs_header_hash[crypto::Sha256Generator::HashSize]; + u8 initial_data_hash[crypto::Sha256Generator::HashSize]; + u32 sel_sec; + u32 sel_t1_key; + u32 sel_key; + u32 lim_area_page; + + union { + u8 raw_encrypted_data[sizeof(CardHeaderEncryptedData)]; + CardHeaderEncryptedData encrypted_data; + }; + }; + static_assert(util::is_pod<CardHeader>::value); + static_assert(sizeof(CardHeader) == 0x100); + + struct CardHeaderWithSignature { + u8 signature[crypto::Rsa2048Pkcs1Sha256Verifier::SignatureSize]; + CardHeader data; + }; + static_assert(util::is_pod<CardHeaderWithSignature>::value); + static_assert(sizeof(CardHeaderWithSignature) == 0x200); + + static constexpr size_t CardDeviceIdLength = 0x10; + + struct T1CardCertificate { + static constexpr u32 Magic = util::FourCC<'C','E','R','T'>::Code; + + u8 signature[crypto::Rsa2048Pkcs1Sha256Verifier::SignatureSize]; + u32 magic; + u32 version; + u8 kek_index; + u8 flags[7]; + u8 t1_card_device_id[CardDeviceIdLength]; + u8 iv[crypto::Aes128CtrEncryptor::IvSize]; + u8 hw_key[crypto::Aes128CtrEncryptor::KeySize]; + u8 reserved[0xC0]; + u8 padding[0x200]; + }; + static_assert(util::is_pod<T1CardCertificate>::value); + static_assert(sizeof(T1CardCertificate) == 0x400); + + struct Ca10Certificate { + u8 signature[crypto::Rsa2048Pkcs1Sha256Verifier::SignatureSize]; + u8 unk_100[0x30]; + u8 modulus[crypto::Rsa2048Pkcs1Sha256Verifier::ModulusSize]; + u8 unk_230[0x1D0]; + }; + static_assert(util::is_pod<Ca10Certificate>::value); + static_assert(sizeof(Ca10Certificate) == 0x400); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio.hpp new file mode 100644 index 00000000..5743498a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/gpio/gpio_types.hpp> +#include <stratosphere/gpio/sf/gpio_sf_i_pad_session.hpp> +#include <stratosphere/gpio/sf/gpio_sf_i_manager.hpp> +#include <stratosphere/gpio/gpio_api.hpp> +#include <stratosphere/gpio/gpio_pad_api.hpp> +#include <stratosphere/gpio/driver/gpio_select_driver_api.hpp> +#include <stratosphere/gpio/driver/gpio_pad_accessor.hpp> +#include <stratosphere/gpio/driver/impl/gpio_pad_session_impl.hpp> +#include <stratosphere/gpio/server/gpio_server_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/board/nintendo/nx/gpio_driver_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/board/nintendo/nx/gpio_driver_api.hpp new file mode 100644 index 00000000..61bb4795 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/board/nintendo/nx/gpio_driver_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/gpio/gpio_types.hpp> +#include <stratosphere/gpio/driver/gpio_i_gpio_driver.hpp> + +namespace ams::gpio::driver::board::nintendo::nx { + + void Initialize(bool enable_interrupt_handlers); + + void SetInitialGpioConfig(); + void SetInitialWakePinConfig(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/gpio_driver_client_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/gpio_driver_client_api.hpp new file mode 100644 index 00000000..18d00bed --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/gpio_driver_client_api.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/gpio/gpio_types.hpp> +#include <stratosphere/gpio/driver/gpio_i_gpio_driver.hpp> + +namespace ams::gpio::driver { + + void Initialize(); + void Finalize(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/gpio_driver_service_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/gpio_driver_service_api.hpp new file mode 100644 index 00000000..0de10d75 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/gpio_driver_service_api.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/gpio/gpio_types.hpp> +#include <stratosphere/gpio/driver/gpio_i_gpio_driver.hpp> + +namespace ams::gpio::driver { + + void RegisterDriver(IGpioDriver *driver); + void UnregisterDriver(IGpioDriver *driver); + + Result RegisterDeviceCode(DeviceCode device_code, Pad *pad); + bool UnregisterDeviceCode(DeviceCode device_code); + + void RegisterInterruptHandler(ddsf::IEventHandler *handler); + void UnregisterInterruptHandler(ddsf::IEventHandler *handler); + + void SetInitialGpioConfig(); + void SetInitialWakePinConfig(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/gpio_i_gpio_driver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/gpio_i_gpio_driver.hpp new file mode 100644 index 00000000..29dfb96e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/gpio_i_gpio_driver.hpp @@ -0,0 +1,75 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/gpio/gpio_types.hpp> +#include <stratosphere/ddsf.hpp> + +namespace ams::gpio::driver { + + class Pad; + + class IGpioDriver : public ::ams::ddsf::IDriver { + NON_COPYABLE(IGpioDriver); + NON_MOVEABLE(IGpioDriver); + AMS_DDSF_CASTABLE_TRAITS(ams::gpio::driver::IGpioDriver, ::ams::ddsf::IDriver); + public: + IGpioDriver() : IDriver() { /* ... */ } + virtual ~IGpioDriver() { /* ... */ } + + virtual void InitializeDriver() = 0; + virtual void FinalizeDriver() = 0; + + virtual Result InitializePad(Pad *pad) = 0; + virtual void FinalizePad(Pad *pad) = 0; + + virtual Result GetDirection(Direction *out, Pad *pad) const = 0; + virtual Result SetDirection(Pad *pad, Direction direction) = 0; + + virtual Result GetValue(GpioValue *out, Pad *pad) const = 0; + virtual Result SetValue(Pad *pad, GpioValue value) = 0; + + virtual Result GetInterruptMode(InterruptMode *out, Pad *pad) const = 0; + virtual Result SetInterruptMode(Pad *pad, InterruptMode mode) = 0; + + virtual Result SetInterruptEnabled(Pad *pad, bool en) = 0; + + virtual Result GetInterruptStatus(InterruptStatus *out, Pad *pad) = 0; + virtual Result ClearInterruptStatus(Pad *pad) = 0; + + virtual os::SdkMutex &GetInterruptControlMutex(const Pad &pad) const = 0; + + virtual Result GetDebounceEnabled(bool *out, Pad *pad) const = 0; + virtual Result SetDebounceEnabled(Pad *pad, bool en) = 0; + + virtual Result GetDebounceTime(s32 *out_ms, Pad *pad) const = 0; + virtual Result SetDebounceTime(Pad *pad, s32 ms) = 0; + + virtual Result GetUnknown22(u32 *out) = 0; + virtual void Unknown23() = 0; + + virtual Result SetValueForSleepState(Pad *pad, GpioValue value) = 0; + virtual Result IsWakeEventActive(bool *out, Pad *pad) const = 0; + virtual Result SetWakeEventActiveFlagSetForDebug(Pad *pad, bool en) = 0; + virtual Result SetWakePinDebugMode(WakePinDebugMode mode) = 0; + + virtual Result Suspend() = 0; + virtual Result SuspendLow() = 0; + virtual Result Resume() = 0; + virtual Result ResumeLow() = 0; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/gpio_pad.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/gpio_pad.hpp new file mode 100644 index 00000000..e276f2dd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/gpio_pad.hpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/gpio/gpio_types.hpp> +#include <stratosphere/ddsf.hpp> + +namespace ams::gpio::driver { + + class Pad : public ::ams::ddsf::IDevice { + NON_COPYABLE(Pad); + NON_MOVEABLE(Pad); + AMS_DDSF_CASTABLE_TRAITS(ams::gpio::driver::Pad, ::ams::ddsf::IDevice); + private: + int m_pad_number; + bool m_is_interrupt_enabled; + public: + explicit Pad(int pad) : IDevice(true), m_pad_number(pad), m_is_interrupt_enabled(false) { /* ... */ } + + Pad() : Pad(0) { /* ... */ } + + virtual ~Pad() { /* ... */ } + + int GetPadNumber() const { + return m_pad_number; + } + + void SetPadNumber(int p) { + m_pad_number = p; + } + + bool IsInterruptEnabled() const { + return m_is_interrupt_enabled; + } + + void SetInterruptEnabled(bool en) { + m_is_interrupt_enabled = en; + } + + bool IsInterruptRequiredForDriver() const { + return this->IsInterruptEnabled() && this->IsAnySessionBoundToInterrupt(); + } + + bool IsAnySessionBoundToInterrupt() const; + void SignalInterruptBoundEvent(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/gpio_pad_accessor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/gpio_pad_accessor.hpp new file mode 100644 index 00000000..feab17d9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/gpio_pad_accessor.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/gpio/gpio_types.hpp> + +namespace ams::gpio::driver { + + namespace impl { + + constexpr inline size_t GpioPadSessionSize = 0x60; + constexpr inline size_t GpioPadSessionAlign = 8; + struct alignas(GpioPadSessionAlign) GpioPadSessionImplPadded; + + } + + struct GpioPadSession { + util::TypedStorage<impl::GpioPadSessionImplPadded, impl::GpioPadSessionSize, impl::GpioPadSessionAlign> _impl; + }; + + Result OpenSession(GpioPadSession *out, DeviceCode device_code, ddsf::AccessMode access_mode); + void CloseSession(GpioPadSession *session); + + Result SetDirection(GpioPadSession *session, gpio::Direction direction); + Result GetDirection(gpio::Direction *out, GpioPadSession *session); + + Result SetValue(GpioPadSession *session, gpio::GpioValue value); + Result GetValue(gpio::GpioValue *out, GpioPadSession *session); + + /* TODO */ + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/gpio_select_driver_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/gpio_select_driver_api.hpp new file mode 100644 index 00000000..2d9cd9bb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/gpio_select_driver_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/gpio/gpio_types.hpp> +#include <stratosphere/gpio/driver/gpio_i_gpio_driver.hpp> +#include <stratosphere/gpio/driver/gpio_driver_service_api.hpp> +#include <stratosphere/gpio/driver/gpio_driver_client_api.hpp> + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + + #include <stratosphere/gpio/driver/board/nintendo/nx/gpio_driver_api.hpp> + + namespace ams::gpio::driver::board { + + using namespace ams::gpio::driver::board::nintendo::nx; + + } + +#else + + // TODO: #error "Unknown board for ams::gpio::driver::" + +#endif + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/impl/gpio_event_holder.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/impl/gpio_event_holder.hpp new file mode 100644 index 00000000..4f24a5fe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/impl/gpio_event_holder.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> + +namespace ams::gpio::driver::impl { + + class EventHolder { + NON_COPYABLE(EventHolder); + NON_MOVEABLE(EventHolder); + private: + os::SystemEventType *m_event; + public: + constexpr EventHolder() : m_event(nullptr) { /* ... */ } + + void AttachEvent(os::SystemEventType *event) { + m_event = event; + } + + os::SystemEventType *DetachEvent() { + auto ev = m_event; + m_event = nullptr; + return ev; + } + + os::SystemEventType *GetSystemEvent() { + return m_event; + } + + bool IsBound() const { + return m_event != nullptr; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/impl/gpio_pad_session_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/impl/gpio_pad_session_impl.hpp new file mode 100644 index 00000000..752adff2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/driver/impl/gpio_pad_session_impl.hpp @@ -0,0 +1,90 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/gpio/gpio_types.hpp> +#include <stratosphere/gpio/driver/gpio_pad_accessor.hpp> +#include <stratosphere/gpio/driver/impl/gpio_event_holder.hpp> +#include <stratosphere/ddsf.hpp> + +namespace ams::gpio::driver { + + class Pad; + +} + +namespace ams::gpio::driver::impl { + + class PadSessionImpl : public ::ams::ddsf::ISession { + NON_COPYABLE(PadSessionImpl); + NON_MOVEABLE(PadSessionImpl); + AMS_DDSF_CASTABLE_TRAITS(ams::gpio::driver::impl::PadSessionImpl, ::ams::ddsf::ISession); + private: + EventHolder m_event_holder; + private: + Result UpdateDriverInterruptEnabled(); + public: + PadSessionImpl() : m_event_holder() { /* ... */ } + + ~PadSessionImpl() { + this->Close(); + } + + bool IsInterruptBound() const { + return m_event_holder.IsBound(); + } + + Result Open(Pad *pad, ddsf::AccessMode access_mode); + void Close(); + + Result BindInterrupt(os::SystemEventType *event); + void UnbindInterrupt(); + + Result GetInterruptEnabled(bool *out) const; + Result SetInterruptEnabled(bool en); + void SignalInterruptBoundEvent(); + }; + static_assert( sizeof(PadSessionImpl) <= GpioPadSessionSize); + static_assert(alignof(PadSessionImpl) <= GpioPadSessionAlign); + + struct alignas(GpioPadSessionAlign) GpioPadSessionImplPadded { + PadSessionImpl _impl; + u8 _padding[GpioPadSessionSize - sizeof(PadSessionImpl)]; + }; + static_assert( sizeof(GpioPadSessionImplPadded) == GpioPadSessionSize); + static_assert(alignof(GpioPadSessionImplPadded) == GpioPadSessionAlign); + + ALWAYS_INLINE PadSessionImpl &GetPadSessionImpl(GpioPadSession &session) { + return GetReference(session._impl)._impl; + } + + ALWAYS_INLINE const PadSessionImpl &GetPadSessionImpl(const GpioPadSession &session) { + return GetReference(session._impl)._impl; + } + + ALWAYS_INLINE PadSessionImpl &GetOpenPadSessionImpl(GpioPadSession &session) { + auto &ref = GetReference(session._impl)._impl; + AMS_ASSERT(ref.IsOpen()); + return ref; + } + + ALWAYS_INLINE const PadSessionImpl &GetOpenPadSessionImpl(const GpioPadSession &session) { + const auto &ref = GetReference(session._impl)._impl; + AMS_ASSERT(ref.IsOpen()); + return ref; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/gpio_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/gpio_api.hpp new file mode 100644 index 00000000..dc76ab9f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/gpio_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/gpio/gpio_types.hpp> +#include <stratosphere/gpio/sf/gpio_sf_i_manager.hpp> + +namespace ams::gpio { + + void Initialize(); + void Finalize(); + + void InitializeWith(ams::sf::SharedPointer<gpio::sf::IManager> sp); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/gpio_pad_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/gpio_pad_api.hpp new file mode 100644 index 00000000..38cf767c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/gpio_pad_api.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/ddsf/ddsf_types.hpp> +#include <stratosphere/gpio/gpio_types.hpp> +#include <stratosphere/gpio/gpio_select_pad_name.hpp> +#include <stratosphere/gpio/driver/gpio_pad.hpp> + +namespace ams::gpio { + + struct GpioPadSession { + void *_session; + os::SystemEventType *_event; + }; + + Result OpenSession(GpioPadSession *out_session, ams::DeviceCode device_code); + void CloseSession(GpioPadSession *session); + + Direction GetDirection(GpioPadSession *session); + void SetDirection(GpioPadSession *session, Direction direction); + + GpioValue GetValue(GpioPadSession *session); + void SetValue(GpioPadSession *session, GpioValue value); + + InterruptMode GetInterruptMode(GpioPadSession *session); + void SetInterruptMode(GpioPadSession *session, InterruptMode mode); + + bool GetInterruptEnable(GpioPadSession *session); + void SetInterruptEnable(GpioPadSession *session, bool en); + + InterruptStatus GetInterruptStatus(GpioPadSession *session); + void ClearInterruptStatus(GpioPadSession *session); + + int GetDebounceTime(GpioPadSession *session); + void SetDebounceTime(GpioPadSession *session, int ms); + + bool GetDebounceEnabled(GpioPadSession *session); + void SetDebounceEnabled(GpioPadSession *session, bool en); + + Result BindInterrupt(os::SystemEventType *event, GpioPadSession *session); + void UnbindInterrupt(GpioPadSession *session); + + Result IsWakeEventActive(bool *out_is_active, ams::DeviceCode device_code); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/gpio_pad_name.board.nintendo_nx.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/gpio_pad_name.board.nintendo_nx.hpp new file mode 100644 index 00000000..60b542e2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/gpio_pad_name.board.nintendo_nx.hpp @@ -0,0 +1,416 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/gpio/gpio_types.hpp> + +namespace ams::gpio { + + enum GpioPadName : u32 { + GpioPadName_CodecLdoEnTemp = 1, + GpioPadName_PowSdEn = 2, + GpioPadName_BtRst = 3, + GpioPadName_RamCode3 = 4, + GpioPadName_GameCardReset = 5, + GpioPadName_CodecAlert = 6, + GpioPadName_PowGc = 7, + GpioPadName_DebugControllerDet = 8, + GpioPadName_BattChgStatus = 9, + GpioPadName_BattChgEnableN = 10, + GpioPadName_FanTach = 11, + GpioPadName_ExtconDetS = 12, + GpioPadName_Vdd50AEn = 13, + GpioPadName_SdevCoaxSel1 = 14, + GpioPadName_GameCardCd = 15, + GpioPadName_ProdType0 = 16, + GpioPadName_ProdType1 = 17, + GpioPadName_ProdType2 = 18, + GpioPadName_ProdType3 = 19, + GpioPadName_TempAlert = 20, + GpioPadName_CodecHpDetIrq = 21, + GpioPadName_MotionInt = 22, + GpioPadName_TpIrq = 23, + GpioPadName_ButtonSleep2 = 24, + GpioPadName_ButtonVolUp = 25, + GpioPadName_ButtonVolDn = 26, + GpioPadName_BattMgicIrq = 27, + GpioPadName_RecoveryKey = 28, + GpioPadName_PowLcdBlEn = 29, + GpioPadName_LcdReset = 30, + GpioPadName_PdVconnEn = 31, + GpioPadName_PdRstN = 32, + GpioPadName_Bq24190Irq = 33, + GpioPadName_SdevCoaxSel0 = 34, + GpioPadName_SdWp = 35, + GpioPadName_TpReset = 36, + GpioPadName_BtGpio2 = 37, + GpioPadName_BtGpio3 = 38, + GpioPadName_BtGpio4 = 39, + GpioPadName_CradleIrq = 40, + GpioPadName_PowVcpuInt = 41, + GpioPadName_Max77621GpuInt = 42, + GpioPadName_ExtconChgU = 43, + GpioPadName_ExtconChgS = 44, + GpioPadName_WifiRfDisable = 45, + GpioPadName_WifiReset = 46, + GpioPadName_ApWakeBt = 47, + GpioPadName_BtWakeAp = 48, + GpioPadName_BtGpio5 = 49, + GpioPadName_PowLcdVddPEn = 50, + GpioPadName_PowLcdVddNEn = 51, + GpioPadName_ExtconDetU = 52, + GpioPadName_RamCode2 = 53, + GpioPadName_Vdd50BEn = 54, + GpioPadName_WifiWakeHost = 55, + GpioPadName_SdCd = 56, + GpioPadName_OtgFet1ForSdev = 57, + GpioPadName_OtgFet2ForSdev = 58, + GpioPadName_ExtConWakeU = 59, + GpioPadName_ExtConWakeS = 60, + GpioPadName_PmuIrq = 61, + GpioPadName_ExtUart2Cts = 62, + GpioPadName_ExtUart3Cts = 63, + GpioPadName_5VStepDownEn = 64, + GpioPadName_UsbSwitchB2Oc = 65, + GpioPadName_5VStepDownPg = 66, + GpioPadName_UsbSwitchAEn = 67, + GpioPadName_UsbSwitchAFlag = 68, + GpioPadName_UsbSwitchB3Oc = 69, + GpioPadName_UsbSwitchB3En = 70, + GpioPadName_UsbSwitchB2En = 71, + GpioPadName_Hdmi5VEn = 72, + GpioPadName_UsbSwitchB1En = 73, + GpioPadName_HdmiPdTrEn = 74, + GpioPadName_FanEn = 75, + GpioPadName_UsbSwitchB1Oc = 76, + GpioPadName_PwmFan = 77, + GpioPadName_HdmiHpd = 78, + GpioPadName_Max77812Irq = 79, + GpioPadName_Debug0 = 80, + GpioPadName_Debug1 = 81, + GpioPadName_Debug2 = 82, + GpioPadName_Debug3 = 83, + GpioPadName_NfcIrq = 84, + GpioPadName_NfcRst = 85, + GpioPadName_McuIrq = 86, + GpioPadName_McuBoot = 87, + GpioPadName_McuRst = 88, + GpioPadName_Vdd5V3En = 89, + GpioPadName_McuPor = 90, + GpioPadName_LcdGpio1 = 91, + GpioPadName_NfcEn = 92, + }; + + constexpr inline const DeviceCode DeviceCode_CodecLdoEnTemp = 0x33000002; + constexpr inline const DeviceCode DeviceCode_PowSdEn = 0x3C000001; + constexpr inline const DeviceCode DeviceCode_BtRst = 0x37000002; + constexpr inline const DeviceCode DeviceCode_RamCode3 = 0xC9000402; + constexpr inline const DeviceCode DeviceCode_GameCardReset = 0x3C000402; + constexpr inline const DeviceCode DeviceCode_CodecAlert = 0x33000003; + constexpr inline const DeviceCode DeviceCode_PowGc = 0x3C000401; + constexpr inline const DeviceCode DeviceCode_DebugControllerDet = 0x350000CA; + constexpr inline const DeviceCode DeviceCode_BattChgStatus = 0x39000407; + constexpr inline const DeviceCode DeviceCode_BattChgEnableN = 0x39000003; + constexpr inline const DeviceCode DeviceCode_FanTach = 0x3D000002; + constexpr inline const DeviceCode DeviceCode_ExtconDetS = 0x3500040B; + constexpr inline const DeviceCode DeviceCode_Vdd50AEn = 0x39000401; + constexpr inline const DeviceCode DeviceCode_SdevCoaxSel1 = 0xCA000406; + constexpr inline const DeviceCode DeviceCode_GameCardCd = 0x3C000403; + constexpr inline const DeviceCode DeviceCode_ProdType0 = 0xC900040B; + constexpr inline const DeviceCode DeviceCode_ProdType1 = 0xC900040C; + constexpr inline const DeviceCode DeviceCode_ProdType2 = 0xC900040D; + constexpr inline const DeviceCode DeviceCode_ProdType3 = 0xC900040E; + constexpr inline const DeviceCode DeviceCode_TempAlert = 0x3E000002; + constexpr inline const DeviceCode DeviceCode_CodecHpDetIrq = 0x33000004; + constexpr inline const DeviceCode DeviceCode_MotionInt = 0x35000041; + constexpr inline const DeviceCode DeviceCode_TpIrq = 0x35000036; + constexpr inline const DeviceCode DeviceCode_ButtonSleep2 = 0x35000001; + constexpr inline const DeviceCode DeviceCode_ButtonVolUp = 0x35000002; + constexpr inline const DeviceCode DeviceCode_ButtonVolDn = 0x35000003; + constexpr inline const DeviceCode DeviceCode_BattMgicIrq = 0x39000034; + constexpr inline const DeviceCode DeviceCode_RecoveryKey = 0x35000004; + constexpr inline const DeviceCode DeviceCode_PowLcdBlEn = 0x3400003E; + constexpr inline const DeviceCode DeviceCode_LcdReset = 0x34000033; + constexpr inline const DeviceCode DeviceCode_PdVconnEn = 0x040000CC; + constexpr inline const DeviceCode DeviceCode_PdRstN = 0x040000CA; + constexpr inline const DeviceCode DeviceCode_Bq24190Irq = 0x39000002; + constexpr inline const DeviceCode DeviceCode_SdevCoaxSel0 = 0xCA000405; + constexpr inline const DeviceCode DeviceCode_SdWp = 0x3C000003; + constexpr inline const DeviceCode DeviceCode_TpReset = 0x35000035; + constexpr inline const DeviceCode DeviceCode_BtGpio2 = 0x37000401; + constexpr inline const DeviceCode DeviceCode_BtGpio3 = 0x37000402; + constexpr inline const DeviceCode DeviceCode_BtGpio4 = 0x37000403; + constexpr inline const DeviceCode DeviceCode_CradleIrq = 0x040000CB; + constexpr inline const DeviceCode DeviceCode_PowVcpuInt = 0x3E000003; + constexpr inline const DeviceCode DeviceCode_Max77621GpuInt = 0x3E000004; + constexpr inline const DeviceCode DeviceCode_ExtconChgU = 0x35000402; + constexpr inline const DeviceCode DeviceCode_ExtconChgS = 0x3500040C; + constexpr inline const DeviceCode DeviceCode_WifiRfDisable = 0x38000003; + constexpr inline const DeviceCode DeviceCode_WifiReset = 0x38000002; + constexpr inline const DeviceCode DeviceCode_ApWakeBt = 0x37000003; + constexpr inline const DeviceCode DeviceCode_BtWakeAp = 0x37000004; + constexpr inline const DeviceCode DeviceCode_BtGpio5 = 0x37000404; + constexpr inline const DeviceCode DeviceCode_PowLcdVddPEn = 0x34000034; + constexpr inline const DeviceCode DeviceCode_PowLcdVddNEn = 0x34000035; + constexpr inline const DeviceCode DeviceCode_ExtconDetU = 0x35000401; + constexpr inline const DeviceCode DeviceCode_RamCode2 = 0xC9000401; + constexpr inline const DeviceCode DeviceCode_Vdd50BEn = 0x39000402; + constexpr inline const DeviceCode DeviceCode_WifiWakeHost = 0x38000004; + constexpr inline const DeviceCode DeviceCode_SdCd = 0x3C000002; + constexpr inline const DeviceCode DeviceCode_OtgFet1ForSdev = 0x39000404; + constexpr inline const DeviceCode DeviceCode_OtgFet2ForSdev = 0x39000405; + constexpr inline const DeviceCode DeviceCode_ExtConWakeU = 0x35000403; + constexpr inline const DeviceCode DeviceCode_ExtConWakeS = 0x3500040D; + constexpr inline const DeviceCode DeviceCode_PmuIrq = 0x39000406; + constexpr inline const DeviceCode DeviceCode_ExtUart2Cts = 0x35000404; + constexpr inline const DeviceCode DeviceCode_ExtUart3Cts = 0x3500040E; + constexpr inline const DeviceCode DeviceCode_5VStepDownEn = 0x39000408; + constexpr inline const DeviceCode DeviceCode_UsbSwitchB2Oc = 0x04000401; + constexpr inline const DeviceCode DeviceCode_5VStepDownPg = 0x39000409; + constexpr inline const DeviceCode DeviceCode_UsbSwitchAEn = 0x04000402; + constexpr inline const DeviceCode DeviceCode_UsbSwitchAFlag = 0x04000403; + constexpr inline const DeviceCode DeviceCode_UsbSwitchB3Oc = 0x04000404; + constexpr inline const DeviceCode DeviceCode_UsbSwitchB3En = 0x04000405; + constexpr inline const DeviceCode DeviceCode_UsbSwitchB2En = 0x04000406; + constexpr inline const DeviceCode DeviceCode_Hdmi5VEn = 0x34000004; + constexpr inline const DeviceCode DeviceCode_UsbSwitchB1En = 0x04000407; + constexpr inline const DeviceCode DeviceCode_HdmiPdTrEn = 0x34000005; + constexpr inline const DeviceCode DeviceCode_FanEn = 0x3D000003; + constexpr inline const DeviceCode DeviceCode_UsbSwitchB1Oc = 0x04000408; + constexpr inline const DeviceCode DeviceCode_PwmFan = 0x3D000001; + constexpr inline const DeviceCode DeviceCode_HdmiHpd = 0x34000006; + constexpr inline const DeviceCode DeviceCode_Max77812Irq = 0x3E000003; + constexpr inline const DeviceCode DeviceCode_Debug0 = 0xCA000001; + constexpr inline const DeviceCode DeviceCode_Debug1 = 0xCA000002; + constexpr inline const DeviceCode DeviceCode_Debug2 = 0xCA000003; + constexpr inline const DeviceCode DeviceCode_Debug3 = 0xCA000004; + constexpr inline const DeviceCode DeviceCode_NfcIrq = 0x36000004; + constexpr inline const DeviceCode DeviceCode_NfcRst = 0x36000003; + constexpr inline const DeviceCode DeviceCode_McuIrq = 0x35000415; + constexpr inline const DeviceCode DeviceCode_McuBoot = 0x35000416; + constexpr inline const DeviceCode DeviceCode_McuRst = 0x35000417; + constexpr inline const DeviceCode DeviceCode_Vdd5V3En = 0x39000403; + constexpr inline const DeviceCode DeviceCode_McuPor = 0x35000418; + constexpr inline const DeviceCode DeviceCode_LcdGpio1 = 0x35000005; + constexpr inline const DeviceCode DeviceCode_NfcEn = 0x36000002; + constexpr inline const DeviceCode DeviceCode_ExtUart2Rts = 0x35000406; + constexpr inline const DeviceCode DeviceCode_ExtUart3Rts = 0x35000410; + constexpr inline const DeviceCode DeviceCode_GpioPortC7 = 0x3500041B; + constexpr inline const DeviceCode DeviceCode_GpioPortD0 = 0x3500041C; + constexpr inline const DeviceCode DeviceCode_GpioPortC5 = 0x3500041D; + constexpr inline const DeviceCode DeviceCode_GpioPortC6 = 0x3500041E; + constexpr inline const DeviceCode DeviceCode_GpioPortY7 = 0x35000065; + constexpr inline const DeviceCode DeviceCode_GpioPortF1 = 0x04000409; + constexpr inline const DeviceCode DeviceCode_GpioPortH0 = 0x34000401; + constexpr inline const DeviceCode DeviceCode_GpioPortI6 = 0x37000405; + + constexpr inline GpioPadName ConvertToGpioPadName(DeviceCode dc) { + switch (dc.GetInternalValue()) { + case DeviceCode_CodecLdoEnTemp .GetInternalValue(): return GpioPadName_CodecLdoEnTemp; + case DeviceCode_PowSdEn .GetInternalValue(): return GpioPadName_PowSdEn; + case DeviceCode_BtRst .GetInternalValue(): return GpioPadName_BtRst; + case DeviceCode_RamCode3 .GetInternalValue(): return GpioPadName_RamCode3; + case DeviceCode_GameCardReset .GetInternalValue(): return GpioPadName_GameCardReset; + case DeviceCode_CodecAlert .GetInternalValue(): return GpioPadName_CodecAlert; + case DeviceCode_PowGc .GetInternalValue(): return GpioPadName_PowGc; + case DeviceCode_DebugControllerDet.GetInternalValue(): return GpioPadName_DebugControllerDet; + case DeviceCode_BattChgStatus .GetInternalValue(): return GpioPadName_BattChgStatus; + case DeviceCode_BattChgEnableN .GetInternalValue(): return GpioPadName_BattChgEnableN; + case DeviceCode_FanTach .GetInternalValue(): return GpioPadName_FanTach; + case DeviceCode_ExtconDetS .GetInternalValue(): return GpioPadName_ExtconDetS; + case DeviceCode_Vdd50AEn .GetInternalValue(): return GpioPadName_Vdd50AEn; + case DeviceCode_SdevCoaxSel1 .GetInternalValue(): return GpioPadName_SdevCoaxSel1; + case DeviceCode_GameCardCd .GetInternalValue(): return GpioPadName_GameCardCd; + case DeviceCode_ProdType0 .GetInternalValue(): return GpioPadName_ProdType0; + case DeviceCode_ProdType1 .GetInternalValue(): return GpioPadName_ProdType1; + case DeviceCode_ProdType2 .GetInternalValue(): return GpioPadName_ProdType2; + case DeviceCode_ProdType3 .GetInternalValue(): return GpioPadName_ProdType3; + case DeviceCode_TempAlert .GetInternalValue(): return GpioPadName_TempAlert; + case DeviceCode_CodecHpDetIrq .GetInternalValue(): return GpioPadName_CodecHpDetIrq; + case DeviceCode_MotionInt .GetInternalValue(): return GpioPadName_MotionInt; + case DeviceCode_TpIrq .GetInternalValue(): return GpioPadName_TpIrq; + case DeviceCode_ButtonSleep2 .GetInternalValue(): return GpioPadName_ButtonSleep2; + case DeviceCode_ButtonVolUp .GetInternalValue(): return GpioPadName_ButtonVolUp; + case DeviceCode_ButtonVolDn .GetInternalValue(): return GpioPadName_ButtonVolDn; + case DeviceCode_BattMgicIrq .GetInternalValue(): return GpioPadName_BattMgicIrq; + case DeviceCode_RecoveryKey .GetInternalValue(): return GpioPadName_RecoveryKey; + case DeviceCode_PowLcdBlEn .GetInternalValue(): return GpioPadName_PowLcdBlEn; + case DeviceCode_LcdReset .GetInternalValue(): return GpioPadName_LcdReset; + case DeviceCode_PdVconnEn .GetInternalValue(): return GpioPadName_PdVconnEn; + case DeviceCode_PdRstN .GetInternalValue(): return GpioPadName_PdRstN; + case DeviceCode_Bq24190Irq .GetInternalValue(): return GpioPadName_Bq24190Irq; + case DeviceCode_SdevCoaxSel0 .GetInternalValue(): return GpioPadName_SdevCoaxSel0; + case DeviceCode_SdWp .GetInternalValue(): return GpioPadName_SdWp; + case DeviceCode_TpReset .GetInternalValue(): return GpioPadName_TpReset; + case DeviceCode_BtGpio2 .GetInternalValue(): return GpioPadName_BtGpio2; + case DeviceCode_BtGpio3 .GetInternalValue(): return GpioPadName_BtGpio3; + case DeviceCode_BtGpio4 .GetInternalValue(): return GpioPadName_BtGpio4; + case DeviceCode_CradleIrq .GetInternalValue(): return GpioPadName_CradleIrq; + /* case DeviceCode_PowVcpuInt .GetInternalValue(): return GpioPadName_PowVcpuInt; */ + case DeviceCode_Max77621GpuInt .GetInternalValue(): return GpioPadName_Max77621GpuInt; + case DeviceCode_ExtconChgU .GetInternalValue(): return GpioPadName_ExtconChgU; + case DeviceCode_ExtconChgS .GetInternalValue(): return GpioPadName_ExtconChgS; + case DeviceCode_WifiRfDisable .GetInternalValue(): return GpioPadName_WifiRfDisable; + case DeviceCode_WifiReset .GetInternalValue(): return GpioPadName_WifiReset; + case DeviceCode_ApWakeBt .GetInternalValue(): return GpioPadName_ApWakeBt; + case DeviceCode_BtWakeAp .GetInternalValue(): return GpioPadName_BtWakeAp; + case DeviceCode_BtGpio5 .GetInternalValue(): return GpioPadName_BtGpio5; + case DeviceCode_PowLcdVddPEn .GetInternalValue(): return GpioPadName_PowLcdVddPEn; + case DeviceCode_PowLcdVddNEn .GetInternalValue(): return GpioPadName_PowLcdVddNEn; + case DeviceCode_ExtconDetU .GetInternalValue(): return GpioPadName_ExtconDetU; + case DeviceCode_RamCode2 .GetInternalValue(): return GpioPadName_RamCode2; + case DeviceCode_Vdd50BEn .GetInternalValue(): return GpioPadName_Vdd50BEn; + case DeviceCode_WifiWakeHost .GetInternalValue(): return GpioPadName_WifiWakeHost; + case DeviceCode_SdCd .GetInternalValue(): return GpioPadName_SdCd; + case DeviceCode_OtgFet1ForSdev .GetInternalValue(): return GpioPadName_OtgFet1ForSdev; + case DeviceCode_OtgFet2ForSdev .GetInternalValue(): return GpioPadName_OtgFet2ForSdev; + case DeviceCode_ExtConWakeU .GetInternalValue(): return GpioPadName_ExtConWakeU; + case DeviceCode_ExtConWakeS .GetInternalValue(): return GpioPadName_ExtConWakeS; + case DeviceCode_PmuIrq .GetInternalValue(): return GpioPadName_PmuIrq; + case DeviceCode_ExtUart2Cts .GetInternalValue(): return GpioPadName_ExtUart2Cts; + case DeviceCode_ExtUart3Cts .GetInternalValue(): return GpioPadName_ExtUart3Cts; + case DeviceCode_5VStepDownEn .GetInternalValue(): return GpioPadName_5VStepDownEn; + case DeviceCode_UsbSwitchB2Oc .GetInternalValue(): return GpioPadName_UsbSwitchB2Oc; + case DeviceCode_5VStepDownPg .GetInternalValue(): return GpioPadName_5VStepDownPg; + case DeviceCode_UsbSwitchAEn .GetInternalValue(): return GpioPadName_UsbSwitchAEn; + case DeviceCode_UsbSwitchAFlag .GetInternalValue(): return GpioPadName_UsbSwitchAFlag; + case DeviceCode_UsbSwitchB3Oc .GetInternalValue(): return GpioPadName_UsbSwitchB3Oc; + case DeviceCode_UsbSwitchB3En .GetInternalValue(): return GpioPadName_UsbSwitchB3En; + case DeviceCode_UsbSwitchB2En .GetInternalValue(): return GpioPadName_UsbSwitchB2En; + case DeviceCode_Hdmi5VEn .GetInternalValue(): return GpioPadName_Hdmi5VEn; + case DeviceCode_UsbSwitchB1En .GetInternalValue(): return GpioPadName_UsbSwitchB1En; + case DeviceCode_HdmiPdTrEn .GetInternalValue(): return GpioPadName_HdmiPdTrEn; + case DeviceCode_FanEn .GetInternalValue(): return GpioPadName_FanEn; + case DeviceCode_UsbSwitchB1Oc .GetInternalValue(): return GpioPadName_UsbSwitchB1Oc; + case DeviceCode_PwmFan .GetInternalValue(): return GpioPadName_PwmFan; + case DeviceCode_HdmiHpd .GetInternalValue(): return GpioPadName_HdmiHpd; + case DeviceCode_Max77812Irq .GetInternalValue(): return GpioPadName_Max77812Irq; + case DeviceCode_Debug0 .GetInternalValue(): return GpioPadName_Debug0; + case DeviceCode_Debug1 .GetInternalValue(): return GpioPadName_Debug1; + case DeviceCode_Debug2 .GetInternalValue(): return GpioPadName_Debug2; + case DeviceCode_Debug3 .GetInternalValue(): return GpioPadName_Debug3; + case DeviceCode_NfcIrq .GetInternalValue(): return GpioPadName_NfcIrq; + case DeviceCode_NfcRst .GetInternalValue(): return GpioPadName_NfcRst; + case DeviceCode_McuIrq .GetInternalValue(): return GpioPadName_McuIrq; + case DeviceCode_McuBoot .GetInternalValue(): return GpioPadName_McuBoot; + case DeviceCode_McuRst .GetInternalValue(): return GpioPadName_McuRst; + case DeviceCode_Vdd5V3En .GetInternalValue(): return GpioPadName_Vdd5V3En; + case DeviceCode_McuPor .GetInternalValue(): return GpioPadName_McuPor; + case DeviceCode_LcdGpio1 .GetInternalValue(): return GpioPadName_LcdGpio1; + case DeviceCode_NfcEn .GetInternalValue(): return GpioPadName_NfcEn; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + constexpr inline DeviceCode ConvertToDeviceCode(GpioPadName gpn) { + switch (gpn) { + case GpioPadName_CodecLdoEnTemp: return DeviceCode_CodecLdoEnTemp; + case GpioPadName_PowSdEn: return DeviceCode_PowSdEn; + case GpioPadName_BtRst: return DeviceCode_BtRst; + case GpioPadName_RamCode3: return DeviceCode_RamCode3; + case GpioPadName_GameCardReset: return DeviceCode_GameCardReset; + case GpioPadName_CodecAlert: return DeviceCode_CodecAlert; + case GpioPadName_PowGc: return DeviceCode_PowGc; + case GpioPadName_DebugControllerDet: return DeviceCode_DebugControllerDet; + case GpioPadName_BattChgStatus: return DeviceCode_BattChgStatus; + case GpioPadName_BattChgEnableN: return DeviceCode_BattChgEnableN; + case GpioPadName_FanTach: return DeviceCode_FanTach; + case GpioPadName_ExtconDetS: return DeviceCode_ExtconDetS; + case GpioPadName_Vdd50AEn: return DeviceCode_Vdd50AEn; + case GpioPadName_SdevCoaxSel1: return DeviceCode_SdevCoaxSel1; + case GpioPadName_GameCardCd: return DeviceCode_GameCardCd; + case GpioPadName_ProdType0: return DeviceCode_ProdType0; + case GpioPadName_ProdType1: return DeviceCode_ProdType1; + case GpioPadName_ProdType2: return DeviceCode_ProdType2; + case GpioPadName_ProdType3: return DeviceCode_ProdType3; + case GpioPadName_TempAlert: return DeviceCode_TempAlert; + case GpioPadName_CodecHpDetIrq: return DeviceCode_CodecHpDetIrq; + case GpioPadName_MotionInt: return DeviceCode_MotionInt; + case GpioPadName_TpIrq: return DeviceCode_TpIrq; + case GpioPadName_ButtonSleep2: return DeviceCode_ButtonSleep2; + case GpioPadName_ButtonVolUp: return DeviceCode_ButtonVolUp; + case GpioPadName_ButtonVolDn: return DeviceCode_ButtonVolDn; + case GpioPadName_BattMgicIrq: return DeviceCode_BattMgicIrq; + case GpioPadName_RecoveryKey: return DeviceCode_RecoveryKey; + case GpioPadName_PowLcdBlEn: return DeviceCode_PowLcdBlEn; + case GpioPadName_LcdReset: return DeviceCode_LcdReset; + case GpioPadName_PdVconnEn: return DeviceCode_PdVconnEn; + case GpioPadName_PdRstN: return DeviceCode_PdRstN; + case GpioPadName_Bq24190Irq: return DeviceCode_Bq24190Irq; + case GpioPadName_SdevCoaxSel0: return DeviceCode_SdevCoaxSel0; + case GpioPadName_SdWp: return DeviceCode_SdWp; + case GpioPadName_TpReset: return DeviceCode_TpReset; + case GpioPadName_BtGpio2: return DeviceCode_BtGpio2; + case GpioPadName_BtGpio3: return DeviceCode_BtGpio3; + case GpioPadName_BtGpio4: return DeviceCode_BtGpio4; + case GpioPadName_CradleIrq: return DeviceCode_CradleIrq; + case GpioPadName_PowVcpuInt: return DeviceCode_PowVcpuInt; + case GpioPadName_Max77621GpuInt: return DeviceCode_Max77621GpuInt; + case GpioPadName_ExtconChgU: return DeviceCode_ExtconChgU; + case GpioPadName_ExtconChgS: return DeviceCode_ExtconChgS; + case GpioPadName_WifiRfDisable: return DeviceCode_WifiRfDisable; + case GpioPadName_WifiReset: return DeviceCode_WifiReset; + case GpioPadName_ApWakeBt: return DeviceCode_ApWakeBt; + case GpioPadName_BtWakeAp: return DeviceCode_BtWakeAp; + case GpioPadName_BtGpio5: return DeviceCode_BtGpio5; + case GpioPadName_PowLcdVddPEn: return DeviceCode_PowLcdVddPEn; + case GpioPadName_PowLcdVddNEn: return DeviceCode_PowLcdVddNEn; + case GpioPadName_ExtconDetU: return DeviceCode_ExtconDetU; + case GpioPadName_RamCode2: return DeviceCode_RamCode2; + case GpioPadName_Vdd50BEn: return DeviceCode_Vdd50BEn; + case GpioPadName_WifiWakeHost: return DeviceCode_WifiWakeHost; + case GpioPadName_SdCd: return DeviceCode_SdCd; + case GpioPadName_OtgFet1ForSdev: return DeviceCode_OtgFet1ForSdev; + case GpioPadName_OtgFet2ForSdev: return DeviceCode_OtgFet2ForSdev; + case GpioPadName_ExtConWakeU: return DeviceCode_ExtConWakeU; + case GpioPadName_ExtConWakeS: return DeviceCode_ExtConWakeS; + case GpioPadName_PmuIrq: return DeviceCode_PmuIrq; + case GpioPadName_ExtUart2Cts: return DeviceCode_ExtUart2Cts; + case GpioPadName_ExtUart3Cts: return DeviceCode_ExtUart3Cts; + case GpioPadName_5VStepDownEn: return DeviceCode_5VStepDownEn; + case GpioPadName_UsbSwitchB2Oc: return DeviceCode_UsbSwitchB2Oc; + case GpioPadName_5VStepDownPg: return DeviceCode_5VStepDownPg; + case GpioPadName_UsbSwitchAEn: return DeviceCode_UsbSwitchAEn; + case GpioPadName_UsbSwitchAFlag: return DeviceCode_UsbSwitchAFlag; + case GpioPadName_UsbSwitchB3Oc: return DeviceCode_UsbSwitchB3Oc; + case GpioPadName_UsbSwitchB3En: return DeviceCode_UsbSwitchB3En; + case GpioPadName_UsbSwitchB2En: return DeviceCode_UsbSwitchB2En; + case GpioPadName_Hdmi5VEn: return DeviceCode_Hdmi5VEn; + case GpioPadName_UsbSwitchB1En: return DeviceCode_UsbSwitchB1En; + case GpioPadName_HdmiPdTrEn: return DeviceCode_HdmiPdTrEn; + case GpioPadName_FanEn: return DeviceCode_FanEn; + case GpioPadName_UsbSwitchB1Oc: return DeviceCode_UsbSwitchB1Oc; + case GpioPadName_PwmFan: return DeviceCode_PwmFan; + case GpioPadName_HdmiHpd: return DeviceCode_HdmiHpd; + case GpioPadName_Max77812Irq: return DeviceCode_Max77812Irq; + case GpioPadName_Debug0: return DeviceCode_Debug0; + case GpioPadName_Debug1: return DeviceCode_Debug1; + case GpioPadName_Debug2: return DeviceCode_Debug2; + case GpioPadName_Debug3: return DeviceCode_Debug3; + case GpioPadName_NfcIrq: return DeviceCode_NfcIrq; + case GpioPadName_NfcRst: return DeviceCode_NfcRst; + case GpioPadName_McuIrq: return DeviceCode_McuIrq; + case GpioPadName_McuBoot: return DeviceCode_McuBoot; + case GpioPadName_McuRst: return DeviceCode_McuRst; + case GpioPadName_Vdd5V3En: return DeviceCode_Vdd5V3En; + case GpioPadName_McuPor: return DeviceCode_McuPor; + case GpioPadName_LcdGpio1: return DeviceCode_LcdGpio1; + case GpioPadName_NfcEn: return DeviceCode_NfcEn; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/gpio_pad_name.generic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/gpio_pad_name.generic.hpp new file mode 100644 index 00000000..03347e9f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/gpio_pad_name.generic.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/gpio/gpio_types.hpp> + +namespace ams::gpio { + + enum GpioPadName : u32 { + GpioPadName_EnableSdCardPower = 2, + }; + constexpr inline const DeviceCode DeviceCode_EnableSdCardPower = 0x3C000001; + + constexpr inline GpioPadName ConvertToGpioPadName(DeviceCode dc) { + switch (dc.GetInternalValue()) { + case DeviceCode_EnableSdCardPower.GetInternalValue(): return GpioPadName_EnableSdCardPower; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + constexpr inline DeviceCode ConvertToDeviceCode(GpioPadName gpn) { + switch (gpn) { + case GpioPadName_EnableSdCardPower: return DeviceCode_EnableSdCardPower; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/gpio_select_pad_name.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/gpio_select_pad_name.hpp new file mode 100644 index 00000000..6cd60806 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/gpio_select_pad_name.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/gpio/gpio_types.hpp> + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + #include <stratosphere/gpio/gpio_pad_name.board.nintendo_nx.hpp> +#else + #include <stratosphere/gpio/gpio_pad_name.generic.hpp> +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/gpio_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/gpio_types.hpp new file mode 100644 index 00000000..5545b739 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/gpio_types.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::gpio { + + enum InterruptMode : u32 { + InterruptMode_LowLevel = 0, + InterruptMode_HighLevel = 1, + InterruptMode_RisingEdge = 2, + InterruptMode_FallingEdge = 3, + InterruptMode_AnyEdge = 4, + }; + + enum Direction : u32 { + Direction_Input = 0, + Direction_Output = 1, + }; + + enum GpioValue : u32 { + GpioValue_Low = 0, + GpioValue_High = 1, + }; + + enum InterruptStatus : u32 { + InterruptStatus_Inactive = 0, + InterruptStatus_Active = 1, + }; + + enum WakePinDebugMode { + WakePinDebugMode_AutoImmediateWake = 1, + WakePinDebugMode_NoWake = 2, + }; + + using WakeBitFlag = util::BitFlagSet<128>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/server/gpio_server_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/server/gpio_server_api.hpp new file mode 100644 index 00000000..b456b42e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/server/gpio_server_api.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/gpio/gpio_types.hpp> +#include <stratosphere/gpio/sf/gpio_sf_i_manager.hpp> + +namespace ams::gpio::server { + + ams::sf::SharedPointer<gpio::sf::IManager> GetServiceObject(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/sf/gpio_sf_i_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/sf/gpio_sf_i_manager.hpp new file mode 100644 index 00000000..077885fc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/sf/gpio_sf_i_manager.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/ddsf/ddsf_types.hpp> +#include <stratosphere/gpio/gpio_types.hpp> +#include <stratosphere/gpio/gpio_select_pad_name.hpp> +#include <stratosphere/gpio/sf/gpio_sf_i_pad_session.hpp> + +#define AMS_GPIO_I_MANAGER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, OpenSessionForDev, (ams::sf::Out<ams::sf::SharedPointer<gpio::sf::IPadSession>> out, s32 pad_descriptor), (out, pad_descriptor) ) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, OpenSession, (ams::sf::Out<ams::sf::SharedPointer<gpio::sf::IPadSession>> out, gpio::GpioPadName pad_name), (out, pad_name) ) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, OpenSessionForTest, (ams::sf::Out<ams::sf::SharedPointer<gpio::sf::IPadSession>> out, gpio::GpioPadName pad_name), (out, pad_name) ) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, IsWakeEventActive, (ams::sf::Out<bool> out, gpio::GpioPadName pad_name), (out, pad_name), hos::Version_Min, hos::Version_6_2_0) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, GetWakeEventActiveFlagSet, (ams::sf::Out<gpio::WakeBitFlag> out), (out), hos::Version_Min, hos::Version_6_2_0) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, SetWakeEventActiveFlagSetForDebug, (gpio::GpioPadName pad_name, bool is_enabled), (pad_name, is_enabled), hos::Version_Min, hos::Version_6_2_0) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, SetWakePinDebugMode, (s32 mode), (mode) ) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, OpenSession2, (ams::sf::Out<ams::sf::SharedPointer<gpio::sf::IPadSession>> out, DeviceCode device_code, ddsf::AccessMode access_mode), (out, device_code, access_mode), hos::Version_5_0_0 ) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, IsWakeEventActive2, (ams::sf::Out<bool> out, DeviceCode device_code), (out, device_code), hos::Version_5_0_0 ) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, SetWakeEventActiveFlagSetForDebug2, (DeviceCode device_code, bool is_enabled), (device_code, is_enabled), hos::Version_5_0_0 ) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, SetRetryValues, (u32 arg0, u32 arg1), (arg0, arg1), hos::Version_6_0_0 ) + +AMS_SF_DEFINE_INTERFACE(ams::gpio::sf, IManager, AMS_GPIO_I_MANAGER_INTERFACE_INFO, 0xD219501E) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/sf/gpio_sf_i_pad_session.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/sf/gpio_sf_i_pad_session.hpp new file mode 100644 index 00000000..9c0fb619 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/gpio/sf/gpio_sf_i_pad_session.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/gpio/gpio_types.hpp> + +#define AMS_GPIO_I_PAD_SESSION_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, SetDirection, (gpio::Direction direction), (direction) ) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, GetDirection, (ams::sf::Out<gpio::Direction> out), (out) ) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, SetInterruptMode, (gpio::InterruptMode mode), (mode) ) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, GetInterruptMode, (ams::sf::Out<gpio::InterruptMode> out), (out) ) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, SetInterruptEnable, (bool enable), (enable) ) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, GetInterruptEnable, (ams::sf::Out<bool> out), (out) ) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, GetInterruptStatus, (ams::sf::Out<gpio::InterruptStatus> out), (out) ) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, ClearInterruptStatus, (), () ) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, SetValue, (gpio::GpioValue value), (value) ) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, GetValue, (ams::sf::Out<gpio::GpioValue> out), (out) ) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, BindInterrupt, (ams::sf::OutCopyHandle out), (out) ) \ + AMS_SF_METHOD_INFO(C, H, 11, Result, UnbindInterrupt, (), () ) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, SetDebounceEnabled, (bool enable), (enable) ) \ + AMS_SF_METHOD_INFO(C, H, 13, Result, GetDebounceEnabled, (ams::sf::Out<bool> out), (out) ) \ + AMS_SF_METHOD_INFO(C, H, 14, Result, SetDebounceTime, (s32 ms), (ms) ) \ + AMS_SF_METHOD_INFO(C, H, 15, Result, GetDebounceTime, (ams::sf::Out<s32> out), (out) ) \ + AMS_SF_METHOD_INFO(C, H, 16, Result, SetValueForSleepState, (gpio::GpioValue value), (value), hos::Version_4_0_0) \ + AMS_SF_METHOD_INFO(C, H, 16, Result, GetValueForSleepState, (ams::sf::Out<gpio::GpioValue> out), (out), hos::Version_6_0_0) + +AMS_SF_DEFINE_INTERFACE(ams::gpio::sf, IPadSession, AMS_GPIO_I_PAD_SESSION_INTERFACE_INFO, 0x7448A8A7) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/hid.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/hid.hpp new file mode 100644 index 00000000..726563b0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/hid.hpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/hid/hid_api.hpp> \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/hid/hid_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/hid/hid_api.hpp new file mode 100644 index 00000000..9a77c024 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/hid/hid_api.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +namespace ams::hid { + + /* Key API. */ + Result GetKeysHeld(u64 *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/hos.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/hos.hpp new file mode 100644 index 00000000..a472b09b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/hos.hpp @@ -0,0 +1,21 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/hos/hos_types.hpp> +#include <stratosphere/hos/hos_version_api.hpp> +#include <stratosphere/hos/hos_stratosphere_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/hos/hos_stratosphere_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/hos/hos_stratosphere_api.hpp new file mode 100644 index 00000000..7346fa9c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/hos/hos_stratosphere_api.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/hos/hos_types.hpp> + +namespace ams::hos { + + void InitializeForStratosphere(); + void InitializeForStratosphereDebug(hos::Version debug_version); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/hos/hos_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/hos/hos_types.hpp new file mode 100644 index 00000000..81f4a843 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/hos/hos_types.hpp @@ -0,0 +1,105 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::hos { + + enum Version : u32 { + Version_Min = ::ams::TargetFirmware_Min, + + Version_1_0_0 = ::ams::TargetFirmware_1_0_0, + Version_2_0_0 = ::ams::TargetFirmware_2_0_0, + Version_2_1_0 = ::ams::TargetFirmware_2_1_0, + Version_2_2_0 = ::ams::TargetFirmware_2_2_0, + Version_2_3_0 = ::ams::TargetFirmware_2_3_0, + Version_3_0_0 = ::ams::TargetFirmware_3_0_0, + Version_3_0_1 = ::ams::TargetFirmware_3_0_1, + Version_3_0_2 = ::ams::TargetFirmware_3_0_2, + Version_4_0_0 = ::ams::TargetFirmware_4_0_0, + Version_4_0_1 = ::ams::TargetFirmware_4_0_1, + Version_4_1_0 = ::ams::TargetFirmware_4_1_0, + Version_5_0_0 = ::ams::TargetFirmware_5_0_0, + Version_5_0_1 = ::ams::TargetFirmware_5_0_1, + Version_5_0_2 = ::ams::TargetFirmware_5_0_2, + Version_5_1_0 = ::ams::TargetFirmware_5_1_0, + Version_6_0_0 = ::ams::TargetFirmware_6_0_0, + Version_6_0_1 = ::ams::TargetFirmware_6_0_1, + Version_6_1_0 = ::ams::TargetFirmware_6_1_0, + Version_6_2_0 = ::ams::TargetFirmware_6_2_0, + Version_7_0_0 = ::ams::TargetFirmware_7_0_0, + Version_7_0_1 = ::ams::TargetFirmware_7_0_1, + Version_8_0_0 = ::ams::TargetFirmware_8_0_0, + Version_8_0_1 = ::ams::TargetFirmware_8_0_1, + Version_8_1_0 = ::ams::TargetFirmware_8_1_0, + Version_8_1_1 = ::ams::TargetFirmware_8_1_1, + Version_9_0_0 = ::ams::TargetFirmware_9_0_0, + Version_9_0_1 = ::ams::TargetFirmware_9_0_1, + Version_9_1_0 = ::ams::TargetFirmware_9_1_0, + Version_9_2_0 = ::ams::TargetFirmware_9_2_0, + Version_10_0_0 = ::ams::TargetFirmware_10_0_0, + Version_10_0_1 = ::ams::TargetFirmware_10_0_1, + Version_10_0_2 = ::ams::TargetFirmware_10_0_2, + Version_10_0_3 = ::ams::TargetFirmware_10_0_3, + Version_10_0_4 = ::ams::TargetFirmware_10_0_4, + Version_10_1_0 = ::ams::TargetFirmware_10_1_0, + Version_10_2_0 = ::ams::TargetFirmware_10_2_0, + Version_11_0_0 = ::ams::TargetFirmware_11_0_0, + Version_11_0_1 = ::ams::TargetFirmware_11_0_1, + Version_12_0_0 = ::ams::TargetFirmware_12_0_0, + Version_12_0_1 = ::ams::TargetFirmware_12_0_1, + Version_12_0_2 = ::ams::TargetFirmware_12_0_2, + Version_12_0_3 = ::ams::TargetFirmware_12_0_3, + Version_12_1_0 = ::ams::TargetFirmware_12_1_0, + Version_13_0_0 = ::ams::TargetFirmware_13_0_0, + Version_13_1_0 = ::ams::TargetFirmware_13_1_0, + Version_13_2_0 = ::ams::TargetFirmware_13_2_0, + Version_13_2_1 = ::ams::TargetFirmware_13_2_1, + Version_14_0_0 = ::ams::TargetFirmware_14_0_0, + Version_14_1_0 = ::ams::TargetFirmware_14_1_0, + Version_14_1_1 = ::ams::TargetFirmware_14_1_1, + Version_14_1_2 = ::ams::TargetFirmware_14_1_2, + Version_15_0_0 = ::ams::TargetFirmware_15_0_0, + Version_15_0_1 = ::ams::TargetFirmware_15_0_1, + Version_16_0_0 = ::ams::TargetFirmware_16_0_0, + Version_16_0_1 = ::ams::TargetFirmware_16_0_1, + Version_16_0_2 = ::ams::TargetFirmware_16_0_2, + Version_16_0_3 = ::ams::TargetFirmware_16_0_3, + Version_16_1_0 = ::ams::TargetFirmware_16_1_0, + Version_17_0_0 = ::ams::TargetFirmware_17_0_0, + Version_17_0_1 = ::ams::TargetFirmware_17_0_1, + Version_18_0_0 = ::ams::TargetFirmware_18_0_0, + Version_18_0_1 = ::ams::TargetFirmware_18_0_1, + Version_18_1_0 = ::ams::TargetFirmware_18_1_0, + Version_19_0_0 = ::ams::TargetFirmware_19_0_0, + Version_19_0_1 = ::ams::TargetFirmware_19_0_1, + Version_20_0_0 = ::ams::TargetFirmware_20_0_0, + Version_20_0_1 = ::ams::TargetFirmware_20_0_1, + Version_20_1_0 = ::ams::TargetFirmware_20_1_0, + Version_20_1_1 = ::ams::TargetFirmware_20_1_1, + Version_20_1_5 = ::ams::TargetFirmware_20_1_5, + Version_20_2_0 = ::ams::TargetFirmware_20_2_0, + Version_20_3_0 = ::ams::TargetFirmware_20_3_0, + Version_20_4_0 = ::ams::TargetFirmware_20_4_0, + Version_20_5_0 = ::ams::TargetFirmware_20_5_0, + + Version_Current = ::ams::TargetFirmware_Current, + + Version_Max = ::ams::TargetFirmware_Max, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/hos/hos_version_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/hos/hos_version_api.hpp new file mode 100644 index 00000000..624b7810 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/hos/hos_version_api.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/hos/hos_types.hpp> + +namespace ams::hos { + + ::ams::hos::Version GetVersion(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc.hpp new file mode 100644 index 00000000..81f2c011 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc.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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <stratosphere/htc/server/htc_htcmisc_hipc_server.hpp> +#include <stratosphere/htc/server/htc_htcmisc_channel_ids.hpp> + +#include <stratosphere/htc/tenv/htc_tenv_types.hpp> +#include <stratosphere/htc/tenv/htc_tenv.hpp> +#include <stratosphere/htc/tenv/htc_tenv_service_manager.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/server/htc_htcmisc_channel_ids.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/server/htc_htcmisc_channel_ids.hpp new file mode 100644 index 00000000..4c7bb652 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/server/htc_htcmisc_channel_ids.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/htclow/htclow_channel_types.hpp> + +namespace ams::htc::server { + + constexpr inline htclow::ChannelId HtcmiscClientChannelId = 1; + constexpr inline htclow::ChannelId HtcmiscServerChannelId = 2; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/server/htc_htcmisc_hipc_server.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/server/htc_htcmisc_hipc_server.hpp new file mode 100644 index 00000000..b55b246b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/server/htc_htcmisc_hipc_server.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::htclow { + + class HtclowManager; + +} + +namespace ams::htc::server { + + void InitializeHtcmiscServer(htclow::HtclowManager *htclow_manager); + void FinalizeHtcmiscServer(); + void LoopHtcmiscServer(); + void RequestStopHtcmiscServer(); + + class HtcmiscImpl; + HtcmiscImpl *GetHtcmiscImpl(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/tenv/htc_tenv.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/tenv/htc_tenv.hpp new file mode 100644 index 00000000..a11e1241 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/tenv/htc_tenv.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::htc::tenv { + + void Initialize(AllocateFunction allocate, DeallocateFunction deallocate); + Result RegisterDefinitionFilePath(os::ProcessId process_id, const char *path, size_t size); + void UnregisterDefinitionFilePath(os::ProcessId process_id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/tenv/htc_tenv_i_service.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/tenv/htc_tenv_i_service.hpp new file mode 100644 index 00000000..28830083 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/tenv/htc_tenv_i_service.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/htc/tenv/htc_tenv_types.hpp> + +#define AMS_HTC_TENV_I_SERVICE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GetVariable, (sf::Out<s64> out_size, const sf::OutBuffer &out_buffer, const htc::tenv::VariableName &name), (out_size, out_buffer, name)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, GetVariableLength, (sf::Out<s64> out_size,const htc::tenv::VariableName &name), (out_size, name)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, WaitUntilVariableAvailable, (s64 timeout_ms), (timeout_ms)) + +AMS_SF_DEFINE_INTERFACE(ams::htc::tenv, IService, AMS_HTC_TENV_I_SERVICE_INTERFACE_INFO, 0x041F65C5) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/tenv/htc_tenv_i_service_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/tenv/htc_tenv_i_service_manager.hpp new file mode 100644 index 00000000..1ff6aba7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/tenv/htc_tenv_i_service_manager.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/os/os_common_types.hpp> +#include <stratosphere/htc/tenv/htc_tenv_i_service.hpp> + +#define AMS_HTC_TENV_I_SERVICE_MANAGER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GetServiceInterface, (sf::Out<sf::SharedPointer<htc::tenv::IService>> out, const sf::ClientProcessId &process_id), (out, process_id)) + +AMS_SF_DEFINE_INTERFACE(ams::htc::tenv, IServiceManager, AMS_HTC_TENV_I_SERVICE_MANAGER_INTERFACE_INFO, 0x38649D88) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/tenv/htc_tenv_service_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/tenv/htc_tenv_service_manager.hpp new file mode 100644 index 00000000..2047d2dd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/tenv/htc_tenv_service_manager.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sm/sm_types.hpp> +#include <stratosphere/htc/tenv/htc_tenv_i_service_manager.hpp> + +namespace ams::htc::tenv { + + constexpr inline sm::ServiceName ServiceName = sm::ServiceName::Encode("htc:tenv"); + + class ServiceManager { + public: + Result GetServiceInterface(sf::Out<sf::SharedPointer<htc::tenv::IService>> out, const sf::ClientProcessId &process_id); + }; + static_assert(htc::tenv::IsIServiceManager<ServiceManager>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/tenv/htc_tenv_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/tenv/htc_tenv_types.hpp new file mode 100644 index 00000000..9c1aee29 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htc/tenv/htc_tenv_types.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::htc::tenv { + + struct VariableName { + char str[0x40]; + }; + + constexpr inline auto PathLengthMax = 0x300; + + struct alignas(4) Path { + char str[PathLengthMax]; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcfs.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcfs.hpp new file mode 100644 index 00000000..d9abf1bc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcfs.hpp @@ -0,0 +1,18 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <stratosphere/htcfs/htcfs_hipc_server.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcfs/htcfs_hipc_server.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcfs/htcfs_hipc_server.hpp new file mode 100644 index 00000000..2b5fd12f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcfs/htcfs_hipc_server.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::htclow { + + class HtclowManager; + +} + +namespace ams::htcfs { + + void Initialize(htclow::HtclowManager *htclow_manager); + + void RegisterHipcServer(); + void LoopHipcServer(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htclow.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htclow.hpp new file mode 100644 index 00000000..f0b7400b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htclow.hpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/htclow/htclow_types.hpp> +#include <stratosphere/htclow/impl/htclow_internal_types.hpp> +#include <stratosphere/htclow/htclow_manager_holder.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htclow/htclow_channel_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htclow/htclow_channel_types.hpp new file mode 100644 index 00000000..98614f47 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htclow/htclow_channel_types.hpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/htclow/htclow_module_types.hpp> + +namespace ams::htclow { + + using ChannelId = u16; + + struct ChannelType { + bool _is_initialized; + ModuleId _module_id; + ChannelId _channel_id; + }; + + enum ChannelState { + ChannelState_Connectable = 0, + ChannelState_Unconnectable = 1, + ChannelState_Connected = 2, + ChannelState_Disconnected = 3, + }; + + struct ChannelConfig { + bool flow_control_enabled; + bool handshake_enabled; + u64 initial_counter_max_data; + size_t max_packet_size; + }; + + constexpr bool IsStateTransitionAllowed(ChannelState from, ChannelState to) { + switch (from) { + case ChannelState_Connectable: + return to == ChannelState_Unconnectable || + to == ChannelState_Connected || + to == ChannelState_Disconnected; + case ChannelState_Unconnectable: + return to == ChannelState_Connectable || + to == ChannelState_Disconnected; + case ChannelState_Connected: + return to == ChannelState_Disconnected; + case ChannelState_Disconnected: + return to == ChannelState_Disconnected; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htclow/htclow_manager_holder.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htclow/htclow_manager_holder.hpp new file mode 100644 index 00000000..ef7f7e05 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htclow/htclow_manager_holder.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/htclow/htclow_types.hpp> + +namespace ams::htclow { + + class HtclowManager; + + namespace HtclowManagerHolder { + + void AddReference(); + void Release(); + + HtclowManager *GetHtclowManager(); + + void SetDefaultDriver(htclow::impl::DriverType driver_type); + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htclow/htclow_module_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htclow/htclow_module_types.hpp new file mode 100644 index 00000000..234b8142 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htclow/htclow_module_types.hpp @@ -0,0 +1,66 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/htclow/htclow_module_types.hpp> + +namespace ams::htclow { + + enum class ModuleId : u8 { + Unknown = 0, + + Htcfs = 1, + + Htcmisc = 3, + Htcs = 4, + }; + + struct ModuleType { + bool _is_initialized; + ModuleId _id; + }; + + constexpr void InitializeModule(ModuleType *out, ModuleId id) { + *out = { + ._is_initialized = true, + ._id = id, + }; + } + + constexpr void FinalizeModule(ModuleType *out) { + *out = { + ._is_initialized = false, + ._id = ModuleId::Unknown, + }; + } + + class Module final { + private: + ModuleType m_impl; + public: + constexpr explicit Module(ModuleId id) : m_impl() { + InitializeModule(std::addressof(m_impl), id); + } + + constexpr ~Module() { + FinalizeModule(std::addressof(m_impl)); + } + + ModuleType *GetBase() { return std::addressof(m_impl); } + const ModuleType *GetBase() const { return std::addressof(m_impl); } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htclow/htclow_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htclow/htclow_types.hpp new file mode 100644 index 00000000..06efd119 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htclow/htclow_types.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::htclow { + + namespace impl { + + enum class DriverType { + Unknown = 0, + Debug = 1, + Socket = 2, + Usb = 3, + HostBridge = 4, + PlainChannel = 5, + }; + + } + + constexpr inline s16 ProtocolVersion = 5; + + enum ReceiveOption { + ReceiveOption_NonBlocking = 0, + ReceiveOption_ReceiveAnyData = 1, + ReceiveOption_ReceiveAllData = 2, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htclow/impl/htclow_internal_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htclow/impl/htclow_internal_types.hpp new file mode 100644 index 00000000..a510d579 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htclow/impl/htclow_internal_types.hpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/htclow/htclow_channel_types.hpp> + +namespace ams::htclow::impl { + + struct ChannelInternalType { + ChannelId channel_id; + s8 reserved; + ModuleId module_id; + }; + static_assert(sizeof(ChannelInternalType) == 4); + + constexpr ALWAYS_INLINE ChannelInternalType ConvertChannelType(ChannelType channel) { + return { + .channel_id = channel._channel_id, + .reserved = 0, + .module_id = channel._module_id, + }; + } + + constexpr ALWAYS_INLINE bool operator==(const ChannelInternalType &lhs, const ChannelInternalType &rhs) { + return lhs.module_id == rhs.module_id && lhs.reserved == rhs.reserved && lhs.channel_id == rhs.channel_id; + } + + constexpr ALWAYS_INLINE bool operator!=(const ChannelInternalType &lhs, const ChannelInternalType &rhs) { + return !(lhs == rhs); + } + + constexpr ALWAYS_INLINE bool operator<(const ChannelInternalType &lhs, const ChannelInternalType &rhs) { + return std::tie(lhs.module_id, lhs.reserved, lhs.channel_id) < std::tie(rhs.module_id, rhs.reserved, rhs.channel_id); + } + + constexpr ALWAYS_INLINE bool operator>(const ChannelInternalType &lhs, const ChannelInternalType &rhs) { + return rhs < lhs; + } + + constexpr ALWAYS_INLINE bool operator<=(const ChannelInternalType &lhs, const ChannelInternalType &rhs) { + return !(lhs > rhs); + } + + constexpr ALWAYS_INLINE bool operator>=(const ChannelInternalType &lhs, const ChannelInternalType &rhs) { + return !(lhs < rhs); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs.hpp new file mode 100644 index 00000000..28e414fc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs.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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <stratosphere/htcs/htcs_types.hpp> +#include <stratosphere/htcs/htcs_api.hpp> +#include <stratosphere/htcs/htcs_socket.hpp> +#include <stratosphere/htcs/impl/htcs_manager_holder.hpp> +#include <stratosphere/htcs/impl/htcs_channel_ids.hpp> +#include <stratosphere/htcs/server/htcs_hipc_server.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs/htcs_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs/htcs_api.hpp new file mode 100644 index 00000000..18d0a9cc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs/htcs_api.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/htcs/htcs_types.hpp> + +namespace ams::htcs { + + bool IsInitialized(); + + size_t GetWorkingMemorySize(int max_socket_count); + + void Initialize(AllocateFunction allocate, DeallocateFunction deallocate, int num_sessions = SessionCountMax); + void Initialize(void *buffer, size_t buffer_size); + + void InitializeForDisableDisconnectionEmulation(AllocateFunction allocate, DeallocateFunction deallocate, int num_sessions = SessionCountMax); + void InitializeForDisableDisconnectionEmulation(void *buffer, size_t buffer_size); + + void InitializeForSystem(void *buffer, size_t buffer_size, int num_sessions); + + void Finalize(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs/htcs_socket.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs/htcs_socket.hpp new file mode 100644 index 00000000..392eacb3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs/htcs_socket.hpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/htcs/htcs_types.hpp> + +namespace ams::htcs { + + const HtcsPeerName GetPeerNameAny(); + const HtcsPeerName GetDefaultHostName(); + + s32 GetLastError(); + + s32 Socket(); + s32 Close(s32 desc); + s32 Connect(s32 desc, const SockAddrHtcs *address); + s32 Bind(s32 desc, const SockAddrHtcs *address); + s32 Listen(s32 desc, s32 backlog_count); + s32 Accept(s32 desc, SockAddrHtcs *address); + s32 Shutdown(s32 desc, s32 how); + s32 Fcntl(s32 desc, s32 command, s32 value); + + s32 Select(s32 count, FdSet *read, FdSet *write, FdSet *exception, TimeVal *timeout); + + ssize_t Recv(s32 desc, void *buffer, size_t buffer_size, s32 flags); + ssize_t Send(s32 desc, const void *buffer, size_t buffer_size, s32 flags); + + void FdSetZero(FdSet *set); + void FdSetSet(s32 fd, FdSet *set); + void FdSetClr(s32 fd, FdSet *set); + bool FdSetIsSet(s32 fd, const FdSet *set); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs/htcs_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs/htcs_types.hpp new file mode 100644 index 00000000..1feaf3d6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs/htcs_types.hpp @@ -0,0 +1,111 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::htcs { + + using ssize_t = intptr_t; + using AddressFamilyType = u16; + + constexpr inline int PeerNameBufferLength = 32; + constexpr inline int PortNameBufferLength = 32; + + constexpr inline int SessionCountMax = 0x10; + + constexpr inline int SocketCountMax = 40; + constexpr inline int FdSetSize = SocketCountMax; + + struct HtcsPeerName { + char name[PeerNameBufferLength]; + }; + + struct HtcsPortName { + char name[PortNameBufferLength]; + }; + + struct SockAddrHtcs { + AddressFamilyType family; + HtcsPeerName peer_name; + HtcsPortName port_name; + }; + + struct TimeVal { + s64 tv_sec; + s64 tv_usec; + }; + + struct FdSet { + int fds[FdSetSize]; + }; + + enum SocketError { + HTCS_ENONE = 0, + HTCS_EACCES = 2, + HTCS_EADDRINUSE = 3, + HTCS_EADDRNOTAVAIL = 4, + HTCS_EAGAIN = 6, + HTCS_EALREADY = 7, + HTCS_EBADF = 8, + HTCS_EBUSY = 10, + HTCS_ECONNABORTED = 13, + HTCS_ECONNREFUSED = 14, + HTCS_ECONNRESET = 15, + HTCS_EDESTADDRREQ = 17, + HTCS_EFAULT = 21, + HTCS_EINPROGRESS = 26, + HTCS_EINTR = 27, + HTCS_EINVAL = 28, + HTCS_EIO = 29, + HTCS_EISCONN = 30, + HTCS_EMFILE = 33, + HTCS_EMSGSIZE = 35, + HTCS_ENETDOWN = 38, + HTCS_ENETRESET = 39, + HTCS_ENOBUFS = 42, + HTCS_ENOMEM = 49, + HTCS_ENOTCONN = 56, + HTCS_ETIMEDOUT = 76, + HTCS_EUNKNOWN = 79, + + HTCS_EWOULDBLOCK = HTCS_EAGAIN, + }; + + enum MessageFlag { + HTCS_MSG_PEEK = 1, + HTCS_MSG_WAITALL = 2, + }; + + enum ShutdownType { + HTCS_SHUT_RD = 0, + HTCS_SHUT_WR = 1, + HTCS_SHUT_RDWR = 2, + }; + + enum FcntlOperation { + HTCS_F_GETFL = 3, + HTCS_F_SETFL = 4, + }; + + enum FcntlFlag { + HTCS_O_NONBLOCK = 4, + }; + + enum AddressFamily { + HTCS_AF_HTCS = 0, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs/impl/htcs_channel_ids.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs/impl/htcs_channel_ids.hpp new file mode 100644 index 00000000..93a0d3cf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs/impl/htcs_channel_ids.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/htclow/htclow_channel_types.hpp> + +namespace ams::htcs::impl { + + constexpr inline htclow::ChannelId HtcsClientChannelId = 0; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs/impl/htcs_manager_holder.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs/impl/htcs_manager_holder.hpp new file mode 100644 index 00000000..144abee4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs/impl/htcs_manager_holder.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/htcs/htcs_types.hpp> + +namespace ams::htcs::impl { + + class HtcsManager; + + namespace HtcsManagerHolder { + + void AddReference(); + void Release(); + + HtcsManager *GetHtcsManager(); + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs/server/htcs_hipc_server.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs/server/htcs_hipc_server.hpp new file mode 100644 index 00000000..62e7add2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/htcs/server/htcs_hipc_server.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::htcs::server { + + void Initialize(); + + void RegisterHipcServer(); + void LoopHipcServer(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c.hpp new file mode 100644 index 00000000..3d33cbe5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/i2c/i2c_types.hpp> +#include <stratosphere/i2c/i2c_select_device_name.hpp> +#include <stratosphere/i2c/i2c_command_list_formatter.hpp> +#include <stratosphere/i2c/sf/i2c_sf_i_session.hpp> +#include <stratosphere/i2c/sf/i2c_sf_i_manager.hpp> +#include <stratosphere/i2c/server/i2c_server_api.hpp> +#include <stratosphere/i2c/driver/i2c_select_driver_api.hpp> +#include <stratosphere/i2c/driver/i2c_driver_service_api.hpp> +#include <stratosphere/i2c/driver/i2c_driver_client_api.hpp> +#include <stratosphere/i2c/driver/i2c_bus_api.hpp> +#include <stratosphere/i2c/driver/impl/i2c_i2c_session_impl.hpp> +#include <stratosphere/i2c/i2c_api.hpp> +#include <stratosphere/i2c/i2c_bus_api.hpp> +#include <stratosphere/i2c/i2c_register_accessor.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/board/nintendo/nx/i2c_driver_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/board/nintendo/nx/i2c_driver_api.hpp new file mode 100644 index 00000000..49e9287a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/board/nintendo/nx/i2c_driver_api.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/i2c/i2c_types.hpp> + +namespace ams::i2c::driver::board::nintendo::nx { + + void Initialize(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/i2c_bus_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/i2c_bus_api.hpp new file mode 100644 index 00000000..f708e16b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/i2c_bus_api.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/i2c/i2c_types.hpp> + +namespace ams::i2c::driver { + + namespace impl { + + constexpr inline size_t I2cSessionSize = 0x60; + constexpr inline size_t I2cSessionAlign = 8; + struct alignas(I2cSessionAlign) I2cSessionImplPadded; + + } + + struct I2cSession { + util::TypedStorage<impl::I2cSessionImplPadded, impl::I2cSessionSize, impl::I2cSessionAlign> _impl; + }; + + Result OpenSession(I2cSession *out, DeviceCode device_code); + void CloseSession(I2cSession &session); + + Result Send(I2cSession &session, const void *src, size_t src_size, TransactionOption option); + Result Receive(void *dst, size_t dst_size, I2cSession &session, TransactionOption option); + + Result ExecuteCommandList(void *dst, size_t dst_size, I2cSession &session, const void *src, size_t src_size); + + Result SetRetryPolicy(I2cSession &session, int max_retry_count, int retry_interval_us); + +} + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/i2c_driver_client_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/i2c_driver_client_api.hpp new file mode 100644 index 00000000..70e5ecb1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/i2c_driver_client_api.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/i2c/i2c_types.hpp> + +namespace ams::i2c::driver { + + void Initialize(); + void Finalize(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/i2c_driver_service_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/i2c_driver_service_api.hpp new file mode 100644 index 00000000..8c60a23d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/i2c_driver_service_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/i2c/i2c_types.hpp> +#include <stratosphere/i2c/driver/i2c_i2c_device_property.hpp> +#include <stratosphere/i2c/driver/i2c_i_i2c_driver.hpp> + +namespace ams::i2c::driver { + + void RegisterDriver(II2cDriver *driver); + void UnregisterDriver(II2cDriver *driver); + + Result RegisterDeviceCode(DeviceCode device_code, I2cDeviceProperty *device); + bool UnregisterDeviceCode(DeviceCode device_code); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/i2c_i2c_device_property.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/i2c_i2c_device_property.hpp new file mode 100644 index 00000000..43ab1f65 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/i2c_i2c_device_property.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/i2c/i2c_types.hpp> +#include <stratosphere/ddsf.hpp> + +namespace ams::i2c::driver { + + class I2cDeviceProperty : public ::ams::ddsf::IDevice { + NON_COPYABLE(I2cDeviceProperty); + NON_MOVEABLE(I2cDeviceProperty); + AMS_DDSF_CASTABLE_TRAITS(ams::i2c::driver::I2cDeviceProperty, ::ams::ddsf::IDevice); + private: + u16 m_address; + AddressingMode m_addressing_mode; + util::IntrusiveListNode m_device_property_list_node; + public: + using DevicePropertyListTraits = util::IntrusiveListMemberTraits<&I2cDeviceProperty::m_device_property_list_node>; + using DevicePropertyList = typename DevicePropertyListTraits::ListType; + friend class util::IntrusiveList<I2cDeviceProperty, util::IntrusiveListMemberTraits<&I2cDeviceProperty::m_device_property_list_node>>; + public: + I2cDeviceProperty() : IDevice(false), m_address(0), m_addressing_mode(AddressingMode_SevenBit), m_device_property_list_node() { /* ... */ } + I2cDeviceProperty(u16 addr, AddressingMode m) : IDevice(false), m_address(addr), m_addressing_mode(m), m_device_property_list_node() { /* ... */ } + + virtual ~I2cDeviceProperty() { /* ... */ } + + u16 GetAddress() const { + return m_address; + } + + AddressingMode GetAddressingMode() const { + return m_addressing_mode; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/i2c_i_i2c_driver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/i2c_i_i2c_driver.hpp new file mode 100644 index 00000000..f7b90bd0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/i2c_i_i2c_driver.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/i2c/i2c_types.hpp> +#include <stratosphere/ddsf.hpp> + +namespace ams::i2c::driver { + + class I2cDeviceProperty; + + class II2cDriver : public ::ams::ddsf::IDriver { + NON_COPYABLE(II2cDriver); + NON_MOVEABLE(II2cDriver); + AMS_DDSF_CASTABLE_TRAITS(ams::i2c::driver::II2cDriver, ::ams::ddsf::IDriver); + public: + II2cDriver() : IDriver() { /* ... */ } + virtual ~II2cDriver() { /* ... */ } + + virtual void InitializeDriver() = 0; + virtual void FinalizeDriver() = 0; + + virtual Result InitializeDevice(I2cDeviceProperty *device) = 0; + virtual void FinalizeDevice(I2cDeviceProperty *device) = 0; + + virtual Result Send(I2cDeviceProperty *device, const void *src, size_t src_size, TransactionOption option) = 0; + virtual Result Receive(void *dst, size_t dst_size, I2cDeviceProperty *device, TransactionOption option) = 0; + + virtual os::SdkMutex &GetTransactionOrderMutex() = 0; + + virtual void SuspendBus() = 0; + virtual void SuspendPowerBus() = 0; + + virtual void ResumeBus() = 0; + virtual void ResumePowerBus() = 0; + + virtual const DeviceCode &GetDeviceCode() const = 0; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/i2c_select_driver_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/i2c_select_driver_api.hpp new file mode 100644 index 00000000..e678bdaa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/i2c_select_driver_api.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/i2c/i2c_types.hpp> +#include <stratosphere/i2c/driver/i2c_i_i2c_driver.hpp> +#include <stratosphere/i2c/driver/i2c_i2c_device_property.hpp> + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + + #include <stratosphere/i2c/driver/board/nintendo/nx/i2c_driver_api.hpp> + + namespace ams::i2c::driver::board { + + using namespace ams::i2c::driver::board::nintendo::nx; + + } + +#else + + // TODO: #error "Unknown board for ams::i2c::driver::" + +#endif + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/impl/i2c_i2c_session_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/impl/i2c_i2c_session_impl.hpp new file mode 100644 index 00000000..47fa2b8c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/driver/impl/i2c_i2c_session_impl.hpp @@ -0,0 +1,93 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/i2c/driver/i2c_bus_api.hpp> +#include <stratosphere/ddsf.hpp> + +namespace ams::i2c::driver { + + class I2cDeviceProperty; + +} + +namespace ams::i2c::driver::impl { + + class I2cSessionImpl : public ::ams::ddsf::ISession { + NON_COPYABLE(I2cSessionImpl); + NON_MOVEABLE(I2cSessionImpl); + AMS_DDSF_CASTABLE_TRAITS(ams::i2c::driver::impl::I2cSessionImpl, ::ams::ddsf::ISession); + private: + enum class Command { + Send = 0, + Receive = 1, + }; + private: + TimeSpan m_retry_interval; + int m_max_retry_count; + private: + Result SendHandler(const u8 **cur_cmd, u8 **cur_dst); + Result ReceiveHandler(const u8 **cur_cmd, u8 **cur_dst); + Result ExtensionHandler(const u8 **cur_cmd, u8 **cur_dst); + + Result ExecuteTransactionWithRetry(void *dst, Command command, const void *src, size_t size, TransactionOption option); + public: + I2cSessionImpl(int mr, TimeSpan rt) : m_retry_interval(rt), m_max_retry_count(mr) { /* ... */ } + + ~I2cSessionImpl() { + this->Close(); + } + + Result Open(I2cDeviceProperty *device, ddsf::AccessMode access_mode); + void Close(); + + Result Send(const void *src, size_t src_size, TransactionOption option); + Result Receive(void *dst, size_t dst_size, TransactionOption option); + Result ExecuteCommandList(void *dst, size_t dst_size, const void *src, size_t src_size); + Result SetRetryPolicy(int mr, int interval_us); + }; + static_assert( sizeof(I2cSessionImpl) <= I2cSessionSize); + static_assert(alignof(I2cSessionImpl) <= I2cSessionAlign); + + struct alignas(I2cSessionAlign) I2cSessionImplPadded { + I2cSessionImpl _impl; + u8 _padding[I2cSessionSize - sizeof(I2cSessionImpl)]; + }; + static_assert( sizeof(I2cSessionImplPadded) == I2cSessionSize); + static_assert(alignof(I2cSessionImplPadded) == I2cSessionAlign); + + ALWAYS_INLINE I2cSessionImpl &GetI2cSessionImpl(I2cSession &session) { + return GetReference(session._impl)._impl; + } + + ALWAYS_INLINE const I2cSessionImpl &GetI2cSessionImpl(const I2cSession &session) { + return GetReference(session._impl)._impl; + } + + ALWAYS_INLINE I2cSessionImpl &GetOpenI2cSessionImpl(I2cSession &session) { + auto &ref = GetReference(session._impl)._impl; + AMS_ASSERT(ref.IsOpen()); + return ref; + } + + ALWAYS_INLINE const I2cSessionImpl &GetOpenI2cSessionImpl(const I2cSession &session) { + const auto &ref = GetReference(session._impl)._impl; + AMS_ASSERT(ref.IsOpen()); + return ref; + } + +} + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_api.hpp new file mode 100644 index 00000000..d0766169 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/i2c/i2c_types.hpp> +#include <stratosphere/i2c/sf/i2c_sf_i_manager.hpp> + +namespace ams::i2c { + + void InitializeWith(ams::sf::SharedPointer<i2c::sf::IManager> sp, ams::sf::SharedPointer<i2c::sf::IManager> sp_pcv); + void InitializeEmpty(); + + void Finalize(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_bus_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_bus_api.hpp new file mode 100644 index 00000000..66b58888 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_bus_api.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/i2c/i2c_types.hpp> + +namespace ams::i2c { + + struct I2cSession { + void *_session; + }; + + Result OpenSession(I2cSession *out, DeviceCode device_code); + void CloseSession(I2cSession &session); + + Result Send(const I2cSession &session, const void *src, size_t src_size, TransactionOption option); + Result Receive(void *dst, size_t dst_size, const I2cSession &session, TransactionOption option); + + Result ExecuteCommandList(void *dst, size_t dst_size, const I2cSession &session, const void *src, size_t src_size); + + void SetRetryPolicy(const I2cSession &session, int max_retry_count, int retry_interval_us); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_command_list_formatter.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_command_list_formatter.hpp new file mode 100644 index 00000000..bfab4062 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_command_list_formatter.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/i2c/i2c_types.hpp> + +namespace ams::i2c { + + constexpr inline size_t CommandListLengthMax = 0x100; + constexpr inline size_t CommandListReceiveCommandSize = 2; + constexpr inline size_t CommandListSendCommandSize = 2; + constexpr inline size_t CommandListSleepCommandSize = 2; + + class CommandListFormatter { + NON_COPYABLE(CommandListFormatter); + NON_MOVEABLE(CommandListFormatter); + private: + size_t m_current_index; + size_t m_command_list_length; + void *m_command_list; + private: + Result IsEnqueueAble(size_t sz) const; + public: + CommandListFormatter(void *p, size_t sz) : m_current_index(0), m_command_list_length(sz), m_command_list(p) { + AMS_ABORT_UNLESS(m_command_list_length <= CommandListLengthMax); + } + + ~CommandListFormatter() { m_command_list = nullptr; } + + size_t GetCurrentLength() const { return m_current_index; } + const void *GetListHead() const { return m_command_list; } + + Result EnqueueReceiveCommand(i2c::TransactionOption option, size_t size); + Result EnqueueSendCommand(i2c::TransactionOption option, const void *src, size_t size); + Result EnqueueSleepCommand(int us); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_device_name.board.nintendo_nx.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_device_name.board.nintendo_nx.hpp new file mode 100644 index 00000000..ae6e5749 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_device_name.board.nintendo_nx.hpp @@ -0,0 +1,254 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/i2c/i2c_types.hpp> + +namespace ams::i2c { + + enum I2cBus { + I2cBus_I2c1 = 0, + I2cBus_I2c2 = 1, + I2cBus_I2c3 = 2, + I2cBus_I2c4 = 3, + I2cBus_I2c5 = 4, + I2cBus_I2c6 = 5, + }; + + constexpr inline const DeviceCode DeviceCode_I2c1 = 0x02000001; + constexpr inline const DeviceCode DeviceCode_I2c2 = 0x02000002; + constexpr inline const DeviceCode DeviceCode_I2c3 = 0x02000003; + constexpr inline const DeviceCode DeviceCode_I2c4 = 0x02000004; + constexpr inline const DeviceCode DeviceCode_I2c5 = 0x02000005; + constexpr inline const DeviceCode DeviceCode_I2c6 = 0x02000006; + + constexpr inline DeviceCode ConvertToDeviceCode(I2cBus bus) { + switch (bus) { + case I2cBus_I2c1: return DeviceCode_I2c1; + case I2cBus_I2c2: return DeviceCode_I2c2; + case I2cBus_I2c3: return DeviceCode_I2c3; + case I2cBus_I2c4: return DeviceCode_I2c4; + case I2cBus_I2c5: return DeviceCode_I2c5; + case I2cBus_I2c6: return DeviceCode_I2c6; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + constexpr inline DeviceCode ConvertToI2cBus(DeviceCode dc) { + switch (dc.GetInternalValue()) { + case DeviceCode_I2c1.GetInternalValue(): return I2cBus_I2c1; + case DeviceCode_I2c2.GetInternalValue(): return I2cBus_I2c2; + case DeviceCode_I2c3.GetInternalValue(): return I2cBus_I2c3; + case DeviceCode_I2c4.GetInternalValue(): return I2cBus_I2c4; + case DeviceCode_I2c5.GetInternalValue(): return I2cBus_I2c5; + case DeviceCode_I2c6.GetInternalValue(): return I2cBus_I2c6; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + enum I2cDevice : u32 { + I2cDevice_ClassicController = 0, + I2cDevice_Ftm3bd56 = 1, + I2cDevice_Tmp451 = 2, + I2cDevice_Nct72 = 3, + I2cDevice_Alc5639 = 4, + I2cDevice_Max77620Rtc = 5, + I2cDevice_Max77620Pmic = 6, + I2cDevice_Max77621Cpu = 7, + I2cDevice_Max77621Gpu = 8, + I2cDevice_Bq24193 = 9, + I2cDevice_Max17050 = 10, + I2cDevice_Bm92t30mwv = 11, + I2cDevice_Ina226Vdd15v0Hb = 12, + + I2cDevice_Ina226VsysCpuDs = 13, + I2cDevice_Ina226VddCpuAp = 13, + + I2cDevice_Ina226VsysGpuDs = 14, + I2cDevice_Ina226VddGpuAp = 14, + + I2cDevice_Ina226VsysDdrDs = 15, + I2cDevice_Ina226VddDdr1V1Pmic = 15, + + I2cDevice_Ina226VsysAp = 16, + I2cDevice_Ina226VsysBlDs = 17, + I2cDevice_Bh1730 = 18, + + I2cDevice_Ina226VsysCore = 19, + I2cDevice_Ina226VddCoreAp = 19, + + I2cDevice_Ina226Soc1V8 = 20, + I2cDevice_Ina226VddSoc1V8 = 20, + + I2cDevice_Ina226Lpddr1V8 = 21, + I2cDevice_Ina226Vdd1V8 = 21, + + I2cDevice_Ina226Reg1V32 = 22, + I2cDevice_Ina226Vdd3V3Sys = 23, + I2cDevice_HdmiDdc = 24, + I2cDevice_HdmiScdc = 25, + I2cDevice_HdmiHdcp = 26, + I2cDevice_Fan53528 = 27, + I2cDevice_Max77812_3 = 28, + I2cDevice_Max77812_2 = 29, + I2cDevice_Ina226VddDdr0V6 = 30, + I2cDevice_HoagNfcIc = 31, /* TODO */ + I2cDevice_PmicUnknownAula_4_18 = 32, /* TODO */ + }; + + /* TODO: Better place for this? */ + constexpr inline const DeviceCode DeviceCode_ClassicController = 0x350000C9; + constexpr inline const DeviceCode DeviceCode_Ftm3bd56 = 0x35000033; + constexpr inline const DeviceCode DeviceCode_Tmp451 = 0x3E000001; + constexpr inline const DeviceCode DeviceCode_Nct72 = 0x3E000001; + constexpr inline const DeviceCode DeviceCode_Alc5639 = 0x33000001; + constexpr inline const DeviceCode DeviceCode_Max77620Rtc = 0x3B000001; + constexpr inline const DeviceCode DeviceCode_Max77620Pmic = 0x3A000001; + constexpr inline const DeviceCode DeviceCode_Max77621Cpu = 0x3A000003; + constexpr inline const DeviceCode DeviceCode_Max77621Gpu = 0x3A000004; + constexpr inline const DeviceCode DeviceCode_Bq24193 = 0x39000001; + constexpr inline const DeviceCode DeviceCode_Max17050 = 0x39000033; + constexpr inline const DeviceCode DeviceCode_Bm92t30mwv = 0x040000C9; + constexpr inline const DeviceCode DeviceCode_Ina226Vdd15v0Hb = 0x3F000401; + + constexpr inline const DeviceCode DeviceCode_Ina226VsysCpuDs = 0x3F000001; + constexpr inline const DeviceCode DeviceCode_Ina226VddCpuAp = 0x3F000001; + + constexpr inline const DeviceCode DeviceCode_Ina226VsysGpuDs = 0x3F000002; + constexpr inline const DeviceCode DeviceCode_Ina226VddGpuAp = 0x3F000002; + + constexpr inline const DeviceCode DeviceCode_Ina226VsysDdrDs = 0x3F000003; + constexpr inline const DeviceCode DeviceCode_Ina226VddDdr1V1Pmi = 0x3F000003; + + constexpr inline const DeviceCode DeviceCode_Ina226VsysAp = 0x3F000402; + constexpr inline const DeviceCode DeviceCode_Ina226VsysBlDs = 0x3F000403; + constexpr inline const DeviceCode DeviceCode_Bh1730 = 0x35000047; + + constexpr inline const DeviceCode DeviceCode_Ina226VsysCore = 0x3F000404; + constexpr inline const DeviceCode DeviceCode_Ina226VddCoreAp = 0x3F000404; + + constexpr inline const DeviceCode DeviceCode_Ina226Soc1V8 = 0x3F000405; + constexpr inline const DeviceCode DeviceCode_Ina226VddSoc1V8 = 0x3F000405; + + constexpr inline const DeviceCode DeviceCode_Ina226Lpddr1V8 = 0x3F000406; + constexpr inline const DeviceCode DeviceCode_Ina226Vdd1V8 = 0x3F000406; + + constexpr inline const DeviceCode DeviceCode_Ina226Reg1V32 = 0x3F000407; + constexpr inline const DeviceCode DeviceCode_Ina226Vdd3V3Sys = 0x3F000408; + constexpr inline const DeviceCode DeviceCode_HdmiDdc = 0x34000001; + constexpr inline const DeviceCode DeviceCode_HdmiScdc = 0x34000002; + constexpr inline const DeviceCode DeviceCode_HdmiHdcp = 0x34000003; + constexpr inline const DeviceCode DeviceCode_Fan53528 = 0x3A000005; + constexpr inline const DeviceCode DeviceCode_Max77812_3 = 0x3A000002; + constexpr inline const DeviceCode DeviceCode_Max77812_2 = 0x3A000006; + constexpr inline const DeviceCode DeviceCode_Ina226VddDdr0V6 = 0x3F000409; + constexpr inline const DeviceCode DeviceCode_HoagNfcIc = 0x36000001; + constexpr inline const DeviceCode DeviceCode_PmicUnknownAula_4_18 = 0x3A000007; + + constexpr inline DeviceCode ConvertToDeviceCode(I2cDevice dv) { + switch (dv) { + case I2cDevice_ClassicController: return DeviceCode_ClassicController; + case I2cDevice_Ftm3bd56: return DeviceCode_Ftm3bd56; + case I2cDevice_Tmp451: return DeviceCode_Tmp451; + case I2cDevice_Nct72: return DeviceCode_Nct72; + case I2cDevice_Alc5639: return DeviceCode_Alc5639; + case I2cDevice_Max77620Rtc: return DeviceCode_Max77620Rtc; + case I2cDevice_Max77620Pmic: return DeviceCode_Max77620Pmic; + case I2cDevice_Max77621Cpu: return DeviceCode_Max77621Cpu; + case I2cDevice_Max77621Gpu: return DeviceCode_Max77621Gpu; + case I2cDevice_Bq24193: return DeviceCode_Bq24193; + case I2cDevice_Max17050: return DeviceCode_Max17050; + case I2cDevice_Bm92t30mwv: return DeviceCode_Bm92t30mwv; + case I2cDevice_Ina226Vdd15v0Hb: return DeviceCode_Ina226Vdd15v0Hb; + case I2cDevice_Ina226VsysCpuDs: return DeviceCode_Ina226VsysCpuDs; + case I2cDevice_Ina226VsysGpuDs: return DeviceCode_Ina226VsysGpuDs; + case I2cDevice_Ina226VsysDdrDs: return DeviceCode_Ina226VsysDdrDs; + case I2cDevice_Ina226VsysAp: return DeviceCode_Ina226VsysAp; + case I2cDevice_Ina226VsysBlDs: return DeviceCode_Ina226VsysBlDs; + case I2cDevice_Bh1730: return DeviceCode_Bh1730; + case I2cDevice_Ina226VsysCore: return DeviceCode_Ina226VsysCore; + case I2cDevice_Ina226Soc1V8: return DeviceCode_Ina226Soc1V8; + case I2cDevice_Ina226Lpddr1V8: return DeviceCode_Ina226Lpddr1V8; + case I2cDevice_Ina226Reg1V32: return DeviceCode_Ina226Reg1V32; + case I2cDevice_Ina226Vdd3V3Sys: return DeviceCode_Ina226Vdd3V3Sys; + case I2cDevice_HdmiDdc: return DeviceCode_HdmiDdc; + case I2cDevice_HdmiScdc: return DeviceCode_HdmiScdc; + case I2cDevice_HdmiHdcp: return DeviceCode_HdmiHdcp; + case I2cDevice_Fan53528: return DeviceCode_Fan53528; + case I2cDevice_Max77812_3: return DeviceCode_Max77812_3; + case I2cDevice_Max77812_2: return DeviceCode_Max77812_2; + case I2cDevice_Ina226VddDdr0V6: return DeviceCode_Ina226VddDdr0V6; + case I2cDevice_HoagNfcIc: return DeviceCode_HoagNfcIc; + case I2cDevice_PmicUnknownAula_4_18: return DeviceCode_PmicUnknownAula_4_18; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + constexpr inline I2cDevice ConvertToI2cDevice(DeviceCode dc) { + switch (dc.GetInternalValue()) { + case DeviceCode_ClassicController .GetInternalValue(): return I2cDevice_ClassicController; + case DeviceCode_Ftm3bd56 .GetInternalValue(): return I2cDevice_Ftm3bd56; + case DeviceCode_Tmp451 .GetInternalValue(): return I2cDevice_Tmp451; + /* case DeviceCode_Nct72 .GetInternalValue(): return I2cDevice_Nct72; */ + case DeviceCode_Alc5639 .GetInternalValue(): return I2cDevice_Alc5639; + case DeviceCode_Max77620Rtc .GetInternalValue(): return I2cDevice_Max77620Rtc; + case DeviceCode_Max77620Pmic .GetInternalValue(): return I2cDevice_Max77620Pmic; + case DeviceCode_Max77621Cpu .GetInternalValue(): return I2cDevice_Max77621Cpu; + case DeviceCode_Max77621Gpu .GetInternalValue(): return I2cDevice_Max77621Gpu; + case DeviceCode_Bq24193 .GetInternalValue(): return I2cDevice_Bq24193; + case DeviceCode_Max17050 .GetInternalValue(): return I2cDevice_Max17050; + case DeviceCode_Bm92t30mwv .GetInternalValue(): return I2cDevice_Bm92t30mwv; + case DeviceCode_Ina226Vdd15v0Hb .GetInternalValue(): return I2cDevice_Ina226Vdd15v0Hb; + case DeviceCode_Ina226VsysCpuDs .GetInternalValue(): return I2cDevice_Ina226VsysCpuDs; + case DeviceCode_Ina226VsysGpuDs .GetInternalValue(): return I2cDevice_Ina226VsysGpuDs; + case DeviceCode_Ina226VsysDdrDs .GetInternalValue(): return I2cDevice_Ina226VsysDdrDs; + case DeviceCode_Ina226VsysAp .GetInternalValue(): return I2cDevice_Ina226VsysAp; + case DeviceCode_Ina226VsysBlDs .GetInternalValue(): return I2cDevice_Ina226VsysBlDs; + case DeviceCode_Bh1730 .GetInternalValue(): return I2cDevice_Bh1730; + case DeviceCode_Ina226VsysCore .GetInternalValue(): return I2cDevice_Ina226VsysCore; + case DeviceCode_Ina226Soc1V8 .GetInternalValue(): return I2cDevice_Ina226Soc1V8; + case DeviceCode_Ina226Lpddr1V8 .GetInternalValue(): return I2cDevice_Ina226Lpddr1V8; + case DeviceCode_Ina226Reg1V32 .GetInternalValue(): return I2cDevice_Ina226Reg1V32; + case DeviceCode_Ina226Vdd3V3Sys .GetInternalValue(): return I2cDevice_Ina226Vdd3V3Sys; + case DeviceCode_HdmiDdc .GetInternalValue(): return I2cDevice_HdmiDdc; + case DeviceCode_HdmiScdc .GetInternalValue(): return I2cDevice_HdmiScdc; + case DeviceCode_HdmiHdcp .GetInternalValue(): return I2cDevice_HdmiHdcp; + case DeviceCode_Fan53528 .GetInternalValue(): return I2cDevice_Fan53528; + case DeviceCode_Max77812_3 .GetInternalValue(): return I2cDevice_Max77812_3; + case DeviceCode_Max77812_2 .GetInternalValue(): return I2cDevice_Max77812_2; + case DeviceCode_Ina226VddDdr0V6 .GetInternalValue(): return I2cDevice_Ina226VddDdr0V6; + case DeviceCode_HoagNfcIc .GetInternalValue(): return I2cDevice_HoagNfcIc; + case DeviceCode_PmicUnknownAula_4_18.GetInternalValue(): return I2cDevice_PmicUnknownAula_4_18; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + constexpr bool IsPowerBusDeviceCode(DeviceCode device_code) { + switch (device_code.GetInternalValue()) { + case DeviceCode_Max77620Pmic.GetInternalValue(): + case DeviceCode_Max77812_3 .GetInternalValue(): + case DeviceCode_Max77621Cpu .GetInternalValue(): + case DeviceCode_Max77621Gpu .GetInternalValue(): + case DeviceCode_Fan53528 .GetInternalValue(): + case DeviceCode_Max77812_2 .GetInternalValue(): + case DeviceCode_Max77620Rtc .GetInternalValue(): + return true; + default: + return false; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_device_name.generic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_device_name.generic.hpp new file mode 100644 index 00000000..0a39dea3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_device_name.generic.hpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/i2c/i2c_types.hpp> + +namespace ams::i2c { + + enum I2cBus { + I2cBus_I2c1 = 0, + }; + + constexpr inline const DeviceCode DeviceCode_I2c1 = 0x02000001; + + constexpr inline DeviceCode ConvertToDeviceCode(I2cBus bus) { + switch (bus) { + case I2cBus_I2c1: return DeviceCode_I2c1; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + constexpr inline DeviceCode ConvertToI2cBus(DeviceCode dc) { + switch (dc.GetInternalValue()) { + case DeviceCode_I2c1.GetInternalValue(): return I2cBus_I2c1; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + enum I2cDevice : u32 { + I2cDevice_ClassicController = 0, + }; + + /* TODO: Better place for this? */ + constexpr inline const DeviceCode DeviceCode_ClassicController = 0x350000C9; + + constexpr inline DeviceCode ConvertToDeviceCode(I2cDevice dv) { + switch (dv) { + case I2cDevice_ClassicController: return DeviceCode_ClassicController; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + constexpr inline I2cDevice ConvertToI2cDevice(DeviceCode dc) { + switch (dc.GetInternalValue()) { + case DeviceCode_ClassicController.GetInternalValue(): return I2cDevice_ClassicController; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + constexpr bool IsPowerBusDeviceCode(DeviceCode device_code) { + switch (device_code.GetInternalValue()) { + default: + return false; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_register_accessor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_register_accessor.hpp new file mode 100644 index 00000000..70d70950 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_register_accessor.hpp @@ -0,0 +1,56 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/i2c/i2c_types.hpp> +#include <stratosphere/i2c/i2c_command_list_formatter.hpp> +#include <stratosphere/i2c/i2c_bus_api.hpp> + +namespace ams::i2c { + + template<typename RegType> requires std::unsigned_integral<RegType> + Result ReadSingleRegister(const I2cSession &session, u8 address, RegType *out) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(out != nullptr); + + constexpr i2c::TransactionOption StartOption = i2c::TransactionOption_StartCondition; + constexpr i2c::TransactionOption StopOption = static_cast<i2c::TransactionOption>(i2c::TransactionOption_StartCondition | i2c::TransactionOption_StopCondition); + + u8 cmd_list[CommandListLengthMax]; + i2c::CommandListFormatter formatter(cmd_list, sizeof(cmd_list)); + + R_TRY(formatter.EnqueueSendCommand(StartOption, std::addressof(address), sizeof(address))); + R_TRY(formatter.EnqueueReceiveCommand(StopOption, sizeof(*out))); + + R_TRY(i2c::ExecuteCommandList(out, sizeof(*out), session, cmd_list, formatter.GetCurrentLength())); + + R_SUCCEED(); + } + + template<typename RegType> requires std::unsigned_integral<RegType> + Result WriteSingleRegister(const I2cSession &session, u8 address, RegType value) { + /* Prepare buffer. */ + u8 buf[sizeof(address) + sizeof(value)]; + std::memcpy(buf + 0, std::addressof(address), sizeof(address)); + std::memcpy(buf + sizeof(address), std::addressof(value), sizeof(value)); + + constexpr i2c::TransactionOption StopOption = static_cast<i2c::TransactionOption>(i2c::TransactionOption_StartCondition | i2c::TransactionOption_StopCondition); + R_TRY(i2c::Send(session, buf, sizeof(buf), StopOption)); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_select_device_name.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_select_device_name.hpp new file mode 100644 index 00000000..cb9a6321 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_select_device_name.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/i2c/i2c_types.hpp> + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + #include <stratosphere/i2c/i2c_device_name.board.nintendo_nx.hpp> +#else + #include <stratosphere/i2c/i2c_device_name.generic.hpp> +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_types.hpp new file mode 100644 index 00000000..83c703be --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/i2c_types.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::i2c { + + enum TransactionOption : u32 { + TransactionOption_StartCondition = (1u << 0), + TransactionOption_StopCondition = (1u << 1), + TransactionOption_MaxBits = (1u << 30), + }; + + enum AddressingMode : u32 { + AddressingMode_SevenBit = 0, + }; + + enum SpeedMode : u32 { + SpeedMode_Standard = 100000, + SpeedMode_Fast = 400000, + SpeedMode_FastPlus = 1000000, + SpeedMode_HighSpeed = 3400000, + }; + + using I2cCommand = u8; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/server/i2c_server_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/server/i2c_server_api.hpp new file mode 100644 index 00000000..d341f546 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/server/i2c_server_api.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/i2c/i2c_types.hpp> +#include <stratosphere/i2c/sf/i2c_sf_i_manager.hpp> + +namespace ams::i2c::server { + + ams::sf::SharedPointer<i2c::sf::IManager> GetServiceObject(); + ams::sf::SharedPointer<i2c::sf::IManager> GetServiceObjectPowerBus(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/sf/i2c_sf_i_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/sf/i2c_sf_i_manager.hpp new file mode 100644 index 00000000..7342f08c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/sf/i2c_sf_i_manager.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/ddsf/ddsf_types.hpp> +#include <stratosphere/i2c/i2c_select_device_name.hpp> +#include <stratosphere/i2c/sf/i2c_sf_i_session.hpp> + +#define AMS_I2C_I_MANAGER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, OpenSessionForDev, (ams::sf::Out<ams::sf::SharedPointer<i2c::sf::ISession>> out, s32 bus_idx, u16 slave_address, i2c::AddressingMode addressing_mode, i2c::SpeedMode speed_mode), (out, bus_idx, slave_address, addressing_mode, speed_mode) ) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, OpenSession, (ams::sf::Out<ams::sf::SharedPointer<i2c::sf::ISession>> out, i2c::I2cDevice device), (out, device) ) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, HasDevice, (ams::sf::Out<bool> out, i2c::I2cDevice device), (out, device), hos::Version_Min, hos::Version_5_1_0) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, HasDeviceForDev, (ams::sf::Out<bool> out, i2c::I2cDevice device), (out, device), hos::Version_Min, hos::Version_5_1_0) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, OpenSession2, (ams::sf::Out<ams::sf::SharedPointer<i2c::sf::ISession>> out, DeviceCode device_code), (out, device_code), hos::Version_6_0_0 ) + +AMS_SF_DEFINE_INTERFACE(ams::i2c::sf, IManager, AMS_I2C_I_MANAGER_INTERFACE_INFO, 0xE4C9D8F0) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/sf/i2c_sf_i_session.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/sf/i2c_sf_i_session.hpp new file mode 100644 index 00000000..22cf9afa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/i2c/sf/i2c_sf_i_session.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/i2c/i2c_types.hpp> + +#define AMS_I2C_I_SESSION_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, SendOld, (const ams::sf::InBuffer &in_data, i2c::TransactionOption option), (in_data, option), hos::Version_Min, hos::Version_5_1_0) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, ReceiveOld, (const ams::sf::OutBuffer &out_data, i2c::TransactionOption option), (out_data, option), hos::Version_Min, hos::Version_5_1_0) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, ExecuteCommandListOld, (const ams::sf::OutBuffer &rcv_buf, const ams::sf::InPointerArray<i2c::I2cCommand> &command_list), (rcv_buf, command_list), hos::Version_Min, hos::Version_5_1_0) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, Send, (const ams::sf::InAutoSelectBuffer &in_data, i2c::TransactionOption option), (in_data, option) ) \ + AMS_SF_METHOD_INFO(C, H, 11, Result, Receive, (const ams::sf::OutAutoSelectBuffer &out_data, i2c::TransactionOption option), (out_data, option) ) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, ExecuteCommandList, (const ams::sf::OutAutoSelectBuffer &rcv_buf, const ams::sf::InPointerArray<i2c::I2cCommand> &command_list), (rcv_buf, command_list) ) \ + AMS_SF_METHOD_INFO(C, H, 13, Result, SetRetryPolicy, (s32 max_retry_count, s32 retry_interval_us), (max_retry_count, retry_interval_us), hos::Version_6_0_0 ) + +AMS_SF_DEFINE_INTERFACE(ams::i2c::sf, ISession, AMS_I2C_I_SESSION_INTERFACE_INFO, 0x40154EFE) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/init.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/init.hpp new file mode 100644 index 00000000..6860a5ca --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/init.hpp @@ -0,0 +1,18 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/init/init_malloc.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/init/init_malloc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/init/init_malloc.hpp new file mode 100644 index 00000000..f1b58596 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/init/init_malloc.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::mem { + + class StandardAllocator; + +} + +namespace ams::init { + + void InitializeAllocator(void *address, size_t size, bool cache_enabled); + void InitializeAllocator(void *address, size_t size); + + mem::StandardAllocator *GetAllocator(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb.hpp new file mode 100644 index 00000000..5d09db9c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/kvdb/kvdb_auto_buffer.hpp> +#include <stratosphere/kvdb/kvdb_bounded_string.hpp> +#include <stratosphere/kvdb/kvdb_archive.hpp> +#include <stratosphere/kvdb/kvdb_memory_key_value_store.hpp> +#include <stratosphere/kvdb/kvdb_file_key_value_store.hpp> +#include <stratosphere/kvdb/kvdb_file_key_value_cache.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_archive.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_archive.hpp new file mode 100644 index 00000000..e6b2be87 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_archive.hpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/kvdb/kvdb_auto_buffer.hpp> + +namespace ams::kvdb { + + /* Functionality for parsing/generating a key value archive. */ + class ArchiveReader { + private: + AutoBuffer &m_buffer; + size_t m_offset; + public: + ArchiveReader(AutoBuffer &b) : m_buffer(b), m_offset(0) { /* ... */ } + private: + Result Peek(void *dst, size_t size); + Result Read(void *dst, size_t size); + public: + Result ReadEntryCount(size_t *out); + Result GetEntrySize(size_t *out_key_size, size_t *out_value_size); + Result ReadEntry(void *out_key, size_t key_size, void *out_value, size_t value_size); + }; + + class ArchiveWriter { + private: + AutoBuffer &m_buffer; + size_t m_offset; + public: + ArchiveWriter(AutoBuffer &b) : m_buffer(b), m_offset(0) { /* ... */ } + private: + Result Write(const void *src, size_t size); + public: + void WriteHeader(size_t entry_count); + void WriteEntry(const void *key, size_t key_size, const void *value, size_t value_size); + }; + + class ArchiveSizeHelper { + private: + size_t m_size; + public: + ArchiveSizeHelper(); + + void AddEntry(size_t key_size, size_t value_size); + + size_t GetSize() const { + return m_size; + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_auto_buffer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_auto_buffer.hpp new file mode 100644 index 00000000..84b03120 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_auto_buffer.hpp @@ -0,0 +1,88 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::kvdb { + + class AutoBuffer { + NON_COPYABLE(AutoBuffer); + private: + u8 *m_buffer; + size_t m_size; + public: + AutoBuffer() : m_buffer(nullptr), m_size(0) { /* ... */ } + + ~AutoBuffer() { + this->Reset(); + } + + AutoBuffer(AutoBuffer &&rhs) { + m_buffer = rhs.m_buffer; + m_size = rhs.m_size; + rhs.m_buffer = nullptr; + rhs.m_size = 0; + } + + AutoBuffer &operator=(AutoBuffer &&rhs) { + AutoBuffer(std::move(rhs)).Swap(*this); + return *this; + } + + void Swap(AutoBuffer &rhs) { + std::swap(m_buffer, rhs.m_buffer); + std::swap(m_size, rhs.m_size); + } + + void Reset() { + if (m_buffer != nullptr) { + delete[] m_buffer; + m_buffer = nullptr; + m_size = 0; + } + } + + u8 *Get() const { + return m_buffer; + } + + size_t GetSize() const { + return m_size; + } + + Result Initialize(size_t size) { + /* Check that we're not already initialized. */ + AMS_ABORT_UNLESS(m_buffer == nullptr); + + /* Allocate a buffer. */ + m_buffer = new (std::nothrow) u8[size]; + R_UNLESS(m_buffer != nullptr, kvdb::ResultAllocationFailed()); + + m_size = size; + R_SUCCEED(); + } + + Result Initialize(const void *buf, size_t size) { + /* Create a new buffer of the right size. */ + R_TRY(this->Initialize(size)); + + /* Copy the input data in. */ + std::memcpy(m_buffer, buf, size); + + R_SUCCEED(); + } + }; +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_bounded_string.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_bounded_string.hpp new file mode 100644 index 00000000..39615c21 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_bounded_string.hpp @@ -0,0 +1,154 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::kvdb { + + /* Represents a string with a backing buffer of N bytes. */ + template<size_t N> + class BoundedString { + static_assert(N > 0, "BoundedString requires non-zero backing buffer!"); + private: + char m_buffer[N]; + public: + /* Constructors. */ + constexpr BoundedString() { + m_buffer[0] = 0; + } + + explicit constexpr BoundedString(const char *s) { + AMS_ABORT_UNLESS(static_cast<size_t>(util::Strnlen(s, N)) < N); + util::Strlcpy(m_buffer, s, N); + } + + /* Static constructors. */ + static constexpr BoundedString<N> Make(const char *s) { + return BoundedString<N>(s); + } + + static BoundedString<N> MakeFormat(const char *format, ...) __attribute__((format (printf, 1, 2))) { + BoundedString<N> string; + + std::va_list vl; + va_start(vl, format); + AMS_ABORT_UNLESS(static_cast<size_t>(util::VSNPrintf(string.m_buffer, N, format, vl)) < N); + va_end(vl); + + return string; + } + + /* Getters. */ + constexpr size_t GetLength() const { + return util::Strnlen(m_buffer, N); + } + + constexpr const char *Get() const { + return m_buffer; + } + + constexpr operator const char *() const { + return m_buffer; + } + + /* Assignment. */ + constexpr BoundedString<N> &Assign(const char *s) { + AMS_ABORT_UNLESS(static_cast<size_t>(util::Strnlen(s, N)) < N); + util::Strlcpy(m_buffer, s, N); + + return *this; + } + + BoundedString<N> &AssignFormat(const char *format, ...) __attribute__((format (printf, 2, 3))) { + std::va_list vl; + va_start(vl, format); + AMS_ABORT_UNLESS(static_cast<size_t>(util::VSNPrintf(m_buffer, N, format, vl)) < N); + va_end(vl); + + return *this; + } + + /* Append to existing. */ + BoundedString<N> &Append(const char *s) { + const size_t length = GetLength(); + AMS_ABORT_UNLESS(length + static_cast<size_t>(util::Strnlen(s, N)) < N); + std::strncat(m_buffer, s, N - length - 1); + + return *this; + } + + BoundedString<N> &Append(char c) { + const size_t length = GetLength(); + AMS_ABORT_UNLESS(length + 1 < N); + m_buffer[length] = c; + m_buffer[length + 1] = 0; + + return *this; + } + + BoundedString<N> &AppendFormat(const char *format, ...) __attribute__((format (printf, 2, 3))) { + const size_t length = GetLength(); + + std::va_list vl; + va_start(vl, format); + AMS_ABORT_UNLESS(static_cast<size_t>(util::VSNPrintf(m_buffer + length, N - length, format, vl)) < static_cast<size_t>(N - length)); + va_end(vl); + + return *this; + } + + /* Substring utilities. */ + void GetSubString(char *dst, size_t dst_size, size_t offset, size_t length) const { + /* Make sure output buffer can hold the substring. */ + AMS_ABORT_UNLESS(offset + length <= GetLength()); + AMS_ABORT_UNLESS(dst_size > length); + + /* Copy substring to dst. */ + std::strncpy(dst, m_buffer + offset, length); + dst[length] = 0; + } + + BoundedString<N> MakeSubString(size_t offset, size_t length) const { + BoundedString<N> string; + GetSubString(string.m_buffer, N, offset, length); + return string; + } + + /* Comparison. */ + constexpr bool Equals(const char *s, size_t offset = 0) const { + if (std::is_constant_evaluated()) { + return util::Strncmp(m_buffer + offset, s, N - offset) == 0; + } else { + return std::strncmp(m_buffer + offset, s, N - offset) == 0; + } + } + + constexpr bool operator==(const BoundedString<N> &rhs) const { + return this->Equals(rhs.m_buffer); + } + + constexpr bool operator!=(const BoundedString<N> &rhs) const { + return !(*this == rhs); + } + + constexpr bool EqualsPostfix(const char *s) const { + const size_t suffix_length = util::Strnlen(s, N); + const size_t length = GetLength(); + return suffix_length <= length && this->Equals(s, length - suffix_length); + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_file_key_value_cache.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_file_key_value_cache.hpp new file mode 100644 index 00000000..f0057b1c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_file_key_value_cache.hpp @@ -0,0 +1,382 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere/fs/fs_filesystem.hpp> +#include <stratosphere/fs/fs_file.hpp> +#include <stratosphere/kvdb/kvdb_bounded_string.hpp> +#include <stratosphere/kvdb/kvdb_file_key_value_store.hpp> + +namespace ams::kvdb { + + namespace impl { + + template<class Key, size_t Capacity> + class LruList { + private: + /* Subtypes. */ + struct LruHeader { + u32 entry_count; + }; + public: + static constexpr size_t BufferSize = sizeof(Key) * Capacity; + static constexpr size_t FileSize = sizeof(LruHeader) + BufferSize; + using Path = FileKeyValueStore::Path; + private: + Path m_file_path; + Key *m_keys; + LruHeader m_header; + public: + static Result CreateNewList(const char *path) { + /* Create new lru_list.dat. */ + R_TRY(fs::CreateFile(path, FileSize)); + + /* Open the file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Write)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Write new header with zero entries to the file. */ + LruHeader new_header = { .entry_count = 0, }; + R_TRY(fs::WriteFile(file, 0, std::addressof(new_header), sizeof(new_header), fs::WriteOption::Flush)); + + R_SUCCEED(); + } + private: + void RemoveIndex(size_t i) { + AMS_ABORT_UNLESS(i < this->GetCount()); + std::memmove(m_keys + i, m_keys + i + 1, sizeof(*m_keys) * (this->GetCount() - (i + 1))); + this->DecrementCount(); + } + + void IncrementCount() { + m_header.entry_count++; + } + + void DecrementCount() { + m_header.entry_count--; + } + public: + LruList() : m_keys(nullptr), m_header() { /* ... */ } + + Result Initialize(const char *path, void *buf, size_t size) { + /* Only initialize once, and ensure we have sufficient memory. */ + AMS_ABORT_UNLESS(m_keys == nullptr); + AMS_ABORT_UNLESS(size >= BufferSize); + + /* Setup member variables. */ + m_keys = static_cast<Key *>(buf); + m_file_path.Assign(path); + std::memset(m_keys, 0, BufferSize); + + /* Open file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), m_file_path, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Read header. */ + R_TRY(fs::ReadFile(file, 0, std::addressof(m_header), sizeof(m_header))); + + /* Read entries. */ + R_TRY(fs::ReadFile(file, sizeof(m_header), m_keys, BufferSize)); + + R_SUCCEED(); + } + + Result Save() { + /* Open file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), m_file_path, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Write header. */ + R_TRY(fs::WriteFile(file, 0, std::addressof(m_header), sizeof(m_header), fs::WriteOption::None)); + + /* Write entries. */ + R_TRY(fs::WriteFile(file, sizeof(m_header), m_keys, BufferSize, fs::WriteOption::None)); + + /* Flush. */ + R_TRY(fs::FlushFile(file)); + + R_SUCCEED(); + } + + size_t GetCount() const { + return m_header.entry_count; + } + + bool IsEmpty() const { + return this->GetCount() == 0; + } + + bool IsFull() const { + return this->GetCount() >= Capacity; + } + + Key Get(size_t i) const { + AMS_ABORT_UNLESS(i < this->GetCount()); + return m_keys[i]; + } + + Key Peek() const { + AMS_ABORT_UNLESS(!this->IsEmpty()); + return this->Get(0); + } + + void Push(const Key &key) { + AMS_ABORT_UNLESS(!this->IsFull()); + m_keys[this->GetCount()] = key; + this->IncrementCount(); + } + + Key Pop() { + AMS_ABORT_UNLESS(!this->IsEmpty()); + this->RemoveIndex(0); + } + + bool Remove(const Key &key) { + const size_t count = this->GetCount(); + + /* Iterate over the list, removing the last entry that matches the key. */ + for (size_t i = 0; i < count; i++) { + if (m_keys[count - 1 - i] == key) { + this->RemoveIndex(count - 1 - i); + return true; + } + } + + return false; + } + + bool Contains(const Key &key) const { + const size_t count = this->GetCount(); + + /* Iterate over the list, checking to see if we have the key. */ + for (size_t i = 0; i < count; i++) { + if (m_keys[count - 1 - i] == key) { + return true; + } + } + + return false; + } + + bool Update(const Key &key) { + if (this->Remove(key)) { + this->Push(key); + return true; + } + + return false; + } + }; + + } + + template<class Key, size_t Capacity> + class FileKeyValueCache { + static_assert(util::is_pod<Key>::value, "FileKeyValueCache Key must be pod!"); + static_assert(sizeof(Key) <= FileKeyValueStore::MaxKeySize, "FileKeyValueCache Key is too big!"); + public: + using LeastRecentlyUsedList = impl::LruList<Key, Capacity>; + /* Note: Nintendo code in NS uses Path = BoundedString<0x180> here. */ + /* It's unclear why, since they use 0x300 everywhere else. */ + /* We'll just use 0x300, since it shouldn't make a difference, */ + /* as FileKeyValueStore paths are limited to 0x100 anyway. */ + using Path = typename LeastRecentlyUsedList::Path; + private: + FileKeyValueStore m_kvs; + LeastRecentlyUsedList m_lru_list; + private: + static constexpr Path GetLeastRecentlyUsedListPath(const char *dir) { + return Path::MakeFormat("%s/%s", dir, "lru_list.dat"); + } + + static constexpr Path GetFileKeyValueStorePath(const char *dir) { + return Path::MakeFormat("%s/%s", dir, "kvs"); + } + + static Result Exists(bool *out, const char *path, fs::DirectoryEntryType type) { + /* Set out to false initially. */ + *out = false; + + /* Try to get the entry type. */ + fs::DirectoryEntryType entry_type; + R_TRY_CATCH(fs::GetEntryType(std::addressof(entry_type), path)) { + /* If the path doesn't exist, nothing has gone wrong. */ + R_CONVERT(fs::ResultPathNotFound, ResultSuccess()); + } R_END_TRY_CATCH; + + /* Check that the entry type is correct. */ + R_UNLESS(entry_type == type, kvdb::ResultInvalidFilesystemState()); + + /* The entry exists and is the correct type. */ + *out = true; + R_SUCCEED(); + } + + static Result DirectoryExists(bool *out, const char *path) { + R_RETURN(Exists(out, path, fs::DirectoryEntryType_Directory)); + } + + static Result FileExists(bool *out, const char *path) { + R_RETURN(Exists(out, path, fs::DirectoryEntryType_File)); + } + public: + static Result CreateNewCache(const char *dir) { + /* Make a new key value store filesystem, and a new lru_list.dat. */ + R_TRY(LeastRecentlyUsedList::CreateNewList(GetLeastRecentlyUsedListPath(dir))); + R_TRY(fs::CreateDirectory(dir)); + + R_SUCCEED(); + } + + static Result ValidateExistingCache(const char *dir) { + /* Check for existence. */ + bool has_lru = false, has_kvs = false; + R_TRY(FileExists(std::addressof(has_lru), GetLeastRecentlyUsedListPath(dir))); + R_TRY(DirectoryExists(std::addressof(has_kvs), GetFileKeyValueStorePath(dir))); + + /* If neither exists, CreateNewCache was never called. */ + R_UNLESS(has_lru || has_kvs, kvdb::ResultNotCreated()); + + /* If one exists but not the other, we have an invalid state. */ + R_UNLESS(has_lru && has_kvs, kvdb::ResultInvalidFilesystemState()); + + R_SUCCEED(); + } + private: + void RemoveOldestKey() { + const Key &oldest_key = m_lru_list.Peek(); + m_lru_list.Pop(); + m_kvs.Remove(oldest_key); + } + public: + Result Initialize(const char *dir, void *buf, size_t size) { + /* Initialize list. */ + R_TRY(m_lru_list.Initialize(GetLeastRecentlyUsedListPath(dir), buf, size)); + + /* Initialize kvs. */ + /* NOTE: Despite creating the kvs folder and returning an error if it does not exist, */ + /* Nintendo does not use the kvs folder, and instead uses the passed dir. */ + /* This causes lru_list.dat to be in the same directory as the store's .val files */ + /* instead of in the same directory as a folder containing the store's .val files. */ + /* This is probably a Nintendo bug, but because system saves contain data in the wrong */ + /* layout it can't really be fixed without breaking existing devices... */ + R_TRY(m_kvs.Initialize(dir)); + + R_SUCCEED(); + } + + size_t GetCount() const { + return m_lru_list.GetCount(); + } + + size_t GetCapacity() const { + return Capacity; + } + + Key GetKey(size_t i) const { + return m_lru_list.Get(i); + } + + bool Contains(const Key &key) const { + return m_lru_list.Contains(key); + } + + Result Get(size_t *out_size, void *out_value, size_t max_out_size, const Key &key) { + /* Note that we accessed the key. */ + m_lru_list.Update(key); + R_RETURN(m_kvs.Get(out_size, out_value, max_out_size, key)); + } + + template<typename Value> + Result Get(Value *out_value, const Key &key) { + /* Note that we accessed the key. */ + m_lru_list.Update(key); + R_RETURN(m_kvs.Get(out_value, key)); + } + + Result GetSize(size_t *out_size, const Key &key) { + R_RETURN(m_kvs.GetSize(out_size, key)); + } + + Result Set(const Key &key, const void *value, size_t value_size) { + if (m_lru_list.Update(key)) { + /* If an entry for the key exists, delete the existing value file. */ + m_kvs.Remove(key); + } else { + /* If the list is full, we need to remove the oldest key. */ + if (m_lru_list.IsFull()) { + this->RemoveOldestKey(); + } + + /* Add the key to the list. */ + m_lru_list.Push(key); + } + + /* Loop, trying to save the new value to disk. */ + while (true) { + /* Try to set the key. */ + R_TRY_CATCH(m_kvs.Set(key, value, value_size)) { + R_CATCH(fs::ResultNotEnoughFreeSpace) { + /* If our entry is the only thing in the Lru list, remove it. */ + if (m_lru_list.GetCount() == 1) { + m_lru_list.Pop(); + R_TRY(m_lru_list.Save()); + R_THROW(fs::ResultNotEnoughFreeSpace()); + } + + /* Otherwise, remove the oldest element from the cache and try again. */ + this->RemoveOldestKey(); + continue; + } + } R_END_TRY_CATCH; + + /* If we got here, we succeeded. */ + break; + } + + /* Save the list. */ + R_TRY(m_lru_list.Save()); + + R_SUCCEED(); + } + + template<typename Value> + Result Set(const Key &key, const Value &value) { + R_RETURN(this->Set(key, &value, sizeof(Value))); + } + + Result Remove(const Key &key) { + /* Remove the key. */ + m_lru_list.Remove(key); + R_TRY(m_kvs.Remove(key)); + R_TRY(m_lru_list.Save()); + + R_SUCCEED(); + } + + Result RemoveAll() { + /* TODO: Nintendo doesn't check errors here. Should we? */ + while (!m_lru_list.IsEmpty()) { + this->RemoveOldestKey(); + } + R_TRY(m_lru_list.Save()); + + R_SUCCEED(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_file_key_value_store.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_file_key_value_store.hpp new file mode 100644 index 00000000..944c4b31 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_file_key_value_store.hpp @@ -0,0 +1,122 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/os.hpp> +#include <stratosphere/fs/fs_directory.hpp> +#include <stratosphere/kvdb/kvdb_bounded_string.hpp> + +namespace ams::kvdb { + + class FileKeyValueStore { + NON_COPYABLE(FileKeyValueStore); + NON_MOVEABLE(FileKeyValueStore); + public: + static constexpr size_t MaxFileLength = 0xFF; + static constexpr char FileExtension[5] = ".val"; + static constexpr size_t FileExtensionLength = sizeof(FileExtension) - 1; + static constexpr size_t MaxKeySize = (MaxFileLength - FileExtensionLength) / 2; + using Path = kvdb::BoundedString<fs::EntryNameLengthMax>; + using FileName = kvdb::BoundedString<MaxFileLength>; + private: + /* Subtypes. */ + struct Entry { + u8 key[MaxKeySize]; + void *value; + size_t key_size; + size_t value_size; + }; + static_assert(util::is_pod<Entry>::value, "FileKeyValueStore::Entry definition!"); + + class Cache { + private: + u8 *m_backing_buffer = nullptr; + size_t m_backing_buffer_size = 0; + size_t m_backing_buffer_free_offset = 0; + Entry *m_entries = nullptr; + size_t m_count = 0; + size_t m_capacity = 0; + private: + void *Allocate(size_t size); + + bool HasEntries() const { + return m_entries != nullptr && m_capacity != 0; + } + public: + Result Initialize(void *buffer, size_t buffer_size, size_t capacity); + void Invalidate(); + util::optional<size_t> TryGet(void *out_value, size_t max_out_size, const void *key, size_t key_size); + util::optional<size_t> TryGetSize(const void *key, size_t key_size); + void Set(const void *key, size_t key_size, const void *value, size_t value_size); + bool Contains(const void *key, size_t key_size); + }; + private: + os::SdkMutex m_lock; + Path m_dir_path; + Cache m_cache; + private: + Path GetPath(const void *key, size_t key_size); + Result GetKey(size_t *out_size, void *out_key, size_t max_out_size, const FileName &file_name); + public: + FileKeyValueStore() : m_lock() { /* ... */ } + + /* Basic accessors. */ + Result Initialize(const char *dir); + Result InitializeWithCache(const char *dir, void *cache_buffer, size_t cache_buffer_size, size_t cache_capacity); + Result Get(size_t *out_size, void *out_value, size_t max_out_size, const void *key, size_t key_size); + Result GetSize(size_t *out_size, const void *key, size_t key_size); + Result Set(const void *key, size_t key_size, const void *value, size_t value_size); + Result Remove(const void *key, size_t key_size); + + /* Niceties. */ + template<typename Key> + Result Get(size_t *out_size, void *out_value, size_t max_out_size, const Key &key) { + static_assert(util::is_pod<Key>::value && sizeof(Key) <= MaxKeySize, "Invalid FileKeyValueStore Key!"); + R_RETURN(this->Get(out_size, out_value, max_out_size, std::addressof(key), sizeof(Key))); + } + + template<typename Key, typename Value> + Result Get(Value *out_value, const Key &key) { + static_assert(util::is_pod<Value>::value && !std::is_pointer<Value>::value, "Invalid FileKeyValueStore Value!"); + size_t size = 0; + R_TRY(this->Get(std::addressof(size), out_value, sizeof(Value), key)); + AMS_ABORT_UNLESS(size >= sizeof(Value)); + R_SUCCEED(); + } + + template<typename Key> + Result GetSize(size_t *out_size, const Key &key) { + R_RETURN(this->GetSize(out_size, std::addressof(key), sizeof(Key))); + } + + template<typename Key> + Result Set(const Key &key, const void *value, size_t value_size) { + static_assert(util::is_pod<Key>::value && sizeof(Key) <= MaxKeySize, "Invalid FileKeyValueStore Key!"); + R_RETURN(this->Set(std::addressof(key), sizeof(Key), value, value_size)); + } + + template<typename Key, typename Value> + Result Set(const Key &key, const Value &value) { + static_assert(util::is_pod<Value>::value && !std::is_pointer<Value>::value, "Invalid FileKeyValueStore Value!"); + R_RETURN(this->Set(key, std::addressof(value), sizeof(Value))); + } + + template<typename Key> + Result Remove(const Key &key) { + R_RETURN(this->Remove(std::addressof(key), sizeof(Key))); + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_memory_key_value_store.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_memory_key_value_store.hpp new file mode 100644 index 00000000..0b09098d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/kvdb/kvdb_memory_key_value_store.hpp @@ -0,0 +1,554 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/fs/fs_file.hpp> +#include <stratosphere/fs/fs_directory.hpp> +#include <stratosphere/fs/fs_filesystem.hpp> +#include <stratosphere/kvdb/kvdb_auto_buffer.hpp> +#include <stratosphere/kvdb/kvdb_archive.hpp> +#include <stratosphere/kvdb/kvdb_bounded_string.hpp> + +namespace ams::kvdb { + + template<class Key> + class MemoryKeyValueStore { + static_assert(util::is_pod<Key>::value, "KeyValueStore Keys must be pod!"); + NON_COPYABLE(MemoryKeyValueStore); + NON_MOVEABLE(MemoryKeyValueStore); + public: + /* Subtypes. */ + class Entry { + private: + Key m_key; + void *m_value; + size_t m_value_size; + public: + constexpr Entry(const Key &k, void *v, size_t s) : m_key(k), m_value(v), m_value_size(s) { /* ... */ } + + const Key &GetKey() const { + return m_key; + } + + template<typename Value = void> + Value *GetValuePointer() { + /* Size check. Note: Nintendo does not size check. */ + if constexpr (!std::is_same<Value, void>::value) { + AMS_ABORT_UNLESS(sizeof(Value) <= m_value_size); + /* Ensure we only get pod. */ + static_assert(util::is_pod<Value>::value, "KeyValueStore Values must be pod"); + } + return reinterpret_cast<Value *>(m_value); + } + + template<typename Value = void> + const Value *GetValuePointer() const { + /* Size check. Note: Nintendo does not size check. */ + if constexpr (!std::is_same<Value, void>::value) { + AMS_ABORT_UNLESS(sizeof(Value) <= m_value_size); + /* Ensure we only get pod. */ + static_assert(util::is_pod<Value>::value, "KeyValueStore Values must be pod"); + } + return reinterpret_cast<Value *>(m_value); + } + + template<typename Value> + Value &GetValue() { + return *(this->GetValuePointer<Value>()); + } + + template<typename Value> + const Value &GetValue() const { + return *(this->GetValuePointer<Value>()); + } + + size_t GetValueSize() const { + return m_value_size; + } + + constexpr inline bool operator<(const Key &rhs) const { + return m_key < rhs; + } + + constexpr inline bool operator==(const Key &rhs) const { + return m_key == rhs; + } + }; + + class Index { + private: + size_t m_count; + size_t m_capacity; + Entry *m_entries; + MemoryResource *m_memory_resource; + public: + Index() : m_count(0), m_capacity(0), m_entries(nullptr), m_memory_resource(nullptr) { /* ... */ } + + ~Index() { + if (m_entries != nullptr) { + this->ResetEntries(); + m_memory_resource->Deallocate(m_entries, sizeof(Entry) * m_capacity); + m_entries = nullptr; + } + } + + size_t GetCount() const { + return m_count; + } + + size_t GetCapacity() const { + return m_capacity; + } + + void ResetEntries() { + for (size_t i = 0; i < m_count; i++) { + m_memory_resource->Deallocate(m_entries[i].GetValuePointer(), m_entries[i].GetValueSize()); + } + m_count = 0; + } + + Result Initialize(size_t capacity, MemoryResource *mr) { + m_entries = reinterpret_cast<Entry *>(mr->Allocate(sizeof(Entry) * capacity)); + R_UNLESS(m_entries != nullptr, kvdb::ResultAllocationFailed()); + m_capacity = capacity; + m_memory_resource = mr; + R_SUCCEED(); + } + + Result Set(const Key &key, const void *value, size_t value_size) { + /* Find entry for key. */ + Entry *it = this->lower_bound(key); + if (it != this->end() && it->GetKey() == key) { + /* Entry already exists. Free old value. */ + m_memory_resource->Deallocate(it->GetValuePointer(), it->GetValueSize()); + } else { + /* We need to add a new entry. Check we have room, move future keys forward. */ + R_UNLESS(m_count < m_capacity, kvdb::ResultOutOfKeyResource()); + std::memmove(it + 1, it, sizeof(*it) * (this->end() - it)); + m_count++; + } + + /* Allocate new value. */ + void *new_value = m_memory_resource->Allocate(value_size); + R_UNLESS(new_value != nullptr, kvdb::ResultAllocationFailed()); + std::memcpy(new_value, value, value_size); + + /* Save the new Entry in the map. */ + *it = Entry(key, new_value, value_size); + R_SUCCEED(); + } + + Result AddUnsafe(const Key &key, void *value, size_t value_size) { + R_UNLESS(m_count < m_capacity, kvdb::ResultOutOfKeyResource()); + + m_entries[m_count++] = Entry(key, value, value_size); + R_SUCCEED(); + } + + Result Remove(const Key &key) { + /* Find entry for key. */ + Entry *it = this->find(key); + R_UNLESS(it != this->end(), kvdb::ResultKeyNotFound()); + + /* Free the value, move entries back. */ + m_memory_resource->Deallocate(it->GetValuePointer(), it->GetValueSize()); + std::memmove(it, it + 1, sizeof(*it) * (this->end() - (it + 1))); + m_count--; + R_SUCCEED(); + } + + Entry *begin() { + return this->GetBegin(); + } + + const Entry *begin() const { + return this->GetBegin(); + } + + Entry *end() { + return this->GetEnd(); + } + + const Entry *end() const { + return this->GetEnd(); + } + + const Entry *cbegin() const { + return this->begin(); + } + + const Entry *cend() const { + return this->end(); + } + + Entry *lower_bound(const Key &key) { + return this->GetLowerBound(key); + } + + const Entry *lower_bound(const Key &key) const { + return this->GetLowerBound(key); + } + + Entry *find(const Key &key) { + return this->Find(key); + } + + const Entry *find(const Key &key) const { + return this->Find(key); + } + private: + Entry *GetBegin() { + return m_entries; + } + + const Entry *GetBegin() const { + return m_entries; + } + + Entry *GetEnd() { + return this->GetBegin() + m_count; + } + + const Entry *GetEnd() const { + return this->GetBegin() + m_count; + } + + Entry *GetLowerBound(const Key &key) { + return std::lower_bound(this->GetBegin(), this->GetEnd(), key); + } + + const Entry *GetLowerBound(const Key &key) const { + return std::lower_bound(this->GetBegin(), this->GetEnd(), key); + } + + Entry *Find(const Key &key) { + auto it = this->GetLowerBound(key); + auto end = this->GetEnd(); + if (it != end && it->GetKey() == key) { + return it; + } + return end; + } + + const Entry *Find(const Key &key) const { + auto it = this->GetLowerBound(key); + auto end = this->GetEnd(); + if (it != end && it->GetKey() == key) { + return it; + } + return end; + } + }; + private: + using Path = kvdb::BoundedString<fs::EntryNameLengthMax>; + private: + Index m_index; + Path m_path; + Path m_temp_path; + MemoryResource *m_memory_resource; + public: + MemoryKeyValueStore() { /* ... */ } + + Result Initialize(const char *dir, size_t capacity, MemoryResource *mr) { + /* Ensure that the passed path is a directory. */ + fs::DirectoryEntryType entry_type; + R_TRY(fs::GetEntryType(std::addressof(entry_type), dir)); + R_UNLESS(entry_type == fs::DirectoryEntryType_Directory, fs::ResultPathNotFound()); + + /* Set paths. */ + m_path.AssignFormat("%s%s", dir, "/imkvdb.arc"); + m_temp_path.AssignFormat("%s%s", dir, "/imkvdb.tmp"); + + /* Initialize our index. */ + R_TRY(m_index.Initialize(capacity, mr)); + m_memory_resource = mr; + + R_SUCCEED(); + } + + Result InitializeForReadOnlyArchiveFile(const char *path, size_t capacity, MemoryResource *mr) { + /* Ensure that the passed path is a directory. */ + fs::DirectoryEntryType entry_type; + R_TRY(fs::GetEntryType(std::addressof(entry_type), path)); + R_UNLESS(entry_type == fs::DirectoryEntryType_File, fs::ResultPathNotFound()); + + /* Set paths. */ + m_path.Assign(path); + m_temp_path.Assign(""); + + /* Initialize our index. */ + R_TRY(m_index.Initialize(capacity, mr)); + m_memory_resource = mr; + + R_SUCCEED(); + } + + Result Initialize(size_t capacity, MemoryResource *mr) { + /* This initializes without an archive file. */ + /* A store initialized this way cannot have its contents loaded from or flushed to disk. */ + m_path.Assign(""); + m_temp_path.Assign(""); + + /* Initialize our index. */ + R_TRY(m_index.Initialize(capacity, mr)); + m_memory_resource = mr; + R_SUCCEED(); + } + + size_t GetCount() const { + return m_index.GetCount(); + } + + size_t GetCapacity() const { + return m_index.GetCapacity(); + } + + Result Load() { + /* Reset any existing entries. */ + m_index.ResetEntries(); + + /* Try to read the archive -- note, path not found is a success condition. */ + /* This is because no archive file = no entries, so we're in the right state. */ + AutoBuffer buffer; + R_TRY_CATCH(this->ReadArchiveFile(std::addressof(buffer))) { + R_CONVERT(fs::ResultPathNotFound, ResultSuccess()); + } R_END_TRY_CATCH; + + /* Parse entries from the buffer. */ + { + ArchiveReader reader(buffer); + + size_t entry_count = 0; + R_TRY(reader.ReadEntryCount(std::addressof(entry_count))); + + for (size_t i = 0; i < entry_count; i++) { + /* Get size of key/value. */ + size_t key_size = 0, value_size = 0; + R_TRY(reader.GetEntrySize(std::addressof(key_size), std::addressof(value_size))); + + /* Allocate memory for value. */ + void *new_value = m_memory_resource->Allocate(value_size); + R_UNLESS(new_value != nullptr, kvdb::ResultAllocationFailed()); + + /* If we fail before adding to the index, deallocate our value. */ + ON_RESULT_FAILURE { m_memory_resource->Deallocate(new_value, value_size); }; + + /* Read key and value. */ + Key key; + R_TRY(reader.ReadEntry(std::addressof(key), sizeof(key), new_value, value_size)); + R_TRY(m_index.AddUnsafe(key, new_value, value_size)); + } + } + + R_SUCCEED(); + } + + Result Save(bool destructive = false) { + /* Create a buffer to hold the archive. */ + AutoBuffer buffer; + R_TRY(buffer.Initialize(this->GetArchiveSize())); + + /* Write the archive to the buffer. */ + { + ArchiveWriter writer(buffer); + writer.WriteHeader(this->GetCount()); + for (const auto &it : m_index) { + const auto &key = it.GetKey(); + writer.WriteEntry(std::addressof(key), sizeof(Key), it.GetValuePointer(), it.GetValueSize()); + } + } + + /* Save the buffer to disk. */ + R_RETURN(this->Commit(buffer, destructive)); + } + + Result Set(const Key &key, const void *value, size_t value_size) { + R_RETURN(m_index.Set(key, value, value_size)); + } + + template<typename Value> + Result Set(const Key &key, const Value &value) { + /* Only allow setting pod. */ + static_assert(util::is_pod<Value>::value, "KeyValueStore Values must be pod"); + R_RETURN(this->Set(key, std::addressof(value), sizeof(Value))); + } + + template<typename Value> + Result Set(const Key &key, const Value *value) { + /* Only allow setting pod. */ + static_assert(util::is_pod<Value>::value, "KeyValueStore Values must be pod"); + R_RETURN(this->Set(key, value, sizeof(Value))); + } + + Result Get(size_t *out_size, void *out_value, size_t max_out_size, const Key &key) { + /* Find entry. */ + auto it = this->find(key); + R_UNLESS(it != this->end(), kvdb::ResultKeyNotFound()); + + size_t size = std::min(max_out_size, it->GetValueSize()); + std::memcpy(out_value, it->GetValuePointer(), size); + *out_size = size; + R_SUCCEED(); + } + + template<typename Value = void> + Result GetValuePointer(Value **out_value, const Key &key) { + /* Find entry. */ + auto it = this->find(key); + R_UNLESS(it != this->end(), kvdb::ResultKeyNotFound()); + + *out_value = it->template GetValuePointer<Value>(); + R_SUCCEED(); + } + + template<typename Value = void> + Result GetValuePointer(const Value **out_value, const Key &key) const { + /* Find entry. */ + auto it = this->find(key); + R_UNLESS(it != this->end(), kvdb::ResultKeyNotFound()); + + *out_value = it->template GetValuePointer<Value>(); + R_SUCCEED(); + } + + template<typename Value> + Result GetValue(Value *out_value, const Key &key) const { + /* Find entry. */ + auto it = this->find(key); + R_UNLESS(it != this->end(), kvdb::ResultKeyNotFound()); + + *out_value = it->template GetValue<Value>(); + R_SUCCEED(); + } + + Result GetValueSize(size_t *out_size, const Key &key) const { + /* Find entry. */ + auto it = this->find(key); + R_UNLESS(it != this->end(), kvdb::ResultKeyNotFound()); + + *out_size = it->GetValueSize(); + R_SUCCEED(); + } + + Result Remove(const Key &key) { + R_RETURN(m_index.Remove(key)); + } + + Entry *begin() { + return m_index.begin(); + } + + const Entry *begin() const { + return m_index.begin(); + } + + Entry *end() { + return m_index.end(); + } + + const Entry *end() const { + return m_index.end(); + } + + const Entry *cbegin() const { + return m_index.cbegin(); + } + + const Entry *cend() const { + return m_index.cend(); + } + + Entry *lower_bound(const Key &key) { + return m_index.lower_bound(key); + } + + const Entry *lower_bound(const Key &key) const { + return m_index.lower_bound(key); + } + + Entry *find(const Key &key) { + return m_index.find(key); + } + + const Entry *find(const Key &key) const { + return m_index.find(key); + } + private: + Result SaveArchiveToFile(const char *path, const void *buf, size_t size) { + /* Try to delete the archive, but allow deletion failure. */ + fs::DeleteFile(path); + + /* Create new archive. */ + R_TRY(fs::CreateFile(path, size)); + + /* Write data to the archive. */ + { + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Write)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + R_TRY(fs::WriteFile(file, 0, buf, size, fs::WriteOption::Flush)); + } + + R_SUCCEED(); + } + + Result Commit(const AutoBuffer &buffer, bool destructive) { + if (destructive) { + /* Delete and save to the real archive. */ + R_TRY(SaveArchiveToFile(m_path.Get(), buffer.Get(), buffer.GetSize())); + } else { + /* Delete and save to a temporary archive. */ + R_TRY(SaveArchiveToFile(m_temp_path.Get(), buffer.Get(), buffer.GetSize())); + + /* Try to delete the saved archive, but allow deletion failure. */ + fs::DeleteFile(m_path.Get()); + + /* Rename the path. */ + R_TRY(fs::RenameFile(m_temp_path.Get(), m_path.Get())); + } + + R_SUCCEED(); + } + + size_t GetArchiveSize() const { + ArchiveSizeHelper size_helper; + + for (const auto &it : m_index) { + size_helper.AddEntry(sizeof(Key), it.GetValueSize()); + } + + return size_helper.GetSize(); + } + + Result ReadArchiveFile(AutoBuffer *dst) const { + /* Open the file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), m_path, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Get the archive file size. */ + s64 archive_size; + R_TRY(fs::GetFileSize(std::addressof(archive_size), file)); + + /* Make a new buffer, read the file. */ + R_TRY(dst->Initialize(static_cast<size_t>(archive_size))); + R_TRY(fs::ReadFile(file, 0, dst->Get(), dst->GetSize())); + + R_SUCCEED(); + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr.hpp new file mode 100644 index 00000000..db44eb84 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/ldr/ldr_types.hpp> +#include <stratosphere/ldr/ldr_shell_api.hpp> +#include <stratosphere/ldr/ldr_pm_api.hpp> +#include <stratosphere/ldr/impl/ldr_process_manager_interface.hpp> +#include <stratosphere/ldr/impl/ldr_debug_monitor_interface.hpp> +#include <stratosphere/ldr/impl/ldr_shell_interface.hpp> \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr/impl/ldr_debug_monitor_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr/impl/ldr_debug_monitor_interface.hpp new file mode 100644 index 00000000..d27cdf52 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr/impl/ldr_debug_monitor_interface.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/ldr/ldr_types.hpp> +#include <stratosphere/sf.hpp> + +#define AMS_LDR_I_DEBUG_MONITOR_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, SetProgramArgumentDeprecated, (ncm::ProgramId program_id, const sf::InPointerBuffer &args, u32 args_size), (program_id, args, args_size), hos::Version_Min, hos::Version_10_2_0) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, SetProgramArgument, (ncm::ProgramId program_id, const sf::InPointerBuffer &args), (program_id, args), hos::Version_11_0_0 ) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, FlushArguments, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, GetProcessModuleInfo, (sf::Out<u32> count, const sf::OutPointerArray<ldr::ModuleInfo> &out, os::ProcessId process_id), (count, out, process_id)) \ + AMS_SF_METHOD_INFO(C, H, 65000, void, AtmosphereHasLaunchedBootProgram, (sf::Out<bool> out, ncm::ProgramId program_id), (out, program_id)) + +AMS_SF_DEFINE_INTERFACE(ams::ldr::impl, IDebugMonitorInterface, AMS_LDR_I_DEBUG_MONITOR_INTERFACE_INTERFACE_INFO, 0xEE195D22) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr/impl/ldr_process_manager_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr/impl/ldr_process_manager_interface.hpp new file mode 100644 index 00000000..39c0bc3d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr/impl/ldr_process_manager_interface.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/ldr/ldr_types.hpp> +#include <stratosphere/sf.hpp> + +#define AMS_LDR_I_PROCESS_MANAGER_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, CreateProcess, (sf::OutMoveHandle proc_h, ldr::PinId id, u32 flags, sf::CopyHandle &&reslimit_h, const ldr::ProgramAttributes &attrs), (proc_h, id, flags, std::move(reslimit_h), attrs)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, GetProgramInfo, (sf::Out<ldr::ProgramInfo> out_program_info, const ncm::ProgramLocation &loc, const ldr::ProgramAttributes &attrs), (out_program_info, loc, attrs)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, PinProgram, (sf::Out<ldr::PinId> out_id, const ncm::ProgramLocation &loc), (out_id, loc)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, UnpinProgram, (ldr::PinId id), (id)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, SetEnabledProgramVerification, (bool enabled), (enabled), hos::Version_10_0_0) \ + AMS_SF_METHOD_INFO(C, H, 65000, void, AtmosphereHasLaunchedBootProgram, (sf::Out<bool> out, ncm::ProgramId program_id), (out, program_id)) \ + AMS_SF_METHOD_INFO(C, H, 65001, Result, AtmosphereGetProgramInfo, (sf::Out<ldr::ProgramInfo> out_program_info, sf::Out<cfg::OverrideStatus> out_status, const ncm::ProgramLocation &loc, const ldr::ProgramAttributes &attrs), (out_program_info, out_status, loc, attrs)) \ + AMS_SF_METHOD_INFO(C, H, 65002, Result, AtmospherePinProgram, (sf::Out<ldr::PinId> out_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status), (out_id, loc, override_status)) + +AMS_SF_DEFINE_INTERFACE(ams::ldr::impl, IProcessManagerInterface, AMS_LDR_I_PROCESS_MANAGER_INTERFACE_INTERFACE_INFO, 0x01518B8E) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr/impl/ldr_shell_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr/impl/ldr_shell_interface.hpp new file mode 100644 index 00000000..0333e7da --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr/impl/ldr_shell_interface.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/ldr/ldr_types.hpp> +#include <stratosphere/sf.hpp> + +#define AMS_LDR_I_SHELL_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, SetProgramArgumentDeprecated, (ncm::ProgramId program_id, const sf::InPointerBuffer &args, u32 args_size), (program_id, args, args_size), hos::Version_Min, hos::Version_10_2_0) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, SetProgramArgument, (ncm::ProgramId program_id, const sf::InPointerBuffer &args), (program_id, args), hos::Version_11_0_0 ) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, FlushArguments, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 65000, Result, AtmosphereRegisterExternalCode, (sf::OutMoveHandle out, ncm::ProgramId program_id), (out, program_id)) \ + AMS_SF_METHOD_INFO(C, H, 65001, void, AtmosphereUnregisterExternalCode, (ncm::ProgramId program_id), (program_id)) + +AMS_SF_DEFINE_INTERFACE(ams::ldr::impl, IShellInterface, AMS_LDR_I_SHELL_INTERFACE_INTERFACE_INFO, 0x3EE5B554) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr/ldr_pm_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr/ldr_pm_api.hpp new file mode 100644 index 00000000..310c6fa5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr/ldr_pm_api.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/ldr/ldr_types.hpp> + +namespace ams::ldr::pm { + + /* Process Manager API. */ + Result CreateProcess(os::NativeHandle *out, PinId pin_id, u32 flags, os::NativeHandle reslimit, ldr::ProgramAttributes attrs); + Result GetProgramInfo(ProgramInfo *out, const ncm::ProgramLocation &loc, ldr::ProgramAttributes attrs); + Result PinProgram(PinId *out, const ncm::ProgramLocation &loc); + Result UnpinProgram(PinId pin_id); + Result SetEnabledProgramVerification(bool enabled); + Result HasLaunchedBootProgram(bool *out, ncm::ProgramId program_id); + + /* Atmosphere extension API. */ + Result AtmosphereGetProgramInfo(ProgramInfo *out, cfg::OverrideStatus *out_status, const ncm::ProgramLocation &loc, ldr::ProgramAttributes attrs); + Result AtmospherePinProgram(PinId *out, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr/ldr_shell_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr/ldr_shell_api.hpp new file mode 100644 index 00000000..9519da79 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr/ldr_shell_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> +#include <stratosphere/ldr/ldr_types.hpp> + +namespace ams::ldr { + + /* Shell API. */ + Result InitializeForShell(); + Result FinalizeForShell(); + + Result SetProgramArgument(ncm::ProgramId program_id, const void *arg, size_t size); + Result FlushArguments(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr/ldr_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr/ldr_types.hpp new file mode 100644 index 00000000..5cc2fbb8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ldr/ldr_types.hpp @@ -0,0 +1,275 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> +#include <stratosphere/ncm/ncm_program_location.hpp> +#include <stratosphere/sf/sf_buffer_tags.hpp> +#include <stratosphere/ncm/ncm_content_meta_platform.hpp> +#include <stratosphere/fs/fs_content_attributes.hpp> + +namespace ams::ldr { + + /* General types. */ + struct ProgramInfo : public sf::LargeData { + u8 main_thread_priority; + u8 default_cpu_id; + u16 flags; + u32 main_thread_stack_size; + ncm::ProgramId program_id; + u32 acid_sac_size; + u32 aci_sac_size; + u32 acid_fac_size; + u32 aci_fah_size; + u8 unused_20[0x10]; + u8 ac_buffer[0x3E0]; + }; + static_assert(util::is_pod<ProgramInfo>::value && sizeof(ProgramInfo) == 0x410, "ProgramInfo definition!"); + + enum ProgramInfoFlag { + ProgramInfoFlag_SystemModule = (0 << 0), + ProgramInfoFlag_Application = (1 << 0), + ProgramInfoFlag_Applet = (2 << 0), + ProgramInfoFlag_InvalidType = (3 << 0), + ProgramInfoFlag_ApplicationTypeMask = (3 << 0), + + ProgramInfoFlag_AllowDebug = (1 << 2), + }; + + enum CreateProcessFlag { + CreateProcessFlag_EnableDebug = (1 << 0), + CreateProcessFlag_DisableAslr = (1 << 1), + }; + + struct ProgramArguments { + u32 allocated_size; + u32 arguments_size; + u8 reserved[0x18]; + u8 arguments[]; + }; + static_assert(sizeof(ProgramArguments) == 0x20, "ProgramArguments definition!"); + + struct PinId { + u64 value; + }; + + inline bool operator==(const PinId &lhs, const PinId &rhs) { + return lhs.value == rhs.value; + } + + inline bool operator!=(const PinId &lhs, const PinId &rhs) { + return lhs.value != rhs.value; + } + static_assert(sizeof(PinId) == sizeof(u64) && util::is_pod<PinId>::value, "PinId definition!"); + + struct ModuleInfo { + u8 module_id[0x20]; + u64 address; + u64 size; + }; + static_assert(sizeof(ModuleInfo) == 0x30); + + /* NSO types. */ + struct NsoHeader { + static constexpr u32 Magic = util::FourCC<'N','S','O','0'>::Code; + enum Segment : size_t { + Segment_Text = 0, + Segment_Ro = 1, + Segment_Rw = 2, + Segment_Count, + }; + + enum Flag : u32 { + Flag_CompressedText = (1 << 0), + Flag_CompressedRo = (1 << 1), + Flag_CompressedRw = (1 << 2), + Flag_CheckHashText = (1 << 3), + Flag_CheckHashRo = (1 << 4), + Flag_CheckHashRw = (1 << 5), + }; + + struct SegmentInfo { + u32 file_offset; + u32 dst_offset; + u32 size; + u32 reserved; + }; + + u32 magic; + u32 version; + u32 reserved_08; + u32 flags; + union { + struct { + u32 text_file_offset; + u32 text_dst_offset; + u32 text_size; + u32 unk_file_offset; + u32 ro_file_offset; + u32 ro_dst_offset; + u32 ro_size; + u32 unk_size; + u32 rw_file_offset; + u32 rw_dst_offset; + u32 rw_size; + u32 bss_size; + }; + SegmentInfo segments[Segment_Count]; + }; + u8 module_id[sizeof(ModuleInfo::module_id)]; + union { + u32 compressed_sizes[Segment_Count]; + struct { + u32 text_compressed_size; + u32 ro_compressed_size; + u32 rw_compressed_size; + }; + }; + u8 reserved_6C[0x34]; + union { + u8 segment_hashes[Segment_Count][crypto::Sha256Generator::HashSize]; + struct { + u8 text_hash[crypto::Sha256Generator::HashSize]; + u8 ro_hash[crypto::Sha256Generator::HashSize]; + u8 rw_hash[crypto::Sha256Generator::HashSize]; + }; + }; + }; + static_assert(sizeof(NsoHeader) == 0x100 && util::is_pod<NsoHeader>::value, "NsoHeader definition!"); + + /* NPDM types. */ + struct Aci { + static constexpr u32 Magic = util::FourCC<'A','C','I','0'>::Code; + + u32 magic; + u8 reserved_04[0xC]; + ncm::ProgramId program_id; + u8 reserved_18[0x8]; + u32 fah_offset; + u32 fah_size; + u32 sac_offset; + u32 sac_size; + u32 kac_offset; + u32 kac_size; + u8 reserved_38[0x8]; + }; + static_assert(sizeof(Aci) == 0x40 && util::is_pod<Aci>::value, "Aci definition!"); + + struct Acid { + static constexpr u32 Magic = util::FourCC<'A','C','I','D'>::Code; + + enum AcidFlag { + AcidFlag_Production = (1 << 0), + AcidFlag_UnqualifiedApproval = (1 << 1), + + AcidFlag_DeprecatedUseSecureMemory = (1 << 2), + + AcidFlag_PoolPartitionShift = 2, + AcidFlag_PoolPartitionMask = (0xF << AcidFlag_PoolPartitionShift), + }; + + enum PoolPartition { + PoolPartition_Application = 0, + PoolPartition_Applet = 1, + PoolPartition_System = 2, + PoolPartition_SystemNonSecure = 3, + }; + + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(PoolPartition_Application == (svc::CreateProcessFlag_PoolPartitionApplication >> svc::CreateProcessFlag_PoolPartitionShift)); + static_assert(PoolPartition_Applet == (svc::CreateProcessFlag_PoolPartitionApplet >> svc::CreateProcessFlag_PoolPartitionShift)); + static_assert(PoolPartition_System == (svc::CreateProcessFlag_PoolPartitionSystem >> svc::CreateProcessFlag_PoolPartitionShift)); + static_assert(PoolPartition_SystemNonSecure == (svc::CreateProcessFlag_PoolPartitionSystemNonSecure >> svc::CreateProcessFlag_PoolPartitionShift)); + #endif + + u8 signature[0x100]; + u8 modulus[0x100]; + u32 magic; + u32 size; + u8 version; + u8 unknown_209; + u8 reserved_20A[2]; + u32 flags; + ncm::ProgramId program_id_min; + ncm::ProgramId program_id_max; + u32 fac_offset; + u32 fac_size; + u32 sac_offset; + u32 sac_size; + u32 kac_offset; + u32 kac_size; + u8 reserved_238[0x8]; + }; + static_assert(sizeof(Acid) == 0x240 && util::is_pod<Acid>::value, "Acid definition!"); + + struct Npdm { + static constexpr u32 Magic = util::FourCC<'M','E','T','A'>::Code; + + enum MetaFlag { + MetaFlag_Is64Bit = (1 << 0), + + MetaFlag_AddressSpaceTypeShift = 1, + MetaFlag_AddressSpaceTypeMask = (7 << MetaFlag_AddressSpaceTypeShift), + + MetaFlag_OptimizeMemoryAllocation = (1 << 4), + MetaFlag_DisableDeviceAddressSpaceMerge = (1 << 5), + MetaFlag_EnableAliasRegionExtraSize = (1 << 6), + MetaFlag_PreventCodeReads = (1 << 7), + }; + + enum AddressSpaceType { + AddressSpaceType_32Bit = 0, + AddressSpaceType_64BitDeprecated = 1, + AddressSpaceType_32BitWithoutAlias = 2, + AddressSpaceType_64Bit = 3, + }; + + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(AddressSpaceType_32Bit == (svc::CreateProcessFlag_AddressSpace32Bit >> svc::CreateProcessFlag_AddressSpaceShift)); + static_assert(AddressSpaceType_64BitDeprecated == (svc::CreateProcessFlag_AddressSpace64BitDeprecated >> svc::CreateProcessFlag_AddressSpaceShift)); + static_assert(AddressSpaceType_32BitWithoutAlias == (svc::CreateProcessFlag_AddressSpace32BitWithoutAlias >> svc::CreateProcessFlag_AddressSpaceShift)); + static_assert(AddressSpaceType_64Bit == (svc::CreateProcessFlag_AddressSpace64Bit >> svc::CreateProcessFlag_AddressSpaceShift)); + #endif + + u32 magic; + u32 signature_key_generation; + u8 reserved_08[4]; + u8 flags; + u8 reserved_0D; + u8 main_thread_priority; + u8 default_cpu_id; + u8 reserved_10[4]; + u32 system_resource_size; + u32 version; + u32 main_thread_stack_size; + char program_name[0x10]; + char product_code[0x10]; + u8 reserved_40[0x30]; + u32 aci_offset; + u32 aci_size; + u32 acid_offset; + u32 acid_size; + }; + static_assert(sizeof(Npdm) == 0x80 && util::is_pod<Npdm>::value, "Npdm definition!"); + + struct ProgramAttributes { + ncm::ContentMetaPlatform platform; + fs::ContentAttributes content_attributes; + }; + static_assert(sizeof(ProgramAttributes) == 2 && util::is_pod<ProgramAttributes>::value, "ProgramAttributes definition!"); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lm.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lm.hpp new file mode 100644 index 00000000..f759c1a4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lm.hpp @@ -0,0 +1,21 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/lm/lm_types.hpp> +#include <stratosphere/lm/lm_api.hpp> +#include <stratosphere/lm/lm_log_getter.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lm/lm_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lm/lm_api.hpp new file mode 100644 index 00000000..91dcc690 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lm/lm_api.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::lm { + + void Initialize(); + void Finalize(); + + void SetDestination(u32 destination); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lm/lm_log_getter.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lm/lm_log_getter.hpp new file mode 100644 index 00000000..950af6c2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lm/lm_log_getter.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::lm { + + void StartLogging(); + void StopLogging(); + + void GetLog(char *dst, size_t size, s64 *out_write_size, u32 *out_drop_count); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lm/lm_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lm/lm_types.hpp new file mode 100644 index 00000000..a38b3f3d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lm/lm_types.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::lm { + + enum LogDestination { + LogDestination_TargetManager = (1 << 0), + LogDestination_Uart = (1 << 1), + LogDestination_UartIfSleep = (1 << 2), + + LogDestination_All = 0xFFFF, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lmem.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lmem.hpp new file mode 100644 index 00000000..19e4dace --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lmem.hpp @@ -0,0 +1,20 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <stratosphere/lmem/lmem_common.hpp> +#include <stratosphere/lmem/lmem_exp_heap.hpp> +#include <stratosphere/lmem/lmem_unit_heap.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lmem/impl/lmem_impl_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lmem/impl/lmem_impl_common.hpp new file mode 100644 index 00000000..00ce8753 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lmem/impl/lmem_impl_common.hpp @@ -0,0 +1,94 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> + +namespace ams::lmem::impl { + + /* NOTE: Nintendo does not use util::IntrusiveListNode. */ + /* They seem to manually manage linked list pointers. */ + /* This is pretty gross, so we're going to use util::IntrusiveListNode. */ + + struct ExpHeapMemoryBlockHead { + u16 magic; + u32 attributes; + size_t block_size; + util::IntrusiveListNode list_node; + }; + static_assert(std::is_trivially_destructible<ExpHeapMemoryBlockHead>::value); + + using ExpHeapMemoryBlockList = typename util::IntrusiveListMemberTraits<&ExpHeapMemoryBlockHead::list_node>::ListType; + + struct ExpHeapHead { + ExpHeapMemoryBlockList free_list; + ExpHeapMemoryBlockList used_list; + u16 group_id; + u16 mode; + bool use_alignment_margins; + char pad[3]; + }; + static_assert(sizeof(ExpHeapHead) == 0x28); + static_assert(std::is_trivially_destructible<ExpHeapHead>::value); + + struct FrameHeapHead { + void *next_block_head; + void *next_block_tail; + }; + static_assert(sizeof(FrameHeapHead) == 0x10); + static_assert(std::is_trivially_destructible<FrameHeapHead>::value); + + struct UnitHead { + UnitHead *next; + }; + + struct UnitHeapList { + UnitHead *head; + }; + + struct UnitHeapHead { + UnitHeapList free_list; + size_t unit_size; + s32 alignment; + s32 num_units; + }; + static_assert(sizeof(UnitHeapHead) == 0x18); + static_assert(std::is_trivially_destructible<UnitHeapHead>::value); + + union ImplementationHeapHead { + ExpHeapHead exp_heap_head; + FrameHeapHead frame_heap_head; + UnitHeapHead unit_heap_head; + }; + + struct HeapHead { + u32 magic; + util::IntrusiveListNode list_node; + + using ChildListTraits = util::IntrusiveListMemberTraits<&HeapHead::list_node>; + using ChildList = ChildListTraits::ListType; + ChildList child_list; + + void *heap_start; + void *heap_end; + os::SdkMutexType mutex; + u8 option; + ImplementationHeapHead impl_head; + }; + static_assert(std::is_trivially_destructible<HeapHead>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lmem/lmem_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lmem/lmem_common.hpp new file mode 100644 index 00000000..181e0110 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lmem/lmem_common.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/lmem/impl/lmem_impl_common.hpp> + +namespace ams::lmem { + + enum CreateOption { + CreateOption_None = (0), + CreateOption_ZeroClear = (1 << 0), + CreateOption_DebugFill = (1 << 1), + CreateOption_ThreadSafe = (1 << 2), + }; + + enum FillType { + FillType_Unallocated, + FillType_Allocated, + FillType_Freed, + FillType_Count, + }; + + namespace impl { + + struct HeapHead; + + } + + using HeapHandle = impl::HeapHead *; + + using HeapCommonHead = impl::HeapHead; + + struct MemoryRange { + uintptr_t address; + size_t size; + }; + + constexpr inline s32 DefaultAlignment = 0x8; + + /* Common API. */ + u32 GetDebugFillValue(FillType fill_type); + void SetDebugFillValue(FillType fill_type, u32 value); + + size_t GetTotalSize(HeapHandle handle); + void *GetStartAddress(HeapHandle handle); + bool ContainsAddress(HeapHandle handle, const void *address); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lmem/lmem_exp_heap.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lmem/lmem_exp_heap.hpp new file mode 100644 index 00000000..d08abcbb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lmem/lmem_exp_heap.hpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/lmem/lmem_common.hpp> + +namespace ams::lmem { + + enum AllocationMode { + AllocationMode_FirstFit, + AllocationMode_BestFit, + }; + + enum AllocationDirection { + AllocationDirection_Front, + AllocationDirection_Back, + }; + + using HeapVisitor = void (*)(void *block, HeapHandle handle, uintptr_t user_data); + + HeapHandle CreateExpHeap(void *address, size_t size, u32 option); + void DestroyExpHeap(HeapHandle handle); + MemoryRange AdjustExpHeap(HeapHandle handle); + + void *AllocateFromExpHeap(HeapHandle handle, size_t size); + void *AllocateFromExpHeap(HeapHandle handle, size_t size, s32 alignment); + void FreeToExpHeap(HeapHandle handle, void *block); + + size_t ResizeExpHeapMemoryBlock(HeapHandle handle, void *block, size_t size); + + size_t GetExpHeapTotalFreeSize(HeapHandle handle); + size_t GetExpHeapAllocatableSize(HeapHandle handle, s32 alignment); + + AllocationMode GetExpHeapAllocationMode(HeapHandle handle); + AllocationMode SetExpHeapAllocationMode(HeapHandle handle, AllocationMode new_mode); + + bool GetExpHeapUseMarginsOfAlignment(HeapHandle handle); + bool SetExpHeapUseMarginsOfAlignment(HeapHandle handle, bool use_margins); + + u16 GetExpHeapGroupId(HeapHandle handle); + u16 SetExpHeapGroupId(HeapHandle handle, u16 group_id); + + size_t GetExpHeapMemoryBlockSize(const void *memory_block); + u16 GetExpHeapMemoryBlockGroupId(const void *memory_block); + AllocationDirection GetExpHeapMemoryBlockAllocationDirection(const void *memory_block); + + void VisitExpHeapAllocatedBlocks(HeapHandle handle, HeapVisitor visitor, uintptr_t user_data); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lmem/lmem_unit_heap.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lmem/lmem_unit_heap.hpp new file mode 100644 index 00000000..db2ff27e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lmem/lmem_unit_heap.hpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/lmem/lmem_common.hpp> + +namespace ams::lmem { + + enum InfoPlacement { + InfoPlacement_Head, + InfoPlacement_Tail, + }; + + HeapHandle CreateUnitHeap(void *address, size_t size, size_t unit_size, u32 option); + HeapHandle CreateUnitHeap(void *address, size_t size, size_t unit_size, u32 option, s32 alignment, InfoPlacement info_placement); + HeapHandle CreateUnitHeap(void *address, size_t size, size_t unit_size, u32 option, s32 alignment, HeapCommonHead *heap_head); + void DestroyUnitHeap(HeapHandle handle); + + void InvalidateUnitHeap(HeapHandle handle); + void ExtendUnitHeap(HeapHandle handle, size_t size); + + void *AllocateFromUnitHeap(HeapHandle handle); + void FreeToUnitHeap(HeapHandle handle, void *block); + + size_t GetUnitHeapUnitSize(HeapHandle handle); + s32 GetUnitHeapAlignment(HeapHandle handle); + size_t GetUnitHeapFreeCount(HeapHandle handle); + size_t GetUnitHeapUsedCount(HeapHandle handle); + + size_t GetUnitHeapRequiredSize(size_t unit_size, size_t unit_count, s32 alignment, bool internal_metadata); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr.hpp new file mode 100644 index 00000000..e3adf440 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr.hpp @@ -0,0 +1,21 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/lr/lr_types.hpp> +#include <stratosphere/lr/lr_api.hpp> +#include <stratosphere/lr/lr_location_resolver_manager_impl.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_add_on_content_location_resolver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_add_on_content_location_resolver.hpp new file mode 100644 index 00000000..877b2317 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_add_on_content_location_resolver.hpp @@ -0,0 +1,75 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/lr/lr_types.hpp> +#include <stratosphere/lr/lr_i_add_on_content_location_resolver.hpp> + +namespace ams::lr { + + class AddOnContentLocationResolver { + NON_COPYABLE(AddOnContentLocationResolver); + private: + sf::SharedPointer<IAddOnContentLocationResolver> m_interface; + public: + AddOnContentLocationResolver() : m_interface(nullptr) { /* ... */ } + explicit AddOnContentLocationResolver(sf::SharedPointer<IAddOnContentLocationResolver> intf) : m_interface(intf) { /* ... */ } + + AddOnContentLocationResolver(AddOnContentLocationResolver &&rhs) { + m_interface = std::move(rhs.m_interface); + } + + AddOnContentLocationResolver &operator=(AddOnContentLocationResolver &&rhs) { + AddOnContentLocationResolver(std::move(rhs)).Swap(*this); + return *this; + } + + void Swap(AddOnContentLocationResolver &rhs) { + std::swap(m_interface, rhs.m_interface); + } + public: + /* Actual commands. */ + Result ResolveAddOnContentPath(Path *out, ncm::DataId id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->ResolveAddOnContentPath(out, id)); + } + + Result RegisterAddOnContentStorage(ncm::DataId id, ncm::ApplicationId application_id, ncm::StorageId storage_id) { + AMS_ASSERT(m_interface != nullptr); + if (hos::GetVersion() >= hos::Version_9_0_0) { + R_RETURN(m_interface->RegisterAddOnContentStorage(id, application_id, storage_id)); + } else { + R_RETURN(m_interface->RegisterAddOnContentStorageDeprecated(id, storage_id)); + } + } + + Result UnregisterAllAddOnContentPath() { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->UnregisterAllAddOnContentPath()); + } + + Result RefreshApplicationAddOnContent(const ncm::ApplicationId *ids, size_t num_ids) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->RefreshApplicationAddOnContent(sf::InArray<ncm::ApplicationId>(ids, num_ids))); + } + + Result UnregisterApplicationAddOnContent(ncm::ApplicationId id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->UnregisterApplicationAddOnContent(id)); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_api.hpp new file mode 100644 index 00000000..4f0320f0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_api.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/lr/lr_types.hpp> +#include <stratosphere/lr/lr_location_resolver.hpp> +#include <stratosphere/lr/lr_add_on_content_location_resolver.hpp> +#include <stratosphere/lr/lr_registered_location_resolver.hpp> + +namespace ams::lr { + + /* Management. */ + void Initialize(); + void Finalize(); + + /* Service API. */ + Result OpenLocationResolver(LocationResolver *out, ncm::StorageId storage_id); + Result OpenRegisteredLocationResolver(RegisteredLocationResolver *out); + Result OpenAddOnContentLocationResolver(AddOnContentLocationResolver *out); + Result RefreshLocationResolver(ncm::StorageId storage_id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_i_add_on_content_location_resolver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_i_add_on_content_location_resolver.hpp new file mode 100644 index 00000000..633a24b7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_i_add_on_content_location_resolver.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/lr/lr_types.hpp> + +#define AMS_LR_I_ADD_ON_CONTENT_LOCATION_RESOLVER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, ResolveAddOnContentPath, (sf::Out<lr::Path> out, ncm::DataId id), (out, id), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, RegisterAddOnContentStorageDeprecated, (ncm::DataId id, ncm::StorageId storage_id), (id, storage_id), hos::Version_2_0_0, hos::Version_8_1_1) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, RegisterAddOnContentStorage, (ncm::DataId id, ncm::ApplicationId application_id, ncm::StorageId storage_id), (id, application_id, storage_id), hos::Version_9_0_0) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, UnregisterAllAddOnContentPath, (), (), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, RefreshApplicationAddOnContent, (const sf::InArray<ncm::ApplicationId> &ids), (ids), hos::Version_9_0_0) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, UnregisterApplicationAddOnContent, (ncm::ApplicationId id), (id), hos::Version_9_0_0) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, GetRegisteredAddOnContentPaths, (sf::Out<lr::PathByMapAlias> out, sf::Out<lr::PathByMapAlias> out2, ncm::DataId id), (out, out2, id), hos::Version_15_0_0) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, RegisterAddOnContentPath, (ncm::DataId id, ncm::ApplicationId application_id, const lr::PathByMapAlias &path), (id, application_id, path), hos::Version_15_0_0) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, RegisterAddOnContentPaths, (ncm::DataId id, ncm::ApplicationId application_id, const lr::PathByMapAlias &path, const lr::PathByMapAlias &path2), (id, application_id, path, path2), hos::Version_15_0_0) + + +AMS_SF_DEFINE_INTERFACE(ams::lr, IAddOnContentLocationResolver, AMS_LR_I_ADD_ON_CONTENT_LOCATION_RESOLVER_INTERFACE_INFO, 0x77617E39) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_i_location_resolver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_i_location_resolver.hpp new file mode 100644 index 00000000..ca039776 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_i_location_resolver.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/lr/lr_types.hpp> + +#define AMS_LR_I_LOCATION_RESOLVER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, ResolveProgramPath, (sf::Out<lr::Path> out, ncm::ProgramId id), (out, id)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, RedirectProgramPath, (const lr::Path &path, ncm::ProgramId id), (path, id)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, ResolveApplicationControlPath, (sf::Out<lr::Path> out, ncm::ProgramId id), (out, id)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, ResolveApplicationHtmlDocumentPath, (sf::Out<lr::Path> out, ncm::ProgramId id), (out, id)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, ResolveDataPath, (sf::Out<lr::Path> out, ncm::DataId id), (out, id)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, RedirectApplicationControlPathDeprecated, (const lr::Path &path, ncm::ProgramId id), (path, id), hos::Version_1_0_0, hos::Version_8_1_1) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, RedirectApplicationControlPath, (const lr::Path &path, ncm::ProgramId id, ncm::ProgramId owner_id), (path, id, owner_id), hos::Version_9_0_0) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, RedirectApplicationHtmlDocumentPathDeprecated, (const lr::Path &path, ncm::ProgramId id), (path, id), hos::Version_1_0_0, hos::Version_8_1_1) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, RedirectApplicationHtmlDocumentPath, (const lr::Path &path, ncm::ProgramId id, ncm::ProgramId owner_id), (path, id, owner_id), hos::Version_9_0_0) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, ResolveApplicationLegalInformationPath, (sf::Out<lr::Path> out, ncm::ProgramId id), (out, id)) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, RedirectApplicationLegalInformationPathDeprecated, (const lr::Path &path, ncm::ProgramId id), (path, id), hos::Version_1_0_0, hos::Version_8_1_1) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, RedirectApplicationLegalInformationPath, (const lr::Path &path, ncm::ProgramId id, ncm::ProgramId owner_id), (path, id, owner_id), hos::Version_9_0_0) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, Refresh, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, RedirectApplicationProgramPathDeprecated, (const lr::Path &path, ncm::ProgramId id), (path, id), hos::Version_5_0_0, hos::Version_8_1_1) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, RedirectApplicationProgramPath, (const lr::Path &path, ncm::ProgramId id, ncm::ProgramId owner_id), (path, id, owner_id), hos::Version_9_0_0) \ + AMS_SF_METHOD_INFO(C, H, 11, Result, ClearApplicationRedirectionDeprecated, (), (), hos::Version_5_0_0, hos::Version_8_1_1) \ + AMS_SF_METHOD_INFO(C, H, 11, Result, ClearApplicationRedirection, (const sf::InArray<ncm::ProgramId> &excluding_ids), (excluding_ids), hos::Version_9_0_0) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, EraseProgramRedirection, (ncm::ProgramId id), (id), hos::Version_5_0_0) \ + AMS_SF_METHOD_INFO(C, H, 13, Result, EraseApplicationControlRedirection, (ncm::ProgramId id), (id), hos::Version_5_0_0) \ + AMS_SF_METHOD_INFO(C, H, 14, Result, EraseApplicationHtmlDocumentRedirection, (ncm::ProgramId id), (id), hos::Version_5_0_0) \ + AMS_SF_METHOD_INFO(C, H, 15, Result, EraseApplicationLegalInformationRedirection, (ncm::ProgramId id), (id), hos::Version_5_0_0) \ + AMS_SF_METHOD_INFO(C, H, 16, Result, ResolveProgramPathForDebug, (sf::Out<lr::Path> out, ncm::ProgramId id), (out, id), hos::Version_7_0_0) \ + AMS_SF_METHOD_INFO(C, H, 17, Result, RedirectProgramPathForDebug, (const lr::Path &path, ncm::ProgramId id), (path, id), hos::Version_7_0_0) \ + AMS_SF_METHOD_INFO(C, H, 18, Result, RedirectApplicationProgramPathForDebugDeprecated, (const lr::Path &path, ncm::ProgramId id), (path, id), hos::Version_7_0_0, hos::Version_8_1_1) \ + AMS_SF_METHOD_INFO(C, H, 18, Result, RedirectApplicationProgramPathForDebug, (const lr::Path &path, ncm::ProgramId id, ncm::ProgramId owner_id), (path, id, owner_id), hos::Version_9_0_0) \ + AMS_SF_METHOD_INFO(C, H, 19, Result, EraseProgramRedirectionForDebug, (ncm::ProgramId id), (id), hos::Version_7_0_0) \ + AMS_SF_METHOD_INFO(C, H, 20, Result, Disable, (), (), hos::Version_15_0_0) + + +AMS_SF_DEFINE_INTERFACE(ams::lr, ILocationResolver, AMS_LR_I_LOCATION_RESOLVER_INTERFACE_INFO, 0xB36C8B0E) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_i_location_resolver_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_i_location_resolver_manager.hpp new file mode 100644 index 00000000..d0f62602 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_i_location_resolver_manager.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/lr/lr_types.hpp> +#include <stratosphere/lr/lr_i_location_resolver.hpp> +#include <stratosphere/lr/lr_i_add_on_content_location_resolver.hpp> +#include <stratosphere/lr/lr_i_registered_location_resolver.hpp> + +#define AMS_LR_I_LOCATION_RESOLVER_MANAGER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, OpenLocationResolver, (sf::Out<ams::sf::SharedPointer<lr::ILocationResolver>> out, ncm::StorageId storage_id), (out, storage_id)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, OpenRegisteredLocationResolver, (sf::Out<ams::sf::SharedPointer<lr::IRegisteredLocationResolver>> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, RefreshLocationResolver, (ncm::StorageId storage_id), (storage_id)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, OpenAddOnContentLocationResolver, (sf::Out<ams::sf::SharedPointer<lr::IAddOnContentLocationResolver>> out), (out), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, SetEnabled, (const ams::sf::InMapAliasArray<ncm::StorageId> &storages), (storages), hos::Version_15_0_0) + +AMS_SF_DEFINE_INTERFACE(ams::lr, ILocationResolverManager, AMS_LR_I_LOCATION_RESOLVER_MANAGER_INTERFACE_INFO, 0xB2950191) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_i_registered_location_resolver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_i_registered_location_resolver.hpp new file mode 100644 index 00000000..ec0e755d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_i_registered_location_resolver.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/lr/lr_types.hpp> + +#define AMS_LR_I_REGISTERED_LOCATION_RESOLVER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, ResolveProgramPath, (sf::Out<lr::Path> out, ncm::ProgramId id), (out, id)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, RegisterProgramPathDeprecated, (const lr::Path &path, ncm::ProgramId id), (path, id), hos::Version_1_0_0, hos::Version_8_1_1) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, RegisterProgramPath, (const lr::Path &path, ncm::ProgramId id, ncm::ProgramId owner_id), (path, id, owner_id), hos::Version_9_0_0) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, UnregisterProgramPath, (ncm::ProgramId id), (id)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, RedirectProgramPathDeprecated, (const lr::Path &path, ncm::ProgramId id), (path, id), hos::Version_1_0_0, hos::Version_8_1_1) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, RedirectProgramPath, (const lr::Path &path, ncm::ProgramId id, ncm::ProgramId owner_id), (path, id, owner_id), hos::Version_9_0_0) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, ResolveHtmlDocumentPath, (sf::Out<lr::Path> out, ncm::ProgramId id), (out, id), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, RegisterHtmlDocumentPathDeprecated, (const lr::Path &path, ncm::ProgramId id), (path, id), hos::Version_2_0_0, hos::Version_8_1_1) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, RegisterHtmlDocumentPath, (const lr::Path &path, ncm::ProgramId id, ncm::ProgramId owner_id), (path, id, owner_id), hos::Version_9_0_0) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, UnregisterHtmlDocumentPath, (ncm::ProgramId id), (id), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, RedirectHtmlDocumentPathDeprecated, (const lr::Path &path, ncm::ProgramId id), (path, id), hos::Version_2_0_0, hos::Version_8_1_1) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, RedirectHtmlDocumentPath, (const lr::Path &path, ncm::ProgramId id, ncm::ProgramId owner_id), (path, id, owner_id), hos::Version_9_0_0) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, Refresh, (), (), hos::Version_7_0_0) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, RefreshExcluding, (const sf::InArray<ncm::ProgramId> &ids), (ids), hos::Version_9_0_0) + +AMS_SF_DEFINE_INTERFACE(ams::lr, IRegisteredLocationResolver, AMS_LR_I_REGISTERED_LOCATION_RESOLVER_INTERFACE_INFO, 0x35346AC9) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_location_resolver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_location_resolver.hpp new file mode 100644 index 00000000..0ee8ad3b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_location_resolver.hpp @@ -0,0 +1,180 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/lr/lr_types.hpp> +#include <stratosphere/lr/lr_i_location_resolver.hpp> + +namespace ams::lr { + + class LocationResolver { + NON_COPYABLE(LocationResolver); + private: + sf::SharedPointer<ILocationResolver> m_interface; + public: + LocationResolver() : m_interface(nullptr) { /* ... */ } + explicit LocationResolver(sf::SharedPointer<ILocationResolver> intf) : m_interface(intf) { /* ... */ } + + LocationResolver(LocationResolver &&rhs) { + m_interface = std::move(rhs.m_interface); + } + + LocationResolver &operator=(LocationResolver &&rhs) { + LocationResolver(std::move(rhs)).swap(*this); + return *this; + } + + void swap(LocationResolver &rhs) { + std::swap(m_interface, rhs.m_interface); + } + public: + Result ResolveProgramPath(Path *out, ncm::ProgramId id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->ResolveProgramPath(out, id)); + } + + void RedirectProgramPath(const Path &path, ncm::ProgramId id) { + AMS_ASSERT(m_interface != nullptr); + R_ABORT_UNLESS(m_interface->RedirectProgramPath(path, id)); + } + + Result ResolveApplicationControlPath(Path *out, ncm::ProgramId id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->ResolveApplicationControlPath(out, id)); + } + + Result ResolveApplicationHtmlDocumentPath(Path *out, ncm::ProgramId id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->ResolveApplicationHtmlDocumentPath(out, id)); + } + + Result ResolveDataPath(Path *out, ncm::DataId id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->ResolveDataPath(out, id)); + } + + void RedirectApplicationControlPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + AMS_ASSERT(m_interface != nullptr); + if (hos::GetVersion() >= hos::Version_9_0_0) { + R_ABORT_UNLESS(m_interface->RedirectApplicationControlPath(path, id, owner_id)); + } else { + R_ABORT_UNLESS(m_interface->RedirectApplicationControlPathDeprecated(path, id)); + } + } + + void RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + AMS_ASSERT(m_interface != nullptr); + if (hos::GetVersion() >= hos::Version_9_0_0) { + R_ABORT_UNLESS(m_interface->RedirectApplicationHtmlDocumentPath(path, id, owner_id)); + } else { + R_ABORT_UNLESS(m_interface->RedirectApplicationHtmlDocumentPathDeprecated(path, id)); + } + } + + Result ResolveApplicationLegalInformationPath(Path *out, ncm::ProgramId id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->ResolveApplicationLegalInformationPath(out, id)); + } + + void RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + AMS_ASSERT(m_interface != nullptr); + if (hos::GetVersion() >= hos::Version_9_0_0) { + R_ABORT_UNLESS(m_interface->RedirectApplicationLegalInformationPath(path, id, owner_id)); + } else { + R_ABORT_UNLESS(m_interface->RedirectApplicationLegalInformationPathDeprecated(path, id)); + } + } + + Result Refresh() { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->Refresh()); + } + + void RedirectApplicationProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + AMS_ASSERT(m_interface != nullptr); + if (hos::GetVersion() >= hos::Version_9_0_0) { + R_ABORT_UNLESS(m_interface->RedirectApplicationProgramPath(path, id, owner_id)); + } else { + R_ABORT_UNLESS(m_interface->RedirectApplicationProgramPathDeprecated(path, id)); + } + } + + Result ClearApplicationRedirection() { + AMS_ASSERT(m_interface != nullptr); + AMS_ASSERT(hos::GetVersion() < hos::Version_9_0_0); + R_RETURN(this->ClearApplicationRedirection(nullptr, 0)); + } + + Result ClearApplicationRedirection(const ncm::ProgramId *excluding_ids, size_t num_ids) { + AMS_ASSERT(m_interface != nullptr); + if (hos::GetVersion() >= hos::Version_9_0_0) { + R_RETURN(m_interface->ClearApplicationRedirection(sf::InArray<ncm::ProgramId>(excluding_ids, num_ids))); + } else { + R_RETURN(m_interface->ClearApplicationRedirectionDeprecated()); + } + } + + Result EraseProgramRedirection(ncm::ProgramId id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->EraseProgramRedirection(id)); + } + + Result EraseApplicationControlRedirection(ncm::ProgramId id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->EraseApplicationControlRedirection(id)); + } + + Result EraseApplicationHtmlDocumentRedirection(ncm::ProgramId id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->EraseApplicationHtmlDocumentRedirection(id)); + } + + Result EraseApplicationLegalInformationRedirection(ncm::ProgramId id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->EraseApplicationLegalInformationRedirection(id)); + } + + Result ResolveProgramPathForDebug(Path *out, ncm::ProgramId id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->ResolveProgramPathForDebug(out, id)); + } + + void RedirectProgramPathForDebug(const Path &path, ncm::ProgramId id) { + AMS_ASSERT(m_interface != nullptr); + R_ABORT_UNLESS(m_interface->RedirectProgramPathForDebug(path, id)); + } + + void RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + AMS_ASSERT(m_interface != nullptr); + if (hos::GetVersion() >= hos::Version_9_0_0) { + R_ABORT_UNLESS(m_interface->RedirectApplicationProgramPathForDebug(path, id, owner_id)); + } else { + R_ABORT_UNLESS(m_interface->RedirectApplicationProgramPathForDebugDeprecated(path, id)); + } + } + + Result EraseProgramRedirectionForDebug(ncm::ProgramId id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->EraseProgramRedirectionForDebug(id)); + } + + Result Disable() { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->Disable()); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_location_resolver_manager_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_location_resolver_manager_impl.hpp new file mode 100644 index 00000000..24d8c683 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_location_resolver_manager_impl.hpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/lr/lr_types.hpp> +#include <stratosphere/lr/lr_i_location_resolver_manager.hpp> +#include <stratosphere/ncm/ncm_bounded_map.hpp> + +namespace ams::lr { + + class LocationResolverManagerImpl { + private: + static constexpr size_t ResolverCountMax = 5; + private: + /* Resolver storage. */ + ncm::BoundedMap<ncm::StorageId, sf::SharedPointer<ILocationResolver>, ResolverCountMax> m_location_resolvers{}; + ncm::BoundedMap<ncm::StorageId, bool, ResolverCountMax> m_location_resolvers_enabled{}; + bool m_default_enabled = true; + sf::SharedPointer<IRegisteredLocationResolver> m_registered_location_resolver = nullptr; + sf::SharedPointer<IAddOnContentLocationResolver> m_add_on_content_location_resolver = nullptr; + + os::SdkMutex m_mutex{}; + public: + /* Actual commands. */ + Result OpenLocationResolver(sf::Out<sf::SharedPointer<ILocationResolver>> out, ncm::StorageId storage_id); + Result OpenRegisteredLocationResolver(sf::Out<sf::SharedPointer<IRegisteredLocationResolver>> out); + Result RefreshLocationResolver(ncm::StorageId storage_id); + Result OpenAddOnContentLocationResolver(sf::Out<sf::SharedPointer<IAddOnContentLocationResolver>> out); + Result SetEnabled(const sf::InMapAliasArray<ncm::StorageId> &storages); + }; + static_assert(IsILocationResolverManager<LocationResolverManagerImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_registered_location_resolver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_registered_location_resolver.hpp new file mode 100644 index 00000000..d33f0a48 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_registered_location_resolver.hpp @@ -0,0 +1,113 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/lr/lr_types.hpp> +#include <stratosphere/lr/lr_i_registered_location_resolver.hpp> + +namespace ams::lr { + + class RegisteredLocationResolver { + NON_COPYABLE(RegisteredLocationResolver); + private: + sf::SharedPointer<IRegisteredLocationResolver> m_interface; + public: + RegisteredLocationResolver() : m_interface(nullptr) { /* ... */ } + explicit RegisteredLocationResolver(sf::SharedPointer<IRegisteredLocationResolver> intf) : m_interface(intf) { /* ... */ } + + RegisteredLocationResolver(RegisteredLocationResolver &&rhs) { + m_interface = std::move(rhs.m_interface); + } + + RegisteredLocationResolver &operator=(RegisteredLocationResolver &&rhs) { + RegisteredLocationResolver(std::move(rhs)).Swap(*this); + return *this; + } + + void Swap(RegisteredLocationResolver &rhs) { + std::swap(m_interface, rhs.m_interface); + } + public: + /* Actual commands. */ + Result ResolveProgramPath(Path *out, ncm::ProgramId id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->ResolveProgramPath(out, id)); + } + + Result RegisterProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + AMS_ASSERT(m_interface != nullptr); + if (hos::GetVersion() >= hos::Version_9_0_0) { + R_RETURN(m_interface->RegisterProgramPath(path, id, owner_id)); + } else { + R_RETURN(m_interface->RegisterProgramPathDeprecated(path, id)); + } + } + + Result UnregisterProgramPath(ncm::ProgramId id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->UnregisterProgramPath(id)); + } + + void RedirectProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + AMS_ASSERT(m_interface != nullptr); + if (hos::GetVersion() >= hos::Version_9_0_0) { + R_ABORT_UNLESS(m_interface->RedirectProgramPath(path, id, owner_id)); + } else { + R_ABORT_UNLESS(m_interface->RedirectProgramPathDeprecated(path, id)); + } + } + + Result ResolveHtmlDocumentPath(Path *out, ncm::ProgramId id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->ResolveHtmlDocumentPath(out, id)); + } + + Result RegisterHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + AMS_ASSERT(m_interface != nullptr); + if (hos::GetVersion() >= hos::Version_9_0_0) { + R_RETURN(m_interface->RegisterHtmlDocumentPath(path, id, owner_id)); + } else { + R_RETURN(m_interface->RegisterHtmlDocumentPathDeprecated(path, id)); + } + } + + Result UnregisterHtmlDocumentPath(ncm::ProgramId id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->UnregisterHtmlDocumentPath(id)); + } + + void RedirectHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + AMS_ASSERT(m_interface != nullptr); + if (hos::GetVersion() >= hos::Version_9_0_0) { + R_ABORT_UNLESS(m_interface->RedirectHtmlDocumentPath(path, id, owner_id)); + } else { + R_ABORT_UNLESS(m_interface->RedirectHtmlDocumentPathDeprecated(path, id)); + } + } + + Result Refresh() { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->Refresh()); + } + + Result RefreshExcluding(const ncm::ProgramId *excluding_ids, size_t num_ids) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->RefreshExcluding(sf::InArray<ncm::ProgramId>(excluding_ids, num_ids))); + } + + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_types.hpp new file mode 100644 index 00000000..734c5fcb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/lr/lr_types.hpp @@ -0,0 +1,65 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fs_directory.hpp> +#include <stratosphere/sf/sf_buffer_tags.hpp> + +namespace ams::lr { + + struct alignas(4) Path : ams::sf::LargeData { + char str[fs::EntryNameLengthMax]; + + static constexpr Path Encode(const char *p) { + Path path = {}; + /* Copy C string to path, terminating when a null byte is found. */ + for (size_t i = 0; i < sizeof(path) - 1; i++) { + path.str[i] = p[i]; + if (p[i] == '\x00') { + break; + } + } + return path; + } + + constexpr inline size_t GetLength() const { + /* Determine length from the first null byte occurence. */ + size_t len = 0; + for (size_t i = 0; i < sizeof(this->str) - 1 && this->str[i] != '\x00'; i++) { + len++; + } + return len; + } + + constexpr inline bool IsValid() const { + /* Determine validity by presence of a terminating null byte. */ + for (size_t i = 0; i < sizeof(this->str); i++) { + if (this->str[i] == '\x00') { + return true; + } + } + return false; + } + }; + + static_assert(util::is_pod<Path>::value && sizeof(Path) == fs::EntryNameLengthMax); + + struct PathByMapAlias : public Path, ams::sf::PrefersMapAliasTransferMode{}; + static_assert(util::is_pod<PathByMapAlias>::value); + static_assert(sizeof(PathByMapAlias) == sizeof(Path)); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem.hpp new file mode 100644 index 00000000..25a68923 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem.hpp @@ -0,0 +1,20 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/mem/mem_standard_allocator.hpp> +#include <stratosphere/mem/impl/mem_impl_heap.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem/impl/heap/mem_impl_heap_cached_heap.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem/impl/heap/mem_impl_heap_cached_heap.hpp new file mode 100644 index 00000000..ce7ad6c3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem/impl/heap/mem_impl_heap_cached_heap.hpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/mem/impl/mem_impl_common.hpp> + +namespace ams::mem::impl::heap { + + class TlsHeapCache; + + class CachedHeap final { + NON_COPYABLE(CachedHeap); + private: + TlsHeapCache *m_tls_heap_cache; + public: + constexpr CachedHeap() : m_tls_heap_cache() { /* ... */ } + ~CachedHeap() { this->Finalize(); } + + ALWAYS_INLINE CachedHeap(CachedHeap &&rhs) : m_tls_heap_cache(rhs.m_tls_heap_cache) { + rhs.m_tls_heap_cache = nullptr; + } + ALWAYS_INLINE CachedHeap &operator=(CachedHeap &&rhs) { + this->Reset(); + m_tls_heap_cache = rhs.m_tls_heap_cache; + rhs.m_tls_heap_cache = nullptr; + return *this; + } + + void *Allocate(size_t n); + void *Allocate(size_t n, size_t align); + size_t GetAllocationSize(const void *ptr); + errno_t Free(void *p); + errno_t FreeWithSize(void *p, size_t size); + errno_t Reallocate(void *ptr, size_t size, void **p); + errno_t Shrink(void *ptr, size_t size); + + void ReleaseAllCache(); + void Finalize(); + bool CheckCache(); + errno_t QueryV(int query, std::va_list vl); + errno_t Query(int query, ...); + + void Reset() { this->Finalize(); } + void Reset(TlsHeapCache *thc); + TlsHeapCache *Release(); + + constexpr explicit ALWAYS_INLINE operator bool() const { return m_tls_heap_cache != nullptr; } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem/impl/heap/mem_impl_heap_central_heap.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem/impl/heap/mem_impl_heap_central_heap.hpp new file mode 100644 index 00000000..2b66ce65 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem/impl/heap/mem_impl_heap_central_heap.hpp @@ -0,0 +1,65 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/mem/impl/mem_impl_common.hpp> +#include <stratosphere/mem/impl/mem_impl_declarations.hpp> + +namespace ams::mem::impl::heap { + + class CachedHeap; + class TlsHeapCentral; + + using HeapWalkCallback = int (*)(void *ptr, size_t size, void *user_data); + + class CentralHeap final { + NON_COPYABLE(CentralHeap); + NON_MOVEABLE(CentralHeap); + public: + static constexpr size_t PageSize = 4_KB; + static constexpr size_t MinimumAlignment = alignof(u64); + using DestructorHandler = void (*)(void *start, void *end); + private: + TlsHeapCentral *m_tls_heap_central; + bool m_use_virtual_memory; + u32 m_option; + u8 *m_start; + u8 *m_end; + public: + constexpr CentralHeap() : m_tls_heap_central(), m_use_virtual_memory(), m_option(), m_start(), m_end() { /* ... */ } + ~CentralHeap() { this->Finalize(); } + + errno_t Initialize(void *start, size_t size, u32 option); + void Finalize(); + + ALWAYS_INLINE void *Allocate(size_t n) { return this->Allocate(n, MinimumAlignment); } + void *Allocate(size_t n, size_t align); + size_t GetAllocationSize(const void *ptr); + errno_t Free(void *p); + errno_t FreeWithSize(void *p, size_t size); + errno_t Reallocate(void *ptr, size_t size, void **p); + errno_t Shrink(void *ptr, size_t size); + + bool MakeCache(CachedHeap *cached_heap); + errno_t WalkAllocatedPointers(HeapWalkCallback callback, void *user_data); + errno_t QueryV(int query, std::va_list vl); + errno_t Query(int query, ...); + }; + + static_assert(sizeof(CentralHeap) <= sizeof(::ams::mem::impl::InternalCentralHeapStorage)); + static_assert(alignof(CentralHeap) <= alignof(void *)); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem/impl/mem_impl_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem/impl/mem_impl_common.hpp new file mode 100644 index 00000000..fb5ad30e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem/impl/mem_impl_common.hpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <errno.h> + +namespace ams::mem::impl { + + constexpr inline size_t MaxSize = static_cast<size_t>(std::numeric_limits<s64>::max()); + + using errno_t = int; + + enum DumpMode { + DumpMode_Basic = (0 << 0), + DumpMode_Spans = (1 << 0), + DumpMode_Pointers = (1 << 1), + DumpMode_Pages = (1 << 2), + DumpMode_All = (DumpMode_Pages | DumpMode_Pointers | DumpMode_Spans | DumpMode_Basic), + }; + + enum AllocQuery { + AllocQuery_Dump = 0, + AllocQuery_PageSize = 1, + AllocQuery_AllocatedSize = 2, + AllocQuery_FreeSize = 3, + AllocQuery_SystemSize = 4, + AllocQuery_MaxAllocatableSize = 5, + AllocQuery_IsClean = 6, + AllocQuery_HeapHash = 7, + AllocQuery_UnifyFreeList = 8, + AllocQuery_SetColor = 9, + AllocQuery_GetColor = 10, + AllocQuery_SetName = 11, + AllocQuery_GetName = 12, + /* AllocQuery_Thirteen = 13, */ + AllocQuery_CheckCache = 14, + AllocQuery_ClearCache = 15, + AllocQuery_FinalizeCache = 16, + AllocQuery_FreeSizeMapped = 17, + AllocQuery_MaxAllocatableSizeMapped = 18, + AllocQuery_DumpJson = 19, + }; + + enum HeapOption { + HeapOption_UseEnvironment = (1 << 0), + HeapOption_DisableCache = (1 << 2), + }; + + struct HeapHash { + size_t alloc_count; + size_t alloc_size; + size_t hash; + }; + static_assert(util::is_pod<HeapHash>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem/impl/mem_impl_declarations.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem/impl/mem_impl_declarations.hpp new file mode 100644 index 00000000..8feb1a9a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem/impl/mem_impl_declarations.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +namespace ams::mem::impl { + + namespace heap { + + class CentralHeap; + + } + + using InternalCentralHeapStorage = ::ams::util::TypedStorage<::ams::mem::impl::heap::CentralHeap, sizeof(void *) * 6, alignof(void *)>; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem/impl/mem_impl_heap.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem/impl/mem_impl_heap.hpp new file mode 100644 index 00000000..2b5c796f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem/impl/mem_impl_heap.hpp @@ -0,0 +1,21 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/mem/impl/mem_impl_common.hpp> +#include <stratosphere/mem/impl/heap/mem_impl_heap_cached_heap.hpp> +#include <stratosphere/mem/impl/heap/mem_impl_heap_central_heap.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem/mem_standard_allocator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem/mem_standard_allocator.hpp new file mode 100644 index 00000000..a74a0e29 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mem/mem_standard_allocator.hpp @@ -0,0 +1,73 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/os.hpp> +#include <stratosphere/mem/impl/mem_impl_declarations.hpp> + +namespace ams::mem { + + class StandardAllocator { + NON_COPYABLE(StandardAllocator); + NON_MOVEABLE(StandardAllocator); + public: + using WalkCallback = int (*)(void *ptr, size_t size, void *user_data); + + struct AllocatorHash { + size_t allocated_count; + size_t allocated_size; + size_t hash; + }; + private: + bool m_initialized; + bool m_enable_thread_cache; + uintptr_t m_unused; + os::TlsSlot m_tls_slot; + impl::InternalCentralHeapStorage m_central_heap_storage; + public: + StandardAllocator(); + StandardAllocator(void *mem, size_t size); + StandardAllocator(void *mem, size_t size, bool enable_cache); + + ~StandardAllocator() { + if (m_initialized) { + this->Finalize(); + } + } + + void Initialize(void *mem, size_t size); + void Initialize(void *mem, size_t size, bool enable_cache); + void Finalize(); + + void *Allocate(size_t size); + void *Allocate(size_t size, size_t alignment); + void Free(void *ptr); + void *Reallocate(void *ptr, size_t new_size); + size_t Shrink(void *ptr, size_t new_size); + + void ClearThreadCache() const; + void CleanUpManagementArea() const; + + size_t GetSizeOf(const void *ptr) const; + size_t GetTotalFreeSize() const; + size_t GetAllocatableSize() const; + + void WalkAllocatedBlocks(WalkCallback callback, void *user_data) const; + + void Dump() const; + AllocatorHash Hash() const; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mitm.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mitm.hpp new file mode 100644 index 00000000..c4e7afe6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mitm.hpp @@ -0,0 +1,20 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/mitm/impl/mitm_pm_interface.hpp> +#include <stratosphere/mitm/mitm_pm_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mitm/impl/mitm_pm_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mitm/impl/mitm_pm_interface.hpp new file mode 100644 index 00000000..704fdb3a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mitm/impl/mitm_pm_interface.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/ncm/ncm_program_id.hpp> +#include <stratosphere/cfg/cfg_types.hpp> +#include <stratosphere/sf.hpp> + +#define AMS_MITM_PM_IMPL_I_PM_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 65000, Result, PrepareLaunchProgram, (sf::Out<u64> out_boost_size, ncm::ProgramId program_id, const cfg::OverrideStatus &override_status, bool is_application), (out_boost_size, program_id, override_status, is_application)) + +AMS_SF_DEFINE_INTERFACE(ams::mitm::pm::impl, IPmInterface, AMS_MITM_PM_IMPL_I_PM_INTERFACE_INFO, 0xEA88789C) + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mitm/mitm_pm_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mitm/mitm_pm_api.hpp new file mode 100644 index 00000000..feab8db6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/mitm/mitm_pm_api.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/ncm/ncm_program_id.hpp> + +namespace ams::mitm::pm { + + /* PM API. */ + void Initialize(); + void Finalize(); + + Result PrepareLaunchProgram(u64 *out, ncm::ProgramId program_id, const cfg::OverrideStatus &status, bool is_application); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm.hpp new file mode 100644 index 00000000..958496c9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/ncm/ncm_ids.hpp> +#include <stratosphere/ncm/ncm_max_count.hpp> +#include <stratosphere/ncm/ncm_program_location.hpp> +#include <stratosphere/ncm/ncm_auto_buffer.hpp> +#include <stratosphere/ncm/ncm_make_path.hpp> +#include <stratosphere/ncm/ncm_content_id_utils.hpp> +#include <stratosphere/ncm/ncm_content_info_utils.hpp> +#include <stratosphere/ncm/ncm_content_meta.hpp> +#include <stratosphere/ncm/ncm_content_meta_extended_data.hpp> +#include <stratosphere/ncm/ncm_content_meta_database.hpp> +#include <stratosphere/ncm/ncm_content_storage.hpp> +#include <stratosphere/ncm/ncm_content_manager_impl.hpp> +#include <stratosphere/ncm/ncm_content_meta_utils.hpp> +#include <stratosphere/ncm/ncm_firmware_variation.hpp> +#include <stratosphere/ncm/ncm_install_task_base.hpp> +#include <stratosphere/ncm/ncm_install_task_data.hpp> +#include <stratosphere/ncm/ncm_install_task_occupied_size.hpp> +#include <stratosphere/ncm/ncm_memory_report.hpp> +#include <stratosphere/ncm/ncm_package_install_task_base.hpp> +#include <stratosphere/ncm/ncm_package_install_task.hpp> +#include <stratosphere/ncm/ncm_package_system_downgrade_task.hpp> +#include <stratosphere/ncm/ncm_package_system_update_task.hpp> +#include <stratosphere/ncm/ncm_registered_host_content.hpp> +#include <stratosphere/ncm/ncm_submission_package_install_task.hpp> +#include <stratosphere/ncm/ncm_storage_utils.hpp> +#include <stratosphere/ncm/ncm_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_api.hpp new file mode 100644 index 00000000..75e8f17a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_api.hpp @@ -0,0 +1,56 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_content_meta_database.hpp> +#include <stratosphere/ncm/ncm_content_storage.hpp> +#include <stratosphere/ncm/ncm_i_content_manager.hpp> +#include <stratosphere/fs/fs_content_storage_id.hpp> + +namespace ams::ncm { + + /* Management. */ + void Initialize(); + void Finalize(); + + void InitializeWithObject(sf::SharedPointer<IContentManager> manager_object); + + /* Service API. */ + Result CreateContentStorage(StorageId storage_id); + Result CreateContentMetaDatabase(StorageId storage_id); + + Result VerifyContentStorage(StorageId storage_id); + Result VerifyContentMetaDatabase(StorageId storage_id); + + Result OpenContentStorage(ContentStorage *out, StorageId storage_id); + Result OpenContentMetaDatabase(ContentMetaDatabase *out, StorageId storage_id); + + Result CleanupContentMetaDatabase(StorageId storage_id); + + Result ActivateContentStorage(StorageId storage_id); + Result InactivateContentStorage(StorageId storage_id); + + Result ActivateContentMetaDatabase(StorageId storage_id); + Result InactivateContentMetaDatabase(StorageId storage_id); + + Result InvalidateRightsIdCache(); + + Result ActivateFsContentStorage(fs::ContentStorageId fs_content_storage_id); + + /* Deprecated API. */ + Result CloseContentStorageForcibly(StorageId storage_id); + Result CloseContentMetaDatabaseForcibly(StorageId storage_id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_auto_buffer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_auto_buffer.hpp new file mode 100644 index 00000000..20c298a4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_auto_buffer.hpp @@ -0,0 +1,89 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::ncm { + + class AutoBuffer { + NON_COPYABLE(AutoBuffer); + private: + u8 *m_buffer; + size_t m_size; + public: + AutoBuffer() : m_buffer(nullptr), m_size(0) { /* ... */ } + + ~AutoBuffer() { + this->Reset(); + } + + AutoBuffer(AutoBuffer &&rhs) { + m_buffer = rhs.m_buffer; + m_size = rhs.m_size; + rhs.m_buffer = nullptr; + rhs.m_size = 0; + } + + AutoBuffer &operator=(AutoBuffer &&rhs) { + AutoBuffer(std::move(rhs)).Swap(*this); + return *this; + } + + void Swap(AutoBuffer &rhs) { + std::swap(m_buffer, rhs.m_buffer); + std::swap(m_size, rhs.m_size); + } + + void Reset() { + if (m_buffer != nullptr) { + delete[] m_buffer; + m_buffer = nullptr; + m_size = 0; + } + } + + u8 *Get() const { + return m_buffer; + } + + size_t GetSize() const { + return m_size; + } + + Result Initialize(size_t size) { + /* Check that we're not already initialized. */ + AMS_ABORT_UNLESS(m_buffer == nullptr); + + /* Allocate a buffer. */ + m_buffer = new (std::nothrow) u8[size]; + R_UNLESS(m_buffer != nullptr, ncm::ResultAllocationFailed()); + + m_size = size; + R_SUCCEED(); + } + + Result Initialize(const void *buf, size_t size) { + /* Create a new buffer of the right size. */ + R_TRY(this->Initialize(size)); + + /* Copy the input data in. */ + std::memcpy(m_buffer, buf, size); + + R_SUCCEED(); + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_bounded_map.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_bounded_map.hpp new file mode 100644 index 00000000..dffb7a55 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_bounded_map.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::ncm { + + template<class Key, class Value, size_t N> + using BoundedMap = util::BoundedMap<Key, Value, N>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_id.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_id.hpp new file mode 100644 index 00000000..640ee06f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_id.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::ncm { + + struct ContentId { + util::Uuid uuid; + + bool operator==(const ContentId &other) const { + return this->uuid == other.uuid; + } + + bool operator!=(const ContentId &other) const { + return this->uuid != other.uuid; + } + + bool operator==(const util::Uuid &other) const { + return this->uuid == other; + } + + bool operator!=(const util::Uuid &other) const { + return this->uuid != other; + } + }; + + static_assert(alignof(ContentId) == 1); + + constexpr inline ContentId InvalidContentId = { util::InvalidUuid }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_id_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_id_utils.hpp new file mode 100644 index 00000000..5e7de864 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_id_utils.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_rights_id.hpp> +#include <stratosphere/ncm/ncm_content_id.hpp> +#include <stratosphere/ncm/ncm_content_info_data.hpp> + +namespace ams::ncm { + + constexpr inline size_t ContentIdStringLength = 2 * sizeof(ContentId); + constexpr inline size_t RightsIdStringLength = 2 * sizeof(fs::RightsId); + constexpr inline size_t TicketFileStringLength = RightsIdStringLength + 4; + constexpr inline size_t CertFileStringLength = RightsIdStringLength + 5; + + struct ContentIdString { + char data[ContentIdStringLength + 1]; + }; + + ContentIdString GetContentIdString(ContentId id); + + void GetStringFromContentId(char *dst, size_t dst_size, ContentId id); + void GetStringFromRightsId(char *dst, size_t dst_size, fs::RightsId id); + + void GetTicketFileStringFromRightsId(char *dst, size_t dst_size, fs::RightsId id); + void GetCertificateFileStringFromRightsId(char *dst, size_t dst_size, fs::RightsId id); + + util::optional<ContentId> GetContentIdFromString(const char *str, size_t len); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_info.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_info.hpp new file mode 100644 index 00000000..da104f07 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_info.hpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_content_attributes.hpp> +#include <stratosphere/ncm/ncm_content_id.hpp> +#include <stratosphere/ncm/ncm_content_type.hpp> + +namespace ams::ncm { + + struct ContentInfo { + static constexpr fs::ContentAttributes DefaultContentAttributes = fs::ContentAttributes_None; + + ContentId content_id; + u32 size_low; + u8 size_high; + u8 content_attributes; + ContentType content_type; + u8 id_offset; + + constexpr const ContentId &GetId() const { + return this->content_id; + } + + constexpr u64 GetSize() const { + return (static_cast<u64>(this->size_high) << 32) | static_cast<u64>(this->size_low); + } + + constexpr fs::ContentAttributes GetContentAttributes() const { + return static_cast<fs::ContentAttributes>(this->content_attributes & 0xF); + } + + constexpr ContentType GetType() const { + return this->content_type; + } + + constexpr u8 GetIdOffset() const { + return this->id_offset; + } + + static constexpr ContentInfo Make(ContentId id, u64 size, fs::ContentAttributes attr, ContentType type, u8 id_ofs = 0) { + const u32 size_low = size & 0xFFFFFFFFu; + const u8 size_high = static_cast<u8>(size >> 32); + return { + .content_id = id, + .size_low = size_low, + .size_high = size_high, + .content_attributes = attr, + .content_type = type, + .id_offset = id_ofs, + }; + } + }; + + static_assert(sizeof(util::is_pod<ContentInfo>::value)); + static_assert(sizeof(ContentInfo) == 0x18); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_info_data.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_info_data.hpp new file mode 100644 index 00000000..12e3c08c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_info_data.hpp @@ -0,0 +1,130 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_content_info.hpp> +#include <stratosphere/ncm/ncm_content_storage.hpp> +#include <stratosphere/ncm/ncm_storage_id.hpp> +#include <stratosphere/ncm/ncm_content_meta_key.hpp> + +namespace ams::ncm { + + struct Digest { + u8 data[crypto::Sha256Generator::HashSize]; + }; + + enum class InstallState : u8 { + NotPrepared, + Prepared, + Installed, + AlreadyExists, + }; + + struct PackagedContentInfo { + Digest digest; + ContentInfo info; + + constexpr const ContentId &GetId() const { + return this->info.GetId(); + } + + constexpr fs::ContentAttributes GetContentAttributes() const { + return this->info.GetContentAttributes(); + } + + constexpr ContentType GetType() const { + return this->info.GetType(); + } + + constexpr u8 GetIdOffset() const { + return this->info.GetIdOffset(); + } + }; + + struct InstallContentInfo { + Digest digest; + crypto::Sha256Context context; + u8 buffered_data[crypto::Sha256Generator::BlockSize]; + u64 buffered_data_size; + ContentInfo info; + PlaceHolderId placeholder_id; + ContentMetaType meta_type; + InstallState install_state; + bool verify_digest; + StorageId storage_id; + bool is_temporary; + bool is_sha256_calculated; + s64 written; + + constexpr const ContentId &GetId() const { + return this->info.GetId(); + } + + constexpr u64 GetSize() const { + return this->info.GetSize(); + } + + constexpr fs::ContentAttributes GetContentAttributes() const { + return this->info.GetContentAttributes(); + } + + constexpr ContentType GetType() const { + return this->info.GetType(); + } + + constexpr u8 GetIdOffset() const { + return this->info.GetIdOffset(); + } + + constexpr const PlaceHolderId &GetPlaceHolderId() const { + return this->placeholder_id; + } + + constexpr ContentMetaType GetContentMetaType() const { + return this->meta_type; + } + + constexpr InstallState GetInstallState() const { + return this->install_state; + } + + constexpr StorageId GetStorageId() const { + return this->storage_id; + } + + constexpr s64 GetSizeWritten() const { + return this->written; + } + + static constexpr InstallContentInfo Make(const ContentInfo &info, ContentMetaType meta_type) { + return { + .info = info, + .meta_type = meta_type, + }; + } + + static constexpr InstallContentInfo Make(const PackagedContentInfo &info, ContentMetaType meta_type) { + return { + .digest = info.digest, + .info = info.info, + .meta_type = meta_type, + .verify_digest = true, + }; + } + }; + + static_assert(sizeof(InstallContentInfo) == 0xC8); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_info_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_info_utils.hpp new file mode 100644 index 00000000..7b8db9ad --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_info_utils.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/ncm/ncm_content_meta_key.hpp> + +namespace ams::ncm { + + constexpr inline s64 MaxClusterSize = 256_KB; + + s64 CalculateRequiredSize(s64 file_size, s64 cluster_size = MaxClusterSize); + s64 CalculateRequiredSizeForExtension(s64 file_size, s64 cluster_size = MaxClusterSize); + + class ContentMetaDatabase; + + Result EstimateRequiredSize(s64 *out_size, const ContentMetaKey &key, ContentMetaDatabase *db); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_management_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_management_utils.hpp new file mode 100644 index 00000000..07954b25 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_management_utils.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_content_meta.hpp> +#include <stratosphere/ncm/ncm_content_meta_database.hpp> +#include <stratosphere/ncm/ncm_content_storage.hpp> + +namespace ams::ncm { + + class ContentMetaDatabaseBuilder { + private: + ContentMetaDatabase *m_db; + private: + Result BuildFromPackageContentMeta(void *buf, size_t size, const ContentInfo &meta_info); + public: + explicit ContentMetaDatabaseBuilder(ContentMetaDatabase *d) : m_db(d) { /* ... */ } + + Result BuildFromStorage(ContentStorage *storage); + Result BuildFromPackage(const char *package_root_path); + + Result Cleanup(); + }; + + Result ListApplicationPackage(s32 *out_count, ApplicationId *out_ids, s32 max_out_ids, const char *package_root_path); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_manager_config.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_manager_config.hpp new file mode 100644 index 00000000..50ba28be --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_manager_config.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::ncm { + + struct ContentManagerConfig { + bool build_system_database; + bool import_database_from_system_on_sd; + bool enable_integrated_system_content; + + bool HasAnyConfig() const { + return this->ShouldBuildDatabase() || this->import_database_from_system_on_sd || this->enable_integrated_system_content; + } + + bool ShouldBuildDatabase() const { + return hos::GetVersion() < hos::Version_4_0_0 || this->build_system_database; + } + + bool ShouldImportDatabaseFromSignedSystemPartitionOnSd() const { + return this->import_database_from_system_on_sd; + } + + bool IsIntegratedSystemContentEnabled() const { + return this->enable_integrated_system_content; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_manager_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_manager_impl.hpp new file mode 100644 index 00000000..348acc48 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_manager_impl.hpp @@ -0,0 +1,269 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/os.hpp> +#include <stratosphere/fs/fs_mount.hpp> +#include <stratosphere/fs/fs_bis.hpp> +#include <stratosphere/fs/fs_content_storage.hpp> +#include <stratosphere/fs/fs_system_save_data.hpp> +#include <stratosphere/ncm/ncm_i_content_manager.hpp> +#include <stratosphere/ncm/ncm_content_manager_config.hpp> +#include <stratosphere/ncm/ncm_content_meta_database.hpp> +#include <stratosphere/ncm/ncm_bounded_map.hpp> +#include <stratosphere/ncm/ncm_rights_id_cache.hpp> +#include <stratosphere/ncm/ncm_content_management_utils.hpp> +#include <stratosphere/ncm/ncm_content_meta_utils.hpp> +#include <stratosphere/ncm/ncm_registered_host_content.hpp> +#include <stratosphere/ncm/ncm_integrated_content_meta_database_impl.hpp> +#include <stratosphere/ncm/ncm_integrated_content_storage_impl.hpp> +#include <stratosphere/kvdb/kvdb_memory_key_value_store.hpp> + +namespace ams::ncm { + + class ContentMetaMemoryResource : public MemoryResource { + private: + mem::StandardAllocator m_allocator; + size_t m_peak_total_alloc_size; + size_t m_peak_alloc_size; + public: + explicit ContentMetaMemoryResource(void *heap, size_t heap_size) : m_allocator(heap, heap_size), m_peak_total_alloc_size(0), m_peak_alloc_size(0) { /* ... */ } + + mem::StandardAllocator *GetAllocator() { return std::addressof(m_allocator); } + size_t GetPeakTotalAllocationSize() const { return m_peak_total_alloc_size; } + size_t GetPeakAllocationSize() const { return m_peak_alloc_size; } + private: + virtual void *AllocateImpl(size_t size, size_t alignment) override { + void *mem = m_allocator.Allocate(size, alignment); + m_peak_total_alloc_size = std::max(m_allocator.Hash().allocated_size, m_peak_total_alloc_size); + m_peak_alloc_size = std::max(size, m_peak_alloc_size); + return mem; + } + + virtual void DeallocateImpl(void *buffer, size_t size, size_t alignment) override { + AMS_UNUSED(size, alignment); + return m_allocator.Free(buffer); + } + + virtual bool IsEqualImpl(const MemoryResource &resource) const override { + return this == std::addressof(resource); + } + }; + + struct SystemSaveDataInfo { + u64 id; + u64 size; + u64 journal_size; + u32 flags; + fs::SaveDataSpaceId space_id; + }; + static_assert(util::is_pod<SystemSaveDataInfo>::value); + + struct IntegratedContentStorageImpl; + + class ContentManagerImpl { + private: + constexpr static size_t MaxContentStorageRoots = 8; + constexpr static size_t MaxIntegratedContentStorageRoots = 8; + constexpr static size_t MaxContentMetaDatabaseRoots = 8; + constexpr static size_t MaxIntegratedContentMetaDatabaseRoots = 8; + constexpr static size_t MaxConfigs = 8; + constexpr static size_t MaxIntegratedConfigs = 8; + private: + struct ContentStorageConfig { + fs::ContentStorageId content_storage_id; + bool skip_verify_and_create; + bool skip_activate; + }; + + struct IntegratedContentStorageConfig { + ncm::StorageId storage_id; + fs::ContentStorageId content_storage_ids[MaxContentStorageRoots]; + int num_content_storage_ids; + bool is_integrated; + }; + private: + struct ContentStorageRoot { + NON_COPYABLE(ContentStorageRoot); + NON_MOVEABLE(ContentStorageRoot); + + char mount_name[fs::MountNameLengthMax + 1]; + char path[128]; + StorageId storage_id; + util::optional<ContentStorageConfig> config; + sf::SharedPointer<IContentStorage> content_storage; + + ContentStorageRoot() : mount_name(), path(), storage_id(), config(util::nullopt), content_storage() { /* ... */ } + }; + + struct IntegratedContentStorageRoot { + NON_COPYABLE(IntegratedContentStorageRoot); + NON_MOVEABLE(IntegratedContentStorageRoot); + + const IntegratedContentStorageConfig *m_config; + ContentStorageRoot *m_roots; + int m_num_roots; + sf::EmplacedRef<IContentStorage, IntegratedContentStorageImpl> m_integrated_content_storage; + + IntegratedContentStorageRoot() : m_config(), m_roots(), m_num_roots(), m_integrated_content_storage() { /* ... */ } + + Result Create(); + Result Verify(); + Result Open(sf::Out<sf::SharedPointer<IContentStorage>> out, RightsIdCache &rights_id_cache, RegisteredHostContent ®istered_host_content); + Result Activate(RightsIdCache &rights_id_cache, RegisteredHostContent ®istered_host_content); + Result Inactivate(RegisteredHostContent ®istered_host_content); + + Result Activate(ContentStorageRoot &root, RightsIdCache &rights_id_cache, RegisteredHostContent ®istered_host_content); + + Result Activate(RightsIdCache &rights_id_cache, RegisteredHostContent ®istered_host_content, fs::ContentStorageId content_storage_id); + + ContentStorageRoot *GetRoot(fs::ContentStorageId storage_id) { + for (auto i = 0; i < m_num_roots; ++i) { + if (auto &root = m_roots[i]; root.config.has_value() && root.config->content_storage_id == storage_id) { + return std::addressof(root); + } + } + + return nullptr; + } + }; + + struct ContentMetaDatabaseRoot { + NON_COPYABLE(ContentMetaDatabaseRoot); + NON_MOVEABLE(ContentMetaDatabaseRoot); + + char mount_name[fs::MountNameLengthMax + 1]; + char path[128]; + StorageId storage_id; + util::optional<ContentStorageConfig> storage_config; + util::optional<SystemSaveDataInfo> save_data_info; + util::optional<kvdb::MemoryKeyValueStore<ContentMetaKey>> kvs; + sf::SharedPointer<IContentMetaDatabase> content_meta_database; + ContentMetaMemoryResource *memory_resource; + u32 max_content_metas; + + ContentMetaDatabaseRoot() : mount_name(), path(), storage_id(), storage_config(util::nullopt), save_data_info(util::nullopt), kvs(util::nullopt), content_meta_database(), memory_resource(), max_content_metas() { /* ... */ } + }; + + struct IntegratedContentMetaDatabaseRoot { + NON_COPYABLE(IntegratedContentMetaDatabaseRoot); + NON_MOVEABLE(IntegratedContentMetaDatabaseRoot); + + const IntegratedContentStorageConfig *m_config; + ContentMetaDatabaseRoot *m_roots; + int m_num_roots; + sf::EmplacedRef<IContentMetaDatabase, IntegratedContentMetaDatabaseImpl> m_integrated_content_meta_database; + + IntegratedContentMetaDatabaseRoot() : m_config(), m_roots(), m_num_roots(), m_integrated_content_meta_database() { /* ... */ } + + Result Create(); + Result Verify(); + Result Open(sf::Out<sf::SharedPointer<IContentMetaDatabase>> out); + Result Cleanup(); + Result Activate(); + Result Inactivate(); + + Result Activate(ContentMetaDatabaseRoot &root); + + Result Activate(fs::ContentStorageId content_storage_id); + + ContentMetaDatabaseRoot *GetRoot(fs::ContentStorageId storage_id) { + for (auto i = 0; i < m_num_roots; ++i) { + if (auto &root = m_roots[i]; root.storage_config.has_value() && root.storage_config->content_storage_id == storage_id) { + return std::addressof(root); + } + } + + return nullptr; + } + }; + private: + os::SdkRecursiveMutex m_mutex{}; + bool m_initialized{false}; + IntegratedContentStorageRoot m_integrated_content_storage_roots[MaxIntegratedContentStorageRoots]{}; + ContentStorageRoot m_content_storage_roots[MaxContentStorageRoots]{}; + IntegratedContentMetaDatabaseRoot m_integrated_content_meta_database_roots[MaxIntegratedContentMetaDatabaseRoots]{}; + ContentMetaDatabaseRoot m_content_meta_database_roots[MaxContentMetaDatabaseRoots]{}; + IntegratedContentStorageConfig m_integrated_configs[MaxIntegratedConfigs]{}; + ContentStorageConfig m_configs[MaxConfigs]{}; + u32 m_num_integrated_content_storage_entries{0}; + u32 m_num_content_storage_entries{0}; + u32 m_num_integrated_content_meta_entries{0}; + u32 m_num_content_meta_entries{0}; + u32 m_num_integrated_configs{0}; + u32 m_num_configs{0}; + RightsIdCache m_rights_id_cache{}; + RegisteredHostContent m_registered_host_content{}; + public: + ContentManagerImpl() = default; + ~ContentManagerImpl(); + public: + Result Initialize(const ContentManagerConfig &config); + private: + Result Initialize(const ContentManagerConfig &manager_config, const IntegratedContentStorageConfig *integrated_configs, size_t num_integrated_configs, const ContentStorageConfig *configs, size_t num_configs, const ncm::StorageId *activated_storages, size_t num_activated_storages); + Result InitializeStorageBuiltInSystem(const ContentManagerConfig &manager_config); + Result InitializeStorage(ncm::StorageId storage_id); + + const ContentStorageConfig &GetContentStorageConfig(fs::ContentStorageId content_storage_id) { + for (size_t i = 0; i < m_num_configs; ++i) { + if (m_configs[i].content_storage_id == content_storage_id) { + return m_configs[i]; + } + } + + /* NOTE: Nintendo accesses out of bounds memory here. Should we explicitly abort? This is guaranteed by data to never happen. */ + AMS_ASSUME(false); + } + private: + /* Helpers. */ + Result GetIntegratedContentStorageConfig(IntegratedContentStorageConfig **out, fs::ContentStorageId content_storage_id); + Result GetIntegratedContentStorageRoot(IntegratedContentStorageRoot **out, StorageId id); + Result GetIntegratedContentMetaDatabaseRoot(IntegratedContentMetaDatabaseRoot **out, StorageId id); + + Result InitializeContentStorageRoot(ContentStorageRoot *out, StorageId storage_id, util::optional<ContentStorageConfig> config); + Result InitializeContentMetaDatabaseRoot(ContentMetaDatabaseRoot *out, StorageId storage_id, util::optional<ContentStorageConfig> storage_config); + + Result InitializeIntegratedContentStorageRoot(IntegratedContentStorageRoot *out, const IntegratedContentStorageConfig *config, size_t root_idx, size_t root_count); + Result InitializeIntegratedContentMetaDatabaseRoot(IntegratedContentMetaDatabaseRoot *out, const IntegratedContentStorageConfig *config, size_t root_idx, size_t root_count); + + Result BuildContentMetaDatabase(StorageId storage_id); + Result BuildContentMetaDatabaseImpl(StorageId storage_id); + Result ImportContentMetaDatabase(StorageId storage_id, bool from_signed_partition); + Result ImportContentMetaDatabaseImpl(ContentMetaDatabaseRoot *root, const char *import_mount_name); + private: + /* Helpers for unofficial functionality. */ + bool IsNeedRebuildSystemContentMetaDatabase(); + public: + /* Actual commands. */ + Result CreateContentStorage(StorageId storage_id); + Result CreateContentMetaDatabase(StorageId storage_id); + Result VerifyContentStorage(StorageId storage_id); + Result VerifyContentMetaDatabase(StorageId storage_id); + Result OpenContentStorage(sf::Out<sf::SharedPointer<IContentStorage>> out, StorageId storage_id); + Result OpenContentMetaDatabase(sf::Out<sf::SharedPointer<IContentMetaDatabase>> out, StorageId storage_id); + Result CloseContentStorageForcibly(StorageId storage_id); + Result CloseContentMetaDatabaseForcibly(StorageId storage_id); + Result CleanupContentMetaDatabase(StorageId storage_id); + Result ActivateContentStorage(StorageId storage_id); + Result InactivateContentStorage(StorageId storage_id); + Result ActivateContentMetaDatabase(StorageId storage_id); + Result InactivateContentMetaDatabase(StorageId storage_id); + Result InvalidateRightsIdCache(); + Result GetMemoryReport(sf::Out<MemoryReport> out); + Result ActivateFsContentStorage(fs::ContentStorageId fs_content_storage_id); + }; + static_assert(IsIContentManager<ContentManagerImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp new file mode 100644 index 00000000..4b7ac2ba --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta.hpp @@ -0,0 +1,399 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_content_meta_id.hpp> +#include <stratosphere/ncm/ncm_content_meta_key.hpp> +#include <stratosphere/ncm/ncm_content_meta_platform.hpp> +#include <stratosphere/ncm/ncm_content_info.hpp> +#include <stratosphere/ncm/ncm_content_info_data.hpp> +#include <stratosphere/ncm/ncm_firmware_variation.hpp> +#include <stratosphere/ncm/ncm_storage_id.hpp> + +namespace ams::ncm { + + enum ContentMetaAttribute : u8 { + ContentMetaAttribute_None = (0 << 0), + ContentMetaAttribute_IncludesExFatDriver = (1 << 0), + ContentMetaAttribute_Rebootless = (1 << 1), + ContentMetaAttribute_Compacted = (1 << 2), + }; + + struct ContentMetaInfo { + u64 id; + u32 version; + ContentMetaType type; + u8 attributes; + u8 padding[2]; + + static constexpr ContentMetaInfo Make(u64 id, u32 version, ContentMetaType type, u8 attributes) { + return { + .id = id, + .version = version, + .type = type, + .attributes = attributes, + }; + } + + constexpr ContentMetaKey ToKey() const { + return ContentMetaKey::Make(this->id, this->version, this->type); + } + }; + + static_assert(sizeof(ContentMetaInfo) == 0x10); + + struct ContentMetaHeader { + u16 extended_header_size; + u16 content_count; + u16 content_meta_count; + u8 attributes; + ContentMetaPlatform platform; + }; + + static_assert(sizeof(ContentMetaHeader) == 0x8); + + struct PackagedContentMetaHeader { + u64 id; + u32 version; + ContentMetaType type; + ContentMetaPlatform platform; + u16 extended_header_size; + u16 content_count; + u16 content_meta_count; + u8 attributes; + u8 storage_id; + ContentInstallType install_type; + bool committed; + u32 required_download_system_version; + u8 reserved_1C[4]; + }; + static_assert(sizeof(PackagedContentMetaHeader) == 0x20); + static_assert(AMS_OFFSETOF(PackagedContentMetaHeader, reserved_1C) == 0x1C); + + using InstallContentMetaHeader = PackagedContentMetaHeader; + + struct ApplicationMetaExtendedHeader { + PatchId patch_id; + u32 required_system_version; + u32 required_application_version; + }; + + struct PatchMetaExtendedHeader { + ApplicationId application_id; + u32 required_system_version; + u32 extended_data_size; + u8 reserved[0x8]; + }; + + struct AddOnContentMetaExtendedHeader { + ApplicationId application_id; + u32 required_application_version; + u8 content_accessibilities; + u8 padding[3]; + DataPatchId data_patch_id; + }; + + struct LegacyAddOnContentMetaExtendedHeader { + ApplicationId application_id; + u32 required_application_version; + u32 padding; + }; + + struct DeltaMetaExtendedHeader { + ApplicationId application_id; + u32 extended_data_size; + u32 padding; + }; + + struct SystemUpdateMetaExtendedHeader { + u32 extended_data_size; + }; + + template<typename ContentMetaHeaderType, typename ContentInfoType> + class ContentMetaAccessor { + public: + using HeaderType = ContentMetaHeaderType; + using InfoType = ContentInfoType; + private: + void *m_data; + const size_t m_size; + bool m_is_header_valid; + private: + static size_t GetExtendedHeaderSize(ContentMetaType type) { + switch (type) { + case ContentMetaType::Application: return sizeof(ApplicationMetaExtendedHeader); + case ContentMetaType::Patch: return sizeof(PatchMetaExtendedHeader); + case ContentMetaType::AddOnContent: return sizeof(AddOnContentMetaExtendedHeader); + case ContentMetaType::Delta: return sizeof(DeltaMetaExtendedHeader); + default: return 0; + } + } + protected: + constexpr ContentMetaAccessor(const void *d, size_t sz) : m_data(const_cast<void *>(d)), m_size(sz), m_is_header_valid(true) { /* ... */ } + constexpr ContentMetaAccessor(void *d, size_t sz) : m_data(d), m_size(sz), m_is_header_valid(false) { /* ... */ } + + template<class NewHeaderType, class NewInfoType> + static constexpr size_t CalculateSizeImpl(size_t ext_header_size, size_t content_count, size_t content_meta_count, size_t extended_data_size, bool has_digest) { + return sizeof(NewHeaderType) + ext_header_size + content_count * sizeof(NewInfoType) + content_meta_count * sizeof(ContentMetaInfo) + extended_data_size + (has_digest ? sizeof(Digest) : 0); + } + + static constexpr size_t CalculateSize(ContentMetaType type, size_t content_count, size_t content_meta_count, size_t extended_data_size, bool has_digest = false) { + return CalculateSizeImpl<ContentMetaHeaderType, ContentInfoType>(GetExtendedHeaderSize(type), content_count, content_meta_count, extended_data_size, has_digest); + } + + uintptr_t GetExtendedHeaderAddress() const { + return reinterpret_cast<uintptr_t>(m_data) + sizeof(HeaderType); + } + + uintptr_t GetContentInfoStartAddress() const { + return this->GetExtendedHeaderAddress() + this->GetExtendedHeaderSize(); + } + + uintptr_t GetContentInfoAddress(size_t i) const { + return this->GetContentInfoStartAddress() + i * sizeof(InfoType); + } + + uintptr_t GetContentMetaInfoStartAddress() const { + return this->GetContentInfoAddress(this->GetContentCount()); + } + + uintptr_t GetContentMetaInfoAddress(size_t i) const { + return this->GetContentMetaInfoStartAddress() + i * sizeof(ContentMetaInfo); + } + + uintptr_t GetExtendedDataAddress() const { + return this->GetContentMetaInfoAddress(this->GetContentMetaCount()); + } + + uintptr_t GetDigestAddress() const { + return this->GetExtendedDataAddress() + this->GetExtendedDataSize(); + } + + InfoType *GetWritableContentInfo(size_t i) const { + AMS_ABORT_UNLESS(i < this->GetContentCount()); + + return reinterpret_cast<InfoType *>(this->GetContentInfoAddress(i)); + } + + InfoType *GetWritableContentInfo(ContentType type) const { + InfoType *found = nullptr; + for (size_t i = 0; i < this->GetContentCount(); i++) { + /* We want to find the info with the lowest id offset and the correct type. */ + InfoType *info = this->GetWritableContentInfo(i); + if (info->GetType() == type && (found == nullptr || info->GetIdOffset() < found->GetIdOffset())) { + found = info; + } + } + return found; + } + + InfoType *GetWritableContentInfo(ContentType type, u8 id_ofs) const { + for (size_t i = 0; i < this->GetContentCount(); i++) { + /* We want to find the info with the correct id offset and the correct type. */ + if (InfoType *info = this->GetWritableContentInfo(i); info->GetType() == type && info->GetIdOffset() == id_ofs) { + return info; + } + } + return nullptr; + } + + s64 CalculateContentRequiredSize() const { + s64 required_size = 0; + for (size_t i = 0; i < this->GetContentCount(); i++) { + required_size += CalculateRequiredSize(this->GetContentInfo(i)->info.GetSize()); + } + return required_size; + } + + void SetStorageId(StorageId storage_id) { + this->GetWritableHeader()->storage_id = static_cast<u8>(storage_id); + } + + public: + const void *GetData() const { + return m_data; + } + + size_t GetSize() const { + return m_size; + } + + HeaderType *GetWritableHeader() const { + AMS_ABORT_UNLESS(m_is_header_valid); + return reinterpret_cast<HeaderType *>(m_data); + } + + const HeaderType *GetHeader() const { + AMS_ABORT_UNLESS(m_is_header_valid); + return static_cast<const HeaderType *>(m_data); + } + + ContentMetaKey GetKey() const { + auto header = this->GetHeader(); + return ContentMetaKey::Make(header->id, header->version, header->type, header->install_type); + } + + size_t GetExtendedHeaderSize() const { + return this->GetHeader()->extended_header_size; + } + + template<typename ExtendedHeaderType> + const ExtendedHeaderType *GetExtendedHeader() const { + return reinterpret_cast<const ExtendedHeaderType *>(this->GetExtendedHeaderAddress()); + } + + size_t GetContentCount() const { + return this->GetHeader()->content_count; + } + + const InfoType *GetContentInfo(size_t i) const { + AMS_ABORT_UNLESS(i < this->GetContentCount()); + + return this->GetWritableContentInfo(i); + } + + const InfoType *GetContentInfo(ContentType type) const { + return this->GetWritableContentInfo(type); + } + + const InfoType *GetContentInfo(ContentType type, u8 id_ofs) const { + return this->GetWritableContentInfo(type, id_ofs); + } + + size_t GetContentMetaCount() const { + return this->GetHeader()->content_meta_count; + } + + const ContentMetaInfo *GetContentMetaInfo(size_t i) const { + AMS_ABORT_UNLESS(i < this->GetContentMetaCount()); + + return reinterpret_cast<const ContentMetaInfo *>(this->GetContentMetaInfoAddress(i)); + } + + size_t GetExtendedDataSize() const { + switch (this->GetHeader()->type) { + case ContentMetaType::Patch: return this->GetExtendedHeader<PatchMetaExtendedHeader>()->extended_data_size; + case ContentMetaType::Delta: return this->GetExtendedHeader<DeltaMetaExtendedHeader>()->extended_data_size; + case ContentMetaType::SystemUpdate: return this->GetExtendedHeaderSize() == 0 ? 0 : this->GetExtendedHeader<SystemUpdateMetaExtendedHeader>()->extended_data_size; + default: return 0; + } + } + + const void *GetExtendedData() const { + return reinterpret_cast<const void *>(this->GetExtendedDataAddress()); + } + + const Digest *GetDigest() const { + return reinterpret_cast<Digest *>(this->GetDigestAddress()); + } + + bool HasContent(const ContentId &id) const { + for (size_t i = 0; i < this->GetContentCount(); i++) { + if (id == this->GetContentInfo(i)->GetId()) { + return true; + } + } + return false; + } + + StorageId GetStorageId() const { + return static_cast<StorageId>(this->GetHeader()->storage_id); + } + + util::optional<ApplicationId> GetApplicationId(const ContentMetaKey &key) const { + switch (key.type) { + case ContentMetaType::Application: return ApplicationId{ key.id }; + case ContentMetaType::Patch: return this->GetExtendedHeader<PatchMetaExtendedHeader>()->application_id; + case ContentMetaType::AddOnContent: return this->GetExtendedHeader<AddOnContentMetaExtendedHeader>()->application_id; + case ContentMetaType::Delta: return this->GetExtendedHeader<DeltaMetaExtendedHeader>()->application_id; + default: return util::nullopt; + } + } + + util::optional<ApplicationId> GetApplicationId() const { + return this->GetApplicationId(this->GetKey()); + } + }; + + class ContentMetaReader : public ContentMetaAccessor<ContentMetaHeader, ContentInfo> { + public: + constexpr ContentMetaReader(const void *data, size_t size) : ContentMetaAccessor(data, size) { /* ... */ } + + using ContentMetaAccessor::CalculateSize; + }; + + class PackagedContentMetaReader : public ContentMetaAccessor<PackagedContentMetaHeader, PackagedContentInfo> { + public: + constexpr PackagedContentMetaReader(const void *data, size_t size) : ContentMetaAccessor(data, size) { /* ... */ } + + size_t CalculateConvertInstallContentMetaSize() const; + void ConvertToInstallContentMeta(void *dst, size_t size, const InstallContentInfo &meta); + + size_t CalculateConvertContentMetaSize() const; + void ConvertToContentMeta(void *dst, size_t size, const ContentInfo &meta); + + size_t CalculateConvertFragmentOnlyInstallContentMetaSize(s32 fragment_count) const { + return CalculateSizeImpl<InstallContentMetaHeader, InstallContentInfo>(this->GetExtendedHeaderSize(), fragment_count + 1, 0, 0, false); + } + + Result CalculateConvertFragmentOnlyInstallContentMetaSize(size_t *out_size, u32 source_version) const; + Result ConvertToFragmentOnlyInstallContentMeta(void *dst, size_t size, const InstallContentInfo &content_info, u32 source_version); + + size_t CountDeltaFragments() const; + + static constexpr size_t CalculateSize(ContentMetaType type, size_t content_count, size_t content_meta_count, size_t extended_data_size) { + return ContentMetaAccessor::CalculateSize(type, content_count, content_meta_count, extended_data_size, true); + } + + size_t GetExtendedDataOffset() const { + return this->GetExtendedDataAddress() - reinterpret_cast<uintptr_t>(this->GetData()); + } + }; + + class InstallContentMetaReader : public ContentMetaAccessor<InstallContentMetaHeader, InstallContentInfo> { + public: + constexpr InstallContentMetaReader(const void *data, size_t size) : ContentMetaAccessor(data, size) { /* ... */ } + + using ContentMetaAccessor::CalculateSize; + using ContentMetaAccessor::CalculateContentRequiredSize; + using ContentMetaAccessor::GetStorageId; + + size_t CalculateConvertSize() const; + + void ConvertToContentMeta(void *dst, size_t size) const; + }; + + class InstallContentMetaWriter : public ContentMetaAccessor<InstallContentMetaHeader, InstallContentInfo> { + public: + InstallContentMetaWriter(const void *data, size_t size) : ContentMetaAccessor(data, size) { /* ... */ } + + using ContentMetaAccessor::CalculateSize; + using ContentMetaAccessor::CalculateContentRequiredSize; + using ContentMetaAccessor::GetWritableContentInfo; + using ContentMetaAccessor::SetStorageId; + }; + + class PatchMetaExtendedDataAccessor; + struct PatchDeltaHeader; + class AutoBuffer; + + class MetaConverter { + public: + static Result CountContentExceptForMeta(s32 *out, PatchMetaExtendedDataAccessor *accessor, const PatchDeltaHeader &header, s32 delta_index); + static Result FindDeltaIndex(s32 *out, PatchMetaExtendedDataAccessor *accessor, u32 source_version, u32 destination_version); + static Result GetFragmentOnlyInstallContentMeta(AutoBuffer *out, const InstallContentInfo &content_info, const PackagedContentMetaReader &reader, PatchMetaExtendedDataAccessor *accessor, u32 source_version); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_database.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_database.hpp new file mode 100644 index 00000000..6c6a8ade --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_database.hpp @@ -0,0 +1,236 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_i_content_meta_database.hpp> + +namespace ams::ncm { + + class ContentMetaDatabase { + NON_COPYABLE(ContentMetaDatabase); + public: + struct ListCount { + s32 written; + s32 total; + }; + private: + sf::SharedPointer<IContentMetaDatabase> m_interface; + public: + ContentMetaDatabase() : m_interface(nullptr) { /* ... */ } + explicit ContentMetaDatabase(sf::SharedPointer<IContentMetaDatabase> intf) : m_interface(intf) { /* ... */ } + + ContentMetaDatabase(ContentMetaDatabase &&rhs) { + m_interface = std::move(rhs.m_interface); + } + + ContentMetaDatabase &operator=(ContentMetaDatabase &&rhs) { + ContentMetaDatabase(std::move(rhs)).swap(*this); + return *this; + } + + void swap(ContentMetaDatabase &rhs) { + std::swap(m_interface, rhs.m_interface); + } + public: + Result Set(const ContentMetaKey &key, const void *buf, size_t size) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->Set(key, sf::InBuffer(buf, size))); + } + + Result Get(size_t *out_size, void *dst, size_t dst_size, const ContentMetaKey &key) { + AMS_ASSERT(m_interface != nullptr); + u64 size; + R_TRY(m_interface->Get(std::addressof(size), key, sf::OutBuffer(dst, dst_size))); + + *out_size = size; + R_SUCCEED(); + } + + #define AMS_NCM_DEFINE_GETTERS(Kind, IdType) \ + Result Get##Kind(ContentId *out, IdType##Id id, u32 version) { \ + R_RETURN(m_interface->GetContentIdByType(out, ContentMetaKey::MakeUnknownType(id.value, version), ContentType::Kind)); \ + } \ + \ + Result Get##Kind(ContentInfo *out, IdType##Id id, u32 version) { \ + R_RETURN(m_interface->GetContentInfoByType(out, ContentMetaKey::MakeUnknownType(id.value, version), ContentType::Kind)); \ + } \ + \ + Result GetLatest##Kind(ContentId *out, IdType##Id id) { \ + ContentMetaKey latest_key; \ + R_TRY(m_interface->GetLatestContentMetaKey(std::addressof(latest_key), id.value)); \ + R_RETURN(m_interface->GetContentIdByType(out, latest_key, ContentType::Kind)); \ + } \ + \ + Result GetLatest##Kind(ContentInfo *out, IdType##Id id) { \ + ContentMetaKey latest_key; \ + R_TRY(m_interface->GetLatestContentMetaKey(std::addressof(latest_key), id.value)); \ + R_RETURN(m_interface->GetContentInfoByType(out, latest_key, ContentType::Kind)); \ + } + + AMS_NCM_DEFINE_GETTERS(Program, Program) + AMS_NCM_DEFINE_GETTERS(Data, Data) + AMS_NCM_DEFINE_GETTERS(Control, Application) + AMS_NCM_DEFINE_GETTERS(HtmlDocument, Application) + AMS_NCM_DEFINE_GETTERS(LegalInformation, Application) + + #undef AMS_NCM_DEFINE_GETTERS + + Result Remove(const ContentMetaKey &key) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->Remove(key)); + } + + Result Remove(SystemProgramId id, u32 version) { + R_RETURN(this->Remove(ContentMetaKey::Make(id, version))); + } + + Result Remove(SystemDataId id, u32 version) { + R_RETURN(this->Remove(ContentMetaKey::Make(id, version))); + } + + Result Remove(ApplicationId id, u32 version) { + R_RETURN(this->Remove(ContentMetaKey::Make(id, version))); + } + + Result GetContentIdByType(ContentId *out_content_id, const ContentMetaKey &key, ContentType type) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->GetContentIdByType(out_content_id, key, type)); + } + + Result GetContentIdByTypeAndIdOffset(ContentId *out_content_id, const ContentMetaKey &key, ContentType type, u8 id_offset) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->GetContentIdByTypeAndIdOffset(out_content_id, key, type, id_offset)); + } + + ListCount ListApplication(ApplicationContentMetaKey *dst, size_t dst_size) { + ListCount lc = {}; + R_ABORT_UNLESS(m_interface->ListApplication(std::addressof(lc.total), std::addressof(lc.written), sf::OutArray<ApplicationContentMetaKey>(dst, dst_size), ContentMetaType::Unknown)); + return lc; + } + + ListCount ListContentMeta(ContentMetaKey *dst, size_t dst_size, ContentMetaType type = ContentMetaType::Unknown, ApplicationId app_id = InvalidApplicationId, u64 min = std::numeric_limits<u64>::min(), u64 max = std::numeric_limits<u64>::max(), ContentInstallType install_type = ContentInstallType::Full) { + ListCount lc = {}; + R_ABORT_UNLESS(m_interface->List(std::addressof(lc.total), std::addressof(lc.written), sf::OutArray<ContentMetaKey>(dst, dst_size), type, app_id, min, max, install_type)); + return lc; + } + + Result GetLatest(ContentMetaKey *out_key, u64 id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->GetLatestContentMetaKey(out_key, id)); + } + + Result ListContentInfo(s32 *out_count, ContentInfo *dst, size_t dst_size, const ContentMetaKey &key, s32 offset) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->ListContentInfo(out_count, sf::OutArray<ContentInfo>(dst, dst_size), key, offset)); + } + + Result ListContentMetaInfo(s32 *out_count, ContentMetaInfo *dst, size_t dst_size, const ContentMetaKey &key, s32 offset) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->ListContentMetaInfo(out_count, sf::OutArray<ContentMetaInfo>(dst, dst_size), key, offset)); + } + + Result Has(bool *out, const ContentMetaKey &key) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->Has(out, key)); + } + + Result HasAll(bool *out, const ContentMetaKey *keys, size_t num_keys) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->HasAll(out, sf::InArray<ContentMetaKey>(keys, num_keys))); + } + + Result HasContent(bool *out, const ContentMetaKey &key, const ContentId &content_id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->HasContent(out, key, content_id)); + } + + Result GetSize(size_t *out_size, const ContentMetaKey &key) { + AMS_ASSERT(m_interface != nullptr); + u64 size; + R_TRY(m_interface->GetSize(std::addressof(size), key)); + + *out_size = size; + R_SUCCEED(); + } + + Result GetRequiredSystemVersion(u32 *out_version, const ContentMetaKey &key) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->GetRequiredSystemVersion(out_version, key)); + } + + Result GetPatchId(PatchId *out_patch_id, const ContentMetaKey &key) { + AMS_ASSERT(m_interface != nullptr); + static_assert(sizeof(*out_patch_id) == sizeof(u64)); + R_RETURN(m_interface->GetPatchContentMetaId(reinterpret_cast<u64 *>(out_patch_id), key)); + } + + Result GetDataPatchId(DataPatchId *out_patch_id, const ContentMetaKey &key) { + AMS_ASSERT(m_interface != nullptr); + static_assert(sizeof(*out_patch_id) == sizeof(u64)); + R_RETURN(m_interface->GetPatchContentMetaId(reinterpret_cast<u64 *>(out_patch_id), key)); + } + + Result DisableForcibly() { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->DisableForcibly()); + } + + Result LookupOrphanContent(bool *out_orphaned, ContentId *content_list, size_t count) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->LookupOrphanContent(sf::OutArray<bool>(out_orphaned, count), sf::InArray<ContentId>(content_list, count))); + } + + Result Commit() { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->Commit()); + } + + Result GetAttributes(u8 *out_attributes, const ContentMetaKey &key) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->GetAttributes(out_attributes, key)); + } + + Result GetRequiredApplicationVersion(u32 *out_version, const ContentMetaKey &key) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->GetRequiredApplicationVersion(out_version, key)); + } + + Result GetContentAccessibilities(u8 *out_accessibilities, const ContentMetaKey &key) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->GetContentAccessibilities(out_accessibilities, key)); + } + + Result GetContentInfoByType(ContentInfo *out_content_info, const ContentMetaKey &key, ContentType type) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->GetContentInfoByType(out_content_info, key, type)); + } + + Result GetContentInfoByTypeAndIdOffset(ContentInfo *out_content_info, const ContentMetaKey &key, ContentType type, u8 id_offset) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->GetContentInfoByTypeAndIdOffset(out_content_info, key, type, id_offset)); + } + + Result GetPlatform(ContentMetaPlatform *out, const ContentMetaKey &key) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->GetPlatform(out, key)); + } + + Result HasAttributes(u8 *out, u8 attr_mask) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->HasAttributes(out, attr_mask)); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_extended_data.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_extended_data.hpp new file mode 100644 index 00000000..f8888e49 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_extended_data.hpp @@ -0,0 +1,857 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_content_info.hpp> +#include <stratosphere/ncm/ncm_content_meta_id.hpp> +#include <stratosphere/ncm/ncm_content_meta.hpp> +#include <stratosphere/ncm/ncm_firmware_variation.hpp> +#include <stratosphere/ncm/ncm_mapped_memory.hpp> + +namespace ams::ncm { + + enum class UpdateType : u8 { + ApplyAsDelta = 0, + Overwrite = 1, + Create = 2, + }; + + struct FragmentIndicator { + u16 content_info_index; + u16 fragment_index; + }; + + struct FragmentSet { + ContentId source_content_id; + ContentId destination_content_id; + u32 source_size_low; + u16 source_size_high; + u16 destination_size_high; + u32 destination_size_low; + u16 fragment_count; + ContentType target_content_type; + UpdateType update_type; + u8 reserved[4]; + + constexpr s64 GetSourceSize() const { + return (static_cast<s64>(this->source_size_high) << 32) + this->source_size_low; + } + + constexpr s64 GetDestinationSize() const { + return (static_cast<s64>(this->destination_size_high) << 32) + this->destination_size_low; + } + + constexpr void SetSourceSize(s64 size) { + this->source_size_low = size & 0xFFFFFFFFll; + this->source_size_high = static_cast<u16>(size >> 32); + } + + constexpr void SetDestinationSize(s64 size) { + this->destination_size_low = size & 0xFFFFFFFFll; + this->destination_size_high = static_cast<u16>(size >> 32); + } + }; + + struct SystemUpdateMetaExtendedDataHeader { + u32 version; + u32 firmware_variation_count; + }; + + struct DeltaMetaExtendedDataHeader { + PatchId source_id; + PatchId destination_id; + u32 source_version; + u32 destination_version; + u16 fragment_set_count; + u8 reserved[6]; + }; + + struct PatchMetaExtendedDataHeader { + u32 history_count; + u32 delta_history_count; + u32 delta_count; + u32 fragment_set_count; + u32 history_content_total_count; + u32 delta_content_total_count; + u8 reserved[4]; + }; + + struct PatchHistoryHeader { + ContentMetaKey key; + Digest digest; + u16 content_count; + u8 reserved[2]; + }; + + struct PatchDeltaHistory { + PatchId source_id; + PatchId destination_id; + u32 source_version; + u32 destination_version; + u64 download_size; + u8 reserved[4]; + }; + + struct PatchDeltaHeader { + DeltaMetaExtendedDataHeader delta; + u16 content_count; + u8 reserved[4]; + }; + + template<typename MemberTypePointer, typename DataTypePointer> + class PatchMetaExtendedDataReaderWriterBase { + private: + MemberTypePointer m_data; + const size_t m_size; + public: + PatchMetaExtendedDataReaderWriterBase(MemberTypePointer d, size_t sz) : m_data(d), m_size(sz) { /* ... */ } + protected: + s32 CountFragmentSet(s32 delta_index) const { + auto delta_header = this->GetPatchDeltaHeader(0); + s32 count = 0; + for (s32 i = 0; i < delta_index; i++) { + count += delta_header[i].delta.fragment_set_count; + } + return count; + } + + s32 CountHistoryContent(s32 history_index) const { + auto history_header = this->GetPatchHistoryHeader(0); + s32 count = 0; + for (s32 i = 0; i < history_index; i++) { + count += history_header[i].content_count; + } + return count; + } + + s32 CountDeltaContent(s32 delta_index) const { + auto delta_header = this->GetPatchDeltaHeader(0); + s32 count = 0; + for (s32 i = 0; i < delta_index; i++) { + count += delta_header[i].content_count; + } + return count; + } + + s32 CountFragment(s32 index) const { + auto fragment_set = this->GetFragmentSet(0, 0); + s32 count = 0; + for (s32 i = 0; i < index; i++) { + count += fragment_set[i].fragment_count; + } + return count; + } + + DataTypePointer GetHeaderAddress() const { + return reinterpret_cast<DataTypePointer>(m_data); + } + + DataTypePointer GetPatchHistoryHeaderAddress(s32 index) const { + auto header = this->GetHeader(); + AMS_ABORT_UNLESS(static_cast<u16>(index) < header->history_count); + + return this->GetHeaderAddress() + + sizeof(PatchMetaExtendedDataHeader) + + sizeof(PatchHistoryHeader) * index; + } + + DataTypePointer GetPatchDeltaHistoryAddress(s32 index) const { + auto header = this->GetHeader(); + AMS_ABORT_UNLESS(static_cast<u16>(index) < header->delta_history_count); + + return this->GetHeaderAddress() + + sizeof(PatchMetaExtendedDataHeader) + + sizeof(PatchHistoryHeader) * header->history_count + + sizeof(PatchDeltaHistory) * index; + } + + DataTypePointer GetPatchDeltaHeaderAddress(s32 index) const { + auto header = this->GetHeader(); + AMS_ABORT_UNLESS(static_cast<u16>(index) < header->delta_count); + + return this->GetHeaderAddress() + + sizeof(PatchMetaExtendedDataHeader) + + sizeof(PatchHistoryHeader) * header->history_count + + sizeof(PatchDeltaHistory) * header->delta_history_count + + sizeof(PatchDeltaHeader) * index; + } + + DataTypePointer GetFragmentSetAddress(s32 delta_index, s32 fragment_set_index) const { + auto header = this->GetHeader(); + AMS_ABORT_UNLESS(static_cast<u16>(delta_index) < header->delta_count); + + auto delta_header = this->GetPatchDeltaHeader(delta_index); + AMS_ABORT_UNLESS(static_cast<u16>(fragment_set_index) < delta_header->delta.fragment_set_count); + + auto previous_fragment_set_count = this->CountFragmentSet(delta_index); + + return this->GetHeaderAddress() + + sizeof(PatchMetaExtendedDataHeader) + + sizeof(PatchHistoryHeader) * header->history_count + + sizeof(PatchDeltaHistory) * header->delta_history_count + + sizeof(PatchDeltaHeader) * header->delta_count + + sizeof(FragmentSet) * (previous_fragment_set_count + fragment_set_index); + } + + DataTypePointer GetPatchHistoryContentInfoAddress(s32 history_index, s32 content_index) const { + auto header = this->GetHeader(); + auto history_header = this->GetPatchHistoryHeader(history_index); + AMS_ABORT_UNLESS(static_cast<u16>(content_index) < history_header->content_count); + + auto prev_history_count = this->CountHistoryContent(history_index); + + return this->GetHeaderAddress() + + sizeof(PatchMetaExtendedDataHeader) + + sizeof(PatchHistoryHeader) * header->history_count + + sizeof(PatchDeltaHistory) * header->delta_history_count + + sizeof(PatchDeltaHeader) * header->delta_count + + sizeof(FragmentSet) * header->fragment_set_count + + sizeof(ContentInfo) * (prev_history_count + content_index); + } + + DataTypePointer GetPatchDeltaPackagedContentInfoAddress(s32 delta_index, s32 content_index) const { + auto header = this->GetHeader(); + auto delta_header = this->GetPatchDeltaHeader(delta_index); + AMS_ABORT_UNLESS(static_cast<u16>(content_index) < delta_header->content_count); + + auto content_count = this->CountDeltaContent(delta_index); + + return this->GetHeaderAddress() + + sizeof(PatchMetaExtendedDataHeader) + + sizeof(PatchHistoryHeader) * header->history_count + + sizeof(PatchDeltaHistory) * header->delta_history_count + + sizeof(PatchDeltaHeader) * header->delta_count + + sizeof(FragmentSet) * header->fragment_set_count + + sizeof(ContentInfo) * header->history_content_total_count + + sizeof(PackagedContentInfo) * (content_count + content_index); + } + + DataTypePointer GetFragmentIndicatorAddress(s32 delta_index, s32 fragment_set_index, s32 index) const { + auto header = this->GetHeader(); + auto fragment_set = this->GetFragmentSet(delta_index, fragment_set_index); + AMS_ABORT_UNLESS(static_cast<u16>(index) < fragment_set->fragment_count); + + auto fragment_set_count = this->CountFragmentSet(delta_index); + auto fragment_count = this->CountFragment(fragment_set_count + fragment_set_index); + + return this->GetHeaderAddress() + + sizeof(PatchMetaExtendedDataHeader) + + sizeof(PatchHistoryHeader) * header->history_count + + sizeof(PatchDeltaHistory) * header->delta_history_count + + sizeof(PatchDeltaHeader) * header->delta_count + + sizeof(FragmentSet) * header->fragment_set_count + + sizeof(ContentInfo) * header->history_content_total_count + + sizeof(PackagedContentInfo) * header->delta_content_total_count + + sizeof(FragmentIndicator) * (fragment_count + index); + } + public: + const PatchMetaExtendedDataHeader *GetHeader() const { + return reinterpret_cast<const PatchMetaExtendedDataHeader *>(this->GetHeaderAddress()); + } + + const PatchHistoryHeader *GetPatchHistoryHeader(s32 index) const { + return reinterpret_cast<const PatchHistoryHeader *>(this->GetPatchHistoryHeaderAddress(index)); + } + + const PatchDeltaHistory *GetPatchDeltaHistory(s32 index) const { + return reinterpret_cast<const PatchDeltaHistory *>(this->GetPatchDeltaHistoryAddress(index)); + } + + const ContentInfo *GetPatchHistoryContentInfo(s32 history_index, s32 content_index) const { + return reinterpret_cast<const ContentInfo *>(this->GetPatchHistoryContentInfoAddress(history_index, content_index)); + } + + const PatchDeltaHeader *GetPatchDeltaHeader(s32 index) const { + return reinterpret_cast<const PatchDeltaHeader *>(this->GetPatchDeltaHeaderAddress(index)); + } + + const PackagedContentInfo *GetPatchDeltaPackagedContentInfo(s32 delta_index, s32 content_index) const { + return reinterpret_cast<const PackagedContentInfo *>(this->GetPatchDeltaPackagedContentInfoAddress(delta_index, content_index)); + } + + const FragmentSet *GetFragmentSet(s32 delta_index, s32 fragment_set_index) const { + return reinterpret_cast<const FragmentSet *>(this->GetFragmentSetAddress(delta_index, fragment_set_index)); + } + + const FragmentIndicator *GetFragmentIndicator(s32 delta_index, s32 fragment_set_index, s32 index) const { + return reinterpret_cast<const FragmentIndicator *>(this->GetFragmentIndicatorAddress(delta_index, fragment_set_index, index)); + } + + const FragmentIndicator *FindFragmentIndicator(s32 delta_index, s32 fragment_set_index, s32 fragment_index) const { + auto fragment_set = this->GetFragmentSet(delta_index, fragment_set_index); + auto fragment = this->GetFragmentIndicator(delta_index, fragment_set_index, 0); + for (s32 i = 0; i < fragment_set->fragment_count; i++) { + if (fragment[i].fragment_index == fragment_index) { + return std::addressof(fragment[i]); + } + } + return nullptr; + } + }; + + class PatchMetaExtendedDataReader : public PatchMetaExtendedDataReaderWriterBase<const void *, const u8 *> { + public: + PatchMetaExtendedDataReader(const void *data, size_t size) : PatchMetaExtendedDataReaderWriterBase(data, size) { /* ... */ } + }; + + class SystemUpdateMetaExtendedDataReaderWriterBase { + private: + void *m_data; + const size_t m_size; + bool m_is_header_valid; + protected: + constexpr SystemUpdateMetaExtendedDataReaderWriterBase(const void *d, size_t sz) : m_data(const_cast<void *>(d)), m_size(sz), m_is_header_valid(true) { /* ... */ } + constexpr SystemUpdateMetaExtendedDataReaderWriterBase(void *d, size_t sz) : m_data(d), m_size(sz), m_is_header_valid(false) { /* ... */ } + + uintptr_t GetHeaderAddress() const { + return reinterpret_cast<uintptr_t>(m_data); + } + + uintptr_t GetFirmwareVariationIdStartAddress() const { + return this->GetHeaderAddress() + sizeof(SystemUpdateMetaExtendedDataHeader); + } + + uintptr_t GetFirmwareVariationIdAddress(size_t i) const { + return this->GetFirmwareVariationIdStartAddress() + i * sizeof(FirmwareVariationId); + } + + uintptr_t GetFirmwareVariationInfoStartAddress() const { + return this->GetFirmwareVariationIdAddress(this->GetFirmwareVariationCount()); + } + + uintptr_t GetFirmwareVariationInfoAddress(size_t i) const { + return this->GetFirmwareVariationInfoStartAddress() + i * sizeof(FirmwareVariationInfo); + } + + uintptr_t GetContentMetaInfoStartAddress() const { + return this->GetFirmwareVariationInfoAddress(this->GetFirmwareVariationCount()); + } + + uintptr_t GetContentMetaInfoAddress(size_t i) const { + return this->GetContentMetaInfoStartAddress() + i * sizeof(ContentMetaInfo); + } + public: + const SystemUpdateMetaExtendedDataHeader *GetHeader() const { + AMS_ABORT_UNLESS(m_is_header_valid); + return reinterpret_cast<const SystemUpdateMetaExtendedDataHeader *>(this->GetHeaderAddress()); + } + + size_t GetFirmwareVariationCount() const { + return this->GetHeader()->firmware_variation_count; + } + + const FirmwareVariationId *GetFirmwareVariationId(size_t i) const { + AMS_ABORT_UNLESS(i < this->GetFirmwareVariationCount()); + + return reinterpret_cast<FirmwareVariationId *>(this->GetFirmwareVariationIdAddress(i)); + } + + const FirmwareVariationInfo *GetFirmwareVariationInfo(size_t i) const { + AMS_ABORT_UNLESS(i < this->GetFirmwareVariationCount()); + + return reinterpret_cast<FirmwareVariationInfo *>(this->GetFirmwareVariationInfoAddress(i)); + } + + void GetContentMetaInfoList(Span<const ContentMetaInfo> *out_list, size_t i) const { + size_t preceding_content_meta_count = 0; + + /* Count the number of preceding content metas. */ + for (size_t j = 0; j < i; j++) { + preceding_content_meta_count += this->GetFirmwareVariationInfo(j)->content_meta_count; + } + + /* Output the list. */ + *out_list = Span<const ContentMetaInfo>(reinterpret_cast<const ContentMetaInfo *>(this->GetContentMetaInfoAddress(preceding_content_meta_count)), this->GetFirmwareVariationInfo(i)->content_meta_count); + } + }; + + class SystemUpdateMetaExtendedDataReader : public SystemUpdateMetaExtendedDataReaderWriterBase { + public: + constexpr SystemUpdateMetaExtendedDataReader(const void *data, size_t size) : SystemUpdateMetaExtendedDataReaderWriterBase(data, size) { /* ... */ } + }; + + template<typename T> + class ReadableStructPin; + + class AccessorBase { + public: + template<typename T> + class PinBase { + private: + AccessorBase *m_accessor; + u64 m_pin_id; + T *m_data; + size_t m_size; + public: + PinBase() : m_accessor(nullptr), m_data(nullptr), m_size(0) { + /* ... */ + } + + PinBase(const PinBase &) = delete; + PinBase &operator=(const PinBase &) = delete; + + PinBase(PinBase &&rhs) : m_accessor(rhs.m_accessor), m_pin_id(rhs.m_pin_id), m_data(rhs.m_data), m_size(rhs.m_size) { + rhs.m_accessor = nullptr; + } + + PinBase &operator=(PinBase &&rhs) { + m_accessor = rhs.m_accessor; + m_pin_id = rhs.m_pin_id; + m_data = rhs.m_data; + m_size = rhs.m_size; + rhs.m_accessor = nullptr; + + return *this; + } + + virtual ~PinBase() { + this->Reset(); + } + public: + void Reset() { + if (m_accessor != nullptr) { + m_accessor->ReleasePin(m_pin_id); + m_accessor = nullptr; + } + } + + void Reset(AccessorBase *accessor, u64 pin_id, void *data, size_t size) { + AMS_ASSERT(data != nullptr || size == 0); + + this->Reset(); + + m_accessor = accessor; + m_pin_id = pin_id; + m_data = reinterpret_cast<T *>(data); + m_size = size; + } + + T *GetData() const { + return m_data; + } + + size_t GetDataSize() const { + return m_size; + } + }; + private: + IMapper *m_mapper; + public: + AccessorBase(IMapper *mapper) : m_mapper(mapper) { + /* ... */ + } + + template<typename T> + Result AcquireReadableStructPin(ReadableStructPin<T> *out, size_t offset) { + /* Acquire mapped memory for the pin. */ + MappedMemory memory = {}; + R_TRY(m_mapper->GetMappedMemory(std::addressof(memory), offset, sizeof(T))); + + /* Mark the memory as in use. */ + R_RETURN(m_mapper->MarkUsing(memory.id)); + + /* Setup the pin. */ + out->Reset(this, memory.id, memory.GetBuffer(offset, sizeof(T)), sizeof(T)); + R_SUCCEED(); + } + + Result ReleasePin(u64 id) { + R_RETURN(m_mapper->UnmarkUsing(id)); + } + + template<typename T> + Result ReadStruct(T *out, size_t offset) { + /* Acquire mapped memory for the pin. */ + MappedMemory memory = {}; + R_TRY(m_mapper->GetMappedMemory(std::addressof(memory), offset, sizeof(T))); + + /* Mark the memory as in use. */ + R_RETURN(m_mapper->MarkUsing(memory.id)); + ON_SCOPE_EXIT { this->ReleasePin(memory.id); }; + + /* Copy out the struct. */ + *out = *reinterpret_cast<const T *>(memory.GetBuffer(offset, sizeof(T))); + R_SUCCEED(); + } + }; + + template<typename T> + class ReadableStructPin final : public AccessorBase::PinBase<const u8> { + public: + using PinBase::PinBase; + using PinBase::operator=; + + const T *Get() const { + return reinterpret_cast<const T *>(this->GetData()); + } + + size_t GetSize() const { + return this->GetDataSize(); + } + + const T &operator*() const { return *this->Get(); } + const T *operator->() const { return this->Get(); } + }; + + class PatchMetaExtendedDataAccessor : public AccessorBase { + private: + struct CachedCount { + s32 index; + s32 count; + }; + private: + util::optional<CachedCount> m_cached_history_content_count = util::nullopt; + util::optional<CachedCount> m_cached_delta_content_count = util::nullopt; + util::optional<CachedCount> m_cached_fragment_set_count = util::nullopt; + util::optional<CachedCount> m_cached_fragment_indicator_count = util::nullopt; + util::optional<PatchMetaExtendedDataHeader> m_header = util::nullopt; + public: + using AccessorBase::AccessorBase; + public: + Result GetHeader(ReadableStructPin<PatchMetaExtendedDataHeader> *out) { R_RETURN(this->AcquireReadableStructPin(out, 0)); } + Result GetHeader(PatchMetaExtendedDataHeader *out) { R_RETURN(this->template ReadStruct<PatchMetaExtendedDataHeader>(out, 0)); } + + Result GetHistoryHeader(ReadableStructPin<PatchHistoryHeader> *out, s32 index) { + /* Ensure we have our header. */ + R_TRY(this->EnsureHeader()); + + /* Check that the index is valid. */ + R_UNLESS(0 <= index && static_cast<u32>(index) < m_header->history_count, ncm::ResultInvalidOffset()); + + /* Get the header. */ + const size_t offset = sizeof(PatchHistoryHeader) + sizeof(PatchHistoryHeader) * index; + R_RETURN(this->AcquireReadableStructPin(out, offset)); + } + + Result GetPatchDeltaHistory(ReadableStructPin<PatchDeltaHistory> *out, s32 index) { + /* Ensure we have our header. */ + R_TRY(this->EnsureHeader()); + + /* Check that the index is valid. */ + R_UNLESS(0 <= index && static_cast<u32>(index) < m_header->delta_history_count, ncm::ResultInvalidOffset()); + + /* Get the history. */ + const size_t offset = sizeof(PatchHistoryHeader) + sizeof(PatchHistoryHeader) * m_header->history_count + sizeof(PatchDeltaHistory) * index; + R_RETURN(this->AcquireReadableStructPin(out, offset)); + } + + Result GetPatchDeltaHeader(ReadableStructPin<PatchDeltaHeader> *out, s32 index) { + /* Ensure we have our header. */ + R_TRY(this->EnsureHeader()); + + /* Check that the index is valid. */ + R_UNLESS(0 <= index && static_cast<u32>(index) < m_header->delta_count, ncm::ResultInvalidOffset()); + + /* Get the header. */ + const size_t offset = sizeof(PatchHistoryHeader) + sizeof(PatchHistoryHeader) * m_header->history_count + sizeof(PatchDeltaHistory) * m_header->delta_history_count + sizeof(PatchDeltaHeader) * index; + R_RETURN(this->AcquireReadableStructPin(out, offset)); + } + + Result GetFragmentSet(ReadableStructPin<FragmentSet> *out, s32 delta_index, s32 fragment_set_index) { + /* Ensure we have our header. */ + R_TRY(this->EnsureHeader()); + + /* Check that the index is valid. */ + R_UNLESS(0 <= delta_index && static_cast<u32>(delta_index) < m_header->delta_count, ncm::ResultInvalidOffset()); + + /* Get the previous fragment set count. */ + s32 previous_fragment_set_count = 0; + R_TRY(this->CountFragmentSet(std::addressof(previous_fragment_set_count), delta_index)); + + /* Get the set. */ + const size_t offset = sizeof(PatchHistoryHeader) + sizeof(PatchHistoryHeader) * m_header->history_count + sizeof(PatchDeltaHistory) * m_header->delta_history_count + sizeof(PatchDeltaHeader) * m_header->delta_count + sizeof(FragmentSet) * (previous_fragment_set_count + fragment_set_index); + R_RETURN(this->AcquireReadableStructPin(out, offset)); + } + + Result GetFragmentSetDirectly(ReadableStructPin<FragmentSet> *out, s32 fragment_set_direct_index) { + /* Ensure we have our header. */ + R_TRY(this->EnsureHeader()); + + /* Check that the index is valid. */ + R_UNLESS(0 <= fragment_set_direct_index && static_cast<u32>(fragment_set_direct_index) < m_header->fragment_set_count, ncm::ResultInvalidOffset()); + + /* Get the set. */ + const size_t offset = sizeof(PatchHistoryHeader) + sizeof(PatchHistoryHeader) * m_header->history_count + sizeof(PatchDeltaHistory) * m_header->delta_history_count + sizeof(PatchDeltaHeader) * m_header->delta_count + sizeof(FragmentSet) * (fragment_set_direct_index); + R_RETURN(this->AcquireReadableStructPin(out, offset)); + } + + Result GetPatchHistoryContentInfo(ReadableStructPin<ContentInfo> *out, s32 history_index, s32 content_index) { + /* Ensure we have our header. */ + R_TRY(this->EnsureHeader()); + + /* Check that the index is valid. */ + R_UNLESS(0 <= history_index && static_cast<u32>(history_index) < m_header->history_count, ncm::ResultInvalidOffset()); + + /* Determine the true history content index. */ + s32 prev_history_count = 0; + R_TRY(this->CountHistoryContentInfo(std::addressof(prev_history_count), history_index)); + + /* Adjust and check the content index. */ + content_index += prev_history_count; + R_UNLESS(0 <= content_index && static_cast<u32>(content_index) < m_header->history_content_total_count, ncm::ResultInvalidOffset()); + + /* Get the info. */ + const size_t offset = sizeof(PatchHistoryHeader) + sizeof(PatchHistoryHeader) * m_header->history_count + sizeof(PatchDeltaHistory) * m_header->delta_history_count + sizeof(PatchDeltaHeader) * m_header->delta_count + sizeof(FragmentSet) * m_header->fragment_set_count + sizeof(ContentInfo) * content_index; + R_RETURN(this->AcquireReadableStructPin(out, offset)); + } + + Result GetPatchDeltaContentInfo(ReadableStructPin<PackagedContentInfo> *out, s32 delta_index, s32 content_index) { + /* Ensure we have our header. */ + R_TRY(this->EnsureHeader()); + + /* Check that the index is valid. */ + R_UNLESS(0 <= delta_index && static_cast<u32>(delta_index) < m_header->delta_count, ncm::ResultInvalidOffset()); + + /* Determine the true delta content index. */ + s32 prev_delta_count = 0; + R_TRY(this->CountDeltaContentInfo(std::addressof(prev_delta_count), delta_index)); + + /* Adjust and check the content index. */ + content_index += prev_delta_count; + R_UNLESS(0 <= content_index && static_cast<u32>(content_index) < m_header->delta_content_total_count, ncm::ResultInvalidOffset()); + + /* Get the info. */ + const size_t offset = sizeof(PatchHistoryHeader) + sizeof(PatchHistoryHeader) * m_header->history_count + sizeof(PatchDeltaHistory) * m_header->delta_history_count + sizeof(PatchDeltaHeader) * m_header->delta_count + sizeof(FragmentSet) * m_header->fragment_set_count + sizeof(ContentInfo) * m_header->history_content_total_count + sizeof(PackagedContentInfo) * content_index; + R_RETURN(this->AcquireReadableStructPin(out, offset)); + } + + Result GetFragmentIndicator(ReadableStructPin<FragmentIndicator> *out, s32 delta_index, s32 fragment_set_index, s32 index) { + /* Ensure we have our header. */ + R_TRY(this->EnsureHeader()); + + /* Check that the index is valid. */ + R_UNLESS(0 <= delta_index && static_cast<u32>(delta_index) < m_header->delta_count, ncm::ResultInvalidOffset()); + + /* Get the previous fragment set count. */ + s32 previous_fragment_set_count = 0; + R_TRY(this->CountFragmentSet(std::addressof(previous_fragment_set_count), delta_index)); + + /* Get the previous fragment indicator count. */ + s32 previous_fragment_count = 0; + R_TRY(this->CountFragmentIndicator(std::addressof(previous_fragment_count), previous_fragment_count + fragment_set_index)); + + /* Get the info. */ + const size_t offset = sizeof(PatchHistoryHeader) + sizeof(PatchHistoryHeader) * m_header->history_count + sizeof(PatchDeltaHistory) * m_header->delta_history_count + sizeof(PatchDeltaHeader) * m_header->delta_count + sizeof(FragmentSet) * m_header->fragment_set_count + sizeof(ContentInfo) * m_header->history_content_total_count + sizeof(PackagedContentInfo) * m_header->delta_content_total_count + sizeof(FragmentIndicator) * (previous_fragment_count + index); + R_RETURN(this->AcquireReadableStructPin(out, offset)); + } + + Result FindFragmentIndicator(ReadableStructPin<FragmentIndicator> *out, s32 delta_index, s32 fragment_set_index, s32 fragment_index) { + /* Ensure we have our header. */ + R_TRY(this->EnsureHeader()); + + /* Check that the index is valid. */ + R_UNLESS(0 <= delta_index && static_cast<u32>(delta_index) < m_header->delta_count, ncm::ResultInvalidOffset()); + + /* Get the fragment count. */ + s32 fragment_count = 0; + { + ReadableStructPin<FragmentSet> set; + R_TRY(this->GetFragmentSet(std::addressof(set), delta_index, fragment_set_index)); + + fragment_count = set->fragment_count; + } + + /* Get the previous fragment set count. */ + s32 previous_fragment_set_count = 0; + R_TRY(this->CountFragmentSet(std::addressof(previous_fragment_set_count), delta_index)); + + /* Get the previous fragment indicator count. */ + s32 previous_fragment_count = 0; + R_TRY(this->CountFragmentIndicator(std::addressof(previous_fragment_count), previous_fragment_count + fragment_set_index)); + + /* Look for a correct indicator. */ + for (auto i = 0; i < fragment_count; ++i) { + /* Get the current info. */ + ReadableStructPin<FragmentIndicator> indicator; + const size_t offset = sizeof(PatchHistoryHeader) + sizeof(PatchHistoryHeader) * m_header->history_count + sizeof(PatchDeltaHistory) * m_header->delta_history_count + sizeof(PatchDeltaHeader) * m_header->delta_count + sizeof(FragmentSet) * m_header->fragment_set_count + sizeof(ContentInfo) * m_header->history_content_total_count + sizeof(PackagedContentInfo) * m_header->delta_content_total_count + sizeof(FragmentIndicator) * (previous_fragment_count + i); + R_TRY(this->AcquireReadableStructPin(std::addressof(indicator), offset)); + + /* If it matches, return it. */ + if (indicator->fragment_index == fragment_index) { + *out = std::move(indicator); + R_SUCCEED(); + } + } + + /* We didn't find an indicator. */ + R_THROW(ncm::ResultFragmentIndicatorNotFound()); + } + + Result GetHistoryHeader(PatchHistoryHeader *out, s32 index) { + /* Get the pin. */ + ReadableStructPin<PatchHistoryHeader> pin; + R_TRY(this->GetHistoryHeader(std::addressof(pin), index)); + + /* Copy it out. */ + *out = *pin; + R_SUCCEED(); + } + + Result GetPatchDeltaHistory(PatchDeltaHistory *out, s32 index) { + /* Get the pin. */ + ReadableStructPin<PatchDeltaHistory> pin; + R_TRY(this->GetPatchDeltaHistory(std::addressof(pin), index)); + + /* Copy it out. */ + *out = *pin; + R_SUCCEED(); + } + + Result GetPatchDeltaHeader(PatchDeltaHeader *out, s32 index) { + /* Get the pin. */ + ReadableStructPin<PatchDeltaHeader> pin; + R_TRY(this->GetPatchDeltaHeader(std::addressof(pin), index)); + + /* Copy it out. */ + *out = *pin; + R_SUCCEED(); + } + + Result GetFragmentSet(FragmentSet *out, s32 delta_index, s32 fragment_set_index) { + /* Get the pin. */ + ReadableStructPin<FragmentSet> pin; + R_TRY(this->GetFragmentSet(std::addressof(pin), delta_index, fragment_set_index)); + + /* Copy it out. */ + *out = *pin; + R_SUCCEED(); + } + + Result GetPatchHistoryContentInfo(ContentInfo *out, s32 history_index, s32 content_index) { + /* Get the header. */ + ReadableStructPin<ContentInfo> pin; + R_TRY(this->GetPatchHistoryContentInfo(std::addressof(pin), history_index, content_index)); + + /* Copy it out. */ + *out = *pin; + R_SUCCEED(); + } + + Result GetPatchDeltaContentInfo(PackagedContentInfo *out, s32 delta_index, s32 content_index) { + /* Get the header. */ + ReadableStructPin<PackagedContentInfo> pin; + R_TRY(this->GetPatchDeltaContentInfo(std::addressof(pin), delta_index, content_index)); + + /* Copy it out. */ + *out = *pin; + R_SUCCEED(); + } + + Result GetFragmentIndicator(FragmentIndicator *out, s32 delta_index, s32 fragment_set_index, s32 index) { + /* Get the header. */ + ReadableStructPin<FragmentIndicator> pin; + R_TRY(this->GetFragmentIndicator(std::addressof(pin), delta_index, fragment_set_index, index)); + + /* Copy it out. */ + *out = *pin; + R_SUCCEED(); + } + + Result FindFragmentIndicator(FragmentIndicator *out, s32 delta_index, s32 fragment_set_index, s32 fragment_index) { + /* Get the header. */ + ReadableStructPin<FragmentIndicator> pin; + R_TRY(this->FindFragmentIndicator(std::addressof(pin), delta_index, fragment_set_index, fragment_index)); + + /* Copy it out. */ + *out = *pin; + R_SUCCEED(); + } + + Result CountHistoryContentInfo(s32 *out, s32 index) { + R_RETURN(this->CountImpl(out, index, m_cached_history_content_count, [&](s32 *out, s32 i) -> Result { + /* Get the history header. */ + ReadableStructPin<ncm::PatchHistoryHeader> header; + R_TRY(this->GetHistoryHeader(std::addressof(header), i)); + + /* Set the content count. */ + *out = header->content_count; + R_SUCCEED(); + })); + } + + Result CountDeltaContentInfo(s32 *out, s32 index) { + R_RETURN(this->CountImpl(out, index, m_cached_delta_content_count, [&](s32 *out, s32 i) -> Result { + /* Get the history header. */ + ReadableStructPin<ncm::PatchDeltaHeader> header; + R_TRY(this->GetPatchDeltaHeader(std::addressof(header), i)); + + /* Set the content count. */ + *out = header->content_count; + R_SUCCEED(); + })); + } + + Result CountFragmentSet(s32 *out, s32 index) { + R_RETURN(this->CountImpl(out, index, m_cached_fragment_set_count, [&](s32 *out, s32 i) -> Result { + /* Get the history header. */ + ReadableStructPin<ncm::PatchDeltaHeader> header; + R_TRY(this->GetPatchDeltaHeader(std::addressof(header), i)); + + /* Set the fragment set count. */ + *out = header->delta.fragment_set_count; + R_SUCCEED(); + })); + } + + Result CountFragmentIndicator(s32 *out, s32 index) { + R_RETURN(this->CountImpl(out, index, m_cached_fragment_indicator_count, [&](s32 *out, s32 i) -> Result { + /* Get the history header. */ + ReadableStructPin<ncm::FragmentSet> set; + R_TRY(this->GetFragmentSetDirectly(std::addressof(set), i)); + + /* Set the indicator count. */ + *out = set->fragment_count; + R_SUCCEED(); + })); + } + private: + Result CountImpl(s32 *out, s32 index, util::optional<CachedCount> &cache, auto get_count_impl) const { + /* Ensure the value is cached. */ + if (!(cache.has_value() && cache->index == index)) { + /* Determine the count. */ + CachedCount calc = { .index = index, .count = 0 }; + for (auto i = 0; i < index; ++i) { + s32 cur_count = 0; + R_TRY(get_count_impl(std::addressof(cur_count), i)); + + calc.count += cur_count; + } + + /* Cache the count. */ + cache = calc; + } + + /* Set the output count. */ + *out = cache->count; + R_SUCCEED(); + } + private: + Result EnsureHeader() { + /* If we have our header, we're good. */ + R_SUCCEED_IF(m_header.has_value()); + + /* Get our header. */ + PatchMetaExtendedDataHeader header{}; + R_TRY(this->GetHeader(std::addressof(header))); + + /* Set our header. */ + m_header.emplace(header); + R_SUCCEED(); + } + + + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_id.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_id.hpp new file mode 100644 index 00000000..35b2d351 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_id.hpp @@ -0,0 +1,101 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_data_id.hpp> +#include <stratosphere/ncm/ncm_program_id.hpp> + +namespace ams::ncm { + + struct ApplicationId { + u64 value; + + constexpr operator ProgramId() const { + return { this->value }; + } + + constexpr inline bool operator==(const ApplicationId &) const = default; + constexpr inline bool operator!=(const ApplicationId &) const = default; + + static const ApplicationId Start; + static const ApplicationId End; + }; + + constexpr inline const ApplicationId InvalidApplicationId = {}; + + inline constexpr const ApplicationId ApplicationId::Start = { 0x0100000000010000ul }; + inline constexpr const ApplicationId ApplicationId::End = { 0x01FFFFFFFFFFFFFFul }; + + inline constexpr bool IsApplicationId(const ProgramId &program_id) { + return ApplicationId::Start <= program_id && program_id <= ApplicationId::End; + } + + inline constexpr bool IsApplicationId(const ApplicationId &) { + return true; + } + + struct ApplicationGroupId { + u64 value; + }; + + struct PatchId { + u64 value; + + constexpr operator ProgramId() const { + return { this->value }; + } + + constexpr inline bool operator==(const PatchId &) const = default; + constexpr inline bool operator!=(const PatchId &) const = default; + }; + + struct PatchGroupId { + u64 value; + }; + + struct AddOnContentId { + u64 value; + + constexpr operator DataId() const { + return { this->value }; + } + + constexpr inline bool operator==(const AddOnContentId &) const = default; + constexpr inline bool operator!=(const AddOnContentId &) const = default; + }; + + struct DeltaId { + u64 value; + + constexpr operator ProgramId() const { + return { this->value }; + } + + constexpr inline bool operator==(const DeltaId &) const = default; + constexpr inline bool operator!=(const DeltaId &) const = default; + }; + + struct DataPatchId { + u64 value; + + constexpr operator DataId() const { + return { this->value }; + } + + constexpr inline bool operator==(const DataPatchId &) const = default; + constexpr inline bool operator!=(const DataPatchId &) const = default; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_key.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_key.hpp new file mode 100644 index 00000000..56eb12ce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_key.hpp @@ -0,0 +1,114 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/ncm/ncm_content_meta_id.hpp> +#include <stratosphere/ncm/ncm_content_meta_type.hpp> +#include <stratosphere/ncm/ncm_system_content_meta_id.hpp> + +namespace ams::ncm { + + enum class ContentInstallType : u8 { + Full = 0, + FragmentOnly = 1, + Unknown = 7, + }; + + struct ContentMetaKey { + u64 id; + u32 version; + ContentMetaType type; + ContentInstallType install_type; + u8 padding[2]; + + bool operator<(const ContentMetaKey &rhs) const { + return std::tie(this->id, this->version, this->type, this->install_type) < std::tie(rhs.id, rhs.version, rhs.type, rhs.install_type); + } + + constexpr bool operator==(const ContentMetaKey &rhs) const { + return std::tie(this->id, this->version, this->type, this->install_type) == std::tie(rhs.id, rhs.version, rhs.type, rhs.install_type); + } + + constexpr bool operator!=(const ContentMetaKey &rhs) const { + return !(*this == rhs); + } + + static constexpr ContentMetaKey MakeUnknownType(u64 id, u32 version) { + return { .id = id, .version = version, .type = ContentMetaType::Unknown }; + } + + static constexpr ContentMetaKey Make(u64 id, u32 version, ContentMetaType type) { + return { .id = id, .version = version, .type = type }; + } + + static constexpr ContentMetaKey Make(u64 id, u32 version, ContentMetaType type, ContentInstallType install_type) { + return { .id = id, .version = version, .type = type, .install_type = install_type }; + } + + static constexpr ContentMetaKey Make(SystemProgramId id, u32 version) { + return { .id = id.value, .version = version, .type = ContentMetaType::SystemProgram }; + } + + static constexpr ContentMetaKey Make(SystemDataId id, u32 version) { + return { .id = id.value, .version = version, .type = ContentMetaType::SystemData }; + } + + static constexpr ContentMetaKey Make(SystemUpdateId id, u32 version) { + return { .id = id.value, .version = version, .type = ContentMetaType::SystemUpdate }; + } + + static constexpr ContentMetaKey Make(ApplicationId id, u32 version) { + return { .id = id.value, .version = version, .type = ContentMetaType::Application }; + } + + static constexpr ContentMetaKey Make(PatchId id, u32 version) { + return { .id = id.value, .version = version, .type = ContentMetaType::Patch }; + } + + static constexpr ContentMetaKey Make(PatchId id, u32 version, ContentInstallType install_type) { + return { .id = id.value, .version = version, .type = ContentMetaType::Patch, .install_type = install_type }; + } + + static constexpr ContentMetaKey Make(DeltaId id, u32 version) { + return { .id = id.value, .version = version, .type = ContentMetaType::Delta }; + } + }; + + static_assert(sizeof(ContentMetaKey) == 0x10); + + struct ApplicationContentMetaKey { + ContentMetaKey key; + ncm::ApplicationId application_id; + }; + + static_assert(sizeof(ApplicationContentMetaKey) == 0x18); + + struct StorageContentMetaKey { + ContentMetaKey key; + StorageId storage_id; + u8 reserved[7]; + + constexpr bool operator==(StorageContentMetaKey &rhs) const { + return this->key == rhs.key && this->storage_id == rhs.storage_id; + } + + constexpr bool operator<(StorageContentMetaKey &rhs) const { + return this->key == rhs.key ? this->storage_id < rhs.storage_id : this->key < rhs.key; + } + }; + static_assert(sizeof(StorageContentMetaKey) == 0x18); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_platform.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_platform.hpp new file mode 100644 index 00000000..2a08d841 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_platform.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::ncm { + + enum class ContentMetaPlatform : u8 { + Nx = 0x0, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_type.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_type.hpp new file mode 100644 index 00000000..1ae57033 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_type.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::ncm { + + enum class ContentMetaType : u8 { + Unknown = 0x0, + SystemProgram = 0x1, + SystemData = 0x2, + SystemUpdate = 0x3, + BootImagePackage = 0x4, + BootImagePackageSafe = 0x5, + Application = 0x80, + Patch = 0x81, + AddOnContent = 0x82, + Delta = 0x83, + DataPatch = 0x84, + }; + + const char *GetContentMetaTypeString(ContentMetaType type); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_utils.hpp new file mode 100644 index 00000000..16b15dd1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_utils.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_auto_buffer.hpp> +#include <stratosphere/ncm/ncm_storage_id.hpp> +#include <stratosphere/ncm/ncm_content_storage.hpp> +#include <stratosphere/ncm/ncm_content_meta_key.hpp> +#include <stratosphere/ncm/ncm_content_meta_database.hpp> +#include <stratosphere/ncm/ncm_firmware_variation.hpp> + +namespace ams::ncm { + + using MountContentMetaFunction = Result (*)(const char *mount_name, const char *path, fs::ContentAttributes attr); + + bool IsContentMetaFileName(const char *name); + + Result ReadContentMetaPathAlongWithExtendedDataAndDigest(AutoBuffer *out, const char *path, fs::ContentAttributes attr); + Result ReadContentMetaPathAlongWithExtendedDataAndDigestSuppressingFsAbort(AutoBuffer *out, const char *path, fs::ContentAttributes attr); + + Result ReadContentMetaPathWithoutExtendedDataOrDigest(AutoBuffer *out, const char *path, fs::ContentAttributes attr); + Result ReadContentMetaPathWithoutExtendedDataOrDigestSuppressingFsAbort(AutoBuffer *out, const char *path, fs::ContentAttributes attr); + + using ReadContentMetaPathFunction = Result (*)(AutoBuffer *out, const char *path, fs::ContentAttributes attr); + + Result TryReadContentMetaPath(fs::ContentAttributes *out_attr, AutoBuffer *out, const char *path, ReadContentMetaPathFunction func); + Result TryReadContentMetaPath(AutoBuffer *out, const char *path, ReadContentMetaPathFunction func); + + Result ReadVariationContentMetaInfoList(s32 *out_count, std::unique_ptr<ContentMetaInfo[]> *out_meta_infos, const Path &path, fs::ContentAttributes attr, FirmwareVariationId firmware_variation_id); + + void SetMountContentMetaFunction(MountContentMetaFunction func); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_storage.hpp new file mode 100644 index 00000000..64512681 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_storage.hpp @@ -0,0 +1,217 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_i_content_storage.hpp> + +namespace ams::ncm { + + class ContentStorage { + NON_COPYABLE(ContentStorage); + private: + sf::SharedPointer<IContentStorage> m_interface; + public: + ContentStorage() : m_interface(nullptr) { /* ... */ } + explicit ContentStorage(sf::SharedPointer<IContentStorage> intf) : m_interface(intf) { /* ... */ } + + ContentStorage(ContentStorage &&rhs) { + m_interface = std::move(rhs.m_interface); + } + + ContentStorage &operator=(ContentStorage &&rhs) { + ContentStorage(std::move(rhs)).swap(*this); + return *this; + } + + void swap(ContentStorage &rhs) { + std::swap(m_interface, rhs.m_interface); + } + public: + PlaceHolderId GeneratePlaceHolderId() { + AMS_ASSERT(m_interface != nullptr); + + PlaceHolderId id; + R_ABORT_UNLESS(m_interface->GeneratePlaceHolderId(std::addressof(id))); + return id; + } + + Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->CreatePlaceHolder(placeholder_id, content_id, size)); + } + + Result DeletePlaceHolder(PlaceHolderId placeholder_id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->DeletePlaceHolder(placeholder_id)); + } + + Result HasPlaceHolder(bool *out, PlaceHolderId placeholder_id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->HasPlaceHolder(out, placeholder_id)); + } + + Result WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, const void *buf, size_t size) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->WritePlaceHolder(placeholder_id, offset, sf::InBuffer(buf, size))); + } + + Result Register(PlaceHolderId placeholder_id, ContentId content_id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->Register(placeholder_id, content_id)); + } + + Result Delete(ContentId content_id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->Delete(content_id)); + } + + Result Has(bool *out, ContentId content_id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->Has(out, content_id)); + } + + void GetPath(Path *out, ContentId content_id) { + AMS_ASSERT(m_interface != nullptr); + R_ABORT_UNLESS(m_interface->GetPath(out, content_id)); + } + + void GetPlaceHolderPath(Path *out, PlaceHolderId placeholder_id) { + AMS_ASSERT(m_interface != nullptr); + R_ABORT_UNLESS(m_interface->GetPlaceHolderPath(out, placeholder_id)); + } + + Result CleanupAllPlaceHolder() { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->CleanupAllPlaceHolder()); + } + + Result ListPlaceHolder(s32 *out_count, PlaceHolderId *out_list, size_t out_list_size) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->ListPlaceHolder(out_count, sf::OutArray<PlaceHolderId>(out_list, out_list_size))); + } + + Result GetContentCount(s32 *out_count) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->GetContentCount(out_count)); + } + + Result ListContentId(s32 *out_count, ContentId *out_list, size_t out_list_size, s32 offset) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->ListContentId(out_count, sf::OutArray<ContentId>(out_list, out_list_size), offset)); + } + + Result GetSize(s64 *out_size, ContentId content_id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->GetSizeFromContentId(out_size, content_id)); + } + + Result GetSize(s64 *out_size, PlaceHolderId placeholder_id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->GetSizeFromPlaceHolderId(out_size, placeholder_id)); + } + + Result DisableForcibly() { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->DisableForcibly()); + } + + Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->RevertToPlaceHolder(placeholder_id, old_content_id, new_content_id)); + } + + Result SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->SetPlaceHolderSize(placeholder_id, size)); + } + + Result ReadContentIdFile(void *dst, size_t size, ContentId content_id, s64 offset) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->ReadContentIdFile(sf::OutBuffer(dst, size), content_id, offset)); + } + + Result GetRightsId(ncm::RightsId *out_rights_id, PlaceHolderId placeholder_id, fs::ContentAttributes attr) { + AMS_ASSERT(m_interface != nullptr); + + const auto vers = hos::GetVersion(); + if (vers >= hos::Version_16_0_0) { + R_RETURN(m_interface->GetRightsIdFromPlaceHolderId(out_rights_id, placeholder_id, attr)); + } else if (vers >= hos::Version_3_0_0) { + R_RETURN(m_interface->GetRightsIdFromPlaceHolderIdDeprecated2(out_rights_id, placeholder_id)); + } else { + AMS_ABORT_UNLESS(vers >= hos::Version_2_0_0); + *out_rights_id = {}; + R_RETURN(m_interface->GetRightsIdFromPlaceHolderIdDeprecated(std::addressof(out_rights_id->id), placeholder_id)); + } + } + + Result GetRightsId(ncm::RightsId *out_rights_id, ContentId content_id, fs::ContentAttributes attr) { + AMS_ASSERT(m_interface != nullptr); + + const auto vers = hos::GetVersion(); + if (vers >= hos::Version_16_0_0) { + R_RETURN(m_interface->GetRightsIdFromContentId(out_rights_id, content_id, attr)); + } else if (vers >= hos::Version_3_0_0) { + R_RETURN(m_interface->GetRightsIdFromContentIdDeprecated2(out_rights_id, content_id)); + } else { + AMS_ABORT_UNLESS(vers >= hos::Version_2_0_0); + *out_rights_id = {}; + R_RETURN(m_interface->GetRightsIdFromContentIdDeprecated(std::addressof(out_rights_id->id), content_id)); + } + } + + Result WriteContentForDebug(ContentId content_id, s64 offset, const void *buf, size_t size) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->WriteContentForDebug(content_id, offset, sf::InBuffer(buf, size))); + } + + Result GetFreeSpaceSize(s64 *out_size) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->GetFreeSpaceSize(out_size)); + } + + Result GetTotalSpaceSize(s64 *out_size) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->GetTotalSpaceSize(out_size)); + } + + Result FlushPlaceHolder() { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->FlushPlaceHolder()); + } + + Result RepairInvalidFileAttribute() { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->RepairInvalidFileAttribute()); + } + + Result GetRightsIdFromPlaceHolderIdWithCache(ncm::RightsId *out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id, fs::ContentAttributes attr) { + AMS_ASSERT(m_interface != nullptr); + + const auto vers = hos::GetVersion(); + if (vers >= hos::Version_16_0_0) { + R_RETURN(m_interface->GetRightsIdFromPlaceHolderIdWithCache(out_rights_id, placeholder_id, cache_content_id, attr)); + } else { + R_RETURN(m_interface->GetRightsIdFromPlaceHolderIdWithCacheDeprecated(out_rights_id, cache_content_id, placeholder_id)); + } + } + + Result GetProgramId(ncm::ProgramId *out, ContentId content_id, fs::ContentAttributes attr) { + AMS_ASSERT(m_interface != nullptr); + R_RETURN(m_interface->GetProgramId(out, content_id, attr)); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_type.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_type.hpp new file mode 100644 index 00000000..34f9788f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_type.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::ncm { + + enum class ContentType : u8 { + Meta = 0, + Program = 1, + Data = 2, + Control = 3, + HtmlDocument = 4, + LegalInformation = 5, + DeltaFragment = 6, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_data_id.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_data_id.hpp new file mode 100644 index 00000000..bb9422e1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_data_id.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::ncm { + + struct DataId { + u64 value; + + static const DataId Invalid; + + constexpr inline auto operator<=>(const DataId &) const = default; + }; + + inline constexpr const DataId DataId::Invalid = {}; + inline constexpr const DataId InvalidDataId = DataId::Invalid; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_firmware_variation.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_firmware_variation.hpp new file mode 100644 index 00000000..06f9b0cb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_firmware_variation.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::ncm { + + struct FirmwareVariationInfo { + bool refer_to_base; + u8 _0x1[3]; + u32 content_meta_count; + u8 reserved[0x18]; + }; + + struct FirmwareVariationId { + u32 value; + }; + + constexpr inline bool operator==(const FirmwareVariationId &lhs, const FirmwareVariationId &rhs) { + return lhs.value == rhs.value; + } + + constexpr inline bool operator!=(const FirmwareVariationId &lhs, const FirmwareVariationId &rhs) { + return lhs.value != rhs.value; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_i_content_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_i_content_manager.hpp new file mode 100644 index 00000000..c974689b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_i_content_manager.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_i_content_storage.hpp> +#include <stratosphere/ncm/ncm_i_content_meta_database.hpp> +#include <stratosphere/ncm/ncm_memory_report.hpp> +#include <stratosphere/fs/fs_content_storage_id.hpp> + +#define AMS_NCM_I_CONTENT_MANAGER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, CreateContentStorage, (ncm::StorageId storage_id), (storage_id)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, CreateContentMetaDatabase, (ncm::StorageId storage_id), (storage_id)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, VerifyContentStorage, (ncm::StorageId storage_id), (storage_id)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, VerifyContentMetaDatabase, (ncm::StorageId storage_id), (storage_id)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, OpenContentStorage, (sf::Out<sf::SharedPointer<ncm::IContentStorage>> out, ncm::StorageId storage_id), (out, storage_id)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, OpenContentMetaDatabase, (sf::Out<sf::SharedPointer<ncm::IContentMetaDatabase>> out, ncm::StorageId storage_id), (out, storage_id)) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, CloseContentStorageForcibly, (ncm::StorageId storage_id), (storage_id), hos::Version_1_0_0, hos::Version_1_0_0) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, CloseContentMetaDatabaseForcibly, (ncm::StorageId storage_id), (storage_id), hos::Version_1_0_0, hos::Version_1_0_0) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, CleanupContentMetaDatabase, (ncm::StorageId storage_id), (storage_id)) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, ActivateContentStorage, (ncm::StorageId storage_id), (storage_id), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, InactivateContentStorage, (ncm::StorageId storage_id), (storage_id), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 11, Result, ActivateContentMetaDatabase, (ncm::StorageId storage_id), (storage_id), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, InactivateContentMetaDatabase, (ncm::StorageId storage_id), (storage_id), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 13, Result, InvalidateRightsIdCache, (), (), hos::Version_9_0_0) \ + AMS_SF_METHOD_INFO(C, H, 14, Result, GetMemoryReport, (sf::Out<ncm::MemoryReport> out), (out), hos::Version_10_0_0) \ + AMS_SF_METHOD_INFO(C, H, 15, Result, ActivateFsContentStorage, (fs::ContentStorageId fs_storage_id), (fs_storage_id)) /* Technically min 16.0.0, but used. */ + +AMS_SF_DEFINE_INTERFACE(ams::ncm, IContentManager, AMS_NCM_I_CONTENT_MANAGER_INTERFACE_INFO, 0xFDB4FFE1); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_i_content_meta_database.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_i_content_meta_database.hpp new file mode 100644 index 00000000..39c04cd0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_i_content_meta_database.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/sf.hpp> +#include <stratosphere/ncm/ncm_content_meta.hpp> + +#define AMS_NCM_I_CONTENT_META_DATABASE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, Set, (const ncm::ContentMetaKey &key, const sf::InBuffer &value), (key, value)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, Get, (sf::Out<u64> out_size, const ncm::ContentMetaKey &key, const sf::OutBuffer &out_value), (out_size, key, out_value)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, Remove, (const ncm::ContentMetaKey &key), (key)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, GetContentIdByType, (sf::Out<ncm::ContentId> out_content_id, const ncm::ContentMetaKey &key, ncm::ContentType type), (out_content_id, key, type)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, ListContentInfo, (sf::Out<s32> out_entries_written, const sf::OutArray<ncm::ContentInfo> &out_info, const ncm::ContentMetaKey &key, s32 offset), (out_entries_written, out_info, key, offset)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, List, (sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ncm::ContentMetaKey> &out_info, ncm::ContentMetaType meta_type, ncm::ApplicationId application_id, u64 min, u64 max, ncm::ContentInstallType install_type), (out_entries_total, out_entries_written, out_info, meta_type, application_id, min, max, install_type)) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, GetLatestContentMetaKey, (sf::Out<ncm::ContentMetaKey> out_key, u64 id), (out_key, id)) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, ListApplication, (sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ncm::ApplicationContentMetaKey> &out_keys, ncm::ContentMetaType meta_type), (out_entries_total, out_entries_written, out_keys, meta_type)) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, Has, (sf::Out<bool> out, const ncm::ContentMetaKey &key), (out, key)) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, HasAll, (sf::Out<bool> out, const sf::InArray<ncm::ContentMetaKey> &keys), (out, keys)) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, GetSize, (sf::Out<u64> out_size, const ncm::ContentMetaKey &key), (out_size, key)) \ + AMS_SF_METHOD_INFO(C, H, 11, Result, GetRequiredSystemVersion, (sf::Out<u32> out_version, const ncm::ContentMetaKey &key), (out_version, key)) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, GetPatchContentMetaId, (sf::Out<u64> out_patch_id, const ncm::ContentMetaKey &key), (out_patch_id, key)) \ + AMS_SF_METHOD_INFO(C, H, 13, Result, DisableForcibly, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 14, Result, LookupOrphanContent, (const sf::OutArray<bool> &out_orphaned, const sf::InArray<ncm::ContentId> &content_ids), (out_orphaned, content_ids)) \ + AMS_SF_METHOD_INFO(C, H, 15, Result, Commit, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 16, Result, HasContent, (sf::Out<bool> out, const ncm::ContentMetaKey &key, const ncm::ContentId &content_id), (out, key, content_id)) \ + AMS_SF_METHOD_INFO(C, H, 17, Result, ListContentMetaInfo, (sf::Out<s32> out_entries_written, const sf::OutArray<ncm::ContentMetaInfo> &out_meta_info, const ncm::ContentMetaKey &key, s32 offset), (out_entries_written, out_meta_info, key, offset)) \ + AMS_SF_METHOD_INFO(C, H, 18, Result, GetAttributes, (sf::Out<u8> out_attributes, const ncm::ContentMetaKey &key), (out_attributes, key)) \ + AMS_SF_METHOD_INFO(C, H, 19, Result, GetRequiredApplicationVersion, (sf::Out<u32> out_version, const ncm::ContentMetaKey &key), (out_version, key), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 20, Result, GetContentIdByTypeAndIdOffset, (sf::Out<ncm::ContentId> out_content_id, const ncm::ContentMetaKey &key, ncm::ContentType type, u8 id_offset), (out_content_id, key, type, id_offset), hos::Version_5_0_0) \ + AMS_SF_METHOD_INFO(C, H, 21, Result, GetCount, (sf::Out<u32> out_count), (out_count), hos::Version_10_0_0) \ + AMS_SF_METHOD_INFO(C, H, 22, Result, GetOwnerApplicationId, (sf::Out<ncm::ApplicationId> out_id, const ncm::ContentMetaKey &key), (out_id, key), hos::Version_10_0_0) \ + AMS_SF_METHOD_INFO(C, H, 23, Result, GetContentAccessibilities, (sf::Out<u8> out_accessibilities, const ncm::ContentMetaKey &key), (out_accessibilities, key), hos::Version_15_0_0) \ + AMS_SF_METHOD_INFO(C, H, 24, Result, GetContentInfoByType, (sf::Out<ncm::ContentInfo> out_content_info, const ncm::ContentMetaKey &key, ncm::ContentType type), (out_content_info, key, type), hos::Version_15_0_0) \ + AMS_SF_METHOD_INFO(C, H, 25, Result, GetContentInfoByTypeAndIdOffset, (sf::Out<ncm::ContentInfo> out_content_info, const ncm::ContentMetaKey &key, ncm::ContentType type, u8 id_offset), (out_content_info, key, type, id_offset), hos::Version_15_0_0) \ + AMS_SF_METHOD_INFO(C, H, 26, Result, GetPlatform, (sf::Out<ncm::ContentMetaPlatform> out, const ncm::ContentMetaKey &key), (out, key), hos::Version_17_0_0) \ + AMS_SF_METHOD_INFO(C, H, 27, Result, HasAttributes, (sf::Out<u8> out, u8 attr_mask), (out, attr_mask), hos::Version_20_0_0) + + +AMS_SF_DEFINE_INTERFACE(ams::ncm, IContentMetaDatabase, AMS_NCM_I_CONTENT_META_DATABASE_INTERFACE_INFO, 0x58021FEC) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_i_content_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_i_content_storage.hpp new file mode 100644 index 00000000..3ea0b73f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_i_content_storage.hpp @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/sf.hpp> +#include <stratosphere/ncm/ncm_content_id.hpp> +#include <stratosphere/ncm/ncm_placeholder_id.hpp> +#include <stratosphere/ncm/ncm_path.hpp> +#include <stratosphere/ncm/ncm_rights_id.hpp> + +#define AMS_NCM_I_CONTENT_STORAGE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GeneratePlaceHolderId, (sf::Out<ncm::PlaceHolderId> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, CreatePlaceHolder_AtmosphereAlignmentFix, (ncm::ContentId content_id, ncm::PlaceHolderId placeholder_id, s64 size), (content_id, placeholder_id, size), hos::Version_Min, hos::Version_15_0_1) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, CreatePlaceHolder, (ncm::PlaceHolderId placeholder_id, ncm::ContentId content_id, s64 size), (placeholder_id, content_id, size), hos::Version_16_0_0) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, DeletePlaceHolder, (ncm::PlaceHolderId placeholder_id), (placeholder_id)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, HasPlaceHolder, (sf::Out<bool> out, ncm::PlaceHolderId placeholder_id), (out, placeholder_id)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, WritePlaceHolder, (ncm::PlaceHolderId placeholder_id, s64 offset, const sf::InBuffer &data), (placeholder_id, offset, data)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, Register_AtmosphereAlignmentFix, (ncm::ContentId content_id, ncm::PlaceHolderId placeholder_id), (content_id, placeholder_id), hos::Version_Min, hos::Version_15_0_1) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, Register, (ncm::PlaceHolderId placeholder_id, ncm::ContentId content_id), (placeholder_id, content_id), hos::Version_16_0_0) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, Delete, (ncm::ContentId content_id), (content_id)) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, Has, (sf::Out<bool> out, ncm::ContentId content_id), (out, content_id)) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, GetPath, (sf::Out<ncm::Path> out, ncm::ContentId content_id), (out, content_id)) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, GetPlaceHolderPath, (sf::Out<ncm::Path> out, ncm::PlaceHolderId placeholder_id), (out, placeholder_id)) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, CleanupAllPlaceHolder, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 11, Result, ListPlaceHolder, (sf::Out<s32> out_count, const sf::OutArray<ncm::PlaceHolderId> &out_buf), (out_count, out_buf)) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, GetContentCount, (sf::Out<s32> out_count), (out_count)) \ + AMS_SF_METHOD_INFO(C, H, 13, Result, ListContentId, (sf::Out<s32> out_count, const sf::OutArray<ncm::ContentId> &out_buf, s32 start_offset), (out_count, out_buf, start_offset)) \ + AMS_SF_METHOD_INFO(C, H, 14, Result, GetSizeFromContentId, (sf::Out<s64> out_size, ncm::ContentId content_id), (out_size, content_id)) \ + AMS_SF_METHOD_INFO(C, H, 15, Result, DisableForcibly, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 16, Result, RevertToPlaceHolder_AtmosphereAlignmentFix, (ncm::ContentId old_content_id, ncm::ContentId new_content_id, ncm::PlaceHolderId placeholder_id), (old_content_id, new_content_id, placeholder_id), hos::Version_2_0_0, hos::Version_15_0_1) \ + AMS_SF_METHOD_INFO(C, H, 16, Result, RevertToPlaceHolder, (ncm::PlaceHolderId placeholder_id, ncm::ContentId old_content_id, ncm::ContentId new_content_id), (placeholder_id, old_content_id, new_content_id), hos::Version_16_0_0) \ + AMS_SF_METHOD_INFO(C, H, 17, Result, SetPlaceHolderSize, (ncm::PlaceHolderId placeholder_id, s64 size), (placeholder_id, size), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 18, Result, ReadContentIdFile, (const sf::OutBuffer &buf, ncm::ContentId content_id, s64 offset), (buf, content_id, offset), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 19, Result, GetRightsIdFromPlaceHolderIdDeprecated, (sf::Out<ams::fs::RightsId> out_rights_id, ncm::PlaceHolderId placeholder_id), (out_rights_id, placeholder_id), hos::Version_2_0_0, hos::Version_2_3_0) \ + AMS_SF_METHOD_INFO(C, H, 19, Result, GetRightsIdFromPlaceHolderIdDeprecated2, (sf::Out<ncm::RightsId> out_rights_id, ncm::PlaceHolderId placeholder_id), (out_rights_id, placeholder_id), hos::Version_3_0_0, hos::Version_15_0_1) \ + AMS_SF_METHOD_INFO(C, H, 19, Result, GetRightsIdFromPlaceHolderId, (sf::Out<ncm::RightsId> out_rights_id, ncm::PlaceHolderId placeholder_id, fs::ContentAttributes attr), (out_rights_id, placeholder_id, attr), hos::Version_16_0_0) \ + AMS_SF_METHOD_INFO(C, H, 20, Result, GetRightsIdFromContentIdDeprecated, (sf::Out<ams::fs::RightsId> out_rights_id, ncm::ContentId content_id), (out_rights_id, content_id), hos::Version_2_0_0, hos::Version_2_3_0) \ + AMS_SF_METHOD_INFO(C, H, 20, Result, GetRightsIdFromContentIdDeprecated2, (sf::Out<ncm::RightsId> out_rights_id, ncm::ContentId content_id), (out_rights_id, content_id), hos::Version_3_0_0, hos::Version_15_0_1) \ + AMS_SF_METHOD_INFO(C, H, 20, Result, GetRightsIdFromContentId, (sf::Out<ncm::RightsId> out_rights_id, ncm::ContentId content_id, fs::ContentAttributes attr), (out_rights_id, content_id, attr), hos::Version_16_0_0) \ + AMS_SF_METHOD_INFO(C, H, 21, Result, WriteContentForDebug, (ncm::ContentId content_id, s64 offset, const sf::InBuffer &data), (content_id, offset, data), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 22, Result, GetFreeSpaceSize, (sf::Out<s64> out_size), (out_size), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 23, Result, GetTotalSpaceSize, (sf::Out<s64> out_size), (out_size), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 24, Result, FlushPlaceHolder, (), (), hos::Version_3_0_0) \ + AMS_SF_METHOD_INFO(C, H, 25, Result, GetSizeFromPlaceHolderId, (sf::Out<s64> out, ncm::PlaceHolderId placeholder_id), (out, placeholder_id), hos::Version_4_0_0) \ + AMS_SF_METHOD_INFO(C, H, 26, Result, RepairInvalidFileAttribute, (), (), hos::Version_4_0_0) \ + AMS_SF_METHOD_INFO(C, H, 27, Result, GetRightsIdFromPlaceHolderIdWithCacheDeprecated, (sf::Out<ncm::RightsId> out_rights_id, ncm::ContentId cache_content_id, ncm::PlaceHolderId placeholder_id), (out_rights_id, cache_content_id, placeholder_id), hos::Version_8_0_0, hos::Version_15_0_1) \ + AMS_SF_METHOD_INFO(C, H, 27, Result, GetRightsIdFromPlaceHolderIdWithCache, (sf::Out<ncm::RightsId> out_rights_id, ncm::PlaceHolderId placeholder_id, ncm::ContentId cache_content_id, fs::ContentAttributes attr), (out_rights_id, placeholder_id, cache_content_id, attr), hos::Version_16_0_0) \ + AMS_SF_METHOD_INFO(C, H, 28, Result, RegisterPath, (const ncm::ContentId &content_id, const ncm::Path &path), (content_id, path), hos::Version_13_0_0) \ + AMS_SF_METHOD_INFO(C, H, 29, Result, ClearRegisteredPath, (), (), hos::Version_13_0_0) \ + AMS_SF_METHOD_INFO(C, H, 30, Result, GetProgramId, (sf::Out<ncm::ProgramId> out, ncm::ContentId content_id, fs::ContentAttributes attr), (out, content_id, attr), hos::Version_17_0_0) + +AMS_SF_DEFINE_INTERFACE(ams::ncm, IContentStorage, AMS_NCM_I_CONTENT_STORAGE_INTERFACE_INFO, 0xFEAE3DD1) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_ids.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_ids.hpp new file mode 100644 index 00000000..d654ce53 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_ids.hpp @@ -0,0 +1,20 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_data_id.hpp> +#include <stratosphere/ncm/ncm_program_id.hpp> +#include <stratosphere/ncm/ncm_content_meta_id.hpp> +#include <stratosphere/ncm/ncm_system_content_meta_id.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_progress.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_progress.hpp new file mode 100644 index 00000000..2892d5d5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_progress.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +namespace ams::ncm { + + enum class InstallProgressState : u8 { + NotPrepared = 0, + DataPrepared = 1, + Prepared = 2, + Downloaded = 3, + Committed = 4, + Fatal = 5, + }; + + struct InstallProgress { + InstallProgressState state; + u8 pad[3]; + util::TypedStorage<Result> last_result; + s64 installed_size; + s64 total_size; + + Result GetLastResult() const { + return util::GetReference(last_result); + } + + void SetLastResult(Result result) { + *util::GetPointer(last_result) = result; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_base.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_base.hpp new file mode 100644 index 00000000..24b71eae --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_base.hpp @@ -0,0 +1,203 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_install_task_data.hpp> +#include <stratosphere/ncm/ncm_install_task_occupied_size.hpp> + +namespace ams::ncm { + + enum class ListContentMetaKeyFilter : u8 { + All = 0, + Committed = 1, + NotCommitted = 2, + }; + + enum InstallConfig { + InstallConfig_None = (0 << 0), + InstallConfig_SystemUpdate = (1 << 2), + InstallConfig_RequiresExFatDriver = (1 << 3), + InstallConfig_IgnoreTicket = (1 << 4), + }; + + struct InstallThroughput { + s64 installed; + TimeSpan elapsed_time; + }; + + struct InstallContentMetaInfo { + ContentId content_id; + s64 content_size; + ContentMetaKey key; + bool verify_digest; + bool has_key; + Digest digest; + + static constexpr InstallContentMetaInfo MakeVerifiable(const ContentId &cid, s64 sz, const ContentMetaKey &ky, const Digest &d) { + return { + .content_id = cid, + .content_size = sz, + .key = ky, + .verify_digest = true, + .has_key = true, + .digest = d, + }; + } + + static constexpr InstallContentMetaInfo MakeUnverifiable(const ContentId &cid, s64 sz, const ContentMetaKey &ky) { + return { + .content_id = cid, + .content_size = sz, + .key = ky, + .verify_digest = false, + .has_key = true, + }; + } + + static constexpr InstallContentMetaInfo MakeUnverifiable(const ContentId &cid, s64 sz) { + return { + .content_id = cid, + .content_size = sz, + .verify_digest = false, + .has_key = false, + }; + } + }; + + static_assert(sizeof(InstallContentMetaInfo) == 0x50); + + class InstallTaskBase { + NON_COPYABLE(InstallTaskBase); + NON_MOVEABLE(InstallTaskBase); + private: + crypto::Sha256Generator m_sha256_generator{}; + StorageId m_install_storage{}; + InstallTaskDataBase *m_data{}; + InstallProgress m_progress{}; + os::SdkMutex m_progress_mutex{}; + u32 m_config{}; + os::SdkMutex m_cancel_mutex{}; + bool m_cancel_requested{}; + InstallThroughput m_throughput{}; + TimeSpan m_throughput_start_time{}; + os::SdkMutex m_throughput_mutex{}; + FirmwareVariationId m_firmware_variation_id{}; + private: + ALWAYS_INLINE Result SetLastResultOnFailure(Result result) { + if (R_FAILED(result)) { + this->SetLastResult(result); + } + R_RETURN(result); + } + public: + InstallTaskBase() : m_data(), m_progress(), m_progress_mutex(), m_cancel_mutex(), m_cancel_requested(), m_throughput_mutex() { /* ... */ } + virtual ~InstallTaskBase() { /* ... */ }; + public: + virtual void Cancel(); + virtual void ResetCancel(); + + Result Prepare(); + Result GetPreparedPlaceHolderPath(Path *out_path, u64 id, ContentMetaType meta_type, ContentType type); + Result CalculateRequiredSize(s64 *out_size); + Result Cleanup(); + Result ListContentMetaKey(s32 *out_keys_written, StorageContentMetaKey *out_keys, s32 out_keys_count, s32 offset, ListContentMetaKeyFilter filter); + Result ListContentMetaKey(s32 *out_keys_written, StorageContentMetaKey *out_keys, s32 out_keys_count, s32 offset) { R_RETURN(this->ListContentMetaKey(out_keys_written, out_keys, out_keys_count, offset, ListContentMetaKeyFilter::All)); } + Result ListApplicationContentMetaKey(s32 *out_keys_written, ApplicationContentMetaKey *out_keys, s32 out_keys_count, s32 offset); + Result Execute(); + Result PrepareAndExecute(); + Result Commit(const StorageContentMetaKey *keys, s32 num_keys); + Result Commit() { R_RETURN(this->Commit(nullptr, 0)); } + virtual InstallProgress GetProgress(); + void ResetLastResult(); + Result IncludesExFatDriver(bool *out); + InstallThroughput GetThroughput(); + Result CalculateContentsSize(s64 *out_size, const ContentMetaKey &key, StorageId storage_id); + Result ListOccupiedSize(s32 *out_written, InstallTaskOccupiedSize *out_list, s32 out_list_size, s32 offset); + + Result FindMaxRequiredApplicationVersion(u32 *out); + Result FindMaxRequiredSystemVersion(u32 *out); + protected: + Result Initialize(StorageId install_storage, InstallTaskDataBase *data, u32 config); + + Result PrepareContentMeta(const InstallContentMetaInfo &meta_info, util::optional<ContentMetaKey> key, util::optional<u32> source_version); + Result PrepareContentMeta(ContentId content_id, s64 size, ContentMetaType meta_type, AutoBuffer *buffer); + Result WritePlaceHolderBuffer(InstallContentInfo *content_info, const void *data, size_t data_size); + void PrepareAgain(); + + Result CountInstallContentMetaData(s32 *out_count); + Result GetInstallContentMetaData(InstallContentMeta *out_content_meta, s32 index); + Result DeleteInstallContentMetaData(const ContentMetaKey *keys, s32 num_keys); + virtual Result GetInstallContentMetaInfo(InstallContentMetaInfo *out_info, const ContentMetaKey &key) = 0; + + virtual Result PrepareDependency(); + Result PrepareSystemUpdateDependency(); + virtual Result PrepareContentMetaIfLatest(const ContentMetaKey &key); /* NOTE: This is not virtual in Nintendo's code. We do so to facilitate downgrades. */ + u32 GetConfig() const { return m_config; } + Result WriteContentMetaToPlaceHolder(InstallContentInfo *out_install_content_info, ContentStorage *storage, const InstallContentMetaInfo &meta_info, util::optional<bool> is_temporary); + + StorageId GetInstallStorage() const { return m_install_storage; } + + virtual Result OnPrepareComplete() { R_SUCCEED(); } + + Result GetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo *out); + + Result CanContinue(); + private: + bool IsCancelRequested(); + Result PrepareImpl(); + Result ExecuteImpl(); + Result CommitImpl(const StorageContentMetaKey *keys, s32 num_keys); + Result CleanupOne(const InstallContentMeta &content_meta); + + Result VerifyAllNotCommitted(const StorageContentMetaKey *keys, s32 num_keys); + + virtual Result PrepareInstallContentMetaData() = 0; + virtual Result GetLatestVersion(util::optional<u32> *out_version, u64 id) { AMS_UNUSED(out_version, id); R_THROW(ncm::ResultContentMetaNotFound()); } + + virtual Result OnExecuteComplete() { R_SUCCEED(); } + + Result WritePlaceHolder(const ContentMetaKey &key, InstallContentInfo *content_info); + virtual Result OnWritePlaceHolder(const ContentMetaKey &key, InstallContentInfo *content_info) = 0; + + bool IsNecessaryInstallTicket(const fs::RightsId &rights_id); + virtual Result InstallTicket(const fs::RightsId &rights_id, ContentMetaType meta_type) = 0; + + Result IsNewerThanInstalled(bool *out, const ContentMetaKey &key); + Result PreparePlaceHolder(); + + void SetProgressState(InstallProgressState state); + void IncrementProgress(s64 size); + void SetTotalSize(s64 size); + void SetLastResult(Result last_result); + void CleanupProgress(); + + void ResetThroughputMeasurement(); + void StartThroughputMeasurement(); + void UpdateThroughputMeasurement(s64 throughput); + + Result GetInstallContentMetaDataFromPath(AutoBuffer *out, const Path &path, const InstallContentInfo &content_info, util::optional<u32> source_version); + + InstallContentInfo MakeInstallContentInfoFrom(const InstallContentMetaInfo &info, const PlaceHolderId &placeholder_id, util::optional<bool> is_temporary); + + Result ReadContentMetaInfoList(s32 *out_count, std::unique_ptr<ContentMetaInfo[]> *out_meta_infos, const ContentMetaKey &key, fs::ContentAttributes attr); + Result ListRightsIdsByInstallContentMeta(s32 *out_count, Span<RightsId> out_span, const InstallContentMeta &content_meta, s32 offset); + public: + virtual Result CheckInstallable() { R_SUCCEED(); } + + void SetFirmwareVariationId(FirmwareVariationId id) { m_firmware_variation_id = id; } + Result ListRightsIds(s32 *out_count, Span<RightsId> out_span, const ContentMetaKey &key, s32 offset); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_data.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_data.hpp new file mode 100644 index 00000000..950c65a7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_data.hpp @@ -0,0 +1,149 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_content_meta.hpp> +#include <stratosphere/ncm/ncm_install_progress.hpp> +#include <stratosphere/ncm/ncm_system_update_task_apply_info.hpp> + +namespace ams::ncm { + + struct InstallContentMeta { + std::unique_ptr<char[]> data; + size_t size; + + InstallContentMetaReader GetReader() const { + return InstallContentMetaReader(this->data.get(), this->size); + } + + InstallContentMetaWriter GetWriter() const { + return InstallContentMetaWriter(this->data.get(), this->size); + } + }; + + class InstallTaskDataBase { + public: + Result Get(InstallContentMeta *out, s32 index); + Result Update(const InstallContentMeta &content_meta, s32 index); + Result Has(bool *out, u64 id); + public: + virtual Result GetProgress(InstallProgress *out_progress) = 0; + virtual Result GetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo *out_info) = 0; + virtual Result SetState(InstallProgressState state) = 0; + virtual Result SetLastResult(Result result) = 0; + virtual Result SetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo info) = 0; + virtual Result Push(const void *data, size_t data_size) = 0; + virtual Result Count(s32 *out) = 0; + virtual Result Delete(const ContentMetaKey *keys, s32 num_keys) = 0; + virtual Result Cleanup() = 0; + private: + virtual Result GetSize(size_t *out_size, s32 index) = 0; + virtual Result Get(s32 index, void *out, size_t out_size) = 0; + virtual Result Update(s32 index, const void *data, size_t data_size) = 0; + }; + + class MemoryInstallTaskData : public InstallTaskDataBase { + private: + struct DataHolder : public InstallContentMeta, public util::IntrusiveListBaseNode<DataHolder>{}; + using DataList = util::IntrusiveListBaseTraits<DataHolder>::ListType; + private: + DataList m_data_list; + InstallProgressState m_state; + Result m_last_result; + SystemUpdateTaskApplyInfo m_system_update_task_apply_info; + public: + MemoryInstallTaskData() : m_data_list(), m_state(InstallProgressState::NotPrepared), m_last_result(ResultSuccess()), m_system_update_task_apply_info() { /* ... */ }; + ~MemoryInstallTaskData() { + this->Cleanup(); + } + public: + virtual Result GetProgress(InstallProgress *out_progress) override; + virtual Result GetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo *out_info) override; + virtual Result SetState(InstallProgressState state) override; + virtual Result SetLastResult(Result result) override; + virtual Result SetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo info) override; + virtual Result Push(const void *data, size_t data_size) override; + virtual Result Count(s32 *out) override; + virtual Result Delete(const ContentMetaKey *keys, s32 num_keys) override; + virtual Result Cleanup() override; + private: + virtual Result GetSize(size_t *out_size, s32 index) override; + virtual Result Get(s32 index, void *out, size_t out_size) override; + virtual Result Update(s32 index, const void *data, size_t data_size) override; + }; + + class FileInstallTaskData : public InstallTaskDataBase { + private: + struct Header { + u32 max_entries; + u32 count; + s64 last_data_offset; + Result last_result; + InstallProgressState progress_state; + SystemUpdateTaskApplyInfo system_update_task_apply_info; + }; + + static_assert(sizeof(Header) == 0x18); + + struct EntryInfo { + s64 offset; + s64 size; + }; + + static_assert(sizeof(EntryInfo) == 0x10); + private: + Header m_header{}; + char m_path[64]{}; + private: + static constexpr Header MakeInitialHeader(s32 max_entries) { + return { + .max_entries = static_cast<u32>(max_entries), + .count = 0, + .last_data_offset = GetEntryInfoOffset(max_entries), + .last_result = ResultSuccess(), + .progress_state = InstallProgressState::NotPrepared, + .system_update_task_apply_info = SystemUpdateTaskApplyInfo::Unknown, + }; + } + + static constexpr s64 GetEntryInfoOffset(s32 index) { + return index * sizeof(EntryInfo) + sizeof(Header); + } + public: + static Result Create(const char *path, s32 max_entries); + Result Initialize(const char *path); + public: + virtual Result GetProgress(InstallProgress *out_progress) override; + virtual Result GetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo *out_info) override; + virtual Result SetState(InstallProgressState state) override; + virtual Result SetLastResult(Result result) override; + virtual Result SetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo info) override; + virtual Result Push(const void *data, size_t data_size) override; + virtual Result Count(s32 *out) override; + virtual Result Delete(const ContentMetaKey *keys, s32 num_keys) override; + virtual Result Cleanup() override; + private: + virtual Result GetSize(size_t *out_size, s32 index) override; + virtual Result Get(s32 index, void *out, size_t out_size) override; + virtual Result Update(s32 index, const void *data, size_t data_size) override; + + Result GetEntryInfo(EntryInfo *out_entry_info, s32 index); + + Result Write(const void *data, size_t size, s64 offset); + Result Read(void *out, size_t out_size, s64 offset); + Result WriteHeader(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_occupied_size.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_occupied_size.hpp new file mode 100644 index 00000000..07088279 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_install_task_occupied_size.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_content_meta_key.hpp> + +namespace ams::ncm { + + struct InstallTaskOccupiedSize { + ContentMetaKey key; + s64 size; + StorageId storage_id; + u8 reserved[7]; + }; + + static_assert(sizeof(InstallTaskOccupiedSize) == 0x20); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_integrated_content_meta_database_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_integrated_content_meta_database_impl.hpp new file mode 100644 index 00000000..8d0e79ff --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_integrated_content_meta_database_impl.hpp @@ -0,0 +1,79 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include <stratosphere/ncm/ncm_i_content_meta_database.hpp> +#include <stratosphere/ncm/ncm_integrated_list.hpp> + +namespace ams::ncm { + + class IntegratedContentMetaDatabaseImpl { + NON_COPYABLE(IntegratedContentMetaDatabaseImpl); + NON_MOVEABLE(IntegratedContentMetaDatabaseImpl); + private: + using ListType = ncm::IntegratedList<ncm::IContentMetaDatabase, 2>; + using DataType = ListType::ListData; + private: + os::SdkRecursiveMutex m_mutex; + ListType m_list; + bool m_disabled; + public: + IntegratedContentMetaDatabaseImpl() : m_mutex(), m_list(), m_disabled(false) { /* ... */ } + + void Add(sf::SharedPointer<ncm::IContentMetaDatabase> p, u8 id) { + DataType data = {std::move(p), id}; + m_list.Add(data); + } + private: + /* Helpers. */ + Result EnsureEnabled() const { + R_UNLESS(!m_disabled, ncm::ResultInvalidContentMetaDatabase()); + R_SUCCEED(); + } + public: + /* Actual commands. */ + Result Set(const ContentMetaKey &key, const sf::InBuffer &value); + Result Get(sf::Out<u64> out_size, const ContentMetaKey &key, const sf::OutBuffer &out_value); + Result Remove(const ContentMetaKey &key); + Result GetContentIdByType(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type); + Result ListContentInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentInfo> &out_info, const ContentMetaKey &key, s32 offset); + Result List(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType meta_type, ApplicationId application_id, u64 min, u64 max, ContentInstallType install_type); + Result GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, u64 id); + Result ListApplication(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ApplicationContentMetaKey> &out_keys, ContentMetaType meta_type); + Result Has(sf::Out<bool> out, const ContentMetaKey &key); + Result HasAll(sf::Out<bool> out, const sf::InArray<ContentMetaKey> &keys); + Result GetSize(sf::Out<u64> out_size, const ContentMetaKey &key); + Result GetRequiredSystemVersion(sf::Out<u32> out_version, const ContentMetaKey &key); + Result GetPatchContentMetaId(sf::Out<u64> out_patch_id, const ContentMetaKey &key); + Result DisableForcibly(); + Result LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids); + Result Commit(); + Result HasContent(sf::Out<bool> out, const ContentMetaKey &key, const ContentId &content_id); + Result ListContentMetaInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaInfo> &out_meta_info, const ContentMetaKey &key, s32 offset); + Result GetAttributes(sf::Out<u8> out_attributes, const ContentMetaKey &key); + Result GetRequiredApplicationVersion(sf::Out<u32> out_version, const ContentMetaKey &key); + Result GetContentIdByTypeAndIdOffset(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type, u8 id_offset); + Result GetCount(sf::Out<u32> out_count); + Result GetOwnerApplicationId(sf::Out<ApplicationId> out_id, const ContentMetaKey &key); + Result GetContentAccessibilities(sf::Out<u8> out_accessibilities, const ContentMetaKey &key); + Result GetContentInfoByType(sf::Out<ContentInfo> out_content_info, const ContentMetaKey &key, ContentType type); + Result GetContentInfoByTypeAndIdOffset(sf::Out<ContentInfo> out_content_info, const ContentMetaKey &key, ContentType type, u8 id_offset); + Result GetPlatform(sf::Out<ncm::ContentMetaPlatform> out, const ContentMetaKey &key); + Result HasAttributes(sf::Out<u8> out, u8 attr_mask); + }; + static_assert(ncm::IsIContentMetaDatabase<IntegratedContentMetaDatabaseImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_integrated_content_storage_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_integrated_content_storage_impl.hpp new file mode 100644 index 00000000..40a3063d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_integrated_content_storage_impl.hpp @@ -0,0 +1,92 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include <stratosphere/ncm/ncm_i_content_storage.hpp> +#include <stratosphere/ncm/ncm_integrated_list.hpp> + +namespace ams::ncm { + + class IntegratedContentStorageImpl { + NON_COPYABLE(IntegratedContentStorageImpl); + NON_MOVEABLE(IntegratedContentStorageImpl); + private: + using ListType = ncm::IntegratedList<ncm::IContentStorage, 2>; + using DataType = ListType::ListData; + private: + os::SdkRecursiveMutex m_mutex; + ListType m_list; + bool m_disabled; + public: + IntegratedContentStorageImpl() : m_mutex(), m_list(), m_disabled(false) { /* ... */ } + + void Add(sf::SharedPointer<ncm::IContentStorage> p, u8 id) { + DataType data = {std::move(p), id}; + m_list.Add(data); + } + private: + /* Helpers. */ + Result EnsureEnabled() const { + R_UNLESS(!m_disabled, ncm::ResultInvalidContentStorage()); + R_SUCCEED(); + } + public: + /* Actual commands. */ + Result GeneratePlaceHolderId(sf::Out<PlaceHolderId> out); + Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size); + Result DeletePlaceHolder(PlaceHolderId placeholder_id); + Result HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id); + Result WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, const sf::InBuffer &data); + Result Register(PlaceHolderId placeholder_id, ContentId content_id); + Result Delete(ContentId content_id); + Result Has(sf::Out<bool> out, ContentId content_id); + Result GetPath(sf::Out<Path> out, ContentId content_id); + Result GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id); + Result CleanupAllPlaceHolder(); + Result ListPlaceHolder(sf::Out<s32> out_count, const sf::OutArray<PlaceHolderId> &out_buf); + Result GetContentCount(sf::Out<s32> out_count); + Result ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out_buf, s32 start_offset); + Result GetSizeFromContentId(sf::Out<s64> out_size, ContentId content_id); + Result DisableForcibly(); + Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id); + Result SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size); + Result ReadContentIdFile(const sf::OutBuffer &buf, ContentId content_id, s64 offset); + Result GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id); + Result GetRightsIdFromPlaceHolderIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id); + Result GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, fs::ContentAttributes attr); + Result GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id); + Result GetRightsIdFromContentIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id); + Result GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id, fs::ContentAttributes attr); + Result WriteContentForDebug(ContentId content_id, s64 offset, const sf::InBuffer &data); + Result GetFreeSpaceSize(sf::Out<s64> out_size); + Result GetTotalSpaceSize(sf::Out<s64> out_size); + Result FlushPlaceHolder(); + Result GetSizeFromPlaceHolderId(sf::Out<s64> out, PlaceHolderId placeholder_id); + Result RepairInvalidFileAttribute(); + Result GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id, fs::ContentAttributes attr); + Result RegisterPath(const ContentId &content_id, const Path &path); + Result ClearRegisteredPath(); + Result GetProgramId(sf::Out<ncm::ProgramId> out, ContentId content_id, fs::ContentAttributes attr); + + /* 16.0.0 Alignment change hacks. */ + Result CreatePlaceHolder_AtmosphereAlignmentFix(ContentId content_id, PlaceHolderId placeholder_id, s64 size) { R_RETURN(this->CreatePlaceHolder(placeholder_id, content_id, size)); } + Result Register_AtmosphereAlignmentFix(ContentId content_id, PlaceHolderId placeholder_id) { R_RETURN(this->Register(placeholder_id, content_id)); } + Result RevertToPlaceHolder_AtmosphereAlignmentFix(ncm::ContentId old_content_id, ncm::ContentId new_content_id, ncm::PlaceHolderId placeholder_id) { R_RETURN(this->RevertToPlaceHolder(placeholder_id, old_content_id, new_content_id)); } + Result GetRightsIdFromPlaceHolderIdWithCacheDeprecated(sf::Out<ncm::RightsId> out_rights_id, ContentId cache_content_id, PlaceHolderId placeholder_id) { R_RETURN(this->GetRightsIdFromPlaceHolderIdWithCache(out_rights_id, placeholder_id, cache_content_id, fs::ContentAttributes_None)); } + }; + static_assert(ncm::IsIContentStorage<IntegratedContentStorageImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_integrated_list.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_integrated_list.hpp new file mode 100644 index 00000000..778eec60 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_integrated_list.hpp @@ -0,0 +1,91 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf/sf_shared_object.hpp> + +namespace ams::ncm { + + template<typename T, size_t N> + class IntegratedList { + static_assert(N <= std::numeric_limits<u8>::max()); + public: + struct ListData { + sf::SharedPointer<T> interface; + u8 id; + }; + private: + size_t m_count; + sf::SharedPointer<T> m_interfaces[N]; + u8 m_ids[N]; + public: + IntegratedList() : m_count(0), m_interfaces(), m_ids() { /* ... */ } + + void Add(ListData &data) { + /* Find place to insert into the list. */ + const size_t pos = std::distance(std::begin(m_ids), std::lower_bound(std::begin(m_ids), std::end(m_ids), data.id)); + + /* If we need to, move stuff to make space. */ + if (m_ids[pos] > data.id) { + AMS_ABORT_UNLESS(m_count < N); + + for (size_t i = m_count; i > pos; --i) { + m_ids[i] = std::move(m_ids[i - 1]); + m_interfaces[i] = std::move(m_interfaces[i - 1]); + } + + /* If we're inserting somewhere in the middle, increment count. */ + m_count++; + } else if (m_ids[pos] < data.id) { + /* If we're inserting at the end, increment count. */ + AMS_ABORT_UNLESS(m_count < N); + m_count++; + } + + /* Set at position. */ + m_interfaces[pos] = data.interface; + m_ids[pos] = data.id; + } + + ListData Get(size_t idx) { + AMS_ABORT_UNLESS(idx < m_count); + + return { m_interfaces[idx], m_ids[idx] }; + } + + Result TryEach(auto callback) { + Result result = ResultSuccess(); + for (size_t i = 0; i < m_count; ++i) { + result = callback(this->Get(i)); + if (R_SUCCEEDED(result)) { + break; + } + } + + R_RETURN(result); + } + + Result ForAll(auto callback) { + for (size_t i = 0; i < m_count; ++i) { + R_TRY(callback(this->Get(i))); + } + R_SUCCEED(); + } + + size_t GetCount() const { return m_count; } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_make_path.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_make_path.hpp new file mode 100644 index 00000000..aefdf38f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_make_path.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_content_id.hpp> +#include <stratosphere/ncm/ncm_placeholder_id.hpp> +#include <stratosphere/ncm/ncm_path_string.hpp> + +namespace ams::ncm { + + using MakeContentPathFunction = void (*)(PathString *out, ContentId content_id, const char *root_path); + using MakePlaceHolderPathFunction = void (*)(PathString *out, PlaceHolderId placeholder_id,const char *root_path); + + void MakeFlatContentFilePath(PathString *out, ContentId content_id, const char *root_path); + void MakeSha256HierarchicalContentFilePath_ForFat4KCluster(PathString *out, ContentId content_id, const char *root_path); + void MakeSha256HierarchicalContentFilePath_ForFat16KCluster(PathString *out, ContentId content_id, const char *root_path); + void MakeSha256HierarchicalContentFilePath_ForFat32KCluster(PathString *out, ContentId content_id, const char *root_path); + + size_t GetHierarchicalContentDirectoryDepth(MakeContentPathFunction func); + + void MakeFlatPlaceHolderFilePath(PathString *out, PlaceHolderId placeholder_id, const char *root_path); + void MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster(PathString *out, PlaceHolderId placeholder_id, const char *root_pathroot); + + size_t GetHierarchicalPlaceHolderDirectoryDepth(MakePlaceHolderPathFunction func); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_mapped_memory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_mapped_memory.hpp new file mode 100644 index 00000000..7523206a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_mapped_memory.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ncm { + + struct MappedMemory { + u64 id; + size_t offset; + u8 *buffer; + size_t buffer_size; + + bool IsIncluded(size_t o, size_t sz) const { + return this->offset <= o && sz <= this->buffer_size && (o + sz) <= (this->offset + this->buffer_size); + } + + u8 *GetBuffer(size_t o, size_t sz) const { + AMS_ASSERT(this->buffer != nullptr); + AMS_ASSERT(this->IsIncluded(o, sz)); + AMS_UNUSED(sz); + + return this->buffer + (o - this->offset); + } + }; + static_assert(util::is_pod<MappedMemory>::value); + + class IMapper { + public: + virtual ~IMapper() { /* ... */ } + public: + virtual Result GetMappedMemory(MappedMemory *out, size_t offset, size_t size) = 0; + virtual Result MarkUsing(u64 id) = 0; + virtual Result UnmarkUsing(u64 id) = 0; + virtual Result MarkDirty(u64 id) = 0; + protected: + virtual Result MapImpl(MappedMemory *out, Span<u8> data, size_t offset, size_t size) = 0; + virtual Result UnmapImpl(MappedMemory *mem) = 0; + virtual bool IsAccessibleSizeUpdatable() = 0; + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_max_count.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_max_count.hpp new file mode 100644 index 00000000..175b7773 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_max_count.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::ncm { + + constexpr inline s32 SystemMaxContentMetaCount = 0x800; + constexpr inline s32 GameCardMaxContentMetaCount = 0x800; + constexpr inline s32 HostMaxContentMetaCount = 0x800; + constexpr inline s32 UserMaxContentMetaCount = 0x2000; + constexpr inline s32 SdCardMaxContentMetaCount = 0x2000; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_memory_report.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_memory_report.hpp new file mode 100644 index 00000000..a54eaa10 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_memory_report.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/lmem/lmem_common.hpp> + +namespace ams::ncm { + + struct MemoryResourceState { + size_t peak_total_alloc_size; + size_t peak_alloc_size; + size_t allocatable_size; + size_t total_free_size; + }; + + static_assert(sizeof(MemoryResourceState) == 0x20); + + struct MemoryReport { + MemoryResourceState system_content_meta_resource_state; + MemoryResourceState sd_and_user_content_meta_resource_state; + MemoryResourceState gamecard_content_meta_resource_state; + MemoryResourceState heap_resource_state; + }; + + static_assert(sizeof(MemoryReport) == 0x80); + + class HeapState { + private: + os::SdkMutex m_mutex; + lmem::HeapHandle m_heap_handle; + size_t m_total_alloc_size; + size_t m_peak_total_alloc_size; + size_t m_peak_alloc_size; + public: + constexpr HeapState() : m_mutex(), m_heap_handle(nullptr), m_total_alloc_size(0), m_peak_total_alloc_size(0), m_peak_alloc_size(0) { /* ... */ } + + void Initialize(lmem::HeapHandle heap_handle); + void Allocate(size_t size); + void Free(size_t size); + void GetMemoryResourceState(MemoryResourceState *out); + }; + + HeapState &GetHeapState(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_install_task.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_install_task.hpp new file mode 100644 index 00000000..9aef5163 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_install_task.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_package_install_task_base.hpp> + +namespace ams::ncm { + + class PackageInstallTask : public PackageInstallTaskBase { + private: + MemoryInstallTaskData m_data{}; + public: + Result Initialize(const char *package_root, StorageId storage_id, void *buffer, size_t buffer_size, bool ignore_ticket); + protected: + bool IsContentMetaContentName(const char *name); + virtual Result PrepareInstallContentMetaData() override; + private: + virtual Result GetInstallContentMetaInfo(InstallContentMetaInfo *out_info, const ContentMetaKey &key) override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_install_task_base.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_install_task_base.hpp new file mode 100644 index 00000000..72341bec --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_install_task_base.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/kvdb/kvdb_bounded_string.hpp> +#include <stratosphere/ncm/ncm_install_task_base.hpp> + +namespace ams::ncm { + + class PackageInstallTaskBase : public InstallTaskBase { + private: + using PackagePath = kvdb::BoundedString<256>; + private: + PackagePath m_package_root; + void *m_buffer{}; + size_t m_buffer_size{}; + public: + PackageInstallTaskBase() : m_package_root() { /* ... */ } + + Result Initialize(const char *package_root_path, void *buffer, size_t buffer_size, StorageId storage_id, InstallTaskDataBase *data, u32 config); + protected: + const char *GetPackageRootPath() { + return m_package_root.Get(); + } + private: + virtual Result OnWritePlaceHolder(const ContentMetaKey &key, InstallContentInfo *content_info) override; + virtual Result InstallTicket(const fs::RightsId &rights_id, ContentMetaType meta_type) override; + + void CreateContentMetaPath(PackagePath *out_path, ContentId content_id); + void CreateContentPath(PackagePath *out_path, ContentId content_id); + void CreateTicketPath(PackagePath *out_path, fs::RightsId id); + void CreateCertificatePath(PackagePath *out_path, fs::RightsId id); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_system_downgrade_task.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_system_downgrade_task.hpp new file mode 100644 index 00000000..0b235f23 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_system_downgrade_task.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_package_system_update_task.hpp> + +namespace ams::ncm { + + class PackageSystemDowngradeTask : public PackageSystemUpdateTask { + public: + Result Commit(); + protected: + virtual Result PrepareContentMetaIfLatest(const ContentMetaKey &key) override; + private: + Result PreCommit(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_system_update_task.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_system_update_task.hpp new file mode 100644 index 00000000..568383c8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_package_system_update_task.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_package_install_task_base.hpp> + +namespace ams::ncm { + + class PackageSystemUpdateTask : public PackageInstallTaskBase { + private: + using PackagePath = kvdb::BoundedString<0x100>; + private: + PackagePath m_context_path{}; + FileInstallTaskData m_data{}; + ContentMetaDatabase m_package_db{}; + bool m_gamecard_content_meta_database_active{}; + public: + ~PackageSystemUpdateTask(); + + void Inactivate(); + Result Initialize(const char *package_root, const char *context_path, void *buffer, size_t buffer_size, bool requires_exfat_driver, FirmwareVariationId firmware_variation_id); + util::optional<ContentMetaKey> GetSystemUpdateMetaKey(); + protected: + virtual Result PrepareInstallContentMetaData() override; + virtual Result GetInstallContentMetaInfo(InstallContentMetaInfo *out, const ContentMetaKey &key) override; + InstallTaskDataBase &GetInstallData() { return m_data; } /* Atmosphere extension. */ + private: + virtual Result PrepareDependency() override; + + Result GetContentInfoOfContentMeta(ContentInfo *out, const ContentMetaKey &key); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_path.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_path.hpp new file mode 100644 index 00000000..41be4d97 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_path.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fs_directory.hpp> +#include <stratosphere/sf/sf_buffer_tags.hpp> + +namespace ams::ncm { + + struct alignas(4) Path : ams::sf::LargeData { + char str[fs::EntryNameLengthMax]; + + static constexpr Path Encode(const char *p) { + Path path = {}; + /* Copy C string to path, terminating when a null byte is found. */ + for (size_t i = 0; i < sizeof(path) - 1; i++) { + path.str[i] = p[i]; + if (p[i] == '\x00') { + break; + } + } + return path; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_path_string.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_path_string.hpp new file mode 100644 index 00000000..1b4a4bd6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_path_string.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/fs_directory.hpp> +#include <stratosphere/kvdb/kvdb_bounded_string.hpp> + +namespace ams::ncm { + + using PathString = kvdb::BoundedString<fs::EntryNameLengthMax>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_placeholder_id.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_placeholder_id.hpp new file mode 100644 index 00000000..ff9407db --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_placeholder_id.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::ncm { + + struct PlaceHolderId { + util::Uuid uuid; + + bool operator==(const PlaceHolderId &other) const { + return this->uuid == other.uuid; + } + + bool operator!=(const PlaceHolderId &other) const { + return this->uuid != other.uuid; + } + + bool operator==(const util::Uuid &other) const { + return this->uuid == other; + } + + bool operator!=(const util::Uuid &other) const { + return this->uuid != other; + } + }; + + static_assert(alignof(PlaceHolderId) == 1); + + constexpr inline PlaceHolderId InvalidPlaceHolderId = { util::InvalidUuid }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_program_id.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_program_id.hpp new file mode 100644 index 00000000..83aa4a44 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_program_id.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::ncm { + + struct ProgramId { + u64 value; + + #if defined(ATMOSPHERE_OS_HORIZON) + inline explicit operator svc::ProgramId() const { + static_assert(sizeof(value) == sizeof(svc::ProgramId)); + return { this->value }; + } + #endif + + constexpr inline auto operator<=>(const ProgramId &) const = default; + }; + + inline constexpr const ProgramId InvalidProgramId = {}; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_program_location.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_program_location.hpp new file mode 100644 index 00000000..9dd88dc6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_program_location.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_program_id.hpp> +#include <stratosphere/ncm/ncm_storage_id.hpp> + +namespace ams::ncm { + + struct ProgramLocation { + ProgramId program_id; + u8 storage_id; + + static constexpr ProgramLocation Make(ProgramId program_id, StorageId storage_id) { + return { .program_id = program_id, .storage_id = static_cast<u8>(storage_id), }; + } + }; + static_assert(sizeof(ProgramLocation) == 0x10 && util::is_pod<ProgramLocation>::value); + + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(sizeof(ProgramLocation) == sizeof(::NcmProgramLocation) && alignof(ProgramLocation) == alignof(::NcmProgramLocation), "ProgramLocation Libnx Compatibility"); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_registered_host_content.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_registered_host_content.hpp new file mode 100644 index 00000000..5270b1ea --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_registered_host_content.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/os.hpp> +#include <stratosphere/ncm/ncm_content_id.hpp> +#include <stratosphere/ncm/ncm_path.hpp> + +namespace ams::ncm { + + class RegisteredHostContent { + NON_COPYABLE(RegisteredHostContent); + NON_MOVEABLE(RegisteredHostContent); + private: + class RegisteredPath; + private: + using RegisteredPathList = ams::util::IntrusiveListBaseTraits<RegisteredPath>::ListType; + private: + os::SdkMutex m_mutex; + RegisteredPathList m_path_list; + public: + RegisteredHostContent() : m_mutex(), m_path_list() { /* ... */ } + ~RegisteredHostContent(); + + Result RegisterPath(const ncm::ContentId &content_id, const ncm::Path &path); + Result GetPath(Path *out, const ncm::ContentId &content_id); + void ClearPaths(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_rights_id.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_rights_id.hpp new file mode 100644 index 00000000..e8fa2896 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_rights_id.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fs_rights_id.hpp> + +namespace ams::ncm { + + struct RightsId { + fs::RightsId id; + u8 key_generation; + u8 reserved[7]; + }; + static_assert(sizeof(RightsId) == 0x18); + static_assert(util::is_pod<RightsId>::value); + + inline bool operator==(const RightsId &lhs, const RightsId &rhs) { + return std::tie(lhs.id, lhs.key_generation) == std::tie(rhs.id, rhs.key_generation); + } + + inline bool operator!=(const RightsId &lhs, const RightsId &rhs) { + return !(lhs == rhs); + } + + inline bool operator<(const RightsId &lhs, const RightsId &rhs) { + return std::tie(lhs.id, lhs.key_generation) < std::tie(rhs.id, rhs.key_generation); + } +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_rights_id_cache.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_rights_id_cache.hpp new file mode 100644 index 00000000..ca6e1ab6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_rights_id_cache.hpp @@ -0,0 +1,90 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/os.hpp> +#include <stratosphere/ncm/ncm_rights_id.hpp> +#include <stratosphere/ncm/ncm_content_id.hpp> + +namespace ams::ncm { + + class RightsIdCache { + NON_COPYABLE(RightsIdCache); + NON_MOVEABLE(RightsIdCache); + private: + static constexpr size_t MaxEntries = 0x80; + private: + struct Entry { + public: + util::Uuid uuid; + ncm::RightsId rights_id; + u64 last_accessed; + }; + private: + Entry m_entries[MaxEntries]; + u64 m_counter; + os::SdkMutex m_mutex; + public: + RightsIdCache() : m_entries(), m_counter(2), m_mutex() { + this->Invalidate(); + } + + void Invalidate() { + m_counter = 2; + for (size_t i = 0; i < MaxEntries; i++) { + m_entries[i].last_accessed = 1; + } + } + + void Store(ContentId content_id, ncm::RightsId rights_id) { + std::scoped_lock lk(m_mutex); + Entry *eviction_candidate = std::addressof(m_entries[0]); + + /* Find a suitable existing entry to store our new one at. */ + for (size_t i = 1; i < MaxEntries; i++) { + Entry *entry = std::addressof(m_entries[i]); + + /* Change eviction candidates if the uuid already matches ours, or if the uuid doesn't already match and the last_accessed count is lower */ + if (content_id == entry->uuid || (content_id != eviction_candidate->uuid && entry->last_accessed < eviction_candidate->last_accessed)) { + eviction_candidate = entry; + } + } + + /* Update the cache. */ + eviction_candidate->uuid = content_id.uuid; + eviction_candidate->rights_id = rights_id; + eviction_candidate->last_accessed = m_counter++; + } + + bool Find(ncm::RightsId *out_rights_id, ContentId content_id) { + std::scoped_lock lk(m_mutex); + + /* Attempt to locate the content id in the cache. */ + for (size_t i = 0; i < MaxEntries; i++) { + Entry *entry = std::addressof(m_entries[i]); + + if (entry->last_accessed != 1 && content_id == entry->uuid) { + entry->last_accessed = m_counter; + m_counter++; + *out_rights_id = entry->rights_id; + return true; + } + } + + return false; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_storage_id.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_storage_id.hpp new file mode 100644 index 00000000..6306319d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_storage_id.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::ncm { + + enum class StorageId : u8 { + None = 0, + Host = 1, + GameCard = 2, + BuiltInSystem = 3, + BuiltInUser = 4, + SdCard = 5, + Any = 6, + + /* Aliases. */ + Card = GameCard, + BuildInSystem = BuiltInSystem, + BuildInUser = BuiltInUser, + }; + + constexpr inline bool IsUniqueStorage(StorageId id) { + return id != StorageId::None && id != StorageId::Any; + } + + constexpr inline bool IsInstallableStorage(StorageId id) { + return id == StorageId::BuiltInSystem || id == StorageId::BuiltInUser || id == StorageId::SdCard || id == StorageId::Any; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_storage_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_storage_utils.hpp new file mode 100644 index 00000000..3264cbf4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_storage_utils.hpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_storage_id.hpp> +#include <stratosphere/ncm/ncm_content_meta_id.hpp> + +namespace ams::ncm { + + class StorageList { + public: + static constexpr s32 MaxCount = 10; + private: + StorageId m_ids[MaxCount]; + s32 m_count; + public: + constexpr StorageList() : m_ids(), m_count() { /* ... */ } + + void Push(StorageId storage_id) { + AMS_ABORT_UNLESS(m_count < MaxCount); + + for (s32 i = 0; i < MaxCount; i++) { + if (m_ids[i] == storage_id) { + return; + } + } + + m_ids[m_count++] = storage_id; + } + + s32 Count() const { + return m_count; + } + + StorageId operator[](s32 i) const { + AMS_ABORT_UNLESS(i < m_count); + return m_ids[i]; + } + }; + + constexpr StorageList GetStorageList(StorageId storage_id) { + StorageList list; + switch (storage_id) { + case StorageId::BuiltInSystem: + case StorageId::BuiltInUser: + case StorageId::SdCard: + list.Push(storage_id); + break; + case StorageId::Any: + list.Push(StorageId::SdCard); + list.Push(StorageId::BuiltInUser); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + return list; + } + + Result SelectDownloadableStorage(StorageId *out_storage_id, StorageId storage_id, s64 required_size); + Result SelectPatchStorage(StorageId *out_storage_id, StorageId storage_id, PatchId patch_id); + const char *GetStorageIdString(StorageId storage_id); + const char *GetStorageIdStringForPlayReport(StorageId storage_id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_submission_package_install_task.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_submission_package_install_task.hpp new file mode 100644 index 00000000..8f94d3a4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_submission_package_install_task.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/fs/common/fs_file_storage.hpp> +#include <stratosphere/ncm/ncm_package_install_task.hpp> + +namespace ams::ncm { + + class SubmissionPackageInstallTask : public PackageInstallTask { + private: + class Impl; + private: + std::unique_ptr<Impl> m_impl; + public: + SubmissionPackageInstallTask(); + virtual ~SubmissionPackageInstallTask() override; + + Result Initialize(fs::FileHandle handle, StorageId storage_id, void *buffer, size_t buffer_size, bool ignore_ticket = false); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_system_content_meta_id.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_system_content_meta_id.hpp new file mode 100644 index 00000000..b8e7bcd4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_system_content_meta_id.hpp @@ -0,0 +1,588 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/ncm/ncm_data_id.hpp> +#include <stratosphere/ncm/ncm_program_id.hpp> + +namespace ams::ncm { + + struct SystemProgramId { + u64 value; + + constexpr operator ProgramId() const { + return { this->value }; + } + + static const SystemProgramId Start; + + static const SystemProgramId Fs; + static const SystemProgramId Loader; + static const SystemProgramId Ncm; + static const SystemProgramId Pm; + static const SystemProgramId Sm; + static const SystemProgramId Boot; + static const SystemProgramId Usb; + static const SystemProgramId Tma; + static const SystemProgramId Boot2; + static const SystemProgramId Settings; + static const SystemProgramId Bus; + static const SystemProgramId Bluetooth; + static const SystemProgramId Bcat; + static const SystemProgramId Dmnt; + static const SystemProgramId Friends; + static const SystemProgramId Nifm; + static const SystemProgramId Ptm; + static const SystemProgramId Shell; + static const SystemProgramId BsdSockets; + static const SystemProgramId Hid; + static const SystemProgramId Audio; + static const SystemProgramId LogManager; + static const SystemProgramId Wlan; + static const SystemProgramId Cs; + static const SystemProgramId Ldn; + static const SystemProgramId NvServices; + static const SystemProgramId Pcv; + static const SystemProgramId Ppc; + static const SystemProgramId NvnFlinger; + static const SystemProgramId Pcie; + static const SystemProgramId Account; + static const SystemProgramId Ns; + static const SystemProgramId Nfc; + static const SystemProgramId Psc; + static const SystemProgramId CapSrv; + static const SystemProgramId Am; + static const SystemProgramId Ssl; + static const SystemProgramId Nim; + static const SystemProgramId Cec; + static const SystemProgramId Tspm; + static const SystemProgramId Spl; + static const SystemProgramId Lbl; + static const SystemProgramId Btm; + static const SystemProgramId Erpt; + static const SystemProgramId Time; + static const SystemProgramId Vi; + static const SystemProgramId Pctl; + static const SystemProgramId Npns; + static const SystemProgramId Eupld; + static const SystemProgramId Arp; + static const SystemProgramId Glue; + static const SystemProgramId Eclct; + static const SystemProgramId Es; + static const SystemProgramId Fatal; + static const SystemProgramId Grc; + static const SystemProgramId Creport; + static const SystemProgramId Ro; + static const SystemProgramId Profiler; + static const SystemProgramId Sdb; + static const SystemProgramId Migration; + static const SystemProgramId Jit; + static const SystemProgramId JpegDec; + static const SystemProgramId SafeMode; + static const SystemProgramId Olsc; + static const SystemProgramId Dt; + static const SystemProgramId Nd; + static const SystemProgramId Ngct; + static const SystemProgramId Pgl; + static const SystemProgramId Omm; + static const SystemProgramId Eth; + static const SystemProgramId Ngc; + + static const SystemProgramId End; + + static const SystemProgramId Manu; + static const SystemProgramId Htc; + static const SystemProgramId DmntGen2; + static const SystemProgramId DevServer; + }; + + struct AtmosphereProgramId { + u64 value; + + constexpr operator SystemProgramId() const { + return { this->value }; + } + + constexpr operator ProgramId() const { + return static_cast<SystemProgramId>(*this); + } + + static const AtmosphereProgramId Mitm; + static const AtmosphereProgramId AtmosphereLogManager; + static const AtmosphereProgramId AtmosphereMemlet; + }; + + inline constexpr const AtmosphereProgramId AtmosphereProgramId::Mitm = { 0x010041544D530000ul }; + inline constexpr const AtmosphereProgramId AtmosphereProgramId::AtmosphereLogManager = { 0x0100000000000420ul }; + inline constexpr const AtmosphereProgramId AtmosphereProgramId::AtmosphereMemlet = { 0x0100000000000421ul }; + + inline constexpr bool IsAtmosphereProgramId(const ProgramId &program_id) { + return program_id == AtmosphereProgramId::Mitm || program_id == AtmosphereProgramId::AtmosphereLogManager || program_id == AtmosphereProgramId::AtmosphereMemlet; + } + + inline constexpr bool IsSystemProgramId(const AtmosphereProgramId &) { + return true; + } + + inline constexpr const SystemProgramId SystemProgramId::Start = { 0x0100000000000000ul }; + + inline constexpr const SystemProgramId SystemProgramId::Fs = { 0x0100000000000000ul }; + inline constexpr const SystemProgramId SystemProgramId::Loader = { 0x0100000000000001ul }; + inline constexpr const SystemProgramId SystemProgramId::Ncm = { 0x0100000000000002ul }; + inline constexpr const SystemProgramId SystemProgramId::Pm = { 0x0100000000000003ul }; + inline constexpr const SystemProgramId SystemProgramId::Sm = { 0x0100000000000004ul }; + inline constexpr const SystemProgramId SystemProgramId::Boot = { 0x0100000000000005ul }; + inline constexpr const SystemProgramId SystemProgramId::Usb = { 0x0100000000000006ul }; + inline constexpr const SystemProgramId SystemProgramId::Tma = { 0x0100000000000007ul }; + inline constexpr const SystemProgramId SystemProgramId::Boot2 = { 0x0100000000000008ul }; + inline constexpr const SystemProgramId SystemProgramId::Settings = { 0x0100000000000009ul }; + inline constexpr const SystemProgramId SystemProgramId::Bus = { 0x010000000000000Aul }; + inline constexpr const SystemProgramId SystemProgramId::Bluetooth = { 0x010000000000000Bul }; + inline constexpr const SystemProgramId SystemProgramId::Bcat = { 0x010000000000000Cul }; + inline constexpr const SystemProgramId SystemProgramId::Dmnt = { 0x010000000000000Dul }; + inline constexpr const SystemProgramId SystemProgramId::Friends = { 0x010000000000000Eul }; + inline constexpr const SystemProgramId SystemProgramId::Nifm = { 0x010000000000000Ful }; + inline constexpr const SystemProgramId SystemProgramId::Ptm = { 0x0100000000000010ul }; + inline constexpr const SystemProgramId SystemProgramId::Shell = { 0x0100000000000011ul }; + inline constexpr const SystemProgramId SystemProgramId::BsdSockets = { 0x0100000000000012ul }; + inline constexpr const SystemProgramId SystemProgramId::Hid = { 0x0100000000000013ul }; + inline constexpr const SystemProgramId SystemProgramId::Audio = { 0x0100000000000014ul }; + inline constexpr const SystemProgramId SystemProgramId::LogManager = { 0x0100000000000015ul }; + inline constexpr const SystemProgramId SystemProgramId::Wlan = { 0x0100000000000016ul }; + inline constexpr const SystemProgramId SystemProgramId::Cs = { 0x0100000000000017ul }; + inline constexpr const SystemProgramId SystemProgramId::Ldn = { 0x0100000000000018ul }; + inline constexpr const SystemProgramId SystemProgramId::NvServices = { 0x0100000000000019ul }; + inline constexpr const SystemProgramId SystemProgramId::Pcv = { 0x010000000000001Aul }; + inline constexpr const SystemProgramId SystemProgramId::Ppc = { 0x010000000000001Bul }; + inline constexpr const SystemProgramId SystemProgramId::NvnFlinger = { 0x010000000000001Cul }; + inline constexpr const SystemProgramId SystemProgramId::Pcie = { 0x010000000000001Dul }; + inline constexpr const SystemProgramId SystemProgramId::Account = { 0x010000000000001Eul }; + inline constexpr const SystemProgramId SystemProgramId::Ns = { 0x010000000000001Ful }; + inline constexpr const SystemProgramId SystemProgramId::Nfc = { 0x0100000000000020ul }; + inline constexpr const SystemProgramId SystemProgramId::Psc = { 0x0100000000000021ul }; + inline constexpr const SystemProgramId SystemProgramId::CapSrv = { 0x0100000000000022ul }; + inline constexpr const SystemProgramId SystemProgramId::Am = { 0x0100000000000023ul }; + inline constexpr const SystemProgramId SystemProgramId::Ssl = { 0x0100000000000024ul }; + inline constexpr const SystemProgramId SystemProgramId::Nim = { 0x0100000000000025ul }; + inline constexpr const SystemProgramId SystemProgramId::Cec = { 0x0100000000000026ul }; + inline constexpr const SystemProgramId SystemProgramId::Tspm = { 0x0100000000000027ul }; + inline constexpr const SystemProgramId SystemProgramId::Spl = { 0x0100000000000028ul }; + inline constexpr const SystemProgramId SystemProgramId::Lbl = { 0x0100000000000029ul }; + inline constexpr const SystemProgramId SystemProgramId::Btm = { 0x010000000000002Aul }; + inline constexpr const SystemProgramId SystemProgramId::Erpt = { 0x010000000000002Bul }; + inline constexpr const SystemProgramId SystemProgramId::Time = { 0x010000000000002Cul }; + inline constexpr const SystemProgramId SystemProgramId::Vi = { 0x010000000000002Dul }; + inline constexpr const SystemProgramId SystemProgramId::Pctl = { 0x010000000000002Eul }; + inline constexpr const SystemProgramId SystemProgramId::Npns = { 0x010000000000002Ful }; + inline constexpr const SystemProgramId SystemProgramId::Eupld = { 0x0100000000000030ul }; + inline constexpr const SystemProgramId SystemProgramId::Arp = { 0x0100000000000031ul }; + inline constexpr const SystemProgramId SystemProgramId::Glue = { 0x0100000000000031ul }; + inline constexpr const SystemProgramId SystemProgramId::Eclct = { 0x0100000000000032ul }; + inline constexpr const SystemProgramId SystemProgramId::Es = { 0x0100000000000033ul }; + inline constexpr const SystemProgramId SystemProgramId::Fatal = { 0x0100000000000034ul }; + inline constexpr const SystemProgramId SystemProgramId::Grc = { 0x0100000000000035ul }; + inline constexpr const SystemProgramId SystemProgramId::Creport = { 0x0100000000000036ul }; + inline constexpr const SystemProgramId SystemProgramId::Ro = { 0x0100000000000037ul }; + inline constexpr const SystemProgramId SystemProgramId::Profiler = { 0x0100000000000038ul }; + inline constexpr const SystemProgramId SystemProgramId::Sdb = { 0x0100000000000039ul }; + inline constexpr const SystemProgramId SystemProgramId::Migration = { 0x010000000000003Aul }; + inline constexpr const SystemProgramId SystemProgramId::Jit = { 0x010000000000003Bul }; + inline constexpr const SystemProgramId SystemProgramId::JpegDec = { 0x010000000000003Cul }; + inline constexpr const SystemProgramId SystemProgramId::SafeMode = { 0x010000000000003Dul }; + inline constexpr const SystemProgramId SystemProgramId::Olsc = { 0x010000000000003Eul }; + inline constexpr const SystemProgramId SystemProgramId::Dt = { 0x010000000000003Ful }; + inline constexpr const SystemProgramId SystemProgramId::Nd = { 0x0100000000000040ul }; + inline constexpr const SystemProgramId SystemProgramId::Ngct = { 0x0100000000000041ul }; + inline constexpr const SystemProgramId SystemProgramId::Pgl = { 0x0100000000000042ul }; + inline constexpr const SystemProgramId SystemProgramId::Omm = { 0x0100000000000045ul }; + inline constexpr const SystemProgramId SystemProgramId::Eth = { 0x0100000000000046ul }; + inline constexpr const SystemProgramId SystemProgramId::Ngc = { 0x0100000000000050ul }; + + inline constexpr const SystemProgramId SystemProgramId::End = { 0x01000000000007FFul }; + + inline constexpr const SystemProgramId SystemProgramId::Manu = { 0x010000000000B14Aul }; + inline constexpr const SystemProgramId SystemProgramId::Htc = { 0x010000000000B240ul }; + inline constexpr const SystemProgramId SystemProgramId::DmntGen2 = { 0x010000000000D609ul }; + inline constexpr const SystemProgramId SystemProgramId::DevServer = { 0x010000000000D623ul }; + + inline constexpr bool IsSystemProgramId(const ProgramId &program_id) { + return (SystemProgramId::Start <= program_id && program_id <= SystemProgramId::End) || IsAtmosphereProgramId(program_id); + } + + inline constexpr bool IsSystemProgramId(const SystemProgramId &) { + return true; + } + + struct SystemDataId { + u64 value; + + constexpr operator DataId() const { + return { this->value }; + } + + constexpr inline bool operator==(const SystemDataId &) const = default; + constexpr inline bool operator!=(const SystemDataId &) const = default; + + static const SystemDataId Start; + + static const SystemDataId CertStore; + static const SystemDataId ErrorMessage; + static const SystemDataId MiiModel; + static const SystemDataId BrowserDll; + static const SystemDataId Help; + static const SystemDataId SharedFont; + static const SystemDataId NgWord; + static const SystemDataId SsidList; + static const SystemDataId Dictionary; + static const SystemDataId SystemVersion; + static const SystemDataId AvatarImage; + static const SystemDataId LocalNews; + static const SystemDataId Eula; + static const SystemDataId UrlBlackList; + static const SystemDataId TimeZoneBinar; + static const SystemDataId CertStoreCruiser; + static const SystemDataId FontNintendoExtension; + static const SystemDataId FontStandard; + static const SystemDataId FontKorean; + static const SystemDataId FontChineseTraditional; + static const SystemDataId FontChineseSimple; + static const SystemDataId FontBfcpx; + static const SystemDataId SystemUpdate; + + static const SystemDataId FirmwareDebugSettings; + static const SystemDataId BootImagePackage; + static const SystemDataId BootImagePackageSafe; + static const SystemDataId BootImagePackageExFat; + static const SystemDataId BootImagePackageExFatSafe; + static const SystemDataId FatalMessage; + static const SystemDataId ControllerIcon; + static const SystemDataId PlatformConfigIcosa; + static const SystemDataId PlatformConfigCopper; + static const SystemDataId PlatformConfigHoag; + static const SystemDataId ControllerFirmware; + static const SystemDataId NgWord2; + static const SystemDataId PlatformConfigIcosaMariko; + static const SystemDataId ApplicationBlackList; + static const SystemDataId RebootlessSystemUpdateVersion; + static const SystemDataId ContentActionTable; + + static const SystemDataId PlatformConfigCalcio; + + static const SystemDataId PlatformConfigAula; + + static const SystemDataId End; + }; + + inline constexpr const SystemDataId SystemDataId::Start = { 0x0100000000000800ul }; + inline constexpr const SystemDataId SystemDataId::CertStore = { 0x0100000000000800ul }; + inline constexpr const SystemDataId SystemDataId::ErrorMessage = { 0x0100000000000801ul }; + inline constexpr const SystemDataId SystemDataId::MiiModel = { 0x0100000000000802ul }; + inline constexpr const SystemDataId SystemDataId::BrowserDll = { 0x0100000000000803ul }; + inline constexpr const SystemDataId SystemDataId::Help = { 0x0100000000000804ul }; + inline constexpr const SystemDataId SystemDataId::SharedFont = { 0x0100000000000805ul }; + inline constexpr const SystemDataId SystemDataId::NgWord = { 0x0100000000000806ul }; + inline constexpr const SystemDataId SystemDataId::SsidList = { 0x0100000000000807ul }; + inline constexpr const SystemDataId SystemDataId::Dictionary = { 0x0100000000000808ul }; + inline constexpr const SystemDataId SystemDataId::SystemVersion = { 0x0100000000000809ul }; + inline constexpr const SystemDataId SystemDataId::AvatarImage = { 0x010000000000080Aul }; + inline constexpr const SystemDataId SystemDataId::LocalNews = { 0x010000000000080Bul }; + inline constexpr const SystemDataId SystemDataId::Eula = { 0x010000000000080Cul }; + inline constexpr const SystemDataId SystemDataId::UrlBlackList = { 0x010000000000080Dul }; + inline constexpr const SystemDataId SystemDataId::TimeZoneBinar = { 0x010000000000080Eul }; + inline constexpr const SystemDataId SystemDataId::CertStoreCruiser = { 0x010000000000080Ful }; + inline constexpr const SystemDataId SystemDataId::FontNintendoExtension = { 0x0100000000000810ul }; + inline constexpr const SystemDataId SystemDataId::FontStandard = { 0x0100000000000811ul }; + inline constexpr const SystemDataId SystemDataId::FontKorean = { 0x0100000000000812ul }; + inline constexpr const SystemDataId SystemDataId::FontChineseTraditional = { 0x0100000000000813ul }; + inline constexpr const SystemDataId SystemDataId::FontChineseSimple = { 0x0100000000000814ul }; + inline constexpr const SystemDataId SystemDataId::FontBfcpx = { 0x0100000000000815ul }; + inline constexpr const SystemDataId SystemDataId::SystemUpdate = { 0x0100000000000816ul }; + + inline constexpr const SystemDataId SystemDataId::FirmwareDebugSettings = { 0x0100000000000818ul }; + inline constexpr const SystemDataId SystemDataId::BootImagePackage = { 0x0100000000000819ul }; + inline constexpr const SystemDataId SystemDataId::BootImagePackageSafe = { 0x010000000000081Aul }; + inline constexpr const SystemDataId SystemDataId::BootImagePackageExFat = { 0x010000000000081Bul }; + inline constexpr const SystemDataId SystemDataId::BootImagePackageExFatSafe = { 0x010000000000081Cul }; + inline constexpr const SystemDataId SystemDataId::FatalMessage = { 0x010000000000081Dul }; + inline constexpr const SystemDataId SystemDataId::ControllerIcon = { 0x010000000000081Eul }; + inline constexpr const SystemDataId SystemDataId::PlatformConfigIcosa = { 0x010000000000081Ful }; + inline constexpr const SystemDataId SystemDataId::PlatformConfigCopper = { 0x0100000000000820ul }; + inline constexpr const SystemDataId SystemDataId::PlatformConfigHoag = { 0x0100000000000821ul }; + inline constexpr const SystemDataId SystemDataId::ControllerFirmware = { 0x0100000000000822ul }; + inline constexpr const SystemDataId SystemDataId::NgWord2 = { 0x0100000000000823ul }; + inline constexpr const SystemDataId SystemDataId::PlatformConfigIcosaMariko = { 0x0100000000000824ul }; + inline constexpr const SystemDataId SystemDataId::ApplicationBlackList = { 0x0100000000000825ul }; + inline constexpr const SystemDataId SystemDataId::RebootlessSystemUpdateVersion = { 0x0100000000000826ul }; + inline constexpr const SystemDataId SystemDataId::ContentActionTable = { 0x0100000000000827ul }; + + inline constexpr const SystemDataId SystemDataId::PlatformConfigCalcio = { 0x0100000000000829ul }; + + inline constexpr const SystemDataId SystemDataId::PlatformConfigAula = { 0x0100000000000831ul }; + + inline constexpr const SystemDataId SystemDataId::End = { 0x0100000000000FFFul }; + + inline constexpr bool IsSystemDataId(const DataId &data_id) { + return SystemDataId::Start <= data_id && data_id <= SystemDataId::End; + } + + inline constexpr bool IsSystemDataId(const SystemDataId &) { + return true; + } + + struct SystemUpdateId { + u64 value; + + constexpr operator DataId() const { + return { this->value }; + } + + constexpr inline bool operator==(const SystemUpdateId &) const = default; + constexpr inline bool operator!=(const SystemUpdateId &) const = default; + }; + + struct SystemAppletId { + u64 value; + + constexpr operator ProgramId() const { + return { this->value }; + } + + constexpr inline bool operator==(const SystemAppletId &) const = default; + constexpr inline bool operator!=(const SystemAppletId &) const = default; + + static const SystemAppletId Start; + + static const SystemAppletId Qlaunch; + static const SystemAppletId Auth; + static const SystemAppletId Cabinet; + static const SystemAppletId Controller; + static const SystemAppletId DataErase; + static const SystemAppletId Error; + static const SystemAppletId NetConnect; + static const SystemAppletId PlayerSelect; + static const SystemAppletId Swkbd; + static const SystemAppletId MiiEdit; + static const SystemAppletId Web; + static const SystemAppletId Shop; + static const SystemAppletId OverlayDisp; + static const SystemAppletId PhotoViewer; + static const SystemAppletId Set; + static const SystemAppletId OfflineWeb; + static const SystemAppletId LoginShare; + static const SystemAppletId WifiWebAuth; + static const SystemAppletId Starter; + static const SystemAppletId MyPage; + static const SystemAppletId PlayReport; + static const SystemAppletId MaintenanceMenu; + + static const SystemAppletId Gift; + static const SystemAppletId DummyShop; + static const SystemAppletId UserMigration; + static const SystemAppletId Encounter; + + static const SystemAppletId Story; + + static const SystemAppletId End; + }; + + inline constexpr const SystemAppletId SystemAppletId::Start = { 0x0100000000001000ul }; + + inline constexpr const SystemAppletId SystemAppletId::Qlaunch = { 0x0100000000001000ul }; + inline constexpr const SystemAppletId SystemAppletId::Auth = { 0x0100000000001001ul }; + inline constexpr const SystemAppletId SystemAppletId::Cabinet = { 0x0100000000001002ul }; + inline constexpr const SystemAppletId SystemAppletId::Controller = { 0x0100000000001003ul }; + inline constexpr const SystemAppletId SystemAppletId::DataErase = { 0x0100000000001004ul }; + inline constexpr const SystemAppletId SystemAppletId::Error = { 0x0100000000001005ul }; + inline constexpr const SystemAppletId SystemAppletId::NetConnect = { 0x0100000000001006ul }; + inline constexpr const SystemAppletId SystemAppletId::PlayerSelect = { 0x0100000000001007ul }; + inline constexpr const SystemAppletId SystemAppletId::Swkbd = { 0x0100000000001008ul }; + inline constexpr const SystemAppletId SystemAppletId::MiiEdit = { 0x0100000000001009ul }; + inline constexpr const SystemAppletId SystemAppletId::Web = { 0x010000000000100Aul }; + inline constexpr const SystemAppletId SystemAppletId::Shop = { 0x010000000000100Bul }; + inline constexpr const SystemAppletId SystemAppletId::OverlayDisp = { 0x010000000000100Cul }; + inline constexpr const SystemAppletId SystemAppletId::PhotoViewer = { 0x010000000000100Dul }; + inline constexpr const SystemAppletId SystemAppletId::Set = { 0x010000000000100Eul }; + inline constexpr const SystemAppletId SystemAppletId::OfflineWeb = { 0x010000000000100Ful }; + inline constexpr const SystemAppletId SystemAppletId::LoginShare = { 0x0100000000001010ul }; + inline constexpr const SystemAppletId SystemAppletId::WifiWebAuth = { 0x0100000000001011ul }; + inline constexpr const SystemAppletId SystemAppletId::Starter = { 0x0100000000001012ul }; + inline constexpr const SystemAppletId SystemAppletId::MyPage = { 0x0100000000001013ul }; + inline constexpr const SystemAppletId SystemAppletId::PlayReport = { 0x0100000000001014ul }; + inline constexpr const SystemAppletId SystemAppletId::MaintenanceMenu = { 0x0100000000001015ul }; + + inline constexpr const SystemAppletId SystemAppletId::Gift = { 0x010000000000101Aul }; + inline constexpr const SystemAppletId SystemAppletId::DummyShop = { 0x010000000000101Bul }; + inline constexpr const SystemAppletId SystemAppletId::UserMigration = { 0x010000000000101Cul }; + inline constexpr const SystemAppletId SystemAppletId::Encounter = { 0x010000000000101Dul }; + + inline constexpr const SystemAppletId SystemAppletId::Story = { 0x0100000000001020ul }; + + inline constexpr const SystemAppletId SystemAppletId::End = { 0x0100000000001FFFul }; + + inline constexpr bool IsSystemAppletId(const ProgramId &program_id) { + return SystemAppletId::Start <= program_id && program_id <= SystemAppletId::End; + } + + inline constexpr bool IsSystemAppletId(const SystemAppletId &) { + return true; + } + + struct SystemDebugAppletId { + u64 value; + + constexpr operator ProgramId() const { + return { this->value }; + } + + constexpr inline bool operator==(const SystemDebugAppletId &) const = default; + constexpr inline bool operator!=(const SystemDebugAppletId &) const = default; + + static const SystemDebugAppletId Start; + + static const SystemDebugAppletId SnapShotDumper; + + static const SystemDebugAppletId End; + }; + + inline constexpr const SystemDebugAppletId SystemDebugAppletId::Start = { 0x0100000000002000ul }; + + inline constexpr const SystemDebugAppletId SystemDebugAppletId::SnapShotDumper = { 0x0100000000002071ul }; + + inline constexpr const SystemDebugAppletId SystemDebugAppletId::End = { 0x0100000000002FFFul }; + + inline constexpr bool IsSystemDebugAppletId(const ProgramId &program_id) { + return SystemDebugAppletId::Start <= program_id && program_id <= SystemDebugAppletId::End; + } + + inline constexpr bool IsSystemDebugAppletId(const SystemDebugAppletId &) { + return true; + } + + struct LibraryAppletId { + u64 value; + + constexpr operator SystemAppletId() const { + return { this->value }; + } + + constexpr operator ProgramId() const { + return static_cast<SystemAppletId>(*this); + } + + constexpr inline bool operator==(const LibraryAppletId &) const = default; + constexpr inline bool operator!=(const LibraryAppletId &) const = default; + + static const LibraryAppletId Auth; + static const LibraryAppletId Controller; + static const LibraryAppletId Error; + static const LibraryAppletId PlayerSelect; + static const LibraryAppletId Swkbd; + static const LibraryAppletId Web; + static const LibraryAppletId Shop; + static const LibraryAppletId PhotoViewer; + static const LibraryAppletId OfflineWeb; + static const LibraryAppletId LoginShare; + static const LibraryAppletId WifiWebAuth; + static const LibraryAppletId MyPage; + + }; + + inline constexpr const LibraryAppletId LibraryAppletId::Auth = { SystemAppletId::Auth.value }; + inline constexpr const LibraryAppletId LibraryAppletId::Controller = { SystemAppletId::Controller.value }; + inline constexpr const LibraryAppletId LibraryAppletId::Error = { SystemAppletId::Error.value }; + inline constexpr const LibraryAppletId LibraryAppletId::PlayerSelect = { SystemAppletId::PlayerSelect.value }; + inline constexpr const LibraryAppletId LibraryAppletId::Swkbd = { SystemAppletId::Swkbd.value }; + inline constexpr const LibraryAppletId LibraryAppletId::Web = { SystemAppletId::Web.value }; + inline constexpr const LibraryAppletId LibraryAppletId::Shop = { SystemAppletId::Shop.value }; + inline constexpr const LibraryAppletId LibraryAppletId::PhotoViewer = { SystemAppletId::PhotoViewer.value }; + inline constexpr const LibraryAppletId LibraryAppletId::OfflineWeb = { SystemAppletId::OfflineWeb.value }; + inline constexpr const LibraryAppletId LibraryAppletId::LoginShare = { SystemAppletId::LoginShare.value }; + inline constexpr const LibraryAppletId LibraryAppletId::WifiWebAuth = { SystemAppletId::WifiWebAuth.value }; + inline constexpr const LibraryAppletId LibraryAppletId::MyPage = { SystemAppletId::MyPage.value }; + + inline constexpr bool IsLibraryAppletId(const ProgramId &id) { + return id == LibraryAppletId::Auth || + id == LibraryAppletId::Controller || + id == LibraryAppletId::Error || + id == LibraryAppletId::PlayerSelect || + id == LibraryAppletId::Swkbd || + id == LibraryAppletId::Web || + id == LibraryAppletId::Shop || + id == LibraryAppletId::PhotoViewer || + id == LibraryAppletId::OfflineWeb || + id == LibraryAppletId::LoginShare || + id == LibraryAppletId::WifiWebAuth || + id == LibraryAppletId::MyPage; + } + + inline constexpr bool IsLibraryAppletId(const LibraryAppletId &) { + return true; + } + + struct WebAppletId { + u64 value; + + constexpr operator LibraryAppletId() const { + return { this->value }; + } + + constexpr operator SystemAppletId() const { + return static_cast<LibraryAppletId>(*this); + } + + constexpr operator ProgramId() const { + return static_cast<SystemAppletId>(*this); + } + + constexpr inline bool operator==(const WebAppletId &) const = default; + constexpr inline bool operator!=(const WebAppletId &) const = default; + + static const WebAppletId Web; + static const WebAppletId Shop; + static const WebAppletId OfflineWeb; + static const WebAppletId LoginShare; + static const WebAppletId WifiWebAuth; + }; + + inline constexpr const WebAppletId WebAppletId::Web = { LibraryAppletId::Web.value }; + inline constexpr const WebAppletId WebAppletId::Shop = { LibraryAppletId::Shop.value }; + inline constexpr const WebAppletId WebAppletId::OfflineWeb = { LibraryAppletId::OfflineWeb.value }; + inline constexpr const WebAppletId WebAppletId::LoginShare = { LibraryAppletId::LoginShare.value }; + inline constexpr const WebAppletId WebAppletId::WifiWebAuth = { LibraryAppletId::WifiWebAuth.value }; + + inline constexpr bool IsWebAppletId(const ProgramId &id) { + return id == WebAppletId::Web || + id == WebAppletId::Shop || + id == WebAppletId::OfflineWeb || + id == WebAppletId::LoginShare || + id == WebAppletId::WifiWebAuth; + } + + inline constexpr bool IsWebAppletId(const WebAppletId &) { + return true; + } + + struct SystemApplicationId { + u64 value; + + constexpr operator ProgramId() const { + return { this->value }; + } + + constexpr inline bool operator==(const SystemApplicationId &) const = default; + constexpr inline bool operator!=(const SystemApplicationId &) const = default; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_system_update_task_apply_info.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_system_update_task_apply_info.hpp new file mode 100644 index 00000000..1488ac07 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ncm/ncm_system_update_task_apply_info.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::ncm { + + enum class SystemUpdateTaskApplyInfo : u8 { + Unknown = 0, + RequireReboot = 1, + RequireNoReboot = 2, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nim.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nim.hpp new file mode 100644 index 00000000..d00e8d33 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nim.hpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/nim/nim_network_install_manager_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nim/nim_network_install_manager_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nim/nim_network_install_manager_api.hpp new file mode 100644 index 00000000..407f1f2b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nim/nim_network_install_manager_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/nim/nim_system_update_task_id.hpp> + +namespace ams::nim { + + /* Management. */ + void InitializeForNetworkInstallManager(); + void FinalizeForNetworkInstallManager(); + + /* Service API. */ + Result DestroySystemUpdateTask(const SystemUpdateTaskId &id); + s32 ListSystemUpdateTask(SystemUpdateTaskId *out_list, size_t out_list_size); + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nim/nim_system_update_task_id.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nim/nim_system_update_task_id.hpp new file mode 100644 index 00000000..d7f868e5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nim/nim_system_update_task_id.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/nim/nim_task_id_common.hpp> + +namespace ams::nim { + + AMS_NIM_DEFINE_TASK_ID(SystemUpdateTaskId, 8); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nim/nim_task_id_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nim/nim_task_id_common.hpp new file mode 100644 index 00000000..fed35f6a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nim/nim_task_id_common.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/nim/nim_task_id_common.hpp> + +namespace ams::nim { + + #define AMS_NIM_DEFINE_TASK_ID(ID, ALIGN) \ + struct alignas(ALIGN) ID { \ + util::Uuid uuid; \ + \ + ALWAYS_INLINE bool IsValid() const { \ + return this->uuid != util::InvalidUuid; \ + } \ + }; \ + \ + static_assert(alignof(ID) == ALIGN); \ + \ + ALWAYS_INLINE bool operator==(const ID &lhs, const ID &rhs) { \ + return lhs.uuid == rhs.uuid; \ + } \ + \ + ALWAYS_INLINE bool operator!=(const ID &lhs, const ID &rhs) { \ + return lhs.uuid != rhs.uuid; \ + } \ + \ + constexpr inline ID Invalid##ID = { util::InvalidUuid } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ns.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ns.hpp new file mode 100644 index 00000000..bc2284b8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ns.hpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/ns/impl/ns_i_async.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ns/impl/ns_i_async.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ns/impl/ns_i_async.hpp new file mode 100644 index 00000000..a6f5b9e4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ns/impl/ns_i_async.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/err/err_error_context.hpp> +#include <stratosphere/sf.hpp> + +#define AMS_NS_I_ASYNC_RESULT_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, Get, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, Cancel, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, GetErrorContext, (::ams::sf::Out<::ams::err::ErrorContext> out), (out)) + +AMS_SF_DEFINE_INTERFACE(ams::ns::impl, IAsyncResult, AMS_NS_I_ASYNC_RESULT_INTERFACE_INFO, 0x66E1ADBD) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nsd.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nsd.hpp new file mode 100644 index 00000000..9db24685 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nsd.hpp @@ -0,0 +1,18 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/nsd/nsd_types.hpp> +#include <stratosphere/nsd/impl/device/nsd_device.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nsd/impl/device/nsd_device.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nsd/impl/device/nsd_device.hpp new file mode 100644 index 00000000..025c9a3f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nsd/impl/device/nsd_device.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/nsd/nsd_types.hpp> + +namespace ams::nsd::impl::device { + + const EnvironmentIdentifier &GetEnvironmentIdentifierFromSettings(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nsd/nsd_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nsd/nsd_types.hpp new file mode 100644 index 00000000..eee90567 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/nsd/nsd_types.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::nsd { + + struct EnvironmentIdentifier { + static constexpr size_t Size = 8; + char value[Size]; + + constexpr friend bool operator==(const EnvironmentIdentifier &lhs, const EnvironmentIdentifier &rhs) { + return util::Strncmp(lhs.value, rhs.value, Size) == 0; + } + + constexpr friend bool operator!=(const EnvironmentIdentifier &lhs, const EnvironmentIdentifier &rhs) { + return !(lhs == rhs); + } + }; + + constexpr inline const EnvironmentIdentifier EnvironmentIdentifierOfProductDevice = {"lp1"}; + constexpr inline const EnvironmentIdentifier EnvironmentIdentifierOfNotProductDevice = {"dd1"}; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os.hpp new file mode 100644 index 00000000..340ea926 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os.hpp @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/os/os_common_types.hpp> +#include <stratosphere/os/os_tick.hpp> +#include <stratosphere/os/os_memory_common.hpp> +#include <stratosphere/os/os_memory_fence.hpp> +#include <stratosphere/os/os_memory_permission.hpp> +#include <stratosphere/os/os_memory_attribute.hpp> +#include <stratosphere/os/os_memory_heap.hpp> +#include <stratosphere/os/os_virtual_address_memory.hpp> +#include <stratosphere/os/os_native_handle.hpp> +#include <stratosphere/os/os_process_handle_api.hpp> +#include <stratosphere/os/os_process_memory_api.hpp> +#include <stratosphere/os/os_process_code_memory_api.hpp> +#include <stratosphere/os/os_insecure_memory_api.hpp> +#include <stratosphere/os/os_unsafe_memory_api.hpp> +#include <stratosphere/os/os_random.hpp> +#include <stratosphere/os/os_mutex.hpp> +#include <stratosphere/os/os_condition_variable.hpp> +#include <stratosphere/os/os_sdk_mutex.hpp> +#include <stratosphere/os/os_sdk_recursive_mutex.hpp> +#include <stratosphere/os/os_sdk_condition_variable.hpp> +#include <stratosphere/os/os_busy_mutex.hpp> +#include <stratosphere/os/os_rw_busy_mutex.hpp> +#include <stratosphere/os/os_rw_lock.hpp> +#include <stratosphere/os/os_shared_memory.hpp> +#include <stratosphere/os/os_transfer_memory.hpp> +#include <stratosphere/os/os_semaphore.hpp> +#include <stratosphere/os/os_event.hpp> +#include <stratosphere/os/os_system_event.hpp> +#include <stratosphere/os/os_interrupt_event.hpp> +#include <stratosphere/os/os_timer_event.hpp> +#include <stratosphere/os/os_thread_local_storage.hpp> +#include <stratosphere/os/os_sdk_thread_local_storage.hpp> +#include <stratosphere/os/os_sdk_reply_and_receive.hpp> +#include <stratosphere/os/os_thread.hpp> +#include <stratosphere/os/os_sdk_thread_api.hpp> +#include <stratosphere/os/os_sdk_thread_info.hpp> +#include <stratosphere/os/os_message_queue.hpp> +#include <stratosphere/os/os_light_event.hpp> +#include <stratosphere/os/os_light_message_queue.hpp> +#include <stratosphere/os/os_light_semaphore.hpp> +#include <stratosphere/os/os_barrier.hpp> +#include <stratosphere/os/os_io_region.hpp> +#include <stratosphere/os/os_multiple_wait.hpp> +#include <stratosphere/os/os_argument.hpp> +#include <stratosphere/os/os_cache.hpp> +#include <stratosphere/os/os_debug.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_busy_mutex.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_busy_mutex.hpp new file mode 100644 index 00000000..7e827eb7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_busy_mutex.hpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) + #include <stratosphere/os/impl/os_internal_busy_mutex_impl.os.horizon.hpp> +#elif defined(ATMOSPHERE_OS_WINDOWS) || defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS) + #include <stratosphere/os/impl/os_internal_busy_mutex_impl.os.generic.hpp> +#else + #error "Unknown OS for ams::os::impl::InternalBusyMutexImpl" +#endif + +namespace ams::os::impl { + + class InternalBusyMutex { + private: + InternalBusyMutexImpl m_impl; + public: + constexpr InternalBusyMutex() : m_impl() { /* ... */ } + + ALWAYS_INLINE void Initialize() { m_impl.Initialize(); } + ALWAYS_INLINE void Finalize() { m_impl.Finalize(); } + + ALWAYS_INLINE bool IsLocked() const { return m_impl.IsLocked(); } + + ALWAYS_INLINE void Lock() { return m_impl.Lock(); } + ALWAYS_INLINE bool TryLock() { return m_impl.TryLock(); } + ALWAYS_INLINE void Unlock() { return m_impl.Unlock(); } + + ALWAYS_INLINE void lock() { return this->Lock(); } + ALWAYS_INLINE bool try_lock() { return this->TryLock(); } + ALWAYS_INLINE void unlock() { return this->Unlock(); } + }; + + using InternalBusyMutexStorage = util::TypedStorage<InternalBusyMutex>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_busy_mutex_impl.os.generic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_busy_mutex_impl.os.generic.hpp new file mode 100644 index 00000000..3355eb36 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_busy_mutex_impl.os.generic.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::os::impl { + + + class InternalBusyMutexImpl { + private: + std::atomic<bool> m_value{false}; + u8 m_padding[3]{}; + public: + constexpr InternalBusyMutexImpl() = default; + + ALWAYS_INLINE void Initialize() { m_value.store(false, std::memory_order_relaxed); } + + ALWAYS_INLINE void Finalize() { /* ... */ } + + ALWAYS_INLINE bool IsLocked() const { return m_value.load(std::memory_order_acquire); } + + ALWAYS_INLINE bool TryLock() { + bool expected = false; + return m_value.compare_exchange_weak(expected, true, std::memory_order_acquire, std::memory_order_acquire); + + } + + ALWAYS_INLINE void Lock() { + while (!this->TryLock()) { + #if defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + _mm_pause(); + #elif defined(ATMOSPHERE_ARCH_ARM64) || defined(ATMOSPHERE_ARCH_ARM) + __asm__ __volatile__("yield" ::: "memory"); + #else + #error "InternalBusyMutex requires yield intrinsics" + #endif + } + } + + ALWAYS_INLINE void Unlock() { + m_value.store(false, std::memory_order_release); + } + }; + static_assert(sizeof(InternalBusyMutexImpl) == sizeof(u32)); + + #define AMS_OS_INTERNAL_BUSY_MUTEX_IMPL_CONSTANT_INITIALIZE_ARRAY_VALUES 0 + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_busy_mutex_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_busy_mutex_impl.os.horizon.hpp new file mode 100644 index 00000000..b7caf76f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_busy_mutex_impl.os.horizon.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::os::impl { + + class InternalBusyMutexImpl { + private: + u32 m_value; + public: + constexpr InternalBusyMutexImpl() : m_value(0) { /* ... */ } + + constexpr void Initialize() { m_value = 0; } + constexpr void Finalize() { /* ... */ } + + constexpr bool IsLocked() const { return m_value != 0; } + + void Lock(); + bool TryLock(); + void Unlock(); + }; + + #define AMS_OS_INTERNAL_BUSY_MUTEX_IMPL_CONSTANT_INITIALIZE_ARRAY_VALUES 0 + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_condition_variable.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_condition_variable.hpp new file mode 100644 index 00000000..5d126e0e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_condition_variable.hpp @@ -0,0 +1,66 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_condition_variable_common.hpp> + +#if defined(AMS_OS_IMPL_USE_PTHREADS) + #include <stratosphere/os/impl/os_internal_condition_variable_impl.pthread.hpp> +#elif defined(ATMOSPHERE_OS_HORIZON) + #include <stratosphere/os/impl/os_internal_condition_variable_impl.os.horizon.hpp> +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include <stratosphere/os/impl/os_internal_condition_variable_impl.os.windows.hpp> +#else + #error "Unknown OS for ams::os::impl::InternalConditionVariableImpl" +#endif + +namespace ams::os::impl { + + class InternalConditionVariable { + private: + InternalConditionVariableImpl m_impl; + public: + constexpr InternalConditionVariable() : m_impl() { /* ... */ } + + void Initialize() { + m_impl.Initialize(); + } + + void Finalize() { + m_impl.Finalize(); + } + + void Signal() { + m_impl.Signal(); + } + + void Broadcast() { + m_impl.Broadcast(); + } + + void Wait(InternalCriticalSection *cs) { + m_impl.Wait(cs); + } + + ConditionVariableStatus TimedWait(InternalCriticalSection *cs, const TimeoutHelper &timeout_helper) { + return m_impl.TimedWait(cs, timeout_helper); + } + }; + + using InternalConditionVariableStorage = util::TypedStorage<InternalConditionVariable>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_condition_variable_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_condition_variable_impl.os.horizon.hpp new file mode 100644 index 00000000..3adc45f6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_condition_variable_impl.os.horizon.hpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_condition_variable_common.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> + +namespace ams::os::impl { + + class TimeoutHelper; + + class InternalConditionVariableImpl { + private: + u32 m_value; + public: + constexpr InternalConditionVariableImpl() : m_value(0) { /* ... */ } + + constexpr void Initialize() { + m_value = 0; + } + constexpr void Finalize() { /* ... */ } + + void Signal(); + void Broadcast(); + + void Wait(InternalCriticalSection *cs); + ConditionVariableStatus TimedWait(InternalCriticalSection *cs, const TimeoutHelper &timeout_helper); + }; + + using InternalConditionVariableStorageTypeForConstantInitialize = u32; + + #define AMS_OS_INTERNAL_CONDITION_VARIABLE_IMPL_CONSTANT_INITIALIZER {0} + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_condition_variable_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_condition_variable_impl.os.windows.hpp new file mode 100644 index 00000000..f0a5e867 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_condition_variable_impl.os.windows.hpp @@ -0,0 +1,67 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_condition_variable_common.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> + +namespace ams::os::impl { + + class TimeoutHelper; + + struct WindowsConditionVariable; + + using WindowsConditionVariableStorage = util::TypedStorage<WindowsConditionVariable, sizeof(void *), alignof(void *)>; + + #if defined(ATMOSPHERE_ARCH_X64) + #define AMS_OS_WINDOWS_CONDITION_VARIABLE_CONSTANT_INITIALIZE_ARRAY_VALUES 0, 0 + #elif defined(ATMOSPHERE_ARCH_X86) + #define AMS_OS_WINDOWS_CONDITION_VARIABLE_CONSTANT_INITIALIZE_ARRAY_VALUES 0 + #else + #error "Unknown architecture for WindowsConditionVariable initializer" + #endif + + class InternalConditionVariableImpl { + private: + u32 m_value; + public: + union { + s32 _arr[sizeof(WindowsConditionVariableStorage) / sizeof(s32)]; + WindowsConditionVariableStorage m_windows_cv_storage; + }; + constexpr InternalConditionVariableImpl() : _arr{AMS_OS_WINDOWS_CONDITION_VARIABLE_CONSTANT_INITIALIZE_ARRAY_VALUES} { /* ... */ } + constexpr ~InternalConditionVariableImpl() { + if (!std::is_constant_evaluated()) { + this->Finalize(); + } + } + + void Initialize(); + void Finalize(); + + void Signal(); + void Broadcast(); + + void Wait(InternalCriticalSection *cs); + ConditionVariableStatus TimedWait(InternalCriticalSection *cs, const TimeoutHelper &timeout_helper); + }; + + struct InternalConditionVariableStorageTypeForConstantInitialize { s32 _arr[sizeof(WindowsConditionVariableStorage) / sizeof(s32)]; }; + + #define AMS_OS_INTERNAL_CONDITION_VARIABLE_IMPL_CONSTANT_INITIALIZER { AMS_OS_WINDOWS_CONDITION_VARIABLE_CONSTANT_INITIALIZE_ARRAY_VALUES } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_condition_variable_impl.pthread.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_condition_variable_impl.pthread.hpp new file mode 100644 index 00000000..a791d7de --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_condition_variable_impl.pthread.hpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_condition_variable_common.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> + +namespace ams::os::impl { + + class TimeoutHelper; + + class InternalConditionVariableImpl { + private: + pthread_cond_t m_pthread_cond = PTHREAD_COND_INITIALIZER; + public: + constexpr InternalConditionVariableImpl() = default; + + constexpr ~InternalConditionVariableImpl() { + if (!std::is_constant_evaluated()) { + this->Finalize(); + } + } + + void Initialize(); + void Finalize(); + + void Signal(); + void Broadcast(); + + void Wait(InternalCriticalSection *cs); + ConditionVariableStatus TimedWait(InternalCriticalSection *cs, const TimeoutHelper &timeout_helper); + }; + + using InternalConditionVariableStorageTypeForConstantInitialize = pthread_cond_t; + + #define AMS_OS_INTERNAL_CONDITION_VARIABLE_IMPL_CONSTANT_INITIALIZER ._storage_for_constant_initialize = PTHREAD_COND_INITIALIZER + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_critical_section.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_critical_section.hpp new file mode 100644 index 00000000..6335f3a8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_critical_section.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +#if defined(AMS_OS_IMPL_USE_PTHREADS) + #include <stratosphere/os/impl/os_internal_critical_section_impl.pthread.hpp> +#elif defined(ATMOSPHERE_OS_HORIZON) + #include <stratosphere/os/impl/os_internal_critical_section_impl.os.horizon.hpp> +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include <stratosphere/os/impl/os_internal_critical_section_impl.os.windows.hpp> +#else + #error "Unknown OS for ams::os::impl::InternalCriticalSectionImpl" +#endif + +namespace ams::os::impl { + + class InternalCriticalSection { + private: + InternalCriticalSectionImpl m_impl; + public: + constexpr InternalCriticalSection() : m_impl() { /* ... */ } + + void Initialize() { m_impl.Initialize(); } + void Finalize() { m_impl.Finalize(); } + + void Enter() { return m_impl.Enter(); } + bool TryEnter() { return m_impl.TryEnter(); } + void Leave() { return m_impl.Leave(); } + + #if defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + bool IsLockedByCurrentThread() const { return m_impl.IsLockedByCurrentThread(); } + #endif + + ALWAYS_INLINE void Lock() { return this->Enter(); } + ALWAYS_INLINE bool TryLock() { return this->TryEnter(); } + ALWAYS_INLINE void Unlock() { return this->Leave(); } + + ALWAYS_INLINE void lock() { return this->Lock(); } + ALWAYS_INLINE bool try_lock() { return this->TryLock(); } + ALWAYS_INLINE void unlock() { return this->Unlock(); } + + InternalCriticalSectionImpl *Get() { + return std::addressof(m_impl); + } + + const InternalCriticalSectionImpl *Get() const { + return std::addressof(m_impl); + } + }; + + using InternalCriticalSectionStorage = util::TypedStorage<InternalCriticalSection>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_critical_section_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_critical_section_impl.os.horizon.hpp new file mode 100644 index 00000000..81e99767 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_critical_section_impl.os.horizon.hpp @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::os::impl { + + #if defined(ATMOSPHERE_OS_HORIZON) + class ReaderWriterLockHorizonImpl; + #endif + + class InternalConditionVariableImpl; + + class InternalCriticalSectionImpl { + private: + #if defined(ATMOSPHERE_OS_HORIZON) + friend class ReaderWriterLockHorizonImpl; + #endif + + friend class InternalConditionVariableImpl; + private: + u32 m_thread_handle; + public: + constexpr InternalCriticalSectionImpl() : m_thread_handle(svc::InvalidHandle) { /* ... */ } + + constexpr void Initialize() { m_thread_handle = svc::InvalidHandle; } + constexpr void Finalize() { /* ... */ } + + void Enter(); + bool TryEnter(); + void Leave(); + + bool IsLockedByCurrentThread() const; + + ALWAYS_INLINE void Lock() { return this->Enter(); } + ALWAYS_INLINE bool TryLock() { return this->TryEnter(); } + ALWAYS_INLINE void Unlock() { return this->Leave(); } + + ALWAYS_INLINE void lock() { return this->Lock(); } + ALWAYS_INLINE bool try_lock() { return this->TryLock(); } + ALWAYS_INLINE void unlock() { return this->Unlock(); } + }; + + using InternalCriticalSectionStorageTypeForConstantInitialize = u32; + + #define AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CONSTANT_INITIALIZER {0} + + #define AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_critical_section_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_critical_section_impl.os.windows.hpp new file mode 100644 index 00000000..221b99fb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_critical_section_impl.os.windows.hpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::os::impl { + + class InternalConditionVariableImpl; + + struct WindowsCriticalSection; + + using WindowsCriticalSectionStorage = util::TypedStorage<WindowsCriticalSection, 8 + 4 * sizeof(void *), alignof(void *)>; + + #if defined(ATMOSPHERE_ARCH_X64) + #define AMS_OS_WINDOWS_CRITICAL_SECTION_CONSTANT_INITIALIZE_ARRAY_VALUES -1, -1, -1, 0, 0, 0, 0, 0, 0, 0 + #elif defined(ATMOSPHERE_ARCH_X86) + #define AMS_OS_WINDOWS_CRITICAL_SECTION_CONSTANT_INITIALIZE_ARRAY_VALUES -1, -1, 0, 0, 0, 0 + #else + #error "Unknown architecture for WindowsCriticalSection initializer" + #endif + + class InternalCriticalSectionImpl { + private: + friend class InternalConditionVariableImpl; + private: + union { + s32 _arr[sizeof(WindowsCriticalSectionStorage) / sizeof(s32)]; + WindowsCriticalSectionStorage m_windows_critical_section_storage; + }; + public: + constexpr InternalCriticalSectionImpl() : _arr{AMS_OS_WINDOWS_CRITICAL_SECTION_CONSTANT_INITIALIZE_ARRAY_VALUES} { /* ... */ } + constexpr ~InternalCriticalSectionImpl() { + if (!std::is_constant_evaluated()) { + this->Finalize(); + } + } + + void Initialize(); + void Finalize(); + + void Enter(); + bool TryEnter(); + void Leave(); + + bool IsLockedByCurrentThread() const; + + ALWAYS_INLINE void Lock() { return this->Enter(); } + ALWAYS_INLINE bool TryLock() { return this->TryEnter(); } + ALWAYS_INLINE void Unlock() { return this->Leave(); } + + ALWAYS_INLINE void lock() { return this->Lock(); } + ALWAYS_INLINE bool try_lock() { return this->TryLock(); } + ALWAYS_INLINE void unlock() { return this->Unlock(); } + }; + + struct InternalCriticalSectionStorageTypeForConstantInitialize { s32 _arr[sizeof(WindowsCriticalSectionStorage) / sizeof(s32)]; }; + + #define AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CONSTANT_INITIALIZER { AMS_OS_WINDOWS_CRITICAL_SECTION_CONSTANT_INITIALIZE_ARRAY_VALUES } + + #define AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_critical_section_impl.pthread.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_critical_section_impl.pthread.hpp new file mode 100644 index 00000000..61f91810 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_critical_section_impl.pthread.hpp @@ -0,0 +1,75 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_common_types.hpp> + +namespace ams::os::impl { + + class InternalConditionVariableImpl; + + /* NOTE: macOS (and possibly other targets) do not provide adaptive mutex. */ + #if defined(PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP) + #define AMS_OS_IMPL_PTHREAD_MUTEX_INITIALIZER PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP + #else + #define AMS_OS_IMPL_PTHREAD_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER + #endif + + #if defined(ATMOSPHERE_OS_LINUX) + #define AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD + #else + //#define AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD + #endif + + class InternalCriticalSectionImpl { + private: + friend class InternalConditionVariableImpl; + private: + pthread_mutex_t m_pthread_mutex = AMS_OS_IMPL_PTHREAD_MUTEX_INITIALIZER; + public: + constexpr InternalCriticalSectionImpl() = default; + constexpr ~InternalCriticalSectionImpl() { + if (!std::is_constant_evaluated()) { + this->Finalize(); + } + } + + void Initialize(); + void Finalize(); + + void Enter(); + bool TryEnter(); + void Leave(); + + #if defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + bool IsLockedByCurrentThread() const; + #endif + + ALWAYS_INLINE void Lock() { return this->Enter(); } + ALWAYS_INLINE bool TryLock() { return this->TryEnter(); } + ALWAYS_INLINE void Unlock() { return this->Leave(); } + + ALWAYS_INLINE void lock() { return this->Lock(); } + ALWAYS_INLINE bool try_lock() { return this->TryLock(); } + ALWAYS_INLINE void unlock() { return this->Unlock(); } + }; + + using InternalCriticalSectionStorageTypeForConstantInitialize = pthread_mutex_t; + + #define AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CONSTANT_INITIALIZER ._storage_for_constant_initialize = AMS_OS_IMPL_PTHREAD_MUTEX_INITIALIZER + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_light_event.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_light_event.hpp new file mode 100644 index 00000000..c0d2e0d4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_light_event.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) + #include <stratosphere/os/impl/os_internal_light_event_impl.os.horizon.hpp> +#elif defined(ATMOSPHERE_OS_WINDOWS) || defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS) + #include <stratosphere/os/impl/os_internal_light_event_impl.os.generic.hpp> +#else + #error "Unknown OS for ams::os::impl::InternalLightEventImpl" +#endif + +namespace ams::os::impl { + + class InternalLightEvent { + private: + InternalLightEventImpl m_impl; + public: + explicit InternalLightEvent(bool signaled) : m_impl(signaled) { /* ... */ } + + ALWAYS_INLINE void SignalWithAutoClear() { return m_impl.SignalWithAutoClear(); } + ALWAYS_INLINE void SignalWithManualClear() { return m_impl.SignalWithManualClear(); } + + ALWAYS_INLINE void Clear() { return m_impl.Clear(); } + + ALWAYS_INLINE void WaitWithAutoClear() { return m_impl.WaitWithAutoClear(); } + ALWAYS_INLINE void WaitWithManualClear() { return m_impl.WaitWithManualClear(); } + + ALWAYS_INLINE bool TryWaitWithAutoClear() { return m_impl.TryWaitWithAutoClear(); } + ALWAYS_INLINE bool TryWaitWithManualClear() { return m_impl.TryWaitWithManualClear(); } + + ALWAYS_INLINE bool TimedWaitWithAutoClear(const TimeoutHelper &timeout_helper) { return m_impl.TimedWaitWithAutoClear(timeout_helper); } + ALWAYS_INLINE bool TimedWaitWithManualClear(const TimeoutHelper &timeout_helper) { return m_impl.TimedWaitWithManualClear(timeout_helper); } + }; + + using InternalLightEventStorage = util::TypedStorage<InternalLightEvent>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_light_event_impl.os.generic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_light_event_impl.os.generic.hpp new file mode 100644 index 00000000..ff57c51c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_light_event_impl.os.generic.hpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> +#include <stratosphere/os/impl/os_internal_condition_variable.hpp> + +namespace ams::os::impl { + + class TimeoutHelper; + + class InternalLightEventImpl { + private: + u16 m_counter_low; + u8 m_counter_high; + std::atomic<u8> m_signal_state; + InternalCriticalSection m_cs; + InternalConditionVariable m_cv; + public: + explicit InternalLightEventImpl(bool signaled) { this->Initialize(signaled); } + ~InternalLightEventImpl() { this->Finalize(); } + + void Initialize(bool signaled); + void Finalize(); + + void SignalWithAutoClear(); + void SignalWithManualClear(); + + void Clear(); + + void WaitWithAutoClear(); + void WaitWithManualClear(); + + bool TryWaitWithAutoClear(); + bool TryWaitWithManualClear(); + + bool TimedWaitWithAutoClear(const TimeoutHelper &timeout_helper); + bool TimedWaitWithManualClear(const TimeoutHelper &timeout_helper); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_light_event_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_light_event_impl.os.horizon.hpp new file mode 100644 index 00000000..99bcca5e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_light_event_impl.os.horizon.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::os::impl { + + class TimeoutHelper; + + class InternalLightEventImpl { + private: + std::atomic<s32> m_state; + u32 m_padding; + public: + explicit InternalLightEventImpl(bool signaled) { this->Initialize(signaled); } + ~InternalLightEventImpl() { this->Finalize(); } + + void Initialize(bool signaled); + void Finalize(); + + void SignalWithAutoClear(); + void SignalWithManualClear(); + + void Clear(); + + void WaitWithAutoClear(); + void WaitWithManualClear(); + + bool TryWaitWithAutoClear(); + bool TryWaitWithManualClear(); + + bool TimedWaitWithAutoClear(const TimeoutHelper &timeout_helper); + bool TimedWaitWithManualClear(const TimeoutHelper &timeout_helper); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_rw_busy_mutex.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_rw_busy_mutex.hpp new file mode 100644 index 00000000..6ee8fb8e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_rw_busy_mutex.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) + #include <stratosphere/os/impl/os_internal_rw_busy_mutex_impl.os.horizon.hpp> +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include <stratosphere/os/impl/os_internal_rw_busy_mutex_impl.os.windows.hpp> +#elif defined(ATMOSPHERE_OS_LINUX) + #include <stratosphere/os/impl/os_internal_rw_busy_mutex_impl.os.linux.hpp> +#elif defined(ATMOSPHERE_OS_MACOS) + #include <stratosphere/os/impl/os_internal_rw_busy_mutex_impl.os.macos.hpp> +#else + #error "Unknown OS for ams::os::impl::InternalReaderWriterBusyMutexImpl" +#endif + +namespace ams::os::impl { + + class InternalReaderWriterBusyMutex { + private: + InternalReaderWriterBusyMutexImpl m_impl; + public: + constexpr InternalReaderWriterBusyMutex() : m_impl() { /* ... */ } + + ALWAYS_INLINE void AcquireReadLock() { return m_impl.AcquireReadLock(); } + ALWAYS_INLINE void ReleaseReadLock() { return m_impl.ReleaseReadLock(); } + + ALWAYS_INLINE void AcquireWriteLock() { return m_impl.AcquireWriteLock(); } + ALWAYS_INLINE void ReleaseWriteLock() { return m_impl.ReleaseWriteLock(); } + }; + + using InternalReaderWriterBusyMutexStorage = util::TypedStorage<InternalReaderWriterBusyMutex>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_rw_busy_mutex_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_rw_busy_mutex_impl.os.horizon.hpp new file mode 100644 index 00000000..e8895f4c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_rw_busy_mutex_impl.os.horizon.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_rw_busy_mutex_value.hpp> + +namespace ams::os::impl { + + class InternalReaderWriterBusyMutexImpl { + private: + u32 m_value; + public: + constexpr InternalReaderWriterBusyMutexImpl() : m_value(0) { /* ... */ } + + constexpr void Initialize() { m_value = 0; } + + void AcquireReadLock(); + void ReleaseReadLock(); + + void AcquireWriteLock(); + void ReleaseWriteLock(); + }; + + #define AMS_OS_INTERNAL_READER_WRITER_BUSY_MUTEX_IMPL_CONSTANT_INITIALIZER {0} + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_rw_busy_mutex_impl.os.linux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_rw_busy_mutex_impl.os.linux.hpp new file mode 100644 index 00000000..7d3d6221 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_rw_busy_mutex_impl.os.linux.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_rw_busy_mutex_value.hpp> + +namespace ams::os::impl { + + /* NOTE: The current implementation is based on load/linked store conditional, we should figure out how to do this on x64. */ + + class InternalReaderWriterBusyMutexImpl { + private: + u32 m_value; + public: + constexpr InternalReaderWriterBusyMutexImpl() : m_value(0) { if (!std::is_constant_evaluated()) { AMS_ABORT("TODO: Linux InternalReaderWriterBusyMutexImpl"); } } + + constexpr void Initialize() { m_value = 0; if (!std::is_constant_evaluated()) { AMS_ABORT("TODO: Linux InternalReaderWriterBusyMutexImpl"); } } + + void AcquireReadLock() { AMS_ABORT("TODO: Linux InternalReaderWriterBusyMutexImpl"); } + void ReleaseReadLock() { AMS_ABORT("TODO: Linux InternalReaderWriterBusyMutexImpl"); } + + void AcquireWriteLock() { AMS_ABORT("TODO: Linux InternalReaderWriterBusyMutexImpl"); } + void ReleaseWriteLock() { AMS_ABORT("TODO: Linux InternalReaderWriterBusyMutexImpl"); } + }; + + #define AMS_OS_INTERNAL_READER_WRITER_BUSY_MUTEX_IMPL_CONSTANT_INITIALIZER {0} + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_rw_busy_mutex_impl.os.macos.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_rw_busy_mutex_impl.os.macos.hpp new file mode 100644 index 00000000..a10b99a6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_rw_busy_mutex_impl.os.macos.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_rw_busy_mutex_value.hpp> + +namespace ams::os::impl { + + /* NOTE: The current implementation is based on load/linked store conditional, we should figure out how to do this on x64. */ + + class InternalReaderWriterBusyMutexImpl { + private: + u32 m_value; + public: + constexpr InternalReaderWriterBusyMutexImpl() : m_value(0) { if (!std::is_constant_evaluated()) { AMS_ABORT("TODO: macOS InternalReaderWriterBusyMutexImpl"); } } + + constexpr void Initialize() { m_value = 0; if (!std::is_constant_evaluated()) { AMS_ABORT("TODO: macOS InternalReaderWriterBusyMutexImpl"); } } + + void AcquireReadLock() { AMS_ABORT("TODO: macOS InternalReaderWriterBusyMutexImpl"); } + void ReleaseReadLock() { AMS_ABORT("TODO: macOS InternalReaderWriterBusyMutexImpl"); } + + void AcquireWriteLock() { AMS_ABORT("TODO: macOS InternalReaderWriterBusyMutexImpl"); } + void ReleaseWriteLock() { AMS_ABORT("TODO: macOS InternalReaderWriterBusyMutexImpl"); } + }; + + #define AMS_OS_INTERNAL_READER_WRITER_BUSY_MUTEX_IMPL_CONSTANT_INITIALIZER {0} + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_rw_busy_mutex_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_rw_busy_mutex_impl.os.windows.hpp new file mode 100644 index 00000000..6882f8d9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_rw_busy_mutex_impl.os.windows.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_rw_busy_mutex_value.hpp> + +namespace ams::os::impl { + + /* NOTE: The current implementation is based on load/linked store conditional, we should figure out how to do this on x64. */ + /* We could also consider using https://docs.microsoft.com/en-us/windows/win32/sync/slim-reader-writer--srw--locks */ + + class InternalReaderWriterBusyMutexImpl { + private: + u32 m_value; + public: + constexpr InternalReaderWriterBusyMutexImpl() : m_value(0) { if (!std::is_constant_evaluated()) { AMS_ABORT("TODO: Windows InternalReaderWriterBusyMutexImpl"); } } + + constexpr void Initialize() { m_value = 0; if (!std::is_constant_evaluated()) { AMS_ABORT("TODO: Windows InternalReaderWriterBusyMutexImpl"); } } + + void AcquireReadLock() { AMS_ABORT("TODO: Windows InternalReaderWriterBusyMutexImpl"); } + void ReleaseReadLock() { AMS_ABORT("TODO: Windows InternalReaderWriterBusyMutexImpl"); } + + void AcquireWriteLock() { AMS_ABORT("TODO: Windows InternalReaderWriterBusyMutexImpl"); } + void ReleaseWriteLock() { AMS_ABORT("TODO: Windows InternalReaderWriterBusyMutexImpl"); } + }; + + #define AMS_OS_INTERNAL_READER_WRITER_BUSY_MUTEX_IMPL_CONSTANT_INITIALIZER {0} + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_rw_busy_mutex_value.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_rw_busy_mutex_value.hpp new file mode 100644 index 00000000..526019ec --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_internal_rw_busy_mutex_value.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::os::impl { + + #if defined(ATMOSPHERE_OS_HORIZON) || defined(ATMOSPHERE_OS_WINDOWS) || defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS) + class InternalReaderWriterBusyMutexValue { + public: + static constexpr inline u8 WriterCountMax = std::numeric_limits<u8>::max(); + + static constexpr ALWAYS_INLINE u16 GetReaderCount(u32 v) { + return static_cast<u16>(v >> 0); + } + + static constexpr ALWAYS_INLINE u8 GetWriterCurrent(u32 v) { + return static_cast<u8>(v >> 16); + } + + static constexpr ALWAYS_INLINE u8 GetWriterNext(u32 v) { + return static_cast<u8>(v >> 24); + } + + static constexpr ALWAYS_INLINE u32 IncrementWriterNext(u32 v) { + return v + (1u << 24); + } + + static constexpr ALWAYS_INLINE bool IsWriteLocked(u32 v) { + return GetWriterCurrent(v) != GetWriterNext(v); + } + + static ALWAYS_INLINE u8 *GetWriterCurrentPointer(u32 *p) { + if constexpr (util::IsLittleEndian()) { + return reinterpret_cast<u8 *>(reinterpret_cast<uintptr_t>(p)) + 2; + } else { + return reinterpret_cast<u8 *>(reinterpret_cast<uintptr_t>(p)) + 1; + } + } + }; + #else + #error "Unknown OS for ams::os::impl::InternalReaderWriterBusyMutexValue" + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_memory_fence_api.os.generic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_memory_fence_api.os.generic.hpp new file mode 100644 index 00000000..072d7bfc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_memory_fence_api.os.generic.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#if defined(ATMOSPHERE_ARCH_X64) +#include <xmmintrin.h> +#include <emmintrin.h> +#endif + +namespace ams::os::impl { + + #if defined(ATMOSPHERE_ARCH_X64) + + ALWAYS_INLINE void FenceMemoryStoreStore() { __asm__ __volatile__("" ::: "memory"); _mm_sfence(); } + ALWAYS_INLINE void FenceMemoryStoreLoad() { __asm__ __volatile__("" ::: "memory"); _mm_mfence(); } + ALWAYS_INLINE void FenceMemoryStoreAny() { __asm__ __volatile__("" ::: "memory"); _mm_mfence(); } + + ALWAYS_INLINE void FenceMemoryLoadStore() { __asm__ __volatile__("" ::: "memory"); _mm_mfence(); } + ALWAYS_INLINE void FenceMemoryLoadLoad() { __asm__ __volatile__("" ::: "memory"); _mm_lfence(); } + ALWAYS_INLINE void FenceMemoryLoadAny() { __asm__ __volatile__("" ::: "memory"); _mm_mfence(); } + + ALWAYS_INLINE void FenceMemoryAnyStore() { __asm__ __volatile__("" ::: "memory"); _mm_mfence(); } + ALWAYS_INLINE void FenceMemoryAnyLoad() { __asm__ __volatile__("" ::: "memory"); _mm_mfence(); } + ALWAYS_INLINE void FenceMemoryAnyAny() {__asm__ __volatile__("" ::: "memory"); _mm_mfence(); } + + #elif defined(ATMOSPHERE_ARCH_ARM64) + + ALWAYS_INLINE void FenceMemoryStoreStore() { __asm__ __volatile__("dmb ishst" ::: "memory"); } + ALWAYS_INLINE void FenceMemoryStoreLoad() { __asm__ __volatile__("dmb ish" ::: "memory"); } + ALWAYS_INLINE void FenceMemoryStoreAny() { __asm__ __volatile__("dmb ish" ::: "memory"); } + + ALWAYS_INLINE void FenceMemoryLoadStore() { __asm__ __volatile__("dmb ishld" ::: "memory"); } + ALWAYS_INLINE void FenceMemoryLoadLoad() { __asm__ __volatile__("dmb ishld" ::: "memory"); } + ALWAYS_INLINE void FenceMemoryLoadAny() { __asm__ __volatile__("dmb ishld" ::: "memory"); } + + ALWAYS_INLINE void FenceMemoryAnyStore() { __asm__ __volatile__("dmb ish" ::: "memory"); } + ALWAYS_INLINE void FenceMemoryAnyLoad() { __asm__ __volatile__("dmb ish" ::: "memory"); } + ALWAYS_INLINE void FenceMemoryAnyAny() { __asm__ __volatile__("dmb ish" ::: "memory"); } + + #else + + #error "Unknown architecture for os::impl::FenceMemory* (Generic)" + + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_memory_fence_api.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_memory_fence_api.os.horizon.hpp new file mode 100644 index 00000000..e69260d7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/impl/os_memory_fence_api.os.horizon.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::os::impl { + + #if defined(ATMOSPHERE_ARCH_ARM64) + + ALWAYS_INLINE void FenceMemoryStoreStore() { __asm__ __volatile__("dmb ishst" ::: "memory"); } + ALWAYS_INLINE void FenceMemoryStoreLoad() { __asm__ __volatile__("dmb ish" ::: "memory"); } + ALWAYS_INLINE void FenceMemoryStoreAny() { __asm__ __volatile__("dmb ish" ::: "memory"); } + + ALWAYS_INLINE void FenceMemoryLoadStore() { __asm__ __volatile__("dmb ishld" ::: "memory"); } + ALWAYS_INLINE void FenceMemoryLoadLoad() { __asm__ __volatile__("dmb ishld" ::: "memory"); } + ALWAYS_INLINE void FenceMemoryLoadAny() { __asm__ __volatile__("dmb ishld" ::: "memory"); } + + ALWAYS_INLINE void FenceMemoryAnyStore() { __asm__ __volatile__("dmb ish" ::: "memory"); } + ALWAYS_INLINE void FenceMemoryAnyLoad() { __asm__ __volatile__("dmb ish" ::: "memory"); } + ALWAYS_INLINE void FenceMemoryAnyAny() { __asm__ __volatile__("dmb ish" ::: "memory"); } + + #else + + #error "Unknown architecture for os::impl::FenceMemory* (Horizon)" + + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_argument.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_argument.hpp new file mode 100644 index 00000000..a43102a5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_argument.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + int GetHostArgc(); + char **GetHostArgv(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_barrier.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_barrier.hpp new file mode 100644 index 00000000..236208db --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_barrier.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_barrier_types.hpp> +#include <stratosphere/os/os_barrier_api.hpp> + +namespace ams::os { + + class Barrier { + NON_COPYABLE(Barrier); + NON_MOVEABLE(Barrier); + private: + BarrierType m_barrier; + public: + explicit Barrier(int num_threads) { + InitializeBarrier(std::addressof(m_barrier), num_threads); + } + + ~Barrier() { + FinalizeBarrier(std::addressof(m_barrier)); + } + + void Await() { + return AwaitBarrier(std::addressof(m_barrier)); + } + + operator BarrierType &() { + return m_barrier; + } + + operator const BarrierType &() const { + return m_barrier; + } + + BarrierType *GetBase() { + return std::addressof(m_barrier); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_barrier_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_barrier_api.hpp new file mode 100644 index 00000000..39830d11 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_barrier_api.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + struct BarrierType; + + void InitializeBarrier(BarrierType *barrier, int num_threads); + void FinalizeBarrier(BarrierType *barrier); + + void AwaitBarrier(BarrierType *barrier); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_barrier_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_barrier_types.hpp new file mode 100644 index 00000000..a0dbc6a3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_barrier_types.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> +#include <stratosphere/os/impl/os_internal_condition_variable.hpp> + +namespace ams::os { + + struct BarrierType { + u16 max_threads; + u16 waiting_threads; + u32 base_counter_lower; + u32 base_counter_upper; + + impl::InternalCriticalSectionStorage cs_barrier; + impl::InternalConditionVariableStorage cv_gathered; + }; + static_assert(std::is_trivial<BarrierType>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_busy_mutex.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_busy_mutex.hpp new file mode 100644 index 00000000..8b314f8f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_busy_mutex.hpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_busy_mutex_types.hpp> +#include <stratosphere/os/os_busy_mutex_api.hpp> + +namespace ams::os { + + class BusyMutex { + NON_COPYABLE(BusyMutex); + NON_MOVEABLE(BusyMutex); + private: + BusyMutexType m_mutex; + public: + constexpr explicit BusyMutex() : m_mutex{::ams::os::BusyMutexType::State_Initialized, nullptr, {{AMS_OS_INTERNAL_BUSY_MUTEX_IMPL_CONSTANT_INITIALIZE_ARRAY_VALUES}}} { /* ... */ } + + ~BusyMutex() { FinalizeBusyMutex(std::addressof(m_mutex)); } + + void lock() { + return LockBusyMutex(std::addressof(m_mutex)); + } + + void unlock() { + return UnlockBusyMutex(std::addressof(m_mutex)); + } + + bool try_lock() { + return TryLockBusyMutex(std::addressof(m_mutex)); + } + + ALWAYS_INLINE void Lock() { + return this->lock(); + } + + ALWAYS_INLINE void Unlock() { + return this->unlock(); + } + + ALWAYS_INLINE bool TryLock() { + return this->try_lock(); + } + + operator BusyMutexType &() { + return m_mutex; + } + + operator const BusyMutexType &() const { + return m_mutex; + } + + BusyMutexType *GetBase() { + return std::addressof(m_mutex); + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_busy_mutex_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_busy_mutex_api.hpp new file mode 100644 index 00000000..0242ef14 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_busy_mutex_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + struct BusyMutexType; + + void InitializeBusyMutex(BusyMutexType *mutex); + void FinalizeBusyMutex(BusyMutexType *mutex); + + void LockBusyMutex(BusyMutexType *mutex); + bool TryLockBusyMutex(BusyMutexType *mutex); + void UnlockBusyMutex(BusyMutexType *mutex); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_busy_mutex_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_busy_mutex_types.hpp new file mode 100644 index 00000000..5e5e761f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_busy_mutex_types.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_busy_mutex.hpp> + +namespace ams::os { + + struct ThreadType; + + struct BusyMutexType { + enum State { + State_NotInitialized = 0, + State_Initialized = 1, + }; + + u8 state; + ThreadType *owner_thread; + union { + s32 _arr[sizeof(impl::InternalBusyMutexStorage) / sizeof(s32)]; + impl::InternalBusyMutexStorage _storage; + }; + }; + static_assert(std::is_trivial<BusyMutexType>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_cache.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_cache.hpp new file mode 100644 index 00000000..67e4b046 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_cache.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + void FlushDataCache(const void *addr, size_t size); + void FlushEntireDataCache(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_common_config.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_common_config.hpp new file mode 100644 index 00000000..02653ed7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_common_config.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + #if defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS) + #define AMS_OS_IMPL_USE_PTHREADS + #elif defined(ATMOSPHERE_OS_WINDOWS) + //#define AMS_OS_IMPL_USE_PTHREADS + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_common_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_common_types.hpp new file mode 100644 index 00000000..599d4aa0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_common_types.hpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_common_config.hpp> + +#if defined(AMS_OS_IMPL_USE_PTHREADS) +#include <pthread.h> +#endif + +namespace ams::os { + + enum class TriBool { + False = 0, + True = 1, + Undefined = 2, + }; + + enum class MessageQueueWaitKind { + ForNotEmpty, + ForNotFull, + }; + + struct ProcessId { + u64 value; + + inline constexpr explicit operator u64() const { + return this->value; + } + + /* Invalid Process ID. */ + static const ProcessId Invalid; + }; + + inline constexpr const ProcessId ProcessId::Invalid = {static_cast<u64>(-1ull)}; + + inline constexpr const ProcessId InvalidProcessId = ProcessId::Invalid; + + inline constexpr bool operator==(const ProcessId &lhs, const ProcessId &rhs) { + return lhs.value == rhs.value; + } + + inline constexpr bool operator!=(const ProcessId &lhs, const ProcessId &rhs) { + return lhs.value != rhs.value; + } + + inline constexpr bool operator<(const ProcessId &lhs, const ProcessId &rhs) { + return lhs.value < rhs.value; + } + + inline constexpr bool operator<=(const ProcessId &lhs, const ProcessId &rhs) { + return lhs.value <= rhs.value; + } + + inline constexpr bool operator>(const ProcessId &lhs, const ProcessId &rhs) { + return lhs.value > rhs.value; + } + + inline constexpr bool operator>=(const ProcessId &lhs, const ProcessId &rhs) { + return lhs.value >= rhs.value; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_condition_variable.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_condition_variable.hpp new file mode 100644 index 00000000..f77fab9c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_condition_variable.hpp @@ -0,0 +1,65 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_mutex_types.hpp> +#include <stratosphere/os/os_condition_variable_common.hpp> +#include <stratosphere/os/os_condition_variable_types.hpp> +#include <stratosphere/os/os_condition_variable_api.hpp> + +namespace ams::os { + + class ConditionVariable { + NON_COPYABLE(ConditionVariable); + NON_MOVEABLE(ConditionVariable); + private: + ConditionVariableType m_cv; + public: + constexpr ConditionVariable() : m_cv{::ams::os::ConditionVariableType::State_Initialized, {AMS_OS_INTERNAL_CONDITION_VARIABLE_IMPL_CONSTANT_INITIALIZER}} { /* ... */ } + + ~ConditionVariable() { FinalizeConditionVariable(std::addressof(m_cv)); } + + void Signal() { + SignalConditionVariable(std::addressof(m_cv)); + } + + void Broadcast() { + BroadcastConditionVariable(std::addressof(m_cv)); + } + + void Wait(ams::os::MutexType &mutex) { + WaitConditionVariable(std::addressof(m_cv), std::addressof(mutex)); + } + + ConditionVariableStatus TimedWait(ams::os::MutexType &mutex, TimeSpan timeout) { + return TimedWaitConditionVariable(std::addressof(m_cv), std::addressof(mutex), timeout); + } + + operator ConditionVariableType &() { + return m_cv; + } + + operator const ConditionVariableType &() const { + return m_cv; + } + + ConditionVariableType *GetBase() { + return std::addressof(m_cv); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_condition_variable_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_condition_variable_api.hpp new file mode 100644 index 00000000..7a36b7d4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_condition_variable_api.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_condition_variable_common.hpp> + +namespace ams::os { + + struct MutexType; + struct ConditionVariableType; + + void InitializeConditionVariable(ConditionVariableType *cv); + void FinalizeConditionVariable(ConditionVariableType *cv); + + void SignalConditionVariable(ConditionVariableType *cv); + void BroadcastConditionVariable(ConditionVariableType *cv); + + void WaitConditionVariable(ConditionVariableType *cv, MutexType *m); + ConditionVariableStatus TimedWaitConditionVariable(ConditionVariableType *cv, MutexType *m, TimeSpan timeout); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_condition_variable_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_condition_variable_common.hpp new file mode 100644 index 00000000..d733f67c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_condition_variable_common.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + enum class ConditionVariableStatus { + TimedOut = 0, + Success = 1, + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_condition_variable_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_condition_variable_types.hpp new file mode 100644 index 00000000..fbb4f459 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_condition_variable_types.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_condition_variable.hpp> + +namespace ams::os { + + struct ConditionVariableType { + enum State { + State_NotInitialized = 0, + State_Initialized = 1, + }; + + u8 state; + union { + s32 _arr[sizeof(impl::InternalConditionVariableStorage) / sizeof(s32)]; + impl::InternalConditionVariableStorage _storage; + impl::InternalConditionVariableStorageTypeForConstantInitialize _storage_for_constant_initialize; + }; + }; + static_assert(std::is_trivial<ConditionVariableType>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_debug.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_debug.hpp new file mode 100644 index 00000000..1e31b0e2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_debug.hpp @@ -0,0 +1,20 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_debug_types.hpp> +#include <stratosphere/os/os_debug_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_debug_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_debug_api.hpp new file mode 100644 index 00000000..6a1fd3d1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_debug_api.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_debug_types.hpp> +#include <stratosphere/os/os_tick.hpp> + +namespace ams::os { + + void GetCurrentStackInfo(uintptr_t *out_stack, size_t *out_size); + + void QueryMemoryInfo(MemoryInfo *out); + + Tick GetIdleTickCount(); + + int GetFreeThreadCount(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_debug_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_debug_types.hpp new file mode 100644 index 00000000..c9e05965 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_debug_types.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + struct MemoryInfo { + u64 total_available_memory_size; + size_t total_used_memory_size; + size_t total_memory_heap_size; + size_t allocated_memory_heap_size; + size_t program_size; + size_t total_thread_stack_size; + int thread_count; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_event.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_event.hpp new file mode 100644 index 00000000..4c4108da --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_event.hpp @@ -0,0 +1,72 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_event_common.hpp> +#include <stratosphere/os/os_event_types.hpp> +#include <stratosphere/os/os_event_api.hpp> + +namespace ams::os { + + class Event { + NON_COPYABLE(Event); + NON_MOVEABLE(Event); + private: + EventType m_event; + public: + explicit Event(EventClearMode clear_mode) { + InitializeEvent(std::addressof(m_event), false, clear_mode); + } + + ~Event() { + FinalizeEvent(std::addressof(m_event)); + } + + void Wait() { + return WaitEvent(std::addressof(m_event)); + } + + bool TryWait() { + return TryWaitEvent(std::addressof(m_event)); + } + + bool TimedWait(TimeSpan timeout) { + return TimedWaitEvent(std::addressof(m_event), timeout); + } + + void Signal() { + return SignalEvent(std::addressof(m_event)); + } + + void Clear() { + return ClearEvent(std::addressof(m_event)); + } + + operator EventType &() { + return m_event; + } + + operator const EventType &() const { + return m_event; + } + + EventType *GetBase() { + return std::addressof(m_event); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_event_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_event_api.hpp new file mode 100644 index 00000000..50d1e76b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_event_api.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_event_common.hpp> + +namespace ams::os { + + struct EventType; + struct MultiWaitHolderType; + + void InitializeEvent(EventType *event, bool signaled, EventClearMode clear_mode); + void FinalizeEvent(EventType *event); + + void SignalEvent(EventType *event); + void WaitEvent(EventType *event); + bool TryWaitEvent(EventType *event); + bool TimedWaitEvent(EventType *event, TimeSpan timeout); + void ClearEvent(EventType *event); + + void InitializeMultiWaitHolder(MultiWaitHolderType *multi_wait_holder, EventType *event); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_event_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_event_common.hpp new file mode 100644 index 00000000..73c9affa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_event_common.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + enum EventClearMode { + EventClearMode_ManualClear = 0, + EventClearMode_AutoClear = 1, + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_event_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_event_types.hpp new file mode 100644 index 00000000..156fd8e0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_event_types.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> +#include <stratosphere/os/impl/os_internal_condition_variable.hpp> + +namespace ams::os { + + namespace impl { + + class MultiWaitObjectList; + + } + + struct EventType { + enum State { + State_NotInitialized = 0, + State_Initialized = 1, + }; + + util::TypedStorage<impl::MultiWaitObjectList, sizeof(util::IntrusiveListNode), alignof(util::IntrusiveListNode)> multi_wait_object_list_storage; + bool signaled; + bool initially_signaled; + u8 clear_mode; + u8 state; + u32 broadcast_counter_low; + u32 broadcast_counter_high; + + impl::InternalCriticalSectionStorage cs_event; + impl::InternalConditionVariableStorage cv_signaled; + }; + static_assert(std::is_trivial<EventType>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_insecure_memory_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_insecure_memory_api.hpp new file mode 100644 index 00000000..d9bec74e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_insecure_memory_api.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_memory_common.hpp> + +namespace ams::os { + + Result AllocateInsecureMemory(uintptr_t *out_address, size_t size); + void FreeInsecureMemory(uintptr_t address, size_t size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_interrupt_event.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_interrupt_event.hpp new file mode 100644 index 00000000..1ffa24e9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_interrupt_event.hpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_event_common.hpp> +#include <stratosphere/os/os_interrupt_event_common.hpp> +#include <stratosphere/os/os_interrupt_event_types.hpp> +#include <stratosphere/os/os_interrupt_event_api.hpp> + +namespace ams::os { + + class InterruptEvent { + NON_COPYABLE(InterruptEvent); + NON_MOVEABLE(InterruptEvent); + private: + InterruptEventType m_event; + public: + explicit InterruptEvent(InterruptName name, EventClearMode clear_mode) { + InitializeInterruptEvent(std::addressof(m_event), name, clear_mode); + } + + ~InterruptEvent() { + FinalizeInterruptEvent(std::addressof(m_event)); + } + + void Wait() { + return WaitInterruptEvent(std::addressof(m_event)); + } + + bool TryWait() { + return TryWaitInterruptEvent(std::addressof(m_event)); + } + + bool TimedWait(TimeSpan timeout) { + return TimedWaitInterruptEvent(std::addressof(m_event), timeout); + } + + void Clear() { + return ClearInterruptEvent(std::addressof(m_event)); + } + + operator InterruptEventType &() { + return m_event; + } + + operator const InterruptEventType &() const { + return m_event; + } + + InterruptEventType *GetBase() { + return std::addressof(m_event); + } + }; + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_interrupt_event_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_interrupt_event_api.hpp new file mode 100644 index 00000000..3a8b4f12 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_interrupt_event_api.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_interrupt_event_common.hpp> +#include <stratosphere/os/os_native_handle.hpp> + +namespace ams::os { + + struct InterruptEventType; + struct MultiWaitHolderType; + + void InitializeInterruptEvent(InterruptEventType *event, InterruptName name, EventClearMode clear_mode); + void FinalizeInterruptEvent(InterruptEventType *event); + + void WaitInterruptEvent(InterruptEventType *event); + bool TryWaitInterruptEvent(InterruptEventType *event); + bool TimedWaitInterruptEvent(InterruptEventType *event, TimeSpan timeout); + void ClearInterruptEvent(InterruptEventType *event); + + NativeHandle GetInterruptEventHandle(const InterruptEventType *event); + + void InitializeMultiWaitHolder(MultiWaitHolderType *multi_wait_holder, InterruptEventType *event); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_interrupt_event_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_interrupt_event_common.hpp new file mode 100644 index 00000000..68e087e0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_interrupt_event_common.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + #if defined(ATMOSPHERE_OS_HORIZON) + using InterruptName = s32; + #elif defined(ATMOSPHERE_OS_WINDOWS) + using InterruptName = const char *; + #elif defined(ATMOSPHERE_OS_LINUX) + using InterruptName = const char *; + #elif defined(ATMOSPHERE_OS_MACOS) + using InterruptName = const char *; + #endif + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_interrupt_event_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_interrupt_event_types.hpp new file mode 100644 index 00000000..635186fe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_interrupt_event_types.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> +#include <stratosphere/os/impl/os_internal_condition_variable.hpp> +#include <stratosphere/os/os_native_handle.hpp> + +namespace ams::os { + + namespace impl { + + class MultiWaitObjectList; + class InterruptEventImpl; + + } + + struct InterruptEventType { + enum State { + State_NotInitialized = 0, + State_Initialized = 1, + }; + + util::TypedStorage<impl::MultiWaitObjectList, sizeof(util::IntrusiveListNode), alignof(util::IntrusiveListNode)> multi_wait_object_list_storage; + + u8 clear_mode; + u8 state; + + util::TypedStorage<impl::InterruptEventImpl, sizeof(NativeHandle) * 2, alignof(NativeHandle)> impl; + }; + static_assert(std::is_trivial<InterruptEventType>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_io_region.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_io_region.hpp new file mode 100644 index 00000000..f9ce90ce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_io_region.hpp @@ -0,0 +1,83 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_io_region_types.hpp> +#include <stratosphere/os/os_io_region_api.hpp> + +namespace ams::os { + + class IoRegion { + NON_COPYABLE(IoRegion); + NON_MOVEABLE(IoRegion); + private: + IoRegionType m_io_region; + public: + constexpr IoRegion() : m_io_region{ .state = IoRegionType::State_NotInitialized } { + /* ... */ + } + + IoRegion(NativeHandle io_pool_handle, uintptr_t address, size_t size, MemoryMapping mapping, MemoryPermission permission) { + R_ABORT_UNLESS(CreateIoRegion(std::addressof(m_io_region), io_pool_handle, address, size, mapping, permission)); + } + + IoRegion(size_t size, NativeHandle handle, bool managed) { + this->AttachHandle(size, handle, managed); + } + + ~IoRegion() { + if (m_io_region.state == IoRegionType::State_NotInitialized) { + return; + } + + if (m_io_region.state == IoRegionType::State_Mapped) { + this->Unmap(); + } + + DestroyIoRegion(std::addressof(m_io_region)); + } + + void AttachHandle(size_t size, NativeHandle handle, bool managed) { + AttachIoRegionHandle(std::addressof(m_io_region), size, handle, managed); + } + + NativeHandle GetHandle() const { + return GetIoRegionHandle(std::addressof(m_io_region)); + } + + Result Map(void **out, MemoryPermission perm) { + R_RETURN(MapIoRegion(out, std::addressof(m_io_region), perm)); + } + + void Unmap() { + UnmapIoRegion(std::addressof(m_io_region)); + } + + operator IoRegionType &() { + return m_io_region; + } + + operator const IoRegionType &() const { + return m_io_region; + } + + IoRegionType *GetBase() { + return std::addressof(m_io_region); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_io_region_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_io_region_api.hpp new file mode 100644 index 00000000..838228a5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_io_region_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_memory_permission.hpp> +#include <stratosphere/os/os_native_handle.hpp> + +namespace ams::os { + + struct IoRegionType; + + Result CreateIoRegion(IoRegionType *io_region, NativeHandle io_pool_handle, uintptr_t address, size_t size, MemoryMapping mapping, MemoryPermission permission); + + void AttachIoRegionHandle(IoRegionType *io_region, size_t size, NativeHandle handle, bool managed); + os::NativeHandle DetachIoRegionHandle(IoRegionType *io_region); + + void DestroyIoRegion(IoRegionType *io_region); + + NativeHandle GetIoRegionHandle(const IoRegionType *io_region); + + Result MapIoRegion(void **out, IoRegionType *io_region, MemoryPermission perm); + void UnmapIoRegion(IoRegionType *io_region); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_io_region_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_io_region_types.hpp new file mode 100644 index 00000000..387541af --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_io_region_types.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> +#include <stratosphere/os/os_native_handle.hpp> + +namespace ams::os { + + struct IoRegionType { + enum State { + State_NotInitialized = 0, + State_Initialized = 1, + State_Mapped = 2, + State_Detached = 3, + }; + + NativeHandle handle; + u8 state; + size_t size; + void *mapped_address; + bool handle_managed; + + mutable impl::InternalCriticalSectionStorage cs_io_region; + }; + static_assert(std::is_trivial<IoRegionType>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_event.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_event.hpp new file mode 100644 index 00000000..270aa0c2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_event.hpp @@ -0,0 +1,72 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_event_common.hpp> +#include <stratosphere/os/os_light_event_types.hpp> +#include <stratosphere/os/os_light_event_api.hpp> + +namespace ams::os { + + class LightEvent { + NON_COPYABLE(LightEvent); + NON_MOVEABLE(LightEvent); + private: + LightEventType m_event; + public: + explicit LightEvent(EventClearMode clear_mode) { + InitializeLightEvent(std::addressof(m_event), false, clear_mode); + } + + ~LightEvent() { + FinalizeLightEvent(std::addressof(m_event)); + } + + void Wait() { + return WaitLightEvent(std::addressof(m_event)); + } + + bool TryWait() { + return TryWaitLightEvent(std::addressof(m_event)); + } + + bool TimedWait(TimeSpan timeout) { + return TimedWaitLightEvent(std::addressof(m_event), timeout); + } + + void Signal() { + return SignalLightEvent(std::addressof(m_event)); + } + + void Clear() { + return ClearLightEvent(std::addressof(m_event)); + } + + operator LightEventType &() { + return m_event; + } + + operator const LightEventType &() const { + return m_event; + } + + LightEventType *GetBase() { + return std::addressof(m_event); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_event_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_event_api.hpp new file mode 100644 index 00000000..5286bf0b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_event_api.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_event_common.hpp> + +namespace ams::os { + + struct LightEventType; + + void InitializeLightEvent(LightEventType *event, bool signaled, EventClearMode clear_mode); + void FinalizeLightEvent(LightEventType *event); + + void SignalLightEvent(LightEventType *event); + void WaitLightEvent(LightEventType *event); + bool TryWaitLightEvent(LightEventType *event); + bool TimedWaitLightEvent(LightEventType *event, TimeSpan timeout); + void ClearLightEvent(LightEventType *event); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_event_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_event_types.hpp new file mode 100644 index 00000000..72b1f32d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_event_types.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_light_event.hpp> + +namespace ams::os { + + struct LightEventType { + bool is_auto_clear; + bool is_initialized; + + impl::InternalLightEventStorage storage; + }; + static_assert(std::is_trivial<LightEventType>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_message_queue.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_message_queue.hpp new file mode 100644 index 00000000..1e170f8b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_message_queue.hpp @@ -0,0 +1,100 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_light_message_queue_types.hpp> +#include <stratosphere/os/os_light_message_queue_api.hpp> + +namespace ams::os { + + class LightMessageQueue { + NON_COPYABLE(LightMessageQueue); + NON_MOVEABLE(LightMessageQueue); + private: + LightMessageQueueType m_mq; + public: + explicit LightMessageQueue(uintptr_t *buf, size_t count) { + InitializeLightMessageQueue(std::addressof(m_mq), buf, count); + } + + ~LightMessageQueue() { FinalizeLightMessageQueue(std::addressof(m_mq)); } + + /* Sending (FIFO functionality) */ + void Send(uintptr_t data) { + return SendLightMessageQueue(std::addressof(m_mq), data); + } + + bool TrySend(uintptr_t data) { + return TrySendLightMessageQueue(std::addressof(m_mq), data); + } + + bool TimedSend(uintptr_t data, TimeSpan timeout) { + return TimedSendLightMessageQueue(std::addressof(m_mq), data, timeout); + } + + /* Jamming (LIFO functionality) */ + void Jam(uintptr_t data) { + return JamLightMessageQueue(std::addressof(m_mq), data); + } + + bool TryJam(uintptr_t data) { + return TryJamLightMessageQueue(std::addressof(m_mq), data); + } + + bool TimedJam(uintptr_t data, TimeSpan timeout) { + return TimedJamLightMessageQueue(std::addressof(m_mq), data, timeout); + } + + /* Receive functionality */ + void Receive(uintptr_t *out) { + return ReceiveLightMessageQueue(out, std::addressof(m_mq)); + } + + bool TryReceive(uintptr_t *out) { + return TryReceiveLightMessageQueue(out, std::addressof(m_mq)); + } + + bool TimedReceive(uintptr_t *out, TimeSpan timeout) { + return TimedReceiveLightMessageQueue(out, std::addressof(m_mq), timeout); + } + + /* Peek functionality */ + void Peek(uintptr_t *out) const { + return PeekLightMessageQueue(out, std::addressof(m_mq)); + } + + bool TryPeek(uintptr_t *out) const { + return TryPeekLightMessageQueue(out, std::addressof(m_mq)); + } + + bool TimedPeek(uintptr_t *out, TimeSpan timeout) const { + return TimedPeekLightMessageQueue(out, std::addressof(m_mq), timeout); + } + + operator LightMessageQueueType &() { + return m_mq; + } + + operator const LightMessageQueueType &() const { + return m_mq; + } + + LightMessageQueueType *GetBase() { + return std::addressof(m_mq); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_message_queue_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_message_queue_api.hpp new file mode 100644 index 00000000..ec0980ed --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_message_queue_api.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + struct LightMessageQueueType; + + void InitializeLightMessageQueue(LightMessageQueueType *mq, uintptr_t *buffer, size_t count); + void FinalizeLightMessageQueue(LightMessageQueueType *mq); + + /* Sending (FIFO functionality) */ + void SendLightMessageQueue(LightMessageQueueType *mq, uintptr_t data); + bool TrySendLightMessageQueue(LightMessageQueueType *mq, uintptr_t data); + bool TimedSendLightMessageQueue(LightMessageQueueType *mq, uintptr_t data, TimeSpan timeout); + + /* Jamming (LIFO functionality) */ + void JamLightMessageQueue(LightMessageQueueType *mq, uintptr_t data); + bool TryJamLightMessageQueue(LightMessageQueueType *mq, uintptr_t data); + bool TimedJamLightMessageQueue(LightMessageQueueType *mq, uintptr_t data, TimeSpan timeout); + + /* Receive functionality */ + void ReceiveLightMessageQueue(uintptr_t *out, LightMessageQueueType *mq); + bool TryReceiveLightMessageQueue(uintptr_t *out, LightMessageQueueType *mq); + bool TimedReceiveLightMessageQueue(uintptr_t *out, LightMessageQueueType *mq, TimeSpan timeout); + + /* Peek functionality */ + void PeekLightMessageQueue(uintptr_t *out, const LightMessageQueueType *mq); + bool TryPeekLightMessageQueue(uintptr_t *out, const LightMessageQueueType *mq); + bool TimedPeekLightMessageQueue(uintptr_t *out, const LightMessageQueueType *mq, TimeSpan timeout); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_message_queue_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_message_queue_types.hpp new file mode 100644 index 00000000..0789d00f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_message_queue_types.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> +#include <stratosphere/os/impl/os_internal_busy_mutex.hpp> +#include <stratosphere/os/impl/os_internal_light_event.hpp> + +namespace ams::os { + + namespace impl { + + using LightMessageQueueMutex = InternalBusyMutex; + using LightMessageQueueMutexStorage = InternalBusyMutexStorage; + + } + + struct LightMessageQueueType { + enum State { + State_NotInitialized = 0, + State_Initialized = 1, + }; + + uintptr_t *buffer; + s32 capacity; + s32 count; + s32 offset; + u8 state; + + mutable impl::LightMessageQueueMutexStorage mutex_queue; + mutable impl::InternalLightEventStorage ev_not_full; + mutable impl::InternalLightEventStorage ev_not_empty; + }; + static_assert(std::is_trivial<LightMessageQueueType>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_semaphore.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_semaphore.hpp new file mode 100644 index 00000000..2514b4d3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_semaphore.hpp @@ -0,0 +1,72 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_light_semaphore_types.hpp> +#include <stratosphere/os/os_light_semaphore_api.hpp> + +namespace ams::os { + + class LightSemaphore { + NON_COPYABLE(LightSemaphore); + NON_MOVEABLE(LightSemaphore); + private: + LightSemaphoreType m_sema; + public: + explicit LightSemaphore(s32 count, s32 max_count) { + InitializeLightSemaphore(std::addressof(m_sema), count, max_count); + } + + ~LightSemaphore() { FinalizeLightSemaphore(std::addressof(m_sema)); } + + void Acquire() { + return os::AcquireLightSemaphore(std::addressof(m_sema)); + } + + bool TryAcquire() { + return os::TryAcquireLightSemaphore(std::addressof(m_sema)); + } + + bool TimedAcquire(TimeSpan timeout) { + return os::TimedAcquireLightSemaphore(std::addressof(m_sema), timeout); + } + + void Release() { + return os::ReleaseLightSemaphore(std::addressof(m_sema)); + } + + void Release(s32 count) { + return os::ReleaseLightSemaphore(std::addressof(m_sema), count); + } + + s32 GetCurrentCount() const { + return os::GetCurrentLightSemaphoreCount(std::addressof(m_sema)); + } + + operator LightSemaphoreType &() { + return m_sema; + } + + operator const LightSemaphoreType &() const { + return m_sema; + } + + LightSemaphoreType *GetBase() { + return std::addressof(m_sema); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_semaphore_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_semaphore_api.hpp new file mode 100644 index 00000000..99aa6416 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_semaphore_api.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + struct LightSemaphoreType; + + void InitializeLightSemaphore(LightSemaphoreType *sema, s32 count, s32 max_count); + void FinalizeLightSemaphore(LightSemaphoreType *sema); + + void AcquireLightSemaphore(LightSemaphoreType *sema); + bool TryAcquireLightSemaphore(LightSemaphoreType *sema); + bool TimedAcquireLightSemaphore(LightSemaphoreType *sema, TimeSpan timeout); + + void ReleaseLightSemaphore(LightSemaphoreType *sema); + void ReleaseLightSemaphore(LightSemaphoreType *sema, s32 count); + + s32 GetCurrentLightSemaphoreCount(const LightSemaphoreType *sema); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_semaphore_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_semaphore_types.hpp new file mode 100644 index 00000000..35d6092e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_light_semaphore_types.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> +#include <stratosphere/os/impl/os_internal_busy_mutex.hpp> +#include <stratosphere/os/impl/os_internal_light_event.hpp> + +namespace ams::os { + + namespace impl { + + using LightSemaphoreMutex = InternalBusyMutex; + using LightSemaphoreMutexStorage = InternalBusyMutexStorage; + + } + + struct LightSemaphoreType { + enum State { + State_NotInitialized = 0, + State_Initialized = 1, + }; + + u8 state; + s32 count; + s32 max_count; + + mutable impl::LightSemaphoreMutexStorage mutex; + impl::InternalLightEventStorage ev_not_zero; + }; + static_assert(std::is_trivial<LightSemaphoreType>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_attribute.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_attribute.hpp new file mode 100644 index 00000000..068516e8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_attribute.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_common_types.hpp> +#include <stratosphere/os/os_memory_common.hpp> + +namespace ams::os { + + enum MemoryAttribute { + MemoryAttribute_Normal, + MemoryAttribute_Uncached, + }; + + void SetMemoryAttribute(uintptr_t address, size_t size, MemoryAttribute attr); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_common.hpp new file mode 100644 index 00000000..070f95a6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_common.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + using AddressSpaceGenerateRandomFunction = u64 (*)(u64); + + enum MemoryPermission { + MemoryPermission_None = (0 << 0), + MemoryPermission_ReadOnly = (1 << 0), + MemoryPermission_WriteOnly = (1 << 1), + MemoryPermission_ExecuteOnly = (1 << 2), + + MemoryPermission_ReadWrite = MemoryPermission_ReadOnly | MemoryPermission_WriteOnly, + MemoryPermission_ReadExecute = MemoryPermission_ReadOnly | MemoryPermission_ExecuteOnly, + }; + + #if defined(ATMOSPHERE_OS_HORIZON) + using MemoryMapping = svc::MemoryMapping; + using enum svc::MemoryMapping; + #else + enum MemoryMapping : u32 { + MemoryMapping_IoRegister = 0, + MemoryMapping_Uncached = 1, + MemoryMapping_Memory = 2, + }; + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_fence.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_fence.hpp new file mode 100644 index 00000000..77313d17 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_fence.hpp @@ -0,0 +1,17 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/os/os_memory_fence_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_fence_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_fence_api.hpp new file mode 100644 index 00000000..b62e4186 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_fence_api.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) + #include <stratosphere/os/impl/os_memory_fence_api.os.horizon.hpp> +#elif defined(ATMOSPHERE_OS_WINDOWS) || defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS) + #include <stratosphere/os/impl/os_memory_fence_api.os.generic.hpp> +#else + #error "Unknown os for os::MemoryFence*" +#endif + +namespace ams::os { + + ALWAYS_INLINE void FenceMemoryStoreStore() { return impl::FenceMemoryStoreStore(); } + ALWAYS_INLINE void FenceMemoryStoreLoad() { return impl::FenceMemoryStoreLoad(); } + ALWAYS_INLINE void FenceMemoryStoreAny() { return impl::FenceMemoryStoreAny(); } + + ALWAYS_INLINE void FenceMemoryLoadStore() { return impl::FenceMemoryLoadStore(); } + ALWAYS_INLINE void FenceMemoryLoadLoad() { return impl::FenceMemoryLoadLoad(); } + ALWAYS_INLINE void FenceMemoryLoadAny() { return impl::FenceMemoryLoadAny(); } + + ALWAYS_INLINE void FenceMemoryAnyStore() { return impl::FenceMemoryLoadStore(); } + ALWAYS_INLINE void FenceMemoryAnyLoad() { return impl::FenceMemoryLoadLoad(); } + ALWAYS_INLINE void FenceMemoryAnyAny() { return impl::FenceMemoryLoadAny(); } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_heap.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_heap.hpp new file mode 100644 index 00000000..e49d9b30 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_heap.hpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_memory_heap_common.hpp> +#include <stratosphere/os/os_memory_heap_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_heap_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_heap_api.hpp new file mode 100644 index 00000000..5f782c1f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_heap_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_common_types.hpp> +#include <stratosphere/os/os_memory_heap_common.hpp> + +namespace ams::os { + + Result SetMemoryHeapSize(size_t size); + + uintptr_t GetMemoryHeapAddress(); + size_t GetMemoryHeapSize(); + + Result AllocateMemoryBlock(uintptr_t *out_address, size_t size); + void FreeMemoryBlock(uintptr_t address, size_t size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_heap_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_heap_common.hpp new file mode 100644 index 00000000..3091541a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_heap_common.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_common_types.hpp> +#include <stratosphere/os/os_memory_common.hpp> + +namespace ams::os { + + constexpr inline size_t MemoryHeapUnitSize = 2_MB; + constexpr inline size_t MemoryBlockUnitSize = 2_MB; + + constexpr inline size_t MemoryPageSize = 4_KB; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_permission.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_permission.hpp new file mode 100644 index 00000000..f65f30b2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_memory_permission.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_common_types.hpp> +#include <stratosphere/os/os_memory_common.hpp> + +namespace ams::os { + + void SetMemoryPermission(uintptr_t address, size_t size, MemoryPermission perm); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_message_queue.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_message_queue.hpp new file mode 100644 index 00000000..ff0994a2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_message_queue.hpp @@ -0,0 +1,101 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_message_queue_common.hpp> +#include <stratosphere/os/os_message_queue_types.hpp> +#include <stratosphere/os/os_message_queue_api.hpp> + +namespace ams::os { + + class MessageQueue { + NON_COPYABLE(MessageQueue); + NON_MOVEABLE(MessageQueue); + private: + MessageQueueType m_mq; + public: + explicit MessageQueue(uintptr_t *buf, size_t count) { + InitializeMessageQueue(std::addressof(m_mq), buf, count); + } + + ~MessageQueue() { FinalizeMessageQueue(std::addressof(m_mq)); } + + /* Sending (FIFO functionality) */ + void Send(uintptr_t data) { + return SendMessageQueue(std::addressof(m_mq), data); + } + + bool TrySend(uintptr_t data) { + return TrySendMessageQueue(std::addressof(m_mq), data); + } + + bool TimedSend(uintptr_t data, TimeSpan timeout) { + return TimedSendMessageQueue(std::addressof(m_mq), data, timeout); + } + + /* Jamming (LIFO functionality) */ + void Jam(uintptr_t data) { + return JamMessageQueue(std::addressof(m_mq), data); + } + + bool TryJam(uintptr_t data) { + return TryJamMessageQueue(std::addressof(m_mq), data); + } + + bool TimedJam(uintptr_t data, TimeSpan timeout) { + return TimedJamMessageQueue(std::addressof(m_mq), data, timeout); + } + + /* Receive functionality */ + void Receive(uintptr_t *out) { + return ReceiveMessageQueue(out, std::addressof(m_mq)); + } + + bool TryReceive(uintptr_t *out) { + return TryReceiveMessageQueue(out, std::addressof(m_mq)); + } + + bool TimedReceive(uintptr_t *out, TimeSpan timeout) { + return TimedReceiveMessageQueue(out, std::addressof(m_mq), timeout); + } + + /* Peek functionality */ + void Peek(uintptr_t *out) const { + return PeekMessageQueue(out, std::addressof(m_mq)); + } + + bool TryPeek(uintptr_t *out) const { + return TryPeekMessageQueue(out, std::addressof(m_mq)); + } + + bool TimedPeek(uintptr_t *out, TimeSpan timeout) const { + return TimedPeekMessageQueue(out, std::addressof(m_mq), timeout); + } + + operator MessageQueueType &() { + return m_mq; + } + + operator const MessageQueueType &() const { + return m_mq; + } + + MessageQueueType *GetBase() { + return std::addressof(m_mq); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_message_queue_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_message_queue_api.hpp new file mode 100644 index 00000000..a7f4cf97 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_message_queue_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_message_queue_common.hpp> + +namespace ams::os { + + struct MessageQueueType; + struct MultiWaitHolderType; + + void InitializeMessageQueue(MessageQueueType *mq, uintptr_t *buffer, size_t count); + void FinalizeMessageQueue(MessageQueueType *mq); + + /* Sending (FIFO functionality) */ + void SendMessageQueue(MessageQueueType *mq, uintptr_t data); + bool TrySendMessageQueue(MessageQueueType *mq, uintptr_t data); + bool TimedSendMessageQueue(MessageQueueType *mq, uintptr_t data, TimeSpan timeout); + + /* Jamming (LIFO functionality) */ + void JamMessageQueue(MessageQueueType *mq, uintptr_t data); + bool TryJamMessageQueue(MessageQueueType *mq, uintptr_t data); + bool TimedJamMessageQueue(MessageQueueType *mq, uintptr_t data, TimeSpan timeout); + + /* Receive functionality */ + void ReceiveMessageQueue(uintptr_t *out, MessageQueueType *mq); + bool TryReceiveMessageQueue(uintptr_t *out, MessageQueueType *mq); + bool TimedReceiveMessageQueue(uintptr_t *out, MessageQueueType *mq, TimeSpan timeout); + + /* Peek functionality */ + void PeekMessageQueue(uintptr_t *out, const MessageQueueType *mq); + bool TryPeekMessageQueue(uintptr_t *out, const MessageQueueType *mq); + bool TimedPeekMessageQueue(uintptr_t *out, const MessageQueueType *mq, TimeSpan timeout); + + void InitializeMultiWaitHolder(MultiWaitHolderType *multi_wait_holder, MessageQueueType *event, MessageQueueWaitType wait_type); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_message_queue_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_message_queue_common.hpp new file mode 100644 index 00000000..04ed38ab --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_message_queue_common.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + enum class MessageQueueWaitType { + ForNotFull = 1, + ForNotEmpty = 2, + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_message_queue_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_message_queue_types.hpp new file mode 100644 index 00000000..541f4342 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_message_queue_types.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> +#include <stratosphere/os/impl/os_internal_condition_variable.hpp> + +namespace ams::os { + + namespace impl { + + class MultiWaitObjectList; + + } + + struct MessageQueueType { + enum State { + State_NotInitialized = 0, + State_Initialized = 1, + }; + + util::TypedStorage<impl::MultiWaitObjectList, sizeof(util::IntrusiveListNode), alignof(util::IntrusiveListNode)> waitlist_not_full; + util::TypedStorage<impl::MultiWaitObjectList, sizeof(util::IntrusiveListNode), alignof(util::IntrusiveListNode)> waitlist_not_empty; + uintptr_t *buffer; + s32 capacity; + s32 count; + s32 offset; + u8 state; + + mutable impl::InternalCriticalSectionStorage cs_queue; + mutable impl::InternalConditionVariableStorage cv_not_full; + mutable impl::InternalConditionVariableStorage cv_not_empty; + }; + static_assert(std::is_trivial<MessageQueueType>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_multiple_wait.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_multiple_wait.hpp new file mode 100644 index 00000000..403d5614 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_multiple_wait.hpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/os/os_multiple_wait_types.hpp> +#include <stratosphere/os/os_multiple_wait_api.hpp> +#include <stratosphere/os/os_multiple_wait_utils.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_multiple_wait_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_multiple_wait_api.hpp new file mode 100644 index 00000000..664d017e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_multiple_wait_api.hpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_message_queue_common.hpp> +#include <stratosphere/os/os_native_handle.hpp> + +namespace ams::os { + + struct MultiWaitHolderType; + struct MultiWaitType; + + void InitializeMultiWait(MultiWaitType *multi_wait); + void FinalizeMultiWait(MultiWaitType *multi_wait); + + MultiWaitHolderType *WaitAny(MultiWaitType *multi_wait); + MultiWaitHolderType *TryWaitAny(MultiWaitType *multi_wait); + MultiWaitHolderType *TimedWaitAny(MultiWaitType *multi_wait, TimeSpan timeout); + + void FinalizeMultiWaitHolder(MultiWaitHolderType *holder); + + void LinkMultiWaitHolder(MultiWaitType *multi_wait, MultiWaitHolderType *holder); + void UnlinkMultiWaitHolder(MultiWaitHolderType *holder); + void UnlinkAllMultiWaitHolder(MultiWaitType *multi_wait); + + void MoveAllMultiWaitHolder(MultiWaitType *dst, MultiWaitType *src); + + void SetMultiWaitHolderUserData(MultiWaitHolderType *holder, uintptr_t user_data); + uintptr_t GetMultiWaitHolderUserData(const MultiWaitHolderType *holder); + + void InitializeMultiWaitHolder(MultiWaitHolderType *holder, NativeHandle handle); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_multiple_wait_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_multiple_wait_types.hpp new file mode 100644 index 00000000..04ab4742 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_multiple_wait_types.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> + +namespace ams::os { + + namespace impl { + + class MultiWaitImpl; + struct MultiWaitHolderImpl; + + } + + struct MultiWaitType { + enum State { + State_NotInitialized, + State_Initialized, + }; + + u8 state; + bool is_waiting; + util::TypedStorage<impl::MultiWaitImpl, util::AlignUp(sizeof(util::IntrusiveListNode) + sizeof(impl::InternalCriticalSection) + 2 * sizeof(void *) + sizeof(NativeHandle), alignof(void *)), alignof(void *)> impl_storage; + }; + static_assert(std::is_trivial<MultiWaitType>::value); + + struct MultiWaitHolderType { + util::TypedStorage<impl::MultiWaitHolderImpl, 2 * sizeof(util::IntrusiveListNode) + 3 * sizeof(void *), alignof(void *)> impl_storage; + uintptr_t user_data; + }; + static_assert(std::is_trivial<MultiWaitHolderType>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_multiple_wait_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_multiple_wait_utils.hpp new file mode 100644 index 00000000..04febe2f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_multiple_wait_utils.hpp @@ -0,0 +1,124 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_message_queue_common.hpp> +#include <stratosphere/os/os_multiple_wait_api.hpp> +#include <stratosphere/os/os_multiple_wait_types.hpp> + +namespace ams::os { + + namespace impl { + + class AutoMultiWaitHolder { + private: + MultiWaitHolderType m_holder; + public: + template<typename T> + ALWAYS_INLINE explicit AutoMultiWaitHolder(MultiWaitType *multi_wait, T &&arg) { + InitializeMultiWaitHolder(std::addressof(m_holder), std::forward<T>(arg)); + LinkMultiWaitHolder(multi_wait, std::addressof(m_holder)); + } + + ALWAYS_INLINE ~AutoMultiWaitHolder() { + UnlinkMultiWaitHolder(std::addressof(m_holder)); + FinalizeMultiWaitHolder(std::addressof(m_holder)); + } + + ALWAYS_INLINE std::pair<MultiWaitHolderType *, int> ConvertResult(const std::pair<MultiWaitHolderType *, int> result, int index) { + if (result.first == std::addressof(m_holder)) { + return std::make_pair(static_cast<MultiWaitHolderType *>(nullptr), index); + } else { + return result; + } + } + }; + + template<typename F> + inline std::pair<MultiWaitHolderType *, int> WaitAnyImpl(F &&func, MultiWaitType *multi_wait, int) { + return std::pair<MultiWaitHolderType *, int>(func(multi_wait), -1); + } + + template<typename F, typename T, typename... Args> + inline std::pair<MultiWaitHolderType *, int> WaitAnyImpl(F &&func, MultiWaitType *multi_wait, int index, T &&x, Args &&... args) { + AutoMultiWaitHolder holder(multi_wait, std::forward<T>(x)); + return holder.ConvertResult(WaitAnyImpl(std::forward<F>(func), multi_wait, index + 1, std::forward<Args>(args)...), index); + } + + template<typename F, typename... Args> + inline std::pair<MultiWaitHolderType *, int> WaitAnyImpl(F &&func, MultiWaitType *multi_wait, Args &&... args) { + return WaitAnyImpl(std::forward<F>(func), multi_wait, 0, std::forward<Args>(args)...); + } + + class TempMultiWait { + private: + MultiWaitType m_multi_wait; + public: + ALWAYS_INLINE TempMultiWait() { + os::InitializeMultiWait(std::addressof(m_multi_wait)); + } + + ALWAYS_INLINE ~TempMultiWait() { + os::FinalizeMultiWait(std::addressof(m_multi_wait)); + } + + MultiWaitType *Get() { + return std::addressof(m_multi_wait); + } + }; + + template<typename F, typename... Args> + inline std::pair<MultiWaitHolderType *, int> WaitAnyImpl(F &&func, Args &&... args) { + TempMultiWait temp_multi_wait; + return WaitAnyImpl(std::forward<F>(func), temp_multi_wait.Get(), 0, std::forward<Args>(args)...); + } + + using WaitAnyFunction = MultiWaitHolderType * (*)(MultiWaitType *); + + class NotBoolButInt { + private: + int m_value; + public: + constexpr ALWAYS_INLINE NotBoolButInt(int v) : m_value(v) { /* ... */ } + + constexpr ALWAYS_INLINE operator int() const { return m_value; } + + explicit operator bool() const = delete; + }; + + } + + template<typename... Args> requires (sizeof...(Args) > 0) + inline std::pair<MultiWaitHolderType *, int> WaitAny(MultiWaitType *multi_wait, Args &&... args) { + return impl::WaitAnyImpl(static_cast<impl::WaitAnyFunction>(&::ams::os::WaitAny), multi_wait, std::forward<Args>(args)...); + } + + template<typename... Args> requires (sizeof...(Args) > 0) + inline int WaitAny(Args &&... args) { + return impl::WaitAnyImpl(static_cast<impl::WaitAnyFunction>(&::ams::os::WaitAny), std::forward<Args>(args)...).second; + } + + template<typename... Args> requires (sizeof...(Args) > 0) + inline std::pair<MultiWaitHolderType *, int> TryWaitAny(MultiWaitType *multi_wait, Args &&... args) { + return impl::WaitAnyImpl(static_cast<impl::WaitAnyFunction>(&::ams::os::TryWaitAny), multi_wait, std::forward<Args>(args)...); + } + + template<typename... Args> requires (sizeof...(Args) > 0) + inline impl::NotBoolButInt TryWaitAny(Args &&... args) { + return impl::WaitAnyImpl(static_cast<impl::WaitAnyFunction>(&::ams::os::TryWaitAny), std::forward<Args>(args)...).second; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_mutex.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_mutex.hpp new file mode 100644 index 00000000..ac2678e7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_mutex.hpp @@ -0,0 +1,75 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_mutex_common.hpp> +#include <stratosphere/os/os_mutex_types.hpp> +#include <stratosphere/os/os_mutex_api.hpp> + +namespace ams::os { + + class Mutex { + NON_COPYABLE(Mutex); + NON_MOVEABLE(Mutex); + private: + MutexType m_mutex; + public: + constexpr explicit Mutex(bool recursive) : m_mutex{::ams::os::MutexType::State_Initialized, recursive, 0, 0, nullptr, { AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CONSTANT_INITIALIZER } } { /* ... */ } + + ~Mutex() { FinalizeMutex(std::addressof(m_mutex)); } + + void lock() { + return LockMutex(std::addressof(m_mutex)); + } + + void unlock() { + return UnlockMutex(std::addressof(m_mutex)); + } + + bool try_lock() { + return TryLockMutex(std::addressof(m_mutex)); + } + + bool IsLockedByCurrentThread() const { + return IsMutexLockedByCurrentThread(std::addressof(m_mutex)); + } + + ALWAYS_INLINE void Lock() { + return this->lock(); + } + + ALWAYS_INLINE void Unlock() { + return this->unlock(); + } + + ALWAYS_INLINE bool TryLock() { + return this->try_lock(); + } + + operator MutexType &() { + return m_mutex; + } + + operator const MutexType &() const { + return m_mutex; + } + + MutexType *GetBase() { + return std::addressof(m_mutex); + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_mutex_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_mutex_api.hpp new file mode 100644 index 00000000..02a1e8b0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_mutex_api.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_mutex_common.hpp> + +namespace ams::os { + + struct MutexType; + + void InitializeMutex(MutexType *mutex, bool recursive, int lock_level); + void FinalizeMutex(MutexType *mutex); + + void LockMutex(MutexType *mutex); + bool TryLockMutex(MutexType *mutex); + void UnlockMutex(MutexType *mutex); + + bool IsMutexLockedByCurrentThread(const MutexType *mutex); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_mutex_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_mutex_common.hpp new file mode 100644 index 00000000..853b1fb9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_mutex_common.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + constexpr inline s32 MutexLockLevelMin = 1; + constexpr inline s32 MutexLockLevelMax = BITSIZEOF(s32) - 1; + constexpr inline s32 MutexLockLevelInitial = 0; + + constexpr inline s32 MutexRecursiveLockCountMax = (1 << BITSIZEOF(u16)) - 1; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_mutex_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_mutex_types.hpp new file mode 100644 index 00000000..69c750d2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_mutex_types.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> + +namespace ams::os { + + struct ThreadType; + + struct MutexType { + enum State { + State_NotInitialized = 0, + State_Initialized = 1, + }; + + u8 state; + bool is_recursive; + s32 lock_level; + s32 nest_count; + ThreadType *owner_thread; + union { + s32 _arr[sizeof(impl::InternalCriticalSectionStorage) / sizeof(s32)]; + impl::InternalCriticalSectionStorage _storage; + impl::InternalCriticalSectionStorageTypeForConstantInitialize _storage_for_constant_initialize; + }; + }; + static_assert(std::is_trivial<MutexType>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_native_handle.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_native_handle.hpp new file mode 100644 index 00000000..225fd661 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_native_handle.hpp @@ -0,0 +1,18 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/os/os_native_handle_types.hpp> +#include <stratosphere/os/os_native_handle_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_native_handle_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_native_handle_api.hpp new file mode 100644 index 00000000..aec26b87 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_native_handle_api.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_native_handle_types.hpp> + +namespace ams::os { + + void CloseNativeHandle(NativeHandle handle); + + NativeHandle GetCurrentProcessHandle(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_native_handle_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_native_handle_types.hpp new file mode 100644 index 00000000..32b12031 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_native_handle_types.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + #if defined(ATMOSPHERE_OS_HORIZON) + + using NativeHandle = svc::Handle; + static_assert(std::unsigned_integral<NativeHandle>); + + constexpr inline NativeHandle InvalidNativeHandle = svc::InvalidHandle; + + #elif defined(ATMOSPHERE_OS_WINDOWS) + + using NativeHandle = void *; + + constexpr inline NativeHandle InvalidNativeHandle = nullptr; + + #elif defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS) + + using NativeHandle = s32; + + constexpr inline NativeHandle InvalidNativeHandle = -1; + + #else + #error "Unknown OS for os::NativeHandle" + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_process_code_memory_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_process_code_memory_api.hpp new file mode 100644 index 00000000..27163ec9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_process_code_memory_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_native_handle.hpp> +#include <stratosphere/os/os_memory_common.hpp> + +namespace ams::os { + + struct ProcessMemoryRegion { + u64 address; + u64 size; + }; + + Result MapProcessCodeMemory(u64 *out, NativeHandle handle, const ProcessMemoryRegion *regions, size_t num_regions, AddressSpaceGenerateRandomFunction generate_random); + Result UnmapProcessCodeMemory(NativeHandle handle, u64 process_code_address, const ProcessMemoryRegion *regions, size_t num_regions); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_process_handle_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_process_handle_api.hpp new file mode 100644 index 00000000..e1284115 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_process_handle_api.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_native_handle.hpp> +#include <stratosphere/ncm/ncm_program_id.hpp> + +namespace ams::os { + + Result GetProcessId(os::ProcessId *out, NativeHandle handle); + + ALWAYS_INLINE ProcessId GetProcessId(NativeHandle handle) { + ProcessId process_id; + R_ABORT_UNLESS(GetProcessId(std::addressof(process_id), handle)); + return process_id; + } + + ALWAYS_INLINE ProcessId GetCurrentProcessId() { + return GetProcessId(GetCurrentProcessHandle()); + } + + Result GetProgramId(ncm::ProgramId *out, NativeHandle handle); + + ALWAYS_INLINE ncm::ProgramId GetProgramId(NativeHandle handle) { + ncm::ProgramId program_id; + R_ABORT_UNLESS(GetProgramId(std::addressof(program_id), handle)); + return program_id; + } + + ALWAYS_INLINE ncm::ProgramId GetCurrentProgramId() { + return GetProgramId(GetCurrentProcessHandle()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_process_memory_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_process_memory_api.hpp new file mode 100644 index 00000000..ab4eda4f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_process_memory_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_native_handle.hpp> +#include <stratosphere/os/os_memory_common.hpp> + +namespace ams::os { + + Result MapProcessMemory(void **out, NativeHandle handle, u64 process_address, size_t process_size, AddressSpaceGenerateRandomFunction generate_random); + void UnmapProcessMemory(void *mapped_memory, NativeHandle handle, u64 process_address, size_t process_size); + + Result SetProcessMemoryPermission(NativeHandle handle, u64 process_address, u64 process_size, MemoryPermission perm); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_random.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_random.hpp new file mode 100644 index 00000000..500b165b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_random.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_common_types.hpp> + +namespace ams::os { + + void GenerateRandomBytes(void *dst, size_t size); + + /* Convenience API. */ + u32 GenerateRandomU32(u32 max = std::numeric_limits<u32>::max()); + u64 GenerateRandomU64(u64 max = std::numeric_limits<u64>::max()); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_busy_mutex.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_busy_mutex.hpp new file mode 100644 index 00000000..86d98da4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_busy_mutex.hpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_rw_busy_mutex_types.hpp> +#include <stratosphere/os/os_rw_busy_mutex_api.hpp> + +namespace ams::os { + + class ReaderWriterBusyMutex { + NON_COPYABLE(ReaderWriterBusyMutex); + NON_MOVEABLE(ReaderWriterBusyMutex); + private: + ReaderWriterBusyMutexType m_rw_mutex; + public: + constexpr explicit ReaderWriterBusyMutex() : m_rw_mutex{ { AMS_OS_INTERNAL_READER_WRITER_BUSY_MUTEX_IMPL_CONSTANT_INITIALIZER } } { /* ... */ } + + void AcquireReadLock() { + return os::AcquireReadLockBusyMutex(std::addressof(m_rw_mutex)); + } + + void ReleaseReadLock() { + return os::ReleaseReadLockBusyMutex(std::addressof(m_rw_mutex)); + } + + void AcquireWriteLock() { + return os::AcquireWriteLockBusyMutex(std::addressof(m_rw_mutex)); + } + + void ReleaseWriteLock() { + return os::ReleaseWriteLockBusyMutex(std::addressof(m_rw_mutex)); + } + + void lock_shared() { + return this->AcquireReadLock(); + } + + void unlock_shared() { + return this->ReleaseReadLock(); + } + + void lock() { + return this->AcquireWriteLock(); + } + + void unlock() { + return this->ReleaseWriteLock(); + } + + operator ReaderWriterBusyMutexType &() { + return m_rw_mutex; + } + + operator const ReaderWriterBusyMutexType &() const { + return m_rw_mutex; + } + + ReaderWriterBusyMutexType *GetBase() { + return std::addressof(m_rw_mutex); + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_busy_mutex_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_busy_mutex_api.hpp new file mode 100644 index 00000000..1992b9d1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_busy_mutex_api.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + struct ReaderWriterBusyMutexType; + + void InitalizeReaderWriterLockBusyMutex(ReaderWriterBusyMutexType *rw_mutex); + + void AcquireReadLockBusyMutex(ReaderWriterBusyMutexType *rw_mutex); + void ReleaseReadLockBusyMutex(ReaderWriterBusyMutexType *rw_mutex); + + void AcquireWriteLockBusyMutex(ReaderWriterBusyMutexType *rw_mutex); + void ReleaseWriteLockBusyMutex(ReaderWriterBusyMutexType *rw_mutex); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_busy_mutex_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_busy_mutex_types.hpp new file mode 100644 index 00000000..4daef18f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_busy_mutex_types.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_rw_busy_mutex.hpp> + +namespace ams::os { + + struct ReaderWriterBusyMutexType { + union { + s32 _arr[sizeof(impl::InternalReaderWriterBusyMutexStorage) / sizeof(s32)]; + impl::InternalReaderWriterBusyMutexStorage _storage; + }; + }; + static_assert(std::is_trivial<ReaderWriterBusyMutexType>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_lock.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_lock.hpp new file mode 100644 index 00000000..1bb1f994 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_lock.hpp @@ -0,0 +1,113 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_rw_lock_common.hpp> +#include <stratosphere/os/os_rw_lock_types.hpp> +#include <stratosphere/os/os_rw_lock_api.hpp> + +namespace ams::os { + + class ReaderWriterLock { + NON_COPYABLE(ReaderWriterLock); + NON_MOVEABLE(ReaderWriterLock); + private: + ReaderWriterLockType m_rw_lock; + public: + constexpr explicit ReaderWriterLock() : m_rw_lock {{ { MakeConstantInitializedLockCount(), 0 } }, 0, ::ams::os::ReaderWriterLockType::State_Initialized, nullptr, 0, {AMS_OS_INTERNAL_CONDITION_VARIABLE_IMPL_CONSTANT_INITIALIZER}, {AMS_OS_INTERNAL_CONDITION_VARIABLE_IMPL_CONSTANT_INITIALIZER} } { + /* ... */ + } + + constexpr ~ReaderWriterLock() { + if (!std::is_constant_evaluated()) { + os::FinalizeReaderWriterLock(std::addressof(m_rw_lock)); + } + } + + void AcquireReadLock() { + return os::AcquireReadLock(std::addressof(m_rw_lock)); + } + + bool TryAcquireReadLock() { + return os::TryAcquireReadLock(std::addressof(m_rw_lock)); + } + + void ReleaseReadLock() { + return os::ReleaseReadLock(std::addressof(m_rw_lock)); + } + + void AcquireWriteLock() { + return os::AcquireWriteLock(std::addressof(m_rw_lock)); + } + + bool TryAcquireWriteLock() { + return os::TryAcquireWriteLock(std::addressof(m_rw_lock)); + } + + void ReleaseWriteLock() { + return os::ReleaseWriteLock(std::addressof(m_rw_lock)); + } + + bool IsReadLockHeld() const { + return os::IsReadLockHeld(std::addressof(m_rw_lock)); + } + + bool IsWriteLockHeldByCurrentThread() const { + return os::IsWriteLockHeldByCurrentThread(std::addressof(m_rw_lock)); + } + + bool IsLockOwner() const { + return os::IsReaderWriterLockOwnerThread(std::addressof(m_rw_lock)); + } + + void lock_shared() { + return this->AcquireReadLock(); + } + + bool try_lock_shared() { + return this->TryAcquireReadLock(); + } + + void unlock_shared() { + return this->ReleaseReadLock(); + } + + void lock() { + return this->AcquireWriteLock(); + } + + bool try_lock() { + return this->TryAcquireWriteLock(); + } + + void unlock() { + return this->ReleaseWriteLock(); + } + + operator ReaderWriterLockType &() { + return m_rw_lock; + } + + operator const ReaderWriterLockType &() const { + return m_rw_lock; + } + + ReaderWriterLockType *GetBase() { + return std::addressof(m_rw_lock); + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_lock_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_lock_api.hpp new file mode 100644 index 00000000..a796cf43 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_lock_api.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_rw_lock_common.hpp> + +namespace ams::os { + + struct ReaderWriterLockType; + + void InitializeReaderWriterLock(ReaderWriterLockType *rw_lock); + void FinalizeReaderWriterLock(ReaderWriterLockType *rw_lock); + + void AcquireReadLock(ReaderWriterLockType *rw_lock); + bool TryAcquireReadLock(ReaderWriterLockType *rw_lock); + void ReleaseReadLock(ReaderWriterLockType *rw_lock); + + void AcquireWriteLock(ReaderWriterLockType *rw_lock); + bool TryAcquireWriteLock(ReaderWriterLockType *rw_lock); + void ReleaseWriteLock(ReaderWriterLockType *rw_lock); + + bool IsReadLockHeld(const ReaderWriterLockType *rw_lock); + bool IsWriteLockHeldByCurrentThread(const ReaderWriterLockType *rw_lock); + bool IsReaderWriterLockOwnerThread(const ReaderWriterLockType *rw_lock); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_lock_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_lock_common.hpp new file mode 100644 index 00000000..fb476db0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_lock_common.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + constexpr inline s32 ReaderWriterLockCountMax = (1 << (BITSIZEOF(u16) - 1)) - 1; + constexpr inline s32 ReaderWriterLockWaiterCountMax = (1 << BITSIZEOF(u8)) - 1; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_lock_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_lock_types.hpp new file mode 100644 index 00000000..51efc5c3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_rw_lock_types.hpp @@ -0,0 +1,88 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> +#include <stratosphere/os/impl/os_internal_condition_variable.hpp> + +namespace ams::os { + + struct ThreadType; + + struct ReaderWriterLockType { + enum State { + State_NotInitialized = 0, + State_Initialized = 1, + }; + + struct LockCount { + union { + s32 _arr[sizeof(impl::InternalCriticalSectionStorage) / sizeof(s32)]; + impl::InternalCriticalSectionStorage cs_storage; + impl::InternalCriticalSectionStorageTypeForConstantInitialize _storage_for_constant_initialize; + }; + util::BitPack32 counter; + }; + static_assert(util::is_pod<LockCount>::value); + static_assert(std::is_trivial<LockCount>::value); + + union { + struct { + LockCount c; + u32 write_lock_count; + } aligned; + struct { + u32 write_lock_count; + LockCount c; + } not_aligned; + } lock_count; + + u32 reserved_1; + + u8 state; + ThreadType *owner_thread; + u32 reserved_2; + + union { + s32 _arr[sizeof(impl::InternalConditionVariableStorage) / sizeof(s32)]; + impl::InternalConditionVariableStorage _storage; + impl::InternalConditionVariableStorageTypeForConstantInitialize _storage_for_constant_initialize; + } cv_read_lock; + union { + s32 _arr[sizeof(impl::InternalConditionVariableStorage) / sizeof(s32)]; + impl::InternalConditionVariableStorage _storage; + impl::InternalConditionVariableStorageTypeForConstantInitialize _storage_for_constant_initialize; + } cv_write_lock; + }; + static_assert(std::is_trivial<ReaderWriterLockType>::value); + + #if defined(ATMOSPHERE_OS_HORIZON) + consteval ReaderWriterLockType::LockCount MakeConstantInitializedLockCount() { return {}; } + #elif defined(ATMOSPHERE_OS_WINDOWS) || defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS) + /* If windows/linux, require that the lock counter have guaranteed alignment, so that we may constant-initialize. */ + static_assert(alignof(ReaderWriterLockType) == sizeof(u64)); + consteval ReaderWriterLockType::LockCount MakeConstantInitializedLockCount() { + return ReaderWriterLockType::LockCount { + { AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CONSTANT_INITIALIZER }, + {}, + }; + } + #else + #error "Unknown OS for constant initialized RW-lock LockCount" + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_condition_variable.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_condition_variable.hpp new file mode 100644 index 00000000..55d3ea77 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_condition_variable.hpp @@ -0,0 +1,83 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_sdk_mutex.hpp> +#include <stratosphere/os/os_sdk_recursive_mutex.hpp> +#include <stratosphere/os/impl/os_internal_condition_variable.hpp> + +namespace ams::os { + + struct SdkConditionVariableType { + union { + s32 _arr[sizeof(impl::InternalConditionVariableStorage) / sizeof(s32)]; + impl::InternalConditionVariableStorage _storage; + impl::InternalConditionVariableStorageTypeForConstantInitialize _storage_for_constant_initialize; + }; + + ALWAYS_INLINE void Initialize() { + GetReference(this->_storage).Initialize(); + } + + void Wait(SdkMutexType &mutex); + bool TimedWait(SdkMutexType &mutex, TimeSpan timeout); + + void Wait(SdkRecursiveMutexType &mutex); + bool TimedWait(SdkRecursiveMutexType &mutex, TimeSpan timeout); + + ALWAYS_INLINE void Signal() { + GetReference(this->_storage).Signal(); + } + + ALWAYS_INLINE void Broadcast() { + GetReference(this->_storage).Broadcast(); + } + }; + static_assert(std::is_trivial<SdkConditionVariableType>::value); + + class SdkConditionVariable { + private: + SdkConditionVariableType m_cv; + public: + constexpr SdkConditionVariable() : m_cv{{AMS_OS_INTERNAL_CONDITION_VARIABLE_IMPL_CONSTANT_INITIALIZER}} { /* ... */ } + + ALWAYS_INLINE void Wait(SdkMutex &m) { + return m_cv.Wait(m.m_mutex); + } + + ALWAYS_INLINE bool TimedWait(SdkMutex &m, TimeSpan timeout) { + return m_cv.TimedWait(m.m_mutex, timeout); + } + + ALWAYS_INLINE void Wait(SdkRecursiveMutex &m) { + return m_cv.Wait(m.m_mutex); + } + + ALWAYS_INLINE bool TimedWait(SdkRecursiveMutex &m, TimeSpan timeout) { + return m_cv.TimedWait(m.m_mutex, timeout); + } + + ALWAYS_INLINE void Signal() { + return m_cv.Signal(); + } + + ALWAYS_INLINE void Broadcast() { + return m_cv.Broadcast(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_mutex.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_mutex.hpp new file mode 100644 index 00000000..d006413b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_mutex.hpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> + +namespace ams::os { + + class SdkConditionVariable; + + struct ThreadType; + + struct SdkMutexType { + #if !defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + os::ThreadType *owner_thread; + #endif + union { + s32 _arr[sizeof(impl::InternalCriticalSectionStorage) / sizeof(s32)]; + impl::InternalCriticalSectionStorage _storage; + impl::InternalCriticalSectionStorageTypeForConstantInitialize _storage_for_constant_initialize; + }; + }; + static_assert(std::is_trivial<SdkMutexType>::value); + + void InitializeSdkMutex(SdkMutexType *mutex); + + void LockSdkMutex(SdkMutexType *mutex); + bool TryLockSdkMutex(SdkMutexType *mutex); + void UnlockSdkMutex(SdkMutexType *mutex); + + bool IsSdkMutexLockedByCurrentThread(const SdkMutexType *mutex); + + class SdkMutex { + private: + friend class SdkConditionVariable; + private: + SdkMutexType m_mutex; + public: + #if defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + constexpr SdkMutex() : m_mutex{{AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CONSTANT_INITIALIZER}} { /* ... */ } + #else + constexpr SdkMutex() : m_mutex{nullptr, {AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CONSTANT_INITIALIZER}} { /* ... */ } + #endif + + ALWAYS_INLINE void Lock() { return os::LockSdkMutex(std::addressof(m_mutex)); } + ALWAYS_INLINE bool TryLock() { return os::TryLockSdkMutex(std::addressof(m_mutex)); } + ALWAYS_INLINE void Unlock() { return os::UnlockSdkMutex(std::addressof(m_mutex)); } + + ALWAYS_INLINE bool IsLockedByCurrentThread() const { return os::IsSdkMutexLockedByCurrentThread(std::addressof(m_mutex)); } + + ALWAYS_INLINE void lock() { return this->Lock(); } + ALWAYS_INLINE bool try_lock() { return this->TryLock(); } + ALWAYS_INLINE void unlock() { return this->Unlock(); } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_recursive_mutex.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_recursive_mutex.hpp new file mode 100644 index 00000000..ecdf1477 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_recursive_mutex.hpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> + +namespace ams::os { + + class SdkConditionVariable; + + struct SdkRecursiveMutexType { + #if !defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + os::ThreadType *owner_thread; + #endif + union { + s32 _arr[sizeof(impl::InternalCriticalSectionStorage) / sizeof(s32)]; + impl::InternalCriticalSectionStorage _storage; + impl::InternalCriticalSectionStorageTypeForConstantInitialize _storage_for_constant_initialize; + }; + u32 recursive_count; + }; + static_assert(std::is_trivial<SdkRecursiveMutexType>::value); + + void InitializeSdkRecursiveMutex(SdkRecursiveMutexType *rmutex); + + void LockSdkRecursiveMutex(SdkRecursiveMutexType *rmutex); + bool TryLockSdkRecursiveMutex(SdkRecursiveMutexType *rmutex); + void UnlockSdkRecursiveMutex(SdkRecursiveMutexType *rmutex); + + bool IsSdkRecursiveMutexLockedByCurrentThread(const SdkRecursiveMutexType *rmutex); + + class SdkRecursiveMutex { + private: + friend class SdkConditionVariable; + private: + SdkRecursiveMutexType m_mutex; + public: + #if defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + constexpr SdkRecursiveMutex() : m_mutex{ { AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CONSTANT_INITIALIZER }, 0 } { /* ... */ } + #else + constexpr SdkRecursiveMutex() : m_mutex{ nullptr, { AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CONSTANT_INITIALIZER }, 0 } { /* ... */ } + #endif + + ALWAYS_INLINE void Lock() { return os::LockSdkRecursiveMutex(std::addressof(m_mutex)); } + ALWAYS_INLINE bool TryLock() { return os::TryLockSdkRecursiveMutex(std::addressof(m_mutex)); } + ALWAYS_INLINE void Unlock() { return os::UnlockSdkRecursiveMutex(std::addressof(m_mutex)); } + + ALWAYS_INLINE bool IsLockedByCurrentThread() const { return os::IsSdkRecursiveMutexLockedByCurrentThread(std::addressof(m_mutex)); } + + ALWAYS_INLINE void lock() { return this->Lock(); } + ALWAYS_INLINE bool try_lock() { return this->TryLock(); } + ALWAYS_INLINE void unlock() { return this->Unlock(); } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_reply_and_receive.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_reply_and_receive.hpp new file mode 100644 index 00000000..ad5a185c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_reply_and_receive.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_native_handle.hpp> + +namespace ams::os { + + struct MultiWaitHolderType; + struct MultiWaitType; + + Result SdkReplyAndReceive(os::MultiWaitHolderType **out, NativeHandle reply_target, MultiWaitType *multi_wait); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_api.hpp new file mode 100644 index 00000000..923ce91c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_api.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_common_types.hpp> +#include <stratosphere/os/os_thread_types.hpp> +#include <stratosphere/os/os_sdk_thread_types.hpp> +#include <stratosphere/os/os_thread_local_storage_common.hpp> + +namespace ams::os { + + ALWAYS_INLINE SdkInternalTlsType *GetSdkInternalTlsArray(ThreadType *thread = os::GetCurrentThread()) { + #if defined(ATMOSPHERE_OS_HORIZON) + return std::addressof(thread->sdk_internal_tls); + #else + return reinterpret_cast<SdkInternalTlsType *>(std::addressof(thread->tls_value_array[TlsSlotCountMax])); + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_info.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_info.hpp new file mode 100644 index 00000000..933c0120 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_info.hpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_sdk_thread_info_types.hpp> +#include <stratosphere/os/os_sdk_thread_info_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_info_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_info_api.hpp new file mode 100644 index 00000000..375ba73b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_info_api.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_sdk_thread_info_types.hpp> +#include <stratosphere/os/os_thread_types.hpp> + +namespace ams::os { + + void GetThreadStackInfo(uintptr_t *out_stack_top, size_t *out_stack_size, const ThreadType *thread); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_info_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_info_types.hpp new file mode 100644 index 00000000..dafc5150 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_info_types.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/os/os_common_types.hpp> + +namespace ams::os { + + enum SdkLastThreadInfoFlag : u32 { + SdkLastThreadInfoFlag_ThreadInSystemCall = (1u << 0), + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_local_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_local_storage.hpp new file mode 100644 index 00000000..7e9c357e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_local_storage.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_thread_local_storage.hpp> +#include <stratosphere/os/os_sdk_thread_local_storage_api.hpp> + +namespace ams::os { + + class SdkThreadLocalStorage { + NON_COPYABLE(SdkThreadLocalStorage); + NON_MOVEABLE(SdkThreadLocalStorage); + private: + TlsSlot m_tls_slot; + public: + SdkThreadLocalStorage() { + R_ABORT_UNLESS(os::SdkAllocateTlsSlot(std::addressof(m_tls_slot), nullptr)); + } + + explicit SdkThreadLocalStorage(TlsDestructor destructor) { + R_ABORT_UNLESS(os::SdkAllocateTlsSlot(std::addressof(m_tls_slot), destructor)); + } + + ~SdkThreadLocalStorage() { + os::FreeTlsSlot(m_tls_slot); + } + + uintptr_t GetValue() const { return os::GetTlsValue(m_tls_slot); } + void SetValue(uintptr_t value) { return os::SetTlsValue(m_tls_slot, value); } + + TlsSlot GetTlsSlot() const { return m_tls_slot; } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_local_storage_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_local_storage_api.hpp new file mode 100644 index 00000000..516e33a5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_local_storage_api.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_common_types.hpp> +#include <stratosphere/os/os_memory_common.hpp> +#include <stratosphere/os/os_thread_local_storage_common.hpp> + +namespace ams::os { + + Result SdkAllocateTlsSlot(TlsSlot *out, TlsDestructor destructor); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_types.hpp new file mode 100644 index 00000000..7d4266d7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_sdk_thread_types.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_common_types.hpp> +#include <stratosphere/os/os_thread_local_storage_common.hpp> + +namespace ams::os { + + struct SdkInternalTlsType { + uintptr_t sf_inline_context; + }; + static_assert(util::is_pod<SdkInternalTlsType>::value); + static_assert((sizeof(SdkInternalTlsType) % sizeof(uintptr_t)) == 0); + + constexpr inline size_t SdkInternalTlsCount = sizeof(SdkInternalTlsType) / sizeof(uintptr_t); + static_assert(SdkInternalTlsCount <= SdkTlsSlotCountMax); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_semaphore.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_semaphore.hpp new file mode 100644 index 00000000..5c7fd03f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_semaphore.hpp @@ -0,0 +1,71 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/os/os_semaphore_types.hpp> +#include <stratosphere/os/os_semaphore_api.hpp> + +namespace ams::os { + + class Semaphore { + NON_COPYABLE(Semaphore); + NON_MOVEABLE(Semaphore); + private: + SemaphoreType m_sema; + public: + explicit Semaphore(s32 count, s32 max_count) { + InitializeSemaphore(std::addressof(m_sema), count, max_count); + } + + ~Semaphore() { FinalizeSemaphore(std::addressof(m_sema)); } + + void Acquire() { + return os::AcquireSemaphore(std::addressof(m_sema)); + } + + bool TryAcquire() { + return os::TryAcquireSemaphore(std::addressof(m_sema)); + } + + bool TimedAcquire(TimeSpan timeout) { + return os::TimedAcquireSemaphore(std::addressof(m_sema), timeout); + } + + void Release() { + return os::ReleaseSemaphore(std::addressof(m_sema)); + } + + void Release(s32 count) { + return os::ReleaseSemaphore(std::addressof(m_sema), count); + } + + s32 GetCurrentCount() const { + return os::GetCurrentSemaphoreCount(std::addressof(m_sema)); + } + + operator SemaphoreType &() { + return m_sema; + } + + operator const SemaphoreType &() const { + return m_sema; + } + + SemaphoreType *GetBase() { + return std::addressof(m_sema); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_semaphore_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_semaphore_api.hpp new file mode 100644 index 00000000..d529cc8a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_semaphore_api.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + struct SemaphoreType; + struct MultiWaitHolderType; + + void InitializeSemaphore(SemaphoreType *sema, s32 count, s32 max_count); + void FinalizeSemaphore(SemaphoreType *sema); + + void AcquireSemaphore(SemaphoreType *sema); + bool TryAcquireSemaphore(SemaphoreType *sema); + bool TimedAcquireSemaphore(SemaphoreType *sema, TimeSpan timeout); + + void ReleaseSemaphore(SemaphoreType *sema); + void ReleaseSemaphore(SemaphoreType *sema, s32 count); + + s32 GetCurrentSemaphoreCount(const SemaphoreType *sema); + + void InitializeMultiWaitHolder(MultiWaitHolderType *multi_wait_holder, SemaphoreType *sema); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_semaphore_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_semaphore_types.hpp new file mode 100644 index 00000000..4946a7ca --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_semaphore_types.hpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> +#include <stratosphere/os/impl/os_internal_condition_variable.hpp> + +namespace ams::os { + + namespace impl { + + class MultiWaitObjectList; + + } + + struct SemaphoreType { + enum State { + State_NotInitialized = 0, + State_Initialized = 1, + }; + + util::TypedStorage<impl::MultiWaitObjectList, sizeof(util::IntrusiveListNode), alignof(util::IntrusiveListNode)> waitlist; + u8 state; + s32 count; + s32 max_count; + + impl::InternalCriticalSectionStorage cs_sema; + impl::InternalConditionVariableStorage cv_not_zero; + }; + static_assert(std::is_trivial<SemaphoreType>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_shared_memory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_shared_memory.hpp new file mode 100644 index 00000000..a0d927f1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_shared_memory.hpp @@ -0,0 +1,86 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_shared_memory_types.hpp> +#include <stratosphere/os/os_shared_memory_api.hpp> + +namespace ams::os { + + class SharedMemory { + NON_COPYABLE(SharedMemory); + NON_MOVEABLE(SharedMemory); + private: + SharedMemoryType m_shared_memory; + public: + constexpr SharedMemory() : m_shared_memory{ .state = SharedMemoryType::State_NotInitialized } { + /* ... */ + } + + SharedMemory(size_t size, MemoryPermission my_perm, MemoryPermission other_perm) { + R_ABORT_UNLESS(CreateSharedMemory(std::addressof(m_shared_memory), size, my_perm, other_perm)); + } + + SharedMemory(size_t size, NativeHandle handle, bool managed) { + this->Attach(size, handle, managed); + } + + ~SharedMemory() { + if (m_shared_memory.state == SharedMemoryType::State_NotInitialized) { + return; + } + DestroySharedMemory(std::addressof(m_shared_memory)); + } + + void Attach(size_t size, NativeHandle handle, bool managed) { + return AttachSharedMemory(std::addressof(m_shared_memory), size, handle, managed); + } + + void *Map(MemoryPermission perm) { + return MapSharedMemory(std::addressof(m_shared_memory), perm); + } + + void Unmap() { + return UnmapSharedMemory(std::addressof(m_shared_memory)); + } + + void *GetMappedAddress() const { + return GetSharedMemoryAddress(std::addressof(m_shared_memory)); + } + + size_t GetSize() const { + return GetSharedMemorySize(std::addressof(m_shared_memory)); + } + + NativeHandle GetHandle() const { + return GetSharedMemoryHandle(std::addressof(m_shared_memory)); + } + + operator SharedMemoryType &() { + return m_shared_memory; + } + + operator const SharedMemoryType &() const { + return m_shared_memory; + } + + SharedMemoryType *GetBase() { + return std::addressof(m_shared_memory); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_shared_memory_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_shared_memory_api.hpp new file mode 100644 index 00000000..c5937821 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_shared_memory_api.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_memory_permission.hpp> +#include <stratosphere/os/os_native_handle.hpp> + +namespace ams::os { + + struct SharedMemoryType; + + Result CreateSharedMemory(SharedMemoryType *shared_memory, size_t size, MemoryPermission my_perm, MemoryPermission other_perm); + + void AttachSharedMemory(SharedMemoryType *shared_memory, size_t size, NativeHandle handle, bool managed); + + void DestroySharedMemory(SharedMemoryType *shared_memory); + + void *MapSharedMemory(SharedMemoryType *shared_memory, MemoryPermission perm); + void UnmapSharedMemory(SharedMemoryType *shared_memory); + + void *GetSharedMemoryAddress(const SharedMemoryType *shared_memory); + size_t GetSharedMemorySize(const SharedMemoryType *shared_memory); + NativeHandle GetSharedMemoryHandle(const SharedMemoryType *shared_memory); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_shared_memory_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_shared_memory_types.hpp new file mode 100644 index 00000000..b6d9e815 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_shared_memory_types.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> +#include <stratosphere/os/os_native_handle.hpp> + +namespace ams::os { + + struct SharedMemoryType { + enum State { + State_NotInitialized = 0, + State_Initialized = 1, + State_Mapped = 2, + }; + + u8 state; + bool handle_managed; + bool allocated; + + void *address; + size_t size; + NativeHandle handle; + + mutable impl::InternalCriticalSectionStorage cs_shared_memory; + }; + static_assert(std::is_trivial<SharedMemoryType>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_system_event.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_system_event.hpp new file mode 100644 index 00000000..9a5e3136 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_system_event.hpp @@ -0,0 +1,113 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_event_common.hpp> +#include <stratosphere/os/os_system_event_types.hpp> +#include <stratosphere/os/os_system_event_api.hpp> + +namespace ams::os { + + class SystemEvent { + NON_COPYABLE(SystemEvent); + NON_MOVEABLE(SystemEvent); + private: + SystemEventType m_system_event; + public: + SystemEvent() { + m_system_event.state = SystemEventType::State_NotInitialized; + } + + explicit SystemEvent(EventClearMode clear_mode, bool inter_process) { + R_ABORT_UNLESS(CreateSystemEvent(std::addressof(m_system_event), clear_mode, inter_process)); + } + + explicit SystemEvent(NativeHandle read_handle, bool manage_read_handle, NativeHandle write_handle, bool manage_write_handle, EventClearMode clear_mode) { + AttachSystemEvent(std::addressof(m_system_event), read_handle, manage_read_handle, write_handle, manage_write_handle, clear_mode); + } + + ~SystemEvent() { + if (m_system_event.state == SystemEventType::State_NotInitialized) { + return; + } + DestroySystemEvent(std::addressof(m_system_event)); + } + + void Attach(NativeHandle read_handle, bool manage_read_handle, NativeHandle write_handle, bool manage_write_handle, EventClearMode clear_mode) { + AMS_ABORT_UNLESS(m_system_event.state == SystemEventType::State_NotInitialized); + return AttachSystemEvent(std::addressof(m_system_event), read_handle, manage_read_handle, write_handle, manage_write_handle, clear_mode); + } + + void AttachReadableHandle(NativeHandle read_handle, bool manage_read_handle, EventClearMode clear_mode) { + AMS_ABORT_UNLESS(m_system_event.state == SystemEventType::State_NotInitialized); + return AttachReadableHandleToSystemEvent(std::addressof(m_system_event), read_handle, manage_read_handle, clear_mode); + } + + void AttachWritableHandle(NativeHandle write_handle, bool manage_write_handle, EventClearMode clear_mode) { + AMS_ABORT_UNLESS(m_system_event.state == SystemEventType::State_NotInitialized); + return AttachWritableHandleToSystemEvent(std::addressof(m_system_event), write_handle, manage_write_handle, clear_mode); + } + + NativeHandle DetachReadableHandle() { + return DetachReadableHandleOfSystemEvent(std::addressof(m_system_event)); + } + + NativeHandle DetachWritableHandle() { + return DetachWritableHandleOfSystemEvent(std::addressof(m_system_event)); + } + + void Wait() { + return WaitSystemEvent(std::addressof(m_system_event)); + } + + bool TryWait() { + return TryWaitSystemEvent(std::addressof(m_system_event)); + } + + bool TimedWait(TimeSpan timeout) { + return TimedWaitSystemEvent(std::addressof(m_system_event), timeout); + } + + void Signal() { + return SignalSystemEvent(std::addressof(m_system_event)); + } + + void Clear() { + return ClearSystemEvent(std::addressof(m_system_event)); + } + + NativeHandle GetReadableHandle() const { + return GetReadableHandleOfSystemEvent(std::addressof(m_system_event)); + } + + NativeHandle GetWritableHandle() const { + return GetWritableHandleOfSystemEvent(std::addressof(m_system_event)); + } + + operator SystemEventType &() { + return m_system_event; + } + + operator const SystemEventType &() const { + return m_system_event; + } + + SystemEventType *GetBase() { + return std::addressof(m_system_event); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_system_event_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_system_event_api.hpp new file mode 100644 index 00000000..b14bf64f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_system_event_api.hpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_event_common.hpp> +#include <stratosphere/os/os_native_handle.hpp> + +namespace ams::os { + + struct SystemEventType; + struct MultiWaitHolderType; + + Result CreateSystemEvent(SystemEventType *event, EventClearMode clear_mode, bool inter_process); + void DestroySystemEvent(SystemEventType *event); + + void AttachSystemEvent(SystemEventType *event, NativeHandle read_handle, bool read_handle_managed, NativeHandle write_handle, bool write_handle_managed, EventClearMode clear_mode); + void AttachReadableHandleToSystemEvent(SystemEventType *event, NativeHandle read_handle, bool manage_read_handle, EventClearMode clear_mode); + void AttachWritableHandleToSystemEvent(SystemEventType *event, NativeHandle write_handle, bool manage_write_handle, EventClearMode clear_mode); + + NativeHandle DetachReadableHandleOfSystemEvent(SystemEventType *event); + NativeHandle DetachWritableHandleOfSystemEvent(SystemEventType *event); + + NativeHandle GetReadableHandleOfSystemEvent(const SystemEventType *event); + NativeHandle GetWritableHandleOfSystemEvent(const SystemEventType *event); + + void SignalSystemEvent(SystemEventType *event); + void WaitSystemEvent(SystemEventType *event); + bool TryWaitSystemEvent(SystemEventType *event); + bool TimedWaitSystemEvent(SystemEventType *event, TimeSpan timeout); + void ClearSystemEvent(SystemEventType *event); + + void InitializeMultiWaitHolder(MultiWaitHolderType *multi_wait_holder, SystemEventType *event); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_system_event_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_system_event_types.hpp new file mode 100644 index 00000000..b0624cb0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_system_event_types.hpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_event_types.hpp> +#include <stratosphere/os/os_native_handle.hpp> + +namespace ams::os { + + namespace impl { + + struct InterProcessEventType { + enum State { + State_NotInitialized = 0, + State_Initialized = 1, + }; + + util::TypedStorage<impl::MultiWaitObjectList, sizeof(util::IntrusiveListNode), alignof(util::IntrusiveListNode)> multi_wait_object_list_storage; + + bool auto_clear; + u8 state; + bool is_readable_handle_managed; + bool is_writable_handle_managed; + NativeHandle readable_handle; + NativeHandle writable_handle; + }; + static_assert(std::is_trivial<InterProcessEventType>::value); + + } + + struct SystemEventType { + enum State { + State_NotInitialized = 0, + State_InitializedAsEvent = 1, + State_InitializedAsInterProcessEvent = 2, + }; + + union { + EventType event; + impl::InterProcessEventType inter_process_event; + }; + + u8 state; + }; + static_assert(std::is_trivial<SystemEventType>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread.hpp new file mode 100644 index 00000000..c2c152fc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread.hpp @@ -0,0 +1,21 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_thread_common.hpp> +#include <stratosphere/os/os_thread_types.hpp> +#include <stratosphere/os/os_thread_api.hpp> + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread_api.hpp new file mode 100644 index 00000000..b946b800 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread_api.hpp @@ -0,0 +1,65 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_thread_common.hpp> + +namespace ams::os { + + struct ThreadType; + struct MultiWaitHolderType; + + Result CreateThread(ThreadType *thread, ThreadFunction function, void *argument, void *stack, size_t stack_size, s32 priority, s32 ideal_core); + Result CreateThread(ThreadType *thread, ThreadFunction function, void *argument, void *stack, size_t stack_size, s32 priority); + void DestroyThread(ThreadType *thread); + void StartThread(ThreadType *thread); + + ThreadType *GetCurrentThread(); + + void WaitThread(ThreadType *thread); + bool TryWaitThread(ThreadType *thread); + + void YieldThread(); + void SleepThread(TimeSpan time); + + s32 SuspendThread(ThreadType *thread); + s32 ResumeThread(ThreadType *thread); + s32 GetThreadSuspendCount(const ThreadType *thread); + + /* TODO: void GetThreadContext(ThreadContextInfo *out_context, const ThreadType *thread); */ + + s32 ChangeThreadPriority(ThreadType *thread, s32 priority); + s32 GetThreadPriority(const ThreadType *thread); + s32 GetThreadCurrentPriority(const ThreadType *thread); + + void SetThreadName(ThreadType *thread, const char *name); + void SetThreadNamePointer(ThreadType *thread, const char *name); + const char *GetThreadNamePointer(const ThreadType *thread); + + s32 GetCurrentProcessorNumber(); + s32 GetCurrentCoreNumber(); + + void SetThreadCoreMask(ThreadType *thread, s32 ideal_core, u64 affinity_mask); + void GetThreadCoreMask(s32 *out_ideal_core, u64 *out_affinity_mask, const ThreadType *thread); + + u64 GetThreadAvailableCoreMask(); + + ThreadId GetThreadId(const ThreadType *thread); + + void InitializeMultiWaitHolder(MultiWaitHolderType *holder, ThreadType *thread); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread_common.hpp new file mode 100644 index 00000000..531a1ee3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread_common.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + constexpr inline s32 ThreadSuspendCountMax = 127; + + constexpr inline s32 ThreadNameLengthMax = 0x20; + + constexpr inline s32 ThreadPriorityRangeSize = 32; + constexpr inline s32 HighestThreadPriority = 0; + constexpr inline s32 DefaultThreadPriority = ThreadPriorityRangeSize / 2; + constexpr inline s32 LowestThreadPriority = ThreadPriorityRangeSize - 1; + + constexpr inline s32 InvalidThreadPriority = 127; + + constexpr inline s32 LowestSystemThreadPriority = 35; + constexpr inline s32 HighestSystemThreadPriority = -12; + + constexpr inline size_t StackGuardAlignment = 4_KB; + constexpr inline size_t ThreadStackAlignment = 4_KB; + + using ThreadFunction = void (*)(void *); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread_local_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread_local_storage.hpp new file mode 100644 index 00000000..6cdd4c03 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread_local_storage.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_thread_local_storage_common.hpp> +#include <stratosphere/os/os_thread_local_storage_api.hpp> + +namespace ams::os { + + class ThreadLocalStorage { + NON_COPYABLE(ThreadLocalStorage); + NON_MOVEABLE(ThreadLocalStorage); + private: + TlsSlot m_tls_slot; + public: + ThreadLocalStorage() { + R_ABORT_UNLESS(os::AllocateTlsSlot(std::addressof(m_tls_slot), nullptr)); + } + + explicit ThreadLocalStorage(TlsDestructor destructor) { + R_ABORT_UNLESS(os::AllocateTlsSlot(std::addressof(m_tls_slot), destructor)); + } + + ~ThreadLocalStorage() { + os::FreeTlsSlot(m_tls_slot); + } + + uintptr_t GetValue() const { return os::GetTlsValue(m_tls_slot); } + void SetValue(uintptr_t value) { return os::SetTlsValue(m_tls_slot, value); } + + TlsSlot GetTlsSlot() const { return m_tls_slot; } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread_local_storage_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread_local_storage_api.hpp new file mode 100644 index 00000000..6bee9dc9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread_local_storage_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_common_types.hpp> +#include <stratosphere/os/os_memory_common.hpp> +#include <stratosphere/os/os_thread_local_storage_common.hpp> + +namespace ams::os { + + Result AllocateTlsSlot(TlsSlot *out, TlsDestructor destructor); + + void FreeTlsSlot(TlsSlot slot); + + uintptr_t GetTlsValue(TlsSlot slot); + void SetTlsValue(TlsSlot slot, uintptr_t value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread_local_storage_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread_local_storage_common.hpp new file mode 100644 index 00000000..11d76cbc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread_local_storage_common.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_common_types.hpp> +#include <stratosphere/os/os_memory_common.hpp> + +namespace ams::os { + + struct TlsSlot { + u32 _value; + }; + + using TlsDestructor = void (*)(uintptr_t arg); + + constexpr inline size_t TlsSlotCountMax = 16; + constexpr inline size_t SdkTlsSlotCountMax = 16; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread_types.hpp new file mode 100644 index 00000000..80be04a0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_thread_types.hpp @@ -0,0 +1,124 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_thread_common.hpp> +#include <stratosphere/os/os_thread_local_storage_common.hpp> +#include <stratosphere/os/os_thread_local_storage_api.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> +#include <stratosphere/os/impl/os_internal_condition_variable.hpp> +#include <stratosphere/os/os_sdk_thread_types.hpp> + +namespace ams::os { + + namespace impl { + + class MultiWaitObjectList; + + } + + #if !defined(AMS_OS_IMPL_USE_PTHREADS) + using ThreadId = u64; + #else + /* TODO: decide whether using pthread_id_np_t or not more thoroughly. */ + #if defined(ATMOSPHERE_OS_MACOS) + #define AMS_OS_IMPL_USE_PTHREADID_NP_FOR_THREAD_ID + #endif + + #if defined(AMS_OS_IMPL_USE_PTHREADID_NP_FOR_THREAD_ID) + using ThreadId = u64; + #else + static_assert(sizeof(pthread_t) <= sizeof(u64)); + using ThreadId = pthread_t; + #endif + #endif + + struct ThreadType { + static constexpr u16 Magic = 0xF5A5; + + enum State { + State_NotInitialized = 0, + State_Initialized = 1, + State_DestroyedBeforeStarted = 2, + State_Started = 3, + State_Terminated = 4, + }; + + util::TypedStorage<util::IntrusiveListNode> all_threads_node; + util::TypedStorage<impl::MultiWaitObjectList, sizeof(util::IntrusiveListNode), alignof(util::IntrusiveListNode)> waitlist; + uintptr_t reserved[4]; + u8 state; + bool stack_is_aliased; + bool auto_registered; + u8 suspend_count; + u16 magic; + s16 base_priority; + u16 version; + char name_buffer[ThreadNameLengthMax]; + const char *name_pointer; + ThreadId thread_id; + void *original_stack; + void *stack; + size_t stack_size; + ThreadFunction function; + void *initial_fiber; + void *current_fiber; + void *argument; + + mutable impl::InternalCriticalSectionStorage cs_thread; + mutable impl::InternalConditionVariableStorage cv_thread; + + /* The following members are arch/os specific. */ + #if defined(AMS_OS_IMPL_USE_PTHREADS) + + mutable uintptr_t tls_value_array[TlsSlotCountMax + SdkTlsSlotCountMax]; + + mutable impl::InternalCriticalSectionStorage cs_pthread_exit; + mutable impl::InternalConditionVariableStorage cv_pthread_exit; + bool exited_pthread; + + pthread_t pthread; + u64 affinity_mask; + int ideal_core; + + #elif defined(ATMOSPHERE_OS_HORIZON) + /* NOTE: Here, Nintendo stores the TLS array. This is handled by libnx in our case. */ + /* However, we need to access certain values in other threads' TLS (Nintendo uses a hardcoded layout for SDK tls members...) */ + /* These members are tls slot holders in sdk code, but just normal thread type members under our scheme. */ + SdkInternalTlsType sdk_internal_tls; + + using ThreadImpl = ::Thread; + + ThreadImpl *thread_impl; + ThreadImpl thread_impl_storage; + #elif defined(ATMOSPHERE_OS_WINDOWS) + + mutable uintptr_t tls_value_array[TlsSlotCountMax + SdkTlsSlotCountMax]; + + NativeHandle native_handle; + int ideal_core; + u64 affinity_mask; + + #endif + }; + static_assert(std::is_trivial<ThreadType>::value); + + constexpr inline s32 IdealCoreDontCare = -1; + constexpr inline s32 IdealCoreUseDefault = -2; + constexpr inline s32 IdealCoreNoUpdate = -3; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_tick.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_tick.hpp new file mode 100644 index 00000000..be96de5d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_tick.hpp @@ -0,0 +1,71 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/os/os_common_types.hpp> + +namespace ams::os { + + class Tick; + + /* Tick API. */ + Tick GetSystemTick(); + Tick GetSystemTickOrdered(); + s64 GetSystemTickFrequency(); + TimeSpan ConvertToTimeSpan(Tick tick); + Tick ConvertToTick(TimeSpan ts); + + class Tick { + private: + s64 m_tick; + public: + constexpr explicit Tick(s64 t = 0) : m_tick(t) { /* ... */ } + Tick(TimeSpan ts) : m_tick(ConvertToTick(ts).GetInt64Value()) { /* ... */ } + public: + constexpr s64 GetInt64Value() const { return m_tick; } + TimeSpan ToTimeSpan() const { return ConvertToTimeSpan(*this); } + + /* Tick arithmetic. */ + constexpr Tick &operator+=(Tick rhs) { m_tick += rhs.m_tick; return *this; } + constexpr Tick &operator-=(Tick rhs) { m_tick -= rhs.m_tick; return *this; } + constexpr Tick operator+(Tick rhs) const { Tick r(*this); return r += rhs; } + constexpr Tick operator-(Tick rhs) const { Tick r(*this); return r -= rhs; } + + constexpr bool operator==(const Tick &rhs) const { + return m_tick == rhs.m_tick; + } + + constexpr bool operator!=(const Tick &rhs) const { + return !(*this == rhs); + } + + constexpr bool operator<(const Tick &rhs) const { + return m_tick < rhs.m_tick; + } + + constexpr bool operator>=(const Tick &rhs) const { + return !(*this < rhs); + } + + constexpr bool operator>(const Tick &rhs) const { + return m_tick > rhs.m_tick; + } + + constexpr bool operator<=(const Tick &rhs) const { + return !(*this > rhs); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_timer_event.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_timer_event.hpp new file mode 100644 index 00000000..8c0d3fa1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_timer_event.hpp @@ -0,0 +1,79 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_timer_event_types.hpp> +#include <stratosphere/os/os_timer_event_api.hpp> + +namespace ams::os { + + class TimerEvent { + NON_COPYABLE(TimerEvent); + NON_MOVEABLE(TimerEvent); + private: + TimerEventType m_event; + public: + explicit TimerEvent(EventClearMode clear_mode) { + InitializeTimerEvent(std::addressof(m_event), clear_mode); + } + + ~TimerEvent() { + FinalizeTimerEvent(std::addressof(m_event)); + } + + void StartOneShot(TimeSpan first_time) { + return StartOneShotTimerEvent(std::addressof(m_event), first_time); + } + + void StartPeriodic(TimeSpan first_time, TimeSpan interval) { + return StartPeriodicTimerEvent(std::addressof(m_event), first_time, interval); + } + + void Stop() { + return StopTimerEvent(std::addressof(m_event)); + } + + void Wait() { + return WaitTimerEvent(std::addressof(m_event)); + } + + bool TryWait() { + return TryWaitTimerEvent(std::addressof(m_event)); + } + + void Signal() { + return SignalTimerEvent(std::addressof(m_event)); + } + + void Clear() { + return ClearTimerEvent(std::addressof(m_event)); + } + + operator TimerEventType &() { + return m_event; + } + + operator const TimerEventType &() const { + return m_event; + } + + TimerEventType *GetBase() { + return std::addressof(m_event); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_timer_event_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_timer_event_api.hpp new file mode 100644 index 00000000..d6921155 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_timer_event_api.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_event_common.hpp> + +namespace ams::os { + + struct TimerEventType; + struct MultiWaitHolderType; + + void InitializeTimerEvent(TimerEventType *event, EventClearMode clear_mode); + void FinalizeTimerEvent(TimerEventType *event); + + void StartOneShotTimerEvent(TimerEventType *event, TimeSpan first_time); + void StartPeriodicTimerEvent(TimerEventType *event, TimeSpan first_time, TimeSpan interval); + void StopTimerEvent(TimerEventType *event); + + void WaitTimerEvent(TimerEventType *event); + bool TryWaitTimerEvent(TimerEventType *event); + + void SignalTimerEvent(TimerEventType *event); + void ClearTimerEvent(TimerEventType *event); + + void InitializeMultiWaitHolder(MultiWaitHolderType *multi_wait_holder, TimerEventType *event); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_timer_event_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_timer_event_types.hpp new file mode 100644 index 00000000..1271ad71 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_timer_event_types.hpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_event_common.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> +#include <stratosphere/os/impl/os_internal_condition_variable.hpp> + +namespace ams::os { + + namespace impl { + + class MultiWaitObjectList; + + } + + struct TimerEventType { + using TimeSpanStorage = util::TypedStorage<TimeSpan>; + + enum State { + State_NotInitialized = 0, + State_Initialized = 1, + }; + + enum TimerState { + TimerState_Stop = 0, + TimerState_OneShot = 1, + TimerState_Periodic = 2, + }; + + util::TypedStorage<impl::MultiWaitObjectList, sizeof(util::IntrusiveListNode), alignof(util::IntrusiveListNode)> multi_wait_object_list_storage; + + u8 state; + u8 clear_mode; + bool signaled; + u8 timer_state; + u32 broadcast_counter_low; + u32 broadcast_counter_high; + + TimeSpanStorage next_time_to_wakeup; + TimeSpanStorage first; + TimeSpanStorage interval; + + impl::InternalCriticalSectionStorage cs_timer_event; + impl::InternalConditionVariableStorage cv_signaled; + }; + static_assert(std::is_trivial<TimerEventType>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_transfer_memory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_transfer_memory.hpp new file mode 100644 index 00000000..b516e8fe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_transfer_memory.hpp @@ -0,0 +1,78 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_transfer_memory_types.hpp> +#include <stratosphere/os/os_transfer_memory_api.hpp> + +namespace ams::os { + + class TransferMemory { + NON_COPYABLE(TransferMemory); + NON_MOVEABLE(TransferMemory); + private: + TransferMemoryType m_tmem; + public: + constexpr TransferMemory() : m_tmem{ .state = TransferMemoryType::State_NotInitialized } { + /* ... */ + } + + TransferMemory(void *address, size_t size, MemoryPermission perm) { + R_ABORT_UNLESS(CreateTransferMemory(std::addressof(m_tmem), address, size, perm)); + } + + TransferMemory(size_t size, NativeHandle handle, bool managed) { + this->Attach(size, handle, managed); + } + + ~TransferMemory() { + if (m_tmem.state == TransferMemoryType::State_NotInitialized) { + return; + } + DestroyTransferMemory(std::addressof(m_tmem)); + } + + void Attach(size_t size, NativeHandle handle, bool managed) { + AttachTransferMemory(std::addressof(m_tmem), size, handle, managed); + } + + NativeHandle Detach() { + return DetachTransferMemory(std::addressof(m_tmem)); + } + + Result Map(void **out, MemoryPermission owner_perm) { + R_RETURN(MapTransferMemory(out, std::addressof(m_tmem), owner_perm)); + } + + void Unmap() { + UnmapTransferMemory(std::addressof(m_tmem)); + } + + operator TransferMemoryType &() { + return m_tmem; + } + + operator const TransferMemoryType &() const { + return m_tmem; + } + + TransferMemoryType *GetBase() { + return std::addressof(m_tmem); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_transfer_memory_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_transfer_memory_api.hpp new file mode 100644 index 00000000..f087889f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_transfer_memory_api.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_memory_permission.hpp> +#include <stratosphere/os/os_native_handle.hpp> + +namespace ams::os { + + struct TransferMemoryType; + + Result CreateTransferMemory(TransferMemoryType *tmem, void *address, size_t size, MemoryPermission perm); + + void AttachTransferMemory(TransferMemoryType *tmem, size_t size, NativeHandle handle, bool managed); + NativeHandle DetachTransferMemory(TransferMemoryType *tmem); + + void DestroyTransferMemory(TransferMemoryType *tmem); + + Result MapTransferMemory(void **out, TransferMemoryType *tmem, MemoryPermission owner_perm); + void UnmapTransferMemory(TransferMemoryType *tmem); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_transfer_memory_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_transfer_memory_types.hpp new file mode 100644 index 00000000..3f11cd91 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_transfer_memory_types.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/impl/os_internal_critical_section.hpp> +#include <stratosphere/os/os_native_handle.hpp> + +namespace ams::os { + + struct TransferMemoryType { + enum State { + State_NotInitialized = 0, + State_Created = 1, + State_Mapped = 2, + State_Detached = 3, + }; + + u8 state; + bool handle_managed; + bool allocated; + + void *address; + size_t size; + NativeHandle handle; + + mutable impl::InternalCriticalSectionStorage cs_transfer_memory; + }; + static_assert(std::is_trivial<TransferMemoryType>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_unsafe_memory_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_unsafe_memory_api.hpp new file mode 100644 index 00000000..6b456be0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_unsafe_memory_api.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_memory_common.hpp> + +namespace ams::os { + + Result AllocateUnsafeMemory(uintptr_t *out_address, size_t size); + Result FreeUnsafeMemory(uintptr_t address, size_t size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_virtual_address_memory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_virtual_address_memory.hpp new file mode 100644 index 00000000..4eb006e6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_virtual_address_memory.hpp @@ -0,0 +1,21 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_virtual_address_memory_common.hpp> +#include <stratosphere/os/os_virtual_address_memory_types.hpp> +#include <stratosphere/os/os_virtual_address_memory_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_virtual_address_memory_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_virtual_address_memory_api.hpp new file mode 100644 index 00000000..11a80326 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_virtual_address_memory_api.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_virtual_address_memory_common.hpp> +#include <stratosphere/os/os_virtual_address_memory_types.hpp> + +namespace ams::os { + + void InitializeVirtualAddressMemory(); + + Result AllocateAddressRegion(uintptr_t *out, size_t size); + Result AllocateMemory(uintptr_t *out, size_t size); + Result AllocateMemoryPages(uintptr_t address, size_t size); + + Result FreeAddressRegion(uintptr_t address); + Result FreeMemoryPages(uintptr_t address, size_t size); + + VirtualAddressMemoryResourceUsage GetVirtualAddressMemoryResourceUsage(); + + bool IsVirtualAddressMemoryEnabled(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_virtual_address_memory_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_virtual_address_memory_common.hpp new file mode 100644 index 00000000..3f4ecc90 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_virtual_address_memory_common.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::os { + + constexpr inline size_t AddressRegionAlignment = 64_KB; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_virtual_address_memory_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_virtual_address_memory_types.hpp new file mode 100644 index 00000000..4da8365c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/os/os_virtual_address_memory_types.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_virtual_address_memory_common.hpp> + +namespace ams::os { + + struct VirtualAddressMemoryResourceUsage { + size_t assigned_size; + size_t used_size; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/osdbg.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/osdbg.hpp new file mode 100644 index 00000000..c4751e27 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/osdbg.hpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/osdbg/osdbg_thread.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/osdbg/osdbg_thread.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/osdbg/osdbg_thread.hpp new file mode 100644 index 00000000..f8aa23f1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/osdbg/osdbg_thread.hpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <stratosphere/osdbg/osdbg_thread_types.hpp> +#include <stratosphere/osdbg/osdbg_thread_api.hpp> \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/osdbg/osdbg_thread_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/osdbg/osdbg_thread_api.hpp new file mode 100644 index 00000000..e9c233da --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/osdbg/osdbg_thread_api.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/osdbg/osdbg_thread_api_impl.hpp> + +namespace ams::osdbg { + + struct ThreadInfo; + + Result InitializeThreadInfo(ThreadInfo *thread_info, os::NativeHandle debug_handle, const osdbg::DebugInfoCreateProcess *create_process, const osdbg::DebugInfoCreateThread *create_thread); + Result UpdateThreadInfo(ThreadInfo *thread_info); + + Result GetThreadName(char *dst, const ThreadInfo *thread_info); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/osdbg/osdbg_thread_api_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/osdbg/osdbg_thread_api_impl.hpp new file mode 100644 index 00000000..64492448 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/osdbg/osdbg_thread_api_impl.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/osdbg/osdbg_thread_types.hpp> + +namespace ams::osdbg { + + constexpr inline s32 GetThreadPriority(const ThreadInfo *thread_info) { + return thread_info->_base_priority; + } + + constexpr inline s32 GetThreadCurrentPriority(const ThreadInfo *thread_info) { + return thread_info->_current_priority; + } + + constexpr inline size_t GetThreadStackSize(const ThreadInfo *thread_info) { + return thread_info->_stack_size; + } + + constexpr inline uintptr_t GetThreadStackAddress(const ThreadInfo *thread_info) { + return thread_info->_stack; + } + + constexpr inline uintptr_t GetThreadFunction(const ThreadInfo *thread_info) { + return thread_info->_function; + } + + constexpr inline uintptr_t GetThreadFunctionArgument(const ThreadInfo *thread_info) { + return thread_info->_argument; + } + + constexpr inline uintptr_t GetThreadNamePointer(const ThreadInfo *thread_info) { + return thread_info->_name_pointer; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/osdbg/osdbg_thread_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/osdbg/osdbg_thread_types.hpp new file mode 100644 index 00000000..21671bf9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/osdbg/osdbg_thread_types.hpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::osdbg { + + namespace impl { + + union ThreadTypeCommon; + + } + + #if defined(ATMOSPHERE_OS_HORIZON) + using DebugInfoCreateProcess = svc::DebugInfoCreateProcess; + using DebugInfoCreateThread = svc::DebugInfoCreateThread; + #else + struct DebugInfoCreateProcess{}; + struct DebugInfoCreateThread{}; + #endif + + enum ThreadTypeType : u8 { + ThreadTypeType_Unknown = 0, + ThreadTypeType_Nintendo, + ThreadTypeType_Stratosphere, + #if defined(ATMOSPHERE_OS_HORIZON) + ThreadTypeType_Libnx, + #endif + }; + + struct ThreadInfo { + s32 _base_priority; + s32 _current_priority; + size_t _stack_size; + uintptr_t _stack; + uintptr_t _argument; + uintptr_t _function; + uintptr_t _name_pointer; + impl::ThreadTypeCommon *_thread_type; + os::NativeHandle _debug_handle; + ThreadTypeType _thread_type_type; + #if defined(ATMOSPHERE_OS_HORIZON) + osdbg::DebugInfoCreateProcess _debug_info_create_process; + osdbg::DebugInfoCreateThread _debug_info_create_thread; + #endif + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/patcher.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/patcher.hpp new file mode 100644 index 00000000..36382425 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/patcher.hpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/patcher/patcher_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/patcher/patcher_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/patcher/patcher_api.hpp new file mode 100644 index 00000000..3b4d6339 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/patcher/patcher_api.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/ro/ro_types.hpp> + +namespace ams::patcher { + + /* Helper for applying to code binaries. */ + void LocateAndApplyIpsPatchesToModule(const char *mount_name, const char *patch_dir, size_t protected_size, size_t offset, const ro::ModuleId *module_id, u8 *mapped_module, size_t mapped_size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pcv.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pcv.hpp new file mode 100644 index 00000000..88c1eeeb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pcv.hpp @@ -0,0 +1,20 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/pcv/pcv_types.hpp> +#include <stratosphere/pcv/pcv_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pcv/pcv_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pcv/pcv_api.hpp new file mode 100644 index 00000000..28bf6c6b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pcv/pcv_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/pcv/pcv_types.hpp> + +namespace ams::pcv { + + void Initialize(); + void Finalize(); + + Result SetClockEnabled(Module module, bool en); + Result SetClockRate(Module module, ClockHz hz); + + Result SetReset(Module module, bool en); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pcv/pcv_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pcv/pcv_types.hpp new file mode 100644 index 00000000..97652325 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pcv/pcv_types.hpp @@ -0,0 +1,116 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::pcv { + + using ClockHz = u32; + using MicroVolt = s32; + using MilliC = s32; + + /* TODO: Device codes? */ + enum Module { + Module_Cpu = 0, + Module_Gpu = 1, + Module_I2s1 = 2, + Module_I2s2 = 3, + Module_I2s3 = 4, + Module_Pwm = 5, + Module_I2c1 = 6, + Module_I2c2 = 7, + Module_I2c3 = 8, + Module_I2c4 = 9, + Module_I2c5 = 10, + Module_I2c6 = 11, + Module_Spi1 = 12, + Module_Spi2 = 13, + Module_Spi3 = 14, + Module_Spi4 = 15, + Module_Disp1 = 16, + Module_Disp2 = 17, + Module_Isp = 18, + Module_Vi = 19, + Module_Sdmmc1 = 20, + Module_Sdmmc2 = 21, + Module_Sdmmc3 = 22, + Module_Sdmmc4 = 23, + Module_Owr = 24, + Module_Csite = 25, + Module_Tsec = 26, + Module_Mselect = 27, + Module_Hda2codec2x = 28, + Module_Actmon = 29, + Module_I2cSlow = 30, + Module_Sor1 = 31, + Module_Sata = 32, + Module_Hda = 33, + Module_XusbCoreHostSrc = 34, + Module_XusbFalconSrc = 35, + Module_XusbFsSrc = 36, + Module_XusbCoreDevSrc = 37, + Module_XusbSsSrc = 38, + Module_UartA = 39, + Module_UartB = 40, + Module_UartC = 41, + Module_UartD = 42, + Module_Host1x = 43, + Module_Entropy = 44, + Module_SocTherm = 45, + Module_Vic = 46, + Module_Nvenc = 47, + Module_Nvjpg = 48, + Module_Nvdec = 49, + Module_Qspi = 50, + Module_ViI2c = 51, + Module_Tsecb = 52, + Module_Ape = 53, + Module_AudioDsp = 54, + Module_AudioUart = 55, + Module_Emc = 56, + Module_Plle = 57, + Module_PlleHwSeq = 58, + Module_Dsi = 59, + Module_Maud = 60, + Module_Dpaux1 = 61, + Module_MipiCal = 62, + Module_UartFstMipiCal = 63, + Module_Osc = 64, + Module_SysBus = 65, + Module_SorSafe = 66, + Module_XusbSs = 67, + Module_XusbHost = 68, + Module_XusbDevice = 69, + Module_Extperiph1 = 70, + Module_Ahub = 71, + Module_Hda2hdmicodec = 72, + Module_Gpuaux = 73, + Module_UsbD = 74, + Module_Usb2 = 75, + Module_Pcie = 76, + Module_Afi = 77, + Module_PciExClk = 78, + Module_PExUsbPhy = 79, + Module_XUsbPadCtl = 80, + Module_Apbdma = 81, + Module_Usb2TrkClk = 82, + Module_XUsbIoPll = 83, + Module_XUsbIoPllHwSeq = 84, + Module_Cec = 85, + Module_Extperiph2 = 86, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl.hpp new file mode 100644 index 00000000..e68ee183 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/pgl/pgl_types.hpp> +#include <stratosphere/pgl/pgl_event_observer.hpp> +#include <stratosphere/pgl/pgl_shell_api.hpp> +#include <stratosphere/pgl/pgl_shell_api.hpp> +#include <stratosphere/pgl/srv/pgl_srv_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/pgl_event_observer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/pgl_event_observer.hpp new file mode 100644 index 00000000..2a9eff31 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/pgl_event_observer.hpp @@ -0,0 +1,124 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os.hpp> +#include <stratosphere/pm.hpp> +#include <stratosphere/pgl/pgl_types.hpp> +#include <stratosphere/pgl/sf/pgl_sf_i_event_observer.hpp> +#include <stratosphere/pgl/tipc/pgl_tipc_i_event_observer.hpp> + +namespace ams::pgl { + + namespace impl { + + class EventObserverInterface { + NON_COPYABLE(EventObserverInterface); + NON_MOVEABLE(EventObserverInterface); + public: + constexpr EventObserverInterface() = default; + + virtual ~EventObserverInterface() { /* ... */ } + + virtual Result GetSystemEvent(os::SystemEventType *out) = 0; + virtual Result GetProcessEventInfo(pm::ProcessEventInfo *out) = 0; + }; + + class EventObserverByCmif final : public EventObserverInterface { + NON_COPYABLE(EventObserverByCmif); + NON_MOVEABLE(EventObserverByCmif); + private: + ams::sf::SharedPointer<pgl::sf::IEventObserver> m_cmif_interface; + public: + explicit EventObserverByCmif(ams::sf::SharedPointer<pgl::sf::IEventObserver> intf) : m_cmif_interface(intf) { /* ... */ } + public: + virtual Result GetSystemEvent(os::SystemEventType *out) override { + ams::sf::NativeHandle handle; + R_TRY(m_cmif_interface->GetProcessEventHandle(std::addressof(handle))); + + os::AttachReadableHandleToSystemEvent(out, handle.GetOsHandle(), handle.IsManaged(), os::EventClearMode_AutoClear); + handle.Detach(); + + R_SUCCEED(); + } + + virtual Result GetProcessEventInfo(pm::ProcessEventInfo *out) override { + R_RETURN(m_cmif_interface->GetProcessEventInfo(out)); + } + }; + + template<typename T> requires tipc::IsIEventObserver<T> + class EventObserverByTipc final : public EventObserverInterface { + NON_COPYABLE(EventObserverByTipc); + NON_MOVEABLE(EventObserverByTipc); + private: + T m_tipc_interface; + public: + template<typename... Args> + explicit EventObserverByTipc(Args &&... args) : m_tipc_interface(std::forward<Args>(args)...) { /* ... */ } + public: + virtual Result GetSystemEvent(os::SystemEventType *out) override { + os::NativeHandle handle; + R_TRY(m_tipc_interface.GetProcessEventHandle(std::addressof(handle))); + os::AttachReadableHandleToSystemEvent(out, handle, true, os::EventClearMode_AutoClear); + R_SUCCEED(); + } + + virtual Result GetProcessEventInfo(pm::ProcessEventInfo *out) override { + R_RETURN(m_tipc_interface.GetProcessEventInfo(ams::tipc::Out<pm::ProcessEventInfo>(out))); + } + }; + + } + + class EventObserver { + NON_COPYABLE(EventObserver); + private: + struct Deleter { + void operator()(impl::EventObserverInterface *); + }; + public: + using UniquePtr = std::unique_ptr<impl::EventObserverInterface, Deleter>; + private: + UniquePtr m_impl; + public: + EventObserver() { /* ... */ } + + explicit EventObserver(UniquePtr impl) : m_impl(std::move(impl)) { /* ... */ } + + EventObserver(EventObserver &&rhs) { + m_impl = std::move(rhs.m_impl); + } + + EventObserver &operator=(EventObserver &&rhs) { + EventObserver(std::move(rhs)).Swap(*this); + return *this; + } + + void Swap(EventObserver &rhs) { + std::swap(m_impl, rhs.m_impl); + } + public: + Result GetSystemEvent(os::SystemEventType *out) { + R_RETURN(m_impl->GetSystemEvent(out)); + } + + Result GetProcessEventInfo(pm::ProcessEventInfo *out) { + R_RETURN(m_impl->GetProcessEventInfo(out)); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/pgl_shell_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/pgl_shell_api.hpp new file mode 100644 index 00000000..f61d008e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/pgl_shell_api.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/pgl/pgl_types.hpp> +#include <stratosphere/pgl/pgl_event_observer.hpp> + +namespace ams::pgl { + + Result Initialize(); + void Finalize(); + + Result LaunchProgram(os::ProcessId *out, const ncm::ProgramLocation &loc, u32 process_flags, u8 pgl_flags); + Result TerminateProcess(os::ProcessId process_id); + Result LaunchProgramFromHost(os::ProcessId *out, const char *content_path, u32 process_flags); + Result GetHostContentMetaInfo(pgl::ContentMetaInfo *out, const char *content_path); + Result GetApplicationProcessId(os::ProcessId *out); + Result BoostSystemMemoryResourceLimit(u64 size); + Result IsProcessTracked(bool *out, os::ProcessId process_id); + Result EnableApplicationCrashReport(bool enabled); + Result IsApplicationCrashReportEnabled(bool *out); + Result EnableApplicationAllThreadDumpOnCrash(bool enabled); + Result TriggerApplicationSnapShotDumper(const char *arg, SnapShotDumpType dump_type); + + Result GetEventObserver(pgl::EventObserver *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/pgl_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/pgl_types.hpp new file mode 100644 index 00000000..5cb5a041 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/pgl_types.hpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/pm.hpp> +#include <stratosphere/ncm.hpp> + +namespace ams::pgl { + + enum LaunchFlags : u8 { + LaunchFlags_None = 0, + LaunchFlags_EnableDetailedCrashReport = (1 << 0), + LaunchFlags_EnableCrashReportScreenShotForProduction = (1 << 1), + LaunchFlags_EnableCrashReportScreenShotForDevelop = (1 << 2), + }; + + enum class SnapShotDumpType : u32 { + None = 0, + Auto = 1, + Full = 2, + }; + + /* TODO: Is this really nn::ncm::Content<Something>Info? */ + struct ContentMetaInfo { + u64 id; + u32 version; + ncm::ContentType content_type; + u8 id_offset; + u8 reserved_0E[2]; + + static constexpr ContentMetaInfo Make(u64 id, u32 version, ncm::ContentType content_type, u8 id_offset) { + return { + .id = id, + .version = version, + .content_type = content_type, + .id_offset = id_offset, + }; + } + }; + static_assert(sizeof(ContentMetaInfo) == 0x10 && util::is_pod<ContentMetaInfo>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/sf/pgl_sf_i_event_observer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/sf/pgl_sf_i_event_observer.hpp new file mode 100644 index 00000000..387fb814 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/sf/pgl_sf_i_event_observer.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/pm.hpp> +#include <stratosphere/pgl/pgl_types.hpp> + +#define AMS_PGL_SF_I_EVENT_OBSERVER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GetProcessEventHandle, (ams::sf::OutCopyHandle out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, GetProcessEventInfo, (ams::sf::Out<pm::ProcessEventInfo> out), (out)) + +AMS_SF_DEFINE_INTERFACE(ams::pgl::sf, IEventObserver, AMS_PGL_SF_I_EVENT_OBSERVER_INTERFACE_INFO, 0x00000000); + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/sf/pgl_sf_i_shell_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/sf/pgl_sf_i_shell_interface.hpp new file mode 100644 index 00000000..a5fb6972 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/sf/pgl_sf_i_shell_interface.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/pm.hpp> +#include <stratosphere/pgl/pgl_types.hpp> +#include <stratosphere/pgl/sf/pgl_sf_i_event_observer.hpp> + +#define AMS_PGL_I_SHELL_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, LaunchProgram, (ams::sf::Out<os::ProcessId> out, const ncm::ProgramLocation &loc, u32 pm_flags, u8 pgl_flags), (out, loc, pm_flags, pgl_flags)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, TerminateProcess, (os::ProcessId process_id), (process_id)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, LaunchProgramFromHost, (ams::sf::Out<os::ProcessId> out, const ams::sf::InBuffer &content_path, u32 pm_flags), (out, content_path, pm_flags)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, GetHostContentMetaInfo, (ams::sf::Out<pgl::ContentMetaInfo> out, const ams::sf::InBuffer &content_path), (out, content_path)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, GetApplicationProcessId, (ams::sf::Out<os::ProcessId> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, BoostSystemMemoryResourceLimit, (u64 size), (size)) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, IsProcessTracked, (ams::sf::Out<bool> out, os::ProcessId process_id), (out, process_id)) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, EnableApplicationCrashReport, (bool enabled), (enabled)) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, IsApplicationCrashReportEnabled, (ams::sf::Out<bool> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, EnableApplicationAllThreadDumpOnCrash, (bool enabled), (enabled)) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, TriggerApplicationSnapShotDumper, (pgl::SnapShotDumpType dump_type, const ams::sf::InBuffer &arg), (dump_type, arg)) \ + AMS_SF_METHOD_INFO(C, H, 20, Result, GetShellEventObserver, (ams::sf::Out<ams::sf::SharedPointer<pgl::sf::IEventObserver>> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 21, Result, Command21NotImplemented, (ams::sf::Out<u64> out, u32 in, const ams::sf::InBuffer &buf1, const ams::sf::InBuffer &buf2), (out, in, buf1, buf2), hos::Version_11_0_0) + +AMS_SF_DEFINE_INTERFACE(ams::pgl::sf, IShellInterface, AMS_PGL_I_SHELL_INTERFACE_INTERFACE_INFO, 0x00000000); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/srv/pgl_srv_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/srv/pgl_srv_api.hpp new file mode 100644 index 00000000..3b2720a6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/srv/pgl_srv_api.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/pgl/pgl_types.hpp> +#include <stratosphere/pgl/srv/pgl_srv_shell_interface.hpp> + +namespace ams::pgl::srv { + + void InitializeHeap(); + void *Allocate(size_t size); + void Deallocate(void *p, size_t size); + + void StartServer(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/srv/pgl_srv_shell_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/srv/pgl_srv_shell_interface.hpp new file mode 100644 index 00000000..77b7cf0d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/srv/pgl_srv_shell_interface.hpp @@ -0,0 +1,93 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/pgl/pgl_types.hpp> +#include <stratosphere/pgl/sf/pgl_sf_i_shell_interface.hpp> +#include <stratosphere/pgl/tipc/pgl_tipc_i_shell_interface.hpp> + +namespace ams::pgl::srv { + + class ShellInterfaceCommon { + NON_COPYABLE(ShellInterfaceCommon); + NON_MOVEABLE(ShellInterfaceCommon); + public: + constexpr ShellInterfaceCommon() = default; + public: + Result LaunchProgramImpl(os::ProcessId *out, const ncm::ProgramLocation &loc, u32 pm_flags, u8 pgl_flags); + Result TerminateProcessImpl(os::ProcessId process_id); + Result LaunchProgramFromHostImpl(os::ProcessId *out, const void *content_path, size_t content_path_size, u32 pm_flags); + Result GetHostContentMetaInfoImpl(pgl::ContentMetaInfo *out, const void *content_path, size_t content_path_size); + Result GetApplicationProcessIdImpl(os::ProcessId *out); + Result BoostSystemMemoryResourceLimitImpl(u64 size); + Result IsProcessTrackedImpl(bool *out, os::ProcessId process_id); + Result EnableApplicationCrashReportImpl(bool enabled); + Result IsApplicationCrashReportEnabledImpl(bool *out); + Result EnableApplicationAllThreadDumpOnCrashImpl(bool enabled); + Result TriggerApplicationSnapShotDumperImpl(SnapShotDumpType dump_type, const void *arg, size_t arg_size); + }; + + class ShellInterfaceCmif : public ShellInterfaceCommon { + NON_COPYABLE(ShellInterfaceCmif); + NON_MOVEABLE(ShellInterfaceCmif); + private: + using Allocator = ams::sf::ExpHeapAllocator; + using ObjectFactory = ams::sf::ObjectFactory<ams::sf::ExpHeapAllocator::Policy>; + private: + Allocator *m_allocator; + public: + constexpr ShellInterfaceCmif(Allocator *a) : ShellInterfaceCommon(), m_allocator(a) { /* ... */ } + public: + /* Interface commands. */ + Result LaunchProgram(ams::sf::Out<os::ProcessId> out, const ncm::ProgramLocation &loc, u32 pm_flags, u8 pgl_flags); + Result TerminateProcess(os::ProcessId process_id); + Result LaunchProgramFromHost(ams::sf::Out<os::ProcessId> out, const ams::sf::InBuffer &content_path, u32 pm_flags); + Result GetHostContentMetaInfo(ams::sf::Out<pgl::ContentMetaInfo> out, const ams::sf::InBuffer &content_path); + Result GetApplicationProcessId(ams::sf::Out<os::ProcessId> out); + Result BoostSystemMemoryResourceLimit(u64 size); + Result IsProcessTracked(ams::sf::Out<bool> out, os::ProcessId process_id); + Result EnableApplicationCrashReport(bool enabled); + Result IsApplicationCrashReportEnabled(ams::sf::Out<bool> out); + Result EnableApplicationAllThreadDumpOnCrash(bool enabled); + Result TriggerApplicationSnapShotDumper(SnapShotDumpType dump_type, const ams::sf::InBuffer &arg); + + Result GetShellEventObserver(ams::sf::Out<ams::sf::SharedPointer<pgl::sf::IEventObserver>> out); + Result Command21NotImplemented(ams::sf::Out<u64> out, u32 in, const ams::sf::InBuffer &buf1, const ams::sf::InBuffer &buf2); + }; + static_assert(pgl::sf::IsIShellInterface<ShellInterfaceCmif>); + + class ShellInterfaceTipc : public ShellInterfaceCommon { + NON_COPYABLE(ShellInterfaceTipc); + NON_MOVEABLE(ShellInterfaceTipc); + public: + constexpr ShellInterfaceTipc() : ShellInterfaceCommon() { /* ... */ } + public: + /* Interface commands. */ + Result LaunchProgram(ams::tipc::Out<os::ProcessId> out, const ncm::ProgramLocation loc, u32 pm_flags, u8 pgl_flags); + Result TerminateProcess(os::ProcessId process_id); + Result LaunchProgramFromHost(ams::tipc::Out<os::ProcessId> out, const ams::tipc::InBuffer content_path, u32 pm_flags); + Result GetHostContentMetaInfo(ams::tipc::Out<pgl::ContentMetaInfo> out, const ams::tipc::InBuffer content_path); + Result GetApplicationProcessId(ams::tipc::Out<os::ProcessId> out); + Result BoostSystemMemoryResourceLimit(u64 size); + Result IsProcessTracked(ams::tipc::Out<bool> out, os::ProcessId process_id); + Result EnableApplicationCrashReport(bool enabled); + Result IsApplicationCrashReportEnabled(ams::tipc::Out<bool> out); + Result EnableApplicationAllThreadDumpOnCrash(bool enabled); + Result GetShellEventObserver(ams::tipc::OutMoveHandle out); + }; + static_assert(pgl::tipc::IsIShellInterface<ShellInterfaceTipc>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/tipc/pgl_tipc_i_event_observer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/tipc/pgl_tipc_i_event_observer.hpp new file mode 100644 index 00000000..2287344c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/tipc/pgl_tipc_i_event_observer.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/pm.hpp> +#include <stratosphere/tipc.hpp> +#include <stratosphere/pgl/pgl_types.hpp> + +#define AMS_PGL_TIPC_I_EVENT_OBSERVER_INTERFACE_INFO(C, H) \ + AMS_TIPC_METHOD_INFO(C, H, 0, Result, GetProcessEventHandle, (ams::tipc::OutCopyHandle out), (out)) \ + AMS_TIPC_METHOD_INFO(C, H, 1, Result, GetProcessEventInfo, (ams::tipc::Out<pm::ProcessEventInfo> out), (out)) + +AMS_TIPC_DEFINE_INTERFACE(ams::pgl::tipc, IEventObserver, AMS_PGL_TIPC_I_EVENT_OBSERVER_INTERFACE_INFO); + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/tipc/pgl_tipc_i_shell_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/tipc/pgl_tipc_i_shell_interface.hpp new file mode 100644 index 00000000..ad61a71e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pgl/tipc/pgl_tipc_i_shell_interface.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/pm.hpp> +#include <stratosphere/tipc.hpp> +#include <stratosphere/pgl/pgl_types.hpp> +#include <stratosphere/pgl/tipc/pgl_tipc_i_event_observer.hpp> + +#define AMS_PGL_TIPC_I_SHELL_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_TIPC_METHOD_INFO(C, H, 0, Result, LaunchProgram, (ams::tipc::Out<os::ProcessId> out, const ncm::ProgramLocation loc, u32 pm_flags, u8 pgl_flags), (out, loc, pm_flags, pgl_flags)) \ + AMS_TIPC_METHOD_INFO(C, H, 1, Result, TerminateProcess, (os::ProcessId process_id), (process_id)) \ + AMS_TIPC_METHOD_INFO(C, H, 2, Result, LaunchProgramFromHost, (ams::tipc::Out<os::ProcessId> out, const ams::tipc::InBuffer content_path, u32 pm_flags), (out, content_path, pm_flags)) \ + AMS_TIPC_METHOD_INFO(C, H, 4, Result, GetHostContentMetaInfo, (ams::tipc::Out<pgl::ContentMetaInfo> out, const ams::tipc::InBuffer content_path), (out, content_path)) \ + AMS_TIPC_METHOD_INFO(C, H, 5, Result, GetApplicationProcessId, (ams::tipc::Out<os::ProcessId> out), (out)) \ + AMS_TIPC_METHOD_INFO(C, H, 6, Result, BoostSystemMemoryResourceLimit, (u64 size), (size)) \ + AMS_TIPC_METHOD_INFO(C, H, 7, Result, IsProcessTracked, (ams::tipc::Out<bool> out, os::ProcessId process_id), (out, process_id)) \ + AMS_TIPC_METHOD_INFO(C, H, 8, Result, EnableApplicationCrashReport, (bool enabled), (enabled)) \ + AMS_TIPC_METHOD_INFO(C, H, 9, Result, IsApplicationCrashReportEnabled, (ams::tipc::Out<bool> out), (out)) \ + AMS_TIPC_METHOD_INFO(C, H, 10, Result, EnableApplicationAllThreadDumpOnCrash, (bool enabled), (enabled)) \ + AMS_TIPC_METHOD_INFO(C, H, 20, Result, GetShellEventObserver, (ams::tipc::OutMoveHandle out), (out)) + +AMS_TIPC_DEFINE_INTERFACE(ams::pgl::tipc, IShellInterface, AMS_PGL_TIPC_I_SHELL_INTERFACE_INTERFACE_INFO); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pinmux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pinmux.hpp new file mode 100644 index 00000000..b3689800 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pinmux.hpp @@ -0,0 +1,20 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/pinmux/pinmux_api.hpp> +#include <stratosphere/pinmux/driver/pinmux_driver_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pinmux/driver/pinmux_driver_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pinmux/driver/pinmux_driver_api.hpp new file mode 100644 index 00000000..0bb14f01 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pinmux/driver/pinmux_driver_api.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::pinmux::driver { + + void Initialize(); + void Finalize(); + + void SetInitialConfig(); + void SetInitialDrivePadConfig(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pinmux/pinmux_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pinmux/pinmux_api.hpp new file mode 100644 index 00000000..fc3d2787 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pinmux/pinmux_api.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::pinmux { + + /* ... */ + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm.hpp new file mode 100644 index 00000000..44ed3730 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/pm/pm_types.hpp> +#include <stratosphere/pm/pm_boot_mode_api.hpp> +#include <stratosphere/pm/pm_info_api.hpp> +#include <stratosphere/pm/pm_shell_api.hpp> +#include <stratosphere/pm/pm_dmnt_api.hpp> +#include <stratosphere/pm/impl/pm_boot_mode_interface.hpp> +#include <stratosphere/pm/impl/pm_debug_monitor_interface.hpp> +#include <stratosphere/pm/impl/pm_information_interface.hpp> +#include <stratosphere/pm/impl/pm_shell_interface.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/impl/pm_boot_mode_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/impl/pm_boot_mode_interface.hpp new file mode 100644 index 00000000..66faf57e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/impl/pm_boot_mode_interface.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/pm/pm_types.hpp> +#include <stratosphere/sf.hpp> + +#define AMS_PM_I_BOOT_MODE_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, void, GetBootMode, (sf::Out<u32> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 1, void, SetMaintenanceBoot, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 2, void, GetUnknown, (sf::Out<u32> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, SetUnknown, (u32 val), (val)) + +AMS_SF_DEFINE_INTERFACE(ams::pm::impl, IBootModeInterface, AMS_PM_I_BOOT_MODE_INTERFACE_INTERFACE_INFO, 0x96D01649) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/impl/pm_debug_monitor_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/impl/pm_debug_monitor_interface.hpp new file mode 100644 index 00000000..7c9676f8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/impl/pm_debug_monitor_interface.hpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/pm/pm_types.hpp> +#include <stratosphere/sf.hpp> + +#define AMS_PM_I_DEBUG_MONITOR_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GetExceptionProcessIdList, (sf::Out<u32> out_count, const sf::OutArray<os::ProcessId> &out_process_ids), (out_count, out_process_ids)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, StartProcess, (os::ProcessId process_id), (process_id)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, GetProcessId, (sf::Out<os::ProcessId> out, ncm::ProgramId program_id), (out, program_id)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, HookToCreateProcess, (sf::OutCopyHandle out_hook, ncm::ProgramId program_id), (out_hook, program_id)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, GetApplicationProcessId, (sf::Out<os::ProcessId> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, HookToCreateApplicationProcess, (sf::OutCopyHandle out_hook), (out_hook)) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, ClearHook, (u32 which), (which), hos::Version_6_0_0) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, GetProgramId, (sf::Out<ncm::ProgramId> out, os::ProcessId process_id), (out, process_id)) \ + AMS_SF_METHOD_INFO(C, H, 65000, Result, AtmosphereGetProcessInfo, (sf::OutCopyHandle out_process_handle, sf::Out<ncm::ProgramLocation> out_loc, sf::Out<cfg::OverrideStatus> out_status, os::ProcessId process_id), (out_process_handle, out_loc, out_status, process_id)) \ + AMS_SF_METHOD_INFO(C, H, 65001, Result, AtmosphereGetCurrentLimitInfo, (sf::Out<s64> out_cur_val, sf::Out<s64> out_lim_val, u32 group, u32 resource), (out_cur_val, out_lim_val, group, resource)) + +AMS_SF_DEFINE_INTERFACE(ams::pm::impl, IDebugMonitorInterface, AMS_PM_I_DEBUG_MONITOR_INTERFACE_INTERFACE_INFO, 0x9391F0EE) + +#define AMS_PM_I_DEPRECATED_DEBUG_MONITOR_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GetModuleIdList, (sf::Out<u32> out_count, const sf::OutBuffer &out_buf, u64 unused), (out_count, out_buf, unused)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, GetExceptionProcessIdList, (sf::Out<u32> out_count, const sf::OutArray<os::ProcessId> &out_process_ids), (out_count, out_process_ids)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, StartProcess, (os::ProcessId process_id), (process_id)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, GetProcessId, (sf::Out<os::ProcessId> out, ncm::ProgramId program_id), (out, program_id)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, HookToCreateProcess, (sf::OutCopyHandle out_hook, ncm::ProgramId program_id), (out_hook, program_id)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, GetApplicationProcessId, (sf::Out<os::ProcessId> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, HookToCreateApplicationProcess, (sf::OutCopyHandle out_hook), (out_hook)) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, GetProgramId, (sf::Out<ncm::ProgramId> out, os::ProcessId process_id), (out, process_id)) \ + AMS_SF_METHOD_INFO(C, H, 65000, Result, AtmosphereGetProcessInfo, (sf::OutCopyHandle out_process_handle, sf::Out<ncm::ProgramLocation> out_loc, sf::Out<cfg::OverrideStatus> out_status, os::ProcessId process_id), (out_process_handle, out_loc, out_status, process_id)) \ + AMS_SF_METHOD_INFO(C, H, 65001, Result, AtmosphereGetCurrentLimitInfo, (sf::Out<s64> out_cur_val, sf::Out<s64> out_lim_val, u32 group, u32 resource), (out_cur_val, out_lim_val, group, resource)) + +AMS_SF_DEFINE_INTERFACE(ams::pm::impl, IDeprecatedDebugMonitorInterface, AMS_PM_I_DEPRECATED_DEBUG_MONITOR_INTERFACE_INTERFACE_INFO, 0x9391F0EE) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/impl/pm_information_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/impl/pm_information_interface.hpp new file mode 100644 index 00000000..6eeda321 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/impl/pm_information_interface.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/pm/pm_types.hpp> +#include <stratosphere/sf.hpp> + +#define AMS_PM_I_INFORMATION_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GetProgramId, (sf::Out<ncm::ProgramId> out, os::ProcessId process_id), (out, process_id)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, GetAppletResourceLimitCurrentValue, (sf::Out<pm::ResourceLimitValue> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, GetAppletResourceLimitPeakValue, (sf::Out<pm::ResourceLimitValue> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 65000, Result, AtmosphereGetProcessId, (sf::Out<os::ProcessId> out, ncm::ProgramId program_id), (out, program_id)) \ + AMS_SF_METHOD_INFO(C, H, 65001, Result, AtmosphereHasLaunchedBootProgram, (sf::Out<bool> out, ncm::ProgramId program_id), (out, program_id)) \ + AMS_SF_METHOD_INFO(C, H, 65002, Result, AtmosphereGetProcessInfo, (sf::Out<ncm::ProgramLocation> out_loc, sf::Out<cfg::OverrideStatus> out_status, os::ProcessId process_id), (out_loc, out_status, process_id)) + +AMS_SF_DEFINE_INTERFACE(ams::pm::impl, IInformationInterface, AMS_PM_I_INFORMATION_INTERFACE_INTERFACE_INFO, 0xF205AA1F) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/impl/pm_shell_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/impl/pm_shell_interface.hpp new file mode 100644 index 00000000..1cccdab6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/impl/pm_shell_interface.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/pm/pm_types.hpp> +#include <stratosphere/sf.hpp> + +#define AMS_PM_I_SHELL_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, LaunchProgram, (sf::Out<os::ProcessId> out_process_id, const ncm::ProgramLocation &loc, u32 flags), (out_process_id, loc, flags)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, TerminateProcess, (os::ProcessId process_id), (process_id)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, TerminateProgram, (ncm::ProgramId program_id), (program_id)) \ + AMS_SF_METHOD_INFO(C, H, 3, void, GetProcessEventHandle, (sf::OutCopyHandle out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 4, void, GetProcessEventInfo, (sf::Out<pm::ProcessEventInfo> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 5, void, NotifyBootFinished, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, GetApplicationProcessIdForShell, (sf::Out<os::ProcessId> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, BoostSystemMemoryResourceLimit, (u64 boost_size), (boost_size)) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, BoostApplicationThreadResourceLimit, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 9, void, GetBootFinishedEventHandle, (sf::OutCopyHandle out), (out), hos::Version_8_0_0) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, BoostSystemThreadResourceLimit, (), ()) + +AMS_SF_DEFINE_INTERFACE(ams::pm::impl, IShellInterface, AMS_PM_I_SHELL_INTERFACE_INTERFACE_INFO, 0x387D60C0) + +#define AMS_PM_I_DEPRECATED_SHELL_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, LaunchProgram, (sf::Out<os::ProcessId> out_process_id, const ncm::ProgramLocation &loc, u32 flags), (out_process_id, loc, flags)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, TerminateProcess, (os::ProcessId process_id), (process_id)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, TerminateProgram, (ncm::ProgramId program_id), (program_id)) \ + AMS_SF_METHOD_INFO(C, H, 3, void, GetProcessEventHandle, (sf::OutCopyHandle out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 4, void, GetProcessEventInfo, (sf::Out<pm::ProcessEventInfo> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, CleanupProcess, (os::ProcessId process_id), (process_id)) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, ClearExceptionOccurred, (os::ProcessId process_id), (process_id)) \ + AMS_SF_METHOD_INFO(C, H, 7, void, NotifyBootFinished, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, GetApplicationProcessIdForShell, (sf::Out<os::ProcessId> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, BoostSystemMemoryResourceLimit, (u64 boost_size), (boost_size), hos::Version_4_0_0) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, BoostSystemThreadResourceLimit, (), ()) + +AMS_SF_DEFINE_INTERFACE(ams::pm::impl, IDeprecatedShellInterface, AMS_PM_I_DEPRECATED_SHELL_INTERFACE_INTERFACE_INFO, 0x387D60C0) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/pm_boot_mode_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/pm_boot_mode_api.hpp new file mode 100644 index 00000000..ee30716b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/pm_boot_mode_api.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/pm/pm_types.hpp> + +namespace ams::pm::bm { + + /* Boot Mode API. */ + BootMode GetBootMode(); + void SetMaintenanceBoot(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/pm_dmnt_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/pm_dmnt_api.hpp new file mode 100644 index 00000000..4a5d53fa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/pm_dmnt_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/ldr.hpp> +#include <stratosphere/pm/pm_types.hpp> +#include <stratosphere/ncm/ncm_program_location.hpp> + +namespace ams::pm::dmnt { + + /* Debug Monitor API. */ + Result StartProcess(os::ProcessId process_id); + Result GetProgramId(ncm::ProgramId *out_program_id, os::ProcessId process_id); + Result GetProcessId(os::ProcessId *out_process_id, const ncm::ProgramId program_id); + Result GetApplicationProcessId(os::ProcessId *out_process_id); + Result HookToCreateApplicationProcess(os::NativeHandle *out_handle); + Result HookToCreateProcess(os::NativeHandle *out_handle, const ncm::ProgramId program_id); + Result AtmosphereGetProcessInfo(os::NativeHandle *out_handle, ncm::ProgramLocation *out_loc, cfg::OverrideStatus *out_status, os::ProcessId process_id); + + #if defined(ATMOSPHERE_OS_HORIZON) + Result AtmosphereGetCurrentLimitInfo(u64 *out_current_value, u64 *out_limit_value, ResourceLimitGroup group, svc::LimitableResource resource); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/pm_info_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/pm_info_api.hpp new file mode 100644 index 00000000..484c4fa7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/pm_info_api.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/os.hpp> +#include <stratosphere/pm/pm_types.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> +#include <stratosphere/ncm/ncm_program_location.hpp> +#include <stratosphere/cfg/cfg_types.hpp> + +namespace ams::pm::info { + + /* Information API. */ + Result GetProgramId(ncm::ProgramId *out_program_id, os::ProcessId process_id); + Result GetProcessId(os::ProcessId *out_process_id, ncm::ProgramId program_id); + Result HasLaunchedBootProgram(bool *out, ncm::ProgramId program_id); + + Result GetAppletResourceLimitCurrentValue(pm::ResourceLimitValue *out); + Result GetAppletResourceLimitPeakValue(pm::ResourceLimitValue *out); + + Result GetProcessInfo(ncm::ProgramLocation *out_loc, cfg::OverrideStatus *out_status, os::ProcessId process_id); + + /* Information convenience API. */ + bool HasLaunchedBootProgram(ncm::ProgramId program_id); + + Result IsHblProcessId(bool *out, os::ProcessId process_id); + Result IsHblProgramId(bool *out, ncm::ProgramId program_id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/pm_shell_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/pm_shell_api.hpp new file mode 100644 index 00000000..b4cd348c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/pm_shell_api.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/ldr.hpp> +#include <stratosphere/pm/pm_types.hpp> +#include <stratosphere/ncm/ncm_program_location.hpp> + +namespace ams::pm::shell { + + /* Shell API. */ + Result LaunchProgram(os::ProcessId *out, const ncm::ProgramLocation &loc, u32 launch_flags); + Result TerminateProcess(os::ProcessId process_id); + Result GetProcessEventEvent(os::SystemEvent *out); + Result GetProcessEventInfo(ProcessEventInfo *out); + Result GetApplicationProcessIdForShell(os::ProcessId *out); + Result BoostSystemMemoryResourceLimit(u64 size); + Result BoostApplicationThreadResourceLimit(); + Result BoostSystemThreadResourceLimit(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/pm_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/pm_types.hpp new file mode 100644 index 00000000..0fb6e3c8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pm/pm_types.hpp @@ -0,0 +1,131 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os/os_common_types.hpp> + +namespace ams::pm { + + enum class BootMode { + Normal = 0, + Maintenance = 1, + SafeMode = 2, + }; + + enum ResourceLimitGroup { + ResourceLimitGroup_System = 0, + ResourceLimitGroup_Application = 1, + ResourceLimitGroup_Applet = 2, + ResourceLimitGroup_Count, + }; + + enum LaunchFlags : u32 { + LaunchFlags_None = 0, + LaunchFlags_SignalOnExit = (1 << 0), + LaunchFlags_SignalOnStart = (1 << 1), + LaunchFlags_SignalOnException = (1 << 2), + LaunchFlags_SignalOnDebugEvent = (1 << 3), + LaunchFlags_StartSuspended = (1 << 4), + LaunchFlags_DisableAslr = (1 << 5), + }; + + enum LaunchFlagsDeprecated : u32 { + LaunchFlagsDeprecated_None = 0, + LaunchFlagsDeprecated_SignalOnExit = (1 << 0), + LaunchFlagsDeprecated_StartSuspended = (1 << 1), + LaunchFlagsDeprecated_SignalOnException = (1 << 2), + LaunchFlagsDeprecated_DisableAslr = (1 << 3), + LaunchFlagsDeprecated_SignalOnDebugEvent = (1 << 4), + LaunchFlagsDeprecated_SignalOnStart = (1 << 5), + }; + + struct ResourceLimitValue { + u64 physical_memory; + u32 thread_count; + u32 event_count; + u32 transfer_memory_count; + u32 session_count; + }; + + constexpr inline u32 LaunchFlagsMask = (1 << 6) - 1; + + enum class ProcessEvent : u32 { + None = 0, + Exited = 1, + Started = 2, + Exception = 3, + DebugRunning = 4, + DebugBreak = 5, + }; + + enum class ProcessEventDeprecated : u32 { + None = 0, + Exception = 1, + Exited = 2, + DebugRunning = 3, + DebugBreak = 4, + Started = 5, + }; + + inline u32 GetProcessEventValue(ProcessEvent event) { + if (hos::GetVersion() >= hos::Version_5_0_0) { + return static_cast<u32>(event); + } + switch (event) { + case ProcessEvent::None: + return static_cast<u32>(ProcessEventDeprecated::None); + case ProcessEvent::Exited: + return static_cast<u32>(ProcessEventDeprecated::Exited); + case ProcessEvent::Started: + return static_cast<u32>(ProcessEventDeprecated::Started); + case ProcessEvent::Exception: + return static_cast<u32>(ProcessEventDeprecated::Exception); + case ProcessEvent::DebugRunning: + return static_cast<u32>(ProcessEventDeprecated::DebugRunning); + case ProcessEvent::DebugBreak: + return static_cast<u32>(ProcessEventDeprecated::DebugBreak); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + struct ProcessEventInfo { + u32 event; + os::ProcessId process_id; + + inline ProcessEvent GetProcessEvent() const { + if (hos::GetVersion() >= hos::Version_5_0_0) { + return static_cast<ProcessEvent>(this->event); + } + switch (static_cast<ProcessEventDeprecated>(event)) { + case ProcessEventDeprecated::None: + return ProcessEvent::None; + case ProcessEventDeprecated::Exited: + return ProcessEvent::Exited; + case ProcessEventDeprecated::Started: + return ProcessEvent::Started; + case ProcessEventDeprecated::Exception: + return ProcessEvent::Exception; + case ProcessEventDeprecated::DebugRunning: + return ProcessEvent::DebugRunning; + case ProcessEventDeprecated::DebugBreak: + return ProcessEvent::DebugBreak; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + }; + static_assert(sizeof(ProcessEventInfo) == 0x10 && util::is_pod<ProcessEventInfo>::value, "ProcessEventInfo definition!"); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl.hpp new file mode 100644 index 00000000..3a447241 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/powctl/powctl_types.hpp> +#include <stratosphere/powctl/powctl_select_devices.hpp> +#include <stratosphere/powctl/powctl_session_api.hpp> +#include <stratosphere/powctl/powctl_battery_api.hpp> +#include <stratosphere/powctl/powctl_charger_api.hpp> +#include <stratosphere/powctl/impl/powctl_battery_charge_percentage.hpp> +#include <stratosphere/powctl/driver/powctl_driver_api.hpp> +#include <stratosphere/powctl/driver/impl/powctl_select_charger_parameters.hpp> +#include <stratosphere/powctl/driver/impl/powctl_charge_arbiter.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/driver/impl/powctl_charge_arbiter.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/driver/impl/powctl_charge_arbiter.hpp new file mode 100644 index 00000000..5ae8c850 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/driver/impl/powctl_charge_arbiter.hpp @@ -0,0 +1,191 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/powctl/powctl_types.hpp> +#include <stratosphere/powctl/driver/impl/powctl_select_charger_parameters.hpp> + +namespace ams::powctl::driver::impl { + + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + class ChargeArbiter { + private: + const ChargeParametersRule *m_rules; + size_t m_num_rules; + int m_charge_voltage_limit; + BatteryTemperatureLevel m_temperature_level; + int m_avg_v_cell; + float m_voltage_fuel_gauge_percentage; + bool m_has_battery_done_current; + int m_battery_done_current; + PowerState m_power_state; + const ChargeParametersRule *m_selected_rule; + bool m_check_battery_done_current; + private: + static constexpr bool IsInRange(int value, int min, int max) { + if (!(min <= value)) { + return false; + } + + if (max == std::numeric_limits<int>::max()) { + return value <= max; + } else { + return value < max; + } + } + static constexpr bool IsInRangeFloat(float value, float min, float max) { + if (!(min <= value)) { + return false; + } + + if (max == std::numeric_limits<float>::max()) { + return value <= max; + } else { + return value < max; + } + } + + bool IsAcceptablePowerState(const PowerState *acceptable, size_t num_acceptable) const { + for (size_t i = 0; i < num_acceptable; ++i) { + if (m_power_state == acceptable[i]) { + return true; + } + } + return false; + } + public: + ChargeArbiter(const ChargeParametersRule *r, size_t nr, int cvl) + : m_rules(r), m_num_rules(nr), m_charge_voltage_limit(cvl), m_temperature_level(BatteryTemperatureLevel::Medium), + m_avg_v_cell(4080), m_voltage_fuel_gauge_percentage(25.0), m_has_battery_done_current(false), m_battery_done_current(0), + m_power_state(PowerState::FullAwake), m_selected_rule(nullptr), m_check_battery_done_current(false) + { + this->UpdateSelectedRule(); + } + + void SetBatteryTemperatureLevel(BatteryTemperatureLevel btl) { + m_temperature_level = btl; + this->UpdateSelectedRule(); + } + + void SetBatteryAverageVCell(int avg) { + m_avg_v_cell = avg; + this->UpdateSelectedRule(); + } + + void SetBatteryVoltageFuelGaugePercentage(float pct) { + m_voltage_fuel_gauge_percentage = pct; + this->UpdateSelectedRule(); + } + + void SetBatteryDoneCurrent(int current) { + m_battery_done_current = current; + m_has_battery_done_current = true; + this->UpdateSelectedRule(); + } + + void SetPowerState(PowerState ps) { + m_power_state = ps; + this->UpdateSelectedRule(); + } + + int GetChargeVoltageLimit() const { + return m_charge_voltage_limit; + } + + bool IsBatteryDoneCurrentAcceptable(int current) const { + const auto *rule = this->GetSelectedRule(); + AMS_ASSERT(rule != nullptr); + + return IsInRange(current, rule->min_battery_done_current, rule->max_battery_done_current); + } + + const ChargeParametersRule *GetSelectedRule() const { + return m_selected_rule; + } + + void UpdateSelectedRule() { + /* Try to find an entry that fits our current requirements. */ + const ChargeParametersRule *best_rule = nullptr; + + for (size_t i = 0; i < m_num_rules; ++i) { + /* Get the current rule. */ + const ChargeParametersRule &cur_rule = m_rules[i]; + + /* Check the temperature level. */ + if (m_temperature_level != cur_rule.temperature_level) { + continue; + } + + /* Check that average voltage is in range. */ + if (!IsInRange(m_avg_v_cell, cur_rule.min_avg_v_cell, cur_rule.max_avg_v_cell)) { + continue; + } + + /* Check that voltage fuel gauge percentage is in range. */ + if (!IsInRangeFloat(m_voltage_fuel_gauge_percentage, cur_rule.min_voltage_fuel_gauge_percentage, cur_rule.max_voltage_fuel_gauge_percentage)) { + continue; + } + + /* Check if our power state is acceptable. */ + if (!this->IsAcceptablePowerState(cur_rule.acceptable_power_states, cur_rule.num_acceptable_power_states)) { + continue; + } + + /* The limit is probably acceptable. */ + if (m_selected_rule != std::addressof(cur_rule)) { + /* We're selecting a new rule. Check if our need to deal with battery current is acceptable. */ + if (cur_rule.check_battery_current && m_check_battery_done_current) { + continue; + } + + /* Determine whether we should check battery done current. */ + bool check_battery_done_current; + + if (m_selected_rule != nullptr && m_selected_rule->check_battery_current) { + if (m_selected_rule->temperature_level == m_temperature_level && + IsInRange(m_avg_v_cell, m_selected_rule->min_avg_v_cell, m_selected_rule->max_avg_v_cell) && + IsInRangeFloat(m_voltage_fuel_gauge_percentage, m_selected_rule->min_voltage_fuel_gauge_percentage, m_selected_rule->max_voltage_fuel_gauge_percentage)) + { + check_battery_done_current = m_has_battery_done_current && !IsInRange(m_battery_done_current, m_selected_rule->min_battery_done_current, m_selected_rule->max_battery_done_current); + } else { + check_battery_done_current = true; + } + } else { + check_battery_done_current = false; + } + + /* Set whether we need to check the battery done current. */ + m_has_battery_done_current = false; + m_check_battery_done_current |= check_battery_done_current; + } else { + /* We're selecting the currently selected rule. Make sure the battery done current is acceptable if we have one. */ + if (m_has_battery_done_current && !IsInRange(m_battery_done_current, cur_rule.min_battery_done_current, cur_rule.max_battery_done_current)) { + continue; + } + } + + /* Select the current rule. */ + best_rule = std::addressof(cur_rule); + break; + } + + /* Update our selected rule. */ + m_selected_rule = best_rule; + } + }; + #endif + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/driver/impl/powctl_charger_parameters.board.nintendo_nx.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/driver/impl/powctl_charger_parameters.board.nintendo_nx.hpp new file mode 100644 index 00000000..bba85ec6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/driver/impl/powctl_charger_parameters.board.nintendo_nx.hpp @@ -0,0 +1,66 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/powctl/powctl_types.hpp> + +namespace ams::powctl::driver::impl { + + struct ChargeParametersRule { + BatteryTemperatureLevel temperature_level; + int min_avg_v_cell; + int max_avg_v_cell; + int min_battery_done_current; + int max_battery_done_current; + float min_unknown_14; + float max_unknown_18; + float min_voltage_fuel_gauge_percentage; + float max_voltage_fuel_gauge_percentage; + const PowerState *acceptable_power_states; + size_t num_acceptable_power_states; + bool check_battery_current; + bool reinitialize_charger; + int charge_voltage_limit; + int fast_charge_current_limit; + int battery_compensation; + int voltage_clamp; + }; + + struct UnknownParameterX { + int _00; + int _04; + double _08; + double _10; + }; + + struct ChargeParameters { + int temp_min; + int temp_low; + int temp_high; + int temp_max; + int low_voltage_fast_charge_current_limit; + int default_charge_voltage_limit; + const UnknownParameterX *unknown_x_table; + size_t x_table_size; + double _28; + double _30; + const ChargeParametersRule *rules; + size_t num_rules; + }; + + const ChargeParameters &GetChargeParameters(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/driver/impl/powctl_charger_parameters.generic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/driver/impl/powctl_charger_parameters.generic.hpp new file mode 100644 index 00000000..03abe782 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/driver/impl/powctl_charger_parameters.generic.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/powctl/powctl_types.hpp> + +namespace ams::powctl::driver::impl { + + struct ChargeParametersRule { + /* ... */ + }; + + struct UnknownParameterX { + /* ... */ + }; + + struct ChargeParameters { + /* ... */ + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/driver/impl/powctl_select_charger_parameters.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/driver/impl/powctl_select_charger_parameters.hpp new file mode 100644 index 00000000..acbb30dc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/driver/impl/powctl_select_charger_parameters.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/powctl/powctl_types.hpp> + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + #include <stratosphere/powctl/driver/impl/powctl_charger_parameters.board.nintendo_nx.hpp> +#else + #include <stratosphere/powctl/driver/impl/powctl_charger_parameters.generic.hpp> +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/driver/powctl_driver_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/driver/powctl_driver_api.hpp new file mode 100644 index 00000000..a0a9978c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/driver/powctl_driver_api.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/powctl/powctl_types.hpp> + +namespace ams::powctl { + + void Initialize(bool enable_interrupt_handlers); + void Finalize(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/impl/powctl_battery_charge_percentage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/impl/powctl_battery_charge_percentage.hpp new file mode 100644 index 00000000..a2a0881f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/impl/powctl_battery_charge_percentage.hpp @@ -0,0 +1,65 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/powctl/powctl_types.hpp> + +namespace ams::powctl::impl { + + constexpr inline const double MinRawDefaultPercentage = 3.0; + constexpr inline const double MaxRawDefaultPercentage = 99.0; + + constexpr inline const double MinRawThresholdPercentage = 11.0; + + constexpr inline const int MinDisplayPercentage = 1; + constexpr inline const int MaxDisplayPercentage = 100; + + constexpr inline void CalculateMarginatedRawPercentage(double *out_marginated_min, double *out_marginated_max, double min, double max) { + /* Ensure minimum is in correct range. */ + min = std::max(std::min(min, MinRawThresholdPercentage), MinRawDefaultPercentage); + + /* Calculate the marginated values. */ + constexpr const double MinMarginPercentage = 0.93359375; + constexpr const double MaxMarginPercentage = -0.83593750; + + const auto margin_factor = (max - min) / (MaxRawDefaultPercentage - MinRawDefaultPercentage); + *out_marginated_min = min + MinMarginPercentage * margin_factor; + *out_marginated_max = max + MaxMarginPercentage * margin_factor; + } + + constexpr inline int GetDisplayPercentage(double raw_percentage, double min, double max) { + /* Calculate the display percentage. */ + constexpr const double BaseDisplayPercentage = 2.0; + const auto display_percentage = BaseDisplayPercentage + ((static_cast<double>(MaxDisplayPercentage - BaseDisplayPercentage) * (raw_percentage - min)) / (max - min)); + + /* Clamp the display percentage within bounds. */ + return std::max(std::min(static_cast<int>(display_percentage), MaxDisplayPercentage), MinDisplayPercentage); + } + + constexpr inline int ConvertBatteryChargePercentage(double raw_percentage, double min, double max) { + /* Marginate the min/max. */ + double marginated_min = 0.0, marginated_max = 0.0; + CalculateMarginatedRawPercentage(std::addressof(marginated_min), std::addressof(marginated_max), min, max); + + /* Convert to display percentage. */ + return GetDisplayPercentage(raw_percentage, marginated_min, marginated_max); + } + + constexpr inline int ConvertBatteryChargePercentage(double raw_percentage) { + return ConvertBatteryChargePercentage(raw_percentage, MinRawDefaultPercentage, MaxRawDefaultPercentage); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/powctl_battery_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/powctl_battery_api.hpp new file mode 100644 index 00000000..e80cbf67 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/powctl_battery_api.hpp @@ -0,0 +1,74 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/powctl/powctl_types.hpp> +#include <stratosphere/powctl/powctl_session_api.hpp> + +namespace ams::powctl { + + /* Battery API. */ + Result GetBatteryChargePercentage(float *out_percent, Session &session); + + Result GetBatteryVoltageFuelGaugePercentage(float *out_percent, Session &session); + + Result GetBatteryFullCapacity(int *out_mah, Session &session); + Result GetBatteryRemainingCapacity(int *out_mah, Session &session); + + Result SetBatteryChargePercentageMinimumAlertThreshold(Session &session, float percentage); + Result SetBatteryChargePercentageMaximumAlertThreshold(Session &session, float percentage); + + Result SetBatteryVoltageFuelGaugePercentageMinimumAlertThreshold(Session &session, float percentage); + Result SetBatteryVoltageFuelGaugePercentageMaximumAlertThreshold(Session &session, float percentage); + + Result SetBatteryFullChargeThreshold(Session &session, float percentage); + + Result GetBatteryAverageCurrent(int *out_ma, Session &session); + Result GetBatteryCurrent(int *out_ma, Session &session); + + Result GetBatteryInternalState(void *dst, size_t *out_size, Session &session, size_t dst_size); + Result SetBatteryInternalState(Session &session, const void *src, size_t src_size); + + Result GetBatteryNeedToRestoreParameters(bool *out, Session &session); + Result SetBatteryNeedToRestoreParameters(Session &session, bool en); + + Result IsBatteryI2cShutdownEnabled(bool *out, Session &session); + Result SetBatteryI2cShutdownEnabled(Session &session, bool en); + + Result IsBatteryPresent(bool *out, Session &session); + + Result GetBatteryCycles(int *out, Session &session); + Result SetBatteryCycles(Session &session, int cycles); + + Result GetBatteryAge(float *out_percent, Session &session); + + Result GetBatteryTemperature(float *out_c, Session &session); + Result GetBatteryMaximumTemperature(float *out_c, Session &session); + + Result SetBatteryTemperatureMinimumAlertThreshold(Session &session, float c); + Result SetBatteryTemperatureMaximumAlertThreshold(Session &session, float c); + + Result GetBatteryVCell(int *out_mv, Session &session); + Result GetBatteryAverageVCell(int *out_mv, Session &session); + + Result GetBatteryAverageVCellTime(TimeSpan *out, Session &session); + + Result GetBatteryOpenCircuitVoltage(int *out_mv, Session &session); + + Result SetBatteryVoltageMinimumAlertThreshold(Session &session, int mv); + Result SetBatteryVoltageMaximumAlertThreshold(Session &session, int mv); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/powctl_charger_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/powctl_charger_api.hpp new file mode 100644 index 00000000..d295f604 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/powctl_charger_api.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/powctl/powctl_types.hpp> +#include <stratosphere/powctl/powctl_session_api.hpp> + +namespace ams::powctl { + + /* Charger API. */ + Result GetChargerChargeCurrentState(ChargeCurrentState *out, Session &session); + Result SetChargerChargeCurrentState(Session &session, ChargeCurrentState state); + + Result GetChargerFastChargeCurrentLimit(int *out_ma, Session &session); + Result SetChargerFastChargeCurrentLimit(Session &session, int ma); + + Result GetChargerChargeVoltageLimit(int *out_mv, Session &session); + Result SetChargerChargeVoltageLimit(Session &session, int mv); + + Result SetChargerChargerConfiguration(Session &session, ChargerConfiguration cfg); + + Result IsChargerHiZEnabled(bool *out, Session &session); + Result SetChargerHiZEnabled(Session &session, bool en); + + Result GetChargerInputCurrentLimit(int *out_ma, Session &session); + Result SetChargerInputCurrentLimit(Session &session, int ma); + + Result SetChargerInputVoltageLimit(Session &session, int mv); + + Result SetChargerBoostModeCurrentLimit(Session &session, int ma); + + Result GetChargerChargerStatus(ChargerStatus *out, Session &session); + + Result IsChargerWatchdogTimerEnabled(bool *out, Session &session); + Result SetChargerWatchdogTimerEnabled(Session &session, bool en); + + Result SetChargerWatchdogTimerTimeout(Session &session, TimeSpan timeout); + Result ResetChargerWatchdogTimer(Session &session); + + Result GetChargerBatteryCompensation(int *out_mo, Session &session); + Result SetChargerBatteryCompensation(Session &session, int mo); + + Result GetChargerVoltageClamp(int *out_mv, Session &session); + Result SetChargerVoltageClamp(Session &session, int mv); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/powctl_devices.board.nintendo_nx.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/powctl_devices.board.nintendo_nx.hpp new file mode 100644 index 00000000..5d560939 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/powctl_devices.board.nintendo_nx.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/i2c.hpp> +#include <stratosphere/powctl/powctl_types.hpp> + +namespace ams::powctl { + + /* Fuel Gauge. */ + constexpr inline const DeviceCode DeviceCode_Max17050 = i2c::DeviceCode_Max17050; + + /* Charger. */ + constexpr inline const DeviceCode DeviceCode_Bq24193 = i2c::DeviceCode_Bq24193; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/powctl_select_devices.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/powctl_select_devices.hpp new file mode 100644 index 00000000..1b6c7a7f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/powctl_select_devices.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/powctl/powctl_types.hpp> + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + #include <stratosphere/powctl/powctl_devices.board.nintendo_nx.hpp> +#else + /* Error? */ +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/powctl_session_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/powctl_session_api.hpp new file mode 100644 index 00000000..f6002106 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/powctl_session_api.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/powctl/powctl_types.hpp> + +namespace ams::powctl { + + namespace impl { + + class SessionImpl : public ::ams::ddsf::ISession { + NON_COPYABLE(SessionImpl); + NON_MOVEABLE(SessionImpl); + AMS_DDSF_CASTABLE_TRAITS(ams::powctl::impl::SessionImpl, ::ams::ddsf::ISession); + public: + SessionImpl() : ISession() { /* ... */ } + + ~SessionImpl() { ddsf::CloseSession(this); } + }; + + } + + struct Session { + bool has_session; + util::TypedStorage<impl::SessionImpl> impl_storage; + + struct ConstantInitializeTag{}; + + constexpr Session(ConstantInitializeTag) : has_session(false), impl_storage() { /* ... */ } + + Session() : has_session(false) { /* ... */ } + }; + + Result OpenSession(Session *out, DeviceCode device_code, ddsf::AccessMode access_mode); + void CloseSession(Session &session); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/powctl_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/powctl_types.hpp new file mode 100644 index 00000000..cdb872e8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/powctl/powctl_types.hpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::powctl { + + /* Charger types. */ + enum ChargerStatus { + ChargerStatus_Charging = 1, + + ChargerStatus_NotCharging = 3, + ChargerStatus_ChargeTerminationDone = 4, + }; + + enum ChargerConfiguration { + ChargerConfiguration_ChargeDisable = 1, + ChargerConfiguration_ChargeBattery = 2, + ChargerConfiguration_Otg = 3, + }; + + enum ChargeCurrentState { + ChargeCurrentState_Unknown = 0x0, + ChargeCurrentState_NotCharging = 0x1, + ChargeCurrentState_ChargingForce20Percent = 0x2, + ChargeCurrentState_Charging = 0x3, + }; + + enum class BatteryTemperatureLevel { + TooLow = 0, + Low = 1, + Medium = 2, + High = 3, + TooHigh = 4, + }; + + enum class PowerState { + FullAwake = 0, + MinimumAwake = 1, + SleepCharge = 2, + SleepDischarge = 3, + ShutdownChargeMain = 4, + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc.hpp new file mode 100644 index 00000000..bebecf10 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/psc/psc_types.hpp> +#include <stratosphere/psc/psc_pm_module_id.hpp> +#include <stratosphere/psc/sf/psc_sf_i_pm_module.hpp> +#include <stratosphere/psc/sf/psc_sf_i_pm_service.hpp> +#include <stratosphere/psc/psc_pm_module.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/psc_pm_module.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/psc_pm_module.hpp new file mode 100644 index 00000000..2bb2cab7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/psc_pm_module.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) + #include <stratosphere/psc/psc_pm_module.os.horizon.hpp> +#else + #include <stratosphere/psc/psc_pm_module.os.generic.hpp> +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/psc_pm_module.os.generic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/psc_pm_module.os.generic.hpp new file mode 100644 index 00000000..070250e8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/psc_pm_module.os.generic.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/psc/psc_types.hpp> +#include <stratosphere/psc/psc_pm_module_id.hpp> +#include <stratosphere/psc/sf/psc_sf_i_pm_module.hpp> + +namespace ams::psc { + + class PmModule { + NON_COPYABLE(PmModule); + NON_MOVEABLE(PmModule); + private: + os::SystemEvent m_system_event; + bool m_initialized; + PmModuleId m_module_id; + uintptr_t m_reserved; + PmState m_state; + PmFlagSet m_flags; + public: + PmModule() : m_initialized(false), m_module_id(PmModuleId_Reserved0), m_reserved(0), m_state(PmState_FullAwake), m_flags() { /* ... */ } + ~PmModule() { + if (m_initialized) { + R_ABORT_UNLESS(this->Finalize()); + } + } + + Result Initialize(const PmModuleId mid, const PmModuleId *dependencies, u32 dependency_count, os::EventClearMode clear_mode) { + /* TODO: Should we do in-process dependency resolution? */ + AMS_UNUSED(dependencies, dependency_count); + + /* Check that we're not already initialized. */ + R_UNLESS(!m_initialized, psc::ResultAlreadyInitialized()); + + /* Create our event. */ + R_ABORT_UNLESS(os::CreateSystemEvent(m_system_event.GetBase(), clear_mode, false)); + + /* Set our state. */ + m_module_id = mid; + m_initialized = true; + + R_SUCCEED(); + } + + Result Finalize() { + /* Check that we're initialized. */ + R_UNLESS(m_initialized, psc::ResultNotInitialized()); + + /* Destroy our system event. */ + os::DestroySystemEvent(m_system_event.GetBase()); + + /* Mark not initialized. */ + m_initialized = false; + + R_SUCCEED(); + } + + constexpr PmModuleId GetId() const { return m_module_id; } + + Result GetRequest(PmState *out_state, PmFlagSet *out_flags) { + /* Check that we're initialized. */ + R_UNLESS(m_initialized, psc::ResultNotInitialized()); + + /* Set output. */ + *out_state = m_state; + *out_flags = m_flags; + + R_SUCCEED(); + } + + Result Acknowledge(PmState state, Result res) { + /* Check that we're initialized. */ + R_UNLESS(m_initialized, psc::ResultNotInitialized()); + + /* Check the transition was successful. */ + R_ABORT_UNLESS(res); + + AMS_UNUSED(state); + R_SUCCEED(); + } + + os::SystemEvent *GetEventPointer() { + return std::addressof(m_system_event); + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/psc_pm_module.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/psc_pm_module.os.horizon.hpp new file mode 100644 index 00000000..a780c4d5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/psc_pm_module.os.horizon.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/psc/psc_types.hpp> +#include <stratosphere/psc/psc_pm_module_id.hpp> +#include <stratosphere/psc/sf/psc_sf_i_pm_module.hpp> + +namespace ams::psc { + + class PmModule { + NON_COPYABLE(PmModule); + NON_MOVEABLE(PmModule); + private: + ams::sf::SharedPointer<psc::sf::IPmModule> m_intf; + os::SystemEvent m_system_event; + bool m_initialized; + PmModuleId m_module_id; + uintptr_t m_reserved; + public: + PmModule(); + ~PmModule(); + + Result Initialize(const PmModuleId mid, const PmModuleId *dependencies, u32 dependency_count, os::EventClearMode clear_mode); + Result Finalize(); + + constexpr PmModuleId GetId() const { return m_module_id; } + + Result GetRequest(PmState *out_state, PmFlagSet *out_flags); + Result Acknowledge(PmState state, Result res); + + os::SystemEvent *GetEventPointer(); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/psc_pm_module_id.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/psc_pm_module_id.hpp new file mode 100644 index 00000000..b3c8a0f3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/psc_pm_module_id.hpp @@ -0,0 +1,85 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::psc { + + enum PmModuleId : u32 { + PmModuleId_Reserved0 = 0, + + PmModuleId_Usb = 4, + PmModuleId_Ethernet = 5, + PmModuleId_Fgm = 6, + PmModuleId_PcvClock = 7, + PmModuleId_PcvVoltage = 8, + PmModuleId_Gpio = 9, + PmModuleId_Pinmux = 10, + PmModuleId_Uart = 11, + PmModuleId_I2c = 12, + PmModuleId_I2cPcv = 13, + PmModuleId_Spi = 14, + PmModuleId_Pwm = 15, + PmModuleId_Psm = 16, + PmModuleId_Tc = 17, + PmModuleId_Omm = 18, + PmModuleId_Pcie = 19, + PmModuleId_Lbl = 20, + PmModuleId_Display = 21, + + PmModuleId_Hid = 24, + PmModuleId_WlanSockets = 25, + + PmModuleId_Fs = 27, + PmModuleId_Audio = 28, + + PmModuleId_TmaHostIo = 30, + PmModuleId_Bluetooth = 31, + PmModuleId_Bpc = 32, + PmModuleId_Fan = 33, + PmModuleId_Pcm = 34, + PmModuleId_Nfc = 35, + PmModuleId_Apm = 36, + PmModuleId_Btm = 37, + PmModuleId_Nifm = 38, + PmModuleId_GpioLow = 39, + PmModuleId_Npns = 40, + PmModuleId_Lm = 41, + PmModuleId_Bcat = 42, + PmModuleId_Time = 43, + PmModuleId_Pctl = 44, + PmModuleId_Erpt = 45, + PmModuleId_Eupld = 46, + PmModuleId_Friends = 47, + PmModuleId_Bgtc = 48, + PmModuleId_Account = 49, + PmModuleId_Sasbus = 50, + PmModuleId_Ntc = 51, + PmModuleId_Idle = 52, + PmModuleId_Tcap = 53, + PmModuleId_PsmLow = 54, + PmModuleId_Ndd = 55, + PmModuleId_Olsc = 56, + + PmModuleId_Ns = 61, + + PmModuleId_Nvservices = 101, + + PmModuleId_Spsm = 127, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/psc_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/psc_types.hpp new file mode 100644 index 00000000..5aa296d0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/psc_types.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::psc { + + enum PmState { + PmState_FullAwake = 0, + PmState_MinimumAwake = 1, + PmState_SleepReady = 2, + PmState_EssentialServicesSleepReady = 3, + PmState_EssentialServicesAwake = 4, + PmState_ShutdownReady = 5, + PmState_Unknown = 6, + }; + + constexpr inline u32 MaximumDependencyLevels = 20; + + struct PmFlag { + + }; + + using PmFlagSet = util::BitFlagSet<BITSIZEOF(u32), PmFlag>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/sf/psc_sf_i_pm_module.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/sf/psc_sf_i_pm_module.hpp new file mode 100644 index 00000000..90c2f5a1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/sf/psc_sf_i_pm_module.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/psc/psc_types.hpp> +#include <stratosphere/psc/psc_pm_module_id.hpp> + +#define AMS_PSC_I_PM_MODULE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, Initialize, (ams::sf::OutCopyHandle out, psc::PmModuleId module_id, const ams::sf::InBuffer &child_list), (out, module_id, child_list)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, GetRequest, (ams::sf::Out<psc::PmState> out_state, ams::sf::Out<psc::PmFlagSet> out_flags), (out_state, out_flags)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, Acknowledge, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, Finalize, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, AcknowledgeEx, (psc::PmState state), (state), hos::Version_5_1_0) + +AMS_SF_DEFINE_INTERFACE(ams::psc::sf, IPmModule, AMS_PSC_I_PM_MODULE_INTERFACE_INFO, 0x4275F38F) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/sf/psc_sf_i_pm_service.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/sf/psc_sf_i_pm_service.hpp new file mode 100644 index 00000000..78fff84f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/psc/sf/psc_sf_i_pm_service.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/psc/sf/psc_sf_i_pm_module.hpp> + +#define AMS_PSC_I_PM_SERVICE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, Initialize, (ams::sf::Out<ams::sf::SharedPointer<psc::sf::IPmModule>> out), (out)) + +AMS_SF_DEFINE_INTERFACE(ams::psc::sf, IPmService, AMS_PSC_I_PM_SERVICE_INTERFACE_INFO, 0xEABE6F26) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm.hpp new file mode 100644 index 00000000..1628b18c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/pwm/pwm_types.hpp> +#include <stratosphere/pwm/pwm_select_channel_name.hpp> +#include <stratosphere/pwm/sf/pwm_sf_i_channel_session.hpp> +#include <stratosphere/pwm/sf/pwm_sf_i_manager.hpp> +#include <stratosphere/pwm/server/pwm_server_api.hpp> +#include <stratosphere/pwm/driver/pwm_select_driver_api.hpp> +#include <stratosphere/pwm/driver/pwm_driver_service_api.hpp> +#include <stratosphere/pwm/driver/pwm_driver_client_api.hpp> +#include <stratosphere/pwm/driver/pwm_channel_api.hpp> +#include <stratosphere/pwm/pwm_api.hpp> +#include <stratosphere/pwm/pwm_channel_api.hpp> \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/board/nintendo/nx/pwm_driver_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/board/nintendo/nx/pwm_driver_api.hpp new file mode 100644 index 00000000..c7aa4bed --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/board/nintendo/nx/pwm_driver_api.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/pwm/pwm_types.hpp> + +namespace ams::pwm::driver::board::nintendo::nx { + + void Initialize(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/pwm_channel_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/pwm_channel_api.hpp new file mode 100644 index 00000000..435ff513 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/pwm_channel_api.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/pwm/pwm_types.hpp> + +namespace ams::pwm::driver { + + namespace impl { + + constexpr inline size_t ChannelSessionSize = 0x60; + constexpr inline size_t ChannelSessionAlign = 8; + struct alignas(ChannelSessionAlign) ChannelSessionImplPadded; + + } + + struct ChannelSession { + util::TypedStorage<impl::ChannelSessionImplPadded, impl::ChannelSessionSize, impl::ChannelSessionAlign> _impl; + }; + + Result OpenSession(ChannelSession *out, DeviceCode device_code); + void CloseSession(ChannelSession &session); + + void SetPeriod(ChannelSession &session, TimeSpan period); + TimeSpan GetPeriod(ChannelSession &session); + + void SetDuty(ChannelSession &session, int duty); + int GetDuty(ChannelSession &session); + + void SetEnabled(ChannelSession &session, bool en); + bool GetEnabled(ChannelSession &session); + + void SetScale(ChannelSession &session, double scale); + double GetScale(ChannelSession &session); + +} + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/pwm_driver_client_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/pwm_driver_client_api.hpp new file mode 100644 index 00000000..69481bb5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/pwm_driver_client_api.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/pwm/pwm_types.hpp> + +namespace ams::pwm::driver { + + void Initialize(); + void Finalize(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/pwm_driver_service_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/pwm_driver_service_api.hpp new file mode 100644 index 00000000..3733b384 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/pwm_driver_service_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/pwm/pwm_types.hpp> +#include <stratosphere/pwm/driver/pwm_i_pwm_device.hpp> +#include <stratosphere/pwm/driver/pwm_i_pwm_driver.hpp> + +namespace ams::pwm::driver { + + void RegisterDriver(IPwmDriver *driver); + void UnregisterDriver(IPwmDriver *driver); + + Result RegisterDeviceCode(DeviceCode device_code, IPwmDevice *device); + bool UnregisterDeviceCode(DeviceCode device_code); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/pwm_i_pwm_device.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/pwm_i_pwm_device.hpp new file mode 100644 index 00000000..3ae612b4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/pwm_i_pwm_device.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/pwm/pwm_types.hpp> +#include <stratosphere/ddsf.hpp> + +namespace ams::pwm::driver { + + class IPwmDevice : public ::ams::ddsf::IDevice { + NON_COPYABLE(IPwmDevice); + NON_MOVEABLE(IPwmDevice); + AMS_DDSF_CASTABLE_TRAITS(ams::pwm::driver::IPwmDevice, ::ams::ddsf::IDevice); + private: + int m_channel_index; + public: + IPwmDevice(int id) : IDevice(false), m_channel_index(id) { /* ... */ } + virtual ~IPwmDevice() { /* ... */ } + + constexpr int GetChannelIndex() const { return m_channel_index; } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/pwm_i_pwm_driver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/pwm_i_pwm_driver.hpp new file mode 100644 index 00000000..5ff831a6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/pwm_i_pwm_driver.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/pwm/pwm_types.hpp> +#include <stratosphere/pwm/driver/pwm_i_pwm_device.hpp> +#include <stratosphere/ddsf.hpp> + +namespace ams::pwm::driver { + + class IPwmDriver : public ::ams::ddsf::IDriver { + NON_COPYABLE(IPwmDriver); + NON_MOVEABLE(IPwmDriver); + AMS_DDSF_CASTABLE_TRAITS(ams::pwm::driver::IPwmDriver, ::ams::ddsf::IDriver); + public: + IPwmDriver() : IDriver() { /* ... */ } + virtual ~IPwmDriver() { /* ... */ } + + virtual void InitializeDriver() = 0; + virtual void FinalizeDriver() = 0; + + virtual Result InitializeDevice(IPwmDevice *device) = 0; + virtual void FinalizeDevice(IPwmDevice *device) = 0; + + virtual Result SetPeriod(IPwmDevice *device, TimeSpan period) = 0; + virtual Result GetPeriod(TimeSpan *out, IPwmDevice *device) = 0; + + /* TODO: Nintendo removed these in 14.0.0. Should we? */ + virtual Result SetDuty(IPwmDevice *device, int duty) = 0; + virtual Result GetDuty(int *out, IPwmDevice *device) = 0; + + virtual Result SetScale(IPwmDevice *device, double scale) = 0; + virtual Result GetScale(double *out, IPwmDevice *device) = 0; + + virtual Result SetEnabled(IPwmDevice *device, bool en) = 0; + virtual Result GetEnabled(bool *out, IPwmDevice *device) = 0; + + virtual Result Suspend() = 0; + virtual void Resume() = 0; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/pwm_select_driver_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/pwm_select_driver_api.hpp new file mode 100644 index 00000000..b5c0f063 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/driver/pwm_select_driver_api.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/pwm/pwm_types.hpp> +#include <stratosphere/pwm/driver/pwm_i_pwm_driver.hpp> + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + + #include <stratosphere/pwm/driver/board/nintendo/nx/pwm_driver_api.hpp> + + namespace ams::pwm::driver::board { + + using namespace ams::pwm::driver::board::nintendo::nx; + + } + +#else + + // TODO: #error "Unknown board for ams::pwm::driver::" + +#endif + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/pwm_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/pwm_api.hpp new file mode 100644 index 00000000..9f983fbc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/pwm_api.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/pwm/pwm_types.hpp> +#include <stratosphere/pwm/sf/pwm_sf_i_manager.hpp> + +namespace ams::pwm { + + void InitializeWith(ams::sf::SharedPointer<pwm::sf::IManager> sp); + void Finalize(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/pwm_channel_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/pwm_channel_api.hpp new file mode 100644 index 00000000..08e07476 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/pwm_channel_api.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/pwm/pwm_types.hpp> + +namespace ams::pwm { + + struct ChannelSession { + void *_session; + }; + + Result OpenSession(ChannelSession *out, DeviceCode device_code); + void CloseSession(ChannelSession &session); + + void SetPeriod(ChannelSession &session, TimeSpan period); + TimeSpan GetPeriod(ChannelSession &session); + + void SetDuty(ChannelSession &session, int duty); + int GetDuty(ChannelSession &session); + + void SetEnabled(ChannelSession &session, bool en); + bool GetEnabled(ChannelSession &session); + + void SetScale(ChannelSession &session, double scale); + double GetScale(ChannelSession &session); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/pwm_channel_name.board.nintendo_nx.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/pwm_channel_name.board.nintendo_nx.hpp new file mode 100644 index 00000000..bfa5eeaf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/pwm_channel_name.board.nintendo_nx.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/pwm/pwm_types.hpp> + +namespace ams::pwm { + + enum ChannelName { + ChannelName_Invalid = 0, + ChannelName_CpuFan = 1, + ChannelName_LcdBacklight = 2, + ChannelName_BlinkLed = 3, + }; + + constexpr inline const DeviceCode DeviceCode_CpuFan = 0x3D000001; + constexpr inline const DeviceCode DeviceCode_LcdBacklight = 0x3400003D; + constexpr inline const DeviceCode DeviceCode_BlinkLed = 0x35000065; + + constexpr inline DeviceCode ConvertToDeviceCode(ChannelName cn) { + switch (cn) { + case ChannelName_CpuFan: return DeviceCode_CpuFan; + case ChannelName_LcdBacklight: return DeviceCode_LcdBacklight; + case ChannelName_BlinkLed: return DeviceCode_BlinkLed; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + constexpr inline ChannelName ConvertToChannelName(DeviceCode dc) { + switch (dc.GetInternalValue()) { + case DeviceCode_CpuFan .GetInternalValue(): return ChannelName_CpuFan; + case DeviceCode_LcdBacklight.GetInternalValue(): return ChannelName_LcdBacklight; + case DeviceCode_BlinkLed .GetInternalValue(): return ChannelName_BlinkLed; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/pwm_channel_name.generic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/pwm_channel_name.generic.hpp new file mode 100644 index 00000000..c371a27c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/pwm_channel_name.generic.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/pwm/pwm_types.hpp> + +namespace ams::pwm { + + enum ChannelName { + ChannelName_Invalid = 0, + }; + + constexpr inline DeviceCode ConvertToDeviceCode(ChannelName cn) { + switch (cn) { + AMS_UNREACHABLE_DEFAULT_CASE(); + } + return InvalidDeviceCode; + } + + constexpr inline ChannelName ConvertToChannelName(DeviceCode dc) { + switch (dc.GetInternalValue()) { + AMS_UNREACHABLE_DEFAULT_CASE(); + } + return ChannelName_Invalid; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/pwm_select_channel_name.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/pwm_select_channel_name.hpp new file mode 100644 index 00000000..c85af383 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/pwm_select_channel_name.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/pwm/pwm_types.hpp> + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + #include <stratosphere/pwm/pwm_channel_name.board.nintendo_nx.hpp> +#else + #include <stratosphere/pwm/pwm_channel_name.generic.hpp> +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/pwm_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/pwm_types.hpp new file mode 100644 index 00000000..fcb538b3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/pwm_types.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::pwm { + + /* ... */ + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/server/pwm_server_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/server/pwm_server_api.hpp new file mode 100644 index 00000000..73daa719 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/server/pwm_server_api.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/pwm/pwm_types.hpp> +#include <stratosphere/pwm/sf/pwm_sf_i_manager.hpp> + +namespace ams::pwm::server { + + ams::sf::SharedPointer<pwm::sf::IManager> GetServiceObject(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/sf/pwm_sf_i_channel_session.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/sf/pwm_sf_i_channel_session.hpp new file mode 100644 index 00000000..98f1c5ba --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/sf/pwm_sf_i_channel_session.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/pwm/pwm_types.hpp> + +#define AMS_PWM_I_CHANNEL_SESSION_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, SetPeriod, (TimeSpanType period), (period)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, GetPeriod, (ams::sf::Out<TimeSpanType> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, SetDuty, (int duty), (duty), hos::Version_Min, hos::Version_13_2_1) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, GetDuty, (ams::sf::Out<int> out), (out), hos::Version_Min, hos::Version_13_2_1) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, SetEnabled, (bool enabled), (enabled)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, GetEnabled, (ams::sf::Out<bool> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, SetScale, (double scale), (scale), hos::Version_6_0_0) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, GetScale, (ams::sf::Out<double> out), (out), hos::Version_6_0_0) + +AMS_SF_DEFINE_INTERFACE(ams::pwm::sf, IChannelSession, AMS_PWM_I_CHANNEL_SESSION_INTERFACE_INFO, 0xAC0A18F9) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/sf/pwm_sf_i_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/sf/pwm_sf_i_manager.hpp new file mode 100644 index 00000000..edf0f4d5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/pwm/sf/pwm_sf_i_manager.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/ddsf/ddsf_types.hpp> +#include <stratosphere/pwm/pwm_select_channel_name.hpp> +#include <stratosphere/pwm/sf/pwm_sf_i_channel_session.hpp> + +#define AMS_PWM_I_MANAGER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, OpenSessionForDev, (ams::sf::Out<ams::sf::SharedPointer<pwm::sf::IChannelSession>> out, int channel), (out, channel) ) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, OpenSession, (ams::sf::Out<ams::sf::SharedPointer<pwm::sf::IChannelSession>> out, pwm::ChannelName channel_name), (out, channel_name) ) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, OpenSession2, (ams::sf::Out<ams::sf::SharedPointer<pwm::sf::IChannelSession>> out, DeviceCode device_code), (out, device_code), hos::Version_6_0_0) + +AMS_SF_DEFINE_INTERFACE(ams::pwm::sf, IManager, AMS_PWM_I_MANAGER_INTERFACE_INFO, 0xBC382479) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson.hpp new file mode 100644 index 00000000..ddd30b9a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/ams/ams_environment.hpp> + +#define RAPIDJSON_NAMESPACE ams::rapidjson +#define RAPIDJSON_ASSERT(x) AMS_ABORT_UNLESS(x) + +#define RAPIDJSON_MALLOC(size) ams::MallocForRapidJson(size) +#define RAPIDJSON_REALLOC(ptr, size) ams::ReallocForRapidJson(ptr, size) +#define RAPIDJSON_FREE(ptr) ams::FreeForRapidJson(ptr) + +#include <stratosphere/rapidjson/rapidjson.h> +#include <stratosphere/rapidjson/encodings.h> +#include <stratosphere/rapidjson/reader.h> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/allocators.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/allocators.h new file mode 100644 index 00000000..44ec5295 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/allocators.h @@ -0,0 +1,284 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ALLOCATORS_H_ +#define RAPIDJSON_ALLOCATORS_H_ + +#include "rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Allocator + +/*! \class rapidjson::Allocator + \brief Concept for allocating, resizing and freeing memory block. + + Note that Malloc() and Realloc() are non-static but Free() is static. + + So if an allocator need to support Free(), it needs to put its pointer in + the header of memory block. + +\code +concept Allocator { + static const bool kNeedFree; //!< Whether this allocator needs to call Free(). + + // Allocate a memory block. + // \param size of the memory block in bytes. + // \returns pointer to the memory block. + void* Malloc(size_t size); + + // Resize a memory block. + // \param originalPtr The pointer to current memory block. Null pointer is permitted. + // \param originalSize The current size in bytes. (Design issue: since some allocator may not book-keep this, explicitly pass to it can save memory.) + // \param newSize the new size in bytes. + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize); + + // Free a memory block. + // \param pointer to the memory block. Null pointer is permitted. + static void Free(void *ptr); +}; +\endcode +*/ + + +/*! \def RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY + \ingroup RAPIDJSON_CONFIG + \brief User-defined kDefaultChunkCapacity definition. + + User can define this as any \c size that is a power of 2. +*/ + +#ifndef RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY +#define RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY (64 * 1024) +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// CrtAllocator + +//! C-runtime library allocator. +/*! This class is just wrapper for standard C library memory routines. + \note implements Allocator concept +*/ +class CrtAllocator { +public: + static const bool kNeedFree = true; + void* Malloc(size_t size) { + if (size) // behavior of malloc(0) is implementation defined. + return RAPIDJSON_MALLOC(size); + else + return NULL; // standardize to returning NULL. + } + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { + (void)originalSize; + if (newSize == 0) { + RAPIDJSON_FREE(originalPtr); + return NULL; + } + return RAPIDJSON_REALLOC(originalPtr, newSize); + } + static void Free(void *ptr) { RAPIDJSON_FREE(ptr); } +}; + +/////////////////////////////////////////////////////////////////////////////// +// MemoryPoolAllocator + +//! Default memory allocator used by the parser and DOM. +/*! This allocator allocate memory blocks from pre-allocated memory chunks. + + It does not free memory blocks. And Realloc() only allocate new memory. + + The memory chunks are allocated by BaseAllocator, which is CrtAllocator by default. + + User may also supply a buffer as the first chunk. + + If the user-buffer is full then additional chunks are allocated by BaseAllocator. + + The user-buffer is not deallocated by this allocator. + + \tparam BaseAllocator the allocator type for allocating memory chunks. Default is CrtAllocator. + \note implements Allocator concept +*/ +template <typename BaseAllocator = CrtAllocator> +class MemoryPoolAllocator { +public: + static const bool kNeedFree = false; //!< Tell users that no need to call Free() with this allocator. (concept Allocator) + + //! Constructor with chunkSize. + /*! \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. + \param baseAllocator The allocator for allocating memory chunks. + */ + MemoryPoolAllocator(size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : + chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(0), baseAllocator_(baseAllocator), ownBaseAllocator_(0) + { + } + + //! Constructor with user-supplied buffer. + /*! The user buffer will be used firstly. When it is full, memory pool allocates new chunk with chunk size. + + The user buffer will not be deallocated when this allocator is destructed. + + \param buffer User supplied buffer. + \param size Size of the buffer in bytes. It must at least larger than sizeof(ChunkHeader). + \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. + \param baseAllocator The allocator for allocating memory chunks. + */ + MemoryPoolAllocator(void *buffer, size_t size, size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : + chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(buffer), baseAllocator_(baseAllocator), ownBaseAllocator_(0) + { + RAPIDJSON_ASSERT(buffer != 0); + RAPIDJSON_ASSERT(size > sizeof(ChunkHeader)); + chunkHead_ = reinterpret_cast<ChunkHeader*>(buffer); + chunkHead_->capacity = size - sizeof(ChunkHeader); + chunkHead_->size = 0; + chunkHead_->next = 0; + } + + //! Destructor. + /*! This deallocates all memory chunks, excluding the user-supplied buffer. + */ + ~MemoryPoolAllocator() { + Clear(); + RAPIDJSON_DELETE(ownBaseAllocator_); + } + + //! Deallocates all memory chunks, excluding the user-supplied buffer. + void Clear() { + while (chunkHead_ && chunkHead_ != userBuffer_) { + ChunkHeader* next = chunkHead_->next; + baseAllocator_->Free(chunkHead_); + chunkHead_ = next; + } + if (chunkHead_ && chunkHead_ == userBuffer_) + chunkHead_->size = 0; // Clear user buffer + } + + //! Computes the total capacity of allocated memory chunks. + /*! \return total capacity in bytes. + */ + size_t Capacity() const { + size_t capacity = 0; + for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) + capacity += c->capacity; + return capacity; + } + + //! Computes the memory blocks allocated. + /*! \return total used bytes. + */ + size_t Size() const { + size_t size = 0; + for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) + size += c->size; + return size; + } + + //! Allocates a memory block. (concept Allocator) + void* Malloc(size_t size) { + if (!size) + return NULL; + + size = RAPIDJSON_ALIGN(size); + if (chunkHead_ == 0 || chunkHead_->size + size > chunkHead_->capacity) + if (!AddChunk(chunk_capacity_ > size ? chunk_capacity_ : size)) + return NULL; + + void *buffer = reinterpret_cast<char *>(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size; + chunkHead_->size += size; + return buffer; + } + + //! Resizes a memory block (concept Allocator) + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { + if (originalPtr == 0) + return Malloc(newSize); + + if (newSize == 0) + return NULL; + + originalSize = RAPIDJSON_ALIGN(originalSize); + newSize = RAPIDJSON_ALIGN(newSize); + + // Do not shrink if new size is smaller than original + if (originalSize >= newSize) + return originalPtr; + + // Simply expand it if it is the last allocation and there is sufficient space + if (originalPtr == reinterpret_cast<char *>(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size - originalSize) { + size_t increment = static_cast<size_t>(newSize - originalSize); + if (chunkHead_->size + increment <= chunkHead_->capacity) { + chunkHead_->size += increment; + return originalPtr; + } + } + + // Realloc process: allocate and copy memory, do not free original buffer. + if (void* newBuffer = Malloc(newSize)) { + if (originalSize) + std::memcpy(newBuffer, originalPtr, originalSize); + return newBuffer; + } + else + return NULL; + } + + //! Frees a memory block (concept Allocator) + static void Free(void *ptr) { (void)ptr; } // Do nothing + +private: + //! Copy constructor is not permitted. + MemoryPoolAllocator(const MemoryPoolAllocator& rhs) /* = delete */; + //! Copy assignment operator is not permitted. + MemoryPoolAllocator& operator=(const MemoryPoolAllocator& rhs) /* = delete */; + + //! Creates a new chunk. + /*! \param capacity Capacity of the chunk in bytes. + \return true if success. + */ + bool AddChunk(size_t capacity) { + if (!baseAllocator_) + ownBaseAllocator_ = baseAllocator_ = RAPIDJSON_NEW(BaseAllocator)(); + if (ChunkHeader* chunk = reinterpret_cast<ChunkHeader*>(baseAllocator_->Malloc(RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + capacity))) { + chunk->capacity = capacity; + chunk->size = 0; + chunk->next = chunkHead_; + chunkHead_ = chunk; + return true; + } + else + return false; + } + + static const int kDefaultChunkCapacity = RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY; //!< Default chunk capacity. + + //! Chunk header for perpending to each chunk. + /*! Chunks are stored as a singly linked list. + */ + struct ChunkHeader { + size_t capacity; //!< Capacity of the chunk in bytes (excluding the header itself). + size_t size; //!< Current size of allocated memory in bytes. + ChunkHeader *next; //!< Next chunk in the linked list. + }; + + ChunkHeader *chunkHead_; //!< Head of the chunk linked-list. Only the head chunk serves allocation. + size_t chunk_capacity_; //!< The minimum capacity of chunk when they are allocated. + void *userBuffer_; //!< User supplied buffer. + BaseAllocator* baseAllocator_; //!< base allocator for allocating memory chunks. + BaseAllocator* ownBaseAllocator_; //!< base allocator created by this object. +}; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ENCODINGS_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/cursorstreamwrapper.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/cursorstreamwrapper.h new file mode 100644 index 00000000..fd6513db --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/cursorstreamwrapper.h @@ -0,0 +1,78 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_CURSORSTREAMWRAPPER_H_ +#define RAPIDJSON_CURSORSTREAMWRAPPER_H_ + +#include "stream.h" + +#if defined(__GNUC__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4702) // unreachable code +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +RAPIDJSON_NAMESPACE_BEGIN + + +//! Cursor stream wrapper for counting line and column number if error exists. +/*! + \tparam InputStream Any stream that implements Stream Concept +*/ +template <typename InputStream, typename Encoding = UTF8<> > +class CursorStreamWrapper : public GenericStreamWrapper<InputStream, Encoding> { +public: + typedef typename Encoding::Ch Ch; + + CursorStreamWrapper(InputStream& is): + GenericStreamWrapper<InputStream, Encoding>(is), line_(1), col_(0) {} + + // counting line and column number + Ch Take() { + Ch ch = this->is_.Take(); + if(ch == '\n') { + line_ ++; + col_ = 0; + } else { + col_ ++; + } + return ch; + } + + //! Get the error line number, if error exists. + size_t GetLine() const { return line_; } + //! Get the error column number, if error exists. + size_t GetColumn() const { return col_; } + +private: + size_t line_; //!< Current Line + size_t col_; //!< Current Column +}; + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +RAPIDJSON_DIAG_POP +#endif + +#if defined(__GNUC__) +RAPIDJSON_DIAG_POP +#endif + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_CURSORSTREAMWRAPPER_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/document.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/document.h new file mode 100644 index 00000000..028235ec --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/document.h @@ -0,0 +1,2737 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_DOCUMENT_H_ +#define RAPIDJSON_DOCUMENT_H_ + +/*! \file document.h */ + +#include "reader.h" +#include "internal/meta.h" +#include "internal/strfunc.h" +#include "memorystream.h" +#include "encodedstream.h" +#include <new> // placement new +#include <limits> +#ifdef __cpp_lib_three_way_comparison +#include <compare> +#endif + +RAPIDJSON_DIAG_PUSH +#ifdef __clang__ +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(switch-enum) +RAPIDJSON_DIAG_OFF(c++98-compat) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +RAPIDJSON_DIAG_OFF(4244) // conversion from kXxxFlags to 'uint16_t', possible loss of data +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_OFF(effc++) +#endif // __GNUC__ + +#ifndef RAPIDJSON_NOMEMBERITERATORCLASS +#include <iterator> // std::random_access_iterator_tag +#endif + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS +#include <utility> // std::move +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +// Forward declaration. +template <typename Encoding, typename Allocator> +class GenericValue; + +template <typename Encoding, typename Allocator, typename StackAllocator> +class GenericDocument; + +/*! \def RAPIDJSON_DEFAULT_ALLOCATOR + \ingroup RAPIDJSON_CONFIG + \brief Allows to choose default allocator. + + User can define this to use CrtAllocator or MemoryPoolAllocator. +*/ +#ifndef RAPIDJSON_DEFAULT_ALLOCATOR +#define RAPIDJSON_DEFAULT_ALLOCATOR MemoryPoolAllocator<CrtAllocator> +#endif + +/*! \def RAPIDJSON_DEFAULT_STACK_ALLOCATOR + \ingroup RAPIDJSON_CONFIG + \brief Allows to choose default stack allocator for Document. + + User can define this to use CrtAllocator or MemoryPoolAllocator. +*/ +#ifndef RAPIDJSON_DEFAULT_STACK_ALLOCATOR +#define RAPIDJSON_DEFAULT_STACK_ALLOCATOR CrtAllocator +#endif + +/*! \def RAPIDJSON_VALUE_DEFAULT_OBJECT_CAPACITY + \ingroup RAPIDJSON_CONFIG + \brief User defined kDefaultObjectCapacity value. + + User can define this as any natural number. +*/ +#ifndef RAPIDJSON_VALUE_DEFAULT_OBJECT_CAPACITY +// number of objects that rapidjson::Value allocates memory for by default +#define RAPIDJSON_VALUE_DEFAULT_OBJECT_CAPACITY 16 +#endif + +/*! \def RAPIDJSON_VALUE_DEFAULT_ARRAY_CAPACITY + \ingroup RAPIDJSON_CONFIG + \brief User defined kDefaultArrayCapacity value. + + User can define this as any natural number. +*/ +#ifndef RAPIDJSON_VALUE_DEFAULT_ARRAY_CAPACITY +// number of array elements that rapidjson::Value allocates memory for by default +#define RAPIDJSON_VALUE_DEFAULT_ARRAY_CAPACITY 16 +#endif + +//! Name-value pair in a JSON object value. +/*! + This class was internal to GenericValue. It used to be a inner struct. + But a compiler (IBM XL C/C++ for AIX) have reported to have problem with that so it moved as a namespace scope struct. + https://code.google.com/p/rapidjson/issues/detail?id=64 +*/ +template <typename Encoding, typename Allocator> +class GenericMember { +public: + GenericValue<Encoding, Allocator> name; //!< name of member (must be a string) + GenericValue<Encoding, Allocator> value; //!< value of member. + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericMember(GenericMember&& rhs) RAPIDJSON_NOEXCEPT + : name(std::move(rhs.name)), + value(std::move(rhs.value)) + { + } + + //! Move assignment in C++11 + GenericMember& operator=(GenericMember&& rhs) RAPIDJSON_NOEXCEPT { + return *this = static_cast<GenericMember&>(rhs); + } +#endif + + //! Assignment with move semantics. + /*! \param rhs Source of the assignment. Its name and value will become a null value after assignment. + */ + GenericMember& operator=(GenericMember& rhs) RAPIDJSON_NOEXCEPT { + if (RAPIDJSON_LIKELY(this != &rhs)) { + name = rhs.name; + value = rhs.value; + } + return *this; + } + + // swap() for std::sort() and other potential use in STL. + friend inline void swap(GenericMember& a, GenericMember& b) RAPIDJSON_NOEXCEPT { + a.name.Swap(b.name); + a.value.Swap(b.value); + } + +private: + //! Copy constructor is not permitted. + GenericMember(const GenericMember& rhs); +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericMemberIterator + +#ifndef RAPIDJSON_NOMEMBERITERATORCLASS + +//! (Constant) member iterator for a JSON object value +/*! + \tparam Const Is this a constant iterator? + \tparam Encoding Encoding of the value. (Even non-string values need to have the same encoding in a document) + \tparam Allocator Allocator type for allocating memory of object, array and string. + + This class implements a Random Access Iterator for GenericMember elements + of a GenericValue, see ISO/IEC 14882:2003(E) C++ standard, 24.1 [lib.iterator.requirements]. + + \note This iterator implementation is mainly intended to avoid implicit + conversions from iterator values to \c NULL, + e.g. from GenericValue::FindMember. + + \note Define \c RAPIDJSON_NOMEMBERITERATORCLASS to fall back to a + pointer-based implementation, if your platform doesn't provide + the C++ <iterator> header. + + \see GenericMember, GenericValue::MemberIterator, GenericValue::ConstMemberIterator + */ +template <bool Const, typename Encoding, typename Allocator> +class GenericMemberIterator { + + friend class GenericValue<Encoding,Allocator>; + template <bool, typename, typename> friend class GenericMemberIterator; + + typedef GenericMember<Encoding,Allocator> PlainType; + typedef typename internal::MaybeAddConst<Const,PlainType>::Type ValueType; + +public: + //! Iterator type itself + typedef GenericMemberIterator Iterator; + //! Constant iterator type + typedef GenericMemberIterator<true,Encoding,Allocator> ConstIterator; + //! Non-constant iterator type + typedef GenericMemberIterator<false,Encoding,Allocator> NonConstIterator; + + /** \name std::iterator_traits support */ + //@{ + typedef ValueType value_type; + typedef ValueType * pointer; + typedef ValueType & reference; + typedef std::ptrdiff_t difference_type; + typedef std::random_access_iterator_tag iterator_category; + //@} + + //! Pointer to (const) GenericMember + typedef pointer Pointer; + //! Reference to (const) GenericMember + typedef reference Reference; + //! Signed integer type (e.g. \c ptrdiff_t) + typedef difference_type DifferenceType; + + //! Default constructor (singular value) + /*! Creates an iterator pointing to no element. + \note All operations, except for comparisons, are undefined on such values. + */ + GenericMemberIterator() : ptr_() {} + + //! Iterator conversions to more const + /*! + \param it (Non-const) iterator to copy from + + Allows the creation of an iterator from another GenericMemberIterator + that is "less const". Especially, creating a non-constant iterator + from a constant iterator are disabled: + \li const -> non-const (not ok) + \li const -> const (ok) + \li non-const -> const (ok) + \li non-const -> non-const (ok) + + \note If the \c Const template parameter is already \c false, this + constructor effectively defines a regular copy-constructor. + Otherwise, the copy constructor is implicitly defined. + */ + GenericMemberIterator(const NonConstIterator & it) : ptr_(it.ptr_) {} + Iterator& operator=(const NonConstIterator & it) { ptr_ = it.ptr_; return *this; } + + //! @name stepping + //@{ + Iterator& operator++(){ ++ptr_; return *this; } + Iterator& operator--(){ --ptr_; return *this; } + Iterator operator++(int){ Iterator old(*this); ++ptr_; return old; } + Iterator operator--(int){ Iterator old(*this); --ptr_; return old; } + //@} + + //! @name increment/decrement + //@{ + Iterator operator+(DifferenceType n) const { return Iterator(ptr_+n); } + Iterator operator-(DifferenceType n) const { return Iterator(ptr_-n); } + + Iterator& operator+=(DifferenceType n) { ptr_+=n; return *this; } + Iterator& operator-=(DifferenceType n) { ptr_-=n; return *this; } + //@} + + //! @name relations + //@{ + template <bool Const_> bool operator==(const GenericMemberIterator<Const_, Encoding, Allocator>& that) const { return ptr_ == that.ptr_; } + template <bool Const_> bool operator!=(const GenericMemberIterator<Const_, Encoding, Allocator>& that) const { return ptr_ != that.ptr_; } + template <bool Const_> bool operator<=(const GenericMemberIterator<Const_, Encoding, Allocator>& that) const { return ptr_ <= that.ptr_; } + template <bool Const_> bool operator>=(const GenericMemberIterator<Const_, Encoding, Allocator>& that) const { return ptr_ >= that.ptr_; } + template <bool Const_> bool operator< (const GenericMemberIterator<Const_, Encoding, Allocator>& that) const { return ptr_ < that.ptr_; } + template <bool Const_> bool operator> (const GenericMemberIterator<Const_, Encoding, Allocator>& that) const { return ptr_ > that.ptr_; } + +#ifdef __cpp_lib_three_way_comparison + template <bool Const_> std::strong_ordering operator<=>(const GenericMemberIterator<Const_, Encoding, Allocator>& that) const { return ptr_ <=> that.ptr_; } +#endif + //@} + + //! @name dereference + //@{ + Reference operator*() const { return *ptr_; } + Pointer operator->() const { return ptr_; } + Reference operator[](DifferenceType n) const { return ptr_[n]; } + //@} + + //! Distance + DifferenceType operator-(ConstIterator that) const { return ptr_-that.ptr_; } + +private: + //! Internal constructor from plain pointer + explicit GenericMemberIterator(Pointer p) : ptr_(p) {} + + Pointer ptr_; //!< raw pointer +}; + +#else // RAPIDJSON_NOMEMBERITERATORCLASS + +// class-based member iterator implementation disabled, use plain pointers + +template <bool Const, typename Encoding, typename Allocator> +class GenericMemberIterator; + +//! non-const GenericMemberIterator +template <typename Encoding, typename Allocator> +class GenericMemberIterator<false,Encoding,Allocator> { +public: + //! use plain pointer as iterator type + typedef GenericMember<Encoding,Allocator>* Iterator; +}; +//! const GenericMemberIterator +template <typename Encoding, typename Allocator> +class GenericMemberIterator<true,Encoding,Allocator> { +public: + //! use plain const pointer as iterator type + typedef const GenericMember<Encoding,Allocator>* Iterator; +}; + +#endif // RAPIDJSON_NOMEMBERITERATORCLASS + +/////////////////////////////////////////////////////////////////////////////// +// GenericStringRef + +//! Reference to a constant string (not taking a copy) +/*! + \tparam CharType character type of the string + + This helper class is used to automatically infer constant string + references for string literals, especially from \c const \b (!) + character arrays. + + The main use is for creating JSON string values without copying the + source string via an \ref Allocator. This requires that the referenced + string pointers have a sufficient lifetime, which exceeds the lifetime + of the associated GenericValue. + + \b Example + \code + Value v("foo"); // ok, no need to copy & calculate length + const char foo[] = "foo"; + v.SetString(foo); // ok + + const char* bar = foo; + // Value x(bar); // not ok, can't rely on bar's lifetime + Value x(StringRef(bar)); // lifetime explicitly guaranteed by user + Value y(StringRef(bar, 3)); // ok, explicitly pass length + \endcode + + \see StringRef, GenericValue::SetString +*/ +template<typename CharType> +struct GenericStringRef { + typedef CharType Ch; //!< character type of the string + + //! Create string reference from \c const character array +#ifndef __clang__ // -Wdocumentation + /*! + This constructor implicitly creates a constant string reference from + a \c const character array. It has better performance than + \ref StringRef(const CharType*) by inferring the string \ref length + from the array length, and also supports strings containing null + characters. + + \tparam N length of the string, automatically inferred + + \param str Constant character array, lifetime assumed to be longer + than the use of the string in e.g. a GenericValue + + \post \ref s == str + + \note Constant complexity. + \note There is a hidden, private overload to disallow references to + non-const character arrays to be created via this constructor. + By this, e.g. function-scope arrays used to be filled via + \c snprintf are excluded from consideration. + In such cases, the referenced string should be \b copied to the + GenericValue instead. + */ +#endif + template<SizeType N> + GenericStringRef(const CharType (&str)[N]) RAPIDJSON_NOEXCEPT + : s(str), length(N-1) {} + + //! Explicitly create string reference from \c const character pointer +#ifndef __clang__ // -Wdocumentation + /*! + This constructor can be used to \b explicitly create a reference to + a constant string pointer. + + \see StringRef(const CharType*) + + \param str Constant character pointer, lifetime assumed to be longer + than the use of the string in e.g. a GenericValue + + \post \ref s == str + + \note There is a hidden, private overload to disallow references to + non-const character arrays to be created via this constructor. + By this, e.g. function-scope arrays used to be filled via + \c snprintf are excluded from consideration. + In such cases, the referenced string should be \b copied to the + GenericValue instead. + */ +#endif + explicit GenericStringRef(const CharType* str) + : s(str), length(NotNullStrLen(str)) {} + + //! Create constant string reference from pointer and length +#ifndef __clang__ // -Wdocumentation + /*! \param str constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \param len length of the string, excluding the trailing NULL terminator + + \post \ref s == str && \ref length == len + \note Constant complexity. + */ +#endif + GenericStringRef(const CharType* str, SizeType len) + : s(RAPIDJSON_LIKELY(str) ? str : emptyString), length(len) { RAPIDJSON_ASSERT(str != 0 || len == 0u); } + + GenericStringRef(const GenericStringRef& rhs) : s(rhs.s), length(rhs.length) {} + + //! implicit conversion to plain CharType pointer + operator const Ch *() const { return s; } + + const Ch* const s; //!< plain CharType pointer + const SizeType length; //!< length of the string (excluding the trailing NULL terminator) + +private: + SizeType NotNullStrLen(const CharType* str) { + RAPIDJSON_ASSERT(str != 0); + return internal::StrLen(str); + } + + /// Empty string - used when passing in a NULL pointer + static const Ch emptyString[]; + + //! Disallow construction from non-const array + template<SizeType N> + GenericStringRef(CharType (&str)[N]) /* = delete */; + //! Copy assignment operator not permitted - immutable type + GenericStringRef& operator=(const GenericStringRef& rhs) /* = delete */; +}; + +template<typename CharType> +const CharType GenericStringRef<CharType>::emptyString[] = { CharType() }; + +//! Mark a character pointer as constant string +/*! Mark a plain character pointer as a "string literal". This function + can be used to avoid copying a character string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + \tparam CharType Character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \return GenericStringRef string reference object + \relatesalso GenericStringRef + + \see GenericValue::GenericValue(StringRefType), GenericValue::operator=(StringRefType), GenericValue::SetString(StringRefType), GenericValue::PushBack(StringRefType, Allocator&), GenericValue::AddMember +*/ +template<typename CharType> +inline GenericStringRef<CharType> StringRef(const CharType* str) { + return GenericStringRef<CharType>(str); +} + +//! Mark a character pointer as constant string +/*! Mark a plain character pointer as a "string literal". This function + can be used to avoid copying a character string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + + This version has better performance with supplied length, and also + supports string containing null characters. + + \tparam CharType character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \param length The length of source string. + \return GenericStringRef string reference object + \relatesalso GenericStringRef +*/ +template<typename CharType> +inline GenericStringRef<CharType> StringRef(const CharType* str, size_t length) { + return GenericStringRef<CharType>(str, SizeType(length)); +} + +#if RAPIDJSON_HAS_STDSTRING +//! Mark a string object as constant string +/*! Mark a string object (e.g. \c std::string) as a "string literal". + This function can be used to avoid copying a string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + + \tparam CharType character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \return GenericStringRef string reference object + \relatesalso GenericStringRef + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. +*/ +template<typename CharType> +inline GenericStringRef<CharType> StringRef(const std::basic_string<CharType>& str) { + return GenericStringRef<CharType>(str.data(), SizeType(str.size())); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +// GenericValue type traits +namespace internal { + +template <typename T, typename Encoding = void, typename Allocator = void> +struct IsGenericValueImpl : FalseType {}; + +// select candidates according to nested encoding and allocator types +template <typename T> struct IsGenericValueImpl<T, typename Void<typename T::EncodingType>::Type, typename Void<typename T::AllocatorType>::Type> + : IsBaseOf<GenericValue<typename T::EncodingType, typename T::AllocatorType>, T>::Type {}; + +// helper to match arbitrary GenericValue instantiations, including derived classes +template <typename T> struct IsGenericValue : IsGenericValueImpl<T>::Type {}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// TypeHelper + +namespace internal { + +template <typename ValueType, typename T> +struct TypeHelper {}; + +template<typename ValueType> +struct TypeHelper<ValueType, bool> { + static bool Is(const ValueType& v) { return v.IsBool(); } + static bool Get(const ValueType& v) { return v.GetBool(); } + static ValueType& Set(ValueType& v, bool data) { return v.SetBool(data); } + static ValueType& Set(ValueType& v, bool data, typename ValueType::AllocatorType&) { return v.SetBool(data); } +}; + +template<typename ValueType> +struct TypeHelper<ValueType, int> { + static bool Is(const ValueType& v) { return v.IsInt(); } + static int Get(const ValueType& v) { return v.GetInt(); } + static ValueType& Set(ValueType& v, int data) { return v.SetInt(data); } + static ValueType& Set(ValueType& v, int data, typename ValueType::AllocatorType&) { return v.SetInt(data); } +}; + +template<typename ValueType> +struct TypeHelper<ValueType, unsigned> { + static bool Is(const ValueType& v) { return v.IsUint(); } + static unsigned Get(const ValueType& v) { return v.GetUint(); } + static ValueType& Set(ValueType& v, unsigned data) { return v.SetUint(data); } + static ValueType& Set(ValueType& v, unsigned data, typename ValueType::AllocatorType&) { return v.SetUint(data); } +}; + +#ifdef _MSC_VER +RAPIDJSON_STATIC_ASSERT(sizeof(long) == sizeof(int)); +template<typename ValueType> +struct TypeHelper<ValueType, long> { + static bool Is(const ValueType& v) { return v.IsInt(); } + static long Get(const ValueType& v) { return v.GetInt(); } + static ValueType& Set(ValueType& v, long data) { return v.SetInt(data); } + static ValueType& Set(ValueType& v, long data, typename ValueType::AllocatorType&) { return v.SetInt(data); } +}; + +RAPIDJSON_STATIC_ASSERT(sizeof(unsigned long) == sizeof(unsigned)); +template<typename ValueType> +struct TypeHelper<ValueType, unsigned long> { + static bool Is(const ValueType& v) { return v.IsUint(); } + static unsigned long Get(const ValueType& v) { return v.GetUint(); } + static ValueType& Set(ValueType& v, unsigned long data) { return v.SetUint(data); } + static ValueType& Set(ValueType& v, unsigned long data, typename ValueType::AllocatorType&) { return v.SetUint(data); } +}; +#endif + +template<typename ValueType> +struct TypeHelper<ValueType, int64_t> { + static bool Is(const ValueType& v) { return v.IsInt64(); } + static int64_t Get(const ValueType& v) { return v.GetInt64(); } + static ValueType& Set(ValueType& v, int64_t data) { return v.SetInt64(data); } + static ValueType& Set(ValueType& v, int64_t data, typename ValueType::AllocatorType&) { return v.SetInt64(data); } +}; + +template<typename ValueType> +struct TypeHelper<ValueType, uint64_t> { + static bool Is(const ValueType& v) { return v.IsUint64(); } + static uint64_t Get(const ValueType& v) { return v.GetUint64(); } + static ValueType& Set(ValueType& v, uint64_t data) { return v.SetUint64(data); } + static ValueType& Set(ValueType& v, uint64_t data, typename ValueType::AllocatorType&) { return v.SetUint64(data); } +}; + +template<typename ValueType> +struct TypeHelper<ValueType, double> { + static bool Is(const ValueType& v) { return v.IsDouble(); } + static double Get(const ValueType& v) { return v.GetDouble(); } + static ValueType& Set(ValueType& v, double data) { return v.SetDouble(data); } + static ValueType& Set(ValueType& v, double data, typename ValueType::AllocatorType&) { return v.SetDouble(data); } +}; + +template<typename ValueType> +struct TypeHelper<ValueType, float> { + static bool Is(const ValueType& v) { return v.IsFloat(); } + static float Get(const ValueType& v) { return v.GetFloat(); } + static ValueType& Set(ValueType& v, float data) { return v.SetFloat(data); } + static ValueType& Set(ValueType& v, float data, typename ValueType::AllocatorType&) { return v.SetFloat(data); } +}; + +template<typename ValueType> +struct TypeHelper<ValueType, const typename ValueType::Ch*> { + typedef const typename ValueType::Ch* StringType; + static bool Is(const ValueType& v) { return v.IsString(); } + static StringType Get(const ValueType& v) { return v.GetString(); } + static ValueType& Set(ValueType& v, const StringType data) { return v.SetString(typename ValueType::StringRefType(data)); } + static ValueType& Set(ValueType& v, const StringType data, typename ValueType::AllocatorType& a) { return v.SetString(data, a); } +}; + +#if RAPIDJSON_HAS_STDSTRING +template<typename ValueType> +struct TypeHelper<ValueType, std::basic_string<typename ValueType::Ch> > { + typedef std::basic_string<typename ValueType::Ch> StringType; + static bool Is(const ValueType& v) { return v.IsString(); } + static StringType Get(const ValueType& v) { return StringType(v.GetString(), v.GetStringLength()); } + static ValueType& Set(ValueType& v, const StringType& data, typename ValueType::AllocatorType& a) { return v.SetString(data, a); } +}; +#endif + +template<typename ValueType> +struct TypeHelper<ValueType, typename ValueType::Array> { + typedef typename ValueType::Array ArrayType; + static bool Is(const ValueType& v) { return v.IsArray(); } + static ArrayType Get(ValueType& v) { return v.GetArray(); } + static ValueType& Set(ValueType& v, ArrayType data) { return v = data; } + static ValueType& Set(ValueType& v, ArrayType data, typename ValueType::AllocatorType&) { return v = data; } +}; + +template<typename ValueType> +struct TypeHelper<ValueType, typename ValueType::ConstArray> { + typedef typename ValueType::ConstArray ArrayType; + static bool Is(const ValueType& v) { return v.IsArray(); } + static ArrayType Get(const ValueType& v) { return v.GetArray(); } +}; + +template<typename ValueType> +struct TypeHelper<ValueType, typename ValueType::Object> { + typedef typename ValueType::Object ObjectType; + static bool Is(const ValueType& v) { return v.IsObject(); } + static ObjectType Get(ValueType& v) { return v.GetObject(); } + static ValueType& Set(ValueType& v, ObjectType data) { return v = data; } + static ValueType& Set(ValueType& v, ObjectType data, typename ValueType::AllocatorType&) { return v = data; } +}; + +template<typename ValueType> +struct TypeHelper<ValueType, typename ValueType::ConstObject> { + typedef typename ValueType::ConstObject ObjectType; + static bool Is(const ValueType& v) { return v.IsObject(); } + static ObjectType Get(const ValueType& v) { return v.GetObject(); } +}; + +} // namespace internal + +// Forward declarations +template <bool, typename> class GenericArray; +template <bool, typename> class GenericObject; + +/////////////////////////////////////////////////////////////////////////////// +// GenericValue + +//! Represents a JSON value. Use Value for UTF8 encoding and default allocator. +/*! + A JSON value can be one of 7 types. This class is a variant type supporting + these types. + + Use the Value if UTF8 and default allocator + + \tparam Encoding Encoding of the value. (Even non-string values need to have the same encoding in a document) + \tparam Allocator Allocator type for allocating memory of object, array and string. +*/ +template <typename Encoding, typename Allocator = RAPIDJSON_DEFAULT_ALLOCATOR > +class GenericValue { +public: + //! Name-value pair in an object. + typedef GenericMember<Encoding, Allocator> Member; + typedef Encoding EncodingType; //!< Encoding type from template parameter. + typedef Allocator AllocatorType; //!< Allocator type from template parameter. + typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. + typedef GenericStringRef<Ch> StringRefType; //!< Reference to a constant string + typedef typename GenericMemberIterator<false,Encoding,Allocator>::Iterator MemberIterator; //!< Member iterator for iterating in object. + typedef typename GenericMemberIterator<true,Encoding,Allocator>::Iterator ConstMemberIterator; //!< Constant member iterator for iterating in object. + typedef GenericValue* ValueIterator; //!< Value iterator for iterating in array. + typedef const GenericValue* ConstValueIterator; //!< Constant value iterator for iterating in array. + typedef GenericValue<Encoding, Allocator> ValueType; //!< Value type of itself. + typedef GenericArray<false, ValueType> Array; + typedef GenericArray<true, ValueType> ConstArray; + typedef GenericObject<false, ValueType> Object; + typedef GenericObject<true, ValueType> ConstObject; + + //!@name Constructors and destructor. + //@{ + + //! Default constructor creates a null value. + GenericValue() RAPIDJSON_NOEXCEPT : data_() { data_.f.flags = kNullFlag; } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericValue(GenericValue&& rhs) RAPIDJSON_NOEXCEPT : data_(rhs.data_) { + rhs.data_.f.flags = kNullFlag; // give up contents + } +#endif + +private: + //! Copy constructor is not permitted. + GenericValue(const GenericValue& rhs); + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Moving from a GenericDocument is not permitted. + template <typename StackAllocator> + GenericValue(GenericDocument<Encoding,Allocator,StackAllocator>&& rhs); + + //! Move assignment from a GenericDocument is not permitted. + template <typename StackAllocator> + GenericValue& operator=(GenericDocument<Encoding,Allocator,StackAllocator>&& rhs); +#endif + +public: + + //! Constructor with JSON value type. + /*! This creates a Value of specified type with default content. + \param type Type of the value. + \note Default content for number is zero. + */ + explicit GenericValue(Type type) RAPIDJSON_NOEXCEPT : data_() { + static const uint16_t defaultFlags[] = { + kNullFlag, kFalseFlag, kTrueFlag, kObjectFlag, kArrayFlag, kShortStringFlag, + kNumberAnyFlag + }; + RAPIDJSON_NOEXCEPT_ASSERT(type >= kNullType && type <= kNumberType); + data_.f.flags = defaultFlags[type]; + + // Use ShortString to store empty string. + if (type == kStringType) + data_.ss.SetLength(0); + } + + //! Explicit copy constructor (with allocator) + /*! Creates a copy of a Value by using the given Allocator + \tparam SourceAllocator allocator of \c rhs + \param rhs Value to copy from (read-only) + \param allocator Allocator for allocating copied elements and buffers. Commonly use GenericDocument::GetAllocator(). + \param copyConstStrings Force copying of constant strings (e.g. referencing an in-situ buffer) + \see CopyFrom() + */ + template <typename SourceAllocator> + GenericValue(const GenericValue<Encoding,SourceAllocator>& rhs, Allocator& allocator, bool copyConstStrings = false) { + switch (rhs.GetType()) { + case kObjectType: { + SizeType count = rhs.data_.o.size; + Member* lm = reinterpret_cast<Member*>(allocator.Malloc(count * sizeof(Member))); + const typename GenericValue<Encoding,SourceAllocator>::Member* rm = rhs.GetMembersPointer(); + for (SizeType i = 0; i < count; i++) { + new (&lm[i].name) GenericValue(rm[i].name, allocator, copyConstStrings); + new (&lm[i].value) GenericValue(rm[i].value, allocator, copyConstStrings); + } + data_.f.flags = kObjectFlag; + data_.o.size = data_.o.capacity = count; + SetMembersPointer(lm); + } + break; + case kArrayType: { + SizeType count = rhs.data_.a.size; + GenericValue* le = reinterpret_cast<GenericValue*>(allocator.Malloc(count * sizeof(GenericValue))); + const GenericValue<Encoding,SourceAllocator>* re = rhs.GetElementsPointer(); + for (SizeType i = 0; i < count; i++) + new (&le[i]) GenericValue(re[i], allocator, copyConstStrings); + data_.f.flags = kArrayFlag; + data_.a.size = data_.a.capacity = count; + SetElementsPointer(le); + } + break; + case kStringType: + if (rhs.data_.f.flags == kConstStringFlag && !copyConstStrings) { + data_.f.flags = rhs.data_.f.flags; + data_ = *reinterpret_cast<const Data*>(&rhs.data_); + } + else + SetStringRaw(StringRef(rhs.GetString(), rhs.GetStringLength()), allocator); + break; + default: + data_.f.flags = rhs.data_.f.flags; + data_ = *reinterpret_cast<const Data*>(&rhs.data_); + break; + } + } + + //! Constructor for boolean value. + /*! \param b Boolean value + \note This constructor is limited to \em real boolean values and rejects + implicitly converted types like arbitrary pointers. Use an explicit cast + to \c bool, if you want to construct a boolean JSON value in such cases. + */ +#ifndef RAPIDJSON_DOXYGEN_RUNNING // hide SFINAE from Doxygen + template <typename T> + explicit GenericValue(T b, RAPIDJSON_ENABLEIF((internal::IsSame<bool, T>))) RAPIDJSON_NOEXCEPT // See #472 +#else + explicit GenericValue(bool b) RAPIDJSON_NOEXCEPT +#endif + : data_() { + // safe-guard against failing SFINAE + RAPIDJSON_STATIC_ASSERT((internal::IsSame<bool,T>::Value)); + data_.f.flags = b ? kTrueFlag : kFalseFlag; + } + + //! Constructor for int value. + explicit GenericValue(int i) RAPIDJSON_NOEXCEPT : data_() { + data_.n.i64 = i; + data_.f.flags = (i >= 0) ? (kNumberIntFlag | kUintFlag | kUint64Flag) : kNumberIntFlag; + } + + //! Constructor for unsigned value. + explicit GenericValue(unsigned u) RAPIDJSON_NOEXCEPT : data_() { + data_.n.u64 = u; + data_.f.flags = (u & 0x80000000) ? kNumberUintFlag : (kNumberUintFlag | kIntFlag | kInt64Flag); + } + + //! Constructor for int64_t value. + explicit GenericValue(int64_t i64) RAPIDJSON_NOEXCEPT : data_() { + data_.n.i64 = i64; + data_.f.flags = kNumberInt64Flag; + if (i64 >= 0) { + data_.f.flags |= kNumberUint64Flag; + if (!(static_cast<uint64_t>(i64) & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x00000000))) + data_.f.flags |= kUintFlag; + if (!(static_cast<uint64_t>(i64) & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + data_.f.flags |= kIntFlag; + } + else if (i64 >= static_cast<int64_t>(RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + data_.f.flags |= kIntFlag; + } + + //! Constructor for uint64_t value. + explicit GenericValue(uint64_t u64) RAPIDJSON_NOEXCEPT : data_() { + data_.n.u64 = u64; + data_.f.flags = kNumberUint64Flag; + if (!(u64 & RAPIDJSON_UINT64_C2(0x80000000, 0x00000000))) + data_.f.flags |= kInt64Flag; + if (!(u64 & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x00000000))) + data_.f.flags |= kUintFlag; + if (!(u64 & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + data_.f.flags |= kIntFlag; + } + + //! Constructor for double value. + explicit GenericValue(double d) RAPIDJSON_NOEXCEPT : data_() { data_.n.d = d; data_.f.flags = kNumberDoubleFlag; } + + //! Constructor for float value. + explicit GenericValue(float f) RAPIDJSON_NOEXCEPT : data_() { data_.n.d = static_cast<double>(f); data_.f.flags = kNumberDoubleFlag; } + + //! Constructor for constant string (i.e. do not make a copy of string) + GenericValue(const Ch* s, SizeType length) RAPIDJSON_NOEXCEPT : data_() { SetStringRaw(StringRef(s, length)); } + + //! Constructor for constant string (i.e. do not make a copy of string) + explicit GenericValue(StringRefType s) RAPIDJSON_NOEXCEPT : data_() { SetStringRaw(s); } + + //! Constructor for copy-string (i.e. do make a copy of string) + GenericValue(const Ch* s, SizeType length, Allocator& allocator) : data_() { SetStringRaw(StringRef(s, length), allocator); } + + //! Constructor for copy-string (i.e. do make a copy of string) + GenericValue(const Ch*s, Allocator& allocator) : data_() { SetStringRaw(StringRef(s), allocator); } + +#if RAPIDJSON_HAS_STDSTRING + //! Constructor for copy-string from a string object (i.e. do make a copy of string) + /*! \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + GenericValue(const std::basic_string<Ch>& s, Allocator& allocator) : data_() { SetStringRaw(StringRef(s), allocator); } +#endif + + //! Constructor for Array. + /*! + \param a An array obtained by \c GetArray(). + \note \c Array is always pass-by-value. + \note the source array is moved into this value and the sourec array becomes empty. + */ + GenericValue(Array a) RAPIDJSON_NOEXCEPT : data_(a.value_.data_) { + a.value_.data_ = Data(); + a.value_.data_.f.flags = kArrayFlag; + } + + //! Constructor for Object. + /*! + \param o An object obtained by \c GetObject(). + \note \c Object is always pass-by-value. + \note the source object is moved into this value and the sourec object becomes empty. + */ + GenericValue(Object o) RAPIDJSON_NOEXCEPT : data_(o.value_.data_) { + o.value_.data_ = Data(); + o.value_.data_.f.flags = kObjectFlag; + } + + //! Destructor. + /*! Need to destruct elements of array, members of object, or copy-string. + */ + ~GenericValue() { + if (Allocator::kNeedFree) { // Shortcut by Allocator's trait + switch(data_.f.flags) { + case kArrayFlag: + { + GenericValue* e = GetElementsPointer(); + for (GenericValue* v = e; v != e + data_.a.size; ++v) + v->~GenericValue(); + Allocator::Free(e); + } + break; + + case kObjectFlag: + for (MemberIterator m = MemberBegin(); m != MemberEnd(); ++m) + m->~Member(); + Allocator::Free(GetMembersPointer()); + break; + + case kCopyStringFlag: + Allocator::Free(const_cast<Ch*>(GetStringPointer())); + break; + + default: + break; // Do nothing for other types. + } + } + } + + //@} + + //!@name Assignment operators + //@{ + + //! Assignment with move semantics. + /*! \param rhs Source of the assignment. It will become a null value after assignment. + */ + GenericValue& operator=(GenericValue& rhs) RAPIDJSON_NOEXCEPT { + if (RAPIDJSON_LIKELY(this != &rhs)) { + this->~GenericValue(); + RawAssign(rhs); + } + return *this; + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move assignment in C++11 + GenericValue& operator=(GenericValue&& rhs) RAPIDJSON_NOEXCEPT { + return *this = rhs.Move(); + } +#endif + + //! Assignment of constant string reference (no copy) + /*! \param str Constant string reference to be assigned + \note This overload is needed to avoid clashes with the generic primitive type assignment overload below. + \see GenericStringRef, operator=(T) + */ + GenericValue& operator=(StringRefType str) RAPIDJSON_NOEXCEPT { + GenericValue s(str); + return *this = s; + } + + //! Assignment with primitive types. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param value The value to be assigned. + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref SetString(const Ch*, Allocator&) (for copying) or + \ref StringRef() (to explicitly mark the pointer as constant) instead. + All other pointer types would implicitly convert to \c bool, + use \ref SetBool() instead. + */ + template <typename T> + RAPIDJSON_DISABLEIF_RETURN((internal::IsPointer<T>), (GenericValue&)) + operator=(T value) { + GenericValue v(value); + return *this = v; + } + + //! Deep-copy assignment from Value + /*! Assigns a \b copy of the Value to the current Value object + \tparam SourceAllocator Allocator type of \c rhs + \param rhs Value to copy from (read-only) + \param allocator Allocator to use for copying + \param copyConstStrings Force copying of constant strings (e.g. referencing an in-situ buffer) + */ + template <typename SourceAllocator> + GenericValue& CopyFrom(const GenericValue<Encoding, SourceAllocator>& rhs, Allocator& allocator, bool copyConstStrings = false) { + RAPIDJSON_ASSERT(static_cast<void*>(this) != static_cast<void const*>(&rhs)); + this->~GenericValue(); + new (this) GenericValue(rhs, allocator, copyConstStrings); + return *this; + } + + //! Exchange the contents of this value with those of other. + /*! + \param other Another value. + \note Constant complexity. + */ + GenericValue& Swap(GenericValue& other) RAPIDJSON_NOEXCEPT { + GenericValue temp; + temp.RawAssign(*this); + RawAssign(other); + other.RawAssign(temp); + return *this; + } + + //! free-standing swap function helper + /*! + Helper function to enable support for common swap implementation pattern based on \c std::swap: + \code + void swap(MyClass& a, MyClass& b) { + using std::swap; + swap(a.value, b.value); + // ... + } + \endcode + \see Swap() + */ + friend inline void swap(GenericValue& a, GenericValue& b) RAPIDJSON_NOEXCEPT { a.Swap(b); } + + //! Prepare Value for move semantics + /*! \return *this */ + GenericValue& Move() RAPIDJSON_NOEXCEPT { return *this; } + //@} + + //!@name Equal-to and not-equal-to operators + //@{ + //! Equal-to operator + /*! + \note If an object contains duplicated named member, comparing equality with any object is always \c false. + \note Complexity is quadratic in Object's member number and linear for the rest (number of all values in the subtree and total lengths of all strings). + */ + template <typename SourceAllocator> + bool operator==(const GenericValue<Encoding, SourceAllocator>& rhs) const { + typedef GenericValue<Encoding, SourceAllocator> RhsType; + if (GetType() != rhs.GetType()) + return false; + + switch (GetType()) { + case kObjectType: // Warning: O(n^2) inner-loop + if (data_.o.size != rhs.data_.o.size) + return false; + for (ConstMemberIterator lhsMemberItr = MemberBegin(); lhsMemberItr != MemberEnd(); ++lhsMemberItr) { + typename RhsType::ConstMemberIterator rhsMemberItr = rhs.FindMember(lhsMemberItr->name); + if (rhsMemberItr == rhs.MemberEnd() || lhsMemberItr->value != rhsMemberItr->value) + return false; + } + return true; + + case kArrayType: + if (data_.a.size != rhs.data_.a.size) + return false; + for (SizeType i = 0; i < data_.a.size; i++) + if ((*this)[i] != rhs[i]) + return false; + return true; + + case kStringType: + return StringEqual(rhs); + + case kNumberType: + if (IsDouble() || rhs.IsDouble()) { + double a = GetDouble(); // May convert from integer to double. + double b = rhs.GetDouble(); // Ditto + return a >= b && a <= b; // Prevent -Wfloat-equal + } + else + return data_.n.u64 == rhs.data_.n.u64; + + default: + return true; + } + } + + //! Equal-to operator with const C-string pointer + bool operator==(const Ch* rhs) const { return *this == GenericValue(StringRef(rhs)); } + +#if RAPIDJSON_HAS_STDSTRING + //! Equal-to operator with string object + /*! \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + bool operator==(const std::basic_string<Ch>& rhs) const { return *this == GenericValue(StringRef(rhs)); } +#endif + + //! Equal-to operator with primitive types + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c double, \c true, \c false + */ + template <typename T> RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr<internal::IsPointer<T>,internal::IsGenericValue<T> >), (bool)) operator==(const T& rhs) const { return *this == GenericValue(rhs); } + + //! Not-equal-to operator + /*! \return !(*this == rhs) + */ + template <typename SourceAllocator> + bool operator!=(const GenericValue<Encoding, SourceAllocator>& rhs) const { return !(*this == rhs); } + + //! Not-equal-to operator with const C-string pointer + bool operator!=(const Ch* rhs) const { return !(*this == rhs); } + + //! Not-equal-to operator with arbitrary types + /*! \return !(*this == rhs) + */ + template <typename T> RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue<T>), (bool)) operator!=(const T& rhs) const { return !(*this == rhs); } + + //! Equal-to operator with arbitrary types (symmetric version) + /*! \return (rhs == lhs) + */ + template <typename T> friend RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue<T>), (bool)) operator==(const T& lhs, const GenericValue& rhs) { return rhs == lhs; } + + //! Not-Equal-to operator with arbitrary types (symmetric version) + /*! \return !(rhs == lhs) + */ + template <typename T> friend RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue<T>), (bool)) operator!=(const T& lhs, const GenericValue& rhs) { return !(rhs == lhs); } + //@} + + //!@name Type + //@{ + + Type GetType() const { return static_cast<Type>(data_.f.flags & kTypeMask); } + bool IsNull() const { return data_.f.flags == kNullFlag; } + bool IsFalse() const { return data_.f.flags == kFalseFlag; } + bool IsTrue() const { return data_.f.flags == kTrueFlag; } + bool IsBool() const { return (data_.f.flags & kBoolFlag) != 0; } + bool IsObject() const { return data_.f.flags == kObjectFlag; } + bool IsArray() const { return data_.f.flags == kArrayFlag; } + bool IsNumber() const { return (data_.f.flags & kNumberFlag) != 0; } + bool IsInt() const { return (data_.f.flags & kIntFlag) != 0; } + bool IsUint() const { return (data_.f.flags & kUintFlag) != 0; } + bool IsInt64() const { return (data_.f.flags & kInt64Flag) != 0; } + bool IsUint64() const { return (data_.f.flags & kUint64Flag) != 0; } + bool IsDouble() const { return (data_.f.flags & kDoubleFlag) != 0; } + bool IsString() const { return (data_.f.flags & kStringFlag) != 0; } + + // Checks whether a number can be losslessly converted to a double. + bool IsLosslessDouble() const { + if (!IsNumber()) return false; + if (IsUint64()) { + uint64_t u = GetUint64(); + volatile double d = static_cast<double>(u); + return (d >= 0.0) + && (d < static_cast<double>((std::numeric_limits<uint64_t>::max)())) + && (u == static_cast<uint64_t>(d)); + } + if (IsInt64()) { + int64_t i = GetInt64(); + volatile double d = static_cast<double>(i); + return (d >= static_cast<double>((std::numeric_limits<int64_t>::min)())) + && (d < static_cast<double>((std::numeric_limits<int64_t>::max)())) + && (i == static_cast<int64_t>(d)); + } + return true; // double, int, uint are always lossless + } + + // Checks whether a number is a float (possible lossy). + bool IsFloat() const { + if ((data_.f.flags & kDoubleFlag) == 0) + return false; + double d = GetDouble(); + return d >= -3.4028234e38 && d <= 3.4028234e38; + } + // Checks whether a number can be losslessly converted to a float. + bool IsLosslessFloat() const { + if (!IsNumber()) return false; + double a = GetDouble(); + if (a < static_cast<double>(-(std::numeric_limits<float>::max)()) + || a > static_cast<double>((std::numeric_limits<float>::max)())) + return false; + double b = static_cast<double>(static_cast<float>(a)); + return a >= b && a <= b; // Prevent -Wfloat-equal + } + + //@} + + //!@name Null + //@{ + + GenericValue& SetNull() { this->~GenericValue(); new (this) GenericValue(); return *this; } + + //@} + + //!@name Bool + //@{ + + bool GetBool() const { RAPIDJSON_ASSERT(IsBool()); return data_.f.flags == kTrueFlag; } + //!< Set boolean value + /*! \post IsBool() == true */ + GenericValue& SetBool(bool b) { this->~GenericValue(); new (this) GenericValue(b); return *this; } + + //@} + + //!@name Object + //@{ + + //! Set this value as an empty object. + /*! \post IsObject() == true */ + GenericValue& SetObject() { this->~GenericValue(); new (this) GenericValue(kObjectType); return *this; } + + //! Get the number of members in the object. + SizeType MemberCount() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.size; } + + //! Get the capacity of object. + SizeType MemberCapacity() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.capacity; } + + //! Check whether the object is empty. + bool ObjectEmpty() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.size == 0; } + + //! Get a value from an object associated with the name. + /*! \pre IsObject() == true + \tparam T Either \c Ch or \c const \c Ch (template used for disambiguation with \ref operator[](SizeType)) + \note In version 0.1x, if the member is not found, this function returns a null value. This makes issue 7. + Since 0.2, if the name is not correct, it will assert. + If user is unsure whether a member exists, user should use HasMember() first. + A better approach is to use FindMember(). + \note Linear time complexity. + */ + template <typename T> + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr<internal::IsSame<typename internal::RemoveConst<T>::Type, Ch> >),(GenericValue&)) operator[](T* name) { + GenericValue n(StringRef(name)); + return (*this)[n]; + } + template <typename T> + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr<internal::IsSame<typename internal::RemoveConst<T>::Type, Ch> >),(const GenericValue&)) operator[](T* name) const { return const_cast<GenericValue&>(*this)[name]; } + + //! Get a value from an object associated with the name. + /*! \pre IsObject() == true + \tparam SourceAllocator Allocator of the \c name value + + \note Compared to \ref operator[](T*), this version is faster because it does not need a StrLen(). + And it can also handle strings with embedded null characters. + + \note Linear time complexity. + */ + template <typename SourceAllocator> + GenericValue& operator[](const GenericValue<Encoding, SourceAllocator>& name) { + MemberIterator member = FindMember(name); + if (member != MemberEnd()) + return member->value; + else { + RAPIDJSON_ASSERT(false); // see above note + + // This will generate -Wexit-time-destructors in clang + // static GenericValue NullValue; + // return NullValue; + + // Use static buffer and placement-new to prevent destruction + static char buffer[sizeof(GenericValue)]; + return *new (buffer) GenericValue(); + } + } + template <typename SourceAllocator> + const GenericValue& operator[](const GenericValue<Encoding, SourceAllocator>& name) const { return const_cast<GenericValue&>(*this)[name]; } + +#if RAPIDJSON_HAS_STDSTRING + //! Get a value from an object associated with name (string object). + GenericValue& operator[](const std::basic_string<Ch>& name) { return (*this)[GenericValue(StringRef(name))]; } + const GenericValue& operator[](const std::basic_string<Ch>& name) const { return (*this)[GenericValue(StringRef(name))]; } +#endif + + //! Const member iterator + /*! \pre IsObject() == true */ + ConstMemberIterator MemberBegin() const { RAPIDJSON_ASSERT(IsObject()); return ConstMemberIterator(GetMembersPointer()); } + //! Const \em past-the-end member iterator + /*! \pre IsObject() == true */ + ConstMemberIterator MemberEnd() const { RAPIDJSON_ASSERT(IsObject()); return ConstMemberIterator(GetMembersPointer() + data_.o.size); } + //! Member iterator + /*! \pre IsObject() == true */ + MemberIterator MemberBegin() { RAPIDJSON_ASSERT(IsObject()); return MemberIterator(GetMembersPointer()); } + //! \em Past-the-end member iterator + /*! \pre IsObject() == true */ + MemberIterator MemberEnd() { RAPIDJSON_ASSERT(IsObject()); return MemberIterator(GetMembersPointer() + data_.o.size); } + + //! Request the object to have enough capacity to store members. + /*! \param newCapacity The capacity that the object at least need to have. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note Linear time complexity. + */ + GenericValue& MemberReserve(SizeType newCapacity, Allocator &allocator) { + RAPIDJSON_ASSERT(IsObject()); + if (newCapacity > data_.o.capacity) { + SetMembersPointer(reinterpret_cast<Member*>(allocator.Realloc(GetMembersPointer(), data_.o.capacity * sizeof(Member), newCapacity * sizeof(Member)))); + data_.o.capacity = newCapacity; + } + return *this; + } + + //! Check whether a member exists in the object. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + bool HasMember(const Ch* name) const { return FindMember(name) != MemberEnd(); } + +#if RAPIDJSON_HAS_STDSTRING + //! Check whether a member exists in the object with string object. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + bool HasMember(const std::basic_string<Ch>& name) const { return FindMember(name) != MemberEnd(); } +#endif + + //! Check whether a member exists in the object with GenericValue name. + /*! + This version is faster because it does not need a StrLen(). It can also handle string with null character. + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + template <typename SourceAllocator> + bool HasMember(const GenericValue<Encoding, SourceAllocator>& name) const { return FindMember(name) != MemberEnd(); } + + //! Find member by name. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + + \note Earlier versions of Rapidjson returned a \c NULL pointer, in case + the requested member doesn't exist. For consistency with e.g. + \c std::map, this has been changed to MemberEnd() now. + \note Linear time complexity. + */ + MemberIterator FindMember(const Ch* name) { + GenericValue n(StringRef(name)); + return FindMember(n); + } + + ConstMemberIterator FindMember(const Ch* name) const { return const_cast<GenericValue&>(*this).FindMember(name); } + + //! Find member by name. + /*! + This version is faster because it does not need a StrLen(). It can also handle string with null character. + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + + \note Earlier versions of Rapidjson returned a \c NULL pointer, in case + the requested member doesn't exist. For consistency with e.g. + \c std::map, this has been changed to MemberEnd() now. + \note Linear time complexity. + */ + template <typename SourceAllocator> + MemberIterator FindMember(const GenericValue<Encoding, SourceAllocator>& name) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(name.IsString()); + MemberIterator member = MemberBegin(); + for ( ; member != MemberEnd(); ++member) + if (name.StringEqual(member->name)) + break; + return member; + } + template <typename SourceAllocator> ConstMemberIterator FindMember(const GenericValue<Encoding, SourceAllocator>& name) const { return const_cast<GenericValue&>(*this).FindMember(name); } + +#if RAPIDJSON_HAS_STDSTRING + //! Find member by string object name. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + */ + MemberIterator FindMember(const std::basic_string<Ch>& name) { return FindMember(GenericValue(StringRef(name))); } + ConstMemberIterator FindMember(const std::basic_string<Ch>& name) const { return FindMember(GenericValue(StringRef(name))); } +#endif + + //! Add a member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value Value of any type. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note The ownership of \c name and \c value will be transferred to this object on success. + \pre IsObject() && name.IsString() + \post name.IsNull() && value.IsNull() + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, GenericValue& value, Allocator& allocator) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(name.IsString()); + + ObjectData& o = data_.o; + if (o.size >= o.capacity) + MemberReserve(o.capacity == 0 ? kDefaultObjectCapacity : (o.capacity + (o.capacity + 1) / 2), allocator); + Member* members = GetMembersPointer(); + members[o.size].name.RawAssign(name); + members[o.size].value.RawAssign(value); + o.size++; + return *this; + } + + //! Add a constant string value as member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(GenericValue&,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, StringRefType value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Add a string object as member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(GenericValue&,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, std::basic_string<Ch>& value, Allocator& allocator) { + GenericValue v(value, allocator); + return AddMember(name, v, allocator); + } +#endif + + //! Add any primitive value as member (name-value pair) to the object. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param name A string value as name of member. + \param value Value of primitive type \c T as value of member + \param allocator Allocator for reallocating memory. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref AddMember(StringRefType, GenericValue&, Allocator&) or \ref + AddMember(StringRefType, StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized Constant time complexity. + */ + template <typename T> + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr<internal::IsPointer<T>, internal::IsGenericValue<T> >), (GenericValue&)) + AddMember(GenericValue& name, T value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericValue& AddMember(GenericValue&& name, GenericValue&& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(GenericValue&& name, GenericValue& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(GenericValue& name, GenericValue&& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(StringRefType name, GenericValue&& value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + + + //! Add a member (name-value pair) to the object. + /*! \param name A constant string reference as name of member. + \param value Value of any type. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note The ownership of \c value will be transferred to this object on success. + \pre IsObject() + \post value.IsNull() + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(StringRefType name, GenericValue& value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } + + //! Add a constant string value as member (name-value pair) to the object. + /*! \param name A constant string reference as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(StringRefType,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(StringRefType name, StringRefType value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + + //! Add any primitive value as member (name-value pair) to the object. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param name A constant string reference as name of member. + \param value Value of primitive type \c T as value of member + \param allocator Allocator for reallocating memory. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref AddMember(StringRefType, GenericValue&, Allocator&) or \ref + AddMember(StringRefType, StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized Constant time complexity. + */ + template <typename T> + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr<internal::IsPointer<T>, internal::IsGenericValue<T> >), (GenericValue&)) + AddMember(StringRefType name, T value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } + + //! Remove all members in the object. + /*! This function do not deallocate memory in the object, i.e. the capacity is unchanged. + \note Linear time complexity. + */ + void RemoveAllMembers() { + RAPIDJSON_ASSERT(IsObject()); + for (MemberIterator m = MemberBegin(); m != MemberEnd(); ++m) + m->~Member(); + data_.o.size = 0; + } + + //! Remove a member in object by its name. + /*! \param name Name of member to be removed. + \return Whether the member existed. + \note This function may reorder the object members. Use \ref + EraseMember(ConstMemberIterator) if you need to preserve the + relative order of the remaining members. + \note Linear time complexity. + */ + bool RemoveMember(const Ch* name) { + GenericValue n(StringRef(name)); + return RemoveMember(n); + } + +#if RAPIDJSON_HAS_STDSTRING + bool RemoveMember(const std::basic_string<Ch>& name) { return RemoveMember(GenericValue(StringRef(name))); } +#endif + + template <typename SourceAllocator> + bool RemoveMember(const GenericValue<Encoding, SourceAllocator>& name) { + MemberIterator m = FindMember(name); + if (m != MemberEnd()) { + RemoveMember(m); + return true; + } + else + return false; + } + + //! Remove a member in object by iterator. + /*! \param m member iterator (obtained by FindMember() or MemberBegin()). + \return the new iterator after removal. + \note This function may reorder the object members. Use \ref + EraseMember(ConstMemberIterator) if you need to preserve the + relative order of the remaining members. + \note Constant time complexity. + */ + MemberIterator RemoveMember(MemberIterator m) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(data_.o.size > 0); + RAPIDJSON_ASSERT(GetMembersPointer() != 0); + RAPIDJSON_ASSERT(m >= MemberBegin() && m < MemberEnd()); + + MemberIterator last(GetMembersPointer() + (data_.o.size - 1)); + if (data_.o.size > 1 && m != last) + *m = *last; // Move the last one to this place + else + m->~Member(); // Only one left, just destroy + --data_.o.size; + return m; + } + + //! Remove a member from an object by iterator. + /*! \param pos iterator to the member to remove + \pre IsObject() == true && \ref MemberBegin() <= \c pos < \ref MemberEnd() + \return Iterator following the removed element. + If the iterator \c pos refers to the last element, the \ref MemberEnd() iterator is returned. + \note This function preserves the relative order of the remaining object + members. If you do not need this, use the more efficient \ref RemoveMember(MemberIterator). + \note Linear time complexity. + */ + MemberIterator EraseMember(ConstMemberIterator pos) { + return EraseMember(pos, pos +1); + } + + //! Remove members in the range [first, last) from an object. + /*! \param first iterator to the first member to remove + \param last iterator following the last member to remove + \pre IsObject() == true && \ref MemberBegin() <= \c first <= \c last <= \ref MemberEnd() + \return Iterator following the last removed element. + \note This function preserves the relative order of the remaining object + members. + \note Linear time complexity. + */ + MemberIterator EraseMember(ConstMemberIterator first, ConstMemberIterator last) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(data_.o.size > 0); + RAPIDJSON_ASSERT(GetMembersPointer() != 0); + RAPIDJSON_ASSERT(first >= MemberBegin()); + RAPIDJSON_ASSERT(first <= last); + RAPIDJSON_ASSERT(last <= MemberEnd()); + + MemberIterator pos = MemberBegin() + (first - MemberBegin()); + for (MemberIterator itr = pos; itr != last; ++itr) + itr->~Member(); + std::memmove(static_cast<void*>(&*pos), &*last, static_cast<size_t>(MemberEnd() - last) * sizeof(Member)); + data_.o.size -= static_cast<SizeType>(last - first); + return pos; + } + + //! Erase a member in object by its name. + /*! \param name Name of member to be removed. + \return Whether the member existed. + \note Linear time complexity. + */ + bool EraseMember(const Ch* name) { + GenericValue n(StringRef(name)); + return EraseMember(n); + } + +#if RAPIDJSON_HAS_STDSTRING + bool EraseMember(const std::basic_string<Ch>& name) { return EraseMember(GenericValue(StringRef(name))); } +#endif + + template <typename SourceAllocator> + bool EraseMember(const GenericValue<Encoding, SourceAllocator>& name) { + MemberIterator m = FindMember(name); + if (m != MemberEnd()) { + EraseMember(m); + return true; + } + else + return false; + } + + Object GetObject() { RAPIDJSON_ASSERT(IsObject()); return Object(*this); } + ConstObject GetObject() const { RAPIDJSON_ASSERT(IsObject()); return ConstObject(*this); } + + //@} + + //!@name Array + //@{ + + //! Set this value as an empty array. + /*! \post IsArray == true */ + GenericValue& SetArray() { this->~GenericValue(); new (this) GenericValue(kArrayType); return *this; } + + //! Get the number of elements in array. + SizeType Size() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size; } + + //! Get the capacity of array. + SizeType Capacity() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.capacity; } + + //! Check whether the array is empty. + bool Empty() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size == 0; } + + //! Remove all elements in the array. + /*! This function do not deallocate memory in the array, i.e. the capacity is unchanged. + \note Linear time complexity. + */ + void Clear() { + RAPIDJSON_ASSERT(IsArray()); + GenericValue* e = GetElementsPointer(); + for (GenericValue* v = e; v != e + data_.a.size; ++v) + v->~GenericValue(); + data_.a.size = 0; + } + + //! Get an element from array by index. + /*! \pre IsArray() == true + \param index Zero-based index of element. + \see operator[](T*) + */ + GenericValue& operator[](SizeType index) { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(index < data_.a.size); + return GetElementsPointer()[index]; + } + const GenericValue& operator[](SizeType index) const { return const_cast<GenericValue&>(*this)[index]; } + + //! Element iterator + /*! \pre IsArray() == true */ + ValueIterator Begin() { RAPIDJSON_ASSERT(IsArray()); return GetElementsPointer(); } + //! \em Past-the-end element iterator + /*! \pre IsArray() == true */ + ValueIterator End() { RAPIDJSON_ASSERT(IsArray()); return GetElementsPointer() + data_.a.size; } + //! Constant element iterator + /*! \pre IsArray() == true */ + ConstValueIterator Begin() const { return const_cast<GenericValue&>(*this).Begin(); } + //! Constant \em past-the-end element iterator + /*! \pre IsArray() == true */ + ConstValueIterator End() const { return const_cast<GenericValue&>(*this).End(); } + + //! Request the array to have enough capacity to store elements. + /*! \param newCapacity The capacity that the array at least need to have. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note Linear time complexity. + */ + GenericValue& Reserve(SizeType newCapacity, Allocator &allocator) { + RAPIDJSON_ASSERT(IsArray()); + if (newCapacity > data_.a.capacity) { + SetElementsPointer(reinterpret_cast<GenericValue*>(allocator.Realloc(GetElementsPointer(), data_.a.capacity * sizeof(GenericValue), newCapacity * sizeof(GenericValue)))); + data_.a.capacity = newCapacity; + } + return *this; + } + + //! Append a GenericValue at the end of the array. + /*! \param value Value to be appended. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \post value.IsNull() == true + \return The value itself for fluent API. + \note The ownership of \c value will be transferred to this array on success. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + \note Amortized constant time complexity. + */ + GenericValue& PushBack(GenericValue& value, Allocator& allocator) { + RAPIDJSON_ASSERT(IsArray()); + if (data_.a.size >= data_.a.capacity) + Reserve(data_.a.capacity == 0 ? kDefaultArrayCapacity : (data_.a.capacity + (data_.a.capacity + 1) / 2), allocator); + GetElementsPointer()[data_.a.size++].RawAssign(value); + return *this; + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericValue& PushBack(GenericValue&& value, Allocator& allocator) { + return PushBack(value, allocator); + } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + + //! Append a constant string reference at the end of the array. + /*! \param value Constant string reference to be appended. + \param allocator Allocator for reallocating memory. It must be the same one used previously. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \return The value itself for fluent API. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + \note Amortized constant time complexity. + \see GenericStringRef + */ + GenericValue& PushBack(StringRefType value, Allocator& allocator) { + return (*this).template PushBack<StringRefType>(value, allocator); + } + + //! Append a primitive value at the end of the array. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param value Value of primitive type T to be appended. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \return The value itself for fluent API. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref PushBack(GenericValue&, Allocator&) or \ref + PushBack(StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized constant time complexity. + */ + template <typename T> + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr<internal::IsPointer<T>, internal::IsGenericValue<T> >), (GenericValue&)) + PushBack(T value, Allocator& allocator) { + GenericValue v(value); + return PushBack(v, allocator); + } + + //! Remove the last element in the array. + /*! + \note Constant time complexity. + */ + GenericValue& PopBack() { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(!Empty()); + GetElementsPointer()[--data_.a.size].~GenericValue(); + return *this; + } + + //! Remove an element of array by iterator. + /*! + \param pos iterator to the element to remove + \pre IsArray() == true && \ref Begin() <= \c pos < \ref End() + \return Iterator following the removed element. If the iterator pos refers to the last element, the End() iterator is returned. + \note Linear time complexity. + */ + ValueIterator Erase(ConstValueIterator pos) { + return Erase(pos, pos + 1); + } + + //! Remove elements in the range [first, last) of the array. + /*! + \param first iterator to the first element to remove + \param last iterator following the last element to remove + \pre IsArray() == true && \ref Begin() <= \c first <= \c last <= \ref End() + \return Iterator following the last removed element. + \note Linear time complexity. + */ + ValueIterator Erase(ConstValueIterator first, ConstValueIterator last) { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(data_.a.size > 0); + RAPIDJSON_ASSERT(GetElementsPointer() != 0); + RAPIDJSON_ASSERT(first >= Begin()); + RAPIDJSON_ASSERT(first <= last); + RAPIDJSON_ASSERT(last <= End()); + ValueIterator pos = Begin() + (first - Begin()); + for (ValueIterator itr = pos; itr != last; ++itr) + itr->~GenericValue(); + std::memmove(static_cast<void*>(pos), last, static_cast<size_t>(End() - last) * sizeof(GenericValue)); + data_.a.size -= static_cast<SizeType>(last - first); + return pos; + } + + Array GetArray() { RAPIDJSON_ASSERT(IsArray()); return Array(*this); } + ConstArray GetArray() const { RAPIDJSON_ASSERT(IsArray()); return ConstArray(*this); } + + //@} + + //!@name Number + //@{ + + int GetInt() const { RAPIDJSON_ASSERT(data_.f.flags & kIntFlag); return data_.n.i.i; } + unsigned GetUint() const { RAPIDJSON_ASSERT(data_.f.flags & kUintFlag); return data_.n.u.u; } + int64_t GetInt64() const { RAPIDJSON_ASSERT(data_.f.flags & kInt64Flag); return data_.n.i64; } + uint64_t GetUint64() const { RAPIDJSON_ASSERT(data_.f.flags & kUint64Flag); return data_.n.u64; } + + //! Get the value as double type. + /*! \note If the value is 64-bit integer type, it may lose precision. Use \c IsLosslessDouble() to check whether the converison is lossless. + */ + double GetDouble() const { + RAPIDJSON_ASSERT(IsNumber()); + if ((data_.f.flags & kDoubleFlag) != 0) return data_.n.d; // exact type, no conversion. + if ((data_.f.flags & kIntFlag) != 0) return data_.n.i.i; // int -> double + if ((data_.f.flags & kUintFlag) != 0) return data_.n.u.u; // unsigned -> double + if ((data_.f.flags & kInt64Flag) != 0) return static_cast<double>(data_.n.i64); // int64_t -> double (may lose precision) + RAPIDJSON_ASSERT((data_.f.flags & kUint64Flag) != 0); return static_cast<double>(data_.n.u64); // uint64_t -> double (may lose precision) + } + + //! Get the value as float type. + /*! \note If the value is 64-bit integer type, it may lose precision. Use \c IsLosslessFloat() to check whether the converison is lossless. + */ + float GetFloat() const { + return static_cast<float>(GetDouble()); + } + + GenericValue& SetInt(int i) { this->~GenericValue(); new (this) GenericValue(i); return *this; } + GenericValue& SetUint(unsigned u) { this->~GenericValue(); new (this) GenericValue(u); return *this; } + GenericValue& SetInt64(int64_t i64) { this->~GenericValue(); new (this) GenericValue(i64); return *this; } + GenericValue& SetUint64(uint64_t u64) { this->~GenericValue(); new (this) GenericValue(u64); return *this; } + GenericValue& SetDouble(double d) { this->~GenericValue(); new (this) GenericValue(d); return *this; } + GenericValue& SetFloat(float f) { this->~GenericValue(); new (this) GenericValue(static_cast<double>(f)); return *this; } + + //@} + + //!@name String + //@{ + + const Ch* GetString() const { RAPIDJSON_ASSERT(IsString()); return (data_.f.flags & kInlineStrFlag) ? data_.ss.str : GetStringPointer(); } + + //! Get the length of string. + /*! Since rapidjson permits "\\u0000" in the json string, strlen(v.GetString()) may not equal to v.GetStringLength(). + */ + SizeType GetStringLength() const { RAPIDJSON_ASSERT(IsString()); return ((data_.f.flags & kInlineStrFlag) ? (data_.ss.GetLength()) : data_.s.length); } + + //! Set this value as a string without copying source string. + /*! This version has better performance with supplied length, and also support string containing null character. + \param s source string pointer. + \param length The length of source string, excluding the trailing null terminator. + \return The value itself for fluent API. + \post IsString() == true && GetString() == s && GetStringLength() == length + \see SetString(StringRefType) + */ + GenericValue& SetString(const Ch* s, SizeType length) { return SetString(StringRef(s, length)); } + + //! Set this value as a string without copying source string. + /*! \param s source string reference + \return The value itself for fluent API. + \post IsString() == true && GetString() == s && GetStringLength() == s.length + */ + GenericValue& SetString(StringRefType s) { this->~GenericValue(); SetStringRaw(s); return *this; } + + //! Set this value as a string by copying from source string. + /*! This version has better performance with supplied length, and also support string containing null character. + \param s source string. + \param length The length of source string, excluding the trailing null terminator. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s && strcmp(GetString(),s) == 0 && GetStringLength() == length + */ + GenericValue& SetString(const Ch* s, SizeType length, Allocator& allocator) { return SetString(StringRef(s, length), allocator); } + + //! Set this value as a string by copying from source string. + /*! \param s source string. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s && strcmp(GetString(),s) == 0 && GetStringLength() == length + */ + GenericValue& SetString(const Ch* s, Allocator& allocator) { return SetString(StringRef(s), allocator); } + + //! Set this value as a string by copying from source string. + /*! \param s source string reference + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s.s && strcmp(GetString(),s) == 0 && GetStringLength() == length + */ + GenericValue& SetString(StringRefType s, Allocator& allocator) { this->~GenericValue(); SetStringRaw(s, allocator); return *this; } + +#if RAPIDJSON_HAS_STDSTRING + //! Set this value as a string by copying from source string. + /*! \param s source string. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s.data() && strcmp(GetString(),s.data() == 0 && GetStringLength() == s.size() + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + GenericValue& SetString(const std::basic_string<Ch>& s, Allocator& allocator) { return SetString(StringRef(s), allocator); } +#endif + + //@} + + //!@name Array + //@{ + + //! Templated version for checking whether this value is type T. + /*! + \tparam T Either \c bool, \c int, \c unsigned, \c int64_t, \c uint64_t, \c double, \c float, \c const \c char*, \c std::basic_string<Ch> + */ + template <typename T> + bool Is() const { return internal::TypeHelper<ValueType, T>::Is(*this); } + + template <typename T> + T Get() const { return internal::TypeHelper<ValueType, T>::Get(*this); } + + template <typename T> + T Get() { return internal::TypeHelper<ValueType, T>::Get(*this); } + + template<typename T> + ValueType& Set(const T& data) { return internal::TypeHelper<ValueType, T>::Set(*this, data); } + + template<typename T> + ValueType& Set(const T& data, AllocatorType& allocator) { return internal::TypeHelper<ValueType, T>::Set(*this, data, allocator); } + + //@} + + //! Generate events of this value to a Handler. + /*! This function adopts the GoF visitor pattern. + Typical usage is to output this JSON value as JSON text via Writer, which is a Handler. + It can also be used to deep clone this value via GenericDocument, which is also a Handler. + \tparam Handler type of handler. + \param handler An object implementing concept Handler. + */ + template <typename Handler> + bool Accept(Handler& handler) const { + switch(GetType()) { + case kNullType: return handler.Null(); + case kFalseType: return handler.Bool(false); + case kTrueType: return handler.Bool(true); + + case kObjectType: + if (RAPIDJSON_UNLIKELY(!handler.StartObject())) + return false; + for (ConstMemberIterator m = MemberBegin(); m != MemberEnd(); ++m) { + RAPIDJSON_ASSERT(m->name.IsString()); // User may change the type of name by MemberIterator. + if (RAPIDJSON_UNLIKELY(!handler.Key(m->name.GetString(), m->name.GetStringLength(), (m->name.data_.f.flags & kCopyFlag) != 0))) + return false; + if (RAPIDJSON_UNLIKELY(!m->value.Accept(handler))) + return false; + } + return handler.EndObject(data_.o.size); + + case kArrayType: + if (RAPIDJSON_UNLIKELY(!handler.StartArray())) + return false; + for (const GenericValue* v = Begin(); v != End(); ++v) + if (RAPIDJSON_UNLIKELY(!v->Accept(handler))) + return false; + return handler.EndArray(data_.a.size); + + case kStringType: + return handler.String(GetString(), GetStringLength(), (data_.f.flags & kCopyFlag) != 0); + + default: + RAPIDJSON_ASSERT(GetType() == kNumberType); + if (IsDouble()) return handler.Double(data_.n.d); + else if (IsInt()) return handler.Int(data_.n.i.i); + else if (IsUint()) return handler.Uint(data_.n.u.u); + else if (IsInt64()) return handler.Int64(data_.n.i64); + else return handler.Uint64(data_.n.u64); + } + } + +private: + template <typename, typename> friend class GenericValue; + template <typename, typename, typename> friend class GenericDocument; + + enum { + kBoolFlag = 0x0008, + kNumberFlag = 0x0010, + kIntFlag = 0x0020, + kUintFlag = 0x0040, + kInt64Flag = 0x0080, + kUint64Flag = 0x0100, + kDoubleFlag = 0x0200, + kStringFlag = 0x0400, + kCopyFlag = 0x0800, + kInlineStrFlag = 0x1000, + + // Initial flags of different types. + kNullFlag = kNullType, + // These casts are added to suppress the warning on MSVC about bitwise operations between enums of different types. + kTrueFlag = static_cast<int>(kTrueType) | static_cast<int>(kBoolFlag), + kFalseFlag = static_cast<int>(kFalseType) | static_cast<int>(kBoolFlag), + kNumberIntFlag = static_cast<int>(kNumberType) | static_cast<int>(kNumberFlag | kIntFlag | kInt64Flag), + kNumberUintFlag = static_cast<int>(kNumberType) | static_cast<int>(kNumberFlag | kUintFlag | kUint64Flag | kInt64Flag), + kNumberInt64Flag = static_cast<int>(kNumberType) | static_cast<int>(kNumberFlag | kInt64Flag), + kNumberUint64Flag = static_cast<int>(kNumberType) | static_cast<int>(kNumberFlag | kUint64Flag), + kNumberDoubleFlag = static_cast<int>(kNumberType) | static_cast<int>(kNumberFlag | kDoubleFlag), + kNumberAnyFlag = static_cast<int>(kNumberType) | static_cast<int>(kNumberFlag | kIntFlag | kInt64Flag | kUintFlag | kUint64Flag | kDoubleFlag), + kConstStringFlag = static_cast<int>(kStringType) | static_cast<int>(kStringFlag), + kCopyStringFlag = static_cast<int>(kStringType) | static_cast<int>(kStringFlag | kCopyFlag), + kShortStringFlag = static_cast<int>(kStringType) | static_cast<int>(kStringFlag | kCopyFlag | kInlineStrFlag), + kObjectFlag = kObjectType, + kArrayFlag = kArrayType, + + kTypeMask = 0x07 + }; + + static const SizeType kDefaultArrayCapacity = RAPIDJSON_VALUE_DEFAULT_ARRAY_CAPACITY; + static const SizeType kDefaultObjectCapacity = RAPIDJSON_VALUE_DEFAULT_OBJECT_CAPACITY; + + struct Flag { +#if RAPIDJSON_48BITPOINTER_OPTIMIZATION + char payload[sizeof(SizeType) * 2 + 6]; // 2 x SizeType + lower 48-bit pointer +#elif RAPIDJSON_64BIT + char payload[sizeof(SizeType) * 2 + sizeof(void*) + 6]; // 6 padding bytes +#else + char payload[sizeof(SizeType) * 2 + sizeof(void*) + 2]; // 2 padding bytes +#endif + uint16_t flags; + }; + + struct String { + SizeType length; + SizeType hashcode; //!< reserved + const Ch* str; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + // implementation detail: ShortString can represent zero-terminated strings up to MaxSize chars + // (excluding the terminating zero) and store a value to determine the length of the contained + // string in the last character str[LenPos] by storing "MaxSize - length" there. If the string + // to store has the maximal length of MaxSize then str[LenPos] will be 0 and therefore act as + // the string terminator as well. For getting the string length back from that value just use + // "MaxSize - str[LenPos]". + // This allows to store 13-chars strings in 32-bit mode, 21-chars strings in 64-bit mode, + // 13-chars strings for RAPIDJSON_48BITPOINTER_OPTIMIZATION=1 inline (for `UTF8`-encoded strings). + struct ShortString { + enum { MaxChars = sizeof(static_cast<Flag*>(0)->payload) / sizeof(Ch), MaxSize = MaxChars - 1, LenPos = MaxSize }; + Ch str[MaxChars]; + + inline static bool Usable(SizeType len) { return (MaxSize >= len); } + inline void SetLength(SizeType len) { str[LenPos] = static_cast<Ch>(MaxSize - len); } + inline SizeType GetLength() const { return static_cast<SizeType>(MaxSize - str[LenPos]); } + }; // at most as many bytes as "String" above => 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + // By using proper binary layout, retrieval of different integer types do not need conversions. + union Number { +#if RAPIDJSON_ENDIAN == RAPIDJSON_LITTLEENDIAN + struct I { + int i; + char padding[4]; + }i; + struct U { + unsigned u; + char padding2[4]; + }u; +#else + struct I { + char padding[4]; + int i; + }i; + struct U { + char padding2[4]; + unsigned u; + }u; +#endif + int64_t i64; + uint64_t u64; + double d; + }; // 8 bytes + + struct ObjectData { + SizeType size; + SizeType capacity; + Member* members; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + struct ArrayData { + SizeType size; + SizeType capacity; + GenericValue* elements; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + union Data { + String s; + ShortString ss; + Number n; + ObjectData o; + ArrayData a; + Flag f; + }; // 16 bytes in 32-bit mode, 24 bytes in 64-bit mode, 16 bytes in 64-bit with RAPIDJSON_48BITPOINTER_OPTIMIZATION + + RAPIDJSON_FORCEINLINE const Ch* GetStringPointer() const { return RAPIDJSON_GETPOINTER(Ch, data_.s.str); } + RAPIDJSON_FORCEINLINE const Ch* SetStringPointer(const Ch* str) { return RAPIDJSON_SETPOINTER(Ch, data_.s.str, str); } + RAPIDJSON_FORCEINLINE GenericValue* GetElementsPointer() const { return RAPIDJSON_GETPOINTER(GenericValue, data_.a.elements); } + RAPIDJSON_FORCEINLINE GenericValue* SetElementsPointer(GenericValue* elements) { return RAPIDJSON_SETPOINTER(GenericValue, data_.a.elements, elements); } + RAPIDJSON_FORCEINLINE Member* GetMembersPointer() const { return RAPIDJSON_GETPOINTER(Member, data_.o.members); } + RAPIDJSON_FORCEINLINE Member* SetMembersPointer(Member* members) { return RAPIDJSON_SETPOINTER(Member, data_.o.members, members); } + + // Initialize this value as array with initial data, without calling destructor. + void SetArrayRaw(GenericValue* values, SizeType count, Allocator& allocator) { + data_.f.flags = kArrayFlag; + if (count) { + GenericValue* e = static_cast<GenericValue*>(allocator.Malloc(count * sizeof(GenericValue))); + SetElementsPointer(e); + std::memcpy(static_cast<void*>(e), values, count * sizeof(GenericValue)); + } + else + SetElementsPointer(0); + data_.a.size = data_.a.capacity = count; + } + + //! Initialize this value as object with initial data, without calling destructor. + void SetObjectRaw(Member* members, SizeType count, Allocator& allocator) { + data_.f.flags = kObjectFlag; + if (count) { + Member* m = static_cast<Member*>(allocator.Malloc(count * sizeof(Member))); + SetMembersPointer(m); + std::memcpy(static_cast<void*>(m), members, count * sizeof(Member)); + } + else + SetMembersPointer(0); + data_.o.size = data_.o.capacity = count; + } + + //! Initialize this value as constant string, without calling destructor. + void SetStringRaw(StringRefType s) RAPIDJSON_NOEXCEPT { + data_.f.flags = kConstStringFlag; + SetStringPointer(s); + data_.s.length = s.length; + } + + //! Initialize this value as copy string with initial data, without calling destructor. + void SetStringRaw(StringRefType s, Allocator& allocator) { + Ch* str = 0; + if (ShortString::Usable(s.length)) { + data_.f.flags = kShortStringFlag; + data_.ss.SetLength(s.length); + str = data_.ss.str; + } else { + data_.f.flags = kCopyStringFlag; + data_.s.length = s.length; + str = static_cast<Ch *>(allocator.Malloc((s.length + 1) * sizeof(Ch))); + SetStringPointer(str); + } + std::memcpy(str, s, s.length * sizeof(Ch)); + str[s.length] = '\0'; + } + + //! Assignment without calling destructor + void RawAssign(GenericValue& rhs) RAPIDJSON_NOEXCEPT { + data_ = rhs.data_; + // data_.f.flags = rhs.data_.f.flags; + rhs.data_.f.flags = kNullFlag; + } + + template <typename SourceAllocator> + bool StringEqual(const GenericValue<Encoding, SourceAllocator>& rhs) const { + RAPIDJSON_ASSERT(IsString()); + RAPIDJSON_ASSERT(rhs.IsString()); + + const SizeType len1 = GetStringLength(); + const SizeType len2 = rhs.GetStringLength(); + if(len1 != len2) { return false; } + + const Ch* const str1 = GetString(); + const Ch* const str2 = rhs.GetString(); + if(str1 == str2) { return true; } // fast path for constant string + + return (std::memcmp(str1, str2, sizeof(Ch) * len1) == 0); + } + + Data data_; +}; + +//! GenericValue with UTF8 encoding +typedef GenericValue<UTF8<> > Value; + +/////////////////////////////////////////////////////////////////////////////// +// GenericDocument + +//! A document for parsing JSON text as DOM. +/*! + \note implements Handler concept + \tparam Encoding Encoding for both parsing and string storage. + \tparam Allocator Allocator for allocating memory for the DOM + \tparam StackAllocator Allocator for allocating memory for stack during parsing. + \warning Although GenericDocument inherits from GenericValue, the API does \b not provide any virtual functions, especially no virtual destructor. To avoid memory leaks, do not \c delete a GenericDocument object via a pointer to a GenericValue. +*/ +template <typename Encoding, typename Allocator = RAPIDJSON_DEFAULT_ALLOCATOR, typename StackAllocator = RAPIDJSON_DEFAULT_STACK_ALLOCATOR > +class GenericDocument : public GenericValue<Encoding, Allocator> { +public: + typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. + typedef GenericValue<Encoding, Allocator> ValueType; //!< Value type of the document. + typedef Allocator AllocatorType; //!< Allocator type from template parameter. + + //! Constructor + /*! Creates an empty document of specified type. + \param type Mandatory type of object to create. + \param allocator Optional allocator for allocating memory. + \param stackCapacity Optional initial capacity of stack in bytes. + \param stackAllocator Optional allocator for allocating memory for stack. + */ + explicit GenericDocument(Type type, Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity, StackAllocator* stackAllocator = 0) : + GenericValue<Encoding, Allocator>(type), allocator_(allocator), ownAllocator_(0), stack_(stackAllocator, stackCapacity), parseResult_() + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + } + + //! Constructor + /*! Creates an empty document which type is Null. + \param allocator Optional allocator for allocating memory. + \param stackCapacity Optional initial capacity of stack in bytes. + \param stackAllocator Optional allocator for allocating memory for stack. + */ + GenericDocument(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity, StackAllocator* stackAllocator = 0) : + allocator_(allocator), ownAllocator_(0), stack_(stackAllocator, stackCapacity), parseResult_() + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericDocument(GenericDocument&& rhs) RAPIDJSON_NOEXCEPT + : ValueType(std::forward<ValueType>(rhs)), // explicit cast to avoid prohibited move from Document + allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + stack_(std::move(rhs.stack_)), + parseResult_(rhs.parseResult_) + { + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.parseResult_ = ParseResult(); + } +#endif + + ~GenericDocument() { + Destroy(); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move assignment in C++11 + GenericDocument& operator=(GenericDocument&& rhs) RAPIDJSON_NOEXCEPT + { + // The cast to ValueType is necessary here, because otherwise it would + // attempt to call GenericValue's templated assignment operator. + ValueType::operator=(std::forward<ValueType>(rhs)); + + // Calling the destructor here would prematurely call stack_'s destructor + Destroy(); + + allocator_ = rhs.allocator_; + ownAllocator_ = rhs.ownAllocator_; + stack_ = std::move(rhs.stack_); + parseResult_ = rhs.parseResult_; + + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.parseResult_ = ParseResult(); + + return *this; + } +#endif + + //! Exchange the contents of this document with those of another. + /*! + \param rhs Another document. + \note Constant complexity. + \see GenericValue::Swap + */ + GenericDocument& Swap(GenericDocument& rhs) RAPIDJSON_NOEXCEPT { + ValueType::Swap(rhs); + stack_.Swap(rhs.stack_); + internal::Swap(allocator_, rhs.allocator_); + internal::Swap(ownAllocator_, rhs.ownAllocator_); + internal::Swap(parseResult_, rhs.parseResult_); + return *this; + } + + // Allow Swap with ValueType. + // Refer to Effective C++ 3rd Edition/Item 33: Avoid hiding inherited names. + using ValueType::Swap; + + //! free-standing swap function helper + /*! + Helper function to enable support for common swap implementation pattern based on \c std::swap: + \code + void swap(MyClass& a, MyClass& b) { + using std::swap; + swap(a.doc, b.doc); + // ... + } + \endcode + \see Swap() + */ + friend inline void swap(GenericDocument& a, GenericDocument& b) RAPIDJSON_NOEXCEPT { a.Swap(b); } + + //! Populate this document by a generator which produces SAX events. + /*! \tparam Generator A functor with <tt>bool f(Handler)</tt> prototype. + \param g Generator functor which sends SAX events to the parameter. + \return The document itself for fluent API. + */ + template <typename Generator> + GenericDocument& Populate(Generator& g) { + ClearStackOnExit scope(*this); + if (g(*this)) { + RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object + ValueType::operator=(*stack_.template Pop<ValueType>(1));// Move value from stack to document + } + return *this; + } + + //!@name Parse from stream + //!@{ + + //! Parse JSON text from an input stream (with Encoding conversion) + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam SourceEncoding Encoding of input stream + \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template <unsigned parseFlags, typename SourceEncoding, typename InputStream> + GenericDocument& ParseStream(InputStream& is) { + GenericReader<SourceEncoding, Encoding, StackAllocator> reader( + stack_.HasAllocator() ? &stack_.GetAllocator() : 0); + ClearStackOnExit scope(*this); + parseResult_ = reader.template Parse<parseFlags>(is, *this); + if (parseResult_) { + RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object + ValueType::operator=(*stack_.template Pop<ValueType>(1));// Move value from stack to document + } + return *this; + } + + //! Parse JSON text from an input stream + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template <unsigned parseFlags, typename InputStream> + GenericDocument& ParseStream(InputStream& is) { + return ParseStream<parseFlags, Encoding, InputStream>(is); + } + + //! Parse JSON text from an input stream (with \ref kParseDefaultFlags) + /*! \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template <typename InputStream> + GenericDocument& ParseStream(InputStream& is) { + return ParseStream<kParseDefaultFlags, Encoding, InputStream>(is); + } + //!@} + + //!@name Parse in-place from mutable string + //!@{ + + //! Parse JSON text from a mutable string + /*! \tparam parseFlags Combination of \ref ParseFlag. + \param str Mutable zero-terminated string to be parsed. + \return The document itself for fluent API. + */ + template <unsigned parseFlags> + GenericDocument& ParseInsitu(Ch* str) { + GenericInsituStringStream<Encoding> s(str); + return ParseStream<parseFlags | kParseInsituFlag>(s); + } + + //! Parse JSON text from a mutable string (with \ref kParseDefaultFlags) + /*! \param str Mutable zero-terminated string to be parsed. + \return The document itself for fluent API. + */ + GenericDocument& ParseInsitu(Ch* str) { + return ParseInsitu<kParseDefaultFlags>(str); + } + //!@} + + //!@name Parse from read-only string + //!@{ + + //! Parse JSON text from a read-only string (with Encoding conversion) + /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). + \tparam SourceEncoding Transcoding from input Encoding + \param str Read-only zero-terminated string to be parsed. + */ + template <unsigned parseFlags, typename SourceEncoding> + GenericDocument& Parse(const typename SourceEncoding::Ch* str) { + RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag)); + GenericStringStream<SourceEncoding> s(str); + return ParseStream<parseFlags, SourceEncoding>(s); + } + + //! Parse JSON text from a read-only string + /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). + \param str Read-only zero-terminated string to be parsed. + */ + template <unsigned parseFlags> + GenericDocument& Parse(const Ch* str) { + return Parse<parseFlags, Encoding>(str); + } + + //! Parse JSON text from a read-only string (with \ref kParseDefaultFlags) + /*! \param str Read-only zero-terminated string to be parsed. + */ + GenericDocument& Parse(const Ch* str) { + return Parse<kParseDefaultFlags>(str); + } + + template <unsigned parseFlags, typename SourceEncoding> + GenericDocument& Parse(const typename SourceEncoding::Ch* str, size_t length) { + RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag)); + MemoryStream ms(reinterpret_cast<const char*>(str), length * sizeof(typename SourceEncoding::Ch)); + EncodedInputStream<SourceEncoding, MemoryStream> is(ms); + ParseStream<parseFlags, SourceEncoding>(is); + return *this; + } + + template <unsigned parseFlags> + GenericDocument& Parse(const Ch* str, size_t length) { + return Parse<parseFlags, Encoding>(str, length); + } + + GenericDocument& Parse(const Ch* str, size_t length) { + return Parse<kParseDefaultFlags>(str, length); + } + +#if RAPIDJSON_HAS_STDSTRING + template <unsigned parseFlags, typename SourceEncoding> + GenericDocument& Parse(const std::basic_string<typename SourceEncoding::Ch>& str) { + // c_str() is constant complexity according to standard. Should be faster than Parse(const char*, size_t) + return Parse<parseFlags, SourceEncoding>(str.c_str()); + } + + template <unsigned parseFlags> + GenericDocument& Parse(const std::basic_string<Ch>& str) { + return Parse<parseFlags, Encoding>(str.c_str()); + } + + GenericDocument& Parse(const std::basic_string<Ch>& str) { + return Parse<kParseDefaultFlags>(str); + } +#endif // RAPIDJSON_HAS_STDSTRING + + //!@} + + //!@name Handling parse errors + //!@{ + + //! Whether a parse error has occurred in the last parsing. + bool HasParseError() const { return parseResult_.IsError(); } + + //! Get the \ref ParseErrorCode of last parsing. + ParseErrorCode GetParseError() const { return parseResult_.Code(); } + + //! Get the position of last parsing error in input, 0 otherwise. + size_t GetErrorOffset() const { return parseResult_.Offset(); } + + //! Implicit conversion to get the last parse result +#ifndef __clang // -Wdocumentation + /*! \return \ref ParseResult of the last parse operation + + \code + Document doc; + ParseResult ok = doc.Parse(json); + if (!ok) + printf( "JSON parse error: %s (%u)\n", GetParseError_En(ok.Code()), ok.Offset()); + \endcode + */ +#endif + operator ParseResult() const { return parseResult_; } + //!@} + + //! Get the allocator of this document. + Allocator& GetAllocator() { + RAPIDJSON_ASSERT(allocator_); + return *allocator_; + } + + //! Get the capacity of stack in bytes. + size_t GetStackCapacity() const { return stack_.GetCapacity(); } + +private: + // clear stack on any exit from ParseStream, e.g. due to exception + struct ClearStackOnExit { + explicit ClearStackOnExit(GenericDocument& d) : d_(d) {} + ~ClearStackOnExit() { d_.ClearStack(); } + private: + ClearStackOnExit(const ClearStackOnExit&); + ClearStackOnExit& operator=(const ClearStackOnExit&); + GenericDocument& d_; + }; + + // callers of the following private Handler functions + // template <typename,typename,typename> friend class GenericReader; // for parsing + template <typename, typename> friend class GenericValue; // for deep copying + +public: + // Implementation of Handler + bool Null() { new (stack_.template Push<ValueType>()) ValueType(); return true; } + bool Bool(bool b) { new (stack_.template Push<ValueType>()) ValueType(b); return true; } + bool Int(int i) { new (stack_.template Push<ValueType>()) ValueType(i); return true; } + bool Uint(unsigned i) { new (stack_.template Push<ValueType>()) ValueType(i); return true; } + bool Int64(int64_t i) { new (stack_.template Push<ValueType>()) ValueType(i); return true; } + bool Uint64(uint64_t i) { new (stack_.template Push<ValueType>()) ValueType(i); return true; } + bool Double(double d) { new (stack_.template Push<ValueType>()) ValueType(d); return true; } + + bool RawNumber(const Ch* str, SizeType length, bool copy) { + if (copy) + new (stack_.template Push<ValueType>()) ValueType(str, length, GetAllocator()); + else + new (stack_.template Push<ValueType>()) ValueType(str, length); + return true; + } + + bool String(const Ch* str, SizeType length, bool copy) { + if (copy) + new (stack_.template Push<ValueType>()) ValueType(str, length, GetAllocator()); + else + new (stack_.template Push<ValueType>()) ValueType(str, length); + return true; + } + + bool StartObject() { new (stack_.template Push<ValueType>()) ValueType(kObjectType); return true; } + + bool Key(const Ch* str, SizeType length, bool copy) { return String(str, length, copy); } + + bool EndObject(SizeType memberCount) { + typename ValueType::Member* members = stack_.template Pop<typename ValueType::Member>(memberCount); + stack_.template Top<ValueType>()->SetObjectRaw(members, memberCount, GetAllocator()); + return true; + } + + bool StartArray() { new (stack_.template Push<ValueType>()) ValueType(kArrayType); return true; } + + bool EndArray(SizeType elementCount) { + ValueType* elements = stack_.template Pop<ValueType>(elementCount); + stack_.template Top<ValueType>()->SetArrayRaw(elements, elementCount, GetAllocator()); + return true; + } + +private: + //! Prohibit copying + GenericDocument(const GenericDocument&); + //! Prohibit assignment + GenericDocument& operator=(const GenericDocument&); + + void ClearStack() { + if (Allocator::kNeedFree) + while (stack_.GetSize() > 0) // Here assumes all elements in stack array are GenericValue (Member is actually 2 GenericValue objects) + (stack_.template Pop<ValueType>(1))->~ValueType(); + else + stack_.Clear(); + stack_.ShrinkToFit(); + } + + void Destroy() { + RAPIDJSON_DELETE(ownAllocator_); + } + + static const size_t kDefaultStackCapacity = 1024; + Allocator* allocator_; + Allocator* ownAllocator_; + internal::Stack<StackAllocator> stack_; + ParseResult parseResult_; +}; + +//! GenericDocument with UTF8 encoding +typedef GenericDocument<UTF8<> > Document; + + +//! Helper class for accessing Value of array type. +/*! + Instance of this helper class is obtained by \c GenericValue::GetArray(). + In addition to all APIs for array type, it provides range-based for loop if \c RAPIDJSON_HAS_CXX11_RANGE_FOR=1. +*/ +template <bool Const, typename ValueT> +class GenericArray { +public: + typedef GenericArray<true, ValueT> ConstArray; + typedef GenericArray<false, ValueT> Array; + typedef ValueT PlainType; + typedef typename internal::MaybeAddConst<Const,PlainType>::Type ValueType; + typedef ValueType* ValueIterator; // This may be const or non-const iterator + typedef const ValueT* ConstValueIterator; + typedef typename ValueType::AllocatorType AllocatorType; + typedef typename ValueType::StringRefType StringRefType; + + template <typename, typename> + friend class GenericValue; + + GenericArray(const GenericArray& rhs) : value_(rhs.value_) {} + GenericArray& operator=(const GenericArray& rhs) { value_ = rhs.value_; return *this; } + ~GenericArray() {} + + operator ValueType&() const { return value_; } + SizeType Size() const { return value_.Size(); } + SizeType Capacity() const { return value_.Capacity(); } + bool Empty() const { return value_.Empty(); } + void Clear() const { value_.Clear(); } + ValueType& operator[](SizeType index) const { return value_[index]; } + ValueIterator Begin() const { return value_.Begin(); } + ValueIterator End() const { return value_.End(); } + GenericArray Reserve(SizeType newCapacity, AllocatorType &allocator) const { value_.Reserve(newCapacity, allocator); return *this; } + GenericArray PushBack(ValueType& value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericArray PushBack(ValueType&& value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericArray PushBack(StringRefType value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } + template <typename T> RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr<internal::IsPointer<T>, internal::IsGenericValue<T> >), (const GenericArray&)) PushBack(T value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } + GenericArray PopBack() const { value_.PopBack(); return *this; } + ValueIterator Erase(ConstValueIterator pos) const { return value_.Erase(pos); } + ValueIterator Erase(ConstValueIterator first, ConstValueIterator last) const { return value_.Erase(first, last); } + +#if RAPIDJSON_HAS_CXX11_RANGE_FOR + ValueIterator begin() const { return value_.Begin(); } + ValueIterator end() const { return value_.End(); } +#endif + +private: + GenericArray(); + GenericArray(ValueType& value) : value_(value) {} + ValueType& value_; +}; + +//! Helper class for accessing Value of object type. +/*! + Instance of this helper class is obtained by \c GenericValue::GetObject(). + In addition to all APIs for array type, it provides range-based for loop if \c RAPIDJSON_HAS_CXX11_RANGE_FOR=1. +*/ +template <bool Const, typename ValueT> +class GenericObject { +public: + typedef GenericObject<true, ValueT> ConstObject; + typedef GenericObject<false, ValueT> Object; + typedef ValueT PlainType; + typedef typename internal::MaybeAddConst<Const,PlainType>::Type ValueType; + typedef GenericMemberIterator<Const, typename ValueT::EncodingType, typename ValueT::AllocatorType> MemberIterator; // This may be const or non-const iterator + typedef GenericMemberIterator<true, typename ValueT::EncodingType, typename ValueT::AllocatorType> ConstMemberIterator; + typedef typename ValueType::AllocatorType AllocatorType; + typedef typename ValueType::StringRefType StringRefType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename ValueType::Ch Ch; + + template <typename, typename> + friend class GenericValue; + + GenericObject(const GenericObject& rhs) : value_(rhs.value_) {} + GenericObject& operator=(const GenericObject& rhs) { value_ = rhs.value_; return *this; } + ~GenericObject() {} + + operator ValueType&() const { return value_; } + SizeType MemberCount() const { return value_.MemberCount(); } + SizeType MemberCapacity() const { return value_.MemberCapacity(); } + bool ObjectEmpty() const { return value_.ObjectEmpty(); } + template <typename T> ValueType& operator[](T* name) const { return value_[name]; } + template <typename SourceAllocator> ValueType& operator[](const GenericValue<EncodingType, SourceAllocator>& name) const { return value_[name]; } +#if RAPIDJSON_HAS_STDSTRING + ValueType& operator[](const std::basic_string<Ch>& name) const { return value_[name]; } +#endif + MemberIterator MemberBegin() const { return value_.MemberBegin(); } + MemberIterator MemberEnd() const { return value_.MemberEnd(); } + GenericObject MemberReserve(SizeType newCapacity, AllocatorType &allocator) const { value_.MemberReserve(newCapacity, allocator); return *this; } + bool HasMember(const Ch* name) const { return value_.HasMember(name); } +#if RAPIDJSON_HAS_STDSTRING + bool HasMember(const std::basic_string<Ch>& name) const { return value_.HasMember(name); } +#endif + template <typename SourceAllocator> bool HasMember(const GenericValue<EncodingType, SourceAllocator>& name) const { return value_.HasMember(name); } + MemberIterator FindMember(const Ch* name) const { return value_.FindMember(name); } + template <typename SourceAllocator> MemberIterator FindMember(const GenericValue<EncodingType, SourceAllocator>& name) const { return value_.FindMember(name); } +#if RAPIDJSON_HAS_STDSTRING + MemberIterator FindMember(const std::basic_string<Ch>& name) const { return value_.FindMember(name); } +#endif + GenericObject AddMember(ValueType& name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(ValueType& name, StringRefType value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#if RAPIDJSON_HAS_STDSTRING + GenericObject AddMember(ValueType& name, std::basic_string<Ch>& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#endif + template <typename T> RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr<internal::IsPointer<T>, internal::IsGenericValue<T> >), (ValueType&)) AddMember(ValueType& name, T value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericObject AddMember(ValueType&& name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(ValueType&& name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(ValueType& name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(StringRefType name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericObject AddMember(StringRefType name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(StringRefType name, StringRefType value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + template <typename T> RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr<internal::IsPointer<T>, internal::IsGenericValue<T> >), (GenericObject)) AddMember(StringRefType name, T value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + void RemoveAllMembers() { value_.RemoveAllMembers(); } + bool RemoveMember(const Ch* name) const { return value_.RemoveMember(name); } +#if RAPIDJSON_HAS_STDSTRING + bool RemoveMember(const std::basic_string<Ch>& name) const { return value_.RemoveMember(name); } +#endif + template <typename SourceAllocator> bool RemoveMember(const GenericValue<EncodingType, SourceAllocator>& name) const { return value_.RemoveMember(name); } + MemberIterator RemoveMember(MemberIterator m) const { return value_.RemoveMember(m); } + MemberIterator EraseMember(ConstMemberIterator pos) const { return value_.EraseMember(pos); } + MemberIterator EraseMember(ConstMemberIterator first, ConstMemberIterator last) const { return value_.EraseMember(first, last); } + bool EraseMember(const Ch* name) const { return value_.EraseMember(name); } +#if RAPIDJSON_HAS_STDSTRING + bool EraseMember(const std::basic_string<Ch>& name) const { return EraseMember(ValueType(StringRef(name))); } +#endif + template <typename SourceAllocator> bool EraseMember(const GenericValue<EncodingType, SourceAllocator>& name) const { return value_.EraseMember(name); } + +#if RAPIDJSON_HAS_CXX11_RANGE_FOR + MemberIterator begin() const { return value_.MemberBegin(); } + MemberIterator end() const { return value_.MemberEnd(); } +#endif + +private: + GenericObject(); + GenericObject(ValueType& value) : value_(value) {} + ValueType& value_; +}; + +RAPIDJSON_NAMESPACE_END +RAPIDJSON_DIAG_POP + +#endif // RAPIDJSON_DOCUMENT_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/encodedstream.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/encodedstream.h new file mode 100644 index 00000000..cf046b89 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/encodedstream.h @@ -0,0 +1,299 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ENCODEDSTREAM_H_ +#define RAPIDJSON_ENCODEDSTREAM_H_ + +#include "stream.h" +#include "memorystream.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Input byte stream wrapper with a statically bound encoding. +/*! + \tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE. + \tparam InputByteStream Type of input byte stream. For example, FileReadStream. +*/ +template <typename Encoding, typename InputByteStream> +class EncodedInputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); +public: + typedef typename Encoding::Ch Ch; + + EncodedInputStream(InputByteStream& is) : is_(is) { + current_ = Encoding::TakeBOM(is_); + } + + Ch Peek() const { return current_; } + Ch Take() { Ch c = current_; current_ = Encoding::Take(is_); return c; } + size_t Tell() const { return is_.Tell(); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + EncodedInputStream(const EncodedInputStream&); + EncodedInputStream& operator=(const EncodedInputStream&); + + InputByteStream& is_; + Ch current_; +}; + +//! Specialized for UTF8 MemoryStream. +template <> +class EncodedInputStream<UTF8<>, MemoryStream> { +public: + typedef UTF8<>::Ch Ch; + + EncodedInputStream(MemoryStream& is) : is_(is) { + if (static_cast<unsigned char>(is_.Peek()) == 0xEFu) is_.Take(); + if (static_cast<unsigned char>(is_.Peek()) == 0xBBu) is_.Take(); + if (static_cast<unsigned char>(is_.Peek()) == 0xBFu) is_.Take(); + } + Ch Peek() const { return is_.Peek(); } + Ch Take() { return is_.Take(); } + size_t Tell() const { return is_.Tell(); } + + // Not implemented + void Put(Ch) {} + void Flush() {} + Ch* PutBegin() { return 0; } + size_t PutEnd(Ch*) { return 0; } + + MemoryStream& is_; + +private: + EncodedInputStream(const EncodedInputStream&); + EncodedInputStream& operator=(const EncodedInputStream&); +}; + +//! Output byte stream wrapper with statically bound encoding. +/*! + \tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE. + \tparam OutputByteStream Type of input byte stream. For example, FileWriteStream. +*/ +template <typename Encoding, typename OutputByteStream> +class EncodedOutputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); +public: + typedef typename Encoding::Ch Ch; + + EncodedOutputStream(OutputByteStream& os, bool putBOM = true) : os_(os) { + if (putBOM) + Encoding::PutBOM(os_); + } + + void Put(Ch c) { Encoding::Put(os_, c); } + void Flush() { os_.Flush(); } + + // Not implemented + Ch Peek() const { RAPIDJSON_ASSERT(false); return 0;} + Ch Take() { RAPIDJSON_ASSERT(false); return 0;} + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + EncodedOutputStream(const EncodedOutputStream&); + EncodedOutputStream& operator=(const EncodedOutputStream&); + + OutputByteStream& os_; +}; + +#define RAPIDJSON_ENCODINGS_FUNC(x) UTF8<Ch>::x, UTF16LE<Ch>::x, UTF16BE<Ch>::x, UTF32LE<Ch>::x, UTF32BE<Ch>::x + +//! Input stream wrapper with dynamically bound encoding and automatic encoding detection. +/*! + \tparam CharType Type of character for reading. + \tparam InputByteStream type of input byte stream to be wrapped. +*/ +template <typename CharType, typename InputByteStream> +class AutoUTFInputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); +public: + typedef CharType Ch; + + //! Constructor. + /*! + \param is input stream to be wrapped. + \param type UTF encoding type if it is not detected from the stream. + */ + AutoUTFInputStream(InputByteStream& is, UTFType type = kUTF8) : is_(&is), type_(type), hasBOM_(false) { + RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE); + DetectType(); + static const TakeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Take) }; + takeFunc_ = f[type_]; + current_ = takeFunc_(*is_); + } + + UTFType GetType() const { return type_; } + bool HasBOM() const { return hasBOM_; } + + Ch Peek() const { return current_; } + Ch Take() { Ch c = current_; current_ = takeFunc_(*is_); return c; } + size_t Tell() const { return is_->Tell(); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + AutoUTFInputStream(const AutoUTFInputStream&); + AutoUTFInputStream& operator=(const AutoUTFInputStream&); + + // Detect encoding type with BOM or RFC 4627 + void DetectType() { + // BOM (Byte Order Mark): + // 00 00 FE FF UTF-32BE + // FF FE 00 00 UTF-32LE + // FE FF UTF-16BE + // FF FE UTF-16LE + // EF BB BF UTF-8 + + const unsigned char* c = reinterpret_cast<const unsigned char *>(is_->Peek4()); + if (!c) + return; + + unsigned bom = static_cast<unsigned>(c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24)); + hasBOM_ = false; + if (bom == 0xFFFE0000) { type_ = kUTF32BE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); } + else if (bom == 0x0000FEFF) { type_ = kUTF32LE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); } + else if ((bom & 0xFFFF) == 0xFFFE) { type_ = kUTF16BE; hasBOM_ = true; is_->Take(); is_->Take(); } + else if ((bom & 0xFFFF) == 0xFEFF) { type_ = kUTF16LE; hasBOM_ = true; is_->Take(); is_->Take(); } + else if ((bom & 0xFFFFFF) == 0xBFBBEF) { type_ = kUTF8; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); } + + // RFC 4627: Section 3 + // "Since the first two characters of a JSON text will always be ASCII + // characters [RFC0020], it is possible to determine whether an octet + // stream is UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE) by looking + // at the pattern of nulls in the first four octets." + // 00 00 00 xx UTF-32BE + // 00 xx 00 xx UTF-16BE + // xx 00 00 00 UTF-32LE + // xx 00 xx 00 UTF-16LE + // xx xx xx xx UTF-8 + + if (!hasBOM_) { + int pattern = (c[0] ? 1 : 0) | (c[1] ? 2 : 0) | (c[2] ? 4 : 0) | (c[3] ? 8 : 0); + switch (pattern) { + case 0x08: type_ = kUTF32BE; break; + case 0x0A: type_ = kUTF16BE; break; + case 0x01: type_ = kUTF32LE; break; + case 0x05: type_ = kUTF16LE; break; + case 0x0F: type_ = kUTF8; break; + default: break; // Use type defined by user. + } + } + + // Runtime check whether the size of character type is sufficient. It only perform checks with assertion. + if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2); + if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4); + } + + typedef Ch (*TakeFunc)(InputByteStream& is); + InputByteStream* is_; + UTFType type_; + Ch current_; + TakeFunc takeFunc_; + bool hasBOM_; +}; + +//! Output stream wrapper with dynamically bound encoding and automatic encoding detection. +/*! + \tparam CharType Type of character for writing. + \tparam OutputByteStream type of output byte stream to be wrapped. +*/ +template <typename CharType, typename OutputByteStream> +class AutoUTFOutputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); +public: + typedef CharType Ch; + + //! Constructor. + /*! + \param os output stream to be wrapped. + \param type UTF encoding type. + \param putBOM Whether to write BOM at the beginning of the stream. + */ + AutoUTFOutputStream(OutputByteStream& os, UTFType type, bool putBOM) : os_(&os), type_(type) { + RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE); + + // Runtime check whether the size of character type is sufficient. It only perform checks with assertion. + if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2); + if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4); + + static const PutFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Put) }; + putFunc_ = f[type_]; + + if (putBOM) + PutBOM(); + } + + UTFType GetType() const { return type_; } + + void Put(Ch c) { putFunc_(*os_, c); } + void Flush() { os_->Flush(); } + + // Not implemented + Ch Peek() const { RAPIDJSON_ASSERT(false); return 0;} + Ch Take() { RAPIDJSON_ASSERT(false); return 0;} + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + AutoUTFOutputStream(const AutoUTFOutputStream&); + AutoUTFOutputStream& operator=(const AutoUTFOutputStream&); + + void PutBOM() { + typedef void (*PutBOMFunc)(OutputByteStream&); + static const PutBOMFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(PutBOM) }; + f[type_](*os_); + } + + typedef void (*PutFunc)(OutputByteStream&, Ch); + + OutputByteStream* os_; + UTFType type_; + PutFunc putFunc_; +}; + +#undef RAPIDJSON_ENCODINGS_FUNC + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/encodings.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/encodings.h new file mode 100644 index 00000000..50ad18bd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/encodings.h @@ -0,0 +1,716 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ENCODINGS_H_ +#define RAPIDJSON_ENCODINGS_H_ + +#include "rapidjson.h" + +#if defined(_MSC_VER) && !defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4244) // conversion from 'type1' to 'type2', possible loss of data +RAPIDJSON_DIAG_OFF(4702) // unreachable code +#elif defined(__GNUC__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +RAPIDJSON_DIAG_OFF(overflow) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Encoding + +/*! \class rapidjson::Encoding + \brief Concept for encoding of Unicode characters. + +\code +concept Encoding { + typename Ch; //! Type of character. A "character" is actually a code unit in unicode's definition. + + enum { supportUnicode = 1 }; // or 0 if not supporting unicode + + //! \brief Encode a Unicode codepoint to an output stream. + //! \param os Output stream. + //! \param codepoint An unicode codepoint, ranging from 0x0 to 0x10FFFF inclusively. + template<typename OutputStream> + static void Encode(OutputStream& os, unsigned codepoint); + + //! \brief Decode a Unicode codepoint from an input stream. + //! \param is Input stream. + //! \param codepoint Output of the unicode codepoint. + //! \return true if a valid codepoint can be decoded from the stream. + template <typename InputStream> + static bool Decode(InputStream& is, unsigned* codepoint); + + //! \brief Validate one Unicode codepoint from an encoded stream. + //! \param is Input stream to obtain codepoint. + //! \param os Output for copying one codepoint. + //! \return true if it is valid. + //! \note This function just validating and copying the codepoint without actually decode it. + template <typename InputStream, typename OutputStream> + static bool Validate(InputStream& is, OutputStream& os); + + // The following functions are deal with byte streams. + + //! Take a character from input byte stream, skip BOM if exist. + template <typename InputByteStream> + static CharType TakeBOM(InputByteStream& is); + + //! Take a character from input byte stream. + template <typename InputByteStream> + static Ch Take(InputByteStream& is); + + //! Put BOM to output byte stream. + template <typename OutputByteStream> + static void PutBOM(OutputByteStream& os); + + //! Put a character to output byte stream. + template <typename OutputByteStream> + static void Put(OutputByteStream& os, Ch c); +}; +\endcode +*/ + +/////////////////////////////////////////////////////////////////////////////// +// UTF8 + +//! UTF-8 encoding. +/*! http://en.wikipedia.org/wiki/UTF-8 + http://tools.ietf.org/html/rfc3629 + \tparam CharType Code unit for storing 8-bit UTF-8 data. Default is char. + \note implements Encoding concept +*/ +template<typename CharType = char> +struct UTF8 { + typedef CharType Ch; + + enum { supportUnicode = 1 }; + + template<typename OutputStream> + static void Encode(OutputStream& os, unsigned codepoint) { + if (codepoint <= 0x7F) + os.Put(static_cast<Ch>(codepoint & 0xFF)); + else if (codepoint <= 0x7FF) { + os.Put(static_cast<Ch>(0xC0 | ((codepoint >> 6) & 0xFF))); + os.Put(static_cast<Ch>(0x80 | ((codepoint & 0x3F)))); + } + else if (codepoint <= 0xFFFF) { + os.Put(static_cast<Ch>(0xE0 | ((codepoint >> 12) & 0xFF))); + os.Put(static_cast<Ch>(0x80 | ((codepoint >> 6) & 0x3F))); + os.Put(static_cast<Ch>(0x80 | (codepoint & 0x3F))); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + os.Put(static_cast<Ch>(0xF0 | ((codepoint >> 18) & 0xFF))); + os.Put(static_cast<Ch>(0x80 | ((codepoint >> 12) & 0x3F))); + os.Put(static_cast<Ch>(0x80 | ((codepoint >> 6) & 0x3F))); + os.Put(static_cast<Ch>(0x80 | (codepoint & 0x3F))); + } + } + + template<typename OutputStream> + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + if (codepoint <= 0x7F) + PutUnsafe(os, static_cast<Ch>(codepoint & 0xFF)); + else if (codepoint <= 0x7FF) { + PutUnsafe(os, static_cast<Ch>(0xC0 | ((codepoint >> 6) & 0xFF))); + PutUnsafe(os, static_cast<Ch>(0x80 | ((codepoint & 0x3F)))); + } + else if (codepoint <= 0xFFFF) { + PutUnsafe(os, static_cast<Ch>(0xE0 | ((codepoint >> 12) & 0xFF))); + PutUnsafe(os, static_cast<Ch>(0x80 | ((codepoint >> 6) & 0x3F))); + PutUnsafe(os, static_cast<Ch>(0x80 | (codepoint & 0x3F))); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + PutUnsafe(os, static_cast<Ch>(0xF0 | ((codepoint >> 18) & 0xFF))); + PutUnsafe(os, static_cast<Ch>(0x80 | ((codepoint >> 12) & 0x3F))); + PutUnsafe(os, static_cast<Ch>(0x80 | ((codepoint >> 6) & 0x3F))); + PutUnsafe(os, static_cast<Ch>(0x80 | (codepoint & 0x3F))); + } + } + + template <typename InputStream> + static bool Decode(InputStream& is, unsigned* codepoint) { +#define RAPIDJSON_COPY() c = is.Take(); *codepoint = (*codepoint << 6) | (static_cast<unsigned char>(c) & 0x3Fu) +#define RAPIDJSON_TRANS(mask) result &= ((GetRange(static_cast<unsigned char>(c)) & mask) != 0) +#define RAPIDJSON_TAIL() RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x70) + typename InputStream::Ch c = is.Take(); + if (!(c & 0x80)) { + *codepoint = static_cast<unsigned char>(c); + return true; + } + + unsigned char type = GetRange(static_cast<unsigned char>(c)); + if (type >= 32) { + *codepoint = 0; + } else { + *codepoint = (0xFFu >> type) & static_cast<unsigned char>(c); + } + bool result = true; + switch (type) { + case 2: RAPIDJSON_TAIL(); return result; + case 3: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 4: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x50); RAPIDJSON_TAIL(); return result; + case 5: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x10); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 6: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 10: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x20); RAPIDJSON_TAIL(); return result; + case 11: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x60); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + default: return false; + } +#undef RAPIDJSON_COPY +#undef RAPIDJSON_TRANS +#undef RAPIDJSON_TAIL + } + + template <typename InputStream, typename OutputStream> + static bool Validate(InputStream& is, OutputStream& os) { +#define RAPIDJSON_COPY() os.Put(c = is.Take()) +#define RAPIDJSON_TRANS(mask) result &= ((GetRange(static_cast<unsigned char>(c)) & mask) != 0) +#define RAPIDJSON_TAIL() RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x70) + Ch c; + RAPIDJSON_COPY(); + if (!(c & 0x80)) + return true; + + bool result = true; + switch (GetRange(static_cast<unsigned char>(c))) { + case 2: RAPIDJSON_TAIL(); return result; + case 3: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 4: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x50); RAPIDJSON_TAIL(); return result; + case 5: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x10); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 6: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 10: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x20); RAPIDJSON_TAIL(); return result; + case 11: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x60); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + default: return false; + } +#undef RAPIDJSON_COPY +#undef RAPIDJSON_TRANS +#undef RAPIDJSON_TAIL + } + + static unsigned char GetRange(unsigned char c) { + // Referring to DFA of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + // With new mapping 1 -> 0x10, 7 -> 0x20, 9 -> 0x40, such that AND operation can test multiple types. + static const unsigned char type[] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, + 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + }; + return type[c]; + } + + template <typename InputByteStream> + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + typename InputByteStream::Ch c = Take(is); + if (static_cast<unsigned char>(c) != 0xEFu) return c; + c = is.Take(); + if (static_cast<unsigned char>(c) != 0xBBu) return c; + c = is.Take(); + if (static_cast<unsigned char>(c) != 0xBFu) return c; + c = is.Take(); + return c; + } + + template <typename InputByteStream> + static Ch Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + return static_cast<Ch>(is.Take()); + } + + template <typename OutputByteStream> + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast<typename OutputByteStream::Ch>(0xEFu)); + os.Put(static_cast<typename OutputByteStream::Ch>(0xBBu)); + os.Put(static_cast<typename OutputByteStream::Ch>(0xBFu)); + } + + template <typename OutputByteStream> + static void Put(OutputByteStream& os, Ch c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast<typename OutputByteStream::Ch>(c)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// UTF16 + +//! UTF-16 encoding. +/*! http://en.wikipedia.org/wiki/UTF-16 + http://tools.ietf.org/html/rfc2781 + \tparam CharType Type for storing 16-bit UTF-16 data. Default is wchar_t. C++11 may use char16_t instead. + \note implements Encoding concept + + \note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness. + For streaming, use UTF16LE and UTF16BE, which handle endianness. +*/ +template<typename CharType = wchar_t> +struct UTF16 { + typedef CharType Ch; + RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 2); + + enum { supportUnicode = 1 }; + + template<typename OutputStream> + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + if (codepoint <= 0xFFFF) { + RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair + os.Put(static_cast<typename OutputStream::Ch>(codepoint)); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + unsigned v = codepoint - 0x10000; + os.Put(static_cast<typename OutputStream::Ch>((v >> 10) | 0xD800)); + os.Put(static_cast<typename OutputStream::Ch>((v & 0x3FF) | 0xDC00)); + } + } + + + template<typename OutputStream> + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + if (codepoint <= 0xFFFF) { + RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair + PutUnsafe(os, static_cast<typename OutputStream::Ch>(codepoint)); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + unsigned v = codepoint - 0x10000; + PutUnsafe(os, static_cast<typename OutputStream::Ch>((v >> 10) | 0xD800)); + PutUnsafe(os, static_cast<typename OutputStream::Ch>((v & 0x3FF) | 0xDC00)); + } + } + + template <typename InputStream> + static bool Decode(InputStream& is, unsigned* codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2); + typename InputStream::Ch c = is.Take(); + if (c < 0xD800 || c > 0xDFFF) { + *codepoint = static_cast<unsigned>(c); + return true; + } + else if (c <= 0xDBFF) { + *codepoint = (static_cast<unsigned>(c) & 0x3FF) << 10; + c = is.Take(); + *codepoint |= (static_cast<unsigned>(c) & 0x3FF); + *codepoint += 0x10000; + return c >= 0xDC00 && c <= 0xDFFF; + } + return false; + } + + template <typename InputStream, typename OutputStream> + static bool Validate(InputStream& is, OutputStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2); + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + typename InputStream::Ch c; + os.Put(static_cast<typename OutputStream::Ch>(c = is.Take())); + if (c < 0xD800 || c > 0xDFFF) + return true; + else if (c <= 0xDBFF) { + os.Put(c = is.Take()); + return c >= 0xDC00 && c <= 0xDFFF; + } + return false; + } +}; + +//! UTF-16 little endian encoding. +template<typename CharType = wchar_t> +struct UTF16LE : UTF16<CharType> { + template <typename InputByteStream> + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast<uint16_t>(c) == 0xFEFFu ? Take(is) : c; + } + + template <typename InputByteStream> + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast<uint8_t>(is.Take()); + c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 8; + return static_cast<CharType>(c); + } + + template <typename OutputByteStream> + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast<typename OutputByteStream::Ch>(0xFFu)); + os.Put(static_cast<typename OutputByteStream::Ch>(0xFEu)); + } + + template <typename OutputByteStream> + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast<typename OutputByteStream::Ch>(static_cast<unsigned>(c) & 0xFFu)); + os.Put(static_cast<typename OutputByteStream::Ch>((static_cast<unsigned>(c) >> 8) & 0xFFu)); + } +}; + +//! UTF-16 big endian encoding. +template<typename CharType = wchar_t> +struct UTF16BE : UTF16<CharType> { + template <typename InputByteStream> + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast<uint16_t>(c) == 0xFEFFu ? Take(is) : c; + } + + template <typename InputByteStream> + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 8; + c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())); + return static_cast<CharType>(c); + } + + template <typename OutputByteStream> + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast<typename OutputByteStream::Ch>(0xFEu)); + os.Put(static_cast<typename OutputByteStream::Ch>(0xFFu)); + } + + template <typename OutputByteStream> + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast<typename OutputByteStream::Ch>((static_cast<unsigned>(c) >> 8) & 0xFFu)); + os.Put(static_cast<typename OutputByteStream::Ch>(static_cast<unsigned>(c) & 0xFFu)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// UTF32 + +//! UTF-32 encoding. +/*! http://en.wikipedia.org/wiki/UTF-32 + \tparam CharType Type for storing 32-bit UTF-32 data. Default is unsigned. C++11 may use char32_t instead. + \note implements Encoding concept + + \note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness. + For streaming, use UTF32LE and UTF32BE, which handle endianness. +*/ +template<typename CharType = unsigned> +struct UTF32 { + typedef CharType Ch; + RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 4); + + enum { supportUnicode = 1 }; + + template<typename OutputStream> + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 4); + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + os.Put(codepoint); + } + + template<typename OutputStream> + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 4); + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + PutUnsafe(os, codepoint); + } + + template <typename InputStream> + static bool Decode(InputStream& is, unsigned* codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4); + Ch c = is.Take(); + *codepoint = c; + return c <= 0x10FFFF; + } + + template <typename InputStream, typename OutputStream> + static bool Validate(InputStream& is, OutputStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4); + Ch c; + os.Put(c = is.Take()); + return c <= 0x10FFFF; + } +}; + +//! UTF-32 little endian enocoding. +template<typename CharType = unsigned> +struct UTF32LE : UTF32<CharType> { + template <typename InputByteStream> + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast<uint32_t>(c) == 0x0000FEFFu ? Take(is) : c; + } + + template <typename InputByteStream> + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast<uint8_t>(is.Take()); + c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 8; + c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 16; + c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 24; + return static_cast<CharType>(c); + } + + template <typename OutputByteStream> + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast<typename OutputByteStream::Ch>(0xFFu)); + os.Put(static_cast<typename OutputByteStream::Ch>(0xFEu)); + os.Put(static_cast<typename OutputByteStream::Ch>(0x00u)); + os.Put(static_cast<typename OutputByteStream::Ch>(0x00u)); + } + + template <typename OutputByteStream> + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast<typename OutputByteStream::Ch>(c & 0xFFu)); + os.Put(static_cast<typename OutputByteStream::Ch>((c >> 8) & 0xFFu)); + os.Put(static_cast<typename OutputByteStream::Ch>((c >> 16) & 0xFFu)); + os.Put(static_cast<typename OutputByteStream::Ch>((c >> 24) & 0xFFu)); + } +}; + +//! UTF-32 big endian encoding. +template<typename CharType = unsigned> +struct UTF32BE : UTF32<CharType> { + template <typename InputByteStream> + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast<uint32_t>(c) == 0x0000FEFFu ? Take(is) : c; + } + + template <typename InputByteStream> + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 24; + c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 16; + c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())) << 8; + c |= static_cast<unsigned>(static_cast<uint8_t>(is.Take())); + return static_cast<CharType>(c); + } + + template <typename OutputByteStream> + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast<typename OutputByteStream::Ch>(0x00u)); + os.Put(static_cast<typename OutputByteStream::Ch>(0x00u)); + os.Put(static_cast<typename OutputByteStream::Ch>(0xFEu)); + os.Put(static_cast<typename OutputByteStream::Ch>(0xFFu)); + } + + template <typename OutputByteStream> + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast<typename OutputByteStream::Ch>((c >> 24) & 0xFFu)); + os.Put(static_cast<typename OutputByteStream::Ch>((c >> 16) & 0xFFu)); + os.Put(static_cast<typename OutputByteStream::Ch>((c >> 8) & 0xFFu)); + os.Put(static_cast<typename OutputByteStream::Ch>(c & 0xFFu)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// ASCII + +//! ASCII encoding. +/*! http://en.wikipedia.org/wiki/ASCII + \tparam CharType Code unit for storing 7-bit ASCII data. Default is char. + \note implements Encoding concept +*/ +template<typename CharType = char> +struct ASCII { + typedef CharType Ch; + + enum { supportUnicode = 0 }; + + template<typename OutputStream> + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_ASSERT(codepoint <= 0x7F); + os.Put(static_cast<Ch>(codepoint & 0xFF)); + } + + template<typename OutputStream> + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + RAPIDJSON_ASSERT(codepoint <= 0x7F); + PutUnsafe(os, static_cast<Ch>(codepoint & 0xFF)); + } + + template <typename InputStream> + static bool Decode(InputStream& is, unsigned* codepoint) { + uint8_t c = static_cast<uint8_t>(is.Take()); + *codepoint = c; + return c <= 0X7F; + } + + template <typename InputStream, typename OutputStream> + static bool Validate(InputStream& is, OutputStream& os) { + uint8_t c = static_cast<uint8_t>(is.Take()); + os.Put(static_cast<typename OutputStream::Ch>(c)); + return c <= 0x7F; + } + + template <typename InputByteStream> + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + uint8_t c = static_cast<uint8_t>(Take(is)); + return static_cast<Ch>(c); + } + + template <typename InputByteStream> + static Ch Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + return static_cast<Ch>(is.Take()); + } + + template <typename OutputByteStream> + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + (void)os; + } + + template <typename OutputByteStream> + static void Put(OutputByteStream& os, Ch c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast<typename OutputByteStream::Ch>(c)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// AutoUTF + +//! Runtime-specified UTF encoding type of a stream. +enum UTFType { + kUTF8 = 0, //!< UTF-8. + kUTF16LE = 1, //!< UTF-16 little endian. + kUTF16BE = 2, //!< UTF-16 big endian. + kUTF32LE = 3, //!< UTF-32 little endian. + kUTF32BE = 4 //!< UTF-32 big endian. +}; + +//! Dynamically select encoding according to stream's runtime-specified UTF encoding type. +/*! \note This class can be used with AutoUTFInputtStream and AutoUTFOutputStream, which provides GetType(). +*/ +template<typename CharType> +struct AutoUTF { + typedef CharType Ch; + + enum { supportUnicode = 1 }; + +#define RAPIDJSON_ENCODINGS_FUNC(x) UTF8<Ch>::x, UTF16LE<Ch>::x, UTF16BE<Ch>::x, UTF32LE<Ch>::x, UTF32BE<Ch>::x + + template<typename OutputStream> + static RAPIDJSON_FORCEINLINE void Encode(OutputStream& os, unsigned codepoint) { + typedef void (*EncodeFunc)(OutputStream&, unsigned); + static const EncodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Encode) }; + (*f[os.GetType()])(os, codepoint); + } + + template<typename OutputStream> + static RAPIDJSON_FORCEINLINE void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + typedef void (*EncodeFunc)(OutputStream&, unsigned); + static const EncodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(EncodeUnsafe) }; + (*f[os.GetType()])(os, codepoint); + } + + template <typename InputStream> + static RAPIDJSON_FORCEINLINE bool Decode(InputStream& is, unsigned* codepoint) { + typedef bool (*DecodeFunc)(InputStream&, unsigned*); + static const DecodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Decode) }; + return (*f[is.GetType()])(is, codepoint); + } + + template <typename InputStream, typename OutputStream> + static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) { + typedef bool (*ValidateFunc)(InputStream&, OutputStream&); + static const ValidateFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Validate) }; + return (*f[is.GetType()])(is, os); + } + +#undef RAPIDJSON_ENCODINGS_FUNC +}; + +/////////////////////////////////////////////////////////////////////////////// +// Transcoder + +//! Encoding conversion. +template<typename SourceEncoding, typename TargetEncoding> +struct Transcoder { + //! Take one Unicode codepoint from source encoding, convert it to target encoding and put it to the output stream. + template<typename InputStream, typename OutputStream> + static RAPIDJSON_FORCEINLINE bool Transcode(InputStream& is, OutputStream& os) { + unsigned codepoint; + if (!SourceEncoding::Decode(is, &codepoint)) + return false; + TargetEncoding::Encode(os, codepoint); + return true; + } + + template<typename InputStream, typename OutputStream> + static RAPIDJSON_FORCEINLINE bool TranscodeUnsafe(InputStream& is, OutputStream& os) { + unsigned codepoint; + if (!SourceEncoding::Decode(is, &codepoint)) + return false; + TargetEncoding::EncodeUnsafe(os, codepoint); + return true; + } + + //! Validate one Unicode codepoint from an encoded stream. + template<typename InputStream, typename OutputStream> + static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) { + return Transcode(is, os); // Since source/target encoding is different, must transcode. + } +}; + +// Forward declaration. +template<typename Stream> +inline void PutUnsafe(Stream& stream, typename Stream::Ch c); + +//! Specialization of Transcoder with same source and target encoding. +template<typename Encoding> +struct Transcoder<Encoding, Encoding> { + template<typename InputStream, typename OutputStream> + static RAPIDJSON_FORCEINLINE bool Transcode(InputStream& is, OutputStream& os) { + os.Put(is.Take()); // Just copy one code unit. This semantic is different from primary template class. + return true; + } + + template<typename InputStream, typename OutputStream> + static RAPIDJSON_FORCEINLINE bool TranscodeUnsafe(InputStream& is, OutputStream& os) { + PutUnsafe(os, is.Take()); // Just copy one code unit. This semantic is different from primary template class. + return true; + } + + template<typename InputStream, typename OutputStream> + static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) { + return Encoding::Validate(is, os); // source/target encoding are the same + } +}; + +RAPIDJSON_NAMESPACE_END + +#if defined(__GNUC__) || (defined(_MSC_VER) && !defined(__clang__)) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ENCODINGS_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/error/en.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/error/en.h new file mode 100644 index 00000000..5d2e57b7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/error/en.h @@ -0,0 +1,122 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ERROR_EN_H_ +#define RAPIDJSON_ERROR_EN_H_ + +#include "error.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(switch-enum) +RAPIDJSON_DIAG_OFF(covered-switch-default) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Maps error code of parsing into error message. +/*! + \ingroup RAPIDJSON_ERRORS + \param parseErrorCode Error code obtained in parsing. + \return the error message. + \note User can make a copy of this function for localization. + Using switch-case is safer for future modification of error codes. +*/ +inline const RAPIDJSON_ERROR_CHARTYPE* GetParseError_En(ParseErrorCode parseErrorCode) { + switch (parseErrorCode) { + case kParseErrorNone: return RAPIDJSON_ERROR_STRING("No error."); + + case kParseErrorDocumentEmpty: return RAPIDJSON_ERROR_STRING("The document is empty."); + case kParseErrorDocumentRootNotSingular: return RAPIDJSON_ERROR_STRING("The document root must not be followed by other values."); + + case kParseErrorValueInvalid: return RAPIDJSON_ERROR_STRING("Invalid value."); + + case kParseErrorObjectMissName: return RAPIDJSON_ERROR_STRING("Missing a name for object member."); + case kParseErrorObjectMissColon: return RAPIDJSON_ERROR_STRING("Missing a colon after a name of object member."); + case kParseErrorObjectMissCommaOrCurlyBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or '}' after an object member."); + + case kParseErrorArrayMissCommaOrSquareBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or ']' after an array element."); + + case kParseErrorStringUnicodeEscapeInvalidHex: return RAPIDJSON_ERROR_STRING("Incorrect hex digit after \\u escape in string."); + case kParseErrorStringUnicodeSurrogateInvalid: return RAPIDJSON_ERROR_STRING("The surrogate pair in string is invalid."); + case kParseErrorStringEscapeInvalid: return RAPIDJSON_ERROR_STRING("Invalid escape character in string."); + case kParseErrorStringMissQuotationMark: return RAPIDJSON_ERROR_STRING("Missing a closing quotation mark in string."); + case kParseErrorStringInvalidEncoding: return RAPIDJSON_ERROR_STRING("Invalid encoding in string."); + + case kParseErrorNumberTooBig: return RAPIDJSON_ERROR_STRING("Number too big to be stored in double."); + case kParseErrorNumberMissFraction: return RAPIDJSON_ERROR_STRING("Miss fraction part in number."); + case kParseErrorNumberMissExponent: return RAPIDJSON_ERROR_STRING("Miss exponent in number."); + + case kParseErrorTermination: return RAPIDJSON_ERROR_STRING("Terminate parsing due to Handler error."); + case kParseErrorUnspecificSyntaxError: return RAPIDJSON_ERROR_STRING("Unspecific syntax error."); + + default: return RAPIDJSON_ERROR_STRING("Unknown error."); + } +} + +//! Maps error code of validation into error message. +/*! + \ingroup RAPIDJSON_ERRORS + \param validateErrorCode Error code obtained from validator. + \return the error message. + \note User can make a copy of this function for localization. + Using switch-case is safer for future modification of error codes. +*/ +inline const RAPIDJSON_ERROR_CHARTYPE* GetValidateError_En(ValidateErrorCode validateErrorCode) { + switch (validateErrorCode) { + case kValidateErrors: return RAPIDJSON_ERROR_STRING("One or more validation errors have occurred"); + case kValidateErrorNone: return RAPIDJSON_ERROR_STRING("No error."); + + case kValidateErrorMultipleOf: return RAPIDJSON_ERROR_STRING("Number '%actual' is not a multiple of the 'multipleOf' value '%expected'."); + case kValidateErrorMaximum: return RAPIDJSON_ERROR_STRING("Number '%actual' is greater than the 'maximum' value '%expected'."); + case kValidateErrorExclusiveMaximum: return RAPIDJSON_ERROR_STRING("Number '%actual' is greater than or equal to the 'exclusiveMaximum' value '%expected'."); + case kValidateErrorMinimum: return RAPIDJSON_ERROR_STRING("Number '%actual' is less than the 'minimum' value '%expected'."); + case kValidateErrorExclusiveMinimum: return RAPIDJSON_ERROR_STRING("Number '%actual' is less than or equal to the 'exclusiveMinimum' value '%expected'."); + + case kValidateErrorMaxLength: return RAPIDJSON_ERROR_STRING("String '%actual' is longer than the 'maxLength' value '%expected'."); + case kValidateErrorMinLength: return RAPIDJSON_ERROR_STRING("String '%actual' is shorter than the 'minLength' value '%expected'."); + case kValidateErrorPattern: return RAPIDJSON_ERROR_STRING("String '%actual' does not match the 'pattern' regular expression."); + + case kValidateErrorMaxItems: return RAPIDJSON_ERROR_STRING("Array of length '%actual' is longer than the 'maxItems' value '%expected'."); + case kValidateErrorMinItems: return RAPIDJSON_ERROR_STRING("Array of length '%actual' is shorter than the 'minItems' value '%expected'."); + case kValidateErrorUniqueItems: return RAPIDJSON_ERROR_STRING("Array has duplicate items at indices '%duplicates' but 'uniqueItems' is true."); + case kValidateErrorAdditionalItems: return RAPIDJSON_ERROR_STRING("Array has an additional item at index '%disallowed' that is not allowed by the schema."); + + case kValidateErrorMaxProperties: return RAPIDJSON_ERROR_STRING("Object has '%actual' members which is more than 'maxProperties' value '%expected'."); + case kValidateErrorMinProperties: return RAPIDJSON_ERROR_STRING("Object has '%actual' members which is less than 'minProperties' value '%expected'."); + case kValidateErrorRequired: return RAPIDJSON_ERROR_STRING("Object is missing the following members required by the schema: '%missing'."); + case kValidateErrorAdditionalProperties: return RAPIDJSON_ERROR_STRING("Object has an additional member '%disallowed' that is not allowed by the schema."); + case kValidateErrorPatternProperties: return RAPIDJSON_ERROR_STRING("Object has 'patternProperties' that are not allowed by the schema."); + case kValidateErrorDependencies: return RAPIDJSON_ERROR_STRING("Object has missing property or schema dependencies, refer to following errors."); + + case kValidateErrorEnum: return RAPIDJSON_ERROR_STRING("Property has a value that is not one of its allowed enumerated values."); + case kValidateErrorType: return RAPIDJSON_ERROR_STRING("Property has a type '%actual' that is not in the following list: '%expected'."); + + case kValidateErrorOneOf: return RAPIDJSON_ERROR_STRING("Property did not match any of the sub-schemas specified by 'oneOf', refer to following errors."); + case kValidateErrorOneOfMatch: return RAPIDJSON_ERROR_STRING("Property matched more than one of the sub-schemas specified by 'oneOf'."); + case kValidateErrorAllOf: return RAPIDJSON_ERROR_STRING("Property did not match all of the sub-schemas specified by 'allOf', refer to following errors."); + case kValidateErrorAnyOf: return RAPIDJSON_ERROR_STRING("Property did not match any of the sub-schemas specified by 'anyOf', refer to following errors."); + case kValidateErrorNot: return RAPIDJSON_ERROR_STRING("Property matched the sub-schema specified by 'not'."); + + default: return RAPIDJSON_ERROR_STRING("Unknown error."); + } +} + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ERROR_EN_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/error/error.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/error/error.h new file mode 100644 index 00000000..6270da11 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/error/error.h @@ -0,0 +1,216 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ERROR_ERROR_H_ +#define RAPIDJSON_ERROR_ERROR_H_ + +#include "../rapidjson.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +/*! \file error.h */ + +/*! \defgroup RAPIDJSON_ERRORS RapidJSON error handling */ + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ERROR_CHARTYPE + +//! Character type of error messages. +/*! \ingroup RAPIDJSON_ERRORS + The default character type is \c char. + On Windows, user can define this macro as \c TCHAR for supporting both + unicode/non-unicode settings. +*/ +#ifndef RAPIDJSON_ERROR_CHARTYPE +#define RAPIDJSON_ERROR_CHARTYPE char +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ERROR_STRING + +//! Macro for converting string literial to \ref RAPIDJSON_ERROR_CHARTYPE[]. +/*! \ingroup RAPIDJSON_ERRORS + By default this conversion macro does nothing. + On Windows, user can define this macro as \c _T(x) for supporting both + unicode/non-unicode settings. +*/ +#ifndef RAPIDJSON_ERROR_STRING +#define RAPIDJSON_ERROR_STRING(x) x +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// ParseErrorCode + +//! Error code of parsing. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericReader::Parse, GenericReader::GetParseErrorCode +*/ +enum ParseErrorCode { + kParseErrorNone = 0, //!< No error. + + kParseErrorDocumentEmpty, //!< The document is empty. + kParseErrorDocumentRootNotSingular, //!< The document root must not follow by other values. + + kParseErrorValueInvalid, //!< Invalid value. + + kParseErrorObjectMissName, //!< Missing a name for object member. + kParseErrorObjectMissColon, //!< Missing a colon after a name of object member. + kParseErrorObjectMissCommaOrCurlyBracket, //!< Missing a comma or '}' after an object member. + + kParseErrorArrayMissCommaOrSquareBracket, //!< Missing a comma or ']' after an array element. + + kParseErrorStringUnicodeEscapeInvalidHex, //!< Incorrect hex digit after \\u escape in string. + kParseErrorStringUnicodeSurrogateInvalid, //!< The surrogate pair in string is invalid. + kParseErrorStringEscapeInvalid, //!< Invalid escape character in string. + kParseErrorStringMissQuotationMark, //!< Missing a closing quotation mark in string. + kParseErrorStringInvalidEncoding, //!< Invalid encoding in string. + + kParseErrorNumberTooBig, //!< Number too big to be stored in double. + kParseErrorNumberMissFraction, //!< Miss fraction part in number. + kParseErrorNumberMissExponent, //!< Miss exponent in number. + + kParseErrorTermination, //!< Parsing was terminated. + kParseErrorUnspecificSyntaxError //!< Unspecific syntax error. +}; + +//! Result of parsing (wraps ParseErrorCode) +/*! + \ingroup RAPIDJSON_ERRORS + \code + Document doc; + ParseResult ok = doc.Parse("[42]"); + if (!ok) { + fprintf(stderr, "JSON parse error: %s (%u)", + GetParseError_En(ok.Code()), ok.Offset()); + exit(EXIT_FAILURE); + } + \endcode + \see GenericReader::Parse, GenericDocument::Parse +*/ +struct ParseResult { + //!! Unspecified boolean type + typedef bool (ParseResult::*BooleanType)() const; +public: + //! Default constructor, no error. + ParseResult() : code_(kParseErrorNone), offset_(0) {} + //! Constructor to set an error. + ParseResult(ParseErrorCode code, size_t offset) : code_(code), offset_(offset) {} + + //! Get the error code. + ParseErrorCode Code() const { return code_; } + //! Get the error offset, if \ref IsError(), 0 otherwise. + size_t Offset() const { return offset_; } + + //! Explicit conversion to \c bool, returns \c true, iff !\ref IsError(). + operator BooleanType() const { return !IsError() ? &ParseResult::IsError : NULL; } + //! Whether the result is an error. + bool IsError() const { return code_ != kParseErrorNone; } + + bool operator==(const ParseResult& that) const { return code_ == that.code_; } + bool operator==(ParseErrorCode code) const { return code_ == code; } + friend bool operator==(ParseErrorCode code, const ParseResult & err) { return code == err.code_; } + + bool operator!=(const ParseResult& that) const { return !(*this == that); } + bool operator!=(ParseErrorCode code) const { return !(*this == code); } + friend bool operator!=(ParseErrorCode code, const ParseResult & err) { return err != code; } + + //! Reset error code. + void Clear() { Set(kParseErrorNone); } + //! Update error code and offset. + void Set(ParseErrorCode code, size_t offset = 0) { code_ = code; offset_ = offset; } + +private: + ParseErrorCode code_; + size_t offset_; +}; + +//! Function pointer type of GetParseError(). +/*! \ingroup RAPIDJSON_ERRORS + + This is the prototype for \c GetParseError_X(), where \c X is a locale. + User can dynamically change locale in runtime, e.g.: +\code + GetParseErrorFunc GetParseError = GetParseError_En; // or whatever + const RAPIDJSON_ERROR_CHARTYPE* s = GetParseError(document.GetParseErrorCode()); +\endcode +*/ +typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetParseErrorFunc)(ParseErrorCode); + +/////////////////////////////////////////////////////////////////////////////// +// ValidateErrorCode + +//! Error codes when validating. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericSchemaValidator +*/ +enum ValidateErrorCode { + kValidateErrors = -1, //!< Top level error code when kValidateContinueOnErrorsFlag set. + kValidateErrorNone = 0, //!< No error. + + kValidateErrorMultipleOf, //!< Number is not a multiple of the 'multipleOf' value. + kValidateErrorMaximum, //!< Number is greater than the 'maximum' value. + kValidateErrorExclusiveMaximum, //!< Number is greater than or equal to the 'maximum' value. + kValidateErrorMinimum, //!< Number is less than the 'minimum' value. + kValidateErrorExclusiveMinimum, //!< Number is less than or equal to the 'minimum' value. + + kValidateErrorMaxLength, //!< String is longer than the 'maxLength' value. + kValidateErrorMinLength, //!< String is longer than the 'maxLength' value. + kValidateErrorPattern, //!< String does not match the 'pattern' regular expression. + + kValidateErrorMaxItems, //!< Array is longer than the 'maxItems' value. + kValidateErrorMinItems, //!< Array is shorter than the 'minItems' value. + kValidateErrorUniqueItems, //!< Array has duplicate items but 'uniqueItems' is true. + kValidateErrorAdditionalItems, //!< Array has additional items that are not allowed by the schema. + + kValidateErrorMaxProperties, //!< Object has more members than 'maxProperties' value. + kValidateErrorMinProperties, //!< Object has less members than 'minProperties' value. + kValidateErrorRequired, //!< Object is missing one or more members required by the schema. + kValidateErrorAdditionalProperties, //!< Object has additional members that are not allowed by the schema. + kValidateErrorPatternProperties, //!< See other errors. + kValidateErrorDependencies, //!< Object has missing property or schema dependencies. + + kValidateErrorEnum, //!< Property has a value that is not one of its allowed enumerated values + kValidateErrorType, //!< Property has a type that is not allowed by the schema.. + + kValidateErrorOneOf, //!< Property did not match any of the sub-schemas specified by 'oneOf'. + kValidateErrorOneOfMatch, //!< Property matched more than one of the sub-schemas specified by 'oneOf'. + kValidateErrorAllOf, //!< Property did not match all of the sub-schemas specified by 'allOf'. + kValidateErrorAnyOf, //!< Property did not match any of the sub-schemas specified by 'anyOf'. + kValidateErrorNot //!< Property matched the sub-schema specified by 'not'. +}; + +//! Function pointer type of GetValidateError(). +/*! \ingroup RAPIDJSON_ERRORS + + This is the prototype for \c GetValidateError_X(), where \c X is a locale. + User can dynamically change locale in runtime, e.g.: +\code + GetValidateErrorFunc GetValidateError = GetValidateError_En; // or whatever + const RAPIDJSON_ERROR_CHARTYPE* s = GetValidateError(validator.GetInvalidSchemaCode()); +\endcode +*/ +typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetValidateErrorFunc)(ValidateErrorCode); + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ERROR_ERROR_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/filereadstream.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/filereadstream.h new file mode 100644 index 00000000..f8bb43cb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/filereadstream.h @@ -0,0 +1,99 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FILEREADSTREAM_H_ +#define RAPIDJSON_FILEREADSTREAM_H_ + +#include "stream.h" +#include <cstdio> + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(unreachable-code) +RAPIDJSON_DIAG_OFF(missing-noreturn) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! File byte stream for input using fread(). +/*! + \note implements Stream concept +*/ +class FileReadStream { +public: + typedef char Ch; //!< Character type (byte). + + //! Constructor. + /*! + \param fp File pointer opened for read. + \param buffer user-supplied buffer. + \param bufferSize size of buffer in bytes. Must >=4 bytes. + */ + FileReadStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferSize_(bufferSize), bufferLast_(0), current_(buffer_), readCount_(0), count_(0), eof_(false) { + RAPIDJSON_ASSERT(fp_ != 0); + RAPIDJSON_ASSERT(bufferSize >= 4); + Read(); + } + + Ch Peek() const { return *current_; } + Ch Take() { Ch c = *current_; Read(); return c; } + size_t Tell() const { return count_ + static_cast<size_t>(current_ - buffer_); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + return (current_ + 4 - !eof_ <= bufferLast_) ? current_ : 0; + } + +private: + void Read() { + if (current_ < bufferLast_) + ++current_; + else if (!eof_) { + count_ += readCount_; + readCount_ = std::fread(buffer_, 1, bufferSize_, fp_); + bufferLast_ = buffer_ + readCount_ - 1; + current_ = buffer_; + + if (readCount_ < bufferSize_) { + buffer_[readCount_] = '\0'; + ++bufferLast_; + eof_ = true; + } + } + } + + std::FILE* fp_; + Ch *buffer_; + size_t bufferSize_; + Ch *bufferLast_; + Ch *current_; + size_t readCount_; + size_t count_; //!< Number of characters read + bool eof_; +}; + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/filewritestream.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/filewritestream.h new file mode 100644 index 00000000..5d89588c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/filewritestream.h @@ -0,0 +1,104 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FILEWRITESTREAM_H_ +#define RAPIDJSON_FILEWRITESTREAM_H_ + +#include "stream.h" +#include <cstdio> + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(unreachable-code) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Wrapper of C file stream for output using fwrite(). +/*! + \note implements Stream concept +*/ +class FileWriteStream { +public: + typedef char Ch; //!< Character type. Only support char. + + FileWriteStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferEnd_(buffer + bufferSize), current_(buffer_) { + RAPIDJSON_ASSERT(fp_ != 0); + } + + void Put(char c) { + if (current_ >= bufferEnd_) + Flush(); + + *current_++ = c; + } + + void PutN(char c, size_t n) { + size_t avail = static_cast<size_t>(bufferEnd_ - current_); + while (n > avail) { + std::memset(current_, c, avail); + current_ += avail; + Flush(); + n -= avail; + avail = static_cast<size_t>(bufferEnd_ - current_); + } + + if (n > 0) { + std::memset(current_, c, n); + current_ += n; + } + } + + void Flush() { + if (current_ != buffer_) { + size_t result = std::fwrite(buffer_, 1, static_cast<size_t>(current_ - buffer_), fp_); + if (result < static_cast<size_t>(current_ - buffer_)) { + // failure deliberately ignored at this time + // added to avoid warn_unused_result build errors + } + current_ = buffer_; + } + } + + // Not implemented + char Peek() const { RAPIDJSON_ASSERT(false); return 0; } + char Take() { RAPIDJSON_ASSERT(false); return 0; } + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + char* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(char*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + // Prohibit copy constructor & assignment operator. + FileWriteStream(const FileWriteStream&); + FileWriteStream& operator=(const FileWriteStream&); + + std::FILE* fp_; + char *buffer_; + char *bufferEnd_; + char *current_; +}; + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(FileWriteStream& stream, char c, size_t n) { + stream.PutN(c, n); +} + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/fwd.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/fwd.h new file mode 100644 index 00000000..d62f77f0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/fwd.h @@ -0,0 +1,151 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FWD_H_ +#define RAPIDJSON_FWD_H_ + +#include "rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN + +// encodings.h + +template<typename CharType> struct UTF8; +template<typename CharType> struct UTF16; +template<typename CharType> struct UTF16BE; +template<typename CharType> struct UTF16LE; +template<typename CharType> struct UTF32; +template<typename CharType> struct UTF32BE; +template<typename CharType> struct UTF32LE; +template<typename CharType> struct ASCII; +template<typename CharType> struct AutoUTF; + +template<typename SourceEncoding, typename TargetEncoding> +struct Transcoder; + +// allocators.h + +class CrtAllocator; + +template <typename BaseAllocator> +class MemoryPoolAllocator; + +// stream.h + +template <typename Encoding> +struct GenericStringStream; + +typedef GenericStringStream<UTF8<char> > StringStream; + +template <typename Encoding> +struct GenericInsituStringStream; + +typedef GenericInsituStringStream<UTF8<char> > InsituStringStream; + +// stringbuffer.h + +template <typename Encoding, typename Allocator> +class GenericStringBuffer; + +typedef GenericStringBuffer<UTF8<char>, CrtAllocator> StringBuffer; + +// filereadstream.h + +class FileReadStream; + +// filewritestream.h + +class FileWriteStream; + +// memorybuffer.h + +template <typename Allocator> +struct GenericMemoryBuffer; + +typedef GenericMemoryBuffer<CrtAllocator> MemoryBuffer; + +// memorystream.h + +struct MemoryStream; + +// reader.h + +template<typename Encoding, typename Derived> +struct BaseReaderHandler; + +template <typename SourceEncoding, typename TargetEncoding, typename StackAllocator> +class GenericReader; + +typedef GenericReader<UTF8<char>, UTF8<char>, CrtAllocator> Reader; + +// writer.h + +template<typename OutputStream, typename SourceEncoding, typename TargetEncoding, typename StackAllocator, unsigned writeFlags> +class Writer; + +// prettywriter.h + +template<typename OutputStream, typename SourceEncoding, typename TargetEncoding, typename StackAllocator, unsigned writeFlags> +class PrettyWriter; + +// document.h + +template <typename Encoding, typename Allocator> +class GenericMember; + +template <bool Const, typename Encoding, typename Allocator> +class GenericMemberIterator; + +template<typename CharType> +struct GenericStringRef; + +template <typename Encoding, typename Allocator> +class GenericValue; + +typedef GenericValue<UTF8<char>, MemoryPoolAllocator<CrtAllocator> > Value; + +template <typename Encoding, typename Allocator, typename StackAllocator> +class GenericDocument; + +typedef GenericDocument<UTF8<char>, MemoryPoolAllocator<CrtAllocator>, CrtAllocator> Document; + +// pointer.h + +template <typename ValueType, typename Allocator> +class GenericPointer; + +typedef GenericPointer<Value, CrtAllocator> Pointer; + +// schema.h + +template <typename SchemaDocumentType> +class IGenericRemoteSchemaDocumentProvider; + +template <typename ValueT, typename Allocator> +class GenericSchemaDocument; + +typedef GenericSchemaDocument<Value, CrtAllocator> SchemaDocument; +typedef IGenericRemoteSchemaDocumentProvider<SchemaDocument> IRemoteSchemaDocumentProvider; + +template < + typename SchemaDocumentType, + typename OutputHandler, + typename StateAllocator> +class GenericSchemaValidator; + +typedef GenericSchemaValidator<SchemaDocument, BaseReaderHandler<UTF8<char>, void>, CrtAllocator> SchemaValidator; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_RAPIDJSONFWD_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/biginteger.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/biginteger.h new file mode 100644 index 00000000..12455788 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/biginteger.h @@ -0,0 +1,290 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_BIGINTEGER_H_ +#define RAPIDJSON_BIGINTEGER_H_ + +#include "../rapidjson.h" + +#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) && defined(_M_AMD64) +#include <intrin.h> // for _umul128 +#pragma intrinsic(_umul128) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +class BigInteger { +public: + typedef uint64_t Type; + + BigInteger(const BigInteger& rhs) : count_(rhs.count_) { + std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type)); + } + + explicit BigInteger(uint64_t u) : count_(1) { + digits_[0] = u; + } + + BigInteger(const char* decimals, size_t length) : count_(1) { + RAPIDJSON_ASSERT(length > 0); + digits_[0] = 0; + size_t i = 0; + const size_t kMaxDigitPerIteration = 19; // 2^64 = 18446744073709551616 > 10^19 + while (length >= kMaxDigitPerIteration) { + AppendDecimal64(decimals + i, decimals + i + kMaxDigitPerIteration); + length -= kMaxDigitPerIteration; + i += kMaxDigitPerIteration; + } + + if (length > 0) + AppendDecimal64(decimals + i, decimals + i + length); + } + + BigInteger& operator=(const BigInteger &rhs) + { + if (this != &rhs) { + count_ = rhs.count_; + std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type)); + } + return *this; + } + + BigInteger& operator=(uint64_t u) { + digits_[0] = u; + count_ = 1; + return *this; + } + + BigInteger& operator+=(uint64_t u) { + Type backup = digits_[0]; + digits_[0] += u; + for (size_t i = 0; i < count_ - 1; i++) { + if (digits_[i] >= backup) + return *this; // no carry + backup = digits_[i + 1]; + digits_[i + 1] += 1; + } + + // Last carry + if (digits_[count_ - 1] < backup) + PushBack(1); + + return *this; + } + + BigInteger& operator*=(uint64_t u) { + if (u == 0) return *this = 0; + if (u == 1) return *this; + if (*this == 1) return *this = u; + + uint64_t k = 0; + for (size_t i = 0; i < count_; i++) { + uint64_t hi; + digits_[i] = MulAdd64(digits_[i], u, k, &hi); + k = hi; + } + + if (k > 0) + PushBack(k); + + return *this; + } + + BigInteger& operator*=(uint32_t u) { + if (u == 0) return *this = 0; + if (u == 1) return *this; + if (*this == 1) return *this = u; + + uint64_t k = 0; + for (size_t i = 0; i < count_; i++) { + const uint64_t c = digits_[i] >> 32; + const uint64_t d = digits_[i] & 0xFFFFFFFF; + const uint64_t uc = u * c; + const uint64_t ud = u * d; + const uint64_t p0 = ud + k; + const uint64_t p1 = uc + (p0 >> 32); + digits_[i] = (p0 & 0xFFFFFFFF) | (p1 << 32); + k = p1 >> 32; + } + + if (k > 0) + PushBack(k); + + return *this; + } + + BigInteger& operator<<=(size_t shift) { + if (IsZero() || shift == 0) return *this; + + size_t offset = shift / kTypeBit; + size_t interShift = shift % kTypeBit; + RAPIDJSON_ASSERT(count_ + offset <= kCapacity); + + if (interShift == 0) { + std::memmove(digits_ + offset, digits_, count_ * sizeof(Type)); + count_ += offset; + } + else { + digits_[count_] = 0; + for (size_t i = count_; i > 0; i--) + digits_[i + offset] = (digits_[i] << interShift) | (digits_[i - 1] >> (kTypeBit - interShift)); + digits_[offset] = digits_[0] << interShift; + count_ += offset; + if (digits_[count_]) + count_++; + } + + std::memset(digits_, 0, offset * sizeof(Type)); + + return *this; + } + + bool operator==(const BigInteger& rhs) const { + return count_ == rhs.count_ && std::memcmp(digits_, rhs.digits_, count_ * sizeof(Type)) == 0; + } + + bool operator==(const Type rhs) const { + return count_ == 1 && digits_[0] == rhs; + } + + BigInteger& MultiplyPow5(unsigned exp) { + static const uint32_t kPow5[12] = { + 5, + 5 * 5, + 5 * 5 * 5, + 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 + }; + if (exp == 0) return *this; + for (; exp >= 27; exp -= 27) *this *= RAPIDJSON_UINT64_C2(0X6765C793, 0XFA10079D); // 5^27 + for (; exp >= 13; exp -= 13) *this *= static_cast<uint32_t>(1220703125u); // 5^13 + if (exp > 0) *this *= kPow5[exp - 1]; + return *this; + } + + // Compute absolute difference of this and rhs. + // Assume this != rhs + bool Difference(const BigInteger& rhs, BigInteger* out) const { + int cmp = Compare(rhs); + RAPIDJSON_ASSERT(cmp != 0); + const BigInteger *a, *b; // Makes a > b + bool ret; + if (cmp < 0) { a = &rhs; b = this; ret = true; } + else { a = this; b = &rhs; ret = false; } + + Type borrow = 0; + for (size_t i = 0; i < a->count_; i++) { + Type d = a->digits_[i] - borrow; + if (i < b->count_) + d -= b->digits_[i]; + borrow = (d > a->digits_[i]) ? 1 : 0; + out->digits_[i] = d; + if (d != 0) + out->count_ = i + 1; + } + + return ret; + } + + int Compare(const BigInteger& rhs) const { + if (count_ != rhs.count_) + return count_ < rhs.count_ ? -1 : 1; + + for (size_t i = count_; i-- > 0;) + if (digits_[i] != rhs.digits_[i]) + return digits_[i] < rhs.digits_[i] ? -1 : 1; + + return 0; + } + + size_t GetCount() const { return count_; } + Type GetDigit(size_t index) const { RAPIDJSON_ASSERT(index < count_); return digits_[index]; } + bool IsZero() const { return count_ == 1 && digits_[0] == 0; } + +private: + void AppendDecimal64(const char* begin, const char* end) { + uint64_t u = ParseUint64(begin, end); + if (IsZero()) + *this = u; + else { + unsigned exp = static_cast<unsigned>(end - begin); + (MultiplyPow5(exp) <<= exp) += u; // *this = *this * 10^exp + u + } + } + + void PushBack(Type digit) { + RAPIDJSON_ASSERT(count_ < kCapacity); + digits_[count_++] = digit; + } + + static uint64_t ParseUint64(const char* begin, const char* end) { + uint64_t r = 0; + for (const char* p = begin; p != end; ++p) { + RAPIDJSON_ASSERT(*p >= '0' && *p <= '9'); + r = r * 10u + static_cast<unsigned>(*p - '0'); + } + return r; + } + + // Assume a * b + k < 2^128 + static uint64_t MulAdd64(uint64_t a, uint64_t b, uint64_t k, uint64_t* outHigh) { +#if defined(_MSC_VER) && defined(_M_AMD64) + uint64_t low = _umul128(a, b, outHigh) + k; + if (low < k) + (*outHigh)++; + return low; +#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) + __extension__ typedef unsigned __int128 uint128; + uint128 p = static_cast<uint128>(a) * static_cast<uint128>(b); + p += k; + *outHigh = static_cast<uint64_t>(p >> 64); + return static_cast<uint64_t>(p); +#else + const uint64_t a0 = a & 0xFFFFFFFF, a1 = a >> 32, b0 = b & 0xFFFFFFFF, b1 = b >> 32; + uint64_t x0 = a0 * b0, x1 = a0 * b1, x2 = a1 * b0, x3 = a1 * b1; + x1 += (x0 >> 32); // can't give carry + x1 += x2; + if (x1 < x2) + x3 += (static_cast<uint64_t>(1) << 32); + uint64_t lo = (x1 << 32) + (x0 & 0xFFFFFFFF); + uint64_t hi = x3 + (x1 >> 32); + + lo += k; + if (lo < k) + hi++; + *outHigh = hi; + return lo; +#endif + } + + static const size_t kBitCount = 3328; // 64bit * 54 > 10^1000 + static const size_t kCapacity = kBitCount / sizeof(Type); + static const size_t kTypeBit = sizeof(Type) * 8; + + Type digits_[kCapacity]; + size_t count_; +}; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_BIGINTEGER_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/clzll.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/clzll.h new file mode 100644 index 00000000..8fc5118a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/clzll.h @@ -0,0 +1,71 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_CLZLL_H_ +#define RAPIDJSON_CLZLL_H_ + +#include "../rapidjson.h" + +#if defined(_MSC_VER) && !defined(UNDER_CE) +#include <intrin.h> +#if defined(_WIN64) +#pragma intrinsic(_BitScanReverse64) +#else +#pragma intrinsic(_BitScanReverse) +#endif +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +inline uint32_t clzll(uint64_t x) { + // Passing 0 to __builtin_clzll is UB in GCC and results in an + // infinite loop in the software implementation. + RAPIDJSON_ASSERT(x != 0); + +#if defined(_MSC_VER) && !defined(UNDER_CE) + unsigned long r = 0; +#if defined(_WIN64) + _BitScanReverse64(&r, x); +#else + // Scan the high 32 bits. + if (_BitScanReverse(&r, static_cast<uint32_t>(x >> 32))) + return 63 - (r + 32); + + // Scan the low 32 bits. + _BitScanReverse(&r, static_cast<uint32_t>(x & 0xFFFFFFFF)); +#endif // _WIN64 + + return 63 - r; +#elif (defined(__GNUC__) && __GNUC__ >= 4) || RAPIDJSON_HAS_BUILTIN(__builtin_clzll) + // __builtin_clzll wrapper + return static_cast<uint32_t>(__builtin_clzll(x)); +#else + // naive version + uint32_t r = 0; + while (!(x & (static_cast<uint64_t>(1) << 63))) { + x <<= 1; + ++r; + } + + return r; +#endif // _MSC_VER +} + +#define RAPIDJSON_CLZLL RAPIDJSON_NAMESPACE::internal::clzll + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_CLZLL_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/diyfp.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/diyfp.h new file mode 100644 index 00000000..a40797ec --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/diyfp.h @@ -0,0 +1,257 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// This is a C++ header-only implementation of Grisu2 algorithm from the publication: +// Loitsch, Florian. "Printing floating-point numbers quickly and accurately with +// integers." ACM Sigplan Notices 45.6 (2010): 233-243. + +#ifndef RAPIDJSON_DIYFP_H_ +#define RAPIDJSON_DIYFP_H_ + +#include "../rapidjson.h" +#include "clzll.h" +#include <limits> + +#if defined(_MSC_VER) && defined(_M_AMD64) && !defined(__INTEL_COMPILER) +#include <intrin.h> +#pragma intrinsic(_umul128) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +struct DiyFp { + DiyFp() : f(), e() {} + + DiyFp(uint64_t fp, int exp) : f(fp), e(exp) {} + + explicit DiyFp(double d) { + union { + double d; + uint64_t u64; + } u = { d }; + + int biased_e = static_cast<int>((u.u64 & kDpExponentMask) >> kDpSignificandSize); + uint64_t significand = (u.u64 & kDpSignificandMask); + if (biased_e != 0) { + f = significand + kDpHiddenBit; + e = biased_e - kDpExponentBias; + } + else { + f = significand; + e = kDpMinExponent + 1; + } + } + + DiyFp operator-(const DiyFp& rhs) const { + return DiyFp(f - rhs.f, e); + } + + DiyFp operator*(const DiyFp& rhs) const { +#if defined(_MSC_VER) && defined(_M_AMD64) + uint64_t h; + uint64_t l = _umul128(f, rhs.f, &h); + if (l & (uint64_t(1) << 63)) // rounding + h++; + return DiyFp(h, e + rhs.e + 64); +#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) + __extension__ typedef unsigned __int128 uint128; + uint128 p = static_cast<uint128>(f) * static_cast<uint128>(rhs.f); + uint64_t h = static_cast<uint64_t>(p >> 64); + uint64_t l = static_cast<uint64_t>(p); + if (l & (uint64_t(1) << 63)) // rounding + h++; + return DiyFp(h, e + rhs.e + 64); +#else + const uint64_t M32 = 0xFFFFFFFF; + const uint64_t a = f >> 32; + const uint64_t b = f & M32; + const uint64_t c = rhs.f >> 32; + const uint64_t d = rhs.f & M32; + const uint64_t ac = a * c; + const uint64_t bc = b * c; + const uint64_t ad = a * d; + const uint64_t bd = b * d; + uint64_t tmp = (bd >> 32) + (ad & M32) + (bc & M32); + tmp += 1U << 31; /// mult_round + return DiyFp(ac + (ad >> 32) + (bc >> 32) + (tmp >> 32), e + rhs.e + 64); +#endif + } + + DiyFp Normalize() const { + int s = static_cast<int>(clzll(f)); + return DiyFp(f << s, e - s); + } + + DiyFp NormalizeBoundary() const { + DiyFp res = *this; + while (!(res.f & (kDpHiddenBit << 1))) { + res.f <<= 1; + res.e--; + } + res.f <<= (kDiySignificandSize - kDpSignificandSize - 2); + res.e = res.e - (kDiySignificandSize - kDpSignificandSize - 2); + return res; + } + + void NormalizedBoundaries(DiyFp* minus, DiyFp* plus) const { + DiyFp pl = DiyFp((f << 1) + 1, e - 1).NormalizeBoundary(); + DiyFp mi = (f == kDpHiddenBit) ? DiyFp((f << 2) - 1, e - 2) : DiyFp((f << 1) - 1, e - 1); + mi.f <<= mi.e - pl.e; + mi.e = pl.e; + *plus = pl; + *minus = mi; + } + + double ToDouble() const { + union { + double d; + uint64_t u64; + }u; + RAPIDJSON_ASSERT(f <= kDpHiddenBit + kDpSignificandMask); + if (e < kDpDenormalExponent) { + // Underflow. + return 0.0; + } + if (e >= kDpMaxExponent) { + // Overflow. + return std::numeric_limits<double>::infinity(); + } + const uint64_t be = (e == kDpDenormalExponent && (f & kDpHiddenBit) == 0) ? 0 : + static_cast<uint64_t>(e + kDpExponentBias); + u.u64 = (f & kDpSignificandMask) | (be << kDpSignificandSize); + return u.d; + } + + static const int kDiySignificandSize = 64; + static const int kDpSignificandSize = 52; + static const int kDpExponentBias = 0x3FF + kDpSignificandSize; + static const int kDpMaxExponent = 0x7FF - kDpExponentBias; + static const int kDpMinExponent = -kDpExponentBias; + static const int kDpDenormalExponent = -kDpExponentBias + 1; + static const uint64_t kDpExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000); + static const uint64_t kDpSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); + static const uint64_t kDpHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000); + + uint64_t f; + int e; +}; + +inline DiyFp GetCachedPowerByIndex(size_t index) { + // 10^-348, 10^-340, ..., 10^340 + static const uint64_t kCachedPowers_F[] = { + RAPIDJSON_UINT64_C2(0xfa8fd5a0, 0x081c0288), RAPIDJSON_UINT64_C2(0xbaaee17f, 0xa23ebf76), + RAPIDJSON_UINT64_C2(0x8b16fb20, 0x3055ac76), RAPIDJSON_UINT64_C2(0xcf42894a, 0x5dce35ea), + RAPIDJSON_UINT64_C2(0x9a6bb0aa, 0x55653b2d), RAPIDJSON_UINT64_C2(0xe61acf03, 0x3d1a45df), + RAPIDJSON_UINT64_C2(0xab70fe17, 0xc79ac6ca), RAPIDJSON_UINT64_C2(0xff77b1fc, 0xbebcdc4f), + RAPIDJSON_UINT64_C2(0xbe5691ef, 0x416bd60c), RAPIDJSON_UINT64_C2(0x8dd01fad, 0x907ffc3c), + RAPIDJSON_UINT64_C2(0xd3515c28, 0x31559a83), RAPIDJSON_UINT64_C2(0x9d71ac8f, 0xada6c9b5), + RAPIDJSON_UINT64_C2(0xea9c2277, 0x23ee8bcb), RAPIDJSON_UINT64_C2(0xaecc4991, 0x4078536d), + RAPIDJSON_UINT64_C2(0x823c1279, 0x5db6ce57), RAPIDJSON_UINT64_C2(0xc2109436, 0x4dfb5637), + RAPIDJSON_UINT64_C2(0x9096ea6f, 0x3848984f), RAPIDJSON_UINT64_C2(0xd77485cb, 0x25823ac7), + RAPIDJSON_UINT64_C2(0xa086cfcd, 0x97bf97f4), RAPIDJSON_UINT64_C2(0xef340a98, 0x172aace5), + RAPIDJSON_UINT64_C2(0xb23867fb, 0x2a35b28e), RAPIDJSON_UINT64_C2(0x84c8d4df, 0xd2c63f3b), + RAPIDJSON_UINT64_C2(0xc5dd4427, 0x1ad3cdba), RAPIDJSON_UINT64_C2(0x936b9fce, 0xbb25c996), + RAPIDJSON_UINT64_C2(0xdbac6c24, 0x7d62a584), RAPIDJSON_UINT64_C2(0xa3ab6658, 0x0d5fdaf6), + RAPIDJSON_UINT64_C2(0xf3e2f893, 0xdec3f126), RAPIDJSON_UINT64_C2(0xb5b5ada8, 0xaaff80b8), + RAPIDJSON_UINT64_C2(0x87625f05, 0x6c7c4a8b), RAPIDJSON_UINT64_C2(0xc9bcff60, 0x34c13053), + RAPIDJSON_UINT64_C2(0x964e858c, 0x91ba2655), RAPIDJSON_UINT64_C2(0xdff97724, 0x70297ebd), + RAPIDJSON_UINT64_C2(0xa6dfbd9f, 0xb8e5b88f), RAPIDJSON_UINT64_C2(0xf8a95fcf, 0x88747d94), + RAPIDJSON_UINT64_C2(0xb9447093, 0x8fa89bcf), RAPIDJSON_UINT64_C2(0x8a08f0f8, 0xbf0f156b), + RAPIDJSON_UINT64_C2(0xcdb02555, 0x653131b6), RAPIDJSON_UINT64_C2(0x993fe2c6, 0xd07b7fac), + RAPIDJSON_UINT64_C2(0xe45c10c4, 0x2a2b3b06), RAPIDJSON_UINT64_C2(0xaa242499, 0x697392d3), + RAPIDJSON_UINT64_C2(0xfd87b5f2, 0x8300ca0e), RAPIDJSON_UINT64_C2(0xbce50864, 0x92111aeb), + RAPIDJSON_UINT64_C2(0x8cbccc09, 0x6f5088cc), RAPIDJSON_UINT64_C2(0xd1b71758, 0xe219652c), + RAPIDJSON_UINT64_C2(0x9c400000, 0x00000000), RAPIDJSON_UINT64_C2(0xe8d4a510, 0x00000000), + RAPIDJSON_UINT64_C2(0xad78ebc5, 0xac620000), RAPIDJSON_UINT64_C2(0x813f3978, 0xf8940984), + RAPIDJSON_UINT64_C2(0xc097ce7b, 0xc90715b3), RAPIDJSON_UINT64_C2(0x8f7e32ce, 0x7bea5c70), + RAPIDJSON_UINT64_C2(0xd5d238a4, 0xabe98068), RAPIDJSON_UINT64_C2(0x9f4f2726, 0x179a2245), + RAPIDJSON_UINT64_C2(0xed63a231, 0xd4c4fb27), RAPIDJSON_UINT64_C2(0xb0de6538, 0x8cc8ada8), + RAPIDJSON_UINT64_C2(0x83c7088e, 0x1aab65db), RAPIDJSON_UINT64_C2(0xc45d1df9, 0x42711d9a), + RAPIDJSON_UINT64_C2(0x924d692c, 0xa61be758), RAPIDJSON_UINT64_C2(0xda01ee64, 0x1a708dea), + RAPIDJSON_UINT64_C2(0xa26da399, 0x9aef774a), RAPIDJSON_UINT64_C2(0xf209787b, 0xb47d6b85), + RAPIDJSON_UINT64_C2(0xb454e4a1, 0x79dd1877), RAPIDJSON_UINT64_C2(0x865b8692, 0x5b9bc5c2), + RAPIDJSON_UINT64_C2(0xc83553c5, 0xc8965d3d), RAPIDJSON_UINT64_C2(0x952ab45c, 0xfa97a0b3), + RAPIDJSON_UINT64_C2(0xde469fbd, 0x99a05fe3), RAPIDJSON_UINT64_C2(0xa59bc234, 0xdb398c25), + RAPIDJSON_UINT64_C2(0xf6c69a72, 0xa3989f5c), RAPIDJSON_UINT64_C2(0xb7dcbf53, 0x54e9bece), + RAPIDJSON_UINT64_C2(0x88fcf317, 0xf22241e2), RAPIDJSON_UINT64_C2(0xcc20ce9b, 0xd35c78a5), + RAPIDJSON_UINT64_C2(0x98165af3, 0x7b2153df), RAPIDJSON_UINT64_C2(0xe2a0b5dc, 0x971f303a), + RAPIDJSON_UINT64_C2(0xa8d9d153, 0x5ce3b396), RAPIDJSON_UINT64_C2(0xfb9b7cd9, 0xa4a7443c), + RAPIDJSON_UINT64_C2(0xbb764c4c, 0xa7a44410), RAPIDJSON_UINT64_C2(0x8bab8eef, 0xb6409c1a), + RAPIDJSON_UINT64_C2(0xd01fef10, 0xa657842c), RAPIDJSON_UINT64_C2(0x9b10a4e5, 0xe9913129), + RAPIDJSON_UINT64_C2(0xe7109bfb, 0xa19c0c9d), RAPIDJSON_UINT64_C2(0xac2820d9, 0x623bf429), + RAPIDJSON_UINT64_C2(0x80444b5e, 0x7aa7cf85), RAPIDJSON_UINT64_C2(0xbf21e440, 0x03acdd2d), + RAPIDJSON_UINT64_C2(0x8e679c2f, 0x5e44ff8f), RAPIDJSON_UINT64_C2(0xd433179d, 0x9c8cb841), + RAPIDJSON_UINT64_C2(0x9e19db92, 0xb4e31ba9), RAPIDJSON_UINT64_C2(0xeb96bf6e, 0xbadf77d9), + RAPIDJSON_UINT64_C2(0xaf87023b, 0x9bf0ee6b) + }; + static const int16_t kCachedPowers_E[] = { + -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, + -954, -927, -901, -874, -847, -821, -794, -768, -741, -715, + -688, -661, -635, -608, -582, -555, -529, -502, -475, -449, + -422, -396, -369, -343, -316, -289, -263, -236, -210, -183, + -157, -130, -103, -77, -50, -24, 3, 30, 56, 83, + 109, 136, 162, 189, 216, 242, 269, 295, 322, 348, + 375, 402, 428, 455, 481, 508, 534, 561, 588, 614, + 641, 667, 694, 720, 747, 774, 800, 827, 853, 880, + 907, 933, 960, 986, 1013, 1039, 1066 + }; + RAPIDJSON_ASSERT(index < 87); + return DiyFp(kCachedPowers_F[index], kCachedPowers_E[index]); +} + +inline DiyFp GetCachedPower(int e, int* K) { + + //int k = static_cast<int>(ceil((-61 - e) * 0.30102999566398114)) + 374; + double dk = (-61 - e) * 0.30102999566398114 + 347; // dk must be positive, so can do ceiling in positive + int k = static_cast<int>(dk); + if (dk - k > 0.0) + k++; + + unsigned index = static_cast<unsigned>((k >> 3) + 1); + *K = -(-348 + static_cast<int>(index << 3)); // decimal exponent no need lookup table + + return GetCachedPowerByIndex(index); +} + +inline DiyFp GetCachedPower10(int exp, int *outExp) { + RAPIDJSON_ASSERT(exp >= -348); + unsigned index = static_cast<unsigned>(exp + 348) / 8u; + *outExp = -348 + static_cast<int>(index) * 8; + return GetCachedPowerByIndex(index); +} + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +RAPIDJSON_DIAG_OFF(padded) +#endif + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_DIYFP_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/dtoa.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/dtoa.h new file mode 100644 index 00000000..621402fd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/dtoa.h @@ -0,0 +1,245 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// This is a C++ header-only implementation of Grisu2 algorithm from the publication: +// Loitsch, Florian. "Printing floating-point numbers quickly and accurately with +// integers." ACM Sigplan Notices 45.6 (2010): 233-243. + +#ifndef RAPIDJSON_DTOA_ +#define RAPIDJSON_DTOA_ + +#include "itoa.h" // GetDigitsLut() +#include "diyfp.h" +#include "ieee754.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +RAPIDJSON_DIAG_OFF(array-bounds) // some gcc versions generate wrong warnings https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59124 +#endif + +inline void GrisuRound(char* buffer, int len, uint64_t delta, uint64_t rest, uint64_t ten_kappa, uint64_t wp_w) { + while (rest < wp_w && delta - rest >= ten_kappa && + (rest + ten_kappa < wp_w || /// closer + wp_w - rest > rest + ten_kappa - wp_w)) { + buffer[len - 1]--; + rest += ten_kappa; + } +} + +inline int CountDecimalDigit32(uint32_t n) { + // Simple pure C++ implementation was faster than __builtin_clz version in this situation. + if (n < 10) return 1; + if (n < 100) return 2; + if (n < 1000) return 3; + if (n < 10000) return 4; + if (n < 100000) return 5; + if (n < 1000000) return 6; + if (n < 10000000) return 7; + if (n < 100000000) return 8; + // Will not reach 10 digits in DigitGen() + //if (n < 1000000000) return 9; + //return 10; + return 9; +} + +inline void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, char* buffer, int* len, int* K) { + static const uint32_t kPow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; + const DiyFp one(uint64_t(1) << -Mp.e, Mp.e); + const DiyFp wp_w = Mp - W; + uint32_t p1 = static_cast<uint32_t>(Mp.f >> -one.e); + uint64_t p2 = Mp.f & (one.f - 1); + int kappa = CountDecimalDigit32(p1); // kappa in [0, 9] + *len = 0; + + while (kappa > 0) { + uint32_t d = 0; + switch (kappa) { + case 9: d = p1 / 100000000; p1 %= 100000000; break; + case 8: d = p1 / 10000000; p1 %= 10000000; break; + case 7: d = p1 / 1000000; p1 %= 1000000; break; + case 6: d = p1 / 100000; p1 %= 100000; break; + case 5: d = p1 / 10000; p1 %= 10000; break; + case 4: d = p1 / 1000; p1 %= 1000; break; + case 3: d = p1 / 100; p1 %= 100; break; + case 2: d = p1 / 10; p1 %= 10; break; + case 1: d = p1; p1 = 0; break; + default:; + } + if (d || *len) + buffer[(*len)++] = static_cast<char>('0' + static_cast<char>(d)); + kappa--; + uint64_t tmp = (static_cast<uint64_t>(p1) << -one.e) + p2; + if (tmp <= delta) { + *K += kappa; + GrisuRound(buffer, *len, delta, tmp, static_cast<uint64_t>(kPow10[kappa]) << -one.e, wp_w.f); + return; + } + } + + // kappa = 0 + for (;;) { + p2 *= 10; + delta *= 10; + char d = static_cast<char>(p2 >> -one.e); + if (d || *len) + buffer[(*len)++] = static_cast<char>('0' + d); + p2 &= one.f - 1; + kappa--; + if (p2 < delta) { + *K += kappa; + int index = -kappa; + GrisuRound(buffer, *len, delta, p2, one.f, wp_w.f * (index < 9 ? kPow10[index] : 0)); + return; + } + } +} + +inline void Grisu2(double value, char* buffer, int* length, int* K) { + const DiyFp v(value); + DiyFp w_m, w_p; + v.NormalizedBoundaries(&w_m, &w_p); + + const DiyFp c_mk = GetCachedPower(w_p.e, K); + const DiyFp W = v.Normalize() * c_mk; + DiyFp Wp = w_p * c_mk; + DiyFp Wm = w_m * c_mk; + Wm.f++; + Wp.f--; + DigitGen(W, Wp, Wp.f - Wm.f, buffer, length, K); +} + +inline char* WriteExponent(int K, char* buffer) { + if (K < 0) { + *buffer++ = '-'; + K = -K; + } + + if (K >= 100) { + *buffer++ = static_cast<char>('0' + static_cast<char>(K / 100)); + K %= 100; + const char* d = GetDigitsLut() + K * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } + else if (K >= 10) { + const char* d = GetDigitsLut() + K * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } + else + *buffer++ = static_cast<char>('0' + static_cast<char>(K)); + + return buffer; +} + +inline char* Prettify(char* buffer, int length, int k, int maxDecimalPlaces) { + const int kk = length + k; // 10^(kk-1) <= v < 10^kk + + if (0 <= k && kk <= 21) { + // 1234e7 -> 12340000000 + for (int i = length; i < kk; i++) + buffer[i] = '0'; + buffer[kk] = '.'; + buffer[kk + 1] = '0'; + return &buffer[kk + 2]; + } + else if (0 < kk && kk <= 21) { + // 1234e-2 -> 12.34 + std::memmove(&buffer[kk + 1], &buffer[kk], static_cast<size_t>(length - kk)); + buffer[kk] = '.'; + if (0 > k + maxDecimalPlaces) { + // When maxDecimalPlaces = 2, 1.2345 -> 1.23, 1.102 -> 1.1 + // Remove extra trailing zeros (at least one) after truncation. + for (int i = kk + maxDecimalPlaces; i > kk + 1; i--) + if (buffer[i] != '0') + return &buffer[i + 1]; + return &buffer[kk + 2]; // Reserve one zero + } + else + return &buffer[length + 1]; + } + else if (-6 < kk && kk <= 0) { + // 1234e-6 -> 0.001234 + const int offset = 2 - kk; + std::memmove(&buffer[offset], &buffer[0], static_cast<size_t>(length)); + buffer[0] = '0'; + buffer[1] = '.'; + for (int i = 2; i < offset; i++) + buffer[i] = '0'; + if (length - kk > maxDecimalPlaces) { + // When maxDecimalPlaces = 2, 0.123 -> 0.12, 0.102 -> 0.1 + // Remove extra trailing zeros (at least one) after truncation. + for (int i = maxDecimalPlaces + 1; i > 2; i--) + if (buffer[i] != '0') + return &buffer[i + 1]; + return &buffer[3]; // Reserve one zero + } + else + return &buffer[length + offset]; + } + else if (kk < -maxDecimalPlaces) { + // Truncate to zero + buffer[0] = '0'; + buffer[1] = '.'; + buffer[2] = '0'; + return &buffer[3]; + } + else if (length == 1) { + // 1e30 + buffer[1] = 'e'; + return WriteExponent(kk - 1, &buffer[2]); + } + else { + // 1234e30 -> 1.234e33 + std::memmove(&buffer[2], &buffer[1], static_cast<size_t>(length - 1)); + buffer[1] = '.'; + buffer[length + 1] = 'e'; + return WriteExponent(kk - 1, &buffer[0 + length + 2]); + } +} + +inline char* dtoa(double value, char* buffer, int maxDecimalPlaces = 324) { + RAPIDJSON_ASSERT(maxDecimalPlaces >= 1); + Double d(value); + if (d.IsZero()) { + if (d.Sign()) + *buffer++ = '-'; // -0.0, Issue #289 + buffer[0] = '0'; + buffer[1] = '.'; + buffer[2] = '0'; + return &buffer[3]; + } + else { + if (value < 0) { + *buffer++ = '-'; + value = -value; + } + int length, K; + Grisu2(value, buffer, &length, &K); + return Prettify(buffer, length, K, maxDecimalPlaces); + } +} + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_DTOA_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/ieee754.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/ieee754.h new file mode 100644 index 00000000..68c9e966 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/ieee754.h @@ -0,0 +1,78 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_IEEE754_ +#define RAPIDJSON_IEEE754_ + +#include "../rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +class Double { +public: + Double() {} + Double(double d) : d_(d) {} + Double(uint64_t u) : u_(u) {} + + double Value() const { return d_; } + uint64_t Uint64Value() const { return u_; } + + double NextPositiveDouble() const { + RAPIDJSON_ASSERT(!Sign()); + return Double(u_ + 1).Value(); + } + + bool Sign() const { return (u_ & kSignMask) != 0; } + uint64_t Significand() const { return u_ & kSignificandMask; } + int Exponent() const { return static_cast<int>(((u_ & kExponentMask) >> kSignificandSize) - kExponentBias); } + + bool IsNan() const { return (u_ & kExponentMask) == kExponentMask && Significand() != 0; } + bool IsInf() const { return (u_ & kExponentMask) == kExponentMask && Significand() == 0; } + bool IsNanOrInf() const { return (u_ & kExponentMask) == kExponentMask; } + bool IsNormal() const { return (u_ & kExponentMask) != 0 || Significand() == 0; } + bool IsZero() const { return (u_ & (kExponentMask | kSignificandMask)) == 0; } + + uint64_t IntegerSignificand() const { return IsNormal() ? Significand() | kHiddenBit : Significand(); } + int IntegerExponent() const { return (IsNormal() ? Exponent() : kDenormalExponent) - kSignificandSize; } + uint64_t ToBias() const { return (u_ & kSignMask) ? ~u_ + 1 : u_ | kSignMask; } + + static int EffectiveSignificandSize(int order) { + if (order >= -1021) + return 53; + else if (order <= -1074) + return 0; + else + return order + 1074; + } + +private: + static const int kSignificandSize = 52; + static const int kExponentBias = 0x3FF; + static const int kDenormalExponent = 1 - kExponentBias; + static const uint64_t kSignMask = RAPIDJSON_UINT64_C2(0x80000000, 0x00000000); + static const uint64_t kExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000); + static const uint64_t kSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); + static const uint64_t kHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000); + + union { + double d_; + uint64_t u_; + }; +}; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_IEEE754_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/itoa.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/itoa.h new file mode 100644 index 00000000..9fe8c932 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/itoa.h @@ -0,0 +1,308 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ITOA_ +#define RAPIDJSON_ITOA_ + +#include "../rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +inline const char* GetDigitsLut() { + static const char cDigitsLut[200] = { + '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9', + '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9', + '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9', + '3','0','3','1','3','2','3','3','3','4','3','5','3','6','3','7','3','8','3','9', + '4','0','4','1','4','2','4','3','4','4','4','5','4','6','4','7','4','8','4','9', + '5','0','5','1','5','2','5','3','5','4','5','5','5','6','5','7','5','8','5','9', + '6','0','6','1','6','2','6','3','6','4','6','5','6','6','6','7','6','8','6','9', + '7','0','7','1','7','2','7','3','7','4','7','5','7','6','7','7','7','8','7','9', + '8','0','8','1','8','2','8','3','8','4','8','5','8','6','8','7','8','8','8','9', + '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9' + }; + return cDigitsLut; +} + +inline char* u32toa(uint32_t value, char* buffer) { + RAPIDJSON_ASSERT(buffer != 0); + + const char* cDigitsLut = GetDigitsLut(); + + if (value < 10000) { + const uint32_t d1 = (value / 100) << 1; + const uint32_t d2 = (value % 100) << 1; + + if (value >= 1000) + *buffer++ = cDigitsLut[d1]; + if (value >= 100) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 10) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + } + else if (value < 100000000) { + // value = bbbbcccc + const uint32_t b = value / 10000; + const uint32_t c = value % 10000; + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + if (value >= 10000000) + *buffer++ = cDigitsLut[d1]; + if (value >= 1000000) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 100000) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + else { + // value = aabbbbcccc in decimal + + const uint32_t a = value / 100000000; // 1 to 42 + value %= 100000000; + + if (a >= 10) { + const unsigned i = a << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else + *buffer++ = static_cast<char>('0' + static_cast<char>(a)); + + const uint32_t b = value / 10000; // 0 to 9999 + const uint32_t c = value % 10000; // 0 to 9999 + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + *buffer++ = cDigitsLut[d1]; + *buffer++ = cDigitsLut[d1 + 1]; + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + return buffer; +} + +inline char* i32toa(int32_t value, char* buffer) { + RAPIDJSON_ASSERT(buffer != 0); + uint32_t u = static_cast<uint32_t>(value); + if (value < 0) { + *buffer++ = '-'; + u = ~u + 1; + } + + return u32toa(u, buffer); +} + +inline char* u64toa(uint64_t value, char* buffer) { + RAPIDJSON_ASSERT(buffer != 0); + const char* cDigitsLut = GetDigitsLut(); + const uint64_t kTen8 = 100000000; + const uint64_t kTen9 = kTen8 * 10; + const uint64_t kTen10 = kTen8 * 100; + const uint64_t kTen11 = kTen8 * 1000; + const uint64_t kTen12 = kTen8 * 10000; + const uint64_t kTen13 = kTen8 * 100000; + const uint64_t kTen14 = kTen8 * 1000000; + const uint64_t kTen15 = kTen8 * 10000000; + const uint64_t kTen16 = kTen8 * kTen8; + + if (value < kTen8) { + uint32_t v = static_cast<uint32_t>(value); + if (v < 10000) { + const uint32_t d1 = (v / 100) << 1; + const uint32_t d2 = (v % 100) << 1; + + if (v >= 1000) + *buffer++ = cDigitsLut[d1]; + if (v >= 100) + *buffer++ = cDigitsLut[d1 + 1]; + if (v >= 10) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + } + else { + // value = bbbbcccc + const uint32_t b = v / 10000; + const uint32_t c = v % 10000; + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + if (value >= 10000000) + *buffer++ = cDigitsLut[d1]; + if (value >= 1000000) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 100000) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + } + else if (value < kTen16) { + const uint32_t v0 = static_cast<uint32_t>(value / kTen8); + const uint32_t v1 = static_cast<uint32_t>(value % kTen8); + + const uint32_t b0 = v0 / 10000; + const uint32_t c0 = v0 % 10000; + + const uint32_t d1 = (b0 / 100) << 1; + const uint32_t d2 = (b0 % 100) << 1; + + const uint32_t d3 = (c0 / 100) << 1; + const uint32_t d4 = (c0 % 100) << 1; + + const uint32_t b1 = v1 / 10000; + const uint32_t c1 = v1 % 10000; + + const uint32_t d5 = (b1 / 100) << 1; + const uint32_t d6 = (b1 % 100) << 1; + + const uint32_t d7 = (c1 / 100) << 1; + const uint32_t d8 = (c1 % 100) << 1; + + if (value >= kTen15) + *buffer++ = cDigitsLut[d1]; + if (value >= kTen14) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= kTen13) + *buffer++ = cDigitsLut[d2]; + if (value >= kTen12) + *buffer++ = cDigitsLut[d2 + 1]; + if (value >= kTen11) + *buffer++ = cDigitsLut[d3]; + if (value >= kTen10) + *buffer++ = cDigitsLut[d3 + 1]; + if (value >= kTen9) + *buffer++ = cDigitsLut[d4]; + + *buffer++ = cDigitsLut[d4 + 1]; + *buffer++ = cDigitsLut[d5]; + *buffer++ = cDigitsLut[d5 + 1]; + *buffer++ = cDigitsLut[d6]; + *buffer++ = cDigitsLut[d6 + 1]; + *buffer++ = cDigitsLut[d7]; + *buffer++ = cDigitsLut[d7 + 1]; + *buffer++ = cDigitsLut[d8]; + *buffer++ = cDigitsLut[d8 + 1]; + } + else { + const uint32_t a = static_cast<uint32_t>(value / kTen16); // 1 to 1844 + value %= kTen16; + + if (a < 10) + *buffer++ = static_cast<char>('0' + static_cast<char>(a)); + else if (a < 100) { + const uint32_t i = a << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else if (a < 1000) { + *buffer++ = static_cast<char>('0' + static_cast<char>(a / 100)); + + const uint32_t i = (a % 100) << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else { + const uint32_t i = (a / 100) << 1; + const uint32_t j = (a % 100) << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + *buffer++ = cDigitsLut[j]; + *buffer++ = cDigitsLut[j + 1]; + } + + const uint32_t v0 = static_cast<uint32_t>(value / kTen8); + const uint32_t v1 = static_cast<uint32_t>(value % kTen8); + + const uint32_t b0 = v0 / 10000; + const uint32_t c0 = v0 % 10000; + + const uint32_t d1 = (b0 / 100) << 1; + const uint32_t d2 = (b0 % 100) << 1; + + const uint32_t d3 = (c0 / 100) << 1; + const uint32_t d4 = (c0 % 100) << 1; + + const uint32_t b1 = v1 / 10000; + const uint32_t c1 = v1 % 10000; + + const uint32_t d5 = (b1 / 100) << 1; + const uint32_t d6 = (b1 % 100) << 1; + + const uint32_t d7 = (c1 / 100) << 1; + const uint32_t d8 = (c1 % 100) << 1; + + *buffer++ = cDigitsLut[d1]; + *buffer++ = cDigitsLut[d1 + 1]; + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + *buffer++ = cDigitsLut[d5]; + *buffer++ = cDigitsLut[d5 + 1]; + *buffer++ = cDigitsLut[d6]; + *buffer++ = cDigitsLut[d6 + 1]; + *buffer++ = cDigitsLut[d7]; + *buffer++ = cDigitsLut[d7 + 1]; + *buffer++ = cDigitsLut[d8]; + *buffer++ = cDigitsLut[d8 + 1]; + } + + return buffer; +} + +inline char* i64toa(int64_t value, char* buffer) { + RAPIDJSON_ASSERT(buffer != 0); + uint64_t u = static_cast<uint64_t>(value); + if (value < 0) { + *buffer++ = '-'; + u = ~u + 1; + } + + return u64toa(u, buffer); +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ITOA_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/meta.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/meta.h new file mode 100644 index 00000000..27092dc0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/meta.h @@ -0,0 +1,186 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_META_H_ +#define RAPIDJSON_INTERNAL_META_H_ + +#include "../rapidjson.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(6334) +#endif + +#if RAPIDJSON_HAS_CXX11_TYPETRAITS +#include <type_traits> +#endif + +//@cond RAPIDJSON_INTERNAL +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +// Helper to wrap/convert arbitrary types to void, useful for arbitrary type matching +template <typename T> struct Void { typedef void Type; }; + +/////////////////////////////////////////////////////////////////////////////// +// BoolType, TrueType, FalseType +// +template <bool Cond> struct BoolType { + static const bool Value = Cond; + typedef BoolType Type; +}; +typedef BoolType<true> TrueType; +typedef BoolType<false> FalseType; + + +/////////////////////////////////////////////////////////////////////////////// +// SelectIf, BoolExpr, NotExpr, AndExpr, OrExpr +// + +template <bool C> struct SelectIfImpl { template <typename T1, typename T2> struct Apply { typedef T1 Type; }; }; +template <> struct SelectIfImpl<false> { template <typename T1, typename T2> struct Apply { typedef T2 Type; }; }; +template <bool C, typename T1, typename T2> struct SelectIfCond : SelectIfImpl<C>::template Apply<T1,T2> {}; +template <typename C, typename T1, typename T2> struct SelectIf : SelectIfCond<C::Value, T1, T2> {}; + +template <bool Cond1, bool Cond2> struct AndExprCond : FalseType {}; +template <> struct AndExprCond<true, true> : TrueType {}; +template <bool Cond1, bool Cond2> struct OrExprCond : TrueType {}; +template <> struct OrExprCond<false, false> : FalseType {}; + +template <typename C> struct BoolExpr : SelectIf<C,TrueType,FalseType>::Type {}; +template <typename C> struct NotExpr : SelectIf<C,FalseType,TrueType>::Type {}; +template <typename C1, typename C2> struct AndExpr : AndExprCond<C1::Value, C2::Value>::Type {}; +template <typename C1, typename C2> struct OrExpr : OrExprCond<C1::Value, C2::Value>::Type {}; + + +/////////////////////////////////////////////////////////////////////////////// +// AddConst, MaybeAddConst, RemoveConst +template <typename T> struct AddConst { typedef const T Type; }; +template <bool Constify, typename T> struct MaybeAddConst : SelectIfCond<Constify, const T, T> {}; +template <typename T> struct RemoveConst { typedef T Type; }; +template <typename T> struct RemoveConst<const T> { typedef T Type; }; + + +/////////////////////////////////////////////////////////////////////////////// +// IsSame, IsConst, IsMoreConst, IsPointer +// +template <typename T, typename U> struct IsSame : FalseType {}; +template <typename T> struct IsSame<T, T> : TrueType {}; + +template <typename T> struct IsConst : FalseType {}; +template <typename T> struct IsConst<const T> : TrueType {}; + +template <typename CT, typename T> +struct IsMoreConst + : AndExpr<IsSame<typename RemoveConst<CT>::Type, typename RemoveConst<T>::Type>, + BoolType<IsConst<CT>::Value >= IsConst<T>::Value> >::Type {}; + +template <typename T> struct IsPointer : FalseType {}; +template <typename T> struct IsPointer<T*> : TrueType {}; + +/////////////////////////////////////////////////////////////////////////////// +// IsBaseOf +// +#if RAPIDJSON_HAS_CXX11_TYPETRAITS + +template <typename B, typename D> struct IsBaseOf + : BoolType< ::std::is_base_of<B,D>::value> {}; + +#else // simplified version adopted from Boost + +template<typename B, typename D> struct IsBaseOfImpl { + RAPIDJSON_STATIC_ASSERT(sizeof(B) != 0); + RAPIDJSON_STATIC_ASSERT(sizeof(D) != 0); + + typedef char (&Yes)[1]; + typedef char (&No) [2]; + + template <typename T> + static Yes Check(const D*, T); + static No Check(const B*, int); + + struct Host { + operator const B*() const; + operator const D*(); + }; + + enum { Value = (sizeof(Check(Host(), 0)) == sizeof(Yes)) }; +}; + +template <typename B, typename D> struct IsBaseOf + : OrExpr<IsSame<B, D>, BoolExpr<IsBaseOfImpl<B, D> > >::Type {}; + +#endif // RAPIDJSON_HAS_CXX11_TYPETRAITS + + +////////////////////////////////////////////////////////////////////////// +// EnableIf / DisableIf +// +template <bool Condition, typename T = void> struct EnableIfCond { typedef T Type; }; +template <typename T> struct EnableIfCond<false, T> { /* empty */ }; + +template <bool Condition, typename T = void> struct DisableIfCond { typedef T Type; }; +template <typename T> struct DisableIfCond<true, T> { /* empty */ }; + +template <typename Condition, typename T = void> +struct EnableIf : EnableIfCond<Condition::Value, T> {}; + +template <typename Condition, typename T = void> +struct DisableIf : DisableIfCond<Condition::Value, T> {}; + +// SFINAE helpers +struct SfinaeTag {}; +template <typename T> struct RemoveSfinaeTag; +template <typename T> struct RemoveSfinaeTag<SfinaeTag&(*)(T)> { typedef T Type; }; + +#define RAPIDJSON_REMOVEFPTR_(type) \ + typename ::RAPIDJSON_NAMESPACE::internal::RemoveSfinaeTag \ + < ::RAPIDJSON_NAMESPACE::internal::SfinaeTag&(*) type>::Type + +#define RAPIDJSON_ENABLEIF(cond) \ + typename ::RAPIDJSON_NAMESPACE::internal::EnableIf \ + <RAPIDJSON_REMOVEFPTR_(cond)>::Type * = NULL + +#define RAPIDJSON_DISABLEIF(cond) \ + typename ::RAPIDJSON_NAMESPACE::internal::DisableIf \ + <RAPIDJSON_REMOVEFPTR_(cond)>::Type * = NULL + +#define RAPIDJSON_ENABLEIF_RETURN(cond,returntype) \ + typename ::RAPIDJSON_NAMESPACE::internal::EnableIf \ + <RAPIDJSON_REMOVEFPTR_(cond), \ + RAPIDJSON_REMOVEFPTR_(returntype)>::Type + +#define RAPIDJSON_DISABLEIF_RETURN(cond,returntype) \ + typename ::RAPIDJSON_NAMESPACE::internal::DisableIf \ + <RAPIDJSON_REMOVEFPTR_(cond), \ + RAPIDJSON_REMOVEFPTR_(returntype)>::Type + +} // namespace internal +RAPIDJSON_NAMESPACE_END +//@endcond + +#if defined(_MSC_VER) && !defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_META_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/pow10.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/pow10.h new file mode 100644 index 00000000..eae1a43e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/pow10.h @@ -0,0 +1,55 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_POW10_ +#define RAPIDJSON_POW10_ + +#include "../rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Computes integer powers of 10 in double (10.0^n). +/*! This function uses lookup table for fast and accurate results. + \param n non-negative exponent. Must <= 308. + \return 10.0^n +*/ +inline double Pow10(int n) { + static const double e[] = { // 1e-0...1e308: 309 * 8 bytes = 2472 bytes + 1e+0, + 1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 1e+7, 1e+8, 1e+9, 1e+10, 1e+11, 1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, 1e+18, 1e+19, 1e+20, + 1e+21, 1e+22, 1e+23, 1e+24, 1e+25, 1e+26, 1e+27, 1e+28, 1e+29, 1e+30, 1e+31, 1e+32, 1e+33, 1e+34, 1e+35, 1e+36, 1e+37, 1e+38, 1e+39, 1e+40, + 1e+41, 1e+42, 1e+43, 1e+44, 1e+45, 1e+46, 1e+47, 1e+48, 1e+49, 1e+50, 1e+51, 1e+52, 1e+53, 1e+54, 1e+55, 1e+56, 1e+57, 1e+58, 1e+59, 1e+60, + 1e+61, 1e+62, 1e+63, 1e+64, 1e+65, 1e+66, 1e+67, 1e+68, 1e+69, 1e+70, 1e+71, 1e+72, 1e+73, 1e+74, 1e+75, 1e+76, 1e+77, 1e+78, 1e+79, 1e+80, + 1e+81, 1e+82, 1e+83, 1e+84, 1e+85, 1e+86, 1e+87, 1e+88, 1e+89, 1e+90, 1e+91, 1e+92, 1e+93, 1e+94, 1e+95, 1e+96, 1e+97, 1e+98, 1e+99, 1e+100, + 1e+101,1e+102,1e+103,1e+104,1e+105,1e+106,1e+107,1e+108,1e+109,1e+110,1e+111,1e+112,1e+113,1e+114,1e+115,1e+116,1e+117,1e+118,1e+119,1e+120, + 1e+121,1e+122,1e+123,1e+124,1e+125,1e+126,1e+127,1e+128,1e+129,1e+130,1e+131,1e+132,1e+133,1e+134,1e+135,1e+136,1e+137,1e+138,1e+139,1e+140, + 1e+141,1e+142,1e+143,1e+144,1e+145,1e+146,1e+147,1e+148,1e+149,1e+150,1e+151,1e+152,1e+153,1e+154,1e+155,1e+156,1e+157,1e+158,1e+159,1e+160, + 1e+161,1e+162,1e+163,1e+164,1e+165,1e+166,1e+167,1e+168,1e+169,1e+170,1e+171,1e+172,1e+173,1e+174,1e+175,1e+176,1e+177,1e+178,1e+179,1e+180, + 1e+181,1e+182,1e+183,1e+184,1e+185,1e+186,1e+187,1e+188,1e+189,1e+190,1e+191,1e+192,1e+193,1e+194,1e+195,1e+196,1e+197,1e+198,1e+199,1e+200, + 1e+201,1e+202,1e+203,1e+204,1e+205,1e+206,1e+207,1e+208,1e+209,1e+210,1e+211,1e+212,1e+213,1e+214,1e+215,1e+216,1e+217,1e+218,1e+219,1e+220, + 1e+221,1e+222,1e+223,1e+224,1e+225,1e+226,1e+227,1e+228,1e+229,1e+230,1e+231,1e+232,1e+233,1e+234,1e+235,1e+236,1e+237,1e+238,1e+239,1e+240, + 1e+241,1e+242,1e+243,1e+244,1e+245,1e+246,1e+247,1e+248,1e+249,1e+250,1e+251,1e+252,1e+253,1e+254,1e+255,1e+256,1e+257,1e+258,1e+259,1e+260, + 1e+261,1e+262,1e+263,1e+264,1e+265,1e+266,1e+267,1e+268,1e+269,1e+270,1e+271,1e+272,1e+273,1e+274,1e+275,1e+276,1e+277,1e+278,1e+279,1e+280, + 1e+281,1e+282,1e+283,1e+284,1e+285,1e+286,1e+287,1e+288,1e+289,1e+290,1e+291,1e+292,1e+293,1e+294,1e+295,1e+296,1e+297,1e+298,1e+299,1e+300, + 1e+301,1e+302,1e+303,1e+304,1e+305,1e+306,1e+307,1e+308 + }; + RAPIDJSON_ASSERT(n >= 0 && n <= 308); + return e[n]; +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_POW10_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/regex.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/regex.h new file mode 100644 index 00000000..6446c403 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/regex.h @@ -0,0 +1,739 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_REGEX_H_ +#define RAPIDJSON_INTERNAL_REGEX_H_ + +#include "../allocators.h" +#include "../stream.h" +#include "stack.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(switch-enum) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifndef RAPIDJSON_REGEX_VERBOSE +#define RAPIDJSON_REGEX_VERBOSE 0 +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +/////////////////////////////////////////////////////////////////////////////// +// DecodedStream + +template <typename SourceStream, typename Encoding> +class DecodedStream { +public: + DecodedStream(SourceStream& ss) : ss_(ss), codepoint_() { Decode(); } + unsigned Peek() { return codepoint_; } + unsigned Take() { + unsigned c = codepoint_; + if (c) // No further decoding when '\0' + Decode(); + return c; + } + +private: + void Decode() { + if (!Encoding::Decode(ss_, &codepoint_)) + codepoint_ = 0; + } + + SourceStream& ss_; + unsigned codepoint_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericRegex + +static const SizeType kRegexInvalidState = ~SizeType(0); //!< Represents an invalid index in GenericRegex::State::out, out1 +static const SizeType kRegexInvalidRange = ~SizeType(0); + +template <typename Encoding, typename Allocator> +class GenericRegexSearch; + +//! Regular expression engine with subset of ECMAscript grammar. +/*! + Supported regular expression syntax: + - \c ab Concatenation + - \c a|b Alternation + - \c a? Zero or one + - \c a* Zero or more + - \c a+ One or more + - \c a{3} Exactly 3 times + - \c a{3,} At least 3 times + - \c a{3,5} 3 to 5 times + - \c (ab) Grouping + - \c ^a At the beginning + - \c a$ At the end + - \c . Any character + - \c [abc] Character classes + - \c [a-c] Character class range + - \c [a-z0-9_] Character class combination + - \c [^abc] Negated character classes + - \c [^a-c] Negated character class range + - \c [\b] Backspace (U+0008) + - \c \\| \\\\ ... Escape characters + - \c \\f Form feed (U+000C) + - \c \\n Line feed (U+000A) + - \c \\r Carriage return (U+000D) + - \c \\t Tab (U+0009) + - \c \\v Vertical tab (U+000B) + + \note This is a Thompson NFA engine, implemented with reference to + Cox, Russ. "Regular Expression Matching Can Be Simple And Fast (but is slow in Java, Perl, PHP, Python, Ruby,...).", + https://swtch.com/~rsc/regexp/regexp1.html +*/ +template <typename Encoding, typename Allocator = CrtAllocator> +class GenericRegex { +public: + typedef Encoding EncodingType; + typedef typename Encoding::Ch Ch; + template <typename, typename> friend class GenericRegexSearch; + + GenericRegex(const Ch* source, Allocator* allocator = 0) : + ownAllocator_(allocator ? 0 : RAPIDJSON_NEW(Allocator)()), allocator_(allocator ? allocator : ownAllocator_), + states_(allocator_, 256), ranges_(allocator_, 256), root_(kRegexInvalidState), stateCount_(), rangeCount_(), + anchorBegin_(), anchorEnd_() + { + GenericStringStream<Encoding> ss(source); + DecodedStream<GenericStringStream<Encoding>, Encoding> ds(ss); + Parse(ds); + } + + ~GenericRegex() + { + RAPIDJSON_DELETE(ownAllocator_); + } + + bool IsValid() const { + return root_ != kRegexInvalidState; + } + +private: + enum Operator { + kZeroOrOne, + kZeroOrMore, + kOneOrMore, + kConcatenation, + kAlternation, + kLeftParenthesis + }; + + static const unsigned kAnyCharacterClass = 0xFFFFFFFF; //!< For '.' + static const unsigned kRangeCharacterClass = 0xFFFFFFFE; + static const unsigned kRangeNegationFlag = 0x80000000; + + struct Range { + unsigned start; // + unsigned end; + SizeType next; + }; + + struct State { + SizeType out; //!< Equals to kInvalid for matching state + SizeType out1; //!< Equals to non-kInvalid for split + SizeType rangeStart; + unsigned codepoint; + }; + + struct Frag { + Frag(SizeType s, SizeType o, SizeType m) : start(s), out(o), minIndex(m) {} + SizeType start; + SizeType out; //!< link-list of all output states + SizeType minIndex; + }; + + State& GetState(SizeType index) { + RAPIDJSON_ASSERT(index < stateCount_); + return states_.template Bottom<State>()[index]; + } + + const State& GetState(SizeType index) const { + RAPIDJSON_ASSERT(index < stateCount_); + return states_.template Bottom<State>()[index]; + } + + Range& GetRange(SizeType index) { + RAPIDJSON_ASSERT(index < rangeCount_); + return ranges_.template Bottom<Range>()[index]; + } + + const Range& GetRange(SizeType index) const { + RAPIDJSON_ASSERT(index < rangeCount_); + return ranges_.template Bottom<Range>()[index]; + } + + template <typename InputStream> + void Parse(DecodedStream<InputStream, Encoding>& ds) { + Stack<Allocator> operandStack(allocator_, 256); // Frag + Stack<Allocator> operatorStack(allocator_, 256); // Operator + Stack<Allocator> atomCountStack(allocator_, 256); // unsigned (Atom per parenthesis) + + *atomCountStack.template Push<unsigned>() = 0; + + unsigned codepoint; + while (ds.Peek() != 0) { + switch (codepoint = ds.Take()) { + case '^': + anchorBegin_ = true; + break; + + case '$': + anchorEnd_ = true; + break; + + case '|': + while (!operatorStack.Empty() && *operatorStack.template Top<Operator>() < kAlternation) + if (!Eval(operandStack, *operatorStack.template Pop<Operator>(1))) + return; + *operatorStack.template Push<Operator>() = kAlternation; + *atomCountStack.template Top<unsigned>() = 0; + break; + + case '(': + *operatorStack.template Push<Operator>() = kLeftParenthesis; + *atomCountStack.template Push<unsigned>() = 0; + break; + + case ')': + while (!operatorStack.Empty() && *operatorStack.template Top<Operator>() != kLeftParenthesis) + if (!Eval(operandStack, *operatorStack.template Pop<Operator>(1))) + return; + if (operatorStack.Empty()) + return; + operatorStack.template Pop<Operator>(1); + atomCountStack.template Pop<unsigned>(1); + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '?': + if (!Eval(operandStack, kZeroOrOne)) + return; + break; + + case '*': + if (!Eval(operandStack, kZeroOrMore)) + return; + break; + + case '+': + if (!Eval(operandStack, kOneOrMore)) + return; + break; + + case '{': + { + unsigned n, m; + if (!ParseUnsigned(ds, &n)) + return; + + if (ds.Peek() == ',') { + ds.Take(); + if (ds.Peek() == '}') + m = kInfinityQuantifier; + else if (!ParseUnsigned(ds, &m) || m < n) + return; + } + else + m = n; + + if (!EvalQuantifier(operandStack, n, m) || ds.Peek() != '}') + return; + ds.Take(); + } + break; + + case '.': + PushOperand(operandStack, kAnyCharacterClass); + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '[': + { + SizeType range; + if (!ParseRange(ds, &range)) + return; + SizeType s = NewState(kRegexInvalidState, kRegexInvalidState, kRangeCharacterClass); + GetState(s).rangeStart = range; + *operandStack.template Push<Frag>() = Frag(s, s, s); + } + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '\\': // Escape character + if (!CharacterEscape(ds, &codepoint)) + return; // Unsupported escape character + // fall through to default + RAPIDJSON_DELIBERATE_FALLTHROUGH; + + default: // Pattern character + PushOperand(operandStack, codepoint); + ImplicitConcatenation(atomCountStack, operatorStack); + } + } + + while (!operatorStack.Empty()) + if (!Eval(operandStack, *operatorStack.template Pop<Operator>(1))) + return; + + // Link the operand to matching state. + if (operandStack.GetSize() == sizeof(Frag)) { + Frag* e = operandStack.template Pop<Frag>(1); + Patch(e->out, NewState(kRegexInvalidState, kRegexInvalidState, 0)); + root_ = e->start; + +#if RAPIDJSON_REGEX_VERBOSE + printf("root: %d\n", root_); + for (SizeType i = 0; i < stateCount_ ; i++) { + State& s = GetState(i); + printf("[%2d] out: %2d out1: %2d c: '%c'\n", i, s.out, s.out1, (char)s.codepoint); + } + printf("\n"); +#endif + } + } + + SizeType NewState(SizeType out, SizeType out1, unsigned codepoint) { + State* s = states_.template Push<State>(); + s->out = out; + s->out1 = out1; + s->codepoint = codepoint; + s->rangeStart = kRegexInvalidRange; + return stateCount_++; + } + + void PushOperand(Stack<Allocator>& operandStack, unsigned codepoint) { + SizeType s = NewState(kRegexInvalidState, kRegexInvalidState, codepoint); + *operandStack.template Push<Frag>() = Frag(s, s, s); + } + + void ImplicitConcatenation(Stack<Allocator>& atomCountStack, Stack<Allocator>& operatorStack) { + if (*atomCountStack.template Top<unsigned>()) + *operatorStack.template Push<Operator>() = kConcatenation; + (*atomCountStack.template Top<unsigned>())++; + } + + SizeType Append(SizeType l1, SizeType l2) { + SizeType old = l1; + while (GetState(l1).out != kRegexInvalidState) + l1 = GetState(l1).out; + GetState(l1).out = l2; + return old; + } + + void Patch(SizeType l, SizeType s) { + for (SizeType next; l != kRegexInvalidState; l = next) { + next = GetState(l).out; + GetState(l).out = s; + } + } + + bool Eval(Stack<Allocator>& operandStack, Operator op) { + switch (op) { + case kConcatenation: + RAPIDJSON_ASSERT(operandStack.GetSize() >= sizeof(Frag) * 2); + { + Frag e2 = *operandStack.template Pop<Frag>(1); + Frag e1 = *operandStack.template Pop<Frag>(1); + Patch(e1.out, e2.start); + *operandStack.template Push<Frag>() = Frag(e1.start, e2.out, Min(e1.minIndex, e2.minIndex)); + } + return true; + + case kAlternation: + if (operandStack.GetSize() >= sizeof(Frag) * 2) { + Frag e2 = *operandStack.template Pop<Frag>(1); + Frag e1 = *operandStack.template Pop<Frag>(1); + SizeType s = NewState(e1.start, e2.start, 0); + *operandStack.template Push<Frag>() = Frag(s, Append(e1.out, e2.out), Min(e1.minIndex, e2.minIndex)); + return true; + } + return false; + + case kZeroOrOne: + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop<Frag>(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + *operandStack.template Push<Frag>() = Frag(s, Append(e.out, s), e.minIndex); + return true; + } + return false; + + case kZeroOrMore: + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop<Frag>(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + Patch(e.out, s); + *operandStack.template Push<Frag>() = Frag(s, s, e.minIndex); + return true; + } + return false; + + case kOneOrMore: + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop<Frag>(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + Patch(e.out, s); + *operandStack.template Push<Frag>() = Frag(e.start, s, e.minIndex); + return true; + } + return false; + + default: + // syntax error (e.g. unclosed kLeftParenthesis) + return false; + } + } + + bool EvalQuantifier(Stack<Allocator>& operandStack, unsigned n, unsigned m) { + RAPIDJSON_ASSERT(n <= m); + RAPIDJSON_ASSERT(operandStack.GetSize() >= sizeof(Frag)); + + if (n == 0) { + if (m == 0) // a{0} not support + return false; + else if (m == kInfinityQuantifier) + Eval(operandStack, kZeroOrMore); // a{0,} -> a* + else { + Eval(operandStack, kZeroOrOne); // a{0,5} -> a? + for (unsigned i = 0; i < m - 1; i++) + CloneTopOperand(operandStack); // a{0,5} -> a? a? a? a? a? + for (unsigned i = 0; i < m - 1; i++) + Eval(operandStack, kConcatenation); // a{0,5} -> a?a?a?a?a? + } + return true; + } + + for (unsigned i = 0; i < n - 1; i++) // a{3} -> a a a + CloneTopOperand(operandStack); + + if (m == kInfinityQuantifier) + Eval(operandStack, kOneOrMore); // a{3,} -> a a a+ + else if (m > n) { + CloneTopOperand(operandStack); // a{3,5} -> a a a a + Eval(operandStack, kZeroOrOne); // a{3,5} -> a a a a? + for (unsigned i = n; i < m - 1; i++) + CloneTopOperand(operandStack); // a{3,5} -> a a a a? a? + for (unsigned i = n; i < m; i++) + Eval(operandStack, kConcatenation); // a{3,5} -> a a aa?a? + } + + for (unsigned i = 0; i < n - 1; i++) + Eval(operandStack, kConcatenation); // a{3} -> aaa, a{3,} -> aaa+, a{3.5} -> aaaa?a? + + return true; + } + + static SizeType Min(SizeType a, SizeType b) { return a < b ? a : b; } + + void CloneTopOperand(Stack<Allocator>& operandStack) { + const Frag src = *operandStack.template Top<Frag>(); // Copy constructor to prevent invalidation + SizeType count = stateCount_ - src.minIndex; // Assumes top operand contains states in [src->minIndex, stateCount_) + State* s = states_.template Push<State>(count); + memcpy(s, &GetState(src.minIndex), count * sizeof(State)); + for (SizeType j = 0; j < count; j++) { + if (s[j].out != kRegexInvalidState) + s[j].out += count; + if (s[j].out1 != kRegexInvalidState) + s[j].out1 += count; + } + *operandStack.template Push<Frag>() = Frag(src.start + count, src.out + count, src.minIndex + count); + stateCount_ += count; + } + + template <typename InputStream> + bool ParseUnsigned(DecodedStream<InputStream, Encoding>& ds, unsigned* u) { + unsigned r = 0; + if (ds.Peek() < '0' || ds.Peek() > '9') + return false; + while (ds.Peek() >= '0' && ds.Peek() <= '9') { + if (r >= 429496729 && ds.Peek() > '5') // 2^32 - 1 = 4294967295 + return false; // overflow + r = r * 10 + (ds.Take() - '0'); + } + *u = r; + return true; + } + + template <typename InputStream> + bool ParseRange(DecodedStream<InputStream, Encoding>& ds, SizeType* range) { + bool isBegin = true; + bool negate = false; + int step = 0; + SizeType start = kRegexInvalidRange; + SizeType current = kRegexInvalidRange; + unsigned codepoint; + while ((codepoint = ds.Take()) != 0) { + if (isBegin) { + isBegin = false; + if (codepoint == '^') { + negate = true; + continue; + } + } + + switch (codepoint) { + case ']': + if (start == kRegexInvalidRange) + return false; // Error: nothing inside [] + if (step == 2) { // Add trailing '-' + SizeType r = NewRange('-'); + RAPIDJSON_ASSERT(current != kRegexInvalidRange); + GetRange(current).next = r; + } + if (negate) + GetRange(start).start |= kRangeNegationFlag; + *range = start; + return true; + + case '\\': + if (ds.Peek() == 'b') { + ds.Take(); + codepoint = 0x0008; // Escape backspace character + } + else if (!CharacterEscape(ds, &codepoint)) + return false; + // fall through to default + RAPIDJSON_DELIBERATE_FALLTHROUGH; + + default: + switch (step) { + case 1: + if (codepoint == '-') { + step++; + break; + } + // fall through to step 0 for other characters + RAPIDJSON_DELIBERATE_FALLTHROUGH; + + case 0: + { + SizeType r = NewRange(codepoint); + if (current != kRegexInvalidRange) + GetRange(current).next = r; + if (start == kRegexInvalidRange) + start = r; + current = r; + } + step = 1; + break; + + default: + RAPIDJSON_ASSERT(step == 2); + GetRange(current).end = codepoint; + step = 0; + } + } + } + return false; + } + + SizeType NewRange(unsigned codepoint) { + Range* r = ranges_.template Push<Range>(); + r->start = r->end = codepoint; + r->next = kRegexInvalidRange; + return rangeCount_++; + } + + template <typename InputStream> + bool CharacterEscape(DecodedStream<InputStream, Encoding>& ds, unsigned* escapedCodepoint) { + unsigned codepoint; + switch (codepoint = ds.Take()) { + case '^': + case '$': + case '|': + case '(': + case ')': + case '?': + case '*': + case '+': + case '.': + case '[': + case ']': + case '{': + case '}': + case '\\': + *escapedCodepoint = codepoint; return true; + case 'f': *escapedCodepoint = 0x000C; return true; + case 'n': *escapedCodepoint = 0x000A; return true; + case 'r': *escapedCodepoint = 0x000D; return true; + case 't': *escapedCodepoint = 0x0009; return true; + case 'v': *escapedCodepoint = 0x000B; return true; + default: + return false; // Unsupported escape character + } + } + + Allocator* ownAllocator_; + Allocator* allocator_; + Stack<Allocator> states_; + Stack<Allocator> ranges_; + SizeType root_; + SizeType stateCount_; + SizeType rangeCount_; + + static const unsigned kInfinityQuantifier = ~0u; + + // For SearchWithAnchoring() + bool anchorBegin_; + bool anchorEnd_; +}; + +template <typename RegexType, typename Allocator = CrtAllocator> +class GenericRegexSearch { +public: + typedef typename RegexType::EncodingType Encoding; + typedef typename Encoding::Ch Ch; + + GenericRegexSearch(const RegexType& regex, Allocator* allocator = 0) : + regex_(regex), allocator_(allocator), ownAllocator_(0), + state0_(allocator, 0), state1_(allocator, 0), stateSet_() + { + RAPIDJSON_ASSERT(regex_.IsValid()); + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + stateSet_ = static_cast<unsigned*>(allocator_->Malloc(GetStateSetSize())); + state0_.template Reserve<SizeType>(regex_.stateCount_); + state1_.template Reserve<SizeType>(regex_.stateCount_); + } + + ~GenericRegexSearch() { + Allocator::Free(stateSet_); + RAPIDJSON_DELETE(ownAllocator_); + } + + template <typename InputStream> + bool Match(InputStream& is) { + return SearchWithAnchoring(is, true, true); + } + + bool Match(const Ch* s) { + GenericStringStream<Encoding> is(s); + return Match(is); + } + + template <typename InputStream> + bool Search(InputStream& is) { + return SearchWithAnchoring(is, regex_.anchorBegin_, regex_.anchorEnd_); + } + + bool Search(const Ch* s) { + GenericStringStream<Encoding> is(s); + return Search(is); + } + +private: + typedef typename RegexType::State State; + typedef typename RegexType::Range Range; + + template <typename InputStream> + bool SearchWithAnchoring(InputStream& is, bool anchorBegin, bool anchorEnd) { + DecodedStream<InputStream, Encoding> ds(is); + + state0_.Clear(); + Stack<Allocator> *current = &state0_, *next = &state1_; + const size_t stateSetSize = GetStateSetSize(); + std::memset(stateSet_, 0, stateSetSize); + + bool matched = AddState(*current, regex_.root_); + unsigned codepoint; + while (!current->Empty() && (codepoint = ds.Take()) != 0) { + std::memset(stateSet_, 0, stateSetSize); + next->Clear(); + matched = false; + for (const SizeType* s = current->template Bottom<SizeType>(); s != current->template End<SizeType>(); ++s) { + const State& sr = regex_.GetState(*s); + if (sr.codepoint == codepoint || + sr.codepoint == RegexType::kAnyCharacterClass || + (sr.codepoint == RegexType::kRangeCharacterClass && MatchRange(sr.rangeStart, codepoint))) + { + matched = AddState(*next, sr.out) || matched; + if (!anchorEnd && matched) + return true; + } + if (!anchorBegin) + AddState(*next, regex_.root_); + } + internal::Swap(current, next); + } + + return matched; + } + + size_t GetStateSetSize() const { + return (regex_.stateCount_ + 31) / 32 * 4; + } + + // Return whether the added states is a match state + bool AddState(Stack<Allocator>& l, SizeType index) { + RAPIDJSON_ASSERT(index != kRegexInvalidState); + + const State& s = regex_.GetState(index); + if (s.out1 != kRegexInvalidState) { // Split + bool matched = AddState(l, s.out); + return AddState(l, s.out1) || matched; + } + else if (!(stateSet_[index >> 5] & (1u << (index & 31)))) { + stateSet_[index >> 5] |= (1u << (index & 31)); + *l.template PushUnsafe<SizeType>() = index; + } + return s.out == kRegexInvalidState; // by using PushUnsafe() above, we can ensure s is not validated due to reallocation. + } + + bool MatchRange(SizeType rangeIndex, unsigned codepoint) const { + bool yes = (regex_.GetRange(rangeIndex).start & RegexType::kRangeNegationFlag) == 0; + while (rangeIndex != kRegexInvalidRange) { + const Range& r = regex_.GetRange(rangeIndex); + if (codepoint >= (r.start & ~RegexType::kRangeNegationFlag) && codepoint <= r.end) + return yes; + rangeIndex = r.next; + } + return !yes; + } + + const RegexType& regex_; + Allocator* allocator_; + Allocator* ownAllocator_; + Stack<Allocator> state0_; + Stack<Allocator> state1_; + uint32_t* stateSet_; +}; + +typedef GenericRegex<UTF8<> > Regex; +typedef GenericRegexSearch<Regex> RegexSearch; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#if defined(__clang__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_REGEX_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/stack.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/stack.h new file mode 100644 index 00000000..73abd706 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/stack.h @@ -0,0 +1,232 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_STACK_H_ +#define RAPIDJSON_INTERNAL_STACK_H_ + +#include "../allocators.h" +#include "swap.h" +#include <cstddef> + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +/////////////////////////////////////////////////////////////////////////////// +// Stack + +//! A type-unsafe stack for storing different types of data. +/*! \tparam Allocator Allocator for allocating stack memory. +*/ +template <typename Allocator> +class Stack { +public: + // Optimization note: Do not allocate memory for stack_ in constructor. + // Do it lazily when first Push() -> Expand() -> Resize(). + Stack(Allocator* allocator, size_t stackCapacity) : allocator_(allocator), ownAllocator_(0), stack_(0), stackTop_(0), stackEnd_(0), initialCapacity_(stackCapacity) { + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + Stack(Stack&& rhs) + : allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + stack_(rhs.stack_), + stackTop_(rhs.stackTop_), + stackEnd_(rhs.stackEnd_), + initialCapacity_(rhs.initialCapacity_) + { + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.stack_ = 0; + rhs.stackTop_ = 0; + rhs.stackEnd_ = 0; + rhs.initialCapacity_ = 0; + } +#endif + + ~Stack() { + Destroy(); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + Stack& operator=(Stack&& rhs) { + if (&rhs != this) + { + Destroy(); + + allocator_ = rhs.allocator_; + ownAllocator_ = rhs.ownAllocator_; + stack_ = rhs.stack_; + stackTop_ = rhs.stackTop_; + stackEnd_ = rhs.stackEnd_; + initialCapacity_ = rhs.initialCapacity_; + + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.stack_ = 0; + rhs.stackTop_ = 0; + rhs.stackEnd_ = 0; + rhs.initialCapacity_ = 0; + } + return *this; + } +#endif + + void Swap(Stack& rhs) RAPIDJSON_NOEXCEPT { + internal::Swap(allocator_, rhs.allocator_); + internal::Swap(ownAllocator_, rhs.ownAllocator_); + internal::Swap(stack_, rhs.stack_); + internal::Swap(stackTop_, rhs.stackTop_); + internal::Swap(stackEnd_, rhs.stackEnd_); + internal::Swap(initialCapacity_, rhs.initialCapacity_); + } + + void Clear() { stackTop_ = stack_; } + + void ShrinkToFit() { + if (Empty()) { + // If the stack is empty, completely deallocate the memory. + Allocator::Free(stack_); // NOLINT (+clang-analyzer-unix.Malloc) + stack_ = 0; + stackTop_ = 0; + stackEnd_ = 0; + } + else + Resize(GetSize()); + } + + // Optimization note: try to minimize the size of this function for force inline. + // Expansion is run very infrequently, so it is moved to another (probably non-inline) function. + template<typename T> + RAPIDJSON_FORCEINLINE void Reserve(size_t count = 1) { + // Expand the stack if needed + if (RAPIDJSON_UNLIKELY(static_cast<std::ptrdiff_t>(sizeof(T) * count) > (stackEnd_ - stackTop_))) + Expand<T>(count); + } + + template<typename T> + RAPIDJSON_FORCEINLINE T* Push(size_t count = 1) { + Reserve<T>(count); + return PushUnsafe<T>(count); + } + + template<typename T> + RAPIDJSON_FORCEINLINE T* PushUnsafe(size_t count = 1) { + RAPIDJSON_ASSERT(stackTop_); + RAPIDJSON_ASSERT(static_cast<std::ptrdiff_t>(sizeof(T) * count) <= (stackEnd_ - stackTop_)); + T* ret = reinterpret_cast<T*>(stackTop_); + stackTop_ += sizeof(T) * count; + return ret; + } + + template<typename T> + T* Pop(size_t count) { + RAPIDJSON_ASSERT(GetSize() >= count * sizeof(T)); + stackTop_ -= count * sizeof(T); + return reinterpret_cast<T*>(stackTop_); + } + + template<typename T> + T* Top() { + RAPIDJSON_ASSERT(GetSize() >= sizeof(T)); + return reinterpret_cast<T*>(stackTop_ - sizeof(T)); + } + + template<typename T> + const T* Top() const { + RAPIDJSON_ASSERT(GetSize() >= sizeof(T)); + return reinterpret_cast<T*>(stackTop_ - sizeof(T)); + } + + template<typename T> + T* End() { return reinterpret_cast<T*>(stackTop_); } + + template<typename T> + const T* End() const { return reinterpret_cast<T*>(stackTop_); } + + template<typename T> + T* Bottom() { return reinterpret_cast<T*>(stack_); } + + template<typename T> + const T* Bottom() const { return reinterpret_cast<T*>(stack_); } + + bool HasAllocator() const { + return allocator_ != 0; + } + + Allocator& GetAllocator() { + RAPIDJSON_ASSERT(allocator_); + return *allocator_; + } + + bool Empty() const { return stackTop_ == stack_; } + size_t GetSize() const { return static_cast<size_t>(stackTop_ - stack_); } + size_t GetCapacity() const { return static_cast<size_t>(stackEnd_ - stack_); } + +private: + template<typename T> + void Expand(size_t count) { + // Only expand the capacity if the current stack exists. Otherwise just create a stack with initial capacity. + size_t newCapacity; + if (stack_ == 0) { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + newCapacity = initialCapacity_; + } else { + newCapacity = GetCapacity(); + newCapacity += (newCapacity + 1) / 2; + } + size_t newSize = GetSize() + sizeof(T) * count; + if (newCapacity < newSize) + newCapacity = newSize; + + Resize(newCapacity); + } + + void Resize(size_t newCapacity) { + const size_t size = GetSize(); // Backup the current size + stack_ = static_cast<char*>(allocator_->Realloc(stack_, GetCapacity(), newCapacity)); + stackTop_ = stack_ + size; + stackEnd_ = stack_ + newCapacity; + } + + void Destroy() { + Allocator::Free(stack_); + RAPIDJSON_DELETE(ownAllocator_); // Only delete if it is owned by the stack + } + + // Prohibit copy constructor & assignment operator. + Stack(const Stack&); + Stack& operator=(const Stack&); + + Allocator* allocator_; + Allocator* ownAllocator_; + char *stack_; + char *stackTop_; + char *stackEnd_; + size_t initialCapacity_; +}; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_STACK_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/strfunc.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/strfunc.h new file mode 100644 index 00000000..baecb6cc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/strfunc.h @@ -0,0 +1,69 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_STRFUNC_H_ +#define RAPIDJSON_INTERNAL_STRFUNC_H_ + +#include "../stream.h" +#include <cwchar> + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Custom strlen() which works on different character types. +/*! \tparam Ch Character type (e.g. char, wchar_t, short) + \param s Null-terminated input string. + \return Number of characters in the string. + \note This has the same semantics as strlen(), the return value is not number of Unicode codepoints. +*/ +template <typename Ch> +inline SizeType StrLen(const Ch* s) { + RAPIDJSON_ASSERT(s != 0); + const Ch* p = s; + while (*p) ++p; + return SizeType(p - s); +} + +template <> +inline SizeType StrLen(const char* s) { + return SizeType(std::strlen(s)); +} + +template <> +inline SizeType StrLen(const wchar_t* s) { + return SizeType(std::wcslen(s)); +} + +//! Returns number of code points in a encoded string. +template<typename Encoding> +bool CountStringCodePoint(const typename Encoding::Ch* s, SizeType length, SizeType* outCount) { + RAPIDJSON_ASSERT(s != 0); + RAPIDJSON_ASSERT(outCount != 0); + GenericStringStream<Encoding> is(s); + const typename Encoding::Ch* end = s + length; + SizeType count = 0; + while (is.src_ < end) { + unsigned codepoint; + if (!Encoding::Decode(is, &codepoint)) + return false; + count++; + } + *outCount = count; + return true; +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_INTERNAL_STRFUNC_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/strtod.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/strtod.h new file mode 100644 index 00000000..d61a67a4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/strtod.h @@ -0,0 +1,290 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_STRTOD_ +#define RAPIDJSON_STRTOD_ + +#include "ieee754.h" +#include "biginteger.h" +#include "diyfp.h" +#include "pow10.h" +#include <climits> +#include <limits> + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +inline double FastPath(double significand, int exp) { + if (exp < -308) + return 0.0; + else if (exp >= 0) + return significand * internal::Pow10(exp); + else + return significand / internal::Pow10(-exp); +} + +inline double StrtodNormalPrecision(double d, int p) { + if (p < -308) { + // Prevent expSum < -308, making Pow10(p) = 0 + d = FastPath(d, -308); + d = FastPath(d, p + 308); + } + else + d = FastPath(d, p); + return d; +} + +template <typename T> +inline T Min3(T a, T b, T c) { + T m = a; + if (m > b) m = b; + if (m > c) m = c; + return m; +} + +inline int CheckWithinHalfULP(double b, const BigInteger& d, int dExp) { + const Double db(b); + const uint64_t bInt = db.IntegerSignificand(); + const int bExp = db.IntegerExponent(); + const int hExp = bExp - 1; + + int dS_Exp2 = 0, dS_Exp5 = 0, bS_Exp2 = 0, bS_Exp5 = 0, hS_Exp2 = 0, hS_Exp5 = 0; + + // Adjust for decimal exponent + if (dExp >= 0) { + dS_Exp2 += dExp; + dS_Exp5 += dExp; + } + else { + bS_Exp2 -= dExp; + bS_Exp5 -= dExp; + hS_Exp2 -= dExp; + hS_Exp5 -= dExp; + } + + // Adjust for binary exponent + if (bExp >= 0) + bS_Exp2 += bExp; + else { + dS_Exp2 -= bExp; + hS_Exp2 -= bExp; + } + + // Adjust for half ulp exponent + if (hExp >= 0) + hS_Exp2 += hExp; + else { + dS_Exp2 -= hExp; + bS_Exp2 -= hExp; + } + + // Remove common power of two factor from all three scaled values + int common_Exp2 = Min3(dS_Exp2, bS_Exp2, hS_Exp2); + dS_Exp2 -= common_Exp2; + bS_Exp2 -= common_Exp2; + hS_Exp2 -= common_Exp2; + + BigInteger dS = d; + dS.MultiplyPow5(static_cast<unsigned>(dS_Exp5)) <<= static_cast<unsigned>(dS_Exp2); + + BigInteger bS(bInt); + bS.MultiplyPow5(static_cast<unsigned>(bS_Exp5)) <<= static_cast<unsigned>(bS_Exp2); + + BigInteger hS(1); + hS.MultiplyPow5(static_cast<unsigned>(hS_Exp5)) <<= static_cast<unsigned>(hS_Exp2); + + BigInteger delta(0); + dS.Difference(bS, &delta); + + return delta.Compare(hS); +} + +inline bool StrtodFast(double d, int p, double* result) { + // Use fast path for string-to-double conversion if possible + // see http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ + if (p > 22 && p < 22 + 16) { + // Fast Path Cases In Disguise + d *= internal::Pow10(p - 22); + p = 22; + } + + if (p >= -22 && p <= 22 && d <= 9007199254740991.0) { // 2^53 - 1 + *result = FastPath(d, p); + return true; + } + else + return false; +} + +// Compute an approximation and see if it is within 1/2 ULP +inline bool StrtodDiyFp(const char* decimals, int dLen, int dExp, double* result) { + uint64_t significand = 0; + int i = 0; // 2^64 - 1 = 18446744073709551615, 1844674407370955161 = 0x1999999999999999 + for (; i < dLen; i++) { + if (significand > RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || + (significand == RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) && decimals[i] > '5')) + break; + significand = significand * 10u + static_cast<unsigned>(decimals[i] - '0'); + } + + if (i < dLen && decimals[i] >= '5') // Rounding + significand++; + + int remaining = dLen - i; + const int kUlpShift = 3; + const int kUlp = 1 << kUlpShift; + int64_t error = (remaining == 0) ? 0 : kUlp / 2; + + DiyFp v(significand, 0); + v = v.Normalize(); + error <<= -v.e; + + dExp += remaining; + + int actualExp; + DiyFp cachedPower = GetCachedPower10(dExp, &actualExp); + if (actualExp != dExp) { + static const DiyFp kPow10[] = { + DiyFp(RAPIDJSON_UINT64_C2(0xa0000000, 0x00000000), -60), // 10^1 + DiyFp(RAPIDJSON_UINT64_C2(0xc8000000, 0x00000000), -57), // 10^2 + DiyFp(RAPIDJSON_UINT64_C2(0xfa000000, 0x00000000), -54), // 10^3 + DiyFp(RAPIDJSON_UINT64_C2(0x9c400000, 0x00000000), -50), // 10^4 + DiyFp(RAPIDJSON_UINT64_C2(0xc3500000, 0x00000000), -47), // 10^5 + DiyFp(RAPIDJSON_UINT64_C2(0xf4240000, 0x00000000), -44), // 10^6 + DiyFp(RAPIDJSON_UINT64_C2(0x98968000, 0x00000000), -40) // 10^7 + }; + int adjustment = dExp - actualExp; + RAPIDJSON_ASSERT(adjustment >= 1 && adjustment < 8); + v = v * kPow10[adjustment - 1]; + if (dLen + adjustment > 19) // has more digits than decimal digits in 64-bit + error += kUlp / 2; + } + + v = v * cachedPower; + + error += kUlp + (error == 0 ? 0 : 1); + + const int oldExp = v.e; + v = v.Normalize(); + error <<= oldExp - v.e; + + const int effectiveSignificandSize = Double::EffectiveSignificandSize(64 + v.e); + int precisionSize = 64 - effectiveSignificandSize; + if (precisionSize + kUlpShift >= 64) { + int scaleExp = (precisionSize + kUlpShift) - 63; + v.f >>= scaleExp; + v.e += scaleExp; + error = (error >> scaleExp) + 1 + kUlp; + precisionSize -= scaleExp; + } + + DiyFp rounded(v.f >> precisionSize, v.e + precisionSize); + const uint64_t precisionBits = (v.f & ((uint64_t(1) << precisionSize) - 1)) * kUlp; + const uint64_t halfWay = (uint64_t(1) << (precisionSize - 1)) * kUlp; + if (precisionBits >= halfWay + static_cast<unsigned>(error)) { + rounded.f++; + if (rounded.f & (DiyFp::kDpHiddenBit << 1)) { // rounding overflows mantissa (issue #340) + rounded.f >>= 1; + rounded.e++; + } + } + + *result = rounded.ToDouble(); + + return halfWay - static_cast<unsigned>(error) >= precisionBits || precisionBits >= halfWay + static_cast<unsigned>(error); +} + +inline double StrtodBigInteger(double approx, const char* decimals, int dLen, int dExp) { + RAPIDJSON_ASSERT(dLen >= 0); + const BigInteger dInt(decimals, static_cast<unsigned>(dLen)); + Double a(approx); + int cmp = CheckWithinHalfULP(a.Value(), dInt, dExp); + if (cmp < 0) + return a.Value(); // within half ULP + else if (cmp == 0) { + // Round towards even + if (a.Significand() & 1) + return a.NextPositiveDouble(); + else + return a.Value(); + } + else // adjustment + return a.NextPositiveDouble(); +} + +inline double StrtodFullPrecision(double d, int p, const char* decimals, size_t length, size_t decimalPosition, int exp) { + RAPIDJSON_ASSERT(d >= 0.0); + RAPIDJSON_ASSERT(length >= 1); + + double result = 0.0; + if (StrtodFast(d, p, &result)) + return result; + + RAPIDJSON_ASSERT(length <= INT_MAX); + int dLen = static_cast<int>(length); + + RAPIDJSON_ASSERT(length >= decimalPosition); + RAPIDJSON_ASSERT(length - decimalPosition <= INT_MAX); + int dExpAdjust = static_cast<int>(length - decimalPosition); + + RAPIDJSON_ASSERT(exp >= INT_MIN + dExpAdjust); + int dExp = exp - dExpAdjust; + + // Make sure length+dExp does not overflow + RAPIDJSON_ASSERT(dExp <= INT_MAX - dLen); + + // Trim leading zeros + while (dLen > 0 && *decimals == '0') { + dLen--; + decimals++; + } + + // Trim trailing zeros + while (dLen > 0 && decimals[dLen - 1] == '0') { + dLen--; + dExp++; + } + + if (dLen == 0) { // Buffer only contains zeros. + return 0.0; + } + + // Trim right-most digits + const int kMaxDecimalDigit = 767 + 1; + if (dLen > kMaxDecimalDigit) { + dExp += dLen - kMaxDecimalDigit; + dLen = kMaxDecimalDigit; + } + + // If too small, underflow to zero. + // Any x <= 10^-324 is interpreted as zero. + if (dLen + dExp <= -324) + return 0.0; + + // If too large, overflow to infinity. + // Any x >= 10^309 is interpreted as +infinity. + if (dLen + dExp > 309) + return std::numeric_limits<double>::infinity(); + + if (StrtodDiyFp(decimals, dLen, dExp, &result)) + return result; + + // Use approximation from StrtodDiyFp and make adjustment with BigInteger comparison + return StrtodBigInteger(result, decimals, dLen, dExp); +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_STRTOD_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/swap.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/swap.h new file mode 100644 index 00000000..2cf92f93 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/internal/swap.h @@ -0,0 +1,46 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_SWAP_H_ +#define RAPIDJSON_INTERNAL_SWAP_H_ + +#include "../rapidjson.h" + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Custom swap() to avoid dependency on C++ <algorithm> header +/*! \tparam T Type of the arguments to swap, should be instantiated with primitive C++ types only. + \note This has the same semantics as std::swap(). +*/ +template <typename T> +inline void Swap(T& a, T& b) RAPIDJSON_NOEXCEPT { + T tmp = a; + a = b; + b = tmp; +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_SWAP_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/istreamwrapper.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/istreamwrapper.h new file mode 100644 index 00000000..01437ec0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/istreamwrapper.h @@ -0,0 +1,128 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ISTREAMWRAPPER_H_ +#define RAPIDJSON_ISTREAMWRAPPER_H_ + +#include "stream.h" +#include <iosfwd> +#include <ios> + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4351) // new behavior: elements of array 'array' will be default initialized +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Wrapper of \c std::basic_istream into RapidJSON's Stream concept. +/*! + The classes can be wrapped including but not limited to: + + - \c std::istringstream + - \c std::stringstream + - \c std::wistringstream + - \c std::wstringstream + - \c std::ifstream + - \c std::fstream + - \c std::wifstream + - \c std::wfstream + + \tparam StreamType Class derived from \c std::basic_istream. +*/ + +template <typename StreamType> +class BasicIStreamWrapper { +public: + typedef typename StreamType::char_type Ch; + + //! Constructor. + /*! + \param stream stream opened for read. + */ + BasicIStreamWrapper(StreamType &stream) : stream_(stream), buffer_(peekBuffer_), bufferSize_(4), bufferLast_(0), current_(buffer_), readCount_(0), count_(0), eof_(false) { + Read(); + } + + //! Constructor. + /*! + \param stream stream opened for read. + \param buffer user-supplied buffer. + \param bufferSize size of buffer in bytes. Must >=4 bytes. + */ + BasicIStreamWrapper(StreamType &stream, char* buffer, size_t bufferSize) : stream_(stream), buffer_(buffer), bufferSize_(bufferSize), bufferLast_(0), current_(buffer_), readCount_(0), count_(0), eof_(false) { + RAPIDJSON_ASSERT(bufferSize >= 4); + Read(); + } + + Ch Peek() const { return *current_; } + Ch Take() { Ch c = *current_; Read(); return c; } + size_t Tell() const { return count_ + static_cast<size_t>(current_ - buffer_); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + return (current_ + 4 - !eof_ <= bufferLast_) ? current_ : 0; + } + +private: + BasicIStreamWrapper(); + BasicIStreamWrapper(const BasicIStreamWrapper&); + BasicIStreamWrapper& operator=(const BasicIStreamWrapper&); + + void Read() { + if (current_ < bufferLast_) + ++current_; + else if (!eof_) { + count_ += readCount_; + readCount_ = bufferSize_; + bufferLast_ = buffer_ + readCount_ - 1; + current_ = buffer_; + + if (!stream_.read(buffer_, static_cast<std::streamsize>(bufferSize_))) { + readCount_ = static_cast<size_t>(stream_.gcount()); + *(bufferLast_ = buffer_ + readCount_) = '\0'; + eof_ = true; + } + } + } + + StreamType &stream_; + Ch peekBuffer_[4], *buffer_; + size_t bufferSize_; + Ch *bufferLast_; + Ch *current_; + size_t readCount_; + size_t count_; //!< Number of characters read + bool eof_; +}; + +typedef BasicIStreamWrapper<std::istream> IStreamWrapper; +typedef BasicIStreamWrapper<std::wistream> WIStreamWrapper; + +#if defined(__clang__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ISTREAMWRAPPER_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/memorybuffer.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/memorybuffer.h new file mode 100644 index 00000000..ffbc41ed --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/memorybuffer.h @@ -0,0 +1,70 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_MEMORYBUFFER_H_ +#define RAPIDJSON_MEMORYBUFFER_H_ + +#include "stream.h" +#include "internal/stack.h" + +RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory output byte stream. +/*! + This class is mainly for being wrapped by EncodedOutputStream or AutoUTFOutputStream. + + It is similar to FileWriteBuffer but the destination is an in-memory buffer instead of a file. + + Differences between MemoryBuffer and StringBuffer: + 1. StringBuffer has Encoding but MemoryBuffer is only a byte buffer. + 2. StringBuffer::GetString() returns a null-terminated string. MemoryBuffer::GetBuffer() returns a buffer without terminator. + + \tparam Allocator type for allocating memory buffer. + \note implements Stream concept +*/ +template <typename Allocator = CrtAllocator> +struct GenericMemoryBuffer { + typedef char Ch; // byte + + GenericMemoryBuffer(Allocator* allocator = 0, size_t capacity = kDefaultCapacity) : stack_(allocator, capacity) {} + + void Put(Ch c) { *stack_.template Push<Ch>() = c; } + void Flush() {} + + void Clear() { stack_.Clear(); } + void ShrinkToFit() { stack_.ShrinkToFit(); } + Ch* Push(size_t count) { return stack_.template Push<Ch>(count); } + void Pop(size_t count) { stack_.template Pop<Ch>(count); } + + const Ch* GetBuffer() const { + return stack_.template Bottom<Ch>(); + } + + size_t GetSize() const { return stack_.GetSize(); } + + static const size_t kDefaultCapacity = 256; + mutable internal::Stack<Allocator> stack_; +}; + +typedef GenericMemoryBuffer<> MemoryBuffer; + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(MemoryBuffer& memoryBuffer, char c, size_t n) { + std::memset(memoryBuffer.stack_.Push<char>(n), c, n * sizeof(c)); +} + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_MEMORYBUFFER_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/memorystream.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/memorystream.h new file mode 100644 index 00000000..77af6c99 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/memorystream.h @@ -0,0 +1,71 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_MEMORYSTREAM_H_ +#define RAPIDJSON_MEMORYSTREAM_H_ + +#include "stream.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(unreachable-code) +RAPIDJSON_DIAG_OFF(missing-noreturn) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory input byte stream. +/*! + This class is mainly for being wrapped by EncodedInputStream or AutoUTFInputStream. + + It is similar to FileReadBuffer but the source is an in-memory buffer instead of a file. + + Differences between MemoryStream and StringStream: + 1. StringStream has encoding but MemoryStream is a byte stream. + 2. MemoryStream needs size of the source buffer and the buffer don't need to be null terminated. StringStream assume null-terminated string as source. + 3. MemoryStream supports Peek4() for encoding detection. StringStream is specified with an encoding so it should not have Peek4(). + \note implements Stream concept +*/ +struct MemoryStream { + typedef char Ch; // byte + + MemoryStream(const Ch *src, size_t size) : src_(src), begin_(src), end_(src + size), size_(size) {} + + Ch Peek() const { return RAPIDJSON_UNLIKELY(src_ == end_) ? '\0' : *src_; } + Ch Take() { return RAPIDJSON_UNLIKELY(src_ == end_) ? '\0' : *src_++; } + size_t Tell() const { return static_cast<size_t>(src_ - begin_); } + + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + return Tell() + 4 <= size_ ? src_ : 0; + } + + const Ch* src_; //!< Current read position. + const Ch* begin_; //!< Original head of the string. + const Ch* end_; //!< End of stream. + size_t size_; //!< Size of the stream. +}; + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_MEMORYBUFFER_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/msinttypes/inttypes.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/msinttypes/inttypes.h new file mode 100644 index 00000000..18111286 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/msinttypes/inttypes.h @@ -0,0 +1,316 @@ +// ISO C9x compliant inttypes.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2013 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the product nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +// The above software in this distribution may have been modified by +// THL A29 Limited ("Tencent Modifications"). +// All Tencent Modifications are Copyright (C) 2015 THL A29 Limited. + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_INTTYPES_H_ // [ +#define _MSC_INTTYPES_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#include "stdint.h" + +// miloyip: VC supports inttypes.h since VC2013 +#if _MSC_VER >= 1800 +#include <inttypes.h> +#else + +// 7.8 Format conversion of integer types + +typedef struct { + intmax_t quot; + intmax_t rem; +} imaxdiv_t; + +// 7.8.1 Macros for format specifiers + +#if !defined(__cplusplus) || defined(__STDC_FORMAT_MACROS) // [ See footnote 185 at page 198 + +// The fprintf macros for signed integers are: +#define PRId8 "d" +#define PRIi8 "i" +#define PRIdLEAST8 "d" +#define PRIiLEAST8 "i" +#define PRIdFAST8 "d" +#define PRIiFAST8 "i" + +#define PRId16 "hd" +#define PRIi16 "hi" +#define PRIdLEAST16 "hd" +#define PRIiLEAST16 "hi" +#define PRIdFAST16 "hd" +#define PRIiFAST16 "hi" + +#define PRId32 "I32d" +#define PRIi32 "I32i" +#define PRIdLEAST32 "I32d" +#define PRIiLEAST32 "I32i" +#define PRIdFAST32 "I32d" +#define PRIiFAST32 "I32i" + +#define PRId64 "I64d" +#define PRIi64 "I64i" +#define PRIdLEAST64 "I64d" +#define PRIiLEAST64 "I64i" +#define PRIdFAST64 "I64d" +#define PRIiFAST64 "I64i" + +#define PRIdMAX "I64d" +#define PRIiMAX "I64i" + +#define PRIdPTR "Id" +#define PRIiPTR "Ii" + +// The fprintf macros for unsigned integers are: +#define PRIo8 "o" +#define PRIu8 "u" +#define PRIx8 "x" +#define PRIX8 "X" +#define PRIoLEAST8 "o" +#define PRIuLEAST8 "u" +#define PRIxLEAST8 "x" +#define PRIXLEAST8 "X" +#define PRIoFAST8 "o" +#define PRIuFAST8 "u" +#define PRIxFAST8 "x" +#define PRIXFAST8 "X" + +#define PRIo16 "ho" +#define PRIu16 "hu" +#define PRIx16 "hx" +#define PRIX16 "hX" +#define PRIoLEAST16 "ho" +#define PRIuLEAST16 "hu" +#define PRIxLEAST16 "hx" +#define PRIXLEAST16 "hX" +#define PRIoFAST16 "ho" +#define PRIuFAST16 "hu" +#define PRIxFAST16 "hx" +#define PRIXFAST16 "hX" + +#define PRIo32 "I32o" +#define PRIu32 "I32u" +#define PRIx32 "I32x" +#define PRIX32 "I32X" +#define PRIoLEAST32 "I32o" +#define PRIuLEAST32 "I32u" +#define PRIxLEAST32 "I32x" +#define PRIXLEAST32 "I32X" +#define PRIoFAST32 "I32o" +#define PRIuFAST32 "I32u" +#define PRIxFAST32 "I32x" +#define PRIXFAST32 "I32X" + +#define PRIo64 "I64o" +#define PRIu64 "I64u" +#define PRIx64 "I64x" +#define PRIX64 "I64X" +#define PRIoLEAST64 "I64o" +#define PRIuLEAST64 "I64u" +#define PRIxLEAST64 "I64x" +#define PRIXLEAST64 "I64X" +#define PRIoFAST64 "I64o" +#define PRIuFAST64 "I64u" +#define PRIxFAST64 "I64x" +#define PRIXFAST64 "I64X" + +#define PRIoMAX "I64o" +#define PRIuMAX "I64u" +#define PRIxMAX "I64x" +#define PRIXMAX "I64X" + +#define PRIoPTR "Io" +#define PRIuPTR "Iu" +#define PRIxPTR "Ix" +#define PRIXPTR "IX" + +// The fscanf macros for signed integers are: +#define SCNd8 "d" +#define SCNi8 "i" +#define SCNdLEAST8 "d" +#define SCNiLEAST8 "i" +#define SCNdFAST8 "d" +#define SCNiFAST8 "i" + +#define SCNd16 "hd" +#define SCNi16 "hi" +#define SCNdLEAST16 "hd" +#define SCNiLEAST16 "hi" +#define SCNdFAST16 "hd" +#define SCNiFAST16 "hi" + +#define SCNd32 "ld" +#define SCNi32 "li" +#define SCNdLEAST32 "ld" +#define SCNiLEAST32 "li" +#define SCNdFAST32 "ld" +#define SCNiFAST32 "li" + +#define SCNd64 "I64d" +#define SCNi64 "I64i" +#define SCNdLEAST64 "I64d" +#define SCNiLEAST64 "I64i" +#define SCNdFAST64 "I64d" +#define SCNiFAST64 "I64i" + +#define SCNdMAX "I64d" +#define SCNiMAX "I64i" + +#ifdef _WIN64 // [ +# define SCNdPTR "I64d" +# define SCNiPTR "I64i" +#else // _WIN64 ][ +# define SCNdPTR "ld" +# define SCNiPTR "li" +#endif // _WIN64 ] + +// The fscanf macros for unsigned integers are: +#define SCNo8 "o" +#define SCNu8 "u" +#define SCNx8 "x" +#define SCNX8 "X" +#define SCNoLEAST8 "o" +#define SCNuLEAST8 "u" +#define SCNxLEAST8 "x" +#define SCNXLEAST8 "X" +#define SCNoFAST8 "o" +#define SCNuFAST8 "u" +#define SCNxFAST8 "x" +#define SCNXFAST8 "X" + +#define SCNo16 "ho" +#define SCNu16 "hu" +#define SCNx16 "hx" +#define SCNX16 "hX" +#define SCNoLEAST16 "ho" +#define SCNuLEAST16 "hu" +#define SCNxLEAST16 "hx" +#define SCNXLEAST16 "hX" +#define SCNoFAST16 "ho" +#define SCNuFAST16 "hu" +#define SCNxFAST16 "hx" +#define SCNXFAST16 "hX" + +#define SCNo32 "lo" +#define SCNu32 "lu" +#define SCNx32 "lx" +#define SCNX32 "lX" +#define SCNoLEAST32 "lo" +#define SCNuLEAST32 "lu" +#define SCNxLEAST32 "lx" +#define SCNXLEAST32 "lX" +#define SCNoFAST32 "lo" +#define SCNuFAST32 "lu" +#define SCNxFAST32 "lx" +#define SCNXFAST32 "lX" + +#define SCNo64 "I64o" +#define SCNu64 "I64u" +#define SCNx64 "I64x" +#define SCNX64 "I64X" +#define SCNoLEAST64 "I64o" +#define SCNuLEAST64 "I64u" +#define SCNxLEAST64 "I64x" +#define SCNXLEAST64 "I64X" +#define SCNoFAST64 "I64o" +#define SCNuFAST64 "I64u" +#define SCNxFAST64 "I64x" +#define SCNXFAST64 "I64X" + +#define SCNoMAX "I64o" +#define SCNuMAX "I64u" +#define SCNxMAX "I64x" +#define SCNXMAX "I64X" + +#ifdef _WIN64 // [ +# define SCNoPTR "I64o" +# define SCNuPTR "I64u" +# define SCNxPTR "I64x" +# define SCNXPTR "I64X" +#else // _WIN64 ][ +# define SCNoPTR "lo" +# define SCNuPTR "lu" +# define SCNxPTR "lx" +# define SCNXPTR "lX" +#endif // _WIN64 ] + +#endif // __STDC_FORMAT_MACROS ] + +// 7.8.2 Functions for greatest-width integer types + +// 7.8.2.1 The imaxabs function +#define imaxabs _abs64 + +// 7.8.2.2 The imaxdiv function + +// This is modified version of div() function from Microsoft's div.c found +// in %MSVC.NET%\crt\src\div.c +#ifdef STATIC_IMAXDIV // [ +static +#else // STATIC_IMAXDIV ][ +_inline +#endif // STATIC_IMAXDIV ] +imaxdiv_t __cdecl imaxdiv(intmax_t numer, intmax_t denom) +{ + imaxdiv_t result; + + result.quot = numer / denom; + result.rem = numer % denom; + + if (numer < 0 && result.rem > 0) { + // did division wrong; must fix up + ++result.quot; + result.rem -= denom; + } + + return result; +} + +// 7.8.2.3 The strtoimax and strtoumax functions +#define strtoimax _strtoi64 +#define strtoumax _strtoui64 + +// 7.8.2.4 The wcstoimax and wcstoumax functions +#define wcstoimax _wcstoi64 +#define wcstoumax _wcstoui64 + +#endif // _MSC_VER >= 1800 + +#endif // _MSC_INTTYPES_H_ ] diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/msinttypes/stdint.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/msinttypes/stdint.h new file mode 100644 index 00000000..3d4477b9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/msinttypes/stdint.h @@ -0,0 +1,300 @@ +// ISO C9x compliant stdint.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2013 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the product nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +// The above software in this distribution may have been modified by +// THL A29 Limited ("Tencent Modifications"). +// All Tencent Modifications are Copyright (C) 2015 THL A29 Limited. + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_STDINT_H_ // [ +#define _MSC_STDINT_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +// miloyip: Originally Visual Studio 2010 uses its own stdint.h. However it generates warning with INT64_C(), so change to use this file for vs2010. +#if _MSC_VER >= 1600 // [ +#include <stdint.h> + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +#undef INT8_C +#undef INT16_C +#undef INT32_C +#undef INT64_C +#undef UINT8_C +#undef UINT16_C +#undef UINT32_C +#undef UINT64_C + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +// These #ifndef's are needed to prevent collisions with <boost/cstdint.hpp>. +// Check out Issue 9 for the details. +#ifndef INTMAX_C // [ +# define INTMAX_C INT64_C +#endif // INTMAX_C ] +#ifndef UINTMAX_C // [ +# define UINTMAX_C UINT64_C +#endif // UINTMAX_C ] + +#endif // __STDC_CONSTANT_MACROS ] + +#else // ] _MSC_VER >= 1700 [ + +#include <limits.h> + +// For Visual Studio 6 in C++ mode and for many Visual Studio versions when +// compiling for ARM we have to wrap <wchar.h> include with 'extern "C++" {}' +// or compiler would give many errors like this: +// error C2733: second C linkage of overloaded function 'wmemchr' not allowed +#if defined(__cplusplus) && !defined(_M_ARM) +extern "C" { +#endif +# include <wchar.h> +#if defined(__cplusplus) && !defined(_M_ARM) +} +#endif + +// Define _W64 macros to mark types changing their size, like intptr_t. +#ifndef _W64 +# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 +# define _W64 __w64 +# else +# define _W64 +# endif +#endif + + +// 7.18.1 Integer types + +// 7.18.1.1 Exact-width integer types + +// Visual Studio 6 and Embedded Visual C++ 4 doesn't +// realize that, e.g. char has the same size as __int8 +// so we give up on __intX for them. +#if (_MSC_VER < 1300) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; +#else + typedef signed __int8 int8_t; + typedef signed __int16 int16_t; + typedef signed __int32 int32_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; +#endif +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + + +// 7.18.1.2 Minimum-width integer types +typedef int8_t int_least8_t; +typedef int16_t int_least16_t; +typedef int32_t int_least32_t; +typedef int64_t int_least64_t; +typedef uint8_t uint_least8_t; +typedef uint16_t uint_least16_t; +typedef uint32_t uint_least32_t; +typedef uint64_t uint_least64_t; + +// 7.18.1.3 Fastest minimum-width integer types +typedef int8_t int_fast8_t; +typedef int16_t int_fast16_t; +typedef int32_t int_fast32_t; +typedef int64_t int_fast64_t; +typedef uint8_t uint_fast8_t; +typedef uint16_t uint_fast16_t; +typedef uint32_t uint_fast32_t; +typedef uint64_t uint_fast64_t; + +// 7.18.1.4 Integer types capable of holding object pointers +#ifdef _WIN64 // [ + typedef signed __int64 intptr_t; + typedef unsigned __int64 uintptr_t; +#else // _WIN64 ][ + typedef _W64 signed int intptr_t; + typedef _W64 unsigned int uintptr_t; +#endif // _WIN64 ] + +// 7.18.1.5 Greatest-width integer types +typedef int64_t intmax_t; +typedef uint64_t uintmax_t; + + +// 7.18.2 Limits of specified-width integer types + +#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 + +// 7.18.2.1 Limits of exact-width integer types +#define INT8_MIN ((int8_t)_I8_MIN) +#define INT8_MAX _I8_MAX +#define INT16_MIN ((int16_t)_I16_MIN) +#define INT16_MAX _I16_MAX +#define INT32_MIN ((int32_t)_I32_MIN) +#define INT32_MAX _I32_MAX +#define INT64_MIN ((int64_t)_I64_MIN) +#define INT64_MAX _I64_MAX +#define UINT8_MAX _UI8_MAX +#define UINT16_MAX _UI16_MAX +#define UINT32_MAX _UI32_MAX +#define UINT64_MAX _UI64_MAX + +// 7.18.2.2 Limits of minimum-width integer types +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST8_MAX INT8_MAX +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST16_MAX INT16_MAX +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST32_MAX INT32_MAX +#define INT_LEAST64_MIN INT64_MIN +#define INT_LEAST64_MAX INT64_MAX +#define UINT_LEAST8_MAX UINT8_MAX +#define UINT_LEAST16_MAX UINT16_MAX +#define UINT_LEAST32_MAX UINT32_MAX +#define UINT_LEAST64_MAX UINT64_MAX + +// 7.18.2.3 Limits of fastest minimum-width integer types +#define INT_FAST8_MIN INT8_MIN +#define INT_FAST8_MAX INT8_MAX +#define INT_FAST16_MIN INT16_MIN +#define INT_FAST16_MAX INT16_MAX +#define INT_FAST32_MIN INT32_MIN +#define INT_FAST32_MAX INT32_MAX +#define INT_FAST64_MIN INT64_MIN +#define INT_FAST64_MAX INT64_MAX +#define UINT_FAST8_MAX UINT8_MAX +#define UINT_FAST16_MAX UINT16_MAX +#define UINT_FAST32_MAX UINT32_MAX +#define UINT_FAST64_MAX UINT64_MAX + +// 7.18.2.4 Limits of integer types capable of holding object pointers +#ifdef _WIN64 // [ +# define INTPTR_MIN INT64_MIN +# define INTPTR_MAX INT64_MAX +# define UINTPTR_MAX UINT64_MAX +#else // _WIN64 ][ +# define INTPTR_MIN INT32_MIN +# define INTPTR_MAX INT32_MAX +# define UINTPTR_MAX UINT32_MAX +#endif // _WIN64 ] + +// 7.18.2.5 Limits of greatest-width integer types +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#define UINTMAX_MAX UINT64_MAX + +// 7.18.3 Limits of other integer types + +#ifdef _WIN64 // [ +# define PTRDIFF_MIN _I64_MIN +# define PTRDIFF_MAX _I64_MAX +#else // _WIN64 ][ +# define PTRDIFF_MIN _I32_MIN +# define PTRDIFF_MAX _I32_MAX +#endif // _WIN64 ] + +#define SIG_ATOMIC_MIN INT_MIN +#define SIG_ATOMIC_MAX INT_MAX + +#ifndef SIZE_MAX // [ +# ifdef _WIN64 // [ +# define SIZE_MAX _UI64_MAX +# else // _WIN64 ][ +# define SIZE_MAX _UI32_MAX +# endif // _WIN64 ] +#endif // SIZE_MAX ] + +// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h> +#ifndef WCHAR_MIN // [ +# define WCHAR_MIN 0 +#endif // WCHAR_MIN ] +#ifndef WCHAR_MAX // [ +# define WCHAR_MAX _UI16_MAX +#endif // WCHAR_MAX ] + +#define WINT_MIN 0 +#define WINT_MAX _UI16_MAX + +#endif // __STDC_LIMIT_MACROS ] + + +// 7.18.4 Limits of other integer types + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +// These #ifndef's are needed to prevent collisions with <boost/cstdint.hpp>. +// Check out Issue 9 for the details. +#ifndef INTMAX_C // [ +# define INTMAX_C INT64_C +#endif // INTMAX_C ] +#ifndef UINTMAX_C // [ +# define UINTMAX_C UINT64_C +#endif // UINTMAX_C ] + +#endif // __STDC_CONSTANT_MACROS ] + +#endif // _MSC_VER >= 1600 ] + +#endif // _MSC_STDINT_H_ ] diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/ostreamwrapper.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/ostreamwrapper.h new file mode 100644 index 00000000..11ed4d33 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/ostreamwrapper.h @@ -0,0 +1,81 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_OSTREAMWRAPPER_H_ +#define RAPIDJSON_OSTREAMWRAPPER_H_ + +#include "stream.h" +#include <iosfwd> + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Wrapper of \c std::basic_ostream into RapidJSON's Stream concept. +/*! + The classes can be wrapped including but not limited to: + + - \c std::ostringstream + - \c std::stringstream + - \c std::wpstringstream + - \c std::wstringstream + - \c std::ifstream + - \c std::fstream + - \c std::wofstream + - \c std::wfstream + + \tparam StreamType Class derived from \c std::basic_ostream. +*/ + +template <typename StreamType> +class BasicOStreamWrapper { +public: + typedef typename StreamType::char_type Ch; + BasicOStreamWrapper(StreamType& stream) : stream_(stream) {} + + void Put(Ch c) { + stream_.put(c); + } + + void Flush() { + stream_.flush(); + } + + // Not implemented + char Peek() const { RAPIDJSON_ASSERT(false); return 0; } + char Take() { RAPIDJSON_ASSERT(false); return 0; } + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + char* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(char*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + BasicOStreamWrapper(const BasicOStreamWrapper&); + BasicOStreamWrapper& operator=(const BasicOStreamWrapper&); + + StreamType& stream_; +}; + +typedef BasicOStreamWrapper<std::ostream> OStreamWrapper; +typedef BasicOStreamWrapper<std::wostream> WOStreamWrapper; + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_OSTREAMWRAPPER_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/pointer.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/pointer.h new file mode 100644 index 00000000..90e5903b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/pointer.h @@ -0,0 +1,1415 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_POINTER_H_ +#define RAPIDJSON_POINTER_H_ + +#include "document.h" +#include "internal/itoa.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(switch-enum) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +static const SizeType kPointerInvalidIndex = ~SizeType(0); //!< Represents an invalid index in GenericPointer::Token + +//! Error code of parsing. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericPointer::GenericPointer, GenericPointer::GetParseErrorCode +*/ +enum PointerParseErrorCode { + kPointerParseErrorNone = 0, //!< The parse is successful + + kPointerParseErrorTokenMustBeginWithSolidus, //!< A token must begin with a '/' + kPointerParseErrorInvalidEscape, //!< Invalid escape + kPointerParseErrorInvalidPercentEncoding, //!< Invalid percent encoding in URI fragment + kPointerParseErrorCharacterMustPercentEncode //!< A character must percent encoded in URI fragment +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericPointer + +//! Represents a JSON Pointer. Use Pointer for UTF8 encoding and default allocator. +/*! + This class implements RFC 6901 "JavaScript Object Notation (JSON) Pointer" + (https://tools.ietf.org/html/rfc6901). + + A JSON pointer is for identifying a specific value in a JSON document + (GenericDocument). It can simplify coding of DOM tree manipulation, because it + can access multiple-level depth of DOM tree with single API call. + + After it parses a string representation (e.g. "/foo/0" or URI fragment + representation (e.g. "#/foo/0") into its internal representation (tokens), + it can be used to resolve a specific value in multiple documents, or sub-tree + of documents. + + Contrary to GenericValue, Pointer can be copy constructed and copy assigned. + Apart from assignment, a Pointer cannot be modified after construction. + + Although Pointer is very convenient, please aware that constructing Pointer + involves parsing and dynamic memory allocation. A special constructor with user- + supplied tokens eliminates these. + + GenericPointer depends on GenericDocument and GenericValue. + + \tparam ValueType The value type of the DOM tree. E.g. GenericValue<UTF8<> > + \tparam Allocator The allocator type for allocating memory for internal representation. + + \note GenericPointer uses same encoding of ValueType. + However, Allocator of GenericPointer is independent of Allocator of Value. +*/ +template <typename ValueType, typename Allocator = CrtAllocator> +class GenericPointer { +public: + typedef typename ValueType::EncodingType EncodingType; //!< Encoding type from Value + typedef typename ValueType::Ch Ch; //!< Character type from Value + + //! A token is the basic units of internal representation. + /*! + A JSON pointer string representation "/foo/123" is parsed to two tokens: + "foo" and 123. 123 will be represented in both numeric form and string form. + They are resolved according to the actual value type (object or array). + + For token that are not numbers, or the numeric value is out of bound + (greater than limits of SizeType), they are only treated as string form + (i.e. the token's index will be equal to kPointerInvalidIndex). + + This struct is public so that user can create a Pointer without parsing and + allocation, using a special constructor. + */ + struct Token { + const Ch* name; //!< Name of the token. It has null character at the end but it can contain null character. + SizeType length; //!< Length of the name. + SizeType index; //!< A valid array index, if it is not equal to kPointerInvalidIndex. + }; + + //!@name Constructors and destructor. + //@{ + + //! Default constructor. + GenericPointer(Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} + + //! Constructor that parses a string or URI fragment representation. + /*! + \param source A null-terminated, string or URI fragment representation of JSON pointer. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + */ + explicit GenericPointer(const Ch* source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source, internal::StrLen(source)); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Constructor that parses a string or URI fragment representation. + /*! + \param source A string or URI fragment representation of JSON pointer. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + explicit GenericPointer(const std::basic_string<Ch>& source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source.c_str(), source.size()); + } +#endif + + //! Constructor that parses a string or URI fragment representation, with length of the source string. + /*! + \param source A string or URI fragment representation of JSON pointer. + \param length Length of source. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + \note Slightly faster than the overload without length. + */ + GenericPointer(const Ch* source, size_t length, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source, length); + } + + //! Constructor with user-supplied tokens. + /*! + This constructor let user supplies const array of tokens. + This prevents the parsing process and eliminates allocation. + This is preferred for memory constrained environments. + + \param tokens An constant array of tokens representing the JSON pointer. + \param tokenCount Number of tokens. + + \b Example + \code + #define NAME(s) { s, sizeof(s) / sizeof(s[0]) - 1, kPointerInvalidIndex } + #define INDEX(i) { #i, sizeof(#i) - 1, i } + + static const Pointer::Token kTokens[] = { NAME("foo"), INDEX(123) }; + static const Pointer p(kTokens, sizeof(kTokens) / sizeof(kTokens[0])); + // Equivalent to static const Pointer p("/foo/123"); + + #undef NAME + #undef INDEX + \endcode + */ + GenericPointer(const Token* tokens, size_t tokenCount) : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(const_cast<Token*>(tokens)), tokenCount_(tokenCount), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} + + //! Copy constructor. + GenericPointer(const GenericPointer& rhs) : allocator_(rhs.allocator_), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + *this = rhs; + } + + //! Copy constructor. + GenericPointer(const GenericPointer& rhs, Allocator* allocator) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + *this = rhs; + } + + //! Destructor. + ~GenericPointer() { + if (nameBuffer_) // If user-supplied tokens constructor is used, nameBuffer_ is nullptr and tokens_ are not deallocated. + Allocator::Free(tokens_); + RAPIDJSON_DELETE(ownAllocator_); + } + + //! Assignment operator. + GenericPointer& operator=(const GenericPointer& rhs) { + if (this != &rhs) { + // Do not delete ownAllcator + if (nameBuffer_) + Allocator::Free(tokens_); + + tokenCount_ = rhs.tokenCount_; + parseErrorOffset_ = rhs.parseErrorOffset_; + parseErrorCode_ = rhs.parseErrorCode_; + + if (rhs.nameBuffer_) + CopyFromRaw(rhs); // Normally parsed tokens. + else { + tokens_ = rhs.tokens_; // User supplied const tokens. + nameBuffer_ = 0; + } + } + return *this; + } + + //! Swap the content of this pointer with an other. + /*! + \param other The pointer to swap with. + \note Constant complexity. + */ + GenericPointer& Swap(GenericPointer& other) RAPIDJSON_NOEXCEPT { + internal::Swap(allocator_, other.allocator_); + internal::Swap(ownAllocator_, other.ownAllocator_); + internal::Swap(nameBuffer_, other.nameBuffer_); + internal::Swap(tokens_, other.tokens_); + internal::Swap(tokenCount_, other.tokenCount_); + internal::Swap(parseErrorOffset_, other.parseErrorOffset_); + internal::Swap(parseErrorCode_, other.parseErrorCode_); + return *this; + } + + //! free-standing swap function helper + /*! + Helper function to enable support for common swap implementation pattern based on \c std::swap: + \code + void swap(MyClass& a, MyClass& b) { + using std::swap; + swap(a.pointer, b.pointer); + // ... + } + \endcode + \see Swap() + */ + friend inline void swap(GenericPointer& a, GenericPointer& b) RAPIDJSON_NOEXCEPT { a.Swap(b); } + + //@} + + //!@name Append token + //@{ + + //! Append a token and return a new Pointer + /*! + \param token Token to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const Token& token, Allocator* allocator = 0) const { + GenericPointer r; + r.allocator_ = allocator; + Ch *p = r.CopyFromRaw(*this, 1, token.length + 1); + std::memcpy(p, token.name, (token.length + 1) * sizeof(Ch)); + r.tokens_[tokenCount_].name = p; + r.tokens_[tokenCount_].length = token.length; + r.tokens_[tokenCount_].index = token.index; + return r; + } + + //! Append a name token with length, and return a new Pointer + /*! + \param name Name to be appended. + \param length Length of name. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const Ch* name, SizeType length, Allocator* allocator = 0) const { + Token token = { name, length, kPointerInvalidIndex }; + return Append(token, allocator); + } + + //! Append a name token without length, and return a new Pointer + /*! + \param name Name (const Ch*) to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + template <typename T> + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr<internal::IsSame<typename internal::RemoveConst<T>::Type, Ch> >), (GenericPointer)) + Append(T* name, Allocator* allocator = 0) const { + return Append(name, internal::StrLen(name), allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Append a name token, and return a new Pointer + /*! + \param name Name to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const std::basic_string<Ch>& name, Allocator* allocator = 0) const { + return Append(name.c_str(), static_cast<SizeType>(name.size()), allocator); + } +#endif + + //! Append a index token, and return a new Pointer + /*! + \param index Index to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(SizeType index, Allocator* allocator = 0) const { + char buffer[21]; + char* end = sizeof(SizeType) == 4 ? internal::u32toa(index, buffer) : internal::u64toa(index, buffer); + SizeType length = static_cast<SizeType>(end - buffer); + buffer[length] = '\0'; + + if (sizeof(Ch) == 1) { + Token token = { reinterpret_cast<Ch*>(buffer), length, index }; + return Append(token, allocator); + } + else { + Ch name[21]; + for (size_t i = 0; i <= length; i++) + name[i] = static_cast<Ch>(buffer[i]); + Token token = { name, length, index }; + return Append(token, allocator); + } + } + + //! Append a token by value, and return a new Pointer + /*! + \param token token to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const ValueType& token, Allocator* allocator = 0) const { + if (token.IsString()) + return Append(token.GetString(), token.GetStringLength(), allocator); + else { + RAPIDJSON_ASSERT(token.IsUint64()); + RAPIDJSON_ASSERT(token.GetUint64() <= SizeType(~0)); + return Append(static_cast<SizeType>(token.GetUint64()), allocator); + } + } + + //!@name Handling Parse Error + //@{ + + //! Check whether this is a valid pointer. + bool IsValid() const { return parseErrorCode_ == kPointerParseErrorNone; } + + //! Get the parsing error offset in code unit. + size_t GetParseErrorOffset() const { return parseErrorOffset_; } + + //! Get the parsing error code. + PointerParseErrorCode GetParseErrorCode() const { return parseErrorCode_; } + + //@} + + //! Get the allocator of this pointer. + Allocator& GetAllocator() { return *allocator_; } + + //!@name Tokens + //@{ + + //! Get the token array (const version only). + const Token* GetTokens() const { return tokens_; } + + //! Get the number of tokens. + size_t GetTokenCount() const { return tokenCount_; } + + //@} + + //!@name Equality/inequality operators + //@{ + + //! Equality operator. + /*! + \note When any pointers are invalid, always returns false. + */ + bool operator==(const GenericPointer& rhs) const { + if (!IsValid() || !rhs.IsValid() || tokenCount_ != rhs.tokenCount_) + return false; + + for (size_t i = 0; i < tokenCount_; i++) { + if (tokens_[i].index != rhs.tokens_[i].index || + tokens_[i].length != rhs.tokens_[i].length || + (tokens_[i].length != 0 && std::memcmp(tokens_[i].name, rhs.tokens_[i].name, sizeof(Ch)* tokens_[i].length) != 0)) + { + return false; + } + } + + return true; + } + + //! Inequality operator. + /*! + \note When any pointers are invalid, always returns true. + */ + bool operator!=(const GenericPointer& rhs) const { return !(*this == rhs); } + + //! Less than operator. + /*! + \note Invalid pointers are always greater than valid ones. + */ + bool operator<(const GenericPointer& rhs) const { + if (!IsValid()) + return false; + if (!rhs.IsValid()) + return true; + + if (tokenCount_ != rhs.tokenCount_) + return tokenCount_ < rhs.tokenCount_; + + for (size_t i = 0; i < tokenCount_; i++) { + if (tokens_[i].index != rhs.tokens_[i].index) + return tokens_[i].index < rhs.tokens_[i].index; + + if (tokens_[i].length != rhs.tokens_[i].length) + return tokens_[i].length < rhs.tokens_[i].length; + + if (int cmp = std::memcmp(tokens_[i].name, rhs.tokens_[i].name, sizeof(Ch) * tokens_[i].length)) + return cmp < 0; + } + + return false; + } + + //@} + + //!@name Stringify + //@{ + + //! Stringify the pointer into string representation. + /*! + \tparam OutputStream Type of output stream. + \param os The output stream. + */ + template<typename OutputStream> + bool Stringify(OutputStream& os) const { + return Stringify<false, OutputStream>(os); + } + + //! Stringify the pointer into URI fragment representation. + /*! + \tparam OutputStream Type of output stream. + \param os The output stream. + */ + template<typename OutputStream> + bool StringifyUriFragment(OutputStream& os) const { + return Stringify<true, OutputStream>(os); + } + + //@} + + //!@name Create value + //@{ + + //! Create a value in a subtree. + /*! + If the value is not exist, it creates all parent values and a JSON Null value. + So it always succeed and return the newly created or existing value. + + Remind that it may change types of parents according to tokens, so it + potentially removes previously stored values. For example, if a document + was an array, and "/foo" is used to create a value, then the document + will be changed to an object, and all existing array elements are lost. + + \param root Root value of a DOM subtree to be resolved. It can be any value other than document root. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \param alreadyExist If non-null, it stores whether the resolved value is already exist. + \return The resolved newly created (a JSON Null value), or already exists value. + */ + ValueType& Create(ValueType& root, typename ValueType::AllocatorType& allocator, bool* alreadyExist = 0) const { + RAPIDJSON_ASSERT(IsValid()); + ValueType* v = &root; + bool exist = true; + for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + if (v->IsArray() && t->name[0] == '-' && t->length == 1) { + v->PushBack(ValueType().Move(), allocator); + v = &((*v)[v->Size() - 1]); + exist = false; + } + else { + if (t->index == kPointerInvalidIndex) { // must be object name + if (!v->IsObject()) + v->SetObject(); // Change to Object + } + else { // object name or array index + if (!v->IsArray() && !v->IsObject()) + v->SetArray(); // Change to Array + } + + if (v->IsArray()) { + if (t->index >= v->Size()) { + v->Reserve(t->index + 1, allocator); + while (t->index >= v->Size()) + v->PushBack(ValueType().Move(), allocator); + exist = false; + } + v = &((*v)[t->index]); + } + else { + typename ValueType::MemberIterator m = v->FindMember(GenericValue<EncodingType>(GenericStringRef<Ch>(t->name, t->length))); + if (m == v->MemberEnd()) { + v->AddMember(ValueType(t->name, t->length, allocator).Move(), ValueType().Move(), allocator); + m = v->MemberEnd(); + v = &(--m)->value; // Assumes AddMember() appends at the end + exist = false; + } + else + v = &m->value; + } + } + } + + if (alreadyExist) + *alreadyExist = exist; + + return *v; + } + + //! Creates a value in a document. + /*! + \param document A document to be resolved. + \param alreadyExist If non-null, it stores whether the resolved value is already exist. + \return The resolved newly created, or already exists value. + */ + template <typename stackAllocator> + ValueType& Create(GenericDocument<EncodingType, typename ValueType::AllocatorType, stackAllocator>& document, bool* alreadyExist = 0) const { + return Create(document, document.GetAllocator(), alreadyExist); + } + + //@} + + //!@name Query value + //@{ + + //! Query a value in a subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param unresolvedTokenIndex If the pointer cannot resolve a token in the pointer, this parameter can obtain the index of unresolved token. + \return Pointer to the value if it can be resolved. Otherwise null. + + \note + There are only 3 situations when a value cannot be resolved: + 1. A value in the path is not an array nor object. + 2. An object value does not contain the token. + 3. A token is out of range of an array value. + + Use unresolvedTokenIndex to retrieve the token index. + */ + ValueType* Get(ValueType& root, size_t* unresolvedTokenIndex = 0) const { + RAPIDJSON_ASSERT(IsValid()); + ValueType* v = &root; + for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + switch (v->GetType()) { + case kObjectType: + { + typename ValueType::MemberIterator m = v->FindMember(GenericValue<EncodingType>(GenericStringRef<Ch>(t->name, t->length))); + if (m == v->MemberEnd()) + break; + v = &m->value; + } + continue; + case kArrayType: + if (t->index == kPointerInvalidIndex || t->index >= v->Size()) + break; + v = &((*v)[t->index]); + continue; + default: + break; + } + + // Error: unresolved token + if (unresolvedTokenIndex) + *unresolvedTokenIndex = static_cast<size_t>(t - tokens_); + return 0; + } + return v; + } + + //! Query a const value in a const subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \return Pointer to the value if it can be resolved. Otherwise null. + */ + const ValueType* Get(const ValueType& root, size_t* unresolvedTokenIndex = 0) const { + return Get(const_cast<ValueType&>(root), unresolvedTokenIndex); + } + + //@} + + //!@name Query a value with default + //@{ + + //! Query a value in a subtree with default value. + /*! + Similar to Get(), but if the specified value do not exists, it creates all parents and clone the default value. + So that this function always succeed. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param defaultValue Default value to be cloned if the value was not exists. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& GetWithDefault(ValueType& root, const ValueType& defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + ValueType& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.CopyFrom(defaultValue, allocator); + } + + //! Query a value in a subtree with default null-terminated string. + ValueType& GetWithDefault(ValueType& root, const Ch* defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + ValueType& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.SetString(defaultValue, allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Query a value in a subtree with default std::basic_string. + ValueType& GetWithDefault(ValueType& root, const std::basic_string<Ch>& defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + ValueType& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.SetString(defaultValue, allocator); + } +#endif + + //! Query a value in a subtree with default primitive value. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template <typename T> + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr<internal::IsPointer<T>, internal::IsGenericValue<T> >), (ValueType&)) + GetWithDefault(ValueType& root, T defaultValue, typename ValueType::AllocatorType& allocator) const { + return GetWithDefault(root, ValueType(defaultValue).Move(), allocator); + } + + //! Query a value in a document with default value. + template <typename stackAllocator> + ValueType& GetWithDefault(GenericDocument<EncodingType, typename ValueType::AllocatorType, stackAllocator>& document, const ValueType& defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + + //! Query a value in a document with default null-terminated string. + template <typename stackAllocator> + ValueType& GetWithDefault(GenericDocument<EncodingType, typename ValueType::AllocatorType, stackAllocator>& document, const Ch* defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Query a value in a document with default std::basic_string. + template <typename stackAllocator> + ValueType& GetWithDefault(GenericDocument<EncodingType, typename ValueType::AllocatorType, stackAllocator>& document, const std::basic_string<Ch>& defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } +#endif + + //! Query a value in a document with default primitive value. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template <typename T, typename stackAllocator> + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr<internal::IsPointer<T>, internal::IsGenericValue<T> >), (ValueType&)) + GetWithDefault(GenericDocument<EncodingType, typename ValueType::AllocatorType, stackAllocator>& document, T defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + + //@} + + //!@name Set a value + //@{ + + //! Set a value in a subtree, with move semantics. + /*! + It creates all parents if they are not exist or types are different to the tokens. + So this function always succeeds but potentially remove existing values. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param value Value to be set. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& Set(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = value; + } + + //! Set a value in a subtree, with copy semantics. + ValueType& Set(ValueType& root, const ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator).CopyFrom(value, allocator); + } + + //! Set a null-terminated string in a subtree. + ValueType& Set(ValueType& root, const Ch* value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value, allocator).Move(); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Set a std::basic_string in a subtree. + ValueType& Set(ValueType& root, const std::basic_string<Ch>& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value, allocator).Move(); + } +#endif + + //! Set a primitive value in a subtree. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template <typename T> + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr<internal::IsPointer<T>, internal::IsGenericValue<T> >), (ValueType&)) + Set(ValueType& root, T value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value).Move(); + } + + //! Set a value in a document, with move semantics. + template <typename stackAllocator> + ValueType& Set(GenericDocument<EncodingType, typename ValueType::AllocatorType, stackAllocator>& document, ValueType& value) const { + return Create(document) = value; + } + + //! Set a value in a document, with copy semantics. + template <typename stackAllocator> + ValueType& Set(GenericDocument<EncodingType, typename ValueType::AllocatorType, stackAllocator>& document, const ValueType& value) const { + return Create(document).CopyFrom(value, document.GetAllocator()); + } + + //! Set a null-terminated string in a document. + template <typename stackAllocator> + ValueType& Set(GenericDocument<EncodingType, typename ValueType::AllocatorType, stackAllocator>& document, const Ch* value) const { + return Create(document) = ValueType(value, document.GetAllocator()).Move(); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Sets a std::basic_string in a document. + template <typename stackAllocator> + ValueType& Set(GenericDocument<EncodingType, typename ValueType::AllocatorType, stackAllocator>& document, const std::basic_string<Ch>& value) const { + return Create(document) = ValueType(value, document.GetAllocator()).Move(); + } +#endif + + //! Set a primitive value in a document. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template <typename T, typename stackAllocator> + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr<internal::IsPointer<T>, internal::IsGenericValue<T> >), (ValueType&)) + Set(GenericDocument<EncodingType, typename ValueType::AllocatorType, stackAllocator>& document, T value) const { + return Create(document) = value; + } + + //@} + + //!@name Swap a value + //@{ + + //! Swap a value with a value in a subtree. + /*! + It creates all parents if they are not exist or types are different to the tokens. + So this function always succeeds but potentially remove existing values. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param value Value to be swapped. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& Swap(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator).Swap(value); + } + + //! Swap a value with a value in a document. + template <typename stackAllocator> + ValueType& Swap(GenericDocument<EncodingType, typename ValueType::AllocatorType, stackAllocator>& document, ValueType& value) const { + return Create(document).Swap(value); + } + + //@} + + //! Erase a value in a subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \return Whether the resolved value is found and erased. + + \note Erasing with an empty pointer \c Pointer(""), i.e. the root, always fail and return false. + */ + bool Erase(ValueType& root) const { + RAPIDJSON_ASSERT(IsValid()); + if (tokenCount_ == 0) // Cannot erase the root + return false; + + ValueType* v = &root; + const Token* last = tokens_ + (tokenCount_ - 1); + for (const Token *t = tokens_; t != last; ++t) { + switch (v->GetType()) { + case kObjectType: + { + typename ValueType::MemberIterator m = v->FindMember(GenericValue<EncodingType>(GenericStringRef<Ch>(t->name, t->length))); + if (m == v->MemberEnd()) + return false; + v = &m->value; + } + break; + case kArrayType: + if (t->index == kPointerInvalidIndex || t->index >= v->Size()) + return false; + v = &((*v)[t->index]); + break; + default: + return false; + } + } + + switch (v->GetType()) { + case kObjectType: + return v->EraseMember(GenericStringRef<Ch>(last->name, last->length)); + case kArrayType: + if (last->index == kPointerInvalidIndex || last->index >= v->Size()) + return false; + v->Erase(v->Begin() + last->index); + return true; + default: + return false; + } + } + +private: + //! Clone the content from rhs to this. + /*! + \param rhs Source pointer. + \param extraToken Extra tokens to be allocated. + \param extraNameBufferSize Extra name buffer size (in number of Ch) to be allocated. + \return Start of non-occupied name buffer, for storing extra names. + */ + Ch* CopyFromRaw(const GenericPointer& rhs, size_t extraToken = 0, size_t extraNameBufferSize = 0) { + if (!allocator_) // allocator is independently owned. + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + + size_t nameBufferSize = rhs.tokenCount_; // null terminators for tokens + for (Token *t = rhs.tokens_; t != rhs.tokens_ + rhs.tokenCount_; ++t) + nameBufferSize += t->length; + + tokenCount_ = rhs.tokenCount_ + extraToken; + tokens_ = static_cast<Token *>(allocator_->Malloc(tokenCount_ * sizeof(Token) + (nameBufferSize + extraNameBufferSize) * sizeof(Ch))); + nameBuffer_ = reinterpret_cast<Ch *>(tokens_ + tokenCount_); + if (rhs.tokenCount_ > 0) { + std::memcpy(tokens_, rhs.tokens_, rhs.tokenCount_ * sizeof(Token)); + } + if (nameBufferSize > 0) { + std::memcpy(nameBuffer_, rhs.nameBuffer_, nameBufferSize * sizeof(Ch)); + } + + // Adjust pointers to name buffer + std::ptrdiff_t diff = nameBuffer_ - rhs.nameBuffer_; + for (Token *t = tokens_; t != tokens_ + rhs.tokenCount_; ++t) + t->name += diff; + + return nameBuffer_ + nameBufferSize; + } + + //! Check whether a character should be percent-encoded. + /*! + According to RFC 3986 2.3 Unreserved Characters. + \param c The character (code unit) to be tested. + */ + bool NeedPercentEncode(Ch c) const { + return !((c >= '0' && c <= '9') || (c >= 'A' && c <='Z') || (c >= 'a' && c <= 'z') || c == '-' || c == '.' || c == '_' || c =='~'); + } + + //! Parse a JSON String or its URI fragment representation into tokens. +#ifndef __clang__ // -Wdocumentation + /*! + \param source Either a JSON Pointer string, or its URI fragment representation. Not need to be null terminated. + \param length Length of the source string. + \note Source cannot be JSON String Representation of JSON Pointer, e.g. In "/\u0000", \u0000 will not be unescaped. + */ +#endif + void Parse(const Ch* source, size_t length) { + RAPIDJSON_ASSERT(source != NULL); + RAPIDJSON_ASSERT(nameBuffer_ == 0); + RAPIDJSON_ASSERT(tokens_ == 0); + + // Create own allocator if user did not supply. + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + + // Count number of '/' as tokenCount + tokenCount_ = 0; + for (const Ch* s = source; s != source + length; s++) + if (*s == '/') + tokenCount_++; + + Token* token = tokens_ = static_cast<Token *>(allocator_->Malloc(tokenCount_ * sizeof(Token) + length * sizeof(Ch))); + Ch* name = nameBuffer_ = reinterpret_cast<Ch *>(tokens_ + tokenCount_); + size_t i = 0; + + // Detect if it is a URI fragment + bool uriFragment = false; + if (source[i] == '#') { + uriFragment = true; + i++; + } + + if (i != length && source[i] != '/') { + parseErrorCode_ = kPointerParseErrorTokenMustBeginWithSolidus; + goto error; + } + + while (i < length) { + RAPIDJSON_ASSERT(source[i] == '/'); + i++; // consumes '/' + + token->name = name; + bool isNumber = true; + + while (i < length && source[i] != '/') { + Ch c = source[i]; + if (uriFragment) { + // Decoding percent-encoding for URI fragment + if (c == '%') { + PercentDecodeStream is(&source[i], source + length); + GenericInsituStringStream<EncodingType> os(name); + Ch* begin = os.PutBegin(); + if (!Transcoder<UTF8<>, EncodingType>().Validate(is, os) || !is.IsValid()) { + parseErrorCode_ = kPointerParseErrorInvalidPercentEncoding; + goto error; + } + size_t len = os.PutEnd(begin); + i += is.Tell() - 1; + if (len == 1) + c = *name; + else { + name += len; + isNumber = false; + i++; + continue; + } + } + else if (NeedPercentEncode(c)) { + parseErrorCode_ = kPointerParseErrorCharacterMustPercentEncode; + goto error; + } + } + + i++; + + // Escaping "~0" -> '~', "~1" -> '/' + if (c == '~') { + if (i < length) { + c = source[i]; + if (c == '0') c = '~'; + else if (c == '1') c = '/'; + else { + parseErrorCode_ = kPointerParseErrorInvalidEscape; + goto error; + } + i++; + } + else { + parseErrorCode_ = kPointerParseErrorInvalidEscape; + goto error; + } + } + + // First check for index: all of characters are digit + if (c < '0' || c > '9') + isNumber = false; + + *name++ = c; + } + token->length = static_cast<SizeType>(name - token->name); + if (token->length == 0) + isNumber = false; + *name++ = '\0'; // Null terminator + + // Second check for index: more than one digit cannot have leading zero + if (isNumber && token->length > 1 && token->name[0] == '0') + isNumber = false; + + // String to SizeType conversion + SizeType n = 0; + if (isNumber) { + for (size_t j = 0; j < token->length; j++) { + SizeType m = n * 10 + static_cast<SizeType>(token->name[j] - '0'); + if (m < n) { // overflow detection + isNumber = false; + break; + } + n = m; + } + } + + token->index = isNumber ? n : kPointerInvalidIndex; + token++; + } + + RAPIDJSON_ASSERT(name <= nameBuffer_ + length); // Should not overflow buffer + parseErrorCode_ = kPointerParseErrorNone; + return; + + error: + Allocator::Free(tokens_); + nameBuffer_ = 0; + tokens_ = 0; + tokenCount_ = 0; + parseErrorOffset_ = i; + return; + } + + //! Stringify to string or URI fragment representation. + /*! + \tparam uriFragment True for stringifying to URI fragment representation. False for string representation. + \tparam OutputStream type of output stream. + \param os The output stream. + */ + template<bool uriFragment, typename OutputStream> + bool Stringify(OutputStream& os) const { + RAPIDJSON_ASSERT(IsValid()); + + if (uriFragment) + os.Put('#'); + + for (Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + os.Put('/'); + for (size_t j = 0; j < t->length; j++) { + Ch c = t->name[j]; + if (c == '~') { + os.Put('~'); + os.Put('0'); + } + else if (c == '/') { + os.Put('~'); + os.Put('1'); + } + else if (uriFragment && NeedPercentEncode(c)) { + // Transcode to UTF8 sequence + GenericStringStream<typename ValueType::EncodingType> source(&t->name[j]); + PercentEncodeStream<OutputStream> target(os); + if (!Transcoder<EncodingType, UTF8<> >().Validate(source, target)) + return false; + j += source.Tell() - 1; + } + else + os.Put(c); + } + } + return true; + } + + //! A helper stream for decoding a percent-encoded sequence into code unit. + /*! + This stream decodes %XY triplet into code unit (0-255). + If it encounters invalid characters, it sets output code unit as 0 and + mark invalid, and to be checked by IsValid(). + */ + class PercentDecodeStream { + public: + typedef typename ValueType::Ch Ch; + + //! Constructor + /*! + \param source Start of the stream + \param end Past-the-end of the stream. + */ + PercentDecodeStream(const Ch* source, const Ch* end) : src_(source), head_(source), end_(end), valid_(true) {} + + Ch Take() { + if (*src_ != '%' || src_ + 3 > end_) { // %XY triplet + valid_ = false; + return 0; + } + src_++; + Ch c = 0; + for (int j = 0; j < 2; j++) { + c = static_cast<Ch>(c << 4); + Ch h = *src_; + if (h >= '0' && h <= '9') c = static_cast<Ch>(c + h - '0'); + else if (h >= 'A' && h <= 'F') c = static_cast<Ch>(c + h - 'A' + 10); + else if (h >= 'a' && h <= 'f') c = static_cast<Ch>(c + h - 'a' + 10); + else { + valid_ = false; + return 0; + } + src_++; + } + return c; + } + + size_t Tell() const { return static_cast<size_t>(src_ - head_); } + bool IsValid() const { return valid_; } + + private: + const Ch* src_; //!< Current read position. + const Ch* head_; //!< Original head of the string. + const Ch* end_; //!< Past-the-end position. + bool valid_; //!< Whether the parsing is valid. + }; + + //! A helper stream to encode character (UTF-8 code unit) into percent-encoded sequence. + template <typename OutputStream> + class PercentEncodeStream { + public: + PercentEncodeStream(OutputStream& os) : os_(os) {} + void Put(char c) { // UTF-8 must be byte + unsigned char u = static_cast<unsigned char>(c); + static const char hexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + os_.Put('%'); + os_.Put(static_cast<typename OutputStream::Ch>(hexDigits[u >> 4])); + os_.Put(static_cast<typename OutputStream::Ch>(hexDigits[u & 15])); + } + private: + OutputStream& os_; + }; + + Allocator* allocator_; //!< The current allocator. It is either user-supplied or equal to ownAllocator_. + Allocator* ownAllocator_; //!< Allocator owned by this Pointer. + Ch* nameBuffer_; //!< A buffer containing all names in tokens. + Token* tokens_; //!< A list of tokens. + size_t tokenCount_; //!< Number of tokens in tokens_. + size_t parseErrorOffset_; //!< Offset in code unit when parsing fail. + PointerParseErrorCode parseErrorCode_; //!< Parsing error code. +}; + +//! GenericPointer for Value (UTF-8, default allocator). +typedef GenericPointer<Value> Pointer; + +//!@name Helper functions for GenericPointer +//@{ + +////////////////////////////////////////////////////////////////////////////// + +template <typename T> +typename T::ValueType& CreateValueByPointer(T& root, const GenericPointer<typename T::ValueType>& pointer, typename T::AllocatorType& a) { + return pointer.Create(root, a); +} + +template <typename T, typename CharType, size_t N> +typename T::ValueType& CreateValueByPointer(T& root, const CharType(&source)[N], typename T::AllocatorType& a) { + return GenericPointer<typename T::ValueType>(source, N - 1).Create(root, a); +} + +// No allocator parameter + +template <typename DocumentType> +typename DocumentType::ValueType& CreateValueByPointer(DocumentType& document, const GenericPointer<typename DocumentType::ValueType>& pointer) { + return pointer.Create(document); +} + +template <typename DocumentType, typename CharType, size_t N> +typename DocumentType::ValueType& CreateValueByPointer(DocumentType& document, const CharType(&source)[N]) { + return GenericPointer<typename DocumentType::ValueType>(source, N - 1).Create(document); +} + +////////////////////////////////////////////////////////////////////////////// + +template <typename T> +typename T::ValueType* GetValueByPointer(T& root, const GenericPointer<typename T::ValueType>& pointer, size_t* unresolvedTokenIndex = 0) { + return pointer.Get(root, unresolvedTokenIndex); +} + +template <typename T> +const typename T::ValueType* GetValueByPointer(const T& root, const GenericPointer<typename T::ValueType>& pointer, size_t* unresolvedTokenIndex = 0) { + return pointer.Get(root, unresolvedTokenIndex); +} + +template <typename T, typename CharType, size_t N> +typename T::ValueType* GetValueByPointer(T& root, const CharType (&source)[N], size_t* unresolvedTokenIndex = 0) { + return GenericPointer<typename T::ValueType>(source, N - 1).Get(root, unresolvedTokenIndex); +} + +template <typename T, typename CharType, size_t N> +const typename T::ValueType* GetValueByPointer(const T& root, const CharType(&source)[N], size_t* unresolvedTokenIndex = 0) { + return GenericPointer<typename T::ValueType>(source, N - 1).Get(root, unresolvedTokenIndex); +} + +////////////////////////////////////////////////////////////////////////////// + +template <typename T> +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer<typename T::ValueType>& pointer, const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +template <typename T> +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer<typename T::ValueType>& pointer, const typename T::Ch* defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template <typename T> +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer<typename T::ValueType>& pointer, const std::basic_string<typename T::Ch>& defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} +#endif + +template <typename T, typename T2> +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr<internal::IsPointer<T2>, internal::IsGenericValue<T2> >), (typename T::ValueType&)) +GetValueByPointerWithDefault(T& root, const GenericPointer<typename T::ValueType>& pointer, T2 defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +template <typename T, typename CharType, size_t N> +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { + return GenericPointer<typename T::ValueType>(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +template <typename T, typename CharType, size_t N> +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::Ch* defaultValue, typename T::AllocatorType& a) { + return GenericPointer<typename T::ValueType>(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template <typename T, typename CharType, size_t N> +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const std::basic_string<typename T::Ch>& defaultValue, typename T::AllocatorType& a) { + return GenericPointer<typename T::ValueType>(source, N - 1).GetWithDefault(root, defaultValue, a); +} +#endif + +template <typename T, typename CharType, size_t N, typename T2> +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr<internal::IsPointer<T2>, internal::IsGenericValue<T2> >), (typename T::ValueType&)) +GetValueByPointerWithDefault(T& root, const CharType(&source)[N], T2 defaultValue, typename T::AllocatorType& a) { + return GenericPointer<typename T::ValueType>(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +// No allocator parameter + +template <typename DocumentType> +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer<typename DocumentType::ValueType>& pointer, const typename DocumentType::ValueType& defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +template <typename DocumentType> +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer<typename DocumentType::ValueType>& pointer, const typename DocumentType::Ch* defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +#if RAPIDJSON_HAS_STDSTRING +template <typename DocumentType> +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer<typename DocumentType::ValueType>& pointer, const std::basic_string<typename DocumentType::Ch>& defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} +#endif + +template <typename DocumentType, typename T2> +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr<internal::IsPointer<T2>, internal::IsGenericValue<T2> >), (typename DocumentType::ValueType&)) +GetValueByPointerWithDefault(DocumentType& document, const GenericPointer<typename DocumentType::ValueType>& pointer, T2 defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +template <typename DocumentType, typename CharType, size_t N> +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const typename DocumentType::ValueType& defaultValue) { + return GenericPointer<typename DocumentType::ValueType>(source, N - 1).GetWithDefault(document, defaultValue); +} + +template <typename DocumentType, typename CharType, size_t N> +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const typename DocumentType::Ch* defaultValue) { + return GenericPointer<typename DocumentType::ValueType>(source, N - 1).GetWithDefault(document, defaultValue); +} + +#if RAPIDJSON_HAS_STDSTRING +template <typename DocumentType, typename CharType, size_t N> +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const std::basic_string<typename DocumentType::Ch>& defaultValue) { + return GenericPointer<typename DocumentType::ValueType>(source, N - 1).GetWithDefault(document, defaultValue); +} +#endif + +template <typename DocumentType, typename CharType, size_t N, typename T2> +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr<internal::IsPointer<T2>, internal::IsGenericValue<T2> >), (typename DocumentType::ValueType&)) +GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], T2 defaultValue) { + return GenericPointer<typename DocumentType::ValueType>(source, N - 1).GetWithDefault(document, defaultValue); +} + +////////////////////////////////////////////////////////////////////////////// + +template <typename T> +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer<typename T::ValueType>& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template <typename T> +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer<typename T::ValueType>& pointer, const typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template <typename T> +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer<typename T::ValueType>& pointer, const typename T::Ch* value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template <typename T> +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer<typename T::ValueType>& pointer, const std::basic_string<typename T::Ch>& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} +#endif + +template <typename T, typename T2> +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr<internal::IsPointer<T2>, internal::IsGenericValue<T2> >), (typename T::ValueType&)) +SetValueByPointer(T& root, const GenericPointer<typename T::ValueType>& pointer, T2 value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template <typename T, typename CharType, size_t N> +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer<typename T::ValueType>(source, N - 1).Set(root, value, a); +} + +template <typename T, typename CharType, size_t N> +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer<typename T::ValueType>(source, N - 1).Set(root, value, a); +} + +template <typename T, typename CharType, size_t N> +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::Ch* value, typename T::AllocatorType& a) { + return GenericPointer<typename T::ValueType>(source, N - 1).Set(root, value, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template <typename T, typename CharType, size_t N> +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const std::basic_string<typename T::Ch>& value, typename T::AllocatorType& a) { + return GenericPointer<typename T::ValueType>(source, N - 1).Set(root, value, a); +} +#endif + +template <typename T, typename CharType, size_t N, typename T2> +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr<internal::IsPointer<T2>, internal::IsGenericValue<T2> >), (typename T::ValueType&)) +SetValueByPointer(T& root, const CharType(&source)[N], T2 value, typename T::AllocatorType& a) { + return GenericPointer<typename T::ValueType>(source, N - 1).Set(root, value, a); +} + +// No allocator parameter + +template <typename DocumentType> +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer<typename DocumentType::ValueType>& pointer, typename DocumentType::ValueType& value) { + return pointer.Set(document, value); +} + +template <typename DocumentType> +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer<typename DocumentType::ValueType>& pointer, const typename DocumentType::ValueType& value) { + return pointer.Set(document, value); +} + +template <typename DocumentType> +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer<typename DocumentType::ValueType>& pointer, const typename DocumentType::Ch* value) { + return pointer.Set(document, value); +} + +#if RAPIDJSON_HAS_STDSTRING +template <typename DocumentType> +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer<typename DocumentType::ValueType>& pointer, const std::basic_string<typename DocumentType::Ch>& value) { + return pointer.Set(document, value); +} +#endif + +template <typename DocumentType, typename T2> +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr<internal::IsPointer<T2>, internal::IsGenericValue<T2> >), (typename DocumentType::ValueType&)) +SetValueByPointer(DocumentType& document, const GenericPointer<typename DocumentType::ValueType>& pointer, T2 value) { + return pointer.Set(document, value); +} + +template <typename DocumentType, typename CharType, size_t N> +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], typename DocumentType::ValueType& value) { + return GenericPointer<typename DocumentType::ValueType>(source, N - 1).Set(document, value); +} + +template <typename DocumentType, typename CharType, size_t N> +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const typename DocumentType::ValueType& value) { + return GenericPointer<typename DocumentType::ValueType>(source, N - 1).Set(document, value); +} + +template <typename DocumentType, typename CharType, size_t N> +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const typename DocumentType::Ch* value) { + return GenericPointer<typename DocumentType::ValueType>(source, N - 1).Set(document, value); +} + +#if RAPIDJSON_HAS_STDSTRING +template <typename DocumentType, typename CharType, size_t N> +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const std::basic_string<typename DocumentType::Ch>& value) { + return GenericPointer<typename DocumentType::ValueType>(source, N - 1).Set(document, value); +} +#endif + +template <typename DocumentType, typename CharType, size_t N, typename T2> +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr<internal::IsPointer<T2>, internal::IsGenericValue<T2> >), (typename DocumentType::ValueType&)) +SetValueByPointer(DocumentType& document, const CharType(&source)[N], T2 value) { + return GenericPointer<typename DocumentType::ValueType>(source, N - 1).Set(document, value); +} + +////////////////////////////////////////////////////////////////////////////// + +template <typename T> +typename T::ValueType& SwapValueByPointer(T& root, const GenericPointer<typename T::ValueType>& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Swap(root, value, a); +} + +template <typename T, typename CharType, size_t N> +typename T::ValueType& SwapValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer<typename T::ValueType>(source, N - 1).Swap(root, value, a); +} + +template <typename DocumentType> +typename DocumentType::ValueType& SwapValueByPointer(DocumentType& document, const GenericPointer<typename DocumentType::ValueType>& pointer, typename DocumentType::ValueType& value) { + return pointer.Swap(document, value); +} + +template <typename DocumentType, typename CharType, size_t N> +typename DocumentType::ValueType& SwapValueByPointer(DocumentType& document, const CharType(&source)[N], typename DocumentType::ValueType& value) { + return GenericPointer<typename DocumentType::ValueType>(source, N - 1).Swap(document, value); +} + +////////////////////////////////////////////////////////////////////////////// + +template <typename T> +bool EraseValueByPointer(T& root, const GenericPointer<typename T::ValueType>& pointer) { + return pointer.Erase(root); +} + +template <typename T, typename CharType, size_t N> +bool EraseValueByPointer(T& root, const CharType(&source)[N]) { + return GenericPointer<typename T::ValueType>(source, N - 1).Erase(root); +} + +//@} + +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_POINTER_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/prettywriter.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/prettywriter.h new file mode 100644 index 00000000..fe45df1d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/prettywriter.h @@ -0,0 +1,277 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_PRETTYWRITER_H_ +#define RAPIDJSON_PRETTYWRITER_H_ + +#include "writer.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Combination of PrettyWriter format flags. +/*! \see PrettyWriter::SetFormatOptions + */ +enum PrettyFormatOptions { + kFormatDefault = 0, //!< Default pretty formatting. + kFormatSingleLineArray = 1 //!< Format arrays on a single line. +}; + +//! Writer with indentation and spacing. +/*! + \tparam OutputStream Type of output os. + \tparam SourceEncoding Encoding of source string. + \tparam TargetEncoding Encoding of output stream. + \tparam StackAllocator Type of allocator for allocating memory of stack. +*/ +template<typename OutputStream, typename SourceEncoding = UTF8<>, typename TargetEncoding = UTF8<>, typename StackAllocator = CrtAllocator, unsigned writeFlags = kWriteDefaultFlags> +class PrettyWriter : public Writer<OutputStream, SourceEncoding, TargetEncoding, StackAllocator, writeFlags> { +public: + typedef Writer<OutputStream, SourceEncoding, TargetEncoding, StackAllocator, writeFlags> Base; + typedef typename Base::Ch Ch; + + //! Constructor + /*! \param os Output stream. + \param allocator User supplied allocator. If it is null, it will create a private one. + \param levelDepth Initial capacity of stack. + */ + explicit PrettyWriter(OutputStream& os, StackAllocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) : + Base(os, allocator, levelDepth), indentChar_(' '), indentCharCount_(4), formatOptions_(kFormatDefault) {} + + + explicit PrettyWriter(StackAllocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) : + Base(allocator, levelDepth), indentChar_(' '), indentCharCount_(4), formatOptions_(kFormatDefault) {} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + PrettyWriter(PrettyWriter&& rhs) : + Base(std::forward<PrettyWriter>(rhs)), indentChar_(rhs.indentChar_), indentCharCount_(rhs.indentCharCount_), formatOptions_(rhs.formatOptions_) {} +#endif + + //! Set custom indentation. + /*! \param indentChar Character for indentation. Must be whitespace character (' ', '\\t', '\\n', '\\r'). + \param indentCharCount Number of indent characters for each indentation level. + \note The default indentation is 4 spaces. + */ + PrettyWriter& SetIndent(Ch indentChar, unsigned indentCharCount) { + RAPIDJSON_ASSERT(indentChar == ' ' || indentChar == '\t' || indentChar == '\n' || indentChar == '\r'); + indentChar_ = indentChar; + indentCharCount_ = indentCharCount; + return *this; + } + + //! Set pretty writer formatting options. + /*! \param options Formatting options. + */ + PrettyWriter& SetFormatOptions(PrettyFormatOptions options) { + formatOptions_ = options; + return *this; + } + + /*! @name Implementation of Handler + \see Handler + */ + //@{ + + bool Null() { PrettyPrefix(kNullType); return Base::EndValue(Base::WriteNull()); } + bool Bool(bool b) { PrettyPrefix(b ? kTrueType : kFalseType); return Base::EndValue(Base::WriteBool(b)); } + bool Int(int i) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteInt(i)); } + bool Uint(unsigned u) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteUint(u)); } + bool Int64(int64_t i64) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteInt64(i64)); } + bool Uint64(uint64_t u64) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteUint64(u64)); } + bool Double(double d) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteDouble(d)); } + + bool RawNumber(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + PrettyPrefix(kNumberType); + return Base::EndValue(Base::WriteString(str, length)); + } + + bool String(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + PrettyPrefix(kStringType); + return Base::EndValue(Base::WriteString(str, length)); + } + +#if RAPIDJSON_HAS_STDSTRING + bool String(const std::basic_string<Ch>& str) { + return String(str.data(), SizeType(str.size())); + } +#endif + + bool StartObject() { + PrettyPrefix(kObjectType); + new (Base::level_stack_.template Push<typename Base::Level>()) typename Base::Level(false); + return Base::WriteStartObject(); + } + + bool Key(const Ch* str, SizeType length, bool copy = false) { return String(str, length, copy); } + +#if RAPIDJSON_HAS_STDSTRING + bool Key(const std::basic_string<Ch>& str) { + return Key(str.data(), SizeType(str.size())); + } +#endif + + bool EndObject(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); // not inside an Object + RAPIDJSON_ASSERT(!Base::level_stack_.template Top<typename Base::Level>()->inArray); // currently inside an Array, not Object + RAPIDJSON_ASSERT(0 == Base::level_stack_.template Top<typename Base::Level>()->valueCount % 2); // Object has a Key without a Value + + bool empty = Base::level_stack_.template Pop<typename Base::Level>(1)->valueCount == 0; + + if (!empty) { + Base::os_->Put('\n'); + WriteIndent(); + } + bool ret = Base::EndValue(Base::WriteEndObject()); + (void)ret; + RAPIDJSON_ASSERT(ret == true); + if (Base::level_stack_.Empty()) // end of json text + Base::Flush(); + return true; + } + + bool StartArray() { + PrettyPrefix(kArrayType); + new (Base::level_stack_.template Push<typename Base::Level>()) typename Base::Level(true); + return Base::WriteStartArray(); + } + + bool EndArray(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); + RAPIDJSON_ASSERT(Base::level_stack_.template Top<typename Base::Level>()->inArray); + bool empty = Base::level_stack_.template Pop<typename Base::Level>(1)->valueCount == 0; + + if (!empty && !(formatOptions_ & kFormatSingleLineArray)) { + Base::os_->Put('\n'); + WriteIndent(); + } + bool ret = Base::EndValue(Base::WriteEndArray()); + (void)ret; + RAPIDJSON_ASSERT(ret == true); + if (Base::level_stack_.Empty()) // end of json text + Base::Flush(); + return true; + } + + //@} + + /*! @name Convenience extensions */ + //@{ + + //! Simpler but slower overload. + bool String(const Ch* str) { return String(str, internal::StrLen(str)); } + bool Key(const Ch* str) { return Key(str, internal::StrLen(str)); } + + //@} + + //! Write a raw JSON value. + /*! + For user to write a stringified JSON as a value. + + \param json A well-formed JSON value. It should not contain null character within [0, length - 1] range. + \param length Length of the json. + \param type Type of the root of json. + \note When using PrettyWriter::RawValue(), the result json may not be indented correctly. + */ + bool RawValue(const Ch* json, size_t length, Type type) { + RAPIDJSON_ASSERT(json != 0); + PrettyPrefix(type); + return Base::EndValue(Base::WriteRawValue(json, length)); + } + +protected: + void PrettyPrefix(Type type) { + (void)type; + if (Base::level_stack_.GetSize() != 0) { // this value is not at root + typename Base::Level* level = Base::level_stack_.template Top<typename Base::Level>(); + + if (level->inArray) { + if (level->valueCount > 0) { + Base::os_->Put(','); // add comma if it is not the first element in array + if (formatOptions_ & kFormatSingleLineArray) + Base::os_->Put(' '); + } + + if (!(formatOptions_ & kFormatSingleLineArray)) { + Base::os_->Put('\n'); + WriteIndent(); + } + } + else { // in object + if (level->valueCount > 0) { + if (level->valueCount % 2 == 0) { + Base::os_->Put(','); + Base::os_->Put('\n'); + } + else { + Base::os_->Put(':'); + Base::os_->Put(' '); + } + } + else + Base::os_->Put('\n'); + + if (level->valueCount % 2 == 0) + WriteIndent(); + } + if (!level->inArray && level->valueCount % 2 == 0) + RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name + level->valueCount++; + } + else { + RAPIDJSON_ASSERT(!Base::hasRoot_); // Should only has one and only one root. + Base::hasRoot_ = true; + } + } + + void WriteIndent() { + size_t count = (Base::level_stack_.GetSize() / sizeof(typename Base::Level)) * indentCharCount_; + PutN(*Base::os_, static_cast<typename OutputStream::Ch>(indentChar_), count); + } + + Ch indentChar_; + unsigned indentCharCount_; + PrettyFormatOptions formatOptions_; + +private: + // Prohibit copy constructor & assignment operator. + PrettyWriter(const PrettyWriter&); + PrettyWriter& operator=(const PrettyWriter&); +}; + +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/rapidjson.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/rapidjson.h new file mode 100644 index 00000000..78aa89a0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/rapidjson.h @@ -0,0 +1,692 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_RAPIDJSON_H_ +#define RAPIDJSON_RAPIDJSON_H_ + +/*!\file rapidjson.h + \brief common definitions and configuration + + \see RAPIDJSON_CONFIG + */ + +/*! \defgroup RAPIDJSON_CONFIG RapidJSON configuration + \brief Configuration macros for library features + + Some RapidJSON features are configurable to adapt the library to a wide + variety of platforms, environments and usage scenarios. Most of the + features can be configured in terms of overridden or predefined + preprocessor macros at compile-time. + + Some additional customization is available in the \ref RAPIDJSON_ERRORS APIs. + + \note These macros should be given on the compiler command-line + (where applicable) to avoid inconsistent values when compiling + different translation units of a single application. + */ + +#include <cstdlib> // malloc(), realloc(), free(), size_t +#include <cstring> // memset(), memcpy(), memmove(), memcmp() + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_VERSION_STRING +// +// ALWAYS synchronize the following 3 macros with corresponding variables in /CMakeLists.txt. +// + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +// token stringification +#define RAPIDJSON_STRINGIFY(x) RAPIDJSON_DO_STRINGIFY(x) +#define RAPIDJSON_DO_STRINGIFY(x) #x + +// token concatenation +#define RAPIDJSON_JOIN(X, Y) RAPIDJSON_DO_JOIN(X, Y) +#define RAPIDJSON_DO_JOIN(X, Y) RAPIDJSON_DO_JOIN2(X, Y) +#define RAPIDJSON_DO_JOIN2(X, Y) X##Y +//!@endcond + +/*! \def RAPIDJSON_MAJOR_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Major version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_MINOR_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Minor version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_PATCH_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Patch version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_VERSION_STRING + \ingroup RAPIDJSON_CONFIG + \brief Version of RapidJSON in "<major>.<minor>.<patch>" string format. +*/ +#define RAPIDJSON_MAJOR_VERSION 1 +#define RAPIDJSON_MINOR_VERSION 1 +#define RAPIDJSON_PATCH_VERSION 0 +#define RAPIDJSON_VERSION_STRING \ + RAPIDJSON_STRINGIFY(RAPIDJSON_MAJOR_VERSION.RAPIDJSON_MINOR_VERSION.RAPIDJSON_PATCH_VERSION) + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NAMESPACE_(BEGIN|END) +/*! \def RAPIDJSON_NAMESPACE + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace + + In order to avoid symbol clashes and/or "One Definition Rule" errors + between multiple inclusions of (different versions of) RapidJSON in + a single binary, users can customize the name of the main RapidJSON + namespace. + + In case of a single nesting level, defining \c RAPIDJSON_NAMESPACE + to a custom name (e.g. \c MyRapidJSON) is sufficient. If multiple + levels are needed, both \ref RAPIDJSON_NAMESPACE_BEGIN and \ref + RAPIDJSON_NAMESPACE_END need to be defined as well: + + \code + // in some .cpp file + #define RAPIDJSON_NAMESPACE my::rapidjson + #define RAPIDJSON_NAMESPACE_BEGIN namespace my { namespace rapidjson { + #define RAPIDJSON_NAMESPACE_END } } + #include "rapidjson/..." + \endcode + + \see rapidjson + */ +/*! \def RAPIDJSON_NAMESPACE_BEGIN + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace (opening expression) + \see RAPIDJSON_NAMESPACE +*/ +/*! \def RAPIDJSON_NAMESPACE_END + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace (closing expression) + \see RAPIDJSON_NAMESPACE +*/ +#ifndef RAPIDJSON_NAMESPACE +#define RAPIDJSON_NAMESPACE rapidjson +#endif +#ifndef RAPIDJSON_NAMESPACE_BEGIN +#define RAPIDJSON_NAMESPACE_BEGIN namespace RAPIDJSON_NAMESPACE { +#endif +#ifndef RAPIDJSON_NAMESPACE_END +#define RAPIDJSON_NAMESPACE_END } +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_HAS_STDSTRING + +#ifndef RAPIDJSON_HAS_STDSTRING +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_HAS_STDSTRING 1 // force generation of documentation +#else +#define RAPIDJSON_HAS_STDSTRING 0 // no std::string support by default +#endif +/*! \def RAPIDJSON_HAS_STDSTRING + \ingroup RAPIDJSON_CONFIG + \brief Enable RapidJSON support for \c std::string + + By defining this preprocessor symbol to \c 1, several convenience functions for using + \ref rapidjson::GenericValue with \c std::string are enabled, especially + for construction and comparison. + + \hideinitializer +*/ +#endif // !defined(RAPIDJSON_HAS_STDSTRING) + +#if RAPIDJSON_HAS_STDSTRING +#include <string> +#endif // RAPIDJSON_HAS_STDSTRING + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NO_INT64DEFINE + +/*! \def RAPIDJSON_NO_INT64DEFINE + \ingroup RAPIDJSON_CONFIG + \brief Use external 64-bit integer types. + + RapidJSON requires the 64-bit integer types \c int64_t and \c uint64_t types + to be available at global scope. + + If users have their own definition, define RAPIDJSON_NO_INT64DEFINE to + prevent RapidJSON from defining its own types. +*/ +#ifndef RAPIDJSON_NO_INT64DEFINE +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#if defined(_MSC_VER) && (_MSC_VER < 1800) // Visual Studio 2013 +#include "msinttypes/stdint.h" +#include "msinttypes/inttypes.h" +#else +// Other compilers should have this. +#include <stdint.h> +#include <inttypes.h> +#endif +//!@endcond +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_NO_INT64DEFINE +#endif +#endif // RAPIDJSON_NO_INT64TYPEDEF + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_FORCEINLINE + +#ifndef RAPIDJSON_FORCEINLINE +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#if defined(_MSC_VER) && defined(NDEBUG) +#define RAPIDJSON_FORCEINLINE __forceinline +#elif defined(__GNUC__) && __GNUC__ >= 4 && defined(NDEBUG) +#define RAPIDJSON_FORCEINLINE __attribute__((always_inline)) +#else +#define RAPIDJSON_FORCEINLINE +#endif +//!@endcond +#endif // RAPIDJSON_FORCEINLINE + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ENDIAN +#define RAPIDJSON_LITTLEENDIAN 0 //!< Little endian machine +#define RAPIDJSON_BIGENDIAN 1 //!< Big endian machine + +//! Endianness of the machine. +/*! + \def RAPIDJSON_ENDIAN + \ingroup RAPIDJSON_CONFIG + + GCC 4.6 provided macro for detecting endianness of the target machine. But other + compilers may not have this. User can define RAPIDJSON_ENDIAN to either + \ref RAPIDJSON_LITTLEENDIAN or \ref RAPIDJSON_BIGENDIAN. + + Default detection implemented with reference to + \li https://gcc.gnu.org/onlinedocs/gcc-4.6.0/cpp/Common-Predefined-Macros.html + \li http://www.boost.org/doc/libs/1_42_0/boost/detail/endian.hpp +*/ +#ifndef RAPIDJSON_ENDIAN +// Detect with GCC 4.6's macro +# ifdef __BYTE_ORDER__ +# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# else +# error Unknown machine endianness detected. User needs to define RAPIDJSON_ENDIAN. +# endif // __BYTE_ORDER__ +// Detect with GLIBC's endian.h +# elif defined(__GLIBC__) +# include <endian.h> +# if (__BYTE_ORDER == __LITTLE_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif (__BYTE_ORDER == __BIG_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# else +# error Unknown machine endianness detected. User needs to define RAPIDJSON_ENDIAN. +# endif // __GLIBC__ +// Detect with _LITTLE_ENDIAN and _BIG_ENDIAN macro +# elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +// Detect with architecture macros +# elif defined(__sparc) || defined(__sparc__) || defined(_POWER) || defined(__powerpc__) || defined(__ppc__) || defined(__hpux) || defined(__hppa) || defined(_MIPSEB) || defined(_POWER) || defined(__s390__) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# elif defined(__i386__) || defined(__alpha__) || defined(__ia64) || defined(__ia64__) || defined(_M_IX86) || defined(_M_IA64) || defined(_M_ALPHA) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) || defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || defined(__bfin__) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64)) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(RAPIDJSON_DOXYGEN_RUNNING) +# define RAPIDJSON_ENDIAN +# else +# error Unknown machine endianness detected. User needs to define RAPIDJSON_ENDIAN. +# endif +#endif // RAPIDJSON_ENDIAN + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_64BIT + +//! Whether using 64-bit architecture +#ifndef RAPIDJSON_64BIT +#if defined(__LP64__) || (defined(__x86_64__) && defined(__ILP32__)) || defined(_WIN64) || defined(__EMSCRIPTEN__) +#define RAPIDJSON_64BIT 1 +#else +#define RAPIDJSON_64BIT 0 +#endif +#endif // RAPIDJSON_64BIT + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ALIGN + +//! Data alignment of the machine. +/*! \ingroup RAPIDJSON_CONFIG + \param x pointer to align + + Some machines require strict data alignment. The default is 8 bytes. + User can customize by defining the RAPIDJSON_ALIGN function macro. +*/ +#ifndef RAPIDJSON_ALIGN +#define RAPIDJSON_ALIGN(x) (((x) + static_cast<size_t>(7u)) & ~static_cast<size_t>(7u)) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_UINT64_C2 + +//! Construct a 64-bit literal by a pair of 32-bit integer. +/*! + 64-bit literal with or without ULL suffix is prone to compiler warnings. + UINT64_C() is C macro which cause compilation problems. + Use this macro to define 64-bit constants by a pair of 32-bit integer. +*/ +#ifndef RAPIDJSON_UINT64_C2 +#define RAPIDJSON_UINT64_C2(high32, low32) ((static_cast<uint64_t>(high32) << 32) | static_cast<uint64_t>(low32)) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_48BITPOINTER_OPTIMIZATION + +//! Use only lower 48-bit address for some pointers. +/*! + \ingroup RAPIDJSON_CONFIG + + This optimization uses the fact that current X86-64 architecture only implement lower 48-bit virtual address. + The higher 16-bit can be used for storing other data. + \c GenericValue uses this optimization to reduce its size form 24 bytes to 16 bytes in 64-bit architecture. +*/ +#ifndef RAPIDJSON_48BITPOINTER_OPTIMIZATION +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64) +#define RAPIDJSON_48BITPOINTER_OPTIMIZATION 1 +#else +#define RAPIDJSON_48BITPOINTER_OPTIMIZATION 0 +#endif +#endif // RAPIDJSON_48BITPOINTER_OPTIMIZATION + +#if RAPIDJSON_48BITPOINTER_OPTIMIZATION == 1 +#if RAPIDJSON_64BIT != 1 +#error RAPIDJSON_48BITPOINTER_OPTIMIZATION can only be set to 1 when RAPIDJSON_64BIT=1 +#endif +#define RAPIDJSON_SETPOINTER(type, p, x) (p = reinterpret_cast<type *>((reinterpret_cast<uintptr_t>(p) & static_cast<uintptr_t>(RAPIDJSON_UINT64_C2(0xFFFF0000, 0x00000000))) | reinterpret_cast<uintptr_t>(reinterpret_cast<const void*>(x)))) +#define RAPIDJSON_GETPOINTER(type, p) (reinterpret_cast<type *>(reinterpret_cast<uintptr_t>(p) & static_cast<uintptr_t>(RAPIDJSON_UINT64_C2(0x0000FFFF, 0xFFFFFFFF)))) +#else +#define RAPIDJSON_SETPOINTER(type, p, x) (p = (x)) +#define RAPIDJSON_GETPOINTER(type, p) (p) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_SSE2/RAPIDJSON_SSE42/RAPIDJSON_NEON/RAPIDJSON_SIMD + +/*! \def RAPIDJSON_SIMD + \ingroup RAPIDJSON_CONFIG + \brief Enable SSE2/SSE4.2/Neon optimization. + + RapidJSON supports optimized implementations for some parsing operations + based on the SSE2, SSE4.2 or NEon SIMD extensions on modern Intel + or ARM compatible processors. + + To enable these optimizations, three different symbols can be defined; + \code + // Enable SSE2 optimization. + #define RAPIDJSON_SSE2 + + // Enable SSE4.2 optimization. + #define RAPIDJSON_SSE42 + \endcode + + // Enable ARM Neon optimization. + #define RAPIDJSON_NEON + \endcode + + \c RAPIDJSON_SSE42 takes precedence over SSE2, if both are defined. + + If any of these symbols is defined, RapidJSON defines the macro + \c RAPIDJSON_SIMD to indicate the availability of the optimized code. +*/ +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) \ + || defined(RAPIDJSON_NEON) || defined(RAPIDJSON_DOXYGEN_RUNNING) +#define RAPIDJSON_SIMD +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NO_SIZETYPEDEFINE + +#ifndef RAPIDJSON_NO_SIZETYPEDEFINE +/*! \def RAPIDJSON_NO_SIZETYPEDEFINE + \ingroup RAPIDJSON_CONFIG + \brief User-provided \c SizeType definition. + + In order to avoid using 32-bit size types for indexing strings and arrays, + define this preprocessor symbol and provide the type rapidjson::SizeType + before including RapidJSON: + \code + #define RAPIDJSON_NO_SIZETYPEDEFINE + namespace rapidjson { typedef ::std::size_t SizeType; } + #include "rapidjson/..." + \endcode + + \see rapidjson::SizeType +*/ +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_NO_SIZETYPEDEFINE +#endif +RAPIDJSON_NAMESPACE_BEGIN +//! Size type (for string lengths, array sizes, etc.) +/*! RapidJSON uses 32-bit array/string indices even on 64-bit platforms, + instead of using \c size_t. Users may override the SizeType by defining + \ref RAPIDJSON_NO_SIZETYPEDEFINE. +*/ +typedef unsigned SizeType; +RAPIDJSON_NAMESPACE_END +#endif + +// always import std::size_t to rapidjson namespace +RAPIDJSON_NAMESPACE_BEGIN +using std::size_t; +RAPIDJSON_NAMESPACE_END + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ASSERT + +//! Assertion. +/*! \ingroup RAPIDJSON_CONFIG + By default, rapidjson uses C \c assert() for internal assertions. + User can override it by defining RAPIDJSON_ASSERT(x) macro. + + \note Parsing errors are handled and can be customized by the + \ref RAPIDJSON_ERRORS APIs. +*/ +#ifndef RAPIDJSON_ASSERT +#include <cassert> +#define RAPIDJSON_ASSERT(x) assert(x) +#endif // RAPIDJSON_ASSERT + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_STATIC_ASSERT + +// Prefer C++11 static_assert, if available +#ifndef RAPIDJSON_STATIC_ASSERT +#if __cplusplus >= 201103L || ( defined(_MSC_VER) && _MSC_VER >= 1800 ) +#define RAPIDJSON_STATIC_ASSERT(x) \ + static_assert(x, RAPIDJSON_STRINGIFY(x)) +#endif // C++11 +#endif // RAPIDJSON_STATIC_ASSERT + +// Adopt C++03 implementation from boost +#ifndef RAPIDJSON_STATIC_ASSERT +#ifndef __clang__ +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#endif +RAPIDJSON_NAMESPACE_BEGIN +template <bool x> struct STATIC_ASSERTION_FAILURE; +template <> struct STATIC_ASSERTION_FAILURE<true> { enum { value = 1 }; }; +template <size_t x> struct StaticAssertTest {}; +RAPIDJSON_NAMESPACE_END + +#if defined(__GNUC__) || defined(__clang__) +#define RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE __attribute__((unused)) +#else +#define RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE +#endif +#ifndef __clang__ +//!@endcond +#endif + +/*! \def RAPIDJSON_STATIC_ASSERT + \brief (Internal) macro to check for conditions at compile-time + \param x compile-time condition + \hideinitializer + */ +#define RAPIDJSON_STATIC_ASSERT(x) \ + typedef ::RAPIDJSON_NAMESPACE::StaticAssertTest< \ + sizeof(::RAPIDJSON_NAMESPACE::STATIC_ASSERTION_FAILURE<bool(x) >)> \ + RAPIDJSON_JOIN(StaticAssertTypedef, __LINE__) RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE +#endif // RAPIDJSON_STATIC_ASSERT + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_LIKELY, RAPIDJSON_UNLIKELY + +//! Compiler branching hint for expression with high probability to be true. +/*! + \ingroup RAPIDJSON_CONFIG + \param x Boolean expression likely to be true. +*/ +#ifndef RAPIDJSON_LIKELY +#if defined(__GNUC__) || defined(__clang__) +#define RAPIDJSON_LIKELY(x) __builtin_expect(!!(x), 1) +#else +#define RAPIDJSON_LIKELY(x) (x) +#endif +#endif + +//! Compiler branching hint for expression with low probability to be true. +/*! + \ingroup RAPIDJSON_CONFIG + \param x Boolean expression unlikely to be true. +*/ +#ifndef RAPIDJSON_UNLIKELY +#if defined(__GNUC__) || defined(__clang__) +#define RAPIDJSON_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else +#define RAPIDJSON_UNLIKELY(x) (x) +#endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Helpers + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN + +#define RAPIDJSON_MULTILINEMACRO_BEGIN do { +#define RAPIDJSON_MULTILINEMACRO_END \ +} while((void)0, 0) + +// adopted from Boost +#define RAPIDJSON_VERSION_CODE(x,y,z) \ + (((x)*100000) + ((y)*100) + (z)) + +#if defined(__has_builtin) +#define RAPIDJSON_HAS_BUILTIN(x) __has_builtin(x) +#else +#define RAPIDJSON_HAS_BUILTIN(x) 0 +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_DIAG_PUSH/POP, RAPIDJSON_DIAG_OFF + +#if defined(__GNUC__) +#define RAPIDJSON_GNUC \ + RAPIDJSON_VERSION_CODE(__GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__) +#endif + +#if defined(__clang__) || (defined(RAPIDJSON_GNUC) && RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,2,0)) + +#define RAPIDJSON_PRAGMA(x) _Pragma(RAPIDJSON_STRINGIFY(x)) +#define RAPIDJSON_DIAG_PRAGMA(x) RAPIDJSON_PRAGMA(GCC diagnostic x) +#define RAPIDJSON_DIAG_OFF(x) \ + RAPIDJSON_DIAG_PRAGMA(ignored RAPIDJSON_STRINGIFY(RAPIDJSON_JOIN(-W,x))) + +// push/pop support in Clang and GCC>=4.6 +#if defined(__clang__) || (defined(RAPIDJSON_GNUC) && RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) +#define RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_PRAGMA(push) +#define RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_PRAGMA(pop) +#else // GCC >= 4.2, < 4.6 +#define RAPIDJSON_DIAG_PUSH /* ignored */ +#define RAPIDJSON_DIAG_POP /* ignored */ +#endif + +#elif defined(_MSC_VER) + +// pragma (MSVC specific) +#define RAPIDJSON_PRAGMA(x) __pragma(x) +#define RAPIDJSON_DIAG_PRAGMA(x) RAPIDJSON_PRAGMA(warning(x)) + +#define RAPIDJSON_DIAG_OFF(x) RAPIDJSON_DIAG_PRAGMA(disable: x) +#define RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_PRAGMA(push) +#define RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_PRAGMA(pop) + +#else + +#define RAPIDJSON_DIAG_OFF(x) /* ignored */ +#define RAPIDJSON_DIAG_PUSH /* ignored */ +#define RAPIDJSON_DIAG_POP /* ignored */ + +#endif // RAPIDJSON_DIAG_* + +/////////////////////////////////////////////////////////////////////////////// +// C++11 features + +#ifndef RAPIDJSON_HAS_CXX11_RVALUE_REFS +#if defined(__clang__) +#if __has_feature(cxx_rvalue_references) && \ + (defined(_MSC_VER) || defined(_LIBCPP_VERSION) || defined(__GLIBCXX__) && __GLIBCXX__ >= 20080306) +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1 +#else +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 0 +#endif +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,3,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || \ + (defined(_MSC_VER) && _MSC_VER >= 1600) || \ + (defined(__SUNPRO_CC) && __SUNPRO_CC >= 0x5140 && defined(__GXX_EXPERIMENTAL_CXX0X__)) + +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1 +#else +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 0 +#endif +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + +#ifndef RAPIDJSON_HAS_CXX11_NOEXCEPT +#if defined(__clang__) +#define RAPIDJSON_HAS_CXX11_NOEXCEPT __has_feature(cxx_noexcept) +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || \ + (defined(_MSC_VER) && _MSC_VER >= 1900) || \ + (defined(__SUNPRO_CC) && __SUNPRO_CC >= 0x5140 && defined(__GXX_EXPERIMENTAL_CXX0X__)) +#define RAPIDJSON_HAS_CXX11_NOEXCEPT 1 +#else +#define RAPIDJSON_HAS_CXX11_NOEXCEPT 0 +#endif +#endif +#if RAPIDJSON_HAS_CXX11_NOEXCEPT +#define RAPIDJSON_NOEXCEPT noexcept +#else +#define RAPIDJSON_NOEXCEPT /* noexcept */ +#endif // RAPIDJSON_HAS_CXX11_NOEXCEPT + +// no automatic detection, yet +#ifndef RAPIDJSON_HAS_CXX11_TYPETRAITS +#if (defined(_MSC_VER) && _MSC_VER >= 1700) +#define RAPIDJSON_HAS_CXX11_TYPETRAITS 1 +#else +#define RAPIDJSON_HAS_CXX11_TYPETRAITS 0 +#endif +#endif + +#ifndef RAPIDJSON_HAS_CXX11_RANGE_FOR +#if defined(__clang__) +#define RAPIDJSON_HAS_CXX11_RANGE_FOR __has_feature(cxx_range_for) +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || \ + (defined(_MSC_VER) && _MSC_VER >= 1700) || \ + (defined(__SUNPRO_CC) && __SUNPRO_CC >= 0x5140 && defined(__GXX_EXPERIMENTAL_CXX0X__)) +#define RAPIDJSON_HAS_CXX11_RANGE_FOR 1 +#else +#define RAPIDJSON_HAS_CXX11_RANGE_FOR 0 +#endif +#endif // RAPIDJSON_HAS_CXX11_RANGE_FOR + +/////////////////////////////////////////////////////////////////////////////// +// C++17 features + +#if defined(__has_cpp_attribute) +# if __has_cpp_attribute(fallthrough) +# define RAPIDJSON_DELIBERATE_FALLTHROUGH [[fallthrough]] +# else +# define RAPIDJSON_DELIBERATE_FALLTHROUGH +# endif +#else +# define RAPIDJSON_DELIBERATE_FALLTHROUGH +#endif + +//!@endcond + +//! Assertion (in non-throwing contexts). + /*! \ingroup RAPIDJSON_CONFIG + Some functions provide a \c noexcept guarantee, if the compiler supports it. + In these cases, the \ref RAPIDJSON_ASSERT macro cannot be overridden to + throw an exception. This macro adds a separate customization point for + such cases. + + Defaults to C \c assert() (as \ref RAPIDJSON_ASSERT), if \c noexcept is + supported, and to \ref RAPIDJSON_ASSERT otherwise. + */ + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NOEXCEPT_ASSERT + +#ifndef RAPIDJSON_NOEXCEPT_ASSERT +#ifdef RAPIDJSON_ASSERT_THROWS +#if RAPIDJSON_HAS_CXX11_NOEXCEPT +#define RAPIDJSON_NOEXCEPT_ASSERT(x) +#else +#include <cassert> +#define RAPIDJSON_NOEXCEPT_ASSERT(x) assert(x) +#endif // RAPIDJSON_HAS_CXX11_NOEXCEPT +#else +#define RAPIDJSON_NOEXCEPT_ASSERT(x) RAPIDJSON_ASSERT(x) +#endif // RAPIDJSON_ASSERT_THROWS +#endif // RAPIDJSON_NOEXCEPT_ASSERT + +/////////////////////////////////////////////////////////////////////////////// +// malloc/realloc/free + +#ifndef RAPIDJSON_MALLOC +///! customization point for global \c malloc +#define RAPIDJSON_MALLOC(size) std::malloc(size) +#endif +#ifndef RAPIDJSON_REALLOC +///! customization point for global \c realloc +#define RAPIDJSON_REALLOC(ptr, new_size) std::realloc(ptr, new_size) +#endif +#ifndef RAPIDJSON_FREE +///! customization point for global \c free +#define RAPIDJSON_FREE(ptr) std::free(ptr) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// new/delete + +#ifndef RAPIDJSON_NEW +///! customization point for global \c new +#define RAPIDJSON_NEW(TypeName) new TypeName +#endif +#ifndef RAPIDJSON_DELETE +///! customization point for global \c delete +#define RAPIDJSON_DELETE(x) delete x +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Type + +/*! \namespace rapidjson + \brief main RapidJSON namespace + \see RAPIDJSON_NAMESPACE +*/ +RAPIDJSON_NAMESPACE_BEGIN + +//! Type of JSON value +enum Type { + kNullType = 0, //!< null + kFalseType = 1, //!< false + kTrueType = 2, //!< true + kObjectType = 3, //!< object + kArrayType = 4, //!< array + kStringType = 5, //!< string + kNumberType = 6 //!< number +}; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/reader.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/reader.h new file mode 100644 index 00000000..09ace4eb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/reader.h @@ -0,0 +1,2244 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_READER_H_ +#define RAPIDJSON_READER_H_ + +/*! \file reader.h */ + +#include "allocators.h" +#include "stream.h" +#include "encodedstream.h" +#include "internal/clzll.h" +#include "internal/meta.h" +#include "internal/stack.h" +#include "internal/strtod.h" +#include <limits> + +#if defined(RAPIDJSON_SIMD) && defined(_MSC_VER) +#include <intrin.h> +#pragma intrinsic(_BitScanForward) +#endif +#ifdef RAPIDJSON_SSE42 +#include <nmmintrin.h> +#elif defined(RAPIDJSON_SSE2) +#include <emmintrin.h> +#elif defined(RAPIDJSON_NEON) +#include <arm_neon.h> +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(old-style-cast) +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(switch-enum) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +RAPIDJSON_DIAG_OFF(4702) // unreachable code +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define RAPIDJSON_NOTHING /* deliberately empty */ +#ifndef RAPIDJSON_PARSE_ERROR_EARLY_RETURN +#define RAPIDJSON_PARSE_ERROR_EARLY_RETURN(value) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + if (RAPIDJSON_UNLIKELY(HasParseError())) { return value; } \ + RAPIDJSON_MULTILINEMACRO_END +#endif +#define RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID \ + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(RAPIDJSON_NOTHING) +//!@endcond + +/*! \def RAPIDJSON_PARSE_ERROR_NORETURN + \ingroup RAPIDJSON_ERRORS + \brief Macro to indicate a parse error. + \param parseErrorCode \ref rapidjson::ParseErrorCode of the error + \param offset position of the error in JSON input (\c size_t) + + This macros can be used as a customization point for the internal + error handling mechanism of RapidJSON. + + A common usage model is to throw an exception instead of requiring the + caller to explicitly check the \ref rapidjson::GenericReader::Parse's + return value: + + \code + #define RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode,offset) \ + throw ParseException(parseErrorCode, #parseErrorCode, offset) + + #include <stdexcept> // std::runtime_error + #include "rapidjson/error/error.h" // rapidjson::ParseResult + + struct ParseException : std::runtime_error, rapidjson::ParseResult { + ParseException(rapidjson::ParseErrorCode code, const char* msg, size_t offset) + : std::runtime_error(msg), ParseResult(code, offset) {} + }; + + #include "rapidjson/reader.h" + \endcode + + \see RAPIDJSON_PARSE_ERROR, rapidjson::GenericReader::Parse + */ +#ifndef RAPIDJSON_PARSE_ERROR_NORETURN +#define RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode, offset) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + RAPIDJSON_ASSERT(!HasParseError()); /* Error can only be assigned once */ \ + SetParseError(parseErrorCode, offset); \ + RAPIDJSON_MULTILINEMACRO_END +#endif + +/*! \def RAPIDJSON_PARSE_ERROR + \ingroup RAPIDJSON_ERRORS + \brief (Internal) macro to indicate and handle a parse error. + \param parseErrorCode \ref rapidjson::ParseErrorCode of the error + \param offset position of the error in JSON input (\c size_t) + + Invokes RAPIDJSON_PARSE_ERROR_NORETURN and stops the parsing. + + \see RAPIDJSON_PARSE_ERROR_NORETURN + \hideinitializer + */ +#ifndef RAPIDJSON_PARSE_ERROR +#define RAPIDJSON_PARSE_ERROR(parseErrorCode, offset) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode, offset); \ + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; \ + RAPIDJSON_MULTILINEMACRO_END +#endif + +#include "error/error.h" // ParseErrorCode, ParseResult + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// ParseFlag + +/*! \def RAPIDJSON_PARSE_DEFAULT_FLAGS + \ingroup RAPIDJSON_CONFIG + \brief User-defined kParseDefaultFlags definition. + + User can define this as any \c ParseFlag combinations. +*/ +#ifndef RAPIDJSON_PARSE_DEFAULT_FLAGS +#define RAPIDJSON_PARSE_DEFAULT_FLAGS kParseNoFlags +#endif + +//! Combination of parseFlags +/*! \see Reader::Parse, Document::Parse, Document::ParseInsitu, Document::ParseStream + */ +enum ParseFlag { + kParseNoFlags = 0, //!< No flags are set. + kParseInsituFlag = 1, //!< In-situ(destructive) parsing. + kParseValidateEncodingFlag = 2, //!< Validate encoding of JSON strings. + kParseIterativeFlag = 4, //!< Iterative(constant complexity in terms of function call stack size) parsing. + kParseStopWhenDoneFlag = 8, //!< After parsing a complete JSON root from stream, stop further processing the rest of stream. When this flag is used, parser will not generate kParseErrorDocumentRootNotSingular error. + kParseFullPrecisionFlag = 16, //!< Parse number in full precision (but slower). + kParseCommentsFlag = 32, //!< Allow one-line (//) and multi-line (/**/) comments. + kParseNumbersAsStringsFlag = 64, //!< Parse all numbers (ints/doubles) as strings. + kParseTrailingCommasFlag = 128, //!< Allow trailing commas at the end of objects and arrays. + kParseNanAndInfFlag = 256, //!< Allow parsing NaN, Inf, Infinity, -Inf and -Infinity as doubles. + kParseEscapedApostropheFlag = 512, //!< Allow escaped apostrophe in strings. + kParseDefaultFlags = RAPIDJSON_PARSE_DEFAULT_FLAGS //!< Default parse flags. Can be customized by defining RAPIDJSON_PARSE_DEFAULT_FLAGS +}; + +/////////////////////////////////////////////////////////////////////////////// +// Handler + +/*! \class rapidjson::Handler + \brief Concept for receiving events from GenericReader upon parsing. + The functions return true if no error occurs. If they return false, + the event publisher should terminate the process. +\code +concept Handler { + typename Ch; + + bool Null(); + bool Bool(bool b); + bool Int(int i); + bool Uint(unsigned i); + bool Int64(int64_t i); + bool Uint64(uint64_t i); + bool Double(double d); + /// enabled via kParseNumbersAsStringsFlag, string is not null-terminated (use length) + bool RawNumber(const Ch* str, SizeType length, bool copy); + bool String(const Ch* str, SizeType length, bool copy); + bool StartObject(); + bool Key(const Ch* str, SizeType length, bool copy); + bool EndObject(SizeType memberCount); + bool StartArray(); + bool EndArray(SizeType elementCount); +}; +\endcode +*/ +/////////////////////////////////////////////////////////////////////////////// +// BaseReaderHandler + +//! Default implementation of Handler. +/*! This can be used as base class of any reader handler. + \note implements Handler concept +*/ +template<typename Encoding = UTF8<>, typename Derived = void> +struct BaseReaderHandler { + typedef typename Encoding::Ch Ch; + + typedef typename internal::SelectIf<internal::IsSame<Derived, void>, BaseReaderHandler, Derived>::Type Override; + + bool Default() { return true; } + bool Null() { return static_cast<Override&>(*this).Default(); } + bool Bool(bool) { return static_cast<Override&>(*this).Default(); } + bool Int(int) { return static_cast<Override&>(*this).Default(); } + bool Uint(unsigned) { return static_cast<Override&>(*this).Default(); } + bool Int64(int64_t) { return static_cast<Override&>(*this).Default(); } + bool Uint64(uint64_t) { return static_cast<Override&>(*this).Default(); } + bool Double(double) { return static_cast<Override&>(*this).Default(); } + /// enabled via kParseNumbersAsStringsFlag, string is not null-terminated (use length) + bool RawNumber(const Ch* str, SizeType len, bool copy) { return static_cast<Override&>(*this).String(str, len, copy); } + bool String(const Ch*, SizeType, bool) { return static_cast<Override&>(*this).Default(); } + bool StartObject() { return static_cast<Override&>(*this).Default(); } + bool Key(const Ch* str, SizeType len, bool copy) { return static_cast<Override&>(*this).String(str, len, copy); } + bool EndObject(SizeType) { return static_cast<Override&>(*this).Default(); } + bool StartArray() { return static_cast<Override&>(*this).Default(); } + bool EndArray(SizeType) { return static_cast<Override&>(*this).Default(); } +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamLocalCopy + +namespace internal { + +template<typename Stream, int = StreamTraits<Stream>::copyOptimization> +class StreamLocalCopy; + +//! Do copy optimization. +template<typename Stream> +class StreamLocalCopy<Stream, 1> { +public: + StreamLocalCopy(Stream& original) : s(original), original_(original) {} + ~StreamLocalCopy() { original_ = s; } + + Stream s; + +private: + StreamLocalCopy& operator=(const StreamLocalCopy&) /* = delete */; + + Stream& original_; +}; + +//! Keep reference. +template<typename Stream> +class StreamLocalCopy<Stream, 0> { +public: + StreamLocalCopy(Stream& original) : s(original) {} + + Stream& s; + +private: + StreamLocalCopy& operator=(const StreamLocalCopy&) /* = delete */; +}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// SkipWhitespace + +//! Skip the JSON white spaces in a stream. +/*! \param is A input stream for skipping white spaces. + \note This function has SSE2/SSE4.2 specialization. +*/ +template<typename InputStream> +void SkipWhitespace(InputStream& is) { + internal::StreamLocalCopy<InputStream> copy(is); + InputStream& s(copy.s); + + typename InputStream::Ch c; + while ((c = s.Peek()) == ' ' || c == '\n' || c == '\r' || c == '\t') + s.Take(); +} + +inline const char* SkipWhitespace(const char* p, const char* end) { + while (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + return p; +} + +#ifdef RAPIDJSON_SSE42 +//! Skip whitespace with SSE 4.2 pcmpistrm instruction, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + // Fast return for single non-whitespace + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // 16-byte align to the next boundary + const char* nextAligned = reinterpret_cast<const char*>((reinterpret_cast<size_t>(p) + 15) & static_cast<size_t>(~15)); + while (p != nextAligned) + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // The rest of string using SIMD + static const char whitespace[16] = " \n\r\t"; + const __m128i w = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&whitespace[0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast<const __m128i *>(p)); + const int r = _mm_cmpistri(w, s, _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_LEAST_SIGNIFICANT | _SIDD_NEGATIVE_POLARITY); + if (r != 16) // some of characters is non-whitespace + return p + r; + } +} + +inline const char *SkipWhitespace_SIMD(const char* p, const char* end) { + // Fast return for single non-whitespace + if (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + else + return p; + + // The middle of string using SIMD + static const char whitespace[16] = " \n\r\t"; + const __m128i w = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&whitespace[0])); + + for (; p <= end - 16; p += 16) { + const __m128i s = _mm_loadu_si128(reinterpret_cast<const __m128i *>(p)); + const int r = _mm_cmpistri(w, s, _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_LEAST_SIGNIFICANT | _SIDD_NEGATIVE_POLARITY); + if (r != 16) // some of characters is non-whitespace + return p + r; + } + + return SkipWhitespace(p, end); +} + +#elif defined(RAPIDJSON_SSE2) + +//! Skip whitespace with SSE2 instructions, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + // Fast return for single non-whitespace + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // 16-byte align to the next boundary + const char* nextAligned = reinterpret_cast<const char*>((reinterpret_cast<size_t>(p) + 15) & static_cast<size_t>(~15)); + while (p != nextAligned) + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // The rest of string + #define C16(c) { c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c } + static const char whitespaces[4][16] = { C16(' '), C16('\n'), C16('\r'), C16('\t') }; + #undef C16 + + const __m128i w0 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&whitespaces[0][0])); + const __m128i w1 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&whitespaces[1][0])); + const __m128i w2 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&whitespaces[2][0])); + const __m128i w3 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&whitespaces[3][0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast<const __m128i *>(p)); + __m128i x = _mm_cmpeq_epi8(s, w0); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w1)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w2)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w3)); + unsigned short r = static_cast<unsigned short>(~_mm_movemask_epi8(x)); + if (r != 0) { // some of characters may be non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + _BitScanForward(&offset, r); + return p + offset; +#else + return p + __builtin_ffs(r) - 1; +#endif + } + } +} + +inline const char *SkipWhitespace_SIMD(const char* p, const char* end) { + // Fast return for single non-whitespace + if (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + else + return p; + + // The rest of string + #define C16(c) { c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c } + static const char whitespaces[4][16] = { C16(' '), C16('\n'), C16('\r'), C16('\t') }; + #undef C16 + + const __m128i w0 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&whitespaces[0][0])); + const __m128i w1 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&whitespaces[1][0])); + const __m128i w2 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&whitespaces[2][0])); + const __m128i w3 = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&whitespaces[3][0])); + + for (; p <= end - 16; p += 16) { + const __m128i s = _mm_loadu_si128(reinterpret_cast<const __m128i *>(p)); + __m128i x = _mm_cmpeq_epi8(s, w0); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w1)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w2)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w3)); + unsigned short r = static_cast<unsigned short>(~_mm_movemask_epi8(x)); + if (r != 0) { // some of characters may be non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + _BitScanForward(&offset, r); + return p + offset; +#else + return p + __builtin_ffs(r) - 1; +#endif + } + } + + return SkipWhitespace(p, end); +} + +#elif defined(RAPIDJSON_NEON) + +//! Skip whitespace with ARM Neon instructions, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + // Fast return for single non-whitespace + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // 16-byte align to the next boundary + const char* nextAligned = reinterpret_cast<const char*>((reinterpret_cast<size_t>(p) + 15) & static_cast<size_t>(~15)); + while (p != nextAligned) + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + const uint8x16_t w0 = vmovq_n_u8(' '); + const uint8x16_t w1 = vmovq_n_u8('\n'); + const uint8x16_t w2 = vmovq_n_u8('\r'); + const uint8x16_t w3 = vmovq_n_u8('\t'); + + for (;; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast<const uint8_t *>(p)); + uint8x16_t x = vceqq_u8(s, w0); + x = vorrq_u8(x, vceqq_u8(s, w1)); + x = vorrq_u8(x, vceqq_u8(s, w2)); + x = vorrq_u8(x, vceqq_u8(s, w3)); + + x = vmvnq_u8(x); // Negate + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(vreinterpretq_u64_u8(x), 0); // extract + uint64_t high = vgetq_lane_u64(vreinterpretq_u64_u8(x), 1); // extract + + if (low == 0) { + if (high != 0) { + uint32_t lz = internal::clzll(high); + return p + 8 + (lz >> 3); + } + } else { + uint32_t lz = internal::clzll(low); + return p + (lz >> 3); + } + } +} + +inline const char *SkipWhitespace_SIMD(const char* p, const char* end) { + // Fast return for single non-whitespace + if (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + else + return p; + + const uint8x16_t w0 = vmovq_n_u8(' '); + const uint8x16_t w1 = vmovq_n_u8('\n'); + const uint8x16_t w2 = vmovq_n_u8('\r'); + const uint8x16_t w3 = vmovq_n_u8('\t'); + + for (; p <= end - 16; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast<const uint8_t *>(p)); + uint8x16_t x = vceqq_u8(s, w0); + x = vorrq_u8(x, vceqq_u8(s, w1)); + x = vorrq_u8(x, vceqq_u8(s, w2)); + x = vorrq_u8(x, vceqq_u8(s, w3)); + + x = vmvnq_u8(x); // Negate + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(vreinterpretq_u64_u8(x), 0); // extract + uint64_t high = vgetq_lane_u64(vreinterpretq_u64_u8(x), 1); // extract + + if (low == 0) { + if (high != 0) { + uint32_t lz = internal::clzll(high); + return p + 8 + (lz >> 3); + } + } else { + uint32_t lz = internal::clzll(low); + return p + (lz >> 3); + } + } + + return SkipWhitespace(p, end); +} + +#endif // RAPIDJSON_NEON + +#ifdef RAPIDJSON_SIMD +//! Template function specialization for InsituStringStream +template<> inline void SkipWhitespace(InsituStringStream& is) { + is.src_ = const_cast<char*>(SkipWhitespace_SIMD(is.src_)); +} + +//! Template function specialization for StringStream +template<> inline void SkipWhitespace(StringStream& is) { + is.src_ = SkipWhitespace_SIMD(is.src_); +} + +template<> inline void SkipWhitespace(EncodedInputStream<UTF8<>, MemoryStream>& is) { + is.is_.src_ = SkipWhitespace_SIMD(is.is_.src_, is.is_.end_); +} +#endif // RAPIDJSON_SIMD + +/////////////////////////////////////////////////////////////////////////////// +// GenericReader + +//! SAX-style JSON parser. Use \ref Reader for UTF8 encoding and default allocator. +/*! GenericReader parses JSON text from a stream, and send events synchronously to an + object implementing Handler concept. + + It needs to allocate a stack for storing a single decoded string during + non-destructive parsing. + + For in-situ parsing, the decoded string is directly written to the source + text string, no temporary buffer is required. + + A GenericReader object can be reused for parsing multiple JSON text. + + \tparam SourceEncoding Encoding of the input stream. + \tparam TargetEncoding Encoding of the parse output. + \tparam StackAllocator Allocator type for stack. +*/ +template <typename SourceEncoding, typename TargetEncoding, typename StackAllocator = CrtAllocator> +class GenericReader { +public: + typedef typename SourceEncoding::Ch Ch; //!< SourceEncoding character type + + //! Constructor. + /*! \param stackAllocator Optional allocator for allocating stack memory. (Only use for non-destructive parsing) + \param stackCapacity stack capacity in bytes for storing a single decoded string. (Only use for non-destructive parsing) + */ + GenericReader(StackAllocator* stackAllocator = 0, size_t stackCapacity = kDefaultStackCapacity) : + stack_(stackAllocator, stackCapacity), parseResult_(), state_(IterativeParsingStartState) {} + + //! Parse JSON text. + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept. + \tparam Handler Type of handler, implementing Handler concept. + \param is Input stream to be parsed. + \param handler The handler to receive events. + \return Whether the parsing is successful. + */ + template <unsigned parseFlags, typename InputStream, typename Handler> + ParseResult Parse(InputStream& is, Handler& handler) { + if (parseFlags & kParseIterativeFlag) + return IterativeParse<parseFlags>(is, handler); + + parseResult_.Clear(); + + ClearStackOnExit scope(*this); + + SkipWhitespaceAndComments<parseFlags>(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + + if (RAPIDJSON_UNLIKELY(is.Peek() == '\0')) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorDocumentEmpty, is.Tell()); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + else { + ParseValue<parseFlags>(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + + if (!(parseFlags & kParseStopWhenDoneFlag)) { + SkipWhitespaceAndComments<parseFlags>(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + + if (RAPIDJSON_UNLIKELY(is.Peek() != '\0')) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorDocumentRootNotSingular, is.Tell()); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + } + } + + return parseResult_; + } + + //! Parse JSON text (with \ref kParseDefaultFlags) + /*! \tparam InputStream Type of input stream, implementing Stream concept + \tparam Handler Type of handler, implementing Handler concept. + \param is Input stream to be parsed. + \param handler The handler to receive events. + \return Whether the parsing is successful. + */ + template <typename InputStream, typename Handler> + ParseResult Parse(InputStream& is, Handler& handler) { + return Parse<kParseDefaultFlags>(is, handler); + } + + //! Initialize JSON text token-by-token parsing + /*! + */ + void IterativeParseInit() { + parseResult_.Clear(); + state_ = IterativeParsingStartState; + } + + //! Parse one token from JSON text + /*! \tparam InputStream Type of input stream, implementing Stream concept + \tparam Handler Type of handler, implementing Handler concept. + \param is Input stream to be parsed. + \param handler The handler to receive events. + \return Whether the parsing is successful. + */ + template <unsigned parseFlags, typename InputStream, typename Handler> + bool IterativeParseNext(InputStream& is, Handler& handler) { + while (RAPIDJSON_LIKELY(is.Peek() != '\0')) { + SkipWhitespaceAndComments<parseFlags>(is); + + Token t = Tokenize(is.Peek()); + IterativeParsingState n = Predict(state_, t); + IterativeParsingState d = Transit<parseFlags>(state_, t, n, is, handler); + + // If we've finished or hit an error... + if (RAPIDJSON_UNLIKELY(IsIterativeParsingCompleteState(d))) { + // Report errors. + if (d == IterativeParsingErrorState) { + HandleError(state_, is); + return false; + } + + // Transition to the finish state. + RAPIDJSON_ASSERT(d == IterativeParsingFinishState); + state_ = d; + + // If StopWhenDone is not set... + if (!(parseFlags & kParseStopWhenDoneFlag)) { + // ... and extra non-whitespace data is found... + SkipWhitespaceAndComments<parseFlags>(is); + if (is.Peek() != '\0') { + // ... this is considered an error. + HandleError(state_, is); + return false; + } + } + + // Success! We are done! + return true; + } + + // Transition to the new state. + state_ = d; + + // If we parsed anything other than a delimiter, we invoked the handler, so we can return true now. + if (!IsIterativeParsingDelimiterState(n)) + return true; + } + + // We reached the end of file. + stack_.Clear(); + + if (state_ != IterativeParsingFinishState) { + HandleError(state_, is); + return false; + } + + return true; + } + + //! Check if token-by-token parsing JSON text is complete + /*! \return Whether the JSON has been fully decoded. + */ + RAPIDJSON_FORCEINLINE bool IterativeParseComplete() const { + return IsIterativeParsingCompleteState(state_); + } + + //! Whether a parse error has occurred in the last parsing. + bool HasParseError() const { return parseResult_.IsError(); } + + //! Get the \ref ParseErrorCode of last parsing. + ParseErrorCode GetParseErrorCode() const { return parseResult_.Code(); } + + //! Get the position of last parsing error in input, 0 otherwise. + size_t GetErrorOffset() const { return parseResult_.Offset(); } + +protected: + void SetParseError(ParseErrorCode code, size_t offset) { parseResult_.Set(code, offset); } + +private: + // Prohibit copy constructor & assignment operator. + GenericReader(const GenericReader&); + GenericReader& operator=(const GenericReader&); + + void ClearStack() { stack_.Clear(); } + + // clear stack on any exit from ParseStream, e.g. due to exception + struct ClearStackOnExit { + explicit ClearStackOnExit(GenericReader& r) : r_(r) {} + ~ClearStackOnExit() { r_.ClearStack(); } + private: + GenericReader& r_; + ClearStackOnExit(const ClearStackOnExit&); + ClearStackOnExit& operator=(const ClearStackOnExit&); + }; + + template<unsigned parseFlags, typename InputStream> + void SkipWhitespaceAndComments(InputStream& is) { + SkipWhitespace(is); + + if (parseFlags & kParseCommentsFlag) { + while (RAPIDJSON_UNLIKELY(Consume(is, '/'))) { + if (Consume(is, '*')) { + while (true) { + if (RAPIDJSON_UNLIKELY(is.Peek() == '\0')) + RAPIDJSON_PARSE_ERROR(kParseErrorUnspecificSyntaxError, is.Tell()); + else if (Consume(is, '*')) { + if (Consume(is, '/')) + break; + } + else + is.Take(); + } + } + else if (RAPIDJSON_LIKELY(Consume(is, '/'))) + while (is.Peek() != '\0' && is.Take() != '\n') {} + else + RAPIDJSON_PARSE_ERROR(kParseErrorUnspecificSyntaxError, is.Tell()); + + SkipWhitespace(is); + } + } + } + + // Parse object: { string : value, ... } + template<unsigned parseFlags, typename InputStream, typename Handler> + void ParseObject(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == '{'); + is.Take(); // Skip '{' + + if (RAPIDJSON_UNLIKELY(!handler.StartObject())) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + + SkipWhitespaceAndComments<parseFlags>(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (Consume(is, '}')) { + if (RAPIDJSON_UNLIKELY(!handler.EndObject(0))) // empty object + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + + for (SizeType memberCount = 0;;) { + if (RAPIDJSON_UNLIKELY(is.Peek() != '"')) + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissName, is.Tell()); + + ParseString<parseFlags>(is, handler, true); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + SkipWhitespaceAndComments<parseFlags>(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (RAPIDJSON_UNLIKELY(!Consume(is, ':'))) + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissColon, is.Tell()); + + SkipWhitespaceAndComments<parseFlags>(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + ParseValue<parseFlags>(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + SkipWhitespaceAndComments<parseFlags>(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + ++memberCount; + + switch (is.Peek()) { + case ',': + is.Take(); + SkipWhitespaceAndComments<parseFlags>(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + break; + case '}': + is.Take(); + if (RAPIDJSON_UNLIKELY(!handler.EndObject(memberCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + default: + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, is.Tell()); break; // This useless break is only for making warning and coverage happy + } + + if (parseFlags & kParseTrailingCommasFlag) { + if (is.Peek() == '}') { + if (RAPIDJSON_UNLIKELY(!handler.EndObject(memberCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + is.Take(); + return; + } + } + } + } + + // Parse array: [ value, ... ] + template<unsigned parseFlags, typename InputStream, typename Handler> + void ParseArray(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == '['); + is.Take(); // Skip '[' + + if (RAPIDJSON_UNLIKELY(!handler.StartArray())) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + + SkipWhitespaceAndComments<parseFlags>(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (Consume(is, ']')) { + if (RAPIDJSON_UNLIKELY(!handler.EndArray(0))) // empty array + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + + for (SizeType elementCount = 0;;) { + ParseValue<parseFlags>(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + ++elementCount; + SkipWhitespaceAndComments<parseFlags>(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (Consume(is, ',')) { + SkipWhitespaceAndComments<parseFlags>(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + } + else if (Consume(is, ']')) { + if (RAPIDJSON_UNLIKELY(!handler.EndArray(elementCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorArrayMissCommaOrSquareBracket, is.Tell()); + + if (parseFlags & kParseTrailingCommasFlag) { + if (is.Peek() == ']') { + if (RAPIDJSON_UNLIKELY(!handler.EndArray(elementCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + is.Take(); + return; + } + } + } + } + + template<unsigned parseFlags, typename InputStream, typename Handler> + void ParseNull(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 'n'); + is.Take(); + + if (RAPIDJSON_LIKELY(Consume(is, 'u') && Consume(is, 'l') && Consume(is, 'l'))) { + if (RAPIDJSON_UNLIKELY(!handler.Null())) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); + } + + template<unsigned parseFlags, typename InputStream, typename Handler> + void ParseTrue(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 't'); + is.Take(); + + if (RAPIDJSON_LIKELY(Consume(is, 'r') && Consume(is, 'u') && Consume(is, 'e'))) { + if (RAPIDJSON_UNLIKELY(!handler.Bool(true))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); + } + + template<unsigned parseFlags, typename InputStream, typename Handler> + void ParseFalse(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 'f'); + is.Take(); + + if (RAPIDJSON_LIKELY(Consume(is, 'a') && Consume(is, 'l') && Consume(is, 's') && Consume(is, 'e'))) { + if (RAPIDJSON_UNLIKELY(!handler.Bool(false))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); + } + + template<typename InputStream> + RAPIDJSON_FORCEINLINE static bool Consume(InputStream& is, typename InputStream::Ch expect) { + if (RAPIDJSON_LIKELY(is.Peek() == expect)) { + is.Take(); + return true; + } + else + return false; + } + + // Helper function to parse four hexadecimal digits in \uXXXX in ParseString(). + template<typename InputStream> + unsigned ParseHex4(InputStream& is, size_t escapeOffset) { + unsigned codepoint = 0; + for (int i = 0; i < 4; i++) { + Ch c = is.Peek(); + codepoint <<= 4; + codepoint += static_cast<unsigned>(c); + if (c >= '0' && c <= '9') + codepoint -= '0'; + else if (c >= 'A' && c <= 'F') + codepoint -= 'A' - 10; + else if (c >= 'a' && c <= 'f') + codepoint -= 'a' - 10; + else { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorStringUnicodeEscapeInvalidHex, escapeOffset); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(0); + } + is.Take(); + } + return codepoint; + } + + template <typename CharType> + class StackStream { + public: + typedef CharType Ch; + + StackStream(internal::Stack<StackAllocator>& stack) : stack_(stack), length_(0) {} + RAPIDJSON_FORCEINLINE void Put(Ch c) { + *stack_.template Push<Ch>() = c; + ++length_; + } + + RAPIDJSON_FORCEINLINE void* Push(SizeType count) { + length_ += count; + return stack_.template Push<Ch>(count); + } + + size_t Length() const { return length_; } + + Ch* Pop() { + return stack_.template Pop<Ch>(length_); + } + + private: + StackStream(const StackStream&); + StackStream& operator=(const StackStream&); + + internal::Stack<StackAllocator>& stack_; + SizeType length_; + }; + + // Parse string and generate String event. Different code paths for kParseInsituFlag. + template<unsigned parseFlags, typename InputStream, typename Handler> + void ParseString(InputStream& is, Handler& handler, bool isKey = false) { + internal::StreamLocalCopy<InputStream> copy(is); + InputStream& s(copy.s); + + RAPIDJSON_ASSERT(s.Peek() == '\"'); + s.Take(); // Skip '\"' + + bool success = false; + if (parseFlags & kParseInsituFlag) { + typename InputStream::Ch *head = s.PutBegin(); + ParseStringToStream<parseFlags, SourceEncoding, SourceEncoding>(s, s); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + size_t length = s.PutEnd(head) - 1; + RAPIDJSON_ASSERT(length <= 0xFFFFFFFF); + const typename TargetEncoding::Ch* const str = reinterpret_cast<typename TargetEncoding::Ch*>(head); + success = (isKey ? handler.Key(str, SizeType(length), false) : handler.String(str, SizeType(length), false)); + } + else { + StackStream<typename TargetEncoding::Ch> stackStream(stack_); + ParseStringToStream<parseFlags, SourceEncoding, TargetEncoding>(s, stackStream); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + SizeType length = static_cast<SizeType>(stackStream.Length()) - 1; + const typename TargetEncoding::Ch* const str = stackStream.Pop(); + success = (isKey ? handler.Key(str, length, true) : handler.String(str, length, true)); + } + if (RAPIDJSON_UNLIKELY(!success)) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, s.Tell()); + } + + // Parse string to an output is + // This function handles the prefix/suffix double quotes, escaping, and optional encoding validation. + template<unsigned parseFlags, typename SEncoding, typename TEncoding, typename InputStream, typename OutputStream> + RAPIDJSON_FORCEINLINE void ParseStringToStream(InputStream& is, OutputStream& os) { +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + static const char escape[256] = { + Z16, Z16, 0, 0,'\"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '/', + Z16, Z16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, + 0, 0,'\b', 0, 0, 0,'\f', 0, 0, 0, 0, 0, 0, 0,'\n', 0, + 0, 0,'\r', 0,'\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 + }; +#undef Z16 +//!@endcond + + for (;;) { + // Scan and copy string before "\\\"" or < 0x20. This is an optional optimzation. + if (!(parseFlags & kParseValidateEncodingFlag)) + ScanCopyUnescapedString(is, os); + + Ch c = is.Peek(); + if (RAPIDJSON_UNLIKELY(c == '\\')) { // Escape + size_t escapeOffset = is.Tell(); // For invalid escaping, report the initial '\\' as error offset + is.Take(); + Ch e = is.Peek(); + if ((sizeof(Ch) == 1 || unsigned(e) < 256) && RAPIDJSON_LIKELY(escape[static_cast<unsigned char>(e)])) { + is.Take(); + os.Put(static_cast<typename TEncoding::Ch>(escape[static_cast<unsigned char>(e)])); + } + else if ((parseFlags & kParseEscapedApostropheFlag) && RAPIDJSON_LIKELY(e == '\'')) { // Allow escaped apostrophe + is.Take(); + os.Put('\''); + } + else if (RAPIDJSON_LIKELY(e == 'u')) { // Unicode + is.Take(); + unsigned codepoint = ParseHex4(is, escapeOffset); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + if (RAPIDJSON_UNLIKELY(codepoint >= 0xD800 && codepoint <= 0xDFFF)) { + // high surrogate, check if followed by valid low surrogate + if (RAPIDJSON_LIKELY(codepoint <= 0xDBFF)) { + // Handle UTF-16 surrogate pair + if (RAPIDJSON_UNLIKELY(!Consume(is, '\\') || !Consume(is, 'u'))) + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, escapeOffset); + unsigned codepoint2 = ParseHex4(is, escapeOffset); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + if (RAPIDJSON_UNLIKELY(codepoint2 < 0xDC00 || codepoint2 > 0xDFFF)) + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, escapeOffset); + codepoint = (((codepoint - 0xD800) << 10) | (codepoint2 - 0xDC00)) + 0x10000; + } + // single low surrogate + else + { + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, escapeOffset); + } + } + TEncoding::Encode(os, codepoint); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorStringEscapeInvalid, escapeOffset); + } + else if (RAPIDJSON_UNLIKELY(c == '"')) { // Closing double quote + is.Take(); + os.Put('\0'); // null-terminate the string + return; + } + else if (RAPIDJSON_UNLIKELY(static_cast<unsigned>(c) < 0x20)) { // RFC 4627: unescaped = %x20-21 / %x23-5B / %x5D-10FFFF + if (c == '\0') + RAPIDJSON_PARSE_ERROR(kParseErrorStringMissQuotationMark, is.Tell()); + else + RAPIDJSON_PARSE_ERROR(kParseErrorStringInvalidEncoding, is.Tell()); + } + else { + size_t offset = is.Tell(); + if (RAPIDJSON_UNLIKELY((parseFlags & kParseValidateEncodingFlag ? + !Transcoder<SEncoding, TEncoding>::Validate(is, os) : + !Transcoder<SEncoding, TEncoding>::Transcode(is, os)))) + RAPIDJSON_PARSE_ERROR(kParseErrorStringInvalidEncoding, offset); + } + } + } + + template<typename InputStream, typename OutputStream> + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(InputStream&, OutputStream&) { + // Do nothing for generic version + } + +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) + // StringStream -> StackStream<char> + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(StringStream& is, StackStream<char>& os) { + const char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast<const char*>((reinterpret_cast<size_t>(p) + 15) & static_cast<size_t>(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast<unsigned>(*p) < 0x20)) { + is.src_ = p; + return; + } + else + os.Put(*p++); + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&space[0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast<const __m128i *>(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast<unsigned short>(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + SizeType length; + #ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + length = offset; + #else + length = static_cast<SizeType>(__builtin_ffs(r) - 1); + #endif + if (length != 0) { + char* q = reinterpret_cast<char*>(os.Push(length)); + for (size_t i = 0; i < length; i++) + q[i] = p[i]; + + p += length; + } + break; + } + _mm_storeu_si128(reinterpret_cast<__m128i *>(os.Push(16)), s); + } + + is.src_ = p; + } + + // InsituStringStream -> InsituStringStream + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(InsituStringStream& is, InsituStringStream& os) { + RAPIDJSON_ASSERT(&is == &os); + (void)os; + + if (is.src_ == is.dst_) { + SkipUnescapedString(is); + return; + } + + char* p = is.src_; + char *q = is.dst_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast<const char*>((reinterpret_cast<size_t>(p) + 15) & static_cast<size_t>(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast<unsigned>(*p) < 0x20)) { + is.src_ = p; + is.dst_ = q; + return; + } + else + *q++ = *p++; + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&space[0])); + + for (;; p += 16, q += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast<const __m128i *>(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast<unsigned short>(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + size_t length; +#ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + length = offset; +#else + length = static_cast<size_t>(__builtin_ffs(r) - 1); +#endif + for (const char* pend = p + length; p != pend; ) + *q++ = *p++; + break; + } + _mm_storeu_si128(reinterpret_cast<__m128i *>(q), s); + } + + is.src_ = p; + is.dst_ = q; + } + + // When read/write pointers are the same for insitu stream, just skip unescaped characters + static RAPIDJSON_FORCEINLINE void SkipUnescapedString(InsituStringStream& is) { + RAPIDJSON_ASSERT(is.src_ == is.dst_); + char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast<const char*>((reinterpret_cast<size_t>(p) + 15) & static_cast<size_t>(~15)); + for (; p != nextAligned; p++) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast<unsigned>(*p) < 0x20)) { + is.src_ = is.dst_ = p; + return; + } + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&space[0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast<const __m128i *>(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast<unsigned short>(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + size_t length; +#ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + length = offset; +#else + length = static_cast<size_t>(__builtin_ffs(r) - 1); +#endif + p += length; + break; + } + } + + is.src_ = is.dst_ = p; + } +#elif defined(RAPIDJSON_NEON) + // StringStream -> StackStream<char> + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(StringStream& is, StackStream<char>& os) { + const char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast<const char*>((reinterpret_cast<size_t>(p) + 15) & static_cast<size_t>(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast<unsigned>(*p) < 0x20)) { + is.src_ = p; + return; + } + else + os.Put(*p++); + + // The rest of string using SIMD + const uint8x16_t s0 = vmovq_n_u8('"'); + const uint8x16_t s1 = vmovq_n_u8('\\'); + const uint8x16_t s2 = vmovq_n_u8('\b'); + const uint8x16_t s3 = vmovq_n_u8(32); + + for (;; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast<const uint8_t *>(p)); + uint8x16_t x = vceqq_u8(s, s0); + x = vorrq_u8(x, vceqq_u8(s, s1)); + x = vorrq_u8(x, vceqq_u8(s, s2)); + x = vorrq_u8(x, vcltq_u8(s, s3)); + + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(vreinterpretq_u64_u8(x), 0); // extract + uint64_t high = vgetq_lane_u64(vreinterpretq_u64_u8(x), 1); // extract + + SizeType length = 0; + bool escaped = false; + if (low == 0) { + if (high != 0) { + uint32_t lz = internal::clzll(high); + length = 8 + (lz >> 3); + escaped = true; + } + } else { + uint32_t lz = internal::clzll(low); + length = lz >> 3; + escaped = true; + } + if (RAPIDJSON_UNLIKELY(escaped)) { // some of characters is escaped + if (length != 0) { + char* q = reinterpret_cast<char*>(os.Push(length)); + for (size_t i = 0; i < length; i++) + q[i] = p[i]; + + p += length; + } + break; + } + vst1q_u8(reinterpret_cast<uint8_t *>(os.Push(16)), s); + } + + is.src_ = p; + } + + // InsituStringStream -> InsituStringStream + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(InsituStringStream& is, InsituStringStream& os) { + RAPIDJSON_ASSERT(&is == &os); + (void)os; + + if (is.src_ == is.dst_) { + SkipUnescapedString(is); + return; + } + + char* p = is.src_; + char *q = is.dst_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast<const char*>((reinterpret_cast<size_t>(p) + 15) & static_cast<size_t>(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast<unsigned>(*p) < 0x20)) { + is.src_ = p; + is.dst_ = q; + return; + } + else + *q++ = *p++; + + // The rest of string using SIMD + const uint8x16_t s0 = vmovq_n_u8('"'); + const uint8x16_t s1 = vmovq_n_u8('\\'); + const uint8x16_t s2 = vmovq_n_u8('\b'); + const uint8x16_t s3 = vmovq_n_u8(32); + + for (;; p += 16, q += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast<uint8_t *>(p)); + uint8x16_t x = vceqq_u8(s, s0); + x = vorrq_u8(x, vceqq_u8(s, s1)); + x = vorrq_u8(x, vceqq_u8(s, s2)); + x = vorrq_u8(x, vcltq_u8(s, s3)); + + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(vreinterpretq_u64_u8(x), 0); // extract + uint64_t high = vgetq_lane_u64(vreinterpretq_u64_u8(x), 1); // extract + + SizeType length = 0; + bool escaped = false; + if (low == 0) { + if (high != 0) { + uint32_t lz = internal::clzll(high); + length = 8 + (lz >> 3); + escaped = true; + } + } else { + uint32_t lz = internal::clzll(low); + length = lz >> 3; + escaped = true; + } + if (RAPIDJSON_UNLIKELY(escaped)) { // some of characters is escaped + for (const char* pend = p + length; p != pend; ) { + *q++ = *p++; + } + break; + } + vst1q_u8(reinterpret_cast<uint8_t *>(q), s); + } + + is.src_ = p; + is.dst_ = q; + } + + // When read/write pointers are the same for insitu stream, just skip unescaped characters + static RAPIDJSON_FORCEINLINE void SkipUnescapedString(InsituStringStream& is) { + RAPIDJSON_ASSERT(is.src_ == is.dst_); + char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast<const char*>((reinterpret_cast<size_t>(p) + 15) & static_cast<size_t>(~15)); + for (; p != nextAligned; p++) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast<unsigned>(*p) < 0x20)) { + is.src_ = is.dst_ = p; + return; + } + + // The rest of string using SIMD + const uint8x16_t s0 = vmovq_n_u8('"'); + const uint8x16_t s1 = vmovq_n_u8('\\'); + const uint8x16_t s2 = vmovq_n_u8('\b'); + const uint8x16_t s3 = vmovq_n_u8(32); + + for (;; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast<uint8_t *>(p)); + uint8x16_t x = vceqq_u8(s, s0); + x = vorrq_u8(x, vceqq_u8(s, s1)); + x = vorrq_u8(x, vceqq_u8(s, s2)); + x = vorrq_u8(x, vcltq_u8(s, s3)); + + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(vreinterpretq_u64_u8(x), 0); // extract + uint64_t high = vgetq_lane_u64(vreinterpretq_u64_u8(x), 1); // extract + + if (low == 0) { + if (high != 0) { + uint32_t lz = internal::clzll(high); + p += 8 + (lz >> 3); + break; + } + } else { + uint32_t lz = internal::clzll(low); + p += lz >> 3; + break; + } + } + + is.src_ = is.dst_ = p; + } +#endif // RAPIDJSON_NEON + + template<typename InputStream, bool backup, bool pushOnTake> + class NumberStream; + + template<typename InputStream> + class NumberStream<InputStream, false, false> { + public: + typedef typename InputStream::Ch Ch; + + NumberStream(GenericReader& reader, InputStream& s) : is(s) { (void)reader; } + + RAPIDJSON_FORCEINLINE Ch Peek() const { return is.Peek(); } + RAPIDJSON_FORCEINLINE Ch TakePush() { return is.Take(); } + RAPIDJSON_FORCEINLINE Ch Take() { return is.Take(); } + RAPIDJSON_FORCEINLINE void Push(char) {} + + size_t Tell() { return is.Tell(); } + size_t Length() { return 0; } + const char* Pop() { return 0; } + + protected: + NumberStream& operator=(const NumberStream&); + + InputStream& is; + }; + + template<typename InputStream> + class NumberStream<InputStream, true, false> : public NumberStream<InputStream, false, false> { + typedef NumberStream<InputStream, false, false> Base; + public: + NumberStream(GenericReader& reader, InputStream& is) : Base(reader, is), stackStream(reader.stack_) {} + + RAPIDJSON_FORCEINLINE Ch TakePush() { + stackStream.Put(static_cast<char>(Base::is.Peek())); + return Base::is.Take(); + } + + RAPIDJSON_FORCEINLINE void Push(char c) { + stackStream.Put(c); + } + + size_t Length() { return stackStream.Length(); } + + const char* Pop() { + stackStream.Put('\0'); + return stackStream.Pop(); + } + + private: + StackStream<char> stackStream; + }; + + template<typename InputStream> + class NumberStream<InputStream, true, true> : public NumberStream<InputStream, true, false> { + typedef NumberStream<InputStream, true, false> Base; + public: + NumberStream(GenericReader& reader, InputStream& is) : Base(reader, is) {} + + RAPIDJSON_FORCEINLINE Ch Take() { return Base::TakePush(); } + }; + + template<unsigned parseFlags, typename InputStream, typename Handler> + void ParseNumber(InputStream& is, Handler& handler) { + internal::StreamLocalCopy<InputStream> copy(is); + NumberStream<InputStream, + ((parseFlags & kParseNumbersAsStringsFlag) != 0) ? + ((parseFlags & kParseInsituFlag) == 0) : + ((parseFlags & kParseFullPrecisionFlag) != 0), + (parseFlags & kParseNumbersAsStringsFlag) != 0 && + (parseFlags & kParseInsituFlag) == 0> s(*this, copy.s); + + size_t startOffset = s.Tell(); + double d = 0.0; + bool useNanOrInf = false; + + // Parse minus + bool minus = Consume(s, '-'); + + // Parse int: zero / ( digit1-9 *DIGIT ) + unsigned i = 0; + uint64_t i64 = 0; + bool use64bit = false; + int significandDigit = 0; + if (RAPIDJSON_UNLIKELY(s.Peek() == '0')) { + i = 0; + s.TakePush(); + } + else if (RAPIDJSON_LIKELY(s.Peek() >= '1' && s.Peek() <= '9')) { + i = static_cast<unsigned>(s.TakePush() - '0'); + + if (minus) + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i >= 214748364)) { // 2^31 = 2147483648 + if (RAPIDJSON_LIKELY(i != 214748364 || s.Peek() > '8')) { + i64 = i; + use64bit = true; + break; + } + } + i = i * 10 + static_cast<unsigned>(s.TakePush() - '0'); + significandDigit++; + } + else + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i >= 429496729)) { // 2^32 - 1 = 4294967295 + if (RAPIDJSON_LIKELY(i != 429496729 || s.Peek() > '5')) { + i64 = i; + use64bit = true; + break; + } + } + i = i * 10 + static_cast<unsigned>(s.TakePush() - '0'); + significandDigit++; + } + } + // Parse NaN or Infinity here + else if ((parseFlags & kParseNanAndInfFlag) && RAPIDJSON_LIKELY((s.Peek() == 'I' || s.Peek() == 'N'))) { + if (Consume(s, 'N')) { + if (Consume(s, 'a') && Consume(s, 'N')) { + d = std::numeric_limits<double>::quiet_NaN(); + useNanOrInf = true; + } + } + else if (RAPIDJSON_LIKELY(Consume(s, 'I'))) { + if (Consume(s, 'n') && Consume(s, 'f')) { + d = (minus ? -std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity()); + useNanOrInf = true; + + if (RAPIDJSON_UNLIKELY(s.Peek() == 'i' && !(Consume(s, 'i') && Consume(s, 'n') + && Consume(s, 'i') && Consume(s, 't') && Consume(s, 'y')))) { + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); + } + } + } + + if (RAPIDJSON_UNLIKELY(!useNanOrInf)) { + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); + } + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); + + // Parse 64bit int + bool useDouble = false; + if (use64bit) { + if (minus) + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i64 >= RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC))) // 2^63 = 9223372036854775808 + if (RAPIDJSON_LIKELY(i64 != RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC) || s.Peek() > '8')) { + d = static_cast<double>(i64); + useDouble = true; + break; + } + i64 = i64 * 10 + static_cast<unsigned>(s.TakePush() - '0'); + significandDigit++; + } + else + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i64 >= RAPIDJSON_UINT64_C2(0x19999999, 0x99999999))) // 2^64 - 1 = 18446744073709551615 + if (RAPIDJSON_LIKELY(i64 != RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || s.Peek() > '5')) { + d = static_cast<double>(i64); + useDouble = true; + break; + } + i64 = i64 * 10 + static_cast<unsigned>(s.TakePush() - '0'); + significandDigit++; + } + } + + // Force double for big integer + if (useDouble) { + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + d = d * 10 + (s.TakePush() - '0'); + } + } + + // Parse frac = decimal-point 1*DIGIT + int expFrac = 0; + size_t decimalPosition; + if (Consume(s, '.')) { + decimalPosition = s.Length(); + + if (RAPIDJSON_UNLIKELY(!(s.Peek() >= '0' && s.Peek() <= '9'))) + RAPIDJSON_PARSE_ERROR(kParseErrorNumberMissFraction, s.Tell()); + + if (!useDouble) { +#if RAPIDJSON_64BIT + // Use i64 to store significand in 64-bit architecture + if (!use64bit) + i64 = i; + + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (i64 > RAPIDJSON_UINT64_C2(0x1FFFFF, 0xFFFFFFFF)) // 2^53 - 1 for fast path + break; + else { + i64 = i64 * 10 + static_cast<unsigned>(s.TakePush() - '0'); + --expFrac; + if (i64 != 0) + significandDigit++; + } + } + + d = static_cast<double>(i64); +#else + // Use double to store significand in 32-bit architecture + d = static_cast<double>(use64bit ? i64 : i); +#endif + useDouble = true; + } + + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (significandDigit < 17) { + d = d * 10.0 + (s.TakePush() - '0'); + --expFrac; + if (RAPIDJSON_LIKELY(d > 0.0)) + significandDigit++; + } + else + s.TakePush(); + } + } + else + decimalPosition = s.Length(); // decimal position at the end of integer. + + // Parse exp = e [ minus / plus ] 1*DIGIT + int exp = 0; + if (Consume(s, 'e') || Consume(s, 'E')) { + if (!useDouble) { + d = static_cast<double>(use64bit ? i64 : i); + useDouble = true; + } + + bool expMinus = false; + if (Consume(s, '+')) + ; + else if (Consume(s, '-')) + expMinus = true; + + if (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + exp = static_cast<int>(s.Take() - '0'); + if (expMinus) { + // (exp + expFrac) must not underflow int => we're detecting when -exp gets + // dangerously close to INT_MIN (a pessimistic next digit 9 would push it into + // underflow territory): + // + // -(exp * 10 + 9) + expFrac >= INT_MIN + // <=> exp <= (expFrac - INT_MIN - 9) / 10 + RAPIDJSON_ASSERT(expFrac <= 0); + int maxExp = (expFrac + 2147483639) / 10; + + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + exp = exp * 10 + static_cast<int>(s.Take() - '0'); + if (RAPIDJSON_UNLIKELY(exp > maxExp)) { + while (RAPIDJSON_UNLIKELY(s.Peek() >= '0' && s.Peek() <= '9')) // Consume the rest of exponent + s.Take(); + } + } + } + else { // positive exp + int maxExp = 308 - expFrac; + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + exp = exp * 10 + static_cast<int>(s.Take() - '0'); + if (RAPIDJSON_UNLIKELY(exp > maxExp)) + RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, startOffset); + } + } + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorNumberMissExponent, s.Tell()); + + if (expMinus) + exp = -exp; + } + + // Finish parsing, call event according to the type of number. + bool cont = true; + + if (parseFlags & kParseNumbersAsStringsFlag) { + if (parseFlags & kParseInsituFlag) { + s.Pop(); // Pop stack no matter if it will be used or not. + typename InputStream::Ch* head = is.PutBegin(); + const size_t length = s.Tell() - startOffset; + RAPIDJSON_ASSERT(length <= 0xFFFFFFFF); + // unable to insert the \0 character here, it will erase the comma after this number + const typename TargetEncoding::Ch* const str = reinterpret_cast<typename TargetEncoding::Ch*>(head); + cont = handler.RawNumber(str, SizeType(length), false); + } + else { + SizeType numCharsToCopy = static_cast<SizeType>(s.Length()); + StringStream srcStream(s.Pop()); + StackStream<typename TargetEncoding::Ch> dstStream(stack_); + while (numCharsToCopy--) { + Transcoder<UTF8<>, TargetEncoding>::Transcode(srcStream, dstStream); + } + dstStream.Put('\0'); + const typename TargetEncoding::Ch* str = dstStream.Pop(); + const SizeType length = static_cast<SizeType>(dstStream.Length()) - 1; + cont = handler.RawNumber(str, SizeType(length), true); + } + } + else { + size_t length = s.Length(); + const char* decimal = s.Pop(); // Pop stack no matter if it will be used or not. + + if (useDouble) { + int p = exp + expFrac; + if (parseFlags & kParseFullPrecisionFlag) + d = internal::StrtodFullPrecision(d, p, decimal, length, decimalPosition, exp); + else + d = internal::StrtodNormalPrecision(d, p); + + // Use > max, instead of == inf, to fix bogus warning -Wfloat-equal + if (d > (std::numeric_limits<double>::max)()) { + // Overflow + // TODO: internal::StrtodX should report overflow (or underflow) + RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, startOffset); + } + + cont = handler.Double(minus ? -d : d); + } + else if (useNanOrInf) { + cont = handler.Double(d); + } + else { + if (use64bit) { + if (minus) + cont = handler.Int64(static_cast<int64_t>(~i64 + 1)); + else + cont = handler.Uint64(i64); + } + else { + if (minus) + cont = handler.Int(static_cast<int32_t>(~i + 1)); + else + cont = handler.Uint(i); + } + } + } + if (RAPIDJSON_UNLIKELY(!cont)) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, startOffset); + } + + // Parse any JSON value + template<unsigned parseFlags, typename InputStream, typename Handler> + void ParseValue(InputStream& is, Handler& handler) { + switch (is.Peek()) { + case 'n': ParseNull <parseFlags>(is, handler); break; + case 't': ParseTrue <parseFlags>(is, handler); break; + case 'f': ParseFalse <parseFlags>(is, handler); break; + case '"': ParseString<parseFlags>(is, handler); break; + case '{': ParseObject<parseFlags>(is, handler); break; + case '[': ParseArray <parseFlags>(is, handler); break; + default : + ParseNumber<parseFlags>(is, handler); + break; + + } + } + + // Iterative Parsing + + // States + enum IterativeParsingState { + IterativeParsingFinishState = 0, // sink states at top + IterativeParsingErrorState, // sink states at top + IterativeParsingStartState, + + // Object states + IterativeParsingObjectInitialState, + IterativeParsingMemberKeyState, + IterativeParsingMemberValueState, + IterativeParsingObjectFinishState, + + // Array states + IterativeParsingArrayInitialState, + IterativeParsingElementState, + IterativeParsingArrayFinishState, + + // Single value state + IterativeParsingValueState, + + // Delimiter states (at bottom) + IterativeParsingElementDelimiterState, + IterativeParsingMemberDelimiterState, + IterativeParsingKeyValueDelimiterState, + + cIterativeParsingStateCount + }; + + // Tokens + enum Token { + LeftBracketToken = 0, + RightBracketToken, + + LeftCurlyBracketToken, + RightCurlyBracketToken, + + CommaToken, + ColonToken, + + StringToken, + FalseToken, + TrueToken, + NullToken, + NumberToken, + + kTokenCount + }; + + RAPIDJSON_FORCEINLINE Token Tokenize(Ch c) const { + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define N NumberToken +#define N16 N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N + // Maps from ASCII to Token + static const unsigned char tokenMap[256] = { + N16, // 00~0F + N16, // 10~1F + N, N, StringToken, N, N, N, N, N, N, N, N, N, CommaToken, N, N, N, // 20~2F + N, N, N, N, N, N, N, N, N, N, ColonToken, N, N, N, N, N, // 30~3F + N16, // 40~4F + N, N, N, N, N, N, N, N, N, N, N, LeftBracketToken, N, RightBracketToken, N, N, // 50~5F + N, N, N, N, N, N, FalseToken, N, N, N, N, N, N, N, NullToken, N, // 60~6F + N, N, N, N, TrueToken, N, N, N, N, N, N, LeftCurlyBracketToken, N, RightCurlyBracketToken, N, N, // 70~7F + N16, N16, N16, N16, N16, N16, N16, N16 // 80~FF + }; +#undef N +#undef N16 +//!@endcond + + if (sizeof(Ch) == 1 || static_cast<unsigned>(c) < 256) + return static_cast<Token>(tokenMap[static_cast<unsigned char>(c)]); + else + return NumberToken; + } + + RAPIDJSON_FORCEINLINE IterativeParsingState Predict(IterativeParsingState state, Token token) const { + // current state x one lookahead token -> new state + static const char G[cIterativeParsingStateCount][kTokenCount] = { + // Finish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // Error(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // Start + { + IterativeParsingArrayInitialState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingValueState, // String + IterativeParsingValueState, // False + IterativeParsingValueState, // True + IterativeParsingValueState, // Null + IterativeParsingValueState // Number + }, + // ObjectInitial + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberKeyState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // MemberKey + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingKeyValueDelimiterState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // MemberValue + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingMemberDelimiterState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // ObjectFinish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // ArrayInitial + { + IterativeParsingArrayInitialState, // Left bracket(push Element state) + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push Element state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingElementState, // String + IterativeParsingElementState, // False + IterativeParsingElementState, // True + IterativeParsingElementState, // Null + IterativeParsingElementState // Number + }, + // Element + { + IterativeParsingErrorState, // Left bracket + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingElementDelimiterState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // ArrayFinish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // Single Value (sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // ElementDelimiter + { + IterativeParsingArrayInitialState, // Left bracket(push Element state) + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push Element state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingElementState, // String + IterativeParsingElementState, // False + IterativeParsingElementState, // True + IterativeParsingElementState, // Null + IterativeParsingElementState // Number + }, + // MemberDelimiter + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberKeyState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // KeyValueDelimiter + { + IterativeParsingArrayInitialState, // Left bracket(push MemberValue state) + IterativeParsingErrorState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push MemberValue state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberValueState, // String + IterativeParsingMemberValueState, // False + IterativeParsingMemberValueState, // True + IterativeParsingMemberValueState, // Null + IterativeParsingMemberValueState // Number + }, + }; // End of G + + return static_cast<IterativeParsingState>(G[state][token]); + } + + // Make an advance in the token stream and state based on the candidate destination state which was returned by Transit(). + // May return a new state on state pop. + template <unsigned parseFlags, typename InputStream, typename Handler> + RAPIDJSON_FORCEINLINE IterativeParsingState Transit(IterativeParsingState src, Token token, IterativeParsingState dst, InputStream& is, Handler& handler) { + (void)token; + + switch (dst) { + case IterativeParsingErrorState: + return dst; + + case IterativeParsingObjectInitialState: + case IterativeParsingArrayInitialState: + { + // Push the state(Element or MemeberValue) if we are nested in another array or value of member. + // In this way we can get the correct state on ObjectFinish or ArrayFinish by frame pop. + IterativeParsingState n = src; + if (src == IterativeParsingArrayInitialState || src == IterativeParsingElementDelimiterState) + n = IterativeParsingElementState; + else if (src == IterativeParsingKeyValueDelimiterState) + n = IterativeParsingMemberValueState; + // Push current state. + *stack_.template Push<SizeType>(1) = n; + // Initialize and push the member/element count. + *stack_.template Push<SizeType>(1) = 0; + // Call handler + bool hr = (dst == IterativeParsingObjectInitialState) ? handler.StartObject() : handler.StartArray(); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return dst; + } + } + + case IterativeParsingMemberKeyState: + ParseString<parseFlags>(is, handler, true); + if (HasParseError()) + return IterativeParsingErrorState; + else + return dst; + + case IterativeParsingKeyValueDelimiterState: + RAPIDJSON_ASSERT(token == ColonToken); + is.Take(); + return dst; + + case IterativeParsingMemberValueState: + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue<parseFlags>(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return dst; + + case IterativeParsingElementState: + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue<parseFlags>(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return dst; + + case IterativeParsingMemberDelimiterState: + case IterativeParsingElementDelimiterState: + is.Take(); + // Update member/element count. + *stack_.template Top<SizeType>() = *stack_.template Top<SizeType>() + 1; + return dst; + + case IterativeParsingObjectFinishState: + { + // Transit from delimiter is only allowed when trailing commas are enabled + if (!(parseFlags & kParseTrailingCommasFlag) && src == IterativeParsingMemberDelimiterState) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorObjectMissName, is.Tell()); + return IterativeParsingErrorState; + } + // Get member count. + SizeType c = *stack_.template Pop<SizeType>(1); + // If the object is not empty, count the last member. + if (src == IterativeParsingMemberValueState) + ++c; + // Restore the state. + IterativeParsingState n = static_cast<IterativeParsingState>(*stack_.template Pop<SizeType>(1)); + // Transit to Finish state if this is the topmost scope. + if (n == IterativeParsingStartState) + n = IterativeParsingFinishState; + // Call handler + bool hr = handler.EndObject(c); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return n; + } + } + + case IterativeParsingArrayFinishState: + { + // Transit from delimiter is only allowed when trailing commas are enabled + if (!(parseFlags & kParseTrailingCommasFlag) && src == IterativeParsingElementDelimiterState) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorValueInvalid, is.Tell()); + return IterativeParsingErrorState; + } + // Get element count. + SizeType c = *stack_.template Pop<SizeType>(1); + // If the array is not empty, count the last element. + if (src == IterativeParsingElementState) + ++c; + // Restore the state. + IterativeParsingState n = static_cast<IterativeParsingState>(*stack_.template Pop<SizeType>(1)); + // Transit to Finish state if this is the topmost scope. + if (n == IterativeParsingStartState) + n = IterativeParsingFinishState; + // Call handler + bool hr = handler.EndArray(c); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return n; + } + } + + default: + // This branch is for IterativeParsingValueState actually. + // Use `default:` rather than + // `case IterativeParsingValueState:` is for code coverage. + + // The IterativeParsingStartState is not enumerated in this switch-case. + // It is impossible for that case. And it can be caught by following assertion. + + // The IterativeParsingFinishState is not enumerated in this switch-case either. + // It is a "derivative" state which cannot triggered from Predict() directly. + // Therefore it cannot happen here. And it can be caught by following assertion. + RAPIDJSON_ASSERT(dst == IterativeParsingValueState); + + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue<parseFlags>(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return IterativeParsingFinishState; + } + } + + template <typename InputStream> + void HandleError(IterativeParsingState src, InputStream& is) { + if (HasParseError()) { + // Error flag has been set. + return; + } + + switch (src) { + case IterativeParsingStartState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentEmpty, is.Tell()); return; + case IterativeParsingFinishState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentRootNotSingular, is.Tell()); return; + case IterativeParsingObjectInitialState: + case IterativeParsingMemberDelimiterState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissName, is.Tell()); return; + case IterativeParsingMemberKeyState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissColon, is.Tell()); return; + case IterativeParsingMemberValueState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, is.Tell()); return; + case IterativeParsingKeyValueDelimiterState: + case IterativeParsingArrayInitialState: + case IterativeParsingElementDelimiterState: RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); return; + default: RAPIDJSON_ASSERT(src == IterativeParsingElementState); RAPIDJSON_PARSE_ERROR(kParseErrorArrayMissCommaOrSquareBracket, is.Tell()); return; + } + } + + RAPIDJSON_FORCEINLINE bool IsIterativeParsingDelimiterState(IterativeParsingState s) const { + return s >= IterativeParsingElementDelimiterState; + } + + RAPIDJSON_FORCEINLINE bool IsIterativeParsingCompleteState(IterativeParsingState s) const { + return s <= IterativeParsingErrorState; + } + + template <unsigned parseFlags, typename InputStream, typename Handler> + ParseResult IterativeParse(InputStream& is, Handler& handler) { + parseResult_.Clear(); + ClearStackOnExit scope(*this); + IterativeParsingState state = IterativeParsingStartState; + + SkipWhitespaceAndComments<parseFlags>(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + while (is.Peek() != '\0') { + Token t = Tokenize(is.Peek()); + IterativeParsingState n = Predict(state, t); + IterativeParsingState d = Transit<parseFlags>(state, t, n, is, handler); + + if (d == IterativeParsingErrorState) { + HandleError(state, is); + break; + } + + state = d; + + // Do not further consume streams if a root JSON has been parsed. + if ((parseFlags & kParseStopWhenDoneFlag) && state == IterativeParsingFinishState) + break; + + SkipWhitespaceAndComments<parseFlags>(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + + // Handle the end of file. + if (state != IterativeParsingFinishState) + HandleError(state, is); + + return parseResult_; + } + + static const size_t kDefaultStackCapacity = 256; //!< Default stack capacity in bytes for storing a single decoded string. + internal::Stack<StackAllocator> stack_; //!< A stack for storing decoded string temporarily during non-destructive parsing. + ParseResult parseResult_; + IterativeParsingState state_; +}; // class GenericReader + +//! Reader with UTF8 encoding and default allocator. +typedef GenericReader<UTF8<>, UTF8<> > Reader; + +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_READER_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/schema.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/schema.h new file mode 100644 index 00000000..11f71609 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/schema.h @@ -0,0 +1,2644 @@ +// Tencent is pleased to support the open source community by making RapidJSON available-> +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip-> All rights reserved-> +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License-> You may obtain a copy of the License at +// +// http://opensource->org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied-> See the License for the +// specific language governing permissions and limitations under the License-> + +#ifndef RAPIDJSON_SCHEMA_H_ +#define RAPIDJSON_SCHEMA_H_ + +#include "document.h" +#include "pointer.h" +#include "stringbuffer.h" +#include "error/en.h" +#include <cmath> // abs, floor + +#if !defined(RAPIDJSON_SCHEMA_USE_INTERNALREGEX) +#define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 1 +#else +#define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 0 +#endif + +#if !RAPIDJSON_SCHEMA_USE_INTERNALREGEX && defined(RAPIDJSON_SCHEMA_USE_STDREGEX) && (__cplusplus >=201103L || (defined(_MSC_VER) && _MSC_VER >= 1800)) +#define RAPIDJSON_SCHEMA_USE_STDREGEX 1 +#else +#define RAPIDJSON_SCHEMA_USE_STDREGEX 0 +#endif + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX +#include "internal/regex.h" +#elif RAPIDJSON_SCHEMA_USE_STDREGEX +#include <regex> +#endif + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX || RAPIDJSON_SCHEMA_USE_STDREGEX +#define RAPIDJSON_SCHEMA_HAS_REGEX 1 +#else +#define RAPIDJSON_SCHEMA_HAS_REGEX 0 +#endif + +#ifndef RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_SCHEMA_VERBOSE 0 +#endif + +#if RAPIDJSON_SCHEMA_VERBOSE +#include "stringbuffer.h" +#endif + +RAPIDJSON_DIAG_PUSH + +#if defined(__GNUC__) +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_OFF(weak-vtables) +RAPIDJSON_DIAG_OFF(exit-time-destructors) +RAPIDJSON_DIAG_OFF(c++98-compat-pedantic) +RAPIDJSON_DIAG_OFF(variadic-macros) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Verbose Utilities + +#if RAPIDJSON_SCHEMA_VERBOSE + +namespace internal { + +inline void PrintInvalidKeyword(const char* keyword) { + printf("Fail keyword: %s\n", keyword); +} + +inline void PrintInvalidKeyword(const wchar_t* keyword) { + wprintf(L"Fail keyword: %ls\n", keyword); +} + +inline void PrintInvalidDocument(const char* document) { + printf("Fail document: %s\n\n", document); +} + +inline void PrintInvalidDocument(const wchar_t* document) { + wprintf(L"Fail document: %ls\n\n", document); +} + +inline void PrintValidatorPointers(unsigned depth, const char* s, const char* d) { + printf("S: %*s%s\nD: %*s%s\n\n", depth * 4, " ", s, depth * 4, " ", d); +} + +inline void PrintValidatorPointers(unsigned depth, const wchar_t* s, const wchar_t* d) { + wprintf(L"S: %*ls%ls\nD: %*ls%ls\n\n", depth * 4, L" ", s, depth * 4, L" ", d); +} + +} // namespace internal + +#endif // RAPIDJSON_SCHEMA_VERBOSE + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_INVALID_KEYWORD_RETURN + +#if RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) internal::PrintInvalidKeyword(keyword) +#else +#define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) +#endif + +#define RAPIDJSON_INVALID_KEYWORD_RETURN(code)\ +RAPIDJSON_MULTILINEMACRO_BEGIN\ + context.invalidCode = code;\ + context.invalidKeyword = SchemaType::GetValidateErrorKeyword(code).GetString();\ + RAPIDJSON_INVALID_KEYWORD_VERBOSE(context.invalidKeyword);\ + return false;\ +RAPIDJSON_MULTILINEMACRO_END + +/////////////////////////////////////////////////////////////////////////////// +// ValidateFlag + +/*! \def RAPIDJSON_VALIDATE_DEFAULT_FLAGS + \ingroup RAPIDJSON_CONFIG + \brief User-defined kValidateDefaultFlags definition. + + User can define this as any \c ValidateFlag combinations. +*/ +#ifndef RAPIDJSON_VALIDATE_DEFAULT_FLAGS +#define RAPIDJSON_VALIDATE_DEFAULT_FLAGS kValidateNoFlags +#endif + +//! Combination of validate flags +/*! \see + */ +enum ValidateFlag { + kValidateNoFlags = 0, //!< No flags are set. + kValidateContinueOnErrorFlag = 1, //!< Don't stop after first validation error. + kValidateDefaultFlags = RAPIDJSON_VALIDATE_DEFAULT_FLAGS //!< Default validate flags. Can be customized by defining RAPIDJSON_VALIDATE_DEFAULT_FLAGS +}; + +/////////////////////////////////////////////////////////////////////////////// +// Forward declarations + +template <typename ValueType, typename Allocator> +class GenericSchemaDocument; + +namespace internal { + +template <typename SchemaDocumentType> +class Schema; + +/////////////////////////////////////////////////////////////////////////////// +// ISchemaValidator + +class ISchemaValidator { +public: + virtual ~ISchemaValidator() {} + virtual bool IsValid() const = 0; + virtual void SetValidateFlags(unsigned flags) = 0; + virtual unsigned GetValidateFlags() const = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// ISchemaStateFactory + +template <typename SchemaType> +class ISchemaStateFactory { +public: + virtual ~ISchemaStateFactory() {} + virtual ISchemaValidator* CreateSchemaValidator(const SchemaType&, const bool inheritContinueOnErrors) = 0; + virtual void DestroySchemaValidator(ISchemaValidator* validator) = 0; + virtual void* CreateHasher() = 0; + virtual uint64_t GetHashCode(void* hasher) = 0; + virtual void DestroryHasher(void* hasher) = 0; + virtual void* MallocState(size_t size) = 0; + virtual void FreeState(void* p) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// IValidationErrorHandler + +template <typename SchemaType> +class IValidationErrorHandler { +public: + typedef typename SchemaType::Ch Ch; + typedef typename SchemaType::SValue SValue; + + virtual ~IValidationErrorHandler() {} + + virtual void NotMultipleOf(int64_t actual, const SValue& expected) = 0; + virtual void NotMultipleOf(uint64_t actual, const SValue& expected) = 0; + virtual void NotMultipleOf(double actual, const SValue& expected) = 0; + virtual void AboveMaximum(int64_t actual, const SValue& expected, bool exclusive) = 0; + virtual void AboveMaximum(uint64_t actual, const SValue& expected, bool exclusive) = 0; + virtual void AboveMaximum(double actual, const SValue& expected, bool exclusive) = 0; + virtual void BelowMinimum(int64_t actual, const SValue& expected, bool exclusive) = 0; + virtual void BelowMinimum(uint64_t actual, const SValue& expected, bool exclusive) = 0; + virtual void BelowMinimum(double actual, const SValue& expected, bool exclusive) = 0; + + virtual void TooLong(const Ch* str, SizeType length, SizeType expected) = 0; + virtual void TooShort(const Ch* str, SizeType length, SizeType expected) = 0; + virtual void DoesNotMatch(const Ch* str, SizeType length) = 0; + + virtual void DisallowedItem(SizeType index) = 0; + virtual void TooFewItems(SizeType actualCount, SizeType expectedCount) = 0; + virtual void TooManyItems(SizeType actualCount, SizeType expectedCount) = 0; + virtual void DuplicateItems(SizeType index1, SizeType index2) = 0; + + virtual void TooManyProperties(SizeType actualCount, SizeType expectedCount) = 0; + virtual void TooFewProperties(SizeType actualCount, SizeType expectedCount) = 0; + virtual void StartMissingProperties() = 0; + virtual void AddMissingProperty(const SValue& name) = 0; + virtual bool EndMissingProperties() = 0; + virtual void PropertyViolations(ISchemaValidator** subvalidators, SizeType count) = 0; + virtual void DisallowedProperty(const Ch* name, SizeType length) = 0; + + virtual void StartDependencyErrors() = 0; + virtual void StartMissingDependentProperties() = 0; + virtual void AddMissingDependentProperty(const SValue& targetName) = 0; + virtual void EndMissingDependentProperties(const SValue& sourceName) = 0; + virtual void AddDependencySchemaError(const SValue& souceName, ISchemaValidator* subvalidator) = 0; + virtual bool EndDependencyErrors() = 0; + + virtual void DisallowedValue(const ValidateErrorCode code) = 0; + virtual void StartDisallowedType() = 0; + virtual void AddExpectedType(const typename SchemaType::ValueType& expectedType) = 0; + virtual void EndDisallowedType(const typename SchemaType::ValueType& actualType) = 0; + virtual void NotAllOf(ISchemaValidator** subvalidators, SizeType count) = 0; + virtual void NoneOf(ISchemaValidator** subvalidators, SizeType count) = 0; + virtual void NotOneOf(ISchemaValidator** subvalidators, SizeType count, bool matched) = 0; + virtual void Disallowed() = 0; +}; + + +/////////////////////////////////////////////////////////////////////////////// +// Hasher + +// For comparison of compound value +template<typename Encoding, typename Allocator> +class Hasher { +public: + typedef typename Encoding::Ch Ch; + + Hasher(Allocator* allocator = 0, size_t stackCapacity = kDefaultSize) : stack_(allocator, stackCapacity) {} + + bool Null() { return WriteType(kNullType); } + bool Bool(bool b) { return WriteType(b ? kTrueType : kFalseType); } + bool Int(int i) { Number n; n.u.i = i; n.d = static_cast<double>(i); return WriteNumber(n); } + bool Uint(unsigned u) { Number n; n.u.u = u; n.d = static_cast<double>(u); return WriteNumber(n); } + bool Int64(int64_t i) { Number n; n.u.i = i; n.d = static_cast<double>(i); return WriteNumber(n); } + bool Uint64(uint64_t u) { Number n; n.u.u = u; n.d = static_cast<double>(u); return WriteNumber(n); } + bool Double(double d) { + Number n; + if (d < 0) n.u.i = static_cast<int64_t>(d); + else n.u.u = static_cast<uint64_t>(d); + n.d = d; + return WriteNumber(n); + } + + bool RawNumber(const Ch* str, SizeType len, bool) { + WriteBuffer(kNumberType, str, len * sizeof(Ch)); + return true; + } + + bool String(const Ch* str, SizeType len, bool) { + WriteBuffer(kStringType, str, len * sizeof(Ch)); + return true; + } + + bool StartObject() { return true; } + bool Key(const Ch* str, SizeType len, bool copy) { return String(str, len, copy); } + bool EndObject(SizeType memberCount) { + uint64_t h = Hash(0, kObjectType); + uint64_t* kv = stack_.template Pop<uint64_t>(memberCount * 2); + for (SizeType i = 0; i < memberCount; i++) + h ^= Hash(kv[i * 2], kv[i * 2 + 1]); // Use xor to achieve member order insensitive + *stack_.template Push<uint64_t>() = h; + return true; + } + + bool StartArray() { return true; } + bool EndArray(SizeType elementCount) { + uint64_t h = Hash(0, kArrayType); + uint64_t* e = stack_.template Pop<uint64_t>(elementCount); + for (SizeType i = 0; i < elementCount; i++) + h = Hash(h, e[i]); // Use hash to achieve element order sensitive + *stack_.template Push<uint64_t>() = h; + return true; + } + + bool IsValid() const { return stack_.GetSize() == sizeof(uint64_t); } + + uint64_t GetHashCode() const { + RAPIDJSON_ASSERT(IsValid()); + return *stack_.template Top<uint64_t>(); + } + +private: + static const size_t kDefaultSize = 256; + struct Number { + union U { + uint64_t u; + int64_t i; + }u; + double d; + }; + + bool WriteType(Type type) { return WriteBuffer(type, 0, 0); } + + bool WriteNumber(const Number& n) { return WriteBuffer(kNumberType, &n, sizeof(n)); } + + bool WriteBuffer(Type type, const void* data, size_t len) { + // FNV-1a from http://isthe.com/chongo/tech/comp/fnv/ + uint64_t h = Hash(RAPIDJSON_UINT64_C2(0x84222325, 0xcbf29ce4), type); + const unsigned char* d = static_cast<const unsigned char*>(data); + for (size_t i = 0; i < len; i++) + h = Hash(h, d[i]); + *stack_.template Push<uint64_t>() = h; + return true; + } + + static uint64_t Hash(uint64_t h, uint64_t d) { + static const uint64_t kPrime = RAPIDJSON_UINT64_C2(0x00000100, 0x000001b3); + h ^= d; + h *= kPrime; + return h; + } + + Stack<Allocator> stack_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// SchemaValidationContext + +template <typename SchemaDocumentType> +struct SchemaValidationContext { + typedef Schema<SchemaDocumentType> SchemaType; + typedef ISchemaStateFactory<SchemaType> SchemaValidatorFactoryType; + typedef IValidationErrorHandler<SchemaType> ErrorHandlerType; + typedef typename SchemaType::ValueType ValueType; + typedef typename ValueType::Ch Ch; + + enum PatternValidatorType { + kPatternValidatorOnly, + kPatternValidatorWithProperty, + kPatternValidatorWithAdditionalProperty + }; + + SchemaValidationContext(SchemaValidatorFactoryType& f, ErrorHandlerType& eh, const SchemaType* s) : + factory(f), + error_handler(eh), + schema(s), + valueSchema(), + invalidKeyword(), + invalidCode(), + hasher(), + arrayElementHashCodes(), + validators(), + validatorCount(), + patternPropertiesValidators(), + patternPropertiesValidatorCount(), + patternPropertiesSchemas(), + patternPropertiesSchemaCount(), + valuePatternValidatorType(kPatternValidatorOnly), + propertyExist(), + inArray(false), + valueUniqueness(false), + arrayUniqueness(false) + { + } + + ~SchemaValidationContext() { + if (hasher) + factory.DestroryHasher(hasher); + if (validators) { + for (SizeType i = 0; i < validatorCount; i++) + factory.DestroySchemaValidator(validators[i]); + factory.FreeState(validators); + } + if (patternPropertiesValidators) { + for (SizeType i = 0; i < patternPropertiesValidatorCount; i++) + factory.DestroySchemaValidator(patternPropertiesValidators[i]); + factory.FreeState(patternPropertiesValidators); + } + if (patternPropertiesSchemas) + factory.FreeState(patternPropertiesSchemas); + if (propertyExist) + factory.FreeState(propertyExist); + } + + SchemaValidatorFactoryType& factory; + ErrorHandlerType& error_handler; + const SchemaType* schema; + const SchemaType* valueSchema; + const Ch* invalidKeyword; + ValidateErrorCode invalidCode; + void* hasher; // Only validator access + void* arrayElementHashCodes; // Only validator access this + ISchemaValidator** validators; + SizeType validatorCount; + ISchemaValidator** patternPropertiesValidators; + SizeType patternPropertiesValidatorCount; + const SchemaType** patternPropertiesSchemas; + SizeType patternPropertiesSchemaCount; + PatternValidatorType valuePatternValidatorType; + PatternValidatorType objectPatternValidatorType; + SizeType arrayElementIndex; + bool* propertyExist; + bool inArray; + bool valueUniqueness; + bool arrayUniqueness; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Schema + +template <typename SchemaDocumentType> +class Schema { +public: + typedef typename SchemaDocumentType::ValueType ValueType; + typedef typename SchemaDocumentType::AllocatorType AllocatorType; + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename EncodingType::Ch Ch; + typedef SchemaValidationContext<SchemaDocumentType> Context; + typedef Schema<SchemaDocumentType> SchemaType; + typedef GenericValue<EncodingType, AllocatorType> SValue; + typedef IValidationErrorHandler<Schema> ErrorHandler; + friend class GenericSchemaDocument<ValueType, AllocatorType>; + + Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator) : + allocator_(allocator), + uri_(schemaDocument->GetURI(), *allocator), + pointer_(p, allocator), + typeless_(schemaDocument->GetTypeless()), + enum_(), + enumCount_(), + not_(), + type_((1 << kTotalSchemaType) - 1), // typeless + validatorCount_(), + notValidatorIndex_(), + properties_(), + additionalPropertiesSchema_(), + patternProperties_(), + patternPropertyCount_(), + propertyCount_(), + minProperties_(), + maxProperties_(SizeType(~0)), + additionalProperties_(true), + hasDependencies_(), + hasRequired_(), + hasSchemaDependencies_(), + additionalItemsSchema_(), + itemsList_(), + itemsTuple_(), + itemsTupleCount_(), + minItems_(), + maxItems_(SizeType(~0)), + additionalItems_(true), + uniqueItems_(false), + pattern_(), + minLength_(0), + maxLength_(~SizeType(0)), + exclusiveMinimum_(false), + exclusiveMaximum_(false), + defaultValueLength_(0) + { + typedef typename ValueType::ConstValueIterator ConstValueIterator; + typedef typename ValueType::ConstMemberIterator ConstMemberIterator; + + if (!value.IsObject()) + return; + + if (const ValueType* v = GetMember(value, GetTypeString())) { + type_ = 0; + if (v->IsString()) + AddType(*v); + else if (v->IsArray()) + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) + AddType(*itr); + } + + if (const ValueType* v = GetMember(value, GetEnumString())) { + if (v->IsArray() && v->Size() > 0) { + enum_ = static_cast<uint64_t*>(allocator_->Malloc(sizeof(uint64_t) * v->Size())); + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) { + typedef Hasher<EncodingType, MemoryPoolAllocator<> > EnumHasherType; + char buffer[256u + 24]; + MemoryPoolAllocator<> hasherAllocator(buffer, sizeof(buffer)); + EnumHasherType h(&hasherAllocator, 256); + itr->Accept(h); + enum_[enumCount_++] = h.GetHashCode(); + } + } + } + + if (schemaDocument) { + AssignIfExist(allOf_, *schemaDocument, p, value, GetAllOfString(), document); + AssignIfExist(anyOf_, *schemaDocument, p, value, GetAnyOfString(), document); + AssignIfExist(oneOf_, *schemaDocument, p, value, GetOneOfString(), document); + } + + if (const ValueType* v = GetMember(value, GetNotString())) { + schemaDocument->CreateSchema(¬_, p.Append(GetNotString(), allocator_), *v, document); + notValidatorIndex_ = validatorCount_; + validatorCount_++; + } + + // Object + + const ValueType* properties = GetMember(value, GetPropertiesString()); + const ValueType* required = GetMember(value, GetRequiredString()); + const ValueType* dependencies = GetMember(value, GetDependenciesString()); + { + // Gather properties from properties/required/dependencies + SValue allProperties(kArrayType); + + if (properties && properties->IsObject()) + for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) + AddUniqueElement(allProperties, itr->name); + + if (required && required->IsArray()) + for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) + if (itr->IsString()) + AddUniqueElement(allProperties, *itr); + + if (dependencies && dependencies->IsObject()) + for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) { + AddUniqueElement(allProperties, itr->name); + if (itr->value.IsArray()) + for (ConstValueIterator i = itr->value.Begin(); i != itr->value.End(); ++i) + if (i->IsString()) + AddUniqueElement(allProperties, *i); + } + + if (allProperties.Size() > 0) { + propertyCount_ = allProperties.Size(); + properties_ = static_cast<Property*>(allocator_->Malloc(sizeof(Property) * propertyCount_)); + for (SizeType i = 0; i < propertyCount_; i++) { + new (&properties_[i]) Property(); + properties_[i].name = allProperties[i]; + properties_[i].schema = typeless_; + } + } + } + + if (properties && properties->IsObject()) { + PointerType q = p.Append(GetPropertiesString(), allocator_); + for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) { + SizeType index; + if (FindPropertyIndex(itr->name, &index)) + schemaDocument->CreateSchema(&properties_[index].schema, q.Append(itr->name, allocator_), itr->value, document); + } + } + + if (const ValueType* v = GetMember(value, GetPatternPropertiesString())) { + PointerType q = p.Append(GetPatternPropertiesString(), allocator_); + patternProperties_ = static_cast<PatternProperty*>(allocator_->Malloc(sizeof(PatternProperty) * v->MemberCount())); + patternPropertyCount_ = 0; + + for (ConstMemberIterator itr = v->MemberBegin(); itr != v->MemberEnd(); ++itr) { + new (&patternProperties_[patternPropertyCount_]) PatternProperty(); + patternProperties_[patternPropertyCount_].pattern = CreatePattern(itr->name); + schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, q.Append(itr->name, allocator_), itr->value, document); + patternPropertyCount_++; + } + } + + if (required && required->IsArray()) + for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) + if (itr->IsString()) { + SizeType index; + if (FindPropertyIndex(*itr, &index)) { + properties_[index].required = true; + hasRequired_ = true; + } + } + + if (dependencies && dependencies->IsObject()) { + PointerType q = p.Append(GetDependenciesString(), allocator_); + hasDependencies_ = true; + for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) { + SizeType sourceIndex; + if (FindPropertyIndex(itr->name, &sourceIndex)) { + if (itr->value.IsArray()) { + properties_[sourceIndex].dependencies = static_cast<bool*>(allocator_->Malloc(sizeof(bool) * propertyCount_)); + std::memset(properties_[sourceIndex].dependencies, 0, sizeof(bool)* propertyCount_); + for (ConstValueIterator targetItr = itr->value.Begin(); targetItr != itr->value.End(); ++targetItr) { + SizeType targetIndex; + if (FindPropertyIndex(*targetItr, &targetIndex)) + properties_[sourceIndex].dependencies[targetIndex] = true; + } + } + else if (itr->value.IsObject()) { + hasSchemaDependencies_ = true; + schemaDocument->CreateSchema(&properties_[sourceIndex].dependenciesSchema, q.Append(itr->name, allocator_), itr->value, document); + properties_[sourceIndex].dependenciesValidatorIndex = validatorCount_; + validatorCount_++; + } + } + } + } + + if (const ValueType* v = GetMember(value, GetAdditionalPropertiesString())) { + if (v->IsBool()) + additionalProperties_ = v->GetBool(); + else if (v->IsObject()) + schemaDocument->CreateSchema(&additionalPropertiesSchema_, p.Append(GetAdditionalPropertiesString(), allocator_), *v, document); + } + + AssignIfExist(minProperties_, value, GetMinPropertiesString()); + AssignIfExist(maxProperties_, value, GetMaxPropertiesString()); + + // Array + if (const ValueType* v = GetMember(value, GetItemsString())) { + PointerType q = p.Append(GetItemsString(), allocator_); + if (v->IsObject()) // List validation + schemaDocument->CreateSchema(&itemsList_, q, *v, document); + else if (v->IsArray()) { // Tuple validation + itemsTuple_ = static_cast<const Schema**>(allocator_->Malloc(sizeof(const Schema*) * v->Size())); + SizeType index = 0; + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr, index++) + schemaDocument->CreateSchema(&itemsTuple_[itemsTupleCount_++], q.Append(index, allocator_), *itr, document); + } + } + + AssignIfExist(minItems_, value, GetMinItemsString()); + AssignIfExist(maxItems_, value, GetMaxItemsString()); + + if (const ValueType* v = GetMember(value, GetAdditionalItemsString())) { + if (v->IsBool()) + additionalItems_ = v->GetBool(); + else if (v->IsObject()) + schemaDocument->CreateSchema(&additionalItemsSchema_, p.Append(GetAdditionalItemsString(), allocator_), *v, document); + } + + AssignIfExist(uniqueItems_, value, GetUniqueItemsString()); + + // String + AssignIfExist(minLength_, value, GetMinLengthString()); + AssignIfExist(maxLength_, value, GetMaxLengthString()); + + if (const ValueType* v = GetMember(value, GetPatternString())) + pattern_ = CreatePattern(*v); + + // Number + if (const ValueType* v = GetMember(value, GetMinimumString())) + if (v->IsNumber()) + minimum_.CopyFrom(*v, *allocator_); + + if (const ValueType* v = GetMember(value, GetMaximumString())) + if (v->IsNumber()) + maximum_.CopyFrom(*v, *allocator_); + + AssignIfExist(exclusiveMinimum_, value, GetExclusiveMinimumString()); + AssignIfExist(exclusiveMaximum_, value, GetExclusiveMaximumString()); + + if (const ValueType* v = GetMember(value, GetMultipleOfString())) + if (v->IsNumber() && v->GetDouble() > 0.0) + multipleOf_.CopyFrom(*v, *allocator_); + + // Default + if (const ValueType* v = GetMember(value, GetDefaultValueString())) + if (v->IsString()) + defaultValueLength_ = v->GetStringLength(); + + } + + ~Schema() { + AllocatorType::Free(enum_); + if (properties_) { + for (SizeType i = 0; i < propertyCount_; i++) + properties_[i].~Property(); + AllocatorType::Free(properties_); + } + if (patternProperties_) { + for (SizeType i = 0; i < patternPropertyCount_; i++) + patternProperties_[i].~PatternProperty(); + AllocatorType::Free(patternProperties_); + } + AllocatorType::Free(itemsTuple_); +#if RAPIDJSON_SCHEMA_HAS_REGEX + if (pattern_) { + pattern_->~RegexType(); + AllocatorType::Free(pattern_); + } +#endif + } + + const SValue& GetURI() const { + return uri_; + } + + const PointerType& GetPointer() const { + return pointer_; + } + + bool BeginValue(Context& context) const { + if (context.inArray) { + if (uniqueItems_) + context.valueUniqueness = true; + + if (itemsList_) + context.valueSchema = itemsList_; + else if (itemsTuple_) { + if (context.arrayElementIndex < itemsTupleCount_) + context.valueSchema = itemsTuple_[context.arrayElementIndex]; + else if (additionalItemsSchema_) + context.valueSchema = additionalItemsSchema_; + else if (additionalItems_) + context.valueSchema = typeless_; + else { + context.error_handler.DisallowedItem(context.arrayElementIndex); + // Must set valueSchema for when kValidateContinueOnErrorFlag is set, else reports spurious type error + context.valueSchema = typeless_; + // Must bump arrayElementIndex for when kValidateContinueOnErrorFlag is set + context.arrayElementIndex++; + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorAdditionalItems); + } + } + else + context.valueSchema = typeless_; + + context.arrayElementIndex++; + } + return true; + } + + RAPIDJSON_FORCEINLINE bool EndValue(Context& context) const { + if (context.patternPropertiesValidatorCount > 0) { + bool otherValid = false; + SizeType count = context.patternPropertiesValidatorCount; + if (context.objectPatternValidatorType != Context::kPatternValidatorOnly) + otherValid = context.patternPropertiesValidators[--count]->IsValid(); + + bool patternValid = true; + for (SizeType i = 0; i < count; i++) + if (!context.patternPropertiesValidators[i]->IsValid()) { + patternValid = false; + break; + } + + if (context.objectPatternValidatorType == Context::kPatternValidatorOnly) { + if (!patternValid) { + context.error_handler.PropertyViolations(context.patternPropertiesValidators, count); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorPatternProperties); + } + } + else if (context.objectPatternValidatorType == Context::kPatternValidatorWithProperty) { + if (!patternValid || !otherValid) { + context.error_handler.PropertyViolations(context.patternPropertiesValidators, count + 1); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorPatternProperties); + } + } + else if (!patternValid && !otherValid) { // kPatternValidatorWithAdditionalProperty) + context.error_handler.PropertyViolations(context.patternPropertiesValidators, count + 1); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorPatternProperties); + } + } + + // For enums only check if we have a hasher + if (enum_ && context.hasher) { + const uint64_t h = context.factory.GetHashCode(context.hasher); + for (SizeType i = 0; i < enumCount_; i++) + if (enum_[i] == h) + goto foundEnum; + context.error_handler.DisallowedValue(kValidateErrorEnum); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorEnum); + foundEnum:; + } + + if (allOf_.schemas) + for (SizeType i = allOf_.begin; i < allOf_.begin + allOf_.count; i++) + if (!context.validators[i]->IsValid()) { + context.error_handler.NotAllOf(&context.validators[allOf_.begin], allOf_.count); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorAllOf); + } + + if (anyOf_.schemas) { + for (SizeType i = anyOf_.begin; i < anyOf_.begin + anyOf_.count; i++) + if (context.validators[i]->IsValid()) + goto foundAny; + context.error_handler.NoneOf(&context.validators[anyOf_.begin], anyOf_.count); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorAnyOf); + foundAny:; + } + + if (oneOf_.schemas) { + bool oneValid = false; + for (SizeType i = oneOf_.begin; i < oneOf_.begin + oneOf_.count; i++) + if (context.validators[i]->IsValid()) { + if (oneValid) { + context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count, true); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorOneOfMatch); + } else + oneValid = true; + } + if (!oneValid) { + context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count, false); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorOneOf); + } + } + + if (not_ && context.validators[notValidatorIndex_]->IsValid()) { + context.error_handler.Disallowed(); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorNot); + } + + return true; + } + + bool Null(Context& context) const { + if (!(type_ & (1 << kNullSchemaType))) { + DisallowedType(context, GetNullString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); + } + return CreateParallelValidator(context); + } + + bool Bool(Context& context, bool) const { + if (!(type_ & (1 << kBooleanSchemaType))) { + DisallowedType(context, GetBooleanString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); + } + return CreateParallelValidator(context); + } + + bool Int(Context& context, int i) const { + if (!CheckInt(context, i)) + return false; + return CreateParallelValidator(context); + } + + bool Uint(Context& context, unsigned u) const { + if (!CheckUint(context, u)) + return false; + return CreateParallelValidator(context); + } + + bool Int64(Context& context, int64_t i) const { + if (!CheckInt(context, i)) + return false; + return CreateParallelValidator(context); + } + + bool Uint64(Context& context, uint64_t u) const { + if (!CheckUint(context, u)) + return false; + return CreateParallelValidator(context); + } + + bool Double(Context& context, double d) const { + if (!(type_ & (1 << kNumberSchemaType))) { + DisallowedType(context, GetNumberString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); + } + + if (!minimum_.IsNull() && !CheckDoubleMinimum(context, d)) + return false; + + if (!maximum_.IsNull() && !CheckDoubleMaximum(context, d)) + return false; + + if (!multipleOf_.IsNull() && !CheckDoubleMultipleOf(context, d)) + return false; + + return CreateParallelValidator(context); + } + + bool String(Context& context, const Ch* str, SizeType length, bool) const { + if (!(type_ & (1 << kStringSchemaType))) { + DisallowedType(context, GetStringString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); + } + + if (minLength_ != 0 || maxLength_ != SizeType(~0)) { + SizeType count; + if (internal::CountStringCodePoint<EncodingType>(str, length, &count)) { + if (count < minLength_) { + context.error_handler.TooShort(str, length, minLength_); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMinLength); + } + if (count > maxLength_) { + context.error_handler.TooLong(str, length, maxLength_); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMaxLength); + } + } + } + + if (pattern_ && !IsPatternMatch(pattern_, str, length)) { + context.error_handler.DoesNotMatch(str, length); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorPattern); + } + + return CreateParallelValidator(context); + } + + bool StartObject(Context& context) const { + if (!(type_ & (1 << kObjectSchemaType))) { + DisallowedType(context, GetObjectString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); + } + + if (hasDependencies_ || hasRequired_) { + context.propertyExist = static_cast<bool*>(context.factory.MallocState(sizeof(bool) * propertyCount_)); + std::memset(context.propertyExist, 0, sizeof(bool) * propertyCount_); + } + + if (patternProperties_) { // pre-allocate schema array + SizeType count = patternPropertyCount_ + 1; // extra for valuePatternValidatorType + context.patternPropertiesSchemas = static_cast<const SchemaType**>(context.factory.MallocState(sizeof(const SchemaType*) * count)); + context.patternPropertiesSchemaCount = 0; + std::memset(context.patternPropertiesSchemas, 0, sizeof(SchemaType*) * count); + } + + return CreateParallelValidator(context); + } + + bool Key(Context& context, const Ch* str, SizeType len, bool) const { + if (patternProperties_) { + context.patternPropertiesSchemaCount = 0; + for (SizeType i = 0; i < patternPropertyCount_; i++) + if (patternProperties_[i].pattern && IsPatternMatch(patternProperties_[i].pattern, str, len)) { + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = patternProperties_[i].schema; + context.valueSchema = typeless_; + } + } + + SizeType index = 0; + if (FindPropertyIndex(ValueType(str, len).Move(), &index)) { + if (context.patternPropertiesSchemaCount > 0) { + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = properties_[index].schema; + context.valueSchema = typeless_; + context.valuePatternValidatorType = Context::kPatternValidatorWithProperty; + } + else + context.valueSchema = properties_[index].schema; + + if (context.propertyExist) + context.propertyExist[index] = true; + + return true; + } + + if (additionalPropertiesSchema_) { + if (additionalPropertiesSchema_ && context.patternPropertiesSchemaCount > 0) { + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = additionalPropertiesSchema_; + context.valueSchema = typeless_; + context.valuePatternValidatorType = Context::kPatternValidatorWithAdditionalProperty; + } + else + context.valueSchema = additionalPropertiesSchema_; + return true; + } + else if (additionalProperties_) { + context.valueSchema = typeless_; + return true; + } + + if (context.patternPropertiesSchemaCount == 0) { // patternProperties are not additional properties + // Must set valueSchema for when kValidateContinueOnErrorFlag is set, else reports spurious type error + context.valueSchema = typeless_; + context.error_handler.DisallowedProperty(str, len); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorAdditionalProperties); + } + + return true; + } + + bool EndObject(Context& context, SizeType memberCount) const { + if (hasRequired_) { + context.error_handler.StartMissingProperties(); + for (SizeType index = 0; index < propertyCount_; index++) + if (properties_[index].required && !context.propertyExist[index]) + if (properties_[index].schema->defaultValueLength_ == 0 ) + context.error_handler.AddMissingProperty(properties_[index].name); + if (context.error_handler.EndMissingProperties()) + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorRequired); + } + + if (memberCount < minProperties_) { + context.error_handler.TooFewProperties(memberCount, minProperties_); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMinProperties); + } + + if (memberCount > maxProperties_) { + context.error_handler.TooManyProperties(memberCount, maxProperties_); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMaxProperties); + } + + if (hasDependencies_) { + context.error_handler.StartDependencyErrors(); + for (SizeType sourceIndex = 0; sourceIndex < propertyCount_; sourceIndex++) { + const Property& source = properties_[sourceIndex]; + if (context.propertyExist[sourceIndex]) { + if (source.dependencies) { + context.error_handler.StartMissingDependentProperties(); + for (SizeType targetIndex = 0; targetIndex < propertyCount_; targetIndex++) + if (source.dependencies[targetIndex] && !context.propertyExist[targetIndex]) + context.error_handler.AddMissingDependentProperty(properties_[targetIndex].name); + context.error_handler.EndMissingDependentProperties(source.name); + } + else if (source.dependenciesSchema) { + ISchemaValidator* dependenciesValidator = context.validators[source.dependenciesValidatorIndex]; + if (!dependenciesValidator->IsValid()) + context.error_handler.AddDependencySchemaError(source.name, dependenciesValidator); + } + } + } + if (context.error_handler.EndDependencyErrors()) + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorDependencies); + } + + return true; + } + + bool StartArray(Context& context) const { + context.arrayElementIndex = 0; + context.inArray = true; // Ensure we note that we are in an array + + if (!(type_ & (1 << kArraySchemaType))) { + DisallowedType(context, GetArrayString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); + } + + return CreateParallelValidator(context); + } + + bool EndArray(Context& context, SizeType elementCount) const { + context.inArray = false; + + if (elementCount < minItems_) { + context.error_handler.TooFewItems(elementCount, minItems_); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMinItems); + } + + if (elementCount > maxItems_) { + context.error_handler.TooManyItems(elementCount, maxItems_); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMaxItems); + } + + return true; + } + + static const ValueType& GetValidateErrorKeyword(ValidateErrorCode validateErrorCode) { + switch (validateErrorCode) { + case kValidateErrorMultipleOf: return GetMultipleOfString(); + case kValidateErrorMaximum: return GetMaximumString(); + case kValidateErrorExclusiveMaximum: return GetMaximumString(); // Same + case kValidateErrorMinimum: return GetMinimumString(); + case kValidateErrorExclusiveMinimum: return GetMinimumString(); // Same + + case kValidateErrorMaxLength: return GetMaxLengthString(); + case kValidateErrorMinLength: return GetMinLengthString(); + case kValidateErrorPattern: return GetPatternString(); + + case kValidateErrorMaxItems: return GetMaxItemsString(); + case kValidateErrorMinItems: return GetMinItemsString(); + case kValidateErrorUniqueItems: return GetUniqueItemsString(); + case kValidateErrorAdditionalItems: return GetAdditionalItemsString(); + + case kValidateErrorMaxProperties: return GetMaxPropertiesString(); + case kValidateErrorMinProperties: return GetMinPropertiesString(); + case kValidateErrorRequired: return GetRequiredString(); + case kValidateErrorAdditionalProperties: return GetAdditionalPropertiesString(); + case kValidateErrorPatternProperties: return GetPatternPropertiesString(); + case kValidateErrorDependencies: return GetDependenciesString(); + + case kValidateErrorEnum: return GetEnumString(); + case kValidateErrorType: return GetTypeString(); + + case kValidateErrorOneOf: return GetOneOfString(); + case kValidateErrorOneOfMatch: return GetOneOfString(); // Same + case kValidateErrorAllOf: return GetAllOfString(); + case kValidateErrorAnyOf: return GetAnyOfString(); + case kValidateErrorNot: return GetNotString(); + + default: return GetNullString(); + } + } + + + // Generate functions for string literal according to Ch +#define RAPIDJSON_STRING_(name, ...) \ + static const ValueType& Get##name##String() {\ + static const Ch s[] = { __VA_ARGS__, '\0' };\ + static const ValueType v(s, static_cast<SizeType>(sizeof(s) / sizeof(Ch) - 1));\ + return v;\ + } + + RAPIDJSON_STRING_(Null, 'n', 'u', 'l', 'l') + RAPIDJSON_STRING_(Boolean, 'b', 'o', 'o', 'l', 'e', 'a', 'n') + RAPIDJSON_STRING_(Object, 'o', 'b', 'j', 'e', 'c', 't') + RAPIDJSON_STRING_(Array, 'a', 'r', 'r', 'a', 'y') + RAPIDJSON_STRING_(String, 's', 't', 'r', 'i', 'n', 'g') + RAPIDJSON_STRING_(Number, 'n', 'u', 'm', 'b', 'e', 'r') + RAPIDJSON_STRING_(Integer, 'i', 'n', 't', 'e', 'g', 'e', 'r') + RAPIDJSON_STRING_(Type, 't', 'y', 'p', 'e') + RAPIDJSON_STRING_(Enum, 'e', 'n', 'u', 'm') + RAPIDJSON_STRING_(AllOf, 'a', 'l', 'l', 'O', 'f') + RAPIDJSON_STRING_(AnyOf, 'a', 'n', 'y', 'O', 'f') + RAPIDJSON_STRING_(OneOf, 'o', 'n', 'e', 'O', 'f') + RAPIDJSON_STRING_(Not, 'n', 'o', 't') + RAPIDJSON_STRING_(Properties, 'p', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(Required, 'r', 'e', 'q', 'u', 'i', 'r', 'e', 'd') + RAPIDJSON_STRING_(Dependencies, 'd', 'e', 'p', 'e', 'n', 'd', 'e', 'n', 'c', 'i', 'e', 's') + RAPIDJSON_STRING_(PatternProperties, 'p', 'a', 't', 't', 'e', 'r', 'n', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(AdditionalProperties, 'a', 'd', 'd', 'i', 't', 'i', 'o', 'n', 'a', 'l', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(MinProperties, 'm', 'i', 'n', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(MaxProperties, 'm', 'a', 'x', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(Items, 'i', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MinItems, 'm', 'i', 'n', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MaxItems, 'm', 'a', 'x', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(AdditionalItems, 'a', 'd', 'd', 'i', 't', 'i', 'o', 'n', 'a', 'l', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(UniqueItems, 'u', 'n', 'i', 'q', 'u', 'e', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MinLength, 'm', 'i', 'n', 'L', 'e', 'n', 'g', 't', 'h') + RAPIDJSON_STRING_(MaxLength, 'm', 'a', 'x', 'L', 'e', 'n', 'g', 't', 'h') + RAPIDJSON_STRING_(Pattern, 'p', 'a', 't', 't', 'e', 'r', 'n') + RAPIDJSON_STRING_(Minimum, 'm', 'i', 'n', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(Maximum, 'm', 'a', 'x', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(ExclusiveMinimum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'i', 'n', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(ExclusiveMaximum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'a', 'x', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(MultipleOf, 'm', 'u', 'l', 't', 'i', 'p', 'l', 'e', 'O', 'f') + RAPIDJSON_STRING_(DefaultValue, 'd', 'e', 'f', 'a', 'u', 'l', 't') + +#undef RAPIDJSON_STRING_ + +private: + enum SchemaValueType { + kNullSchemaType, + kBooleanSchemaType, + kObjectSchemaType, + kArraySchemaType, + kStringSchemaType, + kNumberSchemaType, + kIntegerSchemaType, + kTotalSchemaType + }; + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX + typedef internal::GenericRegex<EncodingType, AllocatorType> RegexType; +#elif RAPIDJSON_SCHEMA_USE_STDREGEX + typedef std::basic_regex<Ch> RegexType; +#else + typedef char RegexType; +#endif + + struct SchemaArray { + SchemaArray() : schemas(), count() {} + ~SchemaArray() { AllocatorType::Free(schemas); } + const SchemaType** schemas; + SizeType begin; // begin index of context.validators + SizeType count; + }; + + template <typename V1, typename V2> + void AddUniqueElement(V1& a, const V2& v) { + for (typename V1::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr) + if (*itr == v) + return; + V1 c(v, *allocator_); + a.PushBack(c, *allocator_); + } + + static const ValueType* GetMember(const ValueType& value, const ValueType& name) { + typename ValueType::ConstMemberIterator itr = value.FindMember(name); + return itr != value.MemberEnd() ? &(itr->value) : 0; + } + + static void AssignIfExist(bool& out, const ValueType& value, const ValueType& name) { + if (const ValueType* v = GetMember(value, name)) + if (v->IsBool()) + out = v->GetBool(); + } + + static void AssignIfExist(SizeType& out, const ValueType& value, const ValueType& name) { + if (const ValueType* v = GetMember(value, name)) + if (v->IsUint64() && v->GetUint64() <= SizeType(~0)) + out = static_cast<SizeType>(v->GetUint64()); + } + + void AssignIfExist(SchemaArray& out, SchemaDocumentType& schemaDocument, const PointerType& p, const ValueType& value, const ValueType& name, const ValueType& document) { + if (const ValueType* v = GetMember(value, name)) { + if (v->IsArray() && v->Size() > 0) { + PointerType q = p.Append(name, allocator_); + out.count = v->Size(); + out.schemas = static_cast<const Schema**>(allocator_->Malloc(out.count * sizeof(const Schema*))); + memset(out.schemas, 0, sizeof(Schema*)* out.count); + for (SizeType i = 0; i < out.count; i++) + schemaDocument.CreateSchema(&out.schemas[i], q.Append(i, allocator_), (*v)[i], document); + out.begin = validatorCount_; + validatorCount_ += out.count; + } + } + } + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX + template <typename ValueType> + RegexType* CreatePattern(const ValueType& value) { + if (value.IsString()) { + RegexType* r = new (allocator_->Malloc(sizeof(RegexType))) RegexType(value.GetString(), allocator_); + if (!r->IsValid()) { + r->~RegexType(); + AllocatorType::Free(r); + r = 0; + } + return r; + } + return 0; + } + + static bool IsPatternMatch(const RegexType* pattern, const Ch *str, SizeType) { + GenericRegexSearch<RegexType> rs(*pattern); + return rs.Search(str); + } +#elif RAPIDJSON_SCHEMA_USE_STDREGEX + template <typename ValueType> + RegexType* CreatePattern(const ValueType& value) { + if (value.IsString()) { + RegexType *r = static_cast<RegexType*>(allocator_->Malloc(sizeof(RegexType))); + try { + return new (r) RegexType(value.GetString(), std::size_t(value.GetStringLength()), std::regex_constants::ECMAScript); + } + catch (const std::regex_error&) { + AllocatorType::Free(r); + } + } + return 0; + } + + static bool IsPatternMatch(const RegexType* pattern, const Ch *str, SizeType length) { + std::match_results<const Ch*> r; + return std::regex_search(str, str + length, r, *pattern); + } +#else + template <typename ValueType> + RegexType* CreatePattern(const ValueType&) { return 0; } + + static bool IsPatternMatch(const RegexType*, const Ch *, SizeType) { return true; } +#endif // RAPIDJSON_SCHEMA_USE_STDREGEX + + void AddType(const ValueType& type) { + if (type == GetNullString() ) type_ |= 1 << kNullSchemaType; + else if (type == GetBooleanString()) type_ |= 1 << kBooleanSchemaType; + else if (type == GetObjectString() ) type_ |= 1 << kObjectSchemaType; + else if (type == GetArrayString() ) type_ |= 1 << kArraySchemaType; + else if (type == GetStringString() ) type_ |= 1 << kStringSchemaType; + else if (type == GetIntegerString()) type_ |= 1 << kIntegerSchemaType; + else if (type == GetNumberString() ) type_ |= (1 << kNumberSchemaType) | (1 << kIntegerSchemaType); + } + + bool CreateParallelValidator(Context& context) const { + if (enum_ || context.arrayUniqueness) + context.hasher = context.factory.CreateHasher(); + + if (validatorCount_) { + RAPIDJSON_ASSERT(context.validators == 0); + context.validators = static_cast<ISchemaValidator**>(context.factory.MallocState(sizeof(ISchemaValidator*) * validatorCount_)); + context.validatorCount = validatorCount_; + + // Always return after first failure for these sub-validators + if (allOf_.schemas) + CreateSchemaValidators(context, allOf_, false); + + if (anyOf_.schemas) + CreateSchemaValidators(context, anyOf_, false); + + if (oneOf_.schemas) + CreateSchemaValidators(context, oneOf_, false); + + if (not_) + context.validators[notValidatorIndex_] = context.factory.CreateSchemaValidator(*not_, false); + + if (hasSchemaDependencies_) { + for (SizeType i = 0; i < propertyCount_; i++) + if (properties_[i].dependenciesSchema) + context.validators[properties_[i].dependenciesValidatorIndex] = context.factory.CreateSchemaValidator(*properties_[i].dependenciesSchema, false); + } + } + + return true; + } + + void CreateSchemaValidators(Context& context, const SchemaArray& schemas, const bool inheritContinueOnErrors) const { + for (SizeType i = 0; i < schemas.count; i++) + context.validators[schemas.begin + i] = context.factory.CreateSchemaValidator(*schemas.schemas[i], inheritContinueOnErrors); + } + + // O(n) + bool FindPropertyIndex(const ValueType& name, SizeType* outIndex) const { + SizeType len = name.GetStringLength(); + const Ch* str = name.GetString(); + for (SizeType index = 0; index < propertyCount_; index++) + if (properties_[index].name.GetStringLength() == len && + (std::memcmp(properties_[index].name.GetString(), str, sizeof(Ch) * len) == 0)) + { + *outIndex = index; + return true; + } + return false; + } + + bool CheckInt(Context& context, int64_t i) const { + if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) { + DisallowedType(context, GetIntegerString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); + } + + if (!minimum_.IsNull()) { + if (minimum_.IsInt64()) { + if (exclusiveMinimum_ ? i <= minimum_.GetInt64() : i < minimum_.GetInt64()) { + context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(exclusiveMinimum_ ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum); + } + } + else if (minimum_.IsUint64()) { + context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(exclusiveMinimum_ ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum); // i <= max(int64_t) < minimum.GetUint64() + } + else if (!CheckDoubleMinimum(context, static_cast<double>(i))) + return false; + } + + if (!maximum_.IsNull()) { + if (maximum_.IsInt64()) { + if (exclusiveMaximum_ ? i >= maximum_.GetInt64() : i > maximum_.GetInt64()) { + context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(exclusiveMaximum_ ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum); + } + } + else if (maximum_.IsUint64()) { } + /* do nothing */ // i <= max(int64_t) < maximum_.GetUint64() + else if (!CheckDoubleMaximum(context, static_cast<double>(i))) + return false; + } + + if (!multipleOf_.IsNull()) { + if (multipleOf_.IsUint64()) { + if (static_cast<uint64_t>(i >= 0 ? i : -i) % multipleOf_.GetUint64() != 0) { + context.error_handler.NotMultipleOf(i, multipleOf_); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMultipleOf); + } + } + else if (!CheckDoubleMultipleOf(context, static_cast<double>(i))) + return false; + } + + return true; + } + + bool CheckUint(Context& context, uint64_t i) const { + if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) { + DisallowedType(context, GetIntegerString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorType); + } + + if (!minimum_.IsNull()) { + if (minimum_.IsUint64()) { + if (exclusiveMinimum_ ? i <= minimum_.GetUint64() : i < minimum_.GetUint64()) { + context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(exclusiveMinimum_ ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum); + } + } + else if (minimum_.IsInt64()) + /* do nothing */; // i >= 0 > minimum.Getint64() + else if (!CheckDoubleMinimum(context, static_cast<double>(i))) + return false; + } + + if (!maximum_.IsNull()) { + if (maximum_.IsUint64()) { + if (exclusiveMaximum_ ? i >= maximum_.GetUint64() : i > maximum_.GetUint64()) { + context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(exclusiveMaximum_ ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum); + } + } + else if (maximum_.IsInt64()) { + context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(exclusiveMaximum_ ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum); // i >= 0 > maximum_ + } + else if (!CheckDoubleMaximum(context, static_cast<double>(i))) + return false; + } + + if (!multipleOf_.IsNull()) { + if (multipleOf_.IsUint64()) { + if (i % multipleOf_.GetUint64() != 0) { + context.error_handler.NotMultipleOf(i, multipleOf_); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMultipleOf); + } + } + else if (!CheckDoubleMultipleOf(context, static_cast<double>(i))) + return false; + } + + return true; + } + + bool CheckDoubleMinimum(Context& context, double d) const { + if (exclusiveMinimum_ ? d <= minimum_.GetDouble() : d < minimum_.GetDouble()) { + context.error_handler.BelowMinimum(d, minimum_, exclusiveMinimum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(exclusiveMinimum_ ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum); + } + return true; + } + + bool CheckDoubleMaximum(Context& context, double d) const { + if (exclusiveMaximum_ ? d >= maximum_.GetDouble() : d > maximum_.GetDouble()) { + context.error_handler.AboveMaximum(d, maximum_, exclusiveMaximum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(exclusiveMaximum_ ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum); + } + return true; + } + + bool CheckDoubleMultipleOf(Context& context, double d) const { + double a = std::abs(d), b = std::abs(multipleOf_.GetDouble()); + double q = std::floor(a / b); + double r = a - q * b; + if (r > 0.0) { + context.error_handler.NotMultipleOf(d, multipleOf_); + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorMultipleOf); + } + return true; + } + + void DisallowedType(Context& context, const ValueType& actualType) const { + ErrorHandler& eh = context.error_handler; + eh.StartDisallowedType(); + + if (type_ & (1 << kNullSchemaType)) eh.AddExpectedType(GetNullString()); + if (type_ & (1 << kBooleanSchemaType)) eh.AddExpectedType(GetBooleanString()); + if (type_ & (1 << kObjectSchemaType)) eh.AddExpectedType(GetObjectString()); + if (type_ & (1 << kArraySchemaType)) eh.AddExpectedType(GetArrayString()); + if (type_ & (1 << kStringSchemaType)) eh.AddExpectedType(GetStringString()); + + if (type_ & (1 << kNumberSchemaType)) eh.AddExpectedType(GetNumberString()); + else if (type_ & (1 << kIntegerSchemaType)) eh.AddExpectedType(GetIntegerString()); + + eh.EndDisallowedType(actualType); + } + + struct Property { + Property() : schema(), dependenciesSchema(), dependenciesValidatorIndex(), dependencies(), required(false) {} + ~Property() { AllocatorType::Free(dependencies); } + SValue name; + const SchemaType* schema; + const SchemaType* dependenciesSchema; + SizeType dependenciesValidatorIndex; + bool* dependencies; + bool required; + }; + + struct PatternProperty { + PatternProperty() : schema(), pattern() {} + ~PatternProperty() { + if (pattern) { + pattern->~RegexType(); + AllocatorType::Free(pattern); + } + } + const SchemaType* schema; + RegexType* pattern; + }; + + AllocatorType* allocator_; + SValue uri_; + PointerType pointer_; + const SchemaType* typeless_; + uint64_t* enum_; + SizeType enumCount_; + SchemaArray allOf_; + SchemaArray anyOf_; + SchemaArray oneOf_; + const SchemaType* not_; + unsigned type_; // bitmask of kSchemaType + SizeType validatorCount_; + SizeType notValidatorIndex_; + + Property* properties_; + const SchemaType* additionalPropertiesSchema_; + PatternProperty* patternProperties_; + SizeType patternPropertyCount_; + SizeType propertyCount_; + SizeType minProperties_; + SizeType maxProperties_; + bool additionalProperties_; + bool hasDependencies_; + bool hasRequired_; + bool hasSchemaDependencies_; + + const SchemaType* additionalItemsSchema_; + const SchemaType* itemsList_; + const SchemaType** itemsTuple_; + SizeType itemsTupleCount_; + SizeType minItems_; + SizeType maxItems_; + bool additionalItems_; + bool uniqueItems_; + + RegexType* pattern_; + SizeType minLength_; + SizeType maxLength_; + + SValue minimum_; + SValue maximum_; + SValue multipleOf_; + bool exclusiveMinimum_; + bool exclusiveMaximum_; + + SizeType defaultValueLength_; +}; + +template<typename Stack, typename Ch> +struct TokenHelper { + RAPIDJSON_FORCEINLINE static void AppendIndexToken(Stack& documentStack, SizeType index) { + *documentStack.template Push<Ch>() = '/'; + char buffer[21]; + size_t length = static_cast<size_t>((sizeof(SizeType) == 4 ? u32toa(index, buffer) : u64toa(index, buffer)) - buffer); + for (size_t i = 0; i < length; i++) + *documentStack.template Push<Ch>() = static_cast<Ch>(buffer[i]); + } +}; + +// Partial specialized version for char to prevent buffer copying. +template <typename Stack> +struct TokenHelper<Stack, char> { + RAPIDJSON_FORCEINLINE static void AppendIndexToken(Stack& documentStack, SizeType index) { + if (sizeof(SizeType) == 4) { + char *buffer = documentStack.template Push<char>(1 + 10); // '/' + uint + *buffer++ = '/'; + const char* end = internal::u32toa(index, buffer); + documentStack.template Pop<char>(static_cast<size_t>(10 - (end - buffer))); + } + else { + char *buffer = documentStack.template Push<char>(1 + 20); // '/' + uint64 + *buffer++ = '/'; + const char* end = internal::u64toa(index, buffer); + documentStack.template Pop<char>(static_cast<size_t>(20 - (end - buffer))); + } + } +}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// IGenericRemoteSchemaDocumentProvider + +template <typename SchemaDocumentType> +class IGenericRemoteSchemaDocumentProvider { +public: + typedef typename SchemaDocumentType::Ch Ch; + + virtual ~IGenericRemoteSchemaDocumentProvider() {} + virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericSchemaDocument + +//! JSON schema document. +/*! + A JSON schema document is a compiled version of a JSON schema. + It is basically a tree of internal::Schema. + + \note This is an immutable class (i.e. its instance cannot be modified after construction). + \tparam ValueT Type of JSON value (e.g. \c Value ), which also determine the encoding. + \tparam Allocator Allocator type for allocating memory of this document. +*/ +template <typename ValueT, typename Allocator = CrtAllocator> +class GenericSchemaDocument { +public: + typedef ValueT ValueType; + typedef IGenericRemoteSchemaDocumentProvider<GenericSchemaDocument> IRemoteSchemaDocumentProviderType; + typedef Allocator AllocatorType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename EncodingType::Ch Ch; + typedef internal::Schema<GenericSchemaDocument> SchemaType; + typedef GenericPointer<ValueType, Allocator> PointerType; + typedef GenericValue<EncodingType, Allocator> URIType; + friend class internal::Schema<GenericSchemaDocument>; + template <typename, typename, typename> + friend class GenericSchemaValidator; + + //! Constructor. + /*! + Compile a JSON document into schema document. + + \param document A JSON document as source. + \param uri The base URI of this schema document for purposes of violation reporting. + \param uriLength Length of \c name, in code points. + \param remoteProvider An optional remote schema document provider for resolving remote reference. Can be null. + \param allocator An optional allocator instance for allocating memory. Can be null. + */ + explicit GenericSchemaDocument(const ValueType& document, const Ch* uri = 0, SizeType uriLength = 0, + IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0) : + remoteProvider_(remoteProvider), + allocator_(allocator), + ownAllocator_(), + root_(), + typeless_(), + schemaMap_(allocator, kInitialSchemaMapSize), + schemaRef_(allocator, kInitialSchemaRefSize) + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + + Ch noUri[1] = {0}; + uri_.SetString(uri ? uri : noUri, uriLength, *allocator_); + + typeless_ = static_cast<SchemaType*>(allocator_->Malloc(sizeof(SchemaType))); + new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_); + + // Generate root schema, it will call CreateSchema() to create sub-schemas, + // And call AddRefSchema() if there are $ref. + CreateSchemaRecursive(&root_, PointerType(), document, document); + + // Resolve $ref + while (!schemaRef_.Empty()) { + SchemaRefEntry* refEntry = schemaRef_.template Pop<SchemaRefEntry>(1); + if (const SchemaType* s = GetSchema(refEntry->target)) { + if (refEntry->schema) + *refEntry->schema = s; + + // Create entry in map if not exist + if (!GetSchema(refEntry->source)) { + new (schemaMap_.template Push<SchemaEntry>()) SchemaEntry(refEntry->source, const_cast<SchemaType*>(s), false, allocator_); + } + } + else if (refEntry->schema) + *refEntry->schema = typeless_; + + refEntry->~SchemaRefEntry(); + } + + RAPIDJSON_ASSERT(root_ != 0); + + schemaRef_.ShrinkToFit(); // Deallocate all memory for ref + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericSchemaDocument(GenericSchemaDocument&& rhs) RAPIDJSON_NOEXCEPT : + remoteProvider_(rhs.remoteProvider_), + allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + root_(rhs.root_), + typeless_(rhs.typeless_), + schemaMap_(std::move(rhs.schemaMap_)), + schemaRef_(std::move(rhs.schemaRef_)), + uri_(std::move(rhs.uri_)) + { + rhs.remoteProvider_ = 0; + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.typeless_ = 0; + } +#endif + + //! Destructor + ~GenericSchemaDocument() { + while (!schemaMap_.Empty()) + schemaMap_.template Pop<SchemaEntry>(1)->~SchemaEntry(); + + if (typeless_) { + typeless_->~SchemaType(); + Allocator::Free(typeless_); + } + + RAPIDJSON_DELETE(ownAllocator_); + } + + const URIType& GetURI() const { return uri_; } + + //! Get the root schema. + const SchemaType& GetRoot() const { return *root_; } + +private: + //! Prohibit copying + GenericSchemaDocument(const GenericSchemaDocument&); + //! Prohibit assignment + GenericSchemaDocument& operator=(const GenericSchemaDocument&); + + struct SchemaRefEntry { + SchemaRefEntry(const PointerType& s, const PointerType& t, const SchemaType** outSchema, Allocator *allocator) : source(s, allocator), target(t, allocator), schema(outSchema) {} + PointerType source; + PointerType target; + const SchemaType** schema; + }; + + struct SchemaEntry { + SchemaEntry(const PointerType& p, SchemaType* s, bool o, Allocator* allocator) : pointer(p, allocator), schema(s), owned(o) {} + ~SchemaEntry() { + if (owned) { + schema->~SchemaType(); + Allocator::Free(schema); + } + } + PointerType pointer; + SchemaType* schema; + bool owned; + }; + + void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { + if (schema) + *schema = typeless_; + + if (v.GetType() == kObjectType) { + const SchemaType* s = GetSchema(pointer); + if (!s) + CreateSchema(schema, pointer, v, document); + + for (typename ValueType::ConstMemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr) + CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document); + } + else if (v.GetType() == kArrayType) + for (SizeType i = 0; i < v.Size(); i++) + CreateSchemaRecursive(0, pointer.Append(i, allocator_), v[i], document); + } + + void CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { + RAPIDJSON_ASSERT(pointer.IsValid()); + if (v.IsObject()) { + if (!HandleRefSchema(pointer, schema, v, document)) { + SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_); + new (schemaMap_.template Push<SchemaEntry>()) SchemaEntry(pointer, s, true, allocator_); + if (schema) + *schema = s; + } + } + } + + bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document) { + static const Ch kRefString[] = { '$', 'r', 'e', 'f', '\0' }; + static const ValueType kRefValue(kRefString, 4); + + typename ValueType::ConstMemberIterator itr = v.FindMember(kRefValue); + if (itr == v.MemberEnd()) + return false; + + if (itr->value.IsString()) { + SizeType len = itr->value.GetStringLength(); + if (len > 0) { + const Ch* s = itr->value.GetString(); + SizeType i = 0; + while (i < len && s[i] != '#') // Find the first # + i++; + + if (i > 0) { // Remote reference, resolve immediately + if (remoteProvider_) { + if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(s, i)) { + PointerType pointer(&s[i], len - i, allocator_); + if (pointer.IsValid()) { + if (const SchemaType* sc = remoteDocument->GetSchema(pointer)) { + if (schema) + *schema = sc; + new (schemaMap_.template Push<SchemaEntry>()) SchemaEntry(source, const_cast<SchemaType*>(sc), false, allocator_); + return true; + } + } + } + } + } + else if (s[i] == '#') { // Local reference, defer resolution + PointerType pointer(&s[i], len - i, allocator_); + if (pointer.IsValid()) { + if (const ValueType* nv = pointer.Get(document)) + if (HandleRefSchema(source, schema, *nv, document)) + return true; + + new (schemaRef_.template Push<SchemaRefEntry>()) SchemaRefEntry(source, pointer, schema, allocator_); + return true; + } + } + } + } + return false; + } + + const SchemaType* GetSchema(const PointerType& pointer) const { + for (const SchemaEntry* target = schemaMap_.template Bottom<SchemaEntry>(); target != schemaMap_.template End<SchemaEntry>(); ++target) + if (pointer == target->pointer) + return target->schema; + return 0; + } + + PointerType GetPointer(const SchemaType* schema) const { + for (const SchemaEntry* target = schemaMap_.template Bottom<SchemaEntry>(); target != schemaMap_.template End<SchemaEntry>(); ++target) + if (schema == target->schema) + return target->pointer; + return PointerType(); + } + + const SchemaType* GetTypeless() const { return typeless_; } + + static const size_t kInitialSchemaMapSize = 64; + static const size_t kInitialSchemaRefSize = 64; + + IRemoteSchemaDocumentProviderType* remoteProvider_; + Allocator *allocator_; + Allocator *ownAllocator_; + const SchemaType* root_; //!< Root schema. + SchemaType* typeless_; + internal::Stack<Allocator> schemaMap_; // Stores created Pointer -> Schemas + internal::Stack<Allocator> schemaRef_; // Stores Pointer from $ref and schema which holds the $ref + URIType uri_; +}; + +//! GenericSchemaDocument using Value type. +typedef GenericSchemaDocument<Value> SchemaDocument; +//! IGenericRemoteSchemaDocumentProvider using SchemaDocument. +typedef IGenericRemoteSchemaDocumentProvider<SchemaDocument> IRemoteSchemaDocumentProvider; + +/////////////////////////////////////////////////////////////////////////////// +// GenericSchemaValidator + +//! JSON Schema Validator. +/*! + A SAX style JSON schema validator. + It uses a \c GenericSchemaDocument to validate SAX events. + It delegates the incoming SAX events to an output handler. + The default output handler does nothing. + It can be reused multiple times by calling \c Reset(). + + \tparam SchemaDocumentType Type of schema document. + \tparam OutputHandler Type of output handler. Default handler does nothing. + \tparam StateAllocator Allocator for storing the internal validation states. +*/ +template < + typename SchemaDocumentType, + typename OutputHandler = BaseReaderHandler<typename SchemaDocumentType::SchemaType::EncodingType>, + typename StateAllocator = CrtAllocator> +class GenericSchemaValidator : + public internal::ISchemaStateFactory<typename SchemaDocumentType::SchemaType>, + public internal::ISchemaValidator, + public internal::IValidationErrorHandler<typename SchemaDocumentType::SchemaType> { +public: + typedef typename SchemaDocumentType::SchemaType SchemaType; + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename SchemaType::EncodingType EncodingType; + typedef typename SchemaType::SValue SValue; + typedef typename EncodingType::Ch Ch; + typedef GenericStringRef<Ch> StringRefType; + typedef GenericValue<EncodingType, StateAllocator> ValueType; + + //! Constructor without output handler. + /*! + \param schemaDocument The schema document to conform to. + \param allocator Optional allocator for storing internal validation states. + \param schemaStackCapacity Optional initial capacity of schema path stack. + \param documentStackCapacity Optional initial capacity of document path stack. + */ + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(schemaDocument.GetRoot()), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + outputHandler_(0), + error_(kObjectType), + currentError_(), + missingDependents_(), + valid_(true), + flags_(kValidateDefaultFlags) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(0) +#endif + { + } + + //! Constructor with output handler. + /*! + \param schemaDocument The schema document to conform to. + \param allocator Optional allocator for storing internal validation states. + \param schemaStackCapacity Optional initial capacity of schema path stack. + \param documentStackCapacity Optional initial capacity of document path stack. + */ + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + OutputHandler& outputHandler, + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(schemaDocument.GetRoot()), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + outputHandler_(&outputHandler), + error_(kObjectType), + currentError_(), + missingDependents_(), + valid_(true), + flags_(kValidateDefaultFlags) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(0) +#endif + { + } + + //! Destructor. + ~GenericSchemaValidator() { + Reset(); + RAPIDJSON_DELETE(ownStateAllocator_); + } + + //! Reset the internal states. + void Reset() { + while (!schemaStack_.Empty()) + PopSchema(); + documentStack_.Clear(); + ResetError(); + } + + //! Reset the error state. + void ResetError() { + error_.SetObject(); + currentError_.SetNull(); + missingDependents_.SetNull(); + valid_ = true; + } + + //! Implementation of ISchemaValidator + void SetValidateFlags(unsigned flags) { + flags_ = flags; + } + virtual unsigned GetValidateFlags() const { + return flags_; + } + + //! Checks whether the current state is valid. + // Implementation of ISchemaValidator + virtual bool IsValid() const { + if (!valid_) return false; + if (GetContinueOnErrors() && !error_.ObjectEmpty()) return false; + return true; + } + + //! Gets the error object. + ValueType& GetError() { return error_; } + const ValueType& GetError() const { return error_; } + + //! Gets the JSON pointer pointed to the invalid schema. + // If reporting all errors, the stack will be empty. + PointerType GetInvalidSchemaPointer() const { + return schemaStack_.Empty() ? PointerType() : CurrentSchema().GetPointer(); + } + + //! Gets the keyword of invalid schema. + // If reporting all errors, the stack will be empty, so return "errors". + const Ch* GetInvalidSchemaKeyword() const { + if (!schemaStack_.Empty()) return CurrentContext().invalidKeyword; + if (GetContinueOnErrors() && !error_.ObjectEmpty()) return (const Ch*)GetErrorsString(); + return 0; + } + + //! Gets the error code of invalid schema. + // If reporting all errors, the stack will be empty, so return kValidateErrors. + ValidateErrorCode GetInvalidSchemaCode() const { + if (!schemaStack_.Empty()) return CurrentContext().invalidCode; + if (GetContinueOnErrors() && !error_.ObjectEmpty()) return kValidateErrors; + return kValidateErrorNone; + } + + //! Gets the JSON pointer pointed to the invalid value. + // If reporting all errors, the stack will be empty. + PointerType GetInvalidDocumentPointer() const { + if (documentStack_.Empty()) { + return PointerType(); + } + else { + return PointerType(documentStack_.template Bottom<Ch>(), documentStack_.GetSize() / sizeof(Ch)); + } + } + + void NotMultipleOf(int64_t actual, const SValue& expected) { + AddNumberError(kValidateErrorMultipleOf, ValueType(actual).Move(), expected); + } + void NotMultipleOf(uint64_t actual, const SValue& expected) { + AddNumberError(kValidateErrorMultipleOf, ValueType(actual).Move(), expected); + } + void NotMultipleOf(double actual, const SValue& expected) { + AddNumberError(kValidateErrorMultipleOf, ValueType(actual).Move(), expected); + } + void AboveMaximum(int64_t actual, const SValue& expected, bool exclusive) { + AddNumberError(exclusive ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum, ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMaximumString : 0); + } + void AboveMaximum(uint64_t actual, const SValue& expected, bool exclusive) { + AddNumberError(exclusive ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum, ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMaximumString : 0); + } + void AboveMaximum(double actual, const SValue& expected, bool exclusive) { + AddNumberError(exclusive ? kValidateErrorExclusiveMaximum : kValidateErrorMaximum, ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMaximumString : 0); + } + void BelowMinimum(int64_t actual, const SValue& expected, bool exclusive) { + AddNumberError(exclusive ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum, ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMinimumString : 0); + } + void BelowMinimum(uint64_t actual, const SValue& expected, bool exclusive) { + AddNumberError(exclusive ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum, ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMinimumString : 0); + } + void BelowMinimum(double actual, const SValue& expected, bool exclusive) { + AddNumberError(exclusive ? kValidateErrorExclusiveMinimum : kValidateErrorMinimum, ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMinimumString : 0); + } + + void TooLong(const Ch* str, SizeType length, SizeType expected) { + AddNumberError(kValidateErrorMaxLength, + ValueType(str, length, GetStateAllocator()).Move(), SValue(expected).Move()); + } + void TooShort(const Ch* str, SizeType length, SizeType expected) { + AddNumberError(kValidateErrorMinLength, + ValueType(str, length, GetStateAllocator()).Move(), SValue(expected).Move()); + } + void DoesNotMatch(const Ch* str, SizeType length) { + currentError_.SetObject(); + currentError_.AddMember(GetActualString(), ValueType(str, length, GetStateAllocator()).Move(), GetStateAllocator()); + AddCurrentError(kValidateErrorPattern); + } + + void DisallowedItem(SizeType index) { + currentError_.SetObject(); + currentError_.AddMember(GetDisallowedString(), ValueType(index).Move(), GetStateAllocator()); + AddCurrentError(kValidateErrorAdditionalItems, true); + } + void TooFewItems(SizeType actualCount, SizeType expectedCount) { + AddNumberError(kValidateErrorMinItems, + ValueType(actualCount).Move(), SValue(expectedCount).Move()); + } + void TooManyItems(SizeType actualCount, SizeType expectedCount) { + AddNumberError(kValidateErrorMaxItems, + ValueType(actualCount).Move(), SValue(expectedCount).Move()); + } + void DuplicateItems(SizeType index1, SizeType index2) { + ValueType duplicates(kArrayType); + duplicates.PushBack(index1, GetStateAllocator()); + duplicates.PushBack(index2, GetStateAllocator()); + currentError_.SetObject(); + currentError_.AddMember(GetDuplicatesString(), duplicates, GetStateAllocator()); + AddCurrentError(kValidateErrorUniqueItems, true); + } + + void TooManyProperties(SizeType actualCount, SizeType expectedCount) { + AddNumberError(kValidateErrorMaxProperties, + ValueType(actualCount).Move(), SValue(expectedCount).Move()); + } + void TooFewProperties(SizeType actualCount, SizeType expectedCount) { + AddNumberError(kValidateErrorMinProperties, + ValueType(actualCount).Move(), SValue(expectedCount).Move()); + } + void StartMissingProperties() { + currentError_.SetArray(); + } + void AddMissingProperty(const SValue& name) { + currentError_.PushBack(ValueType(name, GetStateAllocator()).Move(), GetStateAllocator()); + } + bool EndMissingProperties() { + if (currentError_.Empty()) + return false; + ValueType error(kObjectType); + error.AddMember(GetMissingString(), currentError_, GetStateAllocator()); + currentError_ = error; + AddCurrentError(kValidateErrorRequired); + return true; + } + void PropertyViolations(ISchemaValidator** subvalidators, SizeType count) { + for (SizeType i = 0; i < count; ++i) + MergeError(static_cast<GenericSchemaValidator*>(subvalidators[i])->GetError()); + } + void DisallowedProperty(const Ch* name, SizeType length) { + currentError_.SetObject(); + currentError_.AddMember(GetDisallowedString(), ValueType(name, length, GetStateAllocator()).Move(), GetStateAllocator()); + AddCurrentError(kValidateErrorAdditionalProperties, true); + } + + void StartDependencyErrors() { + currentError_.SetObject(); + } + void StartMissingDependentProperties() { + missingDependents_.SetArray(); + } + void AddMissingDependentProperty(const SValue& targetName) { + missingDependents_.PushBack(ValueType(targetName, GetStateAllocator()).Move(), GetStateAllocator()); + } + void EndMissingDependentProperties(const SValue& sourceName) { + if (!missingDependents_.Empty()) { + // Create equivalent 'required' error + ValueType error(kObjectType); + ValidateErrorCode code = kValidateErrorRequired; + error.AddMember(GetMissingString(), missingDependents_.Move(), GetStateAllocator()); + AddErrorCode(error, code); + AddErrorInstanceLocation(error, false); + // When appending to a pointer ensure its allocator is used + PointerType schemaRef = GetInvalidSchemaPointer().Append(SchemaType::GetValidateErrorKeyword(kValidateErrorDependencies), &GetInvalidSchemaPointer().GetAllocator()); + AddErrorSchemaLocation(error, schemaRef.Append(sourceName.GetString(), sourceName.GetStringLength(), &GetInvalidSchemaPointer().GetAllocator())); + ValueType wrapper(kObjectType); + wrapper.AddMember(ValueType(SchemaType::GetValidateErrorKeyword(code), GetStateAllocator()).Move(), error, GetStateAllocator()); + currentError_.AddMember(ValueType(sourceName, GetStateAllocator()).Move(), wrapper, GetStateAllocator()); + } + } + void AddDependencySchemaError(const SValue& sourceName, ISchemaValidator* subvalidator) { + currentError_.AddMember(ValueType(sourceName, GetStateAllocator()).Move(), + static_cast<GenericSchemaValidator*>(subvalidator)->GetError(), GetStateAllocator()); + } + bool EndDependencyErrors() { + if (currentError_.ObjectEmpty()) + return false; + ValueType error(kObjectType); + error.AddMember(GetErrorsString(), currentError_, GetStateAllocator()); + currentError_ = error; + AddCurrentError(kValidateErrorDependencies); + return true; + } + + void DisallowedValue(const ValidateErrorCode code = kValidateErrorEnum) { + currentError_.SetObject(); + AddCurrentError(code); + } + void StartDisallowedType() { + currentError_.SetArray(); + } + void AddExpectedType(const typename SchemaType::ValueType& expectedType) { + currentError_.PushBack(ValueType(expectedType, GetStateAllocator()).Move(), GetStateAllocator()); + } + void EndDisallowedType(const typename SchemaType::ValueType& actualType) { + ValueType error(kObjectType); + error.AddMember(GetExpectedString(), currentError_, GetStateAllocator()); + error.AddMember(GetActualString(), ValueType(actualType, GetStateAllocator()).Move(), GetStateAllocator()); + currentError_ = error; + AddCurrentError(kValidateErrorType); + } + void NotAllOf(ISchemaValidator** subvalidators, SizeType count) { + // Treat allOf like oneOf and anyOf to match https://rapidjson.org/md_doc_schema.html#allOf-anyOf-oneOf + AddErrorArray(kValidateErrorAllOf, subvalidators, count); + //for (SizeType i = 0; i < count; ++i) { + // MergeError(static_cast<GenericSchemaValidator*>(subvalidators[i])->GetError()); + //} + } + void NoneOf(ISchemaValidator** subvalidators, SizeType count) { + AddErrorArray(kValidateErrorAnyOf, subvalidators, count); + } + void NotOneOf(ISchemaValidator** subvalidators, SizeType count, bool matched = false) { + AddErrorArray(matched ? kValidateErrorOneOfMatch : kValidateErrorOneOf, subvalidators, count); + } + void Disallowed() { + currentError_.SetObject(); + AddCurrentError(kValidateErrorNot); + } + +#define RAPIDJSON_STRING_(name, ...) \ + static const StringRefType& Get##name##String() {\ + static const Ch s[] = { __VA_ARGS__, '\0' };\ + static const StringRefType v(s, static_cast<SizeType>(sizeof(s) / sizeof(Ch) - 1)); \ + return v;\ + } + + RAPIDJSON_STRING_(InstanceRef, 'i', 'n', 's', 't', 'a', 'n', 'c', 'e', 'R', 'e', 'f') + RAPIDJSON_STRING_(SchemaRef, 's', 'c', 'h', 'e', 'm', 'a', 'R', 'e', 'f') + RAPIDJSON_STRING_(Expected, 'e', 'x', 'p', 'e', 'c', 't', 'e', 'd') + RAPIDJSON_STRING_(Actual, 'a', 'c', 't', 'u', 'a', 'l') + RAPIDJSON_STRING_(Disallowed, 'd', 'i', 's', 'a', 'l', 'l', 'o', 'w', 'e', 'd') + RAPIDJSON_STRING_(Missing, 'm', 'i', 's', 's', 'i', 'n', 'g') + RAPIDJSON_STRING_(Errors, 'e', 'r', 'r', 'o', 'r', 's') + RAPIDJSON_STRING_(ErrorCode, 'e', 'r', 'r', 'o', 'r', 'C', 'o', 'd', 'e') + RAPIDJSON_STRING_(ErrorMessage, 'e', 'r', 'r', 'o', 'r', 'M', 'e', 's', 's', 'a', 'g', 'e') + RAPIDJSON_STRING_(Duplicates, 'd', 'u', 'p', 'l', 'i', 'c', 'a', 't', 'e', 's') + +#undef RAPIDJSON_STRING_ + +#if RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() \ +RAPIDJSON_MULTILINEMACRO_BEGIN\ + *documentStack_.template Push<Ch>() = '\0';\ + documentStack_.template Pop<Ch>(1);\ + internal::PrintInvalidDocument(documentStack_.template Bottom<Ch>());\ +RAPIDJSON_MULTILINEMACRO_END +#else +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() +#endif + +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_(method, arg1)\ + if (!valid_) return false; \ + if ((!BeginValue() && !GetContinueOnErrors()) || (!CurrentSchema().method arg1 && !GetContinueOnErrors())) {\ + RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_();\ + return valid_ = false;\ + } + +#define RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2)\ + for (Context* context = schemaStack_.template Bottom<Context>(); context != schemaStack_.template End<Context>(); context++) {\ + if (context->hasher)\ + static_cast<HasherType*>(context->hasher)->method arg2;\ + if (context->validators)\ + for (SizeType i_ = 0; i_ < context->validatorCount; i_++)\ + static_cast<GenericSchemaValidator*>(context->validators[i_])->method arg2;\ + if (context->patternPropertiesValidators)\ + for (SizeType i_ = 0; i_ < context->patternPropertiesValidatorCount; i_++)\ + static_cast<GenericSchemaValidator*>(context->patternPropertiesValidators[i_])->method arg2;\ + } + +#define RAPIDJSON_SCHEMA_HANDLE_END_(method, arg2)\ + valid_ = (EndValue() || GetContinueOnErrors()) && (!outputHandler_ || outputHandler_->method arg2);\ + return valid_; + +#define RAPIDJSON_SCHEMA_HANDLE_VALUE_(method, arg1, arg2) \ + RAPIDJSON_SCHEMA_HANDLE_BEGIN_ (method, arg1);\ + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2);\ + RAPIDJSON_SCHEMA_HANDLE_END_ (method, arg2) + + bool Null() { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Null, (CurrentContext()), ( )); } + bool Bool(bool b) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Bool, (CurrentContext(), b), (b)); } + bool Int(int i) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Int, (CurrentContext(), i), (i)); } + bool Uint(unsigned u) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Uint, (CurrentContext(), u), (u)); } + bool Int64(int64_t i) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Int64, (CurrentContext(), i), (i)); } + bool Uint64(uint64_t u) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Uint64, (CurrentContext(), u), (u)); } + bool Double(double d) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Double, (CurrentContext(), d), (d)); } + bool RawNumber(const Ch* str, SizeType length, bool copy) + { RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); } + bool String(const Ch* str, SizeType length, bool copy) + { RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); } + + bool StartObject() { + RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartObject, (CurrentContext())); + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartObject, ()); + return valid_ = !outputHandler_ || outputHandler_->StartObject(); + } + + bool Key(const Ch* str, SizeType len, bool copy) { + if (!valid_) return false; + AppendToken(str, len); + if (!CurrentSchema().Key(CurrentContext(), str, len, copy) && !GetContinueOnErrors()) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(Key, (str, len, copy)); + return valid_ = !outputHandler_ || outputHandler_->Key(str, len, copy); + } + + bool EndObject(SizeType memberCount) { + if (!valid_) return false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndObject, (memberCount)); + if (!CurrentSchema().EndObject(CurrentContext(), memberCount) && !GetContinueOnErrors()) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_END_(EndObject, (memberCount)); + } + + bool StartArray() { + RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartArray, (CurrentContext())); + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartArray, ()); + return valid_ = !outputHandler_ || outputHandler_->StartArray(); + } + + bool EndArray(SizeType elementCount) { + if (!valid_) return false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndArray, (elementCount)); + if (!CurrentSchema().EndArray(CurrentContext(), elementCount) && !GetContinueOnErrors()) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_END_(EndArray, (elementCount)); + } + +#undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_ +#undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_ +#undef RAPIDJSON_SCHEMA_HANDLE_PARALLEL_ +#undef RAPIDJSON_SCHEMA_HANDLE_VALUE_ + + // Implementation of ISchemaStateFactory<SchemaType> + virtual ISchemaValidator* CreateSchemaValidator(const SchemaType& root, const bool inheritContinueOnErrors) { + ISchemaValidator* sv = new (GetStateAllocator().Malloc(sizeof(GenericSchemaValidator))) GenericSchemaValidator(*schemaDocument_, root, documentStack_.template Bottom<char>(), documentStack_.GetSize(), +#if RAPIDJSON_SCHEMA_VERBOSE + depth_ + 1, +#endif + &GetStateAllocator()); + sv->SetValidateFlags(inheritContinueOnErrors ? GetValidateFlags() : GetValidateFlags() & ~(unsigned)kValidateContinueOnErrorFlag); + return sv; + } + + virtual void DestroySchemaValidator(ISchemaValidator* validator) { + GenericSchemaValidator* v = static_cast<GenericSchemaValidator*>(validator); + v->~GenericSchemaValidator(); + StateAllocator::Free(v); + } + + virtual void* CreateHasher() { + return new (GetStateAllocator().Malloc(sizeof(HasherType))) HasherType(&GetStateAllocator()); + } + + virtual uint64_t GetHashCode(void* hasher) { + return static_cast<HasherType*>(hasher)->GetHashCode(); + } + + virtual void DestroryHasher(void* hasher) { + HasherType* h = static_cast<HasherType*>(hasher); + h->~HasherType(); + StateAllocator::Free(h); + } + + virtual void* MallocState(size_t size) { + return GetStateAllocator().Malloc(size); + } + + virtual void FreeState(void* p) { + StateAllocator::Free(p); + } + +private: + typedef typename SchemaType::Context Context; + typedef GenericValue<UTF8<>, StateAllocator> HashCodeArray; + typedef internal::Hasher<EncodingType, StateAllocator> HasherType; + + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + const SchemaType& root, + const char* basePath, size_t basePathSize, +#if RAPIDJSON_SCHEMA_VERBOSE + unsigned depth, +#endif + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(root), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + outputHandler_(0), + error_(kObjectType), + currentError_(), + missingDependents_(), + valid_(true), + flags_(kValidateDefaultFlags) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(depth) +#endif + { + if (basePath && basePathSize) + memcpy(documentStack_.template Push<char>(basePathSize), basePath, basePathSize); + } + + StateAllocator& GetStateAllocator() { + if (!stateAllocator_) + stateAllocator_ = ownStateAllocator_ = RAPIDJSON_NEW(StateAllocator)(); + return *stateAllocator_; + } + + bool GetContinueOnErrors() const { + return flags_ & kValidateContinueOnErrorFlag; + } + + bool BeginValue() { + if (schemaStack_.Empty()) + PushSchema(root_); + else { + if (CurrentContext().inArray) + internal::TokenHelper<internal::Stack<StateAllocator>, Ch>::AppendIndexToken(documentStack_, CurrentContext().arrayElementIndex); + + if (!CurrentSchema().BeginValue(CurrentContext()) && !GetContinueOnErrors()) + return false; + + SizeType count = CurrentContext().patternPropertiesSchemaCount; + const SchemaType** sa = CurrentContext().patternPropertiesSchemas; + typename Context::PatternValidatorType patternValidatorType = CurrentContext().valuePatternValidatorType; + bool valueUniqueness = CurrentContext().valueUniqueness; + RAPIDJSON_ASSERT(CurrentContext().valueSchema); + PushSchema(*CurrentContext().valueSchema); + + if (count > 0) { + CurrentContext().objectPatternValidatorType = patternValidatorType; + ISchemaValidator**& va = CurrentContext().patternPropertiesValidators; + SizeType& validatorCount = CurrentContext().patternPropertiesValidatorCount; + va = static_cast<ISchemaValidator**>(MallocState(sizeof(ISchemaValidator*) * count)); + for (SizeType i = 0; i < count; i++) + va[validatorCount++] = CreateSchemaValidator(*sa[i], true); // Inherit continueOnError + } + + CurrentContext().arrayUniqueness = valueUniqueness; + } + return true; + } + + bool EndValue() { + if (!CurrentSchema().EndValue(CurrentContext()) && !GetContinueOnErrors()) + return false; + +#if RAPIDJSON_SCHEMA_VERBOSE + GenericStringBuffer<EncodingType> sb; + schemaDocument_->GetPointer(&CurrentSchema()).Stringify(sb); + + *documentStack_.template Push<Ch>() = '\0'; + documentStack_.template Pop<Ch>(1); + internal::PrintValidatorPointers(depth_, sb.GetString(), documentStack_.template Bottom<Ch>()); +#endif + void* hasher = CurrentContext().hasher; + uint64_t h = hasher && CurrentContext().arrayUniqueness ? static_cast<HasherType*>(hasher)->GetHashCode() : 0; + + PopSchema(); + + if (!schemaStack_.Empty()) { + Context& context = CurrentContext(); + // Only check uniqueness if there is a hasher + if (hasher && context.valueUniqueness) { + HashCodeArray* a = static_cast<HashCodeArray*>(context.arrayElementHashCodes); + if (!a) + CurrentContext().arrayElementHashCodes = a = new (GetStateAllocator().Malloc(sizeof(HashCodeArray))) HashCodeArray(kArrayType); + for (typename HashCodeArray::ConstValueIterator itr = a->Begin(); itr != a->End(); ++itr) + if (itr->GetUint64() == h) { + DuplicateItems(static_cast<SizeType>(itr - a->Begin()), a->Size()); + // Cleanup before returning if continuing + if (GetContinueOnErrors()) { + a->PushBack(h, GetStateAllocator()); + while (!documentStack_.Empty() && *documentStack_.template Pop<Ch>(1) != '/'); + } + RAPIDJSON_INVALID_KEYWORD_RETURN(kValidateErrorUniqueItems); + } + a->PushBack(h, GetStateAllocator()); + } + } + + // Remove the last token of document pointer + while (!documentStack_.Empty() && *documentStack_.template Pop<Ch>(1) != '/') + ; + + return true; + } + + void AppendToken(const Ch* str, SizeType len) { + documentStack_.template Reserve<Ch>(1 + len * 2); // worst case all characters are escaped as two characters + *documentStack_.template PushUnsafe<Ch>() = '/'; + for (SizeType i = 0; i < len; i++) { + if (str[i] == '~') { + *documentStack_.template PushUnsafe<Ch>() = '~'; + *documentStack_.template PushUnsafe<Ch>() = '0'; + } + else if (str[i] == '/') { + *documentStack_.template PushUnsafe<Ch>() = '~'; + *documentStack_.template PushUnsafe<Ch>() = '1'; + } + else + *documentStack_.template PushUnsafe<Ch>() = str[i]; + } + } + + RAPIDJSON_FORCEINLINE void PushSchema(const SchemaType& schema) { new (schemaStack_.template Push<Context>()) Context(*this, *this, &schema); } + + RAPIDJSON_FORCEINLINE void PopSchema() { + Context* c = schemaStack_.template Pop<Context>(1); + if (HashCodeArray* a = static_cast<HashCodeArray*>(c->arrayElementHashCodes)) { + a->~HashCodeArray(); + StateAllocator::Free(a); + } + c->~Context(); + } + + void AddErrorInstanceLocation(ValueType& result, bool parent) { + GenericStringBuffer<EncodingType> sb; + PointerType instancePointer = GetInvalidDocumentPointer(); + ((parent && instancePointer.GetTokenCount() > 0) + ? PointerType(instancePointer.GetTokens(), instancePointer.GetTokenCount() - 1) + : instancePointer).StringifyUriFragment(sb); + ValueType instanceRef(sb.GetString(), static_cast<SizeType>(sb.GetSize() / sizeof(Ch)), + GetStateAllocator()); + result.AddMember(GetInstanceRefString(), instanceRef, GetStateAllocator()); + } + + void AddErrorSchemaLocation(ValueType& result, PointerType schema = PointerType()) { + GenericStringBuffer<EncodingType> sb; + SizeType len = CurrentSchema().GetURI().GetStringLength(); + if (len) memcpy(sb.Push(len), CurrentSchema().GetURI().GetString(), len * sizeof(Ch)); + if (schema.GetTokenCount()) schema.StringifyUriFragment(sb); + else GetInvalidSchemaPointer().StringifyUriFragment(sb); + ValueType schemaRef(sb.GetString(), static_cast<SizeType>(sb.GetSize() / sizeof(Ch)), + GetStateAllocator()); + result.AddMember(GetSchemaRefString(), schemaRef, GetStateAllocator()); + } + + void AddErrorCode(ValueType& result, const ValidateErrorCode code) { + result.AddMember(GetErrorCodeString(), code, GetStateAllocator()); + } + + void AddError(ValueType& keyword, ValueType& error) { + typename ValueType::MemberIterator member = error_.FindMember(keyword); + if (member == error_.MemberEnd()) + error_.AddMember(keyword, error, GetStateAllocator()); + else { + if (member->value.IsObject()) { + ValueType errors(kArrayType); + errors.PushBack(member->value, GetStateAllocator()); + member->value = errors; + } + member->value.PushBack(error, GetStateAllocator()); + } + } + + void AddCurrentError(const ValidateErrorCode code, bool parent = false) { + AddErrorCode(currentError_, code); + AddErrorInstanceLocation(currentError_, parent); + AddErrorSchemaLocation(currentError_); + AddError(ValueType(SchemaType::GetValidateErrorKeyword(code), GetStateAllocator(), false).Move(), currentError_); + } + + void MergeError(ValueType& other) { + for (typename ValueType::MemberIterator it = other.MemberBegin(), end = other.MemberEnd(); it != end; ++it) { + AddError(it->name, it->value); + } + } + + void AddNumberError(const ValidateErrorCode code, ValueType& actual, const SValue& expected, + const typename SchemaType::ValueType& (*exclusive)() = 0) { + currentError_.SetObject(); + currentError_.AddMember(GetActualString(), actual, GetStateAllocator()); + currentError_.AddMember(GetExpectedString(), ValueType(expected, GetStateAllocator()).Move(), GetStateAllocator()); + if (exclusive) + currentError_.AddMember(ValueType(exclusive(), GetStateAllocator()).Move(), true, GetStateAllocator()); + AddCurrentError(code); + } + + void AddErrorArray(const ValidateErrorCode code, + ISchemaValidator** subvalidators, SizeType count) { + ValueType errors(kArrayType); + for (SizeType i = 0; i < count; ++i) + errors.PushBack(static_cast<GenericSchemaValidator*>(subvalidators[i])->GetError(), GetStateAllocator()); + currentError_.SetObject(); + currentError_.AddMember(GetErrorsString(), errors, GetStateAllocator()); + AddCurrentError(code); + } + + const SchemaType& CurrentSchema() const { return *schemaStack_.template Top<Context>()->schema; } + Context& CurrentContext() { return *schemaStack_.template Top<Context>(); } + const Context& CurrentContext() const { return *schemaStack_.template Top<Context>(); } + + static const size_t kDefaultSchemaStackCapacity = 1024; + static const size_t kDefaultDocumentStackCapacity = 256; + const SchemaDocumentType* schemaDocument_; + const SchemaType& root_; + StateAllocator* stateAllocator_; + StateAllocator* ownStateAllocator_; + internal::Stack<StateAllocator> schemaStack_; //!< stack to store the current path of schema (BaseSchemaType *) + internal::Stack<StateAllocator> documentStack_; //!< stack to store the current path of validating document (Ch) + OutputHandler* outputHandler_; + ValueType error_; + ValueType currentError_; + ValueType missingDependents_; + bool valid_; + unsigned flags_; +#if RAPIDJSON_SCHEMA_VERBOSE + unsigned depth_; +#endif +}; + +typedef GenericSchemaValidator<SchemaDocument> SchemaValidator; + +/////////////////////////////////////////////////////////////////////////////// +// SchemaValidatingReader + +//! A helper class for parsing with validation. +/*! + This helper class is a functor, designed as a parameter of \ref GenericDocument::Populate(). + + \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept. + \tparam SourceEncoding Encoding of the input stream. + \tparam SchemaDocumentType Type of schema document. + \tparam StackAllocator Allocator type for stack. +*/ +template < + unsigned parseFlags, + typename InputStream, + typename SourceEncoding, + typename SchemaDocumentType = SchemaDocument, + typename StackAllocator = CrtAllocator> +class SchemaValidatingReader { +public: + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename InputStream::Ch Ch; + typedef GenericValue<SourceEncoding, StackAllocator> ValueType; + + //! Constructor + /*! + \param is Input stream. + \param sd Schema document. + */ + SchemaValidatingReader(InputStream& is, const SchemaDocumentType& sd) : is_(is), sd_(sd), invalidSchemaKeyword_(), invalidSchemaCode_(kValidateErrorNone), error_(kObjectType), isValid_(true) {} + + template <typename Handler> + bool operator()(Handler& handler) { + GenericReader<SourceEncoding, typename SchemaDocumentType::EncodingType, StackAllocator> reader; + GenericSchemaValidator<SchemaDocumentType, Handler> validator(sd_, handler); + parseResult_ = reader.template Parse<parseFlags>(is_, validator); + + isValid_ = validator.IsValid(); + if (isValid_) { + invalidSchemaPointer_ = PointerType(); + invalidSchemaKeyword_ = 0; + invalidDocumentPointer_ = PointerType(); + error_.SetObject(); + } + else { + invalidSchemaPointer_ = validator.GetInvalidSchemaPointer(); + invalidSchemaKeyword_ = validator.GetInvalidSchemaKeyword(); + invalidSchemaCode_ = validator.GetInvalidSchemaCode(); + invalidDocumentPointer_ = validator.GetInvalidDocumentPointer(); + error_.CopyFrom(validator.GetError(), allocator_); + } + + return parseResult_; + } + + const ParseResult& GetParseResult() const { return parseResult_; } + bool IsValid() const { return isValid_; } + const PointerType& GetInvalidSchemaPointer() const { return invalidSchemaPointer_; } + const Ch* GetInvalidSchemaKeyword() const { return invalidSchemaKeyword_; } + const PointerType& GetInvalidDocumentPointer() const { return invalidDocumentPointer_; } + const ValueType& GetError() const { return error_; } + ValidateErrorCode GetInvalidSchemaCode() const { return invalidSchemaCode_; } + +private: + InputStream& is_; + const SchemaDocumentType& sd_; + + ParseResult parseResult_; + PointerType invalidSchemaPointer_; + const Ch* invalidSchemaKeyword_; + PointerType invalidDocumentPointer_; + ValidateErrorCode invalidSchemaCode_; + StackAllocator allocator_; + ValueType error_; + bool isValid_; +}; + +RAPIDJSON_NAMESPACE_END +RAPIDJSON_DIAG_POP + +#endif // RAPIDJSON_SCHEMA_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/stream.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/stream.h new file mode 100644 index 00000000..1fd70915 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/stream.h @@ -0,0 +1,223 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "rapidjson.h" + +#ifndef RAPIDJSON_STREAM_H_ +#define RAPIDJSON_STREAM_H_ + +#include "encodings.h" + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Stream + +/*! \class rapidjson::Stream + \brief Concept for reading and writing characters. + + For read-only stream, no need to implement PutBegin(), Put(), Flush() and PutEnd(). + + For write-only stream, only need to implement Put() and Flush(). + +\code +concept Stream { + typename Ch; //!< Character type of the stream. + + //! Read the current character from stream without moving the read cursor. + Ch Peek() const; + + //! Read the current character from stream and moving the read cursor to next character. + Ch Take(); + + //! Get the current read cursor. + //! \return Number of characters read from start. + size_t Tell(); + + //! Begin writing operation at the current read pointer. + //! \return The begin writer pointer. + Ch* PutBegin(); + + //! Write a character. + void Put(Ch c); + + //! Flush the buffer. + void Flush(); + + //! End the writing operation. + //! \param begin The begin write pointer returned by PutBegin(). + //! \return Number of characters written. + size_t PutEnd(Ch* begin); +} +\endcode +*/ + +//! Provides additional information for stream. +/*! + By using traits pattern, this type provides a default configuration for stream. + For custom stream, this type can be specialized for other configuration. + See TEST(Reader, CustomStringStream) in readertest.cpp for example. +*/ +template<typename Stream> +struct StreamTraits { + //! Whether to make local copy of stream for optimization during parsing. + /*! + By default, for safety, streams do not use local copy optimization. + Stream that can be copied fast should specialize this, like StreamTraits<StringStream>. + */ + enum { copyOptimization = 0 }; +}; + +//! Reserve n characters for writing to a stream. +template<typename Stream> +inline void PutReserve(Stream& stream, size_t count) { + (void)stream; + (void)count; +} + +//! Write character to a stream, presuming buffer is reserved. +template<typename Stream> +inline void PutUnsafe(Stream& stream, typename Stream::Ch c) { + stream.Put(c); +} + +//! Put N copies of a character to a stream. +template<typename Stream, typename Ch> +inline void PutN(Stream& stream, Ch c, size_t n) { + PutReserve(stream, n); + for (size_t i = 0; i < n; i++) + PutUnsafe(stream, c); +} + +/////////////////////////////////////////////////////////////////////////////// +// GenericStreamWrapper + +//! A Stream Wrapper +/*! \tThis string stream is a wrapper for any stream by just forwarding any + \treceived message to the origin stream. + \note implements Stream concept +*/ + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4702) // unreachable code +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +template <typename InputStream, typename Encoding = UTF8<> > +class GenericStreamWrapper { +public: + typedef typename Encoding::Ch Ch; + GenericStreamWrapper(InputStream& is): is_(is) {} + + Ch Peek() const { return is_.Peek(); } + Ch Take() { return is_.Take(); } + size_t Tell() { return is_.Tell(); } + Ch* PutBegin() { return is_.PutBegin(); } + void Put(Ch ch) { is_.Put(ch); } + void Flush() { is_.Flush(); } + size_t PutEnd(Ch* ch) { return is_.PutEnd(ch); } + + // wrapper for MemoryStream + const Ch* Peek4() const { return is_.Peek4(); } + + // wrapper for AutoUTFInputStream + UTFType GetType() const { return is_.GetType(); } + bool HasBOM() const { return is_.HasBOM(); } + +protected: + InputStream& is_; +}; + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +RAPIDJSON_DIAG_POP +#endif + +/////////////////////////////////////////////////////////////////////////////// +// StringStream + +//! Read-only string stream. +/*! \note implements Stream concept +*/ +template <typename Encoding> +struct GenericStringStream { + typedef typename Encoding::Ch Ch; + + GenericStringStream(const Ch *src) : src_(src), head_(src) {} + + Ch Peek() const { return *src_; } + Ch Take() { return *src_++; } + size_t Tell() const { return static_cast<size_t>(src_ - head_); } + + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + const Ch* src_; //!< Current read position. + const Ch* head_; //!< Original head of the string. +}; + +template <typename Encoding> +struct StreamTraits<GenericStringStream<Encoding> > { + enum { copyOptimization = 1 }; +}; + +//! String stream with UTF8 encoding. +typedef GenericStringStream<UTF8<> > StringStream; + +/////////////////////////////////////////////////////////////////////////////// +// InsituStringStream + +//! A read-write string stream. +/*! This string stream is particularly designed for in-situ parsing. + \note implements Stream concept +*/ +template <typename Encoding> +struct GenericInsituStringStream { + typedef typename Encoding::Ch Ch; + + GenericInsituStringStream(Ch *src) : src_(src), dst_(0), head_(src) {} + + // Read + Ch Peek() { return *src_; } + Ch Take() { return *src_++; } + size_t Tell() { return static_cast<size_t>(src_ - head_); } + + // Write + void Put(Ch c) { RAPIDJSON_ASSERT(dst_ != 0); *dst_++ = c; } + + Ch* PutBegin() { return dst_ = src_; } + size_t PutEnd(Ch* begin) { return static_cast<size_t>(dst_ - begin); } + void Flush() {} + + Ch* Push(size_t count) { Ch* begin = dst_; dst_ += count; return begin; } + void Pop(size_t count) { dst_ -= count; } + + Ch* src_; + Ch* dst_; + Ch* head_; +}; + +template <typename Encoding> +struct StreamTraits<GenericInsituStringStream<Encoding> > { + enum { copyOptimization = 1 }; +}; + +//! Insitu string stream with UTF8 encoding. +typedef GenericInsituStringStream<UTF8<> > InsituStringStream; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_STREAM_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/stringbuffer.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/stringbuffer.h new file mode 100644 index 00000000..82ad3ca6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/stringbuffer.h @@ -0,0 +1,121 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_STRINGBUFFER_H_ +#define RAPIDJSON_STRINGBUFFER_H_ + +#include "stream.h" +#include "internal/stack.h" + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS +#include <utility> // std::move +#endif + +#include "internal/stack.h" + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory output stream. +/*! + \tparam Encoding Encoding of the stream. + \tparam Allocator type for allocating memory buffer. + \note implements Stream concept +*/ +template <typename Encoding, typename Allocator = CrtAllocator> +class GenericStringBuffer { +public: + typedef typename Encoding::Ch Ch; + + GenericStringBuffer(Allocator* allocator = 0, size_t capacity = kDefaultCapacity) : stack_(allocator, capacity) {} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericStringBuffer(GenericStringBuffer&& rhs) : stack_(std::move(rhs.stack_)) {} + GenericStringBuffer& operator=(GenericStringBuffer&& rhs) { + if (&rhs != this) + stack_ = std::move(rhs.stack_); + return *this; + } +#endif + + void Put(Ch c) { *stack_.template Push<Ch>() = c; } + void PutUnsafe(Ch c) { *stack_.template PushUnsafe<Ch>() = c; } + void Flush() {} + + void Clear() { stack_.Clear(); } + void ShrinkToFit() { + // Push and pop a null terminator. This is safe. + *stack_.template Push<Ch>() = '\0'; + stack_.ShrinkToFit(); + stack_.template Pop<Ch>(1); + } + + void Reserve(size_t count) { stack_.template Reserve<Ch>(count); } + Ch* Push(size_t count) { return stack_.template Push<Ch>(count); } + Ch* PushUnsafe(size_t count) { return stack_.template PushUnsafe<Ch>(count); } + void Pop(size_t count) { stack_.template Pop<Ch>(count); } + + const Ch* GetString() const { + // Push and pop a null terminator. This is safe. + *stack_.template Push<Ch>() = '\0'; + stack_.template Pop<Ch>(1); + + return stack_.template Bottom<Ch>(); + } + + //! Get the size of string in bytes in the string buffer. + size_t GetSize() const { return stack_.GetSize(); } + + //! Get the length of string in Ch in the string buffer. + size_t GetLength() const { return stack_.GetSize() / sizeof(Ch); } + + static const size_t kDefaultCapacity = 256; + mutable internal::Stack<Allocator> stack_; + +private: + // Prohibit copy constructor & assignment operator. + GenericStringBuffer(const GenericStringBuffer&); + GenericStringBuffer& operator=(const GenericStringBuffer&); +}; + +//! String buffer with UTF8 encoding +typedef GenericStringBuffer<UTF8<> > StringBuffer; + +template<typename Encoding, typename Allocator> +inline void PutReserve(GenericStringBuffer<Encoding, Allocator>& stream, size_t count) { + stream.Reserve(count); +} + +template<typename Encoding, typename Allocator> +inline void PutUnsafe(GenericStringBuffer<Encoding, Allocator>& stream, typename Encoding::Ch c) { + stream.PutUnsafe(c); +} + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(GenericStringBuffer<UTF8<> >& stream, char c, size_t n) { + std::memset(stream.stack_.Push<char>(n), c, n * sizeof(c)); +} + +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_STRINGBUFFER_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/writer.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/writer.h new file mode 100644 index 00000000..8b389219 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rapidjson/writer.h @@ -0,0 +1,710 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_WRITER_H_ +#define RAPIDJSON_WRITER_H_ + +#include "stream.h" +#include "internal/clzll.h" +#include "internal/meta.h" +#include "internal/stack.h" +#include "internal/strfunc.h" +#include "internal/dtoa.h" +#include "internal/itoa.h" +#include "stringbuffer.h" +#include <new> // placement new + +#if defined(RAPIDJSON_SIMD) && defined(_MSC_VER) +#include <intrin.h> +#pragma intrinsic(_BitScanForward) +#endif +#ifdef RAPIDJSON_SSE42 +#include <nmmintrin.h> +#elif defined(RAPIDJSON_SSE2) +#include <emmintrin.h> +#elif defined(RAPIDJSON_NEON) +#include <arm_neon.h> +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(unreachable-code) +RAPIDJSON_DIAG_OFF(c++98-compat) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// WriteFlag + +/*! \def RAPIDJSON_WRITE_DEFAULT_FLAGS + \ingroup RAPIDJSON_CONFIG + \brief User-defined kWriteDefaultFlags definition. + + User can define this as any \c WriteFlag combinations. +*/ +#ifndef RAPIDJSON_WRITE_DEFAULT_FLAGS +#define RAPIDJSON_WRITE_DEFAULT_FLAGS kWriteNoFlags +#endif + +//! Combination of writeFlags +enum WriteFlag { + kWriteNoFlags = 0, //!< No flags are set. + kWriteValidateEncodingFlag = 1, //!< Validate encoding of JSON strings. + kWriteNanAndInfFlag = 2, //!< Allow writing of Infinity, -Infinity and NaN. + kWriteDefaultFlags = RAPIDJSON_WRITE_DEFAULT_FLAGS //!< Default write flags. Can be customized by defining RAPIDJSON_WRITE_DEFAULT_FLAGS +}; + +//! JSON writer +/*! Writer implements the concept Handler. + It generates JSON text by events to an output os. + + User may programmatically calls the functions of a writer to generate JSON text. + + On the other side, a writer can also be passed to objects that generates events, + + for example Reader::Parse() and Document::Accept(). + + \tparam OutputStream Type of output stream. + \tparam SourceEncoding Encoding of source string. + \tparam TargetEncoding Encoding of output stream. + \tparam StackAllocator Type of allocator for allocating memory of stack. + \note implements Handler concept +*/ +template<typename OutputStream, typename SourceEncoding = UTF8<>, typename TargetEncoding = UTF8<>, typename StackAllocator = CrtAllocator, unsigned writeFlags = kWriteDefaultFlags> +class Writer { +public: + typedef typename SourceEncoding::Ch Ch; + + static const int kDefaultMaxDecimalPlaces = 324; + + //! Constructor + /*! \param os Output stream. + \param stackAllocator User supplied allocator. If it is null, it will create a private one. + \param levelDepth Initial capacity of stack. + */ + explicit + Writer(OutputStream& os, StackAllocator* stackAllocator = 0, size_t levelDepth = kDefaultLevelDepth) : + os_(&os), level_stack_(stackAllocator, levelDepth * sizeof(Level)), maxDecimalPlaces_(kDefaultMaxDecimalPlaces), hasRoot_(false) {} + + explicit + Writer(StackAllocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth) : + os_(0), level_stack_(allocator, levelDepth * sizeof(Level)), maxDecimalPlaces_(kDefaultMaxDecimalPlaces), hasRoot_(false) {} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + Writer(Writer&& rhs) : + os_(rhs.os_), level_stack_(std::move(rhs.level_stack_)), maxDecimalPlaces_(rhs.maxDecimalPlaces_), hasRoot_(rhs.hasRoot_) { + rhs.os_ = 0; + } +#endif + + //! Reset the writer with a new stream. + /*! + This function reset the writer with a new stream and default settings, + in order to make a Writer object reusable for output multiple JSONs. + + \param os New output stream. + \code + Writer<OutputStream> writer(os1); + writer.StartObject(); + // ... + writer.EndObject(); + + writer.Reset(os2); + writer.StartObject(); + // ... + writer.EndObject(); + \endcode + */ + void Reset(OutputStream& os) { + os_ = &os; + hasRoot_ = false; + level_stack_.Clear(); + } + + //! Checks whether the output is a complete JSON. + /*! + A complete JSON has a complete root object or array. + */ + bool IsComplete() const { + return hasRoot_ && level_stack_.Empty(); + } + + int GetMaxDecimalPlaces() const { + return maxDecimalPlaces_; + } + + //! Sets the maximum number of decimal places for double output. + /*! + This setting truncates the output with specified number of decimal places. + + For example, + + \code + writer.SetMaxDecimalPlaces(3); + writer.StartArray(); + writer.Double(0.12345); // "0.123" + writer.Double(0.0001); // "0.0" + writer.Double(1.234567890123456e30); // "1.234567890123456e30" (do not truncate significand for positive exponent) + writer.Double(1.23e-4); // "0.0" (do truncate significand for negative exponent) + writer.EndArray(); + \endcode + + The default setting does not truncate any decimal places. You can restore to this setting by calling + \code + writer.SetMaxDecimalPlaces(Writer::kDefaultMaxDecimalPlaces); + \endcode + */ + void SetMaxDecimalPlaces(int maxDecimalPlaces) { + maxDecimalPlaces_ = maxDecimalPlaces; + } + + /*!@name Implementation of Handler + \see Handler + */ + //@{ + + bool Null() { Prefix(kNullType); return EndValue(WriteNull()); } + bool Bool(bool b) { Prefix(b ? kTrueType : kFalseType); return EndValue(WriteBool(b)); } + bool Int(int i) { Prefix(kNumberType); return EndValue(WriteInt(i)); } + bool Uint(unsigned u) { Prefix(kNumberType); return EndValue(WriteUint(u)); } + bool Int64(int64_t i64) { Prefix(kNumberType); return EndValue(WriteInt64(i64)); } + bool Uint64(uint64_t u64) { Prefix(kNumberType); return EndValue(WriteUint64(u64)); } + + //! Writes the given \c double value to the stream + /*! + \param d The value to be written. + \return Whether it is succeed. + */ + bool Double(double d) { Prefix(kNumberType); return EndValue(WriteDouble(d)); } + + bool RawNumber(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + Prefix(kNumberType); + return EndValue(WriteString(str, length)); + } + + bool String(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + Prefix(kStringType); + return EndValue(WriteString(str, length)); + } + +#if RAPIDJSON_HAS_STDSTRING + bool String(const std::basic_string<Ch>& str) { + return String(str.data(), SizeType(str.size())); + } +#endif + + bool StartObject() { + Prefix(kObjectType); + new (level_stack_.template Push<Level>()) Level(false); + return WriteStartObject(); + } + + bool Key(const Ch* str, SizeType length, bool copy = false) { return String(str, length, copy); } + +#if RAPIDJSON_HAS_STDSTRING + bool Key(const std::basic_string<Ch>& str) + { + return Key(str.data(), SizeType(str.size())); + } +#endif + + bool EndObject(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); // not inside an Object + RAPIDJSON_ASSERT(!level_stack_.template Top<Level>()->inArray); // currently inside an Array, not Object + RAPIDJSON_ASSERT(0 == level_stack_.template Top<Level>()->valueCount % 2); // Object has a Key without a Value + level_stack_.template Pop<Level>(1); + return EndValue(WriteEndObject()); + } + + bool StartArray() { + Prefix(kArrayType); + new (level_stack_.template Push<Level>()) Level(true); + return WriteStartArray(); + } + + bool EndArray(SizeType elementCount = 0) { + (void)elementCount; + RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); + RAPIDJSON_ASSERT(level_stack_.template Top<Level>()->inArray); + level_stack_.template Pop<Level>(1); + return EndValue(WriteEndArray()); + } + //@} + + /*! @name Convenience extensions */ + //@{ + + //! Simpler but slower overload. + bool String(const Ch* const& str) { return String(str, internal::StrLen(str)); } + bool Key(const Ch* const& str) { return Key(str, internal::StrLen(str)); } + + //@} + + //! Write a raw JSON value. + /*! + For user to write a stringified JSON as a value. + + \param json A well-formed JSON value. It should not contain null character within [0, length - 1] range. + \param length Length of the json. + \param type Type of the root of json. + */ + bool RawValue(const Ch* json, size_t length, Type type) { + RAPIDJSON_ASSERT(json != 0); + Prefix(type); + return EndValue(WriteRawValue(json, length)); + } + + //! Flush the output stream. + /*! + Allows the user to flush the output stream immediately. + */ + void Flush() { + os_->Flush(); + } + + static const size_t kDefaultLevelDepth = 32; + +protected: + //! Information for each nested level + struct Level { + Level(bool inArray_) : valueCount(0), inArray(inArray_) {} + size_t valueCount; //!< number of values in this level + bool inArray; //!< true if in array, otherwise in object + }; + + bool WriteNull() { + PutReserve(*os_, 4); + PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'u'); PutUnsafe(*os_, 'l'); PutUnsafe(*os_, 'l'); return true; + } + + bool WriteBool(bool b) { + if (b) { + PutReserve(*os_, 4); + PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'r'); PutUnsafe(*os_, 'u'); PutUnsafe(*os_, 'e'); + } + else { + PutReserve(*os_, 5); + PutUnsafe(*os_, 'f'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'l'); PutUnsafe(*os_, 's'); PutUnsafe(*os_, 'e'); + } + return true; + } + + bool WriteInt(int i) { + char buffer[11]; + const char* end = internal::i32toa(i, buffer); + PutReserve(*os_, static_cast<size_t>(end - buffer)); + for (const char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast<typename OutputStream::Ch>(*p)); + return true; + } + + bool WriteUint(unsigned u) { + char buffer[10]; + const char* end = internal::u32toa(u, buffer); + PutReserve(*os_, static_cast<size_t>(end - buffer)); + for (const char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast<typename OutputStream::Ch>(*p)); + return true; + } + + bool WriteInt64(int64_t i64) { + char buffer[21]; + const char* end = internal::i64toa(i64, buffer); + PutReserve(*os_, static_cast<size_t>(end - buffer)); + for (const char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast<typename OutputStream::Ch>(*p)); + return true; + } + + bool WriteUint64(uint64_t u64) { + char buffer[20]; + char* end = internal::u64toa(u64, buffer); + PutReserve(*os_, static_cast<size_t>(end - buffer)); + for (char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast<typename OutputStream::Ch>(*p)); + return true; + } + + bool WriteDouble(double d) { + if (internal::Double(d).IsNanOrInf()) { + if (!(writeFlags & kWriteNanAndInfFlag)) + return false; + if (internal::Double(d).IsNan()) { + PutReserve(*os_, 3); + PutUnsafe(*os_, 'N'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'N'); + return true; + } + if (internal::Double(d).Sign()) { + PutReserve(*os_, 9); + PutUnsafe(*os_, '-'); + } + else + PutReserve(*os_, 8); + PutUnsafe(*os_, 'I'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'f'); + PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'y'); + return true; + } + + char buffer[25]; + char* end = internal::dtoa(d, buffer, maxDecimalPlaces_); + PutReserve(*os_, static_cast<size_t>(end - buffer)); + for (char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast<typename OutputStream::Ch>(*p)); + return true; + } + + bool WriteString(const Ch* str, SizeType length) { + static const typename OutputStream::Ch hexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + static const char escape[256] = { +#define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't', 'n', 'u', 'f', 'r', 'u', 'u', // 00 + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', // 10 + 0, 0, '"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20 + Z16, Z16, // 30~4F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, // 50 + Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 // 60~FF +#undef Z16 + }; + + if (TargetEncoding::supportUnicode) + PutReserve(*os_, 2 + length * 6); // "\uxxxx..." + else + PutReserve(*os_, 2 + length * 12); // "\uxxxx\uyyyy..." + + PutUnsafe(*os_, '\"'); + GenericStringStream<SourceEncoding> is(str); + while (ScanWriteUnescapedString(is, length)) { + const Ch c = is.Peek(); + if (!TargetEncoding::supportUnicode && static_cast<unsigned>(c) >= 0x80) { + // Unicode escaping + unsigned codepoint; + if (RAPIDJSON_UNLIKELY(!SourceEncoding::Decode(is, &codepoint))) + return false; + PutUnsafe(*os_, '\\'); + PutUnsafe(*os_, 'u'); + if (codepoint <= 0xD7FF || (codepoint >= 0xE000 && codepoint <= 0xFFFF)) { + PutUnsafe(*os_, hexDigits[(codepoint >> 12) & 15]); + PutUnsafe(*os_, hexDigits[(codepoint >> 8) & 15]); + PutUnsafe(*os_, hexDigits[(codepoint >> 4) & 15]); + PutUnsafe(*os_, hexDigits[(codepoint ) & 15]); + } + else { + RAPIDJSON_ASSERT(codepoint >= 0x010000 && codepoint <= 0x10FFFF); + // Surrogate pair + unsigned s = codepoint - 0x010000; + unsigned lead = (s >> 10) + 0xD800; + unsigned trail = (s & 0x3FF) + 0xDC00; + PutUnsafe(*os_, hexDigits[(lead >> 12) & 15]); + PutUnsafe(*os_, hexDigits[(lead >> 8) & 15]); + PutUnsafe(*os_, hexDigits[(lead >> 4) & 15]); + PutUnsafe(*os_, hexDigits[(lead ) & 15]); + PutUnsafe(*os_, '\\'); + PutUnsafe(*os_, 'u'); + PutUnsafe(*os_, hexDigits[(trail >> 12) & 15]); + PutUnsafe(*os_, hexDigits[(trail >> 8) & 15]); + PutUnsafe(*os_, hexDigits[(trail >> 4) & 15]); + PutUnsafe(*os_, hexDigits[(trail ) & 15]); + } + } + else if ((sizeof(Ch) == 1 || static_cast<unsigned>(c) < 256) && RAPIDJSON_UNLIKELY(escape[static_cast<unsigned char>(c)])) { + is.Take(); + PutUnsafe(*os_, '\\'); + PutUnsafe(*os_, static_cast<typename OutputStream::Ch>(escape[static_cast<unsigned char>(c)])); + if (escape[static_cast<unsigned char>(c)] == 'u') { + PutUnsafe(*os_, '0'); + PutUnsafe(*os_, '0'); + PutUnsafe(*os_, hexDigits[static_cast<unsigned char>(c) >> 4]); + PutUnsafe(*os_, hexDigits[static_cast<unsigned char>(c) & 0xF]); + } + } + else if (RAPIDJSON_UNLIKELY(!(writeFlags & kWriteValidateEncodingFlag ? + Transcoder<SourceEncoding, TargetEncoding>::Validate(is, *os_) : + Transcoder<SourceEncoding, TargetEncoding>::TranscodeUnsafe(is, *os_)))) + return false; + } + PutUnsafe(*os_, '\"'); + return true; + } + + bool ScanWriteUnescapedString(GenericStringStream<SourceEncoding>& is, size_t length) { + return RAPIDJSON_LIKELY(is.Tell() < length); + } + + bool WriteStartObject() { os_->Put('{'); return true; } + bool WriteEndObject() { os_->Put('}'); return true; } + bool WriteStartArray() { os_->Put('['); return true; } + bool WriteEndArray() { os_->Put(']'); return true; } + + bool WriteRawValue(const Ch* json, size_t length) { + PutReserve(*os_, length); + GenericStringStream<SourceEncoding> is(json); + while (RAPIDJSON_LIKELY(is.Tell() < length)) { + RAPIDJSON_ASSERT(is.Peek() != '\0'); + if (RAPIDJSON_UNLIKELY(!(writeFlags & kWriteValidateEncodingFlag ? + Transcoder<SourceEncoding, TargetEncoding>::Validate(is, *os_) : + Transcoder<SourceEncoding, TargetEncoding>::TranscodeUnsafe(is, *os_)))) + return false; + } + return true; + } + + void Prefix(Type type) { + (void)type; + if (RAPIDJSON_LIKELY(level_stack_.GetSize() != 0)) { // this value is not at root + Level* level = level_stack_.template Top<Level>(); + if (level->valueCount > 0) { + if (level->inArray) + os_->Put(','); // add comma if it is not the first element in array + else // in object + os_->Put((level->valueCount % 2 == 0) ? ',' : ':'); + } + if (!level->inArray && level->valueCount % 2 == 0) + RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name + level->valueCount++; + } + else { + RAPIDJSON_ASSERT(!hasRoot_); // Should only has one and only one root. + hasRoot_ = true; + } + } + + // Flush the value if it is the top level one. + bool EndValue(bool ret) { + if (RAPIDJSON_UNLIKELY(level_stack_.Empty())) // end of json text + Flush(); + return ret; + } + + OutputStream* os_; + internal::Stack<StackAllocator> level_stack_; + int maxDecimalPlaces_; + bool hasRoot_; + +private: + // Prohibit copy constructor & assignment operator. + Writer(const Writer&); + Writer& operator=(const Writer&); +}; + +// Full specialization for StringStream to prevent memory copying + +template<> +inline bool Writer<StringBuffer>::WriteInt(int i) { + char *buffer = os_->Push(11); + const char* end = internal::i32toa(i, buffer); + os_->Pop(static_cast<size_t>(11 - (end - buffer))); + return true; +} + +template<> +inline bool Writer<StringBuffer>::WriteUint(unsigned u) { + char *buffer = os_->Push(10); + const char* end = internal::u32toa(u, buffer); + os_->Pop(static_cast<size_t>(10 - (end - buffer))); + return true; +} + +template<> +inline bool Writer<StringBuffer>::WriteInt64(int64_t i64) { + char *buffer = os_->Push(21); + const char* end = internal::i64toa(i64, buffer); + os_->Pop(static_cast<size_t>(21 - (end - buffer))); + return true; +} + +template<> +inline bool Writer<StringBuffer>::WriteUint64(uint64_t u) { + char *buffer = os_->Push(20); + const char* end = internal::u64toa(u, buffer); + os_->Pop(static_cast<size_t>(20 - (end - buffer))); + return true; +} + +template<> +inline bool Writer<StringBuffer>::WriteDouble(double d) { + if (internal::Double(d).IsNanOrInf()) { + // Note: This code path can only be reached if (RAPIDJSON_WRITE_DEFAULT_FLAGS & kWriteNanAndInfFlag). + if (!(kWriteDefaultFlags & kWriteNanAndInfFlag)) + return false; + if (internal::Double(d).IsNan()) { + PutReserve(*os_, 3); + PutUnsafe(*os_, 'N'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'N'); + return true; + } + if (internal::Double(d).Sign()) { + PutReserve(*os_, 9); + PutUnsafe(*os_, '-'); + } + else + PutReserve(*os_, 8); + PutUnsafe(*os_, 'I'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'f'); + PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'y'); + return true; + } + + char *buffer = os_->Push(25); + char* end = internal::dtoa(d, buffer, maxDecimalPlaces_); + os_->Pop(static_cast<size_t>(25 - (end - buffer))); + return true; +} + +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) +template<> +inline bool Writer<StringBuffer>::ScanWriteUnescapedString(StringStream& is, size_t length) { + if (length < 16) + return RAPIDJSON_LIKELY(is.Tell() < length); + + if (!RAPIDJSON_LIKELY(is.Tell() < length)) + return false; + + const char* p = is.src_; + const char* end = is.head_ + length; + const char* nextAligned = reinterpret_cast<const char*>((reinterpret_cast<size_t>(p) + 15) & static_cast<size_t>(~15)); + const char* endAligned = reinterpret_cast<const char*>(reinterpret_cast<size_t>(end) & static_cast<size_t>(~15)); + if (nextAligned > end) + return true; + + while (p != nextAligned) + if (*p < 0x20 || *p == '\"' || *p == '\\') { + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); + } + else + os_->PutUnsafe(*p++); + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast<const __m128i *>(&space[0])); + + for (; p != endAligned; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast<const __m128i *>(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast<unsigned short>(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + SizeType len; +#ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + len = offset; +#else + len = static_cast<SizeType>(__builtin_ffs(r) - 1); +#endif + char* q = reinterpret_cast<char*>(os_->PushUnsafe(len)); + for (size_t i = 0; i < len; i++) + q[i] = p[i]; + + p += len; + break; + } + _mm_storeu_si128(reinterpret_cast<__m128i *>(os_->PushUnsafe(16)), s); + } + + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); +} +#elif defined(RAPIDJSON_NEON) +template<> +inline bool Writer<StringBuffer>::ScanWriteUnescapedString(StringStream& is, size_t length) { + if (length < 16) + return RAPIDJSON_LIKELY(is.Tell() < length); + + if (!RAPIDJSON_LIKELY(is.Tell() < length)) + return false; + + const char* p = is.src_; + const char* end = is.head_ + length; + const char* nextAligned = reinterpret_cast<const char*>((reinterpret_cast<size_t>(p) + 15) & static_cast<size_t>(~15)); + const char* endAligned = reinterpret_cast<const char*>(reinterpret_cast<size_t>(end) & static_cast<size_t>(~15)); + if (nextAligned > end) + return true; + + while (p != nextAligned) + if (*p < 0x20 || *p == '\"' || *p == '\\') { + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); + } + else + os_->PutUnsafe(*p++); + + // The rest of string using SIMD + const uint8x16_t s0 = vmovq_n_u8('"'); + const uint8x16_t s1 = vmovq_n_u8('\\'); + const uint8x16_t s2 = vmovq_n_u8('\b'); + const uint8x16_t s3 = vmovq_n_u8(32); + + for (; p != endAligned; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast<const uint8_t *>(p)); + uint8x16_t x = vceqq_u8(s, s0); + x = vorrq_u8(x, vceqq_u8(s, s1)); + x = vorrq_u8(x, vceqq_u8(s, s2)); + x = vorrq_u8(x, vcltq_u8(s, s3)); + + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(vreinterpretq_u64_u8(x), 0); // extract + uint64_t high = vgetq_lane_u64(vreinterpretq_u64_u8(x), 1); // extract + + SizeType len = 0; + bool escaped = false; + if (low == 0) { + if (high != 0) { + uint32_t lz = internal::clzll(high); + len = 8 + (lz >> 3); + escaped = true; + } + } else { + uint32_t lz = internal::clzll(low); + len = lz >> 3; + escaped = true; + } + if (RAPIDJSON_UNLIKELY(escaped)) { // some of characters is escaped + char* q = reinterpret_cast<char*>(os_->PushUnsafe(len)); + for (size_t i = 0; i < len; i++) + q[i] = p[i]; + + p += len; + break; + } + vst1q_u8(reinterpret_cast<uint8_t *>(os_->PushUnsafe(16)), s); + } + + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); +} +#endif // RAPIDJSON_NEON + +RAPIDJSON_NAMESPACE_END + +#if defined(_MSC_VER) || defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/regulator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/regulator.hpp new file mode 100644 index 00000000..c4ae04df --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/regulator.hpp @@ -0,0 +1,21 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/regulator/regulator_types.hpp> +#include <stratosphere/regulator/regulator_api.hpp> +#include <stratosphere/regulator/regulator_session_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/regulator/regulator_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/regulator/regulator_api.hpp new file mode 100644 index 00000000..1dbc151a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/regulator/regulator_api.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/regulator/regulator_types.hpp> + +namespace ams::regulator { + + void Initialize(); + void Finalize(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/regulator/regulator_session_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/regulator/regulator_session_api.hpp new file mode 100644 index 00000000..0d729f78 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/regulator/regulator_session_api.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/regulator/regulator_types.hpp> + +namespace ams::regulator { + + struct RegulatorSession { + void *_session; + }; + + Result OpenSession(RegulatorSession *out, DeviceCode device_code); + void CloseSession(RegulatorSession *session); + + bool GetVoltageEnabled(RegulatorSession *session); + Result SetVoltageEnabled(RegulatorSession *session, bool enabled); + + Result SetVoltageValue(RegulatorSession *session, u32 micro_volts); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/regulator/regulator_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/regulator/regulator_types.hpp new file mode 100644 index 00000000..b1ed7709 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/regulator/regulator_types.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::regulator { + + /* ... */ + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ro.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ro.hpp new file mode 100644 index 00000000..ec0b48ab --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ro.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/ro/ro_types.hpp> +#include <stratosphere/ro/impl/ro_ro_interface.hpp> +#include <stratosphere/ro/impl/ro_debug_monitor_interface.hpp> +#include <stratosphere/ro/impl/ro_ro_exception_info.hpp> + +#include <stratosphere/rocrt/rocrt.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ro/impl/ro_debug_monitor_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ro/impl/ro_debug_monitor_interface.hpp new file mode 100644 index 00000000..822e271a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ro/impl/ro_debug_monitor_interface.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/ro/ro_types.hpp> +#include <stratosphere/ldr/ldr_types.hpp> +#include <stratosphere/sf.hpp> + +#define AMS_RO_I_DEBUG_MONITOR_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GetProcessModuleInfo, (sf::Out<u32> out_count, const sf::OutArray<ldr::ModuleInfo> &out_infos, os::ProcessId process_id), (out_count, out_infos, process_id)) + +AMS_SF_DEFINE_INTERFACE(ams::ro::impl, IDebugMonitorInterface, AMS_RO_I_DEBUG_MONITOR_INTERFACE_INTERFACE_INFO, 0xBBA11B0A) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ro/impl/ro_ro_exception_info.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ro/impl/ro_ro_exception_info.hpp new file mode 100644 index 00000000..459d1ce8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ro/impl/ro_ro_exception_info.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::ro::impl { + + struct ExceptionInfo { + uintptr_t module_address; + size_t module_size; + uintptr_t info_offset; + size_t info_size; + }; + + bool GetExceptionInfo(ExceptionInfo *out, uintptr_t pc); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ro/impl/ro_ro_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ro/impl/ro_ro_interface.hpp new file mode 100644 index 00000000..8a993f65 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ro/impl/ro_ro_interface.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/ro/ro_types.hpp> +#include <stratosphere/sf.hpp> + +#define AMS_RO_I_RO_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, MapManualLoadModuleMemory, (sf::Out<u64> out_load_address, const sf::ClientProcessId &client_pid, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size), (out_load_address, client_pid, nro_address, nro_size, bss_address, bss_size)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, UnmapManualLoadModuleMemory, (const sf::ClientProcessId &client_pid, u64 nro_address), (client_pid, nro_address)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, RegisterModuleInfo, (const sf::ClientProcessId &client_pid, u64 nrr_address, u64 nrr_size), (client_pid, nrr_address, nrr_size)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, UnregisterModuleInfo, (const sf::ClientProcessId &client_pid, u64 nrr_address), (client_pid, nrr_address)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, RegisterProcessHandle, (const sf::ClientProcessId &client_pid, sf::CopyHandle &&process_h), (client_pid, std::move(process_h))) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, RegisterProcessModuleInfo, (const sf::ClientProcessId &client_pid, u64 nrr_address, u64 nrr_size, sf::CopyHandle &&process_h), (client_pid, nrr_address, nrr_size, std::move(process_h)), hos::Version_7_0_0) + +AMS_SF_DEFINE_INTERFACE(ams::ro::impl, IRoInterface, AMS_RO_I_RO_INTERFACE_INTERFACE_INFO, 0xA52C55A9) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ro/ro_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ro/ro_types.hpp new file mode 100644 index 00000000..4a1ce268 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/ro/ro_types.hpp @@ -0,0 +1,212 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> + +namespace ams::ro { + + enum NrrKind : u8 { + NrrKind_User = 0, + NrrKind_JitPlugin = 1, + + NrrKind_Count, + }; + + static constexpr size_t ModuleIdSize = 0x20; + struct ModuleId { + u8 data[ModuleIdSize]; + }; + static_assert(sizeof(ModuleId) == ModuleIdSize); + + struct NrrCertification { + static constexpr size_t RsaKeySize = 0x100; + static constexpr size_t SignedSize = 0x120; + + u64 program_id_mask; + u64 program_id_pattern; + u8 reserved_10[0x10]; + u8 modulus[RsaKeySize]; + u8 signature[RsaKeySize]; + }; + static_assert(sizeof(NrrCertification) == NrrCertification::RsaKeySize + NrrCertification::SignedSize); + + class NrrHeader { + public: + static constexpr u32 Magic = util::FourCC<'N','R','R','0'>::Code; + private: + u32 m_magic; + u32 m_key_generation; + u8 m_reserved_08[0x08]; + NrrCertification m_certification; + u8 m_signature[0x100]; + ncm::ProgramId m_program_id; + u32 m_size; + u8 m_nrr_kind; /* 7.0.0+ */ + u8 m_reserved_33D[3]; + u32 m_hashes_offset; + u32 m_num_hashes; + u8 m_reserved_348[8]; + public: + bool IsMagicValid() const { + return m_magic == Magic; + } + + bool IsProgramIdValid() const { + return (m_program_id.value & m_certification.program_id_mask) == m_certification.program_id_pattern; + } + + NrrKind GetNrrKind() const { + const NrrKind kind = static_cast<NrrKind>(m_nrr_kind); + AMS_ABORT_UNLESS(kind < NrrKind_Count); + return kind; + } + + ncm::ProgramId GetProgramId() const { + return m_program_id; + } + + u32 GetSize() const { + return m_size; + } + + u32 GetNumHashes() const { + return m_num_hashes; + } + + size_t GetHashesOffset() const { + return m_hashes_offset; + } + + uintptr_t GetHashes() const { + return reinterpret_cast<uintptr_t>(this) + this->GetHashesOffset(); + } + + u32 GetKeyGeneration() const { + return m_key_generation; + } + + const u8 *GetCertificationSignature() const { + return m_certification.signature; + } + + const u8 *GetCertificationSignedArea() const { + return reinterpret_cast<const u8 *>(std::addressof(m_certification)); + } + + const u8 *GetCertificationModulus() const { + return m_certification.modulus; + } + + const u8 *GetSignature() const { + return m_signature; + } + + const u8 *GetSignedArea() const { + return reinterpret_cast<const u8 *>(std::addressof(m_program_id)); + } + + size_t GetSignedAreaSize() const { + return m_size - GetSignedAreaOffset(); + } + + static constexpr size_t GetSignedAreaOffset(); + }; + static_assert(sizeof(NrrHeader) == 0x350, "NrrHeader definition!"); + + constexpr size_t NrrHeader::GetSignedAreaOffset() { + return AMS_OFFSETOF(NrrHeader, m_program_id); + } + + class NroHeader { + public: + static constexpr u32 Magic = util::FourCC<'N','R','O','0'>::Code; + static constexpr u32 FlagAlignedHeader = 1; + private: + u32 m_entrypoint_insn; + u32 m_mod_offset; + u8 m_reserved_08[0x8]; + u32 m_magic; + u8 m_version; + u32 m_size; + u32 m_flags; + u32 m_text_offset; + u32 m_text_size; + u32 m_ro_offset; + u32 m_ro_size; + u32 m_rw_offset; + u32 m_rw_size; + u32 m_bss_size; + u8 m_reserved_3C[0x4]; + ModuleId m_module_id; + u8 m_reserved_60[0x20]; + public: + bool IsMagicValid() const { + return m_magic == Magic; + } + + u32 GetVersion() const { + return m_version; + } + + u32 GetSize() const { + return m_size; + } + + u32 GetFlags() const { + return m_flags; + } + + bool IsAlignedHeader() const { + return m_flags & FlagAlignedHeader; + } + + u32 GetTextOffset() const { + return m_text_offset; + } + + u32 GetTextSize() const { + return m_text_size; + } + + u32 GetRoOffset() const { + return m_ro_offset; + } + + u32 GetRoSize() const { + return m_ro_size; + } + + u32 GetRwOffset() const { + return m_rw_offset; + } + + u32 GetRwSize() const { + return m_rw_size; + } + + u32 GetBssSize() const { + return m_bss_size; + } + + const ModuleId *GetModuleId() const { + return std::addressof(m_module_id); + } + }; + static_assert(sizeof(NroHeader) == 0x80, "NroHeader definition!"); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rocrt/rocrt.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rocrt/rocrt.hpp new file mode 100644 index 00000000..6a4c0ce8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/rocrt/rocrt.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::rocrt { + + constexpr inline const u32 ModuleHeaderVersion = util::FourCC<'M','O','D','0'>::Code; + + struct ModuleHeader { + u32 signature; + u32 dynamic_offset; + u32 bss_start_offset; + u32 bss_end_offset; + u32 exception_info_start_offset; + u32 exception_info_end_offset; + u32 module_offset; + }; + + struct ModuleHeaderLocation { + u32 pad; + u32 header_offset; + }; + + constexpr inline u32 CheckModuleHeaderSignature(const ModuleHeader *header) { + if (header->signature == ModuleHeaderVersion) { + return header->signature; + } else { + return 0; + } + } + + inline ModuleHeader *GetModuleHeader(const ModuleHeaderLocation *loc) { + return reinterpret_cast<ModuleHeader *>(reinterpret_cast<uintptr_t>(loc) + loc->header_offset); + } + + inline uintptr_t GetDynamicOffset(const ModuleHeader *header, const ModuleHeaderLocation *loc) { + return reinterpret_cast<uintptr_t>(loc) + loc->header_offset + header->dynamic_offset; + } + + + inline uintptr_t GetBssStartAddress(const ModuleHeader *header, const ModuleHeaderLocation *loc) { + return reinterpret_cast<uintptr_t>(loc) + loc->header_offset + header->bss_start_offset; + } + + inline uintptr_t GetBssEndAddress(const ModuleHeader *header, const ModuleHeaderLocation *loc) { + return reinterpret_cast<uintptr_t>(loc) + loc->header_offset + header->bss_end_offset; + } + + inline uintptr_t GetModuleOffset(const ModuleHeader *header, const ModuleHeaderLocation *loc) { + return reinterpret_cast<uintptr_t>(loc) + loc->header_offset + header->module_offset; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/scs.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/scs.hpp new file mode 100644 index 00000000..7eaff730 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/scs.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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <stratosphere/scs/scs_command_processor.hpp> +#include <stratosphere/scs/scs_shell_server.hpp> + +#include <stratosphere/scs/scs_shell.hpp> +#include <stratosphere/scs/scs_tenv.hpp> + +#include <stratosphere/scs/scs_server_manager.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/scs/scs_command_processor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/scs/scs_command_processor.hpp new file mode 100644 index 00000000..729cc2b3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/scs/scs_command_processor.hpp @@ -0,0 +1,96 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::scs { + + struct alignas(alignof(u32)) CommandHeader { + u64 id __attribute__((packed)); + u32 command; + u32 body_size; + }; + static_assert(sizeof(CommandHeader) == 0x10); + static_assert(alignof(CommandHeader) == alignof(u32)); + + struct alignas(alignof(u32)) ResponseHeader { + u64 id __attribute__((packed)); + u32 response; + u32 body_size; + }; + static_assert(sizeof(ResponseHeader) == 0x10); + static_assert(alignof(ResponseHeader) == alignof(u32)); + + class CommandProcessor { + protected: + enum Command { + Command_None = 0, + Command_LaunchProgramFromHost = 1, + Command_TerminateProcesses = 2, + Command_GetFirmwareVersion = 3, + Command_Reboot = 4, + Command_SetSafeMode = 5, + Command_RegisterTenvDefinitionFilePath = 6, + Command_TerminateApplication = 7, + Command_Shutdown = 8, + Command_SubscribeProcessEvent = 9, + Command_GetTitleName = 10, + Command_ControlVirtualTemperature = 11, + Command_LaunchInstalledApplication = 12, + Command_LaunchGameCardApplication = 13, + Command_LaunchInstalledSystemProcess = 14, + Command_TakeScreenShot = 15, + Command_TakeForegroundScreenShot = 16, + Command_SimulateGameCardDetection = 17, + Command_SimulateSdCardDetection = 18, + Command_DumpRunningApplication = 19, + }; + + enum Response { + Response_None = 0, + Response_Success = 1, + Response_Error = 2, + Response_ProgramExited = 3, + Response_FirmwareVersion = 4, + Response_JitDebug = 5, + Response_ProgramLaunched = 6, + Response_TitleName = 7, + Response_ScreenShot = 8, + }; + public: + constexpr CommandProcessor() = default; + + void Initialize(); + public: + virtual bool ProcessCommand(const CommandHeader &header, const u8 *body, s32 socket); + protected: + static std::scoped_lock<os::SdkMutex> MakeSendGuardBlock(); + static void Send(s32 socket, const void *data, size_t size); + + static void SendSuccess(s32 socket, const CommandHeader &header); + static void SendErrorResult(s32 socket, const CommandHeader &header, Result result); + private: + static void SendErrorResult(s32 socket, u64 id, Result result); + static void SendExited(s32 socket, u64 id, u64 process_id); + static void SendJitDebug(s32 socket, u64 id); + static void SendLaunched(s32 socket, u64 id, u64 process_id); + + static void OnProcessStart(u64 id, s32 socket, os::ProcessId process_id); + static void OnProcessExit(u64 id, s32 socket, os::ProcessId process_id); + static void OnProcessJitDebug(u64 id, s32 socket, os::ProcessId process_id); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/scs/scs_server_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/scs/scs_server_manager.hpp new file mode 100644 index 00000000..a57e88e6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/scs/scs_server_manager.hpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> + +namespace ams::scs { + + enum Port { + Port_HtcTenv, + Port_Count, + }; + + constexpr inline int SessionCount[Port_Count] = { + 6, + }; + + constexpr inline auto MaxSessions = [] { + auto total = 0; + for (const auto sessions : SessionCount) { + total += sessions; + } + return total; + }(); + + struct ServerOptions { + static constexpr size_t PointerBufferSize = 0; + static constexpr size_t MaxDomains = 6; + static constexpr size_t MaxDomainObjects = 16; + static constexpr bool CanDeferInvokeRequest = false; + static constexpr bool CanManageMitmServers = false; + }; + + class ServerManager final : public sf::hipc::ServerManager<Port_Count, ServerOptions, MaxSessions> { + /* ... */ + }; + + ServerManager *GetServerManager(); + void StartServer(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/scs/scs_shell.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/scs/scs_shell.hpp new file mode 100644 index 00000000..7a601353 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/scs/scs_shell.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> + +namespace ams::scs { + + using ProcessEventHandler = void(*)(u64 id, s32 socket, os::ProcessId process_id); + + void InitializeShell(); + + void RegisterCommonProcessEventHandler(ProcessEventHandler on_start, ProcessEventHandler on_exit, ProcessEventHandler on_jit_debug); + + Result RegisterSocket(s32 socket, u64 id); + void UnregisterSocket(s32 socket); + + Result LaunchProgram(os::ProcessId *out, const ncm::ProgramLocation &loc, const void *args, size_t args_size, u32 process_flags); + + inline Result LaunchProgram(os::ProcessId *out, ncm::ProgramId program_id, const void *args, size_t args_size, u32 process_flags) { + R_RETURN(LaunchProgram(out, ncm::ProgramLocation::Make(program_id, ncm::StorageId::BuiltInSystem), args, args_size, process_flags)); + } + + Result SubscribeProcessEvent(s32 socket, bool is_register, u64 id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/scs/scs_shell_server.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/scs/scs_shell_server.hpp new file mode 100644 index 00000000..e6f2b000 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/scs/scs_shell_server.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/htcs.hpp> +#include <stratosphere/scs/scs_command_processor.hpp> + +namespace ams::scs { + + class ShellServer { + private: + htcs::HtcsPortName m_port_name; + os::ThreadType m_thread; + u8 m_buffer[64_KB]; + CommandProcessor *m_command_processor; + private: + static void ThreadEntry(void *arg) { reinterpret_cast<ShellServer *>(arg)->DoShellServer(); } + + void DoShellServer(); + public: + constexpr ShellServer() = default; + public: + void Initialize(const char *port_name, void *stack, size_t stack_size, CommandProcessor *command_processor); + void Start(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/scs/scs_tenv.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/scs/scs_tenv.hpp new file mode 100644 index 00000000..8eb2b90c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/scs/scs_tenv.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::scs { + + void InitializeTenvServiceManager(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings.hpp new file mode 100644 index 00000000..4bc62878 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/settings/settings_types.hpp> +#include <stratosphere/settings/settings_fwdbg_types.hpp> +#include <stratosphere/settings/settings_fwdbg_api.hpp> +#include <stratosphere/settings/factory/settings_serial_number.hpp> +#include <stratosphere/settings/factory/settings_configuration_id.hpp> +#include <stratosphere/settings/factory/settings_device_certificate.hpp> +#include <stratosphere/settings/system/settings_error_report.hpp> +#include <stratosphere/settings/system/settings_firmware_version.hpp> +#include <stratosphere/settings/system/settings_platform_region.hpp> +#include <stratosphere/settings/system/settings_product_model.hpp> +#include <stratosphere/settings/system/settings_region.hpp> +#include <stratosphere/settings/system/settings_serial_number.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/factory/settings_configuration_id.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/factory/settings_configuration_id.hpp new file mode 100644 index 00000000..74795207 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/factory/settings_configuration_id.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::settings::factory { + + struct ConfigurationId1 { + char str[30]; + }; + static_assert(sizeof(ConfigurationId1) == 30); + static_assert(util::is_pod<ConfigurationId1>::value); + + void GetConfigurationId1(ConfigurationId1 *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/factory/settings_device_certificate.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/factory/settings_device_certificate.hpp new file mode 100644 index 00000000..6ef3de5d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/factory/settings_device_certificate.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::settings::factory { + + struct EccP256DeviceCertificate { + u8 data[0x180]; + }; + static_assert(sizeof(EccP256DeviceCertificate) == 0x180); + static_assert(util::is_pod<EccP256DeviceCertificate>::value); + + struct EccB233DeviceCertificate { + u8 data[0x180]; + }; + static_assert(sizeof(EccB233DeviceCertificate) == 0x180); + static_assert(util::is_pod<EccB233DeviceCertificate>::value); + + struct Rsa2048DeviceCertificate { + u8 data[0x240]; + }; + static_assert(sizeof(Rsa2048DeviceCertificate) == 0x240); + static_assert(util::is_pod<Rsa2048DeviceCertificate>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/factory/settings_serial_number.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/factory/settings_serial_number.hpp new file mode 100644 index 00000000..cac72f06 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/factory/settings_serial_number.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::settings::factory { + + struct SerialNumber { + char str[0x18]; + }; + static_assert(sizeof(SerialNumber) == 0x18); + static_assert(util::is_pod<SerialNumber>::value); + + Result GetSerialNumber(SerialNumber *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/settings_fwdbg_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/settings_fwdbg_api.hpp new file mode 100644 index 00000000..cf22a6d7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/settings_fwdbg_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/settings/settings_fwdbg_types.hpp> + +namespace ams::settings::fwdbg { + + bool IsDebugModeEnabled(); + + size_t GetSettingsItemValueSize(const char *name, const char *key); + size_t GetSettingsItemValue(void *dst, size_t dst_size, const char *name, const char *key); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/settings_fwdbg_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/settings_fwdbg_types.hpp new file mode 100644 index 00000000..63e183e7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/settings_fwdbg_types.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf/sf_buffer_tags.hpp> + +namespace ams::settings::fwdbg { + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/settings_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/settings_types.hpp new file mode 100644 index 00000000..4290a0eb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/settings_types.hpp @@ -0,0 +1,274 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::settings { + + constexpr size_t SettingsNameLengthMax = 0x40; + constexpr size_t SettingsItemKeyLengthMax = 0x40; + + struct SettingsName : public sf::LargeData { + char value[util::AlignUp(SettingsNameLengthMax + 1, alignof(u64))]; + }; + + static_assert(util::is_pod<SettingsName>::value && sizeof(SettingsName) > SettingsNameLengthMax); + + struct SettingsItemKey : public sf::LargeData { + char value[util::AlignUp(SettingsItemKeyLengthMax + 1, alignof(u64))]; + }; + + static_assert(util::is_pod<SettingsItemKey>::value && sizeof(SettingsItemKey) > SettingsItemKeyLengthMax); + + enum Language { + Language_Japanese, + Language_AmericanEnglish, + Language_French, + Language_German, + Language_Italian, + Language_Spanish, + Language_Chinese, + Language_Korean, + Language_Dutch, + Language_Portuguese, + Language_Russian, + Language_Taiwanese, + Language_BritishEnglish, + Language_CanadianFrench, + Language_LatinAmericanSpanish, + /* 4.0.0+ */ + Language_SimplifiedChinese, + Language_TraditionalChinese, + /* 10.1.0+ */ + Language_PortugueseBr, + Language_Count, + }; + + struct LanguageCode { + static constexpr size_t MaxLength = 8; + + char name[MaxLength]; + + static constexpr LanguageCode Encode(util::string_view name) { + LanguageCode out{}; + for (size_t i = 0; i < MaxLength && i < name.size(); i++) { + out.name[i] = name[i]; + } + return out; + } + + template<Language Lang> + static constexpr inline LanguageCode EncodeLanguage() { + if constexpr (false) { /* ... */ } + #define AMS_MATCH_LANGUAGE(lang, enc) else if constexpr (Lang == Language_##lang) { return LanguageCode::Encode(enc); } + AMS_MATCH_LANGUAGE(Japanese, "ja") + AMS_MATCH_LANGUAGE(AmericanEnglish, "en-US") + AMS_MATCH_LANGUAGE(French, "fr") + AMS_MATCH_LANGUAGE(German, "de") + AMS_MATCH_LANGUAGE(Italian, "it") + AMS_MATCH_LANGUAGE(Spanish, "es") + AMS_MATCH_LANGUAGE(Chinese, "zh-CN") + AMS_MATCH_LANGUAGE(Korean, "ko") + AMS_MATCH_LANGUAGE(Dutch, "nl") + AMS_MATCH_LANGUAGE(Portuguese, "pt") + AMS_MATCH_LANGUAGE(Russian, "ru") + AMS_MATCH_LANGUAGE(Taiwanese, "zh-TW") + AMS_MATCH_LANGUAGE(BritishEnglish, "en-GB") + AMS_MATCH_LANGUAGE(CanadianFrench, "fr-CA") + AMS_MATCH_LANGUAGE(LatinAmericanSpanish, "es-419") + /* 4.0.0+ */ + AMS_MATCH_LANGUAGE(SimplifiedChinese, "zh-Hans") + AMS_MATCH_LANGUAGE(TraditionalChinese, "zh-Hant") + /* 10.1.0+ */ + AMS_MATCH_LANGUAGE(PortugueseBr, "pt-BR") + #undef AMS_MATCH_LANGUAGE + else { static_assert(Lang != Language_Japanese); } + } + + static constexpr inline LanguageCode Encode(const Language language) { + constexpr LanguageCode EncodedLanguages[Language_Count] = { + EncodeLanguage<Language_Japanese>(), + EncodeLanguage<Language_AmericanEnglish>(), + EncodeLanguage<Language_French>(), + EncodeLanguage<Language_German>(), + EncodeLanguage<Language_Italian>(), + EncodeLanguage<Language_Spanish>(), + EncodeLanguage<Language_Chinese>(), + EncodeLanguage<Language_Korean>(), + EncodeLanguage<Language_Dutch>(), + EncodeLanguage<Language_Portuguese>(), + EncodeLanguage<Language_Russian>(), + EncodeLanguage<Language_Taiwanese>(), + EncodeLanguage<Language_BritishEnglish>(), + EncodeLanguage<Language_CanadianFrench>(), + EncodeLanguage<Language_LatinAmericanSpanish>(), + /* 4.0.0+ */ + EncodeLanguage<Language_SimplifiedChinese>(), + EncodeLanguage<Language_TraditionalChinese>(), + /* 10.1.0+ */ + EncodeLanguage<Language_PortugueseBr>(), + }; + return EncodedLanguages[language]; + } + + }; + + constexpr inline bool operator==(const LanguageCode &lhs, const LanguageCode &rhs) { + return util::Strncmp<char>(lhs.name, rhs.name, sizeof(lhs)) == 0; + } + + constexpr inline bool operator!=(const LanguageCode &lhs, const LanguageCode &rhs) { + return !(lhs == rhs); + } + + constexpr inline bool operator==(const LanguageCode &lhs, const Language &rhs) { + return lhs == LanguageCode::Encode(rhs); + } + + constexpr inline bool operator!=(const LanguageCode &lhs, const Language &rhs) { + return !(lhs == rhs); + } + + constexpr inline bool operator==(const Language &lhs, const LanguageCode &rhs) { + return rhs == lhs; + } + + constexpr inline bool operator!=(const Language &lhs, const LanguageCode &rhs) { + return !(lhs == rhs); + } + + namespace impl { + + template<size_t ...Is> + constexpr inline bool IsValidLanguageCode(const LanguageCode &lc, std::index_sequence<Is...>) { + return ((lc == LanguageCode::Encode(static_cast<Language>(Is))) || ...); + } + + } + + constexpr inline bool IsValidLanguageCodeDeprecated(const LanguageCode &lc) { + return impl::IsValidLanguageCode(lc, std::make_index_sequence<Language_Count - 3>{}); + } + + constexpr inline bool IsValidLanguageCodeDeprecated2(const LanguageCode &lc) { + return impl::IsValidLanguageCode(lc, std::make_index_sequence<Language_Count - 1>{}); + } + + constexpr inline bool IsValidLanguageCode(const LanguageCode &lc) { + return impl::IsValidLanguageCode(lc, std::make_index_sequence<Language_Count>{}); + } + + static_assert(util::is_pod<LanguageCode>::value); + static_assert(sizeof(LanguageCode) == sizeof(u64)); + + /* Not an official type, but convenient. */ + enum RegionCode : s32 { + RegionCode_Japan, + RegionCode_America, + RegionCode_Europe, + RegionCode_Australia, + RegionCode_China, + RegionCode_Korea, + RegionCode_Taiwan, + + RegionCode_Count, + }; + + constexpr inline bool IsValidRegionCode(const RegionCode rc) { + return 0 <= rc && rc < RegionCode_Count; + } + + /* This needs to be defined separately from libnx's so that it can inherit from sf::LargeData. */ + + struct FirmwareVersion : public sf::LargeData { + u8 major; + u8 minor; + u8 micro; + u8 padding1; + u8 revision_major; + u8 revision_minor; + u8 padding2; + u8 padding3; + char platform[0x20]; + char version_hash[0x40]; + char display_version[0x18]; + char display_title[0x80]; + + constexpr inline u32 GetVersion() const { + return (static_cast<u32>(major) << 16) | (static_cast<u32>(minor) << 8) | (static_cast<u32>(micro) << 0); + } + }; + + static_assert(util::is_pod<FirmwareVersion>::value); + static_assert(sizeof(FirmwareVersion) == 0x100); + + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(sizeof(FirmwareVersion) == sizeof(::SetSysFirmwareVersion)); + #endif + + constexpr inline bool operator==(const FirmwareVersion &lhs, const FirmwareVersion &rhs) { + return lhs.GetVersion() == rhs.GetVersion(); + } + + constexpr inline bool operator!=(const FirmwareVersion &lhs, const FirmwareVersion &rhs) { + return !(lhs == rhs); + } + + constexpr inline bool operator<(const FirmwareVersion &lhs, const FirmwareVersion &rhs) { + return lhs.GetVersion() < rhs.GetVersion(); + } + + constexpr inline bool operator>=(const FirmwareVersion &lhs, const FirmwareVersion &rhs) { + return !(lhs < rhs); + } + + constexpr inline bool operator<=(const FirmwareVersion &lhs, const FirmwareVersion &rhs) { + return lhs.GetVersion() <= rhs.GetVersion(); + } + + constexpr inline bool operator>(const FirmwareVersion &lhs, const FirmwareVersion &rhs) { + return !(lhs <= rhs); + } + + struct BluetoothDevicesSettings : public sf::LargeData { + u8 address[0x6]; + char name[0x20]; + u8 class_of_device[0x3]; + u8 link_key[0x10]; + u8 link_key_present; + u16 version; + u32 trusted_services; + u16 vid; + u16 pid; + u8 sub_class; + u8 attribute_mask; + u16 descriptor_length; + u8 descriptor[0x80]; + u8 key_type; + u8 device_type; + u16 brr_size; + u8 brr[0x9]; + u8 reserved0; + char name2[0xF9]; + u8 reserved1[0x31]; + }; + + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(sizeof(BluetoothDevicesSettings) == sizeof(::SetSysBluetoothDevicesSettings)); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/system/settings_error_report.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/system/settings_error_report.hpp new file mode 100644 index 00000000..77749993 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/system/settings_error_report.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/settings/settings_types.hpp> + +namespace ams::settings::system { + + enum ErrorReportSharePermission { + ErrorReportSharePermission_NotConfirmed = 0, + ErrorReportSharePermission_Granted = 1, + ErrorReportSharePermission_Denied = 2, + }; + + ErrorReportSharePermission GetErrorReportSharePermission(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/system/settings_firmware_version.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/system/settings_firmware_version.hpp new file mode 100644 index 00000000..065239ae --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/system/settings_firmware_version.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/settings/settings_types.hpp> + +namespace ams::settings::system { + + struct alignas(4) FirmwareVersion { + u8 major; + u8 minor; + u8 micro; + u8 padding1; + u8 revision_major; + u8 revision_minor; + u8 padding2; + u8 padding3; + char platform[0x20]; + char revision[0x40]; + char display_version[0x18]; + char display_name[0x80]; + + constexpr int GetComparableVersion() const { + return (static_cast<int>(major) << 16) | (static_cast<int>(minor) << 8) | (static_cast<int>(micro) << 0); + } + + constexpr friend bool operator==(const FirmwareVersion &lhs, const FirmwareVersion &rhs) { + return lhs.GetComparableVersion() == rhs.GetComparableVersion(); + } + + constexpr friend bool operator!=(const FirmwareVersion &lhs, const FirmwareVersion &rhs) { + return lhs.GetComparableVersion() != rhs.GetComparableVersion(); + } + + constexpr friend bool operator<=(const FirmwareVersion &lhs, const FirmwareVersion &rhs) { + return lhs.GetComparableVersion() <= rhs.GetComparableVersion(); + } + + constexpr friend bool operator>=(const FirmwareVersion &lhs, const FirmwareVersion &rhs) { + return lhs.GetComparableVersion() >= rhs.GetComparableVersion(); + } + + constexpr friend bool operator<(const FirmwareVersion &lhs, const FirmwareVersion &rhs) { + return lhs.GetComparableVersion() < rhs.GetComparableVersion(); + } + + constexpr friend bool operator>(const FirmwareVersion &lhs, const FirmwareVersion &rhs) { + return lhs.GetComparableVersion() > rhs.GetComparableVersion(); + } + }; + + void GetFirmwareVersion(FirmwareVersion *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/system/settings_platform_region.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/system/settings_platform_region.hpp new file mode 100644 index 00000000..81991ffc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/system/settings_platform_region.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/settings/settings_types.hpp> + +namespace ams::settings::system { + + enum PlatformRegion { + PlatformRegion_Invalid = 0, + PlatformRegion_Global = 1, + PlatformRegion_China = 2, + }; + + PlatformRegion GetPlatformRegion(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/system/settings_product_model.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/system/settings_product_model.hpp new file mode 100644 index 00000000..406b665f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/system/settings_product_model.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/settings/settings_types.hpp> + +namespace ams::settings::system { + + enum ProductModel { + ProductModel_Invalid = 0, + ProductModel_Nx = 1, + }; + + ProductModel GetProductModel(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/system/settings_region.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/system/settings_region.hpp new file mode 100644 index 00000000..9818eca3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/system/settings_region.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/settings/settings_types.hpp> + +namespace ams::settings::system { + + enum RegionCode { + RegionCode_Japan = 0, + RegionCode_Usa = 1, + RegionCode_Europe = 2, + RegionCode_Australia = 3, + RegionCode_HongKongTaiwanKorea = 4, + RegionCode_China = 5, + }; + + void GetRegionCode(RegionCode *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/system/settings_serial_number.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/system/settings_serial_number.hpp new file mode 100644 index 00000000..dc88b835 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/settings/system/settings_serial_number.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/settings/settings_types.hpp> + +namespace ams::settings::system { + + struct SerialNumber { + char str[0x18]; + }; + + void GetSerialNumber(SerialNumber *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf.hpp new file mode 100644 index 00000000..e073e339 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/sf/sf_common.hpp> +#include <stratosphere/sf/sf_allocation_policies.hpp> +#include <stratosphere/sf/sf_lmem_utility.hpp> +#include <stratosphere/sf/sf_mem_utility.hpp> +#include <stratosphere/sf/sf_exp_heap_allocator.hpp> +#include <stratosphere/sf/sf_standard_allocation_policy.hpp> +#include <stratosphere/sf/sf_std_allocation_policy.hpp> +#include <stratosphere/sf/sf_shared_object.hpp> +#include <stratosphere/sf/sf_service_object.hpp> +#include <stratosphere/sf/hipc/sf_hipc_server_session_manager.hpp> + +#include <stratosphere/sf/cmif/sf_cmif_inline_context.hpp> +#include <stratosphere/sf/sf_fs_inline_context.hpp> + +#include <stratosphere/sf/sf_out.hpp> +#include <stratosphere/sf/sf_native_handle.hpp> +#include <stratosphere/sf/sf_buffers.hpp> +#include <stratosphere/sf/impl/sf_impl_command_serialization.hpp> +#include <stratosphere/sf/impl/sf_impl_autogen_interface_macros.hpp> +#include <stratosphere/sf/impl/sf_impl_autogen_impl_macros.hpp> +#include <stratosphere/sf/impl/sf_impl_template_base.hpp> +#include <stratosphere/sf/sf_object_factory.hpp> + +#include <stratosphere/sf/hipc/sf_hipc_server_manager.hpp> + +#include <stratosphere/sf/sf_mitm_dispatch.h> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_domain_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_domain_api.hpp new file mode 100644 index 00000000..de84497b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_domain_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/sf/sf_common.hpp> +#include <stratosphere/sf/cmif/sf_cmif_service_object_holder.hpp> + +namespace ams::sf::cmif { + + struct DomainObjectId { + u32 value; + + constexpr void SetValue(u32 new_value) { this->value = new_value; } + }; + + static_assert(std::is_trivial<DomainObjectId>::value && sizeof(DomainObjectId) == sizeof(u32), "DomainObjectId"); + + inline constexpr bool operator==(const DomainObjectId &lhs, const DomainObjectId &rhs) { + return lhs.value == rhs.value; + } + + inline constexpr bool operator!=(const DomainObjectId &lhs, const DomainObjectId &rhs) { + return lhs.value != rhs.value; + } + + inline constexpr bool operator<(const DomainObjectId &lhs, const DomainObjectId &rhs) { + return lhs.value < rhs.value; + } + + inline constexpr bool operator<=(const DomainObjectId &lhs, const DomainObjectId &rhs) { + return lhs.value <= rhs.value; + } + + inline constexpr bool operator>(const DomainObjectId &lhs, const DomainObjectId &rhs) { + return lhs.value > rhs.value; + } + + inline constexpr bool operator>=(const DomainObjectId &lhs, const DomainObjectId &rhs) { + return lhs.value >= rhs.value; + } + + constexpr inline const DomainObjectId InvalidDomainObjectId = { .value = 0 }; + + class ServerDomainBase { + public: + virtual Result ReserveIds(DomainObjectId *out_ids, size_t count) = 0; + virtual void ReserveSpecificIds(const DomainObjectId *ids, size_t count) = 0; + virtual void UnreserveIds(const DomainObjectId *ids, size_t count) = 0; + virtual void RegisterObject(DomainObjectId id, ServiceObjectHolder &&obj) = 0; + + virtual ServiceObjectHolder UnregisterObject(DomainObjectId id) = 0; + virtual ServiceObjectHolder GetObject(DomainObjectId id) = 0; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_domain_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_domain_manager.hpp new file mode 100644 index 00000000..287fd40c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_domain_manager.hpp @@ -0,0 +1,140 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/sf/sf_common.hpp> +#include <stratosphere/sf/cmif/sf_cmif_domain_api.hpp> +#include <stratosphere/sf/cmif/sf_cmif_domain_service_object.hpp> +#include <stratosphere/sf/impl/sf_service_object_impl.hpp> + +namespace ams::sf::cmif { + + class ServerDomainManager { + NON_COPYABLE(ServerDomainManager); + NON_MOVEABLE(ServerDomainManager); + private: + class Domain; + + struct Entry { + NON_COPYABLE(Entry); + NON_MOVEABLE(Entry); + + util::IntrusiveListNode free_list_node; + util::IntrusiveListNode domain_list_node; + Domain *owner; + ServiceObjectHolder object; + + explicit Entry() : owner(nullptr) { /* ... */ } + }; + + class Domain final : public DomainServiceObject, private sf::impl::ServiceObjectImplBase2 { + NON_COPYABLE(Domain); + NON_MOVEABLE(Domain); + private: + using EntryList = typename util::IntrusiveListMemberTraits<&Entry::domain_list_node>::ListType; + private: + ServerDomainManager *m_manager; + EntryList m_entries; + public: + explicit Domain(ServerDomainManager *m) : m_manager(m) { /* ... */ } + ~Domain(); + + void DisposeImpl(); + + virtual void AddReference() override { + ServiceObjectImplBase2::AddReferenceImpl(); + } + + virtual void Release() override { + if (ServiceObjectImplBase2::ReleaseImpl()) { + this->DisposeImpl(); + } + } + + virtual ServerDomainBase *GetServerDomain() override final { + return static_cast<ServerDomainBase *>(this); + } + + virtual Result ReserveIds(DomainObjectId *out_ids, size_t count) override final; + virtual void ReserveSpecificIds(const DomainObjectId *ids, size_t count) override final; + virtual void UnreserveIds(const DomainObjectId *ids, size_t count) override final; + virtual void RegisterObject(DomainObjectId id, ServiceObjectHolder &&obj) override final; + + virtual ServiceObjectHolder UnregisterObject(DomainObjectId id) override final; + virtual ServiceObjectHolder GetObject(DomainObjectId id) override final; + }; + public: + using DomainEntryStorage = util::TypedStorage<Entry>; + using DomainStorage = util::TypedStorage<Domain>; + private: + class EntryManager { + private: + using EntryList = typename util::IntrusiveListMemberTraits<&Entry::free_list_node>::ListType; + private: + os::SdkMutex m_lock; + EntryList m_free_list; + Entry *m_entries; + size_t m_num_entries; + public: + EntryManager(DomainEntryStorage *entry_storage, size_t entry_count); + ~EntryManager(); + Entry *AllocateEntry(); + void FreeEntry(Entry *); + + void AllocateSpecificEntries(const DomainObjectId *ids, size_t count); + + inline DomainObjectId GetId(Entry *e) { + const size_t index = e - m_entries; + AMS_ABORT_UNLESS(index < m_num_entries); + return DomainObjectId{ u32(index + 1) }; + } + + inline Entry *GetEntry(DomainObjectId id) { + if (id == InvalidDomainObjectId) { + return nullptr; + } + const size_t index = id.value - 1; + if (!(index < m_num_entries)) { + return nullptr; + } + return m_entries + index; + } + }; + private: + os::SdkMutex m_entry_owner_lock; + EntryManager m_entry_manager; + private: + virtual void *AllocateDomain() = 0; + virtual void FreeDomain(void *) = 0; + protected: + ServerDomainManager(DomainEntryStorage *entry_storage, size_t entry_count) : m_entry_owner_lock(), m_entry_manager(entry_storage, entry_count) { /* ... */ } + + inline DomainServiceObject *AllocateDomainServiceObject() { + void *storage = this->AllocateDomain(); + if (storage == nullptr) { + return nullptr; + } + + return std::construct_at(static_cast<Domain *>(storage), this); + } + public: + static void DestroyDomainServiceObject(DomainServiceObject *obj) { + static_cast<Domain *>(obj)->DisposeImpl(); + } + }; + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_domain_service_object.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_domain_service_object.hpp new file mode 100644 index 00000000..e73a6642 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_domain_service_object.hpp @@ -0,0 +1,137 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/sf/sf_mitm_config.hpp> +#include <stratosphere/sf/cmif/sf_cmif_service_dispatch.hpp> +#include <stratosphere/sf/cmif/sf_cmif_domain_api.hpp> +#include <stratosphere/sf/cmif/sf_cmif_server_message_processor.hpp> + +namespace ams::sf::cmif { + + class DomainServiceObjectDispatchTable : public impl::ServiceDispatchTableBase { + private: + Result ProcessMessageImpl(ServiceDispatchContext &ctx, ServerDomainBase *domain, const cmif::PointerAndSize &in_raw_data) const; + Result ProcessMessageForMitmImpl(ServiceDispatchContext &ctx, ServerDomainBase *domain, const cmif::PointerAndSize &in_raw_data) const; + public: + Result ProcessMessage(ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data) const; + Result ProcessMessageForMitm(ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data) const; + }; + + + class DomainServiceObjectProcessor : public ServerMessageProcessor { + private: + ServerMessageProcessor *m_impl_processor; + ServerDomainBase *m_domain; + DomainObjectId *m_in_object_ids; + DomainObjectId *m_out_object_ids; + size_t m_num_in_objects; + ServerMessageRuntimeMetadata m_impl_metadata; + public: + DomainServiceObjectProcessor(ServerDomainBase *d, DomainObjectId *in_obj_ids, size_t num_in_objs) : m_domain(d), m_in_object_ids(in_obj_ids), m_num_in_objects(num_in_objs) { + AMS_ABORT_UNLESS(m_domain != nullptr); + AMS_ABORT_UNLESS(m_in_object_ids != nullptr); + m_impl_processor = nullptr; + m_out_object_ids = nullptr; + m_impl_metadata = {}; + } + + constexpr size_t GetInObjectCount() const { + return m_num_in_objects; + } + + constexpr size_t GetOutObjectCount() const { + return m_impl_metadata.GetOutObjectCount(); + } + + constexpr size_t GetImplOutHeadersSize() const { + return m_impl_metadata.GetOutHeadersSize(); + } + + constexpr size_t GetImplOutDataTotalSize() const { + return m_impl_metadata.GetUnalignedOutDataSize() + m_impl_metadata.GetOutHeadersSize(); + } + public: + /* Used to enabled templated message processors. */ + virtual void SetImplementationProcessor(ServerMessageProcessor *impl) override final { + if (m_impl_processor == nullptr) { + m_impl_processor = impl; + } else { + m_impl_processor->SetImplementationProcessor(impl); + } + + m_impl_metadata = m_impl_processor->GetRuntimeMetadata(); + } + + virtual const ServerMessageRuntimeMetadata GetRuntimeMetadata() const override final { + const auto runtime_metadata = m_impl_processor->GetRuntimeMetadata(); + + return ServerMessageRuntimeMetadata { + .in_data_size = static_cast<u16>(runtime_metadata.GetInDataSize() + runtime_metadata.GetInObjectCount() * sizeof(DomainObjectId)), + .unaligned_out_data_size = static_cast<u16>(runtime_metadata.GetOutDataSize() + runtime_metadata.GetOutObjectCount() * sizeof(DomainObjectId)), + .in_headers_size = static_cast<u8>(runtime_metadata.GetInHeadersSize() + sizeof(CmifDomainInHeader)), + .out_headers_size = static_cast<u8>(runtime_metadata.GetOutHeadersSize() + sizeof(CmifDomainOutHeader)), + .in_object_count = 0, + .out_object_count = 0, + }; + } + + virtual Result PrepareForProcess(const ServiceDispatchContext &ctx, const ServerMessageRuntimeMetadata runtime_metadata) const override final; + virtual Result GetInObjects(ServiceObjectHolder *in_objects) const override final; + virtual HipcRequest PrepareForReply(const cmif::ServiceDispatchContext &ctx, PointerAndSize &out_raw_data, const ServerMessageRuntimeMetadata runtime_metadata) override final; + virtual void PrepareForErrorReply(const cmif::ServiceDispatchContext &ctx, PointerAndSize &out_raw_data, const ServerMessageRuntimeMetadata runtime_metadata) override final; + virtual void SetOutObjects(const cmif::ServiceDispatchContext &ctx, const HipcRequest &response, ServiceObjectHolder *out_objects, DomainObjectId *ids) override final; + }; + + class DomainServiceObject : public IServiceObject, public ServerDomainBase { + friend class DomainServiceObjectDispatchTable; + public: + static constexpr inline DomainServiceObjectDispatchTable s_CmifServiceDispatchTable{}; + private: + virtual ServerDomainBase *GetServerDomain() = 0; + }; + + class MitmDomainServiceObject : public DomainServiceObject{}; + + static_assert(sizeof(DomainServiceObject) == sizeof(MitmDomainServiceObject)); + + template<> + struct ServiceDispatchTraits<DomainServiceObject> { + static_assert(std::is_base_of<sf::IServiceObject, DomainServiceObject>::value, "DomainServiceObject must derive from sf::IServiceObject"); + #if AMS_SF_MITM_SUPPORTED + static_assert(!std::is_base_of<sf::IMitmServiceObject, DomainServiceObject>::value, "DomainServiceObject must not derive from sf::IMitmServiceObject"); + #endif + using ProcessHandlerType = decltype(ServiceDispatchMeta::ProcessHandler); + + using DispatchTableType = DomainServiceObjectDispatchTable; + static constexpr ProcessHandlerType ProcessHandlerImpl = &impl::ServiceDispatchTableBase::ProcessMessage<DispatchTableType>; + + static constexpr inline ServiceDispatchMeta Meta{std::addressof(DomainServiceObject::s_CmifServiceDispatchTable), ProcessHandlerImpl}; + }; + + template<> + struct ServiceDispatchTraits<MitmDomainServiceObject> { + static_assert(std::is_base_of<DomainServiceObject, MitmDomainServiceObject>::value, "MitmDomainServiceObject must derive from DomainServiceObject"); + using ProcessHandlerType = decltype(ServiceDispatchMeta::ProcessHandler); + + using DispatchTableType = DomainServiceObjectDispatchTable; + static constexpr ProcessHandlerType ProcessHandlerImpl = &impl::ServiceDispatchTableBase::ProcessMessageForMitm<DispatchTableType>; + + static constexpr inline ServiceDispatchMeta Meta{std::addressof(DomainServiceObject::s_CmifServiceDispatchTable), ProcessHandlerImpl}; + }; + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_inline_context.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_inline_context.hpp new file mode 100644 index 00000000..c8838026 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_inline_context.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/sf/sf_common.hpp> + +namespace ams::sf::cmif { + + using InlineContext = u32; + + InlineContext GetInlineContext(); + InlineContext SetInlineContext(InlineContext ctx); + + class ScopedInlineContextChanger { + private: + InlineContext m_prev_ctx; + public: + ALWAYS_INLINE explicit ScopedInlineContextChanger(InlineContext new_ctx) : m_prev_ctx(SetInlineContext(new_ctx)) { /* ... */ } + ~ScopedInlineContextChanger() { SetInlineContext(m_prev_ctx); } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_pointer_and_size.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_pointer_and_size.hpp new file mode 100644 index 00000000..c3558ccf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_pointer_and_size.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/sf/sf_common.hpp> + +namespace ams::sf::cmif { + + class PointerAndSize { + private: + uintptr_t m_pointer; + size_t m_size; + public: + constexpr PointerAndSize() : m_pointer(0), m_size(0) { /* ... */ } + constexpr PointerAndSize(uintptr_t ptr, size_t sz) : m_pointer(ptr), m_size(sz) { /* ... */ } + PointerAndSize(void *ptr, size_t sz) : PointerAndSize(reinterpret_cast<uintptr_t>(ptr), sz) { /* ... */ } + + void *GetPointer() const { + return reinterpret_cast<void *>(m_pointer); + } + + constexpr uintptr_t GetAddress() const { + return m_pointer; + } + + constexpr size_t GetSize() const { + return m_size; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_server_message_processor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_server_message_processor.hpp new file mode 100644 index 00000000..ef454e09 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_server_message_processor.hpp @@ -0,0 +1,85 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/sf/sf_service_object.hpp> +#include <stratosphere/sf/cmif/sf_cmif_pointer_and_size.hpp> + +namespace ams::sf::cmif { + + /* Forward declare ServiceDispatchContext, ServiceObjectHolder. */ + struct ServiceDispatchContext; + class ServiceObjectHolder; + struct DomainObjectId; + + /* This is needed for non-templated domain message processing. */ + struct ServerMessageRuntimeMetadata { + u16 in_data_size; + u16 unaligned_out_data_size; + u8 in_headers_size; + u8 out_headers_size; + u8 in_object_count; + u8 out_object_count; + + constexpr size_t GetInDataSize() const { + return static_cast<size_t>(this->in_data_size); + } + + constexpr size_t GetOutDataSize() const { + return static_cast<size_t>(util::AlignUp(this->unaligned_out_data_size, sizeof(u32))); + } + + constexpr size_t GetUnalignedOutDataSize() const { + return static_cast<size_t>(this->unaligned_out_data_size); + } + + constexpr size_t GetInHeadersSize() const { + return static_cast<size_t>(this->in_headers_size); + } + + constexpr size_t GetOutHeadersSize() const { + return static_cast<size_t>(this->out_headers_size); + } + + constexpr size_t GetInObjectCount() const { + return static_cast<size_t>(this->in_object_count); + } + + constexpr size_t GetOutObjectCount() const { + return static_cast<size_t>(this->out_object_count); + } + + constexpr size_t GetUnfixedOutPointerSizeOffset() const { + return this->GetInDataSize() + this->GetInHeadersSize() + 0x10 /* padding. */; + } + }; + + static_assert(util::is_pod<ServerMessageRuntimeMetadata>::value, "util::is_pod<ServerMessageRuntimeMetadata>::value"); + static_assert(sizeof(ServerMessageRuntimeMetadata) == sizeof(u64), "sizeof(ServerMessageRuntimeMetadata)"); + + class ServerMessageProcessor { + public: + /* Used to enabled templated message processors. */ + virtual void SetImplementationProcessor(ServerMessageProcessor *impl) = 0; + virtual const ServerMessageRuntimeMetadata GetRuntimeMetadata() const = 0; + + virtual Result PrepareForProcess(const ServiceDispatchContext &ctx, const ServerMessageRuntimeMetadata runtime_metadata) const = 0; + virtual Result GetInObjects(ServiceObjectHolder *in_objects) const = 0; + virtual HipcRequest PrepareForReply(const cmif::ServiceDispatchContext &ctx, PointerAndSize &out_raw_data, const ServerMessageRuntimeMetadata runtime_metadata) = 0; + virtual void PrepareForErrorReply(const cmif::ServiceDispatchContext &ctx, PointerAndSize &out_raw_data, const ServerMessageRuntimeMetadata runtime_metadata) = 0; + virtual void SetOutObjects(const cmif::ServiceDispatchContext &ctx, const HipcRequest &response, ServiceObjectHolder *out_objects, DomainObjectId *ids) = 0; + }; +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_service_dispatch.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_service_dispatch.hpp new file mode 100644 index 00000000..b08e45df --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_service_dispatch.hpp @@ -0,0 +1,177 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/sf/sf_mitm_config.hpp> +#include <stratosphere/sf/sf_service_object.hpp> +#include <stratosphere/sf/cmif/sf_cmif_pointer_and_size.hpp> +#include <stratosphere/sf/cmif/sf_cmif_server_message_processor.hpp> + +namespace ams::sf::hipc { + + class ServerSessionManager; + class ServerSession; + +} + +namespace ams::sf::cmif { + + class ServerMessageProcessor; + + struct HandlesToClose { + os::NativeHandle handles[8]; + size_t num_handles; + }; + + struct ServiceDispatchContext { + sf::IServiceObject *srv_obj; + hipc::ServerSessionManager *manager; + hipc::ServerSession *session; + ServerMessageProcessor *processor; + HandlesToClose *handles_to_close; + const PointerAndSize pointer_buffer; + const PointerAndSize in_message_buffer; + const PointerAndSize out_message_buffer; + const HipcParsedRequest request; + }; + + struct ServiceCommandMeta { + hos::Version hosver_low; + hos::Version hosver_high; + u32 cmd_id; + Result (*handler)(CmifOutHeader **out_header_ptr, ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data); + + constexpr inline bool MatchesVersion(hos::Version hosver) const { + const bool min_valid = this->hosver_low == hos::Version_Min; + const bool max_valid = this->hosver_high == hos::Version_Max; + + return (min_valid || this->hosver_low <= hosver) && (max_valid || hosver <= this->hosver_high); + } + + constexpr inline bool Matches(u32 cmd_id, hos::Version hosver) const { + return this->cmd_id == cmd_id && this->MatchesVersion(hosver); + } + + constexpr inline decltype(handler) GetHandler() const { + return this->handler; + } + + constexpr inline bool operator>(const ServiceCommandMeta &rhs) const { + if (this->cmd_id > rhs.cmd_id) { + return true; + } else if (this->cmd_id == rhs.cmd_id && this->hosver_low > rhs.hosver_low) { + return true; + } else if (this->cmd_id == rhs.cmd_id && this->hosver_low == rhs.hosver_low && this->hosver_high == rhs.hosver_high){ + return true; + } else { + return false; + } + } + }; + static_assert(util::is_pod<ServiceCommandMeta>::value && sizeof(ServiceCommandMeta) == 0x18, "sizeof(ServiceCommandMeta)"); + + namespace impl { + + + class ServiceDispatchTableBase { + protected: + Result ProcessMessageImpl(ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data, const ServiceCommandMeta *entries, const size_t entry_count, u32 interface_id_for_debug) const; + Result ProcessMessageForMitmImpl(ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data, const ServiceCommandMeta *entries, const size_t entry_count, u32 interface_id_for_debug) const; + public: + /* CRTP. */ + template<typename T> + Result ProcessMessage(ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data) const { + static_assert(std::is_base_of<ServiceDispatchTableBase, T>::value, "ServiceDispatchTableBase::Process<T>"); + R_RETURN(static_cast<const T *>(this)->ProcessMessage(ctx, in_raw_data)); + } + + template<typename T> + Result ProcessMessageForMitm(ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data) const { + static_assert(std::is_base_of<ServiceDispatchTableBase, T>::value, "ServiceDispatchTableBase::ProcessForMitm<T>"); + R_RETURN(static_cast<const T *>(this)->ProcessMessageForMitm(ctx, in_raw_data)); + } + }; + + template<u32 InterfaceIdForDebug, size_t N> + class ServiceDispatchTableImpl : public ServiceDispatchTableBase { + public: + static constexpr size_t NumEntries = N; + private: + const std::array<ServiceCommandMeta, N> m_entries; + public: + explicit constexpr ServiceDispatchTableImpl(const std::array<ServiceCommandMeta, N> &e) : m_entries{e} { /* ... */ } + + Result ProcessMessage(ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data) const { + R_RETURN(this->ProcessMessageImpl(ctx, in_raw_data, m_entries.data(), m_entries.size(), InterfaceIdForDebug)); + } + + Result ProcessMessageForMitm(ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data) const { + R_RETURN(this->ProcessMessageForMitmImpl(ctx, in_raw_data, m_entries.data(), m_entries.size(), InterfaceIdForDebug)); + } + + constexpr const std::array<ServiceCommandMeta, N> &GetEntries() const { + return m_entries; + } + }; + + } + + template<u32 InterfaceIdForDebug, size_t N> + class ServiceDispatchTable : public impl::ServiceDispatchTableImpl<InterfaceIdForDebug, N> { + public: + explicit constexpr ServiceDispatchTable(const std::array<ServiceCommandMeta, N> &e) : impl::ServiceDispatchTableImpl<InterfaceIdForDebug, N>(e) { /* ... */ } + }; + + struct ServiceDispatchMeta { + const impl::ServiceDispatchTableBase *DispatchTable; + Result (impl::ServiceDispatchTableBase::*ProcessHandler)(ServiceDispatchContext &, const cmif::PointerAndSize &) const; + + uintptr_t GetServiceId() const { + return reinterpret_cast<uintptr_t>(this->DispatchTable); + } + }; + + template<typename T> requires sf::IsServiceObject<T> + struct ServiceDispatchTraits { + using ProcessHandlerType = decltype(ServiceDispatchMeta::ProcessHandler); + + static constexpr inline auto DispatchTable = T::template s_CmifServiceDispatchTable<T>; + using DispatchTableType = decltype(DispatchTable); + + static constexpr ProcessHandlerType ProcessHandlerImpl = sf::IsMitmServiceObject<T> ? (&impl::ServiceDispatchTableBase::ProcessMessageForMitm<DispatchTableType>) + : (&impl::ServiceDispatchTableBase::ProcessMessage<DispatchTableType>); + + static constexpr inline ServiceDispatchMeta Meta{std::addressof(DispatchTable), ProcessHandlerImpl}; + }; + + template<> + struct ServiceDispatchTraits<sf::IServiceObject> { + static constexpr inline auto DispatchTable = ServiceDispatchTable<0, 0>(std::array<ServiceCommandMeta, 0>{}); + }; + + #if AMS_SF_MITM_SUPPORTED + template<> + struct ServiceDispatchTraits<sf::IMitmServiceObject> { + static constexpr inline auto DispatchTable = ServiceDispatchTable<0, 0>(std::array<ServiceCommandMeta, 0>{}); + }; + #endif + + template<typename T> + constexpr ALWAYS_INLINE const ServiceDispatchMeta *GetServiceDispatchMeta() { + return std::addressof(ServiceDispatchTraits<T>::Meta); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_service_object_holder.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_service_object_holder.hpp new file mode 100644 index 00000000..078ddef2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/cmif/sf_cmif_service_object_holder.hpp @@ -0,0 +1,109 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/sf/sf_service_object.hpp> +#include <stratosphere/sf/cmif/sf_cmif_service_dispatch.hpp> + +namespace ams::sf::cmif { + + class ServiceObjectHolder { + private: + SharedPointer<IServiceObject> m_srv; + const ServiceDispatchMeta *m_dispatch_meta; + private: + /* Copy constructor. */ + ServiceObjectHolder(const ServiceObjectHolder &o) : m_srv(o.m_srv), m_dispatch_meta(o.m_dispatch_meta) { /* ... */ } + ServiceObjectHolder &operator=(const ServiceObjectHolder &o) = delete; + public: + /* Default constructor, null all members. */ + ServiceObjectHolder() : m_srv(nullptr, false), m_dispatch_meta(nullptr) { /* ... */ } + + ~ServiceObjectHolder() { + m_dispatch_meta = nullptr; + } + + /* Ensure correct type id at runtime through template constructor. */ + template<typename ServiceImpl> + constexpr explicit ServiceObjectHolder(SharedPointer<ServiceImpl> &&s) : m_srv(std::move(s)), m_dispatch_meta(GetServiceDispatchMeta<ServiceImpl>()) { + /* ... */ + } + + /* Move constructor, assignment operator. */ + ServiceObjectHolder(ServiceObjectHolder &&o) : m_srv(std::move(o.m_srv)), m_dispatch_meta(std::move(o.m_dispatch_meta)) { + o.m_dispatch_meta = nullptr; + } + + ServiceObjectHolder &operator=(ServiceObjectHolder &&o) { + ServiceObjectHolder tmp(std::move(o)); + tmp.swap(*this); + return *this; + } + + /* State management. */ + void swap(ServiceObjectHolder &o) { + m_srv.swap(o.m_srv); + std::swap(m_dispatch_meta, o.m_dispatch_meta); + } + + void Reset() { + m_srv = nullptr; + m_dispatch_meta = nullptr; + } + + ServiceObjectHolder Clone() const { + return ServiceObjectHolder(*this); + } + + /* Boolean operators. */ + explicit constexpr operator bool() const { + return m_srv != nullptr; + } + + constexpr bool operator!() const { + return m_srv == nullptr; + } + + /* Getters. */ + constexpr uintptr_t GetServiceId() const { + if (m_dispatch_meta) { + return m_dispatch_meta->GetServiceId(); + } + return 0; + } + + template<typename Interface> + constexpr inline bool IsServiceObjectValid() const { + return this->GetServiceId() == GetServiceDispatchMeta<Interface>()->GetServiceId(); + } + + template<typename Interface> + inline Interface *GetServiceObject() const { + if (this->GetServiceId() == GetServiceDispatchMeta<Interface>()->GetServiceId()) { + return static_cast<Interface *>(m_srv.Get()); + } + return nullptr; + } + + inline sf::IServiceObject *GetServiceObjectUnsafe() const { + return static_cast<sf::IServiceObject *>(m_srv.Get()); + } + + /* Processing. */ + Result ProcessMessage(ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data) const; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/hipc/sf_hipc_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/hipc/sf_hipc_api.hpp new file mode 100644 index 00000000..69d0fd55 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/hipc/sf_hipc_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/sf/sf_common.hpp> +#include <stratosphere/sf/cmif/sf_cmif_pointer_and_size.hpp> + +namespace ams::sf::hipc { + + void *GetMessageBufferOnTls(); + + constexpr size_t TlsMessageBufferSize = 0x100; + + #if defined(ATMOSPHERE_OS_HORIZON) + ALWAYS_INLINE void *GetMessageBufferOnTls() { + return svc::GetThreadLocalRegion()->message_buffer; + } + #endif + + enum class ReceiveResult { + Success, + Closed, + NeedsRetry, + }; + + void AttachMultiWaitHolderForAccept(os::MultiWaitHolderType *holder, os::NativeHandle port); + void AttachMultiWaitHolderForReply(os::MultiWaitHolderType *holder, os::NativeHandle request); + + Result Receive(ReceiveResult *out_recv_result, os::NativeHandle session_handle, const cmif::PointerAndSize &message_buffer); + Result Receive(bool *out_closed, os::NativeHandle session_handle, const cmif::PointerAndSize &message_buffer); + Result Reply(os::NativeHandle session_handle, const cmif::PointerAndSize &message_buffer); + + Result CreateSession(os::NativeHandle *out_server_handle, os::NativeHandle *out_client_handle); + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/hipc/sf_hipc_server_domain_session_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/hipc/sf_hipc_server_domain_session_manager.hpp new file mode 100644 index 00000000..50a06c64 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/hipc/sf_hipc_server_domain_session_manager.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/sf/hipc/sf_hipc_server_session_manager.hpp> +#include <stratosphere/sf/cmif/sf_cmif_domain_manager.hpp> + +namespace ams::sf::hipc { + + class ServerDomainSessionManager : public ServerSessionManager, private cmif::ServerDomainManager { + protected: + using cmif::ServerDomainManager::DomainEntryStorage; + using cmif::ServerDomainManager::DomainStorage; + protected: + virtual Result DispatchManagerRequest(ServerSession *session, const cmif::PointerAndSize &in_message, const cmif::PointerAndSize &out_message) override final; + public: + ServerDomainSessionManager(DomainEntryStorage *entry_storage, size_t entry_count) : ServerDomainManager(entry_storage, entry_count) { /* ... */ } + + inline cmif::DomainServiceObject *AllocateDomainServiceObject() { + return cmif::ServerDomainManager::AllocateDomainServiceObject(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/hipc/sf_hipc_server_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/hipc/sf_hipc_server_manager.hpp new file mode 100644 index 00000000..6103edda --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/hipc/sf_hipc_server_manager.hpp @@ -0,0 +1,511 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/sf/sf_mitm_config.hpp> +#include <stratosphere/sf/hipc/sf_hipc_server_domain_session_manager.hpp> +#include <stratosphere/sm.hpp> + +namespace ams::sf::hipc { + + struct DefaultServerManagerOptions { + static constexpr size_t PointerBufferSize = 0; + static constexpr size_t MaxDomains = 0; + static constexpr size_t MaxDomainObjects = 0; + static constexpr bool CanDeferInvokeRequest = false; + static constexpr bool CanManageMitmServers = false; + }; + + static constexpr size_t ServerSessionCountMax = 0x40; + static_assert(ServerSessionCountMax == 0x40, "ServerSessionCountMax isn't 0x40 somehow, this assert is a reminder that this will break lots of things"); + + template<size_t, typename, size_t> + class ServerManager; + + class ServerManagerBase : public ServerDomainSessionManager { + NON_COPYABLE(ServerManagerBase); + NON_MOVEABLE(ServerManagerBase); + public: + #if AMS_SF_MITM_SUPPORTED + using MitmQueryFunction = bool (*)(const sm::MitmProcessInfo &); + #endif + private: + enum class UserDataTag : uintptr_t { + Server = 1, + Session = 2, + #if AMS_SF_MITM_SUPPORTED + MitmServer = 3, + #endif + }; + protected: + using ServerDomainSessionManager::DomainEntryStorage; + using ServerDomainSessionManager::DomainStorage; + protected: + class Server : public os::MultiWaitHolderType { + friend class ServerManagerBase; + template<size_t, typename, size_t> + friend class ServerManager; + NON_COPYABLE(Server); + NON_MOVEABLE(Server); + private: + cmif::ServiceObjectHolder m_static_object; + os::NativeHandle m_port_handle; + sm::ServiceName m_service_name; + int m_index; + bool m_service_managed; + #if AMS_SF_MITM_SUPPORTED + bool m_is_mitm_server; + #endif + public: + #if AMS_SF_MITM_SUPPORTED + void AcknowledgeMitmSession(std::shared_ptr<::Service> *out_fsrv, sm::MitmProcessInfo *out_client_info) { + /* Check mitm server. */ + AMS_ABORT_UNLESS(m_is_mitm_server); + + /* Create forward service. */ + *out_fsrv = ServerSession::CreateForwardService(); + + /* Get client info. */ + R_ABORT_UNLESS(sm::mitm::AcknowledgeSession(out_fsrv->get(), out_client_info, m_service_name)); + } + #endif + }; + protected: + static constinit inline bool g_is_any_deferred_supported = false; + #if AMS_SF_MITM_SUPPORTED + static constinit inline bool g_is_any_mitm_supported = false; + #endif + private: + /* Multiple wait management. */ + os::MultiWaitType m_multi_wait; + os::Event m_request_stop_event; + os::MultiWaitHolderType m_request_stop_event_holder; + os::Event m_notify_event; + os::MultiWaitHolderType m_notify_event_holder; + + os::SdkMutex m_selection_mutex; + + os::SdkMutex m_deferred_list_mutex; + os::MultiWaitType m_deferred_list; + + /* Boolean values. */ + const bool m_is_defer_supported; + const bool m_is_mitm_supported; + private: + virtual void RegisterServerSessionToWait(ServerSession *session) override final; + void LinkToDeferredList(os::MultiWaitHolderType *holder); + void LinkDeferred(); + + bool WaitAndProcessImpl(); + + Result ProcessForServer(os::MultiWaitHolderType *holder); + Result ProcessForSession(os::MultiWaitHolderType *holder); + + #if AMS_SF_MITM_SUPPORTED + Result ProcessForMitmServer(os::MultiWaitHolderType *holder); + #endif + + void RegisterServerImpl(Server *server, os::NativeHandle port_handle, bool is_mitm_server) { + server->m_port_handle = port_handle; + hipc::AttachMultiWaitHolderForAccept(server, port_handle); + + #if AMS_SF_MITM_SUPPORTED + server->m_is_mitm_server = is_mitm_server; + if (is_mitm_server) { + /* Mitm server. */ + AMS_ABORT_UNLESS(this->CanManageMitmServers()); + os::SetMultiWaitHolderUserData(server, static_cast<uintptr_t>(UserDataTag::MitmServer)); + } else { + /* Non-mitm server. */ + os::SetMultiWaitHolderUserData(server, static_cast<uintptr_t>(UserDataTag::Server)); + } + #else + AMS_UNUSED(is_mitm_server); + os::SetMultiWaitHolderUserData(server, static_cast<uintptr_t>(UserDataTag::Server)); + #endif + + os::LinkMultiWaitHolder(std::addressof(m_multi_wait), server); + } + + void RegisterServerImpl(int index, cmif::ServiceObjectHolder &&static_holder, os::NativeHandle port_handle, bool is_mitm_server) { + /* Allocate server memory. */ + auto *server = this->AllocateServer(); + AMS_ABORT_UNLESS(server != nullptr); + server->m_service_managed = false; + + if (static_holder) { + server->m_static_object = std::move(static_holder); + } else { + server->m_index = index; + } + + this->RegisterServerImpl(server, port_handle, is_mitm_server); + } + + Result RegisterServerImpl(int index, cmif::ServiceObjectHolder &&static_holder, sm::ServiceName service_name, size_t max_sessions) { + /* Register service. */ + os::NativeHandle port_handle; + R_TRY(sm::RegisterService(&port_handle, service_name, max_sessions, false)); + + /* Allocate server memory. */ + auto *server = this->AllocateServer(); + AMS_ABORT_UNLESS(server != nullptr); + server->m_service_managed = true; + server->m_service_name = service_name; + + if (static_holder) { + server->m_static_object = std::move(static_holder); + } else { + server->m_index = index; + } + + this->RegisterServerImpl(server, port_handle, false); + + R_SUCCEED(); + } + + #if AMS_SF_MITM_SUPPORTED + Result InstallMitmServerImpl(os::NativeHandle *out_port_handle, sm::ServiceName service_name, MitmQueryFunction query_func); + #endif + protected: + virtual Server *AllocateServer() = 0; + virtual void DestroyServer(Server *server) = 0; + virtual Result OnNeedsToAccept(int port_index, Server *server) { + AMS_UNUSED(port_index, server); + AMS_ABORT("OnNeedsToAccept must be overridden when using indexed ports"); + } + + template<typename Interface> + Result AcceptImpl(Server *server, SharedPointer<Interface> p) { + R_RETURN(ServerSessionManager::AcceptSession(server->m_port_handle, std::move(p))); + } + + #if AMS_SF_MITM_SUPPORTED + template<typename Interface> + Result AcceptMitmImpl(Server *server, SharedPointer<Interface> p, std::shared_ptr<::Service> forward_service) { + AMS_ABORT_UNLESS(this->CanManageMitmServers()); + R_RETURN(ServerSessionManager::AcceptMitmSession(server->m_port_handle, std::move(p), std::move(forward_service))); + } + + template<typename Interface> + Result RegisterMitmServerImpl(int index, cmif::ServiceObjectHolder &&static_holder, sm::ServiceName service_name) { + /* Install mitm service. */ + os::NativeHandle port_handle; + R_TRY(this->InstallMitmServerImpl(&port_handle, service_name, &Interface::ShouldMitm)); + + /* Allocate server memory. */ + auto *server = this->AllocateServer(); + AMS_ABORT_UNLESS(server != nullptr); + server->m_service_managed = true; + server->m_service_name = service_name; + + if (static_holder) { + server->m_static_object = std::move(static_holder); + } else { + server->m_index = index; + } + + this->RegisterServerImpl(server, port_handle, true); + + R_SUCCEED(); + } + #endif + public: + ServerManagerBase(DomainEntryStorage *entry_storage, size_t entry_count, bool defer_supported, bool mitm_supported) : + ServerDomainSessionManager(entry_storage, entry_count), + m_request_stop_event(os::EventClearMode_ManualClear), m_notify_event(os::EventClearMode_ManualClear), + m_selection_mutex(), m_deferred_list_mutex(), m_is_defer_supported(defer_supported), m_is_mitm_supported(mitm_supported) + { + /* Link multi-wait holders. */ + os::InitializeMultiWait(std::addressof(m_multi_wait)); + os::InitializeMultiWaitHolder(std::addressof(m_request_stop_event_holder), m_request_stop_event.GetBase()); + os::LinkMultiWaitHolder(std::addressof(m_multi_wait), std::addressof(m_request_stop_event_holder)); + os::InitializeMultiWaitHolder(std::addressof(m_notify_event_holder), m_notify_event.GetBase()); + os::LinkMultiWaitHolder(std::addressof(m_multi_wait), std::addressof(m_notify_event_holder)); + + os::InitializeMultiWait(std::addressof(m_deferred_list)); + } + + virtual ~ServerManagerBase() = default; + + static ALWAYS_INLINE bool CanAnyDeferInvokeRequest() { + return g_is_any_deferred_supported; + } + + ALWAYS_INLINE bool CanDeferInvokeRequest() const { + return CanAnyDeferInvokeRequest() && m_is_defer_supported; + } + + #if AMS_SF_MITM_SUPPORTED + static ALWAYS_INLINE bool CanAnyManageMitmServers() { + return g_is_any_mitm_supported; + } + + ALWAYS_INLINE bool CanManageMitmServers() const { + return CanAnyManageMitmServers() && m_is_mitm_supported; + } + #else + static consteval bool CanAnyManageMitmServers() { return false; } + static consteval bool CanManageMitmServers() { return false; } + #endif + + template<typename Interface> + void RegisterObjectForServer(SharedPointer<Interface> static_object, os::NativeHandle port_handle) { + this->RegisterServerImpl(0, cmif::ServiceObjectHolder(std::move(static_object)), port_handle, false); + } + + template<typename Interface> + Result RegisterObjectForServer(SharedPointer<Interface> static_object, sm::ServiceName service_name, size_t max_sessions) { + R_RETURN(this->RegisterServerImpl(0, cmif::ServiceObjectHolder(std::move(static_object)), service_name, max_sessions)); + } + + void RegisterServer(int port_index, os::NativeHandle port_handle) { + this->RegisterServerImpl(port_index, cmif::ServiceObjectHolder(), port_handle, false); + } + + Result RegisterServer(int port_index, sm::ServiceName service_name, size_t max_sessions) { + R_RETURN(this->RegisterServerImpl(port_index, cmif::ServiceObjectHolder(), service_name, max_sessions)); + } + + /* Processing. */ + os::MultiWaitHolderType *WaitSignaled(); + + void ResumeProcessing(); + void RequestStopProcessing(); + void AddUserMultiWaitHolder(os::MultiWaitHolderType *holder); + + Result Process(os::MultiWaitHolderType *holder); + void WaitAndProcess(); + void LoopProcess(); + }; + + template<size_t MaxServers, typename ManagerOptions = DefaultServerManagerOptions, size_t MaxSessions = ServerSessionCountMax - MaxServers> + class ServerManager : public ServerManagerBase { + NON_COPYABLE(ServerManager); + NON_MOVEABLE(ServerManager); + static_assert(MaxServers <= ServerSessionCountMax, "MaxServers can never be larger than ServerSessionCountMax (0x40)."); + static_assert(MaxSessions <= ServerSessionCountMax, "MaxSessions can never be larger than ServerSessionCountMax (0x40)."); + static_assert(MaxServers + MaxSessions <= ServerSessionCountMax, "MaxServers + MaxSessions can never be larger than ServerSessionCountMax (0x40)."); + private: + static constexpr inline bool DomainCountsValid = [] { + if constexpr (ManagerOptions::MaxDomains > 0) { + return ManagerOptions::MaxDomainObjects > 0; + } else { + return ManagerOptions::MaxDomainObjects == 0; + } + }(); + static_assert(DomainCountsValid, "Invalid Domain Counts"); + + #if !(AMS_SF_MITM_SUPPORTED) + static_assert(!ManagerOptions::CanManageMitmServers); + #endif + protected: + using ServerManagerBase::DomainEntryStorage; + using ServerManagerBase::DomainStorage; + private: + /* Resource storage. */ + os::SdkMutex m_resource_mutex; + util::TypedStorage<Server> m_server_storages[MaxServers]; + bool m_server_allocated[MaxServers]; + util::TypedStorage<ServerSession> m_session_storages[MaxSessions]; + bool m_session_allocated[MaxSessions]; + u8 m_pointer_buffer_storage[0x10 + (MaxSessions * ManagerOptions::PointerBufferSize)]; + #if AMS_SF_MITM_SUPPORTED + u8 m_saved_message_storage[0x10 + (MaxSessions * ((ManagerOptions::CanDeferInvokeRequest || ManagerOptions::CanManageMitmServers) ? hipc::TlsMessageBufferSize : 0))]; + #else + u8 m_saved_message_storage[0x10 + (MaxSessions * ((ManagerOptions::CanDeferInvokeRequest) ? hipc::TlsMessageBufferSize : 0))]; + #endif + uintptr_t m_pointer_buffers_start; + uintptr_t m_saved_messages_start; + + /* Domain resources. */ + DomainStorage m_domain_storages[ManagerOptions::MaxDomains]; + bool m_domain_allocated[ManagerOptions::MaxDomains]; + DomainEntryStorage m_domain_entry_storages[ManagerOptions::MaxDomainObjects]; + private: + constexpr inline size_t GetServerIndex(const Server *server) const { + const size_t i = server - GetPointer(m_server_storages[0]); + AMS_ABORT_UNLESS(i < MaxServers); + return i; + } + + constexpr inline size_t GetSessionIndex(const ServerSession *session) const { + const size_t i = session - GetPointer(m_session_storages[0]); + AMS_ABORT_UNLESS(i < MaxSessions); + return i; + } + + constexpr inline cmif::PointerAndSize GetObjectBySessionIndex(const ServerSession *session, uintptr_t start, size_t size) const { + return cmif::PointerAndSize(start + this->GetSessionIndex(session) * size, size); + } + protected: + virtual ServerSession *AllocateSession() override final { + if constexpr (MaxSessions > 0) { + std::scoped_lock lk(m_resource_mutex); + + for (size_t i = 0; i < MaxSessions; i++) { + if (!m_session_allocated[i]) { + m_session_allocated[i] = true; + return GetPointer(m_session_storages[i]); + } + } + } + + return nullptr; + } + + virtual void FreeSession(ServerSession *session) override final { + std::scoped_lock lk(m_resource_mutex); + const size_t index = this->GetSessionIndex(session); + AMS_ABORT_UNLESS(m_session_allocated[index]); + m_session_allocated[index] = false; + } + + virtual Server *AllocateServer() override final { + if constexpr (MaxServers > 0) { + std::scoped_lock lk(m_resource_mutex); + + for (size_t i = 0; i < MaxServers; i++) { + if (!m_server_allocated[i]) { + m_server_allocated[i] = true; + return GetPointer(m_server_storages[i]); + } + } + } + + return nullptr; + } + + virtual void DestroyServer(Server *server) override final { + std::scoped_lock lk(m_resource_mutex); + const size_t index = this->GetServerIndex(server); + AMS_ABORT_UNLESS(m_server_allocated[index]); + { + os::UnlinkMultiWaitHolder(server); + os::FinalizeMultiWaitHolder(server); + if (server->m_service_managed) { + #if AMS_SF_MITM_SUPPORTED + if constexpr (ManagerOptions::CanManageMitmServers) { + if (server->m_is_mitm_server) { + R_ABORT_UNLESS(sm::mitm::UninstallMitm(server->m_service_name)); + } else { + R_ABORT_UNLESS(sm::UnregisterService(server->m_service_name)); + } + } else { + R_ABORT_UNLESS(sm::UnregisterService(server->m_service_name)); + } + #else + R_ABORT_UNLESS(sm::UnregisterService(server->m_service_name)); + #endif + os::CloseNativeHandle(server->m_port_handle); + } + } + m_server_allocated[index] = false; + } + + virtual void *AllocateDomain() override final { + std::scoped_lock lk(m_resource_mutex); + for (size_t i = 0; i < ManagerOptions::MaxDomains; i++) { + if (!m_domain_allocated[i]) { + m_domain_allocated[i] = true; + return GetPointer(m_domain_storages[i]); + } + } + return nullptr; + } + + virtual void FreeDomain(void *domain) override final { + std::scoped_lock lk(m_resource_mutex); + DomainStorage *ptr = static_cast<DomainStorage *>(domain); + const size_t index = ptr - m_domain_storages; + AMS_ABORT_UNLESS(index < ManagerOptions::MaxDomains); + AMS_ABORT_UNLESS(m_domain_allocated[index]); + m_domain_allocated[index] = false; + } + + virtual cmif::PointerAndSize GetSessionPointerBuffer(const ServerSession *session) const override final { + if constexpr (ManagerOptions::PointerBufferSize > 0) { + return this->GetObjectBySessionIndex(session, m_pointer_buffers_start, ManagerOptions::PointerBufferSize); + } else { + return cmif::PointerAndSize(); + } + } + + virtual cmif::PointerAndSize GetSessionSavedMessageBuffer(const ServerSession *session) const override final { + if constexpr (ManagerOptions::CanDeferInvokeRequest || ManagerOptions::CanManageMitmServers) { + return this->GetObjectBySessionIndex(session, m_saved_messages_start, hipc::TlsMessageBufferSize); + } else { + return cmif::PointerAndSize(); + } + } + public: + ServerManager() : ServerManagerBase(m_domain_entry_storages, ManagerOptions::MaxDomainObjects, ManagerOptions::CanDeferInvokeRequest, ManagerOptions::CanManageMitmServers), m_resource_mutex() { + /* Clear storages. */ + #define SF_SM_MEMCLEAR(obj) if constexpr (sizeof(obj) > 0) { std::memset(obj, 0, sizeof(obj)); } + SF_SM_MEMCLEAR(m_server_storages); + SF_SM_MEMCLEAR(m_server_allocated); + SF_SM_MEMCLEAR(m_session_storages); + SF_SM_MEMCLEAR(m_session_allocated); + SF_SM_MEMCLEAR(m_pointer_buffer_storage); + SF_SM_MEMCLEAR(m_saved_message_storage); + SF_SM_MEMCLEAR(m_domain_allocated); + #undef SF_SM_MEMCLEAR + + /* Set resource starts. */ + m_pointer_buffers_start = util::AlignUp(reinterpret_cast<uintptr_t>(m_pointer_buffer_storage), 0x10); + m_saved_messages_start = util::AlignUp(reinterpret_cast<uintptr_t>(m_saved_message_storage), 0x10); + + /* Update globals. */ + if constexpr (ManagerOptions::CanDeferInvokeRequest) { + ServerManagerBase::g_is_any_deferred_supported = true; + } + #if AMS_SF_MITM_SUPPORTED + if constexpr (ManagerOptions::CanManageMitmServers) { + ServerManagerBase::g_is_any_mitm_supported = true; + } + #endif + } + + ~ServerManager() { + /* Close all sessions. */ + if constexpr (MaxSessions > 0) { + for (size_t i = 0; i < MaxSessions; i++) { + if (m_session_allocated[i]) { + this->CloseSessionImpl(GetPointer(m_session_storages[i])); + } + } + } + + /* Close all servers. */ + if constexpr (MaxServers > 0) { + for (size_t i = 0; i < MaxServers; i++) { + if (m_server_allocated[i]) { + this->DestroyServer(GetPointer(m_server_storages[i])); + } + } + } + } + public: + #if AMS_SF_MITM_SUPPORTED + template<typename Interface, bool Enable = ManagerOptions::CanManageMitmServers, typename = typename std::enable_if<Enable>::type> + Result RegisterMitmServer(int port_index, sm::ServiceName service_name) { + AMS_ABORT_UNLESS(this->CanManageMitmServers()); + R_RETURN(this->template RegisterMitmServerImpl<Interface>(port_index, cmif::ServiceObjectHolder(), service_name)); + } + #endif + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/hipc/sf_hipc_server_session_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/hipc/sf_hipc_server_session_manager.hpp new file mode 100644 index 00000000..f31c2eaf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/hipc/sf_hipc_server_session_manager.hpp @@ -0,0 +1,205 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/sf/sf_common.hpp> +#include <stratosphere/sf/sf_mitm_config.hpp> +#include <stratosphere/sf/sf_service_object.hpp> +#include <stratosphere/sf/cmif/sf_cmif_pointer_and_size.hpp> +#include <stratosphere/sf/cmif/sf_cmif_service_object_holder.hpp> +#include <stratosphere/sf/hipc/sf_hipc_api.hpp> + +namespace ams::sf::cmif { + + struct ServiceDispatchContext; + +} + +namespace ams::sf::hipc { + + class ServerSessionManager; + class ServerManagerBase; + + namespace impl { + + class HipcManagerImpl; + + } + + class ServerSession : public os::MultiWaitHolderType { + friend class ServerSessionManager; + friend class ServerManagerBase; + friend class impl::HipcManagerImpl; + NON_COPYABLE(ServerSession); + NON_MOVEABLE(ServerSession); + private: + cmif::ServiceObjectHolder m_srv_obj_holder; + cmif::PointerAndSize m_pointer_buffer; + cmif::PointerAndSize m_saved_message; + #if AMS_SF_MITM_SUPPORTED + util::TypedStorage<std::shared_ptr<::Service>> m_forward_service; + #endif + os::NativeHandle m_session_handle; + bool m_is_closed; + bool m_has_received; + const bool m_has_forward_service; + public: + ServerSession(os::NativeHandle h, cmif::ServiceObjectHolder &&obj) : m_srv_obj_holder(std::move(obj)), m_session_handle(h), m_has_forward_service(false) { + hipc::AttachMultiWaitHolderForReply(this, h); + m_is_closed = false; + m_has_received = false; + #if AMS_SF_MITM_SUPPORTED + AMS_ABORT_UNLESS(!this->IsMitmSession()); + #endif + } + + ~ServerSession() { + #if AMS_SF_MITM_SUPPORTED + if (m_has_forward_service) { + util::DestroyAt(m_forward_service); + } + #endif + } + + #if AMS_SF_MITM_SUPPORTED + ServerSession(os::NativeHandle h, cmif::ServiceObjectHolder &&obj, std::shared_ptr<::Service> &&fsrv) : m_srv_obj_holder(std::move(obj)), m_session_handle(h), m_has_forward_service(true) { + hipc::AttachMultiWaitHolderForReply(this, h); + m_is_closed = false; + m_has_received = false; + util::ConstructAt(m_forward_service, std::move(fsrv)); + AMS_ABORT_UNLESS(util::GetReference(m_forward_service) != nullptr); + } + + ALWAYS_INLINE bool IsMitmSession() const { + return m_has_forward_service; + } + + Result ForwardRequest(const cmif::ServiceDispatchContext &ctx) const; + + static inline void ForwardServiceDeleter(Service *srv) { + serviceClose(srv); + delete srv; + } + + static inline std::shared_ptr<::Service> CreateForwardService() { + return std::shared_ptr<::Service>(new ::Service(), ForwardServiceDeleter); + } + #endif + }; + + class ServerSessionManager { + private: + template<typename Constructor> + Result CreateSessionImpl(ServerSession **out, const Constructor &ctor) { + /* Allocate session. */ + ServerSession *session_memory = this->AllocateSession(); + R_UNLESS(session_memory != nullptr, sf::hipc::ResultOutOfSessionMemory()); + ON_RESULT_FAILURE { this->DestroySession(session_memory); }; + + /* Register session. */ + R_TRY(ctor(session_memory)); + + /* Save new session to output. */ + *out = session_memory; + R_SUCCEED(); + } + void DestroySession(ServerSession *session); + + Result ProcessRequestImpl(ServerSession *session, const cmif::PointerAndSize &in_message, const cmif::PointerAndSize &out_message); + virtual void RegisterServerSessionToWait(ServerSession *session) = 0; + protected: + Result DispatchRequest(cmif::ServiceObjectHolder &&obj, ServerSession *session, const cmif::PointerAndSize &in_message, const cmif::PointerAndSize &out_message); + virtual Result DispatchManagerRequest(ServerSession *session, const cmif::PointerAndSize &in_message, const cmif::PointerAndSize &out_message); + protected: + virtual ServerSession *AllocateSession() = 0; + virtual void FreeSession(ServerSession *session) = 0; + virtual cmif::PointerAndSize GetSessionPointerBuffer(const ServerSession *session) const = 0; + virtual cmif::PointerAndSize GetSessionSavedMessageBuffer(const ServerSession *session) const = 0; + + Result ReceiveRequestImpl(ServerSession *session, const cmif::PointerAndSize &message); + void CloseSessionImpl(ServerSession *session); + Result RegisterSessionImpl(ServerSession *session_memory, os::NativeHandle session_handle, cmif::ServiceObjectHolder &&obj); + Result AcceptSessionImpl(ServerSession *session_memory, os::NativeHandle port_handle, cmif::ServiceObjectHolder &&obj); + + #if AMS_SF_MITM_SUPPORTED + Result RegisterMitmSessionImpl(ServerSession *session_memory, os::NativeHandle mitm_session_handle, cmif::ServiceObjectHolder &&obj, std::shared_ptr<::Service> &&fsrv); + Result AcceptMitmSessionImpl(ServerSession *session_memory, os::NativeHandle mitm_port_handle, cmif::ServiceObjectHolder &&obj, std::shared_ptr<::Service> &&fsrv); + #endif + + Result ReceiveRequest(ServerSession *session, const cmif::PointerAndSize &message) { + R_RETURN(this->ReceiveRequestImpl(session, message)); + } + + Result RegisterSession(ServerSession **out, os::NativeHandle session_handle, cmif::ServiceObjectHolder &&obj) { + auto ctor = [&](ServerSession *session_memory) -> Result { + R_RETURN(this->RegisterSessionImpl(session_memory, session_handle, std::forward<cmif::ServiceObjectHolder>(obj))); + }; + R_RETURN(this->CreateSessionImpl(out, ctor)); + } + + Result AcceptSession(ServerSession **out, os::NativeHandle port_handle, cmif::ServiceObjectHolder &&obj) { + auto ctor = [&](ServerSession *session_memory) -> Result { + R_RETURN(this->AcceptSessionImpl(session_memory, port_handle, std::forward<cmif::ServiceObjectHolder>(obj))); + }; + R_RETURN(this->CreateSessionImpl(out, ctor)); + } + + #if AMS_SF_MITM_SUPPORTED + Result RegisterMitmSession(ServerSession **out, os::NativeHandle mitm_session_handle, cmif::ServiceObjectHolder &&obj, std::shared_ptr<::Service> &&fsrv) { + auto ctor = [&](ServerSession *session_memory) -> Result { + R_RETURN(this->RegisterMitmSessionImpl(session_memory, mitm_session_handle, std::forward<cmif::ServiceObjectHolder>(obj), std::forward<std::shared_ptr<::Service>>(fsrv))); + }; + R_RETURN(this->CreateSessionImpl(out, ctor)); + } + + Result AcceptMitmSession(ServerSession **out, os::NativeHandle mitm_port_handle, cmif::ServiceObjectHolder &&obj, std::shared_ptr<::Service> &&fsrv) { + auto ctor = [&](ServerSession *session_memory) -> Result { + R_RETURN(this->AcceptMitmSessionImpl(session_memory, mitm_port_handle, std::forward<cmif::ServiceObjectHolder>(obj), std::forward<std::shared_ptr<::Service>>(fsrv))); + }; + R_RETURN(this->CreateSessionImpl(out, ctor)); + } + #endif + public: + Result RegisterSession(os::NativeHandle session_handle, cmif::ServiceObjectHolder &&obj); + Result AcceptSession(os::NativeHandle port_handle, cmif::ServiceObjectHolder &&obj); + + #if AMS_SF_MITM_SUPPORTED + Result RegisterMitmSession(os::NativeHandle session_handle, cmif::ServiceObjectHolder &&obj, std::shared_ptr<::Service> &&fsrv); + Result AcceptMitmSession(os::NativeHandle mitm_port_handle, cmif::ServiceObjectHolder &&obj, std::shared_ptr<::Service> &&fsrv); + #endif + + template<typename Interface> + Result AcceptSession(os::NativeHandle port_handle, SharedPointer<Interface> obj) { + R_RETURN(this->AcceptSession(port_handle, cmif::ServiceObjectHolder(std::move(obj)))); + } + + #if AMS_SF_MITM_SUPPORTED + template<typename Interface> + Result AcceptMitmSession(os::NativeHandle mitm_port_handle, SharedPointer<Interface> obj, std::shared_ptr<::Service> &&fsrv) { + R_RETURN(this->AcceptMitmSession(mitm_port_handle, cmif::ServiceObjectHolder(std::move(obj)), std::forward<std::shared_ptr<::Service>>(fsrv))); + } + #endif + + Result ProcessRequest(ServerSession *session, const cmif::PointerAndSize &message); + + virtual ServerSessionManager *GetSessionManagerByTag(u32 tag) { + /* This is unused. */ + AMS_UNUSED(tag); + return this; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/impl/sf_impl_autogen_impl_macros.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/impl/sf_impl_autogen_impl_macros.hpp new file mode 100644 index 00000000..a17d09e5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/impl/sf_impl_autogen_impl_macros.hpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf/impl/sf_impl_autogen_interface_macros.hpp> +#include <stratosphere/sf/impl/sf_impl_template_base.hpp> + +namespace ams::sf::impl { + + #define AMS_SF_IMPL_DEFINE_IMPL_SYNC_METHOD(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + virtual RETURN AMS_SF_IMPL_SYNC_FUNCTION_NAME(NAME) ARGS override { \ + return ImplGetter::GetImplPointer(static_cast<ImplHolder *>(this))->NAME ARGNAMES; \ + } + + #define AMS_SF_DEFINE_INTERFACE_WITH_DEFAULT_BASE(NAMESPACE, INTERFACE, BASE, CMD_MACRO, INTF_ID) \ + namespace NAMESPACE { \ + \ + AMS_SF_DEFINE_INTERFACE_IMPL(BASE, INTERFACE, CMD_MACRO, INTF_ID) \ + \ + } \ + \ + namespace ams::sf::impl { \ + \ + template<typename Base, typename ImplHolder, typename ImplGetter, typename Root> \ + class ImplTemplateBaseT<::NAMESPACE::INTERFACE, Base, ImplHolder, ImplGetter, Root> : public Base, public ImplHolder { \ + public: \ + template<typename... Args> \ + constexpr explicit ImplTemplateBaseT(Args &&...args) : ImplHolder(std::forward<Args>(args)...) { } \ + private: \ + CMD_MACRO(CLASSNAME, AMS_SF_IMPL_DEFINE_IMPL_SYNC_METHOD) \ + }; \ + \ + } + + #define AMS_SF_DEFINE_INTERFACE(NAMESPACE, INTERFACE, CMD_MACRO, INTF_ID) \ + AMS_SF_DEFINE_INTERFACE_WITH_DEFAULT_BASE(NAMESPACE, INTERFACE, ::ams::sf::IServiceObject, CMD_MACRO, INTF_ID) + + #define AMS_SF_DEFINE_MITM_INTERFACE(NAMESPACE, INTERFACE, CMD_MACRO, INTF_ID) \ + AMS_SF_DEFINE_INTERFACE_WITH_DEFAULT_BASE(NAMESPACE, INTERFACE, ::ams::sf::IMitmServiceObject, CMD_MACRO, INTF_ID) + + #define AMS_SF_DEFINE_INTERFACE_WITH_BASE(NAMESPACE, INTERFACE, BASE, CMD_MACRO, INTF_ID) \ + namespace NAMESPACE { \ + \ + AMS_SF_DEFINE_INTERFACE_IMPL(BASE, INTERFACE, CMD_MACRO, INTF_ID) \ + \ + } \ + \ + namespace ams::sf::impl { \ + \ + template<typename Base, typename ImplHolder, typename ImplGetter, typename Root> \ + class ImplTemplateBaseT<::NAMESPACE::INTERFACE, Base, ImplHolder, ImplGetter, Root> : public ImplTemplateBaseT<BASE, Base, ImplHolder, ImplGetter, Root> { \ + private: \ + using BaseImplTemplateBase = ImplTemplateBaseT<BASE, Base, ImplHolder, ImplGetter, Root>; \ + public: \ + template<typename... Args> \ + constexpr explicit ImplTemplateBaseT(Args &&...args) : BaseImplTemplateBase(std::forward<Args>(args)...) { } \ + private: \ + CMD_MACRO(CLASSNAME, AMS_SF_IMPL_DEFINE_IMPL_SYNC_METHOD) \ + }; \ + \ + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/impl/sf_impl_autogen_interface_macros.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/impl/sf_impl_autogen_interface_macros.hpp new file mode 100644 index 00000000..19e52804 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/impl/sf_impl_autogen_interface_macros.hpp @@ -0,0 +1,171 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::sf::impl { + + struct SyncFunctionTraits { + public: + template<typename R, typename C, typename... A> + static std::tuple<A...> GetArgsImpl(R(C::*)(A...)); + }; + + template<auto F> + using SyncFunctionArgsType = decltype(SyncFunctionTraits::GetArgsImpl(F)); + + template<typename T> + struct TypeTag{}; + + template<size_t First, size_t... Ix> + static constexpr inline size_t ParameterCount = sizeof...(Ix); + + #define AMS_SF_IMPL_SYNC_FUNCTION_NAME(FUNCNAME) \ + _ams_sf_sync_##FUNCNAME + + #define AMS_SF_IMPL_DEFINE_INTERFACE_SYNC_METHOD(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + virtual RETURN AMS_SF_IMPL_SYNC_FUNCTION_NAME(NAME) ARGS = 0; + + #define AMS_SF_IMPL_EXTRACT_INTERFACE_SYNC_METHOD_ARGUMENTS(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + using NAME##ArgumentsType = ::ams::sf::impl::SyncFunctionArgsType<&CLASSNAME::AMS_SF_IMPL_SYNC_FUNCTION_NAME(NAME)>; + + #define AMS_SF_IMPL_DEFINE_INTERFACE_METHOD(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + ALWAYS_INLINE RETURN NAME ARGS { \ + return this->AMS_SF_IMPL_SYNC_FUNCTION_NAME(NAME) ARGNAMES; \ + } + + #define AMS_SF_IMPL_DEFINE_INTERFACE_SERVICE_COMMAND_META_HOLDER(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + template<typename Interface, typename A> struct NAME##ServiceCommandMetaHolder; \ + \ + template<typename Interface, typename ...Arguments> \ + requires std::same_as<std::tuple<Arguments...>, NAME##ArgumentsType> \ + struct NAME##ServiceCommandMetaHolder<Interface, std::tuple<Arguments...>> { \ + static constexpr auto Value = ::ams::sf::impl::MakeServiceCommandMeta<VERSION_MIN, VERSION_MAX, CMD_ID, &Interface::AMS_SF_IMPL_SYNC_FUNCTION_NAME(NAME), RETURN, Interface, Arguments...>(); \ + }; + + #define AMS_SF_IMPL_GET_NULL_FOR_PARAMETER_COUNT(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + , 0 + + #define AMS_SF_IMPL_DEFINE_CMIF_SERVICE_COMMAND_META_TABLE_ENTRY(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + NAME##ServiceCommandMetaHolder<Interface, NAME##ArgumentsType>::Value, + + template<typename...> + struct Print; + + #define AMS_SF_IMPL_DEFINE_INTERFACE(BASECLASS, CLASSNAME, CMD_MACRO, INTF_ID) \ + class CLASSNAME : public BASECLASS { \ + public: \ + static constexpr u32 InterfaceIdForDebug = INTF_ID; \ + private: \ + CMD_MACRO(CLASSNAME, AMS_SF_IMPL_DEFINE_INTERFACE_SYNC_METHOD) \ + public: \ + CMD_MACRO(CLASSNAME, AMS_SF_IMPL_EXTRACT_INTERFACE_SYNC_METHOD_ARGUMENTS) \ + public: \ + CMD_MACRO(CLASSNAME, AMS_SF_IMPL_DEFINE_INTERFACE_METHOD) \ + private: \ + CMD_MACRO(CLASSNAME, AMS_SF_IMPL_DEFINE_INTERFACE_SERVICE_COMMAND_META_HOLDER) \ + public: \ + template<typename Interface> \ + static constexpr inline ::ams::sf::cmif::ServiceDispatchTable s_CmifServiceDispatchTable { \ + [] { \ + constexpr size_t CurSize = ::ams::sf::impl::ParameterCount<0 CMD_MACRO(CLASSNAME, AMS_SF_IMPL_GET_NULL_FOR_PARAMETER_COUNT) >; \ + std::array<::ams::sf::cmif::ServiceCommandMeta, CurSize> cur_entries { CMD_MACRO(CLASSNAME, AMS_SF_IMPL_DEFINE_CMIF_SERVICE_COMMAND_META_TABLE_ENTRY) }; \ + \ + constexpr const auto &BaseEntries = ::ams::sf::cmif::ServiceDispatchTraits<BASECLASS>::DispatchTable.GetEntries(); \ + constexpr size_t BaseSize = BaseEntries.size(); \ + \ + constexpr size_t CombinedSize = BaseSize + CurSize; \ + \ + std::array<size_t, CombinedSize> map{}; \ + for (size_t i = 0; i < CombinedSize; ++i) { map[i] = i; } \ + \ + for (size_t i = 1; i < CombinedSize; ++i) { \ + size_t j = i; \ + while (j > 0) { \ + const auto li = map[j]; \ + const auto ri = map[j - 1]; \ + \ + const auto &lhs = (li < BaseSize) ? BaseEntries[li] : cur_entries[li - BaseSize]; \ + const auto &rhs = (ri < BaseSize) ? BaseEntries[ri] : cur_entries[ri - BaseSize]; \ + \ + if (!(rhs > lhs)) { \ + break; \ + } \ + \ + std::swap(map[j], map[j - 1]); \ + \ + --j; \ + } \ + } \ + \ + std::array<::ams::sf::cmif::ServiceCommandMeta, CombinedSize> combined_entries{}; \ + for (size_t i = 0; i < CombinedSize; ++i) { \ + if (map[i] < BaseSize) { \ + combined_entries[i] = BaseEntries[map[i]]; \ + } else { \ + combined_entries[i] = cur_entries[map[i] - BaseSize]; \ + } \ + } \ + \ + return ::ams::sf::cmif::ServiceDispatchTable<Interface::InterfaceIdForDebug, CombinedSize> { combined_entries }; \ + }() \ + }; \ + }; + + #define AMS_SF_IMPL_DEFINE_CONCEPT_HELPERS(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + template<typename T, typename... Args> \ + concept Is##CLASSNAME##__##NAME##Impl = requires (T &t, Args &&... args) { \ + { t.NAME(std::forward<Args>(args)...) } -> std::same_as<RETURN>; \ + }; \ + \ + template<typename T, typename A> \ + struct Is##CLASSNAME##__##NAME##Holder : std::false_type{}; \ + \ + template<typename T, typename... Args> requires std::same_as<std::tuple<Args...>, CLASSNAME::NAME##ArgumentsType> \ + struct Is##CLASSNAME##__##NAME##Holder<T, std::tuple<Args...>> : std::bool_constant<Is##CLASSNAME##__##NAME##Impl<T, Args...>>{}; \ + \ + template<typename T> \ + static constexpr inline bool Is##CLASSNAME##__##NAME = Is##CLASSNAME##__##NAME##Holder<T, CLASSNAME::NAME##ArgumentsType>::value; + + #define AMS_SF_IMPL_CHECK_CONCEPT_HELPER(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + Is##CLASSNAME##__##NAME<T> && + + #define AMS_SF_IMPL_DEFINE_CONCEPT(CLASSNAME, CMD_MACRO) \ + CMD_MACRO(CLASSNAME, AMS_SF_IMPL_DEFINE_CONCEPT_HELPERS) \ + \ + template<typename T> \ + concept Is##CLASSNAME = CMD_MACRO(CLASSNAME, AMS_SF_IMPL_CHECK_CONCEPT_HELPER) true; + + #define AMS_SF_DEFINE_INTERFACE_IMPL(BASECLASS, CLASSNAME, CMD_MACRO, INTF_ID) \ + AMS_SF_IMPL_DEFINE_INTERFACE(BASECLASS, CLASSNAME, CMD_MACRO, INTF_ID) \ + AMS_SF_IMPL_DEFINE_CONCEPT(CLASSNAME, CMD_MACRO) \ + static_assert(Is##CLASSNAME<CLASSNAME>); + + #define AMS_SF_METHOD_INFO_7(CLASSNAME, HANDLER, CMD_ID, RETURN, NAME, ARGS, ARGNAMES) \ + HANDLER(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, hos::Version_Min, hos::Version_Max) + + #define AMS_SF_METHOD_INFO_8(CLASSNAME, HANDLER, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN) \ + HANDLER(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, hos::Version_Max) + + #define AMS_SF_METHOD_INFO_9(CLASSNAME, HANDLER, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + HANDLER(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) + + #define AMS_SF_METHOD_INFO_X(_, _0, _1, _2, _3, _4, _5, _6, _7, _8, FUNC, ...) FUNC + + #define AMS_SF_METHOD_INFO(...) \ + AMS_SF_METHOD_INFO_X(, ## __VA_ARGS__, AMS_SF_METHOD_INFO_9(__VA_ARGS__), AMS_SF_METHOD_INFO_8(__VA_ARGS__), AMS_SF_METHOD_INFO_7(__VA_ARGS__)) + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/impl/sf_impl_command_serialization.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/impl/sf_impl_command_serialization.hpp new file mode 100644 index 00000000..c91d4480 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/impl/sf_impl_command_serialization.hpp @@ -0,0 +1,1321 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/sf/sf_common.hpp> +#include <stratosphere/sf/sf_service_object.hpp> +#include <stratosphere/sf/sf_out.hpp> +#include <stratosphere/sf/sf_buffers.hpp> +#include <stratosphere/sf/sf_native_handle.hpp> +#include <stratosphere/sf/cmif/sf_cmif_pointer_and_size.hpp> +#include <stratosphere/sf/cmif/sf_cmif_service_dispatch.hpp> +#include <stratosphere/sf/cmif/sf_cmif_service_object_holder.hpp> +#include <stratosphere/sf/cmif/sf_cmif_domain_api.hpp> +#include <stratosphere/sf/hipc/sf_hipc_api.hpp> +#include <stratosphere/sf/hipc/sf_hipc_server_session_manager.hpp> + +/* Serialization classes. */ +namespace ams::sf { + + namespace impl { + + struct ProcessIdHolder { + os::ProcessId process_id; + + constexpr explicit operator os::ProcessId() const { return this->process_id; } + constexpr os::ProcessId GetValue() const { return this->process_id; } + constexpr void SetValue(const os::ProcessId &p) { this->process_id = p; } + }; + + } + + struct ClientProcessId : public impl::ProcessIdHolder {}; + static_assert(std::is_trivial<ClientProcessId>::value && sizeof(ClientProcessId) == sizeof(os::ProcessId), "ClientProcessId"); + + struct ClientAppletResourceUserId : public impl::ProcessIdHolder {}; + static_assert(std::is_trivial<ClientAppletResourceUserId >::value && sizeof(ClientAppletResourceUserId ) == sizeof(os::ProcessId), "ClientAppletResourceUserId"); + + namespace impl { + + constexpr inline Result MarshalProcessId(ClientProcessId &client, const os::ProcessId &client_process_id) { + client.SetValue(client_process_id); + R_SUCCEED(); + } + + constexpr inline Result MarshalProcessId(ClientAppletResourceUserId &client, const os::ProcessId &client_process_id) { + if (client.GetValue() != client_process_id && client.GetValue() != os::ProcessId{}) { + R_THROW(sf::ResultPreconditionViolation()); + } + R_SUCCEED(); + } + + } + + namespace impl { + + struct OutObjectTag{}; + + template<size_t, size_t> + class InOutObjectHolder; + + } + + template<typename ServiceImpl> requires std::derived_from<ServiceImpl, IServiceObject> + class Out<SharedPointer<ServiceImpl>> : public impl::OutObjectTag { + template<typename> + friend class Out; + public: + using Interface = ServiceImpl; + private: + impl::SharedPointerBase *m_out; + cmif::DomainObjectId *m_object_id; + private: + Out(impl::SharedPointerBase *p, cmif::DomainObjectId *o = nullptr) : m_out(p), m_object_id(o) { /* ... */ } + public: + Out(const Out &rhs) : m_out(rhs.m_out), m_object_id(rhs.m_object_id) { /* ... */ } + + Out(SharedPointer<ServiceImpl> *out, cmif::DomainObjectId *o = nullptr) : m_out(std::addressof(out->m_base)), m_object_id(o) { /* .... */ } + + template<typename U> requires std::derived_from<U, ServiceImpl> + Out(Out<SharedPointer<U>> out) : m_out(out.m_out), m_object_id(out.m_object_id) { /* ... */ } + + template<typename U> requires std::derived_from<U, ServiceImpl> + Out(SharedPointer<U> *out, cmif::DomainObjectId *o = nullptr) : m_out(std::addressof(out->m_base)), m_object_id(o) { /* .... */ } + + void SetValue(SharedPointer<ServiceImpl> s, cmif::DomainObjectId new_object_id = cmif::InvalidDomainObjectId) { + *m_out = std::move(s.m_base); + if (new_object_id != cmif::InvalidDomainObjectId) { + AMS_ABORT_UNLESS(m_object_id != nullptr); + *m_object_id = new_object_id; + } + } + + template<typename U> requires std::derived_from<U, ServiceImpl> + void SetValue(SharedPointer<U> s, cmif::DomainObjectId new_object_id = cmif::InvalidDomainObjectId) { + *m_out = std::move(s.m_base); + if (new_object_id != cmif::InvalidDomainObjectId) { + AMS_ABORT_UNLESS(m_object_id != nullptr); + *m_object_id = new_object_id; + } + } + + class DerefProxy { + template<typename> + friend class Out; + private: + Out &m_target; + private: + explicit DerefProxy(Out &t) : m_target(t) { /* ... */ } + public: + DerefProxy &operator=(SharedPointer<ServiceImpl> p) { + m_target.SetValue(std::move(p)); + return *this; + } + + template<typename U> requires std::derived_from<U, ServiceImpl> + DerefProxy &operator=(SharedPointer<U> p) { + m_target.SetValue(std::move(p)); + return *this; + } + }; + + DerefProxy operator *() { + return DerefProxy(*this); + } + + template<typename U> + Out<SharedPointer<U>> DownCast() { + return Out<SharedPointer<U>>(m_out, m_object_id); + } + }; + +} + + +#if defined(ATMOSPHERE_OS_HORIZON) +namespace ams::sf::impl { + + /* Machinery for filtering type lists. */ + template<class, class> + struct TupleCat; + + template<class... First, class... Second> + struct TupleCat<std::tuple<First...>, std::tuple<Second...>> { + using type = std::tuple<First..., Second...>; + }; + + template<template <typename> typename Cond> + struct TupleFilter { + private: + template<typename, typename> + struct ImplType; + + template<typename Res> + struct ImplType<Res, std::tuple<>> { + using type = Res; + }; + + template<typename Res, typename T, typename... Ts> + struct ImplType<Res, std::tuple<T, Ts...>> { + using type = typename std::conditional<Cond<T>::value, + typename ImplType<typename TupleCat<Res, std::tuple<T>>::type, std::tuple<Ts...>>::type, + typename ImplType<Res, std::tuple<Ts...>>::type + >::type; + }; + public: + template<typename T> + using FilteredType = typename ImplType<std::tuple<>, T>::type; + }; + + enum class ArgumentType { + InData, + OutData, + Buffer, + InHandle, + OutHandle, + InObject, + OutObject, + }; + + template<typename T> + struct IsInObject : public std::false_type{}; + + template<typename T> + struct IsInObject<sf::SharedPointer<T>> : public std::true_type { + static_assert(std::is_base_of<sf::IServiceObject, T>::value, "Invalid IsInObject<sf::SharedPointer<T>>"); + }; + + template<typename T> + constexpr inline ArgumentType GetArgumentType = [] { + static_assert(!std::same_as<T, sf::NativeHandle>); + static_assert(!std::same_as<T, sf::Out<sf::NativeHandle>>); + + if constexpr (sf::IsBuffer<T>) { + return ArgumentType::Buffer; + } else if constexpr (IsInObject<T>::value) { + return ArgumentType::InObject; + } else if constexpr (std::is_base_of<sf::impl::OutObjectTag, T>::value) { + return ArgumentType::OutObject; + } else if constexpr (std::same_as<T, sf::CopyHandle> || std::same_as<T, sf::MoveHandle>) { + return ArgumentType::InHandle; + } else if constexpr (std::same_as<T, sf::OutCopyHandle> || std::same_as<T, sf::OutMoveHandle>) { + return ArgumentType::OutHandle; + } else if constexpr (std::is_base_of<sf::impl::OutBaseTag, T>::value) { + return ArgumentType::OutData; + } else if constexpr (std::is_trivial<T>::value && !std::is_pointer<T>::value) { + return ArgumentType::InData; + } else if constexpr (std::is_same<T, ::ams::Result>::value) { + return ArgumentType::InData; + } else { + static_assert(!std::is_same<T, T>::value, "Invalid ArgumentType<T>"); + } + }(); + + template<typename T, ArgumentType ArgT> + struct ArgumentTypeFilter : public std::bool_constant<GetArgumentType<T> == ArgT>{}; + + template<typename T, typename U> + struct TypeEqualityFilter : public std::bool_constant<std::is_same<T, U>::value>{}; + + /* Argument type filters. */ + template<typename T> + using InDataFilter = ArgumentTypeFilter<T, ArgumentType::InData>; + + template<typename T> + using OutDataFilter = ArgumentTypeFilter<T, ArgumentType::OutData>; + + template<typename T> + using BufferFilter = ArgumentTypeFilter<T, ArgumentType::Buffer>; + + template<typename T> + using InHandleFilter = ArgumentTypeFilter<T, ArgumentType::InHandle>; + + template<typename T> + using OutHandleFilter = ArgumentTypeFilter<T, ArgumentType::OutHandle>; + + template<typename T> + using InObjectFilter = ArgumentTypeFilter<T, ArgumentType::InObject>; + + template<typename T> + using OutObjectFilter = ArgumentTypeFilter<T, ArgumentType::OutObject>; + + template<typename T> + struct ProcessIdHolderFilter : public std::bool_constant<std::is_base_of<sf::impl::ProcessIdHolder, T>::value>{}; + + /* Handle kind filters. */ + template<typename T> + using InMoveHandleFilter = TypeEqualityFilter<T, sf::MoveHandle>; + + template<typename T> + using InCopyHandleFilter = TypeEqualityFilter<T, sf::CopyHandle>; + + template<typename T> + using OutMoveHandleFilter = TypeEqualityFilter<T, sf::Out<sf::MoveHandle>>; + + template<typename T> + using OutCopyHandleFilter = TypeEqualityFilter<T, sf::Out<sf::CopyHandle>>; + + template<typename> + struct BufferAttributeArrayGetter; + + template<typename ...Ts> + struct BufferAttributeArrayGetter<std::tuple<Ts...>> { + static constexpr std::array<u32, sizeof...(Ts)> value = { BufferAttributes<Ts>..., }; + }; + + template<> + struct BufferAttributeArrayGetter<std::tuple<>> { + static constexpr std::array<u32, 0> value{}; + }; + + template<auto Predicate> + struct BufferAttributeCounter { + + template<size_t Size> + static constexpr size_t GetCount(const std::array<u32, Size> &attributes_array) { + size_t count = 0; + for (size_t i = 0; i < Size; i++) { + if (Predicate(attributes_array[i])) { + count++; + } + } + return count; + } + + }; + + NX_CONSTEXPR size_t InHipcMapAliasBufferPredicate(const u32 attribute) { + if ((attribute & SfBufferAttr_In) && !(attribute & SfBufferAttr_Out)) { + return (attribute & SfBufferAttr_HipcMapAlias) || (attribute & SfBufferAttr_HipcAutoSelect); + } else { + return false; + } + } + + NX_CONSTEXPR size_t OutHipcMapAliasBufferPredicate(const u32 attribute) { + if (!(attribute & SfBufferAttr_In) && (attribute & SfBufferAttr_Out)) { + return (attribute & SfBufferAttr_HipcMapAlias) || (attribute & SfBufferAttr_HipcAutoSelect); + } else { + return false; + } + } + + NX_CONSTEXPR size_t InHipcPointerBufferPredicate(const u32 attribute) { + if ((attribute & SfBufferAttr_In) && !(attribute & SfBufferAttr_Out)) { + return (attribute & SfBufferAttr_HipcPointer) || (attribute & SfBufferAttr_HipcAutoSelect); + } else { + return false; + } + } + + NX_CONSTEXPR size_t OutHipcPointerBufferPredicate(const u32 attribute) { + if (!(attribute & SfBufferAttr_In) && (attribute & SfBufferAttr_Out)) { + return (attribute & SfBufferAttr_HipcPointer) || (attribute & SfBufferAttr_HipcAutoSelect); + } else { + return false; + } + } + + NX_CONSTEXPR size_t FixedSizeOutHipcPointerBufferPredicate(const u32 attribute) { + return OutHipcPointerBufferPredicate(attribute) && (attribute & SfBufferAttr_FixedSize); + } + + template<typename> + struct RawDataOffsetCalculator; + + template<typename... Ts> + struct RawDataOffsetCalculator<std::tuple<Ts...>> { + private: + template<typename T> + struct LayoutHelper { + static_assert(GetArgumentType<T> == ArgumentType::InData, "LayoutHelper InData"); + static constexpr size_t Alignment = alignof(T); + static constexpr size_t Size = sizeof(T); + }; + template<typename T> + struct LayoutHelper<Out<T>> { + static_assert(GetArgumentType<T> == ArgumentType::InData, "LayoutHelper OutData"); + static constexpr size_t Alignment = alignof(T); + static constexpr size_t Size = sizeof(T); + }; + + static constexpr void StableSort(std::array<size_t, sizeof...(Ts)> &map, const std::array<size_t, sizeof...(Ts)> &values) { + /* Use insertion sort, which is stable and optimal for small numbers of parameters. */ + for (size_t i = 1; i < sizeof...(Ts); i++) { + for (size_t j = i; j > 0 && values[map[j-1]] > values[map[j]]; j--) { + std::swap(map[j], map[j-1]); + } + } + } + public: + static constexpr std::array<size_t, sizeof...(Ts)+1> Offsets = [] { + std::array<size_t, sizeof...(Ts)+1> offsets{}; + offsets[0] = 0; + if constexpr (sizeof...(Ts) > 0) { + /* Get size, alignment for each type. */ + const std::array<size_t, sizeof...(Ts)> sizes = { LayoutHelper<Ts>::Size... }; + const std::array<size_t, sizeof...(Ts)> aligns = { LayoutHelper<Ts>::Alignment... }; + + /* We want to sort...by alignment. */ + std::array<size_t, sizeof...(Ts)> map = {}; + for (size_t i = 0; i < sizeof...(Ts); i++) { map[i] = i; } + StableSort(map, aligns); + + /* Iterate over sorted sizes. */ + size_t cur_offset = 0; + for (size_t i : map) { + cur_offset = util::AlignUp(cur_offset, aligns[i]); + offsets[i] = cur_offset; + cur_offset += sizes[i]; + } + offsets[sizeof...(Ts)] = cur_offset; + } + return offsets; + }(); + + }; + + struct ArgumentSerializationInfo { + /* Type used to select from below fields. */ + ArgumentType arg_type; + + /* Raw data indexing. */ + size_t in_raw_data_index; + size_t out_raw_data_index; + + /* Buffer indexing. */ + size_t buffer_index; + size_t send_map_alias_index; + size_t recv_map_alias_index; + size_t send_pointer_index; + size_t recv_pointer_index; + size_t unfixed_recv_pointer_index; + size_t fixed_size; + + /* Handle indexing. */ + size_t in_move_handle_index; + size_t in_copy_handle_index; + size_t out_move_handle_index; + size_t out_copy_handle_index; + + /* Object indexing. */ + size_t in_object_index; + size_t out_object_index; + }; + + template<typename T> + using DecayForCommandMetaArguments = typename std::conditional<(sf::IsLargeData<typename std::decay<T>::type> && !std::is_base_of<impl::OutBaseTag, typename std::decay<T>::type>::value), T, typename std::conditional<(std::same_as<T, sf::MoveHandle &&> || std::same_as<T, sf::CopyHandle &&>), typename std::decay<T>::type &, typename std::decay<T>::type>::type>::type; + + template<typename... Arguments> + struct CommandMetaInfo { + public: + using ArgsTypeForInvoke = std::tuple<DecayForCommandMetaArguments<Arguments>...>; + using ArgsType = std::tuple<typename std::decay<Arguments>::type...>; + + using InDatas = TupleFilter<InDataFilter>::FilteredType<ArgsType>; + using OutDatas = TupleFilter<OutDataFilter>::FilteredType<ArgsType>; + using Buffers = TupleFilter<BufferFilter>::FilteredType<ArgsType>; + using InHandles = TupleFilter<InHandleFilter>::FilteredType<ArgsType>; + using OutHandles = TupleFilter<OutHandleFilter>::FilteredType<ArgsType>; + using InObjects = TupleFilter<InObjectFilter>::FilteredType<ArgsType>; + using OutObjects = TupleFilter<OutObjectFilter>::FilteredType<ArgsType>; + + /* Not kept separate from InDatas when processing, for reasons. */ + using InProcessIdHolders = TupleFilter<ProcessIdHolderFilter>::FilteredType<InDatas>; + /* TODO: Support OutProcessIdHolders? */ + + static constexpr size_t NumInDatas = std::tuple_size<InDatas>::value; + static constexpr size_t NumOutDatas = std::tuple_size<OutDatas>::value; + static constexpr size_t NumBuffers = std::tuple_size<Buffers>::value; + static constexpr size_t NumInHandles = std::tuple_size<InHandles>::value; + static constexpr size_t NumOutHandles = std::tuple_size<OutHandles>::value; + static constexpr size_t NumInObjects = std::tuple_size<InObjects>::value; + static constexpr size_t NumOutObjects = std::tuple_size<OutObjects>::value; + + static constexpr size_t NumInProcessIdHolders = std::tuple_size<InProcessIdHolders>::value; + static constexpr bool HasInProcessIdHolder = NumInProcessIdHolders >= 1; + + template<typename T> + static constexpr bool IsInProcessIdHolder = GetArgumentType<T> == ArgumentType::InData && std::is_base_of<sf::impl::ProcessIdHolder, T>::value; + + template<size_t I> + static constexpr bool IsInProcessIdHolderIndex = I < std::tuple_size<ArgsType>::value && IsInProcessIdHolder<typename std::tuple_element<I, ArgsType>::type>; + + static_assert(NumBuffers <= 8, "Methods must take in <= 8 Buffers"); + static_assert(NumInHandles <= 8, "Methods must take in <= 8 Handles"); + static_assert(NumOutHandles + NumOutObjects <= 8, "Methods must output <= 8 Handles"); + + /* Buffer marshalling. */ + static constexpr std::array<u32, NumBuffers> BufferAttributes = BufferAttributeArrayGetter<Buffers>::value; + static constexpr size_t NumInHipcMapAliasBuffers = BufferAttributeCounter<InHipcMapAliasBufferPredicate>::GetCount(BufferAttributes); + static constexpr size_t NumOutHipcMapAliasBuffers = BufferAttributeCounter<OutHipcMapAliasBufferPredicate>::GetCount(BufferAttributes); + static constexpr size_t NumInHipcPointerBuffers = BufferAttributeCounter<InHipcPointerBufferPredicate>::GetCount(BufferAttributes); + static constexpr size_t NumOutHipcPointerBuffers = BufferAttributeCounter<OutHipcPointerBufferPredicate>::GetCount(BufferAttributes); + static constexpr size_t NumFixedSizeOutHipcPointerBuffers = BufferAttributeCounter<FixedSizeOutHipcPointerBufferPredicate>::GetCount(BufferAttributes); + static constexpr size_t NumUnfixedSizeOutHipcPointerBuffers = NumOutHipcPointerBuffers - NumFixedSizeOutHipcPointerBuffers; + + /* In/Out data marshalling. */ + static constexpr std::array<size_t, NumInDatas+1> InDataOffsets = RawDataOffsetCalculator<InDatas>::Offsets; + static constexpr size_t InDataSize = util::AlignUp(InDataOffsets[NumInDatas], alignof(u16)); + + static constexpr std::array<size_t, NumOutDatas+1> OutDataOffsets = RawDataOffsetCalculator<OutDatas>::Offsets; + static constexpr size_t UnalignedOutDataSize = OutDataOffsets[NumOutDatas]; + static constexpr size_t OutDataSize = util::AlignUp(OutDataOffsets[NumOutDatas], alignof(u32)); + static constexpr size_t OutDataAlign = [] { + if constexpr (std::tuple_size<OutDatas>::value) { + return alignof(typename std::tuple_element<0, OutDatas>::type); + } + return static_cast<size_t>(0); + }(); + + /* Handle marshalling. */ + static constexpr size_t NumInMoveHandles = std::tuple_size<TupleFilter<InMoveHandleFilter>::FilteredType<InHandles>>::value; + static constexpr size_t NumInCopyHandles = std::tuple_size<TupleFilter<InCopyHandleFilter>::FilteredType<InHandles>>::value; + + static constexpr size_t NumOutMoveHandles = std::tuple_size<TupleFilter<OutMoveHandleFilter>::FilteredType<OutHandles>>::value; + static constexpr size_t NumOutCopyHandles = std::tuple_size<TupleFilter<OutCopyHandleFilter>::FilteredType<OutHandles>>::value; + + static_assert(NumInMoveHandles + NumInCopyHandles == NumInHandles, "NumInMoveHandles + NumInCopyHandles == NumInHandles"); + static_assert(NumOutMoveHandles + NumOutCopyHandles == NumOutHandles, "NumOutMoveHandles + NumOutCopyHandles == NumOutHandles"); + + /* Used by server message processor at runtime. */ + static constexpr inline const cmif::ServerMessageRuntimeMetadata RuntimeMetadata = cmif::ServerMessageRuntimeMetadata{ + .in_data_size = InDataSize, + .unaligned_out_data_size = UnalignedOutDataSize, + .in_headers_size = sizeof(CmifInHeader), + .out_headers_size = sizeof(CmifOutHeader), + .in_object_count = NumInObjects, + .out_object_count = NumOutObjects, + }; + + /* Construction of argument serialization structs. */ + private: + template<typename> + struct ArgumentSerializationInfoConstructor; + + template<typename ...Ts> + struct ArgumentSerializationInfoConstructor<std::tuple<Ts...>> { + + template<typename T> + static constexpr ArgumentSerializationInfo ProcessUpdate(ArgumentSerializationInfo ¤t_info) { + /* Save a copy of the current state to return. */ + ArgumentSerializationInfo returned_info = current_info; + + /* Clear previous iteration's fixed size. */ + returned_info.fixed_size = 0; + current_info.fixed_size = 0; + + constexpr auto arg_type = GetArgumentType<T>; + returned_info.arg_type = arg_type; + if constexpr (arg_type == ArgumentType::InData) { + /* New rawdata, so increment index. */ + current_info.in_raw_data_index++; + } else if constexpr (arg_type == ArgumentType::OutData) { + /* New rawdata, so increment index. */ + current_info.out_raw_data_index++; + } else if constexpr (arg_type == ArgumentType::InHandle) { + /* New InHandle, increment the appropriate index. */ + if constexpr (std::is_same<T, sf::MoveHandle>::value) { + current_info.in_move_handle_index++; + } else if constexpr (std::is_same<T, sf::CopyHandle>::value) { + current_info.in_copy_handle_index++; + } else { + static_assert(!std::is_same<T, T>::value, "Invalid InHandle kind"); + } + } else if constexpr (arg_type == ArgumentType::OutHandle) { + /* New OutHandle, increment the appropriate index. */ + if constexpr (std::is_same<T, sf::Out<sf::MoveHandle>>::value) { + current_info.out_move_handle_index++; + } else if constexpr (std::is_same<T, sf::Out<sf::CopyHandle>>::value) { + current_info.out_copy_handle_index++; + } else { + static_assert(!std::is_same<T, T>::value, "Invalid OutHandle kind"); + } + } else if constexpr (arg_type == ArgumentType::InObject) { + /* New InObject, increment the appropriate index. */ + current_info.in_object_index++; + } else if constexpr (arg_type == ArgumentType::OutObject) { + /* New OutObject, increment the appropriate index. */ + current_info.out_object_index++; + } else if constexpr (arg_type == ArgumentType::Buffer) { + /* New Buffer, increment the appropriate index. */ + const auto attributes = BufferAttributes[current_info.buffer_index]; + current_info.buffer_index++; + + if (attributes & SfBufferAttr_HipcMapAlias) { + if (attributes & SfBufferAttr_In) { + current_info.send_map_alias_index++; + } else if (attributes & SfBufferAttr_Out) { + current_info.recv_map_alias_index++; + } + } else if (attributes & SfBufferAttr_HipcPointer) { + if (attributes & SfBufferAttr_In) { + current_info.send_pointer_index++; + } else if (attributes & SfBufferAttr_Out) { + current_info.recv_pointer_index++; + if (!(attributes & SfBufferAttr_FixedSize)) { + current_info.unfixed_recv_pointer_index++; + } else { + returned_info.fixed_size = LargeDataSize<T>; + } + } + } else if (attributes & SfBufferAttr_HipcAutoSelect) { + if (attributes & SfBufferAttr_In) { + current_info.send_map_alias_index++; + current_info.send_pointer_index++; + } else if (attributes & SfBufferAttr_Out) { + current_info.recv_map_alias_index++; + current_info.recv_pointer_index++; + if (!(attributes & SfBufferAttr_FixedSize)) { + current_info.unfixed_recv_pointer_index++; + } else { + returned_info.fixed_size = LargeDataSize<T>; + } + } + } + } else { + static_assert(!std::is_same<T, T>::value, "Invalid ArgumentType<T>"); + } + + return returned_info; + } + + static constexpr std::array<ArgumentSerializationInfo, sizeof...(Ts)> ArgumentSerializationInfos = [] { + ArgumentSerializationInfo current_info = {}; + + return std::array<ArgumentSerializationInfo, sizeof...(Ts)>{ ProcessUpdate<Ts>(current_info)... }; + }(); + + }; + public: + static constexpr std::array<ArgumentSerializationInfo, std::tuple_size<ArgsType>::value> ArgumentSerializationInfos = ArgumentSerializationInfoConstructor<ArgsType>::ArgumentSerializationInfos; + }; + + template<size_t _Size, size_t _Align> + class OutRawHolder { + public: + static constexpr size_t Size = _Size; + static constexpr size_t Align = _Align ? _Align : alignof(u8); + private: + alignas(Align) u8 m_data[Size]; + public: + constexpr OutRawHolder() : m_data() { /* ... */ } + + template<size_t Offset, size_t TypeSize> + constexpr inline uintptr_t GetAddress() const { + static_assert(Offset <= Size, "Offset <= Size"); + static_assert(TypeSize <= Size, "TypeSize <= Size"); + static_assert(Offset + TypeSize <= Size, "Offset + TypeSize <= Size"); + return reinterpret_cast<uintptr_t>(std::addressof(m_data[Offset])); + } + + constexpr inline void CopyTo(void *dst) const { + if constexpr (Size > 0) { + std::memcpy(dst, m_data, Size); + } + } + }; + + template<size_t _NumMove, size_t _NumCopy> + class InHandleHolder { + public: + static constexpr size_t NumMove = _NumMove; + static constexpr size_t NumCopy = _NumCopy; + private: + MoveHandle m_move_handles[NumMove]; + CopyHandle m_copy_handles[NumCopy]; + public: + constexpr InHandleHolder() : m_move_handles(), m_copy_handles() { /* ... */ } + + template<size_t Index> + constexpr inline MoveHandle &SetMoveHandle(os::NativeHandle os_handle) { + static_assert(Index < NumMove); + m_move_handles[Index] = sf::NativeHandle(os_handle, true); + return m_move_handles[Index]; + } + + template<size_t Index> + constexpr inline CopyHandle &SetCopyHandle(os::NativeHandle os_handle) { + static_assert(Index < NumCopy); + m_copy_handles[Index] = sf::NativeHandle(os_handle, true); + return m_copy_handles[Index]; + } + }; + + template<size_t _NumMove, size_t _NumCopy> + class OutHandleHolder { + public: + static constexpr size_t NumMove = _NumMove; + static constexpr size_t NumCopy = _NumCopy; + private: + NativeHandle m_move_handles[NumMove]; + NativeHandle m_copy_handles[NumCopy]; + public: + constexpr OutHandleHolder() : m_move_handles(), m_copy_handles() { /* ... */ } + + template<size_t Index> + constexpr inline NativeHandle *GetMoveHandlePointer() { + static_assert(Index < NumMove, "Index < NumMove"); + return m_move_handles + Index; + } + + template<size_t Index> + constexpr inline NativeHandle *GetCopyHandlePointer() { + static_assert(Index < NumCopy, "Index < NumCopy"); + return m_copy_handles + Index; + } + + constexpr inline void CopyTo(const cmif::ServiceDispatchContext &ctx, const HipcRequest &response, const size_t num_out_object_handles) { + ctx.handles_to_close->num_handles = 0; + #define _SF_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE(n) do { if constexpr (NumCopy > n) { const auto handle = m_copy_handles[n].GetOsHandle(); response.copy_handles[n] = handle; if (m_copy_handles[n].IsManaged()) { ctx.handles_to_close->handles[ctx.handles_to_close->num_handles++] = handle; } m_copy_handles[n].Detach(); } } while (0) + _SF_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE(0); + _SF_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE(1); + _SF_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE(2); + _SF_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE(3); + _SF_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE(4); + _SF_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE(5); + _SF_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE(6); + _SF_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE(7); + #undef _SF_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE + #define _SF_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE(n) do { if constexpr (NumMove > n) { response.move_handles[n + num_out_object_handles] = m_move_handles[n].GetOsHandle(); m_move_handles[n].Detach(); } } while (0) + _SF_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE(0); + _SF_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE(1); + _SF_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE(2); + _SF_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE(3); + _SF_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE(4); + _SF_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE(5); + _SF_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE(6); + _SF_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE(7); + #undef _SF_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE + } + }; + + template<size_t NumInObjects, size_t NumOutObjects> + class InOutObjectHolder { + private: + std::array<cmif::ServiceObjectHolder, NumInObjects> m_in_object_holders; + std::array<cmif::ServiceObjectHolder, NumOutObjects> m_out_object_holders; + std::array<util::TypedStorage<SharedPointer<sf::IServiceObject>>, NumOutObjects> m_out_shared_pointers; + std::array<cmif::DomainObjectId, NumOutObjects> m_out_object_ids; + public: + constexpr InOutObjectHolder() : m_in_object_holders(), m_out_object_holders() { + #define _SF_IN_OUT_HOLDER_INITIALIZE_OBJECT_ID(n) if constexpr (NumOutObjects > n) { m_out_object_ids[n] = cmif::InvalidDomainObjectId; } + _SF_IN_OUT_HOLDER_INITIALIZE_OBJECT_ID(0) + _SF_IN_OUT_HOLDER_INITIALIZE_OBJECT_ID(1) + _SF_IN_OUT_HOLDER_INITIALIZE_OBJECT_ID(2) + _SF_IN_OUT_HOLDER_INITIALIZE_OBJECT_ID(3) + _SF_IN_OUT_HOLDER_INITIALIZE_OBJECT_ID(4) + _SF_IN_OUT_HOLDER_INITIALIZE_OBJECT_ID(5) + _SF_IN_OUT_HOLDER_INITIALIZE_OBJECT_ID(6) + _SF_IN_OUT_HOLDER_INITIALIZE_OBJECT_ID(7) + #undef _SF_IN_OUT_HOLDER_INITIALIZE_OBJECT_ID + } + + Result GetInObjects(const sf::cmif::ServerMessageProcessor *processor) { + if constexpr (NumInObjects > 0) { + R_TRY(processor->GetInObjects(m_in_object_holders.data())); + } + R_SUCCEED(); + } + + template<typename ServiceImplTuple> + constexpr inline Result ValidateInObjects() const { + static_assert(std::tuple_size<ServiceImplTuple>::value == NumInObjects); + #define _SF_IN_OUT_HOLDER_VALIDATE_IN_OBJECT(n) do { \ + if constexpr (NumInObjects > n) { \ + using SharedPointerType = typename std::tuple_element<n, ServiceImplTuple>::type; \ + using ServiceImplType = typename SharedPointerType::Interface; \ + R_UNLESS((m_in_object_holders[n].template IsServiceObjectValid<ServiceImplType>()), sf::cmif::ResultInvalidInObject()); \ + } \ + } while (0) + _SF_IN_OUT_HOLDER_VALIDATE_IN_OBJECT(0); + _SF_IN_OUT_HOLDER_VALIDATE_IN_OBJECT(1); + _SF_IN_OUT_HOLDER_VALIDATE_IN_OBJECT(2); + _SF_IN_OUT_HOLDER_VALIDATE_IN_OBJECT(3); + _SF_IN_OUT_HOLDER_VALIDATE_IN_OBJECT(4); + _SF_IN_OUT_HOLDER_VALIDATE_IN_OBJECT(5); + _SF_IN_OUT_HOLDER_VALIDATE_IN_OBJECT(6); + _SF_IN_OUT_HOLDER_VALIDATE_IN_OBJECT(7); + #undef _SF_IN_OUT_HOLDER_VALIDATE_IN_OBJECT + R_SUCCEED(); + } + + template<size_t Index, typename Interface> + SharedPointer<Interface> *GetOutObjectSharedPointer() { + static_assert(sizeof(SharedPointer<Interface>) == sizeof(SharedPointer<sf::IServiceObject>)); + return static_cast<SharedPointer<Interface> *>(static_cast<void *>(GetPointer(m_out_shared_pointers[Index]))); + } + + template<size_t Index, typename Interface> + Out<SharedPointer<Interface>> GetOutObject() { + auto sp = std::construct_at(GetOutObjectSharedPointer<Index, Interface>()); + return Out<SharedPointer<Interface>>(sp, std::addressof(m_out_object_ids[Index])); + } + + template<size_t Index, typename Interface> + void SetOutObject() { + m_out_object_holders[Index] = cmif::ServiceObjectHolder(std::move(*GetOutObjectSharedPointer<Index, Interface>())); + } + + constexpr void SetOutObjects(const cmif::ServiceDispatchContext &ctx, const HipcRequest &response) { + if constexpr (NumOutObjects > 0) { + ctx.processor->SetOutObjects(ctx, response, m_out_object_holders.data(), m_out_object_ids.data()); + } + } + }; + + class HipcCommandProcessorCommon : public sf::cmif::ServerMessageProcessor { + public: + virtual void SetImplementationProcessor(sf::cmif::ServerMessageProcessor *) override final { /* ... */ } + + virtual void PrepareForErrorReply(const cmif::ServiceDispatchContext &ctx, cmif::PointerAndSize &out_raw_data, const cmif::ServerMessageRuntimeMetadata runtime_metadata) override final { + const size_t raw_size = runtime_metadata.GetOutHeadersSize(); + const auto response = hipcMakeRequestInline(ctx.out_message_buffer.GetPointer(), + .type = CmifCommandType_Invalid, /* Really response */ + .num_data_words = static_cast<u32>((util::AlignUp(raw_size, 0x4) + 0x10 /* padding */) / sizeof(u32)), + ); + out_raw_data = cmif::PointerAndSize(util::AlignUp(reinterpret_cast<uintptr_t>(response.data_words), 0x10), raw_size); + } + + virtual Result GetInObjects(cmif::ServiceObjectHolder *in_objects) const override final { + /* By default, InObjects aren't supported. */ + AMS_UNUSED(in_objects); + R_THROW(sf::ResultNotSupported()); + } + }; + + template<typename CommandMeta> + struct HipcCommandProcessor : public HipcCommandProcessorCommon { + public: + virtual const cmif::ServerMessageRuntimeMetadata GetRuntimeMetadata() const override final { + return CommandMeta::RuntimeMetadata; + } + + virtual Result PrepareForProcess(const cmif::ServiceDispatchContext &ctx, const cmif::ServerMessageRuntimeMetadata runtime_metadata) const override final { + const auto &meta = ctx.request.meta; + bool is_request_valid = true; + is_request_valid &= meta.send_pid == CommandMeta::HasInProcessIdHolder; + is_request_valid &= meta.num_send_statics == CommandMeta::NumInHipcPointerBuffers; + /* is_request_valid &= meta.num_recv_statics == CommandMeta::NumOutHipcPointerBuffers; */ + is_request_valid &= meta.num_send_buffers == CommandMeta::NumInHipcMapAliasBuffers; + is_request_valid &= meta.num_recv_buffers == CommandMeta::NumOutHipcMapAliasBuffers; + is_request_valid &= meta.num_exch_buffers == 0; /* Exchange buffers aren't supported. */ + is_request_valid &= meta.num_copy_handles == CommandMeta::NumInCopyHandles; + is_request_valid &= meta.num_move_handles == CommandMeta::NumInMoveHandles; + + const size_t meta_raw_size = meta.num_data_words * sizeof(u32); + const size_t command_raw_size = util::AlignUp(runtime_metadata.GetUnfixedOutPointerSizeOffset() + (CommandMeta::NumUnfixedSizeOutHipcPointerBuffers * sizeof(u16)), alignof(u32)); + is_request_valid &= meta_raw_size >= command_raw_size; + + R_UNLESS(is_request_valid, sf::hipc::ResultInvalidCmifRequest()); + R_SUCCEED(); + } + + virtual HipcRequest PrepareForReply(const cmif::ServiceDispatchContext &ctx, cmif::PointerAndSize &out_raw_data, const cmif::ServerMessageRuntimeMetadata runtime_metadata) override final { + const size_t raw_size = runtime_metadata.GetOutDataSize() + runtime_metadata.GetOutHeadersSize(); + const auto response = hipcMakeRequestInline(ctx.out_message_buffer.GetPointer(), + .type = CmifCommandType_Invalid, /* Really response */ + .num_send_statics = CommandMeta::NumOutHipcPointerBuffers, + .num_data_words = static_cast<u32>((util::AlignUp(raw_size, 0x4) + 0x10 /* padding */) / sizeof(u32)), + .num_copy_handles = CommandMeta::NumOutCopyHandles, + .num_move_handles = static_cast<u32>(CommandMeta::NumOutMoveHandles + runtime_metadata.GetOutObjectCount()), + ); + out_raw_data = cmif::PointerAndSize(util::AlignUp(reinterpret_cast<uintptr_t>(response.data_words), 0x10), raw_size); + return response; + } + + virtual void SetOutObjects(const cmif::ServiceDispatchContext &ctx, const HipcRequest &response, cmif::ServiceObjectHolder *out_objects, cmif::DomainObjectId *ids) override final { + AMS_UNUSED(ids); + + #define _SF_IMPL_PROCESSOR_SET_OUT_OBJECT_IMPL(n) do { if constexpr (CommandMeta::NumOutObjects > n) { SetOutObjectImpl<n>(response, ctx.manager, std::move(out_objects[n])); } } while (0) + _SF_IMPL_PROCESSOR_SET_OUT_OBJECT_IMPL(0); + _SF_IMPL_PROCESSOR_SET_OUT_OBJECT_IMPL(1); + _SF_IMPL_PROCESSOR_SET_OUT_OBJECT_IMPL(2); + _SF_IMPL_PROCESSOR_SET_OUT_OBJECT_IMPL(3); + _SF_IMPL_PROCESSOR_SET_OUT_OBJECT_IMPL(4); + _SF_IMPL_PROCESSOR_SET_OUT_OBJECT_IMPL(5); + _SF_IMPL_PROCESSOR_SET_OUT_OBJECT_IMPL(6); + _SF_IMPL_PROCESSOR_SET_OUT_OBJECT_IMPL(7); + #undef _SF_IMPL_PROCESSOR_SET_OUT_OBJECT_IMPL + } + + /* Useful defines. */ + using ArgsTypeForInvoke = typename CommandMeta::ArgsTypeForInvoke; + using ArgsType = typename CommandMeta::ArgsType; + using BufferArrayType = std::array<cmif::PointerAndSize, CommandMeta::NumBuffers>; + using OutRawHolderType = OutRawHolder<CommandMeta::OutDataSize, CommandMeta::OutDataAlign>; + using InHandleHolderType = InHandleHolder<CommandMeta::NumInMoveHandles, CommandMeta::NumInCopyHandles>; + using OutHandleHolderType = OutHandleHolder<CommandMeta::NumOutMoveHandles, CommandMeta::NumOutCopyHandles>; + using InOutObjectHolderType = InOutObjectHolder<CommandMeta::NumInObjects, CommandMeta::NumOutObjects>; + + /* Buffer processing. */ + private: + template<size_t Index> + NX_CONSTEXPR void SetOutObjectImpl(const HipcRequest &response, hipc::ServerSessionManager *manager, cmif::ServiceObjectHolder &&object) { + /* If no object, write os::InvalidNativeHandle. This is what official software does. */ + if (!object) { + response.move_handles[Index] = os::InvalidNativeHandle; + return; + } + os::NativeHandle server_handle, client_handle; + R_ABORT_UNLESS(sf::hipc::CreateSession(std::addressof(server_handle), std::addressof(client_handle))); + R_ABORT_UNLESS(manager->RegisterSession(server_handle, std::move(object))); + response.move_handles[Index] = client_handle; + } + + template<u32 Attributes> + NX_CONSTEXPR bool IsMapTransferModeValid(u32 mode) { + static_assert(!((Attributes & SfBufferAttr_HipcMapTransferAllowsNonSecure) && (Attributes & SfBufferAttr_HipcMapTransferAllowsNonDevice)), "Invalid Attributes"); + if constexpr (Attributes & SfBufferAttr_HipcMapTransferAllowsNonSecure) { + return mode == HipcBufferMode_NonSecure; + } else if constexpr (Attributes & SfBufferAttr_HipcMapTransferAllowsNonDevice) { + return mode == HipcBufferMode_NonDevice; + } else { + return mode == HipcBufferMode_Normal; + } + } + + template<size_t BufferIndex> + static constexpr inline size_t GetIndexFromBufferIndex = [] { + for (size_t i = 0; i < CommandMeta::ArgumentSerializationInfos.size(); i++) { + const auto Info = CommandMeta::ArgumentSerializationInfos[i]; + if (Info.arg_type == ArgumentType::Buffer && Info.buffer_index == BufferIndex) { + return i; + } + } + return std::numeric_limits<size_t>::max(); + }(); + + template<size_t BufferIndex, size_t Index = GetIndexFromBufferIndex<BufferIndex>> + NX_CONSTEXPR void ProcessBufferImpl(const cmif::ServiceDispatchContext &ctx, cmif::PointerAndSize &buffer, bool &is_buffer_map_alias, bool &map_alias_buffers_valid, size_t &pointer_buffer_head, size_t &pointer_buffer_tail, const cmif::ServerMessageRuntimeMetadata runtime_metadata) { + static_assert(Index != std::numeric_limits<size_t>::max(), "Invalid Index From Buffer Index"); + constexpr auto Info = CommandMeta::ArgumentSerializationInfos[Index]; + constexpr auto Attributes = CommandMeta::BufferAttributes[BufferIndex]; + static_assert(BufferIndex == Info.buffer_index && Info.arg_type == ArgumentType::Buffer, "BufferIndex == Info.buffer_index && Info.arg_type == ArgumentType::Buffer"); + + if constexpr (Attributes & SfBufferAttr_HipcMapAlias) { + is_buffer_map_alias = true; + if constexpr (Attributes & SfBufferAttr_In) { + const HipcBufferDescriptor *desc = std::addressof(ctx.request.data.send_buffers[Info.send_map_alias_index]); + buffer = cmif::PointerAndSize(hipcGetBufferAddress(desc), hipcGetBufferSize(desc)); + if (!IsMapTransferModeValid<Attributes>(static_cast<u32>(desc->mode))) { map_alias_buffers_valid = false; } + } else if constexpr (Attributes & SfBufferAttr_Out) { + const HipcBufferDescriptor *desc = std::addressof(ctx.request.data.recv_buffers[Info.recv_map_alias_index]); + buffer = cmif::PointerAndSize(hipcGetBufferAddress(desc), hipcGetBufferSize(desc)); + if (!IsMapTransferModeValid<Attributes>(static_cast<u32>(desc->mode))) { map_alias_buffers_valid = false; } + } else { + static_assert(Attributes != Attributes, "Invalid Buffer Attributes"); + } + } else if constexpr (Attributes & SfBufferAttr_HipcPointer) { + is_buffer_map_alias = false; + if constexpr (Attributes & SfBufferAttr_In) { + const HipcStaticDescriptor *desc = std::addressof(ctx.request.data.send_statics[Info.send_pointer_index]); + buffer = cmif::PointerAndSize(hipcGetStaticAddress(desc), hipcGetStaticSize(desc)); + const size_t size = buffer.GetSize(); + if (size) { + pointer_buffer_tail = std::max(pointer_buffer_tail, buffer.GetAddress() + size); + } + } else if constexpr (Attributes & SfBufferAttr_Out) { + if constexpr (Attributes & SfBufferAttr_FixedSize) { + constexpr size_t size = Info.fixed_size; + static_assert(size > 0, "FixedSize object must have non-zero size!"); + pointer_buffer_head = util::AlignDown(pointer_buffer_head - size, 0x10); + buffer = cmif::PointerAndSize(pointer_buffer_head, size); + } else { + const u16 *recv_pointer_sizes = reinterpret_cast<const u16 *>(reinterpret_cast<uintptr_t>(ctx.request.data.data_words) + runtime_metadata.GetUnfixedOutPointerSizeOffset()); + const size_t size = static_cast<size_t>(recv_pointer_sizes[Info.unfixed_recv_pointer_index]); + pointer_buffer_head = util::AlignDown(pointer_buffer_head - size, 0x10); + buffer = cmif::PointerAndSize(pointer_buffer_head, size); + } + } else { + static_assert(Attributes != Attributes, "Invalid Buffer Attributes"); + } + } else if constexpr (Attributes & SfBufferAttr_HipcAutoSelect) { + if constexpr (Attributes & SfBufferAttr_In) { + const HipcBufferDescriptor *map_desc = std::addressof(ctx.request.data.send_buffers[Info.send_map_alias_index]); + const HipcStaticDescriptor *ptr_desc = std::addressof(ctx.request.data.send_statics[Info.send_pointer_index]); + is_buffer_map_alias = hipcGetBufferAddress(map_desc) != 0; + if (is_buffer_map_alias) { + buffer = cmif::PointerAndSize(hipcGetBufferAddress(map_desc), hipcGetBufferSize(map_desc)); + if (!IsMapTransferModeValid<Attributes>(static_cast<u32>(map_desc->mode))) { map_alias_buffers_valid = false; } + } else { + buffer = cmif::PointerAndSize(hipcGetStaticAddress(ptr_desc), hipcGetStaticSize(ptr_desc)); + const size_t size = buffer.GetSize(); + if (size) { + pointer_buffer_tail = std::max(pointer_buffer_tail, buffer.GetAddress() + size); + } + } + } else if constexpr (Attributes & SfBufferAttr_Out) { + const HipcBufferDescriptor *map_desc = std::addressof(ctx.request.data.recv_buffers[Info.recv_map_alias_index]); + is_buffer_map_alias = hipcGetBufferAddress(map_desc) != 0; + if (is_buffer_map_alias) { + buffer = cmif::PointerAndSize(hipcGetBufferAddress(map_desc), hipcGetBufferSize(map_desc)); + if (!IsMapTransferModeValid<Attributes>(static_cast<u32>(map_desc->mode))) { map_alias_buffers_valid = false; } + } else { + if constexpr (Attributes & SfBufferAttr_FixedSize) { + constexpr size_t size = Info.fixed_size; + static_assert(size > 0, "FixedSize object must have non-zero size!"); + pointer_buffer_head = util::AlignDown(pointer_buffer_head - size, 0x10); + buffer = cmif::PointerAndSize(pointer_buffer_head, size); + } else { + const u16 *recv_pointer_sizes = reinterpret_cast<const u16 *>(reinterpret_cast<uintptr_t>(ctx.request.data.data_words) + runtime_metadata.GetUnfixedOutPointerSizeOffset()); + const size_t size = static_cast<size_t>(recv_pointer_sizes[Info.unfixed_recv_pointer_index]); + pointer_buffer_head = util::AlignDown(pointer_buffer_head - size, 0x10); + buffer = cmif::PointerAndSize(pointer_buffer_head, size); + } + } + } else { + static_assert(Attributes != Attributes, "Invalid Buffer Attributes"); + } + } else { + static_assert(Attributes != Attributes, "Invalid Buffer Attributes"); + } + } + + template<size_t BufferIndex, size_t Index = GetIndexFromBufferIndex<BufferIndex>> + NX_CONSTEXPR void SetOutBufferImpl(const HipcRequest &response, const cmif::PointerAndSize &buffer, const bool is_buffer_map_alias) { + static_assert(Index != std::numeric_limits<size_t>::max(), "Invalid Index From Buffer Index"); + constexpr auto Info = CommandMeta::ArgumentSerializationInfos[Index]; + constexpr auto Attributes = CommandMeta::BufferAttributes[BufferIndex]; + static_assert(BufferIndex == Info.buffer_index && Info.arg_type == ArgumentType::Buffer, "BufferIndex == Info.buffer_index && Info.arg_type == ArgumentType::Buffer"); + + if constexpr (Attributes & SfBufferAttr_Out) { + if constexpr (Attributes & SfBufferAttr_HipcPointer) { + response.send_statics[Info.recv_pointer_index] = hipcMakeSendStatic(buffer.GetPointer(), buffer.GetSize(), Info.recv_pointer_index); + } else if constexpr (Attributes & SfBufferAttr_HipcAutoSelect) { + if (!is_buffer_map_alias) { + response.send_statics[Info.recv_pointer_index] = hipcMakeSendStatic(buffer.GetPointer(), buffer.GetSize(), Info.recv_pointer_index); + } else { + response.send_statics[Info.recv_pointer_index] = hipcMakeSendStatic(nullptr, 0, Info.recv_pointer_index); + } + } + } + } + public: + NX_CONSTEXPR Result ProcessBuffers(const cmif::ServiceDispatchContext &ctx, BufferArrayType &buffers, std::array<bool, CommandMeta::NumBuffers> &is_buffer_map_alias, const cmif::ServerMessageRuntimeMetadata runtime_metadata) { + bool map_alias_buffers_valid = true; + size_t pointer_buffer_tail = ctx.pointer_buffer.GetAddress(); + size_t pointer_buffer_head = pointer_buffer_tail + ctx.pointer_buffer.GetSize(); + #define _SF_IMPL_PROCESSOR_PROCESS_BUFFER_IMPL(n) do { if constexpr (CommandMeta::NumBuffers > n) { ProcessBufferImpl<n>(ctx, buffers[n], is_buffer_map_alias[n], map_alias_buffers_valid, pointer_buffer_head, pointer_buffer_tail, runtime_metadata); } } while (0) + _SF_IMPL_PROCESSOR_PROCESS_BUFFER_IMPL(0); + _SF_IMPL_PROCESSOR_PROCESS_BUFFER_IMPL(1); + _SF_IMPL_PROCESSOR_PROCESS_BUFFER_IMPL(2); + _SF_IMPL_PROCESSOR_PROCESS_BUFFER_IMPL(3); + _SF_IMPL_PROCESSOR_PROCESS_BUFFER_IMPL(4); + _SF_IMPL_PROCESSOR_PROCESS_BUFFER_IMPL(5); + _SF_IMPL_PROCESSOR_PROCESS_BUFFER_IMPL(6); + _SF_IMPL_PROCESSOR_PROCESS_BUFFER_IMPL(7); + #undef _SF_IMPL_PROCESSOR_PROCESS_BUFFER_IMPL + R_UNLESS(map_alias_buffers_valid, sf::hipc::ResultInvalidCmifRequest()); + if constexpr (CommandMeta::NumOutHipcPointerBuffers > 0) { + R_UNLESS(pointer_buffer_tail <= pointer_buffer_head, sf::hipc::ResultPointerBufferTooSmall()); + } + R_SUCCEED(); + } + + NX_CONSTEXPR void SetOutBuffers(const HipcRequest &response, const BufferArrayType &buffers, const std::array<bool, CommandMeta::NumBuffers> &is_buffer_map_alias) { + #define _SF_IMPL_PROCESSOR_SET_OUT_BUFFER_IMPL(n) do { if constexpr (CommandMeta::NumBuffers > n) { SetOutBufferImpl<n>(response, buffers[n], is_buffer_map_alias[n]); } } while (0) + _SF_IMPL_PROCESSOR_SET_OUT_BUFFER_IMPL(0); + _SF_IMPL_PROCESSOR_SET_OUT_BUFFER_IMPL(1); + _SF_IMPL_PROCESSOR_SET_OUT_BUFFER_IMPL(2); + _SF_IMPL_PROCESSOR_SET_OUT_BUFFER_IMPL(3); + _SF_IMPL_PROCESSOR_SET_OUT_BUFFER_IMPL(4); + _SF_IMPL_PROCESSOR_SET_OUT_BUFFER_IMPL(5); + _SF_IMPL_PROCESSOR_SET_OUT_BUFFER_IMPL(6); + _SF_IMPL_PROCESSOR_SET_OUT_BUFFER_IMPL(7); + #undef _SF_IMPL_PROCESSOR_SET_OUT_BUFFER_IMPL + } + + /* Argument deserialization. */ + private: + template<size_t Index, typename T = typename std::tuple_element<Index, ArgsType>::type> + NX_CONSTEXPR typename std::tuple_element<Index, ArgsTypeForInvoke>::type DeserializeArgumentImpl(const cmif::ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data, const OutRawHolderType &out_raw_holder, const BufferArrayType &buffers, InHandleHolderType &in_handles_holder, OutHandleHolderType &out_handles_holder, InOutObjectHolderType &in_out_objects_holder) { + constexpr auto Info = CommandMeta::ArgumentSerializationInfos[Index]; + if constexpr (Info.arg_type == ArgumentType::InData) { + /* New in rawdata. */ + constexpr size_t Offset = CommandMeta::InDataOffsets[Info.in_raw_data_index]; + if constexpr (!std::is_same<T, bool>::value) { + return *reinterpret_cast<const T *>(in_raw_data.GetAddress() + Offset); + } else { + /* Special case bools. */ + return *reinterpret_cast<const u8 *>(in_raw_data.GetAddress() + Offset) & 1; + } + } else if constexpr (Info.arg_type == ArgumentType::OutData) { + /* New out rawdata. */ + constexpr size_t Offset = CommandMeta::OutDataOffsets[Info.out_raw_data_index]; + return T(out_raw_holder.template GetAddress<Offset, T::TypeSize>()); + } else if constexpr (Info.arg_type == ArgumentType::InHandle) { + /* New InHandle. */ + using InvokeType = typename std::tuple_element<Index, ArgsTypeForInvoke>::type; + if constexpr (std::is_same<T, sf::MoveHandle>::value) { + static_assert(std::same_as<InvokeType, sf::MoveHandle &>); + return in_handles_holder.template SetMoveHandle<Info.in_move_handle_index>(ctx.request.data.move_handles[Info.in_move_handle_index]); + } else if constexpr (std::is_same<T, sf::CopyHandle>::value) { + static_assert(std::same_as<InvokeType, sf::CopyHandle &>); + return in_handles_holder.template SetCopyHandle<Info.in_copy_handle_index>(ctx.request.data.copy_handles[Info.in_copy_handle_index]); + } else { + static_assert(!std::is_same<T, T>::value, "Invalid InHandle kind"); + } + } else if constexpr (Info.arg_type == ArgumentType::OutHandle) { + /* New OutHandle. */ + if constexpr (std::is_same<T, sf::OutMoveHandle>::value) { + return T(out_handles_holder.template GetMoveHandlePointer<Info.out_move_handle_index>()); + } else if constexpr (std::is_same<T, sf::OutCopyHandle>::value) { + return T(out_handles_holder.template GetCopyHandlePointer<Info.out_copy_handle_index>()); + } else { + static_assert(!std::is_same<T, T>::value, "Invalid OutHandle kind"); + } + } else if constexpr (Info.arg_type == ArgumentType::InObject) { + /* New InObject. */ + return in_out_objects_holder.template GetInObject<Info.in_object_index, typename T::Interface>(); + } else if constexpr (Info.arg_type == ArgumentType::OutObject) { + /* New OutObject. */ + return in_out_objects_holder.template GetOutObject<Info.out_object_index, typename T::Interface>(); + } else if constexpr (Info.arg_type == ArgumentType::Buffer) { + /* Buffers were already processed earlier. */ + if constexpr (sf::IsLargeData<T>) { + /* Fake buffer. This is either InData or OutData, but serializing over buffers. */ + constexpr auto Attributes = CommandMeta::BufferAttributes[Info.buffer_index]; + if constexpr (Attributes & SfBufferAttr_In) { + /* TODO: AMS_ABORT_UNLESS()? N does not bother. */ + using InvokeType = typename std::tuple_element<Index, ArgsTypeForInvoke>::type; + static_assert(std::same_as<InvokeType, const T &>); + return *reinterpret_cast<const T *>(buffers[Info.buffer_index].GetAddress()); + } else if constexpr (Attributes & SfBufferAttr_Out) { + return T(buffers[Info.buffer_index]); + } else { + static_assert(!std::is_same<T, T>::value, "Invalid BufferAttributes for LargeData type."); + } + } else { + /* Actual buffer! */ + return T(buffers[Info.buffer_index]); + } + } else { + static_assert(!std::is_same<T, T>::value, "Invalid ArgumentType<T>"); + } + } + + template<size_t... Is> + NX_CONSTEXPR ArgsTypeForInvoke DeserializeArgumentsImpl(const cmif::ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data, const OutRawHolderType &out_raw_holder, const BufferArrayType &buffers, InHandleHolderType &in_handles_holder, OutHandleHolderType &out_handles_holder, InOutObjectHolderType &in_out_objects_holder, std::index_sequence<Is...>) { + return ArgsTypeForInvoke { DeserializeArgumentImpl<Is>(ctx, in_raw_data, out_raw_holder, buffers, in_handles_holder, out_handles_holder, in_out_objects_holder)..., }; + } + public: + NX_CONSTEXPR ArgsTypeForInvoke DeserializeArguments(const cmif::ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data, const OutRawHolderType &out_raw_holder, const BufferArrayType &buffers, InHandleHolderType &in_handles_holder, OutHandleHolderType &out_handles_holder, InOutObjectHolderType &in_out_objects_holder) { + return DeserializeArgumentsImpl(ctx, in_raw_data, out_raw_holder, buffers, in_handles_holder, out_handles_holder, in_out_objects_holder, std::make_index_sequence<std::tuple_size<ArgsTypeForInvoke>::value>{}); + } + }; + + inline Result GetCmifOutHeaderPointer(CmifOutHeader **out_header_ptr, cmif::PointerAndSize &out_raw_data) { + CmifOutHeader *header = static_cast<CmifOutHeader *>(out_raw_data.GetPointer()); + R_UNLESS(out_raw_data.GetSize() >= sizeof(*header), sf::cmif::ResultInvalidHeaderSize()); + out_raw_data = cmif::PointerAndSize(out_raw_data.GetAddress() + sizeof(*header), out_raw_data.GetSize() - sizeof(*header)); + *out_header_ptr = header; + R_SUCCEED(); + } + + template<typename CommandMeta, typename... Arguments> + constexpr Result InvokeServiceCommandImplCommon(CmifOutHeader **out_header_ptr, cmif::ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data, Result (*invoke_impl)(sf::IServiceObject *, Arguments &&...)) { + using ImplProcessorType = HipcCommandProcessor<CommandMeta>; + using BufferArrayType = std::array<cmif::PointerAndSize, CommandMeta::NumBuffers>; + using InHandleHolderType = InHandleHolder<CommandMeta::NumInMoveHandles, CommandMeta::NumInCopyHandles>; + using OutHandleHolderType = OutHandleHolder<CommandMeta::NumOutMoveHandles, CommandMeta::NumOutCopyHandles>; + using OutRawHolderType = OutRawHolder<CommandMeta::OutDataSize, CommandMeta::OutDataAlign>; + using InOutObjectHolderType = InOutObjectHolder<CommandMeta::NumInObjects, CommandMeta::NumOutObjects>; + + /* Create a processor for us to work with. */ + ImplProcessorType impl_processor; + if (ctx.processor == nullptr) { + /* In the non-domain case, this is our only processor. */ + ctx.processor = std::addressof(impl_processor); + } else { + /* In the domain case, we already have a processor, so we should give it a pointer to our template implementation. */ + ctx.processor->SetImplementationProcessor(std::addressof(impl_processor)); + } + + /* Validate the metadata has the expected counts. */ + const auto runtime_metadata = ctx.processor->GetRuntimeMetadata(); + R_TRY(ctx.processor->PrepareForProcess(ctx, runtime_metadata)); + + /* Storage for output. */ + BufferArrayType buffers; + std::array<bool, CommandMeta::NumBuffers> is_buffer_map_alias = {}; + OutRawHolderType out_raw_holder; + OutHandleHolderType out_handles_holder; + InOutObjectHolderType in_out_objects_holder; + + /* Process buffers. */ + R_TRY(ImplProcessorType::ProcessBuffers(ctx, buffers, is_buffer_map_alias, runtime_metadata)); + + /* Process input/output objects. */ + R_TRY(in_out_objects_holder.GetInObjects(ctx.processor)); + R_TRY((in_out_objects_holder.template ValidateInObjects<typename CommandMeta::InObjects>())); + + /* Decoding/Invocation. */ + { + Result command_result; + { + InHandleHolderType in_handles_holder; + typename CommandMeta::ArgsTypeForInvoke args_tuple = ImplProcessorType::DeserializeArguments(ctx, in_raw_data, out_raw_holder, buffers, in_handles_holder, out_handles_holder, in_out_objects_holder); + + /* Handle in process ID holder if relevant. */ + if constexpr (CommandMeta::HasInProcessIdHolder) { + /* TODO: More precise value than 32? */ + static_assert(std::tuple_size<typename CommandMeta::ArgsTypeForInvoke>::value <= 32, "Commands must have <= 32 arguments"); + const os::ProcessId process_id{ctx.request.pid}; + #define _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(n) do { \ + using ArgsTypeForInvoke = typename CommandMeta::ArgsTypeForInvoke; \ + if constexpr (n < std::tuple_size<ArgsTypeForInvoke>::value) { \ + if constexpr (CommandMeta::template IsInProcessIdHolderIndex<n>) { \ + R_TRY(MarshalProcessId(std::get<n>(args_tuple), process_id)); \ + } \ + } \ + } while (0) + _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x00); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x01); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x02); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x03); + _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x04); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x05); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x06); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x07); + _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x08); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x09); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x0a); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x0b); + _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x0c); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x0d); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x0e); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x0f); + _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x10); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x11); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x12); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x13); + _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x14); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x15); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x16); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x17); + _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x18); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x19); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x1a); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x1b); + _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x1c); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x1d); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x1e); _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID(0x1f); + #undef _SF_IMPL_PROCESSOR_MARSHAL_PROCESS_ID + } + + using TrueArgumentsTuple = std::tuple<Arguments...>; + + sf::IServiceObject * const this_ptr = ctx.srv_obj; + command_result = [this_ptr, invoke_impl, &args_tuple]<size_t ...Ix>(std::index_sequence<Ix...>) ALWAYS_INLINE_LAMBDA { + R_RETURN(invoke_impl(this_ptr, std::forward<typename std::tuple_element<Ix, TrueArgumentsTuple>::type>(std::get<Ix>(args_tuple))...)); + }(std::make_index_sequence<std::tuple_size<typename CommandMeta::ArgsTypeForInvoke>::value>()); + } + + if (R_FAILED(command_result)) { + cmif::PointerAndSize out_raw_data; + ctx.processor->PrepareForErrorReply(ctx, out_raw_data, runtime_metadata); + R_TRY(GetCmifOutHeaderPointer(out_header_ptr, out_raw_data)); + R_RETURN(command_result); + } + } + + /* Encode. */ + cmif::PointerAndSize out_raw_data; + const auto response = ctx.processor->PrepareForReply(ctx, out_raw_data, runtime_metadata); + R_TRY(GetCmifOutHeaderPointer(out_header_ptr, out_raw_data)); + + /* Copy raw data output struct. */ + R_UNLESS(out_raw_data.GetSize() >= OutRawHolderType::Size, sf::cmif::ResultInvalidOutRawSize()); + out_raw_holder.CopyTo(out_raw_data.GetPointer()); + + /* Set output recvlist buffers. */ + ImplProcessorType::SetOutBuffers(response, buffers, is_buffer_map_alias); + + /* Set out handles. */ + out_handles_holder.CopyTo(ctx, response, runtime_metadata.GetOutObjectCount()); + + /* Set output objects. */ + #define _SF_IMPL_PROCESSOR_MARSHAL_OUT_OBJECT(n) do { \ + if constexpr (n < CommandMeta::NumOutObjects) { \ + in_out_objects_holder.template SetOutObject<n, typename std::tuple_element_t<n, typename CommandMeta::OutObjects>::Interface>(); \ + } \ + } while (0) + _SF_IMPL_PROCESSOR_MARSHAL_OUT_OBJECT(0); + _SF_IMPL_PROCESSOR_MARSHAL_OUT_OBJECT(1); + _SF_IMPL_PROCESSOR_MARSHAL_OUT_OBJECT(2); + _SF_IMPL_PROCESSOR_MARSHAL_OUT_OBJECT(3); + _SF_IMPL_PROCESSOR_MARSHAL_OUT_OBJECT(4); + _SF_IMPL_PROCESSOR_MARSHAL_OUT_OBJECT(5); + _SF_IMPL_PROCESSOR_MARSHAL_OUT_OBJECT(6); + _SF_IMPL_PROCESSOR_MARSHAL_OUT_OBJECT(7); + _SF_IMPL_PROCESSOR_MARSHAL_OUT_OBJECT(8); + #undef _SF_IMPL_PROCESSOR_MARSHAL_OUT_OBJECT + in_out_objects_holder.SetOutObjects(ctx, response); + + R_SUCCEED(); + } + + template<auto ServiceCommandImpl, typename Return, typename ClassType, typename... Arguments> + constexpr Result InvokeServiceCommandImpl(CmifOutHeader **out_header_ptr, cmif::ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data) { + using CommandMeta = CommandMetaInfo<Arguments...>; + static_assert(std::is_base_of<sf::IServiceObject, ClassType>::value, "InvokeServiceCommandImpl: Service Commands must be ServiceObject member functions"); + + constexpr bool ReturnsResult = std::is_same<Return, Result>::value; + constexpr bool ReturnsVoid = std::is_same<Return, void>::value; + static_assert(ReturnsResult || ReturnsVoid, "Service Commands must return Result or void."); + + R_RETURN((InvokeServiceCommandImplCommon<CommandMeta, Arguments...>(out_header_ptr, ctx, in_raw_data, +[](sf::IServiceObject *srv_obj, Arguments &&... args) -> Result { + if constexpr (ReturnsResult) { + R_RETURN((static_cast<ClassType *>(srv_obj)->*ServiceCommandImpl)(std::forward<Arguments>(args)...)); + } else { + (static_cast<ClassType *>(srv_obj)->*ServiceCommandImpl)(std::forward<Arguments>(args)...); + R_SUCCEED(); + } + }))); + } + +} +#elif defined(ATMOSPHERE_OS_WINDOWS) +namespace ams::sf::impl { + + template<auto ServiceCommandImpl, typename Return, typename ClassType, typename... Arguments> + inline Result InvokeServiceCommandImpl(CmifOutHeader **out_header_ptr, cmif::ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data) { + /* TODO: Is some kind of emulated serialization interesting/desirable? */ + AMS_UNUSED(out_header_ptr, ctx, in_raw_data); + AMS_ABORT("HIPC serialization not currently supported on Windows."); + } + + +} +#elif defined(ATMOSPHERE_OS_LINUX) +namespace ams::sf::impl { + + template<auto ServiceCommandImpl, typename Return, typename ClassType, typename... Arguments> + inline Result InvokeServiceCommandImpl(CmifOutHeader **out_header_ptr, cmif::ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data) { + /* TODO: Is some kind of emulated serialization interesting/desirable? */ + AMS_UNUSED(out_header_ptr, ctx, in_raw_data); + AMS_ABORT("HIPC serialization not currently supported on Linux."); + } + + +} +#elif defined(ATMOSPHERE_OS_MACOS) +namespace ams::sf::impl { + + template<auto ServiceCommandImpl, typename Return, typename ClassType, typename... Arguments> + inline Result InvokeServiceCommandImpl(CmifOutHeader **out_header_ptr, cmif::ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data) { + /* TODO: Is some kind of emulated serialization interesting/desirable? */ + AMS_UNUSED(out_header_ptr, ctx, in_raw_data); + AMS_ABORT("HIPC serialization not currently supported on macOS."); + } + + +} +#else + #error "Unknown OS for sf Command serialization." +#endif + +namespace ams::sf::impl { + + template<hos::Version Low, hos::Version High, u32 CommandId, auto CommandImpl, typename Return, typename ClassType, typename... Arguments> + consteval inline cmif::ServiceCommandMeta MakeServiceCommandMeta() { + return { + .hosver_low = Low, + .hosver_high = High, + .cmd_id = static_cast<u32>(CommandId), + .handler = ::ams::sf::impl::InvokeServiceCommandImpl<CommandImpl, Return, ClassType, Arguments...>, + }; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/impl/sf_impl_template_base.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/impl/sf_impl_template_base.hpp new file mode 100644 index 00000000..f1a71739 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/impl/sf_impl_template_base.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::sf::impl { + + template<typename Interface, typename Base, typename ImplHolder, typename ImplGetter, typename Root> + class ImplTemplateBaseT; + + template<typename Interface, typename Base, typename ImplHolder, typename ImplGetter> + class ImplTemplateBase : public ImplTemplateBaseT<Interface, Base, ImplHolder, ImplGetter, Interface> { + private: + using BaseImpl = ImplTemplateBaseT<Interface, Base, ImplHolder, ImplGetter, Interface>; + public: + using BaseImpl::BaseImpl; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/impl/sf_service_object_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/impl/sf_service_object_impl.hpp new file mode 100644 index 00000000..ed0cdfbf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/impl/sf_service_object_impl.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::sf::impl { + + class ServiceObjectImplBase2 { + private: + std::atomic<u32> m_ref_count; + protected: + constexpr ServiceObjectImplBase2() : m_ref_count(1) { /* ... */ } + + void AddReferenceImpl() { + const auto prev = m_ref_count.fetch_add(1); + AMS_ABORT_UNLESS(prev < std::numeric_limits<u32>::max()); + } + + bool ReleaseImpl() { + const auto prev = m_ref_count.fetch_sub(1); + AMS_ABORT_UNLESS(prev != 0); + return prev == 1; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_allocation_policies.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_allocation_policies.hpp new file mode 100644 index 00000000..f154ded6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_allocation_policies.hpp @@ -0,0 +1,92 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::sf { + + namespace impl { + + struct StatelessDummyAllocator; + + } + + template<typename A> + class StatelessAllocationPolicy { + public: + static constexpr bool HasStatefulAllocator = false; + using Allocator = impl::StatelessDummyAllocator; + + template<typename> + using StatelessAllocator = A; + + template<typename> + static void *AllocateAligned(size_t size, size_t align) { + AMS_UNUSED(align); + return A().Allocate(size); + } + + template<typename> + static void DeallocateAligned(void *ptr, size_t size, size_t align) { + AMS_UNUSED(align); + A().Deallocate(ptr, size); + } + }; + + template<template<typename> typename A> + class StatelessTypedAllocationPolicy { + public: + static constexpr bool HasStatefulAllocator = false; + using Allocator = impl::StatelessDummyAllocator; + + template<typename T> + using StatelessAllocator = A<T>; + + template<typename T> + static void *AllocateAligned(size_t size, size_t align) { + AMS_UNUSED(align); + return StatelessAllocator<T>().Allocate(size); + } + + template<typename T> + static void DeallocateAligned(void *ptr, size_t size, size_t align) { + AMS_UNUSED(align); + StatelessAllocator<T>().Deallocate(ptr, size); + } + }; + + template<typename A> + class StatefulAllocationPolicy { + public: + static constexpr bool HasStatefulAllocator = true; + using Allocator = A; + + static void *AllocateAligned(Allocator *allocator, size_t size, size_t align) { + AMS_UNUSED(align); + return allocator->Allocate(size); + } + + static void DeallocateAligned(Allocator *allocator, void *ptr, size_t size, size_t align) { + AMS_UNUSED(align); + allocator->Deallocate(ptr, size); + } + }; + + template<typename T> + concept IsStatefulPolicy = T::HasStatefulAllocator; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_buffer_tags.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_buffer_tags.hpp new file mode 100644 index 00000000..d5e81f11 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_buffer_tags.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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +namespace ams::sf { + + /* Helper structs for serialization of buffers. */ + struct LargeData{}; + + struct PrefersMapAliasTransferMode{}; + + struct PrefersPointerTransferMode{}; + + struct PrefersAutoSelectTransferMode{}; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_buffers.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_buffers.hpp new file mode 100644 index 00000000..bc134fd9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_buffers.hpp @@ -0,0 +1,315 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/sf/sf_common.hpp> +#include <stratosphere/sf/sf_out.hpp> +#include <stratosphere/sf/cmif/sf_cmif_pointer_and_size.hpp> +#include <stratosphere/sf/sf_buffer_tags.hpp> + +namespace ams::sf { + + enum class BufferTransferMode { + MapAlias, + Pointer, + AutoSelect, + }; + + namespace impl { + + /* Buffer utilities. */ + struct BufferBaseTag{}; + + template<BufferTransferMode TransferMode> + constexpr inline u32 BufferTransferModeAttributes = [] { + if constexpr (TransferMode == BufferTransferMode::MapAlias) { + return SfBufferAttr_HipcMapAlias; + } else if constexpr (TransferMode == BufferTransferMode::Pointer) { + return SfBufferAttr_HipcPointer; + } else if constexpr(TransferMode == BufferTransferMode::AutoSelect) { + return SfBufferAttr_HipcAutoSelect; + } else { + static_assert(false, "Invalid BufferTransferMode"); + } + }(); + + } + + template<typename T> + constexpr inline bool IsLargeData = std::is_base_of<sf::LargeData, T>::value; + + template<typename T> + constexpr inline bool IsLargeData<Out<T>> = IsLargeData<T>; + + template<typename T> + constexpr inline size_t LargeDataSize = sizeof(T); + + template<typename T> + constexpr inline size_t LargeDataSize<Out<T>> = sizeof(T); + + template<typename T> + constexpr inline BufferTransferMode PreferredTransferMode = [] { + constexpr bool prefers_map_alias = std::is_base_of<PrefersMapAliasTransferMode, T>::value; + constexpr bool prefers_pointer = std::is_base_of<PrefersPointerTransferMode, T>::value; + constexpr bool prefers_auto_select = std::is_base_of<PrefersAutoSelectTransferMode, T>::value; + if constexpr (prefers_map_alias) { + static_assert(!prefers_pointer && !prefers_auto_select, "Type T must only prefer one transfer mode."); + return BufferTransferMode::MapAlias; + } else if constexpr (prefers_pointer) { + static_assert(!prefers_map_alias && !prefers_auto_select, "Type T must only prefer one transfer mode."); + return BufferTransferMode::Pointer; + } else if constexpr (prefers_auto_select) { + static_assert(!prefers_map_alias && !prefers_pointer, "Type T must only prefer one transfer mode."); + return BufferTransferMode::AutoSelect; + } else if constexpr (IsLargeData<T>) { + return BufferTransferMode::Pointer; + } else { + return BufferTransferMode::MapAlias; + } + }(); + + template<typename T> + constexpr inline BufferTransferMode PreferredTransferMode<Out<T>> = PreferredTransferMode<T>; + + namespace impl { + + class BufferBase : public BufferBaseTag { + public: + static constexpr u32 AdditionalAttributes = 0; + private: + const cmif::PointerAndSize m_pas; + protected: + constexpr uintptr_t GetAddressImpl() const { + return m_pas.GetAddress(); + } + + template<typename Entry> + constexpr inline size_t GetSizeImpl() const { + return m_pas.GetSize() / sizeof(Entry); + } + public: + constexpr BufferBase() : m_pas() { /* ... */ } + constexpr BufferBase(const cmif::PointerAndSize &pas) : m_pas(pas) { /* ... */ } + constexpr BufferBase(uintptr_t ptr, size_t sz) : m_pas(ptr, sz) { /* ... */ } + }; + + class InBufferBase : public BufferBase { + public: + using BaseType = BufferBase; + static constexpr u32 AdditionalAttributes = BaseType::AdditionalAttributes | + SfBufferAttr_In; + public: + constexpr InBufferBase() : BaseType() { /* ... */ } + constexpr InBufferBase(const cmif::PointerAndSize &pas) : BaseType(pas) { /* ... */ } + constexpr InBufferBase(uintptr_t ptr, size_t sz) : BaseType(ptr, sz) { /* ... */ } + + InBufferBase(const void *ptr, size_t sz) : BaseType(reinterpret_cast<uintptr_t>(ptr), sz) { /* ... */ } + InBufferBase(const u8 *ptr, size_t sz) : BaseType(reinterpret_cast<uintptr_t>(ptr), sz) { /* ... */ } + }; + + class OutBufferBase : public BufferBase { + public: + using BaseType = BufferBase; + static constexpr u32 AdditionalAttributes = BaseType::AdditionalAttributes | + SfBufferAttr_Out; + public: + constexpr OutBufferBase() : BaseType() { /* ... */ } + constexpr OutBufferBase(const cmif::PointerAndSize &pas) : BaseType(pas) { /* ... */ } + constexpr OutBufferBase(uintptr_t ptr, size_t sz) : BaseType(ptr, sz) { /* ... */ } + + OutBufferBase(void *ptr, size_t sz) : BaseType(reinterpret_cast<uintptr_t>(ptr), sz) { /* ... */ } + OutBufferBase(u8 *ptr, size_t sz) : BaseType(reinterpret_cast<uintptr_t>(ptr), sz) { /* ... */ } + }; + + template<BufferTransferMode TMode, u32 ExtraAttributes = 0> + class InBufferImpl : public InBufferBase { + public: + using BaseType = InBufferBase; + static constexpr BufferTransferMode TransferMode = TMode; + static constexpr u32 AdditionalAttributes = BaseType::AdditionalAttributes | + ExtraAttributes; + public: + constexpr InBufferImpl() : BaseType() { /* ... */ } + constexpr InBufferImpl(const cmif::PointerAndSize &pas) : BaseType(pas) { /* ... */ } + constexpr InBufferImpl(uintptr_t ptr, size_t sz) : BaseType(ptr, sz) { /* ... */ } + + InBufferImpl(const void *ptr, size_t sz) : BaseType(reinterpret_cast<uintptr_t>(ptr), sz) { /* ... */ } + InBufferImpl(const u8 *ptr, size_t sz) : BaseType(reinterpret_cast<uintptr_t>(ptr), sz) { /* ... */ } + + constexpr const u8 *GetPointer() const { + return reinterpret_cast<const u8 *>(this->GetAddressImpl()); + } + + constexpr size_t GetSize() const { + return this->GetSizeImpl<u8>(); + } + }; + + template<BufferTransferMode TMode, u32 ExtraAttributes = 0> + class OutBufferImpl : public OutBufferBase { + public: + using BaseType = OutBufferBase; + static constexpr BufferTransferMode TransferMode = TMode; + static constexpr u32 AdditionalAttributes = BaseType::AdditionalAttributes | + ExtraAttributes; + public: + constexpr OutBufferImpl() : BaseType() { /* ... */ } + constexpr OutBufferImpl(const cmif::PointerAndSize &pas) : BaseType(pas) { /* ... */ } + constexpr OutBufferImpl(uintptr_t ptr, size_t sz) : BaseType(ptr, sz) { /* ... */ } + + OutBufferImpl(void *ptr, size_t sz) : BaseType(reinterpret_cast<uintptr_t>(ptr), sz) { /* ... */ } + OutBufferImpl(u8 *ptr, size_t sz) : BaseType(reinterpret_cast<uintptr_t>(ptr), sz) { /* ... */ } + + constexpr u8 *GetPointer() const { + return reinterpret_cast<u8 *>(this->GetAddressImpl()); + } + + constexpr size_t GetSize() const { + return this->GetSizeImpl<u8>(); + } + }; + + template<typename T, BufferTransferMode TMode = PreferredTransferMode<T>> + struct InArrayImpl : public InBufferBase { + public: + using BaseType = InBufferBase; + static constexpr BufferTransferMode TransferMode = TMode; + static constexpr u32 AdditionalAttributes = BaseType::AdditionalAttributes; + public: + constexpr InArrayImpl() : BaseType() { /* ... */ } + constexpr InArrayImpl(const cmif::PointerAndSize &pas) : BaseType(pas) { /* ... */ } + InArrayImpl(const T *ptr, size_t num_elements) : BaseType(reinterpret_cast<uintptr_t>(ptr), num_elements * sizeof(T)) { /* ... */ } + + constexpr const T *GetPointer() const { + return reinterpret_cast<const T *>(this->GetAddressImpl()); + } + + constexpr size_t GetSize() const { + return this->GetSizeImpl<T>(); + } + + constexpr const T &operator[](size_t i) const { + return this->GetPointer()[i]; + } + + constexpr explicit operator Span<const T>() const { + return {this->GetPointer(), this->GetSize()}; + } + + constexpr Span<const T> ToSpan() const { + return {this->GetPointer(), this->GetSize()}; + } + }; + + template<typename T, BufferTransferMode TMode = PreferredTransferMode<T>> + struct OutArrayImpl : public OutBufferBase { + public: + using BaseType = OutBufferBase; + static constexpr BufferTransferMode TransferMode = TMode; + static constexpr u32 AdditionalAttributes = BaseType::AdditionalAttributes; + public: + constexpr OutArrayImpl() : BaseType() { /* ... */ } + constexpr OutArrayImpl(const cmif::PointerAndSize &pas) : BaseType(pas) { /* ... */ } + OutArrayImpl(T *ptr, size_t num_elements) : BaseType(reinterpret_cast<uintptr_t>(ptr), num_elements * sizeof(T)) { /* ... */ } + + constexpr T *GetPointer() const { + return reinterpret_cast<T *>(this->GetAddressImpl()); + } + + constexpr size_t GetSize() const { + return this->GetSizeImpl<T>(); + } + + constexpr T &operator[](size_t i) const { + return this->GetPointer()[i]; + } + + constexpr explicit operator Span<T>() const { + return {this->GetPointer(), this->GetSize()}; + } + + constexpr Span<T> ToSpan() const { + return {this->GetPointer(), this->GetSize()}; + } + }; + + } + + /* Buffer Types. */ + using InBuffer = typename impl::InBufferImpl<BufferTransferMode::MapAlias>; + using InMapAliasBuffer = typename impl::InBufferImpl<BufferTransferMode::MapAlias>; + using InPointerBuffer = typename impl::InBufferImpl<BufferTransferMode::Pointer>; + using InAutoSelectBuffer = typename impl::InBufferImpl<BufferTransferMode::AutoSelect>; + using InNonSecureBuffer = typename impl::InBufferImpl<BufferTransferMode::MapAlias, SfBufferAttr_HipcMapTransferAllowsNonSecure>; + using InNonDeviceBuffer = typename impl::InBufferImpl<BufferTransferMode::MapAlias, SfBufferAttr_HipcMapTransferAllowsNonDevice>; + + using InNonSecureAutoSelectBuffer = typename impl::InBufferImpl<BufferTransferMode::AutoSelect, SfBufferAttr_HipcMapTransferAllowsNonSecure>; + + using OutBuffer = typename impl::OutBufferImpl<BufferTransferMode::MapAlias>; + using OutMapAliasBuffer = typename impl::OutBufferImpl<BufferTransferMode::MapAlias>; + using OutPointerBuffer = typename impl::OutBufferImpl<BufferTransferMode::Pointer>; + using OutAutoSelectBuffer = typename impl::OutBufferImpl<BufferTransferMode::AutoSelect>; + using OutNonSecureBuffer = typename impl::OutBufferImpl<BufferTransferMode::MapAlias, SfBufferAttr_HipcMapTransferAllowsNonSecure>; + using OutNonDeviceBuffer = typename impl::OutBufferImpl<BufferTransferMode::MapAlias, SfBufferAttr_HipcMapTransferAllowsNonDevice>; + + using OutNonSecureAutoSelectBuffer = typename impl::OutBufferImpl<BufferTransferMode::AutoSelect, SfBufferAttr_HipcMapTransferAllowsNonSecure>; + + template<typename T> + using InArray = typename impl::InArrayImpl<T>; + template<typename T> + using InMapAliasArray = typename impl::InArrayImpl<T, BufferTransferMode::MapAlias>; + template<typename T> + using InPointerArray = typename impl::InArrayImpl<T, BufferTransferMode::Pointer>; + template<typename T> + using InAutoSelectArray = typename impl::InArrayImpl<T, BufferTransferMode::AutoSelect>; + + template<typename T> + using OutArray = typename impl::OutArrayImpl<T>; + template<typename T> + using OutMapAliasArray = typename impl::OutArrayImpl<T, BufferTransferMode::MapAlias>; + template<typename T> + using OutPointerArray = typename impl::OutArrayImpl<T, BufferTransferMode::Pointer>; + template<typename T> + using OutAutoSelectArray = typename impl::OutArrayImpl<T, BufferTransferMode::AutoSelect>; + + /* Attribute serialization structs. */ + template<typename T> + constexpr inline bool IsBuffer = [] { + const bool is_buffer = std::is_base_of<impl::BufferBaseTag, T>::value; + const bool is_large_data = IsLargeData<T>; + static_assert(!(is_buffer && is_large_data), "Invalid sf::IsBuffer state"); + return is_buffer || is_large_data; + }(); + + template<typename T> + constexpr inline u32 BufferAttributes = [] { + static_assert(IsBuffer<T>, "BufferAttributes requires IsBuffer"); + if constexpr (std::is_base_of<impl::BufferBaseTag, T>::value) { + return impl::BufferTransferModeAttributes<T::TransferMode> | T::AdditionalAttributes; + } else if constexpr (IsLargeData<T>) { + u32 attr = SfBufferAttr_FixedSize | impl::BufferTransferModeAttributes<PreferredTransferMode<T>>; + if constexpr (std::is_base_of<impl::OutBaseTag, T>::value) { + attr |= SfBufferAttr_Out; + } else { + attr |= SfBufferAttr_In; + } + return attr; + } else { + static_assert(!std::is_same<T, T>::value, "Invalid BufferAttributes<T>"); + } + }(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_common.hpp new file mode 100644 index 00000000..ff81a1e4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_common.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/ams.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/sm/sm_types.hpp> +#include <stratosphere/sf/sf_types.hpp> +#include <stratosphere/sf/sf_mitm_config.hpp> \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_default_allocation_policy.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_default_allocation_policy.hpp new file mode 100644 index 00000000..56ca7f5a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_default_allocation_policy.hpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/sf/sf_common.hpp> +#include <stratosphere/sf/sf_allocation_policies.hpp> + +namespace ams::sf { + + namespace impl { + + void *DefaultAllocateImpl(size_t size, size_t align, size_t offset); + void DefaultDeallocateImpl(void *ptr, size_t size, size_t align, size_t offset); + + template<typename T> + class DefaultAllocationPolicyAllocator { + private: + struct Holder { + MemoryResource *allocator; + alignas(alignof(T)) std::byte storage[sizeof(T)]; + }; + public: + void *Allocate(size_t size) { + AMS_ASSERT(size == sizeof(T)); + AMS_UNUSED(size); + return DefaultAllocateImpl(sizeof(Holder), alignof(Holder), AMS_OFFSETOF(Holder, storage)); + } + + void Deallocate(void *ptr, size_t size) { + AMS_ASSERT(size == sizeof(T)); + AMS_UNUSED(size); + return DefaultDeallocateImpl(ptr, sizeof(Holder), alignof(Holder), AMS_OFFSETOF(Holder, storage)); + } + }; + + } + + using DefaultAllocationPolicy = StatelessTypedAllocationPolicy<impl::DefaultAllocationPolicyAllocator>; + + MemoryResource *GetGlobalDefaultMemoryResource(); + MemoryResource *GetCurrentEffectiveMemoryResource(); + MemoryResource *GetCurrentMemoryResource(); + MemoryResource *GetNewDeleteMemoryResource(); + + MemoryResource *SetGlobalDefaultMemoryResource(MemoryResource *mr); + MemoryResource *SetCurrentMemoryResource(MemoryResource *mr); + + class ScopedCurrentMemoryResourceSetter { + NON_COPYABLE(ScopedCurrentMemoryResourceSetter); + NON_MOVEABLE(ScopedCurrentMemoryResourceSetter); + private: + MemoryResource *m_prev; + public: + explicit ScopedCurrentMemoryResourceSetter(MemoryResource *mr); + ~ScopedCurrentMemoryResourceSetter(); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_exp_heap_allocator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_exp_heap_allocator.hpp new file mode 100644 index 00000000..6ec6fcdc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_exp_heap_allocator.hpp @@ -0,0 +1,83 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/sf/sf_common.hpp> +#include <stratosphere/sf/sf_allocation_policies.hpp> + +namespace ams::sf { + + struct ExpHeapAllocator { + using Policy = StatefulAllocationPolicy<ExpHeapAllocator>; + + lmem::HeapHandle _handle; + os::SdkMutexType _mutex; + + void Attach(lmem::HeapHandle h) { + this->_handle = h; + os::InitializeSdkMutex(std::addressof(this->_mutex)); + } + + void Detach() { + this->_handle = {}; + } + + void *Allocate(size_t size) { + os::LockSdkMutex(std::addressof(this->_mutex)); + auto ptr = lmem::AllocateFromExpHeap(this->_handle, size); + os::UnlockSdkMutex(std::addressof(this->_mutex)); + return ptr; + } + + void Deallocate(void *ptr, size_t size) { + AMS_UNUSED(size); + + os::LockSdkMutex(std::addressof(this->_mutex)); + lmem::FreeToExpHeap(this->_handle, ptr); + os::UnlockSdkMutex(std::addressof(this->_mutex)); + } + }; + static_assert(util::is_pod<ExpHeapAllocator>::value); + + template<size_t Size, typename Tag = void> + struct ExpHeapStaticAllocator { + using Policy = StatelessAllocationPolicy<ExpHeapStaticAllocator<Size, Tag>>; + + struct Globals { + ExpHeapAllocator allocator; + alignas(0x10) std::byte buffer[Size == 0 ? 1 : Size]; + }; + + static constinit inline Globals _globals = {}; + + + static void Initialize(int option) { + _globals.allocator.Attach(lmem::CreateExpHeap(std::addressof(_globals.buffer), Size, option)); + } + + static void Initialize(lmem::HeapHandle handle) { + _globals.allocator.Attach(handle); + } + + static void *Allocate(size_t size) { + return _globals.allocator.Allocate(size); + } + + static void Deallocate(void *ptr, size_t size) { + return _globals.allocator.Deallocate(ptr, size); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_fs_inline_context.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_fs_inline_context.hpp new file mode 100644 index 00000000..416c7278 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_fs_inline_context.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/sf/sf_common.hpp> + +namespace ams::os { + + struct ThreadType; + +} + +namespace ams::sf { + + u8 GetFsInlineContext(os::ThreadType *thread); + u8 SetFsInlineContext(os::ThreadType *thread, u8 ctx); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_lmem_utility.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_lmem_utility.hpp new file mode 100644 index 00000000..6dcdbca8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_lmem_utility.hpp @@ -0,0 +1,84 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/sf/sf_common.hpp> +#include <stratosphere/lmem.hpp> + +namespace ams::sf { + + class ExpHeapMemoryResource : public MemoryResource { + private: + lmem::HeapHandle m_handle; + public: + constexpr ExpHeapMemoryResource() : m_handle() { /* ... */ } + constexpr explicit ExpHeapMemoryResource(lmem::HeapHandle h) : m_handle(h) { /* ... */ } + + void Attach(lmem::HeapHandle h) { + AMS_ABORT_UNLESS(m_handle == lmem::HeapHandle()); + m_handle = h; + } + + lmem::HeapHandle GetHandle() const { return m_handle; } + private: + virtual void *AllocateImpl(size_t size, size_t alignment) override { + return lmem::AllocateFromExpHeap(m_handle, size, static_cast<int>(alignment)); + } + + virtual void DeallocateImpl(void *buffer, size_t size, size_t alignment) override { + AMS_UNUSED(size, alignment); + return lmem::FreeToExpHeap(m_handle, buffer); + } + + virtual bool IsEqualImpl(const MemoryResource &resource) const override { + return this == std::addressof(resource); + } + }; + + class UnitHeapMemoryResource : public MemoryResource { + private: + lmem::HeapHandle m_handle; + public: + constexpr UnitHeapMemoryResource() : m_handle() { /* ... */ } + constexpr explicit UnitHeapMemoryResource(lmem::HeapHandle h) : m_handle(h) { /* ... */ } + + void Attach(lmem::HeapHandle h) { + AMS_ABORT_UNLESS(m_handle == lmem::HeapHandle()); + m_handle = h; + } + + lmem::HeapHandle GetHandle() const { return m_handle; } + private: + virtual void *AllocateImpl(size_t size, size_t alignment) override { + AMS_ASSERT(size <= lmem::GetUnitHeapUnitSize(m_handle)); + AMS_ASSERT(alignment <= static_cast<size_t>(lmem::GetUnitHeapAlignment(m_handle))); + AMS_UNUSED(size, alignment); + + return lmem::AllocateFromUnitHeap(m_handle); + } + + virtual void DeallocateImpl(void *buffer, size_t size, size_t alignment) override { + AMS_UNUSED(size, alignment); + + return lmem::FreeToUnitHeap(m_handle, buffer); + } + + virtual bool IsEqualImpl(const MemoryResource &resource) const override { + return this == std::addressof(resource); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_mem_utility.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_mem_utility.hpp new file mode 100644 index 00000000..7e6f7178 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_mem_utility.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/sf/sf_common.hpp> +#include <stratosphere/mem.hpp> + +namespace ams::sf { + + class StandardAllocatorMemoryResource : public MemoryResource { + private: + mem::StandardAllocator *m_standard_allocator; + public: + explicit StandardAllocatorMemoryResource(mem::StandardAllocator *sa) : m_standard_allocator(sa) { /* ... */ } + + mem::StandardAllocator *GetAllocator() const { return m_standard_allocator; } + private: + virtual void *AllocateImpl(size_t size, size_t alignment) override { + return m_standard_allocator->Allocate(size, alignment); + } + + virtual void DeallocateImpl(void *buffer, size_t size, size_t alignment) override { + AMS_UNUSED(size, alignment); + return m_standard_allocator->Free(buffer); + } + + virtual bool IsEqualImpl(const MemoryResource &resource) const override { + return this == std::addressof(resource); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_memory_resource.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_memory_resource.hpp new file mode 100644 index 00000000..1ea6262c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_memory_resource.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/sf/sf_common.hpp> +#include <stratosphere/sf/sf_allocation_policies.hpp> + +namespace ams::sf { + + struct MemoryResourceAllocationPolicy { + static constexpr bool HasStatefulAllocator = true; + using Allocator = MemoryResource; + + static void *AllocateAligned(MemoryResource *mr, size_t size, size_t align) { + return mr->allocate(size, align); + } + + static void DeallocateAligned(MemoryResource *mr, void *ptr, size_t size, size_t align) { + return mr->deallocate(ptr, size, align); + } + }; + + template<typename T> + struct MemoryResourceStaticAllocator { + static constinit inline MemoryResource *g_memory_resource = nullptr; + + static constexpr void Initialize(MemoryResource *mr) { + g_memory_resource = mr; + } + + struct Policy { + static constexpr bool HasStatefulAllocator = false; + using Allocator = MemoryResource; + + template<typename> + using StatelessAllocator = Allocator; + + template<typename> + static void *AllocateAligned(size_t size, size_t align) { + return g_memory_resource->allocate(size, align); + } + + template<typename> + static void DeallocateAligned(void *ptr, size_t size, size_t align) { + g_memory_resource->deallocate(ptr, size, align); + } + }; + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_mitm_config.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_mitm_config.hpp new file mode 100644 index 00000000..be6613a0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_mitm_config.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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#if defined(ATMOSPHERE_OS_HORIZON) + #define AMS_SF_MITM_SUPPORTED 1 +#else + #define AMS_SF_MITM_SUPPORTED 0 +#endif + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_mitm_dispatch.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_mitm_dispatch.h new file mode 100644 index 00000000..ea64525a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_mitm_dispatch.h @@ -0,0 +1,105 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/sf/sf_mitm_config.hpp> + +#if AMS_SF_MITM_SUPPORTED +#include <switch.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + Handle target_session; + u32 context; + + SfBufferAttrs buffer_attrs; + SfBuffer buffers[8]; + + bool in_send_pid; + + u32 in_num_objects; + const Service* in_objects[8]; + + u32 in_num_handles; + Handle in_handles[8]; + + u32 out_num_objects; + Service* out_objects; + + SfOutHandleAttrs out_handle_attrs; + Handle* out_handles; + + u64 override_pid; +} SfMitmDispatchParams; + +NX_INLINE Result serviceMitmDispatchImpl( + Service* s, u32 request_id, + const void* in_data, u32 in_data_size, + void* out_data, u32 out_data_size, + SfMitmDispatchParams disp +) +{ + // Make a copy of the service struct, so that the compiler can assume that it won't be modified by function calls. + Service srv = *s; + + void* in = serviceMakeRequest(&srv, request_id, disp.context, + in_data_size, disp.in_send_pid, + disp.buffer_attrs, disp.buffers, + disp.in_num_objects, disp.in_objects, + disp.in_num_handles, disp.in_handles); + + if (in_data_size) + __builtin_memcpy(in, in_data, in_data_size); + + if (disp.in_send_pid && disp.override_pid) { + const u64 pid = (disp.override_pid & 0x0000FFFFFFFFFFFFul) | 0xFFFE000000000000ul; + __builtin_memcpy((u8 *)armGetTls() + 0xC, &pid, sizeof(pid)); + } + + Result rc = svcSendSyncRequest(disp.target_session == INVALID_HANDLE ? s->session : disp.target_session); + if (R_SUCCEEDED(rc)) { + void* out = NULL; + rc = serviceParseResponse(&srv, + out_data_size, &out, + disp.out_num_objects, disp.out_objects, + disp.out_handle_attrs, disp.out_handles); + + if (R_SUCCEEDED(rc) && out_data && out_data_size) + __builtin_memcpy(out_data, out, out_data_size); + } + + return rc; +} + +#define serviceMitmDispatch(_s,_rid,...) \ + serviceMitmDispatchImpl((_s),(_rid),NULL,0,NULL,0,(SfMitmDispatchParams){ __VA_ARGS__ }) + +#define serviceMitmDispatchIn(_s,_rid,_in,...) \ + serviceMitmDispatchImpl((_s),(_rid),&(_in),sizeof(_in),NULL,0,(SfMitmDispatchParams){ __VA_ARGS__ }) + +#define serviceMitmDispatchOut(_s,_rid,_out,...) \ + serviceMitmDispatchImpl((_s),(_rid),NULL,0,&(_out),sizeof(_out),(SfMitmDispatchParams){ __VA_ARGS__ }) + +#define serviceMitmDispatchInOut(_s,_rid,_in,_out,...) \ + serviceMitmDispatchImpl((_s),(_rid),&(_in),sizeof(_in),&(_out),sizeof(_out),(SfMitmDispatchParams){ __VA_ARGS__ }) + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_native_handle.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_native_handle.hpp new file mode 100644 index 00000000..d8365078 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_native_handle.hpp @@ -0,0 +1,140 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/os/os_native_handle.hpp> +#include <stratosphere/sf/sf_common.hpp> +#include <stratosphere/sf/sf_out.hpp> + +namespace ams::sf { + + class NativeHandle { + protected: + NON_COPYABLE(NativeHandle); + private: + os::NativeHandle m_handle; + bool m_managed; + public: + constexpr NativeHandle() : m_handle(os::InvalidNativeHandle), m_managed(false) { /* ... */ } + + constexpr NativeHandle(os::NativeHandle handle, bool managed) : m_handle(handle), m_managed(managed) { /* ... */ } + + constexpr NativeHandle(NativeHandle &&rhs) : m_handle(rhs.m_handle), m_managed(rhs.m_managed) { + rhs.m_managed = false; + rhs.m_handle = os::InvalidNativeHandle; + } + + constexpr NativeHandle &operator=(NativeHandle &&rhs) { + NativeHandle(std::move(rhs)).swap(*this); + return *this; + } + + constexpr ~NativeHandle() { + if (m_managed) { + os::CloseNativeHandle(m_handle); + } + } + + constexpr void Detach() { + m_managed = false; + m_handle = os::InvalidNativeHandle; + } + + constexpr void Swap(NativeHandle &rhs) { + std::swap(m_handle, rhs.m_handle); + std::swap(m_managed, rhs.m_managed); + } + + constexpr ALWAYS_INLINE void swap(NativeHandle &rhs) { + return Swap(rhs); + } + + constexpr NativeHandle GetShared() const { + return NativeHandle(m_handle, false); + } + + constexpr os::NativeHandle GetOsHandle() const { + return m_handle; + } + + constexpr bool IsManaged() const { + return m_managed; + } + + constexpr void Reset() { + NativeHandle().swap(*this); + } + }; + + class CopyHandle : public NativeHandle { + public: + using NativeHandle::NativeHandle; + using NativeHandle::operator=; + }; + + class MoveHandle : public NativeHandle { + public: + using NativeHandle::NativeHandle; + using NativeHandle::operator=; + }; + + constexpr ALWAYS_INLINE void swap(NativeHandle &lhs, NativeHandle &rhs) { + lhs.swap(rhs); + } + + template<> + class Out<CopyHandle> { + private: + NativeHandle *m_ptr; + public: + Out(NativeHandle *p) : m_ptr(p) { /* ... */ } + + void SetValue(NativeHandle v) const { + *m_ptr = std::move(v); + } + + ALWAYS_INLINE void SetValue(os::NativeHandle os_handle, bool managed) const { + return this->SetValue(NativeHandle(os_handle, managed)); + } + + NativeHandle &operator*() const { + return *m_ptr; + } + }; + + template<> + class Out<MoveHandle> { + private: + NativeHandle *m_ptr; + public: + Out(NativeHandle *p) : m_ptr(p) { /* ... */ } + + void SetValue(NativeHandle v) const { + *m_ptr = std::move(v); + } + + ALWAYS_INLINE void SetValue(os::NativeHandle os_handle, bool managed) const { + return this->SetValue(NativeHandle(os_handle, managed)); + } + + NativeHandle &operator*() const { + return *m_ptr; + } + }; + + using OutCopyHandle = Out<CopyHandle>; + using OutMoveHandle = Out<MoveHandle>; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_object_factory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_object_factory.hpp new file mode 100644 index 00000000..cc5ccd2b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_object_factory.hpp @@ -0,0 +1,355 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf/sf_common.hpp> +#include <stratosphere/sf/impl/sf_impl_template_base.hpp> +#include <stratosphere/sf/sf_object_impl_factory.hpp> +#include <stratosphere/sf/sf_memory_resource.hpp> +#include <stratosphere/sf/sf_default_allocation_policy.hpp> + +namespace ams::sf { + + namespace impl { + + template<typename> + struct IsSmartPointer : public std::false_type{}; + + template<typename T> + struct IsSmartPointer<SharedPointer<T>> : public std::true_type{}; + + template<typename T> + struct IsSmartPointer<std::shared_ptr<T>> : public std::true_type{}; + + template<typename T, typename D> + struct IsSmartPointer<std::unique_ptr<T, D>> : public std::true_type{}; + + template<typename Impl> + struct UnmanagedEmplaceImplHolderBaseGetter { + using Type = Impl; + }; + + template<typename Impl> requires (std::is_abstract<Impl>::value) + struct UnmanagedEmplaceImplHolderBaseGetter<Impl> { + class Impl2 : public Impl { + public: + using Impl::Impl; + private: + constexpr virtual void AddReference() override { /* ... */ } + constexpr virtual void Release() override { /* ... */ } + }; + + using Type = Impl2; + }; + + template<typename Impl> + class UnmanagedEmplacedImplHolder { + template<typename, typename, typename, typename, typename> + friend class impl::ImplTemplateBaseT; + private: + using Impl2 = typename UnmanagedEmplaceImplHolderBaseGetter<Impl>::Type; + static_assert(!std::is_abstract<Impl2>::value); + private: + Impl2 m_impl; + private: + template<typename... Args> + constexpr explicit UnmanagedEmplacedImplHolder(Args &&... args) : m_impl(std::forward<Args>(args)...) { /* ... */ } + public: + static constexpr Impl *GetImplPointer(UnmanagedEmplacedImplHolder *holder) { + return std::addressof(holder->m_impl); + } + }; + + template<typename Impl> + class EmplacedImplHolder : private Impl { + template<typename, typename, typename, typename, typename> + friend class impl::ImplTemplateBaseT; + private: + template<typename... Args> + constexpr explicit EmplacedImplHolder(Args &&... args) : Impl(std::forward<Args>(args)...) { /* ... */ } + public: + static constexpr Impl *GetImplPointer(EmplacedImplHolder *holder) { + return holder; + } + + template<typename Interface> + static constexpr Impl *GetEmplacedImplPointerImpl(const SharedPointer<Interface> &sp) { + using Base = impl::ImplTemplateBase<Interface, Interface, EmplacedImplHolder, EmplacedImplHolder>; + return static_cast<Base *>(sp.Get()); + } + }; + + template<typename Impl> requires (IsSmartPointer<Impl>::value) + class EmplacedImplHolder<Impl> : private Impl { + template<typename, typename, typename, typename, typename> + friend class impl::ImplTemplateBaseT; + private: + template<typename... Args> + constexpr explicit EmplacedImplHolder(Args &&... args) : Impl(std::forward<Args>(args)...) { /* ... */ } + public: + static constexpr auto *GetImplPointer(EmplacedImplHolder *holder) { + return static_cast<Impl *>(holder)->operator ->(); + } + + template<typename Interface> + static constexpr Impl *GetEmplacedImplPointerImpl(const SharedPointer<Interface> &sp) { + using Base = impl::ImplTemplateBase<Interface, Interface, EmplacedImplHolder, EmplacedImplHolder>; + return static_cast<Base *>(sp.Get()); + } + }; + + template<typename T> + using SmartPointerHolder = EmplacedImplHolder<T>; + + template<typename T> + class ManagedPointerHolder { + template<typename, typename, typename, typename, typename> + friend class impl::ImplTemplateBaseT; + private: + T *m_p; + private: + constexpr explicit ManagedPointerHolder(T *p) : m_p(p) { /* ... */ } + constexpr ~ManagedPointerHolder() { m_p->Release(); } + + static constexpr T *GetImplPointer(ManagedPointerHolder *holder) { + return holder->m_p; + } + }; + + template<typename T> + class UnmanagedPointerHolder { + template<typename, typename, typename, typename, typename> + friend class impl::ImplTemplateBaseT; + private: + T *m_p; + private: + constexpr explicit UnmanagedPointerHolder(T *p) : m_p(p) { /* ... */ } + + static constexpr T *GetImplPointer(UnmanagedPointerHolder *holder) { + return holder->m_p; + } + }; + + } + + template<typename Interface, typename Impl> + class UnmanagedServiceObject final : public impl::ImplTemplateBase<Interface, Interface, impl::UnmanagedEmplacedImplHolder<Impl>, impl::UnmanagedEmplacedImplHolder<Impl>> { + private: + using ImplBase = impl::ImplTemplateBase<Interface, Interface, impl::UnmanagedEmplacedImplHolder<Impl>, impl::UnmanagedEmplacedImplHolder<Impl>>; + public: + using ImplBase::ImplBase; + + constexpr virtual void AddReference() override { /* ... */ } + constexpr virtual void Release() override { /* ... */ } + + constexpr Impl &GetImpl() { return *impl::UnmanagedEmplacedImplHolder<Impl>::GetImplPointer(this); } + + constexpr SharedPointer<Interface> GetShared() { return SharedPointer<Interface>(this, false); } + }; + + template<typename Interface, typename T> + class UnmanagedServiceObjectByPointer final : public impl::ImplTemplateBase<Interface, Interface, impl::UnmanagedPointerHolder<T>, impl::UnmanagedPointerHolder<T>> { + private: + using ImplBase = impl::ImplTemplateBase<Interface, Interface, impl::UnmanagedPointerHolder<T>, impl::UnmanagedPointerHolder<T>>; + public: + constexpr explicit UnmanagedServiceObjectByPointer(T *ptr) : ImplBase(ptr) { /* ... */ } + + constexpr virtual void AddReference() override { /* ... */ } + constexpr virtual void Release() override { /* ... */ } + + constexpr SharedPointer<Interface> GetShared() { return SharedPointer<Interface>(this, false); } + }; + + template<typename Policy> + class ObjectFactory; + + template<typename Interface, typename Impl> + class EmplacedRef : public SharedPointer<Interface> { + template<typename> friend class ObjectFactory; + private: + constexpr explicit EmplacedRef(Interface *ptr, bool incref) : SharedPointer<Interface>(ptr, incref) { /* ... */ } + public: + constexpr EmplacedRef() { /* ... */ } + + constexpr Impl &GetImpl() const { + return *impl::EmplacedImplHolder<Impl>::template GetEmplacedImplPointerImpl<Interface>(*this); + } + }; + + template<typename Policy> requires (!IsStatefulPolicy<Policy>) + class ObjectFactory<Policy> { + private: + template<typename Interface, typename Holder, typename T> + static constexpr SharedPointer<Interface> CreateSharedForPointer(T t) { + using Base = impl::ImplTemplateBase<Interface, Interface, Holder, Holder>; + return SharedPointer<Interface>(ObjectImplFactory<Base, Policy>::Create(std::forward<T>(t)), false); + } + public: + template<typename Interface, typename Impl, typename... Args> + static constexpr EmplacedRef<Interface, Impl> CreateSharedEmplaced(Args &&... args) { + using Base = impl::ImplTemplateBase<Interface, Interface, impl::EmplacedImplHolder<Impl>, impl::EmplacedImplHolder<Impl>>; + return EmplacedRef<Interface, Impl>(ObjectImplFactory<Base, Policy>::Create(std::forward<Args>(args)...), false); + } + + template<typename T, typename... Args> + static constexpr SharedPointer<T> CreateUserSharedObject(Args &&... args) { + return SharedPointer<T>(ObjectImplFactory<T, Policy>::Create(std::forward<Args>(args)...), false); + } + + template<typename Impl, typename Interface> + static constexpr Impl *GetEmplacedImplPointer(const SharedPointer<Interface> &sp) { + return impl::EmplacedImplHolder<Impl>::template GetEmplacedImplPointerImpl<Interface>(sp); + } + + template<typename Interface, typename Smart> + static constexpr SharedPointer<Interface> CreateShared(Smart &&sp) { + return CreateSharedForPointer<Interface, impl::SmartPointerHolder<typename std::decay<decltype(sp)>::type>>(std::forward<Smart>(sp)); + } + + template<typename Interface, typename T> + static constexpr SharedPointer<Interface> CreateShared(T *p) { + return CreateSharedForPointer<Interface, impl::ManagedPointerHolder<T>>(p); + } + + template<typename Interface, typename T> + static constexpr SharedPointer<Interface> CreateSharedWithoutManagement(T *p) { + return CreateSharedForPointer<Interface, impl::UnmanagedPointerHolder<T>>(p); + } + }; + + template<typename Policy> requires (IsStatefulPolicy<Policy>) + class ObjectFactory<Policy> { + public: + using Allocator = typename Policy::Allocator; + private: + template<typename Interface, typename Holder, typename T> + static constexpr SharedPointer<Interface> CreateSharedForPointer(Allocator *a, T t) { + using Base = impl::ImplTemplateBase<Interface, Interface, Holder, Holder>; + return SharedPointer<Interface>(ObjectImplFactory<Base, Policy>::Create(a, std::forward<T>(t)), false); + } + public: + template<typename Interface, typename Impl, typename... Args> + static constexpr EmplacedRef<Interface, Impl> CreateSharedEmplaced(Allocator *a, Args &&... args) { + using Base = impl::ImplTemplateBase<Interface, Interface, impl::EmplacedImplHolder<Impl>, impl::EmplacedImplHolder<Impl>>; + return EmplacedRef<Interface, Impl>(ObjectImplFactory<Base, Policy>::Create(a, std::forward<Args>(args)...), false); + } + + template<typename T, typename... Args> + static constexpr SharedPointer<T> CreateUserSharedObject(Allocator *a, Args &&... args) { + return SharedPointer<T>(ObjectImplFactory<T, Policy>::Create(a, std::forward<Args>(args)...), false); + } + + template<typename Impl, typename Interface> + static constexpr Impl *GetEmplacedImplPointer(const SharedPointer<Interface> &sp) { + return impl::EmplacedImplHolder<Impl>::template GetEmplacedImplPointerImpl<Interface>(sp); + } + + template<typename Interface, typename Smart> + static constexpr SharedPointer<Interface> CreateShared(Allocator *a, Smart &&sp) { + return CreateSharedForPointer<Interface, impl::SmartPointerHolder<typename std::decay<decltype(sp)>::type>>(a, std::forward<Smart>(sp)); + } + + template<typename Interface, typename T> + static constexpr SharedPointer<Interface> CreateShared(Allocator *a, T *p) { + return CreateSharedForPointer<Interface, impl::ManagedPointerHolder<T>>(a, p); + } + + template<typename Interface, typename T> + static constexpr SharedPointer<Interface> CreateSharedWithoutManagement(Allocator *a, T *p) { + return CreateSharedForPointer<Interface, impl::UnmanagedPointerHolder<T>>(a, p); + } + }; + + template<typename Policy> + class StatefulObjectFactory { + public: + using Allocator = typename Policy::Allocator; + private: + using StaticObjectFactory = ObjectFactory<Policy>; + private: + Allocator *m_allocator; + public: + constexpr explicit StatefulObjectFactory(Allocator *a) : m_allocator(a) { /* ... */ } + + template<typename Interface, typename Impl, typename... Args> + constexpr EmplacedRef<Interface, Impl> CreateSharedEmplaced(Args &&... args) { + return StaticObjectFactory::template CreateSharedEmplaced<Interface, Impl>(m_allocator, std::forward<Args>(args)...); + } + + template<typename Impl, typename Interface> + static constexpr Impl *GetEmplacedImplPointer(const SharedPointer<Interface> &sp) { + return StaticObjectFactory::template GetEmplacedImplPointer<Impl, Interface>(sp); + } + + template<typename Interface, typename Smart> + constexpr SharedPointer<Interface> CreateShared(Allocator *a, Smart &&sp) { + AMS_UNUSED(a); + return StaticObjectFactory::template CreateShared<Interface>(m_allocator, std::forward<Smart>(sp)); + } + + template<typename Interface, typename T> + constexpr SharedPointer<Interface> CreateShared(Allocator *a, T *p) { + AMS_UNUSED(a); + return StaticObjectFactory::template CreateShared<Interface>(m_allocator, p); + } + + template<typename Interface, typename T> + constexpr SharedPointer<Interface> CreateSharedWithoutManagement(Allocator *a, T *p) { + AMS_UNUSED(a); + return StaticObjectFactory::template CreateSharedWithoutManagement<Interface>(m_allocator, p); + } + }; + + using DefaultObjectFactory = ObjectFactory<DefaultAllocationPolicy>; + using MemoryResourceObjectFactory = ObjectFactory<MemoryResourceAllocationPolicy>; + + template<typename Interface, typename Impl, typename... Args> + inline EmplacedRef<Interface, Impl> CreateSharedObjectEmplaced(Args &&... args) { + return DefaultObjectFactory::CreateSharedEmplaced<Interface, Impl>(std::forward<Args>(args)...); + } + + template<typename Interface, typename Impl, typename... Args> + inline EmplacedRef<Interface, Impl> CreateSharedObjectEmplaced(MemoryResource *mr, Args &&... args) { + return MemoryResourceObjectFactory::CreateSharedEmplaced<Interface, Impl>(mr, std::forward<Args>(args)...); + } + + template<typename Interface, typename Smart> + inline SharedPointer<Interface> CreateSharedObject(Smart &&sp) { + return DefaultObjectFactory::CreateShared<Interface, Smart>(std::forward<Smart>(sp)); + } + + template<typename Interface, typename Smart> + inline SharedPointer<Interface> CreateSharedObject(MemoryResource *mr, Smart &&sp) { + return MemoryResourceObjectFactory::CreateShared<Interface, Smart>(mr, std::forward<Smart>(sp)); + } + + template<typename Interface, typename T> + inline SharedPointer<Interface> CreateSharedObject(T *ptr) { + return DefaultObjectFactory::CreateShared<Interface, T>(std::move(ptr)); + } + + template<typename Interface, typename T> + inline SharedPointer<Interface> CreateSharedObjectWithoutManagement(T *ptr) { + return DefaultObjectFactory::CreateSharedWithoutManagement<Interface, T>(std::move(ptr)); + } + + template<typename Interface, typename T> + inline SharedPointer<Interface> CreateSharedObjectWithoutManagement(MemoryResource *mr, T *ptr) { + return DefaultObjectFactory::CreateSharedWithoutManagement<Interface, T>(mr, std::move(ptr)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_object_impl_factory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_object_impl_factory.hpp new file mode 100644 index 00000000..c0fb5a60 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_object_impl_factory.hpp @@ -0,0 +1,156 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf/sf_common.hpp> +#include <stratosphere/sf/sf_allocation_policies.hpp> +#include <stratosphere/sf/impl/sf_service_object_impl.hpp> + +namespace ams::sf { + + namespace impl { + + struct StatelessDummyAllocator{}; + + template<typename Base, typename Policy> + class ObjectImplFactoryWithStatelessAllocator { + public: + class Object; + using Allocator = StatelessDummyAllocator; + using StatelessAllocator = typename Policy::template StatelessAllocator<Object>; + + class Object final : private ::ams::sf::impl::ServiceObjectImplBase2, public Base { + NON_COPYABLE(Object); + NON_MOVEABLE(Object); + friend class ObjectImplFactoryWithStatelessAllocator; + private: + template<typename... Args> + explicit Object(Args &&... args) : Base(std::forward<Args>(args)...) { /* ... */ } + + static void *operator new(size_t size) { + return Policy::template AllocateAligned<Object>(size, alignof(Object)); + } + + static void operator delete(void *ptr, size_t size) { + return Policy::template DeallocateAligned<Object>(ptr, size, alignof(Object)); + } + + static void *operator new(size_t size, Allocator *); + static void operator delete(void *ptr, Allocator *); + + void DisposeImpl() { + delete this; + } + public: + void AddReference() { + ServiceObjectImplBase2::AddReferenceImpl(); + } + + void Release() { + if (ServiceObjectImplBase2::ReleaseImpl()) { + this->DisposeImpl(); + } + } + + Allocator *GetAllocator() const { + return nullptr; + } + }; + + template<typename... Args> + static Object *Create(Args &&... args) { + return new Object(std::forward<Args>(args)...); + } + + template<typename... Args> + static Object *Create(Allocator *, Args &&... args) { + return new Object(std::forward<Args>(args)...); + } + }; + + template<typename Base, typename Policy> + class ObjectImplFactoryWithStatefulAllocator { + public: + using Allocator = typename Policy::Allocator; + + class Object final : private ::ams::sf::impl::ServiceObjectImplBase2, public Base { + NON_COPYABLE(Object); + NON_MOVEABLE(Object); + friend class ObjectImplFactoryWithStatefulAllocator; + private: + Allocator *m_allocator; + private: + template<typename... Args> + explicit Object(Args &&... args) : Base(std::forward<Args>(args)...) { /* ... */ } + + static void *operator new(size_t size); + + static void operator delete(void *ptr, size_t size) { + AMS_UNUSED(ptr, size); + } + + static void *operator new(size_t size, Allocator *a) { + return Policy::AllocateAligned(a, size, alignof(Object)); + } + + static void operator delete(void *ptr, Allocator *a) { + return Policy::DeallocateAligned(a, ptr, sizeof(Object), alignof(Object)); + } + + void DisposeImpl() { + Allocator *a = this->GetAllocator(); + std::destroy_at(this); + operator delete(this, a); + } + public: + void AddReference() { + ServiceObjectImplBase2::AddReferenceImpl(); + } + + void Release() { + if (ServiceObjectImplBase2::ReleaseImpl()) { + this->DisposeImpl(); + } + } + + Allocator *GetAllocator() const { + return m_allocator; + } + }; + + template<typename... Args> + static Object *Create(Allocator *a, Args &&... args) { + auto *ptr = new (a) Object(std::forward<Args>(args)...); + if (ptr != nullptr) { + ptr->m_allocator = a; + } + return ptr; + } + }; + + } + + template<typename Base, typename Policy> + class ObjectImplFactory; + + template<typename Base, typename Policy> requires (!IsStatefulPolicy<Policy>) + class ObjectImplFactory<Base, Policy> : public impl::ObjectImplFactoryWithStatelessAllocator<Base, Policy>{}; + + template<typename Base, typename Policy> requires (IsStatefulPolicy<Policy>) + class ObjectImplFactory<Base, Policy> : public impl::ObjectImplFactoryWithStatefulAllocator<Base, Policy>{}; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_out.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_out.hpp new file mode 100644 index 00000000..02a52e91 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_out.hpp @@ -0,0 +1,79 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/sf/sf_common.hpp> +#include <stratosphere/sf/cmif/sf_cmif_pointer_and_size.hpp> + +namespace ams::sf { + + namespace impl { + + struct OutBaseTag{}; + + } + + template<typename> + struct IsOutForceEnabled : public std::false_type{}; + + template<> + struct IsOutForceEnabled<::ams::Result> : public std::true_type{}; + + template<typename T> + concept OutEnabled = (std::is_trivial<T>::value || IsOutForceEnabled<T>::value) && !std::is_pointer<T>::value; + + template<typename T> + class Out : public impl::OutBaseTag { + static_assert(OutEnabled<T>); + public: + static constexpr size_t TypeSize = sizeof(T); + private: + T *m_ptr; + public: + constexpr Out(uintptr_t p) : m_ptr(reinterpret_cast<T *>(p)) { /* ... */ } + constexpr Out(T *p) : m_ptr(p) { /* ... */ } + constexpr Out(const cmif::PointerAndSize &pas) : m_ptr(reinterpret_cast<T *>(pas.GetAddress())) { /* TODO: Is AMS_ABORT_UNLESS(pas.GetSize() >= sizeof(T)); necessary? */ } + + template<typename U> requires (std::integral<T> && std::is_enum<U>::value && std::same_as<typename std::underlying_type<U>::type, T>) + constexpr Out(U *p) : m_ptr(reinterpret_cast<T *>(p)) { static_assert(sizeof(U) == sizeof(T)); static_assert(alignof(U) == alignof(T)); } + + void SetValue(const T& value) const { + *m_ptr = value; + } + + const T &GetValue() const { + return *m_ptr; + } + + T *GetPointer() const { + return m_ptr; + } + + /* Convenience operators. */ + T &operator*() const { + return *m_ptr; + } + + T *operator->() const { + return m_ptr; + } + }; + + template<typename T> + class Out<T *> { + static_assert(!std::is_same<T, T>::value, "Invalid sf::Out<T> (Raw Pointer)"); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_service_object.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_service_object.hpp new file mode 100644 index 00000000..a6fa1ce4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_service_object.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/sf/sf_common.hpp> +#include <stratosphere/sf/sf_out.hpp> +#include <stratosphere/sf/sf_shared_object.hpp> + +namespace ams::sf { + + class IServiceObject : public ISharedObject { + public: + virtual ~IServiceObject() { /* ... */ } + }; + + template<typename T> + concept IsServiceObject = std::derived_from<T, IServiceObject>; + + #if AMS_SF_MITM_SUPPORTED + class IMitmServiceObject : public IServiceObject { + public: + virtual ~IMitmServiceObject() { /* ... */ } + }; + + class MitmServiceImplBase { + protected: + std::shared_ptr<::Service> m_forward_service; + sm::MitmProcessInfo m_client_info; + public: + MitmServiceImplBase(std::shared_ptr<::Service> &&s, const sm::MitmProcessInfo &c) : m_forward_service(std::move(s)), m_client_info(c) { /* ... */ } + }; + + template<typename T> + concept IsMitmServiceObject = IsServiceObject<T> && std::derived_from<T, IMitmServiceObject>; + + template<typename T> + concept IsMitmServiceImpl = requires (std::shared_ptr<::Service> &&s, const sm::MitmProcessInfo &c) { + { T(std::forward<std::shared_ptr<::Service>>(s), c) }; + { T::ShouldMitm(c) } -> std::same_as<bool>; + }; + #else + template<typename T> + concept IsMitmServiceObject = false; + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_shared_object.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_shared_object.hpp new file mode 100644 index 00000000..3e74dbcd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_shared_object.hpp @@ -0,0 +1,200 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/sf/sf_common.hpp> +#include <stratosphere/sf/sf_mitm_config.hpp> +#include <stratosphere/sf/sf_out.hpp> + +namespace ams::sf { + + class ISharedObject { + NON_COPYABLE(ISharedObject); + NON_MOVEABLE(ISharedObject); + protected: + constexpr ISharedObject() { /* ... */ } + ~ISharedObject() { /* ... */ } + public: + constexpr virtual void AddReference() = 0; + constexpr virtual void Release() = 0; + }; + + namespace impl { + + class SharedPointerBase { + private: + ISharedObject *m_ptr; + private: + constexpr void AddReferenceImpl() const { + if (m_ptr != nullptr) { + m_ptr->AddReference(); + } + } + + constexpr void ReleaseImpl() const { + if (m_ptr != nullptr) { + m_ptr->Release(); + } + } + public: + constexpr SharedPointerBase() : m_ptr(nullptr) { /* ... */ } + + constexpr SharedPointerBase(ISharedObject *ptr, bool incref) : m_ptr(ptr) { + if (incref) { + this->AddReferenceImpl(); + } + } + + constexpr ~SharedPointerBase() { + this->ReleaseImpl(); + } + + constexpr SharedPointerBase(const SharedPointerBase &rhs) : m_ptr(rhs.m_ptr) { + this->AddReferenceImpl(); + } + + constexpr SharedPointerBase(SharedPointerBase &&rhs) : m_ptr(rhs.m_ptr) { + rhs.m_ptr = nullptr; + } + + constexpr SharedPointerBase &operator=(const SharedPointerBase &rhs) { + SharedPointerBase tmp(rhs); + tmp.swap(*this); + return *this; + } + + constexpr SharedPointerBase &operator=(SharedPointerBase &&rhs) { + SharedPointerBase tmp(std::move(rhs)); + tmp.swap(*this); + return *this; + } + + constexpr void swap(SharedPointerBase &rhs) { + std::swap(m_ptr, rhs.m_ptr); + } + + constexpr ISharedObject *Detach() { + ISharedObject *ret = m_ptr; + m_ptr = nullptr; + return ret; + } + + constexpr ISharedObject *Get() const { + return m_ptr; + } + }; + + } + + template<typename I> + class SharedPointer { + template<typename> friend class ::ams::sf::SharedPointer; + template<typename> friend class ::ams::sf::Out; + public: + using Interface = I; + private: + impl::SharedPointerBase m_base; + public: + constexpr SharedPointer() : m_base() { /* ... */ } + + constexpr SharedPointer(std::nullptr_t) : m_base() { /* ... */ } + + constexpr SharedPointer(Interface *ptr, bool incref) : m_base(static_cast<ISharedObject *>(ptr), incref) { /* ... */ } + + constexpr SharedPointer(const SharedPointer &rhs) : m_base(rhs.m_base) { /* ... */ } + + constexpr SharedPointer(SharedPointer &&rhs) : m_base(std::move(rhs.m_base)) { /* ... */ } + + template<typename U> requires std::derived_from<U, Interface> + constexpr SharedPointer(const SharedPointer<U> &rhs) : m_base(rhs.m_base) { /* ... */ } + + template<typename U> requires std::derived_from<U, Interface> + constexpr SharedPointer(SharedPointer<U> &&rhs) : m_base(std::move(rhs.m_base)) { /* ... */ } + + constexpr SharedPointer &operator=(std::nullptr_t) { + SharedPointer().swap(*this); + return *this; + } + + constexpr SharedPointer &operator=(const SharedPointer &rhs) { + SharedPointer tmp(rhs); + tmp.swap(*this); + return *this; + } + + constexpr SharedPointer &operator=(SharedPointer &&rhs) { + SharedPointer tmp(std::move(rhs)); + tmp.swap(*this); + return *this; + } + + template<typename U> requires std::derived_from<U, Interface> + constexpr SharedPointer &operator=(const SharedPointer<U> &rhs) { + SharedPointer tmp(rhs); + tmp.swap(*this); + return *this; + } + + template<typename U> requires std::derived_from<U, Interface> + constexpr SharedPointer &operator=(SharedPointer<U> &&rhs) { + SharedPointer tmp(std::move(rhs)); + tmp.swap(*this); + return *this; + } + + constexpr void swap(SharedPointer &rhs) { + m_base.swap(rhs.m_base); + } + + constexpr Interface *Detach() { + return static_cast<Interface *>(m_base.Detach()); + } + + constexpr void Reset() { + *this = nullptr; + } + + constexpr Interface *Get() const { + return static_cast<Interface *>(m_base.Get()); + } + + constexpr Interface *operator->() const { + AMS_ASSERT(this->Get() != nullptr); + return this->Get(); + } + + constexpr bool operator!() const { + return this->Get() == nullptr; + } + + constexpr bool operator==(std::nullptr_t) const { + return this->Get() == nullptr; + } + + constexpr bool operator!=(std::nullptr_t) const { + return this->Get() != nullptr; + } + }; + + template<typename Interface> + constexpr void Swap(SharedPointer<Interface> &lhs, SharedPointer<Interface> &rhs) { + lhs.swap(rhs); + } + + constexpr inline void ReleaseSharedObject(ISharedObject *ptr) { + ptr->Release(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_standard_allocation_policy.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_standard_allocation_policy.hpp new file mode 100644 index 00000000..5a7b6cb1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_standard_allocation_policy.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/sf/sf_default_allocation_policy.hpp> + +namespace ams::sf { + + using StandardAllocationPolicy = DefaultAllocationPolicy; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_std_allocation_policy.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_std_allocation_policy.hpp new file mode 100644 index 00000000..7542a287 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_std_allocation_policy.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/sf/sf_common.hpp> +#include <stratosphere/sf/sf_allocation_policies.hpp> + +namespace ams::sf { + + template<template<typename> class StdAllocator> + class StdAllocationPolicy { + public: + static constexpr bool HasStatefulAllocator = false; + using Allocator = impl::StatelessDummyAllocator; + + template<typename T> + struct StatelessAllocator { + static void *Allocate(size_t size) { + return StdAllocator<T>().allocate(size / sizeof(T)); + } + static void Deallocate(void *ptr, size_t size) { + StdAllocator<T>().deallocate(static_cast<T *>(ptr), size / sizeof(T)); + } + }; + + template<typename T> + static void *AllocateAligned(size_t size, size_t align) { + AMS_UNUSED(align); + return StdAllocator<T>().allocate(size / sizeof(T)); + } + + template<typename T> + static void DeallocateAligned(void *ptr, size_t size, size_t align) { + AMS_UNUSED(align); + StdAllocator<T>().deallocate(static_cast<T *>(ptr), size / sizeof(T)); + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_types.hpp new file mode 100644 index 00000000..64146230 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sf/sf_types.hpp @@ -0,0 +1,506 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +#if defined(ATMOSPHERE_OS_WINDOWS) || defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS) + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(ATMOSPHERE_COMPILER_CLANG) +#define AMS_SF_HIPC_PARSE_IMPL_CONSTEXPR ALWAYS_INLINE +#else +#define AMS_SF_HIPC_PARSE_IMPL_CONSTEXPR constexpr ALWAYS_INLINE +#endif + +#define HIPC_AUTO_RECV_STATIC UINT8_MAX +#define HIPC_RESPONSE_NO_PID UINT32_MAX + +typedef struct HipcMetadata { + u32 type; + u32 num_send_statics; + u32 num_send_buffers; + u32 num_recv_buffers; + u32 num_exch_buffers; + u32 num_data_words; + u32 num_recv_statics; // also accepts HIPC_AUTO_RECV_STATIC + u32 send_pid; + u32 num_copy_handles; + u32 num_move_handles; +} HipcMetadata; + +typedef struct HipcHeader { + u32 type : 16; + u32 num_send_statics : 4; + u32 num_send_buffers : 4; + u32 num_recv_buffers : 4; + u32 num_exch_buffers : 4; + u32 num_data_words : 10; + u32 recv_static_mode : 4; + u32 padding : 6; + u32 recv_list_offset : 11; // Unused. + u32 has_special_header : 1; +} HipcHeader; + +typedef struct HipcSpecialHeader { + u32 send_pid : 1; + u32 num_copy_handles : 4; + u32 num_move_handles : 4; + u32 padding : 23; +} HipcSpecialHeader; + +typedef struct HipcStaticDescriptor { + u32 index : 6; + u32 address_high : 6; + u32 address_mid : 4; + u32 size : 16; + u32 address_low; +} HipcStaticDescriptor; + +typedef struct HipcBufferDescriptor { + u32 size_low; + u32 address_low; + u32 mode : 2; + u32 address_high : 22; + u32 size_high : 4; + u32 address_mid : 4; +} HipcBufferDescriptor; + +typedef struct HipcRecvListEntry { + u32 address_low; + u32 address_high : 16; + u32 size : 16; +} HipcRecvListEntry; + +typedef struct HipcRequest { + HipcStaticDescriptor* send_statics; + HipcBufferDescriptor* send_buffers; + HipcBufferDescriptor* recv_buffers; + HipcBufferDescriptor* exch_buffers; + u32* data_words; + HipcRecvListEntry* recv_list; + u32* copy_handles; + u32* move_handles; +} HipcRequest; + +typedef struct HipcParsedRequest { + HipcMetadata meta; + HipcRequest data; + u64 pid; +} HipcParsedRequest; + +typedef struct HipcResponse { + u64 pid; + u32 num_statics; + u32 num_data_words; + u32 num_copy_handles; + u32 num_move_handles; + HipcStaticDescriptor* statics; + u32* data_words; + u32* copy_handles; + u32* move_handles; +} HipcResponse; + +typedef enum HipcBufferMode { + HipcBufferMode_Normal = 0, + HipcBufferMode_NonSecure = 1, + HipcBufferMode_Invalid = 2, + HipcBufferMode_NonDevice = 3, +} HipcBufferMode; + + +AMS_SF_HIPC_PARSE_IMPL_CONSTEXPR HipcStaticDescriptor hipcMakeSendStatic(const void* buffer, size_t size, u8 index) { + return (HipcStaticDescriptor){ + .index = index, + .address_high = (u32)((uintptr_t)buffer >> 36), + .address_mid = (u32)((uintptr_t)buffer >> 32), + .size = (u32)size, + .address_low = (u32)(uintptr_t)buffer, + }; +} + +AMS_SF_HIPC_PARSE_IMPL_CONSTEXPR HipcBufferDescriptor hipcMakeBuffer(const void* buffer, size_t size, HipcBufferMode mode) { + return (HipcBufferDescriptor){ + .size_low = (u32)size, + .address_low = (u32)(uintptr_t)buffer, + .mode = mode, + .address_high = (u32)((uintptr_t)buffer >> 36), + .size_high = (u32)(size >> 32), + .address_mid = (u32)((uintptr_t)buffer >> 32), + }; +} + +AMS_SF_HIPC_PARSE_IMPL_CONSTEXPR HipcRecvListEntry hipcMakeRecvStatic(void* buffer, size_t size) { + return (HipcRecvListEntry){ + .address_low = (u32)((uintptr_t)buffer), + .address_high = (u32)((uintptr_t)buffer >> 32), + .size = (u32)size, + }; +} + +AMS_SF_HIPC_PARSE_IMPL_CONSTEXPR void* hipcGetStaticAddress(const HipcStaticDescriptor* desc) +{ + return (void*)(desc->address_low | ((uintptr_t)desc->address_mid << 32) | ((uintptr_t)desc->address_high << 36)); +} + +AMS_SF_HIPC_PARSE_IMPL_CONSTEXPR size_t hipcGetStaticSize(const HipcStaticDescriptor* desc) +{ + return desc->size; +} + +AMS_SF_HIPC_PARSE_IMPL_CONSTEXPR void* hipcGetBufferAddress(const HipcBufferDescriptor* desc) +{ + return (void*)(desc->address_low | ((uintptr_t)desc->address_mid << 32) | ((uintptr_t)desc->address_high << 36)); +} + +AMS_SF_HIPC_PARSE_IMPL_CONSTEXPR size_t hipcGetBufferSize(const HipcBufferDescriptor* desc) +{ + return desc->size_low | ((size_t)desc->size_high << 32); +} + +AMS_SF_HIPC_PARSE_IMPL_CONSTEXPR HipcRequest hipcCalcRequestLayout(HipcMetadata meta, void* base) { + // Copy handles + u32* copy_handles = NULL; + if (meta.num_copy_handles) { + copy_handles = (u32*)base; + base = copy_handles + meta.num_copy_handles; + } + + // Move handles + u32* move_handles = NULL; + if (meta.num_move_handles) { + move_handles = (u32*)base; + base = move_handles + meta.num_move_handles; + } + + // Send statics + HipcStaticDescriptor* send_statics = NULL; + if (meta.num_send_statics) { + send_statics = (HipcStaticDescriptor*)base; + base = send_statics + meta.num_send_statics; + } + + // Send buffers + HipcBufferDescriptor* send_buffers = NULL; + if (meta.num_send_buffers) { + send_buffers = (HipcBufferDescriptor*)base; + base = send_buffers + meta.num_send_buffers; + } + + // Recv buffers + HipcBufferDescriptor* recv_buffers = NULL; + if (meta.num_recv_buffers) { + recv_buffers = (HipcBufferDescriptor*)base; + base = recv_buffers + meta.num_recv_buffers; + } + + // Exch buffers + HipcBufferDescriptor* exch_buffers = NULL; + if (meta.num_exch_buffers) { + exch_buffers = (HipcBufferDescriptor*)base; + base = exch_buffers + meta.num_exch_buffers; + } + + // Data words + u32* data_words = NULL; + if (meta.num_data_words) { + data_words = (u32*)base; + base = data_words + meta.num_data_words; + } + + // Recv list + HipcRecvListEntry* recv_list = NULL; + if (meta.num_recv_statics) + recv_list = (HipcRecvListEntry*)base; + + return (HipcRequest){ + .send_statics = send_statics, + .send_buffers = send_buffers, + .recv_buffers = recv_buffers, + .exch_buffers = exch_buffers, + .data_words = data_words, + .recv_list = recv_list, + .copy_handles = copy_handles, + .move_handles = move_handles, + }; +} + +AMS_SF_HIPC_PARSE_IMPL_CONSTEXPR HipcRequest hipcMakeRequest(void* base, HipcMetadata meta) { + // Write message header + bool has_special_header = meta.send_pid || meta.num_copy_handles || meta.num_move_handles; + HipcHeader* hdr = (HipcHeader*)base; + base = hdr+1; + *hdr = (HipcHeader){ + .type = meta.type, + .num_send_statics = meta.num_send_statics, + .num_send_buffers = meta.num_send_buffers, + .num_recv_buffers = meta.num_recv_buffers, + .num_exch_buffers = meta.num_exch_buffers, + .num_data_words = meta.num_data_words, + .recv_static_mode = meta.num_recv_statics ? (meta.num_recv_statics != HIPC_AUTO_RECV_STATIC ? 2u + meta.num_recv_statics : 2u) : 0u, + .padding = 0, + .recv_list_offset = 0, + .has_special_header = has_special_header, + }; + + // Write special header + if (has_special_header) { + HipcSpecialHeader* sphdr = (HipcSpecialHeader*)base; + base = sphdr+1; + *sphdr = (HipcSpecialHeader){ + .send_pid = meta.send_pid, + .num_copy_handles = meta.num_copy_handles, + .num_move_handles = meta.num_move_handles, + }; + if (meta.send_pid) + base = (u8*)base + sizeof(u64); + } + + // Calculate layout + return hipcCalcRequestLayout(meta, base); +} + +#define hipcMakeRequestInline(_base,...) hipcMakeRequest((_base),(HipcMetadata){ __VA_ARGS__ }) + +AMS_SF_HIPC_PARSE_IMPL_CONSTEXPR HipcParsedRequest hipcParseRequest(void* base) { + // Parse message header + HipcHeader hdr = {}; + __builtin_memcpy(&hdr, base, sizeof(hdr)); + base = (u8*)base + sizeof(hdr); + u32 num_recv_statics = 0; + u64 pid = 0; + + // Parse recv static mode + if (hdr.recv_static_mode) { + if (hdr.recv_static_mode == 2u) + num_recv_statics = HIPC_AUTO_RECV_STATIC; + else if (hdr.recv_static_mode > 2u) + num_recv_statics = hdr.recv_static_mode - 2u; + } + + // Parse special header + HipcSpecialHeader sphdr = {}; + if (hdr.has_special_header) { + __builtin_memcpy(&sphdr, base, sizeof(sphdr)); + base = (u8*)base + sizeof(sphdr); + + // Read PID descriptor + if (sphdr.send_pid) { + pid = *(u64*)base; + base = (u8*)base + sizeof(u64); + } + } + + const HipcMetadata meta = { + .type = hdr.type, + .num_send_statics = hdr.num_send_statics, + .num_send_buffers = hdr.num_send_buffers, + .num_recv_buffers = hdr.num_recv_buffers, + .num_exch_buffers = hdr.num_exch_buffers, + .num_data_words = hdr.num_data_words, + .num_recv_statics = num_recv_statics, + .send_pid = sphdr.send_pid, + .num_copy_handles = sphdr.num_copy_handles, + .num_move_handles = sphdr.num_move_handles, + }; + + return (HipcParsedRequest){ + .meta = meta, + .data = hipcCalcRequestLayout(meta, base), + .pid = pid, + }; +} + +AMS_SF_HIPC_PARSE_IMPL_CONSTEXPR HipcResponse hipcParseResponse(void* base) { + // Parse header + HipcHeader hdr = {}; + __builtin_memcpy(&hdr, base, sizeof(hdr)); + base = (u8*)base + sizeof(hdr); + + // Initialize response + HipcResponse response = {}; + response.num_statics = hdr.num_send_statics; + response.num_data_words = hdr.num_data_words; + response.pid = HIPC_RESPONSE_NO_PID; + + // Parse special header + if (hdr.has_special_header) + { + HipcSpecialHeader sphdr = {}; + __builtin_memcpy(&sphdr, base, sizeof(sphdr)); + base = (u8*)base + sizeof(sphdr); + + // Update response + response.num_copy_handles = sphdr.num_copy_handles; + response.num_move_handles = sphdr.num_move_handles; + + // Parse PID descriptor + if (sphdr.send_pid) { + response.pid = *(u64*)base; + base = (u8*)base + sizeof(u64); + } + } + + // Copy handles + response.copy_handles = (u32*)base; + base = response.copy_handles + response.num_copy_handles; + + // Move handles + response.move_handles = (u32*)base; + base = response.move_handles + response.num_move_handles; + + // Send statics + response.statics = (HipcStaticDescriptor*)base; + base = response.statics + response.num_statics; + + // Data words + response.data_words = (u32*)base; + + return response; +} + +typedef enum CmifCommandType { + CmifCommandType_Invalid = 0, + CmifCommandType_LegacyRequest = 1, + CmifCommandType_Close = 2, + CmifCommandType_LegacyControl = 3, + CmifCommandType_Request = 4, + CmifCommandType_Control = 5, + CmifCommandType_RequestWithContext = 6, + CmifCommandType_ControlWithContext = 7, +} CmifCommandType; + +typedef enum CmifDomainRequestType { + CmifDomainRequestType_Invalid = 0, + CmifDomainRequestType_SendMessage = 1, + CmifDomainRequestType_Close = 2, +} CmifDomainRequestType; + +typedef struct CmifInHeader { + u32 magic; + u32 version; + u32 command_id; + u32 token; +} CmifInHeader; + +typedef struct CmifOutHeader { + u32 magic; + u32 version; + Result result; + u32 token; +} CmifOutHeader; + +typedef struct CmifDomainInHeader { + u8 type; + u8 num_in_objects; + u16 data_size; + u32 object_id; + u32 padding; + u32 token; +} CmifDomainInHeader; + +typedef struct CmifDomainOutHeader { + u32 num_out_objects; + u32 padding[3]; +} CmifDomainOutHeader; + +typedef struct CmifRequestFormat { + u32 object_id; + u32 request_id; + u32 context; + u32 data_size; + u32 server_pointer_size; + u32 num_in_auto_buffers; + u32 num_out_auto_buffers; + u32 num_in_buffers; + u32 num_out_buffers; + u32 num_inout_buffers; + u32 num_in_pointers; + u32 num_out_pointers; + u32 num_out_fixed_pointers; + u32 num_objects; + u32 num_handles; + u32 send_pid; +} CmifRequestFormat; + +typedef struct CmifRequest { + HipcRequest hipc; + void* data; + u16* out_pointer_sizes; + u32* objects; + u32 server_pointer_size; + u32 cur_in_ptr_id; +} CmifRequest; + +typedef struct CmifResponse { + void* data; + u32* objects; + u32* copy_handles; + u32* move_handles; +} CmifResponse; + +enum { + SfBufferAttr_In = BIT(0), + SfBufferAttr_Out = BIT(1), + SfBufferAttr_HipcMapAlias = BIT(2), + SfBufferAttr_HipcPointer = BIT(3), + SfBufferAttr_FixedSize = BIT(4), + SfBufferAttr_HipcAutoSelect = BIT(5), + SfBufferAttr_HipcMapTransferAllowsNonSecure = BIT(6), + SfBufferAttr_HipcMapTransferAllowsNonDevice = BIT(7), +}; + +typedef struct SfBufferAttrs { + u32 attr0; + u32 attr1; + u32 attr2; + u32 attr3; + u32 attr4; + u32 attr5; + u32 attr6; + u32 attr7; +} SfBufferAttrs; + +typedef struct SfBuffer { + const void* ptr; + size_t size; +} SfBuffer; + +typedef enum SfOutHandleAttr { + SfOutHandleAttr_None = 0, + SfOutHandleAttr_HipcCopy = 1, + SfOutHandleAttr_HipcMove = 2, +} SfOutHandleAttr; + +typedef struct SfOutHandleAttrs { + SfOutHandleAttr attr0; + SfOutHandleAttr attr1; + SfOutHandleAttr attr2; + SfOutHandleAttr attr3; + SfOutHandleAttr attr4; + SfOutHandleAttr attr5; + SfOutHandleAttr attr6; + SfOutHandleAttr attr7; +} SfOutHandleAttrs; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm.hpp new file mode 100644 index 00000000..02476be0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/sm/sm_types.hpp> +#include <stratosphere/sm/sm_api.hpp> +#include <stratosphere/sm/sm_mitm_api.hpp> + +#include <stratosphere/sm/sm_manager_api.hpp> + +#include <stratosphere/sm/impl/sm_user_interface.hpp> +#include <stratosphere/sm/impl/sm_manager_interface.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm/impl/sm_manager_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm/impl/sm_manager_interface.hpp new file mode 100644 index 00000000..66dce5f8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm/impl/sm_manager_interface.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/sm/sm_types.hpp> +#include <stratosphere/tipc.hpp> + +#define AMS_SM_I_MANAGER_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_TIPC_METHOD_INFO(C, H, 0, Result, RegisterProcess, (os::ProcessId process_id, const tipc::InBuffer acid_sac, const tipc::InBuffer aci_sac), (process_id, acid_sac, aci_sac)) \ + AMS_TIPC_METHOD_INFO(C, H, 1, Result, UnregisterProcess, (os::ProcessId process_id), (process_id)) \ + AMS_TIPC_METHOD_INFO(C, H, 65000, void, AtmosphereEndInitDefers, (), ()) \ + AMS_TIPC_METHOD_INFO(C, H, 65001, void, AtmosphereHasMitm, (tipc::Out<bool> out, sm::ServiceName service), (out, service)) \ + AMS_TIPC_METHOD_INFO(C, H, 65002, Result, AtmosphereRegisterProcess, (os::ProcessId process_id, ncm::ProgramId program_id, cfg::OverrideStatus override_status, const tipc::InBuffer acid_sac, const tipc::InBuffer aci_sac), (process_id, program_id, override_status, acid_sac, aci_sac)) + +AMS_TIPC_DEFINE_INTERFACE(ams::sm::impl, IManagerInterface, AMS_SM_I_MANAGER_INTERFACE_INTERFACE_INFO) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm/impl/sm_user_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm/impl/sm_user_interface.hpp new file mode 100644 index 00000000..afec2744 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm/impl/sm_user_interface.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/sm/sm_types.hpp> +#include <stratosphere/tipc.hpp> + +#define AMS_SM_I_USER_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_TIPC_METHOD_INFO(C, H, 0, Result, RegisterClient, (const tipc::ClientProcessId client_process_id), (client_process_id)) \ + AMS_TIPC_METHOD_INFO(C, H, 1, Result, GetServiceHandle, (tipc::OutMoveHandle out_h, sm::ServiceName service), (out_h, service)) \ + AMS_TIPC_METHOD_INFO(C, H, 2, Result, RegisterService, (tipc::OutMoveHandle out_h, sm::ServiceName service, u32 max_sessions, bool is_light), (out_h, service, max_sessions, is_light)) \ + AMS_TIPC_METHOD_INFO(C, H, 3, Result, UnregisterService, (sm::ServiceName service), (service)) \ + AMS_TIPC_METHOD_INFO(C, H, 4, Result, DetachClient, (const tipc::ClientProcessId client_process_id), (client_process_id)) \ + AMS_TIPC_METHOD_INFO(C, H, 65000, Result, AtmosphereInstallMitm, (tipc::OutMoveHandle srv_h, tipc::OutMoveHandle qry_h, sm::ServiceName service), (srv_h, qry_h, service)) \ + AMS_TIPC_METHOD_INFO(C, H, 65001, Result, AtmosphereUninstallMitm, (sm::ServiceName service), (service)) \ + AMS_TIPC_METHOD_INFO(C, H, 65003, Result, AtmosphereAcknowledgeMitmSession, (tipc::Out<sm::MitmProcessInfo> client_info, tipc::OutMoveHandle fwd_h, sm::ServiceName service), (client_info, fwd_h, service)) \ + AMS_TIPC_METHOD_INFO(C, H, 65004, Result, AtmosphereHasMitm, (tipc::Out<bool> out, sm::ServiceName service), (out, service)) \ + AMS_TIPC_METHOD_INFO(C, H, 65005, Result, AtmosphereWaitMitm, (sm::ServiceName service), (service)) \ + AMS_TIPC_METHOD_INFO(C, H, 65006, Result, AtmosphereDeclareFutureMitm, (sm::ServiceName service), (service)) \ + AMS_TIPC_METHOD_INFO(C, H, 65007, Result, AtmosphereClearFutureMitm, (sm::ServiceName service), (service)) \ + AMS_TIPC_METHOD_INFO(C, H, 65100, Result, AtmosphereHasService, (tipc::Out<bool> out, sm::ServiceName service), (out, service)) \ + AMS_TIPC_METHOD_INFO(C, H, 65101, Result, AtmosphereWaitService, (sm::ServiceName service), (service)) + +AMS_TIPC_DEFINE_INTERFACE(ams::sm::impl, IUserInterface, AMS_SM_I_USER_INTERFACE_INTERFACE_INFO) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm/sm_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm/sm_api.hpp new file mode 100644 index 00000000..60f81045 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm/sm_api.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/sm/sm_types.hpp> + +namespace ams::sm { + + /* Initialization. */ + Result Initialize(); + Result Finalize(); + + /* Ordinary SM API. */ + Result GetServiceHandle(os::NativeHandle *out, ServiceName name); + Result RegisterService(os::NativeHandle *out, ServiceName name, size_t max_sessions, bool is_light); + Result UnregisterService(ServiceName name); + + /* Atmosphere extensions. */ + Result HasService(bool *out, ServiceName name); + Result WaitService(ServiceName name); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm/sm_manager_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm/sm_manager_api.hpp new file mode 100644 index 00000000..7fa3ec4f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm/sm_manager_api.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/os.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> +#include <stratosphere/cfg/cfg_types.hpp> +#include <stratosphere/sm/sm_types.hpp> + +namespace ams::sm::manager { + + /* Manager API. */ + Result RegisterProcess(os::ProcessId process_id, ncm::ProgramId program_id, cfg::OverrideStatus status, const void *acid, size_t acid_size, const void *aci, size_t aci_size); + Result UnregisterProcess(os::ProcessId process_id); + + /* Atmosphere extensions. */ + Result EndInitialDefers(); + Result HasMitm(bool *out, ServiceName name); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm/sm_mitm_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm/sm_mitm_api.hpp new file mode 100644 index 00000000..c38d8ce4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm/sm_mitm_api.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/sm/sm_types.hpp> +#include <stratosphere/sf/sf_mitm_config.hpp> + +#if AMS_SF_MITM_SUPPORTED +namespace ams::sm::mitm { + + /* Mitm API. */ + Result InstallMitm(os::NativeHandle *out_port, os::NativeHandle *out_query, ServiceName name); + Result UninstallMitm(ServiceName name); + Result DeclareFutureMitm(ServiceName name); + Result ClearFutureMitm(ServiceName name); + Result AcknowledgeSession(Service *out_service, MitmProcessInfo *out_info, ServiceName name); + Result HasMitm(bool *out, ServiceName name); + Result WaitMitm(ServiceName name); + +} +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm/sm_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm/sm_types.hpp new file mode 100644 index 00000000..675802be --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sm/sm_types.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/ncm/ncm_ids.hpp> +#include <stratosphere/cfg/cfg_types.hpp> + +namespace ams::sm { + + struct ServiceName { + static constexpr size_t MaxLength = 8; + + char name[MaxLength]; + + static constexpr ServiceName Encode(const char *name, size_t name_size) { + ServiceName out{}; + + for (size_t i = 0; i < MaxLength; i++) { + if (i < name_size) { + out.name[i] = name[i]; + } else { + out.name[i] = 0; + } + } + + return out; + } + + static constexpr ServiceName Encode(util::string_view name) { + return Encode(name.data(), name.size()); + } + }; + + static constexpr inline ServiceName InvalidServiceName = ServiceName::Encode(""); + + static_assert(alignof(ServiceName) == 1, "ServiceName definition!"); + + inline bool operator==(const ServiceName &lhs, const ServiceName &rhs) { + return std::memcmp(std::addressof(lhs), std::addressof(rhs), sizeof(ServiceName)) == 0; + } + + inline bool operator!=(const ServiceName &lhs, const ServiceName &rhs) { + return !(lhs == rhs); + } + + /* For Mitm extensions. */ + struct MitmProcessInfo { + os::ProcessId process_id; + ncm::ProgramId program_id; + cfg::OverrideStatus override_status; + }; + static_assert(std::is_trivial<MitmProcessInfo>::value && sizeof(MitmProcessInfo) == 0x20, "MitmProcessInfo definition!"); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket.hpp new file mode 100644 index 00000000..3cc5213f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#if !defined(ATMOSPHERE_OS_WINDOWS) +#include <sys/socket.h> +#include <netdb.h> +#include <netinet/in.h> +#endif + +#include <stratosphere/socket/socket_types.hpp> +#include <stratosphere/socket/socket_options.hpp> +#include <stratosphere/socket/socket_errno.hpp> +#include <stratosphere/socket/socket_constants.hpp> +#include <stratosphere/socket/socket_config.hpp> +#include <stratosphere/socket/socket_system_config.hpp> +#include <stratosphere/socket/socket_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/impl/socket_platform_types_translation.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/impl/socket_platform_types_translation.hpp new file mode 100644 index 00000000..1586af23 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/impl/socket_platform_types_translation.hpp @@ -0,0 +1,99 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/socket/socket_types.hpp> +#include <stratosphere/socket/socket_options.hpp> +#include <stratosphere/socket/socket_constants.hpp> +#include <stratosphere/socket/socket_errno.hpp> + +namespace ams::socket::impl { + + #if defined(ATMOSPHERE_OS_WINDOWS) + class PosixWinSockConverter { + private: + struct SocketData { + SOCKET winsock; + bool exempt; + bool shutdown; + + constexpr SocketData() : winsock(static_cast<SOCKET>(INVALID_SOCKET)), exempt(), shutdown() { /* ... */ } + }; + private: + os::SdkMutex m_mutex{}; + SocketData m_data[MaxSocketsPerClient]{}; + private: + static constexpr int GetInitialIndex(SOCKET winsock) { + /* The lower 2 bits of a winsock are always zero; Nintendo uses the upper bits as a hashmap index into m_data. */ + return (winsock >> 2) % MaxSocketsPerClient; + } + public: + constexpr PosixWinSockConverter() = default; + + s32 AcquirePosixHandle(SOCKET winsock, bool exempt = false); + s32 GetShutdown(bool &shutdown, s32 posix); + s32 GetSocketExempt(bool &exempt, s32 posix); + SOCKET PosixToWinsockSocket(s32 posix); + void ReleaseAllPosixHandles(); + void ReleasePosixHandle(s32 posix); + s32 SetShutdown(s32 posix, bool shutdown); + s32 SetSocketExempt(s32 posix, bool exempt); + s32 WinsockToPosixSocket(SOCKET winsock); + }; + + s32 MapProtocolValue(Protocol protocol); + Protocol MapProtocolValue(s32 protocol); + + s32 MapTypeValue(Type type); + Type MapTypeValue(s32 type); + + s8 MapFamilyValue(Family family); + Family MapFamilyValue(s8 family); + + s32 MapMsgFlagValue(MsgFlag flag); + MsgFlag MapMsgFlagValue(s32 flag); + + u32 MapAddrInfoFlagValue(AddrInfoFlag flag); + AddrInfoFlag MapAddrInfoFlagValue(u32 flag); + + u32 MapShutdownMethodValue(ShutdownMethod how); + ShutdownMethod MapShutdownMethodValue(u32 how); + + u32 MapFcntlFlagValue(FcntlFlag flag); + FcntlFlag MapFcntlFlagValue(u32 flag); + + s32 MapLevelValue(Level level); + Level MapLevelValue(s32 level); + + s32 MapOptionValue(Level level, Option option); + Option MapOptionValue(s32 level, s32 option); + + s32 MapErrnoValue(Errno error); + Errno MapErrnoValue(s32 error); + + #endif + + #define AMS_SOCKET_IMPL_DECLARE_CONVERSION(AMS, PLATFORM) \ + void CopyToPlatform(PLATFORM *dst, const AMS *src); \ + void CopyFromPlatform(AMS *dst, const PLATFORM *src); + + AMS_SOCKET_IMPL_DECLARE_CONVERSION(SockAddrIn, sockaddr_in); + AMS_SOCKET_IMPL_DECLARE_CONVERSION(TimeVal, timeval); + AMS_SOCKET_IMPL_DECLARE_CONVERSION(Linger, linger); + + #undef AMS_SOCKET_IMPL_DECLARE_CONVERSION + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_api.hpp new file mode 100644 index 00000000..4523842e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/socket/socket_types.hpp> +#include <stratosphere/socket/socket_options.hpp> +#include <stratosphere/socket/socket_errno.hpp> +#include <stratosphere/socket/socket_config.hpp> + +namespace ams::socket { + + Errno GetLastError(); + void SetLastError(Errno err); + + u32 InetHtonl(u32 host); + u16 InetHtons(u16 host); + u32 InetNtohl(u32 net); + u16 InetNtohs(u16 net); + + Result Initialize(const Config &config); + Result Finalize(); + + Result InitializeAllocatorForInternal(void *buffer, size_t size); + + ssize_t RecvFrom(s32 desc, void *buffer, size_t buffer_size, MsgFlag flags, SockAddr *out_address, SockLenT *out_addr_len); + ssize_t Recv(s32 desc, void *buffer, size_t buffer_size, MsgFlag flags); + + ssize_t SendTo(s32 desc, const void *buffer, size_t buffer_size, MsgFlag flags, const SockAddr *address, SockLenT len); + ssize_t Send(s32 desc, const void *buffer, size_t buffer_size, MsgFlag flags); + + s32 Shutdown(s32 desc, ShutdownMethod how); + + s32 Socket(Family domain, Type type, Protocol protocol); + s32 SocketExempt(Family domain, Type type, Protocol protocol); + + s32 Accept(s32 desc, SockAddr *out_address, SockLenT *out_addr_len); + s32 Bind(s32 desc, const SockAddr *address, SockLenT len); + + s32 Connect(s32 desc, const SockAddr *address, SockLenT len); + + s32 GetSockName(s32 desc, SockAddr *out_address, SockLenT *out_addr_len); + s32 SetSockOpt(s32 desc, Level level, Option option_name, const void *option_value, SockLenT option_size); + + s32 Listen(s32 desc, s32 backlog); + + s32 Close(s32 desc); + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_config.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_config.hpp new file mode 100644 index 00000000..45224305 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_config.hpp @@ -0,0 +1,89 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/socket/socket_constants.hpp> + +namespace ams::socket { + + constexpr ALWAYS_INLINE size_t AlignMss(size_t size) { + return util::DivideUp(size, static_cast<size_t>(1500)) * static_cast<size_t>(1500); + } + + class Config { + private: + u32 m_version; + protected: + bool m_system; + bool m_smbp; + void *m_memory_pool; + size_t m_memory_pool_size; + size_t m_allocator_pool_size; + size_t m_tcp_initial_send_buffer_size; + size_t m_tcp_initial_receive_buffer_size; + size_t m_tcp_auto_send_buffer_size_max; + size_t m_tcp_auto_receive_buffer_size_max; + size_t m_udp_send_buffer_size; + size_t m_udp_receive_buffer_size; + int m_sb_efficiency; + int m_concurrency_count_max; + public: + constexpr Config(void *mp, size_t mp_sz, size_t ap, size_t is, size_t ir, size_t as, size_t ar, size_t us, size_t ur, int sbe, int c) + : m_version(LibraryVersion), + m_system(false), + m_smbp(false), + m_memory_pool(mp), + m_memory_pool_size(mp_sz), + m_allocator_pool_size(ap), + m_tcp_initial_send_buffer_size(is), + m_tcp_initial_receive_buffer_size(ir), + m_tcp_auto_send_buffer_size_max(as), + m_tcp_auto_receive_buffer_size_max(ar), + m_udp_send_buffer_size(us), + m_udp_receive_buffer_size(ur), + m_sb_efficiency(sbe), + m_concurrency_count_max(c) + { + /* ... */ + } + + constexpr u32 GetVersion() const { return m_version; } + constexpr bool IsSystemClient() const { return m_system; } + constexpr bool IsSmbpClient() const { return m_smbp; } + constexpr void *GetMemoryPool() const { return m_memory_pool; } + constexpr size_t GetMemoryPoolSize() const { return m_memory_pool_size; } + constexpr size_t GetAllocatorPoolSize() const { return m_allocator_pool_size; } + constexpr size_t GetTcpInitialSendBufferSize() const { return m_tcp_initial_send_buffer_size; } + constexpr size_t GetTcpInitialReceiveBufferSize() const { return m_tcp_initial_receive_buffer_size; } + constexpr size_t GetTcpAutoSendBufferSizeMax() const { return m_tcp_auto_send_buffer_size_max; } + constexpr size_t GetTcpAutoReceiveBufferSizeMax() const { return m_tcp_auto_receive_buffer_size_max; } + constexpr size_t GetUdpSendBufferSize() const { return m_udp_send_buffer_size; } + constexpr size_t GetUdpReceiveBufferSize() const { return m_udp_receive_buffer_size; } + constexpr int GetSocketBufferEfficiency() const { return m_sb_efficiency; } + constexpr int GetConcurrencyCountMax() const { return m_concurrency_count_max; } + + constexpr void SetTcpInitialSendBufferSize(size_t size) { m_tcp_initial_send_buffer_size = size; } + constexpr void SetTcpInitialReceiveBufferSize(size_t size) { m_tcp_initial_receive_buffer_size = size; } + constexpr void SetTcpAutoSendBufferSizeMax(size_t size) { m_tcp_auto_send_buffer_size_max = size; } + constexpr void SetTcpAutoReceiveBufferSizeMax(size_t size) { m_tcp_auto_receive_buffer_size_max = size; } + constexpr void SetUdpSendBufferSize(size_t size) { m_udp_send_buffer_size = size; } + constexpr void SetUdpReceiveBufferSize(size_t size) { m_udp_receive_buffer_size = size; } + constexpr void SetSocketBufferEfficiency(int sb) { AMS_ABORT_UNLESS(1 <= sb && sb <= 8); m_sb_efficiency = sb; } + constexpr void SetConcurrencyCountMax(int c) { m_concurrency_count_max = c; } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_constants.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_constants.hpp new file mode 100644 index 00000000..c742dede --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_constants.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::socket { + + constexpr inline s32 InvalidSocket = -1; + constexpr inline s32 SocketError = -1; + + constexpr inline u32 MaxSocketsPerClient = 0x80; + + constexpr inline auto DefaultTcpAutoBufferSizeMax = 192_KB; + constexpr inline auto MinTransferMemorySize = (2 * DefaultTcpAutoBufferSizeMax + 128_KB); + constexpr inline auto MinSocketAllocatorSize = 128_KB; + constexpr inline auto MinSocketMemoryPoolSize = MinSocketAllocatorSize + MinTransferMemorySize; + constexpr inline auto MinMemHeapAllocatorSize = 16_KB; + constexpr inline auto MinimumSharedMbufPoolReservation = 4_KB; + + constexpr inline size_t MemoryPoolAlignment = 4_KB; + + constexpr inline auto ConcurrencyLimitMax = 14; + + /* TODO: Does this need to be 1 for sockets to work on lower firmware versions? */ + /* Is this value actually used/checked by bsdsockets sysmodule? */ + constexpr inline auto LibraryVersion = 7; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_errno.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_errno.hpp new file mode 100644 index 00000000..5a1036e9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_errno.hpp @@ -0,0 +1,181 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::socket { + + enum class Errno : u32 { + ESuccess = 0, + EPerm = 1, + ENoEnt = 2, + ESrch = 3, + EIntr = 4, + EIo = 5, + ENxIo = 6, + E2Big = 7, + ENoExec = 8, + EBadf = 9, + EChild = 10, + EAgain = 11, + EWouldBlock = EAgain, + ENoMem = 12, + EAcces = 13, + EFault = 14, + ENotBlk = 15, + EBusy = 16, + EExist = 17, + EXDev = 18, + ENoDev = 19, + ENotDir = 20, + EIsDir = 21, + EInval = 22, + ENFile = 23, + EMFile = 24, + ENotTy = 25, + ETxtBsy = 26, + EFBig = 27, + ENoSpc = 28, + ESPipe = 29, + ERofs = 30, + EMLink = 31, + EPipe = 32, + EDom = 33, + ERange = 34, + EDeadLk = 35, + EDeadLock = EDeadLk, + ENameTooLong = 36, + ENoLck = 37, + ENoSys = 38, + ENotEmpty = 39, + ELoop = 40, + ENoMsg = 42, + EIdrm = 43, + EChrng = 44, + EL2NSync = 45, + EL3Hlt = 46, + EL3Rst = 47, + ELnrng = 48, + EUnatch = 49, + ENoCsi = 50, + EL2Hlt = 51, + EBade = 52, + EBadr = 53, + EXFull = 54, + ENoAno = 55, + EBadRqc = 56, + EBadSsl = 57, + EBFont = 59, + ENoStr = 60, + ENoData = 61, + ETime = 62, + ENoSr = 63, + ENoNet = 64, + ENoPkg = 65, + ERemote = 66, + ENoLink = 67, + EAdv = 68, + ESrmnt = 69, + EComm = 70, + EProto = 71, + EMultiHop = 72, + EDotDot = 73, + EBadMsg = 74, + EOverflow = 75, + ENotUnuq = 76, + EBadFd = 77, + ERemChg = 78, + ELibAcc = 79, + ELibBad = 80, + ELibScn = 81, + ELibMax = 82, + ELibExec = 83, + EIlSeq = 84, + ERestart = 85, + EStrPipe = 86, + EUsers = 87, + ENotSock = 88, + EDestAddrReq = 89, + EMsgSize = 90, + EPrototype = 91, + ENoProtoOpt = 92, + EProtoNoSupport = 93, + ESocktNoSupport = 94, + EOpNotSupp = 95, + ENotSup = EOpNotSupp, + EPfNoSupport = 96, + EAfNoSupport = 97, + EAddrInUse = 98, + EAddrNotAvail = 99, + ENetDown = 100, + ENetUnreach = 101, + ENetReset = 102, + EConnAborted = 103, + EConnReset = 104, + ENoBufs = 105, + EIsConn = 106, + ENotConn = 107, + EShutDown = 108, + ETooManyRefs = 109, + ETimedOut = 110, + EConnRefused = 111, + EHostDown = 112, + EHostUnreach = 113, + EAlready = 114, + EInProgress = 115, + EStale = 116, + EUClean = 117, + ENotNam = 118, + ENAvail = 119, + EIsNam = 120, + ERemoteIo = 121, + EDQuot = 122, + ENoMedium = 123, + EMediumType = 124, + ECanceled = 125, + ENoKey = 126, + EKeyExpired = 127, + EKeyRevoked = 128, + EKeyRejected = 129, + EOwnerDead = 130, + ENotRecoverable = 131, + ERfKill = 132, + EHwPoison = 133, + /* ... */ + EProcLim = 156, + }; + + enum class HErrno : s32 { + Netdb_Internal = -1, + Netdb_Success = 0, + Host_Not_Found = 1, + Try_Again = 2, + No_Recovery = 3, + No_Data = 4, + + No_Address = No_Data, + }; + + enum class AiErrno : u32 { + EAi_Success = 0, + /* ... */ + }; + + constexpr inline bool operator!(Errno e) { return e == Errno::ESuccess; } + constexpr inline bool operator!(HErrno e) { return e == HErrno::Netdb_Success; } + constexpr inline bool operator!(AiErrno e) { return e == AiErrno::EAi_Success; } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_options.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_options.hpp new file mode 100644 index 00000000..485fdbd6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_options.hpp @@ -0,0 +1,116 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::socket { + + enum class Level : s32 { + Sol_Ip = 0, + Sol_Icmp = 1, + Sol_Tcp = 6, + Sol_Udp = 17, + Sol_UdpLite = 136, + + Sol_Socket = 0xFFFF, + }; + + enum class Option : u32 { + /* ==================================== */ + So_Debug = (1 << 0), + So_AcceptConn = (1 << 1), + So_ReuseAddr = (1 << 2), + So_KeepAlive = (1 << 3), + So_DontRoute = (1 << 4), + So_Broadcast = (1 << 5), + So_UseLoopback = (1 << 6), + So_Linger = (1 << 7), + So_OobInline = (1 << 8), + So_ReusePort = (1 << 9), + + So_SndBuf = (1 << 12) | 0x01, + So_RcvBuf = (1 << 12) | 0x02, + So_SndLoWat = (1 << 12) | 0x03, + So_RcvLoWat = (1 << 12) | 0x04, + So_SndTimeo = (1 << 12) | 0x05, + So_RcvTimeo = (1 << 12) | 0x06, + So_Error = (1 << 12) | 0x07, + So_Type = (1 << 12) | 0x08, + So_Label = (1 << 12) | 0x09, + So_PeerLabel = (1 << 12) | 0x10, + So_ListenQLimit = (1 << 12) | 0x11, + So_ListenQLen = (1 << 12) | 0x12, + So_ListenIncQLen = (1 << 12) | 0x13, + So_SetFib = (1 << 12) | 0x14, + So_User_Cookie = (1 << 12) | 0x15, + So_Protocol = (1 << 12) | 0x16, + + So_Nn_Shutdown_Exempt = (1 << 16), + + So_Vendor = (1u << 31), + So_Nn_Linger = So_Vendor | 0x01, + /* ==================================== */ + + /* ==================================== */ + Ip_Options = 1, + Ip_HdrIncl = 2, + Ip_Tos = 3, + Ip_Ttl = 4, + Ip_RecvOpts = 5, + Ip_Multicast_If = 9, + Ip_Multicast_Ttl = 10, + Ip_Multicast_Loop = 11, + Ip_Add_Membership = 12, + Ip_Drop_Membership = 13, + Ip_Multicast_Vif = 14, + Ip_Rsvp_On = 15, + Ip_Rsvp_Off = 16, + Ip_Rsvp_Vif_On = 17, + Ip_Rsvp_Vif_Off = 18, + Ip_PortRange = 19, + Ip_Faith = 22, + Ip_OnesBcast = 23, + Ip_BindAny = 24, + + Ip_RecvTtl = 65, + Ip_MinTtl = 66, + Ip_DontFrag = 67, + Ip_RecvTos = 68, + + Ip_Add_Source_Membership = 70, + Ip_Drop_Source_Membership = 71, + Ip_Block_Source = 72, + Ip_Unblock_Source = 73, + /* ==================================== */ + + /* ==================================== */ + Tcp_NoDelay = (1 << 0), + Tcp_MaxSeg = (1 << 1), + Tcp_NoPush = (1 << 2), + Tcp_NoOpt = (1 << 3), + Tcp_Md5Sig = (1 << 4), + Tcp_Info = (1 << 5), + Tcp_Congestion = (1 << 6), + Tcp_KeepInit = (1 << 7), + Tcp_KeepIdle = (1 << 8), + Tcp_KeepIntvl = (1 << 9), + Tcp_KeepCnt = (1 << 10), + + Tcp_Vendor = So_Vendor, + /* ==================================== */ + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_system_config.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_system_config.hpp new file mode 100644 index 00000000..c6bac93e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_system_config.hpp @@ -0,0 +1,98 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/socket/socket_config.hpp> + +namespace ams::socket { + + class SystemConfigDefault : public Config { + public: + static constexpr size_t DefaultTcpInitialSendBufferSize = 32_KB; + static constexpr size_t DefaultTcpInitialReceiveBufferSize = 64_KB; + static constexpr size_t DefaultTcpAutoSendBufferSizeMax = 256_KB; + static constexpr size_t DefaultTcpAutoReceiveBufferSizeMax = 256_KB; + static constexpr size_t DefaultUdpSendBufferSize = 9_KB; + static constexpr size_t DefaultUdpReceiveBufferSize = 42240; + static constexpr auto DefaultSocketBufferEfficiency = 2; + static constexpr auto DefaultConcurrency = 8; + static constexpr size_t DefaultAllocatorPoolSize = 128_KB; + + static constexpr size_t PerTcpSocketWorstCaseMemoryPoolSize = [] { + constexpr size_t WorstCaseTcpSendBufferSize = AlignMss(std::max(DefaultTcpInitialSendBufferSize, DefaultTcpAutoSendBufferSizeMax)); + constexpr size_t WorstCaseTcpReceiveBufferSize = AlignMss(std::max(DefaultTcpInitialReceiveBufferSize, DefaultTcpAutoReceiveBufferSizeMax)); + + return util::AlignUp(WorstCaseTcpSendBufferSize * DefaultSocketBufferEfficiency + WorstCaseTcpReceiveBufferSize * DefaultSocketBufferEfficiency, os::MemoryPageSize); + }(); + + static constexpr size_t PerUdpSocketWorstCaseMemoryPoolSize = [] { + constexpr size_t WorstCaseUdpSendBufferSize = AlignMss(DefaultUdpSendBufferSize); + constexpr size_t WorstCaseUdpReceiveBufferSize = AlignMss(DefaultUdpReceiveBufferSize); + + return util::AlignUp(WorstCaseUdpSendBufferSize * DefaultSocketBufferEfficiency + WorstCaseUdpReceiveBufferSize * DefaultSocketBufferEfficiency, os::MemoryPageSize); + }(); + public: + constexpr SystemConfigDefault(void *mp, size_t mp_sz, size_t ap, int c=DefaultConcurrency) + : Config(mp, mp_sz, ap, + DefaultTcpInitialSendBufferSize, DefaultTcpInitialReceiveBufferSize, + DefaultTcpAutoSendBufferSizeMax, DefaultTcpAutoReceiveBufferSizeMax, + DefaultUdpSendBufferSize, DefaultUdpReceiveBufferSize, + DefaultSocketBufferEfficiency, c) + { + /* Mark as system. */ + m_system = true; + } + }; + + class SystemConfigLightDefault : public Config { + public: + static constexpr size_t DefaultTcpInitialSendBufferSize = 16_KB; + static constexpr size_t DefaultTcpInitialReceiveBufferSize = 16_KB; + static constexpr size_t DefaultTcpAutoSendBufferSizeMax = 0_KB; + static constexpr size_t DefaultTcpAutoReceiveBufferSizeMax = 0_KB; + static constexpr size_t DefaultUdpSendBufferSize = 9_KB; + static constexpr size_t DefaultUdpReceiveBufferSize = 42240; + static constexpr auto DefaultSocketBufferEfficiency = 2; + static constexpr auto DefaultConcurrency = 2; + static constexpr size_t DefaultAllocatorPoolSize = 64_KB; + + static constexpr size_t PerTcpSocketWorstCaseMemoryPoolSize = [] { + constexpr size_t WorstCaseTcpSendBufferSize = AlignMss(std::max(DefaultTcpInitialSendBufferSize, DefaultTcpAutoSendBufferSizeMax)); + constexpr size_t WorstCaseTcpReceiveBufferSize = AlignMss(std::max(DefaultTcpInitialReceiveBufferSize, DefaultTcpAutoReceiveBufferSizeMax)); + + return util::AlignUp(WorstCaseTcpSendBufferSize * DefaultSocketBufferEfficiency + WorstCaseTcpReceiveBufferSize * DefaultSocketBufferEfficiency, os::MemoryPageSize); + }(); + + static constexpr size_t PerUdpSocketWorstCaseMemoryPoolSize = [] { + constexpr size_t WorstCaseUdpSendBufferSize = AlignMss(DefaultUdpSendBufferSize); + constexpr size_t WorstCaseUdpReceiveBufferSize = AlignMss(DefaultUdpReceiveBufferSize); + + return util::AlignUp(WorstCaseUdpSendBufferSize * DefaultSocketBufferEfficiency + WorstCaseUdpReceiveBufferSize * DefaultSocketBufferEfficiency, os::MemoryPageSize); + }(); + public: + constexpr SystemConfigLightDefault(void *mp, size_t mp_sz, size_t ap, int c=DefaultConcurrency) + : Config(mp, mp_sz, ap, + DefaultTcpInitialSendBufferSize, DefaultTcpInitialReceiveBufferSize, + DefaultTcpAutoSendBufferSizeMax, DefaultTcpAutoReceiveBufferSizeMax, + DefaultUdpSendBufferSize, DefaultUdpReceiveBufferSize, + DefaultSocketBufferEfficiency, c) + { + /* Mark as system. */ + m_system = true; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_types.hpp new file mode 100644 index 00000000..8547cf82 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/socket/socket_types.hpp @@ -0,0 +1,188 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::socket { + + using InAddrT = u32; + using InPortT = u16; + using SockLenT = u32; + using NfdsT = u64; + using FdMask = u64; + + constexpr inline unsigned int FdSetSize = 0x400; + + template<u32 A, u32 B, u32 C, u32 D> + constexpr inline InAddrT EncodeInAddr = util::ConvertToBigEndian(InAddrT{(A << 24) | (B << 16) | (C << 8) | (D << 0)}); + + constexpr inline InAddrT InAddr_Any = EncodeInAddr< 0, 0, 0, 0>; + constexpr inline InAddrT InAddr_Broadcast = EncodeInAddr<255, 255, 255, 255>; + constexpr inline InAddrT InAddr_None = EncodeInAddr<255, 255, 255, 255>; + constexpr inline InAddrT InAddr_Loopback = EncodeInAddr<127, 0, 0, 1>; + + enum class Protocol : s32 { + IpProto_Ip = 0, + IpProto_Icmp = 1, + + IpProto_Tcp = 6, + + IpProto_Udp = 17, + + IpProto_None = 59, + + IpProto_UdpLite = 136, + + IpProto_Raw = 255, + + IpProto_Max = 256, + }; + + enum class Type : u32 { + Sock_Default = 0, + Sock_Stream = 1, + Sock_Dgram = 2, + Sock_Raw = 3, + Sock_SeqPacket = 5, + + Sock_NonBlock = 0x20000000, + }; + + enum class Family : u8 { + Af_Unspec = 0, + Pf_Unspec = Af_Unspec, + + Af_Inet = 2, + Pf_Inet = Af_Inet, + + Af_Route = 17, + Pf_Route = Af_Route, + + Af_Link = 18, + Pf_Link = Af_Link, + + Af_Inet6 = 28, + Pf_Inet6 = Af_Inet6, + + Af_Max = 42, + Pf_Max = Af_Max + }; + + enum class MsgFlag : s32 { + Msg_None = (0 << 0), + + Msg_Oob = (1 << 0), + Msg_Peek = (1 << 1), + Msg_DontRoute = (1 << 2), + /* ... */ + Msg_Trunc = (1 << 4), + Msg_CTrunc = (1 << 5), + Msg_WaitAll = (1 << 6), + Msg_DontWait = (1 << 7), + /* ... */ + }; + + enum class FcntlCommand : u32 { + F_GetFl = 3, + F_SetFl = 4, + }; + + enum class FcntlFlag : u32 { + None = (0 << 0), + O_NonBlock = (1 << 11), + }; + + enum class ShutdownMethod : u32 { + Shut_Rd = 0, + Shut_Wr = 1, + Shut_RdWr = 2, + }; + + struct HostEnt { + char *h_name; + char **h_aliases; + Family h_addrtype; + int h_length; + char **h_addr_list; + }; + + struct InAddr { + InAddrT s_addr; + }; + + enum class AddrInfoFlag : u32 { + Ai_None = (0 << 0), + Ai_Passive = (1 << 0), + Ai_CanonName = (1 << 1), + Ai_NumericHost = (1 << 2), + Ai_NumericServ = (1 << 3), + + Ai_AddrConfig = (1 << 10), + }; + + struct SockAddr { + u8 sa_len; + Family sa_family; + char sa_data[14]; + }; + + struct SockAddrIn { + u8 sin_len; + Family sin_family; + InPortT sin_port; + InAddr sin_addr; + u8 sin_zero[8]; + }; + static_assert(sizeof(SockAddr) == sizeof(SockAddrIn)); + + struct AddrInfo { + AddrInfoFlag ai_flags; + Family ai_family; + Type ai_socktype; + Protocol ai_protocol; + SockLenT ai_addrlen; + SockAddr *ai_addr; + char *ai_canonname; + AddrInfo *ai_next; + }; + + struct TimeVal { + long tv_sec; + long tv_usec; + }; + + struct Linger { + int l_onoff; + int l_linger; + }; + + #define AMS_SOCKET_IMPL_DEFINE_ENUM_OPERATORS(__ENUM__) \ + constexpr inline __ENUM__ operator | (__ENUM__ lhs, __ENUM__ rhs) { return static_cast<__ENUM__>(static_cast<std::underlying_type_t<__ENUM__>>(lhs) | static_cast<std::underlying_type_t<__ENUM__>>(rhs)); } \ + constexpr inline __ENUM__ operator |=(__ENUM__ &lhs, __ENUM__ rhs) { return lhs = lhs | rhs; } \ + constexpr inline __ENUM__ operator & (__ENUM__ lhs, __ENUM__ rhs) { return static_cast<__ENUM__>(static_cast<std::underlying_type_t<__ENUM__>>(lhs) & static_cast<std::underlying_type_t<__ENUM__>>(rhs)); } \ + constexpr inline __ENUM__ operator &=(__ENUM__ &lhs, __ENUM__ rhs) { return lhs = lhs & rhs; } \ + constexpr inline __ENUM__ operator ^ (__ENUM__ lhs, __ENUM__ rhs) { return static_cast<__ENUM__>(static_cast<std::underlying_type_t<__ENUM__>>(lhs) ^ static_cast<std::underlying_type_t<__ENUM__>>(rhs)); } \ + constexpr inline __ENUM__ operator ^=(__ENUM__ &lhs, __ENUM__ rhs) { return lhs = lhs ^ rhs; } \ + constexpr inline __ENUM__ operator ~ (__ENUM__ e) { return static_cast<__ENUM__>(~static_cast<std::underlying_type_t<__ENUM__>>(e)); } + + AMS_SOCKET_IMPL_DEFINE_ENUM_OPERATORS(Type) + AMS_SOCKET_IMPL_DEFINE_ENUM_OPERATORS(AddrInfoFlag) + AMS_SOCKET_IMPL_DEFINE_ENUM_OPERATORS(MsgFlag) + AMS_SOCKET_IMPL_DEFINE_ENUM_OPERATORS(FcntlFlag) + + #undef AMS_SOCKET_IMPL_DEFINE_ENUM_OPERATORS + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl.hpp new file mode 100644 index 00000000..0afcba70 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/spl/spl_types.hpp> +#include <stratosphere/spl/spl_api.hpp> +#include <stratosphere/spl/smc/spl_secure_monitor_api.hpp> +#include <stratosphere/spl/impl/spl_api_impl.hpp> +#include <stratosphere/spl/impl/spl_random_interface.hpp> +#include <stratosphere/spl/impl/spl_deprecated_general_interface.hpp> +#include <stratosphere/spl/impl/spl_general_interface.hpp> +#include <stratosphere/spl/impl/spl_crypto_interface.hpp> +#include <stratosphere/spl/impl/spl_device_unique_data_interface.hpp> +#include <stratosphere/spl/impl/spl_ssl_interface.hpp> +#include <stratosphere/spl/impl/spl_es_interface.hpp> +#include <stratosphere/spl/impl/spl_manu_interface.hpp> +#include <stratosphere/spl/impl/spl_fs_interface.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_api_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_api_impl.hpp new file mode 100644 index 00000000..1a79d67b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_api_impl.hpp @@ -0,0 +1,85 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/spl/spl_types.hpp> + +namespace ams::spl::impl { + + constexpr inline s32 AesKeySlotMin = 16; + constexpr inline s32 AesKeySlotCount = 9; + constexpr inline s32 AesKeySlotMax = AesKeySlotMin + AesKeySlotCount - 1; + + /* Initialization. */ + void Initialize(); + + /* General. */ + Result GetConfig(u64 *out, spl::ConfigItem key); + Result ModularExponentiate(void *out, size_t out_size, const void *base, size_t base_size, const void *exp, size_t exp_size, const void *mod, size_t mod_size); + Result SetConfig(spl::ConfigItem key, u64 value); + Result GenerateRandomBytes(void *out, size_t size); + Result IsDevelopment(bool *out); + Result SetBootReason(BootReasonValue boot_reason); + Result GetBootReason(BootReasonValue *out); + + ALWAYS_INLINE bool GetConfigBool(spl::ConfigItem key) { + u64 v; + R_ABORT_UNLESS(::ams::spl::impl::GetConfig(std::addressof(v), key)); + return v != 0; + } + + /* Crypto. */ + Result GenerateAesKek(AccessKey *out_access_key, const KeySource &key_source, u32 generation, u32 option); + Result LoadAesKey(s32 keyslot, const AccessKey &access_key, const KeySource &key_source); + Result GenerateAesKey(AesKey *out_key, const AccessKey &access_key, const KeySource &key_source); + Result DecryptAesKey(AesKey *out_key, const KeySource &key_source, u32 generation, u32 option); + Result ComputeCtr(void *dst, size_t dst_size, s32 keyslot, const void *src, size_t src_size, const IvCtr &iv_ctr); + Result ComputeCmac(Cmac *out_cmac, s32 keyslot, const void *data, size_t size); + + Result AllocateAesKeySlot(s32 *out_keyslot); + Result DeallocateAesKeySlot(s32 keyslot); + Result TestAesKeySlot(s32 *out_index, bool *out_virtual, s32 keyslot); + + os::SystemEvent *GetAesKeySlotAvailableEvent(); + + /* RSA. */ + Result DecryptDeviceUniqueData(void *dst, size_t dst_size, const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source, u32 option); + + /* SSL */ + Result DecryptAndStoreSslClientCertKey(const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source); + Result ModularExponentiateWithSslClientCertKey(void *out, size_t out_size, const void *base, size_t base_size, const void *mod, size_t mod_size); + + /* ES */ + Result LoadEsDeviceKey(const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source, u32 option); + Result PrepareEsTitleKey(AccessKey *out_access_key, const void *base, size_t base_size, const void *mod, size_t mod_size, const void *label_digest, size_t label_digest_size, u32 generation); + Result PrepareCommonEsTitleKey(AccessKey *out_access_key, const KeySource &key_source, u32 generation); + Result DecryptAndStoreDrmDeviceCertKey(const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source); + Result ModularExponentiateWithDrmDeviceCertKey(void *out, size_t out_size, const void *base, size_t base_size, const void *mod, size_t mod_size); + Result PrepareEsArchiveKey(AccessKey *out_access_key, const void *base, size_t base_size, const void *mod, size_t mod_size, const void *label_digest, size_t label_digest_size, u32 generation); + Result LoadPreparedAesKey(s32 keyslot, const AccessKey &access_key); + Result PrepareEsUnknown2Key(AccessKey *out_access_key, const void *base, size_t base_size, const void *mod, size_t mod_size, const void *label_digest, size_t label_digest_size, u32 generation); + + /* FS */ + Result DecryptAndStoreGcKey(const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source, u32 option); + Result DecryptGcMessage(u32 *out_size, void *dst, size_t dst_size, const void *base, size_t base_size, const void *mod, size_t mod_size, const void *label_digest, size_t label_digest_size); + Result GenerateSpecificAesKey(AesKey *out_key, const KeySource &key_source, u32 generation, u32 which); + Result LoadPreparedAesKey(s32 keyslot, const AccessKey &access_key); + Result GetPackage2Hash(void *dst, const size_t size); + + /* Manu. */ + Result ReencryptDeviceUniqueData(void *dst, size_t dst_size, const void *src, size_t src_size, const AccessKey &access_key_dec, const KeySource &source_dec, const AccessKey &access_key_enc, const KeySource &source_enc, u32 option); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_crypto_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_crypto_interface.hpp new file mode 100644 index 00000000..cb16205e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_crypto_interface.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/spl/spl_types.hpp> +#include <stratosphere/spl/impl/spl_general_interface.hpp> + +#define AMS_SPL_I_CRYPTO_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, GenerateAesKek, (sf::Out<spl::AccessKey> out_access_key, spl::KeySource key_source, u32 generation, u32 option), (out_access_key, key_source, generation, option)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, LoadAesKey, (s32 keyslot, spl::AccessKey access_key, spl::KeySource key_source), (keyslot, access_key, key_source)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, GenerateAesKey, (sf::Out<spl::AesKey> out_key, spl::AccessKey access_key, spl::KeySource key_source), (out_key, access_key, key_source)) \ + AMS_SF_METHOD_INFO(C, H, 14, Result, DecryptAesKey, (sf::Out<spl::AesKey> out_key, spl::KeySource key_source, u32 generation, u32 option), (out_key, key_source, generation, option)) \ + AMS_SF_METHOD_INFO(C, H, 15, Result, ComputeCtr, (const sf::OutNonSecureBuffer &out_buf, s32 keyslot, const sf::InNonSecureBuffer &in_buf, spl::IvCtr iv_ctr), (out_buf, keyslot, in_buf, iv_ctr)) \ + AMS_SF_METHOD_INFO(C, H, 16, Result, ComputeCmac, (sf::Out<spl::Cmac> out_cmac, s32 keyslot, const sf::InPointerBuffer &in_buf), (out_cmac, keyslot, in_buf)) \ + AMS_SF_METHOD_INFO(C, H, 21, Result, AllocateAesKeySlot, (sf::Out<s32> out_keyslot), (out_keyslot)) \ + AMS_SF_METHOD_INFO(C, H, 22, Result, DeallocateAesKeySlot, (s32 keyslot), (keyslot)) \ + AMS_SF_METHOD_INFO(C, H, 23, Result, GetAesKeySlotAvailableEvent, (sf::OutCopyHandle out_hnd), (out_hnd)) + +AMS_SF_DEFINE_INTERFACE_WITH_BASE(ams::spl::impl, ICryptoInterface, ::ams::spl::impl::IGeneralInterface, AMS_SPL_I_CRYPTO_INTERFACE_INTERFACE_INFO, 0xEF3598D9) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_deprecated_general_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_deprecated_general_interface.hpp new file mode 100644 index 00000000..7e3e210a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_deprecated_general_interface.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/spl/spl_types.hpp> + +#define AMS_SPL_I_DEPRECATED_GENERAL_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GetConfig, (sf::Out<u64> out, u32 which), (out, which)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, ModularExponentiate, (const sf::OutPointerBuffer &out, const sf::InPointerBuffer &base, const sf::InPointerBuffer &exp, const sf::InPointerBuffer &mod), (out, base, exp, mod)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, GenerateAesKek, (sf::Out<spl::AccessKey> out_access_key, spl::KeySource key_source, u32 generation, u32 option), (out_access_key, key_source, generation, option)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, LoadAesKey, (s32 keyslot, spl::AccessKey access_key, spl::KeySource key_source), (keyslot, access_key, key_source)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, GenerateAesKey, (sf::Out<spl::AesKey> out_key, spl::AccessKey access_key, spl::KeySource key_source), (out_key, access_key, key_source)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, SetConfig, (u32 which, u64 value), (which, value)) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, GenerateRandomBytes, (const sf::OutPointerBuffer &out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, DecryptAndStoreGcKey, (const sf::InPointerBuffer &src, spl::AccessKey access_key, spl::KeySource key_source, u32 option), (src, access_key, key_source, option)) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, DecryptGcMessage, (sf::Out<u32> out_size, const sf::OutPointerBuffer &out, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod, const sf::InPointerBuffer &label_digest), (out_size, out, base, mod, label_digest)) \ + AMS_SF_METHOD_INFO(C, H, 11, Result, IsDevelopment, (sf::Out<bool> is_dev), (is_dev)) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, GenerateSpecificAesKey, (sf::Out<spl::AesKey> out_key, spl::KeySource key_source, u32 generation, u32 which), (out_key, key_source, generation, which)) \ + AMS_SF_METHOD_INFO(C, H, 13, Result, DecryptDeviceUniqueData, (const sf::OutPointerBuffer &dst, const sf::InPointerBuffer &src, spl::AccessKey access_key, spl::KeySource key_source, u32 option), (dst, src, access_key, key_source, option)) \ + AMS_SF_METHOD_INFO(C, H, 14, Result, DecryptAesKey, (sf::Out<spl::AesKey> out_key, spl::KeySource key_source, u32 generation, u32 option), (out_key, key_source, generation, option)) \ + AMS_SF_METHOD_INFO(C, H, 15, Result, ComputeCtrDeprecated, (const sf::OutBuffer &out_buf, s32 keyslot, const sf::InBuffer &in_buf, spl::IvCtr iv_ctr), (out_buf, keyslot, in_buf, iv_ctr), hos::Version_1_0_0, hos::Version_1_0_0) \ + AMS_SF_METHOD_INFO(C, H, 15, Result, ComputeCtr, (const sf::OutNonSecureBuffer &out_buf, s32 keyslot, const sf::InNonSecureBuffer &in_buf, spl::IvCtr iv_ctr), (out_buf, keyslot, in_buf, iv_ctr), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 16, Result, ComputeCmac, (sf::Out<spl::Cmac> out_cmac, s32 keyslot, const sf::InPointerBuffer &in_buf), (out_cmac, keyslot, in_buf)) \ + AMS_SF_METHOD_INFO(C, H, 17, Result, LoadEsDeviceKey, (const sf::InPointerBuffer &src, spl::AccessKey access_key, spl::KeySource key_source, u32 option), (src, access_key, key_source, option)) \ + AMS_SF_METHOD_INFO(C, H, 18, Result, PrepareEsTitleKeyDeprecated, (sf::Out<spl::AccessKey> out_access_key, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod, const sf::InPointerBuffer &label_digest), (out_access_key, base, mod, label_digest), hos::Version_1_0_0, hos::Version_2_3_0) \ + AMS_SF_METHOD_INFO(C, H, 18, Result, PrepareEsTitleKey, (sf::Out<spl::AccessKey> out_access_key, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod, const sf::InPointerBuffer &label_digest, u32 generation), (out_access_key, base, mod, label_digest, generation), hos::Version_3_0_0) \ + AMS_SF_METHOD_INFO(C, H, 19, Result, LoadPreparedAesKey, (s32 keyslot, spl::AccessKey access_key), (keyslot, access_key)) \ + AMS_SF_METHOD_INFO(C, H, 20, Result, PrepareCommonEsTitleKeyDeprecated, (sf::Out<spl::AccessKey> out_access_key, spl::KeySource key_source), (out_access_key, key_source), hos::Version_2_0_0, hos::Version_2_3_0) \ + AMS_SF_METHOD_INFO(C, H, 20, Result, PrepareCommonEsTitleKey, (sf::Out<spl::AccessKey> out_access_key, spl::KeySource key_source, u32 generation), (out_access_key, key_source, generation), hos::Version_3_0_0) \ + AMS_SF_METHOD_INFO(C, H, 21, Result, AllocateAesKeySlot, (sf::Out<s32> out_keyslot), (out_keyslot)) \ + AMS_SF_METHOD_INFO(C, H, 22, Result, DeallocateAesKeySlot, (s32 keyslot), (keyslot)) \ + AMS_SF_METHOD_INFO(C, H, 23, Result, GetAesKeySlotAvailableEvent, (sf::OutCopyHandle out_hnd), (out_hnd)) \ + AMS_SF_METHOD_INFO(C, H, 24, Result, SetBootReason, (spl::BootReasonValue boot_reason), (boot_reason), hos::Version_3_0_0) \ + AMS_SF_METHOD_INFO(C, H, 25, Result, GetBootReason, (sf::Out<spl::BootReasonValue> out), (out), hos::Version_3_0_0) + +AMS_SF_DEFINE_INTERFACE(ams::spl::impl, IDeprecatedGeneralInterface, AMS_SPL_I_DEPRECATED_GENERAL_INTERFACE_INTERFACE_INFO, 0x127DDBD0) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_device_unique_data_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_device_unique_data_interface.hpp new file mode 100644 index 00000000..268f2b19 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_device_unique_data_interface.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/spl/spl_types.hpp> +#include <stratosphere/spl/impl/spl_crypto_interface.hpp> + +#define AMS_SPL_I_DEVICE_UNIQUE_DATA_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 13, Result, DecryptDeviceUniqueDataDeprecated, (const sf::OutPointerBuffer &dst, const sf::InPointerBuffer &src, spl::AccessKey access_key, spl::KeySource key_source, u32 option), (dst, src, access_key, key_source, option), hos::Version_Min, hos::Version_4_1_0) \ + AMS_SF_METHOD_INFO(C, H, 13, Result, DecryptDeviceUniqueData, (const sf::OutPointerBuffer &dst, const sf::InPointerBuffer &src, spl::AccessKey access_key, spl::KeySource key_source), (dst, src, access_key, key_source), hos::Version_5_0_0) + +AMS_SF_DEFINE_INTERFACE_WITH_BASE(ams::spl::impl, IDeviceUniqueDataInterface, ::ams::spl::impl::ICryptoInterface, AMS_SPL_I_DEVICE_UNIQUE_DATA_INTERFACE_INTERFACE_INFO, 0xADAD1D0A) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_es_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_es_interface.hpp new file mode 100644 index 00000000..237c3124 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_es_interface.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/spl/spl_types.hpp> +#include <stratosphere/spl/impl/spl_device_unique_data_interface.hpp> + +#define AMS_SPL_I_ES_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 17, Result, LoadEsDeviceKeyDeprecated, (const sf::InPointerBuffer &src, spl::AccessKey access_key, spl::KeySource key_source, u32 option), (src, access_key, key_source, option), hos::Version_Min, hos::Version_4_1_0) \ + AMS_SF_METHOD_INFO(C, H, 17, Result, LoadEsDeviceKey, (const sf::InPointerBuffer &src, spl::AccessKey access_key, spl::KeySource key_source), (src, access_key, key_source), hos::Version_5_0_0) \ + AMS_SF_METHOD_INFO(C, H, 18, Result, PrepareEsTitleKey, (sf::Out<spl::AccessKey> out_access_key, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod, const sf::InPointerBuffer &label_digest, u32 generation), (out_access_key, base, mod, label_digest, generation)) \ + AMS_SF_METHOD_INFO(C, H, 20, Result, PrepareCommonEsTitleKey, (sf::Out<spl::AccessKey> out_access_key, spl::KeySource key_source, u32 generation), (out_access_key, key_source, generation), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 28, Result, DecryptAndStoreDrmDeviceCertKey, (const sf::InPointerBuffer &src, spl::AccessKey access_key, spl::KeySource key_source), (src, access_key, key_source), hos::Version_5_0_0) \ + AMS_SF_METHOD_INFO(C, H, 29, Result, ModularExponentiateWithDrmDeviceCertKey, (const sf::OutPointerBuffer &out, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod), (out, base, mod), hos::Version_5_0_0) \ + AMS_SF_METHOD_INFO(C, H, 31, Result, PrepareEsArchiveKey, (sf::Out<spl::AccessKey> out_access_key, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod, const sf::InPointerBuffer &label_digest, u32 generation), (out_access_key, base, mod, label_digest, generation), hos::Version_6_0_0) \ + AMS_SF_METHOD_INFO(C, H, 32, Result, LoadPreparedAesKey, (s32 keyslot, spl::AccessKey access_key), (keyslot, access_key), hos::Version_6_0_0) \ + AMS_SF_METHOD_INFO(C, H, 33, Result, PrepareEsUnknown2Key, (sf::Out<spl::AccessKey> out_access_key, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod, const sf::InPointerBuffer &label_digest, u32 generation), (out_access_key, base, mod, label_digest, generation), hos::Version_18_0_0) + +AMS_SF_DEFINE_INTERFACE_WITH_BASE(ams::spl::impl, IEsInterface, ::ams::spl::impl::IDeviceUniqueDataInterface, AMS_SPL_I_ES_INTERFACE_INTERFACE_INFO, 0x346D5001) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_fs_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_fs_interface.hpp new file mode 100644 index 00000000..c8e9712c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_fs_interface.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/spl/spl_types.hpp> +#include <stratosphere/spl/impl/spl_crypto_interface.hpp> + +#define AMS_SPL_I_FS_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, DecryptAndStoreGcKeyDeprecated, (const sf::InPointerBuffer &src, spl::AccessKey access_key, spl::KeySource key_source, u32 option), (src, access_key, key_source, option), hos::Version_Min, hos::Version_4_1_0) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, DecryptAndStoreGcKey, (const sf::InPointerBuffer &src, spl::AccessKey access_key, spl::KeySource key_source), (src, access_key, key_source), hos::Version_5_0_0) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, DecryptGcMessage, (sf::Out<u32> out_size, const sf::OutPointerBuffer &out, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod, const sf::InPointerBuffer &label_digest), (out_size, out, base, mod, label_digest)) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, GenerateSpecificAesKey, (sf::Out<spl::AesKey> out_key, spl::KeySource key_source, u32 generation, u32 which), (out_key, key_source, generation, which)) \ + AMS_SF_METHOD_INFO(C, H, 19, Result, LoadPreparedAesKey, (s32 keyslot, spl::AccessKey access_key), (keyslot, access_key)) \ + AMS_SF_METHOD_INFO(C, H, 31, Result, GetPackage2Hash, (const sf::OutPointerBuffer &dst), (dst), hos::Version_5_0_0) + +AMS_SF_DEFINE_INTERFACE_WITH_BASE(ams::spl::impl, IFsInterface, ::ams::spl::impl::ICryptoInterface, AMS_SPL_I_FS_INTERFACE_INTERFACE_INFO, 0x682B3803) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_general_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_general_interface.hpp new file mode 100644 index 00000000..c25102e6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_general_interface.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/spl/spl_types.hpp> + +#define AMS_SPL_I_GENERAL_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GetConfig, (sf::Out<u64> out, u32 which), (out, which)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, ModularExponentiate, (const sf::OutPointerBuffer &out, const sf::InPointerBuffer &base, const sf::InPointerBuffer &exp, const sf::InPointerBuffer &mod), (out, base, exp, mod)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, SetConfig, (u32 which, u64 value), (which, value)) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, GenerateRandomBytes, (const sf::OutPointerBuffer &out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 11, Result, IsDevelopment, (sf::Out<bool> is_dev), (is_dev)) \ + AMS_SF_METHOD_INFO(C, H, 24, Result, SetBootReason, (spl::BootReasonValue boot_reason), (boot_reason), hos::Version_3_0_0) \ + AMS_SF_METHOD_INFO(C, H, 25, Result, GetBootReason, (sf::Out<spl::BootReasonValue> out), (out), hos::Version_3_0_0) + +AMS_SF_DEFINE_INTERFACE(ams::spl::impl, IGeneralInterface, AMS_SPL_I_GENERAL_INTERFACE_INTERFACE_INFO, 0x127DDBD0) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_manu_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_manu_interface.hpp new file mode 100644 index 00000000..be7366b2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_manu_interface.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/spl/spl_types.hpp> +#include <stratosphere/spl/impl/spl_device_unique_data_interface.hpp> + +#define AMS_SPL_I_MANU_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 30, Result, ReencryptDeviceUniqueData, (const sf::OutPointerBuffer &out, const sf::InPointerBuffer &src, spl::AccessKey access_key_dec, spl::KeySource source_dec, spl::AccessKey access_key_enc, spl::KeySource source_enc, u32 option), (out, src, access_key_dec, source_dec, access_key_enc, source_enc, option)) + +AMS_SF_DEFINE_INTERFACE_WITH_BASE(ams::spl::impl, IManuInterface, ::ams::spl::impl::IDeviceUniqueDataInterface, AMS_SPL_I_MANU_INTERFACE_INTERFACE_INFO, 0xF5643734) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_random_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_random_interface.hpp new file mode 100644 index 00000000..811c6762 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_random_interface.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/spl/spl_types.hpp> + +#define AMS_SPL_I_RANDOM_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GenerateRandomBytes, (const sf::OutBuffer &out), (out)) + +AMS_SF_DEFINE_INTERFACE(ams::spl::impl, IRandomInterface, AMS_SPL_I_RANDOM_INTERFACE_INTERFACE_INFO, 0xBDE33ED4) \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_ssl_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_ssl_interface.hpp new file mode 100644 index 00000000..3925a723 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/impl/spl_ssl_interface.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/spl/spl_types.hpp> +#include <stratosphere/spl/impl/spl_device_unique_data_interface.hpp> + +#define AMS_SPL_I_SSL_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 26, Result, DecryptAndStoreSslClientCertKey, (const sf::InPointerBuffer &src, spl::AccessKey access_key, spl::KeySource key_source), (src, access_key, key_source), hos::Version_5_0_0) \ + AMS_SF_METHOD_INFO(C, H, 27, Result, ModularExponentiateWithSslClientCertKey, (const sf::OutPointerBuffer &out, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod), (out, base, mod), hos::Version_5_0_0) + +AMS_SF_DEFINE_INTERFACE_WITH_BASE(ams::spl::impl, ISslInterface, ::ams::spl::impl::IDeviceUniqueDataInterface, AMS_SPL_I_SSL_INTERFACE_INTERFACE_INFO, 0x0E1D71B7) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/smc/spl_secure_monitor_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/smc/spl_secure_monitor_api.hpp new file mode 100644 index 00000000..6ba24ca0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/smc/spl_secure_monitor_api.hpp @@ -0,0 +1,75 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/spl/spl_types.hpp> + +namespace ams::spl::smc { + + /* Helpers for converting arguments. */ + constexpr ALWAYS_INLINE u32 GetComputeAesMode(CipherMode mode, u32 keyslot) { + return static_cast<u32>((static_cast<u32>(mode) << 4) | (keyslot & 7)); + } + + constexpr ALWAYS_INLINE u32 GetPrepareEsDeviceUniqueKeyOption(EsDeviceUniqueKeyType type, u32 generation) { + return static_cast<u32>((static_cast<u32>(type) << 6) | (generation & 0x3F)); + } + + /* Functions. */ + Result SetConfig(AsyncOperationKey *out_op, spl::ConfigItem key, const u64 *value, size_t num_qwords, const void *sign); + Result GetConfig(u64 *out, size_t num_qwords, spl::ConfigItem key); + Result GetResult(Result *out, AsyncOperationKey op); + Result GetResultData(Result *out, void *out_buf, size_t out_buf_size, AsyncOperationKey op); + Result ModularExponentiate(AsyncOperationKey *out_op, const void *base, const void *exp, size_t exp_size, const void *mod); + Result GenerateRandomBytes(void *out, size_t size); + Result GenerateAesKek(AccessKey *out, const KeySource &source, u32 generation, u32 option); + Result LoadAesKey(u32 keyslot, const AccessKey &access_key, const KeySource &source); + Result ComputeAes(AsyncOperationKey *out_op, u64 dst_addr, u32 mode, const IvCtr &iv_ctr, u64 src_addr, size_t size); + Result GenerateSpecificAesKey(AesKey *out_key, const KeySource &source, u32 generation, u32 which); + Result ComputeCmac(Cmac *out_mac, u32 keyslot, const void *data, size_t size); + Result ReencryptDeviceUniqueData(void *data, size_t size, const AccessKey &access_key_dec, const KeySource &source_dec, const AccessKey &access_key_enc, const KeySource &source_enc, u32 option); + Result DecryptDeviceUniqueData(void *data, size_t size, const AccessKey &access_key, const KeySource &source, DeviceUniqueDataMode mode); + Result ModularExponentiateWithStorageKey(AsyncOperationKey *out_op, const void *base, const void *mod, ModularExponentiateWithStorageKeyMode mode); + Result PrepareEsDeviceUniqueKey(AsyncOperationKey *out_op, const void *base, const void *mod, const void *label_digest, size_t label_digest_size, u32 option); + Result LoadPreparedAesKey(u32 keyslot, const AccessKey &access_key); + Result PrepareCommonEsTitleKey(AccessKey *out, const KeySource &source, u32 generation); + + /* Deprecated functions. */ + Result LoadEsDeviceKey(const void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option); + Result DecryptDeviceUniqueData(size_t *out_size, void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option); + Result DecryptAndStoreGcKey(const void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option); + + /* Atmosphere functions. */ + Result AtmosphereCopyToIram(uintptr_t iram_dst, const void *dram_src, size_t size); + Result AtmosphereCopyFromIram(void *dram_dst, uintptr_t iram_src, size_t size); + Result AtmosphereReadWriteRegister(uint64_t address, uint32_t mask, uint32_t value, uint32_t *out_value); + Result AtmosphereGetEmummcConfig(void *out_config, void *out_paths, u32 storage_id); + + /* Helpers. */ + ALWAYS_INLINE Result SetConfig(spl::ConfigItem key, const u64 *value, size_t num_qwords) { + AsyncOperationKey dummy_op; + return SetConfig(std::addressof(dummy_op), key, value, num_qwords, nullptr); + } + + ALWAYS_INLINE Result SetConfig(spl::ConfigItem key, const u64 value) { + return SetConfig(key, std::addressof(value), 1); + } + + #if !defined(ATMOSPHERE_OS_HORIZON) + void PresetInternalKey(const AesKey *key, u32 generation, bool device); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/spl_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/spl_api.hpp new file mode 100644 index 00000000..3d60504f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/spl_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/spl/spl_types.hpp> + +namespace ams::spl { + + void Initialize(); + void InitializeForCrypto(); + void InitializeForSsl(); + void InitializeForEs(); + void InitializeForFs(); + void InitializeForManu(); + + void Finalize(); + + Result AllocateAesKeySlot(s32 *out_slot); + Result DeallocateAesKeySlot(s32 slot); + + Result GenerateAesKek(AccessKey *access_key, const void *key_source, size_t key_source_size, s32 generation, u32 option); + Result LoadAesKey(s32 slot, const AccessKey &access_key, const void *key_source, size_t key_source_size); + Result GenerateAesKey(void *dst, size_t dst_size, const AccessKey &access_key, const void *key_source, size_t key_source_size); + Result GenerateSpecificAesKey(void *dst, size_t dst_size, const void *key_source, size_t key_source_size, s32 generation, u32 option); + Result ComputeCtr(void *dst, size_t dst_size, s32 slot, const void *src, size_t src_size, const void *iv, size_t iv_size); + Result DecryptAesKey(void *dst, size_t dst_size, const void *src, size_t src_size, s32 generation, u32 option); + + Result GetConfig(u64 *out, ConfigItem item); + Result SetConfig(ConfigItem item, u64 v); + bool IsDevelopment(); + MemoryArrangement GetMemoryArrangement(); + + inline bool GetConfigBool(ConfigItem item) { + u64 v; + R_ABORT_UNLESS(::ams::spl::GetConfig(std::addressof(v), item)); + return v != 0; + } + + inline HardwareType GetHardwareType() { + u64 v; + R_ABORT_UNLESS(::ams::spl::GetConfig(std::addressof(v), ::ams::spl::ConfigItem::HardwareType)); + return static_cast<HardwareType>(v); + } + + inline HardwareState GetHardwareState() { + u64 v; + R_ABORT_UNLESS(::ams::spl::GetConfig(std::addressof(v), ::ams::spl::ConfigItem::HardwareState)); + return static_cast<HardwareState>(v); + } + + inline RetailInteractiveDisplayState GetRetailInteractiveDisplayState() { + u64 v; + R_ABORT_UNLESS(::ams::spl::GetConfig(std::addressof(v), ::ams::spl::ConfigItem::RetailInteractiveDisplayState)); + return static_cast<RetailInteractiveDisplayState>(v); + } + + inline u64 GetDeviceIdLow() { + u64 v; + R_ABORT_UNLESS(::ams::spl::GetConfig(std::addressof(v), ::ams::spl::ConfigItem::DeviceId)); + return v; + } + + inline bool IsRecoveryBoot() { + return ::ams::spl::GetConfigBool(::ams::spl::ConfigItem::IsRecoveryBoot); + } + + inline bool IsDevelopmentFunctionEnabled() { + return ::ams::spl::GetConfigBool(::ams::spl::ConfigItem::IsDevelopmentFunctionEnabled); + } + + inline bool IsDisabledProgramVerification() { + return ::ams::spl::GetConfigBool(::ams::spl::ConfigItem::DisableProgramVerification); + } + + inline bool IsUsb30ForceEnabled() { + return ::ams::spl::GetConfigBool(::ams::spl::ConfigItem::ExosphereForceEnableUsb30); + } + + Result SetBootReason(BootReasonValue boot_reason); + Result GetBootReason(BootReasonValue *out); + + inline BootReasonValue GetBootReason() { + BootReasonValue br; + R_ABORT_UNLESS(::ams::spl::GetBootReason(std::addressof(br))); + return br; + } + + SocType GetSocType(); + + Result GetPackage2Hash(void *dst, size_t dst_size); + Result GenerateRandomBytes(void *out, size_t buffer_size); + + Result LoadPreparedAesKey(s32 slot, const AccessKey &access_key); + + Result PrepareCommonEsTitleKey(AccessKey *out, const void *key_source, const size_t key_source_size, int generation); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/spl_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/spl_types.hpp new file mode 100644 index 00000000..11c80ad4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/spl/spl_types.hpp @@ -0,0 +1,265 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::spl { + + namespace smc { + + enum class FunctionId : u32 { + SetConfig = 0xC3000401, + GetConfig = 0xC3000002, + GetResult = 0xC3000003, + GetResultData = 0xC3000404, + ModularExponentiate = 0xC3000E05, + GenerateRandomBytes = 0xC3000006, + GenerateAesKek = 0xC3000007, + LoadAesKey = 0xC3000008, + ComputeAes = 0xC3000009, + GenerateSpecificAesKey = 0xC300000A, + ComputeCmac = 0xC300040B, + ReencryptDeviceUniqueData = 0xC300D60C, + DecryptDeviceUniqueData = 0xC300100D, + + ModularExponentiateWithStorageKey = 0xC300060F, + PrepareEsDeviceUniqueKey = 0xC3000610, + LoadPreparedAesKey = 0xC3000011, + PrepareCommonEsTitleKey = 0xC3000012, + + /* Deprecated functions. */ + LoadEsDeviceKey = 0xC300100C, + DecryptAndStoreGcKey = 0xC300100E, + + /* Atmosphere functions. */ + AtmosphereIramCopy = 0xF0000201, + AtmosphereReadWriteRegister = 0xF0000002, + + AtmosphereGetEmummcConfig = 0xF0000404, + }; + + enum class Result { + Success = 0, + NotSupported = 1, + InvalidArgument = 2, + InProgress = 3, + NoAsyncOperation = 4, + InvalidAsyncOperation = 5, + NotPermitted = 6, + NotInitialized = 7, + }; + + constexpr inline ::ams::Result ConvertResult(Result smc_result) { + /* smc::Result::Success becomes ResultSuccess() directly. */ + R_SUCCEED_IF(smc_result == smc::Result::Success); + + /* Convert to the list of known SecureMonitorErrors. */ + const auto converted = R_MAKE_NAMESPACE_RESULT(::ams::spl, static_cast<u32>(smc_result)); + R_UNLESS(spl::ResultSecureMonitorError::Includes(converted), spl::ResultUnexpectedSecureMonitorResult()); + + /* Return the error. */ + R_RETURN(converted); + } + + enum class CipherMode { + CbcEncrypt = 0, + CbcDecrypt = 1, + Ctr = 2, + }; + + enum class DeviceUniqueDataMode { + DecryptDeviceUniqueData = 0, + DecryptAndStoreGcKey = 1, + DecryptAndStoreEsDeviceKey = 2, + DecryptAndStoreSslKey = 3, + DecryptAndStoreDrmDeviceCertKey = 4, + }; + + enum class ModularExponentiateWithStorageKeyMode { + Gc = 0, + Ssl = 1, + DrmDeviceCert = 2, + }; + + enum class EsDeviceUniqueKeyType { + TitleKey = 0, + ArchiveKey = 1, + Unknown2 = 2, + }; + + struct AsyncOperationKey { + u64 value; + }; + } + + constexpr inline size_t AesKeySize = crypto::AesEncryptor128::KeySize; + constexpr inline size_t AesBlockSize = crypto::AesEncryptor128::BlockSize; + + enum class HardwareType { + Icosa = 0, + Copper = 1, + Hoag = 2, + Iowa = 3, + Calcio = 4, + Aula = 5, + }; + + enum SocType { + SocType_Erista = 0, + SocType_Mariko = 1, + }; + + enum HardwareState { + HardwareState_Development = 0, + HardwareState_Production = 1, + }; + + enum MemoryArrangement { + MemoryArrangement_Standard = 0, + MemoryArrangement_StandardForAppletDev = 1, + MemoryArrangement_StandardForSystemDev = 2, + MemoryArrangement_Expanded = 3, + MemoryArrangement_ExpandedForAppletDev = 4, + + /* Note: MemoryArrangement_Dynamic is not official. */ + /* Atmosphere uses it to maintain compatibility with firmwares prior to 6.0.0, */ + /* which removed the explicit retrieval of memory arrangement from PM. */ + MemoryArrangement_Dynamic = 5, + MemoryArrangement_Count, + }; + + enum RetailInteractiveDisplayState { + RetailInteractiveDisplayState_Disabled = 0, + RetailInteractiveDisplayState_Enabled = 1, + }; + + struct BootReasonValue { + union { + struct { + u8 power_intr; + u8 rtc_intr; + u8 nv_erc; + u8 boot_reason; + }; + u32 value; + }; + }; + static_assert(sizeof(BootReasonValue) == sizeof(u32), "BootReasonValue definition!"); + + enum BootReason { + BootReason_Unknown = 0, + BootReason_AcOk = 1, + BootReason_OnKey = 2, + BootReason_RtcAlarm1 = 3, + BootReason_RtcAlarm2 = 4, + }; + + #pragma pack(push, 1) + + struct AesKey { + union { + u8 data[AesKeySize]; + u64 data64[AesKeySize / sizeof(u64)]; + }; + }; + static_assert(alignof(AesKey) == alignof(u8), "AesKey definition!"); + + struct IvCtr { + union { + u8 data[AesKeySize]; + u64 data64[AesKeySize / sizeof(u64)]; + }; + }; + static_assert(alignof(IvCtr) == alignof(u8), "IvCtr definition!"); + + struct Cmac { + union { + u8 data[AesKeySize]; + u64 data64[AesKeySize / sizeof(u64)]; + }; + }; + static_assert(alignof(Cmac) == alignof(u8), "Cmac definition!"); + + struct AccessKey { + union { + u8 data[AesKeySize]; + u64 data64[AesKeySize / sizeof(u64)]; + }; + }; + static_assert(alignof(AccessKey) == alignof(u8), "AccessKey definition!"); + + struct KeySource { + union { + u8 data[AesKeySize]; + u64 data64[AesKeySize / sizeof(u64)]; + }; + }; + static_assert(alignof(AccessKey) == alignof(u8), "KeySource definition!"); + #pragma pack(pop) + + enum class ConfigItem : u32 { + /* Standard config items. */ + DisableProgramVerification = 1, + DramId = 2, + SecurityEngineInterruptNumber = 3, + FuseVersion = 4, + HardwareType = 5, + HardwareState = 6, + IsRecoveryBoot = 7, + DeviceId = 8, + BootReason = 9, + MemoryMode = 10, + IsDevelopmentFunctionEnabled = 11, + KernelConfiguration = 12, + IsChargerHiZModeEnabled = 13, + RetailInteractiveDisplayState = 14, + RegulatorType = 15, + DeviceUniqueKeyGeneration = 16, + Package2Hash = 17, + + /* Extension config items for exosphere. */ + ExosphereApiVersion = 65000, + ExosphereNeedsReboot = 65001, + ExosphereNeedsShutdown = 65002, + ExosphereGitCommitHash = 65003, + ExosphereHasRcmBugPatch = 65004, + ExosphereBlankProdInfo = 65005, + ExosphereAllowCalWrites = 65006, + ExosphereEmummcType = 65007, + ExospherePayloadAddress = 65008, + ExosphereLogConfiguration = 65009, + ExosphereForceEnableUsb30 = 65010, + ExosphereSupportedHosVersion = 65011, + ExosphereApproximateApiVersion = 65012, /* NOTE: Internal use only. */ + }; + +} + +#if defined(ATMOSPHERE_OS_HORIZON) +/* Extensions to libnx spl config item enum. */ +constexpr inline SplConfigItem SplConfigItem_ExosphereApiVersion = static_cast<SplConfigItem>(65000); +constexpr inline SplConfigItem SplConfigItem_ExosphereNeedsReboot = static_cast<SplConfigItem>(65001); +constexpr inline SplConfigItem SplConfigItem_ExosphereNeedsShutdown = static_cast<SplConfigItem>(65002); +constexpr inline SplConfigItem SplConfigItem_ExosphereGitCommitHash = static_cast<SplConfigItem>(65003); +constexpr inline SplConfigItem SplConfigItem_ExosphereHasRcmBugPatch = static_cast<SplConfigItem>(65004); +constexpr inline SplConfigItem SplConfigItem_ExosphereBlankProdInfo = static_cast<SplConfigItem>(65005); +constexpr inline SplConfigItem SplConfigItem_ExosphereAllowCalWrites = static_cast<SplConfigItem>(65006); +constexpr inline SplConfigItem SplConfigItem_ExosphereEmummcType = static_cast<SplConfigItem>(65007); +constexpr inline SplConfigItem SplConfigItem_ExospherePayloadAddress = static_cast<SplConfigItem>(65008); +constexpr inline SplConfigItem SplConfigItem_ExosphereLogConfiguration = static_cast<SplConfigItem>(65009); +constexpr inline SplConfigItem SplConfigItem_ExosphereForceEnableUsb30 = static_cast<SplConfigItem>(65010); +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sprofile.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sprofile.hpp new file mode 100644 index 00000000..42c75cc5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sprofile.hpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <stratosphere/sprofile/sprofile_types.hpp> +#include <stratosphere/sprofile/srv/sprofile_srv_api.hpp> \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sprofile/sprofile_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sprofile/sprofile_types.hpp new file mode 100644 index 00000000..362a823c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sprofile/sprofile_types.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::sprofile { + + struct Identifier { + u8 data[7]; + + friend bool operator==(const Identifier &lhs, const Identifier &rhs) { + return std::memcmp(lhs.data, rhs.data, sizeof(lhs.data)) == 0; + } + + friend bool operator!=(const Identifier &lhs, const Identifier &rhs) { + return !(lhs == rhs); + } + }; + static_assert(sizeof(Identifier) == 7); + static_assert(util::is_pod<Identifier>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sprofile/srv/sprofile_srv_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sprofile/srv/sprofile_srv_api.hpp new file mode 100644 index 00000000..0d621707 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/sprofile/srv/sprofile_srv_api.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::sprofile::srv { + + void Initialize(); + void StartIpcServer(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/svc/svc_stratosphere_shims.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/svc/svc_stratosphere_shims.hpp new file mode 100644 index 00000000..d68a56f3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/svc/svc_stratosphere_shims.hpp @@ -0,0 +1,530 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + + namespace ams::svc { + + #if defined(ATMOSPHERE_ARCH_ARM64) + + namespace aarch64::lp64 { + + ALWAYS_INLINE Result SetHeapSize(::ams::svc::Address *out_address, ::ams::svc::Size size) { + R_RETURN(::svcSetHeapSize(reinterpret_cast<void **>(out_address), size)); + } + + ALWAYS_INLINE Result SetHeapSize(uintptr_t *out_address, ::ams::svc::Size size) { + static_assert(sizeof(::ams::svc::Address) == sizeof(uintptr_t)); + R_RETURN(::svcSetHeapSize(reinterpret_cast<void **>(out_address), size)); + } + + ALWAYS_INLINE Result SetMemoryPermission(::ams::svc::Address address, ::ams::svc::Size size, ::ams::svc::MemoryPermission perm) { + R_RETURN(::svcSetMemoryPermission(reinterpret_cast<void *>(static_cast<uintptr_t>(address)), size, static_cast<u32>(perm))); + } + + ALWAYS_INLINE Result SetMemoryAttribute(::ams::svc::Address address, ::ams::svc::Size size, uint32_t mask, uint32_t attr) { + R_RETURN(::svcSetMemoryAttribute(reinterpret_cast<void *>(static_cast<uintptr_t>(address)), size, mask, attr)); + } + + ALWAYS_INLINE Result MapMemory(::ams::svc::Address dst_address, ::ams::svc::Address src_address, ::ams::svc::Size size) { + R_RETURN(::svcMapMemory(reinterpret_cast<void *>(static_cast<uintptr_t>(dst_address)), reinterpret_cast<void *>(static_cast<uintptr_t>(src_address)), size)); + } + + ALWAYS_INLINE Result UnmapMemory(::ams::svc::Address dst_address, ::ams::svc::Address src_address, ::ams::svc::Size size) { + R_RETURN(::svcUnmapMemory(reinterpret_cast<void *>(static_cast<uintptr_t>(dst_address)), reinterpret_cast<void *>(static_cast<uintptr_t>(src_address)), size)); + } + + ALWAYS_INLINE Result QueryMemory(::ams::svc::UserPointer< ::ams::svc::lp64::MemoryInfo *> out_memory_info, ::ams::svc::PageInfo *out_page_info, ::ams::svc::Address address) { + R_RETURN(::svcQueryMemory(reinterpret_cast<::MemoryInfo *>(out_memory_info.GetPointerUnsafe()), reinterpret_cast<u32 *>(out_page_info), address)); + } + + ALWAYS_INLINE void ExitProcess() { + return ::svcExitProcess(); + } + + ALWAYS_INLINE Result CreateThread(::ams::svc::Handle *out_handle, ::ams::svc::ThreadFunc func, ::ams::svc::Address arg, ::ams::svc::Address stack_bottom, int32_t priority, int32_t core_id) { + R_RETURN(::svcCreateThread(out_handle, reinterpret_cast<void *>(static_cast<uintptr_t>(func)), reinterpret_cast<void *>(static_cast<uintptr_t>(arg)), reinterpret_cast<void *>(static_cast<uintptr_t>(stack_bottom)), priority, core_id)); + } + + ALWAYS_INLINE Result StartThread(::ams::svc::Handle thread_handle) { + R_RETURN(::svcStartThread(thread_handle)); + } + + ALWAYS_INLINE void ExitThread() { + return ::svcExitThread(); + } + + ALWAYS_INLINE void SleepThread(int64_t ns) { + return ::svcSleepThread(ns); + } + + ALWAYS_INLINE Result GetThreadPriority(int32_t *out_priority, ::ams::svc::Handle thread_handle) { + R_RETURN(::svcGetThreadPriority(out_priority, thread_handle)); + } + + ALWAYS_INLINE Result SetThreadPriority(::ams::svc::Handle thread_handle, int32_t priority) { + R_RETURN(::svcSetThreadPriority(thread_handle, priority)); + } + + ALWAYS_INLINE Result GetThreadCoreMask(int32_t *out_core_id, uint64_t *out_affinity_mask, ::ams::svc::Handle thread_handle) { + R_RETURN(::svcGetThreadCoreMask(out_core_id, out_affinity_mask, thread_handle)); + } + + ALWAYS_INLINE Result SetThreadCoreMask(::ams::svc::Handle thread_handle, int32_t core_id, uint64_t affinity_mask) { + R_RETURN(::svcSetThreadCoreMask(thread_handle, core_id, affinity_mask)); + } + + ALWAYS_INLINE int32_t GetCurrentProcessorNumber() { + return ::svcGetCurrentProcessorNumber(); + } + + ALWAYS_INLINE Result SignalEvent(::ams::svc::Handle event_handle) { + R_RETURN(::svcSignalEvent(event_handle)); + } + + ALWAYS_INLINE Result ClearEvent(::ams::svc::Handle event_handle) { + R_RETURN(::svcClearEvent(event_handle)); + } + + ALWAYS_INLINE Result MapSharedMemory(::ams::svc::Handle shmem_handle, ::ams::svc::Address address, ::ams::svc::Size size, ::ams::svc::MemoryPermission map_perm) { + R_RETURN(::svcMapSharedMemory(shmem_handle, reinterpret_cast<void *>(static_cast<uintptr_t>(address)), size, static_cast<u32>(map_perm))); + } + + ALWAYS_INLINE Result UnmapSharedMemory(::ams::svc::Handle shmem_handle, ::ams::svc::Address address, ::ams::svc::Size size) { + R_RETURN(::svcUnmapSharedMemory(shmem_handle, reinterpret_cast<void *>(static_cast<uintptr_t>(address)), size)); + } + + ALWAYS_INLINE Result CreateTransferMemory(::ams::svc::Handle *out_handle, ::ams::svc::Address address, ::ams::svc::Size size, ::ams::svc::MemoryPermission map_perm) { + R_RETURN(::svcCreateTransferMemory(out_handle, reinterpret_cast<void *>(static_cast<uintptr_t>(address)), size, static_cast<u32>(map_perm))); + } + + ALWAYS_INLINE Result CloseHandle(::ams::svc::Handle handle) { + R_RETURN(::svcCloseHandle(handle)); + } + + ALWAYS_INLINE Result ResetSignal(::ams::svc::Handle handle) { + R_RETURN(::svcResetSignal(handle)); + } + + ALWAYS_INLINE Result WaitSynchronization(int32_t *out_index, ::ams::svc::UserPointer<const ::ams::svc::Handle *> handles, int32_t num_handles, int64_t timeout_ns) { + R_RETURN(::svcWaitSynchronization(out_index, handles.GetPointerUnsafe(), num_handles, timeout_ns)); + } + + ALWAYS_INLINE Result CancelSynchronization(::ams::svc::Handle handle) { + R_RETURN(::svcCancelSynchronization(handle)); + } + + ALWAYS_INLINE Result ArbitrateLock(::ams::svc::Handle thread_handle, ::ams::svc::Address address, uint32_t tag) { + R_RETURN(::svcArbitrateLock(thread_handle, reinterpret_cast<u32 *>(static_cast<uintptr_t>(address)), tag)); + } + + ALWAYS_INLINE Result ArbitrateUnlock(::ams::svc::Address address) { + R_RETURN(::svcArbitrateUnlock(reinterpret_cast<u32 *>(static_cast<uintptr_t>(address)))); + } + + ALWAYS_INLINE Result WaitProcessWideKeyAtomic(::ams::svc::Address address, ::ams::svc::Address cv_key, uint32_t tag, int64_t timeout_ns) { + R_RETURN(::svcWaitProcessWideKeyAtomic(reinterpret_cast<u32 *>(static_cast<uintptr_t>(address)), reinterpret_cast<u32 *>(static_cast<uintptr_t>(cv_key)), tag, timeout_ns)); + } + + ALWAYS_INLINE void SignalProcessWideKey(::ams::svc::Address cv_key, int32_t count) { + return ::svcSignalProcessWideKey(reinterpret_cast<u32 *>(static_cast<uintptr_t>(cv_key)), count); + } + + ALWAYS_INLINE int64_t GetSystemTick() { + return ::svcGetSystemTick(); + } + + ALWAYS_INLINE Result ConnectToNamedPort(::ams::svc::Handle *out_handle, ::ams::svc::UserPointer<const char *> name) { + R_RETURN(::svcConnectToNamedPort(out_handle, name.GetPointerUnsafe())); + } + + ALWAYS_INLINE Result SendSyncRequestLight(::ams::svc::Handle session_handle) { + R_RETURN(::svcSendSyncRequestLight(session_handle)); + } + + ALWAYS_INLINE Result SendSyncRequest(::ams::svc::Handle session_handle) { + R_RETURN(::svcSendSyncRequest(session_handle)); + } + + ALWAYS_INLINE Result SendSyncRequestWithUserBuffer(::ams::svc::Address message_buffer, ::ams::svc::Size message_buffer_size, ::ams::svc::Handle session_handle) { + R_RETURN(::svcSendSyncRequestWithUserBuffer(reinterpret_cast<void *>(static_cast<uintptr_t>(message_buffer)), message_buffer_size, session_handle)); + } + + ALWAYS_INLINE Result SendAsyncRequestWithUserBuffer(::ams::svc::Handle *out_event_handle, ::ams::svc::Address message_buffer, ::ams::svc::Size message_buffer_size, ::ams::svc::Handle session_handle) { + R_RETURN(::svcSendAsyncRequestWithUserBuffer(out_event_handle, reinterpret_cast<void *>(static_cast<uintptr_t>(message_buffer)), message_buffer_size, session_handle)); + } + + ALWAYS_INLINE Result GetProcessId(uint64_t *out_process_id, ::ams::svc::Handle process_handle) { + R_RETURN(::svcGetProcessId(out_process_id, process_handle)); + } + + ALWAYS_INLINE Result GetThreadId(uint64_t *out_thread_id, ::ams::svc::Handle thread_handle) { + R_RETURN(::svcGetThreadId(out_thread_id, thread_handle)); + } + + ALWAYS_INLINE void Break(::ams::svc::BreakReason break_reason, ::ams::svc::Address arg, ::ams::svc::Size size) { + ::svcBreak(break_reason, arg, size); + } + + ALWAYS_INLINE Result OutputDebugString(::ams::svc::UserPointer<const char *> debug_str, ::ams::svc::Size len) { + R_RETURN(::svcOutputDebugString(debug_str.GetPointerUnsafe(), len)); + } + + ALWAYS_INLINE void ReturnFromException(::ams::Result result) { + return ::svcReturnFromException(result.GetValue()); + } + + ALWAYS_INLINE Result GetInfo(uint64_t *out, ::ams::svc::InfoType info_type, ::ams::svc::Handle handle, uint64_t info_subtype) { + R_RETURN(::svcGetInfo(out, static_cast<u32>(info_type), handle, info_subtype)); + } + + ALWAYS_INLINE void FlushEntireDataCache() { + return ::svcFlushEntireDataCache(); + } + + ALWAYS_INLINE Result FlushDataCache(::ams::svc::Address address, ::ams::svc::Size size) { + R_RETURN(::svcFlushDataCache(reinterpret_cast<void *>(static_cast<uintptr_t>(address)), size)); + } + + ALWAYS_INLINE Result MapPhysicalMemory(::ams::svc::Address address, ::ams::svc::Size size) { + R_RETURN(::svcMapPhysicalMemory(reinterpret_cast<void *>(static_cast<uintptr_t>(address)), size)); + } + + ALWAYS_INLINE Result UnmapPhysicalMemory(::ams::svc::Address address, ::ams::svc::Size size) { + R_RETURN(::svcUnmapPhysicalMemory(reinterpret_cast<void *>(static_cast<uintptr_t>(address)), size)); + } + + ALWAYS_INLINE Result GetDebugFutureThreadInfo(::ams::svc::lp64::LastThreadContext *out_context, uint64_t *thread_id, ::ams::svc::Handle debug_handle, int64_t ns) { + R_RETURN(::svcGetDebugFutureThreadInfo(reinterpret_cast<::LastThreadContext *>(out_context), thread_id, debug_handle, ns)); + } + + ALWAYS_INLINE Result GetLastThreadInfo(::ams::svc::lp64::LastThreadContext *out_context, ::ams::svc::Address *out_tls_address, uint32_t *out_flags) { + R_RETURN(::svcGetLastThreadInfo(reinterpret_cast<::LastThreadContext *>(out_context), reinterpret_cast<u64 *>(out_tls_address), out_flags)); + } + + ALWAYS_INLINE Result GetResourceLimitLimitValue(int64_t *out_limit_value, ::ams::svc::Handle resource_limit_handle, ::ams::svc::LimitableResource which) { + R_RETURN(::svcGetResourceLimitLimitValue(out_limit_value, resource_limit_handle, static_cast<::LimitableResource>(which))); + } + + ALWAYS_INLINE Result GetResourceLimitCurrentValue(int64_t *out_current_value, ::ams::svc::Handle resource_limit_handle, ::ams::svc::LimitableResource which) { + R_RETURN(::svcGetResourceLimitCurrentValue(out_current_value, resource_limit_handle, static_cast<::LimitableResource>(which))); + } + + ALWAYS_INLINE Result SetThreadActivity(::ams::svc::Handle thread_handle, ::ams::svc::ThreadActivity thread_activity) { + R_RETURN(::svcSetThreadActivity(thread_handle, static_cast<::ThreadActivity>(thread_activity))); + } + + ALWAYS_INLINE Result GetThreadContext3(::ams::svc::UserPointer< ::ams::svc::ThreadContext *> out_context, ::ams::svc::Handle thread_handle) { + R_RETURN(::svcGetThreadContext3(reinterpret_cast<::ThreadContext *>(out_context.GetPointerUnsafe()), thread_handle)); + } + + ALWAYS_INLINE Result WaitForAddress(::ams::svc::Address address, ::ams::svc::ArbitrationType arb_type, int64_t value, int64_t timeout_ns) { + R_RETURN(::svcWaitForAddress(reinterpret_cast<void *>(static_cast<uintptr_t>(address)), arb_type, value, timeout_ns)); + } + + ALWAYS_INLINE Result SignalToAddress(::ams::svc::Address address, ::ams::svc::SignalType signal_type, int32_t value, int32_t count) { + R_RETURN(::svcSignalToAddress(reinterpret_cast<void *>(static_cast<uintptr_t>(address)), signal_type, value, count)); + } + + ALWAYS_INLINE void SynchronizePreemptionState() { + return ::svcSynchronizePreemptionState(); + } + + ALWAYS_INLINE Result GetResourceLimitPeakValue(int64_t *out_peak_value, ::ams::svc::Handle resource_limit_handle, ::ams::svc::LimitableResource which) { + R_RETURN(::svcGetResourceLimitPeakValue(out_peak_value, resource_limit_handle, static_cast<::LimitableResource>(which))); + } + + ALWAYS_INLINE void KernelDebug(::ams::svc::KernelDebugType kern_debug_type, uint64_t arg0, uint64_t arg1, uint64_t arg2) { + return ::svcKernelDebug(kern_debug_type, arg0, arg1, arg2); + } + + ALWAYS_INLINE void ChangeKernelTraceState(::ams::svc::KernelTraceState kern_trace_state) { + return ::svcChangeKernelTraceState(kern_trace_state); + } + + ALWAYS_INLINE Result CreateSession(::ams::svc::Handle *out_server_session_handle, ::ams::svc::Handle *out_client_session_handle, bool is_light, ::ams::svc::Address name) { + R_RETURN(::svcCreateSession(out_server_session_handle, out_client_session_handle, is_light, name)); + } + + ALWAYS_INLINE Result AcceptSession(::ams::svc::Handle *out_handle, ::ams::svc::Handle port) { + R_RETURN(::svcAcceptSession(out_handle, port)); + } + + ALWAYS_INLINE Result ReplyAndReceiveLight(::ams::svc::Handle handle) { + R_RETURN(::svcReplyAndReceiveLight(handle)); + } + + ALWAYS_INLINE Result ReplyAndReceive(int32_t *out_index, ::ams::svc::UserPointer<const ::ams::svc::Handle *> handles, int32_t num_handles, ::ams::svc::Handle reply_target, int64_t timeout_ns) { + R_RETURN(::svcReplyAndReceive(out_index, handles.GetPointerUnsafe(), num_handles, reply_target, timeout_ns)); + } + + ALWAYS_INLINE Result ReplyAndReceiveWithUserBuffer(int32_t *out_index, ::ams::svc::Address message_buffer, ::ams::svc::Size message_buffer_size, ::ams::svc::UserPointer<const ::ams::svc::Handle *> handles, int32_t num_handles, ::ams::svc::Handle reply_target, int64_t timeout_ns) { + R_RETURN(::svcReplyAndReceiveWithUserBuffer(out_index, reinterpret_cast<void *>(static_cast<uintptr_t>(message_buffer)), message_buffer_size, handles.GetPointerUnsafe(), num_handles, reply_target, timeout_ns)); + } + + ALWAYS_INLINE Result CreateEvent(::ams::svc::Handle *out_write_handle, ::ams::svc::Handle *out_read_handle) { + R_RETURN(::svcCreateEvent(out_write_handle, out_read_handle)); + } + + ALWAYS_INLINE Result MapPhysicalMemoryUnsafe(::ams::svc::Address address, ::ams::svc::Size size) { + R_RETURN(::svcMapPhysicalMemoryUnsafe(reinterpret_cast<void *>(static_cast<uintptr_t>(address)), size)); + } + + ALWAYS_INLINE Result UnmapPhysicalMemoryUnsafe(::ams::svc::Address address, ::ams::svc::Size size) { + R_RETURN(::svcUnmapPhysicalMemoryUnsafe(reinterpret_cast<void *>(static_cast<uintptr_t>(address)), size)); + } + + ALWAYS_INLINE Result SetUnsafeLimit(::ams::svc::Size limit) { + R_RETURN(::svcSetUnsafeLimit(limit)); + } + + ALWAYS_INLINE Result CreateCodeMemory(::ams::svc::Handle *out_handle, ::ams::svc::Address address, ::ams::svc::Size size) { + R_RETURN(::svcCreateCodeMemory(out_handle, reinterpret_cast<void *>(static_cast<uintptr_t>(address)), size)); + } + + ALWAYS_INLINE Result ControlCodeMemory(::ams::svc::Handle code_memory_handle, ::ams::svc::CodeMemoryOperation operation, uint64_t address, uint64_t size, ::ams::svc::MemoryPermission perm) { + R_RETURN(::svcControlCodeMemory(code_memory_handle, static_cast<::CodeMapOperation>(operation), reinterpret_cast<void *>(address), size, static_cast<u32>(perm))); + } + + ALWAYS_INLINE void SleepSystem() { + return ::svcSleepSystem(); + } + + ALWAYS_INLINE Result ReadWriteRegister(uint32_t *out_value, ::ams::svc::PhysicalAddress address, uint32_t mask, uint32_t value) { + R_RETURN(::svcReadWriteRegister(out_value, address, mask, value)); + } + + ALWAYS_INLINE Result SetProcessActivity(::ams::svc::Handle process_handle, ::ams::svc::ProcessActivity process_activity) { + R_RETURN(::svcSetProcessActivity(process_handle, static_cast<::ProcessActivity>(process_activity))); + } + + ALWAYS_INLINE Result CreateSharedMemory(::ams::svc::Handle *out_handle, ::ams::svc::Size size, ::ams::svc::MemoryPermission owner_perm, ::ams::svc::MemoryPermission remote_perm) { + R_RETURN(::svcCreateSharedMemory(out_handle, size, static_cast<u32>(owner_perm), static_cast<u32>(remote_perm))); + } + + ALWAYS_INLINE Result MapTransferMemory(::ams::svc::Handle trmem_handle, ::ams::svc::Address address, ::ams::svc::Size size, ::ams::svc::MemoryPermission owner_perm) { + R_RETURN(::svcMapTransferMemory(trmem_handle, reinterpret_cast<void *>(static_cast<uintptr_t>(address)), size, static_cast<u32>(owner_perm))); + } + + ALWAYS_INLINE Result UnmapTransferMemory(::ams::svc::Handle trmem_handle, ::ams::svc::Address address, ::ams::svc::Size size) { + R_RETURN(::svcUnmapTransferMemory(trmem_handle, reinterpret_cast<void *>(static_cast<uintptr_t>(address)), size)); + } + + ALWAYS_INLINE Result CreateInterruptEvent(::ams::svc::Handle *out_read_handle, int32_t interrupt_id, ::ams::svc::InterruptType interrupt_type) { + R_RETURN(::svcCreateInterruptEvent(out_read_handle, interrupt_id, static_cast<u32>(interrupt_type))); + } + + ALWAYS_INLINE Result QueryPhysicalAddress(::ams::svc::lp64::PhysicalMemoryInfo *out_info, ::ams::svc::Address address) { + R_RETURN(::svcQueryPhysicalAddress(reinterpret_cast<::PhysicalMemoryInfo *>(out_info), address)); + } + + ALWAYS_INLINE Result QueryMemoryMapping(::ams::svc::Address *out_address, ::ams::svc::Size *out_size, ::ams::svc::PhysicalAddress physical_address, ::ams::svc::Size size) { + R_RETURN(::svcQueryMemoryMapping(reinterpret_cast<u64 *>(out_address), reinterpret_cast<u64 *>(out_size), physical_address, size)); + } + + ALWAYS_INLINE Result LegacyQueryIoMapping(::ams::svc::Address *out_address, ::ams::svc::PhysicalAddress physical_address, ::ams::svc::Size size) { + R_RETURN(::svcLegacyQueryIoMapping(reinterpret_cast<u64 *>(out_address), physical_address, size)); + } + + ALWAYS_INLINE Result CreateDeviceAddressSpace(::ams::svc::Handle *out_handle, uint64_t das_address, uint64_t das_size) { + R_RETURN(::svcCreateDeviceAddressSpace(out_handle, das_address, das_size)); + } + + ALWAYS_INLINE Result AttachDeviceAddressSpace(::ams::svc::DeviceName device_name, ::ams::svc::Handle das_handle) { + R_RETURN(::svcAttachDeviceAddressSpace(static_cast<u64>(device_name), das_handle)); + } + + ALWAYS_INLINE Result DetachDeviceAddressSpace(::ams::svc::DeviceName device_name, ::ams::svc::Handle das_handle) { + R_RETURN(::svcDetachDeviceAddressSpace(static_cast<u64>(device_name), das_handle)); + } + + ALWAYS_INLINE Result MapDeviceAddressSpaceByForce(::ams::svc::Handle das_handle, ::ams::svc::Handle process_handle, uint64_t process_address, ::ams::svc::Size size, uint64_t device_address, u32 option) { + R_RETURN(::svcMapDeviceAddressSpaceByForce(das_handle, process_handle, process_address, size, device_address, option)); + } + + ALWAYS_INLINE Result MapDeviceAddressSpaceAligned(::ams::svc::Handle das_handle, ::ams::svc::Handle process_handle, uint64_t process_address, ::ams::svc::Size size, uint64_t device_address, u32 option) { + R_RETURN(::svcMapDeviceAddressSpaceAligned(das_handle, process_handle, process_address, size, device_address, option)); + } + + ALWAYS_INLINE Result UnmapDeviceAddressSpace(::ams::svc::Handle das_handle, ::ams::svc::Handle process_handle, uint64_t process_address, ::ams::svc::Size size, uint64_t device_address) { + R_RETURN(::svcUnmapDeviceAddressSpace(das_handle, process_handle, process_address, size, device_address)); + } + + ALWAYS_INLINE Result InvalidateProcessDataCache(::ams::svc::Handle process_handle, uint64_t address, uint64_t size) { + R_RETURN(::svcInvalidateProcessDataCache(process_handle, address, size)); + } + + ALWAYS_INLINE Result StoreProcessDataCache(::ams::svc::Handle process_handle, uint64_t address, uint64_t size) { + R_RETURN(::svcStoreProcessDataCache(process_handle, address, size)); + } + + ALWAYS_INLINE Result FlushProcessDataCache(::ams::svc::Handle process_handle, uint64_t address, uint64_t size) { + R_RETURN(::svcFlushProcessDataCache(process_handle, address, size)); + } + + ALWAYS_INLINE Result DebugActiveProcess(::ams::svc::Handle *out_handle, uint64_t process_id) { + R_RETURN(::svcDebugActiveProcess(out_handle, process_id)); + } + + ALWAYS_INLINE Result BreakDebugProcess(::ams::svc::Handle debug_handle) { + R_RETURN(::svcBreakDebugProcess(debug_handle)); + } + + ALWAYS_INLINE Result TerminateDebugProcess(::ams::svc::Handle debug_handle) { + R_RETURN(::svcTerminateDebugProcess(debug_handle)); + } + + ALWAYS_INLINE Result GetDebugEvent(::ams::svc::UserPointer< ::ams::svc::lp64::DebugEventInfo *> out_info, ::ams::svc::Handle debug_handle) { + R_RETURN(::svcGetDebugEvent(out_info.GetPointerUnsafe(), debug_handle)); + } + + ALWAYS_INLINE Result ContinueDebugEvent(::ams::svc::Handle debug_handle, uint32_t flags, ::ams::svc::UserPointer<const uint64_t *> thread_ids, int32_t num_thread_ids) { + R_RETURN(::svcContinueDebugEvent(debug_handle, flags, const_cast<u64 *>(thread_ids.GetPointerUnsafe()), num_thread_ids)); + } + + ALWAYS_INLINE Result LegacyContinueDebugEvent(::ams::svc::Handle debug_handle, uint32_t flags, uint64_t thread_id) { + R_RETURN(::svcLegacyContinueDebugEvent(debug_handle, flags, thread_id)); + } + + ALWAYS_INLINE Result GetProcessList(int32_t *out_num_processes, ::ams::svc::UserPointer<uint64_t *> out_process_ids, int32_t max_out_count) { + R_RETURN(::svcGetProcessList(out_num_processes, out_process_ids.GetPointerUnsafe(), max_out_count)); + } + + ALWAYS_INLINE Result GetThreadList(int32_t *out_num_threads, ::ams::svc::UserPointer<uint64_t *> out_thread_ids, int32_t max_out_count, ::ams::svc::Handle debug_handle) { + R_RETURN(::svcGetThreadList(out_num_threads, out_thread_ids.GetPointerUnsafe(), max_out_count, debug_handle)); + } + + ALWAYS_INLINE Result GetDebugThreadContext(::ams::svc::UserPointer< ::ams::svc::ThreadContext *> out_context, ::ams::svc::Handle debug_handle, uint64_t thread_id, uint32_t context_flags) { + R_RETURN(::svcGetDebugThreadContext(reinterpret_cast<::ThreadContext *>(out_context.GetPointerUnsafe()), debug_handle, thread_id, context_flags)); + } + + ALWAYS_INLINE Result SetDebugThreadContext(::ams::svc::Handle debug_handle, uint64_t thread_id, ::ams::svc::UserPointer<const ::ams::svc::ThreadContext *> context, uint32_t context_flags) { + R_RETURN(::svcSetDebugThreadContext(debug_handle, thread_id, reinterpret_cast<const ::ThreadContext *>(context.GetPointerUnsafe()), context_flags)); + } + + ALWAYS_INLINE Result QueryDebugProcessMemory(::ams::svc::UserPointer< ::ams::svc::lp64::MemoryInfo *> out_memory_info, ::ams::svc::PageInfo *out_page_info, ::ams::svc::Handle process_handle, ::ams::svc::Address address) { + R_RETURN(::svcQueryDebugProcessMemory(reinterpret_cast<::MemoryInfo *>(out_memory_info.GetPointerUnsafe()), reinterpret_cast<u32 *>(out_page_info), process_handle, address)); + } + + ALWAYS_INLINE Result ReadDebugProcessMemory(::ams::svc::Address buffer, ::ams::svc::Handle debug_handle, ::ams::svc::Address address, ::ams::svc::Size size) { + R_RETURN(::svcReadDebugProcessMemory(reinterpret_cast<void *>(static_cast<uintptr_t>(buffer)), debug_handle, address, size)); + } + + ALWAYS_INLINE Result WriteDebugProcessMemory(::ams::svc::Handle debug_handle, ::ams::svc::Address buffer, ::ams::svc::Address address, ::ams::svc::Size size) { + R_RETURN(::svcWriteDebugProcessMemory(debug_handle, reinterpret_cast<const void *>(static_cast<uintptr_t>(buffer)), address, size)); + } + + ALWAYS_INLINE Result SetHardwareBreakPoint(::ams::svc::HardwareBreakPointRegisterName name, uint64_t flags, uint64_t value) { + R_RETURN(::svcSetHardwareBreakPoint(static_cast<u32>(name), flags, value)); + } + + ALWAYS_INLINE Result GetDebugThreadParam(uint64_t *out_64, uint32_t *out_32, ::ams::svc::Handle debug_handle, uint64_t thread_id, ::ams::svc::DebugThreadParam param) { + R_RETURN(::svcGetDebugThreadParam(out_64, out_32, debug_handle, thread_id, static_cast<::DebugThreadParam>(param))); + } + + ALWAYS_INLINE Result GetSystemInfo(uint64_t *out, ::ams::svc::SystemInfoType info_type, ::ams::svc::Handle handle, uint64_t info_subtype) { + R_RETURN(::svcGetSystemInfo(out, static_cast<u64>(info_type), handle, info_subtype)); + } + + ALWAYS_INLINE Result CreatePort(::ams::svc::Handle *out_server_handle, ::ams::svc::Handle *out_client_handle, int32_t max_sessions, bool is_light, ::ams::svc::Address name) { + R_RETURN(::svcCreatePort(out_server_handle, out_client_handle, max_sessions, is_light, reinterpret_cast<const char *>(static_cast<uintptr_t>(name)))); + } + + ALWAYS_INLINE Result ManageNamedPort(::ams::svc::Handle *out_server_handle, ::ams::svc::UserPointer<const char *> name, int32_t max_sessions) { + R_RETURN(::svcManageNamedPort(out_server_handle, name.GetPointerUnsafe(), max_sessions)); + } + + ALWAYS_INLINE Result ConnectToPort(::ams::svc::Handle *out_handle, ::ams::svc::Handle port) { + R_RETURN(::svcConnectToPort(out_handle, port)); + } + + ALWAYS_INLINE Result SetProcessMemoryPermission(::ams::svc::Handle process_handle, uint64_t address, uint64_t size, ::ams::svc::MemoryPermission perm) { + R_RETURN(::svcSetProcessMemoryPermission(process_handle, address, size, static_cast<u32>(perm))); + } + + ALWAYS_INLINE Result MapProcessMemory(::ams::svc::Address dst_address, ::ams::svc::Handle process_handle, uint64_t src_address, ::ams::svc::Size size) { + R_RETURN(::svcMapProcessMemory(reinterpret_cast<void *>(static_cast<uintptr_t>(dst_address)), process_handle, src_address, size)); + } + + ALWAYS_INLINE Result UnmapProcessMemory(::ams::svc::Address dst_address, ::ams::svc::Handle process_handle, uint64_t src_address, ::ams::svc::Size size) { + R_RETURN(::svcUnmapProcessMemory(reinterpret_cast<void *>(static_cast<uintptr_t>(dst_address)), process_handle, src_address, size)); + } + + ALWAYS_INLINE Result QueryProcessMemory(::ams::svc::UserPointer< ::ams::svc::lp64::MemoryInfo *> out_memory_info, ::ams::svc::PageInfo *out_page_info, ::ams::svc::Handle process_handle, uint64_t address) { + R_RETURN(::svcQueryProcessMemory(reinterpret_cast<::MemoryInfo *>(out_memory_info.GetPointerUnsafe()), reinterpret_cast<u32 *>(out_page_info), process_handle, address)); + } + + ALWAYS_INLINE Result MapProcessCodeMemory(::ams::svc::Handle process_handle, uint64_t dst_address, uint64_t src_address, uint64_t size) { + R_RETURN(::svcMapProcessCodeMemory(process_handle, dst_address, src_address, size)); + } + + ALWAYS_INLINE Result UnmapProcessCodeMemory(::ams::svc::Handle process_handle, uint64_t dst_address, uint64_t src_address, uint64_t size) { + R_RETURN(::svcUnmapProcessCodeMemory(process_handle, dst_address, src_address, size)); + } + + ALWAYS_INLINE Result CreateProcess(::ams::svc::Handle *out_handle, ::ams::svc::UserPointer<const ::ams::svc::lp64::CreateProcessParameter *> parameters, ::ams::svc::UserPointer<const uint32_t *> caps, int32_t num_caps) { + R_RETURN(::svcCreateProcess(out_handle, parameters.GetPointerUnsafe(), caps.GetPointerUnsafe(), num_caps)); + } + + ALWAYS_INLINE Result StartProcess(::ams::svc::Handle process_handle, int32_t priority, int32_t core_id, uint64_t main_thread_stack_size) { + R_RETURN(::svcStartProcess(process_handle, priority, core_id, main_thread_stack_size)); + } + + ALWAYS_INLINE Result TerminateProcess(::ams::svc::Handle process_handle) { + R_RETURN(::svcTerminateProcess(process_handle)); + } + + ALWAYS_INLINE Result GetProcessInfo(int64_t *out_info, ::ams::svc::Handle process_handle, ::ams::svc::ProcessInfoType info_type) { + R_RETURN(::svcGetProcessInfo(out_info, process_handle, static_cast<::ProcessInfoType>(info_type))); + } + + ALWAYS_INLINE Result CreateResourceLimit(::ams::svc::Handle *out_handle) { + R_RETURN(::svcCreateResourceLimit(out_handle)); + } + + ALWAYS_INLINE Result SetResourceLimitLimitValue(::ams::svc::Handle resource_limit_handle, ::ams::svc::LimitableResource which, int64_t limit_value) { + R_RETURN(::svcSetResourceLimitLimitValue(resource_limit_handle, static_cast<::LimitableResource>(which), limit_value)); + } + + ALWAYS_INLINE void CallSecureMonitor(::ams::svc::lp64::SecureMonitorArguments *args) { + ::svcCallSecureMonitor(reinterpret_cast<::SecmonArgs *>(args)); + } + + ALWAYS_INLINE Result MapInsecurePhysicalMemory(::ams::svc::Address address, ::ams::svc::Size size) { + R_RETURN(::svcMapInsecurePhysicalMemory(reinterpret_cast<void *>(static_cast<uintptr_t>(address)), size)); + } + + ALWAYS_INLINE Result UnmapInsecurePhysicalMemory(::ams::svc::Address address, ::ams::svc::Size size) { + R_RETURN(::svcUnmapInsecurePhysicalMemory(reinterpret_cast<void *>(static_cast<uintptr_t>(address)), size)); + } + + } + + #endif + + ALWAYS_INLINE bool IsKernelMesosphere() { + uint64_t dummy; + return R_SUCCEEDED(::ams::svc::GetInfo(std::addressof(dummy), ::ams::svc::InfoType_MesosphereMeta, ::ams::svc::InvalidHandle, ::ams::svc::MesosphereMetaInfo_KernelVersion)); + } + + ALWAYS_INLINE bool IsKTraceEnabled() { + uint64_t value = 0; + return R_SUCCEEDED(::ams::svc::GetInfo(std::addressof(value), ::ams::svc::InfoType_MesosphereMeta, ::ams::svc::InvalidHandle, ::ams::svc::MesosphereMetaInfo_IsKTraceEnabled)) && value != 0; + } + + } + +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time.hpp new file mode 100644 index 00000000..c756fdaf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/time/time_common.hpp> +#include <stratosphere/time/time_api.hpp> +#include <stratosphere/time/time_posix_time.hpp> +#include <stratosphere/time/time_calendar_time.hpp> +#include <stratosphere/time/time_timezone_api.hpp> +#include <stratosphere/time/time_steady_clock_time_point.hpp> +#include <stratosphere/time/time_standard_steady_clock.hpp> +#include <stratosphere/time/time_standard_user_system_clock.hpp> +#include <stratosphere/time/time_standard_network_system_clock.hpp> +#include <stratosphere/time/impl/util/time_impl_util_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/impl/util/time_impl_util_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/impl/util/time_impl_util_api.hpp new file mode 100644 index 00000000..e7a241eb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/impl/util/time_impl_util_api.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/time/time_common.hpp> +#include <stratosphere/time/time_posix_time.hpp> +#include <stratosphere/time/time_steady_clock_time_point.hpp> + +namespace ams::time::impl::util { + + Result GetSpanBetween(s64 *out, const SteadyClockTimePoint &from, const SteadyClockTimePoint &to); + + bool IsLeapYear(int year); + bool IsValidDate(int year, int month, int day); + + int GetDaysInMonth(int year, int month); + + int DateToDays(int year, int month, int day); + void DaysToDate(int *out_year, int *out_month, int *out_day, int days); + + CalendarTime ToCalendarTimeInUtc(const PosixTime &posix_time); + PosixTime ToPosixTimeFromUtc(const CalendarTime &calendar_time); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_api.hpp new file mode 100644 index 00000000..34cc1394 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_api.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/time/time_common.hpp> +#include <stratosphere/time/time_posix_time.hpp> +#include <stratosphere/time/time_steady_clock_time_point.hpp> + +namespace ams::time { + + Result Initialize(); + Result InitializeForSystem(); + Result InitializeForSystemUser(); + + Result Finalize(); + + bool IsInitialized(); + + bool IsValidDate(int year, int month, int day); + + Result GetElapsedSecondsBetween(s64 *out, const SteadyClockTimePoint &from, const SteadyClockTimePoint &to); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_calendar_time.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_calendar_time.hpp new file mode 100644 index 00000000..56568b08 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_calendar_time.hpp @@ -0,0 +1,67 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/time/time_common.hpp> + +namespace ams::time { + + struct CalendarTime { + s16 year; + s8 month; + s8 day; + s8 hour; + s8 minute; + s8 second; + + bool IsValid() const; + + CalendarTime &operator+=(const TimeSpan &ts); + CalendarTime &operator-=(const TimeSpan &ts); + + friend CalendarTime operator+(const CalendarTime &lhs, const TimeSpan &rhs); + friend CalendarTime operator-(const CalendarTime &lhs, const TimeSpan &rhs); + + friend TimeSpan operator-(const CalendarTime &lhs, const CalendarTime &rhs); + + constexpr friend bool operator==(const CalendarTime &lhs, const CalendarTime &rhs) { return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day && lhs.hour == rhs.hour && lhs.minute == rhs.minute && lhs.second == rhs.second; } + constexpr friend bool operator!=(const CalendarTime &lhs, const CalendarTime &rhs) { return !(lhs == rhs); } + + constexpr friend bool operator<=(const CalendarTime &lhs, const CalendarTime &rhs) { return !(rhs < lhs); } + constexpr friend bool operator>=(const CalendarTime &lhs, const CalendarTime &rhs) { return !(lhs < rhs); } + constexpr friend bool operator> (const CalendarTime &lhs, const CalendarTime &rhs) { return rhs < lhs; } + + constexpr friend bool operator< (const CalendarTime &lhs, const CalendarTime &rhs) { + if (!std::is_constant_evaluated()) { + AMS_ASSERT(lhs.IsValid()); + AMS_ASSERT(rhs.IsValid()); + } + + constexpr auto ToUint64 = [](const time::CalendarTime &time) ALWAYS_INLINE_LAMBDA { + return (static_cast<u64>(time.year) << 40) | + (static_cast<u64>(time.month) << 32) | + (static_cast<u64>(time.day) << 24) | + (static_cast<u64>(time.hour) << 16) | + (static_cast<u64>(time.minute) << 8) | + (static_cast<u64>(time.second) << 0); + }; + + return ToUint64(lhs) < ToUint64(rhs); + } + }; + static_assert(sizeof(CalendarTime) == sizeof(u64)); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_common.hpp new file mode 100644 index 00000000..0e824003 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_common.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::time { + + using SourceId = util::Uuid; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_posix_time.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_posix_time.hpp new file mode 100644 index 00000000..7cd4f20e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_posix_time.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/time/time_common.hpp> + +namespace ams::time { + + struct PosixTime { + s64 value; + + constexpr PosixTime &operator+=(const TimeSpan &ts) { this->value += ts.GetSeconds(); return *this; } + constexpr PosixTime &operator-=(const TimeSpan &ts) { this->value -= ts.GetSeconds(); return *this; } + + constexpr friend PosixTime operator+(const PosixTime &lhs, const TimeSpan &rhs) { return { .value = lhs.value + rhs.GetSeconds() }; } + constexpr friend PosixTime operator-(const PosixTime &lhs, const TimeSpan &rhs) { return { .value = lhs.value - rhs.GetSeconds() }; } + + constexpr friend TimeSpan operator-(const PosixTime &lhs, const PosixTime &rhs) { return TimeSpan::FromSeconds(lhs.value - rhs.value); } + + constexpr friend bool operator==(const PosixTime &lhs, const PosixTime &rhs) { return lhs.value == rhs.value; } + constexpr friend bool operator!=(const PosixTime &lhs, const PosixTime &rhs) { return lhs.value != rhs.value; } + constexpr friend bool operator<=(const PosixTime &lhs, const PosixTime &rhs) { return lhs.value <= rhs.value; } + constexpr friend bool operator>=(const PosixTime &lhs, const PosixTime &rhs) { return lhs.value >= rhs.value; } + constexpr friend bool operator< (const PosixTime &lhs, const PosixTime &rhs) { return lhs.value < rhs.value; } + constexpr friend bool operator> (const PosixTime &lhs, const PosixTime &rhs) { return lhs.value > rhs.value; } + }; + static_assert(sizeof(PosixTime) == sizeof(s64)); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_standard_network_system_clock.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_standard_network_system_clock.hpp new file mode 100644 index 00000000..44605e40 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_standard_network_system_clock.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/time/time_common.hpp> +#include <stratosphere/time/time_system_clock_common.hpp> +#include <stratosphere/time/time_posix_time.hpp> + +namespace ams::time { + + class StandardNetworkSystemClock { + public: + using rep = SystemClockTraits::rep; + using period = SystemClockTraits::period; + using duration = SystemClockTraits::duration; + using time_point = SystemClockTraits::time_point; + static constexpr bool is_steady = false; + public: + static time_point now(); + static std::time_t to_time_t(const StandardUserSystemClock::time_point &t); + static time_point from_time_t(std::time_t t); + public: + static Result GetCurrentTime(PosixTime *out); + /* TODO: static Result GetSystemClockContext(SystemClockContext *out); */ + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_standard_steady_clock.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_standard_steady_clock.hpp new file mode 100644 index 00000000..85660115 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_standard_steady_clock.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/time/time_common.hpp> +#include <stratosphere/time/time_steady_clock_time_point.hpp> + +namespace ams::time { + + class StandardSteadyClock { + public: + using rep = s64; + using period = std::ratio<1>; + using duration = std::chrono::duration<rep, period>; + using time_point = std::chrono::time_point<StandardSteadyClock>; + static constexpr bool is_steady = true; + public: + static time_point now(); + public: + static Result GetCurrentTimePoint(SteadyClockTimePoint *out); + }; + + Result GetStandardSteadyClockCurrentTimePoint(SteadyClockTimePoint *out); + TimeSpan GetStandardSteadyClockInternalOffset(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_standard_user_system_clock.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_standard_user_system_clock.hpp new file mode 100644 index 00000000..b287e6e0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_standard_user_system_clock.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/time/time_common.hpp> +#include <stratosphere/time/time_system_clock_common.hpp> +#include <stratosphere/time/time_posix_time.hpp> + +namespace ams::time { + + class StandardUserSystemClock { + public: + using rep = SystemClockTraits::rep; + using period = SystemClockTraits::period; + using duration = SystemClockTraits::duration; + using time_point = SystemClockTraits::time_point; + static constexpr bool is_steady = false; + public: + static time_point now(); + static std::time_t to_time_t(const StandardUserSystemClock::time_point &t); + static time_point from_time_t(std::time_t t); + public: + static Result GetCurrentTime(PosixTime *out); + /* TODO: static Result GetSystemClockContext(SystemClockContext *out); */ + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_steady_clock_time_point.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_steady_clock_time_point.hpp new file mode 100644 index 00000000..fc5f7678 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_steady_clock_time_point.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/time/time_common.hpp> + +namespace ams::time { + + struct SteadyClockTimePoint { + s64 value; + SourceId source_id; + + constexpr SteadyClockTimePoint &operator+=(const TimeSpan &ts) { this->value += ts.GetSeconds(); return *this; } + constexpr SteadyClockTimePoint &operator-=(const TimeSpan &ts) { this->value -= ts.GetSeconds(); return *this; } + + constexpr friend SteadyClockTimePoint operator+(const SteadyClockTimePoint &lhs, const TimeSpan &rhs) { return { .value = lhs.value + rhs.GetSeconds(), .source_id = lhs.source_id }; } + constexpr friend SteadyClockTimePoint operator-(const SteadyClockTimePoint &lhs, const TimeSpan &rhs) { return { .value = lhs.value - rhs.GetSeconds(), .source_id = lhs.source_id }; } + + constexpr friend bool operator==(const SteadyClockTimePoint &lhs, const SteadyClockTimePoint &rhs) { return lhs.value == rhs.value && lhs.source_id == rhs.source_id; } + constexpr friend bool operator!=(const SteadyClockTimePoint &lhs, const SteadyClockTimePoint &rhs) { return !(lhs == rhs); } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_system_clock_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_system_clock_common.hpp new file mode 100644 index 00000000..7f0732f6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_system_clock_common.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/time/time_common.hpp> + +namespace ams::time { + + class SystemClockTraits { + public: + using rep = s64; + using period = std::ratio<1>; + using duration = std::chrono::duration<rep, period>; + using time_point = std::chrono::time_point<SystemClockTraits>; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_timezone_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_timezone_api.hpp new file mode 100644 index 00000000..24972dc1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/time/time_timezone_api.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/time/time_calendar_time.hpp> +#include <stratosphere/time/time_posix_time.hpp> + +namespace ams::time { + + CalendarTime ToCalendarTimeInUtc(const PosixTime &posix_time); + PosixTime ToPosixTimeFromUtc(const CalendarTime &calendar_time); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc.hpp new file mode 100644 index 00000000..02c561b2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <stratosphere/tipc/tipc_service_object.hpp> +#include <stratosphere/tipc/tipc_allocators.hpp> + +#include <stratosphere/tipc/impl/tipc_impl_command_serialization.hpp> +#include <stratosphere/tipc/impl/tipc_autogen_interface_macros.hpp> + +#include <stratosphere/tipc/tipc_object_manager.hpp> +#include <stratosphere/tipc/tipc_server_manager.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_autogen_interface_macros.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_autogen_interface_macros.hpp new file mode 100644 index 00000000..b9a8dceb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_autogen_interface_macros.hpp @@ -0,0 +1,198 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/tipc/tipc_message_types.hpp> +#include <stratosphere/tipc/tipc_service_object_base.hpp> +#include <stratosphere/tipc/impl/tipc_impl_command_serialization.hpp> + +namespace ams::tipc::impl { + + template<typename T> + concept HasDefaultServiceCommandProcessor = requires (T &t, const MessageBuffer &message_buffer) { + { t.ProcessDefaultServiceCommand(message_buffer) } -> std::same_as<Result>; + }; + + struct SyncFunctionTraits { + public: + template<typename R, typename C, typename... A> + static std::tuple<A...> GetArgsImpl(R(C::*)(A...)); + }; + + template<auto F> + using SyncFunctionArgsType = decltype(SyncFunctionTraits::GetArgsImpl(F)); + + #define AMS_TIPC_IMPL_DEFINE_SYNC_METHOD_HOLDER(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + struct NAME##ArgumentsFunctionHolder { RETURN f ARGS; }; + + #define AMS_TIPC_IMPL_EXTRACT_SYNC_METHOD_ARGUMENTS(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + using NAME##ArgumentsType = ::ams::tipc::impl::SyncFunctionArgsType<&NAME##ArgumentsFunctionHolder::f>; + + #define AMS_TIPC_IMPL_GET_MAXIMUM_REQUEST_SIZE(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + , ::ams::tipc::impl::CommandMetaInfo<CMD_ID + 0x10, NAME##ArgumentsType>::InMessageTotalSize + + #define AMS_TIPC_IMPL_GET_MAXIMUM_RESPONSE_SIZE(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + , ::ams::tipc::impl::CommandMetaInfo<CMD_ID + 0x10, NAME##ArgumentsType>::OutMessageTotalSize + + #define AMS_TIPC_IMPL_DEFINE_INTERFACE(BASECLASS, CLASSNAME, CMD_MACRO) \ + class CLASSNAME : public BASECLASS { \ + private: \ + CMD_MACRO(CLASSNAME, AMS_TIPC_IMPL_DEFINE_SYNC_METHOD_HOLDER) \ + public: \ + CMD_MACRO(CLASSNAME, AMS_TIPC_IMPL_EXTRACT_SYNC_METHOD_ARGUMENTS) \ + public: \ + static constexpr size_t MaximumRequestSize = std::max<size_t>({ \ + 0 \ + CMD_MACRO(CLASSNAME, AMS_TIPC_IMPL_GET_MAXIMUM_REQUEST_SIZE) \ + }); \ + \ + static constexpr size_t MaximumResponseSize = std::max<size_t>({ \ + 0 \ + CMD_MACRO(CLASSNAME, AMS_TIPC_IMPL_GET_MAXIMUM_RESPONSE_SIZE) \ + }); \ + }; + + #define AMS_TIPC_IMPL_DEFINE_CONCEPT_HELPERS(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + template<typename T, typename... Args> \ + concept Is##CLASSNAME##__##NAME##Impl = requires (T &t, Args &&... args) { \ + { t.NAME(std::forward<Args>(args)...) } -> std::same_as<RETURN>; \ + }; \ + \ + template<typename T, typename A> \ + struct Is##CLASSNAME##__##NAME##Holder : std::false_type{}; \ + \ + template<typename T, typename... Args> requires std::same_as<std::tuple<Args...>, CLASSNAME::NAME##ArgumentsType> \ + struct Is##CLASSNAME##__##NAME##Holder<T, std::tuple<Args...>> : std::bool_constant<Is##CLASSNAME##__##NAME##Impl<T, Args...>>{}; \ + \ + template<typename T> \ + static constexpr inline bool Is##CLASSNAME##__##NAME = Is##CLASSNAME##__##NAME##Holder<T, CLASSNAME::NAME##ArgumentsType>::value; + + #define AMS_TIPC_IMPL_CHECK_CONCEPT_HELPER(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + Is##CLASSNAME##__##NAME<T> && + + #define AMS_TIPC_IMPL_DEFINE_CONCEPT(CLASSNAME, CMD_MACRO) \ + CMD_MACRO(CLASSNAME, AMS_TIPC_IMPL_DEFINE_CONCEPT_HELPERS) \ + \ + template<typename T> \ + concept Is##CLASSNAME = CMD_MACRO(CLASSNAME, AMS_TIPC_IMPL_CHECK_CONCEPT_HELPER) true; + + #define AMS_TIPC_IMPL_PROCESS_METHOD_REQUEST(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + else if (constexpr u16 TipcCommandId = CMD_ID + 0x10; tag == TipcCommandId) { \ + R_RETURN((this->ProcessMethodById<TipcCommandId, ImplType>(impl, message_buffer, fw_ver))); \ + } + + #define AMS_TIPC_IMPL_PROCESS_METHOD_REQUEST_BY_ID(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + if constexpr (constexpr u16 TipcCommandId = CMD_ID + 0x10; CommandId == TipcCommandId) { \ + constexpr bool MinValid = VERSION_MIN == hos::Version_Min; \ + constexpr bool MaxValid = VERSION_MAX == hos::Version_Max; \ + if ((MinValid || VERSION_MIN <= fw_ver) && (MaxValid || fw_ver <= VERSION_MAX)) { \ + R_RETURN((::ams::tipc::impl::InvokeServiceCommandImpl<TipcCommandId, &ImplType::NAME, ImplType>(impl, message_buffer))); \ + } \ + } + + #define AMS_TIPC_IMPL_IS_FIRMWARE_VERSION_ALWAYS_VALID(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + { \ + constexpr bool MinValid = VERSION_MIN == hos::Version_Min; \ + constexpr bool MaxValid = VERSION_MAX == hos::Version_Max; \ + if (!MinValid || !MaxValid) { \ + return false; \ + } \ + } + + + #define AMS_TIPC_DEFINE_INTERFACE_WITH_DEFAULT_BASE(NAMESPACE, INTERFACE, BASE, CMD_MACRO) \ + namespace NAMESPACE { \ + \ + AMS_TIPC_IMPL_DEFINE_INTERFACE(BASE, INTERFACE, CMD_MACRO) \ + AMS_TIPC_IMPL_DEFINE_CONCEPT(INTERFACE, CMD_MACRO) \ + \ + } \ + \ + namespace ams::tipc::impl { \ + \ + template<typename Base, typename ImplHolder, typename ImplGetter, typename Root> \ + class ImplTemplateBaseT<::NAMESPACE::INTERFACE, Base, ImplHolder, ImplGetter, Root> : public Base, public ImplHolder { \ + public: \ + template<typename... Args> \ + constexpr explicit ImplTemplateBaseT(Args &&...args) : ImplHolder(std::forward<Args>(args)...) { /* ... */ } \ + private: \ + template<typename ImplType> \ + ALWAYS_INLINE Result ProcessDefaultMethod(ImplType *impl, const MessageBuffer &message_buffer) const { \ + /* Handle a default command. */ \ + if constexpr (HasDefaultServiceCommandProcessor<ImplType>) { \ + R_RETURN(impl->ProcessDefaultServiceCommand(message_buffer)); \ + } else { \ + R_THROW(tipc::ResultInvalidMethod()); \ + } \ + } \ + \ + template<u16 CommandId, typename ImplType> \ + ALWAYS_INLINE Result ProcessMethodById(ImplType *impl, const MessageBuffer &message_buffer, hos::Version fw_ver) const { \ + CMD_MACRO(ImplType, AMS_TIPC_IMPL_PROCESS_METHOD_REQUEST_BY_ID) \ + \ + R_RETURN(this->ProcessDefaultMethod<ImplType>(impl, message_buffer)); \ + } \ + \ + static consteval bool IsFirmwareVersionAlwaysValid() { \ + CMD_MACRO(ImplType, AMS_TIPC_IMPL_IS_FIRMWARE_VERSION_ALWAYS_VALID); \ + return true; \ + } \ + public: \ + virtual Result ProcessRequest() override { \ + /* Get the implementation object. */ \ + auto * const impl = ImplGetter::GetImplPointer(static_cast<ImplHolder *>(this)); \ + \ + /* Get the implementation type. */ \ + using ImplType = typename std::remove_reference<decltype(*impl)>::type; \ + static_assert(::NAMESPACE::Is##INTERFACE<ImplType>); \ + \ + /* Get accessor to the message buffer. */ \ + const MessageBuffer message_buffer(tipc::GetMessageBuffer()); \ + \ + /* Get decision variables. */ \ + const auto tag = MessageBuffer::MessageHeader(message_buffer).GetTag(); \ + const auto fw_ver = IsFirmwareVersionAlwaysValid() ? hos::Version_Current : hos::GetVersion(); \ + \ + /* Process against the command ids. */ \ + if (false) { } \ + CMD_MACRO(ImplType, AMS_TIPC_IMPL_PROCESS_METHOD_REQUEST) \ + else { \ + R_RETURN(this->ProcessDefaultMethod<ImplType>(impl, message_buffer)); \ + } \ + } \ + }; \ + \ + } + + + #define AMS_TIPC_DEFINE_INTERFACE(NAMESPACE, INTERFACE, CMD_MACRO) \ + AMS_TIPC_DEFINE_INTERFACE_WITH_DEFAULT_BASE(NAMESPACE, INTERFACE, ::ams::tipc::ServiceObjectBase, CMD_MACRO) + + #define AMS_TIPC_METHOD_INFO_7(CLASSNAME, HANDLER, CMD_ID, RETURN, NAME, ARGS, ARGNAMES) \ + HANDLER(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, hos::Version_Min, hos::Version_Max) + + #define AMS_TIPC_METHOD_INFO_8(CLASSNAME, HANDLER, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN) \ + HANDLER(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, hos::Version_Max) + + #define AMS_TIPC_METHOD_INFO_9(CLASSNAME, HANDLER, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) \ + HANDLER(CLASSNAME, CMD_ID, RETURN, NAME, ARGS, ARGNAMES, VERSION_MIN, VERSION_MAX) + + #define AMS_TIPC_METHOD_INFO_X(_, _0, _1, _2, _3, _4, _5, _6, _7, _8, FUNC, ...) FUNC + + #define AMS_TIPC_METHOD_INFO(...) \ + AMS_TIPC_METHOD_INFO_X(, ## __VA_ARGS__, AMS_TIPC_METHOD_INFO_9(__VA_ARGS__), AMS_TIPC_METHOD_INFO_8(__VA_ARGS__), AMS_TIPC_METHOD_INFO_7(__VA_ARGS__)) + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_command_serialization.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_command_serialization.hpp new file mode 100644 index 00000000..d3727780 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_command_serialization.hpp @@ -0,0 +1,738 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/tipc/tipc_common.hpp> +#include <stratosphere/tipc/tipc_out.hpp> +#include <stratosphere/tipc/tipc_buffers.hpp> +#include <stratosphere/tipc/tipc_handles.hpp> + +namespace ams::tipc { + + struct ClientProcessId { + os::ProcessId value; + }; + static_assert(std::is_trivial<ClientProcessId>::value && sizeof(ClientProcessId) == sizeof(os::ProcessId), "ClientProcessId"); + +} + +#if defined(ATMOSPHERE_OS_HORIZON) +namespace ams::tipc::impl { + + /* Machinery for filtering type lists. */ + template<class, class> + struct TupleCat; + + template<class... First, class... Second> + struct TupleCat<std::tuple<First...>, std::tuple<Second...>> { + using type = std::tuple<First..., Second...>; + }; + + template<template <typename> typename Cond> + struct TupleFilter { + private: + template<typename, typename> + struct ImplType; + + template<typename Res> + struct ImplType<Res, std::tuple<>> { + using type = Res; + }; + + template<typename Res, typename T, typename... Ts> + struct ImplType<Res, std::tuple<T, Ts...>> { + using type = typename std::conditional<Cond<T>::value, + typename ImplType<typename TupleCat<Res, std::tuple<T>>::type, std::tuple<Ts...>>::type, + typename ImplType<Res, std::tuple<Ts...>>::type + >::type; + }; + public: + template<typename T> + using FilteredType = typename ImplType<std::tuple<>, T>::type; + }; + + enum class ArgumentType { + InData, + OutData, + Buffer, + InHandle, + OutHandle, + ProcessId, + }; + + template<typename T> + constexpr inline ArgumentType GetArgumentType = [] { + if constexpr (tipc::IsBuffer<T>) { + return ArgumentType::Buffer; + } else if constexpr (std::same_as<T, tipc::CopyHandle> || std::same_as<T, tipc::MoveHandle>) { + return ArgumentType::InHandle; + } else if constexpr (std::same_as<T, tipc::OutCopyHandle> || std::same_as<T, tipc::OutMoveHandle>) { + return ArgumentType::OutHandle; + } else if constexpr (std::is_base_of<tipc::impl::OutBaseTag, T>::value) { + return ArgumentType::OutData; + } else if constexpr (std::same_as<T, tipc::ClientProcessId>) { + return ArgumentType::ProcessId; + } else if constexpr (std::is_trivial<T>::value && !std::is_pointer<T>::value) { + return ArgumentType::InData; + } else if constexpr (std::is_same<T, ::ams::Result>::value) { + return ArgumentType::InData; + } else { + static_assert(!std::is_same<T, T>::value, "Invalid ArgumentType<T>"); + } + }(); + + template<typename T, ArgumentType ArgT> + struct ArgumentTypeFilter : public std::bool_constant<GetArgumentType<T> == ArgT>{}; + + template<typename T, typename U> + struct TypeEqualityFilter : public std::bool_constant<std::is_same<T, U>::value>{}; + + /* Argument type filters. */ + template<typename T> + using InDataFilter = ArgumentTypeFilter<T, ArgumentType::InData>; + + template<typename T> + using OutDataFilter = ArgumentTypeFilter<T, ArgumentType::OutData>; + + template<typename T> + using BufferFilter = ArgumentTypeFilter<T, ArgumentType::Buffer>; + + template<typename T> + using InHandleFilter = ArgumentTypeFilter<T, ArgumentType::InHandle>; + + template<typename T> + using OutHandleFilter = ArgumentTypeFilter<T, ArgumentType::OutHandle>; + + template<typename T> + using ProcessIdFilter = ArgumentTypeFilter<T, ArgumentType::ProcessId>; + + /* Handle kind filters. */ + template<typename T> + using InMoveHandleFilter = TypeEqualityFilter<T, tipc::MoveHandle>; + + template<typename T> + using InCopyHandleFilter = TypeEqualityFilter<T, tipc::CopyHandle>; + + template<typename T> + using OutMoveHandleFilter = TypeEqualityFilter<T, tipc::OutMoveHandle>; + + template<typename T> + using OutCopyHandleFilter = TypeEqualityFilter<T, tipc::OutCopyHandle>; + + template<typename> + struct BufferAttributeArrayGetter; + + template<typename ...Ts> + struct BufferAttributeArrayGetter<std::tuple<Ts...>> { + static constexpr std::array<u32, sizeof...(Ts)> value = { BufferAttributes<Ts>..., }; + }; + + template<> + struct BufferAttributeArrayGetter<std::tuple<>> { + static constexpr std::array<u32, 0> value{}; + }; + + template<auto Predicate> + struct BufferAttributeCounter { + + template<size_t Size> + static constexpr size_t GetCount(const std::array<u32, Size> &attributes_array) { + size_t count = 0; + for (size_t i = 0; i < Size; i++) { + if (Predicate(attributes_array[i])) { + count++; + } + } + return count; + } + + }; + + static constexpr size_t InBufferPredicate(const u32 attribute) { + return (attribute & SfBufferAttr_In) && !(attribute & SfBufferAttr_Out); + } + + static constexpr size_t OutBufferPredicate(const u32 attribute) { + return !(attribute & SfBufferAttr_In) && (attribute & SfBufferAttr_Out); + } + + template<typename> + struct RawDataOffsetCalculator; + + template<typename... Ts> + struct RawDataOffsetCalculator<std::tuple<Ts...>> { + private: + template<typename T> + struct LayoutHelper { + static_assert(GetArgumentType<T> == ArgumentType::InData, "LayoutHelper InData"); + static constexpr size_t Alignment = alignof(T); + static constexpr size_t Size = sizeof(T); + }; + template<typename T> + struct LayoutHelper<Out<T>> { + static_assert(GetArgumentType<T> == ArgumentType::InData, "LayoutHelper OutData"); + static constexpr size_t Alignment = alignof(T); + static constexpr size_t Size = sizeof(T); + }; + public: + static constexpr std::array<size_t, sizeof...(Ts)+1> Offsets = [] { + std::array<size_t, sizeof...(Ts)+1> offsets{}; + offsets[0] = 0; + if constexpr (sizeof...(Ts) > 0) { + /* Get size, alignment for each type. */ + const std::array<size_t, sizeof...(Ts)> sizes = { LayoutHelper<Ts>::Size... }; + const std::array<size_t, sizeof...(Ts)> aligns = { LayoutHelper<Ts>::Alignment... }; + + /* We want to sort...by alignment. */ + std::array<size_t, sizeof...(Ts)> map = {}; + for (size_t i = 0; i < sizeof...(Ts); i++) { map[i] = i; } + + /* TODO: Is sorting done on parameters? */ + /* Sort?(map, aligns); */ + + /* Iterate over sorted sizes. */ + size_t cur_offset = 0; + for (size_t i : map) { + cur_offset = util::AlignUp(cur_offset, aligns[i]); + offsets[i] = cur_offset; + cur_offset += sizes[i]; + } + offsets[sizeof...(Ts)] = cur_offset; + } + return offsets; + }(); + + }; + + struct ArgumentSerializationInfo { + /* Type used to select from below fields. */ + ArgumentType arg_type; + + /* Raw data indexing. */ + size_t in_raw_data_index; + size_t out_raw_data_index; + + /* Buffer indexing. */ + size_t buffer_index; + bool is_send_buffer; + size_t send_map_alias_index; + size_t recv_map_alias_index; + + /* Handle indexing. */ + size_t in_move_handle_index; + size_t in_copy_handle_index; + size_t out_move_handle_index; + size_t out_copy_handle_index; + }; + + template<u16 _CommandId, typename ArgumentsTuple> + struct CommandMetaInfo; + + template<u16 _CommandId, typename... Arguments> + struct CommandMetaInfo<_CommandId, std::tuple<Arguments...>> { + public: + static constexpr u16 CommandId = _CommandId; + + using ArgsType = std::tuple<typename std::decay<Arguments>::type...>; + + using InDatas = TupleFilter<InDataFilter>::FilteredType<ArgsType>; + using OutDatas = TupleFilter<OutDataFilter>::FilteredType<ArgsType>; + using Buffers = TupleFilter<BufferFilter>::FilteredType<ArgsType>; + using InHandles = TupleFilter<InHandleFilter>::FilteredType<ArgsType>; + using OutHandles = TupleFilter<OutHandleFilter>::FilteredType<ArgsType>; + using ProcessIds = TupleFilter<ProcessIdFilter>::FilteredType<ArgsType>; + + static constexpr size_t NumInDatas = std::tuple_size<InDatas>::value; + static constexpr size_t NumOutDatas = std::tuple_size<OutDatas>::value; + static constexpr size_t NumBuffers = std::tuple_size<Buffers>::value; + static constexpr size_t NumInHandles = std::tuple_size<InHandles>::value; + static constexpr size_t NumOutHandles = std::tuple_size<OutHandles>::value; + + static constexpr size_t NumProcessIds = std::tuple_size<ProcessIds>::value; + static constexpr bool HasProcessId = NumProcessIds >= 1; + + static_assert(NumBuffers <= 8, "Methods must take in <= 8 Buffers"); + static_assert(NumInHandles <= 8, "Methods must take in <= 8 Handles"); + static_assert(NumOutHandles <= 8, "Methods must output <= 8 Handles"); + + static_assert(NumInHandles == 0, "In Handles not yet implemented!"); + + /* Buffer marshalling. */ + static constexpr std::array<u32, NumBuffers> BufferAttributes = BufferAttributeArrayGetter<Buffers>::value; + static constexpr size_t NumInBuffers = BufferAttributeCounter<InBufferPredicate>::GetCount(BufferAttributes); + static constexpr size_t NumOutBuffers = BufferAttributeCounter<OutBufferPredicate>::GetCount(BufferAttributes); + + /* In/Out data marshalling. */ + static constexpr std::array<size_t, NumInDatas+1> InDataOffsets = RawDataOffsetCalculator<InDatas>::Offsets; + static constexpr size_t InDataSize = util::AlignUp(InDataOffsets[NumInDatas], alignof(u32)); + + static constexpr std::array<size_t, NumOutDatas+1> OutDataOffsets = RawDataOffsetCalculator<OutDatas>::Offsets; + static constexpr size_t OutDataSize = util::AlignUp(OutDataOffsets[NumOutDatas], alignof(u32)); + + /* Useful because reasons. */ + static constexpr size_t OutDataAlign = []() -> size_t { + if constexpr (std::tuple_size<OutDatas>::value) { + return alignof(typename std::tuple_element<0, OutDatas>::type); + } + return 0; + }(); + + /* Handle marshalling. */ + static constexpr size_t NumInMoveHandles = std::tuple_size<TupleFilter<InMoveHandleFilter>::FilteredType<InHandles>>::value; + static constexpr size_t NumInCopyHandles = std::tuple_size<TupleFilter<InCopyHandleFilter>::FilteredType<InHandles>>::value; + + static constexpr size_t NumOutMoveHandles = std::tuple_size<TupleFilter<OutMoveHandleFilter>::FilteredType<OutHandles>>::value; + static constexpr size_t NumOutCopyHandles = std::tuple_size<TupleFilter<OutCopyHandleFilter>::FilteredType<OutHandles>>::value; + + static_assert(NumInMoveHandles + NumInCopyHandles == NumInHandles, "NumInMoveHandles + NumInCopyHandles == NumInHandles"); + static_assert(NumOutMoveHandles + NumOutCopyHandles == NumOutHandles, "NumOutMoveHandles + NumOutCopyHandles == NumOutHandles"); + + /* tipc-specific accessors. */ + static constexpr bool HasInSpecialHeader = HasProcessId || NumInHandles > 0; + + static constexpr MessageBuffer::MessageHeader InMessageHeader{CommandId, HasInSpecialHeader, 0, NumInBuffers, NumOutBuffers, 0, InDataSize / sizeof(u32), 0}; + static constexpr MessageBuffer::SpecialHeader InSpecialHeader{HasProcessId, NumInCopyHandles, NumInMoveHandles, HasInSpecialHeader}; + + static constexpr auto InMessageProcessIdIndex = MessageBuffer::GetSpecialDataIndex(InMessageHeader, InSpecialHeader); + static constexpr auto InMessageHandleIndex = MessageBuffer::GetSpecialDataIndex(InMessageHeader, InSpecialHeader) + (HasProcessId ? sizeof(u64) / sizeof(u32) : 0); + static constexpr auto InMessageBufferIndex = MessageBuffer::GetMapAliasDescriptorIndex(InMessageHeader, InSpecialHeader); + static constexpr auto InMessageRawDataIndex = MessageBuffer::GetRawDataIndex(InMessageHeader, InSpecialHeader); + + static constexpr bool HasOutSpecialHeader = NumOutHandles > 0; + + static constexpr MessageBuffer::MessageHeader OutMessageHeader{CommandId, HasOutSpecialHeader, 0, 0, 0, 0, (OutDataSize / sizeof(u32)) + 1, 0}; + static constexpr MessageBuffer::SpecialHeader OutSpecialHeader{false, NumOutCopyHandles, NumOutMoveHandles, HasOutSpecialHeader}; + + static constexpr auto OutMessageHandleIndex = MessageBuffer::GetSpecialDataIndex(OutMessageHeader, OutSpecialHeader); + static constexpr auto OutMessageResultIndex = MessageBuffer::GetRawDataIndex(OutMessageHeader, OutSpecialHeader); + static constexpr auto OutMessageRawDataIndex = OutMessageResultIndex + 1; + + static constexpr size_t InMessageTotalSize = (InMessageRawDataIndex * sizeof(u32)) + InDataSize; + static constexpr size_t OutMessageTotalSize = (OutMessageRawDataIndex * sizeof(u32)) + OutDataSize; + + /* Construction of argument serialization structs. */ + private: + template<typename> + struct ArgumentSerializationInfoConstructor; + + template<typename ...Ts> + struct ArgumentSerializationInfoConstructor<std::tuple<Ts...>> { + + template<typename T> + static constexpr ArgumentSerializationInfo ProcessUpdate(ArgumentSerializationInfo ¤t_info) { + /* Save a copy of the current state to return. */ + ArgumentSerializationInfo returned_info = current_info; + + constexpr auto arg_type = GetArgumentType<T>; + returned_info.arg_type = arg_type; + if constexpr (arg_type == ArgumentType::InData) { + /* New rawdata, so increment index. */ + current_info.in_raw_data_index++; + } else if constexpr (arg_type == ArgumentType::OutData) { + /* New rawdata, so increment index. */ + current_info.out_raw_data_index++; + } else if constexpr (arg_type == ArgumentType::InHandle) { + /* New InHandle, increment the appropriate index. */ + if constexpr (std::same_as<T, tipc::MoveHandle>) { + current_info.in_move_handle_index++; + } else if constexpr (std::same_as<T, tipc::CopyHandle>) { + current_info.in_copy_handle_index++; + } else { + static_assert(!std::is_same<T, T>::value, "Invalid InHandle kind"); + } + } else if constexpr (arg_type == ArgumentType::OutHandle) { + /* New OutHandle, increment the appropriate index. */ + if constexpr (std::same_as<T, tipc::OutMoveHandle>) { + current_info.out_move_handle_index++; + } else if constexpr (std::same_as<T, tipc::OutCopyHandle>) { + current_info.out_copy_handle_index++; + } else { + static_assert(!std::is_same<T, T>::value, "Invalid OutHandle kind"); + } + } else if constexpr (arg_type == ArgumentType::Buffer) { + /* New Buffer, increment the appropriate index. */ + const auto attributes = BufferAttributes[current_info.buffer_index++]; + + if (attributes & SfBufferAttr_In) { + returned_info.is_send_buffer = true; + current_info.send_map_alias_index++; + } else if (attributes & SfBufferAttr_Out) { + returned_info.is_send_buffer = false; + current_info.recv_map_alias_index++; + } + } else if constexpr (arg_type == ArgumentType::ProcessId) { + /* Nothing needs to be done to track process ids. */ + } else { + static_assert(!std::is_same<T, T>::value, "Invalid ArgumentType<T>"); + } + + return returned_info; + } + + static constexpr std::array<ArgumentSerializationInfo, sizeof...(Ts)> ArgumentSerializationInfos = [] { + ArgumentSerializationInfo current_info = {}; + + return std::array<ArgumentSerializationInfo, sizeof...(Ts)>{ ProcessUpdate<Ts>(current_info)... }; + }(); + + }; + public: + static constexpr std::array<ArgumentSerializationInfo, std::tuple_size<ArgsType>::value> ArgumentSerializationInfos = ArgumentSerializationInfoConstructor<ArgsType>::ArgumentSerializationInfos; + }; + + template<size_t _Size, size_t _Align, size_t OutIndex> + class OutRawHolder { + public: + static constexpr size_t Size = _Size; + static constexpr size_t Align = _Align ? _Align : alignof(u8); + private: + alignas(Align) u8 data[Size]; + public: + constexpr ALWAYS_INLINE OutRawHolder() : data() { /* ... */ } + + template<size_t Offset, size_t TypeSize> + constexpr ALWAYS_INLINE uintptr_t GetAddress() const { + static_assert(Offset <= Size, "Offset <= Size"); + static_assert(TypeSize <= Size, "TypeSize <= Size"); + static_assert(Offset + TypeSize <= Size, "Offset + TypeSize <= Size"); + return reinterpret_cast<uintptr_t>(data + Offset); + } + + constexpr ALWAYS_INLINE void CopyTo(const MessageBuffer &buffer) const { + if constexpr (Size > 0) { + buffer.SetRawArray(OutIndex, data, Size); + } + } + }; + + template<size_t _NumMove, size_t _NumCopy, size_t OutIndex> + class OutHandleHolder { + public: + static constexpr size_t NumMove = _NumMove; + static constexpr size_t NumCopy = _NumCopy; + private: + tipc::NativeHandle move_handles[NumMove]; + tipc::NativeHandle copy_handles[NumCopy]; + public: + ALWAYS_INLINE OutHandleHolder() { /* ... */ } + + template<size_t Index> + constexpr ALWAYS_INLINE tipc::NativeHandle *GetMoveHandlePointer() { + static_assert(Index < NumMove, "Index < NumMove"); + return move_handles + Index; + } + + template<size_t Index> + constexpr ALWAYS_INLINE tipc::NativeHandle *GetCopyHandlePointer() { + static_assert(Index < NumCopy, "Index < NumCopy"); + return copy_handles + Index; + } + + ALWAYS_INLINE void CopyTo(const MessageBuffer &buffer) const { + #define _TIPC_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE(n) do { if constexpr (NumCopy > n) { buffer.SetHandle(OutIndex + n, copy_handles[n]); } } while (0) + _TIPC_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE(0); + _TIPC_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE(1); + _TIPC_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE(2); + _TIPC_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE(3); + _TIPC_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE(4); + _TIPC_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE(5); + _TIPC_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE(6); + _TIPC_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE(7); + #undef _TIPC_OUT_HANDLE_HOLDER_WRITE_COPY_HANDLE + #define _TIPC_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE(n) do { if constexpr (NumMove > n) { buffer.SetHandle(OutIndex + NumCopy + n, move_handles[n]); } } while (0) + _TIPC_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE(0); + _TIPC_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE(1); + _TIPC_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE(2); + _TIPC_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE(3); + _TIPC_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE(4); + _TIPC_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE(5); + _TIPC_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE(6); + _TIPC_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE(7); + #undef _TIPC_OUT_HANDLE_HOLDER_WRITE_MOVE_HANDLE + } + }; + + template<size_t... Ix> + struct PrintIndex; + + template<typename CommandMeta> + class CommandProcessor { + public: + /* Useful defines. */ + using ArgsType = typename CommandMeta::ArgsType; + using OutRawHolderType = OutRawHolder<CommandMeta::OutDataSize, CommandMeta::OutDataAlign, CommandMeta::OutMessageRawDataIndex>; + using OutHandleHolderType = OutHandleHolder<CommandMeta::NumOutMoveHandles, CommandMeta::NumOutCopyHandles, CommandMeta::OutMessageHandleIndex>; + private: + static constexpr u64 GetMessageHeaderForCheck(const MessageBuffer::MessageHeader &header) { + using Value = util::BitPack32::Field<0, BITSIZEOF(util::BitPack32)>; + + const util::BitPack32 *data = header.GetData(); + const u32 lower = data[0].Get<Value>(); + const u32 upper = data[1].Get<Value>(); + + return static_cast<u64>(lower) | (static_cast<u64>(upper) << BITSIZEOF(u32)); + } + + static constexpr u32 GetSpecialHeaderForCheck(const MessageBuffer::SpecialHeader &header) { + using Value = util::BitPack32::Field<0, BITSIZEOF(util::BitPack32)>; + + return header.GetHeader()->Get<Value>(); + } + + /* Argument deserialization. */ + template<size_t Index, typename T = typename std::tuple_element<Index, ArgsType>::type> + static ALWAYS_INLINE typename std::tuple_element<Index, ArgsType>::type DeserializeArgumentImpl(const MessageBuffer &message_buffer, const OutRawHolderType &out_raw_holder, OutHandleHolderType &out_handles_holder) { + constexpr auto Info = CommandMeta::ArgumentSerializationInfos[Index]; + if constexpr (Info.arg_type == ArgumentType::InData) { + /* New in rawdata. */ + constexpr size_t Offset = CommandMeta::InDataOffsets[Info.in_raw_data_index]; + constexpr size_t RawIndex = Offset / sizeof(u32); + static_assert(Offset == RawIndex * sizeof(u32)); /* TODO: Do unaligned data exist? */ + + if constexpr (!std::same_as<T, bool>) { + return message_buffer.GetRaw<T>(CommandMeta::InMessageRawDataIndex + RawIndex); + } else { + return message_buffer.GetRaw<u8>(CommandMeta::InMessageRawDataIndex + RawIndex) & 1; + } + } else if constexpr (Info.arg_type == ArgumentType::OutData) { + /* New out rawdata. */ + constexpr size_t Offset = CommandMeta::OutDataOffsets[Info.out_raw_data_index]; + return T(out_raw_holder.template GetAddress<Offset, T::TypeSize>()); + } else if constexpr (Info.arg_type == ArgumentType::InHandle) { + /* New InHandle. */ + if constexpr (std::same_as<T, tipc::MoveHandle>) { + constexpr auto HandleIndex = CommandMeta::InMessageHandleIndex + CommandMeta::NumInCopyHandles + Info.in_move_handle_index; + return T(message_buffer.GetHandle(HandleIndex)); + } else if constexpr (std::same_as<T, tipc::CopyHandle>) { + constexpr auto HandleIndex = CommandMeta::InMessageHandleIndex + Info.in_copy_handle_index; + return T(message_buffer.GetHandle(HandleIndex)); + } else { + static_assert(!std::is_same<T, T>::value, "Invalid InHandle kind"); + } + } else if constexpr (Info.arg_type == ArgumentType::OutHandle) { + /* New OutHandle. */ + if constexpr (std::same_as<T, tipc::OutMoveHandle>) { + tipc::NativeHandle * const ptr = out_handles_holder.template GetMoveHandlePointer<Info.out_move_handle_index>(); + *ptr = tipc::InvalidNativeHandle; + return T(ptr); + } else if constexpr (std::same_as<T, tipc::OutCopyHandle>) { + tipc::NativeHandle * const ptr = out_handles_holder.template GetCopyHandlePointer<Info.out_copy_handle_index>(); + *ptr = tipc::InvalidNativeHandle; + return T(ptr); + } else { + static_assert(!std::is_same<T, T>::value, "Invalid OutHandle kind"); + } + } else if constexpr (Info.arg_type == ArgumentType::Buffer) { + /* NOTE: There are currently no tipc commands which use buffers-with-attributes */ + /* If these are added (e.g., NonSecure buffers), implement checking here? */ + constexpr size_t MapAliasDescriptorSize = MessageBuffer::MapAliasDescriptor::GetDataSize(); + + if constexpr (Info.is_send_buffer) { + /* Input send buffer. */ + constexpr auto BufferIndex = CommandMeta::InMessageBufferIndex + (Info.send_map_alias_index * MapAliasDescriptorSize / sizeof(util::BitPack32)); + + const MessageBuffer::MapAliasDescriptor descriptor(message_buffer, BufferIndex); + return T(descriptor.GetAddress(), descriptor.GetSize()); + } else { + /* Input receive buffer. */ + constexpr auto BufferIndex = CommandMeta::InMessageBufferIndex + ((CommandMeta::NumInBuffers + Info.recv_map_alias_index) * MapAliasDescriptorSize / sizeof(util::BitPack32)); + + const MessageBuffer::MapAliasDescriptor descriptor(message_buffer, BufferIndex); + return T(descriptor.GetAddress(), descriptor.GetSize()); + } + } else if constexpr (Info.arg_type == ArgumentType::ProcessId) { + return T{ os::ProcessId{ message_buffer.GetProcessId(CommandMeta::InMessageProcessIdIndex) } }; + } else { + static_assert(!std::is_same<T, T>::value, "Invalid ArgumentType<T>"); + } + } + public: + static ALWAYS_INLINE Result ValidateCommandFormat(const MessageBuffer &message_buffer) { + /* Validate the message header. */ + constexpr auto ExpectedMessageHeader = GetMessageHeaderForCheck(CommandMeta::InMessageHeader); + R_UNLESS(message_buffer.Get64(0) == ExpectedMessageHeader, tipc::ResultInvalidMessageFormat()); + + /* Validate the special header. */ + if constexpr (CommandMeta::HasInSpecialHeader) { + constexpr auto ExpectedSpecialHeader = GetSpecialHeaderForCheck(CommandMeta::InSpecialHeader); + constexpr auto SpecialHeaderIndex = MessageBuffer::MessageHeader::GetDataSize() / sizeof(util::BitPack32); + R_UNLESS(message_buffer.Get32(SpecialHeaderIndex) == ExpectedSpecialHeader, tipc::ResultInvalidMessageFormat()); + } + + R_SUCCEED(); + } + + template<size_t Ix> + static ALWAYS_INLINE auto DeserializeArgument(const MessageBuffer &message_buffer, const OutRawHolderType &out_raw_holder, OutHandleHolderType &out_handles_holder) { + return DeserializeArgumentImpl<Ix>(message_buffer, out_raw_holder, out_handles_holder); + } + + static ALWAYS_INLINE void SerializeResults(const MessageBuffer &message_buffer, const Result &result, const OutRawHolderType &out_raw_holder, const OutHandleHolderType &out_handles_holder) { + /* Set output headers. */ + message_buffer.Set(CommandMeta::OutMessageHeader); + if constexpr (CommandMeta::HasOutSpecialHeader) { + message_buffer.Set(CommandMeta::OutSpecialHeader); + } + + /* Set output handles. */ + out_handles_holder.CopyTo(message_buffer); + + /* Set output result. */ + message_buffer.Set(CommandMeta::OutMessageResultIndex, result.GetValue()); + + /* Set output data. */ + out_raw_holder.CopyTo(message_buffer); + } + }; + + struct FunctionTraits { + public: + template<typename R, typename C, typename... A> + static std::tuple<A...> GetArgumentsImpl(R(C::*)(A...)); + + template<typename R, typename C, typename... A> + static R GetReturnImpl(R(C::*)(A...)); + }; + + template<u16 _CommmandId, auto ServiceCommandImpl, typename ClassType> + constexpr ALWAYS_INLINE Result InvokeServiceCommandImpl(ClassType *object, const MessageBuffer &message_buffer) { + using Return = decltype(FunctionTraits::GetReturnImpl(ServiceCommandImpl)); + using TrueArgumentsTuple = decltype(FunctionTraits::GetArgumentsImpl(ServiceCommandImpl)); + + using CommandMeta = CommandMetaInfo<_CommmandId, TrueArgumentsTuple>; + using Processor = CommandProcessor<CommandMeta>; + /* TODO: ValidateClassType is valid? */ + + constexpr bool ReturnsResult = std::is_same<Return, Result>::value; + constexpr bool ReturnsVoid = std::is_same<Return, void>::value; + static_assert(ReturnsResult || ReturnsVoid, "Service Commands must return Result or void."); + + /* Validate that the command is valid. */ + R_TRY(Processor::ValidateCommandFormat(message_buffer)); + + /* Deserialize arguments. */ + typename Processor::OutRawHolderType out_raw_holder; + typename Processor::OutHandleHolderType out_handles_holder; + const Result command_result = [&]<size_t... Ix>(std::index_sequence<Ix...>) ALWAYS_INLINE_LAMBDA { + if constexpr (ReturnsResult) { + R_RETURN((object->*ServiceCommandImpl)(Processor::template DeserializeArgument<Ix>(message_buffer, out_raw_holder, out_handles_holder)...)); + } else { + (object->*ServiceCommandImpl)(Processor::template DeserializeArgument<Ix>(message_buffer, out_raw_holder, out_handles_holder)...); + R_SUCCEED(); + } + }(std::make_index_sequence<std::tuple_size<typename CommandMeta::ArgsType>::value>()); + + /* Serialize output. */ + Processor::SerializeResults(message_buffer, command_result, out_raw_holder, out_handles_holder); + + R_SUCCEED(); + } + +} +#elif defined(ATMOSPHERE_OS_WINDOWS) +namespace ams::tipc::impl { + + template<u16 _CommandId, typename ArgumentsTuple> + struct CommandMetaInfo; + + template<u16 _CommandId, typename... Arguments> + struct CommandMetaInfo<_CommandId, std::tuple<Arguments...>> { + public: + static constexpr size_t InMessageTotalSize = 0x40; /* TODO */ + static constexpr size_t OutMessageTotalSize = 0x40; /* TODO */ + }; + + template<typename CommandMeta> + class CommandProcessor { + public: + static ALWAYS_INLINE Result ValidateCommandFormat(const MessageBuffer &) { + AMS_ABORT("TODO"); + } + }; + + template<u16 _CommmandId, auto ServiceCommandImpl, typename ClassType> + ALWAYS_INLINE Result InvokeServiceCommandImpl(ClassType *object, const MessageBuffer &message_buffer) { + /* TODO: Is some kind of emulated serialization interesting/desirable? */ + /* TIPC is generally a huge TODO. */ + AMS_UNUSED(object, message_buffer); + AMS_ABORT("TIPC serialization not currently supported on Windows."); + } + +} +#elif defined(ATMOSPHERE_OS_LINUX) +namespace ams::tipc::impl { + + template<u16 _CommandId, typename ArgumentsTuple> + struct CommandMetaInfo; + + template<u16 _CommandId, typename... Arguments> + struct CommandMetaInfo<_CommandId, std::tuple<Arguments...>> { + public: + static constexpr size_t InMessageTotalSize = 0x40; /* TODO */ + static constexpr size_t OutMessageTotalSize = 0x40; /* TODO */ + }; + + template<typename CommandMeta> + class CommandProcessor { + public: + static ALWAYS_INLINE Result ValidateCommandFormat(const MessageBuffer &) { + AMS_ABORT("TODO"); + } + }; + + template<u16 _CommmandId, auto ServiceCommandImpl, typename ClassType> + ALWAYS_INLINE Result InvokeServiceCommandImpl(ClassType *object, const MessageBuffer &message_buffer) { + /* TODO: Is some kind of emulated serialization interesting/desirable? */ + /* TIPC is generally a huge TODO. */ + AMS_UNUSED(object, message_buffer); + AMS_ABORT("TIPC serialization not currently supported on Linux."); + } + +} +#elif defined(ATMOSPHERE_OS_MACOS) +namespace ams::tipc::impl { + + template<u16 _CommandId, typename ArgumentsTuple> + struct CommandMetaInfo; + + template<u16 _CommandId, typename... Arguments> + struct CommandMetaInfo<_CommandId, std::tuple<Arguments...>> { + public: + static constexpr size_t InMessageTotalSize = 0x40; /* TODO */ + static constexpr size_t OutMessageTotalSize = 0x40; /* TODO */ + }; + + template<typename CommandMeta> + class CommandProcessor { + public: + static ALWAYS_INLINE Result ValidateCommandFormat(const MessageBuffer &) { + AMS_ABORT("TODO"); + } + }; + + template<u16 _CommmandId, auto ServiceCommandImpl, typename ClassType> + ALWAYS_INLINE Result InvokeServiceCommandImpl(ClassType *object, const MessageBuffer &message_buffer) { + /* TODO: Is some kind of emulated serialization interesting/desirable? */ + /* TIPC is generally a huge TODO. */ + AMS_UNUSED(object, message_buffer); + AMS_ABORT("TIPC serialization not currently supported on macOS."); + } + +} + +#else + #error "Unknown OS for tipc Command serialization." +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_message_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_message_api.hpp new file mode 100644 index 00000000..f8f488f7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_message_api.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) + #include <stratosphere/tipc/impl/tipc_impl_message_api.os.horizon.hpp> +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include <stratosphere/tipc/impl/tipc_impl_message_api.os.windows.hpp> +#elif defined(ATMOSPHERE_OS_LINUX) + #include <stratosphere/tipc/impl/tipc_impl_message_api.os.linux.hpp> +#elif defined(ATMOSPHERE_OS_MACOS) + #include <stratosphere/tipc/impl/tipc_impl_message_api.os.macos.hpp> +#else + #error "Unknown OS for tipc message impl" +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_message_api.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_message_api.os.horizon.hpp new file mode 100644 index 00000000..2655323b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_message_api.os.horizon.hpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/tipc/tipc_common.hpp> +#include <stratosphere/tipc/tipc_message_types.hpp> + +namespace ams::tipc::impl { + + inline void Reply(tipc::NativeHandle reply_target) { + /* Perform the reply. */ + s32 dummy; + R_TRY_CATCH(svc::ReplyAndReceive(std::addressof(dummy), nullptr, 0, reply_target, 0)) { + R_CATCH(svc::ResultTimedOut) { + /* Timing out is acceptable. */ + } + R_CATCH(svc::ResultSessionClosed) { + /* It's okay if we couldn't reply to a closed session. */ + } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + } + + ALWAYS_INLINE Result CloseHandle(tipc::NativeHandle handle) { + R_RETURN(svc::CloseHandle(handle)); + } + + ALWAYS_INLINE Result CreateSession(tipc::NativeHandle *out_server_session_handle, tipc::NativeHandle *out_client_session_handle, bool is_light, uintptr_t name) { + R_RETURN(svc::CreateSession(out_server_session_handle, out_client_session_handle, is_light, name)); + } + + ALWAYS_INLINE Result AcceptSession(tipc::NativeHandle *out_handle, tipc::NativeHandle port) { + R_RETURN(svc::AcceptSession(out_handle, port)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_message_api.os.linux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_message_api.os.linux.hpp new file mode 100644 index 00000000..ea6ac49f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_message_api.os.linux.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/tipc/tipc_common.hpp> +#include <stratosphere/tipc/tipc_message_types.hpp> + +namespace ams::tipc::impl { + + inline void Reply(tipc::NativeHandle reply_target) { + AMS_UNUSED(reply_target); + AMS_ABORT("TODO: tipc Linux Reply"); + } + + ALWAYS_INLINE Result CloseHandle(tipc::NativeHandle handle) { + AMS_UNUSED(handle); + AMS_ABORT("TODO: tipc Linux CloseHandle"); + } + + ALWAYS_INLINE Result CreateSession(tipc::NativeHandle *out_server_session_handle, tipc::NativeHandle *out_client_session_handle, bool is_light, uintptr_t name) { + AMS_UNUSED(out_server_session_handle, out_client_session_handle, is_light, name); + AMS_ABORT("TODO: tipc Linux CreateSession"); + } + + ALWAYS_INLINE Result AcceptSession(tipc::NativeHandle *out_handle, tipc::NativeHandle port) { + AMS_UNUSED(out_handle, port); + AMS_ABORT("TODO: tipc Linux AcceptSession"); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_message_api.os.macos.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_message_api.os.macos.hpp new file mode 100644 index 00000000..dd5d5bd7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_message_api.os.macos.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/tipc/tipc_common.hpp> +#include <stratosphere/tipc/tipc_message_types.hpp> + +namespace ams::tipc::impl { + + inline void Reply(tipc::NativeHandle reply_target) { + AMS_UNUSED(reply_target); + AMS_ABORT("TODO: tipc macOS Reply"); + } + + ALWAYS_INLINE Result CloseHandle(tipc::NativeHandle handle) { + AMS_UNUSED(handle); + AMS_ABORT("TODO: tipc macOS CloseHandle"); + } + + ALWAYS_INLINE Result CreateSession(tipc::NativeHandle *out_server_session_handle, tipc::NativeHandle *out_client_session_handle, bool is_light, uintptr_t name) { + AMS_UNUSED(out_server_session_handle, out_client_session_handle, is_light, name); + AMS_ABORT("TODO: tipc macOS CreateSession"); + } + + ALWAYS_INLINE Result AcceptSession(tipc::NativeHandle *out_handle, tipc::NativeHandle port) { + AMS_UNUSED(out_handle, port); + AMS_ABORT("TODO: tipc macOS AcceptSession"); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_message_api.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_message_api.os.windows.hpp new file mode 100644 index 00000000..a8bd1625 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_message_api.os.windows.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/tipc/tipc_common.hpp> +#include <stratosphere/tipc/tipc_message_types.hpp> + +namespace ams::tipc::impl { + + inline void Reply(tipc::NativeHandle reply_target) { + AMS_UNUSED(reply_target); + AMS_ABORT("TODO: tipc Windows Reply"); + } + + ALWAYS_INLINE Result CloseHandle(tipc::NativeHandle handle) { + AMS_UNUSED(handle); + AMS_ABORT("TODO: tipc Windows CloseHandle"); + } + + ALWAYS_INLINE Result CreateSession(tipc::NativeHandle *out_server_session_handle, tipc::NativeHandle *out_client_session_handle, bool is_light, uintptr_t name) { + AMS_UNUSED(out_server_session_handle, out_client_session_handle, is_light, name); + AMS_ABORT("TODO: tipc Windows CreateSession"); + } + + ALWAYS_INLINE Result AcceptSession(tipc::NativeHandle *out_handle, tipc::NativeHandle port) { + AMS_UNUSED(out_handle, port); + AMS_ABORT("TODO: tipc Windows AcceptSession"); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_template_base.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_template_base.hpp new file mode 100644 index 00000000..926d51f8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/impl/tipc_impl_template_base.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::tipc::impl { + + template<typename Interface, typename Base, typename ImplHolder, typename ImplGetter, typename Root> + class ImplTemplateBaseT; + + template<typename Interface, typename Base, typename ImplHolder, typename ImplGetter> + class ImplTemplateBase : public ImplTemplateBaseT<Interface, Base, ImplHolder, ImplGetter, Interface> { + private: + using BaseImpl = ImplTemplateBaseT<Interface, Base, ImplHolder, ImplGetter, Interface>; + public: + using BaseImpl::BaseImpl; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_allocators.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_allocators.hpp new file mode 100644 index 00000000..ea9ba03b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_allocators.hpp @@ -0,0 +1,87 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/tipc/tipc_common.hpp> +#include <stratosphere/tipc/tipc_service_object_base.hpp> + +namespace ams::tipc { + + template<typename T> + concept IsServiceObjectAllocator = requires (T &t) { + { t.Allocate() } -> std::convertible_to<ServiceObjectBase *>; + }; + + template<typename T, size_t N> requires IsServiceObject<T> + class SingletonAllocator final { + static_assert(N >= 1); + private: + T m_singleton; + public: + constexpr ALWAYS_INLINE SingletonAllocator() : m_singleton() { /* ... */ } + + ALWAYS_INLINE ServiceObjectBase *Allocate() { return std::addressof(m_singleton); } + }; + + template<typename T, size_t N> requires IsServiceObject<T> + class SlabAllocator final : public ServiceObjectDeleter { + private: + struct Entry { + bool used; + util::TypedStorage<T> storage; + }; + private: + Entry m_entries[N]; + os::SdkMutex m_mutex; + public: + constexpr ALWAYS_INLINE SlabAllocator() : m_entries(), m_mutex() { /* ... */ } + + T *Allocate() { + std::scoped_lock lk(m_mutex); + + for (size_t i = 0; i < N; ++i) { + if (!m_entries[i].used) { + m_entries[i].used = true; + return util::ConstructAt(m_entries[i].storage); + } + } + + return nullptr; + } + + void Deallocate(ServiceObjectBase *object) { + std::scoped_lock lk(m_mutex); + + for (size_t i = 0; i < N; ++i) { + if (m_entries[i].used && GetPointer(m_entries[i].storage) == object) { + util::DestroyAt(m_entries[i].storage); + m_entries[i].used = false; + return; + } + } + + AMS_ABORT("Failed to deallocate entry in SlabAllocator<T, N>"); + } + public: + virtual void DeleteServiceObject(ServiceObjectBase *object) override { + return this->Deallocate(object); + } + }; + static_assert(IsServiceObjectAllocator<SlabAllocator<ServiceObjectBase, 1>>); + static_assert(IsServiceObjectDeleter<SlabAllocator<ServiceObjectBase, 1>>); + +} + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_buffers.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_buffers.hpp new file mode 100644 index 00000000..9af8bd7c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_buffers.hpp @@ -0,0 +1,217 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/tipc/tipc_common.hpp> +#include <stratosphere/tipc/tipc_out.hpp> +#include <stratosphere/tipc/tipc_pointer_and_size.hpp> + +namespace ams::tipc { + + namespace impl { + + /* Buffer utilities. */ + struct BufferBaseTag{}; + + } + + namespace impl { + + class BufferBase : public BufferBaseTag { + public: + static constexpr u32 AdditionalAttributes = 0; + private: + const tipc::PointerAndSize m_pas; + protected: + constexpr ALWAYS_INLINE uintptr_t GetAddressImpl() const { + return m_pas.GetAddress(); + } + + template<typename Entry> + constexpr ALWAYS_INLINE size_t GetSizeImpl() const { + return m_pas.GetSize() / sizeof(Entry); + } + public: + constexpr ALWAYS_INLINE BufferBase() : m_pas() { /* ... */ } + constexpr ALWAYS_INLINE BufferBase(const tipc::PointerAndSize &pas) : m_pas(pas) { /* ... */ } + constexpr ALWAYS_INLINE BufferBase(uintptr_t ptr, size_t sz) : m_pas(ptr, sz) { /* ... */ } + }; + + class InBufferBase : public BufferBase { + public: + using BaseType = BufferBase; + static constexpr u32 AdditionalAttributes = BaseType::AdditionalAttributes | + SfBufferAttr_In; + public: + constexpr ALWAYS_INLINE InBufferBase() : BaseType() { /* ... */ } + constexpr ALWAYS_INLINE InBufferBase(const tipc::PointerAndSize &pas) : BaseType(pas) { /* ... */ } + constexpr ALWAYS_INLINE InBufferBase(uintptr_t ptr, size_t sz) : BaseType(ptr, sz) { /* ... */ } + + ALWAYS_INLINE InBufferBase(const void *ptr, size_t sz) : BaseType(reinterpret_cast<uintptr_t>(ptr), sz) { /* ... */ } + ALWAYS_INLINE InBufferBase(const u8 *ptr, size_t sz) : BaseType(reinterpret_cast<uintptr_t>(ptr), sz) { /* ... */ } + }; + + class OutBufferBase : public BufferBase { + public: + using BaseType = BufferBase; + static constexpr u32 AdditionalAttributes = BaseType::AdditionalAttributes | + SfBufferAttr_Out; + public: + constexpr ALWAYS_INLINE OutBufferBase() : BaseType() { /* ... */ } + constexpr ALWAYS_INLINE OutBufferBase(const tipc::PointerAndSize &pas) : BaseType(pas) { /* ... */ } + constexpr ALWAYS_INLINE OutBufferBase(uintptr_t ptr, size_t sz) : BaseType(ptr, sz) { /* ... */ } + + ALWAYS_INLINE OutBufferBase(void *ptr, size_t sz) : BaseType(reinterpret_cast<uintptr_t>(ptr), sz) { /* ... */ } + ALWAYS_INLINE OutBufferBase(u8 *ptr, size_t sz) : BaseType(reinterpret_cast<uintptr_t>(ptr), sz) { /* ... */ } + }; + + template<u32 ExtraAttributes = 0> + class InBufferImpl : public InBufferBase { + public: + using BaseType = InBufferBase; + static constexpr u32 AdditionalAttributes = BaseType::AdditionalAttributes | + ExtraAttributes; + public: + constexpr ALWAYS_INLINE InBufferImpl() : BaseType() { /* ... */ } + constexpr ALWAYS_INLINE InBufferImpl(const tipc::PointerAndSize &pas) : BaseType(pas) { /* ... */ } + constexpr ALWAYS_INLINE InBufferImpl(uintptr_t ptr, size_t sz) : BaseType(ptr, sz) { /* ... */ } + + ALWAYS_INLINE InBufferImpl(const void *ptr, size_t sz) : BaseType(reinterpret_cast<uintptr_t>(ptr), sz) { /* ... */ } + ALWAYS_INLINE InBufferImpl(const u8 *ptr, size_t sz) : BaseType(reinterpret_cast<uintptr_t>(ptr), sz) { /* ... */ } + + ALWAYS_INLINE const u8 *GetPointer() const { + return reinterpret_cast<const u8 *>(this->GetAddressImpl()); + } + + constexpr ALWAYS_INLINE size_t GetSize() const { + return this->GetSizeImpl<u8>(); + } + }; + + template<u32 ExtraAttributes = 0> + class OutBufferImpl : public OutBufferBase { + public: + using BaseType = OutBufferBase; + static constexpr u32 AdditionalAttributes = BaseType::AdditionalAttributes | + ExtraAttributes; + public: + constexpr ALWAYS_INLINE OutBufferImpl() : BaseType() { /* ... */ } + constexpr ALWAYS_INLINE OutBufferImpl(const tipc::PointerAndSize &pas) : BaseType(pas) { /* ... */ } + constexpr ALWAYS_INLINE OutBufferImpl(uintptr_t ptr, size_t sz) : BaseType(ptr, sz) { /* ... */ } + + ALWAYS_INLINE OutBufferImpl(void *ptr, size_t sz) : BaseType(reinterpret_cast<uintptr_t>(ptr), sz) { /* ... */ } + ALWAYS_INLINE OutBufferImpl(u8 *ptr, size_t sz) : BaseType(reinterpret_cast<uintptr_t>(ptr), sz) { /* ... */ } + + ALWAYS_INLINE u8 *GetPointer() const { + return reinterpret_cast<u8 *>(this->GetAddressImpl()); + } + + constexpr ALWAYS_INLINE size_t GetSize() const { + return this->GetSizeImpl<u8>(); + } + }; + + template<typename T> + struct InArrayImpl : public InBufferBase { + public: + using BaseType = InBufferBase; + static constexpr u32 AdditionalAttributes = BaseType::AdditionalAttributes; + public: + constexpr ALWAYS_INLINE InArrayImpl() : BaseType() { /* ... */ } + constexpr ALWAYS_INLINE InArrayImpl(const tipc::PointerAndSize &pas) : BaseType(pas) { /* ... */ } + constexpr ALWAYS_INLINE InArrayImpl(uintptr_t ptr, size_t sz) : BaseType(ptr, sz) { /* ... */ } + + ALWAYS_INLINE InArrayImpl(const T *ptr, size_t num_elements) : BaseType(reinterpret_cast<uintptr_t>(ptr), num_elements * sizeof(T)) { /* ... */ } + + ALWAYS_INLINE const T *GetPointer() const { + return reinterpret_cast<const T *>(this->GetAddressImpl()); + } + + constexpr ALWAYS_INLINE size_t GetSize() const { + return this->GetSizeImpl<T>(); + } + + ALWAYS_INLINE const T &operator[](size_t i) const { + return this->GetPointer()[i]; + } + + constexpr explicit ALWAYS_INLINE operator Span<const T>() const { + return {this->GetPointer(), this->GetSize()}; + } + + constexpr ALWAYS_INLINE Span<const T> ToSpan() const { + return {this->GetPointer(), this->GetSize()}; + } + }; + + template<typename T> + struct OutArrayImpl : public OutBufferBase { + public: + using BaseType = OutBufferBase; + static constexpr u32 AdditionalAttributes = BaseType::AdditionalAttributes; + public: + constexpr ALWAYS_INLINE OutArrayImpl() : BaseType() { /* ... */ } + constexpr ALWAYS_INLINE OutArrayImpl(const tipc::PointerAndSize &pas) : BaseType(pas) { /* ... */ } + constexpr ALWAYS_INLINE OutArrayImpl(uintptr_t ptr, size_t sz) : BaseType(ptr, sz) { /* ... */ } + + ALWAYS_INLINE OutArrayImpl(T *ptr, size_t num_elements) : BaseType(reinterpret_cast<uintptr_t>(ptr), num_elements * sizeof(T)) { /* ... */ } + + ALWAYS_INLINE T *GetPointer() const { + return reinterpret_cast<T *>(this->GetAddressImpl()); + } + + constexpr ALWAYS_INLINE size_t GetSize() const { + return this->GetSizeImpl<T>(); + } + + ALWAYS_INLINE T &operator[](size_t i) const { + return this->GetPointer()[i]; + } + + constexpr explicit ALWAYS_INLINE operator Span<T>() const { + return {this->GetPointer(), this->GetSize()}; + } + + constexpr ALWAYS_INLINE Span<T> ToSpan() const { + return {this->GetPointer(), this->GetSize()}; + } + }; + + } + + /* Buffer Types. */ + using InBuffer = typename impl::InBufferImpl<>; + // using InNonSecureBuffer = typename impl::InBufferImpl<SfBufferAttr_HipcMapTransferAllowsNonSecure>; + // using InNonDeviceBuffer = typename impl::InBufferImpl<SfBufferAttr_HipcMapTransferAllowsNonDevice>; + + using OutBuffer = typename impl::OutBufferImpl<>; + //using OutNonSecureBuffer = typename impl::OutBufferImpl<SfBufferAttr_HipcMapTransferAllowsNonSecure>; + //using OutNonDeviceBuffer = typename impl::OutBufferImpl<SfBufferAttr_HipcMapTransferAllowsNonDevice>; + + template<typename T> + using InArray = typename impl::InArrayImpl<T>; + + template<typename T> + using OutArray = typename impl::OutArrayImpl<T>; + + /* Attribute serialization structs. */ + template<typename T> + concept IsBuffer = std::derived_from<T, impl::BufferBaseTag>; + + template<typename T> requires IsBuffer<T> + constexpr inline u32 BufferAttributes = SfBufferAttr_HipcMapAlias | T::AdditionalAttributes; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_common.hpp new file mode 100644 index 00000000..f62cf609 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_common.hpp @@ -0,0 +1,21 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/ams.hpp> +#include <stratosphere/os.hpp> +#include <stratosphere/sm/sm_types.hpp> +#include <stratosphere/tipc/tipc_message_types.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_deferral_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_deferral_manager.hpp new file mode 100644 index 00000000..ee0db8a9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_deferral_manager.hpp @@ -0,0 +1,221 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/tipc/tipc_message_types.hpp> +#include <stratosphere/tipc/tipc_object_holder.hpp> +#include <stratosphere/tipc/tipc_service_object_base.hpp> + +namespace ams::tipc { + + template<typename T> + concept IsResumeKey = util::is_pod<T>::value && (0 < sizeof(T) && sizeof(T) <= sizeof(uintptr_t)); + + template<IsResumeKey ResumeKey> + static constexpr ALWAYS_INLINE uintptr_t ConvertToInternalResumeKey(ResumeKey key) { + if constexpr (std::same_as<ResumeKey, uintptr_t>) { + return key; + } else if constexpr (sizeof(key) == sizeof(uintptr_t)) { + return std::bit_cast<uintptr_t>(key); + } else { + uintptr_t converted = 0; + std::memcpy(std::addressof(converted), std::addressof(key), sizeof(key)); + return converted; + } + } + + class DeferralManagerBase; + + namespace impl { + + class DeferrableBaseTag{}; + + } + + class DeferrableBaseImpl : public impl::DeferrableBaseTag { + private: + DeferralManagerBase *m_deferral_manager; + ObjectHolder m_object_holder; + uintptr_t m_resume_key; + const u32 m_message_buffer_size; + u8 m_message_buffer_base[0]; + public: + ALWAYS_INLINE DeferrableBaseImpl(u32 mb_size) : m_deferral_manager(nullptr), m_object_holder(), m_resume_key(), m_message_buffer_size(mb_size) { /* ... */ } + + ~DeferrableBaseImpl(); + + ALWAYS_INLINE void SetDeferralManager(DeferralManagerBase *manager, tipc::NativeHandle reply_target, ServiceObjectBase *object) { + m_deferral_manager = manager; + m_object_holder.InitializeForDeferralManager(reply_target, object); + } + + ALWAYS_INLINE bool TestResume(uintptr_t key) const { + return m_resume_key == key; + } + + template<IsResumeKey ResumeKey> + ALWAYS_INLINE void RegisterRetry(ResumeKey key) { + m_resume_key = ConvertToInternalResumeKey(key); + std::memcpy(m_message_buffer_base, tipc::GetMessageBuffer(), m_message_buffer_size); + } + + template<IsResumeKey ResumeKey, typename F> + ALWAYS_INLINE Result RegisterRetryIfDeferred(ResumeKey key, F f) { + ON_RESULT_INCLUDED(tipc::ResultRequestDeferred) { this->RegisterRetry(key); }; + + R_RETURN(f()); + } + + template<typename PortManager> + ALWAYS_INLINE void TriggerResume(PortManager *port_manager) { + /* Clear resume key. */ + m_resume_key = 0; + + /* Restore message buffer. */ + std::memcpy(tipc::GetMessageBuffer(), m_message_buffer_base, m_message_buffer_size); + + /* Process the request. */ + return port_manager->ProcessDeferredRequest(m_object_holder); + } + protected: + static consteval size_t GetMessageBufferOffsetBase(); + }; + static_assert(std::is_standard_layout<DeferrableBaseImpl>::value); + + #define TIPC_REGISTER_RETRY_ON_RESULT_REQUEST_DEFERRED(KEY) ON_RESULT_INCLUDED(tipc::ResultRequestDeferred) { this->RegisterRetry(KEY); } + + template<size_t _MessageBufferRequiredSize> + class DeferrableBaseImplWithBuffer : public DeferrableBaseImpl { + private: + static constexpr size_t MessageBufferRequiredSize = _MessageBufferRequiredSize; + private: + u8 m_message_buffer[MessageBufferRequiredSize]; + public: + DeferrableBaseImplWithBuffer(); + private: + static consteval size_t GetMessageBufferOffset(); + }; + + consteval size_t DeferrableBaseImpl::GetMessageBufferOffsetBase() { + return AMS_OFFSETOF(DeferrableBaseImpl, m_message_buffer_base); + } + + template<size_t _MessageBufferRequiredSize> + consteval size_t DeferrableBaseImplWithBuffer<_MessageBufferRequiredSize>::GetMessageBufferOffset() { + return AMS_OFFSETOF(DeferrableBaseImplWithBuffer<_MessageBufferRequiredSize>, m_message_buffer); + } + + template<size_t _MessageBufferRequiredSize> + ALWAYS_INLINE DeferrableBaseImplWithBuffer<_MessageBufferRequiredSize>::DeferrableBaseImplWithBuffer() : DeferrableBaseImpl(MessageBufferRequiredSize) { + static_assert(GetMessageBufferOffsetBase() == GetMessageBufferOffset()); + static_assert(sizeof(DeferrableBaseImplWithBuffer<_MessageBufferRequiredSize>) >= sizeof(DeferrableBaseImpl) + MessageBufferRequiredSize); + } + + template<typename Interface, size_t MaximumDefaultRequestSize = 0> + class DeferrableBase : public DeferrableBaseImplWithBuffer<std::max(Interface::MaximumRequestSize, MaximumDefaultRequestSize)> { + private: + using BaseImpl = DeferrableBaseImplWithBuffer<std::max(Interface::MaximumRequestSize, MaximumDefaultRequestSize)>; + public: + using BaseImpl::BaseImpl; + }; + + template<class T> + concept IsDeferrable = std::derived_from<T, impl::DeferrableBaseTag>; + + class DeferralManagerBase { + NON_COPYABLE(DeferralManagerBase); + NON_MOVEABLE(DeferralManagerBase); + private: + size_t m_object_count; + DeferrableBaseImpl *m_objects_base[0]; + public: + ALWAYS_INLINE DeferralManagerBase() : m_object_count(0) { /* ... */ } + + void AddObject(DeferrableBaseImpl &object, tipc::NativeHandle reply_target, ServiceObjectBase *service_object) { + /* Set ourselves as the manager for the object. */ + object.SetDeferralManager(this, reply_target, service_object); + + /* Add the object to our entries. */ + m_objects_base[m_object_count++] = std::addressof(object); + } + + void RemoveObject(DeferrableBaseImpl *object) { + /* If the object is present, remove it. */ + for (size_t i = 0; i < m_object_count; ++i) { + if (m_objects_base[i] == object) { + std::swap(m_objects_base[i], m_objects_base[--m_object_count]); + break; + } + } + } + + ALWAYS_INLINE bool TestResume(uintptr_t resume_key) const { + /* Try to resume all entries. */ + for (size_t i = 0; i < m_object_count; ++i) { + if (m_objects_base[i]->TestResume(resume_key)) { + return true; + } + } + + return false; + } + + template<typename PortManager> + ALWAYS_INLINE void TriggerResume(PortManager *port_manager, uintptr_t resume_key) const { + /* Try to resume all entries. */ + for (size_t i = 0; i < m_object_count; ++i) { + if (m_objects_base[i]->TestResume(resume_key)) { + m_objects_base[i]->TriggerResume(port_manager); + } + } + } + protected: + static consteval size_t GetObjectPointersOffsetBase(); + }; + static_assert(std::is_standard_layout<DeferralManagerBase>::value); + + inline DeferrableBaseImpl::~DeferrableBaseImpl() { + AMS_ASSUME(m_deferral_manager != nullptr); + m_deferral_manager->RemoveObject(this); + } + + template<size_t N> requires (N > 0) + class DeferralManager final : public DeferralManagerBase { + private: + DeferrableBaseImpl *m_objects[N]; + public: + DeferralManager(); + private: + static consteval size_t GetObjectPointersOffset(); + }; + + consteval size_t DeferralManagerBase::GetObjectPointersOffsetBase() { + return AMS_OFFSETOF(DeferralManagerBase, m_objects_base); + } + + template<size_t N> requires (N > 0) + consteval size_t DeferralManager<N>::GetObjectPointersOffset() { + return AMS_OFFSETOF(DeferralManager<N>, m_objects); + } + + template<size_t N> requires (N > 0) + inline DeferralManager<N>::DeferralManager() : DeferralManagerBase() { + static_assert(GetObjectPointersOffset() == GetObjectPointersOffsetBase()); + static_assert(sizeof(DeferralManager<N>) == sizeof(DeferralManagerBase) + N * sizeof(DeferrableBaseImpl *)); + } + +} + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_handles.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_handles.hpp new file mode 100644 index 00000000..4929e761 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_handles.hpp @@ -0,0 +1,95 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/tipc/tipc_common.hpp> +#include <stratosphere/tipc/tipc_out.hpp> +#include <stratosphere/tipc/tipc_pointer_and_size.hpp> + +namespace ams::tipc { + + /* TODO: How do InHandles work in tipc? No examples to work off of. */ + class CopyHandle { + private: + CopyHandle(); + }; + + class MoveHandle { + private: + MoveHandle(); + }; + + template<> + class Out<CopyHandle> { + private: + tipc::NativeHandle * const m_ptr; + public: + ALWAYS_INLINE Out(tipc::NativeHandle *p) : m_ptr(p) { /* ... */ } + + ALWAYS_INLINE void SetValue(tipc::NativeHandle v) const { + *m_ptr = v; + } + + ALWAYS_INLINE const tipc::NativeHandle &GetValue() const { + return *m_ptr; + } + + ALWAYS_INLINE tipc::NativeHandle *GetPointer() const { + return m_ptr; + } + + /* Convenience operators. */ + ALWAYS_INLINE tipc::NativeHandle &operator*() const { + return *m_ptr; + } + + ALWAYS_INLINE tipc::NativeHandle *operator->() const { + return m_ptr; + } + }; + + template<> + class Out<MoveHandle> { + private: + tipc::NativeHandle * const m_ptr; + public: + ALWAYS_INLINE Out(tipc::NativeHandle *p) : m_ptr(p) { /* ... */ } + + ALWAYS_INLINE void SetValue(tipc::NativeHandle v) const { + *m_ptr = v; + } + + ALWAYS_INLINE const tipc::NativeHandle &GetValue() const { + return *m_ptr; + } + + ALWAYS_INLINE tipc::NativeHandle *GetPointer() const { + return m_ptr; + } + + /* Convenience operators. */ + ALWAYS_INLINE tipc::NativeHandle &operator*() const { + return *m_ptr; + } + + ALWAYS_INLINE tipc::NativeHandle *operator->() const { + return m_ptr; + } + }; + + using OutMoveHandle = tipc::Out<tipc::MoveHandle>; + using OutCopyHandle = tipc::Out<tipc::CopyHandle>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_message_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_message_types.hpp new file mode 100644 index 00000000..e85f76c1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_message_types.hpp @@ -0,0 +1,128 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::tipc { + + namespace impl { + + template<typename HandleType> + class DummyMessageBuffer { + public: + class MessageHeader { + public: + MessageHeader() { AMS_ABORT("TODO"); } + MessageHeader(const DummyMessageBuffer &) { AMS_ABORT("TODO"); } + + u16 GetTag() const { AMS_ABORT("TODO"); } + }; + class SpecialHeader { + public: + SpecialHeader() { AMS_ABORT("TODO"); } + SpecialHeader(const DummyMessageBuffer &, const MessageHeader &) { AMS_ABORT("TODO"); } + + bool GetHasProcessId() const { AMS_ABORT("TODO"); } + s32 GetCopyHandleCount() const { AMS_ABORT("TODO"); } + }; + public: + DummyMessageBuffer(u32 *) { AMS_ABORT("TODO"); } + DummyMessageBuffer(u32 *, size_t) { AMS_ABORT("TODO"); } + + void SetNull() const { AMS_ABORT("TODO"); } + + HandleType GetHandle(s32) const { AMS_ABORT("TODO"); } + + template<typename T> + const T &GetRaw(s32) const { AMS_ABORT("TODO"); } + + static s32 GetSpecialDataIndex(const MessageHeader &, const SpecialHeader &) { AMS_ABORT("TODO"); } + static s32 GetRawDataIndex(const MessageHeader &, const SpecialHeader &) { AMS_ABORT("TODO"); } + }; + + } + + #if defined(ATMOSPHERE_OS_HORIZON) + + constexpr inline auto MaximumSessionsPerPort = svc::ArgumentHandleCountMax; + + using NativeHandle = svc::Handle; + constexpr inline NativeHandle InvalidNativeHandle = svc::InvalidHandle; + + using MessageBuffer = ::ams::svc::ipc::MessageBuffer; + + ALWAYS_INLINE u32 *GetMessageBuffer() { return svc::ipc::GetMessageBuffer(); } + + constexpr ALWAYS_INLINE u32 EncodeNativeHandleForMessageQueue(NativeHandle h) { return static_cast<u32>(h); } + constexpr ALWAYS_INLINE NativeHandle DecodeNativeHandleForMessageQueue(u32 v) { return static_cast<NativeHandle>(v); } + + #elif defined(ATMOSPHERE_OS_WINDOWS) + + /* TODO */ + constexpr inline auto MaximumSessionsPerPort = 0x40; + + using NativeHandle = void *; + constexpr inline NativeHandle InvalidNativeHandle = nullptr; + + using MessageBuffer = ::ams::tipc::impl::DummyMessageBuffer<NativeHandle>; + + ALWAYS_INLINE u32 *GetMessageBuffer() { AMS_ABORT("TODO"); } + + ALWAYS_INLINE u32 EncodeNativeHandleForMessageQueue(NativeHandle) { AMS_ABORT("TODO"); } + ALWAYS_INLINE NativeHandle DecodeNativeHandleForMessageQueue(u32) { AMS_ABORT("TODO"); } + + #elif defined(ATMOSPHERE_OS_LINUX) + + /* TODO */ + constexpr inline auto MaximumSessionsPerPort = 0x40; + + using NativeHandle = s32; + constexpr inline NativeHandle InvalidNativeHandle = -1; + + using MessageBuffer = ::ams::tipc::impl::DummyMessageBuffer<NativeHandle>; + + ALWAYS_INLINE u32 *GetMessageBuffer() { AMS_ABORT("TODO"); } + + ALWAYS_INLINE u32 EncodeNativeHandleForMessageQueue(NativeHandle) { AMS_ABORT("TODO"); } + ALWAYS_INLINE NativeHandle DecodeNativeHandleForMessageQueue(u32) { AMS_ABORT("TODO"); } + + #elif defined(ATMOSPHERE_OS_MACOS) + + /* TODO */ + constexpr inline auto MaximumSessionsPerPort = 0x40; + + using NativeHandle = s32; + constexpr inline NativeHandle InvalidNativeHandle = -1; + + using MessageBuffer = ::ams::tipc::impl::DummyMessageBuffer<NativeHandle>; + + ALWAYS_INLINE u32 *GetMessageBuffer() { AMS_ABORT("TODO"); } + + ALWAYS_INLINE u32 EncodeNativeHandleForMessageQueue(NativeHandle) { AMS_ABORT("TODO"); } + ALWAYS_INLINE NativeHandle DecodeNativeHandleForMessageQueue(u32) { AMS_ABORT("TODO"); } + + #else + #error "Unknown OS for tipc platform types." + #endif + + namespace impl { + + + + } + +} + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_object_holder.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_object_holder.hpp new file mode 100644 index 00000000..cf2f4051 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_object_holder.hpp @@ -0,0 +1,98 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/tipc/tipc_common.hpp> +#include <stratosphere/tipc/tipc_service_object_base.hpp> + +namespace ams::tipc { + + class ObjectHolder { + public: + enum ObjectType : u8 { + ObjectType_Invalid = 0, + ObjectType_Port = 1, + ObjectType_Session = 2, + + ObjectType_Deferral = ObjectType_Invalid, + }; + private: + tipc::NativeHandle m_handle; + ObjectType m_type; + bool m_managed; + tipc::ServiceObjectBase *m_object; + private: + void InitializeImpl(ObjectType type, tipc::NativeHandle handle, bool managed, tipc::ServiceObjectBase *object) { + /* Validate that the object isn't already constructed. */ + AMS_ASSERT(m_type == ObjectType_Invalid); + + /* Set all fields. */ + m_handle = handle; + m_type = type; + m_managed = managed; + m_object = object; + } + public: + constexpr inline ObjectHolder() : m_handle(tipc::InvalidNativeHandle), m_type(ObjectType_Invalid), m_managed(false), m_object(nullptr) { /* ... */ } + + void InitializeAsPort(tipc::NativeHandle handle) { + /* NOTE: Nintendo sets ports as managed, but this will cause a nullptr-deref if one is ever closed. */ + /* This is theoretically a non-issue, as ports can't be closed, but we will set ours as unmanaged, */ + /* just in case. */ + this->InitializeImpl(ObjectType_Port, handle, false, nullptr); + } + + void InitializeAsSession(tipc::NativeHandle handle, bool managed, tipc::ServiceObjectBase *object) { + this->InitializeImpl(ObjectType_Session, handle, managed, object); + } + + void InitializeForDeferralManager(tipc::NativeHandle handle, tipc::ServiceObjectBase *object) { + this->InitializeImpl(ObjectType_Deferral, handle, false, object); + } + + void Destroy() { + /* Validate that the object is constructed. */ + AMS_ASSERT(m_type != ObjectType_Invalid); + + /* If we're managed, destroy the associated object. */ + if (m_managed) { + if (auto * const deleter = m_object->GetDeleter(); deleter != nullptr) { + deleter->DeleteServiceObject(m_object); + } + } + + /* Reset all fields. */ + m_handle = tipc::InvalidNativeHandle; + m_type = ObjectType_Invalid; + m_managed = false; + m_object = nullptr; + } + + constexpr tipc::NativeHandle GetHandle() const { + return m_handle; + } + + constexpr ObjectType GetType() const { + return m_type; + } + + constexpr tipc::ServiceObjectBase *GetObject() const { + return m_object; + } + }; + +} + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_object_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_object_manager.hpp new file mode 100644 index 00000000..e51141f9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_object_manager.hpp @@ -0,0 +1,210 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/tipc/tipc_common.hpp> +#include <stratosphere/tipc/impl/tipc_impl_message_api.hpp> +#include <stratosphere/tipc/tipc_service_object.hpp> +#include <stratosphere/tipc/tipc_object_holder.hpp> + +namespace ams::tipc { + + /* TODO: Put this in a better header. */ + constexpr inline u16 MethodId_Invalid = 0x0; + constexpr inline u16 MethodId_CloseSession = 0xF; + + class ObjectManagerBase { + protected: + struct Entry { + util::TypedStorage<ObjectHolder> object; + os::MultiWaitHolderType multi_wait_holder; + }; + private: + os::SdkMutex m_mutex{}; + Entry *m_entries_start{}; + Entry *m_entries_end{}; + os::MultiWaitType *m_multi_wait{}; + private: + Entry *FindEntry(tipc::NativeHandle handle) { + for (Entry *cur = m_entries_start; cur != m_entries_end; ++cur) { + if (GetReference(cur->object).GetHandle() == handle) { + return cur; + } + } + return nullptr; + } + + Entry *FindEntry(os::MultiWaitHolderType *holder) { + for (Entry *cur = m_entries_start; cur != m_entries_end; ++cur) { + if (std::addressof(cur->multi_wait_holder) == holder) { + return cur; + } + } + return nullptr; + } + public: + constexpr ObjectManagerBase() = default; + + void InitializeImpl(os::MultiWaitType *multi_wait, Entry *entries, size_t max_objects) { + /* Set our multi wait. */ + m_multi_wait = multi_wait; + + /* Setup entry pointers. */ + m_entries_start = entries; + m_entries_end = entries + max_objects; + + /* Construct all entries. */ + for (size_t i = 0; i < max_objects; ++i) { + util::ConstructAt(m_entries_start[i].object); + } + } + + void AddObject(ObjectHolder &object) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Find an empty entry. */ + auto *entry = this->FindEntry(tipc::InvalidNativeHandle); + AMS_ABORT_UNLESS(entry != nullptr); + + /* Set the entry's object. */ + GetReference(entry->object) = object; + + /* Setup the entry's holder. */ + os::InitializeMultiWaitHolder(std::addressof(entry->multi_wait_holder), object.GetHandle()); + os::LinkMultiWaitHolder(m_multi_wait, std::addressof(entry->multi_wait_holder)); + } + + void CloseObject(tipc::NativeHandle handle) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Find the matching entry. */ + auto *entry = this->FindEntry(handle); + AMS_ABORT_UNLESS(entry != nullptr); + + /* Finalize the entry's holder. */ + os::UnlinkMultiWaitHolder(std::addressof(entry->multi_wait_holder)); + os::FinalizeMultiWaitHolder(std::addressof(entry->multi_wait_holder)); + + /* Destroy the object. */ + GetReference(entry->object).Destroy(); + } + + Result ReplyAndReceive(os::MultiWaitHolderType **out_holder, ObjectHolder *out_object, tipc::NativeHandle reply_target, os::MultiWaitType *multi_wait) { + /* Declare signaled holder for processing ahead of time. */ + os::MultiWaitHolderType *signaled_holder; + + /* Reply and receive until we get a newly signaled target. */ + Result result = os::SdkReplyAndReceive(out_holder, reply_target, multi_wait); + for (signaled_holder = *out_holder; signaled_holder == nullptr; signaled_holder = *out_holder) { + result = os::SdkReplyAndReceive(out_holder, tipc::InvalidNativeHandle, multi_wait); + } + + /* Find the entry matching the signaled holder. */ + if (auto *entry = this->FindEntry(signaled_holder); entry != nullptr) { + /* Get the output object. */ + *out_object = GetReference(entry->object); + *out_holder = nullptr; + + R_RETURN(result); + } else { + R_SUCCEED(); + } + } + + void Reply(tipc::NativeHandle reply_target) { + return tipc::impl::Reply(reply_target); + } + + Result ProcessRequest(ObjectHolder &object) { + /* Get the message buffer. */ + const MessageBuffer message_buffer(tipc::GetMessageBuffer()); + + /* Get the method id. */ + const auto method_id = MessageBuffer::MessageHeader(message_buffer).GetTag(); + + /* Process for the method id. */ + { + /* Ensure that if we fail, we clean up any handles that get sent our way. */ + ON_RESULT_FAILURE { + const MessageBuffer::MessageHeader message_header(message_buffer); + const MessageBuffer::SpecialHeader special_header(message_buffer, message_header); + + /* Determine the offset to the start of handles. */ + auto offset = message_buffer.GetSpecialDataIndex(message_header, special_header); + if (special_header.GetHasProcessId()) { + offset += sizeof(u64) / sizeof(u32); + } + + /* Close all copy handles. */ + for (auto i = 0; i < special_header.GetCopyHandleCount(); ++i) { + tipc::impl::CloseHandle(message_buffer.GetHandle(offset)); + offset += sizeof(typename std::remove_reference<decltype(message_buffer.GetHandle(offset))>::type) / sizeof(u32); + } + }; + + /* Check that the method id is valid. */ + R_UNLESS(method_id != MethodId_Invalid, tipc::ResultInvalidMethod()); + + /* Process the request. */ + if (method_id != MethodId_CloseSession) { + /* Process the generic method for the object. */ + R_TRY(object.GetObject()->ProcessRequest()); + } else { + /* Validate that the close request is of valid format. */ + using CloseSessionCommandMeta = impl::CommandMetaInfo<MethodId_CloseSession, std::tuple<>>; + using CloseSessionProcessor = impl::CommandProcessor<CloseSessionCommandMeta>; + + R_TRY(CloseSessionProcessor::ValidateCommandFormat(message_buffer)); + } + } + + /* If we were asked to close the object, do so. */ + if (method_id == MethodId_CloseSession) { + /* Get the object handle. */ + const auto handle = object.GetHandle(); + + /* Close the object itself. */ + this->CloseObject(handle); + + /* Close the object's handle. */ + /* NOTE: Nintendo does not check that this succeeds. */ + R_ABORT_UNLESS(tipc::impl::CloseHandle(handle)); + + /* Return an error to signify we closed the object. */ + R_THROW(tipc::ResultSessionClosed()); + } + + /* We successfully processed, so we don't need to clean up handles. */ + R_SUCCEED(); + } + }; + + template<size_t MaxObjects> + class ObjectManager : public ObjectManagerBase { + private: + Entry m_entries_storage[MaxObjects]{}; + public: + constexpr ObjectManager() = default; + + void Initialize(os::MultiWaitType *multi_wait) { + this->InitializeImpl(multi_wait, m_entries_storage, MaxObjects); + } + }; + +} + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_out.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_out.hpp new file mode 100644 index 00000000..d608cc85 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_out.hpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/tipc/tipc_common.hpp> +#include <stratosphere/tipc/tipc_pointer_and_size.hpp> + +namespace ams::tipc { + + namespace impl { + + struct OutBaseTag{}; + + } + + template<typename> + struct IsOutForceEnabled : public std::false_type{}; + + template<> + struct IsOutForceEnabled<::ams::Result> : public std::true_type{}; + + template<typename T> + concept OutEnabled = (std::is_trivial<T>::value || IsOutForceEnabled<T>::value) && !std::is_pointer<T>::value; + + template<typename T> + class Out : public impl::OutBaseTag { + static_assert(OutEnabled<T>); + public: + static constexpr size_t TypeSize = sizeof(T); + private: + T *m_ptr; + public: + constexpr Out(uintptr_t p) : m_ptr(reinterpret_cast<T *>(p)) { /* ... */ } + constexpr Out(T *p) : m_ptr(p) { /* ... */ } + constexpr Out(const tipc::PointerAndSize &pas) : m_ptr(reinterpret_cast<T *>(pas.GetAddress())) { /* TODO: Is AMS_ABORT_UNLESS(pas.GetSize() >= sizeof(T)); necessary? */ } + + ALWAYS_INLINE void SetValue(const T& value) const { + *m_ptr = value; + } + + ALWAYS_INLINE const T &GetValue() const { + return *m_ptr; + } + + ALWAYS_INLINE T *GetPointer() const { + return m_ptr; + } + + /* Convenience operators. */ + ALWAYS_INLINE T &operator*() const { + return *m_ptr; + } + + ALWAYS_INLINE T *operator->() const { + return m_ptr; + } + }; + + template<typename T> + class Out<T *> { + static_assert(!std::is_same<T, T>::value, "Invalid tipc::Out<T> (Raw Pointer)"); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_pointer_and_size.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_pointer_and_size.hpp new file mode 100644 index 00000000..d6136908 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_pointer_and_size.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere/tipc/tipc_pointer_and_size.hpp> + +namespace ams::tipc { + + class PointerAndSize { + private: + uintptr_t m_pointer; + size_t m_size; + public: + constexpr PointerAndSize() : m_pointer(0), m_size(0) { /* ... */ } + constexpr PointerAndSize(uintptr_t ptr, size_t sz) : m_pointer(ptr), m_size(sz) { /* ... */ } + PointerAndSize(void *ptr, size_t sz) : PointerAndSize(reinterpret_cast<uintptr_t>(ptr), sz) { /* ... */ } + + ALWAYS_INLINE void *GetPointer() const { + return reinterpret_cast<void *>(m_pointer); + } + + constexpr ALWAYS_INLINE uintptr_t GetAddress() const { + return m_pointer; + } + + constexpr ALWAYS_INLINE size_t GetSize() const { + return m_size; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_server_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_server_manager.hpp new file mode 100644 index 00000000..e5a5a7c6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_server_manager.hpp @@ -0,0 +1,850 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/tipc/tipc_common.hpp> +#include <stratosphere/tipc/tipc_service_object.hpp> +#include <stratosphere/tipc/tipc_object_manager.hpp> +#include <stratosphere/tipc/tipc_deferral_manager.hpp> + +namespace ams::tipc { + + template<size_t NumSessions, typename Interface, typename Impl, template<typename, size_t> typename _Allocator> + struct PortMeta { + static constexpr inline size_t MaxSessions = NumSessions; + + static constexpr bool CanDeferInvokeRequest = IsDeferrable<Impl>; + + using ServiceObject = tipc::ServiceObject<Interface, Impl>; + + using Allocator = _Allocator<ServiceObject, NumSessions>; + }; + + struct DummyDeferralManagerBase{}; + + template<size_t N> + struct DummyDeferralManager : public DummyDeferralManagerBase {}; + + namespace impl { + + template<size_t ThreadStackSize, bool IsDeferralSupported, size_t NumPorts, typename... PortInfos> + class ServerManagerImpl { + private: + static_assert(NumPorts == sizeof...(PortInfos)); + static constexpr inline size_t MaxSessions = (PortInfos::MaxSessions + ...); + + /* Verify that we have at least one port. */ + static_assert(NumPorts > 0); + + /* Verify that it's possible to service this many sessions, with our port manager count. */ + static_assert(MaxSessions <= NumPorts * MaximumSessionsPerPort); + + static_assert(util::IsAligned(ThreadStackSize, os::ThreadStackAlignment)); + alignas(os::ThreadStackAlignment) static constinit inline u8 s_port_stacks[ThreadStackSize * (NumPorts - 1)]; + + template<size_t Ix> AMS_CONCEPTS_REQUIRES_IF_SUPPORTED(Ix < NumPorts) + static constexpr inline size_t SessionsPerPortManager = (Ix == NumPorts - 1) ? ((MaxSessions / NumPorts) + MaxSessions % NumPorts) + : ((MaxSessions / NumPorts)); + + template<size_t Ix> AMS_CONCEPTS_REQUIRES_IF_SUPPORTED(Ix < NumPorts) + using PortInfo = typename std::tuple_element<Ix, std::tuple<PortInfos...>>::type; + + static_assert(IsDeferralSupported == (PortInfos::CanDeferInvokeRequest || ...)); + + template<size_t Sessions> + using DeferralManagerImplType = typename std::conditional<IsDeferralSupported, DeferralManager<Sessions>, DummyDeferralManager<Sessions>>::type; + + using DeferralManagerBaseType = typename std::conditional<IsDeferralSupported, DeferralManagerBase, DummyDeferralManagerBase>::type; + + template<size_t Ix> AMS_CONCEPTS_REQUIRES_IF_SUPPORTED(Ix < NumPorts) + static constexpr inline bool IsPortDeferrable = PortInfo<Ix>::CanDeferInvokeRequest; + public: + class PortManagerBase { + public: + enum MessageType : u8 { + MessageType_AddSession = 0, + MessageType_TriggerResume = 1, + }; + protected: + s32 m_id; + std::atomic<s32> m_num_sessions; + s32 m_port_number; + os::MultiWaitType m_multi_wait; + os::MessageQueueType m_message_queue; + os::MultiWaitHolderType m_message_queue_holder; + uintptr_t m_message_queue_storage[MaxSessions]; + ServerManagerImpl *m_server_manager; + ObjectManagerBase *m_object_manager; + DeferralManagerBaseType *m_deferral_manager; + public: + PortManagerBase() : m_id(), m_num_sessions(), m_port_number(), m_multi_wait(), m_message_queue(), m_message_queue_holder(), m_message_queue_storage(), m_server_manager(), m_object_manager(), m_deferral_manager() { + /* Setup our message queue. */ + os::InitializeMessageQueue(std::addressof(m_message_queue), m_message_queue_storage, util::size(m_message_queue_storage)); + os::InitializeMultiWaitHolder(std::addressof(m_message_queue_holder), std::addressof(m_message_queue), os::MessageQueueWaitType::ForNotEmpty); + } + + constexpr s32 GetPortIndex() const { + return m_port_number; + } + + s32 GetSessionCount() const { + return m_num_sessions; + } + + void InitializeBase(s32 id, ServerManagerImpl *sm, DeferralManagerBaseType *dm, ObjectManagerBase *om) { + /* Set our id. */ + m_id = id; + + /* Set our server manager. */ + m_server_manager = sm; + + /* Reset our session count. */ + m_num_sessions = 0; + + /* Initialize our multi wait. */ + os::InitializeMultiWait(std::addressof(m_multi_wait)); + os::LinkMultiWaitHolder(std::addressof(m_multi_wait), std::addressof(m_message_queue_holder)); + + /* Initialize our object manager. */ + m_object_manager = om; + + /* Initialize our deferral manager. */ + m_deferral_manager = dm; + } + + void RegisterPort(s32 index, tipc::NativeHandle port_handle) { + /* Set our port number. */ + m_port_number = index; + + /* Create an object holder for the port. */ + tipc::ObjectHolder object; + + /* Setup the object. */ + object.InitializeAsPort(port_handle); + + /* Register the object. */ + m_object_manager->AddObject(object); + } + + tipc::NativeHandle ProcessRequest(ObjectHolder &object) { + /* Acquire exclusive server manager access. */ + std::scoped_lock lk(m_server_manager->GetMutex()); + + /* Process the request. */ + const Result result = m_object_manager->ProcessRequest(object); + if (R_SUCCEEDED(result)) { + /* We should reply only if the request isn't deferred. */ + return !IsRequestDeferred() ? object.GetHandle() : tipc::InvalidNativeHandle; + } else { + /* Processing failed, so note the session as closed (or close it). */ + this->CloseSessionIfNecessary(object, !tipc::ResultSessionClosed::Includes(result)); + + /* We shouldn't reply on failure. */ + return tipc::InvalidNativeHandle; + } + } + + template<bool Enable = IsDeferralSupported, typename = typename std::enable_if<Enable>::type> + void ProcessDeferredRequest(ObjectHolder &object) { + static_assert(Enable == IsDeferralSupported); + + if (const auto reply_target = this->ProcessRequest(object); reply_target != tipc::InvalidNativeHandle) { + m_object_manager->Reply(reply_target); + } + } + + bool ReplyAndReceive(os::MultiWaitHolderType **out_holder, ObjectHolder *out_object, tipc::NativeHandle reply_target) { + /* If we don't have a reply target, clear our message buffer. */ + if (reply_target == tipc::InvalidNativeHandle) { + tipc::MessageBuffer(tipc::GetMessageBuffer()).SetNull(); + } + + /* Try to reply/receive. */ + const Result result = m_object_manager->ReplyAndReceive(out_holder, out_object, reply_target, std::addressof(m_multi_wait)); + + /* Acquire exclusive access to the server manager. */ + std::scoped_lock lk(m_server_manager->GetMutex()); + + /* Handle the result. */ + R_TRY_CATCH(result) { + R_CATCH(os::ResultSessionClosedForReceive, os::ResultReceiveListBroken) { + /* Close the object. */ + this->CloseSession(*out_object); + + /* We don't have anything to process. */ + return false; + } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + return true; + } + + void AddSession(tipc::NativeHandle session_handle, tipc::ServiceObjectBase *service_object) { + /* Create an object holder for the session. */ + tipc::ObjectHolder object; + + /* Setup the object. */ + object.InitializeAsSession(session_handle, true, service_object); + + /* Register the object. */ + m_object_manager->AddObject(object); + } + + void ProcessMessages() { + /* While we have messages in our queue, receive and handle them. */ + uintptr_t message_type, message_data; + while (os::TryReceiveMessageQueue(std::addressof(message_type), std::addressof(m_message_queue))) { + /* Receive the message's data. */ + os::ReceiveMessageQueue(std::addressof(message_data), std::addressof(m_message_queue)); + + /* Handle the specific message. */ + switch (static_cast<MessageType>(static_cast<typename std::underlying_type<MessageType>::type>(message_type))) { + case MessageType_AddSession: + { + /* Get the handle from where it's packed into the message type. */ + const tipc::NativeHandle session_handle = tipc::DecodeNativeHandleForMessageQueue(message_type >> BITSIZEOF(u32)); + + /* Allocate a service object for the port. */ + auto *service_object = m_server_manager->AllocateObject(static_cast<size_t>(message_data), session_handle, *m_deferral_manager); + + /* Add the newly-created service object. */ + this->AddSession(session_handle, service_object); + } + break; + case MessageType_TriggerResume: + if constexpr (IsDeferralSupported) { + /* Perform the resume. */ + this->OnTriggerResume(message_data); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + void CloseSession(ObjectHolder &object) { + /* Get the object's handle. */ + const auto handle = object.GetHandle(); + + /* Close the object with our manager. */ + m_object_manager->CloseObject(handle); + + /* Close the handle itself. */ + R_ABORT_UNLESS(tipc::impl::CloseHandle(handle)); + + /* Decrement our session count. */ + --m_num_sessions; + } + + void CloseSessionIfNecessary(ObjectHolder &object, bool necessary) { + if (necessary) { + /* Get the object's handle. */ + const auto handle = object.GetHandle(); + + /* Close the object with our manager. */ + m_object_manager->CloseObject(handle); + + /* Close the handle itself. */ + R_ABORT_UNLESS(tipc::impl::CloseHandle(handle)); + } + + /* Decrement our session count. */ + --m_num_sessions; + } + + bool TestResume(uintptr_t key) { + if constexpr (IsDeferralSupported) { + /* Acquire exclusive server manager access. */ + std::scoped_lock lk(m_server_manager->GetMutex()); + + /* Check to see if the key corresponds to some deferred message. */ + return m_deferral_manager->TestResume(key); + } else { + return false; + } + } + + void TriggerResume(uintptr_t key) { + /* Acquire exclusive server manager access. */ + std::scoped_lock lk(m_server_manager->GetMutex()); + + /* Send the key as a message. */ + os::SendMessageQueue(std::addressof(m_message_queue), static_cast<uintptr_t>(MessageType_TriggerResume)); + os::SendMessageQueue(std::addressof(m_message_queue), key); + } + + void TriggerAddSession(tipc::NativeHandle session_handle, size_t port_index) { + /* Increment our session count. */ + ++m_num_sessions; + + /* Send information about the session as a message. */ + os::SendMessageQueue(std::addressof(m_message_queue), static_cast<uintptr_t>(MessageType_AddSession) | (static_cast<u64>(tipc::EncodeNativeHandleForMessageQueue(session_handle)) << BITSIZEOF(u32))); + os::SendMessageQueue(std::addressof(m_message_queue), static_cast<uintptr_t>(port_index)); + } + private: + void OnTriggerResume(uintptr_t key) { + /* Acquire exclusive server manager access. */ + std::scoped_lock lk(m_server_manager->GetMutex()); + + /* Trigger the resume. */ + m_deferral_manager->TriggerResume(this, key); + } + public: + static bool IsRequestDeferred() { + if constexpr (IsDeferralSupported) { + /* Get the message buffer. */ + const tipc::MessageBuffer message_buffer(tipc::GetMessageBuffer()); + + /* Parse the hipc headers. */ + const tipc::MessageBuffer::MessageHeader message_header(message_buffer); + const tipc::MessageBuffer::SpecialHeader special_header(message_buffer, message_header); + + /* Determine raw data index. */ + const auto raw_data_offset = message_buffer.GetRawDataIndex(message_header, special_header); + + /* Result is the first raw data word. */ + const Result method_result = message_buffer.GetRaw<u32>(raw_data_offset); + + /* Check that the result is the special deferral result. */ + return tipc::ResultRequestDeferred::Includes(method_result); + } else { + /* If deferral isn't supported, requests are never deferred. */ + return false; + } + } + }; + + template<typename PortInfo, size_t PortSessions> + class PortManagerImpl final : public PortManagerBase { + private: + DeferralManagerImplType<PortSessions> m_deferral_manager_impl; + tipc::ObjectManager<1 + PortSessions> m_object_manager_impl; + public: + PortManagerImpl() : PortManagerBase(), m_deferral_manager_impl(), m_object_manager_impl() { + /* ... */ + } + + void Initialize(s32 id, ServerManagerImpl *sm) { + /* Initialize our base. */ + this->InitializeBase(id, sm, std::addressof(m_deferral_manager_impl), std::addressof(m_object_manager_impl)); + + /* Initialize our object manager. */ + m_object_manager_impl.Initialize(std::addressof(this->m_multi_wait)); + } + }; + + template<size_t Ix> + using PortManager = PortManagerImpl<PortInfo<Ix>, SessionsPerPortManager<Ix>>; + + using PortManagerTuple = decltype([]<size_t... Ix>(std::index_sequence<Ix...>) { + return std::tuple<PortManager<Ix>...>{}; + }(std::make_index_sequence<NumPorts>())); + + using PortAllocatorTuple = std::tuple<typename PortInfos::Allocator...>; + private: + os::SdkRecursiveMutex m_mutex; + PortManagerTuple m_port_managers; + PortAllocatorTuple m_port_allocators; + os::ThreadType m_port_threads[NumPorts - 1]; + private: + template<size_t Ix> + ALWAYS_INLINE auto &GetPortManager() { + return std::get<Ix>(m_port_managers); + } + + template<size_t Ix> + ALWAYS_INLINE const auto &GetPortManager() const { + return std::get<Ix>(m_port_managers); + } + + template<size_t Ix> + void LoopAutoForPort() { + R_ABORT_UNLESS(this->LoopProcess(this->GetPortManager<Ix>())); + } + + template<size_t Ix> + static void LoopAutoForPortThreadFunction(void *_this) { + static_cast<ServerManagerImpl *>(_this)->LoopAutoForPort<Ix>(); + } + + template<size_t Ix> + void InitializePortThread(s32 priority, const char *name) { + /* Create the thread. */ + R_ABORT_UNLESS(os::CreateThread(m_port_threads + Ix, &LoopAutoForPortThreadFunction<Ix>, this, s_port_stacks + Ix, ThreadStackSize, priority)); + + /* Set the thread name pointer. */ + if (name != nullptr) { + os::SetThreadNamePointer(m_port_threads + Ix, name); + } + + /* Start the thread. */ + os::StartThread(m_port_threads + Ix); + } + public: + ServerManagerImpl() : m_mutex(), m_port_managers(), m_port_allocators() { /* ... */ } + + os::SdkRecursiveMutex &GetMutex() { return m_mutex; } + + void Initialize() { + /* Initialize our port managers. */ + [this]<size_t... Ix>(std::index_sequence<Ix...>) ALWAYS_INLINE_LAMBDA { + (this->GetPortManager<Ix>().Initialize(static_cast<s32>(Ix), this), ...); + }(std::make_index_sequence<NumPorts>()); + } + + template<size_t Ix> + void RegisterPort(tipc::NativeHandle port_handle) { + this->GetPortManager<Ix>().RegisterPort(static_cast<s32>(Ix), port_handle); + } + + template<size_t Ix> + void RegisterPort(sm::ServiceName service_name, size_t max_sessions) { + /* Register service. */ + tipc::NativeHandle port_handle; + R_ABORT_UNLESS(sm::RegisterService(std::addressof(port_handle), service_name, max_sessions, false)); + + /* Register the port handle. */ + this->RegisterPort<Ix>(port_handle); + } + + void LoopAuto() { + /* If we have additional threads, create and start them. */ + if constexpr (NumPorts > 1) { + const auto thread_priority = os::GetThreadPriority(os::GetCurrentThread()); + + [thread_priority, this]<size_t... Ix>(std::index_sequence<Ix...>) ALWAYS_INLINE_LAMBDA { + /* Create all threads. */ + (this->InitializePortThread<Ix>(thread_priority, nullptr), ...); + }(std::make_index_sequence<NumPorts - 1>()); + } + + /* Process for the last port. */ + this->LoopAutoForPort<NumPorts - 1>(); + } + + void LoopAuto(int priority, const char *name) { + /* If we have additional threads, create and start them. */ + if constexpr (NumPorts > 1) { + [priority, name, this]<size_t... Ix>(std::index_sequence<Ix...>) ALWAYS_INLINE_LAMBDA { + /* Create all threads. */ + (this->InitializePortThread<Ix>(priority, name), ...); + }(std::make_index_sequence<NumPorts - 1>()); + } + + /* Check current thread. */ + { + AMS_ASSERT(priority == os::GetThreadPriority(os::GetCurrentThread())); + /* N does not do: os::SetThreadNamePointer(os::GetCurrentThread(), name); */ + } + + /* Process for the last port. */ + this->LoopAutoForPort<NumPorts - 1>(); + } + + tipc::ServiceObjectBase *AllocateObject(size_t port_index, tipc::NativeHandle handle, DeferralManagerBaseType &deferral_manager) { + /* Check that the port index is valid. */ + AMS_ABORT_UNLESS(port_index < NumPorts); + + /* Try to allocate from each port, in turn. */ + tipc::ServiceObjectBase *allocated = nullptr; + [this, port_index, handle, &deferral_manager, &allocated]<size_t... Ix>(std::index_sequence<Ix...>) ALWAYS_INLINE_LAMBDA { + (this->TryAllocateObject<Ix>(port_index, handle, deferral_manager, allocated), ...); + }(std::make_index_sequence<NumPorts>()); + + /* Return the allocated object. */ + AMS_ABORT_UNLESS(allocated != nullptr); + return allocated; + } + + template<IsResumeKey ResumeKey> + void TriggerResume(const ResumeKey &resume_key) { + /* Acquire exclusive access to ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Convert to internal resume key. */ + const auto internal_resume_key = ConvertToInternalResumeKey(resume_key); + + /* Check/trigger resume on each of our ports. */ + [this, internal_resume_key]<size_t... Ix>(std::index_sequence<Ix...>) ALWAYS_INLINE_LAMBDA { + (this->TriggerResumeImpl<Ix>(internal_resume_key), ...); + }(std::make_index_sequence<NumPorts>()); + } + + Result AddSession(tipc::NativeHandle *out, tipc::ServiceObjectBase *object) { + /* Acquire exclusive access to ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Create a handle for the session. */ + tipc::NativeHandle session_handle; + R_TRY(tipc::impl::CreateSession(std::addressof(session_handle), static_cast<tipc::NativeHandle *>(out), false, 0)); + + /* Select the best port manager. */ + PortManagerBase *best_manager = nullptr; + s32 best_sessions = -1; + [this, &best_manager, &best_sessions]<size_t... Ix>(std::index_sequence<Ix...>) ALWAYS_INLINE_LAMBDA { + (this->TrySelectBetterPort<Ix>(best_manager, best_sessions), ...); + }(std::make_index_sequence<NumPorts>()); + + /* Add the session to the least burdened manager. */ + best_manager->AddSession(session_handle, object); + + R_SUCCEED(); + } + private: + template<size_t Ix> requires (Ix < NumPorts) + void TryAllocateObject(size_t port_index, tipc::NativeHandle handle, DeferralManagerBaseType &deferral_manager, tipc::ServiceObjectBase *&allocated) { + /* Check that the port index matches. */ + if (port_index == Ix) { + /* Check that we haven't already allocated. */ + AMS_ABORT_UNLESS(allocated == nullptr); + + /* Get the allocator. */ + auto &allocator = std::get<Ix>(m_port_allocators); + + /* Allocate the object. */ + auto * const new_object = allocator.Allocate(); + AMS_ABORT_UNLESS(new_object != nullptr); + + /* If we should, set the object's deleter. */ + if constexpr (IsServiceObjectDeleter<typename std::tuple_element<Ix, PortAllocatorTuple>::type>) { + new_object->SetDeleter(std::addressof(allocator)); + } + + /* If we should, set the object's deferral manager. */ + if constexpr (IsPortDeferrable<Ix>) { + deferral_manager.AddObject(new_object->GetImpl(), handle, new_object); + } + + /* Set the allocated object. */ + allocated = new_object; + } + } + + Result LoopProcess(PortManagerBase &port_manager) { + /* Process requests forever. */ + tipc::NativeHandle reply_target = tipc::InvalidNativeHandle; + while (true) { + /* Reply to our pending request, and wait to receive a new one. */ + os::MultiWaitHolderType *signaled_holder = nullptr; + tipc::ObjectHolder signaled_object{}; + while (!port_manager.ReplyAndReceive(std::addressof(signaled_holder), std::addressof(signaled_object), reply_target)) { + reply_target = tipc::InvalidNativeHandle; + signaled_object = {}; + } + + if (signaled_holder == nullptr) { + /* A session was signaled, accessible via signaled_object. */ + switch (signaled_object.GetType()) { + case ObjectHolder::ObjectType_Port: + { + /* Try to accept a new session */ + tipc::NativeHandle session_handle; + if (R_SUCCEEDED(tipc::impl::AcceptSession(std::addressof(session_handle), signaled_object.GetHandle()))) { + this->TriggerAddSession(session_handle, static_cast<size_t>(port_manager.GetPortIndex())); + } + + /* We have nothing to reply to. */ + reply_target = tipc::InvalidNativeHandle; + } + break; + case ObjectHolder::ObjectType_Session: + { + /* Process the request */ + reply_target = port_manager.ProcessRequest(signaled_object); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } else { + /* Our message queue was signaled. */ + port_manager.ProcessMessages(); + + /* We have nothing to reply to. */ + reply_target = tipc::InvalidNativeHandle; + } + } + } + + void TriggerAddSession(tipc::NativeHandle session_handle, size_t port_index) { + /* Acquire exclusive access to ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Select the best port manager. */ + PortManagerBase *best_manager = nullptr; + s32 best_sessions = -1; + [this, &best_manager, &best_sessions]<size_t... Ix>(std::index_sequence<Ix...>) ALWAYS_INLINE_LAMBDA { + (this->TrySelectBetterPort<Ix>(best_manager, best_sessions), ...); + }(std::make_index_sequence<NumPorts>()); + + /* Trigger the session add on the least-burdened manager. */ + best_manager->TriggerAddSession(session_handle, port_index); + } + + template<size_t Ix> requires (Ix < NumPorts) + void TrySelectBetterPort(PortManagerBase *&best_manager, s32 &best_sessions) { + auto &cur_manager = this->GetPortManager<Ix>(); + const auto cur_sessions = cur_manager.GetSessionCount(); + + /* NOTE: It's unknown how nintendo handles the case where the last manager has more sessions (to cover the remainder). */ + /* Our algorithm diverges from theirs (it does not do std::min bounds capping), to accommodate remainder ports. */ + /* If we learn how they handle this edge case, we can change our ways to match theirs. */ + + if constexpr (Ix == 0) { + best_manager = std::addressof(cur_manager); + best_sessions = cur_sessions; + } else { + static_assert(SessionsPerPortManager<Ix - 1> == SessionsPerPortManager<0>); + static_assert(SessionsPerPortManager<Ix - 1> <= SessionsPerPortManager<Ix>); + if (cur_sessions < best_sessions || best_sessions >= static_cast<s32>(SessionsPerPortManager<Ix - 1>)) { + best_manager = std::addressof(cur_manager); + best_sessions = cur_sessions; + } + } + } + + template<size_t Ix> + void TriggerResumeImpl(uintptr_t resume_key) { + /* Get the port manager. */ + auto &port_manager = this->GetPortManager<Ix>(); + + /* If we should, trigger a resume. */ + if (port_manager.TestResume(resume_key)) { + port_manager.TriggerResume(resume_key); + } + } + }; + + template<size_t ThreadStackSize, typename PortInfo> + class ServerManagerImpl<ThreadStackSize, false, 1, PortInfo> { + private: + static constexpr inline size_t NumPorts = 1; + static constexpr inline size_t MaxSessions = PortInfo::MaxSessions; + + /* Verify that it's possible to service this many sessions, with our port manager count. */ + static_assert(MaxSessions <= MaximumSessionsPerPort); + public: + class PortManagerBase { + protected: + os::MultiWaitType m_multi_wait; + ObjectManagerBase *m_object_manager; + public: + constexpr PortManagerBase() : m_multi_wait(), m_object_manager() { /* ... */ } + + void InitializeBase(ObjectManagerBase *om) { + /* Initialize our multi wait. */ + os::InitializeMultiWait(std::addressof(m_multi_wait)); + + /* Initialize our object manager. */ + m_object_manager = om; + } + + void RegisterPort(tipc::NativeHandle port_handle) { + /* Create an object holder for the port. */ + tipc::ObjectHolder object; + + /* Setup the object. */ + object.InitializeAsPort(port_handle); + + /* Register the object. */ + m_object_manager->AddObject(object); + } + + tipc::NativeHandle ProcessRequest(ObjectHolder &object) { + /* Process the request. */ + const Result result = m_object_manager->ProcessRequest(object); + if (R_SUCCEEDED(result)) { + /* We should reply only if the request isn't deferred. */ + return object.GetHandle(); + } else { + /* Processing failed, so close the session if we need to. */ + if (!tipc::ResultSessionClosed::Includes(result)) { + this->CloseSession(object); + } + + /* We shouldn't reply on failure. */ + return tipc::InvalidNativeHandle; + } + } + + bool ReplyAndReceive(ObjectHolder *out_object, tipc::NativeHandle reply_target) { + /* If we don't have a reply target, clear our message buffer. */ + if (reply_target == tipc::InvalidNativeHandle) { + tipc::MessageBuffer(tipc::GetMessageBuffer()).SetNull(); + } + + /* Try to reply/receive. */ + const Result result = [&]() ALWAYS_INLINE_LAMBDA -> Result { + os::MultiWaitHolderType *signaled_holder = nullptr; + ON_SCOPE_EXIT { AMS_ABORT_UNLESS(signaled_holder == nullptr); }; + return m_object_manager->ReplyAndReceive(std::addressof(signaled_holder), out_object, reply_target, std::addressof(m_multi_wait)); + }(); + + /* Handle the result. */ + if (R_FAILED(result)) { + /* Close the object. */ + this->CloseSession(*out_object); + + /* We don't have anything to process. */ + return false; + } + + return true; + } + + void AddSession(tipc::NativeHandle session_handle, tipc::ServiceObjectBase *service_object) { + /* Create an object holder for the session. */ + tipc::ObjectHolder object; + + /* Setup the object. */ + object.InitializeAsSession(session_handle, true, service_object); + + /* Register the object. */ + m_object_manager->AddObject(object); + } + + void CloseSession(ObjectHolder &object) { + /* Get the object's handle. */ + const auto handle = object.GetHandle(); + + /* Close the object with our manager. */ + m_object_manager->CloseObject(handle); + + /* Close the handle itself. */ + R_ABORT_UNLESS(tipc::impl::CloseHandle(handle)); + } + }; + + class PortManagerImpl final : public PortManagerBase { + private: + tipc::ObjectManager<1 + MaxSessions> m_object_manager_impl; + public: + constexpr PortManagerImpl() : PortManagerBase(), m_object_manager_impl() { + /* ... */ + } + + void Initialize() { + /* Initialize our base. */ + this->InitializeBase(std::addressof(m_object_manager_impl)); + + /* Initialize our object manager. */ + m_object_manager_impl.Initialize(std::addressof(this->m_multi_wait)); + } + }; + + using PortManager = PortManagerImpl; + private: + PortManager m_port_manager; + typename PortInfo::Allocator m_port_allocator; + public: + constexpr ServerManagerImpl() : m_port_manager(), m_port_allocator() { /* ... */ } + + void Initialize() { + /* Initialize our port manager. */ + m_port_manager.Initialize(); + } + + void RegisterPort(tipc::NativeHandle port_handle) { + m_port_manager.RegisterPort(port_handle); + } + + void RegisterPort(sm::ServiceName service_name, size_t max_sessions) { + /* Register service. */ + tipc::NativeHandle port_handle; + R_ABORT_UNLESS(sm::RegisterService(std::addressof(port_handle), service_name, max_sessions, false)); + + /* Register the port handle. */ + this->RegisterPort(port_handle); + } + + void LoopAuto() { + /* Process for the only port. */ + this->LoopProcess(m_port_manager); + } + + tipc::ServiceObjectBase *AllocateObject() { + /* Allocate the object. */ + auto * const new_object = m_port_allocator.Allocate(); + AMS_ABORT_UNLESS(new_object != nullptr); + + /* If we should, set the object's deleter. */ + if constexpr (IsServiceObjectDeleter<typename PortInfo::Allocator>) { + new_object->SetDeleter(std::addressof(m_port_allocator)); + } + + return new_object; + } + + Result AddSession(tipc::NativeHandle *out, tipc::ServiceObjectBase *object) { + /* Create a handle for the session. */ + tipc::NativeHandle session_handle; + R_TRY(tipc::impl::CreateSession(std::addressof(session_handle), static_cast<tipc::NativeHandle *>(out), false, 0)); + + /* Add the session to our manager. */ + m_port_manager.AddSession(session_handle, object); + + R_SUCCEED(); + } + private: + void LoopProcess(PortManagerBase &port_manager) { + /* Process requests forever. */ + tipc::NativeHandle reply_target = tipc::InvalidNativeHandle; + while (true) { + /* Reply to our pending request, and wait to receive a new one. */ + tipc::ObjectHolder signaled_object{}; + while (!port_manager.ReplyAndReceive(std::addressof(signaled_object), reply_target)) { + reply_target = tipc::InvalidNativeHandle; + } + + /* A session was signaled, accessible via signaled_object. */ + switch (signaled_object.GetType()) { + case ObjectHolder::ObjectType_Port: + { + /* Try to accept a new session */ + tipc::NativeHandle session_handle; + if (R_SUCCEEDED(tipc::impl::AcceptSession(std::addressof(session_handle), signaled_object.GetHandle()))) { + port_manager.AddSession(session_handle, this->AllocateObject()); + } + + /* We have nothing to reply to. */ + reply_target = tipc::InvalidNativeHandle; + } + break; + case ObjectHolder::ObjectType_Session: + { + /* Process the request */ + reply_target = port_manager.ProcessRequest(signaled_object); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + }; + + } + + template<size_t ThreadStackSize, typename... PortInfos> + using ServerManagerImpl = impl::ServerManagerImpl<ThreadStackSize, (PortInfos::CanDeferInvokeRequest || ...), sizeof...(PortInfos), PortInfos...>; + + + template<typename... PortInfos> + using ServerManager = ServerManagerImpl<os::MemoryPageSize, PortInfos...>; + + template<size_t ThreadStackSize, typename... PortInfos> + using ServerManagerWithThreadStack = ServerManagerImpl<ThreadStackSize, PortInfos...>; + +} + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_service_object.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_service_object.hpp new file mode 100644 index 00000000..0f65da53 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_service_object.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/tipc/tipc_service_object_base.hpp> +#include <stratosphere/tipc/impl/tipc_impl_template_base.hpp> + +namespace ams::tipc { + + namespace impl { + + template<typename Impl> + class EmplacedImplHolderBaseGetter { + public: + using Type = Impl; + }; + + template<typename Impl> + class EmplacedImplHolder { + template<typename, typename, typename, typename, typename> + friend class impl::ImplTemplateBaseT; + private: + using Impl2 = typename EmplacedImplHolderBaseGetter<Impl>::Type; + static_assert(!std::is_abstract<Impl2>::value); + private: + Impl2 m_impl; + private: + template<typename... Args> + constexpr explicit EmplacedImplHolder(Args &&... args) : m_impl(std::forward<Args>(args)...) { /* ... */ } + public: + static constexpr Impl *GetImplPointer(EmplacedImplHolder *holder) { + return std::addressof(holder->m_impl); + } + }; + + } + + template<typename Interface, typename Impl> + class ServiceObject final : public impl::ImplTemplateBase<Interface, Interface, impl::EmplacedImplHolder<Impl>, impl::EmplacedImplHolder<Impl>> { + private: + using ImplBase = impl::ImplTemplateBase<Interface, Interface, impl::EmplacedImplHolder<Impl>, impl::EmplacedImplHolder<Impl>>; + public: + using ImplBase::ImplBase; + + constexpr Impl &GetImpl() { return *impl::EmplacedImplHolder<Impl>::GetImplPointer(this); } + }; + +} + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_service_object_base.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_service_object_base.hpp new file mode 100644 index 00000000..edae67bd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tipc/tipc_service_object_base.hpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/tipc/tipc_message_types.hpp> + +namespace ams::tipc { + + class ServiceObjectBase; + + class ServiceObjectDeleter { + public: + virtual void DeleteServiceObject(ServiceObjectBase *object) = 0; + }; + + template<typename T> + concept IsServiceObjectDeleter = std::derived_from<T, ServiceObjectDeleter>; + + class ServiceObjectBase { + private: + ServiceObjectDeleter *m_deleter; + public: + constexpr ALWAYS_INLINE ServiceObjectBase() : m_deleter(nullptr) { /* ... */ } + + ALWAYS_INLINE void SetDeleter(ServiceObjectDeleter *deleter) { + m_deleter = deleter; + } + + ALWAYS_INLINE ServiceObjectDeleter *GetDeleter() const { + return m_deleter; + } + + virtual ~ServiceObjectBase() { /* ... */ } + virtual Result ProcessRequest() = 0; + }; + + template<typename T> + concept IsServiceObject = std::derived_from<T, ServiceObjectBase>; + +} + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma.hpp new file mode 100644 index 00000000..cc9126e5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma.hpp @@ -0,0 +1,20 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <stratosphere/tma/tma_i_htc_manager.hpp> +#include <stratosphere/tma/tma_i_htcs_manager.hpp> +#include <stratosphere/tma/tma_i_file_manager.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_i_directory_accessor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_i_directory_accessor.hpp new file mode 100644 index 00000000..876c5d63 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_i_directory_accessor.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/fs/fs_directory.hpp> + +/* NOTE: Minimum firmware version not enforced for any commands. */ +#define AMS_TMA_I_DIRECTORY_ACCESSOR_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GetEntryCount, (ams::sf::Out<s64> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, Read, (ams::sf::Out<s64> out, const ams::sf::OutMapAliasArray<fs::DirectoryEntry> &out_entries), (out, out_entries)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, SetPriorityForDirectory, (s32 priority), (priority)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, GetPriorityForDirectory, (ams::sf::Out<s32> out), (out)) + +AMS_SF_DEFINE_INTERFACE(ams::tma, IDirectoryAccessor, AMS_TMA_I_DIRECTORY_ACCESSOR_INTERFACE_INFO, 0x070BADB5) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_i_file_accessor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_i_file_accessor.hpp new file mode 100644 index 00000000..487156fb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_i_file_accessor.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/sf.hpp> +#include <stratosphere/fs/fs_file.hpp> + +/* NOTE: Minimum firmware version not enforced for any commands. */ +#define AMS_TMA_I_FILE_ACCESSOR_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, ReadFile, (ams::sf::Out<s64> out, s64 offset, const ams::sf::OutNonSecureBuffer &buffer, ams::fs::ReadOption option), (out, offset, buffer, option)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, WriteFile, (s64 offset, const ams::sf::InNonSecureBuffer &buffer, ams::fs::WriteOption option), (offset, buffer, option)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, GetFileSize, (ams::sf::Out<s64> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, SetFileSize, (s64 size), (size)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, FlushFile, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, SetPriorityForFile, (s32 priority), (priority)) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, GetPriorityForFile, (ams::sf::Out<s32> out), (out)) + +AMS_SF_DEFINE_INTERFACE(ams::tma, IFileAccessor, AMS_TMA_I_FILE_ACCESSOR_INTERFACE_INFO, 0x985A04E3) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_i_file_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_i_file_manager.hpp new file mode 100644 index 00000000..1949079e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_i_file_manager.hpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/tma/tma_i_directory_accessor.hpp> +#include <stratosphere/tma/tma_i_file_accessor.hpp> +#include <stratosphere/tma/tma_path.hpp> + +/* NOTE: Minimum firmware version not enforced for any commands. */ +#define AMS_TMA_I_FILE_MANAGER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, OpenFile, (sf::Out<sf::SharedPointer<tma::IFileAccessor>> out, const tma::Path &path, u32 open_mode, bool case_sensitive), (out, path, open_mode, case_sensitive)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, FileExists, (sf::Out<bool> out, const tma::Path &path, bool case_sensitive), (out, path, case_sensitive)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, DeleteFile, (const tma::Path &path, bool case_sensitive), (path, case_sensitive)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, RenameFile, (const tma::Path &old_path, const tma::Path &new_path, bool case_sensitive), (old_path, new_path, case_sensitive)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, GetIOType, (sf::Out<s32> out, const tma::Path &path, bool case_sensitive), (out, path, case_sensitive)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, OpenDirectory, (sf::Out<sf::SharedPointer<tma::IDirectoryAccessor>> out, const tma::Path &path, s32 open_mode, bool case_sensitive), (out, path, open_mode, case_sensitive)) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, DirectoryExists, (sf::Out<bool> out, const tma::Path &path, bool case_sensitive), (out, path, case_sensitive)) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, CreateDirectory, (const tma::Path &path, bool case_sensitive), (path, case_sensitive)) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, DeleteDirectory, (const tma::Path &path, bool recursively, bool case_sensitive), (path, recursively, case_sensitive)) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, RenameDirectory, (const tma::Path &old_path, const tma::Path &new_path, bool case_sensitive), (old_path, new_path, case_sensitive)) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, CreateFile, (const tma::Path &path, s64 size, bool case_sensitive), (path, size, case_sensitive)) \ + AMS_SF_METHOD_INFO(C, H, 11, Result, GetFileTimeStamp, (sf::Out<u64> out_create, sf::Out<u64> out_access, sf::Out<u64> out_modify, const tma::Path &path, bool case_sensitive), (out_create, out_access, out_modify, path, case_sensitive)) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, GetCaseSensitivePath, (const tma::Path &path, const sf::OutBuffer &out), (path, out)) \ + AMS_SF_METHOD_INFO(C, H, 13, Result, GetDiskFreeSpaceExW, (sf::Out<s64> out_free, sf::Out<s64> out_total, sf::Out<s64> out_total_free, const tma::Path &path), (out_free, out_total, out_total_free, path)) + +AMS_SF_DEFINE_INTERFACE(ams::tma, IFileManager, AMS_TMA_I_FILE_MANAGER_INTERFACE_INFO, 0xA15AF3E1) + +/* Prior to system version 6.0.0, case sensitivity was not parameterized. */ +#define AMS_TMA_I_DEPRECATED_FILE_MANAGER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, OpenFile, (sf::Out<sf::SharedPointer<tma::IFileAccessor>> out, const tma::Path &path, u32 open_mode), (out, path, open_mode)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, FileExists, (sf::Out<bool> out, const tma::Path &path), (out, path)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, DeleteFile, (const tma::Path &path), (path)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, RenameFile, (const tma::Path &old_path, const tma::Path &new_path), (old_path, new_path)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, GetIOType, (sf::Out<s32> out, const tma::Path &path), (out, path)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, OpenDirectory, (sf::Out<sf::SharedPointer<tma::IDirectoryAccessor>> out, const tma::Path &path, s32 open_mode), (out, path, open_mode)) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, DirectoryExists, (sf::Out<bool> out, const tma::Path &path), (out, path)) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, CreateDirectory, (const tma::Path &path), (path)) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, DeleteDirectory, (const tma::Path &path, bool recursively), (path, recursively)) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, RenameDirectory, (const tma::Path &old_path, const tma::Path &new_path), (old_path, new_path)) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, CreateFile, (const tma::Path &path, s64 size), (path, size)) \ + AMS_SF_METHOD_INFO(C, H, 11, Result, GetFileTimeStamp, (sf::Out<u64> out_create, sf::Out<u64> out_access, sf::Out<u64> out_modify, const tma::Path &path), (out_create, out_access, out_modify, path)) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, GetCaseSensitivePath, (const tma::Path &path, const sf::OutBuffer &out), (path, out)) \ + AMS_SF_METHOD_INFO(C, H, 13, Result, GetDiskFreeSpaceExW, (sf::Out<s64> out_free, sf::Out<s64> out_total, sf::Out<s64> out_total_free, const tma::Path &path), (out_free, out_total, out_total_free, path)) + +AMS_SF_DEFINE_INTERFACE(ams::tma, IDeprecatedFileManager, AMS_TMA_I_DEPRECATED_FILE_MANAGER_INTERFACE_INFO, 0xA15AF3E1) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_i_htc_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_i_htc_manager.hpp new file mode 100644 index 00000000..065719a4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_i_htc_manager.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +/* NOTE: Minimum firmware version not enforced for any commands. */ +#define AMS_TMA_I_HTC_MANAGER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GetEnvironmentVariable, (sf::Out<s32> out_size, const sf::OutBuffer &out, const sf::InBuffer &name), (out_size, out, name)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, GetEnvironmentVariableLength, (sf::Out<s32> out_size, const sf::InBuffer &name), (out_size, name)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, GetHostConnectionEvent, (sf::OutCopyHandle out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, GetHostDisconnectionEvent, (sf::OutCopyHandle out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, GetHostConnectionEventForSystem, (sf::OutCopyHandle out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, GetHostDisconnectionEventForSystem, (sf::OutCopyHandle out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, GetBridgeIpAddress, (const sf::OutBuffer &out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, GetBridgePort, (const sf::OutBuffer &out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, SetCradleAttached, (bool attached), (attached)) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, GetBridgeSubnetMask, (const sf::OutBuffer &out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, GetBridgeMacAddress, (const sf::OutBuffer &out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 11, Result, GetWorkingDirectoryPath, (const sf::OutBuffer &out, s32 max_len), (out, max_len)) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, GetWorkingDirectoryPathSize, (sf::Out<s32> out_size), (out_size)) \ + AMS_SF_METHOD_INFO(C, H, 13, Result, RunOnHostStart, (sf::Out<u32> out_id, sf::OutCopyHandle out, const sf::InBuffer &args), (out_id, out, args)) \ + AMS_SF_METHOD_INFO(C, H, 14, Result, RunOnHostResults, (sf::Out<s32> out_result, u32 id), (out_result, id)) \ + AMS_SF_METHOD_INFO(C, H, 15, Result, SetBridgeIpAddress, (const sf::InBuffer &arg), (arg)) \ + AMS_SF_METHOD_INFO(C, H, 16, Result, SetBridgeSubnetMask, (const sf::InBuffer &arg), (arg)) \ + AMS_SF_METHOD_INFO(C, H, 17, Result, SetBridgePort, (const sf::InBuffer &arg), (arg)) + +AMS_SF_DEFINE_INTERFACE(ams::tma, IHtcManager, AMS_TMA_I_HTC_MANAGER_INTERFACE_INFO, 0x8591F069) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_i_htcs_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_i_htcs_manager.hpp new file mode 100644 index 00000000..6a34c983 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_i_htcs_manager.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/htcs/htcs_types.hpp> +#include <stratosphere/tma/tma_i_socket.hpp> + +/* NOTE: Minimum firmware version not enforced for any commands. */ +#define AMS_TMA_I_HTCS_MANAGER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, Socket, (sf::Out<s32> out_err, sf::Out<s32> out_sock), (out_err, out_sock)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, Close, (sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc), (out_err, out_res, desc)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, Connect, (sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, const htcs::SockAddrHtcs &address), (out_err, out_res, desc, address)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, Bind, (sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, const htcs::SockAddrHtcs &address), (out_err, out_res, desc, address)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, Listen, (sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, s32 backlog_count), (out_err, out_res, desc, backlog_count)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, Accept, (sf::Out<s32> out_err, sf::Out<s32> out_res, sf::Out<htcs::SockAddrHtcs> out_address, s32 desc), (out_err, out_res, out_address, desc)) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, Recv, (sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::OutBuffer &buffer, s32 desc, s32 flags), (out_err, out_size, buffer, desc, flags)) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, Send, (sf::Out<s32> out_err, sf::Out<s64> out_size, s32 desc, const sf::InBuffer &buffer, s32 flags), (out_err, out_size, desc, buffer, flags)) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, Shutdown, (sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, s32 how), (out_err, out_res, desc, how)) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, Fcntl, (sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, s32 command, s32 value), (out_err, out_res, desc, command, value)) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, GetPeerNameAny, (sf::Out<htcs::HtcsPeerName> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 11, Result, GetDefaultHostName, (sf::Out<htcs::HtcsPeerName> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, CreateSocketOld, (sf::Out<s32> out_err, sf::Out<sf::SharedPointer<tma::ISocket>> out), (out_err, out)) \ + AMS_SF_METHOD_INFO(C, H, 13, Result, CreateSocket, (sf::Out<s32> out_err, sf::Out<sf::SharedPointer<tma::ISocket>> out, bool enable_disconnection_emulation), (out_err, out, enable_disconnection_emulation)) \ + AMS_SF_METHOD_INFO(C, H, 100, Result, RegisterProcessId, (const sf::ClientProcessId &client_pid), (client_pid)) \ + AMS_SF_METHOD_INFO(C, H, 101, Result, MonitorManager, (const sf::ClientProcessId &client_pid), (client_pid)) \ + AMS_SF_METHOD_INFO(C, H, 130, Result, StartSelect, (sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, const sf::InMapAliasArray<s32> &read_handles, const sf::InMapAliasArray<s32> &write_handles, const sf::InMapAliasArray<s32> &exception_handles, s64 tv_sec, s64 tv_usec), (out_task_id, out_event, read_handles, write_handles, exception_handles, tv_sec, tv_usec)) \ + AMS_SF_METHOD_INFO(C, H, 131, Result, EndSelect, (sf::Out<s32> out_err, sf::Out<s32> out_count, const sf::OutMapAliasArray<s32> &read_handles, const sf::OutMapAliasArray<s32> &write_handles, const sf::OutMapAliasArray<s32> &exception_handles, u32 task_id), (out_err, out_count, read_handles, write_handles, exception_handles, task_id)) + +AMS_SF_DEFINE_INTERFACE(ams::tma, IHtcsManager, AMS_TMA_I_HTCS_MANAGER_INTERFACE_INFO, 0x91ECD04F) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_i_socket.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_i_socket.hpp new file mode 100644 index 00000000..7adb35f5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_i_socket.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/htcs/htcs_types.hpp> +#include <stratosphere/tma/tma_i_socket.hpp> + +/* NOTE: Minimum firmware version not enforced for any commands. */ +#define AMS_TMA_I_SOCKET_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, Close, (sf::Out<s32> out_err, sf::Out<s32> out_res), (out_err, out_res)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, Connect, (sf::Out<s32> out_err, sf::Out<s32> out_res, const htcs::SockAddrHtcs &address), (out_err, out_res, address)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, Bind, (sf::Out<s32> out_err, sf::Out<s32> out_res, const htcs::SockAddrHtcs &address), (out_err, out_res, address)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, Listen, (sf::Out<s32> out_err, sf::Out<s32> out_res, s32 backlog_count), (out_err, out_res, backlog_count)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, Accept, (sf::Out<s32> out_err, sf::Out<sf::SharedPointer<tma::ISocket>> out, sf::Out<htcs::SockAddrHtcs> out_address), (out_err, out, out_address)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, Recv, (sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::OutAutoSelectBuffer &buffer, s32 flags), (out_err, out_size, buffer, flags)) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, Send, (sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::InAutoSelectBuffer &buffer, s32 flags), (out_err, out_size, buffer, flags)) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, Shutdown, (sf::Out<s32> out_err, sf::Out<s32> out_res, s32 how), (out_err, out_res, how)) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, Fcntl, (sf::Out<s32> out_err, sf::Out<s32> out_res, s32 command, s32 value), (out_err, out_res, command, value)) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, AcceptStart, (sf::Out<u32> out_task_id, sf::OutCopyHandle out_event), (out_task_id, out_event)) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, AcceptResults, (sf::Out<s32> out_err, sf::Out<sf::SharedPointer<tma::ISocket>> out, sf::Out<htcs::SockAddrHtcs> out_address, u32 task_id), (out_err, out, out_address, task_id)) \ + AMS_SF_METHOD_INFO(C, H, 11, Result, RecvStart, (sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, s32 mem_size, s32 flags), (out_task_id, out_event, mem_size, flags)) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, RecvResults, (sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::OutAutoSelectBuffer &buffer, u32 task_id), (out_err, out_size, buffer, task_id)) \ + AMS_SF_METHOD_INFO(C, H, 13, Result, RecvLargeStart, (sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, s32 unaligned_size_start, s32 unaligned_size_end, s64 aligned_size, sf::CopyHandle &&mem_handle, s32 flags), (out_task_id, out_event, unaligned_size_start, unaligned_size_end, aligned_size, std::move(mem_handle), flags)) \ + AMS_SF_METHOD_INFO(C, H, 14, Result, SendStartOld, (sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, const sf::InAutoSelectBuffer &buffer, s32 flags), (out_task_id, out_event, buffer, flags)) \ + AMS_SF_METHOD_INFO(C, H, 15, Result, SendLargeStart, (sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, const sf::InAutoSelectBuffer &start_buffer, const sf::InAutoSelectBuffer &end_buffer, sf::CopyHandle &&mem_handle, s64 aligned_size, s32 flags), (out_task_id, out_event, start_buffer, end_buffer, std::move(mem_handle), aligned_size, flags)) \ + AMS_SF_METHOD_INFO(C, H, 16, Result, SendResults, (sf::Out<s32> out_err, sf::Out<s64> out_size, u32 task_id), (out_err, out_size, task_id)) \ + AMS_SF_METHOD_INFO(C, H, 17, Result, StartSend, (sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, sf::Out<s64> out_max_size, s64 size, s32 flags), (out_task_id, out_event, out_max_size, size, flags)) \ + AMS_SF_METHOD_INFO(C, H, 18, Result, ContinueSendOld, (sf::Out<s64> out_size, sf::Out<bool> out_wait, const sf::InAutoSelectBuffer &buffer, u32 task_id), (out_size, out_wait, buffer, task_id)) \ + AMS_SF_METHOD_INFO(C, H, 19, Result, EndSend, (sf::Out<s32> out_err, sf::Out<s64> out_size, u32 task_id), (out_err, out_size, task_id)) \ + AMS_SF_METHOD_INFO(C, H, 20, Result, StartRecv, (sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, s64 size, s32 flags), (out_task_id, out_event, size, flags)) \ + AMS_SF_METHOD_INFO(C, H, 21, Result, EndRecv, (sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::OutAutoSelectBuffer &buffer, u32 task_id), (out_err, out_size, buffer, task_id)) \ + AMS_SF_METHOD_INFO(C, H, 22, Result, SendStart, (sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, const sf::InNonSecureAutoSelectBuffer &buffer, s32 flags), (out_task_id, out_event, buffer, flags)) \ + AMS_SF_METHOD_INFO(C, H, 23, Result, ContinueSend, (sf::Out<s64> out_size, sf::Out<bool> out_wait, const sf::InNonSecureAutoSelectBuffer &buffer, u32 task_id), (out_size, out_wait, buffer, task_id)) \ + AMS_SF_METHOD_INFO(C, H, 130, Result, GetPrimitive, (sf::Out<s32> out), (out)) + +AMS_SF_DEFINE_INTERFACE(ams::tma, ISocket, AMS_TMA_I_SOCKET_INTERFACE_INFO, 0x34CFC7C1) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_path.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_path.hpp new file mode 100644 index 00000000..8e81fd41 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/tma/tma_path.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fs_common.hpp> +#include <stratosphere/fs/fs_directory.hpp> +#include <stratosphere/sf/sf_buffer_tags.hpp> + +namespace ams::tma { + + struct Path : ams::sf::LargeData { + char str[fs::EntryNameLengthMax + 1]; + + static constexpr Path Encode(const char *p) { + Path path = {}; + for (size_t i = 0; i < sizeof(path) - 1; i++) { + path.str[i] = p[i]; + if (p[i] == '\x00') { + break; + } + } + return path; + } + + static constexpr size_t GetLength(const Path &path) { + size_t len = 0; + for (size_t i = 0; i < sizeof(path) - 1 && path.str[i] != '\x00'; i++) { + len++; + } + return len; + } + }; + + static_assert(util::is_pod<Path>::value && sizeof(Path) == fs::EntryNameLengthMax + 1); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/updater.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/updater.hpp new file mode 100644 index 00000000..340947f6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/updater.hpp @@ -0,0 +1,20 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/updater/updater_types.hpp> +#include <stratosphere/updater/updater_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/updater/updater_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/updater/updater_api.hpp new file mode 100644 index 00000000..8d0e5c48 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/updater/updater_api.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/updater/updater_types.hpp> +#include <stratosphere/spl/spl_types.hpp> + +namespace ams::updater { + + /* Public API. */ + BootImageUpdateType GetBootImageUpdateType(int boot_image_update_type); + BootImageUpdateType GetBootImageUpdateType(spl::HardwareType hw_type); + + Result GetBootImagePackageId(ncm::SystemDataId *out_data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size); + + Result MarkVerifyingRequired(BootModeType mode, void *work_buffer, size_t work_buffer_size); + Result MarkVerified(BootModeType mode, void *work_buffer, size_t work_buffer_size); + + Result UpdateBootImagesFromPackage(ncm::SystemDataId boot_image_package_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type); + + Result VerifyBootImagesAndRepairIfNeeded(bool *out_repaired_normal, bool *out_repaired_safe, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/updater/updater_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/updater/updater_types.hpp new file mode 100644 index 00000000..64e13d43 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/updater/updater_types.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::updater { + + /* Types. */ + enum class BootImageUpdateType { + Erista, + Mariko, + }; + + enum class BootModeType { + Normal, + Safe, + }; + + struct VerificationState { + bool needs_verify_normal; + bool needs_verify_safe; + }; + + /* Convenience size definitions. */ + constexpr size_t BctSize = 0x4000; + constexpr size_t EksSize = 0x4000; + constexpr size_t EksEntrySize = 0x200; + constexpr size_t EksBlobSize = 0xB0; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb.hpp new file mode 100644 index 00000000..0394fbc8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb.hpp @@ -0,0 +1,21 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <stratosphere/usb/usb_limits.hpp> +#include <stratosphere/usb/usb_types.hpp> +#include <stratosphere/usb/usb_device_types.hpp> +#include <stratosphere/usb/usb_device.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/ds/usb_i_ds_endpoint.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/ds/usb_i_ds_endpoint.hpp new file mode 100644 index 00000000..bdbc80e8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/ds/usb_i_ds_endpoint.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/usb/usb_limits.hpp> +#include <stratosphere/usb/usb_types.hpp> +#include <stratosphere/usb/usb_device_types.hpp> + +#define AMS_USB_I_DS_ENDPOINT_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, PostBufferAsync, (sf::Out<u32> out_urb_id, u64 address, u32 size), (out_urb_id, address, size)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, Cancel, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, GetCompletionEvent, (sf::OutCopyHandle out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, GetUrbReport, (sf::Out<usb::UrbReport> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, Stall, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, SetZlt, (bool zlt), (zlt)) + +/* TODO: Deprecated interface? */ + +AMS_SF_DEFINE_INTERFACE(ams::usb::ds, IDsEndpoint, AMS_USB_I_DS_ENDPOINT_INTERFACE_INFO, 0x107E43A4) + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/ds/usb_i_ds_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/ds/usb_i_ds_interface.hpp new file mode 100644 index 00000000..36b96f08 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/ds/usb_i_ds_interface.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/usb/usb_limits.hpp> +#include <stratosphere/usb/usb_types.hpp> +#include <stratosphere/usb/usb_device_types.hpp> +#include <stratosphere/usb/ds/usb_i_ds_endpoint.hpp> + +#define AMS_USB_I_DS_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, RegisterEndpoint, (u8 endpoint_address, sf::Out<sf::SharedPointer<usb::ds::IDsEndpoint>> out), (endpoint_address, out)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, GetSetupEvent, (sf::OutCopyHandle out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, GetSetupPacket, (const sf::OutBuffer &out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, CtrlInAsync, (sf::Out<u32> out_urb_id, u64 address, u32 size), (out_urb_id, address, size)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, CtrlOutAsync, (sf::Out<u32> out_urb_id, u64 address, u32 size), (out_urb_id, address, size)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, GetCtrlInCompletionEvent, (sf::OutCopyHandle out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, GetCtrlInUrbReport, (sf::Out<usb::UrbReport> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, GetCtrlOutCompletionEvent, (sf::OutCopyHandle out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, GetCtrlOutUrbReport, (sf::Out<usb::UrbReport> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, CtrlStall, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, AppendConfigurationData, (u8 bInterfaceNumber, usb::UsbDeviceSpeed device_speed, const sf::InBuffer &data), (bInterfaceNumber, device_speed, data)) \ + AMS_SF_METHOD_INFO(C, H, 65000, Result, Enable, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 65001, Result, Disable, (), ()) + +/* TODO: Deprecated interface? */ + +AMS_SF_DEFINE_INTERFACE(ams::usb::ds, IDsInterface, AMS_USB_I_DS_INTERFACE_INTERFACE_INFO, 0x5632AFB2) + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/ds/usb_i_ds_service.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/ds/usb_i_ds_service.hpp new file mode 100644 index 00000000..2877e341 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/ds/usb_i_ds_service.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/usb/usb_limits.hpp> +#include <stratosphere/usb/usb_types.hpp> +#include <stratosphere/usb/usb_device_types.hpp> +#include <stratosphere/usb/ds/usb_i_ds_interface.hpp> + +#define AMS_USB_I_DS_SERVICE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, Bind, (usb::ComplexId complex_id, sf::CopyHandle &&process_h), (complex_id, std::move(process_h))) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, RegisterInterface, (sf::Out<sf::SharedPointer<usb::ds::IDsInterface>> out, u8 bInterfaceNumber), (out, bInterfaceNumber)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, GetStateChangeEvent, (sf::OutCopyHandle out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, GetState, (sf::Out<usb::UsbState> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, ClearDeviceData, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, AddUsbStringDescriptor, (sf::Out<u8> out, const sf::InBuffer &desc), (out, desc)) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, DeleteUsbStringDescriptor, (u8 index), (index)) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, SetUsbDeviceDescriptor, (const sf::InBuffer &desc, usb::UsbDeviceSpeed speed), (desc, speed)) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, SetBinaryObjectStore, (const sf::InBuffer &bos), (bos)) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, Enable, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, Disable, (), ()) + +/* TODO: Deprecated interface? */ + +AMS_SF_DEFINE_INTERFACE(ams::usb::ds, IDsService, AMS_USB_I_DS_SERVICE_INTERFACE_INFO, 0x306DB3C1) + +#define AMS_USB_I_DS_ROOT_SESSION_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GetService, (sf::Out<sf::SharedPointer<usb::ds::IDsService>> out), (out)) + +AMS_SF_DEFINE_INTERFACE(ams::usb::ds, IDsRootSession, AMS_USB_I_DS_ROOT_SESSION_INTERFACE_INFO, 0x2EC38748) + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/usb_device.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/usb_device.hpp new file mode 100644 index 00000000..6c318560 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/usb_device.hpp @@ -0,0 +1,145 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_system_event.hpp> +#include <stratosphere/sf/sf_lmem_utility.hpp> +#include <stratosphere/usb/usb_limits.hpp> +#include <stratosphere/usb/usb_device_types.hpp> +#include <stratosphere/usb/ds/usb_i_ds_service.hpp> + +namespace ams::usb { + + class DsInterface; + class DsEndpoint; + + class DsClient { + friend class DsInterface; + friend class DsEndpoint; + private: + /* NOTE: Nintendo uses a UnitHeap here on newer firmware versions. */ + /* For now, we'll use an ExpHeap and do it the old way. */ + sf::ExpHeapAllocator m_allocator{}; + u8 m_heap_buffer[32_KB]; + lmem::HeapHandle m_heap_handle{}; + sf::SharedPointer<ds::IDsRootSession> m_root_session{}; + sf::SharedPointer<ds::IDsService> m_ds_service{}; + bool m_is_initialized{false}; + std::atomic<int> m_reference_count{0}; + os::SystemEventType m_state_change_event{}; + DsInterface *m_interfaces[DsLimitMaxInterfacesPerConfigurationCount]{}; + bool m_is_enabled{false}; + public: + DsClient() = default; + ~DsClient() { /* ... */ } + public: + Result Initialize(ComplexId complex_id); + Result Finalize(); + + bool IsInitialized(); + + Result EnableDevice(); + Result DisableDevice(); + + os::SystemEventType *GetStateChangeEvent(); + Result GetState(UsbState *out); + + Result ClearDeviceData(); + + Result AddUsbStringDescriptor(u8 *out_index, UsbStringDescriptor *desc); + Result DeleteUsbStringDescriptor(u8 index); + + Result SetUsbDeviceDescriptor(UsbDeviceDescriptor *desc, UsbDeviceSpeed speed); + + Result SetBinaryObjectStore(u8 *data, int size); + private: + Result AddInterface(DsInterface *intf, sf::SharedPointer<ds::IDsInterface> *out_srv, uint8_t bInterfaceNumber); + Result DeleteInterface(uint8_t bInterfaceNumber); + }; + + class DsInterface { + friend class DsEndpoint; + private: + DsClient *m_client; + sf::SharedPointer<ds::IDsInterface> m_interface; + bool m_is_initialized; + std::atomic<int> m_reference_count; + os::SystemEventType m_setup_event; + os::SystemEventType m_ctrl_in_completion_event; + os::SystemEventType m_ctrl_out_completion_event; + UrbReport m_report; + u8 m_interface_num; + DsEndpoint *m_endpoints[UsbLimitMaxEndpointsCount]; + public: + DsInterface() : m_client(nullptr), m_is_initialized(false), m_reference_count(0) { /* ... */ } + ~DsInterface() { /* ... */ } + public: + Result Initialize(DsClient *client, u8 bInterfaceNumber); + Result Finalize(); + + Result AppendConfigurationData(UsbDeviceSpeed speed, void *data, u32 size); + + bool IsInitialized(); + + os::SystemEventType *GetSetupEvent(); + Result GetSetupPacket(UsbCtrlRequest *out); + + Result Enable(); + Result Disable(); + + Result CtrlRead(u32 *out_transferred, void *dst, u32 size); + Result CtrlWrite(u32 *out_transferred, void *dst, u32 size); + Result CtrlDone(); + Result CtrlStall(); + private: + Result AddEndpoint(DsEndpoint *ep, u8 bEndpointAddress, sf::SharedPointer<ds::IDsEndpoint> *out); + Result DeleteEndpoint(u8 bEndpointAddress); + + Result CtrlIn(u32 *out_transferred, void *dst, u32 size); + Result CtrlOut(u32 *out_transferred, void *dst, u32 size); + }; + + class DsEndpoint { + private: + bool m_is_initialized; + bool m_is_new_format; + std::atomic<int> m_reference_count; + DsInterface *m_interface; + sf::SharedPointer<ds::IDsEndpoint> m_endpoint; + u8 m_address; + os::SystemEventType m_completion_event; + os::SystemEventType m_unknown_event; + public: + DsEndpoint() : m_is_initialized(false), m_is_new_format(false), m_reference_count(0) { /* ... */ } + public: + Result Initialize(DsInterface *interface, u8 bEndpointAddress); + Result Finalize(); + + bool IsInitialized(); + + Result PostBuffer(u32 *out_transferred, void *buf, u32 size); + Result PostBufferAsync(u32 *out_urb_id, void *buf, u32 size); + + os::SystemEventType *GetCompletionEvent(); + + Result GetUrbReport(UrbReport *out); + + Result Cancel(); + + Result SetZeroLengthTransfer(bool zlt); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/usb_device_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/usb_device_types.hpp new file mode 100644 index 00000000..2afc141d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/usb_device_types.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/usb/usb_limits.hpp> +#include <stratosphere/usb/usb_types.hpp> + +namespace ams::usb { + + constexpr inline u8 InterfaceNumberAuto = DsLimitMaxInterfacesPerConfigurationCount; + constexpr inline u8 EndpointAddressAutoIn = UsbEndpointAddressMask_DirDevicetoHost; + constexpr inline u8 EndpointAddressAutoOut = UsbEndpointAddressMask_DirHostToDevice; + + enum UrbStatus { + UrbStatus_Invalid = 0, + UrbStatus_Pending = 1, + UrbStatus_Running = 2, + UrbStatus_Finished = 3, + UrbStatus_Cancelled = 4, + UrbStatus_Failed = 5, + }; + + struct UrbReport { + struct Report { + u32 id; + u32 requested_size; + u32 transferred_size; + UrbStatus status; + } reports[DsLimitRingSize]; + u32 count; + }; + + enum DsString { + DsString_Max = 0x20, + }; + + struct DsVidPidBcd { + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + + char manufacturer[DsString_Max]; + char product[DsString_Max]; + char serial_number[DsString_Max]; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/usb_limits.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/usb_limits.hpp new file mode 100644 index 00000000..5e82129a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/usb_limits.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/dd/dd_device_address_space_common.hpp> + +namespace ams::usb { + + constexpr inline int HwLimitDmaBufferAlignmentSize = dd::DeviceAddressSpaceMemoryRegionAlignment; + constexpr inline int HwLimitDataCacheLineSize = 0x40; + constexpr inline int HwLimitMaxPortCount = 0x4; + + constexpr inline int UsbLimitMaxEndpointsCount = 0x20; + constexpr inline int UsbLimitMaxEndpointPairCount = 0x10; + + constexpr inline int DsLimitMaxConfigurationsPerDeviceCount = 1; + constexpr inline int DsLimitMaxInterfacesPerConfigurationCount = 4; + constexpr inline int DsLimitMaxNameSize = 0x40; + constexpr inline int DsLimitRingSize = 8; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/usb_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/usb_types.hpp new file mode 100644 index 00000000..52b50e6e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/usb/usb_types.hpp @@ -0,0 +1,223 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/usb/usb_limits.hpp> + +namespace ams::usb { + + constexpr ALWAYS_INLINE bool IsDmaAligned(u64 address) { + return util::IsAligned(address, static_cast<u64>(HwLimitDmaBufferAlignmentSize)); + } + + enum ComplexId { + ComplexId_Tegra21x = 2, + }; + + enum UsbDescriptorType { + UsbDescriptorType_Device = 1, + UsbDescriptorType_Config = 2, + UsbDescriptorType_String = 3, + UsbDescriptorType_Interface = 4, + UsbDescriptorType_Endpoint = 5, + UsbDescriptorType_DeviceQualifier = 6, + UsbDescriptorType_OtherSpeedConfig = 7, + UsbDescriptorType_InterfacePower = 8, + UsbDescriptorType_Otg = 9, + UsbDescriptorType_Debug = 10, + UsbDescriptorType_InterfaceAssociation = 11, + UsbDescriptorType_Bos = 15, + UsbDescriptorType_DeviceCapability = 16, + + UsbDescriptorType_Hid = 33, + UsbDescriptorType_Report = 34, + UsbDescriptorType_Physical = 35, + + UsbDescriptorType_Hub = 41, + + UsbDescriptorType_EndpointCompanion = 48, + UsbDescriptorType_IsocEndpointCompanion = 49, + }; + + struct UsbDescriptorHeader { + uint8_t bLength; + uint8_t bDescriptorType; + } PACKED; + + struct UsbInterfaceDescriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bNumEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; + } PACKED; + + struct UsbEndpointDescriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bEndpointAddress; + uint8_t bmAttributes; + uint16_t wMaxPacketSize; + uint8_t bInterval; + } PACKED; + + struct UsbDeviceDescriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; + } PACKED; + + struct UsbConfigDescriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wTotalLength; + uint8_t bNumInterfaces; + uint8_t bConfigurationValue; + uint8_t iConfiguration; + uint8_t bmAttributes; + uint8_t bMaxPower; + } PACKED; + + struct UsbEndpointCompanionDescriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bMaxBurst; + uint8_t bmAttributes; + uint16_t wBytesPerInterval; + } PACKED; + + struct UsbStringDescriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wData[DsLimitMaxNameSize]; + } PACKED; + + struct UsbCtrlRequest { + uint8_t bmRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; + } PACKED; + + enum UsbState { + UsbState_Detached = 0, + UsbState_Attached = 1, + UsbState_Powered = 2, + UsbState_Default = 3, + UsbState_Address = 4, + UsbState_Configured = 5, + UsbState_Suspended = 6, + }; + + enum UsbDescriptorSize { + UsbDescriptorSize_Interface = sizeof(UsbInterfaceDescriptor), + UsbDescriptorSize_Endpoint = sizeof(UsbEndpointDescriptor), + UsbDescriptorSize_Device = sizeof(UsbDeviceDescriptor), + UsbDescriptorSize_Config = sizeof(UsbConfigDescriptor), + UsbDescriptorSize_EndpointCompanion = sizeof(UsbEndpointCompanionDescriptor), + }; + + enum UsbDeviceSpeed { + UsbDeviceSpeed_Invalid = 0, + UsbDeviceSpeed_Low = 1, + UsbDeviceSpeed_Full = 2, + UsbDeviceSpeed_High = 3, + UsbDeviceSpeed_Super = 4, + UsbDeviceSpeed_SuperPlus = 5, + }; + + + enum UsbEndpointAddressMask { + UsbEndpointAddressMask_EndpointNumber = (0xF << 0), + + UsbEndpointAddressMask_Dir = (0x1 << 7), + + UsbEndpointAddressMask_DirHostToDevice = (0x0 << 7), + UsbEndpointAddressMask_DirDevicetoHost = (0x1 << 7), + }; + + enum UsbEndpointAttributeMask { + UsbEndpointAttributeMask_XferType = (0x3 << 0), + + UsbEndpointAttributeMask_XferTypeControl = (0x0 << 0), + UsbEndpointAttributeMask_XferTypeIsoc = (0x1 << 0), + UsbEndpointAttributeMask_XferTypeBulk = (0x2 << 0), + UsbEndpointAttributeMask_XferTypeInt = (0x3 << 0), + }; + + enum UsbEndpointDirection { + UsbEndpointDirection_Invalid = 0, + UsbEndpointDirection_ToDevice = 1, + UsbEndpointDirection_ToHost = 2, + UsbEndpointDirection_Control = 3, + }; + + constexpr inline u8 UsbGetEndpointNumber(const UsbEndpointDescriptor *desc) { + return desc->bEndpointAddress & UsbEndpointAddressMask_EndpointNumber; + } + + constexpr inline bool UsbEndpointIsHostToDevice(const UsbEndpointDescriptor *desc) { + return (desc->bEndpointAddress & UsbEndpointAddressMask_Dir) == UsbEndpointAddressMask_DirHostToDevice; + } + + constexpr inline bool UsbEndpointIsDeviceToHost(const UsbEndpointDescriptor *desc) { + return (desc->bEndpointAddress & UsbEndpointAddressMask_Dir) == UsbEndpointAddressMask_DirDevicetoHost; + } + + constexpr inline u8 UsbGetEndpointAddress(u8 number, UsbEndpointDirection dir) { + u8 val = static_cast<u8>(number & UsbEndpointAddressMask_EndpointNumber); + if (dir == UsbEndpointDirection_ToHost) { + val |= UsbEndpointAddressMask_DirDevicetoHost; + } else { + val |= UsbEndpointAddressMask_DirHostToDevice; + } + return val; + } + + constexpr inline UsbEndpointDirection GetUsbEndpointDirection(const UsbEndpointDescriptor *desc) { + if (UsbEndpointIsDeviceToHost(desc)) { + return UsbEndpointDirection_ToHost; + } else { + return UsbEndpointDirection_ToDevice; + } + } + + constexpr inline bool UsbEndpointIsValid(const UsbEndpointDescriptor *desc) { + return desc != nullptr && desc->bLength >= UsbDescriptorSize_Endpoint && desc->bEndpointAddress != 0; + } + + constexpr inline void UsbMarkEndpointInvalid(UsbEndpointDescriptor *desc) { + desc->bLength = 0; + desc->bEndpointAddress = 0; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/util.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/util.hpp new file mode 100644 index 00000000..85f0625d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/util.hpp @@ -0,0 +1,22 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <stratosphere/util/util_uuid_api.hpp> +#include <stratosphere/util/util_compression.hpp> +#include <stratosphere/util/util_ini.hpp> +#include <stratosphere/util/util_singleton_traits.hpp> +#include <stratosphere/util/util_lock_free_atomic_type.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/util/util_compression.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/util/util_compression.hpp new file mode 100644 index 00000000..ac68b388 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/util/util_compression.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::util { + + /* Compression utilities. */ + int CompressLZ4(void *dst, size_t dst_size, const void *src, size_t src_size); + + /* Decompression utilities. */ + int DecompressLZ4(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/libraries/libstratosphere/include/stratosphere/util/util_ini.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/util/util_ini.hpp new file mode 100644 index 00000000..cb072240 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/util/util_ini.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/fs/fs_file.hpp> + +namespace ams::fs::fsa { + + class IFile; + +} + +namespace ams::util::ini { + + /* Ini handler type. */ + using Handler = int (*)(void *user_ctx, const char *section, const char *name, const char *value); + + /* Utilities for dealing with INI file configuration. */ + int ParseString(const char *ini_str, void *user_ctx, Handler h); + int ParseFile(fs::FileHandle file, void *user_ctx, Handler h); + int ParseFile(fs::fsa::IFile *file, void *user_ctx, Handler h); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/util/util_lock_free_atomic_type.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/util/util_lock_free_atomic_type.hpp new file mode 100644 index 00000000..1ede59f6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/util/util_lock_free_atomic_type.hpp @@ -0,0 +1,65 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_memory_fence_api.hpp> + +namespace ams::util { + + template<typename T> + struct LockFreeAtomicType { + volatile u32 _counter; + T _value[2]; + }; + + template<typename T> + void StoreToLockFreeAtomicType(LockFreeAtomicType<T> *p, const T &value) { + /* Get the current counter. */ + auto counter = p->_counter; + + /* Increment the counter. */ + ++counter; + + /* Store the updated value. */ + p->_value[counter % 2] = value; + + /* Fence memory. */ + os::FenceMemoryStoreStore(); + + /* Set the updated counter. */ + p->_counter = counter; + } + + template<typename T> + T LoadFromLockFreeAtomicType(const LockFreeAtomicType<T> *p) { + while (true) { + /* Get the counter. */ + auto counter = p->_counter; + + /* Get the value. */ + auto value = p->_value[counter % 2]; + + /* Fence memory. */ + os::FenceMemoryLoadLoad(); + + /* Check that the counter matches. */ + if (counter == p->_counter) { + return value; + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/util/util_singleton_traits.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/util/util_singleton_traits.hpp new file mode 100644 index 00000000..ba56dfb6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/util/util_singleton_traits.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> +#include <stratosphere/os/os_sdk_mutex.hpp> + +#define AMS_SINGLETON_TRAITS(_CLASSNAME_) \ + private: \ + NON_COPYABLE(_CLASSNAME_); \ + NON_MOVEABLE(_CLASSNAME_); \ + private: \ + _CLASSNAME_ (); \ + public: \ + static _CLASSNAME_ &GetInstance() { \ + AMS_FUNCTION_LOCAL_STATIC(_CLASSNAME_, s_singleton_instance); \ + \ + return s_singleton_instance; \ + } + +#define AMS_CONSTINIT_SINGLETON_TRAITS(_CLASSNAME_) \ + private: \ + NON_COPYABLE(_CLASSNAME_); \ + NON_MOVEABLE(_CLASSNAME_); \ + private: \ + constexpr _CLASSNAME_ () = default; \ + public: \ + static _CLASSNAME_ &GetInstance() { \ + /* Declare singleton instance variables. */ \ + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(_CLASSNAME_, s_singleton_instance); \ + return s_singleton_instance; \ + } diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/util/util_uuid_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/util/util_uuid_api.hpp new file mode 100644 index 00000000..13b5a93c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/util/util_uuid_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::util { + + /* Nintendo provides UUID generation following RFC 4122. */ + /* By default, UUIDs are generated as version 4 (random). */ + + Uuid GenerateUuid(); + Uuid GenerateUuidVersion5(const void *sha1_hash); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/vi.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/vi.hpp new file mode 100644 index 00000000..a4072b50 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/vi.hpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <stratosphere/vi/vi_layer_stack.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/vi/vi_layer_stack.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/vi/vi_layer_stack.hpp new file mode 100644 index 00000000..9ef44ffe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/vi/vi_layer_stack.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours.hpp> + +namespace ams::vi { + + enum LayerStack { + LayerStack_Default = 0, + LayerStack_Lcd = 1, + LayerStack_Screenshot = 2, + LayerStack_Recording = 3, + LayerStack_LastFrame = 4, + LayerStack_Arbitrary = 5, + LayerStack_ApplicationForDebug = 6, + LayerStack_Null = 10, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/wec.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/wec.hpp new file mode 100644 index 00000000..bf91b471 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/wec.hpp @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/wec/wec_types.hpp> +#include <stratosphere/wec/wec_api.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/wec/wec_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/wec/wec_api.hpp new file mode 100644 index 00000000..a2385ac9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/wec/wec_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <stratosphere/wec/wec_types.hpp> + +namespace ams::wec { + + void Initialize(); + void ClearWakeEvents(); + + void WecRestoreForExitSuspend(); + + void SetWakeEventLevel(wec::WakeEvent event, wec::WakeEventLevel level); + void SetWakeEventEnabled(wec::WakeEvent event, bool en); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/wec/wec_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/wec/wec_types.hpp new file mode 100644 index 00000000..39d915de --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/wec/wec_types.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + #include <stratosphere/wec/wec_wake_event.board.nintendo_nx.hpp> +#else + #include <stratosphere/wec/wec_wake_event.generic.hpp> +#endif + +namespace ams::wec { + + enum WakeEventLevel { + WakeEventLevel_Low = 0, + WakeEventLevel_High = 1, + WakeEventLevel_Auto = 2, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/wec/wec_wake_event.board.nintendo_nx.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/wec/wec_wake_event.board.nintendo_nx.hpp new file mode 100644 index 00000000..0d5aeaf6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/wec/wec_wake_event.board.nintendo_nx.hpp @@ -0,0 +1,88 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::wec { + + enum WakeEvent { + WakeEvent_PexWakeN = 0x00, + WakeEvent_GpioPortA6 = 0x01, + WakeEvent_QspiCsN = 0x02, + WakeEvent_Spi2Mosi = 0x03, + WakeEvent_ExtconDetS = 0x04, + WakeEvent_McuIrq = 0x05, + WakeEvent_Uart2Cts = 0x06, + WakeEvent_Uart3Cts = 0x07, + WakeEvent_WifiWakeAp = 0x08, + WakeEvent_AoTag2Pmc = 0x09, + WakeEvent_ExtconDetU = 0x0A, + WakeEvent_NfcInt = 0x0B, + WakeEvent_Gen1I2cSda = 0x0C, + WakeEvent_Gen2I2cSda = 0x0D, + WakeEvent_CradleIrq = 0x0E, + WakeEvent_GpioPortK6 = 0x0F, + WakeEvent_RtcIrq = 0x10, + WakeEvent_Sdmmc1Dat1 = 0x11, + WakeEvent_Sdmmc2Dat1 = 0x12, + WakeEvent_HdmiCec = 0x13, + WakeEvent_Gen3I2cSda = 0x14, + WakeEvent_GpioPortL1 = 0x15, + WakeEvent_Clk_32kOut = 0x16, + WakeEvent_PwrI2cSda = 0x17, + WakeEvent_ButtonPowerOn = 0x18, + WakeEvent_ButtonVolUp = 0x19, + WakeEvent_ButtonVolDown = 0x1A, + WakeEvent_ButtonSlideSw = 0x1B, + WakeEvent_ButtonHome = 0x1C, + /* ... */ + WakeEvent_AlsProxInt = 0x20, + WakeEvent_TempAlert = 0x21, + WakeEvent_Bq24190Irq = 0x22, + WakeEvent_SdCd = 0x23, + WakeEvent_GpioPortZ2 = 0x24, + /* ... */ + WakeEvent_Utmip0 = 0x27, + WakeEvent_Utmip1 = 0x28, + WakeEvent_Utmip2 = 0x29, + WakeEvent_Utmip3 = 0x2A, + WakeEvent_Uhsic = 0x2B, + WakeEvent_Wake2PmcXusbSystem = 0x2C, + WakeEvent_Sdmmc3Dat1 = 0x2D, + WakeEvent_Sdmmc4Dat1 = 0x2E, + WakeEvent_CamI2cScl = 0x2F, + WakeEvent_CamI2cSda = 0x30, + WakeEvent_GpioPortZ5 = 0x31, + WakeEvent_DpHpd0 = 0x32, + WakeEvent_PwrIntN = 0x33, + WakeEvent_BtWakeAp = 0x34, + WakeEvent_HdmiIntDpHpd = 0x35, + WakeEvent_UsbVbusEn0 = 0x36, + WakeEvent_UsbVbusEn1 = 0x37, + WakeEvent_LcdRst = 0x38, + WakeEvent_LcdGpio1 = 0x39, + WakeEvent_LcdGpio2 = 0x3A, + WakeEvent_Uart4Cts = 0x3B, + WakeEvent_ModemWakeAp = 0x3D, + WakeEvent_TouchInt = 0x3E, + WakeEvent_MotionInt = 0x3F, + + WakeEvent_Count = 0x40, + }; + + constexpr inline WakeEvent WakeEvent_None = static_cast<WakeEvent>(-1); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/wec/wec_wake_event.generic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/wec/wec_wake_event.generic.hpp new file mode 100644 index 00000000..13bae8a5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/wec/wec_wake_event.generic.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::wec { + + enum WakeEvent { + WakeEvent_Debug = 0, + + WakeEvent_Count = 1, + }; + + constexpr inline WakeEvent WakeEvent_None = static_cast<WakeEvent>(-1); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/windows.hpp new file mode 100644 index 00000000..3bba751c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/include/stratosphere/windows.hpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include <Windows.h> + +/* Collides with StandardUserSystemClock. */ +#ifdef GetCurrentTime + +ALWAYS_INLINE auto WindowsGetCurrentTime() { + return GetCurrentTime(); +} + +#undef GetCurrentTime + +#endif + +/* Collides with several things. */ +#ifdef FillMemory + +ALWAYS_INLINE auto WindowsFillMemory(PVOID p, SIZE_T l, BYTE v) { + return FillMemory(p, l, v); +} + +#undef FillMemory + +/* Should be provided by winerror.h, but isn't. */ +#if !defined(ERROR_SPACES_NOT_ENOUGH_DRIVES) +#define ERROR_SPACES_NOT_ENOUGH_DRIVES ((DWORD)0x80E7000Bu) +#endif + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/libstratosphere.mk b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/libstratosphere.mk new file mode 100644 index 00000000..5c444caf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/libstratosphere.mk @@ -0,0 +1,179 @@ +#--------------------------------------------------------------------------------- +# pull in common atmosphere configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(CURRENT_DIRECTORY)/../config/common.mk + +ifeq ($(ATMOSPHERE_BOARD),nx-hac-001) + +#--------------------------------------------------------------------------------- +# pull in switch rules +#--------------------------------------------------------------------------------- +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro") +endif + +include $(DEVKITPRO)/libnx/switch_rules + +endif + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ifeq ($(strip $(ATMOSPHERE_COMPILER_NAME)),gcc) +PRECOMPILED_HEADERS := include/stratosphere.hpp +else +PRECOMPILED_HEADERS := +endif + +ifeq ($(ATMOSPHERE_BUILD_FOR_DEBUGGING),1) +ATMOSPHERE_OPTIMIZATION_FLAG := -Os +else +ATMOSPHERE_OPTIMIZATION_FLAG := -O2 +endif + +DEFINES := $(ATMOSPHERE_DEFINES) -DATMOSPHERE_IS_STRATOSPHERE -D_GNU_SOURCE +SETTINGS := $(ATMOSPHERE_SETTINGS) $(ATMOSPHERE_OPTIMIZATION_FLAG) -Wextra -Werror -Wno-missing-field-initializers -flto +CFLAGS := $(ATMOSPHERE_CFLAGS) $(SETTINGS) $(DEFINES) $(INCLUDE) +CXXFLAGS := $(CFLAGS) $(ATMOSPHERE_CXXFLAGS) +ASFLAGS := $(ATMOSPHERE_ASFLAGS) $(SETTINGS) $(DEFINES) + +ifeq ($(ATMOSPHERE_BOARD),nx-hac-001) +LDFLAGS := -specs=$(DEVKITPRO)/libnx/switch.specs $(SETTINGS) -Wl,-Map,$(notdir $*.map) +else +LDFLAGS := -Wl,-Map,$(notdir $*.map) +endif + + +SOURCES += $(call ALL_SOURCE_DIRS,../libvapours/source) +SOURCES += $(call UNFILTERED_SOURCE_DIRS,source/os) + +ifeq ($(ATMOSPHERE_BOARD),nx-hac-001) +LIBS := -lnx +else +LIBS := +endif + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +ifeq ($(ATMOSPHERE_BOARD),nx-hac-001) +LIBDIRS := $(PORTLIBS) $(LIBNX) $(ATMOSPHERE_LIBDIRS) $(ATMOSPHERE_LIBRARIES_DIR)/libvapours +else +LIBDIRS := $(ATMOSPHERE_LIBDIRS) $(ATMOSPHERE_LIBRARIES_DIR)/libvapours +endif + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +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) + +#--------------------------------------------------------------------------------- +# 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 GCH_DIRS := $(PRECOMPILED_HEADERS:.hpp=.hpp.gch) +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. + +#--------------------------------------------------------------------------------- + +.PHONY: clean all + +all: $(ATMOSPHERE_LIBRARY_DIR) $(ATMOSPHERE_BUILD_DIR) $(SOURCES) $(INCLUDES) $(GCH_DIRS) $(CPPFILES) $(CFILES) $(SFILES) + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_LIBRARY_DIR)/$(TARGET).a \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +#--------------------------------------------------------------------------------- +clean: + @echo clean $(ATMOSPHERE_BUILD_NAME) ... + @rm -fr $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_OUT_DIR) + @rm -fr $(foreach hdr,$(GCH_DIRS),$(hdr)/$(ATMOSPHERE_GCH_IDENTIFIER)) + @for i in $(GCH_DIRS); do [ -d $$i ] && rmdir $$i 2>/dev/null || true; done + +$(ATMOSPHERE_LIBRARY_DIR) $(ATMOSPHERE_BUILD_DIR) $(GCH_DIRS): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +else + +GCH_FILES := $(foreach hdr,$(PRECOMPILED_HEADERS:.hpp=.hpp.gch),$(CURRENT_DIRECTORY)/$(hdr)/$(ATMOSPHERE_GCH_IDENTIFIER)) +DEPENDS := $(OFILES:.o=.d) $(foreach hdr,$(GCH_FILES),$(notdir $(patsubst %.hpp.gch/,%.d,$(dir $(hdr))))) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT) : result_get_name.o $(filter-out result_get_name.o, $(OFILES)) + +$(filter-out result_get_name.o, $(OFILES)) : $(GCH_FILES) + +$(OFILES_SRC) : $(HFILES_BIN) + +ams_environment_weak.os.horizon.o: CXXFLAGS += -fno-lto +hos_version_api_weak_for_unit_test.o: CXXFLAGS += -fno-lto +pm_info_api_weak.o: CXXFLAGS += -fno-lto +hos_stratosphere_api.o: CXXFLAGS += -fno-lto + +init_operator_new.o: CXXFLAGS += -fno-lto +init_libnx_shim.os.horizon.o: CXXFLAGS += -fno-lto + +result_get_name.o: CXXFLAGS += -fno-lto + +crypto_sha256_generator.o: CXXFLAGS += -fno-lto + +spl_secure_monitor_api.os.generic.o: CXXFLAGS += -I$(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/include +fs_id_string_impl.os.generic.o: CXXFLAGS += -I$(ATMOSPHERE_LIBRARIES_DIR)/libexosphere/include + +ifeq ($(ATMOSPHERE_OS_NAME),windows) +# Audit builds fail when these have lto disabled. +# Noting 10/29/24: +# In member function '__ct ': +# internal compiler error: in binds_to_current_def_p, at symtab.cc:2589 +os_%.o: CXXFLAGS += -fno-lto +fssystem_%.o: CXXFLAGS += -fno-lto +fssrv_%.o: CXXFLAGS += -fno-lto +fs_%.o: CXXFLAGS += -fno-lto +endif + +#--------------------------------------------------------------------------------- +%_bin.h %.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_bpc.os.horizon.c b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_bpc.os.horizon.c new file mode 100644 index 00000000..89180e96 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_bpc.os.horizon.c @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#define NX_SERVICE_ASSUME_NON_DOMAIN +#include "../service_guard.h" +#include "ams_bpc.os.horizon.h" + +static Service g_amsBpcSrv; + +NX_GENERATE_SERVICE_GUARD(amsBpc); + +Result _amsBpcInitialize(void) { + Handle h; + Result rc = svcConnectToNamedPort(&h, "bpc:ams"); /* TODO: ams:bpc */ + while (R_VALUE(rc) == KERNELRESULT(NotFound)) { + svcSleepThread(50000000ul); + rc = svcConnectToNamedPort(&h, "bpc:ams"); + } + + if (R_SUCCEEDED(rc)) serviceCreate(&g_amsBpcSrv, h); + return rc; +} + +void _amsBpcCleanup(void) { + serviceClose(&g_amsBpcSrv); +} + +Service *amsBpcGetServiceSession(void) { + return &g_amsBpcSrv; +} + +Result amsBpcRebootToFatalError(void *ctx) { + /* Note: this takes in an sts::ams::FatalErrorContext. */ + /* static_assert(sizeof() == 0x450) is done at type definition. */ + return serviceDispatch(&g_amsBpcSrv, 65000, + .buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcMapAlias | SfBufferAttr_FixedSize }, + .buffers = { { ctx, 0x450 } }, + ); +} + + +Result amsBpcSetRebootPayload(const void *src, size_t src_size) { + return serviceDispatch(&g_amsBpcSrv, 65001, + .buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcMapAlias }, + .buffers = { { src, src_size } }, + ); +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_bpc.os.horizon.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_bpc.os.horizon.h new file mode 100644 index 00000000..a3114ec8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_bpc.os.horizon.h @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <switch.h> + +#ifdef __cplusplus +extern "C" { +#endif + +Result amsBpcInitialize(void); +void amsBpcExit(void); +Service *amsBpcGetServiceSession(void); + +Result amsBpcRebootToFatalError(void *ctx); +Result amsBpcSetRebootPayload(const void *src, size_t src_size); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_emummc_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_emummc_api.cpp new file mode 100644 index 00000000..ca47cc45 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_emummc_api.cpp @@ -0,0 +1,145 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::emummc { + + namespace { + + /* Convenience Definitions. */ + constexpr u32 StorageMagic = util::FourCC<'E','F','S','0'>::Code; + constexpr size_t MaxDirLen = 0x7F; + + /* Types. */ + struct BaseConfig { + u32 magic; + u32 type; + u32 id; + u32 fs_version; + }; + + struct PartitionConfig { + u64 start_sector; + }; + + struct FileConfig { + char path[MaxDirLen + 1]; + }; + + struct ExosphereConfig { + BaseConfig base_cfg; + union { + PartitionConfig partition_cfg; + FileConfig file_cfg; + }; + char emu_dir_path[MaxDirLen + 1]; + }; + + enum Storage : u32 { + Storage_Emmc, + Storage_Sd, + Storage_SdFile, + + Storage_Count, + }; + + /* Globals. */ + constinit os::SdkMutex g_lock; + constinit ExosphereConfig g_exo_config = {}; + constinit bool g_is_emummc; + constinit bool g_has_cached; + + /* Helpers. */ + void CacheValues() { + std::scoped_lock lk(g_lock); + + if (g_has_cached) { + return; + } + + /* Retrieve and cache values. */ + { + + alignas(os::MemoryPageSize) std::byte path_storage[2 * (MaxDirLen + 1)]; + + struct { + char file_path[MaxDirLen + 1]; + char nintendo_path[MaxDirLen + 1]; + } *paths = reinterpret_cast<decltype(paths)>(std::addressof(path_storage)); + + /* Retrieve paths from secure monitor. */ + AMS_ABORT_UNLESS(spl::smc::AtmosphereGetEmummcConfig(std::addressof(g_exo_config), paths, 0) == spl::smc::Result::Success); + + const Storage storage = static_cast<Storage>(g_exo_config.base_cfg.type); + g_is_emummc = g_exo_config.base_cfg.magic == StorageMagic && storage != Storage_Emmc; + + /* Format paths. */ + { + char tmp_path[MaxDirLen + 1]; + + /* Format paths. */ + if (storage == Storage_SdFile) { + util::TSNPrintf(tmp_path, sizeof(tmp_path), "/%s", paths->file_path); + R_ABORT_UNLESS(fs::PathFormatter::Normalize(g_exo_config.file_cfg.path, sizeof(g_exo_config.file_cfg.path), tmp_path, std::strlen(tmp_path) + 1, fs::PathFlags{})); + } + + util::TSNPrintf(tmp_path, sizeof(tmp_path), "/%s", paths->nintendo_path); + R_ABORT_UNLESS(fs::PathFormatter::Normalize(g_exo_config.emu_dir_path, sizeof(g_exo_config.emu_dir_path), tmp_path, std::strlen(tmp_path) + 1, fs::PathFlags{})); + + /* If we're emummc, implement default nintendo redirection path. */ + if (g_is_emummc && std::strcmp(g_exo_config.emu_dir_path, "/") == 0) { + util::TSNPrintf(tmp_path, sizeof(tmp_path), "/emummc/Nintendo_%04x", g_exo_config.base_cfg.id); + R_ABORT_UNLESS(fs::PathFormatter::Normalize(g_exo_config.emu_dir_path, sizeof(g_exo_config.emu_dir_path), tmp_path, std::strlen(tmp_path) + 1, fs::PathFlags{})); + } + } + } + + g_has_cached = true; + } + + } + + /* Get whether emummc is active. */ + bool IsActive() { + CacheValues(); + return g_is_emummc; + } + + /* Get the active emummc id. */ + u32 GetActiveId() { + CacheValues(); + return g_exo_config.base_cfg.id; + } + + /* Get Nintendo redirection path. */ + const char *GetNintendoDirPath() { + CacheValues(); + if (!g_is_emummc) { + return nullptr; + } + return g_exo_config.emu_dir_path; + } + + /* Get Emummc folderpath, NULL if not file-based. */ + const char *GetFilePath() { + CacheValues(); + if (!g_is_emummc || g_exo_config.base_cfg.type != Storage_SdFile) { + return nullptr; + } + return g_exo_config.file_cfg.path; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_environment.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_environment.os.horizon.cpp new file mode 100644 index 00000000..dec1eede --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_environment.os.horizon.cpp @@ -0,0 +1,181 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ams_bpc.os.horizon.h" + +namespace ams { + + namespace { + + #if defined(ATMOSPHERE_ARCH_ARM64) + inline u64 GetPc() { + u64 pc; + __asm__ __volatile__ ("adr %[pc], ." : [pc]"=&r"(pc) :: ); + return pc; + } + + struct StackFrame { + u64 fp; + u64 lr; + }; + #else + #error "Unknown architecture for os Horizon" + #endif + + } + + void InitializeForBoot() { + R_ABORT_UNLESS(amsBpcInitialize()); + } + + void SetInitialRebootPayload(const void *src, size_t src_size) { + R_ABORT_UNLESS(amsBpcSetRebootPayload(src, src_size)); + } + + void WEAK_SYMBOL ExceptionHandler(FatalErrorContext *ctx) { + R_ABORT_UNLESS(amsBpcInitialize()); + R_ABORT_UNLESS(amsBpcRebootToFatalError(ctx)); + while (1) { /* ... */ } + } + + void CrashHandler(ThreadExceptionDump *ctx) { + FatalErrorContext ams_ctx; + + /* Convert thread dump to atmosphere dump. */ + { + ams_ctx.magic = FatalErrorContext::Magic; + ams_ctx.error_desc = ctx->error_desc; + ams_ctx.program_id = os::GetCurrentProgramId().value; + for (size_t i = 0; i < FatalErrorContext::NumGprs; i++) { + ams_ctx.gprs[i] = ctx->cpu_gprs[i].x; + } + if (ams_ctx.error_desc == FatalErrorContext::DataAbortErrorDesc && + ams_ctx.gprs[27] == FatalErrorContext::StdAbortMagicAddress && + ams_ctx.gprs[28] == FatalErrorContext::StdAbortMagicValue) + { + /* Detect std::abort(). */ + ams_ctx.error_desc = FatalErrorContext::StdAbortErrorDesc; + } + + ams_ctx.fp = ctx->fp.x; + ams_ctx.lr = ctx->lr.x; + ams_ctx.sp = ctx->sp.x; + ams_ctx.pc = ctx->pc.x; + ams_ctx.pstate = ctx->pstate; + ams_ctx.afsr0 = static_cast<u32>(::ams::exosphere::GetVersion(ATMOSPHERE_RELEASE_VERSION)); + ams_ctx.afsr1 = static_cast<u32>(hos::GetVersion()); + ams_ctx.far = ctx->far.x; + ams_ctx.report_identifier = armGetSystemTick(); + + /* Detect stack overflow. */ + if (ams_ctx.error_desc == FatalErrorContext::DataAbortErrorDesc) { + svc::lp64::MemoryInfo mem_info; + svc::PageInfo page_info; + + if (/* Check if stack pointer is in guard page. */ + R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), ams_ctx.sp)) && + mem_info.state == svc::MemoryState_Free && + /* Check if stack pointer fell off stack. */ + R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), ams_ctx.sp + 0x1000)) && + mem_info.state == svc::MemoryState_Stack) { + ams_ctx.error_desc = FatalErrorContext::StackOverflowErrorDesc; + } + } + /* Grab module base. */ + { + svc::lp64::MemoryInfo mem_info; + svc::PageInfo page_info; + if (R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), GetPc()))) { + ams_ctx.module_base = mem_info.base_address; + } else { + ams_ctx.module_base = 0; + } + } + ams_ctx.stack_trace_size = 0; + u64 cur_fp = ams_ctx.fp; + for (size_t i = 0; i < FatalErrorContext::MaxStackTrace; i++) { + /* Validate current frame. */ + if (cur_fp == 0 || (cur_fp & 0xF)) { + break; + } + + /* Read a new frame. */ + StackFrame cur_frame; + svc::lp64::MemoryInfo mem_info; + svc::PageInfo page_info; + if (R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), cur_fp)) && (mem_info.permission & svc::MemoryPermission_Read) == svc::MemoryPermission_Read) { + std::memcpy(std::addressof(cur_frame), reinterpret_cast<void *>(cur_fp), sizeof(cur_frame)); + } else { + break; + } + + /* Advance to the next frame. */ + ams_ctx.stack_trace[ams_ctx.stack_trace_size++] = cur_frame.lr; + cur_fp = cur_frame.fp; + } + /* Clear unused parts of stack trace. */ + for (size_t i = ams_ctx.stack_trace_size; i < FatalErrorContext::MaxStackTrace; i++) { + ams_ctx.stack_trace[i] = 0; + } + + /* Grab up to 0x100 of stack. */ + { + svc::lp64::MemoryInfo mem_info; + svc::PageInfo page_info; + if (R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), ams_ctx.sp)) && (mem_info.permission & svc::MemoryPermission_Read) == svc::MemoryPermission_Read) { + size_t copy_size = std::min(FatalErrorContext::MaxStackDumpSize, static_cast<size_t>(mem_info.base_address + mem_info.size - ams_ctx.sp)); + ams_ctx.stack_dump_size = copy_size; + std::memcpy(ams_ctx.stack_dump, reinterpret_cast<void *>(ams_ctx.sp), copy_size); + } else { + ams_ctx.stack_dump_size = 0; + } + } + + /* Grab 0x100 of tls. */ + std::memcpy(ams_ctx.tls, svc::GetThreadLocalRegion(), sizeof(ams_ctx.tls)); + } + + /* Just call the user exception handler. */ + ::ams::ExceptionHandler(std::addressof(ams_ctx)); + } + + NORETURN void AbortImpl(); + +} + +extern "C" { + + /* Redefine C++ exception handlers. Requires wrap linker flag. */ + #define WRAP_ABORT_FUNC(func) void NORETURN __wrap_##func(void) { ::ams::AbortImpl(); __builtin_unreachable(); } + WRAP_ABORT_FUNC(__cxa_throw) + WRAP_ABORT_FUNC(__cxa_rethrow) + WRAP_ABORT_FUNC(__cxa_allocate_exception) + WRAP_ABORT_FUNC(__cxa_free_exception) + WRAP_ABORT_FUNC(__cxa_begin_catch) + WRAP_ABORT_FUNC(__cxa_end_catch) + WRAP_ABORT_FUNC(__cxa_call_unexpected) + WRAP_ABORT_FUNC(__cxa_call_terminate) + WRAP_ABORT_FUNC(__gxx_personality_v0) + WRAP_ABORT_FUNC(_ZSt19__throw_logic_errorPKc) + WRAP_ABORT_FUNC(_ZSt20__throw_length_errorPKc) + WRAP_ABORT_FUNC(_ZNSt11logic_errorC2EPKc) + + /* TODO: We may wish to consider intentionally not defining an _Unwind_Resume wrapper. */ + /* This would mean that a failure to wrap all exception functions is a linker error. */ + WRAP_ABORT_FUNC(_Unwind_Resume) + #undef WRAP_ABORT_FUNC + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_environment_weak.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_environment_weak.os.horizon.cpp new file mode 100644 index 00000000..5fd5a917 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_environment_weak.os.horizon.cpp @@ -0,0 +1,86 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +extern "C" void NORETURN __real_exit(int rc); + +namespace ams { + + WEAK_SYMBOL void *Malloc(size_t size) { + return std::malloc(size); + } + + WEAK_SYMBOL void Free(void *ptr) { + return std::free(ptr); + } + + WEAK_SYMBOL void *MallocForRapidJson(size_t size) { + return std::malloc(size); + } + + WEAK_SYMBOL void *ReallocForRapidJson(void *ptr, size_t size) { + return std::realloc(ptr, size); + } + + WEAK_SYMBOL void FreeForRapidJson(void *ptr) { + return std::free(ptr); + } + + WEAK_SYMBOL void NORETURN Exit(int rc) { + __real_exit(rc); + __builtin_unreachable(); + } + + NOINLINE NORETURN void AbortImpl() { + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + /* Just perform a data abort. */ + register u64 addr __asm__("x27") = FatalErrorContext::StdAbortMagicAddress; + register u64 val __asm__("x28") = FatalErrorContext::StdAbortMagicValue; + while (true) { + __asm__ __volatile__ ( + "str %[val], [%[addr]]" + : + : [val]"r"(val), [addr]"r"(addr) + ); + } + #else + /* What should be done here? */ + AMS_INFINITE_LOOP(); + #endif + __builtin_unreachable(); + } + +} + +extern "C" { + + /* Redefine abort to trigger these handlers. */ + void abort(); + + /* Redefine C++ exception handlers. Requires wrap linker flag. */ + #define WRAP_ABORT_FUNC(func) void NORETURN __wrap_##func(void) { ::ams::AbortImpl(); __builtin_unreachable(); } + WRAP_ABORT_FUNC(__cxa_pure_virtual) + #undef WRAP_ABORT_FUNC + + void NORETURN __wrap_exit(int rc) { ::ams::Exit(rc); __builtin_unreachable(); } + +} + +/* Custom abort handler, so that std::abort will trigger these. */ +void abort() { + ams::AbortImpl(); + __builtin_unreachable(); +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_environment_weak.os.linux.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_environment_weak.os.linux.cpp new file mode 100644 index 00000000..0299f47c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_environment_weak.os.linux.cpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams { + + namespace os { + + void Initialize(); + + } + + void *Malloc(size_t size) { + return std::malloc(size); + } + + void Free(void *ptr) { + return std::free(ptr); + } + + void *MallocForRapidJson(size_t size) { + return std::malloc(size); + } + + void *ReallocForRapidJson(void *ptr, size_t size) { + return std::realloc(ptr, size); + } + + void FreeForRapidJson(void *ptr) { + return std::free(ptr); + } + + NORETURN void AbortImpl() { + std::abort(); + } + +} + +extern "C" { + + /* Redefine C++ exception handlers. Requires wrap linker flag. */ + #define WRAP_ABORT_FUNC(func) void NORETURN __wrap_##func(void) { std::abort(); __builtin_unreachable(); } + WRAP_ABORT_FUNC(__cxa_pure_virtual) + #undef WRAP_ABORT_FUNC + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_environment_weak.os.macos.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_environment_weak.os.macos.cpp new file mode 100644 index 00000000..0299f47c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_environment_weak.os.macos.cpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams { + + namespace os { + + void Initialize(); + + } + + void *Malloc(size_t size) { + return std::malloc(size); + } + + void Free(void *ptr) { + return std::free(ptr); + } + + void *MallocForRapidJson(size_t size) { + return std::malloc(size); + } + + void *ReallocForRapidJson(void *ptr, size_t size) { + return std::realloc(ptr, size); + } + + void FreeForRapidJson(void *ptr) { + return std::free(ptr); + } + + NORETURN void AbortImpl() { + std::abort(); + } + +} + +extern "C" { + + /* Redefine C++ exception handlers. Requires wrap linker flag. */ + #define WRAP_ABORT_FUNC(func) void NORETURN __wrap_##func(void) { std::abort(); __builtin_unreachable(); } + WRAP_ABORT_FUNC(__cxa_pure_virtual) + #undef WRAP_ABORT_FUNC + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_environment_weak.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_environment_weak.os.windows.cpp new file mode 100644 index 00000000..c672270c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_environment_weak.os.windows.cpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +extern "C" char **__real___p__acmdln(void); +extern "C" _invalid_parameter_handler __real__set_invalid_parameter_handler(_invalid_parameter_handler); + +namespace ams { + + namespace os { + + void Initialize(); + + } + + void *Malloc(size_t size) { + return std::malloc(size); + } + + void Free(void *ptr) { + return std::free(ptr); + } + + void *MallocForRapidJson(size_t size) { + return std::malloc(size); + } + + void *ReallocForRapidJson(void *ptr, size_t size) { + return std::realloc(ptr, size); + } + + void FreeForRapidJson(void *ptr) { + return std::free(ptr); + } + + NORETURN void AbortImpl() { + std::abort(); + } + +} + +extern "C" { + + /* Redefine C++ exception handlers. Requires wrap linker flag. */ + #define WRAP_ABORT_FUNC(func) void NORETURN __wrap_##func(void) { std::abort(); __builtin_unreachable(); } + WRAP_ABORT_FUNC(__cxa_pure_virtual) + #undef WRAP_ABORT_FUNC + + /* On windows, mingw may attempt to call malloc before we've initialized globals to set up the command line. */ + /* We perform some critical init here, to make that work. */ + char **__wrap___p__acmdln(void) { + ::ams::os::Initialize(); + return __real___p__acmdln(); + } + + /* On some mingw gcc versions, acmdln isn't used, so we need to hook a different part of crt init. */ + _invalid_parameter_handler __wrap__set_invalid_parameter_handler(_invalid_parameter_handler handler) { + ::ams::os::Initialize(); + return __real__set_invalid_parameter_handler(handler); + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_exosphere_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_exosphere_api.cpp new file mode 100644 index 00000000..d48b0ce5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ams/ams_exosphere_api.cpp @@ -0,0 +1,91 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::exosphere { + + ApiInfo GetApiInfo() { + u64 exosphere_cfg; + if (R_FAILED(spl::impl::GetConfig(std::addressof(exosphere_cfg), spl::ConfigItem::ExosphereApiVersion))) { + R_ABORT_UNLESS(exosphere::ResultNotPresent()); + } + + return ApiInfo{ util::BitPack64{exosphere_cfg} }; + } + + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + void ForceRebootToRcm() { + R_ABORT_UNLESS(spl::impl::SetConfig(spl::ConfigItem::ExosphereNeedsReboot, 1)); + } + + void ForceRebootToIramPayload() { + R_ABORT_UNLESS(spl::impl::SetConfig(spl::ConfigItem::ExosphereNeedsReboot, 2)); + } + + void ForceRebootToFatalError() { + R_ABORT_UNLESS(spl::impl::SetConfig(spl::ConfigItem::ExosphereNeedsReboot, 3)); + } + + void ForceRebootByPmic() { + R_ABORT_UNLESS(spl::impl::SetConfig(spl::ConfigItem::ExosphereNeedsReboot, 4)); + } + + void ForceShutdown() { + R_ABORT_UNLESS(spl::impl::SetConfig(spl::ConfigItem::ExosphereNeedsShutdown, 1)); + } + + void CopyToIram(uintptr_t iram_dst, const void *dram_src, size_t size) { + spl::smc::AtmosphereCopyToIram(iram_dst, dram_src, size); + } + + void CopyFromIram(void *dram_dst, uintptr_t iram_src, size_t size) { + spl::smc::AtmosphereCopyFromIram(dram_dst, iram_src, size); + } + + namespace { + + inline u64 GetU64ConfigItem(spl::ConfigItem cfg) { + u64 tmp; + R_ABORT_UNLESS(spl::smc::ConvertResult(spl::smc::GetConfig(std::addressof(tmp), 1, cfg))); + return tmp; + } + + inline bool GetBooleanConfigItem(spl::ConfigItem cfg) { + return GetU64ConfigItem(cfg) != 0; + } + + } + + bool IsRcmBugPatched() { + return spl::impl::GetConfigBool(spl::ConfigItem::ExosphereHasRcmBugPatch); + } + + bool ShouldBlankProdInfo() { + return spl::impl::GetConfigBool(spl::ConfigItem::ExosphereBlankProdInfo); + } + + bool ShouldAllowWritesToProdInfo() { + return spl::impl::GetConfigBool(spl::ConfigItem::ExosphereAllowCalWrites); + } + + u64 GetDeviceId() { + u64 device_id; + R_ABORT_UNLESS(spl::impl::GetConfig(std::addressof(device_id), spl::ConfigItem::DeviceId)); + return device_id; + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/boot2/boot2_api.board.nintendo_nx.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/boot2/boot2_api.board.nintendo_nx.cpp new file mode 100644 index 00000000..5f7f296a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/boot2/boot2_api.board.nintendo_nx.cpp @@ -0,0 +1,460 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::boot2 { + + namespace { + + /* Launch lists. */ + + /* psc, bus, pcv is the minimal set of required programs to get SD card. */ + /* bus depends on pcie, and pcv depends on settings. */ + constexpr const ncm::SystemProgramId PreSdCardLaunchPrograms[] = { + ncm::SystemProgramId::Psc, /* psc */ + ncm::SystemProgramId::Pcie, /* pcie */ + ncm::SystemProgramId::Bus, /* bus */ + ncm::SystemProgramId::Settings, /* settings */ + ncm::SystemProgramId::Pcv, /* pcv */ + ncm::SystemProgramId::Usb, /* usb */ + }; + constexpr size_t NumPreSdCardLaunchPrograms = util::size(PreSdCardLaunchPrograms); + + constexpr const ncm::SystemProgramId AdditionalLaunchPrograms[] = { + ncm::SystemProgramId::Omm, /* omm */ + ncm::SystemProgramId::Am, /* am */ + ncm::SystemProgramId::NvServices, /* nvservices */ + ncm::SystemProgramId::NvnFlinger, /* nvnflinger */ + ncm::SystemProgramId::Vi, /* vi */ + ncm::SystemProgramId::Pgl, /* pgl */ + ncm::SystemProgramId::Ns, /* ns */ + //ncm::SystemProgramId::LogManager, /* lm */ + ncm::SystemProgramId::Ppc, /* ppc */ + ncm::SystemProgramId::Ptm, /* ptm */ + ncm::SystemProgramId::Hid, /* hid */ + ncm::SystemProgramId::Audio, /* audio */ + ncm::SystemProgramId::Lbl, /* lbl */ + ncm::SystemProgramId::Wlan, /* wlan */ + ncm::SystemProgramId::Bluetooth, /* bluetooth */ + ncm::SystemProgramId::BsdSockets, /* bsdsockets */ + ncm::SystemProgramId::Eth, /* eth */ + ncm::SystemProgramId::Nifm, /* nifm */ + ncm::SystemProgramId::Ldn, /* ldn */ + ncm::SystemProgramId::Account, /* account */ + ncm::SystemProgramId::Friends, /* friends */ + ncm::SystemProgramId::Nfc, /* nfc */ + ncm::SystemProgramId::JpegDec, /* jpegdec */ + ncm::SystemProgramId::CapSrv, /* capsrv */ + ncm::SystemProgramId::Ssl, /* ssl */ + ncm::SystemProgramId::Nim, /* nim */ + ncm::SystemProgramId::Bcat, /* bcat */ + ncm::SystemProgramId::Erpt, /* erpt */ + ncm::SystemProgramId::Es, /* es */ + ncm::SystemProgramId::Pctl, /* pctl */ + ncm::SystemProgramId::Btm, /* btm */ + ncm::SystemProgramId::Eupld, /* eupld */ + ncm::SystemProgramId::Glue, /* glue */ + /* ncm::SystemProgramId::Eclct, */ /* eclct */ /* Skip launching error collection in Atmosphere to lessen telemetry. */ + ncm::SystemProgramId::Npns, /* npns */ + ncm::SystemProgramId::Fatal, /* fatal */ + ncm::SystemProgramId::Ro, /* ro */ + ncm::SystemProgramId::Profiler, /* profiler */ + ncm::SystemProgramId::Sdb, /* sdb */ + ncm::SystemProgramId::Olsc, /* olsc */ + ncm::SystemProgramId::Ngc, /* ngc */ + ncm::SystemProgramId::Ngct, /* ngct */ + }; + constexpr size_t NumAdditionalLaunchPrograms = util::size(AdditionalLaunchPrograms); + + constexpr const ncm::SystemProgramId AdditionalMaintenanceLaunchPrograms[] = { + ncm::SystemProgramId::Omm, /* omm */ + ncm::SystemProgramId::Am, /* am */ + ncm::SystemProgramId::NvServices, /* nvservices */ + ncm::SystemProgramId::NvnFlinger, /* nvnflinger */ + ncm::SystemProgramId::Vi, /* vi */ + ncm::SystemProgramId::Pgl, /* pgl */ + ncm::SystemProgramId::Ns, /* ns */ + //ncm::SystemProgramId::LogManager, /* lm */ + ncm::SystemProgramId::Ppc, /* ppc */ + ncm::SystemProgramId::Ptm, /* ptm */ + ncm::SystemProgramId::Hid, /* hid */ + ncm::SystemProgramId::Audio, /* audio */ + ncm::SystemProgramId::Lbl, /* lbl */ + ncm::SystemProgramId::Wlan, /* wlan */ + ncm::SystemProgramId::Bluetooth, /* bluetooth */ + ncm::SystemProgramId::BsdSockets, /* bsdsockets */ + ncm::SystemProgramId::Eth, /* eth */ + ncm::SystemProgramId::Nifm, /* nifm */ + ncm::SystemProgramId::Ldn, /* ldn */ + ncm::SystemProgramId::Account, /* account */ + ncm::SystemProgramId::Nfc, /* nfc */ + ncm::SystemProgramId::JpegDec, /* jpegdec */ + ncm::SystemProgramId::CapSrv, /* capsrv */ + ncm::SystemProgramId::Ssl, /* ssl */ + ncm::SystemProgramId::Nim, /* nim */ + ncm::SystemProgramId::Erpt, /* erpt */ + ncm::SystemProgramId::Es, /* es */ + ncm::SystemProgramId::Pctl, /* pctl */ + ncm::SystemProgramId::Btm, /* btm */ + ncm::SystemProgramId::Glue, /* glue */ + /* ncm::SystemProgramId::Eclct, */ /* eclct */ /* Skip launching error collection in Atmosphere to lessen telemetry. */ + ncm::SystemProgramId::Fatal, /* fatal */ + ncm::SystemProgramId::Ro, /* ro */ + ncm::SystemProgramId::Profiler, /* profiler */ + ncm::SystemProgramId::Sdb, /* sdb */ + ncm::SystemProgramId::Olsc, /* olsc */ + ncm::SystemProgramId::Ngc, /* ngc */ + ncm::SystemProgramId::Ngct, /* ngct */ + }; + constexpr size_t NumAdditionalMaintenanceLaunchPrograms = util::size(AdditionalMaintenanceLaunchPrograms); + + /* Helpers. */ + inline bool IsHexadecimal(const char *str) { + while (*str) { + if (!std::isxdigit(static_cast<unsigned char>(*(str++)))) { + return false; + } + } + return true; + } + + inline bool IsNewLine(char c) { + return c == '\r' || c == '\n'; + } + + inline bool IsAllowedLaunchProgram(const ncm::ProgramLocation &loc) { + if (loc.program_id == ncm::SystemProgramId::Pgl) { + return hos::GetVersion() >= hos::Version_10_0_0; + } + return true; + } + + void LaunchProgram(os::ProcessId *out_process_id, const ncm::ProgramLocation &loc, u32 launch_flags) { + os::ProcessId process_id = os::InvalidProcessId; + + /* Only launch the process if we're allowed to. */ + if (IsAllowedLaunchProgram(loc)) { + /* Launch, lightly validate result. */ + { + const auto launch_result = pm::shell::LaunchProgram(std::addressof(process_id), loc, launch_flags); + AMS_ABORT_UNLESS(!(svc::ResultOutOfResource::Includes(launch_result))); + AMS_ABORT_UNLESS(!(svc::ResultOutOfMemory::Includes(launch_result))); + AMS_ABORT_UNLESS(!(svc::ResultLimitReached::Includes(launch_result))); + } + + if (out_process_id) { + *out_process_id = process_id; + } + } + } + + void LaunchList(const ncm::SystemProgramId *launch_list, size_t num_entries) { + for (size_t i = 0; i < num_entries; i++) { + LaunchProgram(nullptr, ncm::ProgramLocation::Make(launch_list[i], ncm::StorageId::BuiltInSystem), 0); + } + } + + bool GetGpioPadLow(DeviceCode device_code) { + gpio::GpioPadSession button{}; + if (R_FAILED(gpio::OpenSession(std::addressof(button), device_code))) { + return false; + } + + /* Ensure we close even on early return. */ + ON_SCOPE_EXIT { gpio::CloseSession(std::addressof(button)); }; + + /* Set direction input. */ + gpio::SetDirection(std::addressof(button), gpio::Direction_Input); + + return gpio::GetValue(std::addressof(button)) == gpio::GpioValue_Low; + } + + bool IsForceMaintenance() { + u8 force_maintenance = 1; + settings::fwdbg::GetSettingsItemValue(std::addressof(force_maintenance), sizeof(force_maintenance), "boot", "force_maintenance"); + return force_maintenance != 0; + } + + bool IsHtcEnabled() { + u8 enable_htc = 0; + settings::fwdbg::GetSettingsItemValue(std::addressof(enable_htc), sizeof(enable_htc), "atmosphere", "enable_htc"); + return enable_htc != 0; + } + + bool IsStandaloneGdbstubEnabled() { + u8 enable_gdbstub = 0; + settings::fwdbg::GetSettingsItemValue(std::addressof(enable_gdbstub), sizeof(enable_gdbstub), "atmosphere", "enable_standalone_gdbstub"); + return enable_gdbstub != 0; + } + + bool IsAtmosphereLogManagerEnabled() { + /* If htc is enabled, ams log manager is enabled. */ + if (IsHtcEnabled()) { + return true; + } + + u8 enable_ams_lm = 0; + settings::fwdbg::GetSettingsItemValue(std::addressof(enable_ams_lm), sizeof(enable_ams_lm), "atmosphere", "enable_log_manager"); + return enable_ams_lm != 0; + } + + bool IsMaintenanceMode() { + /* Contact set:sys, retrieve boot!force_maintenance. */ + if (IsForceMaintenance()) { + return true; + } + + /* Contact GPIO, read plus/minus buttons. */ + { + return GetGpioPadLow(gpio::DeviceCode_ButtonVolUp) && GetGpioPadLow(gpio::DeviceCode_ButtonVolDn); + } + } + + template<typename F> + void IterateOverFlaggedProgramsOnSdCard(F f) { + /* Validate that the contents directory exists. */ + fs::DirectoryHandle contents_dir; + if (R_FAILED(fs::OpenDirectory(std::addressof(contents_dir), "sdmc:/atmosphere/contents", fs::OpenDirectoryMode_Directory))) { + return; + } + ON_SCOPE_EXIT { fs::CloseDirectory(contents_dir); }; + + /* Iterate over entries in the contents directory */ + fs::DirectoryEntry entry; + s64 count; + while (R_SUCCEEDED(fs::ReadDirectory(std::addressof(count), std::addressof(entry), contents_dir, 1)) && count == 1) { + /* Check that the subdirectory can be converted to a program id. */ + if (std::strlen(entry.name) == 2 * sizeof(ncm::ProgramId) && IsHexadecimal(entry.name)) { + /* Check if we've already launched the program. */ + ncm::ProgramId program_id{std::strtoul(entry.name, nullptr, 16)}; + + /* Check if the program is flagged. */ + if (!cfg::HasContentSpecificFlag(program_id, "boot2")) { + continue; + } + + /* Call the iteration callback. */ + f(program_id); + } + } + } + + void DetectAndDeclareFutureMitms() { + IterateOverFlaggedProgramsOnSdCard([](ncm::ProgramId program_id) { + /* When we find a flagged program, check if it has a mitm list. */ + char mitm_list[0x400]; + size_t mitm_list_size = 0; + + /* Read the mitm list off the SD card. */ + { + char path[fs::EntryNameLengthMax]; + util::SNPrintf(path, sizeof(path), "sdmc:/atmosphere/contents/%016" PRIx64 "/mitm.lst", static_cast<u64>(program_id)); + + fs::FileHandle f; + if (R_FAILED(fs::OpenFile(std::addressof(f), path, fs::OpenMode_Read))) { + return; + } + ON_SCOPE_EXIT { fs::CloseFile(f); }; + + R_ABORT_UNLESS(fs::ReadFile(std::addressof(mitm_list_size), f, 0, mitm_list, sizeof(mitm_list))); + } + + /* Validate read size. */ + if (mitm_list_size > sizeof(mitm_list) || mitm_list_size == 0) { + return; + } + + /* Iterate over the contents of the file. */ + /* We expect one service name per non-empty line, 1-8 characters. */ + size_t offset = 0; + while (offset < mitm_list_size) { + /* Continue to the start of the next name. */ + while (IsNewLine(mitm_list[offset])) { + offset++; + } + if (offset >= mitm_list_size) { + break; + } + + /* Get the length of the current name. */ + size_t name_len; + for (name_len = 0; name_len <= sizeof(sm::ServiceName) && offset + name_len < mitm_list_size; name_len++) { + if (IsNewLine(mitm_list[offset + name_len])) { + break; + } + } + + /* Allow empty lines. */ + if (name_len == 0) { + continue; + } + + /* Don't allow invalid lines. */ + AMS_ABORT_UNLESS(name_len <= sizeof(sm::ServiceName)); + + /* Declare the service. */ + R_ABORT_UNLESS(sm::mitm::DeclareFutureMitm(sm::ServiceName::Encode(mitm_list + offset, name_len))); + + /* Advance to the next line. */ + offset += name_len; + } + }); + } + + void LaunchFlaggedProgramsOnSdCard() { + IterateOverFlaggedProgramsOnSdCard([](ncm::ProgramId program_id) { + /* Check if we've already launched the program. */ + if (pm::info::HasLaunchedBootProgram(program_id)) { + return; + } + + /* Launch the program. */ + LaunchProgram(nullptr, ncm::ProgramLocation::Make(program_id, ncm::StorageId::None), 0); + }); + } + + bool IsUsbRequiredToMountSdCard() { + return hos::GetVersion() >= hos::Version_9_0_0; + } + + } + + /* Boot2 API. */ + + void LaunchPreSdCardBootProgramsAndBoot2() { + /* This code is normally run by PM. */ + + /* Wait until fs.mitm has installed itself. We want this to happen as early as possible. */ + R_ABORT_UNLESS(sm::mitm::WaitMitm(sm::ServiceName::Encode("fsp-srv"))); + + /* Launch programs required to mount the SD card. */ + /* psc, bus, pcv (and usb on newer firmwares) is the minimal set of required programs. */ + /* bus depends on pcie, and pcv depends on settings. */ + { + /* Launch psc. */ + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Psc, ncm::StorageId::BuiltInSystem), 0); + + /* Launch pcie. */ + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Pcie, ncm::StorageId::BuiltInSystem), 0); + + /* Launch bus. */ + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Bus, ncm::StorageId::BuiltInSystem), 0); + + /* Launch settings. */ + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Settings, ncm::StorageId::BuiltInSystem), 0); + + /* NOTE: Here we work around a race condition in the boot process by ensuring that settings initializes its db. */ + { + /* Connect to set:sys. */ + R_ABORT_UNLESS(::setsysInitialize()); + ON_SCOPE_EXIT { ::setsysExit(); }; + + /* Retrieve setting from the database. */ + u8 force_maintenance = 0; + settings::fwdbg::GetSettingsItemValue(std::addressof(force_maintenance), sizeof(force_maintenance), "boot", "force_maintenance"); + } + + /* Launch pcv. */ + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Pcv, ncm::StorageId::BuiltInSystem), 0); + + /* On 9.0.0+, FS depends on the USB sysmodule having been launched in order to mount the SD card. */ + if (IsUsbRequiredToMountSdCard()) { + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Usb, ncm::StorageId::BuiltInSystem), 0); + } + } + + /* Wait for the SD card required services to be ready. */ + cfg::WaitSdCardRequiredServicesReady(); + + /* Wait for other atmosphere mitm modules to initialize. */ + R_ABORT_UNLESS(sm::mitm::WaitMitm(sm::ServiceName::Encode("set:sys"))); + if (spl::GetSocType() == spl::SocType_Erista) { + if (hos::GetVersion() >= hos::Version_2_0_0) { + R_ABORT_UNLESS(sm::mitm::WaitMitm(sm::ServiceName::Encode("bpc"))); + } else { + R_ABORT_UNLESS(sm::mitm::WaitMitm(sm::ServiceName::Encode("bpc:c"))); + } + } + + /* Launch Atmosphere boot2, using NcmStorageId_None to force SD card boot. */ + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Boot2, ncm::StorageId::None), 0); + } + + void LaunchPostSdCardBootPrograms() { + /* This code is normally run by boot2. */ + + /* Launch the usb system module, if we haven't already. */ + if (!IsUsbRequiredToMountSdCard()) { + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Usb, ncm::StorageId::BuiltInSystem), 0); + } + + /* Activate the system fs content storage. */ + R_ABORT_UNLESS(ncm::ActivateFsContentStorage(fs::ContentStorageId::System)); + + /* Find out whether we are maintenance mode. */ + const bool maintenance = IsMaintenanceMode(); + if (maintenance) { + pm::bm::SetMaintenanceBoot(); + } + + /* Check for and forward declare non-atmosphere mitm modules. */ + DetectAndDeclareFutureMitms(); + + /* Decide whether to launch tma or htc. */ + if (IsHtcEnabled()) { + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Htc, ncm::StorageId::None), 0); + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Cs, ncm::StorageId::None), 0); + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::DmntGen2, ncm::StorageId::None), 0); + } else if (IsStandaloneGdbstubEnabled()) { + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::DmntGen2, ncm::StorageId::None), 0); + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Tma, ncm::StorageId::BuiltInSystem), 0); + } else { + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Dmnt, ncm::StorageId::None), 0); + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Tma, ncm::StorageId::BuiltInSystem), 0); + } + + /* Decide whether to launch atmosphere or nintendo's log manager. */ + if (IsAtmosphereLogManagerEnabled()) { + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::AtmosphereProgramId::AtmosphereLogManager, ncm::StorageId::None), 0); + } else { + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::LogManager, ncm::StorageId::BuiltInSystem), 0); + } + + /* Launch additional programs. */ + if (maintenance) { + LaunchList(AdditionalMaintenanceLaunchPrograms, NumAdditionalMaintenanceLaunchPrograms); + /* Starting in 7.0.0, npns is launched during maintenance boot. */ + if (hos::GetVersion() >= hos::Version_7_0_0) { + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Npns, ncm::StorageId::BuiltInSystem), 0); + } + } else { + LaunchList(AdditionalLaunchPrograms, NumAdditionalLaunchPrograms); + } + + /* Prior to 12.0.0, boot2 was responsible for launching grc and migration. */ + if (hos::GetVersion() < hos::Version_12_0_0) { + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Grc, ncm::StorageId::BuiltInSystem), 0); + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::SystemProgramId::Migration, ncm::StorageId::BuiltInSystem), 0); + } + + /* Launch atmosphere's applet memory service program. */ + LaunchProgram(nullptr, ncm::ProgramLocation::Make(ncm::AtmosphereProgramId::AtmosphereMemlet, ncm::StorageId::None), 0); + + /* Launch user programs off of the SD. */ + LaunchFlaggedProgramsOnSdCard(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cal/cal_battery_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cal/cal_battery_api.cpp new file mode 100644 index 00000000..decad722 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cal/cal_battery_api.cpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "cal_fs_utils.hpp" + +namespace ams::cal { + + namespace { + + constexpr inline s64 BatteryLotOffset = 0x2CE0; + constexpr inline size_t BatteryLotSize = 0x20; + + constexpr inline s64 BatteryVersionOffset = 0x4310; + constexpr inline size_t BatteryVersionSize = 0x10; + + constexpr inline size_t BatteryVendorSizeMax = 0x18; + + } + + Result GetBatteryVersion(u8 *out) { + /* Read the battery version. */ + u8 battery_version[BatteryVersionSize]; + R_TRY(cal::impl::ReadCalibrationBlock(BatteryVersionOffset, battery_version, sizeof(battery_version))); + + /* Write the output. */ + *out = battery_version[0]; + R_SUCCEED(); + } + + Result GetBatteryVendor(size_t *out_vendor_size, void *dst, size_t dst_size) { + /* Read the battery lot. */ + char battery_lot[BatteryLotSize]; + R_TRY(cal::impl::ReadCalibrationBlock(BatteryLotOffset, battery_lot, sizeof(battery_lot))); + + /* Copy output. */ + *out_vendor_size = static_cast<size_t>(util::Strlcpy(static_cast<char *>(dst), battery_lot, std::min(dst_size, BatteryVendorSizeMax))); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cal/cal_crc_utils.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cal/cal_crc_utils.cpp new file mode 100644 index 00000000..6d9d19d6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cal/cal_crc_utils.cpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "cal_crc_utils.hpp" + +namespace ams::cal::impl { + + namespace { + + constexpr inline const u16 CrcTable[0x10] = { + 0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401, + 0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400 + }; + + } + + u16 CalculateCrc16(const void *data, size_t size) { + AMS_ASSERT(data != nullptr); + + u16 crc = 0x55AA; + const u8 *data_u8 = static_cast<const u8 *>(data); + for (size_t i = 0; i < size; ++i) { + crc = (crc >> 4) ^ (CrcTable[crc & 0xF]) ^ (CrcTable[(data_u8[i] >> 0) & 0xF]); + crc = (crc >> 4) ^ (CrcTable[crc & 0xF]) ^ (CrcTable[(data_u8[i] >> 4) & 0xF]); + } + + return crc; + } + + Result ValidateCalibrationCrc(const void *data, size_t size) { + AMS_ASSERT(data != nullptr); + AMS_ASSERT(size >= sizeof(u16)); + + const u16 crc = *reinterpret_cast<const u16 *>(reinterpret_cast<uintptr_t>(data) + size - sizeof(u16)); + R_UNLESS(CalculateCrc16(data, size - sizeof(u16)) == crc, cal::ResultCalibrationDataCrcError()); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cal/cal_crc_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cal/cal_crc_utils.hpp new file mode 100644 index 00000000..617724b4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cal/cal_crc_utils.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::cal::impl { + + u16 CalculateCrc16(const void *data, size_t size); + Result ValidateCalibrationCrc(const void *data, size_t size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cal/cal_fs_utils.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cal/cal_fs_utils.cpp new file mode 100644 index 00000000..2b449930 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cal/cal_fs_utils.cpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "cal_crc_utils.hpp" +#include "cal_fs_utils.hpp" + +namespace ams::cal::impl { + + Result ReadCalibrationBlock(s64 offset, void *dst, size_t block_size) { + /* Open the calibration binary partition. */ + std::unique_ptr<fs::IStorage> storage; + R_TRY(fs::OpenBisPartition(std::addressof(storage), fs::BisPartitionId::CalibrationBinary)); + + /* Read data from the partition. */ + R_TRY(storage->Read(offset, dst, block_size)); + + /* Validate the crc. */ + R_TRY(ValidateCalibrationCrc(dst, block_size)); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cal/cal_fs_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cal/cal_fs_utils.hpp new file mode 100644 index 00000000..b200b838 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cal/cal_fs_utils.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::cal::impl { + + Result ReadCalibrationBlock(s64 offset, void *dst, size_t block_size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/capsrv_screen_shot_control_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/capsrv_screen_shot_control_api.cpp new file mode 100644 index 00000000..68814aea --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/capsrv_screen_shot_control_api.cpp @@ -0,0 +1,83 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::capsrv { + + Result InitializeScreenShotControl() { + #if defined(ATMOSPHERE_OS_HORIZON) + R_RETURN(::capsscInitialize()); + #else + AMS_ABORT("TODO"); + #endif + } + + void FinalizeScreenShotControl() { + #if defined(ATMOSPHERE_OS_HORIZON) + return ::capsscExit(); + #else + AMS_ABORT("TODO"); + #endif + } + + Result CaptureJpegScreenshot(u64 *out_size, void *dst, size_t dst_size, vi::LayerStack layer_stack, TimeSpan timeout) { + #if defined(ATMOSPHERE_OS_HORIZON) + R_RETURN(::capsscCaptureJpegScreenShot(out_size, dst, dst_size, static_cast<::ViLayerStack>(layer_stack), timeout.GetNanoSeconds())); + #else + AMS_UNUSED(out_size, dst, dst_size, layer_stack, timeout); + AMS_ABORT("TODO"); + #endif + } + + Result OpenRawScreenShotReadStreamForDevelop(size_t *out_data_size, s32 *out_width, s32 *out_height, vi::LayerStack layer_stack, TimeSpan timeout) { + #if defined(ATMOSPHERE_OS_HORIZON) + u64 data_size, width, height; + R_TRY(::capsscOpenRawScreenShotReadStream(std::addressof(data_size), std::addressof(width), std::addressof(height), static_cast<::ViLayerStack>(layer_stack), timeout.GetNanoSeconds())); + + *out_data_size = static_cast<size_t>(data_size); + *out_width = static_cast<s32>(width); + *out_height = static_cast<s32>(height); + + R_SUCCEED(); + #else + AMS_UNUSED(out_data_size, out_width, out_height, layer_stack, timeout); + AMS_ABORT("TODO"); + #endif + } + + Result ReadRawScreenShotReadStreamForDevelop(size_t *out_read_size, void *dst, size_t dst_size, std::ptrdiff_t offset) { + #if defined(ATMOSPHERE_OS_HORIZON) + u64 read_size; + R_TRY(::capsscReadRawScreenShotReadStream(std::addressof(read_size), dst, dst_size, static_cast<u64>(offset))); + + *out_read_size = static_cast<size_t>(read_size); + R_SUCCEED(); + #else + AMS_UNUSED(out_read_size, dst, dst_size, offset); + AMS_ABORT("TODO"); + #endif + } + + void CloseRawScreenShotReadStreamForDevelop() { + #if defined(ATMOSPHERE_OS_HORIZON) + ::capsscCloseRawScreenShotReadStream(); + #else + AMS_UNUSED(); + AMS_ABORT("TODO"); + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/capsrv_server_decoder_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/capsrv_server_decoder_api.cpp new file mode 100644 index 00000000..20214c2b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/capsrv_server_decoder_api.cpp @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "decodersrv/decodersrv_decoder_server_object.hpp" + +namespace ams::capsrv::server { + + namespace { + + bool g_initialized = false; + + } + + Result InitializeForDecoderServer() { + /* Ensure we initialize only once. */ + AMS_ABORT_UNLESS(!g_initialized); + + /* Clear work memory. */ + std::memset(std::addressof(g_work_memory), 0, sizeof(g_work_memory)); + + /* Initialize the decoder server. */ + R_ABORT_UNLESS(g_decoder_control_server_manager.Initialize()); + + /* Start the server. */ + g_decoder_control_server_manager.StartServer(); + + /* We're initialized. */ + g_initialized = true; + R_SUCCEED(); + } + + void FinalizeForDecoderServer() { + /* Ensure we don't finalize when uninitialized. */ + AMS_ABORT_UNLESS(g_initialized); + + /* Finalize the server. */ + g_decoder_control_server_manager.Finalize(); + + /* Mark uninitialized. */ + g_initialized = false; + } + + void DecoderControlServerThreadFunction(void *) { + /* We need to be initialized. */ + AMS_ABORT_UNLESS(g_initialized); + + /* Run the server. */ + g_decoder_control_server_manager.RunServer(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_control_server_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_control_server_manager.cpp new file mode 100644 index 00000000..7f799fed --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_control_server_manager.cpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "decodersrv_decoder_server_object.hpp" + +namespace ams::capsrv::server { + + Result DecoderControlServerManager::Initialize() { + /* Create the objects. */ + m_service_holder.emplace(); + m_server_manager_holder.emplace(); + + /* Register the service. */ + R_ABORT_UNLESS((m_server_manager_holder->RegisterObjectForServer(m_service_holder->GetShared(), ServiceName, MaxSessions))); + + /* Initialize the idle event, we're idle initially. */ + os::InitializeEvent(std::addressof(m_idle_event), true, os::EventClearMode_ManualClear); + + R_SUCCEED(); + } + + void DecoderControlServerManager::Finalize() { + /* Check that the server is idle. */ + AMS_ASSERT(os::TryWaitEvent(std::addressof(m_idle_event))); + + /* Destroy the server. */ + os::FinalizeEvent(std::addressof(m_idle_event)); + m_server_manager_holder = util::nullopt; + m_service_holder = util::nullopt; + } + + void DecoderControlServerManager::StartServer() { + m_server_manager_holder->ResumeProcessing(); + } + + void DecoderControlServerManager::StopServer() { + /* Request the server stop, and wait until it does. */ + m_server_manager_holder->RequestStopProcessing(); + os::WaitEvent(std::addressof(m_idle_event)); + } + + void DecoderControlServerManager::RunServer() { + /* Ensure that we are allowed to run. */ + AMS_ABORT_UNLESS(os::TryWaitEvent(std::addressof(m_idle_event))); + + /* Clear the event. */ + os::ClearEvent(std::addressof(m_idle_event)); + + /* Process forever. */ + m_server_manager_holder->LoopProcess(); + + /* Signal that we're idle again. */ + os::SignalEvent(std::addressof(m_idle_event)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_control_server_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_control_server_manager.hpp new file mode 100644 index 00000000..d44137af --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_control_server_manager.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "decodersrv_decoder_control_service.hpp" + +namespace ams::capsrv::server { + + class DecoderControlServerManager { + public: + /* NOTE: Nintendo only allows one session. */ + static constexpr inline size_t NumServers = 1; + static constexpr inline size_t MaxSessions = 2; + static constexpr inline sm::ServiceName ServiceName = sm::ServiceName::Encode("caps:dc"); + + using ServiceHolderType = ams::sf::UnmanagedServiceObject<capsrv::sf::IDecoderControlService, DecoderControlService>; + + using ServerOptions = ams::sf::hipc::DefaultServerManagerOptions; + using ServerManager = ams::sf::hipc::ServerManager<NumServers, ServerOptions, MaxSessions>; + private: + util::optional<ServiceHolderType> m_service_holder; + util::optional<ServerManager> m_server_manager_holder; + os::EventType m_idle_event; + public: + constexpr DecoderControlServerManager() : m_service_holder(), m_server_manager_holder(), m_idle_event{} { /* ... */ } + + Result Initialize(); + void Finalize(); + + void StartServer(); + void StopServer(); + + void RunServer(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_control_service.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_control_service.cpp new file mode 100644 index 00000000..eaa6befb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_control_service.cpp @@ -0,0 +1,150 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "decodersrv_decoder_server_object.hpp" +#include "../jpeg/decodersrv_software_jpeg_decoder.hpp" +#include "../jpeg/decodersrv_software_jpeg_shrinker.hpp" + +namespace ams::capsrv::server { + + namespace { + + constexpr const int JpegShrinkQualities[] = { + 98, 95, 90, 80, 70, 60, 50, 40, 30, 20, 10, 0 + }; + + Result DecodeJpegImpl(void *dst, size_t dst_size, const void *src_jpeg, size_t src_jpeg_size, u32 width, u32 height, const ScreenShotDecodeOption &option, void *work, size_t work_size) { + /* Clear the work memory. */ + std::memset(work, 0, work_size); + ON_SCOPE_EXIT { std::memset(work, 0, work_size); }; + + /* Clear the output memory. */ + std::memset(dst, 0, dst_size); + auto clear_guard = SCOPE_GUARD { std::memset(dst, 0, dst_size); }; + + /* Validate parameters. */ + R_UNLESS(util::IsAligned(width, 0x10), capsrv::ResultAlbumOutOfRange()); + R_UNLESS(util::IsAligned(height, 0x4), capsrv::ResultAlbumOutOfRange()); + + R_UNLESS(dst != nullptr, capsrv::ResultAlbumReadBufferShortage()); + R_UNLESS(dst_size >= 4 * width * height, capsrv::ResultAlbumReadBufferShortage()); + + R_UNLESS(src_jpeg != nullptr, capsrv::ResultAlbumInvalidFileData()); + R_UNLESS(src_jpeg_size != 0, capsrv::ResultAlbumInvalidFileData()); + + /* Create the input. */ + const jpeg::SoftwareJpegDecoderInput decode_input = { + .jpeg = src_jpeg, + .jpeg_size = src_jpeg_size, + .width = width, + .height = height, + .fancy_upsampling = option.HasJpegDecoderFlag(ScreenShotDecoderFlag_EnableFancyUpsampling), + .block_smoothing = option.HasJpegDecoderFlag(ScreenShotDecoderFlag_EnableBlockSmoothing), + }; + + /* Create the output. */ + s32 out_width = 0, out_height = 0; + jpeg::SoftwareJpegDecoderOutput decode_output = { + .out_width = std::addressof(out_width), + .out_height = std::addressof(out_height), + .dst = dst, + .dst_size = dst_size, + }; + + /* Decode the jpeg. */ + R_TRY(jpeg::SoftwareJpegDecoder::DecodeRgba8(decode_output, decode_input, work, work_size)); + + /* We succeeded, so we shouldn't clear the output memory. */ + clear_guard.Cancel(); + R_SUCCEED(); + } + + Result ShrinkJpegImpl(u64 *out_size, void *dst, size_t dst_size, const void *src_jpeg, size_t src_jpeg_size, u32 width, u32 height, const ScreenShotDecodeOption &option, void *work, size_t work_size) { + /* Validate parameters. */ + R_UNLESS(util::IsAligned(width, 0x10), capsrv::ResultAlbumOutOfRange()); + R_UNLESS(util::IsAligned(height, 0x4), capsrv::ResultAlbumOutOfRange()); + + R_UNLESS(dst != nullptr, capsrv::ResultInternalJpegOutBufferShortage()); + R_UNLESS(dst_size != 0, capsrv::ResultAlbumReadBufferShortage()); + + R_UNLESS(src_jpeg != nullptr, capsrv::ResultAlbumInvalidFileData()); + R_UNLESS(src_jpeg_size != 0, capsrv::ResultAlbumInvalidFileData()); + + /* Create the input. */ + const jpeg::SoftwareJpegShrinkerInput shrink_input = { + .jpeg = src_jpeg, + .jpeg_size = src_jpeg_size, + .width = width, + .height = height, + .fancy_upsampling = option.HasJpegDecoderFlag(ScreenShotDecoderFlag_EnableFancyUpsampling), + .block_smoothing = option.HasJpegDecoderFlag(ScreenShotDecoderFlag_EnableBlockSmoothing), + }; + + /* Create the output. */ + u64 shrunk_size = 0; + s32 shrunk_width = 0, shrunk_height = 0; + jpeg::SoftwareJpegShrinkerOutput shrink_output = { + .out_size = std::addressof(shrunk_size), + .out_width = std::addressof(shrunk_width), + .out_height = std::addressof(shrunk_height), + .dst = dst, + .dst_size = dst_size, + }; + + /* Try to shrink the jpeg at various quality levels. */ + for (auto quality : JpegShrinkQualities) { + /* Shrink at the current quality. */ + R_TRY_CATCH(jpeg::SoftwareJpegShrinker::ShrinkRgba8(shrink_output, shrink_input, quality, work, work_size)) { + /* If the output buffer isn't large enough to fit the output, we should try at a lower quality. */ + R_CATCH(capsrv::ResultInternalJpegOutBufferShortage) { + continue; + } + /* Nintendo doesn't catch this result, but our lack of work buffer use makes me think this may be necessary. */ + R_CATCH(capsrv::ResultInternalJpegWorkMemoryShortage) { + continue; + } + } R_END_TRY_CATCH; + + /* Write the output size. */ + *out_size = shrunk_size; + R_SUCCEED(); + } + + /* Nintendo aborts if no quality succeeds. */ + AMS_ABORT("ShrinkJpeg should succeed before this point\n"); + } + + } + + Result DecoderControlService::DecodeJpeg(const ams::sf::OutNonSecureBuffer &out, const ams::sf::InBuffer &in, u32 width, u32 height, const ScreenShotDecodeOption &option) { + /* Get the work buffer. */ + void *work = g_work_memory.jpeg_decoder_memory; + size_t work_size = sizeof(g_work_memory.jpeg_decoder_memory); + + /* Call the decoder implementation. */ + R_RETURN(DecodeJpegImpl(out.GetPointer(), out.GetSize(), in.GetPointer(), in.GetSize(), width, height, option, work, work_size)); + } + + Result DecoderControlService::ShrinkJpeg(ams::sf::Out<u64> out_size, const ams::sf::OutNonSecureBuffer &out, const ams::sf::InBuffer &in, u32 width, u32 height, const capsrv::ScreenShotDecodeOption &option) { + /* Get the work buffer. */ + void *work = g_work_memory.jpeg_decoder_memory; + size_t work_size = sizeof(g_work_memory.jpeg_decoder_memory); + + /* Call the shrink implementation. */ + R_RETURN(ShrinkJpegImpl(out_size.GetPointer(), out.GetPointer(), out.GetSize(), in.GetPointer(), in.GetSize(), width, height, option, work, work_size)); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_control_service.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_control_service.hpp new file mode 100644 index 00000000..1c000ef1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_control_service.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#define AMS_CAPSRV_DECODER_CONTROL_SERVICE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 3001, Result, DecodeJpeg, (const ams::sf::OutNonSecureBuffer &out, const ams::sf::InBuffer &in, u32 width, u32 height, const capsrv::ScreenShotDecodeOption &option), (out, in, width, height, option)) \ + AMS_SF_METHOD_INFO(C, H, 4001, Result, ShrinkJpeg, (ams::sf::Out<u64> out_size, const ams::sf::OutNonSecureBuffer &out, const ams::sf::InBuffer &in, u32 width, u32 height, const capsrv::ScreenShotDecodeOption &option), (out_size, out, in, width, height, option)) + +AMS_SF_DEFINE_INTERFACE(ams::capsrv::sf, IDecoderControlService, AMS_CAPSRV_DECODER_CONTROL_SERVICE_INTERFACE_INFO, 0xD168E90B) + +namespace ams::capsrv::server { + + class DecoderControlService final { + public: + Result DecodeJpeg(const ams::sf::OutNonSecureBuffer &out, const ams::sf::InBuffer &in, u32 width, u32 height, const ScreenShotDecodeOption &option); + Result ShrinkJpeg(ams::sf::Out<u64> out_size, const ams::sf::OutNonSecureBuffer &out, const ams::sf::InBuffer &in, u32 width, u32 height, const capsrv::ScreenShotDecodeOption &option); + }; + static_assert(capsrv::sf::IsIDecoderControlService<DecoderControlService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_server_object.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_server_object.cpp new file mode 100644 index 00000000..8aba8dce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_server_object.cpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "decodersrv_decoder_server_object.hpp" + +namespace ams::capsrv::server { + + /* Instantiate the decoder server globals in a single TU. */ + DecoderWorkMemory g_work_memory; + DecoderControlServerManager g_decoder_control_server_manager; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_server_object.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_server_object.hpp new file mode 100644 index 00000000..183adc32 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_server_object.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "decodersrv_decoder_work_memory.hpp" +#include "decodersrv_decoder_control_server_manager.hpp" + +namespace ams::capsrv::server { + + extern DecoderWorkMemory g_work_memory; + extern DecoderControlServerManager g_decoder_control_server_manager; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_work_memory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_work_memory.hpp new file mode 100644 index 00000000..fe1f0b01 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/decodersrv/decodersrv_decoder_work_memory.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::capsrv::server { + + struct DecoderWorkMemory { + alignas(os::MemoryPageSize) u8 jpeg_decoder_memory[SoftwareJpegDecoderWorkMemorySize]; + }; + static_assert(sizeof(DecoderWorkMemory) == SoftwareJpegDecoderWorkMemorySize); + static_assert(alignof(DecoderWorkMemory) == os::MemoryPageSize); + static_assert(util::is_pod<DecoderWorkMemory>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/jpeg/capsrv_server_jpeg_error_handler.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/jpeg/capsrv_server_jpeg_error_handler.hpp new file mode 100644 index 00000000..04f276f6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/jpeg/capsrv_server_jpeg_error_handler.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include <csetjmp> +#include "capsrv_server_jpeg_library_types.hpp" + +namespace ams::capsrv::server::jpeg { + + struct JpegErrorHandler : public JpegLibraryType::jpeg_error_mgr { + public: + std::jmp_buf jmp_buf; + Result result; + public: + static void HandleError(JpegLibraryType::jpeg_common_struct *common) { + /* Retrieve the handler. */ + JpegErrorHandler *handler = reinterpret_cast<JpegErrorHandler *>(common->err); + + /* Set the result. */ + handler->result = GetResult(handler->msg_code, handler->msg_parm.i[0]); + + /* Return to the caller. */ + longjmp(handler->jmp_buf, -1); + } + + static Result GetResult(int msg_code, int msg_param) { + /* NOTE: Nintendo uses msg_param for error codes that we never trigger. */ + /* TODO: Fully support all J_MESSAGE_CODEs that Nintendo handles? */ + AMS_UNUSED(msg_param); + + switch (msg_code) { + case JpegLibraryType::J_MESSAGE_CODE::JERR_BUFFER_SIZE: + case JpegLibraryType::J_MESSAGE_CODE::JERR_NO_BACKING_STORE: + case JpegLibraryType::J_MESSAGE_CODE::JERR_OUT_OF_MEMORY: + case JpegLibraryType::J_MESSAGE_CODE::JERR_TFILE_CREATE: + case JpegLibraryType::J_MESSAGE_CODE::JERR_TFILE_READ: + case JpegLibraryType::J_MESSAGE_CODE::JERR_TFILE_SEEK: + case JpegLibraryType::J_MESSAGE_CODE::JERR_TFILE_WRITE: + R_THROW(capsrv::ResultInternalJpegWorkMemoryShortage()); + default: + R_THROW(capsrv::ResultInternalJpegEncoderError()); + } + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/jpeg/capsrv_server_jpeg_library_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/jpeg/capsrv_server_jpeg_library_types.hpp new file mode 100644 index 00000000..d2107d53 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/jpeg/capsrv_server_jpeg_library_types.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> +#include <jpeglib.h> +#include <jerror.h> + +namespace ams::capsrv::server::jpeg { + + class JpegLibraryType { + public: + using jpeg_common_struct = ::jpeg_common_struct; + using jpeg_compress_struct = ::jpeg_compress_struct; + using jpeg_decompress_struct = ::jpeg_decompress_struct; + using jpeg_error_mgr = ::jpeg_error_mgr; + using jpeg_destination_mgr = ::jpeg_destination_mgr; + + using j_common_ptr = ::j_common_ptr; + using j_compress_ptr = ::j_compress_ptr; + + using boolean = ::boolean; + using JOCTET = ::JOCTET; + using JDIMENSION = ::JDIMENSION; + + using JSAMPARRAY = ::JSAMPARRAY; + using JSAMPROW = ::JSAMPROW; + + using J_COLOR_SPACE = ::J_COLOR_SPACE; + using J_DCT_METHOD = ::J_DCT_METHOD; + using J_MESSAGE_CODE = ::J_MESSAGE_CODE; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/jpeg/decodersrv_software_jpeg_decoder.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/jpeg/decodersrv_software_jpeg_decoder.cpp new file mode 100644 index 00000000..e76f3331 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/jpeg/decodersrv_software_jpeg_decoder.cpp @@ -0,0 +1,185 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "decodersrv_software_jpeg_decoder.hpp" +#include "capsrv_server_jpeg_library_types.hpp" +#include "capsrv_server_jpeg_error_handler.hpp" + + +namespace ams::capsrv::server::jpeg { + + #define CAPSRV_ABORT_UNLESS(expr) do { \ + const bool __capsrv_assert_res = (expr); \ + AMS_ASSERT(__capsrv_assert_res); \ + AMS_ABORT_UNLESS(__capsrv_assert_res); \ + } while (0) + + #define CAPSRV_ASSERT(expr) do { \ + const bool __capsrv_assert_res = (expr); \ + AMS_ASSERT(__capsrv_assert_res); \ + R_UNLESS(__capsrv_assert_res, capsrv::ResultAlbumError()); \ + } while (0) + + namespace { + + constexpr s32 ImageSizeHorizonalUnit = 0x10; + constexpr s32 ImageSizeVerticalUnit = 0x4; + + constexpr s32 RgbColorComponentCount = 3; + constexpr s32 RgbaColorComponentCount = 4; + + Result GetRgbBufferSize(size_t *out_size, size_t *out_stride, s32 width, size_t work_size) { + /* Calculate the space we need and verify we have enough. */ + const size_t rgb_width = util::AlignUp(static_cast<size_t>(width), ImageSizeHorizonalUnit); + const size_t rgb_stride = rgb_width * RgbColorComponentCount; + const size_t rgb_size = rgb_stride * ImageSizeVerticalUnit; + R_UNLESS(work_size >= rgb_size, capsrv::ResultInternalJpegWorkMemoryShortage()); + + /* Return the output to the caller. */ + *out_size = rgb_size; + *out_stride = rgb_stride; + R_SUCCEED(); + } + + } + + Result SoftwareJpegDecoder::DecodeRgba8(SoftwareJpegDecoderOutput &output, const SoftwareJpegDecoderInput &input, void *work, size_t work_size) { + CAPSRV_ABORT_UNLESS(util::IsAligned(input.width, ImageSizeHorizonalUnit)); + CAPSRV_ABORT_UNLESS(util::IsAligned(input.height, ImageSizeVerticalUnit)); + + CAPSRV_ABORT_UNLESS(output.dst != nullptr); + CAPSRV_ABORT_UNLESS(output.dst_size >= static_cast<size_t>(RgbaColorComponentCount * input.width * input.height)); + + CAPSRV_ABORT_UNLESS(output.out_width != nullptr); + CAPSRV_ABORT_UNLESS(output.out_height != nullptr); + + /* Determine work buffer extents. */ + char *work_start = static_cast<char *>(work); + char *work_end = work_start + work_size; + + /* Determine the buffer extents for our linebuffers. */ + u8 *rgb_buffer = static_cast<u8 *>(static_cast<void *>(work_start)); + size_t rgb_buffer_size; + size_t rgb_buffer_stride; + R_TRY(GetRgbBufferSize(std::addressof(rgb_buffer_size), std::addressof(rgb_buffer_stride), input.width, work_size)); + + /* The start of the workbuffer is reserved for linebuffer space. */ + work_start += rgb_buffer_size; + + /* Create our decompression structure. */ + JpegLibraryType::jpeg_decompress_struct cinfo = {}; + + /* Here nintendo creates a work buffer structure containing work_start + work_size. */ + /* This seems to be a custom patch for/to libjpeg-turbo. */ + /* It would be desirable for us to mimic this, because it gives Nintendo strong */ + /* fixed memory usage guarantees. */ + /* TODO: Determine if it is feasible for us to recreate this ourselves, */ + /* Either by adding support to the devkitPro libjpeg-turbo portlib or otherwise. */ + AMS_UNUSED(work_end); + + /* Create our error manager. */ + JpegErrorHandler jerr = { + .result = ResultSuccess(), + }; + jerr.error_exit = JpegErrorHandler::HandleError, + + + /* Link our error manager to our decompression structure. */ + cinfo.err = jpeg_std_error(std::addressof(jerr)); + + /* Use setjmp, so that on error our handler will longjmp to return an error result. */ + if (setjmp(jerr.jmp_buf) == 0) { + /* Create our decompressor. */ + jpeg_create_decompress(std::addressof(cinfo)); + ON_SCOPE_EXIT { jpeg_destroy_decompress(std::addressof(cinfo)); }; + + /* Setup our memory reader, ensure the header is correct. */ + jpeg_mem_src(std::addressof(cinfo), const_cast<unsigned char *>(static_cast<const unsigned char *>(input.jpeg)), input.jpeg_size); + R_UNLESS(jpeg_read_header(std::addressof(cinfo), true) == JPEG_HEADER_OK, capsrv::ResultAlbumInvalidFileData()); + + /* Ensure width and height are correct. */ + R_UNLESS(cinfo.image_width == input.width, capsrv::ResultAlbumInvalidFileData()); + R_UNLESS(cinfo.image_height == input.height, capsrv::ResultAlbumInvalidFileData()); + + /* Set output parameters. */ + cinfo.out_color_space = JpegLibraryType::J_COLOR_SPACE::JCS_RGB; + cinfo.dct_method = JpegLibraryType::J_DCT_METHOD::JDCT_ISLOW; + cinfo.do_fancy_upsampling = input.fancy_upsampling; + cinfo.do_block_smoothing = input.block_smoothing; + + /* Start decompression. */ + R_UNLESS(jpeg_start_decompress(std::addressof(cinfo)) == TRUE, capsrv::ResultAlbumInvalidFileData()); + + /* Check the parameters. */ + CAPSRV_ASSERT(cinfo.output_width == input.width); + CAPSRV_ASSERT(cinfo.output_height == input.height); + CAPSRV_ASSERT(cinfo.out_color_components == RgbColorComponentCount); + CAPSRV_ASSERT(cinfo.output_components == RgbColorComponentCount); + + /* Parse the scanlines. */ + { + /* Convert our destination to a writable u8 buffer. */ + u8 *dst = static_cast<u8 *>(output.dst); + + /* Create our linebuffer structure. */ + JpegLibraryType::JSAMPROW linebuffers[ImageSizeVerticalUnit] = {}; + for (int i = 0; i < ImageSizeVerticalUnit; i++) { + linebuffers[i] = rgb_buffer + rgb_buffer_stride * i; + } + + /* While we still have scanlines, parse! */ + while (cinfo.output_scanline < input.height) { + /* Decode scanlines. */ + int num_scanlines = jpeg_read_scanlines(std::addressof(cinfo), linebuffers, ImageSizeVerticalUnit); + CAPSRV_ASSERT(num_scanlines <= ImageSizeVerticalUnit); + + /* Write out line by line. */ + for (s32 i = 0; i < num_scanlines; i++) { + const u8 *src = linebuffers[i]; + for (s32 j = 0; j < static_cast<s32>(input.width); j++) { + /* Write the output. */ + + /* First R, */ + *(dst++) = *(src++); + + /* Then G, */ + *(dst++) = *(src++); + + /* Then B, */ + *(dst++) = *(src++); + + /* Then A. */ + *(dst++) = 0xFF; + } + } + } + } + + /* Finish the decompression. */ + R_UNLESS(jpeg_finish_decompress(std::addressof(cinfo)) == TRUE, capsrv::ResultAlbumInvalidFileData()); + } else { + /* Some unknown error was caught by our handler. */ + R_THROW(capsrv::ResultAlbumInvalidFileData()); + } + + /* Write the size we decoded to output. */ + *output.out_width = static_cast<s32>(cinfo.output_width); + *output.out_width = static_cast<s32>(cinfo.output_height); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/jpeg/decodersrv_software_jpeg_decoder.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/jpeg/decodersrv_software_jpeg_decoder.hpp new file mode 100644 index 00000000..ec6a4c0a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/jpeg/decodersrv_software_jpeg_decoder.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::capsrv::server::jpeg { + + struct SoftwareJpegDecoderInput { + const void *jpeg; + size_t jpeg_size; + u32 width; + u32 height; + bool fancy_upsampling; + bool block_smoothing; + }; + + struct SoftwareJpegDecoderOutput { + s32 *out_width; + s32 *out_height; + void *dst; + size_t dst_size; + }; + + class SoftwareJpegDecoder { + public: + static Result DecodeRgba8(SoftwareJpegDecoderOutput &output, const SoftwareJpegDecoderInput &input, void *work, size_t work_size); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/jpeg/decodersrv_software_jpeg_shrinker.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/jpeg/decodersrv_software_jpeg_shrinker.cpp new file mode 100644 index 00000000..119f52fe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/jpeg/decodersrv_software_jpeg_shrinker.cpp @@ -0,0 +1,236 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "decodersrv_software_jpeg_shrinker.hpp" +#include "capsrv_server_jpeg_library_types.hpp" +#include "capsrv_server_jpeg_error_handler.hpp" + + +namespace ams::capsrv::server::jpeg { + + #define CAPSRV_ABORT_UNLESS(expr) do { \ + const bool __capsrv_assert_res = (expr); \ + AMS_ASSERT(__capsrv_assert_res); \ + AMS_ABORT_UNLESS(__capsrv_assert_res); \ + } while (0) + + #define CAPSRV_ASSERT(expr) do { \ + const bool __capsrv_assert_res = (expr); \ + AMS_ASSERT(__capsrv_assert_res); \ + R_UNLESS(__capsrv_assert_res, capsrv::ResultAlbumError()); \ + } while (0) + + namespace { + + constexpr s32 ImageSizeHorizonalUnit = 0x10; + constexpr s32 ImageSizeVerticalUnitDecode = 0x4; + constexpr s32 ImageSizeVerticalUnitEncode = 0x8; + + constexpr s32 RgbColorComponentCount = 3; + constexpr s32 RgbaColorComponentCount = 4; + + Result GetRgbBufferSize(size_t *out_size, size_t *out_stride, s32 width, size_t work_size) { + /* Calculate the space we need and verify we have enough. */ + const size_t rgb_width = util::AlignUp(static_cast<size_t>(width), ImageSizeHorizonalUnit); + const size_t rgb_stride = rgb_width * RgbColorComponentCount; + const size_t rgb_size = rgb_stride * ImageSizeVerticalUnitEncode; + R_UNLESS(work_size >= rgb_size, capsrv::ResultInternalJpegWorkMemoryShortage()); + + /* Return the output to the caller. */ + *out_size = rgb_size; + *out_stride = rgb_stride; + R_SUCCEED(); + } + + void SetupEncodingParameter(JpegLibraryType::jpeg_compress_struct *cinfo, const SoftwareJpegShrinkerInput &input, int quality) { + /* Set parameters. */ + cinfo->image_width = static_cast<JpegLibraryType::JDIMENSION>(input.width / 2); + cinfo->image_height = static_cast<JpegLibraryType::JDIMENSION>(input.height / 2); + cinfo->input_components = RgbColorComponentCount; + cinfo->in_color_space = JpegLibraryType::J_COLOR_SPACE::JCS_RGB; + + /* Set defaults/color space. */ + jpeg_set_defaults(cinfo); + jpeg_set_colorspace(cinfo, JpegLibraryType::J_COLOR_SPACE::JCS_YCbCr); + + /* Configure sampling. */ + /* libjpeg-turbo doesn't actually have this field, as of now. */ + /* cinfo->do_fancy_downsampling = false; */ + cinfo->comp_info[0].h_samp_factor = 2; + cinfo->comp_info[0].v_samp_factor = 1; + cinfo->comp_info[1].h_samp_factor = 1; + cinfo->comp_info[1].v_samp_factor = 1; + cinfo->comp_info[2].h_samp_factor = 1; + cinfo->comp_info[2].v_samp_factor = 1; + + /* Set the quality. */ + jpeg_set_quality(cinfo, quality, true); + + /* Configure remaining parameters. */ + cinfo->dct_method = JpegLibraryType::J_DCT_METHOD::JDCT_ISLOW; + cinfo->optimize_coding = false; + cinfo->write_JFIF_header = true; + } + + } + + Result SoftwareJpegShrinker::ShrinkRgba8(SoftwareJpegShrinkerOutput &output, const SoftwareJpegShrinkerInput &input, int quality, void *work, size_t work_size) { + CAPSRV_ABORT_UNLESS(util::IsAligned(input.width, ImageSizeHorizonalUnit)); + CAPSRV_ABORT_UNLESS(util::IsAligned(input.height, ImageSizeVerticalUnitDecode)); + + const u32 shrunk_width = input.width / 2; + const u32 shrunk_height = input.height / 2; + CAPSRV_ABORT_UNLESS(util::IsAligned(shrunk_width, ImageSizeHorizonalUnit)); + + CAPSRV_ABORT_UNLESS(output.dst != nullptr); + + CAPSRV_ABORT_UNLESS(output.out_width != nullptr); + CAPSRV_ABORT_UNLESS(output.out_height != nullptr); + + /* Determine work buffer extents. */ + char *work_start = static_cast<char *>(work); + char *work_end = work_start + work_size; + + /* Determine the buffer extents for our linebuffers. */ + u8 *rgb_buffer = static_cast<u8 *>(static_cast<void *>(work_start)); + size_t rgb_buffer_size; + size_t rgb_buffer_stride; + R_TRY(GetRgbBufferSize(std::addressof(rgb_buffer_size), std::addressof(rgb_buffer_stride), input.width, work_size)); + + /* The start of the workbuffer is reserved for linebuffer space. */ + work_start += rgb_buffer_size; + + /* Create our compression structures. */ + JpegLibraryType::jpeg_decompress_struct dcinfo = {}; + JpegLibraryType::jpeg_compress_struct ecinfo = {}; + + /* Here nintendo creates a work buffer structure containing work_start + work_size. */ + /* This seems to be a custom patch for/to libjpeg-turbo. */ + /* It would be desirable for us to mimic this, because it gives Nintendo strong */ + /* fixed memory usage guarantees. */ + /* TODO: Determine if it is feasible for us to recreate this ourselves, */ + /* Either by adding support to the devkitPro libjpeg-turbo portlib or otherwise. */ + AMS_UNUSED(work_end); + + /* Create our error managers. */ + JpegErrorHandler jerr_dc = { .result = ResultSuccess(), }; + JpegErrorHandler jerr_ec = { .result = ResultSuccess(), }; + jerr_dc.error_exit = JpegErrorHandler::HandleError, + jerr_ec.error_exit = JpegErrorHandler::HandleError, + + /* Link our error managers to our compression structures. */ + dcinfo.err = jpeg_std_error(std::addressof(jerr_dc)); + ecinfo.err = jpeg_std_error(std::addressof(jerr_ec)); + + /* Use setjmp, so that on error our handler will longjmp to return an error result. */ + if (setjmp(jerr_ec.jmp_buf) == 0) { + if (setjmp(jerr_dc.jmp_buf) == 0) { + /* Create our decompressor. */ + jpeg_create_decompress(std::addressof(dcinfo)); + ON_SCOPE_EXIT { jpeg_destroy_decompress(std::addressof(dcinfo)); }; + + /* Setup our memory reader, ensure the header is correct. */ + jpeg_mem_src(std::addressof(dcinfo), const_cast<unsigned char *>(static_cast<const unsigned char *>(input.jpeg)), input.jpeg_size); + R_UNLESS(jpeg_read_header(std::addressof(dcinfo), true) == JPEG_HEADER_OK, capsrv::ResultAlbumInvalidFileData()); + + /* Ensure width and height are correct. */ + R_UNLESS(dcinfo.image_width == input.width, capsrv::ResultAlbumInvalidFileData()); + R_UNLESS(dcinfo.image_height == input.height, capsrv::ResultAlbumInvalidFileData()); + + /* Set output parameters. */ + dcinfo.out_color_space = JpegLibraryType::J_COLOR_SPACE::JCS_RGB; + dcinfo.dct_method = JpegLibraryType::J_DCT_METHOD::JDCT_ISLOW; + dcinfo.do_fancy_upsampling = input.fancy_upsampling; + dcinfo.do_block_smoothing = input.block_smoothing; + dcinfo.scale_num = 1; + dcinfo.scale_denom = 2; + + /* Start decompression. */ + R_UNLESS(jpeg_start_decompress(std::addressof(dcinfo)) == TRUE, capsrv::ResultAlbumInvalidFileData()); + + /* Check the parameters. */ + CAPSRV_ASSERT(dcinfo.output_width == shrunk_width); + CAPSRV_ASSERT(dcinfo.output_height == shrunk_height); + CAPSRV_ASSERT(dcinfo.out_color_components == RgbColorComponentCount); + CAPSRV_ASSERT(dcinfo.output_components == RgbColorComponentCount); + + /* Create our compressor. */ + jpeg_create_compress(std::addressof(ecinfo)); + ON_SCOPE_EXIT { jpeg_destroy_compress(std::addressof(ecinfo)); }; + + /* Setup our memory writer. */ + unsigned long out_size = static_cast<unsigned long>(output.dst_size); + jpeg_mem_dest(std::addressof(ecinfo), reinterpret_cast<unsigned char **>(std::addressof(output.dst)), std::addressof(out_size)); + + /* Setup the encoding parameters. */ + SetupEncodingParameter(std::addressof(ecinfo), input, quality); + + /* Start compression. */ + jpeg_start_compress(std::addressof(ecinfo), true); + + /* Parse the scanlines. */ + { + /* Create our linebuffer structure. */ + JpegLibraryType::JSAMPROW linebuffers[ImageSizeVerticalUnitEncode] = {}; + for (int i = 0; i < ImageSizeVerticalUnitEncode; i++) { + linebuffers[i] = rgb_buffer + rgb_buffer_stride * i; + } + + /* While we still have scanlines, parse! */ + while (dcinfo.output_scanline < shrunk_height) { + /* Determine remaining scanlines. */ + const auto remaining_scanlines = shrunk_height - dcinfo.output_scanline; + const auto cur_max_scanlines = std::min<s32>(remaining_scanlines, ImageSizeVerticalUnitEncode); + + /* If we have scanlines to decode, try to do so. */ + auto writable_scanlines = 0; + while (writable_scanlines < cur_max_scanlines) { + const auto decoded = jpeg_read_scanlines(std::addressof(dcinfo), linebuffers + writable_scanlines, ImageSizeVerticalUnitDecode); + CAPSRV_ASSERT(decoded <= ImageSizeVerticalUnitDecode / 2); + + writable_scanlines += decoded; + } + + /* If we have scanlines to write, try to do so. */ + jpeg_write_scanlines(std::addressof(ecinfo), linebuffers, writable_scanlines); + } + } + + /* Finish the decompression. */ + R_UNLESS(jpeg_finish_decompress(std::addressof(dcinfo)) == TRUE, capsrv::ResultAlbumInvalidFileData()); + + /* Finish the compression. */ + jpeg_finish_compress(std::addressof(ecinfo)); + + /* Set the output size. */ + *output.out_size = out_size; + } else { + /* Some unknown error was caught by our handler. */ + R_THROW(capsrv::ResultAlbumInvalidFileData()); + } + } else { + /* Return the encoding result. */ + R_THROW(jerr_ec.result); + } + + /* Write the size we decoded to output. */ + *output.out_width = static_cast<s32>(dcinfo.output_width); + *output.out_width = static_cast<s32>(dcinfo.output_height); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/jpeg/decodersrv_software_jpeg_shrinker.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/jpeg/decodersrv_software_jpeg_shrinker.hpp new file mode 100644 index 00000000..a2774b20 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/capsrv/server/jpeg/decodersrv_software_jpeg_shrinker.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::capsrv::server::jpeg { + + struct SoftwareJpegShrinkerInput { + const void *jpeg; + size_t jpeg_size; + u32 width; + u32 height; + bool fancy_upsampling; + bool block_smoothing; + }; + + struct SoftwareJpegShrinkerOutput { + u64 *out_size; + s32 *out_width; + s32 *out_height; + void *dst; + size_t dst_size; + }; + + class SoftwareJpegShrinker { + public: + static Result ShrinkRgba8(SoftwareJpegShrinkerOutput &output, const SoftwareJpegShrinkerInput &input, int quality, void *work, size_t work_size); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_flags.board.nintendo_nx.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_flags.board.nintendo_nx.inc new file mode 100644 index 00000000..be8f4229 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_flags.board.nintendo_nx.inc @@ -0,0 +1,106 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::cfg { + + namespace { + + std::atomic<u32> g_flag_mount_count; + + /* Helper. */ + void GetFlagMountName(char *dst) { + util::SNPrintf(dst, fs::MountNameLengthMax + 1, "#flag%08x", g_flag_mount_count.fetch_add(1)); + } + + bool HasFlagFile(const char *flag_path) { + /* All flags are not present until the SD card is. */ + if (!IsSdCardInitialized()) { + return false; + } + + /* Mount the SD card. */ + char mount_name[fs::MountNameLengthMax + 1]; + GetFlagMountName(mount_name); + if (R_FAILED(fs::MountSdCard(mount_name))) { + return false; + } + ON_SCOPE_EXIT { fs::Unmount(mount_name); }; + + /* Check if the entry exists. */ + char full_path[fs::EntryNameLengthMax + 1]; + util::SNPrintf(full_path, sizeof(full_path), "%s:/%s", mount_name, flag_path[0] == '/' ? flag_path + 1 : flag_path); + + bool has_file; + if (R_FAILED(fs::HasFile(std::addressof(has_file), full_path))) { + return false; + } + + return has_file; + } + + Result DeleteFlagFile(const char *flag_path) { + /* We need the SD card to be available to delete anything. */ + AMS_ABORT_UNLESS(IsSdCardInitialized()); + + /* Mount the sd card. */ + char mount_name[fs::MountNameLengthMax + 1]; + GetFlagMountName(mount_name); + R_TRY(fs::MountSdCard(mount_name)); + ON_SCOPE_EXIT { fs::Unmount(mount_name); }; + + /* Get the flag path. */ + char full_path[fs::EntryNameLengthMax + 1]; + util::SNPrintf(full_path, sizeof(full_path), "%s:/%s", mount_name, flag_path[0] == '/' ? flag_path + 1 : flag_path); + + /* Delete the file. */ + R_TRY(fs::DeleteFile(full_path)); + + return ResultSuccess(); + } + + } + + /* Flag utilities. */ + bool HasFlag(const sm::MitmProcessInfo &process_info, const char *flag) { + return HasContentSpecificFlag(process_info.program_id, flag) || (process_info.override_status.IsHbl() && HasHblFlag(flag)); + } + + bool HasContentSpecificFlag(ncm::ProgramId program_id, const char *flag) { + char content_flag[fs::EntryNameLengthMax + 1]; + util::SNPrintf(content_flag, sizeof(content_flag) - 1, "/atmosphere/contents/%016" PRIx64 "/flags/%s.flag", program_id.value, flag); + return HasFlagFile(content_flag); + } + + bool HasGlobalFlag(const char *flag) { + char global_flag[fs::EntryNameLengthMax + 1]; + util::SNPrintf(global_flag, sizeof(global_flag) - 1, "/atmosphere/flags/%s.flag", flag); + return HasFlagFile(global_flag); + } + + bool HasHblFlag(const char *flag) { + char hbl_flag[0x100]; + util::SNPrintf(hbl_flag, sizeof(hbl_flag) - 1, "hbl_%s", flag); + return HasGlobalFlag(hbl_flag); + } + + Result DeleteGlobalFlag(const char *flag) { + char global_flag[fs::EntryNameLengthMax + 1]; + util::SNPrintf(global_flag, sizeof(global_flag) - 1, "/atmosphere/flags/%s.flag", flag); + return DeleteFlagFile(global_flag); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_flags.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_flags.cpp new file mode 100644 index 00000000..44587af1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_flags.cpp @@ -0,0 +1,22 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + #include "cfg_flags.board.nintendo_nx.inc" +#else + #include "cfg_flags.generic.inc" +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_flags.generic.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_flags.generic.inc new file mode 100644 index 00000000..ebbe6528 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_flags.generic.inc @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::cfg { + + /* Flag utilities. */ + bool HasFlag(const sm::MitmProcessInfo &process_info, const char *flag) { + AMS_UNUSED(process_info, flag); + return false; + } + + bool HasContentSpecificFlag(ncm::ProgramId program_id, const char *flag) { + AMS_UNUSED(program_id, flag); + return false; + } + + bool HasGlobalFlag(const char *flag) { + AMS_UNUSED(flag); + return false; + } + + bool HasHblFlag(const char *flag) { + AMS_UNUSED(flag); + return false; + } + + Result DeleteGlobalFlag(const char *flag) { + AMS_UNUSED(flag); + return false; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_override.board.nintendo_nx.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_override.board.nintendo_nx.inc new file mode 100644 index 00000000..a91da926 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_override.board.nintendo_nx.inc @@ -0,0 +1,443 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::cfg { + + namespace { + + /* Types. */ + struct OverrideKey { + u64 key_combination; + bool override_by_default; + }; + + struct ProgramOverrideKey { + OverrideKey override_key; + ncm::ProgramId program_id; + }; + + constexpr ProgramOverrideKey InvalidProgramOverrideKey = {}; + + constexpr ProgramOverrideKey DefaultAppletPhotoViewerOverrideKey = { + .override_key = { + .key_combination = HidNpadButton_R, + .override_by_default = true, + }, + .program_id = ncm::SystemAppletId::PhotoViewer, + }; + + constexpr size_t MaxProgramOverrideKeys = 8; + + struct HblOverrideConfig { + ProgramOverrideKey program_configs[MaxProgramOverrideKeys]; + impl::OverrideStatusFlag program_as_flags[MaxProgramOverrideKeys]; + OverrideKey override_any_app_key; + impl::OverrideStatusFlag override_any_app_as_flag; + bool override_any_app; + }; + + struct ContentSpecificOverrideConfig { + OverrideKey override_key; + OverrideKey cheat_enable_key; + OverrideLocale locale; + }; + + /* Override globals. */ + OverrideKey g_default_override_key = { + .key_combination = HidNpadButton_L, + .override_by_default = true, + }; + + OverrideKey g_default_cheat_enable_key = { + .key_combination = HidNpadButton_L, + .override_by_default = true, + }; + + HblOverrideConfig g_hbl_override_config = { + .program_configs = { + DefaultAppletPhotoViewerOverrideKey, + InvalidProgramOverrideKey, + InvalidProgramOverrideKey, + InvalidProgramOverrideKey, + InvalidProgramOverrideKey, + InvalidProgramOverrideKey, + InvalidProgramOverrideKey, + InvalidProgramOverrideKey, + }, + .program_as_flags = { + impl::OverrideStatusFlag_AddressSpace64Bit, + impl::OverrideStatusFlag_AddressSpace64Bit, + impl::OverrideStatusFlag_AddressSpace64Bit, + impl::OverrideStatusFlag_AddressSpace64Bit, + impl::OverrideStatusFlag_AddressSpace64Bit, + impl::OverrideStatusFlag_AddressSpace64Bit, + impl::OverrideStatusFlag_AddressSpace64Bit, + impl::OverrideStatusFlag_AddressSpace64Bit, + }, + .override_any_app_key = { + .key_combination = HidNpadButton_R, + .override_by_default = false, + }, + .override_any_app_as_flag = impl::OverrideStatusFlag_AddressSpace64Bit, + .override_any_app = true, + }; + + char g_hbl_sd_path[0x100] = "/atmosphere/hbl.nsp"; + + /* Helpers. */ + OverrideKey ParseOverrideKey(const char *value) { + OverrideKey cfg = {}; + + /* Parse on by default. */ + if (value[0] == '!') { + cfg.override_by_default = true; + value++; + } + + /* Parse key combination. */ + if (strcasecmp(value, "A") == 0) { + cfg.key_combination = HidNpadButton_A; + } else if (strcasecmp(value, "B") == 0) { + cfg.key_combination = HidNpadButton_B; + } else if (strcasecmp(value, "X") == 0) { + cfg.key_combination = HidNpadButton_X; + } else if (strcasecmp(value, "Y") == 0) { + cfg.key_combination = HidNpadButton_Y; + } else if (strcasecmp(value, "LS") == 0) { + cfg.key_combination = HidNpadButton_StickL; + } else if (strcasecmp(value, "RS") == 0) { + cfg.key_combination = HidNpadButton_StickR; + } else if (strcasecmp(value, "L") == 0) { + cfg.key_combination = HidNpadButton_L; + } else if (strcasecmp(value, "R") == 0) { + cfg.key_combination = HidNpadButton_R; + } else if (strcasecmp(value, "ZL") == 0) { + cfg.key_combination = HidNpadButton_ZL; + } else if (strcasecmp(value, "ZR") == 0) { + cfg.key_combination = HidNpadButton_ZR; + } else if (strcasecmp(value, "PLUS") == 0) { + cfg.key_combination = HidNpadButton_Plus; + } else if (strcasecmp(value, "MINUS") == 0) { + cfg.key_combination = HidNpadButton_Minus; + } else if (strcasecmp(value, "DLEFT") == 0) { + cfg.key_combination = HidNpadButton_Left; + } else if (strcasecmp(value, "DUP") == 0) { + cfg.key_combination = HidNpadButton_Up; + } else if (strcasecmp(value, "DRIGHT") == 0) { + cfg.key_combination = HidNpadButton_Right; + } else if (strcasecmp(value, "DDOWN") == 0) { + cfg.key_combination = HidNpadButton_Down; + } else if (strcasecmp(value, "SL") == 0) { + cfg.key_combination = HidNpadButton_AnySL; + } else if (strcasecmp(value, "SR") == 0) { + cfg.key_combination = HidNpadButton_AnySR; + } + + return cfg; + } + + impl::OverrideStatusFlag ParseOverrideAddressSpace(const char *value) { + if (strcasecmp(value, "39_bit") == 0 || strcasecmp(value, "39") == 0) { + return impl::OverrideStatusFlag_AddressSpace64Bit; + } else if (strcasecmp(value, "36_bit") == 0 || strcasecmp(value, "36") == 0) { + return impl::OverrideStatusFlag_AddressSpace64BitDeprecated; + } else if (strcasecmp(value, "32_bit") == 0 || strcasecmp(value, "32") == 0) { + return impl::OverrideStatusFlag_AddressSpace32Bit; + } else if (strcasecmp(value, "32_bit_without_alias") == 0 || + strcasecmp(value, "32_bit_no_alias") == 0 || + strcasecmp(value, "32_without_alias") == 0 || + strcasecmp(value, "32_no_alias") || + strcasecmp(value, "32_bit_without_map") == 0 || + strcasecmp(value, "32_bit_no_map") == 0 || + strcasecmp(value, "32_without_map") == 0 || + strcasecmp(value, "32_no_map") == 0) + { + return impl::OverrideStatusFlag_AddressSpace32BitWithoutAlias; + } else { + /* Default to 39-bit. */ + return impl::OverrideStatusFlag_AddressSpace64Bit; + } + } + + inline void SetHblSpecificProgramId(size_t i, const char *value) { + g_hbl_override_config.program_configs[i].program_id = {strtoul(value, nullptr, 16)}; + } + + inline void SetHblSpecificOverrideKey(size_t i, const char *value) { + g_hbl_override_config.program_configs[i].override_key = ParseOverrideKey(value); + } + + inline void SetHblSpecificAddressSpace(size_t i, const char *value) { + g_hbl_override_config.program_as_flags[i] = ParseOverrideAddressSpace(value); + } + + int OverrideConfigIniHandler(void *user, const char *section, const char *name, const char *value) { + AMS_UNUSED(user); + + /* Taken and modified, with love, from Rajkosto's implementation. */ + if (strcasecmp(section, "hbl_config") == 0) { + if (strcasecmp(name, "program_id") == 0 || strcasecmp(name, "program_id_0") == 0) { + SetHblSpecificProgramId(0, value); + } else if (strcasecmp(name, "program_id_1") == 0) { + SetHblSpecificProgramId(1, value); + } else if (strcasecmp(name, "program_id_2") == 0) { + SetHblSpecificProgramId(2, value); + } else if (strcasecmp(name, "program_id_3") == 0) { + SetHblSpecificProgramId(3, value); + } else if (strcasecmp(name, "program_id_4") == 0) { + SetHblSpecificProgramId(4, value); + } else if (strcasecmp(name, "program_id_5") == 0) { + SetHblSpecificProgramId(5, value); + } else if (strcasecmp(name, "program_id_6") == 0) { + SetHblSpecificProgramId(6, value); + } else if (strcasecmp(name, "program_id_7") == 0) { + SetHblSpecificProgramId(7, value); + } else if (strcasecmp(name, "override_key") == 0 || strcasecmp(name, "override_key_0") == 0) { + SetHblSpecificOverrideKey(0, value); + } else if (strcasecmp(name, "override_key_1") == 0) { + SetHblSpecificOverrideKey(1, value); + } else if (strcasecmp(name, "override_key_2") == 0) { + SetHblSpecificOverrideKey(2, value); + } else if (strcasecmp(name, "override_key_3") == 0) { + SetHblSpecificOverrideKey(3, value); + } else if (strcasecmp(name, "override_key_4") == 0) { + SetHblSpecificOverrideKey(4, value); + } else if (strcasecmp(name, "override_key_5") == 0) { + SetHblSpecificOverrideKey(5, value); + } else if (strcasecmp(name, "override_key_6") == 0) { + SetHblSpecificOverrideKey(6, value); + } else if (strcasecmp(name, "override_key_7") == 0) { + SetHblSpecificOverrideKey(7, value); + } else if (strcasecmp(name, "override_address_space") == 0 || strcasecmp(name, "override_address_space_0") == 0) { + SetHblSpecificAddressSpace(0, value); + } else if (strcasecmp(name, "override_address_space_1") == 0) { + SetHblSpecificAddressSpace(1, value); + } else if (strcasecmp(name, "override_address_space_2") == 0) { + SetHblSpecificAddressSpace(2, value); + } else if (strcasecmp(name, "override_address_space_3") == 0) { + SetHblSpecificAddressSpace(3, value); + } else if (strcasecmp(name, "override_address_space_4") == 0) { + SetHblSpecificAddressSpace(4, value); + } else if (strcasecmp(name, "override_address_space_5") == 0) { + SetHblSpecificAddressSpace(5, value); + } else if (strcasecmp(name, "override_address_space_6") == 0) { + SetHblSpecificAddressSpace(6, value); + } else if (strcasecmp(name, "override_address_space_7") == 0) { + SetHblSpecificAddressSpace(7, value); + } else if (strcasecmp(name, "override_any_app") == 0) { + if (strcasecmp(value, "true") == 0 || strcasecmp(value, "1") == 0) { + g_hbl_override_config.override_any_app = true; + } else if (strcasecmp(value, "false") == 0 || strcasecmp(value, "0") == 0) { + g_hbl_override_config.override_any_app = false; + } else { + /* I guess we default to not changing the value? */ + } + } else if (strcasecmp(name, "override_any_app_key") == 0) { + g_hbl_override_config.override_any_app_key = ParseOverrideKey(value); + } else if (strcasecmp(name, "override_any_app_address_space") == 0) { + g_hbl_override_config.override_any_app_as_flag = ParseOverrideAddressSpace(value); + } else if (strcasecmp(name, "path") == 0) { + while (*value == '/' || *value == '\\') { + value++; + } + util::SNPrintf(g_hbl_sd_path, sizeof(g_hbl_sd_path) - 1, "/%s", value); + g_hbl_sd_path[sizeof(g_hbl_sd_path) - 1] = '\0'; + + for (size_t i = 0; i < sizeof(g_hbl_sd_path); i++) { + if (g_hbl_sd_path[i] == '\\') { + g_hbl_sd_path[i] = '/'; + } + } + } + } else if (strcasecmp(section, "default_config") == 0) { + if (strcasecmp(name, "override_key") == 0) { + g_default_override_key = ParseOverrideKey(value); + } else if (strcasecmp(name, "cheat_enable_key") == 0) { + g_default_cheat_enable_key = ParseOverrideKey(value); + } + } else { + return 0; + } + return 1; + } + + int ContentSpecificIniHandler(void *user, const char *section, const char *name, const char *value) { + ContentSpecificOverrideConfig *config = reinterpret_cast<ContentSpecificOverrideConfig *>(user); + + if (strcasecmp(section, "override_config") == 0) { + if (strcasecmp(name, "override_key") == 0) { + config->override_key = ParseOverrideKey(value); + } else if (strcasecmp(name, "cheat_enable_key") == 0) { + config->cheat_enable_key = ParseOverrideKey(value); + } else if (strcasecmp(name, "override_language") == 0) { + config->locale.language_code = settings::LanguageCode::Encode(value); + } else if (strcasecmp(name, "override_region") == 0) { + if (strcasecmp(value, "jpn") == 0) { + config->locale.region_code = settings::RegionCode_Japan; + } else if (strcasecmp(value, "usa") == 0) { + config->locale.region_code = settings::RegionCode_America; + } else if (strcasecmp(value, "eur") == 0) { + config->locale.region_code = settings::RegionCode_Europe; + } else if (strcasecmp(value, "aus") == 0) { + config->locale.region_code = settings::RegionCode_Australia; + } else if (strcasecmp(value, "chn") == 0) { + config->locale.region_code = settings::RegionCode_China; + } else if (strcasecmp(value, "kor") == 0) { + config->locale.region_code = settings::RegionCode_Korea; + } else if (strcasecmp(value, "twn") == 0) { + config->locale.region_code = settings::RegionCode_Taiwan; + } + } + } else { + return 0; + } + + return 1; + } + + constexpr inline bool IsOverrideMatch(const OverrideStatus &status, const OverrideKey &cfg) { + bool keys_triggered = ((status.keys_held & cfg.key_combination) != 0); + return (cfg.override_by_default ^ keys_triggered); + } + + inline bool IsAnySpecificHblProgramId(ncm::ProgramId program_id) { + for (size_t i = 0; i < MaxProgramOverrideKeys; i++) { + if (program_id == g_hbl_override_config.program_configs[i].program_id) { + return true; + } + } + return false; + } + + inline bool IsSpecificHblProgramId(size_t i, ncm::ProgramId program_id) { + return program_id == g_hbl_override_config.program_configs[i].program_id; + } + + inline bool IsAnyApplicationHblProgramId(ncm::ProgramId program_id) { + return g_hbl_override_config.override_any_app && ncm::IsApplicationId(program_id) && !IsAnySpecificHblProgramId(program_id); + } + + std::atomic<u32> g_ini_mount_count; + + void GetIniMountName(char *dst) { + util::SNPrintf(dst, fs::MountNameLengthMax + 1, "#ini%08x", g_ini_mount_count.fetch_add(1)); + } + + void ParseIniFile(util::ini::Handler handler, const char *path, void *user_ctx) { + /* Mount the SD card. */ + char mount_name[fs::MountNameLengthMax + 1]; + GetIniMountName(mount_name); + if (R_FAILED(fs::MountSdCard(mount_name))) { + return; + } + ON_SCOPE_EXIT { fs::Unmount(mount_name); }; + + /* Open the file. */ + fs::FileHandle file; + { + char full_path[fs::EntryNameLengthMax + 1]; + util::SNPrintf(full_path, sizeof(full_path), "%s:/%s", mount_name, path[0] == '/' ? path + 1 : path); + if (R_FAILED(fs::OpenFile(std::addressof(file), full_path, fs::OpenMode_Read))) { + return; + } + } + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Parse the config. */ + util::ini::ParseFile(file, user_ctx, handler); + } + + void RefreshOverrideConfiguration() { + ParseIniFile(OverrideConfigIniHandler, "/atmosphere/config/override_config.ini", nullptr); + } + + ContentSpecificOverrideConfig GetContentOverrideConfig(ncm::ProgramId program_id) { + char path[fs::EntryNameLengthMax + 1]; + util::SNPrintf(path, sizeof(path), "/atmosphere/contents/%016" PRIx64 "/config.ini", program_id.value); + + ContentSpecificOverrideConfig config = { + .override_key = g_default_override_key, + .cheat_enable_key = g_default_cheat_enable_key, + }; + std::memset(std::addressof(config.locale), 0xCC, sizeof(config.locale)); + + ParseIniFile(ContentSpecificIniHandler, path, std::addressof(config)); + return config; + } + + } + + OverrideStatus CaptureOverrideStatus(ncm::ProgramId program_id) { + OverrideStatus status = {}; + + /* If the SD card isn't initialized, we can't override. */ + if (!IsSdCardInitialized()) { + return status; + } + + /* For system modules and anything launched before the home menu, always override. */ + if (program_id < ncm::SystemAppletId::Start || !pm::info::HasLaunchedBootProgram(ncm::SystemAppletId::Qlaunch)) { + status.SetProgramSpecific(); + return status; + } + + /* Unconditionally refresh override_config.ini contents. */ + RefreshOverrideConfiguration(); + + /* If we can't read the key state, don't override anything. */ + if (R_FAILED(hid::GetKeysHeld(std::addressof(status.keys_held)))) { + return status; + } + + /* Detect Hbl. */ + if (IsAnyApplicationHblProgramId(program_id) && IsOverrideMatch(status, g_hbl_override_config.override_any_app_key)) { + status.SetHbl(); + status.flags &= ~impl::OverrideStatusFlag_AddressSpaceMask; + status.flags |= g_hbl_override_config.override_any_app_as_flag; + } + for (size_t i = 0; i < MaxProgramOverrideKeys; i++) { + if (IsSpecificHblProgramId(i, program_id) && IsOverrideMatch(status, g_hbl_override_config.program_configs[i].override_key)) { + status.SetHbl(); + status.flags &= ~impl::OverrideStatusFlag_AddressSpaceMask; + status.flags |= g_hbl_override_config.program_as_flags[i]; + } + } + + /* Detect content specific keys. */ + const auto content_cfg = GetContentOverrideConfig(program_id); + if (IsOverrideMatch(status, content_cfg.override_key)) { + status.SetProgramSpecific(); + } + + /* Only allow cheat enable if not HBL. */ + if (!status.IsHbl() && IsOverrideMatch(status, content_cfg.cheat_enable_key)) { + status.SetCheatEnabled(); + } + + return status; + } + + OverrideLocale GetOverrideLocale(ncm::ProgramId program_id) { + return GetContentOverrideConfig(program_id).locale; + } + + /* HBL Configuration utilities. */ + const char *GetHblPath() { + return g_hbl_sd_path; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_override.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_override.cpp new file mode 100644 index 00000000..269ba6af --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_override.cpp @@ -0,0 +1,22 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + #include "cfg_override.board.nintendo_nx.inc" +#else + #include "cfg_override.generic.inc" +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_override.generic.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_override.generic.inc new file mode 100644 index 00000000..2d6ba420 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_override.generic.inc @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::cfg { + + OverrideStatus CaptureOverrideStatus(ncm::ProgramId program_id) { + AMS_UNUSED(program_id); + AMS_ABORT("TODO: How should this work?"); + } + + OverrideLocale GetOverrideLocale(ncm::ProgramId program_id) { + AMS_UNUSED(program_id); + AMS_ABORT("TODO: How should this work?"); + } + + /* HBL Configuration utilities. */ + const char *GetHblPath() { + AMS_ABORT("TODO: How should this work?"); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_sd_card.board.nintendo_nx.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_sd_card.board.nintendo_nx.inc new file mode 100644 index 00000000..999f524f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_sd_card.board.nintendo_nx.inc @@ -0,0 +1,96 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::cfg { + + namespace { + + /* Convenience definitions. */ + constexpr sm::ServiceName RequiredServicesForSdCardAccess[] = { + sm::ServiceName::Encode("pcv"), + sm::ServiceName::Encode("gpio"), + sm::ServiceName::Encode("pinmux"), + sm::ServiceName::Encode("psc:m"), + }; + constexpr size_t NumRequiredServicesForSdCardAccess = util::size(RequiredServicesForSdCardAccess); + + /* SD card globals. */ + constinit os::SdkMutex g_sd_card_lock; + constinit bool g_sd_card_initialized = false; + constinit FsFileSystem g_sd_card_filesystem = {}; + + /* SD card helpers. */ + Result CheckSdCardServicesReady() { + for (size_t i = 0; i < NumRequiredServicesForSdCardAccess; i++) { + bool service_present = false; + R_TRY(sm::HasService(std::addressof(service_present), RequiredServicesForSdCardAccess[i])); + if (!service_present) { + return fs::ResultSdCardNotPresent(); + } + } + + return ResultSuccess(); + } + + void WaitSdCardServicesReadyImpl() { + for (size_t i = 0; i < NumRequiredServicesForSdCardAccess; i++) { + R_ABORT_UNLESS(sm::WaitService(RequiredServicesForSdCardAccess[i])); + } + } + + Result TryInitializeSdCard() { + R_TRY(CheckSdCardServicesReady()); + R_ABORT_UNLESS(fsOpenSdCardFileSystem(std::addressof(g_sd_card_filesystem))); + g_sd_card_initialized = true; + return ResultSuccess(); + } + + void InitializeSdCard() { + WaitSdCardServicesReadyImpl(); + R_ABORT_UNLESS(fsOpenSdCardFileSystem(std::addressof(g_sd_card_filesystem))); + g_sd_card_initialized = true; + } + + } + + /* SD card utilities. */ + bool IsSdCardRequiredServicesReady() { + return R_SUCCEEDED(CheckSdCardServicesReady()); + } + + void WaitSdCardRequiredServicesReady() { + WaitSdCardServicesReadyImpl(); + } + + bool IsSdCardInitialized() { + std::scoped_lock lk(g_sd_card_lock); + + if (!g_sd_card_initialized) { + if (R_SUCCEEDED(TryInitializeSdCard())) { + g_sd_card_initialized = true; + } + } + return g_sd_card_initialized; + } + + void WaitSdCardInitialized() { + std::scoped_lock lk(g_sd_card_lock); + + InitializeSdCard(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_sd_card.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_sd_card.cpp new file mode 100644 index 00000000..f3e24356 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_sd_card.cpp @@ -0,0 +1,22 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + #include "cfg_sd_card.board.nintendo_nx.inc" +#else + #include "cfg_sd_card.generic.inc" +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_sd_card.generic.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_sd_card.generic.inc new file mode 100644 index 00000000..acc0e42e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cfg/cfg_sd_card.generic.inc @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::cfg { + + bool IsSdCardRequiredServicesReady() { + return true; + } + + void WaitSdCardRequiredServicesReady() { + /* ... */ + } + + bool IsSdCardInitialized() { + return true; + } + + void WaitSdCardInitialized() { + /* ... */ + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/crypto/crypto_csrng.os.generic.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/crypto/crypto_csrng.os.generic.cpp new file mode 100644 index 00000000..ecb9e2d0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/crypto/crypto_csrng.os.generic.cpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../spl/impl/spl_ctr_drbg.hpp" +#include <sys/random.h> + +namespace ams::crypto { + + namespace { + + using Drbg = ::ams::spl::impl::CtrDrbg<crypto::AesEncryptor128, crypto::AesEncryptor128::KeySize, false>; + + constinit util::TypedStorage<Drbg> g_drbg = {}; + + bool InitializeCsrng() { + u8 seed[Drbg::SeedSize]; + AMS_ABORT_UNLESS(::getentropy(seed, sizeof(seed)) == 0); + + util::ConstructAt(g_drbg); + util::GetReference(g_drbg).Initialize(seed, sizeof(seed), nullptr, 0, nullptr, 0); + + return true; + } + + } + + void GenerateCryptographicallyRandomBytes(void *dst, size_t dst_size) { + AMS_FUNCTION_LOCAL_STATIC(bool, s_initialized, InitializeCsrng()); + AMS_ABORT_UNLESS(s_initialized); + + AMS_ASSERT(dst_size <= Drbg::RequestSizeMax); + + if (!util::GetReference(g_drbg).Generate(dst, dst_size, nullptr, 0)) { + /* Reseed, if needed. */ + { + u8 seed[Drbg::SeedSize]; + AMS_ABORT_UNLESS(::getentropy(seed, sizeof(seed)) == 0); + + util::GetReference(g_drbg).Reseed(seed, sizeof(seed), nullptr, 0); + } + + util::GetReference(g_drbg).Generate(dst, dst_size, nullptr, 0); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/crypto/crypto_csrng.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/crypto/crypto_csrng.os.horizon.cpp new file mode 100644 index 00000000..577b9d1d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/crypto/crypto_csrng.os.horizon.cpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::crypto { + + namespace { + + constinit bool g_initialized = false; + constinit os::SdkMutex g_lock; + + void InitializeCsrng() { + AMS_ASSERT(!g_initialized); + R_ABORT_UNLESS(sm::Initialize()); + R_ABORT_UNLESS(::csrngInitialize()); + } + + } + + void GenerateCryptographicallyRandomBytes(void *dst, size_t dst_size) { + if (AMS_UNLIKELY(!g_initialized)) { + std::scoped_lock lk(g_lock); + + if (AMS_LIKELY(!g_initialized)) { + InitializeCsrng(); + g_initialized = true; + } + } + + R_ABORT_UNLESS(::csrngGetRandomBytes(dst, dst_size)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/crypto/crypto_csrng.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/crypto/crypto_csrng.os.windows.cpp new file mode 100644 index 00000000..a75f0b60 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/crypto/crypto_csrng.os.windows.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <stratosphere/windows.hpp> +#include <ntstatus.h> +#include <bcrypt.h> + +namespace ams::crypto { + + void GenerateCryptographicallyRandomBytes(void *dst, size_t dst_size) { + const auto status = ::BCryptGenRandom(nullptr, static_cast<PUCHAR>(dst), dst_size, BCRYPT_USE_SYSTEM_PREFERRED_RNG); + AMS_ABORT_UNLESS(status == STATUS_SUCCESS); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_audio_server.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_audio_server.cpp new file mode 100644 index 00000000..b29de6ff --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_audio_server.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::cs { + + void InitializeAudioServer() { + /* TODO: Support audio server. */ + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_command_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_command_impl.hpp new file mode 100644 index 00000000..b8613088 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_command_impl.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::cs { + + struct CommandDataTakeScreenShot { + vi::LayerStack layer_stack; + u8 *buffer; + size_t buffer_size; + }; + + Result DoGetFirmwareVersionCommand(settings::system::FirmwareVersion *out); + + template<typename SendHeader, typename SendData> + Result DoTakeScreenShotCommand(const CommandDataTakeScreenShot ¶ms, SendHeader send_header, SendData send_data); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_command_impl.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_command_impl.inc new file mode 100644 index 00000000..5e8affc3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_command_impl.inc @@ -0,0 +1,82 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +namespace ams::cs { + + namespace { + + template<typename SendData> + void SendEmptyData(const CommandDataTakeScreenShot ¶ms, size_t remaining_size, SendData send_data) { + /* Clear the data buffer. */ + std::memset(params.buffer, 0, params.buffer_size); + + /* Send data until the end. */ + while (remaining_size > 0) { + /* Send as much as we can. */ + const auto cur_size = std::min(remaining_size, params.buffer_size); + send_data(params.buffer, cur_size); + + /* Advance. */ + remaining_size -= cur_size; + } + } + + } + + Result DoGetFirmwareVersionCommand(settings::system::FirmwareVersion *out) { + settings::system::GetFirmwareVersion(out); + return ResultSuccess(); + } + + template<typename SendHeader, typename SendData> + Result DoTakeScreenShotCommand(const CommandDataTakeScreenShot ¶ms, SendHeader send_header, SendData send_data) { + /* Initialize screenshot control. */ + R_TRY(capsrv::InitializeScreenShotControl()); + + /* Finalize screenshot control when we're done. */ + ON_SCOPE_EXIT { capsrv::FinalizeScreenShotControl(); }; + + /* Open screenshot read stream. */ + size_t data_size; + s32 width, height; + R_TRY(capsrv::OpenRawScreenShotReadStreamForDevelop(std::addressof(data_size), std::addressof(width), std::addressof(height), params.layer_stack, TimeSpan::FromSeconds(10))); + + /* Close the screenshot stream when we're done. */ + ON_SCOPE_EXIT { capsrv::CloseRawScreenShotReadStreamForDevelop(); }; + + /* Send the header. */ + send_header(static_cast<s32>(data_size), width, height); + + /* Read and send data. */ + size_t total_read_size = 0; + auto data_guard = SCOPE_GUARD { SendEmptyData(params, data_size - total_read_size, send_data); }; + while (total_read_size < data_size) { + /* Read data from the stream. */ + size_t read_size; + R_TRY(capsrv::ReadRawScreenShotReadStreamForDevelop(std::addressof(read_size), params.buffer, params.buffer_size, total_read_size)); + + /* Send the data that was read. */ + send_data(params.buffer, read_size); + + /* Advance. */ + total_read_size += read_size; + } + data_guard.Cancel(); + + return ResultSuccess(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_command_processor.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_command_processor.cpp new file mode 100644 index 00000000..6b1924ae --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_command_processor.cpp @@ -0,0 +1,129 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "cs_command_impl.hpp" + +/* Include command implementations. */ +#include "cs_command_impl.inc" + +namespace ams::cs { + + namespace { + + struct ResponseFirmwareVersion { + ResponseHeader header; + settings::system::FirmwareVersion firmware_version; + }; + + struct ResponseTakeScreenShot { + ResponseHeader header; + s32 data_size; + s32 width; + s32 height; + }; + + constinit u8 g_data[0x1000]; + + } + + bool CommandProcessor::ProcessCommand(const CommandHeader &header, const u8 *body, s32 socket) { + switch (header.command) { + case Command_GetFirmwareVersion: + SendFirmwareVersion(socket, header); + break; + case Command_TakeScreenShot: + this->TakeScreenShot(header, socket, vi::LayerStack_ApplicationForDebug); + break; + case Command_TakeForegroundScreenShot: + this->TakeScreenShot(header, socket, vi::LayerStack_LastFrame); + break; + /* TODO: Command support. */ + default: + scs::CommandProcessor::ProcessCommand(header, body, socket); + break; + } + + return true; + } + + void CommandProcessor::SendFirmwareVersion(s32 socket, const CommandHeader &header) { + /* Build the response. */ + ResponseFirmwareVersion response = { + .header = { + .id = header.id, + .response = Response_FirmwareVersion, + .body_size = sizeof(response) - sizeof(response.header), + }, + .firmware_version = {}, + }; + + /* Get the firmware version. */ + const Result result = DoGetFirmwareVersionCommand(std::addressof(response.firmware_version)); + if (R_SUCCEEDED(result)) { + /* Send the response. */ + auto lk = MakeSendGuardBlock(); + Send(socket, std::addressof(response), sizeof(response)); + } else { + SendErrorResult(socket, header, result); + } + } + + void CommandProcessor::TakeScreenShot(const CommandHeader &header, s32 socket, vi::LayerStack layer_stack) { + /* Create the command data. */ + const CommandDataTakeScreenShot params = { + .layer_stack = layer_stack, + .buffer = g_data, + .buffer_size = sizeof(g_data), + }; + + /* Take the screenshot. */ + Result result; + { + /* Acquire the send lock. */ + auto lk = MakeSendGuardBlock(); + + /* Perform the command. */ + result = DoTakeScreenShotCommand(params, [&](s32 data_size, s32 width, s32 height) { + /* Use global buffer for response. */ + ResponseTakeScreenShot *response = reinterpret_cast<ResponseTakeScreenShot *>(g_data); + + /* Set response header. */ + *response = { + .header = { + .id = header.id, + .response = Response_ScreenShot, + .body_size = static_cast<u32>(sizeof(data_size) + sizeof(width) + sizeof(height) + data_size), + }, + .data_size = data_size, + .width = width, + .height = height, + }; + + /* Send data. */ + Send(socket, response, sizeof(*response)); + }, [&](u8 *data, size_t data_size) { + /* Send data. */ + Send(socket, data, data_size); + }); + } + + /* Handle the error case. */ + if (R_FAILED(result)) { + SendErrorResult(socket, header, result); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_hid_server.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_hid_server.cpp new file mode 100644 index 00000000..c3f64eac --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_hid_server.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::cs { + + void InitializeHidServer() { + /* TODO: Support hid redirection server. */ + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_remote_video_server.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_remote_video_server.cpp new file mode 100644 index 00000000..ac8914f8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_remote_video_server.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::cs { + + void InitializeRemoteVideoServer() { + /* TODO: Support remote video server. */ + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_target_io_server.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_target_io_server.cpp new file mode 100644 index 00000000..04566cc2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/cs/cs_target_io_server.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::cs { + + void InitializeTargetIoServer() { + /* Launch target io server. */ + os::ProcessId process_id; + scs::LaunchProgram(std::addressof(process_id), ncm::ProgramLocation::Make(ncm::SystemProgramId::DevServer, ncm::StorageId::None), nullptr, 0, 0); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dd/dd_device_address_space.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dd/dd_device_address_space.cpp new file mode 100644 index 00000000..7a9fa3d5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dd/dd_device_address_space.cpp @@ -0,0 +1,132 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/dd_device_address_space_impl.hpp" + +namespace ams::dd { + + Result CreateDeviceAddressSpace(DeviceAddressSpaceType *das, u64 address, u64 size) { + /* Check pre-conditions. */ + AMS_ASSERT(util::IsAligned(address, os::MemoryPageSize)); + AMS_ASSERT(util::IsAligned(size, os::MemoryPageSize)); + AMS_ASSERT(size > 0); + + /* Ensure we leave in a consistent state. */ + auto state_guard = SCOPE_GUARD { das->state = DeviceAddressSpaceType::State_NotInitialized; }; + + /* Create the address space. */ + DeviceAddressSpaceHandle handle; + R_TRY(impl::DeviceAddressSpaceImpl::Create(std::addressof(handle), address, size)); + + /* Set the values in the das. */ + das->device_handle = handle; + das->is_handle_managed = true; + das->state = DeviceAddressSpaceType::State_Initialized; + + /* We succeeded. */ + state_guard.Cancel(); + R_SUCCEED(); + } + + Result CreateDeviceAddressSpace(DeviceAddressSpaceType *das, u64 size) { + R_RETURN(CreateDeviceAddressSpace(das, 0, size)); + } + + void DestroyDeviceAddressSpace(DeviceAddressSpaceType *das) { + /* Check pre-conditions. */ + AMS_ASSERT(das->state == DeviceAddressSpaceType::State_Initialized); + + /* Destroy the handle. */ + if (das->is_handle_managed) { + impl::DeviceAddressSpaceImpl::Close(das->device_handle); + } + + das->device_handle = 0; + das->is_handle_managed = false; + das->state = DeviceAddressSpaceType::State_NotInitialized; + } + + void AttachDeviceAddressSpaceHandle(DeviceAddressSpaceType *das, DeviceAddressSpaceHandle handle, bool managed) { + /* Check pre-conditions. */ + AMS_ASSERT(handle != os::InvalidNativeHandle); + + das->device_handle = handle; + das->is_handle_managed = managed; + das->state = DeviceAddressSpaceType::State_Initialized; + } + + DeviceAddressSpaceHandle GetDeviceAddressSpaceHandle(DeviceAddressSpaceType *das) { + /* Check pre-conditions. */ + AMS_ASSERT(das->state == DeviceAddressSpaceType::State_Initialized); + + return das->device_handle; + } + + Result MapDeviceAddressSpaceAligned(DeviceAddressSpaceType *das, ProcessHandle process_handle, u64 process_address, size_t size, DeviceVirtualAddress device_address, MemoryPermission device_perm) { + /* Check pre-conditions. */ + AMS_ASSERT(das->state == DeviceAddressSpaceType::State_Initialized); + AMS_ASSERT(util::IsAligned(process_address, os::MemoryPageSize)); + AMS_ASSERT(util::IsAligned(device_address, os::MemoryPageSize)); + AMS_ASSERT(util::IsAligned(size, os::MemoryPageSize)); + AMS_ASSERT(process_address + size > process_address); + AMS_ASSERT(device_address + size > device_address); + AMS_ASSERT(size > 0); + AMS_ASSERT((process_address & (4_MB - 1)) == (device_address & (4_MB - 1))); + + R_RETURN(impl::DeviceAddressSpaceImpl::MapAligned(das->device_handle, process_handle, process_address, size, device_address, device_perm)); + } + + Result MapDeviceAddressSpaceNotAligned(DeviceAddressSpaceType *das, ProcessHandle process_handle, u64 process_address, size_t size, DeviceVirtualAddress device_address, MemoryPermission device_perm) { + /* Check pre-conditions. */ + AMS_ASSERT(das->state == DeviceAddressSpaceType::State_Initialized); + AMS_ASSERT(util::IsAligned(process_address, os::MemoryPageSize)); + AMS_ASSERT(util::IsAligned(device_address, os::MemoryPageSize)); + AMS_ASSERT(util::IsAligned(size, os::MemoryPageSize)); + AMS_ASSERT(process_address + size > process_address); + AMS_ASSERT(device_address + size > device_address); + AMS_ASSERT(size > 0); + + R_RETURN(impl::DeviceAddressSpaceImpl::MapNotAligned(das->device_handle, process_handle, process_address, size, device_address, device_perm)); + } + + void UnmapDeviceAddressSpace(DeviceAddressSpaceType *das, ProcessHandle process_handle, u64 process_address, size_t size, DeviceVirtualAddress device_address) { + /* Check pre-conditions. */ + AMS_ASSERT(das->state == DeviceAddressSpaceType::State_Initialized); + AMS_ASSERT(util::IsAligned(process_address, os::MemoryPageSize)); + AMS_ASSERT(util::IsAligned(device_address, os::MemoryPageSize)); + AMS_ASSERT(util::IsAligned(size, os::MemoryPageSize)); + AMS_ASSERT(process_address + size > process_address); + AMS_ASSERT(device_address + size > device_address); + AMS_ASSERT(size > 0); + + return impl::DeviceAddressSpaceImpl::Unmap(das->device_handle, process_handle, process_address, size, device_address); + } + + Result AttachDeviceAddressSpace(DeviceAddressSpaceType *das, DeviceName device_name) { + /* Check pre-conditions. */ + AMS_ASSERT(das->state == DeviceAddressSpaceType::State_Initialized); + + R_RETURN(impl::DeviceAddressSpaceImpl::Attach(das, device_name)); + } + + void DetachDeviceAddressSpace(DeviceAddressSpaceType *das, DeviceName device_name) { + /* Check pre-conditions. */ + AMS_ASSERT(das->state == DeviceAddressSpaceType::State_Initialized); + + return impl::DeviceAddressSpaceImpl::Detach(das, device_name); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dd/impl/dd_device_address_space_impl.generic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dd/impl/dd_device_address_space_impl.generic.hpp new file mode 100644 index 00000000..9d2f7731 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dd/impl/dd_device_address_space_impl.generic.hpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::dd::impl { + + class DeviceAddressSpaceImplByWindows { + public: + static Result Create(DeviceAddressSpaceHandle *, u64, u64) { + R_THROW(dd::ResultNotSupported()); + } + + static void Close(DeviceAddressSpaceHandle) { + /* ... */ + } + + static Result MapAligned(DeviceAddressSpaceHandle, ProcessHandle, u64, size_t, DeviceVirtualAddress, dd::MemoryPermission) { + R_THROW(dd::ResultNotSupported()); + } + + static Result MapNotAligned(DeviceAddressSpaceHandle, ProcessHandle, u64, size_t, DeviceVirtualAddress, dd::MemoryPermission) { + R_THROW(dd::ResultNotSupported()); + } + + static void Unmap(DeviceAddressSpaceHandle, ProcessHandle, u64, size_t, DeviceVirtualAddress) { + /* ... */ + } + + static Result Attach(DeviceAddressSpaceType *, DeviceName) { + R_THROW(dd::ResultNotSupported()); + } + + static void Detach(DeviceAddressSpaceType *, DeviceName) { + /* ... */ + } + }; + + using DeviceAddressSpaceImpl = DeviceAddressSpaceImplByWindows; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dd/impl/dd_device_address_space_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dd/impl/dd_device_address_space_impl.hpp new file mode 100644 index 00000000..92611be8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dd/impl/dd_device_address_space_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "dd_device_address_space_impl.os.horizon.hpp" +#else + #include "dd_device_address_space_impl.generic.hpp" +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dd/impl/dd_device_address_space_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dd/impl/dd_device_address_space_impl.os.horizon.cpp new file mode 100644 index 00000000..1b62ad83 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dd/impl/dd_device_address_space_impl.os.horizon.cpp @@ -0,0 +1,88 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dd_device_address_space_impl.os.horizon.hpp" + +namespace ams::dd::impl { + + static_assert(static_cast<int>(dd::MemoryPermission_None) == static_cast<int>(svc::MemoryPermission_None)); + static_assert(static_cast<int>(dd::MemoryPermission_ReadOnly) == static_cast<int>(svc::MemoryPermission_Read)); + static_assert(static_cast<int>(dd::MemoryPermission_WriteOnly) == static_cast<int>(svc::MemoryPermission_Write)); + static_assert(static_cast<int>(dd::MemoryPermission_ReadWrite) == static_cast<int>(svc::MemoryPermission_ReadWrite)); + + Result DeviceAddressSpaceImplByHorizon::Create(DeviceAddressSpaceHandle *out, u64 address, u64 size) { + /* Create the space. */ + svc::Handle handle; + R_TRY_CATCH(svc::CreateDeviceAddressSpace(std::addressof(handle), address, size)) { + R_CONVERT(svc::ResultOutOfMemory, dd::ResultOutOfMemory()) + R_CONVERT(svc::ResultOutOfResource, dd::ResultOutOfResource()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + *out = static_cast<DeviceAddressSpaceHandle>(handle); + R_SUCCEED(); + } + + void DeviceAddressSpaceImplByHorizon::Close(DeviceAddressSpaceHandle handle) { + const auto svc_handle = svc::Handle(handle); + if (svc_handle == svc::PseudoHandle::CurrentThread || svc_handle == svc::PseudoHandle::CurrentProcess) { + return; + } + + R_ABORT_UNLESS(svc::CloseHandle(svc_handle)); + } + + Result DeviceAddressSpaceImplByHorizon::MapAligned(DeviceAddressSpaceHandle handle, ProcessHandle process_handle, u64 process_address, size_t process_size, DeviceVirtualAddress device_address, dd::MemoryPermission device_perm) { + /* Check alignment. */ + AMS_ABORT_UNLESS((process_address & (4_MB - 1)) == (device_address & (4_MB - 1))); + + R_TRY_CATCH(svc::MapDeviceAddressSpaceAligned(svc::Handle(handle), svc::Handle(process_handle), process_address, process_size, device_address, svc::MapDeviceAddressSpaceOption::Encode(static_cast<svc::MemoryPermission>(device_perm), svc::MapDeviceAddressSpaceFlag_None))) { + R_CONVERT(svc::ResultInvalidHandle, dd::ResultInvalidHandle()) + R_CONVERT(svc::ResultOutOfMemory, dd::ResultOutOfMemory()) + R_CONVERT(svc::ResultOutOfResource, dd::ResultOutOfResource()) + R_CONVERT(svc::ResultInvalidCurrentMemory, dd::ResultInvalidMemoryState()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + R_SUCCEED(); + } + + Result DeviceAddressSpaceImplByHorizon::MapNotAligned(DeviceAddressSpaceHandle handle, ProcessHandle process_handle, u64 process_address, size_t process_size, DeviceVirtualAddress device_address, dd::MemoryPermission device_perm) { + R_TRY_CATCH(svc::MapDeviceAddressSpaceByForce(svc::Handle(handle), svc::Handle(process_handle), process_address, process_size, device_address, svc::MapDeviceAddressSpaceOption::Encode(static_cast<svc::MemoryPermission>(device_perm), svc::MapDeviceAddressSpaceFlag_None))) { + R_CONVERT(svc::ResultInvalidHandle, dd::ResultInvalidHandle()) + R_CONVERT(svc::ResultOutOfMemory, dd::ResultOutOfMemory()) + R_CONVERT(svc::ResultOutOfResource, dd::ResultOutOfResource()) + R_CONVERT(svc::ResultInvalidCurrentMemory, dd::ResultInvalidMemoryState()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + R_SUCCEED(); + } + + void DeviceAddressSpaceImplByHorizon::Unmap(DeviceAddressSpaceHandle handle, ProcessHandle process_handle, u64 process_address, size_t process_size, DeviceVirtualAddress device_address) { + R_ABORT_UNLESS(svc::UnmapDeviceAddressSpace(svc::Handle(handle), svc::Handle(process_handle), process_address, process_size, device_address)); + } + + Result DeviceAddressSpaceImplByHorizon::Attach(DeviceAddressSpaceType *das, DeviceName device_name) { + R_TRY_CATCH(svc::AttachDeviceAddressSpace(static_cast<svc::DeviceName>(device_name), svc::Handle(das->device_handle))) { + R_CONVERT(svc::ResultOutOfMemory, dd::ResultOutOfMemory()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + R_SUCCEED(); + } + + void DeviceAddressSpaceImplByHorizon::Detach(DeviceAddressSpaceType *das, DeviceName device_name) { + R_ABORT_UNLESS(svc::DetachDeviceAddressSpace(static_cast<svc::DeviceName>(device_name), svc::Handle(das->device_handle))); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dd/impl/dd_device_address_space_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dd/impl/dd_device_address_space_impl.os.horizon.hpp new file mode 100644 index 00000000..db31b84b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dd/impl/dd_device_address_space_impl.os.horizon.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::dd::impl { + + class DeviceAddressSpaceImplByHorizon { + public: + static Result Create(DeviceAddressSpaceHandle *out, u64 address, u64 size); + static void Close(DeviceAddressSpaceHandle handle); + + static Result MapAligned(DeviceAddressSpaceHandle handle, ProcessHandle process_handle, u64 process_address, size_t process_size, DeviceVirtualAddress device_address, dd::MemoryPermission device_perm); + static Result MapNotAligned(DeviceAddressSpaceHandle handle, ProcessHandle process_handle, u64 process_address, size_t process_size, DeviceVirtualAddress device_address, dd::MemoryPermission device_perm); + static void Unmap(DeviceAddressSpaceHandle handle, ProcessHandle process_handle, u64 process_address, size_t process_size, DeviceVirtualAddress device_address); + + static Result Attach(DeviceAddressSpaceType *das, DeviceName device_name); + static void Detach(DeviceAddressSpaceType *das, DeviceName device_name); + }; + + using DeviceAddressSpaceImpl = DeviceAddressSpaceImplByHorizon; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ddsf/ddsf_device_code_entry_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ddsf/ddsf_device_code_entry_manager.cpp new file mode 100644 index 00000000..f0aad97a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ddsf/ddsf_device_code_entry_manager.cpp @@ -0,0 +1,150 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ddsf { + + Result DeviceCodeEntryManager::Add(DeviceCode device_code, IDevice *device) { + /* Check pre-conditions. */ + AMS_ASSERT(device != nullptr); + AMS_ASSERT(device->IsDriverAttached()); + + /* Acquire exclusive access to the manager. */ + std::scoped_lock lk(m_entry_list_lock); + + /* Check that we don't already have an entry with the code. */ + for (const auto &holder : m_entry_list) { + AMS_ASSERT(holder.IsConstructed()); + AMS_ASSERT(holder.Get().GetDeviceCode() != device_code); + AMS_UNUSED(holder); + } + + /* Allocate memory for a new device code entry holder. */ + void *holder_storage = m_memory_resource->Allocate(sizeof(DeviceCodeEntryHolder)); + R_UNLESS(holder_storage != nullptr, ddsf::ResultOutOfResource()); + + /* Initialize the new holder. */ + auto *holder = std::construct_at(static_cast<DeviceCodeEntryHolder *>(holder_storage)); + holder->Construct(device_code, device); + + /* Link the new holder. */ + holder->AddTo(m_entry_list); + + R_SUCCEED(); + } + + bool DeviceCodeEntryManager::Remove(DeviceCode device_code) { + /* Acquire exclusive access to the manager. */ + std::scoped_lock lk(m_entry_list_lock); + + /* Find and erase the entry. */ + bool erased = false; + for (auto it = m_entry_list.begin(); it != m_entry_list.end(); /* ... */) { + /* Get the current entry, and advance the iterator. */ + DeviceCodeEntryHolder *cur = std::addressof(*(it++)); + + /* If the entry matches the device code, remove it. */ + AMS_ASSERT(cur->IsConstructed()); + if (cur->Get().GetDeviceCode() == device_code) { + /* Destroy and deallocate the holder. */ + cur->Destroy(); + std::destroy_at(cur); + m_memory_resource->Deallocate(cur, sizeof(*cur)); + + erased = true; + } + } + + return erased; + } + + Result DeviceCodeEntryManager::FindDeviceCodeEntry(DeviceCodeEntry **out, DeviceCode device_code) { + /* Check arguments. */ + AMS_ASSERT(out != nullptr); + R_UNLESS(out != nullptr, ddsf::ResultInvalidArgument()); + + /* Find the device. */ + bool found = false; + this->ForEachEntry([&](DeviceCodeEntry &entry) -> bool { + if (entry.GetDeviceCode() == device_code) { + found = true; + *out = std::addressof(entry); + return false; + } + return true; + }); + + /* Check that we found the device. */ + R_UNLESS(found, ddsf::ResultDeviceCodeNotFound()); + + R_SUCCEED(); + } + + Result DeviceCodeEntryManager::FindDeviceCodeEntry(const DeviceCodeEntry **out, DeviceCode device_code) const { + /* Check arguments. */ + AMS_ASSERT(out != nullptr); + R_UNLESS(out != nullptr, ddsf::ResultInvalidArgument()); + + /* Find the device. */ + bool found = false; + this->ForEachEntry([&](const DeviceCodeEntry &entry) -> bool { + if (entry.GetDeviceCode() == device_code) { + found = true; + *out = std::addressof(entry); + return false; + } + return true; + }); + + /* Check that we found the device. */ + R_UNLESS(found, ddsf::ResultDeviceCodeNotFound()); + + R_SUCCEED(); + } + + Result DeviceCodeEntryManager::FindDevice(IDevice **out, DeviceCode device_code) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + + /* Find the entry. */ + DeviceCodeEntry *entry; + R_TRY(this->FindDeviceCodeEntry(std::addressof(entry), device_code)); + + /* Set the output. */ + if (out != nullptr) { + *out = std::addressof(entry->GetDevice()); + } + + R_SUCCEED(); + } + + Result DeviceCodeEntryManager::FindDevice(const IDevice **out, DeviceCode device_code) const { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + + /* Find the entry. */ + const DeviceCodeEntry *entry; + R_TRY(this->FindDeviceCodeEntry(std::addressof(entry), device_code)); + + /* Set the output. */ + if (out != nullptr) { + *out = std::addressof(entry->GetDevice()); + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ddsf/ddsf_event_handler.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ddsf/ddsf_event_handler.cpp new file mode 100644 index 00000000..c6ab0143 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ddsf/ddsf_event_handler.cpp @@ -0,0 +1,214 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ddsf { + + namespace { + + enum class LoopControlCommand { + None = 0, + Register = 1, + Unregister = 2, + Terminate = 3, + }; + + } + + struct EventHandlerManager::LoopControlCommandParameters { + LoopControlCommand command; + IEventHandler *target; + + LoopControlCommandParameters() : command(LoopControlCommand::None), target(nullptr) { /* ... */ } + LoopControlCommandParameters(LoopControlCommand c, IEventHandler *t) : command(c), target(t) { /* ... */ } + }; + + void EventHandlerManager::Initialize() { + /* Check that we're not already initialized. */ + if (m_is_initialized) { + return; + } + + /* Initialize multi wait/holder. */ + os::InitializeMultiWait(std::addressof(m_multi_wait)); + os::InitializeMultiWaitHolder(std::addressof(m_loop_control_event_holder), m_loop_control_event.GetBase()); + os::LinkMultiWaitHolder(std::addressof(m_multi_wait), std::addressof(m_loop_control_event_holder)); + + m_is_initialized = true; + } + + void EventHandlerManager::Finalize() { + /* Check that we're initialized and not looping. */ + AMS_ASSERT(!m_is_looping); + AMS_ASSERT(m_is_initialized); + if (!m_is_initialized) { + return; + } + + /* Finalize multi wait/holder. */ + os::UnlinkMultiWaitHolder(std::addressof(m_loop_control_event_holder)); + os::FinalizeMultiWaitHolder(std::addressof(m_loop_control_event_holder)); + os::FinalizeMultiWait(std::addressof(m_multi_wait)); + + m_is_initialized = false; + } + + void EventHandlerManager::ProcessControlCommand(LoopControlCommandParameters *params) { + /* Check pre-conditions. */ + AMS_ASSERT(m_is_initialized); + AMS_ASSERT(params != nullptr); + + /* Acquire exclusive access. */ + std::scoped_lock lk(m_loop_control_lock); + + /* If we're processing for the loop thread, we can directly handle. */ + if (!m_is_looping || this->IsRunningOnLoopThread()) { + this->ProcessControlCommandImpl(params); + } else { + /* Otherwise, signal to the loop thread. */ + m_loop_control_command_params = params; + m_loop_control_event.Signal(); + m_loop_control_command_done_event.Wait(); + } + } + + void EventHandlerManager::ProcessControlCommandImpl(LoopControlCommandParameters *params) { + /* Check pre-conditions. */ + AMS_ASSERT(m_loop_control_lock.IsLockedByCurrentThread() || !m_loop_control_lock.TryLock()); + AMS_ASSERT(params != nullptr); + AMS_ASSERT(params->target != nullptr); + + /* Process the command. */ + switch (params->command) { + case LoopControlCommand::Register: + params->target->Link(std::addressof(m_multi_wait)); + break; + case LoopControlCommand::Unregister: + params->target->Unlink(); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void EventHandlerManager::RegisterHandler(IEventHandler *handler) { + /* Check that the handler is valid. */ + AMS_ASSERT(handler != nullptr); + AMS_ASSERT(handler->IsInitialized()); + + /* Send registration command. */ + LoopControlCommandParameters params(LoopControlCommand::Register, handler); + return this->ProcessControlCommand(std::addressof(params)); + } + + void EventHandlerManager::UnregisterHandler(IEventHandler *handler) { + /* Check that the handler is valid. */ + AMS_ASSERT(handler != nullptr); + AMS_ASSERT(handler->IsInitialized()); + + /* Send registration command. */ + LoopControlCommandParameters params(LoopControlCommand::Unregister, handler); + return this->ProcessControlCommand(std::addressof(params)); + } + + void EventHandlerManager::WaitLoopEnter() { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_loop_control_lock); + + /* Wait until we're looping. */ + while (!m_is_looping) { + m_is_looping_cv.Wait(m_loop_control_lock); + } + } + + void EventHandlerManager::WaitLoopExit() { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_loop_control_lock); + + /* Wait until we're not looping. */ + while (m_is_looping) { + m_is_looping_cv.Wait(m_loop_control_lock); + } + } + + void EventHandlerManager::RequestStop() { + /* Check that we're looping and not the loop thread. */ + AMS_ASSERT(m_is_looping); + AMS_ASSERT(!this->IsRunningOnLoopThread()); + + if (m_is_looping) { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_loop_control_lock); + + /* Signal to the loop thread. */ + LoopControlCommandParameters params(LoopControlCommand::Terminate, nullptr); + m_loop_control_command_params = std::addressof(params); + m_loop_control_event.Signal(); + m_loop_control_command_done_event.Wait(); + } + } + + void EventHandlerManager::LoopAuto() { + /* Check that we're not already looping. */ + AMS_ASSERT(!m_is_looping); + + /* Begin looping with the current thread. */ + m_loop_thread = os::GetCurrentThread(); + m_is_looping = true; + m_is_looping_cv.Broadcast(); + + /* Whenever we're done looping, clean up. */ + ON_SCOPE_EXIT { + m_loop_thread = nullptr; + m_is_looping = false; + m_is_looping_cv.Broadcast(); + }; + + /* Loop until we're asked to stop. */ + bool should_terminate = false; + while (!should_terminate) { + /* Wait for a holder to be signaled. */ + os::MultiWaitHolderType *event_holder = os::WaitAny(std::addressof(m_multi_wait)); + AMS_ASSERT(event_holder != nullptr); + + /* Check if we have a request to handle. */ + if (event_holder == std::addressof(m_loop_control_event_holder)) { + /* Check that the request hasn't already been handled. */ + if (m_loop_control_event.TryWait()) { + /* Handle the request. */ + AMS_ASSERT(m_loop_control_command_params != nullptr); + switch (m_loop_control_command_params->command) { + case LoopControlCommand::Register: + case LoopControlCommand::Unregister: + this->ProcessControlCommandImpl(m_loop_control_command_params); + break; + case LoopControlCommand::Terminate: + should_terminate = true; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Clear the request, and signal that it's done. */ + m_loop_control_command_params = nullptr; + m_loop_control_command_done_event.Signal(); + } + } else { + /* Handle the event. */ + IEventHandler::ToEventHandler(event_holder).HandleEvent(); + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ddsf/ddsf_memory_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ddsf/ddsf_memory_api.cpp new file mode 100644 index 00000000..11af7bfc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ddsf/ddsf_memory_api.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ddsf { + + namespace { + + constinit ams::MemoryResource *g_memory_resource = nullptr; + constinit ams::MemoryResource *g_device_code_entry_holder_memory_resource = nullptr; + + } + + void SetMemoryResource(ams::MemoryResource *mr) { + AMS_ASSERT(g_memory_resource == nullptr); + g_memory_resource = mr; + AMS_ASSERT(g_memory_resource != nullptr); + } + + ams::MemoryResource *GetMemoryResource() { + AMS_ASSERT(g_memory_resource != nullptr); + return g_memory_resource; + } + + void SetDeviceCodeEntryHolderMemoryResource(ams::MemoryResource *mr) { + AMS_ASSERT(g_device_code_entry_holder_memory_resource == nullptr); + g_device_code_entry_holder_memory_resource = mr; + AMS_ASSERT(g_device_code_entry_holder_memory_resource != nullptr); + } + + ams::MemoryResource *GetDeviceCodeEntryHolderMemoryResource() { + AMS_ASSERT(g_device_code_entry_holder_memory_resource != nullptr); + return g_device_code_entry_holder_memory_resource; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ddsf/ddsf_session_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ddsf/ddsf_session_api.cpp new file mode 100644 index 00000000..d801380e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ddsf/ddsf_session_api.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ddsf { + + Result OpenSession(IDevice *device, ISession *session, AccessMode access_mode) { + /* Check pre-conditions. */ + AMS_ASSERT(device != nullptr); + AMS_ASSERT(session != nullptr); + AMS_ASSERT(!session->IsOpen()); + + /* Attack the session to the device. */ + session->AttachDevice(device, access_mode); + auto session_guard = SCOPE_GUARD { session->DetachDevice(); }; + + /* Attach the device to the session. */ + R_TRY(device->AttachSession(session)); + + /* We succeeded. */ + session_guard.Cancel(); + R_SUCCEED(); + } + + void CloseSession(ISession *session) { + /* Check pre-conditions. */ + AMS_ASSERT(session != nullptr); + + /* Detach the device from the session. */ + session->GetDevice().DetachSession(session); + + /* Detach the session from the device. */ + session->DetachDevice(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_abort_observer.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_abort_observer.cpp new file mode 100644 index 00000000..183822b0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_abort_observer.cpp @@ -0,0 +1,66 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/diag_abort_observer_manager.hpp" + +namespace ams::diag { + + namespace impl { + + constinit bool g_enable_default_abort_observer = true; + + } + + namespace { + + template<typename Holder, typename Observer> + void InitializeAbortObserverHolderImpl(Holder *holder, Observer observer) { + holder->observer = observer; + holder->next = nullptr; + holder->is_registered = false; + } + + } + + void InitializeAbortObserverHolder(AbortObserverHolder *holder, AbortObserver observer) { + InitializeAbortObserverHolderImpl(holder, observer); + } + + void RegisterAbortObserver(AbortObserverHolder *holder) { + impl::GetAbortObserverManager()->RegisterObserver(holder); + } + + void UnregisterAbortObserver(AbortObserverHolder *holder) { + impl::GetAbortObserverManager()->UnregisterObserver(holder); + } + + void EnableDefaultAbortObserver(bool en) { + ::ams::diag::impl::g_enable_default_abort_observer = en; + } + + void InitializeSdkAbortObserverHolder(SdkAbortObserverHolder *holder, SdkAbortObserver observer) { + InitializeAbortObserverHolderImpl(holder, observer); + } + + void RegisterSdkAbortObserver(SdkAbortObserverHolder *holder) { + impl::GetSdkAbortObserverManager()->RegisterObserver(holder); + } + + void UnregisterSdkAbortObserver(SdkAbortObserverHolder *holder) { + impl::GetSdkAbortObserverManager()->UnregisterObserver(holder); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_assertion_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_assertion_impl.cpp new file mode 100644 index 00000000..bd1cb43b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_assertion_impl.cpp @@ -0,0 +1,237 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/diag_get_all_backtrace.hpp" +#include "impl/diag_invoke_abort.hpp" + +namespace ams::diag { + + namespace { + + inline NORETURN void AbortWithValue(u64 debug) { + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + /* Just perform a data abort. */ + register u64 addr __asm__("x27") = FatalErrorContext::StdAbortMagicAddress; + register u64 val __asm__("x28") = FatalErrorContext::StdAbortMagicValue; + while (true) { + __asm__ __volatile__ ( + "mov x0, %[debug]\n" + "str %[val], [%[addr]]\n" + : + : [debug]"r"(debug), [val]"r"(val), [addr]"r"(addr) + : "x0" + ); + } + #else + AMS_UNUSED(debug); + std::abort(); + #endif + __builtin_unreachable(); + } + + constinit os::SdkMutex g_assert_mutex; + constinit os::SdkMutex g_abort_mutex; + + void PrepareAbort() { + #if defined(ATMOSPHERE_OS_HORIZON) + { + /* Get the thread local region. */ + auto * const tlr = svc::GetThreadLocalRegion(); + + /* Clear disable count. */ + tlr->disable_count = 0; + + /* If we need to, unpin. */ + if (tlr->interrupt_flag) { + svc::SynchronizePreemptionState(); + } + } + #endif + } + + AbortReason ToAbortReason(AssertionType type) { + switch (type) { + case AssertionType_Audit: return AbortReason_Audit; + case AssertionType_Assert: return AbortReason_Assert; + default: + return AbortReason_Abort; + } + } + + AssertionFailureOperation DefaultAssertionFailureHandler(const AssertionInfo &) { + return AssertionFailureOperation_Abort; + } + + constinit AssertionFailureHandler g_assertion_failure_handler = &DefaultAssertionFailureHandler; + + void ExecuteAssertionFailureOperation(AssertionFailureOperation operation, const AssertionInfo &info) { + switch (operation) { + case AssertionFailureOperation_Continue: + break; + case AssertionFailureOperation_Abort: + { + const AbortInfo abort_info = { + ToAbortReason(info.type), + info.message, + info.expr, + info.func, + info.file, + info.line, + }; + + ::ams::diag::impl::InvokeAbortObserver(abort_info); + AbortWithValue(0); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void InvokeAssertionFailureHandler(const AssertionInfo &info) { + const auto operation = g_assertion_failure_handler(info); + ExecuteAssertionFailureOperation(operation, info); + } + + + } + + NOINLINE void OnAssertionFailure(AssertionType type, const char *expr, const char *func, const char *file, int line, const char *format, ...) { + /* Prepare to abort. */ + PrepareAbort(); + + /* Acquire exclusive assert rights. */ + if (g_assert_mutex.IsLockedByCurrentThread()) { + AbortWithValue(0); + } + + std::scoped_lock lk(g_assert_mutex); + + /* Create the assertion info. */ + std::va_list vl; + va_start(vl, format); + + const ::ams::diag::LogMessage message = { format, std::addressof(vl) }; + + const AssertionInfo info = { + type, + std::addressof(message), + expr, + func, + file, + line, + }; + + InvokeAssertionFailureHandler(info); + va_end(vl); + } + + void OnAssertionFailure(AssertionType type, const char *expr, const char *func, const char *file, int line) { + return OnAssertionFailure(type, expr, func, file, line, ""); + } + + NORETURN void AbortImpl(const char *expr, const char *func, const char *file, int line) { + const Result res = ResultSuccess(); + + std::va_list vl{}; + VAbortImpl(expr, func, file, line, std::addressof(res), nullptr, "", vl); + } + + NORETURN void AbortImpl(const char *expr, const char *func, const char *file, int line, const char *fmt, ...) { + const Result res = ResultSuccess(); + + std::va_list vl; + va_start(vl, fmt); + VAbortImpl(expr, func, file, line, std::addressof(res), nullptr, fmt, vl); + } + + NORETURN void AbortImpl(const char *expr, const char *func, const char *file, int line, const ::ams::Result *result, const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + VAbortImpl(expr, func, file, line, result, nullptr, fmt, vl); + } + + NORETURN void AbortImpl(const char *expr, const char *func, const char *file, int line, const ::ams::Result *result, const ::ams::os::UserExceptionInfo *exc_info, const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + VAbortImpl(expr, func, file, line, result, exc_info, fmt, vl); + } + + NORETURN NOINLINE void VAbortImpl(const char *expr, const char *func, const char *file, int line, const ::ams::Result *result, const ::ams::os::UserExceptionInfo *exc_info, const char *fmt, std::va_list vl) { + /* Prepare to abort. */ + PrepareAbort(); + + /* Acquire exclusive abort rights. */ + if (g_abort_mutex.IsLockedByCurrentThread()) { + AbortWithValue(result->GetValue()); + } + + std::scoped_lock lk(g_abort_mutex); + + /* Set the abort impl return address. */ + impl::SetAbortImplReturnAddress(reinterpret_cast<uintptr_t>(__builtin_return_address(0))); + + /* Create abort info. */ + std::va_list cvl; + va_copy(cvl, vl); + const diag::LogMessage message = { fmt, std::addressof(cvl) }; + + const AbortInfo abort_info = { + AbortReason_Abort, + std::addressof(message), + expr, + func, + file, + line, + }; + const SdkAbortInfo sdk_abort_info = { + abort_info, + *result, + exc_info + }; + + /* Invoke observers. */ + ::ams::diag::impl::InvokeAbortObserver(abort_info); + ::ams::diag::impl::InvokeSdkAbortObserver(sdk_abort_info); + + /* Abort. */ + AbortWithValue(result->GetValue()); + } + +} + +namespace ams::impl { + + NORETURN NOINLINE void UnexpectedDefaultImpl(const char *func, const char *file, int line) { + /* Create abort info. */ + std::va_list vl{}; + const ::ams::diag::LogMessage message = { "" , std::addressof(vl) }; + const ::ams::diag::AbortInfo abort_info = { + ::ams::diag::AbortReason_UnexpectedDefault, + std::addressof(message), + "", + func, + file, + line, + }; + + /* Invoke observers. */ + ::ams::diag::impl::InvokeAbortObserver(abort_info); + + /* Abort. */ + ::ams::diag::AbortWithValue(0); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_assertion_impl_for_nx_asm.board.nintendo_nx.s b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_assertion_impl_for_nx_asm.board.nintendo_nx.s new file mode 100644 index 00000000..c22914cb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_assertion_impl_for_nx_asm.board.nintendo_nx.s @@ -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 <http://www.gnu.org/licenses/>. + */ + +/* ams::diag::impl::FatalErrorByResultForNx(Result value) */ +.section .text._ZN3ams4diag4impl23FatalErrorByResultForNxENS_6ResultE, "ax", %progbits +.global _ZN3ams4diag4impl23FatalErrorByResultForNxENS_6ResultE +.type _ZN3ams4diag4impl23FatalErrorByResultForNxENS_6ResultE, %function +.balign 0x10 +_ZN3ams4diag4impl23FatalErrorByResultForNxENS_6ResultE: + /* Save x27/x28. */ + stp x27, x28, [sp, #-0x10]! + stp x0, xzr, [sp, #-0x10]! + + /* Inline ams::diag::impl::PrepareAbort() */ + mrs x27, tpidrro_el0 + strh wzr, [x27, #0x100] + ldrh w27, [x27, #0x102] + cbz w27, 0f + svc #0x36 + +0: /* Restore the value from stack. */ + ldr x0, [sp] + add sp, sp, #0x10 + + /* Put magic std::abort values into x27/x28. */ + mov x28, #0xcafe + movk x28, #0xdead, lsl#16 + movk x28, #0xf00d, lsl#32 + movk x28, #0xa55a, lsl#48 + mov x27, #8 + + /* Abort */ +1: + str x28, [x27] + nop + b 1b diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_backtrace.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_backtrace.cpp new file mode 100644 index 00000000..d24dcc2e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_backtrace.cpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/diag_get_all_backtrace.hpp" + +namespace ams::diag { + + size_t GetBacktrace(uintptr_t *out, size_t out_size) { + /* Validate pre-conditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(out_size > 0); + + /* Create the backtrace object. */ + ::ams::diag::Backtrace bt{}; + bt.Step(); + + /* Get the backtrace. */ + return ::ams::diag::impl::GetAllBacktrace(out, out_size, bt); + } + + #if defined(ATMOSPHERE_OS_HORIZON) + size_t GetBacktrace(uintptr_t *out, size_t out_size, uintptr_t fp, uintptr_t sp, uintptr_t pc) { + /* Validate pre-conditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(out_size > 0); + + /* Create the backtrace object. */ + ::ams::diag::Backtrace bt{fp, sp, pc}; + bt.Step(); + + /* Get the backtrace. */ + return ::ams::diag::impl::GetAllBacktrace(out, out_size, bt); + } + #endif + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_log.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_log.cpp new file mode 100644 index 00000000..fc7980e8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_log.cpp @@ -0,0 +1,77 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "diag_log_impl.hpp" + +namespace ams::diag::impl { + + void CallAllLogObserver(const LogMetaData &meta, const LogBody &body); + + namespace { + + struct CallPrintDebugString { + void operator()(const LogMetaData &meta, const char *msg, size_t size, bool head, bool tail) { + const LogBody body = { + .message = msg, + .message_size = size, + .is_head = head, + .is_tail = tail + }; + + CallAllLogObserver(meta, body); + } + }; + + } + + void LogImpl(const LogMetaData &meta, const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + VLogImpl(meta, fmt, vl); + va_end(vl); + } + + void VLogImpl(const LogMetaData &meta, const char *fmt, std::va_list vl) { + #if defined(ATMOSPHERE_OS_HORIZON) + /* Print to stack buffer. */ + char msg_buffer[DebugPrintBufferLength]; + + /* TODO: VFormatString using utf-8 printer. */ + const size_t len = util::VSNPrintf(msg_buffer, sizeof(msg_buffer), fmt, vl); + #else + /* Print to allocated buffer. */ + std::va_list cvl; + va_copy(cvl, vl); + const auto out_len = util::TVSNPrintf(nullptr, 0, fmt, cvl) + 1; + va_end(cvl); + + char *msg_buffer = static_cast<char *>(std::malloc(out_len)); + AMS_ABORT_UNLESS(msg_buffer != nullptr); + ON_SCOPE_EXIT { std::free(msg_buffer); }; + + /* TODO: VFormatString using utf-8 printer. */ + const size_t len = util::TVSNPrintf(msg_buffer, out_len, fmt, vl); + #endif + + /* Call log observer. */ + CallPrintDebugString()(meta, msg_buffer, len, true, true); + } + + void PutImpl(const LogMetaData &meta, const char *msg, size_t msg_size) { + CallPrintDebugString()(meta, msg, msg_size, true, true); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_log_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_log_impl.hpp new file mode 100644 index 00000000..4a504375 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_log_impl.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::diag { + + namespace impl { + + constexpr inline size_t DebugPrintBufferLength = 0x100; + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_log_observer.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_log_observer.cpp new file mode 100644 index 00000000..88f737b3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_log_observer.cpp @@ -0,0 +1,166 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "diag_log_impl.hpp" +#include "impl/diag_observer_manager.hpp" +#include "impl/diag_print_debug_string.hpp" + +namespace ams::diag { + + namespace impl { + + namespace { + + constexpr inline size_t DecorationStringLengthMax = 0x61; + + constexpr inline const char *EscapeSequencesForSeverity[] = { + "\x1B[90m", /* Dark Gray (Trace) */ + nullptr, /* None (Info) */ + "\x1B[33m", /* Yellow (Warn) */ + "\x1B[31m", /* Red (Error) */ + "\x1B[41m\x1B[37m", /* White-on-red (Fatal) */ + }; + + constexpr inline const char EscapeSequenceReset[] = "\x1B[0m"; + + constexpr inline size_t PrintBufferLength = DecorationStringLengthMax + impl::DebugPrintBufferLength + 1; + + constinit os::SdkMutex g_print_buffer_mutex; + constinit char g_print_buffer[PrintBufferLength]; + + inline void GetCurrentTime(int *h, int *m, int *s, int *ms) { + /* Get the current time. */ + const auto cur_time = os::GetSystemTick().ToTimeSpan(); + + /* Extract fields. */ + const s64 hours = cur_time.GetHours(); + const s64 minutes = cur_time.GetMinutes(); + const s64 seconds = cur_time.GetSeconds(); + const s64 milliseconds = cur_time.GetMilliSeconds(); + + /* Set out fields. */ + *h = static_cast<int>(hours); + *m = static_cast<int>(minutes - hours * 60); + *s = static_cast<int>(seconds - minutes * 60); + *ms = static_cast<int>(milliseconds - seconds * 1000); + } + + void TentativeDefaultLogObserver(const LogMetaData &meta, const LogBody &body, void *) { + /* Acquire access to the print buffer */ + std::scoped_lock lk(g_print_buffer_mutex); + + /* Get the escape sequence. */ + const char *escape = nullptr; + if (LogSeverity_Trace <= meta.severity && meta.severity <= LogSeverity_Fatal) { + escape = EscapeSequencesForSeverity[meta.severity]; + } + + /* Declare message variables. */ + const char *msg = nullptr; + size_t msg_size = 0; + + /* Handle structured logs. */ + const bool structured = meta.module_name != nullptr && std::strlen(meta.module_name) >= 2; + if (escape || structured) { + /* Insert timestamp, if head. */ + if (structured && body.is_head) { + /* Get current timestamp. */ + int hours, minutes, seconds, milliseconds; + GetCurrentTime(std::addressof(hours), std::addressof(minutes), std::addressof(seconds), std::addressof(milliseconds)); + + /* Print the timestamp/header. */ + msg_size += util::SNPrintf(g_print_buffer + msg_size, PrintBufferLength - msg_size, "%s%d:%02d:%02d.%03d [%-5.63s] ", escape ? escape : "", hours, minutes, seconds, milliseconds, meta.module_name[0] == '$' ? meta.module_name + 1 : meta.module_name + 0); + + AMS_AUDIT(msg_size <= DecorationStringLengthMax); + } else if (escape) { + msg_size += util::SNPrintf(g_print_buffer + msg_size, PrintBufferLength - msg_size, "%s", escape); + } + + /* Determine maximum remaining size. */ + const size_t max_msg_size = PrintBufferLength - msg_size - (escape ? sizeof(EscapeSequenceReset) - 1 : 0); + + /* Determine printable size. */ + size_t printable_size = std::min<size_t>(body.message_size, max_msg_size); + + /* Determine newline status. */ + bool new_line = false; + if (body.message_size > 0 && body.message[body.message_size - 1] == '\n') { + --printable_size; + new_line = true; + } + + /* Print the messsage. */ + msg_size += util::SNPrintf(g_print_buffer + msg_size, PrintBufferLength - msg_size, "%.*s%s%s", static_cast<int>(printable_size), body.message, escape ? EscapeSequenceReset : "", new_line ? "\n" : ""); + + /* Set the message. */ + msg = g_print_buffer; + } else { + /* Use the body's message directly. */ + msg = body.message; + msg_size = body.message_size; + } + + /* Print the string. */ + impl::PrintDebugString(msg, msg_size); + } + + struct LogObserverContext { + const LogMetaData &meta; + const LogBody &body; + }; + + using LogObserverManager = ObserverManagerWithDefaultHolder<LogObserverHolder, LogObserverContext>; + + constinit LogObserverManager g_log_observer_manager(::ams::diag::InitializeLogObserverHolder, TentativeDefaultLogObserver, nullptr); + + } + + void CallAllLogObserver(const LogMetaData &meta, const LogBody &body) { + /* Create context. */ + const LogObserverContext context = { .meta = meta, .body = body }; + + /* Invoke the log observer. */ + g_log_observer_manager.InvokeAllObserver(context, [] (const LogObserverHolder &holder, const LogObserverContext &context) { + holder.log_observer(context.meta, context.body, holder.arg); + }); + } + + void ReplaceDefaultLogObserver(LogObserver observer) { + /* Get the default observer. */ + auto *default_holder = std::addressof(g_log_observer_manager.GetDefaultObserverHolder()); + + /* Unregister, replace, and re-register. */ + UnregisterLogObserver(default_holder); + InitializeLogObserverHolder(default_holder, observer, nullptr); + RegisterLogObserver(default_holder); + } + + void ResetDefaultLogObserver() { + /* Restore the default observer. */ + ReplaceDefaultLogObserver(TentativeDefaultLogObserver); + } + + } + + void RegisterLogObserver(LogObserverHolder *holder) { + impl::g_log_observer_manager.RegisterObserver(holder); + } + + void UnregisterLogObserver(LogObserverHolder *holder) { + impl::g_log_observer_manager.UnregisterObserver(holder); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_symbol.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_symbol.cpp new file mode 100644 index 00000000..a9a5b496 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/diag_symbol.cpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/diag_symbol_impl.hpp" + +namespace ams::diag { + + uintptr_t GetSymbolName(char *dst, size_t dst_size, uintptr_t address) { + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(dst_size > 0); + AMS_ASSERT(address > 0); + + return ::ams::diag::impl::GetSymbolNameImpl(dst, dst_size, address); + } + + size_t GetSymbolSize(uintptr_t address) { + AMS_ASSERT(address > 0); + + return ::ams::diag::impl::GetSymbolSizeImpl(address); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_abort_observer_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_abort_observer_manager.cpp new file mode 100644 index 00000000..ed8c6698 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_abort_observer_manager.cpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "diag_abort_observer_manager.hpp" + +namespace ams::diag::impl { + + AbortObserverManager *GetAbortObserverManager() { + AMS_FUNCTION_LOCAL_STATIC(AbortObserverManager, s_manager); + + return std::addressof(s_manager); + } + + SdkAbortObserverManager *GetSdkAbortObserverManager() { + AMS_FUNCTION_LOCAL_STATIC(SdkAbortObserverManager, s_manager); + + return std::addressof(s_manager); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_abort_observer_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_abort_observer_manager.hpp new file mode 100644 index 00000000..e2bc6f1d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_abort_observer_manager.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "diag_observer_manager.hpp" + +namespace ams::diag::impl { + + using AbortObserverManager = ObserverManager<AbortObserverHolder, const AbortInfo &>; + using SdkAbortObserverManager = ObserverManager<SdkAbortObserverHolder, const SdkAbortInfo &>; + + AbortObserverManager *GetAbortObserverManager(); + SdkAbortObserverManager *GetSdkAbortObserverManager(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_backtrace_impl.os.generic.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_backtrace_impl.os.generic.cpp new file mode 100644 index 00000000..db9a076a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_backtrace_impl.os.generic.cpp @@ -0,0 +1,135 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_OS_WINDOWS) +#include <stratosphere/windows.hpp> +#elif defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS) +#include <fcntl.h> +#include <unistd.h> +#endif + +namespace ams::diag::impl { + + namespace { + + #if defined(ATMOSPHERE_ARCH_X64) + struct StackFrame { + u64 fp; /* rbp */ + u64 lr; /* rip */ + }; + #elif defined(ATMOSPHERE_ARCH_X86) + struct StackFrame { + u32 fp; /* ebp */ + u32 lr; /* eip */ + } + #elif defined(ATMOSPHERE_ARCH_ARM64) + struct StackFrame { + u64 fp; + u64 lr; + }; + #elif defined(ATMOSPHERE_ARCH_ARM) + struct StackFrame { + u32 fp; + u32 lr; + } + #else + #error "Unknown architecture for generic backtrace." + #endif + + bool TryRead(os::NativeHandle native_handle, void *dst, size_t size, const void *address) { + #if defined(ATMOSPHERE_OS_WINDOWS) + return ::ReadProcessMemory(native_handle, address, dst, size, nullptr); + #elif defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS) + s32 ret; + do { + ret = ::write(native_handle, address, size); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) { + return false; + } + + std::memcpy(dst, address, size); + return true; + #else + #error "Unknown OS for Backtrace native handle" + #endif + } + + } + + NOINLINE void Backtrace::Initialize() { + /* Clear our size. */ + m_index = 0; + m_size = 0; + + /* Get the base frame pointer. */ + const void *cur_fp = __builtin_frame_address(0); + + /* Try to read stack frames, until we run out. */ + #if defined(ATMOSPHERE_OS_WINDOWS) + const os::NativeHandle native_handle = ::GetCurrentProcess(); + #elif defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS) + os::NativeHandle pipe_handles[2]; + s32 nret; + do { nret = ::pipe(pipe_handles); } while (nret < 0 && errno == EINTR); + if (nret < 0) { return; } + do { nret = ::fcntl(pipe_handles[0], F_SETFL, O_NONBLOCK); } while (nret < 0 && errno == EINTR); + if (nret < 0) { return; } + do { nret = ::fcntl(pipe_handles[1], F_SETFL, O_NONBLOCK); } while (nret < 0 && errno == EINTR); + if (nret < 0) { return; } + ON_SCOPE_EXIT { + do { nret = ::close(pipe_handles[0]); } while (nret < 0 && errno == EINTR); + do { nret = ::close(pipe_handles[1]); } while (nret < 0 && errno == EINTR); + }; + const os::NativeHandle native_handle = pipe_handles[1]; + if (native_handle < 0) { return; } + #else + #error "Unknown OS for Backtrace native handle" + #endif + + StackFrame frame; + while (m_size < BacktraceEntryCountMax) { + /* Clear the frame. */ + frame = {}; + + /* Read the next frame. */ + if (!TryRead(native_handle, std::addressof(frame), sizeof(frame), cur_fp)) { + break; + } + + /* Add the return address. */ + m_backtrace_addresses[m_size++] = reinterpret_cast<void *>(frame.lr); + + /* Set the next fp. */ + cur_fp = reinterpret_cast<const void *>(frame.fp); + } + } + + bool Backtrace::Step() { + return (++m_index) < m_size; + } + + uintptr_t Backtrace::GetStackPointer() const { + return 0; + } + + uintptr_t Backtrace::GetReturnAddress() const { + return reinterpret_cast<uintptr_t>(m_backtrace_addresses[m_index]); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_backtrace_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_backtrace_impl.os.horizon.cpp new file mode 100644 index 00000000..b1f05c93 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_backtrace_impl.os.horizon.cpp @@ -0,0 +1,217 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::diag::impl { + + namespace { + + uintptr_t GetAddressValue(uintptr_t address) { + if (address == 0) { + return 0; + } + + if (!util::IsAligned(address, alignof(uintptr_t))) { + return 0; + } + + return *reinterpret_cast<uintptr_t *>(address); + } + + template<typename T, size_t N> + svc::MemoryInfo *GetMemoryInfoPointer(T (&arr)[N]) { + /* Check that there's enough space. */ + static_assert(sizeof(T) * N <= sizeof(svc::MemoryInfo)); + static_assert(alignof(T) >= alignof(svc::MemoryInfo)); + return reinterpret_cast<svc::MemoryInfo *>(std::addressof(arr[0])); + } + + bool IsValidLinkRegisterValue(uintptr_t lr, svc::MemoryInfo *info) { + /* Ensure the memory info is valid. */ + Result query_res; + + if (!(info->base_address <= lr && lr < info->base_address + info->size) && ({ svc::PageInfo page_info; query_res = svc::QueryMemory(info, std::addressof(page_info), lr); R_FAILED(query_res); })) { + AMS_SDK_LOG("Failed to get backtrace. Query memory failed. (lr: %p, result: %03d-%04d)\n", reinterpret_cast<void *>(lr), query_res.GetModule(), query_res.GetDescription()); + return false; + } + + /* Check that lr is valid. */ + if (lr == 0) { + return false; + } + + if (!util::IsAligned(lr, sizeof(u32))) { + AMS_SDK_LOG("Failed to get backtrace. The link register alignment is invalid. (lr: %p)\n", reinterpret_cast<void *>(lr)); + return false; + } + + /* Check that the lr points to code. */ + if (info->permission != svc::MemoryPermission_ReadExecute) { + AMS_SDK_LOG("Failed to get backtrace. The link register points out of the code. (lr: %p)\n", reinterpret_cast<void *>(lr)); + return false; + } + + return true; + } + + void GetNormalStackInfo(Backtrace::StackInfo *out) { + if (void * const fiber = nullptr /* TODO: os::GetCurrentFiber() */; fiber == nullptr) { + /* Get thread. */ + auto * const thread = os::GetCurrentThread(); + out->stack_top = reinterpret_cast<uintptr_t>(thread->stack); + out->stack_bottom = reinterpret_cast<uintptr_t>(thread->stack) + thread->stack_size; + } else { + /* TODO: Fiber. */ + } + } + + bool GetExceptionStackInfo(Backtrace::StackInfo *out, uintptr_t sp) { + /* Get the current stack info. */ + uintptr_t cur_stack = 0; + size_t cur_stack_size = 0; + os::GetCurrentStackInfo(std::addressof(cur_stack), std::addressof(cur_stack_size)); + + /* Get the thread's stack info. */ + uintptr_t thread_stack = 0; + size_t thread_stack_size = 0; + os::GetThreadStackInfo(std::addressof(thread_stack), std::addressof(thread_stack_size), os::GetCurrentThread()); + + /* If the current stack is the thread stack, exception stack isn't being used. */ + if (cur_stack == thread_stack) { + AMS_ASSERT(cur_stack_size == thread_stack_size); + return false; + } + + /* Check if the stack pointer is contained in the current stack. */ + if (!(cur_stack <= sp && sp < cur_stack + cur_stack_size)) { + return false; + } + + /* Set the output. */ + out->stack_top = cur_stack; + out->stack_bottom = cur_stack + cur_stack_size; + return true; + } + + } + + + NOINLINE void Backtrace::Initialize() { + /* Get the stack pointer/frame pointer. */ + uintptr_t fp, sp; + + __asm__ __volatile__( + #if defined(ATMOSPHERE_ARCH_ARM64) + "mov %[fp], fp\n" + "mov %[sp], sp\n" + #elif defined(ATMOSPHERE_ARCH_ARM) + "mov %[fp], x29\n" + "mov %[sp], sp\n" + #else + #error "Unknown architecture for Horizon fp/sp retrieval." + #endif + : [fp]"=&r"(fp), [sp]"=&r"(sp) + : + : "memory" + ); + + /* Set our stack info. */ + this->SetStackInfo(fp, sp); + + /* Step, to get our first lr. */ + this->Step(); + } + + void Backtrace::Initialize(uintptr_t fp, uintptr_t sp, uintptr_t pc) { + /* Set our initial lr. */ + m_lr = pc; + + /* Set our stack info. */ + this->SetStackInfo(fp, sp); + } + + bool Backtrace::Step() { + /* We can't step without a frame pointer. */ + if (m_fp == 0) { + if (m_current_stack_info != std::addressof(m_normal_stack_info)) { + AMS_SDK_LOG("Failed to get backtrace. The frame pointer is null.\n"); + } + return false; + } + + /* The frame pointer needs to be aligned. */ + if (!util::IsAligned(m_fp, sizeof(uintptr_t))) { + AMS_SDK_LOG("Failed to get backtrace. The frame pointer alignment is invalid. (fp: %p)\n", reinterpret_cast<void *>(m_fp)); + return false; + } + + /* Ensure our current stack info is good. */ + if (!(m_current_stack_info->stack_top <= m_fp && m_fp < m_current_stack_info->stack_bottom)) { + if (m_current_stack_info != std::addressof(m_exception_stack_info) || !(m_normal_stack_info.stack_top <= m_fp && m_fp < m_normal_stack_info.stack_bottom)) { + AMS_SDK_LOG("Failed to get backtrace. The frame pointer points out of the stack. (fp: %p, stack: %p-%p)\n", reinterpret_cast<void *>(m_fp), reinterpret_cast<void *>(m_current_stack_info->stack_top), reinterpret_cast<void *>(m_current_stack_info->stack_bottom)); + return false; + } + + m_current_stack_info = std::addressof(m_normal_stack_info); + } else if (m_fp <= m_prev_fp) { + AMS_SDK_LOG("Failed to get backtrace. The frame pointer is rewinding. (fp: %p, prev fp: %p, stack: %p-%p)\n", reinterpret_cast<void *>(m_fp), reinterpret_cast<void *>(m_prev_fp), reinterpret_cast<void *>(m_current_stack_info->stack_top), reinterpret_cast<void *>(m_current_stack_info->stack_bottom)); + return false; + } + + /* Update our previous fp. */ + m_prev_fp = m_fp; + + /* Read lr/fp. */ + m_lr = GetAddressValue(m_fp + sizeof(m_fp)); + m_fp = GetAddressValue(m_fp); + + /* Check that lr is valid. */ + if (IsValidLinkRegisterValue(m_lr, GetMemoryInfoPointer(m_memory_info_buffer))) { + return true; + } else { + m_lr = 0; + return false; + } + } + + uintptr_t Backtrace::GetStackPointer() const { + if (m_fp != 0) { + return m_fp - sizeof(m_fp); + } else { + return m_current_stack_info->stack_bottom - sizeof(m_fp); + } + } + + uintptr_t Backtrace::GetReturnAddress() const { + return m_lr; + } + + void Backtrace::SetStackInfo(uintptr_t fp, uintptr_t sp) { + /* Get the normal stack info. */ + GetNormalStackInfo(std::addressof(m_normal_stack_info)); + + /* Get the exception stack info. */ + if (GetExceptionStackInfo(std::addressof(m_exception_stack_info), sp)) { + m_current_stack_info = std::addressof(m_exception_stack_info); + } else { + m_current_stack_info = std::addressof(m_normal_stack_info); + } + + /* Set our frame pointer. */ + m_fp = fp; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_default_abort_observer.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_default_abort_observer.cpp new file mode 100644 index 00000000..3e381745 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_default_abort_observer.cpp @@ -0,0 +1,88 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "diag_dump_stack_trace.hpp" + +#if defined(ATMOSPHERE_OS_WINDOWS) +#include <stratosphere/windows.hpp> +#endif + +namespace ams::diag::impl { + + #if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_DEBUGGING) + namespace { + + constexpr const char *ToString(AbortReason reason) { + switch (reason) { + case AbortReason_Audit: + return "Auditing Assertion Failure"; + case AbortReason_Assert: + return "Assertion Failure"; + case AbortReason_UnexpectedDefault: + return "Unexpected Default"; + case AbortReason_Abort: + default: + return "Abort"; + } + } + + void DefaultPrinter(const AbortInfo &info) { + /* Get the thread name. */ + const char *thread_name; + if (auto *cur_thread = os::GetCurrentThread(); cur_thread != nullptr) { + thread_name = os::GetThreadNamePointer(cur_thread); + } else { + thread_name = "unknown"; + } + + #if defined(ATMOSPHERE_OS_HORIZON) + { + u64 process_id = 0; + u64 thread_id = 0; + svc::GetProcessId(std::addressof(process_id), svc::PseudoHandle::CurrentProcess); + svc::GetThreadId(std::addressof(thread_id), svc::PseudoHandle::CurrentThread); + AMS_SDK_LOG("%s: '%s' in %s, process=0x%02" PRIX64 ", thread=%" PRIu64 " (%s)\n%s:%d\n", ToString(info.reason), info.expr, info.func, process_id, thread_id, thread_name, info.file, info.line); + } + #elif defined(ATMOSPHERE_OS_WINDOWS) + { + DWORD process_id = ::GetCurrentProcessId(); + DWORD thread_id = ::GetCurrentThreadId(); + AMS_SDK_LOG("%s: '%s' in %s, process=0x%" PRIX64 ", thread=%" PRIu64 " (%s)\n%s:%d\n", ToString(info.reason), info.expr, info.func, static_cast<u64>(process_id), static_cast<u64>(thread_id), thread_name, info.file, info.line); + } + #else + { + AMS_SDK_LOG("%s: '%s' in %s, thread=%s\n%s:%d\n", ToString(info.reason), info.expr, info.func, thread_name, info.file, info.line); + } + #endif + + AMS_SDK_VLOG(info.message->fmt, *(info.message->vl)); + AMS_SDK_LOG("\n"); + + TentativeDumpStackTrace(); + } + + } + #endif + + void DefaultAbortObserver(const AbortInfo &info) { + #if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING) + DefaultPrinter(info); + #else + AMS_UNUSED(info); + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_dump_stack_trace.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_dump_stack_trace.hpp new file mode 100644 index 00000000..295b2c89 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_dump_stack_trace.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::diag::impl { + + void TentativeDumpStackTrace(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_dump_stack_trace.os.generic.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_dump_stack_trace.os.generic.cpp new file mode 100644 index 00000000..1b054329 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_dump_stack_trace.os.generic.cpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "diag_dump_stack_trace.hpp" + +namespace ams::diag::impl { + + void TentativeDumpStackTrace() { + AMS_SDK_LOG("----------------Stack Trace----------------\n"); + { + /* Get the backtrace. */ + constexpr size_t MaxBackTraceSize = 0x40; + uintptr_t backtrace[MaxBackTraceSize]; + const size_t num_items = ::ams::diag::GetBacktrace(backtrace, MaxBackTraceSize); + + /* Print each item. */ + for (size_t i = 0; i < num_items; ++i) { + char symbol_name[0x200]; + if (const uintptr_t symbol_base = ::ams::diag::GetSymbolName(symbol_name, sizeof(symbol_name), backtrace[i] - 1); symbol_base != 0) { + AMS_SDK_LOG("0x%016" PRIX64 " [ %s+0x%" PRIX64 " ]\n", static_cast<u64>(backtrace[i]), symbol_name, static_cast<u64>(backtrace[i] - symbol_base)); + } else { + AMS_SDK_LOG("0x%016" PRIX64 " [ unknown ]\n", static_cast<u64>(backtrace[i])); + } + } + } + AMS_SDK_LOG("-------------------------------------------\n"); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_dump_stack_trace.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_dump_stack_trace.os.horizon.cpp new file mode 100644 index 00000000..971bc538 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_dump_stack_trace.os.horizon.cpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "diag_dump_stack_trace.hpp" + +namespace ams::diag::impl { + + void TentativeDumpStackTrace() { + /* TODO */ + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_get_all_backtrace.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_get_all_backtrace.cpp new file mode 100644 index 00000000..c79973fe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_get_all_backtrace.cpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::diag::impl { + + namespace { + + constinit uintptr_t g_abort_impl_return_address = std::numeric_limits<uintptr_t>::max(); + + } + + void SetAbortImplReturnAddress(uintptr_t address) { + g_abort_impl_return_address = address; + } + + size_t GetAllBacktrace(uintptr_t *out, size_t out_size, ::ams::diag::Backtrace &bt) { + size_t count = 0; + do { + /* Check that we can write another return address. */ + if (count >= out_size) { + break; + } + + /* Get the current return address. */ + const uintptr_t ret_addr = bt.GetReturnAddress(); + + /* If it's abort impl, reset the trace we're writing. */ + if (ret_addr == g_abort_impl_return_address) { + count = 0; + } + + /* Set the output pointer. */ + out[count++] = ret_addr; + } while (bt.Step()); + + /* Return the number of addresses written. */ + return count; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_get_all_backtrace.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_get_all_backtrace.hpp new file mode 100644 index 00000000..7ee2aaaa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_get_all_backtrace.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::diag::impl { + + void SetAbortImplReturnAddress(uintptr_t address); + + size_t GetAllBacktrace(uintptr_t *out, size_t out_size, ::ams::diag::Backtrace &bt); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_invoke_abort.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_invoke_abort.hpp new file mode 100644 index 00000000..513bd0ab --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_invoke_abort.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::diag::impl { + + void InvokeAbortObserver(const AbortInfo &info); + void InvokeSdkAbortObserver(const SdkAbortInfo &info); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_invoke_abort.os.generic.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_invoke_abort.os.generic.cpp new file mode 100644 index 00000000..33812ab3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_invoke_abort.os.generic.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "diag_abort_observer_manager.hpp" +#include "diag_invoke_abort.hpp" + +namespace ams::diag::impl { + + extern bool g_enable_default_abort_observer; + + void DefaultAbortObserver(const AbortInfo &info); + + void InvokeAbortObserver(const AbortInfo &info) { + if (g_enable_default_abort_observer) { + DefaultAbortObserver(info); + } + + GetAbortObserverManager()->InvokeAllObserver(info); + } + + void InvokeSdkAbortObserver(const SdkAbortInfo &info) { + GetSdkAbortObserverManager()->InvokeAllObserver(info); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_invoke_abort.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_invoke_abort.os.horizon.cpp new file mode 100644 index 00000000..33812ab3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_invoke_abort.os.horizon.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "diag_abort_observer_manager.hpp" +#include "diag_invoke_abort.hpp" + +namespace ams::diag::impl { + + extern bool g_enable_default_abort_observer; + + void DefaultAbortObserver(const AbortInfo &info); + + void InvokeAbortObserver(const AbortInfo &info) { + if (g_enable_default_abort_observer) { + DefaultAbortObserver(info); + } + + GetAbortObserverManager()->InvokeAllObserver(info); + } + + void InvokeSdkAbortObserver(const SdkAbortInfo &info) { + GetSdkAbortObserverManager()->InvokeAllObserver(info); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_module_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_module_impl.os.horizon.cpp new file mode 100644 index 00000000..0aebd2b4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_module_impl.os.horizon.cpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::diag::impl { + + namespace { + + constexpr inline uintptr_t ModulePathLengthOffset = 4; + constexpr inline uintptr_t ModulePathOffset = 8; + + } + + uintptr_t GetModuleInfoForHorizon(const char **out_path, size_t *out_path_length, size_t *out_module_size, uintptr_t address) { + /* Check for null address. */ + if (address == 0) { + return 0; + } + + /* Get module info. */ + ro::impl::ExceptionInfo exception_info; + if (!ro::impl::GetExceptionInfo(std::addressof(exception_info), address)) { + return 0; + } + + /* Locate the path in the first non-read-execute segment. */ + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + auto cur_address = exception_info.module_address; + while (cur_address < exception_info.module_address + exception_info.module_size) { + if (R_FAILED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), cur_address))) { + return 0; + } + if (mem_info.permission != svc::MemoryPermission_ReadExecute) { + break; + } + cur_address += mem_info.size; + } + + /* Set output info. */ + *out_path = reinterpret_cast<const char *>(cur_address + ModulePathOffset); + *out_path_length = *reinterpret_cast<const u32 *>(cur_address + ModulePathLengthOffset); + *out_module_size = exception_info.module_size; + + return exception_info.module_address; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_observer_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_observer_manager.hpp new file mode 100644 index 00000000..64767873 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_observer_manager.hpp @@ -0,0 +1,157 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::diag::impl { + + template<typename Holder, typename Context> + class ObserverManager { + NON_COPYABLE(ObserverManager); + NON_MOVEABLE(ObserverManager); + private: + Holder *m_observer_list_head; + Holder **m_observer_list_tail; + os::ReaderWriterLock m_lock; + public: + constexpr ObserverManager() : m_observer_list_head(nullptr), m_observer_list_tail(std::addressof(m_observer_list_head)), m_lock() { + /* ... */ + } + + constexpr ~ObserverManager() { + if (std::is_constant_evaluated()) { + this->UnregisterAllObserverLocked(); + } else { + this->UnregisterAllObserver(); + } + } + + void RegisterObserver(Holder *holder) { + /* Acquire a write hold on our lock. */ + std::scoped_lock lk(m_lock); + + this->RegisterObserverLocked(holder); + } + + void UnregisterObserver(Holder *holder) { + /* Acquire a write hold on our lock. */ + std::scoped_lock lk(m_lock); + + /* Check that we can unregister. */ + AMS_ASSERT(holder->is_registered); + + /* Remove the holder. */ + if (m_observer_list_head == holder) { + m_observer_list_head = holder->next; + if (m_observer_list_tail == std::addressof(holder->next)) { + m_observer_list_tail = std::addressof(m_observer_list_head); + } + } else { + for (auto *cur = m_observer_list_head; cur != nullptr; cur = cur->next) { + if (cur->next == holder) { + cur->next = holder->next; + + if (m_observer_list_tail == std::addressof(holder->next)) { + m_observer_list_tail = std::addressof(cur->next); + } + + break; + } + } + } + + /* Set unregistered. */ + holder->next = nullptr; + } + + void UnregisterAllObserver() { + /* Acquire a write hold on our lock. */ + std::scoped_lock lk(m_lock); + + this->UnregisterAllObserverLocked(); + } + + void InvokeAllObserver(const Context &context) { + /* Use the holder's observer. */ + InvokeAllObserver(context, [] (const Holder &holder, const Context &context) { + holder.observer(context); + }); + } + + template<typename Observer> + void InvokeAllObserver(const Context &context, Observer observer) { + /* Acquire a read hold on our lock. */ + std::shared_lock lk(m_lock); + + /* Invoke all observers. */ + for (const auto *holder = m_observer_list_head; holder != nullptr; holder = holder->next) { + observer(*holder, context); + } + } + protected: + constexpr void RegisterObserverLocked(Holder *holder) { + /* Check that we can register. */ + AMS_ASSERT(!holder->is_registered); + + /* Insert the holder. */ + *m_observer_list_tail = holder; + m_observer_list_tail = std::addressof(holder->next); + + /* Set registered. */ + holder->next = nullptr; + holder->is_registered = true; + } + + constexpr void UnregisterAllObserverLocked() { + /* Unregister all observers. */ + for (auto *holder = m_observer_list_head; holder != nullptr; holder = holder->next) { + holder->is_registered = false; + } + + /* Reset head/fail. */ + m_observer_list_head = nullptr; + m_observer_list_tail = std::addressof(m_observer_list_head); + } + }; + + template<typename Holder, typename Context> + class ObserverManagerWithDefaultHolder : public ObserverManager<Holder, Context> { + private: + Holder m_default_holder; + public: + template<typename Initializer, typename... Args> + constexpr ObserverManagerWithDefaultHolder(Initializer initializer, Args &&... args) : ObserverManager<Holder, Context>(), m_default_holder{} { + /* Initialize the default observer. */ + initializer(std::addressof(m_default_holder), std::forward<Args>(args)...); + + /* Register the default observer. */ + if (std::is_constant_evaluated()) { + this->RegisterObserverLocked(std::addressof(m_default_holder)); + } else { + this->RegisterObserver(std::addressof(m_default_holder)); + } + } + + Holder &GetDefaultObserverHolder() { + return m_default_holder; + } + + const Holder &GetDefaulObservertHolder() const { + return m_default_holder; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.hpp new file mode 100644 index 00000000..a112e842 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::diag::impl { + + void PrintDebugString(const char *msg, size_t size); + + inline void PrintDebugString(const char *msg) { + AMS_AUDIT(msg != nullptr); + + PrintDebugString(msg, std::strlen(msg)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.os.horizon.cpp new file mode 100644 index 00000000..55bbcb93 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.os.horizon.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "diag_print_debug_string.hpp" + +namespace ams::diag::impl { + + void PrintDebugString(const char *msg, size_t size) { + AMS_AUDIT(msg != nullptr || size == 0); + + if (size != 0) { + svc::OutputDebugString(msg, size); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.os.linux.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.os.linux.cpp new file mode 100644 index 00000000..2168e2d3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.os.linux.cpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "diag_print_debug_string.hpp" + +namespace ams::diag::impl { + + NOINLINE void PrintDebugString(const char *msg, size_t size) { + AMS_AUDIT(msg != nullptr || size == 0); + + if (size == 0) { + return; + } + + /* TODO: Printf? */ + printf("%s", msg); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.os.macos.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.os.macos.cpp new file mode 100644 index 00000000..2168e2d3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.os.macos.cpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "diag_print_debug_string.hpp" + +namespace ams::diag::impl { + + NOINLINE void PrintDebugString(const char *msg, size_t size) { + AMS_AUDIT(msg != nullptr || size == 0); + + if (size == 0) { + return; + } + + /* TODO: Printf? */ + printf("%s", msg); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.os.windows.cpp new file mode 100644 index 00000000..49105fff --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_print_debug_string.os.windows.cpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "diag_print_debug_string.hpp" + +namespace ams::diag::impl { + + NOINLINE void PrintDebugString(const char *msg, size_t size) { + AMS_AUDIT(msg != nullptr || size == 0); + + if (size == 0) { + return; + } + + /* TODO: OutputDebugString? Printf? */ + printf("%s", msg); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_process.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_process.os.horizon.cpp new file mode 100644 index 00000000..a421af0e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_process.os.horizon.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +/* TODO: Rename, if we change to e.g. use amsMain? */ +extern "C" int main(int argc, char **argv); + +namespace ams::diag::impl { + + uintptr_t GetModuleInfoForHorizon(const char **out_path, size_t *out_path_length, size_t *out_module_size, uintptr_t address); + + namespace { + + const char *GetLastCharacterPointer(const char *str, size_t len, char c) { + for (const char *last = str + len - 1; last >= str; --last) { + if (*last == c) { + return last; + } + } + return nullptr; + } + + void GetFileNameWithoutExtension(const char **out, size_t *out_size, const char *path, size_t path_length) { + const auto last_sep1 = GetLastCharacterPointer(path, path_length, '\\'); + const auto last_sep2 = GetLastCharacterPointer(path, path_length, '/'); + const auto ext = GetLastCharacterPointer(path, path_length, '.'); + + /* Handle last-separator. */ + if (last_sep1 && last_sep2) { + if (last_sep1 > last_sep2) { + *out = last_sep1 + 1; + } else { + *out = last_sep2 + 1; + } + } else if (last_sep1) { + *out = last_sep1 + 1; + } else if (last_sep2) { + *out = last_sep2 + 1; + } else { + *out = path; + } + + /* Handle extension. */ + if (ext && ext >= *out) { + *out_size = ext - *out; + } else { + *out_size = (path + path_length) - *out; + } + } + + constinit const char *g_process_name = nullptr; + constinit size_t g_process_name_size = 0; + constinit os::SdkMutex g_process_name_lock; + constinit bool g_got_process_name = false; + + void EnsureProcessNameCached() { + /* Ensure process name. */ + if (AMS_UNLIKELY(!g_got_process_name)) { + std::scoped_lock lk(g_process_name_lock); + if (AMS_LIKELY(!g_got_process_name)) { + const char *path; + size_t path_length; + size_t module_size; + + if (GetModuleInfoForHorizon(std::addressof(path), std::addressof(path_length), std::addressof(module_size), reinterpret_cast<uintptr_t>(main)) != 0) { + GetFileNameWithoutExtension(std::addressof(g_process_name), std::addressof(g_process_name_size), path, path_length); + AMS_ASSERT(g_process_name_size == 0 || util::VerifyUtf8String(g_process_name, g_process_name_size)); + } else { + g_process_name = ""; + g_process_name_size = 0; + } + + g_got_process_name = true; + } + } + } + + } + + void GetProcessNamePointer(const char **out, size_t *out_size) { + /* Ensure process name is cached. */ + EnsureProcessNameCached(); + + /* Get cached process name. */ + *out = g_process_name; + *out_size = g_process_name_size; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.hpp new file mode 100644 index 00000000..cdaebf89 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::diag::impl { + + uintptr_t GetSymbolNameImpl(char *dst, size_t dst_size, uintptr_t address); + size_t GetSymbolSizeImpl(uintptr_t address); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.generic.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.generic.cpp new file mode 100644 index 00000000..9a351ae4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.generic.cpp @@ -0,0 +1,241 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "diag_symbol_impl.hpp" + +#define PACKAGE "stratosphere" +#define PACKAGE_VERSION STRINGIFY(ATMOSPHERE_RELEASE_VERSION_MAJOR.ATMOSPHERE_RELEASE_VERSION_MINOR.ATMOSPHERE_RELEASE_VERSION_MICRO) + +#if defined(ATMOSPHERE_OS_LINUX) +#include <bfd.h> +#include <unistd.h> +#include <dlfcn.h> +#include <link.h> + +extern "C" char __init_array_start; +#endif + +#include <cxxabi.h> + +namespace ams::diag::impl { + + namespace { + + class BfdHelper { + private: + bfd *m_handle; + asymbol **m_symbol; + size_t m_num_symbol; + size_t m_num_func_symbol; + const char *m_module_name; + uintptr_t m_module_address; + size_t m_module_size; + private: + BfdHelper() : m_handle(nullptr), m_symbol(nullptr), m_module_name(nullptr) { + /* Get the current executable name. */ + char exe_path[4_KB] = {}; + GetExecutablePath(exe_path, sizeof(exe_path)); + + /* Open bfd. */ + bfd *b = ::bfd_openr(exe_path, 0); + if (b == nullptr) { + return; + } + auto bfd_guard = SCOPE_GUARD { ::bfd_close(b); }; + + /* Check the format. */ + if (!::bfd_check_format(b, bfd_object)) { + return; + } + + /* Verify the file has symbols. */ + if ((bfd_get_file_flags(b) & HAS_SYMS) == 0) { + return; + } + + /* Read the symbols. */ + unsigned int _; + void *symbol_table; + s64 num_symbols = bfd_read_minisymbols(b, false, std::addressof(symbol_table), std::addressof(_)); + if (num_symbols == 0) { + num_symbols = bfd_read_minisymbols(b, true, std::addressof(symbol_table), std::addressof(_)); + if (num_symbols < 0) { + return; + } + } + + /* We successfully got the symbol table. */ + bfd_guard.Cancel(); + + m_handle = b; + m_symbol = reinterpret_cast<asymbol **>(symbol_table); + m_num_symbol = static_cast<size_t>(num_symbols); + + /* Sort the symbol table. */ + std::sort(m_symbol + 0, m_symbol + m_num_symbol, [] (asymbol *lhs, asymbol *rhs) { + const bool l_func = (lhs->flags & BSF_FUNCTION); + const bool r_func = (rhs->flags & BSF_FUNCTION); + if (l_func == r_func) { + return bfd_asymbol_value(lhs) < bfd_asymbol_value(rhs); + } else { + return l_func; + } + }); + + /* Determine number of function symbols. */ + m_num_func_symbol = 0; + for (size_t i = 0; i < m_num_symbol; ++i) { + if ((m_symbol[i]->flags & BSF_FUNCTION) == 0) { + m_num_func_symbol = i; + break; + } + } + + for (int i = std::strlen(exe_path) - 1; i >= 0; --i) { + if (exe_path[i] == '/' || exe_path[i] == '\\') { + m_module_name = strdup(exe_path + i + 1); + break; + } + } + + /* Get our module base/size. */ + #if defined(ATMOSPHERE_OS_LINUX) + { + m_module_address = _r_debug.r_map->l_addr; + + m_module_size = reinterpret_cast<uintptr_t>(std::addressof(__init_array_start)) - m_module_address; + } + #endif + } + + ~BfdHelper() { + if (m_symbol != nullptr) { + std::free(m_symbol); + } + if (m_handle != nullptr) { + ::bfd_close(m_handle); + } + } + public: + static BfdHelper &GetInstance() { + AMS_FUNCTION_LOCAL_STATIC(BfdHelper, s_bfd_helper_instance); + return s_bfd_helper_instance; + } + private: + size_t GetSymbolSizeImpl(asymbol **symbol) const { + /* Do our best to guess. */ + const auto vma = bfd_asymbol_value(*symbol); + if (symbol != m_symbol + m_num_func_symbol - 1) { + return bfd_asymbol_value(*(symbol + 1)) - vma; + } else { + const auto *sec = (*symbol)->section; + return (sec->vma + sec->size) - vma; + } + } + + std::ptrdiff_t GetSymbolAddressDisplacement(uintptr_t address) const { + std::ptrdiff_t displacement = 0; + + if (m_module_address <= address && address < m_module_address + m_module_size) { + displacement = m_module_address; + } + + return displacement; + } + + asymbol **GetBestSymbol(uintptr_t address) const { + /* Adjust the symbol address. */ + address -= this->GetSymbolAddressDisplacement(address); + + asymbol **best_symbol = std::lower_bound(m_symbol + 0, m_symbol + m_num_func_symbol, address, [](asymbol *lhs, uintptr_t rhs) { + return bfd_asymbol_value(lhs) < rhs; + }); + + if (best_symbol == m_symbol + m_num_func_symbol) { + return nullptr; + } + + if (bfd_asymbol_value(*best_symbol) != address && best_symbol > m_symbol) { + --best_symbol; + } + + const auto vma = bfd_asymbol_value(*best_symbol); + const auto end = vma + this->GetSymbolSizeImpl(best_symbol); + + if (vma <= address && address < end) { + return best_symbol; + } else { + return nullptr; + } + } + public: + uintptr_t GetSymbolName(char *dst, size_t dst_size, uintptr_t address) const { + /* Get the symbol. */ + auto **symbol = this->GetBestSymbol(address); + if (symbol == nullptr) { + return 0; + } + + /* Print the symbol. */ + const char *name = bfd_asymbol_name(*symbol); + + int cpp_name_status = 0; + if (char *demangled = abi::__cxa_demangle(name, nullptr, 0, std::addressof(cpp_name_status)); cpp_name_status == 0) { + AMS_ASSERT(demangled != nullptr); + util::TSNPrintf(dst, dst_size, "%s", demangled); + std::free(demangled); + } else { + util::TSNPrintf(dst, dst_size, "%s", name); + } + + return bfd_asymbol_value(*symbol) + this->GetSymbolAddressDisplacement(address); + } + + size_t GetSymbolSize(uintptr_t address) const { + /* Get the symbol. */ + auto **symbol = this->GetBestSymbol(address); + if (symbol == nullptr) { + return 0; + } + + return this->GetSymbolSizeImpl(symbol); + } + private: + static void GetExecutablePath(char *dst, size_t dst_size) { + #if defined(ATMOSPHERE_OS_LINUX) + { + if (::readlink("/proc/self/exe", dst, dst_size) == -1) { + dst[0] = 0; + return; + } + } + #else + #error "Unknown OS for BfdHelper GetExecutablePath" + #endif + } + }; + + } + + uintptr_t GetSymbolNameImpl(char *dst, size_t dst_size, uintptr_t address) { + return BfdHelper::GetInstance().GetSymbolName(dst, dst_size, address); + } + + size_t GetSymbolSizeImpl(uintptr_t address) { + return BfdHelper::GetInstance().GetSymbolSize(address); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.horizon.cpp new file mode 100644 index 00000000..28e74a78 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.horizon.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "diag_symbol_impl.hpp" + +namespace ams::diag::impl { + + uintptr_t GetSymbolNameImpl(char *dst, size_t dst_size, uintptr_t address) { + AMS_UNUSED(dst, dst_size, address); + AMS_ABORT("TODO"); + } + + size_t GetSymbolSizeImpl(uintptr_t address) { + AMS_UNUSED(address); + AMS_ABORT("TODO"); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.macos.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.macos.cpp new file mode 100644 index 00000000..0507df85 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.macos.cpp @@ -0,0 +1,318 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "diag_symbol_impl.hpp" + +#include <unistd.h> +#include <mach-o/dyld.h> +#include <mach-o/loader.h> +#include <mach-o/nlist.h> +#include <mach-o/stab.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/param.h> +#include <sys/mman.h> +#include <cxxabi.h> + + +extern "C" { + + void __module_offset_helper() { /* ... */ } + +} + +namespace ams::diag::impl { + + namespace { + + class CurrentExecutableHelper { + private: + struct SymbolInfo { + uintptr_t address; + const char *name; + }; + private: + os::NativeHandle m_fd; + void *m_file_map; + size_t m_file_size; + SymbolInfo *m_symbols; + size_t m_num_symbol; + const char *m_module_name; + uintptr_t m_module_address; + size_t m_module_size; + uintptr_t m_module_displacement; + private: + CurrentExecutableHelper() : m_fd(-1), m_file_map(nullptr), m_file_size(0), m_symbols(nullptr), m_num_symbol(0), m_module_name(nullptr), m_module_address(0), m_module_size(0), m_module_displacement(0) { + /* Get the current executable name. */ + char exe_path[4_KB] = {}; + GetExecutablePath(exe_path, sizeof(exe_path)); + + /* Open the current executable. */ + os::NativeHandle fd; + do { + fd = ::open(exe_path, O_RDONLY); + } while (fd < 0 && errno == EINTR); + if (fd < 0) { + return; + } + ON_SCOPE_EXIT { if (fd >= 0) { s32 ret; do { ret = ::close(fd); } while (ret < 0 && errno == EINTR); } }; + + /* Get the file size. */ + struct stat st; + if (fstat(fd, std::addressof(st)) < 0) { + return; + } + + /* Check that the file can be mapped. */ + const size_t exe_size = st.st_size; + if (exe_size == 0) { + return; + } + + /* Map the executable. */ + void *exe_map = mmap(nullptr, exe_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (exe_map == MAP_FAILED) { + return; + } + ON_SCOPE_EXIT { if (exe_map != nullptr) { munmap(exe_map, exe_size); } }; + + /* Get the file's u32 magic. */ + const uintptr_t exe_start = reinterpret_cast<uintptr_t>(exe_map); + const u32 magic = *reinterpret_cast<const u32 *>(exe_start); + + /* Get/parse the mach header. */ + u32 ncmds; + bool is_64; + if (magic == MH_MAGIC) { + const auto *header = reinterpret_cast<const struct mach_header *>(exe_start); + ncmds = header->ncmds; + is_64 = false; + } else if (magic == MH_MAGIC_64) { + const auto *header = reinterpret_cast<const struct mach_header_64 *>(exe_start); + ncmds = header->ncmds; + is_64 = true; + } else { + return; + } + + /* Find the symbol load command. */ + const auto *lc = reinterpret_cast<const struct load_command *>(exe_start + (is_64 ? sizeof(struct mach_header_64) : sizeof(struct mach_header))); + for (u32 i = 0; i < ncmds; ++i) { + /* If we encounter the symbol table, parse it. */ + if (lc->cmd == LC_SYMTAB) { + if (is_64) { + this->ParseSymbolTable<struct nlist_64>(exe_start, reinterpret_cast<const struct symtab_command *>(lc)); + } else { + this->ParseSymbolTable<struct nlist>(exe_start, reinterpret_cast<const struct symtab_command *>(lc)); + } + break; + } else if (lc->cmd == LC_SEGMENT) { + const auto *sc = reinterpret_cast<const struct segment_command *>(lc); + if (std::strcmp(sc->segname, "__TEXT") == 0) { + AMS_ASSERT(m_module_address == 0); + m_module_address = sc->vmaddr; + m_module_size = sc->vmsize; + AMS_ASSERT(m_module_address != 0); + } + } else if (lc->cmd == LC_SEGMENT_64) { + const auto *sc = reinterpret_cast<const struct segment_command_64 *>(lc); + if (std::strcmp(sc->segname, "__TEXT") == 0) { + AMS_ASSERT(m_module_address == 0); + m_module_address = sc->vmaddr; + m_module_size = sc->vmsize; + AMS_ASSERT(m_module_address != 0); + } + } + + /* Advance to the next load command. */ + lc = reinterpret_cast<const struct load_command *>(reinterpret_cast<uintptr_t>(lc) + lc->cmdsize); + } + + for (size_t i = 0; i < m_num_symbol; ++i) { + if (std::strcmp(m_symbols[i].name, "___module_offset_helper") == 0) { + m_module_displacement = reinterpret_cast<uintptr_t>(&__module_offset_helper) - m_symbols[i].address; + break; + } + } + + if (m_module_address > 0 && m_module_size > 0 && m_num_symbol > 0) { + std::swap(m_fd, fd); + std::swap(m_file_map, exe_map); + m_file_size = exe_size; + } + } + + ~CurrentExecutableHelper() { + if (m_file_map != nullptr) { + munmap(m_file_map, m_file_size); + } + + if (m_fd >= 0) { + s32 ret; + do { ret = ::close(m_fd); } while (ret < 0 && errno == EINTR); + } + } + public: + static CurrentExecutableHelper &GetInstance() { + AMS_FUNCTION_LOCAL_STATIC(CurrentExecutableHelper, s_current_executable_helper_instance); + return s_current_executable_helper_instance; + } + private: + template<typename NlistType> + void ParseSymbolTable(uintptr_t exe_start, const struct symtab_command *c) { + /* Check pre-conditions. */ + AMS_ASSERT(m_fd == -1); + AMS_ASSERT(m_file_map == nullptr); + AMS_ASSERT(m_symbols == nullptr); + + /* Get the strtab/symtab. */ + const auto *symtab = reinterpret_cast<const NlistType *>(exe_start + c->symoff); + const char *strtab = reinterpret_cast<const char *>(exe_start + c->stroff); + + /* Determine the number of functions. */ + size_t funcs = 0; + + for (size_t i = 0; i < c->nsyms; ++i) { + if (symtab[i].n_type != N_FUN || symtab[i].n_sect == NO_SECT) { + continue; + } + + ++funcs; + } + + /* Allocate functions. */ + m_symbols = reinterpret_cast<SymbolInfo *>(std::malloc(sizeof(SymbolInfo) * funcs)); + if (m_symbols == nullptr) { + return; + } + + /* Set all symbols. */ + m_num_symbol = 0; + for (size_t i = 0; i < c->nsyms; ++i) { + if (symtab[i].n_type != N_FUN || symtab[i].n_sect == NO_SECT) { + continue; + } + + m_symbols[m_num_symbol].address = symtab[i].n_value; + m_symbols[m_num_symbol].name = strtab + symtab[i].n_un.n_strx; + + ++m_num_symbol; + } + AMS_ASSERT(m_num_symbol == funcs); + + /* Sort the symbols. */ + std::sort(m_symbols + 0, m_symbols + m_num_symbol, [] (const SymbolInfo &lhs, const SymbolInfo &rhs) { + return lhs.address < rhs.address; + }); + } + + size_t GetSymbolSizeImpl(const SymbolInfo *symbol) const { + /* Do our best to guess. */ + if (symbol != m_symbols + m_num_symbol - 1) { + return (symbol + 1)->address - symbol->address; + } else if (m_module_address + m_module_size >= symbol->address) { + return m_module_address + m_module_size - symbol->address; + } else { + return 0; + } + } + + const SymbolInfo *GetBestSymbol(uintptr_t address) const { + address -= m_module_displacement; + + const SymbolInfo *best_symbol = std::lower_bound(m_symbols + 0, m_symbols + m_num_symbol, address, [](const SymbolInfo &lhs, uintptr_t rhs) { + return lhs.address < rhs; + }); + + if (best_symbol == m_symbols + m_num_symbol) { + return nullptr; + } + + if (best_symbol->address != address && best_symbol > m_symbols) { + --best_symbol; + } + + const auto vma = best_symbol->address; + const auto end = vma + this->GetSymbolSizeImpl(best_symbol); + + if (vma <= address && address < end) { + return best_symbol; + } else { + return nullptr; + } + } + public: + uintptr_t GetSymbolName(char *dst, size_t dst_size, uintptr_t address) const { + if (m_fd < 0) { + return 0; + } + + /* Get the symbol. */ + const auto *symbol = this->GetBestSymbol(address); + if (symbol == nullptr) { + return 0; + } + + /* Print the symbol. */ + const char *name = symbol->name; + + int cpp_name_status = 0; + if (char *demangled = abi::__cxa_demangle(name, nullptr, 0, std::addressof(cpp_name_status)); cpp_name_status == 0) { + AMS_ASSERT(demangled != nullptr); + util::TSNPrintf(dst, dst_size, "%s", demangled); + std::free(demangled); + } else { + util::TSNPrintf(dst, dst_size, "%s", name); + } + + return symbol->address + m_module_displacement; + } + + size_t GetSymbolSize(uintptr_t address) const { + if (m_fd < 0) { + return 0; + } + + /* Get the symbol. */ + const auto *symbol = this->GetBestSymbol(address); + if (symbol == nullptr) { + return 0; + } + + return this->GetSymbolSizeImpl(symbol); + } + private: + static void GetExecutablePath(char *dst, size_t dst_size) { + u32 len = dst_size; + if (_NSGetExecutablePath(dst, std::addressof(len)) != 0) { + dst[0] = 0; + return; + } + } + }; + + } + + uintptr_t GetSymbolNameImpl(char *dst, size_t dst_size, uintptr_t address) { + return CurrentExecutableHelper::GetInstance().GetSymbolName(dst, dst_size, address); + } + + size_t GetSymbolSizeImpl(uintptr_t address) { + return CurrentExecutableHelper::GetInstance().GetSymbolSize(address); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.windows.cpp new file mode 100644 index 00000000..20660f6b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_symbol_impl.os.windows.cpp @@ -0,0 +1,292 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <stratosphere/windows.hpp> +#include "diag_symbol_impl.hpp" + +#include <cxxabi.h> +extern "C" { + + void __module_offset_helper() { /* ... */ } + +} + +namespace ams::diag::impl { + + namespace { + + class CurrentExecutableHelper { + private: + struct SymbolInfo { + uintptr_t address; + const char *name; + }; + private: + os::NativeHandle m_file_handle; + os::NativeHandle m_map_handle; + void *m_file_map; + size_t m_file_size; + SymbolInfo *m_symbols; + size_t m_num_symbol; + uintptr_t m_module_address; + size_t m_module_size; + uintptr_t m_module_displacement; + private: + CurrentExecutableHelper() : m_file_handle(INVALID_HANDLE_VALUE), m_map_handle(INVALID_HANDLE_VALUE), m_file_map(nullptr), m_file_size(0), m_symbols(nullptr), m_num_symbol(0), m_module_address(0), m_module_size(0), m_module_displacement(0) { + /* Open the current executable. */ + auto exe_handle = OpenExecutableFile(); + if (exe_handle == INVALID_HANDLE_VALUE) { + return; + } + ON_SCOPE_EXIT { if (exe_handle != INVALID_HANDLE_VALUE) { ::CloseHandle(exe_handle); } }; + + /* Get the exe size. */ + LARGE_INTEGER exe_size_li; + if (::GetFileSizeEx(exe_handle, std::addressof(exe_size_li)) == 0) { + return; + } + + /* Check the exe size. */ + s64 exe_size = exe_size_li.QuadPart; + if (exe_size == 0) { + return; + } + + /* Create a file mapping. */ + auto map_handle = ::CreateFileMappingW(exe_handle, nullptr, PAGE_READONLY, 0, 0, nullptr); + if (map_handle == INVALID_HANDLE_VALUE) { + return; + } + ON_SCOPE_EXIT { if (map_handle != INVALID_HANDLE_VALUE) { ::CloseHandle(map_handle); } }; + + /* Map the file. */ + void *exe_map = ::MapViewOfFile(map_handle, FILE_MAP_READ, 0, 0, 0); + if (exe_map == nullptr) { + return; + } + ON_SCOPE_EXIT { if (exe_map != nullptr) { ::UnmapViewOfFile(exe_map); } }; + + /* Get/parse the DOS header. */ + const uintptr_t exe_start = reinterpret_cast<uintptr_t>(exe_map); + const auto *dos_header = reinterpret_cast<const IMAGE_DOS_HEADER *>(exe_start); + if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) { + return; + } + + /* Set up the nt headers. */ + const auto *nt32 = reinterpret_cast<const IMAGE_NT_HEADERS32 *>(exe_start + dos_header->e_lfanew); + const auto *nt64 = reinterpret_cast<const IMAGE_NT_HEADERS64 *>(exe_start + dos_header->e_lfanew); + if (nt32->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64 || nt32->FileHeader.Machine == IMAGE_FILE_MACHINE_IA64) { + nt32 = nullptr; + } else { + nt64 = nullptr; + } + + #define EXE_NT_HEADER(x) ((nt64 != nullptr) ? nt64->x : nt32->x) + + if (EXE_NT_HEADER(Signature) != IMAGE_NT_SIGNATURE) { + return; + } + + /* Check that the optional header is really present. */ + if (EXE_NT_HEADER(FileHeader.SizeOfOptionalHeader) < sizeof(IMAGE_OPTIONAL_HEADER32)) { + return; + } + + /* Get sections. */ + const auto *sec = nt64 != nullptr ? IMAGE_FIRST_SECTION(nt64) : IMAGE_FIRST_SECTION(nt32); + + /* Get the symbol table. */ + const auto *symtab = reinterpret_cast<const IMAGE_SYMBOL *>(exe_start + EXE_NT_HEADER(FileHeader.PointerToSymbolTable)); + const size_t nsym = EXE_NT_HEADER(FileHeader.NumberOfSymbols); + const char *strtab = reinterpret_cast<const char *>(symtab + nsym); + + /* Find the section containing our code. */ + int text_section = -1; + for (size_t i = 0; i < nsym; ++i) { + if (ISFCN(symtab[i].Type)) { + const char *name = symtab[i].N.Name.Short == 0 ? strtab + symtab[i].N.Name.Long : reinterpret_cast<const char *>(symtab[i].N.ShortName); + if (std::strcmp(name, "__module_offset_helper") == 0) { + AMS_ASSERT(text_section == -1); + AMS_ASSERT(m_module_displacement == 0); + + m_module_displacement = reinterpret_cast<uintptr_t>(&__module_offset_helper) - symtab[i].Value; + text_section = symtab[i].SectionNumber; + + AMS_ASSERT(m_module_displacement != 0); + AMS_ASSERT(text_section != -1); + break; + } + } + } + + /* Determine the number of functions. */ + size_t funcs = 0; + for (size_t i = 0; i < nsym; ++i) { + if (ISFCN(symtab[i].Type) && symtab[i].SectionNumber == text_section) { + ++funcs; + } + } + + /* Allocate functions. */ + m_symbols = reinterpret_cast<SymbolInfo *>(std::malloc(sizeof(SymbolInfo) * funcs)); + if (m_symbols == nullptr) { + return; + } + + /* Set all symbols. */ + m_num_symbol = 0; + for (size_t i = 0; i < nsym; ++i) { + if (ISFCN(symtab[i].Type) && symtab[i].SectionNumber == text_section) { + m_symbols[m_num_symbol].address = symtab[i].Value; + m_symbols[m_num_symbol].name = symtab[i].N.Name.Short == 0 ? strtab + symtab[i].N.Name.Long : reinterpret_cast<const char *>(symtab[i].N.ShortName); + + ++m_num_symbol; + } + } + AMS_ASSERT(m_num_symbol == funcs); + + /* Sort the symbols. */ + std::sort(m_symbols + 0, m_symbols + m_num_symbol, [] (const SymbolInfo &lhs, const SymbolInfo &rhs) { + return lhs.address < rhs.address; + }); + + m_module_address = 0; + m_module_size = sec[text_section - 1].Misc.VirtualSize; + + if (m_module_displacement != 0 && m_module_size > 0 && m_num_symbol > 0) { + std::swap(m_file_handle, exe_handle); + std::swap(m_map_handle, map_handle); + std::swap(m_file_map, exe_map); + m_file_size = exe_size; + } + } + + ~CurrentExecutableHelper() { + if (m_file_map != nullptr) { + ::UnmapViewOfFile(m_file_map); + } + if (m_map_handle != nullptr) { + ::CloseHandle(m_map_handle); + } + if (m_file_handle != nullptr) { + ::CloseHandle(m_file_handle); + } + } + public: + static CurrentExecutableHelper &GetInstance() { + AMS_FUNCTION_LOCAL_STATIC(CurrentExecutableHelper, s_current_executable_helper_instance); + return s_current_executable_helper_instance; + } + private: + size_t GetSymbolSizeImpl(const SymbolInfo *symbol) const { + /* Do our best to guess. */ + if (symbol != m_symbols + m_num_symbol - 1) { + return (symbol + 1)->address - symbol->address; + } else if (m_module_address + m_module_size >= symbol->address) { + return m_module_address + m_module_size - symbol->address; + } else { + return 0; + } + } + + const SymbolInfo *GetBestSymbol(uintptr_t address) const { + address -= m_module_displacement; + + const SymbolInfo *best_symbol = std::lower_bound(m_symbols + 0, m_symbols + m_num_symbol, address, [](const SymbolInfo &lhs, uintptr_t rhs) { + return lhs.address < rhs; + }); + + if (best_symbol == m_symbols + m_num_symbol) { + return nullptr; + } + + if (best_symbol->address != address && best_symbol > m_symbols) { + --best_symbol; + } + + const auto vma = best_symbol->address; + const auto end = vma + this->GetSymbolSizeImpl(best_symbol); + + if (vma <= address && address < end) { + return best_symbol; + } else { + return nullptr; + } + } + public: + uintptr_t GetSymbolName(char *dst, size_t dst_size, uintptr_t address) const { + if (m_file_handle == INVALID_HANDLE_VALUE) { + return 0; + } + + /* Get the symbol. */ + const auto *symbol = this->GetBestSymbol(address); + if (symbol == nullptr) { + return 0; + } + + /* Print the symbol. */ + const char *name = symbol->name; + + int cpp_name_status = 0; + if (char *demangled = abi::__cxa_demangle(name, nullptr, 0, std::addressof(cpp_name_status)); cpp_name_status == 0) { + AMS_ASSERT(demangled != nullptr); + util::TSNPrintf(dst, dst_size, "%s", demangled); + std::free(demangled); + } else { + util::TSNPrintf(dst, dst_size, "%s", name); + } + + return symbol->address + m_module_displacement; + } + + size_t GetSymbolSize(uintptr_t address) const { + if (m_file_handle == INVALID_HANDLE_VALUE) { + return 0; + } + + /* Get the symbol. */ + const auto *symbol = this->GetBestSymbol(address); + if (symbol == nullptr) { + return 0; + } + + return this->GetSymbolSizeImpl(symbol); + } + private: + static os::NativeHandle OpenExecutableFile() { + /* Get the module file name. */ + wchar_t module_file_name[0x1000]; + if (::GetModuleFileNameW(0, module_file_name, util::size(module_file_name)) == 0) { + return INVALID_HANDLE_VALUE; + } + + return ::CreateFileW(module_file_name, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + } + }; + + } + + uintptr_t GetSymbolNameImpl(char *dst, size_t dst_size, uintptr_t address) { + return CurrentExecutableHelper::GetInstance().GetSymbolName(dst, dst_size, address); + } + + size_t GetSymbolSizeImpl(uintptr_t address) { + return CurrentExecutableHelper::GetInstance().GetSymbolSize(address); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_utf8_util.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_utf8_util.cpp new file mode 100644 index 00000000..4de80d3d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/diag/impl/diag_utf8_util.cpp @@ -0,0 +1,91 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::diag::impl { + + namespace { + + bool IsHeadOfCharacter(u8 c) { + return (c & 0xC0) != 0x80; + } + + size_t GetCharacterSize(u8 c) { + if ((c & 0x80) == 0) { + return 1; + } else if ((c & 0xE0) == 0xC0) { + return 2; + } else if ((c & 0xF0) == 0xE0) { + return 3; + } else if ((c & 0xF8) == 0xF0) { + return 4; + } + + return 0; + } + + const char *FindLastCharacterPointer(const char *str, size_t len) { + /* Find the head of the last character. */ + const char *cur; + for (cur = str + len - 1; cur >= str && !IsHeadOfCharacter(*reinterpret_cast<const u8 *>(cur)); --cur) { + /* ... */ + } + + /* Return the last character. */ + if (AMS_LIKELY(cur >= str)) { + return cur; + } else { + return nullptr; + } + } + + } + + int GetValidSizeAsUtf8String(const char *str, size_t len) { + /* Check pre-condition. */ + AMS_ASSERT(str != nullptr); + + /* Check if we have no data. */ + if (len == 0) { + return 0; + } + + /* Get the last character pointer. */ + const auto *last_char_ptr = FindLastCharacterPointer(str, len); + if (last_char_ptr == nullptr) { + return -1; + } + + /* Get sizes. */ + const size_t actual_size = (str + len) - last_char_ptr; + const size_t last_char_size = GetCharacterSize(*reinterpret_cast<const u8 *>(last_char_ptr)); + if (last_char_size == 0) { + return -1; + } else if (actual_size >= last_char_size) { + if (actual_size == last_char_size) { + return len; + } else { + return -1; + } + } else if (actual_size >= len) { + AMS_ASSERT(actual_size == len); + return -1; + } else { + return static_cast<int>(len - actual_size); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dmnt/dmntcht.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dmnt/dmntcht.h new file mode 100644 index 00000000..e42e1122 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dmnt/dmntcht.h @@ -0,0 +1,98 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <switch.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + u64 base; + u64 size; +} DmntMemoryRegionExtents; + +typedef struct { + u64 process_id; + u64 title_id; + DmntMemoryRegionExtents main_nso_extents; + DmntMemoryRegionExtents heap_extents; + DmntMemoryRegionExtents alias_extents; + DmntMemoryRegionExtents address_space_extents; + u8 main_nso_build_id[0x20]; +} DmntCheatProcessMetadata; + +typedef struct { + char readable_name[0x40]; + uint32_t num_opcodes; + uint32_t opcodes[0x100]; +} DmntCheatDefinition; + +typedef struct { + bool enabled; + uint32_t cheat_id; + DmntCheatDefinition definition; +} DmntCheatEntry; + +typedef struct { + u64 value; + u8 width; +} DmntFrozenAddressValue; + +typedef struct { + u64 address; + DmntFrozenAddressValue value; +} DmntFrozenAddressEntry; + +Result dmntchtInitialize(void); +void dmntchtExit(void); +Service* dmntchtGetServiceSession(void); + +Result dmntchtHasCheatProcess(bool *out); +Result dmntchtGetCheatProcessEvent(Event *event); +Result dmntchtGetCheatProcessMetadata(DmntCheatProcessMetadata *out_metadata); +Result dmntchtForceOpenCheatProcess(void); +Result dmntchtForceCloseCheatProcess(void); + +Result dmntchtGetCheatProcessMappingCount(u64 *out_count); +Result dmntchtGetCheatProcessMappings(MemoryInfo *buffer, u64 max_count, u64 offset, u64 *out_count); +Result dmntchtReadCheatProcessMemory(u64 address, void *buffer, size_t size); +Result dmntchtWriteCheatProcessMemory(u64 address, const void *buffer, size_t size); +Result dmntchtQueryCheatProcessMemory(MemoryInfo *mem_info, u64 address); +Result dmntchtPauseCheatProcess(void); +Result dmntchtResumeCheatProcess(void); + +Result dmntchtGetCheatCount(u64 *out_count); +Result dmntchtGetCheats(DmntCheatEntry *buffer, u64 max_count, u64 offset, u64 *out_count); +Result dmntchtGetCheatById(DmntCheatEntry *out_cheat, u32 cheat_id); +Result dmntchtToggleCheat(u32 cheat_id); +Result dmntchtAddCheat(DmntCheatDefinition *cheat, bool enabled, u32 *out_cheat_id); +Result dmntchtRemoveCheat(u32 cheat_id); +Result dmntchtReadStaticRegister(u64 *out, u8 which); +Result dmntchtWriteStaticRegister(u8 which, u64 value); +Result dmntchtResetStaticRegisters(); +Result dmntchtSetMasterCheat(DmntCheatDefinition *cheat); + +Result dmntchtGetFrozenAddressCount(u64 *out_count); +Result dmntchtGetFrozenAddresses(DmntFrozenAddressEntry *buffer, u64 max_count, u64 offset, u64 *out_count); +Result dmntchtGetFrozenAddress(DmntFrozenAddressEntry *out, u64 address); +Result dmntchtEnableFrozenAddress(u64 address, u64 width, u64 *out_value); +Result dmntchtDisableFrozenAddress(u64 address); + +#ifdef __cplusplus +} +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dmnt/dmntcht.os.horizon.c b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dmnt/dmntcht.os.horizon.c new file mode 100644 index 00000000..73e866ce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/dmnt/dmntcht.os.horizon.c @@ -0,0 +1,207 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#define NX_SERVICE_ASSUME_NON_DOMAIN +#include "../service_guard.h" +#include "dmntcht.h" + +static Service g_dmntchtSrv; + +NX_GENERATE_SERVICE_GUARD(dmntcht); + +Result _dmntchtInitialize(void) { + return smGetService(&g_dmntchtSrv, "dmnt:cht"); +} + +void _dmntchtCleanup(void) { + serviceClose(&g_dmntchtSrv); +} + +Service* dmntchtGetServiceSession(void) { + return &g_dmntchtSrv; +} + +Result dmntchtHasCheatProcess(bool *out) { + u8 tmp; + Result rc = serviceDispatchOut(&g_dmntchtSrv, 65000, tmp); + if (R_SUCCEEDED(rc) && out) *out = tmp & 1; + return rc; +} + +Result dmntchtGetCheatProcessEvent(Event *event) { + Handle evt_handle; + Result rc = serviceDispatch(&g_dmntchtSrv, 65001, + .out_handle_attrs = { SfOutHandleAttr_HipcCopy }, + .out_handles = &evt_handle, + ); + + if (R_SUCCEEDED(rc)) { + eventLoadRemote(event, evt_handle, true); + } + + return rc; +} + +Result dmntchtGetCheatProcessMetadata(DmntCheatProcessMetadata *out_metadata) { + return serviceDispatchOut(&g_dmntchtSrv, 65002, *out_metadata); +} + +static Result _dmntchtCmdVoid(Service* srv, u32 cmd_id) { + return serviceDispatch(srv, cmd_id); +} + +Result dmntchtForceOpenCheatProcess(void) { + return _dmntchtCmdVoid(&g_dmntchtSrv, 65003); +} + +Result dmntchtPauseCheatProcess(void) { + return _dmntchtCmdVoid(&g_dmntchtSrv, 65004); +} + +Result dmntchtResumeCheatProcess(void) { + return _dmntchtCmdVoid(&g_dmntchtSrv, 65005); +} + +Result dmntchtForceCloseCheatProcess(void) { + return _dmntchtCmdVoid(&g_dmntchtSrv, 65006); +} + +static Result _dmntchtGetCount(u64 *out_count, u32 cmd_id) { + return serviceDispatchOut(&g_dmntchtSrv, cmd_id, *out_count); +} + +static Result _dmntchtGetEntries(void *buffer, u64 buffer_size, u64 offset, u64 *out_count, u32 cmd_id) { + return serviceDispatchInOut(&g_dmntchtSrv, cmd_id, offset, *out_count, + .buffer_attrs = { SfBufferAttr_Out | SfBufferAttr_HipcMapAlias }, + .buffers = { { buffer, buffer_size } }, + ); +} + +static Result _dmntchtCmdInU32NoOut(u32 in, u32 cmd_id) { + return serviceDispatchIn(&g_dmntchtSrv, cmd_id, in); +} + +Result dmntchtGetCheatProcessMappingCount(u64 *out_count) { + return _dmntchtGetCount(out_count, 65100); +} + +Result dmntchtGetCheatProcessMappings(MemoryInfo *buffer, u64 max_count, u64 offset, u64 *out_count) { + return _dmntchtGetEntries(buffer, sizeof(*buffer) * max_count, offset, out_count, 65101); +} + +Result dmntchtReadCheatProcessMemory(u64 address, void *buffer, size_t size) { + const struct { + u64 address; + u64 size; + } in = { address, size }; + return serviceDispatchIn(&g_dmntchtSrv, 65102, in, + .buffer_attrs = { SfBufferAttr_Out | SfBufferAttr_HipcMapAlias }, + .buffers = { { buffer, size } }, + ); +} + +Result dmntchtWriteCheatProcessMemory(u64 address, const void *buffer, size_t size) { + const struct { + u64 address; + u64 size; + } in = { address, size }; + return serviceDispatchIn(&g_dmntchtSrv, 65103, in, + .buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcMapAlias }, + .buffers = { { buffer, size } }, + ); +} + +Result dmntchtQueryCheatProcessMemory(MemoryInfo *mem_info, u64 address){ + return serviceDispatchInOut(&g_dmntchtSrv, 65104, address, *mem_info); +} + +Result dmntchtGetCheatCount(u64 *out_count) { + return _dmntchtGetCount(out_count, 65200); +} + +Result dmntchtGetCheats(DmntCheatEntry *buffer, u64 max_count, u64 offset, u64 *out_count) { + return _dmntchtGetEntries(buffer, sizeof(*buffer) * max_count, offset, out_count, 65201); +} + +Result dmntchtGetCheatById(DmntCheatEntry *out, u32 cheat_id) { + return serviceDispatchIn(&g_dmntchtSrv, 65202, cheat_id, + .buffer_attrs = { SfBufferAttr_Out | SfBufferAttr_HipcMapAlias | SfBufferAttr_FixedSize }, + .buffers = { { out, sizeof(*out) } }, + ); +} + +Result dmntchtToggleCheat(u32 cheat_id) { + return _dmntchtCmdInU32NoOut(cheat_id, 65203); +} + +Result dmntchtAddCheat(DmntCheatDefinition *cheat_def, bool enabled, u32 *out_cheat_id) { + const u8 in = enabled != 0; + return serviceDispatchInOut(&g_dmntchtSrv, 65204, in, *out_cheat_id, + .buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcMapAlias | SfBufferAttr_FixedSize }, + .buffers = { { cheat_def, sizeof(*cheat_def) } }, + ); +} + +Result dmntchtRemoveCheat(u32 cheat_id) { + return _dmntchtCmdInU32NoOut(cheat_id, 65205); +} + +Result dmntchtReadStaticRegister(u64 *out, u8 which) { + return serviceDispatchInOut(&g_dmntchtSrv, 65206, which, *out); +} + +Result dmntchtWriteStaticRegister(u8 which, u64 value) { + const struct { + u64 which; + u64 value; + } in = { which, value }; + + return serviceDispatchIn(&g_dmntchtSrv, 65207, in); +} + +Result dmntchtResetStaticRegisters() { + return _dmntchtCmdVoid(&g_dmntchtSrv, 65208); +} + +Result dmntchtSetMasterCheat(DmntCheatDefinition *cheat_def) { + return serviceDispatch(&g_dmntchtSrv, 65209, + .buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcMapAlias | SfBufferAttr_FixedSize }, + .buffers = { { cheat_def, sizeof(*cheat_def) } }, + ); +} + +Result dmntchtGetFrozenAddressCount(u64 *out_count) { + return _dmntchtGetCount(out_count, 65300); +} + +Result dmntchtGetFrozenAddresses(DmntFrozenAddressEntry *buffer, u64 max_count, u64 offset, u64 *out_count) { + return _dmntchtGetEntries(buffer, sizeof(*buffer) * max_count, offset, out_count, 65301); +} + +Result dmntchtGetFrozenAddress(DmntFrozenAddressEntry *out, u64 address) { + return serviceDispatchInOut(&g_dmntchtSrv, 65302, address, *out); +} + +Result dmntchtEnableFrozenAddress(u64 address, u64 width, u64 *out_value) { + const struct { + u64 address; + u64 width; + } in = { address, width }; + return serviceDispatchInOut(&g_dmntchtSrv, 65303, in, *out_value); +} + +Result dmntchtDisableFrozenAddress(u64 address) { + return serviceDispatchIn(&g_dmntchtSrv, 65304, address); +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_allocator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_allocator.hpp new file mode 100644 index 00000000..063ec480 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_allocator.hpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::erpt::srv { + + extern lmem::HeapHandle g_heap_handle; + + class Allocator { + public: + void *operator new(size_t sz) noexcept { return lmem::AllocateFromExpHeap(g_heap_handle, sz); } + void *operator new(size_t sz, size_t algn) noexcept { return lmem::AllocateFromExpHeap(g_heap_handle, sz, static_cast<s32>(algn)); } + void *operator new[](size_t sz) noexcept { return lmem::AllocateFromExpHeap(g_heap_handle, sz); } + void *operator new[](size_t sz, size_t algn) noexcept { return lmem::AllocateFromExpHeap(g_heap_handle, sz, static_cast<s32>(algn)); } + + void operator delete(void *p) noexcept { lmem::FreeToExpHeap(g_heap_handle, p); } + void operator delete[](void *p) noexcept { lmem::FreeToExpHeap(g_heap_handle, p); } + }; + + inline void *Allocate(size_t sz) { + return lmem::AllocateFromExpHeap(g_heap_handle, sz); + } + + inline void *AllocateWithAlign(size_t sz, size_t align) { + return lmem::AllocateFromExpHeap(g_heap_handle, sz, align); + } + + inline void Deallocate(void *p) { + return lmem::FreeToExpHeap(g_heap_handle, p); + } + + inline void DeallocateWithSize(void *p, size_t size) { + AMS_UNUSED(size); + + return lmem::FreeToExpHeap(g_heap_handle, p); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_attachment.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_attachment.cpp new file mode 100644 index 00000000..93685480 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_attachment.cpp @@ -0,0 +1,84 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_attachment_impl.hpp" +#include "erpt_srv_attachment.hpp" + +namespace ams::erpt::srv { + + AttachmentFileName Attachment::FileName(AttachmentId attachment_id) { + char uuid_str[AttachmentFileNameLength]; + attachment_id.uuid.ToString(uuid_str, sizeof(uuid_str)); + + AttachmentFileName attachment_name; + util::SNPrintf(attachment_name.name, sizeof(attachment_name.name), "%s:/%s.att", ReportStoragePath, uuid_str); + return attachment_name; + } + + Attachment::Attachment(JournalRecord<AttachmentInfo> *r) : m_record(r) { + m_record->AddReference(); + } + + Attachment::~Attachment() { + this->CloseStream(); + if (m_record->RemoveReference()) { + this->DeleteStream(this->FileName().name); + delete m_record; + } + } + + AttachmentFileName Attachment::FileName() const { + return FileName(m_record->m_info.attachment_id); + } + + Result Attachment::Open(AttachmentOpenType type) { + switch (type) { + case AttachmentOpenType_Create: R_RETURN(this->OpenStream(this->FileName().name, StreamMode_Write, AttachmentStreamBufferSize)); + case AttachmentOpenType_Read: R_RETURN(this->OpenStream(this->FileName().name, StreamMode_Read, AttachmentStreamBufferSize)); + default: R_THROW(erpt::ResultInvalidArgument()); + } + } + + Result Attachment::Read(u32 *out_read_count, u8 *dst, u32 dst_size) { + R_RETURN(this->ReadStream(out_read_count, dst, dst_size)); + } + + Result Attachment::Delete() { + R_RETURN(this->DeleteStream(this->FileName().name)); + } + + void Attachment::Close() { + return this->CloseStream(); + } + + Result Attachment::GetFlags(AttachmentFlagSet *out) const { + *out = m_record->m_info.flags; + R_SUCCEED(); + } + + Result Attachment::SetFlags(AttachmentFlagSet flags) { + if (((~m_record->m_info.flags) & flags).IsAnySet()) { + m_record->m_info.flags |= flags; + R_RETURN(Journal::Commit()); + } + R_SUCCEED(); + } + + Result Attachment::GetSize(s64 *out) const { + R_RETURN(this->GetStreamSize(out)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_attachment.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_attachment.hpp new file mode 100644 index 00000000..a80b74cc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_attachment.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "erpt_srv_allocator.hpp" +#include "erpt_srv_stream.hpp" +#include "erpt_srv_journal.hpp" + +namespace ams::erpt::srv { + + enum AttachmentOpenType { + AttachmentOpenType_Create = 0, + AttachmentOpenType_Read = 1, + }; + + constexpr inline u32 AttachmentStreamBufferSize = 1_KB; + + class Attachment : public Allocator, public Stream { + private: + JournalRecord<AttachmentInfo> *m_record; + private: + AttachmentFileName FileName() const; + public: + static AttachmentFileName FileName(AttachmentId attachment_id); + public: + explicit Attachment(JournalRecord<AttachmentInfo> *r); + ~Attachment(); + + Result Open(AttachmentOpenType type); + Result Read(u32 *out_read_count, u8 *dst, u32 dst_size); + Result Delete(); + void Close(); + + Result GetFlags(AttachmentFlagSet *out) const; + Result SetFlags(AttachmentFlagSet flags); + Result GetSize(s64 *out) const; + + template<typename T> + Result Write(T val) { + R_RETURN(this->WriteStream(reinterpret_cast<const u8 *>(std::addressof(val)), sizeof(val))); + } + + template<typename T> + Result Write(const T *buf, u32 buffer_size) { + R_RETURN(this->WriteStream(reinterpret_cast<const u8 *>(buf), buffer_size)); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_attachment_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_attachment_impl.cpp new file mode 100644 index 00000000..1d9083e8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_attachment_impl.cpp @@ -0,0 +1,79 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_attachment_impl.hpp" +#include "erpt_srv_attachment.hpp" + +namespace ams::erpt::srv { + + AttachmentImpl::AttachmentImpl() : m_attachment(nullptr) { + /* ... */ + } + + AttachmentImpl::~AttachmentImpl() { + R_ABORT_UNLESS(this->Close()); + } + + Result AttachmentImpl::Open(const AttachmentId &attachment_id) { + R_UNLESS(m_attachment == nullptr, erpt::ResultAlreadyInitialized()); + + JournalRecord<AttachmentInfo> *record = Journal::Retrieve(attachment_id); + R_UNLESS(record != nullptr, erpt::ResultNotFound()); + + m_attachment = new Attachment(record); + R_UNLESS(m_attachment != nullptr, erpt::ResultOutOfMemory()); + auto attachment_guard = SCOPE_GUARD { delete m_attachment; m_attachment = nullptr; }; + + R_TRY(m_attachment->Open(AttachmentOpenType_Read)); + attachment_guard.Cancel(); + + R_SUCCEED(); + } + + Result AttachmentImpl::Read(ams::sf::Out<u32> out_count, const ams::sf::OutBuffer &out_buffer) { + R_UNLESS(m_attachment != nullptr, erpt::ResultNotInitialized()); + + R_RETURN(m_attachment->Read(out_count.GetPointer(), static_cast<u8 *>(out_buffer.GetPointer()), static_cast<u32>(out_buffer.GetSize()))); + } + + Result AttachmentImpl::SetFlags(AttachmentFlagSet flags) { + R_UNLESS(m_attachment != nullptr, erpt::ResultNotInitialized()); + + R_RETURN(m_attachment->SetFlags(flags)); + } + + Result AttachmentImpl::GetFlags(ams::sf::Out<AttachmentFlagSet> out) { + R_UNLESS(m_attachment != nullptr, erpt::ResultNotInitialized()); + + R_RETURN(m_attachment->GetFlags(out.GetPointer())); + } + + Result AttachmentImpl::Close() { + if (m_attachment != nullptr) { + m_attachment->Close(); + delete m_attachment; + m_attachment = nullptr; + } + R_SUCCEED(); + } + + Result AttachmentImpl::GetSize(ams::sf::Out<s64> out) { + R_UNLESS(m_attachment != nullptr, erpt::ResultNotInitialized()); + + R_RETURN(m_attachment->GetSize(out.GetPointer())); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_attachment_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_attachment_impl.hpp new file mode 100644 index 00000000..63e14f48 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_attachment_impl.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::erpt::srv { + + class Attachment; + + class AttachmentImpl { + private: + Attachment *m_attachment; + public: + AttachmentImpl(); + ~AttachmentImpl(); + public: + Result Open(const AttachmentId &attachment_id); + Result Read(ams::sf::Out<u32> out_count, const ams::sf::OutBuffer &out_buffer); + Result SetFlags(AttachmentFlagSet flags); + Result GetFlags(ams::sf::Out<AttachmentFlagSet> out); + Result Close(); + Result GetSize(ams::sf::Out<s64> out); + }; + static_assert(erpt::sf::IsIAttachment<AttachmentImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_cipher.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_cipher.cpp new file mode 100644 index 00000000..1bcd34bc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_cipher.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_cipher.hpp" + +namespace ams::erpt::srv { + + constinit u8 Cipher::s_key[crypto::Aes128CtrEncryptor::KeySize + crypto::Aes128CtrEncryptor::IvSize + crypto::Aes128CtrEncryptor::BlockSize]; + constinit bool Cipher::s_need_to_store_cipher = false; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_cipher.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_cipher.hpp new file mode 100644 index 00000000..a71c3c6f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_cipher.hpp @@ -0,0 +1,125 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "erpt_srv_formatter.hpp" +#include "erpt_srv_keys.hpp" + +namespace ams::erpt::srv { + + class Cipher : private Formatter { + private: + static constexpr u32 RsaKeySize = 0x100; + static constexpr u32 SaltSize = 0x20; + + static u8 s_key[crypto::Aes128CtrEncryptor::KeySize + crypto::Aes128CtrEncryptor::IvSize + crypto::Aes128CtrEncryptor::BlockSize]; + static bool s_need_to_store_cipher; + + struct Header { + u32 magic; + u32 field_type; + u32 element_count; + u32 reserved; + u8 data[0]; + }; + static_assert(sizeof(Header) == 0x10); + + static constexpr u32 HeaderMagic = util::FourCC<'C', 'R', 'P', 'T'>::Code; + private: + template<typename T> + static Result EncryptArray(Report *report, FieldId field_id, T *arr, u32 arr_size) { + const u32 data_size = util::AlignUp(arr_size * sizeof(T), crypto::Aes128CtrEncryptor::BlockSize); + + Header *hdr = reinterpret_cast<Header *>(AllocateWithAlign(sizeof(Header) + data_size, crypto::Aes128CtrEncryptor::BlockSize)); + R_UNLESS(hdr != nullptr, erpt::ResultOutOfMemory()); + ON_SCOPE_EXIT { Deallocate(hdr); }; + + hdr->magic = HeaderMagic; + hdr->field_type = static_cast<u32>(ConvertFieldToType(field_id)); + hdr->element_count = arr_size; + hdr->reserved = 0; + + std::memset(hdr->data, 0, data_size); + std::memcpy(hdr->data, arr, arr_size * sizeof(T)); + + crypto::EncryptAes128Ctr(hdr->data, data_size, s_key, crypto::Aes128CtrEncryptor::KeySize, s_key + crypto::Aes128CtrEncryptor::KeySize, crypto::Aes128CtrEncryptor::IvSize, hdr->data, data_size); + + ON_SCOPE_EXIT { std::memset(hdr, 0, sizeof(hdr) + data_size); s_need_to_store_cipher = true; }; + + R_RETURN(Formatter::AddField(report, field_id, reinterpret_cast<u8 *>(hdr), sizeof(hdr) + data_size)); + } + public: + static Result Begin(Report *report, u32 record_count) { + s_need_to_store_cipher = false; + crypto::GenerateCryptographicallyRandomBytes(s_key, sizeof(s_key)); + + R_RETURN(Formatter::Begin(report, record_count + 1)); + } + + static Result End(Report *report) { + u8 cipher[RsaKeySize] = {}; + + if (s_need_to_store_cipher) { + u8 salt[SaltSize]; + crypto::RsaOaepEncryptor<RsaKeySize, crypto::Sha256Generator> oaep; + crypto::GenerateCryptographicallyRandomBytes(salt, sizeof(salt)); + + oaep.Initialize(GetPublicKeyModulus(), GetPublicKeyModulusSize(), GetPublicKeyExponent(), GetPublicKeyExponentSize()); + oaep.Encrypt(cipher, sizeof(cipher), s_key, sizeof(s_key), salt, sizeof(salt)); + } + + Formatter::AddField(report, FieldId_CipherKey, cipher, sizeof(cipher)); + std::memset(s_key, 0, sizeof(s_key)); + + R_RETURN(Formatter::End(report)); + } + + static Result AddField(Report *report, FieldId field_id, bool value) { + R_RETURN(Formatter::AddField(report, field_id, value)); + } + + template<typename T> + static Result AddField(Report *report, FieldId field_id, T value) { + R_RETURN(Formatter::AddField<T>(report, field_id, value)); + } + + static Result AddField(Report *report, FieldId field_id, char *str, u32 len) { + if (ConvertFieldToFlag(field_id) == FieldFlag_Encrypt) { + R_RETURN(EncryptArray<char>(report, field_id, str, len)); + } else { + R_RETURN(Formatter::AddField(report, field_id, str, len)); + } + } + + static Result AddField(Report *report, FieldId field_id, u8 *bin, u32 len) { + if (ConvertFieldToFlag(field_id) == FieldFlag_Encrypt) { + R_RETURN(EncryptArray<u8>(report, field_id, bin, len)); + } else { + R_RETURN(Formatter::AddField(report, field_id, bin, len)); + } + } + + template<typename T> + static Result AddField(Report *report, FieldId field_id, T *arr, u32 len) { + if (ConvertFieldToFlag(field_id) == FieldFlag_Encrypt) { + R_RETURN(EncryptArray<T>(report, field_id, arr, len)); + } else { + R_RETURN(Formatter::AddField<T>(report, field_id, arr, len)); + } + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_context.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_context.cpp new file mode 100644 index 00000000..2d4c021a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_context.cpp @@ -0,0 +1,114 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_context.hpp" +#include "erpt_srv_cipher.hpp" +#include "erpt_srv_context_record.hpp" +#include "erpt_srv_report.hpp" + +namespace ams::erpt::srv { + + namespace { + + using ContextList = util::IntrusiveListBaseTraits<Context>::ListType; + + constinit ContextList g_category_list; + + } + + Context::Context(CategoryId cat) : m_category(cat) { + g_category_list.push_front(*this); + } + + Context::~Context() { + g_category_list.erase(g_category_list.iterator_to(*this)); + } + + Result Context::AddCategoryToReport(Report *report) { + if (m_record != nullptr) { + const auto *entry = m_record->GetContextEntryPtr(); + for (u32 i = 0; i < entry->field_count; i++) { + auto *field = std::addressof(entry->fields[i]); + u8 *arr_buf = entry->array_buffer; + + switch (field->type) { + case FieldType_Bool: R_TRY(Cipher::AddField(report, field->id, field->value_bool)); break; + case FieldType_NumericU8: R_TRY(Cipher::AddField(report, field->id, field->value_u8)); break; + case FieldType_NumericU16: R_TRY(Cipher::AddField(report, field->id, field->value_u16)); break; + case FieldType_NumericU32: R_TRY(Cipher::AddField(report, field->id, field->value_u32)); break; + case FieldType_NumericU64: R_TRY(Cipher::AddField(report, field->id, field->value_u64)); break; + case FieldType_NumericI8: R_TRY(Cipher::AddField(report, field->id, field->value_i8)); break; + case FieldType_NumericI16: R_TRY(Cipher::AddField(report, field->id, field->value_i16)); break; + case FieldType_NumericI32: R_TRY(Cipher::AddField(report, field->id, field->value_i32)); break; + case FieldType_NumericI64: R_TRY(Cipher::AddField(report, field->id, field->value_i64)); break; + case FieldType_String: R_TRY(Cipher::AddField(report, field->id, reinterpret_cast<char *>(arr_buf + field->value_array.start_idx), field->value_array.size / sizeof(char))); break; + case FieldType_U8Array: R_TRY(Cipher::AddField(report, field->id, reinterpret_cast< u8 *>(arr_buf + field->value_array.start_idx), field->value_array.size / sizeof(u8))); break; + case FieldType_U32Array: R_TRY(Cipher::AddField(report, field->id, reinterpret_cast< u32 *>(arr_buf + field->value_array.start_idx), field->value_array.size / sizeof(u32))); break; + case FieldType_U64Array: R_TRY(Cipher::AddField(report, field->id, reinterpret_cast< u64 *>(arr_buf + field->value_array.start_idx), field->value_array.size / sizeof(u64))); break; + case FieldType_I8Array: R_TRY(Cipher::AddField(report, field->id, reinterpret_cast< s8 *>(arr_buf + field->value_array.start_idx), field->value_array.size / sizeof(s8))); break; + case FieldType_I32Array: R_TRY(Cipher::AddField(report, field->id, reinterpret_cast< s32 *>(arr_buf + field->value_array.start_idx), field->value_array.size / sizeof(s32))); break; + case FieldType_I64Array: R_TRY(Cipher::AddField(report, field->id, reinterpret_cast< s64 *>(arr_buf + field->value_array.start_idx), field->value_array.size / sizeof(s64))); break; + default: R_THROW(erpt::ResultInvalidArgument()); + } + } + } + + R_SUCCEED(); + } + + Result Context::SubmitContext(const ContextEntry *entry, const u8 *data, u32 data_size) { + auto record = std::make_unique<ContextRecord>(); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + R_TRY(record->Initialize(entry, data, data_size)); + + R_RETURN(SubmitContextRecord(std::move(record))); + } + + Result Context::SubmitContextRecord(std::unique_ptr<ContextRecord> record) { + auto it = util::range::find_if(g_category_list, [&](const Context &cur) { + return cur.m_category == record->GetContextEntryPtr()->category; + }); + R_UNLESS(it != g_category_list.end(), erpt::ResultCategoryNotFound()); + + it->m_record = std::move(record); + R_SUCCEED(); + } + + Result Context::WriteContextsToReport(Report *report) { + R_TRY(report->Open(ReportOpenType_Create)); + R_TRY(Cipher::Begin(report, ContextRecord::GetRecordCount())); + + for (auto it = g_category_list.begin(); it != g_category_list.end(); it++) { + R_TRY(it->AddCategoryToReport(report)); + } + + Cipher::End(report); + report->Close(); + + R_SUCCEED(); + } + + Result Context::ClearContext(CategoryId cat) { + /* Make an empty record for the category. */ + auto record = std::make_unique<ContextRecord>(cat); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + /* Submit the context record. */ + R_RETURN(SubmitContextRecord(std::move(record))); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_context.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_context.hpp new file mode 100644 index 00000000..fb7639e8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_context.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "erpt_srv_allocator.hpp" +#include "erpt_srv_cipher.hpp" + +namespace ams::erpt::srv { + + class ContextRecord; + class Report; + + class Context : public Allocator, public util::IntrusiveListBaseNode<Context> { + private: + const CategoryId m_category; + std::unique_ptr<ContextRecord> m_record; + public: + Context(CategoryId cat); + ~Context(); + + Result AddCategoryToReport(Report *report); + public: + static Result SubmitContext(const ContextEntry *entry, const u8 *data, u32 data_size); + static Result SubmitContextRecord(std::unique_ptr<ContextRecord> record); + static Result WriteContextsToReport(Report *report); + static Result ClearContext(CategoryId cat); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_context_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_context_impl.cpp new file mode 100644 index 00000000..77a39a5c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_context_impl.cpp @@ -0,0 +1,218 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_context_impl.hpp" +#include "erpt_srv_manager_impl.hpp" +#include "erpt_srv_context.hpp" +#include "erpt_srv_reporter.hpp" +#include "erpt_srv_journal.hpp" +#include "erpt_srv_forced_shutdown.hpp" + +namespace ams::erpt::srv { + + Result ContextImpl::SubmitContext(const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer) { + const ContextEntry *ctx = reinterpret_cast<const ContextEntry *>( ctx_buffer.GetPointer()); + const u8 *data = reinterpret_cast<const u8 *>(data_buffer.GetPointer()); + + const u32 ctx_size = static_cast<u32>(ctx_buffer.GetSize()); + const u32 data_size = static_cast<u32>(data_buffer.GetSize()); + + R_UNLESS(ctx_size == sizeof(ContextEntry), erpt::ResultInvalidArgument()); + R_UNLESS(data_size <= ArrayBufferSizeMax, erpt::ResultInvalidArgument()); + + SubmitContextForForcedShutdownDetection(ctx, data, data_size); + + R_RETURN(Context::SubmitContext(ctx, data, data_size)); + } + + Result ContextImpl::CreateReport(ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer, const ams::sf::InBuffer &meta_buffer, Result result, erpt::CreateReportOptionFlagSet flags) { + const ContextEntry *ctx = reinterpret_cast<const ContextEntry *>( ctx_buffer.GetPointer()); + const u8 *data = reinterpret_cast<const u8 *>(data_buffer.GetPointer()); + const ReportMetaData *meta = reinterpret_cast<const ReportMetaData *>(meta_buffer.GetPointer()); + + const u32 ctx_size = static_cast<u32>(ctx_buffer.GetSize()); + const u32 data_size = static_cast<u32>(data_buffer.GetSize()); + const u32 meta_size = static_cast<u32>(meta_buffer.GetSize()); + + R_UNLESS(ctx_size == sizeof(ContextEntry), erpt::ResultInvalidArgument()); + R_UNLESS(meta_size == 0 || meta_size == sizeof(ReportMetaData), erpt::ResultInvalidArgument()); + + R_TRY(Reporter::CreateReport(report_type, result, ctx, data, data_size, meta_size != 0 ? meta : nullptr, nullptr, 0, flags, nullptr)); + + ManagerImpl::NotifyAll(); + + R_SUCCEED(); + } + + Result ContextImpl::CreateReportV1(ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer, const ams::sf::InBuffer &meta_buffer, Result result) { + R_RETURN(this->CreateReport(report_type, ctx_buffer, data_buffer, meta_buffer, result, erpt::srv::MakeNoCreateReportOptionFlags())); + } + + Result ContextImpl::CreateReportV0(ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer, const ams::sf::InBuffer &meta_buffer) { + R_RETURN(this->CreateReportV1(report_type, ctx_buffer, data_buffer, meta_buffer, ResultSuccess())); + } + + Result ContextImpl::SetInitialLaunchSettingsCompletionTime(const time::SteadyClockTimePoint &time_point) { + Reporter::SetInitialLaunchSettingsCompletionTime(time_point); + R_SUCCEED(); + } + + Result ContextImpl::ClearInitialLaunchSettingsCompletionTime() { + Reporter::ClearInitialLaunchSettingsCompletionTime(); + R_SUCCEED(); + } + + Result ContextImpl::UpdatePowerOnTime() { + /* NOTE: Prior to 12.0.0, this set the power on time, but now erpt does it during initialization. */ + R_SUCCEED(); + } + + Result ContextImpl::UpdateAwakeTime() { + /* NOTE: Prior to 12.0.0, this set the power on time, but now erpt does it during initialization. */ + R_SUCCEED(); + } + + Result ContextImpl::SubmitMultipleCategoryContext(const MultipleCategoryContextEntry &ctx_entry, const ams::sf::InBuffer &str_buffer) { + R_UNLESS(ctx_entry.category_count <= CategoriesPerMultipleCategoryContext, erpt::ResultInvalidArgument()); + + const u8 *str = reinterpret_cast<const u8 *>(str_buffer.GetPointer()); + const u32 str_size = static_cast<u32>(str_buffer.GetSize()); + + u32 total_field_count = 0, total_arr_count = 0; + for (u32 i = 0; i < ctx_entry.category_count; i++) { + ContextEntry entry = { + .version = ctx_entry.version, + .field_count = ctx_entry.field_counts[i], + .category = ctx_entry.categories[i], + }; + R_UNLESS(entry.field_count <= erpt::FieldsPerContext, erpt::ResultInvalidArgument()); + R_UNLESS(entry.field_count + total_field_count <= erpt::FieldsPerMultipleCategoryContext, erpt::ResultInvalidArgument()); + R_UNLESS(ctx_entry.array_buf_counts[i] <= ArrayBufferSizeMax, erpt::ResultInvalidArgument()); + R_UNLESS(ctx_entry.array_buf_counts[i] + total_arr_count <= str_size, erpt::ResultInvalidArgument()); + + std::memcpy(entry.fields, ctx_entry.fields + total_field_count, entry.field_count * sizeof(FieldEntry)); + + R_TRY(Context::SubmitContext(std::addressof(entry), str + total_arr_count, ctx_entry.array_buf_counts[i])); + + total_field_count += entry.field_count; + total_arr_count += ctx_entry.array_buf_counts[i]; + } + + R_SUCCEED(); + } + + Result ContextImpl::UpdateApplicationLaunchTime() { + Reporter::UpdateApplicationLaunchTime(); + R_SUCCEED(); + } + + Result ContextImpl::ClearApplicationLaunchTime() { + Reporter::ClearApplicationLaunchTime(); + R_SUCCEED(); + } + + Result ContextImpl::SubmitAttachment(ams::sf::Out<AttachmentId> out, const ams::sf::InBuffer &attachment_name, const ams::sf::InBuffer &attachment_data) { + const char *name = reinterpret_cast<const char *>(attachment_name.GetPointer()); + const u8 *data = reinterpret_cast<const u8 *>(attachment_data.GetPointer()); + + const u32 name_size = static_cast<u32>(attachment_name.GetSize()); + const u32 data_size = static_cast<u32>(attachment_data.GetSize()); + + R_UNLESS(data != nullptr, erpt::ResultInvalidArgument()); + R_UNLESS(data_size <= AttachmentSizeMax, erpt::ResultInvalidArgument()); + R_UNLESS(name != nullptr, erpt::ResultInvalidArgument()); + R_UNLESS(name_size <= AttachmentNameSizeMax, erpt::ResultInvalidArgument()); + + char name_safe[AttachmentNameSizeMax]; + util::Strlcpy(name_safe, name, sizeof(name_safe)); + + R_RETURN(JournalForAttachments::SubmitAttachment(out.GetPointer(), name_safe, data, data_size)); + } + + Result ContextImpl::SubmitAttachmentWithLz4Compression(ams::sf::Out<AttachmentId> out, const ams::sf::InBuffer &attachment_name, const ams::sf::InBuffer &attachment_data) { + /* TODO: Implement LZ4 compression on attachments. */ + R_RETURN(this->SubmitAttachment(out, attachment_name, attachment_data)); + } + + Result ContextImpl::CreateReportWithAttachments(ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer, const ams::sf::InBuffer &attachment_ids_buffer, Result result, erpt::CreateReportOptionFlagSet flags) { + const ContextEntry *ctx = reinterpret_cast<const ContextEntry *>( ctx_buffer.GetPointer()); + const u8 *data = reinterpret_cast<const u8 *>(data_buffer.GetPointer()); + const u32 ctx_size = static_cast<u32>(ctx_buffer.GetSize()); + const u32 data_size = static_cast<u32>(data_buffer.GetSize()); + + const AttachmentId *attachments = reinterpret_cast<const AttachmentId *>(attachment_ids_buffer.GetPointer()); + const u32 num_attachments = attachment_ids_buffer.GetSize() / sizeof(*attachments); + + R_UNLESS(ctx_size == sizeof(ContextEntry), erpt::ResultInvalidArgument()); + R_UNLESS(num_attachments <= AttachmentsPerReportMax, erpt::ResultInvalidArgument()); + + R_TRY(Reporter::CreateReport(report_type, result, ctx, data, data_size, nullptr, attachments, num_attachments, flags, nullptr)); + + ManagerImpl::NotifyAll(); + + R_SUCCEED(); + } + + Result ContextImpl::CreateReportWithAttachmentsDeprecated2(ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer, const ams::sf::InBuffer &attachment_ids_buffer, Result result) { + R_RETURN(this->CreateReportWithAttachments(report_type, ctx_buffer, data_buffer, attachment_ids_buffer, result, erpt::srv::MakeNoCreateReportOptionFlags())); + } + + Result ContextImpl::CreateReportWithAttachmentsDeprecated(ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer, const ams::sf::InBuffer &attachment_ids_buffer) { + R_RETURN(this->CreateReportWithAttachmentsDeprecated2(report_type, ctx_buffer, data_buffer, attachment_ids_buffer, ResultSuccess())); + } + + Result ContextImpl::CreateReportWithSpecifiedReprotId(ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer, const ams::sf::InBuffer &meta_buffer, const ams::sf::InBuffer &attachment_ids_buffer, Result result, erpt::CreateReportOptionFlagSet flags, const ReportId &report_id) { + const ContextEntry *ctx = reinterpret_cast<const ContextEntry *>( ctx_buffer.GetPointer()); + const u8 *data = reinterpret_cast<const u8 *>(data_buffer.GetPointer()); + const ReportMetaData *meta = reinterpret_cast<const ReportMetaData *>(meta_buffer.GetPointer()); + + const u32 ctx_size = static_cast<u32>(ctx_buffer.GetSize()); + const u32 data_size = static_cast<u32>(data_buffer.GetSize()); + const u32 meta_size = static_cast<u32>(meta_buffer.GetSize()); + + const AttachmentId *attachments = reinterpret_cast<const AttachmentId *>(attachment_ids_buffer.GetPointer()); + const u32 num_attachments = attachment_ids_buffer.GetSize() / sizeof(*attachments); + + R_UNLESS(ctx_size == sizeof(ContextEntry), erpt::ResultInvalidArgument()); + R_UNLESS(meta_size == 0 || meta_size == sizeof(ReportMetaData), erpt::ResultInvalidArgument()); + R_UNLESS(num_attachments <= AttachmentsPerReportMax, erpt::ResultInvalidArgument()); + + R_TRY(Reporter::CreateReport(report_type, result, ctx, data, data_size, meta_size != 0 ? meta : nullptr, attachments, num_attachments, flags, std::addressof(report_id))); + + ManagerImpl::NotifyAll(); + + R_SUCCEED(); + } + + Result ContextImpl::RegisterRunningApplet(ncm::ProgramId program_id) { + R_RETURN(Reporter::RegisterRunningApplet(program_id)); + } + + Result ContextImpl::UnregisterRunningApplet(ncm::ProgramId program_id) { + R_RETURN(Reporter::UnregisterRunningApplet(program_id)); + } + + Result ContextImpl::UpdateAppletSuspendedDuration(ncm::ProgramId program_id, TimeSpanType duration) { + R_RETURN(Reporter::UpdateAppletSuspendedDuration(program_id, duration)); + } + + Result ContextImpl::InvalidateForcedShutdownDetection() { + /* NOTE: Nintendo does not check the result here. */ + erpt::srv::InvalidateForcedShutdownDetection(); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_context_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_context_impl.hpp new file mode 100644 index 00000000..cada2ceb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_context_impl.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::erpt::srv { + + class ContextImpl { + public: + Result SubmitContext(const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer); + Result CreateReportV0(ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer, const ams::sf::InBuffer &meta_buffer); + Result SetInitialLaunchSettingsCompletionTime(const time::SteadyClockTimePoint &time_point); + Result ClearInitialLaunchSettingsCompletionTime(); + Result UpdatePowerOnTime(); + Result UpdateAwakeTime(); + Result SubmitMultipleCategoryContext(const MultipleCategoryContextEntry &ctx_entry, const ams::sf::InBuffer &str_buffer); + Result UpdateApplicationLaunchTime(); + Result ClearApplicationLaunchTime(); + Result SubmitAttachment(ams::sf::Out<AttachmentId> out, const ams::sf::InBuffer &attachment_name, const ams::sf::InBuffer &attachment_data); + Result CreateReportWithAttachmentsDeprecated(ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer, const ams::sf::InBuffer &attachment_ids_buffer); + Result CreateReportWithAttachmentsDeprecated2(ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer, const ams::sf::InBuffer &attachment_ids_buffer, Result result); + Result CreateReportWithAttachments(ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer, const ams::sf::InBuffer &attachment_ids_buffer, Result result, erpt::CreateReportOptionFlagSet flags); + Result CreateReportV1(ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer, const ams::sf::InBuffer &meta_buffer, Result result); + Result CreateReport(ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer, const ams::sf::InBuffer &meta_buffer, Result result, erpt::CreateReportOptionFlagSet flags); + Result SubmitAttachmentWithLz4Compression(ams::sf::Out<AttachmentId> out, const ams::sf::InBuffer &attachment_name, const ams::sf::InBuffer &attachment_data); + Result CreateReportWithSpecifiedReprotId(ReportType report_type, const ams::sf::InBuffer &ctx_buffer, const ams::sf::InBuffer &data_buffer, const ams::sf::InBuffer &meta_buffer, const ams::sf::InBuffer &attachment_data, Result result, erpt::CreateReportOptionFlagSet flags, const ReportId &report_id); + Result RegisterRunningApplet(ncm::ProgramId program_id); + Result UnregisterRunningApplet(ncm::ProgramId program_id); + Result UpdateAppletSuspendedDuration(ncm::ProgramId program_id, TimeSpanType duration); + Result InvalidateForcedShutdownDetection(); + }; + static_assert(erpt::sf::IsIContext<ContextImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_context_record.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_context_record.cpp new file mode 100644 index 00000000..23dde0b3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_context_record.cpp @@ -0,0 +1,212 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_context_record.hpp" + +namespace ams::erpt::srv { + + constinit u32 ContextRecord::s_record_count = 0; + + namespace { + + bool IsArrayFieldType(FieldType type) { + return type == FieldType_String || + type == FieldType_U8Array || + type == FieldType_U32Array || + type == FieldType_U64Array || + type == FieldType_I32Array || + type == FieldType_I64Array; + } + + } + + ContextRecord::ContextRecord() { + m_ctx = {}; + } + + ContextRecord::ContextRecord(CategoryId category, u32 array_buf_size) { + m_ctx = { + .category = category, + .array_buffer = static_cast<u8 *>(Allocate(array_buf_size)), + }; + if (m_ctx.array_buffer != nullptr) { + m_ctx.array_buffer_size = array_buf_size; + m_ctx.array_free_count = array_buf_size; + } + } + + ContextRecord::~ContextRecord() { + if (m_ctx.array_buffer != nullptr) { + Deallocate(m_ctx.array_buffer); + } + + AMS_ABORT_UNLESS(s_record_count >= m_ctx.field_count); + s_record_count -= m_ctx.field_count; + } + + Result ContextRecord::Initialize(const ContextEntry *ctx_ptr, const u8 *data, u32 data_size) { + R_UNLESS(data_size <= ArrayBufferSizeMax, erpt::ResultInvalidArgument()); + + m_ctx.version = ctx_ptr->version; + m_ctx.field_count = ctx_ptr->field_count; + m_ctx.category = ctx_ptr->category; + m_ctx.array_buffer = nullptr; + m_ctx.array_buffer_size = data_size; + m_ctx.array_free_count = 0; + + auto guard = SCOPE_GUARD { m_ctx.field_count = 0; }; + + R_UNLESS(m_ctx.field_count <= FieldsPerContext, erpt::ResultInvalidArgument()); + R_UNLESS(FindCategoryIndex(m_ctx.category).has_value(), erpt::ResultInvalidArgument()); + + for (u32 i = 0; i < m_ctx.field_count; i++) { + m_ctx.fields[i] = ctx_ptr->fields[i]; + + R_UNLESS(FindFieldIndex(m_ctx.fields[i].id).has_value(), erpt::ResultInvalidArgument()); + R_UNLESS(0 <= m_ctx.fields[i].type && m_ctx.fields[i].type < FieldType_Count, erpt::ResultInvalidArgument()); + + R_UNLESS(m_ctx.fields[i].type == ConvertFieldToType(m_ctx.fields[i].id), erpt::ResultFieldTypeMismatch()); + R_UNLESS(m_ctx.category == ConvertFieldToCategory(m_ctx.fields[i].id), erpt::ResultFieldCategoryMismatch()); + + if (IsArrayFieldType(m_ctx.fields[i].type)) { + const u32 start_idx = m_ctx.fields[i].value_array.start_idx; + const u32 size = m_ctx.fields[i].value_array.size; + const u32 end_idx = start_idx + size; + + R_UNLESS(start_idx <= data_size, erpt::ResultInvalidArgument()); + R_UNLESS(size <= data_size, erpt::ResultInvalidArgument()); + R_UNLESS(end_idx <= data_size, erpt::ResultInvalidArgument()); + R_UNLESS(size <= ArrayFieldSizeMax, erpt::ResultInvalidArgument()); + } + } + + if (data_size > 0) { + /* If array buffer isn't nullptr, we'll leak memory here, so verify that it is. */ + AMS_ABORT_UNLESS(m_ctx.array_buffer == nullptr); + + m_ctx.array_buffer = static_cast<u8 *>(AllocateWithAlign(data_size, alignof(u64))); + R_UNLESS(m_ctx.array_buffer != nullptr, erpt::ResultOutOfMemory()); + + std::memcpy(m_ctx.array_buffer, data, data_size); + } + + guard.Cancel(); + s_record_count += m_ctx.field_count; + + R_SUCCEED(); + } + + Result ContextRecord::Add(FieldId field_id, bool value_bool) { + R_UNLESS(m_ctx.field_count < FieldsPerContext, erpt::ResultOutOfFieldSpace()); + + s_record_count++; + auto &field = m_ctx.fields[m_ctx.field_count++]; + + field.id = field_id; + field.type = FieldType_Bool; + + field.value_bool = value_bool; + + R_SUCCEED(); + } + + Result ContextRecord::Add(FieldId field_id, u32 value_u32) { + R_UNLESS(m_ctx.field_count < FieldsPerContext, erpt::ResultOutOfFieldSpace()); + + s_record_count++; + auto &field = m_ctx.fields[m_ctx.field_count++]; + + field.id = field_id; + field.type = FieldType_NumericU32; + + field.value_u32 = value_u32; + + R_SUCCEED(); + } + + Result ContextRecord::Add(FieldId field_id, u64 value_u64) { + R_UNLESS(m_ctx.field_count < FieldsPerContext, erpt::ResultOutOfFieldSpace()); + + s_record_count++; + auto &field = m_ctx.fields[m_ctx.field_count++]; + + field.id = field_id; + field.type = FieldType_NumericU64; + + field.value_u64 = value_u64; + + R_SUCCEED(); + } + + Result ContextRecord::Add(FieldId field_id, s32 value_i32) { + R_UNLESS(m_ctx.field_count < FieldsPerContext, erpt::ResultOutOfFieldSpace()); + + s_record_count++; + auto &field = m_ctx.fields[m_ctx.field_count++]; + + field.id = field_id; + field.type = FieldType_NumericI32; + + field.value_i32 = value_i32; + + R_SUCCEED(); + } + + Result ContextRecord::Add(FieldId field_id, s64 value_i64) { + R_UNLESS(m_ctx.field_count < FieldsPerContext, erpt::ResultOutOfFieldSpace()); + + s_record_count++; + auto &field = m_ctx.fields[m_ctx.field_count++]; + + field.id = field_id; + field.type = FieldType_NumericI64; + + field.value_i64 = value_i64; + + R_SUCCEED(); + } + + Result ContextRecord::Add(FieldId field_id, const void *arr, u32 size, FieldType type) { + R_UNLESS(m_ctx.field_count < FieldsPerContext, erpt::ResultOutOfFieldSpace()); + R_UNLESS(size <= m_ctx.array_free_count, erpt::ResultOutOfArraySpace()); + + const u32 start_idx = m_ctx.array_buffer_size - m_ctx.array_free_count; + m_ctx.array_free_count -= size; + + s_record_count++; + auto &field = m_ctx.fields[m_ctx.field_count++]; + + field.id = field_id; + field.type = type; + + field.value_array = { + .start_idx = start_idx, + .size = size, + }; + + std::memcpy(m_ctx.array_buffer + start_idx, arr, size); + R_SUCCEED(); + } + + Result ContextRecord::Add(FieldId field_id, const char *str, u32 str_size) { + R_RETURN(this->Add(field_id, str, str_size, FieldType_String)); + } + + Result ContextRecord::Add(FieldId field_id, const u8 *data, u32 size) { + R_RETURN(this->Add(field_id, data, size, FieldType_U8Array)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_context_record.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_context_record.hpp new file mode 100644 index 00000000..041470fa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_context_record.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "erpt_srv_allocator.hpp" + +namespace ams::erpt::srv { + + class Context; + + class ContextRecord : public Allocator { + private: + static u32 s_record_count; + public: + static u32 GetRecordCount() { + return s_record_count; + } + private: + ContextEntry m_ctx; + private: + Result Add(FieldId field_id, const void *arr, u32 size, FieldType type); + public: + ContextRecord(); + explicit ContextRecord(CategoryId category, u32 array_buf_size = ArrayBufferSizeDefault); + ~ContextRecord(); + + const ContextEntry *GetContextEntryPtr() const { + return std::addressof(m_ctx); + } + + Result Initialize(const ContextEntry *ctx_ptr, const u8 *data, u32 data_size); + + Result Add(FieldId field_id, bool value_bool); + Result Add(FieldId field_id, u32 value_u32); + Result Add(FieldId field_id, u64 value_u64); + Result Add(FieldId field_id, s32 value_i32); + Result Add(FieldId field_id, s64 value_i64); + Result Add(FieldId field_id, const char *str, u32 str_size); + Result Add(FieldId field_id, const u8 *data, u32 size); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_forced_shutdown.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_forced_shutdown.cpp new file mode 100644 index 00000000..c41e04f3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_forced_shutdown.cpp @@ -0,0 +1,331 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_forced_shutdown.hpp" +#include "erpt_srv_context.hpp" +#include "erpt_srv_context_record.hpp" +#include "erpt_srv_reporter.hpp" +#include "erpt_srv_stream.hpp" + +namespace ams::erpt::srv { + + namespace { + + constexpr u32 ForcedShutdownContextBufferSize = 1_KB; + + constexpr u32 ForcedShutdownContextVersion = 1; + + struct ForcedShutdownContextHeader { + u32 version; + u32 num_contexts; + }; + static_assert(sizeof(ForcedShutdownContextHeader) == 8); + + struct ForcedShutdownContextEntry { + u32 version; + CategoryId category; + u32 field_count; + u32 array_buffer_size; + }; + static_assert(sizeof(ForcedShutdownContextEntry) == 16); + + os::Event g_forced_shutdown_update_event(os::EventClearMode_ManualClear); + + constinit ContextEntry g_forced_shutdown_contexts[] = { + { .category = CategoryId_RunningApplicationInfo, }, + { .category = CategoryId_RunningAppletInfo, }, + { .category = CategoryId_FocusedAppletHistoryInfo, }, + }; + + bool IsForceShutdownDetected() { + fs::DirectoryEntryType entry_type; + return R_SUCCEEDED(fs::GetEntryType(std::addressof(entry_type), ForcedShutdownContextFileName)); + } + + Result CreateForcedShutdownContext() { + /* Create the context. */ + { + /* Create the stream. */ + Stream stream; + R_TRY(stream.OpenStream(ForcedShutdownContextFileName, StreamMode_Write, 0)); + + /* Write a context header. */ + const ForcedShutdownContextHeader header = { .version = ForcedShutdownContextVersion, .num_contexts = 0, }; + R_TRY(stream.WriteStream(reinterpret_cast<const u8 *>(std::addressof(header)), sizeof(header))); + } + + /* Commit the context. */ + R_TRY(Stream::CommitStream()); + + R_SUCCEED(); + } + + Result CreateReportForForcedShutdown() { + /* Create a new context record. */ + /* NOTE: Nintendo does not check that this allocation succeeds. */ + auto record = std::make_unique<ContextRecord>(CategoryId_ErrorInfo); + + /* Create error code for the report. */ + char error_code_str[err::ErrorCode::StringLengthMax]; + err::GetErrorCodeString(error_code_str, sizeof(error_code_str), err::ConvertResultToErrorCode(err::ResultForcedShutdownDetected())); + + /* Add error code to the context. */ + R_TRY(record->Add(FieldId_ErrorCode, error_code_str, std::strlen(error_code_str))); + + /* Create report. */ + R_TRY(Reporter::CreateReport(ReportType_Invisible, ResultSuccess(), std::move(record), nullptr, nullptr, 0, erpt::srv::MakeNoCreateReportOptionFlags(), nullptr)); + + R_SUCCEED(); + } + + Result LoadForcedShutdownContext() { + /* Create the stream to read the context. */ + Stream stream; + R_TRY(stream.OpenStream(ForcedShutdownContextFileName, StreamMode_Read, ForcedShutdownContextBufferSize)); + + /* Read the header. */ + u32 read_size; + ForcedShutdownContextHeader header; + R_TRY(stream.ReadStream(std::addressof(read_size), reinterpret_cast<u8 *>(std::addressof(header)), sizeof(header))); + + /* Validate the header. */ + R_SUCCEED_IF(read_size != sizeof(header)); + R_SUCCEED_IF(ForcedShutdownContextVersion); + R_SUCCEED_IF(header.num_contexts == 0); + + /* Read out the contexts. */ + for (u32 i = 0; i < header.num_contexts; ++i) { + /* Read the context entry header. */ + ForcedShutdownContextEntry entry_header; + R_TRY(stream.ReadStream(std::addressof(read_size), reinterpret_cast<u8 *>(std::addressof(entry_header)), sizeof(entry_header))); + + if (read_size != sizeof(entry_header)) { + break; + } + + if (entry_header.field_count == 0) { + continue; + } + + /* Read the saved data into a context entry. */ + ContextEntry ctx = { + .version = entry_header.version, + .field_count = entry_header.field_count, + .category = entry_header.category, + }; + + /* Check that the field count is valid. */ + AMS_ABORT_UNLESS(entry_header.field_count <= util::size(ctx.fields)); + + /* Read the fields. */ + R_TRY(stream.ReadStream(std::addressof(read_size), reinterpret_cast<u8 *>(std::addressof(ctx.fields)), entry_header.field_count * sizeof(ctx.fields[0]))); + if (read_size != entry_header.field_count * sizeof(ctx.fields[0])) { + break; + } + + /* Allocate an array buffer. */ + u8 *array_buffer = static_cast<u8 *>(Allocate(entry_header.array_buffer_size)); + if (array_buffer == nullptr) { + break; + } + ON_SCOPE_EXIT { Deallocate(array_buffer); }; + + /* Read the array buffer data. */ + R_TRY(stream.ReadStream(std::addressof(read_size), array_buffer, entry_header.array_buffer_size)); + if (read_size != entry_header.array_buffer_size) { + break; + } + + /* Create a record for the context. */ + auto record = std::make_unique<ContextRecord>(); + if (record == nullptr) { + break; + } + + /* Initialize the record. */ + R_TRY(record->Initialize(std::addressof(ctx), array_buffer, entry_header.array_buffer_size)); + + /* Submit the record. */ + R_TRY(Context::SubmitContextRecord(std::move(record))); + } + + R_SUCCEED(); + } + + u32 GetForcedShutdownContextCount() { + u32 count = 0; + for (const auto &ctx : g_forced_shutdown_contexts) { + if (ctx.field_count != 0) { + ++count; + } + } + return count; + } + + Result SaveForcedShutdownContextImpl() { + /* Save context to file. */ + { + /* Create the stream to write the context. */ + Stream stream; + R_TRY(stream.OpenStream(ForcedShutdownContextFileName, StreamMode_Write, ForcedShutdownContextBufferSize)); + + /* Write a context header. */ + const ForcedShutdownContextHeader header = { .version = ForcedShutdownContextVersion, .num_contexts = GetForcedShutdownContextCount(), }; + R_TRY(stream.WriteStream(reinterpret_cast<const u8 *>(std::addressof(header)), sizeof(header))); + + /* Write each context. */ + for (const auto &ctx : g_forced_shutdown_contexts) { + /* If the context has no fields, continue. */ + if (ctx.field_count == 0) { + continue; + } + + /* Write a context entry header. */ + const ForcedShutdownContextEntry entry_header = { + .version = ctx.version, + .category = ctx.category, + .field_count = ctx.field_count, + .array_buffer_size = ctx.array_buffer_size, + }; + R_TRY(stream.WriteStream(reinterpret_cast<const u8 *>(std::addressof(entry_header)), sizeof(entry_header))); + + /* Write all fields. */ + for (u32 i = 0; i < ctx.field_count; ++i) { + R_TRY(stream.WriteStream(reinterpret_cast<const u8 *>(ctx.fields + i), sizeof(ctx.fields[0]))); + } + + /* Write the array buffer. */ + R_TRY(stream.WriteStream(ctx.array_buffer, ctx.array_buffer_size)); + } + } + + /* Commit the context. */ + R_TRY(Stream::CommitStream()); + + R_SUCCEED(); + } + + } + + os::Event *GetForcedShutdownUpdateEvent() { + return std::addressof(g_forced_shutdown_update_event); + } + + void InitializeForcedShutdownDetection() { + /* Check if the forced shutdown context exists; if it doesn't, we should create an empty one. */ + if (!IsForceShutdownDetected()) { + /* NOTE: Nintendo does not check result here. */ + CreateForcedShutdownContext(); + return; + } + + /* Load the forced shutdown context. */ + /* NOTE: Nintendo does not check that this succeeds. */ + LoadForcedShutdownContext(); + + /* Create report for the forced shutdown. */ + /* NOTE: Nintendo does not check that this succeeds. */ + CreateReportForForcedShutdown(); + + /* Clear the forced shutdown categories. */ + /* NOTE: Nintendo does not check that this succeeds. */ + Context::ClearContext(CategoryId_RunningApplicationInfo); + Context::ClearContext(CategoryId_RunningAppletInfo); + Context::ClearContext(CategoryId_FocusedAppletHistoryInfo); + + /* Save the forced shutdown context. */ + /* NOTE: Nintendo does not check that this succeeds. */ + SaveForcedShutdownContext(); + } + + void FinalizeForcedShutdownDetection() { + /* Try to delete the context. */ + const Result result = Stream::DeleteStream(ForcedShutdownContextFileName); + if (!fs::ResultPathNotFound::Includes(result)) { + /* We must have succeeded, if the file existed. */ + R_ABORT_UNLESS(result); + + /* Commit the deletion. */ + R_ABORT_UNLESS(Stream::CommitStream()); + } + } + + void SaveForcedShutdownContext() { + /* NOTE: Nintendo does not check that saving the report succeeds. */ + SaveForcedShutdownContextImpl(); + } + + void SubmitContextForForcedShutdownDetection(const ContextEntry *entry, const u8 *data, u32 data_size) { + /* If the context entry matches one of our tracked categories, update our stored category. */ + for (auto &ctx : g_forced_shutdown_contexts) { + /* Check for a match. */ + if (ctx.category != entry->category) { + continue; + } + + /* If we have an existing array buffer, free it. */ + if (ctx.array_buffer != nullptr) { + Deallocate(ctx.array_buffer); + ctx.array_buffer = nullptr; + ctx.array_buffer_size = 0; + ctx.array_free_count = 0; + } + + /* Copy in the context. */ + ctx = *entry; + + /* Add the submitted data. */ + if (data != nullptr && data_size > 0) { + /* Allocate new array buffer. */ + ctx.array_buffer = static_cast<u8 *>(Allocate(data_size)); + if (ctx.array_buffer == nullptr) { + /* We failed to allocate; this is okay, but clear our field count. */ + ctx.field_count = 0; + break; + } + + /* Copy in the data. */ + std::memcpy(ctx.array_buffer, data, data_size); + + /* Set buffer extents. */ + ctx.array_buffer_size = data_size; + ctx.array_free_count = 0; + } else { + ctx.array_buffer = nullptr; + ctx.array_buffer_size = 0; + ctx.array_free_count = 0; + } + + /* Signal, to notify that we had an update. */ + g_forced_shutdown_update_event.Signal(); + + /* We're done processing, since we found a match. */ + break; + } + } + + Result InvalidateForcedShutdownDetection() { + /* Delete the forced shutdown context. */ + R_TRY(Stream::DeleteStream(ForcedShutdownContextFileName)); + + /* Commit the deletion. */ + R_TRY(Stream::CommitStream()); + + R_SUCCEED(); + } + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_forced_shutdown.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_forced_shutdown.hpp new file mode 100644 index 00000000..77ebfd66 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_forced_shutdown.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::erpt::srv { + + os::Event *GetForcedShutdownUpdateEvent(); + + void InitializeForcedShutdownDetection(); + void FinalizeForcedShutdownDetection(); + + void SaveForcedShutdownContext(); + + void SubmitContextForForcedShutdownDetection(const ContextEntry *entry, const u8 *data, u32 data_size); + + Result InvalidateForcedShutdownDetection(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_formatter.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_formatter.hpp new file mode 100644 index 00000000..fcdb4836 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_formatter.hpp @@ -0,0 +1,187 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "erpt_srv_report.hpp" + +namespace ams::erpt::srv { + + class Formatter { + private: + enum ElementSize { + ElementSize_16 = 16, + ElementSize_32 = 32, + ElementSize_256 = 256, + ElementSize_16384 = 16384, + }; + private: + static ValueTypeTag GetTag(s8) { return ValueTypeTag::I8; } + static ValueTypeTag GetTag(s16) { return ValueTypeTag::I16; } + static ValueTypeTag GetTag(s32) { return ValueTypeTag::I32; } + static ValueTypeTag GetTag(s64) { return ValueTypeTag::I64; } + static ValueTypeTag GetTag(u8) { return ValueTypeTag::U8; } + static ValueTypeTag GetTag(u16) { return ValueTypeTag::U16; } + static ValueTypeTag GetTag(u32) { return ValueTypeTag::U32; } + static ValueTypeTag GetTag(u64) { return ValueTypeTag::U64; } + + static Result AddStringValue(Report *report, const char *str, u32 len) { + const u32 str_len = str != nullptr ? static_cast<u32>(strnlen(str, len)) : 0; + + if (str_len < ElementSize_32) { + R_TRY(report->Write(static_cast<u8>(static_cast<u8>(ValueTypeTag::FixStr) | str_len))); + } else if (str_len < ElementSize_256) { + R_TRY(report->Write(static_cast<u8>(ValueTypeTag::Str8))); + R_TRY(report->Write(static_cast<u8>(str_len))); + } else { + R_UNLESS(str_len < ElementSize_16384, erpt::ResultFormatterError()); + R_TRY(report->Write(static_cast<u8>(ValueTypeTag::Str16))); + + u16 be_str_len; + util::StoreBigEndian(std::addressof(be_str_len), static_cast<u16>(str_len)); + R_TRY(report->Write(be_str_len)); + } + + R_TRY(report->Write(str, str_len)); + + R_SUCCEED(); + } + + static Result AddId(Report *report, FieldId field_id) { + static_assert(MaxFieldStringSize < ElementSize_256); + + const auto index = FindFieldIndex(field_id); + AMS_ASSERT(index.has_value()); + + R_TRY(AddStringValue(report, FieldString[index.value()], strnlen(FieldString[index.value()], MaxFieldStringSize))); + + R_SUCCEED(); + } + + template<typename T> + static Result AddValue(Report *report, T value) { + const u8 tag = static_cast<u8>(GetTag(value)); + + T big_endian_value; + util::StoreBigEndian(std::addressof(big_endian_value), value); + + R_TRY(report->Write(tag)); + R_TRY(report->Write(reinterpret_cast<u8 *>(std::addressof(big_endian_value)), sizeof(big_endian_value))); + + R_SUCCEED(); + } + + template<typename T> + static Result AddValueArray(Report *report, T *arr, u32 arr_size) { + if (arr_size < ElementSize_16) { + R_TRY(report->Write(static_cast<u8>(static_cast<u8>(ValueTypeTag::FixArray) | arr_size))); + } else { + R_UNLESS(arr_size < ElementSize_16384, erpt::ResultFormatterError()); + + u16 be_arr_size; + util::StoreBigEndian(std::addressof(be_arr_size), static_cast<u16>(arr_size)); + + R_TRY(report->Write(static_cast<u8>(ValueTypeTag::Array16))); + R_TRY(report->Write(be_arr_size)); + } + + for (u32 i = 0; i < arr_size; i++) { + R_TRY(AddValue(report, arr[i])); + } + + R_SUCCEED(); + } + + template<typename T> + static Result AddIdValuePair(Report *report, FieldId field_id, T value) { + R_TRY(AddId(report, field_id)); + R_TRY(AddValue(report, value)); + R_SUCCEED(); + } + + template<typename T> + static Result AddIdValueArray(Report *report, FieldId field_id, T *arr, u32 arr_size) { + R_TRY(AddId(report, field_id)); + R_TRY(AddValueArray(report, arr, arr_size)); + R_SUCCEED(); + } + public: + static Result Begin(Report *report, u32 record_count) { + if (record_count < ElementSize_16) { + R_TRY(report->Write(static_cast<u8>(static_cast<u8>(ValueTypeTag::FixMap) | record_count))); + } else { + R_UNLESS(record_count < ElementSize_16384, erpt::ResultFormatterError()); + + u16 be_count; + util::StoreBigEndian(std::addressof(be_count), static_cast<u16>(record_count)); + + R_TRY(report->Write(static_cast<u8>(ValueTypeTag::Map16))); + R_TRY(report->Write(be_count)); + } + + R_SUCCEED(); + } + + static Result End(Report *report) { + AMS_UNUSED(report); + R_SUCCEED(); + } + + template<typename T> + static Result AddField(Report *report, FieldId field_id, T value) { + R_RETURN(AddIdValuePair<T>(report, field_id, value)); + } + + template<typename T> + static Result AddField(Report *report, FieldId field_id, T *arr, u32 arr_size) { + R_RETURN(AddIdValueArray(report, field_id, arr, arr_size)); + } + + static Result AddField(Report *report, FieldId field_id, bool value) { + R_TRY(AddId(report, field_id)); + R_TRY(report->Write(static_cast<u8>(value ? ValueTypeTag::True : ValueTypeTag::False))); + R_SUCCEED(); + } + + static Result AddField(Report *report, FieldId field_id, char *str, u32 len) { + R_TRY(AddId(report, field_id)); + + R_TRY(AddStringValue(report, str, len)); + + R_SUCCEED(); + } + + static Result AddField(Report *report, FieldId field_id, u8 *bin, u32 len) { + R_TRY(AddId(report, field_id)); + + if (len < ElementSize_256) { + R_TRY(report->Write(static_cast<u8>(ValueTypeTag::Bin8))); + R_TRY(report->Write(static_cast<u8>(len))); + } else { + R_UNLESS(len < ElementSize_16384, erpt::ResultFormatterError()); + R_TRY(report->Write(static_cast<u8>(ValueTypeTag::Bin16))); + + u16 be_len; + util::StoreBigEndian(std::addressof(be_len), static_cast<u16>(len)); + R_TRY(report->Write(be_len)); + } + + R_TRY(report->Write(bin, len)); + + R_SUCCEED(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_fs_info.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_fs_info.hpp new file mode 100644 index 00000000..5d9d7ad1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_fs_info.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::erpt::srv { + + Result SubmitFsInfo(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_fs_info.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_fs_info.os.horizon.cpp new file mode 100644 index 00000000..1b784078 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_fs_info.os.horizon.cpp @@ -0,0 +1,499 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_fs_info.hpp" +#include "erpt_srv_context_record.hpp" +#include "erpt_srv_context.hpp" + +namespace ams::erpt::srv { + + namespace { + + Result SubmitMmcDetailInfo() { + /* Submit the mmc cid. */ + { + u8 mmc_cid[fs::MmcCidSize] = {}; + if (R_SUCCEEDED(fs::GetMmcCid(mmc_cid, sizeof(mmc_cid)))) { + /* Clear the serial number from the cid. */ + fs::ClearMmcCidSerialNumber(mmc_cid); + + /* Create a record. */ + auto record = std::make_unique<ContextRecord>(CategoryId_NANDTypeInfo, sizeof(mmc_cid)); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + /* Add the cid. */ + R_ABORT_UNLESS(record->Add(FieldId_NANDType, mmc_cid, sizeof(mmc_cid))); + + /* Submit the record. */ + R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record))); + } + } + + /* Submit the mmc speed mode. */ + { + /* Create a record. */ + auto record = std::make_unique<ContextRecord>(CategoryId_NANDSpeedModeInfo, 0x20); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + /* Get the speed mode. */ + fs::MmcSpeedMode speed_mode{}; + const auto res = fs::GetMmcSpeedMode(std::addressof(speed_mode)); + if (R_SUCCEEDED(res)) { + const char *speed_mode_name = "None"; + switch (speed_mode) { + case fs::MmcSpeedMode_Identification: + speed_mode_name = "Identification"; + break; + case fs::MmcSpeedMode_LegacySpeed: + speed_mode_name = "LegacySpeed"; + break; + case fs::MmcSpeedMode_HighSpeed: + speed_mode_name = "HighSpeed"; + break; + case fs::MmcSpeedMode_Hs200: + speed_mode_name = "Hs200"; + break; + case fs::MmcSpeedMode_Hs400: + speed_mode_name = "Hs400"; + break; + case fs::MmcSpeedMode_Unknown: + speed_mode_name = "Unknown"; + break; + default: + speed_mode_name = "UnDefined"; + break; + } + + R_ABORT_UNLESS(record->Add(FieldId_NANDSpeedMode, speed_mode_name, std::strlen(speed_mode_name))); + } else { + /* Getting speed mode failed, so add the result. */ + char res_str[0x20]; + util::SNPrintf(res_str, sizeof(res_str), "0x%08X", res.GetValue()); + R_ABORT_UNLESS(record->Add(FieldId_NANDSpeedMode, res_str, std::strlen(res_str))); + } + + /* Submit the record. */ + R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record))); + } + + /* Submit the mmc extended csd. */ + { + u8 mmc_csd[fs::MmcExtendedCsdSize] = {}; + if (R_SUCCEEDED(fs::GetMmcExtendedCsd(mmc_csd, sizeof(mmc_csd)))) { + /* Create a record. */ + auto record = std::make_unique<ContextRecord>(CategoryId_NANDExtendedCsd, 0); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + /* Add fields from the csd. */ + R_ABORT_UNLESS(record->Add(FieldId_NANDPreEolInfo, static_cast<u32>(mmc_csd[fs::MmcExtendedCsdOffsetReEolInfo]))); + R_ABORT_UNLESS(record->Add(FieldId_NANDDeviceLifeTimeEstTypA, static_cast<u32>(mmc_csd[fs::MmcExtendedCsdOffsetDeviceLifeTimeEstTypA]))); + R_ABORT_UNLESS(record->Add(FieldId_NANDDeviceLifeTimeEstTypB, static_cast<u32>(mmc_csd[fs::MmcExtendedCsdOffsetDeviceLifeTimeEstTypB]))); + + /* Submit the record. */ + R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record))); + } + } + + /* Submit the mmc patrol count. */ + { + u32 count = 0; + if (R_SUCCEEDED(fs::GetMmcPatrolCount(std::addressof(count)))) { + /* Create a record. */ + auto record = std::make_unique<ContextRecord>(CategoryId_NANDPatrolInfo, 0); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + /* Add the count. */ + R_ABORT_UNLESS(record->Add(FieldId_NANDPatrolCount, count)); + + /* Submit the record. */ + R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record))); + } + } + + R_SUCCEED(); + } + + Result SubmitMmcErrorInfo() { + /* Get the mmc error info. */ + fs::StorageErrorInfo sei = {}; + char log_buffer[erpt::ArrayBufferSizeDefault] = {}; + size_t log_size = 0; + if (R_SUCCEEDED(fs::GetAndClearMmcErrorInfo(std::addressof(sei), std::addressof(log_size), log_buffer, sizeof(log_buffer)))) { + /* Submit the error info. */ + { + /* Create a record. */ + auto record = std::make_unique<ContextRecord>(CategoryId_NANDErrorInfo, 0); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + /* Add fields. */ + R_ABORT_UNLESS(record->Add(FieldId_NANDNumActivationFailures, sei.num_activation_failures)); + R_ABORT_UNLESS(record->Add(FieldId_NANDNumActivationErrorCorrections, sei.num_activation_error_corrections)); + R_ABORT_UNLESS(record->Add(FieldId_NANDNumReadWriteFailures, sei.num_read_write_failures)); + R_ABORT_UNLESS(record->Add(FieldId_NANDNumReadWriteErrorCorrections, sei.num_read_write_error_corrections)); + + /* Submit the record. */ + R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record))); + } + + /* If we have a log, submit it. */ + if (log_size > 0) { + /* Create a record. */ + auto record = std::make_unique<ContextRecord>(CategoryId_NANDDriverLog, log_size); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + /* Add fields. */ + R_ABORT_UNLESS(record->Add(FieldId_NANDErrorLog, log_buffer, log_size)); + + /* Submit the record. */ + R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record))); + } + } + + R_SUCCEED(); + } + + Result SubmitSdCardDetailInfo() { + /* Submit the sd card cid. */ + { + u8 sd_cid[fs::SdCardCidSize] = {}; + if (R_SUCCEEDED(fs::GetSdCardCid(sd_cid, sizeof(sd_cid)))) { + /* Clear the serial number from the cid. */ + fs::ClearSdCardCidSerialNumber(sd_cid); + + /* Create a record. */ + auto record = std::make_unique<ContextRecord>(CategoryId_MicroSDTypeInfo, sizeof(sd_cid)); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + /* Add the cid. */ + R_ABORT_UNLESS(record->Add(FieldId_MicroSDType, sd_cid, sizeof(sd_cid))); + + /* Submit the record. */ + R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record))); + } + } + + /* Submit the sd card speed mode. */ + { + /* Create a record. */ + auto record = std::make_unique<ContextRecord>(CategoryId_MicroSDSpeedModeInfo, 0x20); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + /* Get the speed mode. */ + fs::SdCardSpeedMode speed_mode{}; + const auto res = fs::GetSdCardSpeedMode(std::addressof(speed_mode)); + if (R_SUCCEEDED(res)) { + const char *speed_mode_name = "None"; + switch (speed_mode) { + case fs::SdCardSpeedMode_Identification: + speed_mode_name = "Identification"; + break; + case fs::SdCardSpeedMode_DefaultSpeed: + speed_mode_name = "DefaultSpeed"; + break; + case fs::SdCardSpeedMode_HighSpeed: + speed_mode_name = "HighSpeed"; + break; + case fs::SdCardSpeedMode_Sdr12: + speed_mode_name = "Sdr12"; + break; + case fs::SdCardSpeedMode_Sdr25: + speed_mode_name = "Sdr25"; + break; + case fs::SdCardSpeedMode_Sdr50: + speed_mode_name = "Sdr50"; + break; + case fs::SdCardSpeedMode_Sdr104: + speed_mode_name = "Sdr104"; + break; + case fs::SdCardSpeedMode_Ddr50: + speed_mode_name = "Ddr50"; + break; + case fs::SdCardSpeedMode_Unknown: + speed_mode_name = "Unknown"; + break; + default: + speed_mode_name = "UnDefined"; + break; + } + + R_ABORT_UNLESS(record->Add(FieldId_MicroSDSpeedMode, speed_mode_name, std::strlen(speed_mode_name))); + } else { + /* Getting speed mode failed, so add the result. */ + char res_str[0x20]; + util::SNPrintf(res_str, sizeof(res_str), "0x%08X", res.GetValue()); + R_ABORT_UNLESS(record->Add(FieldId_MicroSDSpeedMode, res_str, std::strlen(res_str))); + } + + /* Submit the record. */ + R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record))); + } + + /* Submit the area sizes. */ + { + s64 user_area_size = 0; + s64 prot_area_size = 0; + const Result res_user = fs::GetSdCardUserAreaSize(std::addressof(user_area_size)); + const Result res_prot = fs::GetSdCardProtectedAreaSize(std::addressof(prot_area_size)); + if (R_SUCCEEDED(res_user) || R_SUCCEEDED(res_prot)) { + /* Create a record. */ + auto record = std::make_unique<ContextRecord>(CategoryId_SdCardSizeSpec, 0); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + /* Add sizes. */ + if (R_SUCCEEDED(res_user)) { + R_ABORT_UNLESS(record->Add(FieldId_SdCardUserAreaSize, user_area_size)); + } + if (R_SUCCEEDED(res_prot)) { + R_ABORT_UNLESS(record->Add(FieldId_SdCardProtectedAreaSize, prot_area_size)); + } + + /* Submit the record. */ + R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record))); + } + } + + R_SUCCEED(); + } + + Result SubmitSdCardErrorInfo() { + /* Get the sd card error info. */ + fs::StorageErrorInfo sei = {}; + char log_buffer[erpt::ArrayBufferSizeDefault] = {}; + size_t log_size = 0; + if (R_SUCCEEDED(fs::GetAndClearSdCardErrorInfo(std::addressof(sei), std::addressof(log_size), log_buffer, sizeof(log_buffer)))) { + /* Submit the error info. */ + { + /* Create a record. */ + auto record = std::make_unique<ContextRecord>(CategoryId_SdCardErrorInfo, 0); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + /* Add fields. */ + R_ABORT_UNLESS(record->Add(FieldId_SdCardNumActivationFailures, sei.num_activation_failures)); + R_ABORT_UNLESS(record->Add(FieldId_SdCardNumActivationErrorCorrections, sei.num_activation_error_corrections)); + R_ABORT_UNLESS(record->Add(FieldId_SdCardNumReadWriteFailures, sei.num_read_write_failures)); + R_ABORT_UNLESS(record->Add(FieldId_SdCardNumReadWriteErrorCorrections, sei.num_read_write_error_corrections)); + + /* Submit the record. */ + R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record))); + } + + /* If we have a log, submit it. */ + if (log_size > 0) { + /* Create a record. */ + auto record = std::make_unique<ContextRecord>(CategoryId_SdCardDriverLog, log_size); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + /* Add fields. */ + R_ABORT_UNLESS(record->Add(FieldId_SdCardErrorLog, log_buffer, log_size)); + + /* Submit the record. */ + R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record))); + } + } + + R_SUCCEED(); + } + + Result SubmitGameCardDetailInfo() { + /* Create a record. */ + auto record = std::make_unique<ContextRecord>(CategoryId_GameCardCIDInfo, fs::GameCardCidSize + fs::GameCardDeviceIdSize); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + /* Add the game card cid. */ + { + u8 gc_cid[fs::GameCardCidSize] = {}; + if (fs::IsGameCardInserted() && R_SUCCEEDED(fs::GetGameCardCid(gc_cid, sizeof(gc_cid)))) { + /* Add the cid. */ + R_ABORT_UNLESS(record->Add(FieldId_GameCardCID, gc_cid, sizeof(gc_cid))); + } + } + + /* Add the game card device id. */ + { + u8 gc_device_id[fs::GameCardDeviceIdSize] = {}; + if (fs::IsGameCardInserted() && R_SUCCEEDED(fs::GetGameCardDeviceId(gc_device_id, sizeof(gc_device_id)))) { + /* Add the cid. */ + R_ABORT_UNLESS(record->Add(FieldId_GameCardDeviceId, gc_device_id, sizeof(gc_device_id))); + } + } + + /* Submit the record. */ + R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record))); + + R_SUCCEED(); + } + + Result SubmitGameCardErrorInfo() { + /* Get the game card error info. */ + fs::GameCardErrorReportInfo ei = {}; + if (R_SUCCEEDED(fs::GetGameCardErrorReportInfo(std::addressof(ei)))) { + /* Create a record. */ + auto record = std::make_unique<ContextRecord>(CategoryId_GameCardErrorInfo, 0); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + /* Add fields. */ + R_ABORT_UNLESS(record->Add(FieldId_GameCardCrcErrorCount, static_cast<u32>(ei.game_card_crc_error_num))); + R_ABORT_UNLESS(record->Add(FieldId_GameCardAsicCrcErrorCount, static_cast<u32>(ei.asic_crc_error_num))); + R_ABORT_UNLESS(record->Add(FieldId_GameCardRefreshCount, static_cast<u32>(ei.refresh_num))); + R_ABORT_UNLESS(record->Add(FieldId_GameCardReadRetryCount, static_cast<u32>(ei.retry_limit_out_num))); + R_ABORT_UNLESS(record->Add(FieldId_GameCardTimeoutRetryErrorCount, static_cast<u32>(ei.timeout_retry_num))); + R_ABORT_UNLESS(record->Add(FieldId_GameCardInsertionCount, static_cast<u32>(ei.insertion_count))); + R_ABORT_UNLESS(record->Add(FieldId_GameCardRemovalCount, static_cast<u32>(ei.removal_count))); + R_ABORT_UNLESS(record->Add(FieldId_GameCardAsicInitializeCount, ei.initialize_count)); + R_ABORT_UNLESS(record->Add(FieldId_GameCardAsicReinitializeCount, ei.asic_reinitialize_num)); + R_ABORT_UNLESS(record->Add(FieldId_GameCardAsicReinitializeFailureCount, ei.asic_reinitialize_failure_num)); + R_ABORT_UNLESS(record->Add(FieldId_GameCardAsicReinitializeFailureDetail, ei.asic_reinitialize_failure_detail)); + R_ABORT_UNLESS(record->Add(FieldId_GameCardRefreshSuccessCount, ei.refresh_succeeded_count)); + R_ABORT_UNLESS(record->Add(FieldId_GameCardAwakenCount, ei.awaken_count)); + R_ABORT_UNLESS(record->Add(FieldId_GameCardAwakenFailureCount, ei.awaken_failure_num)); + R_ABORT_UNLESS(record->Add(FieldId_GameCardReadCountFromInsert, ei.read_count_from_insert)); + R_ABORT_UNLESS(record->Add(FieldId_GameCardReadCountFromAwaken, ei.read_count_from_awaken)); + R_ABORT_UNLESS(record->Add(FieldId_GameCardLastReadErrorPageAddress, ei.last_read_error_page_address)); + R_ABORT_UNLESS(record->Add(FieldId_GameCardLastReadErrorPageCount, ei.last_read_error_page_count)); + + /* Submit the record. */ + R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record))); + } + + R_SUCCEED(); + } + + Result SubmitFileSystemErrorInfo() { + /* Get the fsp error info. */ + fs::FileSystemProxyErrorInfo ei = {}; + if (R_SUCCEEDED(fs::GetAndClearFileSystemProxyErrorInfo(std::addressof(ei)))) { + /* Submit FsProxyErrorInfo. */ + { + /* Create a record. */ + auto record = std::make_unique<ContextRecord>(CategoryId_FsProxyErrorInfo, fat::FatErrorNameMaxLength); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + /* Add fields. */ + R_ABORT_UNLESS(record->Add(FieldId_FsRemountForDataCorruptCount, ei.rom_fs_remount_for_data_corruption_count)); + R_ABORT_UNLESS(record->Add(FieldId_FsRemountForDataCorruptRetryOutCount, ei.rom_fs_unrecoverable_data_corruption_by_remount_count)); + + R_ABORT_UNLESS(record->Add(FieldId_FatFsError, ei.fat_fs_error.error)); + R_ABORT_UNLESS(record->Add(FieldId_FatFsExtraError, ei.fat_fs_error.extra_error)); + R_ABORT_UNLESS(record->Add(FieldId_FatFsErrorDrive, ei.fat_fs_error.drive_id)); + R_ABORT_UNLESS(record->Add(FieldId_FatFsErrorName, ei.fat_fs_error.name, fat::FatErrorNameMaxLength)); + + R_ABORT_UNLESS(record->Add(FieldId_FsRecoveredByInvalidateCacheCount, ei.rom_fs_recovered_by_invalidate_cache_count)); + R_ABORT_UNLESS(record->Add(FieldId_FsSaveDataIndexCount, ei.save_data_index_count)); + + R_ABORT_UNLESS(record->Add(FieldId_FatFsBisSystemFilePeakOpenCount, ei.bis_system_fat_report_info_1.open_file_peak_count)); + R_ABORT_UNLESS(record->Add(FieldId_FatFsBisSystemDirectoryPeakOpenCount, ei.bis_system_fat_report_info_1.open_directory_peak_count)); + + R_ABORT_UNLESS(record->Add(FieldId_FatFsBisUserFilePeakOpenCount, ei.bis_user_fat_report_info_1.open_file_peak_count)); + R_ABORT_UNLESS(record->Add(FieldId_FatFsBisUserDirectoryPeakOpenCount, ei.bis_user_fat_report_info_1.open_directory_peak_count)); + + R_ABORT_UNLESS(record->Add(FieldId_FatFsSdCardFilePeakOpenCount, ei.sd_card_fat_report_info_1.open_file_peak_count)); + R_ABORT_UNLESS(record->Add(FieldId_FatFsSdCardDirectoryPeakOpenCount, ei.sd_card_fat_report_info_1.open_directory_peak_count)); + + R_ABORT_UNLESS(record->Add(FieldId_FatFsBisSystemUniqueFileEntryPeakOpenCount, ei.bis_system_fat_report_info_2.open_unique_file_entry_peak_count)); + R_ABORT_UNLESS(record->Add(FieldId_FatFsBisSystemUniqueDirectoryEntryPeakOpenCount, ei.bis_system_fat_report_info_2.open_unique_directory_entry_peak_count)); + + R_ABORT_UNLESS(record->Add(FieldId_FatFsBisUserUniqueFileEntryPeakOpenCount, ei.bis_user_fat_report_info_2.open_unique_file_entry_peak_count)); + R_ABORT_UNLESS(record->Add(FieldId_FatFsBisUserUniqueDirectoryEntryPeakOpenCount, ei.bis_user_fat_report_info_2.open_unique_directory_entry_peak_count)); + + R_ABORT_UNLESS(record->Add(FieldId_FatFsSdCardUniqueFileEntryPeakOpenCount, ei.sd_card_fat_report_info_2.open_unique_file_entry_peak_count)); + R_ABORT_UNLESS(record->Add(FieldId_FatFsSdCardUniqueDirectoryEntryPeakOpenCount, ei.sd_card_fat_report_info_2.open_unique_directory_entry_peak_count)); + + /* Submit the record. */ + R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record))); + } + + /* Submit FsProxyErrorInfo2. */ + { + /* Create a record. */ + auto record = std::make_unique<ContextRecord>(CategoryId_FsProxyErrorInfo2, 0); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + /* Add fields. */ + R_ABORT_UNLESS(record->Add(FieldId_FsDeepRetryStartCount, ei.rom_fs_deep_retry_start_count)); + R_ABORT_UNLESS(record->Add(FieldId_FsUnrecoverableByGameCardAccessFailedCount, ei.rom_fs_unrecoverable_by_game_card_access_failed_count)); + + R_ABORT_UNLESS(record->Add(FieldId_FatFsBisSystemFatSafeControlResult, static_cast<u8>(ei.bis_system_fat_safe_info.result))); + R_ABORT_UNLESS(record->Add(FieldId_FatFsBisSystemFatErrorNumber, ei.bis_system_fat_safe_info.error_number)); + R_ABORT_UNLESS(record->Add(FieldId_FatFsBisSystemFatSafeErrorNumber, ei.bis_system_fat_safe_info.safe_error_number)); + + R_ABORT_UNLESS(record->Add(FieldId_FatFsBisUserFatSafeControlResult, static_cast<u8>(ei.bis_user_fat_safe_info.result))); + R_ABORT_UNLESS(record->Add(FieldId_FatFsBisUserFatErrorNumber, ei.bis_user_fat_safe_info.error_number)); + R_ABORT_UNLESS(record->Add(FieldId_FatFsBisUserFatSafeErrorNumber, ei.bis_user_fat_safe_info.safe_error_number)); + + /* Submit the record. */ + R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record))); + } + } + + R_SUCCEED(); + } + + Result SubmitMemoryReportInfo() { + /* Get the memory report info. */ + fs::MemoryReportInfo mri = {}; + if (R_SUCCEEDED(fs::GetAndClearMemoryReportInfo(std::addressof(mri)))) { + /* Create a record. */ + auto record = std::make_unique<ContextRecord>(CategoryId_FsMemoryInfo, 0); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + /* Add fields. */ + R_ABORT_UNLESS(record->Add(FieldId_FsPooledBufferPeakFreeSize, mri.pooled_buffer_peak_free_size)); + R_ABORT_UNLESS(record->Add(FieldId_FsPooledBufferRetriedCount, mri.pooled_buffer_retried_count)); + R_ABORT_UNLESS(record->Add(FieldId_FsPooledBufferReduceAllocationCount, mri.pooled_buffer_reduce_allocation_count)); + + R_ABORT_UNLESS(record->Add(FieldId_FsBufferManagerPeakFreeSize, mri.buffer_manager_peak_free_size)); + R_ABORT_UNLESS(record->Add(FieldId_FsBufferManagerRetriedCount, mri.buffer_manager_retried_count)); + + R_ABORT_UNLESS(record->Add(FieldId_FsExpHeapPeakFreeSize, mri.exp_heap_peak_free_size)); + + R_ABORT_UNLESS(record->Add(FieldId_FsBufferPoolPeakFreeSize, mri.buffer_pool_peak_free_size)); + + R_ABORT_UNLESS(record->Add(FieldId_FsPatrolReadAllocateBufferSuccessCount, mri.patrol_read_allocate_buffer_success_count)); + R_ABORT_UNLESS(record->Add(FieldId_FsPatrolReadAllocateBufferFailureCount, mri.patrol_read_allocate_buffer_failure_count)); + + R_ABORT_UNLESS(record->Add(FieldId_FsBufferManagerPeakTotalAllocatableSize, mri.buffer_manager_peak_total_allocatable_size)); + + R_ABORT_UNLESS(record->Add(FieldId_FsBufferPoolMaxAllocateSize, mri.buffer_pool_max_allocate_size)); + + R_ABORT_UNLESS(record->Add(FieldId_FsPooledBufferFailedIdealAllocationCountOnAsyncAccess, mri.pooled_buffer_failed_ideal_allocation_count_on_async_access)); + + /* Submit the record. */ + R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record))); + } + + R_SUCCEED(); + } + + } + + Result SubmitFsInfo() { + /* Temporarily disable auto-abort. */ + fs::ScopedAutoAbortDisabler aad; + + /* Submit various FS info. */ + R_TRY(SubmitMmcDetailInfo()); + R_TRY(SubmitMmcErrorInfo()); + R_TRY(SubmitSdCardDetailInfo()); + R_TRY(SubmitSdCardErrorInfo()); + R_TRY(SubmitGameCardDetailInfo()); + R_TRY(SubmitGameCardErrorInfo()); + R_TRY(SubmitFileSystemErrorInfo()); + R_TRY(SubmitMemoryReportInfo()); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_journal.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_journal.cpp new file mode 100644 index 00000000..84dd89f5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_journal.cpp @@ -0,0 +1,119 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_journal.hpp" + +namespace ams::erpt::srv { + + void Journal::CleanupAttachments() { + return JournalForAttachments::CleanupAttachments(); + } + + void Journal::CleanupReports() { + return JournalForReports::CleanupReports(); + } + + Result Journal::Commit() { + /* Open the stream. */ + Stream stream; + R_TRY(stream.OpenStream(JournalFileName, StreamMode_Write, JournalStreamBufferSize)); + + /* Commit the reports. */ + R_TRY(JournalForReports::CommitJournal(std::addressof(stream))); + + /* Commit the meta. */ + R_TRY(JournalForMeta::CommitJournal(std::addressof(stream))); + + /* Commit the attachments. */ + R_TRY(JournalForAttachments::CommitJournal(std::addressof(stream))); + + /* Close and commit the stream. */ + stream.CloseStream(); + stream.CommitStream(); + + R_SUCCEED(); + } + + Result Journal::Delete(ReportId report_id) { + R_RETURN(JournalForReports::DeleteReport(report_id)); + } + + Result Journal::GetAttachmentList(u32 *out_count, AttachmentInfo *out_infos, size_t max_out_infos, ReportId report_id) { + R_RETURN(JournalForAttachments::GetAttachmentList(out_count, out_infos, max_out_infos, report_id)); + } + + util::Uuid Journal::GetJournalId() { + return JournalForMeta::GetJournalId(); + } + + s64 Journal::GetMaxReportSize() { + return JournalForReports::GetMaxReportSize(); + } + + Result Journal::GetReportList(ReportList *out, ReportType type_filter) { + R_RETURN(JournalForReports::GetReportList(out, type_filter)); + } + + u32 Journal::GetStoredReportCount(ReportType type) { + return JournalForReports::GetStoredReportCount(type); + } + + u32 Journal::GetTransmittedCount(ReportType type) { + return JournalForMeta::GetTransmittedCount(type); + } + + u32 Journal::GetUntransmittedCount(ReportType type) { + return JournalForMeta::GetUntransmittedCount(type); + } + + u32 Journal::GetUsedStorage() { + return JournalForReports::GetUsedStorage() + JournalForAttachments::GetUsedStorage(); + } + + Result Journal::Restore() { + /* Open the stream. */ + Stream stream; + R_TRY(stream.OpenStream(JournalFileName, StreamMode_Read, JournalStreamBufferSize)); + + /* Restore the reports. */ + R_TRY(JournalForReports::RestoreJournal(std::addressof(stream))); + + /* Restore the meta. */ + R_TRY(JournalForMeta::RestoreJournal(std::addressof(stream))); + + /* Restore the attachments. */ + R_TRY(JournalForAttachments::RestoreJournal(std::addressof(stream))); + + R_SUCCEED(); + } + + JournalRecord<ReportInfo> *Journal::Retrieve(ReportId report_id) { + return JournalForReports::RetrieveRecord(report_id); + } + + JournalRecord<AttachmentInfo> *Journal::Retrieve(AttachmentId attachment_id) { + return JournalForAttachments::RetrieveRecord(attachment_id); + } + + Result Journal::Store(JournalRecord<ReportInfo> *record) { + R_RETURN(JournalForReports::StoreRecord(record)); + } + + Result Journal::Store(JournalRecord<AttachmentInfo> *record) { + R_RETURN(JournalForAttachments::StoreRecord(record)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_journal.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_journal.hpp new file mode 100644 index 00000000..16011e75 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_journal.hpp @@ -0,0 +1,119 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "erpt_srv_allocator.hpp" +#include "erpt_srv_ref_count.hpp" +#include "erpt_srv_journal_record.hpp" +#include "erpt_srv_stream.hpp" + +namespace ams::erpt::srv { + + constexpr inline s32 JournalVersion = 1; + + constexpr inline u32 JournalStreamBufferSize = 4_KB; + + struct JournalMeta { + s32 version; + u32 transmitted_count[ReportType_Count]; + u32 untransmitted_count[ReportType_Count]; + util::Uuid journal_id; + u32 reserved[4]; + }; + static_assert(sizeof(JournalMeta) == 0x34); + + class JournalForMeta { + private: + static JournalMeta s_journal_meta; + public: + static void InitializeJournal(); + static Result CommitJournal(Stream *stream); + static Result RestoreJournal(Stream *stream); + static u32 GetTransmittedCount(ReportType type); + static u32 GetUntransmittedCount(ReportType type); + static void IncrementCount(bool transmitted, ReportType type); + static util::Uuid GetJournalId(); + }; + + class JournalForReports { + private: + using RecordListType = util::IntrusiveListBaseTraits<JournalRecord<ReportInfo>>::ListType; + static RecordListType s_record_list; + static u32 s_record_count; + static u32 s_record_count_by_type[ReportType_Count]; + static u32 s_used_storage; + private: + static void EraseReportImpl(JournalRecord<ReportInfo> *record, bool increment_count, bool force_delete_attachments); + public: + static void CleanupReports(); + static Result CommitJournal(Stream *stream); + static Result DeleteReport(ReportId report_id); + static Result DeleteReportWithAttachments(); + static s64 GetMaxReportSize(); + static Result GetReportList(ReportList *out, ReportType type_filter); + static u32 GetStoredReportCount(ReportType type); + static u32 GetUsedStorage(); + static Result RestoreJournal(Stream *stream); + + static JournalRecord<ReportInfo> *RetrieveRecord(ReportId report_id); + static Result StoreRecord(JournalRecord<ReportInfo> *record); + }; + + class JournalForAttachments { + private: + using AttachmentListType = util::IntrusiveListBaseTraits<JournalRecord<AttachmentInfo>>::ListType; + static AttachmentListType s_attachment_list; + static u32 s_attachment_count; + static u32 s_used_storage; + public: + static void CleanupAttachments(); + static Result CommitJournal(Stream *stream); + static Result DeleteAttachments(ReportId report_id); + static Result GetAttachmentList(u32 *out_count, AttachmentInfo *out_infos, size_t max_out_infos, ReportId report_id); + static u32 GetUsedStorage(); + static Result RestoreJournal(Stream *stream); + + static JournalRecord<AttachmentInfo> *RetrieveRecord(AttachmentId attachment_id); + static Result SetOwner(AttachmentId attachment_id, ReportId report_id); + static Result StoreRecord(JournalRecord<AttachmentInfo> *record); + + static Result SubmitAttachment(AttachmentId *out, char *name, const u8 *data, u32 data_size); + }; + + class Journal { + public: + static void CleanupAttachments(); + static void CleanupReports(); + static Result Commit(); + static Result Delete(ReportId report_id); + static Result GetAttachmentList(u32 *out_count, AttachmentInfo *out_infos, size_t max_out_infos, ReportId report_id); + static util::Uuid GetJournalId(); + static s64 GetMaxReportSize(); + static Result GetReportList(ReportList *out, ReportType type_filter); + static u32 GetStoredReportCount(ReportType type); + static u32 GetTransmittedCount(ReportType type); + static u32 GetUntransmittedCount(ReportType type); + static u32 GetUsedStorage(); + static Result Restore(); + + static JournalRecord<ReportInfo> *Retrieve(ReportId report_id); + static JournalRecord<AttachmentInfo> *Retrieve(AttachmentId attachment_id); + + static Result Store(JournalRecord<ReportInfo> *record); + static Result Store(JournalRecord<AttachmentInfo> *record); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_journal_for_attachments.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_journal_for_attachments.cpp new file mode 100644 index 00000000..40295138 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_journal_for_attachments.cpp @@ -0,0 +1,228 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_journal.hpp" +#include "erpt_srv_attachment.hpp" + +namespace ams::erpt::srv { + + constinit util::IntrusiveListBaseTraits<JournalRecord<AttachmentInfo>>::ListType JournalForAttachments::s_attachment_list; + constinit u32 JournalForAttachments::s_attachment_count = 0; + constinit u32 JournalForAttachments::s_used_storage = 0; + + namespace { + + constexpr inline u32 AttachmentUsedStorageMax = 4_MB; + + } + + void JournalForAttachments::CleanupAttachments() { + for (auto it = s_attachment_list.begin(); it != s_attachment_list.end(); /* ... */) { + auto *record = std::addressof(*it); + it = s_attachment_list.erase(s_attachment_list.iterator_to(*record)); + if (record->RemoveReference()) { + Stream::DeleteStream(Attachment::FileName(record->m_info.attachment_id).name); + delete record; + } + } + AMS_ASSERT(s_attachment_list.empty()); + + s_attachment_count = 0; + s_used_storage = 0; + + } + + Result JournalForAttachments::CommitJournal(Stream *stream) { + R_TRY(stream->WriteStream(reinterpret_cast<const u8 *>(std::addressof(s_attachment_count)), sizeof(s_attachment_count))); + for (auto it = s_attachment_list.crbegin(); it != s_attachment_list.crend(); it++) { + R_TRY(stream->WriteStream(reinterpret_cast<const u8 *>(std::addressof(it->m_info)), sizeof(it->m_info))); + } + R_SUCCEED(); + } + + Result JournalForAttachments::DeleteAttachments(ReportId report_id) { + for (auto it = s_attachment_list.begin(); it != s_attachment_list.end(); /* ... */) { + auto *record = std::addressof(*it); + if (record->m_info.owner_report_id == report_id) { + /* Erase from the list. */ + it = s_attachment_list.erase(s_attachment_list.iterator_to(*record)); + + /* Update storage tracking counts. */ + --s_attachment_count; + s_used_storage -= static_cast<u32>(record->m_info.attachment_size); + + /* Delete the object, if we should. */ + if (record->RemoveReference()) { + Stream::DeleteStream(Attachment::FileName(record->m_info.attachment_id).name); + delete record; + } + } else { + /* Not attached, just advance. */ + it++; + } + } + R_SUCCEED(); + } + + Result JournalForAttachments::GetAttachmentList(u32 *out_count, AttachmentInfo *out_infos, size_t max_out_infos, ReportId report_id) { + if (hos::GetVersion() >= hos::Version_20_0_0) { + /* TODO: What define gives a minimum of 10? */ + R_UNLESS(max_out_infos >= 10, erpt::ResultInvalidArgument()); + } + + u32 count = 0; + for (auto it = s_attachment_list.cbegin(); it != s_attachment_list.cend() && count < max_out_infos; it++) { + if (report_id == it->m_info.owner_report_id) { + out_infos[count++] = it->m_info; + } + } + *out_count = count; + R_SUCCEED(); + } + + u32 JournalForAttachments::GetUsedStorage() { + return s_used_storage; + } + + Result JournalForAttachments::RestoreJournal(Stream *stream) { + /* Clear the used storage. */ + s_used_storage = 0; + + /* Read the count from storage. */ + u32 read_size; + u32 count; + R_TRY(stream->ReadStream(std::addressof(read_size), reinterpret_cast<u8 *>(std::addressof(count)), sizeof(count))); + + R_UNLESS(read_size == sizeof(count), erpt::ResultCorruptJournal()); + R_UNLESS(count <= AttachmentCountMax, erpt::ResultCorruptJournal()); + + /* If we fail in the middle of reading reports, we want to do cleanup. */ + auto cleanup_guard = SCOPE_GUARD { CleanupAttachments(); }; + + AttachmentInfo info; + for (u32 i = 0; i < count; i++) { + R_TRY(stream->ReadStream(std::addressof(read_size), reinterpret_cast<u8 *>(std::addressof(info)), sizeof(info))); + + R_UNLESS(read_size == sizeof(info), erpt::ResultCorruptJournal()); + + auto *record = new JournalRecord<AttachmentInfo>(info); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + auto record_guard = SCOPE_GUARD { delete record; }; + + if (R_FAILED(Stream::GetStreamSize(std::addressof(record->m_info.attachment_size), Attachment::FileName(record->m_info.attachment_id).name))) { + continue; + } + + if (record->m_info.flags.Test<AttachmentFlag::HasOwner>() && JournalForReports::RetrieveRecord(record->m_info.owner_report_id) != nullptr) { + /* NOTE: Nintendo does not check the result of storing the new record... */ + record_guard.Cancel(); + StoreRecord(record); + } else { + /* If the attachment has no owner (or we deleted the report), delete the file associated with it. */ + Stream::DeleteStream(Attachment::FileName(record->m_info.attachment_id).name); + } + } + + cleanup_guard.Cancel(); + R_SUCCEED(); + } + + JournalRecord<AttachmentInfo> *JournalForAttachments::RetrieveRecord(AttachmentId attachment_id) { + for (auto it = s_attachment_list.begin(); it != s_attachment_list.end(); it++) { + if (auto *record = std::addressof(*it); record->m_info.attachment_id == attachment_id) { + return record; + } + } + return nullptr; + } + + Result JournalForAttachments::SetOwner(AttachmentId attachment_id, ReportId report_id) { + for (auto it = s_attachment_list.begin(); it != s_attachment_list.end(); it++) { + auto *record = std::addressof(*it); + if (record->m_info.attachment_id == attachment_id) { + R_UNLESS(!record->m_info.flags.Test<AttachmentFlag::HasOwner>(), erpt::ResultAlreadyOwned()); + + record->m_info.owner_report_id = report_id; + record->m_info.flags.Set<AttachmentFlag::HasOwner>(); + R_SUCCEED(); + } + } + R_THROW(erpt::ResultInvalidArgument()); + } + + Result JournalForAttachments::StoreRecord(JournalRecord<AttachmentInfo> *record) { + /* Check if the record already exists. */ + for (auto it = s_attachment_list.begin(); it != s_attachment_list.end(); it++) { + R_UNLESS(it->m_info.attachment_id != record->m_info.attachment_id, erpt::ResultAlreadyExists()); + } + + /* Add a reference to the new record. */ + record->AddReference(); + + /* Push the record into the list. */ + s_attachment_list.push_front(*record); + s_attachment_count++; + s_used_storage += static_cast<u32>(record->m_info.attachment_size); + + R_SUCCEED(); + } + + Result JournalForAttachments::SubmitAttachment(AttachmentId *out, char *name, const u8 *data, u32 data_size) { + R_UNLESS(data_size > 0, erpt::ResultInvalidArgument()); + R_UNLESS(data_size < AttachmentSizeMax, erpt::ResultInvalidArgument()); + + const auto name_len = std::strlen(name); + R_UNLESS(name_len < AttachmentNameSizeMax, erpt::ResultInvalidArgument()); + + /* Ensure that we have free space. */ + while (s_used_storage > AttachmentUsedStorageMax) { + R_TRY(JournalForReports::DeleteReportWithAttachments()); + } + + AttachmentInfo info; + info.attachment_id.uuid = util::GenerateUuid(); + info.flags = erpt::srv::MakeNoAttachmentFlags(); + info.attachment_size = data_size; + util::Strlcpy(info.attachment_name, name, sizeof(info.attachment_name)); + + auto *record = new JournalRecord<AttachmentInfo>(info); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + record->AddReference(); + ON_SCOPE_EXIT { + if (record->RemoveReference()) { + delete record; + } + }; + + { + auto attachment = std::make_unique<Attachment>(record); + R_UNLESS(attachment != nullptr, erpt::ResultOutOfMemory()); + + R_TRY(attachment->Open(AttachmentOpenType_Create)); + ON_SCOPE_EXIT { attachment->Close(); }; + + R_TRY(attachment->Write(data, data_size)); + R_TRY(StoreRecord(record)); + } + + *out = info.attachment_id; + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_journal_for_meta.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_journal_for_meta.cpp new file mode 100644 index 00000000..152b574f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_journal_for_meta.cpp @@ -0,0 +1,71 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_journal.hpp" + +namespace ams::erpt::srv { + + constinit JournalMeta JournalForMeta::s_journal_meta = {}; + + void JournalForMeta::InitializeJournal() { + std::memset(std::addressof(s_journal_meta), 0, sizeof(s_journal_meta)); + s_journal_meta.journal_id = util::GenerateUuid(); + s_journal_meta.version = JournalVersion; + } + + Result JournalForMeta::CommitJournal(Stream *stream) { + R_RETURN(stream->WriteStream(reinterpret_cast<const u8 *>(std::addressof(s_journal_meta)), sizeof(s_journal_meta))); + } + + Result JournalForMeta::RestoreJournal(Stream *stream) { + u32 size; + if (R_FAILED(stream->ReadStream(std::addressof(size), reinterpret_cast<u8 *>(std::addressof(s_journal_meta)), sizeof(s_journal_meta))) || size != sizeof(s_journal_meta)) { + InitializeJournal(); + } + R_SUCCEED(); + } + + u32 JournalForMeta::GetTransmittedCount(ReportType type) { + if (ReportType_Start <= type && type < ReportType_End) { + return s_journal_meta.transmitted_count[type]; + } else { + return 0; + } + } + + u32 JournalForMeta::GetUntransmittedCount(ReportType type) { + if (ReportType_Start <= type && type < ReportType_End) { + return s_journal_meta.untransmitted_count[type]; + } else { + return 0; + } + } + + void JournalForMeta::IncrementCount(bool transmitted, ReportType type) { + if (ReportType_Start <= type && type < ReportType_End) { + if (transmitted) { + s_journal_meta.transmitted_count[type]++; + } else { + s_journal_meta.untransmitted_count[type]++; + } + } + } + + util::Uuid JournalForMeta::GetJournalId() { + return s_journal_meta.journal_id; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_journal_for_reports.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_journal_for_reports.cpp new file mode 100644 index 00000000..87c7f8d7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_journal_for_reports.cpp @@ -0,0 +1,228 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_journal.hpp" +#include "erpt_srv_report.hpp" + +namespace ams::erpt::srv { + + constinit util::IntrusiveListBaseTraits<JournalRecord<ReportInfo>>::ListType JournalForReports::s_record_list; + constinit u32 JournalForReports::s_record_count = 0; + constinit u32 JournalForReports::s_record_count_by_type[ReportType_Count] = {}; + constinit u32 JournalForReports::s_used_storage = 0; + + void JournalForReports::CleanupReports() { + for (auto it = s_record_list.begin(); it != s_record_list.end(); /* ... */) { + auto *record = std::addressof(*it); + it = s_record_list.erase(s_record_list.iterator_to(*record)); + if (record->RemoveReference()) { + Stream::DeleteStream(Report::FileName(record->m_info.id, false).name); + delete record; + } + } + AMS_ASSERT(s_record_list.empty()); + + s_record_count = 0; + s_used_storage = 0; + + std::memset(s_record_count_by_type, 0, sizeof(s_record_count_by_type)); + } + + Result JournalForReports::CommitJournal(Stream *stream) { + R_TRY(stream->WriteStream(reinterpret_cast<const u8 *>(std::addressof(s_record_count)), sizeof(s_record_count))); + for (auto it = s_record_list.crbegin(); it != s_record_list.crend(); it++) { + R_TRY(stream->WriteStream(reinterpret_cast<const u8 *>(std::addressof(it->m_info)), sizeof(it->m_info))); + } + R_SUCCEED(); + } + + void JournalForReports::EraseReportImpl(JournalRecord<ReportInfo> *record, bool increment_count, bool force_delete_attachments) { + /* Erase from the list. */ + s_record_list.erase(s_record_list.iterator_to(*record)); + + /* Update storage tracking counts. */ + --s_record_count; + --s_record_count_by_type[record->m_info.type]; + s_used_storage -= static_cast<u32>(record->m_info.report_size); + + /* If we should increment count, do so. */ + if (increment_count) { + JournalForMeta::IncrementCount(record->m_info.flags.Test<ReportFlag::Transmitted>(), record->m_info.type); + } + + /* Delete any attachments. */ + if (force_delete_attachments || record->m_info.flags.Test<ReportFlag::HasAttachment>()) { + JournalForAttachments::DeleteAttachments(record->m_info.id); + } + + /* Delete the object, if we should. */ + if (record->RemoveReference()) { + Stream::DeleteStream(Report::FileName(record->m_info.id, false).name); + delete record; + } + } + + Result JournalForReports::DeleteReport(ReportId report_id) { + for (auto it = s_record_list.begin(); it != s_record_list.end(); it++) { + auto *record = std::addressof(*it); + if (record->m_info.id == report_id) { + EraseReportImpl(record, false, false); + R_SUCCEED(); + } + } + R_THROW(erpt::ResultInvalidArgument()); + } + + Result JournalForReports::DeleteReportWithAttachments() { + for (auto it = s_record_list.rbegin(); it != s_record_list.rend(); it++) { + auto *record = std::addressof(*it); + if (record->m_info.flags.Test<ReportFlag::HasAttachment>()) { + EraseReportImpl(record, true, true); + R_SUCCEED(); + } + } + R_THROW(erpt::ResultNotFound()); + } + + s64 JournalForReports::GetMaxReportSize() { + s64 max_size = 0; + for (auto it = s_record_list.begin(); it != s_record_list.end(); it++) { + max_size = std::max(max_size, it->m_info.report_size); + } + return max_size; + } + + Result JournalForReports::GetReportList(ReportList *out, ReportType type_filter) { + u32 count = 0; + for (auto it = s_record_list.cbegin(); it != s_record_list.cend() && count < util::size(out->reports); it++) { + if (type_filter == ReportType_Any || type_filter == it->m_info.type) { + out->reports[count++] = it->m_info; + } + } + out->report_count = count; + R_SUCCEED(); + } + + u32 JournalForReports::GetStoredReportCount(ReportType type) { + if (ReportType_Start <= type && type < ReportType_End) { + return s_record_count_by_type[type]; + } else { + return 0; + } + } + + u32 JournalForReports::GetUsedStorage() { + return s_used_storage; + } + + Result JournalForReports::RestoreJournal(Stream *stream) { + /* Clear the used storage. */ + s_used_storage = 0; + + /* Read the count from storage. */ + u32 read_size; + u32 count; + R_TRY(stream->ReadStream(std::addressof(read_size), reinterpret_cast<u8 *>(std::addressof(count)), sizeof(count))); + + R_UNLESS(read_size == sizeof(count), erpt::ResultCorruptJournal()); + R_UNLESS(count <= ReportCountMax, erpt::ResultCorruptJournal()); + + /* If we fail in the middle of reading reports, we want to do cleanup. */ + auto cleanup_guard = SCOPE_GUARD { CleanupReports(); }; + + ReportInfo info; + for (u32 i = 0; i < count; i++) { + R_TRY(stream->ReadStream(std::addressof(read_size), reinterpret_cast<u8 *>(std::addressof(info)), sizeof(info))); + + R_UNLESS(read_size == sizeof(info), erpt::ResultCorruptJournal()); + R_UNLESS(ReportType_Start <= info.type, erpt::ResultCorruptJournal()); + R_UNLESS(info.type < ReportType_End, erpt::ResultCorruptJournal()); + + auto *record = new JournalRecord<ReportInfo>(info); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + /* NOTE: Nintendo does not ensure that the newly allocated record does not leak in the failure case. */ + /* We will ensure it is freed if we early error. */ + auto record_guard = SCOPE_GUARD { delete record; }; + + if (record->m_info.report_size == 0) { + R_UNLESS(R_SUCCEEDED(Stream::GetStreamSize(std::addressof(record->m_info.report_size), Report::FileName(record->m_info.id, false).name)), erpt::ResultCorruptJournal()); + } + + record_guard.Cancel(); + + /* NOTE: Nintendo does not check the result of storing the new record... */ + StoreRecord(record); + } + + cleanup_guard.Cancel(); + R_SUCCEED(); + } + + JournalRecord<ReportInfo> *JournalForReports::RetrieveRecord(ReportId report_id) { + for (auto it = s_record_list.begin(); it != s_record_list.end(); it++) { + if (auto *record = std::addressof(*it); record->m_info.id == report_id) { + return record; + } + } + + return nullptr; + } + + Result JournalForReports::StoreRecord(JournalRecord<ReportInfo> *record) { + /* Check if the record already exists. */ + for (auto it = s_record_list.begin(); it != s_record_list.end(); it++) { + R_UNLESS(it->m_info.id != record->m_info.id, erpt::ResultAlreadyExists()); + } + + /* Delete an older report if we need to. */ + if (s_record_count >= ReportCountMax) { + /* Nintendo deletes the oldest report from the type with the most reports. */ + /* This is an approximation of FIFO. */ + ReportType most_used_type = record->m_info.type; + u32 most_used_count = s_record_count_by_type[most_used_type]; + + for (int i = ReportType_Start; i < ReportType_End; i++) { + if (s_record_count_by_type[i] > most_used_count) { + most_used_type = static_cast<ReportType>(i); + most_used_count = s_record_count_by_type[i]; + } + } + + for (auto it = s_record_list.rbegin(); it != s_record_list.rend(); it++) { + if (it->m_info.type != most_used_type) { + continue; + } + + EraseReportImpl(std::addressof(*it), true, false); + break; + } + } + AMS_ASSERT(s_record_count < ReportCountMax); + + /* Add a reference to the new record. */ + record->AddReference(); + + /* Push the record into the list. */ + s_record_list.push_front(*record); + s_record_count++; + s_record_count_by_type[record->m_info.type]++; + s_used_storage += static_cast<u32>(record->m_info.report_size); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_journal_record.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_journal_record.hpp new file mode 100644 index 00000000..484ffde5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_journal_record.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "erpt_srv_allocator.hpp" +#include "erpt_srv_ref_count.hpp" + +namespace ams::erpt::srv { + + template<typename Info> + class JournalRecord : public Allocator, public RefCount, public util::IntrusiveListBaseNode<JournalRecord<Info>> { + public: + Info m_info; + + JournalRecord() { + std::memset(std::addressof(m_info), 0, sizeof(m_info)); + } + + explicit JournalRecord(Info info) : m_info(info) { /* ... */ } + + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_keys.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_keys.cpp new file mode 100644 index 00000000..95f226a4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_keys.cpp @@ -0,0 +1,99 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_keys.hpp" + +namespace ams::erpt::srv { + + namespace { + + constexpr const u8 PublicKeyModulusProduction[] = { + 0x00, + 0xAB, 0xB6, 0x6C, 0x43, 0x86, 0xDF, 0x52, 0x5F, 0xB9, 0xD3, 0x61, 0xEB, 0xFB, 0x16, 0x2B, 0x44, + 0xE1, 0x1F, 0xDD, 0x81, 0xFA, 0x20, 0x48, 0x7B, 0xDB, 0x17, 0x9F, 0x9A, 0x92, 0x1E, 0x0A, 0x80, + 0xD1, 0x1A, 0x4F, 0xD7, 0x49, 0xFE, 0xBA, 0x65, 0xE4, 0x61, 0x08, 0x26, 0x43, 0x7B, 0x4A, 0x16, + 0x59, 0x60, 0xD1, 0xE0, 0x42, 0x0A, 0x26, 0x54, 0xC4, 0xC7, 0x2A, 0xE3, 0x17, 0x1F, 0x8E, 0x35, + 0x79, 0xC0, 0x1B, 0xA1, 0xF8, 0x6F, 0x5C, 0xDC, 0x05, 0x2D, 0x90, 0x75, 0xD5, 0x98, 0x7E, 0x5A, + 0x07, 0x3F, 0x4E, 0x78, 0xF1, 0x69, 0x2F, 0xE9, 0x2E, 0x50, 0x01, 0x9F, 0xCE, 0x35, 0xC8, 0x4D, + 0x65, 0x23, 0xA8, 0x9F, 0xC3, 0x3C, 0x4A, 0xF2, 0x29, 0x4D, 0x10, 0x03, 0x1C, 0xB4, 0x0E, 0x64, + 0xB6, 0xDD, 0xB2, 0x74, 0xE3, 0x32, 0x84, 0x25, 0x99, 0xEA, 0xE1, 0x6C, 0x78, 0x24, 0xF2, 0xB0, + 0xD2, 0x2C, 0xA5, 0x1A, 0x70, 0xA6, 0x49, 0x08, 0x73, 0x8A, 0x74, 0x3A, 0x12, 0x0E, 0x1B, 0x68, + 0xD1, 0x6A, 0x6C, 0x3F, 0x2C, 0x2C, 0x53, 0xD5, 0xCE, 0x5A, 0x07, 0xA2, 0xB9, 0x2E, 0x0A, 0x77, + 0x51, 0x4B, 0xD2, 0x8E, 0x4F, 0xA3, 0xA8, 0x56, 0x99, 0x6F, 0x63, 0xBC, 0x23, 0x04, 0x6E, 0x71, + 0x57, 0x7C, 0xFD, 0x84, 0xA7, 0xF8, 0x8D, 0x7F, 0xD6, 0xA0, 0x6E, 0x92, 0xBC, 0xCC, 0x28, 0x82, + 0x60, 0xE9, 0x78, 0xC1, 0x31, 0x82, 0x4F, 0xF8, 0xC5, 0xDB, 0xB6, 0x6B, 0xF9, 0x62, 0x95, 0xD3, + 0xC8, 0x63, 0x59, 0x53, 0x3F, 0x82, 0xEB, 0x06, 0xA7, 0xB8, 0x55, 0xEC, 0x9E, 0x33, 0x04, 0xCF, + 0x5E, 0x42, 0x32, 0x09, 0x26, 0xFF, 0xB4, 0x5E, 0xBD, 0xD7, 0xA8, 0x6B, 0x2C, 0xF5, 0x68, 0x86, + 0xCD, 0x8A, 0x13, 0xF3, 0x1C, 0x5F, 0xE6, 0x4F, 0xFC, 0xD1, 0x07, 0x28, 0x5C, 0x2D, 0xA7, 0xF7 + }; + + constexpr const u8 PublicKeyModulusDevelopment[] = { + 0x00, + 0xAE, 0x7D, 0x6C, 0xD0, 0xC3, 0x13, 0x61, 0x01, 0x9D, 0x1B, 0x55, 0xA0, 0xE5, 0xF4, 0x3D, 0x56, + 0x7D, 0xCA, 0x0E, 0x49, 0xFB, 0x82, 0x08, 0x06, 0x33, 0xB6, 0x37, 0xB3, 0x4A, 0x3F, 0x57, 0x39, + 0x05, 0x84, 0x18, 0x3D, 0x82, 0xD8, 0x8F, 0xBC, 0xF3, 0xE1, 0x66, 0xEE, 0xD2, 0x80, 0x43, 0xF8, + 0xA7, 0xB7, 0x5E, 0x5B, 0x5C, 0xF9, 0x9D, 0x7F, 0xE9, 0x6C, 0x93, 0x8A, 0x65, 0xBB, 0xD1, 0xDD, + 0x56, 0xFF, 0x7C, 0x9D, 0x24, 0x66, 0x09, 0x84, 0x21, 0x2C, 0x7F, 0x0A, 0xB8, 0x31, 0x42, 0x29, + 0xE6, 0xD3, 0x20, 0x76, 0xA1, 0x1F, 0x7E, 0x59, 0x5B, 0x7C, 0xF6, 0xC6, 0x02, 0xDB, 0xC9, 0x1B, + 0xB9, 0x24, 0x99, 0xAD, 0x0F, 0x7B, 0x0D, 0x8E, 0x7E, 0x01, 0xFE, 0x95, 0xCE, 0x9B, 0xB5, 0x09, + 0xC5, 0xF5, 0xA5, 0x6A, 0x82, 0xF6, 0x57, 0xF8, 0x06, 0x72, 0xAE, 0x73, 0x71, 0xD1, 0x09, 0x2B, + 0xE2, 0x84, 0x0D, 0x66, 0x39, 0xB6, 0x21, 0x8B, 0x35, 0xE4, 0xDF, 0x90, 0x36, 0xE1, 0x3F, 0xC0, + 0x9F, 0xF8, 0x85, 0x03, 0xD6, 0xCA, 0xBB, 0x1A, 0x62, 0x2D, 0xE5, 0x03, 0xF6, 0x47, 0x00, 0x6E, + 0x98, 0x5A, 0x1C, 0x51, 0x94, 0x47, 0xF6, 0x83, 0x0C, 0x25, 0xBD, 0xBE, 0xBD, 0x6A, 0x35, 0xC0, + 0xAB, 0x65, 0xF8, 0x01, 0xF4, 0xC3, 0x2A, 0xA3, 0xBC, 0xD7, 0xD9, 0xF7, 0x2A, 0x98, 0x27, 0xE1, + 0x3F, 0x9A, 0xCF, 0xDF, 0xB1, 0x30, 0x82, 0xA4, 0xAA, 0x78, 0xCA, 0xC8, 0xB8, 0x34, 0xFA, 0xA7, + 0x75, 0x23, 0xC9, 0x9C, 0x11, 0x68, 0x7E, 0x0F, 0x80, 0x8F, 0x90, 0xA6, 0xDE, 0x2B, 0x47, 0x5B, + 0x94, 0x6F, 0xB9, 0x67, 0x4C, 0xC1, 0xAE, 0x50, 0x8F, 0xD8, 0xE3, 0xD1, 0xF2, 0x92, 0x54, 0x4C, + 0x25, 0x02, 0x5B, 0x31, 0x65, 0x5E, 0x41, 0x81, 0x34, 0xF4, 0xF1, 0x34, 0xE7, 0x64, 0x7A, 0xC1 + }; + + constexpr const u8 PublicKeyExponent[] = { + 0x01, 0x00, 0x01 + }; + + bool IsProductionModeImpl() { + bool is_prod = true; + if (settings::fwdbg::GetSettingsItemValue(std::addressof(is_prod), sizeof(is_prod), "erpt", "production_mode") != sizeof(is_prod)) { + return true; + } + return is_prod; + } + + bool IsProductionMode() { + AMS_FUNCTION_LOCAL_STATIC(bool, s_is_prod_mode, IsProductionModeImpl()); + + return s_is_prod_mode; + } + + } + + const u8 *GetPublicKeyModulus() { + return IsProductionMode() ? PublicKeyModulusProduction : PublicKeyModulusDevelopment; + } + + size_t GetPublicKeyModulusSize() { + return IsProductionMode() ? sizeof(PublicKeyModulusProduction) : sizeof(PublicKeyModulusDevelopment); + } + + const u8 *GetPublicKeyExponent() { + return PublicKeyExponent; + } + + size_t GetPublicKeyExponentSize() { + return sizeof(PublicKeyExponent); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_keys.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_keys.hpp new file mode 100644 index 00000000..21f53d96 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_keys.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::erpt::srv { + + const u8 *GetPublicKeyModulus(); + size_t GetPublicKeyModulusSize(); + + const u8 *GetPublicKeyExponent(); + size_t GetPublicKeyExponentSize(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_main.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_main.cpp new file mode 100644 index 00000000..8fb68224 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_main.cpp @@ -0,0 +1,178 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_allocator.hpp" +#include "erpt_srv_context_record.hpp" +#include "erpt_srv_context.hpp" +#include "erpt_srv_reporter.hpp" +#include "erpt_srv_journal.hpp" +#include "erpt_srv_service.hpp" +#include "erpt_srv_forced_shutdown.hpp" + +namespace ams::erpt::srv { + + constinit lmem::HeapHandle g_heap_handle; + constinit ams::sf::ExpHeapAllocator g_sf_allocator = {}; + + namespace { + + constexpr fs::SystemSaveDataId SystemSaveDataId = 0x80000000000000D1; + constexpr u32 SystemSaveDataFlags = fs::SaveDataFlags_KeepAfterResettingSystemSaveDataWithoutUserSaveData; + constexpr s64 SystemSaveDataSize = 11_MB; + constexpr s64 SystemSaveDataJournalSize = 2720_KB; + + constinit bool g_automatic_report_cleanup_enabled = true; + + Result ExtendSystemSaveData() { + s64 cur_journal_size; + s64 cur_savedata_size; + + R_TRY(fs::GetSaveDataJournalSize(std::addressof(cur_journal_size), SystemSaveDataId)); + R_TRY(fs::GetSaveDataAvailableSize(std::addressof(cur_savedata_size), SystemSaveDataId)); + + if (cur_journal_size < SystemSaveDataJournalSize || cur_savedata_size < SystemSaveDataSize) { + if (hos::GetVersion() >= hos::Version_3_0_0) { + R_TRY(fs::ExtendSaveData(fs::SaveDataSpaceId::System, SystemSaveDataId, SystemSaveDataSize, SystemSaveDataJournalSize)); + } + } + + R_SUCCEED(); + } + + Result MountSystemSaveData() { + fs::DisableAutoSaveDataCreation(); + + /* Extend the system save data. */ + /* NOTE: Nintendo does not check result of this. */ + ExtendSystemSaveData(); + + R_TRY_CATCH(fs::MountSystemSaveData(ReportStoragePath, SystemSaveDataId)) { + R_CATCH(fs::ResultTargetNotFound) { + R_TRY(fs::CreateSystemSaveData(SystemSaveDataId, SystemSaveDataSize, SystemSaveDataJournalSize, SystemSaveDataFlags)); + R_TRY(fs::MountSystemSaveData(ReportStoragePath, SystemSaveDataId)); + } + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + } + + Result Initialize(u8 *mem, size_t mem_size) { + R_ABORT_UNLESS(time::Initialize()); + + g_heap_handle = lmem::CreateExpHeap(mem, mem_size, lmem::CreateOption_ThreadSafe); + AMS_ABORT_UNLESS(g_heap_handle != nullptr); + + fs::InitializeForSystem(); + fs::SetAllocator(Allocate, DeallocateWithSize); + fs::SetEnabledAutoAbort(false); + + R_ABORT_UNLESS(fs::MountSdCardErrorReportDirectoryForAtmosphere(ReportOnSdStoragePath)); + + if (g_automatic_report_cleanup_enabled) { + constexpr s64 MinimumReportCountForCleanup = 1000; + s64 report_count = MinimumReportCountForCleanup; + + fs::DirectoryHandle dir; + if (R_SUCCEEDED(fs::OpenDirectory(std::addressof(dir), ReportOnSdStorageRootDirectoryPath, fs::OpenDirectoryMode_All))) { + ON_SCOPE_EXIT { fs::CloseDirectory(dir); }; + + if (R_FAILED(fs::GetDirectoryEntryCount(std::addressof(report_count), dir))) { + report_count = MinimumReportCountForCleanup; + } + } + + if (report_count >= MinimumReportCountForCleanup) { + fs::CleanDirectoryRecursively(ReportOnSdStorageRootDirectoryPath); + } + } + + R_ABORT_UNLESS(MountSystemSaveData()); + + g_sf_allocator.Attach(g_heap_handle); + + for (const auto category_id : CategoryIndexToCategoryIdMap) { + Context *ctx = new Context(category_id); + AMS_ABORT_UNLESS(ctx != nullptr); + } + + Journal::Restore(); + + Reporter::UpdatePowerOnTime(); + Reporter::UpdateAwakeTime(); + + R_SUCCEED(); + } + + Result InitializeAndStartService() { + /* Initialize forced shutdown detection. */ + /* NOTE: Nintendo does not check error code here. */ + InitializeForcedShutdownDetection(); + + R_RETURN(InitializeService()); + } + + Result SetSerialNumberAndOsVersion(const char *sn, u32 sn_len, const char *os, u32 os_len, const char *os_priv, u32 os_priv_len) { + R_RETURN(Reporter::SetSerialNumberAndOsVersion(sn, sn_len, os, os_len, os_priv, os_priv_len)); + } + + Result SetProductModel(const char *model, u32 model_len) { + /* NOTE: Nintendo does not check that this allocation succeeds. */ + auto record = std::make_unique<ContextRecord>(CategoryId_ProductModelInfo); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + R_TRY(record->Add(FieldId_ProductModel, model, model_len)); + R_TRY(Context::SubmitContextRecord(std::move(record))); + + R_SUCCEED(); + } + + Result SetRegionSetting(const char *region, u32 region_len) { + /* NOTE: Nintendo does not check that this allocation succeeds. */ + auto record = std::make_unique<ContextRecord>(CategoryId_RegionSettingInfo); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + R_TRY(record->Add(FieldId_RegionSetting, region, region_len)); + R_TRY(Context::SubmitContextRecord(std::move(record))); + + R_SUCCEED(); + } + + Result SetRedirectNewReportsToSdCard(bool redirect) { + Reporter::SetRedirectNewReportsToSdCard(redirect); + R_SUCCEED(); + } + + Result SetEnabledAutomaticReportCleanup(bool en) { + g_automatic_report_cleanup_enabled = en; + R_SUCCEED(); + } + + void Wait() { + /* Get the update event. */ + os::Event *event = GetForcedShutdownUpdateEvent(); + + /* Forever wait, saving any updates. */ + while (true) { + event->Wait(); + event->Clear(); + SaveForcedShutdownContext(); + } + } + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_manager_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_manager_impl.cpp new file mode 100644 index 00000000..2130a520 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_manager_impl.cpp @@ -0,0 +1,108 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_manager_impl.hpp" +#include "erpt_srv_journal.hpp" + +namespace ams::erpt::srv { + + namespace { + + using ManagerList = util::IntrusiveListBaseTraits<ManagerImpl>::ListType; + + constinit ManagerList g_manager_list; + + } + + ManagerImpl::ManagerImpl() : m_system_event(os::EventClearMode_AutoClear, true) { + g_manager_list.push_front(*this); + } + + ManagerImpl::~ManagerImpl() { + g_manager_list.erase(g_manager_list.iterator_to(*this)); + } + + void ManagerImpl::NotifyOne() { + m_system_event.Signal(); + } + + Result ManagerImpl::NotifyAll() { + for (auto &manager : g_manager_list) { + manager.NotifyOne(); + } + R_SUCCEED(); + } + + Result ManagerImpl::GetReportList(const ams::sf::OutBuffer &out_list, ReportType type_filter) { + R_UNLESS(out_list.GetSize() == sizeof(ReportList), erpt::ResultInvalidArgument()); + + R_RETURN(Journal::GetReportList(reinterpret_cast<ReportList *>(out_list.GetPointer()), type_filter)); + } + + Result ManagerImpl::GetEvent(ams::sf::OutCopyHandle out) { + out.SetValue(m_system_event.GetReadableHandle(), false); + R_SUCCEED(); + } + + Result ManagerImpl::CleanupReports() { + Journal::CleanupReports(); + Journal::CleanupAttachments(); + R_RETURN(Journal::Commit()); + } + + Result ManagerImpl::DeleteReport(const ReportId &report_id) { + R_TRY(Journal::Delete(report_id)); + R_RETURN(Journal::Commit()); + } + + Result ManagerImpl::GetStorageUsageStatistics(ams::sf::Out<StorageUsageStatistics> out) { + StorageUsageStatistics stats = {}; + + stats.journal_uuid = Journal::GetJournalId(); + stats.used_storage_size = Journal::GetUsedStorage(); + stats.max_report_size = Journal::GetMaxReportSize(); + + for (int i = ReportType_Start; i < ReportType_End; i++) { + const auto type = static_cast<ReportType>(i); + stats.report_count[i] = Journal::GetStoredReportCount(type); + stats.transmitted_count[i] = Journal::GetTransmittedCount(type); + stats.untransmitted_count[i] = Journal::GetUntransmittedCount(type); + } + + out.SetValue(stats); + R_SUCCEED(); + } + + Result ManagerImpl::GetAttachmentListDeprecated(const ams::sf::OutBuffer &out_list, const ReportId &report_id) { + R_UNLESS(out_list.GetSize() == sizeof(AttachmentList), erpt::ResultInvalidArgument()); + + auto *attachment_list = reinterpret_cast<AttachmentList *>(out_list.GetPointer()); + + R_RETURN(Journal::GetAttachmentList(std::addressof(attachment_list->attachment_count), attachment_list->attachments, util::size(attachment_list->attachments), report_id)); + } + + Result ManagerImpl::GetAttachmentList(ams::sf::Out<u32> out_count, const ams::sf::OutBuffer &out_buf, const ReportId &report_id) { + R_RETURN(Journal::GetAttachmentList(out_count.GetPointer(), reinterpret_cast<AttachmentInfo *>(out_buf.GetPointer()), out_buf.GetSize() / sizeof(AttachmentInfo), report_id)); + } + + Result ManagerImpl::GetReportSizeMax(ams::sf::Out<u32> out) { + /* TODO: Where is this size defined? */ + constexpr size_t ReportSizeMax = 0x3FF4F; + *out = ReportSizeMax; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_manager_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_manager_impl.hpp new file mode 100644 index 00000000..24046b29 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_manager_impl.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::erpt::srv { + + class ManagerImpl : public util::IntrusiveListBaseNode<ManagerImpl> { + private: + os::SystemEvent m_system_event; + public: + ManagerImpl(); + ~ManagerImpl(); + private: + void NotifyOne(); + public: + static Result NotifyAll(); + public: + Result GetReportList(const ams::sf::OutBuffer &out_list, ReportType type_filter); + Result GetEvent(ams::sf::OutCopyHandle out); + Result CleanupReports(); + Result DeleteReport(const ReportId &report_id); + Result GetStorageUsageStatistics(ams::sf::Out<StorageUsageStatistics> out); + Result GetAttachmentListDeprecated(const ams::sf::OutBuffer &out_buf, const ReportId &report_id); + Result GetAttachmentList(ams::sf::Out<u32> out_count, const ams::sf::OutBuffer &out_buf, const ReportId &report_id); + Result GetReportSizeMax(ams::sf::Out<u32> out); + }; + static_assert(erpt::sf::IsIManager<ManagerImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_ref_count.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_ref_count.hpp new file mode 100644 index 00000000..ec4f0047 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_ref_count.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::erpt::srv { + + class RefCount { + private: + static constexpr u32 MaxReferenceCount = 1000; + std::atomic<u32> m_ref_count; + public: + RefCount() : m_ref_count(0) { /* ... */ } + + void AddReference() { + const auto prev = m_ref_count.fetch_add(1); + AMS_ABORT_UNLESS(prev <= MaxReferenceCount); + } + + bool RemoveReference() { + auto prev = m_ref_count.fetch_sub(1); + AMS_ABORT_UNLESS(prev != 0); + return prev == 1; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_report.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_report.cpp new file mode 100644 index 00000000..67f92781 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_report.cpp @@ -0,0 +1,90 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_report_impl.hpp" +#include "erpt_srv_report.hpp" + +namespace ams::erpt::srv { + + ReportFileName Report::FileName(ReportId report_id, bool redirect_to_sd) { + ReportFileName report_name; + util::SNPrintf(report_name.name, sizeof(report_name.name), + "%s:/%08x-%04x-%04x-%02x%02x-%04x%08x", + (redirect_to_sd ? ReportOnSdStoragePath : ReportStoragePath), + report_id.uuid_data.time_low, + report_id.uuid_data.time_mid, + report_id.uuid_data.time_high_and_version, + report_id.uuid_data.clock_high, + report_id.uuid_data.clock_low, + static_cast<u32>((report_id.uuid_data.node >> BITSIZEOF(u32)) & 0x0000FFFF), + static_cast<u32>((report_id.uuid_data.node >> 0) & 0xFFFFFFFF)); + return report_name; + } + + Report::Report(JournalRecord<ReportInfo> *r, bool redirect_to_sd) : m_record(r), m_redirect_to_sd_card(redirect_to_sd) { + m_record->AddReference(); + } + + Report::~Report() { + this->CloseStream(); + if (m_record->RemoveReference()) { + this->DeleteStream(this->FileName().name); + delete m_record; + } + } + + ReportFileName Report::FileName() const { + return FileName(m_record->m_info.id, m_redirect_to_sd_card); + } + + Result Report::Open(ReportOpenType type) { + switch (type) { + case ReportOpenType_Create: R_RETURN(this->OpenStream(this->FileName().name, StreamMode_Write, ReportStreamBufferSize)); + case ReportOpenType_Read: R_RETURN(this->OpenStream(this->FileName().name, StreamMode_Read, ReportStreamBufferSize)); + default: R_THROW(erpt::ResultInvalidArgument()); + } + } + + Result Report::Read(u32 *out_read_count, u8 *dst, u32 dst_size) { + R_RETURN(this->ReadStream(out_read_count, dst, dst_size)); + } + + Result Report::Delete() { + R_RETURN(this->DeleteStream(this->FileName().name)); + } + + void Report::Close() { + return this->CloseStream(); + } + + Result Report::GetFlags(ReportFlagSet *out) const { + *out = m_record->m_info.flags; + R_SUCCEED(); + } + + Result Report::SetFlags(ReportFlagSet flags) { + if (((~m_record->m_info.flags) & flags).IsAnySet()) { + m_record->m_info.flags |= flags; + R_RETURN(Journal::Commit()); + } + R_SUCCEED(); + } + + Result Report::GetSize(s64 *out) const { + R_RETURN(this->GetStreamSize(out)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_report.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_report.hpp new file mode 100644 index 00000000..c231439c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_report.hpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "erpt_srv_allocator.hpp" +#include "erpt_srv_stream.hpp" +#include "erpt_srv_journal.hpp" + +namespace ams::erpt::srv { + + enum ReportOpenType { + ReportOpenType_Create = 0, + ReportOpenType_Read = 1, + }; + + constexpr inline u32 ReportStreamBufferSize = 1_KB; + + class Report : public Allocator, public Stream { + private: + JournalRecord<ReportInfo> *m_record; + bool m_redirect_to_sd_card; + private: + ReportFileName FileName() const; + public: + static ReportFileName FileName(ReportId report_id, bool redirect_to_sd); + public: + explicit Report(JournalRecord<ReportInfo> *r, bool redirect_to_sd); + ~Report(); + + Result Open(ReportOpenType type); + Result Read(u32 *out_read_count, u8 *dst, u32 dst_size); + Result Delete(); + void Close(); + + Result GetFlags(ReportFlagSet *out) const; + Result SetFlags(ReportFlagSet flags); + Result GetSize(s64 *out) const; + + template<typename T> + Result Write(T val) { + R_RETURN(this->WriteStream(reinterpret_cast<const u8 *>(std::addressof(val)), sizeof(val))); + } + + template<typename T> + Result Write(const T *buf, u32 buffer_size) { + R_RETURN(this->WriteStream(reinterpret_cast<const u8 *>(buf), buffer_size)); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_report_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_report_impl.cpp new file mode 100644 index 00000000..f3c53067 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_report_impl.cpp @@ -0,0 +1,79 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_report_impl.hpp" +#include "erpt_srv_report.hpp" + +namespace ams::erpt::srv { + + ReportImpl::ReportImpl() : m_report(nullptr) { + /* ... */ + } + + ReportImpl::~ReportImpl() { + R_ABORT_UNLESS(this->Close()); + } + + Result ReportImpl::Open(const ReportId &report_id) { + R_UNLESS(m_report == nullptr, erpt::ResultAlreadyInitialized()); + + JournalRecord<ReportInfo> *record = Journal::Retrieve(report_id); + R_UNLESS(record != nullptr, erpt::ResultNotFound()); + + m_report = new Report(record, false); + R_UNLESS(m_report != nullptr, erpt::ResultOutOfMemory()); + auto report_guard = SCOPE_GUARD { delete m_report; m_report = nullptr; }; + + R_TRY(m_report->Open(ReportOpenType_Read)); + report_guard.Cancel(); + + R_SUCCEED(); + } + + Result ReportImpl::Read(ams::sf::Out<u32> out_count, const ams::sf::OutBuffer &out_buffer) { + R_UNLESS(m_report != nullptr, erpt::ResultNotInitialized()); + + R_RETURN(m_report->Read(out_count.GetPointer(), static_cast<u8 *>(out_buffer.GetPointer()), static_cast<u32>(out_buffer.GetSize()))); + } + + Result ReportImpl::SetFlags(ReportFlagSet flags) { + R_UNLESS(m_report != nullptr, erpt::ResultNotInitialized()); + + R_RETURN(m_report->SetFlags(flags)); + } + + Result ReportImpl::GetFlags(ams::sf::Out<ReportFlagSet> out) { + R_UNLESS(m_report != nullptr, erpt::ResultNotInitialized()); + + R_RETURN(m_report->GetFlags(out.GetPointer())); + } + + Result ReportImpl::Close() { + if (m_report != nullptr) { + m_report->Close(); + delete m_report; + m_report = nullptr; + } + R_SUCCEED(); + } + + Result ReportImpl::GetSize(ams::sf::Out<s64> out) { + R_UNLESS(m_report != nullptr, erpt::ResultNotInitialized()); + + R_RETURN(m_report->GetSize(out.GetPointer())); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_report_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_report_impl.hpp new file mode 100644 index 00000000..f6ac7735 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_report_impl.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::erpt::srv { + + class Report; + + class ReportImpl { + private: + Report *m_report; + public: + ReportImpl(); + ~ReportImpl(); + public: + Result Open(const ReportId &report_id); + Result Read(ams::sf::Out<u32> out_count, const ams::sf::OutBuffer &out_buffer); + Result SetFlags(ReportFlagSet flags); + Result GetFlags(ams::sf::Out<ReportFlagSet> out); + Result Close(); + Result GetSize(ams::sf::Out<s64> out); + }; + static_assert(erpt::sf::IsIReport<ReportImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_reporter.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_reporter.cpp new file mode 100644 index 00000000..a9a2871c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_reporter.cpp @@ -0,0 +1,548 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_reporter.hpp" +#include "erpt_srv_report.hpp" +#include "erpt_srv_journal.hpp" +#include "erpt_srv_context_record.hpp" +#include "erpt_srv_context.hpp" +#include "erpt_srv_fs_info.hpp" + +namespace ams::erpt::srv { + + constinit bool Reporter::s_redirect_new_reports = true; + constinit char Reporter::s_serial_number[24] = "Unknown"; + constinit char Reporter::s_os_version[24] = "Unknown"; + constinit char Reporter::s_private_os_version[96] = "Unknown"; + constinit util::optional<os::Tick> Reporter::s_application_launch_time; + constinit util::optional<os::Tick> Reporter::s_awake_time; + constinit util::optional<os::Tick> Reporter::s_power_on_time; + constinit util::optional<time::SteadyClockTimePoint> Reporter::s_initial_launch_settings_completion_time; + + namespace { + + class AppletActiveTimeInfoList { + private: + struct AppletActiveTimeInfo { + ncm::ProgramId program_id; + os::Tick register_tick; + TimeSpan suspended_duration; + }; + static constexpr AppletActiveTimeInfo InvalidAppletActiveTimeInfo = { ncm::InvalidProgramId, os::Tick{}, TimeSpan::FromNanoSeconds(0) }; + private: + std::array<AppletActiveTimeInfo, 8> m_list; + public: + constexpr AppletActiveTimeInfoList() : m_list{InvalidAppletActiveTimeInfo, InvalidAppletActiveTimeInfo, InvalidAppletActiveTimeInfo, InvalidAppletActiveTimeInfo, InvalidAppletActiveTimeInfo, InvalidAppletActiveTimeInfo, InvalidAppletActiveTimeInfo, InvalidAppletActiveTimeInfo} { + m_list.fill(InvalidAppletActiveTimeInfo); + } + public: + void Register(ncm::ProgramId program_id) { + /* Find an unused entry. */ + auto entry = util::range::find_if(m_list, [](const AppletActiveTimeInfo &info) { return info.program_id == ncm::InvalidProgramId; }); + AMS_ASSERT(entry != m_list.end()); + + /* Create the entry. */ + *entry = { program_id, os::GetSystemTick(), TimeSpan::FromNanoSeconds(0) }; + } + + void Unregister(ncm::ProgramId program_id) { + /* Find a matching entry. */ + auto entry = util::range::find_if(m_list, [&](const AppletActiveTimeInfo &info) { return info.program_id == program_id; }); + AMS_ASSERT(entry != m_list.end()); + + /* Clear the entry. */ + *entry = InvalidAppletActiveTimeInfo; + } + + void UpdateSuspendedDuration(ncm::ProgramId program_id, TimeSpan suspended_duration) { + /* Find a matching entry. */ + auto entry = util::range::find_if(m_list, [&](const AppletActiveTimeInfo &info) { return info.program_id == program_id; }); + AMS_ASSERT(entry != m_list.end()); + + /* Set the suspended duration. */ + entry->suspended_duration = suspended_duration; + } + + util::optional<TimeSpan> GetActiveDuration(ncm::ProgramId program_id) const { + /* Try to find a matching entry. */ + const auto entry = util::range::find_if(m_list, [&](const AppletActiveTimeInfo &info) { return info.program_id == program_id; }); + if (entry != m_list.end()) { + return (os::GetSystemTick() - entry->register_tick).ToTimeSpan() - entry->suspended_duration; + } else { + return util::nullopt; + } + } + }; + + constinit AppletActiveTimeInfoList g_applet_active_time_info_list; + + #if defined(ATMOSPHERE_OS_HORIZON) + Result PullErrorContext(size_t *out_total_size, size_t *out_size, void *dst, size_t dst_size, const err::ContextDescriptor &descriptor, Result result) { + s32 unk0; + u32 total_size, size; + R_TRY(::ectxrPullContext(std::addressof(unk0), std::addressof(total_size), std::addressof(size), dst, dst_size, descriptor.value, result.GetValue())); + + *out_total_size = total_size; + *out_size = size; + R_SUCCEED(); + } + + void SubmitErrorContext(ContextRecord *record, Result result) { + /* Only support submitting context on 11.x. */ + if (hos::GetVersion() < hos::Version_11_0_0) { + return; + } + + /* Get the context descriptor. */ + const auto descriptor = err::GetContextDescriptorFromResult(result); + if (descriptor == err::InvalidContextDescriptor) { + return; + } + + /* Pull the error context. */ + u8 error_context[0x200]; + size_t error_context_total_size; + size_t error_context_size; + if (R_FAILED(PullErrorContext(std::addressof(error_context_total_size), std::addressof(error_context_size), error_context, util::size(error_context), descriptor, result))) { + return; + } + + /* Set the total size. */ + if (error_context_total_size == 0) { + return; + } + record->Add(FieldId_ErrorContextTotalSize, error_context_total_size); + + /* Set the context. */ + if (error_context_size == 0) { + return; + } + record->Add(FieldId_ErrorContextSize, error_context_size); + record->Add(FieldId_ErrorContext, error_context, error_context_size); + } + + constinit os::SdkMutex g_limit_mutex; + constinit bool g_submitted_limit = false; + + void SubmitResourceLimitLimitContext() { + std::scoped_lock lk(g_limit_mutex); + if (g_submitted_limit) { + return; + } + + ON_SCOPE_EXIT { g_submitted_limit = true; }; + + /* Create and populate the record. */ + auto record = std::make_unique<ContextRecord>(CategoryId_ResourceLimitLimitInfo); + if (record == nullptr) { + return; + } + + u64 reslimit_handle_value; + if (R_FAILED(svc::GetInfo(std::addressof(reslimit_handle_value), svc::InfoType_ResourceLimit, svc::InvalidHandle, 0))) { + return; + } + + const auto handle = static_cast<svc::Handle>(reslimit_handle_value); + ON_SCOPE_EXIT { R_ABORT_UNLESS(svc::CloseHandle(handle)); }; + + #define ADD_RESOURCE(__RESOURCE__) \ + do { \ + s64 limit_value; \ + if (R_FAILED(svc::GetResourceLimitLimitValue(std::addressof(limit_value), handle, svc::LimitableResource_##__RESOURCE__##Max))) { \ + return; \ + } \ + if (R_FAILED(record->Add(FieldId_System##__RESOURCE__##Limit, limit_value))) { \ + return; \ + } \ + } while (0) + + ADD_RESOURCE(PhysicalMemory); + ADD_RESOURCE(ThreadCount); + ADD_RESOURCE(EventCount); + ADD_RESOURCE(TransferMemoryCount); + ADD_RESOURCE(SessionCount); + + #undef ADD_RESOURCE + + Context::SubmitContextRecord(std::move(record)); + + g_submitted_limit = true; + } + + void SubmitResourceLimitPeakContext() { + /* Create and populate the record. */ + auto record = std::make_unique<ContextRecord>(CategoryId_ResourceLimitPeakInfo); + if (record == nullptr) { + return; + } + + u64 reslimit_handle_value; + if (R_FAILED(svc::GetInfo(std::addressof(reslimit_handle_value), svc::InfoType_ResourceLimit, svc::InvalidHandle, 0))) { + return; + } + + const auto handle = static_cast<svc::Handle>(reslimit_handle_value); + ON_SCOPE_EXIT { R_ABORT_UNLESS(svc::CloseHandle(handle)); }; + + #define ADD_RESOURCE(__RESOURCE__) \ + do { \ + s64 peak_value; \ + if (R_FAILED(svc::GetResourceLimitPeakValue(std::addressof(peak_value), handle, svc::LimitableResource_##__RESOURCE__##Max))) { \ + return; \ + } \ + if (R_FAILED(record->Add(FieldId_System##__RESOURCE__##Peak, peak_value))) { \ + return; \ + } \ + } while (0) + + ADD_RESOURCE(PhysicalMemory); + ADD_RESOURCE(ThreadCount); + ADD_RESOURCE(EventCount); + ADD_RESOURCE(TransferMemoryCount); + ADD_RESOURCE(SessionCount); + + #undef ADD_RESOURCE + + Context::SubmitContextRecord(std::move(record)); + } + + void SubmitResourceLimitContexts() { + SubmitResourceLimitLimitContext(); + SubmitResourceLimitPeakContext(); + } + #else + void SubmitErrorContext(ContextRecord *record, Result result) { + AMS_UNUSED(record, result); + } + #endif + + Result ValidateCreateReportContext(const ContextEntry *ctx) { + R_UNLESS(ctx->category == CategoryId_ErrorInfo, erpt::ResultRequiredContextMissing()); + R_UNLESS(ctx->field_count <= FieldsPerContext, erpt::ResultInvalidArgument()); + + const bool found_error_code = util::range::any_of(MakeSpan(ctx->fields, ctx->field_count), [] (const FieldEntry &entry) { + return entry.id == FieldId_ErrorCode; + }); + R_UNLESS(found_error_code, erpt::ResultRequiredFieldMissing()); + + R_SUCCEED(); + } + + Result SubmitReportDefaults(const ContextEntry *ctx) { + AMS_ASSERT(ctx->category == CategoryId_ErrorInfo); + + auto record = std::make_unique<ContextRecord>(CategoryId_ErrorInfoDefaults); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + bool found_abort_flag = false, found_syslog_flag = false; + for (u32 i = 0; i < ctx->field_count; i++) { + if (ctx->fields[i].id == FieldId_AbortFlag) { + found_abort_flag = true; + } + if (ctx->fields[i].id == FieldId_HasSyslogFlag) { + found_syslog_flag = true; + } + if (found_abort_flag && found_syslog_flag) { + break; + } + } + + if (!found_abort_flag) { + record->Add(FieldId_AbortFlag, false); + } + + if (!found_syslog_flag) { + record->Add(FieldId_HasSyslogFlag, true); + } + + R_TRY(Context::SubmitContextRecord(std::move(record))); + + R_SUCCEED(); + } + + void SaveSyslogReportIfRequired(const ContextEntry *ctx, const ReportId &report_id) { + bool needs_save_syslog = true; + for (u32 i = 0; i < ctx->field_count; i++) { + static_assert(FieldIndexToTypeMap[*FindFieldIndex(FieldId_HasSyslogFlag)] == FieldType_Bool); + if (ctx->fields[i].id == FieldId_HasSyslogFlag && !ctx->fields[i].value_bool) { + needs_save_syslog = false; + break; + } + } + + if (needs_save_syslog) { + /* Here nintendo sends a report to srepo:u (vtable offset 0xE8) with data report_id. */ + /* We will not send report ids to srepo:u. */ + AMS_UNUSED(report_id); + } + } + + void SubmitAppletActiveDurationForCrashReport(const ContextEntry *error_info_ctx, const void *data, u32 data_size, ContextRecord *error_info_auto_record) { + /* Check pre-conditions. */ + AMS_ASSERT(error_info_ctx != nullptr); + AMS_ASSERT(error_info_ctx->category == CategoryId_ErrorInfo); + AMS_ASSERT(data != nullptr); + AMS_ASSERT(error_info_auto_record != nullptr); + + /* Find the program id entry. */ + const auto fields_span = MakeSpan(error_info_ctx->fields, error_info_ctx->field_count); + const auto program_id_entry = util::range::find_if(fields_span, [](const FieldEntry &entry) { return entry.id == FieldId_ProgramId; }); + if (program_id_entry == fields_span.end()) { + return; + } + + /* Check that the report has abort flag set. */ + AMS_ASSERT(util::range::any_of(fields_span, [](const FieldEntry &entry) { return entry.id == FieldId_AbortFlag && entry.value_bool; })); + + /* Check that the program id's value is a string. */ + AMS_ASSERT(program_id_entry->type == FieldType_String); + + /* Check that the program id's length is valid/in bounds. */ + const auto program_id_ofs = program_id_entry->value_array.start_idx; + const auto program_id_len = program_id_entry->value_array.size; + AMS_ASSERT(16 <= program_id_len && program_id_len <= 17); + AMS_ASSERT(program_id_ofs + program_id_len <= data_size); + AMS_UNUSED(data_size); + + /* Get the program id string. */ + char program_id_str[17]; + std::memcpy(program_id_str, static_cast<const u8 *>(data) + program_id_ofs, std::min<size_t>(program_id_len, sizeof(program_id_str))); + program_id_str[sizeof(program_id_str) - 1] = '\x00'; + + /* Convert the string to an integer. */ + char *end_ptr = nullptr; + const ncm::ProgramId program_id = { std::strtoull(program_id_str, std::addressof(end_ptr), 16) }; + AMS_ASSERT(*end_ptr == '\x00'); + + /* Get the active duration. */ + const auto active_duration = g_applet_active_time_info_list.GetActiveDuration(program_id); + if (!active_duration.has_value()) { + return; + } + + /* Add the active applet time. */ + const auto result = error_info_auto_record->Add(FieldId_AppletTotalActiveTime, (*active_duration).GetSeconds()); + R_ASSERT(result); + } + + Result LinkAttachments(const ReportId &report_id, const AttachmentId *attachments, u32 num_attachments) { + for (u32 i = 0; i < num_attachments; i++) { + R_TRY(JournalForAttachments::SetOwner(attachments[i], report_id)); + } + R_SUCCEED(); + } + + Result CreateReportFile(const ReportId &report_id, ReportType type, const ReportMetaData *meta, u32 num_attachments, const time::PosixTime ×tamp_user, const time::PosixTime ×tamp_network, bool redirect_new_reports) { + /* Define journal record deleter. */ + struct JournalRecordDeleter { + void operator()(JournalRecord<ReportInfo> *record) { + if (record != nullptr) { + if (record->RemoveReference()) { + delete record; + } + } + } + }; + + /* Make a journal record. */ + auto record = std::unique_ptr<JournalRecord<ReportInfo>, JournalRecordDeleter>{new JournalRecord<ReportInfo>, JournalRecordDeleter{}}; + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + record->AddReference(); + + record->m_info.type = type; + record->m_info.id = report_id; + record->m_info.flags = erpt::srv::MakeNoReportFlags(); + record->m_info.timestamp_user = timestamp_user; + record->m_info.timestamp_network = timestamp_network; + if (meta != nullptr) { + record->m_info.meta_data = *meta; + } + if (num_attachments > 0) { + record->m_info.flags.Set<ReportFlag::HasAttachment>(); + } + + auto report = std::make_unique<Report>(record.get(), redirect_new_reports); + R_UNLESS(report != nullptr, erpt::ResultOutOfMemory()); + auto report_guard = SCOPE_GUARD { report->Delete(); }; + + R_TRY(Context::WriteContextsToReport(report.get())); + R_TRY(report->GetSize(std::addressof(record->m_info.report_size))); + + if (!redirect_new_reports) { + /* If we're not redirecting new reports, then we want to store the report in the journal. */ + R_TRY(Journal::Store(record.get())); + } else { + /* If we are redirecting new reports, we don't want to store the report in the journal. */ + /* We should take this opportunity to delete any attachments associated with the report. */ + R_ABORT_UNLESS(JournalForAttachments::DeleteAttachments(report_id)); + } + + R_TRY(Journal::Commit()); + + report_guard.Cancel(); + R_SUCCEED(); + } + + } + + Result Reporter::RegisterRunningApplet(ncm::ProgramId program_id) { + g_applet_active_time_info_list.Register(program_id); + R_SUCCEED(); + } + + Result Reporter::UnregisterRunningApplet(ncm::ProgramId program_id) { + g_applet_active_time_info_list.Unregister(program_id); + R_SUCCEED(); + } + + Result Reporter::UpdateAppletSuspendedDuration(ncm::ProgramId program_id, TimeSpan duration) { + g_applet_active_time_info_list.UpdateSuspendedDuration(program_id, duration); + R_SUCCEED(); + } + + Result Reporter::CreateReport(ReportType type, Result ctx_result, const ContextEntry *ctx, const u8 *data, u32 data_size, const ReportMetaData *meta, const AttachmentId *attachments, u32 num_attachments, erpt::CreateReportOptionFlagSet flags, const ReportId *specified_report_id) { + /* Create a context record for the report. */ + auto record = std::make_unique<ContextRecord>(); + R_UNLESS(record != nullptr, erpt::ResultOutOfMemory()); + + /* Initialize the record. */ + R_TRY(record->Initialize(ctx, data, data_size)); + + /* Create the report. */ + R_RETURN(CreateReport(type, ctx_result, std::move(record), meta, attachments, num_attachments, flags, specified_report_id)); + } + + Result Reporter::CreateReport(ReportType type, Result ctx_result, std::unique_ptr<ContextRecord> record, const ReportMetaData *meta, const AttachmentId *attachments, u32 num_attachments, erpt::CreateReportOptionFlagSet flags, const ReportId *specified_report_id) { + /* Clear the automatic categories, when we're done with our report. */ + ON_SCOPE_EXIT { + Context::ClearContext(CategoryId_ErrorInfo); + Context::ClearContext(CategoryId_ErrorInfoAuto); + Context::ClearContext(CategoryId_ErrorInfoDefaults); + }; + + /* Get the context entry pointer. */ + const ContextEntry *ctx = record->GetContextEntryPtr(); + + /* Validate the context. */ + R_TRY(ValidateCreateReportContext(ctx)); + + /* Submit report defaults. */ + R_TRY(SubmitReportDefaults(ctx)); + + /* Generate report id. */ + const ReportId report_id = specified_report_id ? *specified_report_id : ReportId{ .uuid = util::GenerateUuid() }; + + /* Get posix timestamps. */ + time::PosixTime timestamp_user; + time::PosixTime timestamp_network; + R_TRY(time::StandardUserSystemClock::GetCurrentTime(std::addressof(timestamp_user))); + if (R_FAILED(time::StandardNetworkSystemClock::GetCurrentTime(std::addressof(timestamp_network)))) { + timestamp_network = {}; + } + + /* Save syslog report, if required. */ + SaveSyslogReportIfRequired(ctx, report_id); + + /* Submit report contexts. */ + R_TRY(SubmitReportContexts(report_id, type, ctx_result, std::move(record), timestamp_user, timestamp_network, flags)); + + /* Link attachments to the report. */ + R_TRY(LinkAttachments(report_id, attachments, num_attachments)); + + /* Create the report file. */ + R_TRY(CreateReportFile(report_id, type, meta, num_attachments, timestamp_user, timestamp_network, s_redirect_new_reports)); + + R_SUCCEED(); + } + + Result Reporter::SubmitReportContexts(const ReportId &report_id, ReportType type, Result ctx_result, std::unique_ptr<ContextRecord> record, const time::PosixTime ×tamp_user, const time::PosixTime ×tamp_network, erpt::CreateReportOptionFlagSet flags) { + /* Create automatic record. */ + auto auto_record = std::make_unique<ContextRecord>(CategoryId_ErrorInfoAuto, 0x300); + R_UNLESS(auto_record != nullptr, erpt::ResultOutOfMemory()); + + /* Handle error context. */ + if (R_FAILED(ctx_result)) { + SubmitErrorContext(auto_record.get(), ctx_result); + } + + /* Collect unique report fields. */ + char identifier_str[0x40]; + report_id.uuid.ToString(identifier_str, sizeof(identifier_str)); + + const auto occurrence_tick = os::GetSystemTick(); + const s64 steady_clock_internal_offset_seconds = (hos::GetVersion() >= hos::Version_5_0_0) ? time::GetStandardSteadyClockInternalOffset().GetSeconds() : 0; + + time::SteadyClockTimePoint steady_clock_current_timepoint; + R_ABORT_UNLESS(time::GetStandardSteadyClockCurrentTimePoint(std::addressof(steady_clock_current_timepoint))); + + /* Add automatic fields. */ + auto_record->Add(FieldId_OsVersion, s_os_version, util::Strnlen(s_os_version, sizeof(s_os_version))); + auto_record->Add(FieldId_PrivateOsVersion, s_private_os_version, util::Strnlen(s_private_os_version, sizeof(s_private_os_version))); + auto_record->Add(FieldId_SerialNumber, s_serial_number, util::Strnlen(s_serial_number, sizeof(s_serial_number))); + auto_record->Add(FieldId_ReportIdentifier, identifier_str, util::Strnlen(identifier_str, sizeof(identifier_str))); + auto_record->Add(FieldId_OccurrenceTimestamp, timestamp_user.value); + auto_record->Add(FieldId_OccurrenceTimestampNet, timestamp_network.value); + auto_record->Add(FieldId_ReportVisibilityFlag, type == ReportType_Visible); + auto_record->Add(FieldId_OccurrenceTick, occurrence_tick.GetInt64Value()); + auto_record->Add(FieldId_SteadyClockInternalOffset, steady_clock_internal_offset_seconds); + auto_record->Add(FieldId_SteadyClockCurrentTimePointValue, steady_clock_current_timepoint.value); + auto_record->Add(FieldId_ElapsedTimeSincePowerOn, (occurrence_tick - *s_power_on_time).ToTimeSpan().GetSeconds()); + auto_record->Add(FieldId_ElapsedTimeSinceLastAwake, (occurrence_tick - *s_awake_time).ToTimeSpan().GetSeconds()); + + if (s_initial_launch_settings_completion_time) { + s64 elapsed_seconds; + if (R_SUCCEEDED(time::GetElapsedSecondsBetween(std::addressof(elapsed_seconds), *s_initial_launch_settings_completion_time, steady_clock_current_timepoint))) { + auto_record->Add(FieldId_ElapsedTimeSinceInitialLaunch, elapsed_seconds); + } + } + + if (s_application_launch_time) { + auto_record->Add(FieldId_ApplicationAliveTime, (occurrence_tick - *s_application_launch_time).ToTimeSpan().GetSeconds()); + } + + /* Submit applet active duration information. */ + { + const auto *error_info_ctx = record->GetContextEntryPtr(); + SubmitAppletActiveDurationForCrashReport(error_info_ctx, error_info_ctx->array_buffer, error_info_ctx->array_buffer_size - error_info_ctx->array_free_count, auto_record.get()); + } + + /* Submit the auto record. */ + R_TRY(Context::SubmitContextRecord(std::move(auto_record))); + + /* Submit the info record. */ + R_TRY(Context::SubmitContextRecord(std::move(record))); + + /* Submit context for resource limits. */ + #if defined(ATMOSPHERE_OS_HORIZON) + SubmitResourceLimitContexts(); + #endif + + /* If we should, submit fs info. */ + #if defined(ATMOSPHERE_OS_HORIZON) + if (hos::GetVersion() >= hos::Version_17_0_0 && flags.Test<CreateReportOptionFlag::SubmitFsInfo>()) { + /* NOTE: Nintendo ignores the result of this call. */ + SubmitFsInfo(); + } + #else + AMS_UNUSED(flags); + #endif + + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_reporter.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_reporter.hpp new file mode 100644 index 00000000..f5db475e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_reporter.hpp @@ -0,0 +1,65 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "erpt_srv_context_record.hpp" + +namespace ams::erpt::srv { + + class Reporter { + private: + static bool s_redirect_new_reports; + static char s_serial_number[24]; + static char s_os_version[24]; + static char s_private_os_version[96]; + static util::optional<os::Tick> s_application_launch_time; + static util::optional<os::Tick> s_awake_time; + static util::optional<os::Tick> s_power_on_time; + static util::optional<time::SteadyClockTimePoint> s_initial_launch_settings_completion_time; + public: + static void ClearApplicationLaunchTime() { s_application_launch_time = util::nullopt; } + static void ClearInitialLaunchSettingsCompletionTime() { s_initial_launch_settings_completion_time = util::nullopt; } + + static void SetInitialLaunchSettingsCompletionTime(const time::SteadyClockTimePoint &time) { s_initial_launch_settings_completion_time = time; } + + static void UpdateApplicationLaunchTime() { s_application_launch_time = os::GetSystemTick(); } + static void UpdateAwakeTime() { s_awake_time = os::GetSystemTick(); } + static void UpdatePowerOnTime() { s_power_on_time = os::GetSystemTick(); } + + static Result SetSerialNumberAndOsVersion(const char *sn, u32 sn_len, const char *os, u32 os_len, const char *os_priv, u32 os_priv_len) { + R_UNLESS(sn_len <= sizeof(s_serial_number), erpt::ResultInvalidArgument()); + R_UNLESS(os_len <= sizeof(s_os_version), erpt::ResultInvalidArgument()); + R_UNLESS(os_priv_len <= sizeof(s_private_os_version), erpt::ResultInvalidArgument()); + + std::memcpy(s_serial_number, sn, sn_len); + std::memcpy(s_os_version, os, os_len); + std::memcpy(s_private_os_version, os_priv, os_priv_len); + R_SUCCEED(); + } + + static Result RegisterRunningApplet(ncm::ProgramId program_id); + static Result UnregisterRunningApplet(ncm::ProgramId program_id); + static Result UpdateAppletSuspendedDuration(ncm::ProgramId program_id, TimeSpan duration); + + static void SetRedirectNewReportsToSdCard(bool en) { s_redirect_new_reports = en; } + private: + static Result SubmitReportContexts(const ReportId &report_id, ReportType type, Result ctx_result, std::unique_ptr<ContextRecord> record, const time::PosixTime &user_timestamp, const time::PosixTime &network_timestamp, erpt::CreateReportOptionFlagSet flags); + public: + static Result CreateReport(ReportType type, Result ctx_result, const ContextEntry *ctx, const u8 *data, u32 data_size, const ReportMetaData *meta, const AttachmentId *attachments, u32 num_attachments, erpt::CreateReportOptionFlagSet flags, const ReportId *specified_report_id); + static Result CreateReport(ReportType type, Result ctx_result, std::unique_ptr<ContextRecord> record, const ReportMetaData *meta, const AttachmentId *attachments, u32 num_attachments, erpt::CreateReportOptionFlagSet flags, const ReportId *specified_report_id); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_service.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_service.cpp new file mode 100644 index 00000000..3df8b45e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_service.cpp @@ -0,0 +1,154 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_service.hpp" +#include "erpt_srv_context_impl.hpp" +#include "erpt_srv_session_impl.hpp" +#include "erpt_srv_stream.hpp" +#include "erpt_srv_forced_shutdown.hpp" + +namespace ams::erpt::srv { + + extern ams::sf::ExpHeapAllocator g_sf_allocator; + + namespace { + + struct ErrorReportServerOptions { + static constexpr size_t PointerBufferSize = 0; + static constexpr size_t MaxDomains = 64; + static constexpr size_t MaxDomainObjects = 2 * ReportCountMax + 5 + 2; + static constexpr bool CanDeferInvokeRequest = false; + static constexpr bool CanManageMitmServers = false; + }; + + constexpr inline size_t ErrorReportNumServers = 2; + constexpr inline size_t ErrorReportReportSessions = 5; + constexpr inline size_t ErrorReportContextSessions = 10; + constexpr inline size_t ErrorReportMaxSessions = ErrorReportReportSessions + ErrorReportContextSessions; + + constexpr inline sm::ServiceName ErrorReportContextServiceName = sm::ServiceName::Encode("erpt:c"); + constexpr inline sm::ServiceName ErrorReportReportServiceName = sm::ServiceName::Encode("erpt:r"); + + alignas(os::ThreadStackAlignment) constinit u8 g_server_thread_stack[16_KB]; + + enum PortIndex { + PortIndex_Report, + PortIndex_Context, + }; + + class ErrorReportServiceManager : public ams::sf::hipc::ServerManager<ErrorReportNumServers, ErrorReportServerOptions, ErrorReportMaxSessions> { + private: + os::ThreadType m_thread; + ams::sf::UnmanagedServiceObject<erpt::sf::IContext, erpt::srv::ContextImpl> m_context_session_object; + private: + static void ThreadFunction(void *_this) { + reinterpret_cast<ErrorReportServiceManager *>(_this)->SetupAndLoopProcess(); + } + + void SetupAndLoopProcess(); + + virtual Result OnNeedsToAccept(int port_index, Server *server) override { + switch (port_index) { + case PortIndex_Report: + { + auto intf = ams::sf::ObjectFactory<ams::sf::ExpHeapAllocator::Policy>::CreateSharedEmplaced<erpt::sf::ISession, erpt::srv::SessionImpl>(std::addressof(g_sf_allocator)); + AMS_ABORT_UNLESS(intf != nullptr); + R_RETURN(this->AcceptImpl(server, intf)); + } + case PortIndex_Context: + R_RETURN(AcceptImpl(server, m_context_session_object.GetShared())); + default: + R_THROW(erpt::ResultNotSupported()); + } + } + public: + Result Initialize() { + R_ABORT_UNLESS(this->RegisterServer(PortIndex_Context, ErrorReportContextServiceName, ErrorReportContextSessions)); + R_ABORT_UNLESS(this->RegisterServer(PortIndex_Report, ErrorReportReportServiceName, ErrorReportReportSessions)); + + this->ResumeProcessing(); + + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_thread), ThreadFunction, this, g_server_thread_stack, sizeof(g_server_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(erpt, IpcServer))); + os::SetThreadNamePointer(std::addressof(m_thread), AMS_GET_SYSTEM_THREAD_NAME(erpt, IpcServer)); + + os::StartThread(std::addressof(m_thread)); + + R_SUCCEED(); + } + + void Wait() { + os::WaitThread(std::addressof(m_thread)); + } + }; + + void ErrorReportServiceManager::SetupAndLoopProcess() { + const psc::PmModuleId dependencies[] = { psc::PmModuleId_Fs }; + psc::PmModule pm_module; + psc::PmState pm_state; + psc::PmFlagSet pm_flags; + os::MultiWaitHolderType module_event_holder; + + R_ABORT_UNLESS(pm_module.Initialize(psc::PmModuleId_Erpt, dependencies, util::size(dependencies), os::EventClearMode_ManualClear)); + + os::InitializeMultiWaitHolder(std::addressof(module_event_holder), pm_module.GetEventPointer()->GetBase()); + os::SetMultiWaitHolderUserData(std::addressof(module_event_holder), static_cast<uintptr_t>(psc::PmModuleId_Erpt)); + this->AddUserMultiWaitHolder(std::addressof(module_event_holder)); + + while (true) { + /* NOTE: Nintendo checks the user holder data to determine what's signaled, we will prefer to just check the address. */ + auto *signaled_holder = this->WaitSignaled(); + if (signaled_holder != std::addressof(module_event_holder)) { + R_ABORT_UNLESS(this->Process(signaled_holder)); + } else { + pm_module.GetEventPointer()->Clear(); + if (R_SUCCEEDED(pm_module.GetRequest(std::addressof(pm_state), std::addressof(pm_flags)))) { + switch (pm_state) { + case psc::PmState_FullAwake: + case psc::PmState_MinimumAwake: + Stream::EnableFsAccess(true); + break; + case psc::PmState_ShutdownReady: + FinalizeForcedShutdownDetection(); + [[fallthrough]]; + case psc::PmState_SleepReady: + Stream::EnableFsAccess(false); + break; + default: + break; + } + R_ABORT_UNLESS(pm_module.Acknowledge(pm_state, ResultSuccess())); + } else { + AMS_ASSERT(false); + } + this->AddUserMultiWaitHolder(signaled_holder); + } + } + } + + constinit util::TypedStorage<ErrorReportServiceManager> g_erpt_server_manager = {}; + + } + + Result InitializeService() { + util::ConstructAt(g_erpt_server_manager); + R_RETURN(util::GetReference(g_erpt_server_manager).Initialize()); + } + + void WaitService() { + return util::GetReference(g_erpt_server_manager).Wait(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_service.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_service.hpp new file mode 100644 index 00000000..01d3c6ae --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_service.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::erpt::srv { + + Result InitializeService(); + void WaitService(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_session_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_session_impl.cpp new file mode 100644 index 00000000..0959aaf5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_session_impl.cpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_session_impl.hpp" +#include "erpt_srv_report_impl.hpp" +#include "erpt_srv_manager_impl.hpp" +#include "erpt_srv_attachment_impl.hpp" + +namespace ams::erpt::srv { + + extern ams::sf::ExpHeapAllocator g_sf_allocator; + + namespace { + + template<typename Interface, typename Impl> + ALWAYS_INLINE Result OpenInterface(ams::sf::Out<ams::sf::SharedPointer<Interface>> &out) { + /* Create an interface holder. */ + auto intf = ams::sf::ObjectFactory<ams::sf::ExpHeapAllocator::Policy>::CreateSharedEmplaced<Interface, Impl>(std::addressof(g_sf_allocator)); + R_UNLESS(intf != nullptr, erpt::ResultOutOfMemory()); + + /* Return it. */ + out.SetValue(intf); + R_SUCCEED(); + } + + } + + Result SessionImpl::OpenReport(ams::sf::Out<ams::sf::SharedPointer<erpt::sf::IReport>> out) { + R_RETURN((OpenInterface<erpt::sf::IReport, ReportImpl>(out))); + } + + Result SessionImpl::OpenManager(ams::sf::Out<ams::sf::SharedPointer<erpt::sf::IManager>> out) { + R_RETURN((OpenInterface<erpt::sf::IManager, ManagerImpl>(out))); + } + + Result SessionImpl::OpenAttachment(ams::sf::Out<ams::sf::SharedPointer<erpt::sf::IAttachment>> out) { + R_RETURN((OpenInterface<erpt::sf::IAttachment, AttachmentImpl>(out))); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_session_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_session_impl.hpp new file mode 100644 index 00000000..91df0311 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_session_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::erpt::srv { + + class SessionImpl { + public: + Result OpenReport(ams::sf::Out<ams::sf::SharedPointer<erpt::sf::IReport>> out); + Result OpenManager(ams::sf::Out<ams::sf::SharedPointer<erpt::sf::IManager>> out); + Result OpenAttachment(ams::sf::Out<ams::sf::SharedPointer<erpt::sf::IAttachment>> out); + }; + static_assert(erpt::sf::IsISession<SessionImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_stream.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_stream.cpp new file mode 100644 index 00000000..35c0db84 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_stream.cpp @@ -0,0 +1,223 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "erpt_srv_allocator.hpp" +#include "erpt_srv_stream.hpp" + +namespace ams::erpt::srv { + + constinit bool Stream::s_can_access_fs = true; + constinit os::SdkMutex Stream::s_fs_commit_mutex; + + void Stream::EnableFsAccess(bool en) { + s_can_access_fs = en; + } + + Result Stream::DeleteStream(const char *path) { + R_UNLESS(s_can_access_fs, erpt::ResultInvalidPowerState()); + R_RETURN(fs::DeleteFile(path)); + } + + Result Stream::CommitStream() { + R_UNLESS(s_can_access_fs, erpt::ResultInvalidPowerState()); + + std::scoped_lock lk(s_fs_commit_mutex); + + fs::CommitSaveData(ReportStoragePath); + R_SUCCEED(); + } + + Result Stream::GetStreamSize(s64 *out, const char *path) { + R_UNLESS(s_can_access_fs, erpt::ResultInvalidPowerState()); + + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + R_RETURN(fs::GetFileSize(out, file)); + } + + Stream::Stream() : m_buffer_size(0), m_file_position(0), m_buffer_count(0), m_buffer(nullptr), m_stream_mode(StreamMode_Invalid), m_initialized(false) { + /* ... */ + } + + Stream::~Stream() { + this->CloseStream(); + AMS_ASSERT(!s_fs_commit_mutex.IsLockedByCurrentThread()); + } + + Result Stream::OpenStream(const char *path, StreamMode mode, u32 buffer_size) { + R_UNLESS(s_can_access_fs, erpt::ResultInvalidPowerState()); + R_UNLESS(!m_initialized, erpt::ResultAlreadyInitialized()); + + auto lock_guard = SCOPE_GUARD { + if (s_fs_commit_mutex.IsLockedByCurrentThread()) { + s_fs_commit_mutex.Unlock(); + } + }; + + if (mode == StreamMode_Write) { + s_fs_commit_mutex.Lock(); + + while (true) { + R_TRY_CATCH(fs::OpenFile(std::addressof(m_file_handle), path, fs::OpenMode_Write | fs::OpenMode_AllowAppend)) { + R_CATCH(fs::ResultPathNotFound) { + R_TRY(fs::CreateFile(path, 0)); + continue; + } + } R_END_TRY_CATCH; + break; + } + fs::SetFileSize(m_file_handle, 0); + } else { + R_UNLESS(mode == StreamMode_Read, erpt::ResultInvalidArgument()); + + R_TRY(fs::OpenFile(std::addressof(m_file_handle), path, fs::OpenMode_Read)); + } + auto file_guard = SCOPE_GUARD { fs::CloseFile(m_file_handle); }; + + std::strncpy(m_file_name, path, sizeof(m_file_name)); + m_file_name[sizeof(m_file_name) - 1] = '\x00'; + + if (buffer_size > 0) { + m_buffer = reinterpret_cast<u8 *>(Allocate(buffer_size)); + AMS_ASSERT(m_buffer != nullptr); + } else { + m_buffer = nullptr; + } + + + m_buffer_size = m_buffer != nullptr ? buffer_size : 0; + m_buffer_count = 0; + m_buffer_position = 0; + m_file_position = 0; + m_stream_mode = mode; + m_initialized = true; + + file_guard.Cancel(); + lock_guard.Cancel(); + R_SUCCEED(); + } + + Result Stream::ReadStream(u32 *out, u8 *dst, u32 dst_size) { + R_UNLESS(s_can_access_fs, erpt::ResultInvalidPowerState()); + R_UNLESS(m_initialized, erpt::ResultNotInitialized()); + R_UNLESS(m_stream_mode == StreamMode_Read, erpt::ResultNotInitialized()); + R_UNLESS(out != nullptr, erpt::ResultInvalidArgument()); + R_UNLESS(dst != nullptr, erpt::ResultInvalidArgument()); + + size_t fs_read_size; + u32 read_count = 0; + + ON_SCOPE_EXIT { + *out = read_count; + }; + + if (m_buffer != nullptr) { + while (dst_size > 0) { + if (u32 cur = std::min<u32>(m_buffer_count - m_buffer_position, dst_size); cur > 0) { + std::memcpy(dst, m_buffer + m_buffer_position, cur); + m_buffer_position += cur; + dst += cur; + dst_size -= cur; + read_count += cur; + } else { + R_TRY(fs::ReadFile(std::addressof(fs_read_size), m_file_handle, m_file_position, m_buffer, m_buffer_size)); + + m_buffer_position = 0; + m_file_position += static_cast<u32>(fs_read_size); + m_buffer_count = static_cast<u32>(fs_read_size); + + if (m_buffer_count == 0) { + break; + } + } + } + } else { + R_TRY(fs::ReadFile(std::addressof(fs_read_size), m_file_handle, m_file_position, dst, dst_size)); + + m_file_position += static_cast<u32>(fs_read_size); + read_count = static_cast<u32>(fs_read_size); + } + + R_SUCCEED(); + } + + Result Stream::WriteStream(const u8 *src, u32 src_size) { + R_UNLESS(s_can_access_fs, erpt::ResultInvalidPowerState()); + R_UNLESS(m_initialized, erpt::ResultNotInitialized()); + R_UNLESS(m_stream_mode == StreamMode_Write, erpt::ResultNotInitialized()); + R_UNLESS(src != nullptr || src_size == 0, erpt::ResultInvalidArgument()); + + if (m_buffer != nullptr) { + while (src_size > 0) { + if (u32 cur = std::min<u32>(m_buffer_size - m_buffer_count, src_size); cur > 0) { + std::memcpy(m_buffer + m_buffer_count, src, cur); + m_buffer_count += cur; + src += cur; + src_size -= cur; + } + + if (m_buffer_count == m_buffer_size) { + R_TRY(this->Flush()); + } + } + } else { + R_TRY(fs::WriteFile(m_file_handle, m_file_position, src, src_size, fs::WriteOption::None)); + m_file_position += src_size; + } + + R_SUCCEED(); + } + + void Stream::CloseStream() { + if (m_initialized) { + if (s_can_access_fs) { + if (m_stream_mode == StreamMode_Write) { + this->Flush(); + fs::FlushFile(m_file_handle); + } + fs::CloseFile(m_file_handle); + } + + if (m_buffer != nullptr) { + Deallocate(m_buffer); + } + + m_initialized = false; + + if (s_fs_commit_mutex.IsLockedByCurrentThread()) { + s_fs_commit_mutex.Unlock(); + } + } + } + + Result Stream::GetStreamSize(s64 *out) const { + R_RETURN(GetStreamSize(out, m_file_name)); + } + + Result Stream::Flush() { + AMS_ASSERT(s_fs_commit_mutex.IsLockedByCurrentThread()); + + R_SUCCEED_IF(m_buffer_count == 0); + R_TRY(fs::WriteFile(m_file_handle, m_file_position, m_buffer, m_buffer_count, fs::WriteOption::None)); + + m_file_position += m_buffer_count; + m_buffer_count = 0; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_stream.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_stream.hpp new file mode 100644 index 00000000..cd0975f3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/erpt/srv/erpt_srv_stream.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::erpt::srv { + + enum StreamMode { + StreamMode_Write = 0, + StreamMode_Read = 1, + StreamMode_Invalid = 2, + }; + + class Stream { + private: + static bool s_can_access_fs; + static os::SdkMutex s_fs_commit_mutex; + private: + u32 m_buffer_size; + u32 m_file_position; + u32 m_buffer_position; + u32 m_buffer_count; + u8 *m_buffer; + StreamMode m_stream_mode; + bool m_initialized; + fs::FileHandle m_file_handle; + char m_file_name[ReportFileNameLength]; + public: + Stream(); + ~Stream(); + + Result OpenStream(const char *path, StreamMode mode, u32 buffer_size); + Result ReadStream(u32 *out, u8 *dst, u32 dst_size); + Result WriteStream(const u8 *src, u32 src_size); + void CloseStream(); + + Result GetStreamSize(s64 *out) const; + private: + Result Flush(); + public: + static void EnableFsAccess(bool en); + static Result DeleteStream(const char *path); + static Result CommitStream(); + static Result GetStreamSize(s64 *out, const char *path); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/err/err_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/err/err_api.cpp new file mode 100644 index 00000000..81c693eb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/err/err_api.cpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/err_string_util.hpp" + +namespace ams::err { + + namespace impl { + + namespace { + + constexpr int ErrorCodeCategoryPlatformPrefixForResultModule = 2000; + + ALWAYS_INLINE ErrorCode ConvertResultToErrorCode(const Result &result) { + return { + .category = static_cast<ErrorCodeCategory>(ErrorCodeCategoryPlatformPrefixForResultModule + result.GetModule()), + .number = static_cast<ErrorCodeNumber>(result.GetDescription()), + }; + } + + [[maybe_unused]] ALWAYS_INLINE Result ConvertErrorCodeToResult(const ErrorCode &error_code) { + const auto result_value = ::ams::result::impl::ResultTraits::MakeValue(error_code.category - ErrorCodeCategoryPlatformPrefixForResultModule, error_code.number); + return ::ams::result::impl::MakeResult(result_value); + } + + } + + } + + ErrorCode ConvertResultToErrorCode(const Result &result) { + AMS_ASSERT(R_FAILED(result)); + + return ::ams::err::impl::ConvertResultToErrorCode(result); + } + + void GetErrorCodeString(char *dst, size_t dst_size, ErrorCode error_code) { + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(dst_size >= static_cast<size_t>(ErrorCode::StringLengthMax)); + AMS_ASSERT(error_code.IsValid()); + + return ::ams::err::impl::MakeErrorCodeString(dst, dst_size, error_code); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/err/impl/err_string_util.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/err/impl/err_string_util.cpp new file mode 100644 index 00000000..88144c90 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/err/impl/err_string_util.cpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "err_string_util.hpp" + +namespace ams::err::impl { + + void MakeErrorCodeString(char *dst, size_t dst_size, ErrorCode error_code) { + const auto len = util::TSNPrintf(dst, dst_size, "%04d-%04d", error_code.category, error_code.number); + AMS_ASSERT(static_cast<size_t>(len) < dst_size); + AMS_UNUSED(len); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/err/impl/err_string_util.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/err/impl/err_string_util.hpp new file mode 100644 index 00000000..790a87da --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/err/impl/err_string_util.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::err::impl { + + void MakeErrorCodeString(char *dst, size_t dst_size, ErrorCode error_code); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/common/fs_dbm_hierarchical_rom_file_table.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/common/fs_dbm_hierarchical_rom_file_table.cpp new file mode 100644 index 00000000..aa743cd6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/common/fs_dbm_hierarchical_rom_file_table.cpp @@ -0,0 +1,549 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fs { + + s64 HierarchicalRomFileTable::QueryDirectoryEntryBucketStorageSize(StorageSizeType count) { + return DirectoryEntryMapTable::QueryBucketStorageSize(count); + } + + s64 HierarchicalRomFileTable::QueryDirectoryEntrySize(StorageSizeType aux_size) { + return DirectoryEntryMapTable::QueryEntrySize(aux_size); + } + + s64 HierarchicalRomFileTable::QueryFileEntryBucketStorageSize(StorageSizeType count) { + return FileEntryMapTable::QueryBucketStorageSize(count); + } + + s64 HierarchicalRomFileTable::QueryFileEntrySize(StorageSizeType aux_size) { + return FileEntryMapTable::QueryEntrySize(aux_size); + } + + Result HierarchicalRomFileTable::Format(SubStorage dir_bucket, SubStorage file_bucket) { + s64 dir_bucket_size; + R_TRY(dir_bucket.GetSize(std::addressof(dir_bucket_size))); + R_TRY(DirectoryEntryMapTable::Format(dir_bucket, DirectoryEntryMapTable::QueryBucketCount(dir_bucket_size))); + + s64 file_bucket_size; + R_TRY(file_bucket.GetSize(std::addressof(file_bucket_size))); + R_TRY(FileEntryMapTable::Format(file_bucket, FileEntryMapTable::QueryBucketCount(file_bucket_size))); + + R_SUCCEED(); + } + + HierarchicalRomFileTable::HierarchicalRomFileTable() { /* ... */ } + + Result HierarchicalRomFileTable::Initialize(SubStorage dir_bucket, SubStorage dir_entry, SubStorage file_bucket, SubStorage file_entry) { + s64 dir_bucket_size; + R_TRY(dir_bucket.GetSize(std::addressof(dir_bucket_size))); + R_TRY(m_dir_table.Initialize(dir_bucket, DirectoryEntryMapTable::QueryBucketCount(dir_bucket_size), dir_entry)); + + s64 file_bucket_size; + R_TRY(file_bucket.GetSize(std::addressof(file_bucket_size))); + R_TRY(m_file_table.Initialize(file_bucket, FileEntryMapTable::QueryBucketCount(file_bucket_size), file_entry)); + + R_SUCCEED(); + } + + void HierarchicalRomFileTable::Finalize() { + m_dir_table.Finalize(); + m_file_table.Finalize(); + } + + Result HierarchicalRomFileTable::CreateRootDirectory() { + Position root_pos = RootPosition; + EntryKey root_key = {}; + root_key.key.parent = root_pos; + RomDirectoryEntry root_entry = { + .next = InvalidPosition, + .dir = InvalidPosition, + .file = InvalidPosition, + }; + R_RETURN(m_dir_table.Add(std::addressof(root_pos), root_key, root_entry)); + } + + Result HierarchicalRomFileTable::CreateDirectory(RomDirectoryId *out, const RomPathChar *path) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(path != nullptr); + + RomDirectoryEntry parent_entry = {}; + EntryKey new_key = {}; + R_TRY(this->FindDirectoryRecursive(std::addressof(new_key), std::addressof(parent_entry), path)); + + R_TRY(this->CheckSameEntryExists(new_key, fs::ResultDbmAlreadyExists())); + + RomDirectoryEntry new_entry = { + .next = InvalidPosition, + .dir = InvalidPosition, + .file = InvalidPosition, + }; + + Position new_pos = 0; + R_TRY_CATCH(m_dir_table.Add(std::addressof(new_pos), new_key, new_entry)) { + R_CONVERT(fs::ResultDbmKeyFull, fs::ResultDbmDirectoryEntryFull()) + } R_END_TRY_CATCH; + + *out = PositionToDirectoryId(new_pos); + + if (parent_entry.dir == InvalidPosition) { + parent_entry.dir = new_pos; + + R_TRY(m_dir_table.SetByPosition(new_key.key.parent, parent_entry)); + } else { + Position cur_pos = parent_entry.dir; + while (true) { + RomEntryKey cur_key = {}; + RomDirectoryEntry cur_entry = {}; + R_TRY(m_dir_table.GetByPosition(std::addressof(cur_key), std::addressof(cur_entry), cur_pos)); + + if (cur_entry.next == InvalidPosition) { + cur_entry.next = new_pos; + + R_TRY(m_dir_table.SetByPosition(cur_pos, cur_entry)); + break; + } + + cur_pos = cur_entry.next; + } + } + + R_SUCCEED(); + } + + Result HierarchicalRomFileTable::CreateFile(RomFileId *out, const RomPathChar *path, const FileInfo &info) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(path != nullptr); + + RomDirectoryEntry parent_entry = {}; + EntryKey new_key = {}; + R_TRY(this->FindFileRecursive(std::addressof(new_key), std::addressof(parent_entry), path)); + + R_TRY(this->CheckSameEntryExists(new_key, fs::ResultDbmAlreadyExists())); + + RomFileEntry new_entry = { + .next = InvalidPosition, + .info = info, + }; + + Position new_pos = 0; + R_TRY_CATCH(m_file_table.Add(std::addressof(new_pos), new_key, new_entry)) { + R_CONVERT(fs::ResultDbmKeyFull, fs::ResultDbmFileEntryFull()) + } R_END_TRY_CATCH; + + *out = PositionToFileId(new_pos); + + if (parent_entry.file == InvalidPosition) { + parent_entry.file = new_pos; + + R_TRY(m_dir_table.SetByPosition(new_key.key.parent, parent_entry)); + } else { + Position cur_pos = parent_entry.file; + while (true) { + RomEntryKey cur_key = {}; + RomFileEntry cur_entry = {}; + R_TRY(m_file_table.GetByPosition(std::addressof(cur_key), std::addressof(cur_entry), cur_pos)); + + if (cur_entry.next == InvalidPosition) { + cur_entry.next = new_pos; + + R_TRY(m_file_table.SetByPosition(cur_pos, cur_entry)); + break; + } + + cur_pos = cur_entry.next; + } + } + + R_SUCCEED(); + } + + Result HierarchicalRomFileTable::ConvertPathToDirectoryId(RomDirectoryId *out, const RomPathChar *path) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(path != nullptr); + + RomDirectoryEntry parent_entry = {}; + EntryKey key = {}; + R_TRY(this->FindDirectoryRecursive(std::addressof(key), std::addressof(parent_entry), path)); + + Position pos = 0; + RomDirectoryEntry entry = {}; + R_TRY(this->GetDirectoryEntry(std::addressof(pos), std::addressof(entry), key)); + + *out = PositionToDirectoryId(pos); + R_SUCCEED(); + } + + Result HierarchicalRomFileTable::ConvertPathToFileId(RomFileId *out, const RomPathChar *path) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(path != nullptr); + + RomDirectoryEntry parent_entry = {}; + EntryKey key = {}; + R_TRY(this->FindDirectoryRecursive(std::addressof(key), std::addressof(parent_entry), path)); + + Position pos = 0; + RomFileEntry entry = {}; + R_TRY(this->GetFileEntry(std::addressof(pos), std::addressof(entry), key)); + + *out = PositionToFileId(pos); + R_SUCCEED(); + } + + Result HierarchicalRomFileTable::OpenFile(FileInfo *out, const RomPathChar *path) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(path != nullptr); + + RomDirectoryEntry parent_entry = {}; + EntryKey key = {}; + R_TRY(this->FindFileRecursive(std::addressof(key), std::addressof(parent_entry), path)); + + R_RETURN(this->OpenFile(out, key)); + } + + Result HierarchicalRomFileTable::OpenFile(FileInfo *out, RomFileId id) { + AMS_ASSERT(out != nullptr); + + RomFileEntry entry = {}; + R_TRY(this->GetFileEntry(std::addressof(entry), id)); + + *out = entry.info; + R_SUCCEED(); + } + + Result HierarchicalRomFileTable::FindOpen(FindPosition *out, const RomPathChar *path) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(path != nullptr); + + RomDirectoryEntry parent_entry = {}; + EntryKey key = {}; + R_TRY(this->FindDirectoryRecursive(std::addressof(key), std::addressof(parent_entry), path)); + + R_RETURN(this->FindOpen(out, key)); + } + + Result HierarchicalRomFileTable::FindOpen(FindPosition *out, RomDirectoryId id) { + AMS_ASSERT(out != nullptr); + + out->next_dir = InvalidPosition; + out->next_file = InvalidPosition; + + RomDirectoryEntry entry = {}; + R_TRY(this->GetDirectoryEntry(std::addressof(entry), id)); + + out->next_dir = entry.dir; + out->next_file = entry.file; + + R_SUCCEED(); + } + + Result HierarchicalRomFileTable::FindNextDirectory(RomPathChar *out, FindPosition *find, size_t length) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(find != nullptr); + AMS_ASSERT(length > RomPathTool::MaxPathLength); + AMS_UNUSED(length); + + R_UNLESS(find->next_dir != InvalidPosition, fs::ResultDbmFindFinished()); + + RomEntryKey key = {}; + RomDirectoryEntry entry = {}; + size_t aux_size = 0; + R_TRY(m_dir_table.GetByPosition(std::addressof(key), std::addressof(entry), out, std::addressof(aux_size), find->next_dir)); + AMS_ASSERT(aux_size / sizeof(RomPathChar) <= RomPathTool::MaxPathLength); + + out[aux_size / sizeof(RomPathChar)] = RomStringTraits::NullTerminator; + + find->next_dir = entry.next; + R_SUCCEED(); + } + + Result HierarchicalRomFileTable::FindNextFile(RomPathChar *out, FindPosition *find, size_t length) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(find != nullptr); + AMS_ASSERT(length > RomPathTool::MaxPathLength); + AMS_UNUSED(length); + + R_UNLESS(find->next_file != InvalidPosition, fs::ResultDbmFindFinished()); + + RomEntryKey key = {}; + RomFileEntry entry = {}; + size_t aux_size = 0; + R_TRY(m_file_table.GetByPosition(std::addressof(key), std::addressof(entry), out, std::addressof(aux_size), find->next_file)); + AMS_ASSERT(aux_size / sizeof(RomPathChar) <= RomPathTool::MaxPathLength); + + out[aux_size / sizeof(RomPathChar)] = RomStringTraits::NullTerminator; + + find->next_file = entry.next; + R_SUCCEED(); + } + + Result HierarchicalRomFileTable::QueryRomFileSystemSize(s64 *out_dir_entry_size, s64 *out_file_entry_size) { + AMS_ASSERT(out_dir_entry_size != nullptr); + AMS_ASSERT(out_file_entry_size != nullptr); + + *out_dir_entry_size = m_dir_table.GetTotalEntrySize(); + *out_file_entry_size = m_file_table.GetTotalEntrySize(); + R_SUCCEED(); + } + + Result HierarchicalRomFileTable::GetParent(Position *out_pos, EntryKey *out_dir_key, RomDirectoryEntry *out_dir_entry, Position pos, RomPathTool::RomEntryName name, const RomPathChar *path) { + AMS_ASSERT(out_pos != nullptr); + AMS_ASSERT(out_dir_key != nullptr); + AMS_ASSERT(out_dir_entry != nullptr); + AMS_ASSERT(path != nullptr); + + RomEntryKey p_key = {}; + RomDirectoryEntry p_entry = {}; + R_TRY(m_dir_table.GetByPosition(std::addressof(p_key), std::addressof(p_entry), pos)); + + out_dir_key->key = p_key; + R_TRY(RomPathTool::GetParentDirectoryName(std::addressof(out_dir_key->name), name, path)); + + R_TRY(this->GetDirectoryEntry(out_pos, out_dir_entry, *out_dir_key)); + + R_SUCCEED(); + } + + Result HierarchicalRomFileTable::FindParentDirectoryRecursive(Position *out_pos, EntryKey *out_dir_key, RomDirectoryEntry *out_dir_entry, RomPathTool::PathParser *parser, const RomPathChar *path) { + AMS_ASSERT(out_pos != nullptr); + AMS_ASSERT(out_dir_key != nullptr); + AMS_ASSERT(out_dir_entry != nullptr); + AMS_ASSERT(parser != nullptr); + AMS_ASSERT(path != nullptr); + + Position dir_pos = RootPosition; + EntryKey dir_key = {}; + RomDirectoryEntry dir_entry = {}; + dir_key.key.parent = RootPosition; + + R_TRY(parser->GetNextDirectoryName(std::addressof(dir_key.name))); + R_TRY(this->GetDirectoryEntry(std::addressof(dir_pos), std::addressof(dir_entry), dir_key)); + + Position parent_pos = dir_pos; + while (!parser->IsParseFinished()) { + EntryKey old_key = dir_key; + + R_TRY(parser->GetNextDirectoryName(std::addressof(dir_key.name))); + + if (dir_key.name.IsCurrentDirectory()) { + dir_key = old_key; + continue; + } else if (dir_key.name.IsParentDirectory()) { + R_UNLESS(parent_pos != RootPosition, fs::ResultDirectoryUnobtainable()); + + R_TRY(this->GetParent(std::addressof(parent_pos), std::addressof(dir_key), std::addressof(dir_entry), dir_key.key.parent, dir_key.name, path)); + } else { + dir_key.key.parent = parent_pos; + R_TRY_CATCH(this->GetDirectoryEntry(std::addressof(dir_pos), std::addressof(dir_entry), dir_key)) { + R_CONVERT(fs::ResultDbmInvalidOperation, fs::ResultDbmNotFound()) + } R_END_TRY_CATCH; + + parent_pos = dir_pos; + } + } + + *out_pos = parent_pos; + *out_dir_key = dir_key; + *out_dir_entry = dir_entry; + R_SUCCEED(); + } + + Result HierarchicalRomFileTable::FindPathRecursive(EntryKey *out_key, RomDirectoryEntry *out_dir_entry, bool is_dir, const RomPathChar *path) { + AMS_ASSERT(out_key != nullptr); + AMS_ASSERT(out_dir_entry != nullptr); + AMS_ASSERT(path != nullptr); + + RomPathTool::PathParser parser; + R_TRY(parser.Initialize(path)); + + EntryKey parent_key = {}; + Position parent_pos = 0; + R_TRY(this->FindParentDirectoryRecursive(std::addressof(parent_pos), std::addressof(parent_key), out_dir_entry, std::addressof(parser), path)); + + if (is_dir) { + RomPathTool::RomEntryName name = {}; + R_TRY(parser.GetAsDirectoryName(std::addressof(name))); + + if (name.IsCurrentDirectory()) { + *out_key = parent_key; + if (out_key->key.parent != RootPosition) { + Position pos = 0; + R_TRY(this->GetParent(std::addressof(pos), std::addressof(parent_key), out_dir_entry, out_key->key.parent, out_key->name, path)); + } + } else if (name.IsParentDirectory()) { + R_UNLESS(parent_pos != RootPosition, fs::ResultDirectoryUnobtainable()); + + Position pos = 0; + RomDirectoryEntry cur_entry = {}; + R_TRY(this->GetParent(std::addressof(pos), out_key, std::addressof(cur_entry), parent_key.key.parent, parent_key.name, path)); + + if (out_key->key.parent != RootPosition) { + R_TRY(this->GetParent(std::addressof(pos), std::addressof(parent_key), out_dir_entry, out_key->key.parent, out_key->name, path)); + } + } else { + out_key->name = name; + out_key->key.parent = out_key->name.IsRootDirectory() ? RootPosition : parent_pos; + } + } else { + { + RomPathTool::RomEntryName name = {}; + R_TRY(parser.GetAsDirectoryName(std::addressof(name))); + R_UNLESS(!name.IsParentDirectory() || parent_pos != RootPosition, fs::ResultDirectoryUnobtainable()); + } + + R_UNLESS(!parser.IsDirectoryPath(), fs::ResultDbmInvalidOperation()); + + out_key->key.parent = parent_pos; + R_TRY(parser.GetAsFileName(std::addressof(out_key->name))); + } + + R_SUCCEED(); + } + + Result HierarchicalRomFileTable::FindDirectoryRecursive(EntryKey *out_key, RomDirectoryEntry *out_dir_entry, const RomPathChar *path) { + AMS_ASSERT(out_key != nullptr); + AMS_ASSERT(out_dir_entry != nullptr); + AMS_ASSERT(path != nullptr); + + R_RETURN(this->FindPathRecursive(out_key, out_dir_entry, true, path)); + } + + Result HierarchicalRomFileTable::FindFileRecursive(EntryKey *out_key, RomDirectoryEntry *out_dir_entry, const RomPathChar *path) { + AMS_ASSERT(out_key != nullptr); + AMS_ASSERT(out_dir_entry != nullptr); + AMS_ASSERT(path != nullptr); + + R_RETURN(this->FindPathRecursive(out_key, out_dir_entry, false, path)); + } + + Result HierarchicalRomFileTable::CheckSameEntryExists(const EntryKey &key, Result if_exists) { + /* Check dir */ + { + Position pos = InvalidPosition; + RomDirectoryEntry entry = {}; + const Result get_res = m_dir_table.Get(std::addressof(pos), std::addressof(entry), key); + if (!fs::ResultDbmKeyNotFound::Includes(get_res)) { + R_TRY(get_res); + R_THROW(if_exists); + } + } + + /* Check file */ + { + Position pos = InvalidPosition; + RomFileEntry entry = {}; + const Result get_res = m_file_table.Get(std::addressof(pos), std::addressof(entry), key); + if (!fs::ResultDbmKeyNotFound::Includes(get_res)) { + R_TRY(get_res); + R_THROW(if_exists); + } + } + + R_SUCCEED(); + } + + Result HierarchicalRomFileTable::GetDirectoryEntry(Position *out_pos, RomDirectoryEntry *out_entry, const EntryKey &key) { + AMS_ASSERT(out_pos != nullptr); + AMS_ASSERT(out_entry != nullptr); + + const Result dir_res = m_dir_table.Get(out_pos, out_entry, key); + R_UNLESS(R_FAILED(dir_res), dir_res); + R_UNLESS(fs::ResultDbmKeyNotFound::Includes(dir_res), dir_res); + + Position pos = 0; + RomFileEntry entry = {}; + const Result file_res = m_file_table.Get(std::addressof(pos), std::addressof(entry), key); + R_UNLESS(R_FAILED(file_res), fs::ResultDbmInvalidOperation()); + R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(file_res), fs::ResultDbmDirectoryNotFound()); + R_RETURN(file_res); + } + + Result HierarchicalRomFileTable::GetDirectoryEntry(RomDirectoryEntry *out_entry, RomDirectoryId id) { + AMS_ASSERT(out_entry != nullptr); + Position pos = DirectoryIdToPosition(id); + + RomEntryKey key = {}; + const Result dir_res = m_dir_table.GetByPosition(std::addressof(key), out_entry, pos); + R_UNLESS(R_FAILED(dir_res), dir_res); + R_UNLESS(fs::ResultDbmKeyNotFound::Includes(dir_res), dir_res); + + RomFileEntry entry = {}; + const Result file_res = m_file_table.GetByPosition(std::addressof(key), std::addressof(entry), pos); + R_UNLESS(R_FAILED(file_res), fs::ResultDbmInvalidOperation()); + R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(file_res), fs::ResultDbmDirectoryNotFound()); + R_RETURN(file_res); + } + + Result HierarchicalRomFileTable::GetFileEntry(Position *out_pos, RomFileEntry *out_entry, const EntryKey &key) { + AMS_ASSERT(out_pos != nullptr); + AMS_ASSERT(out_entry != nullptr); + + const Result file_res = m_file_table.Get(out_pos, out_entry, key); + R_UNLESS(R_FAILED(file_res), file_res); + R_UNLESS(fs::ResultDbmKeyNotFound::Includes(file_res), file_res); + + Position pos = 0; + RomDirectoryEntry entry = {}; + const Result dir_res = m_dir_table.Get(std::addressof(pos), std::addressof(entry), key); + R_UNLESS(R_FAILED(dir_res), fs::ResultDbmInvalidOperation()); + R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(dir_res), fs::ResultDbmFileNotFound()); + R_RETURN(dir_res); + } + + Result HierarchicalRomFileTable::GetFileEntry(RomFileEntry *out_entry, RomFileId id) { + AMS_ASSERT(out_entry != nullptr); + Position pos = FileIdToPosition(id); + + RomEntryKey key = {}; + const Result file_res = m_file_table.GetByPosition(std::addressof(key), out_entry, pos); + R_UNLESS(R_FAILED(file_res), file_res); + R_UNLESS(fs::ResultDbmKeyNotFound::Includes(file_res), file_res); + + RomDirectoryEntry entry = {}; + const Result dir_res = m_dir_table.GetByPosition(std::addressof(key), std::addressof(entry), pos); + R_UNLESS(R_FAILED(dir_res), fs::ResultDbmInvalidOperation()); + R_UNLESS(!fs::ResultDbmKeyNotFound::Includes(dir_res), fs::ResultDbmFileNotFound()); + R_RETURN(dir_res); + } + + Result HierarchicalRomFileTable::OpenFile(FileInfo *out, const EntryKey &key) { + AMS_ASSERT(out != nullptr); + + Position pos = 0; + RomFileEntry entry = {}; + R_TRY(this->GetFileEntry(std::addressof(pos), std::addressof(entry), key)); + + *out = entry.info; + R_SUCCEED(); + } + + Result HierarchicalRomFileTable::FindOpen(FindPosition *out, const EntryKey &key) { + AMS_ASSERT(out != nullptr); + + out->next_dir = InvalidPosition; + out->next_file = InvalidPosition; + + Position pos = 0; + RomDirectoryEntry entry = {}; + R_TRY(this->GetDirectoryEntry(std::addressof(pos), std::addressof(entry), key)); + + out->next_dir = entry.dir; + out->next_file = entry.file; + + R_SUCCEED(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/common/fs_dbm_rom_path_tool.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/common/fs_dbm_rom_path_tool.cpp new file mode 100644 index 00000000..b3842cb2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/common/fs_dbm_rom_path_tool.cpp @@ -0,0 +1,181 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fs::RomPathTool { + + Result PathParser::Initialize(const RomPathChar *path) { + AMS_ASSERT(path != nullptr); + + /* Require paths start with a separator, and skip repeated separators. */ + R_UNLESS(RomPathTool::IsSeparator(path[0]), fs::ResultDbmInvalidPathFormat()); + while (RomPathTool::IsSeparator(path[1])) { + ++path; + } + + m_prev_path_start = path; + m_prev_path_end = path; + m_next_path = path + 1; + + R_SUCCEED(); + } + + void PathParser::Finalize() { + m_prev_path_start = nullptr; + m_prev_path_end = nullptr; + m_next_path = nullptr; + m_finished = false; + } + + bool PathParser::IsParseFinished() const { + return m_finished; + } + + bool PathParser::IsDirectoryPath() const { + AMS_ASSERT(m_next_path != nullptr); + + if (RomPathTool::IsNullTerminator(m_next_path[0]) && RomPathTool::IsSeparator(m_next_path[-1])) { + return true; + } + + if (RomPathTool::IsCurrentDirectory(m_next_path)) { + return true; + } + + return RomPathTool::IsParentDirectory(m_next_path); + } + + Result PathParser::GetNextDirectoryName(RomEntryName *out) { + AMS_ASSERT(m_prev_path_start != nullptr); + AMS_ASSERT(m_prev_path_end != nullptr); + AMS_ASSERT(m_next_path != nullptr); + AMS_ASSERT(out != nullptr); + + /* Get as directory name. */ + R_TRY(this->GetAsDirectoryName(out)); + + /* Parse the next path. */ + const RomPathChar *cur = m_next_path; + size_t name_len; + for (name_len = 0; !RomPathTool::IsSeparator(cur[name_len]); ++name_len) { + if (RomPathTool::IsNullTerminator(cur[name_len])) { + m_finished = true; + m_prev_path_start = m_next_path; + m_next_path = cur + name_len; + m_prev_path_end = cur + name_len; + R_SUCCEED(); + } + } + + /* Advance past separators. */ + m_prev_path_start = m_next_path; + m_prev_path_end = cur + name_len; + for (m_next_path = m_prev_path_end + 1; RomPathTool::IsSeparator(m_next_path[0]); ++m_next_path) { + /* ... */ + } + + /* Check if we're finished. */ + if (RomPathTool::IsNullTerminator(m_next_path[0])) { + m_finished = true; + } + + R_SUCCEED(); + } + + Result PathParser::GetAsDirectoryName(RomEntryName *out) const { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(m_prev_path_start != nullptr); + AMS_ASSERT(m_prev_path_end != nullptr); + AMS_ASSERT(m_next_path != nullptr); + + AMS_ASSERT(m_prev_path_start <= m_prev_path_end); + + const size_t len = m_prev_path_end - m_prev_path_start; + R_UNLESS(len <= MaxPathLength, fs::ResultDbmDirectoryNameTooLong()); + + out->Initialize(m_prev_path_start, len); + R_SUCCEED(); + } + + Result PathParser::GetAsFileName(RomEntryName *out) const { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(m_prev_path_start != nullptr); + AMS_ASSERT(m_prev_path_end != nullptr); + AMS_ASSERT(m_next_path != nullptr); + + AMS_ASSERT(m_prev_path_start <= m_prev_path_end); + + const size_t len = m_prev_path_end - m_prev_path_start; + R_UNLESS(len <= MaxPathLength, fs::ResultDbmFileNameTooLong()); + + out->Initialize(m_prev_path_start, len); + R_SUCCEED(); + } + + Result GetParentDirectoryName(RomEntryName *out, const RomEntryName &cur, const RomPathChar *p) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(p != nullptr); + + const RomPathChar *start = cur.begin(); + const RomPathChar *end = cur.end() - 1; + + s32 depth = 1; + if (cur.IsParentDirectory()) { + ++depth; + } + + if (start > p) { + size_t len = 0; + for (const RomPathChar *head = start - 1; head >= p; --head) { + if (RomPathTool::IsSeparator(*head)) { + if (IsCurrentDirectory(head + 1, len)) { + ++depth; + } + + if (IsParentDirectory(head + 1, len)) { + depth += 2; + } + + if (depth == 0) { + start = head + 1; + break; + } + + do { + --head; + } while (head > p && RomPathTool::IsSeparator(*head)); + + end = head; + len = 0; + --depth; + } + + ++len; + } + + R_UNLESS(depth == 0, fs::ResultDirectoryUnobtainable()); + } + + if (end <= p) { + out->Initialize(p, 0); + } else { + out->Initialize(start, end - start + 1); + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/common/fs_file_storage.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/common/fs_file_storage.cpp new file mode 100644 index 00000000..816bd0a1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/common/fs_file_storage.cpp @@ -0,0 +1,179 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fs { + + Result FileStorage::UpdateSize() { + R_SUCCEED_IF(m_size != InvalidSize); + R_RETURN(m_base_file->GetSize(std::addressof(m_size))); + } + + Result FileStorage::Read(s64 offset, void *buffer, size_t size) { + /* Immediately succeed if there's nothing to read. */ + R_SUCCEED_IF(size == 0); + + /* Validate buffer. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Ensure our size is valid. */ + R_TRY(this->UpdateSize()); + + /* Ensure our access is valid. */ + R_TRY(IStorage::CheckAccessRange(offset, size, m_size)); + + size_t read_size; + R_RETURN(m_base_file->Read(std::addressof(read_size), offset, buffer, size)); + } + + Result FileStorage::Write(s64 offset, const void *buffer, size_t size) { + /* Immediately succeed if there's nothing to write. */ + R_SUCCEED_IF(size == 0); + + /* Validate buffer. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Ensure our size is valid. */ + R_TRY(this->UpdateSize()); + + /* Ensure our access is valid. */ + R_TRY(IStorage::CheckAccessRange(offset, size, m_size)); + + R_RETURN(m_base_file->Write(offset, buffer, size, fs::WriteOption())); + } + + Result FileStorage::Flush() { + R_RETURN(m_base_file->Flush()); + } + + Result FileStorage::GetSize(s64 *out_size) { + R_TRY(this->UpdateSize()); + *out_size = m_size; + R_SUCCEED(); + } + + Result FileStorage::SetSize(s64 size) { + m_size = InvalidSize; + R_RETURN(m_base_file->SetSize(size)); + } + + Result FileStorage::OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + switch (op_id) { + case OperationId::Invalidate: + R_RETURN(m_base_file->OperateRange(OperationId::Invalidate, offset, size)); + case OperationId::QueryRange: + if (size == 0) { + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(QueryRangeInfo), fs::ResultInvalidSize()); + reinterpret_cast<QueryRangeInfo *>(dst)->Clear(); + R_SUCCEED(); + } + + R_TRY(this->UpdateSize()); + R_TRY(IStorage::CheckOffsetAndSize(offset, size)); + + R_RETURN(m_base_file->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + default: + R_THROW(fs::ResultUnsupportedOperateRangeForFileStorage()); + } + } + + Result FileStorageBasedFileSystem::Initialize(std::shared_ptr<fs::fsa::IFileSystem> base_file_system, const fs::Path &path, fs::OpenMode mode) { + /* Open the file. */ + std::unique_ptr<fs::fsa::IFile> base_file; + R_TRY(base_file_system->OpenFile(std::addressof(base_file), path, mode)); + + /* Set the file. */ + this->SetFile(std::move(base_file)); + m_base_file_system = std::move(base_file_system); + + R_SUCCEED(); + } + + Result FileHandleStorage::UpdateSize() { + R_SUCCEED_IF(m_size != InvalidSize); + R_RETURN(GetFileSize(std::addressof(m_size), m_handle)); + } + + Result FileHandleStorage::Read(s64 offset, void *buffer, size_t size) { + /* Lock the mutex. */ + std::scoped_lock lk(m_mutex); + + /* Immediately succeed if there's nothing to read. */ + R_SUCCEED_IF(size == 0); + + /* Validate buffer. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Ensure our size is valid. */ + R_TRY(this->UpdateSize()); + + /* Ensure our access is valid. */ + R_TRY(IStorage::CheckAccessRange(offset, size, m_size)); + + R_RETURN(ReadFile(m_handle, offset, buffer, size, fs::ReadOption())); + } + + Result FileHandleStorage::Write(s64 offset, const void *buffer, size_t size) { + /* Lock the mutex. */ + std::scoped_lock lk(m_mutex); + + /* Immediately succeed if there's nothing to write. */ + R_SUCCEED_IF(size == 0); + + /* Validate buffer. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Ensure our size is valid. */ + R_TRY(this->UpdateSize()); + + /* Ensure our access is valid. */ + R_TRY(IStorage::CheckAccessRange(offset, size, m_size)); + + R_RETURN(WriteFile(m_handle, offset, buffer, size, fs::WriteOption())); + } + + Result FileHandleStorage::Flush() { + R_RETURN(FlushFile(m_handle)); + } + + Result FileHandleStorage::GetSize(s64 *out_size) { + R_TRY(this->UpdateSize()); + *out_size = m_size; + R_SUCCEED(); + } + + Result FileHandleStorage::SetSize(s64 size) { + m_size = InvalidSize; + R_RETURN(SetFileSize(m_handle, size)); + } + + Result FileHandleStorage::OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + AMS_UNUSED(src, src_size); + + switch (op_id) { + case OperationId::QueryRange: + /* Validate buffer and size. */ + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(QueryRangeInfo), fs::ResultInvalidSize()); + + R_RETURN(QueryRange(static_cast<QueryRangeInfo *>(dst), m_handle, offset, size)); + default: + R_THROW(fs::ResultUnsupportedOperateRangeForFileHandleStorage()); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_access_log.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_access_log.cpp new file mode 100644 index 00000000..157e1ca3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_access_log.cpp @@ -0,0 +1,910 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/fs_file_system_proxy_service_object.hpp" +#include "fsa/fs_user_mount_table.hpp" +#include "fsa/fs_directory_accessor.hpp" +#include "fsa/fs_file_accessor.hpp" +#include "fsa/fs_filesystem_accessor.hpp" + +#define AMS_FS_IMPL_ACCESS_LOG_AMS_API_VERSION "ams_version: " STRINGIZE(ATMOSPHERE_RELEASE_VERSION_MAJOR) "." STRINGIZE(ATMOSPHERE_RELEASE_VERSION_MINOR) "." STRINGIZE(ATMOSPHERE_RELEASE_VERSION_MICRO) + +/* TODO: Other specs? */ +#define AMS_FS_IMPL_ACCESS_LOG_SPEC "spec: NX" + +namespace ams::fs { + + /* Forward declare priority getter. */ + fs::PriorityRaw GetPriorityRawOnCurrentThreadInternal(); + + namespace { + + constinit u32 g_global_access_log_mode = fs::AccessLogMode_None; + constinit u32 g_local_access_log_target = fs::impl::AccessLogTarget_None; + + constinit std::atomic_bool g_access_log_initialized = false; + constinit os::SdkMutex g_access_log_initialization_mutex; + + void SetLocalAccessLogImpl(bool enabled) { + if (enabled) { + g_local_access_log_target |= fs::impl::AccessLogTarget_Application; + } else { + g_local_access_log_target &= ~fs::impl::AccessLogTarget_Application; + } + } + + } + + Result GetGlobalAccessLogMode(u32 *out) { + const auto fsp = impl::GetFileSystemProxyServiceObject(); + AMS_FS_R_TRY(fsp->GetGlobalAccessLogMode(out)); + R_SUCCEED(); + } + + Result SetGlobalAccessLogMode(u32 mode) { + const auto fsp = impl::GetFileSystemProxyServiceObject(); + AMS_FS_R_TRY(fsp->SetGlobalAccessLogMode(mode)); + R_SUCCEED(); + } + + void SetLocalAccessLog(bool enabled) { + SetLocalAccessLogImpl(enabled); + } + + void SetLocalApplicationAccessLog(bool enabled) { + SetLocalAccessLogImpl(enabled); + } + + void SetLocalSystemAccessLogForDebug(bool enabled) { + #if defined(AMS_BUILD_FOR_DEBUGGING) + if (enabled) { + g_local_access_log_target |= (fs::impl::AccessLogTarget_Application | fs::impl::AccessLogTarget_System); + } else { + g_local_access_log_target &= ~(fs::impl::AccessLogTarget_Application | fs::impl::AccessLogTarget_System); + } + #else + AMS_UNUSED(enabled); + #endif + } + +} + +namespace ams::fs::impl { + + #define ADD_ENUM_CASE(v) case v: return #v + + const char *IdString::ToValueString(int id) { + const int len = util::SNPrintf(m_buffer, sizeof(m_buffer), "%d", id); + AMS_ASSERT(static_cast<size_t>(len) < sizeof(m_buffer)); + AMS_UNUSED(len); + return m_buffer; + } + + template<> const char *IdString::ToString<fs::Priority>(fs::Priority id) { + switch (id) { + case fs::Priority_Realtime: return "Realtime"; + case fs::Priority_Normal: return "Normal"; + case fs::Priority_Low: return "Low"; + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<fs::PriorityRaw>(fs::PriorityRaw id) { + switch (id) { + case fs::PriorityRaw_Realtime: return "Realtime"; + case fs::PriorityRaw_Normal: return "Normal"; + case fs::PriorityRaw_Low: return "Low"; + case fs::PriorityRaw_Background: return "Realtime"; + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<fs::ContentStorageId>(fs::ContentStorageId id) { + switch (id) { + using enum fs::ContentStorageId; + ADD_ENUM_CASE(User); + ADD_ENUM_CASE(System); + ADD_ENUM_CASE(SdCard); + ADD_ENUM_CASE(System0); + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<fs::SaveDataSpaceId>(fs::SaveDataSpaceId id) { + switch (id) { + using enum fs::SaveDataSpaceId; + ADD_ENUM_CASE(System); + ADD_ENUM_CASE(User); + ADD_ENUM_CASE(SdSystem); + ADD_ENUM_CASE(ProperSystem); + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<fs::ContentType>(fs::ContentType id) { + switch (id) { + case fs::ContentType_Meta: return "Meta"; + case fs::ContentType_Control: return "Control"; + case fs::ContentType_Manual: return "Manual"; + case fs::ContentType_Logo: return "Logo"; + case fs::ContentType_Data: return "Data"; + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<fs::MountHostOption>(fs::MountHostOption id) { + if (id == MountHostOption::PseudoCaseSensitive) { + return "MountHostOptionFlag_PseudoCaseSensitive"; + } else { + return ToValueString(static_cast<int>(id._value)); + } + } + + template<> const char *IdString::ToString<fs::BisPartitionId>(fs::BisPartitionId id) { + switch (id) { + using enum fs::BisPartitionId; + ADD_ENUM_CASE(BootPartition1Root); + ADD_ENUM_CASE(BootPartition2Root); + ADD_ENUM_CASE(UserDataRoot); + ADD_ENUM_CASE(BootConfigAndPackage2Part1); + ADD_ENUM_CASE(BootConfigAndPackage2Part2); + ADD_ENUM_CASE(BootConfigAndPackage2Part3); + ADD_ENUM_CASE(BootConfigAndPackage2Part4); + ADD_ENUM_CASE(BootConfigAndPackage2Part5); + ADD_ENUM_CASE(BootConfigAndPackage2Part6); + ADD_ENUM_CASE(CalibrationBinary); + ADD_ENUM_CASE(CalibrationFile); + ADD_ENUM_CASE(SafeMode); + ADD_ENUM_CASE(User); + ADD_ENUM_CASE(System); + ADD_ENUM_CASE(SystemProperEncryption); + ADD_ENUM_CASE(SystemProperPartition); + ADD_ENUM_CASE(DeviceTreeBlob); + ADD_ENUM_CASE(System0); + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<fs::DirectoryEntryType>(fs::DirectoryEntryType type) { + switch (type) { + case fs::DirectoryEntryType_Directory: return "Directory"; + case fs::DirectoryEntryType_File: return "File"; + default: return ToValueString(static_cast<int>(type)); + } + } + + template<> const char *IdString::ToString<fs::GameCardPartition>(fs::GameCardPartition id) { + switch (id) { + using enum fs::GameCardPartition; + ADD_ENUM_CASE(Update); + ADD_ENUM_CASE(Normal); + ADD_ENUM_CASE(Secure); + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<fssystem::NcaHeader::ContentType>(fssystem::NcaHeader::ContentType id) { + switch (id) { + using enum fssystem::NcaHeader::ContentType; + ADD_ENUM_CASE(Program); + ADD_ENUM_CASE(Meta); + ADD_ENUM_CASE(Control); + ADD_ENUM_CASE(Manual); + ADD_ENUM_CASE(Data); + ADD_ENUM_CASE(PublicData); + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<fssystem::NcaHeader::DistributionType>(fssystem::NcaHeader::DistributionType id) { + switch (id) { + using enum fssystem::NcaHeader::DistributionType; + ADD_ENUM_CASE(Download); + ADD_ENUM_CASE(GameCard); + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<fssystem::NcaHeader::EncryptionType>(fssystem::NcaHeader::EncryptionType id) { + switch (id) { + using enum fssystem::NcaHeader::EncryptionType; + ADD_ENUM_CASE(Auto); + ADD_ENUM_CASE(None); + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<fssystem::NcaHeader::DecryptionKey>(fssystem::NcaHeader::DecryptionKey id) { + switch (id) { + using enum fssystem::NcaHeader::DecryptionKey; + case DecryptionKey_AesXts1: return "AesXts1"; + case DecryptionKey_AesXts2: return "AesXts2"; + case DecryptionKey_AesCtr: return "AesCtr"; + case DecryptionKey_AesCtrEx: return "AesCtrEx"; + case DecryptionKey_AesCtrHw: return "AesCtrHw"; + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<fssystem::NcaFsHeader::FsType>(fssystem::NcaFsHeader::FsType id) { + switch (id) { + using enum fssystem::NcaFsHeader::FsType; + ADD_ENUM_CASE(RomFs); + ADD_ENUM_CASE(PartitionFs); + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<fssystem::NcaFsHeader::EncryptionType>(fssystem::NcaFsHeader::EncryptionType id) { + switch (id) { + using enum fssystem::NcaFsHeader::EncryptionType; + ADD_ENUM_CASE(Auto); + ADD_ENUM_CASE(None); + ADD_ENUM_CASE(AesXts); + ADD_ENUM_CASE(AesCtr); + ADD_ENUM_CASE(AesCtrEx); + ADD_ENUM_CASE(AesCtrSkipLayerHash); + ADD_ENUM_CASE(AesCtrExSkipLayerHash); + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<fssystem::NcaFsHeader::HashType>(fssystem::NcaFsHeader::HashType id) { + switch (id) { + using enum fssystem::NcaFsHeader::HashType; + ADD_ENUM_CASE(Auto); + ADD_ENUM_CASE(None); + ADD_ENUM_CASE(HierarchicalSha256Hash); + ADD_ENUM_CASE(HierarchicalIntegrityHash); + ADD_ENUM_CASE(AutoSha3); + ADD_ENUM_CASE(HierarchicalSha3256Hash); + ADD_ENUM_CASE(HierarchicalIntegritySha3Hash); + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<fssystem::NcaFsHeader::MetaDataHashType>(fssystem::NcaFsHeader::MetaDataHashType id) { + switch (id) { + using enum fssystem::NcaFsHeader::MetaDataHashType; + ADD_ENUM_CASE(None); + ADD_ENUM_CASE(HierarchicalIntegrity); + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<gc::impl::MemoryCapacity>(gc::impl::MemoryCapacity id) { + switch (id) { + using enum gc::impl::MemoryCapacity; + case MemoryCapacity_1GB: return "1GB"; + case MemoryCapacity_2GB: return "2GB"; + case MemoryCapacity_4GB: return "4GB"; + case MemoryCapacity_8GB: return "8GB"; + case MemoryCapacity_16GB: return "16GB"; + case MemoryCapacity_32GB: return "32GB"; + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<gc::impl::SelSec>(gc::impl::SelSec id) { + switch (id) { + using enum gc::impl::SelSec; + case SelSec_T1: return "T1"; + case SelSec_T2: return "T2"; + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<gc::impl::KekIndex>(gc::impl::KekIndex id) { + switch (id) { + using enum gc::impl::KekIndex; + case KekIndex_Version0: return "Version0"; + case KekIndex_VersionForDev: return "VersionForDev"; + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<gc::impl::AccessControl1ClockRate>(gc::impl::AccessControl1ClockRate id) { + switch (id) { + using enum gc::impl::AccessControl1ClockRate; + case AccessControl1ClockRate_25MHz: return "25 MHz"; + case AccessControl1ClockRate_50MHz: return "50 MHz"; + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<gc::impl::FwVersion>(gc::impl::FwVersion id) { + switch (id) { + using enum gc::impl::FwVersion; + case FwVersion_ForDev: return "ForDev"; + case FwVersion_1_0_0: return "1.0.0"; + case FwVersion_4_0_0: return "4.0.0"; + case FwVersion_9_0_0: return "9.0.0"; + case FwVersion_11_0_0: return "11.0.0"; + case FwVersion_12_0_0: return "12.0.0"; + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<fs::GameCardCompatibilityType>(fs::GameCardCompatibilityType id) { + switch (id) { + using enum fs::GameCardCompatibilityType; + ADD_ENUM_CASE(Normal); + ADD_ENUM_CASE(Terra); + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<fssrv::impl::AccessControlBits::Bits>(fssrv::impl::AccessControlBits::Bits id) { + switch (id) { + using enum fssrv::impl::AccessControlBits::Bits; + ADD_ENUM_CASE(ApplicationInfo); + ADD_ENUM_CASE(BootModeControl); + ADD_ENUM_CASE(Calibration); + ADD_ENUM_CASE(SystemSaveData); + ADD_ENUM_CASE(GameCard); + ADD_ENUM_CASE(SaveDataBackUp); + ADD_ENUM_CASE(SaveDataManagement); + ADD_ENUM_CASE(BisAllRaw); + ADD_ENUM_CASE(GameCardRaw); + ADD_ENUM_CASE(GameCardPrivate); + ADD_ENUM_CASE(SetTime); + ADD_ENUM_CASE(ContentManager); + ADD_ENUM_CASE(ImageManager); + ADD_ENUM_CASE(CreateSaveData); + ADD_ENUM_CASE(SystemSaveDataManagement); + ADD_ENUM_CASE(BisFileSystem); + ADD_ENUM_CASE(SystemUpdate); + ADD_ENUM_CASE(SaveDataMeta); + ADD_ENUM_CASE(DeviceSaveData); + ADD_ENUM_CASE(SettingsControl); + ADD_ENUM_CASE(SystemData); + ADD_ENUM_CASE(SdCard); + ADD_ENUM_CASE(Host); + ADD_ENUM_CASE(FillBis); + ADD_ENUM_CASE(CorruptSaveData); + ADD_ENUM_CASE(SaveDataForDebug); + ADD_ENUM_CASE(FormatSdCard); + ADD_ENUM_CASE(GetRightsId); + ADD_ENUM_CASE(RegisterExternalKey); + ADD_ENUM_CASE(RegisterUpdatePartition); + ADD_ENUM_CASE(SaveDataTransfer); + ADD_ENUM_CASE(DeviceDetection); + ADD_ENUM_CASE(AccessFailureResolution); + ADD_ENUM_CASE(SaveDataTransferVersion2); + ADD_ENUM_CASE(RegisterProgramIndexMapInfo); + ADD_ENUM_CASE(CreateOwnSaveData); + ADD_ENUM_CASE(MoveCacheStorage); + + ADD_ENUM_CASE(Debug); + ADD_ENUM_CASE(FullPermission); + default: return ToValueString(util::CountTrailingZeros(util::ToUnderlying(id))); + } + } + + template<> const char *IdString::ToString<fssrv::impl::AccessControl::AccessibilityType>(fssrv::impl::AccessControl::AccessibilityType id) { + switch (id) { + using enum fssrv::impl::AccessControl::AccessibilityType; + ADD_ENUM_CASE(MountLogo); + ADD_ENUM_CASE(MountContentMeta); + ADD_ENUM_CASE(MountContentControl); + ADD_ENUM_CASE(MountContentManual); + ADD_ENUM_CASE(MountContentData); + ADD_ENUM_CASE(MountApplicationPackage); + ADD_ENUM_CASE(MountSaveDataStorage); + ADD_ENUM_CASE(MountContentStorage); + ADD_ENUM_CASE(MountImageAndVideoStorage); + ADD_ENUM_CASE(MountCloudBackupWorkStorage); + ADD_ENUM_CASE(MountCustomStorage); + ADD_ENUM_CASE(MountBisCalibrationFile); + ADD_ENUM_CASE(MountBisSafeMode); + ADD_ENUM_CASE(MountBisUser); + ADD_ENUM_CASE(MountBisSystem); + ADD_ENUM_CASE(MountBisSystemProperEncryption); + ADD_ENUM_CASE(MountBisSystemProperPartition); + ADD_ENUM_CASE(MountSdCard); + ADD_ENUM_CASE(MountGameCard); + ADD_ENUM_CASE(MountDeviceSaveData); + ADD_ENUM_CASE(MountSystemSaveData); + ADD_ENUM_CASE(MountOthersSaveData); + ADD_ENUM_CASE(MountOthersSystemSaveData); + ADD_ENUM_CASE(OpenBisPartitionBootPartition1Root); + ADD_ENUM_CASE(OpenBisPartitionBootPartition2Root); + ADD_ENUM_CASE(OpenBisPartitionUserDataRoot); + ADD_ENUM_CASE(OpenBisPartitionBootConfigAndPackage2Part1); + ADD_ENUM_CASE(OpenBisPartitionBootConfigAndPackage2Part2); + ADD_ENUM_CASE(OpenBisPartitionBootConfigAndPackage2Part3); + ADD_ENUM_CASE(OpenBisPartitionBootConfigAndPackage2Part4); + ADD_ENUM_CASE(OpenBisPartitionBootConfigAndPackage2Part5); + ADD_ENUM_CASE(OpenBisPartitionBootConfigAndPackage2Part6); + ADD_ENUM_CASE(OpenBisPartitionCalibrationBinary); + ADD_ENUM_CASE(OpenBisPartitionCalibrationFile); + ADD_ENUM_CASE(OpenBisPartitionSafeMode); + ADD_ENUM_CASE(OpenBisPartitionUser); + ADD_ENUM_CASE(OpenBisPartitionSystem); + ADD_ENUM_CASE(OpenBisPartitionSystemProperEncryption); + ADD_ENUM_CASE(OpenBisPartitionSystemProperPartition); + ADD_ENUM_CASE(OpenSdCardStorage); + ADD_ENUM_CASE(OpenGameCardStorage); + ADD_ENUM_CASE(MountSystemDataPrivate); + ADD_ENUM_CASE(MountHost); + ADD_ENUM_CASE(MountRegisteredUpdatePartition); + ADD_ENUM_CASE(MountSaveDataInternalStorage); + ADD_ENUM_CASE(MountTemporaryDirectory); + ADD_ENUM_CASE(MountAllBaseFileSystem); + ADD_ENUM_CASE(NotMount); + default: return ToValueString(static_cast<int>(id)); + } + } + + template<> const char *IdString::ToString<fssrv::impl::AccessControl::OperationType>(fssrv::impl::AccessControl::OperationType id) { + switch (id) { + using enum fssrv::impl::AccessControl::OperationType; + ADD_ENUM_CASE(InvalidateBisCache); + ADD_ENUM_CASE(EraseMmc); + ADD_ENUM_CASE(GetGameCardDeviceCertificate); + ADD_ENUM_CASE(GetGameCardIdSet); + ADD_ENUM_CASE(FinalizeGameCardDriver); + ADD_ENUM_CASE(GetGameCardAsicInfo); + ADD_ENUM_CASE(CreateSaveData); + ADD_ENUM_CASE(DeleteSaveData); + ADD_ENUM_CASE(CreateSystemSaveData); + ADD_ENUM_CASE(CreateOthersSystemSaveData); + ADD_ENUM_CASE(DeleteSystemSaveData); + ADD_ENUM_CASE(OpenSaveDataInfoReader); + ADD_ENUM_CASE(OpenSaveDataInfoReaderForSystem); + ADD_ENUM_CASE(OpenSaveDataInfoReaderForInternal); + ADD_ENUM_CASE(OpenSaveDataMetaFile); + ADD_ENUM_CASE(SetCurrentPosixTime); + ADD_ENUM_CASE(ReadSaveDataFileSystemExtraData); + ADD_ENUM_CASE(SetGlobalAccessLogMode); + ADD_ENUM_CASE(SetSpeedEmulationMode); + ADD_ENUM_CASE(Debug); + ADD_ENUM_CASE(FillBis); + ADD_ENUM_CASE(CorruptSaveData); + ADD_ENUM_CASE(CorruptSystemSaveData); + ADD_ENUM_CASE(VerifySaveData); + ADD_ENUM_CASE(DebugSaveData); + ADD_ENUM_CASE(FormatSdCard); + ADD_ENUM_CASE(GetRightsId); + ADD_ENUM_CASE(RegisterExternalKey); + ADD_ENUM_CASE(SetEncryptionSeed); + ADD_ENUM_CASE(WriteSaveDataFileSystemExtraDataTimeStamp); + ADD_ENUM_CASE(WriteSaveDataFileSystemExtraDataFlags); + ADD_ENUM_CASE(WriteSaveDataFileSystemExtraDataCommitId); + ADD_ENUM_CASE(WriteSaveDataFileSystemExtraDataAll); + ADD_ENUM_CASE(ExtendSaveData); + ADD_ENUM_CASE(ExtendSystemSaveData); + ADD_ENUM_CASE(ExtendOthersSystemSaveData); + ADD_ENUM_CASE(RegisterUpdatePartition); + ADD_ENUM_CASE(OpenSaveDataTransferManager); + ADD_ENUM_CASE(OpenSaveDataTransferManagerVersion2); + ADD_ENUM_CASE(OpenSaveDataTransferManagerForSaveDataRepair); + ADD_ENUM_CASE(OpenSaveDataTransferManagerForSaveDataRepairTool); + ADD_ENUM_CASE(OpenSaveDataTransferProhibiter); + ADD_ENUM_CASE(OpenSaveDataMover); + ADD_ENUM_CASE(OpenBisWiper); + ADD_ENUM_CASE(ListAccessibleSaveDataOwnerId); + ADD_ENUM_CASE(ControlMmcPatrol); + ADD_ENUM_CASE(OverrideSaveDataTransferTokenSignVerificationKey); + ADD_ENUM_CASE(OpenSdCardDetectionEventNotifier); + ADD_ENUM_CASE(OpenGameCardDetectionEventNotifier); + ADD_ENUM_CASE(OpenSystemDataUpdateEventNotifier); + ADD_ENUM_CASE(NotifySystemDataUpdateEvent); + ADD_ENUM_CASE(OpenAccessFailureDetectionEventNotifier); + ADD_ENUM_CASE(GetAccessFailureDetectionEvent); + ADD_ENUM_CASE(IsAccessFailureDetected); + ADD_ENUM_CASE(ResolveAccessFailure); + ADD_ENUM_CASE(AbandonAccessFailure); + ADD_ENUM_CASE(QuerySaveDataInternalStorageTotalSize); + ADD_ENUM_CASE(GetSaveDataCommitId); + ADD_ENUM_CASE(SetSdCardAccessibility); + ADD_ENUM_CASE(SimulateDevice); + ADD_ENUM_CASE(CreateSaveDataWithHashSalt); + ADD_ENUM_CASE(RegisterProgramIndexMapInfo); + ADD_ENUM_CASE(ChallengeCardExistence); + ADD_ENUM_CASE(CreateOwnSaveData); + ADD_ENUM_CASE(DeleteOwnSaveData); + ADD_ENUM_CASE(ReadOwnSaveDataFileSystemExtraData); + ADD_ENUM_CASE(ExtendOwnSaveData); + ADD_ENUM_CASE(OpenOwnSaveDataTransferProhibiter); + ADD_ENUM_CASE(FindOwnSaveDataWithFilter); + ADD_ENUM_CASE(OpenSaveDataTransferManagerForRepair); + ADD_ENUM_CASE(SetDebugConfiguration); + ADD_ENUM_CASE(OpenDataStorageByPath); + default: return ToValueString(static_cast<int>(id)); + } + } + + namespace { + + class AccessLogPrinterCallbackManager { + private: + AccessLogPrinterCallback m_callback; + public: + constexpr AccessLogPrinterCallbackManager() : m_callback(nullptr) { /* ... */ } + + constexpr bool IsRegisteredCallback() const { return m_callback != nullptr; } + + constexpr void RegisterCallback(AccessLogPrinterCallback c) { + AMS_ASSERT(m_callback == nullptr); + m_callback = c; + } + + constexpr int InvokeCallback(char *buf, size_t size) const { + AMS_ASSERT(m_callback != nullptr); + return m_callback(buf, size); + } + }; + + constinit AccessLogPrinterCallbackManager g_access_log_manager_printer_callback_manager; + + ALWAYS_INLINE AccessLogPrinterCallbackManager &GetStartAccessLogPrinterCallbackManager() { + return g_access_log_manager_printer_callback_manager; + } + + const char *GetPriorityRawName(fs::impl::IdString &id_string) { + return id_string.ToString(fs::GetPriorityRawOnCurrentThreadInternal()); + } + + Result OutputAccessLogToSdCardImpl(const char *log, size_t size) { + const auto fsp = impl::GetFileSystemProxyServiceObject(); + AMS_FS_R_TRY(fsp->OutputAccessLogToSdCard(sf::InBuffer(log, size))); + R_SUCCEED(); + } + + void OutputAccessLogToSdCard(const char *format, std::va_list vl) { + if ((g_global_access_log_mode & AccessLogMode_SdCard) != 0) { + /* Create a buffer to hold the log's input string. */ + int log_buffer_size = 1_KB; + auto log_buffer = fs::impl::MakeUnique<char[]>(log_buffer_size); + while (true) { + if (log_buffer == nullptr) { + return; + } + + const auto size = util::VSNPrintf(log_buffer.get(), log_buffer_size, format, vl); + if (size < log_buffer_size) { + break; + } + + log_buffer_size = size + 1; + log_buffer = fs::impl::MakeUnique<char[]>(log_buffer_size); + } + + /* Output. */ + OutputAccessLogToSdCardImpl(log_buffer.get(), log_buffer_size - 1); + } + } + + void OutputAccessLogImpl(const char *log, size_t size) { + if ((g_global_access_log_mode & AccessLogMode_Log) != 0) { + /* TODO: Support logging. */ + } else if ((g_global_access_log_mode & AccessLogMode_SdCard) != 0) { + OutputAccessLogToSdCardImpl(log, size - 1); + } + } + + void OutputAccessLog(Result result, const char *priority, os::Tick start, os::Tick end, const char *name, const void *handle, const char *format, std::va_list vl) { + /* Create a buffer to hold the log's input string. */ + int str_buffer_size = 1_KB; + auto str_buffer = fs::impl::MakeUnique<char[]>(str_buffer_size); + while (true) { + if (str_buffer == nullptr) { + return; + } + + const auto size = util::VSNPrintf(str_buffer.get(), str_buffer_size, format, vl); + if (size < str_buffer_size) { + break; + } + + str_buffer_size = size + 1; + str_buffer = fs::impl::MakeUnique<char[]>(str_buffer_size); + } + + /* Create a buffer to hold the log. */ + int log_buffer_size = 0; + decltype(str_buffer) log_buffer; + { + /* Declare format string. */ + constexpr const char FormatString[] = "FS_ACCESS { " + "start: %9" PRId64 ", " + "end: %9" PRId64 ", " + "result: 0x%08" PRIX32 ", " + "handle: 0x%p, " + "priority: %s, " + "function: \"%s\"" + "%s" + " }\n"; + + /* Convert the timing to ms. */ + const s64 start_ms = start.ToTimeSpan().GetMilliSeconds(); + const s64 end_ms = end.ToTimeSpan().GetMilliSeconds(); + + /* Print the log. */ + int try_size = std::max<int>(str_buffer_size + sizeof(FormatString) + 0x100, 1_KB); + while (true) { + log_buffer = fs::impl::MakeUnique<char[]>(try_size); + if (log_buffer == nullptr) { + return; + } + + log_buffer_size = 1 + util::SNPrintf(log_buffer.get(), try_size, FormatString, start_ms, end_ms, result.GetValue(), handle, priority, name, str_buffer.get()); + if (log_buffer_size <= try_size) { + break; + } + try_size = log_buffer_size; + } + } + + OutputAccessLogImpl(log_buffer.get(), log_buffer_size); + } + + void GetProgramIndexForAccessLog(u32 *out_index, u32 *out_count) { + if (hos::GetVersion() >= hos::Version_7_0_0) { + /* Use libnx bindings if available. */ + const auto fsp = impl::GetFileSystemProxyServiceObject(); + R_ABORT_UNLESS(fsp->GetProgramIndexForAccessLog(out_index, out_count)); + } else { + /* Use hardcoded defaults. */ + *out_index = 0; + *out_count = 0; + } + } + + void OutputAccessLogStart() { + /* Get the program index. */ + u32 program_index = 0, program_count = 0; + GetProgramIndexForAccessLog(std::addressof(program_index), std::addressof(program_count)); + + /* Print the log buffer. */ + if (program_count < 2) { + constexpr const char StartLog[] = "FS_ACCESS: { " + AMS_FS_IMPL_ACCESS_LOG_AMS_API_VERSION ", " + AMS_FS_IMPL_ACCESS_LOG_SPEC + " }\n"; + + OutputAccessLogImpl(StartLog, sizeof(StartLog)); + } else { + constexpr const char StartLog[] = "FS_ACCESS: { " + AMS_FS_IMPL_ACCESS_LOG_AMS_API_VERSION ", " + AMS_FS_IMPL_ACCESS_LOG_SPEC ", " + "program_index: %d" + " }\n"; + + char log_buffer[0x80]; + const int len = 1 + util::SNPrintf(log_buffer, sizeof(log_buffer), StartLog, static_cast<int>(program_index)); + if (static_cast<size_t>(len) <= sizeof(log_buffer)) { + OutputAccessLogImpl(log_buffer, len); + } + } + } + + [[maybe_unused]] void OutputAccessLogStartForSystem() { + constexpr const char StartLog[] = "FS_ACCESS: { " + AMS_FS_IMPL_ACCESS_LOG_AMS_API_VERSION ", " + AMS_FS_IMPL_ACCESS_LOG_SPEC ", " + "for_system: true" + " }\n"; + OutputAccessLogImpl(StartLog, sizeof(StartLog)); + } + + void OutputAccessLogStartGeneratedByCallback() { + /* Get the manager. */ + const auto &manager = GetStartAccessLogPrinterCallbackManager(); + if (manager.IsRegisteredCallback()) { + /* Invoke the callback. */ + char log_buffer[0x80]; + const int len = 1 + manager.InvokeCallback(log_buffer, sizeof(log_buffer)); + + /* Print, if we fit. */ + if (static_cast<size_t>(len) <= sizeof(log_buffer)) { + OutputAccessLogImpl(log_buffer, len); + } + } + } + + } + + bool IsEnabledAccessLog(u32 target) { + /* If we don't need to log to the target, return false. */ + if ((g_local_access_log_target & target) == 0) { + return false; + } + + /* Ensure we've initialized. */ + if (!g_access_log_initialized) { + std::scoped_lock lk(g_access_log_initialization_mutex); + if (!g_access_log_initialized) { + + #if defined (AMS_BUILD_FOR_DEBUGGING) + if ((g_local_access_log_target & fs::impl::AccessLogTarget_System) != 0) + { + g_global_access_log_mode = AccessLogMode_Log; + OutputAccessLogStartForSystem(); + OutputAccessLogStartGeneratedByCallback(); + } + else + #endif + { + AMS_FS_R_ABORT_UNLESS(GetGlobalAccessLogMode(std::addressof(g_global_access_log_mode))); + if (g_global_access_log_mode != AccessLogMode_None) { + OutputAccessLogStart(); + OutputAccessLogStartGeneratedByCallback(); + } + } + + g_access_log_initialized = true; + } + } + + return g_global_access_log_mode != AccessLogMode_None; + } + + bool IsEnabledAccessLog() { + return IsEnabledAccessLog(fs::impl::AccessLogTarget_Application | fs::impl::AccessLogTarget_System); + } + + void RegisterStartAccessLogPrinterCallback(AccessLogPrinterCallback callback) { + GetStartAccessLogPrinterCallbackManager().RegisterCallback(callback); + } + + void OutputAccessLog(Result result, fs::Priority priority, os::Tick start, os::Tick end, const char *name, const void *handle, const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + OutputAccessLog(result, fs::impl::IdString().ToString(priority), start, end, name, handle, fmt, vl); + va_end(vl); + } + + void OutputAccessLog(Result result, fs::PriorityRaw priority_raw, os::Tick start, os::Tick end, const char *name, const void *handle, const char *fmt, ...){ + std::va_list vl; + va_start(vl, fmt); + OutputAccessLog(result, fs::impl::IdString().ToString(priority_raw), start, end, name, handle, fmt, vl); + va_end(vl); + } + + void OutputAccessLog(Result result, os::Tick start, os::Tick end, const char *name, fs::FileHandle handle, const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + fs::impl::IdString id_string; + OutputAccessLog(result, GetPriorityRawName(id_string), start, end, name, handle.handle, fmt, vl); + va_end(vl); + } + + void OutputAccessLog(Result result, os::Tick start, os::Tick end, const char *name, fs::DirectoryHandle handle, const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + fs::impl::IdString id_string; + OutputAccessLog(result, GetPriorityRawName(id_string), start, end, name, handle.handle, fmt, vl); + va_end(vl); + } + + void OutputAccessLog(Result result, os::Tick start, os::Tick end, const char *name, fs::impl::IdentifyAccessLogHandle handle, const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + fs::impl::IdString id_string; + OutputAccessLog(result, GetPriorityRawName(id_string), start, end, name, handle.handle, fmt, vl); + va_end(vl); + } + + void OutputAccessLog(Result result, os::Tick start, os::Tick end, const char *name, const void *handle, const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + fs::impl::IdString id_string; + OutputAccessLog(result, GetPriorityRawName(id_string), start, end, name, handle, fmt, vl); + va_end(vl); + } + + void OutputAccessLogToOnlySdCard(const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + OutputAccessLogToSdCard(fmt, vl); + va_end(vl); + } + + void OutputAccessLogUnlessResultSuccess(Result result, os::Tick start, os::Tick end, const char *name, fs::FileHandle handle, const char *fmt, ...) { + if (R_FAILED(result)) { + std::va_list vl; + va_start(vl, fmt); + fs::impl::IdString id_string; + OutputAccessLog(result, GetPriorityRawName(id_string), start, end, name, handle.handle, fmt, vl); + va_end(vl); + } + } + + void OutputAccessLogUnlessResultSuccess(Result result, os::Tick start, os::Tick end, const char *name, fs::DirectoryHandle handle, const char *fmt, ...) { + if (R_FAILED(result)) { + std::va_list vl; + va_start(vl, fmt); + fs::impl::IdString id_string; + OutputAccessLog(result, GetPriorityRawName(id_string), start, end, name, handle.handle, fmt, vl); + va_end(vl); + } + } + + void OutputAccessLogUnlessResultSuccess(Result result, os::Tick start, os::Tick end, const char *name, const void *handle, const char *fmt, ...) { + if (R_FAILED(result)) { + std::va_list vl; + va_start(vl, fmt); + fs::impl::IdString id_string; + OutputAccessLog(result, GetPriorityRawName(id_string), start, end, name, handle, fmt, vl); + va_end(vl); + } + } + + bool IsEnabledHandleAccessLog(fs::FileHandle handle) { + /* Get the file accessor. */ + impl::FileAccessor *accessor = reinterpret_cast<impl::FileAccessor *>(handle.handle); + if (accessor == nullptr) { + return true; + } + + /* Check the parent. */ + if (auto *parent = accessor->GetParent(); parent != nullptr) { + return parent->IsEnabledAccessLog(); + } else { + return false; + } + } + + bool IsEnabledHandleAccessLog(fs::DirectoryHandle handle) { + /* Get the file accessor. */ + impl::DirectoryAccessor *accessor = reinterpret_cast<impl::DirectoryAccessor *>(handle.handle); + if (accessor == nullptr) { + return true; + } + + /* Check the parent. */ + if (auto *parent = accessor->GetParent(); parent != nullptr) { + return parent->IsEnabledAccessLog(); + } else { + return false; + } + } + + bool IsEnabledHandleAccessLog(fs::impl::IdentifyAccessLogHandle handle) { + AMS_UNUSED(handle); + return true; + } + + bool IsEnabledHandleAccessLog(const void *handle) { + if (handle == nullptr) { + return true; + } + + /* We should never receive non-null here. */ + AMS_ASSERT(handle == nullptr); + return false; + } + + bool IsEnabledFileSystemAccessorAccessLog(const char *mount_name) { + /* Get the accessor. */ + impl::FileSystemAccessor *accessor = nullptr; + if (R_FAILED(impl::Find(std::addressof(accessor), mount_name))) { + return true; + } + + return accessor->IsEnabledAccessLog(); + } + + void EnableFileSystemAccessorAccessLog(const char *mount_name) { + /* Get the accessor. */ + impl::FileSystemAccessor *accessor = nullptr; + AMS_FS_R_ABORT_UNLESS(impl::Find(std::addressof(accessor), mount_name)); + accessor->SetAccessLogEnabled(true); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_api.cpp new file mode 100644 index 00000000..c548fc67 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_api.cpp @@ -0,0 +1,195 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fs_remote_file_system_proxy.hpp" +#include "fs_remote_file_system_proxy_for_loader.hpp" +#include "impl/fs_library.hpp" + +namespace ams::fs { + + #if defined(ATMOSPHERE_OS_HORIZON) + namespace { + + alignas(0x10) constinit std::byte g_fsp_service_object_buffer[0x80] = {}; + alignas(0x10) constinit std::byte g_fsp_ldr_service_object_buffer[0x80] = {}; + constinit bool g_use_static_fsp_service_object_buffer = false; + constinit bool g_use_static_fsp_ldr_service_object_buffer = false; + + class HipcClientAllocator { + public: + using Policy = sf::StatelessAllocationPolicy<HipcClientAllocator>; + public: + constexpr HipcClientAllocator() = default; + + void *Allocate(size_t size) { + if (g_use_static_fsp_service_object_buffer) { + return std::addressof(g_fsp_service_object_buffer); + } else if (g_use_static_fsp_ldr_service_object_buffer) { + return std::addressof(g_fsp_ldr_service_object_buffer); + } else { + return ::ams::fs::impl::Allocate(size); + } + } + + void Deallocate(void *ptr, size_t size) { + if (ptr == std::addressof(g_fsp_service_object_buffer)) { + return; + } else if (ptr == std::addressof(g_fsp_ldr_service_object_buffer)) { + return; + } else { + return ::ams::fs::impl::Deallocate(ptr, size); + } + } + }; + + enum class FileSystemProxySessionSetting { + SystemNormal = 0, + Application = 1, + SystemMulti = 2, + }; + + constexpr ALWAYS_INLINE int GetSessionCount(FileSystemProxySessionSetting setting) { + switch (setting) { + case FileSystemProxySessionSetting::Application: return 3; + case FileSystemProxySessionSetting::SystemNormal: return 1; + case FileSystemProxySessionSetting::SystemMulti: return 2; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + constinit bool g_is_fsp_object_initialized = false; + constinit FileSystemProxySessionSetting g_fsp_session_setting = FileSystemProxySessionSetting::SystemNormal; + + /* TODO: SessionResourceManager */ + + } + #endif + + namespace { + + constinit sf::SharedPointer<fssrv::sf::IFileSystemProxy> g_fsp_service_object; + + sf::SharedPointer<fssrv::sf::IFileSystemProxy> GetFileSystemProxyServiceObjectImpl() { + /* Ensure the library is initialized. */ + ::ams::fs::impl::InitializeFileSystemLibrary(); + + sf::SharedPointer<fssrv::sf::IFileSystemProxy> fsp_object; + + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) && defined(ATMOSPHERE_OS_HORIZON) + /* Try to use the custom object. */ + fsp_object = g_fsp_service_object; + + /* If we don't have one, create a remote object. */ + if (fsp_object == nullptr) { + /* Make our next allocation use our static reserved buffer for the service object. */ + g_use_static_fsp_service_object_buffer = true; + ON_SCOPE_EXIT { g_use_static_fsp_service_object_buffer = false; }; + + using ObjectFactory = sf::ObjectFactory<HipcClientAllocator::Policy>; + + /* Create the object. */ + fsp_object = ObjectFactory::CreateSharedEmplaced<fssrv::sf::IFileSystemProxy, RemoteFileSystemProxy>(GetSessionCount(g_fsp_session_setting)); + AMS_ABORT_UNLESS(fsp_object != nullptr); + + /* Set the current process. */ + fsp_object->SetCurrentProcess({}); + } + #else + /* On non-horizon, use the system object. */ + fsp_object = fssrv::impl::GetFileSystemProxyServiceObject(); + AMS_ABORT_UNLESS(fsp_object != nullptr); + + /* Set the current process. */ + fsp_object->SetCurrentProcess({}); + #endif + + + /* Return the object. */ + return fsp_object; + } + + sf::SharedPointer<fssrv::sf::IFileSystemProxyForLoader> GetFileSystemProxyForLoaderServiceObjectImpl() { + /* Ensure the library is initialized. */ + ::ams::fs::impl::InitializeFileSystemLibrary(); + + sf::SharedPointer<fssrv::sf::IFileSystemProxyForLoader> fsp_ldr_object; + + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) && defined(ATMOSPHERE_OS_HORIZON) + /* Make our next allocation use our static reserved buffer for the service object. */ + g_use_static_fsp_ldr_service_object_buffer = true; + ON_SCOPE_EXIT { g_use_static_fsp_ldr_service_object_buffer = false; }; + + using ObjectFactory = sf::ObjectFactory<HipcClientAllocator::Policy>; + + /* Create the object. */ + fsp_ldr_object = ObjectFactory::CreateSharedEmplaced<fssrv::sf::IFileSystemProxyForLoader, RemoteFileSystemProxyForLoader>(); + AMS_ABORT_UNLESS(fsp_ldr_object != nullptr); + #else + /* On non-horizon, use the system object. */ + fsp_ldr_object = fssrv::impl::GetFileSystemProxyForLoaderServiceObject(); + AMS_ABORT_UNLESS(fsp_ldr_object != nullptr); + #endif + + /* Return the object. */ + return fsp_ldr_object; + } + + } + + namespace impl { + + sf::SharedPointer<fssrv::sf::IFileSystemProxy> GetFileSystemProxyServiceObject() { + AMS_FUNCTION_LOCAL_STATIC(sf::SharedPointer<fssrv::sf::IFileSystemProxy>, s_fsp_service_object, GetFileSystemProxyServiceObjectImpl()); + return s_fsp_service_object; + } + + sf::SharedPointer<fssrv::sf::IFileSystemProxyForLoader> GetFileSystemProxyForLoaderServiceObject() { + AMS_FUNCTION_LOCAL_STATIC(sf::SharedPointer<fssrv::sf::IFileSystemProxyForLoader>, s_fsp_ldr_service_object, GetFileSystemProxyForLoaderServiceObjectImpl()); + return s_fsp_ldr_service_object; + } + + } + + void InitializeForHostTool() { + #if !defined(ATMOSPHERE_OS_HORIZON) + AMS_ABORT_UNLESS(impl::GetFileSystemProxyServiceObject() != nullptr); + R_ABORT_UNLESS(::ams::fs::MountHostRoot()); + #endif + } + + void InitializeForSystem() { + #if defined(ATMOSPHERE_OS_HORIZON) + AMS_ABORT_UNLESS(!g_is_fsp_object_initialized); + AMS_ABORT_UNLESS(g_fsp_session_setting == FileSystemProxySessionSetting::SystemNormal); + g_fsp_session_setting = FileSystemProxySessionSetting::SystemNormal; + + /* Nintendo doesn't do this, but we have to for timing reasons. */ + AMS_ABORT_UNLESS(impl::GetFileSystemProxyServiceObject() != nullptr); + #endif + } + + void InitializeWithMultiSessionForSystem() { + #if defined(ATMOSPHERE_OS_HORIZON) + AMS_ABORT_UNLESS(!g_is_fsp_object_initialized); + AMS_ABORT_UNLESS(g_fsp_session_setting == FileSystemProxySessionSetting::SystemNormal); + g_fsp_session_setting = FileSystemProxySessionSetting::SystemMulti; + + /* Nintendo doesn't do this, but we have to for timing reasons. */ + AMS_ABORT_UNLESS(impl::GetFileSystemProxyServiceObject() != nullptr); + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_application.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_application.cpp new file mode 100644 index 00000000..7aadc540 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_application.cpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsa/fs_mount_utils.hpp" +#include "impl/fs_file_system_service_object_adapter.hpp" +#include "impl/fs_file_system_proxy_service_object.hpp" + +namespace ams::fs { + + Result MountApplicationPackage(const char *name, const char *common_path) { + auto mount_impl = [=]() -> Result { + /* Validate the mount name. */ + R_TRY(impl::CheckMountName(name)); + + /* Validate the path. */ + R_UNLESS(common_path != nullptr, fs::ResultInvalidPath()); + + /* Convert the path for ipc. */ + fssrv::sf::FspPath sf_path; + R_TRY(fs::ConvertToFspPath(std::addressof(sf_path), common_path)); + + /* Open the filesystem. */ + auto fsp = impl::GetFileSystemProxyServiceObject(); + sf::SharedPointer<fssrv::sf::IFileSystem> fs; + R_TRY(fsp->OpenFileSystemWithId(std::addressof(fs), sf_path, fs::ContentAttributes_None, ncm::InvalidProgramId.value, impl::FileSystemProxyType_Package)); + + /* Allocate a new filesystem wrapper. */ + auto fsa = std::make_unique<impl::FileSystemServiceObjectAdapter>(std::move(fs)); + R_UNLESS(fsa != nullptr, fs::ResultAllocationMemoryFailedInApplicationA()); + + /* Register. */ + R_RETURN(fsa::Register(name, std::move(fsa))); + }; + + /* Perform the mount. */ + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM_MOUNT(mount_impl(), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_APPLICATION_PACKAGE(name, common_path))); + + /* Enable access logging. */ + AMS_FS_IMPL_ACCESS_LOG_SYSTEM_FS_ACCESSOR_ENABLE(name); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_bis.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_bis.cpp new file mode 100644 index 00000000..8aba6fff --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_bis.cpp @@ -0,0 +1,136 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsa/fs_mount_utils.hpp" +#include "impl/fs_file_system_service_object_adapter.hpp" +#include "impl/fs_file_system_proxy_service_object.hpp" + +namespace ams::fs { + + namespace { + + class BisCommonMountNameGenerator : public fsa::ICommonMountNameGenerator, public impl::Newable { + private: + const BisPartitionId m_id; + public: + explicit BisCommonMountNameGenerator(BisPartitionId i) : m_id(i) { /* ... */ } + + virtual Result GenerateCommonMountName(char *dst, size_t dst_size) override { + /* Determine how much space we need. */ + const char *bis_mount_name = GetBisMountName(m_id); + const size_t needed_size = util::Strnlen(bis_mount_name, MountNameLengthMax) + 2; + AMS_ABORT_UNLESS(dst_size >= needed_size); + + /* Generate the name. */ + const auto size = util::SNPrintf(dst, dst_size, "%s:", bis_mount_name); + AMS_ASSERT(static_cast<size_t>(size) == needed_size - 1); + AMS_UNUSED(size); + + R_SUCCEED(); + } + }; + + } + + namespace impl { + + Result MountBisImpl(const char *name, BisPartitionId id, const char *root_path) { + auto mount_impl = [=]() -> Result { + /* Validate the mount name. */ + R_TRY(impl::CheckMountNameAllowingReserved(name)); + + /* Convert the path for ipc. */ + /* NOTE: Nintendo ignores the root_path here. */ + fssrv::sf::FspPath sf_path; + sf_path.str[0] = '\x00'; + AMS_UNUSED(root_path); + + /* Open the filesystem. */ + auto fsp = impl::GetFileSystemProxyServiceObject(); + sf::SharedPointer<fssrv::sf::IFileSystem> fs; + R_TRY(fsp->OpenBisFileSystem(std::addressof(fs), sf_path, static_cast<u32>(id))); + + /* Allocate a new mountname generator. */ + auto generator = std::make_unique<BisCommonMountNameGenerator>(id); + R_UNLESS(generator != nullptr, fs::ResultAllocationMemoryFailedInBisA()); + + /* Allocate a new filesystem wrapper. */ + auto fsa = std::make_unique<impl::FileSystemServiceObjectAdapter>(std::move(fs)); + R_UNLESS(fsa != nullptr, fs::ResultAllocationMemoryFailedInBisB()); + + /* Register. */ + R_RETURN(fsa::Register(name, std::move(fsa), std::move(generator))); + }; + + /* Perform the mount. */ + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM_MOUNT(mount_impl(), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_BIS(name, id, root_path))); + + /* Enable access logging. */ + AMS_FS_IMPL_ACCESS_LOG_SYSTEM_FS_ACCESSOR_ENABLE(name); + + R_SUCCEED(); + } + + Result SetBisRootForHostImpl(BisPartitionId id, const char *root_path) { + AMS_UNUSED(id, root_path); + AMS_ABORT("TODO"); + } + + } + + const char *GetBisMountName(BisPartitionId id) { + switch (id) { + case BisPartitionId::CalibrationFile: return impl::BisCalibrationFilePartitionMountName; + case BisPartitionId::SafeMode: return impl::BisSafeModePartitionMountName; + case BisPartitionId::User: return impl::BisUserPartitionMountName; + case BisPartitionId::System: return impl::BisSystemPartitionMountName; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + Result MountBis(BisPartitionId id, const char *root_path) { + R_RETURN(impl::MountBisImpl(GetBisMountName(id), id, root_path)); + } + + Result MountBis(const char *name, BisPartitionId id) { + R_RETURN(impl::MountBisImpl(name, id, nullptr)); + } + + void SetBisRootForHost(BisPartitionId id, const char *root_path) { + R_ABORT_UNLESS(impl::SetBisRootForHostImpl(id, root_path)); + } + + Result OpenBisPartition(std::unique_ptr<fs::IStorage> *out, BisPartitionId id) { + /* Open the partition. */ + auto fsp = impl::GetFileSystemProxyServiceObject(); + sf::SharedPointer<fssrv::sf::IStorage> s; + AMS_FS_R_TRY(fsp->OpenBisStorage(std::addressof(s), static_cast<u32>(id))); + + /* Allocate a new storage wrapper. */ + auto storage = std::make_unique<impl::StorageServiceObjectAdapter<fssrv::sf::IStorage>>(std::move(s)); + AMS_FS_R_UNLESS(storage != nullptr, fs::ResultAllocationMemoryFailedInBisC()); + + *out = std::move(storage); + R_SUCCEED(); + } + + Result InvalidateBisCache() { + auto fsp = impl::GetFileSystemProxyServiceObject(); + AMS_FS_R_ABORT_UNLESS(fsp->InvalidateBisCache()); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_code.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_code.cpp new file mode 100644 index 00000000..e7c694e0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_code.cpp @@ -0,0 +1,486 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsa/fs_mount_utils.hpp" +#include "impl/fs_file_system_service_object_adapter.hpp" +#include "impl/fs_file_system_proxy_service_object.hpp" + +namespace ams::fs { + + namespace { + + constinit os::SdkMutex g_mount_stratosphere_romfs_lock; + constinit bool g_mounted_stratosphere_romfs = false; + + constinit util::TypedStorage<FileHandleStorage> g_stratosphere_romfs_storage = {}; + constinit util::TypedStorage<RomFsFileSystem> g_stratosphere_romfs_fs = {}; + + Result EnsureStratosphereRomfsMounted() { + std::scoped_lock lk(g_mount_stratosphere_romfs_lock); + + if (AMS_UNLIKELY(!g_mounted_stratosphere_romfs)) { + /* Mount the SD card. */ + R_TRY(fs::MountSdCard("#strat-romfs-sd")); + auto sd_guard = SCOPE_GUARD { fs::Unmount("#strat-romfs-sd"); }; + + /* Open sd:/atmosphere/stratosphere.romfs. */ + fs::FileHandle stratosphere_romfs_file; + R_TRY(fs::OpenFile(std::addressof(stratosphere_romfs_file), "#strat-romfs-sd:/atmosphere/stratosphere.romfs", fs::OpenMode_Read)); + + /* Setup the storage. */ + /* NOTE: This owns the file, and so on failure it will be closed appropriately. */ + auto storage_guard = util::ConstructAtGuarded(g_stratosphere_romfs_storage, stratosphere_romfs_file, true); + + /* Create the filesystem. */ + auto fs_guard = util::ConstructAtGuarded(g_stratosphere_romfs_fs); + + /* Initialize the filesystem. */ + R_TRY(GetReference(g_stratosphere_romfs_fs).Initialize(GetPointer(g_stratosphere_romfs_storage), nullptr, 0, false)); + + /* We succeeded, and so stratosphere.romfs is mounted. */ + fs_guard.Cancel(); + storage_guard.Cancel(); + sd_guard.Cancel(); + + g_mounted_stratosphere_romfs = true; + } + + R_SUCCEED(); + } + + fsa::IFileSystem &GetStratosphereRomFsFileSystem() { + /* Ensure that stratosphere.romfs is mounted. */ + /* NOTE: Abort is used here to ensure that atmosphere's filesystem is structurally valid. */ + R_ABORT_UNLESS(EnsureStratosphereRomfsMounted()); + + return GetReference(g_stratosphere_romfs_fs); + } + + Result OpenCodeFileSystemImpl(CodeVerificationData *out_verification_data, std::unique_ptr<fsa::IFileSystem> *out, const char *path, fs::ContentAttributes attr, ncm::ProgramId program_id, ncm::StorageId storage_id) { + /* Print a path suitable for the remote service. */ + fssrv::sf::Path sf_path; + R_TRY(FormatToFspPath(std::addressof(sf_path), "%s", path)); + + /* Open the filesystem. */ + auto fsp = impl::GetFileSystemProxyForLoaderServiceObject(); + R_TRY(fsp->SetCurrentProcess({})); + + sf::SharedPointer<fssrv::sf::IFileSystem> fs; + if (hos::GetVersion() >= hos::Version_20_0_0) { + R_TRY(fsp->OpenCodeFileSystem(std::addressof(fs), ams::sf::OutBuffer(out_verification_data, sizeof(*out_verification_data)), attr, program_id, storage_id)); + } else { + R_TRY(fsp->OpenCodeFileSystemDeprecated4(std::addressof(fs), ams::sf::OutBuffer(out_verification_data, sizeof(*out_verification_data)), sf_path, attr, program_id)); + } + + /* Allocate a new filesystem wrapper. */ + auto fsa = std::make_unique<impl::FileSystemServiceObjectAdapter>(std::move(fs)); + R_UNLESS(fsa != nullptr, fs::ResultAllocationMemoryFailedInCodeA()); + + *out = std::move(fsa); + R_SUCCEED(); + } + + Result OpenPackageFileSystemImpl(std::unique_ptr<fsa::IFileSystem> *out, const fssrv::sf::FspPath &path) { + /* Open the filesystem. */ + auto fsp = impl::GetFileSystemProxyServiceObject(); + sf::SharedPointer<fssrv::sf::IFileSystem> fs; + R_TRY(fsp->OpenFileSystemWithId(std::addressof(fs), path, fs::ContentAttributes_None, ncm::InvalidProgramId.value, impl::FileSystemProxyType_Package)); + + /* Allocate a new filesystem wrapper. */ + auto fsa = std::make_unique<impl::FileSystemServiceObjectAdapter>(std::move(fs)); + R_UNLESS(fsa != nullptr, fs::ResultAllocationMemoryFailedInCodeA()); + + *out = std::move(fsa); + R_SUCCEED(); + } + + Result OpenSdCardCodeFileSystemImpl(std::unique_ptr<fsa::IFileSystem> *out, ncm::ProgramId program_id) { + /* Ensure we don't access the SD card too early. */ + R_UNLESS(cfg::IsSdCardInitialized(), fs::ResultSdCardNotPresent()); + + /* Print a path to the program's package. */ + fssrv::sf::Path sf_path; + R_TRY(FormatToFspPath(std::addressof(sf_path), "%s:/atmosphere/contents/%016" PRIx64 "/exefs.nsp", impl::SdCardFileSystemMountName, program_id.value)); + + R_RETURN(OpenPackageFileSystemImpl(out, sf_path)); + } + + Result OpenStratosphereCodeFileSystemImpl(std::unique_ptr<fsa::IFileSystem> *out, ncm::ProgramId program_id) { + /* Ensure we don't access the SD card too early. */ + R_UNLESS(cfg::IsSdCardInitialized(), fs::ResultSdCardNotPresent()); + + /* Open the program's package. */ + std::unique_ptr<fsa::IFile> package_file; + { + /* Get the stratosphere.romfs filesystem. */ + auto &romfs_fs = GetStratosphereRomFsFileSystem(); + + /* Print a path to the program's package. */ + fs::Path path; + R_TRY(path.InitializeWithFormat("/atmosphere/contents/%016" PRIx64 "/exefs.nsp", program_id.value)); + R_TRY(path.Normalize(fs::PathFlags{})); + + /* Open the package within stratosphere.romfs. */ + R_TRY(romfs_fs.OpenFile(std::addressof(package_file), path, fs::OpenMode_Read)); + } + + /* Create a file storage for the program's package. */ + auto package_storage = fs::AllocateShared<FileStorage>(std::move(package_file)); + R_UNLESS(package_storage != nullptr, fs::ResultAllocationMemoryFailedInCodeA()); + + /* Create a partition filesystem. */ + auto package_fs = std::make_unique<fssystem::PartitionFileSystem>(); + R_UNLESS(package_fs != nullptr, fs::ResultAllocationMemoryFailedInCodeA()); + + /* Initialize the partition filesystem. */ + R_TRY(package_fs->Initialize(package_storage)); + + *out = std::move(package_fs); + R_SUCCEED(); + } + + Result OpenSdCardCodeOrStratosphereCodeOrCodeFileSystemImpl(CodeVerificationData *out_verification_data, std::unique_ptr<fsa::IFileSystem> *out, const char *path, fs::ContentAttributes attr, ncm::ProgramId program_id, ncm::StorageId storage_id) { + /* If we can open an sd card code fs, use it. */ + R_SUCCEED_IF(R_SUCCEEDED(OpenSdCardCodeFileSystemImpl(out, program_id))); + + /* If we can open a stratosphere code fs, use it. */ + R_SUCCEED_IF(R_SUCCEEDED(OpenStratosphereCodeFileSystemImpl(out, program_id))); + + /* Otherwise, fall back to a normal code fs. */ + R_RETURN(OpenCodeFileSystemImpl(out_verification_data, out, path, attr, program_id, storage_id)); + } + + Result OpenHblCodeFileSystemImpl(std::unique_ptr<fsa::IFileSystem> *out) { + /* Get the HBL path. */ + const char *hbl_path = cfg::GetHblPath(); + + /* Print a path to the hbl package. */ + fssrv::sf::Path sf_path; + R_TRY(FormatToFspPath(std::addressof(sf_path), "%s:/%s", impl::SdCardFileSystemMountName, hbl_path[0] == '/' ? hbl_path + 1 : hbl_path)); + + R_RETURN(OpenPackageFileSystemImpl(out, sf_path)); + } + + Result OpenSdCardFileSystemImpl(std::shared_ptr<fsa::IFileSystem> *out) { + /* Open the SD card filesystem. */ + auto fsp = impl::GetFileSystemProxyServiceObject(); + sf::SharedPointer<fssrv::sf::IFileSystem> fs; + R_TRY(fsp->OpenSdCardFileSystem(std::addressof(fs))); + + /* Allocate a new filesystem wrapper. */ + auto fsa = fs::AllocateShared<impl::FileSystemServiceObjectAdapter>(std::move(fs)); + R_UNLESS(fsa != nullptr, fs::ResultAllocationMemoryFailedInCodeA()); + + *out = std::move(fsa); + R_SUCCEED(); + } + + class OpenFileOnlyFileSystem : public fsa::IFileSystem, public impl::Newable { + private: + virtual Result DoCommit() override final { + R_SUCCEED(); + } + + virtual Result DoOpenDirectory(std::unique_ptr<fsa::IDirectory> *out_dir, const fs::Path &path, OpenDirectoryMode mode) override final { + AMS_UNUSED(out_dir, path, mode); + R_THROW(fs::ResultUnsupportedOperation()); + } + + virtual Result DoGetEntryType(DirectoryEntryType *out, const fs::Path &path) override final { + AMS_UNUSED(out, path); + R_THROW(fs::ResultUnsupportedOperation()); + } + + virtual Result DoCreateFile(const fs::Path &path, s64 size, int flags) override final { + AMS_UNUSED(path, size, flags); + R_THROW(fs::ResultUnsupportedOperation()); + } + + virtual Result DoDeleteFile(const fs::Path &path) override final { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedOperation()); + } + + virtual Result DoCreateDirectory(const fs::Path &path) override final { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedOperation()); + } + + virtual Result DoDeleteDirectory(const fs::Path &path) override final { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedOperation()); + } + + virtual Result DoDeleteDirectoryRecursively(const fs::Path &path) override final { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedOperation()); + } + + virtual Result DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) override final { + AMS_UNUSED(old_path, new_path); + R_THROW(fs::ResultUnsupportedOperation()); + } + + virtual Result DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) override final { + AMS_UNUSED(old_path, new_path); + R_THROW(fs::ResultUnsupportedOperation()); + } + + virtual Result DoCleanDirectoryRecursively(const fs::Path &path) override final { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedOperation()); + } + + virtual Result DoGetFreeSpaceSize(s64 *out, const fs::Path &path) override final { + AMS_UNUSED(out, path); + R_THROW(fs::ResultUnsupportedOperation()); + } + + virtual Result DoGetTotalSpaceSize(s64 *out, const fs::Path &path) override final { + AMS_UNUSED(out, path); + R_THROW(fs::ResultUnsupportedOperation()); + } + + virtual Result DoCommitProvisionally(s64 counter) override final { + AMS_UNUSED(counter); + R_THROW(fs::ResultUnsupportedOperation()); + } + }; + + class SdCardRedirectionCodeFileSystem : public OpenFileOnlyFileSystem { + private: + util::optional<ReadOnlyFileSystem> m_sd_content_fs; + ReadOnlyFileSystem m_code_fs; + bool m_is_redirect; + public: + SdCardRedirectionCodeFileSystem(std::unique_ptr<fsa::IFileSystem> &&code, ncm::ProgramId program_id, bool redirect) : m_code_fs(std::move(code)), m_is_redirect(redirect) { + if (!cfg::IsSdCardInitialized()) { + return; + } + + /* Open an SD card filesystem. */ + std::shared_ptr<fsa::IFileSystem> sd_fs; + if (R_FAILED(OpenSdCardFileSystemImpl(std::addressof(sd_fs)))) { + return; + } + + /* Create a redirection filesystem to the relevant content folder. */ + auto subdir_fs = std::make_unique<fssystem::SubDirectoryFileSystem>(std::move(sd_fs)); + if (subdir_fs == nullptr) { + return; + } + + fs::Path path; + R_ABORT_UNLESS(path.InitializeWithFormat("/atmosphere/contents/%016" PRIx64 "/exefs", program_id.value)); + R_ABORT_UNLESS(path.Normalize(fs::PathFlags{})); + + R_ABORT_UNLESS(subdir_fs->Initialize(path)); + + m_sd_content_fs.emplace(std::move(subdir_fs)); + } + private: + bool IsFileStubbed(const fs::Path &path) { + /* If we don't have an sd content fs, nothing is stubbed. */ + if (!m_sd_content_fs) { + return false; + } + + /* Create a path representing the stub. */ + fs::Path stub_path; + if (R_FAILED(stub_path.InitializeWithFormat("%s.stub", path.GetString()))) { + return false; + } + if (R_FAILED(stub_path.Normalize(fs::PathFlags{}))) { + return false; + } + + /* Query whether we have the file. */ + bool has_file; + if (R_FAILED(fssystem::HasFile(std::addressof(has_file), std::addressof(*m_sd_content_fs), stub_path))) { + return false; + } + + return has_file; + } + + virtual Result DoOpenFile(std::unique_ptr<fsa::IFile> *out_file, const fs::Path &path, OpenMode mode) override final { + /* Only allow opening files with mode = read. */ + R_UNLESS((mode & fs::OpenMode_All) == fs::OpenMode_Read, fs::ResultInvalidOpenMode()); + + /* If we support redirection, we'd like to prefer a file from the sd card. */ + if (m_is_redirect) { + R_SUCCEED_IF(R_SUCCEEDED(m_sd_content_fs->OpenFile(out_file, path, mode))); + } + + /* Otherwise, check if the file is stubbed. */ + R_UNLESS(!this->IsFileStubbed(path), fs::ResultPathNotFound()); + + /* Open a file from the base code fs. */ + R_RETURN(m_code_fs.OpenFile(out_file, path, mode)); + } + }; + + class AtmosphereCodeFileSystem : public OpenFileOnlyFileSystem { + private: + util::optional<SdCardRedirectionCodeFileSystem> m_code_fs; + util::optional<ReadOnlyFileSystem> m_hbl_fs; + ncm::ProgramId m_program_id; + ncm::StorageId m_storage_id; + bool m_initialized; + public: + AtmosphereCodeFileSystem() : m_initialized(false) { /* ... */ } + + Result Initialize(CodeVerificationData *out_verification_data, const char *path, fs::ContentAttributes attr, ncm::ProgramId program_id, ncm::StorageId storage_id, bool is_hbl, bool is_specific) { + AMS_ABORT_UNLESS(!m_initialized); + + /* If we're hbl, we need to open a hbl fs. */ + if (is_hbl) { + std::unique_ptr<fsa::IFileSystem> fsa; + R_TRY(OpenHblCodeFileSystemImpl(std::addressof(fsa))); + m_hbl_fs.emplace(std::move(fsa)); + } + + /* Open the code filesystem. */ + std::unique_ptr<fsa::IFileSystem> fsa; + R_TRY(OpenSdCardCodeOrStratosphereCodeOrCodeFileSystemImpl(out_verification_data, std::addressof(fsa), path, attr, program_id, storage_id)); + m_code_fs.emplace(std::move(fsa), program_id, is_specific); + + m_program_id = program_id; + m_storage_id = storage_id; + m_initialized = true; + + R_SUCCEED(); + } + private: + virtual Result DoOpenFile(std::unique_ptr<fsa::IFile> *out_file, const fs::Path &path, OpenMode mode) override final { + /* Ensure that we're initialized. */ + R_UNLESS(m_initialized, fs::ResultNotInitialized()); + + /* Only allow opening files with mode = read. */ + R_UNLESS((mode & fs::OpenMode_All) == fs::OpenMode_Read, fs::ResultInvalidOpenMode()); + + /* First, check if there's an external code. */ + { + fsa::IFileSystem *ecs = fssystem::GetExternalCodeFileSystem(m_program_id); + if (ecs != nullptr) { + R_RETURN(ecs->OpenFile(out_file, path, mode)); + } + } + + /* If we're hbl, open from the hbl fs. */ + if (m_hbl_fs) { + R_RETURN(m_hbl_fs->OpenFile(out_file, path, mode)); + } + + /* If we're not hbl, fall back to our code filesystem. */ + R_RETURN(m_code_fs->OpenFile(out_file, path, mode)); + } + }; + + } + + Result MountCode(CodeVerificationData *out, const char *name, const char *path, fs::ContentAttributes attr, ncm::ProgramId program_id, ncm::StorageId storage_id) { + auto mount_impl = [=]() -> Result { + /* Clear the output. */ + std::memset(out, 0, sizeof(*out)); + + /* Validate the mount name. */ + R_TRY(impl::CheckMountName(name)); + + /* Validate the path isn't null. */ + R_UNLESS(path != nullptr, fs::ResultInvalidPath()); + + /* Open the code file system. */ + std::unique_ptr<fsa::IFileSystem> fsa; + R_TRY(OpenCodeFileSystemImpl(out, std::addressof(fsa), path, attr, program_id, storage_id)); + + /* Register. */ + R_RETURN(fsa::Register(name, std::move(fsa))); + }; + + /* Perform the mount. */ + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM_MOUNT(mount_impl(), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_CODE(name, path, program_id))); + + /* Enable access logging. */ + AMS_FS_IMPL_ACCESS_LOG_SYSTEM_FS_ACCESSOR_ENABLE(name); + + R_SUCCEED(); + } + + Result MountCodeForAtmosphereWithRedirection(CodeVerificationData *out, const char *name, const char *path, fs::ContentAttributes attr, ncm::ProgramId program_id, ncm::StorageId storage_id, bool is_hbl, bool is_specific) { + auto mount_impl = [=]() -> Result { + /* Clear the output. */ + std::memset(out, 0, sizeof(*out)); + + /* Validate the mount name. */ + R_TRY(impl::CheckMountName(name)); + + /* Validate the path isn't null. */ + R_UNLESS(path != nullptr, fs::ResultInvalidPath()); + + /* Create an AtmosphereCodeFileSystem. */ + auto ams_code_fs = std::make_unique<AtmosphereCodeFileSystem>(); + R_UNLESS(ams_code_fs != nullptr, fs::ResultAllocationMemoryFailedInCodeA()); + + /* Initialize the code file system. */ + R_TRY(ams_code_fs->Initialize(out, path, attr, program_id, storage_id, is_hbl, is_specific)); + + /* Register. */ + R_RETURN(fsa::Register(name, std::move(ams_code_fs))); + }; + + /* Perform the mount. */ + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM_MOUNT(mount_impl(), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_CODE(name, path, program_id))); + + /* Enable access logging. */ + AMS_FS_IMPL_ACCESS_LOG_SYSTEM_FS_ACCESSOR_ENABLE(name); + + R_SUCCEED(); + } + + Result MountCodeForAtmosphere(CodeVerificationData *out, const char *name, const char *path, fs::ContentAttributes attr, ncm::ProgramId program_id, ncm::StorageId storage_id) { + auto mount_impl = [=]() -> Result { + /* Clear the output. */ + std::memset(out, 0, sizeof(*out)); + + /* Validate the mount name. */ + R_TRY(impl::CheckMountName(name)); + + /* Validate the path isn't null. */ + R_UNLESS(path != nullptr, fs::ResultInvalidPath()); + + /* Open the code file system. */ + std::unique_ptr<fsa::IFileSystem> fsa; + R_TRY(OpenSdCardCodeOrStratosphereCodeOrCodeFileSystemImpl(out, std::addressof(fsa), path, attr, program_id, storage_id)); + + /* Create a wrapper fs. */ + auto wrap_fsa = std::make_unique<SdCardRedirectionCodeFileSystem>(std::move(fsa), program_id, false); + R_UNLESS(wrap_fsa != nullptr, fs::ResultAllocationMemoryFailedInCodeA()); + + /* Register. */ + R_RETURN(fsa::Register(name, std::move(wrap_fsa))); + }; + + /* Perform the mount. */ + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM_MOUNT(mount_impl(), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_CODE(name, path, program_id))); + + /* Enable access logging. */ + AMS_FS_IMPL_ACCESS_LOG_SYSTEM_FS_ACCESSOR_ENABLE(name); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_content.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_content.cpp new file mode 100644 index 00000000..059edb64 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_content.cpp @@ -0,0 +1,106 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsa/fs_mount_utils.hpp" +#include "impl/fs_file_system_proxy_service_object.hpp" +#include "impl/fs_file_system_service_object_adapter.hpp" + +namespace ams::fs { + + namespace { + + impl::FileSystemProxyType ConvertToFileSystemProxyType(ContentType content_type) { + switch (content_type) { + case ContentType_Control: return impl::FileSystemProxyType_Control; + case ContentType_Manual: return impl::FileSystemProxyType_Manual; + case ContentType_Meta: return impl::FileSystemProxyType_Meta; + case ContentType_Data: return impl::FileSystemProxyType_Data; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + Result MountContentImpl(const char *name, const char *path, fs::ContentAttributes attr, u64 id, ContentType type) { + /* Validate the mount name. */ + R_TRY(impl::CheckMountNameAllowingReserved(name)); + + /* Validate the path. */ + R_UNLESS(path != nullptr, fs::ResultInvalidPath()); + + /* Convert the path for fsp. */ + fssrv::sf::FspPath sf_path; + R_TRY(fs::ConvertToFspPath(std::addressof(sf_path), path)); + + /* Open the filesystem. */ + auto fsp = impl::GetFileSystemProxyServiceObject(); + sf::SharedPointer<fssrv::sf::IFileSystem> fs; + R_TRY(fsp->OpenFileSystemWithId(std::addressof(fs), sf_path, attr, id, ConvertToFileSystemProxyType(type))); + + /* Allocate a new filesystem wrapper. */ + auto fsa = std::make_unique<impl::FileSystemServiceObjectAdapter>(std::move(fs)); + R_UNLESS(fsa != nullptr, fs::ResultAllocationMemoryFailedInContentA()); + + /* Register. */ + R_RETURN(fsa::Register(name, std::move(fsa))); + } + + } + + Result MountContent(const char *name, const char *path, fs::ContentAttributes attr, ContentType content_type) { + auto mount_impl = [=]() -> Result { + /* This API only supports mounting Meta content. */ + R_UNLESS(content_type == ContentType_Meta, fs::ResultInvalidArgument()); + + R_RETURN(MountContentImpl(name, path, attr, ncm::InvalidProgramId.value, content_type)); + }; + + /* Perform the mount. */ + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM_MOUNT(mount_impl(), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_CONTENT_PATH(name, path, content_type))); + + /* Enable access logging. */ + AMS_FS_IMPL_ACCESS_LOG_SYSTEM_FS_ACCESSOR_ENABLE(name); + + R_SUCCEED(); + } + + Result MountContent(const char *name, const char *path, fs::ContentAttributes attr, ncm::ProgramId id, ContentType content_type) { + auto mount_impl = [=]() -> Result { + R_RETURN(MountContentImpl(name, path, attr, id.value, content_type)); + }; + + /* Perform the mount. */ + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM_MOUNT(mount_impl(), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_CONTENT_PATH_AND_PROGRAM_ID(name, path, id, content_type))); + + /* Enable access logging. */ + AMS_FS_IMPL_ACCESS_LOG_SYSTEM_FS_ACCESSOR_ENABLE(name); + + R_SUCCEED(); + } + + Result MountContent(const char *name, const char *path, fs::ContentAttributes attr, ncm::DataId id, ContentType content_type) { + auto mount_impl = [=]() -> Result { + R_RETURN(MountContentImpl(name, path, attr, id.value, content_type)); + }; + + /* Perform the mount. */ + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM_MOUNT(mount_impl(), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_CONTENT_PATH_AND_DATA_ID(name, path, id, content_type))); + + /* Enable access logging. */ + AMS_FS_IMPL_ACCESS_LOG_SYSTEM_FS_ACCESSOR_ENABLE(name); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_content_storage.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_content_storage.cpp new file mode 100644 index 00000000..de6f2e60 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_content_storage.cpp @@ -0,0 +1,112 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsa/fs_mount_utils.hpp" +#include "impl/fs_file_system_service_object_adapter.hpp" +#include "impl/fs_file_system_proxy_service_object.hpp" + +namespace ams::fs { + + namespace { + + class ContentStorageCommonMountNameGenerator : public fsa::ICommonMountNameGenerator, public impl::Newable { + private: + const ContentStorageId id; + public: + explicit ContentStorageCommonMountNameGenerator(ContentStorageId i) : id(i) { /* ... */ } + + virtual Result GenerateCommonMountName(char *dst, size_t dst_size) override { + /* Determine how much space we need. */ + const size_t needed_size = util::Strnlen(GetContentStorageMountName(id), MountNameLengthMax) + 2; + AMS_ABORT_UNLESS(dst_size >= needed_size); + + /* Generate the name. */ + const auto size = util::SNPrintf(dst, dst_size, "%s:", GetContentStorageMountName(id)); + AMS_ASSERT(static_cast<size_t>(size) == needed_size - 1); + AMS_UNUSED(size); + + R_SUCCEED(); + } + }; + + } + + const char *GetContentStorageMountName(ContentStorageId id) { + switch (id) { + case ContentStorageId::System: return impl::ContentStorageSystemMountName; + case ContentStorageId::User: return impl::ContentStorageUserMountName; + case ContentStorageId::SdCard: return impl::ContentStorageSdCardMountName; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + Result MountContentStorage(ContentStorageId id) { + R_RETURN(MountContentStorage(GetContentStorageMountName(id), id)); + } + + Result MountContentStorage(const char *name, ContentStorageId id) { + auto mount_impl = [=]() -> Result { + /* Validate the mount name. */ + R_TRY(impl::CheckMountNameAllowingReserved(name)); + + /* It can take some time for the system partition to be ready (if it's on the SD card). */ + /* Thus, we will retry up to 10 times, waiting one second each time. */ + constexpr size_t MaxRetries = 10; + constexpr auto RetryInterval = TimeSpan::FromSeconds(1); + + /* Mount the content storage */ + auto fsp = impl::GetFileSystemProxyServiceObject(); + + sf::SharedPointer<fssrv::sf::IFileSystem> fs; + for (size_t i = 0; i < MaxRetries; i++) { + /* Try to open the filesystem. */ + R_TRY_CATCH(fsp->OpenContentStorageFileSystem(std::addressof(fs), static_cast<u32>(id))) { + R_CATCH(fs::ResultSystemPartitionNotReady) { + if (i < MaxRetries - 1) { + os::SleepThread(RetryInterval); + continue; + } else { + R_THROW(fs::ResultSystemPartitionNotReady()); + } + } + } R_END_TRY_CATCH; + + /* The filesystem was opened successfully. */ + break; + } + + /* Allocate a new filesystem wrapper. */ + auto fsa = std::make_unique<impl::FileSystemServiceObjectAdapter>(std::move(fs)); + R_UNLESS(fsa != nullptr, fs::ResultAllocationMemoryFailedInContentStorageA()); + + /* Allocate a new mountname generator. */ + auto generator = std::make_unique<ContentStorageCommonMountNameGenerator>(id); + R_UNLESS(generator != nullptr, fs::ResultAllocationMemoryFailedInContentStorageB()); + + /* Register. */ + R_RETURN(fsa::Register(name, std::move(fsa), std::move(generator))); + }; + + /* Perform the mount. */ + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM_MOUNT(mount_impl(), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_CONTENT_STORAGE(name, id))); + + /* Enable access logging. */ + AMS_FS_IMPL_ACCESS_LOG_SYSTEM_FS_ACCESSOR_ENABLE(name); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_context.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_context.cpp new file mode 100644 index 00000000..3cc28ab8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_context.cpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fs { + + namespace { + + constinit bool g_auto_abort_enabled = true; + + /* NOTE: This generates a global constructor. */ + os::SdkThreadLocalStorage g_context_tls; + + } + + void SetEnabledAutoAbort(bool enabled) { + g_auto_abort_enabled = enabled; + } + + AbortSpecifier DefaultResultHandler(Result result) { + AMS_UNUSED(result); + if (g_auto_abort_enabled) { + return AbortSpecifier::Default; + } else { + return AbortSpecifier::Return; + } + } + + AbortSpecifier AlwaysReturnResultHandler(Result result) { + AMS_UNUSED(result); + return AbortSpecifier::Return; + } + + constinit FsContext g_default_context(DefaultResultHandler); + constinit FsContext g_always_return_context(AlwaysReturnResultHandler); + + void SetDefaultFsContextResultHandler(const ResultHandler handler) { + if (handler == nullptr) { + g_default_context.SetHandler(DefaultResultHandler); + } else { + g_default_context.SetHandler(handler); + } + } + + const FsContext *GetCurrentThreadFsContext() { + const FsContext *context = reinterpret_cast<const FsContext *>(g_context_tls.GetValue()); + + if (context == nullptr) { + context = std::addressof(g_default_context); + } + + return context; + } + + void SetCurrentThreadFsContext(const FsContext *context) { + g_context_tls.SetValue(reinterpret_cast<uintptr_t>(context)); + } + + ScopedAutoAbortDisabler::ScopedAutoAbortDisabler() : m_prev_context(GetCurrentThreadFsContext()) { + SetCurrentThreadFsContext(std::addressof(g_always_return_context)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_data.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_data.cpp new file mode 100644 index 00000000..74218d88 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_data.cpp @@ -0,0 +1,96 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsa/fs_mount_utils.hpp" +#include "impl/fs_file_system_proxy_service_object.hpp" + +namespace ams::fs::impl { + + namespace { + + Result OpenDataStorageByDataIdImpl(sf::SharedPointer<fssrv::sf::IStorage> *out, ncm::DataId data_id, ncm::StorageId storage_id) { + auto fsp = impl::GetFileSystemProxyServiceObject(); + R_TRY_CATCH(fsp->OpenDataStorageByDataId(out, data_id, static_cast<u8>(storage_id))) { + R_CONVERT(ncm::ResultContentMetaNotFound, fs::ResultTargetNotFound()); + } R_END_TRY_CATCH; + R_SUCCEED(); + } + + Result OpenDataStorageByDataId(std::unique_ptr<ams::fs::IStorage> *out, ncm::DataId data_id, ncm::StorageId storage_id) { + /* Open storage. */ + sf::SharedPointer<fssrv::sf::IStorage> s; + AMS_FS_R_TRY(OpenDataStorageByDataIdImpl(std::addressof(s), data_id, storage_id)); + + auto storage = std::make_unique<impl::StorageServiceObjectAdapter<fssrv::sf::IStorage>>(std::move(s)); + R_UNLESS(storage != nullptr, fs::ResultAllocationMemoryFailedInDataA()); + + *out = std::move(storage); + R_SUCCEED(); + } + + Result MountDataImpl(const char *name, ncm::DataId data_id, ncm::StorageId storage_id, void *cache_buffer, size_t cache_size, bool use_cache, bool use_data_cache, bool use_path_cache) { + std::unique_ptr<fs::IStorage> storage; + R_TRY(OpenDataStorageByDataId(std::addressof(storage), data_id, storage_id)); + + auto fs = std::make_unique<RomFsFileSystem>(); + R_UNLESS(fs != nullptr, fs::ResultAllocationMemoryFailedInDataB()); + R_TRY(fs->Initialize(std::move(storage), cache_buffer, cache_size, use_cache)); + + R_RETURN(fsa::Register(name, std::move(fs), nullptr, use_data_cache, use_path_cache, false)); + } + + } + + Result QueryMountDataCacheSize(size_t *out, ncm::DataId data_id, ncm::StorageId storage_id) { + AMS_FS_R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + + std::unique_ptr<fs::IStorage> storage; + AMS_FS_R_TRY(OpenDataStorageByDataId(std::addressof(storage), data_id, storage_id)); + + size_t size = 0; + AMS_FS_R_TRY(RomFsFileSystem::GetRequiredWorkingMemorySize(std::addressof(size), storage.get())); + + constexpr size_t MinimumCacheSize = 32; + *out = std::max(size, MinimumCacheSize); + R_SUCCEED(); + } + + Result MountData(const char *name, ncm::DataId data_id, ncm::StorageId storage_id) { + /* Validate the mount name. */ + AMS_FS_R_TRY(impl::CheckMountName(name)); + + R_RETURN(MountDataImpl(name, data_id, storage_id, nullptr, 0, false, false, false)); + } + + Result MountData(const char *name, ncm::DataId data_id, ncm::StorageId storage_id, void *cache_buffer, size_t cache_size) { + /* Validate the mount name. */ + AMS_FS_R_TRY(impl::CheckMountName(name)); + + AMS_FS_R_UNLESS(cache_buffer != nullptr, fs::ResultNullptrArgument()); + + R_RETURN(MountDataImpl(name, data_id, storage_id, cache_buffer, cache_size, true, false, false)); + } + + Result MountData(const char *name, ncm::DataId data_id, ncm::StorageId storage_id, void *cache_buffer, size_t cache_size, bool use_data_cache, bool use_path_cache) { + /* Validate the mount name. */ + AMS_FS_R_TRY(impl::CheckMountName(name)); + + AMS_FS_R_UNLESS(cache_buffer != nullptr, fs::ResultNullptrArgument()); + + R_RETURN(MountDataImpl(name, data_id, storage_id, cache_buffer, cache_size, true, use_data_cache, use_path_cache)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_device_save_data.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_device_save_data.cpp new file mode 100644 index 00000000..cf0a0611 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_device_save_data.cpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsa/fs_mount_utils.hpp" +#include "impl/fs_file_system_proxy_service_object.hpp" +#include "impl/fs_file_system_service_object_adapter.hpp" + +namespace ams::fs { + + namespace { + + constexpr inline SaveDataSpaceId DeviceSaveDataSpaceId = SaveDataSpaceId::User; + + Result MountDeviceSaveDataImpl(const char *name, const SaveDataAttribute &attribute) { + /* Validate the mount name. */ + R_TRY(impl::CheckMountName(name)); + + /* Open the filesystem. */ + auto fsp = impl::GetFileSystemProxyServiceObject(); + sf::SharedPointer<fssrv::sf::IFileSystem> fs; + R_TRY(fsp->OpenSaveDataFileSystem(std::addressof(fs), static_cast<u8>(DeviceSaveDataSpaceId), attribute)); + + /* Allocate a new filesystem wrapper. */ + auto fsa = std::make_unique<impl::FileSystemServiceObjectAdapter>(std::move(fs)); + R_UNLESS(fsa != nullptr, fs::ResultAllocationMemoryFailedInDeviceSaveDataA()); + + /* Register. */ + R_RETURN(fsa::Register(name, std::move(fsa))); + } + + } + + Result MountDeviceSaveData(const char *name) { + const auto attr = SaveDataAttribute::Make(ncm::InvalidProgramId, SaveDataType::Device, InvalidUserId, InvalidSystemSaveDataId); + + /* Perform the mount. */ + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_MOUNT(MountDeviceSaveDataImpl(name, attr), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT, name)); + + /* Enable access logging. */ + AMS_FS_IMPL_ACCESS_LOG_FS_ACCESSOR_ENABLE(name); + + R_SUCCEED(); + } + + Result MountDeviceSaveData(const char *name, const ncm::ApplicationId application_id) { + const auto attr = SaveDataAttribute::Make(application_id, SaveDataType::Device, InvalidUserId, InvalidSystemSaveDataId); + + /* Perform the mount. */ + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_MOUNT(MountDeviceSaveDataImpl(name, attr), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_DEVICE_SAVE_DATA_APPLICATION_ID(name, application_id))); + + /* Enable access logging. */ + AMS_FS_IMPL_ACCESS_LOG_FS_ACCESSOR_ENABLE(name); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_error_info.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_error_info.cpp new file mode 100644 index 00000000..a6dab78e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_error_info.cpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsa/fs_mount_utils.hpp" +#include "impl/fs_file_system_proxy_service_object.hpp" +#include "impl/fs_file_system_service_object_adapter.hpp" + +namespace ams::fs { + + Result GetAndClearFileSystemProxyErrorInfo(FileSystemProxyErrorInfo *out) { + /* Check pre-conditions. */ + AMS_FS_R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + + auto fsp = impl::GetFileSystemProxyServiceObject(); + + /* Get the error info. */ + AMS_FS_R_TRY(fsp->GetAndClearErrorInfo(out)); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_file_path_hash.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_file_path_hash.hpp new file mode 100644 index 00000000..becfa7c7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_file_path_hash.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fs::impl { + + constexpr inline size_t FilePathHashSize = 4; + + struct FilePathHash : public Newable { + u8 data[FilePathHashSize]; + }; + static_assert(util::is_pod<FilePathHash>::value); + + inline bool operator==(const FilePathHash &lhs, const FilePathHash &rhs) { + return std::memcmp(lhs.data, rhs.data, FilePathHashSize) == 0; + } + + inline bool operator!=(const FilePathHash &lhs, const FilePathHash &rhs) { + return !(lhs == rhs); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_filesystem_utils.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_filesystem_utils.cpp new file mode 100644 index 00000000..dd073fc0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_filesystem_utils.cpp @@ -0,0 +1,79 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsa/fs_mount_utils.hpp" +#include "fsa/fs_filesystem_accessor.hpp" + +namespace ams::fs { + + Result EnsureDirectory(const char *path) { + /* Get the filesystem accessor and sub path. */ + impl::FileSystemAccessor *accessor; + const char *sub_path; + R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path)); + + /* Set up the true sub path. */ + fs::Path sub_fs_path; + R_TRY(accessor->SetUpPath(std::addressof(sub_fs_path), sub_path)); + + /* Use the system implementation. */ + R_RETURN(fssystem::EnsureDirectory(accessor->GetRawFileSystemUnsafe(), sub_fs_path)); + } + + Result EnsureParentDirectory(const char *path) { + /* Get the filesystem accessor and sub path. */ + impl::FileSystemAccessor *accessor; + const char *sub_path; + R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path)); + + /* Set up the true sub path. */ + fs::Path sub_fs_path; + R_TRY(accessor->SetUpPath(std::addressof(sub_fs_path), sub_path)); + + /* Convert the path to the parent directory path. */ + R_TRY(sub_fs_path.RemoveChild()); + + /* Use the system implementation. */ + R_RETURN(fssystem::EnsureDirectory(accessor->GetRawFileSystemUnsafe(), sub_fs_path)); + } + + Result HasFile(bool *out, const char *path) { + /* Get the filesystem accessor and sub path. */ + impl::FileSystemAccessor *accessor; + const char *sub_path; + R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path)); + + /* Set up the true sub path. */ + fs::Path sub_fs_path; + R_TRY(accessor->SetUpPath(std::addressof(sub_fs_path), sub_path)); + + R_RETURN(fssystem::HasFile(out, accessor->GetRawFileSystemUnsafe(), sub_fs_path)); + } + + Result HasDirectory(bool *out, const char *path) { + /* Get the filesystem accessor and sub path. */ + impl::FileSystemAccessor *accessor; + const char *sub_path; + R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path)); + + /* Set up the true sub path. */ + fs::Path sub_fs_path; + R_TRY(accessor->SetUpPath(std::addressof(sub_fs_path), sub_path)); + + R_RETURN(fssystem::HasDirectory(out, accessor->GetRawFileSystemUnsafe(), sub_fs_path)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_game_card.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_game_card.cpp new file mode 100644 index 00000000..08e88ecb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_game_card.cpp @@ -0,0 +1,164 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsa/fs_mount_utils.hpp" +#include "impl/fs_file_system_proxy_service_object.hpp" +#include "impl/fs_file_system_service_object_adapter.hpp" + +namespace ams::fs { + + namespace { + + const char *GetGameCardMountNameSuffix(GameCardPartition which) { + switch (which) { + case GameCardPartition::Update: return impl::GameCardFileSystemMountNameUpdateSuffix; + case GameCardPartition::Normal: return impl::GameCardFileSystemMountNameNormalSuffix; + case GameCardPartition::Secure: return impl::GameCardFileSystemMountNameSecureSuffix; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + class GameCardCommonMountNameGenerator : public fsa::ICommonMountNameGenerator, public impl::Newable { + private: + const GameCardHandle m_handle; + const GameCardPartition m_partition; + public: + explicit GameCardCommonMountNameGenerator(GameCardHandle h, GameCardPartition p) : m_handle(h), m_partition(p) { /* ... */ } + + virtual Result GenerateCommonMountName(char *dst, size_t dst_size) override { + /* Determine how much space we need. */ + const size_t needed_size = util::Strnlen(impl::GameCardFileSystemMountName, MountNameLengthMax) + util::Strnlen(GetGameCardMountNameSuffix(m_partition), MountNameLengthMax) + sizeof(GameCardHandle) * 2 + 2; + AMS_ABORT_UNLESS(dst_size >= needed_size); + + /* Generate the name. */ + const auto size = util::SNPrintf(dst, dst_size, "%s%s%08x:", impl::GameCardFileSystemMountName, GetGameCardMountNameSuffix(m_partition), m_handle); + AMS_ASSERT(static_cast<size_t>(size) == needed_size - 1); + AMS_UNUSED(size); + + R_SUCCEED(); + } + }; + + } + + Result GetGameCardHandle(GameCardHandle *out) { + auto fsp = impl::GetFileSystemProxyServiceObject(); + + /* Open a device operator. */ + sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator; + AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator))); + + /* Get the handle. */ + u32 handle; + AMS_FS_R_TRY(device_operator->GetGameCardHandle(std::addressof(handle))); + + *out = handle; + R_SUCCEED(); + } + + Result MountGameCardPartition(const char *name, GameCardHandle handle, GameCardPartition partition) { + auto mount_impl = [=]() -> Result { + /* Validate the mount name. */ + R_TRY(impl::CheckMountNameAllowingReserved(name)); + + /* Open the gamecard filesystem. */ + auto fsp = impl::GetFileSystemProxyServiceObject(); + sf::SharedPointer<fssrv::sf::IFileSystem> fs; + R_TRY(fsp->OpenGameCardFileSystem(std::addressof(fs), static_cast<u32>(handle), static_cast<u32>(partition))); + + /* Allocate a new filesystem wrapper. */ + auto fsa = std::make_unique<impl::FileSystemServiceObjectAdapter>(std::move(fs)); + R_UNLESS(fsa != nullptr, fs::ResultAllocationMemoryFailedInGameCardC()); + + /* Allocate a new mountname generator. */ + auto generator = std::make_unique<GameCardCommonMountNameGenerator>(handle, partition); + R_UNLESS(generator != nullptr, fs::ResultAllocationMemoryFailedInGameCardD()); + + /* Register. */ + R_RETURN(fsa::Register(name, std::move(fsa), std::move(generator))); + }; + + /* Perform the mount. */ + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM_MOUNT(mount_impl(), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_GAME_CARD_PARTITION(name, handle, partition))); + + /* Enable access logging. */ + AMS_FS_IMPL_ACCESS_LOG_SYSTEM_FS_ACCESSOR_ENABLE(name); + + R_SUCCEED(); + } + + bool IsGameCardInserted() { + auto fsp = impl::GetFileSystemProxyServiceObject(); + + /* Open a device operator. */ + sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator; + AMS_FS_R_ABORT_UNLESS(fsp->OpenDeviceOperator(std::addressof(device_operator))); + + /* Get insertion status. */ + bool inserted; + AMS_FS_R_ABORT_UNLESS(device_operator->IsGameCardInserted(std::addressof(inserted))); + + return inserted; + } + + Result GetGameCardCid(void *dst, size_t size) { + /* Check pre-conditions. */ + AMS_FS_R_UNLESS(size >= sizeof(gc::GameCardIdSet), fs::ResultInvalidSize()); + + auto fsp = impl::GetFileSystemProxyServiceObject(); + + /* Open a device operator. */ + sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator; + AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator))); + + /* Get the id set. */ + gc::GameCardIdSet gc_id_set; + AMS_FS_R_TRY(device_operator->GetGameCardIdSet(sf::OutBuffer(std::addressof(gc_id_set), sizeof(gc_id_set)), static_cast<s64>(sizeof(gc_id_set)))); + + /* Copy the id set to output. */ + std::memcpy(dst, std::addressof(gc_id_set), sizeof(gc_id_set)); + + R_SUCCEED(); + + } + + Result GetGameCardDeviceId(void *dst, size_t size) { + auto fsp = impl::GetFileSystemProxyServiceObject(); + + /* Open a device operator. */ + sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator; + AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator))); + + /* Get the cid. */ + AMS_FS_R_TRY(device_operator->GetGameCardDeviceId(sf::OutBuffer(dst, size), static_cast<s64>(size))); + + R_SUCCEED(); + } + + Result GetGameCardErrorReportInfo(GameCardErrorReportInfo *out) { + auto fsp = impl::GetFileSystemProxyServiceObject(); + + /* Open a device operator. */ + sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator; + AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator))); + + /* Get the error report info. */ + AMS_FS_R_TRY(device_operator->GetGameCardErrorReportInfo(out)); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_host.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_host.cpp new file mode 100644 index 00000000..6e00e505 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_host.cpp @@ -0,0 +1,241 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsa/fs_mount_utils.hpp" +#include "impl/fs_file_system_proxy_service_object.hpp" +#include "impl/fs_file_system_service_object_adapter.hpp" + +namespace ams::fs { + + namespace { + + class HostRootCommonMountNameGenerator : public fsa::ICommonMountNameGenerator, public impl::Newable { + public: + explicit HostRootCommonMountNameGenerator() { /* ... */ } + + virtual Result GenerateCommonMountName(char *dst, size_t dst_size) override { + /* Determine how much space we need. */ + constexpr size_t RequiredNameBufferSizeSize = AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME_LEN + 1 + 1; + AMS_ASSERT(dst_size >= RequiredNameBufferSizeSize); + AMS_UNUSED(RequiredNameBufferSizeSize); + + /* Generate the name. */ + const auto size = util::SNPrintf(dst, dst_size, AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME ":"); + AMS_ASSERT(static_cast<size_t>(size) == RequiredNameBufferSizeSize - 1); + AMS_UNUSED(size); + + R_SUCCEED(); + } + }; + + class HostCommonMountNameGenerator : public fsa::ICommonMountNameGenerator, public impl::Newable { + private: + char m_path[fs::EntryNameLengthMax + 1]; + public: + HostCommonMountNameGenerator(const char *path) { + util::Strlcpy<char>(m_path, path, sizeof(m_path)); + } + + virtual Result GenerateCommonMountName(char *dst, size_t dst_size) override { + /* Determine how much space we need. */ + const size_t required_size = AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME_LEN + 1 + util::Strnlen<char>(m_path, sizeof(m_path)) + 1; /* @Host:%s */ + R_UNLESS(dst_size >= required_size, fs::ResultTooLongPath()); + + + /* Generate the name. */ + const auto size = util::SNPrintf(dst, dst_size, AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME ":%s", m_path); + AMS_ASSERT(static_cast<size_t>(size) == required_size - 1); + AMS_UNUSED(size); + + R_SUCCEED(); + } + }; + + Result OpenHostFileSystemImpl(std::unique_ptr<fs::fsa::IFileSystem> *out, const fssrv::sf::FspPath &path, const MountHostOption &option) { + /* Open the filesystem. */ + auto fsp = impl::GetFileSystemProxyServiceObject(); + sf::SharedPointer<fssrv::sf::IFileSystem> fs; + if (option != MountHostOption::None) { + R_TRY(fsp->OpenHostFileSystemWithOption(std::addressof(fs), path, option._value)); + } else { + R_TRY(fsp->OpenHostFileSystem(std::addressof(fs), path)); + } + + /* Allocate a new filesystem wrapper. */ + auto fsa = std::make_unique<impl::FileSystemServiceObjectAdapter>(std::move(fs)); + R_UNLESS(fsa != nullptr, fs::ResultAllocationMemoryFailedInHostA()); + + /* Set the output. */ + *out = std::move(fsa); + R_SUCCEED(); + } + + Result PreMountHost(std::unique_ptr<fs::HostCommonMountNameGenerator> *out, const char *name, const char *path) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + + /* Check the mount name. */ + R_TRY(impl::CheckMountName(name)); + + /* Check that path is valid. */ + R_UNLESS(path != nullptr, fs::ResultNullptrArgument()); + + /* Create a new HostCommonMountNameGenerator. */ + *out = std::make_unique<HostCommonMountNameGenerator>(path); + R_UNLESS(out->get() != nullptr, fs::ResultAllocationMemoryFailedInHostB()); + + R_SUCCEED(); + } + + } + + namespace impl { + + Result OpenHostFileSystem(std::unique_ptr<fs::fsa::IFileSystem> *out, const char *name, const char *path, const fs::MountHostOption &option) { + /* Validate arguments. */ + R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(name != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(path != nullptr, fs::ResultNullptrArgument()); + + /* Check mount name isn't windows path or reserved. */ + R_UNLESS(!fs::IsWindowsDrive(name), fs::ResultInvalidMountName()); + R_UNLESS(!fs::impl::IsReservedMountName(name), fs::ResultInvalidMountName()); + + /* Convert the path for fsp. */ + fssrv::sf::FspPath sf_path; + R_TRY(fs::ConvertToFspPath(std::addressof(sf_path), path)); + + /* Ensure that the path doesn't correspond to the root. */ + if (sf_path.str[0] == 0) { + sf_path.str[0] = '.'; + sf_path.str[1] = 0; + } + + /* Open the host file system. */ + R_RETURN(OpenHostFileSystemImpl(out, sf_path, option)); + } + + } + + Result MountHost(const char *name, const char *root_path) { + /* Pre-mount host. */ + std::unique_ptr<fs::HostCommonMountNameGenerator> generator; + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_MOUNT_UNLESS_R_SUCCEEDED(PreMountHost(std::addressof(generator), name, root_path), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_HOST(name, root_path))); + + /* Open the filesystem. */ + std::unique_ptr<fs::fsa::IFileSystem> fsa; + R_TRY(AMS_FS_IMPL_ACCESS_LOG_MOUNT_UNLESS_R_SUCCEEDED(impl::OpenHostFileSystem(std::addressof(fsa), name, root_path, MountHostOption::None), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_HOST(name, root_path))); + + /* Declare registration helper. */ + auto register_impl = [&]() -> Result { + /* Register. */ + R_RETURN(fsa::Register(name, std::move(fsa), std::move(generator))); + }; + + /* Mount the filesystem. */ + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_MOUNT(register_impl(), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_HOST(name, root_path))); + + /* Enable access logging. */ + AMS_FS_IMPL_ACCESS_LOG_FS_ACCESSOR_ENABLE(name); + + R_SUCCEED(); + } + + Result MountHost(const char *name, const char *root_path, const MountHostOption &option) { + /* Pre-mount host. */ + std::unique_ptr<fs::HostCommonMountNameGenerator> generator; + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_MOUNT_UNLESS_R_SUCCEEDED(PreMountHost(std::addressof(generator), name, root_path), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_HOST_WITH_OPTION(name, root_path, option))); + + /* Open the filesystem. */ + std::unique_ptr<fs::fsa::IFileSystem> fsa; + R_TRY(AMS_FS_IMPL_ACCESS_LOG_MOUNT_UNLESS_R_SUCCEEDED(impl::OpenHostFileSystem(std::addressof(fsa), name, root_path, option), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_HOST_WITH_OPTION(name, root_path, option))); + + /* Declare registration helper. */ + auto register_impl = [&]() -> Result { + /* Register. */ + R_RETURN(fsa::Register(name, std::move(fsa), std::move(generator))); + }; + + /* Mount the filesystem. */ + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_MOUNT(register_impl(), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_HOST_WITH_OPTION(name, root_path, option))); + + /* Enable access logging. */ + AMS_FS_IMPL_ACCESS_LOG_FS_ACCESSOR_ENABLE(name); + + R_SUCCEED(); + } + + Result MountHostRoot() { + /* Create host root path. */ + fssrv::sf::FspPath sf_path; + sf_path.str[0] = 0; + + /* Open the filesystem. */ + std::unique_ptr<fs::fsa::IFileSystem> fsa; + R_TRY(AMS_FS_IMPL_ACCESS_LOG_MOUNT_UNLESS_R_SUCCEEDED(OpenHostFileSystemImpl(std::addressof(fsa), sf_path, MountHostOption::None), impl::HostRootFileSystemMountName, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_HOST_ROOT())); + + /* Declare registration helper. */ + auto register_impl = [&]() -> Result { + /* Allocate a new mountname generator. */ + auto generator = std::make_unique<HostRootCommonMountNameGenerator>(); + R_UNLESS(generator != nullptr, fs::ResultAllocationMemoryFailedInHostC()); + + /* Register. */ + R_RETURN(fsa::Register(impl::HostRootFileSystemMountName, std::move(fsa), std::move(generator))); + }; + + /* Mount the filesystem. */ + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_MOUNT(register_impl(), impl::HostRootFileSystemMountName, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_HOST_ROOT())); + + /* Enable access logging. */ + AMS_FS_IMPL_ACCESS_LOG_FS_ACCESSOR_ENABLE(impl::HostRootFileSystemMountName); + + R_SUCCEED(); + } + + Result MountHostRoot(const MountHostOption &option) { + /* Create host root path. */ + fssrv::sf::FspPath sf_path; + sf_path.str[0] = 0; + + /* Open the filesystem. */ + std::unique_ptr<fs::fsa::IFileSystem> fsa; + R_TRY(AMS_FS_IMPL_ACCESS_LOG_MOUNT_UNLESS_R_SUCCEEDED(OpenHostFileSystemImpl(std::addressof(fsa), sf_path, option), impl::HostRootFileSystemMountName, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_HOST_ROOT_WITH_OPTION(option))); + + /* Declare registration helper. */ + auto register_impl = [&]() -> Result { + /* Allocate a new mountname generator. */ + auto generator = std::make_unique<HostRootCommonMountNameGenerator>(); + R_UNLESS(generator != nullptr, fs::ResultAllocationMemoryFailedInHostC()); + + /* Register. */ + R_RETURN(fsa::Register(impl::HostRootFileSystemMountName, std::move(fsa), std::move(generator))); + }; + + /* Mount the filesystem. */ + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_MOUNT(register_impl(), impl::HostRootFileSystemMountName, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_HOST_ROOT_WITH_OPTION(option))); + + /* Enable access logging. */ + AMS_FS_IMPL_ACCESS_LOG_FS_ACCESSOR_ENABLE(impl::HostRootFileSystemMountName); + + R_SUCCEED(); + } + + void UnmountHostRoot() { + return Unmount(impl::HostRootFileSystemMountName); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_image_directory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_image_directory.cpp new file mode 100644 index 00000000..5563f9e0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_image_directory.cpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsa/fs_mount_utils.hpp" +#include "impl/fs_file_system_proxy_service_object.hpp" +#include "impl/fs_file_system_service_object_adapter.hpp" + +namespace ams::fs { + + Result MountImageDirectory(const char *name, ImageDirectoryId id) { + auto mount_impl = [=]() -> Result { + /* Validate the mount name. */ + R_TRY(impl::CheckMountName(name)); + + /* Open the image directory. */ + auto fsp = impl::GetFileSystemProxyServiceObject(); + sf::SharedPointer<fssrv::sf::IFileSystem> fs; + R_TRY(fsp->OpenImageDirectoryFileSystem(std::addressof(fs), static_cast<u32>(id))); + + /* Allocate a new filesystem wrapper. */ + auto fsa = std::make_unique<impl::FileSystemServiceObjectAdapter>(std::move(fs)); + R_UNLESS(fsa != nullptr, fs::ResultAllocationMemoryFailedInImageDirectoryA()); + + /* Register. */ + R_RETURN(fsa::Register(name, std::move(fsa))); + }; + + /* Perform the mount. */ + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM_MOUNT(mount_impl(), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_IMAGE_DIRECTORY(name, id))); + + /* Enable access logging. */ + AMS_FS_IMPL_ACCESS_LOG_SYSTEM_FS_ACCESSOR_ENABLE(name); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_memory_management.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_memory_management.cpp new file mode 100644 index 00000000..05140b5c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_memory_management.cpp @@ -0,0 +1,119 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fs { + + namespace { + + constinit bool g_used_default_allocator = false; + + void *DefaultAllocate(size_t size) { + g_used_default_allocator = true; + return ams::Malloc(size); + } + + void DefaultDeallocate(void *ptr, size_t size) { + AMS_UNUSED(size); + ams::Free(ptr); + } + + constinit os::SdkMutex g_mutex; + constinit AllocateFunction g_allocate_func = DefaultAllocate; + constinit DeallocateFunction g_deallocate_func = DefaultDeallocate; + + constexpr size_t RequiredAlignment = alignof(u64); + + Result SetAllocatorImpl(AllocateFunction allocator, DeallocateFunction deallocator) { + /* Ensure SetAllocator is used correctly. */ + R_UNLESS(g_allocate_func == DefaultAllocate, fs::ResultAllocatorAlreadyRegistered()); + R_UNLESS(g_deallocate_func == DefaultDeallocate, fs::ResultAllocatorAlreadyRegistered()); + R_UNLESS(allocator != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(deallocator != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(!g_used_default_allocator, fs::ResultDefaultAllocatorUsed()); + + /* Set allocators. */ + g_allocate_func = allocator; + g_deallocate_func = deallocator; + R_SUCCEED(); + } + + } + + void SetAllocator(AllocateFunction allocator, DeallocateFunction deallocator) { + R_ABORT_UNLESS(SetAllocatorImpl(allocator, deallocator)); + } + + namespace impl { + + void LockAllocatorMutex() { + g_mutex.Lock(); + } + + void UnlockAllocatorMutex() { + g_mutex.Unlock(); + } + + void *AllocateUnsafe(size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(g_mutex.IsLockedByCurrentThread()); + + /* Allocate. */ + void * const ptr = g_allocate_func(size); + + /* Check alignment. */ + if (AMS_UNLIKELY(!util::IsAligned(reinterpret_cast<uintptr_t>(ptr), RequiredAlignment))) { + R_ABORT_UNLESS(fs::ResultAllocatorAlignmentViolation()); + } + + /* Return allocated pointer. */ + return ptr; + } + + void DeallocateUnsafe(void *ptr, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(g_mutex.IsLockedByCurrentThread()); + + /* Deallocate the pointer. */ + g_deallocate_func(ptr, size); + } + + void *Allocate(size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(g_allocate_func != nullptr); + + /* Lock the allocator. */ + std::scoped_lock lk(g_mutex); + + return AllocateUnsafe(size); + } + + void Deallocate(void *ptr, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(g_deallocate_func != nullptr); + + /* If the pointer is non-null, deallocate it. */ + if (ptr != nullptr) { + /* Lock the allocator. */ + std::scoped_lock lk(g_mutex); + + DeallocateUnsafe(ptr, size); + } + } + + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_memory_report_info.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_memory_report_info.cpp new file mode 100644 index 00000000..f858aea3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_memory_report_info.cpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsa/fs_mount_utils.hpp" +#include "impl/fs_file_system_proxy_service_object.hpp" +#include "impl/fs_file_system_service_object_adapter.hpp" + +namespace ams::fs { + + Result GetAndClearMemoryReportInfo(MemoryReportInfo *out) { + /* Check pre-conditions. */ + AMS_FS_R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + + auto fsp = impl::GetFileSystemProxyServiceObject(); + + /* Get the memory report info. */ + AMS_FS_R_TRY(fsp->GetAndClearMemoryReportInfo(out)); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_mmc.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_mmc.cpp new file mode 100644 index 00000000..f14364b9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_mmc.cpp @@ -0,0 +1,109 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsa/fs_mount_utils.hpp" +#include "impl/fs_file_system_proxy_service_object.hpp" +#include "impl/fs_file_system_service_object_adapter.hpp" + +namespace ams::fs { + + Result GetMmcCid(void *dst, size_t size) { + /* Check pre-conditions. */ + AMS_FS_R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + + auto fsp = impl::GetFileSystemProxyServiceObject(); + + /* Open a device operator. */ + sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator; + AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator))); + + /* Get the cid. */ + AMS_FS_R_TRY(device_operator->GetMmcCid(sf::OutBuffer(dst, size), static_cast<s64>(size))); + + R_SUCCEED(); + } + + Result GetMmcSpeedMode(MmcSpeedMode *out) { + /* Check pre-conditions. */ + AMS_FS_R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + + auto fsp = impl::GetFileSystemProxyServiceObject(); + + /* Open a device operator. */ + sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator; + AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator))); + + /* Get the speed mode. */ + s64 speed_mode = 0; + AMS_FS_R_TRY(device_operator->GetMmcSpeedMode(std::addressof(speed_mode))); + + *out = static_cast<MmcSpeedMode>(speed_mode); + R_SUCCEED(); + } + + Result GetMmcPatrolCount(u32 *out) { + /* Check pre-conditions. */ + AMS_FS_R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + + auto fsp = impl::GetFileSystemProxyServiceObject(); + + /* Open a device operator. */ + sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator; + AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator))); + + /* Get the patrol count. */ + AMS_FS_R_TRY(device_operator->GetMmcPatrolCount(out)); + + R_SUCCEED(); + } + + Result GetAndClearMmcErrorInfo(StorageErrorInfo *out_sei, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size) { + /* Check pre-conditions. */ + AMS_FS_R_UNLESS(out_sei != nullptr, fs::ResultNullptrArgument()); + AMS_FS_R_UNLESS(out_log_size != nullptr, fs::ResultNullptrArgument()); + AMS_FS_R_UNLESS(out_log_buffer != nullptr, fs::ResultNullptrArgument()); + + auto fsp = impl::GetFileSystemProxyServiceObject(); + + /* Open a device operator. */ + sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator; + AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator))); + + /* Get the error info. */ + s64 log_size = 0; + AMS_FS_R_TRY(device_operator->GetAndClearMmcErrorInfo(out_sei, std::addressof(log_size), sf::OutBuffer(out_log_buffer, log_buffer_size), static_cast<s64>(log_buffer_size))); + + *out_log_size = static_cast<size_t>(log_size); + R_SUCCEED(); + } + + Result GetMmcExtendedCsd(void *dst, size_t size) { + /* Check pre-conditions. */ + AMS_FS_R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + + auto fsp = impl::GetFileSystemProxyServiceObject(); + + /* Open a device operator. */ + sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator; + AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator))); + + /* Get the csd. */ + AMS_FS_R_TRY(device_operator->GetMmcExtendedCsd(sf::OutBuffer(dst, size), static_cast<s64>(size))); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_priority.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_priority.cpp new file mode 100644 index 00000000..c53ffab8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_priority.cpp @@ -0,0 +1,160 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fs { + + namespace { + + constexpr bool IsValidPriority(fs::Priority priority) { + return priority == Priority_Low || priority == Priority_Normal || priority == Priority_Realtime; + } + + constexpr bool IsValidPriorityRaw(fs::PriorityRaw priority_raw) { + return priority_raw == PriorityRaw_Background || priority_raw == PriorityRaw_Low || priority_raw == PriorityRaw_Normal || priority_raw == PriorityRaw_Realtime; + } + + fs::PriorityRaw ConvertPriorityToPriorityRaw(fs::Priority priority) { + AMS_ASSERT(IsValidPriority(priority)); + + switch (priority) { + case Priority_Low: return PriorityRaw_Low; + case Priority_Normal: return PriorityRaw_Normal; + case Priority_Realtime: return PriorityRaw_Realtime; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + fs::Priority ConvertPriorityRawToPriority(fs::PriorityRaw priority_raw) { + AMS_ASSERT(IsValidPriorityRaw(priority_raw)); + + switch (priority_raw) { + case PriorityRaw_Background: return Priority_Low; + case PriorityRaw_Low: return Priority_Low; + case PriorityRaw_Normal: return Priority_Normal; + case PriorityRaw_Realtime: return Priority_Realtime; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void UpdateTlsIoPriority(os::ThreadType *thread, u8 tls_io) { + sf::SetFsInlineContext(thread, (tls_io & impl::TlsIoPriorityMask) | (sf::GetFsInlineContext(thread) & ~impl::TlsIoPriorityMask)); + } + + Result GetPriorityRawImpl(fs::PriorityRaw *out, os::ThreadType *thread) { + /* Validate arguments. */ + R_UNLESS(thread != nullptr, fs::ResultNullptrArgument()); + + /* Get the raw priority. */ + PriorityRaw priority_raw; + R_TRY(impl::ConvertTlsIoPriorityToFsPriority(std::addressof(priority_raw), impl::GetTlsIoPriority(thread))); + + /* Set output. */ + *out = priority_raw; + R_SUCCEED(); + } + + Result GetPriorityImpl(fs::Priority *out, os::ThreadType *thread) { + /* Validate arguments. */ + R_UNLESS(thread != nullptr, fs::ResultNullptrArgument()); + + /* Get the raw priority. */ + PriorityRaw priority_raw; + R_TRY(impl::ConvertTlsIoPriorityToFsPriority(std::addressof(priority_raw), impl::GetTlsIoPriority(thread))); + + /* Set output. */ + *out = ConvertPriorityRawToPriority(priority_raw); + R_SUCCEED(); + } + + Result SetPriorityRawImpl(os::ThreadType *thread, fs::PriorityRaw priority_raw) { + /* Validate arguments. */ + R_UNLESS(thread != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(IsValidPriorityRaw(priority_raw), fs::ResultInvalidArgument()); + + /* Convert to tls io. */ + u8 tls_io; + R_TRY(impl::ConvertFsPriorityToTlsIoPriority(std::addressof(tls_io), priority_raw)); + + /* Update the priority. */ + UpdateTlsIoPriority(thread, tls_io); + R_SUCCEED(); + } + + Result SetPriorityImpl(os::ThreadType *thread, fs::Priority priority) { + /* Validate arguments. */ + R_UNLESS(thread != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(IsValidPriority(priority), fs::ResultInvalidArgument()); + + /* Convert to tls io. */ + u8 tls_io; + R_TRY(impl::ConvertFsPriorityToTlsIoPriority(std::addressof(tls_io), ConvertPriorityToPriorityRaw(priority))); + + /* Update the priority. */ + UpdateTlsIoPriority(thread, tls_io); + R_SUCCEED(); + } + + } + + Priority GetPriorityOnCurrentThread() { + fs::Priority priority; + AMS_FS_R_ABORT_UNLESS(AMS_FS_IMPL_ACCESS_LOG(GetPriorityImpl(std::addressof(priority), os::GetCurrentThread()), nullptr, AMS_FS_IMPL_ACCESS_LOG_FORMAT_NONE)); + return priority; + } + + Priority GetPriority(os::ThreadType *thread) { + fs::Priority priority; + AMS_FS_R_ABORT_UNLESS(AMS_FS_IMPL_ACCESS_LOG(GetPriorityImpl(std::addressof(priority), thread), nullptr, AMS_FS_IMPL_ACCESS_LOG_FORMAT_THREAD_ID, reinterpret_cast<u64>(thread != nullptr ? os::GetThreadId(thread) : os::ThreadId{}))); + return priority; + } + + PriorityRaw GetPriorityRawOnCurrentThread() { + fs::PriorityRaw priority_raw; + AMS_FS_R_ABORT_UNLESS(AMS_FS_IMPL_ACCESS_LOG(GetPriorityRawImpl(std::addressof(priority_raw), os::GetCurrentThread()), nullptr, AMS_FS_IMPL_ACCESS_LOG_FORMAT_NONE)); + return priority_raw; + } + + PriorityRaw GetPriorityRawOnCurrentThreadInternal() { + fs::PriorityRaw priority_raw; + R_ABORT_UNLESS(GetPriorityRawImpl(std::addressof(priority_raw), os::GetCurrentThread())); + return priority_raw; + + } + + PriorityRaw GetPriorityRaw(os::ThreadType *thread) { + fs::PriorityRaw priority_raw; + AMS_FS_R_ABORT_UNLESS(AMS_FS_IMPL_ACCESS_LOG(GetPriorityRawImpl(std::addressof(priority_raw), thread), nullptr, AMS_FS_IMPL_ACCESS_LOG_FORMAT_THREAD_ID, reinterpret_cast<u64>(thread != nullptr ? os::GetThreadId(thread) : os::ThreadId{}))); + return priority_raw; + } + + void SetPriorityOnCurrentThread(Priority priority) { + AMS_FS_R_ABORT_UNLESS(AMS_FS_IMPL_ACCESS_LOG(SetPriorityImpl(os::GetCurrentThread(), priority), nullptr, AMS_FS_IMPL_ACCESS_LOG_FORMAT_NONE)); + } + + void SetPriority(os::ThreadType *thread, Priority priority) { + AMS_FS_R_ABORT_UNLESS(AMS_FS_IMPL_ACCESS_LOG(SetPriorityImpl(os::GetCurrentThread(), priority), nullptr, AMS_FS_IMPL_ACCESS_LOG_FORMAT_THREAD_ID, reinterpret_cast<u64>(thread != nullptr ? os::GetThreadId(thread) : os::ThreadId{}))); + } + + void SetPriorityRawOnCurrentThread(PriorityRaw priority_raw) { + AMS_FS_R_ABORT_UNLESS(AMS_FS_IMPL_ACCESS_LOG(SetPriorityRawImpl(os::GetCurrentThread(), priority_raw), nullptr, AMS_FS_IMPL_ACCESS_LOG_FORMAT_NONE)); + } + + void SetPriorityRaw(os::ThreadType *thread, PriorityRaw priority_raw) { + AMS_FS_R_ABORT_UNLESS(AMS_FS_IMPL_ACCESS_LOG(SetPriorityRawImpl(os::GetCurrentThread(), priority_raw), nullptr, AMS_FS_IMPL_ACCESS_LOG_FORMAT_THREAD_ID, reinterpret_cast<u64>(thread != nullptr ? os::GetThreadId(thread) : os::ThreadId{}))); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_program_id.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_program_id.cpp new file mode 100644 index 00000000..3ef64dae --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_program_id.cpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <stratosphere/fs/fs_rights_id.hpp> +#include "impl/fs_file_system_proxy_service_object.hpp" + +namespace ams::fs { + + Result GetProgramId(ncm::ProgramId *out, const char *path, fs::ContentAttributes attr) { + AMS_FS_R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + AMS_FS_R_UNLESS(path != nullptr, fs::ResultNullptrArgument()); + + /* Convert the path for fsp. */ + fssrv::sf::FspPath sf_path; + R_TRY(fs::ConvertToFspPath(std::addressof(sf_path), path)); + + auto fsp = impl::GetFileSystemProxyServiceObject(); + AMS_FS_R_TRY(fsp->GetProgramId(out, sf_path, attr)); + + R_SUCCEED(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_remote_file_system_proxy.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_remote_file_system_proxy.cpp new file mode 100644 index 00000000..db61eb68 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_remote_file_system_proxy.cpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fs_remote_file_system_proxy.hpp" +#include "fs_remote_file_system_proxy_for_loader.hpp" + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) +extern "C" { + + extern u32 __nx_fs_num_sessions; + +} +#endif + +namespace ams::fs { + + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + RemoteFileSystemProxy::RemoteFileSystemProxy(int session_count) { + /* Ensure we can connect to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + ON_SCOPE_EXIT { R_ABORT_UNLESS(sm::Finalize()); }; + + /* Initialize libnx. */ + __nx_fs_num_sessions = static_cast<u32>(session_count); + R_ABORT_UNLESS(::fsInitialize()); + } + + RemoteFileSystemProxy::~RemoteFileSystemProxy() { + ::fsExit(); + } + + RemoteFileSystemProxyForLoader::RemoteFileSystemProxyForLoader() { + /* Ensure we can connect to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + ON_SCOPE_EXIT { R_ABORT_UNLESS(sm::Finalize()); }; + + R_ABORT_UNLESS(::fsldrInitialize()); + } + + RemoteFileSystemProxyForLoader::~RemoteFileSystemProxyForLoader() { + ::fsldrExit(); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_remote_file_system_proxy.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_remote_file_system_proxy.hpp new file mode 100644 index 00000000..6cfcd8ab --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_remote_file_system_proxy.hpp @@ -0,0 +1,492 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include <stratosphere/fssrv/fssrv_interface_adapters.hpp> +#include "../fssrv/impl/fssrv_allocator_for_service_framework.hpp" +#include "impl/fs_remote_event_notifier.hpp" +#include "impl/fs_remote_device_operator.hpp" + +namespace ams::fs { + + #if defined(ATMOSPHERE_OS_HORIZON) + + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-parameter" + + class RemoteFileSystemProxy { + NON_COPYABLE(RemoteFileSystemProxy); + NON_MOVEABLE(RemoteFileSystemProxy); + private: + using ObjectFactory = fssrv::impl::FileSystemObjectFactory; + public: + RemoteFileSystemProxy(int session_count); + ~RemoteFileSystemProxy(); + public: + /* Command interface */ + Result OpenFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, u32 type) { + AMS_ABORT("TODO"); + } + + + Result SetCurrentProcess(const ams::sf::ClientProcessId &client_pid) { + /* Libnx does this for us automatically. */ + AMS_UNUSED(client_pid); + R_SUCCEED(); + } + + Result OpenDataFileSystemByCurrentProcess(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out) { + AMS_ABORT("TODO"); + } + + Result OpenFileSystemWithPatch(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, ncm::ProgramId program_id, u32 type) { + AMS_ABORT("TODO"); + } + + Result OpenFileSystemWithIdObsolete(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, u64 program_id, u32 type) { + R_RETURN(this->OpenFileSystemWithId(out, path, fs::ContentAttributes_None, program_id, type)); + } + + Result OpenFileSystemWithId(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, fs::ContentAttributes attr, u64 program_id, u32 type) { + ::FsFileSystem fs; + R_TRY(fsOpenFileSystemWithId(std::addressof(fs), program_id, static_cast<::FsFileSystemType>(type), path.str, static_cast<::FsContentAttributes>(static_cast<u8>(attr)))); + + out.SetValue(ObjectFactory::CreateSharedEmplaced<fssrv::sf::IFileSystem, fssrv::impl::RemoteFileSystem>(fs)); + R_SUCCEED(); + } + + Result OpenDataFileSystemByProgramId(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, ncm::ProgramId program_id) { + AMS_ABORT("TODO"); + } + + + Result OpenBisFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, u32 id) { + AMS_ABORT("TODO"); + } + + + Result OpenBisStorage(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, u32 id) { + FsStorage s; + R_TRY(fsOpenBisStorage(std::addressof(s), static_cast<::FsBisPartitionId>(id))); + + out.SetValue(ObjectFactory::CreateSharedEmplaced<fssrv::sf::IStorage, fssrv::impl::RemoteStorage>(s)); + R_SUCCEED(); + } + + Result InvalidateBisCache() { + AMS_ABORT("TODO"); + } + + Result OpenHostFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path) { + AMS_ABORT("TODO"); + } + + + Result OpenSdCardFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out) { + ::FsFileSystem fs; + R_TRY(fsOpenSdCardFileSystem(std::addressof(fs))); + + out.SetValue(ObjectFactory::CreateSharedEmplaced<fssrv::sf::IFileSystem, fssrv::impl::RemoteFileSystem>(fs)); + R_SUCCEED(); + } + + Result FormatSdCardFileSystem() { + AMS_ABORT("TODO"); + } + + + Result DeleteSaveDataFileSystem(u64 save_data_id) { + AMS_UNUSED(save_data_id); + AMS_ABORT("TODO: libnx binding"); + } + + Result CreateSaveDataFileSystem(const fs::SaveDataAttribute &attribute, const fs::SaveDataCreationInfo &creation_info, const fs::SaveDataMetaInfo &meta_info) { + AMS_ABORT("TODO"); + } + + + Result CreateSaveDataFileSystemBySystemSaveDataId(const fs::SaveDataAttribute &attribute, const fs::SaveDataCreationInfo &creation_info) { + static_assert(sizeof(attribute) == sizeof(::FsSaveDataAttribute)); + static_assert(sizeof(creation_info) == sizeof(::FsSaveDataCreationInfo)); + R_RETURN(fsCreateSaveDataFileSystemBySystemSaveDataId(reinterpret_cast<const ::FsSaveDataAttribute *>(std::addressof(attribute)), reinterpret_cast<const ::FsSaveDataCreationInfo *>(std::addressof(creation_info)))); + } + + Result RegisterSaveDataFileSystemAtomicDeletion(const ams::sf::InBuffer &save_data_ids) { + AMS_ABORT("TODO"); + } + + + Result DeleteSaveDataFileSystemBySaveDataSpaceId(u8 indexer_space_id, u64 save_data_id) { + R_RETURN(fsDeleteSaveDataFileSystemBySaveDataSpaceId(static_cast<::FsSaveDataSpaceId>(indexer_space_id), save_data_id)); + } + + Result FormatSdCardDryRun() { + AMS_ABORT("TODO"); + } + + Result IsExFatSupported(ams::sf::Out<bool> out) { + AMS_ABORT("TODO"); + } + + + Result DeleteSaveDataFileSystemBySaveDataAttribute(u8 space_id, const fs::SaveDataAttribute &attribute) { + static_assert(sizeof(attribute) == sizeof(::FsSaveDataAttribute)); + R_RETURN(fsDeleteSaveDataFileSystemBySaveDataAttribute(static_cast<::FsSaveDataSpaceId>(space_id), reinterpret_cast<const ::FsSaveDataAttribute *>(std::addressof(attribute)))); + } + + Result OpenGameCardStorage(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, u32 handle, u32 partition) { + AMS_ABORT("TODO"); + } + + + Result OpenGameCardFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u32 handle, u32 partition) { + ::FsFileSystem fs; + const ::FsGameCardHandle _hnd = {handle}; + R_TRY(fsOpenGameCardFileSystem(std::addressof(fs), std::addressof(_hnd), static_cast<::FsGameCardPartition>(partition))); + + out.SetValue(ObjectFactory::CreateSharedEmplaced<fssrv::sf::IFileSystem, fssrv::impl::RemoteFileSystem>(fs)); + R_SUCCEED(); + } + + Result ExtendSaveDataFileSystem(u8 space_id, u64 save_data_id, s64 available_size, s64 journal_size) { + R_RETURN(::fsExtendSaveDataFileSystem(static_cast<::FsSaveDataSpaceId>(space_id), save_data_id, available_size, journal_size)); + } + + Result DeleteCacheStorage(u16 index) { + AMS_ABORT("TODO"); + } + + Result GetCacheStorageSize(ams::sf::Out<s64> out_size, ams::sf::Out<s64> out_journal_size, u16 index) { + AMS_ABORT("TODO"); + } + + Result CreateSaveDataFileSystemWithHashSalt(const fs::SaveDataAttribute &attribute, const fs::SaveDataCreationInfo &creation_info, const fs::SaveDataMetaInfo &meta_info, const fs::HashSalt &salt) { + AMS_ABORT("TODO"); + } + + Result OpenHostFileSystemWithOption(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, u32 option) { + AMS_ABORT("TODO"); + } + + + Result OpenSaveDataFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u8 space_id, const fs::SaveDataAttribute &attribute) { + ::FsFileSystem fs; + R_TRY(fsOpenSaveDataFileSystem(std::addressof(fs), static_cast<::FsSaveDataSpaceId>(space_id), reinterpret_cast<const ::FsSaveDataAttribute *>(std::addressof(attribute)))); + + out.SetValue(ObjectFactory::CreateSharedEmplaced<fssrv::sf::IFileSystem, fssrv::impl::RemoteFileSystem>(fs)); + R_SUCCEED(); + } + + Result OpenSaveDataFileSystemBySystemSaveDataId(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u8 space_id, const fs::SaveDataAttribute &attribute) { + ::FsFileSystem fs; + R_TRY(fsOpenSaveDataFileSystemBySystemSaveDataId(std::addressof(fs), static_cast<::FsSaveDataSpaceId>(space_id), reinterpret_cast<const ::FsSaveDataAttribute *>(std::addressof(attribute)))); + + out.SetValue(ObjectFactory::CreateSharedEmplaced<fssrv::sf::IFileSystem, fssrv::impl::RemoteFileSystem>(fs)); + R_SUCCEED(); + } + + Result OpenReadOnlySaveDataFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u8 space_id, const fs::SaveDataAttribute &attribute) { + AMS_ABORT("TODO"); + } + + + Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(const ams::sf::OutBuffer &buffer, u8 space_id, u64 save_data_id) { + R_RETURN(fsReadSaveDataFileSystemExtraDataBySaveDataSpaceId(buffer.GetPointer(), buffer.GetSize(), static_cast<::FsSaveDataSpaceId>(space_id), save_data_id)); + } + + Result ReadSaveDataFileSystemExtraData(const ams::sf::OutBuffer &buffer, u64 save_data_id) { + R_RETURN(fsReadSaveDataFileSystemExtraData(buffer.GetPointer(), buffer.GetSize(), save_data_id)); + } + + Result WriteSaveDataFileSystemExtraData(u64 save_data_id, u8 space_id, const ams::sf::InBuffer &buffer) { + R_RETURN(fsWriteSaveDataFileSystemExtraData(buffer.GetPointer(), buffer.GetSize(), static_cast<::FsSaveDataSpaceId>(space_id), save_data_id)); + } + + /* ... */ + + Result OpenImageDirectoryFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u32 id) { + ::FsFileSystem fs; + R_TRY(fsOpenImageDirectoryFileSystem(std::addressof(fs), static_cast<::FsImageDirectoryId>(id))); + + out.SetValue(ObjectFactory::CreateSharedEmplaced<fssrv::sf::IFileSystem, fssrv::impl::RemoteFileSystem>(fs)); + R_SUCCEED(); + } + + /* ... */ + + Result OpenContentStorageFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u32 id) { + ::FsFileSystem fs; + R_TRY(fsOpenContentStorageFileSystem(std::addressof(fs), static_cast<::FsContentStorageId>(id))); + + out.SetValue(ObjectFactory::CreateSharedEmplaced<fssrv::sf::IFileSystem, fssrv::impl::RemoteFileSystem>(fs)); + R_SUCCEED(); + } + + /* ... */ + + Result OpenDataStorageByCurrentProcess(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out) { + AMS_ABORT("TODO"); + } + + Result OpenDataStorageByProgramId(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, ncm::ProgramId program_id) { + AMS_ABORT("TODO"); + } + + Result OpenDataStorageByDataId(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, ncm::DataId data_id, u8 storage_id) { + ::FsStorage s; + R_TRY(fsOpenDataStorageByDataId(std::addressof(s), data_id.value, static_cast<::NcmStorageId>(storage_id))); + + out.SetValue(ObjectFactory::CreateSharedEmplaced<fssrv::sf::IStorage, fssrv::impl::RemoteStorage>(s)); + R_SUCCEED(); + } + Result OpenPatchDataStorageByCurrentProcess(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out) { + AMS_ABORT("TODO"); + } + + Result OpenDataFileSystemWithProgramIndex(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u8 index) { + AMS_ABORT("TODO"); + } + + Result OpenDataStorageWithProgramIndex(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, u8 index) { + AMS_ABORT("TODO"); + } + + Result OpenDataStorageByPathObsolete(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, const fssrv::sf::FspPath &path, u32 type) { + AMS_ABORT("TODO"); + } + + Result OpenDataStorageByPath(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, const fssrv::sf::FspPath &path, fs::ContentAttributes attr, u32 type) { + AMS_ABORT("TODO"); + } + + Result OpenDeviceOperator(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IDeviceOperator>> out) { + ::FsDeviceOperator d; + R_TRY(fsOpenDeviceOperator(std::addressof(d))); + + out.SetValue(ObjectFactory::CreateSharedEmplaced<fssrv::sf::IDeviceOperator, impl::RemoteDeviceOperator>(d)); + R_SUCCEED(); + } + + Result OpenSdCardDetectionEventNotifier(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IEventNotifier>> out) { + ::FsEventNotifier e; + R_TRY(fsOpenSdCardDetectionEventNotifier(std::addressof(e))); + + out.SetValue(ObjectFactory::CreateSharedEmplaced<fssrv::sf::IEventNotifier, impl::RemoteEventNotifier>(e)); + R_SUCCEED(); + } + + Result OpenGameCardDetectionEventNotifier(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IEventNotifier>> out) { + AMS_ABORT("TODO"); + } + + Result OpenSystemDataUpdateEventNotifier(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IEventNotifier>> out) { + AMS_ABORT("TODO"); + } + + Result NotifySystemDataUpdateEvent() { + AMS_ABORT("TODO"); + } + + /* ... */ + + Result SetCurrentPosixTime(s64 posix_time) { + AMS_ABORT("TODO"); + } + + /* ... */ + + Result GetRightsId(ams::sf::Out<fs::RightsId> out, ncm::ProgramId program_id, ncm::StorageId storage_id) { + AMS_ABORT("TODO"); + } + + Result RegisterExternalKey(const fs::RightsId &rights_id, const spl::AccessKey &access_key) { + AMS_ABORT("TODO"); + } + + Result UnregisterAllExternalKey() { + AMS_ABORT("TODO"); + } + + Result GetProgramId(ams::sf::Out<ncm::ProgramId> out, const fssrv::sf::FspPath &path, fs::ContentAttributes attr) { + static_assert(sizeof(ncm::ProgramId) == sizeof(u64)); + R_RETURN(fsGetProgramId(reinterpret_cast<u64 *>(out.GetPointer()), path.str, static_cast<::FsContentAttributes>(static_cast<u8>(attr)))); + } + + Result GetRightsIdByPath(ams::sf::Out<fs::RightsId> out, const fssrv::sf::FspPath &path) { + static_assert(sizeof(RightsId) == sizeof(::FsRightsId)); + R_RETURN(fsGetRightsIdByPath(path.str, reinterpret_cast<::FsRightsId *>(out.GetPointer()))); + } + + Result GetRightsIdAndKeyGenerationByPathObsolete(ams::sf::Out<fs::RightsId> out, ams::sf::Out<u8> out_key_generation, const fssrv::sf::FspPath &path) { + R_RETURN(this->GetRightsIdAndKeyGenerationByPath(out, out_key_generation, path, fs::ContentAttributes_None)) + } + + Result GetRightsIdAndKeyGenerationByPath(ams::sf::Out<fs::RightsId> out, ams::sf::Out<u8> out_key_generation, const fssrv::sf::FspPath &path, fs::ContentAttributes attr) { + static_assert(sizeof(RightsId) == sizeof(::FsRightsId)); + R_RETURN(fsGetRightsIdAndKeyGenerationByPath(path.str, static_cast<::FsContentAttributes>(static_cast<u8>(attr)), out_key_generation.GetPointer(), reinterpret_cast<::FsRightsId *>(out.GetPointer()))); + } + + Result SetCurrentPosixTimeWithTimeDifference(s64 posix_time, s32 time_difference) { + AMS_ABORT("TODO"); + } + + Result GetFreeSpaceSizeForSaveData(ams::sf::Out<s64> out, u8 space_id) { + AMS_ABORT("TODO"); + } + + Result VerifySaveDataFileSystemBySaveDataSpaceId() { + AMS_ABORT("TODO"); + } + + Result CorruptSaveDataFileSystemBySaveDataSpaceId() { + AMS_ABORT("TODO"); + } + + Result QuerySaveDataInternalStorageTotalSize() { + AMS_ABORT("TODO"); + } + + Result GetSaveDataCommitId() { + AMS_ABORT("TODO"); + } + + Result UnregisterExternalKey(const fs::RightsId &rights_id) { + AMS_ABORT("TODO"); + } + + Result SetSdCardEncryptionSeed(const fs::EncryptionSeed &seed) { + AMS_ABORT("TODO"); + } + + Result SetSdCardAccessibility(bool accessible) { + AMS_ABORT("TODO"); + } + + Result IsSdCardAccessible(ams::sf::Out<bool> out) { + AMS_ABORT("TODO"); + } + + Result IsSignedSystemPartitionOnSdCardValid(ams::sf::Out<bool> out) { + R_RETURN(fsIsSignedSystemPartitionOnSdCardValid(out.GetPointer())); + } + + Result OpenAccessFailureDetectionEventNotifier() { + AMS_ABORT("TODO"); + } + + /* ... */ + + Result GetAndClearErrorInfo(ams::sf::Out<fs::FileSystemProxyErrorInfo> out) { + static_assert(sizeof(fs::FileSystemProxyErrorInfo) == sizeof(::FsFileSystemProxyErrorInfo)); + R_RETURN(::fsGetAndClearErrorInfo(reinterpret_cast<::FsFileSystemProxyErrorInfo *>(out.GetPointer()))); + } + + Result RegisterProgramIndexMapInfo(const ams::sf::InBuffer &buffer, s32 count) { + AMS_ABORT("TODO"); + } + + Result SetBisRootForHost(u32 id, const fssrv::sf::FspPath &path) { + AMS_ABORT("TODO"); + } + + Result SetSaveDataSize(s64 size, s64 journal_size) { + AMS_ABORT("TODO"); + } + + Result SetSaveDataRootPath(const fssrv::sf::FspPath &path) { + AMS_ABORT("TODO"); + } + + Result DisableAutoSaveDataCreation() { + R_RETURN(::fsDisableAutoSaveDataCreation()); + } + + Result SetGlobalAccessLogMode(u32 mode) { + R_RETURN(::fsSetGlobalAccessLogMode(mode)); + } + + Result GetGlobalAccessLogMode(sf::Out<u32> out) { + R_RETURN(::fsGetGlobalAccessLogMode(out.GetPointer())); + } + + Result OutputAccessLogToSdCard(const ams::sf::InBuffer &buf) { + R_RETURN(::fsOutputAccessLogToSdCard(reinterpret_cast<const char *>(buf.GetPointer()), buf.GetSize())); + } + + Result RegisterUpdatePartition() { + AMS_ABORT("TODO"); + } + + Result OpenRegisteredUpdatePartition(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out) { + AMS_ABORT("TODO"); + } + + Result GetAndClearMemoryReportInfo(ams::sf::Out<fs::MemoryReportInfo> out) { + static_assert(sizeof(fs::MemoryReportInfo) == sizeof(::FsMemoryReportInfo)); + R_RETURN(::fsGetAndClearMemoryReportInfo(reinterpret_cast<::FsMemoryReportInfo *>(out.GetPointer()))); + } + + /* ... */ + + Result GetProgramIndexForAccessLog(ams::sf::Out<u32> out_idx, ams::sf::Out<u32> out_count) { + R_RETURN(::fsGetProgramIndexForAccessLog(out_idx.GetPointer(), out_count.GetPointer())); + } + + Result GetFsStackUsage(ams::sf::Out<u32> out, u32 type) { + AMS_ABORT("TODO"); + } + + Result UnsetSaveDataRootPath() { + AMS_ABORT("TODO"); + } + + Result OutputMultiProgramTagAccessLog() { + AMS_ABORT("TODO"); + } + + Result FlushAccessLogOnSdCard() { + AMS_ABORT("TODO"); + } + + Result OutputApplicationInfoAccessLog() { + AMS_ABORT("TODO"); + } + + Result RegisterDebugConfiguration(u32 key, s64 value) { + AMS_ABORT("TODO"); + } + + Result UnregisterDebugConfiguration(u32 key) { + AMS_ABORT("TODO"); + } + + Result OverrideSaveDataTransferTokenSignVerificationKey(const ams::sf::InBuffer &buf) { + AMS_ABORT("TODO"); + } + + Result CorruptSaveDataFileSystemByOffset(u8 space_id, u64 save_data_id, s64 offset) { + AMS_ABORT("TODO"); + } + + /* ... */ + }; + static_assert(fssrv::sf::IsIFileSystemProxy<RemoteFileSystemProxy>); + + #pragma GCC diagnostic pop + + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_remote_file_system_proxy_for_loader.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_remote_file_system_proxy_for_loader.hpp new file mode 100644 index 00000000..528b1a92 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_remote_file_system_proxy_for_loader.hpp @@ -0,0 +1,87 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include <stratosphere/fssrv/fssrv_interface_adapters.hpp> +#include "../fssrv/impl/fssrv_allocator_for_service_framework.hpp" + +namespace ams::fs { + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemoteFileSystemProxyForLoader { + NON_COPYABLE(RemoteFileSystemProxyForLoader); + NON_MOVEABLE(RemoteFileSystemProxyForLoader); + private: + using ObjectFactory = fssrv::impl::FileSystemObjectFactory; + public: + RemoteFileSystemProxyForLoader(); + ~RemoteFileSystemProxyForLoader(); + public: + Result OpenCodeFileSystemDeprecated(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, const fssrv::sf::Path &path, ncm::ProgramId program_id) { + ::FsCodeInfo dummy; + ::FsFileSystem fs; + R_TRY(fsldrOpenCodeFileSystem(std::addressof(dummy), program_id.value, ::NcmStorageId_None, path.str, static_cast<::FsContentAttributes>(static_cast<u8>(fs::ContentAttributes_None)), std::addressof(fs))); + + out_fs.SetValue(ObjectFactory::CreateSharedEmplaced<fssrv::sf::IFileSystem, fssrv::impl::RemoteFileSystem>(fs)); + R_SUCCEED(); + } + + Result OpenCodeFileSystemDeprecated2(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, ams::sf::Out<fs::CodeVerificationData> out_verif, const fssrv::sf::Path &path, ncm::ProgramId program_id) { + ::FsFileSystem fs; + R_TRY(fsldrOpenCodeFileSystem(reinterpret_cast<::FsCodeInfo *>(out_verif.GetPointer()), program_id.value, ::NcmStorageId_None, path.str, static_cast<::FsContentAttributes>(static_cast<u8>(fs::ContentAttributes_None)), std::addressof(fs))); + + out_fs.SetValue(ObjectFactory::CreateSharedEmplaced<fssrv::sf::IFileSystem, fssrv::impl::RemoteFileSystem>(fs)); + R_SUCCEED(); + } + + Result OpenCodeFileSystemDeprecated3(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, ams::sf::Out<fs::CodeVerificationData> out_verif, const fssrv::sf::Path &path, fs::ContentAttributes attr, ncm::ProgramId program_id) { + ::FsFileSystem fs; + R_TRY(fsldrOpenCodeFileSystem(reinterpret_cast<::FsCodeInfo *>(out_verif.GetPointer()), program_id.value, ::NcmStorageId_None, path.str, static_cast<::FsContentAttributes>(static_cast<u8>(attr)), std::addressof(fs))); + + out_fs.SetValue(ObjectFactory::CreateSharedEmplaced<fssrv::sf::IFileSystem, fssrv::impl::RemoteFileSystem>(fs)); + R_SUCCEED(); + } + + Result OpenCodeFileSystemDeprecated4(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, const ams::sf::OutBuffer &out_verif, const fssrv::sf::Path &path, fs::ContentAttributes attr, ncm::ProgramId program_id) { + ::FsFileSystem fs; + R_TRY(fsldrOpenCodeFileSystem(reinterpret_cast<::FsCodeInfo *>(out_verif.GetPointer()), program_id.value, ::NcmStorageId_None, path.str, static_cast<::FsContentAttributes>(static_cast<u8>(attr)), std::addressof(fs))); + + out_fs.SetValue(ObjectFactory::CreateSharedEmplaced<fssrv::sf::IFileSystem, fssrv::impl::RemoteFileSystem>(fs)); + R_SUCCEED(); + } + + Result OpenCodeFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, const ams::sf::OutBuffer &out_verif, fs::ContentAttributes attr, ncm::ProgramId program_id, ncm::StorageId storage_id) { + ::FsFileSystem fs; + R_TRY(fsldrOpenCodeFileSystem(reinterpret_cast<::FsCodeInfo *>(out_verif.GetPointer()), program_id.value, static_cast<::NcmStorageId>(static_cast<u8>(storage_id)), nullptr, static_cast<::FsContentAttributes>(static_cast<u8>(attr)), std::addressof(fs))); + + out_fs.SetValue(ObjectFactory::CreateSharedEmplaced<fssrv::sf::IFileSystem, fssrv::impl::RemoteFileSystem>(fs)); + R_SUCCEED(); + } + + Result IsArchivedProgram(ams::sf::Out<bool> out, u64 process_id) { + R_RETURN(fsldrIsArchivedProgram(process_id, out.GetPointer())); + } + + Result SetCurrentProcess(const ams::sf::ClientProcessId &client_pid) { + /* Libnx does this for us automatically. */ + AMS_UNUSED(client_pid); + R_SUCCEED(); + } + }; + static_assert(fssrv::sf::IsIFileSystemProxyForLoader<RemoteFileSystemProxyForLoader>); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_result_utils.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_result_utils.cpp new file mode 100644 index 00000000..94e7da6e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_result_utils.cpp @@ -0,0 +1,71 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fs { + + namespace { + + constinit bool g_handled_by_application = false; + + } + + void SetResultHandledByApplication(bool application) { + g_handled_by_application = application; + } + + namespace impl { + + bool IsAbortNeeded(Result result) { + /* If the result succeeded, we never need to abort. */ + if (R_SUCCEEDED(result)) { + return false; + } + + /* Get the abort specifier from current context. */ + switch (GetCurrentThreadFsContext()->HandleResult(result)) { + case AbortSpecifier::Default: + if (g_handled_by_application) { + return !fs::ResultHandledByAllProcess::Includes(result); + } else { + return !(fs::ResultHandledByAllProcess::Includes(result) || fs::ResultHandledBySystemProcess::Includes(result)); + } + case AbortSpecifier::Abort: + return true; + case AbortSpecifier::Return: + return false; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void LogResultErrorMessage(Result result) { + /* TODO: log specific results */ + AMS_UNUSED(result); + } + + void LogErrorMessage(Result result, const char *function) { + /* If the result succeeded, there's nothing to log. */ + if (R_SUCCEEDED(result)) { + return; + } + + /* TODO: Actually log stuff. */ + AMS_UNUSED(function); + } + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_rights_id.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_rights_id.cpp new file mode 100644 index 00000000..0de77322 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_rights_id.cpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <stratosphere/fs/fs_rights_id.hpp> +#include "impl/fs_file_system_proxy_service_object.hpp" + +namespace ams::fs { + + Result GetRightsId(RightsId *out, const char *path, fs::ContentAttributes attr) { + /* If possible, prefer the non-removed functionality. */ + if (hos::GetVersion() >= hos::Version_3_0_0) { + u8 dummy_key_generation; + R_RETURN(GetRightsId(out, std::addressof(dummy_key_generation), path, attr)); + } + + AMS_FS_R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + AMS_FS_R_UNLESS(path != nullptr, fs::ResultNullptrArgument()); + + /* Convert the path for fsp. */ + fssrv::sf::FspPath sf_path; + R_TRY(fs::ConvertToFspPath(std::addressof(sf_path), path)); + + auto fsp = impl::GetFileSystemProxyServiceObject(); + AMS_FS_R_TRY(fsp->GetRightsIdByPath(out, sf_path)); + + R_SUCCEED(); + } + + Result GetRightsId(RightsId *out, u8 *out_key_generation, const char *path, fs::ContentAttributes attr) { + AMS_FS_R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + AMS_FS_R_UNLESS(out_key_generation != nullptr, fs::ResultNullptrArgument()); + AMS_FS_R_UNLESS(path != nullptr, fs::ResultNullptrArgument()); + + /* Convert the path for fsp. */ + fssrv::sf::FspPath sf_path; + R_TRY(fs::ConvertToFspPath(std::addressof(sf_path), path)); + + auto fsp = impl::GetFileSystemProxyServiceObject(); + AMS_FS_R_TRY(fsp->GetRightsIdAndKeyGenerationByPath(out, out_key_generation, sf_path, attr)); + + R_SUCCEED(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_romfs_filesystem.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_romfs_filesystem.cpp new file mode 100644 index 00000000..02845fdf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_romfs_filesystem.cpp @@ -0,0 +1,558 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fs { + + namespace { + + Result ConvertNcaCorruptedResult(Result res) { + AMS_ASSERT(fs::ResultNcaCorrupted::Includes(res)); + + R_TRY_CATCH(res) { + R_CONVERT(fs::ResultInvalidNcaFileSystemType, fs::ResultInvalidRomNcaFileSystemType()) + R_CONVERT(fs::ResultInvalidAcidFileSize, fs::ResultInvalidRomAcidFileSize()) + R_CONVERT(fs::ResultInvalidAcidSize, fs::ResultInvalidRomAcidSize()) + R_CONVERT(fs::ResultInvalidAcid, fs::ResultInvalidRomAcid()) + R_CONVERT(fs::ResultAcidVerificationFailed, fs::ResultRomAcidVerificationFailed()) + R_CONVERT(fs::ResultInvalidNcaSignature, fs::ResultInvalidRomNcaSignature()) + R_CONVERT(fs::ResultNcaHeaderSignature1VerificationFailed, fs::ResultRomNcaHeaderSignature1VerificationFailed()) + R_CONVERT(fs::ResultNcaHeaderSignature2VerificationFailed, fs::ResultRomNcaHeaderSignature2VerificationFailed()) + R_CONVERT(fs::ResultNcaFsHeaderHashVerificationFailed, fs::ResultRomNcaFsHeaderHashVerificationFailed()) + R_CONVERT(fs::ResultInvalidNcaKeyIndex, fs::ResultInvalidRomNcaKeyIndex()) + R_CONVERT(fs::ResultInvalidNcaFsHeaderHashType, fs::ResultInvalidRomNcaFsHeaderHashType()) + R_CONVERT(fs::ResultInvalidNcaFsHeaderEncryptionType, fs::ResultInvalidRomNcaFsHeaderEncryptionType()) + R_CONVERT(fs::ResultInvalidHierarchicalSha256BlockSize, fs::ResultInvalidRomHierarchicalSha256BlockSize()) + R_CONVERT(fs::ResultInvalidHierarchicalSha256LayerCount, fs::ResultInvalidRomHierarchicalSha256LayerCount()) + R_CONVERT(fs::ResultHierarchicalSha256BaseStorageTooLarge, fs::ResultRomHierarchicalSha256BaseStorageTooLarge()) + R_CONVERT(fs::ResultHierarchicalSha256HashVerificationFailed, fs::ResultRomHierarchicalSha256HashVerificationFailed()) + R_CATCH_ALL() { /* ... */ } + } R_END_TRY_CATCH; + + AMS_ASSERT(false); + R_THROW(fs::ResultNcaCorrupted()); + } + + Result ConvertIntegrityVerificationStorageCorruptedResult(Result res) { + AMS_ASSERT(fs::ResultIntegrityVerificationStorageCorrupted::Includes(res)); + + R_TRY_CATCH(res) { + R_CONVERT(fs::ResultIncorrectIntegrityVerificationMagic, fs::ResultIncorrectRomIntegrityVerificationMagic()) + R_CONVERT(fs::ResultInvalidZeroHash, fs::ResultInvalidRomZeroHash()) + R_CONVERT(fs::ResultNonRealDataVerificationFailed, fs::ResultRomNonRealDataVerificationFailed()) + R_CONVERT(fs::ResultInvalidHierarchicalIntegrityVerificationLayerCount, fs::ResultInvalidRomHierarchicalIntegrityVerificationLayerCount()) + R_CONVERT(fs::ResultClearedRealDataVerificationFailed, fs::ResultClearedRomRealDataVerificationFailed()) + R_CONVERT(fs::ResultUnclearedRealDataVerificationFailed, fs::ResultUnclearedRomRealDataVerificationFailed()) + R_CATCH_ALL() { /* ... */ } + } R_END_TRY_CATCH; + + AMS_ASSERT(false); + R_THROW(fs::ResultIntegrityVerificationStorageCorrupted()); + } + + Result ConvertBuiltInStorageCorruptedResult(Result res) { + AMS_ASSERT(fs::ResultBuiltInStorageCorrupted::Includes(res)); + + R_TRY_CATCH(res) { + R_CONVERT(fs::ResultGptHeaderVerificationFailed, fs::ResultRomGptHeaderVerificationFailed()) + R_CATCH_ALL() { /* ... */ } + } R_END_TRY_CATCH; + + AMS_ASSERT(false); + R_THROW(fs::ResultBuiltInStorageCorrupted()); + } + + Result ConvertPartitionFileSystemCorruptedResult(Result res) { + AMS_ASSERT(fs::ResultPartitionFileSystemCorrupted::Includes(res)); + + R_TRY_CATCH(res) { + R_CONVERT(fs::ResultInvalidSha256PartitionHashTarget, fs::ResultInvalidRomSha256PartitionHashTarget()) + R_CONVERT(fs::ResultSha256PartitionHashVerificationFailed, fs::ResultRomSha256PartitionHashVerificationFailed()) + R_CONVERT(fs::ResultPartitionSignatureVerificationFailed, fs::ResultRomPartitionSignatureVerificationFailed()) + R_CONVERT(fs::ResultSha256PartitionSignatureVerificationFailed, fs::ResultRomSha256PartitionSignatureVerificationFailed()) + R_CONVERT(fs::ResultInvalidPartitionEntryOffset, fs::ResultInvalidRomPartitionEntryOffset()) + R_CONVERT(fs::ResultInvalidSha256PartitionMetaDataSize, fs::ResultInvalidRomSha256PartitionMetaDataSize()) + R_CATCH_ALL() { /* ... */ } + } R_END_TRY_CATCH; + + AMS_ASSERT(false); + R_THROW(fs::ResultPartitionFileSystemCorrupted()); + } + + Result ConvertFatFileSystemCorruptedResult(Result res) { + AMS_ASSERT(fs::ResultFatFileSystemCorrupted::Includes(res)); + + R_RETURN(res); + } + + Result ConvertHostFileSystemCorruptedResult(Result res) { + AMS_ASSERT(fs::ResultHostFileSystemCorrupted::Includes(res)); + + R_TRY_CATCH(res) { + R_CONVERT(fs::ResultHostEntryCorrupted, fs::ResultRomHostEntryCorrupted()) + R_CONVERT(fs::ResultHostFileDataCorrupted, fs::ResultRomHostFileDataCorrupted()) + R_CONVERT(fs::ResultHostFileCorrupted, fs::ResultRomHostFileCorrupted()) + R_CONVERT(fs::ResultInvalidHostHandle, fs::ResultInvalidRomHostHandle()) + R_CATCH_ALL() { /* ... */ } + } R_END_TRY_CATCH; + + AMS_ASSERT(false); + R_THROW(fs::ResultHostFileSystemCorrupted()); + } + + Result ConvertDatabaseCorruptedResult(Result res) { + AMS_ASSERT(fs::ResultDatabaseCorrupted::Includes(res)); + + R_TRY_CATCH(res) { + R_CONVERT(fs::ResultInvalidAllocationTableBlock, fs::ResultInvalidRomAllocationTableBlock()) + R_CONVERT(fs::ResultInvalidKeyValueListElementIndex, fs::ResultInvalidRomKeyValueListElementIndex()) + R_CATCH_ALL() { /* ... */ } + } R_END_TRY_CATCH; + + AMS_ASSERT(false); + R_THROW(fs::ResultDatabaseCorrupted()); + } + + Result ConvertRomFsResult(Result res) { + R_TRY_CATCH(res) { + R_CONVERT(fs::ResultUnsupportedVersion, fs::ResultUnsupportedRomVersion()) + R_CONVERT(fs::ResultNcaCorrupted, ConvertNcaCorruptedResult(res)) + R_CONVERT(fs::ResultIntegrityVerificationStorageCorrupted, ConvertIntegrityVerificationStorageCorruptedResult(res)) + R_CONVERT(fs::ResultBuiltInStorageCorrupted, ConvertBuiltInStorageCorruptedResult(res)) + R_CONVERT(fs::ResultPartitionFileSystemCorrupted, ConvertPartitionFileSystemCorruptedResult(res)) + R_CONVERT(fs::ResultFatFileSystemCorrupted, ConvertFatFileSystemCorruptedResult(res)) + R_CONVERT(fs::ResultHostFileSystemCorrupted, ConvertHostFileSystemCorruptedResult(res)) + R_CONVERT(fs::ResultDatabaseCorrupted, ConvertDatabaseCorruptedResult(res)) + R_CONVERT(fs::ResultNotFound, fs::ResultPathNotFound()) + R_CONVERT(fs::ResultPermissionDenied, fs::ResultTargetLocked()) + R_CONVERT(fs::ResultIncompatiblePath, fs::ResultPathNotFound()) + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + Result ReadFile(IStorage *storage, s64 offset, void *buffer, size_t size) { + AMS_ASSERT(storage != nullptr); + AMS_ASSERT(offset >= 0); + AMS_ASSERT(buffer != nullptr || size == 0); + + R_RETURN(ConvertRomFsResult(storage->Read(offset, buffer, size))); + } + + Result ReadFileHeader(IStorage *storage, RomFileSystemInformation *out) { + AMS_ASSERT(storage != nullptr); + AMS_ASSERT(out != nullptr); + + R_RETURN(ReadFile(storage, 0, out, sizeof(*out))); + } + + constexpr size_t CalculateRequiredWorkingMemorySize(const RomFileSystemInformation &header) { + const size_t needed_size = header.directory_bucket_size + header.directory_entry_size + header.file_bucket_size + header.file_entry_size; + return util::AlignUp(needed_size, 8); + } + + class RomFsFile : public fsa::IFile, public impl::Newable { + private: + RomFsFileSystem *m_parent; + s64 m_start; + s64 m_end; + public: + RomFsFile(RomFsFileSystem *p, s64 s, s64 e) : m_parent(p), m_start(s), m_end(e) { /* ... */ } + virtual ~RomFsFile() { /* ... */ } + + Result VerifyArguments(size_t *out, s64 offset, void *buf, size_t size, const fs::ReadOption &option) { + R_TRY(DryRead(out, offset, size, option, fs::OpenMode_Read)); + + AMS_ASSERT(this->GetStorage() != nullptr); + AMS_ASSERT(offset >= 0); + AMS_ASSERT(buf != nullptr || size == 0); + AMS_UNUSED(buf); + + R_SUCCEED(); + } + + Result ConvertResult(Result res) const { + R_RETURN(ConvertRomFsResult(res)); + } + + s64 GetOffset() const { + return m_start; + } + + s64 GetSize() const { + return m_end - m_start; + } + + IStorage *GetStorage() { + return m_parent->GetBaseStorage(); + } + public: + virtual Result DoRead(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override { + size_t read_size = 0; + R_TRY(this->VerifyArguments(std::addressof(read_size), offset, buffer, size, option)); + + R_TRY(this->ConvertResult(this->GetStorage()->Read(offset + m_start, buffer, size))); + *out = read_size; + + R_SUCCEED(); + } + + virtual Result DoGetSize(s64 *out) override { + *out = this->GetSize(); + R_SUCCEED(); + } + + virtual Result DoFlush() override { + R_SUCCEED(); + } + + virtual Result DoWrite(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override { + AMS_UNUSED(offset, buffer, size, option); + R_THROW(fs::ResultUnsupportedWriteForRomFsFile()); + } + + virtual Result DoSetSize(s64 size) override { + AMS_UNUSED(size); + R_THROW(fs::ResultUnsupportedWriteForRomFsFile()); + } + + virtual Result DoOperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + switch (op_id) { + case OperationId::Invalidate: + R_RETURN(this->GetStorage()->OperateRange(fs::OperationId::Invalidate, 0, std::numeric_limits<s64>::max())); + case OperationId::QueryRange: + { + R_UNLESS(offset >= 0, fs::ResultInvalidOffset()); + R_UNLESS(this->GetSize() >= offset, fs::ResultOutOfRange()); + + auto operate_size = size; + if (offset + operate_size > this->GetSize() || offset + operate_size < offset) { + operate_size = this->GetSize() - offset; + } + + R_RETURN(this->GetStorage()->OperateRange(dst, dst_size, op_id, m_start + offset, operate_size, src, src_size)); + } + default: + R_THROW(fs::ResultUnsupportedOperateRangeForRomFsFile()); + } + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override { + AMS_ABORT(); + } + }; + + class RomFsDirectory : public fsa::IDirectory, public impl::Newable { + private: + using FindPosition = RomFsFileSystem::RomFileTable::FindPosition; + private: + RomFsFileSystem *m_parent; + FindPosition m_current_find; + FindPosition m_first_find; + fs::OpenDirectoryMode m_mode; + public: + RomFsDirectory(RomFsFileSystem *p, const FindPosition &f, fs::OpenDirectoryMode m) : m_parent(p), m_current_find(f), m_first_find(f), m_mode(m) { /* ... */ } + virtual ~RomFsDirectory() override { /* ... */ } + public: + virtual Result DoRead(s64 *out_count, DirectoryEntry *out_entries, s64 max_entries) override { + R_RETURN(this->ReadInternal(out_count, std::addressof(m_current_find), out_entries, max_entries)); + } + + virtual Result DoGetEntryCount(s64 *out) override { + FindPosition find = m_first_find; + R_RETURN(this->ReadInternal(out, std::addressof(find), nullptr, 0)); + } + private: + Result ReadInternal(s64 *out_count, FindPosition *find, DirectoryEntry *out_entries, s64 max_entries) { + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(find != nullptr); + + constexpr size_t NameBufferSize = fs::EntryNameLengthMax + 1; + char *name_buf = static_cast<char *>(::ams::fs::impl::Allocate(NameBufferSize)); + R_UNLESS(name_buf != nullptr, fs::ResultAllocationMemoryFailedInRomFsFileSystemE()); + ON_SCOPE_EXIT { ::ams::fs::impl::Deallocate(name_buf, NameBufferSize); }; + + s32 i = 0; + + if (m_mode & fs::OpenDirectoryMode_Directory) { + while (i < max_entries || out_entries == nullptr) { + R_TRY_CATCH(m_parent->GetRomFileTable()->FindNextDirectory(name_buf, find, NameBufferSize)) { + R_CATCH(fs::ResultDbmFindFinished) { break; } + } R_END_TRY_CATCH; + + if (out_entries) { + const size_t name_len = util::Strnlen(name_buf, NameBufferSize); + R_UNLESS(name_len < NameBufferSize, fs::ResultTooLongPath()); + std::memcpy(out_entries[i].name, name_buf, name_len); + out_entries[i].name[name_len] = '\x00'; + out_entries[i].type = fs::DirectoryEntryType_Directory; + out_entries[i].file_size = 0; + } + + i++; + } + } + + if (m_mode & fs::OpenDirectoryMode_File) { + while (i < max_entries || out_entries == nullptr) { + auto file_pos = find->next_file; + + R_TRY_CATCH(m_parent->GetRomFileTable()->FindNextFile(name_buf, find, NameBufferSize)) { + R_CATCH(fs::ResultDbmFindFinished) { break; } + } R_END_TRY_CATCH; + + if (out_entries) { + const size_t name_len = util::Strnlen(name_buf, NameBufferSize); + R_UNLESS(name_len < NameBufferSize, fs::ResultTooLongPath()); + std::memcpy(out_entries[i].name, name_buf, name_len); + out_entries[i].name[name_len] = '\x00'; + out_entries[i].type = fs::DirectoryEntryType_File; + + RomFsFileSystem::RomFileTable::FileInfo file_info; + R_TRY(m_parent->GetRomFileTable()->OpenFile(std::addressof(file_info), m_parent->GetRomFileTable()->PositionToFileId(file_pos))); + out_entries[i].file_size = file_info.size.Get(); + } + + i++; + } + } + + *out_count = i; + R_SUCCEED(); + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override { + AMS_ABORT(); + } + }; + + } + + + RomFsFileSystem::RomFsFileSystem() : m_base_storage() { + /* ... */ + } + + RomFsFileSystem::~RomFsFileSystem() { + /* ... */ + } + + Result RomFsFileSystem::GetRequiredWorkingMemorySize(size_t *out, IStorage *storage) { + RomFileSystemInformation header; + R_TRY(ReadFileHeader(storage, std::addressof(header))); + + *out = CalculateRequiredWorkingMemorySize(header); + R_SUCCEED(); + } + + Result RomFsFileSystem::Initialize(IStorage *base, void *work, size_t work_size, bool use_cache) { + AMS_ABORT_UNLESS(!use_cache || work != nullptr); + AMS_ABORT_UNLESS(base != nullptr); + + /* Read the header. */ + RomFileSystemInformation header; + R_TRY(ReadFileHeader(base, std::addressof(header))); + + /* Set up our storages. */ + if (use_cache) { + const size_t needed_size = CalculateRequiredWorkingMemorySize(header); + R_UNLESS(work_size >= needed_size, fs::ResultPreconditionViolation()); + + u8 *buf = static_cast<u8 *>(work); + auto dir_bucket_buf = buf; buf += header.directory_bucket_size; + auto dir_entry_buf = buf; buf += header.directory_entry_size; + auto file_bucket_buf = buf; buf += header.file_bucket_size; + auto file_entry_buf = buf; buf += header.file_entry_size; + + R_TRY(ReadFile(base, header.directory_bucket_offset, dir_bucket_buf, header.directory_bucket_size)); + R_TRY(ReadFile(base, header.directory_entry_offset, dir_entry_buf, header.directory_entry_size)); + R_TRY(ReadFile(base, header.file_bucket_offset, file_bucket_buf, header.file_bucket_size)); + R_TRY(ReadFile(base, header.file_entry_offset, file_entry_buf, header.file_entry_size)); + + m_dir_bucket_storage.reset(new MemoryStorage(dir_bucket_buf, header.directory_bucket_size)); + m_dir_entry_storage.reset(new MemoryStorage(dir_entry_buf, header.directory_entry_size)); + m_file_bucket_storage.reset(new MemoryStorage(file_bucket_buf, header.file_bucket_size)); + m_file_entry_storage.reset(new MemoryStorage(file_entry_buf, header.file_entry_size)); + } else { + m_dir_bucket_storage.reset(new SubStorage(base, header.directory_bucket_offset, header.directory_bucket_size)); + m_dir_entry_storage.reset(new SubStorage(base, header.directory_entry_offset, header.directory_entry_size)); + m_file_bucket_storage.reset(new SubStorage(base, header.file_bucket_offset, header.file_bucket_size)); + m_file_entry_storage.reset(new SubStorage(base, header.file_entry_offset, header.file_entry_size)); + } + + /* Ensure we allocated storages successfully. */ + R_UNLESS(m_dir_bucket_storage != nullptr, fs::ResultAllocationMemoryFailedInRomFsFileSystemA()); + R_UNLESS(m_dir_entry_storage != nullptr, fs::ResultAllocationMemoryFailedInRomFsFileSystemA()); + R_UNLESS(m_file_bucket_storage != nullptr, fs::ResultAllocationMemoryFailedInRomFsFileSystemA()); + R_UNLESS(m_file_entry_storage != nullptr, fs::ResultAllocationMemoryFailedInRomFsFileSystemA()); + + /* Initialize the rom table. */ + { + + SubStorage db(m_dir_bucket_storage.get(), 0, header.directory_bucket_size); + SubStorage de(m_dir_entry_storage.get(), 0, header.directory_entry_size); + SubStorage fb(m_file_bucket_storage.get(), 0, header.file_bucket_size); + SubStorage fe(m_file_entry_storage.get(), 0, header.file_entry_size); + R_TRY(m_rom_file_table.Initialize(db, de, fb, fe)); + } + + /* Set members. */ + m_entry_size = header.body_offset; + m_base_storage = base; + R_SUCCEED(); + } + + Result RomFsFileSystem::Initialize(std::unique_ptr<IStorage>&& base, void *work, size_t work_size, bool use_cache) { + m_unique_storage = std::move(base); + R_RETURN(this->Initialize(m_unique_storage.get(), work, work_size, use_cache)); + } + + Result RomFsFileSystem::GetFileInfo(RomFileTable::FileInfo *out, const char *path) { + R_TRY_CATCH(m_rom_file_table.OpenFile(out, path)) { + R_CONVERT(fs::ResultDbmNotFound, fs::ResultPathNotFound()); + R_CONVERT(fs::ResultDbmInvalidOperation, fs::ResultPathNotFound()); + } R_END_TRY_CATCH; + R_SUCCEED(); + } + + IStorage *RomFsFileSystem::GetBaseStorage() { + return m_base_storage; + } + + RomFsFileSystem::RomFileTable *RomFsFileSystem::GetRomFileTable() { + return std::addressof(m_rom_file_table); + } + + Result RomFsFileSystem::GetFileBaseOffset(s64 *out, const char *path) { + AMS_ABORT_UNLESS(out != nullptr); + AMS_ABORT_UNLESS(path != nullptr); + + RomFileTable::FileInfo info; + R_TRY(this->GetFileInfo(std::addressof(info), path)); + *out = m_entry_size + info.offset.Get(); + R_SUCCEED(); + } + + Result RomFsFileSystem::DoCreateFile(const fs::Path &path, s64 size, int flags) { + AMS_UNUSED(path, size, flags); + R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem()); + } + + Result RomFsFileSystem::DoDeleteFile(const fs::Path &path) { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem()); + } + + Result RomFsFileSystem::DoCreateDirectory(const fs::Path &path) { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem()); + } + + Result RomFsFileSystem::DoDeleteDirectory(const fs::Path &path) { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem()); + } + + Result RomFsFileSystem::DoDeleteDirectoryRecursively(const fs::Path &path) { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem()); + } + + Result RomFsFileSystem::DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) { + AMS_UNUSED(old_path, new_path); + R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem()); + } + + Result RomFsFileSystem::DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) { + AMS_UNUSED(old_path, new_path); + R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem()); + } + + Result RomFsFileSystem::DoGetEntryType(fs::DirectoryEntryType *out, const fs::Path &path) { + HierarchicalRomFileTable::FindPosition find_pos; + R_TRY_CATCH(m_rom_file_table.FindOpen(std::addressof(find_pos), path.GetString())) { + R_CONVERT(fs::ResultDbmNotFound, fs::ResultPathNotFound()) + R_CATCH(fs::ResultDbmInvalidOperation) { + *out = fs::DirectoryEntryType_File; + R_SUCCEED(); + } + } R_END_TRY_CATCH; + + *out = fs::DirectoryEntryType_Directory; + R_SUCCEED(); + } + + Result RomFsFileSystem::DoOpenFile(std::unique_ptr<fs::fsa::IFile> *out_file, const fs::Path &path, fs::OpenMode mode) { + AMS_ASSERT(out_file != nullptr); + + R_UNLESS((mode & fs::OpenMode_All) == fs::OpenMode_Read, fs::ResultInvalidOpenMode()); + + RomFileTable::FileInfo file_info{}; + R_TRY(this->GetFileInfo(std::addressof(file_info), path.GetString())); + + auto file = std::make_unique<RomFsFile>(this, m_entry_size + file_info.offset.Get(), m_entry_size + file_info.offset.Get() + file_info.size.Get()); + R_UNLESS(file != nullptr, fs::ResultAllocationMemoryFailedInRomFsFileSystemB()); + + *out_file = std::move(file); + R_SUCCEED(); + } + + Result RomFsFileSystem::DoOpenDirectory(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const fs::Path &path, fs::OpenDirectoryMode mode) { + AMS_ASSERT(out_dir != nullptr); + + RomFileTable::FindPosition find; + R_TRY_CATCH(m_rom_file_table.FindOpen(std::addressof(find), path.GetString())) { + R_CONVERT(fs::ResultDbmNotFound, fs::ResultPathNotFound()) + R_CONVERT(fs::ResultDbmInvalidOperation, fs::ResultPathNotFound()) + } R_END_TRY_CATCH; + + auto dir = std::make_unique<RomFsDirectory>(this, find, mode); + R_UNLESS(dir != nullptr, fs::ResultAllocationMemoryFailedInRomFsFileSystemC()); + + *out_dir = std::move(dir); + R_SUCCEED(); + } + + Result RomFsFileSystem::DoCommit() { + R_SUCCEED(); + } + + Result RomFsFileSystem::DoGetFreeSpaceSize(s64 *out, const fs::Path &path) { + AMS_UNUSED(path); + + *out = 0; + R_SUCCEED(); + } + + Result RomFsFileSystem::DoGetTotalSpaceSize(s64 *out, const fs::Path &path) { + AMS_UNUSED(out, path); + R_THROW(fs::ResultUnsupportedGetTotalSpaceSizeForRomFsFileSystem()); + } + + Result RomFsFileSystem::DoCleanDirectoryRecursively(const fs::Path &path) { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem()); + } + + Result RomFsFileSystem::DoCommitProvisionally(s64 counter) { + AMS_UNUSED(counter); + R_THROW(fs::ResultUnsupportedCommitProvisionallyForRomFsFileSystem()); + } + + Result RomFsFileSystem::DoRollback() { + R_SUCCEED(); + } +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_save_data_management.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_save_data_management.cpp new file mode 100644 index 00000000..0e9bdeb1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_save_data_management.cpp @@ -0,0 +1,171 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsa/fs_mount_utils.hpp" +#include "impl/fs_file_system_proxy_service_object.hpp" + +namespace ams::fs { + + namespace impl { + + Result ReadSaveDataFileSystemExtraData(SaveDataExtraData *out, SaveDataId id) { + auto fsp = impl::GetFileSystemProxyServiceObject(); + AMS_FS_R_TRY(fsp->ReadSaveDataFileSystemExtraData(sf::OutBuffer(out, sizeof(*out)), id)); + R_SUCCEED(); + } + + Result ReadSaveDataFileSystemExtraData(SaveDataExtraData *out, SaveDataSpaceId space_id, SaveDataId id) { + auto fsp = impl::GetFileSystemProxyServiceObject(); + AMS_FS_R_TRY(fsp->ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(sf::OutBuffer(out, sizeof(*out)), static_cast<u8>(space_id), id)); + R_SUCCEED(); + } + + Result WriteSaveDataFileSystemExtraData(SaveDataSpaceId space_id, SaveDataId id, const SaveDataExtraData &extra_data) { + auto fsp = impl::GetFileSystemProxyServiceObject(); + AMS_FS_R_TRY(fsp->WriteSaveDataFileSystemExtraData(id, static_cast<u8>(space_id), sf::InBuffer(std::addressof(extra_data), sizeof(extra_data)))); + R_SUCCEED(); + } + + } + + void DisableAutoSaveDataCreation() { + auto fsp = impl::GetFileSystemProxyServiceObject(); + AMS_FS_R_ABORT_UNLESS(fsp->DisableAutoSaveDataCreation()); + } + + Result CreateSystemSaveData(SaveDataSpaceId space_id, SystemSaveDataId save_id, UserId user_id, u64 owner_id, s64 size, s64 journal_size, u32 flags) { + auto create_impl = [=]() -> Result { + const auto attribute = SaveDataAttribute::Make(ncm::InvalidProgramId, SaveDataType::System, user_id, save_id); + const SaveDataCreationInfo info = { + .size = size, + .journal_size = journal_size, + .block_size = DefaultSaveDataBlockSize, + .owner_id = owner_id, + .flags = flags, + .space_id = space_id, + .pseudo = false, + }; + + auto fsp = impl::GetFileSystemProxyServiceObject(); + R_RETURN(fsp->CreateSaveDataFileSystemBySystemSaveDataId(attribute, info)); + }; + + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM(create_impl(), nullptr, AMS_FS_IMPL_ACCESS_LOG_FORMAT_CREATE_SYSTEM_SAVE_DATA(space_id, save_id, user_id, owner_id, size, journal_size, flags))); + R_SUCCEED(); + } + + Result CreateSystemSaveData(SystemSaveDataId save_id, s64 size, s64 journal_size, u32 flags) { + R_RETURN(CreateSystemSaveData(SaveDataSpaceId::System, save_id, InvalidUserId, 0, size, journal_size, flags)); + } + + Result CreateSystemSaveData(SystemSaveDataId save_id, u64 owner_id, s64 size, s64 journal_size, u32 flags) { + R_RETURN(CreateSystemSaveData(SaveDataSpaceId::System, save_id, InvalidUserId, owner_id, size, journal_size, flags)); + } + + Result CreateSystemSaveData(SaveDataSpaceId space_id, SystemSaveDataId save_id, u64 owner_id, s64 size, s64 journal_size, u32 flags) { + R_RETURN(CreateSystemSaveData(space_id, save_id, InvalidUserId, owner_id, size, journal_size, flags)); + } + + Result CreateSystemSaveData(SystemSaveDataId save_id, UserId user_id, s64 size, s64 journal_size, u32 flags) { + R_RETURN(CreateSystemSaveData(SaveDataSpaceId::System, save_id, user_id, 0, size, journal_size, flags)); + } + + Result CreateSystemSaveData(SystemSaveDataId save_id, UserId user_id, u64 owner_id, s64 size, s64 journal_size, u32 flags) { + R_RETURN(CreateSystemSaveData(SaveDataSpaceId::System, save_id, user_id, owner_id, size, journal_size, flags)); + } + + Result DeleteSaveData(SaveDataId id) { + auto delete_impl = [=]() -> Result { + auto fsp = impl::GetFileSystemProxyServiceObject(); + R_RETURN(fsp->DeleteSaveDataFileSystem(id)); + }; + + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM(delete_impl(), nullptr, AMS_FS_IMPL_ACCESS_LOG_FORMAT_SAVE_DATA_ID, id)); + R_SUCCEED(); + } + + Result DeleteSaveData(SaveDataSpaceId space_id, SaveDataId id) { + auto delete_impl = [=]() -> Result { + auto fsp = impl::GetFileSystemProxyServiceObject(); + R_RETURN(fsp->DeleteSaveDataFileSystemBySaveDataSpaceId(static_cast<u8>(space_id), id)); + }; + + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM(delete_impl(), nullptr, AMS_FS_IMPL_ACCESS_LOG_FORMAT_DELETE_SAVE_DATA(space_id, id))); + R_SUCCEED(); + } + + Result DeleteSystemSaveData(SaveDataSpaceId space_id, SystemSaveDataId id, UserId user_id) { + auto delete_impl = [=]() -> Result { + const auto attribute = SaveDataAttribute::Make(ncm::InvalidProgramId, SaveDataType::System, user_id, id); + + auto fsp = impl::GetFileSystemProxyServiceObject(); + R_RETURN(fsp->DeleteSaveDataFileSystemBySaveDataAttribute(static_cast<u8>(space_id), attribute)); + }; + + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM(delete_impl(), nullptr, AMS_FS_IMPL_ACCESS_LOG_FORMAT_DELETE_SYSTEM_SAVE_DATA(space_id, id, user_id))); + R_SUCCEED(); + } + + Result GetSaveDataFlags(u32 *out, SaveDataId id) { + SaveDataExtraData extra_data; + R_TRY(impl::ReadSaveDataFileSystemExtraData(std::addressof(extra_data), id)); + + *out = extra_data.flags; + R_SUCCEED(); + } + + Result GetSaveDataFlags(u32 *out, SaveDataSpaceId space_id, SaveDataId id) { + SaveDataExtraData extra_data; + R_TRY(impl::ReadSaveDataFileSystemExtraData(std::addressof(extra_data), space_id, id)); + + *out = extra_data.flags; + R_SUCCEED(); + } + + Result SetSaveDataFlags(SaveDataId id, SaveDataSpaceId space_id, u32 flags) { + SaveDataExtraData extra_data; + R_TRY(impl::ReadSaveDataFileSystemExtraData(std::addressof(extra_data), space_id, id)); + extra_data.flags = flags; + R_RETURN(impl::WriteSaveDataFileSystemExtraData(space_id, id, extra_data)); + } + + Result GetSaveDataAvailableSize(s64 *out, SaveDataId id) { + SaveDataExtraData extra_data; + R_TRY(impl::ReadSaveDataFileSystemExtraData(std::addressof(extra_data), id)); + + *out = extra_data.available_size; + R_SUCCEED(); + } + + Result GetSaveDataJournalSize(s64 *out, SaveDataId id) { + SaveDataExtraData extra_data; + R_TRY(impl::ReadSaveDataFileSystemExtraData(std::addressof(extra_data), id)); + + *out = extra_data.journal_size; + R_SUCCEED(); + } + + Result ExtendSaveData(SaveDataSpaceId space_id, SaveDataId id, s64 available_size, s64 journal_size) { + auto extend_impl = [=]() -> Result { + auto fsp = impl::GetFileSystemProxyServiceObject(); + R_RETURN(fsp->ExtendSaveDataFileSystem(static_cast<u8>(space_id), id, available_size, journal_size)); + }; + + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM(extend_impl(), nullptr, AMS_FS_IMPL_ACCESS_LOG_FORMAT_EXTEND_SAVE_DATA(space_id, id, available_size, journal_size))); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_scoped_setter.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_scoped_setter.hpp new file mode 100644 index 00000000..d488fdc9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_scoped_setter.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fs { + + template<typename T> + class ScopedSetter { + NON_COPYABLE(ScopedSetter); + private: + T *m_ptr; + T m_value; + public: + constexpr ALWAYS_INLINE ScopedSetter(T &p, T v) : m_ptr(std::addressof(p)), m_value(v) { /* ... */ } + ALWAYS_INLINE ~ScopedSetter() { + if (m_ptr) { + *m_ptr = m_value; + } + } + + ALWAYS_INLINE ScopedSetter(ScopedSetter &&rhs) { + m_ptr = rhs.m_ptr; + m_value = rhs.m_value; + rhs.Reset(); + } + + ALWAYS_INLINE ScopedSetter &operator=(ScopedSetter &&rhs) { + m_ptr = rhs.m_ptr; + m_value = rhs.m_value; + rhs.Reset(); + return *this; + } + + ALWAYS_INLINE void Set(T v) { m_value = v; } + private: + ALWAYS_INLINE void Reset() { + m_ptr = nullptr; + } + }; + + template<typename T> + ALWAYS_INLINE ScopedSetter<T> MakeScopedSetter(T &p, T v) { + return ScopedSetter<T>(p, v); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_sd_card.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_sd_card.cpp new file mode 100644 index 00000000..cf923c3f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_sd_card.cpp @@ -0,0 +1,213 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsa/fs_mount_utils.hpp" +#include "impl/fs_file_system_proxy_service_object.hpp" +#include "impl/fs_file_system_service_object_adapter.hpp" +#include "impl/fs_event_notifier_service_object_adapter.hpp" + +namespace ams::fs { + + namespace { + + constexpr inline const char AtmosphereErrorReportDirectory[] = "/atmosphere/erpt_reports"; + + /* NOTE: Nintendo does not attach a generator to a mounted SD card filesystem. */ + /* However, it is desirable for homebrew to be able to access SD via common path. */ + class SdCardCommonMountNameGenerator : public fsa::ICommonMountNameGenerator, public impl::Newable { + public: + explicit SdCardCommonMountNameGenerator() { /* ... */ } + + virtual Result GenerateCommonMountName(char *dst, size_t dst_size) override { + /* Determine how much space we need. */ + const size_t needed_size = util::Strnlen(impl::SdCardFileSystemMountName, MountNameLengthMax) + 2; + AMS_ABORT_UNLESS(dst_size >= needed_size); + + /* Generate the name. */ + const auto size = util::SNPrintf(dst, dst_size, "%s:", impl::SdCardFileSystemMountName); + AMS_ASSERT(static_cast<size_t>(size) == needed_size - 1); + AMS_UNUSED(size); + + R_SUCCEED(); + } + }; + + } + + Result MountSdCard(const char *name) { + /* Validate the mount name. */ + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_MOUNT_UNLESS_R_SUCCEEDED(impl::CheckMountNameAllowingReserved(name), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT, name)); + + /* Open the SD card filesystem. */ + auto fsp = impl::GetFileSystemProxyServiceObject(); + sf::SharedPointer<fssrv::sf::IFileSystem> fs; + R_TRY(fsp->OpenSdCardFileSystem(std::addressof(fs))); + + /* Allocate a new filesystem wrapper. */ + auto fsa = std::make_unique<impl::FileSystemServiceObjectAdapter>(std::move(fs)); + R_UNLESS(fsa != nullptr, fs::ResultAllocationMemoryFailedInSdCardA()); + + /* Allocate a new mountname generator. */ + /* NOTE: Nintendo does not attach a generator. */ + auto generator = std::make_unique<SdCardCommonMountNameGenerator>(); + R_UNLESS(generator != nullptr, fs::ResultAllocationMemoryFailedInSdCardA()); + + /* Register. */ + R_RETURN(fsa::Register(name, std::move(fsa), std::move(generator))); + } + + Result MountSdCardErrorReportDirectoryForAtmosphere(const char *name) { + /* Validate the mount name. */ + R_TRY(impl::CheckMountName(name)); + + /* Open the SD card filesystem. */ + auto fsp = impl::GetFileSystemProxyServiceObject(); + sf::SharedPointer<fssrv::sf::IFileSystem> fs; + R_TRY(fsp->OpenSdCardFileSystem(std::addressof(fs))); + + /* Allocate a new filesystem wrapper. */ + auto fsa = fs::AllocateShared<impl::FileSystemServiceObjectAdapter>(std::move(fs)); + R_UNLESS(fsa != nullptr, fs::ResultAllocationMemoryFailedInSdCardA()); + + /* Ensure that the error report directory exists. */ + constexpr fs::Path fs_path = fs::MakeConstantPath(AtmosphereErrorReportDirectory); + R_TRY(fssystem::EnsureDirectory(fsa.get(), fs_path)); + + /* Create a subdirectory filesystem. */ + auto subdir_fs = std::make_unique<fssystem::SubDirectoryFileSystem>(std::move(fsa)); + R_UNLESS(subdir_fs != nullptr, fs::ResultAllocationMemoryFailedInSdCardA()); + R_TRY(subdir_fs->Initialize(fs_path)); + + /* Register. */ + R_RETURN(fsa::Register(name, std::move(subdir_fs))); + } + + Result OpenSdCardDetectionEventNotifier(std::unique_ptr<IEventNotifier> *out) { + auto fsp = impl::GetFileSystemProxyServiceObject(); + + /* Try to open an event notifier. */ + sf::SharedPointer<fssrv::sf::IEventNotifier> notifier; + AMS_FS_R_TRY(fsp->OpenSdCardDetectionEventNotifier(std::addressof(notifier))); + + /* Create an event notifier adapter. */ + auto adapter = std::make_unique<impl::EventNotifierObjectAdapter>(std::move(notifier)); + AMS_FS_R_UNLESS(adapter != nullptr, fs::ResultAllocationMemoryFailedInSdCardB()); + + *out = std::move(adapter); + R_SUCCEED(); + } + + bool IsSdCardInserted() { + auto fsp = impl::GetFileSystemProxyServiceObject(); + + /* Open a device operator. */ + sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator; + AMS_FS_R_ABORT_UNLESS(fsp->OpenDeviceOperator(std::addressof(device_operator))); + + /* Get insertion status. */ + bool inserted; + AMS_FS_R_ABORT_UNLESS(device_operator->IsSdCardInserted(std::addressof(inserted))); + + return inserted; + } + + Result GetSdCardSpeedMode(SdCardSpeedMode *out) { + /* Check pre-conditions. */ + AMS_FS_R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + + auto fsp = impl::GetFileSystemProxyServiceObject(); + + /* Open a device operator. */ + sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator; + AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator))); + + /* Get the speed mode. */ + s64 speed_mode = 0; + AMS_FS_R_TRY(device_operator->GetSdCardSpeedMode(std::addressof(speed_mode))); + + *out = static_cast<SdCardSpeedMode>(speed_mode); + R_SUCCEED(); + } + + Result GetSdCardCid(void *dst, size_t size) { + /* Check pre-conditions. */ + AMS_FS_R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + + auto fsp = impl::GetFileSystemProxyServiceObject(); + + /* Open a device operator. */ + sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator; + AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator))); + + /* Get the cid. */ + AMS_FS_R_TRY(device_operator->GetSdCardCid(sf::OutBuffer(dst, size), static_cast<s64>(size))); + + R_SUCCEED(); + } + + Result GetSdCardUserAreaSize(s64 *out) { + /* Check pre-conditions. */ + AMS_FS_R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + + auto fsp = impl::GetFileSystemProxyServiceObject(); + + /* Open a device operator. */ + sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator; + AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator))); + + /* Get the size. */ + AMS_FS_R_TRY(device_operator->GetSdCardUserAreaSize(out)); + + R_SUCCEED(); + } + + Result GetSdCardProtectedAreaSize(s64 *out) { + /* Check pre-conditions. */ + AMS_FS_R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + + auto fsp = impl::GetFileSystemProxyServiceObject(); + + /* Open a device operator. */ + sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator; + AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator))); + + /* Get the size. */ + AMS_FS_R_TRY(device_operator->GetSdCardProtectedAreaSize(out)); + + R_SUCCEED(); + } + + Result GetAndClearSdCardErrorInfo(StorageErrorInfo *out_sei, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size) { + /* Check pre-conditions. */ + AMS_FS_R_UNLESS(out_sei != nullptr, fs::ResultNullptrArgument()); + AMS_FS_R_UNLESS(out_log_size != nullptr, fs::ResultNullptrArgument()); + AMS_FS_R_UNLESS(out_log_buffer != nullptr, fs::ResultNullptrArgument()); + + auto fsp = impl::GetFileSystemProxyServiceObject(); + + /* Open a device operator. */ + sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator; + AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator))); + + /* Get the error info. */ + s64 log_size = 0; + AMS_FS_R_TRY(device_operator->GetAndClearSdCardErrorInfo(out_sei, std::addressof(log_size), sf::OutBuffer(out_log_buffer, log_buffer_size), static_cast<s64>(log_buffer_size))); + + *out_log_size = static_cast<size_t>(log_size); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_signed_system_partition.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_signed_system_partition.cpp new file mode 100644 index 00000000..7ef4c773 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_signed_system_partition.cpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsa/fs_filesystem_accessor.hpp" +#include "fsa/fs_mount_utils.hpp" +#include "impl/fs_file_system_proxy_service_object.hpp" + +namespace ams::fs { + + namespace { + + Result GetSignedSystemPartitionOnSdCardValid(char *out, impl::FileSystemAccessor *accessor) { + R_TRY_CATCH(accessor->QueryEntry(out, sizeof(*out), nullptr, 0, fsa::QueryId::IsSignedSystemPartitionOnSdCardValid, "/")) { + /* If querying isn't supported, then the partition isn't valid. */ + R_CATCH(fs::ResultUnsupportedOperation) { *out = false; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + R_SUCCEED(); + } + + } + + bool IsSignedSystemPartitionOnSdCardValid(const char *system_root_path) { + /* Get the accessor for the system filesystem. */ + impl::FileSystemAccessor *accessor; + const char *sub_path; + AMS_FS_R_ABORT_UNLESS(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), system_root_path)); + + char is_valid; + AMS_FS_R_ABORT_UNLESS(GetSignedSystemPartitionOnSdCardValid(std::addressof(is_valid), accessor)); + return is_valid; + } + + bool IsSignedSystemPartitionOnSdCardValidDeprecated() { + /* Ensure we only call with correct version. */ + auto version = hos::GetVersion(); + AMS_ABORT_UNLESS(hos::Version_4_0_0 <= version && version < hos::Version_8_0_0); + + /* Check that the partition is valid. */ + auto fsp = impl::GetFileSystemProxyServiceObject(); + bool is_valid; + AMS_FS_R_ABORT_UNLESS(fsp->IsSignedSystemPartitionOnSdCardValid(std::addressof(is_valid))); + return is_valid; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_system_data.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_system_data.cpp new file mode 100644 index 00000000..201f93fd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_system_data.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsa/fs_mount_utils.hpp" + +namespace ams::fs { + + Result QueryMountSystemDataCacheSize(size_t *out, ncm::SystemDataId data_id) { + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM(impl::QueryMountDataCacheSize(out, data_id, ncm::StorageId::BuiltInSystem), nullptr, AMS_FS_IMPL_ACCESS_LOG_FORMAT_QUERY_MOUNT_SYSTEM_DATA_CACHE_SIZE(data_id, out))); + R_SUCCEED(); + } + + Result MountSystemData(const char *name, ncm::SystemDataId data_id) { + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM_MOUNT(impl::MountData(name, data_id, ncm::StorageId::BuiltInSystem), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_SYSTEM_DATA(name, data_id))); + AMS_FS_IMPL_ACCESS_LOG_SYSTEM_FS_ACCESSOR_ENABLE(name); + R_SUCCEED(); + } + + Result MountSystemData(const char *name, ncm::SystemDataId data_id, void *cache_buffer, size_t cache_size) { + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM_MOUNT(impl::MountData(name, data_id, ncm::StorageId::BuiltInSystem, cache_buffer, cache_size), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_SYSTEM_DATA(name, data_id))); + AMS_FS_IMPL_ACCESS_LOG_SYSTEM_FS_ACCESSOR_ENABLE(name); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_system_save_data.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_system_save_data.cpp new file mode 100644 index 00000000..1cae025d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fs_system_save_data.cpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsa/fs_mount_utils.hpp" +#include "impl/fs_file_system_proxy_service_object.hpp" +#include "impl/fs_file_system_service_object_adapter.hpp" + +namespace ams::fs { + + Result MountSystemSaveData(const char *name, SystemSaveDataId id) { + R_RETURN(MountSystemSaveData(name, id, InvalidUserId)); + } + + Result MountSystemSaveData(const char *name, SaveDataSpaceId space_id, SystemSaveDataId id) { + R_RETURN(MountSystemSaveData(name, space_id, id, InvalidUserId)); + } + + Result MountSystemSaveData(const char *name, SystemSaveDataId id, UserId user_id) { + R_RETURN(MountSystemSaveData(name, SaveDataSpaceId::System, id, user_id)); + } + + Result MountSystemSaveData(const char *name, SaveDataSpaceId space_id, SystemSaveDataId id, UserId user_id) { + auto mount_impl = [=]() -> Result { + /* Validate the mount name. */ + R_TRY(impl::CheckMountName(name)); + + /* Create the attribute. */ + const auto attribute = SaveDataAttribute::Make(ncm::InvalidProgramId, SaveDataType::System, user_id, id); + + /* Open the filesystem. */ + auto fsp = impl::GetFileSystemProxyServiceObject(); + sf::SharedPointer<fssrv::sf::IFileSystem> fs; + R_TRY(fsp->OpenSaveDataFileSystemBySystemSaveDataId(std::addressof(fs), static_cast<u8>(space_id), attribute)); + + /* Allocate a new filesystem wrapper. */ + auto fsa = std::make_unique<impl::FileSystemServiceObjectAdapter>(std::move(fs)); + R_UNLESS(fsa != nullptr, fs::ResultAllocationMemoryFailedInSystemSaveDataA()); + + /* Register. */ + R_RETURN(fsa::Register(name, std::move(fsa))); + }; + + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_SYSTEM_MOUNT(mount_impl(), name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT_SYSTEM_SAVE_DATA(name, space_id, id, user_id))); + AMS_FS_IMPL_ACCESS_LOG_SYSTEM_FS_ACCESSOR_ENABLE(name); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_directory_accessor.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_directory_accessor.cpp new file mode 100644 index 00000000..fd86a532 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_directory_accessor.cpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fs_directory_accessor.hpp" +#include "fs_filesystem_accessor.hpp" + +namespace ams::fs::impl { + + DirectoryAccessor::DirectoryAccessor(std::unique_ptr<fsa::IDirectory>&& d, FileSystemAccessor &p) : m_impl(std::move(d)), m_parent(p) { + /* ... */ + } + + DirectoryAccessor::~DirectoryAccessor() { + m_impl.reset(); + m_parent.NotifyCloseDirectory(this); + } + + Result DirectoryAccessor::Read(s64 *out_count, DirectoryEntry *out_entries, s64 max_entries) { + R_RETURN(m_impl->Read(out_count, out_entries, max_entries)); + } + + Result DirectoryAccessor::GetEntryCount(s64 *out) { + R_RETURN(m_impl->GetEntryCount(out)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_directory_accessor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_directory_accessor.hpp new file mode 100644 index 00000000..a7e7dc47 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_directory_accessor.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fs::impl { + + class FileSystemAccessor; + + class DirectoryAccessor : public util::IntrusiveListBaseNode<DirectoryAccessor>, public Newable { + NON_COPYABLE(DirectoryAccessor); + private: + std::unique_ptr<fsa::IDirectory> m_impl; + FileSystemAccessor &m_parent; + public: + DirectoryAccessor(std::unique_ptr<fsa::IDirectory>&& d, FileSystemAccessor &p); + ~DirectoryAccessor(); + + Result Read(s64 *out_count, DirectoryEntry *out_entries, s64 max_entries); + Result GetEntryCount(s64 *out); + + FileSystemAccessor *GetParent() const { return std::addressof(m_parent); } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_file_accessor.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_file_accessor.cpp new file mode 100644 index 00000000..fc8ab265 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_file_accessor.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../fs_scoped_setter.hpp" +#include "../fs_file_path_hash.hpp" +#include "fs_file_accessor.hpp" +#include "fs_filesystem_accessor.hpp" + +namespace ams::fs::impl { + + FileAccessor::FileAccessor(std::unique_ptr<fsa::IFile>&& f, FileSystemAccessor *p, OpenMode mode) + : m_impl(std::move(f)), m_parent(p), m_write_state(WriteState::None), m_write_result(ResultSuccess()), m_open_mode(mode) + { + /* ... */ + } + + FileAccessor::~FileAccessor() { + /* Ensure that all files are flushed. */ + if (R_SUCCEEDED(m_write_result)) { + AMS_FS_ABORT_UNLESS_WITH_RESULT(m_write_state != WriteState::NeedsFlush, fs::ResultNeedFlush()); + } + m_impl.reset(); + + if (m_parent != nullptr) { + m_parent->NotifyCloseFile(this); + } + } + + Result FileAccessor::ReadWithCacheAccessLog(size_t *out, s64 offset, void *buf, size_t size, const ReadOption &option, bool use_path_cache, bool use_data_cache) { + /* TODO */ + AMS_UNUSED(out, offset, buf, size, option, use_path_cache, use_data_cache); + AMS_ABORT(); + } + + Result FileAccessor::ReadWithoutCacheAccessLog(size_t *out, s64 offset, void *buf, size_t size, const ReadOption &option) { + R_RETURN(m_impl->Read(out, offset, buf, size, option)); + } + + Result FileAccessor::Read(size_t *out, s64 offset, void *buf, size_t size, const ReadOption &option) { + /* Get a handle to this file for use in logging. */ + FileHandle handle = { this }; + + /* Fail after a write fails. */ + R_UNLESS(R_SUCCEEDED(m_write_result), AMS_FS_IMPL_ACCESS_LOG_WITH_NAME(m_write_result, handle, "ReadFile", AMS_FS_IMPL_ACCESS_LOG_FORMAT_READ_FILE(out, offset, size))); + + /* TODO: Support cache. */ + const bool use_path_cache = m_parent != nullptr && m_file_path_hash != nullptr; + const bool use_data_cache = /* TODO */false && m_parent != nullptr && m_parent->IsFileDataCacheAttachable(); + + if (use_path_cache && use_data_cache && false) { + /* TODO */ + R_RETURN(this->ReadWithCacheAccessLog(out, offset, buf, size, option, use_path_cache, use_data_cache)); + } else { + R_RETURN(AMS_FS_IMPL_ACCESS_LOG_WITH_NAME(this->ReadWithoutCacheAccessLog(out, offset, buf, size, option), handle, "ReadFile", AMS_FS_IMPL_ACCESS_LOG_FORMAT_READ_FILE(out, offset, size))); + } + } + + Result FileAccessor::Write(s64 offset, const void *buf, size_t size, const WriteOption &option) { + /* Fail after a write fails. */ + R_TRY(m_write_result); + + auto setter = MakeScopedSetter(m_write_state, WriteState::Failed); + if (m_file_path_hash != nullptr && /* TODO */ false) { + /* TODO */ + AMS_ABORT(); + } else { + R_TRY(this->UpdateLastResult(m_impl->Write(offset, buf, size, option))); + } + + setter.Set(option.HasFlushFlag() ? WriteState::None : WriteState::NeedsFlush); + + R_SUCCEED(); + } + + Result FileAccessor::Flush() { + /* Fail after a write fails. */ + R_TRY(m_write_result); + + auto setter = MakeScopedSetter(m_write_state, WriteState::Failed); + R_TRY(this->UpdateLastResult(m_impl->Flush())); + setter.Set(WriteState::None); + + R_SUCCEED(); + } + + Result FileAccessor::SetSize(s64 size) { + /* Fail after a write fails. */ + R_TRY(m_write_result); + + const WriteState old_write_state = m_write_state; + auto setter = MakeScopedSetter(m_write_state, WriteState::Failed); + + R_TRY(this->UpdateLastResult(m_impl->SetSize(size))); + + if (m_file_path_hash != nullptr) { + /* TODO: invalidate path cache */ + } + + setter.Set(old_write_state); + R_SUCCEED(); + } + + Result FileAccessor::GetSize(s64 *out) { + /* Fail after a write fails. */ + R_TRY(m_write_result); + + R_RETURN(m_impl->GetSize(out)); + } + + Result FileAccessor::OperateRange(void *dst, size_t dst_size, OperationId operation, s64 offset, s64 size, const void *src, size_t src_size) { + R_RETURN(m_impl->OperateRange(dst, dst_size, operation, offset, size, src, src_size)); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_file_accessor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_file_accessor.hpp new file mode 100644 index 00000000..3b293feb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_file_accessor.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fs::impl { + + struct FilePathHash; + class FileSystemAccessor; + + enum class WriteState { + None, + NeedsFlush, + Failed, + }; + + class FileAccessor : public util::IntrusiveListBaseNode<FileAccessor>, public Newable { + NON_COPYABLE(FileAccessor); + private: + std::unique_ptr<fsa::IFile> m_impl; + FileSystemAccessor * const m_parent; + WriteState m_write_state; + Result m_write_result; + const OpenMode m_open_mode; + std::unique_ptr<FilePathHash> m_file_path_hash; + s32 m_path_hash_index; + public: + FileAccessor(std::unique_ptr<fsa::IFile>&& f, FileSystemAccessor *p, OpenMode mode); + ~FileAccessor(); + + Result Read(size_t *out, s64 offset, void *buf, size_t size, const ReadOption &option); + Result Write(s64 offset, const void *buf, size_t size, const WriteOption &option); + Result Flush(); + Result SetSize(s64 size); + Result GetSize(s64 *out); + Result OperateRange(void *dst, size_t dst_size, OperationId operation, s64 offset, s64 size, const void *src, size_t src_size); + + OpenMode GetOpenMode() const { return m_open_mode; } + WriteState GetWriteState() const { return m_write_state; } + FileSystemAccessor *GetParent() const { return m_parent; } + + void SetFilePathHash(std::unique_ptr<FilePathHash>&& file_path_hash, s32 index); + Result ReadWithoutCacheAccessLog(size_t *out, s64 offset, void *buf, size_t size, const ReadOption &option); + private: + Result ReadWithCacheAccessLog(size_t *out, s64 offset, void *buf, size_t size, const ReadOption &option, bool use_path_cache, bool use_data_cache); + + ALWAYS_INLINE Result UpdateLastResult(Result r) { + if (!fs::ResultNotEnoughFreeSpace::Includes(r)) { + m_write_result = r; + } + R_RETURN(r); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_filesystem_accessor.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_filesystem_accessor.cpp new file mode 100644 index 00000000..23357d66 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_filesystem_accessor.cpp @@ -0,0 +1,314 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fs_file_accessor.hpp" +#include "fs_directory_accessor.hpp" +#include "fs_filesystem_accessor.hpp" + +namespace ams::fs::impl { + + namespace { + + template<typename List, typename Iter> + void Remove(List &list, Iter *desired) { + for (auto it = list.cbegin(); it != list.cend(); ++it) { + if (it.operator->() == desired) { + list.erase(it); + return; + } + } + + /* This should never happen. */ + AMS_ABORT(); + } + + Result SetMountName(char *dst, const char *name) { + R_UNLESS(name[0] != 0, fs::ResultInvalidMountName()); + + const size_t n_len = util::Strlcpy<char>(dst, name, MountNameLengthMax + 1); + R_UNLESS(n_len <= MountNameLengthMax, fs::ResultInvalidMountName()); + + R_SUCCEED(); + } + + template<typename List> + Result ValidateNoOpenWriteModeFiles(List &list) { + for (auto it = list.cbegin(); it != list.cend(); it++) { + R_UNLESS((it->GetOpenMode() & OpenMode_Write) == 0, fs::ResultWriteModeFileNotClosed()); + } + R_SUCCEED(); + } + + } + + FileSystemAccessor::FileSystemAccessor(const char *n, std::unique_ptr<fsa::IFileSystem> &&fs, std::unique_ptr<fsa::ICommonMountNameGenerator> &&generator) + : m_impl(std::move(fs)), m_open_list_lock(), m_mount_name_generator(std::move(generator)), + m_access_log_enabled(false), m_data_cache_attachable(false), m_path_cache_attachable(false), m_path_cache_attached(false), m_multi_commit_supported(false), + m_path_flags() + { + R_ABORT_UNLESS(SetMountName(m_name.str, n)); + + if (std::strcmp(m_name.str, AMS_FS_IMPL_HOST_ROOT_FILE_SYSTEM_MOUNT_NAME) == 0) { + m_path_flags.AllowWindowsPath(); + } + } + + FileSystemAccessor::~FileSystemAccessor() { + std::scoped_lock lk(m_open_list_lock); + + /* TODO: Iterate over list entries. */ + + if (!m_open_file_list.empty()) { R_ABORT_UNLESS(fs::ResultFileNotClosed()); } + if (!m_open_dir_list.empty()) { R_ABORT_UNLESS(fs::ResultDirectoryNotClosed()); } + + if (m_path_cache_attached) { + /* TODO: Invalidate path cache */ + } + } + + Result FileSystemAccessor::GetCommonMountName(char *dst, size_t dst_size) const { + R_UNLESS(m_mount_name_generator != nullptr, fs::ResultPreconditionViolation()); + R_RETURN(m_mount_name_generator->GenerateCommonMountName(dst, dst_size)); + } + + std::shared_ptr<fssrv::impl::FileSystemInterfaceAdapter> FileSystemAccessor::GetMultiCommitTarget() { + if (m_multi_commit_supported) { + AMS_ABORT("TODO: Support multi commit"); + } + return nullptr; + } + + void FileSystemAccessor::NotifyCloseFile(FileAccessor *f) { + std::scoped_lock lk(m_open_list_lock); + Remove(m_open_file_list, f); + } + + void FileSystemAccessor::NotifyCloseDirectory(DirectoryAccessor *d) { + std::scoped_lock lk(m_open_list_lock); + Remove(m_open_dir_list, d); + } + + Result FileSystemAccessor::SetUpPath(fs::Path *out, const char *p) { + /* Initialize the path appropriately. */ + bool normalized; + size_t len; + if (R_SUCCEEDED(PathFormatter::IsNormalized(std::addressof(normalized), std::addressof(len), p, m_path_flags)) && normalized) { + /* We can use the input buffer directly. */ + out->SetShallowBuffer(p); + } else { + /* Initialize with appropriate slash replacement. */ + if (m_path_flags.IsWindowsPathAllowed()) { + R_TRY(out->InitializeWithReplaceForwardSlashes(p)); + } else { + R_TRY(out->InitializeWithReplaceBackslash(p)); + } + + /* Ensure we're normalized. */ + R_TRY(out->Normalize(m_path_flags)); + } + + /* Check the path isn't too long. */ + R_UNLESS(out->GetLength() <= fs::EntryNameLengthMax, fs::ResultTooLongPath()); + R_SUCCEED(); + } + + Result FileSystemAccessor::CreateFile(const char *path, s64 size, int option) { + /* Create path. */ + fs::Path normalized_path; + R_TRY(this->SetUpPath(std::addressof(normalized_path), path)); + + if (m_path_cache_attached) { + /* TODO: Path cache */ + R_TRY(m_impl->CreateFile(normalized_path, size, option)); + } else { + R_TRY(m_impl->CreateFile(normalized_path, size, option)); + } + + R_SUCCEED(); + } + + Result FileSystemAccessor::DeleteFile(const char *path) { + /* Create path. */ + fs::Path normalized_path; + R_TRY(this->SetUpPath(std::addressof(normalized_path), path)); + + R_RETURN(m_impl->DeleteFile(normalized_path)); + } + + Result FileSystemAccessor::CreateDirectory(const char *path) { + /* Create path. */ + fs::Path normalized_path; + R_TRY(this->SetUpPath(std::addressof(normalized_path), path)); + + R_RETURN(m_impl->CreateDirectory(normalized_path)); + } + + Result FileSystemAccessor::DeleteDirectory(const char *path) { + /* Create path. */ + fs::Path normalized_path; + R_TRY(this->SetUpPath(std::addressof(normalized_path), path)); + + R_RETURN(m_impl->DeleteDirectory(normalized_path)); + } + + Result FileSystemAccessor::DeleteDirectoryRecursively(const char *path) { + /* Create path. */ + fs::Path normalized_path; + R_TRY(this->SetUpPath(std::addressof(normalized_path), path)); + + R_RETURN(m_impl->DeleteDirectoryRecursively(normalized_path)); + } + + Result FileSystemAccessor::RenameFile(const char *old_path, const char *new_path) { + /* Create path. */ + fs::Path normalized_old_path; + fs::Path normalized_new_path; + R_TRY(this->SetUpPath(std::addressof(normalized_old_path), old_path)); + R_TRY(this->SetUpPath(std::addressof(normalized_new_path), new_path)); + + if (m_path_cache_attached) { + /* TODO: Path cache */ + R_TRY(m_impl->RenameFile(normalized_old_path, normalized_new_path)); + } else { + R_TRY(m_impl->RenameFile(normalized_old_path, normalized_new_path)); + } + + R_SUCCEED(); + } + + Result FileSystemAccessor::RenameDirectory(const char *old_path, const char *new_path) { + /* Create path. */ + fs::Path normalized_old_path; + fs::Path normalized_new_path; + R_TRY(this->SetUpPath(std::addressof(normalized_old_path), old_path)); + R_TRY(this->SetUpPath(std::addressof(normalized_new_path), new_path)); + + if (m_path_cache_attached) { + /* TODO: Path cache */ + R_TRY(m_impl->RenameDirectory(normalized_old_path, normalized_new_path)); + } else { + R_TRY(m_impl->RenameDirectory(normalized_old_path, normalized_new_path)); + } + + R_SUCCEED(); + } + + Result FileSystemAccessor::GetEntryType(DirectoryEntryType *out, const char *path) { + /* Create path. */ + fs::Path normalized_path; + R_TRY(this->SetUpPath(std::addressof(normalized_path), path)); + + R_RETURN(m_impl->GetEntryType(out, normalized_path)); + } + + Result FileSystemAccessor::OpenFile(std::unique_ptr<FileAccessor> *out_file, const char *path, OpenMode mode) { + /* Create path. */ + fs::Path normalized_path; + R_TRY(this->SetUpPath(std::addressof(normalized_path), path)); + + std::unique_ptr<fsa::IFile> file; + R_TRY(m_impl->OpenFile(std::addressof(file), normalized_path, mode)); + + auto accessor = new FileAccessor(std::move(file), this, mode); + R_UNLESS(accessor != nullptr, fs::ResultAllocationMemoryFailedInFileSystemAccessorA()); + + { + std::scoped_lock lk(m_open_list_lock); + m_open_file_list.push_back(*accessor); + } + + if (m_path_cache_attached) { + if (mode & OpenMode_AllowAppend) { + /* TODO: Append Path cache */ + } else { + /* TODO: Non-append path cache */ + } + } + + out_file->reset(accessor); + R_SUCCEED(); + } + + Result FileSystemAccessor::OpenDirectory(std::unique_ptr<DirectoryAccessor> *out_dir, const char *path, OpenDirectoryMode mode) { + /* Create path. */ + fs::Path normalized_path; + R_TRY(this->SetUpPath(std::addressof(normalized_path), path)); + + std::unique_ptr<fsa::IDirectory> dir; + R_TRY(m_impl->OpenDirectory(std::addressof(dir), normalized_path, mode)); + + auto accessor = new DirectoryAccessor(std::move(dir), *this); + R_UNLESS(accessor != nullptr, fs::ResultAllocationMemoryFailedInFileSystemAccessorB()); + + { + std::scoped_lock lk(m_open_list_lock); + m_open_dir_list.push_back(*accessor); + } + + out_dir->reset(accessor); + R_SUCCEED(); + } + + Result FileSystemAccessor::Commit() { + { + std::scoped_lock lk(m_open_list_lock); + R_ABORT_UNLESS(ValidateNoOpenWriteModeFiles(m_open_file_list)); + } + R_RETURN(m_impl->Commit()); + } + + Result FileSystemAccessor::GetFreeSpaceSize(s64 *out, const char *path) { + /* Create path. */ + fs::Path normalized_path; + R_TRY(this->SetUpPath(std::addressof(normalized_path), path)); + + R_RETURN(m_impl->GetFreeSpaceSize(out, normalized_path)); + } + + Result FileSystemAccessor::GetTotalSpaceSize(s64 *out, const char *path) { + /* Create path. */ + fs::Path normalized_path; + R_TRY(this->SetUpPath(std::addressof(normalized_path), path)); + + R_RETURN(m_impl->GetTotalSpaceSize(out, normalized_path)); + } + + Result FileSystemAccessor::CleanDirectoryRecursively(const char *path) { + /* Create path. */ + fs::Path normalized_path; + R_TRY(this->SetUpPath(std::addressof(normalized_path), path)); + + R_RETURN(m_impl->CleanDirectoryRecursively(normalized_path)); + } + + Result FileSystemAccessor::GetFileTimeStampRaw(FileTimeStampRaw *out, const char *path) { + /* Create path. */ + fs::Path normalized_path; + R_TRY(this->SetUpPath(std::addressof(normalized_path), path)); + + R_RETURN(m_impl->GetFileTimeStampRaw(out, normalized_path)); + } + + Result FileSystemAccessor::QueryEntry(char *dst, size_t dst_size, const char *src, size_t src_size, fsa::QueryId query, const char *path) { + /* Create path. */ + fs::Path normalized_path; + R_TRY(this->SetUpPath(std::addressof(normalized_path), path)); + + R_RETURN(m_impl->QueryEntry(dst, dst_size, src, src_size, query, normalized_path)); + } + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_filesystem_accessor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_filesystem_accessor.hpp new file mode 100644 index 00000000..d8420646 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_filesystem_accessor.hpp @@ -0,0 +1,101 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include <stratosphere/fssrv/fssrv_interface_adapters.hpp> +#include "fs_mount_name.hpp" + +namespace ams::fs::impl { + + class FileAccessor; + class DirectoryAccessor; + + class FileSystemAccessor : public util::IntrusiveListBaseNode<FileSystemAccessor>, public Newable { + NON_COPYABLE(FileSystemAccessor); + friend class FileAccessor; + friend class DirectoryAccessor; + private: + using FileList = util::IntrusiveListBaseTraits<FileAccessor>::ListType; + using DirList = util::IntrusiveListBaseTraits<DirectoryAccessor>::ListType; + private: + MountName m_name; + std::unique_ptr<fsa::IFileSystem> m_impl; + FileList m_open_file_list; + DirList m_open_dir_list; + os::SdkMutex m_open_list_lock; + std::unique_ptr<fsa::ICommonMountNameGenerator> m_mount_name_generator; + bool m_access_log_enabled; + bool m_data_cache_attachable; + bool m_path_cache_attachable; + bool m_path_cache_attached; + bool m_multi_commit_supported; + PathFlags m_path_flags; + public: + FileSystemAccessor(const char *name, std::unique_ptr<fsa::IFileSystem> &&fs, std::unique_ptr<fsa::ICommonMountNameGenerator> &&generator = nullptr); + virtual ~FileSystemAccessor(); + + Result CreateFile(const char *path, s64 size, int option); + Result DeleteFile(const char *path); + Result CreateDirectory(const char *path); + Result DeleteDirectory(const char *path); + Result DeleteDirectoryRecursively(const char *path); + Result RenameFile(const char *old_path, const char *new_path); + Result RenameDirectory(const char *old_path, const char *new_path); + Result GetEntryType(DirectoryEntryType *out, const char *path); + Result OpenFile(std::unique_ptr<FileAccessor> *out_file, const char *path, OpenMode mode); + Result OpenDirectory(std::unique_ptr<DirectoryAccessor> *out_dir, const char *path, OpenDirectoryMode mode); + Result Commit(); + Result GetFreeSpaceSize(s64 *out, const char *path); + Result GetTotalSpaceSize(s64 *out, const char *path); + Result CleanDirectoryRecursively(const char *path); + Result GetFileTimeStampRaw(FileTimeStampRaw *out, const char *path); + Result QueryEntry(char *dst, size_t dst_size, const char *src, size_t src_size, fsa::QueryId query, const char *path); + + const char *GetName() const { return m_name.str; } + Result GetCommonMountName(char *dst, size_t dst_size) const; + + void SetAccessLogEnabled(bool en) { m_access_log_enabled = en; } + void SetFileDataCacheAttachable(bool en) { m_data_cache_attachable = en; } + void SetPathBasedFileDataCacheAttachable(bool en) { m_path_cache_attachable = en; } + void SetMultiCommitSupported(bool en) { m_multi_commit_supported = en; } + + bool IsEnabledAccessLog() const { return m_access_log_enabled; } + bool IsFileDataCacheAttachable() const { return m_data_cache_attachable; } + bool IsPathBasedFileDataCacheAttachable() const { return m_path_cache_attachable; } + + void AttachPathBasedFileDataCache() { + if (this->IsPathBasedFileDataCacheAttachable()) { + m_path_cache_attached = true; + } + } + + void DetachPathBasedFileDataCache() { + m_path_cache_attached = false; + } + + std::shared_ptr<fssrv::impl::FileSystemInterfaceAdapter> GetMultiCommitTarget(); + + fsa::IFileSystem *GetRawFileSystemUnsafe() { + return m_impl.get(); + } + private: + void NotifyCloseFile(FileAccessor *f); + void NotifyCloseDirectory(DirectoryAccessor *d); + public: + Result SetUpPath(fs::Path *out, const char *p); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_mount_name.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_mount_name.hpp new file mode 100644 index 00000000..6afc7d57 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_mount_name.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fs { + + struct MountName { + char str[MountNameLengthMax + 1]; + }; + static_assert(util::is_pod<MountName>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_mount_table.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_mount_table.cpp new file mode 100644 index 00000000..83f13876 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_mount_table.cpp @@ -0,0 +1,75 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fs_mount_table.hpp" + +namespace ams::fs::impl { + + namespace { + + bool MatchesName(const FileSystemAccessor &accessor, const char *name) { + return std::strncmp(accessor.GetName(), name, sizeof(MountName)) == 0; + } + + } + + bool MountTable::CanAcceptMountName(const char *name) { + for (const auto &fs : m_fs_list) { + if (MatchesName(fs, name)) { + return false; + } + } + return true; + } + + Result MountTable::Mount(std::unique_ptr<FileSystemAccessor> &&fs) { + std::scoped_lock lk(m_mutex); + + R_UNLESS(this->CanAcceptMountName(fs->GetName()), fs::ResultMountNameAlreadyExists()); + + m_fs_list.push_back(*fs.release()); + R_SUCCEED(); + } + + Result MountTable::Find(FileSystemAccessor **out, const char *name) { + std::scoped_lock lk(m_mutex); + + for (auto &fs : m_fs_list) { + if (MatchesName(fs, name)) { + *out = std::addressof(fs); + R_SUCCEED(); + } + } + + R_THROW(fs::ResultNotMounted()); + } + + void MountTable::Unmount(const char *name) { + std::scoped_lock lk(m_mutex); + + for (auto it = m_fs_list.cbegin(); it != m_fs_list.cend(); it++) { + if (MatchesName(*it, name)) { + auto p = std::addressof(*it); + m_fs_list.erase(it); + delete p; + return; + } + } + + R_ABORT_UNLESS(fs::ResultNotMounted()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_mount_table.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_mount_table.hpp new file mode 100644 index 00000000..96f06eee --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_mount_table.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "fs_filesystem_accessor.hpp" + +namespace ams::fs::impl { + + class MountTable : public Newable { + NON_COPYABLE(MountTable); + NON_MOVEABLE(MountTable); + private: + using FileSystemList = util::IntrusiveListBaseTraits<FileSystemAccessor>::ListType; + private: + FileSystemList m_fs_list; + os::SdkMutex m_mutex; + public: + constexpr MountTable() : m_fs_list(), m_mutex() { /* ... */ } + private: + bool CanAcceptMountName(const char *name); + public: + Result Mount(std::unique_ptr<FileSystemAccessor> &&fs); + Result Find(FileSystemAccessor **out, const char *name); + void Unmount(const char *name); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_mount_utils.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_mount_utils.cpp new file mode 100644 index 00000000..74be6c49 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_mount_utils.cpp @@ -0,0 +1,172 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fs_filesystem_accessor.hpp" +#include "fs_mount_utils.hpp" +#include "fs_user_mount_table.hpp" + +namespace ams::fs::impl { + + namespace { + + const char *FindMountNameDriveSeparator(const char *path) { + for (const char *cur = path; cur < path + MountNameLengthMax + 1 && *cur != StringTraits::NullTerminator; ++cur) { + if (*cur == StringTraits::DriveSeparator) { + return cur; + } + } + return nullptr; + } + + constexpr bool IsHostRootPath(const char *path) { + #if defined(ATMOSPHERE_OS_HORIZON) || defined(ATMOSPHERE_OS_WINDOWS) + return fs::IsWindowsDrive(path) || fs::IsUncPath(path); + #elif defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS) + return fs::IsPathAbsolute(path); + #else + #error "Unknown OS for host path identification" + #endif + } + + Result GetMountNameAndSubPath(MountName *out_mount_name, const char **out_sub_path, const char *path) { + /* Handle the Host-path case. */ + if (IsHostRootPath(path)) { + std::strncpy(out_mount_name->str, HostRootFileSystemMountName, MountNameLengthMax); + out_mount_name->str[MountNameLengthMax] = '\x00'; + + *out_sub_path = path; + R_SUCCEED(); + } + + /* Locate the drive separator. */ + const char *drive_separator = FindMountNameDriveSeparator(path); + R_UNLESS(drive_separator != nullptr, fs::ResultInvalidMountName()); + + /* Ensure the mount name isn't too long. */ + const size_t len = drive_separator - path; + R_UNLESS(0 < len, fs::ResultInvalidMountName()); + R_UNLESS(len <= MountNameLengthMax, fs::ResultInvalidMountName()); + + /* Ensure the result sub-path is valid. */ + const char *sub_path = drive_separator + 1; + + const auto starts_with_dir = (sub_path[0] == StringTraits::DirectorySeparator) || (sub_path[0] == StringTraits::AlternateDirectorySeparator); + R_UNLESS(starts_with_dir, fs::ResultInvalidPathFormat()); + + /* Set output. */ + std::memcpy(out_mount_name->str, path, len); + out_mount_name->str[len] = StringTraits::NullTerminator; + + *out_sub_path = sub_path; + R_SUCCEED(); + } + + } + + bool IsValidMountName(const char *name) { + if (name[0] == StringTraits::NullTerminator) { + return false; + } + + if ((('a' <= name[0] && name[0] <= 'z') || ('A' <= name[0] && name[0] <= 'Z')) && name[1] == StringTraits::NullTerminator) { + return false; + } + + size_t len = 0; + for (const char *cur = name; *cur != StringTraits::NullTerminator; ++cur) { + if (*cur == StringTraits::DriveSeparator || *cur == StringTraits::DirectorySeparator) { + return false; + } + + if ((++len) > MountNameLengthMax) { + return false; + } + } + + return util::VerifyUtf8String(name, len); + } + + bool IsReservedMountName(const char *name) { + return name[0] == ReservedMountNamePrefixCharacter; + } + + Result CheckMountName(const char *name) { + R_TRY(CheckMountNameAllowingReserved(name)); + R_UNLESS(!impl::IsReservedMountName(name), fs::ResultInvalidMountName()); + R_SUCCEED(); + } + + Result CheckMountNameAllowingReserved(const char *name) { + R_UNLESS(name != nullptr, fs::ResultInvalidMountName()); + R_UNLESS(impl::IsValidMountName(name), fs::ResultInvalidMountName()); + R_SUCCEED(); + } + + Result FindFileSystem(FileSystemAccessor **out_accessor, const char **out_sub_path, const char *path) { + R_UNLESS(out_accessor != nullptr, fs::ResultUnexpectedInFindFileSystemA()); + R_UNLESS(out_sub_path != nullptr, fs::ResultUnexpectedInFindFileSystemA()); + R_UNLESS(path != nullptr, fs::ResultNullptrArgument()); + + R_UNLESS(strncmp(path, HostRootFileSystemMountName, util::Strnlen(HostRootFileSystemMountName, sizeof(MountName))) != 0, fs::ResultNotMounted()); + + MountName mount_name; + R_TRY(GetMountNameAndSubPath(std::addressof(mount_name), out_sub_path, path)); + + R_RETURN(impl::Find(out_accessor, mount_name.str)); + } + + Result Unmount(const char *name) { + impl::FileSystemAccessor *accessor; + R_TRY(impl::Find(std::addressof(accessor), name)); + + if (accessor->IsFileDataCacheAttachable()) { + /* TODO: Data cache purge */ + } + + impl::Unregister(name); + R_SUCCEED(); + } + +} + +namespace ams::fs { + + Result ConvertToFsCommonPath(char *dst, size_t dst_size, const char *src) { + /* Ensure neither argument is nullptr. */ + AMS_FS_R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + AMS_FS_R_UNLESS(src != nullptr, fs::ResultNullptrArgument()); + + /* Get the mount name and sub path for the path. */ + MountName mount_name; + const char *sub_path; + AMS_FS_R_TRY(impl::GetMountNameAndSubPath(std::addressof(mount_name), std::addressof(sub_path), src)); + + impl::FileSystemAccessor *accessor; + AMS_FS_R_TRY(impl::Find(std::addressof(accessor), mount_name.str)); + AMS_FS_R_TRY(accessor->GetCommonMountName(dst, dst_size)); + + const auto mount_name_len = util::Strnlen(dst, dst_size); + const auto common_path_len = util::SNPrintf(dst + mount_name_len, dst_size - mount_name_len, "%s", sub_path); + + AMS_FS_R_UNLESS(static_cast<size_t>(common_path_len) < dst_size - mount_name_len, fs::ResultTooLongPath()); + R_SUCCEED(); + } + + void Unmount(const char *mount_name) { + AMS_FS_R_ABORT_UNLESS(AMS_FS_IMPL_ACCESS_LOG_UNMOUNT(impl::Unmount(mount_name), mount_name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT, mount_name)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_mount_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_mount_utils.hpp new file mode 100644 index 00000000..3e5de246 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_mount_utils.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fs::impl { + + class FileSystemAccessor; + + Result FindFileSystem(FileSystemAccessor **out_accessor, const char **out_sub_path, const char *path); + + bool IsWindowsDrive(const char *name); + bool IsReservedMountName(const char *name); + bool IsValidMountName(const char *name); + Result CheckMountName(const char *name); + Result CheckMountNameAllowingReserved(const char *name); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_registrar.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_registrar.cpp new file mode 100644 index 00000000..876cb406 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_registrar.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fs_filesystem_accessor.hpp" +#include "fs_user_mount_table.hpp" + +namespace ams::fs::fsa { + + Result Register(const char *name, std::unique_ptr<IFileSystem> &&fs) { + auto accessor = std::make_unique<impl::FileSystemAccessor>(name, std::move(fs)); + R_UNLESS(accessor != nullptr, fs::ResultAllocationMemoryFailedInRegisterA()); + + R_RETURN(impl::Register(std::move(accessor))); + } + + Result Register(const char *name, std::unique_ptr<IFileSystem> &&fs, std::unique_ptr<ICommonMountNameGenerator> &&generator) { + auto accessor = std::make_unique<impl::FileSystemAccessor>(name, std::move(fs), std::move(generator)); + R_UNLESS(accessor != nullptr, fs::ResultAllocationMemoryFailedInRegisterB()); + + R_RETURN(impl::Register(std::move(accessor))); + } + + Result Register(const char *name, std::unique_ptr<IFileSystem> &&fs, std::unique_ptr<ICommonMountNameGenerator> &&generator, bool use_data_cache, bool use_path_cache, bool support_multi_commit) { + auto accessor = std::make_unique<impl::FileSystemAccessor>(name, std::move(fs), std::move(generator)); + R_UNLESS(accessor != nullptr, fs::ResultAllocationMemoryFailedInRegisterB()); + + accessor->SetFileDataCacheAttachable(use_data_cache); + accessor->SetPathBasedFileDataCacheAttachable(use_path_cache); + accessor->SetMultiCommitSupported(support_multi_commit); + + R_RETURN(impl::Register(std::move(accessor))); + } + + void Unregister(const char *name) { + impl::Unregister(name); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_user_directory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_user_directory.cpp new file mode 100644 index 00000000..f5e8cbc4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_user_directory.cpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fs_directory_accessor.hpp" +#include "fs_filesystem_accessor.hpp" + +namespace ams::fs { + + namespace { + + ALWAYS_INLINE impl::DirectoryAccessor *Get(DirectoryHandle handle) { + return reinterpret_cast<impl::DirectoryAccessor *>(handle.handle); + } + + } + + Result ReadDirectory(s64 *out_count, DirectoryEntry *out_entries, DirectoryHandle handle, s64 max_entries) { + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG(Get(handle)->Read(out_count, out_entries, max_entries), handle, AMS_FS_IMPL_ACCESS_LOG_FORMAT_READ_DIRECTORY(out_count, max_entries))); + R_SUCCEED(); + } + + Result GetDirectoryEntryCount(s64 *out, DirectoryHandle handle) { + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG(Get(handle)->GetEntryCount(out), handle, AMS_FS_IMPL_ACCESS_LOG_FORMAT_GET_DIRECTORY_ENTRY_COUNT(out))); + R_SUCCEED(); + } + + void CloseDirectory(DirectoryHandle handle) { + AMS_FS_IMPL_ACCESS_LOG((delete Get(handle), ResultSuccess()), handle, AMS_FS_IMPL_ACCESS_LOG_FORMAT_NONE); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_user_file.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_user_file.cpp new file mode 100644 index 00000000..6b6ec66d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_user_file.cpp @@ -0,0 +1,94 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fs_file_accessor.hpp" +#include "fs_filesystem_accessor.hpp" + +namespace ams::fs { + + namespace { + + ALWAYS_INLINE impl::FileAccessor *Get(FileHandle handle) { + return reinterpret_cast<impl::FileAccessor *>(handle.handle); + } + + Result ReadFileImpl(size_t *out, FileHandle handle, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) { + R_TRY(Get(handle)->Read(out, offset, buffer, size, option)); + R_SUCCEED(); + } + + } + + Result ReadFile(FileHandle handle, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) { + size_t read_size; + AMS_FS_R_TRY(ReadFileImpl(std::addressof(read_size), handle, offset, buffer, size, option)); + AMS_FS_R_UNLESS(read_size == size, fs::ResultOutOfRange()); + R_SUCCEED(); + } + + Result ReadFile(FileHandle handle, s64 offset, void *buffer, size_t size) { + size_t read_size; + AMS_FS_R_TRY(ReadFileImpl(std::addressof(read_size), handle, offset, buffer, size, ReadOption())); + AMS_FS_R_UNLESS(read_size == size, fs::ResultOutOfRange()); + R_SUCCEED(); + } + + Result ReadFile(size_t *out, FileHandle handle, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) { + AMS_FS_R_TRY(ReadFileImpl(out, handle, offset, buffer, size, option)); + R_SUCCEED(); + } + + Result ReadFile(size_t *out, FileHandle handle, s64 offset, void *buffer, size_t size) { + AMS_FS_R_TRY(ReadFileImpl(out, handle, offset, buffer, size, ReadOption())); + R_SUCCEED(); + } + + Result GetFileSize(s64 *out, FileHandle handle) { + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG(Get(handle)->GetSize(out), handle, AMS_FS_IMPL_ACCESS_LOG_FORMAT_GET_FILE_SIZE(out))); + R_SUCCEED(); + } + + Result FlushFile(FileHandle handle) { + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG(Get(handle)->Flush(), handle, AMS_FS_IMPL_ACCESS_LOG_FORMAT_NONE)); + R_SUCCEED(); + } + + Result WriteFile(FileHandle handle, s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) { + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG(Get(handle)->Write(offset, buffer, size, option), handle, AMS_FS_IMPL_ACCESS_LOG_FORMAT_WRITE_FILE(option), offset, size)); + R_SUCCEED(); + } + + Result SetFileSize(FileHandle handle, s64 size) { + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG(Get(handle)->SetSize(size), handle, AMS_FS_IMPL_ACCESS_LOG_FORMAT_SIZE, size)); + R_SUCCEED(); + } + + int GetFileOpenMode(FileHandle handle) { + const int mode = Get(handle)->GetOpenMode(); + AMS_FS_IMPL_ACCESS_LOG(ResultSuccess(), handle, AMS_FS_IMPL_ACCESS_LOG_FORMAT_OPEN_MODE, static_cast<u32>(mode)); + return mode; + } + + void CloseFile(FileHandle handle) { + AMS_FS_IMPL_ACCESS_LOG((delete Get(handle), ResultSuccess()), handle, AMS_FS_IMPL_ACCESS_LOG_FORMAT_NONE); + } + + Result QueryRange(QueryRangeInfo *out, FileHandle handle, s64 offset, s64 size) { + AMS_FS_R_TRY(Get(handle)->OperateRange(out, sizeof(*out), OperationId::QueryRange, offset, size, nullptr, 0)); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_user_filesystem.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_user_filesystem.cpp new file mode 100644 index 00000000..ec3732ce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_user_filesystem.cpp @@ -0,0 +1,230 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fs_filesystem_accessor.hpp" +#include "fs_file_accessor.hpp" +#include "fs_directory_accessor.hpp" +#include "fs_mount_utils.hpp" +#include "fs_user_mount_table.hpp" + +namespace ams::fs { + + Result CreateFile(const char *path, s64 size) { + R_RETURN(CreateFile(path, size, 0)); + } + + Result CreateFile(const char* path, s64 size, int option) { + impl::FileSystemAccessor *accessor; + const char *sub_path; + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_UNLESS_R_SUCCEEDED(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path), AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH, path)); + + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_FILESYSTEM(accessor->CreateFile(sub_path, size, option), nullptr, accessor, AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH_AND_SIZE, path, size)); + R_SUCCEED(); + } + + Result DeleteFile(const char *path) { + impl::FileSystemAccessor *accessor; + const char *sub_path; + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_UNLESS_R_SUCCEEDED(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path), AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH, path)); + + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_FILESYSTEM(accessor->DeleteFile(sub_path), nullptr, accessor, AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH, path)); + R_SUCCEED(); + } + + Result CreateDirectory(const char *path) { + impl::FileSystemAccessor *accessor; + const char *sub_path; + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_UNLESS_R_SUCCEEDED(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path), AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH, path)); + + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_FILESYSTEM(accessor->CreateDirectory(sub_path), nullptr, accessor, AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH, path)); + R_SUCCEED(); + } + + Result DeleteDirectory(const char *path) { + impl::FileSystemAccessor *accessor; + const char *sub_path; + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_UNLESS_R_SUCCEEDED(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path), AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH, path)); + + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_FILESYSTEM(accessor->DeleteDirectory(sub_path), nullptr, accessor, AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH, path)); + R_SUCCEED(); + } + + Result DeleteDirectoryRecursively(const char *path) { + impl::FileSystemAccessor *accessor; + const char *sub_path; + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_UNLESS_R_SUCCEEDED(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path), AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH, path)); + + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_FILESYSTEM(accessor->DeleteDirectoryRecursively(sub_path), nullptr, accessor, AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH, path)); + R_SUCCEED(); + } + + Result RenameFile(const char *old_path, const char *new_path) { + impl::FileSystemAccessor *old_accessor; + impl::FileSystemAccessor *new_accessor; + const char *old_sub_path; + const char *new_sub_path; + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_UNLESS_R_SUCCEEDED(impl::FindFileSystem(std::addressof(old_accessor), std::addressof(old_sub_path), old_path), AMS_FS_IMPL_ACCESS_LOG_FORMAT_RENAME, old_path, new_path)); + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_UNLESS_R_SUCCEEDED(impl::FindFileSystem(std::addressof(new_accessor), std::addressof(new_sub_path), new_path), AMS_FS_IMPL_ACCESS_LOG_FORMAT_RENAME, old_path, new_path)); + + auto rename_impl = [=]() -> Result { + R_UNLESS(old_accessor == new_accessor, fs::ResultRenameToOtherFileSystem()); + R_TRY(old_accessor->RenameFile(old_sub_path, new_sub_path)); + R_SUCCEED(); + }; + + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_FILESYSTEM(rename_impl(), nullptr, old_accessor, AMS_FS_IMPL_ACCESS_LOG_FORMAT_RENAME, old_path, new_path)); + R_SUCCEED(); + } + + Result RenameDirectory(const char *old_path, const char *new_path) { + impl::FileSystemAccessor *old_accessor; + impl::FileSystemAccessor *new_accessor; + const char *old_sub_path; + const char *new_sub_path; + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_UNLESS_R_SUCCEEDED(impl::FindFileSystem(std::addressof(old_accessor), std::addressof(old_sub_path), old_path), AMS_FS_IMPL_ACCESS_LOG_FORMAT_RENAME, old_path, new_path)); + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_UNLESS_R_SUCCEEDED(impl::FindFileSystem(std::addressof(new_accessor), std::addressof(new_sub_path), new_path), AMS_FS_IMPL_ACCESS_LOG_FORMAT_RENAME, old_path, new_path)); + + auto rename_impl = [=]() -> Result { + R_UNLESS(old_accessor == new_accessor, fs::ResultRenameToOtherFileSystem()); + R_TRY(old_accessor->RenameDirectory(old_sub_path, new_sub_path)); + R_SUCCEED(); + }; + + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_FILESYSTEM(rename_impl(), nullptr, old_accessor, AMS_FS_IMPL_ACCESS_LOG_FORMAT_RENAME, old_path, new_path)); + R_SUCCEED(); + } + + Result GetEntryType(DirectoryEntryType *out, const char *path) { + impl::FileSystemAccessor *accessor; + const char *sub_path; + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_UNLESS_R_SUCCEEDED(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path), AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH, path)); + + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_FILESYSTEM(accessor->GetEntryType(out, sub_path), nullptr, accessor, AMS_FS_IMPL_ACCESS_LOG_FORMAT_GET_ENTRY_TYPE(out, path))); + R_SUCCEED(); + } + + Result OpenFile(FileHandle *out_file, const char *path, int mode) { + impl::FileSystemAccessor *accessor; + const char *sub_path; + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_UNLESS_R_SUCCEEDED(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path), AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH_AND_OPEN_MODE, path, static_cast<u32>(mode))); + + std::unique_ptr<impl::FileAccessor> file_accessor; + + auto open_impl = [&]() -> Result { + R_UNLESS(out_file != nullptr, fs::ResultNullptrArgument()); + R_TRY(accessor->OpenFile(std::addressof(file_accessor), sub_path, static_cast<OpenMode>(mode))); + R_SUCCEED(); + }; + + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_FILESYSTEM(open_impl(), nullptr, accessor, AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH_AND_OPEN_MODE, path, static_cast<u32>(mode))); + + out_file->handle = file_accessor.release(); + R_SUCCEED(); + } + + Result OpenDirectory(DirectoryHandle *out_dir, const char *path, int mode) { + impl::FileSystemAccessor *accessor; + const char *sub_path; + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_UNLESS_R_SUCCEEDED(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path), AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH_AND_OPEN_MODE, path, static_cast<u32>(mode))); + + std::unique_ptr<impl::DirectoryAccessor> dir_accessor; + + auto open_impl = [&]() -> Result { + R_UNLESS(out_dir != nullptr, fs::ResultNullptrArgument()); + R_TRY(accessor->OpenDirectory(std::addressof(dir_accessor), sub_path, static_cast<OpenDirectoryMode>(mode))); + R_SUCCEED(); + }; + + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_FILESYSTEM(open_impl(), nullptr, accessor, AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH_AND_OPEN_MODE, path, static_cast<u32>(mode))); + + out_dir->handle = dir_accessor.release(); + R_SUCCEED(); + } + + Result CleanDirectoryRecursively(const char *path) { + impl::FileSystemAccessor *accessor; + const char *sub_path; + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_UNLESS_R_SUCCEEDED(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path), AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH, path)); + + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_FILESYSTEM(accessor->CleanDirectoryRecursively(sub_path), nullptr, accessor, AMS_FS_IMPL_ACCESS_LOG_FORMAT_PATH, path)); + R_SUCCEED(); + } + + Result GetFreeSpaceSize(s64 *out, const char *path) { + /* Find the filesystem without access logging. */ + impl::FileSystemAccessor *accessor; + const char *sub_path; + AMS_FS_R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path)); + + /* Get the total space size. */ + AMS_FS_R_TRY(accessor->GetFreeSpaceSize(out, sub_path)); + + R_SUCCEED(); + } + + Result GetTotalSpaceSize(s64 *out, const char *path) { + /* Find the filesystem without access logging. */ + impl::FileSystemAccessor *accessor; + const char *sub_path; + AMS_FS_R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path)); + + /* Get the total space size. */ + AMS_FS_R_TRY(accessor->GetTotalSpaceSize(out, sub_path)); + + R_SUCCEED(); + } + + Result SetConcatenationFileAttribute(const char *path) { + impl::FileSystemAccessor *accessor; + const char *sub_path; + AMS_FS_R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path)); + + AMS_FS_R_TRY(accessor->QueryEntry(nullptr, 0, nullptr, 0, fsa::QueryId::SetConcatenationFileAttribute, sub_path)); + + R_SUCCEED(); + } + + Result OpenFile(FileHandle *out, std::unique_ptr<fsa::IFile> &&file, int mode) { + AMS_FS_R_UNLESS(out != nullptr, fs::ResultNullptrArgument()); + + auto file_accessor = std::make_unique<impl::FileAccessor>(std::move(file), nullptr, static_cast<OpenMode>(mode)); + AMS_FS_R_UNLESS(file_accessor != nullptr, fs::ResultAllocationMemoryFailedNew()); + out->handle = file_accessor.release(); + + R_SUCCEED(); + } + + namespace { + + Result CommitImpl(const char *mount_name, const char *func_name) { + impl::FileSystemAccessor *accessor{}; + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_UNLESS_R_SUCCEEDED(impl::Find(std::addressof(accessor), mount_name), AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT, mount_name)); + + AMS_FS_R_TRY(AMS_FS_IMPL_ACCESS_LOG_FILESYSTEM_WITH_NAME(accessor->Commit(), nullptr, accessor, func_name, AMS_FS_IMPL_ACCESS_LOG_FORMAT_MOUNT, mount_name)); + R_SUCCEED(); + } + + } + + Result Commit(const char *mount_name) { + R_RETURN(CommitImpl(mount_name, AMS_CURRENT_FUNCTION_NAME)); + } + + Result CommitSaveData(const char *mount_name) { + R_RETURN(CommitImpl(mount_name, AMS_CURRENT_FUNCTION_NAME)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_user_filesystem_for_debug.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_user_filesystem_for_debug.cpp new file mode 100644 index 00000000..7c52c83d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_user_filesystem_for_debug.cpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fs_filesystem_accessor.hpp" +#include "fs_file_accessor.hpp" +#include "fs_directory_accessor.hpp" +#include "fs_mount_utils.hpp" +#include "fs_user_mount_table.hpp" + +namespace ams::fs { + + namespace impl { + + Result GetFileTimeStampRawForDebug(FileTimeStampRaw *out, const char *path) { + impl::FileSystemAccessor *accessor; + const char *sub_path; + R_TRY(impl::FindFileSystem(std::addressof(accessor), std::addressof(sub_path), path)); + + R_TRY(accessor->GetFileTimeStampRaw(out, sub_path)); + + R_SUCCEED(); + } + + } + + Result GetFileTimeStamp(FileTimeStamp *out, const char *path) { + fs::FileTimeStampRaw raw; + AMS_FS_R_TRY(impl::GetFileTimeStampRawForDebug(std::addressof(raw), path)); + + static_assert(sizeof(raw) == sizeof(*out)); + std::memcpy(out, std::addressof(raw), sizeof(raw)); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_user_mount_table.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_user_mount_table.cpp new file mode 100644 index 00000000..d81ed977 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_user_mount_table.cpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fs_user_mount_table.hpp" +#include "fs_mount_table.hpp" +#include "fs_filesystem_accessor.hpp" + +namespace ams::fs::impl { + + namespace { + + constinit MountTable g_mount_table; + + } + + Result Register(std::unique_ptr<FileSystemAccessor> &&fs) { + R_RETURN(g_mount_table.Mount(std::move(fs))); + } + + Result Find(FileSystemAccessor **out, const char *name) { + R_RETURN(g_mount_table.Find(out, name)); + } + + void Unregister(const char *name) { + g_mount_table.Unmount(name); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_user_mount_table.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_user_mount_table.hpp new file mode 100644 index 00000000..5a127bce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/fsa/fs_user_mount_table.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fs::impl { + + class FileSystemAccessor; + + Result Register(std::unique_ptr<FileSystemAccessor> &&fs); + Result Find(FileSystemAccessor **out, const char *name); + void Unregister(const char *name); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_event_notifier_service_object_adapter.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_event_notifier_service_object_adapter.hpp new file mode 100644 index 00000000..8ed9d9a1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_event_notifier_service_object_adapter.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fs::impl { + + class EventNotifierObjectAdapter final : public ::ams::fs::IEventNotifier, public ::ams::fs::impl::Newable { + private: + sf::SharedPointer<fssrv::sf::IEventNotifier> m_object; + public: + EventNotifierObjectAdapter(sf::SharedPointer<fssrv::sf::IEventNotifier> &&obj) : m_object(obj) { /* ... */ } + virtual ~EventNotifierObjectAdapter() { /* ... */ } + private: + virtual Result DoBindEvent(os::SystemEventType *out, os::EventClearMode clear_mode) override { + /* Get the handle. */ + sf::NativeHandle handle; + AMS_FS_R_TRY(m_object->GetEventHandle(std::addressof(handle))); + + /* Create the system event. */ + os::AttachReadableHandleToSystemEvent(out, handle.GetOsHandle(), handle.IsManaged(), clear_mode); + handle.Detach(); + + R_SUCCEED(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_file_system_proxy_service_object.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_file_system_proxy_service_object.hpp new file mode 100644 index 00000000..2435c10f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_file_system_proxy_service_object.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fs::impl { + + sf::SharedPointer<fssrv::sf::IFileSystemProxy> GetFileSystemProxyServiceObject(); + sf::SharedPointer<fssrv::sf::IFileSystemProxyForLoader> GetFileSystemProxyForLoaderServiceObject(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_file_system_service_object_adapter.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_file_system_service_object_adapter.hpp new file mode 100644 index 00000000..1abbfcdd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_file_system_service_object_adapter.hpp @@ -0,0 +1,263 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fs::impl { + + class FileServiceObjectAdapter : public ::ams::fs::impl::Newable, public ::ams::fs::fsa::IFile { + NON_COPYABLE(FileServiceObjectAdapter); + NON_MOVEABLE(FileServiceObjectAdapter); + private: + sf::SharedPointer<fssrv::sf::IFile> m_x; + public: + explicit FileServiceObjectAdapter(sf::SharedPointer<fssrv::sf::IFile> &&o) : m_x(o) { /* ... */} + virtual ~FileServiceObjectAdapter() { /* ... */ } + public: + virtual Result DoRead(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override final { + s64 read_size = 0; + R_TRY(m_x->Read(std::addressof(read_size), offset, sf::OutNonSecureBuffer(buffer, size), static_cast<s64>(size), option)); + + *out = static_cast<size_t>(read_size); + R_SUCCEED(); + } + + virtual Result DoGetSize(s64 *out) override final { + R_RETURN(m_x->GetSize(out)); + } + + virtual Result DoFlush() override final { + R_RETURN(m_x->Flush()); + } + + virtual Result DoWrite(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override final { + R_RETURN(m_x->Write(offset, sf::InNonSecureBuffer(buffer, size), static_cast<s64>(size), option)); + } + + virtual Result DoSetSize(s64 size) override final { + R_RETURN(m_x->SetSize(size)); + } + + virtual Result DoOperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override final { + switch (op_id) { + case OperationId::Invalidate: + { + fs::QueryRangeInfo dummy_range_info; + R_RETURN(m_x->OperateRange(std::addressof(dummy_range_info), static_cast<s32>(op_id), offset, size)); + } + case OperationId::QueryRange: + { + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize()); + + R_RETURN(m_x->OperateRange(reinterpret_cast<fs::QueryRangeInfo *>(dst), static_cast<s32>(op_id), offset, size)); + } + default: + { + R_RETURN(m_x->OperateRangeWithBuffer(sf::OutNonSecureBuffer(dst, dst_size), sf::InNonSecureBuffer(src, src_size), static_cast<s32>(op_id), offset, size)); + } + } + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override final { + AMS_ABORT("Invalid GetDomainObjectId call"); + } + }; + + class DirectoryServiceObjectAdapter : public ::ams::fs::impl::Newable, public ::ams::fs::fsa::IDirectory { + NON_COPYABLE(DirectoryServiceObjectAdapter); + NON_MOVEABLE(DirectoryServiceObjectAdapter); + private: + sf::SharedPointer<fssrv::sf::IDirectory> m_x; + public: + explicit DirectoryServiceObjectAdapter(sf::SharedPointer<fssrv::sf::IDirectory> &&o) : m_x(o) { /* ... */} + virtual ~DirectoryServiceObjectAdapter() { /* ... */ } + public: + virtual Result DoRead(s64 *out_count, DirectoryEntry *out_entries, s64 max_entries) override final { + R_RETURN(m_x->Read(out_count, sf::OutBuffer(out_entries, max_entries * sizeof(*out_entries)))); + } + + virtual Result DoGetEntryCount(s64 *out) override final { + R_RETURN(m_x->GetEntryCount(out)); + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override final { + AMS_ABORT("Invalid GetDomainObjectId call"); + } + }; + + class FileSystemServiceObjectAdapter : public ::ams::fs::impl::Newable, public ::ams::fs::fsa::IFileSystem { + NON_COPYABLE(FileSystemServiceObjectAdapter); + NON_MOVEABLE(FileSystemServiceObjectAdapter); + private: + sf::SharedPointer<fssrv::sf::IFileSystem> m_x; + public: + explicit FileSystemServiceObjectAdapter(sf::SharedPointer<fssrv::sf::IFileSystem> &&o) : m_x(o) { /* ... */} + virtual ~FileSystemServiceObjectAdapter() { /* ... */ } + + sf::SharedPointer<fssrv::sf::IFileSystem> GetFileSystem() const { return m_x; } + private: + static Result GetPathForServiceObject(fssrv::sf::Path *out, const fs::Path &path) { + const size_t len = util::Strlcpy<char>(out->str, path.GetString(), sizeof(out->str)); + R_UNLESS(len < sizeof(out->str), fs::ResultTooLongPath()); + R_SUCCEED(); + } + private: + virtual Result DoOpenFile(std::unique_ptr<fsa::IFile> *out_file, const fs::Path &path, OpenMode mode) override final { + /* Convert the path. */ + fssrv::sf::Path fsp_path; + R_TRY(GetPathForServiceObject(std::addressof(fsp_path), path)); + + /* Open the file. */ + sf::SharedPointer<fssrv::sf::IFile> file; + R_TRY(m_x->OpenFile(std::addressof(file), fsp_path, mode)); + + /* Create the output fsa file. */ + out_file->reset(new FileServiceObjectAdapter(std::move(file))); + R_UNLESS(out_file != nullptr, fs::ResultAllocationMemoryFailedNew()); + + R_SUCCEED(); + } + + virtual Result DoOpenDirectory(std::unique_ptr<fsa::IDirectory> *out_dir, const fs::Path &path, OpenDirectoryMode mode) override final { + /* Convert the path. */ + fssrv::sf::Path fsp_path; + R_TRY(GetPathForServiceObject(std::addressof(fsp_path), path)); + + /* Open the directory. */ + sf::SharedPointer<fssrv::sf::IDirectory> dir; + R_TRY(m_x->OpenDirectory(std::addressof(dir), fsp_path, mode)); + + /* Create the output fsa directory. */ + out_dir->reset(new DirectoryServiceObjectAdapter(std::move(dir))); + R_UNLESS(out_dir != nullptr, fs::ResultAllocationMemoryFailedNew()); + + R_SUCCEED(); + } + + virtual Result DoGetEntryType(DirectoryEntryType *out, const fs::Path &path) override final { + /* Convert the path. */ + fssrv::sf::Path fsp_path; + R_TRY(GetPathForServiceObject(std::addressof(fsp_path), path)); + + R_RETURN(m_x->GetEntryType(out, fsp_path)); + } + + virtual Result DoCommit() override final { + R_RETURN(m_x->Commit()); + } + + virtual Result DoCreateFile(const fs::Path &path, s64 size, int flags) override final { + /* Convert the path. */ + fssrv::sf::Path fsp_path; + R_TRY(GetPathForServiceObject(std::addressof(fsp_path), path)); + + R_RETURN(m_x->CreateFile(fsp_path, size, flags)); + } + + virtual Result DoDeleteFile(const fs::Path &path) override final { + /* Convert the path. */ + fssrv::sf::Path fsp_path; + R_TRY(GetPathForServiceObject(std::addressof(fsp_path), path)); + + R_RETURN(m_x->DeleteFile(fsp_path)); + } + + virtual Result DoCreateDirectory(const fs::Path &path) override final { + /* Convert the path. */ + fssrv::sf::Path fsp_path; + R_TRY(GetPathForServiceObject(std::addressof(fsp_path), path)); + + R_RETURN(m_x->CreateDirectory(fsp_path)); + } + + virtual Result DoDeleteDirectory(const fs::Path &path) override final { + /* Convert the path. */ + fssrv::sf::Path fsp_path; + R_TRY(GetPathForServiceObject(std::addressof(fsp_path), path)); + + R_RETURN(m_x->DeleteDirectory(fsp_path)); + } + + virtual Result DoDeleteDirectoryRecursively(const fs::Path &path) override final { + /* Convert the path. */ + fssrv::sf::Path fsp_path; + R_TRY(GetPathForServiceObject(std::addressof(fsp_path), path)); + + R_RETURN(m_x->DeleteDirectoryRecursively(fsp_path)); + } + + virtual Result DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) override final { + /* Convert the path. */ + fssrv::sf::Path fsp_old_path; + fssrv::sf::Path fsp_new_path; + R_TRY(GetPathForServiceObject(std::addressof(fsp_old_path), old_path)); + R_TRY(GetPathForServiceObject(std::addressof(fsp_new_path), new_path)); + + R_RETURN(m_x->RenameFile(fsp_old_path, fsp_new_path)); + } + + virtual Result DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) override final { + /* Convert the path. */ + fssrv::sf::Path fsp_old_path; + fssrv::sf::Path fsp_new_path; + R_TRY(GetPathForServiceObject(std::addressof(fsp_old_path), old_path)); + R_TRY(GetPathForServiceObject(std::addressof(fsp_new_path), new_path)); + + R_RETURN(m_x->RenameDirectory(fsp_old_path, fsp_new_path)); + } + + virtual Result DoCleanDirectoryRecursively(const fs::Path &path) override final { + /* Convert the path. */ + fssrv::sf::Path fsp_path; + R_TRY(GetPathForServiceObject(std::addressof(fsp_path), path)); + + R_RETURN(m_x->CleanDirectoryRecursively(fsp_path)); + } + + virtual Result DoGetFreeSpaceSize(s64 *out, const fs::Path &path) override final { + /* Convert the path. */ + fssrv::sf::Path fsp_path; + R_TRY(GetPathForServiceObject(std::addressof(fsp_path), path)); + + R_RETURN(m_x->GetFreeSpaceSize(out, fsp_path)); + } + + virtual Result DoGetTotalSpaceSize(s64 *out, const fs::Path &path) override final { + /* Convert the path. */ + fssrv::sf::Path fsp_path; + R_TRY(GetPathForServiceObject(std::addressof(fsp_path), path)); + + R_RETURN(m_x->GetTotalSpaceSize(out, fsp_path)); + } + + virtual Result DoGetFileTimeStampRaw(fs::FileTimeStampRaw *out, const fs::Path &path) override final { + /* Convert the path. */ + fssrv::sf::Path fsp_path; + R_TRY(GetPathForServiceObject(std::addressof(fsp_path), path)); + + R_RETURN(m_x->GetFileTimeStampRaw(out, fsp_path)); + } + + virtual Result DoQueryEntry(char *dst, size_t dst_size, const char *src, size_t src_size, fs::fsa::QueryId query, const fs::Path &path) override { + fssrv::sf::Path fsp_path; + R_TRY(GetPathForServiceObject(std::addressof(fsp_path), path)); + + R_RETURN(m_x->QueryEntry(sf::OutNonSecureBuffer(dst, dst_size), sf::InNonSecureBuffer(src, src_size), static_cast<s32>(query), fsp_path)); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_hash_generator_factory_selector.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_hash_generator_factory_selector.cpp new file mode 100644 index 00000000..38a15ae6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_hash_generator_factory_selector.cpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fs::impl { + + namespace { + + constinit fssystem::ShaHashGeneratorFactorySelector g_sha_hash_generator_factory_selector; + + } + + fssystem::IHash256GeneratorFactorySelector *GetNcaHashGeneratorFactorySelector() { + return std::addressof(g_sha_hash_generator_factory_selector); + } + + fssystem::IHash256GeneratorFactorySelector *GetSaveDataHashGeneratorFactorySelector() { + return std::addressof(g_sha_hash_generator_factory_selector); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_id_string_impl.os.generic.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_id_string_impl.os.generic.cpp new file mode 100644 index 00000000..15dc8c24 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_id_string_impl.os.generic.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <exosphere/pkg1.hpp> + +namespace ams::fs::impl { + + #define ADD_ENUM_CASE(v) case v: return #v + + template<> const char *IdString::ToString<pkg1::KeyGeneration>(pkg1::KeyGeneration id) { + static_assert(pkg1::KeyGeneration_Current == pkg1::KeyGeneration_20_0_0); + switch (id) { + using enum pkg1::KeyGeneration; + case KeyGeneration_1_0_0: return "1.0.0-2.3.0"; + case KeyGeneration_3_0_0: return "3.0.0"; + case KeyGeneration_3_0_1: return "3.0.1-3.0.2"; + case KeyGeneration_4_0_0: return "4.0.0-4.1.0"; + case KeyGeneration_5_0_0: return "5.0.0-5.1.0"; + case KeyGeneration_6_0_0: return "6.0.0-6.1.0"; + case KeyGeneration_6_2_0: return "6.2.0"; + case KeyGeneration_7_0_0: return "7.0.0-8.0.1"; + case KeyGeneration_8_1_0: return "8.1.0-8.1.1"; + case KeyGeneration_9_0_0: return "9.0.0-9.0.1"; + case KeyGeneration_9_1_0: return "9.1.0-12.0.3"; + case KeyGeneration_12_1_0: return "12.1.0"; + case KeyGeneration_13_0_0: return "13.0.0-13.2.1"; + case KeyGeneration_14_0_0: return "14.0.0-14.1.2"; + case KeyGeneration_15_0_0: return "15.0.0-15.0.1"; + case KeyGeneration_16_0_0: return "16.0.0-16.0.3"; + case KeyGeneration_17_0_0: return "17.0.0-17.0.1"; + case KeyGeneration_18_0_0: return "18.0.0-18.1.0"; + case KeyGeneration_19_0_0: return "19.0.0-19.0.1"; + case KeyGeneration_20_0_0: return "20.0.0-"; + default: return "Unknown"; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_library.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_library.cpp new file mode 100644 index 00000000..e256d324 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_library.cpp @@ -0,0 +1,121 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <stratosphere/fssrv/fssrv_interface_adapters.hpp> +#include "fs_library.hpp" +#include "fs_file_system_service_object_adapter.hpp" +#include "../../fssrv/impl/fssrv_allocator_for_service_framework.hpp" + +namespace ams::fs::impl { + + #if !defined(ATMOSPHERE_OS_HORIZON) + namespace { + + constexpr size_t SystemHeapSize = 8_MB; + alignas(os::MemoryPageSize) constinit u8 g_system_heap[SystemHeapSize]; + + ALWAYS_INLINE auto &GetSystemHeapAllocator() { + AMS_FUNCTION_LOCAL_STATIC(mem::StandardAllocator, s_system_heap_allocator, g_system_heap, sizeof(g_system_heap)); + return s_system_heap_allocator; + } + + constinit util::optional<fssrv::MemoryResourceFromStandardAllocator> g_system_heap_memory_resource; + + void *AllocateForSystem(size_t size) { return g_system_heap_memory_resource->Allocate(size); } + void DeallocateForSystem(void *p, size_t size) { return g_system_heap_memory_resource->Deallocate(p, size); } + + [[maybe_unused]] constexpr size_t BufferPoolSize = 6_MB; + [[maybe_unused]] constexpr size_t DeviceBufferSize = 8_MB; + [[maybe_unused]] constexpr size_t DeviceWorkBufferSize = os::MemoryPageSize; + [[maybe_unused]] constexpr size_t BufferManagerHeapSize = 14_MB; + + constexpr size_t DeviceWorkBufferRequiredSize = 0x140; + + static_assert(util::IsAligned(BufferManagerHeapSize, os::MemoryBlockUnitSize)); + + //alignas(os::MemoryPageSize) u8 g_buffer_pool[BufferPoolSize]; + alignas(os::MemoryPageSize) u8 g_device_buffer[DeviceBufferSize]; + alignas(os::MemoryPageSize) u8 g_device_work_buffer[DeviceWorkBufferSize]; + //alignas(os::MemoryPageSize) u8 g_buffer_manager_heap[BufferManagerHeapSize]; + // + //alignas(os::MemoryPageSize) u8 g_buffer_manager_work_buffer[64_KB]; + /* TODO: Other work buffers. */ + + /* TODO: Implement pooled threads. */ + // constexpr int PooledThreadCount = 12; + // constexpr size_t PooledThreadStackSize = 32_KB; + // fssystem::PooledThread g_pooled_threads[PooledThreadCount]; + + /* FileSystem creators. */ + constinit util::optional<fssrv::fscreator::LocalFileSystemCreator> g_local_fs_creator; + constinit util::optional<fssrv::fscreator::SubDirectoryFileSystemCreator> g_subdir_fs_creator; + + constinit fssrv::fscreator::FileSystemCreatorInterfaces g_fs_creator_interfaces = {}; + + Result InitializeFileSystemLibraryImpl() { + /* Set system allocator. */ + fssystem::InitializeAllocator(::ams::fs::impl::Allocate, ::ams::fs::impl::Deallocate); + fssystem::InitializeAllocatorForSystem(::ams::fs::impl::AllocateForSystem, ::ams::fs::impl::DeallocateForSystem); + + /* TODO: Many things. */ + g_system_heap_memory_resource.emplace(std::addressof(GetSystemHeapAllocator())); + + fssystem::InitializeBufferPool(reinterpret_cast<char *>(g_device_buffer), DeviceBufferSize, reinterpret_cast<char *>(g_device_work_buffer), DeviceWorkBufferRequiredSize); + + /* Setup fscreators/interfaces. */ + g_local_fs_creator.emplace(true); + g_subdir_fs_creator.emplace(); + + g_fs_creator_interfaces.local_fs_creator = std::addressof(*g_local_fs_creator); + g_fs_creator_interfaces.subdir_fs_creator = std::addressof(*g_subdir_fs_creator); + + /* Initialize fssrv. */ + const fssrv::FileSystemProxyConfiguration config = { + .m_fs_creator_interfaces = std::addressof(g_fs_creator_interfaces), + .m_base_storage_service_impl = nullptr /* TODO */, + .m_base_file_system_service_impl = nullptr /* TODO */, + .m_nca_file_system_service_impl = nullptr /* TODO */, + .m_save_data_file_system_service_impl = nullptr /* TODO */, + .m_access_failure_management_service_impl = nullptr /* TODO */, + .m_time_service_impl = nullptr /* TODO */, + .m_status_report_service_impl = nullptr /* TODO */, + .m_program_registry_service_impl = nullptr /* TODO */, + .m_access_log_service_impl = nullptr /* TODO */, + .m_debug_configuration_service_impl = nullptr /* TODO */, + }; + + fssrv::InitializeForFileSystemProxy(config); + R_SUCCEED(); + } + + class FileSystemLibraryInitializer { + public: + FileSystemLibraryInitializer() { + R_ABORT_UNLESS(InitializeFileSystemLibraryImpl()); + } + }; + + } + #endif + + void InitializeFileSystemLibrary() { + #if !defined(ATMOSPHERE_OS_HORIZON) + AMS_FUNCTION_LOCAL_STATIC(FileSystemLibraryInitializer, s_library_initializer); + AMS_UNUSED(s_library_initializer); + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_library.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_library.hpp new file mode 100644 index 00000000..51c1d3c6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_library.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fs::impl { + + void InitializeFileSystemLibrary(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_remote_device_operator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_remote_device_operator.hpp new file mode 100644 index 00000000..d3f656c2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_remote_device_operator.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fs::impl { + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemoteDeviceOperator { + private: + ::FsDeviceOperator m_operator; + public: + RemoteDeviceOperator(::FsDeviceOperator &o) : m_operator(o) { /* ... */ } + + virtual ~RemoteDeviceOperator() { + fsDeviceOperatorClose(std::addressof(m_operator)); + } + public: + Result IsSdCardInserted(ams::sf::Out<bool> out) { + R_RETURN(fsDeviceOperatorIsSdCardInserted(std::addressof(m_operator), out.GetPointer())); + } + + Result GetSdCardSpeedMode(ams::sf::Out<s64> out) { + R_RETURN(fsDeviceOperatorGetSdCardSpeedMode(std::addressof(m_operator), out.GetPointer())); + } + + Result GetSdCardCid(ams::sf::OutBuffer out, s64 size) { + R_RETURN(fsDeviceOperatorGetSdCardCid(std::addressof(m_operator), out.GetPointer(), out.GetSize(), size)); + } + + Result GetSdCardUserAreaSize(ams::sf::Out<s64> out) { + R_RETURN(fsDeviceOperatorGetSdCardUserAreaSize(std::addressof(m_operator), out.GetPointer())); + } + + Result GetSdCardProtectedAreaSize(ams::sf::Out<s64> out) { + R_RETURN(fsDeviceOperatorGetSdCardProtectedAreaSize(std::addressof(m_operator), out.GetPointer())); + } + + Result GetAndClearSdCardErrorInfo(ams::sf::Out<fs::StorageErrorInfo> out_sei, ams::sf::Out<s64> out_size, ams::sf::OutBuffer out_buf, s64 size) { + static_assert(sizeof(::FsStorageErrorInfo) == sizeof(fs::StorageErrorInfo)); + R_RETURN(fsDeviceOperatorGetAndClearSdCardErrorInfo(std::addressof(m_operator), reinterpret_cast<::FsStorageErrorInfo *>(out_sei.GetPointer()), out_size.GetPointer(), out_buf.GetPointer(), out_buf.GetSize(), size)); + } + + Result GetMmcCid(ams::sf::OutBuffer out, s64 size) { + R_RETURN(fsDeviceOperatorGetMmcCid(std::addressof(m_operator), out.GetPointer(), out.GetSize(), size)); + } + + Result GetMmcSpeedMode(ams::sf::Out<s64> out) { + R_RETURN(fsDeviceOperatorGetMmcSpeedMode(std::addressof(m_operator), out.GetPointer())); + } + + Result GetMmcPatrolCount(ams::sf::Out<u32> out) { + R_RETURN(fsDeviceOperatorGetMmcPatrolCount(std::addressof(m_operator), out.GetPointer())); + } + + Result GetAndClearMmcErrorInfo(ams::sf::Out<fs::StorageErrorInfo> out_sei, ams::sf::Out<s64> out_size, ams::sf::OutBuffer out_buf, s64 size) { + static_assert(sizeof(::FsStorageErrorInfo) == sizeof(fs::StorageErrorInfo)); + R_RETURN(fsDeviceOperatorGetAndClearMmcErrorInfo(std::addressof(m_operator), reinterpret_cast<::FsStorageErrorInfo *>(out_sei.GetPointer()), out_size.GetPointer(), out_buf.GetPointer(), out_buf.GetSize(), size)); + } + + Result GetMmcExtendedCsd(ams::sf::OutBuffer out, s64 size) { + R_RETURN(fsDeviceOperatorGetMmcExtendedCsd(std::addressof(m_operator), out.GetPointer(), out.GetSize(), size)); + } + + Result IsGameCardInserted(ams::sf::Out<bool> out) { + R_RETURN(fsDeviceOperatorIsGameCardInserted(std::addressof(m_operator), out.GetPointer())); + } + + Result GetGameCardHandle(ams::sf::Out<u32> out) { + static_assert(sizeof(::FsGameCardHandle) == sizeof(u32)); + R_RETURN(fsDeviceOperatorGetGameCardHandle(std::addressof(m_operator), reinterpret_cast<::FsGameCardHandle *>(out.GetPointer()))); + } + + Result GetGameCardIdSet(ams::sf::OutBuffer out, s64 size) { + R_RETURN(fsDeviceOperatorGetGameCardIdSet(std::addressof(m_operator), out.GetPointer(), out.GetSize(), size)); + } + + Result GetGameCardErrorReportInfo(ams::sf::Out<fs::GameCardErrorReportInfo> out) { + static_assert(sizeof(::FsGameCardErrorReportInfo) == sizeof(fs::GameCardErrorReportInfo)); + R_RETURN(fsDeviceOperatorGetGameCardErrorReportInfo(std::addressof(m_operator), reinterpret_cast<::FsGameCardErrorReportInfo *>(out.GetPointer()))); + } + + Result GetGameCardDeviceId(ams::sf::OutBuffer out, s64 size) { + R_RETURN(fsDeviceOperatorGetGameCardDeviceId(std::addressof(m_operator), out.GetPointer(), out.GetSize(), size)); + } + }; + static_assert(fssrv::sf::IsIDeviceOperator<RemoteDeviceOperator>); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_remote_event_notifier.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_remote_event_notifier.hpp new file mode 100644 index 00000000..c31eeaf3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/impl/fs_remote_event_notifier.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fs::impl { + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemoteEventNotifier { + private: + ::FsEventNotifier m_notifier; + public: + RemoteEventNotifier(::FsEventNotifier &n) : m_notifier(n) { /* ... */ } + + virtual ~RemoteEventNotifier() { + fsEventNotifierClose(std::addressof(m_notifier)); + } + public: + Result GetEventHandle(ams::sf::OutCopyHandle out) { + ::Event e; + R_TRY(fsEventNotifierGetEventHandle(std::addressof(m_notifier), std::addressof(e), false)); + + out.SetValue(e.revent, true); + R_SUCCEED(); + } + }; + static_assert(fssrv::sf::IsIEventNotifier<RemoteEventNotifier>); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/tests/fs_path_formatter_tests.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/tests/fs_path_formatter_tests.cpp new file mode 100644 index 00000000..f9dcbd77 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/tests/fs_path_formatter_tests.cpp @@ -0,0 +1,623 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fs { + + namespace { + + constexpr size_t DefaultPathBufferSize = fs::EntryNameLengthMax + 1; + + consteval PathFlags DecodeFlags(const char *desc) { + PathFlags flags{}; + + while (*desc) { + switch (*(desc++)) { + case 'B': + flags.AllowBackslash(); + break; + case 'E': + flags.AllowEmptyPath(); + break; + case 'M': + flags.AllowMountName(); + break; + case 'R': + flags.AllowRelativePath(); + break; + case 'W': + flags.AllowWindowsPath(); + break; + case 'C': + flags.AllowAllCharacters(); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + return flags; + } + + consteval size_t Strlen(const char *p) { + size_t len = 0; + + while (p[len] != StringTraits::NullTerminator) { + ++len; + } + + return len; + } + + //#define ENABLE_PRINT_FORMAT_TEST_DEBUGGING + + #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) + template<u32 Expected, u32 Actual> + struct Print { + constexpr Print() { + if (std::is_constant_evaluated()) { + __builtin_unreachable(); + } + } + }; + + template<u32 E = 0, u32 A = 0, size_t N = 0> + consteval void PrintResultMismatchImpl(u32 e, u32 a) { + if constexpr (N == 32) { + Print<E, A>{}; + } else { + const bool is_e = (e & (1 << N)) != 0; + const bool is_a = (a & (1 << N)) != 0; + if (is_e) { + if (is_a) { + PrintResultMismatchImpl<E | (1 << N), A | (1 << N), N + 1>(e, a); + } else { + PrintResultMismatchImpl<E | (1 << N), A | (0 << N), N + 1>(e, a); + } + } else { + if (is_a) { + PrintResultMismatchImpl<E | (0 << N), A | (1 << N), N + 1>(e, a); + } else { + PrintResultMismatchImpl<E | (0 << N), A | (0 << N), N + 1>(e, a); + } + } + } + } + + consteval void PrintResultMismatch(const Result &lhs, const Result &rhs) { + PrintResultMismatchImpl(lhs.GetDescription(), rhs.GetDescription()); + } + + template<size_t Index, char Expected, char Actual> + struct PrintMismatchChar { + constexpr PrintMismatchChar() { + if (std::is_constant_evaluated()) { + __builtin_unreachable(); + } + } + }; + + template<size_t Ix, char Expected, size_t C = 0> + consteval void PrintCharacterMismatch(char c) { + if (c == static_cast<char>(C)) { + PrintMismatchChar<Ix, Expected, static_cast<char>(C)>{}; + return; + } + + if constexpr (C < std::numeric_limits<unsigned char>::max()) { + PrintCharacterMismatch<Ix, Expected, C + 1>(c); + } + } + + template<size_t Ix, char C = 0> + consteval void PrintCharacterMismatch(char c, char c2) { + if (c == static_cast<char>(C)) { + PrintCharacterMismatch<Ix, static_cast<char>(C)>(c2); + return; + } + + + if constexpr (C < std::numeric_limits<unsigned char>::max()) { + PrintCharacterMismatch<Ix, C + 1>(c, c2); + } + } + + template<size_t Ix = 0> + consteval void PrintCharacterMismatch(size_t ix, char c, char c2) { + if (Ix == ix) { + PrintCharacterMismatch<Ix>(c, c2); + return; + } + + if constexpr (Ix <= DefaultPathBufferSize) { + PrintCharacterMismatch<Ix + 1>(ix, c, c2); + } + } + #endif + + consteval bool TestNormalizedImpl(const char *path, const PathFlags &flags, size_t buffer_size, const char *normalized, Result expected_result) { + /* Allocate a buffer to normalize into. */ + char *buffer = new char[buffer_size]; + ON_SCOPE_EXIT { delete[] buffer; }; + buffer[buffer_size - 1] = '\xcc'; + + /* Perform normalization. */ + const Result actual_result = PathFormatter::Normalize(buffer, buffer_size, path, Strlen(path) + 1, flags); + + /* Check that the expected result matches the actual. */ + if (actual_result.GetValue() != expected_result.GetValue()) { + #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) + PrintResultMismatch(expected_result.GetValue(), actual_result.GetValue()); + #endif + + return false; + } + + /* Check that the expected string matches the actual. */ + for (size_t i = 0; i < buffer_size; ++i) { + if (normalized[i] != StringTraits::NullTerminator || R_SUCCEEDED(expected_result)) { + if (buffer[i] != normalized[i]) { + #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) + PrintCharacterMismatch(i, normalized[i], buffer[i]); + #endif + return false; + } + } + + if (normalized[i] == StringTraits::NullTerminator || buffer[i] == StringTraits::NullTerminator) { + break; + } + } + + return true; + } + + struct NormalizeTestData { + const char *path; + const char *flag_desc; + const char *normalized; + Result result; + size_t buffer_size = DefaultPathBufferSize; + }; + + template<size_t N, size_t Ix = 0> + consteval bool DoNormalizeTests(const NormalizeTestData (&tests)[N]) { + if constexpr (Ix >= N) { + return true; + } + + const auto &test = tests[Ix]; + if (!TestNormalizedImpl(test.path, DecodeFlags(test.flag_desc), test.buffer_size, test.normalized, test.result)) { + return false; + } + + if constexpr (Ix < N) { + return DoNormalizeTests<N, Ix + 1>(tests); + } else { + AMS_ASSUME(false); + } + } + + consteval bool TestNormalizeEmptyPath() { + constexpr NormalizeTestData Tests[] = { + { "", "", "", fs::ResultInvalidPathFormat() }, + { "", "E", "", ResultSuccess() }, + { "/aa/bb/../cc", "E", "/aa/cc", ResultSuccess() }, + }; + + return DoNormalizeTests(Tests); + } + static_assert(TestNormalizeEmptyPath()); + + consteval bool TestNormalizeMountName() { + constexpr NormalizeTestData Tests[] = { + { "mount:/aa/bb", "", "", fs::ResultInvalidPathFormat() }, + { "mount:/aa/bb", "W", "", fs::ResultInvalidPathFormat() }, + { "mount:/aa/bb", "M", "mount:/aa/bb", ResultSuccess() }, + { "mount:/aa/./bb", "M", "mount:/aa/bb", ResultSuccess() }, + { "mount:\\aa\\bb", "M", "mount:", fs::ResultInvalidPathFormat() }, + { "m:/aa/bb", "M", "", fs::ResultInvalidPathFormat() }, + { "mo>unt:/aa/bb", "M", "", fs::ResultInvalidCharacter() }, + { "moun?t:/aa/bb", "M", "", fs::ResultInvalidCharacter() }, + { "mo&unt:/aa/bb", "M", "mo&unt:/aa/bb", ResultSuccess() }, + { "/aa/./bb", "M", "/aa/bb", ResultSuccess() }, + { "mount/aa/./bb", "M", "", fs::ResultInvalidPathFormat() } + }; + + return DoNormalizeTests(Tests); + } + static_assert(TestNormalizeMountName()); + + consteval bool TestNormalizeWindowsPath() { + constexpr NormalizeTestData Tests[] = { + { "c:/aa/bb", "", "", fs::ResultInvalidPathFormat() }, + { "c:\\aa\\bb", "", "", fs::ResultInvalidCharacter() }, + { "\\\\host\\share", "", "", fs::ResultInvalidCharacter() }, + { "\\\\.\\c:\\", "", "", fs::ResultInvalidCharacter() }, + { "\\\\.\\c:/aa/bb/.", "", "", fs::ResultInvalidCharacter() }, + { "\\\\?\\c:\\", "", "", fs::ResultInvalidCharacter() }, + { "mount:\\\\host\\share\\aa\\bb", "M", "mount:", fs::ResultInvalidCharacter() }, + { "mount:\\\\host/share\\aa\\bb", "M", "mount:", fs::ResultInvalidCharacter() }, + { "c:\\aa\\..\\..\\..\\bb", "W", "c:/bb", ResultSuccess() }, + { "mount:/\\\\aa\\..\\bb", "MW", "mount:", fs::ResultInvalidPathFormat() }, + { "mount:/c:\\aa\\..\\bb", "MW", "mount:c:/bb", ResultSuccess() }, + { "mount:/aa/bb", "MW", "mount:/aa/bb", ResultSuccess() }, + { "/mount:/aa/bb", "MW", "/", fs::ResultInvalidCharacter() }, + { "/mount:/aa/bb", "W", "/", fs::ResultInvalidCharacter() }, + { "a:aa/../bb", "MW", "a:aa/bb", ResultSuccess() }, + { "a:aa\\..\\bb", "MW", "a:aa/bb", ResultSuccess() }, + { "/a:aa\\..\\bb", "W", "/", fs::ResultInvalidCharacter() }, + { "\\\\?\\c:\\.\\aa", "W", "\\\\?\\c:/aa", ResultSuccess() }, + { "\\\\.\\c:\\.\\aa", "W", "\\\\.\\c:/aa", ResultSuccess() }, + { "\\\\.\\mount:\\.\\aa", "W", "\\\\./", fs::ResultInvalidCharacter() }, + { "\\\\./.\\aa", "W", "\\\\./aa", ResultSuccess() }, + { "\\\\/aa", "W", "", fs::ResultInvalidPathFormat() }, + { "\\\\\\aa", "W", "", fs::ResultInvalidPathFormat() }, + { "\\\\", "W", "/", ResultSuccess() }, + { "\\\\host\\share", "W", "\\\\host\\share/", ResultSuccess() }, + { "\\\\host\\share\\path", "W", "\\\\host\\share/path", ResultSuccess() }, + { "\\\\host\\share\\path\\aa\\bb\\..\\cc\\.", "W", "\\\\host\\share/path/aa/cc", ResultSuccess() }, + { "\\\\host\\", "W", "", fs::ResultInvalidPathFormat() }, + { "\\\\ho$st\\share\\path", "W", "", fs::ResultInvalidCharacter() }, + { "\\\\host:\\share\\path", "W", "", fs::ResultInvalidCharacter() }, + { "\\\\..\\share\\path", "W", "", fs::ResultInvalidPathFormat() }, + { "\\\\host\\s:hare\\path", "W", "", fs::ResultInvalidCharacter() }, + { "\\\\host\\.\\path", "W", "", fs::ResultInvalidPathFormat() }, + { "\\\\host\\..\\path", "W", "", fs::ResultInvalidPathFormat() }, + { "\\\\host\\sha:re", "W", "", fs::ResultInvalidCharacter() }, + { ".\\\\host\\share", "RW", "..\\\\host\\share/", ResultSuccess() } + }; + + return DoNormalizeTests(Tests); + } + static_assert(TestNormalizeWindowsPath()); + + consteval bool TestNormalizeRelativePath() { + constexpr NormalizeTestData Tests[] = { + { "./aa/bb", "", "", fs::ResultInvalidPathFormat() }, + { "./aa/bb/../cc", "R", "./aa/cc", ResultSuccess() }, + { ".\\aa/bb/../cc", "R", "..", fs::ResultInvalidCharacter() }, + { ".", "R", ".", ResultSuccess() }, + { "../aa/bb", "R", "", fs::ResultDirectoryUnobtainable() }, + { "/aa/./bb", "R", "/aa/bb", ResultSuccess() }, + { "mount:./aa/bb", "MR", "mount:./aa/bb", ResultSuccess() }, + { "mount:./aa/./bb", "MR", "mount:./aa/bb", ResultSuccess() }, + { "mount:./aa/bb", "M", "mount:", fs::ResultInvalidPathFormat() } + }; + + return DoNormalizeTests(Tests); + } + static_assert(TestNormalizeRelativePath()); + + consteval bool TestNormalizeBackslash() { + constexpr NormalizeTestData Tests[] = { + { "\\aa\\bb\\..\\cc", "", "", fs::ResultInvalidPathFormat() }, + { "\\aa\\bb\\..\\cc", "B", "", fs::ResultInvalidPathFormat() }, + { "/aa\\bb\\..\\cc", "", "", fs::ResultInvalidCharacter() }, + { "/aa\\bb\\..\\cc", "B", "/cc", ResultSuccess() }, + { "/aa\\bb\\cc", "", "", fs::ResultInvalidCharacter() }, + { "/aa\\bb\\cc", "B", "/aa\\bb\\cc", ResultSuccess() }, + { "\\\\host\\share\\path\\aa\\bb\\cc", "W", "\\\\host\\share/path/aa/bb/cc", ResultSuccess() }, + { "\\\\host\\share\\path\\aa\\bb\\cc", "WB", "\\\\host\\share/path/aa/bb/cc", ResultSuccess() }, + { "/aa/bb\\../cc/..\\dd\\..\\ee/..", "", "", fs::ResultInvalidCharacter() }, + { "/aa/bb\\../cc/..\\dd\\..\\ee/..", "B", "/aa", ResultSuccess() } + }; + + return DoNormalizeTests(Tests); + } + static_assert(TestNormalizeBackslash()); + + consteval bool TestNormalizeAllowAllChars() { + constexpr NormalizeTestData Tests[] = { + { "/aa/b:b/cc", "", "/aa/", fs::ResultInvalidCharacter() }, + { "/aa/b*b/cc", "", "/aa/", fs::ResultInvalidCharacter() }, + { "/aa/b?b/cc", "", "/aa/", fs::ResultInvalidCharacter() }, + { "/aa/b<b/cc", "", "/aa/", fs::ResultInvalidCharacter() }, + { "/aa/b>b/cc", "", "/aa/", fs::ResultInvalidCharacter() }, + { "/aa/b|b/cc", "", "/aa/", fs::ResultInvalidCharacter() }, + { "/aa/b:b/cc", "C", "/aa/b:b/cc", ResultSuccess() }, + { "/aa/b*b/cc", "C", "/aa/b*b/cc", ResultSuccess() }, + { "/aa/b?b/cc", "C", "/aa/b?b/cc", ResultSuccess() }, + { "/aa/b<b/cc", "C", "/aa/b<b/cc", ResultSuccess() }, + { "/aa/b>b/cc", "C", "/aa/b>b/cc", ResultSuccess() }, + { "/aa/b|b/cc", "C", "/aa/b|b/cc", ResultSuccess() }, + { "/aa/b'b/cc", "", "/aa/b'b/cc", ResultSuccess() }, + { "/aa/b\"b/cc", "", "/aa/b\"b/cc", ResultSuccess() }, + { "/aa/b(b/cc", "", "/aa/b(b/cc", ResultSuccess() }, + { "/aa/b)b/cc", "", "/aa/b)b/cc", ResultSuccess() }, + { "/aa/b'b/cc", "C", "/aa/b'b/cc", ResultSuccess() }, + { "/aa/b\"b/cc", "C", "/aa/b\"b/cc", ResultSuccess() }, + { "/aa/b(b/cc", "C", "/aa/b(b/cc", ResultSuccess() }, + { "/aa/b)b/cc", "C", "/aa/b)b/cc", ResultSuccess() }, + { "mount:/aa/b<b/cc", "MC", "mount:/aa/b<b/cc", ResultSuccess() }, + { "mo>unt:/aa/bb/cc", "MC", "", fs::ResultInvalidCharacter() } + }; + + return DoNormalizeTests(Tests); + } + static_assert(TestNormalizeAllowAllChars()); + + consteval bool TestNormalizeAll() { + constexpr NormalizeTestData Tests[] = { + { "mount:./aa/bb", "WRM", "mount:./aa/bb", ResultSuccess() }, + { "mount:./aa/bb\\cc/dd", "WRM", "mount:./aa/bb/cc/dd", ResultSuccess() }, + { "mount:./aa/bb\\cc/dd", "WRMB", "mount:./aa/bb/cc/dd", ResultSuccess() }, + { "mount:./.c:/aa/bb", "RM", "mount:./", fs::ResultInvalidCharacter() }, + { "mount:.c:/aa/bb", "WRM", "mount:./", fs::ResultInvalidCharacter() }, + { "mount:./cc:/aa/bb", "WRM", "mount:./", fs::ResultInvalidCharacter() }, + { "mount:./\\\\host\\share/aa/bb", "MW", "mount:", fs::ResultInvalidPathFormat() }, + { "mount:./\\\\host\\share/aa/bb", "WRM", "mount:.\\\\host\\share/aa/bb", ResultSuccess() }, + { "mount:.\\\\host\\share/aa/bb", "WRM", "mount:..\\\\host\\share/aa/bb", ResultSuccess() }, + { "mount:..\\\\host\\share/aa/bb", "WRM", "mount:.", fs::ResultDirectoryUnobtainable() }, + { ".\\\\host\\share/aa/bb", "WRM", "..\\\\host\\share/aa/bb", ResultSuccess() }, + { "..\\\\host\\share/aa/bb", "WRM", ".", fs::ResultDirectoryUnobtainable() }, + { "mount:\\\\host\\share/aa/bb", "MW", "mount:\\\\host\\share/aa/bb", ResultSuccess() }, + { "mount:\\aa\\bb", "BM", "mount:", fs::ResultInvalidPathFormat() }, + { "mount:/aa\\bb", "BM", "mount:/aa\\bb", ResultSuccess() }, + { ".//aa/bb", "RW", "./aa/bb", ResultSuccess() }, + { "./aa/bb", "R", "./aa/bb", ResultSuccess() }, + { "./c:/aa/bb", "RW", "./", fs::ResultInvalidCharacter() }, + { "mount:./aa/b:b\\cc/dd", "WRMBC", "mount:./aa/b:b/cc/dd", ResultSuccess() } + }; + + return DoNormalizeTests(Tests); + } + static_assert(TestNormalizeAll()); + + consteval bool TestNormalizeSmallBuffer() { + constexpr NormalizeTestData Tests[] = { + { "/aa/bb", "M", "", fs::ResultTooLongPath(), 1}, + { "mount:/aa/bb", "MR", "", fs::ResultTooLongPath(), 6 }, + { "mount:/aa/bb", "MR", "mount:", fs::ResultTooLongPath(), 7 }, + { "aa/bb", "MR", "./", fs::ResultTooLongPath(), 3 }, + { "\\\\host\\share", "W", "\\\\host\\share", fs::ResultTooLongPath(), 13 } + }; + + return DoNormalizeTests(Tests); + } + static_assert(TestNormalizeSmallBuffer()); + + consteval bool TestIsNormalizedImpl(const char *path, const PathFlags &flags, bool expected_normalized, size_t expected_size, Result expected_result) { + /* Perform normalization checking. */ + bool actual_normalized; + size_t actual_size; + const Result actual_result = PathFormatter::IsNormalized(std::addressof(actual_normalized), std::addressof(actual_size), path, flags); + + /* Check that the expected result matches the actual. */ + if (actual_result.GetValue() != expected_result.GetValue()) { + #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) + PrintResultMismatch(expected_result.GetValue(), actual_result.GetValue()); + #endif + + return false; + } + + /* Check that the expected output matches the actual. */ + if (R_SUCCEEDED(expected_result)) { + if (expected_normalized != actual_normalized) { + #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) + PrintResultMismatchImpl(static_cast<u32>(expected_normalized), static_cast<u32>(actual_normalized)); + #endif + return false; + } + + if (expected_normalized) { + if (expected_size != actual_size) { + #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) + PrintResultMismatchImpl(static_cast<u32>(expected_size), static_cast<u32>(actual_size)); + #endif + return false; + } + } + } + + return true; + } + + struct IsNormalizedTestData { + const char *path; + const char *flag_desc; + bool normalized; + size_t len; + Result result; + }; + + template<size_t N, size_t Ix = 0> + consteval bool DoIsNormalizedTests(const IsNormalizedTestData (&tests)[N]) { + if constexpr (Ix >= N) { + return true; + } + + const auto &test = tests[Ix]; + if (!TestIsNormalizedImpl(test.path, DecodeFlags(test.flag_desc), test.normalized, test.len, test.result)) { + return false; + } + + if constexpr (Ix < N) { + return DoIsNormalizedTests<N, Ix + 1>(tests); + } else { + AMS_ASSUME(false); + } + } + + consteval bool TestIsNormalizedEmptyPath() { + constexpr IsNormalizedTestData Tests[] = { + { "", "", false, 0, fs::ResultInvalidPathFormat() }, + { "", "E", true, 0, ResultSuccess() }, + { "/aa/bb/../cc", "E", false, 0, ResultSuccess() } + }; + + return DoIsNormalizedTests(Tests); + } + static_assert(TestIsNormalizedEmptyPath()); + + consteval bool TestIsNormalizedMountName() { + constexpr IsNormalizedTestData Tests[] = { + { "mount:/aa/bb", "", false, 0, fs::ResultInvalidPathFormat() }, + { "mount:/aa/bb", "W", false, 0, fs::ResultInvalidPathFormat() }, + { "mount:/aa/bb", "M", true, 12, ResultSuccess() }, + { "mount:/aa/./bb", "M", false, 6, ResultSuccess() }, + { "mount:\\aa\\bb", "M", false, 0, fs::ResultInvalidPathFormat() }, + { "m:/aa/bb", "M", false, 0, fs::ResultInvalidPathFormat() }, + { "mo>unt:/aa/bb", "M", false, 0, fs::ResultInvalidCharacter() }, + { "moun?t:/aa/bb", "M", false, 0, fs::ResultInvalidCharacter() }, + { "mo&unt:/aa/bb", "M", true, 13, ResultSuccess() }, + { "/aa/./bb", "M", false, 0, ResultSuccess() }, + { "mount/aa/./bb", "M", false, 0, fs::ResultInvalidPathFormat() } + }; + + return DoIsNormalizedTests(Tests); + } + static_assert(TestIsNormalizedMountName()); + + consteval bool TestIsNormalizedWindowsPath() { + constexpr IsNormalizedTestData Tests[] = { + { "c:/aa/bb", "", false, 0, fs::ResultInvalidPathFormat() }, + { "c:\\aa\\bb", "", false, 0, fs::ResultInvalidPathFormat() }, + { "\\\\host\\share", "", false, 0, fs::ResultInvalidPathFormat() }, + { "\\\\.\\c:\\", "", false, 0, fs::ResultInvalidPathFormat() }, + { "\\\\.\\c:/aa/bb/.", "", false, 0, fs::ResultInvalidPathFormat() }, + { "\\\\?\\c:\\", "", false, 0, fs::ResultInvalidPathFormat() }, + { "mount:\\\\host\\share\\aa\\bb", "M", false, 0, fs::ResultInvalidPathFormat() }, + { "mount:\\\\host/share\\aa\\bb", "M", false, 0, fs::ResultInvalidPathFormat() }, + { "c:\\aa\\..\\..\\..\\bb", "W", false, 0, ResultSuccess() }, + { "mount:/\\\\aa\\..\\bb", "MW", false, 0, ResultSuccess() }, + { "mount:/c:\\aa\\..\\bb", "MW", false, 0, ResultSuccess() }, + { "mount:/aa/bb", "MW", true, 12, ResultSuccess() }, + { "/mount:/aa/bb", "MW", false, 0, fs::ResultInvalidCharacter() }, + { "/mount:/aa/bb", "W", false, 0, fs::ResultInvalidCharacter() }, + { "a:aa/../bb", "MW", false, 8, ResultSuccess() }, + { "a:aa\\..\\bb", "MW", false, 0, ResultSuccess() }, + { "/a:aa\\..\\bb", "W", false, 0, ResultSuccess() }, + { "\\\\?\\c:\\.\\aa", "W", false, 0, ResultSuccess() }, + { "\\\\.\\c:\\.\\aa", "W", false, 0, ResultSuccess() }, + { "\\\\.\\mount:\\.\\aa", "W", false, 0, ResultSuccess() }, + { "\\\\./.\\aa", "W", false, 0, ResultSuccess() }, + { "\\\\/aa", "W", false, 0, fs::ResultInvalidPathFormat() }, + { "\\\\\\aa", "W", false, 0, fs::ResultInvalidPathFormat() }, + { "\\\\", "W", false, 0, ResultSuccess() }, + { "\\\\host\\share", "W", false, 0, ResultSuccess() }, + { "\\\\host\\share\\path", "W", false, 0, ResultSuccess() }, + { "\\\\host\\share\\path\\aa\\bb\\..\\cc\\.", "W", false, 0, ResultSuccess() }, + { "\\\\host\\", "W", false, 0, fs::ResultInvalidPathFormat() }, + { "\\\\ho$st\\share\\path", "W", false, 0, fs::ResultInvalidCharacter() }, + { "\\\\host:\\share\\path", "W", false, 0, fs::ResultInvalidCharacter() }, + { "\\\\..\\share\\path", "W", false, 0, fs::ResultInvalidPathFormat() }, + { "\\\\host\\s:hare\\path", "W", false, 0, fs::ResultInvalidCharacter() }, + { "\\\\host\\.\\path", "W", false, 0, fs::ResultInvalidPathFormat() }, + { "\\\\host\\..\\path", "W", false, 0, fs::ResultInvalidPathFormat() }, + { "\\\\host\\sha:re", "W", false, 0, fs::ResultInvalidCharacter() }, + { ".\\\\host\\share", "RW", false, 0, ResultSuccess() } + }; + + return DoIsNormalizedTests(Tests); + } + static_assert(TestIsNormalizedWindowsPath()); + + consteval bool TestIsNormalizedRelativePath() { + constexpr IsNormalizedTestData Tests[] = { + { "./aa/bb", "", false, 0, fs::ResultInvalidPathFormat() }, + { "./aa/bb/../cc", "R", false, 1, ResultSuccess() }, + { ".\\aa/bb/../cc", "R", false, 0, ResultSuccess() }, + { ".", "R", true, 1, ResultSuccess() }, + { "../aa/bb", "R", false, 0, fs::ResultDirectoryUnobtainable() }, + { "/aa/./bb", "R", false, 0, ResultSuccess() }, + { "mount:./aa/bb", "MR", true, 13, ResultSuccess() }, + { "mount:./aa/./bb", "MR", false, 7, ResultSuccess() }, + { "mount:./aa/bb", "M", false, 0, fs::ResultInvalidPathFormat() } + }; + + return DoIsNormalizedTests(Tests); + } + static_assert(TestIsNormalizedRelativePath()); + + consteval bool TestIsNormalizedBackslash() { + constexpr IsNormalizedTestData Tests[] = { + { "\\aa\\bb\\..\\cc", "", false, 0, fs::ResultInvalidPathFormat() }, + { "\\aa\\bb\\..\\cc", "B", false, 0, fs::ResultInvalidPathFormat() }, + { "/aa\\bb\\..\\cc", "", false, 0, fs::ResultInvalidCharacter() }, + { "/aa\\bb\\..\\cc", "B", false, 0, ResultSuccess() }, + { "/aa\\bb\\cc", "", false, 0, fs::ResultInvalidCharacter() }, + { "/aa\\bb\\cc", "B", true, 9, ResultSuccess() }, + { "\\\\host\\share\\path\\aa\\bb\\cc", "W", false, 0, ResultSuccess() }, + { "\\\\host\\share\\path\\aa\\bb\\cc", "WB", false, 0, ResultSuccess() }, + { "/aa/bb\\../cc/..\\dd\\..\\ee/..", "", false, 0, fs::ResultInvalidCharacter() }, + { "/aa/bb\\../cc/..\\dd\\..\\ee/..", "B", false, 0, ResultSuccess() } + }; + + return DoIsNormalizedTests(Tests); + } + static_assert(TestIsNormalizedBackslash()); + + consteval bool TestIsNormalizedAllowAllCharacters() { + constexpr IsNormalizedTestData Tests[] = { + { "/aa/b:b/cc", "", false, 0, fs::ResultInvalidCharacter() }, + { "/aa/b*b/cc", "", false, 0, fs::ResultInvalidCharacter() }, + { "/aa/b?b/cc", "", false, 0, fs::ResultInvalidCharacter() }, + { "/aa/b<b/cc", "", false, 0, fs::ResultInvalidCharacter() }, + { "/aa/b>b/cc", "", false, 0, fs::ResultInvalidCharacter() }, + { "/aa/b|b/cc", "", false, 0, fs::ResultInvalidCharacter() }, + { "/aa/b:b/cc", "C", true, 10, ResultSuccess() }, + { "/aa/b*b/cc", "C", true, 10, ResultSuccess() }, + { "/aa/b?b/cc", "C", true, 10, ResultSuccess() }, + { "/aa/b<b/cc", "C", true, 10, ResultSuccess() }, + { "/aa/b>b/cc", "C", true, 10, ResultSuccess() }, + { "/aa/b|b/cc", "C", true, 10, ResultSuccess() }, + { "/aa/b'b/cc", "", true, 10, ResultSuccess() }, + { "/aa/b\"b/cc", "", true, 10, ResultSuccess() }, + { "/aa/b(b/cc", "", true, 10, ResultSuccess() }, + { "/aa/b)b/cc", "", true, 10, ResultSuccess() }, + { "/aa/b'b/cc", "C", true, 10, ResultSuccess() }, + { "/aa/b\"b/cc", "C", true, 10, ResultSuccess() }, + { "/aa/b(b/cc", "C", true, 10, ResultSuccess() }, + { "/aa/b)b/cc", "C", true, 10, ResultSuccess() }, + { "mount:/aa/b<b/cc", "MC", true, 16, ResultSuccess() }, + { "mo>unt:/aa/bb/cc", "MC", false, 0, fs::ResultInvalidCharacter() } + }; + + return DoIsNormalizedTests(Tests); + } + static_assert(TestIsNormalizedAllowAllCharacters()); + + consteval bool TestIsNormalizedAll() { + constexpr IsNormalizedTestData Tests[] = { + { "mount:./aa/bb", "WRM", true, 13, ResultSuccess() }, + { "mount:./aa/bb\\cc/dd", "WRM", false, 0, ResultSuccess() }, + { "mount:./aa/bb\\cc/dd", "WRMB", true, 19, ResultSuccess() }, + { "mount:./.c:/aa/bb", "RM", false, 0, fs::ResultInvalidCharacter() }, + { "mount:.c:/aa/bb", "WRM", false, 0, ResultSuccess() }, + { "mount:./cc:/aa/bb", "WRM", false, 0, fs::ResultInvalidCharacter() }, + { "mount:./\\\\host\\share/aa/bb", "MW", false, 0, fs::ResultInvalidPathFormat() }, + { "mount:./\\\\host\\share/aa/bb", "WRM", false, 0, ResultSuccess() }, + { "mount:.\\\\host\\share/aa/bb", "WRM", false, 0, ResultSuccess() }, + { "mount:..\\\\host\\share/aa/bb", "WRM", false, 0, ResultSuccess() }, + { ".\\\\host\\share/aa/bb", "WRM", false, 0, ResultSuccess() }, + { "..\\\\host\\share/aa/bb", "WRM", false, 0, ResultSuccess() }, + { "mount:\\\\host\\share/aa/bb", "MW", true, 24, ResultSuccess() }, + { "mount:\\aa\\bb", "BM", false, 0, fs::ResultInvalidPathFormat() }, + { "mount:/aa\\bb", "BM", true, 12, ResultSuccess() }, + { ".//aa/bb", "RW", false, 1, ResultSuccess() }, + { "./aa/bb", "R", true, 7, ResultSuccess() }, + { "./c:/aa/bb", "RW", false, 0, fs::ResultInvalidCharacter() }, + { "mount:./aa/b:b\\cc/dd", "WRMBC", true, 20, ResultSuccess() } + }; + + return DoIsNormalizedTests(Tests); + } + static_assert(TestIsNormalizedAll()); + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/tests/fs_path_normalizer_tests.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/tests/fs_path_normalizer_tests.cpp new file mode 100644 index 00000000..2092bded --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/tests/fs_path_normalizer_tests.cpp @@ -0,0 +1,394 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fs { + + namespace { + + constexpr size_t DefaultPathBufferSize = fs::EntryNameLengthMax + 1; + + //#define ENABLE_PRINT_FORMAT_TEST_DEBUGGING + + #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) + template<u32 Expected, u32 Actual> + struct Print { + constexpr Print() { + if (std::is_constant_evaluated()) { + __builtin_unreachable(); + } + } + }; + + template<u32 E = 0, u32 A = 0, size_t N = 0> + consteval void PrintResultMismatchImpl(u32 e, u32 a) { + if constexpr (N == 32) { + Print<E, A>{}; + } else { + const bool is_e = (e & (1 << N)) != 0; + const bool is_a = (a & (1 << N)) != 0; + if (is_e) { + if (is_a) { + PrintResultMismatchImpl<E | (1 << N), A | (1 << N), N + 1>(e, a); + } else { + PrintResultMismatchImpl<E | (1 << N), A | (0 << N), N + 1>(e, a); + } + } else { + if (is_a) { + PrintResultMismatchImpl<E | (0 << N), A | (1 << N), N + 1>(e, a); + } else { + PrintResultMismatchImpl<E | (0 << N), A | (0 << N), N + 1>(e, a); + } + } + } + } + + consteval void PrintResultMismatch(const Result &lhs, const Result &rhs) { + PrintResultMismatchImpl(lhs.GetDescription(), rhs.GetDescription()); + } + + template<size_t Index, char Expected, char Actual> + struct PrintMismatchChar { + constexpr PrintMismatchChar() { + if (std::is_constant_evaluated()) { + __builtin_unreachable(); + } + } + }; + + template<size_t Ix, char Expected, size_t C = 0> + consteval void PrintCharacterMismatch(char c) { + if (c == static_cast<char>(C)) { + PrintMismatchChar<Ix, Expected, static_cast<char>(C)>{}; + return; + } + + if constexpr (C < std::numeric_limits<unsigned char>::max()) { + PrintCharacterMismatch<Ix, Expected, C + 1>(c); + } + } + + template<size_t Ix, char C = 0> + consteval void PrintCharacterMismatch(char c, char c2) { + if (c == static_cast<char>(C)) { + PrintCharacterMismatch<Ix, static_cast<char>(C)>(c2); + return; + } + + + if constexpr (C < std::numeric_limits<unsigned char>::max()) { + PrintCharacterMismatch<Ix, C + 1>(c, c2); + } + } + + template<size_t Ix = 0> + consteval void PrintCharacterMismatch(size_t ix, char c, char c2) { + if (Ix == ix) { + PrintCharacterMismatch<Ix>(c, c2); + return; + } + + if constexpr (Ix <= DefaultPathBufferSize) { + PrintCharacterMismatch<Ix + 1>(ix, c, c2); + } + } + #endif + + consteval bool TestNormalizeImpl(const char *path, size_t buffer_size, const char *normalized, bool windows_path, bool drive_relative, bool all_chars, size_t expected_length, Result expected_result) { + /* Allocate a buffer to normalize into. */ + char *buffer = new char[buffer_size]; + ON_SCOPE_EXIT { delete[] buffer; }; + buffer[buffer_size - 1] = '\xcc'; + + /* Perform normalization. */ + size_t actual_length; + const Result actual_result = PathNormalizer::Normalize(buffer, std::addressof(actual_length), path, buffer_size, windows_path, drive_relative, all_chars); + + /* Check that the expected result matches the actual. */ + if (actual_result.GetValue() != expected_result.GetValue()) { + #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) + PrintResultMismatch(expected_result.GetValue(), actual_result.GetValue()); + #endif + + return false; + } + + /* Check that the expected string matches the actual. */ + for (size_t i = 0; i < buffer_size; ++i) { + if (normalized[i] != StringTraits::NullTerminator || R_SUCCEEDED(expected_result)) { + if (buffer[i] != normalized[i]) { + #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) + PrintCharacterMismatch(i, normalized[i], buffer[i]); + #endif + return false; + } + } + + if (normalized[i] == StringTraits::NullTerminator || buffer[i] == StringTraits::NullTerminator) { + break; + } + } + + /* Check that the expected length matches the actual. */ + if (R_SUCCEEDED(expected_result) || fs::ResultTooLongPath::Includes(expected_result)) { + if (expected_length != actual_length) { + #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) + PrintResultMismatchImpl(static_cast<u32>(expected_length), static_cast<u32>(actual_length)); + #endif + return false; + } + } + + return true; + } + + struct NormalizeTestData { + const char *path; + bool windows; + bool rel; + bool allow_all; + const char *normalized; + size_t len; + Result result; + }; + + template<size_t N, size_t Ix = 0> + consteval bool DoNormalizeTests(const NormalizeTestData (&tests)[N]) { + if constexpr (Ix >= N) { + return true; + } + + const auto &test = tests[Ix]; + if (!TestNormalizeImpl(test.path, DefaultPathBufferSize, test.normalized, test.windows, test.rel, test.allow_all, test.len, test.result)) { + return false; + } + + if constexpr (Ix < N) { + return DoNormalizeTests<N, Ix + 1>(tests); + } else { + AMS_ASSUME(false); + } + } + + consteval bool TestNormalized() { + constexpr NormalizeTestData Tests[] = { + { "/aa/bb/c/", false, true, false, "/aa/bb/c", 8, ResultSuccess() }, + { "aa/bb/c/", false, false, false, "", 0, fs::ResultInvalidPathFormat() }, + { "aa/bb/c/", false, true, false, "/aa/bb/c", 8, ResultSuccess() }, + { "mount:a/b", false, true, false, "/", 0, fs::ResultInvalidCharacter() }, + { "mo|unt:a/b", false, true, true, "/mo|unt:a/b", 11, ResultSuccess() }, + { "/aa/bb/../..", true, false, false, "/", 1, ResultSuccess() }, + { "/aa/bb/../../..", true, false, false, "/", 1, ResultSuccess() }, + { "/aa/bb/../../..", false, false, false, "/aa/bb/", 0, fs::ResultDirectoryUnobtainable() }, + { "aa/bb/../../..", true, true, false, "/", 1, ResultSuccess() }, + { "aa/bb/../../..", false, true, false, "/aa/bb/", 0, fs::ResultDirectoryUnobtainable() }, + { "mount:a/b", false, true, true, "/mount:a/b", 10, ResultSuccess() }, + { "/a|/bb/cc", false, false, true, "/a|/bb/cc", 9, ResultSuccess() }, + { "/>a/bb/cc", false, false, true, "/>a/bb/cc", 9, ResultSuccess() }, + { "/aa/.</cc", false, false, true, "/aa/.</cc", 9, ResultSuccess() }, + { "/aa/..</cc", false, false, true, "/aa/..</cc", 10, ResultSuccess() }, + { "", false, false, false, "", 0, fs::ResultInvalidPathFormat() }, + { "/", false, false, false, "/", 1, ResultSuccess() }, + { "/.", false, false, false, "/", 1, ResultSuccess() }, + { "/./", false, false, false, "/", 1, ResultSuccess() }, + { "/..", false, false, false, "/", 0, fs::ResultDirectoryUnobtainable() }, + { "//.", false, false, false, "/", 1, ResultSuccess() }, + { "/ ..", false, false, false, "/ ..", 4, ResultSuccess() }, + { "/.. /", false, false, false, "/.. ", 4, ResultSuccess() }, + { "/. /.", false, false, false, "/. ", 3, ResultSuccess() }, + { "/aa/bb/cc/dd/./.././../..", false, false, false, "/aa", 3, ResultSuccess() }, + { "/aa/bb/cc/dd/./.././../../..", false, false, false, "/", 1, ResultSuccess() }, + { "/./aa/./bb/./cc/./dd/.", false, false, false, "/aa/bb/cc/dd", 12, ResultSuccess() }, + { "/aa\\bb/cc", false, false, false, "/aa\\bb/cc", 9, ResultSuccess() }, + { "/aa\\bb/cc", false, false, false, "/aa\\bb/cc", 9, ResultSuccess() }, + { "/a|/bb/cc", false, false, false, "/", 0, fs::ResultInvalidCharacter() }, + { "/>a/bb/cc", false, false, false, "/", 0, fs::ResultInvalidCharacter() }, + { "/aa/.</cc", false, false, false, "/aa/", 0, fs::ResultInvalidCharacter() }, + { "/aa/..</cc", false, false, false, "/aa/", 0, fs::ResultInvalidCharacter() }, + { "\\\\aa/bb/cc", false, false, false, "", 0, fs::ResultInvalidPathFormat() }, + { "\\\\aa\\bb\\cc", false, false, false, "", 0, fs::ResultInvalidPathFormat() }, + { "/aa/bb/..\\cc", false, false, false, "/aa/cc", 6, ResultSuccess() }, + { "/aa/bb\\..\\cc", false, false, false, "/aa/cc", 6, ResultSuccess() }, + { "/aa/bb\\..", false, false, false, "/aa", 3, ResultSuccess() }, + { "/aa\\bb/../cc", false, false, false, "/cc", 3, ResultSuccess() } + }; + + return DoNormalizeTests(Tests); + } + static_assert(TestNormalized()); + + struct NormalizeTestDataSmallBuffer { + const char *path; + size_t buffer_size; + const char *normalized; + size_t len; + Result result; + }; + + template<size_t N, size_t Ix = 0> + consteval bool DoNormalizeTests(const NormalizeTestDataSmallBuffer (&tests)[N]) { + if constexpr (Ix >= N) { + return true; + } + + const auto &test = tests[Ix]; + if (!TestNormalizeImpl(test.path, test.buffer_size, test.normalized, false, false, false, test.len, test.result)) { + return false; + } + + if constexpr (Ix < N) { + return DoNormalizeTests<N, Ix + 1>(tests); + } else { + AMS_ASSUME(false); + } + } + + consteval bool TestNormalizedSmallBuffer() { + constexpr NormalizeTestDataSmallBuffer Tests[] = { + { "/aa/bb/cc/", 7, "/aa/bb", 6, fs::ResultTooLongPath() }, + { "/aa/bb/cc/", 8, "/aa/bb/", 7, fs::ResultTooLongPath() }, + { "/aa/bb/cc/", 9, "/aa/bb/c", 8, fs::ResultTooLongPath() }, + { "/aa/bb/cc/", 10, "/aa/bb/cc", 9, ResultSuccess() }, + { "/aa/bb/cc", 9, "/aa/bb/c", 8, fs::ResultTooLongPath() }, + { "/aa/bb/cc", 10, "/aa/bb/cc", 9, ResultSuccess() }, + { "/./aa/./bb/./cc", 9, "/aa/bb/c", 8, fs::ResultTooLongPath() }, + { "/./aa/./bb/./cc", 10, "/aa/bb/cc", 9, ResultSuccess() }, + { "/aa/bb/cc/../../..", 9, "/aa/bb/c", 8, fs::ResultTooLongPath() }, + { "/aa/bb/cc/../../..", 10, "/aa/bb/cc", 9, fs::ResultTooLongPath() }, + { "/aa/bb/.", 7, "/aa/bb", 6, fs::ResultTooLongPath() }, + { "/aa/bb/./", 7, "/aa/bb", 6, fs::ResultTooLongPath() }, + { "/aa/bb/..", 8, "/aa", 3, ResultSuccess() }, + { "/aa/bb", 1, "", 0, fs::ResultTooLongPath() }, + { "/aa/bb", 2, "/", 1, fs::ResultTooLongPath() }, + { "/aa/bb", 3, "/a", 2, fs::ResultTooLongPath() }, + { "aa/bb", 1, "", 0, fs::ResultInvalidPathFormat() } + }; + + return DoNormalizeTests(Tests); + } + static_assert(TestNormalizedSmallBuffer()); + + consteval bool TestIsNormalizedImpl(const char *path, bool allow_all, bool expected_normalized, size_t expected_size, Result expected_result) { + /* Perform normalization checking. */ + bool actual_normalized; + size_t actual_size = 0; + const Result actual_result = PathNormalizer::IsNormalized(std::addressof(actual_normalized), std::addressof(actual_size), path, allow_all); + + /* Check that the expected result matches the actual. */ + if (actual_result.GetValue() != expected_result.GetValue()) { + #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) + PrintResultMismatch(expected_result.GetValue(), actual_result.GetValue()); + #endif + + return false; + } + + if (expected_size != actual_size) { + #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) + PrintResultMismatchImpl(static_cast<u32>(expected_size), static_cast<u32>(actual_size)); + #endif + return false; + } + + /* Check that the expected output matches the actual. */ + if (R_SUCCEEDED(expected_result)) { + if (expected_normalized != actual_normalized) { + #if defined(ENABLE_PRINT_FORMAT_TEST_DEBUGGING) + PrintResultMismatchImpl(static_cast<u32>(expected_normalized), static_cast<u32>(actual_normalized)); + #endif + return false; + } + } + + return true; + } + + struct IsNormalizedTestData { + const char *path; + bool allow_all; + bool normalized; + size_t len; + Result result; + }; + + template<size_t N, size_t Ix = 0> + consteval bool DoIsNormalizedTests(const IsNormalizedTestData (&tests)[N]) { + if constexpr (Ix >= N) { + return true; + } + + const auto &test = tests[Ix]; + if (!TestIsNormalizedImpl(test.path, test.allow_all, test.normalized, test.len, test.result)) { + return false; + } + + if constexpr (Ix < N) { + return DoIsNormalizedTests<N, Ix + 1>(tests); + } else { + AMS_ASSUME(false); + } + } + + consteval bool TestIsNormalized() { + constexpr IsNormalizedTestData Tests[] = { + { "/aa/bb/c/", false, false, 9, ResultSuccess() }, + { "aa/bb/c/", false, false, 0, fs::ResultInvalidPathFormat() }, + { "aa/bb/c/", false, false, 0, fs::ResultInvalidPathFormat() }, + { "mount:a/b", false, false, 0, fs::ResultInvalidPathFormat() }, + { "mo|unt:a/b", true, false, 0, fs::ResultInvalidPathFormat() }, + { "/aa/bb/../..", false, false, 0, ResultSuccess() }, + { "/aa/bb/../../..", false, false, 0, ResultSuccess() }, + { "/aa/bb/../../..", false, false, 0, ResultSuccess() }, + { "aa/bb/../../..", false, false, 0, fs::ResultInvalidPathFormat() }, + { "aa/bb/../../..", false, false, 0, fs::ResultInvalidPathFormat() }, + { "mount:a/b", true, false, 0, fs::ResultInvalidPathFormat() }, + { "/a|/bb/cc", true, true, 9, ResultSuccess() }, + { "/>a/bb/cc", true, true, 9, ResultSuccess() }, + { "/aa/.</cc", true, true, 9, ResultSuccess() }, + { "/aa/..</cc", true, true, 10, ResultSuccess() }, + { "", false, false, 0, fs::ResultInvalidPathFormat() }, + { "/", false, true, 1, ResultSuccess() }, + { "/.", false, false, 2, ResultSuccess() }, + { "/./", false, false, 0, ResultSuccess() }, + { "/..", false, false, 3, ResultSuccess() }, + { "//.", false, false, 0, ResultSuccess() }, + { "/ ..", false, true, 4, ResultSuccess() }, + { "/.. /", false, false, 5, ResultSuccess() }, + { "/. /.", false, false, 5, ResultSuccess() }, + { "/aa/bb/cc/dd/./.././../..", false, false, 0, ResultSuccess() }, + { "/aa/bb/cc/dd/./.././../../..", false, false, 0, ResultSuccess() }, + { "/./aa/./bb/./cc/./dd/.", false, false, 0, ResultSuccess() }, + { "/aa\\bb/cc", false, true, 9, ResultSuccess() }, + { "/aa\\bb/cc", false, true, 9, ResultSuccess() }, + { "/a|/bb/cc", false, false, 0, fs::ResultInvalidCharacter() }, + { "/>a/bb/cc", false, false, 0, fs::ResultInvalidCharacter() }, + { "/aa/.</cc", false, false, 0, fs::ResultInvalidCharacter() }, + { "/aa/..</cc", false, false, 0, fs::ResultInvalidCharacter() }, + { "\\\\aa/bb/cc", false, false, 0, fs::ResultInvalidPathFormat() }, + { "\\\\aa\\bb\\cc", false, false, 0, fs::ResultInvalidPathFormat() }, + { "/aa/bb/..\\cc", false, true, 12, ResultSuccess() }, + { "/aa/bb\\..\\cc", false, true, 12, ResultSuccess() }, + { "/aa/bb\\..", false, true, 9, ResultSuccess() }, + { "/aa\\bb/../cc", false, false, 0, ResultSuccess() } + }; + + return DoIsNormalizedTests(Tests); + } + static_assert(TestIsNormalized()); + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/tests/fs_path_utility_tests.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/tests/fs_path_utility_tests.cpp new file mode 100644 index 00000000..cac3f30f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fs/tests/fs_path_utility_tests.cpp @@ -0,0 +1,73 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fs { + + namespace { + + struct IsSubPathTestData { + const char *lhs; + const char *rhs; + bool is_sub_path; + }; + + template<size_t N, size_t Ix = 0> + consteval bool DoIsSubPathTests(const IsSubPathTestData (&tests)[N]) { + if constexpr (Ix >= N) { + return true; + } + + const auto &test = tests[Ix]; + if (::ams::fs::IsSubPath(test.lhs, test.rhs) != test.is_sub_path) { + return false; + } + + if constexpr (Ix < N) { + return DoIsSubPathTests<N, Ix + 1>(tests); + } else { + AMS_ASSUME(false); + } + } + + consteval bool TestIsSubPath() { + constexpr IsSubPathTestData Tests[] = { + { "//a/b", "/a", false }, + { "/a", "//a/b", false }, + { "//a/b", "\\\\a", false }, + { "//a/b", "//a", true }, + { "/", "/a", true }, + { "/a", "/", true }, + { "/", "/", false }, + { "", "", false }, + { "/", "", true }, + { "/", "mount:/a", false }, + { "mount:/", "mount:/", false }, + { "mount:/a/b", "mount:/a/b", false }, + { "mount:/a/b", "mount:/a/b/c", true }, + { "/a/b", "/a/b/c", true }, + { "/a/b/c", "/a/b", true }, + { "/a/b", "/a/b", false }, + { "/a/b", "/a/b\\c", false } + }; + + return DoIsSubPathTests(Tests); + } + static_assert(TestIsSubPath()); + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fscreator/fssrv_local_file_system_creator.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fscreator/fssrv_local_file_system_creator.cpp new file mode 100644 index 00000000..11584eb2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fscreator/fssrv_local_file_system_creator.cpp @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssrv::fscreator { + + Result LocalFileSystemCreator::Create(std::shared_ptr<fs::fsa::IFileSystem> *out, const fs::Path &path, bool case_sensitive, bool ensure_root, Result on_path_not_found) { + /* Check that we're configured for development. */ + R_UNLESS(m_is_development, fs::ResultPermissionDeniedForCreateHostFileSystem()); + + /* Allocate a local filesystem. */ + auto local_fs = fs::AllocateShared<fssystem::LocalFileSystem>(); + R_UNLESS(local_fs != nullptr, fs::ResultAllocationMemoryFailedInLocalFileSystemCreatorA()); + + /* If we're supposed to make sure the root path exists, do so. */ + if (ensure_root) { + /* Sanity check that the path will be possible to create. */ + AMS_ASSERT(!path.IsEmpty()); + + /* Initialize the local fs with an empty path. */ + fs::Path empty_path; + R_TRY(empty_path.InitializeAsEmpty()); + R_TRY(local_fs->Initialize(empty_path, fssystem::PathCaseSensitiveMode_CaseInsensitive)); + + /* Ensure the directory exists. */ + if (const Result ensure_result = fssystem::EnsureDirectory(local_fs.get(), path); R_FAILED(ensure_result)) { + if (R_SUCCEEDED(on_path_not_found)) { + R_THROW(ensure_result); + } else { + R_THROW(on_path_not_found); + } + } + } + + /* Initialize the local filesystem. */ + R_TRY_CATCH(local_fs->Initialize(path, case_sensitive ? fssystem::PathCaseSensitiveMode_CaseSensitive : fssystem::PathCaseSensitiveMode_CaseInsensitive)) { + R_CATCH(fs::ResultPathNotFound) { + if (R_SUCCEEDED(on_path_not_found)) { + R_THROW(R_CURRENT_RESULT); + } else { + R_THROW(on_path_not_found); + } + } + } R_END_TRY_CATCH; + + /* Set the output fs. */ + *out = std::move(local_fs); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fscreator/fssrv_partition_file_system_creator.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fscreator/fssrv_partition_file_system_creator.cpp new file mode 100644 index 00000000..25855b5b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fscreator/fssrv_partition_file_system_creator.cpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssrv::fscreator { + + Result PartitionFileSystemCreator::Create(std::shared_ptr<fs::fsa::IFileSystem> *out, std::shared_ptr<fs::IStorage> storage) { + /* Allocate a filesystem. */ + std::shared_ptr fs = fssystem::AllocateShared<fssystem::PartitionFileSystem>(); + R_UNLESS(fs != nullptr, fs::ResultAllocationMemoryFailedInPartitionFileSystemCreatorA()); + + /* Initialize the filesystem. */ + R_TRY(fs->Initialize(std::move(storage))); + + /* Set the output. */ + *out = std::move(fs); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fscreator/fssrv_rom_file_system_creator.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fscreator/fssrv_rom_file_system_creator.cpp new file mode 100644 index 00000000..61720a1e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fscreator/fssrv_rom_file_system_creator.cpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssrv::fscreator { + + namespace { + + class RomFileSystemWithBuffer : public ::ams::fssystem::RomFsFileSystem { + private: + void *m_meta_cache_buffer; + size_t m_meta_cache_buffer_size; + MemoryResource *m_allocator; + public: + explicit RomFileSystemWithBuffer(MemoryResource *mr) : m_meta_cache_buffer(nullptr), m_allocator(mr) { /* ... */ } + + ~RomFileSystemWithBuffer() { + if (m_meta_cache_buffer != nullptr) { + m_allocator->Deallocate(m_meta_cache_buffer, m_meta_cache_buffer_size); + } + } + + Result Initialize(std::shared_ptr<fs::IStorage> storage) { + /* Check if the buffer is eligible for cache. */ + size_t buffer_size = 0; + if (R_FAILED(RomFsFileSystem::GetRequiredWorkingMemorySize(std::addressof(buffer_size), storage.get())) || buffer_size == 0 || buffer_size >= 128_KB) { + R_RETURN(RomFsFileSystem::Initialize(std::move(storage), nullptr, 0, false)); + } + + /* Allocate a buffer. */ + m_meta_cache_buffer = m_allocator->Allocate(buffer_size); + if (m_meta_cache_buffer == nullptr) { + R_RETURN(RomFsFileSystem::Initialize(std::move(storage), nullptr, 0, false)); + } + + /* Initialize with cache buffer. */ + m_meta_cache_buffer_size = buffer_size; + R_RETURN(RomFsFileSystem::Initialize(std::move(storage), m_meta_cache_buffer, m_meta_cache_buffer_size, true)); + } + }; + + } + + Result RomFileSystemCreator::Create(std::shared_ptr<fs::fsa::IFileSystem> *out, std::shared_ptr<fs::IStorage> storage) { + /* Allocate a filesystem. */ + std::shared_ptr fs = fssystem::AllocateShared<RomFileSystemWithBuffer>(m_allocator); + R_UNLESS(fs != nullptr, fs::ResultAllocationMemoryFailedInRomFileSystemCreatorA()); + + /* Initialize the filesystem. */ + R_TRY(fs->Initialize(std::move(storage))); + + /* Set the output. */ + *out = std::move(fs); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fscreator/fssrv_storage_on_nca_creator.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fscreator/fssrv_storage_on_nca_creator.cpp new file mode 100644 index 00000000..f6fcd730 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fscreator/fssrv_storage_on_nca_creator.cpp @@ -0,0 +1,111 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssrv::fscreator { + + Result StorageOnNcaCreator::Create(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::IAsynchronousAccessSplitter> *out_splitter, fssystem::NcaFsHeaderReader *out_header_reader, std::shared_ptr<fssystem::NcaReader> nca_reader, s32 index) { + /* Create a fs driver. */ + fssystem::NcaFileSystemDriver nca_fs_driver(nca_reader, m_allocator, m_buffer_manager, m_hash_generator_factory_selector); + + /* Open the storage. */ + std::shared_ptr<fs::IStorage> storage; + std::shared_ptr<fssystem::IAsynchronousAccessSplitter> splitter; + R_TRY(nca_fs_driver.OpenStorage(std::addressof(storage), std::addressof(splitter), out_header_reader, index)); + + /* Set the out storage. */ + *out = std::move(storage); + *out_splitter = std::move(splitter); + R_SUCCEED(); + } + + Result StorageOnNcaCreator::CreateWithPatch(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::IAsynchronousAccessSplitter> *out_splitter, fssystem::NcaFsHeaderReader *out_header_reader, std::shared_ptr<fssystem::NcaReader> original_nca_reader, std::shared_ptr<fssystem::NcaReader> current_nca_reader, s32 index) { + /* Create a fs driver. */ + fssystem::NcaFileSystemDriver nca_fs_driver(original_nca_reader, current_nca_reader, m_allocator, m_buffer_manager, m_hash_generator_factory_selector); + + /* Open the storage. */ + std::shared_ptr<fs::IStorage> storage; + std::shared_ptr<fssystem::IAsynchronousAccessSplitter> splitter; + R_TRY(nca_fs_driver.OpenStorage(std::addressof(storage), std::addressof(splitter), out_header_reader, index)); + + /* Set the out storage. */ + *out = std::move(storage); + *out_splitter = std::move(splitter); + R_SUCCEED(); + } + + Result StorageOnNcaCreator::CreateNcaReader(std::shared_ptr<fssystem::NcaReader> *out, std::shared_ptr<fs::IStorage> storage) { + /* Create a reader. */ + std::shared_ptr reader = fssystem::AllocateShared<fssystem::NcaReader>(); + R_UNLESS(reader != nullptr, fs::ResultAllocationMemoryFailedInStorageOnNcaCreatorB()); + + /* Initialize the reader. */ + R_TRY(reader->Initialize(std::move(storage), m_nca_crypto_cfg, m_nca_compression_cfg, m_hash_generator_factory_selector)); + + /* Set the output. */ + *out = std::move(reader); + R_SUCCEED(); + } + + #if !defined(ATMOSPHERE_BOARD_NINTENDO_NX) + Result StorageOnNcaCreator::CreateWithContext(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::IAsynchronousAccessSplitter> *out_splitter, fssystem::NcaFsHeaderReader *out_header_reader, void *ctx, std::shared_ptr<fssystem::NcaReader> nca_reader, s32 index) { + /* Create a fs driver. */ + fssystem::NcaFileSystemDriver nca_fs_driver(nca_reader, m_allocator, m_buffer_manager, m_hash_generator_factory_selector); + + /* Open the storage. */ + std::shared_ptr<fs::IStorage> storage; + std::shared_ptr<fssystem::IAsynchronousAccessSplitter> splitter; + R_TRY(nca_fs_driver.OpenStorageWithContext(std::addressof(storage), std::addressof(splitter), out_header_reader, index, static_cast<fssystem::NcaFileSystemDriver::StorageContext *>(ctx))); + + /* Set the out storage. */ + *out = std::move(storage); + *out_splitter = std::move(splitter); + R_SUCCEED(); + } + + Result StorageOnNcaCreator::CreateWithPatchWithContext(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::IAsynchronousAccessSplitter> *out_splitter, fssystem::NcaFsHeaderReader *out_header_reader, void *ctx, std::shared_ptr<fssystem::NcaReader> original_nca_reader, std::shared_ptr<fssystem::NcaReader> current_nca_reader, s32 index) { + /* Create a fs driver. */ + fssystem::NcaFileSystemDriver nca_fs_driver(original_nca_reader, current_nca_reader, m_allocator, m_buffer_manager, m_hash_generator_factory_selector); + + /* Open the storage. */ + std::shared_ptr<fs::IStorage> storage; + std::shared_ptr<fssystem::IAsynchronousAccessSplitter> splitter; + R_TRY(nca_fs_driver.OpenStorageWithContext(std::addressof(storage), std::addressof(splitter), out_header_reader, index, static_cast<fssystem::NcaFileSystemDriver::StorageContext *>(ctx))); + + /* Set the out storage. */ + *out = std::move(storage); + *out_splitter = std::move(splitter); + R_SUCCEED(); + } + + Result StorageOnNcaCreator::CreateByRawStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::IAsynchronousAccessSplitter> *out_splitter, const fssystem::NcaFsHeaderReader *header_reader, std::shared_ptr<fs::IStorage> raw_storage, void *ctx, std::shared_ptr<fssystem::NcaReader> nca_reader) { + /* Create a fs driver. */ + fssystem::NcaFileSystemDriver nca_fs_driver(nca_reader, m_allocator, m_buffer_manager, m_hash_generator_factory_selector); + + /* Open the storage. */ + auto *storage_ctx = static_cast<fssystem::NcaFileSystemDriver::StorageContext *>(ctx); + R_TRY(nca_fs_driver.CreateStorageByRawStorage(out, header_reader, std::move(raw_storage), storage_ctx)); + + /* Update the splitter. */ + if (storage_ctx->compressed_storage != nullptr) { + *out_splitter = storage_ctx->compressed_storage; + } + + R_SUCCEED(); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fscreator/fssrv_subdirectory_file_system_creator.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fscreator/fssrv_subdirectory_file_system_creator.cpp new file mode 100644 index 00000000..ee9941b5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fscreator/fssrv_subdirectory_file_system_creator.cpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssrv::fscreator { + + Result SubDirectoryFileSystemCreator::Create(std::shared_ptr<fs::fsa::IFileSystem> *out, std::shared_ptr<fs::fsa::IFileSystem> base_fs, const fs::Path &path) { + /* Verify that we can the directory on the base filesystem. */ + { + std::unique_ptr<fs::fsa::IDirectory> sub_dir; + R_TRY(base_fs->OpenDirectory(std::addressof(sub_dir), path, fs::OpenDirectoryMode_Directory)); + } + + /* Allocate a SubDirectoryFileSystem. */ + auto sub_dir_fs = fs::AllocateShared<fssystem::SubDirectoryFileSystem>(std::move(base_fs)); + R_UNLESS(sub_dir_fs != nullptr, fs::ResultAllocationMemoryFailedInSubDirectoryFileSystemCreatorA()); + + /* Initialize the new filesystem. */ + R_TRY(sub_dir_fs->Initialize(path)); + + /* Return the new filesystem. */ + *out = std::move(sub_dir_fs); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_access_control.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_access_control.cpp new file mode 100644 index 00000000..06d53c33 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_access_control.cpp @@ -0,0 +1,407 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssrv { + + namespace { + + constinit bool g_is_debug_flag_enabled = false; + + } + + bool IsDebugFlagEnabled() { + return g_is_debug_flag_enabled; + } + + void SetDebugFlagEnabled(bool en) { + /* Set global debug flag. */ + g_is_debug_flag_enabled = en; + } + + namespace impl { + + namespace { + + constexpr u8 LatestFsAccessControlInfoVersion = 1; + + } + + AccessControl::AccessControl(const void *data, s64 data_size, const void *desc, s64 desc_size) : AccessControl(data, data_size, desc, desc_size, g_is_debug_flag_enabled ? AllFlagBitsMask : DebugFlagDisableMask) { + /* ... */ + } + + AccessControl::AccessControl(const void *fac_data, s64 data_size, const void *fac_desc, s64 desc_size, u64 flag_mask) { + /* If either our data or descriptor is null, give no permissions. */ + if (fac_data == nullptr || fac_desc == nullptr) { + m_flag_bits.emplace(util::ToUnderlying(AccessControlBits::Bits::None)); + return; + } + + /* Check that data/desc are big enough. */ + AMS_ABORT_UNLESS(data_size >= static_cast<s64>(sizeof(AccessControlDataHeader))); + AMS_ABORT_UNLESS(desc_size >= static_cast<s64>(sizeof(AccessControlDescriptor))); + + /* Copy in the data/descriptor. */ + AccessControlDataHeader data = {}; + AccessControlDescriptor desc = {}; + std::memcpy(std::addressof(data), fac_data, sizeof(data)); + std::memcpy(std::addressof(desc), fac_desc, sizeof(desc)); + + /* If we don't know how to parse the descriptors, don't. */ + if (data.version != desc.version || data.version < LatestFsAccessControlInfoVersion) { + m_flag_bits.emplace(util::ToUnderlying(AccessControlBits::Bits::None)); + return; + } + + /* Restrict the descriptor's flag bits. */ + desc.flag_bits &= flag_mask; + + /* Create our flag bits. */ + m_flag_bits.emplace(data.flag_bits & desc.flag_bits); + + /* Further check sizes. */ + AMS_ABORT_UNLESS(data_size >= data.content_owner_infos_offset + data.content_owner_infos_size); + AMS_ABORT_UNLESS(desc_size >= static_cast<s64>(sizeof(AccessControlDescriptor) + desc.content_owner_id_count * sizeof(u64))); + + /* Read out the content data owner infos. */ + uintptr_t data_start = reinterpret_cast<uintptr_t>(fac_data); + uintptr_t desc_start = reinterpret_cast<uintptr_t>(fac_desc); + if (data.content_owner_infos_size > 0) { + /* Get the count. */ + const u32 num_content_owner_infos = util::LoadLittleEndian<u32>(reinterpret_cast<u32 *>(data_start + data.content_owner_infos_offset)); + + /* Validate the id range. */ + uintptr_t id_start = data_start + data.content_owner_infos_offset + sizeof(u32); + uintptr_t id_end = id_start + sizeof(u64) * num_content_owner_infos; + AMS_ABORT_UNLESS(id_end == data_start + data.content_owner_infos_offset + data.content_owner_infos_size); + + for (u32 i = 0; i < num_content_owner_infos; ++i) { + /* Read the id. */ + const u64 id = util::LoadLittleEndian<u64>(reinterpret_cast<u64 *>(id_start + i * sizeof(u64))); + + /* Check that the descriptor allows it. */ + bool allowed = false; + if (desc.content_owner_id_count != 0) { + for (u8 n = 0; n < desc.content_owner_id_count; ++n) { + if (id == util::LoadLittleEndian<u64>(reinterpret_cast<u64 *>(desc_start + sizeof(AccessControlDescriptor) + n * sizeof(u64)))) { + allowed = true; + break; + } + } + } else if ((desc.content_owner_id_min == 0 && desc.content_owner_id_max == 0) || (desc.content_owner_id_min <= id && id <= desc.content_owner_id_max)) { + allowed = true; + } + + /* If the id is allowed, create it. */ + if (allowed) { + if (auto *info = new ContentOwnerInfo(id); info != nullptr) { + m_content_owner_infos.push_front(*info); + } + } + } + } + + /* Read out the save data owner infos. */ + AMS_ABORT_UNLESS(data_size >= data.save_data_owner_infos_offset + data.save_data_owner_infos_size); + AMS_ABORT_UNLESS(desc_size >= static_cast<s64>(sizeof(AccessControlDescriptor) + desc.content_owner_id_count * sizeof(u64) + desc.save_data_owner_id_count * sizeof(u64))); + if (data.save_data_owner_infos_size > 0) { + /* Get the count. */ + const u32 num_save_data_owner_infos = util::LoadLittleEndian<u32>(reinterpret_cast<u32 *>(data_start + data.save_data_owner_infos_offset)); + + /* Get accessibility region.*/ + uintptr_t accessibility_start = data_start + data.save_data_owner_infos_offset + sizeof(u32); + + /* Validate the id range. */ + uintptr_t id_start = accessibility_start + util::AlignUp(num_save_data_owner_infos * sizeof(Accessibility), alignof(u32)); + uintptr_t id_end = id_start + sizeof(u64) * num_save_data_owner_infos; + AMS_ABORT_UNLESS(id_end == data_start + data.save_data_owner_infos_offset + data.save_data_owner_infos_size); + + for (u32 i = 0; i < num_save_data_owner_infos; ++i) { + /* Read the accessibility/id. */ + static_assert(sizeof(Accessibility) == 1); + const Accessibility accessibility = *reinterpret_cast<Accessibility *>(accessibility_start + i * sizeof(Accessibility)); + const u64 id = util::LoadLittleEndian<u64>(reinterpret_cast<u64 *>(id_start + i * sizeof(u64))); + + /* Check that the descriptor allows it. */ + bool allowed = false; + if (desc.save_data_owner_id_count != 0) { + for (u8 n = 0; n < desc.save_data_owner_id_count; ++n) { + if (id == util::LoadLittleEndian<u64>(reinterpret_cast<u64 *>(desc_start + sizeof(AccessControlDescriptor) + desc.content_owner_id_count * sizeof(u64) + n * sizeof(u64)))) { + allowed = true; + break; + } + } + } else if ((desc.save_data_owner_id_min == 0 && desc.save_data_owner_id_max == 0) || (desc.save_data_owner_id_min <= id && id <= desc.save_data_owner_id_max)) { + allowed = true; + } + + /* If the id is allowed, create it. */ + if (allowed) { + if (auto *info = new SaveDataOwnerInfo(id, accessibility); info != nullptr) { + m_save_data_owner_infos.push_front(*info); + } + } + } + } + + } + + AccessControl::~AccessControl() { + /* Delete all content owner infos. */ + while (!m_content_owner_infos.empty()) { + auto *info = std::addressof(*m_content_owner_infos.rbegin()); + m_content_owner_infos.erase(m_content_owner_infos.iterator_to(*info)); + delete info; + } + + /* Delete all save data owner infos. */ + while (!m_save_data_owner_infos.empty()) { + auto *info = std::addressof(*m_save_data_owner_infos.rbegin()); + m_save_data_owner_infos.erase(m_save_data_owner_infos.iterator_to(*info)); + delete info; + } + } + + bool AccessControl::HasContentOwnerId(u64 owner_id) const { + /* Check if we have a matching id. */ + for (const auto &info : m_content_owner_infos) { + if (info.GetId() == owner_id) { + return true; + } + } + + return false; + } + + Accessibility AccessControl::GetAccessibilitySaveDataOwnedBy(u64 owner_id) const { + /* Find a matching save data owner. */ + for (const auto &info : m_save_data_owner_infos) { + if (info.GetId() == owner_id) { + return info.GetAccessibility(); + } + } + + /* Default to no accessibility. */ + return Accessibility::MakeAccessibility(false, false); + } + + void AccessControl::ListContentOwnerId(s32 *out_count, u64 *out_owner_ids, s32 offset, s32 count) const { + /* If we have nothing to read, just give the count. */ + if (count == 0) { + *out_count = m_content_owner_infos.size(); + return; + } + + /* Read out the ids. */ + s32 read_offset = 0; + s32 read_count = 0; + if (out_owner_ids != nullptr) { + auto *cur_out = out_owner_ids; + for (const auto &info : m_content_owner_infos) { + /* Skip until we get to the desired offset. */ + if (read_offset < offset) { + ++read_offset; + continue; + } + + /* Set the output value. */ + *cur_out = info.GetId(); + ++cur_out; + ++read_count; + + /* If we've read as many as we can, finish. */ + if (read_count == count) { + break; + } + } + } + + /* Set the out value. */ + *out_count = read_count; + } + + void AccessControl::ListSaveDataOwnedId(s32 *out_count, ncm::ApplicationId *out_owner_ids, s32 offset, s32 count) const { + /* If we have nothing to read, just give the count. */ + if (count == 0) { + *out_count = m_save_data_owner_infos.size(); + return; + } + + /* Read out the ids. */ + s32 read_offset = 0; + s32 read_count = 0; + if (out_owner_ids != nullptr) { + auto *cur_out = out_owner_ids; + for (const auto &info : m_save_data_owner_infos) { + /* Skip until we get to the desired offset. */ + if (read_offset < offset) { + ++read_offset; + continue; + } + + /* Set the output value. */ + cur_out->value = info.GetId(); + ++cur_out; + ++read_count; + + /* If we've read as many as we can, finish. */ + if (read_count == count) { + break; + } + } + } + + /* Set the out value. */ + *out_count = read_count; + } + + Accessibility AccessControl::GetAccessibilityFor(AccessibilityType type) const { + switch (type) { + using enum AccessibilityType; + case MountLogo: return Accessibility::MakeAccessibility(m_flag_bits->CanMountLogoRead(), false); + case MountContentMeta: return Accessibility::MakeAccessibility(m_flag_bits->CanMountContentMetaRead(), false); + case MountContentControl: return Accessibility::MakeAccessibility(m_flag_bits->CanMountContentControlRead(), false); + case MountContentManual: return Accessibility::MakeAccessibility(m_flag_bits->CanMountContentManualRead(), false); + case MountContentData: return Accessibility::MakeAccessibility(m_flag_bits->CanMountContentDataRead(), false); + case MountApplicationPackage: return Accessibility::MakeAccessibility(m_flag_bits->CanMountApplicationPackageRead(), false); + case MountSaveDataStorage: return Accessibility::MakeAccessibility(m_flag_bits->CanMountSaveDataStorageRead(), m_flag_bits->CanMountSaveDataStorageWrite()); + case MountContentStorage: return Accessibility::MakeAccessibility(m_flag_bits->CanMountContentStorageRead(), m_flag_bits->CanMountContentStorageWrite()); + case MountImageAndVideoStorage: return Accessibility::MakeAccessibility(m_flag_bits->CanMountImageAndVideoStorageRead(), m_flag_bits->CanMountImageAndVideoStorageWrite()); + case MountCloudBackupWorkStorage: return Accessibility::MakeAccessibility(m_flag_bits->CanMountCloudBackupWorkStorageRead(), m_flag_bits->CanMountCloudBackupWorkStorageWrite()); + case MountCustomStorage: return Accessibility::MakeAccessibility(m_flag_bits->CanMountCustomStorage0Read(), m_flag_bits->CanMountCustomStorage0Write()); + case MountBisCalibrationFile: return Accessibility::MakeAccessibility(m_flag_bits->CanMountBisCalibrationFileRead(), m_flag_bits->CanMountBisCalibrationFileWrite()); + case MountBisSafeMode: return Accessibility::MakeAccessibility(m_flag_bits->CanMountBisSafeModeRead(), m_flag_bits->CanMountBisSafeModeWrite()); + case MountBisUser: return Accessibility::MakeAccessibility(m_flag_bits->CanMountBisUserRead(), m_flag_bits->CanMountBisUserWrite()); + case MountBisSystem: return Accessibility::MakeAccessibility(m_flag_bits->CanMountBisSystemRead(), m_flag_bits->CanMountBisSystemWrite()); + case MountBisSystemProperEncryption: return Accessibility::MakeAccessibility(m_flag_bits->CanMountBisSystemProperEncryptionRead(), m_flag_bits->CanMountBisSystemProperEncryptionWrite()); + case MountBisSystemProperPartition: return Accessibility::MakeAccessibility(m_flag_bits->CanMountBisSystemProperPartitionRead(), m_flag_bits->CanMountBisSystemProperPartitionWrite()); + case MountSdCard: return Accessibility::MakeAccessibility(m_flag_bits->CanMountSdCardRead(), m_flag_bits->CanMountSdCardWrite()); + case MountGameCard: return Accessibility::MakeAccessibility(m_flag_bits->CanMountGameCardRead(), false); + case MountDeviceSaveData: return Accessibility::MakeAccessibility(m_flag_bits->CanMountDeviceSaveDataRead(), m_flag_bits->CanMountDeviceSaveDataWrite()); + case MountSystemSaveData: return Accessibility::MakeAccessibility(m_flag_bits->CanMountSystemSaveDataRead(), m_flag_bits->CanMountSystemSaveDataWrite()); + case MountOthersSaveData: return Accessibility::MakeAccessibility(m_flag_bits->CanMountOthersSaveDataRead(), m_flag_bits->CanMountOthersSaveDataWrite()); + case MountOthersSystemSaveData: return Accessibility::MakeAccessibility(m_flag_bits->CanMountOthersSystemSaveDataRead(), m_flag_bits->CanMountOthersSystemSaveDataWrite()); + case OpenBisPartitionBootPartition1Root: return Accessibility::MakeAccessibility(m_flag_bits->CanOpenBisPartitionBootPartition1RootRead(), m_flag_bits->CanOpenBisPartitionBootPartition1RootWrite()); + case OpenBisPartitionBootPartition2Root: return Accessibility::MakeAccessibility(m_flag_bits->CanOpenBisPartitionBootPartition2RootRead(), m_flag_bits->CanOpenBisPartitionBootPartition2RootWrite()); + case OpenBisPartitionUserDataRoot: return Accessibility::MakeAccessibility(m_flag_bits->CanOpenBisPartitionUserDataRootRead(), m_flag_bits->CanOpenBisPartitionUserDataRootWrite()); + case OpenBisPartitionBootConfigAndPackage2Part1: return Accessibility::MakeAccessibility(m_flag_bits->CanOpenBisPartitionBootConfigAndPackage2Part1Read(), m_flag_bits->CanOpenBisPartitionBootConfigAndPackage2Part1Write()); + case OpenBisPartitionBootConfigAndPackage2Part2: return Accessibility::MakeAccessibility(m_flag_bits->CanOpenBisPartitionBootConfigAndPackage2Part2Read(), m_flag_bits->CanOpenBisPartitionBootConfigAndPackage2Part2Write()); + case OpenBisPartitionBootConfigAndPackage2Part3: return Accessibility::MakeAccessibility(m_flag_bits->CanOpenBisPartitionBootConfigAndPackage2Part3Read(), m_flag_bits->CanOpenBisPartitionBootConfigAndPackage2Part3Write()); + case OpenBisPartitionBootConfigAndPackage2Part4: return Accessibility::MakeAccessibility(m_flag_bits->CanOpenBisPartitionBootConfigAndPackage2Part4Read(), m_flag_bits->CanOpenBisPartitionBootConfigAndPackage2Part4Write()); + case OpenBisPartitionBootConfigAndPackage2Part5: return Accessibility::MakeAccessibility(m_flag_bits->CanOpenBisPartitionBootConfigAndPackage2Part5Read(), m_flag_bits->CanOpenBisPartitionBootConfigAndPackage2Part5Write()); + case OpenBisPartitionBootConfigAndPackage2Part6: return Accessibility::MakeAccessibility(m_flag_bits->CanOpenBisPartitionBootConfigAndPackage2Part6Read(), m_flag_bits->CanOpenBisPartitionBootConfigAndPackage2Part6Write()); + case OpenBisPartitionCalibrationBinary: return Accessibility::MakeAccessibility(m_flag_bits->CanOpenBisPartitionCalibrationBinaryRead(), m_flag_bits->CanOpenBisPartitionCalibrationBinaryWrite()); + case OpenBisPartitionCalibrationFile: return Accessibility::MakeAccessibility(m_flag_bits->CanOpenBisPartitionCalibrationFileRead(), m_flag_bits->CanOpenBisPartitionCalibrationFileWrite()); + case OpenBisPartitionSafeMode: return Accessibility::MakeAccessibility(m_flag_bits->CanOpenBisPartitionSafeModeRead(), m_flag_bits->CanOpenBisPartitionSafeModeWrite()); + case OpenBisPartitionUser: return Accessibility::MakeAccessibility(m_flag_bits->CanOpenBisPartitionUserRead(), m_flag_bits->CanOpenBisPartitionUserWrite()); + case OpenBisPartitionSystem: return Accessibility::MakeAccessibility(m_flag_bits->CanOpenBisPartitionSystemRead(), m_flag_bits->CanOpenBisPartitionSystemWrite()); + case OpenBisPartitionSystemProperEncryption: return Accessibility::MakeAccessibility(m_flag_bits->CanOpenBisPartitionSystemProperEncryptionRead(), m_flag_bits->CanOpenBisPartitionSystemProperEncryptionWrite()); + case OpenBisPartitionSystemProperPartition: return Accessibility::MakeAccessibility(m_flag_bits->CanOpenBisPartitionSystemProperPartitionRead(), m_flag_bits->CanOpenBisPartitionSystemProperPartitionWrite()); + case OpenSdCardStorage: return Accessibility::MakeAccessibility(m_flag_bits->CanOpenSdCardStorageRead(), m_flag_bits->CanOpenSdCardStorageWrite()); + case OpenGameCardStorage: return Accessibility::MakeAccessibility(m_flag_bits->CanOpenGameCardStorageRead(), m_flag_bits->CanOpenGameCardStorageWrite()); + case MountSystemDataPrivate: return Accessibility::MakeAccessibility(m_flag_bits->CanMountSystemDataPrivateRead(), false); + case MountHost: return Accessibility::MakeAccessibility(m_flag_bits->CanMountHostRead(), m_flag_bits->CanMountHostWrite()); + case MountRegisteredUpdatePartition: return Accessibility::MakeAccessibility(m_flag_bits->CanMountRegisteredUpdatePartitionRead() && g_is_debug_flag_enabled, false); + case MountSaveDataInternalStorage: return Accessibility::MakeAccessibility(m_flag_bits->CanOpenSaveDataInternalStorageRead(), m_flag_bits->CanOpenSaveDataInternalStorageWrite()); + case MountTemporaryDirectory: return Accessibility::MakeAccessibility(m_flag_bits->CanMountTemporaryDirectoryRead(), m_flag_bits->CanMountTemporaryDirectoryWrite()); + case MountAllBaseFileSystem: return Accessibility::MakeAccessibility(m_flag_bits->CanMountAllBaseFileSystemRead(), m_flag_bits->CanMountAllBaseFileSystemWrite()); + case NotMount: return Accessibility::MakeAccessibility(false, false); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + bool AccessControl::CanCall(OperationType type) const { + switch (type) { + using enum OperationType; + case InvalidateBisCache: return m_flag_bits->CanInvalidateBisCache(); + case EraseMmc: return m_flag_bits->CanEraseMmc(); + case GetGameCardDeviceCertificate: return m_flag_bits->CanGetGameCardDeviceCertificate(); + case GetGameCardIdSet: return m_flag_bits->CanGetGameCardIdSet(); + case FinalizeGameCardDriver: return m_flag_bits->CanFinalizeGameCardDriver(); + case GetGameCardAsicInfo: return m_flag_bits->CanGetGameCardAsicInfo(); + case CreateSaveData: return m_flag_bits->CanCreateSaveData(); + case DeleteSaveData: return m_flag_bits->CanDeleteSaveData(); + case CreateSystemSaveData: return m_flag_bits->CanCreateSystemSaveData(); + case CreateOthersSystemSaveData: return m_flag_bits->CanCreateOthersSystemSaveData(); + case DeleteSystemSaveData: return m_flag_bits->CanDeleteSystemSaveData(); + case OpenSaveDataInfoReader: return m_flag_bits->CanOpenSaveDataInfoReader(); + case OpenSaveDataInfoReaderForSystem: return m_flag_bits->CanOpenSaveDataInfoReaderForSystem(); + case OpenSaveDataInfoReaderForInternal: return m_flag_bits->CanOpenSaveDataInfoReaderForInternal(); + case OpenSaveDataMetaFile: return m_flag_bits->CanOpenSaveDataMetaFile(); + case SetCurrentPosixTime: return m_flag_bits->CanSetCurrentPosixTime(); + case ReadSaveDataFileSystemExtraData: return m_flag_bits->CanReadSaveDataFileSystemExtraData(); + case SetGlobalAccessLogMode: return m_flag_bits->CanSetGlobalAccessLogMode(); + case SetSpeedEmulationMode: return m_flag_bits->CanSetSpeedEmulationMode(); + case FillBis: return m_flag_bits->CanFillBis(); + case CorruptSaveData: return m_flag_bits->CanCorruptSaveData(); + case CorruptSystemSaveData: return m_flag_bits->CanCorruptSystemSaveData(); + case VerifySaveData: return m_flag_bits->CanVerifySaveData(); + case DebugSaveData: return m_flag_bits->CanDebugSaveData(); + case FormatSdCard: return m_flag_bits->CanFormatSdCard(); + case GetRightsId: return m_flag_bits->CanGetRightsId(); + case RegisterExternalKey: return m_flag_bits->CanRegisterExternalKey(); + case SetEncryptionSeed: return m_flag_bits->CanSetEncryptionSeed(); + case WriteSaveDataFileSystemExtraDataTimeStamp: return m_flag_bits->CanWriteSaveDataFileSystemExtraDataTimeStamp(); + case WriteSaveDataFileSystemExtraDataFlags: return m_flag_bits->CanWriteSaveDataFileSystemExtraDataFlags(); + case WriteSaveDataFileSystemExtraDataCommitId: return m_flag_bits->CanWriteSaveDataFileSystemExtraDataCommitId(); + case WriteSaveDataFileSystemExtraDataAll: return m_flag_bits->CanWriteSaveDataFileSystemExtraDataAll(); + case ExtendSaveData: return m_flag_bits->CanExtendSaveData(); + case ExtendSystemSaveData: return m_flag_bits->CanExtendSystemSaveData(); + case ExtendOthersSystemSaveData: return m_flag_bits->CanExtendOthersSystemSaveData(); + case RegisterUpdatePartition: return m_flag_bits->CanRegisterUpdatePartition() && g_is_debug_flag_enabled; + case OpenSaveDataTransferManager: return m_flag_bits->CanOpenSaveDataTransferManager(); + case OpenSaveDataTransferManagerVersion2: return m_flag_bits->CanOpenSaveDataTransferManagerVersion2(); + case OpenSaveDataTransferManagerForSaveDataRepair: return m_flag_bits->CanOpenSaveDataTransferManagerForSaveDataRepair(); + case OpenSaveDataTransferManagerForSaveDataRepairTool: return m_flag_bits->CanOpenSaveDataTransferManagerForSaveDataRepairTool(); + case OpenSaveDataTransferProhibiter: return m_flag_bits->CanOpenSaveDataTransferProhibiter(); + case OpenSaveDataMover: return m_flag_bits->CanOpenSaveDataMover(); + case OpenBisWiper: return m_flag_bits->CanOpenBisWiper(); + case ListAccessibleSaveDataOwnerId: return m_flag_bits->CanListAccessibleSaveDataOwnerId(); + case ControlMmcPatrol: return m_flag_bits->CanControlMmcPatrol(); + case OverrideSaveDataTransferTokenSignVerificationKey: return m_flag_bits->CanOverrideSaveDataTransferTokenSignVerificationKey(); + case OpenSdCardDetectionEventNotifier: return m_flag_bits->CanOpenSdCardDetectionEventNotifier(); + case OpenGameCardDetectionEventNotifier: return m_flag_bits->CanOpenGameCardDetectionEventNotifier(); + case OpenSystemDataUpdateEventNotifier: return m_flag_bits->CanOpenSystemDataUpdateEventNotifier(); + case NotifySystemDataUpdateEvent: return m_flag_bits->CanNotifySystemDataUpdateEvent(); + case OpenAccessFailureDetectionEventNotifier: return m_flag_bits->CanOpenAccessFailureDetectionEventNotifier(); + case GetAccessFailureDetectionEvent: return m_flag_bits->CanGetAccessFailureDetectionEvent(); + case IsAccessFailureDetected: return m_flag_bits->CanIsAccessFailureDetected(); + case ResolveAccessFailure: return m_flag_bits->CanResolveAccessFailure(); + case AbandonAccessFailure: return m_flag_bits->CanAbandonAccessFailure(); + case QuerySaveDataInternalStorageTotalSize: return m_flag_bits->CanQuerySaveDataInternalStorageTotalSize(); + case GetSaveDataCommitId: return m_flag_bits->CanGetSaveDataCommitId(); + case SetSdCardAccessibility: return m_flag_bits->CanSetSdCardAccessibility(); + case SimulateDevice: return m_flag_bits->CanSimulateDevice(); + case CreateSaveDataWithHashSalt: return m_flag_bits->CanCreateSaveDataWithHashSalt(); + case RegisterProgramIndexMapInfo: return m_flag_bits->CanRegisterProgramIndexMapInfo(); + case ChallengeCardExistence: return m_flag_bits->CanChallengeCardExistence(); + case CreateOwnSaveData: return m_flag_bits->CanCreateOwnSaveData(); + case DeleteOwnSaveData: return m_flag_bits->CanDeleteOwnSaveData(); + case ReadOwnSaveDataFileSystemExtraData: return m_flag_bits->CanReadOwnSaveDataFileSystemExtraData(); + case ExtendOwnSaveData: return m_flag_bits->CanExtendOwnSaveData(); + case OpenOwnSaveDataTransferProhibiter: return m_flag_bits->CanOpenOwnSaveDataTransferProhibiter(); + case FindOwnSaveDataWithFilter: return m_flag_bits->CanFindOwnSaveDataWithFilter(); + case OpenSaveDataTransferManagerForRepair: return m_flag_bits->CanOpenSaveDataTransferManagerForRepair(); + case SetDebugConfiguration: return m_flag_bits->CanSetDebugConfiguration(); + case OpenDataStorageByPath: return m_flag_bits->CanOpenDataStorageByPath(); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_deferred_process_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_deferred_process_manager.hpp new file mode 100644 index 00000000..7914a5c0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_deferred_process_manager.hpp @@ -0,0 +1,83 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fssrv { + + using NotifyProcessDeferredFunction = void (*)(u64 process_id); + + struct DeferredProcessEntryForDeviceError : public util::IntrusiveListBaseNode<DeferredProcessEntryForDeviceError>, public fs::impl::Newable { + os::MultiWaitHolderType *process_holder; + u64 process_id; + }; + + class DeferredProcessQueueForDeviceError { + NON_COPYABLE(DeferredProcessQueueForDeviceError); + NON_MOVEABLE(DeferredProcessQueueForDeviceError); + private: + using EntryList = util::IntrusiveListBaseTraits<DeferredProcessEntryForDeviceError>::ListType; + private: + EntryList m_list{}; + os::SdkMutex m_mutex{}; + public: + constexpr DeferredProcessQueueForDeviceError() = default; + }; + + class DeferredProcessEntryForPriority : public util::IntrusiveListBaseNode<DeferredProcessEntryForPriority>, public fs::impl::Newable { + private: + os::MultiWaitHolderType *m_process_holder; + FileSystemProxyServerSessionType m_session_type; + }; + + class DeferredProcessQueueForPriority { + NON_COPYABLE(DeferredProcessQueueForPriority); + NON_MOVEABLE(DeferredProcessQueueForPriority); + private: + using EntryList = util::IntrusiveListBaseTraits<DeferredProcessEntryForPriority>::ListType; + private: + EntryList m_list{}; + os::SdkRecursiveMutex m_mutex{}; + os::SdkConditionVariable m_cv{}; + public: + constexpr DeferredProcessQueueForPriority() = default; + }; + + template<typename ServerManager, NotifyProcessDeferredFunction NotifyProcessDeferred> + class DeferredProcessManager { + NON_COPYABLE(DeferredProcessManager); + NON_MOVEABLE(DeferredProcessManager); + private: + DeferredProcessQueueForDeviceError m_queue_for_device_error{}; + os::SdkMutex m_invoke_mutex_for_device_error{}; + DeferredProcessQueueForPriority m_queue_for_priority{}; + std::atomic_bool m_is_invoke_deferred_process_event_linked{}; + os::EventType m_invoke_event{}; + os::MultiWaitHolderType m_invoke_event_holder{}; + bool m_initialized{false}; + public: + constexpr DeferredProcessManager() = default; + + void Initialize() { + /* Check pre-conditions. */ + AMS_ASSERT(!m_initialized); + os::InitializeEvent(std::addressof(m_invoke_event), false, os::EventClearMode_ManualClear); + os::InitializeMultiWaitHolder(std::addressof(m_invoke_event_holder), std::addressof(m_invoke_event)); + m_initialized = true; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_file_system_proxy_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_file_system_proxy_api.cpp new file mode 100644 index 00000000..06b52666 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_file_system_proxy_api.cpp @@ -0,0 +1,125 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <stratosphere/fs/impl/fs_service_name.hpp> +#include "fssrv_deferred_process_manager.hpp" + +namespace ams::fssrv { + + namespace { + + struct FileSystemProxyServerOptions { + static constexpr size_t PointerBufferSize = 0x800; + static constexpr size_t MaxDomains = 0x40; + static constexpr size_t MaxDomainObjects = 0x4000; + static constexpr bool CanDeferInvokeRequest = true; + static constexpr bool CanManageMitmServers = false; + }; + + enum PortIndex { + PortIndex_FileSystemProxy, + PortIndex_ProgramRegistry, + PortIndex_FileSystemProxyForLoader, + PortIndex_Count, + }; + + constexpr size_t FileSystemProxyMaxSessions = 59; + constexpr size_t ProgramRegistryMaxSessions = 1; + constexpr size_t FileSystemProxyForLoaderMaxSessions = 1; + + constexpr size_t NumSessions = FileSystemProxyMaxSessions + ProgramRegistryMaxSessions + FileSystemProxyForLoaderMaxSessions; + + constinit os::SemaphoreType g_semaphore_for_file_system_proxy_for_loader = {}; + constinit os::SemaphoreType g_semaphore_for_program_registry = {}; + + class FileSystemProxyServerManager final : public ams::sf::hipc::ServerManager<PortIndex_Count, FileSystemProxyServerOptions, NumSessions> { + private: + virtual ams::Result OnNeedsToAccept(int port_index, Server *server) override { + switch (port_index) { + case PortIndex_FileSystemProxy: + { + R_RETURN(this->AcceptImpl(server, impl::GetFileSystemProxyServiceObject())); + } + case PortIndex_ProgramRegistry: + { + if (os::TryAcquireSemaphore(std::addressof(g_semaphore_for_program_registry))) { + ON_RESULT_FAILURE { os::ReleaseSemaphore(std::addressof(g_semaphore_for_program_registry)); }; + + R_RETURN(this->AcceptImpl(server, impl::GetProgramRegistryServiceObject())); + } else { + R_RETURN(this->AcceptImpl(server, impl::GetInvalidProgramRegistryServiceObject())); + } + } + case PortIndex_FileSystemProxyForLoader: + { + if (os::TryAcquireSemaphore(std::addressof(g_semaphore_for_file_system_proxy_for_loader))) { + ON_RESULT_FAILURE { os::ReleaseSemaphore(std::addressof(g_semaphore_for_file_system_proxy_for_loader)); }; + + R_RETURN(this->AcceptImpl(server, impl::GetFileSystemProxyForLoaderServiceObject())); + } else { + R_RETURN(this->AcceptImpl(server, impl::GetInvalidFileSystemProxyForLoaderServiceObject())); + } + } + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + }; + + constinit util::TypedStorage<FileSystemProxyServerManager> g_server_manager_storage = {}; + constinit FileSystemProxyServerManager *g_server_manager = nullptr; + + constinit os::BarrierType g_server_loop_barrier = {}; + constinit os::EventType g_resume_wait_event = {}; + + constinit bool g_is_suspended = false; + + void TemporaryNotifyProcessDeferred(u64) { /* TODO */ } + + constinit DeferredProcessManager<FileSystemProxyServerManager, TemporaryNotifyProcessDeferred> g_deferred_process_manager; + + } + + void InitializeForFileSystemProxy(const FileSystemProxyConfiguration &config) { + /* TODO FS-REIMPL */ + AMS_UNUSED(config); + } + + void InitializeFileSystemProxyServer(int threads) { + /* Initialize synchronization primitives. */ + os::InitializeBarrier(std::addressof(g_server_loop_barrier), threads + 1); + os::InitializeEvent(std::addressof(g_resume_wait_event), false, os::EventClearMode_ManualClear); + g_is_suspended = false; + os::InitializeSemaphore(std::addressof(g_semaphore_for_file_system_proxy_for_loader), 1, 1); + os::InitializeSemaphore(std::addressof(g_semaphore_for_program_registry), 1, 1); + + /* Initialize deferred process manager. */ + g_deferred_process_manager.Initialize(); + + /* Create the server and register our services. */ + AMS_ASSERT(g_server_manager == nullptr); + g_server_manager = util::ConstructAt(g_server_manager_storage); + + /* TODO: Manager handler. */ + + R_ABORT_UNLESS(g_server_manager->RegisterServer(PortIndex_FileSystemProxy, fs::impl::FileSystemProxyServiceName, FileSystemProxyMaxSessions)); + R_ABORT_UNLESS(g_server_manager->RegisterServer(PortIndex_ProgramRegistry, fs::impl::ProgramRegistryServiceName, ProgramRegistryMaxSessions)); + R_ABORT_UNLESS(g_server_manager->RegisterServer(PortIndex_FileSystemProxyForLoader, fs::impl::FileSystemProxyForLoaderServiceName, FileSystemProxyForLoaderMaxSessions)); + + /* Enable processing on server. */ + g_server_manager->ResumeProcessing(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_file_system_proxy_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_file_system_proxy_impl.cpp new file mode 100644 index 00000000..a22a2d88 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_file_system_proxy_impl.cpp @@ -0,0 +1,500 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <stratosphere/fssrv/fssrv_interface_adapters.hpp> +#include "impl/fssrv_allocator_for_service_framework.hpp" +#include "impl/fssrv_program_info.hpp" + +namespace ams::fssrv { + + FileSystemProxyImpl::FileSystemProxyImpl() { + /* TODO: Set core impl. */ + m_process_id = os::InvalidProcessId.value; + } + + FileSystemProxyImpl::~FileSystemProxyImpl() { + /* ... */ + } + + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-parameter" + + Result FileSystemProxyImpl::OpenFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, u32 type) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::FileSystemProxyImpl::SetCurrentProcess(const ams::sf::ClientProcessId &client_pid) { + /* Set current process. */ + m_process_id = client_pid.GetValue().value; + + /* TODO: Allocate NcaFileSystemService. */ + /* TODO: Allocate SaveDataFileSystemService. */ + R_SUCCEED(); + } + + Result FileSystemProxyImpl::OpenDataFileSystemByCurrentProcess(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenFileSystemWithPatch(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, ncm::ProgramId program_id, u32 type) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenFileSystemWithIdObsolete(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, u64 program_id, u32 type) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenFileSystemWithId(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, fs::ContentAttributes attr, u64 program_id, u32 type) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenDataFileSystemByProgramId(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, ncm::ProgramId program_id) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenBisFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, u32 id) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenBisStorage(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, u32 id) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::InvalidateBisCache() { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenHostFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path) { + /* Invoke the modern API from the legacy API. */ + R_RETURN(this->OpenHostFileSystemWithOption(out, path, fs::MountHostOption::None._value)); + } + + Result FileSystemProxyImpl::OpenSdCardFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::FormatSdCardFileSystem() { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::DeleteSaveDataFileSystem(u64 save_data_id) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::CreateSaveDataFileSystem(const fs::SaveDataAttribute &attribute, const fs::SaveDataCreationInfo &creation_info, const fs::SaveDataMetaInfo &meta_info) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::CreateSaveDataFileSystemBySystemSaveDataId(const fs::SaveDataAttribute &attribute, const fs::SaveDataCreationInfo &creation_info) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::RegisterSaveDataFileSystemAtomicDeletion(const ams::sf::InBuffer &save_data_ids) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::DeleteSaveDataFileSystemBySaveDataSpaceId(u8 indexer_space_id, u64 save_data_id) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::FormatSdCardDryRun() { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::IsExFatSupported(ams::sf::Out<bool> out) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::DeleteSaveDataFileSystemBySaveDataAttribute(u8 space_id, const fs::SaveDataAttribute &attribute) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenGameCardStorage(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, u32 handle, u32 partition) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenGameCardFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u32 handle, u32 partition) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::ExtendSaveDataFileSystem(u8 space_id, u64 save_data_id, s64 available_size, s64 journal_size) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::DeleteCacheStorage(u16 index) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::GetCacheStorageSize(ams::sf::Out<s64> out_size, ams::sf::Out<s64> out_journal_size, u16 index) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::CreateSaveDataFileSystemWithHashSalt(const fs::SaveDataAttribute &attribute, const fs::SaveDataCreationInfo &creation_info, const fs::SaveDataMetaInfo &meta_info, const fs::HashSalt &salt) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenHostFileSystemWithOption(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, const fssrv::sf::FspPath &path, u32 _option) { + /* TODO: GetProgramInfo */ + /* TODO: GetAccessibility. */ + /* TODO: Check Accessibility CanRead/CanWrite */ + + /* Convert the path. */ + fs::Path normalized_path; + #if defined(ATMOSPHERE_OS_WINDOWS) + R_TRY(normalized_path.Initialize(path.str)); + #else + if (path.str[0] == '/' && path.str[1] == '/') { + R_TRY(normalized_path.Initialize(path.str)); + } else { + R_TRY(normalized_path.InitializeWithReplaceUnc(path.str)); + } + #endif + + /* Normalize the path. */ + fs::PathFlags path_flags; + path_flags.AllowWindowsPath(); + path_flags.AllowRelativePath(); + path_flags.AllowEmptyPath(); + R_TRY(normalized_path.Normalize(path_flags)); + + /* Parse option. */ + const fs::MountHostOption option{ _option }; + + /* TODO: FileSystemProxyCoreImpl::OpenHostFileSystem */ + /* TODO: use creator interfaces */ + std::shared_ptr<fs::fsa::IFileSystem> fs; + { + fssrv::fscreator::LocalFileSystemCreator local_fs_creator(true); + + R_TRY(static_cast<fscreator::ILocalFileSystemCreator &>(local_fs_creator).Create(std::addressof(fs), normalized_path, option.HasPseudoCaseSensitiveFlag())); + } + + /* Determine path flags for the result fs. */ + fs::PathFlags host_path_flags; + if (path.str[0] == 0) { + host_path_flags.AllowWindowsPath(); + } + + /* Create an interface adapter. */ + auto sf_fs = impl::FileSystemObjectFactory::CreateSharedEmplaced<fssrv::sf::IFileSystem, impl::FileSystemInterfaceAdapter>(std::move(fs), host_path_flags, false); + R_UNLESS(sf_fs != nullptr, fs::ResultAllocationMemoryFailedInFileSystemProxyImplA()); + + /* Set the output. */ + *out = std::move(sf_fs); + R_SUCCEED(); + } + + Result FileSystemProxyImpl::OpenSaveDataFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u8 space_id, const fs::SaveDataAttribute &attribute) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenSaveDataFileSystemBySystemSaveDataId(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u8 space_id, const fs::SaveDataAttribute &attribute) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenReadOnlySaveDataFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u8 space_id, const fs::SaveDataAttribute &attribute) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(const ams::sf::OutBuffer &buffer, u8 space_id, u64 save_data_id) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::ReadSaveDataFileSystemExtraData(const ams::sf::OutBuffer &buffer, u64 save_data_id) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::WriteSaveDataFileSystemExtraData(u64 save_data_id, u8 space_id, const ams::sf::InBuffer &buffer) { + AMS_ABORT("TODO"); + } + + /* ... */ + + Result FileSystemProxyImpl::OpenImageDirectoryFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u32 id) { + AMS_ABORT("TODO"); + } + + /* ... */ + + Result FileSystemProxyImpl::OpenContentStorageFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u32 id) { + AMS_ABORT("TODO"); + } + + /* ... */ + + Result FileSystemProxyImpl::OpenDataStorageByCurrentProcess(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenDataStorageByProgramId(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, ncm::ProgramId program_id) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenDataStorageByDataId(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, ncm::DataId data_id, u8 storage_id) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenPatchDataStorageByCurrentProcess(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenDataFileSystemWithProgramIndex(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out, u8 index) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenDataStorageWithProgramIndex(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, u8 index) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenDataStorageByPathObsolete(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, const fssrv::sf::FspPath &path, u32 type) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenDataStorageByPath(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IStorage>> out, const fssrv::sf::FspPath &path, fs::ContentAttributes attr, u32 type) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenDeviceOperator(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IDeviceOperator>> out) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenSdCardDetectionEventNotifier(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IEventNotifier>> out) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenGameCardDetectionEventNotifier(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IEventNotifier>> out) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenSystemDataUpdateEventNotifier(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IEventNotifier>> out) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::NotifySystemDataUpdateEvent() { + AMS_ABORT("TODO"); + } + + /* ... */ + + Result FileSystemProxyImpl::SetCurrentPosixTime(s64 posix_time) { + AMS_ABORT("TODO"); + } + + /* ... */ + + Result FileSystemProxyImpl::GetRightsId(ams::sf::Out<fs::RightsId> out, ncm::ProgramId program_id, ncm::StorageId storage_id) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::RegisterExternalKey(const fs::RightsId &rights_id, const spl::AccessKey &access_key) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::UnregisterAllExternalKey() { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::GetProgramId(ams::sf::Out<ncm::ProgramId> out_program_id, const fssrv::sf::FspPath &path, fs::ContentAttributes attr) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::GetRightsIdByPath(ams::sf::Out<fs::RightsId> out, const fssrv::sf::FspPath &path) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::GetRightsIdAndKeyGenerationByPathObsolete(ams::sf::Out<fs::RightsId> out, ams::sf::Out<u8> out_key_generation, const fssrv::sf::FspPath &path) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::GetRightsIdAndKeyGenerationByPath(ams::sf::Out<fs::RightsId> out, ams::sf::Out<u8> out_key_generation, const fssrv::sf::FspPath &path, fs::ContentAttributes attr) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::SetCurrentPosixTimeWithTimeDifference(s64 posix_time, s32 time_difference) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::GetFreeSpaceSizeForSaveData(ams::sf::Out<s64> out, u8 space_id) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::VerifySaveDataFileSystemBySaveDataSpaceId() { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::CorruptSaveDataFileSystemBySaveDataSpaceId() { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::QuerySaveDataInternalStorageTotalSize() { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::GetSaveDataCommitId() { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::UnregisterExternalKey(const fs::RightsId &rights_id) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::SetSdCardEncryptionSeed(const fs::EncryptionSeed &seed) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::SetSdCardAccessibility(bool accessible) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::IsSdCardAccessible(ams::sf::Out<bool> out) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::IsSignedSystemPartitionOnSdCardValid(ams::sf::Out<bool> out) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenAccessFailureDetectionEventNotifier() { + AMS_ABORT("TODO"); + } + + /* ... */ + + Result FileSystemProxyImpl::GetAndClearErrorInfo(ams::sf::Out<fs::FileSystemProxyErrorInfo> out) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::RegisterProgramIndexMapInfo(const ams::sf::InBuffer &buffer, s32 count) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::SetBisRootForHost(u32 id, const fssrv::sf::FspPath &path) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::SetSaveDataSize(s64 size, s64 journal_size) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::SetSaveDataRootPath(const fssrv::sf::FspPath &path) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::DisableAutoSaveDataCreation() { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::SetGlobalAccessLogMode(u32 mode) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::GetGlobalAccessLogMode(ams::sf::Out<u32> out) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OutputAccessLogToSdCard(const ams::sf::InBuffer &buf) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::RegisterUpdatePartition() { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OpenRegisteredUpdatePartition(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::GetAndClearMemoryReportInfo(ams::sf::Out<fs::MemoryReportInfo> out) { + AMS_ABORT("TODO"); + } + + /* ... */ + + Result FileSystemProxyImpl::GetProgramIndexForAccessLog(ams::sf::Out<u32> out_idx, ams::sf::Out<u32> out_count) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::GetFsStackUsage(ams::sf::Out<u32> out, u32 type) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::UnsetSaveDataRootPath() { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OutputMultiProgramTagAccessLog() { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::FlushAccessLogOnSdCard() { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OutputApplicationInfoAccessLog() { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::RegisterDebugConfiguration(u32 key, s64 value) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::UnregisterDebugConfiguration(u32 key) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::OverrideSaveDataTransferTokenSignVerificationKey(const ams::sf::InBuffer &buf) { + AMS_ABORT("TODO"); + } + + Result FileSystemProxyImpl::CorruptSaveDataFileSystemByOffset(u8 space_id, u64 save_data_id, s64 offset) { + AMS_ABORT("TODO"); + } + + /* ... */ + + #pragma GCC diagnostic pop + + Result FileSystemProxyImpl::OpenCodeFileSystemDeprecated(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, const fssrv::sf::Path &path, ncm::ProgramId program_id) { + AMS_ABORT("TODO"); + AMS_UNUSED(out_fs, path, program_id); + } + + Result FileSystemProxyImpl::OpenCodeFileSystemDeprecated2(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, ams::sf::Out<fs::CodeVerificationData> out_verif, const fssrv::sf::Path &path, ncm::ProgramId program_id) { + AMS_ABORT("TODO"); + AMS_UNUSED(out_fs, out_verif, path, program_id); + } + + Result FileSystemProxyImpl::OpenCodeFileSystemDeprecated3(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, ams::sf::Out<fs::CodeVerificationData> out_verif, const fssrv::sf::Path &path, fs::ContentAttributes attr, ncm::ProgramId program_id) { + AMS_ABORT("TODO"); + AMS_UNUSED(out_fs, out_verif, path, attr, program_id); + } + + Result FileSystemProxyImpl::OpenCodeFileSystemDeprecated4(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, const ams::sf::OutBuffer &out_verif, const fssrv::sf::Path &path, fs::ContentAttributes attr, ncm::ProgramId program_id) { + AMS_ABORT("TODO"); + AMS_UNUSED(out_fs, out_verif, path, attr, program_id); + } + + Result FileSystemProxyImpl::OpenCodeFileSystem(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out_fs, const ams::sf::OutBuffer &out_verif, fs::ContentAttributes attr, ncm::ProgramId program_id, ncm::StorageId storage_id) { + AMS_ABORT("TODO"); + AMS_UNUSED(out_fs, out_verif, attr, program_id, storage_id); + } + + Result FileSystemProxyImpl::IsArchivedProgram(ams::sf::Out<bool> out, u64 process_id) { + AMS_ABORT("TODO"); + AMS_UNUSED(out, process_id); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_filesystem_interface_adapter.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_filesystem_interface_adapter.cpp new file mode 100644 index 00000000..a9f19164 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_filesystem_interface_adapter.cpp @@ -0,0 +1,379 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <stratosphere/fssrv/fssrv_interface_adapters.hpp> +#include "impl/fssrv_allocator_for_service_framework.hpp" +#include "fssrv_retry_utility.hpp" + +namespace ams::fssrv::impl { + + namespace { + + constexpr const char *RootDirectory = "/"; + + } + + FileInterfaceAdapter::FileInterfaceAdapter(std::unique_ptr<fs::fsa::IFile> &&file, FileSystemInterfaceAdapter *parent, bool allow_all) + : m_parent_filesystem(parent, true), m_base_file(std::move(file)), m_allow_all_operations(allow_all) + { + /* ... */ + } + + Result FileInterfaceAdapter::Read(ams::sf::Out<s64> out, s64 offset, const ams::sf::OutNonSecureBuffer &buffer, s64 size, fs::ReadOption option) { + /* Check pre-conditions. */ + R_UNLESS(0 <= offset, fs::ResultInvalidOffset()); + R_UNLESS(0 <= size, fs::ResultInvalidSize()); + R_UNLESS(size <= static_cast<s64>(buffer.GetSize()), fs::ResultInvalidSize()); + + /* Read the data, retrying on corruption. */ + size_t read_size = 0; + R_TRY(RetryFinitelyForDataCorrupted([&] () ALWAYS_INLINE_LAMBDA { + R_RETURN(m_base_file->Read(std::addressof(read_size), offset, buffer.GetPointer(), static_cast<size_t>(size), option)); + })); + + /* Set the output size. */ + *out = read_size; + R_SUCCEED(); + } + + Result FileInterfaceAdapter::Write(s64 offset, const ams::sf::InNonSecureBuffer &buffer, s64 size, fs::WriteOption option) { + /* Check pre-conditions. */ + R_UNLESS(0 <= offset, fs::ResultInvalidOffset()); + R_UNLESS(0 <= size, fs::ResultInvalidSize()); + R_UNLESS(size <= static_cast<s64>(buffer.GetSize()), fs::ResultInvalidSize()); + + /* Temporarily increase our thread's priority. */ + fssystem::ScopedThreadPriorityChangerByAccessPriority cp(fssystem::ScopedThreadPriorityChangerByAccessPriority::AccessMode::Write); + + R_RETURN(m_base_file->Write(offset, buffer.GetPointer(), size, option)); + } + + Result FileInterfaceAdapter::Flush() { + R_RETURN(m_base_file->Flush()); + } + + Result FileInterfaceAdapter::SetSize(s64 size) { + R_UNLESS(size >= 0, fs::ResultInvalidSize()); + R_RETURN(m_base_file->SetSize(size)); + } + + Result FileInterfaceAdapter::GetSize(ams::sf::Out<s64> out) { + /* Get the size, retrying on corruption. */ + R_RETURN(RetryFinitelyForDataCorrupted([&] () ALWAYS_INLINE_LAMBDA { + R_RETURN(m_base_file->GetSize(out.GetPointer())); + })); + } + + Result FileInterfaceAdapter::OperateRange(ams::sf::Out<fs::FileQueryRangeInfo> out, s32 op_id, s64 offset, s64 size) { + /* N includes this redundant check, so we will too. */ + R_UNLESS(out.GetPointer() != nullptr, fs::ResultNullptrArgument()); + + /* Clear the range info. */ + out->Clear(); + + if (op_id == static_cast<s32>(fs::OperationId::QueryRange)) { + fs::FileQueryRangeInfo info; + R_TRY(m_base_file->OperateRange(std::addressof(info), sizeof(info), fs::OperationId::QueryRange, offset, size, nullptr, 0)); + out->Merge(info); + } else if (op_id == static_cast<s32>(fs::OperationId::Invalidate)) { + R_TRY(m_base_file->OperateRange(nullptr, 0, fs::OperationId::Invalidate, offset, size, nullptr, 0)); + } + + R_SUCCEED(); + } + + Result FileInterfaceAdapter::OperateRangeWithBuffer(const ams::sf::OutNonSecureBuffer &out_buf, const ams::sf::InNonSecureBuffer &in_buf, s32 op_id, s64 offset, s64 size) { + /* Check that we have permission to perform the operation. */ + switch (static_cast<fs::OperationId>(op_id)) { + using enum fs::OperationId; + case QueryUnpreparedRange: + case QueryLazyLoadCompletionRate: + case SetLazyLoadPriority: + /* Lazy load/unprepared operations are always allowed to be performed with buffer. */ + break; + default: + R_UNLESS(m_allow_all_operations, fs::ResultPermissionDenied()); + } + + /* Perform the operation. */ + R_RETURN(m_base_file->OperateRange(out_buf.GetPointer(), out_buf.GetSize(), static_cast<fs::OperationId>(op_id), offset, size, in_buf.GetPointer(), in_buf.GetSize())); + } + + DirectoryInterfaceAdapter::DirectoryInterfaceAdapter(std::unique_ptr<fs::fsa::IDirectory> &&dir, FileSystemInterfaceAdapter *parent, bool allow_all) + : m_parent_filesystem(parent, true), m_base_dir(std::move(dir)), m_allow_all_operations(allow_all) + { + /* ... */ + } + + Result DirectoryInterfaceAdapter::Read(ams::sf::Out<s64> out, const ams::sf::OutBuffer &out_entries) { + /* Get the maximum number of entries we can read into the buffer. */ + const s64 max_num_entries = out_entries.GetSize() / sizeof(fs::DirectoryEntry); + R_UNLESS(max_num_entries >= 0, fs::ResultInvalidSize()); + + /* Get the size, retrying on corruption. */ + s64 num_read = 0; + R_TRY(RetryFinitelyForDataCorrupted([&] () ALWAYS_INLINE_LAMBDA { + R_RETURN(m_base_dir->Read(std::addressof(num_read), reinterpret_cast<fs::DirectoryEntry *>(out_entries.GetPointer()), max_num_entries)); + })); + + /* Set the output. */ + *out = num_read; + R_SUCCEED(); + + } + + Result DirectoryInterfaceAdapter::GetEntryCount(ams::sf::Out<s64> out) { + R_RETURN(m_base_dir->GetEntryCount(out.GetPointer())); + } + + Result FileSystemInterfaceAdapter::SetUpPath(fs::Path *out, const fssrv::sf::Path &sf_path) { + /* Initialize the fs path. */ + if (m_path_flags.IsWindowsPathAllowed()) { + R_TRY(out->InitializeWithReplaceUnc(sf_path.str)); + } else { + R_TRY(out->Initialize(sf_path.str)); + } + + /* Ensure the path is normalized. */ + R_RETURN(out->Normalize(m_path_flags)); + } + + Result FileSystemInterfaceAdapter::CreateFile(const fssrv::sf::Path &path, s64 size, s32 option) { + /* Check pre-conditions. */ + R_UNLESS(size >= 0, fs::ResultInvalidSize()); + + /* Normalize the input path. */ + fs::Path fs_path; + R_TRY(this->SetUpPath(std::addressof(fs_path), path)); + + R_RETURN(m_base_fs->CreateFile(fs_path, size, option)); + } + + Result FileSystemInterfaceAdapter::DeleteFile(const fssrv::sf::Path &path) { + /* Normalize the input path. */ + fs::Path fs_path; + R_TRY(this->SetUpPath(std::addressof(fs_path), path)); + + R_RETURN(m_base_fs->DeleteFile(fs_path)); + } + + Result FileSystemInterfaceAdapter::CreateDirectory(const fssrv::sf::Path &path) { + /* Normalize the input path. */ + fs::Path fs_path; + R_TRY(this->SetUpPath(std::addressof(fs_path), path)); + + /* Sanity check that the directory isn't the root. */ + R_UNLESS(fs_path != RootDirectory, fs::ResultPathAlreadyExists()); + + R_RETURN(m_base_fs->CreateDirectory(fs_path)); + } + + Result FileSystemInterfaceAdapter::DeleteDirectory(const fssrv::sf::Path &path) { + /* Normalize the input path. */ + fs::Path fs_path; + R_TRY(this->SetUpPath(std::addressof(fs_path), path)); + + /* Sanity check that the directory isn't the root. */ + R_UNLESS(fs_path != RootDirectory, fs::ResultDirectoryNotDeletable()); + + R_RETURN(m_base_fs->DeleteDirectory(fs_path)); + } + + Result FileSystemInterfaceAdapter::DeleteDirectoryRecursively(const fssrv::sf::Path &path) { + /* Normalize the input path. */ + fs::Path fs_path; + R_TRY(this->SetUpPath(std::addressof(fs_path), path)); + + /* Sanity check that the directory isn't the root. */ + R_UNLESS(fs_path != RootDirectory, fs::ResultDirectoryNotDeletable()); + + R_RETURN(m_base_fs->DeleteDirectoryRecursively(fs_path)); + } + + Result FileSystemInterfaceAdapter::RenameFile(const fssrv::sf::Path &old_path, const fssrv::sf::Path &new_path) { + /* Normalize the input paths. */ + fs::Path fs_old_path; + fs::Path fs_new_path; + R_TRY(this->SetUpPath(std::addressof(fs_old_path), old_path)); + R_TRY(this->SetUpPath(std::addressof(fs_new_path), new_path)); + + R_RETURN(m_base_fs->RenameFile(fs_old_path, fs_new_path)); + } + + Result FileSystemInterfaceAdapter::RenameDirectory(const fssrv::sf::Path &old_path, const fssrv::sf::Path &new_path) { + /* Normalize the input paths. */ + fs::Path fs_old_path; + fs::Path fs_new_path; + R_TRY(this->SetUpPath(std::addressof(fs_old_path), old_path)); + R_TRY(this->SetUpPath(std::addressof(fs_new_path), new_path)); + + R_UNLESS(!fs::IsSubPath(fs_old_path.GetString(), fs_new_path.GetString()), fs::ResultDirectoryNotRenamable()); + + R_RETURN(m_base_fs->RenameDirectory(fs_old_path, fs_new_path)); + } + + Result FileSystemInterfaceAdapter::GetEntryType(ams::sf::Out<u32> out, const fssrv::sf::Path &path) { + /* Normalize the input path. */ + fs::Path fs_path; + R_TRY(this->SetUpPath(std::addressof(fs_path), path)); + + static_assert(sizeof(*out.GetPointer()) == sizeof(fs::DirectoryEntryType)); + R_RETURN(m_base_fs->GetEntryType(reinterpret_cast<fs::DirectoryEntryType *>(out.GetPointer()), fs_path)); + } + + Result FileSystemInterfaceAdapter::OpenFile(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFile>> out, const fssrv::sf::Path &path, u32 mode) { + /* Normalize the input path. */ + fs::Path fs_path; + R_TRY(this->SetUpPath(std::addressof(fs_path), path)); + + /* Open the file, retrying on corruption. */ + std::unique_ptr<fs::fsa::IFile> file; + R_TRY(RetryFinitelyForDataCorrupted([&] () ALWAYS_INLINE_LAMBDA { + R_RETURN(m_base_fs->OpenFile(std::addressof(file), fs_path, static_cast<fs::OpenMode>(mode))); + })); + + /* If we're a mitm interface, we should preserve the resulting target object id. */ + if (m_is_mitm_interface) { + /* TODO: This is a hack to get the mitm API to work. Better solution? */ + const auto target_object_id = file->GetDomainObjectId(); + + ams::sf::SharedPointer<fssrv::sf::IFile> file_intf = FileSystemObjectFactory::CreateSharedEmplaced<fssrv::sf::IFile, FileInterfaceAdapter>(std::move(file), this, m_allow_all_operations); + R_UNLESS(file_intf != nullptr, fs::ResultAllocationMemoryFailedInFileSystemInterfaceAdapterA()); + + out.SetValue(std::move(file_intf), target_object_id); + } else { + ams::sf::SharedPointer<fssrv::sf::IFile> file_intf = FileSystemObjectFactory::CreateSharedEmplaced<fssrv::sf::IFile, FileInterfaceAdapter>(std::move(file), this, m_allow_all_operations); + R_UNLESS(file_intf != nullptr, fs::ResultAllocationMemoryFailedInFileSystemInterfaceAdapterA()); + + out.SetValue(std::move(file_intf)); + } + + R_SUCCEED(); + } + + Result FileSystemInterfaceAdapter::OpenDirectory(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IDirectory>> out, const fssrv::sf::Path &path, u32 mode) { + /* Normalize the input path. */ + fs::Path fs_path; + R_TRY(this->SetUpPath(std::addressof(fs_path), path)); + + /* Open the directory, retrying on corruption. */ + std::unique_ptr<fs::fsa::IDirectory> dir; + R_TRY(RetryFinitelyForDataCorrupted([&] () ALWAYS_INLINE_LAMBDA { + R_RETURN(m_base_fs->OpenDirectory(std::addressof(dir), fs_path, static_cast<fs::OpenDirectoryMode>(mode))); + })); + + /* If we're a mitm interface, we should preserve the resulting target object id. */ + if (m_is_mitm_interface) { + /* TODO: This is a hack to get the mitm API to work. Better solution? */ + const auto target_object_id = dir->GetDomainObjectId(); + + ams::sf::SharedPointer<fssrv::sf::IDirectory> dir_intf = FileSystemObjectFactory::CreateSharedEmplaced<fssrv::sf::IDirectory, DirectoryInterfaceAdapter>(std::move(dir), this, m_allow_all_operations); + R_UNLESS(dir_intf != nullptr, fs::ResultAllocationMemoryFailedInFileSystemInterfaceAdapterA()); + + out.SetValue(std::move(dir_intf), target_object_id); + } else { + ams::sf::SharedPointer<fssrv::sf::IDirectory> dir_intf = FileSystemObjectFactory::CreateSharedEmplaced<fssrv::sf::IDirectory, DirectoryInterfaceAdapter>(std::move(dir), this, m_allow_all_operations); + R_UNLESS(dir_intf != nullptr, fs::ResultAllocationMemoryFailedInFileSystemInterfaceAdapterA()); + + out.SetValue(std::move(dir_intf)); + } + + R_SUCCEED(); + } + + Result FileSystemInterfaceAdapter::Commit() { + R_RETURN(m_base_fs->Commit()); + } + + Result FileSystemInterfaceAdapter::GetFreeSpaceSize(ams::sf::Out<s64> out, const fssrv::sf::Path &path) { + /* Normalize the input path. */ + fs::Path fs_path; + R_TRY(this->SetUpPath(std::addressof(fs_path), path)); + + R_RETURN(m_base_fs->GetFreeSpaceSize(out.GetPointer(), fs_path)); + } + + Result FileSystemInterfaceAdapter::GetTotalSpaceSize(ams::sf::Out<s64> out, const fssrv::sf::Path &path) { + /* Normalize the input path. */ + fs::Path fs_path; + R_TRY(this->SetUpPath(std::addressof(fs_path), path)); + + R_RETURN(m_base_fs->GetTotalSpaceSize(out.GetPointer(), fs_path)); + } + + Result FileSystemInterfaceAdapter::CleanDirectoryRecursively(const fssrv::sf::Path &path) { + /* Normalize the input path. */ + fs::Path fs_path; + R_TRY(this->SetUpPath(std::addressof(fs_path), path)); + + R_RETURN(m_base_fs->CleanDirectoryRecursively(fs_path)); + } + + Result FileSystemInterfaceAdapter::GetFileTimeStampRaw(ams::sf::Out<fs::FileTimeStampRaw> out, const fssrv::sf::Path &path) { + /* Normalize the input path. */ + fs::Path fs_path; + R_TRY(this->SetUpPath(std::addressof(fs_path), path)); + + R_RETURN(m_base_fs->GetFileTimeStampRaw(out.GetPointer(), fs_path)); + } + + Result FileSystemInterfaceAdapter::QueryEntry(const ams::sf::OutNonSecureBuffer &out_buf, const ams::sf::InNonSecureBuffer &in_buf, s32 query_id, const fssrv::sf::Path &path) { + /* Check that we have permission to perform the operation. */ + switch (static_cast<fs::fsa::QueryId>(query_id)) { + using enum fs::fsa::QueryId; + case SetConcatenationFileAttribute: + case IsSignedSystemPartitionOnSdCardValid: + case QueryUnpreparedFileInformation: + /* Only certain operations are unconditionally allowable. */ + break; + default: + R_UNLESS(m_allow_all_operations, fs::ResultPermissionDenied()); + } + + /* Normalize the input path. */ + fs::Path fs_path; + R_TRY(this->SetUpPath(std::addressof(fs_path), path)); + + char *dst = reinterpret_cast<char *>(out_buf.GetPointer()); + const char *src = reinterpret_cast<const char *>(in_buf.GetPointer()); + R_RETURN(m_base_fs->QueryEntry(dst, out_buf.GetSize(), src, in_buf.GetSize(), static_cast<fs::fsa::QueryId>(query_id), fs_path)); + } + + #if defined(ATMOSPHERE_OS_HORIZON) + Result RemoteFileSystem::OpenFile(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFile>> out, const fssrv::sf::Path &path, u32 mode) { + FsFile f; + R_TRY(fsFsOpenFile(std::addressof(m_base_fs), path.str, mode, std::addressof(f))); + + auto intf = FileSystemObjectFactory::CreateSharedEmplaced<fssrv::sf::IFile, RemoteFile>(f); + R_UNLESS(intf != nullptr, fs::ResultAllocationMemoryFailedInFileSystemInterfaceAdapterA()); + + out.SetValue(std::move(intf)); + R_SUCCEED(); + } + + Result RemoteFileSystem::OpenDirectory(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IDirectory>> out, const fssrv::sf::Path &path, u32 mode) { + FsDir d; + R_TRY(fsFsOpenDirectory(std::addressof(m_base_fs), path.str, mode, std::addressof(d))); + + auto intf = FileSystemObjectFactory::CreateSharedEmplaced<fssrv::sf::IDirectory, RemoteDirectory>(d); + R_UNLESS(intf != nullptr, fs::ResultAllocationMemoryFailedInFileSystemInterfaceAdapterA()); + + out.SetValue(std::move(intf)); + R_SUCCEED(); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_memory_resource_from_exp_heap.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_memory_resource_from_exp_heap.cpp new file mode 100644 index 00000000..44dde37b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_memory_resource_from_exp_heap.cpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssrv { + + namespace { + + size_t GetUsedSize(void *p) { + const auto block_head = reinterpret_cast<const lmem::impl::ExpHeapMemoryBlockHead *>(reinterpret_cast<uintptr_t>(p) - sizeof(lmem::impl::ExpHeapMemoryBlockHead)); + return block_head->block_size + ((block_head->attributes >> 8) & 0x7F) + sizeof(lmem::impl::ExpHeapMemoryBlockHead); + } + + } + + void PeakCheckableMemoryResourceFromExpHeap::OnAllocate(void *p, size_t size) { + AMS_UNUSED(size); + + if (p != nullptr) { + m_current_free_size = GetUsedSize(p); + m_peak_free_size = std::min(m_peak_free_size, m_current_free_size); + } + } + + void PeakCheckableMemoryResourceFromExpHeap::OnDeallocate(void *p, size_t size) { + AMS_UNUSED(size); + + if (p != nullptr) { + m_current_free_size += GetUsedSize(p); + } + } + + void *PeakCheckableMemoryResourceFromExpHeap::AllocateImpl(size_t size, size_t align) { + std::scoped_lock lk(m_mutex); + + void *p = lmem::AllocateFromExpHeap(m_heap_handle, size, static_cast<s32>(align)); + this->OnAllocate(p, size); + return p; + } + + void PeakCheckableMemoryResourceFromExpHeap::DeallocateImpl(void *p, size_t size, size_t align) { + AMS_UNUSED(align); + + std::scoped_lock lk(m_mutex); + + this->OnDeallocate(p, size); + lmem::FreeToExpHeap(m_heap_handle, p); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_memory_resource_from_standard_allocator.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_memory_resource_from_standard_allocator.cpp new file mode 100644 index 00000000..d198aac6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_memory_resource_from_standard_allocator.cpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssrv { + + MemoryResourceFromStandardAllocator::MemoryResourceFromStandardAllocator(mem::StandardAllocator *allocator) : m_allocator(allocator), m_mutex() { + m_current_free_size = m_allocator->GetTotalFreeSize(); + this->ClearPeak(); + } + + void MemoryResourceFromStandardAllocator::ClearPeak() { + std::scoped_lock lk(m_mutex); + m_peak_free_size = m_current_free_size; + m_peak_allocated_size = 0; + } + + void *MemoryResourceFromStandardAllocator::AllocateImpl(size_t size, size_t align) { + std::scoped_lock lk(m_mutex); + + void *p = m_allocator->Allocate(size, align); + + if (p != nullptr) { + m_current_free_size -= m_allocator->GetSizeOf(p); + m_peak_free_size = std::min(m_peak_free_size, m_current_free_size); + } + + m_peak_allocated_size = std::max(m_peak_allocated_size, size); + + return p; + } + + void MemoryResourceFromStandardAllocator::DeallocateImpl(void *p, size_t size, size_t align) { + AMS_UNUSED(size, align); + + std::scoped_lock lk(m_mutex); + + m_current_free_size += m_allocator->GetSizeOf(p); + m_allocator->Free(p); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_nca_crypto_configuration.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_nca_crypto_configuration.cpp new file mode 100644 index 00000000..514b9dd5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_nca_crypto_configuration.cpp @@ -0,0 +1,212 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssrv { + + namespace { + + constexpr inline const u8 HeaderSign1KeyModulusDev[fssystem::NcaCryptoConfiguration::Header1SignatureKeyGenerationMax + 1][fssystem::NcaCryptoConfiguration::Rsa2048KeyModulusSize] = { + { + 0xD8, 0xF1, 0x18, 0xEF, 0x32, 0x72, 0x4C, 0xA7, 0x47, 0x4C, 0xB9, 0xEA, 0xB3, 0x04, 0xA8, 0xA4, + 0xAC, 0x99, 0x08, 0x08, 0x04, 0xBF, 0x68, 0x57, 0xB8, 0x43, 0x94, 0x2B, 0xC7, 0xB9, 0x66, 0x49, + 0x85, 0xE5, 0x8A, 0x9B, 0xC1, 0x00, 0x9A, 0x6A, 0x8D, 0xD0, 0xEF, 0xCE, 0xFF, 0x86, 0xC8, 0x5C, + 0x5D, 0xE9, 0x53, 0x7B, 0x19, 0x2A, 0xA8, 0xC0, 0x22, 0xD1, 0xF3, 0x22, 0x0A, 0x50, 0xF2, 0x2B, + 0x65, 0x05, 0x1B, 0x9E, 0xEC, 0x61, 0xB5, 0x63, 0xA3, 0x6F, 0x3B, 0xBA, 0x63, 0x3A, 0x53, 0xF4, + 0x49, 0x2F, 0xCF, 0x03, 0xCC, 0xD7, 0x50, 0x82, 0x1B, 0x29, 0x4F, 0x08, 0xDE, 0x1B, 0x6D, 0x47, + 0x4F, 0xA8, 0xB6, 0x6A, 0x26, 0xA0, 0x83, 0x3F, 0x1A, 0xAF, 0x83, 0x8F, 0x0E, 0x17, 0x3F, 0xFE, + 0x44, 0x1C, 0x56, 0x94, 0x2E, 0x49, 0x83, 0x83, 0x03, 0xE9, 0xB6, 0xAD, 0xD5, 0xDE, 0xE3, 0x2D, + 0xA1, 0xD9, 0x66, 0x20, 0x5D, 0x1F, 0x5E, 0x96, 0x5D, 0x5B, 0x55, 0x0D, 0xD4, 0xB4, 0x77, 0x6E, + 0xAE, 0x1B, 0x69, 0xF3, 0xA6, 0x61, 0x0E, 0x51, 0x62, 0x39, 0x28, 0x63, 0x75, 0x76, 0xBF, 0xB0, + 0xD2, 0x22, 0xEF, 0x98, 0x25, 0x02, 0x05, 0xC0, 0xD7, 0x6A, 0x06, 0x2C, 0xA5, 0xD8, 0x5A, 0x9D, + 0x7A, 0xA4, 0x21, 0x55, 0x9F, 0xF9, 0x3E, 0xBF, 0x16, 0xF6, 0x07, 0xC2, 0xB9, 0x6E, 0x87, 0x9E, + 0xB5, 0x1C, 0xBE, 0x97, 0xFA, 0x82, 0x7E, 0xED, 0x30, 0xD4, 0x66, 0x3F, 0xDE, 0xD8, 0x1B, 0x4B, + 0x15, 0xD9, 0xFB, 0x2F, 0x50, 0xF0, 0x9D, 0x1D, 0x52, 0x4C, 0x1C, 0x4D, 0x8D, 0xAE, 0x85, 0x1E, + 0xEA, 0x7F, 0x86, 0xF3, 0x0B, 0x7B, 0x87, 0x81, 0x98, 0x23, 0x80, 0x63, 0x4F, 0x2F, 0xB0, 0x62, + 0xCC, 0x6E, 0xD2, 0x46, 0x13, 0x65, 0x2B, 0xD6, 0x44, 0x33, 0x59, 0xB5, 0x8F, 0xB9, 0x4A, 0xA9 + }, + { + 0x9A, 0xBC, 0x88, 0xBD, 0x0A, 0xBE, 0xD7, 0x0C, 0x9B, 0x42, 0x75, 0x65, 0x38, 0x5E, 0xD1, 0x01, + 0xCD, 0x12, 0xAE, 0xEA, 0xE9, 0x4B, 0xDB, 0xB4, 0x5E, 0x36, 0x10, 0x96, 0xDA, 0x3D, 0x2E, 0x66, + 0xD3, 0x99, 0x13, 0x8A, 0xBE, 0x67, 0x41, 0xC8, 0x93, 0xD9, 0x3E, 0x42, 0xCE, 0x34, 0xCE, 0x96, + 0xFA, 0x0B, 0x23, 0xCC, 0x2C, 0xDF, 0x07, 0x3F, 0x3B, 0x24, 0x4B, 0x12, 0x67, 0x3A, 0x29, 0x36, + 0xA3, 0xAA, 0x06, 0xF0, 0x65, 0xA5, 0x85, 0xBA, 0xFD, 0x12, 0xEC, 0xF1, 0x60, 0x67, 0xF0, 0x8F, + 0xD3, 0x5B, 0x01, 0x1B, 0x1E, 0x84, 0xA3, 0x5C, 0x65, 0x36, 0xF9, 0x23, 0x7E, 0xF3, 0x26, 0x38, + 0x64, 0x98, 0xBA, 0xE4, 0x19, 0x91, 0x4C, 0x02, 0xCF, 0xC9, 0x6D, 0x86, 0xEC, 0x1D, 0x41, 0x69, + 0xDD, 0x56, 0xEA, 0x5C, 0xA3, 0x2A, 0x58, 0xB4, 0x39, 0xCC, 0x40, 0x31, 0xFD, 0xFB, 0x42, 0x74, + 0xF8, 0xEC, 0xEA, 0x00, 0xF0, 0xD9, 0x28, 0xEA, 0xFA, 0x2D, 0x00, 0xE1, 0x43, 0x53, 0xC6, 0x32, + 0xF4, 0xA2, 0x07, 0xD4, 0x5F, 0xD4, 0xCB, 0xAC, 0xCA, 0xFF, 0xDF, 0x84, 0xD2, 0x86, 0x14, 0x3C, + 0xDE, 0x22, 0x75, 0xA5, 0x73, 0xFF, 0x68, 0x07, 0x4A, 0xF9, 0x7C, 0x2C, 0xCC, 0xDE, 0x45, 0xB6, + 0x54, 0x82, 0x90, 0x36, 0x1F, 0x2C, 0x51, 0x96, 0xC5, 0x0A, 0x53, 0x5B, 0xF0, 0x8B, 0x4A, 0xAA, + 0x3B, 0x68, 0x97, 0x19, 0x17, 0x1F, 0x01, 0xB8, 0xED, 0xB9, 0x9A, 0x5E, 0x08, 0xC5, 0x20, 0x1E, + 0x6A, 0x09, 0xF0, 0xE9, 0x73, 0xA3, 0xBE, 0x10, 0x06, 0x02, 0xE9, 0xFB, 0x85, 0xFA, 0x5F, 0x01, + 0xAC, 0x60, 0xE0, 0xED, 0x7D, 0xB9, 0x49, 0xA8, 0x9E, 0x98, 0x7D, 0x91, 0x40, 0x05, 0xCF, 0xF9, + 0x1A, 0xFC, 0x40, 0x22, 0xA8, 0x96, 0x5B, 0xB0, 0xDC, 0x7A, 0xF5, 0xB7, 0xE9, 0x91, 0x4C, 0x49 + } + }; + + constexpr inline const u8 HeaderSign1KeyModulusProd[fssystem::NcaCryptoConfiguration::Header1SignatureKeyGenerationMax + 1][fssystem::NcaCryptoConfiguration::Rsa2048KeyModulusSize] = { + { + 0xBF, 0xBE, 0x40, 0x6C, 0xF4, 0xA7, 0x80, 0xE9, 0xF0, 0x7D, 0x0C, 0x99, 0x61, 0x1D, 0x77, 0x2F, + 0x96, 0xBC, 0x4B, 0x9E, 0x58, 0x38, 0x1B, 0x03, 0xAB, 0xB1, 0x75, 0x49, 0x9F, 0x2B, 0x4D, 0x58, + 0x34, 0xB0, 0x05, 0xA3, 0x75, 0x22, 0xBE, 0x1A, 0x3F, 0x03, 0x73, 0xAC, 0x70, 0x68, 0xD1, 0x16, + 0xB9, 0x04, 0x46, 0x5E, 0xB7, 0x07, 0x91, 0x2F, 0x07, 0x8B, 0x26, 0xDE, 0xF6, 0x00, 0x07, 0xB2, + 0xB4, 0x51, 0xF8, 0x0D, 0x0A, 0x5E, 0x58, 0xAD, 0xEB, 0xBC, 0x9A, 0xD6, 0x49, 0xB9, 0x64, 0xEF, + 0xA7, 0x82, 0xB5, 0xCF, 0x6D, 0x70, 0x13, 0xB0, 0x0F, 0x85, 0xF6, 0xA9, 0x08, 0xAA, 0x4D, 0x67, + 0x66, 0x87, 0xFA, 0x89, 0xFF, 0x75, 0x90, 0x18, 0x1E, 0x6B, 0x3D, 0xE9, 0x8A, 0x68, 0xC9, 0x26, + 0x04, 0xD9, 0x80, 0xCE, 0x3F, 0x5E, 0x92, 0xCE, 0x01, 0xFF, 0x06, 0x3B, 0xF2, 0xC1, 0xA9, 0x0C, + 0xCE, 0x02, 0x6F, 0x16, 0xBC, 0x92, 0x42, 0x0A, 0x41, 0x64, 0xCD, 0x52, 0xB6, 0x34, 0x4D, 0xAE, + 0xC0, 0x2E, 0xDE, 0xA4, 0xDF, 0x27, 0x68, 0x3C, 0xC1, 0xA0, 0x60, 0xAD, 0x43, 0xF3, 0xFC, 0x86, + 0xC1, 0x3E, 0x6C, 0x46, 0xF7, 0x7C, 0x29, 0x9F, 0xFA, 0xFD, 0xF0, 0xE3, 0xCE, 0x64, 0xE7, 0x35, + 0xF2, 0xF6, 0x56, 0x56, 0x6F, 0x6D, 0xF1, 0xE2, 0x42, 0xB0, 0x83, 0x40, 0xA5, 0xC3, 0x20, 0x2B, + 0xCC, 0x9A, 0xAE, 0xCA, 0xED, 0x4D, 0x70, 0x30, 0xA8, 0x70, 0x1C, 0x70, 0xFD, 0x13, 0x63, 0x29, + 0x02, 0x79, 0xEA, 0xD2, 0xA7, 0xAF, 0x35, 0x28, 0x32, 0x1C, 0x7B, 0xE6, 0x2F, 0x1A, 0xAA, 0x40, + 0x7E, 0x32, 0x8C, 0x27, 0x42, 0xFE, 0x82, 0x78, 0xEC, 0x0D, 0xEB, 0xE6, 0x83, 0x4B, 0x6D, 0x81, + 0x04, 0x40, 0x1A, 0x9E, 0x9A, 0x67, 0xF6, 0x72, 0x29, 0xFA, 0x04, 0xF0, 0x9D, 0xE4, 0xF4, 0x03 + }, + { + 0xAD, 0xE3, 0xE1, 0xFA, 0x04, 0x35, 0xE5, 0xB6, 0xDD, 0x49, 0xEA, 0x89, 0x29, 0xB1, 0xFF, 0xB6, + 0x43, 0xDF, 0xCA, 0x96, 0xA0, 0x4A, 0x13, 0xDF, 0x43, 0xD9, 0x94, 0x97, 0x96, 0x43, 0x65, 0x48, + 0x70, 0x58, 0x33, 0xA2, 0x7D, 0x35, 0x7B, 0x96, 0x74, 0x5E, 0x0B, 0x5C, 0x32, 0x18, 0x14, 0x24, + 0xC2, 0x58, 0xB3, 0x6C, 0x22, 0x7A, 0xA1, 0xB7, 0xCB, 0x90, 0xA7, 0xA3, 0xF9, 0x7D, 0x45, 0x16, + 0xA5, 0xC8, 0xED, 0x8F, 0xAD, 0x39, 0x5E, 0x9E, 0x4B, 0x51, 0x68, 0x7D, 0xF8, 0x0C, 0x35, 0xC6, + 0x3F, 0x91, 0xAE, 0x44, 0xA5, 0x92, 0x30, 0x0D, 0x46, 0xF8, 0x40, 0xFF, 0xD0, 0xFF, 0x06, 0xD2, + 0x1C, 0x7F, 0x96, 0x18, 0xDC, 0xB7, 0x1D, 0x66, 0x3E, 0xD1, 0x73, 0xBC, 0x15, 0x8A, 0x2F, 0x94, + 0xF3, 0x00, 0xC1, 0x83, 0xF1, 0xCD, 0xD7, 0x81, 0x88, 0xAB, 0xDF, 0x8C, 0xEF, 0x97, 0xDD, 0x1B, + 0x17, 0x5F, 0x58, 0xF6, 0x9A, 0xE9, 0xE8, 0xC2, 0x2F, 0x38, 0x15, 0xF5, 0x21, 0x07, 0xF8, 0x37, + 0x90, 0x5D, 0x2E, 0x02, 0x40, 0x24, 0x15, 0x0D, 0x25, 0xB7, 0x26, 0x5D, 0x09, 0xCC, 0x4C, 0xF4, + 0xF2, 0x1B, 0x94, 0x70, 0x5A, 0x9E, 0xEE, 0xED, 0x77, 0x77, 0xD4, 0x51, 0x99, 0xF5, 0xDC, 0x76, + 0x1E, 0xE3, 0x6C, 0x8C, 0xD1, 0x12, 0xD4, 0x57, 0xD1, 0xB6, 0x83, 0xE4, 0xE4, 0xFE, 0xDA, 0xE9, + 0xB4, 0x3B, 0x33, 0xE5, 0x37, 0x8A, 0xDF, 0xB5, 0x7F, 0x89, 0xF1, 0x9B, 0x9E, 0xB0, 0x15, 0xB2, + 0x3A, 0xFE, 0xEA, 0x61, 0x84, 0x5B, 0x7D, 0x4B, 0x23, 0x12, 0x0B, 0x83, 0x12, 0xF2, 0x22, 0x6B, + 0xB9, 0x22, 0x96, 0x4B, 0x26, 0x0B, 0x63, 0x5E, 0x96, 0x57, 0x52, 0xA3, 0x67, 0x64, 0x22, 0xCA, + 0xD0, 0x56, 0x3E, 0x74, 0xB5, 0x98, 0x1F, 0x0D, 0xF8, 0xB3, 0x34, 0xE6, 0x98, 0x68, 0x5A, 0xAD + } + }; + + constexpr inline const ::ams::fssystem::NcaCryptoConfiguration DefaultNcaCryptoConfigurationDev = { + /* Header1 Signature Key Moduli */ + { HeaderSign1KeyModulusDev[0], HeaderSign1KeyModulusDev[1] }, + + /* Header 1 Signature Key Public Exponent */ + { 0x01, 0x00, 0x01 }, + + /* Key Area Encryption Key Sources */ + { + /* Application */ + { 0x7F, 0x59, 0x97, 0x1E, 0x62, 0x9F, 0x36, 0xA1, 0x30, 0x98, 0x06, 0x6F, 0x21, 0x44, 0xC3, 0x0D }, + + /* Ocean */ + { 0x32, 0x7D, 0x36, 0x08, 0x5A, 0xD1, 0x75, 0x8D, 0xAB, 0x4E, 0x6F, 0xBA, 0xA5, 0x55, 0xD8, 0x82 }, + + /* System */ + { 0x87, 0x45, 0xF1, 0xBB, 0xA6, 0xBE, 0x79, 0x64, 0x7D, 0x04, 0x8B, 0xA6, 0x7B, 0x5F, 0xDA, 0x4A }, + }, + + /* Header Encryption Key Source */ + { 0x1F, 0x12, 0x91, 0x3A, 0x4A, 0xCB, 0xF0, 0x0D, 0x4C, 0xDE, 0x3A, 0xF6, 0xD5, 0x23, 0x88, 0x2A }, + + /* Encrypted Header Encryption Key */ + { + { 0x5A, 0x3E, 0xD8, 0x4F, 0xDE, 0xC0, 0xD8, 0x26, 0x31, 0xF7, 0xE2, 0x5D, 0x19, 0x7B, 0xF5, 0xD0 }, + { 0x1C, 0x9B, 0x7B, 0xFA, 0xF6, 0x28, 0x18, 0x3D, 0x71, 0xF6, 0x4D, 0x73, 0xF1, 0x50, 0xB9, 0xD2 } + }, + + /* Key Generation Function */ + nullptr, + + /* Decrypt Aes Xts Eternal Function */ + nullptr, + + /* Encrypt Aes Xts Eternal Function */ + nullptr, + + /* Decrypt Aes Ctr Function */ + nullptr, + + /* Decrypt Aes Ctr External Function */ + nullptr, + + /* Verify Sign1 Function */ + nullptr, + + /* Plaintext Header Available */ + false, + + /* Software Key Available */ + true, + }; + + constexpr inline const ::ams::fssystem::NcaCryptoConfiguration DefaultNcaCryptoConfigurationProd = { + /* Header1 Signature Key Moduli */ + { HeaderSign1KeyModulusProd[0], HeaderSign1KeyModulusProd[1] }, + + /* Header 1 Signature Key Public Exponent */ + { 0x01, 0x00, 0x01 }, + + /* Key Area Encryption Key Sources */ + { + /* Application */ + { 0x7F, 0x59, 0x97, 0x1E, 0x62, 0x9F, 0x36, 0xA1, 0x30, 0x98, 0x06, 0x6F, 0x21, 0x44, 0xC3, 0x0D }, + + /* Ocean */ + { 0x32, 0x7D, 0x36, 0x08, 0x5A, 0xD1, 0x75, 0x8D, 0xAB, 0x4E, 0x6F, 0xBA, 0xA5, 0x55, 0xD8, 0x82 }, + + /* System */ + { 0x87, 0x45, 0xF1, 0xBB, 0xA6, 0xBE, 0x79, 0x64, 0x7D, 0x04, 0x8B, 0xA6, 0x7B, 0x5F, 0xDA, 0x4A }, + }, + + /* Header Encryption Key Source */ + { 0x1F, 0x12, 0x91, 0x3A, 0x4A, 0xCB, 0xF0, 0x0D, 0x4C, 0xDE, 0x3A, 0xF6, 0xD5, 0x23, 0x88, 0x2A }, + + /* Encrypted Header Encryption Key */ + { + { 0x5A, 0x3E, 0xD8, 0x4F, 0xDE, 0xC0, 0xD8, 0x26, 0x31, 0xF7, 0xE2, 0x5D, 0x19, 0x7B, 0xF5, 0xD0 }, + { 0x1C, 0x9B, 0x7B, 0xFA, 0xF6, 0x28, 0x18, 0x3D, 0x71, 0xF6, 0x4D, 0x73, 0xF1, 0x50, 0xB9, 0xD2 } + }, + + /* Key Generation Function */ + nullptr, + + /* Decrypt Aes Xts Eternal Function */ + nullptr, + + /* Encrypt Aes Xts Eternal Function */ + nullptr, + + /* Decrypt Aes Ctr Function */ + nullptr, + + /* Decrypt Aes Ctr External Function */ + nullptr, + + /* Verify Sign1 Function */ + nullptr, + + /* Plaintext Header Available */ + false, + + /* Software Key Available */ + true, + }; + + } + + const ::ams::fssystem::NcaCryptoConfiguration *GetDefaultNcaCryptoConfiguration(bool prod) { + return prod ? std::addressof(DefaultNcaCryptoConfigurationProd) : std::addressof(DefaultNcaCryptoConfigurationDev); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_program_registry_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_program_registry_impl.cpp new file mode 100644 index 00000000..5de1482f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_program_registry_impl.cpp @@ -0,0 +1,83 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/fssrv_program_info.hpp" + +namespace ams::fssrv { + + namespace { + + constinit ProgramRegistryServiceImpl *g_impl = nullptr; + + } + + ProgramRegistryImpl::ProgramRegistryImpl() : m_process_id(os::InvalidProcessId.value) { + /* ... */ + } + + ProgramRegistryImpl::~ProgramRegistryImpl() { + /* ... */ + } + + void ProgramRegistryImpl::Initialize(ProgramRegistryServiceImpl *service) { + /* Check pre-conditions. */ + AMS_ASSERT(service != nullptr); + AMS_ASSERT(g_impl == nullptr); + + /* Set the global service. */ + g_impl = service; + } + + Result ProgramRegistryImpl::RegisterProgram(u64 process_id, u64 program_id, u8 storage_id, const ams::sf::InBuffer &data, s64 data_size, const ams::sf::InBuffer &desc, s64 desc_size) { + /* Check pre-conditions. */ + AMS_ASSERT(g_impl != nullptr); + + /* Check that we're allowed to register. */ + R_UNLESS(fssrv::impl::IsInitialProgram(m_process_id), fs::ResultPermissionDenied()); + + /* Check buffer sizes. */ + R_UNLESS(data.GetSize() >= static_cast<size_t>(data_size), fs::ResultInvalidSize()); + R_UNLESS(desc.GetSize() >= static_cast<size_t>(desc_size), fs::ResultInvalidSize()); + + /* Register the program. */ + R_RETURN(g_impl->RegisterProgramInfo(process_id, program_id, storage_id, data.GetPointer(), data_size, desc.GetPointer(), desc_size)); + } + + Result ProgramRegistryImpl::UnregisterProgram(u64 process_id) { + /* Check pre-conditions. */ + AMS_ASSERT(g_impl != nullptr); + + /* Check that we're allowed to register. */ + R_UNLESS(fssrv::impl::IsInitialProgram(m_process_id), fs::ResultPermissionDenied()); + + /* Unregister the program. */ + R_RETURN(g_impl->UnregisterProgramInfo(process_id)); + } + + Result ProgramRegistryImpl::SetCurrentProcess(const ams::sf::ClientProcessId &client_pid) { + /* Set our process id. */ + m_process_id = client_pid.GetValue().value; + + R_SUCCEED(); + } + + Result ProgramRegistryImpl::SetEnabledProgramVerification(bool en) { + /* TODO: How to deal with this backwards compat? */ + AMS_ABORT("TODO: SetEnabledProgramVerification"); + AMS_UNUSED(en); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_program_registry_service.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_program_registry_service.cpp new file mode 100644 index 00000000..d721a759 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_program_registry_service.cpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/fssrv_program_info.hpp" +#include "impl/fssrv_program_registry_manager.hpp" + +namespace ams::fssrv { + + Result ProgramRegistryServiceImpl::RegisterProgramInfo(u64 process_id, u64 program_id, u8 storage_id, const void *data, s64 data_size, const void *desc, s64 desc_size) { + R_RETURN(m_registry_manager->RegisterProgram(process_id, program_id, storage_id, data, data_size, desc, desc_size)); + } + + Result ProgramRegistryServiceImpl::UnregisterProgramInfo(u64 process_id) { + R_RETURN(m_registry_manager->UnregisterProgram(process_id)); + } + + Result ProgramRegistryServiceImpl::ResetProgramIndexMapInfo(const fs::ProgramIndexMapInfo *infos, int count) { + R_RETURN(m_index_map_info_manager->Reset(infos, count)); + } + + Result ProgramRegistryServiceImpl::GetProgramInfo(std::shared_ptr<impl::ProgramInfo> *out, u64 process_id) { + R_RETURN(m_registry_manager->GetProgramInfo(out, process_id)); + } + + Result ProgramRegistryServiceImpl::GetProgramInfoByProgramId(std::shared_ptr<impl::ProgramInfo> *out, u64 program_id) { + R_RETURN(m_registry_manager->GetProgramInfoByProgramId(out, program_id)); + } + + size_t ProgramRegistryServiceImpl::GetProgramIndexMapInfoCount() { + return m_index_map_info_manager->GetProgramCount(); + } + + util::optional<fs::ProgramIndexMapInfo> ProgramRegistryServiceImpl::GetProgramIndexMapInfo(const ncm::ProgramId &program_id) { + return m_index_map_info_manager->Get(program_id); + } + + ncm::ProgramId ProgramRegistryServiceImpl::GetProgramIdByIndex(const ncm::ProgramId &program_id, u8 index) { + return m_index_map_info_manager->GetProgramId(program_id, index); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_retry_utility.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_retry_utility.hpp new file mode 100644 index 00000000..a77840d2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_retry_utility.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fssrv::impl { + + template<typename F> + ALWAYS_INLINE Result RetryFinitelyForDataCorrupted(F f) { + /* All official uses of this retry once, for two tries total. */ + constexpr auto MaxTryCount = 2; + + /* Perform the operation, retrying on fs::ResultDataCorrupted. */ + auto tries = 0; + while (true) { + /* Try to perform the operation. */ + const auto rc = f(); + + /* If we should, retry. */ + if (fs::ResultDataCorrupted::Includes(rc)) { + if ((++tries) < MaxTryCount) { + continue; + } + } + + /* Ensure the current attempt succeeded. */ + R_TRY(rc); + + /* Return success. */ + R_SUCCEED(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_storage_interface_adapter.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_storage_interface_adapter.cpp new file mode 100644 index 00000000..b466d2be --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/fssrv_storage_interface_adapter.cpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <stratosphere/fssrv/fssrv_interface_adapters.hpp> +#include "fssrv_retry_utility.hpp" + +namespace ams::fssrv::impl { + + Result StorageInterfaceAdapter::Read(s64 offset, const ams::sf::OutNonSecureBuffer &buffer, s64 size) { + /* Check pre-conditions. */ + R_UNLESS(0 <= offset, fs::ResultInvalidOffset()); + R_UNLESS(0 <= size, fs::ResultInvalidSize()); + R_UNLESS(size <= static_cast<s64>(buffer.GetSize()), fs::ResultInvalidSize()); + + R_RETURN(RetryFinitelyForDataCorrupted([&] () ALWAYS_INLINE_LAMBDA { + R_RETURN(m_base_storage->Read(offset, buffer.GetPointer(), size)); + })); + } + + Result StorageInterfaceAdapter::Write(s64 offset, const ams::sf::InNonSecureBuffer &buffer, s64 size) { + /* Check pre-conditions. */ + R_UNLESS(0 <= offset, fs::ResultInvalidOffset()); + R_UNLESS(0 <= size, fs::ResultInvalidSize()); + R_UNLESS(size <= static_cast<s64>(buffer.GetSize()), fs::ResultInvalidSize()); + + /* Temporarily increase our thread's priority. */ + fssystem::ScopedThreadPriorityChangerByAccessPriority cp(fssystem::ScopedThreadPriorityChangerByAccessPriority::AccessMode::Write); + + R_RETURN(m_base_storage->Write(offset, buffer.GetPointer(), size)); + } + + Result StorageInterfaceAdapter::Flush() { + R_RETURN(m_base_storage->Flush()); + } + + Result StorageInterfaceAdapter::SetSize(s64 size) { + R_UNLESS(size >= 0, fs::ResultInvalidSize()); + R_RETURN(m_base_storage->SetSize(size)); + } + + Result StorageInterfaceAdapter::GetSize(ams::sf::Out<s64> out) { + R_RETURN(m_base_storage->GetSize(out.GetPointer())); + } + + Result StorageInterfaceAdapter::OperateRange(ams::sf::Out<fs::StorageQueryRangeInfo> out, s32 op_id, s64 offset, s64 size) { + /* N includes this redundant check, so we will too. */ + R_UNLESS(out.GetPointer() != nullptr, fs::ResultNullptrArgument()); + + /* Clear the range info. */ + out->Clear(); + + if (op_id == static_cast<s32>(fs::OperationId::QueryRange)) { + fs::FileQueryRangeInfo info; + R_TRY(m_base_storage->OperateRange(std::addressof(info), sizeof(info), fs::OperationId::QueryRange, offset, size, nullptr, 0)); + out->Merge(info); + } else if (op_id == static_cast<s32>(fs::OperationId::Invalidate)) { + R_TRY(m_base_storage->OperateRange(nullptr, 0, fs::OperationId::Invalidate, offset, size, nullptr, 0)); + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_allocator_for_service_framework.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_allocator_for_service_framework.hpp new file mode 100644 index 00000000..68c223c8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_allocator_for_service_framework.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fssrv::impl { + + class AllocatorForServiceFramework { + public: + using Policy = ams::sf::StatelessAllocationPolicy<AllocatorForServiceFramework>; + + void *Allocate(size_t size) { + return fs::impl::Allocate(size); + } + + void Deallocate(void *ptr, size_t size) { + return fs::impl::Deallocate(ptr, size); + } + }; + + using FileSystemObjectFactory = ams::sf::ObjectFactory<AllocatorForServiceFramework::Policy>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_file_system_proxy_service_object.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_file_system_proxy_service_object.cpp new file mode 100644 index 00000000..bab4e341 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_file_system_proxy_service_object.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fssrv_allocator_for_service_framework.hpp" + +namespace ams::fssrv::impl { + + namespace { + + using FileSystemProxyServiceFactory = ams::sf::ObjectFactory<AllocatorForServiceFramework::Policy>; + using ProgramRegistryServiceFactory = ams::sf::ObjectFactory<AllocatorForServiceFramework::Policy>; + using FileSystemProxyForLoaderServiceFactory = ams::sf::ObjectFactory<AllocatorForServiceFramework::Policy>; + + } + + ams::sf::EmplacedRef<fssrv::sf::IFileSystemProxy, fssrv::FileSystemProxyImpl> GetFileSystemProxyServiceObject() { + return FileSystemProxyServiceFactory::CreateSharedEmplaced<fssrv::sf::IFileSystemProxy, fssrv::FileSystemProxyImpl>(); + } + + ams::sf::SharedPointer<fssrv::sf::IProgramRegistry> GetProgramRegistryServiceObject() { + return ProgramRegistryServiceFactory::CreateSharedEmplaced<fssrv::sf::IProgramRegistry, fssrv::ProgramRegistryImpl>(); + } + + ams::sf::SharedPointer<fssrv::sf::IProgramRegistry> GetInvalidProgramRegistryServiceObject() { + return ProgramRegistryServiceFactory::CreateSharedEmplaced<fssrv::sf::IProgramRegistry, fssrv::InvalidProgramRegistryImpl>(); + } + + ams::sf::SharedPointer<fssrv::sf::IFileSystemProxyForLoader> GetFileSystemProxyForLoaderServiceObject() { + return FileSystemProxyForLoaderServiceFactory ::CreateSharedEmplaced<fssrv::sf::IFileSystemProxyForLoader, fssrv::FileSystemProxyImpl>(); + } + + ams::sf::SharedPointer<fssrv::sf::IFileSystemProxyForLoader> GetInvalidFileSystemProxyForLoaderServiceObject() { + return FileSystemProxyForLoaderServiceFactory ::CreateSharedEmplaced<fssrv::sf::IFileSystemProxyForLoader, fssrv::InvalidFileSystemProxyImplForLoader>(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_program_info.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_program_info.cpp new file mode 100644 index 00000000..18b157a4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_program_info.cpp @@ -0,0 +1,125 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fssrv_program_info.hpp" + +namespace ams::fssrv::impl { + + namespace { + + alignas(0x10) constinit std::byte g_static_buffer_for_program_info_for_initial_process[0x80] = {}; + + template<typename T> + class StaticAllocatorForProgramInfoForInitialProcess : public std::allocator<T> { + public: + StaticAllocatorForProgramInfoForInitialProcess() { /* ... */ } + + template<typename U> + StaticAllocatorForProgramInfoForInitialProcess(const StaticAllocatorForProgramInfoForInitialProcess<U> &) { /* ... */ } + + template<typename U> + struct rebind { + using other = StaticAllocatorForProgramInfoForInitialProcess<U>; + }; + + [[nodiscard]] T *allocate(::std::size_t n) { + AMS_ABORT_UNLESS(sizeof(T) * n <= sizeof(g_static_buffer_for_program_info_for_initial_process)); + return reinterpret_cast<T *>(std::addressof(g_static_buffer_for_program_info_for_initial_process)); + } + + void deallocate(T *p, ::std::size_t n) { + AMS_UNUSED(p, n); + } + }; + + constexpr const u32 FileAccessControlForInitialProgram[0x1C / sizeof(u32)] = {0x00000001, 0x00000000, 0x80000000, 0x0000001C, 0x00000000, 0x0000001C, 0x00000000}; + constexpr const u32 FileAccessControlDescForInitialProgram[0x2C / sizeof(u32)] = {0x00000001, 0x00000000, 0x80000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF}; + + #if defined(ATMOSPHERE_OS_HORIZON) + + constinit os::SdkMutex g_mutex; + constinit bool g_initialized = false; + + constinit u64 g_initial_process_id_min = 0; + constinit u64 g_initial_process_id_max = 0; + + constinit u64 g_current_process_id = 0; + + ALWAYS_INLINE void InitializeInitialAndCurrentProcessId() { + if (AMS_UNLIKELY(!g_initialized)) { + std::scoped_lock lk(g_mutex); + if (AMS_LIKELY(!g_initialized)) { + /* Get initial process id range. */ + R_ABORT_UNLESS(svc::GetSystemInfo(std::addressof(g_initial_process_id_min), svc::SystemInfoType_InitialProcessIdRange, svc::InvalidHandle, svc::InitialProcessIdRangeInfo_Minimum)); + R_ABORT_UNLESS(svc::GetSystemInfo(std::addressof(g_initial_process_id_max), svc::SystemInfoType_InitialProcessIdRange, svc::InvalidHandle, svc::InitialProcessIdRangeInfo_Maximum)); + + AMS_ABORT_UNLESS(0 < g_initial_process_id_min); + AMS_ABORT_UNLESS(g_initial_process_id_min <= g_initial_process_id_max); + + /* Get current procss id. */ + R_ABORT_UNLESS(svc::GetProcessId(std::addressof(g_current_process_id), svc::PseudoHandle::CurrentProcess)); + + /* Set initialized. */ + g_initialized = true; + } + } + } + #endif + + } + + std::shared_ptr<ProgramInfo> ProgramInfo::GetProgramInfoForInitialProcess() { + class ProgramInfoHelper : public ProgramInfo { + public: + ProgramInfoHelper(const void *data, s64 data_size, const void *desc, s64 desc_size) : ProgramInfo(data, data_size, desc, desc_size) { /* ... */ } + }; + + AMS_FUNCTION_LOCAL_STATIC(std::shared_ptr<ProgramInfo>, s_initial_program_info, std::allocate_shared<ProgramInfoHelper>(StaticAllocatorForProgramInfoForInitialProcess<char>{}, FileAccessControlForInitialProgram, sizeof(FileAccessControlForInitialProgram), FileAccessControlDescForInitialProgram, sizeof(FileAccessControlDescForInitialProgram))); + + return s_initial_program_info; + } + + bool IsInitialProgram(u64 process_id) { + #if defined(ATMOSPHERE_OS_HORIZON) + /* Initialize/sanity check. */ + InitializeInitialAndCurrentProcessId(); + AMS_ABORT_UNLESS(g_initial_process_id_min > 0); + + /* Check process id in range. */ + return g_initial_process_id_min <= process_id && process_id <= g_initial_process_id_max; + #elif defined(ATMOSPHERE_OS_WINDOWS) || defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS) + AMS_UNUSED(process_id); + return true; + #else + #error "Unknown os for fssrv::impl::IsInitialProgram" + #endif + } + + bool IsCurrentProcess(u64 process_id) { + #if defined(ATMOSPHERE_OS_HORIZON) + /* Initialize. */ + InitializeInitialAndCurrentProcessId(); + + return process_id == g_current_process_id; + #elif defined(ATMOSPHERE_OS_WINDOWS) || defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS) + AMS_UNUSED(process_id); + return true; + #else + #error "Unknown os for fssrv::impl::IsCurrentProcess" + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_program_info.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_program_info.hpp new file mode 100644 index 00000000..6b54ed69 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_program_info.hpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fssrv::impl { + + class ProgramInfo : public ::ams::fs::impl::Newable { + private: + u64 m_process_id; + ncm::ProgramId m_program_id; + ncm::StorageId m_storage_id; + /* TODO: AccessControl m_access_control; */ + public: + ProgramInfo(u64 process_id, u64 program_id, u8 storage_id, const void *data, s64 data_size, const void *desc, s64 desc_size) : m_process_id(process_id) /* TODO: m_access_control */ { + m_program_id.value = program_id; + m_storage_id = static_cast<ncm::StorageId>(storage_id); + + /* TODO */ + AMS_UNUSED(data, data_size, desc, desc_size); + } + + bool Contains(u64 process_id) const { return m_process_id == process_id; } + u64 GetProcessId() const { return m_process_id; } + ncm::ProgramId GetProgramId() const { return m_program_id; } + u64 GetProgramIdValue() const { return m_program_id.value; } + ncm::StorageId GetStorageId() const { return m_storage_id; } + public: + static std::shared_ptr<ProgramInfo> GetProgramInfoForInitialProcess(); + private: + ProgramInfo(const void *data, s64 data_size, const void *desc, s64 desc_size) : m_process_id(os::InvalidProcessId), m_program_id(ncm::InvalidProgramId), m_storage_id(static_cast<ncm::StorageId>(0)) /* TODO: m_access_control */ { + /* TODO */ + AMS_UNUSED(data, data_size, desc, desc_size); + } + }; + + struct ProgramInfoNode : public util::IntrusiveListBaseNode<ProgramInfoNode>, public ::ams::fs::impl::Newable { + std::shared_ptr<ProgramInfo> program_info; + }; + + bool IsInitialProgram(u64 process_id); + bool IsCurrentProcess(u64 process_id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_program_registry_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_program_registry_manager.cpp new file mode 100644 index 00000000..6d268d2a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_program_registry_manager.cpp @@ -0,0 +1,105 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fssrv_program_registry_manager.hpp" + +namespace ams::fssrv::impl { + + Result ProgramRegistryManager::RegisterProgram(u64 process_id, u64 program_id, u8 storage_id, const void *data, s64 data_size, const void *desc, s64 desc_size) { + /* Allocate a new node. */ + std::unique_ptr<ProgramInfoNode> new_node(new ProgramInfoNode()); + R_UNLESS(new_node != nullptr, fs::ResultAllocationMemoryFailedInProgramRegistryManagerA()); + + /* Create a new program info. */ + { + /* Allocate the new info. */ + auto new_info = fssystem::AllocateShared<ProgramInfo>(process_id, program_id, storage_id, data, data_size, desc, desc_size); + R_UNLESS(new_info != nullptr, fs::ResultAllocationMemoryFailedInProgramRegistryManagerA()); + + /* Set the info in the node. */ + new_node->program_info = std::move(new_info); + } + + /* Acquire exclusive access to the registry. */ + std::scoped_lock lk(m_mutex); + + /* Check that the process isn't already in the registry. */ + for (const auto &node : m_program_info_list) { + R_UNLESS(!node.program_info->Contains(process_id), fs::ResultInvalidArgument()); + } + + /* Add the node to the registry. */ + m_program_info_list.push_back(*new_node.release()); + + R_SUCCEED(); + } + + Result ProgramRegistryManager::UnregisterProgram(u64 process_id) { + /* Acquire exclusive access to the registry. */ + std::scoped_lock lk(m_mutex); + + /* Try to find and remove the process's node. */ + for (auto &node : m_program_info_list) { + if (node.program_info->Contains(process_id)) { + m_program_info_list.erase(m_program_info_list.iterator_to(node)); + delete std::addressof(node); + R_SUCCEED(); + } + } + + /* We couldn't find/unregister the process's node. */ + R_THROW(fs::ResultInvalidArgument()); + } + + Result ProgramRegistryManager::GetProgramInfo(std::shared_ptr<ProgramInfo> *out, u64 process_id) { + /* Acquire exclusive access to the registry. */ + std::scoped_lock lk(m_mutex); + + /* Check if we're getting permissions for an initial program. */ + if (IsInitialProgram(process_id)) { + *out = ProgramInfo::GetProgramInfoForInitialProcess(); + R_SUCCEED(); + } + + /* Find a matching node. */ + for (const auto &node : m_program_info_list) { + if (node.program_info->Contains(process_id)) { + *out = node.program_info; + R_SUCCEED(); + } + } + + /* We didn't find the program info. */ + R_THROW(fs::ResultProgramInfoNotFound()); + } + + Result ProgramRegistryManager::GetProgramInfoByProgramId(std::shared_ptr<ProgramInfo> *out, u64 program_id) { + /* Acquire exclusive access to the registry. */ + std::scoped_lock lk(m_mutex); + + /* Find a matching node. */ + for (const auto &node : m_program_info_list) { + if (node.program_info->GetProgramIdValue() == program_id) { + *out = node.program_info; + R_SUCCEED(); + } + } + + /* We didn't find the program info. */ + R_THROW(fs::ResultProgramInfoNotFound()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_program_registry_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_program_registry_manager.hpp new file mode 100644 index 00000000..0f2f7f3d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_program_registry_manager.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "fssrv_program_info.hpp" + +namespace ams::fssrv::impl { + + class ProgramRegistryManager { + NON_COPYABLE(ProgramRegistryManager); + NON_MOVEABLE(ProgramRegistryManager); + private: + using ProgramInfoList = util::IntrusiveListBaseTraits<ProgramInfoNode>::ListType; + private: + ProgramInfoList m_program_info_list{}; + os::SdkMutex m_mutex{}; + public: + constexpr ProgramRegistryManager() = default; + + Result RegisterProgram(u64 process_id, u64 program_id, u8 storage_id, const void *data, s64 data_size, const void *desc, s64 desc_size); + Result UnregisterProgram(u64 process_id); + + Result GetProgramInfo(std::shared_ptr<ProgramInfo> *out, u64 process_id); + Result GetProgramInfoByProgramId(std::shared_ptr<ProgramInfo> *out, u64 program_id); + }; + +} + +AMS_FSSYSTEM_ENABLE_PIMPL(::ams::fssrv::impl::ProgramRegistryManager); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_utility.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_utility.cpp new file mode 100644 index 00000000..d5489b4e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_utility.cpp @@ -0,0 +1,202 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fssrv_utility.hpp" + + +#if defined(ATMOSPHERE_OS_WINDOWS) +#include <stratosphere/windows.hpp> +#elif defined(ATMOSPHERE_OS_LINUX) +#include <unistd.h> +#elif defined(ATMOSPHERE_OS_MACOS) +#include <unistd.h> +#include <mach-o/dyld.h> +#include <sys/param.h> +#endif + +namespace ams::fssystem { + + class PathOnExecutionDirectory { + private: + char m_path[fs::EntryNameLengthMax + 1]; + public: + PathOnExecutionDirectory() { + #if defined(ATMOSPHERE_OS_WINDOWS) + { + /* Get the module file name. */ + wchar_t module_file_name[fs::EntryNameLengthMax + 1]; + if (::GetModuleFileNameW(0, module_file_name, util::size(module_file_name)) == 0) { + AMS_FS_R_ABORT_UNLESS(fs::ResultUnexpectedInPathOnExecutionDirectoryA()); + } + + /* Split the path. */ + wchar_t drive_name[3]; + wchar_t dir_name[fs::EntryNameLengthMax + 1]; + ::_wsplitpath_s(module_file_name, drive_name, util::size(drive_name), dir_name, util::size(dir_name), nullptr, 0, nullptr, 0); + + /* Print the drive and directory. */ + wchar_t path[fs::EntryNameLengthMax + 1]; + ::swprintf_s(path, util::size(path), L"%s%s", drive_name, dir_name); + + /* Convert to utf-8. */ + const auto res = ::WideCharToMultiByte(CP_UTF8, 0, path, -1, m_path, util::size(m_path), nullptr, nullptr); + if (res == 0) { + AMS_FS_R_ABORT_UNLESS(fs::ResultUnexpectedInPathOnExecutionDirectoryB()); + } + } + #elif defined(ATMOSPHERE_OS_LINUX) + { + char full_path[PATH_MAX] = {}; + if (::readlink("/proc/self/exe", full_path, sizeof(full_path)) == -1) { + AMS_FS_R_ABORT_UNLESS(fs::ResultUnexpectedInPathOnExecutionDirectoryA()); + } + + const int len = std::strlen(full_path); + if (len >= static_cast<int>(sizeof(m_path))) { + AMS_FS_R_ABORT_UNLESS(fs::ResultUnexpectedInPathOnExecutionDirectoryB()); + } + + std::memcpy(m_path, full_path, len + 1); + + for (int i = len - 1; i >= 0; --i) { + if (m_path[i] == '/') { + m_path[i + 1] = 0; + break; + } + } + } + #elif defined(ATMOSPHERE_OS_MACOS) + { + char full_path[MAXPATHLEN] = {}; + uint32_t size = sizeof(full_path); + if (_NSGetExecutablePath(full_path, std::addressof(size)) != 0) { + AMS_FS_R_ABORT_UNLESS(fs::ResultUnexpectedInPathOnExecutionDirectoryA()); + } + + const int len = std::strlen(full_path); + if (len >= static_cast<int>(sizeof(m_path))) { + AMS_FS_R_ABORT_UNLESS(fs::ResultUnexpectedInPathOnExecutionDirectoryB()); + } + + std::memcpy(m_path, full_path, len + 1); + + for (int i = len - 1; i >= 0; --i) { + if (m_path[i] == '/') { + m_path[i + 1] = 0; + break; + } + } + } + #else + AMS_ABORT("TODO: Unknown OS for PathOnExecutionDirectory"); + #endif + + const auto len = std::strlen(m_path); + if (m_path[len - 1] != '/' && m_path[len - 1] != '\\') { + if (len + 1 >= sizeof(m_path)) { + AMS_FS_R_ABORT_UNLESS(fs::ResultUnexpectedInPathOnExecutionDirectoryB()); + } + m_path[len] = '/'; + m_path[len + 1] = 0; + } + } + + const char *Get() const { + return m_path; + } + }; + + class PathOnWorkingDirectory { + private: + char m_path[fs::EntryNameLengthMax + 1]; + public: + PathOnWorkingDirectory() { + #if defined(ATMOSPHERE_OS_WINDOWS) + { + /* Get the current directory. */ + wchar_t current_directory[fs::EntryNameLengthMax + 1]; + if (::GetCurrentDirectoryW(util::size(current_directory), current_directory) == 0) { + AMS_FS_R_ABORT_UNLESS(fs::ResultUnexpectedInPathOnExecutionDirectoryB()); + } + + /* Convert to utf-8. */ + const auto res = ::WideCharToMultiByte(CP_UTF8, 0, current_directory, -1, m_path, util::size(m_path), nullptr, nullptr); + if (res == 0) { + AMS_FS_R_ABORT_UNLESS(fs::ResultUnexpectedInPathOnExecutionDirectoryB()); + } + } + #elif defined(ATMOSPHERE_OS_LINUX) + { + char full_path[PATH_MAX] = {}; + if (::getcwd(full_path, sizeof(full_path)) == nullptr) { + AMS_FS_R_ABORT_UNLESS(fs::ResultUnexpectedInPathOnExecutionDirectoryB()); + } + + const int len = std::strlen(full_path); + if (len >= static_cast<int>(sizeof(m_path))) { + AMS_FS_R_ABORT_UNLESS(fs::ResultUnexpectedInPathOnExecutionDirectoryB()); + } + + std::memcpy(m_path, full_path, len + 1); + } + #elif defined(ATMOSPHERE_OS_MACOS) + { + char full_path[MAXPATHLEN] = {}; + if (::getcwd(full_path, sizeof(full_path)) == nullptr) { + AMS_FS_R_ABORT_UNLESS(fs::ResultUnexpectedInPathOnExecutionDirectoryB()); + } + + const int len = std::strlen(full_path); + if (len >= static_cast<int>(sizeof(m_path))) { + AMS_FS_R_ABORT_UNLESS(fs::ResultUnexpectedInPathOnExecutionDirectoryB()); + } + + std::memcpy(m_path, full_path, len + 1); + } + #else + AMS_ABORT("TODO: Unknown OS for PathOnWorkingDirectory"); + #endif + + const auto len = std::strlen(m_path); + if (m_path[len - 1] != '/' && m_path[len - 1] != '\\') { + if (len + 1 >= sizeof(m_path)) { + AMS_FS_R_ABORT_UNLESS(fs::ResultUnexpectedInPathOnExecutionDirectoryB()); + } + m_path[len] = '/'; + m_path[len + 1] = 0; + } + } + + const char *Get() const { + return m_path; + } + }; + +} + +namespace ams::fssrv::impl { + + const char *GetExecutionDirectoryPath() { + AMS_FUNCTION_LOCAL_STATIC(fssystem::PathOnExecutionDirectory, s_path_on_execution_directory); + return s_path_on_execution_directory.Get(); + } + + const char *GetWorkingDirectoryPath() { + AMS_FUNCTION_LOCAL_STATIC(fssystem::PathOnWorkingDirectory, s_path_on_working_directory); + return s_path_on_working_directory.Get(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_utility.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_utility.hpp new file mode 100644 index 00000000..36fe2c33 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssrv/impl/fssrv_utility.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fssrv::impl { + + const char *GetExecutionDirectoryPath(); + const char *GetWorkingDirectoryPath(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/buffers/fssystem_buffer_manager_utils.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/buffers/fssystem_buffer_manager_utils.cpp new file mode 100644 index 00000000..37f1f13b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/buffers/fssystem_buffer_manager_utils.cpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem::buffers { + + namespace { + + /* TODO: os::SdkThreadLocalStorage g_buffer_manager_context_tls_slot; */ + + class ThreadLocalStorageWrapper { + private: + os::TlsSlot m_tls_slot; + public: + ThreadLocalStorageWrapper() { R_ABORT_UNLESS(os::AllocateTlsSlot(std::addressof(m_tls_slot), nullptr)); } + ~ThreadLocalStorageWrapper() { os::FreeTlsSlot(m_tls_slot); } + + void SetValue(uintptr_t value) { os::SetTlsValue(m_tls_slot, value); } + uintptr_t GetValue() const { return os::GetTlsValue(m_tls_slot); } + os::TlsSlot GetTlsSlot() const { return m_tls_slot; } + } g_buffer_manager_context_tls_slot; + + } + + void RegisterBufferManagerContext(const BufferManagerContext *context) { + g_buffer_manager_context_tls_slot.SetValue(reinterpret_cast<uintptr_t>(context)); + } + + BufferManagerContext *GetBufferManagerContext() { + return reinterpret_cast<BufferManagerContext *>(g_buffer_manager_context_tls_slot.GetValue()); + } + + void EnableBlockingBufferManagerAllocation() { + if (auto context = GetBufferManagerContext(); context != nullptr) { + context->SetNeedBlocking(true); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/buffers/fssystem_file_system_buddy_heap.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/buffers/fssystem_file_system_buddy_heap.cpp new file mode 100644 index 00000000..047baba1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/buffers/fssystem_file_system_buddy_heap.cpp @@ -0,0 +1,360 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + FileSystemBuddyHeap::PageEntry *FileSystemBuddyHeap::PageList::PopFront() { + AMS_ASSERT(m_entry_count > 0); + + /* Get the first entry. */ + auto page_entry = m_first_page_entry; + + /* Advance our list. */ + m_first_page_entry = page_entry->next; + page_entry->next = nullptr; + + /* Decrement our count. */ + --m_entry_count; + AMS_ASSERT(m_entry_count >= 0); + + /* If this was our last page, clear our last entry. */ + if (m_entry_count == 0) { + m_last_page_entry = nullptr; + } + + return page_entry; + } + + void FileSystemBuddyHeap::PageList::PushBack(PageEntry *page_entry) { + AMS_ASSERT(page_entry != nullptr); + + /* If we're empty, we want to set the first page entry. */ + if (this->IsEmpty()) { + m_first_page_entry = page_entry; + } else { + /* We're not empty, so push the page to the back. */ + AMS_ASSERT(m_last_page_entry != page_entry); + m_last_page_entry->next = page_entry; + } + + /* Set our last page entry to be this one, and link it to the list. */ + m_last_page_entry = page_entry; + m_last_page_entry->next = nullptr; + + /* Increment our entry count. */ + ++m_entry_count; + AMS_ASSERT(m_entry_count > 0); + } + + bool FileSystemBuddyHeap::PageList::Remove(PageEntry *page_entry) { + AMS_ASSERT(page_entry != nullptr); + + /* If we're empty, we can't remove the page list. */ + if (this->IsEmpty()) { + return false; + } + + /* We're going to loop over all pages to find this one, then unlink it. */ + PageEntry *prev_entry = nullptr; + PageEntry *cur_entry = m_first_page_entry; + + while (true) { + /* Check if we found the page. */ + if (cur_entry == page_entry) { + if (cur_entry == m_first_page_entry) { + /* If it's the first page, we just set our first. */ + m_first_page_entry = cur_entry->next; + } else if (cur_entry == m_last_page_entry) { + /* If it's the last page, we set our last. */ + m_last_page_entry = prev_entry; + m_last_page_entry->next = nullptr; + } else { + /* If it's in the middle, we just unlink. */ + prev_entry->next = cur_entry->next; + } + + /* Unlink this entry's next. */ + cur_entry->next = nullptr; + + /* Update our entry count. */ + --m_entry_count; + AMS_ASSERT(m_entry_count >= 0); + + return true; + } + + /* If we have no next page, we can't remove. */ + if (cur_entry->next == nullptr) { + return false; + } + + /* Advance to the next item in the list. */ + prev_entry = cur_entry; + cur_entry = cur_entry->next; + } + } + + Result FileSystemBuddyHeap::Initialize(uintptr_t address, size_t size, size_t block_size, s32 order_max) { + /* Ensure our preconditions. */ + AMS_ASSERT(m_free_lists == nullptr); + AMS_ASSERT(address != 0); + AMS_ASSERT(util::IsAligned(address, BufferAlignment)); + AMS_ASSERT(block_size >= BlockSizeMin); + AMS_ASSERT(util::IsPowerOfTwo(block_size)); + AMS_ASSERT(size >= block_size); + AMS_ASSERT(order_max > 0); + AMS_ASSERT(order_max < OrderUpperLimit); + + /* Set up our basic member variables */ + m_block_size = block_size; + m_order_max = order_max; + m_heap_start = address; + m_heap_size = (size / m_block_size) * m_block_size; + + m_total_free_size = 0; + + /* Determine page sizes. */ + const auto max_page_size = m_block_size << m_order_max; + const auto max_page_count = util::AlignUp(m_heap_size, max_page_size) / max_page_size; + AMS_ASSERT(max_page_count > 0); + + /* Setup the free lists. */ + if (m_external_free_lists != nullptr) { + AMS_ASSERT(m_internal_free_lists == nullptr); + m_free_lists = m_external_free_lists; + } else { + m_internal_free_lists.reset(new PageList[m_order_max + 1]); + m_free_lists = m_internal_free_lists.get(); + R_UNLESS(m_free_lists != nullptr, fs::ResultAllocationMemoryFailedInFileSystemBuddyHeapA()); + } + + /* All but the last page region should go to the max order. */ + for (size_t i = 0; i < max_page_count - 1; i++) { + auto page_entry = this->GetPageEntryFromAddress(m_heap_start + i * max_page_size); + m_free_lists[m_order_max].PushBack(page_entry); + } + m_total_free_size += m_free_lists[m_order_max].GetSize() * this->GetBytesFromOrder(m_order_max); + + /* Allocate remaining space to smaller orders as possible. */ + { + auto remaining = m_heap_size - (max_page_count - 1) * max_page_size; + auto cur_address = m_heap_start + (max_page_count - 1) * max_page_size; + AMS_ASSERT(util::IsAligned(remaining, m_block_size)); + + do { + /* Determine what order we can use. */ + auto order = GetOrderFromBytes(remaining + 1); + if (order < 0) { + AMS_ASSERT(GetOrderFromBytes(remaining) == m_order_max); + order = m_order_max + 1; + } + AMS_ASSERT(0 < order); + AMS_ASSERT(order <= m_order_max + 1); + + /* Add to the correct free list. */ + m_free_lists[order - 1].PushBack(GetPageEntryFromAddress(cur_address)); + m_total_free_size += GetBytesFromOrder(order - 1); + + /* Move on to the next order. */ + const auto page_size = GetBytesFromOrder(order - 1); + cur_address += page_size; + remaining -= page_size; + } while (m_block_size <= remaining); + } + + R_SUCCEED(); + } + + void FileSystemBuddyHeap::Finalize() { + AMS_ASSERT(m_free_lists != nullptr); + m_free_lists = nullptr; + m_external_free_lists = nullptr; + m_internal_free_lists.reset(); + } + + void *FileSystemBuddyHeap::AllocateByOrder(s32 order) { + AMS_ASSERT(m_free_lists != nullptr); + AMS_ASSERT(order >= 0); + AMS_ASSERT(order <= this->GetOrderMax()); + + /* Get the page entry. */ + if (const auto page_entry = this->GetFreePageEntry(order); page_entry != nullptr) { + /* Ensure we're allocating an unlinked page. */ + AMS_ASSERT(page_entry->next == nullptr); + + /* Return the address for this entry. */ + return reinterpret_cast<void *>(this->GetAddressFromPageEntry(*page_entry)); + } else { + return nullptr; + } + } + + void FileSystemBuddyHeap::Free(void *ptr, s32 order) { + AMS_ASSERT(m_free_lists != nullptr); + AMS_ASSERT(order >= 0); + AMS_ASSERT(order <= this->GetOrderMax()); + + /* Allow free(nullptr) */ + if (ptr == nullptr) { + return; + } + + /* Ensure the pointer is block aligned. */ + AMS_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(ptr) - m_heap_start, this->GetBlockSize())); + + /* Get the page entry. */ + auto page_entry = this->GetPageEntryFromAddress(reinterpret_cast<uintptr_t>(ptr)); + AMS_ASSERT(this->IsAlignedToOrder(page_entry, order)); + + /* Reinsert into the free lists. */ + this->JoinBuddies(page_entry, order); + } + + size_t FileSystemBuddyHeap::GetTotalFreeSize() const { + AMS_ASSERT(m_free_lists != nullptr); + return m_total_free_size; + } + + size_t FileSystemBuddyHeap::GetAllocatableSizeMax() const { + AMS_ASSERT(m_free_lists != nullptr); + + /* The maximum allocatable size is a chunk from the biggest non-empty order. */ + for (s32 order = this->GetOrderMax(); order >= 0; --order) { + if (!m_free_lists[order].IsEmpty()) { + return this->GetBytesFromOrder(order); + } + } + + /* If all orders are empty, then we can't allocate anything. */ + return 0; + } + + void FileSystemBuddyHeap::Dump() const { + AMS_ASSERT(m_free_lists != nullptr); + /* TODO: Support logging metrics. */ + } + + void FileSystemBuddyHeap::DivideBuddies(PageEntry *page_entry, s32 required_order, s32 chosen_order) { + AMS_ASSERT(page_entry != nullptr); + AMS_ASSERT(required_order >= 0); + AMS_ASSERT(chosen_order >= required_order); + AMS_ASSERT(chosen_order <= this->GetOrderMax()); + + /* Start at the end of the entry. */ + auto address = this->GetAddressFromPageEntry(*page_entry) + this->GetBytesFromOrder(chosen_order); + + for (auto order = chosen_order; order > required_order; --order) { + /* For each order, subtract that order's size from the address to get the start of a new block. */ + address -= this->GetBytesFromOrder(order - 1); + auto divided_entry = this->GetPageEntryFromAddress(address); + + /* Push back to the list. */ + m_free_lists[order - 1].PushBack(divided_entry); + m_total_free_size += this->GetBytesFromOrder(order - 1); + } + } + + void FileSystemBuddyHeap::JoinBuddies(PageEntry *page_entry, s32 order) { + AMS_ASSERT(page_entry != nullptr); + AMS_ASSERT(order >= 0); + AMS_ASSERT(order <= this->GetOrderMax()); + + auto cur_entry = page_entry; + auto cur_order = order; + + while (cur_order < this->GetOrderMax()) { + /* Get the buddy page. */ + const auto buddy_entry = this->GetBuddy(cur_entry, cur_order); + + /* Check whether the buddy is in the relevant free list. */ + if (buddy_entry != nullptr && m_free_lists[cur_order].Remove(buddy_entry)) { + m_total_free_size -= GetBytesFromOrder(cur_order); + + /* Ensure we coalesce with the correct buddy when page is aligned */ + if (!this->IsAlignedToOrder(cur_entry, cur_order + 1)) { + cur_entry = buddy_entry; + } + + ++cur_order; + } else { + /* Buddy isn't in the free list, so we can't coalesce. */ + break; + } + } + + /* Insert the coalesced entry into the free list. */ + m_free_lists[cur_order].PushBack(cur_entry); + m_total_free_size += this->GetBytesFromOrder(cur_order); + } + + FileSystemBuddyHeap::PageEntry *FileSystemBuddyHeap::GetBuddy(PageEntry *page_entry, s32 order) { + AMS_ASSERT(page_entry != nullptr); + AMS_ASSERT(order >= 0); + AMS_ASSERT(order <= this->GetOrderMax()); + + const auto address = this->GetAddressFromPageEntry(*page_entry); + const auto offset = this->GetBlockCountFromOrder(order) * this->GetBlockSize(); + + if (this->IsAlignedToOrder(page_entry, order + 1)) { + /* If the page entry is aligned to the next order, return the buddy block to the right of the current entry. */ + return (address + offset < m_heap_start + m_heap_size) ? GetPageEntryFromAddress(address + offset) : nullptr; + } else { + /* If the page entry isn't aligned, return the buddy block to the left of the current entry. */ + return (m_heap_start <= address - offset) ? GetPageEntryFromAddress(address - offset) : nullptr; + } + } + + FileSystemBuddyHeap::PageEntry *FileSystemBuddyHeap::GetFreePageEntry(s32 order) { + AMS_ASSERT(order >= 0); + AMS_ASSERT(order <= this->GetOrderMax()); + + /* Try orders from low to high until we find a free page entry. */ + for (auto cur_order = order; cur_order <= this->GetOrderMax(); cur_order++) { + if (auto &free_list = m_free_lists[cur_order]; !free_list.IsEmpty()) { + /* The current list isn't empty, so grab an entry from it. */ + PageEntry *page_entry = free_list.PopFront(); + AMS_ASSERT(page_entry != nullptr); + + /* Update size bookkeeping. */ + m_total_free_size -= GetBytesFromOrder(cur_order); + + /* If we allocated more memory than needed, free the unneeded portion. */ + this->DivideBuddies(page_entry, order, cur_order); + AMS_ASSERT(page_entry->next == nullptr); + + /* Return the newly-divided entry. */ + return page_entry; + } + } + + /* We failed to find a free page. */ + return nullptr; + } + + s32 FileSystemBuddyHeap::GetOrderFromBlockCount(s32 block_count) const { + AMS_ASSERT(block_count >= 0); + + /* Return the first order with a big enough block count. */ + for (s32 order = 0; order <= this->GetOrderMax(); ++order) { + if (block_count <= this->GetBlockCountFromOrder(order)) { + return order; + } + } + return -1; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/buffers/fssystem_file_system_buffer_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/buffers/fssystem_file_system_buffer_manager.cpp new file mode 100644 index 00000000..f4d22213 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/buffers/fssystem_file_system_buffer_manager.cpp @@ -0,0 +1,419 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + Result FileSystemBufferManager::CacheHandleTable::Initialize(s32 max_cache_count) { + /* Validate pre-conditions. */ + AMS_ASSERT(m_entries == nullptr); + AMS_ASSERT(m_internal_entry_buffer == nullptr); + + /* If we don't have an external buffer, try to allocate an internal one. */ + if (m_external_entry_buffer == nullptr) { + m_entry_buffer_size = sizeof(Entry) * max_cache_count; + m_internal_entry_buffer = fs::impl::MakeUnique<char[]>(m_entry_buffer_size); + } + + /* We need to have at least one entry buffer. */ + R_UNLESS(m_internal_entry_buffer != nullptr || m_external_entry_buffer != nullptr, fs::ResultAllocationMemoryFailedInFileSystemBufferManagerA()); + + /* Set entries. */ + m_entries = reinterpret_cast<Entry *>(m_external_entry_buffer != nullptr ? m_external_entry_buffer : m_internal_entry_buffer.get()); + m_entry_count = 0; + m_entry_count_max = max_cache_count; + AMS_ASSERT(m_entries != nullptr); + + m_cache_count_min = max_cache_count / 16; + m_cache_size_min = m_cache_count_min * 0x100; + + R_SUCCEED(); + } + + void FileSystemBufferManager::CacheHandleTable::Finalize() { + if (m_entries != nullptr) { + AMS_ASSERT(m_entry_count == 0); + + if (m_external_attr_info_buffer == nullptr) { + auto it = m_attr_list.begin(); + while (it != m_attr_list.end()) { + const auto attr_info = std::addressof(*it); + it = m_attr_list.erase(it); + delete attr_info; + } + } + + m_internal_entry_buffer.reset(); + m_external_entry_buffer = nullptr; + m_entry_buffer_size = 0; + m_entries = nullptr; + m_total_cache_size = 0; + } + } + + bool FileSystemBufferManager::CacheHandleTable::Register(CacheHandle *out, uintptr_t address, size_t size, const BufferAttribute &attr) { + /* Validate pre-conditions. */ + AMS_ASSERT(m_entries != nullptr); + AMS_ASSERT(out != nullptr); + + /* Get the entry. */ + auto entry = this->AcquireEntry(address, size, attr); + + /* If we don't have an entry, we can't register. */ + if (entry == nullptr) { + return false; + } + + /* Get the attr info. If we have one, increment. */ + if (const auto attr_info = this->FindAttrInfo(attr); attr_info != nullptr) { + attr_info->IncrementCacheCount(); + attr_info->AddCacheSize(size); + } else { + /* Make a new attr info and add it to the list. */ + AttrInfo *new_info = nullptr; + + if (m_external_attr_info_buffer == nullptr) { + new_info = new AttrInfo(attr.GetLevel(), 1, size); + } else if (0 <= attr.GetLevel() && attr.GetLevel() < m_external_attr_info_count) { + void *buffer = m_external_attr_info_buffer + attr.GetLevel() * sizeof(AttrInfo); + new_info = std::construct_at(reinterpret_cast<AttrInfo *>(buffer), attr.GetLevel(), 1, size); + } + + /* If we failed to make a new attr info, we can't register. */ + if (new_info == nullptr) { + this->ReleaseEntry(entry); + return false; + } + + m_attr_list.push_back(*new_info); + } + + m_total_cache_size += size; + *out = entry->GetHandle(); + return true; + } + + bool FileSystemBufferManager::CacheHandleTable::Unregister(uintptr_t *out_address, size_t *out_size, CacheHandle handle) { + /* Validate pre-conditions. */ + AMS_ASSERT(m_entries != nullptr); + AMS_ASSERT(out_address != nullptr); + AMS_ASSERT(out_size != nullptr); + + /* Find the lower bound for the entry. */ + const auto entry = std::lower_bound(m_entries, m_entries + m_entry_count, handle, [](const Entry &entry, CacheHandle handle) { + return entry.GetHandle() < handle; + }); + + /* If the entry is a match, unregister it. */ + if (entry != m_entries + m_entry_count && entry->GetHandle() == handle) { + this->UnregisterCore(out_address, out_size, entry); + return true; + } else { + return false; + } + } + + bool FileSystemBufferManager::CacheHandleTable::UnregisterOldest(uintptr_t *out_address, size_t *out_size, const BufferAttribute &attr, size_t required_size) { + AMS_UNUSED(attr, required_size); + + /* Validate pre-conditions. */ + AMS_ASSERT(m_entries != nullptr); + AMS_ASSERT(out_address != nullptr); + AMS_ASSERT(out_size != nullptr); + + /* If we have no entries, we can't unregister any. */ + if (m_entry_count == 0) { + return false; + } + + const auto CanUnregister = [this](const Entry &entry) { + const auto attr_info = this->FindAttrInfo(entry.GetBufferAttribute()); + AMS_ASSERT(attr_info != nullptr); + + const auto ccm = this->GetCacheCountMin(entry.GetBufferAttribute()); + const auto csm = this->GetCacheSizeMin(entry.GetBufferAttribute()); + + return ccm < attr_info->GetCacheCount() && csm + entry.GetSize() <= attr_info->GetCacheSize(); + }; + + /* Find an entry, falling back to the first entry. */ + auto entry = std::find_if(m_entries, m_entries + m_entry_count, CanUnregister); + if (entry == m_entries + m_entry_count) { + entry = m_entries; + } + + AMS_ASSERT(entry != m_entries + m_entry_count); + this->UnregisterCore(out_address, out_size, entry); + return true; + } + + void FileSystemBufferManager::CacheHandleTable::UnregisterCore(uintptr_t *out_address, size_t *out_size, Entry *entry) { + /* Validate pre-conditions. */ + AMS_ASSERT(m_entries != nullptr); + AMS_ASSERT(out_address != nullptr); + AMS_ASSERT(out_size != nullptr); + AMS_ASSERT(entry != nullptr); + + /* Get the attribute info. */ + const auto attr_info = this->FindAttrInfo(entry->GetBufferAttribute()); + AMS_ASSERT(attr_info != nullptr); + AMS_ASSERT(attr_info->GetCacheCount() > 0); + AMS_ASSERT(attr_info->GetCacheSize() >= entry->GetSize()); + + /* Release from the attr info. */ + attr_info->DecrementCacheCount(); + attr_info->SubtractCacheSize(entry->GetSize()); + + /* Release from cached size. */ + AMS_ASSERT(m_total_cache_size >= entry->GetSize()); + m_total_cache_size -= entry->GetSize(); + + /* Release the entry. */ + *out_address = entry->GetAddress(); + *out_size = entry->GetSize(); + this->ReleaseEntry(entry); + } + + FileSystemBufferManager::CacheHandle FileSystemBufferManager::CacheHandleTable::PublishCacheHandle() { + AMS_ASSERT(m_entries != nullptr); + return (++m_current_handle); + } + + size_t FileSystemBufferManager::CacheHandleTable::GetTotalCacheSize() const { + return m_total_cache_size; + } + + FileSystemBufferManager::CacheHandleTable::Entry *FileSystemBufferManager::CacheHandleTable::AcquireEntry(uintptr_t address, size_t size, const BufferAttribute &attr) { + /* Validate pre-conditions. */ + AMS_ASSERT(m_entries != nullptr); + + Entry *entry = nullptr; + if (m_entry_count < m_entry_count_max) { + entry = m_entries + m_entry_count; + entry->Initialize(this->PublishCacheHandle(), address, size, attr); + ++m_entry_count; + AMS_ASSERT(m_entry_count == 1 || (entry-1)->GetHandle() < entry->GetHandle()); + } + + return entry; + } + + void FileSystemBufferManager::CacheHandleTable::ReleaseEntry(Entry *entry) { + /* Validate pre-conditions. */ + AMS_ASSERT(m_entries != nullptr); + AMS_ASSERT(entry != nullptr); + + /* Ensure the entry is valid. */ + { + const auto entry_buffer = m_external_entry_buffer != nullptr ? m_external_entry_buffer : m_internal_entry_buffer.get(); + AMS_ASSERT(static_cast<void *>(entry_buffer) <= static_cast<void *>(entry)); + AMS_ASSERT(static_cast<void *>(entry) < static_cast<void *>(entry_buffer + m_entry_buffer_size)); + AMS_UNUSED(entry_buffer); + } + + /* Copy the entries back by one. */ + std::memmove(entry, entry + 1, sizeof(Entry) * (m_entry_count - ((entry + 1) - m_entries))); + + /* Decrement our entry count. */ + --m_entry_count; + } + + FileSystemBufferManager::CacheHandleTable::AttrInfo *FileSystemBufferManager::CacheHandleTable::FindAttrInfo(const BufferAttribute &attr) { + const auto it = std::find_if(m_attr_list.begin(), m_attr_list.end(), [&attr](const AttrInfo &info) { + return attr.GetLevel() == info.GetLevel(); + }); + + return it != m_attr_list.end() ? std::addressof(*it) : nullptr; + } + + const fs::IBufferManager::MemoryRange FileSystemBufferManager::AllocateBufferImpl(size_t size, const BufferAttribute &attr) { + /* Get/sanity check the required order. */ + fs::IBufferManager::MemoryRange range = {}; + const auto order = m_buddy_heap.GetOrderFromBytes(size); + AMS_ASSERT(order >= 0); + + while (true) { + /* Try to allocate a buffer at the desired order. */ + if (auto address = m_buddy_heap.AllocateByOrder(order); address != 0) { + /* Check that we allocated enough. */ + const auto allocated_size = m_buddy_heap.GetBytesFromOrder(order); + AMS_ASSERT(size <= allocated_size); + + /* Set up the range extents. */ + range.first = reinterpret_cast<uintptr_t>(address); + range.second = allocated_size; + + /* Update our peak tracking variables. */ + const size_t free_size = m_buddy_heap.GetTotalFreeSize(); + m_peak_free_size = std::min(m_peak_free_size, free_size); + + const size_t total_allocatable_size = free_size + m_cache_handle_table.GetTotalCacheSize(); + m_peak_total_allocatable_size = std::min(m_peak_total_allocatable_size, total_allocatable_size); + break; + } + + /* We failed, to we'll need to deallocate something and retry. */ + ++m_retried_count; + + /* Deallocate a buffer. */ + uintptr_t deallocate_address = 0; + size_t deallocate_size = 0; + if (m_cache_handle_table.UnregisterOldest(std::addressof(deallocate_address), std::addressof(deallocate_size), attr, size)) { + this->DeallocateBufferImpl(deallocate_address, deallocate_size); + } else { + break; + } + } + + /* Return the range we allocated. */ + return range; + } + + void FileSystemBufferManager::DeallocateBufferImpl(uintptr_t address, size_t size) { + AMS_ASSERT(util::IsPowerOfTwo(size)); + + m_buddy_heap.Free(reinterpret_cast<void *>(address), m_buddy_heap.GetOrderFromBytes(size)); + } + + FileSystemBufferManager::CacheHandle FileSystemBufferManager::RegisterCacheImpl(uintptr_t address, size_t size, const BufferAttribute &attr) { + CacheHandle handle = 0; + while (true) { + /* Try to register the handle. */ + if (m_cache_handle_table.Register(std::addressof(handle), address, size, attr)) { + break; + } + + /* Deallocate a buffer. */ + uintptr_t deallocate_address = 0; + size_t deallocate_size = 0; + + ++m_retried_count; + if (m_cache_handle_table.UnregisterOldest(std::addressof(deallocate_address), std::addressof(deallocate_size), attr)) { + this->DeallocateBufferImpl(deallocate_address, deallocate_size); + } else { + this->DeallocateBufferImpl(address, size); + handle = m_cache_handle_table.PublishCacheHandle(); + break; + } + } + + return handle; + } + + const fs::IBufferManager::MemoryRange FileSystemBufferManager::AcquireCacheImpl(CacheHandle handle) { + fs::IBufferManager::MemoryRange range = {}; + if (m_cache_handle_table.Unregister(std::addressof(range.first), std::addressof(range.second), handle)) { + const size_t total_allocatable_size = m_buddy_heap.GetTotalFreeSize() + m_cache_handle_table.GetTotalCacheSize(); + m_peak_total_allocatable_size = std::min(m_peak_total_allocatable_size, total_allocatable_size); + } else { + range.first = 0; + range.second = 0; + } + + return range; + } + + size_t FileSystemBufferManager::GetFreeSizeImpl() const { + return m_buddy_heap.GetTotalFreeSize(); + } + + size_t FileSystemBufferManager::GetTotalAllocatableSizeImpl() const { + return this->GetFreeSizeImpl() + m_cache_handle_table.GetTotalCacheSize(); + } + + size_t FileSystemBufferManager::GetFreeSizePeakImpl() const { + return m_peak_free_size; + } + + size_t FileSystemBufferManager::GetTotalAllocatableSizePeakImpl() const { + return m_peak_total_allocatable_size; + } + + size_t FileSystemBufferManager::GetRetriedCountImpl() const { + return m_retried_count; + } + + void FileSystemBufferManager::ClearPeakImpl() { + m_peak_free_size = this->GetFreeSizeImpl(); + m_peak_total_allocatable_size = this->GetTotalAllocatableSizeImpl(); + m_retried_count = 0; + } + + const fs::IBufferManager::MemoryRange FileSystemBufferManager::DoAllocateBuffer(size_t size, const BufferAttribute &attr) { + std::scoped_lock lk(m_mutex); + + return this->AllocateBufferImpl(size, attr); + } + + void FileSystemBufferManager::DoDeallocateBuffer(uintptr_t address, size_t size) { + std::scoped_lock lk(m_mutex); + + return this->DeallocateBufferImpl(address, size); + } + + FileSystemBufferManager::CacheHandle FileSystemBufferManager::DoRegisterCache(uintptr_t address, size_t size, const BufferAttribute &attr) { + std::scoped_lock lk(m_mutex); + + return this->RegisterCacheImpl(address, size, attr); + } + + const fs::IBufferManager::MemoryRange FileSystemBufferManager::DoAcquireCache(CacheHandle handle) { + std::scoped_lock lk(m_mutex); + + return this->AcquireCacheImpl(handle); + } + + size_t FileSystemBufferManager::DoGetTotalSize() const { + return m_total_size; + } + + size_t FileSystemBufferManager::DoGetFreeSize() const { + std::scoped_lock lk(m_mutex); + + return this->GetFreeSizeImpl(); + } + + size_t FileSystemBufferManager::DoGetTotalAllocatableSize() const { + std::scoped_lock lk(m_mutex); + + return this->GetTotalAllocatableSizeImpl(); + } + + size_t FileSystemBufferManager::DoGetFreeSizePeak() const { + std::scoped_lock lk(m_mutex); + + return this->GetFreeSizePeakImpl(); + } + + size_t FileSystemBufferManager::DoGetTotalAllocatableSizePeak() const { + std::scoped_lock lk(m_mutex); + + return this->GetTotalAllocatableSizePeakImpl(); + } + + size_t FileSystemBufferManager::DoGetRetriedCount() const { + std::scoped_lock lk(m_mutex); + + return this->GetRetriedCountImpl(); + } + + void FileSystemBufferManager::DoClearPeak() { + std::scoped_lock lk(m_mutex); + + return this->ClearPeakImpl(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp new file mode 100644 index 00000000..53430d10 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp @@ -0,0 +1,364 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + namespace { + + class SoftwareDecryptor final : public AesCtrCounterExtendedStorage::IDecryptor { + public: + virtual void Decrypt(void *buf, size_t buf_size, const void *enc_key, size_t enc_key_size, void *iv, size_t iv_size) override final; + virtual bool HasExternalDecryptionKey() const override final { return false; } + }; + + class ExternalDecryptor final : public AesCtrCounterExtendedStorage::IDecryptor { + public: + static constexpr size_t BlockSize = AesCtrCounterExtendedStorage::BlockSize; + static constexpr size_t KeySize = AesCtrCounterExtendedStorage::KeySize; + static constexpr size_t IvSize = AesCtrCounterExtendedStorage::IvSize; + private: + AesCtrCounterExtendedStorage::DecryptFunction m_decrypt_function; + s32 m_key_index; + s32 m_key_generation; + public: + ExternalDecryptor(AesCtrCounterExtendedStorage::DecryptFunction df, s32 key_idx, s32 key_gen) : m_decrypt_function(df), m_key_index(key_idx), m_key_generation(key_gen) { + AMS_ASSERT(m_decrypt_function != nullptr); + } + public: + virtual void Decrypt(void *buf, size_t buf_size, const void *enc_key, size_t enc_key_size, void *iv, size_t iv_size) override final; + virtual bool HasExternalDecryptionKey() const override final { return m_key_index < 0; } + }; + + } + + Result AesCtrCounterExtendedStorage::CreateExternalDecryptor(std::unique_ptr<IDecryptor> *out, DecryptFunction func, s32 key_index, s32 key_generation) { + std::unique_ptr<IDecryptor> decryptor = std::make_unique<ExternalDecryptor>(func, key_index, key_generation); + R_UNLESS(decryptor != nullptr, fs::ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA()); + *out = std::move(decryptor); + R_SUCCEED(); + } + + Result AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::unique_ptr<IDecryptor> *out) { + std::unique_ptr<IDecryptor> decryptor = std::make_unique<SoftwareDecryptor>(); + R_UNLESS(decryptor != nullptr, fs::ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA()); + *out = std::move(decryptor); + R_SUCCEED(); + } + + Result AesCtrCounterExtendedStorage::Initialize(IAllocator *allocator, const void *key, size_t key_size, u32 secure_value, fs::SubStorage data_storage, fs::SubStorage table_storage) { + /* Read and verify the bucket tree header. */ + BucketTree::Header header; + R_TRY(table_storage.Read(0, std::addressof(header), sizeof(header))); + R_TRY(header.Verify()); + + /* Determine extents. */ + const auto node_storage_size = QueryNodeStorageSize(header.entry_count); + const auto entry_storage_size = QueryEntryStorageSize(header.entry_count); + const auto node_storage_offset = QueryHeaderStorageSize(); + const auto entry_storage_offset = node_storage_offset + node_storage_size; + + /* Create a software decryptor. */ + std::unique_ptr<IDecryptor> sw_decryptor; + R_TRY(CreateSoftwareDecryptor(std::addressof(sw_decryptor))); + + /* Initialize. */ + R_RETURN(this->Initialize(allocator, key, key_size, secure_value, 0, data_storage, fs::SubStorage(std::addressof(table_storage), node_storage_offset, node_storage_size), fs::SubStorage(std::addressof(table_storage), entry_storage_offset, entry_storage_size), header.entry_count, std::move(sw_decryptor))); + } + + Result AesCtrCounterExtendedStorage::Initialize(IAllocator *allocator, const void *key, size_t key_size, u32 secure_value, s64 counter_offset, fs::SubStorage data_storage, fs::SubStorage node_storage, fs::SubStorage entry_storage, s32 entry_count, std::unique_ptr<IDecryptor> &&decryptor) { + /* Validate preconditions. */ + AMS_ASSERT(key != nullptr); + AMS_ASSERT(key_size == KeySize); + AMS_ASSERT(counter_offset >= 0); + AMS_ASSERT(decryptor != nullptr); + + /* Initialize the bucket tree table. */ + if (entry_count > 0) { + R_TRY(m_table.Initialize(allocator, node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count)); + } else { + m_table.Initialize(NodeSize, 0); + } + + /* Set members. */ + m_data_storage = data_storage; + std::memcpy(m_key, key, key_size); + m_secure_value = secure_value; + m_counter_offset = counter_offset; + m_decryptor = std::move(decryptor); + + R_SUCCEED(); + } + + void AesCtrCounterExtendedStorage::Finalize() { + if (this->IsInitialized()) { + m_table.Finalize(); + m_data_storage = fs::SubStorage(); + } + } + + Result AesCtrCounterExtendedStorage::GetEntryList(Entry *out_entries, s32 *out_entry_count, s32 entry_count, s64 offset, s64 size) { + /* Validate pre-conditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(size >= 0); + AMS_ASSERT(this->IsInitialized()); + + /* Clear the out count. */ + R_UNLESS(out_entry_count != nullptr, fs::ResultNullptrArgument()); + *out_entry_count = 0; + + /* Succeed if there's no range. */ + R_SUCCEED_IF(size == 0); + + /* If we have an output array, we need it to be non-null. */ + R_UNLESS(out_entries != nullptr || entry_count == 0, fs::ResultNullptrArgument()); + + /* Check that our range is valid. */ + BucketTree::Offsets table_offsets; + R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); + + R_UNLESS(table_offsets.IsInclude(offset, size), fs::ResultOutOfRange()); + + /* Find the offset in our tree. */ + BucketTree::Visitor visitor; + R_TRY(m_table.Find(std::addressof(visitor), offset)); + { + const auto entry_offset = visitor.Get<Entry>()->GetOffset(); + R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), fs::ResultInvalidAesCtrCounterExtendedEntryOffset()); + } + + /* Prepare to loop over entries. */ + const auto end_offset = offset + static_cast<s64>(size); + s32 count = 0; + + auto cur_entry = *visitor.Get<Entry>(); + while (cur_entry.GetOffset() < end_offset) { + /* Try to write the entry to the out list. */ + if (entry_count != 0) { + if (count >= entry_count) { + break; + } + std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry)); + } + + count++; + + /* Advance. */ + if (visitor.CanMoveNext()) { + R_TRY(visitor.MoveNext()); + cur_entry = *visitor.Get<Entry>(); + } else { + break; + } + } + + /* Write the output count. */ + *out_entry_count = count; + R_SUCCEED(); + } + + Result AesCtrCounterExtendedStorage::Read(s64 offset, void *buffer, size_t size) { + /* Validate preconditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(this->IsInitialized()); + + /* Allow zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidOffset()); + R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidSize()); + + BucketTree::Offsets table_offsets; + R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); + + R_UNLESS(table_offsets.IsInclude(offset, size), fs::ResultOutOfRange()); + + /* Read the data. */ + R_TRY(m_data_storage.Read(offset, buffer, size)); + + /* Temporarily increase our thread priority. */ + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + /* Find the offset in our tree. */ + BucketTree::Visitor visitor; + R_TRY(m_table.Find(std::addressof(visitor), offset)); + { + const auto entry_offset = visitor.Get<Entry>()->GetOffset(); + R_UNLESS(util::IsAligned(entry_offset, BlockSize), fs::ResultInvalidAesCtrCounterExtendedEntryOffset()); + R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), fs::ResultInvalidAesCtrCounterExtendedEntryOffset()); + } + + /* Prepare to read in chunks. */ + u8 *cur_data = static_cast<u8 *>(buffer); + auto cur_offset = offset; + const auto end_offset = offset + static_cast<s64>(size); + + while (cur_offset < end_offset) { + /* Get the current entry. */ + const auto cur_entry = *visitor.Get<Entry>(); + + /* Get and validate the entry's offset. */ + const auto cur_entry_offset = cur_entry.GetOffset(); + R_UNLESS(cur_entry_offset <= cur_offset, fs::ResultInvalidAesCtrCounterExtendedEntryOffset()); + + /* Get and validate the next entry offset. */ + s64 next_entry_offset; + if (visitor.CanMoveNext()) { + R_TRY(visitor.MoveNext()); + next_entry_offset = visitor.Get<Entry>()->GetOffset(); + R_UNLESS(table_offsets.IsInclude(next_entry_offset), fs::ResultInvalidAesCtrCounterExtendedEntryOffset()); + } else { + next_entry_offset = table_offsets.end_offset; + } + R_UNLESS(util::IsAligned(next_entry_offset, BlockSize), fs::ResultInvalidAesCtrCounterExtendedEntryOffset()); + R_UNLESS(cur_offset < next_entry_offset, fs::ResultInvalidAesCtrCounterExtendedEntryOffset()); + + /* Get the offset of the entry in the data we read. */ + const auto data_offset = cur_offset - cur_entry_offset; + const auto data_size = (next_entry_offset - cur_entry_offset) - data_offset; + AMS_ASSERT(data_size > 0); + + /* Determine how much is left. */ + const auto remaining_size = end_offset - cur_offset; + const auto cur_size = static_cast<size_t>(std::min(remaining_size, data_size)); + AMS_ASSERT(cur_size <= size); + + /* If necessary, perform decryption. */ + if (cur_entry.encryption_value == Entry::Encryption::Encrypted) { + /* Make the CTR for the data we're decrypting. */ + const auto counter_offset = m_counter_offset + cur_entry_offset + data_offset; + NcaAesCtrUpperIv upper_iv = { .part = { .generation = static_cast<u32>(cur_entry.generation), .secure_value = m_secure_value } }; + + u8 iv[IvSize]; + AesCtrStorageByPointer::MakeIv(iv, IvSize, upper_iv.value, counter_offset); + + /* Decrypt. */ + m_decryptor->Decrypt(cur_data, cur_size, m_key, KeySize, iv, IvSize); + } + + /* Advance. */ + cur_data += cur_size; + cur_offset += cur_size; + } + + R_SUCCEED(); + } + + Result AesCtrCounterExtendedStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + switch (op_id) { + case fs::OperationId::Invalidate: + { + /* Validate preconditions. */ + AMS_ASSERT(this->IsInitialized()); + + /* Invalidate our table's cache. */ + R_TRY(m_table.InvalidateCache()); + + /* Operate on our data storage. */ + R_TRY(m_data_storage.OperateRange(fs::OperationId::Invalidate, 0, std::numeric_limits<s64>::max())); + + R_SUCCEED(); + } + case fs::OperationId::QueryRange: + { + /* Validate preconditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(this->IsInitialized()); + + /* Validate that we have an output range info. */ + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize()); + + /* Succeed if there's nothing to operate on. */ + if (size == 0) { + reinterpret_cast<fs::QueryRangeInfo *>(dst)->Clear(); + R_SUCCEED(); + } + + /* Validate arguments. */ + R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidOffset()); + R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidSize()); + + BucketTree::Offsets table_offsets; + R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); + + R_UNLESS(table_offsets.IsInclude(offset, size), fs::ResultOutOfRange()); + + /* Operate on our data storage. */ + R_TRY(m_data_storage.OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + + /* Add in new flags. */ + fs::QueryRangeInfo new_info; + new_info.Clear(); + new_info.aes_ctr_key_type = static_cast<s32>(m_decryptor->HasExternalDecryptionKey() ? fs::AesCtrKeyTypeFlag::ExternalKeyForHardwareAes : fs::AesCtrKeyTypeFlag::InternalKeyForHardwareAes); + + /* Merge in the new info. */ + reinterpret_cast<fs::QueryRangeInfo *>(dst)->Merge(new_info); + + R_SUCCEED(); + } + default: + R_THROW(fs::ResultUnsupportedOperateRangeForAesCtrCounterExtendedStorage()); + } + } + + void SoftwareDecryptor::Decrypt(void *buf, size_t buf_size, const void *enc_key, size_t enc_key_size, void *iv, size_t iv_size) { + crypto::DecryptAes128Ctr(buf, buf_size, enc_key, enc_key_size, iv, iv_size, buf, buf_size); + } + + void ExternalDecryptor::Decrypt(void *buf, size_t buf_size, const void *enc_key, size_t enc_key_size, void *iv, size_t iv_size) { + /* Validate preconditions. */ + AMS_ASSERT(buf != nullptr); + AMS_ASSERT(enc_key != nullptr); + AMS_ASSERT(enc_key_size == KeySize); + AMS_ASSERT(iv != nullptr); + AMS_ASSERT(iv_size == IvSize); + AMS_UNUSED(iv_size); + + /* Copy the ctr. */ + u8 ctr[IvSize]; + std::memcpy(ctr, iv, IvSize); + + /* Setup tracking. */ + size_t remaining_size = buf_size; + s64 cur_offset = 0; + + /* Allocate a pooled buffer for decryption. */ + PooledBuffer pooled_buffer; + pooled_buffer.AllocateParticularlyLarge(buf_size, BlockSize); + AMS_ASSERT(pooled_buffer.GetSize() > 0 && util::IsAligned(pooled_buffer.GetSize(), BlockSize)); + + /* Read and decrypt in chunks. */ + while (remaining_size > 0) { + size_t cur_size = std::min(pooled_buffer.GetSize(), remaining_size); + u8 *dst = static_cast<u8 *>(buf) + cur_offset; + + m_decrypt_function(pooled_buffer.GetBuffer(), cur_size, m_key_index, m_key_generation, enc_key, enc_key_size, ctr, IvSize, dst, cur_size); + + std::memcpy(dst, pooled_buffer.GetBuffer(), cur_size); + + cur_offset += cur_size; + remaining_size -= cur_size; + + if (remaining_size > 0) { + AddCounter(ctr, IvSize, cur_size / BlockSize); + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_storage.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_storage.cpp new file mode 100644 index 00000000..817900cb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_storage.cpp @@ -0,0 +1,195 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + template<fs::PointerToStorage BasePointer> + void AesCtrStorage<BasePointer>::MakeIv(void *dst, size_t dst_size, u64 upper, s64 offset) { + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(dst_size == IvSize); + AMS_ASSERT(offset >= 0); + AMS_UNUSED(dst_size); + + const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst); + + util::StoreBigEndian(reinterpret_cast<u64 *>(out_addr + 0), upper); + util::StoreBigEndian(reinterpret_cast<s64 *>(out_addr + sizeof(u64)), static_cast<s64>(offset / BlockSize)); + } + + template<fs::PointerToStorage BasePointer> + AesCtrStorage<BasePointer>::AesCtrStorage(BasePointer base, const void *key, size_t key_size, const void *iv, size_t iv_size) : m_base_storage(std::move(base)) { + AMS_ASSERT(m_base_storage != nullptr); + AMS_ASSERT(key != nullptr); + AMS_ASSERT(iv != nullptr); + AMS_ASSERT(key_size == KeySize); + AMS_ASSERT(iv_size == IvSize); + AMS_UNUSED(key_size, iv_size); + + std::memcpy(m_key, key, KeySize); + std::memcpy(m_iv, iv, IvSize); + } + + template<fs::PointerToStorage BasePointer> + Result AesCtrStorage<BasePointer>::Read(s64 offset, void *buffer, size_t size) { + /* Allow zero-size reads. */ + R_SUCCEED_IF(size == 0); + + /* Ensure buffer is valid. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* We can only read at block aligned offsets. */ + R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidArgument()); + + /* Read the data. */ + R_TRY(m_base_storage->Read(offset, buffer, size)); + + /* Prepare to decrypt the data, with temporarily increased priority. */ + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + /* Setup the counter. */ + char ctr[IvSize]; + std::memcpy(ctr, m_iv, IvSize); + AddCounter(ctr, IvSize, offset / BlockSize); + + /* Decrypt, ensure we decrypt correctly. */ + auto dec_size = crypto::DecryptAes128Ctr(buffer, size, m_key, KeySize, ctr, IvSize, buffer, size); + R_UNLESS(size == dec_size, fs::ResultUnexpectedInAesCtrStorageA()); + + R_SUCCEED(); + } + + template<fs::PointerToStorage BasePointer> + Result AesCtrStorage<BasePointer>::Write(s64 offset, const void *buffer, size_t size) { + /* Allow zero-size writes. */ + R_SUCCEED_IF(size == 0); + + /* Ensure buffer is valid. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* We can only write at block aligned offsets. */ + R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidArgument()); + + /* Get a pooled buffer. */ + PooledBuffer pooled_buffer; + const bool use_work_buffer = !IsDeviceAddress(buffer); + if (use_work_buffer) { + pooled_buffer.Allocate(size, BlockSize); + } + + /* Setup the counter. */ + char ctr[IvSize]; + std::memcpy(ctr, m_iv, IvSize); + AddCounter(ctr, IvSize, offset / BlockSize); + + /* Loop until all data is written. */ + size_t remaining = size; + s64 cur_offset = 0; + while (remaining > 0) { + /* Determine data we're writing and where. */ + const size_t write_size = use_work_buffer ? std::min(pooled_buffer.GetSize(), remaining) : remaining; + void *write_buf = use_work_buffer ? pooled_buffer.GetBuffer() : const_cast<void *>(buffer); + + /* Encrypt the data, with temporarily increased priority. */ + { + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + auto enc_size = crypto::EncryptAes128Ctr(write_buf, write_size, m_key, KeySize, ctr, IvSize, reinterpret_cast<const char *>(buffer) + cur_offset, write_size); + R_UNLESS(enc_size == write_size, fs::ResultUnexpectedInAesCtrStorageA()); + } + + /* Write the encrypted data. */ + R_TRY(m_base_storage->Write(offset + cur_offset, write_buf, write_size)); + + /* Advance. */ + cur_offset += write_size; + remaining -= write_size; + if (remaining > 0) { + AddCounter(ctr, IvSize, write_size / BlockSize); + } + } + + R_SUCCEED(); + } + + template<fs::PointerToStorage BasePointer> + Result AesCtrStorage<BasePointer>::Flush() { + R_RETURN(m_base_storage->Flush()); + } + + template<fs::PointerToStorage BasePointer> + Result AesCtrStorage<BasePointer>::SetSize(s64 size) { + AMS_UNUSED(size); + R_THROW(fs::ResultUnsupportedSetSizeForAesCtrStorage()); + } + + template<fs::PointerToStorage BasePointer> + Result AesCtrStorage<BasePointer>::GetSize(s64 *out) { + R_RETURN(m_base_storage->GetSize(out)); + } + + template<fs::PointerToStorage BasePointer> + Result AesCtrStorage<BasePointer>::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + /* If operation isn't invalidate, special case. */ + if (op_id != fs::OperationId::Invalidate) { + /* Handle the zero-size case. */ + if (size == 0) { + if (op_id == fs::OperationId::QueryRange) { + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize()); + + reinterpret_cast<fs::QueryRangeInfo *>(dst)->Clear(); + } + + R_SUCCEED(); + } + + /* Ensure alignment. */ + R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidArgument()); + } + + switch (op_id) { + case fs::OperationId::QueryRange: + { + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize()); + + R_TRY(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + + fs::QueryRangeInfo info; + info.Clear(); + info.aes_ctr_key_type = static_cast<s32>(fs::AesCtrKeyTypeFlag::InternalKeyForSoftwareAes); + + reinterpret_cast<fs::QueryRangeInfo *>(dst)->Merge(info); + } + break; + default: + { + R_TRY(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + } + break; + } + + R_SUCCEED(); + } + + template class AesCtrStorage<fs::IStorage *>; + template class AesCtrStorage<std::shared_ptr<fs::IStorage>>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_storage_external.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_storage_external.cpp new file mode 100644 index 00000000..256dfa31 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_storage_external.cpp @@ -0,0 +1,130 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + AesCtrStorageExternal::AesCtrStorageExternal(std::shared_ptr<fs::IStorage> bs, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, DecryptAesCtrFunction df, s32 kidx, s32 kgen) : m_base_storage(std::move(bs)), m_decrypt_function(df), m_key_index(kidx), m_key_generation(kgen) { + AMS_ASSERT(m_base_storage != nullptr); + AMS_ASSERT(enc_key_size == KeySize); + AMS_ASSERT(iv != nullptr); + AMS_ASSERT(iv_size == IvSize); + AMS_UNUSED(iv_size); + + std::memcpy(m_iv, iv, IvSize); + std::memcpy(m_encrypted_key, enc_key, enc_key_size); + } + + Result AesCtrStorageExternal::Read(s64 offset, void *buffer, size_t size) { + /* Allow zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + /* NOTE: For some reason, Nintendo uses InvalidArgument instead of InvalidOffset/InvalidSize here. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidArgument()); + + /* Read the data. */ + R_TRY(m_base_storage->Read(offset, buffer, size)); + + /* Temporarily increase our thread priority. */ + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + /* Allocate a pooled buffer for decryption. */ + PooledBuffer pooled_buffer; + pooled_buffer.AllocateParticularlyLarge(size, BlockSize); + AMS_ASSERT(pooled_buffer.GetSize() >= BlockSize); + + /* Setup the counter. */ + u8 ctr[IvSize]; + std::memcpy(ctr, m_iv, IvSize); + AddCounter(ctr, IvSize, offset / BlockSize); + + /* Setup tracking. */ + size_t remaining_size = size; + s64 cur_offset = 0; + + while (remaining_size > 0) { + /* Get the current size to process. */ + size_t cur_size = std::min(pooled_buffer.GetSize(), remaining_size); + char *dst = static_cast<char *>(buffer) + cur_offset; + + /* Decrypt into the temporary buffer */ + m_decrypt_function(pooled_buffer.GetBuffer(), cur_size, m_key_index, m_key_generation, m_encrypted_key, KeySize, ctr, IvSize, dst, cur_size); + + /* Copy to the destination. */ + std::memcpy(dst, pooled_buffer.GetBuffer(), cur_size); + + /* Update tracking. */ + cur_offset += cur_size; + remaining_size -= cur_size; + + if (remaining_size > 0) { + AddCounter(ctr, IvSize, cur_size / BlockSize); + } + } + + R_SUCCEED(); + } + + Result AesCtrStorageExternal::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + switch (op_id) { + case fs::OperationId::QueryRange: + { + /* Validate that we have an output range info. */ + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize()); + + /* Operate on our base storage. */ + R_TRY(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + + /* Add in new flags. */ + fs::QueryRangeInfo new_info; + new_info.Clear(); + new_info.aes_ctr_key_type = static_cast<s32>(m_key_index >= 0 ? fs::AesCtrKeyTypeFlag::InternalKeyForHardwareAes : fs::AesCtrKeyTypeFlag::ExternalKeyForHardwareAes); + + /* Merge the new info in. */ + reinterpret_cast<fs::QueryRangeInfo *>(dst)->Merge(new_info); + R_SUCCEED(); + } + default: + { + /* Operate on our base storage. */ + R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + } + } + } + + Result AesCtrStorageExternal::GetSize(s64 *out) { + R_RETURN(m_base_storage->GetSize(out)); + } + + Result AesCtrStorageExternal::Flush() { + R_SUCCEED(); + } + + Result AesCtrStorageExternal::Write(s64 offset, const void *buffer, size_t size) { + AMS_UNUSED(offset, buffer, size); + R_THROW(fs::ResultUnsupportedWriteForAesCtrStorageExternal()); + } + + Result AesCtrStorageExternal::SetSize(s64 size) { + AMS_UNUSED(size); + R_THROW(fs::ResultUnsupportedSetSizeForAesCtrStorageExternal()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_aes_xts_storage.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_aes_xts_storage.cpp new file mode 100644 index 00000000..c8a8326d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_aes_xts_storage.cpp @@ -0,0 +1,247 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + template<fs::PointerToStorage BasePointer> + void AesXtsStorage<BasePointer>::MakeAesXtsIv(void *dst, size_t dst_size, s64 offset, size_t block_size) { + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(dst_size == IvSize); + AMS_ASSERT(offset >= 0); + AMS_UNUSED(dst_size); + + const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst); + + util::StoreBigEndian<s64>(reinterpret_cast<s64 *>(out_addr + sizeof(s64)), offset / block_size); + } + + template<fs::PointerToStorage BasePointer> + AesXtsStorage<BasePointer>::AesXtsStorage(BasePointer base, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, size_t block_size) : m_base_storage(std::move(base)), m_block_size(block_size), m_mutex() { + AMS_ASSERT(m_base_storage != nullptr); + AMS_ASSERT(key1 != nullptr); + AMS_ASSERT(key2 != nullptr); + AMS_ASSERT(iv != nullptr); + AMS_ASSERT(key_size == KeySize); + AMS_ASSERT(iv_size == IvSize); + AMS_ASSERT(util::IsAligned(m_block_size, AesBlockSize)); + AMS_UNUSED(key_size, iv_size); + + std::memcpy(m_key[0], key1, KeySize); + std::memcpy(m_key[1], key2, KeySize); + std::memcpy(m_iv, iv, IvSize); + } + + template<fs::PointerToStorage BasePointer> + Result AesXtsStorage<BasePointer>::Read(s64 offset, void *buffer, size_t size) { + /* Allow zero-size reads. */ + R_SUCCEED_IF(size == 0); + + /* Ensure buffer is valid. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* We can only read at block aligned offsets. */ + R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument()); + + /* Read the data. */ + R_TRY(m_base_storage->Read(offset, buffer, size)); + + /* Prepare to decrypt the data, with temporarily increased priority. */ + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + /* Setup the counter. */ + char ctr[IvSize]; + std::memcpy(ctr, m_iv, IvSize); + AddCounter(ctr, IvSize, offset / m_block_size); + + /* Handle any unaligned data before the start. */ + size_t processed_size = 0; + if ((offset % m_block_size) != 0) { + /* Determine the size of the pre-data read. */ + const size_t skip_size = static_cast<size_t>(offset - util::AlignDown(offset, m_block_size)); + const size_t data_size = std::min(size, m_block_size - skip_size); + + /* Decrypt into a pooled buffer. */ + { + PooledBuffer tmp_buf(m_block_size, m_block_size); + AMS_ASSERT(tmp_buf.GetSize() >= m_block_size); + + std::memset(tmp_buf.GetBuffer(), 0, skip_size); + std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size); + + const size_t dec_size = crypto::DecryptAes128Xts(tmp_buf.GetBuffer(), m_block_size, m_key[0], m_key[1], KeySize, ctr, IvSize, tmp_buf.GetBuffer(), m_block_size); + R_UNLESS(dec_size == m_block_size, fs::ResultUnexpectedInAesXtsStorageA()); + + std::memcpy(buffer, tmp_buf.GetBuffer() + skip_size, data_size); + } + + AddCounter(ctr, IvSize, 1); + processed_size += data_size; + AMS_ASSERT(processed_size == std::min(size, m_block_size - skip_size)); + } + + /* Decrypt aligned chunks. */ + char *cur = static_cast<char *>(buffer) + processed_size; + size_t remaining = size - processed_size; + while (remaining > 0) { + const size_t cur_size = std::min(m_block_size, remaining); + const size_t dec_size = crypto::DecryptAes128Xts(cur, cur_size, m_key[0], m_key[1], KeySize, ctr, IvSize, cur, cur_size); + R_UNLESS(cur_size == dec_size, fs::ResultUnexpectedInAesXtsStorageA()); + + remaining -= cur_size; + cur += cur_size; + + AddCounter(ctr, IvSize, 1); + } + + R_SUCCEED(); + } + + template<fs::PointerToStorage BasePointer> + Result AesXtsStorage<BasePointer>::Write(s64 offset, const void *buffer, size_t size) { + /* Allow zero-size writes. */ + R_SUCCEED_IF(size == 0); + + /* Ensure buffer is valid. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* We can only read at block aligned offsets. */ + R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument()); + + /* Get a pooled buffer. */ + PooledBuffer pooled_buffer; + const bool use_work_buffer = !IsDeviceAddress(buffer); + if (use_work_buffer) { + pooled_buffer.Allocate(size, m_block_size); + } + + /* Setup the counter. */ + char ctr[IvSize]; + std::memcpy(ctr, m_iv, IvSize); + AddCounter(ctr, IvSize, offset / m_block_size); + + /* Handle any unaligned data before the start. */ + size_t processed_size = 0; + if ((offset % m_block_size) != 0) { + /* Determine the size of the pre-data read. */ + const size_t skip_size = static_cast<size_t>(offset - util::AlignDown(offset, m_block_size)); + const size_t data_size = std::min(size, m_block_size - skip_size); + + /* Create an encryptor. */ + /* NOTE: This is completely unnecessary, because crypto::EncryptAes128Xts is used below. */ + /* However, Nintendo does it, so we will too. */ + crypto::Aes128XtsEncryptor xts; + xts.Initialize(m_key[0], m_key[1], KeySize, ctr, IvSize); + + /* Encrypt into a pooled buffer. */ + { + /* NOTE: Nintendo allocates a second pooled buffer here despite having one already allocated above. */ + PooledBuffer tmp_buf(m_block_size, m_block_size); + AMS_ASSERT(tmp_buf.GetSize() >= m_block_size); + + std::memset(tmp_buf.GetBuffer(), 0, skip_size); + std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size); + + const size_t enc_size = crypto::EncryptAes128Xts(tmp_buf.GetBuffer(), m_block_size, m_key[0], m_key[1], KeySize, ctr, IvSize, tmp_buf.GetBuffer(), m_block_size); + R_UNLESS(enc_size == m_block_size, fs::ResultUnexpectedInAesXtsStorageA()); + + R_TRY(m_base_storage->Write(offset, tmp_buf.GetBuffer() + skip_size, data_size)); + } + + AddCounter(ctr, IvSize, 1); + processed_size += data_size; + AMS_ASSERT(processed_size == std::min(size, m_block_size - skip_size)); + } + + /* Encrypt aligned chunks. */ + size_t remaining = size - processed_size; + s64 cur_offset = offset + processed_size; + while (remaining > 0) { + /* Determine data we're writing and where. */ + const size_t write_size = use_work_buffer ? std::min(pooled_buffer.GetSize(), remaining) : remaining; + + /* Encrypt the data, with temporarily increased priority. */ + { + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + size_t remaining_write = write_size; + size_t encrypt_offset = 0; + while (remaining_write > 0) { + const size_t cur_size = std::min(remaining_write, m_block_size); + const void *src = static_cast<const char *>(buffer) + processed_size + encrypt_offset; + void *dst = use_work_buffer ? pooled_buffer.GetBuffer() + encrypt_offset : const_cast<void *>(src); + + const size_t enc_size = crypto::EncryptAes128Xts(dst, cur_size, m_key[0], m_key[1], KeySize, ctr, IvSize, src, cur_size); + R_UNLESS(enc_size == cur_size, fs::ResultUnexpectedInAesXtsStorageA()); + + AddCounter(ctr, IvSize, 1); + + encrypt_offset += cur_size; + remaining_write -= cur_size; + } + } + + /* Write the encrypted data. */ + const void *write_buf = use_work_buffer ? pooled_buffer.GetBuffer() : static_cast<const char *>(buffer) + processed_size; + R_TRY(m_base_storage->Write(cur_offset, write_buf, write_size)); + + /* Advance. */ + cur_offset += write_size; + processed_size += write_size; + remaining -= write_size; + } + + R_SUCCEED(); + } + + template<fs::PointerToStorage BasePointer> + Result AesXtsStorage<BasePointer>::Flush() { + R_RETURN(m_base_storage->Flush()); + } + + template<fs::PointerToStorage BasePointer> + Result AesXtsStorage<BasePointer>::SetSize(s64 size) { + R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultUnexpectedInAesXtsStorageA()); + + R_RETURN(m_base_storage->SetSize(size)); + } + + template<fs::PointerToStorage BasePointer> + Result AesXtsStorage<BasePointer>::GetSize(s64 *out) { + R_RETURN(m_base_storage->GetSize(out)); + } + + template<fs::PointerToStorage BasePointer> + Result AesXtsStorage<BasePointer>::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + /* Unless invalidating cache, check the arguments. */ + if (op_id != fs::OperationId::Invalidate) { + /* Handle the zero size case. */ + R_SUCCEED_IF(size == 0); + + /* Ensure alignment. */ + R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument()); + } + + R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + } + + template class AesXtsStorage<fs::IStorage *>; + template class AesXtsStorage<std::shared_ptr<fs::IStorage>>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_aes_xts_storage_external.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_aes_xts_storage_external.cpp new file mode 100644 index 00000000..a2741ec6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_aes_xts_storage_external.cpp @@ -0,0 +1,233 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + template<fs::PointerToStorage BasePointer> + AesXtsStorageExternal<BasePointer>::AesXtsStorageExternal(BasePointer bs, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, size_t block_size, CryptAesXtsFunction ef, CryptAesXtsFunction df) : m_base_storage(std::move(bs)), m_block_size(block_size), m_encrypt_function(ef), m_decrypt_function(df) { + AMS_ASSERT(key_size == KeySize); + AMS_ASSERT(iv_size == IvSize); + AMS_UNUSED(key_size, iv_size); + + if (key1 != nullptr) { + std::memcpy(m_key[0], key1, KeySize); + } + + if (key2 != nullptr) { + std::memcpy(m_key[1], key2, KeySize); + } + + std::memcpy(m_iv, iv, IvSize); + } + + template<fs::PointerToStorage BasePointer> + Result AesXtsStorageExternal<BasePointer>::Read(s64 offset, void *buffer, size_t size) { + /* Allow zero size. */ + R_SUCCEED_IF(size == 0); + + /* Ensure buffer is valid. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Ensure we can decrypt. */ + R_UNLESS(m_decrypt_function != nullptr, fs::ResultNullptrArgument()); + + /* We can only read at block aligned offsets. */ + R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument()); + + /* Read the data. */ + R_TRY(m_base_storage->Read(offset, buffer, size)); + + /* Temporarily increase our thread priority. */ + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + /* Setup the counter. */ + char ctr[IvSize]; + std::memcpy(ctr, m_iv, IvSize); + AddCounter(ctr, IvSize, offset / m_block_size); + + /* Handle any unaligned data before the start. */ + size_t processed_size = 0; + if ((offset % m_block_size) != 0) { + /* Determine the size of the pre-data read. */ + const size_t skip_size = static_cast<size_t>(offset - util::AlignDown(offset, m_block_size)); + const size_t data_size = std::min(size, m_block_size - skip_size); + + /* Decrypt into a pooled buffer. */ + { + PooledBuffer tmp_buf(m_block_size, m_block_size); + AMS_ASSERT(tmp_buf.GetSize() >= m_block_size); + + std::memset(tmp_buf.GetBuffer(), 0, skip_size); + std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size); + + /* Decrypt. */ + R_TRY(m_decrypt_function(tmp_buf.GetBuffer(), m_block_size, m_key[0], m_key[1], KeySize, ctr, IvSize, tmp_buf.GetBuffer(), m_block_size)); + + std::memcpy(buffer, tmp_buf.GetBuffer() + skip_size, data_size); + } + + AddCounter(ctr, IvSize, 1); + processed_size += data_size; + AMS_ASSERT(processed_size == std::min(size, m_block_size - skip_size)); + } + + /* Decrypt aligned chunks. */ + char *cur = static_cast<char *>(buffer) + processed_size; + size_t remaining = size - processed_size; + while (remaining > 0) { + const size_t cur_size = std::min(m_block_size, remaining); + R_TRY(m_decrypt_function(cur, cur_size, m_key[0], m_key[1], KeySize, ctr, IvSize, cur, cur_size)); + + remaining -= cur_size; + cur += cur_size; + + AddCounter(ctr, IvSize, 1); + } + + R_SUCCEED(); + } + + template<fs::PointerToStorage BasePointer> + Result AesXtsStorageExternal<BasePointer>::Write(s64 offset, const void *buffer, size_t size) { + /* Allow zero-size writes. */ + R_SUCCEED_IF(size == 0); + + /* Ensure buffer is valid. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Ensure we can encrypt. */ + R_UNLESS(m_encrypt_function != nullptr, fs::ResultNullptrArgument()); + + /* We can only write at block aligned offsets. */ + R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument()); + + /* Get a pooled buffer. */ + PooledBuffer pooled_buffer; + const bool use_work_buffer = !IsDeviceAddress(buffer); + if (use_work_buffer) { + pooled_buffer.Allocate(size, m_block_size); + } + + /* Setup the counter. */ + char ctr[IvSize]; + std::memcpy(ctr, m_iv, IvSize); + AddCounter(ctr, IvSize, offset / m_block_size); + + /* Handle any unaligned data before the start. */ + size_t processed_size = 0; + if ((offset % m_block_size) != 0) { + /* Determine the size of the pre-data read. */ + const size_t skip_size = static_cast<size_t>(offset - util::AlignDown(offset, m_block_size)); + const size_t data_size = std::min(size, m_block_size - skip_size); + + /* Encrypt into a pooled buffer. */ + { + /* NOTE: Nintendo allocates a second pooled buffer here despite having one already allocated above. */ + PooledBuffer tmp_buf(m_block_size, m_block_size); + AMS_ASSERT(tmp_buf.GetSize() >= m_block_size); + + std::memset(tmp_buf.GetBuffer(), 0, skip_size); + std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size); + + R_TRY(m_encrypt_function(tmp_buf.GetBuffer(), m_block_size, m_key[0], m_key[1], KeySize, ctr, IvSize, tmp_buf.GetBuffer(), m_block_size)); + + R_TRY(m_base_storage->Write(offset, tmp_buf.GetBuffer() + skip_size, data_size)); + } + + AddCounter(ctr, IvSize, 1); + processed_size += data_size; + AMS_ASSERT(processed_size == std::min(size, m_block_size - skip_size)); + } + + /* Encrypt aligned chunks. */ + size_t remaining = size - processed_size; + s64 cur_offset = offset + processed_size; + while (remaining > 0) { + /* Determine data we're writing and where. */ + const size_t write_size = use_work_buffer ? std::min(pooled_buffer.GetSize(), remaining) : remaining; + + /* Encrypt the data, with temporarily increased priority. */ + { + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + size_t remaining_write = write_size; + size_t encrypt_offset = 0; + while (remaining_write > 0) { + const size_t cur_size = std::min(remaining_write, m_block_size); + const void *src = static_cast<const char *>(buffer) + processed_size + encrypt_offset; + void *dst = use_work_buffer ? pooled_buffer.GetBuffer() + encrypt_offset : const_cast<void *>(src); + + R_TRY(m_encrypt_function(dst, cur_size, m_key[0], m_key[1], KeySize, ctr, IvSize, src, cur_size)); + + AddCounter(ctr, IvSize, 1); + + encrypt_offset += cur_size; + remaining_write -= cur_size; + } + } + + /* Write the encrypted data. */ + const void *write_buf = use_work_buffer ? pooled_buffer.GetBuffer() : static_cast<const char *>(buffer) + processed_size; + R_TRY(m_base_storage->Write(cur_offset, write_buf, write_size)); + + /* Advance. */ + cur_offset += write_size; + processed_size += write_size; + remaining -= write_size; + } + + R_SUCCEED(); + } + + template<fs::PointerToStorage BasePointer> + Result AesXtsStorageExternal<BasePointer>::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + /* Unless invalidating cache, check the arguments. */ + if (op_id != fs::OperationId::Invalidate) { + /* Handle the zero size case. */ + R_SUCCEED_IF(size == 0); + + /* Ensure alignment. */ + R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument()); + } + + R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + } + + template<fs::PointerToStorage BasePointer> + Result AesXtsStorageExternal<BasePointer>::GetSize(s64 *out) { + R_RETURN(m_base_storage->GetSize(out)); + } + + template<fs::PointerToStorage BasePointer> + Result AesXtsStorageExternal<BasePointer>::Flush() { + R_RETURN(m_base_storage->Flush()); + } + + template<fs::PointerToStorage BasePointer> + Result AesXtsStorageExternal<BasePointer>::SetSize(s64 size) { + R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultUnexpectedInAesXtsStorageA()); + + R_RETURN(m_base_storage->SetSize(size)); + } + + template class AesXtsStorageExternal<fs::IStorage *>; + template class AesXtsStorageExternal<std::shared_ptr<fs::IStorage>>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_alignment_matching_storage_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_alignment_matching_storage_impl.cpp new file mode 100644 index 00000000..61f72e0a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_alignment_matching_storage_impl.cpp @@ -0,0 +1,261 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + namespace { + + template<typename T> + constexpr ALWAYS_INLINE size_t GetRoundDownDifference(T x, size_t align) { + return static_cast<size_t>(x - util::AlignDown(x, align)); + } + + template<typename T> + constexpr ALWAYS_INLINE size_t GetRoundUpDifference(T x, size_t align) { + return static_cast<size_t>(util::AlignUp(x, align) - x); + } + + template<typename T> + ALWAYS_INLINE size_t GetRoundUpDifference(T *x, size_t align) { + return GetRoundUpDifference(reinterpret_cast<uintptr_t>(x), align); + } + + } + + Result AlignmentMatchingStorageImpl::Read(fs::IStorage *base_storage, char *work_buf, size_t work_buf_size, size_t data_alignment, size_t buffer_alignment, s64 offset, char *buffer, size_t size) { + /* Check preconditions. */ + AMS_ASSERT(work_buf_size >= data_alignment); + AMS_UNUSED(work_buf_size); + + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Determine extents. */ + char *aligned_core_buffer; + s64 core_offset; + size_t core_size; + size_t buffer_gap; + size_t offset_gap; + s64 covered_offset; + + const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment); + if (util::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference, buffer_alignment)) { + aligned_core_buffer = buffer + offset_round_up_difference; + + core_offset = util::AlignUp(offset, data_alignment); + core_size = (size < offset_round_up_difference) ? 0 : util::AlignDown(size - offset_round_up_difference, data_alignment); + buffer_gap = 0; + offset_gap = 0; + + covered_offset = core_size > 0 ? core_offset : offset; + } else { + const size_t buffer_round_up_difference = GetRoundUpDifference(buffer, buffer_alignment); + + aligned_core_buffer = buffer + buffer_round_up_difference; + + core_offset = util::AlignDown(offset, data_alignment); + core_size = (size < buffer_round_up_difference) ? 0 : util::AlignDown(size - buffer_round_up_difference, data_alignment); + buffer_gap = buffer_round_up_difference; + offset_gap = GetRoundDownDifference(offset, data_alignment); + + covered_offset = offset; + } + + /* Read the core portion. */ + if (core_size > 0) { + R_TRY(base_storage->Read(core_offset, aligned_core_buffer, core_size)); + + if (offset_gap != 0 || buffer_gap != 0) { + std::memmove(aligned_core_buffer - buffer_gap, aligned_core_buffer + offset_gap, core_size - offset_gap); + core_size -= offset_gap; + } + } + + /* Handle the head portion. */ + if (offset < covered_offset) { + const s64 head_offset = util::AlignDown(offset, data_alignment); + const size_t head_size = static_cast<size_t>(covered_offset - offset); + + AMS_ASSERT(GetRoundDownDifference(offset, data_alignment) + head_size <= work_buf_size); + + R_TRY(base_storage->Read(head_offset, work_buf, data_alignment)); + std::memcpy(buffer, work_buf + GetRoundDownDifference(offset, data_alignment), head_size); + } + + /* Handle the tail portion. */ + s64 tail_offset = covered_offset + core_size; + size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset); + while (remaining_tail_size > 0) { + const auto aligned_tail_offset = util::AlignDown(tail_offset, data_alignment); + const auto cur_size = std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset), remaining_tail_size); + R_TRY(base_storage->Read(aligned_tail_offset, work_buf, data_alignment)); + + AMS_ASSERT((tail_offset - offset) + cur_size <= size); + AMS_ASSERT((tail_offset - aligned_tail_offset) + cur_size <= data_alignment); + std::memcpy(static_cast<char *>(buffer) + (tail_offset - offset), work_buf + (tail_offset - aligned_tail_offset), cur_size); + + remaining_tail_size -= cur_size; + tail_offset += cur_size; + } + + R_SUCCEED(); + } + + Result AlignmentMatchingStorageImpl::Write(fs::IStorage *base_storage, char *work_buf, size_t work_buf_size, size_t data_alignment, size_t buffer_alignment, s64 offset, const char *buffer, size_t size) { + /* Check preconditions. */ + AMS_ASSERT(work_buf_size >= data_alignment); + AMS_UNUSED(work_buf_size); + + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Determine extents. */ + const char *aligned_core_buffer; + s64 core_offset; + size_t core_size; + s64 covered_offset; + + const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment); + if (util::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference, buffer_alignment)) { + aligned_core_buffer = buffer + offset_round_up_difference; + + core_offset = util::AlignUp(offset, data_alignment); + core_size = (size < offset_round_up_difference) ? 0 : util::AlignDown(size - offset_round_up_difference, data_alignment); + + covered_offset = core_size > 0 ? core_offset : offset; + } else { + aligned_core_buffer = nullptr; + + core_offset = util::AlignDown(offset, data_alignment); + core_size = 0; + + covered_offset = offset; + } + + /* Write the core portion. */ + if (core_size > 0) { + R_TRY(base_storage->Write(core_offset, aligned_core_buffer, core_size)); + } + + /* Handle the head portion. */ + if (offset < covered_offset) { + const s64 head_offset = util::AlignDown(offset, data_alignment); + const size_t head_size = static_cast<size_t>(covered_offset - offset); + + AMS_ASSERT((offset - head_offset) + head_size <= data_alignment); + + R_TRY(base_storage->Read(head_offset, work_buf, data_alignment)); + std::memcpy(work_buf + (offset - head_offset), buffer, head_size); + R_TRY(base_storage->Write(head_offset, work_buf, data_alignment)); + } + + /* Handle the tail portion. */ + s64 tail_offset = covered_offset + core_size; + size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset); + while (remaining_tail_size > 0) { + AMS_ASSERT(static_cast<size_t>(tail_offset - offset) < size); + + const auto aligned_tail_offset = util::AlignDown(tail_offset, data_alignment); + const auto cur_size = std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset), remaining_tail_size); + + R_TRY(base_storage->Read(aligned_tail_offset, work_buf, data_alignment)); + std::memcpy(work_buf + GetRoundDownDifference(tail_offset, data_alignment), buffer + (tail_offset - offset), cur_size); + R_TRY(base_storage->Write(aligned_tail_offset, work_buf, data_alignment)); + + remaining_tail_size -= cur_size; + tail_offset += cur_size; + } + + R_SUCCEED(); + } + + template<> + Result AlignmentMatchingStorageInBulkRead<1>::Read(s64 offset, void *buffer, size_t size) { + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + s64 bs_size = 0; + R_TRY(this->GetSize(std::addressof(bs_size))); + R_TRY(fs::IStorage::CheckAccessRange(offset, size, bs_size)); + + /* Determine extents. */ + const auto offset_end = offset + static_cast<s64>(size); + const auto aligned_offset = util::AlignDown(offset, m_data_align); + const auto aligned_offset_end = util::AlignUp(offset_end, m_data_align); + const auto aligned_size = static_cast<size_t>(aligned_offset_end - aligned_offset); + + /* If we aren't aligned, we need to allocate a buffer. */ + PooledBuffer pooled_buffer; + if (aligned_offset != offset || aligned_size != size) { + if (aligned_size <= pooled_buffer.GetAllocatableSizeMax()) { + pooled_buffer.Allocate(aligned_size, m_data_align); + + if (aligned_size <= pooled_buffer.GetSize()) { + R_TRY(m_base_storage->Read(aligned_offset, pooled_buffer.GetBuffer(), aligned_size)); + std::memcpy(buffer, pooled_buffer.GetBuffer() + (offset - aligned_offset), size); + R_SUCCEED(); + } else { + pooled_buffer.Shrink(m_data_align); + } + } else { + pooled_buffer.Allocate(m_data_align, m_data_align); + } + + AMS_ASSERT(pooled_buffer.GetSize() >= static_cast<size_t>(m_data_align)); + } + + /* Determine read extents for the aligned portion. */ + const auto core_offset = util::AlignUp(offset, m_data_align); + const auto core_offset_end = util::AlignDown(offset_end, m_data_align); + + /* Handle any data before the aligned portion. */ + if (offset < core_offset) { + const auto head_size = static_cast<size_t>(core_offset - offset); + AMS_ASSERT(head_size < size); + R_TRY(m_base_storage->Read(aligned_offset, pooled_buffer.GetBuffer(), m_data_align)); + std::memcpy(buffer, pooled_buffer.GetBuffer() + (offset - aligned_offset), head_size); + } + + /* Handle the aligned portion. */ + if (core_offset < core_offset_end) { + const auto core_buffer = static_cast<char *>(buffer) + (core_offset - offset); + const auto core_size = static_cast<size_t>(core_offset_end - core_offset); + + R_TRY(m_base_storage->Read(core_offset, core_buffer, core_size)); + } + + /* Handle any data after the aligned portion. */ + if (core_offset_end < offset_end) { + const auto tail_buffer = static_cast<char *>(buffer) + (core_offset_end - offset); + const auto tail_size = static_cast<size_t>(offset_end - core_offset_end); + R_TRY(m_base_storage->Read(core_offset_end, pooled_buffer.GetBuffer(), m_data_align)); + std::memcpy(tail_buffer, pooled_buffer.GetBuffer(), tail_size); + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_allocator_utility.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_allocator_utility.cpp new file mode 100644 index 00000000..bb9ca6f2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_allocator_utility.cpp @@ -0,0 +1,154 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + namespace { + + constexpr bool UseDefaultAllocators = false; + + constinit bool g_used_default_allocator = false; + + void *DefaultAllocate(size_t size) { + g_used_default_allocator = true; + return ams::Malloc(size); + } + + void DefaultDeallocate(void *ptr, size_t size) { + AMS_UNUSED(size); + ams::Free(ptr); + } + + constinit os::SdkMutex g_allocate_mutex; + constinit os::SdkMutex g_allocate_mutex_for_system; + + constinit AllocateFunction g_allocate_func = UseDefaultAllocators ? DefaultAllocate : nullptr; + constinit DeallocateFunction g_deallocate_func = UseDefaultAllocators ? DefaultDeallocate : nullptr; + + constinit AllocateFunction g_allocate_func_for_system = nullptr; + constinit DeallocateFunction g_deallocate_func_for_system = nullptr; + + void *AllocateUnsafe(size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(g_allocate_mutex.IsLockedByCurrentThread()); + AMS_ASSERT(g_allocate_func != nullptr); + + /* Allocate memory. */ + return g_allocate_func(size); + } + + void DeallocateUnsafe(void *ptr, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(g_allocate_mutex.IsLockedByCurrentThread()); + AMS_ASSERT(g_deallocate_func != nullptr); + + /* Deallocate the pointer. */ + g_deallocate_func(ptr, size); + } + + + } + + namespace impl { + + /* Normal allocator set. */ + template<> void *AllocatorFunctionSet<false>::Allocate(size_t size) { return ::ams::fssystem::Allocate(size); } + template<> void *AllocatorFunctionSet<false>::AllocateUnsafe(size_t size) { return ::ams::fssystem::AllocateUnsafe(size); } + + template<> void AllocatorFunctionSet<false>::Deallocate(void *ptr, size_t size) { return ::ams::fssystem::Deallocate(ptr, size); } + template<> void AllocatorFunctionSet<false>::DeallocateUnsafe(void *ptr, size_t size) { return ::ams::fssystem::DeallocateUnsafe(ptr, size); } + + template<> void AllocatorFunctionSet<false>::LockAllocatorMutex() { ::ams::fssystem::g_allocate_mutex.Lock(); } + template<> void AllocatorFunctionSet<false>::UnlockAllocatorMutex() { ::ams::fssystem::g_allocate_mutex.Unlock(); } + + /* System allocator set. */ + template<> void *AllocatorFunctionSet<true>::AllocateUnsafe(size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(::ams::fssystem::g_allocate_mutex_for_system.IsLockedByCurrentThread()); + AMS_ASSERT(::ams::fssystem::g_allocate_func_for_system != nullptr); + + /* Allocate memory. */ + return g_allocate_func_for_system(size); + } + + template<> void AllocatorFunctionSet<true>::DeallocateUnsafe(void *ptr, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(::ams::fssystem::g_allocate_mutex_for_system.IsLockedByCurrentThread()); + AMS_ASSERT(::ams::fssystem::g_deallocate_func_for_system != nullptr); + + /* Deallocate the pointer. */ + ::ams::fssystem::g_deallocate_func_for_system(ptr, size); + } + + template<> void *AllocatorFunctionSet<true>::Allocate(size_t size) { + std::scoped_lock lk(::ams::fssystem::g_allocate_mutex_for_system); + return ::ams::fssystem::impl::AllocatorFunctionSet<true>::AllocateUnsafe(size); + } + + template<> void AllocatorFunctionSet<true>::Deallocate(void *ptr, size_t size) { + std::scoped_lock lk(::ams::fssystem::g_allocate_mutex_for_system); + return ::ams::fssystem::impl::AllocatorFunctionSet<true>::DeallocateUnsafe(ptr, size); + } + + template<> void AllocatorFunctionSet<true>::LockAllocatorMutex() { ::ams::fssystem::g_allocate_mutex_for_system.Lock(); } + template<> void AllocatorFunctionSet<true>::UnlockAllocatorMutex() { ::ams::fssystem::g_allocate_mutex_for_system.Unlock(); } + + } + + void *Allocate(size_t size) { + std::scoped_lock lk(g_allocate_mutex); + return AllocateUnsafe(size); + } + + void Deallocate(void *ptr, size_t size) { + std::scoped_lock lk(g_allocate_mutex); + return DeallocateUnsafe(ptr, size); + } + + void InitializeAllocator(AllocateFunction allocate_func, DeallocateFunction deallocate_func) { + /* Check pre-conditions. */ + AMS_ASSERT(allocate_func != nullptr); + AMS_ASSERT(deallocate_func != nullptr); + + /* Check that we can initialize. */ + if constexpr (UseDefaultAllocators) { + AMS_ASSERT(g_used_default_allocator == false); + } else { + AMS_ASSERT(g_allocate_func == nullptr); + AMS_ASSERT(g_deallocate_func == nullptr); + } + + /* Set the allocator functions. */ + g_allocate_func = allocate_func; + g_deallocate_func = deallocate_func; + } + + void InitializeAllocatorForSystem(AllocateFunction allocate_func, DeallocateFunction deallocate_func) { + /* Check pre-conditions. */ + AMS_ASSERT(allocate_func != nullptr); + AMS_ASSERT(deallocate_func != nullptr); + + /* Check that we can initialize. */ + AMS_ASSERT(g_allocate_func_for_system == nullptr); + AMS_ASSERT(g_deallocate_func_for_system == nullptr); + + /* Set the system allocator functions. */ + g_allocate_func_for_system = allocate_func; + g_deallocate_func_for_system = deallocate_func; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_block_cache_buffered_storage.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_block_cache_buffered_storage.cpp new file mode 100644 index 00000000..90654ad0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_block_cache_buffered_storage.cpp @@ -0,0 +1,1067 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + BlockCacheBufferedStorage::BlockCacheBufferedStorage() : m_mutex(), m_data_storage(), m_last_result(ResultSuccess()), m_data_size(), m_verification_block_size(), m_verification_block_shift(), m_flags(), m_buffer_level(-1), m_block_cache_manager() { + /* ... */ + } + + BlockCacheBufferedStorage::~BlockCacheBufferedStorage() { + this->Finalize(); + } + + Result BlockCacheBufferedStorage::Initialize(fs::IBufferManager *bm, os::SdkRecursiveMutex *mtx, IStorage *data, s64 data_size, size_t verif_block_size, s32 max_cache_entries, bool is_real_data, s8 buffer_level, bool is_keep_burst_mode, bool is_writable) { + /* Validate preconditions. */ + AMS_ASSERT(data != nullptr); + AMS_ASSERT(bm != nullptr); + AMS_ASSERT(mtx != nullptr); + AMS_ASSERT(m_mutex == nullptr); + AMS_ASSERT(m_data_storage == nullptr); + AMS_ASSERT(max_cache_entries > 0); + + /* Initialize our manager. */ + R_TRY(m_block_cache_manager.Initialize(bm, max_cache_entries)); + + /* Set members. */ + m_mutex = mtx; + m_data_storage = data; + m_data_size = data_size; + m_verification_block_size = verif_block_size; + m_last_result = ResultSuccess(); + m_flags = 0; + m_buffer_level = buffer_level; + m_is_writable = is_writable; + + /* Calculate block shift. */ + m_verification_block_shift = ILog2(static_cast<u32>(verif_block_size)); + AMS_ASSERT(static_cast<size_t>(UINT64_C(1) << m_verification_block_shift) == m_verification_block_size); + + /* Set burst mode. */ + this->SetKeepBurstMode(is_keep_burst_mode); + + /* Set real data cache. */ + this->SetRealDataCache(is_real_data); + + R_SUCCEED(); + } + + void BlockCacheBufferedStorage::Finalize() { + if (m_block_cache_manager.IsInitialized()) { + /* Invalidate all cache entries. */ + this->InvalidateAllCacheEntries(); + + /* Finalize our block cache manager. */ + m_block_cache_manager.Finalize(); + + /* Clear members. */ + m_mutex = nullptr; + m_data_storage = nullptr; + m_data_size = 0; + m_verification_block_size = 0; + m_verification_block_shift = 0; + } + } + + Result BlockCacheBufferedStorage::Read(s64 offset, void *buffer, size_t size) { + /* Validate pre-conditions. */ + AMS_ASSERT(m_data_storage != nullptr); + AMS_ASSERT(m_block_cache_manager.IsInitialized()); + + /* Ensure we aren't already in a failed state. */ + R_TRY(m_last_result); + + /* Succeed if zero-size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Determine the extents to read. */ + s64 read_offset = offset; + size_t read_size = size; + + R_UNLESS(read_offset < m_data_size, fs::ResultInvalidOffset()); + + if (static_cast<s64>(read_offset + read_size) > m_data_size) { + read_size = static_cast<size_t>(m_data_size - read_offset); + } + + /* Determine the aligned range to read. */ + const size_t block_alignment = m_verification_block_size; + s64 aligned_offset = util::AlignDown(read_offset, block_alignment); + s64 aligned_offset_end = util::AlignUp(read_offset + read_size, block_alignment); + + AMS_ASSERT(0 <= aligned_offset && aligned_offset_end <= static_cast<s64>(util::AlignUp(m_data_size, block_alignment))); + + /* Try to read using cache. */ + char *dst = static_cast<char *>(buffer); + { + /* Determine if we can do bulk reads. */ + constexpr s64 BulkReadSizeMax = 2_MB; + const bool bulk_read_enabled = (read_offset != aligned_offset || static_cast<s64>(read_offset + read_size) != aligned_offset_end) && aligned_offset_end - aligned_offset <= BulkReadSizeMax; + + /* Read the head cache. */ + CacheEntry head_entry = {}; + MemoryRange head_range = {}; + bool head_cache_needed = true; + R_TRY(this->ReadHeadCache(std::addressof(head_range), std::addressof(head_entry), std::addressof(head_cache_needed), std::addressof(read_offset), std::addressof(aligned_offset), aligned_offset_end, std::addressof(dst), std::addressof(read_size))); + + /* We may be done after reading the head cache, so check if we are. */ + R_SUCCEED_IF(aligned_offset >= aligned_offset_end); + + /* Ensure we destroy the head buffer. */ + auto head_guard = SCOPE_GUARD { m_block_cache_manager.ReleaseCacheEntry(std::addressof(head_entry), head_range); }; + + /* Read the tail cache. */ + CacheEntry tail_entry = {}; + MemoryRange tail_range = {}; + bool tail_cache_needed = true; + R_TRY(this->ReadTailCache(std::addressof(tail_range), std::addressof(tail_entry), std::addressof(tail_cache_needed), read_offset, aligned_offset, std::addressof(aligned_offset_end), dst, std::addressof(read_size))); + + /* We may be done after reading the tail cache, so check if we are. */ + R_SUCCEED_IF(aligned_offset >= aligned_offset_end); + + /* Ensure that we destroy the tail buffer. */ + auto tail_guard = SCOPE_GUARD { m_block_cache_manager.ReleaseCacheEntry(std::addressof(tail_entry), tail_range); }; + + /* Try to do a bulk read. */ + if (bulk_read_enabled) { + /* The bulk read will destroy our head/tail buffers. */ + head_guard.Cancel(); + tail_guard.Cancel(); + + do { + /* Do the bulk read. If we fail due to pooled buffer allocation failing, fall back to the normal read codepath. */ + R_TRY_CATCH(this->BulkRead(read_offset, dst, read_size, std::addressof(head_range), std::addressof(tail_range), std::addressof(head_entry), std::addressof(tail_entry), head_cache_needed, tail_cache_needed)) { + R_CATCH(fs::ResultAllocationPooledBufferNotEnoughSize) { break; } + } R_END_TRY_CATCH; + + /* Se successfully did a bulk read, so we're done. */ + R_SUCCEED(); + } while (0); + } + } + + /* Read the data using non-bulk reads. */ + while (aligned_offset < aligned_offset_end) { + /* Ensure that there is data for us to read. */ + AMS_ASSERT(read_size > 0); + + /* If conditions allow us to, read in burst mode. This doesn't use the cache. */ + if (this->IsEnabledKeepBurstMode() && read_offset == aligned_offset && (block_alignment * 2 <= read_size)) { + const size_t aligned_size = util::AlignDown(read_size, block_alignment); + + /* Flush the entries. */ + R_TRY(this->UpdateLastResult(this->FlushRangeCacheEntries(read_offset, aligned_size, false))); + + /* Read the data. */ + R_TRY(this->UpdateLastResult(m_data_storage->Read(read_offset, dst, aligned_size))); + + /* Advance. */ + dst += aligned_size; + read_offset += aligned_size; + read_size -= aligned_size; + aligned_offset += aligned_size; + } else { + /* Get the buffer associated with what we're reading. */ + CacheEntry entry; + MemoryRange range; + R_TRY(this->UpdateLastResult(this->GetAssociateBuffer(std::addressof(range), std::addressof(entry), aligned_offset, static_cast<size_t>(aligned_offset_end - aligned_offset), true))); + + /* Determine where to read data into, and ensure that our entry is aligned. */ + char *src = reinterpret_cast<char *>(range.first); + AMS_ASSERT(util::IsAligned(entry.range.size, block_alignment)); + + /* If the entry isn't cached, read the data. */ + if (!entry.is_cached) { + if (const Result result = m_data_storage->Read(entry.range.offset, src, entry.range.size); R_FAILED(result)) { + m_block_cache_manager.ReleaseCacheEntry(std::addressof(entry), range); + R_RETURN(this->UpdateLastResult(result)); + } + entry.is_cached = true; + } + + /* Validate the entry extents. */ + AMS_ASSERT(static_cast<s64>(entry.range.offset) <= aligned_offset); + AMS_ASSERT(aligned_offset < entry.range.GetEndOffset()); + AMS_ASSERT(aligned_offset <= read_offset); + + /* Copy the data. */ + { + /* Determine where and how much to copy. */ + const s64 buffer_offset = read_offset - entry.range.offset; + const size_t copy_size = std::min(read_size, static_cast<size_t>(entry.range.GetEndOffset() - read_offset)); + + /* Actually copy the data. */ + std::memcpy(dst, src + buffer_offset, copy_size); + + /* Advance. */ + dst += copy_size; + read_offset += copy_size; + read_size -= copy_size; + } + + /* Release the cache entry. */ + R_TRY(this->UpdateLastResult(this->StoreOrDestroyBuffer(range, std::addressof(entry)))); + aligned_offset = entry.range.GetEndOffset(); + } + } + + /* Ensure that we read all the data. */ + AMS_ASSERT(read_size == 0); + + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::Write(s64 offset, const void *buffer, size_t size) { + /* Validate pre-conditions. */ + AMS_ASSERT(m_data_storage != nullptr); + AMS_ASSERT(m_block_cache_manager.IsInitialized()); + + /* Ensure we aren't already in a failed state. */ + R_TRY(m_last_result); + + /* Succeed if zero-size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Determine the extents to read. */ + R_UNLESS(offset < m_data_size, fs::ResultInvalidOffset()); + + if (static_cast<s64>(offset + size) > m_data_size) { + size = static_cast<size_t>(m_data_size - offset); + } + + /* The actual extents may be zero-size, so succeed if that's the case. */ + R_SUCCEED_IF(size == 0); + + /* Determine the aligned range to read. */ + const size_t block_alignment = m_verification_block_size; + s64 aligned_offset = util::AlignDown(offset, block_alignment); + const s64 aligned_offset_end = util::AlignUp(offset + size, block_alignment); + + AMS_ASSERT(0 <= aligned_offset && aligned_offset_end <= static_cast<s64>(util::AlignUp(m_data_size, block_alignment))); + + /* Write the data. */ + const u8 *src = static_cast<const u8 *>(buffer); + while (aligned_offset < aligned_offset_end) { + /* If conditions allow us to, write in burst mode. This doesn't use the cache. */ + if (this->IsEnabledKeepBurstMode() && offset == aligned_offset && (block_alignment * 2 <= size)) { + const size_t aligned_size = util::AlignDown(size, block_alignment); + + /* Flush the entries. */ + R_TRY(this->UpdateLastResult(this->FlushRangeCacheEntries(offset, aligned_size, true))); + + /* Read the data. */ + R_TRY(this->UpdateLastResult(m_data_storage->Write(offset, src, aligned_size))); + + /* Set blocking buffer manager allocations. */ + buffers::EnableBlockingBufferManagerAllocation(); + + /* Advance. */ + src += aligned_size; + offset += aligned_size; + size -= aligned_size; + aligned_offset += aligned_size; + } else { + /* Get the buffer associated with what we're writing. */ + CacheEntry entry; + MemoryRange range; + R_TRY(this->UpdateLastResult(this->GetAssociateBuffer(std::addressof(range), std::addressof(entry), aligned_offset, static_cast<size_t>(aligned_offset_end - aligned_offset), true))); + + /* Determine where to write data into. */ + char *dst = reinterpret_cast<char *>(range.first); + + /* If the entry isn't cached and we're writing a partial entry, read in the entry. */ + if (!entry.is_cached && ((offset != entry.range.offset) || (offset + size < static_cast<size_t>(entry.range.GetEndOffset())))) { + if (Result result = m_data_storage->Read(entry.range.offset, dst, entry.range.size); R_FAILED(result)) { + m_block_cache_manager.ReleaseCacheEntry(std::addressof(entry), range); + R_RETURN(this->UpdateLastResult(result)); + } + } + entry.is_cached = true; + + /* Validate the entry extents. */ + AMS_ASSERT(static_cast<s64>(entry.range.offset) <= aligned_offset); + AMS_ASSERT(aligned_offset < entry.range.GetEndOffset()); + AMS_ASSERT(aligned_offset <= offset); + + /* Copy the data. */ + { + /* Determine where and how much to copy. */ + const s64 buffer_offset = offset - entry.range.offset; + const size_t copy_size = std::min(size, static_cast<size_t>(entry.range.GetEndOffset() - offset)); + + /* Actually copy the data. */ + std::memcpy(dst + buffer_offset, src, copy_size); + + /* Advance. */ + src += copy_size; + offset += copy_size; + size -= copy_size; + } + + /* Set the entry as write-back. */ + entry.is_write_back = true; + + /* Set blocking buffer manager allocations. */ + buffers::EnableBlockingBufferManagerAllocation(); + + /* Store the associated buffer. */ + CacheIndex index; + R_TRY(this->UpdateLastResult(this->StoreOrDestroyBuffer(std::addressof(index), range, std::addressof(entry)))); + + /* Set the after aligned offset. */ + aligned_offset = entry.range.GetEndOffset(); + + /* If we need to, flush the cache entry. */ + if (index >= 0 && IsEnabledKeepBurstMode() && offset == aligned_offset && (block_alignment * 2 <= size)) { + R_TRY(this->UpdateLastResult(this->FlushCacheEntry(index, false))); + } + + } + } + + /* Ensure that didn't end up in a failure state. */ + R_TRY(m_last_result); + + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::GetSize(s64 *out) { + /* Validate pre-conditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(m_data_storage != nullptr); + + /* Set the size. */ + *out = m_data_size; + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::Flush() { + /* Validate pre-conditions. */ + AMS_ASSERT(m_data_storage != nullptr); + AMS_ASSERT(m_block_cache_manager.IsInitialized()); + + /* Ensure we aren't already in a failed state. */ + R_TRY(m_last_result); + + /* Flush all cache entries. */ + R_TRY(this->UpdateLastResult(this->FlushAllCacheEntries())); + + /* Flush the data storage. */ + R_TRY(this->UpdateLastResult(m_data_storage->Flush())); + + /* Set blocking buffer manager allocations. */ + buffers::EnableBlockingBufferManagerAllocation(); + + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + AMS_UNUSED(src, src_size); + + /* Validate pre-conditions. */ + AMS_ASSERT(m_data_storage != nullptr); + + switch (op_id) { + case fs::OperationId::FillZero: + { + R_RETURN(this->FillZeroImpl(offset, size)); + } + case fs::OperationId::DestroySignature: + { + R_RETURN(this->DestroySignatureImpl(offset, size)); + } + case fs::OperationId::Invalidate: + { + R_UNLESS(!m_is_writable, fs::ResultUnsupportedOperateRangeForWritableBlockCacheBufferedStorage()); + R_RETURN(this->InvalidateImpl()); + } + case fs::OperationId::QueryRange: + { + R_RETURN(this->QueryRangeImpl(dst, dst_size, offset, size)); + } + default: + R_THROW(fs::ResultUnsupportedOperateRangeForBlockCacheBufferedStorage()); + } + } + + Result BlockCacheBufferedStorage::Commit() { + /* Validate pre-conditions. */ + AMS_ASSERT(m_data_storage != nullptr); + AMS_ASSERT(m_block_cache_manager.IsInitialized()); + + /* Ensure we aren't already in a failed state. */ + R_TRY(m_last_result); + + /* Flush all cache entries. */ + R_TRY(this->UpdateLastResult(this->FlushAllCacheEntries())); + + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::OnRollback() { + /* Validate pre-conditions. */ + AMS_ASSERT(m_block_cache_manager.IsInitialized()); + + /* Ensure we aren't already in a failed state. */ + R_TRY(m_last_result); + + /* Release all valid entries back to the buffer manager. */ + const auto max_cache_entry_count = m_block_cache_manager.GetCount(); + for (auto index = 0; index < max_cache_entry_count; index++) { + if (const auto &entry = m_block_cache_manager[index]; entry.is_valid) { + m_block_cache_manager.InvalidateCacheEntry(index); + } + } + + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::FillZeroImpl(s64 offset, s64 size) { + /* Ensure we aren't already in a failed state. */ + R_TRY(m_last_result); + + /* Get our storage size. */ + s64 storage_size = 0; + R_TRY(this->UpdateLastResult(m_data_storage->GetSize(std::addressof(storage_size)))); + + /* Check the access range. */ + R_UNLESS(0 <= offset && offset < storage_size, fs::ResultInvalidOffset()); + + /* Determine the extents to data signature for. */ + auto start_offset = util::AlignDown(offset, m_verification_block_size); + auto end_offset = util::AlignUp(std::min(offset + size, storage_size), m_verification_block_size); + + /* Flush the entries. */ + R_TRY(this->UpdateLastResult(this->FlushRangeCacheEntries(offset, size, true))); + + /* Handle any data before or after the aligned range. */ + if (start_offset < offset || offset + size < end_offset) { + /* Allocate a work buffer. */ + std::unique_ptr<char[], fs::impl::Deleter> work = fs::impl::MakeUnique<char[]>(m_verification_block_size); + R_UNLESS(work != nullptr, fs::ResultAllocationMemoryFailedInBlockCacheBufferedStorageB()); + + /* Handle data before the aligned range. */ + if (start_offset < offset) { + /* Read the block. */ + R_TRY(this->UpdateLastResult(m_data_storage->Read(start_offset, work.get(), m_verification_block_size))); + + /* Determine the partial extents to clear. */ + const auto clear_offset = static_cast<size_t>(offset - start_offset); + const auto clear_size = static_cast<size_t>(std::min(static_cast<s64>(m_verification_block_size - clear_offset), size)); + + /* Clear the partial block. */ + std::memset(work.get() + clear_offset, 0, clear_size); + + /* Write the partially cleared block. */ + R_TRY(this->UpdateLastResult(m_data_storage->Write(start_offset, work.get(), m_verification_block_size))); + + /* Update the start offset. */ + start_offset += m_verification_block_size; + + /* Set blocking buffer manager allocations. */ + buffers::EnableBlockingBufferManagerAllocation(); + } + + /* Handle data after the aligned range. */ + if (start_offset < offset + size && offset + size < end_offset) { + /* Read the block. */ + const auto last_offset = end_offset - m_verification_block_size; + R_TRY(this->UpdateLastResult(m_data_storage->Read(last_offset, work.get(), m_verification_block_size))); + + /* Clear the partial block. */ + const auto clear_size = static_cast<size_t>((offset + size) - last_offset); + std::memset(work.get(), 0, clear_size); + + /* Write the partially cleared block. */ + R_TRY(this->UpdateLastResult(m_data_storage->Write(last_offset, work.get(), m_verification_block_size))); + + /* Update the end offset. */ + end_offset -= m_verification_block_size; + + /* Set blocking buffer manager allocations. */ + buffers::EnableBlockingBufferManagerAllocation(); + } + } + + /* We're done if there's no data to clear. */ + R_SUCCEED_IF(start_offset == end_offset); + + /* Clear the signature for the aligned range. */ + R_TRY(this->UpdateLastResult(m_data_storage->OperateRange(fs::OperationId::FillZero, start_offset, end_offset - start_offset))); + + /* Set blocking buffer manager allocations. */ + buffers::EnableBlockingBufferManagerAllocation(); + + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::DestroySignatureImpl(s64 offset, s64 size) { + /* Ensure we aren't already in a failed state. */ + R_TRY(m_last_result); + + /* Get our storage size. */ + s64 storage_size = 0; + R_TRY(this->UpdateLastResult(m_data_storage->GetSize(std::addressof(storage_size)))); + + /* Check the access range. */ + R_UNLESS(0 <= offset && offset < storage_size, fs::ResultInvalidOffset()); + + /* Determine the extents to clear signature for. */ + const auto start_offset = util::AlignUp(offset, m_verification_block_size); + const auto end_offset = util::AlignDown(std::min(offset + size, storage_size), m_verification_block_size); + + /* Flush the entries. */ + R_TRY(this->UpdateLastResult(this->FlushRangeCacheEntries(offset, size, true))); + + /* Clear the signature for the aligned range. */ + R_TRY(this->UpdateLastResult(m_data_storage->OperateRange(fs::OperationId::DestroySignature, start_offset, end_offset - start_offset))); + + /* Set blocking buffer manager allocations. */ + buffers::EnableBlockingBufferManagerAllocation(); + + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::InvalidateImpl() { + /* Invalidate cache entries. */ + { + std::scoped_lock lk(*m_mutex); + + m_block_cache_manager.Invalidate(); + } + + /* Invalidate the aligned range. */ + { + Result result = m_data_storage->OperateRange(fs::OperationId::Invalidate, 0, std::numeric_limits<s64>::max()); + AMS_ASSERT(!fs::ResultBufferAllocationFailed::Includes(result)); + R_TRY(result); + } + + /* Clear our last result if we should. */ + if (fs::ResultIntegrityVerificationStorageCorrupted::Includes(m_last_result)) { + m_last_result = ResultSuccess(); + } + + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::QueryRangeImpl(void *dst, size_t dst_size, s64 offset, s64 size) { + /* Get our storage size. */ + s64 storage_size = 0; + R_TRY(this->GetSize(std::addressof(storage_size))); + + /* Determine the extents we can actually query. */ + const auto actual_size = std::min(size, storage_size - offset); + const auto aligned_offset = util::AlignDown(offset, m_verification_block_size); + const auto aligned_offset_end = util::AlignUp(offset + actual_size, m_verification_block_size); + const auto aligned_size = aligned_offset_end - aligned_offset; + + /* Query the aligned range. */ + R_TRY(this->UpdateLastResult(m_data_storage->OperateRange(dst, dst_size, fs::OperationId::QueryRange, aligned_offset, aligned_size, nullptr, 0))); + + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::GetAssociateBuffer(MemoryRange *out_range, CacheEntry *out_entry, s64 offset, size_t ideal_size, bool is_allocate_for_write) { + AMS_UNUSED(is_allocate_for_write); + + /* Validate pre-conditions. */ + AMS_ASSERT(m_data_storage != nullptr); + AMS_ASSERT(m_block_cache_manager.IsInitialized()); + AMS_ASSERT(out_range != nullptr); + AMS_ASSERT(out_entry != nullptr); + + /* Lock our mutex. */ + std::scoped_lock lk(*m_mutex); + + /* Get the maximum cache entry count. */ + const CacheIndex max_cache_entry_count = m_block_cache_manager.GetCount(); + + /* Locate the index of the cache entry, if present. */ + CacheIndex index; + size_t actual_size = ideal_size; + for (index = 0; index < max_cache_entry_count; ++index) { + if (const auto &entry = m_block_cache_manager[index]; entry.IsAllocated()) { + if (entry.range.IsIncluded(offset)) { + break; + } + + if (offset <= entry.range.offset && entry.range.offset < static_cast<s64>(offset + actual_size)) { + actual_size = static_cast<s64>(entry.range.offset - offset); + } + } + } + + /* Clear the out range. */ + out_range->first = 0; + out_range->second = 0; + + /* If we located an entry, use it. */ + if (index != max_cache_entry_count) { + m_block_cache_manager.AcquireCacheEntry(out_entry, out_range, index); + + actual_size = out_entry->range.size - (offset - out_entry->range.offset); + } + + /* If we don't have an out entry, allocate one. */ + if (out_range->first == 0) { + /* Ensure that the allocatable size is above a threshold. */ + const auto size_threshold = m_block_cache_manager.GetAllocator()->GetTotalSize() / 8; + if (m_block_cache_manager.GetAllocator()->GetTotalAllocatableSize() < size_threshold) { + R_TRY(this->FlushAllCacheEntries()); + } + + /* Decide in advance on a block alignment. */ + const size_t block_alignment = m_verification_block_size; + + /* Ensure that the size we request is valid. */ + { + AMS_ASSERT(actual_size >= 1); + actual_size = std::min(actual_size, block_alignment * 2); + } + AMS_ASSERT(actual_size >= block_alignment); + + /* Allocate a buffer. */ + R_TRY(buffers::AllocateBufferUsingBufferManagerContext(out_range, m_block_cache_manager.GetAllocator(), actual_size, fs::IBufferManager::BufferAttribute(m_buffer_level), [=](const MemoryRange &buffer) { + return buffer.first != 0 && block_alignment <= buffer.second; + }, AMS_CURRENT_FUNCTION_NAME)); + + /* Ensure our size is accurate. */ + actual_size = std::min(actual_size, out_range->second); + + /* Set the output entry. */ + out_entry->is_valid = true; + out_entry->is_write_back = false; + out_entry->is_cached = false; + out_entry->is_flushing = false; + out_entry->handle = 0; + out_entry->memory_address = 0; + out_entry->memory_size = 0; + out_entry->range.offset = offset; + out_entry->range.size = actual_size; + out_entry->lru_counter = 0; + } + + /* Check that we ended up with a coherent out range. */ + AMS_ASSERT(out_range->second >= out_entry->range.size); + + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::StoreOrDestroyBuffer(CacheIndex *out, const MemoryRange &range, CacheEntry *entry) { + /* Validate pre-conditions. */ + AMS_ASSERT(out != nullptr); + + /* Lock our mutex. */ + std::scoped_lock lk(*m_mutex); + + /* In the event that we fail, release our buffer. */ + ON_RESULT_FAILURE { m_block_cache_manager.ReleaseCacheEntry(entry, range); }; + + /* If the entry is write-back, ensure we don't exceed certain dirtiness thresholds. */ + if (entry->is_write_back) { + R_TRY(this->ControlDirtiness()); + } + + /* Get unused cache entry index. */ + CacheIndex empty_index, lru_index; + m_block_cache_manager.GetEmptyCacheEntryIndex(std::addressof(empty_index), std::addressof(lru_index)); + + /* If all entries are valid, we need to invalidate one. */ + if (empty_index == BlockCacheManager::InvalidCacheIndex) { + /* Invalidate the lease recently used entry. */ + empty_index = lru_index; + + /* Get the entry to invalidate, sanity check that we can invalidate it. */ + const CacheEntry &entry_to_invalidate = m_block_cache_manager[empty_index]; + AMS_ASSERT(entry_to_invalidate.is_valid); + AMS_ASSERT(!entry_to_invalidate.is_flushing); + AMS_UNUSED(entry_to_invalidate); + + /* Invalidate the entry. */ + R_TRY(this->FlushCacheEntry(empty_index, true)); + + /* Check that the entry was invalidated successfully. */ + AMS_ASSERT(!entry_to_invalidate.is_valid); + AMS_ASSERT(!entry_to_invalidate.is_flushing); + } + + /* Store the entry. */ + if (m_block_cache_manager.SetCacheEntry(empty_index, *entry, range, fs::IBufferManager::BufferAttribute(m_buffer_level))) { + *out = empty_index; + } else { + *out = BlockCacheManager::InvalidCacheIndex; + } + + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::FlushCacheEntry(CacheIndex index, bool invalidate) { + /* Lock our mutex. */ + std::scoped_lock lk(*m_mutex); + + /* Get the entry, sanity check that the entry's state allows for flush. */ + auto &entry = m_block_cache_manager[index]; + AMS_ASSERT(entry.is_valid); + AMS_ASSERT(!entry.is_flushing); + + /* If we're not write back (i.e. an invalidate is happening), just release the buffer. */ + if (!entry.is_write_back) { + AMS_ASSERT(invalidate); + + m_block_cache_manager.InvalidateCacheEntry(index); + + R_SUCCEED(); + } + + /* Note that we've started flushing, while we process. */ + m_block_cache_manager.SetFlushing(index, true); + ON_SCOPE_EXIT { m_block_cache_manager.SetFlushing(index, false); }; + + /* Create and check our memory range. */ + MemoryRange memory_range = fs::IBufferManager::MakeMemoryRange(entry.memory_address, entry.memory_size); + AMS_ASSERT(memory_range.first != 0); + AMS_ASSERT(memory_range.second >= entry.range.size); + + /* Validate the entry's offset. */ + AMS_ASSERT(entry.range.offset >= 0); + AMS_ASSERT(entry.range.offset < m_data_size); + AMS_ASSERT(util::IsAligned(entry.range.offset, m_verification_block_size)); + + /* Write back the data. */ + Result result = ResultSuccess(); + size_t write_size = entry.range.size; + if (R_SUCCEEDED(m_last_result)) { + /* Set blocking buffer manager allocations. */ + result = m_data_storage->Write(entry.range.offset, reinterpret_cast<const void *>(memory_range.first), write_size); + + /* Check the result. */ + AMS_ASSERT(!fs::ResultBufferAllocationFailed::Includes(result)); + } else { + result = m_last_result; + } + + /* Set that we're not write-back. */ + m_block_cache_manager.SetWriteBack(index, false); + + /* If we're invalidating, release the buffer. Otherwise, register the flushed data. */ + if (invalidate) { + m_block_cache_manager.ReleaseCacheEntry(index, memory_range); + } else { + AMS_ASSERT(entry.is_valid); + m_block_cache_manager.RegisterCacheEntry(index, memory_range, fs::IBufferManager::BufferAttribute(m_buffer_level)); + } + + /* Try to succeed. */ + R_TRY(result); + + /* We succeeded. */ + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::FlushRangeCacheEntries(s64 offset, s64 size, bool invalidate) { + /* Validate pre-conditions. */ + AMS_ASSERT(m_data_storage != nullptr); + AMS_ASSERT(m_block_cache_manager.IsInitialized()); + + /* Iterate over all entries that fall within the range. */ + Result result = ResultSuccess(); + const auto max_cache_entry_count = m_block_cache_manager.GetCount(); + for (auto i = 0; i < max_cache_entry_count; ++i) { + auto &entry = m_block_cache_manager[i]; + if (entry.is_valid && (entry.is_write_back || invalidate) && (entry.range.offset < (offset + size)) && (offset < entry.range.GetEndOffset())) { + const auto cur_result = this->FlushCacheEntry(i, invalidate); + if (R_FAILED(cur_result) && R_SUCCEEDED(result)) { + result = cur_result; + } + } + } + + /* Try to succeed. */ + R_TRY(result); + + /* We succeeded. */ + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::FlushAllCacheEntries() { + R_TRY(this->FlushRangeCacheEntries(0, std::numeric_limits<s64>::max(), false)); + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::InvalidateAllCacheEntries() { + R_TRY(this->FlushRangeCacheEntries(0, std::numeric_limits<s64>::max(), true)); + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::ControlDirtiness() { + /* Get and validate the max cache entry count. */ + const auto max_cache_entry_count = m_block_cache_manager.GetCount(); + AMS_ASSERT(max_cache_entry_count > 0); + + /* Get size metrics from the buffer manager. */ + const auto total_size = m_block_cache_manager.GetAllocator()->GetTotalSize(); + const auto allocatable_size = m_block_cache_manager.GetAllocator()->GetTotalAllocatableSize(); + + /* If we have enough allocatable space, we don't need to do anything. */ + R_SUCCEED_IF(allocatable_size >= total_size / 4); + + /* Iterate over all entries (up to the threshold) and flush the least recently used dirty entry. */ + constexpr auto Threshold = 2; + for (int n = 0; n < Threshold; ++n) { + auto flushed_index = BlockCacheManager::InvalidCacheIndex; + for (auto index = 0; index < max_cache_entry_count; ++index) { + if (auto &entry = m_block_cache_manager[index]; entry.is_valid && entry.is_write_back) { + if (flushed_index == BlockCacheManager::InvalidCacheIndex || m_block_cache_manager[flushed_index].lru_counter < entry.lru_counter) { + flushed_index = index; + } + } + } + + /* If we can't flush anything, break. */ + if (flushed_index == BlockCacheManager::InvalidCacheIndex) { + break; + } + + R_TRY(this->FlushCacheEntry(flushed_index, false)); + } + + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::UpdateLastResult(Result result) { + /* Update the last result. */ + if (R_FAILED(result) && !fs::ResultBufferAllocationFailed::Includes(result) && R_SUCCEEDED(m_last_result)) { + m_last_result = result; + } + + /* Try to succeed with the result. */ + R_TRY(result); + + /* We succeeded. */ + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::ReadHeadCache(MemoryRange *out_range, CacheEntry *out_entry, bool *out_cache_needed, s64 *offset, s64 *aligned_offset, s64 aligned_offset_end, char **buffer, size_t *size) { + /* Valdiate pre-conditions. */ + AMS_ASSERT(out_range != nullptr); + AMS_ASSERT(out_entry != nullptr); + AMS_ASSERT(out_cache_needed != nullptr); + AMS_ASSERT(offset != nullptr); + AMS_ASSERT(aligned_offset != nullptr); + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(*buffer != nullptr); + AMS_ASSERT(size != nullptr); + + AMS_ASSERT(*aligned_offset < aligned_offset_end); + + /* Iterate over the region. */ + CacheEntry entry = {}; + MemoryRange memory_range = {}; + *out_cache_needed = true; + + while (*aligned_offset < aligned_offset_end) { + /* Get the associated buffer for the offset. */ + R_TRY(this->UpdateLastResult(this->GetAssociateBuffer(std::addressof(memory_range), std::addressof(entry), *aligned_offset, m_verification_block_size, true))); + + /* If the entry isn't cached, we're done. */ + if (!entry.is_cached) { + break; + } + + /* Set cache not needed. */ + *out_cache_needed = false; + + /* Determine the size to copy. */ + const s64 buffer_offset = *offset - entry.range.offset; + const size_t copy_size = std::min(*size, static_cast<size_t>(entry.range.GetEndOffset() - *offset)); + + /* Copy data from the entry. */ + std::memcpy(*buffer, reinterpret_cast<const void *>(memory_range.first + buffer_offset), copy_size); + + /* Advance. */ + *buffer += copy_size; + *offset += copy_size; + *size -= copy_size; + *aligned_offset = entry.range.GetEndOffset(); + + /* Handle the buffer. */ + R_TRY(this->UpdateLastResult(this->StoreOrDestroyBuffer(memory_range, std::addressof(entry)))); + } + + /* Set the output entry. */ + *out_entry = entry; + *out_range = memory_range; + + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::ReadTailCache(MemoryRange *out_range, CacheEntry *out_entry, bool *out_cache_needed, s64 offset, s64 aligned_offset, s64 *aligned_offset_end, char *buffer, size_t *size) { + /* Valdiate pre-conditions. */ + AMS_ASSERT(out_range != nullptr); + AMS_ASSERT(out_entry != nullptr); + AMS_ASSERT(out_cache_needed != nullptr); + AMS_ASSERT(aligned_offset_end != nullptr); + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(size != nullptr); + + AMS_ASSERT(aligned_offset < *aligned_offset_end); + + /* Iterate over the region. */ + CacheEntry entry = {}; + MemoryRange memory_range = {}; + *out_cache_needed = true; + + while (aligned_offset < *aligned_offset_end) { + /* Get the associated buffer for the offset. */ + R_TRY(this->UpdateLastResult(this->GetAssociateBuffer(std::addressof(memory_range), std::addressof(entry), *aligned_offset_end - m_verification_block_size, m_verification_block_size, true))); + + /* If the entry isn't cached, we're done. */ + if (!entry.is_cached) { + break; + } + + /* Set cache not needed. */ + *out_cache_needed = false; + + /* Determine the size to copy. */ + const s64 buffer_offset = std::max(static_cast<s64>(0), offset - entry.range.offset); + const size_t copy_size = std::min(*size, static_cast<size_t>(offset + *size - entry.range.offset)); + + /* Copy data from the entry. */ + std::memcpy(buffer + *size - copy_size, reinterpret_cast<const void *>(memory_range.first + buffer_offset), copy_size); + + /* Advance. */ + *size -= copy_size; + *aligned_offset_end = entry.range.offset; + + /* Handle the buffer. */ + R_TRY(this->UpdateLastResult(this->StoreOrDestroyBuffer(memory_range, std::addressof(entry)))); + } + + /* Set the output entry. */ + *out_entry = entry; + *out_range = memory_range; + + R_SUCCEED(); + } + + Result BlockCacheBufferedStorage::BulkRead(s64 offset, void *buffer, size_t size, MemoryRange *range_head, MemoryRange *range_tail, CacheEntry *entry_head, CacheEntry *entry_tail, bool head_cache_needed, bool tail_cache_needed) { + /* Validate pre-conditions. */ + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(range_head != nullptr); + AMS_ASSERT(range_tail != nullptr); + AMS_ASSERT(entry_head != nullptr); + AMS_ASSERT(entry_tail != nullptr); + + /* Determine bulk read offsets. */ + const s64 read_offset = offset; + const size_t read_size = size; + const s64 aligned_offset = util::AlignDown(read_offset, m_verification_block_size); + const s64 aligned_offset_end = util::AlignUp(read_offset + read_size, m_verification_block_size); + char *dst = static_cast<char *>(buffer); + + /* Prepare to do our reads. */ + auto head_guard = SCOPE_GUARD { m_block_cache_manager.ReleaseCacheEntry(entry_head, *range_head); }; + auto tail_guard = SCOPE_GUARD { m_block_cache_manager.ReleaseCacheEntry(entry_tail, *range_tail); }; + + /* Flush the entries. */ + R_TRY(this->UpdateLastResult(this->FlushRangeCacheEntries(aligned_offset, aligned_offset_end - aligned_offset, false))); + + /* Determine the buffer to read into. */ + PooledBuffer pooled_buffer; + const size_t buffer_size = static_cast<size_t>(aligned_offset_end - aligned_offset); + char *read_buffer = nullptr; + if (read_offset == aligned_offset && read_size == buffer_size) { + read_buffer = dst; + } else if (tail_cache_needed && entry_tail->range.offset == aligned_offset && entry_tail->range.size == buffer_size) { + read_buffer = reinterpret_cast<char *>(range_tail->first); + } else if (head_cache_needed && entry_head->range.offset == aligned_offset && entry_head->range.size == buffer_size) { + read_buffer = reinterpret_cast<char *>(range_head->first); + } else { + pooled_buffer.AllocateParticularlyLarge(buffer_size, 1); + R_UNLESS(pooled_buffer.GetSize() >= buffer_size, fs::ResultAllocationPooledBufferNotEnoughSize()); + read_buffer = pooled_buffer.GetBuffer(); + } + + /* Read the data. */ + R_TRY(m_data_storage->Read(aligned_offset, read_buffer, buffer_size)); + + /* Copy the data out. */ + if (dst != read_buffer) { + std::memcpy(dst, read_buffer + read_offset - aligned_offset, read_size); + } + + /* Create a helper to populate our caches. */ + const auto PopulateCacheFromPooledBuffer = [&](CacheEntry *entry, MemoryRange *range) { + AMS_ASSERT(entry != nullptr); + AMS_ASSERT(range != nullptr); + + if (aligned_offset <= entry->range.offset && entry->range.GetEndOffset() <= static_cast<s64>(aligned_offset + buffer_size)) { + AMS_ASSERT(!entry->is_cached); + if (reinterpret_cast<void *>(range->first) != read_buffer) { + std::memcpy(reinterpret_cast<void *>(range->first), read_buffer + entry->range.offset - aligned_offset, entry->range.size); + } + entry->is_cached = true; + } + }; + + /* Populate tail cache if needed. */ + if (tail_cache_needed) { + PopulateCacheFromPooledBuffer(entry_tail, range_tail); + } + + /* Populate head cache if needed. */ + if (head_cache_needed) { + PopulateCacheFromPooledBuffer(entry_head, range_head); + } + + /* If both entries are cached, one may contain the other; in that case, we need only the larger entry. */ + if (entry_head->is_cached && entry_tail->is_cached) { + if (entry_tail->range.offset <= entry_head->range.offset && entry_head->range.GetEndOffset() <= entry_tail->range.GetEndOffset()) { + entry_head->is_cached = false; + } else if (entry_head->range.offset <= entry_tail->range.offset && entry_tail->range.GetEndOffset() <= entry_head->range.GetEndOffset()) { + entry_tail->is_cached = false; + } + } + + /* Destroy the tail cache. */ + tail_guard.Cancel(); + if (entry_tail->is_cached) { + R_TRY(this->UpdateLastResult(this->StoreOrDestroyBuffer(*range_tail, entry_tail))); + } else { + m_block_cache_manager.ReleaseCacheEntry(entry_tail, *range_tail); + } + + /* Destroy the head cache. */ + head_guard.Cancel(); + if (entry_head->is_cached) { + R_TRY(this->UpdateLastResult(this->StoreOrDestroyBuffer(*range_head, entry_head))); + } else { + m_block_cache_manager.ReleaseCacheEntry(entry_head, *range_head); + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_bucket_tree.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_bucket_tree.cpp new file mode 100644 index 00000000..f3b9f9eb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_bucket_tree.cpp @@ -0,0 +1,557 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + namespace { + + using Node = impl::BucketTreeNode<const s64 *>; + static_assert(sizeof(Node) == sizeof(BucketTree::NodeHeader)); + static_assert(util::is_pod<Node>::value); + + constexpr inline s32 NodeHeaderSize = sizeof(BucketTree::NodeHeader); + + class StorageNode { + private: + class Offset { + public: + using difference_type = s64; + private: + s64 m_offset; + s32 m_stride; + public: + constexpr Offset(s64 offset, s32 stride) : m_offset(offset), m_stride(stride) { /* ... */ } + + constexpr Offset &operator++() { m_offset += m_stride; return *this; } + constexpr Offset operator++(int) { Offset ret(*this); m_offset += m_stride; return ret; } + + constexpr Offset &operator--() { m_offset -= m_stride; return *this; } + constexpr Offset operator--(int) { Offset ret(*this); m_offset -= m_stride; return ret; } + + constexpr difference_type operator-(const Offset &rhs) const { return (m_offset - rhs.m_offset) / m_stride; } + + constexpr Offset operator+(difference_type ofs) const { return Offset(m_offset + ofs * m_stride, m_stride); } + constexpr Offset operator-(difference_type ofs) const { return Offset(m_offset - ofs * m_stride, m_stride); } + + constexpr Offset &operator+=(difference_type ofs) { m_offset += ofs * m_stride; return *this; } + constexpr Offset &operator-=(difference_type ofs) { m_offset -= ofs * m_stride; return *this; } + + constexpr bool operator==(const Offset &rhs) const { return m_offset == rhs.m_offset; } + constexpr bool operator!=(const Offset &rhs) const { return m_offset != rhs.m_offset; } + + constexpr s64 Get() const { return m_offset; } + }; + private: + const Offset m_start; + const s32 m_count; + s32 m_index; + public: + StorageNode(size_t size, s32 count) : m_start(NodeHeaderSize, static_cast<s32>(size)), m_count(count), m_index(-1) { /* ... */ } + StorageNode(s64 ofs, size_t size, s32 count) : m_start(NodeHeaderSize + ofs, static_cast<s32>(size)), m_count(count), m_index(-1) { /* ... */ } + + s32 GetIndex() const { return m_index; } + + void Find(const char *buffer, s64 virtual_address) { + s32 end = m_count; + auto pos = m_start; + + while (end > 0) { + auto half = end / 2; + auto mid = pos + half; + + s64 offset = 0; + std::memcpy(std::addressof(offset), buffer + mid.Get(), sizeof(s64)); + + if (offset <= virtual_address) { + pos = mid + 1; + end -= half + 1; + } else { + end = half; + } + } + + m_index = static_cast<s32>(pos - m_start) - 1; + } + + Result Find(fs::SubStorage &storage, s64 virtual_address) { + s32 end = m_count; + auto pos = m_start; + + while (end > 0) { + auto half = end / 2; + auto mid = pos + half; + + s64 offset = 0; + R_TRY(storage.Read(mid.Get(), std::addressof(offset), sizeof(s64))); + + if (offset <= virtual_address) { + pos = mid + 1; + end -= half + 1; + } else { + end = half; + } + } + + m_index = static_cast<s32>(pos - m_start) - 1; + R_SUCCEED(); + } + }; + + } + + void BucketTree::Header::Format(s32 entry_count) { + AMS_ASSERT(entry_count >= 0); + + this->magic = Magic; + this->version = Version; + this->entry_count = entry_count; + this->reserved = 0; + } + + Result BucketTree::Header::Verify() const { + R_UNLESS(this->magic == Magic, fs::ResultInvalidBucketTreeSignature()); + R_UNLESS(this->entry_count >= 0, fs::ResultInvalidBucketTreeEntryCount()); + R_UNLESS(this->version <= Version, fs::ResultUnsupportedVersion()); + R_SUCCEED(); + } + + Result BucketTree::NodeHeader::Verify(s32 node_index, size_t node_size, size_t entry_size) const { + R_UNLESS(this->index == node_index, fs::ResultInvalidBucketTreeNodeIndex()); + R_UNLESS(entry_size != 0 && node_size >= entry_size + NodeHeaderSize, fs::ResultInvalidSize()); + + const size_t max_entry_count = (node_size - NodeHeaderSize) / entry_size; + R_UNLESS(this->count > 0 && static_cast<size_t>(this->count) <= max_entry_count, fs::ResultInvalidBucketTreeNodeEntryCount()); + R_UNLESS(this->offset >= 0, fs::ResultInvalidBucketTreeNodeOffset()); + + R_SUCCEED(); + } + + Result BucketTree::Initialize(IAllocator *allocator, fs::SubStorage node_storage, fs::SubStorage entry_storage, size_t node_size, size_t entry_size, s32 entry_count) { + /* Validate preconditions. */ + AMS_ASSERT(allocator != nullptr); + AMS_ASSERT(entry_size >= sizeof(s64)); + AMS_ASSERT(node_size >= entry_size + sizeof(NodeHeader)); + AMS_ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); + AMS_ASSERT(util::IsPowerOfTwo(node_size)); + AMS_ASSERT(!this->IsInitialized()); + + /* Ensure valid entry count. */ + R_UNLESS(entry_count > 0, fs::ResultInvalidArgument()); + + /* Allocate node. */ + R_UNLESS(m_node_l1.Allocate(allocator, node_size), fs::ResultBufferAllocationFailed()); + ON_RESULT_FAILURE { m_node_l1.Free(node_size); }; + + /* Read node. */ + R_TRY(node_storage.Read(0, m_node_l1.Get(), node_size)); + + /* Verify node. */ + R_TRY(m_node_l1->Verify(0, node_size, sizeof(s64))); + + /* Validate offsets. */ + const auto offset_count = GetOffsetCount(node_size); + const auto entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count); + const auto * const node = m_node_l1.Get<Node>(); + + s64 start_offset; + if (offset_count < entry_set_count && node->GetCount() < offset_count) { + start_offset = *node->GetEnd(); + } else { + start_offset = *node->GetBegin(); + } + const auto end_offset = node->GetEndOffset(); + + R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(), fs::ResultInvalidBucketTreeEntryOffset()); + R_UNLESS(start_offset < end_offset, fs::ResultInvalidBucketTreeEntryOffset()); + + /* Set member variables. */ + m_node_storage = node_storage; + m_entry_storage = entry_storage; + m_node_size = node_size; + m_entry_size = entry_size; + m_entry_count = entry_count; + m_offset_count = offset_count; + m_entry_set_count = entry_set_count; + + m_offset_cache.offsets.start_offset = start_offset; + m_offset_cache.offsets.end_offset = end_offset; + m_offset_cache.is_initialized = true; + + /* We succeeded. */ + R_SUCCEED(); + } + + void BucketTree::Initialize(size_t node_size, s64 end_offset) { + AMS_ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); + AMS_ASSERT(util::IsPowerOfTwo(node_size)); + AMS_ASSERT(end_offset > 0); + AMS_ASSERT(!this->IsInitialized()); + + m_node_size = node_size; + + m_offset_cache.offsets.start_offset = 0; + m_offset_cache.offsets.end_offset = end_offset; + m_offset_cache.is_initialized = true; + } + + void BucketTree::Finalize() { + if (this->IsInitialized()) { + m_node_storage = fs::SubStorage(); + m_entry_storage = fs::SubStorage(); + m_node_l1.Free(m_node_size); + m_node_size = 0; + m_entry_size = 0; + m_entry_count = 0; + m_offset_count = 0; + m_entry_set_count = 0; + + m_offset_cache.offsets.start_offset = 0; + m_offset_cache.offsets.end_offset = 0; + m_offset_cache.is_initialized = false; + } + } + + Result BucketTree::Find(Visitor *visitor, s64 virtual_address) { + AMS_ASSERT(visitor != nullptr); + AMS_ASSERT(this->IsInitialized()); + + R_UNLESS(virtual_address >= 0, fs::ResultInvalidOffset()); + R_UNLESS(!this->IsEmpty(), fs::ResultOutOfRange()); + + BucketTree::Offsets offsets; + R_TRY(this->GetOffsets(std::addressof(offsets))); + + R_TRY(visitor->Initialize(this, offsets)); + + R_RETURN(visitor->Find(virtual_address)); + } + + Result BucketTree::InvalidateCache() { + /* Invalidate the node storage cache. */ + R_TRY(m_node_storage.OperateRange(fs::OperationId::Invalidate, 0, std::numeric_limits<s64>::max())); + + /* Invalidate the entry storage cache. */ + R_TRY(m_entry_storage.OperateRange(fs::OperationId::Invalidate, 0, std::numeric_limits<s64>::max())); + + /* Reset our offsets. */ + m_offset_cache.is_initialized = false; + + R_SUCCEED(); + } + + Result BucketTree::EnsureOffsetCache() { + /* If we already have an offset cache, we're good. */ + R_SUCCEED_IF(m_offset_cache.is_initialized); + + /* Acquire exclusive right to edit the offset cache. */ + std::scoped_lock lk(m_offset_cache.mutex); + + /* Check again, to be sure. */ + R_SUCCEED_IF(m_offset_cache.is_initialized); + + /* Read/verify L1. */ + R_TRY(m_node_storage.Read(0, m_node_l1.Get(), m_node_size)); + R_TRY(m_node_l1->Verify(0, m_node_size, sizeof(s64))); + + /* Get the node. */ + auto * const node = m_node_l1.Get<Node>(); + + s64 start_offset; + if (m_offset_count < m_entry_set_count && node->GetCount() < m_offset_count) { + start_offset = *node->GetEnd(); + } else { + start_offset = *node->GetBegin(); + } + const auto end_offset = node->GetEndOffset(); + + R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(), fs::ResultInvalidBucketTreeEntryOffset()); + R_UNLESS(start_offset < end_offset, fs::ResultInvalidBucketTreeEntryOffset()); + + m_offset_cache.offsets.start_offset = start_offset; + m_offset_cache.offsets.end_offset = end_offset; + m_offset_cache.is_initialized = true; + + R_SUCCEED(); + } + + Result BucketTree::Visitor::Initialize(const BucketTree *tree, const BucketTree::Offsets &offsets) { + AMS_ASSERT(tree != nullptr); + AMS_ASSERT(m_tree == nullptr || m_tree == tree); + + if (m_entry == nullptr) { + m_entry = tree->GetAllocator()->Allocate(tree->m_entry_size); + R_UNLESS(m_entry != nullptr, fs::ResultBufferAllocationFailed()); + + m_tree = tree; + m_offsets = offsets; + } + + R_SUCCEED(); + } + + Result BucketTree::Visitor::MoveNext() { + R_UNLESS(this->IsValid(), fs::ResultOutOfRange()); + + /* Invalidate our index, and read the header for the next index. */ + auto entry_index = m_entry_index + 1; + if (entry_index == m_entry_set.info.count) { + const auto entry_set_index = m_entry_set.info.index + 1; + R_UNLESS(entry_set_index < m_entry_set_count, fs::ResultOutOfRange()); + + m_entry_index = -1; + + const auto end = m_entry_set.info.end; + + const auto entry_set_size = m_tree->m_node_size; + const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); + + R_TRY(m_tree->m_entry_storage.Read(entry_set_offset, std::addressof(m_entry_set), sizeof(EntrySetHeader))); + R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size)); + + R_UNLESS(m_entry_set.info.start == end && m_entry_set.info.start < m_entry_set.info.end, fs::ResultInvalidBucketTreeEntrySetOffset()); + + entry_index = 0; + } else { + m_entry_index = -1; + } + + /* Read the new entry. */ + const auto entry_size = m_tree->m_entry_size; + const auto entry_offset = impl::GetBucketTreeEntryOffset(m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index); + R_TRY(m_tree->m_entry_storage.Read(entry_offset, m_entry, entry_size)); + + /* Note that we changed index. */ + m_entry_index = entry_index; + R_SUCCEED(); + } + + Result BucketTree::Visitor::MovePrevious() { + R_UNLESS(this->IsValid(), fs::ResultOutOfRange()); + + /* Invalidate our index, and read the heasder for the previous index. */ + auto entry_index = m_entry_index; + if (entry_index == 0) { + R_UNLESS(m_entry_set.info.index > 0, fs::ResultOutOfRange()); + + m_entry_index = -1; + + const auto start = m_entry_set.info.start; + + const auto entry_set_size = m_tree->m_node_size; + const auto entry_set_index = m_entry_set.info.index - 1; + const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); + + R_TRY(m_tree->m_entry_storage.Read(entry_set_offset, std::addressof(m_entry_set), sizeof(EntrySetHeader))); + R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size)); + + R_UNLESS(m_entry_set.info.end == start && m_entry_set.info.start < m_entry_set.info.end, fs::ResultInvalidBucketTreeEntrySetOffset()); + + entry_index = m_entry_set.info.count; + } else { + m_entry_index = -1; + } + + --entry_index; + + /* Read the new entry. */ + const auto entry_size = m_tree->m_entry_size; + const auto entry_offset = impl::GetBucketTreeEntryOffset(m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index); + R_TRY(m_tree->m_entry_storage.Read(entry_offset, m_entry, entry_size)); + + /* Note that we changed index. */ + m_entry_index = entry_index; + R_SUCCEED(); + } + + Result BucketTree::Visitor::Find(s64 virtual_address) { + AMS_ASSERT(m_tree != nullptr); + + /* Get the node. */ + const auto * const node = m_tree->m_node_l1.Get<Node>(); + R_UNLESS(virtual_address < node->GetEndOffset(), fs::ResultOutOfRange()); + + /* Get the entry set index. */ + s32 entry_set_index = -1; + if (m_tree->IsExistOffsetL2OnL1() && virtual_address < node->GetBeginOffset()) { + const auto start = node->GetEnd(); + const auto end = node->GetBegin() + m_tree->m_offset_count; + + auto pos = std::upper_bound(start, end, virtual_address); + R_UNLESS(start < pos, fs::ResultOutOfRange()); + --pos; + + entry_set_index = static_cast<s32>(pos - start); + } else { + const auto start = node->GetBegin(); + const auto end = node->GetEnd(); + + auto pos = std::upper_bound(start, end, virtual_address); + R_UNLESS(start < pos, fs::ResultOutOfRange()); + --pos; + + if (m_tree->IsExistL2()) { + const auto node_index = static_cast<s32>(pos - start); + R_UNLESS(0 <= node_index && node_index < m_tree->m_offset_count, fs::ResultInvalidBucketTreeNodeOffset()); + + R_TRY(this->FindEntrySet(std::addressof(entry_set_index), virtual_address, node_index)); + } else { + entry_set_index = static_cast<s32>(pos - start); + } + } + + /* Validate the entry set index. */ + R_UNLESS(0 <= entry_set_index && entry_set_index < m_tree->m_entry_set_count, fs::ResultInvalidBucketTreeNodeOffset()); + + /* Find the entry. */ + R_TRY(this->FindEntry(virtual_address, entry_set_index)); + + /* Set count. */ + m_entry_set_count = m_tree->m_entry_set_count; + R_SUCCEED(); + } + + Result BucketTree::Visitor::FindEntrySet(s32 *out_index, s64 virtual_address, s32 node_index) { + const auto node_size = m_tree->m_node_size; + + PooledBuffer pool(node_size, 1); + if (node_size <= pool.GetSize()) { + R_RETURN(this->FindEntrySetWithBuffer(out_index, virtual_address, node_index, pool.GetBuffer())); + } else { + pool.Deallocate(); + R_RETURN(this->FindEntrySetWithoutBuffer(out_index, virtual_address, node_index)); + } + } + + Result BucketTree::Visitor::FindEntrySetWithBuffer(s32 *out_index, s64 virtual_address, s32 node_index, char *buffer) { + /* Calculate node extents. */ + const auto node_size = m_tree->m_node_size; + const auto node_offset = (node_index + 1) * static_cast<s64>(node_size); + fs::SubStorage &storage = m_tree->m_node_storage; + + /* Read the node. */ + R_TRY(storage.Read(node_offset, buffer, node_size)); + + /* Validate the header. */ + NodeHeader header; + std::memcpy(std::addressof(header), buffer, NodeHeaderSize); + R_TRY(header.Verify(node_index, node_size, sizeof(s64))); + + /* Create the node, and find. */ + StorageNode node(sizeof(s64), header.count); + node.Find(buffer, virtual_address); + R_UNLESS(node.GetIndex() >= 0, fs::ResultInvalidBucketTreeVirtualOffset()); + + /* Return the index. */ + *out_index = m_tree->GetEntrySetIndex(header.index, node.GetIndex()); + R_SUCCEED(); + } + + Result BucketTree::Visitor::FindEntrySetWithoutBuffer(s32 *out_index, s64 virtual_address, s32 node_index) { + /* Calculate node extents. */ + const auto node_size = m_tree->m_node_size; + const auto node_offset = (node_index + 1) * static_cast<s64>(node_size); + fs::SubStorage &storage = m_tree->m_node_storage; + + /* Read and validate the header. */ + NodeHeader header; + R_TRY(storage.Read(node_offset, std::addressof(header), NodeHeaderSize)); + R_TRY(header.Verify(node_index, node_size, sizeof(s64))); + + /* Create the node, and find. */ + StorageNode node(node_offset, sizeof(s64), header.count); + R_TRY(node.Find(storage, virtual_address)); + R_UNLESS(node.GetIndex() >= 0, fs::ResultOutOfRange()); + + /* Return the index. */ + *out_index = m_tree->GetEntrySetIndex(header.index, node.GetIndex()); + R_SUCCEED(); + } + + Result BucketTree::Visitor::FindEntry(s64 virtual_address, s32 entry_set_index) { + const auto entry_set_size = m_tree->m_node_size; + + PooledBuffer pool(entry_set_size, 1); + if (entry_set_size <= pool.GetSize()) { + R_RETURN(this->FindEntryWithBuffer(virtual_address, entry_set_index, pool.GetBuffer())); + } else { + pool.Deallocate(); + R_RETURN(this->FindEntryWithoutBuffer(virtual_address, entry_set_index)); + } + } + + Result BucketTree::Visitor::FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, char *buffer) { + /* Calculate entry set extents. */ + const auto entry_size = m_tree->m_entry_size; + const auto entry_set_size = m_tree->m_node_size; + const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); + fs::SubStorage &storage = m_tree->m_entry_storage; + + /* Read the entry set. */ + R_TRY(storage.Read(entry_set_offset, buffer, entry_set_size)); + + /* Validate the entry_set. */ + EntrySetHeader entry_set; + std::memcpy(std::addressof(entry_set), buffer, sizeof(EntrySetHeader)); + R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size)); + + /* Create the node, and find. */ + StorageNode node(entry_size, entry_set.info.count); + node.Find(buffer, virtual_address); + R_UNLESS(node.GetIndex() >= 0, fs::ResultOutOfRange()); + + /* Copy the data into entry. */ + const auto entry_index = node.GetIndex(); + const auto entry_offset = impl::GetBucketTreeEntryOffset(0, entry_size, entry_index); + std::memcpy(m_entry, buffer + entry_offset, entry_size); + + /* Set our entry set/index. */ + m_entry_set = entry_set; + m_entry_index = entry_index; + + R_SUCCEED(); + } + + Result BucketTree::Visitor::FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index) { + /* Calculate entry set extents. */ + const auto entry_size = m_tree->m_entry_size; + const auto entry_set_size = m_tree->m_node_size; + const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); + fs::SubStorage &storage = m_tree->m_entry_storage; + + /* Read and validate the entry_set. */ + EntrySetHeader entry_set; + R_TRY(storage.Read(entry_set_offset, std::addressof(entry_set), sizeof(EntrySetHeader))); + R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size)); + + /* Create the node, and find. */ + StorageNode node(entry_set_offset, entry_size, entry_set.info.count); + R_TRY(node.Find(storage, virtual_address)); + R_UNLESS(node.GetIndex() >= 0, fs::ResultOutOfRange()); + + /* Copy the data into entry. */ + const auto entry_index = node.GetIndex(); + const auto entry_offset = impl::GetBucketTreeEntryOffset(entry_set_offset, entry_size, entry_index); + R_TRY(storage.Read(entry_offset, m_entry, entry_size)); + + /* Set our entry set/index. */ + m_entry_set = entry_set; + m_entry_index = entry_index; + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_buffered_storage.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_buffered_storage.cpp new file mode 100644 index 00000000..f612a8ee --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_buffered_storage.cpp @@ -0,0 +1,1082 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + namespace { + + constexpr inline uintptr_t InvalidAddress = 0; + constexpr inline s64 InvalidOffset = std::numeric_limits<s64>::max(); + + } + + class BufferedStorage::Cache : public ::ams::fs::impl::Newable { + private: + struct FetchParameter { + s64 offset; + void *buffer; + size_t size; + }; + static_assert(util::is_pod<FetchParameter>::value); + private: + BufferedStorage *m_buffered_storage; + std::pair<uintptr_t, size_t> m_memory_range; + fs::IBufferManager::CacheHandle m_cache_handle; + s64 m_offset; + std::atomic<bool> m_is_valid; + std::atomic<bool> m_is_dirty; + u8 m_reserved[2]; + s32 m_reference_count; + Cache *m_next; + Cache *m_prev; + public: + Cache() : m_buffered_storage(nullptr), m_memory_range(InvalidAddress, 0), m_cache_handle(), m_offset(InvalidOffset), m_is_valid(false), m_is_dirty(false), m_reference_count(1), m_next(nullptr), m_prev(nullptr) { + /* ... */ + } + + ~Cache() { + this->Finalize(); + } + + void Initialize(BufferedStorage *bs) { + AMS_ASSERT(bs != nullptr); + AMS_ASSERT(m_buffered_storage == nullptr); + + m_buffered_storage = bs; + this->Link(); + } + + void Finalize() { + AMS_ASSERT(m_buffered_storage != nullptr); + AMS_ASSERT(m_buffered_storage->m_buffer_manager != nullptr); + AMS_ASSERT(m_reference_count == 0); + + /* If we're valid, acquire our cache handle and free our buffer. */ + if (this->IsValid()) { + const auto buffer_manager = m_buffered_storage->m_buffer_manager; + if (!m_is_dirty) { + AMS_ASSERT(m_memory_range.first == InvalidAddress); + m_memory_range = buffer_manager->AcquireCache(m_cache_handle); + } + if (m_memory_range.first != InvalidAddress) { + buffer_manager->DeallocateBuffer(m_memory_range.first, m_memory_range.second); + m_memory_range.first = InvalidAddress; + m_memory_range.second = 0; + } + } + + /* Clear all our members. */ + m_buffered_storage = nullptr; + m_offset = InvalidOffset; + m_is_valid = false; + m_is_dirty = false; + m_next = nullptr; + m_prev = nullptr; + } + + void Link() { + AMS_ASSERT(m_buffered_storage != nullptr); + AMS_ASSERT(m_buffered_storage->m_buffer_manager != nullptr); + AMS_ASSERT(m_reference_count > 0); + + if ((--m_reference_count) == 0) { + AMS_ASSERT(m_next == nullptr); + AMS_ASSERT(m_prev == nullptr); + + if (m_buffered_storage->m_next_fetch_cache == nullptr) { + m_buffered_storage->m_next_fetch_cache = this; + m_next = this; + m_prev = this; + } else { + /* Check against a cache being registered twice. */ + { + auto cache = m_buffered_storage->m_next_fetch_cache; + do { + if (cache->IsValid() && this->Hits(cache->m_offset, m_buffered_storage->m_block_size)) { + m_is_valid = false; + break; + } + cache = cache->m_next; + } while (cache != m_buffered_storage->m_next_fetch_cache); + } + + /* Link into the fetch list. */ + { + AMS_ASSERT(m_buffered_storage->m_next_fetch_cache->m_prev != nullptr); + AMS_ASSERT(m_buffered_storage->m_next_fetch_cache->m_prev->m_next == m_buffered_storage->m_next_fetch_cache); + m_next = m_buffered_storage->m_next_fetch_cache; + m_prev = m_buffered_storage->m_next_fetch_cache->m_prev; + m_next->m_prev = this; + m_prev->m_next = this; + } + + /* Insert invalid caches at the start of the list. */ + if (!this->IsValid()) { + m_buffered_storage->m_next_fetch_cache = this; + } + } + + /* If we're not valid, clear our offset. */ + if (!this->IsValid()) { + m_offset = InvalidOffset; + m_is_dirty = false; + } + + /* Ensure our buffer state is coherent. */ + if (m_memory_range.first != InvalidAddress && !m_is_dirty) { + if (this->IsValid()) { + m_cache_handle = m_buffered_storage->m_buffer_manager->RegisterCache(m_memory_range.first, m_memory_range.second, fs::IBufferManager::BufferAttribute()); + } else { + m_buffered_storage->m_buffer_manager->DeallocateBuffer(m_memory_range.first, m_memory_range.second); + } + m_memory_range.first = InvalidAddress; + m_memory_range.second = 0; + } + } + } + + void Unlink() { + AMS_ASSERT(m_buffered_storage != nullptr); + AMS_ASSERT(m_reference_count >= 0); + + if ((++m_reference_count) == 1) { + AMS_ASSERT(m_next != nullptr); + AMS_ASSERT(m_prev != nullptr); + AMS_ASSERT(m_next->m_prev == this); + AMS_ASSERT(m_prev->m_next == this); + + if (m_buffered_storage->m_next_fetch_cache == this) { + if (m_next != this) { + m_buffered_storage->m_next_fetch_cache = m_next; + } else { + m_buffered_storage->m_next_fetch_cache = nullptr; + } + } + + m_buffered_storage->m_next_acquire_cache = this; + + m_next->m_prev = m_prev; + m_prev->m_next = m_next; + m_next = nullptr; + m_prev = nullptr; + } else { + AMS_ASSERT(m_next == nullptr); + AMS_ASSERT(m_prev == nullptr); + } + } + + void Read(s64 offset, void *buffer, size_t size) const { + AMS_ASSERT(m_buffered_storage != nullptr); + AMS_ASSERT(m_next == nullptr); + AMS_ASSERT(m_prev == nullptr); + AMS_ASSERT(this->IsValid()); + AMS_ASSERT(this->Hits(offset, 1)); + AMS_ASSERT(m_memory_range.first != InvalidAddress); + + const auto read_offset = offset - m_offset; + const auto readable_offset_max = m_buffered_storage->m_block_size - size; + const auto cache_buffer = reinterpret_cast<u8 *>(m_memory_range.first) + read_offset; + AMS_ASSERT(read_offset >= 0); + AMS_ASSERT(static_cast<size_t>(read_offset) <= readable_offset_max); + AMS_UNUSED(readable_offset_max); + + std::memcpy(buffer, cache_buffer, size); + } + + void Write(s64 offset, const void *buffer, size_t size) { + AMS_ASSERT(m_buffered_storage != nullptr); + AMS_ASSERT(m_next == nullptr); + AMS_ASSERT(m_prev == nullptr); + AMS_ASSERT(this->IsValid()); + AMS_ASSERT(this->Hits(offset, 1)); + AMS_ASSERT(m_memory_range.first != InvalidAddress); + + const auto write_offset = offset - m_offset; + const auto writable_offset_max = m_buffered_storage->m_block_size - size; + const auto cache_buffer = reinterpret_cast<u8 *>(m_memory_range.first) + write_offset; + AMS_ASSERT(write_offset >= 0); + AMS_ASSERT(static_cast<size_t>(write_offset) <= writable_offset_max); + AMS_UNUSED(writable_offset_max); + + std::memcpy(cache_buffer, buffer, size); + m_is_dirty = true; + } + + Result Flush() { + AMS_ASSERT(m_buffered_storage != nullptr); + AMS_ASSERT(m_next == nullptr); + AMS_ASSERT(m_prev == nullptr); + AMS_ASSERT(this->IsValid()); + + if (m_is_dirty) { + AMS_ASSERT(m_memory_range.first != InvalidAddress); + + const auto base_size = m_buffered_storage->m_base_storage_size; + const auto block_size = static_cast<s64>(m_buffered_storage->m_block_size); + const auto flush_size = static_cast<size_t>(std::min(block_size, base_size - m_offset)); + + auto &base_storage = m_buffered_storage->m_base_storage; + const auto cache_buffer = reinterpret_cast<void *>(m_memory_range.first); + + R_TRY(base_storage.Write(m_offset, cache_buffer, flush_size)); + m_is_dirty = false; + + buffers::EnableBlockingBufferManagerAllocation(); + } + + R_SUCCEED(); + } + + const std::pair<Result, bool> PrepareFetch() { + AMS_ASSERT(m_buffered_storage != nullptr); + AMS_ASSERT(m_buffered_storage->m_buffer_manager != nullptr); + AMS_ASSERT(m_next == nullptr); + AMS_ASSERT(m_prev == nullptr); + AMS_ASSERT(this->IsValid()); + AMS_ASSERT(m_buffered_storage->m_mutex.IsLockedByCurrentThread()); + + std::pair<Result, bool> result(ResultSuccess(), false); + if (m_reference_count == 1) { + result.first = this->Flush(); + if (R_SUCCEEDED(result.first)) { + m_is_valid = false; + m_reference_count = 0; + result.second = true; + } + } + + return result; + } + + void UnprepareFetch() { + AMS_ASSERT(m_buffered_storage != nullptr); + AMS_ASSERT(m_buffered_storage->m_buffer_manager != nullptr); + AMS_ASSERT(m_next == nullptr); + AMS_ASSERT(m_prev == nullptr); + AMS_ASSERT(!this->IsValid()); + AMS_ASSERT(!m_is_dirty); + AMS_ASSERT(m_buffered_storage->m_mutex.IsLockedByCurrentThread()); + + m_is_valid = true; + m_reference_count = 1; + } + + Result Fetch(s64 offset) { + AMS_ASSERT(m_buffered_storage != nullptr); + AMS_ASSERT(m_buffered_storage->m_buffer_manager != nullptr); + AMS_ASSERT(m_next == nullptr); + AMS_ASSERT(m_prev == nullptr); + AMS_ASSERT(!this->IsValid()); + AMS_ASSERT(!m_is_dirty); + + if (m_memory_range.first == InvalidAddress) { + R_TRY(this->AllocateFetchBuffer()); + } + + FetchParameter fetch_param = {}; + this->CalcFetchParameter(std::addressof(fetch_param), offset); + + auto &base_storage = m_buffered_storage->m_base_storage; + R_TRY(base_storage.Read(fetch_param.offset, fetch_param.buffer, fetch_param.size)); + m_offset = fetch_param.offset; + AMS_ASSERT(this->Hits(offset, 1)); + + R_SUCCEED(); + } + + Result FetchFromBuffer(s64 offset, const void *buffer, size_t buffer_size) { + AMS_ASSERT(m_buffered_storage != nullptr); + AMS_ASSERT(m_buffered_storage->m_buffer_manager != nullptr); + AMS_ASSERT(m_next == nullptr); + AMS_ASSERT(m_prev == nullptr); + AMS_ASSERT(!this->IsValid()); + AMS_ASSERT(!m_is_dirty); + AMS_ASSERT(util::IsAligned(offset, m_buffered_storage->m_block_size)); + + if (m_memory_range.first == InvalidAddress) { + R_TRY(this->AllocateFetchBuffer()); + } + + FetchParameter fetch_param = {}; + this->CalcFetchParameter(std::addressof(fetch_param), offset); + AMS_ASSERT(fetch_param.offset == offset); + AMS_ASSERT(fetch_param.size <= buffer_size); + AMS_UNUSED(buffer_size); + + std::memcpy(fetch_param.buffer, buffer, fetch_param.size); + m_offset = fetch_param.offset; + AMS_ASSERT(this->Hits(offset, 1)); + + R_SUCCEED(); + } + + bool TryAcquireCache() { + AMS_ASSERT(m_buffered_storage != nullptr); + AMS_ASSERT(m_buffered_storage->m_buffer_manager != nullptr); + AMS_ASSERT(this->IsValid()); + + if (m_memory_range.first != InvalidAddress) { + return true; + } else { + m_memory_range = m_buffered_storage->m_buffer_manager->AcquireCache(m_cache_handle); + m_is_valid = m_memory_range.first != InvalidAddress; + return m_is_valid; + } + } + + void Invalidate() { + AMS_ASSERT(m_buffered_storage != nullptr); + m_is_valid = false; + } + + bool IsValid() const { + AMS_ASSERT(m_buffered_storage != nullptr); + return m_is_valid || m_reference_count > 0; + } + + bool IsDirty() const { + AMS_ASSERT(m_buffered_storage != nullptr); + return m_is_dirty; + } + + bool Hits(s64 offset, s64 size) const { + AMS_ASSERT(m_buffered_storage != nullptr); + const auto block_size = static_cast<s64>(m_buffered_storage->m_block_size); + return (offset < m_offset + block_size) && (m_offset < offset + size); + } + private: + Result AllocateFetchBuffer() { + fs::IBufferManager *buffer_manager = m_buffered_storage->m_buffer_manager; + AMS_ASSERT(buffer_manager->AcquireCache(m_cache_handle).first == InvalidAddress); + + auto range_guard = SCOPE_GUARD { m_memory_range.first = InvalidAddress; }; + R_TRY(buffers::AllocateBufferUsingBufferManagerContext(std::addressof(m_memory_range), buffer_manager, m_buffered_storage->m_block_size, fs::IBufferManager::BufferAttribute(), [](const std::pair<uintptr_t, size_t> &buffer) { + return buffer.first != 0; + }, AMS_CURRENT_FUNCTION_NAME)); + + range_guard.Cancel(); + R_SUCCEED(); + } + + void CalcFetchParameter(FetchParameter *out, s64 offset) const { + AMS_ASSERT(out != nullptr); + + const auto block_size = static_cast<s64>(m_buffered_storage->m_block_size); + const auto cache_offset = util::AlignDown(offset, m_buffered_storage->m_block_size); + const auto base_size = m_buffered_storage->m_base_storage_size; + const auto cache_size = static_cast<size_t>(std::min(block_size, base_size - cache_offset)); + const auto cache_buffer = reinterpret_cast<void *>(m_memory_range.first); + AMS_ASSERT(offset >= 0); + AMS_ASSERT(offset < base_size); + + out->offset = cache_offset; + out->buffer = cache_buffer; + out->size = cache_size; + } + }; + + class BufferedStorage::SharedCache { + NON_COPYABLE(SharedCache); + NON_MOVEABLE(SharedCache); + friend class UniqueCache; + private: + Cache *m_cache; + Cache *m_start_cache; + BufferedStorage *m_buffered_storage; + public: + explicit SharedCache(BufferedStorage *bs) : m_cache(nullptr), m_start_cache(bs->m_next_acquire_cache), m_buffered_storage(bs) { + AMS_ASSERT(m_buffered_storage != nullptr); + } + + ~SharedCache() { + std::scoped_lock lk(m_buffered_storage->m_mutex); + this->Release(); + } + + bool AcquireNextOverlappedCache(s64 offset, s64 size) { + AMS_ASSERT(m_buffered_storage != nullptr); + + auto is_first = m_cache == nullptr; + const auto start = is_first ? m_start_cache : m_cache + 1; + + AMS_ASSERT(start >= m_buffered_storage->m_caches.get()); + AMS_ASSERT(start <= m_buffered_storage->m_caches.get() + m_buffered_storage->m_cache_count); + + std::scoped_lock lk(m_buffered_storage->m_mutex); + + this->Release(); + AMS_ASSERT(m_cache == nullptr); + + for (auto cache = start; true; ++cache) { + if (m_buffered_storage->m_caches.get() + m_buffered_storage->m_cache_count <= cache) { + cache = m_buffered_storage->m_caches.get(); + } + if (!is_first && cache == m_start_cache) { + break; + } + if (cache->IsValid() && cache->Hits(offset, size) && cache->TryAcquireCache()) { + cache->Unlink(); + m_cache = cache; + return true; + } + is_first = false; + } + + m_cache = nullptr; + return false; + } + + bool AcquireNextDirtyCache() { + AMS_ASSERT(m_buffered_storage != nullptr); + const auto start = m_cache != nullptr ? m_cache + 1 : m_buffered_storage->m_caches.get(); + const auto end = m_buffered_storage->m_caches.get() + m_buffered_storage->m_cache_count; + + AMS_ASSERT(start >= m_buffered_storage->m_caches.get()); + AMS_ASSERT(start <= end); + + this->Release(); + AMS_ASSERT(m_cache == nullptr); + + for (auto cache = start; cache < end; ++cache) { + if (cache->IsValid() && cache->IsDirty() && cache->TryAcquireCache()) { + cache->Unlink(); + m_cache = cache; + return true; + } + } + + m_cache = nullptr; + return false; + } + + bool AcquireNextValidCache() { + AMS_ASSERT(m_buffered_storage != nullptr); + const auto start = m_cache != nullptr ? m_cache + 1 : m_buffered_storage->m_caches.get(); + const auto end = m_buffered_storage->m_caches.get() + m_buffered_storage->m_cache_count; + + AMS_ASSERT(start >= m_buffered_storage->m_caches.get()); + AMS_ASSERT(start <= end); + + this->Release(); + AMS_ASSERT(m_cache == nullptr); + + for (auto cache = start; cache < end; ++cache) { + if (cache->IsValid() && cache->TryAcquireCache()) { + cache->Unlink(); + m_cache = cache; + return true; + } + } + + m_cache = nullptr; + return false; + } + + bool AcquireFetchableCache() { + AMS_ASSERT(m_buffered_storage != nullptr); + + std::scoped_lock lk(m_buffered_storage->m_mutex); + + this->Release(); + AMS_ASSERT(m_cache == nullptr); + + m_cache = m_buffered_storage->m_next_fetch_cache; + if (m_cache != nullptr) { + if (m_cache->IsValid()) { + m_cache->TryAcquireCache(); + } + m_cache->Unlink(); + } + + return m_cache != nullptr; + } + + void Read(s64 offset, void *buffer, size_t size) { + AMS_ASSERT(m_cache != nullptr); + m_cache->Read(offset, buffer, size); + } + + void Write(s64 offset, const void *buffer, size_t size) { + AMS_ASSERT(m_cache != nullptr); + m_cache->Write(offset, buffer, size); + } + + Result Flush() { + AMS_ASSERT(m_cache != nullptr); + R_RETURN(m_cache->Flush()); + } + + void Invalidate() { + AMS_ASSERT(m_cache != nullptr); + return m_cache->Invalidate(); + } + + bool Hits(s64 offset, s64 size) const { + AMS_ASSERT(m_cache != nullptr); + return m_cache->Hits(offset, size); + } + private: + void Release() { + if (m_cache != nullptr) { + AMS_ASSERT(m_buffered_storage->m_caches.get() <= m_cache); + AMS_ASSERT(m_cache <= m_buffered_storage->m_caches.get() + m_buffered_storage->m_cache_count); + + m_cache->Link(); + m_cache = nullptr; + } + } + }; + + class BufferedStorage::UniqueCache { + NON_COPYABLE(UniqueCache); + NON_MOVEABLE(UniqueCache); + private: + Cache *m_cache; + BufferedStorage *m_buffered_storage; + public: + explicit UniqueCache(BufferedStorage *bs) : m_cache(nullptr), m_buffered_storage(bs) { + AMS_ASSERT(m_buffered_storage != nullptr); + } + + ~UniqueCache() { + if (m_cache != nullptr) { + std::scoped_lock lk(m_buffered_storage->m_mutex); + m_cache->UnprepareFetch(); + } + } + + const std::pair<Result, bool> Upgrade(const SharedCache &shared_cache) { + AMS_ASSERT(m_buffered_storage == shared_cache.m_buffered_storage); + AMS_ASSERT(shared_cache.m_cache != nullptr); + + std::scoped_lock lk(m_buffered_storage->m_mutex); + const auto result = shared_cache.m_cache->PrepareFetch(); + if (R_SUCCEEDED(result.first) && result.second) { + m_cache = shared_cache.m_cache; + } + return result; + } + + Result Fetch(s64 offset) { + AMS_ASSERT(m_cache != nullptr); + R_RETURN(m_cache->Fetch(offset)); + } + + Result FetchFromBuffer(s64 offset, const void *buffer, size_t buffer_size) { + AMS_ASSERT(m_cache != nullptr); + R_TRY(m_cache->FetchFromBuffer(offset, buffer, buffer_size)); + R_SUCCEED(); + } + }; + + BufferedStorage::BufferedStorage() : m_base_storage(), m_buffer_manager(), m_block_size(), m_base_storage_size(), m_caches(), m_cache_count(), m_next_acquire_cache(), m_next_fetch_cache(), m_mutex(), m_bulk_read_enabled() { + /* ... */ + } + + BufferedStorage::~BufferedStorage() { + this->Finalize(); + } + + Result BufferedStorage::Initialize(fs::SubStorage base_storage, fs::IBufferManager *buffer_manager, size_t block_size, s32 buffer_count) { + AMS_ASSERT(buffer_manager != nullptr); + AMS_ASSERT(block_size > 0); + AMS_ASSERT(util::IsPowerOfTwo(block_size)); + AMS_ASSERT(buffer_count > 0); + + /* Get the base storage size. */ + R_TRY(base_storage.GetSize(std::addressof(m_base_storage_size))); + + /* Set members. */ + m_base_storage = base_storage; + m_buffer_manager = buffer_manager; + m_block_size = block_size; + m_cache_count = buffer_count; + + /* Allocate the caches. */ + m_caches.reset(new Cache[buffer_count]); + R_UNLESS(m_caches != nullptr, fs::ResultAllocationMemoryFailedInBufferedStorageA()); + + /* Initialize the caches. */ + for (auto i = 0; i < buffer_count; i++) { + m_caches[i].Initialize(this); + } + + m_next_acquire_cache = std::addressof(m_caches[0]); + R_SUCCEED(); + } + + void BufferedStorage::Finalize() { + m_base_storage = fs::SubStorage(); + m_base_storage_size = 0; + m_caches.reset(); + m_cache_count = 0; + m_next_fetch_cache = nullptr; + } + + Result BufferedStorage::Read(s64 offset, void *buffer, size_t size) { + AMS_ASSERT(this->IsInitialized()); + + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Do the read. */ + R_TRY(this->ReadCore(offset, buffer, size)); + R_SUCCEED(); + } + + Result BufferedStorage::Write(s64 offset, const void *buffer, size_t size) { + AMS_ASSERT(this->IsInitialized()); + + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Do the write. */ + R_TRY(this->WriteCore(offset, buffer, size)); + R_SUCCEED(); + } + + Result BufferedStorage::GetSize(s64 *out) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(this->IsInitialized()); + + *out = m_base_storage_size; + R_SUCCEED(); + } + + Result BufferedStorage::SetSize(s64 size) { + AMS_ASSERT(this->IsInitialized()); + const s64 prev_size = m_base_storage_size; + if (prev_size < size) { + /* Prepare to expand. */ + if (!util::IsAligned(prev_size, m_block_size)) { + SharedCache cache(this); + const auto invalidate_offset = prev_size; + const auto invalidate_size = size - prev_size; + if (cache.AcquireNextOverlappedCache(invalidate_offset, invalidate_size)) { + R_TRY(cache.Flush()); + cache.Invalidate(); + } + AMS_ASSERT(!cache.AcquireNextOverlappedCache(invalidate_offset, invalidate_size)); + } + } else if (size < prev_size) { + /* Prepare to do a shrink. */ + SharedCache cache(this); + const auto invalidate_offset = prev_size; + const auto invalidate_size = size - prev_size; + const auto is_fragment = util::IsAligned(size, m_block_size); + while (cache.AcquireNextOverlappedCache(invalidate_offset, invalidate_size)) { + if (is_fragment && cache.Hits(invalidate_offset, 1)) { + R_TRY(cache.Flush()); + } + cache.Invalidate(); + } + } + + /* Set the size. */ + R_TRY(m_base_storage.SetSize(size)); + + /* Get our new size. */ + s64 new_size = 0; + R_TRY(m_base_storage.GetSize(std::addressof(new_size))); + + m_base_storage_size = new_size; + R_SUCCEED(); + } + + Result BufferedStorage::Flush() { + AMS_ASSERT(this->IsInitialized()); + + /* Flush caches. */ + SharedCache cache(this); + while (cache.AcquireNextDirtyCache()) { + R_TRY(cache.Flush()); + } + + /* Flush the base storage. */ + R_TRY(m_base_storage.Flush()); + R_SUCCEED(); + } + + Result BufferedStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + AMS_ASSERT(this->IsInitialized()); + + /* Invalidate caches, if we should. */ + if (op_id == fs::OperationId::Invalidate) { + this->InvalidateCaches(); + } + + R_RETURN(m_base_storage.OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + } + + void BufferedStorage::InvalidateCaches() { + AMS_ASSERT(this->IsInitialized()); + + SharedCache cache(this); + while (cache.AcquireNextValidCache()) { + cache.Invalidate(); + } + } + + Result BufferedStorage::PrepareAllocation() { + const auto flush_threshold = m_buffer_manager->GetTotalSize() / 8; + if (m_buffer_manager->GetTotalAllocatableSize() < flush_threshold) { + R_TRY(this->Flush()); + } + R_SUCCEED(); + } + + Result BufferedStorage::ControlDirtiness() { + const auto flush_threshold = m_buffer_manager->GetTotalSize() / 4; + if (m_buffer_manager->GetTotalAllocatableSize() < flush_threshold) { + s32 dirty_count = 0; + SharedCache cache(this); + while (cache.AcquireNextDirtyCache()) { + if ((++dirty_count) > 1) { + R_TRY(cache.Flush()); + cache.Invalidate(); + } + } + } + R_SUCCEED(); + } + + Result BufferedStorage::ReadCore(s64 offset, void *buffer, size_t size) { + AMS_ASSERT(m_caches != nullptr); + AMS_ASSERT(buffer != nullptr); + + /* Validate the offset. */ + const auto base_storage_size = m_base_storage_size; + R_UNLESS(offset >= 0, fs::ResultInvalidOffset()); + R_UNLESS(offset <= base_storage_size, fs::ResultInvalidOffset()); + + /* Setup tracking variables. */ + size_t remaining_size = static_cast<size_t>(std::min<s64>(size, base_storage_size - offset)); + s64 cur_offset = offset; + s64 buf_offset = 0; + + /* Determine what caches are needed, if we have bulk read set. */ + if (m_bulk_read_enabled) { + /* Check head cache. */ + const auto head_cache_needed = this->ReadHeadCache(std::addressof(cur_offset), buffer, std::addressof(remaining_size), std::addressof(buf_offset)); + R_SUCCEED_IF(remaining_size == 0); + + /* Check tail cache. */ + const auto tail_cache_needed = this->ReadTailCache(cur_offset, buffer, std::addressof(remaining_size), buf_offset); + R_SUCCEED_IF(remaining_size == 0); + + /* Perform bulk reads. */ + constexpr size_t BulkReadSizeMax = 2_MB; + if (remaining_size <= BulkReadSizeMax) { + do { + /* Try to do a bulk read. */ + R_TRY_CATCH(this->BulkRead(cur_offset, static_cast<u8 *>(buffer) + buf_offset, remaining_size, head_cache_needed, tail_cache_needed)) { + R_CATCH(fs::ResultAllocationPooledBufferNotEnoughSize) { + /* If the read fails due to insufficient pooled buffer size, */ + /* then we want to fall back to the normal read path. */ + break; + } + } R_END_TRY_CATCH; + + R_SUCCEED(); + } while(0); + } + } + + /* Repeatedly read until we're done. */ + while (remaining_size > 0) { + /* Determine how much to read this iteration. */ + auto *cur_dst = static_cast<u8 *>(buffer) + buf_offset; + size_t cur_size = 0; + + if (!util::IsAligned(cur_offset, m_block_size)) { + const size_t aligned_size = m_block_size - (cur_offset & (m_block_size - 1)); + cur_size = std::min(aligned_size, remaining_size); + } else if (remaining_size < m_block_size) { + cur_size = remaining_size; + } else { + cur_size = util::AlignDown(remaining_size, m_block_size); + } + + if (cur_size <= m_block_size) { + SharedCache cache(this); + if (!cache.AcquireNextOverlappedCache(cur_offset, cur_size)) { + R_TRY(this->PrepareAllocation()); + while (true) { + R_UNLESS(cache.AcquireFetchableCache(), fs::ResultOutOfResource()); + + UniqueCache fetch_cache(this); + const auto upgrade_result = fetch_cache.Upgrade(cache); + R_TRY(upgrade_result.first); + if (upgrade_result.second) { + R_TRY(fetch_cache.Fetch(cur_offset)); + break; + } + } + R_TRY(this->ControlDirtiness()); + } + cache.Read(cur_offset, cur_dst, cur_size); + } else { + { + SharedCache cache(this); + while (cache.AcquireNextOverlappedCache(cur_offset, cur_size)) { + R_TRY(cache.Flush()); + cache.Invalidate(); + } + } + R_TRY(m_base_storage.Read(cur_offset, cur_dst, cur_size)); + } + + remaining_size -= cur_size; + cur_offset += cur_size; + buf_offset += cur_size; + } + + R_SUCCEED(); + } + + bool BufferedStorage::ReadHeadCache(s64 *offset, void *buffer, size_t *size, s64 *buffer_offset) { + AMS_ASSERT(offset != nullptr); + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(size != nullptr); + AMS_ASSERT(buffer_offset != nullptr); + + bool is_cache_needed = !util::IsAligned(*offset, m_block_size); + + while (*size > 0) { + size_t cur_size = 0; + + if (!util::IsAligned(*offset, m_block_size)) { + const s64 aligned_size = util::AlignUp(*offset, m_block_size) - *offset; + cur_size = std::min(aligned_size, static_cast<s64>(*size)); + } else if (*size < m_block_size) { + cur_size = *size; + } else { + cur_size = m_block_size; + } + + SharedCache cache(this); + if (!cache.AcquireNextOverlappedCache(*offset, cur_size)) { + break; + } + + cache.Read(*offset, static_cast<u8 *>(buffer) + *buffer_offset, cur_size); + *offset += cur_size; + *buffer_offset += cur_size; + *size -= cur_size; + is_cache_needed = false; + } + + return is_cache_needed; + } + + bool BufferedStorage::ReadTailCache(s64 offset, void *buffer, size_t *size, s64 buffer_offset) { + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(size != nullptr); + + bool is_cache_needed = !util::IsAligned(offset + *size, m_block_size); + + while (*size > 0) { + const s64 cur_offset_end = offset + *size; + size_t cur_size = 0; + + if (!util::IsAligned(cur_offset_end, m_block_size)) { + const s64 aligned_size = cur_offset_end - util::AlignDown(cur_offset_end, m_block_size); + cur_size = std::min(aligned_size, static_cast<s64>(*size)); + } else if (*size < m_block_size) { + cur_size = *size; + } else { + cur_size = m_block_size; + } + + const s64 cur_offset = cur_offset_end - static_cast<s64>(cur_size); + AMS_ASSERT(cur_offset >= 0); + + SharedCache cache(this); + if (!cache.AcquireNextOverlappedCache(cur_offset, cur_size)) { + break; + } + + cache.Read(cur_offset, static_cast<u8 *>(buffer) + buffer_offset + cur_offset - offset, cur_size); + *size -= cur_size; + is_cache_needed = false; + } + + return is_cache_needed; + } + + Result BufferedStorage::BulkRead(s64 offset, void *buffer, size_t size, bool head_cache_needed, bool tail_cache_needed) { + /* Determine aligned extents. */ + const s64 aligned_offset = util::AlignDown(offset, m_block_size); + const s64 aligned_offset_end = std::min(util::AlignUp(offset + static_cast<s64>(size), m_block_size), m_base_storage_size); + const s64 aligned_size = aligned_offset_end - aligned_offset; + + /* Allocate a work buffer. */ + char *work_buffer = nullptr; + PooledBuffer pooled_buffer; + if (offset == aligned_offset && size == static_cast<size_t>(aligned_size)) { + work_buffer = static_cast<char *>(buffer); + } else { + pooled_buffer.AllocateParticularlyLarge(static_cast<size_t>(aligned_size), 1); + R_UNLESS(static_cast<s64>(pooled_buffer.GetSize()) >= aligned_size, fs::ResultAllocationPooledBufferNotEnoughSize()); + work_buffer = pooled_buffer.GetBuffer(); + } + + /* Ensure cache is coherent. */ + { + SharedCache cache(this); + while (cache.AcquireNextOverlappedCache(aligned_offset, aligned_size)) { + R_TRY(cache.Flush()); + cache.Invalidate(); + } + } + + /* Read from the base storage. */ + R_TRY(m_base_storage.Read(aligned_offset, work_buffer, static_cast<size_t>(aligned_size))); + if (work_buffer != static_cast<char *>(buffer)) { + std::memcpy(buffer, work_buffer + offset - aligned_offset, size); + } + + bool cached = false; + + /* Handle head cache if needed. */ + if (head_cache_needed) { + R_TRY(this->PrepareAllocation()); + + SharedCache cache(this); + while (true) { + R_UNLESS(cache.AcquireFetchableCache(), fs::ResultOutOfResource()); + + UniqueCache fetch_cache(this); + const auto upgrade_result = fetch_cache.Upgrade(cache); + R_TRY(upgrade_result.first); + if (upgrade_result.second) { + R_TRY(fetch_cache.FetchFromBuffer(aligned_offset, work_buffer, static_cast<size_t>(aligned_size))); + break; + } + } + + cached = true; + } + + /* Handle tail cache if needed. */ + if (tail_cache_needed && (!head_cache_needed || aligned_size > static_cast<s64>(m_block_size))) { + if (!cached) { + R_TRY(this->PrepareAllocation()); + } + + SharedCache cache(this); + while (true) { + R_UNLESS(cache.AcquireFetchableCache(), fs::ResultOutOfResource()); + + UniqueCache fetch_cache(this); + const auto upgrade_result = fetch_cache.Upgrade(cache); + R_TRY(upgrade_result.first); + if (upgrade_result.second) { + const s64 tail_cache_offset = util::AlignDown(offset + static_cast<s64>(size), m_block_size); + const size_t tail_cache_size = static_cast<size_t>(aligned_size - tail_cache_offset + aligned_offset); + R_TRY(fetch_cache.FetchFromBuffer(tail_cache_offset, work_buffer + tail_cache_offset - aligned_offset, tail_cache_size)); + break; + } + } + } + + if (cached) { + R_TRY(this->ControlDirtiness()); + } + + R_SUCCEED(); + } + + Result BufferedStorage::WriteCore(s64 offset, const void *buffer, size_t size) { + AMS_ASSERT(m_caches != nullptr); + AMS_ASSERT(buffer != nullptr); + + /* Validate the offset. */ + const auto base_storage_size = m_base_storage_size; + R_UNLESS(offset >= 0, fs::ResultInvalidOffset()); + R_UNLESS(offset <= base_storage_size, fs::ResultInvalidOffset()); + + /* Setup tracking variables. */ + size_t remaining_size = static_cast<size_t>(std::min<s64>(size, base_storage_size - offset)); + s64 cur_offset = offset; + s64 buf_offset = 0; + + /* Repeatedly read until we're done. */ + while (remaining_size > 0) { + /* Determine how much to read this iteration. */ + const auto *cur_src = static_cast<const u8 *>(buffer) + buf_offset; + size_t cur_size = 0; + + if (!util::IsAligned(cur_offset, m_block_size)) { + const size_t aligned_size = m_block_size - (cur_offset & (m_block_size - 1)); + cur_size = std::min(aligned_size, remaining_size); + } else if (remaining_size < m_block_size) { + cur_size = remaining_size; + } else { + cur_size = util::AlignDown(remaining_size, m_block_size); + } + + if (cur_size <= m_block_size) { + SharedCache cache(this); + if (!cache.AcquireNextOverlappedCache(cur_offset, cur_size)) { + R_TRY(this->PrepareAllocation()); + while (true) { + R_UNLESS(cache.AcquireFetchableCache(), fs::ResultOutOfResource()); + + UniqueCache fetch_cache(this); + const auto upgrade_result = fetch_cache.Upgrade(cache); + R_TRY(upgrade_result.first); + if (upgrade_result.second) { + R_TRY(fetch_cache.Fetch(cur_offset)); + break; + } + } + } + cache.Write(cur_offset, cur_src, cur_size); + + buffers::EnableBlockingBufferManagerAllocation(); + + R_TRY(this->ControlDirtiness()); + } else { + { + SharedCache cache(this); + while (cache.AcquireNextOverlappedCache(cur_offset, cur_size)) { + R_TRY(cache.Flush()); + cache.Invalidate(); + } + } + + R_TRY(m_base_storage.Write(cur_offset, cur_src, cur_size)); + + buffers::EnableBlockingBufferManagerAllocation(); + } + + remaining_size -= cur_size; + cur_offset += cur_size; + buf_offset += cur_size; + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_compression_configuration.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_compression_configuration.cpp new file mode 100644 index 00000000..9f8a7fab --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_compression_configuration.cpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fssystem_key_slot_cache.hpp" + +namespace ams::fssystem { + + namespace { + + Result DecompressLz4(void *dst, size_t dst_size, const void *src, size_t src_size) { + R_UNLESS(util::DecompressLZ4(dst, dst_size, src, src_size) == static_cast<int>(dst_size), fs::ResultUnexpectedInCompressedStorageC()); + R_SUCCEED(); + } + + constexpr DecompressorFunction GetNcaDecompressorFunction(CompressionType type) { + switch (type) { + case CompressionType_Lz4: + return DecompressLz4; + default: + return nullptr; + } + } + + constexpr NcaCompressionConfiguration g_nca_compression_configuration { + .get_decompressor = GetNcaDecompressorFunction, + }; + + } + + const ::ams::fssystem::NcaCompressionConfiguration *GetNcaCompressionConfiguration() { + return std::addressof(g_nca_compression_configuration); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_crypto_configuration.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_crypto_configuration.cpp new file mode 100644 index 00000000..23e4d0b3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_crypto_configuration.cpp @@ -0,0 +1,336 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fssystem_key_slot_cache.hpp" + +namespace ams::fssystem { + + namespace { + + constexpr inline const size_t KeySize = crypto::AesDecryptor128::KeySize; + + constexpr inline const size_t NxAcidSignatureKeyGenerationMax = 1; + + constexpr inline const size_t NxAcidSignatureKeyModulusSize = NcaCryptoConfiguration::Rsa2048KeyModulusSize; + + constexpr inline const u8 NxAcidSignatureKeyModulusDev[NxAcidSignatureKeyGenerationMax + 1][NxAcidSignatureKeyModulusSize] = { + { + 0xD6, 0x34, 0xA5, 0x78, 0x6C, 0x68, 0xCE, 0x5A, 0xC2, 0x37, 0x17, 0xF3, 0x82, 0x45, 0xC6, 0x89, + 0xE1, 0x2D, 0x06, 0x67, 0xBF, 0xB4, 0x06, 0x19, 0x55, 0x6B, 0x27, 0x66, 0x0C, 0xA4, 0xB5, 0x87, + 0x81, 0x25, 0xF4, 0x30, 0xBC, 0x53, 0x08, 0x68, 0xA2, 0x48, 0x49, 0x8C, 0x3F, 0x38, 0x40, 0x9C, + 0xC4, 0x26, 0xF4, 0x79, 0xE2, 0xA1, 0x85, 0xF5, 0x5C, 0x7F, 0x58, 0xBA, 0xA6, 0x1C, 0xA0, 0x8B, + 0x84, 0x16, 0x14, 0x6F, 0x85, 0xD9, 0x7C, 0xE1, 0x3C, 0x67, 0x22, 0x1E, 0xFB, 0xD8, 0xA7, 0xA5, + 0x9A, 0xBF, 0xEC, 0x0E, 0xCF, 0x96, 0x7E, 0x85, 0xC2, 0x1D, 0x49, 0x5D, 0x54, 0x26, 0xCB, 0x32, + 0x7C, 0xF6, 0xBB, 0x58, 0x03, 0x80, 0x2B, 0x5D, 0xF7, 0xFB, 0xD1, 0x9D, 0xC7, 0xC6, 0x2E, 0x53, + 0xC0, 0x6F, 0x39, 0x2C, 0x1F, 0xA9, 0x92, 0xF2, 0x4D, 0x7D, 0x4E, 0x74, 0xFF, 0xE4, 0xEF, 0xE4, + 0x7C, 0x3D, 0x34, 0x2A, 0x71, 0xA4, 0x97, 0x59, 0xFF, 0x4F, 0xA2, 0xF4, 0x66, 0x78, 0xD8, 0xBA, + 0x99, 0xE3, 0xE6, 0xDB, 0x54, 0xB9, 0xE9, 0x54, 0xA1, 0x70, 0xFC, 0x05, 0x1F, 0x11, 0x67, 0x4B, + 0x26, 0x8C, 0x0C, 0x3E, 0x03, 0xD2, 0xA3, 0x55, 0x5C, 0x7D, 0xC0, 0x5D, 0x9D, 0xFF, 0x13, 0x2F, + 0xFD, 0x19, 0xBF, 0xED, 0x44, 0xC3, 0x8C, 0xA7, 0x28, 0xCB, 0xE5, 0xE0, 0xB1, 0xA7, 0x9C, 0x33, + 0x8D, 0xB8, 0x6E, 0xDE, 0x87, 0x18, 0x22, 0x60, 0xC4, 0xAE, 0xF2, 0x87, 0x9F, 0xCE, 0x09, 0x5C, + 0xB5, 0x99, 0xA5, 0x9F, 0x49, 0xF2, 0xD7, 0x58, 0xFA, 0xF9, 0xC0, 0x25, 0x7D, 0xD6, 0xCB, 0xF3, + 0xD8, 0x6C, 0xA2, 0x69, 0x91, 0x68, 0x73, 0xB1, 0x94, 0x6F, 0xA3, 0xF3, 0xB9, 0x7D, 0xF8, 0xE0, + 0x72, 0x9E, 0x93, 0x7B, 0x7A, 0xA2, 0x57, 0x60, 0xB7, 0x5B, 0xA9, 0x84, 0xAE, 0x64, 0x88, 0x69 + }, + { + 0xBC, 0xA5, 0x6A, 0x7E, 0xEA, 0x38, 0x34, 0x62, 0xA6, 0x10, 0x18, 0x3C, 0xE1, 0x63, 0x7B, 0xF0, + 0xD3, 0x08, 0x8C, 0xF5, 0xC5, 0xC4, 0xC7, 0x93, 0xE9, 0xD9, 0xE6, 0x32, 0xF3, 0xA0, 0xF6, 0x6E, + 0x8A, 0x98, 0x76, 0x47, 0x33, 0x47, 0x65, 0x02, 0x70, 0xDC, 0x86, 0x5F, 0x3D, 0x61, 0x5A, 0x70, + 0xBC, 0x5A, 0xCA, 0xCA, 0x50, 0xAD, 0x61, 0x7E, 0xC9, 0xEC, 0x27, 0xFF, 0xE8, 0x64, 0x42, 0x9A, + 0xEE, 0xBE, 0xC3, 0xD1, 0x0B, 0xC0, 0xE9, 0xBF, 0x83, 0x8D, 0xC0, 0x0C, 0xD8, 0x00, 0x5B, 0x76, + 0x90, 0xD2, 0x4B, 0x30, 0x84, 0x35, 0x8B, 0x1E, 0x20, 0xB7, 0xE4, 0xDC, 0x63, 0xE5, 0xDF, 0xCD, + 0x00, 0x5F, 0x81, 0x5F, 0x67, 0xC5, 0x8B, 0xDF, 0xFC, 0xE1, 0x37, 0x5F, 0x07, 0xD9, 0xDE, 0x4F, + 0xE6, 0x7B, 0xF1, 0xFB, 0xA1, 0x5A, 0x71, 0x40, 0xFE, 0xBA, 0x1E, 0xAE, 0x13, 0x22, 0xD2, 0xFE, + 0x37, 0xA2, 0xB6, 0x8B, 0xAB, 0xEB, 0x84, 0x81, 0x4E, 0x7C, 0x1E, 0x02, 0xD1, 0xFB, 0xD7, 0x5D, + 0x11, 0x84, 0x64, 0xD2, 0x4D, 0xBB, 0x50, 0x00, 0x67, 0x54, 0xE2, 0x77, 0x89, 0xBA, 0x0B, 0xE7, + 0x05, 0x57, 0x9A, 0x22, 0x5A, 0xEC, 0x76, 0x1C, 0xFD, 0xE8, 0xA8, 0x18, 0x16, 0x41, 0x65, 0x03, + 0xFA, 0xC4, 0xA6, 0x31, 0x5C, 0x1A, 0x7F, 0xAB, 0x11, 0xC8, 0x4A, 0x99, 0xB9, 0xE6, 0xCF, 0x62, + 0x21, 0xA6, 0x72, 0x47, 0xDB, 0xBA, 0x96, 0x26, 0x4E, 0x2E, 0xD4, 0x8C, 0x46, 0xD6, 0xA7, 0x1A, + 0x6C, 0x32, 0xA7, 0xDF, 0x85, 0x1C, 0x03, 0xC3, 0x6D, 0xA9, 0xE9, 0x68, 0xF4, 0x17, 0x1E, 0xB2, + 0x70, 0x2A, 0xA1, 0xE5, 0xE1, 0xF3, 0x8F, 0x6F, 0x63, 0xAC, 0xEB, 0x72, 0x0B, 0x4C, 0x4A, 0x36, + 0x3C, 0x60, 0x91, 0x9F, 0x6E, 0x1C, 0x71, 0xEA, 0xD0, 0x78, 0x78, 0xA0, 0x2E, 0xC6, 0x32, 0x6B + } + }; + + constexpr inline const u8 NxAcidSignatureKeyModulusProd[NxAcidSignatureKeyGenerationMax + 1][NxAcidSignatureKeyModulusSize] = { + { + 0xDD, 0xC8, 0xDD, 0xF2, 0x4E, 0x6D, 0xF0, 0xCA, 0x9E, 0xC7, 0x5D, 0xC7, 0x7B, 0xAD, 0xFE, 0x7D, + 0x23, 0x89, 0x69, 0xB6, 0xF2, 0x06, 0xA2, 0x02, 0x88, 0xE1, 0x55, 0x91, 0xAB, 0xCB, 0x4D, 0x50, + 0x2E, 0xFC, 0x9D, 0x94, 0x76, 0xD6, 0x4C, 0xD8, 0xFF, 0x10, 0xFA, 0x5E, 0x93, 0x0A, 0xB4, 0x57, + 0xAC, 0x51, 0xC7, 0x16, 0x66, 0xF4, 0x1A, 0x54, 0xC2, 0xC5, 0x04, 0x3D, 0x1B, 0xFE, 0x30, 0x20, + 0x8A, 0xAC, 0x6F, 0x6F, 0xF5, 0xC7, 0xB6, 0x68, 0xB8, 0xC9, 0x40, 0x6B, 0x42, 0xAD, 0x11, 0x21, + 0xE7, 0x8B, 0xE9, 0x75, 0x01, 0x86, 0xE4, 0x48, 0x9B, 0x0A, 0x0A, 0xF8, 0x7F, 0xE8, 0x87, 0xF2, + 0x82, 0x01, 0xE6, 0xA3, 0x0F, 0xE4, 0x66, 0xAE, 0x83, 0x3F, 0x4E, 0x9F, 0x5E, 0x01, 0x30, 0xA4, + 0x00, 0xB9, 0x9A, 0xAE, 0x5F, 0x03, 0xCC, 0x18, 0x60, 0xE5, 0xEF, 0x3B, 0x5E, 0x15, 0x16, 0xFE, + 0x1C, 0x82, 0x78, 0xB5, 0x2F, 0x47, 0x7C, 0x06, 0x66, 0x88, 0x5D, 0x35, 0xA2, 0x67, 0x20, 0x10, + 0xE7, 0x6C, 0x43, 0x68, 0xD3, 0xE4, 0x5A, 0x68, 0x2A, 0x5A, 0xE2, 0x6D, 0x73, 0xB0, 0x31, 0x53, + 0x1C, 0x20, 0x09, 0x44, 0xF5, 0x1A, 0x9D, 0x22, 0xBE, 0x12, 0xA1, 0x77, 0x11, 0xE2, 0xA1, 0xCD, + 0x40, 0x9A, 0xA2, 0x8B, 0x60, 0x9B, 0xEF, 0xA0, 0xD3, 0x48, 0x63, 0xA2, 0xF8, 0xA3, 0x2C, 0x08, + 0x56, 0x52, 0x2E, 0x60, 0x19, 0x67, 0x5A, 0xA7, 0x9F, 0xDC, 0x3F, 0x3F, 0x69, 0x2B, 0x31, 0x6A, + 0xB7, 0x88, 0x4A, 0x14, 0x84, 0x80, 0x33, 0x3C, 0x9D, 0x44, 0xB7, 0x3F, 0x4C, 0xE1, 0x75, 0xEA, + 0x37, 0xEA, 0xE8, 0x1E, 0x7C, 0x77, 0xB7, 0xC6, 0x1A, 0xA2, 0xF0, 0x9F, 0x10, 0x61, 0xCD, 0x7B, + 0x5B, 0x32, 0x4C, 0x37, 0xEF, 0xB1, 0x71, 0x68, 0x53, 0x0A, 0xED, 0x51, 0x7D, 0x35, 0x22, 0xFD + }, + { + 0xE7, 0xAA, 0x25, 0xC8, 0x01, 0xA5, 0x14, 0x6B, 0x01, 0x60, 0x3E, 0xD9, 0x96, 0x5A, 0xBF, 0x90, + 0xAC, 0xA7, 0xFD, 0x9B, 0x5B, 0xBD, 0x8A, 0x26, 0xB0, 0xCB, 0x20, 0x28, 0x9A, 0x72, 0x12, 0xF5, + 0x20, 0x65, 0xB3, 0xB9, 0x84, 0x58, 0x1F, 0x27, 0xBC, 0x7C, 0xA2, 0xC9, 0x9E, 0x18, 0x95, 0xCF, + 0xC2, 0x73, 0x2E, 0x74, 0x8C, 0x66, 0xE5, 0x9E, 0x79, 0x2B, 0xB8, 0x07, 0x0C, 0xB0, 0x4E, 0x8E, + 0xAB, 0x85, 0x21, 0x42, 0xC4, 0xC5, 0x6D, 0x88, 0x9C, 0xDB, 0x15, 0x95, 0x3F, 0x80, 0xDB, 0x7A, + 0x9A, 0x7D, 0x41, 0x56, 0x25, 0x17, 0x18, 0x42, 0x4D, 0x8C, 0xAC, 0xA5, 0x7B, 0xDB, 0x42, 0x5D, + 0x59, 0x35, 0x45, 0x5D, 0x8A, 0x02, 0xB5, 0x70, 0xC0, 0x72, 0x35, 0x46, 0xD0, 0x1D, 0x60, 0x01, + 0x4A, 0xCC, 0x1C, 0x46, 0xD3, 0xD6, 0x35, 0x52, 0xD6, 0xE1, 0xF8, 0x3B, 0x5D, 0xEA, 0xDD, 0xB8, + 0xFE, 0x7D, 0x50, 0xCB, 0x35, 0x23, 0x67, 0x8B, 0xB6, 0xE4, 0x74, 0xD2, 0x60, 0xFC, 0xFD, 0x43, + 0xBF, 0x91, 0x08, 0x81, 0xC5, 0x4F, 0x5D, 0x16, 0x9A, 0xC4, 0x9A, 0xC6, 0xF6, 0xF3, 0xE1, 0xF6, + 0x5C, 0x07, 0xAA, 0x71, 0x6C, 0x13, 0xA4, 0xB1, 0xB3, 0x66, 0xBF, 0x90, 0x4C, 0x3D, 0xA2, 0xC4, + 0x0B, 0xB8, 0x3D, 0x7A, 0x8C, 0x19, 0xFA, 0xFF, 0x6B, 0xB9, 0x1F, 0x02, 0xCC, 0xB6, 0xD3, 0x0C, + 0x7D, 0x19, 0x1F, 0x47, 0xF9, 0xC7, 0x40, 0x01, 0xFA, 0x46, 0xEA, 0x0B, 0xD4, 0x02, 0xE0, 0x3D, + 0x30, 0x9A, 0x1A, 0x0F, 0xEA, 0xA7, 0x66, 0x55, 0xF7, 0xCB, 0x28, 0xE2, 0xBB, 0x99, 0xE4, 0x83, + 0xC3, 0x43, 0x03, 0xEE, 0xDC, 0x1F, 0x02, 0x23, 0xDD, 0xD1, 0x2D, 0x39, 0xA4, 0x65, 0x75, 0x03, + 0xEF, 0x37, 0x9C, 0x06, 0xD6, 0xFA, 0xA1, 0x15, 0xF0, 0xDB, 0x17, 0x47, 0x26, 0x4F, 0x49, 0x03 + } + }; + + static_assert(sizeof(NxAcidSignatureKeyModulusProd) == sizeof(NxAcidSignatureKeyModulusDev)); + + constexpr inline const u8 AcidSignatureKeyPublicExponent[] = { + 0x01, 0x00, 0x01 + }; + + NcaCryptoConfiguration g_nca_crypto_configuration_dev; + NcaCryptoConfiguration g_nca_crypto_configuration_prod; + + constexpr inline s32 KeySlotCacheEntryCount = 3; + constinit KeySlotCache g_key_slot_cache; + constinit util::optional<KeySlotCacheEntry> g_key_slot_cache_entry[KeySlotCacheEntryCount]; + + spl::AccessKey &GetNcaKekAccessKey(s32 key_type) { + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(spl::AccessKey, s_nca_kek_access_key_array[KeyAreaEncryptionKeyCount]); + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(spl::AccessKey, s_nca_header_kek_access_key); + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(spl::AccessKey, s_invalid_nca_kek_access_key); + + if (key_type > static_cast<s32>(KeyType::NcaHeaderKey2) || IsInvalidKeyTypeValue(key_type)) { + return s_invalid_nca_kek_access_key; + } else if (key_type == static_cast<s32>(KeyType::NcaHeaderKey1) || key_type == static_cast<s32>(KeyType::NcaHeaderKey2)) { + return s_nca_header_kek_access_key; + } else { + return s_nca_kek_access_key_array[key_type]; + } + } + + void GenerateNcaKey(void *dst, size_t dst_size, const void *src, size_t src_size, s32 key_type) { + R_ABORT_UNLESS(spl::GenerateAesKey(dst, dst_size, GetNcaKekAccessKey(key_type), src, src_size)); + } + + void ComputeCtr(void *dst, size_t dst_size, int key_slot_idx, const void *src, size_t src_size, const void *iv, size_t iv_size) { + if (dst == src) { + /* If the destination and source are the same, we'll use an intermediate buffer. */ + constexpr size_t MinimumSizeToRequireLocking = 256_KB; + constexpr size_t MinimumWorkBufferSize = 16_KB; + + /* If the request is large enough, acquire a lock to prevent too many large requests in flight simultaneously. */ + static constinit os::SdkMutex s_large_work_buffer_mutex; + util::optional<std::scoped_lock<os::SdkMutex>> lk = util::nullopt; + if (dst_size >= MinimumSizeToRequireLocking) { + lk.emplace(s_large_work_buffer_mutex); + } + + /* Allocate a pooled buffer. */ + PooledBuffer pooled_buffer; + pooled_buffer.AllocateParticularlyLarge(dst_size, MinimumWorkBufferSize); + + /* Copy the iv locally. */ + AMS_ASSERT(iv_size == crypto::Aes128CtrEncryptor::IvSize); + u8 work_iv[crypto::Aes128CtrEncryptor::IvSize]; + std::memcpy(work_iv, iv, sizeof(work_iv)); + + /* Process all data. */ + size_t processed = 0; + while (processed < dst_size) { + /* Determine the currently processable size. */ + const size_t cur_size = std::min<size_t>(dst_size - processed, pooled_buffer.GetSize()); + + /* Process. */ + R_ABORT_UNLESS(spl::ComputeCtr(pooled_buffer.GetBuffer(), cur_size, key_slot_idx, static_cast<const u8 *>(src) + processed, cur_size, work_iv, sizeof(work_iv))); + + /* Copy to dst. */ + std::memcpy(static_cast<u8 *>(dst) + processed, pooled_buffer.GetBuffer(), cur_size); + + /* Advance. */ + processed += cur_size; + + /* Increment the counter. */ + fssystem::AddCounter(work_iv, sizeof(work_iv), cur_size / crypto::Aes128CtrEncryptor::BlockSize); + } + } else { + /* If the destination and source are different, we can just call ComputeCtr directly. */ + R_ABORT_UNLESS(spl::ComputeCtr(dst, dst_size, key_slot_idx, src, src_size, iv, iv_size)); + } + } + + void DecryptAesCtr(void *dst, size_t dst_size, u8 key_index, u8 key_generation, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + std::unique_ptr<KeySlotCacheAccessor> accessor; + + const s32 key_type = GetKeyTypeValue(key_index, key_generation); + + R_TRY_CATCH(g_key_slot_cache.Find(std::addressof(accessor), enc_key, enc_key_size, key_type)) { + R_CATCH(fs::ResultTargetNotFound) { + R_ABORT_UNLESS(g_key_slot_cache.AllocateHighPriority(std::addressof(accessor), enc_key, enc_key_size, key_type)); + R_ABORT_UNLESS(spl::LoadAesKey(accessor->GetKeySlotIndex(), GetNcaKekAccessKey(key_type), enc_key, enc_key_size)); + } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + ComputeCtr(dst, dst_size, accessor->GetKeySlotIndex(), src, src_size, iv, iv_size); + } + + void DecryptAesCtrForPreparedKey(void *dst, size_t dst_size, u8 key_index, u8 key_generation, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + std::unique_ptr<KeySlotCacheAccessor> accessor; + + AMS_UNUSED(key_index, key_generation); + const s32 key_type = static_cast<s32>(KeyType::NcaExternalKey); + + R_TRY_CATCH(g_key_slot_cache.Find(std::addressof(accessor), enc_key, enc_key_size, key_type)) { + R_CATCH(fs::ResultTargetNotFound) { + R_ABORT_UNLESS(g_key_slot_cache.AllocateHighPriority(std::addressof(accessor), enc_key, enc_key_size, key_type)); + + spl::AccessKey access_key; + AMS_ABORT_UNLESS(enc_key_size == sizeof(access_key)); + std::memcpy(std::addressof(access_key), enc_key, sizeof(access_key)); + R_ABORT_UNLESS(spl::LoadPreparedAesKey(accessor->GetKeySlotIndex(), access_key)); + } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + ComputeCtr(dst, dst_size, accessor->GetKeySlotIndex(), src, src_size, iv, iv_size); + } + + bool VerifySign1Prod(const void *sig, size_t sig_size, const void *data, size_t data_size, u8 generation) { + const u8 *mod = g_nca_crypto_configuration_prod.header_1_sign_key_moduli[generation]; + const size_t mod_size = NcaCryptoConfiguration::Rsa2048KeyModulusSize; + const u8 *exp = g_nca_crypto_configuration_prod.header_1_sign_key_public_exponent; + const size_t exp_size = NcaCryptoConfiguration::Rsa2048KeyPublicExponentSize; + + return crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, data, data_size); + } + + bool VerifySign1Dev(const void *sig, size_t sig_size, const void *data, size_t data_size, u8 generation) { + const u8 *mod = g_nca_crypto_configuration_dev.header_1_sign_key_moduli[generation]; + const size_t mod_size = NcaCryptoConfiguration::Rsa2048KeyModulusSize; + const u8 *exp = g_nca_crypto_configuration_dev.header_1_sign_key_public_exponent; + const size_t exp_size = NcaCryptoConfiguration::Rsa2048KeyPublicExponentSize; + + return crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, data, data_size); + } + + } + + const ::ams::fssystem::NcaCryptoConfiguration *GetNcaCryptoConfiguration(bool prod) { + /* Decide which configuration to use. */ + NcaCryptoConfiguration * const cfg = prod ? std::addressof(g_nca_crypto_configuration_prod) : std::addressof(g_nca_crypto_configuration_dev); + std::memcpy(cfg, fssrv::GetDefaultNcaCryptoConfiguration(prod), sizeof(NcaCryptoConfiguration)); + + /* Set the key generation functions. */ + cfg->generate_key = GenerateNcaKey; + cfg->decrypt_aes_xts_external = nullptr; + cfg->encrypt_aes_xts_external = nullptr; + cfg->decrypt_aes_ctr = DecryptAesCtr; + cfg->decrypt_aes_ctr_external = DecryptAesCtrForPreparedKey; + cfg->verify_sign1 = prod ? VerifySign1Prod : VerifySign1Dev; + cfg->is_plaintext_header_available = !prod; + cfg->is_available_sw_key = true; + + /* TODO: Should this default to false for host tools with api to set explicitly? */ + #if !defined(ATMOSPHERE_BOARD_NINTENDO_NX) + cfg->is_unsigned_header_available_for_host_tool = true; + #endif + + return cfg; + } + + void SetUpKekAccessKeys(bool prod) { + /* Get the crypto configuration. */ + const NcaCryptoConfiguration *nca_crypto_cfg = GetNcaCryptoConfiguration(prod); + + /* Setup the nca keys. */ + { + constexpr s32 Option = 0; + + /* Setup the key area encryption keys. */ + for (u8 i = 0; i < NcaCryptoConfiguration::KeyGenerationMax; ++i) { + spl::GenerateAesKek(std::addressof(GetNcaKekAccessKey(GetKeyTypeValue(0, i))), nca_crypto_cfg->key_area_encryption_key_source[0], KeySize, i, Option); + spl::GenerateAesKek(std::addressof(GetNcaKekAccessKey(GetKeyTypeValue(1, i))), nca_crypto_cfg->key_area_encryption_key_source[1], KeySize, i, Option); + spl::GenerateAesKek(std::addressof(GetNcaKekAccessKey(GetKeyTypeValue(2, i))), nca_crypto_cfg->key_area_encryption_key_source[2], KeySize, i, Option); + } + + /* Setup the header encryption key. */ + R_ABORT_UNLESS(spl::GenerateAesKek(std::addressof(GetNcaKekAccessKey(static_cast<s32>(KeyType::NcaHeaderKey1))), nca_crypto_cfg->header_encryption_key_source, KeySize, 0, Option)); + } + + /* TODO FS-REIMPL: Save stuff. */ + + /* Setup the keyslot cache. */ + for (s32 i = 0; i < KeySlotCacheEntryCount; i++) { + s32 slot_index = -1; + R_ABORT_UNLESS(spl::AllocateAesKeySlot(std::addressof(slot_index))); + g_key_slot_cache_entry[i].emplace(slot_index); + g_key_slot_cache.AddEntry(std::addressof(g_key_slot_cache_entry[i].value())); + } + } + + void InvalidateHardwareAesKey() { + constexpr u8 InvalidKey[KeySize] = {}; + for (s32 i = 0; i < KeySlotCacheEntryCount; ++i) { + std::unique_ptr<KeySlotCacheAccessor> accessor; + R_ABORT_UNLESS(g_key_slot_cache.AllocateHighPriority(std::addressof(accessor), InvalidKey, KeySize, -1 - i)); + } + } + + bool IsValidSignatureKeyGeneration(ncm::ContentMetaPlatform platform, size_t key_generation) { + switch (platform) { + case ncm::ContentMetaPlatform::Nx: + return key_generation <= NxAcidSignatureKeyGenerationMax; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + const u8 *GetAcidSignatureKeyModulus(ncm::ContentMetaPlatform platform, bool prod, size_t key_generation, bool unk_unused) { + AMS_ASSERT(IsValidSignatureKeyGeneration(platform, key_generation)); + AMS_UNUSED(unk_unused); + + switch (platform) { + case ncm::ContentMetaPlatform::Nx: + { + const size_t used_keygen = (key_generation % (NxAcidSignatureKeyGenerationMax + 1)); + return prod ? NxAcidSignatureKeyModulusProd[used_keygen] : NxAcidSignatureKeyModulusDev[used_keygen]; + } + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + size_t GetAcidSignatureKeyModulusSize(ncm::ContentMetaPlatform platform, bool unk_unused) { + AMS_UNUSED(unk_unused); + + switch (platform) { + case ncm::ContentMetaPlatform::Nx: + return NxAcidSignatureKeyModulusSize; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + const u8 *GetAcidSignatureKeyPublicExponent() { + return AcidSignatureKeyPublicExponent; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_directory_savedata_filesystem.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_directory_savedata_filesystem.cpp new file mode 100644 index 00000000..ac470f24 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_directory_savedata_filesystem.cpp @@ -0,0 +1,366 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + namespace { + + constexpr size_t IdealWorkBufferSize = 1_MB; + constexpr size_t MinimumWorkBufferSize = 1_KB; + + constexpr const fs::Path CommittedDirectoryPath = fs::MakeConstantPath("/0"); + constexpr const fs::Path WorkingDirectoryPath = fs::MakeConstantPath("/1"); + constexpr const fs::Path SynchronizingDirectoryPath = fs::MakeConstantPath("/_"); + constexpr const fs::Path LockFilePath = fs::MakeConstantPath("/.lock"); + + class DirectorySaveDataFile : public fs::fsa::IFile, public fs::impl::Newable { + private: + std::unique_ptr<fs::fsa::IFile> m_base_file; + DirectorySaveDataFileSystem *m_parent_fs; + fs::OpenMode m_open_mode; + public: + DirectorySaveDataFile(std::unique_ptr<fs::fsa::IFile> f, DirectorySaveDataFileSystem *p, fs::OpenMode m) : m_base_file(std::move(f)), m_parent_fs(p), m_open_mode(m) { + /* ... */ + } + + virtual ~DirectorySaveDataFile() { + /* Observe closing of writable file. */ + if (m_open_mode & fs::OpenMode_Write) { + m_parent_fs->DecrementWriteOpenFileCount(); + } + } + public: + virtual Result DoRead(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override { + R_RETURN(m_base_file->Read(out, offset, buffer, size, option)); + } + + virtual Result DoGetSize(s64 *out) override { + R_RETURN(m_base_file->GetSize(out)); + } + + virtual Result DoFlush() override { + R_RETURN(m_base_file->Flush()); + } + + virtual Result DoWrite(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override { + R_RETURN(m_base_file->Write(offset, buffer, size, option)); + } + + virtual Result DoSetSize(s64 size) override { + R_RETURN(m_base_file->SetSize(size)); + } + + virtual Result DoOperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + R_RETURN(m_base_file->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override { + return m_base_file->GetDomainObjectId(); + } + }; + + } + + Result DirectorySaveDataFileSystem::Initialize(bool journaling_supported, bool multi_commit_supported, bool journaling_enabled) { + /* Configure ourselves. */ + m_is_journaling_supported = journaling_supported; + m_is_multi_commit_supported = multi_commit_supported; + m_is_journaling_enabled = journaling_enabled; + + /* Ensure that we can initialize further by acquiring a lock on the filesystem. */ + R_TRY(this->AcquireLockFile()); + + fs::DirectoryEntryType type; + + /* Check that the working directory exists. */ + R_TRY_CATCH(m_base_fs->GetEntryType(std::addressof(type), WorkingDirectoryPath)) { + /* If path isn't found, create working directory and committed directory. */ + R_CATCH(fs::ResultPathNotFound) { + R_TRY(m_base_fs->CreateDirectory(WorkingDirectoryPath)); + + if (m_is_journaling_supported) { + R_TRY(m_base_fs->CreateDirectory(CommittedDirectoryPath)); + } + } + } R_END_TRY_CATCH; + + /* If we support journaling, we need to set up the committed directory. */ + if (m_is_journaling_supported) { + /* Now check for the committed directory. */ + R_TRY_CATCH(m_base_fs->GetEntryType(std::addressof(type), CommittedDirectoryPath)) { + /* Committed doesn't exist, so synchronize and rename. */ + R_CATCH(fs::ResultPathNotFound) { + R_TRY(this->SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath)); + R_TRY(m_base_fs->RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath)); + R_SUCCEED(); + } + } R_END_TRY_CATCH; + + /* The committed directory exists, so if we should, synchronize it to the working directory. */ + if (m_is_journaling_enabled) { + R_TRY(this->SynchronizeDirectory(WorkingDirectoryPath, CommittedDirectoryPath)); + } + } + + R_SUCCEED(); + } + + Result DirectorySaveDataFileSystem::SynchronizeDirectory(const fs::Path &dst, const fs::Path &src) { + /* Delete destination dir and recreate it. */ + R_TRY_CATCH(m_base_fs->DeleteDirectoryRecursively(dst)) { + R_CATCH(fs::ResultPathNotFound) { /* Nintendo returns error unconditionally, but I think that's a bug in their code. */} + } R_END_TRY_CATCH; + + R_TRY(m_base_fs->CreateDirectory(dst)); + + /* Get a work buffer to work with. */ + fssystem::PooledBuffer buffer; + buffer.AllocateParticularlyLarge(IdealWorkBufferSize, MinimumWorkBufferSize); + + /* Copy the directory recursively. */ + fs::DirectoryEntry dir_entry_buffer = {}; + R_RETURN(fssystem::CopyDirectoryRecursively(m_base_fs, dst, src, std::addressof(dir_entry_buffer), buffer.GetBuffer(), buffer.GetSize())); + } + + Result DirectorySaveDataFileSystem::ResolvePath(fs::Path *out, const fs::Path &path) { + const fs::Path &directory = (m_is_journaling_supported && !m_is_journaling_enabled) ? CommittedDirectoryPath : WorkingDirectoryPath; + R_RETURN(out->Combine(directory, path)); + } + + Result DirectorySaveDataFileSystem::AcquireLockFile() { + /* If we already have a lock file, we don't need to lock again. */ + R_SUCCEED_IF(m_lock_file != nullptr); + + /* Open the lock file. */ + std::unique_ptr<fs::fsa::IFile> file; + R_TRY_CATCH(m_base_fs->OpenFile(std::addressof(file), LockFilePath, fs::OpenMode_ReadWrite)) { + /* If the lock file doesn't yet exist, we may need to create it. */ + R_CATCH(fs::ResultPathNotFound) { + R_TRY(m_base_fs->CreateFile(LockFilePath, 0)); + R_TRY(m_base_fs->OpenFile(std::addressof(file), LockFilePath, fs::OpenMode_ReadWrite)); + } + } R_END_TRY_CATCH; + + /* Set our lock file. */ + m_lock_file = std::move(file); + R_SUCCEED(); + } + + void DirectorySaveDataFileSystem::DecrementWriteOpenFileCount() { + std::scoped_lock lk(m_accessor_mutex); + + --m_open_writable_files; + } + + Result DirectorySaveDataFileSystem::DoCreateFile(const fs::Path &path, s64 size, int option) { + /* Resolve the final path. */ + fs::Path resolved; + R_TRY(this->ResolvePath(std::addressof(resolved), path)); + + /* Lock ourselves. */ + std::scoped_lock lk(m_accessor_mutex); + + R_RETURN(m_base_fs->CreateFile(resolved, size, option)); + } + Result DirectorySaveDataFileSystem::DoDeleteFile(const fs::Path &path) { + /* Resolve the final path. */ + fs::Path resolved; + R_TRY(this->ResolvePath(std::addressof(resolved), path)); + + /* Lock ourselves. */ + std::scoped_lock lk(m_accessor_mutex); + + R_RETURN(m_base_fs->DeleteFile(resolved)); + } + + Result DirectorySaveDataFileSystem::DoCreateDirectory(const fs::Path &path) { + /* Resolve the final path. */ + fs::Path resolved; + R_TRY(this->ResolvePath(std::addressof(resolved), path)); + + /* Lock ourselves. */ + std::scoped_lock lk(m_accessor_mutex); + + R_RETURN(m_base_fs->CreateDirectory(resolved)); + } + + Result DirectorySaveDataFileSystem::DoDeleteDirectory(const fs::Path &path) { + /* Resolve the final path. */ + fs::Path resolved; + R_TRY(this->ResolvePath(std::addressof(resolved), path)); + + /* Lock ourselves. */ + std::scoped_lock lk(m_accessor_mutex); + + R_RETURN(m_base_fs->DeleteDirectory(resolved)); + } + + Result DirectorySaveDataFileSystem::DoDeleteDirectoryRecursively(const fs::Path &path) { + /* Resolve the final path. */ + fs::Path resolved; + R_TRY(this->ResolvePath(std::addressof(resolved), path)); + + /* Lock ourselves. */ + std::scoped_lock lk(m_accessor_mutex); + + R_RETURN(m_base_fs->DeleteDirectoryRecursively(resolved)); + } + + Result DirectorySaveDataFileSystem::DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) { + /* Resolve the final paths. */ + fs::Path old_resolved; + fs::Path new_resolved; + R_TRY(this->ResolvePath(std::addressof(old_resolved), old_path)); + R_TRY(this->ResolvePath(std::addressof(new_resolved), new_path)); + + /* Lock ourselves. */ + std::scoped_lock lk(m_accessor_mutex); + + R_RETURN(m_base_fs->RenameFile(old_resolved, new_resolved)); + } + + Result DirectorySaveDataFileSystem::DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) { + /* Resolve the final paths. */ + fs::Path old_resolved; + fs::Path new_resolved; + R_TRY(this->ResolvePath(std::addressof(old_resolved), old_path)); + R_TRY(this->ResolvePath(std::addressof(new_resolved), new_path)); + + /* Lock ourselves. */ + std::scoped_lock lk(m_accessor_mutex); + + R_RETURN(m_base_fs->RenameDirectory(old_resolved, new_resolved)); + } + + Result DirectorySaveDataFileSystem::DoGetEntryType(fs::DirectoryEntryType *out, const fs::Path &path) { + /* Resolve the final path. */ + fs::Path resolved; + R_TRY(this->ResolvePath(std::addressof(resolved), path)); + + /* Lock ourselves. */ + std::scoped_lock lk(m_accessor_mutex); + + R_RETURN(m_base_fs->GetEntryType(out, resolved)); + } + + Result DirectorySaveDataFileSystem::DoOpenFile(std::unique_ptr<fs::fsa::IFile> *out_file, const fs::Path &path, fs::OpenMode mode) { + /* Resolve the final path. */ + fs::Path resolved; + R_TRY(this->ResolvePath(std::addressof(resolved), path)); + + /* Lock ourselves. */ + std::scoped_lock lk(m_accessor_mutex); + + /* Open base file. */ + std::unique_ptr<fs::fsa::IFile> base_file; + R_TRY(m_base_fs->OpenFile(std::addressof(base_file), resolved, mode)); + + /* Make DirectorySaveDataFile. */ + std::unique_ptr<fs::fsa::IFile> file = std::make_unique<DirectorySaveDataFile>(std::move(base_file), this, mode); + R_UNLESS(file != nullptr, fs::ResultAllocationMemoryFailedInDirectorySaveDataFileSystemA()); + + /* Increment our open writable files, if the file is writable. */ + if (mode & fs::OpenMode_Write) { + ++m_open_writable_files; + } + + /* Set the output. */ + *out_file = std::move(file); + R_SUCCEED(); + } + + Result DirectorySaveDataFileSystem::DoOpenDirectory(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const fs::Path &path, fs::OpenDirectoryMode mode) { + /* Resolve the final path. */ + fs::Path resolved; + R_TRY(this->ResolvePath(std::addressof(resolved), path)); + + /* Lock ourselves. */ + std::scoped_lock lk(m_accessor_mutex); + + R_RETURN(m_base_fs->OpenDirectory(out_dir, resolved, mode)); + } + + Result DirectorySaveDataFileSystem::DoCommit() { + /* Lock ourselves. */ + std::scoped_lock lk(m_accessor_mutex); + + /* If we aren't journaling, we don't need to do anything. */ + R_SUCCEED_IF(!m_is_journaling_enabled); + R_SUCCEED_IF(!m_is_journaling_supported); + + /* Check that there are no open files blocking the commit. */ + R_UNLESS(m_open_writable_files == 0, fs::ResultWriteModeFileNotClosed()); + + /* Remove the previous commit by renaming the folder. */ + R_TRY(fssystem::RetryFinitelyForTargetLocked([&] () ALWAYS_INLINE_LAMBDA { R_RETURN(m_base_fs->RenameDirectory(CommittedDirectoryPath, SynchronizingDirectoryPath)); })); + + /* Synchronize the working directory to the synchronizing directory. */ + R_TRY(fssystem::RetryFinitelyForTargetLocked([&] () ALWAYS_INLINE_LAMBDA { R_RETURN(this->SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath)); })); + + /* Rename the synchronized directory to commit it. */ + R_TRY(fssystem::RetryFinitelyForTargetLocked([&] () ALWAYS_INLINE_LAMBDA { R_RETURN(m_base_fs->RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath)); })); + + R_SUCCEED(); + } + + Result DirectorySaveDataFileSystem::DoGetFreeSpaceSize(s64 *out, const fs::Path &path) { + /* Lock ourselves. */ + std::scoped_lock lk(m_accessor_mutex); + + /* Get the free space size in our working directory. */ + AMS_UNUSED(path); + R_RETURN(m_base_fs->GetFreeSpaceSize(out, WorkingDirectoryPath)); + } + + Result DirectorySaveDataFileSystem::DoGetTotalSpaceSize(s64 *out, const fs::Path &path) { + /* Lock ourselves. */ + std::scoped_lock lk(m_accessor_mutex); + + /* Get the free space size in our working directory. */ + AMS_UNUSED(path); + R_RETURN(m_base_fs->GetTotalSpaceSize(out, WorkingDirectoryPath)); + } + + Result DirectorySaveDataFileSystem::DoCleanDirectoryRecursively(const fs::Path &path) { + /* Resolve the final path. */ + fs::Path resolved; + R_TRY(this->ResolvePath(std::addressof(resolved), path)); + + /* Lock ourselves. */ + std::scoped_lock lk(m_accessor_mutex); + + R_RETURN(m_base_fs->CleanDirectoryRecursively(resolved)); + } + + Result DirectorySaveDataFileSystem::DoCommitProvisionally(s64 counter) { + /* Check that we support multi-commit. */ + R_UNLESS(m_is_multi_commit_supported, fs::ResultUnsupportedCommitProvisionallyForDirectorySaveDataFileSystem()); + + /* Do nothing. */ + AMS_UNUSED(counter); + R_SUCCEED(); + } + + Result DirectorySaveDataFileSystem::DoRollback() { + /* On non-journaled savedata, there's nothing to roll back to. */ + R_SUCCEED_IF(!m_is_journaling_supported); + + /* Perform a re-initialize. */ + R_RETURN(this->Initialize(m_is_journaling_supported, m_is_multi_commit_supported, m_is_journaling_enabled)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_external_code.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_external_code.cpp new file mode 100644 index 00000000..2f907839 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_external_code.cpp @@ -0,0 +1,93 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + namespace { + + constexpr inline size_t MaxExternalCodeFileSystem = 0x10; + + util::BoundedMap<ncm::ProgramId, fs::RemoteFileSystem, MaxExternalCodeFileSystem> g_ecs_map; + util::BoundedMap<ncm::ProgramId, os::NativeHandle, MaxExternalCodeFileSystem> g_hnd_map; + + } + + + fs::fsa::IFileSystem *GetExternalCodeFileSystem(ncm::ProgramId program_id) { + /* Return a fs from the map if one exists. */ + if (auto *fs = g_ecs_map.Find(program_id); fs != nullptr) { + return fs; + } + + /* Otherwise, we may have a handle. */ + if (auto *hnd = g_hnd_map.Find(program_id); hnd != nullptr) { + /* Create a service using libnx bindings. */ + Service srv; + serviceCreate(std::addressof(srv), *hnd); + g_hnd_map.Remove(program_id); + + /* Create a remote filesystem. */ + const FsFileSystem fs = { srv }; + g_ecs_map.Emplace(program_id, fs); + + /* Return the created filesystem. */ + return g_ecs_map.Find(program_id); + } + + /* Otherwise, we have no filesystem. */ + return nullptr; + } + + Result CreateExternalCode(os::NativeHandle *out, ncm::ProgramId program_id) { + /* Create a handle pair. */ + os::NativeHandle server, client; + R_TRY(svc::CreateSession(std::addressof(server), std::addressof(client), false, 0)); + + /* Insert the handle into the map. */ + g_hnd_map.Emplace(program_id, client); + + *out = server; + R_SUCCEED(); + } + + void DestroyExternalCode(ncm::ProgramId program_id) { + g_ecs_map.Remove(program_id); + + if (auto *hnd = g_hnd_map.Find(program_id); hnd != nullptr) { + os::CloseNativeHandle(*hnd); + g_hnd_map.Remove(program_id); + } + } + #else + fs::fsa::IFileSystem *GetExternalCodeFileSystem(ncm::ProgramId program_id) { + AMS_UNUSED(program_id); + return nullptr; + } + + Result CreateExternalCode(os::NativeHandle *out, ncm::ProgramId program_id) { + AMS_UNUSED(out, program_id); + R_THROW(fs::ResultNotImplemented()); + } + + void DestroyExternalCode(ncm::ProgramId program_id) { + AMS_UNUSED(program_id); + } + #endif + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_file_system_proxy_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_file_system_proxy_api.cpp new file mode 100644 index 00000000..0e6539f7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_file_system_proxy_api.cpp @@ -0,0 +1,287 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../fssrv/impl/fssrv_program_registry_manager.hpp" + +namespace ams::fssystem { + + /* TODO: All of this should really be inside fs process, but ams.mitm wants it to. */ + /* How should we handle this? */ + + namespace { + + constexpr inline auto FileSystemProxyServerThreadCount = fssrv::FileSystemProxyServerActiveSessionCount; + + /* TODO: Heap sizes need to match FS, when this is FS in master rather than ams.mitm. */ + + /* Official FS has a 4.5 MB exp heap, a 6 MB buffer pool, an 8 MB device buffer manager heap, and a 14 MB buffer manager heap. */ + /* We don't need so much memory for ams.mitm (as we're servicing a much more limited context). */ + /* We'll give ourselves a 1.5 MB exp heap, a 1 MB buffer pool, a 1 MB device buffer manager heap, and a 1 MB buffer manager heap. */ + /* These numbers are 1 MB less than signed-system-partition safe FS in all pools. */ + constexpr size_t ExpHeapSize = 1_MB + 512_KB; + constexpr size_t BufferPoolSize = 1_MB; + constexpr size_t DeviceBufferSize = 1_MB; + constexpr size_t BufferManagerHeapSize = 1_MB; + + constexpr size_t MaxCacheCount = 1024; + constexpr size_t BlockSize = 16_KB; + + alignas(os::MemoryPageSize) constinit u8 g_exp_heap_buffer[ExpHeapSize]; + constinit lmem::HeapHandle g_exp_heap_handle = nullptr; + constinit fssrv::PeakCheckableMemoryResourceFromExpHeap g_exp_allocator(ExpHeapSize); + + void InitializeExpHeap() { + if (g_exp_heap_handle == nullptr) { + g_exp_heap_handle = lmem::CreateExpHeap(g_exp_heap_buffer, ExpHeapSize, lmem::CreateOption_ThreadSafe); + AMS_ABORT_UNLESS(g_exp_heap_handle != nullptr); + g_exp_allocator.SetHeapHandle(g_exp_heap_handle); + } + } + + void *AllocateForFileSystemProxy(size_t size) { + AMS_ABORT_UNLESS(g_exp_heap_handle != nullptr); + + auto scoped_lock = g_exp_allocator.GetScopedLock(); + + void *p = lmem::AllocateFromExpHeap(g_exp_heap_handle, size); + g_exp_allocator.OnAllocate(p, size); + return p; + } + + void DeallocateForFileSystemProxy(void *p, size_t size) { + AMS_ABORT_UNLESS(g_exp_heap_handle != nullptr); + + auto scoped_lock = g_exp_allocator.GetScopedLock(); + + g_exp_allocator.OnDeallocate(p, size); + lmem::FreeToExpHeap(g_exp_heap_handle, p); + } + + alignas(os::MemoryPageSize) constinit u8 g_device_buffer[DeviceBufferSize] = {}; + + alignas(os::MemoryPageSize) constinit u8 g_buffer_pool[BufferPoolSize] = {}; + + constinit util::TypedStorage<mem::StandardAllocator> g_buffer_allocator = {}; + constinit util::TypedStorage<fssrv::MemoryResourceFromStandardAllocator> g_allocator = {}; + + /* TODO: Nintendo uses os::SetMemoryHeapSize (svc::SetHeapSize) and os::AllocateMemoryBlock for the BufferManager heap. */ + /* It's unclear how we should handle this in ams.mitm (especially hoping to reuse some logic for fs reimpl). */ + /* Should we be doing the same(?) */ + constinit util::TypedStorage<fssystem::FileSystemBufferManager> g_buffer_manager = {}; + alignas(os::MemoryPageSize) constinit u8 g_buffer_manager_heap[BufferManagerHeapSize] = {}; + + /* FileSystem creators. */ + constinit util::TypedStorage<fssrv::fscreator::RomFileSystemCreator> g_rom_fs_creator = {}; + constinit util::TypedStorage<fssrv::fscreator::PartitionFileSystemCreator> g_partition_fs_creator = {}; + constinit util::TypedStorage<fssrv::fscreator::StorageOnNcaCreator> g_storage_on_nca_creator = {}; + + constinit fssrv::fscreator::FileSystemCreatorInterfaces g_fs_creator_interfaces = {}; + + } + + void InitializeForFileSystemProxy() { + /* TODO FS-REIMPL: Setup MainThreadStackUsageReporter. */ + + /* Register service context for main thread. */ + fssystem::ServiceContext context; + fssystem::RegisterServiceContext(std::addressof(context)); + + /* Initialize spl library. */ + spl::InitializeForFs(); + /* TODO FS-REIMPL: spl::SetIsAvailableAccessKeyHandler(fssrv::IsAvailableAccessKey) */ + + /* Determine whether we're prod or dev. */ + bool is_prod = !spl::IsDevelopment(); + bool is_development_function_enabled = spl::IsDevelopmentFunctionEnabled(); + + /* Set debug flags. */ + fssrv::SetDebugFlagEnabled(is_development_function_enabled); + + /* Setup our crypto configuration. */ + SetUpKekAccessKeys(is_prod); + + /* Setup our heap. */ + InitializeExpHeap(); + + /* Initialize buffer allocator. */ + util::ConstructAt(g_buffer_allocator, g_buffer_pool, BufferPoolSize); + util::ConstructAt(g_allocator, GetPointer(g_buffer_allocator)); + + /* Set allocators. */ + /* TODO FS-REIMPL: sf::SetGlobalDefaultMemoryResource() */ + fs::SetAllocator(AllocateForFileSystemProxy, DeallocateForFileSystemProxy); + fssystem::InitializeAllocator(AllocateForFileSystemProxy, DeallocateForFileSystemProxy); + fssystem::InitializeAllocatorForSystem(AllocateForFileSystemProxy, DeallocateForFileSystemProxy); + + /* Initialize the buffer manager. */ + /* TODO FS-REIMPL: os::AllocateMemoryBlock(...); */ + util::ConstructAt(g_buffer_manager); + GetReference(g_buffer_manager).Initialize(MaxCacheCount, reinterpret_cast<uintptr_t>(g_buffer_manager_heap), BufferManagerHeapSize, BlockSize); + + /* TODO FS-REIMPL: os::AllocateMemoryBlock(...); */ + /* TODO FS-REIMPL: fssrv::storage::CreateDeviceAddressSpace(...); */ + fssystem::InitializeBufferPool(reinterpret_cast<char *>(g_device_buffer), DeviceBufferSize); + + /* TODO FS-REIMPL: Create Pooled Threads/Stack Usage Reporter, fssystem::RegisterThreadPool. */ + + /* TODO FS-REIMPL: fssrv::GetFileSystemProxyServices(), some service creation. */ + + /* Initialize fs creators. */ + /* TODO FS-REIMPL: Revise for accuracy. */ + util::ConstructAt(g_rom_fs_creator, GetPointer(g_allocator)); + util::ConstructAt(g_partition_fs_creator); + util::ConstructAt(g_storage_on_nca_creator, GetPointer(g_allocator), *GetNcaCryptoConfiguration(is_prod), *GetNcaCompressionConfiguration(), GetPointer(g_buffer_manager), fs::impl::GetNcaHashGeneratorFactorySelector()); + + /* TODO FS-REIMPL: Initialize other creators. */ + + g_fs_creator_interfaces = { + .rom_fs_creator = GetPointer(g_rom_fs_creator), + .partition_fs_creator = GetPointer(g_partition_fs_creator), + .storage_on_nca_creator = GetPointer(g_storage_on_nca_creator), + }; + + /* TODO FS-REIMPL: Revise above for latest firmware, all the new Services creation. */ + fssrv::ProgramRegistryServiceImpl program_registry_service(fssrv::ProgramRegistryServiceImpl::Configuration{}); + fssrv::ProgramRegistryImpl::Initialize(std::addressof(program_registry_service)); + + /* TODO FS-REIMPL: Memory Report Creators, fssrv::SetMemoryReportCreator */ + + /* TODO FS-REIMPL: Sd Card detection, speed emulation. */ + + /* Initialize fssrv. TODO FS-REIMPL: More arguments, more actions taken. */ + const fssrv::FileSystemProxyConfiguration config = { + .m_fs_creator_interfaces = std::addressof(g_fs_creator_interfaces), + .m_base_storage_service_impl = nullptr /* TODO */, + .m_base_file_system_service_impl = nullptr /* TODO */, + .m_nca_file_system_service_impl = nullptr /* TODO */, + .m_save_data_file_system_service_impl = nullptr /* TODO */, + .m_access_failure_management_service_impl = nullptr /* TODO */, + .m_time_service_impl = nullptr /* TODO */, + .m_status_report_service_impl = nullptr /* TODO */, + .m_program_registry_service_impl = std::addressof(program_registry_service), + .m_access_log_service_impl = nullptr /* TODO */, + .m_debug_configuration_service_impl = nullptr /* TODO */, + }; + + fssrv::InitializeForFileSystemProxy(config); + + /* TODO FS-REIMPL: GetFileSystemProxyServiceObject(), set current process, initialize global service object. */ + + /* Disable auto-abort in fs library code. */ + fs::SetEnabledAutoAbort(false); + + /* Initialize fsp server. */ + fssrv::InitializeFileSystemProxyServer(FileSystemProxyServerThreadCount); + + /* TODO FS-REIMPL: Cleanup calls. */ + + /* TODO FS-REIMPL: Spawn worker threads. */ + + /* TODO FS-REIMPL: Set mmc devices ready. */ + + /* TODO FS-REIMPL: fssrv::LoopPmEventServer(...); */ + + /* TODO FS-REIMPL: Wait/destroy threads. */ + + /* TODO FS-REIMPL: spl::Finalize(); */ + } + + void InitializeForAtmosphereMitm() { + /* Initialize spl library. */ + spl::InitializeForFs(); + + /* TODO FS-REIMPL: spl::SetIsAvailableAccessKeyHandler(fssrv::IsAvailableAccessKey) */ + + /* Determine whether we're prod or dev. */ + bool is_prod = !spl::IsDevelopment(); + bool is_development_function_enabled = spl::IsDevelopmentFunctionEnabled(); + + /* Set debug flags. */ + fssrv::SetDebugFlagEnabled(is_development_function_enabled); + + /* Setup our crypto configuration. */ + SetUpKekAccessKeys(is_prod); + + /* Setup our heap. */ + InitializeExpHeap(); + + /* Initialize buffer allocator. */ + util::ConstructAt(g_buffer_allocator, g_buffer_pool, BufferPoolSize); + util::ConstructAt(g_allocator, GetPointer(g_buffer_allocator)); + + /* Set allocators. */ + /* TODO FS-REIMPL: sf::SetGlobalDefaultMemoryResource() */ + fs::SetAllocator(AllocateForFileSystemProxy, DeallocateForFileSystemProxy); + fssystem::InitializeAllocator(AllocateForFileSystemProxy, DeallocateForFileSystemProxy); + fssystem::InitializeAllocatorForSystem(AllocateForFileSystemProxy, DeallocateForFileSystemProxy); + + /* Initialize the buffer manager. */ + /* TODO FS-REIMPL: os::AllocateMemoryBlock(...); */ + util::ConstructAt(g_buffer_manager); + GetReference(g_buffer_manager).Initialize(MaxCacheCount, reinterpret_cast<uintptr_t>(g_buffer_manager_heap), BufferManagerHeapSize, BlockSize); + + /* TODO FS-REIMPL: os::AllocateMemoryBlock(...); */ + /* TODO FS-REIMPL: fssrv::storage::CreateDeviceAddressSpace(...); */ + fssystem::InitializeBufferPool(reinterpret_cast<char *>(g_device_buffer), DeviceBufferSize); + + /* TODO FS-REIMPL: Create Pooled Threads/Stack Usage Reporter, fssystem::RegisterThreadPool. */ + + /* TODO FS-REIMPL: fssrv::GetFileSystemProxyServices(), some service creation. */ + + /* Initialize fs creators. */ + /* TODO FS-REIMPL: Revise for accuracy. */ + util::ConstructAt(g_rom_fs_creator, GetPointer(g_allocator)); + util::ConstructAt(g_partition_fs_creator); + util::ConstructAt(g_storage_on_nca_creator, GetPointer(g_allocator), *GetNcaCryptoConfiguration(is_prod), *GetNcaCompressionConfiguration(), GetPointer(g_buffer_manager), fs::impl::GetNcaHashGeneratorFactorySelector()); + + /* TODO FS-REIMPL: Initialize other creators. */ + g_fs_creator_interfaces = { + .rom_fs_creator = GetPointer(g_rom_fs_creator), + .partition_fs_creator = GetPointer(g_partition_fs_creator), + .storage_on_nca_creator = GetPointer(g_storage_on_nca_creator), + }; + + /* Initialize fssrv. TODO FS-REIMPL: More arguments, more actions taken. */ + const fssrv::FileSystemProxyConfiguration config = { + .m_fs_creator_interfaces = std::addressof(g_fs_creator_interfaces), + .m_base_storage_service_impl = nullptr /* TODO */, + .m_base_file_system_service_impl = nullptr /* TODO */, + .m_nca_file_system_service_impl = nullptr /* TODO */, + .m_save_data_file_system_service_impl = nullptr /* TODO */, + .m_access_failure_management_service_impl = nullptr /* TODO */, + .m_time_service_impl = nullptr /* TODO */, + .m_status_report_service_impl = nullptr /* TODO */, + .m_program_registry_service_impl = nullptr /* TODO */, + .m_access_log_service_impl = nullptr /* TODO */, + .m_debug_configuration_service_impl = nullptr /* TODO */, + }; + + fssrv::InitializeForFileSystemProxy(config); + + /* Disable auto-abort in fs library code. */ + fs::SetEnabledAutoAbort(false); + + /* Quick sanity check, before we leave. */ + #if defined(ATMOSPHERE_OS_HORIZON) + AMS_ABORT_UNLESS(os::GetCurrentProgramId() == ncm::AtmosphereProgramId::Mitm); + #endif + } + + const ::ams::fssrv::fscreator::FileSystemCreatorInterfaces *GetFileSystemCreatorInterfaces() { + return std::addressof(g_fs_creator_interfaces); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp new file mode 100644 index 00000000..a9c5533f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp @@ -0,0 +1,354 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + namespace { + + constexpr inline u32 IntegrityVerificationStorageMagic = util::FourCC<'I','V','F','C'>::Code; + constexpr inline u32 IntegrityVerificationStorageVersion = 0x00020000; + constexpr inline u32 IntegrityVerificationStorageVersionMask = 0xFFFF0000; + + constexpr inline auto AccessCountMax = 5; + constexpr inline auto AccessTimeout = TimeSpan::FromMilliSeconds(10); + + os::Semaphore g_read_semaphore(AccessCountMax, AccessCountMax); + os::Semaphore g_write_semaphore(AccessCountMax, AccessCountMax); + + constexpr inline const char MasterKey[] = "HierarchicalIntegrityVerificationStorage::Master"; + constexpr inline const char L1Key[] = "HierarchicalIntegrityVerificationStorage::L1"; + constexpr inline const char L2Key[] = "HierarchicalIntegrityVerificationStorage::L2"; + constexpr inline const char L3Key[] = "HierarchicalIntegrityVerificationStorage::L3"; + constexpr inline const char L4Key[] = "HierarchicalIntegrityVerificationStorage::L4"; + constexpr inline const char L5Key[] = "HierarchicalIntegrityVerificationStorage::L5"; + + constexpr inline const struct { + const char *key; + size_t size; + } KeyArray[] = { + { MasterKey, sizeof(MasterKey) }, + { L1Key, sizeof(L1Key) }, + { L2Key, sizeof(L2Key) }, + { L3Key, sizeof(L3Key) }, + { L4Key, sizeof(L4Key) }, + { L5Key, sizeof(L5Key) }, + }; + + } + + /* Instantiate the global random generation function. */ + constinit HierarchicalIntegrityVerificationStorage::GenerateRandomFunction HierarchicalIntegrityVerificationStorage::s_generate_random = nullptr; + + Result HierarchicalIntegrityVerificationStorageControlArea::QuerySize(HierarchicalIntegrityVerificationSizeSet *out, const InputParam &input_param, s32 layer_count, s64 data_size) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT((static_cast<s32>(IntegrityMinLayerCount) <= layer_count) && (layer_count <= static_cast<s32>(IntegrityMaxLayerCount))); + for (s32 level = 0; level < (layer_count - 1); ++level) { + AMS_ASSERT(input_param.level_block_size[level] > 0); + AMS_ASSERT(IsPowerOfTwo(static_cast<s32>(input_param.level_block_size[level]))); + } + + /* Set the control size. */ + out->control_size = sizeof(HierarchicalIntegrityVerificationMetaInformation); + + /* Determine the level sizes. */ + s64 level_size[IntegrityMaxLayerCount]; + s32 level = layer_count - 1; + + level_size[level] = util::AlignUp(data_size, input_param.level_block_size[level - 1]); + --level; + + for (/* ... */; level > 0; --level) { + level_size[level] = util::AlignUp(level_size[level + 1] / input_param.level_block_size[level] * HashSize, input_param.level_block_size[level - 1]); + } + + /* Determine the master size. */ + level_size[0] = level_size[1] / input_param.level_block_size[0] * HashSize; + + /* Set the master size. */ + out->master_hash_size = level_size[0]; + + /* Set the level sizes. */ + for (level = 1; level < layer_count - 1; ++level) { + out->layered_hash_sizes[level - 1] = level_size[level]; + } + + R_SUCCEED(); + } + + Result HierarchicalIntegrityVerificationStorageControlArea::Expand(fs::SubStorage meta_storage, const HierarchicalIntegrityVerificationMetaInformation &meta) { + /* Check the meta size. */ + { + s64 meta_size = 0; + R_TRY(meta_storage.GetSize(std::addressof(meta_size))); + R_UNLESS(meta_size >= static_cast<s64>(sizeof(meta)), fs::ResultInvalidSize()); + } + + /* Validate both the previous and new metas. */ + { + /* Read the previous meta. */ + HierarchicalIntegrityVerificationMetaInformation prev_meta = {}; + R_TRY(meta_storage.Read(0, std::addressof(prev_meta), sizeof(prev_meta))); + + /* Validate both magics. */ + R_UNLESS(prev_meta.magic == IntegrityVerificationStorageMagic, fs::ResultIncorrectIntegrityVerificationMagic()); + R_UNLESS(prev_meta.magic == meta.magic, fs::ResultIncorrectIntegrityVerificationMagic()); + + /* Validate both versions. */ + R_UNLESS(prev_meta.version == IntegrityVerificationStorageVersion, fs::ResultUnsupportedVersion()); + R_UNLESS(prev_meta.version == meta.version, fs::ResultUnsupportedVersion()); + } + + /* Write the new meta. */ + R_TRY(meta_storage.Write(0, std::addressof(meta), sizeof(meta))); + R_TRY(meta_storage.Flush()); + + R_SUCCEED(); + } + + Result HierarchicalIntegrityVerificationStorageControlArea::Initialize(fs::SubStorage meta_storage) { + /* Check the meta size. */ + { + s64 meta_size = 0; + R_TRY(meta_storage.GetSize(std::addressof(meta_size))); + R_UNLESS(meta_size >= static_cast<s64>(sizeof(m_meta)), fs::ResultInvalidSize()); + } + + /* Set the storage and read the meta. */ + m_storage = meta_storage; + R_TRY(m_storage.Read(0, std::addressof(m_meta), sizeof(m_meta))); + + /* Validate the meta magic. */ + R_UNLESS(m_meta.magic == IntegrityVerificationStorageMagic, fs::ResultIncorrectIntegrityVerificationMagic()); + + /* Validate the meta version. */ + R_UNLESS((m_meta.version & IntegrityVerificationStorageVersionMask) == (IntegrityVerificationStorageVersion & IntegrityVerificationStorageVersionMask), fs::ResultUnsupportedVersion()); + + R_SUCCEED(); + } + + void HierarchicalIntegrityVerificationStorageControlArea::Finalize() { + m_storage = fs::SubStorage(); + } + + Result HierarchicalIntegrityVerificationStorage::Initialize(const HierarchicalIntegrityVerificationInformation &info, HierarchicalStorageInformation storage, FileSystemBufferManagerSet *bufs, IHash256GeneratorFactory *hgf, bool hash_salt_enabled, os::SdkRecursiveMutex *mtx, os::Semaphore *read_sema, os::Semaphore *write_sema, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, bool is_writable, bool allow_cleared_blocks) { + /* Validate preconditions. */ + AMS_ASSERT(bufs != nullptr); + AMS_ASSERT(IntegrityMinLayerCount <= info.max_layers && info.max_layers <= IntegrityMaxLayerCount); + + /* Set member variables. */ + m_max_layers = info.max_layers; + m_buffers = bufs; + m_mutex = mtx; + m_read_semaphore = read_sema; + m_write_semaphore = write_sema; + + /* If hash salt is enabled, generate it. */ + util::optional<fs::HashSalt> hash_salt = util::nullopt; + if (hash_salt_enabled) { + hash_salt.emplace(); + crypto::GenerateHmacSha256(hash_salt->value, sizeof(hash_salt->value), info.seed.value, sizeof(info.seed), KeyArray[0].key, KeyArray[0].size); + } + + /* Initialize the top level verification storage. */ + m_verify_storages[0].Initialize(storage[HierarchicalStorageInformation::MasterStorage], storage[HierarchicalStorageInformation::Layer1Storage], static_cast<s64>(1) << info.info[0].block_order, HashSize, m_buffers->buffers[m_max_layers - 2], hgf, hash_salt, false, is_writable, allow_cleared_blocks); + + /* Ensure we don't leak state if further initialization goes wrong. */ + ON_RESULT_FAILURE { + m_verify_storages[0].Finalize(); + + m_data_size = -1; + m_buffers = nullptr; + m_mutex = nullptr; + }; + + /* Initialize the top level buffer storage. */ + R_TRY(m_buffer_storages[0].Initialize(m_buffers->buffers[0], m_mutex, std::addressof(m_verify_storages[0]), info.info[0].size, static_cast<s64>(1) << info.info[0].block_order, max_hash_cache_entries, false, 0x10, false, is_writable)); + ON_RESULT_FAILURE_2 { m_buffer_storages[0].Finalize(); }; + + /* Prepare to initialize the level storages. */ + s32 level = 0; + + /* Ensure we don't leak state if further initialization goes wrong. */ + ON_RESULT_FAILURE_2 { + m_verify_storages[level + 1].Finalize(); + for (/* ... */; level > 0; --level) { + m_buffer_storages[level].Finalize(); + m_verify_storages[level].Finalize(); + } + }; + + /* Initialize the level storages. */ + for (/* ... */; level < m_max_layers - 3; ++level) { + /* If hash salt is enabled, generate it. */ + util::optional<fs::HashSalt> hash_salt = util::nullopt; + if (hash_salt_enabled) { + hash_salt.emplace(); + crypto::GenerateHmacSha256(hash_salt->value, sizeof(hash_salt->value), info.seed.value, sizeof(info.seed), KeyArray[level + 1].key, KeyArray[level + 1].size); + } + + /* Initialize the verification storage. */ + fs::SubStorage buffer_storage(std::addressof(m_buffer_storages[level]), 0, info.info[level].size); + m_verify_storages[level + 1].Initialize(buffer_storage, storage[level + 2], static_cast<s64>(1) << info.info[level + 1].block_order, static_cast<s64>(1) << info.info[level].block_order, m_buffers->buffers[m_max_layers - 2], hgf, hash_salt, false, is_writable, allow_cleared_blocks); + + /* Initialize the buffer storage. */ + R_TRY(m_buffer_storages[level + 1].Initialize(m_buffers->buffers[level + 1], m_mutex, std::addressof(m_verify_storages[level + 1]), info.info[level + 1].size, static_cast<s64>(1) << info.info[level + 1].block_order, max_hash_cache_entries, false, 0x11 + static_cast<s8>(level), false, is_writable)); + } + + /* Initialize the final level storage. */ + { + /* If hash salt is enabled, generate it. */ + util::optional<fs::HashSalt> hash_salt = util::nullopt; + if (hash_salt_enabled) { + hash_salt.emplace(); + crypto::GenerateHmacSha256(hash_salt->value, sizeof(hash_salt->value), info.seed.value, sizeof(info.seed), KeyArray[level + 1].key, KeyArray[level + 1].size); + } + + /* Initialize the verification storage. */ + fs::SubStorage buffer_storage(std::addressof(m_buffer_storages[level]), 0, info.info[level].size); + m_verify_storages[level + 1].Initialize(buffer_storage, storage[level + 2], static_cast<s64>(1) << info.info[level + 1].block_order, static_cast<s64>(1) << info.info[level].block_order, m_buffers->buffers[m_max_layers - 2], hgf, hash_salt, true, is_writable, allow_cleared_blocks); + + /* Initialize the buffer storage. */ + R_TRY(m_buffer_storages[level + 1].Initialize(m_buffers->buffers[level + 1], m_mutex, std::addressof(m_verify_storages[level + 1]), info.info[level + 1].size, static_cast<s64>(1) << info.info[level + 1].block_order, max_data_cache_entries, true, buffer_level, true, is_writable)); + } + + /* Set the data size. */ + m_data_size = info.info[level + 1].size; + + /* We succeeded. */ + R_SUCCEED(); + } + + void HierarchicalIntegrityVerificationStorage::Finalize() { + if (m_data_size >= 0) { + m_data_size = 0; + + m_buffers = nullptr; + m_mutex = nullptr; + + for (s32 level = m_max_layers - 2; level >= 0; --level) { + m_buffer_storages[level].Finalize(); + m_verify_storages[level].Finalize(); + } + + m_data_size = -1; + } + } + + Result HierarchicalIntegrityVerificationStorage::Read(s64 offset, void *buffer, size_t size) { + /* Validate preconditions. */ + AMS_ASSERT(m_data_size >= 0); + + /* Succeed if zero-size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* If we have a read semaphore, acquire it. */ + if (m_read_semaphore != nullptr) { m_read_semaphore->Acquire(); } + ON_SCOPE_EXIT { if (m_read_semaphore != nullptr) { m_read_semaphore->Release(); } }; + + /* Acquire access to the global read semaphore. */ + if (!g_read_semaphore.TimedAcquire(AccessTimeout)) { + for (auto level = m_max_layers - 2; level >= 0; --level) { + R_TRY(m_buffer_storages[level].Flush()); + } + g_read_semaphore.Acquire(); + } + + /* Ensure that we release the semaphore when done. */ + ON_SCOPE_EXIT { g_read_semaphore.Release(); }; + + /* Read the data. */ + R_RETURN(m_buffer_storages[m_max_layers - 2].Read(offset, buffer, size)); + } + + Result HierarchicalIntegrityVerificationStorage::Write(s64 offset, const void *buffer, size_t size) { + /* Validate preconditions. */ + AMS_ASSERT(m_data_size >= 0); + + /* Succeed if zero-size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* If we have a write semaphore, acquire it. */ + if (m_write_semaphore != nullptr) { m_write_semaphore->Acquire(); } + ON_SCOPE_EXIT { if (m_write_semaphore != nullptr) { m_write_semaphore->Release(); } }; + + /* Acquire access to the write semaphore. */ + if (!g_write_semaphore.TimedAcquire(AccessTimeout)) { + for (auto level = m_max_layers - 2; level >= 0; --level) { + R_TRY(m_buffer_storages[level].Flush()); + } + g_write_semaphore.Acquire(); + } + + /* Ensure that we release the semaphore when done. */ + ON_SCOPE_EXIT { g_write_semaphore.Release(); }; + + /* Write the data. */ + R_RETURN(m_buffer_storages[m_max_layers - 2].Write(offset, buffer, size)); + } + + Result HierarchicalIntegrityVerificationStorage::GetSize(s64 *out) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(m_data_size >= 0); + *out = m_data_size; + R_SUCCEED(); + } + + Result HierarchicalIntegrityVerificationStorage::Flush() { + R_SUCCEED(); + } + + Result HierarchicalIntegrityVerificationStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + switch (op_id) { + case fs::OperationId::FillZero: + case fs::OperationId::DestroySignature: + { + R_TRY(m_buffer_storages[m_max_layers - 2].OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + R_SUCCEED(); + } + case fs::OperationId::Invalidate: + case fs::OperationId::QueryRange: + { + R_TRY(m_buffer_storages[m_max_layers - 2].OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + R_SUCCEED(); + } + default: + R_THROW(fs::ResultUnsupportedOperateRangeForHierarchicalIntegrityVerificationStorage()); + } + } + + Result HierarchicalIntegrityVerificationStorage::Commit() { + for (s32 level = m_max_layers - 2; level >= 0; --level) { + R_TRY(m_buffer_storages[level].Commit()); + } + R_SUCCEED(); + } + + Result HierarchicalIntegrityVerificationStorage::OnRollback() { + for (s32 level = m_max_layers - 2; level >= 0; --level) { + R_TRY(m_buffer_storages[level].OnRollback()); + } + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_hierarchical_sha256_storage.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_hierarchical_sha256_storage.cpp new file mode 100644 index 00000000..448aa340 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_hierarchical_sha256_storage.cpp @@ -0,0 +1,197 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fssystem_hierarchical_sha256_storage.hpp" + +namespace ams::fssystem { + + namespace { + + s32 Log2(s32 value) { + AMS_ASSERT(value > 0); + AMS_ASSERT(util::IsPowerOfTwo(value)); + + s32 log = 0; + while ((value >>= 1) > 0) { + ++log; + } + return log; + } + + } + + template<typename BaseStorageType> + Result HierarchicalSha256Storage<BaseStorageType>::Initialize(BaseStorageType *base_storages, s32 layer_count, size_t htbs, void *hash_buf, size_t hash_buf_size, fssystem::IHash256GeneratorFactory *hgf) { + /* Validate preconditions. */ + AMS_ASSERT(layer_count == LayerCount); + AMS_ASSERT(util::IsPowerOfTwo(htbs)); + AMS_ASSERT(hash_buf != nullptr); + AMS_ASSERT(hgf != nullptr); + AMS_UNUSED(layer_count); + + /* Set size tracking members. */ + m_hash_target_block_size = htbs; + m_log_size_ratio = Log2(m_hash_target_block_size / HashSize); + m_hash_generator_factory = hgf; + + /* Get the base storage size. */ + R_TRY(base_storages[2]->GetSize(std::addressof(m_base_storage_size))); + { + auto size_guard = SCOPE_GUARD { m_base_storage_size = 0; }; + R_UNLESS(m_base_storage_size <= static_cast<s64>(HashSize) << m_log_size_ratio << m_log_size_ratio, fs::ResultHierarchicalSha256BaseStorageTooLarge()); + size_guard.Cancel(); + } + + /* Set hash buffer tracking members. */ + m_base_storage = base_storages[2]; + m_hash_buffer = static_cast<char *>(hash_buf); + m_hash_buffer_size = hash_buf_size; + + /* Read the master hash. */ + u8 master_hash[HashSize]; + R_TRY(base_storages[0]->Read(0, master_hash, HashSize)); + + /* Read and validate the data being hashed. */ + s64 hash_storage_size; + R_TRY(base_storages[1]->GetSize(std::addressof(hash_storage_size))); + AMS_ASSERT(util::IsAligned(hash_storage_size, HashSize)); + AMS_ASSERT(hash_storage_size <= m_hash_target_block_size); + AMS_ASSERT(hash_storage_size <= static_cast<s64>(m_hash_buffer_size)); + + R_TRY(base_storages[1]->Read(0, m_hash_buffer, static_cast<size_t>(hash_storage_size))); + + /* Calculate and verify the master hash. */ + u8 calc_hash[HashSize]; + m_hash_generator_factory->GenerateHash(calc_hash, sizeof(calc_hash), m_hash_buffer, static_cast<size_t>(hash_storage_size)); + R_UNLESS(crypto::IsSameBytes(master_hash, calc_hash, HashSize), fs::ResultHierarchicalSha256HashVerificationFailed()); + + R_SUCCEED(); + } + + template<typename BaseStorageType> + Result HierarchicalSha256Storage<BaseStorageType>::Read(s64 offset, void *buffer, size_t size) { + /* Succeed if zero-size. */ + R_SUCCEED_IF(size == 0); + + /* Validate that we have a buffer to read into. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Validate preconditions. */ + R_UNLESS(util::IsAligned(offset, m_hash_target_block_size), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, m_hash_target_block_size), fs::ResultInvalidArgument()); + + /* Read the data. */ + const size_t reduced_size = static_cast<size_t>(std::min<s64>(m_base_storage_size, util::AlignUp(offset + size, m_hash_target_block_size)) - offset); + R_TRY(m_base_storage->Read(offset, buffer, reduced_size)); + + /* Temporarily increase our thread priority. */ + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + /* Setup tracking variables. */ + auto cur_offset = offset; + auto remaining_size = reduced_size; + while (remaining_size > 0) { + /* Generate the hash of the region we're validating. */ + u8 hash[HashSize]; + const auto cur_size = static_cast<size_t>(std::min<s64>(m_hash_target_block_size, remaining_size)); + m_hash_generator_factory->GenerateHash(hash, sizeof(hash), static_cast<u8 *>(buffer) + (cur_offset - offset), cur_size); + + AMS_ASSERT(static_cast<size_t>(cur_offset >> m_log_size_ratio) < m_hash_buffer_size); + + /* Check the hash. */ + { + std::scoped_lock lk(m_mutex); + auto clear_guard = SCOPE_GUARD { std::memset(buffer, 0, size); }; + + R_UNLESS(crypto::IsSameBytes(hash, std::addressof(m_hash_buffer[cur_offset >> m_log_size_ratio]), HashSize), fs::ResultHierarchicalSha256HashVerificationFailed()); + + clear_guard.Cancel(); + } + + /* Advance. */ + cur_offset += cur_size; + remaining_size -= cur_size; + } + + R_SUCCEED(); + } + + template<typename BaseStorageType> + Result HierarchicalSha256Storage<BaseStorageType>::Write(s64 offset, const void *buffer, size_t size) { + /* Succeed if zero-size. */ + R_SUCCEED_IF(size == 0); + + /* Validate that we have a buffer to read into. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Validate preconditions. */ + R_UNLESS(util::IsAligned(offset, m_hash_target_block_size), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, m_hash_target_block_size), fs::ResultInvalidArgument()); + + /* Setup tracking variables. */ + const size_t reduced_size = static_cast<size_t>(std::min<s64>(m_base_storage_size, util::AlignUp(offset + size, m_hash_target_block_size)) - offset); + auto cur_offset = offset; + auto remaining_size = reduced_size; + while (remaining_size > 0) { + /* Generate the hash of the region we're validating. */ + u8 hash[HashSize]; + const auto cur_size = static_cast<size_t>(std::min<s64>(m_hash_target_block_size, remaining_size)); + { + /* Temporarily increase our thread priority. */ + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + m_hash_generator_factory->GenerateHash(hash, sizeof(hash), static_cast<const u8 *>(buffer) + (cur_offset - offset), cur_size); + } + + /* Write the data. */ + R_TRY(m_base_storage->Write(cur_offset, static_cast<const u8 *>(buffer) + (cur_offset - offset), cur_size)); + + /* Write the hash. */ + { + std::scoped_lock lk(m_mutex); + std::memcpy(std::addressof(m_hash_buffer[cur_offset >> m_log_size_ratio]), hash, HashSize); + } + + /* Advance. */ + cur_offset += cur_size; + remaining_size -= cur_size; + } + + R_SUCCEED(); + } + + template<typename BaseStorageType> + Result HierarchicalSha256Storage<BaseStorageType>::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + if (op_id == fs::OperationId::Invalidate) { + R_RETURN(m_base_storage->OperateRange(fs::OperationId::Invalidate, offset, size)); + } else { + /* Succeed if zero-size. */ + R_SUCCEED_IF(size == 0); + + /* Validate preconditions. */ + R_UNLESS(util::IsAligned(offset, m_hash_target_block_size), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, m_hash_target_block_size), fs::ResultInvalidArgument()); + + /* Determine size to use. */ + const auto reduced_size = std::min<s64>(m_base_storage_size, util::AlignUp(offset + size, m_hash_target_block_size)) - offset; + + /* Operate on the base storage. */ + R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, offset, reduced_size, src, src_size)); + } + } + + template class HierarchicalSha256Storage<fs::SubStorage>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_hierarchical_sha256_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_hierarchical_sha256_storage.hpp new file mode 100644 index 00000000..6c872a91 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_hierarchical_sha256_storage.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fssystem { + + template<typename BaseStorageType> + class HierarchicalSha256Storage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(HierarchicalSha256Storage); + NON_MOVEABLE(HierarchicalSha256Storage); + public: + static constexpr s32 LayerCount = 3; + static constexpr size_t HashSize = crypto::Sha256Generator::HashSize; + private: + BaseStorageType m_base_storage; + s64 m_base_storage_size; + char *m_hash_buffer; + size_t m_hash_buffer_size; + s32 m_hash_target_block_size; + s32 m_log_size_ratio; + fssystem::IHash256GeneratorFactory *m_hash_generator_factory; + os::SdkMutex m_mutex; + public: + HierarchicalSha256Storage() : m_mutex() { /* ... */ } + + Result Initialize(BaseStorageType *base_storages, s32 layer_count, size_t htbs, void *hash_buf, size_t hash_buf_size, fssystem::IHash256GeneratorFactory *hgf); + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + + virtual Result GetSize(s64 *out) override { + R_RETURN(m_base_storage->GetSize(out)); + } + + virtual Result Flush() override { + R_RETURN(m_base_storage->Flush()); + } + + virtual Result SetSize(s64 size) override { + AMS_UNUSED(size); + R_THROW(fs::ResultUnsupportedSetSizeForHierarchicalSha256Storage()); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_indirect_storage.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_indirect_storage.cpp new file mode 100644 index 00000000..47d06101 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_indirect_storage.cpp @@ -0,0 +1,185 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + Result IndirectStorage::Initialize(IAllocator *allocator, fs::SubStorage table_storage) { + /* Read and verify the bucket tree header. */ + BucketTree::Header header; + R_TRY(table_storage.Read(0, std::addressof(header), sizeof(header))); + R_TRY(header.Verify()); + + /* Determine extents. */ + const auto node_storage_size = QueryNodeStorageSize(header.entry_count); + const auto entry_storage_size = QueryEntryStorageSize(header.entry_count); + const auto node_storage_offset = QueryHeaderStorageSize(); + const auto entry_storage_offset = node_storage_offset + node_storage_size; + + /* Initialize. */ + R_RETURN(this->Initialize(allocator, fs::SubStorage(std::addressof(table_storage), node_storage_offset, node_storage_size), fs::SubStorage(std::addressof(table_storage), entry_storage_offset, entry_storage_size), header.entry_count)); + } + + void IndirectStorage::Finalize() { + if (this->IsInitialized()) { + m_table.Finalize(); + for (auto i = 0; i < StorageCount; i++) { + m_data_storage[i] = fs::SubStorage(); + } + } + } + + Result IndirectStorage::GetEntryList(Entry *out_entries, s32 *out_entry_count, s32 entry_count, s64 offset, s64 size) { + /* Validate pre-conditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(size >= 0); + AMS_ASSERT(this->IsInitialized()); + + /* Clear the out count. */ + R_UNLESS(out_entry_count != nullptr, fs::ResultNullptrArgument()); + *out_entry_count = 0; + + /* Succeed if there's no range. */ + R_SUCCEED_IF(size == 0); + + /* If we have an output array, we need it to be non-null. */ + R_UNLESS(out_entries != nullptr || entry_count == 0, fs::ResultNullptrArgument()); + + /* Check that our range is valid. */ + BucketTree::Offsets table_offsets; + R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); + + R_UNLESS(table_offsets.IsInclude(offset, size), fs::ResultOutOfRange()); + + /* Find the offset in our tree. */ + BucketTree::Visitor visitor; + R_TRY(m_table.Find(std::addressof(visitor), offset)); + { + const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset(); + R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), fs::ResultInvalidIndirectEntryOffset()); + } + + /* Prepare to loop over entries. */ + const auto end_offset = offset + static_cast<s64>(size); + s32 count = 0; + + auto cur_entry = *visitor.Get<Entry>(); + while (cur_entry.GetVirtualOffset() < end_offset) { + /* Try to write the entry to the out list. */ + if (entry_count != 0) { + if (count >= entry_count) { + break; + } + std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry)); + } + + count++; + + /* Advance. */ + if (visitor.CanMoveNext()) { + R_TRY(visitor.MoveNext()); + cur_entry = *visitor.Get<Entry>(); + } else { + break; + } + } + + /* Write the output count. */ + *out_entry_count = count; + R_SUCCEED(); + } + + Result IndirectStorage::Read(s64 offset, void *buffer, size_t size) { + /* Validate pre-conditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(this->IsInitialized()); + + /* Succeed if there's nothing to read. */ + R_SUCCEED_IF(size == 0); + + /* Ensure that we have a buffer to read to. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + R_TRY((this->OperatePerEntry<true, true>(offset, size, [=](fs::IStorage *storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result { + R_TRY(storage->Read(data_offset, reinterpret_cast<u8 *>(buffer) + (cur_offset - offset), static_cast<size_t>(cur_size))); + R_SUCCEED(); + }))); + + R_SUCCEED(); + } + + Result IndirectStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + /* Validate pre-conditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(size >= 0); + AMS_ASSERT(this->IsInitialized()); + + switch (op_id) { + case fs::OperationId::Invalidate: + { + if (!m_table.IsEmpty()) { + /* Invalidate our table's cache. */ + R_TRY(m_table.InvalidateCache()); + + /* Invalidate our storages. */ + for (auto &storage : m_data_storage) { + R_TRY(storage.OperateRange(fs::OperationId::Invalidate, 0, std::numeric_limits<s64>::max())); + } + } + R_SUCCEED(); + } + case fs::OperationId::QueryRange: + { + /* Validate that we have an output range info. */ + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize()); + + if (size > 0) { + /* Validate arguments. */ + BucketTree::Offsets table_offsets; + R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); + + R_UNLESS(table_offsets.IsInclude(offset, size), fs::ResultOutOfRange()); + + if (!m_table.IsEmpty()) { + /* Create a new info. */ + fs::QueryRangeInfo merged_info; + merged_info.Clear(); + + /* Operate on our entries. */ + R_TRY((this->OperatePerEntry<false, true>(offset, size, [=, &merged_info](fs::IStorage *storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result { + AMS_UNUSED(cur_offset); + + fs::QueryRangeInfo cur_info; + R_TRY(storage->OperateRange(std::addressof(cur_info), sizeof(cur_info), op_id, data_offset, cur_size, src, src_size)); + merged_info.Merge(cur_info); + R_SUCCEED(); + }))); + + /* Write the merged info. */ + *reinterpret_cast<fs::QueryRangeInfo *>(dst) = merged_info; + } + } + R_SUCCEED(); + } + default: + R_THROW(fs::ResultUnsupportedOperateRangeForIndirectStorage()); + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_integrity_romfs_storage.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_integrity_romfs_storage.cpp new file mode 100644 index 00000000..a3b2a285 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_integrity_romfs_storage.cpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + Result IntegrityRomFsStorage::Initialize(HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, fs::IBufferManager *bm, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, IHash256GeneratorFactory *hgf) { + /* Validate preconditions. */ + AMS_ASSERT(bm != nullptr); + + /* Set master hash. */ + m_master_hash = master_hash; + m_master_hash_storage = std::make_unique<fs::MemoryStorage>(std::addressof(m_master_hash), sizeof(Hash)); + R_UNLESS(m_master_hash_storage != nullptr, fs::ResultAllocationMemoryFailedInIntegrityRomFsStorageA()); + + /* Set the master hash storage. */ + storage_info[0] = fs::SubStorage(m_master_hash_storage.get(), 0, sizeof(Hash)); + + /* Set buffers. */ + for (size_t i = 0; i < util::size(m_buffers.buffers); ++i) { + m_buffers.buffers[i] = bm; + } + + /* Initialize our integrity storage. */ + R_RETURN(m_integrity_storage.Initialize(level_hash_info, storage_info, std::addressof(m_buffers), hgf, false, std::addressof(m_mutex), max_data_cache_entries, max_hash_cache_entries, buffer_level, false, false)); + } + + void IntegrityRomFsStorage::Finalize() { + m_integrity_storage.Finalize(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_integrity_verification_storage.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_integrity_verification_storage.cpp new file mode 100644 index 00000000..6493c5a5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_integrity_verification_storage.cpp @@ -0,0 +1,500 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + void IntegrityVerificationStorage::Initialize(fs::SubStorage hs, fs::SubStorage ds, s64 verif_block_size, s64 upper_layer_verif_block_size, fs::IBufferManager *bm, fssystem::IHash256GeneratorFactory *hgf, const util::optional<fs::HashSalt> &salt, bool is_real_data, bool is_writable, bool allow_cleared_blocks) { + /* Validate preconditions. */ + AMS_ASSERT(verif_block_size >= HashSize); + AMS_ASSERT(bm != nullptr); + AMS_ASSERT(hgf != nullptr); + + /* Set storages. */ + m_hash_storage = hs; + m_data_storage = ds; + + /* Set hash generator factory. */ + m_hash_generator_factory = hgf; + + /* Set verification block sizes. */ + m_verification_block_size = verif_block_size; + m_verification_block_order = ILog2(static_cast<u32>(verif_block_size)); + AMS_ASSERT(m_verification_block_size == (1l << m_verification_block_order)); + + /* Set buffer manager. */ + m_buffer_manager = bm; + + /* Set upper layer block sizes. */ + upper_layer_verif_block_size = std::max(upper_layer_verif_block_size, HashSize); + m_upper_layer_verification_block_size = upper_layer_verif_block_size; + m_upper_layer_verification_block_order = ILog2(static_cast<u32>(upper_layer_verif_block_size)); + AMS_ASSERT(m_upper_layer_verification_block_size == (1l << m_upper_layer_verification_block_order)); + + /* Validate sizes. */ + { + s64 hash_size = 0; + s64 data_size = 0; + AMS_ASSERT(R_SUCCEEDED(m_hash_storage.GetSize(std::addressof(hash_size)))); + AMS_ASSERT(R_SUCCEEDED(m_data_storage.GetSize(std::addressof(data_size)))); + AMS_ASSERT(((hash_size / HashSize) * m_verification_block_size) >= data_size); + AMS_UNUSED(hash_size, data_size); + } + + /* Set salt. */ + m_salt = salt; + + /* Set data, writable, and allow cleared. */ + m_is_real_data = is_real_data; + m_is_writable = is_writable; + m_allow_cleared_blocks = allow_cleared_blocks; + } + + void IntegrityVerificationStorage::Finalize() { + if (m_buffer_manager != nullptr) { + m_hash_storage = fs::SubStorage(); + m_data_storage = fs::SubStorage(); + m_buffer_manager = nullptr; + } + } + + Result IntegrityVerificationStorage::Read(s64 offset, void *buffer, size_t size) { + /* Although we support zero-size reads, we expect non-zero sizes. */ + AMS_ASSERT(size != 0); + + /* Validate other preconditions. */ + AMS_ASSERT(util::IsAligned(offset, static_cast<size_t>(m_verification_block_size))); + AMS_ASSERT(util::IsAligned(size, static_cast<size_t>(m_verification_block_size))); + + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Validate the offset. */ + s64 data_size; + R_TRY(m_data_storage.GetSize(std::addressof(data_size))); + R_UNLESS(offset <= data_size, fs::ResultInvalidOffset()); + + /* Validate the access range. */ + R_TRY(IStorage::CheckAccessRange(offset, size, util::AlignUp(data_size, static_cast<size_t>(m_verification_block_size)))); + + /* Determine the read extents. */ + size_t read_size = size; + if (static_cast<s64>(offset + read_size) > data_size) { + /* Determine the padding sizes. */ + s64 padding_offset = data_size - offset; + size_t padding_size = static_cast<size_t>(m_verification_block_size - (padding_offset & (m_verification_block_size - 1))); + AMS_ASSERT(static_cast<s64>(padding_size) < m_verification_block_size); + + /* Clear the padding. */ + std::memset(static_cast<u8 *>(buffer) + padding_offset, 0, padding_size); + + /* Set the new in-bounds size. */ + read_size = static_cast<size_t>(data_size - offset); + } + + /* Perform the read. */ + { + auto clear_guard = SCOPE_GUARD { std::memset(buffer, 0, size); }; + R_TRY(m_data_storage.Read(offset, buffer, read_size)); + clear_guard.Cancel(); + } + + /* Verify the signatures. */ + Result verify_hash_result = ResultSuccess(); + + /* Create hash generator. */ + std::unique_ptr<IHash256Generator> generator = nullptr; + R_TRY(m_hash_generator_factory->Create(std::addressof(generator))); + + /* Prepare to validate the signatures. */ + const auto signature_count = size >> m_verification_block_order; + PooledBuffer signature_buffer(signature_count * sizeof(BlockHash), sizeof(BlockHash)); + const auto buffer_count = std::min(signature_count, signature_buffer.GetSize() / sizeof(BlockHash)); + + size_t verified_count = 0; + while (verified_count < signature_count) { + /* Read the current signatures. */ + const auto cur_count = std::min(buffer_count, signature_count - verified_count); + auto cur_result = this->ReadBlockSignature(signature_buffer.GetBuffer(), signature_buffer.GetSize(), offset + (verified_count << m_verification_block_order), cur_count << m_verification_block_order); + + /* Temporarily increase our priority. */ + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + /* Loop over each signature we read. */ + for (size_t i = 0; i < cur_count && R_SUCCEEDED(cur_result); ++i) { + const auto verified_size = (verified_count + i) << m_verification_block_order; + u8 *cur_buf = static_cast<u8 *>(buffer) + verified_size; + cur_result = this->VerifyHash(cur_buf, reinterpret_cast<BlockHash *>(signature_buffer.GetBuffer()) + i, generator); + + /* If the data is corrupted, clear the corrupted parts. */ + if (fs::ResultIntegrityVerificationStorageCorrupted::Includes(cur_result)) { + std::memset(cur_buf, 0, m_verification_block_size); + + /* Set the result if we should. */ + if (!fs::ResultClearedRealDataVerificationFailed::Includes(cur_result) && !m_allow_cleared_blocks) { + verify_hash_result = cur_result; + } + + cur_result = ResultSuccess(); + } + } + + /* If we failed, clear and return. */ + if (R_FAILED(cur_result)) { + std::memset(buffer, 0, size); + R_THROW(cur_result); + } + + /* Advance. */ + verified_count += cur_count; + } + + R_RETURN(verify_hash_result); + } + + Result IntegrityVerificationStorage::Write(s64 offset, const void *buffer, size_t size) { + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Check the offset/size. */ + R_TRY(IStorage::CheckOffsetAndSize(offset, size)); + + /* Validate the offset. */ + s64 data_size; + R_TRY(m_data_storage.GetSize(std::addressof(data_size))); + R_UNLESS(offset < data_size, fs::ResultInvalidOffset()); + + /* Validate the access range. */ + R_TRY(IStorage::CheckAccessRange(offset, size, util::AlignUp(data_size, static_cast<size_t>(m_verification_block_size)))); + + /* Validate preconditions. */ + AMS_ASSERT(util::IsAligned(offset, m_verification_block_size)); + AMS_ASSERT(util::IsAligned(size, m_verification_block_size)); + AMS_ASSERT(offset <= data_size); + AMS_ASSERT(static_cast<s64>(offset + size) < data_size + m_verification_block_size); + + /* Validate that if writing past the end, all extra data is zero padding. */ + if (static_cast<s64>(offset + size) > data_size) { + const u8 *padding_cur = static_cast<const u8 *>(buffer) + data_size - offset; + const u8 *padding_end = padding_cur + (offset + size - data_size); + + while (padding_cur < padding_end) { + AMS_ASSERT((*padding_cur) == 0); + ++padding_cur; + } + } + + /* Determine the unpadded size to write. */ + auto write_size = size; + if (static_cast<s64>(offset + write_size) > data_size) { + write_size = static_cast<size_t>(data_size - offset); + R_SUCCEED_IF(write_size == 0); + } + + /* Determine the size we're writing in blocks. */ + const auto aligned_write_size = util::AlignUp(write_size, m_verification_block_size); + + /* Write the updated block signatures. */ + Result update_result = ResultSuccess(); + size_t updated_count = 0; + { + const auto signature_count = aligned_write_size >> m_verification_block_order; + PooledBuffer signature_buffer(signature_count * sizeof(BlockHash), sizeof(BlockHash)); + const auto buffer_count = std::min(signature_count, signature_buffer.GetSize() / sizeof(BlockHash)); + + /* Create hash generator. */ + std::unique_ptr<IHash256Generator> generator = nullptr; + R_TRY(m_hash_generator_factory->Create(std::addressof(generator))); + + while (updated_count < signature_count) { + const auto cur_count = std::min(buffer_count, signature_count - updated_count); + + /* Calculate the hash with temporarily increased priority. */ + { + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + for (size_t i = 0; i < cur_count; ++i) { + const auto updated_size = (updated_count + i) << m_verification_block_order; + this->CalcBlockHash(reinterpret_cast<BlockHash *>(signature_buffer.GetBuffer()) + i, reinterpret_cast<const u8 *>(buffer) + updated_size, generator); + } + } + + /* Write the new block signatures. */ + if (R_FAILED((update_result = this->WriteBlockSignature(signature_buffer.GetBuffer(), signature_buffer.GetSize(), offset + (updated_count << m_verification_block_order), cur_count << m_verification_block_order)))) { + break; + } + + /* Advance. */ + updated_count += cur_count; + } + } + + /* Write the data. */ + R_TRY(m_data_storage.Write(offset, buffer, std::min(write_size, updated_count << m_verification_block_order))); + + R_RETURN(update_result); + } + + Result IntegrityVerificationStorage::GetSize(s64 *out) { + R_RETURN(m_data_storage.GetSize(out)); + } + + Result IntegrityVerificationStorage::Flush() { + /* Flush both storages. */ + R_TRY(m_hash_storage.Flush()); + R_TRY(m_data_storage.Flush()); + R_SUCCEED(); + } + + Result IntegrityVerificationStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + /* Validate preconditions. */ + if (op_id != fs::OperationId::Invalidate) { + AMS_ASSERT(util::IsAligned(offset, static_cast<size_t>(m_verification_block_size))); + AMS_ASSERT(util::IsAligned(size, static_cast<size_t>(m_verification_block_size))); + } + + switch (op_id) { + case fs::OperationId::FillZero: + { + /* FillZero should only be called for writable storages. */ + AMS_ASSERT(m_is_writable); + + /* Validate the range. */ + s64 data_size = 0; + R_TRY(m_data_storage.GetSize(std::addressof(data_size))); + R_UNLESS(0 <= offset && offset <= data_size, fs::ResultInvalidOffset()); + + /* Determine the extents to clear. */ + const auto sign_offset = (offset >> m_verification_block_order) * HashSize; + const auto sign_size = (std::min(size, data_size - offset) >> m_verification_block_order) * HashSize; + + /* Allocate a work buffer. */ + const auto buf_size = static_cast<size_t>(std::min(sign_size, static_cast<s64>(1) << (m_upper_layer_verification_block_order + 2))); + std::unique_ptr<char[], fs::impl::Deleter> buf = fs::impl::MakeUnique<char[]>(buf_size); + R_UNLESS(buf != nullptr, fs::ResultAllocationMemoryFailedInIntegrityVerificationStorageA()); + + /* Clear the work buffer. */ + std::memset(buf.get(), 0, buf_size); + + /* Clear in chunks. */ + auto remaining_size = sign_size; + + while (remaining_size > 0) { + const auto cur_size = static_cast<size_t>(std::min(remaining_size, static_cast<s64>(buf_size))); + R_TRY(m_hash_storage.Write(sign_offset + sign_size - remaining_size, buf.get(), cur_size)); + remaining_size -= cur_size; + } + + R_SUCCEED(); + } + case fs::OperationId::DestroySignature: + { + /* DestroySignature should only be called for save data. */ + AMS_ASSERT(m_is_writable); + + /* Validate the range. */ + s64 data_size = 0; + R_TRY(m_data_storage.GetSize(std::addressof(data_size))); + R_UNLESS(0 <= offset && offset <= data_size, fs::ResultInvalidOffset()); + + /* Determine the extents to clear the signature for. */ + const auto sign_offset = (offset >> m_verification_block_order) * HashSize; + const auto sign_size = (std::min(size, data_size - offset) >> m_verification_block_order) * HashSize; + + /* Allocate a work buffer. */ + std::unique_ptr<char[], fs::impl::Deleter> buf = fs::impl::MakeUnique<char[]>(sign_size); + R_UNLESS(buf != nullptr, fs::ResultAllocationMemoryFailedInIntegrityVerificationStorageB()); + + /* Read the existing signature. */ + R_TRY(m_hash_storage.Read(sign_offset, buf.get(), sign_size)); + + /* Clear the signature. */ + /* This flips all bits other than the verification bit. */ + for (auto i = 0; i < sign_size; ++i) { + buf[i] ^= ((i + 1) % HashSize == 0 ? 0x7F : 0xFF); + } + + /* Write the cleared signature. */ + R_RETURN(m_hash_storage.Write(sign_offset, buf.get(), sign_size)); + } + case fs::OperationId::Invalidate: + { + /* Only allow cache invalidation read-only storages. */ + R_UNLESS(!m_is_writable, fs::ResultUnsupportedOperateRangeForWritableIntegrityVerificationStorage()); + + + /* Operate on our storages. */ + R_TRY(m_hash_storage.OperateRange(op_id, 0, std::numeric_limits<s64>::max())); + R_TRY(m_data_storage.OperateRange(op_id, offset, size)); + + R_SUCCEED(); + } + case fs::OperationId::QueryRange: + { + /* Validate the range. */ + s64 data_size = 0; + R_TRY(m_data_storage.GetSize(std::addressof(data_size))); + R_UNLESS(0 <= offset && offset <= data_size, fs::ResultInvalidOffset()); + + /* Determine the real size to query. */ + const auto actual_size = std::min(size, data_size - offset); + + /* Query the data storage. */ + R_RETURN(m_data_storage.OperateRange(dst, dst_size, op_id, offset, actual_size, src, src_size)); + } + default: + R_THROW(fs::ResultUnsupportedOperateRangeForIntegrityVerificationStorage()); + } + } + + void IntegrityVerificationStorage::CalcBlockHash(BlockHash *out, const void *buffer, size_t block_size, std::unique_ptr<fssystem::IHash256Generator> &generator) const { + /* Hash procedure depends on whether or not we're writable. */ + if (m_is_writable) { + /* Compute the hash with or without the hash salt, if we have one. */ + if (m_salt.has_value()) { + /* Initialize the generator. */ + generator->Initialize(); + + /* Hash the salt. */ + generator->Update(m_salt->value, sizeof(m_salt->value)); + + /* Update with the buffer and get the hash. */ + generator->Update(buffer, block_size); + generator->GetHash(out, sizeof(*out)); + } else { + /* If we have no hash salt, just calculate the hash. */ + m_hash_generator_factory->GenerateHash(out, sizeof(*out), buffer, block_size); + } + + /* Set the validation bit. */ + SetValidationBit(out); + } else { + /* If we're not writable, just calculate the hash. */ + m_hash_generator_factory->GenerateHash(out, sizeof(*out), buffer, block_size); + } + } + + Result IntegrityVerificationStorage::ReadBlockSignature(void *dst, size_t dst_size, s64 offset, size_t size) { + /* Validate preconditions. */ + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(util::IsAligned(offset, static_cast<size_t>(m_verification_block_size))); + AMS_ASSERT(util::IsAligned(size, static_cast<size_t>(m_verification_block_size))); + + /* Determine where to read the signature. */ + const s64 sign_offset = (offset >> m_verification_block_order) * HashSize; + const auto sign_size = static_cast<size_t>((size >> m_verification_block_order) * HashSize); + AMS_ASSERT(dst_size >= sign_size); + AMS_UNUSED(dst_size); + + /* Create a guard in the event of failure. */ + auto clear_guard = SCOPE_GUARD { std::memset(dst, 0, sign_size); }; + + /* Validate that we can read the signature. */ + s64 hash_size; + R_TRY(m_hash_storage.GetSize(std::addressof(hash_size))); + const bool range_valid = static_cast<s64>(sign_offset + sign_size) <= hash_size; + AMS_ASSERT(range_valid); + R_UNLESS(range_valid, fs::ResultOutOfRange()); + + /* Read the signature. */ + R_TRY(m_hash_storage.Read(sign_offset, dst, sign_size)); + + /* We succeeded. */ + clear_guard.Cancel(); + R_SUCCEED(); + } + + Result IntegrityVerificationStorage::WriteBlockSignature(const void *src, size_t src_size, s64 offset, size_t size) { + /* Validate preconditions. */ + AMS_ASSERT(src != nullptr); + AMS_ASSERT(util::IsAligned(offset, static_cast<size_t>(m_verification_block_size))); + + /* Determine where to write the signature. */ + const s64 sign_offset = (offset >> m_verification_block_order) * HashSize; + const auto sign_size = static_cast<size_t>((size >> m_verification_block_order) * HashSize); + AMS_ASSERT(src_size >= sign_size); + AMS_UNUSED(src_size); + + /* Write the signature. */ + R_TRY(m_hash_storage.Write(sign_offset, src, sign_size)); + + /* We succeeded. */ + R_SUCCEED(); + } + + Result IntegrityVerificationStorage::VerifyHash(const void *buf, BlockHash *hash, std::unique_ptr<fssystem::IHash256Generator> &generator) { + /* Validate preconditions. */ + AMS_ASSERT(buf != nullptr); + AMS_ASSERT(hash != nullptr); + + /* Get the comparison hash. */ + auto &cmp_hash = *hash; + + /* If writable, check if the data is uninitialized. */ + if (m_is_writable) { + bool is_cleared = false; + R_TRY(this->IsCleared(std::addressof(is_cleared), cmp_hash)); + R_UNLESS(!is_cleared, fs::ResultClearedRealDataVerificationFailed()); + } + + /* Get the calculated hash. */ + BlockHash calc_hash; + this->CalcBlockHash(std::addressof(calc_hash), buf, generator); + + /* Check that the signatures are equal. */ + if (!crypto::IsSameBytes(std::addressof(cmp_hash), std::addressof(calc_hash), sizeof(BlockHash))) { + /* Clear the comparison hash. */ + std::memset(std::addressof(cmp_hash), 0, sizeof(cmp_hash)); + + /* Return the appropriate result. */ + if (m_is_real_data) { + R_THROW(fs::ResultUnclearedRealDataVerificationFailed()); + } else { + R_THROW(fs::ResultNonRealDataVerificationFailed()); + } + } + + R_SUCCEED(); + } + + Result IntegrityVerificationStorage::IsCleared(bool *is_cleared, const BlockHash &hash) { + /* Validate preconditions. */ + AMS_ASSERT(is_cleared != nullptr); + AMS_ASSERT(m_is_writable); + + /* Default to uncleared. */ + *is_cleared = false; + + /* Succeed if the validation bit is set. */ + R_SUCCEED_IF(IsValidationBit(std::addressof(hash))); + + /* Otherwise, we expect the hash to be all zero. */ + for (size_t i = 0; i < sizeof(hash.hash); ++i) { + R_UNLESS(hash.hash[i] == 0, fs::ResultInvalidZeroHash()); + } + + /* Set cleared. */ + *is_cleared = true; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_key_slot_cache.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_key_slot_cache.hpp new file mode 100644 index 00000000..a0d2cc10 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_key_slot_cache.hpp @@ -0,0 +1,135 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fssystem { + + class KeySlotCacheAccessor : public ::ams::fs::impl::Newable { + NON_COPYABLE(KeySlotCacheAccessor); + NON_MOVEABLE(KeySlotCacheAccessor); + private: + util::unique_lock<os::SdkMutex> m_lk; + const s32 m_slot_index; + public: + KeySlotCacheAccessor(s32 idx, util::unique_lock<os::SdkMutex> &&l) : m_lk(std::move(l)), m_slot_index(idx) { /* ... */ } + + s32 GetKeySlotIndex() const { return m_slot_index; } + }; + + class KeySlotCacheEntry : public util::IntrusiveListBaseNode<KeySlotCacheEntry> { + NON_COPYABLE(KeySlotCacheEntry); + NON_MOVEABLE(KeySlotCacheEntry); + public: + static constexpr size_t KeySize = crypto::AesDecryptor128::KeySize; + private: + const s32 m_slot_index; + u8 m_key1[KeySize]; + s32 m_key2; + public: + explicit KeySlotCacheEntry(s32 idx) : m_slot_index(idx), m_key2(-1) { + std::memset(m_key1, 0, sizeof(m_key1)); + } + + bool Contains(const void *key, size_t key_size, s32 key2) const { + AMS_ASSERT(key_size == KeySize); + AMS_UNUSED(key_size); + + return key2 == m_key2 && std::memcmp(m_key1, key, KeySize) == 0; + } + + s32 GetKeySlotIndex() const { return m_slot_index; } + + void SetKey(const void *key, size_t key_size, s32 key2) { + AMS_ASSERT(key_size == KeySize); + std::memcpy(m_key1, key, key_size); + m_key2 = key2; + } + }; + + class KeySlotCache { + NON_COPYABLE(KeySlotCache); + NON_MOVEABLE(KeySlotCache); + private: + using KeySlotCacheEntryList = util::IntrusiveListBaseTraits<KeySlotCacheEntry>::ListType; + private: + os::SdkMutex m_mutex; + KeySlotCacheEntryList m_high_priority_mru_list; + KeySlotCacheEntryList m_low_priority_mru_list; + public: + constexpr KeySlotCache() : m_mutex(), m_high_priority_mru_list(), m_low_priority_mru_list() { /* ... */ } + + Result AllocateHighPriority(std::unique_ptr<KeySlotCacheAccessor> *out, const void *key, size_t key_size, s32 key2) { + R_RETURN(this->AllocateFromLru(out, m_high_priority_mru_list, key, key_size, key2)); + } + + Result AllocateLowPriority(std::unique_ptr<KeySlotCacheAccessor> *out, const void *key, size_t key_size, s32 key2) { + R_RETURN(this->AllocateFromLru(out, m_high_priority_mru_list, key, key_size, key2)); + } + + Result Find(std::unique_ptr<KeySlotCacheAccessor> *out, const void *key, size_t key_size, s32 key2) { + util::unique_lock lk(m_mutex); + + KeySlotCacheEntryList *lists[2] = { std::addressof(m_high_priority_mru_list), std::addressof(m_low_priority_mru_list) }; + for (auto list : lists) { + for (auto it = list->begin(); it != list->end(); ++it) { + if (it->Contains(key, key_size, key2)) { + std::unique_ptr accessor = std::make_unique<KeySlotCacheAccessor>(it->GetKeySlotIndex(), std::move(lk)); + R_UNLESS(accessor != nullptr, fs::ResultAllocationMemoryFailed()); + + *out = std::move(accessor); + + this->UpdateMru(list, it); + R_SUCCEED(); + } + } + } + + R_THROW(fs::ResultTargetNotFound()); + } + + void AddEntry(KeySlotCacheEntry *entry) { + util::unique_lock lk(m_mutex); + m_low_priority_mru_list.push_front(*entry); + } + private: + Result AllocateFromLru(std::unique_ptr<KeySlotCacheAccessor> *out, KeySlotCacheEntryList &dst_list, const void *key, size_t key_size, s32 key2) { + util::unique_lock lk(m_mutex); + + KeySlotCacheEntryList &src_list = m_low_priority_mru_list.empty() ? m_high_priority_mru_list : m_low_priority_mru_list; + AMS_ASSERT(!src_list.empty()); + + auto it = src_list.rbegin(); + std::unique_ptr accessor = std::make_unique<KeySlotCacheAccessor>(it->GetKeySlotIndex(), std::move(lk)); + *out = std::move(accessor); + + it->SetKey(key, key_size, key2); + + auto *entry = std::addressof(*it); + src_list.pop_back(); + dst_list.push_front(*entry); + + R_SUCCEED(); + } + + void UpdateMru(KeySlotCacheEntryList *list, KeySlotCacheEntryList::iterator it) { + auto *entry = std::addressof(*it); + list->erase(it); + list->push_front(*entry); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_local_file_system.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_local_file_system.cpp new file mode 100644 index 00000000..0f5760e6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_local_file_system.cpp @@ -0,0 +1,1774 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#if defined(ATMOSPHERE_OS_WINDOWS) +#include <stratosphere/windows.hpp> +#include <winerror.h> +#include <winioctl.h> +#elif defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS) +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/statvfs.h> +#include <unistd.h> +#include <dirent.h> +#endif + +#if defined(ATMOSPHERE_OS_LINUX) +#include <sys/syscall.h> +#elif defined(ATMOSPHERE_OS_MACOS) +extern "C" ssize_t __getdirentries64(int fd, char *buffer, size_t buffer_size, uintptr_t *basep); +#endif + +#if !defined(ATMOSPHERE_OS_HORIZON) +namespace ams::fssystem { + + namespace { + + constexpr s64 NanoSecondsPerWindowsTick = 100; + constexpr s64 WindowsTicksPerSecond = TimeSpan::FromSeconds(1).GetNanoSeconds() / TimeSpan::FromNanoSeconds(NanoSecondsPerWindowsTick).GetNanoSeconds(); + constexpr s64 OffsetToConvertToPosixTime = 11644473600; + + [[maybe_unused]] constexpr ALWAYS_INLINE s64 ConvertWindowsTimeToPosixTime(s64 windows_ticks) { + return (windows_ticks / WindowsTicksPerSecond) - OffsetToConvertToPosixTime; + } + + [[maybe_unused]] constexpr ALWAYS_INLINE s64 ConvertPosixTimeToWindowsTime(s64 posix_sec, s64 posix_ns = 0) { + return ((posix_sec + OffsetToConvertToPosixTime) * WindowsTicksPerSecond) + util::DivideUp<s64>(posix_ns, NanoSecondsPerWindowsTick); + } + + #if defined(ATMOSPHERE_OS_WINDOWS) + constexpr int MaxFilePathLength = MAX_PATH - 1; + constexpr int MaxDirectoryPathLength = MaxFilePathLength - (8 + 1 + 3); + static_assert(MaxFilePathLength == 259); + static_assert(MaxDirectoryPathLength == 247); + + bool AreLongPathsEnabledImpl() { + /* Get handle to ntdll. */ + const HMODULE module = ::GetModuleHandleW(L"ntdll"); + if (module == nullptr) { + return true; + } + + /* Get function pointer to long paths enabled. */ + const auto enabled_funcptr = ::GetProcAddress(module, "RtlAreLongPathsEnabled"); + if (enabled_funcptr == nullptr) { + return true; + } + + /* Get whether long paths are enabled. */ + using FunctionType = BOOLEAN (NTAPI *)(); + return reinterpret_cast<FunctionType>(reinterpret_cast<uintptr_t>(enabled_funcptr))(); + } + + Result ConvertLastErrorToResult() { + switch (::GetLastError()) { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_NO_MORE_FILES: + case ERROR_BAD_NETPATH: + case ERROR_BAD_NET_NAME: + case ERROR_DIRECTORY: + case ERROR_BAD_DEVICE: + case ERROR_CONNECTION_UNAVAIL: + case ERROR_NO_NET_OR_BAD_PATH: + case ERROR_NOT_CONNECTED: + R_THROW(fs::ResultPathNotFound()); + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + R_THROW(fs::ResultTargetLocked()); + case ERROR_HANDLE_EOF: + R_THROW(fs::ResultOutOfRange()); + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: + R_THROW(fs::ResultPathAlreadyExists()); + case ERROR_DISK_FULL: + case ERROR_SPACES_NOT_ENOUGH_DRIVES: + R_THROW(fs::ResultNotEnoughFreeSpace()); + case ERROR_DIR_NOT_EMPTY: + R_THROW(fs::ResultDirectoryNotEmpty()); + case ERROR_BAD_PATHNAME: + R_THROW(fs::ResultInvalidPathFormat()); + case ERROR_FILENAME_EXCED_RANGE: + R_THROW(fs::ResultTooLongPath()); + default: + //printf("Returning ConvertLastErrorToResult() -> ResultUnexpectedInLocalFileSystemE, last_error=0x%08x\n", static_cast<u32>(::GetLastError())); + R_THROW(fs::ResultUnexpectedInLocalFileSystemE()); + } + } + + Result WaitDeletionCompletion(const wchar_t *native_path) { + /* Wait for the path to be deleted. */ + constexpr int MaxTryCount = 25; + for (int i = 0; i < MaxTryCount; ++i) { + /* Get the file attributes. */ + const auto attr = ::GetFileAttributesW(native_path); + + /* If they're not invalid, we're done. */ + R_SUCCEED_IF(attr != INVALID_FILE_ATTRIBUTES); + + /* Get last error. */ + const auto err = ::GetLastError(); + + /* If error was file not found, the delete is complete. */ + R_SUCCEED_IF(err == ERROR_FILE_NOT_FOUND); + + /* If the error was access denied, we want to try again. */ + R_UNLESS(err == ERROR_ACCESS_DENIED, ConvertLastErrorToResult()); + + /* Sleep before checking again. */ + ::Sleep(2); + } + + /* We received access denied 25 times in a row. */ + R_THROW(fs::ResultTargetLocked()); + } + + Result GetEntryTypeImpl(fs::DirectoryEntryType *out, const wchar_t *native_path) { + const auto res = ::GetFileAttributesW(native_path); + if (res == INVALID_FILE_ATTRIBUTES) { + switch (::GetLastError()) { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_ACCESS_DENIED: + case ERROR_BAD_NETPATH: + case ERROR_BAD_NET_NAME: + case ERROR_BAD_DEVICE: + case ERROR_CONNECTION_UNAVAIL: + case ERROR_NO_NET_OR_BAD_PATH: + case ERROR_NOT_CONNECTED: + R_THROW(fs::ResultPathNotFound()); + default: + //printf("Returning GetEntryTypeImpl() -> ResultUnexpectedInLocalFileSystemF, last_error=0x%08x\n", static_cast<u32>(::GetLastError())); + R_THROW(fs::ResultUnexpectedInLocalFileSystemF()); + } + } + + *out = (res & FILE_ATTRIBUTE_DIRECTORY) ? fs::DirectoryEntryType_Directory : fs::DirectoryEntryType_File; + R_SUCCEED(); + } + + Result SetFileSizeImpl(HANDLE handle, s64 size) { + /* Seek to the desired size. */ + LARGE_INTEGER seek; + seek.QuadPart = size; + R_UNLESS(::SetFilePointerEx(handle, seek, nullptr, FILE_BEGIN) != 0, ConvertLastErrorToResult()); + + /* Try to set the file size. */ + if (::SetEndOfFile(handle) == 0) { + /* Check if the error resulted from too large size. */ + R_UNLESS(::GetLastError() == ERROR_INVALID_PARAMETER, ConvertLastErrorToResult()); + R_UNLESS(size <= INT64_C(0x00000FFFFFFF0000), ConvertLastErrorToResult()); + + /* The file size is too large. */ + R_THROW(fs::ResultTooLargeSize()); + } + + R_SUCCEED(); + } + + class LocalFile : public ::ams::fs::fsa::IFile, public ::ams::fs::impl::Newable { + private: + const HANDLE m_handle; + const fs::OpenMode m_open_mode; + public: + LocalFile(HANDLE h, fs::OpenMode m) : m_handle(h), m_open_mode(m) { /* ... */ } + + virtual ~LocalFile() { + ::CloseHandle(m_handle); + } + public: + virtual Result DoRead(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override { + /* Check that read is possible. */ + size_t dry_read_size; + R_TRY(this->DryRead(std::addressof(dry_read_size), offset, size, option, m_open_mode)); + + /* If we have nothing to read, we don't need to do anything. */ + if (dry_read_size == 0) { + *out = 0; + R_SUCCEED(); + } + + /* Prepare to do asynchronous IO. */ + OVERLAPPED overlapped = {}; + overlapped.Offset = static_cast<DWORD>(offset); + overlapped.OffsetHigh = static_cast<DWORD>(offset >> BITSIZEOF(DWORD)); + overlapped.hEvent = ::CreateEvent(nullptr, true, false, nullptr); + R_UNLESS(overlapped.hEvent != nullptr, fs::ResultUnexpectedInLocalFileSystemA()); + ON_SCOPE_EXIT { ::CloseHandle(overlapped.hEvent); }; + + /* Read from the file. */ + DWORD size_read; + if (!::ReadFile(m_handle, buffer, static_cast<DWORD>(size), std::addressof(size_read), std::addressof(overlapped))) { + /* If we fail for reason other than io pending, return the error result. */ + const auto err = ::GetLastError(); + R_UNLESS(err == ERROR_IO_PENDING, ConvertLastErrorToResult()); + + /* Get the wait result. */ + if (!::GetOverlappedResult(m_handle, std::addressof(overlapped), std::addressof(size_read), true)) { + /* We failed...check if it's because we're at the end of the file. */ + R_UNLESS(::GetLastError() == ERROR_HANDLE_EOF, ConvertLastErrorToResult()); + + /* Get the file size. */ + LARGE_INTEGER file_size; + R_UNLESS(::GetFileSizeEx(m_handle, std::addressof(file_size)), ConvertLastErrorToResult()); + + /* Check the filesize matches offset. */ + R_UNLESS(file_size.QuadPart == offset, ConvertLastErrorToResult()); + } + } + + /* Set the output read size. */ + *out = size_read; + R_SUCCEED(); + } + + virtual Result DoGetSize(s64 *out) override { + /* Get the file size. */ + LARGE_INTEGER size; + R_UNLESS(::GetFileSizeEx(m_handle, std::addressof(size)), fs::ResultUnexpectedInLocalFileSystemD()); + + /* Set the output. */ + *out = size.QuadPart; + R_SUCCEED(); + } + + virtual Result DoFlush() override { + /* If we're not writable, we have nothing to flush. */ + R_SUCCEED_IF((m_open_mode & fs::OpenMode_Write) == 0); + + /* Flush our buffer. */ + R_UNLESS(::FlushFileBuffers(m_handle), fs::ResultUnexpectedInLocalFileSystemC()); + R_SUCCEED(); + } + + virtual Result DoWrite(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override { + /* Verify that we can write. */ + bool needs_append; + R_TRY(this->DryWrite(std::addressof(needs_append), offset, size, option, m_open_mode)); + + /* If we need to, perform the write. */ + if (size != 0) { + /* Prepare to do asynchronous IO. */ + OVERLAPPED overlapped = {}; + overlapped.Offset = static_cast<DWORD>(offset); + overlapped.OffsetHigh = static_cast<DWORD>(offset >> BITSIZEOF(DWORD)); + overlapped.hEvent = ::CreateEvent(nullptr, true, false, nullptr); + R_UNLESS(overlapped.hEvent != nullptr, fs::ResultUnexpectedInLocalFileSystemA()); + ON_SCOPE_EXIT { ::CloseHandle(overlapped.hEvent); }; + + /* Write to the file. */ + DWORD size_written; + if (!::WriteFile(m_handle, buffer, static_cast<DWORD>(size), std::addressof(size_written), std::addressof(overlapped))) { + /* If we fail for reason other than io pending, return the error result. */ + const auto err = ::GetLastError(); + R_UNLESS(err == ERROR_IO_PENDING, ConvertLastErrorToResult()); + + /* Get the wait result. */ + R_UNLESS(::GetOverlappedResult(m_handle, std::addressof(overlapped), std::addressof(size_written), true), ConvertLastErrorToResult()); + } + + /* Check that a correct amount of data was written. */ + R_UNLESS(size_written >= size, fs::ResultNotEnoughFreeSpace()); + + /* Sanity check that we wrote the right amount. */ + AMS_ASSERT(size_written == size); + } + + /* If we need to, flush. */ + if (option.HasFlushFlag()) { + R_TRY(this->Flush()); + } + + R_SUCCEED(); + } + + virtual Result DoSetSize(s64 size) override { + /* Verify we can set the size. */ + R_TRY(this->DrySetSize(size, m_open_mode)); + + /* Try to set the file size. */ + R_RETURN(SetFileSizeImpl(m_handle, size)); + } + + virtual Result DoOperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + AMS_UNUSED(offset, size, src, src_size); + switch (op_id) { + case fs::OperationId::Invalidate: + R_SUCCEED(); + case fs::OperationId::QueryRange: + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize()); + static_cast<fs::QueryRangeInfo *>(dst)->Clear(); + R_SUCCEED(); + default: + R_THROW(fs::ResultUnsupportedOperateRangeForTmFileSystemFile()); + } + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override { + AMS_ABORT("GetDomainObjectId() should never be called on a LocalFile"); + } + }; + + bool IsDirectory(const WIN32_FIND_DATAW &fd) { + return fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; + } + + class LocalDirectory : public ::ams::fs::fsa::IDirectory, public ::ams::fs::impl::Newable { + private: + std::unique_ptr<wchar_t[], ::ams::fs::impl::Deleter> m_path; + HANDLE m_dir_handle; + HANDLE m_search_handle; + fs::OpenDirectoryMode m_open_mode; + public: + LocalDirectory(HANDLE d, fs::OpenDirectoryMode m, std::unique_ptr<wchar_t[], ::ams::fs::impl::Deleter> &&p) : m_path(std::move(p)), m_dir_handle(d), m_search_handle(INVALID_HANDLE_VALUE) { + m_open_mode = static_cast<fs::OpenDirectoryMode>(util::ToUnderlying(m) & ~util::ToUnderlying(fs::OpenDirectoryMode_NotRequireFileSize)); + } + + virtual ~LocalDirectory() { + if (m_search_handle != INVALID_HANDLE_VALUE) { + ::FindClose(m_search_handle); + } + ::CloseHandle(m_dir_handle); + } + public: + virtual Result DoRead(s64 *out_count, fs::DirectoryEntry *out_entries, s64 max_entries) override { + auto read_count = 0; + while (read_count < max_entries) { + /* Read the next file. */ + WIN32_FIND_DATAW fd; + std::memset(fd.cFileName, 0, sizeof(fd.cFileName)); + if (m_search_handle == INVALID_HANDLE_VALUE) { + /* Create our search handle. */ + if (m_search_handle = ::FindFirstFileW(m_path.get(), std::addressof(fd)); m_search_handle == INVALID_HANDLE_VALUE) { + /* Check that we failed because there are no files. */ + R_UNLESS(::GetLastError() == ERROR_FILE_NOT_FOUND, ConvertLastErrorToResult()); + break; + } + } else if (!::FindNextFileW(m_search_handle, std::addressof(fd))) { + /* Check that we failed because we ran out of files. */ + R_UNLESS(::GetLastError() == ERROR_NO_MORE_FILES, ConvertLastErrorToResult()); + break; + } + + /* If we shouldn't create an entry, continue. */ + if (!this->IsReadTarget(fd)) { + continue; + } + + /* Create the entry. */ + auto &entry = out_entries[read_count++]; + + std::memset(entry.name, 0, sizeof(entry.name)); + const auto wide_res = ::WideCharToMultiByte(CP_UTF8, 0, fd.cFileName, -1, entry.name, sizeof(entry.name), nullptr, nullptr); + R_UNLESS(wide_res != 0, fs::ResultInvalidPath()); + + entry.type = IsDirectory(fd) ? fs::DirectoryEntryType_Directory : fs::DirectoryEntryType_File; + entry.file_size = static_cast<s64>(fd.nFileSizeLow) | static_cast<s64>(static_cast<u64>(fd.nFileSizeHigh) << BITSIZEOF(fd.nFileSizeLow)); + } + + /* Set the output read count. */ + *out_count = read_count; + R_SUCCEED(); + } + + virtual Result DoGetEntryCount(s64 *out) override { + /* Open a new search handle. */ + WIN32_FIND_DATAW fd; + auto handle = ::FindFirstFileW(m_path.get(), std::addressof(fd)); + R_UNLESS(handle != INVALID_HANDLE_VALUE, ConvertLastErrorToResult()); + ON_SCOPE_EXIT { ::FindClose(handle); }; + + /* Iterate to get the total entry count. */ + auto entry_count = 0; + while (::FindNextFileW(handle, std::addressof(fd))) { + if (this->IsReadTarget(fd)) { + ++entry_count; + } + } + + /* Check that we stopped iterating because we ran out of files. */ + R_UNLESS(::GetLastError() == ERROR_NO_MORE_FILES, ConvertLastErrorToResult()); + + /* Set the output. */ + *out = entry_count; + R_SUCCEED(); + } + private: + bool IsReadTarget(const WIN32_FIND_DATAW &fd) const { + /* If the file is "..", don't return it. */ + if (::wcsncmp(fd.cFileName, L"..", 3) == 0 || ::wcsncmp(fd.cFileName, L".", 2) == 0) { + return false; + } + + /* Return whether our open mode supports the target. */ + if (IsDirectory(fd)) { + return m_open_mode != fs::OpenDirectoryMode_File; + } else { + return m_open_mode != fs::OpenDirectoryMode_Directory; + } + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override { + AMS_ABORT("GetDomainObjectId() should never be called on a LocalDirectory"); + } + }; + + #else + constexpr int MaxFilePathLength = PATH_MAX - 1; + constexpr int MaxDirectoryPathLength = PATH_MAX - 1; + + #if defined (ATMOSPHERE_OS_LINUX) + struct linux_dirent64 { + ino64_t d_ino; + off64_t d_off; + unsigned short d_reclen; + unsigned char d_type; + char d_name[]; + }; + + using NativeDirectoryEntryType = struct linux_dirent64; + #else + using NativeDirectoryEntryType = struct dirent; + #endif + + bool AreLongPathsEnabledImpl() { + /* TODO: How does this work on linux/macos? */ + return true; + } + + enum ErrnoSource { + ErrnoSource_OpenFile, // 0 + ErrnoSource_CreateFile, // 1 + ErrnoSource_Unlink, // 2 + ErrnoSource_Pread, // 3 + ErrnoSource_Pwrite, // 4 + ErrnoSource_Ftruncate, // 5 + // + ErrnoSource_OpenDirectory, // 6 + ErrnoSource_Mkdir, // 7 + ErrnoSource_Rmdir, // 8 + ErrnoSource_GetDents, // 9 + // + ErrnoSource_RenameDirectory, // 10 + ErrnoSource_RenameFile, // 11 + // + ErrnoSource_Stat, // 12 + ErrnoSource_Statvfs, // 13 + }; + + Result ConvertErrnoToResult(ErrnoSource source) { + switch (errno) { + case ENOENT: + R_THROW(fs::ResultPathNotFound()); + case EEXIST: + switch (source) { + case ErrnoSource_Rmdir: + R_THROW(fs::ResultDirectoryNotEmpty()); + default: + R_THROW(fs::ResultPathAlreadyExists()); + } + case ENOTDIR: + switch (source) { + case ErrnoSource_Rmdir: + R_THROW(fs::ResultPathNotFound()); + default: + R_THROW(fs::ResultPathNotFound()); + } + case EISDIR: + switch (source) { + case ErrnoSource_CreateFile: + R_THROW(fs::ResultPathAlreadyExists()); + case ErrnoSource_OpenFile: + case ErrnoSource_Unlink: + R_THROW(fs::ResultPathNotFound()); + default: + R_THROW(fs::ResultUnexpectedInLocalFileSystemE()); + } + case ENOTEMPTY: + R_THROW(fs::ResultDirectoryNotEmpty()); + case EACCES: + case EINTR: + R_THROW(fs::ResultTargetLocked()); + default: + //printf("Returning default errno -> result, errno=%d, source=%d\n", errno, static_cast<int>(source)); + R_THROW(fs::ResultUnexpectedInLocalFileSystemE()); + } + } + + Result WaitDeletionCompletion(const char *native_path) { + /* TODO: Does linux need to wait for delete to complete? */ + AMS_UNUSED(native_path); + R_SUCCEED(); + } + + + Result GetEntryTypeImpl(fs::DirectoryEntryType *out, const char *native_path) { + struct stat st; + R_UNLESS(::stat(native_path, std::addressof(st)) == 0, ConvertErrnoToResult(ErrnoSource_Stat)); + + *out = (S_ISDIR(st.st_mode)) ? fs::DirectoryEntryType_Directory : fs::DirectoryEntryType_File; + R_SUCCEED(); + } + + auto RetryForEIntr(auto f) { + decltype(f()) res; + do { + res = f(); + } while (res < 0 && errno == EINTR); + return res; + }; + + void CloseFileDescriptor(int handle) { + const int res = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA { + return ::close(handle); + }); + AMS_ASSERT(res == 0); + AMS_UNUSED(res); + } + + Result SetFileSizeImpl(int handle, s64 size) { + const auto res = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA { return ::ftruncate(handle, size); }); + R_UNLESS(res == 0, ConvertErrnoToResult(ErrnoSource_Ftruncate)); + R_SUCCEED(); + } + + class LocalFile : public ::ams::fs::fsa::IFile, public ::ams::fs::impl::Newable { + private: + const int m_handle; + const fs::OpenMode m_open_mode; + public: + LocalFile(int h, fs::OpenMode m) : m_handle(h), m_open_mode(m) { /* ... */ } + + virtual ~LocalFile() { + CloseFileDescriptor(m_handle); + } + public: + virtual Result DoRead(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override { + /* Check that read is possible. */ + size_t dry_read_size; + R_TRY(this->DryRead(std::addressof(dry_read_size), offset, size, option, m_open_mode)); + + /* If we have nothing to read, we don't need to do anything. */ + if (dry_read_size == 0) { + *out = 0; + R_SUCCEED(); + } + + /* Read. */ + const auto read_size = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA -> ssize_t { return ::pread(m_handle, buffer, size, offset); }); + R_UNLESS(read_size >= 0, ConvertErrnoToResult(ErrnoSource_Pread)); + + /* Set output. */ + *out = static_cast<size_t>(read_size); + R_SUCCEED(); + } + + virtual Result DoGetSize(s64 *out) override { + /* Get the file size. */ + const auto size = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA -> s64 { return ::lseek(m_handle, 0, SEEK_END); }); + R_UNLESS(size >= 0, fs::ResultUnexpectedInLocalFileSystemD()); + + /* Set the output. */ + *out = size; + R_SUCCEED(); + } + + virtual Result DoFlush() override { + /* If we're not writable, we have nothing to flush. */ + R_SUCCEED_IF((m_open_mode & fs::OpenMode_Write) == 0); + + /* Flush our buffer. */ + const auto res = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA { return ::fsync(m_handle); }); + R_UNLESS(res == 0, fs::ResultUnexpectedInLocalFileSystemC()); + R_SUCCEED(); + } + + virtual Result DoWrite(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override { + /* Verify that we can write. */ + bool needs_append; + R_TRY(this->DryWrite(std::addressof(needs_append), offset, size, option, m_open_mode)); + + /* If we need to, perform the write. */ + if (size != 0) { + /* Read. */ + const auto size_written = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA -> ssize_t { return ::pwrite(m_handle, buffer, size, offset); }); + R_UNLESS(size_written >= 0, ConvertErrnoToResult(ErrnoSource_Pwrite)); + + /* Check that a correct amount of data was written. */ + R_UNLESS(static_cast<size_t>(size_written) >= size, fs::ResultNotEnoughFreeSpace()); + + /* Sanity check that we wrote the right amount. */ + AMS_ASSERT(static_cast<size_t>(size_written) == size); + } + + /* If we need to, flush. */ + if (option.HasFlushFlag()) { + R_TRY(this->Flush()); + } + + R_SUCCEED(); + } + + virtual Result DoSetSize(s64 size) override { + /* Verify we can set the size. */ + R_TRY(this->DrySetSize(size, m_open_mode)); + + /* Try to set the file size. */ + R_RETURN(SetFileSizeImpl(m_handle, size)); + } + + virtual Result DoOperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + AMS_UNUSED(offset, size, src, src_size); + switch (op_id) { + case fs::OperationId::Invalidate: + R_SUCCEED(); + case fs::OperationId::QueryRange: + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize()); + static_cast<fs::QueryRangeInfo *>(dst)->Clear(); + R_SUCCEED(); + default: + R_THROW(fs::ResultUnsupportedOperateRangeForTmFileSystemFile()); + } + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override { + AMS_ABORT("GetDomainObjectId() should never be called on a LocalFile"); + } + }; + + class LocalDirectory : public ::ams::fs::fsa::IDirectory, public ::ams::fs::impl::Newable { + private: + std::unique_ptr<char[], ::ams::fs::impl::Deleter> m_path; + int m_dir_handle; + fs::OpenDirectoryMode m_open_mode; + bool m_not_require_file_size; + + std::unique_ptr<fs::DirectoryEntry[], ::ams::fs::impl::Deleter> m_temp_entries; + int m_temp_entries_count; + int m_temp_entries_ofs; + + #if defined(ATMOSPHERE_OS_MACOS) + uintptr_t m_basep = 0; + #endif + public: + LocalDirectory(int d, fs::OpenDirectoryMode m, std::unique_ptr<char[], ::ams::fs::impl::Deleter> &&p) : m_path(std::move(p)), m_dir_handle(d), m_temp_entries(nullptr), m_temp_entries_count(0), m_temp_entries_ofs(0) { + m_open_mode = static_cast<fs::OpenDirectoryMode>(util::ToUnderlying(m) & ~util::ToUnderlying(fs::OpenDirectoryMode_NotRequireFileSize)); + m_not_require_file_size = m & fs::OpenDirectoryMode_NotRequireFileSize; + } + + virtual ~LocalDirectory() { + CloseFileDescriptor(m_dir_handle); + } + public: + virtual Result DoRead(s64 *out_count, fs::DirectoryEntry *out_entries, s64 max_entries) override { + auto read_count = 0; + + /* Copy out any pending entries from a previous call. */ + while (m_temp_entries_ofs < m_temp_entries_count && read_count < max_entries) { + out_entries[read_count++] = m_temp_entries[m_temp_entries_ofs++]; + } + + /* If we're done with our temporary entries, release them. */ + if (m_temp_entries_ofs == m_temp_entries_count) { + m_temp_entries.reset(); + m_temp_entries_ofs = 0; + m_temp_entries_count = 0; + } + + if (read_count < max_entries) { + /* Declare buffer to hold temporary path. */ + char path_buf[PATH_MAX]; + auto base_path_len = std::strlen(m_path.get()); + std::memcpy(path_buf, m_path.get(), base_path_len); + if (path_buf[base_path_len - 1] != '/') { + path_buf[base_path_len++] = '/'; + } + + #if defined(ATMOSPHERE_OS_LINUX) + char buf[1_KB]; + #else + char buf[2_KB]; + #endif + NativeDirectoryEntryType *ent = nullptr; + while (read_count < max_entries) { + /* Read next entries. */ + #if defined (ATMOSPHERE_OS_LINUX) + const auto nread = ::syscall(SYS_getdents64, m_dir_handle, buf, sizeof(buf)); + #elif defined(ATMOSPHERE_OS_MACOS) + const auto nread = ::__getdirentries64(m_dir_handle, buf, sizeof(buf), std::addressof(m_basep)); + #else + #error "Unknown OS to read from directory FD" + #endif + R_UNLESS(nread >= 0, ConvertErrnoToResult(ErrnoSource_GetDents)); + + /* If we read nothing, we've hit the end of the directory. */ + if (nread == 0) { + break; + } + + /* Determine the number of entries we read. */ + auto cur_read_entries = 0; + for (auto pos = 0; pos < nread; pos += ent->d_reclen) { + /* Get the native entry. */ + ent = reinterpret_cast<NativeDirectoryEntryType *>(buf + pos); + + /* If the entry isn't a read target, ignore it. */ + if (IsReadTarget(ent)) { + ++cur_read_entries; + } + } + + /* If we'll end up reading more than we can fit, allocate a temporary buffer. */ + if (read_count + cur_read_entries > max_entries) { + /* Allocate temporary entries. */ + m_temp_entries_count = (read_count + cur_read_entries) - max_entries; + m_temp_entries_ofs = 0; + + /* TODO: Non-fatal? */ + m_temp_entries = fs::impl::MakeUnique<fs::DirectoryEntry[]>(m_temp_entries_count); + AMS_ABORT_UNLESS(m_temp_entries != nullptr); + } + + /* Iterate received entries. */ + for (auto pos = 0; pos < nread; pos += ent->d_reclen) { + /* Get the native entry. */ + ent = reinterpret_cast<NativeDirectoryEntryType *>(buf + pos); + + /* If the entry isn't a read target, ignore it. */ + if (!IsReadTarget(ent)) { + continue; + } + + /* Decide on the output entry. */ + fs::DirectoryEntry *out_entry; + if (read_count < max_entries) { + out_entry = std::addressof(out_entries[read_count++]); + } else { + out_entry = std::addressof(m_temp_entries[m_temp_entries_ofs++]); + } + + /* Setup the output entry. */ + { + std::memset(out_entry->name, 0, sizeof(out_entry->name)); + + const auto name_len = std::strlen(ent->d_name); + AMS_ABORT_UNLESS(name_len <= fs::EntryNameLengthMax); + + std::memcpy(out_entry->name, ent->d_name, name_len + 1); + + out_entry->type = (ent->d_type == DT_DIR) ? fs::DirectoryEntryType_Directory : fs::DirectoryEntryType_File; + + /* If we have to, get the filesize. This is (unfortunately) expensive on linux. */ + if (out_entry->type == fs::DirectoryEntryType_File && !m_not_require_file_size) { + /* Set up the temporary file path. */ + AMS_ABORT_UNLESS(base_path_len + name_len + 1 <= PATH_MAX); + std::memcpy(path_buf + base_path_len, ent->d_name, name_len + 1); + + /* Get the file stats. */ + struct stat st; + R_UNLESS(::stat(path_buf, std::addressof(st)) == 0, ConvertErrnoToResult(ErrnoSource_Stat)); + + out_entry->file_size = static_cast<s64>(st.st_size); + } + } + } + + /* Ensure our temporary entries are correct. */ + if (m_temp_entries != nullptr) { + AMS_ASSERT(read_count == max_entries); + AMS_ASSERT(m_temp_entries_ofs == m_temp_entries_count); + m_temp_entries_ofs = 0; + } + } + } + + /* Set the output read count. */ + *out_count = read_count; + R_SUCCEED(); + } + + virtual Result DoGetEntryCount(s64 *out) override { + /* Open the directory anew. */ + const auto handle = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA { return ::open(m_path.get(), O_RDONLY | O_DIRECTORY); }); + R_UNLESS(handle >= 0, ConvertErrnoToResult(ErrnoSource_OpenDirectory)); + ON_SCOPE_EXIT { CloseFileDescriptor(handle); }; + + /* Iterate to get the total entry count. */ + auto entry_count = 0; + { + #if defined(ATMOSPHERE_OS_LINUX) + char buf[1_KB]; + #else + char buf[2_KB]; + uintptr_t basep = 0; + #endif + + NativeDirectoryEntryType *ent = nullptr; + while (true) { + /* Read next entries. */ + #if defined (ATMOSPHERE_OS_LINUX) + const auto nread = ::syscall(SYS_getdents64, handle, buf, sizeof(buf)); + #elif defined(ATMOSPHERE_OS_MACOS) + const auto nread = ::__getdirentries64(handle, buf, sizeof(buf), std::addressof(basep)); + #else + #error "Unknown OS to read from directory FD" + #endif + + R_UNLESS(nread >= 0, ConvertErrnoToResult(ErrnoSource_GetDents)); + + /* If we read nothing, we've hit the end of the directory. */ + if (nread == 0) { + break; + } + + /* Iterate received entries. */ + for (auto pos = 0; pos < nread; pos += ent->d_reclen) { + /* Get the entry. */ + ent = reinterpret_cast<NativeDirectoryEntryType *>(buf + pos); + + /* If the entry is a read target, increment our count. */ + if (IsReadTarget(ent)) { + ++entry_count; + } + } + } + } + + *out = entry_count; + R_SUCCEED(); + } + private: + bool IsReadTarget(const NativeDirectoryEntryType *ent) const { + /* If the file is "..", don't return it. */ + if (std::strcmp(ent->d_name, ".") == 0 || std::strcmp(ent->d_name, "..") == 0) { + return false; + } + + /* Return whether our open mode supports the target. */ + if (ent->d_type == DT_DIR) { + return m_open_mode != fs::OpenDirectoryMode_File; + } else { + return m_open_mode != fs::OpenDirectoryMode_Directory; + } + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override { + AMS_ABORT("GetDomainObjectId() should never be called on a LocalDirectory"); + } + }; + + #endif + + bool AreLongPathsEnabled() { + AMS_FUNCTION_LOCAL_STATIC(bool, s_enabled, AreLongPathsEnabledImpl()); + return s_enabled; + } + + } + + Result LocalFileSystem::Initialize(const fs::Path &root_path, fssystem::PathCaseSensitiveMode case_sensitive_mode) { + /* Initialize our root path. */ + R_TRY(m_root_path.Initialize(root_path)); + + /* If we're not empty, we'll need to convert to a native path. */ + if (m_root_path.IsEmpty()) { + /* Reset our native path, since we're acting without a root. */ + m_native_path_buffer.reset(nullptr); + m_native_path_length = 0; + } else { + /* Convert to native path. */ + NativePathBuffer native_path = nullptr; + int native_len = 0; + #if defined(ATMOSPHERE_OS_WINDOWS) + { + /* Get path length. */ + native_len = ::MultiByteToWideChar(CP_UTF8, 0, m_root_path.GetString(), -1, nullptr, 0); + + /* Allocate our native path buffer. */ + native_path = fs::impl::MakeUnique<NativeCharacterType[]>(native_len + 1); + R_UNLESS(native_path != nullptr, fs::ResultAllocationMemoryFailedMakeUnique()); + + /* Convert path. */ + const auto res = ::MultiByteToWideChar(CP_UTF8, 0, m_root_path.GetString(), -1, native_path.get(), native_len); + R_UNLESS(res != 0, fs::ResultTooLongPath()); + R_UNLESS(res <= static_cast<int>(fs::EntryNameLengthMax + 1), fs::ResultTooLongPath()); + + /* Fix up directory separators. */ + for (NativeCharacterType *p = native_path.get(); *p != 0; ++p) { + if (*p == '/') { + *p = '\\'; + } + } + } + #else + { + /* Get path size. */ + native_len = std::strlen(m_root_path.GetString()); + + /* Tentatively assume other operating systems do the sane thing and use utf-8 strings. */ + native_path = fs::impl::MakeUnique<NativeCharacterType[]>(native_len + 1); + R_UNLESS(native_path != nullptr, fs::ResultAllocationMemoryFailedMakeUnique()); + + /* Copy in path. */ + std::memcpy(native_path.get(), m_root_path.GetString(), native_len + 1); + } + #endif + + /* Temporarily set case sensitive mode to insensitive, and verify we can get the root directory. */ + m_case_sensitive_mode = fssystem::PathCaseSensitiveMode_CaseInsensitive; + { + constexpr fs::Path RequiredRootPath = fs::MakeConstantPath("/"); + + fs::DirectoryEntryType type; + R_TRY(this->GetEntryType(std::addressof(type), RequiredRootPath)); + + R_UNLESS(type == fs::DirectoryEntryType_Directory, fs::ResultPathNotFound()); + } + + /* Set our native path members. */ + m_native_path_buffer = std::move(native_path); + m_native_path_length = native_len; + } + + /* Set our case sensitive mode. */ + m_case_sensitive_mode = case_sensitive_mode; + R_SUCCEED(); + } + + Result LocalFileSystem::GetCaseSensitivePath(int *out_size, char *dst, size_t dst_size, const char *path, const char *work_path) { + AMS_UNUSED(out_size, dst, dst_size, path, work_path); + AMS_ABORT("TODO"); + } + + Result LocalFileSystem::CheckPathCaseSensitively(const NativeCharacterType *path, const NativeCharacterType *root_path, NativeCharacterType *cs_buf, size_t cs_size, bool check_case_sensitivity) { + AMS_UNUSED(path, root_path, cs_buf, cs_size, check_case_sensitivity); + AMS_ABORT("TODO"); + } + + Result LocalFileSystem::ResolveFullPath(NativePathBuffer *out, const fs::Path &path, int max_len, int min_len, bool check_case_sensitivity) { + /* Create the full path. */ + fs::Path full_path; + R_TRY(full_path.Combine(m_root_path, path)); + + /* Check that the path is valid. */ + fs::PathFlags flags; + flags.AllowWindowsPath(); + flags.AllowRelativePath(); + flags.AllowEmptyPath(); + R_TRY(fs::PathFormatter::CheckPathFormat(full_path.GetString(), flags)); + + /* Check the path's character count. */ + #if defined(ATMOSPHERE_OS_WINDOWS) + AreLongPathsEnabled(); + // TODO: R_TRY(fs::CheckCharacterCountForWindows(full_path.GetString(), MaxBasePathLength, AreLongPathsEnabled() ? 0 : max_len)); + AMS_UNUSED(max_len); + #else + AreLongPathsEnabled(); + /* TODO: Check character count for linux/macos? */ + AMS_UNUSED(max_len); + #endif + + + /* Convert to native path. */ + NativePathBuffer native_path = nullptr; + #if defined(ATMOSPHERE_OS_WINDOWS) + { + /* Get path length. */ + const int native_len = ::MultiByteToWideChar(CP_UTF8, 0, full_path.GetString(), -1, nullptr, 0); + + /* Allocate our native path buffer. */ + native_path = fs::impl::MakeUnique<NativeCharacterType[]>(native_len + min_len + 1); + R_UNLESS(native_path != nullptr, fs::ResultAllocationMemoryFailedMakeUnique()); + + /* Convert path. */ + const auto res = ::MultiByteToWideChar(CP_UTF8, 0, full_path.GetString(), -1, native_path.get(), native_len); + R_UNLESS(res != 0, fs::ResultTooLongPath()); + R_UNLESS(res <= native_len, fs::ResultTooLongPath()); + + /* Fix up directory separators. */ + s32 len = 0; + for (NativeCharacterType *p = native_path.get(); *p != 0; ++p) { + if (*p == '/') { + *p = '\\'; + } + ++len; + } + + /* Fix up trailing : */ + if (native_path[len - 1] == ':') { + native_path[len] = '\\'; + native_path[len + 1] = 0; + } + + /* If case sensitivity is required, allocate case sensitive buffer. */ + if (m_case_sensitive_mode == PathCaseSensitiveMode_CaseSensitive && native_path[0] != 0) { + /* Allocate case sensitive buffer. */ + auto case_sensitive_buffer_size = sizeof(NativeCharacterType) * (m_native_path_length + native_len + 1 + fs::EntryNameLengthMax); + NativePathBuffer case_sensitive_path_buffer = fs::impl::MakeUnique<NativeCharacterType[]>(case_sensitive_buffer_size / sizeof(NativeCharacterType)); + R_UNLESS(case_sensitive_path_buffer != nullptr, fs::ResultAllocationMemoryFailedMakeUnique()); + + /* Get root path. */ + const NativeCharacterType *root_path = m_native_path_buffer.get() != nullptr ? m_native_path_buffer.get() : L""; + + /* Perform case sensitive path checking. */ + R_TRY(this->CheckPathCaseSensitively(native_path.get(), root_path, case_sensitive_path_buffer.get(), case_sensitive_buffer_size, check_case_sensitivity)); + } + + /* Set default path, if empty. */ + if (native_path[0] == 0) { + native_path[0] = '.'; + native_path[1] = '\\'; + native_path[2] = 0; + } + } + #else + { + /* Get path size. */ + const int native_len = std::strlen(full_path.GetString()); + + /* Tentatively assume other operating systems do the sane thing and use utf-8 strings. */ + native_path = fs::impl::MakeUnique<NativeCharacterType[]>(native_len + min_len + 1); + R_UNLESS(native_path != nullptr, fs::ResultAllocationMemoryFailedMakeUnique()); + + /* Copy in path. */ + std::memcpy(native_path.get(), full_path.GetString(), native_len + 1); + + /* TODO: Is case sensitivity adjustment needed here? */ + AMS_UNUSED(check_case_sensitivity); + } + #endif + + /* Set the output path. */ + *out = std::move(native_path); + R_SUCCEED(); + } + + Result LocalFileSystem::DoGetDiskFreeSpace(s64 *out_free, s64 *out_total, s64 *out_total_free, const fs::Path &path) { + /* Resolve the path. */ + NativePathBuffer native_path; + R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 0, false)); + + /* Get the disk free space. */ + #if defined(ATMOSPHERE_OS_WINDOWS) + { + ULARGE_INTEGER free, total, total_free; + R_UNLESS(::GetDiskFreeSpaceExW(native_path.get(), std::addressof(free), std::addressof(total), std::addressof(total_free)), ConvertLastErrorToResult()); + + *out_free = static_cast<s64>(free.QuadPart); + *out_total = static_cast<s64>(total.QuadPart); + *out_total_free = static_cast<s64>(total_free.QuadPart); + } + #else + { + struct statvfs st; + const auto res = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA { return ::statvfs(native_path.get(), std::addressof(st)); }); + R_UNLESS(res >= 0, ConvertErrnoToResult(ErrnoSource_Statvfs)); + + *out_free = static_cast<s64>(st.f_bavail) * static_cast<s64>(st.f_frsize); + *out_total = static_cast<s64>(st.f_blocks) * static_cast<s64>(st.f_frsize); + *out_total_free = static_cast<s64>(st.f_bfree) * static_cast<s64>(st.f_frsize); + } + #endif + + R_SUCCEED(); + } + + Result LocalFileSystem::DeleteDirectoryRecursivelyInternal(const NativeCharacterType *path, bool delete_top) { + #if defined(ATMOSPHERE_OS_WINDOWS) + { + /* Get the path length. */ + const auto path_len = ::wcslen(path); + + /* Allocate a new path buffer. */ + NativePathBuffer cur_path_buf = fs::impl::MakeUnique<NativeCharacterType[]>(path_len + MAX_PATH); + R_UNLESS(cur_path_buf.get() != nullptr, fs::ResultAllocationMemoryFailedMakeUnique()); + + /* Copy the path into the temporary buffer. */ + ::wcscpy(cur_path_buf.get(), path); + ::wcscat(cur_path_buf.get(), L"\\*"); + + /* Iterate the directory, deleting all contents. */ + { + /* Begin finding. */ + WIN32_FIND_DATAW fd; + const auto handle = ::FindFirstFileW(cur_path_buf.get(), std::addressof(fd)); + R_UNLESS(handle != INVALID_HANDLE_VALUE, ConvertLastErrorToResult()); + ON_SCOPE_EXIT { ::FindClose(handle); }; + + /* Clear the path from <path>\\* to path\\ */ + wchar_t * const dst = cur_path_buf.get() + path_len + 1; + *dst = 0; + + /* Loop files. */ + while (::FindNextFileW(handle, std::addressof(fd))) { + /* Skip . and .. */ + if (::wcsncmp(fd.cFileName, L"..", 3) == 0 || ::wcsncmp(fd.cFileName, L".", 2) == 0) { + continue; + } + + /* Copy the current filename to our working path. */ + ::wcscpy(dst, fd.cFileName); + + if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + /* If a directory, delete it recursively. */ + R_TRY(this->DeleteDirectoryRecursivelyInternal(cur_path_buf.get(), true)); + } else { + /* If a file, just delete it. */ + auto delete_file = [&]() -> Result { + R_UNLESS(::DeleteFileW(cur_path_buf.get()), ConvertLastErrorToResult()); + R_SUCCEED(); + }; + + R_TRY(fssystem::RetryToAvoidTargetLocked(delete_file)); + R_TRY(WaitDeletionCompletion(cur_path_buf.get())); + } + } + + /* Check that we stopped iterating because we ran out of files. */ + R_UNLESS(::GetLastError() == ERROR_NO_MORE_FILES, ConvertLastErrorToResult()); + } + + /* If we should, delete the top level directory. */ + if (delete_top) { + auto delete_impl = [&] () -> Result { + R_UNLESS(::RemoveDirectoryW(path), ConvertLastErrorToResult()); + R_SUCCEED(); + }; + + /* Perform the delete. */ + R_TRY(fssystem::RetryToAvoidTargetLocked(delete_impl)); + + /* Wait for the deletion to complete. */ + R_TRY(WaitDeletionCompletion(path)); + } + } + #else + { + /* Get the path length. */ + const auto path_len = std::strlen(path); + + /* Allocate a temporary buffer. */ + NativePathBuffer cur_path_buf = fs::impl::MakeUnique<NativeCharacterType[]>(path_len + PATH_MAX); + R_UNLESS(cur_path_buf.get() != nullptr, fs::ResultAllocationMemoryFailedMakeUnique()); + + /* Copy the path into the temporary buffer. */ + std::memcpy(cur_path_buf.get(), path, path_len); + auto ofs = path_len; + if (cur_path_buf.get()[ofs - 1] != '/') { + cur_path_buf.get()[ofs++] = '/'; + } + + /* Iterate the directory, deleting all contents. */ + { + /* Open the directory. */ + const auto handle = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA { return ::open(path, O_RDONLY | O_DIRECTORY); }); + R_UNLESS(handle >= 0, ConvertErrnoToResult(ErrnoSource_OpenDirectory)); + ON_SCOPE_EXIT { CloseFileDescriptor(handle); }; + + #if defined(ATMOSPHERE_OS_LINUX) + char buf[1_KB]; + #else + char buf[2_KB]; + uintptr_t basep = 0; + #endif + + NativeDirectoryEntryType *ent = nullptr; + static_assert(sizeof(*ent) <= sizeof(buf)); + while (true) { + /* Read next entries. */ + #if defined (ATMOSPHERE_OS_LINUX) + const auto nread = ::syscall(SYS_getdents64, handle, buf, sizeof(buf)); + #elif defined(ATMOSPHERE_OS_MACOS) + const auto nread = ::__getdirentries64(handle, buf, sizeof(buf), std::addressof(basep)); + #else + #error "Unknown OS to read from directory FD" + #endif + R_UNLESS(nread >= 0, ConvertErrnoToResult(ErrnoSource_GetDents)); + + /* If we read nothing, we've hit the end of the directory. */ + if (nread == 0) { + break; + } + + /* Iterate received entries. */ + for (auto pos = 0; pos < nread; pos += ent->d_reclen) { + /* Get the entry. */ + ent = reinterpret_cast<NativeDirectoryEntryType *>(buf + pos); + + /* Skip . and .. */ + if (std::strcmp(ent->d_name, ".") == 0 || std::strcmp(ent->d_name, "..") == 0) { + continue; + } + + /* Get the entry name length. */ + const int e_len = std::strlen(ent->d_name); + std::memcpy(cur_path_buf.get() + ofs, ent->d_name, e_len + 1); + + /* Get the dir type. */ + const auto d_type = ent->d_type; + + if (d_type == DT_DIR) { + /* If a directory, recursively delete it. */ + R_TRY(this->DeleteDirectoryRecursivelyInternal(cur_path_buf.get(), true)); + } else { + /* If a file, just delete it. */ + auto delete_file = [&]() -> Result { + const auto res = ::unlink(cur_path_buf.get()); + R_UNLESS(res >= 0, ConvertErrnoToResult(ErrnoSource_Unlink)); + R_SUCCEED(); + }; + + R_TRY(fssystem::RetryToAvoidTargetLocked(delete_file)); + R_TRY(WaitDeletionCompletion(cur_path_buf.get())); + } + } + } + } + + /* If we should, delete the top level directory. */ + if (delete_top) { + auto delete_impl = [&] () -> Result { + R_UNLESS(::rmdir(path) == 0, ConvertErrnoToResult(ErrnoSource_Rmdir)); + R_SUCCEED(); + }; + + /* Perform the delete. */ + R_TRY(fssystem::RetryToAvoidTargetLocked(delete_impl)); + + /* Wait for the deletion to complete. */ + R_TRY(WaitDeletionCompletion(path)); + } + } + #endif + + R_SUCCEED(); + } + + Result LocalFileSystem::DoCreateFile(const fs::Path &path, s64 size, int flags) { + AMS_UNUSED(flags); + + /* Resolve the path. */ + NativePathBuffer native_path; + R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 0, false)); + + /* Create the file. */ + #if defined(ATMOSPHERE_OS_WINDOWS) + { + /* Get handle to created file. */ + const auto handle = ::CreateFileW(native_path.get(), GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); + if (handle == INVALID_HANDLE_VALUE) { + /* If we failed because of target locked, it may be the case that the path already exists as a directory. */ + R_TRY_CATCH(ConvertLastErrorToResult()) { + R_CATCH(fs::ResultTargetLocked) { + /* Get the file attributes. */ + const auto attr = ::GetFileAttributesW(native_path.get()); + + /* Check they're valid. */ + R_UNLESS(attr != INVALID_FILE_ATTRIBUTES, R_CURRENT_RESULT); + + /* Check that they specify a directory. */ + R_UNLESS((attr & FILE_ATTRIBUTE_DIRECTORY) != 0, R_CURRENT_RESULT); + + /* The path is an existing directory. */ + R_THROW(fs::ResultPathAlreadyExists()); + } + } R_END_TRY_CATCH; + } + ON_RESULT_FAILURE { ::DeleteFileW(native_path.get()); }; + ON_SCOPE_EXIT { ::CloseHandle(handle); }; + + /* Set the file as sparse. */ + { + DWORD dummy; + ::DeviceIoControl(handle, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, std::addressof(dummy), nullptr); + } + + /* Set the file size. */ + if (size > 0) { + R_TRY(SetFileSizeImpl(handle, size)); + } + } + #else + { + /* Create the file. */ + const auto handle = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA -> int { + return ::open(native_path.get(), O_WRONLY | O_CREAT | O_EXCL, 0666); + }); + R_UNLESS(handle >= 0, ConvertErrnoToResult(ErrnoSource_CreateFile)); + ON_RESULT_FAILURE { ::unlink(native_path.get()); }; + ON_SCOPE_EXIT { CloseFileDescriptor(handle); }; + + /* Set the file as sparse. */ + /* TODO: How do you do this on macos/linux? */ + + /* Set the file size. */ + if (size > 0) { + R_TRY(SetFileSizeImpl(handle, size)); + } + } + #endif + + R_SUCCEED(); + } + + Result LocalFileSystem::DoDeleteFile(const fs::Path &path) { + /* Resolve the path. */ + NativePathBuffer native_path; + R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 0, true)); + + /* Delete the file, retrying on target locked. */ + auto delete_impl = [&] () -> Result { + #if defined(ATMOSPHERE_OS_WINDOWS) + { + /* Try to delete the file directly. */ + R_SUCCEED_IF(::DeleteFileW(native_path.get())); + + /* Convert the last error to a result. */ + const auto last_error_result = ConvertLastErrorToResult(); + + /* Check if access denied; it may indicate we tried to open a directory. */ + R_UNLESS(::GetLastError() == ERROR_ACCESS_DENIED, last_error_result); + + /* Check if we tried to open a directory. */ + fs::DirectoryEntryType type{}; + R_TRY(GetEntryTypeImpl(std::addressof(type), native_path.get())); + + /* If the type is anything other than directory, perform generic result conversion. */ + R_UNLESS(type == fs::DirectoryEntryType_Directory, last_error_result); + + /* Return path not found, for trying to open a file as a directory. */ + R_THROW(fs::ResultPathNotFound()); + } + #else + { + /* If on macOS, we need to check if the path is a directory before trying to unlink it. */ + /* This is because unlink succeeds on directories when executing as superuser. */ + #if defined(ATMOSPHERE_OS_MACOS) + { + /* Check if we tried to open a directory. */ + fs::DirectoryEntryType type; + R_TRY(GetEntryTypeImpl(std::addressof(type), native_path.get())); + R_UNLESS(type == fs::DirectoryEntryType_File, fs::ResultPathNotFound()); + } + #endif + + /* Delete the file. */ + const auto res = ::unlink(native_path.get()); + R_UNLESS(res >= 0, ConvertErrnoToResult(ErrnoSource_Unlink)); + } + #endif + + R_SUCCEED(); + }; + + /* Perform the delete. */ + R_TRY(fssystem::RetryToAvoidTargetLocked(delete_impl)); + + /* Wait for the deletion to complete. */ + R_RETURN(WaitDeletionCompletion(native_path.get())); + } + + Result LocalFileSystem::DoCreateDirectory(const fs::Path &path) { + /* Check for path validity. */ + R_UNLESS(path != "/", fs::ResultPathNotFound()); + R_UNLESS(path != ".", fs::ResultPathNotFound()); + + /* Resolve the path. */ + NativePathBuffer native_path; + R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxDirectoryPathLength, 0, false)); + + /* Create the directory. */ + #if defined(ATMOSPHERE_OS_WINDOWS) + R_UNLESS(::CreateDirectoryW(native_path.get(), nullptr), ConvertLastErrorToResult()); + #else + R_UNLESS(::mkdir(native_path.get(), 0777) == 0, ConvertErrnoToResult(ErrnoSource_Mkdir)); + #endif + + R_SUCCEED(); + } + + Result LocalFileSystem::DoDeleteDirectory(const fs::Path &path) { + /* Guard against deletion of raw drive. */ + #if defined(ATMOSPHERE_OS_WINDOWS) + R_UNLESS(!fs::IsWindowsDriveRootPath(path), fs::ResultDirectoryNotDeletable()); + #else + /* TODO: Linux/macOS? */ + #endif + + /* Resolve the path. */ + NativePathBuffer native_path; + R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 0, true)); + + /* Delete the directory, retrying on target locked. */ + auto delete_impl = [&] () -> Result { + #if defined(ATMOSPHERE_OS_WINDOWS) + R_UNLESS(::RemoveDirectoryW(native_path.get()), ConvertLastErrorToResult()); + #else + R_UNLESS(::rmdir(native_path.get()) == 0, ConvertErrnoToResult(ErrnoSource_Rmdir)); + #endif + + R_SUCCEED(); + }; + + /* Perform the delete. */ + R_TRY(fssystem::RetryToAvoidTargetLocked(delete_impl)); + + /* Wait for the deletion to complete. */ + R_RETURN(WaitDeletionCompletion(native_path.get())); + } + + Result LocalFileSystem::DoDeleteDirectoryRecursively(const fs::Path &path) { + /* Guard against deletion of raw drive. */ + #if defined(ATMOSPHERE_OS_WINDOWS) + R_UNLESS(!fs::IsWindowsDriveRootPath(path), fs::ResultDirectoryNotDeletable()); + #else + /* TODO: Linux/macOS? */ + #endif + + /* Resolve the path. */ + NativePathBuffer native_path; + R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 0, true)); + + /* Delete the directory. */ + R_RETURN(this->DeleteDirectoryRecursivelyInternal(native_path.get(), true)); + } + + Result LocalFileSystem::DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) { + /* Resolve the old path. */ + NativePathBuffer native_old_path; + R_TRY(this->ResolveFullPath(std::addressof(native_old_path), old_path, MaxFilePathLength, 0, true)); + + /* Resolve the new path. */ + NativePathBuffer native_new_path; + R_TRY(this->ResolveFullPath(std::addressof(native_new_path), new_path, MaxFilePathLength, 0, false)); + + /* Check that the old path is a file. */ + fs::DirectoryEntryType type{}; + R_TRY(GetEntryTypeImpl(std::addressof(type), native_old_path.get())); + R_UNLESS(type == fs::DirectoryEntryType_File, fs::ResultPathNotFound()); + + /* Perform the rename. */ + auto rename_impl = [&]() -> Result { + #if defined(ATMOSPHERE_OS_WINDOWS) + if (!::MoveFileW(native_old_path.get(), native_new_path.get())) { + R_TRY_CATCH(ConvertLastErrorToResult()) { + R_CATCH(fs::ResultTargetLocked) { + /* If we're performing a self rename, succeed. */ + R_SUCCEED_IF(::wcscmp(native_old_path.get(), native_new_path.get()) == 0); + + /* Otherwise, check if the new path already exists. */ + const auto attr = ::GetFileAttributesW(native_new_path.get()); + R_UNLESS(attr == INVALID_FILE_ATTRIBUTES, fs::ResultPathAlreadyExists()); + + /* Return the original result. */ + R_THROW(R_CURRENT_RESULT); + } + } R_END_TRY_CATCH; + } + #else + { + /* ::rename() will destroy an existing file at new path...so check for that case ahead of time. */ + { + struct stat st; + R_UNLESS(::stat(native_new_path.get(), std::addressof(st)) < 0, fs::ResultPathAlreadyExists()); + } + + /* Rename the file. */ + R_UNLESS(::rename(native_old_path.get(), native_new_path.get()) == 0, ConvertErrnoToResult(ErrnoSource_RenameFile)); + } + #endif + R_SUCCEED(); + }; + + R_RETURN(fssystem::RetryToAvoidTargetLocked(rename_impl)); + } + + Result LocalFileSystem::DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) { + /* Resolve the old path. */ + NativePathBuffer native_old_path; + R_TRY(this->ResolveFullPath(std::addressof(native_old_path), old_path, MaxDirectoryPathLength, 0, true)); + + /* Resolve the new path. */ + NativePathBuffer native_new_path; + R_TRY(this->ResolveFullPath(std::addressof(native_new_path), new_path, MaxDirectoryPathLength, 0, false)); + + /* Check that the old path is a file. */ + fs::DirectoryEntryType type{}; + R_TRY(GetEntryTypeImpl(std::addressof(type), native_old_path.get())); + R_UNLESS(type == fs::DirectoryEntryType_Directory, fs::ResultPathNotFound()); + + /* Perform the rename. */ + auto rename_impl = [&]() -> Result { + #if defined(ATMOSPHERE_OS_WINDOWS) + if (!::MoveFileW(native_old_path.get(), native_new_path.get())) { + R_TRY_CATCH(ConvertLastErrorToResult()) { + R_CATCH(fs::ResultTargetLocked) { + /* If we're performing a self rename, succeed. */ + R_SUCCEED_IF(::wcscmp(native_old_path.get(), native_new_path.get()) == 0); + + /* Otherwise, check if the new path already exists. */ + const auto attr = ::GetFileAttributesW(native_new_path.get()); + R_UNLESS(attr == INVALID_FILE_ATTRIBUTES, fs::ResultPathAlreadyExists()); + + /* Return the original result. */ + R_THROW(R_CURRENT_RESULT); + } + } R_END_TRY_CATCH; + } + #else + { + /* ::rename() will overwrite an existing empty directory at the target, so check for that ahead of time. */ + { + struct stat st; + R_UNLESS(::stat(native_new_path.get(), std::addressof(st)) < 0, fs::ResultPathAlreadyExists()); + } + + /* Rename the directory. */ + R_UNLESS(::rename(native_old_path.get(), native_new_path.get()) == 0, ConvertErrnoToResult(ErrnoSource_RenameDirectory)); + } + #endif + R_SUCCEED(); + }; + + R_RETURN(fssystem::RetryToAvoidTargetLocked(rename_impl)); + } + + Result LocalFileSystem::DoGetEntryType(fs::DirectoryEntryType *out, const fs::Path &path) { + /* Resolve the path. */ + NativePathBuffer native_path; + R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 0, true)); + + /* Get the entry type. */ + R_RETURN(GetEntryTypeImpl(out, native_path.get())); + } + + Result LocalFileSystem::DoOpenFile(std::unique_ptr<fs::fsa::IFile> *out_file, const fs::Path &path, fs::OpenMode mode) { + /* Resolve the path. */ + NativePathBuffer native_path; + R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 0, true)); + + /* Open the file, retrying on target locked. */ + auto open_impl = [&] () -> Result { + #if defined(ATMOSPHERE_OS_WINDOWS) + /* Open a windows file handle. */ + const DWORD desired_access = ((mode & fs::OpenMode_Read) ? GENERIC_READ : 0) | ((mode & fs::OpenMode_Write) ? GENERIC_WRITE : 0); + const auto file_handle = ::CreateFileW(native_path.get(), desired_access, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr); + if (file_handle == INVALID_HANDLE_VALUE) { + /* Convert the last error to a result. */ + const auto last_error_result = ConvertLastErrorToResult(); + + /* Check if access denied; it may indicate we tried to open a directory. */ + R_UNLESS(::GetLastError() == ERROR_ACCESS_DENIED, last_error_result); + + /* Check if we tried to open a directory. */ + fs::DirectoryEntryType type{}; + R_TRY(GetEntryTypeImpl(std::addressof(type), native_path.get())); + + /* If the type isn't file, return path not found. */ + R_UNLESS(type == fs::DirectoryEntryType_File, fs::ResultPathNotFound()); + + /* Return the error we encountered earlier. */ + R_THROW(last_error_result); + } + ON_RESULT_FAILURE { ::CloseHandle(file_handle); }; + #else + const bool is_read = (mode & fs::OpenMode_Read); + const bool is_write = (mode & fs::OpenMode_Write); + int file_handle = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA { + return ::open(native_path.get(), (is_read && is_write) ? (O_RDWR) : (is_write ? (O_WRONLY) : (is_read ? (O_RDONLY) : (0)))); + }); + R_UNLESS(file_handle >= 0, ConvertErrnoToResult(ErrnoSource_OpenFile)); + ON_RESULT_FAILURE { CloseFileDescriptor(file_handle); }; + #endif + + /* Create a new local file. */ + auto file = std::make_unique<LocalFile>(file_handle, mode); + R_UNLESS(file != nullptr, fs::ResultAllocationMemoryFailedInLocalFileSystemA()); + + /* Set the output file. */ + *out_file = std::move(file); + R_SUCCEED(); + }; + + R_RETURN(fssystem::RetryToAvoidTargetLocked(open_impl)); + } + + Result LocalFileSystem::DoOpenDirectory(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const fs::Path &path, fs::OpenDirectoryMode mode) { + /* Resolve the path. */ + NativePathBuffer native_path; + R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 3, true)); + + /* Open the directory, retrying on target locked. */ + auto open_impl = [&] () -> Result { + #if defined(ATMOSPHERE_OS_WINDOWS) + /* Open a handle file handle. */ + const auto dir_handle = ::CreateFileW(native_path.get(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + R_UNLESS(dir_handle != INVALID_HANDLE_VALUE, ConvertLastErrorToResult()); + ON_RESULT_FAILURE { ::CloseHandle(dir_handle); }; + + /* Check that we tried to open a directory. */ + fs::DirectoryEntryType type{}; + R_TRY(GetEntryTypeImpl(std::addressof(type), native_path.get())); + + /* If the type isn't directory, return path not found. */ + R_UNLESS(type == fs::DirectoryEntryType_Directory, fs::ResultPathNotFound()); + + /* Fix up the path for us to perform a windows search. */ + const auto native_len = ::wcslen(native_path.get()); + const bool has_sep = native_len > 0 && native_path[native_len - 1] == '\\'; + if (has_sep) { + native_path[native_len + 0] = '*'; + native_path[native_len + 1] = 0; + } else { + native_path[native_len + 0] = '\\'; + native_path[native_len + 1] = '*'; + native_path[native_len + 2] = 0; + } + #else + /* Open the directory. */ + const auto dir_handle = RetryForEIntr([&] () ALWAYS_INLINE_LAMBDA { return ::open(native_path.get(), O_RDONLY | O_DIRECTORY); }); + R_UNLESS(dir_handle >= 0, ConvertErrnoToResult(ErrnoSource_OpenDirectory)); + ON_RESULT_FAILURE { CloseFileDescriptor(dir_handle); }; + #endif + + /* Create a new local directory. */ + auto dir = std::make_unique<LocalDirectory>(dir_handle, mode, std::move(native_path)); + R_UNLESS(dir != nullptr, fs::ResultAllocationMemoryFailedInLocalFileSystemB()); + + /* Set the output directory. */ + *out_dir = std::move(dir); + R_SUCCEED(); + }; + + R_RETURN(fssystem::RetryToAvoidTargetLocked(open_impl)); + } + + Result LocalFileSystem::DoCommit() { + R_SUCCEED(); + } + + Result LocalFileSystem::DoGetFreeSpaceSize(s64 *out, const fs::Path &path) { + s64 dummy; + R_RETURN(this->DoGetDiskFreeSpace(out, std::addressof(dummy), std::addressof(dummy), path)); + } + + Result LocalFileSystem::DoGetTotalSpaceSize(s64 *out, const fs::Path &path) { + s64 dummy; + R_RETURN(this->DoGetDiskFreeSpace(std::addressof(dummy), out, std::addressof(dummy), path)); + } + + Result LocalFileSystem::DoCleanDirectoryRecursively(const fs::Path &path) { + /* Resolve the path. */ + NativePathBuffer native_path; + R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 0, true)); + + /* Delete the directory. */ + R_RETURN(this->DeleteDirectoryRecursivelyInternal(native_path.get(), false)); + } + + Result LocalFileSystem::DoGetFileTimeStampRaw(fs::FileTimeStampRaw *out, const fs::Path &path) { + /* Resolve the path. */ + NativePathBuffer native_path; + R_TRY(this->ResolveFullPath(std::addressof(native_path), path, MaxFilePathLength, 0, true)); + + /* Get the file timestamp. */ + #if defined(ATMOSPHERE_OS_WINDOWS) + { + /* Get the file attributes. */ + WIN32_FILE_ATTRIBUTE_DATA attr; + R_UNLESS(::GetFileAttributesExW(native_path.get(), GetFileExInfoStandard, std::addressof(attr)), ConvertLastErrorToResult()); + + /* Check that the file isn't a directory. */ + R_UNLESS((attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0, fs::ResultPathNotFound()); + + /* Decode the FILETIME values. */ + const s64 create = static_cast<s64>(static_cast<u64>(attr.ftCreationTime.dwLowDateTime ) | (static_cast<u64>(attr.ftCreationTime.dwHighDateTime ) << BITSIZEOF(attr.ftCreationTime.dwLowDateTime ))); + const s64 access = static_cast<s64>(static_cast<u64>(attr.ftLastAccessTime.dwLowDateTime) | (static_cast<u64>(attr.ftLastAccessTime.dwHighDateTime) << BITSIZEOF(attr.ftLastAccessTime.dwLowDateTime))); + const s64 modify = static_cast<s64>(static_cast<u64>(attr.ftLastWriteTime.dwLowDateTime ) | (static_cast<u64>(attr.ftLastWriteTime.dwHighDateTime ) << BITSIZEOF(attr.ftLastWriteTime.dwLowDateTime ))); + + /* Set the output timestamps. */ + if (m_use_posix_time) { + out->create = ConvertWindowsTimeToPosixTime(create); + out->access = ConvertWindowsTimeToPosixTime(access); + out->modify = ConvertWindowsTimeToPosixTime(modify); + } else { + out->create = create; + out->access = access; + out->modify = modify; + } + + /* We're not using local timestamps. */ + out->is_local_time = false; + } + #else + { + /* Get the file stats. */ + struct stat st; + R_UNLESS(::stat(native_path.get(), std::addressof(st)) == 0, ConvertErrnoToResult(ErrnoSource_Stat)); + + /* Check that the path isn't a directory. */ + R_UNLESS(!(S_ISDIR(st.st_mode)), fs::ResultPathNotFound()); + + /* Set the output timestamps. */ + #if defined(ATMOSPHERE_OS_LINUX) + if (m_use_posix_time) { + out->create = st.st_ctim.tv_sec; + out->access = st.st_atim.tv_sec; + out->modify = st.st_mtim.tv_sec; + } else { + out->create = ConvertPosixTimeToWindowsTime(st.st_ctim.tv_sec, st.st_ctim.tv_nsec); + out->access = ConvertPosixTimeToWindowsTime(st.st_atim.tv_sec, st.st_atim.tv_nsec); + out->modify = ConvertPosixTimeToWindowsTime(st.st_mtim.tv_sec, st.st_mtim.tv_nsec); + } + #else + if (m_use_posix_time) { + out->create = st.st_ctimespec.tv_sec; + out->access = st.st_atimespec.tv_sec; + out->modify = st.st_mtimespec.tv_sec; + } else { + out->create = ConvertPosixTimeToWindowsTime(st.st_ctimespec.tv_sec, st.st_ctimespec.tv_nsec); + out->access = ConvertPosixTimeToWindowsTime(st.st_atimespec.tv_sec, st.st_atimespec.tv_nsec); + out->modify = ConvertPosixTimeToWindowsTime(st.st_mtimespec.tv_sec, st.st_mtimespec.tv_nsec); + } + #endif + + /* We're not using local timestamps. */ + out->is_local_time = false; + } + #endif + + R_SUCCEED(); + } + + Result LocalFileSystem::DoQueryEntry(char *dst, size_t dst_size, const char *src, size_t src_size, fs::fsa::QueryId query, const fs::Path &path) { + AMS_UNUSED(dst, dst_size, src, src_size, query, path); + R_THROW(fs::ResultUnsupportedOperation()); + } + + Result LocalFileSystem::DoCommitProvisionally(s64 counter) { + AMS_UNUSED(counter); + R_SUCCEED(); + } + + Result LocalFileSystem::DoRollback() { + R_SUCCEED(); + } + +} +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_lru_list_cache.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_lru_list_cache.hpp new file mode 100644 index 00000000..68bf127b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_lru_list_cache.hpp @@ -0,0 +1,89 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fssystem { + + template<typename Key, typename Value> + class LruListCache { + NON_COPYABLE(LruListCache); + NON_MOVEABLE(LruListCache); + public: + class Node : public ::ams::fs::impl::Newable { + NON_COPYABLE(Node); + NON_MOVEABLE(Node); + public: + Key m_key; + Value m_value; + util::IntrusiveListNode m_mru_list_node; + public: + explicit Node(const Value &value) : m_value(value) { /* ... */ } + }; + private: + using MruList = typename util::IntrusiveListMemberTraits<&Node::m_mru_list_node>::ListType; + private: + MruList m_mru_list; + public: + constexpr LruListCache() : m_mru_list() { /* ... */ } + + bool FindValueAndUpdateMru(Value *out, const Key &key) { + for (auto it = m_mru_list.begin(); it != m_mru_list.end(); ++it) { + if (it->m_key == key) { + *out = it->m_value; + + m_mru_list.erase(it); + m_mru_list.push_front(*it); + + return true; + } + } + + return false; + } + + std::unique_ptr<Node> PopLruNode() { + AMS_ABORT_UNLESS(!m_mru_list.empty()); + Node *lru = std::addressof(*m_mru_list.rbegin()); + m_mru_list.pop_back(); + + return std::unique_ptr<Node>(lru); + } + + void PushMruNode(std::unique_ptr<Node> &&node, const Key &key) { + node->m_key = key; + m_mru_list.push_front(*node); + node.release(); + } + + void DeleteAllNodes() { + while (!m_mru_list.empty()) { + Node *lru = std::addressof(*m_mru_list.rbegin()); + m_mru_list.erase(m_mru_list.iterator_to(*lru)); + delete lru; + } + } + + size_t GetSize() const { + return m_mru_list.size(); + } + + bool IsEmpty() const { + return m_mru_list.empty(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_memory_resource_buffer_hold_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_memory_resource_buffer_hold_storage.hpp new file mode 100644 index 00000000..8043cd46 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_memory_resource_buffer_hold_storage.hpp @@ -0,0 +1,87 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fssystem { + + class MemoryResourceBufferHoldStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(MemoryResourceBufferHoldStorage); + NON_MOVEABLE(MemoryResourceBufferHoldStorage); + private: + std::shared_ptr<fs::IStorage> m_storage; + MemoryResource *m_memory_resource; + void *m_buffer; + size_t m_buffer_size; + public: + MemoryResourceBufferHoldStorage(std::shared_ptr<fs::IStorage> storage, MemoryResource *mr, size_t buffer_size) : m_storage(std::move(storage)), m_memory_resource(mr), m_buffer(m_memory_resource->Allocate(buffer_size)), m_buffer_size(buffer_size) { + /* ... */ + } + + virtual ~MemoryResourceBufferHoldStorage() { + /* If we have a buffer, deallocate it. */ + if (m_buffer != nullptr) { + m_memory_resource->Deallocate(m_buffer, m_buffer_size); + } + } + + ALWAYS_INLINE bool IsValid() const { return m_buffer != nullptr; } + ALWAYS_INLINE void *GetBuffer() const { return m_buffer; } + public: + virtual Result Read(s64 offset, void *buffer, size_t size) override { + /* Check pre-conditions. */ + AMS_ASSERT(m_storage != nullptr); + + R_RETURN(m_storage->Read(offset, buffer, size)); + } + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + /* Check pre-conditions. */ + AMS_ASSERT(m_storage != nullptr); + + R_RETURN(m_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + } + + virtual Result GetSize(s64 *out) override { + /* Check pre-conditions. */ + AMS_ASSERT(m_storage != nullptr); + + R_RETURN(m_storage->GetSize(out)); + } + + virtual Result Flush() override { + /* Check pre-conditions. */ + AMS_ASSERT(m_storage != nullptr); + + R_RETURN(m_storage->Flush()); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + /* Check pre-conditions. */ + AMS_ASSERT(m_storage != nullptr); + + R_RETURN(m_storage->Write(offset, buffer, size)); + } + + virtual Result SetSize(s64 size) override { + /* Check pre-conditions. */ + AMS_ASSERT(m_storage != nullptr); + + R_RETURN(m_storage->SetSize(size)); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_nca_file_system_driver.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_nca_file_system_driver.cpp new file mode 100644 index 00000000..514ec847 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_nca_file_system_driver.cpp @@ -0,0 +1,1259 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fssystem_read_only_block_cache_storage.hpp" +#include "fssystem_hierarchical_sha256_storage.hpp" +#include "fssystem_memory_resource_buffer_hold_storage.hpp" + +namespace ams::fssystem { + + namespace { + + constexpr inline s32 AesCtrExTableCacheBlockSize = AesCtrCounterExtendedStorage::NodeSize; + constexpr inline s32 AesCtrExTableCacheCount = 8; + constexpr inline s32 IndirectTableCacheBlockSize = IndirectStorage::NodeSize; + constexpr inline s32 IndirectTableCacheCount = 8; + constexpr inline s32 IndirectDataCacheBlockSize = 32_KB; + constexpr inline s32 IndirectDataCacheCount = 16; + constexpr inline s32 SparseTableCacheBlockSize = SparseStorage::NodeSize; + constexpr inline s32 SparseTableCacheCount = 4; + + constexpr inline s32 IntegrityDataCacheCount = 24; + constexpr inline s32 IntegrityHashCacheCount = 8; + + constexpr inline s32 IntegrityDataCacheCountForMeta = 16; + constexpr inline s32 IntegrityHashCacheCountForMeta = 2; + + //TODO: Better names for these? + //constexpr inline s32 CompressedDataBlockSize = 64_KB; + //constexpr inline s32 CompressedContinuousReadingSizeMax = 640_KB; + //constexpr inline s32 CompressedCacheBlockSize = 16_KB; + //constexpr inline s32 CompressedCacheCount = 32; + + constexpr inline s32 AesCtrStorageCacheBlockSize = 0x200; + constexpr inline s32 AesCtrStorageCacheCount = 9; + + class SharedNcaBodyStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(SharedNcaBodyStorage); + NON_MOVEABLE(SharedNcaBodyStorage); + private: + std::shared_ptr<fs::IStorage> m_storage; + std::shared_ptr<fssystem::NcaReader> m_nca_reader; + public: + SharedNcaBodyStorage(std::shared_ptr<fs::IStorage> s, std::shared_ptr<fssystem::NcaReader> r) : m_storage(std::move(s)), m_nca_reader(std::move(r)) { + /* ... */ + } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + /* Validate pre-conditions. */ + AMS_ASSERT(m_storage != nullptr); + + /* Read from the base storage. */ + R_RETURN(m_storage->Read(offset, buffer, size)); + } + + virtual Result GetSize(s64 *out) override { + /* Validate pre-conditions. */ + AMS_ASSERT(m_storage != nullptr); + + R_RETURN(m_storage->GetSize(out)); + } + + virtual Result Flush() override { + /* Validate pre-conditions. */ + AMS_ASSERT(m_storage != nullptr); + + R_RETURN(m_storage->Flush()); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + /* Validate pre-conditions. */ + AMS_ASSERT(m_storage != nullptr); + + /* Read from the base storage. */ + R_RETURN(m_storage->Write(offset, buffer, size)); + } + + virtual Result SetSize(s64 size) override { + /* Validate pre-conditions. */ + AMS_ASSERT(m_storage != nullptr); + + R_RETURN(m_storage->SetSize(size)); + } + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + /* Validate pre-conditions. */ + AMS_ASSERT(m_storage != nullptr); + + R_RETURN(m_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + } + }; + + inline s64 GetFsOffset(const NcaReader &reader, s32 fs_index) { + return static_cast<s64>(reader.GetFsOffset(fs_index)); + } + + inline s64 GetFsEndOffset(const NcaReader &reader, s32 fs_index) { + return static_cast<s64>(reader.GetFsEndOffset(fs_index)); + } + + inline bool IsUsingHwAesCtrForSpeedEmulation() { + auto mode = fssystem::SpeedEmulationConfiguration::GetSpeedEmulationMode(); + return mode == fs::SpeedEmulationMode::None || mode == fs::SpeedEmulationMode::Slower; + } + + using Sha256DataRegion = NcaFsHeader::Region; + using IntegrityLevelInfo = NcaFsHeader::HashData::IntegrityMetaInfo::LevelHashInfo; + using IntegrityDataInfo = IntegrityLevelInfo::HierarchicalIntegrityVerificationLevelInformation; + + } + + Result NcaFileSystemDriver::OpenStorageWithContext(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<IAsynchronousAccessSplitter> *out_splitter, NcaFsHeaderReader *out_header_reader, s32 fs_index, StorageContext *ctx) { + /* Open storage. */ + R_TRY(this->OpenStorageImpl(out, out_header_reader, fs_index, ctx)); + + /* If we have a compressed storage, use it as splitter. */ + if (ctx->compressed_storage != nullptr) { + *out_splitter = ctx->compressed_storage; + } else { + /* Otherwise, allocate a default splitter. */ + *out_splitter = fssystem::AllocateShared<DefaultAsynchronousAccessSplitter>(); + R_UNLESS(*out_splitter != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + } + + R_SUCCEED(); + } + + Result NcaFileSystemDriver::OpenStorageImpl(std::shared_ptr<fs::IStorage> *out, NcaFsHeaderReader *out_header_reader, s32 fs_index, StorageContext *ctx) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(out_header_reader != nullptr); + AMS_ASSERT(0 <= fs_index && fs_index < NcaHeader::FsCountMax); + + /* Validate the fs index. */ + R_UNLESS(m_reader->HasFsInfo(fs_index), fs::ResultPartitionNotFound()); + + /* Initialize our header reader for the fs index. */ + R_TRY(out_header_reader->Initialize(*m_reader, fs_index)); + + /* Declare the storage we're opening. */ + std::shared_ptr<fs::IStorage> storage; + + /* Process sparse layer. */ + s64 fs_data_offset = 0; + if (out_header_reader->ExistsSparseLayer()) { + /* Get the sparse info. */ + const auto &sparse_info = out_header_reader->GetSparseInfo(); + + /* Create based on whether we have a meta hash layer. */ + if (out_header_reader->ExistsSparseMetaHashLayer()) { + /* Create the sparse storage with verification. */ + R_TRY(this->CreateSparseStorageWithVerification(std::addressof(storage), std::addressof(fs_data_offset), ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, out_header_reader->GetAesCtrUpperIv(), sparse_info, out_header_reader->GetSparseMetaDataHashDataInfo(), out_header_reader->GetSparseMetaHashType())); + } else { + /* Create the sparse storage. */ + R_TRY(this->CreateSparseStorage(std::addressof(storage), std::addressof(fs_data_offset), ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, fs_index, out_header_reader->GetAesCtrUpperIv(), sparse_info)); + } + } else { + /* Get the data offsets. */ + fs_data_offset = GetFsOffset(*m_reader, fs_index); + const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index); + + /* Validate that we're within range. */ + const auto data_size = fs_end_offset - fs_data_offset; + R_UNLESS(data_size > 0, fs::ResultInvalidNcaHeader()); + + /* Create the body substorage. */ + R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size)); + + /* Potentially save the body substorage to our context. */ + if (ctx != nullptr) { + ctx->body_substorage = storage; + } + } + + /* Process patch layer. */ + const auto &patch_info = out_header_reader->GetPatchInfo(); + std::shared_ptr<fs::IStorage> patch_meta_aes_ctr_ex_meta_storage; + std::shared_ptr<fs::IStorage> patch_meta_indirect_meta_storage; + if (out_header_reader->ExistsPatchMetaHashLayer()) { + /* Check the meta hash type. */ + R_UNLESS(out_header_reader->GetPatchMetaHashType() == NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, fs::ResultRomNcaInvalidPatchMetaDataHashType()); + + /* Create the patch meta storage. */ + R_TRY(this->CreatePatchMetaStorage(std::addressof(patch_meta_aes_ctr_ex_meta_storage), std::addressof(patch_meta_indirect_meta_storage), ctx != nullptr ? std::addressof(ctx->patch_layer_info_storage) : nullptr, storage, fs_data_offset, out_header_reader->GetAesCtrUpperIv(), patch_info, out_header_reader->GetPatchMetaDataHashDataInfo(), m_hash_generator_factory_selector->GetFactory(fssystem::HashAlgorithmType_Sha2))); + } + + if (patch_info.HasAesCtrExTable()) { + /* Check the encryption type. */ + AMS_ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::None || out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx || out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash); + + /* Create the ex meta storage. */ + std::shared_ptr<fs::IStorage> aes_ctr_ex_storage_meta_storage = patch_meta_aes_ctr_ex_meta_storage; + if (aes_ctr_ex_storage_meta_storage == nullptr) { + /* If we don't have a meta storage, we must not have a patch meta hash layer. */ + AMS_ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); + + R_TRY(this->CreateAesCtrExStorageMetaStorage(std::addressof(aes_ctr_ex_storage_meta_storage), storage, fs_data_offset, out_header_reader->GetEncryptionType(), out_header_reader->GetAesCtrUpperIv(), patch_info)); + } + + /* Create the ex storage. */ + std::shared_ptr<fs::IStorage> aes_ctr_ex_storage; + R_TRY(this->CreateAesCtrExStorage(std::addressof(aes_ctr_ex_storage), ctx != nullptr ? std::addressof(ctx->aes_ctr_ex_storage) : nullptr, std::move(storage), aes_ctr_ex_storage_meta_storage, fs_data_offset, out_header_reader->GetAesCtrUpperIv(), patch_info)); + + /* Set the base storage as the ex storage. */ + storage = std::move(aes_ctr_ex_storage); + + /* Potentially save storages to our context. */ + if (ctx != nullptr) { + ctx->aes_ctr_ex_storage_meta_storage = aes_ctr_ex_storage_meta_storage; + ctx->aes_ctr_ex_storage_data_storage = storage; + ctx->fs_data_storage = storage; + } + } else { + /* Create the appropriate storage for the encryption type. */ + switch (out_header_reader->GetEncryptionType()) { + case NcaFsHeader::EncryptionType::None: + /* If there's no encryption, use the base storage we made previously. */ + break; + case NcaFsHeader::EncryptionType::AesXts: + R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), fs_data_offset)); + break; + case NcaFsHeader::EncryptionType::AesCtr: + R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset, out_header_reader->GetAesCtrUpperIv(), AlignmentStorageRequirement_None)); + break; + case NcaFsHeader::EncryptionType::AesCtrSkipLayerHash: + { + /* Create the aes ctr storage. */ + std::shared_ptr<fs::IStorage> aes_ctr_storage; + R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_storage), storage, fs_data_offset, out_header_reader->GetAesCtrUpperIv(), AlignmentStorageRequirement_None)); + + /* Create region switch storage. */ + R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), out_header_reader, std::move(storage), std::move(aes_ctr_storage))); + } + break; + default: + R_THROW(fs::ResultInvalidNcaFsHeaderEncryptionType()); + } + + /* Potentially save storages to our context. */ + if (ctx != nullptr) { + ctx->fs_data_storage = storage; + } + } + + /* Process indirect layer. */ + if (patch_info.HasIndirectTable()) { + /* Create the indirect meta storage. */ + std::shared_ptr<fs::IStorage> indirect_storage_meta_storage = patch_meta_indirect_meta_storage; + if (indirect_storage_meta_storage == nullptr) { + /* If we don't have a meta storage, we must not have a patch meta hash layer. */ + AMS_ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); + + R_TRY(this->CreateIndirectStorageMetaStorage(std::addressof(indirect_storage_meta_storage), storage, patch_info)); + } + + /* Potentially save the indirect meta storage to our context. */ + if (ctx != nullptr) { + ctx->indirect_storage_meta_storage = indirect_storage_meta_storage; + } + + /* Get the original indirectable storage. */ + std::shared_ptr<fs::IStorage> original_indirectable_storage; + if (m_original_reader != nullptr && m_original_reader->HasFsInfo(fs_index)) { + /* Create a driver for the original. */ + NcaFileSystemDriver original_driver(m_original_reader, m_allocator, m_buffer_manager, m_hash_generator_factory_selector); + + /* Create a header reader for the original. */ + NcaFsHeaderReader original_header_reader; + R_TRY(original_header_reader.Initialize(*m_original_reader, fs_index)); + + /* Open original indirectable storage. */ + R_TRY(original_driver.OpenIndirectableStorageAsOriginal(std::addressof(original_indirectable_storage), std::addressof(original_header_reader), ctx)); + } else if (ctx != nullptr && ctx->external_original_storage != nullptr) { + /* Use the external original storage. */ + original_indirectable_storage = ctx->external_original_storage; + } else { + /* Allocate a dummy memory storage as original storage. */ + original_indirectable_storage = fssystem::AllocateShared<fs::MemoryStorage>(nullptr, 0); + R_UNLESS(original_indirectable_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + } + + /* Create the indirect storage. */ + std::shared_ptr<fs::IStorage> indirect_storage; + R_TRY(this->CreateIndirectStorage(std::addressof(indirect_storage), ctx != nullptr ? std::addressof(ctx->indirect_storage) : nullptr, std::move(storage), std::move(original_indirectable_storage), std::move(indirect_storage_meta_storage), patch_info)); + + /* Set storage as the indirect storage. */ + storage = std::move(indirect_storage); + } + + /* Check if we're sparse or requested to skip the integrity layer. */ + if (out_header_reader->ExistsSparseLayer() || (ctx != nullptr && ctx->open_raw_storage)) { + *out = std::move(storage); + R_SUCCEED(); + } + + /* Create the non-raw storage. */ + R_RETURN(this->CreateStorageByRawStorage(out, out_header_reader, std::move(storage), ctx)); + } + + Result NcaFileSystemDriver::CreateStorageByRawStorage(std::shared_ptr<fs::IStorage> *out, const NcaFsHeaderReader *header_reader, std::shared_ptr<fs::IStorage> raw_storage, StorageContext *ctx) { + /* Initialize storage as raw storage. */ + std::shared_ptr<fs::IStorage> storage = std::move(raw_storage); + + /* Process hash/integrity layer. */ + switch (header_reader->GetHashType()) { + case NcaFsHeader::HashType::HierarchicalSha256Hash: + R_TRY(this->CreateSha256Storage(std::addressof(storage), std::move(storage), header_reader->GetHashData().hierarchical_sha256_data, m_hash_generator_factory_selector->GetFactory(fssystem::HashAlgorithmType_Sha2))); + break; + case NcaFsHeader::HashType::HierarchicalIntegrityHash: + R_TRY(this->CreateIntegrityVerificationStorage(std::addressof(storage), std::move(storage), header_reader->GetHashData().integrity_meta_info, m_hash_generator_factory_selector->GetFactory(fssystem::HashAlgorithmType_Sha2))); + break; + default: + R_THROW(fs::ResultInvalidNcaFsHeaderHashType()); + } + + /* Process compression layer. */ + if (header_reader->ExistsCompressionLayer()) { + R_TRY(this->CreateCompressedStorage(std::addressof(storage), ctx != nullptr ? std::addressof(ctx->compressed_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->compressed_storage_meta_storage) : nullptr, std::move(storage), header_reader->GetCompressionInfo())); + } + + /* Set output storage. */ + *out = std::move(storage); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::OpenIndirectableStorageAsOriginal(std::shared_ptr<fs::IStorage> *out, const NcaFsHeaderReader *header_reader, StorageContext *ctx) { + /* Get the fs index. */ + const auto fs_index = header_reader->GetFsIndex(); + + /* Declare the storage we're opening. */ + std::shared_ptr<fs::IStorage> storage; + + /* Process sparse layer. */ + s64 fs_data_offset = 0; + if (header_reader->ExistsSparseLayer()) { + /* Get the sparse info. */ + const auto &sparse_info = header_reader->GetSparseInfo(); + + /* Create based on whether we have a meta hash layer. */ + if (header_reader->ExistsSparseMetaHashLayer()) { + /* Create the sparse storage with verification. */ + R_TRY(this->CreateSparseStorageWithVerification(std::addressof(storage), std::addressof(fs_data_offset), ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, header_reader->GetAesCtrUpperIv(), sparse_info, header_reader->GetSparseMetaDataHashDataInfo(), header_reader->GetSparseMetaHashType())); + } else { + /* Create the sparse storage. */ + R_TRY(this->CreateSparseStorage(std::addressof(storage), std::addressof(fs_data_offset), ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, fs_index, header_reader->GetAesCtrUpperIv(), sparse_info)); + } + } else { + /* Get the data offsets. */ + fs_data_offset = GetFsOffset(*m_reader, fs_index); + const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index); + + /* Validate that we're within range. */ + const auto data_size = fs_end_offset - fs_data_offset; + R_UNLESS(data_size > 0, fs::ResultInvalidNcaHeader()); + + /* Create the body substorage. */ + R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size)); + } + + /* Create the appropriate storage for the encryption type. */ + switch (header_reader->GetEncryptionType()) { + case NcaFsHeader::EncryptionType::None: + /* If there's no encryption, use the base storage we made previously. */ + break; + case NcaFsHeader::EncryptionType::AesXts: + R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), fs_data_offset)); + break; + case NcaFsHeader::EncryptionType::AesCtr: + R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset, header_reader->GetAesCtrUpperIv(), AlignmentStorageRequirement_CacheBlockSize)); + break; + default: + R_THROW(fs::ResultInvalidNcaFsHeaderEncryptionType()); + } + + /* Set output storage. */ + *out = std::move(storage); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::CreateBodySubStorage(std::shared_ptr<fs::IStorage> *out, s64 offset, s64 size) { + /* Create the body storage. */ + auto body_storage = fssystem::AllocateShared<SharedNcaBodyStorage>(m_reader->GetSharedBodyStorage(), m_reader); + R_UNLESS(body_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Get the body storage size. */ + s64 body_size = 0; + R_TRY(body_storage->GetSize(std::addressof(body_size))); + + /* Check that we're within range. */ + R_UNLESS(offset + size <= body_size, fs::ResultNcaBaseStorageOutOfRangeB()); + + /* Create substorage. */ + auto body_substorage = fssystem::AllocateShared<fs::SubStorage>(std::move(body_storage), offset, size); + R_UNLESS(body_substorage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Set the output storage. */ + *out = std::move(body_substorage); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::CreateAesCtrStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, AlignmentStorageRequirement alignment_storage_requirement) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(base_storage != nullptr); + + /* Enforce alignment of accesses to base storage. */ + switch (alignment_storage_requirement) { + case AlignmentStorageRequirement_CacheBlockSize: + { + /* Get the base storage's size. */ + s64 base_size; + R_TRY(base_storage->GetSize(std::addressof(base_size))); + + /* Create buffered storage. */ + auto buffered_storage = fssystem::AllocateShared<BufferedStorage>(); + R_UNLESS(buffered_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Initialize the buffered storage. */ + R_TRY(buffered_storage->Initialize(fs::SubStorage(std::move(base_storage), 0, base_size), m_buffer_manager, AesCtrStorageCacheBlockSize, AesCtrStorageCacheCount)); + + /* Enable bulk read in the buffered storage. */ + buffered_storage->EnableBulkRead(); + + /* Use the buffered storage in place of our base storage. */ + base_storage = std::move(buffered_storage); + } + break; + case AlignmentStorageRequirement_None: + default: + /* No alignment enforcing is required. */ + break; + } + + /* Create the iv. */ + u8 iv[AesCtrStorageBySharedPointer::IvSize] = {}; + AesCtrStorageBySharedPointer::MakeIv(iv, sizeof(iv), upper_iv.value, offset); + + /* Create the ctr storage. */ + std::shared_ptr<fs::IStorage> aes_ctr_storage; + if (m_reader->HasExternalDecryptionKey()) { + aes_ctr_storage = fssystem::AllocateShared<AesCtrStorageExternal>(std::move(base_storage), m_reader->GetExternalDecryptionKey(), AesCtrStorageExternal::KeySize, iv, AesCtrStorageExternal::IvSize, m_reader->GetExternalDecryptAesCtrFunctionForExternalKey(), -1, -1); + R_UNLESS(aes_ctr_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + } else { + /* Create software decryption storage. */ + auto sw_storage = fssystem::AllocateShared<AesCtrStorageBySharedPointer>(base_storage, m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr), AesCtrStorageBySharedPointer::KeySize, iv, AesCtrStorageBySharedPointer::IvSize); + R_UNLESS(sw_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* If we have a hardware key and should use it, make the hardware decryption storage. */ + if (m_reader->HasInternalDecryptionKeyForAesHw() && !m_reader->IsSoftwareAesPrioritized()) { + auto hw_storage = fssystem::AllocateShared<AesCtrStorageExternal>(base_storage, m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw), AesCtrStorageExternal::KeySize, iv, AesCtrStorageExternal::IvSize, m_reader->GetExternalDecryptAesCtrFunction(), m_reader->GetKeyIndex(), m_reader->GetKeyGeneration()); + R_UNLESS(hw_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Create the selection storage. */ + auto switch_storage = fssystem::AllocateShared<SwitchStorage<bool (*)()>>(std::move(hw_storage), std::move(sw_storage), IsUsingHwAesCtrForSpeedEmulation); + R_UNLESS(switch_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Use the selection storage. */ + aes_ctr_storage = std::move(switch_storage); + } else { + /* Otherwise, just use the software decryption storage. */ + aes_ctr_storage = std::move(sw_storage); + } + } + + /* Create alignment matching storage. */ + auto aligned_storage = fssystem::AllocateShared<AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>>(std::move(aes_ctr_storage)); + R_UNLESS(aligned_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Set the out storage. */ + *out = std::move(aligned_storage); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::CreateAesXtsStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, s64 offset) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(base_storage != nullptr); + + /* Create the iv. */ + u8 iv[AesXtsStorageBySharedPointer::IvSize] = {}; + AesXtsStorageBySharedPointer::MakeAesXtsIv(iv, sizeof(iv), offset, NcaHeader::XtsBlockSize); + + /* Make the aes xts storage. */ + const auto * const key1 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts1); + const auto * const key2 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts2); + auto xts_storage = fssystem::AllocateShared<AesXtsStorageBySharedPointer>(std::move(base_storage), key1, key2, AesXtsStorageBySharedPointer::KeySize, iv, AesXtsStorageBySharedPointer::IvSize, NcaHeader::XtsBlockSize); + R_UNLESS(xts_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Create alignment matching storage. */ + auto aligned_storage = fssystem::AllocateShared<AlignmentMatchingStorage<NcaHeader::XtsBlockSize, 1>>(std::move(xts_storage)); + R_UNLESS(aligned_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Set the out storage. */ + *out = std::move(aligned_storage); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::CreateSparseStorageMetaStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(base_storage != nullptr); + + /* Get the base storage size. */ + s64 base_size = 0; + R_TRY(base_storage->GetSize(std::addressof(base_size))); + + /* Get the meta extents. */ + const auto meta_offset = sparse_info.bucket.offset; + const auto meta_size = sparse_info.bucket.size; + R_UNLESS(meta_offset + meta_size - offset <= base_size, fs::ResultNcaBaseStorageOutOfRangeB()); + + /* Create the encrypted storage. */ + auto enc_storage = fssystem::AllocateShared<fs::SubStorage>(std::move(base_storage), meta_offset, meta_size); + R_UNLESS(enc_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Create the decrypted storage. */ + std::shared_ptr<fs::IStorage> decrypted_storage; + R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv), AlignmentStorageRequirement_None)); + + /* Create meta storage. */ + auto meta_storage = fssystem::AllocateShared<BufferedStorage>(); + R_UNLESS(meta_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Initialize the meta storage. */ + R_TRY(meta_storage->Initialize(fs::SubStorage(std::move(decrypted_storage), 0, meta_size), m_buffer_manager, SparseTableCacheBlockSize, SparseTableCacheCount)); + + /* Set the output. */ + *out = std::move(meta_storage); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::CreateSparseStorageCore(std::shared_ptr<fssystem::SparseStorage> *out, std::shared_ptr<fs::IStorage> base_storage, s64 base_size, std::shared_ptr<fs::IStorage> meta_storage, const NcaSparseInfo &sparse_info, bool external_info) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(base_storage != nullptr); + AMS_ASSERT(meta_storage != nullptr); + + /* Read and verify the bucket tree header. */ + BucketTree::Header header; + std::memcpy(std::addressof(header), sparse_info.bucket.header, sizeof(header)); + R_TRY(header.Verify()); + + /* Determine storage extents. */ + const auto node_offset = 0; + const auto node_size = SparseStorage::QueryNodeStorageSize(header.entry_count); + const auto entry_offset = node_offset + node_size; + const auto entry_size = SparseStorage::QueryEntryStorageSize(header.entry_count); + + /* Create the sparse storage. */ + auto sparse_storage = fssystem::AllocateShared<fssystem::SparseStorage>(); + R_UNLESS(sparse_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Sanity check that we can be doing this. */ + AMS_ASSERT(header.entry_count != 0); + + /* Initialize the sparse storage. */ + R_TRY(sparse_storage->Initialize(m_allocator, fs::SubStorage(meta_storage, node_offset, node_size), fs::SubStorage(meta_storage, entry_offset, entry_size), header.entry_count)); + + /* If not external, set the data storage. */ + if (!external_info) { + sparse_storage->SetDataStorage(fs::SubStorage(std::move(base_storage), 0, base_size)); + } + + /* Set the output. */ + *out = std::move(sparse_storage); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::CreateSparseStorage(std::shared_ptr<fs::IStorage> *out, s64 *out_fs_data_offset, std::shared_ptr<fssystem::SparseStorage> *out_sparse_storage, std::shared_ptr<fs::IStorage> *out_meta_storage, s32 index, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(out_fs_data_offset != nullptr); + + /* Check the sparse info generation. */ + R_UNLESS(sparse_info.generation != 0, fs::ResultInvalidNcaHeader()); + + /* Read and verify the bucket tree header. */ + BucketTree::Header header; + std::memcpy(std::addressof(header), sparse_info.bucket.header, sizeof(header)); + R_TRY(header.Verify()); + + /* Determine the storage extents. */ + const auto fs_offset = GetFsOffset(*m_reader, index); + const auto fs_end_offset = GetFsEndOffset(*m_reader, index); + const auto fs_size = fs_end_offset - fs_offset; + + /* Create the sparse storage. */ + std::shared_ptr<fssystem::SparseStorage> sparse_storage; + if (header.entry_count != 0) { + /* Create the body substorage. */ + std::shared_ptr<fs::IStorage> body_substorage; + R_TRY(this->CreateBodySubStorage(std::addressof(body_substorage), sparse_info.physical_offset, sparse_info.GetPhysicalSize())); + + /* Create the meta storage. */ + std::shared_ptr<fs::IStorage> meta_storage; + R_TRY(this->CreateSparseStorageMetaStorage(std::addressof(meta_storage), body_substorage, sparse_info.physical_offset, upper_iv, sparse_info)); + + /* Potentially set the output meta storage. */ + if (out_meta_storage != nullptr) { + *out_meta_storage = meta_storage; + } + + /* Create the sparse storage. */ + R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage, sparse_info.GetPhysicalSize(), std::move(meta_storage), sparse_info, false)); + } else { + /* If there are no entries, there's nothing to actually do. */ + sparse_storage = fssystem::AllocateShared<fssystem::SparseStorage>(); + R_UNLESS(sparse_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + sparse_storage->Initialize(fs_size); + } + + /* Potentially set the output sparse storage. */ + if (out_sparse_storage != nullptr) { + *out_sparse_storage = sparse_storage; + } + + /* Set the output fs data offset. */ + *out_fs_data_offset = fs_offset; + + /* Set the output storage. */ + *out = std::move(sparse_storage); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::CreateSparseStorageMetaStorageWithVerification(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> *out_layer_info_storage, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, IHash256GeneratorFactory *hgf) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(base_storage != nullptr); + AMS_ASSERT(hgf != nullptr); + + /* Get the base storage size. */ + s64 base_size = 0; + R_TRY(base_storage->GetSize(std::addressof(base_size))); + + /* Get the meta extents. */ + const auto meta_offset = sparse_info.bucket.offset; + const auto meta_size = sparse_info.bucket.size; + R_UNLESS(meta_offset + meta_size - offset <= base_size, fs::ResultNcaBaseStorageOutOfRangeB()); + + /* Get the meta data hash data extents. */ + const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset; + const s64 meta_data_hash_data_size = util::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize); + R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, fs::ResultNcaBaseStorageOutOfRangeB()); + + /* Check that the meta is before the hash data. */ + R_UNLESS(meta_offset + meta_size <= meta_data_hash_data_offset, fs::ResultRomNcaInvalidSparseMetaDataHashDataOffset()); + + /* Check that offsets are appropriately aligned. */ + R_UNLESS(util::IsAligned<s64>(meta_data_hash_data_offset, NcaHeader::CtrBlockSize), fs::ResultRomNcaInvalidSparseMetaDataHashDataOffset()); + R_UNLESS(util::IsAligned<s64>(meta_offset, NcaHeader::CtrBlockSize), fs::ResultInvalidNcaFsHeader()); + + /* Create the meta storage. */ + auto enc_storage = fssystem::AllocateShared<fs::SubStorage>(std::move(base_storage), meta_offset, meta_data_hash_data_offset + meta_data_hash_data_size - meta_offset); + R_UNLESS(enc_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Create the decrypted storage. */ + std::shared_ptr<fs::IStorage> decrypted_storage; + R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv), AlignmentStorageRequirement_None)); + + /* Create the verification storage. */ + std::shared_ptr<fs::IStorage> integrity_storage; + R_TRY_CATCH(this->CreateIntegrityVerificationStorageForMeta(std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), meta_offset, meta_data_hash_data_info, hgf)) { + R_CONVERT(fs::ResultInvalidNcaMetaDataHashDataSize, fs::ResultRomNcaInvalidSparseMetaDataHashDataSize()) + R_CONVERT(fs::ResultInvalidNcaMetaDataHashDataHash, fs::ResultRomNcaInvalidSparseMetaDataHashDataHash()) + } R_END_TRY_CATCH; + + /* Create the meta storage. */ + auto meta_storage = fssystem::AllocateShared<fs::SubStorage>(std::move(integrity_storage), 0, meta_size); + R_UNLESS(meta_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Set the output. */ + *out = std::move(meta_storage); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::CreateSparseStorageWithVerification(std::shared_ptr<fs::IStorage> *out, s64 *out_fs_data_offset, std::shared_ptr<fssystem::SparseStorage> *out_sparse_storage, std::shared_ptr<fs::IStorage> *out_meta_storage, std::shared_ptr<fs::IStorage> *out_layer_info_storage, s32 index, const NcaAesCtrUpperIv &upper_iv, const NcaSparseInfo &sparse_info, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, NcaFsHeader::MetaDataHashType meta_data_hash_type) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(out_fs_data_offset != nullptr); + + /* Check the sparse info generation. */ + R_UNLESS(sparse_info.generation != 0, fs::ResultInvalidNcaHeader()); + + /* Read and verify the bucket tree header. */ + BucketTree::Header header; + std::memcpy(std::addressof(header), sparse_info.bucket.header, sizeof(header)); + R_TRY(header.Verify()); + + /* Determine the storage extents. */ + const auto fs_offset = GetFsOffset(*m_reader, index); + const auto fs_end_offset = GetFsEndOffset(*m_reader, index); + const auto fs_size = fs_end_offset - fs_offset; + + /* Create the sparse storage. */ + std::shared_ptr<fssystem::SparseStorage> sparse_storage; + if (header.entry_count != 0) { + /* Create the body substorage. */ + std::shared_ptr<fs::IStorage> body_substorage; + R_TRY(this->CreateBodySubStorage(std::addressof(body_substorage), sparse_info.physical_offset, util::AlignUp<s64>(static_cast<s64>(meta_data_hash_data_info.offset) + static_cast<s64>(meta_data_hash_data_info.size), NcaHeader::CtrBlockSize))); + + /* Check the meta data hash type. */ + R_UNLESS(meta_data_hash_type == NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, fs::ResultRomNcaInvalidSparseMetaDataHashType()); + + /* Create the meta storage. */ + std::shared_ptr<fs::IStorage> meta_storage; + R_TRY(this->CreateSparseStorageMetaStorageWithVerification(std::addressof(meta_storage), out_layer_info_storage, body_substorage, sparse_info.physical_offset, upper_iv, sparse_info, meta_data_hash_data_info, m_hash_generator_factory_selector->GetFactory(fssystem::HashAlgorithmType_Sha2))); + + /* Potentially set the output meta storage. */ + if (out_meta_storage != nullptr) { + *out_meta_storage = meta_storage; + } + + /* Create the sparse storage. */ + R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage, sparse_info.GetPhysicalSize(), std::move(meta_storage), sparse_info, false)); + } else { + /* If there are no entries, there's nothing to actually do. */ + sparse_storage = fssystem::AllocateShared<fssystem::SparseStorage>(); + R_UNLESS(sparse_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + sparse_storage->Initialize(fs_size); + } + + /* Potentially set the output sparse storage. */ + if (out_sparse_storage != nullptr) { + *out_sparse_storage = sparse_storage; + } + + /* Set the output fs data offset. */ + *out_fs_data_offset = fs_offset; + + /* Set the output storage. */ + *out = std::move(sparse_storage); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::CreateAesCtrExStorageMetaStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, s64 offset, NcaFsHeader::EncryptionType encryption_type, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(base_storage != nullptr); + AMS_ASSERT(encryption_type == NcaFsHeader::EncryptionType::None || encryption_type == NcaFsHeader::EncryptionType::AesCtrEx || encryption_type == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash); + AMS_ASSERT(patch_info.HasAesCtrExTable()); + + /* Validate patch info extents. */ + R_UNLESS(patch_info.indirect_size > 0, fs::ResultInvalidNcaPatchInfoIndirectSize()); + R_UNLESS(patch_info.aes_ctr_ex_size > 0, fs::ResultInvalidNcaPatchInfoAesCtrExSize()); + R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset, fs::ResultInvalidNcaPatchInfoAesCtrExOffset()); + + /* Get the base storage size. */ + s64 base_size; + R_TRY(base_storage->GetSize(std::addressof(base_size))); + + /* Get and validate the meta extents. */ + const s64 meta_offset = patch_info.aes_ctr_ex_offset; + const s64 meta_size = util::AlignUp(static_cast<s64>(patch_info.aes_ctr_ex_size), NcaHeader::XtsBlockSize); + R_UNLESS(meta_offset + meta_size <= base_size, fs::ResultNcaBaseStorageOutOfRangeB()); + + /* Create the encrypted storage. */ + auto enc_storage = fssystem::AllocateShared<fs::SubStorage>(std::move(base_storage), meta_offset, meta_size); + R_UNLESS(enc_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Create the decrypted storage. */ + std::shared_ptr<fs::IStorage> decrypted_storage; + if (encryption_type != NcaFsHeader::EncryptionType::None) { + R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), offset + meta_offset, upper_iv, AlignmentStorageRequirement_None)); + } else { + /* If encryption type is none, don't do any decryption. */ + decrypted_storage = std::move(enc_storage); + } + + /* Create meta storage. */ + auto meta_storage = fssystem::AllocateShared<BufferedStorage>(); + R_UNLESS(meta_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Initialize the meta storage. */ + R_TRY(meta_storage->Initialize(fs::SubStorage(std::move(decrypted_storage), 0, meta_size), m_buffer_manager, AesCtrExTableCacheBlockSize, AesCtrExTableCacheCount)); + + /* Create an alignment-matching storage. */ + using AlignedStorage = AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>; + auto aligned_storage = fssystem::AllocateShared<AlignedStorage>(std::move(meta_storage)); + R_UNLESS(aligned_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Set the output. */ + *out = std::move(aligned_storage); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::CreateAesCtrExStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::AesCtrCounterExtendedStorage> *out_ext, std::shared_ptr<fs::IStorage> base_storage, std::shared_ptr<fs::IStorage> meta_storage, s64 counter_offset, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info) { + /* Validate pre-conditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(base_storage != nullptr); + AMS_ASSERT(meta_storage != nullptr); + AMS_ASSERT(patch_info.HasAesCtrExTable()); + + /* Read the bucket tree header. */ + BucketTree::Header header; + std::memcpy(std::addressof(header), patch_info.aes_ctr_ex_header, sizeof(header)); + R_TRY(header.Verify()); + + /* Determine the bucket extents. */ + const auto entry_count = header.entry_count; + const s64 data_offset = 0; + const s64 data_size = patch_info.aes_ctr_ex_offset; + const s64 node_offset = 0; + const s64 node_size = AesCtrCounterExtendedStorage::QueryNodeStorageSize(entry_count); + const s64 entry_offset = node_offset + node_size; + const s64 entry_size = AesCtrCounterExtendedStorage::QueryEntryStorageSize(entry_count); + + /* Create bucket storages. */ + fs::SubStorage data_storage(std::move(base_storage), data_offset, data_size); + fs::SubStorage node_storage(meta_storage, node_offset, node_size); + fs::SubStorage entry_storage(meta_storage, entry_offset, entry_size); + + /* Get the secure value. */ + const auto secure_value = upper_iv.part.secure_value; + + /* Create the aes ctr ex storage. */ + std::shared_ptr<fs::IStorage> aes_ctr_ex_storage; + if (m_reader->HasExternalDecryptionKey()) { + /* Create the decryptor. */ + std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> decryptor; + R_TRY(AesCtrCounterExtendedStorage::CreateExternalDecryptor(std::addressof(decryptor), m_reader->GetExternalDecryptAesCtrFunctionForExternalKey(), -1, -1)); + + /* Create the aes ctr ex storage. */ + auto impl_storage = fssystem::AllocateShared<AesCtrCounterExtendedStorage>(); + R_UNLESS(impl_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Initialize the aes ctr ex storage. */ + R_TRY(impl_storage->Initialize(m_allocator, m_reader->GetExternalDecryptionKey(), AesCtrStorageBySharedPointer::KeySize, secure_value, counter_offset, data_storage, node_storage, entry_storage, entry_count, std::move(decryptor))); + + /* Potentially set the output implementation storage. */ + if (out_ext != nullptr) { + *out_ext = impl_storage; + } + + /* Set the implementation storage. */ + aes_ctr_ex_storage = std::move(impl_storage); + } else { + /* Create the software decryptor. */ + std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> sw_decryptor; + R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(sw_decryptor))); + + /* Make the software storage. */ + auto sw_storage = fssystem::AllocateShared<AesCtrCounterExtendedStorage>(); + R_UNLESS(sw_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Initialize the software storage. */ + R_TRY(sw_storage->Initialize(m_allocator, m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr), AesCtrStorageBySharedPointer::KeySize, secure_value, counter_offset, data_storage, node_storage, entry_storage, entry_count, std::move(sw_decryptor))); + + /* Potentially set the output implementation storage. */ + if (out_ext != nullptr) { + *out_ext = sw_storage; + } + + /* If we have a hardware key and should use it, make the hardware decryption storage. */ + if (m_reader->HasInternalDecryptionKeyForAesHw() && !m_reader->IsSoftwareAesPrioritized()) { + /* Create the hardware decryptor. */ + std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> hw_decryptor; + R_TRY(AesCtrCounterExtendedStorage::CreateExternalDecryptor(std::addressof(hw_decryptor), m_reader->GetExternalDecryptAesCtrFunction(), m_reader->GetKeyIndex(), m_reader->GetKeyGeneration())); + + /* Create the hardware storage. */ + auto hw_storage = fssystem::AllocateShared<AesCtrCounterExtendedStorage>(); + R_UNLESS(hw_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Initialize the hardware storage. */ + R_TRY(hw_storage->Initialize(m_allocator, m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw), AesCtrStorageBySharedPointer::KeySize, secure_value, counter_offset, data_storage, node_storage, entry_storage, entry_count, std::move(hw_decryptor))); + + /* Create the selection storage. */ + auto switch_storage = fssystem::AllocateShared<SwitchStorage<bool (*)()>>(std::move(hw_storage), std::move(sw_storage), IsUsingHwAesCtrForSpeedEmulation); + R_UNLESS(switch_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Set the implementation storage. */ + aes_ctr_ex_storage = std::move(switch_storage); + } else { + /* Set the implementation storage. */ + aes_ctr_ex_storage = std::move(sw_storage); + } + } + + /* Create an alignment-matching storage. */ + using AlignedStorage = AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>; + auto aligned_storage = fssystem::AllocateShared<AlignedStorage>(std::move(aes_ctr_ex_storage)); + R_UNLESS(aligned_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Set the output. */ + *out = std::move(aligned_storage); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::CreateIndirectStorageMetaStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, const NcaPatchInfo &patch_info) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(base_storage != nullptr); + AMS_ASSERT(patch_info.HasIndirectTable()); + + /* Get the base storage size. */ + s64 base_size = 0; + R_TRY(base_storage->GetSize(std::addressof(base_size))); + + /* Check that we're within range. */ + R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size, fs::ResultNcaBaseStorageOutOfRangeE()); + + /* Allocate the meta storage. */ + auto meta_storage = fssystem::AllocateShared<BufferedStorage>(); + R_UNLESS(meta_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Initialize the meta storage. */ + R_TRY(meta_storage->Initialize(fs::SubStorage(base_storage, patch_info.indirect_offset, patch_info.indirect_size), m_buffer_manager, IndirectTableCacheBlockSize, IndirectTableCacheCount)); + + /* Set the output. */ + *out = std::move(meta_storage); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::CreateIndirectStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::IndirectStorage> *out_ind, std::shared_ptr<fs::IStorage> base_storage, std::shared_ptr<fs::IStorage> original_data_storage, std::shared_ptr<fs::IStorage> meta_storage, const NcaPatchInfo &patch_info) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(base_storage != nullptr); + AMS_ASSERT(meta_storage != nullptr); + AMS_ASSERT(patch_info.HasIndirectTable()); + + /* Read the bucket tree header. */ + BucketTree::Header header; + std::memcpy(std::addressof(header), patch_info.indirect_header, sizeof(header)); + R_TRY(header.Verify()); + + /* Determine the storage sizes. */ + const auto node_size = IndirectStorage::QueryNodeStorageSize(header.entry_count); + const auto entry_size = IndirectStorage::QueryEntryStorageSize(header.entry_count); + R_UNLESS(node_size + entry_size <= patch_info.indirect_size, fs::ResultInvalidNcaIndirectStorageOutOfRange()); + + /* Get the indirect data size. */ + const s64 indirect_data_size = patch_info.indirect_offset; + AMS_ASSERT(util::IsAligned(indirect_data_size, NcaHeader::XtsBlockSize)); + + /* Create the indirect data storage. */ + auto indirect_data_storage = fssystem::AllocateShared<BufferedStorage>(); + R_UNLESS(indirect_data_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Initialize the indirect data storage. */ + R_TRY(indirect_data_storage->Initialize(fs::SubStorage(base_storage, 0, indirect_data_size), m_buffer_manager, IndirectDataCacheBlockSize, IndirectDataCacheCount)); + + /* Enable bulk read on the data storage. */ + indirect_data_storage->EnableBulkRead(); + + /* Create the indirect storage. */ + auto indirect_storage = fssystem::AllocateShared<IndirectStorage>(); + R_UNLESS(indirect_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Initialize the indirect storage. */ + R_TRY(indirect_storage->Initialize(m_allocator, fs::SubStorage(meta_storage, 0, node_size), fs::SubStorage(meta_storage, node_size, entry_size), header.entry_count)); + + /* Get the original data size. */ + s64 original_data_size; + R_TRY(original_data_storage->GetSize(std::addressof(original_data_size))); + + /* Set the indirect storages. */ + indirect_storage->SetStorage(0, fs::SubStorage(original_data_storage, 0, original_data_size)); + indirect_storage->SetStorage(1, fs::SubStorage(indirect_data_storage, 0, indirect_data_size)); + + /* If necessary, set the output indirect storage. */ + if (out_ind != nullptr) { + *out_ind = indirect_storage; + } + + /* Set the output. */ + *out = std::move(indirect_storage); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::CreatePatchMetaStorage(std::shared_ptr<fs::IStorage> *out_aes_ctr_ex_meta, std::shared_ptr<fs::IStorage> *out_indirect_meta, std::shared_ptr<fs::IStorage> *out_layer_info_storage, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaAesCtrUpperIv &upper_iv, const NcaPatchInfo &patch_info, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, IHash256GeneratorFactory *hgf) { + /* Validate preconditions. */ + AMS_ASSERT(out_aes_ctr_ex_meta != nullptr); + AMS_ASSERT(out_indirect_meta != nullptr); + AMS_ASSERT(base_storage != nullptr); + AMS_ASSERT(patch_info.HasAesCtrExTable()); + AMS_ASSERT(patch_info.HasIndirectTable()); + AMS_ASSERT(util::IsAligned<s64>(patch_info.aes_ctr_ex_size, NcaHeader::XtsBlockSize)); + + /* Validate patch info extents. */ + R_UNLESS(patch_info.indirect_size > 0, fs::ResultInvalidNcaPatchInfoIndirectSize()); + R_UNLESS(patch_info.aes_ctr_ex_size >= 0, fs::ResultInvalidNcaPatchInfoAesCtrExSize()); + R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset, fs::ResultInvalidNcaPatchInfoAesCtrExOffset()); + R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= meta_data_hash_data_info.offset, fs::ResultRomNcaInvalidPatchMetaDataHashDataOffset()); + + /* Get the base storage size. */ + s64 base_size; + R_TRY(base_storage->GetSize(std::addressof(base_size))); + + /* Check that extents remain within range. */ + R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size, fs::ResultNcaBaseStorageOutOfRangeE()); + R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= base_size, fs::ResultNcaBaseStorageOutOfRangeB()); + + /* Check that metadata hash data extents remain within range. */ + const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset; + const s64 meta_data_hash_data_size = util::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize); + R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, fs::ResultNcaBaseStorageOutOfRangeB()); + + /* Create the encrypted storage. */ + auto enc_storage = fssystem::AllocateShared<fs::SubStorage>(std::move(base_storage), patch_info.indirect_offset, meta_data_hash_data_offset + meta_data_hash_data_size - patch_info.indirect_offset); + R_UNLESS(enc_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Create the decrypted storage. */ + std::shared_ptr<fs::IStorage> decrypted_storage; + R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), offset + patch_info.indirect_offset, upper_iv, AlignmentStorageRequirement_None)); + + /* Create the verification storage. */ + std::shared_ptr<fs::IStorage> integrity_storage; + R_TRY_CATCH(this->CreateIntegrityVerificationStorageForMeta(std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), patch_info.indirect_offset, meta_data_hash_data_info, hgf)) { + R_CONVERT(fs::ResultInvalidNcaMetaDataHashDataSize, fs::ResultRomNcaInvalidPatchMetaDataHashDataSize()) + R_CONVERT(fs::ResultInvalidNcaMetaDataHashDataHash, fs::ResultRomNcaInvalidPatchMetaDataHashDataHash()) + } R_END_TRY_CATCH; + + /* Create the indirect meta storage. */ + auto indirect_meta_storage = fssystem::AllocateShared<fs::SubStorage>(integrity_storage, patch_info.indirect_offset - patch_info.indirect_offset, patch_info.indirect_size); + R_UNLESS(indirect_meta_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Create the aes ctr ex meta storage. */ + auto aes_ctr_ex_meta_storage = fssystem::AllocateShared<fs::SubStorage>(integrity_storage, patch_info.aes_ctr_ex_offset - patch_info.indirect_offset, patch_info.aes_ctr_ex_size); + R_UNLESS(aes_ctr_ex_meta_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Set the output. */ + *out_aes_ctr_ex_meta = std::move(aes_ctr_ex_meta_storage); + *out_indirect_meta = std::move(indirect_meta_storage); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::CreateSha256Storage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, const NcaFsHeader::HashData::HierarchicalSha256Data &hash_data, IHash256GeneratorFactory *hgf) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(base_storage != nullptr); + + /* Define storage types. */ + using VerificationStorage = HierarchicalSha256Storage<fs::SubStorage>; + using CacheStorage = ReadOnlyBlockCacheStorage; + using AlignedStorage = AlignmentMatchingStoragePooledBuffer<std::shared_ptr<fs::IStorage>, 1>; + + /* Validate the hash data. */ + R_UNLESS(util::IsPowerOfTwo(hash_data.hash_block_size), fs::ResultInvalidHierarchicalSha256BlockSize()); + R_UNLESS(hash_data.hash_layer_count == VerificationStorage::LayerCount - 1, fs::ResultInvalidHierarchicalSha256LayerCount()); + + /* Get the regions. */ + const auto &hash_region = hash_data.hash_layer_region[0]; + const auto &data_region = hash_data.hash_layer_region[1]; + + /* Determine buffer sizes. */ + constexpr s32 CacheBlockCount = 2; + const auto hash_buffer_size = static_cast<size_t>(hash_region.size); + const auto cache_buffer_size = CacheBlockCount * hash_data.hash_block_size; + const auto total_buffer_size = hash_buffer_size + cache_buffer_size; + + /* Make a buffer holder storage. */ + auto buffer_hold_storage = fssystem::AllocateShared<MemoryResourceBufferHoldStorage>(std::move(base_storage), m_allocator, total_buffer_size); + R_UNLESS(buffer_hold_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + R_UNLESS(buffer_hold_storage->IsValid(), fs::ResultAllocationMemoryFailedInNcaFileSystemDriverI()); + + /* Get storage size. */ + s64 base_size; + R_TRY(buffer_hold_storage->GetSize(std::addressof(base_size))); + + /* Check that we're within range. */ + R_UNLESS(hash_region.offset + hash_region.size <= base_size, fs::ResultNcaBaseStorageOutOfRangeC()); + R_UNLESS(data_region.offset + data_region.size <= base_size, fs::ResultNcaBaseStorageOutOfRangeC()); + + /* Create the master hash storage. */ + fs::MemoryStorage master_hash_storage(const_cast<Hash *>(std::addressof(hash_data.fs_data_master_hash)), sizeof(Hash)); + + /* Make the verification storage. */ + auto verification_storage = fssystem::AllocateShared<VerificationStorage>(); + R_UNLESS(verification_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Make layer storages. */ + fs::SubStorage layer_storages[VerificationStorage::LayerCount] = { + fs::SubStorage(std::addressof(master_hash_storage), 0, sizeof(Hash)), + fs::SubStorage(buffer_hold_storage.get(), hash_region.offset, hash_region.size), + fs::SubStorage(buffer_hold_storage, data_region.offset, data_region.size) + }; + + /* Initialize the verification storage. */ + R_TRY(verification_storage->Initialize(layer_storages, util::size(layer_storages), hash_data.hash_block_size, buffer_hold_storage->GetBuffer(), hash_buffer_size, hgf)); + + /* Make the cache storage. */ + auto cache_storage = fssystem::AllocateShared<CacheStorage>(std::move(verification_storage), hash_data.hash_block_size, static_cast<char *>(buffer_hold_storage->GetBuffer()) + hash_buffer_size, cache_buffer_size, CacheBlockCount); + R_UNLESS(cache_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Make the aligned storage. */ + auto aligned_storage = fssystem::AllocateShared<AlignedStorage>(std::move(cache_storage), hash_data.hash_block_size); + R_UNLESS(aligned_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Set the output. */ + *out = std::move(aligned_storage); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::CreateIntegrityVerificationStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, const NcaFsHeader::HashData::IntegrityMetaInfo &meta_info, IHash256GeneratorFactory *hgf) { + R_RETURN(this->CreateIntegrityVerificationStorageImpl(out, base_storage, meta_info, 0, IntegrityDataCacheCount, IntegrityHashCacheCount, fssystem::HierarchicalIntegrityVerificationStorage::GetDefaultDataCacheBufferLevel(meta_info.level_hash_info.max_layers), hgf)); + } + + Result NcaFileSystemDriver::CreateIntegrityVerificationStorageForMeta(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> *out_layer_info_storage, std::shared_ptr<fs::IStorage> base_storage, s64 offset, const NcaMetaDataHashDataInfo &meta_data_hash_data_info, IHash256GeneratorFactory *hgf) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + + /* Check the meta data hash data size. */ + R_UNLESS(meta_data_hash_data_info.size == sizeof(NcaMetaDataHashData), fs::ResultInvalidNcaMetaDataHashDataSize()); + + /* Read the meta data hash data. */ + NcaMetaDataHashData meta_data_hash_data; + R_TRY(base_storage->Read(meta_data_hash_data_info.offset - offset, std::addressof(meta_data_hash_data), sizeof(meta_data_hash_data))); + + /* Check the meta data hash data hash. */ + u8 meta_data_hash_data_hash[IHash256Generator::HashSize]; + m_hash_generator_factory_selector->GetFactory(fssystem::HashAlgorithmType_Sha2)->GenerateHash(meta_data_hash_data_hash, sizeof(meta_data_hash_data_hash), std::addressof(meta_data_hash_data), sizeof(meta_data_hash_data)); + R_UNLESS(crypto::IsSameBytes(meta_data_hash_data_hash, std::addressof(meta_data_hash_data_info.hash), sizeof(meta_data_hash_data_hash)), fs::ResultInvalidNcaMetaDataHashDataHash()); + + /* Set the out layer info storage, if necessary. */ + if (out_layer_info_storage != nullptr) { + auto layer_info_storage = fssystem::AllocateShared<fs::SubStorage>(base_storage, meta_data_hash_data.layer_info_offset - offset, meta_data_hash_data_info.offset + meta_data_hash_data_info.size - meta_data_hash_data.layer_info_offset); + R_UNLESS(layer_info_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + *out_layer_info_storage = std::move(layer_info_storage); + } + + /* Create the meta storage. */ + auto meta_storage = fssystem::AllocateShared<fs::SubStorage>(std::move(base_storage), 0, meta_data_hash_data_info.offset - offset); + R_UNLESS(meta_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Create the integrity verification storage. */ + R_RETURN(this->CreateIntegrityVerificationStorageImpl(out, std::move(meta_storage), meta_data_hash_data.integrity_meta_info, meta_data_hash_data.layer_info_offset - offset, IntegrityDataCacheCountForMeta, IntegrityHashCacheCountForMeta, 0, hgf)); + } + + Result NcaFileSystemDriver::CreateIntegrityVerificationStorageImpl(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fs::IStorage> base_storage, const NcaFsHeader::HashData::IntegrityMetaInfo &meta_info, s64 layer_info_offset, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, IHash256GeneratorFactory *hgf) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(base_storage != nullptr); + AMS_ASSERT(layer_info_offset >= 0); + + /* Define storage types. */ + using VerificationStorage = HierarchicalIntegrityVerificationStorage; + using StorageInfo = VerificationStorage::HierarchicalStorageInformation; + + /* Validate the meta info. */ + HierarchicalIntegrityVerificationInformation level_hash_info; + std::memcpy(std::addressof(level_hash_info), std::addressof(meta_info.level_hash_info), sizeof(level_hash_info)); + + R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers, fs::ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount()); + R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount, fs::ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount()); + + /* Get the base storage size. */ + s64 base_storage_size; + R_TRY(base_storage->GetSize(std::addressof(base_storage_size))); + + /* Create storage info. */ + StorageInfo storage_info; + for (s32 i = 0; i < static_cast<s32>(level_hash_info.max_layers - 2); ++i) { + const auto &layer_info = level_hash_info.info[i]; + R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size, fs::ResultNcaBaseStorageOutOfRangeD()); + + storage_info[i + 1] = fs::SubStorage(base_storage, layer_info_offset + layer_info.offset, layer_info.size); + } + + /* Set the last layer info. */ + const auto &layer_info = level_hash_info.info[level_hash_info.max_layers - 2]; + const s64 last_layer_info_offset = layer_info_offset > 0 ? 0 : layer_info.offset; + R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size, fs::ResultNcaBaseStorageOutOfRangeD()); + if (layer_info_offset > 0) { + R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset, fs::ResultRomNcaInvalidIntegrityLayerInfoOffset()); + } + storage_info.SetDataStorage(fs::SubStorage(std::move(base_storage), last_layer_info_offset, layer_info.size)); + + /* Make the integrity romfs storage. */ + auto integrity_storage = fssystem::AllocateShared<fssystem::IntegrityRomFsStorage>(); + R_UNLESS(integrity_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Initialize the integrity storage. */ + R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info, m_buffer_manager, max_data_cache_entries, max_hash_cache_entries, buffer_level, hgf)); + + /* Set the output. */ + *out = std::move(integrity_storage); + R_SUCCEED(); + } + + + Result NcaFileSystemDriver::CreateRegionSwitchStorage(std::shared_ptr<fs::IStorage> *out, const NcaFsHeaderReader *header_reader, std::shared_ptr<fs::IStorage> inside_storage, std::shared_ptr<fs::IStorage> outside_storage) { + /* Check pre-conditions. */ + AMS_ASSERT(header_reader->GetHashType() == NcaFsHeader::HashType::HierarchicalIntegrityHash); + + /* Create the region. */ + fssystem::RegionSwitchStorage::Region region = {}; + R_TRY(header_reader->GetHashTargetOffset(std::addressof(region.size))); + + /* Create the region switch storage. */ + auto region_switch_storage = fssystem::AllocateShared<fssystem::RegionSwitchStorage>(std::move(inside_storage), std::move(outside_storage), region); + R_UNLESS(region_switch_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Set the output. */ + *out = std::move(region_switch_storage); + R_SUCCEED(); + } + + Result NcaFileSystemDriver::CreateCompressedStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::CompressedStorage> *out_cmp, std::shared_ptr<fs::IStorage> *out_meta, std::shared_ptr<fs::IStorage> base_storage, const NcaCompressionInfo &compression_info) { + R_RETURN(this->CreateCompressedStorage(out, out_cmp, out_meta, std::move(base_storage), compression_info, m_reader->GetDecompressor(), m_allocator, m_buffer_manager)); + } + + Result NcaFileSystemDriver::CreateCompressedStorage(std::shared_ptr<fs::IStorage> *out, std::shared_ptr<fssystem::CompressedStorage> *out_cmp, std::shared_ptr<fs::IStorage> *out_meta, std::shared_ptr<fs::IStorage> base_storage, const NcaCompressionInfo &compression_info, GetDecompressorFunction get_decompressor, MemoryResource *allocator, fs::IBufferManager *buffer_manager) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(base_storage != nullptr); + AMS_ASSERT(get_decompressor != nullptr); + + /* Read and verify the bucket tree header. */ + BucketTree::Header header; + std::memcpy(std::addressof(header), compression_info.bucket.header, sizeof(header)); + R_TRY(header.Verify()); + + /* Determine the storage extents. */ + const auto table_offset = compression_info.bucket.offset; + const auto table_size = compression_info.bucket.size; + const auto node_size = CompressedStorage::QueryNodeStorageSize(header.entry_count); + const auto entry_size = CompressedStorage::QueryEntryStorageSize(header.entry_count); + R_UNLESS(node_size + entry_size <= table_size, fs::ResultInvalidCompressedStorageSize()); + + /* If we should, set the output meta storage. */ + if (out_meta != nullptr) { + auto meta_storage = fssystem::AllocateShared<fs::SubStorage>(base_storage, table_offset, table_size); + R_UNLESS(meta_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + *out_meta = std::move(meta_storage); + } + + /* Allocate the compressed storage. */ + auto compressed_storage = fssystem::AllocateShared<fssystem::CompressedStorage>(); + R_UNLESS(compressed_storage != nullptr, fs::ResultAllocationMemoryFailedAllocateShared()); + + /* Initialize the compressed storage. */ + R_TRY(compressed_storage->Initialize(allocator, buffer_manager, fs::SubStorage(base_storage, 0, table_offset), fs::SubStorage(base_storage, table_offset, node_size), fs::SubStorage(base_storage, table_offset + node_size, entry_size), header.entry_count, 64_KB, 640_KB, get_decompressor, 16_KB, 16_KB, 32)); + + /* Potentially set the output compressed storage. */ + if (out_cmp) { + *out_cmp = compressed_storage; + } + + /* Set the output. */ + *out = std::move(compressed_storage); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_nca_header.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_nca_header.cpp new file mode 100644 index 00000000..3437fda3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_nca_header.cpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + u8 NcaHeader::GetProperKeyGeneration() const { + return std::max(this->key_generation, this->key_generation_2); + } + + bool NcaPatchInfo::HasIndirectTable() const { + return this->indirect_size != 0; + } + + bool NcaPatchInfo::HasAesCtrExTable() const { + return this->aes_ctr_ex_size != 0; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_nca_reader.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_nca_reader.cpp new file mode 100644 index 00000000..5ea29cd3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_nca_reader.cpp @@ -0,0 +1,582 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + namespace { + + constexpr inline u32 SdkAddonVersionMin = 0x000B0000; + + constexpr Result CheckNcaMagic(u32 magic) { + /* Verify the magic is not a deprecated one. */ + R_UNLESS(magic != NcaHeader::Magic0, fs::ResultUnsupportedSdkVersion()); + R_UNLESS(magic != NcaHeader::Magic1, fs::ResultUnsupportedSdkVersion()); + R_UNLESS(magic != NcaHeader::Magic2, fs::ResultUnsupportedSdkVersion()); + + /* Verify the magic is the current one. */ + R_UNLESS(magic == NcaHeader::Magic3, fs::ResultInvalidNcaSignature()); + + R_SUCCEED(); + } + + } + + NcaReader::NcaReader() : m_body_storage(), m_header_storage(), m_decrypt_aes_ctr(), m_decrypt_aes_ctr_external(), m_is_software_aes_prioritized(false), m_is_available_sw_key(false), m_header_encryption_type(NcaHeader::EncryptionType::Auto), m_get_decompressor(), m_hash_generator_factory_selector() { + std::memset(std::addressof(m_header), 0, sizeof(m_header)); + std::memset(std::addressof(m_decryption_keys), 0, sizeof(m_decryption_keys)); + std::memset(std::addressof(m_external_decryption_key), 0, sizeof(m_external_decryption_key)); + } + + NcaReader::~NcaReader() { + /* ... */ + } + + Result NcaReader::Initialize(std::shared_ptr<fs::IStorage> base_storage, const NcaCryptoConfiguration &crypto_cfg, const NcaCompressionConfiguration &compression_cfg, IHash256GeneratorFactorySelector *hgf_selector) { + /* Validate preconditions. */ + AMS_ASSERT(base_storage != nullptr); + AMS_ASSERT(hgf_selector != nullptr); + AMS_ASSERT(m_body_storage == nullptr); + + /* Check that the crypto config is valid. */ + R_UNLESS(crypto_cfg.verify_sign1 != nullptr, fs::ResultInvalidArgument()); + + /* Create the work header storage storage. */ + std::unique_ptr<fs::IStorage> work_header_storage; + if (crypto_cfg.is_available_sw_key) { + /* If software key is available, we need to be able to generate keys. */ + R_UNLESS(crypto_cfg.generate_key != nullptr, fs::ResultInvalidArgument()); + + /* Generate keys for header. */ + using AesXtsStorageForNcaHeader = AesXtsStorageBySharedPointer; + + constexpr const s32 HeaderKeyTypeValues[NcaCryptoConfiguration::HeaderEncryptionKeyCount] = { + static_cast<s32>(KeyType::NcaHeaderKey1), + static_cast<s32>(KeyType::NcaHeaderKey2), + }; + + u8 header_decryption_keys[NcaCryptoConfiguration::HeaderEncryptionKeyCount][NcaCryptoConfiguration::Aes128KeySize]; + for (size_t i = 0; i < NcaCryptoConfiguration::HeaderEncryptionKeyCount; i++) { + crypto_cfg.generate_key(header_decryption_keys[i], AesXtsStorageForNcaHeader::KeySize, crypto_cfg.header_encrypted_encryption_keys[i], AesXtsStorageForNcaHeader::KeySize, HeaderKeyTypeValues[i]); + } + + /* Create the header storage. */ + const u8 header_iv[AesXtsStorageForNcaHeader::IvSize] = {}; + work_header_storage = std::make_unique<AesXtsStorageForNcaHeader>(base_storage, header_decryption_keys[0], header_decryption_keys[1], AesXtsStorageForNcaHeader::KeySize, header_iv, AesXtsStorageForNcaHeader::IvSize, NcaHeader::XtsBlockSize); + } else { + /* Software key isn't available, so we need to be able to decrypt externally. */ + R_UNLESS(crypto_cfg.decrypt_aes_xts_external, fs::ResultInvalidArgument()); + + /* Create the header storage. */ + using AesXtsStorageExternalForNcaHeader = AesXtsStorageExternalByPointer; + + const u8 header_iv[AesXtsStorageExternalForNcaHeader::IvSize] = {}; + work_header_storage = std::make_unique<AesXtsStorageExternalForNcaHeader>(base_storage.get(), nullptr, nullptr, AesXtsStorageExternalForNcaHeader::KeySize, header_iv, AesXtsStorageExternalForNcaHeader::IvSize, NcaHeader::XtsBlockSize, crypto_cfg.encrypt_aes_xts_external, crypto_cfg.decrypt_aes_xts_external); + } + + /* Check that we successfully created the storage. */ + R_UNLESS(work_header_storage != nullptr, fs::ResultAllocationMemoryFailedInNcaReaderA()); + + /* Read the header. */ + R_TRY(work_header_storage->Read(0, std::addressof(m_header), sizeof(m_header))); + + /* Validate the magic. */ + if (const Result magic_result = CheckNcaMagic(m_header.magic); R_FAILED(magic_result)) { + /* If we're not allowed to use plaintext headers, stop here. */ + R_UNLESS(crypto_cfg.is_plaintext_header_available, magic_result); + + /* Try to use a plaintext header. */ + R_TRY(base_storage->Read(0, std::addressof(m_header), sizeof(m_header))); + R_UNLESS(R_SUCCEEDED(CheckNcaMagic(m_header.magic)), magic_result); + + /* Configure to use the plaintext header. */ + s64 base_storage_size; + R_TRY(base_storage->GetSize(std::addressof(base_storage_size))); + work_header_storage.reset(new fs::SubStorage(base_storage, 0, base_storage_size)); + R_UNLESS(work_header_storage != nullptr, fs::ResultAllocationMemoryFailedInNcaReaderA()); + + /* Set encryption type as plaintext. */ + m_header_encryption_type = NcaHeader::EncryptionType::None; + } + + /* Validate the fixed key signature. */ + R_UNLESS(m_header.header1_signature_key_generation <= NcaCryptoConfiguration::Header1SignatureKeyGenerationMax, fs::ResultInvalidNcaHeader1SignatureKeyGeneration()); + + /* Verify the header sign1. */ + { + const u8 *sig = m_header.header_sign_1; + const size_t sig_size = NcaHeader::HeaderSignSize; + const u8 *msg = static_cast<const u8 *>(static_cast<const void *>(std::addressof(m_header.magic))); + const size_t msg_size = NcaHeader::Size - NcaHeader::HeaderSignSize * NcaHeader::HeaderSignCount; + + m_is_header_sign1_signature_valid = crypto_cfg.verify_sign1(sig, sig_size, msg, msg_size, m_header.header1_signature_key_generation); + + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + R_UNLESS(m_is_header_sign1_signature_valid, fs::ResultNcaHeaderSignature1VerificationFailed()); + #else + R_UNLESS(m_is_header_sign1_signature_valid || crypto_cfg.is_unsigned_header_available_for_host_tool, fs::ResultNcaHeaderSignature1VerificationFailed()); + #endif + } + + /* Validate the sdk version. */ + R_UNLESS(m_header.sdk_addon_version >= SdkAddonVersionMin, fs::ResultUnsupportedSdkVersion()); + + /* Validate the key index. */ + R_UNLESS(m_header.key_index < NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount || m_header.key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey, fs::ResultInvalidNcaKeyIndex()); + + /* Set our hash generator factory selector. */ + m_hash_generator_factory_selector = hgf_selector; + + /* Check if we have a rights id. */ + constexpr const u8 ZeroRightsId[NcaHeader::RightsIdSize] = {}; + if (crypto::IsSameBytes(ZeroRightsId, m_header.rights_id, NcaHeader::RightsIdSize)) { + /* If we don't, then we don't have an external key, so we need to generate decryption keys if software keys are available. */ + if (crypto_cfg.is_available_sw_key) { + crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesCtr], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesCtr * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); + + /* If we're building for non-nx board (i.e., a host tool), generate all keys for debug. */ + #if !defined(ATMOSPHERE_BOARD_NINTENDO_NX) + crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesXts1], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesXts1 * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); + crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesXts2], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesXts2 * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); + crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrEx], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesCtrEx * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); + #endif + } + + /* Copy the hardware speed emulation key. */ + std::memcpy(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrHw], m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesCtrHw * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize); + } + + /* Clear the external decryption key. */ + std::memset(m_external_decryption_key, 0, sizeof(m_external_decryption_key)); + + /* Set software key availability. */ + m_is_available_sw_key = crypto_cfg.is_available_sw_key; + + /* Set our decryptor functions. */ + m_decrypt_aes_ctr = crypto_cfg.decrypt_aes_ctr; + m_decrypt_aes_ctr_external = crypto_cfg.decrypt_aes_ctr_external; + + /* Set our decompressor function getter. */ + m_get_decompressor = compression_cfg.get_decompressor; + + /* Set our storages. */ + m_header_storage = std::move(work_header_storage); + m_body_storage = std::move(base_storage); + + R_SUCCEED(); + } + + std::shared_ptr<fs::IStorage> NcaReader::GetSharedBodyStorage() { + AMS_ASSERT(m_body_storage != nullptr); + return m_body_storage; + } + + u32 NcaReader::GetMagic() const { + AMS_ASSERT(m_body_storage != nullptr); + return m_header.magic; + } + + NcaHeader::DistributionType NcaReader::GetDistributionType() const { + AMS_ASSERT(m_body_storage != nullptr); + return m_header.distribution_type; + } + + NcaHeader::ContentType NcaReader::GetContentType() const { + AMS_ASSERT(m_body_storage != nullptr); + return m_header.content_type; + } + + u8 NcaReader::GetHeaderSign1KeyGeneration() const { + AMS_ASSERT(m_body_storage != nullptr); + return m_header.header1_signature_key_generation; + } + + u8 NcaReader::GetKeyGeneration() const { + AMS_ASSERT(m_body_storage != nullptr); + return m_header.GetProperKeyGeneration(); + } + + u8 NcaReader::GetKeyIndex() const { + AMS_ASSERT(m_body_storage != nullptr); + return m_header.key_index; + } + + u64 NcaReader::GetContentSize() const { + AMS_ASSERT(m_body_storage != nullptr); + return m_header.content_size; + } + + u64 NcaReader::GetProgramId() const { + AMS_ASSERT(m_body_storage != nullptr); + return m_header.program_id; + } + + u32 NcaReader::GetContentIndex() const { + AMS_ASSERT(m_body_storage != nullptr); + return m_header.content_index; + } + + u32 NcaReader::GetSdkAddonVersion() const { + AMS_ASSERT(m_body_storage != nullptr); + return m_header.sdk_addon_version; + } + + void NcaReader::GetRightsId(u8 *dst, size_t dst_size) const { + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(dst_size >= NcaHeader::RightsIdSize); + AMS_UNUSED(dst_size); + + std::memcpy(dst, m_header.rights_id, NcaHeader::RightsIdSize); + } + + bool NcaReader::HasFsInfo(s32 index) const { + AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return m_header.fs_info[index].start_sector != 0 || m_header.fs_info[index].end_sector != 0; + } + + s32 NcaReader::GetFsCount() const { + AMS_ASSERT(m_body_storage != nullptr); + for (s32 i = 0; i < NcaHeader::FsCountMax; i++) { + if (!this->HasFsInfo(i)) { + return i; + } + } + return NcaHeader::FsCountMax; + } + + const Hash &NcaReader::GetFsHeaderHash(s32 index) const { + AMS_ASSERT(m_body_storage != nullptr); + AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return m_header.fs_header_hash[index]; + } + + void NcaReader::GetFsHeaderHash(Hash *dst, s32 index) const { + AMS_ASSERT(m_body_storage != nullptr); + AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax); + AMS_ASSERT(dst != nullptr); + std::memcpy(dst, std::addressof(m_header.fs_header_hash[index]), sizeof(*dst)); + } + + void NcaReader::GetFsInfo(NcaHeader::FsInfo *dst, s32 index) const { + AMS_ASSERT(m_body_storage != nullptr); + AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax); + AMS_ASSERT(dst != nullptr); + std::memcpy(dst, std::addressof(m_header.fs_info[index]), sizeof(*dst)); + } + + u64 NcaReader::GetFsOffset(s32 index) const { + AMS_ASSERT(m_body_storage != nullptr); + AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return NcaHeader::SectorToByte(m_header.fs_info[index].start_sector); + } + + u64 NcaReader::GetFsEndOffset(s32 index) const { + AMS_ASSERT(m_body_storage != nullptr); + AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector); + } + + u64 NcaReader::GetFsSize(s32 index) const { + AMS_ASSERT(m_body_storage != nullptr); + AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector - m_header.fs_info[index].start_sector); + } + + void NcaReader::GetEncryptedKey(void *dst, size_t size) const { + AMS_ASSERT(m_body_storage != nullptr); + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(size >= NcaHeader::EncryptedKeyAreaSize); + AMS_UNUSED(size); + + std::memcpy(dst, m_header.encrypted_key_area, NcaHeader::EncryptedKeyAreaSize); + } + + const void *NcaReader::GetDecryptionKey(s32 index) const { + AMS_ASSERT(m_body_storage != nullptr); + AMS_ASSERT(0 <= index && index < NcaHeader::DecryptionKey_Count); + return m_decryption_keys[index]; + } + + bool NcaReader::HasValidInternalKey() const { + constexpr const u8 ZeroKey[crypto::AesDecryptor128::KeySize] = {}; + for (s32 i = 0; i < NcaHeader::DecryptionKey_Count; i++) { + if (!crypto::IsSameBytes(ZeroKey, m_header.encrypted_key_area + i * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize)) { + return true; + } + } + return false; + } + + bool NcaReader::HasInternalDecryptionKeyForAesHw() const { + constexpr const u8 ZeroKey[crypto::AesDecryptor128::KeySize] = {}; + return !crypto::IsSameBytes(ZeroKey, this->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw), crypto::AesDecryptor128::KeySize); + } + + bool NcaReader::IsSoftwareAesPrioritized() const { + return m_is_software_aes_prioritized; + } + + void NcaReader::PrioritizeSoftwareAes() { + m_is_software_aes_prioritized = true; + } + + bool NcaReader::IsAvailableSwKey() const { + return m_is_available_sw_key; + } + + bool NcaReader::HasExternalDecryptionKey() const { + constexpr const u8 ZeroKey[crypto::AesDecryptor128::KeySize] = {}; + return !crypto::IsSameBytes(ZeroKey, this->GetExternalDecryptionKey(), crypto::AesDecryptor128::KeySize); + } + + const void *NcaReader::GetExternalDecryptionKey() const { + return m_external_decryption_key; + } + + void NcaReader::SetExternalDecryptionKey(const void *src, size_t size) { + AMS_ASSERT(src != nullptr); + AMS_ASSERT(size == sizeof(m_external_decryption_key)); + AMS_UNUSED(size); + + std::memcpy(m_external_decryption_key, src, sizeof(m_external_decryption_key)); + } + + void NcaReader::GetRawData(void *dst, size_t dst_size) const { + AMS_ASSERT(m_body_storage != nullptr); + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(dst_size >= sizeof(NcaHeader)); + AMS_UNUSED(dst_size); + + std::memcpy(dst, std::addressof(m_header), sizeof(NcaHeader)); + } + + DecryptAesCtrFunction NcaReader::GetExternalDecryptAesCtrFunction() const { + AMS_ASSERT(m_decrypt_aes_ctr != nullptr); + return m_decrypt_aes_ctr; + } + + DecryptAesCtrFunction NcaReader::GetExternalDecryptAesCtrFunctionForExternalKey() const { + AMS_ASSERT(m_decrypt_aes_ctr_external != nullptr); + return m_decrypt_aes_ctr_external; + } + + GetDecompressorFunction NcaReader::GetDecompressor() const { + AMS_ASSERT(m_get_decompressor != nullptr); + return m_get_decompressor; + } + + IHash256GeneratorFactorySelector *NcaReader::GetHashGeneratorFactorySelector() const { + AMS_ASSERT(m_hash_generator_factory_selector != nullptr); + return m_hash_generator_factory_selector; + } + + NcaHeader::EncryptionType NcaReader::GetEncryptionType() const { + return m_header_encryption_type; + } + + Result NcaReader::ReadHeader(NcaFsHeader *dst, s32 index) const { + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax); + + const s64 offset = sizeof(NcaHeader) + sizeof(NcaFsHeader) * index; + R_RETURN(m_header_storage->Read(offset, dst, sizeof(NcaFsHeader))); + } + + bool NcaReader::GetHeaderSign1Valid() const { + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + AMS_ABORT_UNLESS(m_is_header_sign1_signature_valid); + #endif + + return m_is_header_sign1_signature_valid; + } + + void NcaReader::GetHeaderSign2(void *dst, size_t size) const { + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(size == NcaHeader::HeaderSignSize); + + std::memcpy(dst, m_header.header_sign_2, size); + } + + void NcaReader::GetHeaderSign2TargetHash(void *dst, size_t size) const { + AMS_ASSERT(m_hash_generator_factory_selector!= nullptr); + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(size == IHash256Generator::HashSize); + + auto * const factory = m_hash_generator_factory_selector->GetFactory(fssystem::HashAlgorithmType_Sha2); + return factory->GenerateHash(dst, size, static_cast<const void *>(std::addressof(m_header.magic)), NcaHeader::Size - NcaHeader::HeaderSignSize * NcaHeader::HeaderSignCount); + } + + Result NcaFsHeaderReader::Initialize(const NcaReader &reader, s32 index) { + /* Reset ourselves to uninitialized. */ + m_fs_index = -1; + + /* Read the header. */ + R_TRY(reader.ReadHeader(std::addressof(m_data), index)); + + /* Generate the hash. */ + Hash hash; + crypto::GenerateSha256(std::addressof(hash), sizeof(hash), std::addressof(m_data), sizeof(NcaFsHeader)); + + /* Validate the hash. */ + R_UNLESS(crypto::IsSameBytes(std::addressof(reader.GetFsHeaderHash(index)), std::addressof(hash), sizeof(Hash)), fs::ResultNcaFsHeaderHashVerificationFailed()); + + /* Set our index. */ + m_fs_index = index; + R_SUCCEED(); + } + + void NcaFsHeaderReader::GetRawData(void *dst, size_t dst_size) const { + AMS_ASSERT(this->IsInitialized()); + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(dst_size >= sizeof(NcaFsHeader)); + AMS_UNUSED(dst_size); + + std::memcpy(dst, std::addressof(m_data), sizeof(NcaFsHeader)); + } + + NcaFsHeader::HashData &NcaFsHeaderReader::GetHashData() { + AMS_ASSERT(this->IsInitialized()); + return m_data.hash_data; + } + + const NcaFsHeader::HashData &NcaFsHeaderReader::GetHashData() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.hash_data; + } + + u16 NcaFsHeaderReader::GetVersion() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.version; + } + + s32 NcaFsHeaderReader::GetFsIndex() const { + AMS_ASSERT(this->IsInitialized()); + return m_fs_index; + } + + NcaFsHeader::FsType NcaFsHeaderReader::GetFsType() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.fs_type; + } + + NcaFsHeader::HashType NcaFsHeaderReader::GetHashType() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.hash_type; + } + + NcaFsHeader::EncryptionType NcaFsHeaderReader::GetEncryptionType() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.encryption_type; + } + + NcaPatchInfo &NcaFsHeaderReader::GetPatchInfo() { + AMS_ASSERT(this->IsInitialized()); + return m_data.patch_info; + } + + const NcaPatchInfo &NcaFsHeaderReader::GetPatchInfo() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.patch_info; + } + + const NcaAesCtrUpperIv NcaFsHeaderReader::GetAesCtrUpperIv() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.aes_ctr_upper_iv; + } + + bool NcaFsHeaderReader::IsSkipLayerHashEncryption() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.IsSkipLayerHashEncryption(); + } + + Result NcaFsHeaderReader::GetHashTargetOffset(s64 *out) const { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(this->IsInitialized()); + + R_RETURN(m_data.GetHashTargetOffset(out)); + } + + bool NcaFsHeaderReader::ExistsSparseLayer() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.sparse_info.generation != 0; + } + + NcaSparseInfo &NcaFsHeaderReader::GetSparseInfo() { + AMS_ASSERT(this->IsInitialized()); + return m_data.sparse_info; + } + + const NcaSparseInfo &NcaFsHeaderReader::GetSparseInfo() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.sparse_info; + } + + bool NcaFsHeaderReader::ExistsCompressionLayer() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.compression_info.bucket.offset != 0 && m_data.compression_info.bucket.size != 0; + } + + NcaCompressionInfo &NcaFsHeaderReader::GetCompressionInfo() { + AMS_ASSERT(this->IsInitialized()); + return m_data.compression_info; + } + + const NcaCompressionInfo &NcaFsHeaderReader::GetCompressionInfo() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.compression_info; + } + + bool NcaFsHeaderReader::ExistsPatchMetaHashLayer() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info.size != 0 && this->GetPatchInfo().HasIndirectTable(); + } + + NcaMetaDataHashDataInfo &NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() { + AMS_ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; + } + + const NcaMetaDataHashDataInfo &NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; + } + + NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetPatchMetaHashType() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_type; + } + + bool NcaFsHeaderReader::ExistsSparseMetaHashLayer() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info.size != 0 && this->ExistsSparseLayer(); + } + + NcaMetaDataHashDataInfo &NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() { + AMS_ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; + } + + const NcaMetaDataHashDataInfo &NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; + } + + NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetSparseMetaHashType() const { + AMS_ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_type; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system.cpp new file mode 100644 index 00000000..1cb74ff1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system.cpp @@ -0,0 +1,474 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + namespace { + + constexpr const char RootPath[] = "/"; + + class PartitionFileSystemDefaultAllocator : public MemoryResource { + private: + virtual void *AllocateImpl(size_t size, size_t alignment) override { + AMS_UNUSED(alignment); + return ::ams::fs::impl::Allocate(size); + } + + virtual void DeallocateImpl(void *buffer, size_t size, size_t alignment) override { + AMS_UNUSED(alignment); + ::ams::fs::impl::Deallocate(buffer, size); + } + + virtual bool IsEqualImpl(const MemoryResource &rhs) const override { + return this == std::addressof(rhs); + } + }; + + PartitionFileSystemDefaultAllocator g_partition_filesystem_default_allocator; + + } + + template <typename MetaType> + class PartitionFileSystemCore<MetaType>::PartitionFile : public fs::fsa::IFile, public fs::impl::Newable { + private: + const typename MetaType::PartitionEntry *m_partition_entry; + const PartitionFileSystemCore<MetaType> *m_parent; + const fs::OpenMode m_mode; + public: + PartitionFile(PartitionFileSystemCore<MetaType> *parent, const typename MetaType::PartitionEntry *partition_entry, fs::OpenMode mode) : m_partition_entry(partition_entry), m_parent(parent), m_mode(mode) { /* ... */ } + private: + virtual Result DoRead(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override final; + + virtual Result DoGetSize(s64 *out) override final { + *out = m_partition_entry->size; + R_SUCCEED(); + } + + virtual Result DoFlush() override final { + /* Nothing to do if writing disallowed. */ + R_SUCCEED_IF((m_mode & fs::OpenMode_Write) == 0); + + /* Flush base storage. */ + R_RETURN(m_parent->m_base_storage->Flush()); + } + + virtual Result DoWrite(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override final { + /* Ensure appending is not required. */ + bool needs_append; + R_TRY(this->DryWrite(std::addressof(needs_append), offset, size, option, m_mode)); + R_UNLESS(!needs_append, fs::ResultUnsupportedWriteForPartitionFile()); + + /* Appending is prohibited. */ + AMS_ASSERT((m_mode & fs::OpenMode_AllowAppend) == 0); + + /* Validate offset and size. */ + R_UNLESS(offset <= static_cast<s64>(m_partition_entry->size), fs::ResultOutOfRange()); + R_UNLESS(static_cast<s64>(offset + size) <= static_cast<s64>(m_partition_entry->size), fs::ResultInvalidSize()); + + /* Write to the base storage. */ + R_RETURN(m_parent->m_base_storage->Write(m_parent->m_meta_data_size + m_partition_entry->offset + offset, buffer, size)); + } + + virtual Result DoSetSize(s64 size) override final { + R_TRY(this->DrySetSize(size, m_mode)); + R_RETURN(fs::ResultUnsupportedWriteForPartitionFile()); + } + + virtual Result DoOperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override final { + /* Validate preconditions for operation. */ + s64 operate_offset; + s64 operate_size; + switch (op_id) { + case fs::OperationId::Invalidate: + R_UNLESS((m_mode & fs::OpenMode_Read) != 0, fs::ResultReadNotPermitted()); + R_UNLESS((m_mode & fs::OpenMode_Write) == 0, fs::ResultUnsupportedOperateRangeForPartitionFile()); + + /* Set offset/size. */ + operate_offset = 0; + operate_size = std::numeric_limits<s64>::max(); + break; + case fs::OperationId::QueryRange: + /* Validate offset and size. */ + R_UNLESS(offset >= 0, fs::ResultOutOfRange()); + R_UNLESS(offset <= static_cast<s64>(m_partition_entry->size), fs::ResultOutOfRange()); + R_UNLESS(static_cast<s64>(offset + size) <= static_cast<s64>(m_partition_entry->size), fs::ResultInvalidSize()); + R_UNLESS(static_cast<s64>(offset + size) >= offset, fs::ResultInvalidSize()); + + /* Set offset/size. */ + operate_offset = m_parent->m_meta_data_size + m_partition_entry->offset + offset; + operate_size = size; + break; + default: + R_THROW(fs::ResultUnsupportedOperateRangeForPartitionFile()); + } + + R_RETURN(m_parent->m_base_storage->OperateRange(dst, dst_size, op_id, operate_offset, operate_size, src, src_size)); + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override { + /* TODO: How should this be handled? */ + return sf::cmif::InvalidDomainObjectId; + } + }; + + template<> + Result PartitionFileSystemCore<PartitionFileSystemMeta>::PartitionFile::DoRead(size_t *out, s64 offset, void *dst, size_t dst_size, const fs::ReadOption &option) { + /* Perform a dry read. */ + size_t read_size = 0; + R_TRY(this->DryRead(std::addressof(read_size), offset, dst_size, option, m_mode)); + + /* Read from the base storage. */ + R_TRY(m_parent->m_base_storage->Read(m_parent->m_meta_data_size + m_partition_entry->offset + offset, dst, read_size)); + + /* Set output size. */ + *out = read_size; + R_SUCCEED(); + } + + template<> + Result PartitionFileSystemCore<Sha256PartitionFileSystemMeta>::PartitionFile::DoRead(size_t *out, s64 offset, void *dst, size_t dst_size, const fs::ReadOption &option) { + /* Perform a dry read. */ + size_t read_size = 0; + R_TRY(this->DryRead(std::addressof(read_size), offset, dst_size, option, m_mode)); + + const s64 entry_start = m_parent->m_meta_data_size + m_partition_entry->offset; + const s64 read_end = static_cast<s64>(offset + read_size); + const s64 hash_start = static_cast<s64>(m_partition_entry->hash_target_offset); + const s64 hash_end = hash_start + m_partition_entry->hash_target_size; + + if (read_end <= hash_start || hash_end <= offset) { + /* We aren't reading hashed data, so we can just read from the base storage. */ + R_TRY(m_parent->m_base_storage->Read(entry_start + offset, dst, read_size)); + } else { + /* Only hash target offset == 0 is supported. */ + R_UNLESS(hash_start == 0, fs::ResultInvalidSha256PartitionHashTarget()); + + /* Ensure that the hash region is valid. */ + R_UNLESS(m_partition_entry->hash_target_offset + m_partition_entry->hash_target_size <= m_partition_entry->size, fs::ResultInvalidSha256PartitionHashTarget()); + + /* Validate our read offset. */ + const s64 read_offset = entry_start + offset; + R_UNLESS(read_offset >= offset, fs::ResultOutOfRange()); + + /* Prepare a buffer for our calculated hash. */ + char hash[crypto::Sha256Generator::HashSize]; + crypto::Sha256Generator generator; + + /* Ensure we can perform our read. */ + const bool hash_in_read = offset <= hash_start && hash_end <= read_end; + const bool read_in_hash = hash_start <= offset && read_end <= hash_end; + R_UNLESS(hash_in_read || read_in_hash, fs::ResultInvalidSha256PartitionHashTarget()); + + /* Initialize the generator. */ + generator.Initialize(); + + if (hash_in_read) { + /* Easy case: hash region is contained within the bounds. */ + R_TRY(m_parent->m_base_storage->Read(entry_start + offset, dst, read_size)); + generator.Update(static_cast<u8 *>(dst) + hash_start - offset, m_partition_entry->hash_target_size); + } else /* if (read_in_hash) */ { + /* We're reading a portion of what's hashed. */ + s64 remaining_hash_size = m_partition_entry->hash_target_size; + s64 hash_offset = entry_start + hash_start; + s64 remaining_size = read_size; + s64 copy_offset = 0; + while (remaining_hash_size > 0) { + /* Read some portion of data into the buffer. */ + constexpr size_t HashBufferSize = 0x200; + char hash_buffer[HashBufferSize]; + size_t cur_size = static_cast<size_t>(std::min(static_cast<s64>(HashBufferSize), remaining_hash_size)); + R_TRY(m_parent->m_base_storage->Read(hash_offset, hash_buffer, cur_size)); + + /* Update the hash. */ + generator.Update(hash_buffer, cur_size); + + /* If we need to copy, do so. */ + if (read_offset <= (hash_offset + static_cast<s64>(cur_size)) && remaining_size > 0) { + const s64 hash_buffer_offset = std::max<s64>(read_offset - hash_offset, 0); + const size_t copy_size = static_cast<size_t>(std::min<s64>(cur_size - hash_buffer_offset, remaining_size)); + std::memcpy(static_cast<u8 *>(dst) + copy_offset, hash_buffer + hash_buffer_offset, copy_size); + remaining_size -= copy_size; + copy_offset += copy_size; + } + + /* Update offsets. */ + remaining_hash_size -= cur_size; + hash_offset += cur_size; + } + } + + /* Get the hash. */ + generator.GetHash(hash, sizeof(hash)); + + /* Validate the hash. */ + auto hash_guard = SCOPE_GUARD { std::memset(dst, 0, read_size); }; + R_UNLESS(crypto::IsSameBytes(m_partition_entry->hash, hash, sizeof(hash)), fs::ResultSha256PartitionHashVerificationFailed()); + + /* We successfully completed our read. */ + hash_guard.Cancel(); + } + + /* Set output size. */ + *out = read_size; + R_SUCCEED(); + } + + template <typename MetaType> + class PartitionFileSystemCore<MetaType>::PartitionDirectory : public fs::fsa::IDirectory, public fs::impl::Newable { + private: + u32 m_cur_index; + const PartitionFileSystemCore<MetaType> *m_parent; + const fs::OpenDirectoryMode m_mode; + public: + PartitionDirectory(PartitionFileSystemCore<MetaType> *parent, fs::OpenDirectoryMode mode) : m_cur_index(0), m_parent(parent), m_mode(mode) { /* ... */ } + public: + virtual Result DoRead(s64 *out_count, fs::DirectoryEntry *out_entries, s64 max_entries) override final { + /* There are no subdirectories. */ + if ((m_mode & fs::OpenDirectoryMode_File) == 0) { + *out_count = 0; + R_SUCCEED(); + } + + /* Calculate number of entries. */ + const s64 entry_count = std::min(max_entries, static_cast<s64>(m_parent->m_meta_data->GetEntryCount() - m_cur_index)); + + /* Populate output directory entries. */ + for (auto i = 0; i < entry_count; i++, m_cur_index++) { + fs::DirectoryEntry &dir_entry = out_entries[i]; + + /* Setup the output directory entry. */ + dir_entry.type = fs::DirectoryEntryType_File; + dir_entry.file_size = m_parent->m_meta_data->GetEntry(m_cur_index)->size; + std::strncpy(dir_entry.name, m_parent->m_meta_data->GetEntryName(m_cur_index), sizeof(dir_entry.name) - 1); + dir_entry.name[sizeof(dir_entry.name) - 1] = fs::StringTraits::NullTerminator; + } + + *out_count = entry_count; + R_SUCCEED(); + } + + virtual Result DoGetEntryCount(s64 *out) override final { + /* Output the parent meta data entry count for files, otherwise 0. */ + if (m_mode & fs::OpenDirectoryMode_File) { + *out = m_parent->m_meta_data->GetEntryCount(); + } else { + *out = 0; + } + + R_SUCCEED(); + } + + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override { + /* TODO: How should this be handled? */ + return sf::cmif::InvalidDomainObjectId; + } + }; + + template <typename MetaType> + PartitionFileSystemCore<MetaType>::PartitionFileSystemCore() : m_initialized(false) { + /* ... */ + } + + template <typename MetaType> + PartitionFileSystemCore<MetaType>::~PartitionFileSystemCore() { + /* ... */ + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::Initialize(fs::IStorage *base_storage, MemoryResource *allocator) { + /* Validate preconditions. */ + R_UNLESS(!m_initialized, fs::ResultPreconditionViolation()); + + /* Allocate meta data. */ + m_unique_meta_data = std::make_unique<MetaType>(); + R_UNLESS(m_unique_meta_data != nullptr, fs::ResultAllocationMemoryFailedInPartitionFileSystemA()); + + /* Initialize meta data. */ + R_TRY(m_unique_meta_data->Initialize(base_storage, allocator)); + + /* Initialize members. */ + m_meta_data = m_unique_meta_data.get(); + m_base_storage = base_storage; + m_meta_data_size = m_meta_data->GetMetaDataSize(); + m_initialized = true; + R_SUCCEED(); + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::Initialize(std::unique_ptr<MetaType> &&meta_data, std::shared_ptr<fs::IStorage> base_storage) { + m_unique_meta_data = std::move(meta_data); + R_RETURN(this->Initialize(m_unique_meta_data.get(), base_storage)); + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::Initialize(MetaType *meta_data, std::shared_ptr<fs::IStorage> base_storage) { + /* Validate preconditions. */ + R_UNLESS(!m_initialized, fs::ResultPreconditionViolation()); + + /* Initialize members. */ + m_shared_storage = std::move(base_storage); + m_base_storage = m_shared_storage.get(); + m_meta_data = meta_data; + m_meta_data_size = m_meta_data->GetMetaDataSize(); + m_initialized = true; + R_SUCCEED(); + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::Initialize(fs::IStorage *base_storage) { + R_RETURN(this->Initialize(base_storage, std::addressof(g_partition_filesystem_default_allocator))); + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::Initialize(std::shared_ptr<fs::IStorage> base_storage) { + m_shared_storage = std::move(base_storage); + R_RETURN(this->Initialize(m_shared_storage.get())); + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::Initialize(std::shared_ptr<fs::IStorage> base_storage, MemoryResource *allocator) { + m_shared_storage = std::move(base_storage); + R_RETURN(this->Initialize(m_shared_storage.get(), allocator)); + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::GetFileBaseOffset(s64 *out_offset, const char *path) { + /* Validate preconditions. */ + R_UNLESS(m_initialized, fs::ResultPreconditionViolation()); + + /* Obtain and validate the entry index. */ + const s32 entry_index = m_meta_data->GetEntryIndex(path + 1); + R_UNLESS(entry_index >= 0, fs::ResultPathNotFound()); + + /* Output offset. */ + *out_offset = m_meta_data_size + m_meta_data->GetEntry(entry_index)->offset; + R_SUCCEED(); + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::DoGetEntryType(fs::DirectoryEntryType *out, const fs::Path &path) { + /* Validate preconditions. */ + R_UNLESS(m_initialized, fs::ResultPreconditionViolation()); + + const char * const p = path.GetString(); + R_UNLESS(p[0] == RootPath[0], fs::ResultInvalidPathFormat()); + + /* Check if the path is for a directory. */ + if (util::Strncmp(p, RootPath, sizeof(RootPath)) == 0) { + *out = fs::DirectoryEntryType_Directory; + R_SUCCEED(); + } + + /* Ensure that path is for a file. */ + R_UNLESS(m_meta_data->GetEntryIndex(p + 1) >= 0, fs::ResultPathNotFound()); + + *out = fs::DirectoryEntryType_File; + R_SUCCEED(); + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::DoOpenFile(std::unique_ptr<fs::fsa::IFile> *out_file, const fs::Path &path, fs::OpenMode mode) { + /* Validate preconditions. */ + R_UNLESS(m_initialized, fs::ResultPreconditionViolation()); + + /* Obtain and validate the entry index. */ + const s32 entry_index = m_meta_data->GetEntryIndex(path.GetString() + 1); + R_UNLESS(entry_index >= 0, fs::ResultPathNotFound()); + + /* Create and output the file directory. */ + std::unique_ptr file = std::make_unique<PartitionFile>(this, m_meta_data->GetEntry(entry_index), mode); + R_UNLESS(file != nullptr, fs::ResultAllocationMemoryFailedInPartitionFileSystemB()); + *out_file = std::move(file); + R_SUCCEED(); + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::DoOpenDirectory(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const fs::Path &path, fs::OpenDirectoryMode mode) { + /* Validate preconditions. */ + R_UNLESS(m_initialized, fs::ResultPreconditionViolation()); + R_UNLESS(path == RootPath, fs::ResultPathNotFound()); + + /* Create and output the partition directory. */ + std::unique_ptr directory = std::make_unique<PartitionDirectory>(this, mode); + R_UNLESS(directory != nullptr, fs::ResultAllocationMemoryFailedInPartitionFileSystemC()); + *out_dir = std::move(directory); + R_SUCCEED(); + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::DoCommit() { + R_SUCCEED(); + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::DoCleanDirectoryRecursively(const fs::Path &path) { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForPartitionFileSystem()); + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::DoCreateDirectory(const fs::Path &path) { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForPartitionFileSystem()); + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::DoCreateFile(const fs::Path &path, s64 size, int option) { + AMS_UNUSED(path, size, option); + R_THROW(fs::ResultUnsupportedWriteForPartitionFileSystem()); + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::DoDeleteDirectory(const fs::Path &path) { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForPartitionFileSystem()); + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::DoDeleteDirectoryRecursively(const fs::Path &path) { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForPartitionFileSystem()); + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::DoDeleteFile(const fs::Path &path) { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForPartitionFileSystem()); + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) { + AMS_UNUSED(old_path, new_path); + R_THROW(fs::ResultUnsupportedWriteForPartitionFileSystem()); + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) { + AMS_UNUSED(old_path, new_path); + R_THROW(fs::ResultUnsupportedWriteForPartitionFileSystem()); + } + + template <typename MetaType> + Result PartitionFileSystemCore<MetaType>::DoCommitProvisionally(s64 counter) { + AMS_UNUSED(counter); + R_THROW(fs::ResultUnsupportedCommitProvisionallyForPartitionFileSystem()); + } + + template class PartitionFileSystemCore<PartitionFileSystemMeta>; + template class PartitionFileSystemCore<Sha256PartitionFileSystemMeta>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system_meta.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system_meta.cpp new file mode 100644 index 00000000..6ff68df8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_partition_file_system_meta.cpp @@ -0,0 +1,219 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + template <typename Format> + struct PartitionFileSystemMetaCore<Format>::PartitionFileSystemHeader { + char signature[sizeof(Format::VersionSignature)]; + s32 entry_count; + u32 name_table_size; + u32 reserved; + }; + static_assert(util::is_pod<PartitionFileSystemMeta::PartitionFileSystemHeader>::value); + static_assert(sizeof(PartitionFileSystemMeta::PartitionFileSystemHeader) == 0x10); + + template <typename Format> + PartitionFileSystemMetaCore<Format>::~PartitionFileSystemMetaCore() { + this->DeallocateBuffer(); + } + + template <typename Format> + Result PartitionFileSystemMetaCore<Format>::Initialize(fs::IStorage *storage, MemoryResource *allocator) { + /* Validate preconditions. */ + AMS_ASSERT(allocator != nullptr); + + /* Determine the meta data size. */ + R_TRY(this->QueryMetaDataSize(std::addressof(m_meta_data_size), storage)); + + /* Deallocate any old meta buffer and allocate a new one. */ + this->DeallocateBuffer(); + m_allocator = allocator; + m_buffer = static_cast<char *>(m_allocator->Allocate(m_meta_data_size)); + R_UNLESS(m_buffer != nullptr, fs::ResultAllocationMemoryFailedInPartitionFileSystemMetaA()); + + /* Perform regular initialization. */ + R_RETURN(this->Initialize(storage, m_buffer, m_meta_data_size)); + } + + template <typename Format> + Result PartitionFileSystemMetaCore<Format>::Initialize(fs::IStorage *storage, void *meta, size_t meta_size) { + /* Validate size for header. */ + R_UNLESS(meta_size >= sizeof(PartitionFileSystemHeader), fs::ResultInvalidSize()); + + /* Read the header. */ + R_TRY(storage->Read(0, meta, sizeof(PartitionFileSystemHeader))); + + /* Set and validate the header. */ + m_header = reinterpret_cast<PartitionFileSystemHeader *>(meta); + R_UNLESS(crypto::IsSameBytes(m_header->signature, Format::VersionSignature, sizeof(Format::VersionSignature)), typename Format::ResultSignatureVerificationFailed()); + + /* Setup entries and name table. */ + const size_t entries_size = m_header->entry_count * sizeof(typename Format::PartitionEntry); + m_entries = reinterpret_cast<PartitionEntry *>(static_cast<u8 *>(meta) + sizeof(PartitionFileSystemHeader)); + m_name_table = static_cast<char *>(meta) + sizeof(PartitionFileSystemHeader) + entries_size; + + /* Validate size for header + entries + name table. */ + R_UNLESS(meta_size >= sizeof(PartitionFileSystemHeader) + entries_size + m_header->name_table_size, fs::ResultInvalidSize()); + + /* Read entries and name table. */ + R_TRY(storage->Read(sizeof(PartitionFileSystemHeader), m_entries, entries_size + m_header->name_table_size)); + + /* Mark as initialized. */ + m_initialized = true; + R_SUCCEED(); + } + + template <typename Format> + void PartitionFileSystemMetaCore<Format>::DeallocateBuffer() { + if (m_buffer != nullptr) { + AMS_ABORT_UNLESS(m_allocator != nullptr); + m_allocator->Deallocate(m_buffer, m_meta_data_size); + m_buffer = nullptr; + } + } + + template <typename Format> + const typename Format::PartitionEntry *PartitionFileSystemMetaCore<Format>::GetEntry(s32 index) const { + if (m_initialized && 0 <= index && index < static_cast<s32>(m_header->entry_count)) { + return std::addressof(m_entries[index]); + } + return nullptr; + } + + template <typename Format> + s32 PartitionFileSystemMetaCore<Format>::GetEntryCount() const { + if (m_initialized) { + return m_header->entry_count; + } + return 0; + } + + template <typename Format> + s32 PartitionFileSystemMetaCore<Format>::GetEntryIndex(const char *name) const { + /* Fail if not initialized. */ + if (!m_initialized) { + return 0; + } + + for (s32 i = 0; i < static_cast<s32>(m_header->entry_count); i++) { + const auto &entry = m_entries[i]; + + /* Name offset is invalid. */ + if (entry.name_offset >= m_header->name_table_size) { + return 0; + } + + /* Compare to input name. */ + const s32 max_name_len = m_header->name_table_size - entry.name_offset; + if (std::strncmp(std::addressof(m_name_table[entry.name_offset]), name, max_name_len) == 0) { + return i; + } + } + + /* Not found. */ + return -1; + } + + template <typename Format> + const char *PartitionFileSystemMetaCore<Format>::GetEntryName(s32 index) const { + if (m_initialized && index < static_cast<s32>(m_header->entry_count)) { + return std::addressof(m_name_table[this->GetEntry(index)->name_offset]); + } + return nullptr; + } + + template <typename Format> + size_t PartitionFileSystemMetaCore<Format>::GetHeaderSize() const { + return sizeof(PartitionFileSystemHeader); + } + + template <typename Format> + size_t PartitionFileSystemMetaCore<Format>::GetMetaDataSize() const { + return m_meta_data_size; + } + + template <typename Format> + Result PartitionFileSystemMetaCore<Format>::QueryMetaDataSize(size_t *out_size, fs::IStorage *storage) { + /* Read and validate the header. */ + PartitionFileSystemHeader header; + R_TRY(storage->Read(0, std::addressof(header), sizeof(PartitionFileSystemHeader))); + R_UNLESS(crypto::IsSameBytes(std::addressof(header), Format::VersionSignature, sizeof(Format::VersionSignature)), typename Format::ResultSignatureVerificationFailed()); + + /* Output size. */ + *out_size = sizeof(PartitionFileSystemHeader) + header.entry_count * sizeof(typename Format::PartitionEntry) + header.name_table_size; + R_SUCCEED(); + } + + template class PartitionFileSystemMetaCore<impl::PartitionFileSystemFormat>; + template class PartitionFileSystemMetaCore<impl::Sha256PartitionFileSystemFormat>; + + Result Sha256PartitionFileSystemMeta::Initialize(fs::IStorage *base_storage, MemoryResource *allocator, const void *hash, size_t hash_size, util::optional<u8> suffix) { + /* Ensure preconditions. */ + R_UNLESS(hash_size == crypto::Sha256Generator::HashSize, fs::ResultPreconditionViolation()); + + /* Get metadata size. */ + R_TRY(QueryMetaDataSize(std::addressof(m_meta_data_size), base_storage)); + + /* Ensure we have no buffer. */ + this->DeallocateBuffer(); + + /* Set allocator and allocate buffer. */ + m_allocator = allocator; + m_buffer = static_cast<char *>(m_allocator->Allocate(m_meta_data_size)); + R_UNLESS(m_buffer != nullptr, fs::ResultAllocationMemoryFailedInPartitionFileSystemMetaB()); + + /* Read metadata. */ + R_TRY(base_storage->Read(0, m_buffer, m_meta_data_size)); + + /* Calculate hash. */ + char calc_hash[crypto::Sha256Generator::HashSize]; + { + crypto::Sha256Generator generator; + generator.Initialize(); + generator.Update(m_buffer, m_meta_data_size); + if (suffix) { + u8 suffix_val = *suffix; + generator.Update(std::addressof(suffix_val), 1); + } + generator.GetHash(calc_hash, sizeof(calc_hash)); + } + + /* Ensure hash is valid. */ + R_UNLESS(crypto::IsSameBytes(hash, calc_hash, sizeof(calc_hash)), fs::ResultSha256PartitionHashVerificationFailed()); + + /* Give access to Format */ + using Format = impl::Sha256PartitionFileSystemFormat; + + /* Set header. */ + m_header = reinterpret_cast<PartitionFileSystemHeader *>(m_buffer); + R_UNLESS(crypto::IsSameBytes(m_header->signature, Format::VersionSignature, sizeof(Format::VersionSignature)), typename Format::ResultSignatureVerificationFailed()); + + /* Validate size for entries and name table. */ + const size_t entries_size = m_header->entry_count * sizeof(typename Format::PartitionEntry); + R_UNLESS(m_meta_data_size >= sizeof(PartitionFileSystemHeader) + entries_size + m_header->name_table_size, fs::ResultInvalidSha256PartitionMetaDataSize()); + + /* Set entries and name table. */ + m_entries = reinterpret_cast<PartitionEntry *>(m_buffer + sizeof(PartitionFileSystemHeader)); + m_name_table = m_buffer + sizeof(PartitionFileSystemHeader) + entries_size; + + /* We initialized. */ + m_initialized = true; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_pooled_buffer.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_pooled_buffer.cpp new file mode 100644 index 00000000..b260bac9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_pooled_buffer.cpp @@ -0,0 +1,269 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + namespace { + + class AdditionalDeviceAddressEntry { + private: + os::SdkMutex m_mutex; + bool m_is_registered; + uintptr_t m_address; + size_t m_size; + public: + constexpr AdditionalDeviceAddressEntry() : m_mutex(), m_is_registered(), m_address(), m_size() { /* ... */ } + + void Register(uintptr_t addr, size_t sz) { + std::scoped_lock lk(m_mutex); + + AMS_ASSERT(!m_is_registered); + if (!m_is_registered) { + m_is_registered = true; + m_address = addr; + m_size = sz; + } + } + + void Unregister(uintptr_t addr) { + std::scoped_lock lk(m_mutex); + + if (m_is_registered && m_address == addr) { + m_is_registered = false; + m_address = 0; + m_size = 0; + } + } + + bool Includes(const void *ptr) { + std::scoped_lock lk(m_mutex); + + if (m_is_registered) { + const uintptr_t addr = reinterpret_cast<uintptr_t>(ptr); + return m_address <= addr && addr < m_address + m_size; + } else { + return false; + } + } + }; + + constexpr auto RetryWait = TimeSpan::FromMilliSeconds(10); + + constexpr size_t HeapBlockSize = BufferPoolAlignment; + static_assert(HeapBlockSize == 4_KB); + + /* A heap block is 4KB. An order is a power of two. */ + /* This gives blocks of the order 32KB, 512KB, 4MB. */ + constexpr s32 HeapOrderTrim = 3; + constexpr s32 HeapOrderMax = 7; + constexpr s32 HeapOrderMaxForLarge = HeapOrderMax + 3; + + constexpr size_t HeapAllocatableSizeTrim = HeapBlockSize * (static_cast<size_t>(1) << HeapOrderTrim); + constexpr size_t HeapAllocatableSizeMax = HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMax); + constexpr size_t HeapAllocatableSizeMaxForLarge = HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMaxForLarge); + + constinit os::SdkMutex g_heap_mutex; + constinit FileSystemBuddyHeap g_heap; + + constinit std::atomic<size_t> g_retry_count; + constinit std::atomic<size_t> g_reduce_allocation_count; + + constinit void *g_heap_buffer; + constinit size_t g_heap_size; + constinit size_t g_heap_free_size_peak; + + constinit AdditionalDeviceAddressEntry g_additional_device_address_entry; + + } + + size_t PooledBuffer::GetAllocatableSizeMaxCore(bool large) { + return large ? HeapAllocatableSizeMaxForLarge : HeapAllocatableSizeMax; + } + + void PooledBuffer::AllocateCore(size_t ideal_size, size_t required_size, bool large) { + /* Ensure preconditions. */ + AMS_ASSERT(g_heap_buffer != nullptr); + AMS_ASSERT(m_buffer == nullptr); + AMS_ASSERT(g_heap.GetBlockSize() == HeapBlockSize); + + /* Check that we can allocate this size. */ + AMS_ASSERT(required_size <= GetAllocatableSizeMaxCore(large)); + + const size_t target_size = std::min(std::max(ideal_size, required_size), GetAllocatableSizeMaxCore(large)); + + /* Loop until we allocate. */ + while (true) { + /* Lock the heap and try to allocate. */ + { + std::scoped_lock lk(g_heap_mutex); + + /* Determine how much we can allocate, and don't allocate more than half the heap. */ + size_t allocatable_size = g_heap.GetAllocatableSizeMax(); + if (allocatable_size > HeapBlockSize) { + allocatable_size >>= 1; + } + + /* Check if this allocation is acceptable. */ + if (allocatable_size >= required_size) { + /* Get the order. */ + const auto order = g_heap.GetOrderFromBytes(std::min(target_size, allocatable_size)); + + /* Allocate and get the size. */ + m_buffer = reinterpret_cast<char *>(g_heap.AllocateByOrder(order)); + m_size = g_heap.GetBytesFromOrder(order); + } + } + + /* Check if we allocated. */ + if (m_buffer != nullptr) { + /* If we need to trim the end, do so. */ + if (this->GetSize() >= target_size + HeapAllocatableSizeTrim) { + this->Shrink(util::AlignUp(target_size, HeapAllocatableSizeTrim)); + } + AMS_ASSERT(this->GetSize() >= required_size); + + /* If we reduced, note so. */ + if (this->GetSize() < std::min(target_size, HeapAllocatableSizeMax)) { + g_reduce_allocation_count++; + } + break; + } else { + /* Sleep. */ + os::SleepThread(RetryWait); + g_retry_count++; + } + } + + /* Update metrics. */ + { + std::scoped_lock lk(g_heap_mutex); + + const size_t free_size = g_heap.GetTotalFreeSize(); + if (free_size < g_heap_free_size_peak) { + g_heap_free_size_peak = free_size; + } + } + } + + void PooledBuffer::Shrink(size_t ideal_size) { + AMS_ASSERT(ideal_size <= GetAllocatableSizeMaxCore(true)); + + /* Check if we actually need to shrink. */ + if (m_size > ideal_size) { + /* If we do, we need to have a buffer allocated from the heap. */ + AMS_ASSERT(m_buffer != nullptr); + AMS_ASSERT(g_heap.GetBlockSize() == HeapBlockSize); + + const size_t new_size = util::AlignUp(ideal_size, HeapBlockSize); + + /* Repeatedly free the tail of our buffer until we're done. */ + { + std::scoped_lock lk(g_heap_mutex); + + while (new_size < m_size) { + /* Determine the size and order to free. */ + const size_t tail_align = util::LeastSignificantOneBit(m_size); + const size_t free_size = std::min(util::FloorPowerOfTwo(m_size - new_size), tail_align); + const s32 free_order = g_heap.GetOrderFromBytes(free_size); + + /* Ensure we determined size correctly. */ + AMS_ASSERT(util::IsAligned(free_size, HeapBlockSize)); + AMS_ASSERT(free_size == g_heap.GetBytesFromOrder(free_order)); + + /* Actually free the memory. */ + g_heap.Free(m_buffer + m_size - free_size, free_order); + m_size -= free_size; + } + } + + /* Shrinking to zero means that we have no buffer. */ + if (m_size == 0) { + m_buffer = nullptr; + } + } + } + + Result InitializeBufferPool(char *buffer, size_t size) { + AMS_ASSERT(g_heap_buffer == nullptr); + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(buffer), BufferPoolAlignment)); + + /* Initialize the heap. */ + R_TRY(g_heap.Initialize(reinterpret_cast<uintptr_t>(buffer), size, HeapBlockSize, HeapOrderMaxForLarge + 1)); + + /* Initialize metrics. */ + g_heap_buffer = buffer; + g_heap_size = size; + g_heap_free_size_peak = size; + + R_SUCCEED(); + } + + Result InitializeBufferPool(char *buffer, size_t size, char *work, size_t work_size) { + AMS_ASSERT(g_heap_buffer == nullptr); + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(buffer), BufferPoolAlignment)); + AMS_ASSERT(work_size >= BufferPoolWorkSize); + + /* Initialize the heap. */ + R_TRY(g_heap.Initialize(reinterpret_cast<uintptr_t>(buffer), size, HeapBlockSize, HeapOrderMaxForLarge + 1, work, work_size)); + + /* Initialize metrics. */ + g_heap_buffer = buffer; + g_heap_size = size; + g_heap_free_size_peak = size; + + R_SUCCEED(); + } + + bool IsPooledBuffer(const void *buffer) { + AMS_ASSERT(buffer != nullptr); + return g_heap_buffer <= buffer && buffer < reinterpret_cast<char *>(g_heap_buffer) + g_heap_size; + } + + size_t GetPooledBufferRetriedCount() { + return g_retry_count; + } + + size_t GetPooledBufferReduceAllocationCount() { + return g_reduce_allocation_count; + } + + size_t GetPooledBufferFreeSizePeak() { + return g_heap_free_size_peak; + } + + void ClearPooledBufferPeak() { + std::scoped_lock lk(g_heap_mutex); + g_heap_free_size_peak = g_heap.GetTotalFreeSize(); + g_retry_count = 0; + g_reduce_allocation_count = 0; + } + + void RegisterAdditionalDeviceAddress(uintptr_t address, size_t size) { + g_additional_device_address_entry.Register(address, size); + } + + void UnregisterAdditionalDeviceAddress(uintptr_t address) { + g_additional_device_address_entry.Unregister(address); + } + + bool IsAdditionalDeviceAddress(const void *ptr) { + return g_additional_device_address_entry.Includes(ptr); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_read_only_block_cache_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_read_only_block_cache_storage.hpp new file mode 100644 index 00000000..a04de819 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_read_only_block_cache_storage.hpp @@ -0,0 +1,131 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "fssystem_lru_list_cache.hpp" + +namespace ams::fssystem { + + class ReadOnlyBlockCacheStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(ReadOnlyBlockCacheStorage); + NON_MOVEABLE(ReadOnlyBlockCacheStorage); + private: + using BlockCache = LruListCache<s64, char *>; + private: + os::SdkMutex m_mutex; + BlockCache m_block_cache; + std::shared_ptr<fs::IStorage> m_base_storage; + s32 m_block_size; + public: + ReadOnlyBlockCacheStorage(std::shared_ptr<fs::IStorage> bs, s32 bsz, char *buf, size_t buf_size, s32 cache_block_count) : m_mutex(), m_block_cache(), m_base_storage(std::move(bs)), m_block_size(bsz) { + /* Validate preconditions. */ + AMS_ASSERT(buf_size >= static_cast<size_t>(m_block_size)); + AMS_ASSERT(util::IsPowerOfTwo(m_block_size)); + AMS_ASSERT(cache_block_count > 0); + AMS_ASSERT(buf_size >= static_cast<size_t>(m_block_size * cache_block_count)); + AMS_UNUSED(buf_size); + + /* Create a node for each cache block. */ + for (auto i = 0; i < cache_block_count; i++) { + std::unique_ptr node = std::make_unique<BlockCache::Node>(buf + m_block_size * i); + AMS_ASSERT(node != nullptr); + + if (node != nullptr) { + m_block_cache.PushMruNode(std::move(node), -1); + } + } + } + + ~ReadOnlyBlockCacheStorage() { + m_block_cache.DeleteAllNodes(); + } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + /* Validate preconditions. */ + AMS_ASSERT(util::IsAligned(offset, m_block_size)); + AMS_ASSERT(util::IsAligned(size, m_block_size)); + + if (size == static_cast<size_t>(m_block_size)) { + char *cached_buffer = nullptr; + + /* Try to find a cached copy of the data. */ + { + std::scoped_lock lk(m_mutex); + bool found = m_block_cache.FindValueAndUpdateMru(std::addressof(cached_buffer), offset / m_block_size); + if (found) { + std::memcpy(buffer, cached_buffer, size); + R_SUCCEED(); + } + } + + /* We failed to get a cache hit, so read in the data. */ + R_TRY(m_base_storage->Read(offset, buffer, size)); + + /* Add the block to the cache. */ + { + std::scoped_lock lk(m_mutex); + auto lru = m_block_cache.PopLruNode(); + std::memcpy(lru->m_value, buffer, m_block_size); + m_block_cache.PushMruNode(std::move(lru), offset / m_block_size); + } + + R_SUCCEED(); + } else { + R_RETURN(m_base_storage->Read(offset, buffer, size)); + } + } + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + /* If invalidating cache, invalidate our blocks. */ + if (op_id == fs::OperationId::Invalidate) { + std::scoped_lock lk(m_mutex); + + const size_t cache_block_count = m_block_cache.GetSize(); + for (size_t i = 0; i < cache_block_count; ++i) { + auto lru = m_block_cache.PopLruNode(); + m_block_cache.PushMruNode(std::move(lru), -1); + } + + R_SUCCEED(); + } else { + /* Validate preconditions. */ + AMS_ASSERT(util::IsAligned(offset, m_block_size)); + AMS_ASSERT(util::IsAligned(size, m_block_size)); + } + + /* Operate on the base storage. */ + R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + } + + virtual Result GetSize(s64 *out) override { + R_RETURN(m_base_storage->GetSize(out)); + } + + virtual Result Flush() override { + R_SUCCEED(); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + AMS_UNUSED(offset, buffer, size); + R_THROW(fs::ResultUnsupportedWriteForReadOnlyBlockCacheStorage()); + } + + virtual Result SetSize(s64 size) override { + AMS_UNUSED(size); + R_THROW(fs::ResultUnsupportedSetSizeForReadOnlyBlockCacheStorage()); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_romfs_filesystem.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_romfs_filesystem.cpp new file mode 100644 index 00000000..50e7ba54 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_romfs_filesystem.cpp @@ -0,0 +1,418 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + namespace { + + constexpr size_t CalculateRequiredWorkingMemorySize(const fs::RomFileSystemInformation &header) { + return header.directory_bucket_size + header.directory_entry_size + header.file_bucket_size + header.file_entry_size; + } + + class RomFsFile : public ams::fs::fsa::IFile, public ams::fs::impl::Newable { + private: + RomFsFileSystem *m_parent; + s64 m_start; + s64 m_end; + private: + s64 GetSize() const { + return m_end - m_start; + } + public: + RomFsFile(RomFsFileSystem *p, s64 s, s64 e) : m_parent(p), m_start(s), m_end(e) { /* ... */ } + virtual ~RomFsFile() { /* ... */ } + public: + virtual Result DoRead(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override { + R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result { + size_t read_size = 0; + R_TRY(this->DryRead(std::addressof(read_size), offset, size, option, fs::OpenMode_Read)); + + R_TRY(m_parent->GetBaseStorage()->Read(offset + m_start, buffer, read_size)); + *out = read_size; + + R_SUCCEED(); + }, AMS_CURRENT_FUNCTION_NAME)); + + R_SUCCEED(); + } + + virtual Result DoGetSize(s64 *out) override { + *out = this->GetSize(); + R_SUCCEED(); + } + + virtual Result DoFlush() override { + R_SUCCEED(); + } + + virtual Result DoWrite(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override { + AMS_UNUSED(buffer); + + bool needs_append; + R_TRY(this->DryWrite(std::addressof(needs_append), offset, size, option, fs::OpenMode_Read)); + AMS_ASSERT(needs_append == false); + + R_THROW(fs::ResultUnsupportedWriteForRomFsFile()); + } + + virtual Result DoSetSize(s64 size) override { + R_TRY(this->DrySetSize(size, fs::OpenMode_Read)); + R_THROW(fs::ResultUnsupportedWriteForRomFsFile()); + } + + virtual Result DoOperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + switch (op_id) { + case fs::OperationId::Invalidate: + { + R_RETURN(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result { + R_RETURN(m_parent->GetBaseStorage()->OperateRange(fs::OperationId::Invalidate, 0, std::numeric_limits<s64>::max())); + }, AMS_CURRENT_FUNCTION_NAME)); + } + case fs::OperationId::QueryRange: + { + R_UNLESS(offset >= 0, fs::ResultInvalidOffset()); + R_UNLESS(this->GetSize() >= offset, fs::ResultOutOfRange()); + + auto operate_size = size; + if (offset + operate_size > this->GetSize() || offset + operate_size < offset) { + operate_size = this->GetSize() - offset; + } + + R_RETURN(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result { + R_RETURN(m_parent->GetBaseStorage()->OperateRange(dst, dst_size, op_id, m_start + offset, operate_size, src, src_size)); + }, AMS_CURRENT_FUNCTION_NAME)); + } + default: + R_THROW(fs::ResultUnsupportedOperateRangeForRomFsFile()); + } + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override { + AMS_ABORT(); + } + }; + + class RomFsDirectory : public ams::fs::fsa::IDirectory, public ams::fs::impl::Newable { + private: + using FindPosition = RomFsFileSystem::RomFileTable::FindPosition; + private: + RomFsFileSystem *m_parent; + FindPosition m_current_find; + FindPosition m_first_find; + fs::OpenDirectoryMode m_mode; + public: + RomFsDirectory(RomFsFileSystem *p, const FindPosition &f, fs::OpenDirectoryMode m) : m_parent(p), m_current_find(f), m_first_find(f), m_mode(m) { /* ... */ } + virtual ~RomFsDirectory() override { /* ... */ } + public: + virtual Result DoRead(s64 *out_count, fs::DirectoryEntry *out_entries, s64 max_entries) override { + R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result { + R_RETURN(this->ReadInternal(out_count, std::addressof(m_current_find), out_entries, max_entries)); + }, AMS_CURRENT_FUNCTION_NAME)); + R_SUCCEED(); + } + + virtual Result DoGetEntryCount(s64 *out) override { + FindPosition find = m_first_find; + + R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result { + R_TRY(this->ReadInternal(out, std::addressof(find), nullptr, 0)); + R_SUCCEED(); + }, AMS_CURRENT_FUNCTION_NAME)); + R_SUCCEED(); + } + private: + Result ReadInternal(s64 *out_count, FindPosition *find, fs::DirectoryEntry *out_entries, s64 max_entries) { + constexpr size_t NameBufferSize = fs::EntryNameLengthMax + 1; + fs::RomPathChar name[NameBufferSize]; + s32 i = 0; + + if (m_mode & fs::OpenDirectoryMode_Directory) { + while (i < max_entries || out_entries == nullptr) { + R_TRY_CATCH(m_parent->GetRomFileTable()->FindNextDirectory(name, find, NameBufferSize)) { + R_CATCH(fs::ResultDbmFindFinished) { break; } + } R_END_TRY_CATCH; + + if (out_entries) { + R_UNLESS(strnlen(name, NameBufferSize) < NameBufferSize, fs::ResultTooLongPath()); + strncpy(out_entries[i].name, name, fs::EntryNameLengthMax); + out_entries[i].name[fs::EntryNameLengthMax] = '\x00'; + out_entries[i].type = fs::DirectoryEntryType_Directory; + out_entries[i].file_size = 0; + } + + i++; + } + } + + if (m_mode & fs::OpenDirectoryMode_File) { + while (i < max_entries || out_entries == nullptr) { + auto file_pos = find->next_file; + + R_TRY_CATCH(m_parent->GetRomFileTable()->FindNextFile(name, find, NameBufferSize)) { + R_CATCH(fs::ResultDbmFindFinished) { break; } + } R_END_TRY_CATCH; + + if (out_entries) { + R_UNLESS(strnlen(name, NameBufferSize) < NameBufferSize, fs::ResultTooLongPath()); + strncpy(out_entries[i].name, name, fs::EntryNameLengthMax); + out_entries[i].name[fs::EntryNameLengthMax] = '\x00'; + out_entries[i].type = fs::DirectoryEntryType_File; + + RomFsFileSystem::RomFileTable::FileInfo file_info; + R_TRY(m_parent->GetRomFileTable()->OpenFile(std::addressof(file_info), m_parent->GetRomFileTable()->PositionToFileId(file_pos))); + out_entries[i].file_size = file_info.size.Get(); + } + + i++; + } + } + + *out_count = i; + R_SUCCEED(); + } + public: + virtual sf::cmif::DomainObjectId GetDomainObjectId() const override { + AMS_ABORT(); + } + }; + + } + + + RomFsFileSystem::RomFsFileSystem() : m_base_storage() { + /* ... */ + } + + RomFsFileSystem::~RomFsFileSystem() { + /* ... */ + } + + fs::IStorage *RomFsFileSystem::GetBaseStorage() { + return m_base_storage; + } + + RomFsFileSystem::RomFileTable *RomFsFileSystem::GetRomFileTable() { + return std::addressof(m_rom_file_table); + } + + Result RomFsFileSystem::GetRequiredWorkingMemorySize(size_t *out, fs::IStorage *storage) { + fs::RomFileSystemInformation header; + + R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result { + R_TRY(storage->Read(0, std::addressof(header), sizeof(header))); + R_SUCCEED(); + }, AMS_CURRENT_FUNCTION_NAME)); + + *out = CalculateRequiredWorkingMemorySize(header); + R_SUCCEED(); + } + + Result RomFsFileSystem::Initialize(fs::IStorage *base, void *work, size_t work_size, bool use_cache) { + AMS_ABORT_UNLESS(!use_cache || work != nullptr); + AMS_ABORT_UNLESS(base != nullptr); + + /* Register blocking context for the scope. */ + buffers::ScopedBufferManagerContextRegistration _sr; + buffers::EnableBlockingBufferManagerAllocation(); + + /* Read the header. */ + fs::RomFileSystemInformation header; + R_TRY(base->Read(0, std::addressof(header), sizeof(header))); + + /* Set up our storages. */ + if (use_cache) { + const size_t needed_size = CalculateRequiredWorkingMemorySize(header); + R_UNLESS(work_size >= needed_size, fs::ResultAllocationMemoryFailedInRomFsFileSystemA()); + + u8 *buf = static_cast<u8 *>(work); + auto dir_bucket_buf = buf; buf += header.directory_bucket_size; + auto dir_entry_buf = buf; buf += header.directory_entry_size; + auto file_bucket_buf = buf; buf += header.file_bucket_size; + auto file_entry_buf = buf; buf += header.file_entry_size; + + R_TRY(base->Read(header.directory_bucket_offset, dir_bucket_buf, static_cast<size_t>(header.directory_bucket_size))); + R_TRY(base->Read(header.directory_entry_offset, dir_entry_buf, static_cast<size_t>(header.directory_entry_size))); + R_TRY(base->Read(header.file_bucket_offset, file_bucket_buf, static_cast<size_t>(header.file_bucket_size))); + R_TRY(base->Read(header.file_entry_offset, file_entry_buf, static_cast<size_t>(header.file_entry_size))); + + m_dir_bucket_storage.reset(new fs::MemoryStorage(dir_bucket_buf, header.directory_bucket_size)); + m_dir_entry_storage.reset(new fs::MemoryStorage(dir_entry_buf, header.directory_entry_size)); + m_file_bucket_storage.reset(new fs::MemoryStorage(file_bucket_buf, header.file_bucket_size)); + m_file_entry_storage.reset(new fs::MemoryStorage(file_entry_buf, header.file_entry_size)); + } else { + m_dir_bucket_storage.reset(new fs::SubStorage(base, header.directory_bucket_offset, header.directory_bucket_size)); + m_dir_entry_storage.reset(new fs::SubStorage(base, header.directory_entry_offset, header.directory_entry_size)); + m_file_bucket_storage.reset(new fs::SubStorage(base, header.file_bucket_offset, header.file_bucket_size)); + m_file_entry_storage.reset(new fs::SubStorage(base, header.file_entry_offset, header.file_entry_size)); + } + + /* Ensure we allocated storages successfully. */ + R_UNLESS(m_dir_bucket_storage != nullptr, fs::ResultAllocationMemoryFailedInRomFsFileSystemB()); + R_UNLESS(m_dir_entry_storage != nullptr, fs::ResultAllocationMemoryFailedInRomFsFileSystemB()); + R_UNLESS(m_file_bucket_storage != nullptr, fs::ResultAllocationMemoryFailedInRomFsFileSystemB()); + R_UNLESS(m_file_entry_storage != nullptr, fs::ResultAllocationMemoryFailedInRomFsFileSystemB()); + + /* Initialize the rom table. */ + R_TRY(m_rom_file_table.Initialize(fs::SubStorage(m_dir_bucket_storage.get(), 0, static_cast<u32>(header.directory_bucket_size)), + fs::SubStorage(m_dir_entry_storage.get(), 0, static_cast<u32>(header.directory_entry_size)), + fs::SubStorage(m_file_bucket_storage.get(), 0, static_cast<u32>(header.file_bucket_size)), + fs::SubStorage(m_file_entry_storage.get(), 0, static_cast<u32>(header.file_entry_size)))); + + /* Set members. */ + m_entry_size = header.body_offset; + m_base_storage = base; + R_SUCCEED(); + } + + Result RomFsFileSystem::Initialize(std::shared_ptr<fs::IStorage> base, void *work, size_t work_size, bool use_cache) { + m_shared_storage = std::move(base); + R_RETURN(this->Initialize(m_shared_storage.get(), work, work_size, use_cache)); + } + + Result RomFsFileSystem::GetFileInfo(RomFileTable::FileInfo *out, const char *path) { + R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result { + R_TRY_CATCH(m_rom_file_table.OpenFile(out, path)) { + R_CONVERT(fs::ResultDbmNotFound, fs::ResultPathNotFound()); + } R_END_TRY_CATCH; + + R_SUCCEED(); + }, AMS_CURRENT_FUNCTION_NAME)); + + R_SUCCEED(); + } + + Result RomFsFileSystem::GetFileBaseOffset(s64 *out, const fs::Path &path) { + R_TRY(this->CheckPathFormat(path)); + + RomFileTable::FileInfo info; + R_TRY(this->GetFileInfo(std::addressof(info), path)); + *out = m_entry_size + info.offset.Get(); + R_SUCCEED(); + } + + Result RomFsFileSystem::DoCreateFile(const fs::Path &path, s64 size, int flags) { + AMS_UNUSED(path, size, flags); + R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem()); + } + + Result RomFsFileSystem::DoDeleteFile(const fs::Path &path) { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem()); + } + + Result RomFsFileSystem::DoCreateDirectory(const fs::Path &path) { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem()); + } + + Result RomFsFileSystem::DoDeleteDirectory(const fs::Path &path) { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem()); + } + + Result RomFsFileSystem::DoDeleteDirectoryRecursively(const fs::Path &path) { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem()); + } + + Result RomFsFileSystem::DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) { + AMS_UNUSED(old_path, new_path); + R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem()); + } + + Result RomFsFileSystem::DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) { + AMS_UNUSED(old_path, new_path); + R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem()); + } + + Result RomFsFileSystem::DoGetEntryType(fs::DirectoryEntryType *out, const fs::Path &path) { + R_TRY(this->CheckPathFormat(path)); + + R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result { + fs::HierarchicalRomFileTable::FindPosition find_pos; + + R_TRY_CATCH(m_rom_file_table.FindOpen(std::addressof(find_pos), path.GetString())) { + R_CONVERT(fs::ResultDbmNotFound, fs::ResultPathNotFound()) + R_CATCH(fs::ResultDbmInvalidOperation) { + *out = fs::DirectoryEntryType_File; + R_SUCCEED(); + } + } R_END_TRY_CATCH; + + *out = fs::DirectoryEntryType_Directory; + R_SUCCEED(); + }, AMS_CURRENT_FUNCTION_NAME)); + + R_SUCCEED(); + } + + Result RomFsFileSystem::DoOpenFile(std::unique_ptr<fs::fsa::IFile> *out_file, const fs::Path &path, fs::OpenMode mode) { + R_UNLESS(mode == fs::OpenMode_Read, fs::ResultInvalidOpenMode()); + + R_TRY(this->CheckPathFormat(path)); + + RomFileTable::FileInfo file_info; + R_TRY(this->GetFileInfo(std::addressof(file_info), path)); + + auto file = std::make_unique<RomFsFile>(this, m_entry_size + file_info.offset.Get(), m_entry_size + file_info.offset.Get() + file_info.size.Get()); + R_UNLESS(file != nullptr, fs::ResultAllocationMemoryFailedInRomFsFileSystemC()); + + *out_file = std::move(file); + R_SUCCEED(); + } + + Result RomFsFileSystem::DoOpenDirectory(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const fs::Path &path, fs::OpenDirectoryMode mode) { + R_TRY(this->CheckPathFormat(path)); + + RomFileTable::FindPosition find; + R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result { + R_TRY_CATCH(m_rom_file_table.FindOpen(std::addressof(find), path.GetString())) { + R_CONVERT(fs::ResultDbmNotFound, fs::ResultPathNotFound()) + } R_END_TRY_CATCH; + + R_SUCCEED(); + }, AMS_CURRENT_FUNCTION_NAME)); + + auto dir = std::make_unique<RomFsDirectory>(this, find, mode); + R_UNLESS(dir != nullptr, fs::ResultAllocationMemoryFailedInRomFsFileSystemD()); + + *out_dir = std::move(dir); + R_SUCCEED(); + } + + Result RomFsFileSystem::DoCommit() { + R_SUCCEED(); + } + + Result RomFsFileSystem::DoGetFreeSpaceSize(s64 *out, const fs::Path &path) { + AMS_UNUSED(path); + + *out = 0; + R_SUCCEED(); + } + + Result RomFsFileSystem::DoCleanDirectoryRecursively(const fs::Path &path) { + AMS_UNUSED(path); + R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem()); + } + + Result RomFsFileSystem::DoCommitProvisionally(s64 counter) { + AMS_UNUSED(counter); + R_THROW(fs::ResultUnsupportedCommitProvisionallyForRomFsFileSystem()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_service_context.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_service_context.cpp new file mode 100644 index 00000000..9255ee35 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_service_context.cpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + namespace { + + os::SdkThreadLocalStorage g_tls_service_context; + + } + + void RegisterServiceContext(ServiceContext *context) { + /* Check pre-conditions. */ + AMS_ASSERT(context != nullptr); + AMS_ASSERT(g_tls_service_context.GetValue() == 0); + + /* Register context. */ + g_tls_service_context.SetValue(reinterpret_cast<uintptr_t>(context)); + } + + void UnregisterServiceContext() { + /* Unregister context. */ + g_tls_service_context.SetValue(0); + } + + ServiceContext *GetServiceContext() { + /* Get context. */ + auto * const context = reinterpret_cast<ServiceContext *>(g_tls_service_context.GetValue()); + AMS_ASSERT(context != nullptr); + + return context; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_sparse_storage.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_sparse_storage.cpp new file mode 100644 index 00000000..3c23f081 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_sparse_storage.cpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + Result SparseStorage::Read(s64 offset, void *buffer, size_t size) { + /* Validate preconditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(this->IsInitialized()); + + /* Allow zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + if (this->GetEntryTable().IsEmpty()) { + BucketTree::Offsets table_offsets; + R_TRY(this->GetEntryTable().GetOffsets(std::addressof(table_offsets))); + + R_UNLESS(table_offsets.IsInclude(offset, size), fs::ResultOutOfRange()); + + std::memset(buffer, 0, size); + } else { + R_TRY((this->OperatePerEntry<false, true>(offset, size, [=](fs::IStorage *storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result { + R_TRY(storage->Read(data_offset, reinterpret_cast<u8 *>(buffer) + (cur_offset - offset), static_cast<size_t>(cur_size))); + R_SUCCEED(); + }))); + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_speed_emulation_configuration.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_speed_emulation_configuration.cpp new file mode 100644 index 00000000..65840c9c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_speed_emulation_configuration.cpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + namespace { + + std::atomic<::ams::fs::SpeedEmulationMode> g_speed_emulation_mode = ::ams::fs::SpeedEmulationMode::None; + + } + + void SpeedEmulationConfiguration::SetSpeedEmulationMode(::ams::fs::SpeedEmulationMode mode) { + g_speed_emulation_mode = mode; + } + + ::ams::fs::SpeedEmulationMode SpeedEmulationConfiguration::GetSpeedEmulationMode() { + return g_speed_emulation_mode; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_thread_priority_changer.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_thread_priority_changer.cpp new file mode 100644 index 00000000..6dc51556 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_thread_priority_changer.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + s32 ScopedThreadPriorityChangerByAccessPriority::GetThreadPriorityByAccessPriority(AccessMode mode) { + /* TODO: Actually implement this for real. */ + AMS_UNUSED(mode); + return os::GetThreadPriority(os::GetCurrentThread()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_utility.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_utility.cpp new file mode 100644 index 00000000..fc3ce1f0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/fssystem/fssystem_utility.cpp @@ -0,0 +1,192 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::fssystem { + + namespace { + + Result EnsureDirectoryImpl(fs::fsa::IFileSystem *fs, const fs::Path &path) { + /* Create work path. */ + fs::Path work_path; + R_TRY(work_path.Initialize(path)); + + /* Create a directory path parser. */ + fs::DirectoryPathParser parser; + R_TRY(parser.Initialize(std::addressof(work_path))); + + bool is_finished; + do { + /* Get the current path. */ + const fs::Path &cur_path = parser.GetCurrentPath(); + + /* Get the entry type for the current path. */ + fs::DirectoryEntryType type; + R_TRY_CATCH(fs->GetEntryType(std::addressof(type), cur_path)) { + R_CATCH(fs::ResultPathNotFound) { + /* The path doesn't exist. We should create it. */ + R_TRY(fs->CreateDirectory(cur_path)); + + /* Get the updated entry type. */ + R_TRY(fs->GetEntryType(std::addressof(type), cur_path)); + } + } R_END_TRY_CATCH; + + /* Verify that the current entry isn't a file. */ + R_UNLESS(type != fs::DirectoryEntryType_File, fs::ResultPathAlreadyExists()); + + /* Advance to the next part of the path. */ + R_TRY(parser.ReadNext(std::addressof(is_finished))); + } while (!is_finished); + + R_SUCCEED(); + } + + Result HasEntry(bool *out, fs::fsa::IFileSystem *fsa, const fs::Path &path, fs::DirectoryEntryType type) { + /* Set out to false initially. */ + *out = false; + + /* Try to get the entry type. */ + fs::DirectoryEntryType entry_type; + R_TRY_CATCH(fsa->GetEntryType(std::addressof(entry_type), path)) { + /* If the path doesn't exist, nothing has gone wrong. */ + R_CONVERT(fs::ResultPathNotFound, ResultSuccess()); + } R_END_TRY_CATCH; + + /* We succeeded. */ + *out = entry_type == type; + R_SUCCEED(); + } + + } + + Result CopyFile(fs::fsa::IFileSystem *dst_fs, fs::fsa::IFileSystem *src_fs, const fs::Path &dst_path, const fs::Path &src_path, void *work_buf, size_t work_buf_size) { + /* Open source file. */ + std::unique_ptr<fs::fsa::IFile> src_file; + R_TRY(src_fs->OpenFile(std::addressof(src_file), src_path, fs::OpenMode_Read)); + + /* Get the file size. */ + s64 file_size; + R_TRY(src_file->GetSize(std::addressof(file_size))); + + /* Open dst file. */ + std::unique_ptr<fs::fsa::IFile> dst_file; + R_TRY(dst_fs->CreateFile(dst_path, file_size)); + R_TRY(dst_fs->OpenFile(std::addressof(dst_file), dst_path, fs::OpenMode_Write)); + + /* Read/Write file in work buffer sized chunks. */ + s64 remaining = file_size; + s64 offset = 0; + while (remaining > 0) { + size_t read_size; + R_TRY(src_file->Read(std::addressof(read_size), offset, work_buf, work_buf_size, fs::ReadOption())); + R_TRY(dst_file->Write(offset, work_buf, read_size, fs::WriteOption())); + + remaining -= read_size; + offset += read_size; + } + + R_SUCCEED(); + } + + Result CopyDirectoryRecursively(fs::fsa::IFileSystem *dst_fs, fs::fsa::IFileSystem *src_fs, const fs::Path &dst_path, const fs::Path &src_path, fs::DirectoryEntry *entry, void *work_buf, size_t work_buf_size) { + /* Set up the destination work path to point at the target directory. */ + fs::Path dst_work_path; + R_TRY(dst_work_path.Initialize(dst_path)); + + /* Iterate, copying files. */ + R_RETURN(IterateDirectoryRecursively(src_fs, src_path, entry, + [&](const fs::Path &path, const fs::DirectoryEntry &entry) -> Result { /* On Enter Directory */ + AMS_UNUSED(path, entry); + + /* Append the current entry to the dst work path. */ + R_TRY(dst_work_path.AppendChild(entry.name)); + + /* Create the directory. */ + R_RETURN(dst_fs->CreateDirectory(dst_work_path)); + }, + [&](const fs::Path &path, const fs::DirectoryEntry &entry) -> Result { /* On Exit Directory */ + AMS_UNUSED(path, entry); + + /* Remove the directory we're leaving from the dst work path. */ + R_RETURN(dst_work_path.RemoveChild()); + }, + [&](const fs::Path &path, const fs::DirectoryEntry &entry) -> Result { /* On File */ + /* Append the current entry to the dst work path. */ + R_TRY(dst_work_path.AppendChild(entry.name)); + + /* Copy the file. */ + R_TRY(fssystem::CopyFile(dst_fs, src_fs, dst_work_path, path, work_buf, work_buf_size)); + + /* Remove the current entry from the dst work path. */ + R_RETURN(dst_work_path.RemoveChild()); + } + )); + } + + Result HasFile(bool *out, fs::fsa::IFileSystem *fs, const fs::Path &path) { + R_RETURN(HasEntry(out, fs, path, fs::DirectoryEntryType_File)); + } + + Result HasDirectory(bool *out, fs::fsa::IFileSystem *fs, const fs::Path &path) { + R_RETURN(HasEntry(out, fs, path, fs::DirectoryEntryType_Directory)); + } + + Result EnsureDirectory(fs::fsa::IFileSystem *fs, const fs::Path &path) { + /* First, check if the directory already exists. If it does, we're good to go. */ + fs::DirectoryEntryType type; + R_TRY_CATCH(fs->GetEntryType(std::addressof(type), path)) { + /* If the directory doesn't already exist, we should create it. */ + R_CATCH(fs::ResultPathNotFound) { + R_TRY(EnsureDirectoryImpl(fs, path)); + } + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + Result TryAcquireCountSemaphore(util::unique_lock<SemaphoreAdaptor> *out, SemaphoreAdaptor *adaptor) { + /* Create deferred unique lock. */ + util::unique_lock<SemaphoreAdaptor> lock(*adaptor, std::defer_lock); + + /* Try to lock. */ + R_UNLESS(lock.try_lock(), fs::ResultOpenCountLimit()); + + /* Set the output lock. */ + *out = std::move(lock); + R_SUCCEED(); + } + + void AddCounter(void *_counter, size_t counter_size, u64 value) { + u8 *counter = static_cast<u8 *>(_counter); + u64 remaining = value; + u8 carry = 0; + + for (size_t i = 0; i < counter_size; i++) { + auto sum = counter[counter_size - 1 - i] + (remaining & 0xFF) + carry; + carry = static_cast<u8>(sum >> BITSIZEOF(u8)); + auto sum8 = static_cast<u8>(sum & 0xFF); + + counter[counter_size - 1 - i] = sum8; + + remaining >>= BITSIZEOF(u8); + if (carry == 0 && remaining == 0) { + break; + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gc/impl/gc_embedded_data_holder.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gc/impl/gc_embedded_data_holder.cpp new file mode 100644 index 00000000..e5510c29 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gc/impl/gc_embedded_data_holder.cpp @@ -0,0 +1,235 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::gc::impl { + + namespace { + + constexpr const u8 LibraryEmbeddedCa1Modulus[GcCrypto::GcRsaKeyLength] = { + 0xAF, 0xB6, 0xA0, 0x1F, 0x7F, 0x8C, 0xEC, 0xD6, 0x62, 0xC9, 0xF9, 0x83, 0x69, 0x4F, 0x0E, 0x8E, + 0xC3, 0x71, 0x70, 0x60, 0x63, 0xEB, 0x90, 0x2F, 0x1E, 0x3B, 0xA2, 0xCD, 0xD5, 0x7A, 0xAA, 0x45, + 0x27, 0x61, 0xF9, 0x10, 0xC0, 0x0C, 0x72, 0xE7, 0xBD, 0x70, 0x2E, 0x32, 0xA0, 0xD0, 0x8A, 0x29, + 0x43, 0x19, 0x12, 0x8C, 0x8D, 0x10, 0xE4, 0x04, 0xE0, 0x13, 0x7F, 0x26, 0x02, 0x1B, 0xFD, 0x44, + 0xAF, 0x70, 0xD7, 0xBF, 0xDF, 0x97, 0xD2, 0x34, 0xFD, 0xBB, 0x51, 0x8D, 0x7C, 0x04, 0x9D, 0x30, + 0xFF, 0xB1, 0xB4, 0xD3, 0xEF, 0x2C, 0xEE, 0xAD, 0x4C, 0x4A, 0x26, 0x94, 0x15, 0x13, 0xA9, 0xDA, + 0xF4, 0xA4, 0x22, 0xE7, 0x11, 0x8A, 0xE4, 0xB0, 0xE9, 0x66, 0x23, 0xA7, 0xED, 0x7D, 0x73, 0x8B, + 0x32, 0xE5, 0xE1, 0x19, 0x34, 0x15, 0x06, 0x5D, 0xA6, 0xCD, 0x80, 0xA5, 0xC0, 0xD0, 0xCE, 0x7C, + 0x3E, 0x3D, 0x1F, 0x2B, 0x65, 0x26, 0xBE, 0xAE, 0x55, 0xC7, 0x03, 0xCF, 0x4A, 0xD3, 0xDA, 0x54, + 0x13, 0x1F, 0x20, 0x05, 0xE9, 0x9B, 0x3F, 0xDE, 0x73, 0xD3, 0xA0, 0xFF, 0xA0, 0x7E, 0xA9, 0x6A, + 0xBC, 0xF8, 0x6C, 0xF1, 0x3C, 0x72, 0x4E, 0x6F, 0xA1, 0x3C, 0x20, 0xCD, 0x3A, 0x1A, 0x65, 0xE0, + 0xF0, 0xF8, 0x84, 0xEB, 0x6B, 0x38, 0x49, 0xB6, 0xF2, 0x5B, 0x81, 0x16, 0x8B, 0x1A, 0xE0, 0x4F, + 0x18, 0x88, 0xF1, 0xAD, 0x66, 0xA9, 0xC8, 0xE0, 0x9D, 0xD0, 0x9B, 0xFA, 0xAD, 0xBE, 0xC9, 0x5C, + 0xE5, 0x54, 0x2A, 0xF4, 0x59, 0xE2, 0xFA, 0xD4, 0xC9, 0x58, 0x1C, 0x83, 0xD9, 0x23, 0x77, 0xDC, + 0x78, 0xBE, 0xCA, 0x7B, 0xC1, 0x69, 0xF3, 0x9C, 0xFE, 0xF7, 0xB9, 0x9E, 0xD6, 0x44, 0x70, 0x1C, + 0x8B, 0x08, 0xB0, 0x44, 0xE5, 0x63, 0xFB, 0xB8, 0x45, 0x40, 0xDA, 0xA2, 0x3C, 0xB9, 0xFB, 0x75, + }; + + constexpr const u8 LibraryEmbeddedCa9Modulus[GcCrypto::GcRsaKeyLength] = { + 0xCD, 0xF3, 0x2C, 0xB0, 0xF5, 0x14, 0x78, 0x34, 0xE5, 0x02, 0xD0, 0x29, 0x6A, 0xA5, 0xFD, 0x97, + 0x6A, 0xE0, 0xB0, 0xBB, 0xB0, 0x3B, 0x1A, 0x80, 0xB7, 0xD7, 0x58, 0x92, 0x79, 0x84, 0xC0, 0x36, + 0xB1, 0x55, 0x23, 0xD8, 0xA5, 0x60, 0x91, 0x26, 0x48, 0x1A, 0x80, 0x4A, 0xEA, 0x00, 0x98, 0x2A, + 0xEC, 0x52, 0x17, 0x72, 0x92, 0x4D, 0xF5, 0x42, 0xA7, 0x8A, 0x6F, 0x7F, 0xD2, 0x48, 0x51, 0x8E, + 0xDF, 0xCB, 0xBF, 0x77, 0xF6, 0x18, 0xBD, 0xE5, 0x00, 0xD9, 0x70, 0x8C, 0xEF, 0x57, 0xB2, 0x96, + 0xD0, 0x36, 0x83, 0x88, 0x9C, 0xC5, 0xFB, 0xA0, 0x33, 0x81, 0xA2, 0x12, 0x23, 0xC6, 0xC7, 0x86, + 0x0A, 0x98, 0x57, 0x4D, 0x2E, 0xB5, 0xAE, 0x64, 0xE4, 0x6F, 0xC2, 0xC5, 0xAC, 0x6A, 0x1D, 0xDB, + 0xA5, 0xAF, 0x12, 0x22, 0xAB, 0x1F, 0x51, 0xC8, 0x0E, 0x0D, 0xC9, 0xF5, 0x03, 0xE8, 0xD2, 0xFC, + 0x84, 0x62, 0x26, 0x55, 0xA4, 0xC3, 0xE2, 0xA8, 0x98, 0x05, 0x67, 0x23, 0xFD, 0xA5, 0x46, 0x40, + 0x78, 0x51, 0x09, 0x3D, 0x91, 0x74, 0xD6, 0xD0, 0x54, 0x23, 0x0D, 0xA0, 0xFB, 0x07, 0xD0, 0xAA, + 0x9D, 0x50, 0x4E, 0x2B, 0x26, 0x9A, 0x14, 0xE5, 0x6C, 0x73, 0x66, 0x24, 0x18, 0xA1, 0x93, 0x9C, + 0x2A, 0x40, 0x40, 0x05, 0x6B, 0xF1, 0x45, 0xDF, 0x22, 0x8B, 0x40, 0x61, 0xA4, 0x11, 0x06, 0x03, + 0xA5, 0x53, 0x84, 0xC0, 0x12, 0xE1, 0x88, 0x9D, 0x55, 0x55, 0x07, 0x40, 0x88, 0x01, 0x8C, 0xAB, + 0xA2, 0xFD, 0xFD, 0x19, 0x48, 0x25, 0xAB, 0x59, 0x59, 0x28, 0x63, 0x68, 0x69, 0x1B, 0x99, 0x73, + 0x8D, 0xAB, 0x5A, 0xFA, 0x71, 0x60, 0x1B, 0x12, 0xE7, 0x99, 0x70, 0xF1, 0x99, 0x2A, 0x50, 0x18, + 0x8B, 0x6B, 0x61, 0x90, 0xE2, 0x7E, 0x8B, 0x90, 0xD4, 0xD5, 0xC0, 0xCB, 0x7C, 0x08, 0x06, 0xD9, + }; + + constexpr const u8 LibraryEmbeddedCaPublicExponent[GcCrypto::GcRsaPublicExponentLength] = { + 0x01, 0x00, 0x01 + }; + + constexpr const u8 LibraryEmbeddedCa10Modulus[2][GcCrypto::GcRsaKeyLength] = { + { + 0x98, 0xC7, 0x26, 0xB6, 0x0D, 0x0A, 0x50, 0xA7, 0x39, 0x21, 0x0A, 0xE3, 0x2F, 0xE4, 0x3E, 0x2E, + 0x5B, 0xA2, 0x86, 0x75, 0xAA, 0x5C, 0xEE, 0x34, 0xF1, 0xA3, 0x3A, 0x7E, 0xBD, 0x90, 0x4E, 0xF7, + 0x8D, 0xFA, 0x17, 0xAA, 0x6B, 0xC6, 0x36, 0x6D, 0x4C, 0x9A, 0x6D, 0x57, 0x2F, 0x80, 0xA2, 0xBC, + 0x38, 0x4D, 0xDA, 0x99, 0xA1, 0xD8, 0xC3, 0xE2, 0x99, 0x79, 0x36, 0x71, 0x90, 0x20, 0x25, 0x9D, + 0x4D, 0x11, 0xB8, 0x2E, 0x63, 0x6B, 0x5A, 0xFA, 0x1E, 0x9C, 0x04, 0xD1, 0xC5, 0xF0, 0x9C, 0xB1, + 0x0F, 0xB8, 0xC1, 0x7B, 0xBF, 0xE8, 0xB0, 0xD2, 0x2B, 0x47, 0x01, 0x22, 0x6B, 0x23, 0xC9, 0xD0, + 0xBC, 0xEB, 0x75, 0x6E, 0x41, 0x7D, 0x4C, 0x26, 0xA4, 0x73, 0x21, 0xB4, 0xF0, 0x14, 0xE5, 0xD9, + 0x8D, 0xB3, 0x64, 0xEE, 0xA8, 0xFA, 0x84, 0x1B, 0xB8, 0xB8, 0x7C, 0x88, 0x6B, 0xEF, 0xCC, 0x97, + 0x04, 0x04, 0x9A, 0x67, 0x2F, 0xDF, 0xEC, 0x0D, 0xB2, 0x5F, 0xB5, 0xB2, 0xBD, 0xB5, 0x4B, 0xDE, + 0x0E, 0x88, 0xA3, 0xBA, 0xD1, 0xB4, 0xE0, 0x91, 0x81, 0xA7, 0x84, 0xEB, 0x77, 0x85, 0x8B, 0xEF, + 0xA5, 0xE3, 0x27, 0xB2, 0xF2, 0x82, 0x2B, 0x29, 0xF1, 0x75, 0x2D, 0xCE, 0xCC, 0xAE, 0x9B, 0x8D, + 0xED, 0x5C, 0xF1, 0x8E, 0xDB, 0x9A, 0xD7, 0xAF, 0x42, 0x14, 0x52, 0xCD, 0xE3, 0xC5, 0xDD, 0xCE, + 0x08, 0x12, 0x17, 0xD0, 0x7F, 0x1A, 0xAA, 0x1F, 0x7D, 0xE0, 0x93, 0x54, 0xC8, 0xBC, 0x73, 0x8A, + 0xCB, 0xAD, 0x6E, 0x93, 0xE2, 0x19, 0x72, 0x6B, 0xD3, 0x45, 0xF8, 0x73, 0x3D, 0x2B, 0x6A, 0x55, + 0xD2, 0x3A, 0x8B, 0xB0, 0x8A, 0x42, 0xE3, 0x3D, 0xF1, 0x92, 0x23, 0x42, 0x2E, 0xBA, 0xCC, 0x9C, + 0x9A, 0xC1, 0xDD, 0x62, 0x86, 0x9C, 0x2E, 0xE1, 0x2D, 0x6F, 0x62, 0x67, 0x51, 0x08, 0x0E, 0xCF, + }, + { + 0xC8, 0x65, 0x8D, 0x9D, 0x15, 0xF4, 0xCC, 0x35, 0x7D, 0x3C, 0x7B, 0xBF, 0xA3, 0x7D, 0xA9, 0xFE, + 0x93, 0xD9, 0x3A, 0x64, 0x7C, 0x12, 0x81, 0xB8, 0xA7, 0x6D, 0xE6, 0x76, 0xA5, 0x9F, 0x95, 0xB1, + 0x0B, 0xC5, 0x93, 0x9F, 0x48, 0xE9, 0x4F, 0x3D, 0xD1, 0x94, 0x0F, 0x78, 0x70, 0x5A, 0x2C, 0x82, + 0x6C, 0xE9, 0xB0, 0xA7, 0x6C, 0xEA, 0xB5, 0xC1, 0x20, 0xD0, 0x2A, 0x29, 0x42, 0xA6, 0x33, 0x70, + 0x75, 0x53, 0x3E, 0x88, 0x4A, 0xEF, 0x35, 0x0E, 0x79, 0xE4, 0xB0, 0x0F, 0x90, 0xA2, 0xAC, 0xF8, + 0x31, 0x02, 0xA3, 0x8E, 0x99, 0x7E, 0xF4, 0x72, 0x5A, 0x0B, 0xE8, 0x23, 0x4E, 0x87, 0xFB, 0x2F, + 0x22, 0x22, 0x57, 0xF6, 0xE1, 0x43, 0xFD, 0x11, 0xDA, 0x2D, 0xE6, 0x25, 0x96, 0x4C, 0x6B, 0x3B, + 0x54, 0x0C, 0x22, 0x8C, 0xB5, 0x82, 0xDB, 0x49, 0x5C, 0xB0, 0x36, 0x13, 0x31, 0x6F, 0x1A, 0xFF, + 0xA5, 0x1F, 0x70, 0x15, 0xAC, 0xDA, 0xF5, 0xD6, 0xE5, 0x71, 0x2F, 0x47, 0x43, 0xAB, 0x00, 0x03, + 0xCE, 0x9C, 0x70, 0xEB, 0x58, 0x6C, 0xE1, 0x3F, 0xC8, 0xD7, 0x43, 0xDA, 0x34, 0xDD, 0x23, 0x76, + 0xE3, 0x39, 0xB6, 0x8E, 0x5D, 0x63, 0xD6, 0xDD, 0x42, 0x5B, 0xB4, 0x58, 0xCF, 0x2D, 0x47, 0x61, + 0x2F, 0x3F, 0xC3, 0x20, 0xF5, 0xD6, 0xDB, 0xFD, 0x75, 0xCB, 0x06, 0xBC, 0x94, 0x4E, 0xE5, 0x3D, + 0xC8, 0x70, 0xC6, 0xCB, 0xB9, 0xE0, 0x9B, 0x0F, 0x32, 0xA4, 0xC3, 0xCA, 0x46, 0x8C, 0x44, 0x2D, + 0x2E, 0x71, 0xC8, 0xF0, 0x51, 0x17, 0x94, 0x5D, 0x40, 0xE2, 0x31, 0x9A, 0x24, 0x9F, 0x7C, 0xC5, + 0xDC, 0xB9, 0xB4, 0x43, 0x23, 0x70, 0xF7, 0x73, 0x0A, 0x5A, 0x6B, 0x8D, 0x9C, 0x76, 0xB1, 0x23, + 0x49, 0x35, 0x4E, 0x3E, 0x92, 0x22, 0xDF, 0xBB, 0x5E, 0xF4, 0x9E, 0x98, 0xCF, 0x51, 0xBE, 0xDF, + }, + }; + + constexpr const u8 LibraryEmbeddedCa10CertificateModulus[2][GcCrypto::GcRsaKeyLength] = { + { + 0xAA, 0x9B, 0x6C, 0xE2, 0x50, 0xE5, 0xEC, 0x25, 0xEE, 0x2D, 0x21, 0x9C, 0xB7, 0x4F, 0xA2, 0x72, + 0x1E, 0x44, 0xB7, 0xFC, 0x65, 0x86, 0xAC, 0x81, 0xCC, 0x09, 0xDC, 0xAD, 0xB7, 0x68, 0x37, 0x52, + 0x72, 0x81, 0xD5, 0xBA, 0x72, 0x11, 0x41, 0x71, 0x98, 0x46, 0xA9, 0x47, 0xF6, 0x95, 0x9D, 0x9B, + 0x5E, 0xCA, 0x07, 0x5A, 0x57, 0xE0, 0xAB, 0x2E, 0xDB, 0xE5, 0xF3, 0x01, 0x3B, 0xBB, 0x2B, 0x2E, + 0x44, 0x31, 0xA3, 0x0B, 0x2F, 0x3A, 0x51, 0xC4, 0x6B, 0x64, 0xD8, 0xF1, 0x01, 0x2D, 0xE9, 0xE8, + 0x86, 0x30, 0xAC, 0xF8, 0x02, 0xA3, 0x5A, 0xBE, 0x60, 0xFB, 0x5C, 0x1C, 0x39, 0x7C, 0x8B, 0x4F, + 0xBF, 0xE2, 0xDF, 0x1E, 0xF2, 0x69, 0x4E, 0xA3, 0x6A, 0x6C, 0x69, 0x97, 0xDD, 0xF1, 0xB2, 0x14, + 0x63, 0x8F, 0xDD, 0x94, 0xC5, 0x7D, 0x73, 0xF6, 0xE1, 0xDA, 0x0C, 0xD5, 0x8B, 0x69, 0x76, 0x06, + 0xC1, 0xE7, 0x61, 0x1C, 0x4B, 0xF2, 0x5B, 0x18, 0x6B, 0xB0, 0x05, 0x34, 0x2C, 0x4C, 0xAB, 0x45, + 0xF3, 0x88, 0x2E, 0x71, 0xFD, 0x7A, 0x7F, 0xC3, 0x0D, 0xB4, 0xB4, 0x71, 0xDF, 0xEE, 0x9A, 0xAA, + 0x1E, 0x26, 0xD5, 0x17, 0x43, 0x6A, 0x6B, 0x4E, 0x93, 0xA2, 0xEE, 0x88, 0xAB, 0x5E, 0xFB, 0x68, + 0x32, 0x78, 0xB3, 0xF7, 0xA5, 0x16, 0x1C, 0x19, 0x6A, 0x66, 0xA4, 0xE3, 0x97, 0x8F, 0x7B, 0x19, + 0x1D, 0xE4, 0x2D, 0x50, 0x09, 0x41, 0x4A, 0x77, 0xF7, 0xA0, 0xBD, 0xEE, 0x99, 0x18, 0x9B, 0xA7, + 0x67, 0xFF, 0x67, 0xF6, 0xDA, 0xD0, 0x31, 0xEF, 0x8E, 0x4F, 0x1C, 0xC6, 0xBA, 0xC6, 0xC4, 0x3D, + 0x81, 0xB9, 0xFD, 0x9F, 0x2E, 0xF0, 0x4C, 0x50, 0x05, 0x9D, 0x08, 0x45, 0xA2, 0x15, 0x35, 0xE9, + 0xC2, 0xED, 0xFD, 0x2F, 0xF7, 0xD3, 0xA8, 0x39, 0xD5, 0xD2, 0xF4, 0x79, 0x58, 0x76, 0x43, 0xCB, + }, + { + 0xC5, 0x42, 0x7B, 0x81, 0x51, 0x70, 0x8B, 0x84, 0xBD, 0x16, 0x21, 0x37, 0x9E, 0xBC, 0x54, 0xEC, + 0x97, 0xFB, 0x16, 0x81, 0x77, 0x5B, 0x67, 0x02, 0xE5, 0x7E, 0x06, 0x60, 0xC9, 0x3B, 0x4B, 0x98, + 0xB1, 0xEB, 0xE2, 0xA6, 0x46, 0xA5, 0xBB, 0xD1, 0x8A, 0xF4, 0xAB, 0x6D, 0x60, 0xD0, 0xC3, 0xFD, + 0xE5, 0x9F, 0x80, 0xA4, 0xA5, 0xDF, 0xD2, 0xAD, 0x64, 0x8E, 0xB4, 0x72, 0x22, 0x95, 0xDB, 0x5F, + 0xB1, 0x38, 0x43, 0x1C, 0x25, 0xFE, 0x73, 0x10, 0xF4, 0xB8, 0xBD, 0xAA, 0xCF, 0x1A, 0x12, 0x1F, + 0x7C, 0xED, 0x72, 0x3E, 0xCC, 0xF9, 0x75, 0x28, 0x21, 0x83, 0x74, 0x92, 0x72, 0xD4, 0xD5, 0x01, + 0x59, 0x2A, 0x7A, 0x6F, 0x80, 0xA3, 0xA5, 0x63, 0xD5, 0x09, 0x36, 0xCE, 0x0C, 0x3F, 0xCF, 0x08, + 0x10, 0x29, 0xEE, 0xB9, 0xB1, 0xE2, 0x79, 0x02, 0xEC, 0xE8, 0x51, 0x72, 0x4D, 0x60, 0xE4, 0xAC, + 0x76, 0x23, 0x06, 0x45, 0x6F, 0x02, 0xDB, 0x7A, 0xBC, 0x46, 0xC4, 0xF8, 0x3E, 0xC2, 0x1B, 0x9C, + 0x6A, 0xC3, 0x37, 0xE7, 0xC2, 0x85, 0x80, 0xA6, 0xB1, 0x41, 0xC6, 0x43, 0x2A, 0xD9, 0x45, 0x63, + 0x4D, 0x8E, 0xCB, 0xA2, 0x79, 0x54, 0x94, 0x54, 0xE7, 0x34, 0xEA, 0xAD, 0xE9, 0x47, 0x52, 0x6C, + 0x96, 0x22, 0xF2, 0xD6, 0xDC, 0xB7, 0x45, 0x03, 0xB6, 0xC8, 0x36, 0x92, 0x10, 0x4A, 0x40, 0x2B, + 0x05, 0x34, 0x78, 0x2A, 0xAD, 0x6A, 0x8E, 0x7F, 0xA1, 0x22, 0x3A, 0xC5, 0xD1, 0x0A, 0x4D, 0xD8, + 0x7A, 0x9A, 0x53, 0x9A, 0x00, 0xAF, 0x70, 0x76, 0xC1, 0xF9, 0x9C, 0x98, 0x02, 0xCB, 0x4C, 0xF5, + 0x9E, 0x51, 0x29, 0x72, 0x4C, 0x13, 0x45, 0xCA, 0xB1, 0xA4, 0x4A, 0x4E, 0x32, 0xCC, 0x23, 0xA8, + 0x69, 0xBE, 0x82, 0xD5, 0x86, 0x22, 0xA5, 0xEE, 0x97, 0x1A, 0xFF, 0x11, 0xF0, 0xE7, 0x66, 0x0B, + }, + }; + + constexpr const u8 LibraryEmbeddedCardHeaderKey[2][GcCrypto::GcAesKeyLength] = { + { 0x01, 0xC5, 0x8F, 0xE7, 0x00, 0x2D, 0x13, 0x5A, 0xB2, 0x9A, 0x3F, 0x69, 0x33, 0x95, 0x74, 0xB1, }, + { 0xCB, 0xA7, 0xB8, 0x75, 0xEB, 0x67, 0x05, 0xFB, 0x46, 0x0A, 0x33, 0xFD, 0x34, 0x09, 0x13, 0xB4, }, + }; + + constexpr const u8 LibraryEmbeddedConcatenatedGcKeys[2][0x70] = { + { + 0x98, 0x42, 0xD1, 0x45, 0x92, 0xEE, 0x79, 0xAE, 0xE3, 0xAA, 0xC9, 0xEA, 0x6A, 0x67, 0xC4, 0xB4, + 0x5E, 0x18, 0x1E, 0x0C, 0xC0, 0xA2, 0x1C, 0x0E, 0x05, 0xA4, 0x49, 0x30, 0x53, 0x7F, 0xC8, 0xE2, + 0xB9, 0xFB, 0x97, 0x31, 0x0A, 0x4E, 0x28, 0xE7, 0x1E, 0x69, 0x8C, 0xEE, 0xED, 0x26, 0x20, 0x14, + 0x63, 0x76, 0xBC, 0x1D, 0x86, 0xED, 0x11, 0x01, 0x4D, 0xB0, 0xFC, 0x88, 0xD4, 0x64, 0x15, 0x03, + 0x68, 0x95, 0x4D, 0x5A, 0x87, 0x57, 0x81, 0xB6, 0x6C, 0xD1, 0xEF, 0x40, 0x9D, 0x74, 0xF1, 0xA5, + 0xDA, 0xCA, 0x1F, 0x3E, 0x78, 0x96, 0xCA, 0x2F, 0x1A, 0x47, 0xA3, 0x19, 0x47, 0x47, 0xC4, 0x54, + 0x5C, 0x97, 0x02, 0x74, 0xF2, 0x69, 0xA2, 0x14, 0x46, 0xFC, 0x5B, 0x21, 0x85, 0x29, 0xCB, 0x16, + }, + { + 0xE8, 0x17, 0xE6, 0x0B, 0xE2, 0x6C, 0x32, 0x30, 0x45, 0xF7, 0xBA, 0x8D, 0xBD, 0x99, 0x15, 0x62, + 0xD1, 0x1C, 0x1C, 0x2C, 0x42, 0xC1, 0x2E, 0x1B, 0x4A, 0xF1, 0x65, 0x3B, 0x0D, 0x37, 0xF3, 0xC6, + 0x91, 0xB2, 0x5C, 0x22, 0xB9, 0x47, 0xF1, 0x15, 0xB0, 0xEE, 0x16, 0xC5, 0x3F, 0xCC, 0x58, 0xD6, + 0xA6, 0xAC, 0x06, 0x47, 0x3A, 0xA0, 0x9B, 0x12, 0xE2, 0x50, 0x80, 0x13, 0x49, 0x2C, 0x3C, 0xED, + 0x35, 0x5B, 0xA3, 0x6D, 0x26, 0x1E, 0xF6, 0xC9, 0xFA, 0xD2, 0x43, 0x81, 0x5A, 0xD0, 0x22, 0x75, + 0x78, 0x5A, 0x92, 0xE4, 0x91, 0x49, 0xD8, 0x28, 0x57, 0x57, 0x49, 0x68, 0x01, 0x0E, 0xA4, 0x10, + 0x5B, 0x05, 0x47, 0x03, 0xD1, 0x1B, 0xA7, 0xCA, 0xD9, 0x06, 0x10, 0x02, 0x85, 0xA3, 0x99, 0x26, + }, + }; + + constexpr const u8 LibraryEmbeddedSplEncryptedKek[GcCrypto::GcAesKeyLength] = { + 0x42, 0xF1, 0xEB, 0xCB, 0xDD, 0xED, 0x82, 0xAF, 0x32, 0x4E, 0x0D, 0xF4, 0x84, 0xF2, 0xAB, 0x57, + }; + + constexpr const u8 LibraryEmbeddedIvForKek[2][GcCrypto::GcAesCbcIvLength] = { + { 0xC6, 0x09, 0x0A, 0x32, 0x51, 0xA5, 0x26, 0xEC, 0x8F, 0x2B, 0xA9, 0x3E, 0xCC, 0x62, 0xF0, 0x92 }, + { 0x23, 0xA6, 0xFE, 0x53, 0xE7, 0x16, 0x86, 0xFB, 0x9B, 0xB5, 0x72, 0x32, 0x42, 0x01, 0xA3, 0xC7 }, + }; + + } + + constinit bool EmbeddedDataHolder::s_is_dev = false; + + constinit const void *EmbeddedDataHolder::s_ca_public_exponent = LibraryEmbeddedCaPublicExponent; + constinit const void *EmbeddedDataHolder::s_ca1_modulus = LibraryEmbeddedCa1Modulus; + constinit const void *EmbeddedDataHolder::s_ca9_modulus = LibraryEmbeddedCa9Modulus; + constinit const void *EmbeddedDataHolder::s_ca10_modulus = LibraryEmbeddedCa10Modulus[0]; + constinit const void *EmbeddedDataHolder::s_ca10_certificate_modulus = LibraryEmbeddedCa10CertificateModulus[0]; + constinit const void *EmbeddedDataHolder::s_card_header_key = LibraryEmbeddedCardHeaderKey[0]; + + Result EmbeddedDataHolder::SetLibraryEmbeddedKeys(bool is_dev) { + ConcatenatedGcLibraryEmbeddedKeys embedded_keys; + R_TRY(DecryptoEmbeddedKeys(std::addressof(embedded_keys), sizeof(embedded_keys), is_dev)); + + { + /* TODO: Set hmac/cv keys. */ + AMS_UNUSED(embedded_keys); + } + + R_SUCCEED(); + } + + Result EmbeddedDataHolder::DecryptoEmbeddedKeys(ConcatenatedGcLibraryEmbeddedKeys *out, size_t out_size, bool is_dev) { + /* Determine key index. */ + const auto key_idx = is_dev ? 1 : 0; + + /* Set global pointers. */ + s_is_dev = is_dev; + s_ca10_modulus = LibraryEmbeddedCa10Modulus[key_idx]; + s_ca10_certificate_modulus = LibraryEmbeddedCa10CertificateModulus[key_idx]; + s_card_header_key = LibraryEmbeddedCardHeaderKey[key_idx]; + + /* Get the keys/iv. */ + ConcatenatedGcLibraryEmbeddedKeys keys; + std::memcpy(std::addressof(keys), LibraryEmbeddedConcatenatedGcKeys[key_idx], sizeof(keys)); + static_assert(sizeof(LibraryEmbeddedConcatenatedGcKeys[0]) == sizeof(*out)); + + const void *iv_for_kek = LibraryEmbeddedIvForKek[key_idx]; + + /* Generate the kek. */ + u8 kek[GcCrypto::GcAesKeyLength] = {}; + crypto::Aes128CtrDecryptor aes_ctr; + ON_SCOPE_EXIT { + crypto::ClearMemory(kek, sizeof(kek)); + aes_ctr.Initialize(kek, sizeof(kek), iv_for_kek, GcCrypto::GcAesCbcIvLength); + }; + + constexpr const auto KeyGeneration = 4; + R_TRY(GcCrypto::DecryptAesKeySpl(kek, sizeof(kek), LibraryEmbeddedSplEncryptedKek, sizeof(LibraryEmbeddedSplEncryptedKek), KeyGeneration, 0)); + + /* Decrypt the embedded keys. */ + aes_ctr.Initialize(kek, sizeof(kek), iv_for_kek, GcCrypto::GcAesCbcIvLength); + aes_ctr.Update(out, out_size, std::addressof(keys), sizeof(keys)); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gc/impl/gc_gc_crypto.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gc/impl/gc_gc_crypto.cpp new file mode 100644 index 00000000..49023d78 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gc/impl/gc_gc_crypto.cpp @@ -0,0 +1,176 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::gc::impl { + + bool GcCrypto::CheckDevelopmentSpl() { + return spl::IsDevelopment(); + } + + Result GcCrypto::DecryptAesKeySpl(void *dst, size_t dst_size, const void *src, size_t src_size, s32 generation, u32 option) { + R_UNLESS(R_SUCCEEDED(spl::DecryptAesKey(dst, dst_size, src, src_size, generation, option)), fs::ResultGameCardSplDecryptAesKeyFailure()); + R_SUCCEED(); + } + + Result GcCrypto::VerifyCardHeader(const void *header_buffer, size_t header_size, const void *modulus, size_t modulus_size) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(header_size == sizeof(CardHeaderWithSignature)); + AMS_ABORT_UNLESS(modulus_size == GcRsaKeyLength); + + /* Get cert buffer as type. */ + const auto * const header = static_cast<const CardHeaderWithSignature *>(header_buffer); + + /* Verify the signature. */ + const void *mod = modulus != nullptr ? modulus : EmbeddedDataHolder::s_ca10_modulus; + const size_t mod_size = GcRsaKeyLength; + const void *exp = EmbeddedDataHolder::s_ca_public_exponent; + const size_t exp_size = GcRsaPublicExponentLength; + const void *sig = header->signature; + const size_t sig_size = sizeof(header->signature); + const void *msg = std::addressof(header->data); + const size_t msg_size = sizeof(header->data); + + const bool is_signature_valid = crypto::VerifyRsa2048Pkcs1Sha256(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size); + R_UNLESS(is_signature_valid, fs::ResultGameCardInvalidCardHeader()); + + R_SUCCEED(); + } + + Result GcCrypto::VerifyT1CardCertificate(const void *cert_buffer, size_t cert_size) { + /* Check pre-conditions. */ + R_UNLESS(cert_size == sizeof(T1CardCertificate), fs::ResultGameCardPreconditionViolation()); + + /* Get cert buffer as type. */ + const auto * const cert = static_cast<const T1CardCertificate *>(cert_buffer); + + /* Verify the signature. */ + const void *mod = EmbeddedDataHolder::s_ca9_modulus; + const size_t mod_size = GcRsaKeyLength; + const void *exp = EmbeddedDataHolder::s_ca_public_exponent; + const size_t exp_size = GcRsaPublicExponentLength; + const void *sig = cert->signature; + const size_t sig_size = sizeof(cert->signature); + const void *msg = reinterpret_cast<const u8 *>(cert) + sig_size; + const size_t msg_size = sizeof(*cert) - (sig_size + sizeof(cert->padding)); + + const bool is_signature_valid = crypto::VerifyRsa2048Pkcs1Sha256(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size); + R_UNLESS(is_signature_valid, fs::ResultGameCardInvalidT1CardCertificate()); + + R_SUCCEED(); + } + + Result GcCrypto::VerifyCa10Certificate(const void *cert_buffer, size_t cert_size) { + /* Check pre-conditions. */ + R_UNLESS(cert_size == sizeof(Ca10Certificate), fs::ResultGameCardPreconditionViolation()); + + /* Get header buffer as type. */ + const auto * const cert = static_cast<const Ca10Certificate *>(cert_buffer); + + /* Verify the signature. */ + const void *mod = EmbeddedDataHolder::s_ca10_certificate_modulus; + const size_t mod_size = GcRsaKeyLength; + const void *exp = EmbeddedDataHolder::s_ca_public_exponent; + const size_t exp_size = GcRsaPublicExponentLength; + const void *sig = cert->signature; + const size_t sig_size = sizeof(cert->signature); + const void *msg = reinterpret_cast<const u8 *>(cert) + sig_size; + const size_t msg_size = sizeof(*cert) - sig_size; + + const bool is_signature_valid = crypto::VerifyRsa2048Pkcs1Sha256(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size); + R_UNLESS(is_signature_valid, fs::ResultGameCardInvalidCa10Certificate()); + + R_SUCCEED(); + } + + Result GcCrypto::EncryptCardHeader(void *header_buffer, size_t header_size) { + /* Check pre-conditions. */ + R_UNLESS(header_size == sizeof(CardHeader), fs::ResultGameCardPreconditionViolation()); + + /* Get header buffer as type. */ + auto * const header = static_cast<CardHeader *>(header_buffer); + + /* Construct iv. */ + u8 iv[GcAesCbcIvLength]; + for (size_t i = 0; i < GcAesCbcIvLength; ++i) { + iv[i] = header->iv[GcAesCbcIvLength - 1 - i]; + } + + /* Encrypt. */ + crypto::EncryptAes128Cbc(std::addressof(header->encrypted_data), sizeof(header->encrypted_data), EmbeddedDataHolder::s_card_header_key, GcAesKeyLength, iv, GcAesCbcIvLength, std::addressof(header->encrypted_data), sizeof(header->encrypted_data)); + R_SUCCEED(); + } + + Result GcCrypto::DecryptCardHeader(void *header_buffer, size_t header_size) { + /* Check pre-conditions. */ + R_UNLESS(header_size == sizeof(CardHeader), fs::ResultGameCardPreconditionViolation()); + + /* Get header buffer as type. */ + auto * const header = static_cast<CardHeader *>(header_buffer); + + /* Construct iv. */ + u8 iv[GcAesCbcIvLength]; + for (size_t i = 0; i < GcAesCbcIvLength; ++i) { + iv[i] = header->iv[GcAesCbcIvLength - 1 - i]; + } + + /* Decrypt. */ + crypto::DecryptAes128Cbc(std::addressof(header->encrypted_data), sizeof(header->encrypted_data), EmbeddedDataHolder::s_card_header_key, GcAesKeyLength, iv, GcAesCbcIvLength, std::addressof(header->encrypted_data), sizeof(header->encrypted_data)); + R_SUCCEED(); + } + + Result GcCrypto::DecryptCardInitialData(void *dst, size_t dst_size, const void *initial_data, size_t data_size, size_t kek_index) { + /* Check pre-conditions. */ + R_UNLESS(data_size == sizeof(CardInitialData), fs::ResultGameCardPreconditionViolation()); + R_UNLESS(kek_index < GcTitleKeyKekIndexMax, fs::ResultGameCardPreconditionViolation()); + + /* Verify the kek is preset. */ + const void * const kek = EmbeddedDataHolder::s_titlekey_keks[kek_index]; + { + u8 zeros[GcAesKeyLength] = {}; + R_UNLESS(!crypto::IsSameBytes(kek, zeros, sizeof(zeros)), fs::ResultGameCardPreconditionViolation()); + } + + /*Generate the key. */ + u8 key[GcAesKeyLength]; + { + crypto::AesDecryptor128 aes; + aes.Initialize(kek, GcAesKeyLength); + aes.DecryptBlock(key, sizeof(key), initial_data, 0x10); + } + + /* Get data buffer as type. */ + const auto * const data = static_cast<const CardInitialData *>(initial_data); + R_UNLESS(dst_size == sizeof(data->payload.auth_data), fs::ResultGameCardPreconditionViolation()); + + /* Verify padding is all-zero. */ + bool any_nonzero = false; + for (size_t i = 0; i < util::size(data->padding); ++i) { + any_nonzero |= data->padding[i] != 0; + } + R_UNLESS(!any_nonzero, fs::ResultGameCardInitialNotFilledWithZero()); + + /* Decrypt the auth data. */ + u8 mac[sizeof(data->payload.auth_mac)]; + crypto::DecryptAes128Ccm(dst, dst_size, mac, sizeof(mac), key, GcAesKeyLength, data->payload.auth_nonce, sizeof(data->payload.auth_nonce), data->payload.auth_data, sizeof(data->payload.auth_data), nullptr, 0, sizeof(mac)); + + /* Check the mac. */ + R_UNLESS(crypto::IsSameBytes(mac, data->payload.auth_mac, sizeof(mac)), fs::ResultGameCardKekIndexMismatch()); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/gpio_driver_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/gpio_driver_api.cpp new file mode 100644 index 00000000..e2a526c1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/gpio_driver_api.cpp @@ -0,0 +1,88 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/gpio_driver_impl.hpp" +#include "impl/gpio_initial_config.hpp" +#include "impl/gpio_tegra_pad.hpp" + +namespace ams::gpio::driver::board::nintendo::nx { + + namespace { + + ams::gpio::driver::board::nintendo::nx::impl::DriverImpl *g_driver_impl = nullptr; + + } + + void Initialize(bool enable_interrupt_handlers) { + /* Check that we haven't previously initialized. */ + AMS_ABORT_UNLESS(g_driver_impl == nullptr); + + /* Get the device driver subsystem framework memory resource. */ + auto *memory_resource = ddsf::GetMemoryResource(); + + /* Allocate a new driver. */ + auto *driver_storage = static_cast<decltype(g_driver_impl)>(memory_resource->Allocate(sizeof(*g_driver_impl))); + AMS_ABORT_UNLESS(driver_storage != nullptr); + + /* Construct the new driver. */ + g_driver_impl = std::construct_at(driver_storage, impl::GpioRegistersPhysicalAddress, impl::GpioRegistersSize); + + /* Register the driver. */ + gpio::driver::RegisterDriver(g_driver_impl); + + /* Register interrupt handlers, if we should. */ + if (enable_interrupt_handlers) { + for (size_t i = 0; i < util::size(impl::InterruptNameTable); ++i) { + /* Allocate a handler. */ + void *handler_storage = memory_resource->Allocate(sizeof(impl::InterruptEventHandler)); + AMS_ABORT_UNLESS(handler_storage != nullptr); + + /* Initialize the handler. */ + auto *handler = std::construct_at(static_cast<impl::InterruptEventHandler *>(handler_storage)); + handler->Initialize(g_driver_impl, impl::InterruptNameTable[i], static_cast<int>(i)); + + /* Register the handler. */ + gpio::driver::RegisterInterruptHandler(handler); + } + } + + /* Create and register all pads. */ + for (const auto &entry : impl::PadMapCombinationList) { + /* Allocate a pad for our device. */ + void *pad_storage = memory_resource->Allocate(sizeof(impl::TegraPad)); + AMS_ABORT_UNLESS(pad_storage != nullptr); + + /* Create a pad for our device. */ + auto *pad = std::construct_at(static_cast<impl::TegraPad *>(pad_storage)); + pad->SetParameters(entry.internal_number, impl::PadInfo{entry.wake_event}); + + /* Register the pad with our driver. */ + g_driver_impl->RegisterDevice(pad); + + /* Register the device code with our driver. */ + R_ABORT_UNLESS(gpio::driver::RegisterDeviceCode(entry.device_code, pad)); + } + } + + void SetInitialGpioConfig() { + return impl::SetInitialGpioConfig(); + } + + void SetInitialWakePinConfig() { + return impl::SetInitialWakePinConfig(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_driver_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_driver_impl.cpp new file mode 100644 index 00000000..66ddc2ab --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_driver_impl.cpp @@ -0,0 +1,354 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "gpio_driver_impl.hpp" +#include "gpio_register_accessor.hpp" + +namespace ams::gpio::driver::board::nintendo::nx::impl { + + void InterruptEventHandler::Initialize(DriverImpl *drv, os::InterruptName intr, int ctlr) { + /* Set fields. */ + m_driver = drv; + m_interrupt_name = intr; + m_controller_number = ctlr; + + /* Initialize interrupt event. */ + os::InitializeInterruptEvent(std::addressof(m_interrupt_event), intr, os::EventClearMode_ManualClear); + + /* Initialize base. */ + IEventHandler::Initialize(std::addressof(m_interrupt_event)); + } + + void InterruptEventHandler::HandleEvent() { + /* Lock the driver's interrupt mutex. */ + std::scoped_lock lk(m_driver->m_interrupt_control_mutex); + + /* Check each pad. */ + bool found = false; + for (auto it = m_driver->m_interrupt_pad_list.begin(); !found && it != m_driver->m_interrupt_pad_list.end(); ++it) { + found = this->CheckAndHandleInterrupt(*it); + } + + /* If we didn't find a pad, clear the interrupt event. */ + if (!found) { + os::ClearInterruptEvent(std::addressof(m_interrupt_event)); + } + } + + bool InterruptEventHandler::CheckAndHandleInterrupt(TegraPad &pad) { + /* Get the pad's number. */ + const InternalGpioPadNumber pad_number = static_cast<InternalGpioPadNumber>(pad.GetPadNumber()); + + /* Check if the pad matches our controller number. */ + if (m_controller_number != ConvertInternalGpioPadNumberToController(pad_number)) { + return false; + } + + /* Get the addresses of INT_STA, INT_ENB. */ + const uintptr_t sta_address = GetGpioRegisterAddress(m_driver->m_gpio_virtual_address, GpioRegisterType_GPIO_INT_STA, pad_number); + const uintptr_t enb_address = GetGpioRegisterAddress(m_driver->m_gpio_virtual_address, GpioRegisterType_GPIO_INT_STA, pad_number); + const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); + + /* Check if both STA and ENB are set. */ + if (reg::Read(sta_address, 1u << pad_index) == 0 || reg::Read(enb_address, 1u << pad_index) == 0) { + return false; + } + + /* The pad is signaled. First, clear the enb bit. */ + SetMaskedBit(enb_address, pad_index, 0); + reg::Read(enb_address); + + /* Disable the interrupt on the pad. */ + pad.SetInterruptEnabled(false); + m_driver->RemoveInterruptPad(std::addressof(pad)); + + /* Clear the interrupt event. */ + os::ClearInterruptEvent(std::addressof(m_interrupt_event)); + + /* Signal the pad's bound event. */ + pad.SignalInterruptBoundEvent(); + + return true; + } + + DriverImpl::DriverImpl(dd::PhysicalAddress reg_paddr, size_t size) : m_gpio_physical_address(reg_paddr), m_gpio_virtual_address(), m_suspend_handler(this), m_interrupt_pad_list(), m_interrupt_control_mutex() { + /* Get the corresponding virtual address for our physical address. */ + m_gpio_virtual_address = dd::QueryIoMapping(reg_paddr, size); + AMS_ABORT_UNLESS(m_gpio_virtual_address != 0); + } + + void DriverImpl::InitializeDriver() { + /* Initialize our suspend handler. */ + m_suspend_handler.Initialize(m_gpio_virtual_address); + } + + void DriverImpl::FinalizeDriver() { + /* ... */ + } + + Result DriverImpl::InitializePad(Pad *pad) { + /* Validate arguments. */ + AMS_ASSERT(pad != nullptr); + + /* Convert the pad to an internal number. */ + const InternalGpioPadNumber pad_number = pad->GetPadNumber(); + + /* Configure the pad as GPIO by modifying the appropriate bit in CNF. */ + const uintptr_t pad_address = GetGpioRegisterAddress(m_gpio_virtual_address, GpioRegisterType_GPIO_CNF, pad_number); + const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); + SetMaskedBit(pad_address, pad_index, 1); + + /* Read the pad address to make sure our configuration takes. */ + reg::Read(pad_address); + + R_SUCCEED(); + } + + void DriverImpl::FinalizePad(Pad *pad) { + /* Validate arguments. */ + AMS_ASSERT(pad != nullptr); + + /* Nothing to do. */ + AMS_UNUSED(pad); + } + + Result DriverImpl::GetDirection(Direction *out, Pad *pad) const { + /* Validate arguments. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(pad != nullptr); + + /* Convert the pad to an internal number. */ + const InternalGpioPadNumber pad_number = pad->GetPadNumber(); + + /* Get the pad direction by reading the appropriate bit in OE */ + const uintptr_t pad_address = GetGpioRegisterAddress(m_gpio_virtual_address, GpioRegisterType_GPIO_OE, pad_number); + const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); + + if (reg::Read(pad_address, 1u << pad_index) != 0) { + *out = Direction_Output; + } else { + *out = Direction_Input; + } + + R_SUCCEED(); + + } + + Result DriverImpl::SetDirection(Pad *pad, Direction direction) { + /* Validate arguments. */ + AMS_ASSERT(pad != nullptr); + + /* Convert the pad to an internal number. */ + const InternalGpioPadNumber pad_number = pad->GetPadNumber(); + + /* Configure the pad direction by modifying the appropriate bit in OE */ + const uintptr_t pad_address = GetGpioRegisterAddress(m_gpio_virtual_address, GpioRegisterType_GPIO_OE, pad_number); + const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); + SetMaskedBit(pad_address, pad_index, direction); + + /* Read the pad address to make sure our configuration takes. */ + reg::Read(pad_address); + + R_SUCCEED(); + } + + Result DriverImpl::GetValue(GpioValue *out, Pad *pad) const { + /* Validate arguments. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(pad != nullptr); + + /* Convert the pad to an internal number. */ + const InternalGpioPadNumber pad_number = pad->GetPadNumber(); + + /* Get the pad value by reading the appropriate bit in IN */ + const uintptr_t pad_address = GetGpioRegisterAddress(m_gpio_virtual_address, GpioRegisterType_GPIO_IN, pad_number); + const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); + + if (reg::Read(pad_address, 1u << pad_index) != 0) { + *out = GpioValue_High; + } else { + *out = GpioValue_Low; + } + + R_SUCCEED(); + } + + Result DriverImpl::SetValue(Pad *pad, GpioValue value) { + /* Validate arguments. */ + AMS_ASSERT(pad != nullptr); + + /* Convert the pad to an internal number. */ + const InternalGpioPadNumber pad_number = pad->GetPadNumber(); + + /* Configure the pad value by modifying the appropriate bit in IN */ + const uintptr_t pad_address = GetGpioRegisterAddress(m_gpio_virtual_address, GpioRegisterType_GPIO_OUT, pad_number); + const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); + SetMaskedBit(pad_address, pad_index, value); + + /* Read the pad address to make sure our configuration takes. */ + reg::Read(pad_address); + + R_SUCCEED(); + } + + Result DriverImpl::GetInterruptMode(InterruptMode *out, Pad *pad) const { + /* Validate arguments. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(pad != nullptr); + + /* Convert the pad to an internal number. */ + const InternalGpioPadNumber pad_number = pad->GetPadNumber(); + + /* Get the pad mode by reading the appropriate bits in INT_LVL */ + const uintptr_t pad_address = GetGpioRegisterAddress(m_gpio_virtual_address, GpioRegisterType_GPIO_INT_LVL, pad_number); + const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); + + switch ((reg::Read(pad_address) >> pad_index) & InternalInterruptMode_Mask) { + case InternalInterruptMode_LowLevel: *out = InterruptMode_LowLevel; break; + case InternalInterruptMode_HighLevel: *out = InterruptMode_HighLevel; break; + case InternalInterruptMode_RisingEdge: *out = InterruptMode_RisingEdge; break; + case InternalInterruptMode_FallingEdge: *out = InterruptMode_FallingEdge; break; + case InternalInterruptMode_AnyEdge: *out = InterruptMode_AnyEdge; break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + R_SUCCEED(); + } + + Result DriverImpl::SetInterruptMode(Pad *pad, InterruptMode mode) { + /* Validate arguments. */ + AMS_ASSERT(pad != nullptr); + + /* Convert the pad to an internal number. */ + const InternalGpioPadNumber pad_number = pad->GetPadNumber(); + + /* Configure the pad mode by modifying the appropriate bits in INT_LVL */ + const uintptr_t pad_address = GetGpioRegisterAddress(m_gpio_virtual_address, GpioRegisterType_GPIO_INT_LVL, pad_number); + const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); + + switch (mode) { + case InterruptMode_LowLevel: reg::ReadWrite(pad_address, InternalInterruptMode_LowLevel << pad_index, InternalInterruptMode_Mask << pad_index); break; + case InterruptMode_HighLevel: reg::ReadWrite(pad_address, InternalInterruptMode_HighLevel << pad_index, InternalInterruptMode_Mask << pad_index); break; + case InterruptMode_RisingEdge: reg::ReadWrite(pad_address, InternalInterruptMode_RisingEdge << pad_index, InternalInterruptMode_Mask << pad_index); break; + case InterruptMode_FallingEdge: reg::ReadWrite(pad_address, InternalInterruptMode_FallingEdge << pad_index, InternalInterruptMode_Mask << pad_index); break; + case InterruptMode_AnyEdge: reg::ReadWrite(pad_address, InternalInterruptMode_AnyEdge << pad_index, InternalInterruptMode_Mask << pad_index); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Read the pad address to make sure our configuration takes. */ + reg::Read(pad_address); + + R_SUCCEED(); + } + + Result DriverImpl::SetInterruptEnabled(Pad *pad, bool en) { + /* TODO */ + AMS_UNUSED(pad, en); + AMS_ABORT(); + } + + Result DriverImpl::GetInterruptStatus(InterruptStatus *out, Pad *pad) { + /* TODO */ + AMS_UNUSED(out, pad); + AMS_ABORT(); + } + + Result DriverImpl::ClearInterruptStatus(Pad *pad) { + /* TODO */ + AMS_UNUSED(pad); + AMS_ABORT(); + } + + Result DriverImpl::GetDebounceEnabled(bool *out, Pad *pad) const { + /* TODO */ + AMS_UNUSED(out, pad); + AMS_ABORT(); + } + + Result DriverImpl::SetDebounceEnabled(Pad *pad, bool en) { + AMS_UNUSED(pad, en); + /* TODO */ + AMS_ABORT(); + } + + Result DriverImpl::GetDebounceTime(s32 *out_ms, Pad *pad) const { + /* TODO */ + AMS_UNUSED(out_ms, pad); + AMS_ABORT(); + } + + Result DriverImpl::SetDebounceTime(Pad *pad, s32 ms) { + /* TODO */ + AMS_UNUSED(pad, ms); + AMS_ABORT(); + } + + Result DriverImpl::GetUnknown22(u32 *out) { + /* TODO */ + AMS_UNUSED(out); + AMS_ABORT(); + } + + void DriverImpl::Unknown23() { + /* TODO */ + AMS_ABORT(); + } + + Result DriverImpl::SetValueForSleepState(Pad *pad, GpioValue value) { + /* TODO */ + AMS_UNUSED(pad, value); + AMS_ABORT(); + } + + Result DriverImpl::IsWakeEventActive(bool *out, Pad *pad) const { + /* TODO */ + AMS_UNUSED(out, pad); + AMS_ABORT(); + } + + Result DriverImpl::SetWakeEventActiveFlagSetForDebug(Pad *pad, bool en) { + /* TODO */ + AMS_UNUSED(pad, en); + AMS_ABORT(); + } + + Result DriverImpl::SetWakePinDebugMode(WakePinDebugMode mode) { + /* TODO */ + AMS_UNUSED(mode); + AMS_ABORT(); + } + + Result DriverImpl::Suspend() { + /* TODO */ + AMS_ABORT(); + } + + Result DriverImpl::SuspendLow() { + /* TODO */ + AMS_ABORT(); + } + + Result DriverImpl::Resume() { + /* TODO */ + AMS_ABORT(); + } + + Result DriverImpl::ResumeLow() { + /* TODO */ + AMS_ABORT(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_driver_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_driver_impl.hpp new file mode 100644 index 00000000..e67016b1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_driver_impl.hpp @@ -0,0 +1,129 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#include "gpio_tegra_pad.hpp" +#include "gpio_register_accessor.hpp" +#include "gpio_suspend_handler.hpp" + +namespace ams::gpio::driver::board::nintendo::nx::impl { + + class DriverImpl; + + class InterruptEventHandler : public ddsf::IEventHandler { + private: + DriverImpl *m_driver; + os::InterruptName m_interrupt_name; + os::InterruptEventType m_interrupt_event; + int m_controller_number; + private: + bool CheckAndHandleInterrupt(TegraPad &pad); + public: + InterruptEventHandler() : IEventHandler(), m_driver(nullptr), m_interrupt_name(), m_interrupt_event(), m_controller_number() { /* ... */ } + + void Initialize(DriverImpl *drv, os::InterruptName intr, int ctlr); + + virtual void HandleEvent() override; + }; + + class DriverImpl : public ::ams::gpio::driver::IGpioDriver { + NON_COPYABLE(DriverImpl); + NON_MOVEABLE(DriverImpl); + AMS_DDSF_CASTABLE_TRAITS(ams::gpio::driver::board::nintendo::nx::impl::DriverImpl, ::ams::gpio::driver::IGpioDriver); + friend class InterruptEventHandler; + private: + dd::PhysicalAddress m_gpio_physical_address; + uintptr_t m_gpio_virtual_address; + SuspendHandler m_suspend_handler; + TegraPad::InterruptList m_interrupt_pad_list; + mutable os::SdkMutex m_interrupt_control_mutex; + public: + DriverImpl(dd::PhysicalAddress reg_paddr, size_t size); + + virtual void InitializeDriver() override; + virtual void FinalizeDriver() override; + + virtual Result InitializePad(Pad *pad) override; + virtual void FinalizePad(Pad *pad) override; + + virtual Result GetDirection(Direction *out, Pad *pad) const override; + virtual Result SetDirection(Pad *pad, Direction direction) override; + + virtual Result GetValue(GpioValue *out, Pad *pad) const override; + virtual Result SetValue(Pad *pad, GpioValue value) override; + + virtual Result GetInterruptMode(InterruptMode *out, Pad *pad) const override; + virtual Result SetInterruptMode(Pad *pad, InterruptMode mode) override; + + virtual Result SetInterruptEnabled(Pad *pad, bool en) override; + + virtual Result GetInterruptStatus(InterruptStatus *out, Pad *pad) override; + virtual Result ClearInterruptStatus(Pad *pad) override; + + virtual os::SdkMutex &GetInterruptControlMutex(const Pad &pad) const override { + AMS_UNUSED(pad); + return m_interrupt_control_mutex; + } + + virtual Result GetDebounceEnabled(bool *out, Pad *pad) const override; + virtual Result SetDebounceEnabled(Pad *pad, bool en) override; + + virtual Result GetDebounceTime(s32 *out_ms, Pad *pad) const override; + virtual Result SetDebounceTime(Pad *pad, s32 ms) override; + + virtual Result GetUnknown22(u32 *out) override; + virtual void Unknown23() override; + + virtual Result SetValueForSleepState(Pad *pad, GpioValue value) override; + virtual Result IsWakeEventActive(bool *out, Pad *pad) const override; + virtual Result SetWakeEventActiveFlagSetForDebug(Pad *pad, bool en) override; + virtual Result SetWakePinDebugMode(WakePinDebugMode mode) override; + + virtual Result Suspend() override; + virtual Result SuspendLow() override; + virtual Result Resume() override; + virtual Result ResumeLow() override; + private: + static constexpr ALWAYS_INLINE TegraPad &GetTegraPad(Pad *pad) { + AMS_ASSERT(pad != nullptr); + return static_cast<TegraPad &>(*pad); + } + + static ALWAYS_INLINE const PadInfo &GetInfo(Pad *pad) { + return GetTegraPad(pad).GetInfo(); + } + + static ALWAYS_INLINE PadStatus &GetStatus(Pad *pad) { + return GetTegraPad(pad).GetStatus(); + } + + void AddInterruptPad(TegraPad *pad) { + AMS_ASSERT(pad != nullptr); + if (!pad->IsLinkedToInterruptBoundPadList()) { + m_interrupt_pad_list.push_back(*pad); + } + } + + void RemoveInterruptPad(TegraPad *pad) { + AMS_ASSERT(pad != nullptr); + if (pad->IsLinkedToInterruptBoundPadList()) { + m_interrupt_pad_list.erase(m_interrupt_pad_list.iterator_to(*pad)); + } + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config.cpp new file mode 100644 index 00000000..0b48c861 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config.cpp @@ -0,0 +1,172 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "gpio_driver_impl.hpp" +#include "gpio_initial_config.hpp" +#include "gpio_wake_pin_config.hpp" + +namespace ams::gpio::driver::board::nintendo::nx::impl { + + namespace { + + spl::HardwareType GetHardwareType() { + /* Acquire access to spl: */ + spl::Initialize(); + ON_SCOPE_EXIT { spl::Finalize(); }; + + /* Get config. */ + return ::ams::spl::GetHardwareType(); + } + + #include "gpio_initial_wake_pin_config_icosa.inc" + /* #include "gpio_initial_wake_pin_config_copper.inc" */ + #include "gpio_initial_wake_pin_config_hoag.inc" + #include "gpio_initial_wake_pin_config_iowa.inc" + #include "gpio_initial_wake_pin_config_calcio.inc" + #include "gpio_initial_wake_pin_config_aula.inc" + + #include "gpio_initial_config_icosa.inc" + /* #include "gpio_initial_config_copper.inc" */ + #include "gpio_initial_config_hoag.inc" + #include "gpio_initial_config_iowa.inc" + #include "gpio_initial_config_calcio.inc" + #include "gpio_initial_config_aula.inc" + + } + + void SetInitialGpioConfig() { + /* Set wake event levels, wake event enables. */ + const GpioInitialConfig *configs = nullptr; + size_t num_configs = 0; + + /* Select the correct config. */ + switch (GetHardwareType()) { + case spl::HardwareType::Icosa: + configs = InitialGpioConfigsIcosa; + num_configs = NumInitialGpioConfigsIcosa; + break; + case spl::HardwareType::Hoag: + configs = InitialGpioConfigsHoag; + num_configs = NumInitialGpioConfigsHoag; + break; + case spl::HardwareType::Iowa: + configs = InitialGpioConfigsIowa; + num_configs = NumInitialGpioConfigsIowa; + break; + case spl::HardwareType::Calcio: + configs = InitialGpioConfigsCalcio; + num_configs = NumInitialGpioConfigsCalcio; + break; + case spl::HardwareType::Aula: + configs = InitialGpioConfigsAula; + num_configs = NumInitialGpioConfigsAula; + break; + case spl::HardwareType::Copper: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Check we can use our config. */ + AMS_ABORT_UNLESS(configs != nullptr); + + /* Apply the configs. */ + { + /* Create a driver to use for the duration of our application. */ + DriverImpl driver(GpioRegistersPhysicalAddress, GpioRegistersSize); + driver.InitializeDriver(); + + for (size_t i = 0; i < num_configs; ++i) { + /* Find the internal pad number for our device. */ + bool found = false; + for (const auto &entry : PadMapCombinationList) { + if (entry.device_code == configs[i].device_code) { + /* We found an entry. */ + found = true; + + /* Create a pad for our device. */ + TegraPad pad; + pad.SetParameters(entry.internal_number, PadInfo{entry.wake_event}); + + /* Initialize the pad. */ + R_ABORT_UNLESS(driver.InitializePad(std::addressof(pad))); + + /* Set the direction. */ + R_ABORT_UNLESS(driver.SetDirection(std::addressof(pad), configs[i].direction)); + + /* If the direction is output, set the value. */ + if (configs[i].direction == Direction_Output) { + R_ABORT_UNLESS(driver.SetValue(std::addressof(pad), configs[i].value)); + } + + /* Finalize the pad we made. */ + driver.FinalizePad(std::addressof(pad)); + break; + } + } + + /* Ensure that we applied the config for the pad we wanted. */ + AMS_ABORT_UNLESS(found); + } + + /* Finalize the driver. */ + driver.FinalizeDriver(); + } + } + + void SetInitialWakePinConfig() { + /* Ensure the wec driver is initialized. */ + ams::wec::Initialize(); + + /* Set wake event levels, wake event enables. */ + const WakePinConfig *configs = nullptr; + size_t num_configs = 0; + + /* Select the correct config. */ + switch (GetHardwareType()) { + case spl::HardwareType::Icosa: + configs = InitialWakePinConfigsIcosa; + num_configs = NumInitialWakePinConfigsIcosa; + break; + case spl::HardwareType::Hoag: + configs = InitialWakePinConfigsHoag; + num_configs = NumInitialWakePinConfigsHoag; + break; + case spl::HardwareType::Iowa: + configs = InitialWakePinConfigsIowa; + num_configs = NumInitialWakePinConfigsIowa; + break; + case spl::HardwareType::Calcio: + configs = InitialWakePinConfigsCalcio; + num_configs = NumInitialWakePinConfigsCalcio; + break; + case spl::HardwareType::Aula: + configs = InitialWakePinConfigsAula; + num_configs = NumInitialWakePinConfigsAula; + break; + case spl::HardwareType::Copper: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Check we can use our config. */ + AMS_ABORT_UNLESS(configs != nullptr); + + /* Apply the config. */ + for (size_t i = 0; i < num_configs; ++i) { + wec::SetWakeEventLevel(configs[i].wake_event, configs[i].level); + wec::SetWakeEventEnabled(configs[i].wake_event, configs[i].enable); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config.hpp new file mode 100644 index 00000000..900c12e0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::gpio::driver::board::nintendo::nx::impl { + + struct GpioInitialConfig { + DeviceCode device_code; + gpio::Direction direction; + gpio::GpioValue value; + }; + + void SetInitialGpioConfig(); + void SetInitialWakePinConfig(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config_aula.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config_aula.inc new file mode 100644 index 00000000..a2500334 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config_aula.inc @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by gpio_pad_gen.py, do not edit manually. */ + +constexpr inline const GpioInitialConfig InitialGpioConfigsAula[] = { + { DeviceCode_GameCardReset, Direction_Output, GpioValue_Low }, + { DeviceCode_CodecAlert, Direction_Input, GpioValue_Low }, + { DeviceCode_Debug0, Direction_Output, GpioValue_Low }, + { DeviceCode_Debug1, Direction_Output, GpioValue_Low }, + { DeviceCode_Debug2, Direction_Output, GpioValue_Low }, + { DeviceCode_Debug3, Direction_Output, GpioValue_Low }, + { DeviceCode_PowSdEn, Direction_Output, GpioValue_Low }, + { DeviceCode_ExtConWakeS, Direction_Input, GpioValue_Low }, + { DeviceCode_GameCardCd, Direction_Input, GpioValue_High }, + { DeviceCode_BattChgStatus, Direction_Input, GpioValue_Low }, + { DeviceCode_BattChgEnableN, Direction_Output, GpioValue_Low }, + { DeviceCode_FanTach, Direction_Input, GpioValue_Low }, + { DeviceCode_Vdd50AEn, Direction_Output, GpioValue_Low }, + { DeviceCode_Vdd5V3En, Direction_Output, GpioValue_Low }, + { DeviceCode_TempAlert, Direction_Input, GpioValue_High }, + { DeviceCode_MotionInt, Direction_Input, GpioValue_Low }, + { DeviceCode_CodecHpDetIrq, Direction_Input, GpioValue_Low }, + { DeviceCode_TpIrq, Direction_Input, GpioValue_High }, + { DeviceCode_ButtonSleep2, Direction_Input, GpioValue_Low }, + { DeviceCode_ButtonVolUp, Direction_Input, GpioValue_High }, + { DeviceCode_ButtonVolDn, Direction_Input, GpioValue_High }, + { DeviceCode_BattMgicIrq, Direction_Input, GpioValue_Low }, + { DeviceCode_RecoveryKey, Direction_Input, GpioValue_Low }, + { DeviceCode_LcdReset, Direction_Output, GpioValue_Low }, + { DeviceCode_PdRstN, Direction_Output, GpioValue_Low }, + { DeviceCode_Bq24190Irq, Direction_Input, GpioValue_Low }, + { DeviceCode_SdCd, Direction_Input, GpioValue_High }, + { DeviceCode_SdWp, Direction_Input, GpioValue_High }, + { DeviceCode_CodecLdoEnTemp, Direction_Output, GpioValue_Low }, + { DeviceCode_TpReset, Direction_Output, GpioValue_Low }, + { DeviceCode_ExtconDetU, Direction_Input, GpioValue_Low }, + { DeviceCode_BtGpio2, Direction_Input, GpioValue_Low }, + { DeviceCode_BtGpio3, Direction_Input, GpioValue_Low }, + { DeviceCode_BtGpio4, Direction_Input, GpioValue_Low }, + { DeviceCode_ExtconChgU, Direction_Output, GpioValue_Low }, + { DeviceCode_CradleIrq, Direction_Input, GpioValue_High }, + { DeviceCode_PdVconnEn, Direction_Output, GpioValue_Low }, + { DeviceCode_PowVcpuInt, Direction_Input, GpioValue_High }, + { DeviceCode_ExtconDetS, Direction_Input, GpioValue_Low }, + { DeviceCode_GpioPortH0, Direction_Input, GpioValue_Low }, + { DeviceCode_WifiReset, Direction_Output, GpioValue_Low }, + { DeviceCode_WifiWakeHost, Direction_Input, GpioValue_Low }, + { DeviceCode_ApWakeBt, Direction_Output, GpioValue_Low }, + { DeviceCode_BtRst, Direction_Output, GpioValue_Low }, + { DeviceCode_BtWakeAp, Direction_Input, GpioValue_Low }, + { DeviceCode_ExtConWakeU, Direction_Input, GpioValue_Low }, + { DeviceCode_BtGpio5, Direction_Output, GpioValue_Low }, + { DeviceCode_ExtconChgS, Direction_Output, GpioValue_Low }, +}; + +constexpr inline size_t NumInitialGpioConfigsAula = util::size(InitialGpioConfigsAula); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config_calcio.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config_calcio.inc new file mode 100644 index 00000000..f76622d3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config_calcio.inc @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by gpio_pad_gen.py, do not edit manually. */ + +constexpr inline const GpioInitialConfig InitialGpioConfigsCalcio[] = { + { DeviceCode_Debug0, Direction_Output, GpioValue_Low }, + { DeviceCode_Debug1, Direction_Output, GpioValue_Low }, + { DeviceCode_Debug2, Direction_Output, GpioValue_Low }, + { DeviceCode_Debug3, Direction_Output, GpioValue_Low }, + { DeviceCode_PowSdEn, Direction_Output, GpioValue_Low }, + { DeviceCode_GpioPortF1, Direction_Output, GpioValue_Low }, + { DeviceCode_FanTach, Direction_Input, GpioValue_Low }, + { DeviceCode_TempAlert, Direction_Input, GpioValue_High }, + { DeviceCode_ButtonSleep2, Direction_Input, GpioValue_Low }, + { DeviceCode_ButtonVolUp, Direction_Input, GpioValue_High }, + { DeviceCode_ButtonVolDn, Direction_Input, GpioValue_High }, + { DeviceCode_RecoveryKey, Direction_Input, GpioValue_High }, + { DeviceCode_PdRstN, Direction_Output, GpioValue_Low }, + { DeviceCode_SdCd, Direction_Input, GpioValue_High }, + { DeviceCode_SdWp, Direction_Input, GpioValue_High }, + { DeviceCode_BtGpio2, Direction_Input, GpioValue_Low }, + { DeviceCode_BtGpio3, Direction_Input, GpioValue_Low }, + { DeviceCode_BtGpio4, Direction_Input, GpioValue_Low }, + { DeviceCode_CradleIrq, Direction_Input, GpioValue_High }, + { DeviceCode_PowVcpuInt, Direction_Input, GpioValue_High }, + { DeviceCode_Hdmi5VEn, Direction_Output, GpioValue_Low }, + { DeviceCode_UsbSwitchB1Oc, Direction_Input, GpioValue_High }, + { DeviceCode_HdmiPdTrEn, Direction_Output, GpioValue_Low }, + { DeviceCode_WifiRfDisable, Direction_Output, GpioValue_Low }, + { DeviceCode_WifiReset, Direction_Output, GpioValue_Low }, + { DeviceCode_WifiWakeHost, Direction_Input, GpioValue_Low }, + { DeviceCode_ApWakeBt, Direction_Output, GpioValue_Low }, + { DeviceCode_BtRst, Direction_Output, GpioValue_Low }, + { DeviceCode_BtWakeAp, Direction_Input, GpioValue_Low }, + { DeviceCode_BtGpio5, Direction_Output, GpioValue_Low }, + { DeviceCode_UsbSwitchB1En, Direction_Output, GpioValue_Low }, + { DeviceCode_HdmiHpd, Direction_Input, GpioValue_Low }, +}; + +constexpr inline size_t NumInitialGpioConfigsCalcio = util::size(InitialGpioConfigsCalcio); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config_hoag.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config_hoag.inc new file mode 100644 index 00000000..cf7d10e0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config_hoag.inc @@ -0,0 +1,82 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by gpio_pad_gen.py, do not edit manually. */ + +constexpr inline const GpioInitialConfig InitialGpioConfigsHoag[] = { + { DeviceCode_GameCardReset, Direction_Output, GpioValue_Low }, + { DeviceCode_CodecAlert, Direction_Input, GpioValue_Low }, + { DeviceCode_Debug0, Direction_Output, GpioValue_Low }, + { DeviceCode_Debug1, Direction_Output, GpioValue_Low }, + { DeviceCode_Debug2, Direction_Output, GpioValue_Low }, + { DeviceCode_Debug3, Direction_Output, GpioValue_Low }, + { DeviceCode_PowSdEn, Direction_Output, GpioValue_Low }, + { DeviceCode_ExtConWakeS, Direction_Input, GpioValue_Low }, + { DeviceCode_McuIrq, Direction_Input, GpioValue_High }, + { DeviceCode_GameCardCd, Direction_Input, GpioValue_High }, + { DeviceCode_BattChgStatus, Direction_Input, GpioValue_Low }, + { DeviceCode_BattChgEnableN, Direction_Output, GpioValue_Low }, + { DeviceCode_FanTach, Direction_Input, GpioValue_Low }, + { DeviceCode_McuBoot, Direction_Output, GpioValue_Low }, + { DeviceCode_McuRst, Direction_Output, GpioValue_Low }, + { DeviceCode_Vdd50AEn, Direction_Output, GpioValue_Low }, + { DeviceCode_Vdd5V3En, Direction_Output, GpioValue_Low }, + { DeviceCode_TempAlert, Direction_Input, GpioValue_High }, + { DeviceCode_MotionInt, Direction_Input, GpioValue_Low }, + { DeviceCode_CodecHpDetIrq, Direction_Input, GpioValue_Low }, + { DeviceCode_TpIrq, Direction_Input, GpioValue_High }, + { DeviceCode_ButtonSleep2, Direction_Input, GpioValue_Low }, + { DeviceCode_ButtonVolUp, Direction_Input, GpioValue_High }, + { DeviceCode_ButtonVolDn, Direction_Input, GpioValue_High }, + { DeviceCode_BattMgicIrq, Direction_Input, GpioValue_Low }, + { DeviceCode_RecoveryKey, Direction_Input, GpioValue_High }, + { DeviceCode_PowLcdBlEn, Direction_Output, GpioValue_Low }, + { DeviceCode_LcdReset, Direction_Output, GpioValue_Low }, + { DeviceCode_LcdGpio1, Direction_Input, GpioValue_High }, + { DeviceCode_PdRstN, Direction_Output, GpioValue_Low }, + { DeviceCode_Bq24190Irq, Direction_Input, GpioValue_Low }, + { DeviceCode_SdCd, Direction_Input, GpioValue_High }, + { DeviceCode_SdWp, Direction_Input, GpioValue_High }, + { DeviceCode_CodecLdoEnTemp, Direction_Output, GpioValue_Low }, + { DeviceCode_NfcEn, Direction_Output, GpioValue_Low }, + { DeviceCode_NfcIrq, Direction_Input, GpioValue_Low }, + { DeviceCode_TpReset, Direction_Output, GpioValue_Low }, + { DeviceCode_BtGpio2, Direction_Input, GpioValue_Low }, + { DeviceCode_BtGpio3, Direction_Input, GpioValue_Low }, + { DeviceCode_BtGpio4, Direction_Input, GpioValue_Low }, + { DeviceCode_CradleIrq, Direction_Input, GpioValue_High }, + { DeviceCode_PdVconnEn, Direction_Output, GpioValue_Low }, + { DeviceCode_PowVcpuInt, Direction_Input, GpioValue_High }, + { DeviceCode_NfcRst, Direction_Output, GpioValue_Low }, + { DeviceCode_GpioPortC7, Direction_Input, GpioValue_Low }, + { DeviceCode_GpioPortD0, Direction_Input, GpioValue_Low }, + { DeviceCode_GpioPortC5, Direction_Input, GpioValue_Low }, + { DeviceCode_GpioPortC6, Direction_Input, GpioValue_Low }, + { DeviceCode_GpioPortY7, Direction_Output, GpioValue_Low }, + { DeviceCode_WifiRfDisable, Direction_Output, GpioValue_Low }, + { DeviceCode_WifiReset, Direction_Output, GpioValue_Low }, + { DeviceCode_WifiWakeHost, Direction_Input, GpioValue_Low }, + { DeviceCode_ApWakeBt, Direction_Output, GpioValue_Low }, + { DeviceCode_BtRst, Direction_Output, GpioValue_Low }, + { DeviceCode_BtWakeAp, Direction_Input, GpioValue_Low }, + { DeviceCode_ExtConWakeU, Direction_Input, GpioValue_Low }, + { DeviceCode_BtGpio5, Direction_Output, GpioValue_Low }, + { DeviceCode_PowLcdVddPEn, Direction_Output, GpioValue_Low }, + { DeviceCode_PowLcdVddNEn, Direction_Output, GpioValue_Low }, + { DeviceCode_McuPor, Direction_Output, GpioValue_Low }, +}; + +constexpr inline size_t NumInitialGpioConfigsHoag = util::size(InitialGpioConfigsHoag); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config_icosa.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config_icosa.inc new file mode 100644 index 00000000..2a05f008 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config_icosa.inc @@ -0,0 +1,82 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by gpio_pad_gen.py, do not edit manually. */ + +constexpr inline const GpioInitialConfig InitialGpioConfigsIcosa[] = { + { DeviceCode_RamCode3, Direction_Input, GpioValue_High }, + { DeviceCode_GameCardReset, Direction_Output, GpioValue_Low }, + { DeviceCode_CodecAlert, Direction_Input, GpioValue_Low }, + { DeviceCode_PowSdEn, Direction_Output, GpioValue_Low }, + { DeviceCode_PowGc, Direction_Output, GpioValue_Low }, + { DeviceCode_ExtConWakeS, Direction_Input, GpioValue_Low }, + { DeviceCode_GameCardCd, Direction_Input, GpioValue_High }, + { DeviceCode_DebugControllerDet, Direction_Input, GpioValue_Low }, + { DeviceCode_BattChgStatus, Direction_Input, GpioValue_Low }, + { DeviceCode_BattChgEnableN, Direction_Output, GpioValue_Low }, + { DeviceCode_FanTach, Direction_Input, GpioValue_High }, + { DeviceCode_Vdd50AEn, Direction_Output, GpioValue_Low }, + { DeviceCode_SdevCoaxSel1, Direction_Input, GpioValue_Low }, + { DeviceCode_ProdType0, Direction_Input, GpioValue_Low }, + { DeviceCode_ProdType1, Direction_Input, GpioValue_Low }, + { DeviceCode_ProdType2, Direction_Input, GpioValue_Low }, + { DeviceCode_ProdType3, Direction_Input, GpioValue_Low }, + { DeviceCode_TempAlert, Direction_Input, GpioValue_High }, + { DeviceCode_MotionInt, Direction_Input, GpioValue_Low }, + { DeviceCode_CodecHpDetIrq, Direction_Input, GpioValue_Low }, + { DeviceCode_TpIrq, Direction_Input, GpioValue_High }, + { DeviceCode_ButtonSleep2, Direction_Input, GpioValue_High }, + { DeviceCode_ButtonVolUp, Direction_Input, GpioValue_High }, + { DeviceCode_ButtonVolDn, Direction_Input, GpioValue_High }, + { DeviceCode_BattMgicIrq, Direction_Input, GpioValue_High }, + { DeviceCode_RecoveryKey, Direction_Input, GpioValue_Low }, + { DeviceCode_PowLcdBlEn, Direction_Output, GpioValue_Low }, + { DeviceCode_LcdReset, Direction_Output, GpioValue_Low }, + { DeviceCode_PdRstN, Direction_Output, GpioValue_Low }, + { DeviceCode_Bq24190Irq, Direction_Input, GpioValue_High }, + { DeviceCode_SdCd, Direction_Input, GpioValue_High }, + { DeviceCode_SdevCoaxSel0, Direction_Input, GpioValue_Low }, + { DeviceCode_SdWp, Direction_Input, GpioValue_High }, + { DeviceCode_CodecLdoEnTemp, Direction_Output, GpioValue_Low }, + { DeviceCode_OtgFet1ForSdev, Direction_Output, GpioValue_Low }, + { DeviceCode_TpReset, Direction_Output, GpioValue_Low }, + { DeviceCode_ExtconDetU, Direction_Input, GpioValue_Low }, + { DeviceCode_BtGpio2, Direction_Input, GpioValue_Low }, + { DeviceCode_BtGpio3, Direction_Input, GpioValue_Low }, + { DeviceCode_BtGpio4, Direction_Input, GpioValue_Low }, + { DeviceCode_ExtconChgU, Direction_Output, GpioValue_Low }, + { DeviceCode_CradleIrq, Direction_Input, GpioValue_High }, + { DeviceCode_PdVconnEn, Direction_Output, GpioValue_Low }, + { DeviceCode_PowVcpuInt, Direction_Input, GpioValue_High }, + { DeviceCode_Max77621GpuInt, Direction_Input, GpioValue_High }, + { DeviceCode_OtgFet2ForSdev, Direction_Output, GpioValue_Low }, + { DeviceCode_ExtconDetS, Direction_Input, GpioValue_Low }, + { DeviceCode_WifiRfDisable, Direction_Output, GpioValue_Low }, + { DeviceCode_WifiReset, Direction_Output, GpioValue_Low }, + { DeviceCode_WifiWakeHost, Direction_Input, GpioValue_Low }, + { DeviceCode_ApWakeBt, Direction_Output, GpioValue_Low }, + { DeviceCode_BtRst, Direction_Output, GpioValue_Low }, + { DeviceCode_BtWakeAp, Direction_Input, GpioValue_Low }, + { DeviceCode_ExtConWakeU, Direction_Input, GpioValue_Low }, + { DeviceCode_BtGpio5, Direction_Output, GpioValue_Low }, + { DeviceCode_PowLcdVddPEn, Direction_Output, GpioValue_Low }, + { DeviceCode_PowLcdVddNEn, Direction_Output, GpioValue_Low }, + { DeviceCode_RamCode2, Direction_Input, GpioValue_High }, + { DeviceCode_ExtconChgS, Direction_Output, GpioValue_Low }, + { DeviceCode_Vdd50BEn, Direction_Output, GpioValue_Low }, +}; + +constexpr inline size_t NumInitialGpioConfigsIcosa = util::size(InitialGpioConfigsIcosa); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config_iowa.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config_iowa.inc new file mode 100644 index 00000000..c61f511c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_config_iowa.inc @@ -0,0 +1,81 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by gpio_pad_gen.py, do not edit manually. */ + +constexpr inline const GpioInitialConfig InitialGpioConfigsIowa[] = { + { DeviceCode_RamCode3, Direction_Input, GpioValue_High }, + { DeviceCode_GameCardReset, Direction_Output, GpioValue_Low }, + { DeviceCode_CodecAlert, Direction_Input, GpioValue_Low }, + { DeviceCode_PowSdEn, Direction_Output, GpioValue_Low }, + { DeviceCode_ExtConWakeS, Direction_Input, GpioValue_Low }, + { DeviceCode_GameCardCd, Direction_Input, GpioValue_High }, + { DeviceCode_DebugControllerDet, Direction_Input, GpioValue_Low }, + { DeviceCode_BattChgStatus, Direction_Input, GpioValue_Low }, + { DeviceCode_BattChgEnableN, Direction_Output, GpioValue_Low }, + { DeviceCode_FanTach, Direction_Input, GpioValue_Low }, + { DeviceCode_Vdd50AEn, Direction_Output, GpioValue_Low }, + { DeviceCode_SdevCoaxSel1, Direction_Input, GpioValue_Low }, + { DeviceCode_ProdType0, Direction_Input, GpioValue_Low }, + { DeviceCode_ProdType1, Direction_Input, GpioValue_Low }, + { DeviceCode_ProdType2, Direction_Input, GpioValue_Low }, + { DeviceCode_ProdType3, Direction_Input, GpioValue_Low }, + { DeviceCode_Vdd5V3En, Direction_Output, GpioValue_Low }, + { DeviceCode_TempAlert, Direction_Input, GpioValue_High }, + { DeviceCode_MotionInt, Direction_Input, GpioValue_Low }, + { DeviceCode_CodecHpDetIrq, Direction_Input, GpioValue_Low }, + { DeviceCode_TpIrq, Direction_Input, GpioValue_High }, + { DeviceCode_ButtonSleep2, Direction_Input, GpioValue_Low }, + { DeviceCode_ButtonVolUp, Direction_Input, GpioValue_High }, + { DeviceCode_ButtonVolDn, Direction_Input, GpioValue_High }, + { DeviceCode_BattMgicIrq, Direction_Input, GpioValue_Low }, + { DeviceCode_RecoveryKey, Direction_Input, GpioValue_Low }, + { DeviceCode_PowLcdBlEn, Direction_Output, GpioValue_Low }, + { DeviceCode_LcdReset, Direction_Output, GpioValue_Low }, + { DeviceCode_PdRstN, Direction_Output, GpioValue_Low }, + { DeviceCode_Bq24190Irq, Direction_Input, GpioValue_Low }, + { DeviceCode_SdCd, Direction_Input, GpioValue_High }, + { DeviceCode_SdevCoaxSel0, Direction_Input, GpioValue_Low }, + { DeviceCode_SdWp, Direction_Input, GpioValue_High }, + { DeviceCode_CodecLdoEnTemp, Direction_Output, GpioValue_Low }, + { DeviceCode_OtgFet1ForSdev, Direction_Output, GpioValue_Low }, + { DeviceCode_TpReset, Direction_Output, GpioValue_Low }, + { DeviceCode_ExtconDetU, Direction_Input, GpioValue_Low }, + { DeviceCode_BtGpio2, Direction_Input, GpioValue_Low }, + { DeviceCode_BtGpio3, Direction_Input, GpioValue_Low }, + { DeviceCode_BtGpio4, Direction_Input, GpioValue_Low }, + { DeviceCode_ExtconChgU, Direction_Output, GpioValue_Low }, + { DeviceCode_CradleIrq, Direction_Input, GpioValue_High }, + { DeviceCode_PdVconnEn, Direction_Output, GpioValue_Low }, + { DeviceCode_PowVcpuInt, Direction_Input, GpioValue_High }, + { DeviceCode_OtgFet2ForSdev, Direction_Output, GpioValue_Low }, + { DeviceCode_ExtconDetS, Direction_Input, GpioValue_Low }, + { DeviceCode_WifiRfDisable, Direction_Output, GpioValue_Low }, + { DeviceCode_WifiReset, Direction_Output, GpioValue_Low }, + { DeviceCode_WifiWakeHost, Direction_Input, GpioValue_Low }, + { DeviceCode_ApWakeBt, Direction_Output, GpioValue_Low }, + { DeviceCode_BtRst, Direction_Output, GpioValue_Low }, + { DeviceCode_BtWakeAp, Direction_Input, GpioValue_Low }, + { DeviceCode_ExtConWakeU, Direction_Input, GpioValue_Low }, + { DeviceCode_BtGpio5, Direction_Output, GpioValue_Low }, + { DeviceCode_PowLcdVddPEn, Direction_Output, GpioValue_Low }, + { DeviceCode_PowLcdVddNEn, Direction_Output, GpioValue_Low }, + { DeviceCode_RamCode2, Direction_Input, GpioValue_High }, + { DeviceCode_ExtconChgS, Direction_Output, GpioValue_Low }, + { DeviceCode_Vdd50BEn, Direction_Output, GpioValue_Low }, +}; + +constexpr inline size_t NumInitialGpioConfigsIowa = util::size(InitialGpioConfigsIowa); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_wake_pin_config_aula.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_wake_pin_config_aula.inc new file mode 100644 index 00000000..06511a20 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_wake_pin_config_aula.inc @@ -0,0 +1,80 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by wake_pin_gen.py, do not edit manually. */ + +constexpr inline const WakePinConfig InitialWakePinConfigsAula[] = { + { ams::wec::WakeEvent_PexWakeN, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortA6, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_QspiCsN, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Spi2Mosi, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ExtconDetS, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_McuIrq, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Uart2Cts, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Uart3Cts, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_WifiWakeAp, true, ams::wec::WakeEventLevel_High }, + /* { ams::wec::WakeEvent_AoTag2Pmc, }, */ + { ams::wec::WakeEvent_ExtconDetU, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_NfcInt, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Gen1I2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Gen2I2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_CradleIrq, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_GpioPortK6, false, ams::wec::WakeEventLevel_Auto }, + /* { ams::wec::WakeEvent_RtcIrq, }, */ + { ams::wec::WakeEvent_Sdmmc1Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Sdmmc2Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_HdmiCec, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Gen3I2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortL1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Clk_32kOut, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_PwrI2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonPowerOn, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonVolUp, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonVolDown, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonSlideSw, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_ButtonHome, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_AlsProxInt, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_TempAlert, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Bq24190Irq, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_SdCd, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortZ2, false, ams::wec::WakeEventLevel_Auto }, + /* { ams::wec::WakeEvent_Utmip0, }, */ + /* { ams::wec::WakeEvent_Utmip1, }, */ + /* { ams::wec::WakeEvent_Utmip2, }, */ + /* { ams::wec::WakeEvent_Utmip3, }, */ + /* { ams::wec::WakeEvent_Uhsic, }, */ + /* { ams::wec::WakeEvent_Wake2PmcXusbSystem, }, */ + { ams::wec::WakeEvent_Sdmmc3Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Sdmmc4Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_CamI2cScl, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_CamI2cSda, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortZ5, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_DpHpd0, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_PwrIntN, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_BtWakeAp, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_HdmiIntDpHpd, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_UsbVbusEn0, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_UsbVbusEn1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_LcdRst, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_LcdGpio1, false, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_LcdGpio2, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Uart4Cts, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ModemWakeAp, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_TouchInt, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_MotionInt, false, ams::wec::WakeEventLevel_Auto }, +}; + +constexpr inline size_t NumInitialWakePinConfigsAula = util::size(InitialWakePinConfigsAula); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_wake_pin_config_calcio.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_wake_pin_config_calcio.inc new file mode 100644 index 00000000..eb8cc00d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_wake_pin_config_calcio.inc @@ -0,0 +1,80 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by wake_pin_gen.py, do not edit manually. */ + +constexpr inline const WakePinConfig InitialWakePinConfigsCalcio[] = { + { ams::wec::WakeEvent_PexWakeN, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortA6, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_QspiCsN, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Spi2Mosi, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ExtconDetS, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_McuIrq, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Uart2Cts, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Uart3Cts, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_WifiWakeAp, true, ams::wec::WakeEventLevel_High }, + /* { ams::wec::WakeEvent_AoTag2Pmc, }, */ + { ams::wec::WakeEvent_ExtconDetU, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_NfcInt, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Gen1I2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Gen2I2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_CradleIrq, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_GpioPortK6, false, ams::wec::WakeEventLevel_Auto }, + /* { ams::wec::WakeEvent_RtcIrq, }, */ + { ams::wec::WakeEvent_Sdmmc1Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Sdmmc2Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_HdmiCec, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Gen3I2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortL1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Clk_32kOut, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_PwrI2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonPowerOn, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonVolUp, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonVolDown, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonSlideSw, false, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_ButtonHome, false, ams::wec::WakeEventLevel_Auto }, + /* { ams::wec::WakeEvent_AlsProxInt, }, */ + { ams::wec::WakeEvent_TempAlert, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Bq24190Irq, false, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_SdCd, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortZ2, false, ams::wec::WakeEventLevel_Auto }, + /* { ams::wec::WakeEvent_Utmip0, }, */ + /* { ams::wec::WakeEvent_Utmip1, }, */ + /* { ams::wec::WakeEvent_Utmip2, }, */ + /* { ams::wec::WakeEvent_Utmip3, }, */ + /* { ams::wec::WakeEvent_Uhsic, }, */ + /* { ams::wec::WakeEvent_Wake2PmcXusbSystem, }, */ + { ams::wec::WakeEvent_Sdmmc3Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Sdmmc4Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_CamI2cScl, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_CamI2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortZ5, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_DpHpd0, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_PwrIntN, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_BtWakeAp, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_HdmiIntDpHpd, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_UsbVbusEn0, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_UsbVbusEn1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_LcdRst, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_LcdGpio1, false, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_LcdGpio2, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Uart4Cts, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ModemWakeAp, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_TouchInt, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_MotionInt, false, ams::wec::WakeEventLevel_Auto }, +}; + +constexpr inline size_t NumInitialWakePinConfigsCalcio = util::size(InitialWakePinConfigsCalcio); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_wake_pin_config_hoag.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_wake_pin_config_hoag.inc new file mode 100644 index 00000000..48b97ed9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_wake_pin_config_hoag.inc @@ -0,0 +1,80 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by wake_pin_gen.py, do not edit manually. */ + +constexpr inline const WakePinConfig InitialWakePinConfigsHoag[] = { + { ams::wec::WakeEvent_PexWakeN, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortA6, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_QspiCsN, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Spi2Mosi, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ExtconDetS, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_McuIrq, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Uart2Cts, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Uart3Cts, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_WifiWakeAp, true, ams::wec::WakeEventLevel_High }, + /* { ams::wec::WakeEvent_AoTag2Pmc, }, */ + { ams::wec::WakeEvent_ExtconDetU, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_NfcInt, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Gen1I2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Gen2I2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_CradleIrq, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_GpioPortK6, false, ams::wec::WakeEventLevel_Auto }, + /* { ams::wec::WakeEvent_RtcIrq, }, */ + { ams::wec::WakeEvent_Sdmmc1Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Sdmmc2Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_HdmiCec, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Gen3I2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortL1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Clk_32kOut, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_PwrI2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonPowerOn, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonVolUp, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonVolDown, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonSlideSw, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_ButtonHome, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_AlsProxInt, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_TempAlert, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Bq24190Irq, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_SdCd, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortZ2, false, ams::wec::WakeEventLevel_Auto }, + /* { ams::wec::WakeEvent_Utmip0, }, */ + /* { ams::wec::WakeEvent_Utmip1, }, */ + /* { ams::wec::WakeEvent_Utmip2, }, */ + /* { ams::wec::WakeEvent_Utmip3, }, */ + /* { ams::wec::WakeEvent_Uhsic, }, */ + /* { ams::wec::WakeEvent_Wake2PmcXusbSystem, }, */ + { ams::wec::WakeEvent_Sdmmc3Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Sdmmc4Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_CamI2cScl, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_CamI2cSda, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortZ5, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_DpHpd0, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_PwrIntN, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_BtWakeAp, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_HdmiIntDpHpd, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_UsbVbusEn0, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_UsbVbusEn1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_LcdRst, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_LcdGpio1, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_LcdGpio2, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Uart4Cts, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ModemWakeAp, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_TouchInt, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_MotionInt, false, ams::wec::WakeEventLevel_Auto }, +}; + +constexpr inline size_t NumInitialWakePinConfigsHoag = util::size(InitialWakePinConfigsHoag); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_wake_pin_config_icosa.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_wake_pin_config_icosa.inc new file mode 100644 index 00000000..e6b6c840 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_wake_pin_config_icosa.inc @@ -0,0 +1,80 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by wake_pin_gen.py, do not edit manually. */ + +constexpr inline const WakePinConfig InitialWakePinConfigsIcosa[] = { + { ams::wec::WakeEvent_PexWakeN, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortA6, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_QspiCsN, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Spi2Mosi, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ExtconDetS, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_McuIrq, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Uart2Cts, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Uart3Cts, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_WifiWakeAp, true, ams::wec::WakeEventLevel_High }, + /* { ams::wec::WakeEvent_AoTag2Pmc, }, */ + { ams::wec::WakeEvent_ExtconDetU, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_NfcInt, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Gen1I2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Gen2I2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_CradleIrq, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_GpioPortK6, false, ams::wec::WakeEventLevel_Auto }, + /* { ams::wec::WakeEvent_RtcIrq, }, */ + { ams::wec::WakeEvent_Sdmmc1Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Sdmmc2Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_HdmiCec, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Gen3I2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortL1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Clk_32kOut, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_PwrI2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonPowerOn, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonVolUp, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonVolDown, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonSlideSw, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_ButtonHome, false, ams::wec::WakeEventLevel_Auto }, + /* { ams::wec::WakeEvent_AlsProxInt, }, */ + { ams::wec::WakeEvent_TempAlert, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Bq24190Irq, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_SdCd, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortZ2, false, ams::wec::WakeEventLevel_Auto }, + /* { ams::wec::WakeEvent_Utmip0, }, */ + /* { ams::wec::WakeEvent_Utmip1, }, */ + /* { ams::wec::WakeEvent_Utmip2, }, */ + /* { ams::wec::WakeEvent_Utmip3, }, */ + /* { ams::wec::WakeEvent_Uhsic, }, */ + /* { ams::wec::WakeEvent_Wake2PmcXusbSystem, }, */ + { ams::wec::WakeEvent_Sdmmc3Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Sdmmc4Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_CamI2cScl, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_CamI2cSda, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortZ5, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_DpHpd0, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_PwrIntN, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_BtWakeAp, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_HdmiIntDpHpd, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_UsbVbusEn0, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_UsbVbusEn1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_LcdRst, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_LcdGpio1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_LcdGpio2, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Uart4Cts, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ModemWakeAp, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_TouchInt, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_MotionInt, false, ams::wec::WakeEventLevel_Auto }, +}; + +constexpr inline size_t NumInitialWakePinConfigsIcosa = util::size(InitialWakePinConfigsIcosa); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_wake_pin_config_iowa.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_wake_pin_config_iowa.inc new file mode 100644 index 00000000..068d8fa0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_initial_wake_pin_config_iowa.inc @@ -0,0 +1,80 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by wake_pin_gen.py, do not edit manually. */ + +constexpr inline const WakePinConfig InitialWakePinConfigsIowa[] = { + { ams::wec::WakeEvent_PexWakeN, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortA6, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_QspiCsN, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Spi2Mosi, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ExtconDetS, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_McuIrq, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Uart2Cts, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Uart3Cts, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_WifiWakeAp, true, ams::wec::WakeEventLevel_High }, + /* { ams::wec::WakeEvent_AoTag2Pmc, }, */ + { ams::wec::WakeEvent_ExtconDetU, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_NfcInt, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Gen1I2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Gen2I2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_CradleIrq, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_GpioPortK6, false, ams::wec::WakeEventLevel_Auto }, + /* { ams::wec::WakeEvent_RtcIrq, }, */ + { ams::wec::WakeEvent_Sdmmc1Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Sdmmc2Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_HdmiCec, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Gen3I2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortL1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Clk_32kOut, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_PwrI2cSda, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonPowerOn, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonVolUp, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonVolDown, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ButtonSlideSw, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_ButtonHome, false, ams::wec::WakeEventLevel_Auto }, + /* { ams::wec::WakeEvent_AlsProxInt, }, */ + { ams::wec::WakeEvent_TempAlert, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Bq24190Irq, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_SdCd, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortZ2, false, ams::wec::WakeEventLevel_Auto }, + /* { ams::wec::WakeEvent_Utmip0, }, */ + /* { ams::wec::WakeEvent_Utmip1, }, */ + /* { ams::wec::WakeEvent_Utmip2, }, */ + /* { ams::wec::WakeEvent_Utmip3, }, */ + /* { ams::wec::WakeEvent_Uhsic, }, */ + /* { ams::wec::WakeEvent_Wake2PmcXusbSystem, }, */ + { ams::wec::WakeEvent_Sdmmc3Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Sdmmc4Dat1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_CamI2cScl, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_CamI2cSda, true, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_GpioPortZ5, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_DpHpd0, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_PwrIntN, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_BtWakeAp, true, ams::wec::WakeEventLevel_Low }, + { ams::wec::WakeEvent_HdmiIntDpHpd, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_UsbVbusEn0, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_UsbVbusEn1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_LcdRst, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_LcdGpio1, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_LcdGpio2, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_Uart4Cts, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_ModemWakeAp, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_TouchInt, false, ams::wec::WakeEventLevel_Auto }, + { ams::wec::WakeEvent_MotionInt, false, ams::wec::WakeEventLevel_Auto }, +}; + +constexpr inline size_t NumInitialWakePinConfigsIowa = util::size(InitialWakePinConfigsIowa); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_internal_pad_map_combination.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_internal_pad_map_combination.inc new file mode 100644 index 00000000..6b72a209 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_internal_pad_map_combination.inc @@ -0,0 +1,113 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by gpio_pad_gen.py, do not edit manually. */ + +constexpr inline const PadMapCombination PadMapCombinationList[] = { + { DeviceCode_CodecLdoEnTemp, InternalGpioPadNumber_Port_Z_4, ams::wec::WakeEvent_None }, + { DeviceCode_PowSdEn, InternalGpioPadNumber_Port_E_4, ams::wec::WakeEvent_None }, + { DeviceCode_BtRst, InternalGpioPadNumber_Port_H_4, ams::wec::WakeEvent_None }, + { DeviceCode_RamCode3, InternalGpioPadNumber_Port_BB_2, ams::wec::WakeEvent_None }, + { DeviceCode_GameCardReset, InternalGpioPadNumber_Port_BB_3, ams::wec::WakeEvent_None }, + { DeviceCode_CodecAlert, InternalGpioPadNumber_Port_BB_4, ams::wec::WakeEvent_None }, + { DeviceCode_PowGc, InternalGpioPadNumber_Port_E_5, ams::wec::WakeEvent_None }, + { DeviceCode_DebugControllerDet, InternalGpioPadNumber_Port_S_0, ams::wec::WakeEvent_None }, + { DeviceCode_BattChgStatus, InternalGpioPadNumber_Port_S_1, ams::wec::WakeEvent_None }, + { DeviceCode_BattChgEnableN, InternalGpioPadNumber_Port_S_6, ams::wec::WakeEvent_None }, + { DeviceCode_FanTach, InternalGpioPadNumber_Port_S_7, ams::wec::WakeEvent_None }, + { DeviceCode_Vdd50AEn, InternalGpioPadNumber_Port_A_5, ams::wec::WakeEvent_None }, + { DeviceCode_SdevCoaxSel1, InternalGpioPadNumber_Port_P_0, ams::wec::WakeEvent_None }, + { DeviceCode_ProdType0, InternalGpioPadNumber_Port_P_5, ams::wec::WakeEvent_None }, + { DeviceCode_ProdType1, InternalGpioPadNumber_Port_P_4, ams::wec::WakeEvent_None }, + { DeviceCode_ProdType2, InternalGpioPadNumber_Port_P_3, ams::wec::WakeEvent_None }, + { DeviceCode_ProdType3, InternalGpioPadNumber_Port_P_2, ams::wec::WakeEvent_None }, + { DeviceCode_TempAlert, InternalGpioPadNumber_Port_X_4, ams::wec::WakeEvent_None }, + { DeviceCode_CodecHpDetIrq, InternalGpioPadNumber_Port_V_6, ams::wec::WakeEvent_None }, + { DeviceCode_MotionInt, InternalGpioPadNumber_Port_X_2, ams::wec::WakeEvent_None }, + { DeviceCode_TpIrq, InternalGpioPadNumber_Port_X_1, ams::wec::WakeEvent_None }, + { DeviceCode_ButtonSleep2, InternalGpioPadNumber_Port_X_5, ams::wec::WakeEvent_None }, + { DeviceCode_ButtonVolUp, InternalGpioPadNumber_Port_X_6, ams::wec::WakeEvent_None }, + { DeviceCode_ButtonVolDn, InternalGpioPadNumber_Port_X_7, ams::wec::WakeEvent_None }, + { DeviceCode_RecoveryKey, InternalGpioPadNumber_Port_Y_1, ams::wec::WakeEvent_None }, + { DeviceCode_PowLcdBlEn, InternalGpioPadNumber_Port_V_1, ams::wec::WakeEvent_None }, + { DeviceCode_LcdReset, InternalGpioPadNumber_Port_V_2, ams::wec::WakeEvent_None }, + { DeviceCode_PdVconnEn, InternalGpioPadNumber_Port_K_5, ams::wec::WakeEvent_None }, + { DeviceCode_PdRstN, InternalGpioPadNumber_Port_V_5, ams::wec::WakeEvent_None }, + { DeviceCode_SdevCoaxSel0, InternalGpioPadNumber_Port_Z_2, ams::wec::WakeEvent_None }, + { DeviceCode_SdWp, InternalGpioPadNumber_Port_Z_3, ams::wec::WakeEvent_None }, + { DeviceCode_TpReset, InternalGpioPadNumber_Port_J_7, ams::wec::WakeEvent_None }, + { DeviceCode_BtGpio2, InternalGpioPadNumber_Port_K_0, ams::wec::WakeEvent_None }, + { DeviceCode_BtGpio3, InternalGpioPadNumber_Port_K_1, ams::wec::WakeEvent_None }, + { DeviceCode_BtGpio4, InternalGpioPadNumber_Port_K_2, ams::wec::WakeEvent_None }, + { DeviceCode_PowVcpuInt, InternalGpioPadNumber_Port_K_6, ams::wec::WakeEvent_None }, + { DeviceCode_Max77621GpuInt, InternalGpioPadNumber_Port_K_7, ams::wec::WakeEvent_None }, + { DeviceCode_ExtconChgU, InternalGpioPadNumber_Port_K_3, ams::wec::WakeEvent_None }, + { DeviceCode_ExtconChgS, InternalGpioPadNumber_Port_CC_3, ams::wec::WakeEvent_None }, + { DeviceCode_WifiRfDisable, InternalGpioPadNumber_Port_H_0, ams::wec::WakeEvent_None }, + { DeviceCode_WifiReset, InternalGpioPadNumber_Port_H_1, ams::wec::WakeEvent_None }, + { DeviceCode_ApWakeBt, InternalGpioPadNumber_Port_H_3, ams::wec::WakeEvent_None }, + { DeviceCode_BtGpio5, InternalGpioPadNumber_Port_H_7, ams::wec::WakeEvent_None }, + { DeviceCode_PowLcdVddPEn, InternalGpioPadNumber_Port_I_0, ams::wec::WakeEvent_None }, + { DeviceCode_PowLcdVddNEn, InternalGpioPadNumber_Port_I_1, ams::wec::WakeEvent_None }, + { DeviceCode_RamCode2, InternalGpioPadNumber_Port_CC_2, ams::wec::WakeEvent_None }, + { DeviceCode_Vdd50BEn, InternalGpioPadNumber_Port_CC_4, ams::wec::WakeEvent_None }, + { DeviceCode_OtgFet1ForSdev, InternalGpioPadNumber_Port_J_5, ams::wec::WakeEvent_None }, + { DeviceCode_OtgFet2ForSdev, InternalGpioPadNumber_Port_L_0, ams::wec::WakeEvent_None }, + { DeviceCode_ExtConWakeU, InternalGpioPadNumber_Port_H_6, ams::wec::WakeEvent_None }, + { DeviceCode_ExtConWakeS, InternalGpioPadNumber_Port_E_6, ams::wec::WakeEvent_None }, + { DeviceCode_ExtUart2Rts, InternalGpioPadNumber_Port_G_2, ams::wec::WakeEvent_None }, + { DeviceCode_ExtUart3Rts, InternalGpioPadNumber_Port_D_3, ams::wec::WakeEvent_None }, + { DeviceCode_Debug0, InternalGpioPadNumber_Port_E_0, ams::wec::WakeEvent_None }, + { DeviceCode_Debug1, InternalGpioPadNumber_Port_E_1, ams::wec::WakeEvent_None }, + { DeviceCode_Debug2, InternalGpioPadNumber_Port_E_2, ams::wec::WakeEvent_None }, + { DeviceCode_Debug3, InternalGpioPadNumber_Port_E_3, ams::wec::WakeEvent_None }, + { DeviceCode_NfcIrq, InternalGpioPadNumber_Port_J_4, ams::wec::WakeEvent_None }, + { DeviceCode_NfcRst, InternalGpioPadNumber_Port_K_7, ams::wec::WakeEvent_None }, + { DeviceCode_McuIrq, InternalGpioPadNumber_Port_E_7, ams::wec::WakeEvent_McuIrq }, + { DeviceCode_McuBoot, InternalGpioPadNumber_Port_T_0, ams::wec::WakeEvent_None }, + { DeviceCode_McuRst, InternalGpioPadNumber_Port_T_1, ams::wec::WakeEvent_None }, + { DeviceCode_Vdd5V3En, InternalGpioPadNumber_Port_X_3, ams::wec::WakeEvent_None }, + { DeviceCode_McuPor, InternalGpioPadNumber_Port_CC_5, ams::wec::WakeEvent_None }, + { DeviceCode_NfcEn, InternalGpioPadNumber_Port_J_6, ams::wec::WakeEvent_None }, + { DeviceCode_GpioPortC7, InternalGpioPadNumber_Port_C_7, ams::wec::WakeEvent_None }, + { DeviceCode_GpioPortD0, InternalGpioPadNumber_Port_D_0, ams::wec::WakeEvent_None }, + { DeviceCode_GpioPortC5, InternalGpioPadNumber_Port_C_5, ams::wec::WakeEvent_None }, + { DeviceCode_GpioPortC6, InternalGpioPadNumber_Port_C_6, ams::wec::WakeEvent_None }, + { DeviceCode_GpioPortY7, InternalGpioPadNumber_Port_Y_5, ams::wec::WakeEvent_None }, + { DeviceCode_Hdmi5VEn, InternalGpioPadNumber_Port_C_0, ams::wec::WakeEvent_None }, + { DeviceCode_UsbSwitchB1En, InternalGpioPadNumber_Port_C_1, ams::wec::WakeEvent_None }, + { DeviceCode_HdmiPdTrEn, InternalGpioPadNumber_Port_C_2, ams::wec::WakeEvent_None }, + { DeviceCode_UsbSwitchB1Oc, InternalGpioPadNumber_Port_CC_6, ams::wec::WakeEvent_None }, + { DeviceCode_HdmiHpd, InternalGpioPadNumber_Port_CC_1, ams::wec::WakeEvent_None }, + { DeviceCode_GpioPortF1, InternalGpioPadNumber_Port_F_1, ams::wec::WakeEvent_None }, + { DeviceCode_GpioPortH0, InternalGpioPadNumber_Port_H_0, ams::wec::WakeEvent_None }, + { DeviceCode_GpioPortI6, InternalGpioPadNumber_Port_I_6, ams::wec::WakeEvent_None }, + { DeviceCode_ExtconDetS, InternalGpioPadNumber_Port_E_6, ams::wec::WakeEvent_ExtconDetS }, + { DeviceCode_GameCardCd, InternalGpioPadNumber_Port_S_3, ams::wec::WakeEvent_CamI2cSda }, + { DeviceCode_BattMgicIrq, InternalGpioPadNumber_Port_Y_0, ams::wec::WakeEvent_ButtonSlideSw }, + { DeviceCode_Bq24190Irq, InternalGpioPadNumber_Port_Z_0, ams::wec::WakeEvent_Bq24190Irq }, + { DeviceCode_CradleIrq, InternalGpioPadNumber_Port_K_4, ams::wec::WakeEvent_CradleIrq }, + { DeviceCode_BtWakeAp, InternalGpioPadNumber_Port_H_5, ams::wec::WakeEvent_BtWakeAp }, + { DeviceCode_ExtconDetU, InternalGpioPadNumber_Port_H_6, ams::wec::WakeEvent_ExtconDetU }, + { DeviceCode_WifiWakeHost, InternalGpioPadNumber_Port_H_2, ams::wec::WakeEvent_WifiWakeAp }, + { DeviceCode_SdCd, InternalGpioPadNumber_Port_Z_1, ams::wec::WakeEvent_SdCd }, + { DeviceCode_ExtUart2Cts, InternalGpioPadNumber_Port_G_3, ams::wec::WakeEvent_Uart2Cts }, + { DeviceCode_ExtUart3Cts, InternalGpioPadNumber_Port_D_4, ams::wec::WakeEvent_Uart3Cts }, + { DeviceCode_LcdGpio1, InternalGpioPadNumber_Port_V_3, ams::wec::WakeEvent_LcdGpio1 }, + { DeviceCode_PmuIrq, InternalGpioPadNumber_None, ams::wec::WakeEvent_PwrIntN }, +}; + +constexpr inline size_t PadMapCombinationListSize = util::size(PadMapCombinationList); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_register_accessor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_register_accessor.hpp new file mode 100644 index 00000000..dd744d2a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_register_accessor.hpp @@ -0,0 +1,135 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#include "gpio_tegra_pad.hpp" + +namespace ams::gpio::driver::board::nintendo::nx::impl { + + constexpr inline dd::PhysicalAddress GpioRegistersPhysicalAddress = 0x6000D000; + constexpr inline size_t GpioRegistersSize = 4_KB; + + constexpr inline int GpioPerControllerBitWidth = 5; + constexpr inline int GpioControllerBitWidth = 3; + + constexpr inline int PortPerControllerBitWidth = 2; + constexpr inline int PortPerController = (1 << PortPerControllerBitWidth); + + constexpr inline int GpioPerPortBitWidth = 3; + constexpr inline int GpioPerPort = (1 << GpioPerPortBitWidth); + + static_assert(PortPerControllerBitWidth + GpioPerPortBitWidth == GpioPerControllerBitWidth); + static_assert(PortPerController * GpioPerPort == (1 << GpioPerControllerBitWidth)); + + constexpr int ConvertInternalGpioPadNumberToController(InternalGpioPadNumber number) { + return (number >> GpioPerControllerBitWidth); + } + + constexpr int ConvertInternalGpioPadNumberToPort(InternalGpioPadNumber number) { + return (number >> GpioControllerBitWidth); + } + + constexpr InternalGpioPadNumber ConvertPortToInternalGpioPadNumber(int port) { + return static_cast<InternalGpioPadNumber>(port << GpioControllerBitWidth); + } + + constexpr int ConvertInternalGpioPadNumberToBitIndex(InternalGpioPadNumber number) { + return (number & (GpioPerPort - 1)); + } + + constexpr int ConvertPortNumberToOffset(int port_number) { + return (port_number & (PortPerController - 1)); + } + + enum GpioController { + GpioController_1 = 0, + GpioController_2 = 1, + GpioController_3 = 2, + GpioController_4 = 3, + GpioController_5 = 4, + GpioController_6 = 5, + GpioController_7 = 6, + GpioController_8 = 7, + GpioController_Count = 8, + }; + static_assert(GpioController_Count == static_cast<GpioController>(1 << GpioControllerBitWidth)); + + constexpr inline os::InterruptName InterruptNameTable[GpioController_Count] = { + 64, /* GpioController_1 */ + 65, /* GpioController_2 */ + 66, /* GpioController_3 */ + 67, /* GpioController_4 */ + 87, /* GpioController_5 */ + 119, /* GpioController_6 */ + 121, /* GpioController_7 */ + 157, /* GpioController_8 */ + }; + + enum InternalInterruptMode { + InternalInterruptMode_LowLevel = 0x000000, + InternalInterruptMode_HighLevel = 0x000001, + InternalInterruptMode_RisingEdge = 0x000101, + InternalInterruptMode_FallingEdge = 0x000100, + InternalInterruptMode_AnyEdge = 0x010100, + + InternalInterruptMode_Mask = 0x010101, + }; + + enum GpioRegisterType { + GpioRegisterType_GPIO_CNF = 0, + GpioRegisterType_GPIO_OE = 1, + GpioRegisterType_GPIO_OUT = 2, + GpioRegisterType_GPIO_IN = 3, + GpioRegisterType_GPIO_INT_STA = 4, + GpioRegisterType_GPIO_INT_ENB = 5, + GpioRegisterType_GPIO_INT_LVL = 6, + GpioRegisterType_GPIO_INT_CLR = 7, + GpioRegisterType_GPIO_DB_CTRL = 8, + GpioRegisterType_GPIO_DB_CNT = 9, + }; + + constexpr inline uintptr_t MaskedWriteAddressOffset = 0x80; + constexpr inline int MaskedWriteBitOffset = 8; + + constexpr inline uintptr_t GetGpioRegisterAddress(uintptr_t gpio_address, GpioRegisterType reg_type, InternalGpioPadNumber pad_number) { + const auto controller = ConvertInternalGpioPadNumberToController(pad_number); + const auto port = ConvertInternalGpioPadNumberToPort(pad_number); + const auto offset = ConvertPortNumberToOffset(port); + + switch (reg_type) { + default: + return gpio_address + (0x100 * controller) + (0x10 * reg_type) + (0x4 * offset); + case GpioRegisterType_GPIO_DB_CTRL: + return gpio_address + (0x100 * controller) + (0x10 * GpioRegisterType_GPIO_IN) + (0x4 * offset); + case GpioRegisterType_GPIO_DB_CNT: + return gpio_address + (0x100 * controller) + MaskedWriteAddressOffset + (0x10 * GpioRegisterType_GPIO_INT_CLR) + (0x4 * offset); + } + } + + inline void SetMaskedBit(uintptr_t pad_address, int index, int value) { + const uintptr_t mask_address = pad_address + MaskedWriteAddressOffset; + + reg::Write(mask_address, (1u << (MaskedWriteBitOffset + index)) | (static_cast<unsigned int>(value) << index)); + } + + inline void SetMaskedBits(uintptr_t pad_address, unsigned int mask, unsigned int value) { + const uintptr_t mask_address = pad_address + MaskedWriteAddressOffset; + + reg::Write(mask_address, (mask << MaskedWriteBitOffset) | (value)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_suspend_handler.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_suspend_handler.cpp new file mode 100644 index 00000000..d6fc297e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_suspend_handler.cpp @@ -0,0 +1,73 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "gpio_suspend_handler.hpp" + +namespace ams::gpio::driver::board::nintendo::nx::impl { + + void SuspendHandler::Initialize(uintptr_t gpio_vaddr) { + /* Set our gpio virtual address. */ + m_gpio_virtual_address = gpio_vaddr; + + /* Ensure that we can use the wec library. */ + ams::wec::Initialize(); + } + + void SuspendHandler::SetValueForSleepState(TegraPad *pad, GpioValue value) { + /* TODO */ + AMS_UNUSED(pad, value); + AMS_ABORT(); + } + + Result SuspendHandler::IsWakeEventActive(bool *out, TegraPad *pad) const { + /* TODO */ + AMS_UNUSED(out, pad); + AMS_ABORT(); + } + + Result SuspendHandler::SetWakeEventActiveFlagSetForDebug(TegraPad *pad, bool en) { + /* TODO */ + AMS_UNUSED(pad, en); + AMS_ABORT(); + } + + void SuspendHandler::SetWakePinDebugMode(WakePinDebugMode mode) { + /* TODO */ + AMS_UNUSED(mode); + AMS_ABORT(); + } + + void SuspendHandler::Suspend() { + /* TODO */ + AMS_ABORT(); + } + + void SuspendHandler::SuspendLow() { + /* TODO */ + AMS_ABORT(); + } + + void SuspendHandler::Resume() { + /* TODO */ + AMS_ABORT(); + } + + void SuspendHandler::ResumeLow() { + /* TODO */ + AMS_ABORT(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_suspend_handler.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_suspend_handler.hpp new file mode 100644 index 00000000..09faf34e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_suspend_handler.hpp @@ -0,0 +1,89 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#include "gpio_tegra_pad.hpp" + +namespace ams::gpio::driver::board::nintendo::nx::impl { + + class SuspendHandler { + NON_COPYABLE(SuspendHandler); + NON_MOVEABLE(SuspendHandler); + private: + struct RegisterValues { + u16 conf; + u8 oe; + u8 out; + u8 int_enb; + u32 int_lvl; + u8 db_ctrl; + u8 db_cnt; + + void Reset() { + this->conf = 0; + this->oe = 0; + this->out = 0; + this->int_enb = 0; + this->int_lvl = 0; + this->db_ctrl = 0; + this->db_cnt = 0; + } + }; + + struct ValuesForSleepState { + u8 force_set; + u8 out; + + void Reset() { + this->force_set = 0; + this->out = 0; + } + }; + private: + ddsf::IDriver &m_driver; + uintptr_t m_gpio_virtual_address; + RegisterValues m_register_values[GpioPadPort_Count]; + ValuesForSleepState m_values_for_sleep_state[GpioPadPort_Count]; + private: + uintptr_t GetGpioVirtualAddress() const { + AMS_ASSERT(m_gpio_virtual_address != 0); + return m_gpio_virtual_address; + } + public: + explicit SuspendHandler(ddsf::IDriver *drv) : m_driver(*drv), m_gpio_virtual_address(0) { + for (auto &rv : m_register_values) { + rv.Reset(); + } + for (auto &v : m_values_for_sleep_state) { + v.Reset(); + } + } + + void Initialize(uintptr_t gpio_vaddr); + + void SetValueForSleepState(TegraPad *pad, GpioValue value); + Result IsWakeEventActive(bool *out, TegraPad *pad) const; + Result SetWakeEventActiveFlagSetForDebug(TegraPad *pad, bool en); + void SetWakePinDebugMode(WakePinDebugMode mode); + + void Suspend(); + void SuspendLow(); + void Resume(); + void ResumeLow(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_tegra_pad.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_tegra_pad.hpp new file mode 100644 index 00000000..d39b4a98 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_tegra_pad.hpp @@ -0,0 +1,377 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::gpio::driver::board::nintendo::nx::impl { + + enum GpioPadPort { + GpioPadPort_A = 0, + GpioPadPort_B = 1, + GpioPadPort_C = 2, + GpioPadPort_D = 3, + GpioPadPort_E = 4, + GpioPadPort_F = 5, + GpioPadPort_G = 6, + GpioPadPort_H = 7, + GpioPadPort_I = 8, + GpioPadPort_J = 9, + GpioPadPort_K = 10, + GpioPadPort_L = 11, + GpioPadPort_M = 12, + GpioPadPort_N = 13, + GpioPadPort_O = 14, + GpioPadPort_P = 15, + GpioPadPort_Q = 16, + GpioPadPort_R = 17, + GpioPadPort_S = 18, + GpioPadPort_T = 19, + GpioPadPort_U = 20, + GpioPadPort_V = 21, + GpioPadPort_W = 22, + GpioPadPort_X = 23, + GpioPadPort_Y = 24, + GpioPadPort_Z = 25, + GpioPadPort_AA = 26, + GpioPadPort_BB = 27, + GpioPadPort_CC = 28, + GpioPadPort_DD = 29, + GpioPadPort_EE = 30, + GpioPadPort_FF = 31, + GpioPadPort_Count = 32, + }; + + using InternalGpioPadNumber = int; + + constexpr unsigned int GetInternalGpioPadNumber(GpioPadPort port, unsigned int which) { + AMS_ASSERT(which < 8); + + return (static_cast<unsigned int>(port) * 8) + which; + } + + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_None = -1; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_A_0 = 0x00; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_A_1 = 0x01; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_A_2 = 0x02; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_A_3 = 0x03; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_A_4 = 0x04; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_A_5 = 0x05; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_A_6 = 0x06; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_A_7 = 0x07; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_B_0 = 0x08; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_B_1 = 0x09; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_B_2 = 0x0A; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_B_3 = 0x0B; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_B_4 = 0x0C; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_B_5 = 0x0D; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_B_6 = 0x0E; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_B_7 = 0x0F; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_C_0 = 0x10; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_C_1 = 0x11; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_C_2 = 0x12; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_C_3 = 0x13; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_C_4 = 0x14; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_C_5 = 0x15; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_C_6 = 0x16; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_C_7 = 0x17; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_D_0 = 0x18; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_D_1 = 0x19; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_D_2 = 0x1A; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_D_3 = 0x1B; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_D_4 = 0x1C; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_D_5 = 0x1D; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_D_6 = 0x1E; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_D_7 = 0x1F; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_E_0 = 0x20; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_E_1 = 0x21; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_E_2 = 0x22; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_E_3 = 0x23; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_E_4 = 0x24; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_E_5 = 0x25; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_E_6 = 0x26; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_E_7 = 0x27; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_F_0 = 0x28; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_F_1 = 0x29; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_F_2 = 0x2A; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_F_3 = 0x2B; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_F_4 = 0x2C; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_F_5 = 0x2D; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_F_6 = 0x2E; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_F_7 = 0x2F; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_G_0 = 0x30; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_G_1 = 0x31; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_G_2 = 0x32; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_G_3 = 0x33; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_G_4 = 0x34; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_G_5 = 0x35; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_G_6 = 0x36; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_G_7 = 0x37; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_H_0 = 0x38; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_H_1 = 0x39; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_H_2 = 0x3A; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_H_3 = 0x3B; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_H_4 = 0x3C; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_H_5 = 0x3D; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_H_6 = 0x3E; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_H_7 = 0x3F; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_I_0 = 0x40; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_I_1 = 0x41; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_I_2 = 0x42; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_I_3 = 0x43; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_I_4 = 0x44; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_I_5 = 0x45; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_I_6 = 0x46; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_I_7 = 0x47; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_J_0 = 0x48; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_J_1 = 0x49; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_J_2 = 0x4A; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_J_3 = 0x4B; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_J_4 = 0x4C; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_J_5 = 0x4D; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_J_6 = 0x4E; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_J_7 = 0x4F; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_K_0 = 0x50; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_K_1 = 0x51; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_K_2 = 0x52; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_K_3 = 0x53; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_K_4 = 0x54; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_K_5 = 0x55; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_K_6 = 0x56; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_K_7 = 0x57; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_L_0 = 0x58; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_L_1 = 0x59; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_L_2 = 0x5A; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_L_3 = 0x5B; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_L_4 = 0x5C; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_L_5 = 0x5D; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_L_6 = 0x5E; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_L_7 = 0x5F; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_M_0 = 0x60; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_M_1 = 0x61; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_M_2 = 0x62; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_M_3 = 0x63; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_M_4 = 0x64; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_M_5 = 0x65; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_M_6 = 0x66; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_M_7 = 0x67; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_N_0 = 0x68; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_N_1 = 0x69; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_N_2 = 0x6A; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_N_3 = 0x6B; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_N_4 = 0x6C; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_N_5 = 0x6D; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_N_6 = 0x6E; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_N_7 = 0x6F; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_O_0 = 0x70; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_O_1 = 0x71; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_O_2 = 0x72; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_O_3 = 0x73; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_O_4 = 0x74; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_O_5 = 0x75; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_O_6 = 0x76; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_O_7 = 0x77; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_P_0 = 0x78; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_P_1 = 0x79; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_P_2 = 0x7A; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_P_3 = 0x7B; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_P_4 = 0x7C; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_P_5 = 0x7D; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_P_6 = 0x7E; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_P_7 = 0x7F; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Q_0 = 0x80; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Q_1 = 0x81; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Q_2 = 0x82; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Q_3 = 0x83; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Q_4 = 0x84; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Q_5 = 0x85; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Q_6 = 0x86; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Q_7 = 0x87; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_R_0 = 0x88; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_R_1 = 0x89; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_R_2 = 0x8A; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_R_3 = 0x8B; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_R_4 = 0x8C; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_R_5 = 0x8D; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_R_6 = 0x8E; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_R_7 = 0x8F; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_S_0 = 0x90; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_S_1 = 0x91; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_S_2 = 0x92; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_S_3 = 0x93; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_S_4 = 0x94; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_S_5 = 0x95; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_S_6 = 0x96; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_S_7 = 0x97; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_T_0 = 0x98; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_T_1 = 0x99; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_T_2 = 0x9A; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_T_3 = 0x9B; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_T_4 = 0x9C; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_T_5 = 0x9D; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_T_6 = 0x9E; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_T_7 = 0x9F; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_U_0 = 0xA0; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_U_1 = 0xA1; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_U_2 = 0xA2; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_U_3 = 0xA3; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_U_4 = 0xA4; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_U_5 = 0xA5; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_U_6 = 0xA6; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_U_7 = 0xA7; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_V_0 = 0xA8; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_V_1 = 0xA9; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_V_2 = 0xAA; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_V_3 = 0xAB; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_V_4 = 0xAC; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_V_5 = 0xAD; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_V_6 = 0xAE; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_V_7 = 0xAF; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_W_0 = 0xB0; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_W_1 = 0xB1; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_W_2 = 0xB2; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_W_3 = 0xB3; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_W_4 = 0xB4; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_W_5 = 0xB5; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_W_6 = 0xB6; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_W_7 = 0xB7; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_X_0 = 0xB8; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_X_1 = 0xB9; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_X_2 = 0xBA; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_X_3 = 0xBB; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_X_4 = 0xBC; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_X_5 = 0xBD; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_X_6 = 0xBE; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_X_7 = 0xBF; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Y_0 = 0xC0; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Y_1 = 0xC1; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Y_2 = 0xC2; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Y_3 = 0xC3; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Y_4 = 0xC4; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Y_5 = 0xC5; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Y_6 = 0xC6; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Y_7 = 0xC7; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Z_0 = 0xC8; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Z_1 = 0xC9; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Z_2 = 0xCA; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Z_3 = 0xCB; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Z_4 = 0xCC; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Z_5 = 0xCD; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Z_6 = 0xCE; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_Z_7 = 0xCF; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_AA_0 = 0xD0; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_AA_1 = 0xD1; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_AA_2 = 0xD2; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_AA_3 = 0xD3; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_AA_4 = 0xD4; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_AA_5 = 0xD5; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_AA_6 = 0xD6; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_AA_7 = 0xD7; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_BB_0 = 0xD8; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_BB_1 = 0xD9; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_BB_2 = 0xDA; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_BB_3 = 0xDB; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_BB_4 = 0xDC; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_BB_5 = 0xDD; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_BB_6 = 0xDE; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_BB_7 = 0xDF; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_CC_0 = 0xE0; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_CC_1 = 0xE1; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_CC_2 = 0xE2; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_CC_3 = 0xE3; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_CC_4 = 0xE4; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_CC_5 = 0xE5; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_CC_6 = 0xE6; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_CC_7 = 0xE7; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_DD_0 = 0xE8; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_DD_1 = 0xE9; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_DD_2 = 0xEA; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_DD_3 = 0xEB; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_DD_4 = 0xEC; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_DD_5 = 0xED; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_DD_6 = 0xEE; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_DD_7 = 0xEF; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_EE_0 = 0xF0; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_EE_1 = 0xF1; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_EE_2 = 0xF2; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_EE_3 = 0xF3; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_EE_4 = 0xF4; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_EE_5 = 0xF5; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_EE_6 = 0xF6; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_EE_7 = 0xF7; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_FF_0 = 0xF8; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_FF_1 = 0xF9; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_FF_2 = 0xFA; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_FF_3 = 0xFB; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_FF_4 = 0xFC; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_FF_5 = 0xFD; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_FF_6 = 0xFE; + constexpr inline InternalGpioPadNumber InternalGpioPadNumber_Port_FF_7 = 0xFF; + + struct PadMapCombination { + DeviceCode device_code; + InternalGpioPadNumber internal_number; + wec::WakeEvent wake_event; + }; + + #include "gpio_internal_pad_map_combination.inc" + + struct PadInfo { + wec::WakeEvent wake_event; + + constexpr PadInfo() : wake_event(wec::WakeEvent_None) { /* ... */ } + + constexpr explicit PadInfo(wec::WakeEvent we) : wake_event(we) { /* ... */ } + + constexpr bool operator ==(const PadInfo &rhs) const { return this->wake_event == rhs.wake_event; } + constexpr bool operator !=(const PadInfo &rhs) const { return !(*this == rhs); } + }; + + struct PadStatus { + bool is_wake_active; + bool is_wake_active_debug; + + constexpr PadStatus() : is_wake_active(false), is_wake_active_debug(false) { /* ... */ } + }; + + class TegraPad : public ::ams::gpio::driver::Pad { + AMS_DDSF_CASTABLE_TRAITS(ams::gpio::driver::board::nintendo::nx::impl::TegraPad, ::ams::gpio::driver::Pad); + private: + using Base = ::ams::gpio::driver::Pad; + private: + util::IntrusiveListNode m_interrupt_list_node; + PadInfo m_info; + PadStatus m_status; + public: + using InterruptListTraits = util::IntrusiveListMemberTraits<&TegraPad::m_interrupt_list_node>; + using InterruptList = typename InterruptListTraits::ListType; + friend class util::IntrusiveList<TegraPad, util::IntrusiveListMemberTraits<&TegraPad::m_interrupt_list_node>>; + public: + TegraPad() : Pad(), m_interrupt_list_node(), m_info(), m_status() { /* ... */ } + + const PadInfo &GetInfo() const { return m_info; } + PadStatus &GetStatus() { return m_status; } + + void SetParameters(int pad, const PadInfo &i) { + Base::SetPadNumber(pad); + m_info = i; + } + + bool IsLinkedToInterruptBoundPadList() const { + return m_interrupt_list_node.IsLinked(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_wake_pin_config.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_wake_pin_config.hpp new file mode 100644 index 00000000..cc8dad19 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/board/nintendo/nx/impl/gpio_wake_pin_config.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::gpio::driver::board::nintendo::nx::impl { + + struct WakePinConfig { + wec::WakeEvent wake_event; + bool enable; + wec::WakeEventLevel level; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/gpio_driver_client_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/gpio_driver_client_api.cpp new file mode 100644 index 00000000..212b959c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/gpio_driver_client_api.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#include "impl/gpio_driver_core.hpp" + +namespace ams::gpio::driver { + + void Initialize() { + return impl::InitializeDrivers(); + } + + void Finalize() { + return impl::FinalizeDrivers(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/gpio_driver_service_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/gpio_driver_service_api.cpp new file mode 100644 index 00000000..7e87df1f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/gpio_driver_service_api.cpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#include "impl/gpio_driver_core.hpp" + +namespace ams::gpio::driver { + + void RegisterDriver(IGpioDriver *driver) { + return impl::RegisterDriver(driver); + } + + void UnregisterDriver(IGpioDriver *driver) { + return impl::UnregisterDriver(driver); + } + + Result RegisterDeviceCode(DeviceCode device_code, Pad *pad) { + R_RETURN(impl::RegisterDeviceCode(device_code, pad)); + } + + bool UnregisterDeviceCode(DeviceCode device_code) { + return impl::UnregisterDeviceCode(device_code); + } + + void RegisterInterruptHandler(ddsf::IEventHandler *handler) { + return impl::RegisterInterruptHandler(handler); + } + + void UnregisterInterruptHandler(ddsf::IEventHandler *handler) { + return impl::UnregisterInterruptHandler(handler); + } + + void SetInitialGpioConfig() { + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + return board::SetInitialGpioConfig(); + #endif + } + + void SetInitialWakePinConfig() { + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + return board::SetInitialWakePinConfig(); + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/gpio_pad.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/gpio_pad.cpp new file mode 100644 index 00000000..d0458407 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/gpio_pad.cpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#include "impl/gpio_driver_core.hpp" + +namespace ams::gpio::driver { + + bool Pad::IsAnySessionBoundToInterrupt() const { + /* Check to see if any session has an interrupt bound. */ + bool bound = false; + this->ForEachSession([&](const ddsf::ISession &session) -> bool { + const auto &impl = session.SafeCastTo<impl::PadSessionImpl>(); + if (impl.IsInterruptBound()) { + bound = true; + return false; + } + return true; + }); + + return bound; + } + + void Pad::SignalInterruptBoundEvent() { + /* Signal relevant sessions. */ + this->ForEachSession([&](ddsf::ISession &session) -> bool { + auto &impl = session.SafeCastTo<impl::PadSessionImpl>(); + if (impl.IsInterruptBound()) { + impl.SignalInterruptBoundEvent(); + } + return true; + }); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/gpio_pad_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/gpio_pad_api.cpp new file mode 100644 index 00000000..9a076fb0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/gpio_pad_api.cpp @@ -0,0 +1,137 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#include "impl/gpio_driver_core.hpp" + +namespace ams::gpio::driver { + + namespace { + + Result OpenSessionImpl(GpioPadSession *out, Pad *pad, ddsf::AccessMode access_mode) { + /* Construct the session. */ + auto *session = std::construct_at(std::addressof(impl::GetPadSessionImpl(*out))); + auto session_guard = SCOPE_GUARD { std::destroy_at(session); }; + + /* Open the session. */ + R_TRY(session->Open(pad, access_mode)); + + session_guard.Cancel(); + R_SUCCEED(); + } + + } + + Result OpenSession(GpioPadSession *out, DeviceCode device_code, ddsf::AccessMode access_mode) { + /* Find the pad. */ + Pad *pad = nullptr; + R_TRY(impl::FindPad(std::addressof(pad), device_code)); + AMS_ASSERT(pad != nullptr); + + /* Sanitize the access mode. */ + if ((access_mode & ~(ddsf::AccessMode_ReadWrite)) != 0) { + access_mode = ddsf::AccessMode_None; + } + + /* Try to open the session with the desired mode. */ + R_TRY_CATCH(OpenSessionImpl(out, pad, access_mode)) { + R_CATCH(ddsf::ResultAccessModeDenied) { + /* Our current access mode was denied. Try adding the shared flag. */ + R_TRY(OpenSessionImpl(out, pad, static_cast<ddsf::AccessMode>(access_mode | ddsf::AccessMode_Shared))); + } + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + void CloseSession(GpioPadSession *session) { + AMS_ASSERT(session != nullptr); + std::destroy_at(std::addressof(impl::GetOpenPadSessionImpl(*session))); + } + + Result SetDirection(GpioPadSession *session, gpio::Direction direction) { + /* Check that we have a valid session. */ + AMS_ASSERT(session != nullptr); + auto &impl = impl::GetOpenPadSessionImpl(*session); + R_UNLESS(impl.IsOpen(), gpio::ResultNotOpen()); + + /* Check that we can perform the access. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the pad. */ + auto &pad = impl.GetDevice().SafeCastTo<Pad>(); + + /* Perform the call. */ + R_TRY(pad.GetDriver().SafeCastTo<IGpioDriver>().SetDirection(std::addressof(pad), direction)); + + R_SUCCEED(); + } + + Result GetDirection(gpio::Direction *out, GpioPadSession *session) { + /* Check that we have a valid session. */ + AMS_ASSERT(session != nullptr); + auto &impl = impl::GetOpenPadSessionImpl(*session); + R_UNLESS(impl.IsOpen(), gpio::ResultNotOpen()); + + /* Check that we can perform the access. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the pad. */ + auto &pad = impl.GetDevice().SafeCastTo<Pad>(); + + /* Perform the call. */ + R_TRY(pad.GetDriver().SafeCastTo<IGpioDriver>().GetDirection(out, std::addressof(pad))); + + R_SUCCEED(); + } + + Result SetValue(GpioPadSession *session, gpio::GpioValue value) { + /* Check that we have a valid session. */ + AMS_ASSERT(session != nullptr); + auto &impl = impl::GetOpenPadSessionImpl(*session); + R_UNLESS(impl.IsOpen(), gpio::ResultNotOpen()); + + /* Check that we can perform the access. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the pad. */ + auto &pad = impl.GetDevice().SafeCastTo<Pad>(); + + /* Perform the call. */ + R_TRY(pad.GetDriver().SafeCastTo<IGpioDriver>().SetValue(std::addressof(pad), value)); + + R_SUCCEED(); + } + + Result GetValue(gpio::GpioValue *out, GpioPadSession *session) { + /* Check that we have a valid session. */ + AMS_ASSERT(session != nullptr); + auto &impl = impl::GetOpenPadSessionImpl(*session); + R_UNLESS(impl.IsOpen(), gpio::ResultNotOpen()); + + /* Check that we can perform the access. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the pad. */ + auto &pad = impl.GetDevice().SafeCastTo<Pad>(); + + /* Perform the call. */ + R_TRY(pad.GetDriver().SafeCastTo<IGpioDriver>().GetValue(out, std::addressof(pad))); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/impl/gpio_driver_core.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/impl/gpio_driver_core.cpp new file mode 100644 index 00000000..7ef9a15f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/impl/gpio_driver_core.cpp @@ -0,0 +1,151 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "gpio_driver_core.hpp" + +namespace ams::gpio::driver::impl { + + namespace { + + os::ThreadType g_interrupt_thread; + + constexpr inline size_t InterruptThreadStackSize = os::MemoryPageSize; + alignas(os::MemoryPageSize) u8 g_interrupt_thread_stack[InterruptThreadStackSize]; + + gpio::driver::IGpioDriver::List &GetGpioDriverList() { + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(gpio::driver::IGpioDriver::List, s_gpio_driver_list); + return s_gpio_driver_list; + } + + ddsf::EventHandlerManager &GetInterruptHandlerManager() { + AMS_FUNCTION_LOCAL_STATIC(ddsf::EventHandlerManager, s_interrupt_handler_manager); + + return s_interrupt_handler_manager; + } + + ddsf::DeviceCodeEntryManager &GetDeviceCodeEntryManager() { + AMS_FUNCTION_LOCAL_STATIC(ddsf::DeviceCodeEntryManager, s_device_code_entry_manager, ddsf::GetDeviceCodeEntryHolderMemoryResource()); + + return s_device_code_entry_manager; + } + + void InterruptThreadFunction(void *arg) { + AMS_UNUSED(arg); + GetInterruptHandlerManager().LoopAuto(); + } + + } + + void InitializeDrivers() { + /* Ensure the event handler manager is initialized. */ + GetInterruptHandlerManager().Initialize(); + + /* Initialize all registered drivers. */ + for (auto &driver : GetGpioDriverList()) { + driver.SafeCastTo<IGpioDriver>().InitializeDriver(); + } + + /* Create the interrupt thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(g_interrupt_thread), InterruptThreadFunction, nullptr, g_interrupt_thread_stack, InterruptThreadStackSize, AMS_GET_SYSTEM_THREAD_PRIORITY(gpio, InterruptHandler))); + os::SetThreadNamePointer(std::addressof(g_interrupt_thread), AMS_GET_SYSTEM_THREAD_NAME(gpio, InterruptHandler)); + os::StartThread(std::addressof(g_interrupt_thread)); + + /* Wait for the interrupt thread to enter the loop. */ + GetInterruptHandlerManager().WaitLoopEnter(); + } + + void FinalizeDrivers() { + /* Request the interrupt thread stop. */ + GetInterruptHandlerManager().RequestStop(); + os::WaitThread(std::addressof(g_interrupt_thread)); + os::DestroyThread(std::addressof(g_interrupt_thread)); + + /* TODO: What else? */ + AMS_ABORT(); + } + + void RegisterDriver(IGpioDriver *driver) { + AMS_ASSERT(driver != nullptr); + GetGpioDriverList().push_back(*driver); + } + + void UnregisterDriver(IGpioDriver *driver) { + AMS_ASSERT(driver != nullptr); + if (driver->IsLinkedToList()) { + auto &list = GetGpioDriverList(); + list.erase(list.iterator_to(*driver)); + } + } + + Result RegisterDeviceCode(DeviceCode device_code, Pad *pad) { + AMS_ASSERT(pad != nullptr); + R_TRY(GetDeviceCodeEntryManager().Add(device_code, pad)); + R_SUCCEED(); + } + + bool UnregisterDeviceCode(DeviceCode device_code) { + return GetDeviceCodeEntryManager().Remove(device_code); + } + + void RegisterInterruptHandler(ddsf::IEventHandler *handler) { + AMS_ASSERT(handler != nullptr); + GetInterruptHandlerManager().RegisterHandler(handler); + } + + void UnregisterInterruptHandler(ddsf::IEventHandler *handler) { + AMS_ASSERT(handler != nullptr); + GetInterruptHandlerManager().UnregisterHandler(handler); + } + + Result FindPad(Pad **out, DeviceCode device_code) { + /* Validate output. */ + AMS_ASSERT(out != nullptr); + + /* Find the device. */ + ddsf::IDevice *device; + R_TRY(GetDeviceCodeEntryManager().FindDevice(std::addressof(device), device_code)); + + /* Set output. */ + *out = device->SafeCastToPointer<Pad>(); + R_SUCCEED(); + } + + Result FindPadByNumber(Pad **out, int pad_number) { + /* Validate output. */ + AMS_ASSERT(out != nullptr); + + /* Find the pad. */ + bool found = false; + GetDeviceCodeEntryManager().ForEachEntry([&](ddsf::DeviceCodeEntry &entry) -> bool { + /* Convert the entry to a pad. */ + auto &pad = entry.GetDevice().SafeCastTo<Pad>(); + + /* Check if the pad is the one we're looking for. */ + if (pad.GetPadNumber() == pad_number) { + found = true; + *out = std::addressof(pad); + return false; + } + return true; + }); + + /* Check that we found the pad. */ + R_UNLESS(found, ddsf::ResultDeviceCodeNotFound()); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/impl/gpio_driver_core.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/impl/gpio_driver_core.hpp new file mode 100644 index 00000000..81a9a1b0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/impl/gpio_driver_core.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::gpio::driver::impl { + + void InitializeDrivers(); + void FinalizeDrivers(); + + void RegisterDriver(IGpioDriver *driver); + void UnregisterDriver(IGpioDriver *driver); + + Result RegisterDeviceCode(DeviceCode device_code, Pad *pad); + bool UnregisterDeviceCode(DeviceCode device_code); + + void RegisterInterruptHandler(ddsf::IEventHandler *handler); + void UnregisterInterruptHandler(ddsf::IEventHandler *handler); + + Result FindPad(Pad **out, DeviceCode device_code); + Result FindPadByNumber(Pad **out, int pad_number); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/impl/gpio_pad_session_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/impl/gpio_pad_session_impl.cpp new file mode 100644 index 00000000..029013f1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/driver/impl/gpio_pad_session_impl.cpp @@ -0,0 +1,151 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::gpio::driver::impl { + + Result PadSessionImpl::Open(Pad *pad, ddsf::AccessMode access_mode) { + /* Check if the pad has any open sessions. */ + const bool first_session = !pad->HasAnyOpenSession(); + + /* Open the session. */ + R_TRY(ddsf::OpenSession(pad, this, access_mode)); + auto pad_guard = SCOPE_GUARD { ddsf::CloseSession(this); }; + + /* If we're the first, we want to initialize the pad. */ + if (first_session) { + R_TRY(pad->GetDriver().SafeCastTo<IGpioDriver>().InitializePad(pad)); + } + + /* We opened successfully. */ + pad_guard.Cancel(); + R_SUCCEED(); + } + + void PadSessionImpl::Close() { + /* If the session isn't open, nothing to do. */ + if (!this->IsOpen()) { + return; + } + + /* Unbind the interrupt, if it's bound. */ + if (this->IsInterruptBound()) { + this->UnbindInterrupt(); + } + + /* Get the pad we're a session for. */ + auto &pad = this->GetDevice().SafeCastTo<Pad>(); + + /* Close the session. */ + ddsf::CloseSession(this); + + /* If we were the last session on the pad, finalize the pad. */ + if (!pad.HasAnyOpenSession()) { + pad.GetDriver().SafeCastTo<IGpioDriver>().FinalizePad(std::addressof(pad)); + } + } + + Result PadSessionImpl::BindInterrupt(os::SystemEventType *event) { + /* Acquire exclusive access to the relevant interrupt control mutex. */ + auto &pad = this->GetDevice().SafeCastTo<Pad>(); + auto &mutex = pad.GetDriver().SafeCastTo<IGpioDriver>().GetInterruptControlMutex(pad); + std::scoped_lock lk(mutex); + + /* Check that we're not already bound. */ + R_UNLESS(!this->IsInterruptBound(), gpio::ResultAlreadyBound()); + R_UNLESS(!this->GetDevice().SafeCastTo<Pad>().IsAnySessionBoundToInterrupt(), gpio::ResultAlreadyBound()); + + /* Create the system event. */ + R_TRY(os::CreateSystemEvent(event, os::EventClearMode_ManualClear, true)); + auto ev_guard = SCOPE_GUARD { os::DestroySystemEvent(event); }; + + /* Attach the event to our holder. */ + m_event_holder.AttachEvent(event); + auto hl_guard = SCOPE_GUARD { m_event_holder.DetachEvent(); }; + + /* Update interrupt needed. */ + R_TRY(this->UpdateDriverInterruptEnabled()); + + /* We succeeded. */ + hl_guard.Cancel(); + ev_guard.Cancel(); + R_SUCCEED(); + } + + void PadSessionImpl::UnbindInterrupt() { + /* Acquire exclusive access to the relevant interrupt control mutex. */ + auto &pad = this->GetDevice().SafeCastTo<Pad>(); + auto &mutex = pad.GetDriver().SafeCastTo<IGpioDriver>().GetInterruptControlMutex(pad); + std::scoped_lock lk(mutex); + + /* If we're not bound, nothing to do. */ + if (!this->IsInterruptBound()) { + return; + } + + /* Detach and destroy the event */ + os::DestroySystemEvent(m_event_holder.DetachEvent()); + + /* Update interrupt needed. */ + R_ABORT_UNLESS(this->UpdateDriverInterruptEnabled()); + } + + Result PadSessionImpl::UpdateDriverInterruptEnabled() { + /* Check we have exclusive access to the relevant interrupt control mutex. */ + auto &pad = this->GetDevice().SafeCastTo<Pad>(); + auto &driver = pad.GetDriver().SafeCastTo<IGpioDriver>(); + AMS_ASSERT(driver.GetInterruptControlMutex(pad).IsLockedByCurrentThread()); + + /* Set interrupt enabled. */ + R_RETURN(driver.SetInterruptEnabled(std::addressof(pad), pad.IsInterruptRequiredForDriver())); + } + + Result PadSessionImpl::GetInterruptEnabled(bool *out) const { + *out = this->GetDevice().SafeCastTo<Pad>().IsInterruptEnabled(); + R_SUCCEED(); + } + + Result PadSessionImpl::SetInterruptEnabled(bool en) { + /* Acquire exclusive access to the relevant interrupt control mutex. */ + auto &pad = this->GetDevice().SafeCastTo<Pad>(); + auto &mutex = pad.GetDriver().SafeCastTo<IGpioDriver>().GetInterruptControlMutex(pad); + std::scoped_lock lk(mutex); + + /* Set the interrupt enable. */ + const bool prev = pad.IsInterruptEnabled(); + pad.SetInterruptEnabled(en); + auto pad_guard = SCOPE_GUARD { pad.SetInterruptEnabled(prev); }; + + /* Update interrupt needed. */ + R_TRY(this->UpdateDriverInterruptEnabled()); + + pad_guard.Cancel(); + R_SUCCEED(); + } + + void PadSessionImpl::SignalInterruptBoundEvent() { + /* Check we have exclusive access to the relevant interrupt control mutex. */ + auto &pad = this->GetDevice().SafeCastTo<Pad>(); + auto &driver = pad.GetDriver().SafeCastTo<IGpioDriver>(); + AMS_ASSERT(driver.GetInterruptControlMutex(pad).IsLockedByCurrentThread()); + AMS_UNUSED(pad, driver); + + if (auto *event = m_event_holder.GetSystemEvent(); event != nullptr) { + os::SignalSystemEvent(event); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/gpio_client_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/gpio_client_api.cpp new file mode 100644 index 00000000..ce51b8ce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/gpio_client_api.cpp @@ -0,0 +1,213 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "gpio_remote_manager_impl.hpp" + +namespace ams::gpio { + + namespace { + + constinit os::SdkMutex g_init_mutex; + constinit int g_initialize_count = 0; + ams::sf::SharedPointer<gpio::sf::IManager> g_manager; + + #if defined(ATMOSPHERE_OS_HORIZON) + constinit bool g_remote = false; + ams::sf::UnmanagedServiceObject<gpio::sf::IManager, RemoteManagerImpl> g_remote_manager_impl; + #endif + + gpio::sf::IPadSession *GetInterface(GpioPadSession *session) { + AMS_ASSERT(session->_session != nullptr); + return static_cast<gpio::sf::IPadSession *>(session->_session); + } + + } + + void Initialize() { + std::scoped_lock lk(g_init_mutex); + + if ((g_initialize_count++) == 0) { + #if defined(ATMOSPHERE_OS_HORIZON) + R_ABORT_UNLESS(::gpioInitialize()); + g_manager = g_remote_manager_impl.GetShared(); + g_remote = true; + #else + AMS_ABORT("TODO"); + #endif + } + } + + void InitializeWith(ams::sf::SharedPointer<gpio::sf::IManager> manager) { + std::scoped_lock lk(g_init_mutex); + + AMS_ABORT_UNLESS(g_initialize_count == 0); + + g_manager = manager; + g_initialize_count = 1; + } + + void Finalize() { + std::scoped_lock lk(g_init_mutex); + + AMS_ASSERT(g_initialize_count > 0); + + if ((--g_initialize_count) == 0) { + g_manager = nullptr; + #if defined(ATMOSPHERE_OS_HORIZON) + if (g_remote) { + ::gpioExit(); + } + #else + AMS_ABORT("TODO"); + #endif + } + } + + Result OpenSession(GpioPadSession *out_session, ams::DeviceCode device_code) { + /* Get the session. */ + ams::sf::SharedPointer<gpio::sf::IPadSession> session; + { + if (hos::GetVersion() >= hos::Version_7_0_0) { + R_TRY(g_manager->OpenSession2(std::addressof(session), device_code, ddsf::AccessMode_ReadWrite)); + } else { + R_TRY(g_manager->OpenSession(std::addressof(session), ConvertToGpioPadName(device_code))); + } + } + + /* Set output. */ + out_session->_session = session.Detach(); + out_session->_event = nullptr; + + /* We succeeded. */ + R_SUCCEED(); + } + + void CloseSession(GpioPadSession *session) { + AMS_ASSERT(session != nullptr); + + /* Unbind the interrupt, if it's still bound. */ + if (session->_event != nullptr) { + gpio::UnbindInterrupt(session); + } + + /* Close the session. */ + ams::sf::ReleaseSharedObject(GetInterface(session)); + session->_session = nullptr; + } + + Result IsWakeEventActive(bool *out_is_active, ams::DeviceCode device_code) { + if (hos::GetVersion() >= hos::Version_7_0_0) { + R_TRY(g_manager->IsWakeEventActive2(out_is_active, device_code)); + } else { + R_TRY(g_manager->IsWakeEventActive(out_is_active, ConvertToGpioPadName(device_code))); + } + + R_SUCCEED(); + } + + Direction GetDirection(GpioPadSession *session) { + Direction out; + R_ABORT_UNLESS(GetInterface(session)->GetDirection(std::addressof(out))); + return out; + } + + void SetDirection(GpioPadSession *session, Direction direction) { + R_ABORT_UNLESS(GetInterface(session)->SetDirection(direction)); + } + + GpioValue GetValue(GpioPadSession *session) { + GpioValue out; + R_ABORT_UNLESS(GetInterface(session)->GetValue(std::addressof(out))); + return out; + } + + void SetValue(GpioPadSession *session, GpioValue value) { + R_ABORT_UNLESS(GetInterface(session)->SetValue(value)); + } + + InterruptMode GetInterruptMode(GpioPadSession *session) { + InterruptMode out; + R_ABORT_UNLESS(GetInterface(session)->GetInterruptMode(std::addressof(out))); + return out; + } + + void SetInterruptMode(GpioPadSession *session, InterruptMode mode) { + R_ABORT_UNLESS(GetInterface(session)->SetInterruptMode(mode)); + } + + bool GetInterruptEnable(GpioPadSession *session) { + bool out; + R_ABORT_UNLESS(GetInterface(session)->GetInterruptEnable(std::addressof(out))); + return out; + } + + void SetInterruptEnable(GpioPadSession *session, bool en) { + R_ABORT_UNLESS(GetInterface(session)->SetInterruptEnable(en)); + } + + InterruptStatus GetInterruptStatus(GpioPadSession *session) { + InterruptStatus out; + R_ABORT_UNLESS(GetInterface(session)->GetInterruptStatus(std::addressof(out))); + return out; + } + + void ClearInterruptStatus(GpioPadSession *session) { + R_ABORT_UNLESS(GetInterface(session)->ClearInterruptStatus()); + } + + int GetDebounceTime(GpioPadSession *session) { + int out; + R_ABORT_UNLESS(GetInterface(session)->GetDebounceTime(std::addressof(out))); + return out; + } + + void SetDebounceTime(GpioPadSession *session, int ms) { + R_ABORT_UNLESS(GetInterface(session)->SetDebounceTime(ms)); + } + + bool GetDebounceEnabled(GpioPadSession *session) { + bool out; + R_ABORT_UNLESS(GetInterface(session)->GetDebounceEnabled(std::addressof(out))); + return out; + } + + void SetDebounceEnabled(GpioPadSession *session, bool en) { + R_ABORT_UNLESS(GetInterface(session)->SetDebounceEnabled(en)); + } + + Result BindInterrupt(os::SystemEventType *event, GpioPadSession *session) { + AMS_ASSERT(session->_event == nullptr); + + ams::sf::NativeHandle handle; + R_TRY(GetInterface(session)->BindInterrupt(std::addressof(handle))); + + os::AttachReadableHandleToSystemEvent(event, handle.GetOsHandle(), handle.IsManaged(), os::EventClearMode_ManualClear); + handle.Detach(); + + session->_event = event; + R_SUCCEED(); + } + + void UnbindInterrupt(GpioPadSession *session) { + AMS_ASSERT(session->_event != nullptr); + + R_ABORT_UNLESS(GetInterface(session)->UnbindInterrupt()); + + os::DestroySystemEvent(session->_event); + session->_event = nullptr; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/gpio_remote_manager_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/gpio_remote_manager_impl.cpp new file mode 100644 index 00000000..72da31d0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/gpio_remote_manager_impl.cpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "gpio_remote_manager_impl.hpp" + +namespace ams::gpio { + + #if defined(ATMOSPHERE_OS_HORIZON) + namespace { + + struct GpioRemoteManagerTag; + using RemoteAllocator = ams::sf::ExpHeapStaticAllocator<3_KB, GpioRemoteManagerTag>; + using RemoteObjectFactory = ams::sf::ObjectFactory<typename RemoteAllocator::Policy>; + + class StaticAllocatorInitializer { + public: + StaticAllocatorInitializer() { + RemoteAllocator::Initialize(lmem::CreateOption_None); + } + } g_static_allocator_initializer; + + } + + Result RemoteManagerImpl::OpenSession(ams::sf::Out<ams::sf::SharedPointer<gpio::sf::IPadSession>> out, gpio::GpioPadName pad_name) { + ::GpioPadSession p; + R_TRY(::gpioOpenSession(std::addressof(p), static_cast<::GpioPadName>(static_cast<u32>(pad_name)))); + + out.SetValue(RemoteObjectFactory::CreateSharedEmplaced<gpio::sf::IPadSession, RemotePadSessionImpl>(p)); + R_SUCCEED(); + } + + Result RemoteManagerImpl::OpenSession2(ams::sf::Out<ams::sf::SharedPointer<gpio::sf::IPadSession>> out, DeviceCode device_code, ddsf::AccessMode access_mode) { + ::GpioPadSession p; + R_TRY(::gpioOpenSession2(std::addressof(p), device_code.GetInternalValue(), access_mode)); + + out.SetValue(RemoteObjectFactory::CreateSharedEmplaced<gpio::sf::IPadSession, RemotePadSessionImpl>(p)); + R_SUCCEED(); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/gpio_remote_manager_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/gpio_remote_manager_impl.hpp new file mode 100644 index 00000000..bd6e47fc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/gpio_remote_manager_impl.hpp @@ -0,0 +1,88 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "gpio_remote_pad_session_impl.hpp" + +namespace ams::gpio { + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemoteManagerImpl { + public: + RemoteManagerImpl() { /* ... */ } + + ~RemoteManagerImpl() { /* ... */ } + public: + /* Actual commands. */ + Result OpenSessionForDev(ams::sf::Out<ams::sf::SharedPointer<gpio::sf::IPadSession>> out, s32 pad_descriptor) { + /* TODO: libnx bindings */ + AMS_UNUSED(out, pad_descriptor); + AMS_ABORT(); + } + + Result OpenSession(ams::sf::Out<ams::sf::SharedPointer<gpio::sf::IPadSession>> out, gpio::GpioPadName pad_name); + + Result OpenSessionForTest(ams::sf::Out<ams::sf::SharedPointer<gpio::sf::IPadSession>> out, gpio::GpioPadName pad_name) { + /* TODO: libnx bindings */ + AMS_UNUSED(out, pad_name); + AMS_ABORT(); + } + + Result IsWakeEventActive(ams::sf::Out<bool> out, gpio::GpioPadName pad_name) { + R_RETURN(::gpioIsWakeEventActive2(out.GetPointer(), static_cast<::GpioPadName>(static_cast<u32>(pad_name)))); + } + + Result GetWakeEventActiveFlagSet(ams::sf::Out<gpio::WakeBitFlag> out) { + /* TODO: libnx bindings */ + AMS_UNUSED(out); + AMS_ABORT(); + } + + Result SetWakeEventActiveFlagSetForDebug(gpio::GpioPadName pad_name, bool is_enabled) { + /* TODO: libnx bindings */ + AMS_UNUSED(pad_name, is_enabled); + AMS_ABORT(); + } + + Result SetWakePinDebugMode(s32 mode) { + /* TODO: libnx bindings */ + AMS_UNUSED(mode); + AMS_ABORT(); + } + + Result OpenSession2(ams::sf::Out<ams::sf::SharedPointer<gpio::sf::IPadSession>> out, DeviceCode device_code, ddsf::AccessMode access_mode); + + Result IsWakeEventActive2(ams::sf::Out<bool> out, DeviceCode device_code) { + R_RETURN(::gpioIsWakeEventActive2(out.GetPointer(), device_code.GetInternalValue())); + } + + Result SetWakeEventActiveFlagSetForDebug2(DeviceCode device_code, bool is_enabled) { + /* TODO: libnx bindings */ + AMS_UNUSED(device_code, is_enabled); + AMS_ABORT(); + } + + Result SetRetryValues(u32 arg0, u32 arg1) { + /* TODO: libnx bindings */ + AMS_UNUSED(arg0, arg1); + AMS_ABORT(); + } + + }; + static_assert(gpio::sf::IsIManager<RemoteManagerImpl>); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/gpio_remote_pad_session_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/gpio_remote_pad_session_impl.hpp new file mode 100644 index 00000000..ee587450 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/gpio_remote_pad_session_impl.hpp @@ -0,0 +1,117 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::gpio { + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemotePadSessionImpl { + private: + ::GpioPadSession m_srv; + public: + RemotePadSessionImpl(::GpioPadSession &p) : m_srv(p) { /* ... */ } + + ~RemotePadSessionImpl() { ::gpioPadClose(std::addressof(m_srv)); } + public: + /* Actual commands. */ + Result SetDirection(gpio::Direction direction) { + R_RETURN(::gpioPadSetDirection(std::addressof(m_srv), static_cast<::GpioDirection>(static_cast<u32>(direction)))); + } + + Result GetDirection(ams::sf::Out<gpio::Direction> out) { + static_assert(sizeof(gpio::Direction) == sizeof(::GpioDirection)); + R_RETURN(::gpioPadGetDirection(std::addressof(m_srv), reinterpret_cast<::GpioDirection *>(out.GetPointer()))); + } + + Result SetInterruptMode(gpio::InterruptMode mode) { + R_RETURN(::gpioPadSetInterruptMode(std::addressof(m_srv), static_cast<::GpioInterruptMode>(static_cast<u32>(mode)))); + } + + Result GetInterruptMode(ams::sf::Out<gpio::InterruptMode> out) { + static_assert(sizeof(gpio::InterruptMode) == sizeof(::GpioInterruptMode)); + R_RETURN(::gpioPadGetInterruptMode(std::addressof(m_srv), reinterpret_cast<::GpioInterruptMode *>(out.GetPointer()))); + } + + Result SetInterruptEnable(bool enable) { + R_RETURN(::gpioPadSetInterruptEnable(std::addressof(m_srv), enable)); + } + + Result GetInterruptEnable(ams::sf::Out<bool> out) { + R_RETURN(::gpioPadGetInterruptEnable(std::addressof(m_srv), out.GetPointer())); + } + + Result GetInterruptStatus(ams::sf::Out<gpio::InterruptStatus> out) { + static_assert(sizeof(gpio::InterruptStatus) == sizeof(::GpioInterruptStatus)); + R_RETURN(::gpioPadGetInterruptStatus(std::addressof(m_srv), reinterpret_cast<::GpioInterruptStatus *>(out.GetPointer()))); + } + + Result ClearInterruptStatus() { + R_RETURN(::gpioPadClearInterruptStatus(std::addressof(m_srv))); + } + + Result SetValue(gpio::GpioValue value) { + R_RETURN(::gpioPadSetValue(std::addressof(m_srv), static_cast<::GpioValue>(static_cast<u32>(value)))); + } + + Result GetValue(ams::sf::Out<gpio::GpioValue> out) { + static_assert(sizeof(gpio::GpioValue) == sizeof(::GpioValue)); + R_RETURN(::gpioPadGetValue(std::addressof(m_srv), reinterpret_cast<::GpioValue *>(out.GetPointer()))); + } + + Result BindInterrupt(ams::sf::OutCopyHandle out) { + ::Event ev; + R_TRY(::gpioPadBindInterrupt(std::addressof(m_srv), std::addressof(ev))); + out.SetValue(ev.revent, true); + R_SUCCEED(); + } + + Result UnbindInterrupt() { + R_RETURN(::gpioPadUnbindInterrupt(std::addressof(m_srv))); + } + + Result SetDebounceEnabled(bool enable) { + R_RETURN(::gpioPadSetDebounceEnabled(std::addressof(m_srv), enable)); + } + + Result GetDebounceEnabled(ams::sf::Out<bool> out) { + R_RETURN(::gpioPadGetDebounceEnabled(std::addressof(m_srv), out.GetPointer())); + } + + Result SetDebounceTime(s32 ms) { + R_RETURN(::gpioPadSetDebounceTime(std::addressof(m_srv), ms)); + } + + Result GetDebounceTime(ams::sf::Out<s32> out) { + R_RETURN(::gpioPadGetDebounceTime(std::addressof(m_srv), out.GetPointer())); + } + + Result SetValueForSleepState(gpio::GpioValue value) { + /* TODO: libnx bindings. */ + AMS_UNUSED(value); + AMS_ABORT(); + } + + Result GetValueForSleepState(ams::sf::Out<gpio::GpioValue> out) { + /* TODO: libnx bindings. */ + AMS_UNUSED(out); + AMS_ABORT(); + } + }; + static_assert(gpio::sf::IsIPadSession<RemotePadSessionImpl>); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/server/gpio_server_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/server/gpio_server_api.cpp new file mode 100644 index 00000000..585af478 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/server/gpio_server_api.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "gpio_server_manager_impl.hpp" + +namespace ams::gpio::server { + + namespace { + + ams::sf::UnmanagedServiceObject<gpio::sf::IManager, gpio::server::ManagerImpl> g_manager_impl; + + } + + ams::sf::SharedPointer<gpio::sf::IManager> GetServiceObject() { + return g_manager_impl.GetShared(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/server/gpio_server_manager_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/server/gpio_server_manager_impl.cpp new file mode 100644 index 00000000..22681321 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/server/gpio_server_manager_impl.cpp @@ -0,0 +1,100 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "gpio_server_manager_impl.hpp" + +namespace ams::gpio::server { + + ManagerImpl::ManagerImpl() { + m_heap_handle = lmem::CreateExpHeap(m_heap_buffer, sizeof(m_heap_buffer), lmem::CreateOption_None); + m_pad_allocator.Attach(m_heap_handle); + } + + ManagerImpl::~ManagerImpl() { + lmem::DestroyExpHeap(m_heap_handle); + } + + Result ManagerImpl::OpenSessionForDev(ams::sf::Out<ams::sf::SharedPointer<gpio::sf::IPadSession>> out, s32 pad_descriptor) { + /* TODO */ + AMS_UNUSED(out, pad_descriptor); + AMS_ABORT(); + } + + Result ManagerImpl::OpenSession(ams::sf::Out<ams::sf::SharedPointer<gpio::sf::IPadSession>> out, gpio::GpioPadName pad_name) { + R_RETURN(this->OpenSession2(out, ConvertToDeviceCode(pad_name), ddsf::AccessMode_ReadWrite)); + } + + Result ManagerImpl::OpenSessionForTest(ams::sf::Out<ams::sf::SharedPointer<gpio::sf::IPadSession>> out, gpio::GpioPadName pad_name) { + /* TODO */ + AMS_UNUSED(out, pad_name); + AMS_ABORT(); + } + + Result ManagerImpl::IsWakeEventActive(ams::sf::Out<bool> out, gpio::GpioPadName pad_name) { + /* TODO */ + AMS_UNUSED(out, pad_name); + AMS_ABORT(); + } + + Result ManagerImpl::GetWakeEventActiveFlagSet(ams::sf::Out<gpio::WakeBitFlag> out) { + /* TODO */ + AMS_UNUSED(out); + AMS_ABORT(); + } + + Result ManagerImpl::SetWakeEventActiveFlagSetForDebug(gpio::GpioPadName pad_name, bool is_enabled) { + /* TODO */ + AMS_UNUSED(pad_name, is_enabled); + AMS_ABORT(); + } + + Result ManagerImpl::SetWakePinDebugMode(s32 mode) { + /* TODO */ + AMS_UNUSED(mode); + AMS_ABORT(); + } + + Result ManagerImpl::OpenSession2(ams::sf::Out<ams::sf::SharedPointer<gpio::sf::IPadSession>> out, DeviceCode device_code, ddsf::AccessMode access_mode) { + /* Allocate a session. */ + auto session = Factory::CreateSharedEmplaced<gpio::sf::IPadSession, PadSessionImpl>(std::addressof(m_pad_allocator), this); + + /* Open the session. */ + R_TRY(session.GetImpl().OpenSession(device_code, access_mode)); + + /* We succeeded. */ + *out = std::move(session); + R_SUCCEED(); + } + + Result ManagerImpl::IsWakeEventActive2(ams::sf::Out<bool> out, DeviceCode device_code) { + /* TODO */ + AMS_UNUSED(out, device_code); + AMS_ABORT(); + } + + Result ManagerImpl::SetWakeEventActiveFlagSetForDebug2(DeviceCode device_code, bool is_enabled) { + /* TODO */ + AMS_UNUSED(device_code, is_enabled); + AMS_ABORT(); + } + + Result ManagerImpl::SetRetryValues(u32 arg0, u32 arg1) { + /* TODO */ + AMS_UNUSED(arg0, arg1); + AMS_ABORT(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/server/gpio_server_manager_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/server/gpio_server_manager_impl.hpp new file mode 100644 index 00000000..be5d862a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/server/gpio_server_manager_impl.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "gpio_server_pad_session_impl.hpp" + +namespace ams::gpio::server { + + class ManagerImpl : public ams::sf::ISharedObject { + private: + using Allocator = ams::sf::ExpHeapAllocator; + using Factory = ams::sf::ObjectFactory<Allocator::Policy>; + private: + lmem::HeapHandle m_heap_handle; + Allocator m_pad_allocator; + u8 m_heap_buffer[12_KB]; + public: + ManagerImpl(); + + ~ManagerImpl(); + public: + /* Actual commands. */ + Result OpenSessionForDev(ams::sf::Out<ams::sf::SharedPointer<gpio::sf::IPadSession>> out, s32 pad_descriptor); + Result OpenSession(ams::sf::Out<ams::sf::SharedPointer<gpio::sf::IPadSession>> out, gpio::GpioPadName pad_name); + Result OpenSessionForTest(ams::sf::Out<ams::sf::SharedPointer<gpio::sf::IPadSession>> out, gpio::GpioPadName pad_name); + Result IsWakeEventActive(ams::sf::Out<bool> out, gpio::GpioPadName pad_name); + Result GetWakeEventActiveFlagSet(ams::sf::Out<gpio::WakeBitFlag> out); + Result SetWakeEventActiveFlagSetForDebug(gpio::GpioPadName pad_name, bool is_enabled); + Result SetWakePinDebugMode(s32 mode); + Result OpenSession2(ams::sf::Out<ams::sf::SharedPointer<gpio::sf::IPadSession>> out, DeviceCode device_code, ddsf::AccessMode access_mode); + Result IsWakeEventActive2(ams::sf::Out<bool> out, DeviceCode device_code); + Result SetWakeEventActiveFlagSetForDebug2(DeviceCode device_code, bool is_enabled); + Result SetRetryValues(u32 arg0, u32 arg1); + + }; + static_assert(gpio::sf::IsIManager<ManagerImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/server/gpio_server_pad_session_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/server/gpio_server_pad_session_impl.hpp new file mode 100644 index 00000000..a1ce1e6a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/gpio/server/gpio_server_pad_session_impl.hpp @@ -0,0 +1,219 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::gpio::server { + + class ManagerImpl; + + class PadSessionImpl { + private: + ManagerImpl *m_parent; /* NOTE: this is an sf::SharedPointer<> in Nintendo's code. */ + gpio::driver::GpioPadSession m_internal_pad_session; + bool m_has_session; + os::SystemEvent m_system_event; + public: + explicit PadSessionImpl(ManagerImpl *p) : m_parent(p), m_has_session(false) { /* ... */ } + + ~PadSessionImpl() { + if (m_has_session) { + gpio::driver::CloseSession(std::addressof(m_internal_pad_session)); + } + } + + Result OpenSession(DeviceCode device_code, ddsf::AccessMode access_mode) { + AMS_ABORT_UNLESS(!m_has_session); + + R_TRY(gpio::driver::OpenSession(std::addressof(m_internal_pad_session), device_code, access_mode)); + m_has_session = true; + R_SUCCEED(); + } + public: + /* Actual commands. */ + Result SetDirection(gpio::Direction direction) { + /* Validate our state. */ + AMS_ASSERT(m_has_session); + + /* Validate the direction. */ + R_UNLESS((direction == Direction_Input || direction == Direction_Output), gpio::ResultInvalidArgument()); + + /* Invoke the driver library. */ + R_TRY(gpio::driver::SetDirection(std::addressof(m_internal_pad_session), direction)); + + R_SUCCEED(); + } + + Result GetDirection(ams::sf::Out<gpio::Direction> out) { + /* Validate our state. */ + AMS_ASSERT(m_has_session); + + /* Invoke the driver library. */ + R_TRY(gpio::driver::GetDirection(out.GetPointer(), std::addressof(m_internal_pad_session))); + + R_SUCCEED(); + } + + Result SetInterruptMode(gpio::InterruptMode mode) { + /* Validate our state. */ + AMS_ASSERT(m_has_session); + + /* TODO */ + AMS_UNUSED(mode); + AMS_ABORT(); + } + + Result GetInterruptMode(ams::sf::Out<gpio::InterruptMode> out) { + /* Validate our state. */ + AMS_ASSERT(m_has_session); + + /* TODO */ + AMS_UNUSED(out); + AMS_ABORT(); + } + + Result SetInterruptEnable(bool enable) { + /* Validate our state. */ + AMS_ASSERT(m_has_session); + + /* TODO */ + AMS_UNUSED(enable); + AMS_ABORT(); + } + + Result GetInterruptEnable(ams::sf::Out<bool> out) { + /* Validate our state. */ + AMS_ASSERT(m_has_session); + + /* TODO */ + AMS_UNUSED(out); + AMS_ABORT(); + } + + Result GetInterruptStatus(ams::sf::Out<gpio::InterruptStatus> out) { + /* Validate our state. */ + AMS_ASSERT(m_has_session); + + /* TODO */ + AMS_UNUSED(out); + AMS_ABORT(); + } + + Result ClearInterruptStatus() { + /* Validate our state. */ + AMS_ASSERT(m_has_session); + + /* TODO */ + AMS_ABORT(); + } + + Result SetValue(gpio::GpioValue value) { + /* Validate our state. */ + AMS_ASSERT(m_has_session); + + /* Validate the value. */ + R_UNLESS((value == GpioValue_Low || value == GpioValue_High), gpio::ResultInvalidArgument()); + + /* Invoke the driver library. */ + R_TRY(gpio::driver::SetValue(std::addressof(m_internal_pad_session), value)); + + R_SUCCEED(); + } + + Result GetValue(ams::sf::Out<gpio::GpioValue> out) { + /* Validate our state. */ + AMS_ASSERT(m_has_session); + + /* Invoke the driver library. */ + R_TRY(gpio::driver::GetValue(out.GetPointer(), std::addressof(m_internal_pad_session))); + + R_SUCCEED(); + } + + Result BindInterrupt(ams::sf::OutCopyHandle out) { + /* Validate our state. */ + AMS_ASSERT(m_has_session); + + /* TODO */ + AMS_UNUSED(out); + AMS_ABORT(); + } + + Result UnbindInterrupt() { + /* Validate our state. */ + AMS_ASSERT(m_has_session); + + /* TODO */ + AMS_ABORT(); + } + + Result SetDebounceEnabled(bool enable) { + /* Validate our state. */ + AMS_ASSERT(m_has_session); + + /* TODO */ + AMS_UNUSED(enable); + AMS_ABORT(); + } + + Result GetDebounceEnabled(ams::sf::Out<bool> out) { + /* Validate our state. */ + AMS_ASSERT(m_has_session); + + /* TODO */ + AMS_UNUSED(out); + AMS_ABORT(); + } + + Result SetDebounceTime(s32 ms) { + /* Validate our state. */ + AMS_ASSERT(m_has_session); + + /* TODO */ + AMS_UNUSED(ms); + AMS_ABORT(); + } + + Result GetDebounceTime(ams::sf::Out<s32> out) { + /* Validate our state. */ + AMS_ASSERT(m_has_session); + + /* TODO */ + AMS_UNUSED(out); + AMS_ABORT(); + } + + Result SetValueForSleepState(gpio::GpioValue value) { + /* Validate our state. */ + AMS_ASSERT(m_has_session); + + /* TODO */ + AMS_UNUSED(value); + AMS_ABORT(); + } + + Result GetValueForSleepState(ams::sf::Out<gpio::GpioValue> out) { + /* Validate our state. */ + AMS_ASSERT(m_has_session); + + /* TODO */ + AMS_UNUSED(out); + AMS_ABORT(); + } + }; + static_assert(gpio::sf::IsIPadSession<PadSessionImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/hid/hid_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/hid/hid_api.cpp new file mode 100644 index 00000000..26291eae --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/hid/hid_api.cpp @@ -0,0 +1,93 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::hid { + + #if defined(ATMOSPHERE_OS_HORIZON) + namespace { + + /* Global lock. */ + constinit os::SdkMutex g_hid_lock; + constinit bool g_initialized_hid = false; + + /* Set of supported NpadIds (we want to read from any connected controllers). */ + constexpr const HidNpadIdType NpadIdTypes[] = { + HidNpadIdType_No1, + HidNpadIdType_No2, + HidNpadIdType_No3, + HidNpadIdType_No4, + HidNpadIdType_No5, + HidNpadIdType_No6, + HidNpadIdType_No7, + HidNpadIdType_No8, + HidNpadIdType_Handheld, + }; + + constexpr const size_t NumNpadIdTypes = util::size(NpadIdTypes); + + /* Helper. */ + void InitializeHid() { + R_ABORT_UNLESS(sm::Initialize()); + R_ABORT_UNLESS(hidInitialize()); + hidInitializeNpad(); + R_ABORT_UNLESS(hidSetSupportedNpadIdType(NpadIdTypes, NumNpadIdTypes)); + R_ABORT_UNLESS(hidSetSupportedNpadStyleSet(HidNpadStyleSet_NpadStandard | HidNpadStyleTag_NpadSystemExt)); + } + + Result EnsureHidInitialized() { + if (!g_initialized_hid) { + if (!serviceIsActive(hidGetServiceSession())) { + if (!pm::info::HasLaunchedBootProgram(ncm::SystemProgramId::Hid)) { + R_THROW(MAKERESULT(Module_Libnx, LibnxError_InitFail_HID)); + } + InitializeHid(); + } + g_initialized_hid = true; + } + + R_SUCCEED(); + } + + u64 ReadHidNpad(HidNpadIdType id) { + HidNpadSystemExtState state; + + size_t count = hidGetNpadStatesSystemExt(id, std::addressof(state), 1); + if (count != 0 && (state.attributes & HidNpadAttribute_IsConnected)) { + return state.buttons; + } + + return 0; + } + + } + + Result GetKeysHeld(u64 *out) { + std::scoped_lock lk(g_hid_lock); + + R_TRY(EnsureHidInitialized()); + + *out = ReadHidNpad(HidNpadIdType_Handheld); + + for (size_t controller = 0; controller < 8; controller++) { + *out |= ReadHidNpad(static_cast<HidNpadIdType>(controller)); + } + + R_SUCCEED(); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/hos/hos_stratosphere_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/hos/hos_stratosphere_api.cpp new file mode 100644 index 00000000..2a54ef06 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/hos/hos_stratosphere_api.cpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../os/impl/os_rng_manager.hpp" + +namespace ams::os { + + void Initialize(); + +} + +#if defined(ATMOSPHERE_OS_HORIZON) +extern "C" { + + /* Provide libnx address space allocation shim. */ + uintptr_t __libnx_virtmem_rng(void) { + return static_cast<uintptr_t>(::ams::os::impl::GetRngManager().GenerateRandomU64()); + } + +} +#endif + +namespace ams::hos { + + namespace { + + bool CanAllowTemporaryApproximateVersion() { + /* Check if we're a program that can use a temporary approximate version. */ + const auto program_id = os::GetCurrentProgramId(); + + return program_id == ncm::SystemProgramId::Pm || /* Needed so that boot has permissions to use fsp-srv. */ + program_id == ncm::SystemProgramId::Sm || /* Needed so that boot can acquire fsp-srv. */ + program_id == ncm::SystemProgramId::Boot || /* Needed so that boot can set the true target firmware. */ + program_id == ncm::SystemProgramId::Spl || /* Needed so that FS can complete initialization. */ + program_id == ncm::SystemProgramId::Ncm; /* Needed so that FS can determine where SystemVersion is located. */ + } + + } + + bool IsUnitTestProgramForSetVersion(); + void InitializeVersionInternal(bool allow_approximate); + + void InitializeForStratosphere() { + /* Initialize the global os resource managers. This *must* be done before anything else in stratosphere. */ + os::Initialize(); + + /* Initialize hos::Version API. */ + hos::InitializeVersionInternal(CanAllowTemporaryApproximateVersion()); + + #if defined(ATMOSPHERE_OS_HORIZON) + /* Check that we're running under mesosphere. */ + AMS_ABORT_UNLESS(IsUnitTestProgramForSetVersion() || svc::IsKernelMesosphere()); + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/hos/hos_version_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/hos/hos_version_api.cpp new file mode 100644 index 00000000..db3d6f62 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/hos/hos_version_api.cpp @@ -0,0 +1,146 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::hos { + + namespace { + + constinit os::SdkMutex g_hos_init_lock; + constinit hos::Version g_hos_version; + constinit bool g_set_hos_version; + + Result GetExosphereApiInfo(exosphere::ApiInfo *out) { + u64 exosphere_cfg; + R_TRY_CATCH(spl::impl::GetConfig(std::addressof(exosphere_cfg), spl::ConfigItem::ExosphereApiVersion)) { + R_CATCH_RETHROW(spl::ResultSecureMonitorNotInitialized) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + *out = { exosphere_cfg }; + R_SUCCEED(); + } + + #if defined(ATMOSPHERE_OS_HORIZON) + Result GetApproximateExosphereApiInfo(exosphere::ApiInfo *out) { + u64 exosphere_cfg; + + R_TRY_CATCH(spl::impl::GetConfig(std::addressof(exosphere_cfg), spl::ConfigItem::ExosphereApproximateApiVersion)) { + R_CATCH_RETHROW(spl::ResultSecureMonitorBusy) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + *out = { exosphere_cfg }; + R_SUCCEED(); + } + #endif + + exosphere::ApiInfo GetExosphereApiInfo(bool allow_approximate) { + exosphere::ApiInfo info; + #if defined(ATMOSPHERE_OS_HORIZON) + while (true) { + if (R_SUCCEEDED(GetExosphereApiInfo(std::addressof(info)))) { + break; + } + + if (allow_approximate && R_SUCCEEDED(GetApproximateExosphereApiInfo(std::addressof(info)))) { + break; + } + + svc::SleepThread(TimeSpan::FromMilliSeconds(25).GetNanoSeconds()); + } + #else + AMS_UNUSED(allow_approximate); + R_ABORT_UNLESS(GetExosphereApiInfo(std::addressof(info))); + #endif + + return info; + } + + } + + bool IsUnitTestProgramForSetVersion(); + + void InitializeVersionInternal(bool allow_approximate) { + hos::Version current = hos::Version_Current; + + /* If we're unit testing, just set the version and move on. */ + if (IsUnitTestProgramForSetVersion()) { + g_hos_version = hos::Version_Current; + g_set_hos_version = true; + } else { + /* Get the current (and previous approximation of) target firmware. */ + hos::Version prev; + bool has_prev = false; + { + /* Acquire exclusive access to set hos version. */ + std::scoped_lock lk(g_hos_init_lock); + + /* Save the previous value of g_hos_version. */ + prev = g_hos_version; + has_prev = g_set_hos_version; + + /* Set hos version = exosphere api version target firmware. */ + g_hos_version = static_cast<hos::Version>(GetExosphereApiInfo(allow_approximate).GetTargetFirmware()); + + /* Save the current value of g_hos_version. */ + current = g_hos_version; + + /* Note that we've set a previous hos version. */ + g_set_hos_version = true; + } + + /* Ensure that this is a hos version we can sanely *try* to run. */ + /* To be friendly, we will only require that we recognize the major and minor versions. */ + /* We can consider only recognizing major in the future, but micro seems safe to ignore as */ + /* there are no breaking IPC changes in minor updates. */ + { + constexpr u32 MaxMajor = (static_cast<u32>(hos::Version_Max) >> 24) & 0xFF; + constexpr u32 MaxMinor = (static_cast<u32>(hos::Version_Max) >> 16) & 0xFF; + + const u32 major = (static_cast<u32>(current) >> 24) & 0xFF; + const u32 minor = (static_cast<u32>(current) >> 16) & 0xFF; + + const bool is_safely_tryable_version = (current <= hos::Version_Max) || (major == MaxMajor && minor <= MaxMinor); + AMS_ABORT_UNLESS(is_safely_tryable_version); + } + + /* Ensure that this is a hos version compatible with previous approximations. */ + if (has_prev) { + AMS_ABORT_UNLESS(current >= prev); + + const u32 current_major = (static_cast<u32>(current) >> 24) & 0xFF; + const u32 prev_major = (static_cast<u32>(prev) >> 24) & 0xFF; + + AMS_ABORT_UNLESS(current_major == prev_major); + } + } + + + #if defined(ATMOSPHERE_OS_HORIZON) + /* Set the version for libnx. */ + { + const u32 major = (static_cast<u32>(current) >> 24) & 0xFF; + const u32 minor = (static_cast<u32>(current) >> 16) & 0xFF; + const u32 micro = (static_cast<u32>(current) >> 8) & 0xFF; + hosversionSet((BIT(31)) | (MAKEHOSVERSION(major, minor, micro))); + } + #endif + } + + ::ams::hos::Version GetVersion() { + return g_hos_version; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/hos/hos_version_api_private.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/hos/hos_version_api_private.cpp new file mode 100644 index 00000000..79afeb04 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/hos/hos_version_api_private.cpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::hos { + + namespace { + + #if defined(ATMOSPHERE_OS_HORIZON) + settings::FirmwareVersion GetSettingsFirmwareVersion() { + /* Mount the system version title. */ + R_ABORT_UNLESS(ams::fs::MountSystemData("sysver", ncm::SystemDataId::SystemVersion)); + ON_SCOPE_EXIT { ams::fs::Unmount("sysver"); }; + + /* Read the firmware version file. */ + ams::fs::FileHandle file; + R_ABORT_UNLESS(ams::fs::OpenFile(std::addressof(file), "sysver:/file", fs::OpenMode_Read)); + ON_SCOPE_EXIT { ams::fs::CloseFile(file); }; + + /* Must be possible to read firmware version from file. */ + settings::FirmwareVersion firmware_version; + R_ABORT_UNLESS(ams::fs::ReadFile(file, 0, std::addressof(firmware_version), sizeof(firmware_version))); + + return firmware_version; + } + #endif + + } + + void InitializeVersionInternal(bool allow_approximate); + + void SetNonApproximateVersionInternal() { + #if defined(ATMOSPHERE_OS_HORIZON) + /* Get the settings . */ + const auto firmware_version = GetSettingsFirmwareVersion(); + + /* Set the exosphere api version. */ + R_ABORT_UNLESS(spl::SetConfig(spl::ConfigItem::ExosphereApiVersion, (static_cast<u32>(firmware_version.major) << 24) | (static_cast<u32>(firmware_version.minor) << 16) | (static_cast<u32>(firmware_version.micro) << 8))); + + /* Update our own version value. */ + InitializeVersionInternal(false); + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/hos/hos_version_api_weak_for_unit_test.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/hos/hos_version_api_weak_for_unit_test.cpp new file mode 100644 index 00000000..a1797f02 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/hos/hos_version_api_weak_for_unit_test.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::hos { + + WEAK_SYMBOL bool IsUnitTestProgramForSetVersion() { + #if defined(ATMOSPHERE_OS_HORIZON) + return false; + #else + return true; + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/driver/htc_driver_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/driver/htc_driver_manager.hpp new file mode 100644 index 00000000..11c1b664 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/driver/htc_driver_manager.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htc_i_driver.hpp" + +namespace ams::htc::server::driver { + + class DriverManager { + private: + IDriver *m_driver; + public: + DriverManager(IDriver *driver) : m_driver(driver) { /* ... */ } + + IDriver *GetDriver() { return m_driver; } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/driver/htc_htclow_driver.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/driver/htc_htclow_driver.cpp new file mode 100644 index 00000000..6ede55b2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/driver/htc_htclow_driver.cpp @@ -0,0 +1,187 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htc_htclow_driver.hpp" + +namespace ams::htc::server::driver { + + namespace { + + constexpr ALWAYS_INLINE htclow::impl::ChannelInternalType GetHtclowChannel(htclow::ChannelId channel_id, htclow::ModuleId module_id) { + return { + .channel_id = channel_id, + .reserved = 0, + .module_id = module_id, + }; + } + + } + + void HtclowDriver::WaitTask(u32 task_id) { + os::WaitEvent(m_manager->GetTaskEvent(task_id)); + } + + void HtclowDriver::SetDisconnectionEmulationEnabled(bool en) { + /* NOTE: Nintendo ignores the input, here. */ + AMS_UNUSED(en); + m_disconnection_emulation_enabled = false; + } + + Result HtclowDriver::Open(htclow::ChannelId channel) { + /* Check the channel/module combination. */ + if (m_module_id == htclow::ModuleId::Htcmisc) { + AMS_ABORT_UNLESS(channel == HtcmiscClientChannelId ); + } else if (m_module_id == htclow::ModuleId::Htcs) { + AMS_ABORT_UNLESS(channel == 0); + } else { + AMS_ABORT("Unsupported channel"); + } + + R_RETURN(this->Open(channel, m_default_receive_buffer, sizeof(m_default_receive_buffer), m_default_send_buffer, sizeof(m_default_send_buffer))); + } + + Result HtclowDriver::Open(htclow::ChannelId channel, void *receive_buffer, size_t receive_buffer_size, void *send_buffer, size_t send_buffer_size) { + /* Open the channel. */ + R_TRY(m_manager->Open(GetHtclowChannel(channel, m_module_id))); + + /* Set the send/receive buffers. */ + m_manager->SetReceiveBuffer(GetHtclowChannel(channel, m_module_id), receive_buffer, receive_buffer_size); + m_manager->SetSendBuffer(GetHtclowChannel(channel, m_module_id), send_buffer, send_buffer_size); + + R_SUCCEED(); + } + + void HtclowDriver::Close(htclow::ChannelId channel) { + /* Close the channel. */ + const auto result = m_manager->Close(GetHtclowChannel(channel, m_module_id)); + R_ASSERT(result); + } + + Result HtclowDriver::Connect(htclow::ChannelId channel) { + /* Check if we should emulate disconnection. */ + R_UNLESS(!m_disconnection_emulation_enabled, htclow::ResultConnectionFailure()); + + /* Begin connecting. */ + u32 task_id{}; + R_TRY(m_manager->ConnectBegin(std::addressof(task_id), GetHtclowChannel(channel, m_module_id))); + + /* Wait for the task to complete. */ + this->WaitTask(task_id); + + /* Finish connecting. */ + R_TRY(m_manager->ConnectEnd(GetHtclowChannel(channel, m_module_id), task_id)); + + R_SUCCEED(); + } + + void HtclowDriver::Shutdown(htclow::ChannelId channel) { + /* Shut down the channel. */ + m_manager->Shutdown(GetHtclowChannel(channel, m_module_id)); + } + + Result HtclowDriver::Send(s64 *out, const void *src, s64 src_size, htclow::ChannelId channel) { + /* Check if we should emulate disconnection. */ + R_UNLESS(!m_disconnection_emulation_enabled, htclow::ResultConnectionFailure()); + + /* Validate that dst_size is okay. */ + R_UNLESS(util::IsIntValueRepresentable<size_t>(src_size), htclow::ResultOverflow()); + + /* Repeatedly send until we're done. */ + size_t cur_send; + size_t sent; + for (sent = 0; sent < static_cast<size_t>(src_size); sent += cur_send) { + /* Begin sending. */ + u32 task_id{}; + R_TRY(m_manager->SendBegin(std::addressof(task_id), std::addressof(cur_send), static_cast<const u8 *>(src) + sent, static_cast<size_t>(src_size) - sent, GetHtclowChannel(channel, m_module_id))); + + /* Wait for the task to complete. */ + this->WaitTask(task_id); + + /* Finish sending. */ + R_ABORT_UNLESS(m_manager->SendEnd(task_id)); + } + + /* Set the output sent size. */ + *out = static_cast<s64>(sent); + + R_SUCCEED(); + } + + Result HtclowDriver::ReceiveInternal(size_t *out, void *dst, size_t dst_size, htclow::ChannelId channel, htclow::ReceiveOption option) { + /* Determine whether we're blocking. */ + const bool blocking = option != htclow::ReceiveOption_NonBlocking; + + /* Begin receiving. */ + u32 task_id{}; + R_TRY(m_manager->ReceiveBegin(std::addressof(task_id), GetHtclowChannel(channel, m_module_id), blocking ? 1 : 0)); + + /* Wait for the task to complete. */ + this->WaitTask(task_id); + + /* Finish receiving. */ + R_RETURN(m_manager->ReceiveEnd(out, dst, dst_size, GetHtclowChannel(channel, m_module_id), task_id)); + } + + Result HtclowDriver::Receive(s64 *out, void *dst, s64 dst_size, htclow::ChannelId channel, htclow::ReceiveOption option) { + /* Check if we should emulate disconnection. */ + R_UNLESS(!m_disconnection_emulation_enabled, htclow::ResultConnectionFailure()); + + /* Validate that dst_size is okay. */ + R_UNLESS(util::IsIntValueRepresentable<size_t>(dst_size), htclow::ResultOverflow()); + + /* Determine the minimum allowable receive size. */ + size_t min_size; + switch (option) { + case htclow::ReceiveOption_NonBlocking: min_size = 0; break; + case htclow::ReceiveOption_ReceiveAnyData: min_size = 1; break; + case htclow::ReceiveOption_ReceiveAllData: min_size = dst_size; break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Repeatedly receive. */ + size_t received = 0; + do { + size_t cur_received; + const Result result = this->ReceiveInternal(std::addressof(cur_received), static_cast<u8 *>(dst) + received, static_cast<size_t>(dst_size) - received, channel, option); + + if (R_FAILED(result)) { + if (htclow::ResultChannelReceiveBufferEmpty::Includes(result)) { + R_UNLESS(option != htclow::ReceiveOption_NonBlocking, htclow::ResultNonBlockingReceiveFailed()); + } + if (htclow::ResultChannelNotExist::Includes(result)) { + *out = received; + } + R_RETURN(result); + } + + received += cur_received; + } while (received < min_size); + + /* Set the output received size. */ + *out = static_cast<s64>(received); + + R_SUCCEED(); + } + + htclow::ChannelState HtclowDriver::GetChannelState(htclow::ChannelId channel) { + return m_manager->GetChannelState(GetHtclowChannel(channel, m_module_id)); + } + + os::EventType *HtclowDriver::GetChannelStateEvent(htclow::ChannelId channel) { + return m_manager->GetChannelStateEvent(GetHtclowChannel(channel, m_module_id)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/driver/htc_htclow_driver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/driver/htc_htclow_driver.hpp new file mode 100644 index 00000000..6646c6d7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/driver/htc_htclow_driver.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../../../htclow/htclow_manager.hpp" +#include "htc_i_driver.hpp" + +namespace ams::htc::server::driver { + + class HtclowDriver final : public IDriver { + private: + static constexpr size_t DefaultBufferSize = 128_KB; + private: + u8 m_default_receive_buffer[DefaultBufferSize]; + u8 m_default_send_buffer[DefaultBufferSize]; + htclow::HtclowManager *m_manager; + bool m_disconnection_emulation_enabled; + htclow::ModuleId m_module_id; + public: + HtclowDriver(htclow::HtclowManager *manager, htclow::ModuleId module_id) : m_manager(manager), m_disconnection_emulation_enabled(false), m_module_id(module_id) { /* ... */ } + private: + void WaitTask(u32 task_id); + Result ReceiveInternal(size_t *out, void *dst, size_t dst_size, htclow::ChannelId channel, htclow::ReceiveOption option); + public: + virtual void SetDisconnectionEmulationEnabled(bool en) override; + virtual Result Open(htclow::ChannelId channel) override; + virtual Result Open(htclow::ChannelId channel, void *receive_buffer, size_t receive_buffer_size, void *send_buffer, size_t send_buffer_size) override; + virtual void Close(htclow::ChannelId channel) override; + virtual Result Connect(htclow::ChannelId channel) override; + virtual void Shutdown(htclow::ChannelId channel) override; + virtual Result Send(s64 *out, const void *src, s64 src_size, htclow::ChannelId channel) override; + virtual Result Receive(s64 *out, void *dst, s64 dst_size, htclow::ChannelId channel, htclow::ReceiveOption option) override; + virtual htclow::ChannelState GetChannelState(htclow::ChannelId channel) override; + virtual os::EventType *GetChannelStateEvent(htclow::ChannelId channel) override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/driver/htc_i_driver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/driver/htc_i_driver.hpp new file mode 100644 index 00000000..87e5d6c6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/driver/htc_i_driver.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htc::server::driver { + + class IDriver { + public: + virtual void SetDisconnectionEmulationEnabled(bool en) = 0; + virtual Result Open(htclow::ChannelId channel) = 0; + virtual Result Open(htclow::ChannelId channel, void *receive_buffer, size_t receive_buffer_size, void *send_buffer, size_t send_buffer_size) = 0; + virtual void Close(htclow::ChannelId channel) = 0; + virtual Result Connect(htclow::ChannelId channel) = 0; + virtual void Shutdown(htclow::ChannelId channel) = 0; + virtual Result Send(s64 *out, const void *src, s64 src_size, htclow::ChannelId channel) = 0; + virtual Result Receive(s64 *out, void *dst, s64 dst_size, htclow::ChannelId channel, htclow::ReceiveOption option) = 0; + virtual htclow::ChannelState GetChannelState(htclow::ChannelId channel) = 0; + virtual os::EventType *GetChannelStateEvent(htclow::ChannelId channel) = 0; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htc_service_object.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htc_service_object.cpp new file mode 100644 index 00000000..03e31443 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htc_service_object.cpp @@ -0,0 +1,165 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htc_htc_service_object.hpp" +#include "../../htcfs/htcfs_working_directory.hpp" + +namespace ams::htc::server { + + HtcServiceObject::HtcServiceObject(htclow::HtclowManager *htclow_manager) : m_set(), m_misc_impl(htclow_manager), m_observer(m_misc_impl), m_mutex(){ + /* Initialize our set. */ + m_set.Initialize(MaxSetElements, m_set_memory, sizeof(m_set_memory)); + } + + + HtcmiscImpl *HtcServiceObject::GetHtcmiscImpl() { + return std::addressof(m_misc_impl); + } + + Result HtcServiceObject::GetEnvironmentVariable(sf::Out<s32> out_size, const sf::OutBuffer &out, const sf::InBuffer &name) { + /* Get the variable. */ + size_t var_size = std::numeric_limits<size_t>::max(); + R_TRY(m_misc_impl.GetEnvironmentVariable(std::addressof(var_size), reinterpret_cast<char *>(out.GetPointer()), out.GetSize(), reinterpret_cast<const char *>(name.GetPointer()), name.GetSize())); + + /* Check the output size. */ + R_UNLESS(util::IsIntValueRepresentable<s32>(var_size), htc::ResultUnknown()); + + /* Set the output size. */ + *out_size = static_cast<s32>(var_size); + R_SUCCEED(); + } + + Result HtcServiceObject::GetEnvironmentVariableLength(sf::Out<s32> out_size, const sf::InBuffer &name) { + /* Get the variable. */ + size_t var_size = std::numeric_limits<size_t>::max(); + R_TRY(m_misc_impl.GetEnvironmentVariableLength(std::addressof(var_size), reinterpret_cast<const char *>(name.GetPointer()), name.GetSize())); + + /* Check the output size. */ + R_UNLESS(util::IsIntValueRepresentable<s32>(var_size), htc::ResultUnknown()); + + /* Set the output size. */ + *out_size = static_cast<s32>(var_size); + R_SUCCEED(); + } + + Result HtcServiceObject::GetHostConnectionEvent(sf::OutCopyHandle out) { + /* Set the output handle. */ + out.SetValue(m_observer.GetConnectEvent()->GetReadableHandle(), false); + R_SUCCEED(); + } + + Result HtcServiceObject::GetHostDisconnectionEvent(sf::OutCopyHandle out) { + /* Set the output handle. */ + out.SetValue(m_observer.GetDisconnectEvent()->GetReadableHandle(), false); + R_SUCCEED(); + } + + Result HtcServiceObject::GetHostConnectionEventForSystem(sf::OutCopyHandle out) { + /* NOTE: Nintendo presumably reserved this command in case they need it, but they haven't implemented it yet. */ + AMS_UNUSED(out); + AMS_ABORT("HostEventForSystem not implemented."); + } + + Result HtcServiceObject::GetHostDisconnectionEventForSystem(sf::OutCopyHandle out) { + /* NOTE: Nintendo presumably reserved this command in case they need it, but they haven't implemented it yet. */ + AMS_UNUSED(out); + AMS_ABORT("HostEventForSystem not implemented."); + } + + Result HtcServiceObject::GetWorkingDirectoryPath(const sf::OutBuffer &out, s32 max_len) { + R_RETURN(htcfs::GetWorkingDirectory(reinterpret_cast<char *>(out.GetPointer()), max_len)); + } + + Result HtcServiceObject::GetWorkingDirectoryPathSize(sf::Out<s32> out_size) { + R_RETURN(htcfs::GetWorkingDirectorySize(out_size.GetPointer())); + } + + Result HtcServiceObject::RunOnHostStart(sf::Out<u32> out_id, sf::OutCopyHandle out, const sf::InBuffer &args) { + /* Begin the run on host task. */ + os::NativeHandle event_handle; + R_TRY(m_misc_impl.RunOnHostBegin(out_id.GetPointer(), std::addressof(event_handle), reinterpret_cast<const char *>(args.GetPointer()), args.GetSize())); + + /* Add the task id to our set. */ + { + std::scoped_lock lk(m_mutex); + m_set.insert(*out_id); + } + + /* Set the output event. */ + out.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result HtcServiceObject::RunOnHostResults(sf::Out<s32> out_result, u32 id) { + /* Verify that we have the task. */ + { + std::scoped_lock lk(m_mutex); + R_UNLESS(m_set.erase(id), htc::ResultInvalidTaskId()); + } + + /* Finish the run on host task. */ + R_RETURN(m_misc_impl.RunOnHostEnd(out_result.GetPointer(), id)); + } + + Result HtcServiceObject::GetBridgeIpAddress(const sf::OutBuffer &out) { + /* NOTE: Atmosphere does not support HostBridge, and it's unclear if we ever will. */ + AMS_UNUSED(out); + AMS_ABORT("HostBridge currently not supported."); + } + + Result HtcServiceObject::GetBridgePort(const sf::OutBuffer &out) { + /* NOTE: Atmosphere does not support HostBridge, and it's unclear if we ever will. */ + AMS_UNUSED(out); + AMS_ABORT("HostBridge currently not supported."); + } + + Result HtcServiceObject::SetCradleAttached(bool attached) { + /* NOTE: Atmosphere does not support HostBridge, and it's unclear if we ever will. */ + AMS_UNUSED(attached); + AMS_ABORT("HostBridge currently not supported."); + } + + Result HtcServiceObject::GetBridgeSubnetMask(const sf::OutBuffer &out) { + /* NOTE: Atmosphere does not support HostBridge, and it's unclear if we ever will. */ + AMS_UNUSED(out); + AMS_ABORT("HostBridge currently not supported."); + } + + Result HtcServiceObject::GetBridgeMacAddress(const sf::OutBuffer &out) { + /* NOTE: Atmosphere does not support HostBridge, and it's unclear if we ever will. */ + AMS_UNUSED(out); + AMS_ABORT("HostBridge currently not supported."); + } + + Result HtcServiceObject::SetBridgeIpAddress(const sf::InBuffer &arg) { + /* NOTE: Atmosphere does not support HostBridge, and it's unclear if we ever will. */ + AMS_UNUSED(arg); + AMS_ABORT("HostBridge currently not supported."); + } + + Result HtcServiceObject::SetBridgeSubnetMask(const sf::InBuffer &arg) { + /* NOTE: Atmosphere does not support HostBridge, and it's unclear if we ever will. */ + AMS_UNUSED(arg); + AMS_ABORT("HostBridge currently not supported."); + } + + Result HtcServiceObject::SetBridgePort(const sf::InBuffer &arg) { + /* NOTE: Atmosphere does not support HostBridge, and it's unclear if we ever will. */ + AMS_UNUSED(arg); + AMS_ABORT("HostBridge currently not supported."); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htc_service_object.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htc_service_object.hpp new file mode 100644 index 00000000..11087ad2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htc_service_object.hpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../../htclow/htclow_manager.hpp" +#include "htc_htcmisc_impl.hpp" +#include "htc_observer.hpp" + +namespace ams::htc::server { + + class HtcServiceObject { + private: + static constexpr inline auto MaxSetElements = 0x48; + using Set = util::FixedSet<u32>; + private: + u8 m_set_memory[Set::GetRequiredMemorySize(MaxSetElements)]; + Set m_set; + HtcmiscImpl m_misc_impl; + Observer m_observer; + os::SdkMutex m_mutex; + public: + HtcServiceObject(htclow::HtclowManager *htclow_manager); + public: + HtcmiscImpl *GetHtcmiscImpl(); + public: + Result GetEnvironmentVariable(sf::Out<s32> out_size, const sf::OutBuffer &out, const sf::InBuffer &name); + Result GetEnvironmentVariableLength(sf::Out<s32> out_size, const sf::InBuffer &name); + Result GetHostConnectionEvent(sf::OutCopyHandle out); + Result GetHostDisconnectionEvent(sf::OutCopyHandle out); + Result GetHostConnectionEventForSystem(sf::OutCopyHandle out); + Result GetHostDisconnectionEventForSystem(sf::OutCopyHandle out); + Result GetBridgeIpAddress(const sf::OutBuffer &out); + Result GetBridgePort(const sf::OutBuffer &out); + Result SetCradleAttached(bool attached); + Result GetBridgeSubnetMask(const sf::OutBuffer &out); + Result GetBridgeMacAddress(const sf::OutBuffer &out); + Result GetWorkingDirectoryPath(const sf::OutBuffer &out, s32 max_len); + Result GetWorkingDirectoryPathSize(sf::Out<s32> out_size); + Result RunOnHostStart(sf::Out<u32> out_id, sf::OutCopyHandle out, const sf::InBuffer &args); + Result RunOnHostResults(sf::Out<s32> out_result, u32 id); + Result SetBridgeIpAddress(const sf::InBuffer &arg); + Result SetBridgeSubnetMask(const sf::InBuffer &arg); + Result SetBridgePort(const sf::InBuffer &arg); + + }; + static_assert(tma::IsIHtcManager<HtcServiceObject>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htcmisc_hipc_server.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htcmisc_hipc_server.cpp new file mode 100644 index 00000000..9c875f99 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htcmisc_hipc_server.cpp @@ -0,0 +1,86 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htc_htc_service_object.hpp" +#include "htc_htcmisc_manager.hpp" + +namespace ams::htc::server { + + namespace { + + static constexpr inline size_t NumServers = 1; + static constexpr inline size_t MaxSessions = 30; + static constexpr inline sm::ServiceName ServiceName = sm::ServiceName::Encode("htc"); + + using ServerOptions = sf::hipc::DefaultServerManagerOptions; + using ServerManager = sf::hipc::ServerManager<NumServers, ServerOptions, MaxSessions>; + + constinit util::TypedStorage<ServerManager> g_server_manager_storage = {}; + constinit ServerManager *g_server_manager = nullptr; + + constinit HtcmiscImpl *g_misc_impl = nullptr; + + } + + void InitializeHtcmiscServer(htclow::HtclowManager *htclow_manager) { + /* Check that we haven't already initialized. */ + AMS_ASSERT(g_server_manager == nullptr); + + /* Create/Set the server manager pointer. */ + g_server_manager = util::ConstructAt(g_server_manager_storage); + + /* Create and register the htc manager object. */ + HtcServiceObject *service_object; + R_ABORT_UNLESS(g_server_manager->RegisterObjectForServer(CreateHtcmiscManager(std::addressof(service_object), htclow_manager), ServiceName, MaxSessions)); + + /* Set the misc impl. */ + g_misc_impl = service_object->GetHtcmiscImpl(); + + /* Start the server. */ + g_server_manager->ResumeProcessing(); + } + + void FinalizeHtcmiscServer() { + /* Check that we've already initialized. */ + AMS_ASSERT(g_server_manager != nullptr); + + /* Clear the misc impl. */ + g_misc_impl = nullptr; + + /* Clear and destroy. */ + std::destroy_at(g_server_manager); + g_server_manager = nullptr; + } + + void LoopHtcmiscServer() { + /* Check that we've already initialized. */ + AMS_ASSERT(g_server_manager != nullptr); + + g_server_manager->LoopProcess(); + } + + void RequestStopHtcmiscServer() { + /* Check that we've already initialized. */ + AMS_ASSERT(g_server_manager != nullptr); + + g_server_manager->RequestStopProcessing(); + } + + HtcmiscImpl *GetHtcmiscImpl() { + return g_misc_impl; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htcmisc_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htcmisc_impl.cpp new file mode 100644 index 00000000..eeb5dd32 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htcmisc_impl.cpp @@ -0,0 +1,254 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htc_htcmisc_impl.hpp" + +namespace ams::htc::server { + + namespace { + + alignas(os::ThreadStackAlignment) u8 g_client_thread_stack[os::MemoryPageSize]; + alignas(os::ThreadStackAlignment) u8 g_server_thread_stack[os::MemoryPageSize]; + + } + + HtcmiscImpl::HtcmiscImpl(htclow::HtclowManager *htclow_manager) + : m_htclow_driver(htclow_manager, htclow::ModuleId::Htcmisc), + m_driver_manager(std::addressof(m_htclow_driver)), + m_rpc_client(std::addressof(m_htclow_driver), HtcmiscClientChannelId), + m_rpc_server(std::addressof(m_htclow_driver), HtcmiscServerChannelId), + m_cancel_event(os::EventClearMode_ManualClear), + m_cancelled(false), + m_connection_event(os::EventClearMode_ManualClear), + m_client_connected(false), + m_server_connected(false), + m_connected(false), + m_connection_mutex() + { + /* Create the client thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_client_thread), ClientThreadEntry, this, g_client_thread_stack, sizeof(g_client_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(htc, Htcmisc))); + + /* Set the client thread name. */ + os::SetThreadNamePointer(std::addressof(m_client_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, Htcmisc)); + + /* Start the client thread. */ + os::StartThread(std::addressof(m_client_thread)); + + /* Create the server thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_server_thread), ServerThreadEntry, this, g_server_thread_stack, sizeof(g_server_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(htc, Htcmisc))); + + /* Set the server thread name. */ + os::SetThreadNamePointer(std::addressof(m_server_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, Htcmisc)); + + /* Start the server thread. */ + os::StartThread(std::addressof(m_server_thread)); + } + + HtcmiscImpl::~HtcmiscImpl() { + /* Cancel ourselves. */ + this->Cancel(); + + /* Wait for our threads to be done, and destroy them. */ + os::WaitThread(std::addressof(m_client_thread)); + os::DestroyThread(std::addressof(m_client_thread)); + os::WaitThread(std::addressof(m_server_thread)); + os::DestroyThread(std::addressof(m_server_thread)); + } + + void HtcmiscImpl::Cancel() { + /* Set ourselves as cancelled. */ + m_cancelled = true; + + /* Signal our cancel event. */ + m_cancel_event.Signal(); + } + + void HtcmiscImpl::WaitTask(u32 task_id) { + return m_rpc_client.Wait(task_id); + } + + Result HtcmiscImpl::GetEnvironmentVariable(size_t *out_size, char *dst, size_t dst_size, const char *name, size_t name_size) { + /* Begin the task. */ + u32 task_id{}; + R_TRY(m_rpc_client.Begin<rpc::GetEnvironmentVariableTask>(std::addressof(task_id), name, name_size)); + + /* Wait for the task to complete. */ + this->WaitTask(task_id); + + /* Finish the task. */ + R_TRY(m_rpc_client.End<rpc::GetEnvironmentVariableTask>(task_id, out_size, dst, dst_size)); + + R_SUCCEED(); + } + + Result HtcmiscImpl::GetEnvironmentVariableLength(size_t *out_size, const char *name, size_t name_size) { + /* Begin the task. */ + u32 task_id{}; + R_TRY(m_rpc_client.Begin<rpc::GetEnvironmentVariableLengthTask>(std::addressof(task_id), name, name_size)); + + /* Wait for the task to complete. */ + this->WaitTask(task_id); + + /* Finish the task. */ + R_TRY(m_rpc_client.End<rpc::GetEnvironmentVariableLengthTask>(task_id, out_size)); + + R_SUCCEED(); + } + + Result HtcmiscImpl::RunOnHostBegin(u32 *out_task_id, os::NativeHandle *out_event, const char *args, size_t args_size) { + /* Begin the task. */ + u32 task_id{}; + R_TRY(m_rpc_client.Begin<rpc::RunOnHostTask>(std::addressof(task_id), args, args_size)); + + /* Detach the task. */ + *out_task_id = task_id; + *out_event = m_rpc_client.DetachReadableHandle(task_id); + + R_SUCCEED(); + } + + Result HtcmiscImpl::RunOnHostEnd(s32 *out_result, u32 task_id) { + /* Finish the task. */ + s32 res; + R_TRY(m_rpc_client.End<rpc::RunOnHostTask>(task_id, std::addressof(res))); + + /* Set output. */ + *out_result = res; + + R_SUCCEED(); + } + + void HtcmiscImpl::ClientThread() { + /* Loop so long as we're not cancelled. */ + while (!m_cancelled) { + /* Open the rpc client. */ + m_rpc_client.Open(); + + /* Ensure we close, if something goes wrong. */ + auto client_guard = SCOPE_GUARD { m_rpc_client.Close(); }; + + /* Wait for the rpc client. */ + if (m_rpc_client.WaitAny(htclow::ChannelState_Connectable, m_cancel_event.GetBase()) != 0) { + break; + } + + /* Start the rpc client. */ + if (R_FAILED(m_rpc_client.Start())) { + break; + } + + /* We're connected! */ + this->SetClientConnectionEvent(true); + client_guard.Cancel(); + + /* We're connected, so we want to cleanup when we're done. */ + ON_SCOPE_EXIT { + m_rpc_client.Close(); + m_rpc_client.Cancel(); + m_rpc_client.Wait(); + this->SetClientConnectionEvent(false); + }; + + /* Wait to become disconnected. */ + if (m_rpc_client.WaitAny(htclow::ChannelState_Disconnected, m_cancel_event.GetBase()) != 0) { + break; + } + } + } + + void HtcmiscImpl::ServerThread() { + /* Loop so long as we're not cancelled. */ + while (!m_cancelled) { + /* Open the rpc server. */ + m_rpc_server.Open(); + + /* Ensure we close, if something goes wrong. */ + auto server_guard = SCOPE_GUARD { m_rpc_server.Close(); }; + + /* Wait for the rpc server. */ + if (m_rpc_server.WaitAny(htclow::ChannelState_Connectable, m_cancel_event.GetBase()) != 0) { + break; + } + + /* Start the rpc server. */ + if (R_FAILED(m_rpc_server.Start())) { + break; + } + + /* We're connected! */ + this->SetServerConnectionEvent(true); + server_guard.Cancel(); + + /* We're connected, so we want to cleanup when we're done. */ + ON_SCOPE_EXIT { + m_rpc_server.Close(); + m_rpc_server.Cancel(); + m_rpc_server.Wait(); + this->SetServerConnectionEvent(false); + }; + + /* Wait to become disconnected. */ + if (m_rpc_server.WaitAny(htclow::ChannelState_Disconnected, m_cancel_event.GetBase()) != 0) { + break; + } + } + } + + void HtcmiscImpl::SetClientConnectionEvent(bool en) { + /* Lock ourselves. */ + std::scoped_lock lk(m_connection_mutex); + + /* Update our state. */ + if (m_client_connected != en) { + m_client_connected = en; + this->UpdateConnectionEvent(); + } + } + + void HtcmiscImpl::SetServerConnectionEvent(bool en) { + /* Lock ourselves. */ + std::scoped_lock lk(m_connection_mutex); + + /* Update our state. */ + if (m_server_connected != en) { + m_server_connected = en; + this->UpdateConnectionEvent(); + } + } + + void HtcmiscImpl::UpdateConnectionEvent() { + /* Determine if we're connected. */ + const bool connected = m_client_connected && m_server_connected; + + /* Update our state. */ + if (m_connected != connected) { + m_connected = connected; + m_connection_event.Signal(); + } + } + + os::EventType *HtcmiscImpl::GetConnectionEvent() const { + return m_connection_event.GetBase(); + } + + bool HtcmiscImpl::IsConnected() const { + /* Lock ourselves. */ + std::scoped_lock lk(m_connection_mutex); + + return m_connected; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htcmisc_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htcmisc_impl.hpp new file mode 100644 index 00000000..8ed96d5d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htcmisc_impl.hpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../../htclow/htclow_manager.hpp" +#include "driver/htc_htclow_driver.hpp" +#include "driver/htc_driver_manager.hpp" +#include "rpc/htc_rpc_client.hpp" +#include "rpc/htc_htcmisc_rpc_server.hpp" + +namespace ams::htc::server { + + class HtcmiscImpl { + private: + driver::HtclowDriver m_htclow_driver; + driver::DriverManager m_driver_manager; + rpc::RpcClient m_rpc_client; + rpc::HtcmiscRpcServer m_rpc_server; + os::ThreadType m_client_thread; + os::ThreadType m_server_thread; + os::Event m_cancel_event; + bool m_cancelled; + mutable os::Event m_connection_event; + bool m_client_connected; + bool m_server_connected; + bool m_connected; + mutable os::SdkMutex m_connection_mutex; + private: + static void ClientThreadEntry(void *arg) { static_cast<HtcmiscImpl *>(arg)->ClientThread(); } + static void ServerThreadEntry(void *arg) { static_cast<HtcmiscImpl *>(arg)->ServerThread(); } + + void ClientThread(); + void ServerThread(); + public: + HtcmiscImpl(htclow::HtclowManager *htclow_manager); + ~HtcmiscImpl(); + + os::EventType *GetConnectionEvent() const; + bool IsConnected() const; + private: + void SetClientConnectionEvent(bool en); + void SetServerConnectionEvent(bool en); + + void UpdateConnectionEvent(); + + void WaitTask(u32 task_id); + public: + void Cancel(); + + Result GetEnvironmentVariable(size_t *out_size, char *dst, size_t dst_size, const char *name, size_t name_size); + Result GetEnvironmentVariableLength(size_t *out_size, const char *name, size_t name_size); + + Result RunOnHostBegin(u32 *out_task_id, os::NativeHandle *out_event, const char *args, size_t args_size); + Result RunOnHostEnd(s32 *out_result, u32 task_id); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htcmisc_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htcmisc_manager.cpp new file mode 100644 index 00000000..1f35c2db --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htcmisc_manager.cpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htc_htcmisc_manager.hpp" + +namespace ams::htc::server { + + namespace { + + + mem::StandardAllocator g_allocator; + sf::StandardAllocatorMemoryResource g_sf_resource(std::addressof(g_allocator)); + + alignas(os::MemoryPageSize) constinit u8 g_heap[util::AlignUp(sizeof(HtcServiceObject), os::MemoryPageSize) + 12_KB]; + + class StaticAllocatorInitializer { + public: + StaticAllocatorInitializer() { + g_allocator.Initialize(g_heap, sizeof(g_heap)); + } + } g_static_allocator_initializer; + + using ObjectFactory = sf::ObjectFactory<sf::MemoryResourceAllocationPolicy>; + + } + + sf::SharedPointer<tma::IHtcManager> CreateHtcmiscManager(HtcServiceObject **out, htclow::HtclowManager *htclow_manager) { + auto obj = ObjectFactory::CreateSharedEmplaced<tma::IHtcManager, HtcServiceObject>(std::addressof(g_sf_resource), htclow_manager); + *out = std::addressof(obj.GetImpl()); + return obj; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htcmisc_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htcmisc_manager.hpp new file mode 100644 index 00000000..fefe5cc9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_htcmisc_manager.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htc_htc_service_object.hpp" + +namespace ams::htc::server { + + sf::SharedPointer<tma::IHtcManager> CreateHtcmiscManager(HtcServiceObject **out, htclow::HtclowManager *htclow_manager); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_observer.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_observer.cpp new file mode 100644 index 00000000..a80708d8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_observer.cpp @@ -0,0 +1,120 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htc_observer.hpp" +#include "../../htcs/impl/htcs_manager.hpp" + +namespace ams::htc::server { + + Observer::Observer(const HtcmiscImpl &misc_impl) + : m_connect_event(os::EventClearMode_ManualClear, true), + m_disconnect_event(os::EventClearMode_ManualClear, true), + m_stop_event(os::EventClearMode_ManualClear), + m_misc_impl(misc_impl), + m_thread_running(false), + m_stopped(false), + m_connected(false), + m_is_service_available(false) + { + /* Initialize htcs library. */ + htcs::impl::HtcsManagerHolder::AddReference(); + + /* Update our event state. */ + this->UpdateEvent(); + + /* Start. */ + R_ABORT_UNLESS(this->Start()); + } + + Result Observer::Start() { + /* Check that we're not already running. */ + AMS_ASSERT(!m_thread_running); + + /* Create the thread. */ + R_TRY(os::CreateThread(std::addressof(m_observer_thread), ObserverThreadEntry, this, m_observer_thread_stack, sizeof(m_observer_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtcObserver))); + + /* Set the thread name pointer. */ + os::SetThreadNamePointer(std::addressof(m_observer_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtcObserver)); + + /* Mark our thread as running. */ + m_thread_running = true; + m_stopped = false; + + /* Start our thread. */ + os::StartThread(std::addressof(m_observer_thread)); + + R_SUCCEED(); + } + + void Observer::UpdateEvent() { + if (m_connected && m_is_service_available) { + m_disconnect_event.Clear(); + m_connect_event.Signal(); + } else { + m_connect_event.Clear(); + m_disconnect_event.Signal(); + } + } + + void Observer::ObserverThreadBody() { + /* When we're done observing, clear our state. */ + ON_SCOPE_EXIT { + m_connected = false; + m_is_service_available = false; + this->UpdateEvent(); + }; + + /* Get the htcs manager. */ + auto * const htcs_manager = htcs::impl::HtcsManagerHolder::GetHtcsManager(); + + /* Get the events we're waiting on. */ + os::EventType * const stop_event = m_stop_event.GetBase(); + os::EventType * const conn_event = m_misc_impl.GetConnectionEvent(); + os::EventType * const htcs_event = htcs_manager->GetServiceAvailabilityEvent(); + + /* Loop until we're asked to stop. */ + while (!m_stopped) { + /* Wait for an event to be signaled. */ + const auto index = os::WaitAny(stop_event, conn_event /*, htcs_event */); + switch (index) { + case 0: + /* Stop event, just break out of the loop. */ + os::ClearEvent(stop_event); + break; + case 1: + /* Connection event, update our connection status. */ + os::ClearEvent(conn_event); + m_connected = m_misc_impl.IsConnected(); + break; + case 2: + /* Htcs event, update our service status. */ + os::ClearEvent(htcs_event); + m_is_service_available = htcs_manager->IsServiceAvailable(); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* If the event was our stop event, break. */ + if (index == 0) { + break; + } + + /* Update event status. */ + this->UpdateEvent(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_observer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_observer.hpp new file mode 100644 index 00000000..b04b0603 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_observer.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htc_htcmisc_impl.hpp" + +namespace ams::htc::server { + + class Observer { + private: + os::SystemEvent m_connect_event; + os::SystemEvent m_disconnect_event; + os::Event m_stop_event; + os::ThreadType m_observer_thread; + const HtcmiscImpl &m_misc_impl; + bool m_thread_running; + bool m_stopped; + bool m_connected; + bool m_is_service_available; + alignas(os::ThreadStackAlignment) u8 m_observer_thread_stack[os::MemoryPageSize]; + public: + Observer(const HtcmiscImpl &misc_impl); + private: + static void ObserverThreadEntry(void *arg) { static_cast<Observer *>(arg)->ObserverThreadBody(); } + + void ObserverThreadBody(); + private: + Result Start(); + + void UpdateEvent(); + public: + os::SystemEvent *GetConnectEvent() { return std::addressof(m_connect_event); } + os::SystemEvent *GetDisconnectEvent() { return std::addressof(m_disconnect_event); } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_power_state_control.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_power_state_control.cpp new file mode 100644 index 00000000..243cc49e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/htc_power_state_control.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../../htclow/htclow_manager.hpp" + +namespace ams::htc::server { + + namespace { + + constinit htclow::HtclowManager *g_htclow_manager = nullptr; + + constexpr const psc::PmModuleId PscModuleDependencies[] = { psc::PmModuleId_Pcie, psc::PmModuleId_Usb }; + psc::PmModule g_pm_module; + + constinit bool g_is_asleep = false; + constinit bool g_is_suspended = false; + + } + + void InitializePowerStateMonitor(htclow::impl::DriverType driver_type, htclow::HtclowManager *htclow_manager) { + AMS_UNUSED(driver_type); + + /* Set the htclow manager. */ + g_htclow_manager = htclow_manager; + + /* Initialize pm module. */ + R_ABORT_UNLESS(g_pm_module.Initialize(psc::PmModuleId_TmaHostIo, PscModuleDependencies, util::size(PscModuleDependencies), os::EventClearMode_AutoClear)); + + /* We're neither asleep nor suspended. */ + g_is_asleep = false; + g_is_suspended = false; + } + + void FinalizePowerStateMonitor() { + R_ABORT_UNLESS(g_pm_module.Finalize()); + } + + void LoopMonitorPowerState() { + /* Get the psc module's event pointer. */ + auto *event = g_pm_module.GetEventPointer(); + while (true) { + /* Wait for a new power state event. */ + event->Wait(); + + /* Get the power state. */ + psc::PmState pm_state; + psc::PmFlagSet pm_flags; + R_ABORT_UNLESS(g_pm_module.GetRequest(std::addressof(pm_state), std::addressof(pm_flags))); + + /* Update sleeping state. */ + switch (pm_state) { + case psc::PmState_FullAwake: + if (g_is_asleep) { + g_htclow_manager->NotifyAwake(); + g_is_asleep = false; + } + break; + case psc::PmState_MinimumAwake: + case psc::PmState_SleepReady: + case psc::PmState_EssentialServicesSleepReady: + case psc::PmState_EssentialServicesAwake: + if (!g_is_asleep) { + g_htclow_manager->NotifyAsleep(); + g_is_asleep = true; + } + break; + default: + break; + } + + /* Update suspend state. */ + switch (pm_state) { + case psc::PmState_FullAwake: + case psc::PmState_MinimumAwake: + if (g_is_suspended) { + g_htclow_manager->Resume(); + g_is_suspended = false; + } + break; + case psc::PmState_SleepReady: + case psc::PmState_EssentialServicesSleepReady: + case psc::PmState_EssentialServicesAwake: + if (!g_is_suspended) { + g_htclow_manager->Suspend(); + g_is_suspended = true; + } + break; + default: + break; + } + + /* Acknowledge the pm request. */ + R_ABORT_UNLESS(g_pm_module.Acknowledge(pm_state, ResultSuccess())); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_htcmisc_rpc_server.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_htcmisc_rpc_server.cpp new file mode 100644 index 00000000..a3d05ef7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_htcmisc_rpc_server.cpp @@ -0,0 +1,178 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htc_htcmisc_rpc_server.hpp" + +namespace ams::htc::server::rpc { + + namespace { + + constexpr inline size_t ReceiveThreadStackSize = os::MemoryPageSize; + + alignas(os::ThreadStackAlignment) constinit u8 g_receive_thread_stack[ReceiveThreadStackSize]; + + } + + HtcmiscRpcServer::HtcmiscRpcServer(driver::IDriver *driver, htclow::ChannelId channel) + : m_allocator(nullptr), + m_driver(driver), + m_channel_id(channel), + m_receive_thread_stack(g_receive_thread_stack), + m_cancelled(false), + m_thread_running(false) + { + /* ... */ + } + + void HtcmiscRpcServer::Open() { + R_ABORT_UNLESS(m_driver->Open(m_channel_id, m_driver_receive_buffer, sizeof(m_driver_receive_buffer), m_driver_send_buffer, sizeof(m_driver_send_buffer))); + } + + void HtcmiscRpcServer::Close() { + m_driver->Close(m_channel_id); + } + + Result HtcmiscRpcServer::Start() { + /* Connect. */ + R_TRY(m_driver->Connect(m_channel_id)); + + /* Create our thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_receive_thread), ReceiveThreadEntry, this, m_receive_thread_stack, ReceiveThreadStackSize, AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtcmiscReceive))); + + /* Set thread name pointer. */ + os::SetThreadNamePointer(std::addressof(m_receive_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtcmiscReceive)); + + /* Start thread. */ + os::StartThread(std::addressof(m_receive_thread)); + + /* Set initial state. */ + m_cancelled = false; + m_thread_running = true; + + R_SUCCEED(); + } + + void HtcmiscRpcServer::Cancel() { + /* Set cancelled. */ + m_cancelled = true; + } + + void HtcmiscRpcServer::Wait() { + /* Wait for thread to not be running. */ + if (m_thread_running) { + os::WaitThread(std::addressof(m_receive_thread)); + os::DestroyThread(std::addressof(m_receive_thread)); + } + m_thread_running = false; + } + + int HtcmiscRpcServer::WaitAny(htclow::ChannelState state, os::EventType *event) { + /* Check if we're already signaled. */ + if (os::TryWaitEvent(event)) { + return 1; + } + + /* Wait. */ + while (m_driver->GetChannelState(m_channel_id) != state) { + const auto idx = os::WaitAny(m_driver->GetChannelStateEvent(m_channel_id), event); + if (idx != 0) { + return idx; + } + + /* Clear the channel state event. */ + os::ClearEvent(m_driver->GetChannelStateEvent(m_channel_id)); + } + + return 0; + } + + Result HtcmiscRpcServer::ReceiveThread() { + /* Loop forever. */ + auto *header = reinterpret_cast<HtcmiscRpcPacket *>(m_receive_buffer); + while (true) { + /* Try to receive a packet header. */ + R_TRY(this->ReceiveHeader(header)); + + /* Track how much we've received. */ + size_t received = sizeof(*header); + + /* If the packet has one, receive its body. */ + if (header->body_size > 0) { + /* Sanity check the body size. */ + AMS_ABORT_UNLESS(util::IsIntValueRepresentable<size_t>(header->body_size)); + AMS_ABORT_UNLESS(static_cast<size_t>(header->body_size) <= sizeof(m_receive_buffer) - received); + + /* Receive the body. */ + R_TRY(this->ReceiveBody(header->data, header->body_size)); + + /* Note that we received the body. */ + received += header->body_size; + } + + /* Check that the packet is a request packet. */ + R_UNLESS(header->category == HtcmiscPacketCategory::Request, htc::ResultInvalidCategory()); + + /* Handle specific requests. */ + if (header->type == HtcmiscPacketType::SetTargetName) { + R_TRY(this->ProcessSetTargetNameRequest(header->data, header->body_size, header->task_id)); + } + } + } + + Result HtcmiscRpcServer::ProcessSetTargetNameRequest(const char *name, size_t size, u32 task_id) { + /* TODO: we need to use settings::fwdbg::SetSettingsItemValue here, but this will require ams support for set:fd re-enable? */ + /* Needs some thought. */ + AMS_UNUSED(name, size, task_id); + AMS_ABORT("HtcmiscRpcServer::ProcessSetTargetNameRequest"); + } + + Result HtcmiscRpcServer::ReceiveHeader(HtcmiscRpcPacket *header) { + /* Receive. */ + s64 received; + R_TRY(m_driver->Receive(std::addressof(received), reinterpret_cast<char *>(header), sizeof(*header), m_channel_id, htclow::ReceiveOption_ReceiveAllData)); + + /* Check size. */ + R_UNLESS(static_cast<size_t>(received) == sizeof(*header), htc::ResultInvalidSize()); + + R_SUCCEED(); + } + + Result HtcmiscRpcServer::ReceiveBody(char *dst, size_t size) { + /* Receive. */ + s64 received; + R_TRY(m_driver->Receive(std::addressof(received), dst, size, m_channel_id, htclow::ReceiveOption_ReceiveAllData)); + + /* Check size. */ + R_UNLESS(static_cast<size_t>(received) == size, htc::ResultInvalidSize()); + + R_SUCCEED(); + } + + Result HtcmiscRpcServer::SendRequest(const char *src, size_t size) { + /* Sanity check our size. */ + AMS_ASSERT(util::IsIntValueRepresentable<s64>(size)); + + /* Send the data. */ + s64 sent; + R_TRY(m_driver->Send(std::addressof(sent), src, static_cast<s64>(size), m_channel_id)); + + /* Check that we sent the right amount. */ + R_UNLESS(sent == static_cast<s64>(size), htc::ResultInvalidSize()); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_htcmisc_rpc_server.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_htcmisc_rpc_server.hpp new file mode 100644 index 00000000..3b98dd0e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_htcmisc_rpc_server.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../driver/htc_i_driver.hpp" +#include "htc_htcmisc_rpc_tasks.hpp" + +namespace ams::htc::server::rpc { + + class HtcmiscRpcServer { + private: + /* TODO: where is this value coming from, again? */ + static constexpr size_t BufferSize = 1_KB; + private: + mem::StandardAllocator *m_allocator; + driver::IDriver *m_driver; + htclow::ChannelId m_channel_id; + void *m_receive_thread_stack; + os::ThreadType m_receive_thread; + bool m_cancelled; + bool m_thread_running; + char m_receive_buffer[BufferSize]; + char m_send_buffer[BufferSize]; + u8 m_driver_receive_buffer[4_KB]; + u8 m_driver_send_buffer[4_KB]; + private: + static void ReceiveThreadEntry(void *arg) { static_cast<HtcmiscRpcServer *>(arg)->ReceiveThread(); } + + Result ReceiveThread(); + public: + HtcmiscRpcServer(driver::IDriver *driver, htclow::ChannelId channel); + public: + void Open(); + void Close(); + + Result Start(); + void Cancel(); + void Wait(); + + int WaitAny(htclow::ChannelState state, os::EventType *event); + private: + Result ProcessSetTargetNameRequest(const char *name, size_t size, u32 task_id); + + Result ReceiveHeader(HtcmiscRpcPacket *header); + Result ReceiveBody(char *dst, size_t size); + Result SendRequest(const char *src, size_t size); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_htcmisc_rpc_tasks.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_htcmisc_rpc_tasks.cpp new file mode 100644 index 00000000..e31c432f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_htcmisc_rpc_tasks.cpp @@ -0,0 +1,300 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htc_htcmisc_rpc_tasks.hpp" + +namespace ams::htc::server::rpc { + + Result GetEnvironmentVariableTask::SetArguments(const char *args, size_t size) { + /* Copy to our name. */ + const size_t copied = util::Strlcpy(m_name, args, sizeof(m_name)); + m_name_size = copied; + + /* Require that the size be correct. */ + + R_UNLESS(size == copied || size == copied + 1, htc::ResultUnknown()); + + R_SUCCEED(); + } + + void GetEnvironmentVariableTask::Complete(HtcmiscResult result, const char *data, size_t size) { + /* Sanity check input. */ + if (size < sizeof(m_value)) { + /* Convert the result. */ + switch (result) { + case HtcmiscResult::Success: + /* Copy to our value. */ + std::memcpy(m_value, data, size); + m_value[size] = '\x00'; + m_value_size = size + 1; + + m_result = ResultSuccess(); + break; + case HtcmiscResult::UnknownError: + m_result = htc::ResultUnknown(); + break; + case HtcmiscResult::UnsupportedVersion: + m_result = htc::ResultConnectionFailure(); + break; + case HtcmiscResult::InvalidRequest: + m_result = htc::ResultNotFound(); + break; + } + } else { + m_result = htc::ResultUnknown(); + } + + /* Complete the task. */ + Task::Complete(); + } + + Result GetEnvironmentVariableTask::GetResult(size_t *out, char *dst, size_t size) const { + /* Check our task state. */ + AMS_ASSERT(this->GetTaskState() == RpcTaskState::Completed); + + /* Check that we succeeded. */ + R_TRY(m_result); + + /* Check that we can convert successfully. */ + R_UNLESS(util::IsIntValueRepresentable<int>(size), htc::ResultUnknown()); + + /* Copy out. */ + const auto copied = util::Strlcpy(dst, m_value, size); + R_UNLESS(copied < static_cast<int>(size), htc::ResultNotEnoughBuffer()); + + /* Set the output size. */ + *out = m_value_size; + + R_SUCCEED(); + } + + Result GetEnvironmentVariableTask::CreateRequest(size_t *out, char *data, size_t size, u32 task_id) { + /* Validate pre-conditions. */ + AMS_ASSERT(size >= sizeof(HtcmiscRpcPacket)); + AMS_UNUSED(size); + + /* Create the packet. */ + auto *packet = reinterpret_cast<HtcmiscRpcPacket *>(data); + *packet = { + .protocol = HtcmiscProtocol, + .version = HtcmiscMaxVersion, + .category = HtcmiscPacketCategory::Request, + .type = HtcmiscPacketType::GetEnvironmentVariable, + .body_size = this->GetNameSize(), + .task_id = task_id, + .params = { + /* ... */ + }, + }; + + /* Set the packet body. */ + std::memcpy(packet->data, this->GetName(), this->GetNameSize()); + + /* Set the output size. */ + *out = sizeof(*packet) + this->GetNameSize(); + + R_SUCCEED(); + } + + Result GetEnvironmentVariableTask::ProcessResponse(const char *data, size_t size) { + /* Convert the input to a packet. */ + auto *packet = reinterpret_cast<const HtcmiscRpcPacket *>(data); + + /* Process the packet. */ + this->Complete(static_cast<HtcmiscResult>(packet->params[0]), data + sizeof(*packet), size - sizeof(*packet)); + + /* Complete the task. */ + Task::Complete(); + + R_SUCCEED(); + } + + Result GetEnvironmentVariableLengthTask::SetArguments(const char *args, size_t size) { + /* Copy to our name. */ + const size_t copied = util::Strlcpy(m_name, args, sizeof(m_name)); + m_name_size = copied; + + /* Require that the size be correct. */ + + R_UNLESS(size == copied || size == copied + 1, htc::ResultUnknown()); + + R_SUCCEED(); + } + + void GetEnvironmentVariableLengthTask::Complete(HtcmiscResult result, const char *data, size_t size) { + /* Sanity check input. */ + if (size == sizeof(s64)) { + /* Convert the result. */ + switch (result) { + case HtcmiscResult::Success: + /* Copy to our value. */ + s64 tmp; + std::memcpy(std::addressof(tmp), data, sizeof(tmp)); + if (util::IsIntValueRepresentable<size_t>(tmp)) { + m_value_size = static_cast<size_t>(tmp); + } + + m_result = ResultSuccess(); + break; + case HtcmiscResult::UnknownError: + m_result = htc::ResultUnknown(); + break; + case HtcmiscResult::UnsupportedVersion: + m_result = htc::ResultConnectionFailure(); + break; + case HtcmiscResult::InvalidRequest: + m_result = htc::ResultNotFound(); + break; + } + } else { + m_result = htc::ResultUnknown(); + } + + /* Complete the task. */ + Task::Complete(); + } + + Result GetEnvironmentVariableLengthTask::GetResult(size_t *out) const { + /* Check our task state. */ + AMS_ASSERT(this->GetTaskState() == RpcTaskState::Completed); + + /* Check that we succeeded. */ + R_TRY(m_result); + + /* Set the output size. */ + *out = m_value_size; + + R_SUCCEED(); + } + + Result GetEnvironmentVariableLengthTask::CreateRequest(size_t *out, char *data, size_t size, u32 task_id) { + /* Validate pre-conditions. */ + AMS_ASSERT(size >= sizeof(HtcmiscRpcPacket)); + AMS_UNUSED(size); + + /* Create the packet. */ + auto *packet = reinterpret_cast<HtcmiscRpcPacket *>(data); + *packet = { + .protocol = HtcmiscProtocol, + .version = HtcmiscMaxVersion, + .category = HtcmiscPacketCategory::Request, + .type = HtcmiscPacketType::GetEnvironmentVariableLength, + .body_size = this->GetNameSize(), + .task_id = task_id, + .params = { + /* ... */ + }, + }; + + /* Set the packet body. */ + std::memcpy(packet->data, this->GetName(), this->GetNameSize()); + + /* Set the output size. */ + *out = sizeof(*packet) + this->GetNameSize(); + + R_SUCCEED(); + } + + Result GetEnvironmentVariableLengthTask::ProcessResponse(const char *data, size_t size) { + /* Convert the input to a packet. */ + auto *packet = reinterpret_cast<const HtcmiscRpcPacket *>(data); + + /* Process the packet. */ + this->Complete(static_cast<HtcmiscResult>(packet->params[0]), data + sizeof(*packet), size - sizeof(*packet)); + + /* Complete the task. */ + Task::Complete(); + + R_SUCCEED(); + } + + Result RunOnHostTask::SetArguments(const char *args, size_t size) { + /* Verify command fits in our buffer. */ + R_UNLESS(size < sizeof(m_command), htc::ResultNotEnoughBuffer()); + + /* Set our command. */ + std::memcpy(m_command, args, size); + m_command_size = size; + + R_SUCCEED(); + } + + void RunOnHostTask::Complete(int host_result) { + /* Set our host result. */ + m_host_result = host_result; + + /* Signal. */ + m_system_event.Signal(); + + /* Complete the task. */ + Task::Complete(); + } + + Result RunOnHostTask::GetResult(int *out) const { + *out = m_host_result; + R_SUCCEED(); + } + + void RunOnHostTask::Cancel(RpcTaskCancelReason reason) { + /* Cancel the task. */ + Task::Cancel(reason); + + /* Signal our event. */ + m_system_event.Signal(); + } + + Result RunOnHostTask::CreateRequest(size_t *out, char *data, size_t size, u32 task_id) { + /* Validate pre-conditions. */ + AMS_ASSERT(size >= sizeof(HtcmiscRpcPacket)); + AMS_UNUSED(size); + + /* Create the packet. */ + auto *packet = reinterpret_cast<HtcmiscRpcPacket *>(data); + *packet = { + .protocol = HtcmiscProtocol, + .version = HtcmiscMaxVersion, + .category = HtcmiscPacketCategory::Request, + .type = HtcmiscPacketType::RunOnHost, + .body_size = this->GetCommandSize(), + .task_id = task_id, + .params = { + /* ... */ + }, + }; + + /* Set the packet body. */ + std::memcpy(packet->data, this->GetCommand(), this->GetCommandSize()); + + /* Set the output size. */ + *out = sizeof(*packet) + this->GetCommandSize(); + + R_SUCCEED(); + } + + Result RunOnHostTask::ProcessResponse(const char *data, size_t size) { + /* Validate pre-conditions. */ + AMS_ASSERT(size >= sizeof(HtcmiscRpcPacket)); + AMS_UNUSED(size); + + this->Complete(reinterpret_cast<const HtcmiscRpcPacket *>(data)->params[0]); + R_SUCCEED(); + } + + os::SystemEventType *RunOnHostTask::GetSystemEvent() { + return m_system_event.GetBase(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_htcmisc_rpc_tasks.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_htcmisc_rpc_tasks.hpp new file mode 100644 index 00000000..44e409c4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_htcmisc_rpc_tasks.hpp @@ -0,0 +1,147 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htc_rpc_tasks.hpp" + +namespace ams::htc::server::rpc { + + enum class HtcmiscTaskType { + GetEnvironmentVariable = 0, + GetEnvironmentVariableLength = 1, + SetTargetStatus = 2, + RunOnHost = 3, + }; + + enum class HtcmiscResult { + Success = 0, + UnknownError = 1, + UnsupportedVersion = 2, + InvalidRequest = 3, + }; + + enum class HtcmiscPacketCategory : s16 { + Request = 0, + Response = 1, + }; + + enum class HtcmiscPacketType : s16 { + GetMaxProtocolVersion = 0, + SetProtocolVersion = 1, + GetEnvironmentVariable = 16, + GetEnvironmentVariableLength = 17, + SetTargetStatus = 18, + RunOnHost = 19, + GetWorkingDirectory = 20, + GetWorkingDirectorySize = 21, + SetTargetName = 22, + }; + + constexpr inline s16 HtcmiscProtocol = 4; + constexpr inline s16 HtcmiscMaxVersion = 2; + + struct HtcmiscRpcPacket { + s16 protocol; + s16 version; + HtcmiscPacketCategory category; + HtcmiscPacketType type; + s64 body_size; + u32 task_id{}; + u64 params[5]; + char data[]; + }; + static_assert(sizeof(HtcmiscRpcPacket) == 0x40); + + class HtcmiscTask : public Task { + private: + HtcmiscTaskType m_task_type; + public: + HtcmiscTask(HtcmiscTaskType type) : m_task_type(type) { /* ... */ } + + HtcmiscTaskType GetTaskType() const { return m_task_type; } + }; + + class GetEnvironmentVariableTask : public HtcmiscTask { + public: + static constexpr inline HtcmiscTaskType TaskType = HtcmiscTaskType::GetEnvironmentVariable; + private: + char m_name[0x800]; + int m_name_size; + Result m_result; + size_t m_value_size; + char m_value[0x8000]; + public: + GetEnvironmentVariableTask() : HtcmiscTask(HtcmiscTaskType::GetEnvironmentVariable) { /* ... */ } + + Result SetArguments(const char *args, size_t size); + void Complete(HtcmiscResult result, const char *data, size_t size); + Result GetResult(size_t *out, char *dst, size_t size) const; + + const char *GetName() const { return m_name; } + int GetNameSize() const { return m_name_size; } + public: + virtual Result ProcessResponse(const char *data, size_t size) override; + virtual Result CreateRequest(size_t *out, char *data, size_t size, u32 task_id) override; + }; + + class GetEnvironmentVariableLengthTask : public HtcmiscTask { + public: + static constexpr inline HtcmiscTaskType TaskType = HtcmiscTaskType::GetEnvironmentVariableLength; + private: + char m_name[0x800]; + int m_name_size; + Result m_result; + size_t m_value_size; + public: + GetEnvironmentVariableLengthTask() : HtcmiscTask(HtcmiscTaskType::GetEnvironmentVariableLength) { /* ... */ } + + Result SetArguments(const char *args, size_t size); + void Complete(HtcmiscResult result, const char *data, size_t size); + Result GetResult(size_t *out) const; + + const char *GetName() const { return m_name; } + int GetNameSize() const { return m_name_size; } + public: + virtual Result ProcessResponse(const char *data, size_t size) override; + virtual Result CreateRequest(size_t *out, char *data, size_t size, u32 task_id) override; + }; + + class RunOnHostTask : public HtcmiscTask { + public: + static constexpr inline HtcmiscTaskType TaskType = HtcmiscTaskType::RunOnHost; + private: + char m_command[0x2000]; + int m_command_size; + Result m_result; + int m_host_result; + os::SystemEvent m_system_event; + public: + RunOnHostTask() : HtcmiscTask(HtcmiscTaskType::RunOnHost), m_system_event(os::EventClearMode_ManualClear, true) { /* ... */ } + + Result SetArguments(const char *args, size_t size); + void Complete(int host_result); + Result GetResult(int *out) const; + + const char *GetCommand() const { return m_command; } + int GetCommandSize() const { return m_command_size; } + public: + virtual void Cancel(RpcTaskCancelReason reason) override; + virtual Result ProcessResponse(const char *data, size_t size) override; + virtual Result CreateRequest(size_t *out, char *data, size_t size, u32 task_id) override; + virtual os::SystemEventType *GetSystemEvent() override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_rpc_client.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_rpc_client.cpp new file mode 100644 index 00000000..df5f84d5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_rpc_client.cpp @@ -0,0 +1,472 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htc_rpc_client.hpp" + +namespace ams::htc::server::rpc { + + namespace { + + constexpr inline size_t ThreadStackSize = os::MemoryPageSize; + + alignas(os::ThreadStackAlignment) constinit u8 g_receive_thread_stack[ThreadStackSize]; + alignas(os::ThreadStackAlignment) constinit u8 g_send_thread_stack[ThreadStackSize]; + + constinit os::SdkMutex g_rpc_mutex; + + constinit RpcTaskIdFreeList g_task_id_free_list; + constinit RpcTaskTable g_task_table; + + } + + RpcClient::RpcClient(driver::IDriver *driver, htclow::ChannelId channel) + : m_allocator(nullptr), + m_driver(driver), + m_channel_id(channel), + m_receive_thread_stack(g_receive_thread_stack), + m_send_thread_stack(g_send_thread_stack), + m_mutex(g_rpc_mutex), + m_task_id_free_list(g_task_id_free_list), + m_task_table(g_task_table), + m_task_active(), + m_is_htcs_task(), + m_task_queue(), + m_cancelled(false), + m_thread_running(false) + { + /* Initialize all events. */ + for (size_t i = 0; i < MaxRpcCount; ++i) { + os::InitializeEvent(std::addressof(m_receive_buffer_available_events[i]), false, os::EventClearMode_AutoClear); + os::InitializeEvent(std::addressof(m_send_buffer_available_events[i]), false, os::EventClearMode_AutoClear); + } + } + + RpcClient::RpcClient(mem::StandardAllocator *allocator, driver::IDriver *driver, htclow::ChannelId channel) + : m_allocator(allocator), + m_driver(driver), + m_channel_id(channel), + m_receive_thread_stack(m_allocator->Allocate(ThreadStackSize, os::ThreadStackAlignment)), + m_send_thread_stack(m_allocator->Allocate(ThreadStackSize, os::ThreadStackAlignment)), + m_mutex(g_rpc_mutex), + m_task_id_free_list(g_task_id_free_list), + m_task_table(g_task_table), + m_task_active(), + m_is_htcs_task(), + m_task_queue(), + m_cancelled(false), + m_thread_running(false) + { + /* Initialize all events. */ + for (size_t i = 0; i < MaxRpcCount; ++i) { + os::InitializeEvent(std::addressof(m_receive_buffer_available_events[i]), false, os::EventClearMode_AutoClear); + os::InitializeEvent(std::addressof(m_send_buffer_available_events[i]), false, os::EventClearMode_AutoClear); + } + } + + RpcClient::~RpcClient() { + /* Finalize all events. */ + for (size_t i = 0; i < MaxRpcCount; ++i) { + os::FinalizeEvent(std::addressof(m_receive_buffer_available_events[i])); + os::FinalizeEvent(std::addressof(m_send_buffer_available_events[i])); + } + + /* Free the thread stacks. */ + if (m_allocator != nullptr) { + m_allocator->Free(m_receive_thread_stack); + m_allocator->Free(m_send_thread_stack); + } + m_receive_thread_stack = nullptr; + m_send_thread_stack = nullptr; + + /* Free all tasks. */ + for (u32 i = 0; i < MaxRpcCount; ++i) { + if (m_task_active[i]) { + std::scoped_lock lk(m_mutex); + + m_task_table.Delete(i); + m_task_id_free_list.Free(i); + } + } + } + + void RpcClient::Open() { + R_ABORT_UNLESS(m_driver->Open(m_channel_id)); + } + + void RpcClient::Close() { + m_driver->Close(m_channel_id); + } + + Result RpcClient::Start() { + /* Connect. */ + R_TRY(m_driver->Connect(m_channel_id)); + + /* Initialize our task queue. */ + m_task_queue.Initialize(); + + /* Create our threads. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_receive_thread), ReceiveThreadEntry, this, m_receive_thread_stack, ThreadStackSize, AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtcmiscReceive))); + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_send_thread), SendThreadEntry, this, m_send_thread_stack, ThreadStackSize, AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtcmiscSend))); + + /* Set thread name pointers. */ + os::SetThreadNamePointer(std::addressof(m_receive_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtcmiscReceive)); + os::SetThreadNamePointer(std::addressof(m_send_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtcmiscSend)); + + /* Start threads. */ + os::StartThread(std::addressof(m_receive_thread)); + os::StartThread(std::addressof(m_send_thread)); + + /* Set initial state. */ + m_cancelled = false; + m_thread_running = true; + + /* Clear events. */ + for (size_t i = 0; i < MaxRpcCount; ++i) { + os::ClearEvent(std::addressof(m_receive_buffer_available_events[i])); + os::ClearEvent(std::addressof(m_send_buffer_available_events[i])); + } + + R_SUCCEED(); + } + + void RpcClient::Cancel() { + /* Set cancelled. */ + m_cancelled = true; + + /* Signal all events. */ + for (size_t i = 0; i < MaxRpcCount; ++i) { + os::SignalEvent(std::addressof(m_receive_buffer_available_events[i])); + os::SignalEvent(std::addressof(m_send_buffer_available_events[i])); + } + + /* Cancel our queue. */ + m_task_queue.Cancel(); + } + + void RpcClient::Wait() { + /* Wait for thread to not be running. */ + if (m_thread_running) { + os::WaitThread(std::addressof(m_receive_thread)); + os::WaitThread(std::addressof(m_send_thread)); + os::DestroyThread(std::addressof(m_receive_thread)); + os::DestroyThread(std::addressof(m_send_thread)); + } + m_thread_running = false; + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Finalize the task queue. */ + m_task_queue.Finalize(); + + /* Cancel all tasks. */ + for (size_t i = 0; i < MaxRpcCount; ++i) { + if (m_task_active[i]) { + m_task_table.Get<Task>(i)->Cancel(RpcTaskCancelReason::ClientFinalized); + } + } + } + + int RpcClient::WaitAny(htclow::ChannelState state, os::EventType *event) { + /* Check if we're already signaled. */ + if (os::TryWaitEvent(event)) { + return 1; + } + + /* Wait. */ + while (m_driver->GetChannelState(m_channel_id) != state) { + const auto idx = os::WaitAny(m_driver->GetChannelStateEvent(m_channel_id), event); + if (idx != 0) { + return idx; + } + + /* Clear the channel state event. */ + os::ClearEvent(m_driver->GetChannelStateEvent(m_channel_id)); + } + + return 0; + } + + Result RpcClient::ReceiveThread() { + /* Loop forever. */ + auto *header = reinterpret_cast<RpcPacket *>(m_receive_buffer); + while (true) { + /* Try to receive a packet header. */ + R_TRY(this->ReceiveHeader(header)); + + /* Track how much we've received. */ + size_t received = sizeof(*header); + + /* If the packet has one, receive its body. */ + if (header->body_size > 0) { + /* Sanity check the task id. */ + AMS_ABORT_UNLESS(header->task_id < static_cast<int>(MaxRpcCount)); + + /* Sanity check the body size. */ + AMS_ABORT_UNLESS(util::IsIntValueRepresentable<size_t>(header->body_size)); + AMS_ABORT_UNLESS(static_cast<size_t>(header->body_size) <= sizeof(m_receive_buffer) - received); + + /* Receive the body. */ + R_TRY(this->ReceiveBody(header->data, header->body_size)); + + /* Note that we received the body. */ + received += header->body_size; + } + + /* Acquire exclusive access to the task tables. */ + std::scoped_lock lk(m_mutex); + + /* Get the specified task. */ + Task *task = m_task_table.Get<Task>(header->task_id); + R_UNLESS(task != nullptr, htc::ResultInvalidTaskId()); + + /* If the task is canceled, free it. */ + if (task->GetTaskState() == RpcTaskState::Cancelled) { + m_task_active[header->task_id] = false; + m_is_htcs_task[header->task_id] = false; + m_task_table.Delete(header->task_id); + m_task_id_free_list.Free(header->task_id); + continue; + } + + /* Handle the packet. */ + switch (header->category) { + case PacketCategory::Response: + R_TRY(task->ProcessResponse(m_receive_buffer, received)); + break; + case PacketCategory::Notification: + R_TRY(task->ProcessNotification(m_receive_buffer, received)); + break; + default: + R_THROW(htc::ResultInvalidCategory()); + } + + /* If we used the receive buffer, signal that we're done with it. */ + if (task->IsReceiveBufferRequired()) { + os::SignalEvent(std::addressof(m_receive_buffer_available_events[header->task_id])); + } + } + } + + Result RpcClient::ReceiveHeader(RpcPacket *header) { + /* Receive. */ + s64 received; + R_TRY(m_driver->Receive(std::addressof(received), reinterpret_cast<char *>(header), sizeof(*header), m_channel_id, htclow::ReceiveOption_ReceiveAllData)); + + /* Check size. */ + R_UNLESS(static_cast<size_t>(received) == sizeof(*header), htc::ResultInvalidSize()); + + R_SUCCEED(); + } + + Result RpcClient::ReceiveBody(char *dst, size_t size) { + /* Receive. */ + s64 received; + R_TRY(m_driver->Receive(std::addressof(received), dst, size, m_channel_id, htclow::ReceiveOption_ReceiveAllData)); + + /* Check size. */ + R_UNLESS(static_cast<size_t>(received) == size, htc::ResultInvalidSize()); + + R_SUCCEED(); + } + + Result RpcClient::SendThread() { + while (true) { + /* Get a task. */ + Task *task; + u32 task_id{}; + PacketCategory category{}; + do { + /* Dequeue a task. */ + R_TRY(m_task_queue.Take(std::addressof(task_id), std::addressof(category))); + + /* Get the task from the table. */ + std::scoped_lock lk(m_mutex); + + task = m_task_table.Get<Task>(task_id); + } while (task == nullptr); + + /* If required, wait for the send buffer to become available. */ + if (task->IsSendBufferRequired()) { + os::WaitEvent(std::addressof(m_send_buffer_available_events[task_id])); + + /* Check if we've been cancelled. */ + if (m_cancelled) { + break; + } + } + + /* Handle the task. */ + size_t packet_size; + switch (category) { + case PacketCategory::Request: + R_TRY(task->CreateRequest(std::addressof(packet_size), m_send_buffer, sizeof(m_send_buffer), task_id)); + break; + case PacketCategory::Notification: + R_TRY(task->CreateNotification(std::addressof(packet_size), m_send_buffer, sizeof(m_send_buffer), task_id)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Send the request. */ + R_TRY(this->SendRequest(m_send_buffer, packet_size)); + } + + R_THROW(htc::ResultCancelled()); + } + + Result RpcClient::SendRequest(const char *src, size_t size) { + /* Sanity check our size. */ + AMS_ASSERT(util::IsIntValueRepresentable<s64>(size)); + + /* Send the data. */ + s64 sent; + R_TRY(m_driver->Send(std::addressof(sent), src, static_cast<s64>(size), m_channel_id)); + + /* Check that we sent the right amount. */ + R_UNLESS(sent == static_cast<s64>(size), htc::ResultInvalidSize()); + + R_SUCCEED(); + } + + void RpcClient::CancelBySocket(s32 handle) { + /* Check if we need to cancel each task. */ + for (size_t i = 0; i < MaxRpcCount; ++i) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that the task is active and is an htcs task. */ + if (!m_task_active[i] || !m_is_htcs_task[i]) { + continue; + } + + /* Get the htcs task. */ + auto *htcs_task = m_task_table.Get<htcs::impl::rpc::HtcsTask>(i); + + /* Handle the case where the task handle is the one we're cancelling. */ + if (this->GetTaskHandle(i) == handle) { + /* If the task is complete, free it. */ + if (htcs_task->GetTaskState() == RpcTaskState::Completed) { + m_task_active[i] = false; + m_is_htcs_task[i] = false; + m_task_table.Delete(i); + m_task_id_free_list.Free(i); + } else { + /* If the task is a send task, notify. */ + if (htcs_task->GetTaskType() == htcs::impl::rpc::HtcsTaskType::Send) { + m_task_queue.Add(i, PacketCategory::Notification); + } + + /* Cancel the task. */ + htcs_task->Cancel(RpcTaskCancelReason::BySocket); + } + + /* The task has been cancelled, so we can move on. */ + continue; + } + + /* Handle the case where the task is a select task. */ + if (htcs_task->GetTaskType() == htcs::impl::rpc::HtcsTaskType::Select) { + /* Get the select task. */ + auto *select_task = m_task_table.Get<htcs::impl::rpc::SelectTask>(i); + + /* Get the handle counts. */ + const auto num_read = select_task->GetReadHandleCount(); + const auto num_write = select_task->GetWriteHandleCount(); + const auto num_exception = select_task->GetExceptionHandleCount(); + const auto total = num_read + num_write + num_exception; + + /* Get the handle array. */ + const auto *handles = select_task->GetHandles(); + + /* Check each handle. */ + for (auto handle_idx = 0; handle_idx < total; ++handle_idx) { + if (handles[handle_idx] == handle) { + /* If the select is complete, free it. */ + if (select_task->GetTaskState() == RpcTaskState::Completed) { + m_task_active[i] = false; + m_is_htcs_task[i] = false; + m_task_table.Delete(i); + m_task_id_free_list.Free(i); + } else { + /* Cancel the task. */ + select_task->Cancel(RpcTaskCancelReason::BySocket); + } + } + } + } + } + } + + s32 RpcClient::GetTaskHandle(u32 task_id) { + /* TODO: Why is this necessary to avoid a bogus array-bounds warning? */ + AMS_ASSUME(task_id < MaxRpcCount); + + /* Check pre-conditions. */ + AMS_ASSERT(m_task_active[task_id]); + AMS_ASSERT(m_is_htcs_task[task_id]); + + /* Get the htcs task. */ + auto *task = m_task_table.Get<htcs::impl::rpc::HtcsTask>(task_id); + + /* Check that the task has a handle. */ + if (!m_task_active[task_id] || !m_is_htcs_task[task_id] || task == nullptr) { + return -1; + } + + /* Get the task's type. */ + const auto type = task->GetTaskType(); + + /* Check that the task is new enough. */ + if (task->GetVersion() == 3) { + if (type == htcs::impl::rpc::HtcsTaskType::Receive || type == htcs::impl::rpc::HtcsTaskType::Send) { + return -1; + } + } + + /* Get the handle from the task. */ + switch (type) { + case htcs::impl::rpc::HtcsTaskType::Receive: + return static_cast<htcs::impl::rpc::ReceiveTask *>(task)->GetHandle(); + case htcs::impl::rpc::HtcsTaskType::Send: + return static_cast<htcs::impl::rpc::SendTask *>(task)->GetHandle(); + case htcs::impl::rpc::HtcsTaskType::Shutdown: + return static_cast<htcs::impl::rpc::ShutdownTask *>(task)->GetHandle(); + case htcs::impl::rpc::HtcsTaskType::Close: + return -1; + case htcs::impl::rpc::HtcsTaskType::Connect: + return static_cast<htcs::impl::rpc::ConnectTask *>(task)->GetHandle(); + case htcs::impl::rpc::HtcsTaskType::Listen: + return static_cast<htcs::impl::rpc::ListenTask *>(task)->GetHandle(); + case htcs::impl::rpc::HtcsTaskType::Accept: + return static_cast<htcs::impl::rpc::AcceptTask *>(task)->GetServerHandle(); + case htcs::impl::rpc::HtcsTaskType::Socket: + return -1; + case htcs::impl::rpc::HtcsTaskType::Bind: + return static_cast<htcs::impl::rpc::BindTask *>(task)->GetHandle(); + case htcs::impl::rpc::HtcsTaskType::Fcntl: + return static_cast<htcs::impl::rpc::FcntlTask *>(task)->GetHandle(); + case htcs::impl::rpc::HtcsTaskType::ReceiveSmall: + return static_cast<htcs::impl::rpc::ReceiveSmallTask *>(task)->GetHandle(); + case htcs::impl::rpc::HtcsTaskType::SendSmall: + return static_cast<htcs::impl::rpc::SendSmallTask *>(task)->GetHandle(); + case htcs::impl::rpc::HtcsTaskType::Select: + return -1; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_rpc_client.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_rpc_client.hpp new file mode 100644 index 00000000..3a943fb4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_rpc_client.hpp @@ -0,0 +1,356 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../driver/htc_i_driver.hpp" +#include "htc_rpc_task_table.hpp" +#include "htc_rpc_task_queue.hpp" +#include "htc_rpc_task_id_free_list.hpp" +#include "../../../htcs/impl/rpc/htcs_rpc_tasks.hpp" + +namespace ams::htc::server::rpc { + + template<typename T> + concept IsRpcTask = std::derived_from<T, Task>; + + struct RpcTaskFunctionTraits { + public: + template<typename R, typename C, typename... A> + static std::tuple<A...> GetSetArgumentsImpl(R(C::*)(A...)); + template<typename R, typename C, typename... A> + static std::tuple<A...> GetGetResultImpl(R(C::*)(A...) const); + }; + + template<typename T> requires IsRpcTask<T> + using RpcTaskArgumentsType = decltype(RpcTaskFunctionTraits::GetSetArgumentsImpl(&T::SetArguments)); + + template<typename T> requires IsRpcTask<T> + using RpcTaskResultsType = decltype(RpcTaskFunctionTraits::GetGetResultImpl(&T::GetResult)); + + template<typename T, size_t Ix> requires IsRpcTask<T> + using RpcTaskArgumentType = typename std::tuple_element<Ix, RpcTaskArgumentsType<T>>::type; + + template<typename T, size_t Ix> requires IsRpcTask<T> + using RpcTaskResultType = typename std::tuple_element<Ix, RpcTaskResultsType<T>>::type; + + class RpcClient { + private: + /* TODO: where is this value coming from, again? */ + static constexpr size_t BufferSize = 0xE400; + private: + mem::StandardAllocator *m_allocator; + driver::IDriver *m_driver; + htclow::ChannelId m_channel_id; + void *m_receive_thread_stack; + void *m_send_thread_stack; + os::ThreadType m_receive_thread; + os::ThreadType m_send_thread; + os::SdkMutex &m_mutex; + RpcTaskIdFreeList &m_task_id_free_list; + RpcTaskTable &m_task_table; + bool m_task_active[MaxRpcCount]; + bool m_is_htcs_task[MaxRpcCount]; + RpcTaskQueue m_task_queue; + bool m_cancelled; + bool m_thread_running; + os::EventType m_receive_buffer_available_events[MaxRpcCount]; + os::EventType m_send_buffer_available_events[MaxRpcCount]; + char m_receive_buffer[BufferSize]; + char m_send_buffer[BufferSize]; + private: + static void ReceiveThreadEntry(void *arg) { static_cast<RpcClient *>(arg)->ReceiveThread(); } + static void SendThreadEntry(void *arg) { static_cast<RpcClient *>(arg)->SendThread(); } + + Result ReceiveThread(); + Result SendThread(); + public: + RpcClient(driver::IDriver *driver, htclow::ChannelId channel); + RpcClient(mem::StandardAllocator *allocator, driver::IDriver *driver, htclow::ChannelId channel); + ~RpcClient(); + public: + void Open(); + void Close(); + + Result Start(); + void Cancel(); + void Wait(); + + int WaitAny(htclow::ChannelState state, os::EventType *event); + private: + Result ReceiveHeader(RpcPacket *header); + Result ReceiveBody(char *dst, size_t size); + Result SendRequest(const char *src, size_t size); + private: + s32 GetTaskHandle(u32 task_id); + public: + void Wait(u32 task_id) { + os::WaitEvent(m_task_table.Get<Task>(task_id)->GetEvent()); + } + + os::NativeHandle DetachReadableHandle(u32 task_id) { + return os::DetachReadableHandleOfSystemEvent(m_task_table.Get<Task>(task_id)->GetSystemEvent()); + } + + void CancelBySocket(s32 handle); + + template<typename T, typename... Args> requires (IsRpcTask<T> && sizeof...(Args) == std::tuple_size<RpcTaskArgumentsType<T>>::value) + Result Begin(u32 *out_task_id, Args &&... args) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Allocate a free task id. */ + u32 task_id{}; + R_TRY(m_task_id_free_list.Allocate(std::addressof(task_id))); + + /* Create the new task. */ + T *task = m_task_table.New<T>(task_id); + m_task_active[task_id] = true; + m_is_htcs_task[task_id] = htcs::impl::rpc::IsHtcsTask<T>; + + /* Ensure we clean up the task, if we fail after this. */ + auto task_guard = SCOPE_GUARD { + m_task_active[task_id] = false; + m_is_htcs_task[task_id] = false; + m_task_table.Delete<T>(task_id); + m_task_id_free_list.Free(task_id); + }; + + /* Set the task arguments. */ + R_TRY(task->SetArguments(std::forward<Args>(args)...)); + + /* Clear the task's events. */ + os::ClearEvent(std::addressof(m_receive_buffer_available_events[task_id])); + os::ClearEvent(std::addressof(m_send_buffer_available_events[task_id])); + + /* Add the task to our queue if we can, or cancel it. */ + if (m_thread_running) { + m_task_queue.Add(task_id, PacketCategory::Request); + } else { + task->Cancel(RpcTaskCancelReason::QueueNotAvailable); + } + + /* Set the output task id. */ + *out_task_id = task_id; + + /* We succeeded. */ + task_guard.Cancel(); + R_SUCCEED(); + } + + template<typename T, typename... Args> requires (IsRpcTask<T> && sizeof...(Args) == std::tuple_size<RpcTaskResultsType<T>>::value) + Result GetResult(u32 task_id, Args &&... args) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Get the task. */ + T *task = m_task_table.Get<T>(task_id); + R_UNLESS(task != nullptr, htc::ResultInvalidTaskId()); + + /* Check that the task is completed. */ + R_UNLESS(task->GetTaskState() == RpcTaskState::Completed, htc::ResultTaskNotCompleted()); + + /* Get the task's result. */ + R_TRY(task->GetResult(std::forward<Args>(args)...)); + + R_SUCCEED(); + } + + template<typename T, typename... Args> requires (IsRpcTask<T> && sizeof...(Args) == std::tuple_size<RpcTaskResultsType<T>>::value) + Result End(u32 task_id, Args &&... args) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Get the task. */ + T *task = m_task_table.Get<T>(task_id); + R_UNLESS(task != nullptr, htc::ResultInvalidTaskId()); + + /* Ensure the task is freed if it needs to be, when we're done. */ + auto task_guard = SCOPE_GUARD { + m_task_active[task_id] = false; + m_is_htcs_task[task_id] = false; + m_task_table.Delete<T>(task_id); + m_task_id_free_list.Free(task_id); + }; + + /* If the task was cancelled, handle that. */ + if (task->GetTaskState() == RpcTaskState::Cancelled) { + switch (task->GetTaskCancelReason()) { + case RpcTaskCancelReason::BySocket: + task_guard.Cancel(); + R_THROW(htc::ResultTaskCancelled()); + case RpcTaskCancelReason::ClientFinalized: + R_THROW(htc::ResultCancelled()); + case RpcTaskCancelReason::QueueNotAvailable: + R_THROW(htc::ResultTaskQueueNotAvailable()); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + /* Get the task's result. */ + R_TRY(task->GetResult(std::forward<Args>(args)...)); + + R_SUCCEED(); + } + + template<typename T> requires IsRpcTask<T> + Result VerifyTaskIdWithHandle(u32 task_id, s32 handle) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Get the task. */ + T *task = m_task_table.Get<T>(task_id); + R_UNLESS(task != nullptr, htc::ResultInvalidTaskId()); + + /* Check the task handle. */ + R_UNLESS(task->GetHandle() == handle, htc::ResultInvalidTaskId()); + + R_SUCCEED(); + } + + template<typename T> requires IsRpcTask<T> + Result Notify(u32 task_id) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that our queue is available. */ + R_UNLESS(m_thread_running, htc::ResultTaskQueueNotAvailable()); + + /* Get the task. */ + T *task = m_task_table.Get<T>(task_id); + R_UNLESS(task != nullptr, htc::ResultInvalidTaskId()); + + /* Add notification to our queue. */ + m_task_queue.Add(task_id, PacketCategory::Notification); + + R_SUCCEED(); + } + + template<typename T> requires IsRpcTask<T> + void WaitNotification(u32 task_id) { + /* Get the task from the table, releasing our lock afterwards. */ + T *task; + { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Get the task. */ + task = m_task_table.Get<T>(task_id); + } + + /* Wait for a notification. */ + task->WaitNotification(); + } + + template<typename T> requires IsRpcTask<T> + bool IsCancelled(u32 task_id) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Get the task. */ + T *task = m_task_table.Get<T>(task_id); + + /* Check the task state. */ + return task != nullptr && task->GetTaskState() == RpcTaskState::Cancelled; + } + + template<typename T> requires IsRpcTask<T> + bool IsCompleted(u32 task_id) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Get the task. */ + T *task = m_task_table.Get<T>(task_id); + + /* Check the task state. */ + return task != nullptr && task->GetTaskState() == RpcTaskState::Completed; + } + + template<typename T> requires IsRpcTask<T> + Result SendContinue(u32 task_id, const void *buffer, s64 buffer_size) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Get the task. */ + T *task = m_task_table.Get<T>(task_id); + R_UNLESS(task != nullptr, htc::ResultInvalidTaskId()); + + /* If the task was cancelled, handle that. */ + if (task->GetTaskState() == RpcTaskState::Cancelled) { + switch (task->GetTaskCancelReason()) { + case RpcTaskCancelReason::QueueNotAvailable: + R_THROW(htc::ResultTaskQueueNotAvailable()); + default: + R_THROW(htc::ResultTaskCancelled()); + } + } + + /* Set the task's buffer. */ + if (buffer_size > 0) { + task->SetBuffer(buffer, buffer_size); + os::SignalEvent(std::addressof(m_send_buffer_available_events[task_id])); + } + + R_SUCCEED(); + } + + template<typename T> requires IsRpcTask<T> + Result ReceiveContinue(u32 task_id, void *buffer, s64 buffer_size) { + /* Get the task's buffer, and prepare to receive. */ + const void *result_buffer; + s64 result_size; + { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Get the task. */ + T *task = m_task_table.Get<T>(task_id); + R_UNLESS(task != nullptr, htc::ResultInvalidTaskId()); + + /* If the task was cancelled, handle that. */ + if (task->GetTaskState() == RpcTaskState::Cancelled) { + switch (task->GetTaskCancelReason()) { + case RpcTaskCancelReason::QueueNotAvailable: + R_THROW(htc::ResultTaskQueueNotAvailable()); + default: + R_THROW(htc::ResultTaskCancelled()); + } + } + + /* Get the result size. */ + result_size = task->GetResultSize(); + R_SUCCEED_IF(result_size == 0); + + /* Get the result buffer. */ + result_buffer = task->GetBuffer(); + } + + /* Wait for the receive buffer to become available. */ + os::WaitEvent(std::addressof(m_receive_buffer_available_events[task_id])); + + /* Check that we weren't cancelled. */ + R_UNLESS(!m_cancelled, htc::ResultCancelled()); + + /* Copy the received data. */ + AMS_ASSERT(0 <= result_size && result_size <= buffer_size); + AMS_UNUSED(buffer_size); + + std::memcpy(buffer, result_buffer, result_size); + + R_SUCCEED(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_rpc_task_id_free_list.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_rpc_task_id_free_list.hpp new file mode 100644 index 00000000..b8936874 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_rpc_task_id_free_list.hpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htc_rpc_tasks.hpp" +#include "htc_htcmisc_rpc_tasks.hpp" + +namespace ams::htc::server::rpc { + + class RpcTaskIdFreeList { + private: + u32 m_task_ids[MaxRpcCount]; + u32 m_offset; + u32 m_free_count; + public: + constexpr RpcTaskIdFreeList() : m_task_ids(), m_offset(0), m_free_count(MaxRpcCount) { + for (auto i = 0; i < static_cast<int>(MaxRpcCount); ++i) { + m_task_ids[i] = i; + } + } + + Result Allocate(u32 *out) { + /* Check that we have free tasks. */ + R_UNLESS(m_free_count > 0, htc::ResultOutOfRpcTask()); + + /* Get index. */ + const auto index = m_offset; + m_offset = (m_offset + 1) % MaxRpcCount; + --m_free_count; + + /* Get the task id. */ + *out = m_task_ids[index]; + R_SUCCEED(); + } + + void Free(u32 task_id) { + /* Check pre-conditions. */ + AMS_ASSERT(m_free_count < static_cast<int>(MaxRpcCount)); + + /* Determine index. */ + const auto index = ((m_free_count++) + m_offset) % MaxRpcCount; + + /* Set the task id. */ + m_task_ids[index] = task_id; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_rpc_task_queue.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_rpc_task_queue.hpp new file mode 100644 index 00000000..18332815 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_rpc_task_queue.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htc_rpc_tasks.hpp" +#include "htc_htcmisc_rpc_tasks.hpp" + +namespace ams::htc::server::rpc { + + class RpcTaskQueue { + private: + u32 m_task_ids[MaxRpcCount]; + PacketCategory m_task_categories[MaxRpcCount]; + int m_offset; + int m_count; + os::SdkConditionVariable m_cv; + os::SdkMutex m_mutex; + bool m_cancelled; + public: + constexpr RpcTaskQueue() = default; + + void Initialize() { + m_offset = 0; + m_count = 0; + m_cancelled = false; + } + + void Finalize() { + m_offset = 0; + m_count = 0; + } + + void Cancel() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Cancel ourselves. */ + m_cancelled = true; + + /* Signal to consumers/producers. */ + m_cv.Signal(); + } + + void Add(u32 task_id, PacketCategory category) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check pre-conditions. */ + AMS_ASSERT(m_count < static_cast<int>(MaxRpcCount)); + + /* Determine index. */ + const auto index = ((m_count++) + m_offset) % MaxRpcCount; + + /* Set task. */ + m_task_ids[index] = task_id; + m_task_categories[index] = category; + + /* Signal. */ + if (m_count > 0) { + m_cv.Signal(); + } + } + + Result Take(u32 *out_id, PacketCategory *out_category) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Wait until we can take. */ + while (m_count == 0 && !m_cancelled) { + m_cv.Wait(m_mutex); + } + + /* Check that we're not cancelled. */ + R_UNLESS(!m_cancelled, htc::ResultCancelled()); + + /* Determine index. */ + const auto index = m_offset; + + /* Advance the queue. */ + m_offset = (m_offset + 1) % MaxRpcCount; + --m_count; + + /* Return the task info. */ + *out_id = m_task_ids[index]; + *out_category = m_task_categories[index]; + R_SUCCEED(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_rpc_task_table.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_rpc_task_table.hpp new file mode 100644 index 00000000..318c0164 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_rpc_task_table.hpp @@ -0,0 +1,122 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htc_rpc_tasks.hpp" +#include "htc_htcmisc_rpc_tasks.hpp" + +namespace ams::htc::server::rpc { + + /* For convenience. */ + template<typename T> + concept IsTypeCheckableTask = std::derived_from<T, Task> && requires (T &t) { + { t.GetTaskType() } -> std::convertible_to<decltype(T::TaskType)>; + }; + + static_assert(!IsTypeCheckableTask<Task>); + static_assert(IsTypeCheckableTask<GetEnvironmentVariableTask>); + + class RpcTaskTable { + private: + /* htcs::ReceiveSmallTask/htcs::ReceiveSendTask are the largest tasks, containing an inline 0xE000 buffer. */ + /* We allow for ~0x100 task overhead from the additional events those contain. */ + /* NOTE: Nintendo hardcodes a maximum size of 0xE1D8, despite SendSmallTask being 0xE098 as of latest check. */ + #if defined(ATMOSPHERE_OS_HORIZON) + static constexpr size_t MaxTaskSize = 0xE100; + #elif defined(ATMOSPHERE_OS_MACOS) + static constexpr size_t MaxTaskSize = 0xE400; + #else + static constexpr size_t MaxTaskSize = 0xE1D8; + #endif + struct TaskStorage { alignas(alignof(void *)) std::byte _storage[MaxTaskSize]; }; + private: + bool m_valid[MaxRpcCount]{}; + TaskStorage m_storages[MaxRpcCount]{}; + private: + template<typename T> + ALWAYS_INLINE T *GetPointer(u32 index) { + static_assert(alignof(T) <= alignof(TaskStorage)); + static_assert(sizeof(T) <= sizeof(TaskStorage)); + return reinterpret_cast<T *>(std::addressof(m_storages[index])); + } + + ALWAYS_INLINE bool IsValid(u32 index) { + return index < MaxRpcCount && m_valid[index]; + } + public: + constexpr RpcTaskTable() = default; + + template<typename T> requires std::derived_from<T, Task> + T *New(u32 index) { + /* Sanity check input. */ + AMS_ASSERT(!this->IsValid(index)); + + /* Set valid. */ + m_valid[index] = true; + + /* Allocate the task. */ + T *task = this->GetPointer<T>(index); + + /* Create the task. */ + std::construct_at(task); + + /* Return the task. */ + return task; + } + + template<typename T> requires std::derived_from<T, Task> + T *Get(u32 index) { + /* Check that the task is valid. */ + if (!this->IsValid(index)) { + return nullptr; + } + + /* Get the task pointer. */ + T *task = this->GetPointer<T>(index); + + /* Type check the task. */ + if constexpr (IsTypeCheckableTask<T>) { + if (task->GetTaskType() != T::TaskType) { + task = nullptr; + } + } + + /* Return the task. */ + return task; + } + + template<typename T> requires std::derived_from<T, Task> + void Delete(u32 index) { + /* Check that the task is valid. */ + if (!this->IsValid(index)) { + return; + } + + /* Delete the task. */ + std::destroy_at(this->GetPointer<T>(index)); + + /* Mark the task as invalid. */ + m_valid[index] = false; + } + + void Delete(u32 index) { + if (this->IsValid(index)) { + this->Delete<Task>(index); + } + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_rpc_tasks.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_rpc_tasks.hpp new file mode 100644 index 00000000..0b4ae63a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/server/rpc/htc_rpc_tasks.hpp @@ -0,0 +1,123 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htc::server::rpc { + + constexpr inline size_t MaxRpcCount = 0x48; + + enum class PacketCategory : s16 { + Request = 0, + Response = 1, + Notification = 2, + }; + + struct RpcPacket { + s16 protocol; + s16 version; + PacketCategory category; + u16 type; + s64 body_size; + u32 task_id{}; + u64 params[5]; + char data[]; + }; + static_assert(sizeof(RpcPacket) == 0x40); + + enum class RpcTaskCancelReason { + None = 0, + BySocket = 1, + ClientFinalized = 2, + QueueNotAvailable = 3, + }; + + enum class RpcTaskState { + Started = 0, + Completed = 1, + Cancelled = 2, + Notified = 3, + }; + + class Task { + private: + RpcTaskState m_state; + RpcTaskCancelReason m_cancel_reason; + os::Event m_event; + public: + Task() : m_state(RpcTaskState::Started), m_cancel_reason(RpcTaskCancelReason::None), m_event(os::EventClearMode_AutoClear) { /* ... */ } + virtual ~Task() { /* ... */ } + public: + void SetTaskState(RpcTaskState state) { m_state = state; } + RpcTaskState GetTaskState() const { return m_state; } + + RpcTaskCancelReason GetTaskCancelReason() const { return m_cancel_reason; } + + os::EventType *GetEvent() { return m_event.GetBase(); } + + void Notify() { + AMS_ASSERT(m_state == RpcTaskState::Started); + + m_state = RpcTaskState::Notified; + } + + void Complete() { + AMS_ASSERT(m_state == RpcTaskState::Started || m_state == RpcTaskState::Notified); + + m_state = RpcTaskState::Completed; + m_event.Signal(); + } + public: + virtual void Cancel(RpcTaskCancelReason reason) { + m_state = RpcTaskState::Cancelled; + m_cancel_reason = reason; + m_event.Signal(); + } + + virtual Result ProcessResponse(const char *data, size_t size) { + AMS_UNUSED(data, size); + R_SUCCEED(); + } + + virtual Result CreateRequest(size_t *out, char *data, size_t size, u32 task_id) { + AMS_UNUSED(out, data, size, task_id); + R_SUCCEED(); + } + + virtual Result ProcessNotification(const char *data, size_t size) { + AMS_UNUSED(data, size); + R_SUCCEED(); + } + + virtual Result CreateNotification(size_t *out, char *data, size_t size, u32 task_id) { + AMS_UNUSED(out, data, size, task_id); + R_SUCCEED(); + } + + virtual bool IsReceiveBufferRequired() { + return false; + } + + virtual bool IsSendBufferRequired() { + return false; + } + + virtual os::SystemEventType *GetSystemEvent() { + return nullptr; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/htc_tenv.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/htc_tenv.cpp new file mode 100644 index 00000000..3475674b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/htc_tenv.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/htc_tenv_allocator.hpp" +#include "impl/htc_tenv_impl.hpp" + +namespace ams::htc::tenv { + + void Initialize(AllocateFunction allocate, DeallocateFunction deallocate) { + /* Initialize the library allocator. */ + impl::InitializeAllocator(allocate, deallocate); + } + + void UnregisterDefinitionFilePath(os::ProcessId process_id) { + return impl::UnregisterDefinitionFilePath(process_id.value); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/htc_tenv_service.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/htc_tenv_service.cpp new file mode 100644 index 00000000..f7cda678 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/htc_tenv_service.cpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htc_tenv_service.hpp" + +namespace ams::htc::tenv { + + Result Service::GetVariable(sf::Out<s64> out_size, const sf::OutBuffer &out_buffer, const htc::tenv::VariableName &name) { + /* TODO */ + AMS_UNUSED(out_size, out_buffer, name); + AMS_ABORT("Service::GetVariable"); + } + + Result Service::GetVariableLength(sf::Out<s64> out_size, const htc::tenv::VariableName &name) { + /* TODO */ + AMS_UNUSED(out_size, name); + AMS_ABORT("Service::GetVariableLength"); + } + + Result Service::WaitUntilVariableAvailable(s64 timeout_ms) { + /* TODO */ + AMS_UNUSED(timeout_ms); + AMS_ABORT("Service::WaitUntilVariableAvailable"); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/htc_tenv_service.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/htc_tenv_service.hpp new file mode 100644 index 00000000..12abea8d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/htc_tenv_service.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htc::tenv { + + class Service { + private: + os::ProcessId m_process_id; + public: + constexpr Service(os::ProcessId pid) : m_process_id(pid) { /* ... */ } + public: + Result GetVariable(sf::Out<s64> out_size, const sf::OutBuffer &out_buffer, const htc::tenv::VariableName &name); + Result GetVariableLength(sf::Out<s64> out_size,const htc::tenv::VariableName &name); + Result WaitUntilVariableAvailable(s64 timeout_ms); + }; + static_assert(htc::tenv::IsIService<Service>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/htc_tenv_service_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/htc_tenv_service_manager.cpp new file mode 100644 index 00000000..ba9ecdf7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/htc_tenv_service_manager.cpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/htc_tenv_allocator.hpp" +#include "htc_tenv_service.hpp" + +namespace ams::htc::tenv { + + Result ServiceManager::GetServiceInterface(sf::Out<sf::SharedPointer<htc::tenv::IService>> out, const sf::ClientProcessId &process_id) { + *out = impl::SfObjectFactory::CreateSharedEmplaced<htc::tenv::IService, Service>(process_id.GetValue()); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/impl/htc_tenv_allocator.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/impl/htc_tenv_allocator.cpp new file mode 100644 index 00000000..47a7f1fa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/impl/htc_tenv_allocator.cpp @@ -0,0 +1,56 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htc_tenv_allocator.hpp" + +namespace ams::htc::tenv::impl { + + namespace { + + constinit AllocateFunction g_allocate = nullptr; + constinit DeallocateFunction g_deallocate = nullptr; + + } + + void InitializeAllocator(AllocateFunction allocate, DeallocateFunction deallocate) { + /* Check that we don't already have allocator functions. */ + AMS_ASSERT(g_allocate == nullptr); + AMS_ASSERT(g_deallocate == nullptr); + + /* Set our allocator functions. */ + g_allocate = allocate; + g_deallocate = deallocate; + + /* Check that we have allocator functions. */ + AMS_ASSERT(g_allocate != nullptr); + AMS_ASSERT(g_deallocate != nullptr); + } + + void *Allocate(size_t size) { + /* Check that we have an allocator. */ + AMS_ASSERT(g_allocate != nullptr); + + return g_allocate(size); + } + + void Deallocate(void *p, size_t size) { + /* Check that we have a deallocator. */ + AMS_ASSERT(g_deallocate != nullptr); + + return g_deallocate(p, size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/impl/htc_tenv_allocator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/impl/htc_tenv_allocator.hpp new file mode 100644 index 00000000..67992d32 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/impl/htc_tenv_allocator.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htc::tenv::impl { + + void InitializeAllocator(AllocateFunction allocate, DeallocateFunction deallocate); + + void *Allocate(size_t size); + void Deallocate(void *p, size_t size); + + class SfAllocator { + public: + constexpr ALWAYS_INLINE SfAllocator() { /* ... */ } + + void *Allocate(size_t size) { + return ::ams::htc::tenv::impl::Allocate(size); + } + + void Deallocate(void *p, size_t size) { + return ::ams::htc::tenv::impl::Deallocate(p, size); + } + }; + + using SfPolicy = sf::StatelessAllocationPolicy<SfAllocator>; + using SfObjectFactory = sf::ObjectFactory<SfPolicy>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/impl/htc_tenv_definition_file_info.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/impl/htc_tenv_definition_file_info.hpp new file mode 100644 index 00000000..fe521d0c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/impl/htc_tenv_definition_file_info.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htc_tenv_allocator.hpp" + +namespace ams::htc::tenv::impl { + + struct DefinitionFileInfo : public util::IntrusiveListBaseNode<DefinitionFileInfo> { + u64 process_id; + Path path; + + DefinitionFileInfo(u64 pid, Path *p) : process_id(pid) { + AMS_ASSERT(p != nullptr); + util::Strlcpy(this->path.str, p->str, PathLengthMax); + } + + static void *operator new(size_t size) { + return Allocate(size); + } + + static void operator delete(void *p, size_t size) { + Deallocate(p, size); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/impl/htc_tenv_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/impl/htc_tenv_impl.cpp new file mode 100644 index 00000000..8a9b5664 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/impl/htc_tenv_impl.cpp @@ -0,0 +1,81 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htc_tenv_impl.hpp" +#include "htc_tenv_definition_file_info.hpp" + +namespace ams::htc::tenv::impl { + + namespace { + + class DefinitionFileInfoManager { + private: + using DefinitionFileInfoList = util::IntrusiveListBaseTraits<DefinitionFileInfo>::ListType; + private: + DefinitionFileInfoList m_list; + os::SdkMutex m_mutex; + public: + constexpr DefinitionFileInfoManager() = default; + + ~DefinitionFileInfoManager() { + while (!m_list.empty()) { + auto *p = std::addressof(*m_list.rbegin()); + m_list.erase(m_list.iterator_to(*p)); + delete p; + } + } + + void Remove(DefinitionFileInfo *info) { + std::scoped_lock lk(m_mutex); + + m_list.erase(m_list.iterator_to(*info)); + delete info; + } + + DefinitionFileInfo *GetInfo(u64 process_id) { + std::scoped_lock lk(m_mutex); + + for (auto &info : m_list) { + if (info.process_id == process_id) { + return std::addressof(info); + } + } + + return nullptr; + } + }; + + constinit DefinitionFileInfoManager g_definition_file_info_manager; + + ALWAYS_INLINE DefinitionFileInfoManager &GetDefinitionFileInfoManager() { + return g_definition_file_info_manager; + } + + } + + void UnregisterDefinitionFilePath(u64 process_id) { + /* Require the process id to be valid. */ + if (process_id == 0) { + return; + } + + /* Remove the definition file info, if we have one. */ + if (auto *info = GetDefinitionFileInfoManager().GetInfo(process_id); info != nullptr) { + GetDefinitionFileInfoManager().Remove(info); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/impl/htc_tenv_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/impl/htc_tenv_impl.hpp new file mode 100644 index 00000000..f8ca178b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htc/tenv/impl/htc_tenv_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htc::tenv::impl { + + void UnregisterDefinitionFilePath(u64 process_id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_cache_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_cache_manager.hpp new file mode 100644 index 00000000..549a838a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_cache_manager.hpp @@ -0,0 +1,113 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htcfs { + + class CacheManager { + private: + os::SdkMutex m_mutex; + void *m_cache; + size_t m_cache_size; + s64 m_cached_file_size; + size_t m_cached_data_size; + s32 m_cached_handle; + bool m_has_cached_handle; + public: + CacheManager(void *cache, size_t cache_size) : m_mutex(), m_cache(cache), m_cache_size(cache_size), m_cached_file_size(), m_cached_data_size(), m_cached_handle(), m_has_cached_handle() { /* ... */ } + public: + bool GetFileSize(s64 *out, s32 handle) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Get the cached size, if we match. */ + if (m_has_cached_handle && m_cached_handle == handle) { + *out = m_cached_file_size; + return true; + } else { + return false; + } + } + + void Invalidate() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Note that we have no handle. */ + m_has_cached_handle = false; + } + + void Invalidate(s32 handle) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + if (m_has_cached_handle && m_cached_handle == handle) { + /* Note that we have no handle. */ + m_has_cached_handle = false; + } + } + + void Record(s64 file_size, const void *data, s32 handle, size_t data_size) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Set our cached file size. */ + m_cached_file_size = file_size; + + /* Set our cached data size. */ + m_cached_data_size = std::min(m_cache_size, data_size); + + /* Copy the data. */ + std::memcpy(m_cache, data, m_cached_data_size); + + /* Set our cache handle. */ + m_cached_handle = handle; + + /* Note that we have cached data. */ + m_has_cached_handle = true; + } + + bool ReadFile(size_t *out, void *dst, s32 handle, size_t offset, size_t size) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we have a cached file. */ + if (!m_has_cached_handle) { + return false; + } + + /* Check the file is our cached one. */ + if (handle != m_cached_handle) { + return false; + } + + /* Check that we can read data. */ + if (offset + size > m_cached_data_size) { + return false; + } + + /* Copy the cached data. */ + std::memcpy(dst, static_cast<const u8 *>(m_cache) + offset, size); + + /* Set the output read size. */ + *out = size; + + return true; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_client.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_client.cpp new file mode 100644 index 00000000..377b6801 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_client.cpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcfs_client.hpp" + +namespace ams::htcfs { + + namespace { + + constinit util::TypedStorage<Client> g_client_storage = {}; + [[maybe_unused]] constinit bool g_initialized = false; + + } + + void InitializeClient(htclow::HtclowManager *manager) { + AMS_ASSERT(!g_initialized); + + util::ConstructAt(g_client_storage, manager); + } + + void FinalizeClient() { + AMS_ASSERT(g_initialized); + + util::DestroyAt(g_client_storage); + } + + Client &GetClient() { + AMS_ASSERT(g_initialized); + + return GetReference(g_client_storage); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_client.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_client.hpp new file mode 100644 index 00000000..3e3953d7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_client.hpp @@ -0,0 +1,73 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htcfs_client_impl.hpp" +#include "htcfs_result_utils.hpp" + +namespace ams::htcfs { + + class Client { + private: + ClientImpl m_impl; + public: + Client(htclow::HtclowManager *manager) : m_impl(manager) { /* ... */ } + public: + Result OpenFile(s32 *out_handle, const char *path, fs::OpenMode mode, bool case_sensitive) { R_RETURN(ConvertToFsResult(m_impl.OpenFile(out_handle, path, mode, case_sensitive))); } + Result FileExists(bool *out, const char *path, bool case_sensitive) { R_RETURN(ConvertToFsResult(m_impl.FileExists(out, path, case_sensitive))); } + Result DeleteFile(const char *path, bool case_sensitive) { R_RETURN(ConvertToFsResult(m_impl.DeleteFile(path, case_sensitive))); } + Result RenameFile(const char *old_path, const char *new_path, bool case_sensitive) { R_RETURN(ConvertToFsResult(m_impl.RenameFile(old_path, new_path, case_sensitive))); } + Result GetEntryType(fs::DirectoryEntryType *out, const char *path, bool case_sensitive) { R_RETURN(ConvertToFsResult(m_impl.GetEntryType(out, path, case_sensitive))); } + Result OpenDirectory(s32 *out_handle, const char *path, fs::OpenDirectoryMode mode, bool case_sensitive) { R_RETURN(ConvertToFsResult(m_impl.OpenDirectory(out_handle, path, mode, case_sensitive))); } + Result DirectoryExists(bool *out, const char *path, bool case_sensitive) { R_RETURN(ConvertToFsResult(m_impl.DirectoryExists(out, path, case_sensitive))); } + Result CreateDirectory(const char *path, bool case_sensitive) { R_RETURN(ConvertToFsResult(m_impl.CreateDirectory(path, case_sensitive))); } + Result DeleteDirectory(const char *path, bool recursively, bool case_sensitive) { R_RETURN(ConvertToFsResult(m_impl.DeleteDirectory(path, recursively, case_sensitive))); } + Result RenameDirectory(const char *old_path, const char *new_path, bool case_sensitive) { R_RETURN(ConvertToFsResult(m_impl.RenameDirectory(old_path, new_path, case_sensitive))); } + Result CreateFile(const char *path, s64 size, bool case_sensitive) { R_RETURN(ConvertToFsResult(m_impl.CreateFile(path, size, case_sensitive))); } + Result GetFileTimeStamp(u64 *out_create, u64 *out_access, u64 *out_modify, const char *path, bool case_sensitive) { R_RETURN(ConvertToFsResult(m_impl.GetFileTimeStamp(out_create, out_access, out_modify, path, case_sensitive))); } + Result GetCaseSensitivePath(char *dst, size_t dst_size, const char *path) { R_RETURN(ConvertToFsResult(m_impl.GetCaseSensitivePath(dst, dst_size, path))); } + Result GetDiskFreeSpace(s64 *out_free, s64 *out_total, s64 *out_total_free, const char *path) { R_RETURN(ConvertToFsResult(m_impl.GetDiskFreeSpace(out_free, out_total, out_total_free, path))); } + + Result CloseDirectory(s32 handle) { R_RETURN(ConvertToFsResult(m_impl.CloseDirectory(handle))); } + + Result GetEntryCount(s64 *out, s32 handle) { R_RETURN(ConvertToFsResult(m_impl.GetEntryCount(out, handle))); } + Result ReadDirectory(s64 *out, fs::DirectoryEntry *out_entries, size_t max_out_entries, s32 handle) { R_RETURN(ConvertToFsResult(m_impl.ReadDirectory(out, out_entries, max_out_entries, handle))); } + Result ReadDirectoryLarge(s64 *out, fs::DirectoryEntry *out_entries, size_t max_out_entries, s32 handle) { R_RETURN(ConvertToFsResult(m_impl.ReadDirectoryLarge(out, out_entries, max_out_entries, handle))); } + Result GetPriorityForDirectory(s32 *out, s32 handle) { R_RETURN(ConvertToFsResult(m_impl.GetPriorityForDirectory(out, handle))); } + Result SetPriorityForDirectory(s32 priority, s32 handle) { R_RETURN(ConvertToFsResult(m_impl.SetPriorityForDirectory(priority, handle))); } + + Result CloseFile(s32 handle) { R_RETURN(ConvertToFsResult(m_impl.CloseFile(handle))); } + + Result ReadFile(s64 *out, void *buffer, s32 handle, s64 offset, s64 buffer_size, fs::ReadOption option) { R_RETURN(ConvertToFsResult(m_impl.ReadFile(out, buffer, handle, offset, buffer_size, option))); } + Result ReadFileLarge(s64 *out, void *buffer, s32 handle, s64 offset, s64 buffer_size, fs::ReadOption option) { R_RETURN(ConvertToFsResult(m_impl.ReadFileLarge(out, buffer, handle, offset, buffer_size, option))); } + Result WriteFile(const void *buffer, s32 handle, s64 offset, s64 buffer_size, fs::WriteOption option) { R_RETURN(ConvertToFsResult(m_impl.WriteFile(buffer, handle, offset, buffer_size, option))); } + Result WriteFileLarge(const void *buffer, s32 handle, s64 offset, s64 buffer_size, fs::WriteOption option) { R_RETURN(ConvertToFsResult(m_impl.WriteFileLarge(buffer, handle, offset, buffer_size, option))); } + Result GetFileSize(s64 *out, s32 handle) { R_RETURN(ConvertToFsResult(m_impl.GetFileSize(out, handle))); } + Result SetFileSize(s64 size, s32 handle) { R_RETURN(ConvertToFsResult(m_impl.SetFileSize(size, handle))); } + Result FlushFile(s32 handle) { R_RETURN(ConvertToFsResult(m_impl.FlushFile(handle))); } + Result GetPriorityForFile(s32 *out, s32 handle) { R_RETURN(ConvertToFsResult(m_impl.GetPriorityForFile(out, handle))); } + Result SetPriorityForFile(s32 priority, s32 handle) { R_RETURN(ConvertToFsResult(m_impl.SetPriorityForFile(priority, handle))); } + + Result GetWorkingDirectory(char *dst, size_t dst_size) { R_RETURN(ConvertToFsResult(m_impl.GetWorkingDirectory(dst, dst_size))); } + Result GetWorkingDirectorySize(s32 *out) { R_RETURN(ConvertToFsResult(m_impl.GetWorkingDirectorySize(out))); } + }; + + void InitializeClient(htclow::HtclowManager *manager); + void FinalizeClient(); + + Client &GetClient(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_client_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_client_impl.cpp new file mode 100644 index 00000000..2dc9c270 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_client_impl.cpp @@ -0,0 +1,1625 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcfs_client_impl.hpp" +#include "htcfs_result.hpp" + +namespace ams::htcfs { + + namespace { + + /* TODO: Move to a header? */ + constexpr u16 RpcChannelId = 0; + constexpr u16 DataChannelId = 1; + + alignas(os::ThreadStackAlignment) constinit u8 g_monitor_thread_stack[os::MemoryPageSize]; + + constexpr size_t FileDataCacheSize = 32_KB; + constinit u8 g_cache[FileDataCacheSize]; + + ALWAYS_INLINE Result ConvertNativeResult(s64 value) { + return result::impl::MakeResult(value); + } + + } + + ClientImpl::ClientImpl(htclow::HtclowManager *manager) + : m_htclow_manager(manager), + m_cache_manager(g_cache, sizeof(g_cache)), + m_header_factory(), + m_mutex(), + m_module(htclow::ModuleId::Htcfs), + m_rpc_channel(manager), + m_data_channel(manager), + m_connected(false), + m_event(os::EventClearMode_ManualClear) + { + /* Start our thread. */ + this->Start(); + } + + void ClientImpl::Start() { + /* Create our thread. */ + os::CreateThread(std::addressof(m_monitor_thread), ThreadEntry, this, g_monitor_thread_stack, sizeof(g_monitor_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtcfsMonitor)); + + /* Set thread name pointer. */ + os::SetThreadNamePointer(std::addressof(m_monitor_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtcfsMonitor)); + + /* Start our thread. */ + os::StartThread(std::addressof(m_monitor_thread)); + } + + void ClientImpl::Cancel() { + /* Signal our event. */ + m_event.Signal(); + } + + void ClientImpl::Wait() { + /* Wait for and destroy our thread. */ + os::WaitThread(std::addressof(m_monitor_thread)); + os::DestroyThread(std::addressof(m_monitor_thread)); + } + + void ClientImpl::ThreadBody() { + /* Loop forever, until we're cancelled. */ + while (!m_event.TryWait()) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Open our channel. */ + R_ABORT_UNLESS(m_rpc_channel.Open(std::addressof(m_module), RpcChannelId)); + + /* Ensure that we clean up our channel when we're done. */ + ON_SCOPE_EXIT { + m_rpc_channel.Close(); + m_cache_manager.Invalidate(); + }; + + /* Set our channel config and buffers. */ + m_rpc_channel.SetConfig(htclow::DefaultChannelConfig); + m_rpc_channel.SetReceiveBuffer(m_receive_buffer, sizeof(m_receive_buffer)); + m_rpc_channel.SetSendBuffer(m_send_buffer, sizeof(m_send_buffer)); + + /* Wait for our channel state to be connectable. */ + if (this->WaitAny(htclow::ChannelState_Connectable, m_event.GetBase()) != 0) { + return; + } + + /* Ensure that when we're done, we reset our connected status. */ + ON_SCOPE_EXIT { m_connected = false; }; + + /* Try to connect. */ + const Result conn_result = m_rpc_channel.Connect(); + if (R_FAILED(conn_result)) { + /* DEBUG */ + R_ABORT_UNLESS(conn_result); + continue; + } + + /* Ensure that we manage our connection correctly. */ + auto conn_guard = SCOPE_GUARD { m_rpc_channel.Shutdown(); }; + + /* Try to set up the protocol. */ + const Result setup_result = this->SetUpProtocol(); + if (R_FAILED(setup_result)) { + R_ABORT_UNLESS(setup_result); + continue; + } + + /* We're properly connected now. */ + m_connected = true; + conn_guard.Cancel(); + + /* Tear down the protocol when we're done processing. */ + ON_SCOPE_EXIT { this->TearDownProtocol(); }; + + /* Wait to become disconnected. */ + if (this->WaitAny(htclow::ChannelState_Disconnected, m_event.GetBase()) != 0) { + break; + } + } + } + + int ClientImpl::WaitAny(htclow::ChannelState state, os::EventType *event) { + /* Wait. */ + int idx = 0; + while (m_rpc_channel.GetChannelState() != state) { + /* Get the channel state event. */ + os::EventType *channel_state_event = m_rpc_channel.GetChannelStateEvent(); + + /* Perform wait with lock temporarily not held. */ + { + m_mutex.Unlock(); + idx = os::WaitAny(channel_state_event, event); + m_mutex.Lock(); + } + + /* If we're cancel-signalled, we're done. */ + if (idx != 0) { + break; + } + + /* Clear the channel state event. */ + os::ClearEvent(channel_state_event); + } + return idx; + } + + Result ClientImpl::SetUpProtocol() { + /* Get the maximum supported protocol on the host side. */ + s16 max_host_protocol; + R_TRY(this->GetMaxProtocolVersion(std::addressof(max_host_protocol))); + + /* Verify that the host protocol is >= 0. */ + R_UNLESS(max_host_protocol >= 0, htcfs::ResultUnsupportedProtocolVersion()); + + /* Inform the host what protocol we're using. */ + const auto use_version = std::min(MaxProtocolVersion, max_host_protocol); + R_TRY(this->SetProtocolVersion(use_version)); + + /* Set the version in our header factory. */ + m_header_factory.SetVersion(use_version); + R_SUCCEED(); + } + + void ClientImpl::TearDownProtocol() { + /* Set the header factory version to zero. */ + m_header_factory.SetVersion(0); + } + + Result ClientImpl::CheckResponseHeaderWithoutVersion(const Header &response, PacketType packet_type) { + /* Check the protocol. */ + R_UNLESS(response.protocol == HtcfsProtocol, htcfs::ResultUnexpectedResponseProtocolId()); + + /* Check the packet category. */ + R_UNLESS(response.packet_category == PacketCategory::Response, htcfs::ResultUnexpectedResponsePacketCategory()); + + /* Check the type. */ + R_UNLESS(response.packet_type == packet_type, htcfs::ResultUnexpectedResponsePacketType()); + + R_SUCCEED(); + } + + Result ClientImpl::CheckResponseHeader(const Header &response, PacketType packet_type) { + /* Perform base checks. */ + R_TRY(this->CheckResponseHeaderWithoutVersion(response, packet_type)); + + /* Check the version. */ + R_UNLESS(response.version == m_header_factory.GetVersion(), htcfs::ResultUnexpectedResponseProtocolVersion()); + + R_SUCCEED(); + } + + Result ClientImpl::CheckResponseHeader(const Header &response, PacketType packet_type, s64 body_size) { + /* Perform base checks. */ + R_TRY(this->CheckResponseHeader(response, packet_type)); + + /* Check the body size. */ + R_UNLESS(response.body_size == body_size, htcfs::ResultUnexpectedResponseBodySize()); + + R_SUCCEED(); + } + + Result ClientImpl::GetMaxProtocolVersion(s16 *out) { + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeGetMaxProtocolVersionHeader(std::addressof(request)); + + /* Send the request to the host. */ + R_TRY(this->SendToRpcChannel(std::addressof(request), sizeof(request))); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeaderWithoutVersion(response, request.packet_type)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Set the maximum protocol version. */ + *out = response.params[1]; + + R_SUCCEED(); + } + + Result ClientImpl::SetProtocolVersion(s16 version) { + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeSetProtocolVersionHeader(std::addressof(request), version); + + /* Send the request to the host. */ + R_TRY(this->SendToRpcChannel(std::addressof(request), sizeof(request))); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeaderWithoutVersion(response, request.packet_type)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + R_SUCCEED(); + } + + Result ClientImpl::SendToRpcChannel(const void *src, s64 size) { + R_RETURN(this->SendToHtclow(src, size, std::addressof(m_rpc_channel))); + } + + Result ClientImpl::ReceiveFromRpcChannel(void *dst, s64 size) { + R_RETURN(this->ReceiveFromHtclow(dst, size, std::addressof(m_rpc_channel))); + } + + Result ClientImpl::ReceiveFromDataChannel(s64 size) { + R_RETURN(m_data_channel.WaitReceive(size)); + } + + Result ClientImpl::SendToDataChannel() { + R_RETURN(m_data_channel.Flush()); + } + + Result ClientImpl::SendToHtclow(const void *src, s64 size, htclow::Channel *channel) { + /* Check size. */ + R_UNLESS(size >= 0, htcfs::ResultInvalidArgument()); + + /* Iteratively send. */ + s64 sent; + for (s64 total = 0; total < size; total += sent) { + /* Send the current batch of data. */ + R_TRY(channel->Send(std::addressof(sent), static_cast<const u8 *>(src) + total, size - total)); + + /* Check that we sent the right amount. */ + R_UNLESS(sent == size - total, htcfs::ResultHtclowChannelClosed()); + } + + /* Flush. */ + R_TRY(channel->Flush()); + + R_SUCCEED(); + } + + Result ClientImpl::ReceiveFromHtclow(void *dst, s64 size, htclow::Channel *channel) { + /* Check size. */ + R_UNLESS(size >= 0, htcfs::ResultInvalidArgument()); + + /* Iteratively receive. */ + s64 received; + for (s64 total = 0; total < size; total += received) { + /* Receive the current batch of data. */ + R_TRY(channel->Receive(std::addressof(received), static_cast<u8 *>(dst) + total, size - total, htclow::ReceiveOption_ReceiveAllData)); + + /* Check that we received the right amount. */ + R_UNLESS(received == size - total, htcfs::ResultHtclowChannelClosed()); + } + + /* Flush. */ + R_TRY(channel->Flush()); + + R_SUCCEED(); + } + + Result ClientImpl::InitializeRpcChannel() { + /* Check that we're not cancelled. */ + R_UNLESS(!m_event.TryWait(), htcfs::ResultConnectionFailure()); + + /* Check that we're connected. */ + R_UNLESS(m_connected, htcfs::ResultConnectionFailure()); + + R_SUCCEED(); + } + + void ClientImpl::InitializeDataChannelForReceive(void *dst, size_t size) { + /* Open the data channel. */ + R_ABORT_UNLESS(m_data_channel.Open(std::addressof(m_module), DataChannelId)); + + /* Set our config. */ + constexpr htclow::ChannelConfig BulkReceiveConfig = { + .flow_control_enabled = false, + .handshake_enabled = false, + .max_packet_size = 0x3E000, + }; + m_data_channel.SetConfig(BulkReceiveConfig); + + /* Set receive buffer. */ + m_data_channel.SetReceiveBuffer(dst, size); + + /* Connect. */ + R_ABORT_UNLESS(m_data_channel.Connect()); + } + + void ClientImpl::InitializeDataChannelForSend(const void *src, size_t size) { + /* Open the data channel. */ + R_ABORT_UNLESS(m_data_channel.Open(std::addressof(m_module), DataChannelId)); + + /* Check that the size is valid. */ + AMS_ASSERT(util::IsIntValueRepresentable<s64>(size)); + + /* Set our config. */ + constexpr htclow::ChannelConfig BulkSendConfig = { + .flow_control_enabled = false, + .handshake_enabled = false, + .max_packet_size = 0xE020, + }; + m_data_channel.SetConfig(BulkSendConfig); + + /* Set our send buffer. */ + m_data_channel.SetSendBufferWithData(src, size); + + /* Connect. */ + R_ABORT_UNLESS(m_data_channel.Connect()); + } + + void ClientImpl::FinalizeDataChannel() { + /* Close our data channel. */ + m_data_channel.Close(); + } + + Result ClientImpl::SendRequest(const Header &request, const void *arg1, size_t arg1_size, const void *arg2, size_t arg2_size) { + /* Try to perform an optimized send. */ + if (sizeof(request) + arg1_size + arg2_size < sizeof(m_packet_buffer)) { + /* Setup our packet buffer. */ + std::memcpy(m_packet_buffer, std::addressof(request), sizeof(request)); + if (arg1_size > 0) { + std::memcpy(m_packet_buffer + sizeof(request), arg1, arg1_size); + } + if (arg2_size > 0) { + std::memcpy(m_packet_buffer + sizeof(request) + arg1_size, arg2, arg2_size); + } + + /* Send the request. */ + R_TRY(this->SendToRpcChannel(m_packet_buffer, sizeof(request) + arg1_size + arg2_size)); + } else { + /* We can't perform a single optimized send, so perform three separate sends. */ + R_TRY(this->SendToRpcChannel(std::addressof(request), sizeof(request))); + + if (arg1_size > 0) { + R_TRY(this->SendToRpcChannel(arg1, arg1_size)); + } + + if (arg2_size > 0) { + R_TRY(this->SendToRpcChannel(arg2, arg2_size)); + } + } + + R_SUCCEED(); + } + + Result ClientImpl::OpenFile(s32 *out_handle, const char *path, fs::OpenMode mode, bool case_sensitive) { + /* Invalidate the cache manager. */ + m_cache_manager.Invalidate(); + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + const auto path_len = std::strlen(path); + m_header_factory.MakeOpenFileHeader(std::addressof(request), path_len, mode, case_sensitive, FileDataCacheSize); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request, path, path_len)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type)); + + /* Check the response body size. */ + R_UNLESS(response.body_size >= 0, htcfs::ResultUnexpectedResponseBodySize()); + R_UNLESS(static_cast<size_t>(response.body_size) <= MaxPacketBodySize, htcfs::ResultUnexpectedResponseBodySize()); + + /* Receive the response body. */ + if (response.body_size > 0) { + R_TRY(this->ReceiveFromRpcChannel(m_packet_buffer, response.body_size)); + } + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + /* Set our output handle. */ + *out_handle = response.params[2]; + + /* If we have data to cache, cache it. */ + if (response.params[3]) { + m_cache_manager.Record(response.params[4], m_packet_buffer, response.params[2], response.body_size); + } + + R_SUCCEED(); + } + + Result ClientImpl::FileExists(bool *out, const char *path, bool case_sensitive) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + const auto path_len = std::strlen(path); + m_header_factory.MakeFileExistsHeader(std::addressof(request), path_len, case_sensitive); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request, path, path_len)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + /* Set the output. */ + *out = response.params[2] != 0; + + R_SUCCEED(); + } + + Result ClientImpl::DeleteFile(const char *path, bool case_sensitive) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + const auto path_len = std::strlen(path); + m_header_factory.MakeDeleteFileHeader(std::addressof(request), path_len, case_sensitive); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request, path, path_len)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + R_SUCCEED(); + } + + Result ClientImpl::RenameFile(const char *old_path, const char *new_path, bool case_sensitive) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + const auto old_path_len = std::strlen(new_path); + const auto new_path_len = std::strlen(old_path); + m_header_factory.MakeRenameFileHeader(std::addressof(request), old_path_len, new_path_len, case_sensitive); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request, old_path, old_path_len, new_path, new_path_len)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + R_SUCCEED(); + } + + Result ClientImpl::GetEntryType(fs::DirectoryEntryType *out, const char *path, bool case_sensitive) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + const auto path_len = std::strlen(path); + m_header_factory.MakeGetEntryTypeHeader(std::addressof(request), path_len, case_sensitive); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request, path, path_len)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + /* Set the output. */ + *out = static_cast<fs::DirectoryEntryType>(response.params[2]); + + R_SUCCEED(); + } + + Result ClientImpl::OpenDirectory(s32 *out_handle, const char *path, fs::OpenDirectoryMode mode, bool case_sensitive) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + const auto path_len = std::strlen(path); + m_header_factory.MakeOpenDirectoryHeader(std::addressof(request), path_len, mode, case_sensitive); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request, path, path_len)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + /* Set the output handle. */ + *out_handle = static_cast<s32>(response.params[2]); + + R_SUCCEED(); + } + + Result ClientImpl::DirectoryExists(bool *out, const char *path, bool case_sensitive) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + const auto path_len = std::strlen(path); + m_header_factory.MakeDirectoryExistsHeader(std::addressof(request), path_len, case_sensitive); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request, path, path_len)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + /* Set the output. */ + *out = response.params[2] != 0; + + R_SUCCEED(); + } + + Result ClientImpl::CreateDirectory(const char *path, bool case_sensitive) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + const auto path_len = std::strlen(path); + m_header_factory.MakeCreateDirectoryHeader(std::addressof(request), path_len, case_sensitive); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request, path, path_len)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + R_SUCCEED(); + } + + Result ClientImpl::DeleteDirectory(const char *path, bool recursively, bool case_sensitive) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + const auto path_len = std::strlen(path); + m_header_factory.MakeDeleteDirectoryHeader(std::addressof(request), path_len, recursively, case_sensitive); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request, path, path_len)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + R_SUCCEED(); + } + + Result ClientImpl::RenameDirectory(const char *old_path, const char *new_path, bool case_sensitive) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + const auto old_path_len = std::strlen(new_path); + const auto new_path_len = std::strlen(old_path); + m_header_factory.MakeRenameDirectoryHeader(std::addressof(request), old_path_len, new_path_len, case_sensitive); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request, old_path, old_path_len, new_path, new_path_len)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + R_SUCCEED(); + } + + Result ClientImpl::CreateFile(const char *path, s64 size, bool case_sensitive) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + const auto path_len = std::strlen(path); + m_header_factory.MakeCreateFileHeader(std::addressof(request), path_len, size, case_sensitive); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request, path, path_len)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + R_SUCCEED(); + } + + Result ClientImpl::GetFileTimeStamp(u64 *out_create, u64 *out_access, u64 *out_modify, const char *path, bool case_sensitive) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + const auto path_len = std::strlen(path); + m_header_factory.MakeGetFileTimeStampHeader(std::addressof(request), path_len, case_sensitive); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request, path, path_len)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + /* Set output. */ + *out_create = static_cast<u64>(response.params[2]); + *out_access = static_cast<u64>(response.params[3]); + *out_modify = static_cast<u64>(response.params[4]); + + R_SUCCEED(); + } + + Result ClientImpl::GetCaseSensitivePath(char *dst, size_t dst_size, const char *path) { + /* Sanity check the output buffer. */ + R_UNLESS(util::IsIntValueRepresentable<s64>(dst_size), htcfs::ResultInvalidArgument()); + R_UNLESS(dst_size > 0, htcfs::ResultInvalidArgument()); + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + const auto path_len = std::strlen(path); + m_header_factory.MakeGetCaseSensitivePathHeader(std::addressof(request), path_len); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request, path, path_len)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type)); + + /* Check that we succeeded. */ + const auto htcfs_result = ConvertHtcfsResult(response.params[0]); + if (R_FAILED(htcfs_result)) { + R_UNLESS(response.body_size == 0, htcfs::ResultUnexpectedResponseBodySize()); + R_RETURN(htcfs_result); + } + + /* Check our operation's result. */ + const auto native_result = ConvertNativeResult(response.params[1]); + if (R_FAILED(native_result)) { + R_UNLESS(response.body_size == 0, htcfs::ResultUnexpectedResponseBodySize()); + R_RETURN(native_result); + } + + /* Check the body size. */ + R_UNLESS(response.body_size < static_cast<s64>(dst_size), htcfs::ResultUnexpectedResponseBodySize()); + + /* Receive the response body. */ + R_TRY(this->ReceiveFromRpcChannel(dst, response.body_size)); + + /* Null-terminate the output path. */ + dst[response.body_size] = '\x00'; + + R_SUCCEED(); + } + + Result ClientImpl::GetDiskFreeSpace(s64 *out_free, s64 *out_total, s64 *out_total_free, const char *path) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + const auto path_len = std::strlen(path); + m_header_factory.MakeGetDiskFreeSpaceHeader(std::addressof(request), path_len); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request, path, path_len)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + /* Set output. */ + *out_free = response.params[2]; + *out_total = response.params[3]; + *out_total_free = response.params[4]; + + R_SUCCEED(); + } + + Result ClientImpl::CloseDirectory(s32 handle) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeCloseDirectoryHeader(std::addressof(request), handle); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + R_SUCCEED(); + } + + Result ClientImpl::GetEntryCount(s64 *out, s32 handle) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeGetEntryCountHeader(std::addressof(request), handle); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + /* Set the output count. */ + *out = response.params[2]; + + R_SUCCEED(); + } + + Result ClientImpl::ReadDirectory(s64 *out, fs::DirectoryEntry *out_entries, size_t max_out_entries, s32 handle) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeReadDirectoryHeader(std::addressof(request), handle, max_out_entries); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + /* Check that the response body size is expected. */ + R_UNLESS(static_cast<size_t>(response.body_size) == max_out_entries * sizeof(*out_entries), htcfs::ResultUnexpectedResponseBody()); + + /* Check that the number of entries read is allowable. */ + R_UNLESS(static_cast<size_t>(response.params[2]) <= max_out_entries, htcfs::ResultUnexpectedResponseBody()); + + /* Receive the entries. */ + *out = response.params[2]; + R_RETURN(this->ReceiveFromRpcChannel(out_entries, response.body_size)); + } + + Result ClientImpl::ReadDirectoryLarge(s64 *out, fs::DirectoryEntry *out_entries, size_t max_out_entries, s32 handle) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Setup data channel. */ + const bool use_data_channel = max_out_entries > 0; + if (use_data_channel) { + this->InitializeDataChannelForReceive(out_entries, max_out_entries * sizeof(*out_entries)); + } + ON_SCOPE_EXIT { if (use_data_channel) { this->FinalizeDataChannel(); } }; + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeReadDirectoryLargeHeader(std::addressof(request), handle, max_out_entries, DataChannelId); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + const auto htcfs_result = ConvertHtcfsResult(response.params[0]); + if (R_FAILED(htcfs_result)) { + R_UNLESS(response.body_size == 0, htcfs::ResultUnexpectedResponseBodySize()); + R_RETURN(htcfs_result); + } + + /* Check our operation's result. */ + const auto native_result = ConvertNativeResult(response.params[1]); + if (R_FAILED(native_result)) { + R_UNLESS(response.body_size == 0, htcfs::ResultUnexpectedResponseBodySize()); + R_RETURN(native_result); + } + + /* Check that the number of entries read is allowable. */ + R_UNLESS(static_cast<size_t>(response.params[2]) <= max_out_entries, htcfs::ResultUnexpectedResponseBody()); + + /* Read the entries, if there are any. */ + if (response.params[2] > 0) { + R_TRY(this->ReceiveFromDataChannel(response.params[2] * sizeof(*out_entries))); + } + + /* Set the number of output entries. */ + *out = response.params[2]; + + R_SUCCEED(); + } + + Result ClientImpl::GetPriorityForDirectory(s32 *out, s32 handle) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeGetPriorityForDirectoryHeader(std::addressof(request), handle); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Set the output. */ + *out = static_cast<s32>(response.params[1]); + + R_SUCCEED(); + } + + Result ClientImpl::SetPriorityForDirectory(s32 priority, s32 handle) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeSetPriorityForDirectoryHeader(std::addressof(request), handle, priority); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + R_SUCCEED(); + } + + Result ClientImpl::CloseFile(s32 handle) { + /* Invalidate the cache. */ + m_cache_manager.Invalidate(handle); + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeCloseFileHeader(std::addressof(request), handle); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + R_SUCCEED(); + } + + Result ClientImpl::ReadFile(s64 *out, void *buffer, s32 handle, s64 offset, s64 buffer_size, fs::ReadOption option) { + AMS_UNUSED(option); + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Try to read from our cache. */ + if (util::IsIntValueRepresentable<size_t>(offset) && util::IsIntValueRepresentable<size_t>(buffer_size)) { + size_t read_size; + if (m_cache_manager.ReadFile(std::addressof(read_size), buffer, handle, static_cast<size_t>(offset), static_cast<size_t>(buffer_size))) { + AMS_ASSERT(util::IsIntValueRepresentable<s64>(read_size)); + + *out = static_cast<s64>(read_size); + R_SUCCEED(); + } + } + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeReadFileHeader(std::addressof(request), handle, offset, buffer_size); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type)); + + /* Check that we succeeded. */ + const auto htcfs_result = ConvertHtcfsResult(response.params[0]); + if (R_FAILED(htcfs_result)) { + R_UNLESS(response.body_size == 0, htcfs::ResultUnexpectedResponseBodySize()); + R_RETURN(htcfs_result); + } + + /* Check our operation's result. */ + const auto native_result = ConvertNativeResult(response.params[1]); + if (R_FAILED(native_result)) { + R_UNLESS(response.body_size == 0, htcfs::ResultUnexpectedResponseBodySize()); + R_RETURN(native_result); + } + + /* Check the body size. */ + R_UNLESS(response.body_size <= buffer_size, htcfs::ResultUnexpectedResponseBodySize()); + + /* Receive the file data. */ + R_TRY(this->ReceiveFromRpcChannel(buffer, response.body_size)); + + /* Set the output size. */ + *out = response.body_size; + + R_SUCCEED(); + } + + Result ClientImpl::ReadFileLarge(s64 *out, void *buffer, s32 handle, s64 offset, s64 buffer_size, fs::ReadOption option) { + AMS_UNUSED(option); + + /* Check our buffer size. */ + R_UNLESS(util::IsIntValueRepresentable<size_t>(buffer_size), htcfs::ResultInvalidArgument()); + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Setup data channel. */ + this->InitializeDataChannelForReceive(buffer, buffer_size); + ON_SCOPE_EXIT { this->FinalizeDataChannel(); }; + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeReadFileLargeHeader(std::addressof(request), handle, offset, buffer_size, DataChannelId); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + const auto htcfs_result = ConvertHtcfsResult(response.params[0]); + if (R_FAILED(htcfs_result)) { + R_UNLESS(response.body_size == 0, htcfs::ResultUnexpectedResponseBodySize()); + R_RETURN(htcfs_result); + } + + /* Check our operation's result. */ + const auto native_result = ConvertNativeResult(response.params[1]); + if (R_FAILED(native_result)) { + R_UNLESS(response.body_size == 0, htcfs::ResultUnexpectedResponseBodySize()); + R_RETURN(native_result); + } + + /* Check that the size read is allowable. */ + R_UNLESS(response.params[2] <= buffer_size, htcfs::ResultUnexpectedResponseBodySize()); + + /* Read the entries, if there are any. */ + R_TRY(this->ReceiveFromDataChannel(response.params[2])); + + /* Set the number of output entries. */ + *out = response.params[2]; + + R_SUCCEED(); + } + + Result ClientImpl::WriteFile(const void *buffer, s32 handle, s64 offset, s64 buffer_size, fs::WriteOption option) { + /* Invalidate the cache. */ + m_cache_manager.Invalidate(handle); + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeWriteFileHeader(std::addressof(request), buffer_size, handle, option._value, offset); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request, buffer, buffer_size)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + R_SUCCEED(); + } + + Result ClientImpl::WriteFileLarge(const void *buffer, s32 handle, s64 offset, s64 buffer_size, fs::WriteOption option) { + /* Invalidate the cache. */ + m_cache_manager.Invalidate(handle); + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeWriteFileLargeHeader(std::addressof(request), handle, option._value, offset, buffer_size, DataChannelId); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Verify that the host reports ready to receive our data. */ + if (static_cast<HtcfsResult>(response.params[0]) != HtcfsResult::Ready) { + R_RETURN(ConvertHtcfsResult(response.params[0])); + } + + /* Verify that our send will be valid. */ + AMS_ASSERT(util::IsIntValueRepresentable<size_t>(buffer_size)); + + /* Perform the send. */ + { + /* Initialize data channel for our write. */ + this->InitializeDataChannelForSend(buffer, buffer_size); + + /* Ensure that we clean up our data channel. */ + ON_SCOPE_EXIT { this->FinalizeDataChannel(); }; + + /* Send to our data channel. */ + R_TRY(this->SendToDataChannel()); + } + + /* Receive the large-write response. */ + Header write_resp; + R_TRY(this->ReceiveFromRpcChannel(std::addressof(write_resp), sizeof(write_resp))); + + /* Check the write-response header. */ + R_TRY(this->CheckResponseHeader(write_resp, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(write_resp.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(write_resp.params[1])); + + R_SUCCEED(); + } + + Result ClientImpl::GetFileSize(s64 *out, s32 handle) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Check if we have the file size cached. */ + R_SUCCEED_IF(m_cache_manager.GetFileSize(out, handle)); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeGetFileSizeHeader(std::addressof(request), handle); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + /* Set the output. */ + *out = response.params[2]; + + R_SUCCEED(); + } + + Result ClientImpl::SetFileSize(s64 size, s32 handle) { + /* Invalidate the cache. */ + m_cache_manager.Invalidate(handle); + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeSetFileSizeHeader(std::addressof(request), handle, size); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + R_SUCCEED(); + } + + Result ClientImpl::FlushFile(s32 handle) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeFlushFileHeader(std::addressof(request), handle); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Check our operation's result. */ + R_TRY(ConvertNativeResult(response.params[1])); + + R_SUCCEED(); + } + + Result ClientImpl::GetPriorityForFile(s32 *out, s32 handle) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeGetPriorityForFileHeader(std::addressof(request), handle); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + /* Set the output. */ + *out = static_cast<s32>(response.params[1]); + + R_SUCCEED(); + } + + Result ClientImpl::SetPriorityForFile(s32 priority, s32 handle) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeSetPriorityForFileHeader(std::addressof(request), handle, priority); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type, 0)); + + /* Check that we succeeded. */ + R_TRY(ConvertHtcfsResult(response.params[0])); + + R_SUCCEED(); + } + + Result ClientImpl::GetWorkingDirectory(char *dst, size_t dst_size) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeGetWorkingDirectoryHeader(std::addressof(request)); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type)); + + /* Check that we succeeded. */ + const auto htcfs_result = ConvertHtcfsResult(response.params[0]); + if (R_FAILED(htcfs_result)) { + R_UNLESS(response.body_size == 0, htcfs::ResultUnexpectedResponseBodySize()); + R_RETURN(htcfs_result); + } + + /* Check that the body size is valid. */ + R_UNLESS(response.body_size < static_cast<s64>(dst_size), htcfs::ResultUnexpectedResponseBodySize()); + + /* Receive the response body. */ + R_TRY(this->ReceiveFromRpcChannel(dst, response.body_size)); + + /* Null-terminate the response body. */ + dst[response.body_size] = '\x00'; + + R_SUCCEED(); + } + + Result ClientImpl::GetWorkingDirectorySize(s32 *out) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our rpc channel. */ + R_TRY(this->InitializeRpcChannel()); + + /* Create space for request and response. */ + Header request, response; + + /* Create header for the request. */ + m_header_factory.MakeGetWorkingDirectorySizeHeader(std::addressof(request)); + + /* Send the request to the host. */ + R_TRY(this->SendRequest(request)); + + /* Receive response from the host. */ + R_TRY(this->ReceiveFromRpcChannel(std::addressof(response), sizeof(response))); + + /* Check the response header. */ + R_TRY(this->CheckResponseHeader(response, request.packet_type)); + + /* Check that we succeeded. */ + const auto htcfs_result = ConvertHtcfsResult(response.params[0]); + if (R_FAILED(htcfs_result)) { + R_UNLESS(response.body_size == 0, htcfs::ResultUnexpectedResponseBodySize()); + R_RETURN(htcfs_result); + } + + /* Check that the size is representable. */ + R_UNLESS(util::IsIntValueRepresentable<s32>(response.params[1]), htcfs::ResultInvalidSize()); + + /* Set the output size. */ + *out = static_cast<s32>(response.params[1]); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_client_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_client_impl.hpp new file mode 100644 index 00000000..a6587690 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_client_impl.hpp @@ -0,0 +1,129 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../htclow/htclow_manager.hpp" +#include "../htclow/htclow_channel.hpp" +#include "htcfs_cache_manager.hpp" +#include "htcfs_header_factory.hpp" +#include "../htclow/htclow_default_channel_config.hpp" + +namespace ams::htcfs { + + class ClientImpl { + public: + static constexpr size_t MaxPacketBodySize = htclow::DefaultChannelConfig.max_packet_size - sizeof(htclow::PacketHeader); + private: + u8 m_receive_buffer[0x1C040]; + u8 m_send_buffer[0x1C040]; + u8 m_packet_buffer[MaxPacketBodySize + sizeof(htclow::PacketHeader)]; + htclow::HtclowManager *m_htclow_manager; + CacheManager m_cache_manager; + HeaderFactory m_header_factory; + os::SdkMutex m_mutex; + htclow::Module m_module; + htclow::Channel m_rpc_channel; + htclow::Channel m_data_channel; + bool m_connected; + os::ThreadType m_monitor_thread; + os::Event m_event; + private: + static void ThreadEntry(void *arg) { static_cast<ClientImpl *>(arg)->ThreadBody(); } + + void ThreadBody(); + public: + ClientImpl(htclow::HtclowManager *manager); + + ~ClientImpl() { + this->Cancel(); + this->Wait(); + } + public: + void Start(); + void Cancel(); + void Wait(); + public: + Result OpenFile(s32 *out_handle, const char *path, fs::OpenMode mode, bool case_sensitive); + Result FileExists(bool *out, const char *path, bool case_sensitive); + Result DeleteFile(const char *path, bool case_sensitive); + Result RenameFile(const char *old_path, const char *new_path, bool case_sensitive); + Result GetEntryType(fs::DirectoryEntryType *out, const char *path, bool case_sensitive); + Result OpenDirectory(s32 *out_handle, const char *path, fs::OpenDirectoryMode mode, bool case_sensitive); + Result DirectoryExists(bool *out, const char *path, bool case_sensitive); + Result CreateDirectory(const char *path, bool case_sensitive); + Result DeleteDirectory(const char *path, bool recursively, bool case_sensitive); + Result RenameDirectory(const char *old_path, const char *new_path, bool case_sensitive); + Result CreateFile(const char *path, s64 size, bool case_sensitive); + Result GetFileTimeStamp(u64 *out_create, u64 *out_access, u64 *out_modify, const char *path, bool case_sensitive); + Result GetCaseSensitivePath(char *dst, size_t dst_size, const char *path); + Result GetDiskFreeSpace(s64 *out_free, s64 *out_total, s64 *out_total_free, const char *path); + + Result CloseDirectory(s32 handle); + + Result GetEntryCount(s64 *out, s32 handle); + Result ReadDirectory(s64 *out, fs::DirectoryEntry *out_entries, size_t max_out_entries, s32 handle); + Result ReadDirectoryLarge(s64 *out, fs::DirectoryEntry *out_entries, size_t max_out_entries, s32 handle); + Result GetPriorityForDirectory(s32 *out, s32 handle); + Result SetPriorityForDirectory(s32 priority, s32 handle); + + Result CloseFile(s32 handle); + + Result ReadFile(s64 *out, void *buffer, s32 handle, s64 offset, s64 buffer_size, fs::ReadOption option); + Result ReadFileLarge(s64 *out, void *buffer, s32 handle, s64 offset, s64 buffer_size, fs::ReadOption option); + Result WriteFile(const void *buffer, s32 handle, s64 offset, s64 buffer_size, fs::WriteOption option); + Result WriteFileLarge(const void *buffer, s32 handle, s64 offset, s64 buffer_size, fs::WriteOption option); + Result GetFileSize(s64 *out, s32 handle); + Result SetFileSize(s64 size, s32 handle); + Result FlushFile(s32 handle); + Result GetPriorityForFile(s32 *out, s32 handle); + Result SetPriorityForFile(s32 priority, s32 handle); + + Result GetWorkingDirectory(char *dst, size_t dst_size); + Result GetWorkingDirectorySize(s32 *out); + private: + int WaitAny(htclow::ChannelState state, os::EventType *event); + + Result SetUpProtocol(); + void TearDownProtocol(); + + Result CheckResponseHeaderWithoutVersion(const Header &response, PacketType packet_type); + Result CheckResponseHeader(const Header &response, PacketType packet_type); + Result CheckResponseHeader(const Header &response, PacketType packet_type, s64 body_size); + + Result GetMaxProtocolVersion(s16 *out); + Result SetProtocolVersion(s16 version); + + Result InitializeRpcChannel(); + + Result SendToRpcChannel(const void *src, s64 size); + Result ReceiveFromRpcChannel(void *dst, s64 size); + + Result ReceiveFromDataChannel(s64 size); + Result SendToDataChannel(); + + Result SendToHtclow(const void *src, s64 size, htclow::Channel *channel); + Result ReceiveFromHtclow(void *dst, s64 size, htclow::Channel *channel); + + Result SendRequest(const Header &request) { R_RETURN(this->SendRequest(request, nullptr, 0, nullptr, 0)); } + Result SendRequest(const Header &request, const void *arg1, size_t arg1_size) { R_RETURN(this->SendRequest(request, arg1, arg1_size, nullptr, 0)); } + Result SendRequest(const Header &request, const void *arg1, size_t arg1_size, const void *arg2, size_t arg2_size); + + void InitializeDataChannelForReceive(void *dst, size_t size); + void InitializeDataChannelForSend(const void *src, size_t size); + void FinalizeDataChannel(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_directory_service_object.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_directory_service_object.cpp new file mode 100644 index 00000000..a24c066a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_directory_service_object.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcfs_directory_service_object.hpp" +#include "htcfs_client.hpp" + +namespace ams::htcfs { + + DirectoryServiceObject::DirectoryServiceObject(s32 handle) : m_handle(handle) { /* ... */ } + + DirectoryServiceObject::~DirectoryServiceObject() { + htcfs::GetClient().CloseDirectory(m_handle); + } + + Result DirectoryServiceObject::GetEntryCount(ams::sf::Out<s64> out) { + R_RETURN(htcfs::GetClient().GetEntryCount(out.GetPointer(), m_handle)); + } + + Result DirectoryServiceObject::Read(ams::sf::Out<s64> out, const ams::sf::OutMapAliasArray<fs::DirectoryEntry> &out_entries) { + if (out_entries.GetSize() * sizeof(fs::DirectoryEntry) >= ClientImpl::MaxPacketBodySize) { + R_RETURN(htcfs::GetClient().ReadDirectoryLarge(out.GetPointer(), out_entries.GetPointer(), out_entries.GetSize(), m_handle)); + } else { + R_RETURN(htcfs::GetClient().ReadDirectory(out.GetPointer(), out_entries.GetPointer(), out_entries.GetSize(), m_handle)); + } + } + + Result DirectoryServiceObject::SetPriorityForDirectory(s32 priority) { + R_RETURN(htcfs::GetClient().SetPriorityForDirectory(priority, m_handle)); + } + + Result DirectoryServiceObject::GetPriorityForDirectory(ams::sf::Out<s32> out) { + R_RETURN(htcfs::GetClient().GetPriorityForDirectory(out.GetPointer(), m_handle)); + } + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_directory_service_object.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_directory_service_object.hpp new file mode 100644 index 00000000..b6f43c2e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_directory_service_object.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htcfs { + + class DirectoryServiceObject { + private: + s32 m_handle; + public: + explicit DirectoryServiceObject(s32 handle); + ~DirectoryServiceObject(); + public: + Result GetEntryCount(ams::sf::Out<s64> out); + Result Read(ams::sf::Out<s64> out, const ams::sf::OutMapAliasArray<fs::DirectoryEntry> &out_entries); + Result SetPriorityForDirectory(s32 priority); + Result GetPriorityForDirectory(ams::sf::Out<s32> out); + }; + static_assert(tma::IsIDirectoryAccessor<DirectoryServiceObject>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_file_service_object.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_file_service_object.cpp new file mode 100644 index 00000000..974542dd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_file_service_object.cpp @@ -0,0 +1,73 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcfs_file_service_object.hpp" +#include "htcfs_client.hpp" + +namespace ams::htcfs { + + FileServiceObject::FileServiceObject(s32 handle) : m_handle(handle) { /* ... */ } + + FileServiceObject::~FileServiceObject() { + htcfs::GetClient().CloseFile(m_handle); + } + + Result FileServiceObject::ReadFile(ams::sf::Out<s64> out, s64 offset, const ams::sf::OutNonSecureBuffer &buffer, ams::fs::ReadOption option) { + /* Validate offset. */ + R_UNLESS(offset >= 0, htcfs::ResultInvalidArgument()); + + if (buffer.GetSize() >= ClientImpl::MaxPacketBodySize) { + R_RETURN(htcfs::GetClient().ReadFileLarge(out.GetPointer(), buffer.GetPointer(), m_handle, offset, buffer.GetSize(), option)); + } else { + R_RETURN(htcfs::GetClient().ReadFile(out.GetPointer(), buffer.GetPointer(), m_handle, offset, buffer.GetSize(), option)); + } + } + + Result FileServiceObject::WriteFile(s64 offset, const ams::sf::InNonSecureBuffer &buffer, ams::fs::WriteOption option) { + /* Validate offset. */ + R_UNLESS(offset >= 0, htcfs::ResultInvalidArgument()); + + if (buffer.GetSize() >= ClientImpl::MaxPacketBodySize) { + R_RETURN(htcfs::GetClient().WriteFileLarge(buffer.GetPointer(), m_handle, offset, buffer.GetSize(), option)); + } else { + R_RETURN(htcfs::GetClient().WriteFile(buffer.GetPointer(), m_handle, offset, buffer.GetSize(), option)); + } + } + + Result FileServiceObject::GetFileSize(ams::sf::Out<s64> out) { + R_RETURN(htcfs::GetClient().GetFileSize(out.GetPointer(), m_handle)); + } + + Result FileServiceObject::SetFileSize(s64 size) { + /* Validate size. */ + R_UNLESS(size >= 0, htcfs::ResultInvalidArgument()); + + R_RETURN(htcfs::GetClient().SetFileSize(size, m_handle)); + } + + Result FileServiceObject::FlushFile() { + R_RETURN(htcfs::GetClient().FlushFile(m_handle)); + } + + Result FileServiceObject::SetPriorityForFile(s32 priority) { + R_RETURN(htcfs::GetClient().SetPriorityForFile(priority, m_handle)); + } + + Result FileServiceObject::GetPriorityForFile(ams::sf::Out<s32> out) { + R_RETURN(htcfs::GetClient().GetPriorityForFile(out.GetPointer(), m_handle)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_file_service_object.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_file_service_object.hpp new file mode 100644 index 00000000..f7f2428a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_file_service_object.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htcfs { + + class FileServiceObject { + private: + s32 m_handle; + public: + explicit FileServiceObject(s32 handle); + ~FileServiceObject(); + public: + Result ReadFile(ams::sf::Out<s64> out, s64 offset, const ams::sf::OutNonSecureBuffer &buffer, ams::fs::ReadOption option); + Result WriteFile(s64 offset, const ams::sf::InNonSecureBuffer &buffer, ams::fs::WriteOption option); + Result GetFileSize(ams::sf::Out<s64> out); + Result SetFileSize(s64 size); + Result FlushFile(); + Result SetPriorityForFile(s32 priority); + Result GetPriorityForFile(ams::sf::Out<s32> out); + }; + static_assert(tma::IsIFileAccessor<FileServiceObject>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_file_system_service_object.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_file_system_service_object.cpp new file mode 100644 index 00000000..b079f801 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_file_system_service_object.cpp @@ -0,0 +1,196 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcfs_file_system_service_object.hpp" +#include "htcfs_file_service_object.hpp" +#include "htcfs_directory_service_object.hpp" +#include "htcfs_client.hpp" + +namespace ams::htcfs { + + namespace { + + struct DirectoryServiceObjectAllocatorTag; + struct FileServiceObjectAllocatorTag; + + using DirectoryServiceObjectAllocator = ams::sf::ExpHeapStaticAllocator<4_KB, DirectoryServiceObjectAllocatorTag>; + using FileServiceObjectAllocator = ams::sf::ExpHeapStaticAllocator<4_KB, FileServiceObjectAllocatorTag>; + using DirectoryServiceObjectFactory = ams::sf::ObjectFactory<typename DirectoryServiceObjectAllocator::Policy>; + using FileServiceObjectFactory = ams::sf::ObjectFactory<typename FileServiceObjectAllocator::Policy>; + + class StaticAllocatorInitializer { + public: + StaticAllocatorInitializer() { + DirectoryServiceObjectAllocator::Initialize(lmem::CreateOption_ThreadSafe); + FileServiceObjectAllocator::Initialize(lmem::CreateOption_ThreadSafe); + } + } g_static_allocator_initializer; + + constexpr bool IsValidPath(const tma::Path &path) { + const auto len = util::Strnlen(path.str, fs::EntryNameLengthMax + 1); + return 0 < len && len < static_cast<int>(fs::EntryNameLengthMax + 1); + } + + Result ConvertOpenMode(fs::OpenMode *out, u32 open_mode) { + switch (open_mode) { + case 1: + *out = fs::OpenMode_Read; + break; + case 2: + *out = static_cast<fs::OpenMode>(fs::OpenMode_Write | fs::OpenMode_AllowAppend); + break; + case 3: + *out = static_cast<fs::OpenMode>(fs::OpenMode_ReadWrite | fs::OpenMode_AllowAppend); + break; + default: + R_THROW(htcfs::ResultInvalidArgument()); + } + + R_SUCCEED(); + } + + } + + Result FileSystemServiceObject::OpenFile(sf::Out<sf::SharedPointer<tma::IFileAccessor>> out, const tma::Path &path, u32 open_mode, bool case_sensitive) { + /* Check that the path is valid. */ + R_UNLESS(IsValidPath(path), htcfs::ResultInvalidArgument()); + + /* Convert the open mode. */ + fs::OpenMode fs_open_mode; + R_TRY(ConvertOpenMode(std::addressof(fs_open_mode), open_mode)); + + /* Open the file. */ + s32 handle = -1; + R_TRY(htcfs::GetClient().OpenFile(std::addressof(handle), path.str, fs_open_mode, case_sensitive)); + + /* Set the output file. */ + *out = FileServiceObjectFactory::CreateSharedEmplaced<tma::IFileAccessor, FileServiceObject>(handle); + R_SUCCEED(); + } + + Result FileSystemServiceObject::FileExists(sf::Out<bool> out, const tma::Path &path, bool case_sensitive) { + /* Check that the path is valid. */ + R_UNLESS(IsValidPath(path), htcfs::ResultInvalidArgument()); + + /* Get whether the file exists. */ + R_RETURN(htcfs::GetClient().FileExists(out.GetPointer(), path.str, case_sensitive)); + } + + Result FileSystemServiceObject::DeleteFile(const tma::Path &path, bool case_sensitive) { + /* Check that the path is valid. */ + R_UNLESS(IsValidPath(path), htcfs::ResultInvalidArgument()); + + /* Delete the file. */ + R_RETURN(htcfs::GetClient().DeleteFile(path.str, case_sensitive)); + } + + Result FileSystemServiceObject::RenameFile(const tma::Path &old_path, const tma::Path &new_path, bool case_sensitive) { + /* Check that the paths are valid. */ + R_UNLESS(IsValidPath(old_path), htcfs::ResultInvalidArgument()); + R_UNLESS(IsValidPath(new_path), htcfs::ResultInvalidArgument()); + + /* Rename the file. */ + R_RETURN(htcfs::GetClient().RenameFile(old_path.str, new_path.str, case_sensitive)); + } + + Result FileSystemServiceObject::GetIOType(sf::Out<s32> out, const tma::Path &path, bool case_sensitive) { + /* Check that the path is valid. */ + R_UNLESS(IsValidPath(path), htcfs::ResultInvalidArgument()); + + /* Get the entry type. */ + static_assert(sizeof(s32) == sizeof(fs::DirectoryEntryType)); + R_RETURN(htcfs::GetClient().GetEntryType(reinterpret_cast<fs::DirectoryEntryType *>(out.GetPointer()), path.str, case_sensitive)); + } + + Result FileSystemServiceObject::OpenDirectory(sf::Out<sf::SharedPointer<tma::IDirectoryAccessor>> out, const tma::Path &path, s32 open_mode, bool case_sensitive) { + /* Check that the path is valid. */ + R_UNLESS(IsValidPath(path), htcfs::ResultInvalidArgument()); + + /* Open the directory. */ + s32 handle = -1; + R_TRY(htcfs::GetClient().OpenDirectory(std::addressof(handle), path.str, static_cast<fs::OpenDirectoryMode>(open_mode), case_sensitive)); + + /* Set the output directory. */ + *out = DirectoryServiceObjectFactory::CreateSharedEmplaced<tma::IDirectoryAccessor, DirectoryServiceObject>(handle); + R_SUCCEED(); + } + + Result FileSystemServiceObject::DirectoryExists(sf::Out<bool> out, const tma::Path &path, bool case_sensitive) { + /* Check that the path is valid. */ + R_UNLESS(IsValidPath(path), htcfs::ResultInvalidArgument()); + + /* Get whether the file exists. */ + R_RETURN(htcfs::GetClient().DirectoryExists(out.GetPointer(), path.str, case_sensitive)); + } + + Result FileSystemServiceObject::CreateDirectory(const tma::Path &path, bool case_sensitive) { + /* Check that the path is valid. */ + R_UNLESS(IsValidPath(path), htcfs::ResultInvalidArgument()); + + /* Create the directory. */ + R_RETURN(htcfs::GetClient().CreateDirectory(path.str, case_sensitive)); + } + + Result FileSystemServiceObject::DeleteDirectory(const tma::Path &path, bool recursively, bool case_sensitive) { + /* Check that the path is valid. */ + R_UNLESS(IsValidPath(path), htcfs::ResultInvalidArgument()); + + /* Delete the directory. */ + R_RETURN(htcfs::GetClient().DeleteDirectory(path.str, recursively, case_sensitive)); + } + + Result FileSystemServiceObject::RenameDirectory(const tma::Path &old_path, const tma::Path &new_path, bool case_sensitive) { + /* Check that the paths are valid. */ + R_UNLESS(IsValidPath(old_path), htcfs::ResultInvalidArgument()); + R_UNLESS(IsValidPath(new_path), htcfs::ResultInvalidArgument()); + + /* Rename the file. */ + R_RETURN(htcfs::GetClient().RenameDirectory(old_path.str, new_path.str, case_sensitive)); + } + + Result FileSystemServiceObject::CreateFile(const tma::Path &path, s64 size, bool case_sensitive) { + /* Check that the path is valid. */ + R_UNLESS(IsValidPath(path), htcfs::ResultInvalidArgument()); + + /* Create the file. */ + R_RETURN(htcfs::GetClient().CreateFile(path.str, size, case_sensitive)); + } + + Result FileSystemServiceObject::GetFileTimeStamp(sf::Out<u64> out_create, sf::Out<u64> out_access, sf::Out<u64> out_modify, const tma::Path &path, bool case_sensitive) { + /* Check that the path is valid. */ + R_UNLESS(IsValidPath(path), htcfs::ResultInvalidArgument()); + + /* Get the timestamp. */ + R_RETURN(htcfs::GetClient().GetFileTimeStamp(out_create.GetPointer(), out_access.GetPointer(), out_modify.GetPointer(), path.str, case_sensitive)); + } + + Result FileSystemServiceObject::GetCaseSensitivePath(const tma::Path &path, const sf::OutBuffer &out) { + /* Check that the path is valid. */ + R_UNLESS(IsValidPath(path), htcfs::ResultInvalidArgument()); + + /* Get the case sensitive path. */ + R_RETURN(htcfs::GetClient().GetCaseSensitivePath(reinterpret_cast<char *>(out.GetPointer()), out.GetSize(), path.str)); + } + + Result FileSystemServiceObject::GetDiskFreeSpaceExW(sf::Out<s64> out_free, sf::Out<s64> out_total, sf::Out<s64> out_total_free, const tma::Path &path) { + /* Check that the path is valid. */ + R_UNLESS(IsValidPath(path), htcfs::ResultInvalidArgument()); + + /* Get the timestamp. */ + R_RETURN(htcfs::GetClient().GetDiskFreeSpace(out_free.GetPointer(), out_total.GetPointer(), out_total_free.GetPointer(), path.str)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_file_system_service_object.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_file_system_service_object.hpp new file mode 100644 index 00000000..348d3889 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_file_system_service_object.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htcfs { + + constexpr inline bool DefaultCaseSensitivityForDeprecatedFileSystemServiceObject = false; + + class FileSystemServiceObject { + public: + Result OpenFile(sf::Out<sf::SharedPointer<tma::IFileAccessor>> out, const tma::Path &path, u32 open_mode, bool case_sensitive = DefaultCaseSensitivityForDeprecatedFileSystemServiceObject); + Result FileExists(sf::Out<bool> out, const tma::Path &path, bool case_sensitive = DefaultCaseSensitivityForDeprecatedFileSystemServiceObject); + Result DeleteFile(const tma::Path &path, bool case_sensitive = DefaultCaseSensitivityForDeprecatedFileSystemServiceObject); + Result RenameFile(const tma::Path &old_path, const tma::Path &new_path, bool case_sensitive = DefaultCaseSensitivityForDeprecatedFileSystemServiceObject); + Result GetIOType(sf::Out<s32> out, const tma::Path &path, bool case_sensitive = DefaultCaseSensitivityForDeprecatedFileSystemServiceObject); + Result OpenDirectory(sf::Out<sf::SharedPointer<tma::IDirectoryAccessor>> out, const tma::Path &path, s32 open_mode, bool case_sensitive = DefaultCaseSensitivityForDeprecatedFileSystemServiceObject); + Result DirectoryExists(sf::Out<bool> out, const tma::Path &path, bool case_sensitive = DefaultCaseSensitivityForDeprecatedFileSystemServiceObject); + Result CreateDirectory(const tma::Path &path, bool case_sensitive = DefaultCaseSensitivityForDeprecatedFileSystemServiceObject); + Result DeleteDirectory(const tma::Path &path, bool recursively, bool case_sensitive = DefaultCaseSensitivityForDeprecatedFileSystemServiceObject); + Result RenameDirectory(const tma::Path &old_path, const tma::Path &new_path, bool case_sensitive = DefaultCaseSensitivityForDeprecatedFileSystemServiceObject); + Result CreateFile(const tma::Path &path, s64 size, bool case_sensitive = DefaultCaseSensitivityForDeprecatedFileSystemServiceObject); + Result GetFileTimeStamp(sf::Out<u64> out_create, sf::Out<u64> out_access, sf::Out<u64> out_modify, const tma::Path &path, bool case_sensitive = DefaultCaseSensitivityForDeprecatedFileSystemServiceObject); + Result GetCaseSensitivePath(const tma::Path &path, const sf::OutBuffer &out); + Result GetDiskFreeSpaceExW(sf::Out<s64> out_free, sf::Out<s64> out_total, sf::Out<s64> out_total_free, const tma::Path &path); + }; + static_assert(tma::IsIFileManager<FileSystemServiceObject>); + static_assert(tma::IsIDeprecatedFileManager<FileSystemServiceObject>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_header_factory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_header_factory.hpp new file mode 100644 index 00000000..51841cd5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_header_factory.hpp @@ -0,0 +1,247 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htcfs { + + constexpr inline s16 HtcfsProtocol = 1; + constexpr inline s16 MaxProtocolVersion = 1; + + enum class PacketCategory : u16 { + Request = 0, + Response = 1, + }; + + enum class PacketType : u16 { + GetMaxProtocolVersion = 0, + SetProtocolVersion = 1, + GetEntryType = 16, + OpenFile = 32, + CloseFile = 33, + GetPriorityForFile = 34, + SetPriorityForFile = 35, + CreateFile = 36, + DeleteFile = 37, + RenameFile = 38, + FileExists = 39, + ReadFile = 40, + WriteFile = 41, + FlushFile = 42, + GetFileTimeStamp = 43, + GetFileSize = 44, + SetFileSize = 45, + ReadFileLarge = 46, + WriteFileLarge = 47, + OpenDirectory = 48, + CloseDirectory = 49, + GetPriorityForDirectory = 50, + SetPriorityForDirectory = 51, + CreateDirectory = 52, + DeleteDirectory = 53, + RenameDirectory = 54, + DirectoryExists = 55, + ReadDirectory = 56, + GetEntryCount = 57, + GetWorkingDirectory = 58, + GetWorkingDirectorySize = 59, + GetCaseSensitivePath = 60, + GetDiskFreeSpace = 61, + ReadDirectoryLarge = 62, + }; + + struct Header { + s16 protocol; + s16 version; + PacketCategory packet_category; + PacketType packet_type; + s64 body_size; + s64 params[5]; + s64 reserved; + }; + static_assert(util::is_pod<Header>::value); + static_assert(sizeof(Header) == 0x40); + + class HeaderFactory { + private: + s16 m_version; + public: + HeaderFactory() : m_version() { /* ... */ } + public: + s16 GetVersion() const { return m_version; } + void SetVersion(s16 version) { m_version = version; } + public: + ALWAYS_INLINE void MakeRequestHeader(Header *out, PacketType packet_type, s64 body_size = 0, s64 param0 = 0, s64 param1 = 0, s64 param2 = 0, s64 param3 = 0, s64 param4 = 0) { + /* Set protocol and version. */ + out->protocol = HtcfsProtocol; + out->version = m_version; + + /* Set type and category. */ + out->packet_category = PacketCategory::Request; + out->packet_type = packet_type; + + /* Set body size. */ + out->body_size = body_size; + + /* Set params. */ + out->params[0] = param0; + out->params[1] = param1; + out->params[2] = param2; + out->params[3] = param3; + out->params[4] = param4; + + /* Clear reserved. */ + out->reserved = 0; + } + + void MakeGetMaxProtocolVersionHeader(Header *out) { + return this->MakeRequestHeader(out, PacketType::GetMaxProtocolVersion, 0); + } + + void MakeSetProtocolVersionHeader(Header *out, s16 version) { + return this->MakeRequestHeader(out, PacketType::SetProtocolVersion, 0, version); + } + + void MakeOpenFileHeader(Header *out, int path_len, fs::OpenMode mode, bool case_sensitive, s64 cache_size) { + return this->MakeRequestHeader(out, PacketType::OpenFile, path_len, static_cast<s64>(mode), case_sensitive ? 1 : 0, cache_size); + } + + void MakeFileExistsHeader(Header *out, int path_len, bool case_sensitive) { + return this->MakeRequestHeader(out, PacketType::FileExists, path_len, case_sensitive ? 1 : 0); + } + + void MakeDeleteFileHeader(Header *out, int path_len, bool case_sensitive) { + return this->MakeRequestHeader(out, PacketType::DeleteFile, path_len, case_sensitive ? 1 : 0); + } + + void MakeRenameFileHeader(Header *out, int old_path_len, int new_path_len, bool case_sensitive) { + return this->MakeRequestHeader(out, PacketType::RenameFile, old_path_len + new_path_len, old_path_len, new_path_len, case_sensitive ? 1 : 0); + } + + void MakeGetEntryTypeHeader(Header *out, int path_len, bool case_sensitive) { + return this->MakeRequestHeader(out, PacketType::GetEntryType, path_len, case_sensitive ? 1 : 0); + } + + void MakeOpenDirectoryHeader(Header *out, int path_len, fs::OpenDirectoryMode mode, bool case_sensitive) { + return this->MakeRequestHeader(out, PacketType::OpenDirectory, path_len, static_cast<s64>(mode), case_sensitive ? 1 : 0); + } + + void MakeDirectoryExistsHeader(Header *out, int path_len, bool case_sensitive) { + return this->MakeRequestHeader(out, PacketType::DirectoryExists, path_len, case_sensitive ? 1 : 0); + } + + void MakeCreateDirectoryHeader(Header *out, int path_len, bool case_sensitive) { + return this->MakeRequestHeader(out, PacketType::CreateDirectory, path_len, case_sensitive ? 1 : 0); + } + + void MakeDeleteDirectoryHeader(Header *out, int path_len, bool recursively, bool case_sensitive) { + return this->MakeRequestHeader(out, PacketType::DeleteDirectory, path_len, recursively ? 1 : 0, case_sensitive ? 1 : 0); + } + + void MakeRenameDirectoryHeader(Header *out, int old_path_len, int new_path_len, bool case_sensitive) { + return this->MakeRequestHeader(out, PacketType::RenameDirectory, old_path_len + new_path_len, old_path_len, new_path_len, case_sensitive ? 1 : 0); + } + + void MakeCreateFileHeader(Header *out, int path_len, s64 size, bool case_sensitive) { + return this->MakeRequestHeader(out, PacketType::CreateFile, path_len, size, case_sensitive ? 1 : 0); + } + + void MakeGetFileTimeStampHeader(Header *out, int path_len, bool case_sensitive) { + return this->MakeRequestHeader(out, PacketType::GetFileTimeStamp, path_len, case_sensitive ? 1 : 0); + } + + void MakeGetCaseSensitivePathHeader(Header *out, int path_len) { + return this->MakeRequestHeader(out, PacketType::GetCaseSensitivePath, path_len); + } + + void MakeGetDiskFreeSpaceHeader(Header *out, int path_len) { + return this->MakeRequestHeader(out, PacketType::GetDiskFreeSpace, path_len); + } + + void MakeCloseDirectoryHeader(Header *out, s32 handle) { + return this->MakeRequestHeader(out, PacketType::CloseDirectory, 0, handle); + } + + void MakeGetEntryCountHeader(Header *out, s32 handle) { + return this->MakeRequestHeader(out, PacketType::GetEntryCount, 0, handle); + } + + void MakeGetWorkingDirectoryHeader(Header *out) { + return this->MakeRequestHeader(out, PacketType::GetWorkingDirectory, 0); + } + + void MakeGetWorkingDirectorySizeHeader(Header *out) { + return this->MakeRequestHeader(out, PacketType::GetWorkingDirectorySize, 0); + } + + void MakeReadDirectoryHeader(Header *out, s32 handle, size_t max_out_entries) { + return this->MakeRequestHeader(out, PacketType::ReadDirectory, 0, handle, max_out_entries); + } + + void MakeReadDirectoryLargeHeader(Header *out, s32 handle, size_t max_out_entries, u16 data_channel_id) { + return this->MakeRequestHeader(out, PacketType::ReadDirectoryLarge, 0, handle, max_out_entries, data_channel_id); + } + + void MakeGetPriorityForDirectoryHeader(Header *out, s32 handle) { + return this->MakeRequestHeader(out, PacketType::GetPriorityForDirectory, 0, handle); + } + + void MakeSetPriorityForDirectoryHeader(Header *out, s32 handle, s32 priority) { + return this->MakeRequestHeader(out, PacketType::SetPriorityForDirectory, 0, handle, priority); + } + + void MakeCloseFileHeader(Header *out, s32 handle) { + return this->MakeRequestHeader(out, PacketType::CloseFile, 0, handle); + } + + void MakeReadFileHeader(Header *out, s32 handle, s64 offset, s64 buffer_size) { + return this->MakeRequestHeader(out, PacketType::ReadFile, 0, handle, offset, buffer_size); + } + + void MakeReadFileLargeHeader(Header *out, s32 handle, s64 offset, s64 buffer_size, u16 data_channel_id) { + return this->MakeRequestHeader(out, PacketType::ReadFileLarge, 0, handle, offset, buffer_size, data_channel_id); + } + + void MakeWriteFileHeader(Header *out, s64 buffer_size, s32 handle, u32 option, s64 offset) { + return this->MakeRequestHeader(out, PacketType::WriteFile, buffer_size, handle, option, offset); + } + + void MakeWriteFileLargeHeader(Header *out, s32 handle, u32 option, s64 offset, s64 buffer_size, u16 data_channel_id) { + return this->MakeRequestHeader(out, PacketType::WriteFileLarge, 0, handle, option, offset, buffer_size, data_channel_id); + } + + void MakeGetFileSizeHeader(Header *out, s32 handle) { + return this->MakeRequestHeader(out, PacketType::GetFileSize, 0, handle); + } + + void MakeSetFileSizeHeader(Header *out, s32 handle, s64 size) { + return this->MakeRequestHeader(out, PacketType::SetFileSize, 0, handle, size); + } + + void MakeFlushFileHeader(Header *out, s32 handle) { + return this->MakeRequestHeader(out, PacketType::FlushFile, 0, handle); + } + + void MakeGetPriorityForFileHeader(Header *out, s32 handle) { + return this->MakeRequestHeader(out, PacketType::GetPriorityForFile, 0, handle); + } + + void MakeSetPriorityForFileHeader(Header *out, s32 handle, s32 priority) { + return this->MakeRequestHeader(out, PacketType::SetPriorityForFile, 0, handle, priority); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_hipc_server.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_hipc_server.cpp new file mode 100644 index 00000000..a5a4c17e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_hipc_server.cpp @@ -0,0 +1,66 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcfs_client.hpp" +#include "htcfs_file_system_service_object.hpp" + +namespace ams::htcfs { + + namespace { + + static constexpr inline size_t NumServers = 1; + static constexpr inline size_t MaxSessions = 30; + static constexpr inline sm::ServiceName ServiceName = sm::ServiceName::Encode("file_io"); + + struct ServerOptions { + static constexpr size_t PointerBufferSize = 0x1000; + static constexpr size_t MaxDomains = 0x10; + static constexpr size_t MaxDomainObjects = 0x100; + static constexpr bool CanDeferInvokeRequest = false; + static constexpr bool CanManageMitmServers = false; + }; + + using ServerManager = sf::hipc::ServerManager<NumServers, ServerOptions, MaxSessions>; + + /* Service object. */ + ServerManager g_server_manager; + + /* Service object. */ + constinit sf::UnmanagedServiceObject<tma::IFileManager, FileSystemServiceObject> g_htcfs_service_object; + constinit sf::UnmanagedServiceObject<tma::IDeprecatedFileManager, FileSystemServiceObject> g_htcfs_deprecated_service_object; + + } + + void Initialize(htclow::HtclowManager *htclow_manager) { + /* Initialize the htcfs client library. */ + htcfs::InitializeClient(htclow_manager); + } + + void RegisterHipcServer() { + /* Register the service. */ + if (hos::GetVersion() >= hos::Version_6_0_0) { + R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_htcfs_service_object.GetShared(), ServiceName, MaxSessions)); + } else { + R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_htcfs_deprecated_service_object.GetShared(), ServiceName, MaxSessions)); + } + } + + void LoopHipcServer() { + /* Loop, servicing services. */ + g_server_manager.LoopProcess(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_result.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_result.hpp new file mode 100644 index 00000000..ebe23300 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_result.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htcfs_client_impl.hpp" + +namespace ams::htcfs { + + enum class HtcfsResult { + Success = 0, + UnknownError = 1, + UnsupportedProtocolVersion = 2, + InvalidRequest = 3, + InvalidHandle = 4, + OutOfHandle = 5, + Ready = 6, + }; + + inline Result ConvertHtcfsResult(HtcfsResult result) { + switch (result) { + case HtcfsResult::Success: + R_SUCCEED(); + case HtcfsResult::UnknownError: + R_THROW(htcfs::ResultUnknownError()); + case HtcfsResult::UnsupportedProtocolVersion: + R_THROW(htcfs::ResultUnsupportedProtocolVersion()); + case HtcfsResult::InvalidRequest: + R_THROW(htcfs::ResultInvalidRequest()); + case HtcfsResult::InvalidHandle: + R_THROW(htcfs::ResultInvalidHandle()); + case HtcfsResult::OutOfHandle: + R_THROW(htcfs::ResultOutOfHandle()); + default: + R_THROW(htcfs::ResultUnknownError()); + } + } + + inline Result ConvertHtcfsResult(s64 param) { + R_RETURN(ConvertHtcfsResult(static_cast<HtcfsResult>(param))); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_result_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_result_utils.hpp new file mode 100644 index 00000000..5d6c78fc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_result_utils.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htcfs { + + inline Result ConvertToFsResult(Result result) { + R_TRY_CATCH(result) { + R_CONVERT(htcfs::ResultInvalidArgument, fs::ResultInvalidArgument()) + R_CONVERT(htcfs::ResultConnectionFailure, fs::ResultTargetNotFound()) + R_CONVERT(htcfs::ResultOutOfHandle, fs::ResultOpenCountLimit()) + R_CONVERT(htcfs::ResultInternalError, fs::ResultInternal()) + } R_END_TRY_CATCH; + + R_RETURN(result); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_working_directory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_working_directory.cpp new file mode 100644 index 00000000..88475d0f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_working_directory.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcfs_client.hpp" + +namespace ams::htcfs { + + Result GetWorkingDirectory(char *dst, size_t dst_size) { + R_RETURN(htcfs::GetClient().GetWorkingDirectory(dst, dst_size)); + } + + Result GetWorkingDirectorySize(s32 *out) { + R_RETURN(htcfs::GetClient().GetWorkingDirectorySize(out)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_working_directory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_working_directory.hpp new file mode 100644 index 00000000..c478253f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcfs/htcfs_working_directory.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htcfs { + + Result GetWorkingDirectory(char *dst, size_t dst_size); + Result GetWorkingDirectorySize(s32 *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_packet.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_packet.hpp new file mode 100644 index 00000000..6a2eb8cd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_packet.hpp @@ -0,0 +1,73 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../htclow_packet.hpp" + +namespace ams::htclow::ctrl { + + enum HtcctrlPacketType : u16 { + HtcctrlPacketType_ConnectFromHost = 16, + HtcctrlPacketType_ConnectFromTarget = 17, + HtcctrlPacketType_ReadyFromHost = 18, + HtcctrlPacketType_ReadyFromTarget = 19, + HtcctrlPacketType_SuspendFromHost = 20, + HtcctrlPacketType_SuspendFromTarget = 21, + HtcctrlPacketType_ResumeFromHost = 22, + HtcctrlPacketType_ResumeFromTarget = 23, + HtcctrlPacketType_DisconnectFromHost = 24, + HtcctrlPacketType_DisconnectFromTarget = 25, + HtcctrlPacketType_BeaconQuery = 28, + HtcctrlPacketType_BeaconResponse = 29, + HtcctrlPacketType_InformationFromTarget = 33, + }; + + static constexpr inline u32 HtcctrlSignature = 0x78825637; + + struct HtcctrlPacketHeader { + u32 signature; + u32 sequence_id; + u32 reserved; + u32 body_size; + s16 version; + HtcctrlPacketType packet_type; + impl::ChannelInternalType channel; + u64 share; + }; + static_assert(util::is_pod<HtcctrlPacketHeader>::value); + static_assert(sizeof(HtcctrlPacketHeader) == 0x20); + + static constexpr inline size_t HtcctrlPacketBodySizeMax = 0x1000; + + struct HtcctrlPacketBody { + u8 data[HtcctrlPacketBodySizeMax]; + }; + + class HtcctrlPacket : public BasePacket<HtcctrlPacketHeader>, public util::IntrusiveListBaseNode<HtcctrlPacket> { + public: + using BasePacket<HtcctrlPacketHeader>::BasePacket; + }; + + struct HtcctrlPacketDeleter { + mem::StandardAllocator *m_allocator; + + void operator()(HtcctrlPacket *packet) { + std::destroy_at(packet); + m_allocator->Free(packet); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_packet_factory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_packet_factory.cpp new file mode 100644 index 00000000..22dd1061 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_packet_factory.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_ctrl_packet_factory.hpp" + +namespace ams::htclow::ctrl { + + std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> HtcctrlPacketFactory::MakeSendPacketCommon(int body_size) { + /* Allocate memory for the packet. */ + if (void *buffer = m_allocator->Allocate(sizeof(HtcctrlPacket), alignof(HtcctrlPacket)); buffer != nullptr) { + /* Convert the buffer to a packet. */ + HtcctrlPacket *packet = static_cast<HtcctrlPacket *>(buffer); + + /* Construct the packet. */ + std::construct_at(packet, m_allocator, body_size + sizeof(HtcctrlPacketHeader)); + + /* Create the unique pointer. */ + std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> ptr(packet, HtcctrlPacketDeleter{m_allocator}); + + /* Set packet header fields. */ + if (ptr && ptr->IsAllocationSucceeded()) { + HtcctrlPacketHeader *header = ptr->GetHeader(); + + header->signature = HtcctrlSignature; + header->sequence_id = m_sequence_id++; + header->reserved = 0; + header->body_size = body_size; + header->version = 1; + header->channel = {}; + header->share = 0; + } + + return ptr; + } else { + return std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter>(nullptr, HtcctrlPacketDeleter{m_allocator}); + } + } + + std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> HtcctrlPacketFactory::MakeSuspendPacket() { + auto packet = this->MakeSendPacketCommon(0); + if (packet && packet->IsAllocationSucceeded()) { + packet->GetHeader()->packet_type = HtcctrlPacketType_SuspendFromTarget; + } + + return packet; + } + + std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> HtcctrlPacketFactory::MakeResumePacket() { + auto packet = this->MakeSendPacketCommon(0); + if (packet && packet->IsAllocationSucceeded()) { + packet->GetHeader()->packet_type = HtcctrlPacketType_ResumeFromTarget; + } + + return packet; + } + + std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> HtcctrlPacketFactory::MakeReadyPacket(const void *body, int body_size) { + auto packet = this->MakeSendPacketCommon(body_size); + if (packet && packet->IsAllocationSucceeded()) { + packet->GetHeader()->packet_type = HtcctrlPacketType_ReadyFromTarget; + + std::memcpy(packet->GetBody(), body, packet->GetBodySize()); + } + + return packet; + } + + std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> HtcctrlPacketFactory::MakeInformationPacket(const void *body, int body_size) { + auto packet = this->MakeSendPacketCommon(body_size); + if (packet && packet->IsAllocationSucceeded()) { + packet->GetHeader()->packet_type = HtcctrlPacketType_InformationFromTarget; + + std::memcpy(packet->GetBody(), body, packet->GetBodySize()); + } + + return packet; + } + + std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> HtcctrlPacketFactory::MakeDisconnectPacket() { + auto packet = this->MakeSendPacketCommon(0); + if (packet) { + packet->GetHeader()->packet_type = HtcctrlPacketType_DisconnectFromTarget; + } + + return packet; + } + + std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> HtcctrlPacketFactory::MakeConnectPacket(const void *body, int body_size) { + auto packet = this->MakeSendPacketCommon(body_size); + if (packet && packet->IsAllocationSucceeded()) { + packet->GetHeader()->packet_type = HtcctrlPacketType_ConnectFromTarget; + + std::memcpy(packet->GetBody(), body, packet->GetBodySize()); + } + + return packet; + } + + std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> HtcctrlPacketFactory::MakeBeaconResponsePacket(const void *body, int body_size) { + auto packet = this->MakeSendPacketCommon(body_size); + if (packet && packet->IsAllocationSucceeded()) { + packet->GetHeader()->packet_type = HtcctrlPacketType_BeaconResponse; + + std::memcpy(packet->GetBody(), body, packet->GetBodySize()); + } + + return packet; + } + + void HtcctrlPacketFactory::Delete(HtcctrlPacket *packet) { + HtcctrlPacketDeleter{m_allocator}(packet); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_packet_factory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_packet_factory.hpp new file mode 100644 index 00000000..a33a14b0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_packet_factory.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htclow_ctrl_packet.hpp" + +namespace ams::htclow::ctrl { + + class HtcctrlPacketFactory { + private: + mem::StandardAllocator *m_allocator; + u32 m_sequence_id; + public: + HtcctrlPacketFactory(mem::StandardAllocator *allocator) : m_allocator(allocator) { + /* Get the current time. */ + const u64 time = os::GetSystemTick().GetInt64Value(); + + /* Set a random sequence id. */ + { + util::TinyMT rng; + rng.Initialize(reinterpret_cast<const u32 *>(std::addressof(time)), sizeof(time) / sizeof(u32)); + + m_sequence_id = rng.GenerateRandomU32(); + } + } + public: + std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> MakeSuspendPacket(); + std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> MakeResumePacket(); + std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> MakeReadyPacket(const void *body, int body_size); + std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> MakeInformationPacket(const void *body, int body_size); + std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> MakeDisconnectPacket(); + std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> MakeConnectPacket(const void *body, int body_size); + std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> MakeBeaconResponsePacket(const void *body, int body_size); + + void Delete(HtcctrlPacket *packet); + private: + std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> MakeSendPacketCommon(int body_size); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_send_buffer.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_send_buffer.cpp new file mode 100644 index 00000000..aaed17d6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_send_buffer.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_ctrl_send_buffer.hpp" +#include "htclow_ctrl_packet_factory.hpp" + +namespace ams::htclow::ctrl { + + bool HtcctrlSendBuffer::IsPriorPacket(HtcctrlPacketType packet_type) const { + return packet_type == HtcctrlPacketType_DisconnectFromTarget; + } + + bool HtcctrlSendBuffer::IsPosteriorPacket(HtcctrlPacketType packet_type) const { + switch (packet_type) { + case HtcctrlPacketType_ConnectFromTarget: + case HtcctrlPacketType_ReadyFromTarget: + case HtcctrlPacketType_SuspendFromTarget: + case HtcctrlPacketType_ResumeFromTarget: + case HtcctrlPacketType_BeaconResponse: + case HtcctrlPacketType_InformationFromTarget: + return true; + default: + return false; + } + } + + void HtcctrlSendBuffer::AddPacket(std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> ptr) { + /* Get the packet. */ + HtcctrlPacket *packet = ptr.release(); + + /* Get the packet type. */ + const auto packet_type = packet->GetHeader()->packet_type; + + /* Add the packet to the appropriate list. */ + if (this->IsPriorPacket(packet_type)) { + m_prior_packet_list.push_back(*packet); + } else { + AMS_ABORT_UNLESS(this->IsPosteriorPacket(packet_type)); + m_posterior_packet_list.push_back(*packet); + } + } + + void HtcctrlSendBuffer::RemovePacket(const HtcctrlPacketHeader &header) { + /* Get the packet type. */ + const auto packet_type = header.packet_type; + + /* Remove the front from the appropriate list. */ + HtcctrlPacket *packet; + if (this->IsPriorPacket(packet_type)) { + packet = std::addressof(m_prior_packet_list.front()); + m_prior_packet_list.pop_front(); + } else { + AMS_ABORT_UNLESS(this->IsPosteriorPacket(packet_type)); + packet = std::addressof(m_posterior_packet_list.front()); + m_posterior_packet_list.pop_front(); + } + + /* Delete the packet. */ + m_packet_factory->Delete(packet); + } + + bool HtcctrlSendBuffer::QueryNextPacket(HtcctrlPacketHeader *header, HtcctrlPacketBody *body, int *out_body_size) { + if (!m_prior_packet_list.empty()) { + this->CopyPacket(header, body, out_body_size, m_prior_packet_list.front()); + return true; + } else if (!m_posterior_packet_list.empty()) { + this->CopyPacket(header, body, out_body_size, m_posterior_packet_list.front()); + return true; + } else { + return false; + } + } + + void HtcctrlSendBuffer::CopyPacket(HtcctrlPacketHeader *header, HtcctrlPacketBody *body, int *out_body_size, const HtcctrlPacket &packet) { + /* Get the body size. */ + const int body_size = packet.GetBodySize(); + AMS_ASSERT(0 <= body_size && body_size <= static_cast<int>(sizeof(*body))); + + /* Copy the header. */ + std::memcpy(header, packet.GetHeader(), sizeof(*header)); + + /* Copy the body. */ + std::memcpy(body, packet.GetBody(), body_size); + + /* Set the output body size. */ + *out_body_size = body_size; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_send_buffer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_send_buffer.hpp new file mode 100644 index 00000000..f9c2fd51 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_send_buffer.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htclow_ctrl_packet.hpp" + +namespace ams::htclow::ctrl { + + class HtcctrlPacketFactory; + + class HtcctrlSendBuffer { + private: + using PacketList = util::IntrusiveListBaseTraits<HtcctrlPacket>::ListType; + private: + HtcctrlPacketFactory *m_packet_factory; + PacketList m_prior_packet_list; + PacketList m_posterior_packet_list; + private: + bool IsPriorPacket(HtcctrlPacketType packet_type) const; + bool IsPosteriorPacket(HtcctrlPacketType packet_type) const; + + void CopyPacket(HtcctrlPacketHeader *header, HtcctrlPacketBody *body, int *out_body_size, const HtcctrlPacket &packet); + public: + HtcctrlSendBuffer(HtcctrlPacketFactory *pf) : m_packet_factory(pf), m_prior_packet_list(), m_posterior_packet_list() { /* ... */ } + + void AddPacket(std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> ptr); + void RemovePacket(const HtcctrlPacketHeader &header); + + bool QueryNextPacket(HtcctrlPacketHeader *header, HtcctrlPacketBody *body, int *out_body_size); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_service.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_service.cpp new file mode 100644 index 00000000..6f39cab5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_service.cpp @@ -0,0 +1,502 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_ctrl_service.hpp" +#include "htclow_ctrl_state.hpp" +#include "htclow_ctrl_state_machine.hpp" +#include "htclow_ctrl_packet_factory.hpp" +#include "htclow_service_channel_parser.hpp" +#include "htclow_ctrl_service_channels.hpp" +#include "../mux/htclow_mux.hpp" + +namespace ams::htclow::ctrl { + + namespace { + + constexpr const char BeaconPacketResponseTemplate[] = + "{\r\n" + " \"Spec\" : \"%s\",\r\n" + " \"Conn\" : \"%s\",\r\n" + " \"HW\" : \"%s\",\r\n" + " \"Name\" : \"%s\",\r\n" + " \"SN\" : \"%s\",\r\n" + " \"FW\" : \"%s\",\r\n" + " \"Prot\" : \"%d\"\r\n" + "}\r\n"; + + } + + HtcctrlService::HtcctrlService(HtcctrlPacketFactory *pf, HtcctrlStateMachine *sm, mux::Mux *mux) + : m_settings_holder(), m_beacon_response(), m_information_body(), m_packet_factory(pf), m_state_machine(sm), m_mux(mux), m_event(os::EventClearMode_ManualClear), + m_send_buffer(pf), m_mutex(), m_condvar(), m_service_channels_packet(), m_version(ProtocolVersion) + { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Set the mux version. */ + m_mux->SetVersion(m_version); + + /* Update our beacon response. */ + this->UpdateBeaconResponse(this->GetConnectionType(impl::DriverType::Unknown)); + } + + const char *HtcctrlService::GetConnectionType(impl::DriverType driver_type) const { + switch (driver_type) { + case impl::DriverType::Socket: return "TCP"; + case impl::DriverType::Usb: return "USB-gen2"; + case impl::DriverType::PlainChannel: return "HBPC-gen2"; + default: return "Unknown"; + } + } + + void HtcctrlService::UpdateBeaconResponse(const char *connection) { + /* Load settings into the holder. */ + m_settings_holder.LoadSettings(); + + /* Print our beacon response. */ + util::SNPrintf(m_beacon_response, sizeof(m_beacon_response), BeaconPacketResponseTemplate, + m_settings_holder.GetSpec(), + connection, + m_settings_holder.GetHardwareType(), + m_settings_holder.GetTargetName(), + m_settings_holder.GetSerialNumber(), + m_settings_holder.GetFirmwareVersion(), + ProtocolVersion + ); + } + + void HtcctrlService::UpdateInformationBody(const char *status) { + util::SNPrintf(m_information_body, sizeof(m_information_body), "{\r\n \"Status\" : \"%s\"\r\n}\r\n", status); + } + + void HtcctrlService::SetDriverType(impl::DriverType driver_type) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Update our beacon response. */ + this->UpdateBeaconResponse(this->GetConnectionType(driver_type)); + } + + Result HtcctrlService::CheckReceivedHeader(const HtcctrlPacketHeader &header) const { + /* Check the packet signature. */ + AMS_ASSERT(header.signature == HtcctrlSignature); + + /* Validate version. */ + R_UNLESS(header.version == 1, htclow::ResultProtocolError()); + + /* Switch on the packet type. */ + switch (header.packet_type) { + case HtcctrlPacketType_ConnectFromHost: + case HtcctrlPacketType_SuspendFromHost: + case HtcctrlPacketType_ResumeFromHost: + case HtcctrlPacketType_DisconnectFromHost: + case HtcctrlPacketType_BeaconQuery: + R_UNLESS(header.body_size == 0, htclow::ResultProtocolError()); + break; + case HtcctrlPacketType_ReadyFromHost: + R_UNLESS(header.body_size <= sizeof(HtcctrlPacketBody), htclow::ResultProtocolError()); + break; + default: + R_THROW(htclow::ResultProtocolError()); + } + + R_SUCCEED(); + } + + Result HtcctrlService::ProcessReceivePacket(const HtcctrlPacketHeader &header, const void *body, size_t body_size) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + switch (header.packet_type) { + case HtcctrlPacketType_ConnectFromHost: + R_RETURN(this->ProcessReceiveConnectPacket()); + case HtcctrlPacketType_ReadyFromHost: + R_RETURN(this->ProcessReceiveReadyPacket(body, body_size)); + case HtcctrlPacketType_SuspendFromHost: + R_RETURN(this->ProcessReceiveSuspendPacket()); + case HtcctrlPacketType_ResumeFromHost: + R_RETURN(this->ProcessReceiveResumePacket()); + case HtcctrlPacketType_DisconnectFromHost: + R_RETURN(this->ProcessReceiveDisconnectPacket()); + case HtcctrlPacketType_BeaconQuery: + R_RETURN(this->ProcessReceiveBeaconQueryPacket()); + default: + R_RETURN(this->ProcessReceiveUnexpectedPacket()); + } + } + + Result HtcctrlService::ProcessReceiveConnectPacket() { + /* Try to transition to sent connect state. */ + if (R_FAILED(this->SetState(HtcctrlState_SentConnectFromHost))) { + /* We couldn't transition to sent connect. */ + R_RETURN(this->ProcessReceiveUnexpectedPacket()); + } + + /* Send a connect packet. */ + m_send_buffer.AddPacket(m_packet_factory->MakeConnectPacket(m_beacon_response, util::Strnlen(m_beacon_response, sizeof(m_beacon_response)) + 1)); + + /* Signal our event. */ + m_event.Signal(); + + R_SUCCEED(); + } + + Result HtcctrlService::ProcessReceiveReadyPacket(const void *body, size_t body_size) { + /* Update our service channels. */ + this->UpdateServiceChannels(body, body_size); + + /* Check that our version is correct. */ + if (m_version < ProtocolVersion) { + R_RETURN(this->ProcessReceiveUnexpectedPacket()); + } + + /* Set our version. */ + m_version = ProtocolVersion; + m_mux->SetVersion(m_version); + + /* Set our state. */ + if (R_FAILED(this->SetState(HtcctrlState_SentReadyFromHost))) { + R_RETURN(this->ProcessReceiveUnexpectedPacket()); + } + + /* Ready ourselves. */ + this->TryReadyInternal(); + R_SUCCEED(); + } + + Result HtcctrlService::ProcessReceiveSuspendPacket() { + /* Try to set our state to enter sleep. */ + if (R_FAILED(this->SetState(HtcctrlState_EnterSleep))) { + /* We couldn't transition to sleep. */ + R_RETURN(this->ProcessReceiveUnexpectedPacket()); + } + + R_SUCCEED(); + } + + Result HtcctrlService::ProcessReceiveResumePacket() { + /* If our state is sent-resume, change to readied. */ + if (m_state_machine->GetHtcctrlState() != HtcctrlState_SentResumeFromTarget || R_FAILED(this->SetState(HtcctrlState_Ready))) { + /* We couldn't perform a valid resume transition. */ + R_RETURN(this->ProcessReceiveUnexpectedPacket()); + } + + R_SUCCEED(); + } + + Result HtcctrlService::ProcessReceiveDisconnectPacket() { + /* Set our state. */ + R_TRY(this->SetState(HtcctrlState_Disconnected)); + + R_SUCCEED(); + } + + Result HtcctrlService::ProcessReceiveBeaconQueryPacket() { + /* Send a beacon response packet. */ + m_send_buffer.AddPacket(m_packet_factory->MakeBeaconResponsePacket(m_beacon_response, util::Strnlen(m_beacon_response, sizeof(m_beacon_response)) + 1)); + + /* Signal our event. */ + m_event.Signal(); + + R_SUCCEED(); + } + + Result HtcctrlService::ProcessReceiveUnexpectedPacket() { + /* Set our state. */ + R_TRY(this->SetState(HtcctrlState_Error)); + + /* Send a disconnection packet. */ + m_send_buffer.AddPacket(m_packet_factory->MakeDisconnectPacket()); + + /* Signal our event. */ + m_event.Signal(); + + /* Return unexpected packet error. */ + R_THROW(htclow::ResultHtcctrlReceiveUnexpectedPacket()); + } + + void HtcctrlService::ProcessSendConnectPacket() { + /* Set our state. */ + const Result result = this->SetState(HtcctrlState_Connected); + R_ASSERT(result); + } + + void HtcctrlService::ProcessSendReadyPacket() { + /* Set our state. */ + if (m_state_machine->GetHtcctrlState() == HtcctrlState_SentReadyFromHost) { + const Result result = this->SetState(HtcctrlState_Ready); + R_ASSERT(result); + } + + /* Update channel states. */ + m_mux->UpdateChannelState(); + } + + void HtcctrlService::ProcessSendSuspendPacket() { + /* Set our state. */ + const Result result = this->SetState(HtcctrlState_SentSuspendFromTarget); + R_ASSERT(result); + } + + void HtcctrlService::ProcessSendResumePacket() { + /* Set our state. */ + const Result result = this->SetState(HtcctrlState_SentResumeFromTarget); + R_ASSERT(result); + } + + void HtcctrlService::ProcessSendDisconnectPacket() { + /* Set our state. */ + const Result result = this->SetState(HtcctrlState_Disconnected); + R_ASSERT(result); + } + + void HtcctrlService::UpdateServiceChannels(const void *body, size_t body_size) { + /* Copy the packet body to our member. */ + std::memcpy(m_service_channels_packet, body, body_size); + + /* Parse service channels. */ + impl::ChannelInternalType channels[10]; + int num_channels; + s16 version = m_version; + ctrl::ParseServiceChannel(std::addressof(version), channels, std::addressof(num_channels), util::size(channels), m_service_channels_packet, body_size); + + /* Update version. */ + m_version = version; + + /* Notify state machine of supported channels. */ + m_state_machine->NotifySupportedServiceChannels(channels, num_channels); + } + + void HtcctrlService::TryReadyInternal() { + /* If we can send ready, do so. */ + if (m_state_machine->IsPossibleToSendReady()) { + /* Print the channels. */ + char channel_str[0x100]; + this->PrintServiceChannels(channel_str, sizeof(channel_str)); + + /* Send a ready packet. */ + m_send_buffer.AddPacket(m_packet_factory->MakeReadyPacket(channel_str, util::Strnlen(channel_str, sizeof(channel_str)) + 1)); + + /* Signal our event. */ + m_event.Signal(); + + /* Set connecting checked in state machine. */ + m_state_machine->SetConnectingChecked(); + } + } + + bool HtcctrlService::QuerySendPacket(HtcctrlPacketHeader *header, HtcctrlPacketBody *body, int *out_body_size) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + return m_send_buffer.QueryNextPacket(header, body, out_body_size); + } + + void HtcctrlService::RemovePacket(const HtcctrlPacketHeader &header) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Remove the packet from our buffer. */ + m_send_buffer.RemovePacket(header); + + /* Switch on the packet type. */ + switch (header.packet_type) { + case HtcctrlPacketType_ConnectFromTarget: + this->ProcessSendConnectPacket(); + break; + case HtcctrlPacketType_ReadyFromTarget: + this->ProcessSendReadyPacket(); + break; + case HtcctrlPacketType_SuspendFromTarget: + this->ProcessSendSuspendPacket(); + break; + case HtcctrlPacketType_ResumeFromTarget: + this->ProcessSendResumePacket(); + break; + case HtcctrlPacketType_DisconnectFromTarget: + this->ProcessSendDisconnectPacket(); + break; + case HtcctrlPacketType_BeaconResponse: + case HtcctrlPacketType_InformationFromTarget: + break; + default: + AMS_ABORT("Send unsupported packet 0x%04x\n", static_cast<u32>(header.packet_type)); + } + } + + void HtcctrlService::TryReady() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + this->TryReadyInternal(); + } + + void HtcctrlService::Disconnect() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + this->DisconnectInternal(); + } + + void HtcctrlService::DisconnectInternal() { + /* Disconnect, if we need to. */ + if (m_state_machine->IsDisconnectionNeeded()) { + /* Send a disconnect packet. */ + m_send_buffer.AddPacket(m_packet_factory->MakeDisconnectPacket()); + + /* Signal our event. */ + m_event.Signal(); + + /* Wait for us to be disconnected. */ + while (!m_state_machine->IsDisconnected()) { + m_condvar.Wait(m_mutex); + } + } + } + + void HtcctrlService::Resume() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Send resume packet, if we can. */ + if (const auto state = m_state_machine->GetHtcctrlState(); state == HtcctrlState_Sleep || state == HtcctrlState_ExitSleep) { + /* Send a resume packet. */ + m_send_buffer.AddPacket(m_packet_factory->MakeResumePacket()); + + /* Signal our event. */ + m_event.Signal(); + } + } + + void HtcctrlService::Suspend() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* If we can, perform a suspend. */ + if (m_state_machine->GetHtcctrlState() == HtcctrlState_Ready) { + /* Send a suspend packet. */ + m_send_buffer.AddPacket(m_packet_factory->MakeSuspendPacket()); + + /* Signal our event. */ + m_event.Signal(); + + /* Wait for our state to transition. */ + for (auto state = m_state_machine->GetHtcctrlState(); state == HtcctrlState_Ready || state == HtcctrlState_SentSuspendFromTarget; state = m_state_machine->GetHtcctrlState()) { + m_condvar.Wait(m_mutex); + } + } else { + /* Otherwise, just disconnect. */ + this->DisconnectInternal(); + } + } + + void HtcctrlService::NotifyAwake() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Update our information. */ + this->UpdateInformationBody("Awake"); + + /* Send information to host. */ + this->SendInformation(); + } + + void HtcctrlService::NotifyAsleep() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Update our information. */ + this->UpdateInformationBody("Asleep"); + + /* Send information to host. */ + this->SendInformation(); + } + + void HtcctrlService::SendInformation() { + /* If we need information, send information. */ + if (m_state_machine->IsInformationNeeded()) { + /* Send an information packet. */ + m_send_buffer.AddPacket(m_packet_factory->MakeInformationPacket(m_information_body, util::Strnlen(m_information_body, sizeof(m_information_body)) + 1)); + + /* Signal our event. */ + m_event.Signal(); + } + } + + Result HtcctrlService::NotifyDriverConnected() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + if (m_state_machine->GetHtcctrlState() == HtcctrlState_Sleep) { + R_TRY(this->SetState(HtcctrlState_ExitSleep)); + } else { + R_TRY(this->SetState(HtcctrlState_DriverConnected)); + } + + R_SUCCEED(); + } + + Result HtcctrlService::NotifyDriverDisconnected() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + if (m_state_machine->GetHtcctrlState() == HtcctrlState_EnterSleep) { + R_TRY(this->SetState(HtcctrlState_Sleep)); + } else { + R_TRY(this->SetState(HtcctrlState_DriverDisconnected)); + } + + R_SUCCEED(); + } + + Result HtcctrlService::SetState(HtcctrlState state) { + /* Set the state. */ + bool did_transition; + R_TRY(m_state_machine->SetHtcctrlState(std::addressof(did_transition), state)); + + /* Reflect the state transition, if one occurred. */ + if (did_transition) { + this->ReflectState(); + } + + R_SUCCEED(); + } + + void HtcctrlService::ReflectState() { + /* If our connected status changed, update. */ + if (m_state_machine->IsConnectedStatusChanged()) { + m_mux->UpdateChannelState(); + } + + /* If our sleeping status changed, update. */ + if (m_state_machine->IsSleepingStatusChanged()) { + m_mux->UpdateMuxState(); + } + + /* Broadcast our state transition. */ + m_condvar.Broadcast(); + } + + void HtcctrlService::PrintServiceChannels(char *dst, size_t dst_size) { + size_t ofs = 0; + ofs += util::SNPrintf(dst + ofs, dst_size - ofs, "{\r\n \"Chan\" : [\r\n \"%d:%d:%d\"", static_cast<int>(ServiceChannels[0].module_id), ServiceChannels[0].reserved, static_cast<int>(ServiceChannels[0].channel_id)); + for (size_t i = 1; i < util::size(ServiceChannels); ++i) { + ofs += util::SNPrintf(dst + ofs, dst_size - ofs, ",\r\n \"%d:%d:%d\"", static_cast<int>(ServiceChannels[i].module_id), ServiceChannels[i].reserved, static_cast<int>(ServiceChannels[i].channel_id)); + } + ofs += util::SNPrintf(dst + ofs, dst_size - ofs, "\r\n],\r\n \"Prot\" : %d\r\n}\r\n", ProtocolVersion); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_service.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_service.hpp new file mode 100644 index 00000000..0feca91b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_service.hpp @@ -0,0 +1,112 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htclow_ctrl_settings_holder.hpp" +#include "htclow_ctrl_send_buffer.hpp" +#include "htclow_ctrl_state.hpp" + +namespace ams::htclow { + + namespace ctrl { + + class HtcctrlPacketFactory; + class HtcctrlStateMachine; + + } + + namespace mux { + + class Mux; + + } + +} + +namespace ams::htclow::ctrl { + + class HtcctrlService { + private: + SettingsHolder m_settings_holder; + char m_beacon_response[0x1000]; + char m_information_body[0x1000]; + HtcctrlPacketFactory *m_packet_factory; + HtcctrlStateMachine *m_state_machine; + mux::Mux *m_mux; + os::Event m_event; + HtcctrlSendBuffer m_send_buffer; + os::SdkMutex m_mutex; + os::SdkConditionVariable m_condvar; + char m_service_channels_packet[0x1000]; + s16 m_version; + private: + const char *GetConnectionType(impl::DriverType driver_type) const; + + void UpdateBeaconResponse(const char *connection); + void UpdateInformationBody(const char *status); + + void SendInformation(); + + Result ProcessReceiveConnectPacket(); + Result ProcessReceiveReadyPacket(const void *body, size_t body_size); + Result ProcessReceiveSuspendPacket(); + Result ProcessReceiveResumePacket(); + Result ProcessReceiveDisconnectPacket(); + Result ProcessReceiveBeaconQueryPacket(); + Result ProcessReceiveUnexpectedPacket(); + + void ProcessSendConnectPacket(); + void ProcessSendReadyPacket(); + void ProcessSendSuspendPacket(); + void ProcessSendResumePacket(); + void ProcessSendDisconnectPacket(); + + void UpdateServiceChannels(const void *body, size_t body_size); + + void PrintServiceChannels(char *dst, size_t dst_size); + + void TryReadyInternal(); + void DisconnectInternal(); + + Result SetState(HtcctrlState state); + void ReflectState(); + public: + HtcctrlService(HtcctrlPacketFactory *pf, HtcctrlStateMachine *sm, mux::Mux *mux); + + void SetDriverType(impl::DriverType driver_type); + + os::EventType *GetSendPacketEvent() { return m_event.GetBase(); } + + Result CheckReceivedHeader(const HtcctrlPacketHeader &header) const; + Result ProcessReceivePacket(const HtcctrlPacketHeader &header, const void *body, size_t body_size); + + bool QuerySendPacket(HtcctrlPacketHeader *header, HtcctrlPacketBody *body, int *out_body_size); + void RemovePacket(const HtcctrlPacketHeader &header); + + void TryReady(); + void Disconnect(); + + void Resume(); + void Suspend(); + + void NotifyAwake(); + void NotifyAsleep(); + + Result NotifyDriverConnected(); + Result NotifyDriverDisconnected(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_service_channels.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_service_channels.hpp new file mode 100644 index 00000000..b1218c49 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_service_channels.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htclow::ctrl { + + constexpr inline const impl::ChannelInternalType ServiceChannels[] = { + { + .channel_id = 0, /* TODO: htcfs::ChannelId? */ + .module_id = ModuleId::Htcfs, + }, + { + .channel_id = 1, /* TODO: htcmisc::ClientChannelId? */ + .module_id = ModuleId::Htcmisc, + }, + { + .channel_id = 2, /* TODO: htcmisc::ServerChannelId? */ + .module_id = ModuleId::Htcmisc, + }, + { + .channel_id = 0, /* TODO: htcs::ChannelId? */ + .module_id = ModuleId::Htcs, + }, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_settings_holder.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_settings_holder.cpp new file mode 100644 index 00000000..8e1bca47 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_settings_holder.cpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_ctrl_settings_holder.hpp" + +namespace ams::htclow::ctrl { + + void SettingsHolder::LoadSettings() { + /* Load configuration id. */ + { + settings::factory::ConfigurationId1 cfg_id; + settings::factory::GetConfigurationId1(std::addressof(cfg_id)); + + if (cfg_id.str[0]) { + util::Strlcpy(m_hardware_type, cfg_id.str, sizeof(m_hardware_type)); + } else { + util::Strlcpy(m_hardware_type, "Unknown", sizeof(m_hardware_type)); + } + } + + /* Load device name. */ + { + char device_name[0x40]; + settings::fwdbg::GetSettingsItemValue(device_name, sizeof(device_name), "target_manager", "device_name"); + util::Strlcpy(m_target_name, device_name, sizeof(m_target_name)); + } + + /* Load serial number. */ + { + settings::factory::SerialNumber sn; + if (R_SUCCEEDED(settings::factory::GetSerialNumber(std::addressof(sn)))) { + util::Strlcpy(m_serial_number, sn.str, sizeof(m_serial_number)); + } else { + m_serial_number[0] = '\x00'; + } + } + + /* Load firmware version. */ + { + settings::system::FirmwareVersion fw_ver; + settings::system::GetFirmwareVersion(std::addressof(fw_ver)); + util::Strlcpy(m_firmware_version, fw_ver.display_name, sizeof(m_firmware_version)); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_settings_holder.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_settings_holder.hpp new file mode 100644 index 00000000..4f6585fd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_settings_holder.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htclow::ctrl { + + class SettingsHolder { + private: + char m_hardware_type[0x40]{}; + char m_target_name[0x40]{}; + char m_serial_number[0x40]{}; + char m_firmware_version[0x40]{}; + public: + constexpr SettingsHolder() = default; + + void LoadSettings(); + + const char *GetSpec() { return "NX"; } + const char *GetHardwareType() { return m_hardware_type; } + const char *GetTargetName() { return m_target_name; } + const char *GetSerialNumber() { return m_serial_number; } + const char *GetFirmwareVersion() { return m_firmware_version; } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_state.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_state.hpp new file mode 100644 index 00000000..720bd1c5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_state.hpp @@ -0,0 +1,165 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htclow::ctrl { + + enum HtcctrlState : u32 { + HtcctrlState_DriverConnected = 0, + HtcctrlState_SentConnectFromHost = 1, + HtcctrlState_Connected = 2, + HtcctrlState_SentReadyFromHost = 3, + HtcctrlState_Ready = 4, + HtcctrlState_SentSuspendFromTarget = 5, + HtcctrlState_EnterSleep = 6, + HtcctrlState_Sleep = 7, + HtcctrlState_ExitSleep = 8, + HtcctrlState_SentResumeFromTarget = 9, + HtcctrlState_Disconnected = 10, + HtcctrlState_DriverDisconnected = 11, + HtcctrlState_Error = 12, + }; + + constexpr bool IsStateTransitionAllowed(HtcctrlState from, HtcctrlState to) { + switch (from) { + case HtcctrlState_DriverConnected: + return to == HtcctrlState_SentConnectFromHost || + to == HtcctrlState_Disconnected || + to == HtcctrlState_DriverDisconnected || + to == HtcctrlState_Error; + case HtcctrlState_SentConnectFromHost: + return to == HtcctrlState_Connected || + to == HtcctrlState_Disconnected || + to == HtcctrlState_DriverDisconnected || + to == HtcctrlState_Error; + case HtcctrlState_Connected: + return to == HtcctrlState_SentReadyFromHost || + to == HtcctrlState_Disconnected || + to == HtcctrlState_DriverDisconnected || + to == HtcctrlState_Error; + case HtcctrlState_SentReadyFromHost: + return to == HtcctrlState_Ready || + to == HtcctrlState_Disconnected || + to == HtcctrlState_DriverDisconnected || + to == HtcctrlState_Error; + case HtcctrlState_Ready: + return to == HtcctrlState_SentSuspendFromTarget || + to == HtcctrlState_Disconnected || + to == HtcctrlState_DriverDisconnected || + to == HtcctrlState_Error; + case HtcctrlState_SentSuspendFromTarget: + return to == HtcctrlState_EnterSleep || + to == HtcctrlState_Disconnected || + to == HtcctrlState_DriverDisconnected || + to == HtcctrlState_Error; + case HtcctrlState_EnterSleep: + return to == HtcctrlState_Sleep || + to == HtcctrlState_Disconnected || + to == HtcctrlState_DriverDisconnected || + to == HtcctrlState_Error; + case HtcctrlState_Sleep: + return to == HtcctrlState_ExitSleep; + case HtcctrlState_ExitSleep: + return to == HtcctrlState_SentResumeFromTarget || + to == HtcctrlState_Disconnected || + to == HtcctrlState_DriverDisconnected || + to == HtcctrlState_Error; + case HtcctrlState_SentResumeFromTarget: + return to == HtcctrlState_Ready || + to == HtcctrlState_Disconnected || + to == HtcctrlState_DriverDisconnected || + to == HtcctrlState_Error; + case HtcctrlState_Disconnected: + return to == HtcctrlState_SentConnectFromHost || + to == HtcctrlState_Disconnected || + to == HtcctrlState_DriverDisconnected || + to == HtcctrlState_Error; + case HtcctrlState_DriverDisconnected: + return to == HtcctrlState_DriverConnected; + case HtcctrlState_Error: + return to == HtcctrlState_Disconnected || + to == HtcctrlState_DriverDisconnected || + to == HtcctrlState_Error; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + constexpr bool IsDisconnected(HtcctrlState state) { + switch (state) { + case HtcctrlState_Disconnected: + case HtcctrlState_DriverDisconnected: + return true; + default: + return false; + } + } + + constexpr bool IsConnecting(HtcctrlState state) { + switch (state) { + case HtcctrlState_DriverConnected: + case HtcctrlState_SentConnectFromHost: + case HtcctrlState_Disconnected: + return true; + default: + return false; + } + } + + constexpr bool IsConnected(HtcctrlState state) { + switch (state) { + case HtcctrlState_Connected: + case HtcctrlState_SentReadyFromHost: + case HtcctrlState_Ready: + case HtcctrlState_SentSuspendFromTarget: + case HtcctrlState_EnterSleep: + case HtcctrlState_Sleep: + case HtcctrlState_ExitSleep: + case HtcctrlState_SentResumeFromTarget: + return true; + default: + return false; + } + } + + constexpr bool IsReadied(HtcctrlState state) { + switch (state) { + case HtcctrlState_Ready: + case HtcctrlState_SentSuspendFromTarget: + case HtcctrlState_EnterSleep: + case HtcctrlState_Sleep: + case HtcctrlState_ExitSleep: + case HtcctrlState_SentResumeFromTarget: + return true; + default: + return false; + } + } + + constexpr bool IsSleeping(HtcctrlState state) { + switch (state) { + case HtcctrlState_SentSuspendFromTarget: + case HtcctrlState_EnterSleep: + case HtcctrlState_Sleep: + case HtcctrlState_ExitSleep: + case HtcctrlState_SentResumeFromTarget: + return true; + default: + return false; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_state_machine.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_state_machine.cpp new file mode 100644 index 00000000..dc772770 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_state_machine.cpp @@ -0,0 +1,228 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_ctrl_state_machine.hpp" +#include "htclow_ctrl_service_channels.hpp" + +namespace ams::htclow::ctrl { + + HtcctrlStateMachine::HtcctrlStateMachine() : m_map(), m_state(HtcctrlState_DriverDisconnected), m_prev_state(HtcctrlState_DriverDisconnected), m_mutex() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Initialize our map. */ + m_map.Initialize(MaxChannelCount, m_map_buffer, sizeof(m_map_buffer)); + + /* Insert each service channel the map. */ + for (const auto &channel : ServiceChannels) { + m_map.insert(std::make_pair<impl::ChannelInternalType, ServiceChannelState>(impl::ChannelInternalType{channel}, ServiceChannelState{})); + } + } + + HtcctrlState HtcctrlStateMachine::GetHtcctrlState() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + return m_state; + } + + Result HtcctrlStateMachine::SetHtcctrlState(bool *out_transitioned, HtcctrlState state) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that the transition is allowed. */ + R_UNLESS(ctrl::IsStateTransitionAllowed(m_state, state), htclow::ResultHtcctrlStateTransitionNotAllowed()); + + /* Get the state pre-transition. */ + const auto old_state = m_state; + + /* Set the state. */ + this->SetStateWithoutCheckInternal(state); + + /* Note whether we transitioned. */ + *out_transitioned = state != old_state; + R_SUCCEED(); + } + + bool HtcctrlStateMachine::IsInformationNeeded() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + return !ctrl::IsDisconnected(m_state) && m_state != HtcctrlState_DriverConnected; + } + + bool HtcctrlStateMachine::IsDisconnectionNeeded() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + return !ctrl::IsDisconnected(m_state) && m_state != HtcctrlState_Sleep && m_state != HtcctrlState_DriverConnected; + } + + bool HtcctrlStateMachine::IsConnected() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + return ctrl::IsConnected(m_state); + } + + bool HtcctrlStateMachine::IsReadied() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + return ctrl::IsReadied(m_state); + } + + bool HtcctrlStateMachine::IsUnconnectable() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + return !ctrl::IsConnected(m_state); + } + + bool HtcctrlStateMachine::IsDisconnected() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + return ctrl::IsDisconnected(m_state); + } + + bool HtcctrlStateMachine::IsSleeping() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + return ctrl::IsSleeping(m_state); + } + + bool HtcctrlStateMachine::IsConnectedStatusChanged() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + return ctrl::IsConnected(m_prev_state) ^ ctrl::IsConnected(m_state); + } + + bool HtcctrlStateMachine::IsSleepingStatusChanged() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + return ctrl::IsSleeping(m_prev_state) ^ ctrl::IsSleeping(m_state); + } + + void HtcctrlStateMachine::SetStateWithoutCheckInternal(HtcctrlState state) { + if (m_state != state) { + /* Clear service channel states, if we should. */ + if (ctrl::IsDisconnected(state)) { + this->ClearServiceChannelStates(); + } + + /* Transition our state. */ + m_prev_state = m_state; + m_state = state; + } + } + + void HtcctrlStateMachine::ClearServiceChannelStates() { + /* Clear all values in our map. */ + for (auto &pair : m_map) { + pair.second = {}; + } + } + + bool HtcctrlStateMachine::IsPossibleToSendReady() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + return m_state == HtcctrlState_SentReadyFromHost && this->AreServiceChannelsConnecting(); + } + + bool HtcctrlStateMachine::IsUnsupportedServiceChannelToShutdown(const impl::ChannelInternalType &channel) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + auto it = m_map.find(channel); + return it != m_map.end() && it->second.connect == ServiceChannelConnect_ConnectingChecked && it->second.support == ServiceChannelSupport_Unsupported; + } + + bool HtcctrlStateMachine::IsConnectable(const impl::ChannelInternalType &channel) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + auto it = m_map.find(channel); + return ctrl::IsConnected(m_state) && (it == m_map.end() || it->second.connect != ServiceChannelConnect_ConnectingChecked); + } + + void HtcctrlStateMachine::SetConnecting(const impl::ChannelInternalType &channel) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + auto it = m_map.find(channel); + if (it != m_map.end() && it->second.connect != ServiceChannelConnect_ConnectingChecked) { + it->second.connect = ServiceChannelConnect_Connecting; + } + } + + void HtcctrlStateMachine::SetNotConnecting(const impl::ChannelInternalType &channel) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + auto it = m_map.find(channel); + if (it != m_map.end() && it->second.connect != ServiceChannelConnect_ConnectingChecked) { + it->second.connect = ServiceChannelConnect_NotConnecting; + } + } + + void HtcctrlStateMachine::SetConnectingChecked() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + for (auto &pair : m_map) { + pair.second.connect = ServiceChannelConnect_ConnectingChecked; + } + } + + void HtcctrlStateMachine::NotifySupportedServiceChannels(const impl::ChannelInternalType *channels, int num_channels) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + auto IsSupportedServiceChannel = [](const impl::ChannelInternalType &channel, const impl::ChannelInternalType *supported, int num_supported) ALWAYS_INLINE_LAMBDA -> bool { + for (auto i = 0; i < num_supported; ++i) { + if (channel.module_id == supported[i].module_id && channel.channel_id == supported[i].channel_id) { + return true; + } + } + + return false; + }; + + for (auto &pair : m_map) { + if (IsSupportedServiceChannel(pair.first, channels, num_channels)) { + pair.second.support = ServiceChannelSupport_Suppported; + } else { + pair.second.support = ServiceChannelSupport_Unsupported; + } + } + } + + bool HtcctrlStateMachine::AreServiceChannelsConnecting() { + for (auto &pair : m_map) { + if (pair.second.connect != ServiceChannelConnect_Connecting) { + return false; + } + } + + return true; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_state_machine.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_state_machine.hpp new file mode 100644 index 00000000..6359860c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_ctrl_state_machine.hpp @@ -0,0 +1,87 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htclow_ctrl_state.hpp" + +namespace ams::htclow::ctrl { + + class HtcctrlStateMachine { + private: + enum ServiceChannelSupport { + ServiceChannelSupport_Unknown = 0, + ServiceChannelSupport_Suppported = 1, + ServiceChannelSupport_Unsupported = 2, + }; + + enum ServiceChannelConnect { + ServiceChannelConnect_NotConnecting = 0, + ServiceChannelConnect_Connecting = 1, + ServiceChannelConnect_ConnectingChecked = 2, + }; + + struct ServiceChannelState { + ServiceChannelSupport support; + ServiceChannelConnect connect; + }; + + static constexpr int MaxChannelCount = 10; + + using MapType = util::FixedMap<impl::ChannelInternalType, ServiceChannelState>; + + static constexpr size_t MapRequiredMemorySize = MapType::GetRequiredMemorySize(MaxChannelCount); + private: + u8 m_map_buffer[MapRequiredMemorySize]; + MapType m_map; + HtcctrlState m_state; + HtcctrlState m_prev_state; + os::SdkMutex m_mutex; + public: + HtcctrlStateMachine(); + + HtcctrlState GetHtcctrlState(); + Result SetHtcctrlState(bool *out_transitioned, HtcctrlState state); + + bool IsConnectedStatusChanged(); + bool IsSleepingStatusChanged(); + + bool IsInformationNeeded(); + bool IsDisconnectionNeeded(); + + bool IsConnected(); + bool IsReadied(); + bool IsUnconnectable(); + bool IsDisconnected(); + bool IsSleeping(); + + bool IsPossibleToSendReady(); + bool IsUnsupportedServiceChannelToShutdown(const impl::ChannelInternalType &channel); + bool IsConnectable(const impl::ChannelInternalType &channel); + + void SetConnecting(const impl::ChannelInternalType &channel); + void SetNotConnecting(const impl::ChannelInternalType &channel); + void SetConnectingChecked(); + + void NotifySupportedServiceChannels(const impl::ChannelInternalType *channels, int num_channels); + private: + void SetStateWithoutCheckInternal(HtcctrlState state); + + bool AreServiceChannelsConnecting(); + + void ClearServiceChannelStates(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_json.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_json.cpp new file mode 100644 index 00000000..57b7899e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_json.cpp @@ -0,0 +1,84 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_json.hpp" + +namespace ams::htclow::ctrl { + + namespace { + + constexpr const char ChannelKey[] = "Chan"; + constexpr const char ProtocolVersionKey[] = "Prot"; + + } + + bool JsonHandler::Key(const Ch *str, rapidjson::SizeType len, bool copy) { + AMS_UNUSED(len, copy); + + if (m_state == State::ParseObject) { + if (!util::Strncmp(str, ChannelKey, sizeof(ChannelKey))) { + m_state = State::ParseServiceChannels; + } + if (!util::Strncmp(str, ProtocolVersionKey, sizeof(ProtocolVersionKey))) { + m_state = State::ParseProtocolVersion; + } + } + return true; + } + + bool JsonHandler::Uint(unsigned val) { + if (m_state == State::ParseProtocolVersion) { + *m_version = val; + } + return true; + } + + bool JsonHandler::String(const Ch *str, rapidjson::SizeType len, bool copy) { + AMS_UNUSED(len, copy); + + if (m_state == State::ParseServiceChannelsArray && *m_num_strings < m_max_strings) { + m_strings[(*m_num_strings)++] = str; + } + return true; + } + + bool JsonHandler::StartObject() { + if (m_state == State::Begin) { + m_state = State::ParseObject; + } + return true; + } + + bool JsonHandler::EndObject(rapidjson::SizeType) { + m_state = State::End; + return true; + } + + bool JsonHandler::StartArray() { + if (m_state == State::ParseServiceChannels) { + m_state = State::ParseServiceChannelsArray; + } + return true; + } + + bool JsonHandler::EndArray(rapidjson::SizeType len) { + if (m_state == State::ParseServiceChannelsArray && len) { + m_state = State::ParseObject; + } + return true; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_json.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_json.hpp new file mode 100644 index 00000000..ad149937 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_json.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htclow::ctrl { + + class JsonHandler : public rapidjson::BaseReaderHandler<rapidjson::UTF8<char>, JsonHandler>{ + private: + enum class State { + Begin = 0, + ParseObject = 1, + ParseServiceChannels = 2, + ParseServiceChannelsArray = 3, + ParseProtocolVersion = 4, + End = 5, + }; + private: + State m_state; + s16 *m_version; + const char **m_strings; + int *m_num_strings; + int m_max_strings; + public: + JsonHandler(s16 *vers, const char **str, int *ns, int max) : m_state(State::Begin), m_version(vers), m_strings(str), m_num_strings(ns), m_max_strings(max) { /* ... */ } + + bool Key(const Ch *str, rapidjson::SizeType len, bool copy); + bool Uint(unsigned); + bool String(const Ch *, rapidjson::SizeType, bool); + + bool StartObject(); + bool EndObject(rapidjson::SizeType); + bool StartArray(); + bool EndArray(rapidjson::SizeType); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_service_channel_parser.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_service_channel_parser.cpp new file mode 100644 index 00000000..bba6ea0a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_service_channel_parser.cpp @@ -0,0 +1,160 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_json.hpp" +#include "htclow_service_channel_parser.hpp" + +namespace ams::htclow::ctrl { + + namespace { + + constexpr auto JsonParseFlags = rapidjson::kParseTrailingCommasFlag | rapidjson::kParseInsituFlag; + + void ParseBody(s16 *out_version, const char **out_channels, int *out_num_channels, int max_channels, void *str, size_t str_size) { + AMS_UNUSED(str_size); + + /* Create JSON handler. */ + JsonHandler json_handler(out_version, out_channels, out_num_channels, max_channels); + + /* Create reader. */ + rapidjson::Reader json_reader; + + /* Create stream. */ + rapidjson::InsituStringStream json_stream(static_cast<char *>(str)); + + /* Parse the json. */ + json_reader.Parse<JsonParseFlags>(json_stream, json_handler); + } + + constexpr bool IsNumericCharacter(char c) { + return '0' <= c && c <= '9'; + } + + constexpr bool IsValidCharacter(char c) { + return IsNumericCharacter(c) || c == ':'; + } + + bool IntegerToModuleId(ModuleId *out, int id) { + switch (id) { + case 0: + case 1: + case 3: + case 4: + *out = static_cast<ModuleId>(id); + return true; + default: + return false; + } + } + + bool StringToChannel(impl::ChannelInternalType *out, char *str, size_t size) { + enum class State { + Begin, + ModuleId, + Sep1, + Reserved, + Sep2, + ChannelId + }; + + State state = State::Begin; + + const char * cur = nullptr; + + const char * const str_end = str + size; + while (str < str_end && IsValidCharacter(*str)) { + const char c = *str; + + switch (state) { + case State::Begin: + if (IsNumericCharacter(c)) { + cur = str; + state = State::ModuleId; + } + break; + case State::ModuleId: + if (c == ':') { + *str = 0; + if (!IntegerToModuleId(std::addressof(out->module_id), atoi(cur))) { + return false; + } + state = State::Sep1; + } else if (!IsNumericCharacter(c)) { + return false; + } + break; + case State::Sep1: + if (IsNumericCharacter(c)) { + cur = str; + state = State::Reserved; + } + break; + case State::Reserved: + if (c == ':') { + *str = 0; + out->reserved = 0; + state = State::Sep2; + } else if (!IsNumericCharacter(c)) { + return false; + } + break; + case State::Sep2: + if (IsNumericCharacter(c)) { + cur = str; + state = State::ChannelId; + } + break; + case State::ChannelId: + if (!IsNumericCharacter(c)) { + return false; + } + break; + } + + ++str; + } + + if (str != str_end) { + return false; + } + + out->channel_id = atoi(cur); + + return true; + } + + } + + void ParseServiceChannel(s16 *out_version, impl::ChannelInternalType *out_channels, int *out_num_channels, int max_channels, void *str, size_t str_size) { + /* Parse the JSON. */ + const char *channel_strs[0x20]; + int num_channels = 0; + ParseBody(out_version, channel_strs, std::addressof(num_channels), util::size(channel_strs), str, str_size); + + /* Parse the channel strings. */ + char * const str_end = static_cast<char *>(str) + str_size; + int parsed_channels = 0; + for (auto i = 0; i < num_channels && i < max_channels; ++i) { + impl::ChannelInternalType channel; + if (StringToChannel(std::addressof(channel), const_cast<char *>(channel_strs[i]), util::Strnlen(channel_strs[i], str_end - channel_strs[i]))) { + out_channels[parsed_channels++] = channel; + } + } + + *out_num_channels = parsed_channels; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_service_channel_parser.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_service_channel_parser.hpp new file mode 100644 index 00000000..5ce87890 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/ctrl/htclow_service_channel_parser.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htclow::ctrl { + + void ParseServiceChannel(s16 *out_version, impl::ChannelInternalType *out_channels, int *out_num_channels, int max_channels, void *str, size_t str_size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_driver_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_driver_manager.cpp new file mode 100644 index 00000000..5167925a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_driver_manager.cpp @@ -0,0 +1,94 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_driver_manager.hpp" + +namespace ams::htclow::driver { + + Result DriverManager::OpenDriver(impl::DriverType driver_type) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're not already open. */ + R_UNLESS(m_open_driver == nullptr, htclow::ResultDriverOpened()); + + /* Open the driver. */ + switch (driver_type) { + case impl::DriverType::Debug: + R_TRY(m_debug_driver->Open()); + m_open_driver = m_debug_driver; + break; + case impl::DriverType::Socket: + m_socket_driver.Open(); + m_open_driver = std::addressof(m_socket_driver); + break; + case impl::DriverType::Usb: + m_usb_driver.Open(); + m_open_driver = std::addressof(m_usb_driver); + break; + case impl::DriverType::PlainChannel: + //m_plain_channel_driver.Open(); + //m_open_driver = std::addressof(m_plain_channel_driver); + //break; + R_THROW(htclow::ResultUnknownDriverType()); + default: + R_THROW(htclow::ResultUnknownDriverType()); + } + + /* Set the driver type. */ + m_driver_type = driver_type; + + R_SUCCEED(); + } + + void DriverManager::CloseDriver() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Clear our driver type. */ + m_driver_type = util::nullopt; + + /* Close our driver. */ + if (m_open_driver != nullptr) { + m_open_driver->Close(); + m_open_driver = nullptr; + } + } + + impl::DriverType DriverManager::GetDriverType() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + return m_driver_type.value_or(impl::DriverType::Unknown); + } + + IDriver *DriverManager::GetCurrentDriver() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + return m_open_driver; + } + + void DriverManager::Cancel() { + m_open_driver->CancelSendReceive(); + } + + void DriverManager::SetDebugDriver(IDriver *driver) { + m_debug_driver = driver; + m_driver_type = impl::DriverType::Debug; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_driver_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_driver_manager.hpp new file mode 100644 index 00000000..3642625a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_driver_manager.hpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htclow_i_driver.hpp" +#include "htclow_socket_driver.hpp" +#include "htclow_usb_driver.hpp" + +namespace ams::htclow::driver { + + class DriverManager { + private: + util::optional<htclow::impl::DriverType> m_driver_type{}; + IDriver *m_debug_driver{}; + SocketDriver m_socket_driver; + UsbDriver m_usb_driver{}; + /* TODO: PlainChannelDriver m_plain_channel_driver; */ + os::SdkMutex m_mutex{}; + IDriver *m_open_driver{}; + public: + DriverManager(mem::StandardAllocator *allocator) : m_socket_driver(allocator) { /* ... */ } + + Result OpenDriver(impl::DriverType driver_type); + void CloseDriver(); + + impl::DriverType GetDriverType(); + + IDriver *GetCurrentDriver(); + + void Cancel(); + + void SetDebugDriver(IDriver *driver); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_driver_memory_management.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_driver_memory_management.cpp new file mode 100644 index 00000000..334ce26f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_driver_memory_management.cpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_driver_memory_management.hpp" + +namespace ams::htclow::driver { + + namespace { + + constexpr inline size_t RequiredAlignment = std::max(os::ThreadStackAlignment, os::MemoryPageSize); + + using SocketConfigType = socket::SystemConfigDefault; + + /* TODO: If we ever use resolvers, increase this. */ + constexpr inline size_t SocketAllocatorSize = 4_KB; + constexpr inline size_t SocketMemoryPoolSize = util::AlignUp(SocketConfigType::PerTcpSocketWorstCaseMemoryPoolSize + SocketConfigType::PerUdpSocketWorstCaseMemoryPoolSize, os::MemoryPageSize); + + constexpr inline size_t SocketRequiredSize = util::AlignUp(SocketMemoryPoolSize + SocketAllocatorSize, os::MemoryPageSize); + constexpr inline size_t UsbRequiredSize = 2 * UsbDmaBufferSize + UsbIndicationThreadStackSize; + static_assert(util::IsAligned(UsbDmaBufferSize, RequiredAlignment)); + + constexpr inline size_t RequiredSize = std::max(SocketRequiredSize, UsbRequiredSize); + static_assert(util::IsAligned(RequiredSize, os::MemoryPageSize)); + + /* Declare the memory pool. */ + alignas(RequiredAlignment) constinit u8 g_driver_memory[RequiredSize]; + + constexpr inline const socket::SystemConfigDefault SocketConfig(g_driver_memory, RequiredSize, SocketAllocatorSize, 2); + + } + + void *GetUsbReceiveBuffer() { + return g_driver_memory; + } + + void *GetUsbSendBuffer() { + return g_driver_memory + UsbDmaBufferSize; + } + + void *GetUsbIndicationThreadStack() { + return g_driver_memory + 2 * UsbDmaBufferSize; + } + + void InitializeSocketApiForSocketDriver() { + R_ABORT_UNLESS(socket::Initialize(SocketConfig)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_driver_memory_management.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_driver_memory_management.hpp new file mode 100644 index 00000000..7414a09f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_driver_memory_management.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htclow::driver { + + constexpr inline size_t UsbDmaBufferSize = 0x80000; + constexpr inline size_t UsbIndicationThreadStackSize = 16_KB; + + void *GetUsbReceiveBuffer(); + void *GetUsbSendBuffer(); + void *GetUsbIndicationThreadStack(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_i_driver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_i_driver.hpp new file mode 100644 index 00000000..14401240 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_i_driver.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htclow::driver { + + class IDriver { + public: + virtual Result Open() = 0; + virtual void Close() = 0; + virtual Result Connect(os::EventType *event) = 0; + virtual void Shutdown() = 0; + virtual Result Send(const void *src, int src_size) = 0; + virtual Result Receive(void *dst, int dst_size) = 0; + virtual void CancelSendReceive() = 0; + virtual void Suspend() = 0; + virtual void Resume() = 0; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_manager.cpp new file mode 100644 index 00000000..f864efc1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_manager.cpp @@ -0,0 +1,151 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_socket_discovery_manager.hpp" +#include "htclow_socket_discovery_util.hpp" + +namespace ams::htclow::driver { + + namespace { + + constexpr inline u32 BeaconQueryServiceId = 0xB48F5C51; + + } + + void SocketDiscoveryManager::OnDriverOpen() { + /* Create our socket. */ + m_socket = socket::SocketExempt(socket::Family::Af_Inet, socket::Type::Sock_Dgram, socket::Protocol::IpProto_Udp); + AMS_ABORT_UNLESS(m_socket != -1); + + /* Mark driver open. */ + m_driver_closed = false; + + /* Create our thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_discovery_thread), ThreadEntry, this, m_thread_stack, os::MemoryPageSize, AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtclowDiscovery))); + + /* Set our thread name. */ + os::SetThreadNamePointer(std::addressof(m_discovery_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtclowDiscovery)); + + /* Start our thread. */ + os::StartThread(std::addressof(m_discovery_thread)); + } + + void SocketDiscoveryManager::OnDriverClose() { + /* Mark driver closed. */ + m_driver_closed = true; + + /* Shutdown our socket. */ + socket::Shutdown(m_socket, socket::ShutdownMethod::Shut_RdWr); + + /* Close our socket. */ + socket::Close(m_socket); + + /* Destroy our thread. */ + os::WaitThread(std::addressof(m_discovery_thread)); + os::DestroyThread(std::addressof(m_discovery_thread)); + } + + void SocketDiscoveryManager::OnSocketAcceptBegin(u16 port) { + AMS_UNUSED(port); + } + + void SocketDiscoveryManager::OnSocketAcceptEnd() { + /* ... */ + } + + void SocketDiscoveryManager::ThreadFunc() { + for (this->DoDiscovery(); !m_driver_closed; this->DoDiscovery()) { + /* Check if the driver is closed five times. */ + for (size_t i = 0; i < 5; ++i) { + os::SleepThread(TimeSpan::FromSeconds(1)); + if (m_driver_closed) { + return; + } + } + } + } + + Result SocketDiscoveryManager::DoDiscovery() { + /* Ensure we close our socket if we fail. */ + auto socket_guard = SCOPE_GUARD { socket::Close(m_socket); }; + + /* Create sockaddr for our socket. */ + const socket::SockAddrIn sockaddr = { + .sin_len = 0, + .sin_family = socket::Family::Af_Inet, + .sin_port = socket::InetHtons(20181), + .sin_addr = { socket::InetHtonl(0) }, + }; + + /* Bind our socket. */ + const auto bind_res = socket::Bind(m_socket, reinterpret_cast<const socket::SockAddr *>(std::addressof(sockaddr)), sizeof(sockaddr)); + R_UNLESS(bind_res != 0, htclow::ResultSocketBindError()); + + /* Loop processing beacon queries. */ + while (true) { + /* Receive a tmipc query header. */ + TmipcHeader header; + socket::SockAddr recv_sockaddr; + socket::SockLenT recv_sockaddr_len = sizeof(recv_sockaddr); + const auto recv_res = socket::RecvFrom(m_socket, std::addressof(header), sizeof(header), socket::MsgFlag::Msg_None, std::addressof(recv_sockaddr), std::addressof(recv_sockaddr_len)); + + /* Check that our receive was valid. */ + R_UNLESS(recv_res >= 0, htclow::ResultSocketReceiveFromError()); + R_UNLESS(recv_sockaddr_len == sizeof(recv_sockaddr), htclow::ResultSocketReceiveFromError()); + + /* Check we received a packet header. */ + if (recv_res != sizeof(header)) { + continue; + } + + /* Check that we received a correctly versioned BeaconQuery packet. */ + /* NOTE: Nintendo checks this *after* the following receive, but this seems saner. */ + if (header.version != TmipcVersion || header.service_id != BeaconQueryServiceId) { + continue; + } + + /* Receive the packet body, if there is one. */ + char packet_data[0x120]; + + /* NOTE: Nintendo does not check this... */ + if (header.data_len > sizeof(packet_data)) { + continue; + } + + if (header.data_len > 0) { + const auto body_res = socket::RecvFrom(m_socket, packet_data, header.data_len, socket::MsgFlag::Msg_None, std::addressof(recv_sockaddr), std::addressof(recv_sockaddr_len)); + R_UNLESS(body_res >= 0, htclow::ResultSocketReceiveFromError()); + R_UNLESS(recv_sockaddr_len == sizeof(recv_sockaddr), htclow::ResultSocketReceiveFromError()); + + if (body_res != header.data_len) { + continue; + } + } + + /* Make our beacon response packet. */ + const auto len = MakeBeaconResponsePacket(packet_data, sizeof(packet_data)); + + /* Send the beacon response data. */ + const auto send_res = socket::SendTo(m_socket, packet_data, len, socket::MsgFlag::Msg_None, std::addressof(recv_sockaddr), sizeof(recv_sockaddr)); + R_UNLESS(send_res >= 0, htclow::ResultSocketSendToError()); + } + + /* This can never happen, as the above loop should be infinite, but completion logic is here for posterity. */ + socket_guard.Cancel(); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_manager.hpp new file mode 100644 index 00000000..b558414f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_manager.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htclow::driver { + + class SocketDiscoveryManager { + private: + bool m_driver_closed; + mem::StandardAllocator *m_allocator; + void *m_thread_stack; + os::ThreadType m_discovery_thread; + s32 m_socket; + public: + SocketDiscoveryManager(mem::StandardAllocator *allocator) + : m_driver_closed(false), m_allocator(allocator), m_thread_stack(allocator->Allocate(os::MemoryPageSize, os::ThreadStackAlignment)) + { + /* ... */ + } + private: + static void ThreadEntry(void *arg) { + static_cast<SocketDiscoveryManager *>(arg)->ThreadFunc(); + } + + void ThreadFunc(); + + Result DoDiscovery(); + public: + void OnDriverOpen(); + void OnDriverClose(); + void OnSocketAcceptBegin(u16 port); + void OnSocketAcceptEnd(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_util.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_util.cpp new file mode 100644 index 00000000..25512381 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_util.cpp @@ -0,0 +1,132 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_socket_discovery_util.hpp" +#include "../ctrl/htclow_ctrl_settings_holder.hpp" + +namespace ams::htclow::driver { + + namespace { + + constexpr inline u32 AutoConnectIpv4RequestServiceId = 0x834C775A; + constexpr inline u32 BeaconResponseServiceId = 0xA6F7FA96; + + constexpr const char MakeAutoConnectIpv4RequestPacketFormat[] = + "{\r\n" + " \"Address\" : \"%u.%u.%u.%u\",\r\n" + " \"Port\" : %u,\r\n" + " \"HW\" : \"%s\",\r\n" + " \"SN\" : \"%s\"\r\n" + "}\r\n"; + + constexpr const char BeaconResponsePacketFormat[] = + "{\r\n" + " \"Gen\" : 2,\r\n" + " \"Spec \": \"%s\",\r\n" + " \"MAC\" : \"00:00:00:00:00:00\",\r\n" + " \"Conn\" : \"TCP\",\r\n" + " \"HW\" : \"%s\",\r\n" + " \"Name\" : \"%s\",\r\n" + " \"SN\" : \"%s\",\r\n" + " \"FW\" : \"%s\"\r\n" + "}\r\n"; + + constinit os::SdkMutex g_settings_holder_mutex; + constinit bool g_settings_holder_initialized = false; + constinit htclow::ctrl::SettingsHolder g_settings_holder; + + void InitializeSettingsHolder() { + std::scoped_lock lk(g_settings_holder_mutex); + + if (!g_settings_holder_initialized) { + g_settings_holder.LoadSettings(); + g_settings_holder_initialized = true; + } + } + + } + + s32 MakeAutoConnectIpv4RequestPacket(char *dst, size_t dst_size, const socket::SockAddrIn &sockaddr) { + /* Initialize the settings holder. */ + InitializeSettingsHolder(); + + /* Create the packet header. */ + TmipcHeader header = { + .service_id = AutoConnectIpv4RequestServiceId, + .version = TmipcVersion, + }; + + /* Create the packet body. */ + std::scoped_lock lk(g_settings_holder_mutex); + const auto addr = sockaddr.sin_addr.s_addr; + + char packet_body[0x100]; + const auto ideal_len = util::SNPrintf(packet_body, sizeof(packet_body), MakeAutoConnectIpv4RequestPacketFormat, + (addr >> 0) & 0xFF, (addr >> 8) & 0xFF, (addr >> 16) & 0xFF, (addr >> 24) & 0xFF, + socket::InetNtohs(sockaddr.sin_port), + g_settings_holder.GetHardwareType(), + "" /* Nintendo passes empty string as serial number here. */ + ); + + /* Determine actual usable body length. */ + header.data_len = std::max<u32>(ideal_len, sizeof(packet_body)); + + /* Check that the packet will fit. */ + AMS_ABORT_UNLESS(sizeof(header) + header.data_len <= dst_size); + + /* Copy the formatted header. */ + std::memcpy(dst, std::addressof(header), sizeof(header)); + std::memcpy(dst + sizeof(header), packet_body, header.data_len); + + return header.data_len; + } + + s32 MakeBeaconResponsePacket(char *dst, size_t dst_size) { + /* Initialize the settings holder. */ + InitializeSettingsHolder(); + + /* Create the packet header. */ + TmipcHeader header = { + .service_id = BeaconResponseServiceId, + .version = TmipcVersion, + }; + + /* Create the packet body. */ + std::scoped_lock lk(g_settings_holder_mutex); + + char packet_body[0x100]; + const auto ideal_len = util::SNPrintf(packet_body, sizeof(packet_body), BeaconResponsePacketFormat, + g_settings_holder.GetSpec(), + g_settings_holder.GetHardwareType(), + g_settings_holder.GetTargetName(), + g_settings_holder.GetSerialNumber(), + g_settings_holder.GetFirmwareVersion() + ); + + /* Determine actual usable body length. */ + header.data_len = std::max<u32>(ideal_len, sizeof(packet_body)); + + /* Check that the packet will fit. */ + AMS_ABORT_UNLESS(sizeof(header) + header.data_len <= dst_size); + + /* Copy the formatted header. */ + std::memcpy(dst, std::addressof(header), sizeof(header)); + std::memcpy(dst + sizeof(header), packet_body, header.data_len); + + return header.data_len; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_util.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_util.hpp new file mode 100644 index 00000000..7ea34130 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_socket_discovery_util.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htclow::driver { + + constexpr inline u8 TmipcVersion = 5; + + struct TmipcHeader { + u32 service_id; + u32 reserved_00; + u16 reserved_01; + u8 reserved_02; + u8 version; + u32 data_len; + u32 reserved[4]; + }; + static_assert(util::is_pod<TmipcHeader>::value); + static_assert(sizeof(TmipcHeader) == 0x20); + + s32 MakeAutoConnectIpv4RequestPacket(char *dst, size_t dst_size, const socket::SockAddrIn &sockaddr); + s32 MakeBeaconResponsePacket(char *dst, size_t dst_size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_socket_driver.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_socket_driver.cpp new file mode 100644 index 00000000..63afcf78 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_socket_driver.cpp @@ -0,0 +1,283 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_socket_driver.hpp" +#include "htclow_socket_discovery_util.hpp" + +namespace ams::htclow::driver { + + Result SocketDriver::ConnectThread() { + /* Do auto connect, if we should. */ + if (m_auto_connect_reserved) { + this->DoAutoConnect(); + } + + /* Get the socket's name. */ + socket::SockAddrIn sockaddr; + socket::SockLenT sockaddr_len = sizeof(sockaddr); + R_UNLESS(socket::GetSockName(m_server_socket, reinterpret_cast<socket::SockAddr *>(std::addressof(sockaddr)), std::addressof(sockaddr_len)) == 0, htclow::ResultSocketGetSockNameError()); + + /* Accept. */ + m_discovery_manager.OnSocketAcceptBegin(sockaddr.sin_port); + + sockaddr_len = sizeof(m_server_sockaddr); + const auto client_desc = socket::Accept(m_server_socket, reinterpret_cast<socket::SockAddr *>(std::addressof(m_server_sockaddr)), std::addressof(sockaddr_len)); + + m_discovery_manager.OnSocketAcceptEnd(); + + /* Check accept result. */ + R_UNLESS(client_desc >= 0, htclow::ResultSocketAcceptError()); + + /* Setup client socket. */ + R_TRY(this->SetupClientSocket(client_desc)); + + R_SUCCEED(); + } + + Result SocketDriver::CreateServerSocket() { + /* Check that we don't have a server socket. */ + AMS_ASSERT(!m_server_socket_valid); + + /* Create the socket. */ + const auto desc = socket::SocketExempt(socket::Family::Af_Inet, socket::Type::Sock_Stream, socket::Protocol::IpProto_Tcp); + R_UNLESS(desc != -1, htclow::ResultSocketSocketExemptError()); + + /* Be sure that we close the socket if we don't succeed. */ + auto socket_guard = SCOPE_GUARD { socket::Close(desc); }; + + /* Create sockaddr for our socket. */ + const socket::SockAddrIn sockaddr = { + .sin_len = 0, + .sin_family = socket::Family::Af_Inet, + .sin_port = socket::InetHtons(20180), + .sin_addr = { socket::InetHtonl(0) }, + }; + + /* Enable local address reuse. */ + { + u32 enable = 1; + const auto res = socket::SetSockOpt(desc, socket::Level::Sol_Socket, socket::Option::So_ReuseAddr, std::addressof(enable), sizeof(enable)); + AMS_ABORT_UNLESS(res == 0); + } + + /* Bind the socket. */ + const auto bind_res = socket::Bind(desc, reinterpret_cast<const socket::SockAddr *>(std::addressof(sockaddr)), sizeof(sockaddr)); + R_UNLESS(bind_res == 0, htclow::ResultSocketBindError()); + + /* Listen on the socket. */ + const auto listen_res = socket::Listen(desc, 1); + R_UNLESS(listen_res == 0, htclow::ResultSocketListenError()); + + /* We succeeded. */ + socket_guard.Cancel(); + m_server_socket = desc; + m_server_socket_valid = true; + + R_SUCCEED(); + } + + void SocketDriver::DestroyServerSocket() { + if (m_server_socket_valid) { + socket::Shutdown(m_server_socket, socket::ShutdownMethod::Shut_RdWr); + socket::Close(m_server_socket); + m_server_socket_valid = false; + } + } + + Result SocketDriver::SetupClientSocket(s32 desc) { + std::scoped_lock lk(m_mutex); + + /* Check that we don't have a client socket. */ + AMS_ASSERT(!m_client_socket_valid); + + /* Be sure that we close the socket if we don't succeed. */ + auto socket_guard = SCOPE_GUARD { socket::Close(desc); }; + + /* Enable debug logging for the socket. */ + u32 debug = 1; + const auto res = socket::SetSockOpt(desc, socket::Level::Sol_Tcp, socket::Option::So_Debug, std::addressof(debug), sizeof(debug)); + R_UNLESS(res >= 0, htclow::ResultSocketSetSockOptError()); + + /* We succeeded. */ + socket_guard.Cancel(); + m_client_socket = desc; + m_client_socket_valid = true; + + R_SUCCEED(); + } + + bool SocketDriver::IsAutoConnectReserved() { + return m_auto_connect_reserved; + } + + void SocketDriver::ReserveAutoConnect() { + std::scoped_lock lk(m_mutex); + + if (m_client_socket_valid) { + /* Save our client sockaddr. */ + socket::SockLenT sockaddr_len = sizeof(m_saved_client_sockaddr); + if (socket::GetSockName(m_server_socket, reinterpret_cast<socket::SockAddr *>(std::addressof(m_saved_client_sockaddr)), std::addressof(sockaddr_len)) != 0) { + return; + } + + /* Save our server sockaddr. */ + m_saved_server_sockaddr = m_server_sockaddr; + + /* Mark auto-connect reserved. */ + m_auto_connect_reserved = true; + } + } + + void SocketDriver::DoAutoConnect() { + /* Clear auto-connect reserved. */ + m_auto_connect_reserved = false; + + /* Create udb socket. */ + const auto desc = socket::SocketExempt(socket::Family::Af_Inet, socket::Type::Sock_Dgram, socket::Protocol::IpProto_Udp); + if (desc == -1) { + return; + } + + /* Clean up the desc when we're done. */ + ON_SCOPE_EXIT { socket::Close(desc); }; + + /* Create auto-connect packet. */ + char auto_connect_packet[0x120]; + s32 len; + { + const socket::SockAddrIn sockaddr = { + .sin_family = socket::Family::Af_Inet, + .sin_port = m_saved_client_sockaddr.sin_port, + .sin_addr = m_saved_client_sockaddr.sin_addr, + }; + + len = htclow::driver::MakeAutoConnectIpv4RequestPacket(auto_connect_packet, sizeof(auto_connect_packet), sockaddr); + } + + /* Send the auto-connect packet to the host on port 20181. */ + const socket::SockAddrIn sockaddr = { + .sin_family = socket::Family::Af_Inet, + .sin_port = socket::InetHtons(20181), + .sin_addr = m_saved_server_sockaddr.sin_addr, + }; + + /* Send the auto-connect packet. */ + socket::SendTo(desc, auto_connect_packet, len, socket::MsgFlag::Msg_None, reinterpret_cast<const socket::SockAddr *>(std::addressof(sockaddr)), sizeof(sockaddr)); + } + + Result SocketDriver::Open() { + m_discovery_manager.OnDriverOpen(); + R_SUCCEED(); + } + + void SocketDriver::Close() { + m_discovery_manager.OnDriverClose(); + } + + Result SocketDriver::Connect(os::EventType *event) { + /* Allocate a temporary thread stack. */ + void *stack = m_allocator->Allocate(os::MemoryPageSize, os::ThreadStackAlignment); + ON_SCOPE_EXIT { m_allocator->Free(stack); }; + + /* Try to create a server socket. */ + R_TRY(this->CreateServerSocket()); + + /* Prepare to run our connect thread. */ + m_event.Clear(); + + /* Run our connect thread. */ + { + /* Create the thread. */ + os::ThreadType connect_thread; + R_ABORT_UNLESS(os::CreateThread(std::addressof(connect_thread), ConnectThreadEntry, this, stack, os::MemoryPageSize, AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtclowTcpServer))); + + /* Set the thread's name. */ + os::SetThreadNamePointer(std::addressof(connect_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtclowTcpServer)); + + /* Start the thread. */ + os::StartThread(std::addressof(connect_thread)); + + /* Check if we should cancel the connection. */ + if (os::WaitAny(event, m_event.GetBase()) == 0) { + this->DestroyServerSocket(); + } + + /* Wait for the connect thread to finish. */ + os::WaitThread(std::addressof(connect_thread)); + + /* Destroy the connection thread. */ + os::DestroyThread(std::addressof(connect_thread)); + + /* Destroy the server socket. */ + this->DestroyServerSocket(); + } + + /* Return our connection result. */ + R_RETURN(m_connect_result); + } + + void SocketDriver::Shutdown() { + std::scoped_lock lk(m_mutex); + + /* Shut down our client socket, if we need to. */ + if (m_client_socket_valid) { + socket::Shutdown(m_client_socket, socket::ShutdownMethod::Shut_RdWr); + socket::Close(m_client_socket); + m_client_socket_valid = 0; + } + } + + Result SocketDriver::Send(const void *src, int src_size) { + /* Check the input size. */ + R_UNLESS(src_size >= 0, htclow::ResultInvalidArgument()); + + /* Repeatedly send data until it's all sent. */ + ssize_t cur_sent; + for (ssize_t sent = 0; sent < src_size; sent += cur_sent) { + cur_sent = socket::Send(m_client_socket, static_cast<const u8 *>(src) + sent, src_size - sent, socket::MsgFlag::Msg_None); + R_UNLESS(cur_sent > 0, htclow::ResultSocketSendError()); + } + + R_SUCCEED(); + } + + Result SocketDriver::Receive(void *dst, int dst_size) { + /* Check the input size. */ + R_UNLESS(dst_size >= 0, htclow::ResultInvalidArgument()); + + /* Repeatedly receive data until it's all sent. */ + ssize_t cur_recv; + for (ssize_t received = 0; received < dst_size; received += cur_recv) { + cur_recv = socket::Recv(m_client_socket, static_cast<u8 *>(dst) + received, dst_size - received, socket::MsgFlag::Msg_None); + R_UNLESS(cur_recv > 0, htclow::ResultSocketReceiveError()); + } + + R_SUCCEED(); + } + + void SocketDriver::CancelSendReceive() { + this->Shutdown(); + } + + void SocketDriver::Suspend() { + this->ReserveAutoConnect(); + } + + void SocketDriver::Resume() { + /* ... */ + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_socket_driver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_socket_driver.hpp new file mode 100644 index 00000000..23ac01c3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_socket_driver.hpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htclow_i_driver.hpp" +#include "htclow_socket_discovery_manager.hpp" + +namespace ams::htclow::driver { + + class SocketDriver final : public IDriver { + private: + mem::StandardAllocator *m_allocator; + SocketDiscoveryManager m_discovery_manager; + socket::SockAddrIn m_server_sockaddr; + socket::SockAddrIn m_saved_server_sockaddr; + socket::SockAddrIn m_saved_client_sockaddr; + os::Event m_event; + Result m_connect_result; + os::SdkMutex m_mutex; + s32 m_server_socket; + s32 m_client_socket; + bool m_server_socket_valid; + bool m_client_socket_valid; + bool m_auto_connect_reserved; + public: + SocketDriver(mem::StandardAllocator *allocator) + : m_allocator(allocator), m_discovery_manager(m_allocator), m_event(os::EventClearMode_ManualClear), + m_connect_result(), m_mutex(), m_server_socket(), m_client_socket(), m_server_socket_valid(false), + m_client_socket_valid(false), m_auto_connect_reserved(false) + { + /* ... */ + } + private: + static void ConnectThreadEntry(void *arg) { + auto * const driver = static_cast<SocketDriver *>(arg); + + driver->m_connect_result = driver->ConnectThread(); + driver->m_event.Signal(); + } + + Result ConnectThread(); + private: + Result CreateServerSocket(); + void DestroyServerSocket(); + + Result SetupClientSocket(s32 desc); + + bool IsAutoConnectReserved(); + void ReserveAutoConnect(); + void DoAutoConnect(); + public: + virtual Result Open() override; + virtual void Close() override; + virtual Result Connect(os::EventType *event) override; + virtual void Shutdown() override; + virtual Result Send(const void *src, int src_size) override; + virtual Result Receive(void *dst, int dst_size) override; + virtual void CancelSendReceive() override; + virtual void Suspend() override; + virtual void Resume() override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_usb_driver.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_usb_driver.cpp new file mode 100644 index 00000000..fceb0b5f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_usb_driver.cpp @@ -0,0 +1,125 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_usb_driver.hpp" +#include "htclow_usb_impl.hpp" + +namespace ams::htclow::driver { + + void UsbDriver::OnUsbAvailabilityChange(UsbAvailability availability, void *param) { + /* Convert the argument to a driver. */ + UsbDriver *driver = static_cast<UsbDriver *>(param); + + /* Handle the change. */ + switch (availability) { + case UsbAvailability_Unavailable: + CancelUsbSendReceive(); + break; + case UsbAvailability_Available: + driver->m_event.Signal(); + break; + case UsbAvailability_Unknown: + driver->CancelSendReceive(); + break; + } + } + + Result UsbDriver::Open() { + /* Clear our event. */ + m_event.Clear(); + + /* Set the availability change callback. */ + SetUsbAvailabilityChangeCallback(OnUsbAvailabilityChange, this); + + /* Initialize the interface. */ + R_RETURN(InitializeUsbInterface()); + } + + void UsbDriver::Close() { + /* Finalize the interface. */ + FinalizeUsbInterface(); + + /* Clear the availability callback. */ + ClearUsbAvailabilityChangeCallback(); + } + + Result UsbDriver::Connect(os::EventType *event) { + /* We must not already be connected. */ + AMS_ABORT_UNLESS(!m_connected); + + /* Perform a wait on our event. */ + const int idx = os::WaitAny(m_event.GetBase(), event); + R_UNLESS(idx == 0, htclow::ResultCancelled()); + + /* Clear our event. */ + m_event.Clear(); + + /* We're connected. */ + m_connected = true; + R_SUCCEED(); + } + + void UsbDriver::Shutdown() { + /* If we're connected, cancel anything we're doing. */ + if (m_connected) { + this->CancelSendReceive(); + m_connected = false; + } + } + + Result UsbDriver::Send(const void *src, int src_size) { + /* Check size. */ + R_UNLESS(src_size >= 0, htclow::ResultInvalidArgument()); + + /* Send until we've sent everything. */ + for (auto transferred = 0; transferred < src_size; /* ... */) { + int cur; + R_TRY(SendUsb(std::addressof(cur), reinterpret_cast<const void *>(reinterpret_cast<uintptr_t>(src) + transferred), src_size - transferred)); + + transferred += cur; + } + + R_SUCCEED(); + } + + Result UsbDriver::Receive(void *dst, int dst_size) { + /* Check size. */ + R_UNLESS(dst_size >= 0, htclow::ResultInvalidArgument()); + + /* Send until we've sent everything. */ + for (auto transferred = 0; transferred < dst_size; /* ... */) { + int cur; + R_TRY(ReceiveUsb(std::addressof(cur), reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(dst) + transferred), dst_size - transferred)); + + transferred += cur; + } + + R_SUCCEED(); + } + + void UsbDriver::CancelSendReceive() { + CancelUsbSendReceive(); + } + + void UsbDriver::Suspend() { + this->Close(); + } + + void UsbDriver::Resume() { + R_ABORT_UNLESS(this->Open()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_usb_driver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_usb_driver.hpp new file mode 100644 index 00000000..6ebc921f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_usb_driver.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htclow_i_driver.hpp" +#include "htclow_usb_impl.hpp" + +namespace ams::htclow::driver { + + class UsbDriver final : public IDriver { + private: + bool m_connected; + os::Event m_event; + public: + UsbDriver() : m_connected(false), m_event(os::EventClearMode_ManualClear) { /* ... */ } + public: + static void OnUsbAvailabilityChange(UsbAvailability availability, void *param); + public: + virtual Result Open() override; + virtual void Close() override; + virtual Result Connect(os::EventType *event) override; + virtual void Shutdown() override; + virtual Result Send(const void *src, int src_size) override; + virtual Result Receive(void *dst, int dst_size) override; + virtual void CancelSendReceive() override; + virtual void Suspend() override; + virtual void Resume() override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_usb_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_usb_impl.cpp new file mode 100644 index 00000000..24aac8f6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_usb_impl.cpp @@ -0,0 +1,462 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_usb_impl.hpp" +#include "htclow_driver_memory_management.hpp" + +namespace ams::htclow::driver { + + namespace { + + /* TODO: Should we identify differently than Nintendo does? */ + /* It's kind of silly to identify as "NintendoSDK DevKit", but it's also kind of amusing. */ + /* TBD */ + + constinit usb::UsbStringDescriptor LanguageStringDescriptor = { 4, usb::UsbDescriptorType_String, {0x0409}}; + constinit usb::UsbStringDescriptor ManufacturerStringDescriptor = {18, usb::UsbDescriptorType_String, {'N', 'i', 'n', 't', 'e', 'n', 'd', 'o'}}; + constinit usb::UsbStringDescriptor ProductStringFullSpeedDescriptor = {38, usb::UsbDescriptorType_String, {'N', 'i', 'n', 't', 'e', 'n', 'd', 'o', 'S', 'D', 'K', ' ', 'D', 'e', 'v', 'K', 'i', 't'}}; + constinit usb::UsbStringDescriptor SerialNumberStringDescriptor = { 0, usb::UsbDescriptorType_String, {}}; + constinit usb::UsbStringDescriptor InterfaceStringDescriptor = {16, usb::UsbDescriptorType_String, {'h', 't', 'c', ' ', 'u', 's', 'b'}}; + + constinit usb::UsbDeviceDescriptor UsbDeviceDescriptorFullSpeed = { + .bLength = usb::UsbDescriptorSize_Device, + .bDescriptorType = usb::UsbDescriptorType_Device, + .bcdUSB = 0x0110, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 0x40, + .idVendor = 0x057E, + .idProduct = 0x3005, + .bcdDevice = 0x0100, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + .bNumConfigurations = 0x01, + }; + + constinit usb::UsbDeviceDescriptor UsbDeviceDescriptorHighSpeed = { + .bLength = usb::UsbDescriptorSize_Device, + .bDescriptorType = usb::UsbDescriptorType_Device, + .bcdUSB = 0x0200, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 0x40, + .idVendor = 0x057E, + .idProduct = 0x3005, + .bcdDevice = 0x0100, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + .bNumConfigurations = 0x01, + }; + + constinit usb::UsbDeviceDescriptor UsbDeviceDescriptorSuperSpeed = { + .bLength = usb::UsbDescriptorSize_Device, + .bDescriptorType = usb::UsbDescriptorType_Device, + .bcdUSB = 0x0300, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 0x09, + .idVendor = 0x057E, + .idProduct = 0x3005, + .bcdDevice = 0x0100, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + .bNumConfigurations = 0x01, + }; + + constinit u8 BinaryObjectStore[] = { + 0x05, + usb::UsbDescriptorType_Bos, + 0x16, 0x00, + 0x02, + + 0x07, + usb::UsbDescriptorType_DeviceCapability, + 0x02, + 0x02, 0x00, 0x00, 0x00, + + 0x0A, + usb::UsbDescriptorType_DeviceCapability, + 0x03, + 0x00, + 0x0E, 0x00, + 0x03, + 0x00, + 0x00, 0x00, + }; + + constinit usb::UsbInterfaceDescriptor UsbInterfaceDescriptor = { + .bLength = usb::UsbDescriptorSize_Interface, + .bDescriptorType = usb::UsbDescriptorType_Interface, + .bInterfaceNumber = 0x00, + .bAlternateSetting = 0x00, + .bNumEndpoints = 0x02, + .bInterfaceClass = 0xFF, + .bInterfaceSubClass = 0xFF, + .bInterfaceProtocol = 0xFF, + .iInterface = 0x04, + }; + + constinit usb::UsbEndpointDescriptor UsbEndpointDescriptorsFullSpeed[2] = { + { + .bLength = usb::UsbDescriptorSize_Endpoint, + .bDescriptorType = usb::UsbDescriptorType_Endpoint, + .bEndpointAddress = 0x81, + .bmAttributes = usb::UsbEndpointAttributeMask_XferTypeBulk, + .wMaxPacketSize = 0x40, + .bInterval = 0x00, + }, + { + .bLength = usb::UsbDescriptorSize_Endpoint, + .bDescriptorType = usb::UsbDescriptorType_Endpoint, + .bEndpointAddress = 0x01, + .bmAttributes = usb::UsbEndpointAttributeMask_XferTypeBulk, + .wMaxPacketSize = 0x40, + .bInterval = 0x00, + } + }; + + constinit usb::UsbEndpointDescriptor UsbEndpointDescriptorsHighSpeed[2] = { + { + .bLength = usb::UsbDescriptorSize_Endpoint, + .bDescriptorType = usb::UsbDescriptorType_Endpoint, + .bEndpointAddress = 0x81, + .bmAttributes = usb::UsbEndpointAttributeMask_XferTypeBulk, + .wMaxPacketSize = 0x200, + .bInterval = 0x00, + }, + { + .bLength = usb::UsbDescriptorSize_Endpoint, + .bDescriptorType = usb::UsbDescriptorType_Endpoint, + .bEndpointAddress = 0x01, + .bmAttributes = usb::UsbEndpointAttributeMask_XferTypeBulk, + .wMaxPacketSize = 0x200, + .bInterval = 0x00, + } + }; + + constinit usb::UsbEndpointDescriptor UsbEndpointDescriptorsSuperSpeed[2] = { + { + .bLength = usb::UsbDescriptorSize_Endpoint, + .bDescriptorType = usb::UsbDescriptorType_Endpoint, + .bEndpointAddress = 0x81, + .bmAttributes = usb::UsbEndpointAttributeMask_XferTypeBulk, + .wMaxPacketSize = 0x400, + .bInterval = 0x00, + }, + { + .bLength = usb::UsbDescriptorSize_Endpoint, + .bDescriptorType = usb::UsbDescriptorType_Endpoint, + .bEndpointAddress = 0x01, + .bmAttributes = usb::UsbEndpointAttributeMask_XferTypeBulk, + .wMaxPacketSize = 0x400, + .bInterval = 0x00, + } + }; + + constinit usb::UsbEndpointCompanionDescriptor UsbEndpointCompanionDescriptor = { + .bLength = usb::UsbDescriptorSize_EndpointCompanion, + .bDescriptorType = usb::UsbDescriptorType_EndpointCompanion, + .bMaxBurst = 0x0F, + .bmAttributes = 0x00, + .wBytesPerInterval = 0x0000, + }; + + constinit void *g_usb_receive_buffer = nullptr; + constinit void *g_usb_send_buffer = nullptr; + + constinit UsbAvailabilityChangeCallback g_availability_change_callback = nullptr; + constinit void *g_availability_change_param = nullptr; + + constinit bool g_usb_interface_initialized = false; + + os::Event g_usb_break_event(os::EventClearMode_ManualClear); + + constinit os::ThreadType g_usb_indication_thread = {}; + + constinit os::SdkMutex g_usb_driver_mutex; + + usb::DsClient g_ds_client; + usb::DsInterface g_ds_interface; + usb::DsEndpoint g_ds_endpoints[2]; + + void InvokeAvailabilityChangeCallback(UsbAvailability availability) { + if (g_availability_change_callback) { + g_availability_change_callback(availability, g_availability_change_param); + } + } + + Result ConvertUsbDriverResult(Result result) { + if (result.GetModule() == R_NAMESPACE_MODULE_ID(usb)) { + if (usb::ResultResourceBusy::Includes(result)) { + R_THROW(htclow::ResultUsbDriverBusyError()); + } else if (usb::ResultMemAllocFailure::Includes(result)) { + R_THROW(htclow::ResultOutOfMemory()); + } else { + R_THROW(htclow::ResultUsbDriverUnknownError()); + } + } else { + R_RETURN(result); + } + } + + Result InitializeDsClient() { + /* Initialize the client. */ + R_TRY(ConvertUsbDriverResult(g_ds_client.Initialize(usb::ComplexId_Tegra21x))); + + /* Clear device data. */ + R_ABORT_UNLESS(g_ds_client.ClearDeviceData()); + + /* Add string descriptors. */ + u8 index; + R_TRY(g_ds_client.AddUsbStringDescriptor(std::addressof(index), std::addressof(LanguageStringDescriptor))); + R_TRY(g_ds_client.AddUsbStringDescriptor(std::addressof(index), std::addressof(ManufacturerStringDescriptor))); + R_TRY(g_ds_client.AddUsbStringDescriptor(std::addressof(index), std::addressof(ProductStringFullSpeedDescriptor))); + R_TRY(g_ds_client.AddUsbStringDescriptor(std::addressof(index), std::addressof(SerialNumberStringDescriptor))); + R_TRY(g_ds_client.AddUsbStringDescriptor(std::addressof(index), std::addressof(InterfaceStringDescriptor))); + + /* Add device descriptors. */ + R_TRY(g_ds_client.SetUsbDeviceDescriptor(std::addressof(UsbDeviceDescriptorFullSpeed), usb::UsbDeviceSpeed_Full)); + R_TRY(g_ds_client.SetUsbDeviceDescriptor(std::addressof(UsbDeviceDescriptorHighSpeed), usb::UsbDeviceSpeed_High)); + R_TRY(g_ds_client.SetUsbDeviceDescriptor(std::addressof(UsbDeviceDescriptorSuperSpeed), usb::UsbDeviceSpeed_Super)); + + /* Set binary object store. */ + R_TRY(g_ds_client.SetBinaryObjectStore(BinaryObjectStore, sizeof(BinaryObjectStore))); + + R_SUCCEED(); + } + + Result InitializeDsInterface() { + /* Initialize the interface. */ + R_TRY(ConvertUsbDriverResult(g_ds_interface.Initialize(std::addressof(g_ds_client), 0))); + + /* Append the interface descriptors for all speeds. */ + R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_Full, std::addressof(UsbInterfaceDescriptor), sizeof(usb::UsbInterfaceDescriptor))); + R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_Full, std::addressof(UsbEndpointDescriptorsFullSpeed[0]), sizeof(usb::UsbEndpointDescriptor))); + R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_Full, std::addressof(UsbEndpointDescriptorsFullSpeed[1]), sizeof(usb::UsbEndpointDescriptor))); + + R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_High, std::addressof(UsbInterfaceDescriptor), sizeof(usb::UsbInterfaceDescriptor))); + R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_High, std::addressof(UsbEndpointDescriptorsHighSpeed[0]), sizeof(usb::UsbEndpointDescriptor))); + R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_High, std::addressof(UsbEndpointDescriptorsHighSpeed[1]), sizeof(usb::UsbEndpointDescriptor))); + + R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_Super, std::addressof(UsbInterfaceDescriptor), sizeof(usb::UsbInterfaceDescriptor))); + R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_Super, std::addressof(UsbEndpointDescriptorsSuperSpeed[0]), sizeof(usb::UsbEndpointDescriptor))); + R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_Super, std::addressof(UsbEndpointCompanionDescriptor), sizeof(usb::UsbEndpointCompanionDescriptor))); + R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_Super, std::addressof(UsbEndpointDescriptorsSuperSpeed[1]), sizeof(usb::UsbEndpointDescriptor))); + R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_Super, std::addressof(UsbEndpointCompanionDescriptor), sizeof(usb::UsbEndpointCompanionDescriptor))); + + R_SUCCEED(); + } + + Result InitializeDsEndpoints() { + R_TRY(g_ds_endpoints[0].Initialize(std::addressof(g_ds_interface), 0x81)); + R_TRY(g_ds_endpoints[1].Initialize(std::addressof(g_ds_interface), 0x01)); + R_SUCCEED(); + } + + void UsbIndicationThreadFunction(void *) { + /* Get the state change event. */ + os::SystemEventType *state_change_event = g_ds_client.GetStateChangeEvent(); + + /* Setup multi wait. */ + os::MultiWaitType multi_wait; + os::InitializeMultiWait(std::addressof(multi_wait)); + + /* Link multi wait holders. */ + os::MultiWaitHolderType state_change_holder; + os::MultiWaitHolderType break_holder; + os::InitializeMultiWaitHolder(std::addressof(state_change_holder), state_change_event); + os::LinkMultiWaitHolder(std::addressof(multi_wait), std::addressof(state_change_holder)); + os::InitializeMultiWaitHolder(std::addressof(break_holder), g_usb_break_event.GetBase()); + os::LinkMultiWaitHolder(std::addressof(multi_wait), std::addressof(break_holder)); + + /* Loop forever. */ + while (true) { + /* If we should break, do so. */ + if (os::WaitAny(std::addressof(multi_wait)) == std::addressof(break_holder)) { + break; + } + + /* Clear the state change event. */ + os::ClearSystemEvent(state_change_event); + + /* Get the new state. */ + usb::UsbState usb_state; + R_ABORT_UNLESS(g_ds_client.GetState(std::addressof(usb_state))); + + switch (usb_state) { + case usb::UsbState_Detached: + case usb::UsbState_Suspended: + InvokeAvailabilityChangeCallback(UsbAvailability_Unavailable); + break; + case usb::UsbState_Configured: + InvokeAvailabilityChangeCallback(UsbAvailability_Available); + break; + default: + /* Nothing to do. */ + break; + } + } + + /* Clear the break event. */ + g_usb_break_event.Clear(); + + /* Unlink all holders. */ + os::UnlinkAllMultiWaitHolder(std::addressof(multi_wait)); + + /* Finalize the multi wait/holders. */ + os::FinalizeMultiWaitHolder(std::addressof(break_holder)); + os::FinalizeMultiWaitHolder(std::addressof(state_change_holder)); + os::FinalizeMultiWait(std::addressof(multi_wait)); + } + + } + + void SetUsbAvailabilityChangeCallback(UsbAvailabilityChangeCallback callback, void *param) { + g_availability_change_callback = callback; + g_availability_change_param = param; + } + + void ClearUsbAvailabilityChangeCallback() { + g_availability_change_callback = nullptr; + g_availability_change_param = nullptr; + } + + Result InitializeUsbInterface() { + /* Set the interface as initialized. */ + g_usb_interface_initialized = true; + + /* Get the dma buffers. */ + g_usb_receive_buffer = GetUsbReceiveBuffer(); + g_usb_send_buffer = GetUsbSendBuffer(); + + /* If we fail somewhere, finalize. */ + auto init_guard = SCOPE_GUARD { FinalizeUsbInterface(); }; + + /* Get the serial number. */ + { + settings::factory::SerialNumber serial_number; + serial_number.str[0] = '\x00'; + + if (R_FAILED(settings::factory::GetSerialNumber(std::addressof(serial_number))) || serial_number.str[0] == '\x00') { + std::strcpy(serial_number.str, "Corrupted S/N"); + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Waddress-of-packed-member" + + u16 *dst = SerialNumberStringDescriptor.wData; + u8 *src = reinterpret_cast<u8 *>(serial_number.str); + u8 count = 0; + +#pragma GCC diagnostic pop + + while (*src) { + *dst++ = static_cast<u16>(*src++); + ++count; + } + + SerialNumberStringDescriptor.bLength = 2 + (2 * count); + } + + /* Initialize the client. */ + R_TRY(InitializeDsClient()); + + /* Initialize the interface. */ + R_TRY(InitializeDsInterface()); + + /* Initialize the endpoints. */ + R_TRY(ConvertUsbDriverResult(InitializeDsEndpoints())); + + /* Create the indication thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(g_usb_indication_thread), &UsbIndicationThreadFunction, nullptr, GetUsbIndicationThreadStack(), UsbIndicationThreadStackSize, AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtclowUsbIndication))); + + /* Set the thread name. */ + os::SetThreadNamePointer(std::addressof(g_usb_indication_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtclowUsbIndication)); + + /* Start the indication thread. */ + os::StartThread(std::addressof(g_usb_indication_thread)); + + /* Enable the usb device. */ + R_TRY(g_ds_client.EnableDevice()); + + /* We succeeded! */ + init_guard.Cancel(); + R_SUCCEED(); + } + + void FinalizeUsbInterface() { + g_usb_break_event.Signal(); + os::WaitThread(std::addressof(g_usb_indication_thread)); + os::DestroyThread(std::addressof(g_usb_indication_thread)); + g_ds_client.DisableDevice(); + g_ds_endpoints[1].Finalize(); + g_ds_endpoints[0].Finalize(); + g_ds_interface.Finalize(); + g_ds_client.Finalize(); + g_usb_interface_initialized = false; + } + + Result SendUsb(int *out_transferred, const void *src, int src_size) { + /* Acquire exclusive access to the driver. */ + std::scoped_lock lk(g_usb_driver_mutex); + + /* Check that we can send the data. */ + R_UNLESS(src_size <= static_cast<int>(UsbDmaBufferSize), htclow::ResultInvalidArgument()); + + /* Copy the data to the dma buffer. */ + std::memcpy(g_usb_send_buffer, src, src_size); + + /* Transfer data. */ + u32 transferred; + R_UNLESS(R_SUCCEEDED(g_ds_endpoints[0].PostBuffer(std::addressof(transferred), g_usb_send_buffer, src_size)), htclow::ResultUsbDriverSendError()); + R_UNLESS(transferred == static_cast<u32>(src_size), htclow::ResultUsbDriverSendError()); + + /* Set output transferred size. */ + *out_transferred = src_size; + R_SUCCEED(); + } + + Result ReceiveUsb(int *out_transferred, void *dst, int dst_size) { + /* Check that we can send the data. */ + R_UNLESS(dst_size <= static_cast<int>(UsbDmaBufferSize), htclow::ResultInvalidArgument()); + + /* Transfer data. */ + u32 transferred; + R_UNLESS(R_SUCCEEDED(g_ds_endpoints[1].PostBuffer(std::addressof(transferred), g_usb_receive_buffer, dst_size)), htclow::ResultUsbDriverReceiveError()); + R_UNLESS(transferred == static_cast<u32>(dst_size), htclow::ResultUsbDriverReceiveError()); + + /* Copy the data. */ + std::memcpy(dst, g_usb_receive_buffer, dst_size); + + /* Set output transferred size. */ + *out_transferred = dst_size; + R_SUCCEED(); + } + + void CancelUsbSendReceive() { + if (g_usb_interface_initialized) { + g_ds_endpoints[0].Cancel(); + g_ds_endpoints[1].Cancel(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_usb_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_usb_impl.hpp new file mode 100644 index 00000000..48bac5ab --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/driver/htclow_usb_impl.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htclow::driver { + + enum UsbAvailability { + UsbAvailability_Unavailable = 1, + UsbAvailability_Available = 2, + UsbAvailability_Unknown = 3, + }; + + using UsbAvailabilityChangeCallback = void (*)(UsbAvailability availability, void *param); + + void SetUsbAvailabilityChangeCallback(UsbAvailabilityChangeCallback callback, void *param); + void ClearUsbAvailabilityChangeCallback(); + + Result InitializeUsbInterface(); + void FinalizeUsbInterface(); + + Result SendUsb(int *out_transferred, const void *src, int src_size); + Result ReceiveUsb(int *out_transferred, void *dst, int dst_size); + + void CancelUsbSendReceive(); + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_channel.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_channel.cpp new file mode 100644 index 00000000..464fe47e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_channel.cpp @@ -0,0 +1,235 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_channel.hpp" + +namespace ams::htclow { + + Result Channel::Open(const Module *module, ChannelId id) { + /* Check we're not already initialized. */ + AMS_ASSERT(!module->GetBase()->_is_initialized); + + /* Set our channel type. */ + m_channel = { + ._is_initialized = true, + ._module_id = module->GetBase()->_id, + ._channel_id = id, + }; + + /* Open the channel. */ + R_RETURN(m_manager->Open(impl::ConvertChannelType(m_channel))); + } + + void Channel::Close() { + m_manager->Close(impl::ConvertChannelType(m_channel)); + } + + ChannelState Channel::GetChannelState() { + return m_manager->GetChannelState(impl::ConvertChannelType(m_channel)); + } + + os::EventType *Channel::GetChannelStateEvent() { + return m_manager->GetChannelStateEvent(impl::ConvertChannelType(m_channel)); + } + + Result Channel::Connect() { + const auto channel = impl::ConvertChannelType(m_channel); + + /* Begin the flush. */ + u32 task_id{}; + R_TRY(m_manager->ConnectBegin(std::addressof(task_id), channel)); + + /* Wait for the task to finish. */ + this->WaitEvent(m_manager->GetTaskEvent(task_id), false); + + /* End the flush. */ + R_RETURN(m_manager->ConnectEnd(channel, task_id)); + } + + Result Channel::Flush() { + /* Begin the flush. */ + u32 task_id{}; + R_TRY(m_manager->FlushBegin(std::addressof(task_id), impl::ConvertChannelType(m_channel))); + + /* Wait for the task to finish. */ + this->WaitEvent(m_manager->GetTaskEvent(task_id), true); + + /* End the flush. */ + R_RETURN(m_manager->FlushEnd(task_id)); + } + + void Channel::Shutdown() { + m_manager->Shutdown(impl::ConvertChannelType(m_channel)); + } + + Result Channel::Receive(s64 *out, void *dst, s64 size, ReceiveOption option) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(util::IsIntValueRepresentable<size_t>(size)); + + /* Determine the minimum allowable receive size. */ + s64 min_size; + switch (option) { + case htclow::ReceiveOption_NonBlocking: min_size = 0; break; + case htclow::ReceiveOption_ReceiveAnyData: min_size = 1; break; + case htclow::ReceiveOption_ReceiveAllData: min_size = size; break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Repeatedly receive. */ + s64 received = 0; + do { + s64 cur_received; + const Result result = this->ReceiveInternal(std::addressof(cur_received), static_cast<u8 *>(dst) + received, size - received, option); + + if (R_FAILED(result)) { + if (htclow::ResultChannelReceiveBufferEmpty::Includes(result)) { + R_UNLESS(option != htclow::ReceiveOption_NonBlocking, htclow::ResultNonBlockingReceiveFailed()); + } + if (htclow::ResultChannelNotExist::Includes(result)) { + *out = received; + } + R_RETURN(result); + } + + received += static_cast<size_t>(cur_received); + } while (received < min_size); + + /* Set output size. */ + AMS_ASSERT(received <= size); + *out = received; + + R_SUCCEED(); + } + + Result Channel::Send(s64 *out, const void *src, s64 size) { + /* Check pre-conditions. */ + AMS_ASSERT(util::IsIntValueRepresentable<size_t>(size)); + + /* Convert channel. */ + const auto channel = impl::ConvertChannelType(m_channel); + + /* Loop sending. */ + s64 total_sent; + size_t cur_sent; + for (total_sent = 0; total_sent < size; total_sent += cur_sent) { + AMS_ASSERT(util::IsIntValueRepresentable<size_t>(size - total_sent)); + + /* Begin the send. */ + u32 task_id{}; + const auto begin_result = m_manager->SendBegin(std::addressof(task_id), std::addressof(cur_sent), static_cast<const u8 *>(src) + total_sent, size - total_sent, channel); + if (R_FAILED(begin_result)) { + if (total_sent != 0) { + break; + } else { + R_RETURN(begin_result); + } + } + + /* Wait for the task to finish. */ + this->WaitEvent(m_manager->GetTaskEvent(task_id), true); + + /* Finish the send. */ + R_ABORT_UNLESS(m_manager->SendEnd(task_id)); + } + + /* Set output size. */ + AMS_ASSERT(total_sent <= size); + *out = total_sent; + + R_SUCCEED(); + } + + void Channel::SetConfig(const ChannelConfig &config) { + m_manager->SetConfig(impl::ConvertChannelType(m_channel), config); + } + + void Channel::SetReceiveBuffer(void *buf, size_t size) { + m_manager->SetReceiveBuffer(impl::ConvertChannelType(m_channel), buf, size); + } + + void Channel::SetSendBuffer(void *buf, size_t size) { + m_manager->SetSendBuffer(impl::ConvertChannelType(m_channel), buf, size); + } + + void Channel::SetSendBufferWithData(const void *buf, size_t size) { + m_manager->SetSendBufferWithData(impl::ConvertChannelType(m_channel), buf, size); + } + + Result Channel::WaitReceive(s64 size) { + /* Check pre-conditions. */ + AMS_ASSERT(util::IsIntValueRepresentable<size_t>(size)); + + R_RETURN(this->WaitReceiveInternal(size, nullptr)); + } + + Result Channel::WaitReceive(s64 size, os::EventType *event) { + /* Check pre-conditions. */ + AMS_ASSERT(util::IsIntValueRepresentable<size_t>(size)); + AMS_ASSERT(event != nullptr); + + R_RETURN(this->WaitReceiveInternal(size, event)); + } + + void Channel::WaitEvent(os::EventType *event, bool) { + return os::WaitEvent(event); + } + + Result Channel::ReceiveInternal(s64 *out, void *dst, s64 size, ReceiveOption option) { + const auto channel = impl::ConvertChannelType(m_channel); + const bool blocking = option != ReceiveOption_NonBlocking; + + /* Begin the receive. */ + u32 task_id{}; + R_TRY(m_manager->ReceiveBegin(std::addressof(task_id), channel, blocking ? 1 : 0)); + + /* Wait for the task to finish. */ + this->WaitEvent(m_manager->GetTaskEvent(task_id), false); + + /* Receive the data. */ + size_t received; + AMS_ASSERT(util::IsIntValueRepresentable<size_t>(size)); + R_TRY(m_manager->ReceiveEnd(std::addressof(received), dst, static_cast<size_t>(size), channel, task_id)); + + /* Set the output size. */ + AMS_ASSERT(util::IsIntValueRepresentable<s64>(received)); + *out = static_cast<s64>(received); + + R_SUCCEED(); + } + + Result Channel::WaitReceiveInternal(s64 size, os::EventType *event) { + const auto channel = impl::ConvertChannelType(m_channel); + + /* Begin the wait. */ + u32 task_id{}; + R_TRY(m_manager->WaitReceiveBegin(std::addressof(task_id), channel, size)); + + + /* Perform the wait. */ + if (event != nullptr) { + if (os::WaitAny(event, m_manager->GetTaskEvent(task_id)) == 0) { + m_manager->WaitReceiveEnd(task_id); + R_THROW(htclow::ResultChannelWaitCancelled()); + } + } else { + this->WaitEvent(m_manager->GetTaskEvent(task_id), false); + } + + /* End the wait. */ + R_RETURN(m_manager->WaitReceiveEnd(task_id)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_channel.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_channel.hpp new file mode 100644 index 00000000..7de3f123 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_channel.hpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htclow_manager.hpp" + +namespace ams::htclow { + + class Channel final { + private: + HtclowManager *m_manager; + ChannelType m_channel; + public: + constexpr Channel(HtclowManager *manager) : m_manager(manager), m_channel() { /* ... */ } + constexpr ~Channel() { m_channel = {}; } + + ChannelType *GetBase() { return std::addressof(m_channel); } + public: + Result Open(const Module *module, ChannelId id); + void Close(); + + ChannelState GetChannelState(); + os::EventType *GetChannelStateEvent(); + + Result Connect(); + Result Flush(); + void Shutdown(); + + Result Receive(s64 *out, void *dst, s64 size, ReceiveOption option); + Result Send(s64 *out, const void *src, s64 size); + + void SetConfig(const ChannelConfig &config); + void SetReceiveBuffer(void *buf, size_t size); + void SetSendBuffer(void *buf, size_t size); + void SetSendBufferWithData(const void *buf, size_t size); + + Result WaitReceive(s64 size); + Result WaitReceive(s64 size, os::EventType *event); + private: + void WaitEvent(os::EventType *event, bool); + Result ReceiveInternal(s64 *out, void *dst, s64 size, ReceiveOption option); + Result WaitReceiveInternal(s64 size, os::EventType *event); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_default_channel_config.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_default_channel_config.hpp new file mode 100644 index 00000000..e403b37d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_default_channel_config.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htclow_packet.hpp" + +namespace ams::htclow { + + constexpr inline const ChannelConfig DefaultChannelConfig = { + .flow_control_enabled = true, + .handshake_enabled = true, + .initial_counter_max_data = 0, + .max_packet_size = 0xE000 + sizeof(PacketHeader), + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_listener.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_listener.cpp new file mode 100644 index 00000000..bb7e7367 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_listener.cpp @@ -0,0 +1,118 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_listener.hpp" + +namespace ams::htclow { + + namespace { + + constexpr inline size_t ThreadStackSize = 4_KB; + + } + + Listener::Listener(mem::StandardAllocator *allocator, mux::Mux *mux, ctrl::HtcctrlService *ctrl_srv, Worker *worker) + : m_thread_stack_size(ThreadStackSize), m_allocator(allocator), m_mux(mux), m_service(ctrl_srv), m_worker(worker), m_event(os::EventClearMode_ManualClear), m_driver(nullptr), m_thread_running(false), m_cancelled(false) + { + /* Allocate stack. */ + m_listen_thread_stack = m_allocator->Allocate(m_thread_stack_size, os::ThreadStackAlignment); + } + + void Listener::Start(driver::IDriver *driver) { + /* Check pre-conditions. */ + AMS_ASSERT(!m_thread_running); + + /* Create the thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_listen_thread), ListenThreadEntry, this, m_listen_thread_stack, ThreadStackSize, AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtclowListen))); + + /* Set the thread name. */ + os::SetThreadNamePointer(std::addressof(m_listen_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtclowListen)); + + /* Set our driver. */ + m_driver = driver; + + /* Set state. */ + m_thread_running = true; + m_cancelled = false; + + /* Clear our event. */ + m_event.Clear(); + + /* Start the thread. */ + os::StartThread(std::addressof(m_listen_thread)); + } + + void Listener::Cancel() { + /* Mark ourselves as cancelled. */ + m_cancelled = true; + + /* Cancel our worker. */ + m_worker->Cancel(); + + /* Signal our event. */ + m_event.Signal(); + + /* Cancel our driver. */ + m_driver->CancelSendReceive(); + } + + void Listener::Wait() { + /* Wait for our listen thread to exit. */ + os::WaitThread(std::addressof(m_listen_thread)); + + /* Destroy our listen thread. */ + os::DestroyThread(std::addressof(m_listen_thread)); + + /* Clear our driver. */ + m_driver = nullptr; + + /* Mark our thread as not running. */ + m_thread_running = false; + } + + void Listener::ListenThread() { + /* Check pre-conditions. */ + AMS_ASSERT(m_driver != nullptr); + AMS_ASSERT(m_worker != nullptr); + + /* Set the worker's driver. */ + m_worker->SetDriver(m_driver); + + /* Loop forever, while we're not cancelled. */ + while (!m_cancelled) { + /* Connect. */ + if (R_FAILED(m_driver->Connect(m_event.GetBase()))) { + return; + } + + /* Notify that we're connected. */ + R_ABORT_UNLESS(m_service->NotifyDriverConnected()); + + /* Start the worker. */ + m_worker->Start(); + + /* Wait for the worker to end. */ + m_worker->Wait(); + + /* Shutdown the driver. */ + m_driver->Shutdown(); + + /* Notify that we're disconnected. */ + R_ABORT_UNLESS(m_service->NotifyDriverDisconnected()); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_listener.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_listener.hpp new file mode 100644 index 00000000..0c659566 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_listener.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htclow_worker.hpp" + +namespace ams::htclow { + + class Listener { + private: + u32 m_thread_stack_size; + mem::StandardAllocator *m_allocator; + mux::Mux *m_mux; + ctrl::HtcctrlService *m_service; + Worker *m_worker; + os::Event m_event; + os::ThreadType m_listen_thread; + void *m_listen_thread_stack; + driver::IDriver *m_driver; + bool m_thread_running; + bool m_cancelled; + private: + static void ListenThreadEntry(void *arg) { + static_cast<Listener *>(arg)->ListenThread(); + } + + void ListenThread(); + public: + Listener(mem::StandardAllocator *allocator, mux::Mux *mux, ctrl::HtcctrlService *ctrl_srv, Worker *worker); + + void Start(driver::IDriver *driver); + void Cancel(); + void Wait(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_manager.cpp new file mode 100644 index 00000000..01b9ee38 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_manager.cpp @@ -0,0 +1,147 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_manager.hpp" +#include "htclow_manager_impl.hpp" + +namespace ams::htclow { + + HtclowManager::HtclowManager(mem::StandardAllocator *allocator) : m_allocator(allocator), m_impl(static_cast<HtclowManagerImpl *>(allocator->Allocate(sizeof(HtclowManagerImpl), alignof(HtclowManagerImpl)))) { + std::construct_at(m_impl, m_allocator); + } + + HtclowManager::~HtclowManager() { + std::destroy_at(m_impl); + m_allocator->Free(m_impl); + } + + Result HtclowManager::OpenDriver(impl::DriverType driver_type) { + R_RETURN(m_impl->OpenDriver(driver_type)); + } + + void HtclowManager::CloseDriver() { + return m_impl->CloseDriver(); + } + + Result HtclowManager::Open(impl::ChannelInternalType channel) { + R_RETURN(m_impl->Open(channel)); + } + + Result HtclowManager::Close(impl::ChannelInternalType channel) { + R_RETURN(m_impl->Close(channel)); + } + + void HtclowManager::Resume() { + return m_impl->Resume(); + } + + void HtclowManager::Suspend() { + return m_impl->Suspend(); + } + + Result HtclowManager::ConnectBegin(u32 *out_task_id, impl::ChannelInternalType channel) { + R_RETURN(m_impl->ConnectBegin(out_task_id, channel)); + } + + Result HtclowManager::ConnectEnd(impl::ChannelInternalType channel, u32 task_id) { + R_RETURN(m_impl->ConnectEnd(channel, task_id)); + } + + void HtclowManager::Disconnect() { + return m_impl->Disconnect(); + } + + Result HtclowManager::FlushBegin(u32 *out_task_id, impl::ChannelInternalType channel) { + R_RETURN(m_impl->FlushBegin(out_task_id, channel)); + } + + Result HtclowManager::FlushEnd(u32 task_id) { + R_RETURN(m_impl->FlushEnd(task_id)); + } + + ChannelState HtclowManager::GetChannelState(impl::ChannelInternalType channel) { + return m_impl->GetChannelState(channel); + } + + os::EventType *HtclowManager::GetChannelStateEvent(impl::ChannelInternalType channel) { + return m_impl->GetChannelStateEvent(channel); + } + + impl::DriverType HtclowManager::GetDriverType() { + return m_impl->GetDriverType(); + } + + os::EventType *HtclowManager::GetTaskEvent(u32 task_id) { + return m_impl->GetTaskEvent(task_id); + } + + void HtclowManager::NotifyAsleep() { + return m_impl->NotifyAsleep(); + } + + void HtclowManager::NotifyAwake() { + return m_impl->NotifyAwake(); + } + + Result HtclowManager::ReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size) { + R_RETURN(m_impl->ReceiveBegin(out_task_id, channel, size)); + } + + Result HtclowManager::ReceiveEnd(size_t *out, void *dst, size_t dst_size, impl::ChannelInternalType channel, u32 task_id) { + R_RETURN(m_impl->ReceiveEnd(out, dst, dst_size, channel, task_id)); + } + + Result HtclowManager::SendBegin(u32 *out_task_id, size_t *out, const void *src, size_t src_size, impl::ChannelInternalType channel) { + R_RETURN(m_impl->SendBegin(out_task_id, out, src, src_size, channel)); + } + + Result HtclowManager::SendEnd(u32 task_id) { + R_RETURN(m_impl->SendEnd(task_id)); + } + + Result HtclowManager::WaitReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size) { + R_RETURN(m_impl->WaitReceiveBegin(out_task_id, channel, size)); + } + + Result HtclowManager::WaitReceiveEnd(u32 task_id) { + R_RETURN(m_impl->WaitReceiveEnd(task_id)); + } + + void HtclowManager::SetConfig(impl::ChannelInternalType channel, const ChannelConfig &config) { + return m_impl->SetConfig(channel, config); + } + + void HtclowManager::SetDebugDriver(driver::IDriver *driver) { + return m_impl->SetDebugDriver(driver); + } + + void HtclowManager::SetReceiveBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size) { + return m_impl->SetReceiveBuffer(channel, buf, buf_size); + } + + void HtclowManager::SetSendBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size) { + return m_impl->SetSendBuffer(channel, buf, buf_size); + } + + void HtclowManager::SetSendBufferWithData(impl::ChannelInternalType channel, const void *buf, size_t buf_size) { + return m_impl->SetSendBufferWithData(channel, buf, buf_size); + } + + Result HtclowManager::Shutdown(impl::ChannelInternalType channel) { + R_RETURN(m_impl->Shutdown(channel)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_manager.hpp new file mode 100644 index 00000000..74470aaa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_manager.hpp @@ -0,0 +1,79 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "driver/htclow_i_driver.hpp" + +namespace ams::htclow { + + class HtclowManagerImpl; + + class HtclowManager { + private: + mem::StandardAllocator *m_allocator; + HtclowManagerImpl *m_impl; + public: + HtclowManager(mem::StandardAllocator *allocator); + ~HtclowManager(); + public: + Result OpenDriver(impl::DriverType driver_type); + void CloseDriver(); + + Result Open(impl::ChannelInternalType channel); + Result Close(impl::ChannelInternalType channel); + + void Resume(); + void Suspend(); + + Result ConnectBegin(u32 *out_task_id, impl::ChannelInternalType channel); + Result ConnectEnd(impl::ChannelInternalType channel, u32 task_id); + + void Disconnect(); + + Result FlushBegin(u32 *out_task_id, impl::ChannelInternalType channel); + Result FlushEnd(u32 task_id); + + ChannelState GetChannelState(impl::ChannelInternalType channel); + os::EventType *GetChannelStateEvent(impl::ChannelInternalType channel); + + impl::DriverType GetDriverType(); + + os::EventType *GetTaskEvent(u32 task_id); + + void NotifyAsleep(); + void NotifyAwake(); + + Result ReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size); + Result ReceiveEnd(size_t *out, void *dst, size_t dst_size, impl::ChannelInternalType channel, u32 task_id); + + Result SendBegin(u32 *out_task_id, size_t *out, const void *src, size_t src_size, impl::ChannelInternalType channel); + Result SendEnd(u32 task_id); + + Result WaitReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size); + Result WaitReceiveEnd(u32 task_id); + + void SetConfig(impl::ChannelInternalType channel, const ChannelConfig &config); + + void SetDebugDriver(driver::IDriver *driver); + + void SetReceiveBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size); + void SetSendBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size); + void SetSendBufferWithData(impl::ChannelInternalType channel, const void *buf, size_t buf_size); + + Result Shutdown(impl::ChannelInternalType channel); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_manager_holder.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_manager_holder.cpp new file mode 100644 index 00000000..74668b1f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_manager_holder.cpp @@ -0,0 +1,88 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_manager.hpp" + +namespace ams::htclow::HtclowManagerHolder { + + namespace { + + constinit os::SdkMutex g_holder_mutex; + constinit int g_holder_reference_count = 0; + + mem::StandardAllocator g_allocator; + + constinit HtclowManager *g_manager = nullptr; + + constinit htclow::impl::DriverType g_default_driver_type = htclow::impl::DriverType::Socket; + + alignas(os::MemoryPageSize) u8 g_heap_buffer[928_KB]; + + } + + void AddReference() { + std::scoped_lock lk(g_holder_mutex); + + if ((g_holder_reference_count++) == 0) { + /* Initialize the allocator for the manager. */ + g_allocator.Initialize(g_heap_buffer, sizeof(g_heap_buffer)); + + /* Allocate the manager. */ + g_manager = static_cast<HtclowManager *>(g_allocator.Allocate(sizeof(HtclowManager), alignof(HtclowManager))); + + /* Construct the manager. */ + std::construct_at(g_manager, std::addressof(g_allocator)); + + /* Open the driver. */ + R_ABORT_UNLESS(g_manager->OpenDriver(g_default_driver_type)); + } + + AMS_ASSERT(g_holder_reference_count > 0); + } + + void Release() { + std::scoped_lock lk(g_holder_mutex); + + AMS_ASSERT(g_holder_reference_count > 0); + + if ((--g_holder_reference_count) == 0) { + /* Disconnect. */ + g_manager->Disconnect(); + + /* Close the driver. */ + g_manager->CloseDriver(); + + /* Destroy the manager. */ + std::destroy_at(g_manager); + g_allocator.Free(g_manager); + g_manager = nullptr; + + /* Finalize the allocator. */ + g_allocator.Finalize(); + } + } + + HtclowManager *GetHtclowManager() { + std::scoped_lock lk(g_holder_mutex); + + return g_manager; + } + + void SetDefaultDriver(htclow::impl::DriverType driver_type) { + g_default_driver_type = driver_type; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_manager_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_manager_impl.cpp new file mode 100644 index 00000000..8cc75d98 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_manager_impl.cpp @@ -0,0 +1,208 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_manager_impl.hpp" +#include "htclow_default_channel_config.hpp" + +namespace ams::htclow { + + HtclowManagerImpl::HtclowManagerImpl(mem::StandardAllocator *allocator) + : m_packet_factory(allocator), m_driver_manager(allocator), m_mux(std::addressof(m_packet_factory), std::addressof(m_ctrl_state_machine)), + m_ctrl_packet_factory(allocator), m_ctrl_state_machine(), m_ctrl_service(std::addressof(m_ctrl_packet_factory), std::addressof(m_ctrl_state_machine), std::addressof(m_mux)), + m_worker(allocator, std::addressof(m_mux), std::addressof(m_ctrl_service)), + m_listener(allocator, std::addressof(m_mux), std::addressof(m_ctrl_service), std::addressof(m_worker)), + m_is_driver_open(false) + { + /* ... */ + } + + HtclowManagerImpl::~HtclowManagerImpl() { + /* ... */ + } + + Result HtclowManagerImpl::OpenDriver(impl::DriverType driver_type) { + /* Set the driver type. */ + m_ctrl_service.SetDriverType(driver_type); + + /* Ensure that we don't end up in an invalid state. */ + auto drv_guard = SCOPE_GUARD { m_ctrl_service.SetDriverType(impl::DriverType::Unknown); }; + + /* Try to open the driver. */ + R_TRY(m_driver_manager.OpenDriver(driver_type)); + + /* Start the listener. */ + m_listener.Start(m_driver_manager.GetCurrentDriver()); + + /* Note the driver as open. */ + m_is_driver_open = true; + + drv_guard.Cancel(); + R_SUCCEED(); + } + + void HtclowManagerImpl::CloseDriver() { + /* Close the driver, if we're open. */ + if (m_is_driver_open) { + /* Cancel the driver. */ + m_driver_manager.Cancel(); + + /* Stop our listener. */ + m_listener.Cancel(); + m_listener.Wait(); + + /* Close the driver. */ + m_driver_manager.CloseDriver(); + + /* Set the driver type to unknown. */ + m_ctrl_service.SetDriverType(impl::DriverType::Unknown); + + /* Note the driver as closed. */ + m_is_driver_open = false; + } + } + + Result HtclowManagerImpl::Open(impl::ChannelInternalType channel) { + R_RETURN(m_mux.Open(channel)); + } + + Result HtclowManagerImpl::Close(impl::ChannelInternalType channel) { + R_RETURN(m_mux.Close(channel)); + } + + void HtclowManagerImpl::Resume() { + /* Get our driver. */ + auto *driver = m_driver_manager.GetCurrentDriver(); + + /* Resume our driver. */ + driver->Resume(); + + /* Start the listener. */ + m_listener.Start(driver); + + /* Resume our control service. */ + m_ctrl_service.Resume(); + } + + void HtclowManagerImpl::Suspend() { + /* Suspend our control service. */ + m_ctrl_service.Suspend(); + + /* Stop our listener. */ + m_listener.Cancel(); + m_listener.Wait(); + + /* Suspend our driver. */ + m_driver_manager.GetCurrentDriver()->Suspend(); + } + + Result HtclowManagerImpl::ConnectBegin(u32 *out_task_id, impl::ChannelInternalType channel) { + /* Begin connecting. */ + R_TRY(m_mux.ConnectBegin(out_task_id, channel)); + + /* Try to ready ourselves. */ + m_ctrl_service.TryReady(); + R_SUCCEED(); + } + + Result HtclowManagerImpl::ConnectEnd(impl::ChannelInternalType channel, u32 task_id) { + R_RETURN(m_mux.ConnectEnd(channel, task_id)); + } + + void HtclowManagerImpl::Disconnect() { + return m_ctrl_service.Disconnect(); + } + + Result HtclowManagerImpl::FlushBegin(u32 *out_task_id, impl::ChannelInternalType channel) { + R_RETURN(m_mux.FlushBegin(out_task_id, channel)); + } + + Result HtclowManagerImpl::FlushEnd(u32 task_id) { + R_RETURN(m_mux.FlushEnd(task_id)); + } + + ChannelState HtclowManagerImpl::GetChannelState(impl::ChannelInternalType channel) { + return m_mux.GetChannelState(channel); + } + + os::EventType *HtclowManagerImpl::GetChannelStateEvent(impl::ChannelInternalType channel) { + return m_mux.GetChannelStateEvent(channel); + } + + impl::DriverType HtclowManagerImpl::GetDriverType() { + return m_driver_manager.GetDriverType(); + } + + os::EventType *HtclowManagerImpl::GetTaskEvent(u32 task_id) { + return m_mux.GetTaskEvent(task_id); + } + + void HtclowManagerImpl::NotifyAsleep() { + return m_ctrl_service.NotifyAsleep(); + } + + void HtclowManagerImpl::NotifyAwake() { + return m_ctrl_service.NotifyAwake(); + } + + Result HtclowManagerImpl::ReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size) { + R_RETURN(m_mux.ReceiveBegin(out_task_id, channel, size)); + } + + Result HtclowManagerImpl::ReceiveEnd(size_t *out, void *dst, size_t dst_size, impl::ChannelInternalType channel, u32 task_id) { + R_RETURN(m_mux.ReceiveEnd(out, dst, dst_size, channel, task_id)); + } + + Result HtclowManagerImpl::SendBegin(u32 *out_task_id, size_t *out, const void *src, size_t src_size, impl::ChannelInternalType channel) { + R_RETURN(m_mux.SendBegin(out_task_id, out, src, src_size, channel)); + } + + Result HtclowManagerImpl::SendEnd(u32 task_id) { + R_RETURN(m_mux.SendEnd(task_id)); + } + + Result HtclowManagerImpl::WaitReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size) { + R_RETURN(m_mux.WaitReceiveBegin(out_task_id, channel, size)); + } + + Result HtclowManagerImpl::WaitReceiveEnd(u32 task_id) { + R_RETURN(m_mux.WaitReceiveEnd(task_id)); + } + + void HtclowManagerImpl::SetConfig(impl::ChannelInternalType channel, const ChannelConfig &config) { + return m_mux.SetConfig(channel, config); + } + + void HtclowManagerImpl::SetDebugDriver(driver::IDriver *driver) { + m_driver_manager.SetDebugDriver(driver); + } + + void HtclowManagerImpl::SetReceiveBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size) { + return m_mux.SetReceiveBuffer(channel, buf, buf_size); + } + + void HtclowManagerImpl::SetSendBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size) { + return m_mux.SetSendBuffer(channel, buf, buf_size, m_driver_manager.GetDriverType() == impl::DriverType::Usb ? sizeof(PacketBody) : DefaultChannelConfig.max_packet_size); + } + + void HtclowManagerImpl::SetSendBufferWithData(impl::ChannelInternalType channel, const void *buf, size_t buf_size) { + return m_mux.SetSendBufferWithData(channel, buf, buf_size, m_driver_manager.GetDriverType() == impl::DriverType::Usb ? sizeof(PacketBody) : DefaultChannelConfig.max_packet_size); + } + + Result HtclowManagerImpl::Shutdown(impl::ChannelInternalType channel) { + R_RETURN(m_mux.Shutdown(channel)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_manager_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_manager_impl.hpp new file mode 100644 index 00000000..7613c718 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_manager_impl.hpp @@ -0,0 +1,91 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htclow_packet_factory.hpp" +#include "driver/htclow_driver_manager.hpp" +#include "mux/htclow_mux.hpp" +#include "ctrl/htclow_ctrl_packet_factory.hpp" +#include "ctrl/htclow_ctrl_state_machine.hpp" +#include "ctrl/htclow_ctrl_service.hpp" +#include "htclow_worker.hpp" +#include "htclow_listener.hpp" + +namespace ams::htclow { + + class HtclowManagerImpl { + private: + PacketFactory m_packet_factory; + driver::DriverManager m_driver_manager; + mux::Mux m_mux; + ctrl::HtcctrlPacketFactory m_ctrl_packet_factory; + ctrl::HtcctrlStateMachine m_ctrl_state_machine; + ctrl::HtcctrlService m_ctrl_service; + Worker m_worker; + Listener m_listener; + bool m_is_driver_open; + public: + HtclowManagerImpl(mem::StandardAllocator *allocator); + ~HtclowManagerImpl(); + public: + Result OpenDriver(impl::DriverType driver_type); + void CloseDriver(); + + Result Open(impl::ChannelInternalType channel); + Result Close(impl::ChannelInternalType channel); + + void Resume(); + void Suspend(); + + Result ConnectBegin(u32 *out_task_id, impl::ChannelInternalType channel); + Result ConnectEnd(impl::ChannelInternalType channel, u32 task_id); + + void Disconnect(); + + Result FlushBegin(u32 *out_task_id, impl::ChannelInternalType channel); + Result FlushEnd(u32 task_id); + + ChannelState GetChannelState(impl::ChannelInternalType channel); + os::EventType *GetChannelStateEvent(impl::ChannelInternalType channel); + + impl::DriverType GetDriverType(); + + os::EventType *GetTaskEvent(u32 task_id); + + void NotifyAsleep(); + void NotifyAwake(); + + Result ReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size); + Result ReceiveEnd(size_t *out, void *dst, size_t dst_size, impl::ChannelInternalType channel, u32 task_id); + + Result SendBegin(u32 *out_task_id, size_t *out, const void *src, size_t src_size, impl::ChannelInternalType channel); + Result SendEnd(u32 task_id); + + Result WaitReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size); + Result WaitReceiveEnd(u32 task_id); + + void SetConfig(impl::ChannelInternalType channel, const ChannelConfig &config); + + void SetDebugDriver(driver::IDriver *driver); + + void SetReceiveBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size); + void SetSendBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size); + void SetSendBufferWithData(impl::ChannelInternalType channel, const void *buf, size_t buf_size); + + Result Shutdown(impl::ChannelInternalType channel); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_packet.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_packet.hpp new file mode 100644 index 00000000..e24a00ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_packet.hpp @@ -0,0 +1,112 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htclow { + + enum PacketType : u16 { + PacketType_Data = 24, + PacketType_MaxData = 25, + PacketType_Error = 26, + }; + + static constexpr inline u32 HtcGen2Signature = 0xA79F3540; + + struct PacketHeader { + u32 signature; + u32 offset; + u32 reserved; + u32 body_size; + s16 version; + PacketType packet_type; + impl::ChannelInternalType channel; + u64 share; + }; + static_assert(util::is_pod<PacketHeader>::value); + static_assert(sizeof(PacketHeader) == 0x20); + + static constexpr inline size_t PacketBodySizeMax = 0x3E000; + + struct PacketBody { + u8 data[PacketBodySizeMax]; + }; + + template<typename HeaderType> + class BasePacket { + private: + mem::StandardAllocator *m_allocator; + u8 *m_header; + int m_packet_size; + public: + BasePacket(mem::StandardAllocator *allocator, int packet_size) : m_allocator(allocator), m_header(nullptr), m_packet_size(packet_size) { + AMS_ASSERT(packet_size >= static_cast<int>(sizeof(HeaderType))); + + m_header = static_cast<u8 *>(m_allocator->Allocate(m_packet_size)); + } + + virtual ~BasePacket() { + if (m_header != nullptr) { + m_allocator->Free(m_header); + } + } + + bool IsAllocationSucceeded() const { + return m_header != nullptr; + } + + HeaderType *GetHeader() { + return reinterpret_cast<HeaderType *>(m_header); + } + + const HeaderType *GetHeader() const { + return reinterpret_cast<const HeaderType *>(m_header); + } + + int GetBodySize() const { + return m_packet_size - sizeof(HeaderType); + } + + u8 *GetBody() const { + if (this->GetBodySize() > 0) { + return m_header + sizeof(HeaderType); + } else { + return nullptr; + } + } + + void CopyBody(const void *src, int src_size) { + AMS_ASSERT(this->GetBodySize() >= 0); + + std::memcpy(this->GetBody(), src, src_size); + } + }; + + class Packet : public BasePacket<PacketHeader>, public util::IntrusiveListBaseNode<Packet> { + public: + using BasePacket<PacketHeader>::BasePacket; + }; + + struct PacketDeleter { + mem::StandardAllocator *m_allocator; + + void operator()(Packet *packet) { + std::destroy_at(packet); + m_allocator->Free(packet); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_packet_factory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_packet_factory.cpp new file mode 100644 index 00000000..b26b9fc2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_packet_factory.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_packet_factory.hpp" + +namespace ams::htclow { + + void PacketFactory::Delete(Packet *packet) { + PacketDeleter{m_allocator}(packet); + } + + std::unique_ptr<Packet, PacketDeleter> PacketFactory::MakeSendPacketCommon(impl::ChannelInternalType channel, s16 version, int body_size) { + /* Allocate memory for the packet. */ + if (void *buffer = m_allocator->Allocate(sizeof(Packet), alignof(Packet)); buffer != nullptr) { + /* Convert the buffer to a packet. */ + Packet *packet = static_cast<Packet *>(buffer); + + /* Construct the packet. */ + std::construct_at(packet, m_allocator, body_size + sizeof(PacketHeader)); + + /* Create the unique pointer. */ + std::unique_ptr<Packet, PacketDeleter> ptr(packet, PacketDeleter{m_allocator}); + + /* Set packet header fields. */ + if (ptr && ptr->IsAllocationSucceeded()) { + PacketHeader *header = ptr->GetHeader(); + + header->signature = HtcGen2Signature; + header->offset = 0; + header->reserved = 0; + header->body_size = body_size; + header->version = version; + header->channel = channel; + header->share = 0; + } + + return ptr; + } else { + return std::unique_ptr<Packet, PacketDeleter>(nullptr, PacketDeleter{m_allocator}); + } + } + + std::unique_ptr<Packet, PacketDeleter> PacketFactory::MakeDataPacket(impl::ChannelInternalType channel, s16 version, const void *body, int body_size, u64 share, u32 offset) { + auto packet = this->MakeSendPacketCommon(channel, version, body_size); + if (packet) { + PacketHeader *header = packet->GetHeader(); + + header->packet_type = PacketType_Data; + header->offset = offset; + header->share = share; + + packet->CopyBody(body, body_size); + + AMS_ASSERT(packet->GetBodySize() == body_size); + } + + return packet; + } + + + std::unique_ptr<Packet, PacketDeleter> PacketFactory::MakeMaxDataPacket(impl::ChannelInternalType channel, s16 version, u64 share) { + auto packet = this->MakeSendPacketCommon(channel, version, 0); + if (packet) { + PacketHeader *header = packet->GetHeader(); + + header->packet_type = PacketType_MaxData; + header->share = share; + } + + return packet; + } + + std::unique_ptr<Packet, PacketDeleter> PacketFactory::MakeErrorPacket(impl::ChannelInternalType channel) { + auto packet = this->MakeSendPacketCommon(channel, 0, 0); + if (packet) { + PacketHeader *header = packet->GetHeader(); + + header->packet_type = PacketType_Error; + } + + return packet; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_packet_factory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_packet_factory.hpp new file mode 100644 index 00000000..65d6cd2c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_packet_factory.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htclow_packet.hpp" + +namespace ams::htclow { + + class PacketFactory { + private: + mem::StandardAllocator *m_allocator; + public: + PacketFactory(mem::StandardAllocator *allocator) : m_allocator(allocator) { /* ... */ } + + std::unique_ptr<Packet, PacketDeleter> MakeDataPacket(impl::ChannelInternalType channel, s16 version, const void *body, int body_size, u64 share, u32 offset); + std::unique_ptr<Packet, PacketDeleter> MakeMaxDataPacket(impl::ChannelInternalType channel, s16 version, u64 share); + std::unique_ptr<Packet, PacketDeleter> MakeErrorPacket(impl::ChannelInternalType channel); + + void Delete(Packet *packet); + private: + std::unique_ptr<Packet, PacketDeleter> MakeSendPacketCommon(impl::ChannelInternalType channel, s16 version, int body_size); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_worker.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_worker.cpp new file mode 100644 index 00000000..4e725e2e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_worker.cpp @@ -0,0 +1,181 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_worker.hpp" + +namespace ams::htclow { + + namespace { + + constexpr inline size_t ThreadStackSize = 4_KB; + + } + + Worker::Worker(mem::StandardAllocator *allocator, mux::Mux *mux, ctrl::HtcctrlService *ctrl_srv) + : m_thread_stack_size(ThreadStackSize), m_allocator(allocator), m_mux(mux), m_service(ctrl_srv), m_driver(nullptr), m_event(os::EventClearMode_ManualClear), m_cancelled(false) + { + /* Allocate stacks. */ + m_receive_thread_stack = m_allocator->Allocate(m_thread_stack_size, os::ThreadStackAlignment); + m_send_thread_stack = m_allocator->Allocate(m_thread_stack_size, os::ThreadStackAlignment); + } + + void Worker::SetDriver(driver::IDriver *driver) { + m_driver = driver; + } + + void Worker::Start() { + /* Clear our cancelled state. */ + m_cancelled = false; + + /* Clear our event. */ + m_event.Clear(); + + /* Create our threads. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_receive_thread), ReceiveThreadEntry, this, m_receive_thread_stack, ThreadStackSize, AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtclowReceive))); + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_send_thread), SendThreadEntry, this, m_send_thread_stack, ThreadStackSize, AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtclowSend))); + + /* Set thread names. */ + os::SetThreadNamePointer(std::addressof(m_receive_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtclowReceive)); + os::SetThreadNamePointer(std::addressof(m_send_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtclowSend)); + + /* Start our threads. */ + os::StartThread(std::addressof(m_receive_thread)); + os::StartThread(std::addressof(m_send_thread)); + } + + void Worker::Wait() { + os::WaitThread(std::addressof(m_receive_thread)); + os::WaitThread(std::addressof(m_send_thread)); + os::DestroyThread(std::addressof(m_receive_thread)); + os::DestroyThread(std::addressof(m_send_thread)); + } + + void Worker::ReceiveThread() { + this->ProcessReceive(); + m_driver->CancelSendReceive(); + this->Cancel(); + } + + void Worker::SendThread() { + this->ProcessSend(); + m_driver->CancelSendReceive(); + this->Cancel(); + } + + void Worker::Cancel() { + /* Set ourselves as cancelled, and signal. */ + m_cancelled = true; + m_event.Signal(); + } + + Result Worker::ProcessReceive() { + /* Forever receive packets. */ + constexpr size_t MaxPacketHeaderSize = std::max(sizeof(PacketHeader), sizeof(ctrl::HtcctrlPacketHeader)); + u8 packet_header_storage[MaxPacketHeaderSize]; + while (true) { + /* Receive the packet header. */ + R_TRY(m_driver->Receive(packet_header_storage, sizeof(packet_header_storage))); + + /* Check if the packet is a control packet. */ + if (ctrl::HtcctrlPacketHeader *ctrl_header = reinterpret_cast<ctrl::HtcctrlPacketHeader *>(packet_header_storage); ctrl_header->signature == ctrl::HtcctrlSignature) { + /* Process the packet. */ + R_TRY(this->ProcessReceive(*ctrl_header)); + } else { + /* Otherwise, we must have a normal packet. */ + PacketHeader *header = reinterpret_cast<PacketHeader *>(packet_header_storage); + R_UNLESS(header->signature == HtcGen2Signature, htclow::ResultProtocolError()); + + /* Process the packet. */ + R_TRY(this->ProcessReceive(*header)); + } + } + } + + Result Worker::ProcessReceive(const ctrl::HtcctrlPacketHeader &header) { + /* Check the header. */ + R_TRY(m_service->CheckReceivedHeader(header)); + + /* Receive the body, if we have one. */ + if (header.body_size > 0) { + R_TRY(m_driver->Receive(m_receive_packet_body, header.body_size)); + } + + /* Process the received packet. */ + m_service->ProcessReceivePacket(header, m_receive_packet_body, header.body_size); + + R_SUCCEED(); + } + + Result Worker::ProcessReceive(const PacketHeader &header) { + /* Check the header. */ + R_TRY(m_mux->CheckReceivedHeader(header)); + + /* Receive the body, if we have one. */ + if (header.body_size > 0) { + R_TRY(m_driver->Receive(m_receive_packet_body, header.body_size)); + } + + /* Process the received packet. */ + m_mux->ProcessReceivePacket(header, m_receive_packet_body, header.body_size); + + R_SUCCEED(); + } + + Result Worker::ProcessSend() { + /* Forever process packets. */ + while (true) { + const auto index = os::WaitAny(m_service->GetSendPacketEvent(), m_mux->GetSendPacketEvent(), m_event.GetBase()); + if (index == 0) { + /* HtcctrlService packet. */ + + /* Clear the packet event. */ + os::ClearEvent(m_service->GetSendPacketEvent()); + + /* While we have packets, send them. */ + auto *packet_header = reinterpret_cast<ctrl::HtcctrlPacketHeader *>(m_send_buffer); + auto *packet_body = reinterpret_cast<ctrl::HtcctrlPacketBody *>(m_send_buffer + sizeof(*packet_header)); + int body_size; + while (m_service->QuerySendPacket(packet_header, packet_body, std::addressof(body_size))) { + m_service->RemovePacket(*packet_header); + R_TRY(m_driver->Send(packet_header, body_size + sizeof(*packet_header))); + } + } else if (index == 1) { + /* Mux packet. */ + + /* Clear the packet event. */ + os::ClearEvent(m_mux->GetSendPacketEvent()); + + /* While we have packets, send them. */ + auto *packet_header = reinterpret_cast<PacketHeader *>(m_send_buffer); + auto *packet_body = reinterpret_cast<PacketBody *>(m_send_buffer + sizeof(*packet_header)); + int body_size; + while (m_mux->QuerySendPacket(packet_header, packet_body, std::addressof(body_size))) { + R_TRY(m_driver->Send(packet_header, body_size + sizeof(*packet_header))); + m_mux->RemovePacket(*packet_header); + } + } else { + /* Our event. */ + + /* Check if we're cancelled. */ + if (m_cancelled) { + R_THROW(htclow::ResultCancelled()); + } + } + } + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_worker.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_worker.hpp new file mode 100644 index 00000000..33b6ba54 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/htclow_worker.hpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "driver/htclow_i_driver.hpp" +#include "ctrl/htclow_ctrl_service.hpp" +#include "mux/htclow_mux.hpp" + +namespace ams::htclow { + + class Worker { + private: + static_assert(sizeof(ctrl::HtcctrlPacketHeader) <= sizeof(PacketHeader)); + static_assert(sizeof(ctrl::HtcctrlPacketBody) <= sizeof(PacketBody)); + private: + u32 m_thread_stack_size; + u8 m_send_buffer[sizeof(PacketHeader) + sizeof(PacketBody)]; + u8 m_receive_packet_body[sizeof(PacketBody)]; + mem::StandardAllocator *m_allocator; + mux::Mux *m_mux; + ctrl::HtcctrlService *m_service; + driver::IDriver *m_driver; + os::ThreadType m_receive_thread; + os::ThreadType m_send_thread; + os::Event m_event; + void *m_receive_thread_stack; + void *m_send_thread_stack; + bool m_cancelled; + private: + static void ReceiveThreadEntry(void *arg) { + static_cast<Worker *>(arg)->ReceiveThread(); + } + + static void SendThreadEntry(void *arg) { + static_cast<Worker *>(arg)->SendThread(); + } + + void ReceiveThread(); + void SendThread(); + public: + Worker(mem::StandardAllocator *allocator, mux::Mux *mux, ctrl::HtcctrlService *ctrl_srv); + + void SetDriver(driver::IDriver *driver); + + void Start(); + void Cancel(); + void Wait(); + private: + Result ProcessReceive(); + Result ProcessSend(); + + Result ProcessReceive(const ctrl::HtcctrlPacketHeader &header); + Result ProcessReceive(const PacketHeader &header); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux.cpp new file mode 100644 index 00000000..16b223f3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux.cpp @@ -0,0 +1,426 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_mux.hpp" +#include "../htclow_packet_factory.hpp" +#include "../ctrl/htclow_ctrl_state_machine.hpp" + +namespace ams::htclow::mux { + + Mux::Mux(PacketFactory *pf, ctrl::HtcctrlStateMachine *sm) + : m_packet_factory(pf), m_state_machine(sm), m_task_manager(), m_event(os::EventClearMode_ManualClear), + m_channel_impl_map(pf, sm, std::addressof(m_task_manager), std::addressof(m_event)), m_global_send_buffer(pf), + m_mutex(), m_state(MuxState::Normal), m_version(ProtocolVersion) + { + /* ... */ + } + + void Mux::SetVersion(u16 version) { + /* Set our version. */ + m_version = version; + + /* Set all entries in our map. */ + /* NOTE: Nintendo does this highly inefficiently... */ + for (auto &pair : m_channel_impl_map.GetMap()) { + m_channel_impl_map[pair.second].SetVersion(m_version); + } + } + + Result Mux::CheckReceivedHeader(const PacketHeader &header) const { + /* Check the packet signature. */ + AMS_ASSERT(header.signature == HtcGen2Signature); + + /* Switch on the packet type. */ + switch (header.packet_type) { + case PacketType_Data: + R_UNLESS(header.version == m_version, htclow::ResultProtocolError()); + R_UNLESS(header.body_size <= sizeof(PacketBody), htclow::ResultProtocolError()); + break; + case PacketType_MaxData: + R_UNLESS(header.version == m_version, htclow::ResultProtocolError()); + R_UNLESS(header.body_size == 0, htclow::ResultProtocolError()); + break; + case PacketType_Error: + R_UNLESS(header.body_size == 0, htclow::ResultProtocolError()); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + R_SUCCEED(); + } + + Result Mux::ProcessReceivePacket(const PacketHeader &header, const void *body, size_t body_size) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Process for the channel. */ + if (auto it = m_channel_impl_map.GetMap().find(header.channel); it != m_channel_impl_map.GetMap().end()) { + R_RETURN(m_channel_impl_map[it->second].ProcessReceivePacket(header, body, body_size)); + } else { + if (header.packet_type == PacketType_Data || header.packet_type == PacketType_MaxData) { + this->SendErrorPacket(header.channel); + } + R_THROW(htclow::ResultChannelNotExist()); + } + } + + bool Mux::QuerySendPacket(PacketHeader *header, PacketBody *body, int *out_body_size) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check for an error packet. */ + /* NOTE: Nintendo checks this once per iteration of the below loop. */ + /* The extra checks are unnecessary, because we hold our mutex. */ + if (auto *error_packet = m_global_send_buffer.GetNextPacket(); error_packet != nullptr) { + std::memcpy(header, error_packet->GetHeader(), sizeof(*header)); + *out_body_size = 0; + return true; + } + + /* Iterate the map, checking each channel for a valid valid packet. */ + for (auto &pair : m_channel_impl_map.GetMap()) { + /* Get the current channel impl. */ + /* See if the channel has something for us to send. */ + if (m_channel_impl_map[pair.second].QuerySendPacket(header, body, out_body_size)) { + return this->IsSendable(header->packet_type); + } + } + + return false; + } + + void Mux::RemovePacket(const PacketHeader &header) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Remove the packet from the appropriate source. */ + if (header.packet_type == PacketType_Error) { + m_global_send_buffer.RemovePacket(); + } else if (auto it = m_channel_impl_map.GetMap().find(header.channel); it != m_channel_impl_map.GetMap().end()) { + m_channel_impl_map[it->second].RemovePacket(header); + } + + /* Notify the task manager. */ + m_task_manager.NotifySendReady(); + } + + void Mux::UpdateChannelState() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Update the state of all channels in our map. */ + /* NOTE: Nintendo does this highly inefficiently... */ + for (auto pair : m_channel_impl_map.GetMap()) { + m_channel_impl_map[pair.second].UpdateState(); + } + } + + void Mux::UpdateMuxState() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Update whether we're sleeping. */ + if (m_state_machine->IsSleeping()) { + m_state = MuxState::Sleep; + } else { + m_state = MuxState::Normal; + m_event.Signal(); + } + } + + Result Mux::CheckChannelExist(impl::ChannelInternalType channel) { + R_UNLESS(m_channel_impl_map.Exists(channel), htclow::ResultChannelNotExist()); + R_SUCCEED(); + } + + Result Mux::SendErrorPacket(impl::ChannelInternalType channel) { + /* Create and send the packet. */ + R_TRY(m_global_send_buffer.AddPacket(m_packet_factory->MakeErrorPacket(channel))); + + /* Signal our event. */ + m_event.Signal(); + + R_SUCCEED(); + } + + bool Mux::IsSendable(PacketType packet_type) const { + AMS_UNUSED(packet_type); + + switch (m_state) { + case MuxState::Normal: + return true; + case MuxState::Sleep: + return false; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + Result Mux::Open(impl::ChannelInternalType channel) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that the channel doesn't already exist. */ + R_UNLESS(!m_channel_impl_map.Exists(channel), htclow::ResultChannelAlreadyExist()); + + /* Add the channel. */ + R_TRY(m_channel_impl_map.AddChannel(channel)); + + /* Set the channel version. */ + m_channel_impl_map.GetChannelImpl(channel).SetVersion(m_version); + + R_SUCCEED(); + } + + Result Mux::Close(impl::ChannelInternalType channel) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* If we have the channel, close it. */ + if (auto it = m_channel_impl_map.GetMap().find(channel); it != m_channel_impl_map.GetMap().end()) { + /* Shut down the channel. */ + m_channel_impl_map[it->second].ShutdownForce(); + + /* Remove the channel. */ + R_ABORT_UNLESS(m_channel_impl_map.RemoveChannel(channel)); + } + + R_SUCCEED(); + } + + Result Mux::ConnectBegin(u32 *out_task_id, impl::ChannelInternalType channel) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Find the channel. */ + auto it = m_channel_impl_map.GetMap().find(channel); + R_UNLESS(it != m_channel_impl_map.GetMap().end(), htclow::ResultChannelNotExist()); + + /* Perform the connection. */ + R_RETURN(m_channel_impl_map[it->second].DoConnectBegin(out_task_id)); + } + + Result Mux::ConnectEnd(impl::ChannelInternalType channel, u32 task_id) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Get the trigger for the task. */ + const auto trigger = m_task_manager.GetTrigger(task_id); + + /* Free the task. */ + m_task_manager.FreeTask(task_id); + + /* Check that we didn't hit a disconnect. */ + R_UNLESS(trigger != EventTrigger_Disconnect, htclow::ResultInvalidChannelStateDisconnected()); + + /* Find the channel. */ + auto it = m_channel_impl_map.GetMap().find(channel); + R_UNLESS(it != m_channel_impl_map.GetMap().end(), htclow::ResultChannelNotExist()); + + /* Perform the disconnection. */ + R_RETURN(m_channel_impl_map[it->second].DoConnectEnd()); + } + + ChannelState Mux::GetChannelState(impl::ChannelInternalType channel) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + return m_channel_impl_map.GetChannelImpl(channel).GetChannelState(); + } + + os::EventType *Mux::GetChannelStateEvent(impl::ChannelInternalType channel) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + return m_channel_impl_map.GetChannelImpl(channel).GetChannelStateEvent(); + } + + Result Mux::FlushBegin(u32 *out_task_id, impl::ChannelInternalType channel) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Find the channel. */ + auto it = m_channel_impl_map.GetMap().find(channel); + R_UNLESS(it != m_channel_impl_map.GetMap().end(), htclow::ResultChannelNotExist()); + + /* Perform the connection. */ + R_RETURN(m_channel_impl_map[it->second].DoFlush(out_task_id)); + } + + Result Mux::FlushEnd(u32 task_id) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Get the trigger for the task. */ + const auto trigger = m_task_manager.GetTrigger(task_id); + + /* Free the task. */ + m_task_manager.FreeTask(task_id); + + /* Check that we didn't hit a disconnect. */ + R_UNLESS(trigger != EventTrigger_Disconnect, htclow::ResultInvalidChannelStateDisconnected()); + + R_SUCCEED(); + } + + os::EventType *Mux::GetTaskEvent(u32 task_id) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + return m_task_manager.GetTaskEvent(task_id); + } + + Result Mux::ReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Find the channel. */ + auto it = m_channel_impl_map.GetMap().find(channel); + R_UNLESS(it != m_channel_impl_map.GetMap().end(), htclow::ResultChannelNotExist()); + + /* Perform the connection. */ + R_RETURN(m_channel_impl_map[it->second].DoReceiveBegin(out_task_id, size)); + } + + Result Mux::ReceiveEnd(size_t *out, void *dst, size_t dst_size, impl::ChannelInternalType channel, u32 task_id) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Free the task. */ + m_task_manager.FreeTask(task_id); + + /* If we have data, perform the receive. */ + if (dst_size > 0) { + /* Find the channel. */ + auto it = m_channel_impl_map.GetMap().find(channel); + R_UNLESS(it != m_channel_impl_map.GetMap().end(), htclow::ResultChannelNotExist()); + + /* Perform the receive. */ + R_RETURN(m_channel_impl_map[it->second].DoReceiveEnd(out, dst, dst_size)); + } else { + *out = 0; + R_SUCCEED(); + } + } + + Result Mux::SendBegin(u32 *out_task_id, size_t *out, const void *src, size_t src_size, impl::ChannelInternalType channel) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Find the channel. */ + auto it = m_channel_impl_map.GetMap().find(channel); + R_UNLESS(it != m_channel_impl_map.GetMap().end(), htclow::ResultChannelNotExist()); + + /* Perform the connection. */ + R_RETURN(m_channel_impl_map[it->second].DoSend(out_task_id, out, src, src_size)); + } + + Result Mux::SendEnd(u32 task_id) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Get the trigger for the task. */ + const auto trigger = m_task_manager.GetTrigger(task_id); + + /* Free the task. */ + m_task_manager.FreeTask(task_id); + + /* Check that we didn't hit a disconnect. */ + R_UNLESS(trigger != EventTrigger_Disconnect, htclow::ResultInvalidChannelStateDisconnected()); + + R_SUCCEED(); + } + + Result Mux::WaitReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size) { + R_RETURN(this->ReceiveBegin(out_task_id, channel, size)); + } + + Result Mux::WaitReceiveEnd(u32 task_id) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Get the trigger for the task. */ + const auto trigger = m_task_manager.GetTrigger(task_id); + + /* Free the task. */ + m_task_manager.FreeTask(task_id); + + /* Check that we didn't hit a disconnect. */ + R_UNLESS(trigger != EventTrigger_Disconnect, htclow::ResultInvalidChannelStateDisconnected()); + + R_SUCCEED(); + } + + void Mux::SetConfig(impl::ChannelInternalType channel, const ChannelConfig &config) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Find the channel. */ + auto it = m_channel_impl_map.GetMap().find(channel); + AMS_ABORT_UNLESS(it != m_channel_impl_map.GetMap().end()); + + /* Perform the connection. */ + return m_channel_impl_map[it->second].SetConfig(config); + } + + void Mux::SetSendBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size, size_t max_packet_size) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Find the channel. */ + auto it = m_channel_impl_map.GetMap().find(channel); + AMS_ABORT_UNLESS(it != m_channel_impl_map.GetMap().end()); + + /* Set the send buffer. */ + m_channel_impl_map[it->second].SetSendBuffer(buf, buf_size, max_packet_size); + } + + void Mux::SetSendBufferWithData(impl::ChannelInternalType channel, const void *buf, size_t buf_size, size_t max_packet_size) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Find the channel. */ + auto it = m_channel_impl_map.GetMap().find(channel); + AMS_ABORT_UNLESS(it != m_channel_impl_map.GetMap().end()); + + /* Set the send buffer. */ + m_channel_impl_map[it->second].SetSendBufferWithData(buf, buf_size, max_packet_size); + } + + void Mux::SetReceiveBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Find the channel. */ + auto it = m_channel_impl_map.GetMap().find(channel); + AMS_ABORT_UNLESS(it != m_channel_impl_map.GetMap().end()); + + /* Set the send buffer. */ + m_channel_impl_map[it->second].SetReceiveBuffer(buf, buf_size); + } + + Result Mux::Shutdown(impl::ChannelInternalType channel) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Find the channel. */ + auto it = m_channel_impl_map.GetMap().find(channel); + R_UNLESS(it != m_channel_impl_map.GetMap().end(), htclow::ResultChannelNotExist()); + + /* Perform the shutdown. */ + R_RETURN(m_channel_impl_map[it->second].DoShutdown()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux.hpp new file mode 100644 index 00000000..1c08e1c4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux.hpp @@ -0,0 +1,95 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htclow_mux_task_manager.hpp" +#include "htclow_mux_channel_impl_map.hpp" +#include "htclow_mux_global_send_buffer.hpp" + +namespace ams::htclow::mux { + + enum class MuxState { + Normal, + Sleep, + }; + + class Mux { + private: + private: + PacketFactory *m_packet_factory; + ctrl::HtcctrlStateMachine *m_state_machine; + TaskManager m_task_manager; + os::Event m_event; + ChannelImplMap m_channel_impl_map; + GlobalSendBuffer m_global_send_buffer; + os::SdkMutex m_mutex; + MuxState m_state; + s16 m_version; + public: + Mux(PacketFactory *pf, ctrl::HtcctrlStateMachine *sm); + + void SetVersion(u16 version); + + os::EventType *GetSendPacketEvent() { return m_event.GetBase(); } + + Result CheckReceivedHeader(const PacketHeader &header) const; + Result ProcessReceivePacket(const PacketHeader &header, const void *body, size_t body_size); + + bool QuerySendPacket(PacketHeader *header, PacketBody *body, int *out_body_size); + void RemovePacket(const PacketHeader &header); + + void UpdateChannelState(); + void UpdateMuxState(); + public: + Result Open(impl::ChannelInternalType channel); + Result Close(impl::ChannelInternalType channel); + + Result ConnectBegin(u32 *out_task_id, impl::ChannelInternalType channel); + Result ConnectEnd(impl::ChannelInternalType channel, u32 task_id); + + ChannelState GetChannelState(impl::ChannelInternalType channel); + os::EventType *GetChannelStateEvent(impl::ChannelInternalType channel); + + Result FlushBegin(u32 *out_task_id, impl::ChannelInternalType channel); + Result FlushEnd(u32 task_id); + + os::EventType *GetTaskEvent(u32 task_id); + + Result ReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size); + Result ReceiveEnd(size_t *out, void *dst, size_t dst_size, impl::ChannelInternalType channel, u32 task_id); + + Result SendBegin(u32 *out_task_id, size_t *out, const void *src, size_t src_size, impl::ChannelInternalType channel); + Result SendEnd(u32 task_id); + + Result WaitReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size); + Result WaitReceiveEnd(u32 task_id); + + void SetConfig(impl::ChannelInternalType channel, const ChannelConfig &config); + + void SetSendBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size, size_t max_packet_size); + void SetReceiveBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size); + void SetSendBufferWithData(impl::ChannelInternalType channel, const void *buf, size_t buf_size, size_t max_packet_size); + + Result Shutdown(impl::ChannelInternalType channel); + private: + Result CheckChannelExist(impl::ChannelInternalType channel); + + Result SendErrorPacket(impl::ChannelInternalType channel); + + bool IsSendable(PacketType packet_type) const; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_channel_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_channel_impl.cpp new file mode 100644 index 00000000..82ae6c8e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_channel_impl.cpp @@ -0,0 +1,473 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_mux_channel_impl.hpp" +#include "../ctrl/htclow_ctrl_state_machine.hpp" +#include "../htclow_default_channel_config.hpp" +#include "../htclow_packet_factory.hpp" + +namespace ams::htclow::mux { + + ChannelImpl::ChannelImpl(impl::ChannelInternalType channel, PacketFactory *pf, ctrl::HtcctrlStateMachine *sm, TaskManager *tm, os::Event *ev) + : m_channel(channel), m_packet_factory(pf), m_state_machine(sm), m_task_manager(tm), m_event(ev), + m_send_buffer(m_channel, pf), m_receive_buffer(), m_version(ProtocolVersion), m_config(DefaultChannelConfig), + m_offset(0), m_total_send_size(0), m_cur_max_data(0), m_prev_max_data(0), m_share(), + m_state_change_event(os::EventClearMode_ManualClear), m_state(ChannelState_Unconnectable) + + { + this->UpdateState(); + } + + void ChannelImpl::SetVersion(s16 version) { + /* Sanity check the version. */ + AMS_ASSERT(version <= ProtocolVersion); + + /* Set version. */ + m_version = version; + m_send_buffer.SetVersion(version); + } + + Result ChannelImpl::CheckState(std::initializer_list<ChannelState> states) const { + /* Determine if we have a matching state. */ + bool match = false; + for (const auto &state : states) { + match |= m_state == state; + } + + /* If we do, we're good. */ + R_SUCCEED_IF(match); + + /* Otherwise, return appropriate failure error. */ + if (m_state == ChannelState_Disconnected) { + R_THROW(htclow::ResultInvalidChannelStateDisconnected()); + } else { + R_THROW(htclow::ResultInvalidChannelState()); + } + } + + Result ChannelImpl::CheckPacketVersion(s16 version) const { + R_UNLESS(version == m_version, htclow::ResultChannelVersionNotMatched()); + R_SUCCEED(); + } + + + Result ChannelImpl::ProcessReceivePacket(const PacketHeader &header, const void *body, size_t body_size) { + switch (header.packet_type) { + case PacketType_Data: + R_RETURN(this->ProcessReceiveDataPacket(header.version, header.share, header.offset, body, body_size)); + case PacketType_MaxData: + R_RETURN(this->ProcessReceiveMaxDataPacket(header.version, header.share)); + case PacketType_Error: + R_RETURN(this->ProcessReceiveErrorPacket()); + default: + R_THROW(htclow::ResultProtocolError()); + } + } + + Result ChannelImpl::ProcessReceiveDataPacket(s16 version, u64 share, u32 offset, const void *body, size_t body_size) { + /* Check our state. */ + R_TRY(this->CheckState({ChannelState_Connectable, ChannelState_Connected})); + + /* Check the packet version. */ + R_TRY(this->CheckPacketVersion(version)); + + /* Check that offset matches. */ + R_UNLESS(offset == static_cast<u32>(m_offset), htclow::ResultProtocolError()); + + /* Check for flow control, if we should. */ + if (m_config.flow_control_enabled) { + /* Check that the share increases monotonically. */ + if (m_share.has_value()) { + R_UNLESS(m_share.value() <= share, htclow::ResultProtocolError()); + } + + /* Update our share. */ + m_share = share; + + /* Signal our event. */ + this->SignalSendPacketEvent(); + } + + /* Update our offset. */ + m_offset += body_size; + + /* Write the packet body. */ + R_ABORT_UNLESS(m_receive_buffer.Write(body, body_size)); + + /* Notify the data was received. */ + m_task_manager->NotifyReceiveData(m_channel, m_receive_buffer.GetDataSize()); + + R_SUCCEED(); + } + + Result ChannelImpl::ProcessReceiveMaxDataPacket(s16 version, u64 share) { + /* Check our state. */ + R_TRY(this->CheckState({ChannelState_Connectable, ChannelState_Connected})); + + /* Check the packet version. */ + R_TRY(this->CheckPacketVersion(version)); + + /* Check for flow control, if we should. */ + if (m_config.flow_control_enabled) { + /* Check that the share increases monotonically. */ + if (m_share.has_value()) { + R_UNLESS(m_share.value() <= share, htclow::ResultProtocolError()); + } + + /* Update our share. */ + m_share = share; + + /* Signal our event. */ + this->SignalSendPacketEvent(); + } + + R_SUCCEED(); + } + + Result ChannelImpl::ProcessReceiveErrorPacket() { + if (m_state == ChannelState_Connected || m_state == ChannelState_Disconnected) { + this->ShutdownForce(); + } + R_SUCCEED(); + } + + bool ChannelImpl::QuerySendPacket(PacketHeader *header, PacketBody *body, int *out_body_size) { + /* Check our send buffer. */ + if (m_send_buffer.QueryNextPacket(header, body, out_body_size, m_cur_max_data, m_total_send_size, m_share.has_value(), m_share.value_or(0))) { + /* Update tracking variables. */ + if (header->packet_type == PacketType_Data) { + m_prev_max_data = m_cur_max_data; + } + + return true; + } else { + return false; + } + } + + void ChannelImpl::RemovePacket(const PacketHeader &header) { + /* Remove the packet. */ + m_send_buffer.RemovePacket(header); + + /* Check if the send buffer is now empty. */ + if (m_send_buffer.Empty()) { + m_task_manager->NotifySendBufferEmpty(m_channel); + } + } + + void ChannelImpl::UpdateState() { + /* Check if shutdown must be forced. */ + if (m_state_machine->IsUnsupportedServiceChannelToShutdown(m_channel)) { + this->ShutdownForce(); + } + + /* Check if we're readied. */ + if (m_state_machine->IsReadied()) { + m_task_manager->NotifyConnectReady(); + } + + /* Update our state transition. */ + if (m_state_machine->IsConnectable(m_channel)) { + if (m_state == ChannelState_Unconnectable) { + this->SetState(ChannelState_Connectable); + } + } else if (m_state_machine->IsUnconnectable()) { + if (m_state == ChannelState_Connectable) { + this->SetState(ChannelState_Unconnectable); + m_state_machine->SetNotConnecting(m_channel); + } else if (m_state == ChannelState_Connected) { + this->ShutdownForce(); + } + } + } + + void ChannelImpl::ShutdownForce() { + /* Clear our send buffer. */ + m_send_buffer.Clear(); + + /* Set our state to shutdown. */ + this->SetState(ChannelState_Disconnected); + } + + void ChannelImpl::SetState(ChannelState state) { + /* Check that we can perform the transition. */ + AMS_ABORT_UNLESS(IsStateTransitionAllowed(m_state, state)); + + /* Perform the transition. */ + this->SetStateWithoutCheck(state); + } + + void ChannelImpl::SetStateWithoutCheck(ChannelState state) { + /* Change our state. */ + if (m_state != state) { + m_state = state; + m_state_change_event.Signal(); + } + + /* If relevant, notify disconnect. */ + if (m_state == ChannelState_Disconnected) { + m_task_manager->NotifyDisconnect(m_channel); + } + } + + void ChannelImpl::SignalSendPacketEvent() { + if (m_event != nullptr) { + m_event->Signal(); + } + } + + Result ChannelImpl::DoConnectBegin(u32 *out_task_id) { + /* Check our state. */ + R_TRY(this->CheckState({ChannelState_Connectable})); + + /* Set ourselves as connecting. */ + m_state_machine->SetConnecting(m_channel); + + /* Allocate a task. */ + u32 task_id{}; + R_TRY(m_task_manager->AllocateTask(std::addressof(task_id), m_channel)); + + /* Configure the task. */ + m_task_manager->ConfigureConnectTask(task_id); + + /* If we're ready, complete the task immediately. */ + if (m_state_machine->IsReadied()) { + m_task_manager->CompleteTask(task_id, EventTrigger_ConnectReady); + } + + /* Set the output task id. */ + *out_task_id = task_id; + R_SUCCEED(); + } + + Result ChannelImpl::DoConnectEnd() { + /* Check our state. */ + R_TRY(this->CheckState({ChannelState_Connectable})); + + /* Perform handshake, if we should. */ + if (m_config.handshake_enabled) { + /* Set our current max data. */ + m_cur_max_data = m_receive_buffer.GetBufferSize(); + + /* Make a max data packet. */ + auto packet = m_packet_factory->MakeMaxDataPacket(m_channel, m_version, m_cur_max_data); + R_UNLESS(packet, htclow::ResultOutOfMemory()); + + /* Send the packet. */ + m_send_buffer.AddPacket(std::move(packet)); + + /* Signal that we have an packet to send. */ + this->SignalSendPacketEvent(); + + /* Set our prev max data. */ + m_prev_max_data = m_cur_max_data; + } else { + /* Set our share. */ + m_share = m_config.initial_counter_max_data; + + /* If we're not empty, signal. */ + if (!m_send_buffer.Empty()) { + this->SignalSendPacketEvent(); + } + } + + /* Set our state as connected. */ + this->SetState(ChannelState_Connected); + + R_SUCCEED(); + } + + Result ChannelImpl::DoFlush(u32 *out_task_id) { + /* Check our state. */ + R_TRY(this->CheckState({ChannelState_Connected})); + + /* Allocate a task. */ + u32 task_id{}; + R_TRY(m_task_manager->AllocateTask(std::addressof(task_id), m_channel)); + + /* Configure the task. */ + m_task_manager->ConfigureFlushTask(task_id); + + /* If we're already flushed, complete the task immediately. */ + if (m_send_buffer.Empty()) { + m_task_manager->CompleteTask(task_id, EventTrigger_SendBufferEmpty); + } + + /* Set the output task id. */ + *out_task_id = task_id; + R_SUCCEED(); + } + + Result ChannelImpl::DoReceiveBegin(u32 *out_task_id, size_t size) { + /* Check our state. */ + R_TRY(this->CheckState({ChannelState_Connected, ChannelState_Disconnected})); + + /* Allocate a task. */ + u32 task_id{}; + R_TRY(m_task_manager->AllocateTask(std::addressof(task_id), m_channel)); + + /* Configure the task. */ + m_task_manager->ConfigureReceiveTask(task_id, size); + + /* Check if the task is already complete. */ + if (m_receive_buffer.GetDataSize() >= size) { + m_task_manager->CompleteTask(task_id, EventTrigger_ReceiveData); + } else if (m_state == ChannelState_Disconnected) { + m_task_manager->CompleteTask(task_id, EventTrigger_Disconnect); + } + + /* Set the output task id. */ + *out_task_id = task_id; + R_SUCCEED(); + + } + Result ChannelImpl::DoReceiveEnd(size_t *out, void *dst, size_t dst_size) { + /* Check our state. */ + R_TRY(this->CheckState({ChannelState_Connected, ChannelState_Disconnected})); + + /* If we have nowhere to receive, we're done. */ + if (dst_size == 0) { + *out = 0; + R_SUCCEED(); + } + + /* Get the amount of receivable data. */ + const size_t receivable = m_receive_buffer.GetDataSize(); + const size_t received = std::min(dst_size, receivable); + + /* Read the data. */ + R_ABORT_UNLESS(m_receive_buffer.Read(dst, received)); + + /* Handle flow control, if we should. */ + if (m_config.flow_control_enabled) { + /* Read our fields. */ + const auto prev_max_data = m_prev_max_data; + const auto next_max_data = m_cur_max_data + received; + const auto max_packet_size = m_config.max_packet_size; + const auto offset = m_offset; + + /* Update our current max data. */ + m_cur_max_data = next_max_data; + + /* If we can, send a max data packet. */ + if (prev_max_data - offset < max_packet_size + sizeof(PacketHeader)) { + /* Make a max data packet. */ + auto packet = m_packet_factory->MakeMaxDataPacket(m_channel, m_version, next_max_data); + R_UNLESS(packet, htclow::ResultOutOfMemory()); + + /* Send the packet. */ + m_send_buffer.AddPacket(std::move(packet)); + + /* Signal that we have an packet to send. */ + this->SignalSendPacketEvent(); + + /* Set our prev max data. */ + m_prev_max_data = m_cur_max_data; + } + } + + /* Set the output size. */ + *out = received; + R_SUCCEED(); + } + + Result ChannelImpl::DoSend(u32 *out_task_id, size_t *out, const void *src, size_t src_size) { + /* Check our state. */ + R_TRY(this->CheckState({ChannelState_Connected})); + + /* Allocate a task. */ + u32 task_id{}; + R_TRY(m_task_manager->AllocateTask(std::addressof(task_id), m_channel)); + + /* Send the data. */ + const size_t sent = m_send_buffer.AddData(src, src_size); + + /* Add the size to our total. */ + m_total_send_size += sent; + + /* Signal our event. */ + this->SignalSendPacketEvent(); + + /* Configure the task. */ + m_task_manager->ConfigureSendTask(task_id); + + /* If we sent all the data, we're done. */ + if (sent == src_size) { + m_task_manager->CompleteTask(task_id, EventTrigger_SendComplete); + } + + /* Set the output. */ + *out_task_id = task_id; + *out = sent; + + R_SUCCEED(); + } + + Result ChannelImpl::DoShutdown() { + /* Check our state. */ + R_TRY(this->CheckState({ChannelState_Connected})); + + /* Set our state. */ + this->SetState(ChannelState_Disconnected); + R_SUCCEED(); + } + + void ChannelImpl::SetConfig(const ChannelConfig &config) { + /* Check our state. */ + R_ABORT_UNLESS(this->CheckState({ChannelState_Unconnectable, ChannelState_Connectable})); + + /* Set our config. */ + m_config = config; + + /* Set flow control for our send buffer. */ + m_send_buffer.SetFlowControlEnabled(m_config.flow_control_enabled); + } + + void ChannelImpl::SetSendBuffer(void *buf, size_t buf_size, size_t max_packet_size) { + /* Set buffer. */ + m_send_buffer.SetBuffer(buf, buf_size); + + /* Determine true max packet size. */ + if (m_config.flow_control_enabled) { + max_packet_size = std::min(max_packet_size, m_config.max_packet_size); + } + + /* Set max packet size. */ + m_send_buffer.SetMaxPacketSize(max_packet_size); + } + + void ChannelImpl::SetReceiveBuffer(void *buf, size_t buf_size) { + /* Set the buffer. */ + m_receive_buffer.Initialize(buf, buf_size); + } + + void ChannelImpl::SetSendBufferWithData(const void *buf, size_t buf_size, size_t max_packet_size) { + /* Set buffer. */ + m_send_buffer.SetReadOnlyBuffer(buf, buf_size); + + /* Determine true max packet size. */ + if (m_config.flow_control_enabled) { + max_packet_size = std::min(max_packet_size, m_config.max_packet_size); + } + + /* Set max packet size. */ + m_send_buffer.SetMaxPacketSize(max_packet_size); + + /* Set our total send size. */ + m_total_send_size = buf_size; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_channel_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_channel_impl.hpp new file mode 100644 index 00000000..61192966 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_channel_impl.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htclow_mux_task_manager.hpp" +#include "htclow_mux_send_buffer.hpp" + +namespace ams::htclow { + + class PacketFactory; + + namespace ctrl { + + class HtcctrlStateMachine; + + } + +} + +namespace ams::htclow::mux { + + class ChannelImpl { + private: + impl::ChannelInternalType m_channel; + PacketFactory *m_packet_factory; + ctrl::HtcctrlStateMachine *m_state_machine; + TaskManager *m_task_manager; + os::Event *m_event; + SendBuffer m_send_buffer; + RingBuffer m_receive_buffer; + s16 m_version; + ChannelConfig m_config; + u64 m_offset; + u64 m_total_send_size; + u64 m_cur_max_data; + u64 m_prev_max_data; + util::optional<u64> m_share; + os::Event m_state_change_event; + ChannelState m_state; + public: + ChannelImpl(impl::ChannelInternalType channel, PacketFactory *pf, ctrl::HtcctrlStateMachine *sm, TaskManager *tm, os::Event *ev); + + void SetVersion(s16 version); + + ChannelState GetChannelState() { return m_state; } + os::EventType *GetChannelStateEvent() { return m_state_change_event.GetBase(); } + + Result ProcessReceivePacket(const PacketHeader &header, const void *body, size_t body_size); + + bool QuerySendPacket(PacketHeader *header, PacketBody *body, int *out_body_size); + + void RemovePacket(const PacketHeader &header); + + void ShutdownForce(); + + void UpdateState(); + public: + Result DoConnectBegin(u32 *out_task_id); + Result DoConnectEnd(); + + Result DoFlush(u32 *out_task_id); + + Result DoReceiveBegin(u32 *out_task_id, size_t size); + Result DoReceiveEnd(size_t *out, void *dst, size_t dst_size); + + Result DoSend(u32 *out_task_id, size_t *out, const void *src, size_t src_size); + + Result DoShutdown(); + + void SetConfig(const ChannelConfig &config); + + void SetSendBuffer(void *buf, size_t buf_size, size_t max_packet_size); + void SetReceiveBuffer(void *buf, size_t buf_size); + void SetSendBufferWithData(const void *buf, size_t buf_size, size_t max_packet_size); + private: + void SetState(ChannelState state); + void SetStateWithoutCheck(ChannelState state); + + void SignalSendPacketEvent(); + + Result CheckState(std::initializer_list<ChannelState> states) const; + Result CheckPacketVersion(s16 version) const; + + Result ProcessReceiveDataPacket(s16 version, u64 share, u32 offset, const void *body, size_t body_size); + Result ProcessReceiveMaxDataPacket(s16 version, u64 share); + Result ProcessReceiveErrorPacket(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_channel_impl_map.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_channel_impl_map.cpp new file mode 100644 index 00000000..3090d966 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_channel_impl_map.cpp @@ -0,0 +1,94 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_mux_channel_impl_map.hpp" + +namespace ams::htclow::mux { + + ChannelImplMap::ChannelImplMap(PacketFactory *pf, ctrl::HtcctrlStateMachine *sm, TaskManager *tm, os::Event *ev) + : m_packet_factory(pf), m_state_machine(sm), m_task_manager(tm), m_event(ev), m_map() + { + /* Initialize the map. */ + m_map.Initialize(MaxChannelCount, m_map_buffer, sizeof(m_map_buffer)); + + /* Set all storages as invalid. */ + for (auto i = 0; i < MaxChannelCount; ++i) { + m_storage_valid[i] = false; + } + } + + ChannelImpl &ChannelImplMap::GetChannelImpl(int index) { + return GetReference(m_channel_storage[index]); + } + + ChannelImpl &ChannelImplMap::GetChannelImpl(impl::ChannelInternalType channel) { + /* Find the channel. */ + auto it = m_map.find(channel); + AMS_ASSERT(it != m_map.end()); + + /* Return the implementation object. */ + return this->GetChannelImpl(it->second); + } + + Result ChannelImplMap::AddChannel(impl::ChannelInternalType channel) { + /* Find a free storage. */ + int idx; + for (idx = 0; idx < MaxChannelCount; ++idx) { + if (!m_storage_valid[idx]) { + break; + } + } + + /* Validate that the storage is free. */ + R_UNLESS(idx < MaxChannelCount, htclow::ResultOutOfChannel()); + + /* Create the channel impl. */ + util::ConstructAt(m_channel_storage[idx], channel, m_packet_factory, m_state_machine, m_task_manager, m_event); + + /* Mark the storage valid. */ + m_storage_valid[idx] = true; + + /* Insert into our map. */ + m_map.insert(std::pair<const impl::ChannelInternalType, int>{channel, idx}); + + R_SUCCEED(); + } + + Result ChannelImplMap::RemoveChannel(impl::ChannelInternalType channel) { + /* Find the storage. */ + auto it = m_map.find(channel); + AMS_ASSERT(it != m_map.end()); + + /* Get the channel index. */ + const auto index = it->second; + AMS_ASSERT(0 <= index && index < MaxChannelCount); + + /* Get the channel impl. */ + auto *channel_impl = GetPointer(m_channel_storage[index]); + + /* Mark the storage as invalid. */ + m_storage_valid[index] = false; + + /* Erase the channel from the map. */ + m_map.erase(channel); + + /* Destroy the channel. */ + std::destroy_at(channel_impl); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_channel_impl_map.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_channel_impl_map.hpp new file mode 100644 index 00000000..8cdb0666 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_channel_impl_map.hpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htclow_mux_channel_impl.hpp" + +namespace ams::htclow::mux { + + class ChannelImplMap { + NON_COPYABLE(ChannelImplMap); + NON_MOVEABLE(ChannelImplMap); + public: + static constexpr int MaxChannelCount = 64; + + using MapType = util::FixedMap<impl::ChannelInternalType, int>; + + static constexpr size_t MapRequiredMemorySize = MapType::GetRequiredMemorySize(MaxChannelCount); + private: + PacketFactory *m_packet_factory; + ctrl::HtcctrlStateMachine *m_state_machine; + TaskManager *m_task_manager; + os::Event *m_event; + u8 m_map_buffer[MapRequiredMemorySize]; + MapType m_map; + util::TypedStorage<ChannelImpl> m_channel_storage[MaxChannelCount]; + bool m_storage_valid[MaxChannelCount]; + public: + ChannelImplMap(PacketFactory *pf, ctrl::HtcctrlStateMachine *sm, TaskManager *tm, os::Event *ev); + + ChannelImpl &GetChannelImpl(int index); + ChannelImpl &GetChannelImpl(impl::ChannelInternalType channel); + + bool Exists(impl::ChannelInternalType channel) const { + return m_map.find(channel) != m_map.end(); + } + + Result AddChannel(impl::ChannelInternalType channel); + Result RemoveChannel(impl::ChannelInternalType channel); + private: + public: + MapType &GetMap() { + return m_map; + } + + ChannelImpl &operator[](int index) { + return this->GetChannelImpl(index); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_global_send_buffer.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_global_send_buffer.cpp new file mode 100644 index 00000000..fe207680 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_global_send_buffer.cpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_mux_global_send_buffer.hpp" +#include "../htclow_packet_factory.hpp" + +namespace ams::htclow::mux { + + Packet *GlobalSendBuffer::GetNextPacket() { + if (!m_packet_list.empty()) { + return std::addressof(m_packet_list.front()); + } else { + return nullptr; + } + } + + Result GlobalSendBuffer::AddPacket(std::unique_ptr<Packet, PacketDeleter> ptr) { + /* Global send buffer only supports adding error packets. */ + R_UNLESS(ptr->GetHeader()->packet_type == PacketType_Error, htclow::ResultInvalidArgument()); + + /* Check if we already have an error packet for the channel. */ + for (const auto &packet : m_packet_list) { + R_SUCCEED_IF(packet.GetHeader()->channel == ptr->GetHeader()->channel); + } + + /* We don't, so push back a new one. */ + m_packet_list.push_back(*(ptr.release())); + + R_SUCCEED(); + } + + void GlobalSendBuffer::RemovePacket() { + auto *packet = std::addressof(m_packet_list.front()); + m_packet_list.pop_front(); + + m_packet_factory->Delete(packet); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_global_send_buffer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_global_send_buffer.hpp new file mode 100644 index 00000000..31575ab5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_global_send_buffer.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../htclow_packet.hpp" + +namespace ams::htclow { + + class PacketFactory; + +} + +namespace ams::htclow::mux { + + class GlobalSendBuffer { + private: + using PacketList = util::IntrusiveListBaseTraits<Packet>::ListType; + private: + PacketFactory *m_packet_factory; + PacketList m_packet_list; + public: + GlobalSendBuffer(PacketFactory *pf) : m_packet_factory(pf), m_packet_list() { /* ... */ } + + Packet *GetNextPacket(); + + Result AddPacket(std::unique_ptr<Packet, PacketDeleter> ptr); + void RemovePacket(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_ring_buffer.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_ring_buffer.cpp new file mode 100644 index 00000000..f47813b9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_ring_buffer.cpp @@ -0,0 +1,133 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_mux_ring_buffer.hpp" + +namespace ams::htclow::mux { + + void RingBuffer::Initialize(void *buffer, size_t buffer_size) { + /* Validate pre-conditions. */ + AMS_ASSERT(m_buffer == nullptr); + AMS_ASSERT(m_read_only_buffer == nullptr); + + /* Set our fields. */ + m_buffer = buffer; + m_buffer_size = buffer_size; + m_is_read_only = false; + } + + void RingBuffer::InitializeForReadOnly(const void *buffer, size_t buffer_size) { + /* Validate pre-conditions. */ + AMS_ASSERT(m_buffer == nullptr); + AMS_ASSERT(m_read_only_buffer == nullptr); + + /* Set our fields. */ + m_read_only_buffer = const_cast<void *>(buffer); + m_buffer_size = buffer_size; + m_data_size = buffer_size; + m_is_read_only = true; + } + + void RingBuffer::Clear() { + m_data_size = 0; + m_offset = 0; + m_can_discard = false; + } + + Result RingBuffer::Read(void *dst, size_t size) { + /* Copy the data. */ + R_TRY(this->Copy(dst, size)); + + /* Discard. */ + R_TRY(this->Discard(size)); + + R_SUCCEED(); + } + + Result RingBuffer::Write(const void *data, size_t size) { + /* Validate pre-conditions. */ + AMS_ASSERT(!m_is_read_only); + + /* Check that our buffer can hold the data. */ + R_UNLESS(m_buffer != nullptr, htclow::ResultChannelBufferOverflow()); + R_UNLESS(m_data_size + size <= m_buffer_size, htclow::ResultChannelBufferOverflow()); + + /* Determine position and copy sizes. */ + const size_t pos = (m_data_size + m_offset) % m_buffer_size; + const size_t left = std::min(m_buffer_size - pos, size); + const size_t over = size - left; + + /* Copy. */ + if (left != 0) { + std::memcpy(static_cast<u8 *>(m_buffer) + pos, data, left); + } + if (over != 0) { + std::memcpy(m_buffer, static_cast<const u8 *>(data) + left, over); + } + + /* Update our data size. */ + m_data_size += size; + + R_SUCCEED(); + } + + Result RingBuffer::Copy(void *dst, size_t size) { + /* Select buffer to discard from. */ + void *buffer = m_is_read_only ? m_read_only_buffer : m_buffer; + R_UNLESS(buffer != nullptr, htclow::ResultChannelBufferHasNotEnoughData()); + + /* Verify that we have enough data. */ + R_UNLESS(m_data_size >= size, htclow::ResultChannelBufferHasNotEnoughData()); + + /* Determine position and copy sizes. */ + const size_t pos = m_offset; + const size_t left = std::min(m_buffer_size - pos, size); + const size_t over = size - left; + + /* Copy. */ + if (left != 0) { + std::memcpy(dst, static_cast<const u8 *>(buffer) + pos, left); + } + if (over != 0) { + std::memcpy(static_cast<u8 *>(dst) + left, buffer, over); + } + + /* Mark that we can discard. */ + m_can_discard = true; + + R_SUCCEED(); + } + + Result RingBuffer::Discard(size_t size) { + /* Select buffer to discard from. */ + void *buffer = m_is_read_only ? m_read_only_buffer : m_buffer; + R_UNLESS(buffer != nullptr, htclow::ResultChannelBufferHasNotEnoughData()); + + /* Verify that the data we're discarding has been read. */ + R_UNLESS(m_can_discard, htclow::ResultChannelCannotDiscard()); + + /* Verify that we have enough data. */ + R_UNLESS(m_data_size >= size, htclow::ResultChannelBufferHasNotEnoughData()); + + /* Discard. */ + m_offset = (m_offset + size) % m_buffer_size; + m_data_size -= size; + m_can_discard = false; + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_ring_buffer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_ring_buffer.hpp new file mode 100644 index 00000000..bacb6019 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_ring_buffer.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htclow::mux { + + class RingBuffer { + private: + void *m_buffer; + void *m_read_only_buffer; + bool m_is_read_only; + size_t m_buffer_size; + size_t m_data_size; + size_t m_offset; + bool m_can_discard; + public: + RingBuffer() : m_buffer(), m_read_only_buffer(), m_is_read_only(true), m_buffer_size(), m_data_size(), m_offset(), m_can_discard(false) { /* ... */ } + + void Initialize(void *buffer, size_t buffer_size); + void InitializeForReadOnly(const void *buffer, size_t buffer_size); + + void Clear(); + + size_t GetBufferSize() { return m_buffer_size; } + size_t GetDataSize() { return m_data_size; } + + Result Read(void *dst, size_t size); + Result Write(const void *data, size_t size); + + Result Copy(void *dst, size_t size); + + Result Discard(size_t size); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_send_buffer.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_send_buffer.cpp new file mode 100644 index 00000000..24ee4271 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_send_buffer.cpp @@ -0,0 +1,182 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_mux_channel_impl.hpp" +#include "../htclow_packet_factory.hpp" +#include "../htclow_default_channel_config.hpp" + +namespace ams::htclow::mux { + + SendBuffer::SendBuffer(impl::ChannelInternalType channel, PacketFactory *pf) + : m_channel(channel), m_packet_factory(pf), m_ring_buffer(), m_packet_list(), + m_version(ProtocolVersion), m_flow_control_enabled(true), m_max_packet_size(DefaultChannelConfig.max_packet_size) + { + /* ... */ + } + + SendBuffer::~SendBuffer() { + m_ring_buffer.Clear(); + this->Clear(); + } + + bool SendBuffer::IsPriorPacket(PacketType packet_type) const { + return packet_type == PacketType_MaxData; + } + + void SendBuffer::SetVersion(s16 version) { + /* Set version. */ + m_version = version; + } + + void SendBuffer::SetFlowControlEnabled(bool en) { + /* Set flow control enabled. */ + m_flow_control_enabled = en; + } + + void SendBuffer::MakeDataPacketHeader(PacketHeader *header, int body_size, s16 version, u64 share, u32 offset) const { + /* Set all packet fields. */ + header->signature = HtcGen2Signature; + header->offset = offset; + header->reserved = 0; + header->version = version; + header->body_size = body_size; + header->channel = m_channel; + header->packet_type = PacketType_Data; + header->share = share; + } + + void SendBuffer::CopyPacket(PacketHeader *header, PacketBody *body, int *out_body_size, const Packet &packet) { + /* Get the body size. */ + const int body_size = packet.GetBodySize(); + AMS_ASSERT(0 <= body_size && body_size <= static_cast<int>(sizeof(*body))); + + /* Copy the header. */ + std::memcpy(header, packet.GetHeader(), sizeof(*header)); + + /* Copy the body. */ + std::memcpy(body, packet.GetBody(), body_size); + + /* Set the output body size. */ + *out_body_size = body_size; + } + + bool SendBuffer::QueryNextPacket(PacketHeader *header, PacketBody *body, int *out_body_size, u64 max_data, u64 total_send_size, bool has_share, u64 share) { + /* Check for a max data packet. */ + if (!m_packet_list.empty()) { + this->CopyPacket(header, body, out_body_size, m_packet_list.front()); + return true; + } + + /* Check that we have data. */ + const auto ring_buffer_data_size = m_ring_buffer.GetDataSize(); + if (ring_buffer_data_size == 0) { + return false; + } + + /* Check that we're valid for flow control. */ + if (m_flow_control_enabled && !has_share) { + return false; + } + + /* Determine the sendable size. */ + const auto offset = total_send_size - ring_buffer_data_size; + const auto sendable_size = m_flow_control_enabled ? std::min<size_t>(share - offset, ring_buffer_data_size) : ring_buffer_data_size; + if (sendable_size == 0) { + return false; + } + + /* We're additionally bound by the actual packet size. */ + const auto data_size = std::min(sendable_size, m_max_packet_size); + + /* Make data packet header. */ + this->MakeDataPacketHeader(header, data_size, m_version, max_data, offset); + + /* Copy the data. */ + R_ABORT_UNLESS(m_ring_buffer.Copy(body, data_size)); + + /* Set output body size. */ + *out_body_size = data_size; + return true; + } + + void SendBuffer::AddPacket(std::unique_ptr<Packet, PacketDeleter> ptr) { + /* Get the packet. */ + auto *packet = ptr.release(); + + /* Check the packet type. */ + AMS_ABORT_UNLESS(this->IsPriorPacket(packet->GetHeader()->packet_type)); + + /* Add the packet. */ + m_packet_list.push_back(*packet); + } + + void SendBuffer::RemovePacket(const PacketHeader &header) { + /* Get the packet type. */ + const auto packet_type = header.packet_type; + + if (this->IsPriorPacket(packet_type)) { + /* Packet will be using our list. */ + auto *packet = std::addressof(m_packet_list.front()); + m_packet_list.pop_front(); + m_packet_factory->Delete(packet); + } else { + /* Packet managed by ring buffer. */ + AMS_ABORT_UNLESS(packet_type == PacketType_Data); + + /* Discard the packet's data. */ + const Result result = m_ring_buffer.Discard(header.body_size); + if (!htclow::ResultChannelCannotDiscard::Includes(result)) { + R_ABORT_UNLESS(result); + } + } + } + + size_t SendBuffer::AddData(const void *data, size_t size) { + /* Determine how much to actually add. */ + size = std::min(size, m_ring_buffer.GetBufferSize() - m_ring_buffer.GetDataSize()); + + /* Write the data. */ + R_ABORT_UNLESS(m_ring_buffer.Write(data, size)); + + /* Return the size we wrote. */ + return size; + } + + void SendBuffer::SetBuffer(void *buffer, size_t buffer_size) { + m_ring_buffer.Initialize(buffer, buffer_size); + } + + void SendBuffer::SetReadOnlyBuffer(const void *buffer, size_t buffer_size) { + m_ring_buffer.InitializeForReadOnly(buffer, buffer_size); + } + + void SendBuffer::SetMaxPacketSize(size_t max_packet_size) { + m_max_packet_size = max_packet_size; + } + + bool SendBuffer::Empty() { + return m_packet_list.empty() && m_ring_buffer.GetDataSize() == 0; + } + + void SendBuffer::Clear() { + while (!m_packet_list.empty()) { + auto *packet = std::addressof(m_packet_list.front()); + m_packet_list.pop_front(); + m_packet_factory->Delete(packet); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_send_buffer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_send_buffer.hpp new file mode 100644 index 00000000..f91a788a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_send_buffer.hpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../htclow_packet.hpp" +#include "htclow_mux_ring_buffer.hpp" + +namespace ams::htclow { + + class PacketFactory; + +} + +namespace ams::htclow::mux { + + class SendBuffer { + private: + using PacketList = util::IntrusiveListBaseTraits<Packet>::ListType; + private: + impl::ChannelInternalType m_channel; + PacketFactory *m_packet_factory; + RingBuffer m_ring_buffer; + PacketList m_packet_list; + s16 m_version; + bool m_flow_control_enabled; + size_t m_max_packet_size; + private: + bool IsPriorPacket(PacketType packet_type) const; + + void MakeDataPacketHeader(PacketHeader *header, int body_size, s16 version, u64 share, u32 offset) const; + + void CopyPacket(PacketHeader *header, PacketBody *body, int *out_body_size, const Packet &packet); + public: + SendBuffer(impl::ChannelInternalType channel, PacketFactory *pf); + ~SendBuffer(); + + void SetVersion(s16 version); + void SetFlowControlEnabled(bool en); + + bool QueryNextPacket(PacketHeader *header, PacketBody *body, int *out_body_size, u64 max_data, u64 total_send_size, bool has_share, u64 share); + + void AddPacket(std::unique_ptr<Packet, PacketDeleter> ptr); + void RemovePacket(const PacketHeader &header); + + size_t AddData(const void *data, size_t size); + + void SetBuffer(void *buffer, size_t buffer_size); + void SetReadOnlyBuffer(const void *buffer, size_t buffer_size); + void SetMaxPacketSize(size_t max_packet_size); + + bool Empty(); + + void Clear(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_task_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_task_manager.cpp new file mode 100644 index 00000000..4d62d0a9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_task_manager.cpp @@ -0,0 +1,165 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htclow_mux_task_manager.hpp" + +namespace ams::htclow::mux { + + os::EventType *TaskManager::GetTaskEvent(u32 task_id) { + /* Check pre-conditions. */ + AMS_ASSERT(task_id < MaxTaskCount); + AMS_ASSERT(m_valid[task_id]); + + return std::addressof(m_tasks[task_id].event); + } + + EventTrigger TaskManager::GetTrigger(u32 task_id) { + /* Check pre-conditions. */ + AMS_ASSERT(task_id < MaxTaskCount); + AMS_ASSERT(m_valid[task_id]); + + return m_tasks[task_id].event_trigger; + } + + Result TaskManager::AllocateTask(u32 *out_task_id, impl::ChannelInternalType channel) { + /* Find a free task. */ + u32 task_id = 0; + for (task_id = 0; task_id < util::size(m_tasks); ++task_id) { + if (!m_valid[task_id]) { + break; + } + } + + /* Verify the task is free. */ + R_UNLESS(task_id < util::size(m_tasks), htclow::ResultOutOfTask()); + + /* Mark the task as allocated. */ + m_valid[task_id] = true; + + /* Setup the task. */ + m_tasks[task_id].channel = channel; + m_tasks[task_id].has_event_trigger = false; + os::InitializeEvent(std::addressof(m_tasks[task_id].event), false, os::EventClearMode_ManualClear); + + /* Return the task id. */ + *out_task_id = task_id; + R_SUCCEED(); + } + + void TaskManager::FreeTask(u32 task_id) { + /* Check pre-conditions. */ + AMS_ASSERT(task_id < MaxTaskCount); + + /* Invalidate the task. */ + if (m_valid[task_id]) { + os::FinalizeEvent(std::addressof(m_tasks[task_id].event)); + m_valid[task_id] = false; + } + } + + void TaskManager::ConfigureConnectTask(u32 task_id) { + /* Check pre-conditions. */ + AMS_ASSERT(task_id < MaxTaskCount); + AMS_ASSERT(m_valid[task_id]); + + /* Set the task type. */ + m_tasks[task_id].type = TaskType_Connect; + } + + void TaskManager::ConfigureFlushTask(u32 task_id) { + /* Check pre-conditions. */ + AMS_ASSERT(task_id < MaxTaskCount); + AMS_ASSERT(m_valid[task_id]); + + /* Set the task type. */ + m_tasks[task_id].type = TaskType_Flush; + } + + void TaskManager::ConfigureReceiveTask(u32 task_id, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(task_id < MaxTaskCount); + AMS_ASSERT(m_valid[task_id]); + + /* Set the task type. */ + m_tasks[task_id].type = TaskType_Receive; + + /* Set the task size. */ + m_tasks[task_id].size = size; + } + + void TaskManager::ConfigureSendTask(u32 task_id) { + /* Check pre-conditions. */ + AMS_ASSERT(task_id < MaxTaskCount); + AMS_ASSERT(m_valid[task_id]); + + /* Set the task type. */ + m_tasks[task_id].type = TaskType_Send; + } + + void TaskManager::NotifyDisconnect(impl::ChannelInternalType channel) { + for (auto i = 0; i < MaxTaskCount; ++i) { + if (m_valid[i] && m_tasks[i].channel == channel) { + this->CompleteTask(i, EventTrigger_Disconnect); + } + } + } + + void TaskManager::NotifyReceiveData(impl::ChannelInternalType channel, size_t size) { + for (auto i = 0; i < MaxTaskCount; ++i) { + if (m_valid[i] && m_tasks[i].channel == channel && m_tasks[i].type == TaskType_Receive && m_tasks[i].size <= size) { + this->CompleteTask(i, EventTrigger_ReceiveData); + } + } + } + + void TaskManager::NotifySendReady() { + for (auto i = 0; i < MaxTaskCount; ++i) { + if (m_valid[i] && m_tasks[i].type == TaskType_Send) { + this->CompleteTask(i, EventTrigger_SendReady); + } + } + } + + void TaskManager::NotifySendBufferEmpty(impl::ChannelInternalType channel) { + for (auto i = 0; i < MaxTaskCount; ++i) { + if (m_valid[i] && m_tasks[i].channel == channel && m_tasks[i].type == TaskType_Flush) { + this->CompleteTask(i, EventTrigger_SendBufferEmpty); + } + } + } + + void TaskManager::NotifyConnectReady() { + for (auto i = 0; i < MaxTaskCount; ++i) { + if (m_valid[i] && m_tasks[i].type == TaskType_Connect) { + this->CompleteTask(i, EventTrigger_ConnectReady); + } + } + } + + void TaskManager::CompleteTask(int index, EventTrigger trigger) { + /* Check pre-conditions. */ + AMS_ASSERT(0 <= index && index < MaxTaskCount); + AMS_ASSERT(m_valid[index]); + + /* Complete the task. */ + if (!m_tasks[index].has_event_trigger) { + m_tasks[index].has_event_trigger = true; + m_tasks[index].event_trigger = trigger; + os::SignalEvent(std::addressof(m_tasks[index].event)); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_task_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_task_manager.hpp new file mode 100644 index 00000000..5644d0fc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htclow/mux/htclow_mux_task_manager.hpp @@ -0,0 +1,75 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htclow::mux { + + constexpr inline int MaxTaskCount = 0x80; + + enum EventTrigger : u8 { + EventTrigger_Disconnect = 1, + EventTrigger_ReceiveData = 2, + EventTrigger_SendComplete = 4, + EventTrigger_SendReady = 5, + EventTrigger_SendBufferEmpty = 10, + EventTrigger_ConnectReady = 11, + }; + + class TaskManager { + private: + enum TaskType : u8 { + TaskType_Receive = 0, + TaskType_Send = 1, + TaskType_Flush = 6, + TaskType_Connect = 7, + }; + + struct Task { + impl::ChannelInternalType channel; + os::EventType event; + bool has_event_trigger; + EventTrigger event_trigger; + TaskType type; + size_t size; + }; + private: + bool m_valid[MaxTaskCount]; + Task m_tasks[MaxTaskCount]; + public: + TaskManager() : m_valid() { /* ... */ } + + Result AllocateTask(u32 *out_task_id, impl::ChannelInternalType channel); + void FreeTask(u32 task_id); + + os::EventType *GetTaskEvent(u32 task_id); + EventTrigger GetTrigger(u32 task_id); + + void ConfigureConnectTask(u32 task_id); + void ConfigureFlushTask(u32 task_id); + void ConfigureReceiveTask(u32 task_id, size_t size); + void ConfigureSendTask(u32 task_id); + + void NotifyDisconnect(impl::ChannelInternalType channel); + void NotifyReceiveData(impl::ChannelInternalType channel, size_t size); + void NotifySendReady(); + void NotifySendBufferEmpty(impl::ChannelInternalType channel); + void NotifyConnectReady(); + + void CompleteTask(int index, EventTrigger trigger); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/client/htcs_session.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/client/htcs_session.hpp new file mode 100644 index 00000000..597f2818 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/client/htcs_session.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htcs::client { + + void InitializeSessionManager(tma::IHtcsManager **out_manager, tma::IHtcsManager **out_monitor, u32 num_sessions); + void FinalizeSessionManager(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/client/htcs_session.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/client/htcs_session.os.horizon.cpp new file mode 100644 index 00000000..2e643980 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/client/htcs_session.os.horizon.cpp @@ -0,0 +1,276 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_session.hpp" + +extern "C" { + +#include <switch/services/htcs.h> + +} + +namespace ams::htcs::client { + + namespace { + + struct HtcsObjectAllocatorTag; + using ObjectAllocator = ams::sf::ExpHeapStaticAllocator<16_KB, HtcsObjectAllocatorTag>; + using ObjectFactory = ams::sf::ObjectFactory<typename ObjectAllocator::Policy>; + + class StaticAllocatorInitializer { + public: + StaticAllocatorInitializer() { + ObjectAllocator::Initialize(lmem::CreateOption_ThreadSafe); + } + } g_static_allocator_initializer; + + } + + namespace { + + class RemoteSocket { + private: + ::HtcsSocket m_s; + public: + RemoteSocket(::HtcsSocket &s) : m_s(s) { /* ... */ } + ~RemoteSocket() { ::htcsCloseSocket(std::addressof(m_s)); } + public: + Result Accept(sf::Out<s32> out_err, sf::Out<sf::SharedPointer<tma::ISocket>> out, sf::Out<htcs::SockAddrHtcs> out_address) { AMS_UNUSED(out_err, out, out_address); AMS_ABORT("Not Implemented"); } + Result Recv(sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::OutAutoSelectBuffer &buffer, s32 flags) { AMS_UNUSED(out_err, out_size, buffer, flags); AMS_ABORT("Not Implemented"); } + Result Send(sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::InAutoSelectBuffer &buffer, s32 flags) { AMS_UNUSED(out_err, out_size, buffer, flags); AMS_ABORT("Not Implemented"); } + Result RecvLargeStart(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, s32 unaligned_size_start, s32 unaligned_size_end, s64 aligned_size, sf::CopyHandle &&mem_handle, s32 flags) { AMS_UNUSED(out_task_id, out_event, unaligned_size_start, unaligned_size_end, aligned_size, mem_handle, flags); AMS_ABORT("Not Implemented"); } + Result SendStartOld(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, const sf::InAutoSelectBuffer &buffer, s32 flags) { AMS_UNUSED(out_task_id, out_event, buffer, flags); AMS_ABORT("Not Implemented"); } + Result SendLargeStart(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, const sf::InAutoSelectBuffer &start_buffer, const sf::InAutoSelectBuffer &end_buffer, sf::CopyHandle &&mem_handle, s64 aligned_size, s32 flags) { AMS_UNUSED(out_task_id, out_event, start_buffer, end_buffer, mem_handle, aligned_size, flags); AMS_ABORT("Not Implemented"); } + Result ContinueSendOld(sf::Out<s64> out_size, sf::Out<bool> out_wait, const sf::InAutoSelectBuffer &buffer, u32 task_id) { AMS_UNUSED(out_size, out_wait, buffer, task_id); AMS_ABORT("Not Implemented"); } + + Result Close(sf::Out<s32> out_err, sf::Out<s32> out_res); + Result Connect(sf::Out<s32> out_err, sf::Out<s32> out_res, const htcs::SockAddrHtcs &address); + Result Bind(sf::Out<s32> out_err, sf::Out<s32> out_res, const htcs::SockAddrHtcs &address); + Result Listen(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 backlog_count); + Result Shutdown(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 how); + Result Fcntl(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 command, s32 value); + + Result AcceptStart(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event); + Result AcceptResults(sf::Out<s32> out_err, sf::Out<sf::SharedPointer<tma::ISocket>> out, sf::Out<htcs::SockAddrHtcs> out_address, u32 task_id); + + Result RecvStart(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, s32 mem_size, s32 flags); + Result RecvResults(sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::OutAutoSelectBuffer &buffer, u32 task_id); + + Result SendStart(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, const sf::InNonSecureAutoSelectBuffer &buffer, s32 flags); + Result SendResults(sf::Out<s32> out_err, sf::Out<s64> out_size, u32 task_id); + + Result StartSend(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, sf::Out<s64> out_max_size, s64 size, s32 flags); + Result ContinueSend(sf::Out<s64> out_size, sf::Out<bool> out_wait, const sf::InNonSecureAutoSelectBuffer &buffer, u32 task_id); + Result EndSend(sf::Out<s32> out_err, sf::Out<s64> out_size, u32 task_id); + + Result StartRecv(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, s64 size, s32 flags); + Result EndRecv(sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::OutAutoSelectBuffer &buffer, u32 task_id); + + Result GetPrimitive(sf::Out<s32> out); + }; + static_assert(tma::IsISocket<RemoteSocket>); + + class RemoteManager { + public: + Result Socket(sf::Out<s32> out_err, sf::Out<s32> out_sock) { AMS_UNUSED(out_err, out_sock); AMS_ABORT("Not Implemented"); } + Result Close(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc) { AMS_UNUSED(out_err, out_res, desc); AMS_ABORT("Not Implemented"); } + Result Connect(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, const htcs::SockAddrHtcs &address) { AMS_UNUSED(out_err, out_res, desc, address); AMS_ABORT("Not Implemented"); } + Result Bind(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, const htcs::SockAddrHtcs &address) { AMS_UNUSED(out_err, out_res, desc, address); AMS_ABORT("Not Implemented"); } + Result Listen(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, s32 backlog_count) { AMS_UNUSED(out_err, out_res, desc, backlog_count); AMS_ABORT("Not Implemented"); } + Result Accept(sf::Out<s32> out_err, sf::Out<s32> out_res, sf::Out<htcs::SockAddrHtcs> out_address, s32 desc) { AMS_UNUSED(out_err, out_res, out_address, desc); AMS_ABORT("Not Implemented"); } + Result Recv(sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::OutBuffer &buffer, s32 desc, s32 flags) { AMS_UNUSED(out_err, out_size, buffer, desc, flags); AMS_ABORT("Not Implemented"); } + Result Send(sf::Out<s32> out_err, sf::Out<s64> out_size, s32 desc, const sf::InBuffer &buffer, s32 flags) { AMS_UNUSED(out_err, out_size, desc, buffer, flags); AMS_ABORT("Not Implemented"); } + Result Shutdown(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, s32 how) { AMS_UNUSED(out_err, out_res, desc, how); AMS_ABORT("Not Implemented"); } + Result Fcntl(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, s32 command, s32 value) { AMS_UNUSED(out_err, out_res, desc, command, value); AMS_ABORT("Not Implemented"); } + Result CreateSocketOld(sf::Out<s32> out_err, sf::Out<sf::SharedPointer<tma::ISocket>> out) { AMS_UNUSED(out_err, out); AMS_ABORT("Not Implemented"); } + + Result GetPeerNameAny(sf::Out<htcs::HtcsPeerName> out); + Result GetDefaultHostName(sf::Out<htcs::HtcsPeerName> out); + Result CreateSocket(sf::Out<s32> out_err, sf::Out<sf::SharedPointer<tma::ISocket>> out, bool enable_disconnection_emulation); + Result RegisterProcessId(const sf::ClientProcessId &client_pid); + Result MonitorManager(const sf::ClientProcessId &client_pid); + Result StartSelect(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, const sf::InMapAliasArray<s32> &read_handles, const sf::InMapAliasArray<s32> &write_handles, const sf::InMapAliasArray<s32> &exception_handles, s64 tv_sec, s64 tv_usec); + Result EndSelect(sf::Out<s32> out_err, sf::Out<s32> out_count, const sf::OutMapAliasArray<s32> &read_handles, const sf::OutMapAliasArray<s32> &write_handles, const sf::OutMapAliasArray<s32> &exception_handles, u32 task_id); + }; + static_assert(tma::IsIHtcsManager<RemoteManager>); + + Result RemoteManager::GetPeerNameAny(sf::Out<htcs::HtcsPeerName> out) { + static_assert(sizeof(htcs::HtcsPeerName) == sizeof(::HtcsPeerName)); + R_RETURN(::htcsGetPeerNameAny(reinterpret_cast<::HtcsPeerName *>(out.GetPointer()))); + } + + Result RemoteManager::GetDefaultHostName(sf::Out<htcs::HtcsPeerName> out) { + static_assert(sizeof(htcs::HtcsPeerName) == sizeof(::HtcsPeerName)); + R_RETURN(::htcsGetDefaultHostName(reinterpret_cast<::HtcsPeerName *>(out.GetPointer()))); + } + + Result RemoteManager::CreateSocket(sf::Out<s32> out_err, sf::Out<sf::SharedPointer<tma::ISocket>> out, bool enable_disconnection_emulation) { + ::HtcsSocket libnx_socket; + R_TRY(::htcsCreateSocket(out_err.GetPointer(), std::addressof(libnx_socket), enable_disconnection_emulation)); + + R_SUCCEED_IF(*out_err != 0); + + *out = ObjectFactory::CreateSharedEmplaced<tma::ISocket, RemoteSocket>(libnx_socket); + R_SUCCEED(); + } + + Result RemoteManager::RegisterProcessId(const sf::ClientProcessId &client_pid) { + /* Handled by libnx init. */ + AMS_UNUSED(client_pid); + R_SUCCEED(); + } + + Result RemoteManager::MonitorManager(const sf::ClientProcessId &client_pid) { + /* Handled by libnx init. */ + AMS_UNUSED(client_pid); + R_SUCCEED(); + } + + Result RemoteManager::StartSelect(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, const sf::InMapAliasArray<s32> &read_handles, const sf::InMapAliasArray<s32> &write_handles, const sf::InMapAliasArray<s32> &exception_handles, s64 tv_sec, s64 tv_usec) { + os::NativeHandle event_handle; + R_TRY(::htcsStartSelect(out_task_id.GetPointer(), std::addressof(event_handle), read_handles.GetPointer(), read_handles.GetSize(), write_handles.GetPointer(), write_handles.GetSize(), exception_handles.GetPointer(), exception_handles.GetSize(), tv_sec, tv_usec)); + + out_event.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result RemoteManager::EndSelect(sf::Out<s32> out_err, sf::Out<s32> out_count, const sf::OutMapAliasArray<s32> &read_handles, const sf::OutMapAliasArray<s32> &write_handles, const sf::OutMapAliasArray<s32> &exception_handles, u32 task_id) { + R_RETURN(::htcsEndSelect(out_err.GetPointer(), out_count.GetPointer(), read_handles.GetPointer(), read_handles.GetSize(), write_handles.GetPointer(), write_handles.GetSize(), exception_handles.GetPointer(), exception_handles.GetSize(), task_id)); + } + + Result RemoteSocket::Close(sf::Out<s32> out_err, sf::Out<s32> out_res) { + R_RETURN(::htcsSocketClose(std::addressof(m_s), out_err.GetPointer(), out_res.GetPointer())); + } + + Result RemoteSocket::Connect(sf::Out<s32> out_err, sf::Out<s32> out_res, const htcs::SockAddrHtcs &address) { + static_assert(sizeof(htcs::SockAddrHtcs) == sizeof(::HtcsSockAddr)); + R_RETURN(::htcsSocketConnect(std::addressof(m_s), out_err.GetPointer(), out_res.GetPointer(), reinterpret_cast<const ::HtcsSockAddr *>(std::addressof(address)))); + } + + Result RemoteSocket::Bind(sf::Out<s32> out_err, sf::Out<s32> out_res, const htcs::SockAddrHtcs &address) { + static_assert(sizeof(htcs::SockAddrHtcs) == sizeof(::HtcsSockAddr)); + R_RETURN(::htcsSocketBind(std::addressof(m_s), out_err.GetPointer(), out_res.GetPointer(), reinterpret_cast<const ::HtcsSockAddr *>(std::addressof(address)))); + } + + Result RemoteSocket::Listen(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 backlog_count) { + R_RETURN(::htcsSocketListen(std::addressof(m_s), out_err.GetPointer(), out_res.GetPointer(), backlog_count)); + } + + Result RemoteSocket::Shutdown(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 how) { + R_RETURN(::htcsSocketShutdown(std::addressof(m_s), out_err.GetPointer(), out_res.GetPointer(), how)); + } + + Result RemoteSocket::Fcntl(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 command, s32 value) { + R_RETURN(::htcsSocketFcntl(std::addressof(m_s), out_err.GetPointer(), out_res.GetPointer(), command, value)); + } + + Result RemoteSocket::AcceptStart(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event) { + os::NativeHandle event_handle; + R_TRY(::htcsSocketAcceptStart(std::addressof(m_s), out_task_id.GetPointer(), std::addressof(event_handle))); + + out_event.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result RemoteSocket::AcceptResults(sf::Out<s32> out_err, sf::Out<sf::SharedPointer<tma::ISocket>> out, sf::Out<htcs::SockAddrHtcs> out_address, u32 task_id) { + static_assert(sizeof(htcs::SockAddrHtcs) == sizeof(::HtcsSockAddr)); + ::HtcsSocket libnx_socket; + R_TRY(::htcsSocketAcceptResults(std::addressof(m_s), out_err.GetPointer(), std::addressof(libnx_socket), reinterpret_cast<::HtcsSockAddr *>(out_address.GetPointer()), task_id)); + + R_SUCCEED_IF(*out_err != 0); + + *out = ObjectFactory::CreateSharedEmplaced<tma::ISocket, RemoteSocket>(libnx_socket); + R_SUCCEED(); + } + + Result RemoteSocket::RecvStart(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, s32 mem_size, s32 flags) { + os::NativeHandle event_handle; + R_TRY(::htcsSocketRecvStart(std::addressof(m_s), out_task_id.GetPointer(), std::addressof(event_handle), mem_size, flags)); + + out_event.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result RemoteSocket::RecvResults(sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::OutAutoSelectBuffer &buffer, u32 task_id) { + R_RETURN(::htcsSocketRecvResults(std::addressof(m_s), out_err.GetPointer(), out_size.GetPointer(), buffer.GetPointer(), buffer.GetSize(), task_id)); + } + + Result RemoteSocket::SendStart(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, const sf::InNonSecureAutoSelectBuffer &buffer, s32 flags) { + os::NativeHandle event_handle; + R_TRY(::htcsSocketSendStart(std::addressof(m_s), out_task_id.GetPointer(), std::addressof(event_handle), buffer.GetPointer(), buffer.GetSize(), flags)); + + out_event.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result RemoteSocket::SendResults(sf::Out<s32> out_err, sf::Out<s64> out_size, u32 task_id) { + R_RETURN(::htcsSocketSendResults(std::addressof(m_s), out_err.GetPointer(), out_size.GetPointer(), task_id)); + } + + Result RemoteSocket::StartSend(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, sf::Out<s64> out_max_size, s64 size, s32 flags) { + os::NativeHandle event_handle; + R_TRY(::htcsSocketStartSend(std::addressof(m_s), out_task_id.GetPointer(), std::addressof(event_handle), out_max_size.GetPointer(), size, flags)); + + out_event.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result RemoteSocket::ContinueSend(sf::Out<s64> out_size, sf::Out<bool> out_wait, const sf::InNonSecureAutoSelectBuffer &buffer, u32 task_id) { + R_RETURN(::htcsSocketContinueSend(std::addressof(m_s), out_size.GetPointer(), out_wait.GetPointer(), buffer.GetPointer(), buffer.GetSize(), task_id)); + } + + Result RemoteSocket::EndSend(sf::Out<s32> out_err, sf::Out<s64> out_size, u32 task_id) { + R_RETURN(::htcsSocketEndSend(std::addressof(m_s), out_err.GetPointer(), out_size.GetPointer(), task_id)); + } + + Result RemoteSocket::StartRecv(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, s64 size, s32 flags) { + os::NativeHandle event_handle; + R_TRY(::htcsSocketStartRecv(std::addressof(m_s), out_task_id.GetPointer(), std::addressof(event_handle), size, flags)); + + out_event.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result RemoteSocket::EndRecv(sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::OutAutoSelectBuffer &buffer, u32 task_id) { + R_RETURN(::htcsSocketEndRecv(std::addressof(m_s), out_err.GetPointer(), out_size.GetPointer(), buffer.GetPointer(), buffer.GetSize(), task_id)); + } + + Result RemoteSocket::GetPrimitive(sf::Out<s32> out) { + R_RETURN(::htcsSocketGetPrimitive(std::addressof(m_s), out.GetPointer())); + } + + } + + void InitializeSessionManager(tma::IHtcsManager **out_manager, tma::IHtcsManager **out_monitor, u32 num_sessions) { + /* Ensure we can contact the libnx wrapper. */ + R_ABORT_UNLESS(sm::Initialize()); + + /* Initialize the libnx wrapper. */ + R_ABORT_UNLESS(::htcsInitialize(num_sessions)); + + /* Create the output objects. */ + *out_manager = ObjectFactory::CreateSharedEmplaced<tma::IHtcsManager, RemoteManager>().Detach(); + + /* Create the output objects. */ + *out_monitor = ObjectFactory::CreateSharedEmplaced<tma::IHtcsManager, RemoteManager>().Detach(); + } + + void FinalizeSessionManager() { + /* Exit the libnx wrapper. */ + ::htcsExit(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/client/htcs_session.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/client/htcs_session.os.windows.cpp new file mode 100644 index 00000000..55d5f0ed --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/client/htcs_session.os.windows.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_session.hpp" + +namespace ams::htcs::client { + + void InitializeSessionManager(tma::IHtcsManager **out_manager, tma::IHtcsManager **out_monitor, u32 num_sessions) { + AMS_UNUSED(out_manager, out_monitor, num_sessions); + AMS_ABORT("TODO"); + } + + void FinalizeSessionManager() { + AMS_ABORT("TODO"); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/client/htcs_virtual_socket_collection.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/client/htcs_virtual_socket_collection.cpp new file mode 100644 index 00000000..525939c6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/client/htcs_virtual_socket_collection.cpp @@ -0,0 +1,834 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_session.hpp" +#include "htcs_virtual_socket_collection.hpp" + +namespace ams::htcs::client { + + namespace { + + constexpr inline s32 InvalidSocket = -1; + constexpr inline s32 InvalidPrimitive = -1; + + } + + /* Declare client functions. */ + sf::SharedPointer<tma::ISocket> socket(s32 &last_error); + s32 close(sf::SharedPointer<tma::ISocket> socket, s32 &last_error); + s32 bind(sf::SharedPointer<tma::ISocket> socket, const htcs::SockAddrHtcs *address, s32 &last_error); + s32 listen(sf::SharedPointer<tma::ISocket> socket, s32 backlog_count, s32 &last_error); + sf::SharedPointer<tma::ISocket> accept(sf::SharedPointer<tma::ISocket> socket, htcs::SockAddrHtcs *address, s32 &last_error); + s32 fcntl(sf::SharedPointer<tma::ISocket> socket, s32 command, s32 value, s32 &last_error); + + s32 shutdown(sf::SharedPointer<tma::ISocket> socket, s32 how, s32 &last_error); + + ssize_t recv(sf::SharedPointer<tma::ISocket> socket, void *buffer, size_t buffer_size, s32 flags, s32 &last_error); + ssize_t send(sf::SharedPointer<tma::ISocket> socket, const void *buffer, size_t buffer_size, s32 flags, s32 &last_error); + + s32 connect(sf::SharedPointer<tma::ISocket> socket, const htcs::SockAddrHtcs *address, s32 &last_error); + + s32 select(s32 * const read, s32 &num_read, s32 * const write, s32 &num_write, s32 * const except, s32 &num_except, htcs::TimeVal *timeout, s32 &last_error); + + struct VirtualSocket { + s32 m_id; + s32 m_primitive; + sf::SharedPointer<tma::ISocket> m_socket; + bool m_do_bind; + htcs::SockAddrHtcs m_address; + s32 m_listen_backlog_count; + s32 m_fcntl_command; + s32 m_fcntl_value; + bool m_blocking; + + + VirtualSocket() { + /* Initialize. */ + this->Init(); + } + + ~VirtualSocket() { /* ... */ } + + void Init() { + /* Setup fields. */ + m_id = InvalidSocket; + m_primitive = InvalidPrimitive; + m_socket = nullptr; + m_blocking = true; + m_do_bind = false; + + std::memset(std::addressof(m_address), 0, sizeof(m_address)); + + m_listen_backlog_count = -1; + + m_fcntl_command = -1; + m_fcntl_value = 0; + } + + s32 Bind(const htcs::SockAddrHtcs *address, s32 &last_error) { + /* Mark the bind. */ + m_do_bind = true; + + /* Set our address. */ + std::memcpy(std::addressof(m_address), address, sizeof(m_address)); + + /* Clear the error. */ + last_error = 0; + return 0; + } + + s32 Listen(s32 backlog_count, s32 &last_error) { + s32 res = -1; + if (m_do_bind) { + /* Set backlog count. */ + m_listen_backlog_count = std::max(backlog_count, 1); + + /* Clear error. */ + last_error = 0; + res = 0; + } else { + last_error = HTCS_EINVAL; + } + return res; + } + + s32 Fcntl(s32 command, s32 value, s32 &last_error) { + /* Clear error. */ + s32 res = 0; + last_error = 0; + + if (command == HTCS_F_SETFL) { + m_fcntl_command = command; + m_fcntl_value = value; + + m_blocking = (value & HTCS_O_NONBLOCK) == 0; + } else if (command == HTCS_F_GETFL) { + res = m_fcntl_value; + } else { + last_error = HTCS_EINVAL; + res = -1; + } + + return res; + } + + s32 SetSocket(sf::SharedPointer<tma::ISocket> socket, s32 &last_error) { + s32 res = 0; + + if (m_socket == nullptr && socket != nullptr) { + /* Set our socket. */ + m_socket = socket; + + /* Bind, fcntl, and listen, since those may have been deferred. */ + if (m_do_bind) { + res = bind(m_socket, std::addressof(m_address), last_error); + } + + if (res == 0 && m_fcntl_command != -1) { + res = fcntl(m_socket, m_fcntl_command, m_fcntl_value, last_error); + } + + if (res == 0 && m_listen_backlog_count > 0) { + res = listen(m_socket, m_listen_backlog_count, last_error); + } + } + + return res; + } + }; + + VirtualSocketCollection::VirtualSocketCollection() + : m_socket_list(nullptr), + m_list_count(0), + m_list_size(0), + m_next_id(1), + m_mutex() + { + /* ... */ + } + + VirtualSocketCollection::~VirtualSocketCollection() { + /* Clear ourselves. */ + this->Clear(); + + /* Destroy all sockets in our list. */ + for (auto i = 0; i < m_list_size; ++i) { + std::destroy_at(m_socket_list + i); + } + + /* Clear the backing memory for our socket list. */ + std::memset(m_buffer, 0, sizeof(VirtualSocket) * m_list_size); + } + + size_t VirtualSocketCollection::GetWorkingMemorySize(int num_sockets) { + AMS_ASSERT(num_sockets < htcs::SocketCountMax); + return num_sockets * sizeof(VirtualSocket); + } + + void VirtualSocketCollection::Init(void *buffer, size_t buffer_size) { + /* Set our buffer. */ + m_buffer = buffer; + m_buffer_size = buffer_size; + + /* Configure our list. */ + m_list_size = static_cast<s32>(m_buffer_size / sizeof(VirtualSocket)); + m_list_size = std::min(m_list_size, htcs::SocketCountMax); + + /* Initialize our list. */ + m_socket_list = static_cast<VirtualSocket *>(m_buffer); + for (auto i = 0; i < m_list_size; ++i) { + std::construct_at(m_socket_list + i); + } + } + + void VirtualSocketCollection::Clear() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Clear our list. */ + m_list_count = 0; + } + + s32 VirtualSocketCollection::Socket(s32 &error_code) { + /* Clear error code. */ + error_code = 0; + + /* Create the socket. */ + return this->CreateSocket(sf::SharedPointer<tma::ISocket>{nullptr}, error_code); + } + + s32 VirtualSocketCollection::Close(s32 id, s32 &error_code) { + /* Clear error code. */ + error_code = 0; + + /* Prepare to find the socket. */ + s32 res = 0; + sf::SharedPointer<tma::ISocket> socket = nullptr; + + /* Find the socket. */ + s32 index; + { + std::scoped_lock lk(m_mutex); + + if (index = this->Find(id, std::addressof(error_code)); index >= 0) { + /* Get the socket's object. */ + VirtualSocket *virt_socket = m_socket_list + index; + socket = virt_socket->m_socket; + + /* Move the list. */ + for (/* ... */; index < m_list_count - 1; ++index) { + m_socket_list[index] = m_socket_list[index + 1]; + } + + /* Clear the now unused last list entry. */ + m_socket_list[index].Init(); + + /* Decrement our list count. */ + --m_list_count; + } + } + + /* If we found the socket, close it. */ + if (socket != nullptr) { + close(socket, error_code); + + /* Clear the error code. */ + res = 0; + error_code = 0; + } + + return index >= 0 ? res : -1; + } + + s32 VirtualSocketCollection::Bind(s32 id, const htcs::SockAddrHtcs *address, s32 &error_code) { + /* Setup result/error code. */ + s32 res = -1; + error_code = 0; + + /* Get the socket. */ + sf::SharedPointer<tma::ISocket> socket = this->GetSocket(id, std::addressof(error_code)); + + /* If we found the socket, bind. */ + if (socket != nullptr) { + res = bind(socket, address, error_code); + } else if (error_code != HTCS_EBADF) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check if the socket is already bound. */ + if (const auto index = this->Find(id); index >= 0) { + const auto exists = this->HasAddr(address); + + if (m_socket_list[index].m_do_bind) { + error_code = HTCS_EINVAL; + res = -1; + } else if (exists) { + error_code = HTCS_EADDRINUSE; + res = -1; + } else { + res = m_socket_list[index].Bind(address, error_code); + } + } else { + error_code = HTCS_EBADF; + } + } + + return res; + } + s32 VirtualSocketCollection::Listen(s32 id, s32 backlog_count, s32 &error_code) { + /* Setup result/error code. */ + s32 res = -1; + error_code = 0; + + /* Get the socket. */ + sf::SharedPointer<tma::ISocket> socket = this->GetSocket(id, std::addressof(error_code)); + + /* If we found the socket, bind. */ + if (socket != nullptr) { + res = listen(socket, backlog_count, error_code); + } else if (error_code != HTCS_EBADF) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Try to listen on the virtual socket. */ + if (const auto index = this->Find(id); index >= 0) { + res = m_socket_list[index].Listen(backlog_count, error_code); + } + } + + return res; + } + + s32 VirtualSocketCollection::Accept(s32 id, htcs::SockAddrHtcs *address, s32 &error_code) { + /* Setup result/error code. */ + s32 res = -1; + error_code = 0; + + /* Declare socket that we're creating. */ + sf::SharedPointer<tma::ISocket> new_socket = nullptr; + + /* Get the socket. */ + sf::SharedPointer<tma::ISocket> socket = this->GetSocket(id, std::addressof(error_code)); + + /* If we found the socket, bind. */ + if (socket != nullptr) { + if (error_code != HTCS_ENONE) { + return -1; + } + + new_socket = this->DoAccept(socket, id, address, error_code); + } else if (error_code != HTCS_EBADF) { + /* Fetch the socket. */ + socket = this->FetchSocket(id, error_code); + + /* Wait for the socket. */ + while (socket == nullptr) { + /* Determine whether we should block/listen. */ + bool block_until_done = true; + bool listened = false; + + s32 index; + { + std::scoped_lock lk(m_mutex); + if (index = this->Find(id, std::addressof(error_code)); index >= 0) { + block_until_done = m_socket_list[index].m_blocking; + listened = m_socket_list[index].m_listen_backlog_count > 0; + } + } + + /* Check that the socket exists. */ + if (index < 0) { + error_code = HTCS_EINTR; + return -1; + } + + /* Check that the socket has been listened. */ + if (!listened) { + error_code = HTCS_EINVAL; + return -1; + } + + /* Check that we should block. */ + if (!block_until_done) { + error_code = HTCS_EWOULDBLOCK; + return -1; + } + + /* Wait before trying again. */ + os::SleepThread(TimeSpan::FromMilliSeconds(500)); + + /* Fetch the potentially updated socket. */ + socket = this->FetchSocket(id, error_code); + } + + /* Check that we haven't errored. */ + if (error_code != HTCS_ENONE) { + return -1; + } + + /* Do the accept. */ + new_socket = this->DoAccept(socket, id, address, error_code); + } + + /* If we have a new socket, register it. */ + if (new_socket != 0) { + res = this->CreateSocket(new_socket, error_code); + if (res < 0) { + s32 tmp_error_code; + close(new_socket, tmp_error_code); + } + } + + return res; + } + + s32 VirtualSocketCollection::Fcntl(s32 id, s32 command, s32 value, s32 &error_code) { + /* Setup result/error code. */ + s32 res = -1; + error_code = 0; + + /* Get the socket. */ + sf::SharedPointer<tma::ISocket> socket = this->GetSocket(id, std::addressof(error_code)); + + /* If we found the socket, bind. */ + if (socket != nullptr) { + res = fcntl(socket, command, value, error_code); + } else if (error_code != HTCS_EBADF) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Try to listen on the virtual socket. */ + if (const auto index = this->Find(id); index >= 0) { + res = m_socket_list[index].Fcntl(command, value, error_code); + } + } + + return res; + } + + s32 VirtualSocketCollection::Shutdown(s32 id, s32 how, s32 &error_code) { + /* Setup result/error code. */ + s32 res = -1; + error_code = 0; + + /* Get the socket. */ + sf::SharedPointer<tma::ISocket> socket = this->GetSocket(id, std::addressof(error_code)); + + /* If we found the socket, bind. */ + if (socket != nullptr) { + res = shutdown(socket, how, error_code); + } else if (error_code != HTCS_EBADF) { + error_code = HTCS_ENOTCONN; + } + + return res; + } + + ssize_t VirtualSocketCollection::Recv(s32 id, void *buffer, size_t buffer_size, s32 flags, s32 &error_code) { + /* Setup result/error code. */ + ssize_t res = -1; + error_code = 0; + + /* Fetch the socket. */ + sf::SharedPointer<tma::ISocket> socket = this->FetchSocket(id, error_code); + + /* If we found the socket, bind. */ + if (socket != nullptr) { + if (error_code != HTCS_ENONE) { + return -1; + } + res = recv(socket, buffer, buffer_size, flags, error_code); + } else if (error_code != HTCS_EBADF) { + error_code = HTCS_ENOTCONN; + } + + return res; + } + + ssize_t VirtualSocketCollection::Send(s32 id, const void *buffer, size_t buffer_size, s32 flags, s32 &error_code) { + /* Setup result/error code. */ + ssize_t res = -1; + error_code = 0; + + /* Fetch the socket. */ + sf::SharedPointer<tma::ISocket> socket = this->FetchSocket(id, error_code); + + /* If we found the socket, bind. */ + if (socket != nullptr) { + if (error_code != HTCS_ENONE) { + return -1; + } + res = send(socket, buffer, buffer_size, flags, error_code); + } else if (error_code != HTCS_EBADF) { + error_code = HTCS_ENOTCONN; + } + + return res; + } + + s32 VirtualSocketCollection::Connect(s32 id, const htcs::SockAddrHtcs *address, s32 &error_code) { + /* Setup result/error code. */ + s32 res = -1; + error_code = 0; + + /* Fetch the socket. */ + sf::SharedPointer<tma::ISocket> socket = this->FetchSocket(id, error_code); + + /* If we found the socket, bind. */ + if (socket != nullptr) { + if (error_code != HTCS_ENONE) { + return -1; + } + res = connect(socket, address, error_code); + } else if (error_code != HTCS_EBADF) { + error_code = HTCS_EADDRNOTAVAIL; + } + + return res; + } + + s32 VirtualSocketCollection::Select(htcs::FdSet *read, htcs::FdSet *write, htcs::FdSet *except, htcs::TimeVal *timeout, s32 &error_code) { + /* Setup result/error code. */ + s32 res = -1; + s32 tmp_error_code = 0; + + /* Declare buffers. */ + s32 read_primitives[SocketCountMax]; + s32 write_primitives[SocketCountMax]; + s32 except_primitives[SocketCountMax]; + + /* Get reads. */ + s32 num_read = this->GetSockets(read_primitives, read, tmp_error_code); + if (tmp_error_code != HTCS_ENONE) { + error_code = tmp_error_code; + return res; + } + + /* Get writes. */ + s32 num_write = this->GetSockets(write_primitives, write, tmp_error_code); + if (tmp_error_code != HTCS_ENONE) { + error_code = tmp_error_code; + return res; + } + + /* Get excepts. */ + s32 num_except = this->GetSockets(except_primitives, except, tmp_error_code); + if (tmp_error_code != HTCS_ENONE) { + error_code = tmp_error_code; + return res; + } + + /* Perform the select. */ + if (num_read + num_write + num_except > 0) { + res = select(read_primitives, num_read, write_primitives, num_write, except_primitives, num_except, timeout, error_code); + + /* Set the socket primitives. */ + this->SetSockets(read, read_primitives, num_read); + this->SetSockets(write, write_primitives, num_write); + this->SetSockets(except, except_primitives, num_except); + } else { + error_code = HTCS_EINVAL; + } + + return res; + } + + s32 VirtualSocketCollection::CreateId() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Get a free id. */ + s32 res = 0; + do { + res = m_next_id++; + if (m_next_id <= 0) { + m_next_id = 1; + } + } while (this->Find(res) >= 0); + + return res; + } + + s32 VirtualSocketCollection::Add(sf::SharedPointer<tma::ISocket> socket) { + /* Check that the socket isn't null. */ + if (socket == nullptr) { + return -1; + } + + /* Create the socket. */ + s32 error_code; + return this->CreateSocket(socket, error_code); + } + + void VirtualSocketCollection::Insert(s32 id, sf::SharedPointer<tma::ISocket> socket) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Add the socket to the list. */ + if (m_list_count != 0) { + /* Ensure the list remains in sorted order. */ + s32 index; + for (index = m_list_count - 1; index >= 0; --index) { + if (m_socket_list[index].m_id < id) { + break; + } + m_socket_list[index + 1] = m_socket_list[index]; + } + + /* Set the socket in the list. */ + m_socket_list[index + 1].m_id = id; + m_socket_list[index + 1].m_socket = socket; + } else { + /* Set the socket in the list. */ + m_socket_list[0].m_id = id; + m_socket_list[0].m_socket = socket; + } + + /* Increment our count. */ + ++m_list_count; + } + + void VirtualSocketCollection::SetSize(s32 size) { + AMS_UNUSED(size); + } + + s32 VirtualSocketCollection::Find(s32 id, s32 *error_code) { + /* Perform a binary search to find the socket. */ + if (m_list_count > 0) { + s32 left = 0; + s32 right = m_list_count - 1; + while (left <= right) { + const s32 mid = (left + right) / 2; + if (m_socket_list[mid].m_id == id) { + return mid; + } else if (m_socket_list[mid].m_id > id) { + right = mid - 1; + } else /* if (m_socket_list[mid].m_id < id) */ { + left = mid + 1; + } + } + } + + /* We failed to find the socket. */ + if (error_code != nullptr) { + *error_code = HTCS_EBADF; + } + + return InvalidSocket; + } + + s32 VirtualSocketCollection::FindByPrimitive(s32 primitive) { + /* Find a socket with the desired primitive. */ + for (auto i = 0; i < m_list_size; ++i) { + if (m_socket_list[i].m_primitive == primitive) { + return i; + } + } + + return InvalidPrimitive; + } + + bool VirtualSocketCollection::HasAddr(const htcs::SockAddrHtcs *address) { + /* Try to find a matching socket. */ + for (auto i = 0; i < m_list_count; ++i) { + if (m_socket_list[i].m_address.family == address->family && + std::strcmp(m_socket_list[i].m_address.peer_name.name, address->peer_name.name) == 0 && + std::strcmp(m_socket_list[i].m_address.port_name.name, address->port_name.name) == 0) + { + return true; + } + } + return false; + } + + sf::SharedPointer<tma::ISocket> VirtualSocketCollection::GetSocket(s32 id, s32 *error_code) { + sf::SharedPointer<tma::ISocket> res = nullptr; + + /* Get the socket. */ + { + std::scoped_lock lk(m_mutex); + + if (const auto index = this->Find(id, error_code); index >= 0) { + res = m_socket_list[index].m_socket; + } + } + + return res; + } + + sf::SharedPointer<tma::ISocket> VirtualSocketCollection::FetchSocket(s32 id, s32 &error_code) { + /* Clear the error code. */ + error_code = 0; + + /* Get the socket. */ + auto socket = this->GetSocket(id, std::addressof(error_code)); + if (socket == nullptr && error_code == HTCS_ENONE) { + socket = this->RealizeSocket(id); + } + + return socket; + } + + sf::SharedPointer<tma::ISocket> VirtualSocketCollection::RealizeSocket(s32 id) { + /* Clear the error code. */ + s32 error_code = 0; + + /* Get socket. */ + sf::SharedPointer<tma::ISocket> res = socket(error_code); + if (res != nullptr) { + /* Assign the new socket. */ + s32 index; + { + std::scoped_lock lk(m_mutex); + + index = this->Find(id, std::addressof(error_code)); + if (index >= 0) { + m_socket_list[index].SetSocket(res, error_code); + } + } + + /* If the socket was deleted, close it. */ + if (index < 0) { + s32 temp_error = 0; + close(res, temp_error); + res = nullptr; + } + } + + return res; + } + + sf::SharedPointer<tma::ISocket> VirtualSocketCollection::DoAccept(sf::SharedPointer<tma::ISocket> socket, s32 id, htcs::SockAddrHtcs *address, s32 &error_code) { + /* Clear the error code. */ + error_code = 0; + + /* Try to accept. */ + sf::SharedPointer<tma::ISocket> new_socket = accept(socket, address, error_code); + if (error_code == HTCS_ENETDOWN) { + new_socket = accept(socket, address, error_code); + + std::scoped_lock lk(m_mutex); + if (const auto index = this->Find(id, std::addressof(error_code)); index >= 0) { + m_socket_list[index].m_socket = nullptr; + } + } + + return new_socket; + } + + s32 VirtualSocketCollection::GetSockets(s32 * const out_primitives, htcs::FdSet *set, s32 &error_code) { + /* Clear the error code. */ + error_code = 0; + s32 count = 0; + + /* Walk the fdset. */ + if (set != nullptr) { + for (auto i = 0; i < FdSetSize; ++i) { + /* If the set no longer has fds, we're done. */ + if (set->fds[i] == 0) { + break; + } + + /* Find the fd's primitive. */ + s32 primitive = InvalidPrimitive; + s32 index; + { + std::scoped_lock lk(m_mutex); + if (index = this->Find(set->fds[i], std::addressof(error_code)); index >= 0) { + /* Get the primitive, if necessary. */ + if (m_socket_list[index].m_primitive == InvalidPrimitive && m_socket_list[index].m_socket != nullptr) { + m_socket_list[index].m_socket->GetPrimitive(std::addressof(m_socket_list[index].m_primitive)); + } + + primitive = m_socket_list[index].m_primitive; + } + } + + /* Check that an error didn't occur. */ + if (error_code != HTCS_ENONE) { + return 0; + } + + /* If the primitive is invalid, try to realize the socket. */ + if (primitive == InvalidPrimitive) { + if (this->RealizeSocket(set->fds[i]) != nullptr) { + std::scoped_lock lk(m_mutex); + + /* Get the primitive. */ + if (index = this->Find(set->fds[i], std::addressof(error_code)); index >= 0) { + m_socket_list[index].m_socket->GetPrimitive(std::addressof(m_socket_list[index].m_primitive)); + + primitive = m_socket_list[index].m_primitive; + } + } + + /* Check that an error didn't occur. */ + if (error_code != HTCS_ENONE) { + return 0; + } + } + + /* Set the output primitive. */ + if (primitive != InvalidPrimitive) { + out_primitives[count++] = primitive; + } + } + } + + return count; + } + + void VirtualSocketCollection::SetSockets(htcs::FdSet *set, s32 * const primitives, s32 count) { + if (set != nullptr) { + /* Clear the set. */ + FdSetZero(set); + + /* Copy the fds. */ + for (auto i = 0; i < count; ++i) { + std::scoped_lock lk(m_mutex); + + if (const auto index = this->FindByPrimitive(primitives[i]); index >= 0) { + set->fds[i] = m_socket_list[index].m_id; + } + } + } + } + + s32 VirtualSocketCollection::CreateSocket(sf::SharedPointer<tma::ISocket> socket, s32 &error_code) { + /* Clear the error code. */ + error_code = 0; + s32 id = InvalidSocket; + + /* Check that we can add to the list. */ + if (m_list_count < m_list_size) { + /* Create a new id. */ + id = this->CreateId(); + + /* Insert the socket into the list. */ + this->Insert(id, socket); + } else { + if (socket != nullptr) { + s32 tmp_error_code; + close(socket, tmp_error_code); + } + + error_code = HTCS_EMFILE; + } + + return id; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/client/htcs_virtual_socket_collection.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/client/htcs_virtual_socket_collection.hpp new file mode 100644 index 00000000..e91e1ee6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/client/htcs_virtual_socket_collection.hpp @@ -0,0 +1,81 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htcs::client { + + struct VirtualSocket; + + class VirtualSocketCollection { + private: + void *m_buffer; + size_t m_buffer_size; + VirtualSocket *m_socket_list; + s32 m_list_count; + s32 m_list_size; + s32 m_next_id; + os::SdkMutex m_mutex; + public: + static size_t GetWorkingMemorySize(int num_sockets); + public: + explicit VirtualSocketCollection(); + ~VirtualSocketCollection(); + public: + void Init(void *buffer, size_t buffer_size); + void Clear(); + + s32 Socket(s32 &error_code); + s32 Close(s32 id, s32 &error_code); + s32 Bind(s32 id, const htcs::SockAddrHtcs *address, s32 &error_code); + s32 Listen(s32 id, s32 backlog_count, s32 &error_code); + s32 Accept(s32 id, htcs::SockAddrHtcs *address, s32 &error_code); + s32 Fcntl(s32 id, s32 command, s32 value, s32 &error_code); + + s32 Shutdown(s32 id, s32 how, s32 &error_code); + + ssize_t Recv(s32 id, void *buffer, size_t buffer_size, s32 flags, s32 &error_code); + ssize_t Send(s32 id, const void *buffer, size_t buffer_size, s32 flags, s32 &error_code); + + s32 Connect(s32 id, const htcs::SockAddrHtcs *address, s32 &error_code); + + s32 Select(htcs::FdSet *read, htcs::FdSet *write, htcs::FdSet *except, htcs::TimeVal *timeout, s32 &error_code); + private: + s32 CreateId(); + + s32 Add(sf::SharedPointer<tma::ISocket> socket); + + void Insert(s32 id, sf::SharedPointer<tma::ISocket> socket); + void SetSize(s32 size); + + s32 Find(s32 id, s32 *error_code = nullptr); + s32 FindByPrimitive(s32 primitive); + + bool HasAddr(const htcs::SockAddrHtcs *address); + + sf::SharedPointer<tma::ISocket> GetSocket(s32 id, s32 *error_code = nullptr); + sf::SharedPointer<tma::ISocket> FetchSocket(s32 id, s32 &error_code); + sf::SharedPointer<tma::ISocket> RealizeSocket(s32 id); + + sf::SharedPointer<tma::ISocket> DoAccept(sf::SharedPointer<tma::ISocket> socket, s32 id, htcs::SockAddrHtcs *address, s32 &error_code); + + s32 GetSockets(s32 * const out_primitives, htcs::FdSet *set, s32 &error_code); + void SetSockets(htcs::FdSet *set, s32 * const primitives, s32 count); + + s32 CreateSocket(sf::SharedPointer<tma::ISocket> socket, s32 &error_code); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/htcs_socket.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/htcs_socket.cpp new file mode 100644 index 00000000..8b28b1a1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/htcs_socket.cpp @@ -0,0 +1,765 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "client/htcs_session.hpp" +#include "client/htcs_virtual_socket_collection.hpp" + +namespace ams::htcs { + + namespace { + + constinit bool g_initialized = false; + constinit bool g_enable_disconnection_emulation = false; + + constinit AllocateFunction g_allocate_function = nullptr; + constinit DeallocateFunction g_deallocate_function = nullptr; + + constinit void *g_buffer = nullptr; + constinit size_t g_buffer_size = 0; + + constinit os::TlsSlot g_tls_slot = {}; + + constinit tma::IHtcsManager *g_manager = nullptr; + constinit tma::IHtcsManager *g_monitor = nullptr; + + constinit client::VirtualSocketCollection *g_sockets = nullptr; + + void SetLastError(uintptr_t error_code) { + os::SetTlsValue(g_tls_slot, error_code); + } + + void InitializeImpl(void *buffer, size_t buffer_size, int num_sessions) { + /* Check the session count. */ + AMS_ASSERT(0 < num_sessions && num_sessions <= SessionCountMax); + + /* Initialize the manager and monitor. */ + client::InitializeSessionManager(std::addressof(g_manager), std::addressof(g_monitor), num_sessions); + + /* Register the process. */ + const sf::ClientProcessId process_id{0}; + R_ABORT_UNLESS(g_manager->RegisterProcessId(process_id)); + R_ABORT_UNLESS(g_monitor->MonitorManager(process_id)); + + /* Allocate a tls slot for our last error. */ + os::SdkAllocateTlsSlot(std::addressof(g_tls_slot), nullptr); + + /* Setup the virtual socket collection. */ + AMS_ASSERT(buffer != nullptr); + g_sockets = reinterpret_cast<client::VirtualSocketCollection *>(buffer); + std::construct_at(g_sockets); + g_sockets->Init(static_cast<u8 *>(buffer) + sizeof(*g_sockets), buffer_size - sizeof(*g_sockets)); + + /* Mark initialized. */ + g_initialized = true; + } + + void InitializeImpl(AllocateFunction allocate, DeallocateFunction deallocate, int num_sessions, int num_sockets) { + /* Check the session count. */ + AMS_ASSERT(0 < num_sessions && num_sessions <= SessionCountMax); + + /* Set the allocation functions. */ + g_allocate_function = allocate; + g_deallocate_function = deallocate; + + /* Allocate a buffer. */ + g_buffer_size = sizeof(client::VirtualSocketCollection) + client::VirtualSocketCollection::GetWorkingMemorySize(num_sockets); + g_buffer = g_allocate_function(g_buffer_size); + + /* Initialize. */ + InitializeImpl(g_buffer, g_buffer_size, num_sessions); + } + + } + + bool IsInitialized() { + return g_initialized; + } + + size_t GetWorkingMemorySize(int num_sockets) { + AMS_ASSERT(num_sockets <= SocketCountMax); + return sizeof(client::VirtualSocketCollection) + client::VirtualSocketCollection::GetWorkingMemorySize(num_sockets); + } + + void Initialize(AllocateFunction allocate, DeallocateFunction deallocate, int num_sessions) { + /* Check that we're not already initialized. */ + AMS_ASSERT(!IsInitialized()); + + /* Configure disconnection emulation. */ + g_enable_disconnection_emulation = true; + + /* Initialize. */ + InitializeImpl(allocate, deallocate, num_sessions, htcs::SocketCountMax); + } + + void Initialize(void *buffer, size_t buffer_size) { + /* Check that we're not already initialized. */ + AMS_ASSERT(!IsInitialized()); + + /* Configure disconnection emulation. */ + g_enable_disconnection_emulation = true; + + /* Initialize. */ + InitializeImpl(buffer, buffer_size, htcs::SessionCountMax); + } + + void InitializeForDisableDisconnectionEmulation(AllocateFunction allocate, DeallocateFunction deallocate, int num_sessions) { + /* Check that we're not already initialized. */ + AMS_ASSERT(!IsInitialized()); + + /* Configure disconnection emulation. */ + g_enable_disconnection_emulation = false; + + /* Initialize. */ + InitializeImpl(allocate, deallocate, num_sessions, htcs::SocketCountMax); + } + + void InitializeForDisableDisconnectionEmulation(void *buffer, size_t buffer_size) { + /* Check that we're not already initialized. */ + AMS_ASSERT(!IsInitialized()); + + /* Configure disconnection emulation. */ + g_enable_disconnection_emulation = false; + + /* Initialize. */ + InitializeImpl(buffer, buffer_size, htcs::SessionCountMax); + } + + void InitializeForSystem(void *buffer, size_t buffer_size, int num_sessions) { + /* Check that we're not already initialized. */ + AMS_ASSERT(!IsInitialized()); + + /* Configure disconnection emulation. */ + g_enable_disconnection_emulation = true; + + /* Initialize. */ + InitializeImpl(buffer, buffer_size, num_sessions); + } + + void Finalize() { + /* Check that we're initialized. */ + AMS_ASSERT(IsInitialized()); + + /* Set not initialized. */ + g_initialized = false; + + /* Destroy the virtual socket collection. */ + std::destroy_at(g_sockets); + g_sockets = nullptr; + + /* Free the buffer, if we have one. */ + if (g_buffer != nullptr) { + g_deallocate_function(g_buffer, g_buffer_size); + g_buffer = nullptr; + g_buffer_size = 0; + } + + /* Free the tls slot. */ + os::FreeTlsSlot(g_tls_slot); + + /* Release the manager objects. */ + sf::ReleaseSharedObject(g_manager); + sf::ReleaseSharedObject(g_monitor); + g_manager = nullptr; + g_monitor = nullptr; + + /* Finalize the htcs client sessions. */ + client::FinalizeSessionManager(); + } + + const HtcsPeerName GetPeerNameAny() { + /* Check that we have a manager. */ + AMS_ASSERT(g_manager != nullptr); + + /* Get name. */ + HtcsPeerName name; + g_manager->GetPeerNameAny(std::addressof(name)); + + return name; + } + + const HtcsPeerName GetDefaultHostName() { + /* Check that we have a manager. */ + AMS_ASSERT(g_manager != nullptr); + + /* Get name. */ + HtcsPeerName name; + g_manager->GetDefaultHostName(std::addressof(name)); + + return name; + } + + s32 GetLastError() { + /* Check that we have a manager. */ + AMS_ASSERT(g_manager != nullptr); + + return static_cast<s32>(os::GetTlsValue(g_tls_slot)); + } + + s32 Socket() { + /* Check that we have a manager. */ + AMS_ASSERT(g_manager != nullptr); + + /* Check that we have a socket collection. */ + AMS_ASSERT(g_sockets != nullptr); + + /* Perform the operation. */ + s32 error_code = 0; + const s32 ret = g_sockets->Socket(error_code); + if (ret < 0) { + SetLastError(static_cast<uintptr_t>(error_code)); + } + + return ret; + } + + s32 Close(s32 desc) { + /* Check that we have a manager. */ + AMS_ASSERT(g_manager != nullptr); + + /* Check that we have a socket collection. */ + AMS_ASSERT(g_sockets != nullptr); + + /* Perform the operation. */ + s32 error_code = 0; + const s32 ret = g_sockets->Close(desc, error_code); + if (ret < 0) { + SetLastError(static_cast<uintptr_t>(error_code)); + } + + return ret; + } + + s32 Connect(s32 desc, const SockAddrHtcs *address) { + /* Check that we have a manager. */ + AMS_ASSERT(g_manager != nullptr); + + /* Check that we have a socket collection. */ + AMS_ASSERT(g_sockets != nullptr); + + /* Check that the address family is correct. */ + AMS_ASSERT(address->family == HTCS_AF_HTCS); + + /* Perform the operation. */ + s32 error_code = 0; + const s32 ret = g_sockets->Connect(desc, address, error_code); + if (ret < 0) { + SetLastError(static_cast<uintptr_t>(error_code)); + } + + return ret; + } + + s32 Bind(s32 desc, const SockAddrHtcs *address) { + /* Check that we have a manager. */ + AMS_ASSERT(g_manager != nullptr); + + /* Check that we have a socket collection. */ + AMS_ASSERT(g_sockets != nullptr); + + /* Check that the address family is correct. */ + AMS_ASSERT(address->family == HTCS_AF_HTCS); + + /* Perform the operation. */ + s32 error_code = 0; + const s32 ret = g_sockets->Bind(desc, address, error_code); + if (ret < 0) { + SetLastError(static_cast<uintptr_t>(error_code)); + } + + return ret; + } + + s32 Listen(s32 desc, s32 backlog_count) { + /* Check that we have a manager. */ + AMS_ASSERT(g_manager != nullptr); + + /* Check that we have a socket collection. */ + AMS_ASSERT(g_sockets != nullptr); + + /* Perform the operation. */ + s32 error_code = 0; + const s32 ret = g_sockets->Listen(desc, backlog_count, error_code); + if (ret < 0) { + SetLastError(static_cast<uintptr_t>(error_code)); + } + + return ret; + } + + s32 Accept(s32 desc, SockAddrHtcs *address) { + /* Check that we have a manager. */ + AMS_ASSERT(g_manager != nullptr); + + /* Check that we have a socket collection. */ + AMS_ASSERT(g_sockets != nullptr); + + /* Ensure we have an address. */ + SockAddrHtcs tmp; + if (address == nullptr) { + address = std::addressof(tmp); + } + + /* Perform the operation. */ + s32 error_code = 0; + const s32 ret = g_sockets->Accept(desc, address, error_code); + if (ret < 0) { + SetLastError(static_cast<uintptr_t>(error_code)); + } + + return ret; + } + + s32 Shutdown(s32 desc, s32 how) { + /* Check that we have a manager. */ + AMS_ASSERT(g_manager != nullptr); + + /* Check that we have a socket collection. */ + AMS_ASSERT(g_sockets != nullptr); + + /* Perform the operation. */ + s32 error_code = 0; + const s32 ret = g_sockets->Shutdown(desc, how, error_code); + if (ret < 0) { + SetLastError(static_cast<uintptr_t>(error_code)); + } + + return ret; + } + + s32 Fcntl(s32 desc, s32 command, s32 value) { + /* Check that we have a manager. */ + AMS_ASSERT(g_manager != nullptr); + + /* Check that we have a socket collection. */ + AMS_ASSERT(g_sockets != nullptr); + + /* Perform the operation. */ + s32 error_code = 0; + const s32 ret = g_sockets->Fcntl(desc, command, value, error_code); + if (ret < 0) { + SetLastError(static_cast<uintptr_t>(error_code)); + } + + return ret; + } + + s32 Select(s32 count, FdSet *read, FdSet *write, FdSet *exception, TimeVal *timeout) { + AMS_UNUSED(count); + + /* Check that we have a manager. */ + AMS_ASSERT(g_manager != nullptr); + + /* Check that we have a socket collection. */ + AMS_ASSERT(g_sockets != nullptr); + + /* Check that we have some form of input. */ + if (read == nullptr && write == nullptr && exception == nullptr) { + SetLastError(static_cast<uintptr_t>(HTCS_EINVAL)); + return -1; + } + + /* Check that the timeout is valid. */ + if (timeout != nullptr && (timeout->tv_sec < 0 || timeout->tv_usec < 0)) { + SetLastError(static_cast<uintptr_t>(HTCS_EINVAL)); + return -1; + } + + /* Perform the operation. */ + s32 error_code = 0; + const s32 ret = g_sockets->Select(read, write, exception, timeout, error_code); + if (ret < 0) { + SetLastError(static_cast<uintptr_t>(error_code)); + } + + return ret; + } + + ssize_t Recv(s32 desc, void *buffer, size_t buffer_size, s32 flags) { + /* Check that we have a manager. */ + AMS_ASSERT(g_manager != nullptr); + + /* Check that we have a socket collection. */ + AMS_ASSERT(g_sockets != nullptr); + + /* Perform the operation. */ + s32 error_code = 0; + const ssize_t ret = g_sockets->Recv(desc, buffer, buffer_size, flags, error_code); + if (ret < 0) { + SetLastError(static_cast<uintptr_t>(error_code)); + } + + return ret; + } + + ssize_t Send(s32 desc, const void *buffer, size_t buffer_size, s32 flags) { + /* Check that we have a manager. */ + AMS_ASSERT(g_manager != nullptr); + + /* Check that we have a socket collection. */ + AMS_ASSERT(g_sockets != nullptr); + + /* Perform the operation. */ + s32 error_code = 0; + const ssize_t ret = g_sockets->Send(desc, buffer, buffer_size, flags, error_code); + if (ret < 0) { + SetLastError(static_cast<uintptr_t>(error_code)); + } + + return ret; + } + + void FdSetZero(FdSet *set) { + AMS_ASSERT(set != nullptr); + + std::memset(set, 0, sizeof(*set)); + } + + void FdSetSet(s32 fd, FdSet *set) { + AMS_ASSERT(set != nullptr); + + for (auto i = 0; i < FdSetSize; ++i) { + if (set->fds[i] == 0) { + set->fds[i] = fd; + break; + } + } + } + + void FdSetClr(s32 fd, FdSet *set) { + AMS_ASSERT(set != nullptr); + + for (auto i = 0; i < FdSetSize; ++i) { + if (set->fds[i] == fd) { + std::memcpy(set->fds + i, set->fds + i + 1, (FdSetSize - (i + 1)) * sizeof(fd)); + set->fds[FdSetSize - 1] = 0; + break; + } + } + } + + bool FdSetIsSet(s32 fd, const FdSet *set) { + AMS_ASSERT(set != nullptr); + + for (auto i = 0; i < FdSetSize; ++i) { + if (set->fds[i] == fd) { + return true; + } + } + + return false; + } + + namespace client { + + sf::SharedPointer<tma::ISocket> socket(s32 &last_error) { + sf::SharedPointer<tma::ISocket> socket = nullptr; + R_ABORT_UNLESS(g_manager->CreateSocket(std::addressof(last_error), std::addressof(socket), g_enable_disconnection_emulation)); + return socket; + } + + s32 close(sf::SharedPointer<tma::ISocket> socket, s32 &last_error) { + s32 res; + socket->Close(std::addressof(last_error), std::addressof(res)); + return res; + } + + s32 bind(sf::SharedPointer<tma::ISocket> socket, const htcs::SockAddrHtcs *address, s32 &last_error) { + /* Create null-terminated address. */ + htcs::SockAddrHtcs null_terminated_address; + null_terminated_address.family = address->family; + util::Strlcpy(null_terminated_address.peer_name.name, address->peer_name.name, PeerNameBufferLength); + util::Strlcpy(null_terminated_address.port_name.name, address->port_name.name, PortNameBufferLength); + + s32 res; + socket->Bind(std::addressof(last_error), std::addressof(res), null_terminated_address); + return res; + } + + s32 listen(sf::SharedPointer<tma::ISocket> socket, s32 backlog_count, s32 &last_error) { + s32 res; + socket->Listen(std::addressof(last_error), std::addressof(res), backlog_count); + return res; + } + + sf::SharedPointer<tma::ISocket> accept(sf::SharedPointer<tma::ISocket> socket, htcs::SockAddrHtcs *address, s32 &last_error) { + /* Begin the accept. */ + sf::SharedPointer<tma::ISocket> res = nullptr; + u32 task_id = 0; + sf::NativeHandle event_handle; + if (R_SUCCEEDED(socket->AcceptStart(std::addressof(task_id), std::addressof(event_handle)))) { + /* Create system event. */ + os::SystemEventType event; + os::AttachReadableHandleToSystemEvent(std::addressof(event), event_handle.GetOsHandle(), event_handle.IsManaged(), os::EventClearMode_ManualClear); + event_handle.Detach(); + + /* When we're done, clean up the event. */ + ON_SCOPE_EXIT { os::DestroySystemEvent(std::addressof(event)); }; + + /* Wait for the accept to finish. */ + os::WaitSystemEvent(std::addressof(event)); + + /* End the accept. */ + socket->AcceptResults(std::addressof(last_error), std::addressof(res), address, task_id); + } else { + /* Set error. */ + last_error = HTCS_EINTR; + } + + /* Sleep, if an error occurred. */ + if (last_error != HTCS_ENONE) { + os::SleepThread(TimeSpan::FromMilliSeconds(1)); + } + + return res; + } + + s32 fcntl(sf::SharedPointer<tma::ISocket> socket, s32 command, s32 value, s32 &last_error) { + s32 res; + socket->Fcntl(std::addressof(last_error), std::addressof(res), command, value); + return res; + } + + s32 shutdown(sf::SharedPointer<tma::ISocket> socket, s32 how, s32 &last_error) { + s32 res; + socket->Shutdown(std::addressof(last_error), std::addressof(res), how); + return res; + } + + s32 connect(sf::SharedPointer<tma::ISocket> socket, const htcs::SockAddrHtcs *address, s32 &last_error) { + /* Create null-terminated address. */ + htcs::SockAddrHtcs null_terminated_address; + null_terminated_address.family = address->family; + util::Strlcpy(null_terminated_address.peer_name.name, address->peer_name.name, PeerNameBufferLength); + util::Strlcpy(null_terminated_address.port_name.name, address->port_name.name, PortNameBufferLength); + + s32 res; + socket->Connect(std::addressof(last_error), std::addressof(res), null_terminated_address); + return res; + } + + s32 select(s32 * const read, s32 &num_read, s32 * const write, s32 &num_write, s32 * const except, s32 &num_except, htcs::TimeVal *timeout, s32 &last_error) { + /* Determine the timeout values. */ + s64 tv_sec = -1; + s64 tv_usec = -1; + if (timeout != nullptr) { + tv_sec = timeout->tv_sec; + tv_usec = timeout->tv_usec; + } + + using InArray = sf::InMapAliasArray<s32>; + using OutArray = sf::OutMapAliasArray<s32>; + + /* Begin the select. */ + s32 res = -1; + u32 task_id = 0; + sf::NativeHandle event_handle; + if (R_SUCCEEDED(g_manager->StartSelect(std::addressof(task_id), std::addressof(event_handle), InArray(read, num_read), InArray(write, num_write), InArray(except, num_except), tv_sec, tv_usec))) { + /* Create system event. */ + os::SystemEventType event; + os::AttachReadableHandleToSystemEvent(std::addressof(event), event_handle.GetOsHandle(), event_handle.IsManaged(), os::EventClearMode_ManualClear); + event_handle.Detach(); + + /* When we're done, clean up the event. */ + ON_SCOPE_EXIT { os::DestroySystemEvent(std::addressof(event)); }; + + /* Wait for the select to finish. */ + os::WaitSystemEvent(std::addressof(event)); + + /* End the select. */ + g_manager->EndSelect(std::addressof(last_error), std::addressof(res), OutArray(read, num_read), OutArray(write, num_write), OutArray(except, num_except), task_id); + } else { + /* Set error. */ + last_error = HTCS_EINTR; + os::SleepThread(TimeSpan::FromMilliSeconds(1)); + } + + return res; + } + + namespace { + + constexpr size_t MaximumBufferSizeForSmallTransfer = 0xDFE0; + + ssize_t recvLarge(sf::SharedPointer<tma::ISocket> socket, void *buffer, size_t buffer_size, s32 flags, s32 &last_error) { + /* Setup. */ + s64 res = -1; + last_error = HTCS_EINTR; + + /* Start the receive. */ + u32 task_id = 0; + sf::NativeHandle event_handle; + if (R_SUCCEEDED(socket->StartRecv(std::addressof(task_id), std::addressof(event_handle), static_cast<s64>(buffer_size), flags))) { + /* Create system event. */ + os::SystemEventType event; + os::AttachReadableHandleToSystemEvent(std::addressof(event), event_handle.GetOsHandle(), event_handle.IsManaged(), os::EventClearMode_ManualClear); + event_handle.Detach(); + + /* When we're done, clean up the event. */ + ON_SCOPE_EXIT { os::DestroySystemEvent(std::addressof(event)); }; + + /* Wait for the receive to finish. */ + os::WaitSystemEvent(std::addressof(event)); + + /* End the receive. */ + socket->EndRecv(std::addressof(last_error), std::addressof(res), sf::OutAutoSelectBuffer(buffer, buffer_size), task_id); + } else { + /* Set error. */ + last_error = HTCS_EINTR; + os::SleepThread(TimeSpan::FromMilliSeconds(1)); + } + + return static_cast<ssize_t>(res); + } + + ssize_t sendLarge(sf::SharedPointer<tma::ISocket> socket, const void *buffer, size_t buffer_size, s32 flags, s32 &last_error) { + /* Setup. */ + s64 res = -1; + last_error = HTCS_EINTR; + + /* Start the send. */ + u32 task_id = 0; + s64 max_size = 0; + sf::NativeHandle event_handle; + if (R_SUCCEEDED(socket->StartSend(std::addressof(task_id), std::addressof(event_handle), std::addressof(max_size), static_cast<s64>(buffer_size), flags))) { + /* Create system event. */ + os::SystemEventType event; + os::AttachReadableHandleToSystemEvent(std::addressof(event), event_handle.GetOsHandle(), event_handle.IsManaged(), os::EventClearMode_ManualClear); + event_handle.Detach(); + + /* When we're done, clean up the event. */ + ON_SCOPE_EXIT { os::DestroySystemEvent(std::addressof(event)); }; + + /* Send all the data. */ + bool done = false; + size_t sent = 0; + while (sent < buffer_size) { + /* Determine how much to send, this iteration. */ + const u8 *cur = static_cast<const u8 *>(buffer) + sent; + const s64 cur_size = std::min(max_size, static_cast<s64>(buffer_size - sent)); + + /* Continue sending data. */ + s64 cur_sent = 0; + bool wait = false; + const Result result = socket->ContinueSend(std::addressof(cur_sent), std::addressof(wait), sf::InNonSecureAutoSelectBuffer(cur, cur_size), task_id); + if (cur_sent <= 0 || R_FAILED(result)) { + done = true; + break; + } + + /* Wait if we should. */ + if (wait) { + os::WaitSystemEvent(std::addressof(event)); + os::ClearSystemEvent(std::addressof(event)); + } + + /* Advance. */ + sent += cur_sent; + } + + /* Wait for the send to finish. */ + if (!done) { + os::WaitSystemEvent(std::addressof(event)); + } + + /* End the send. */ + socket->EndSend(std::addressof(last_error), std::addressof(res), task_id); + } else { + /* Set error. */ + last_error = HTCS_EINTR; + os::SleepThread(TimeSpan::FromMilliSeconds(1)); + } + + return static_cast<ssize_t>(res); + } + + } + + ssize_t recv(sf::SharedPointer<tma::ISocket> socket, void *buffer, size_t buffer_size, s32 flags, s32 &last_error) { + /* Determine how much to receive. */ + size_t recv_size = buffer_size; + + if ((flags & HTCS_MSG_WAITALL) == 0) { + recv_size = std::min(MaximumBufferSizeForSmallTransfer, buffer_size); + } + + /* Perform a large receive, if we have to. */ + if (recv_size > MaximumBufferSizeForSmallTransfer) { + return recvLarge(socket, buffer, recv_size, flags, last_error); + } + + /* Start the receive. */ + s64 res = -1; + u32 task_id = 0; + sf::NativeHandle event_handle; + if (R_SUCCEEDED(socket->RecvStart(std::addressof(task_id), std::addressof(event_handle), static_cast<s32>(recv_size), flags))) { + /* Create system event. */ + os::SystemEventType event; + os::AttachReadableHandleToSystemEvent(std::addressof(event), event_handle.GetOsHandle(), event_handle.IsManaged(), os::EventClearMode_ManualClear); + event_handle.Detach(); + + /* When we're done, clean up the event. */ + ON_SCOPE_EXIT { os::DestroySystemEvent(std::addressof(event)); }; + + /* Wait for the receive to finish. */ + os::WaitSystemEvent(std::addressof(event)); + + /* End the receive. */ + socket->RecvResults(std::addressof(last_error), std::addressof(res), sf::OutAutoSelectBuffer(buffer, recv_size), task_id); + } else { + /* Set error. */ + last_error = HTCS_EINTR; + os::SleepThread(TimeSpan::FromMilliSeconds(1)); + } + + return static_cast<ssize_t>(res); + } + + ssize_t send(sf::SharedPointer<tma::ISocket> socket, const void *buffer, size_t buffer_size, s32 flags, s32 &last_error) { + /* Perform a large send, if we have to. */ + if (buffer_size > MaximumBufferSizeForSmallTransfer) { + return sendLarge(socket, buffer, buffer_size, flags, last_error); + } + + /* Start the send. */ + s64 res = -1; + u32 task_id = 0; + sf::NativeHandle event_handle; + if (R_SUCCEEDED(socket->SendStart(std::addressof(task_id), std::addressof(event_handle), sf::InNonSecureAutoSelectBuffer(buffer, buffer_size), flags))) { + /* Create system event. */ + os::SystemEventType event; + os::AttachReadableHandleToSystemEvent(std::addressof(event), event_handle.GetOsHandle(), event_handle.IsManaged(), os::EventClearMode_ManualClear); + event_handle.Detach(); + + /* When we're done, clean up the event. */ + ON_SCOPE_EXIT { os::DestroySystemEvent(std::addressof(event)); }; + + /* Wait for the send to finish. */ + os::WaitSystemEvent(std::addressof(event)); + + /* End the send. */ + socket->SendResults(std::addressof(last_error), std::addressof(res), task_id); + } else { + /* Set error. */ + last_error = HTCS_EINTR; + os::SleepThread(TimeSpan::FromMilliSeconds(1)); + } + + return static_cast<ssize_t>(res); + } + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_impl.cpp new file mode 100644 index 00000000..a25ea618 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_impl.hpp" + +namespace ams::htcs::impl { + + namespace { + + constexpr const htcs::HtcsPeerName PeerNameAny = {""}; + constexpr const htcs::HtcsPeerName DefaultHostName = {""}; + + } + + const htcs::HtcsPeerName GetPeerNameAny() { return PeerNameAny; } + const htcs::HtcsPeerName GetDefaultHostName() { return DefaultHostName; } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_impl.hpp new file mode 100644 index 00000000..8bf75f49 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htcs::impl { + + const htcs::HtcsPeerName GetPeerNameAny(); + const htcs::HtcsPeerName GetDefaultHostName(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_manager.cpp new file mode 100644 index 00000000..aac4c719 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_manager.cpp @@ -0,0 +1,398 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_manager.hpp" +#include "htcs_manager_impl.hpp" +#include "htcs_util.hpp" + +namespace ams::htcs::impl { + + HtcsManager::HtcsManager(mem::StandardAllocator *allocator, htclow::HtclowManager *htclow_manager) : m_allocator(allocator), m_impl(static_cast<HtcsManagerImpl *>(allocator->Allocate(sizeof(HtcsManagerImpl), alignof(HtcsManagerImpl)))) { + std::construct_at(m_impl, m_allocator, htclow_manager); + } + + HtcsManager::~HtcsManager() { + std::destroy_at(m_impl); + m_allocator->Free(m_impl); + } + + os::EventType *HtcsManager::GetServiceAvailabilityEvent() { + return m_impl->GetServiceAvailabilityEvent(); + } + + bool HtcsManager::IsServiceAvailable() { + return m_impl->IsServiceAvailable(); + } + + void HtcsManager::Socket(s32 *out_err, s32 *out_desc, bool enable_disconnection_emulation) { + /* Invoke our implementation. */ + s32 err = -1, desc = -1; + const Result result = m_impl->CreateSocket(std::addressof(err), std::addressof(desc), enable_disconnection_emulation); + + /* Set output. */ + if (R_SUCCEEDED(result)) { + *out_err = err; + if (err == 0) { + *out_desc = desc; + } else { + *out_desc = -1; + } + } else { + *out_err = ConvertResultToErrorCode(result); + *out_desc = -1; + } + } + + void HtcsManager::Close(s32 *out_err, s32 *out_res, s32 desc) { + /* Invoke our implementation. */ + const Result result = m_impl->DestroySocket(desc); + + /* Set output. */ + *out_err = ConvertResultToErrorCode(result); + if (R_SUCCEEDED(result)) { + *out_res = 0; + } else { + *out_res = -1; + } + } + + void HtcsManager::Connect(s32 *out_err, s32 *out_res, const SockAddrHtcs &address, s32 desc) { + /* Invoke our implementation. */ + s32 err = -1; + const Result result = m_impl->Connect(std::addressof(err), desc, address); + + /* Set output. */ + if (R_SUCCEEDED(result)) { + *out_err = err; + if (err == 0) { + *out_res = 0; + } else { + *out_res = -1; + } + } else { + *out_err = ConvertResultToErrorCode(result); + *out_res = -1; + } + } + + void HtcsManager::Bind(s32 *out_err, s32 *out_res, const SockAddrHtcs &address, s32 desc) { + /* Invoke our implementation. */ + s32 err = -1; + const Result result = m_impl->Bind(std::addressof(err), desc, address); + + /* Set output. */ + if (R_SUCCEEDED(result)) { + *out_err = err; + if (err == 0) { + *out_res = 0; + } else { + *out_res = -1; + } + } else { + *out_err = ConvertResultToErrorCode(result); + *out_res = -1; + } + } + + void HtcsManager::Listen(s32 *out_err, s32 *out_res, s32 backlog_count, s32 desc) { + /* Invoke our implementation. */ + s32 err = -1; + const Result result = m_impl->Listen(std::addressof(err), desc, backlog_count); + + /* Set output. */ + if (R_SUCCEEDED(result)) { + *out_err = err; + if (err == 0) { + *out_res = 0; + } else { + *out_res = -1; + } + } else { + *out_err = ConvertResultToErrorCode(result); + *out_res = -1; + } + } + + void HtcsManager::Recv(s32 *out_err, s64 *out_size, char *buffer, size_t size, s32 flags, s32 desc) { + /* Invoke our implementation. */ + s32 err = -1; + s64 recv_size = -1; + const Result result = m_impl->Receive(std::addressof(err), std::addressof(recv_size), buffer, size, desc, flags); + + /* Set output. */ + if (R_SUCCEEDED(result)) { + *out_err = err; + if (err == 0) { + *out_size = recv_size; + } else { + *out_size = -1; + } + } else { + *out_err = ConvertResultToErrorCode(result); + *out_size = -1; + } + } + + void HtcsManager::Send(s32 *out_err, s64 *out_size, const char *buffer, size_t size, s32 flags, s32 desc) { + /* Invoke our implementation. */ + s32 err = -1; + s64 send_size = -1; + const Result result = m_impl->Send(std::addressof(err), std::addressof(send_size), buffer, size, desc, flags); + + /* Set output. */ + if (R_SUCCEEDED(result)) { + *out_err = err; + if (err == 0) { + *out_size = send_size; + } else { + *out_size = -1; + } + } else { + *out_err = ConvertResultToErrorCode(result); + *out_size = -1; + } + } + + void HtcsManager::Shutdown(s32 *out_err, s32 *out_res, s32 how, s32 desc) { + /* Invoke our implementation. */ + s32 err = -1; + const Result result = m_impl->Shutdown(std::addressof(err), desc, how); + + /* Set output. */ + if (R_SUCCEEDED(result)) { + *out_err = err; + if (err == 0) { + *out_res = 0; + } else { + *out_res = -1; + } + } else { + if (htcs::ResultInvalidHandle::Includes(result)) { + *out_err = HTCS_ENOTCONN; + } else { + *out_err = ConvertResultToErrorCode(result); + } + *out_res = -1; + } + } + + void HtcsManager::Fcntl(s32 *out_err, s32 *out_res, s32 command, s32 value, s32 desc) { + /* Invoke our implementation. */ + s32 err = -1, res = -1; + const Result result = m_impl->Fcntl(std::addressof(err), std::addressof(res), desc, command, value); + + /* Set output. */ + if (R_SUCCEEDED(result)) { + *out_err = err; + *out_res = res; + } else { + *out_err = ConvertResultToErrorCode(result); + *out_res = -1; + } + } + + Result HtcsManager::AcceptStart(u32 *out_task_id, os::NativeHandle *out_handle, s32 desc) { + R_RETURN(m_impl->AcceptStart(out_task_id, out_handle, desc)); + } + + void HtcsManager::AcceptResults(s32 *out_err, s32 *out_desc, SockAddrHtcs *out_address, u32 task_id, s32 desc) { + /* Invoke our implementation. */ + s32 err = -1; + const Result result = m_impl->AcceptResults(std::addressof(err), out_desc, out_address, task_id, desc); + + /* Set output. */ + if (R_SUCCEEDED(result)) { + *out_err = err; + } else { + if (htc::ResultCancelled::Includes(result)) { + *out_err = HTCS_ENETDOWN; + } else if (htc::ResultTaskQueueNotAvailable::Includes(result)) { + *out_err = HTCS_EINTR; + } else { + *out_err = ConvertResultToErrorCode(result); + } + } + } + + Result HtcsManager::RecvStart(u32 *out_task_id, os::NativeHandle *out_handle, s64 size, s32 desc, s32 flags) { + R_RETURN(m_impl->RecvStart(out_task_id, out_handle, size, desc, flags)); + } + + void HtcsManager::RecvResults(s32 *out_err, s64 *out_size, char *buffer, s64 buffer_size, u32 task_id, s32 desc) { + /* Invoke our implementation. */ + s32 err = -1; + s64 size = -1; + const Result result = m_impl->RecvResults(std::addressof(err), std::addressof(size), buffer, buffer_size, task_id, desc); + + /* Set output. */ + if (R_SUCCEEDED(result)) { + *out_err = err; + if (err == 0) { + *out_size = size; + } else { + *out_size = -1; + } + } else { + if (htc::ResultTaskQueueNotAvailable::Includes(result)) { + *out_err = HTCS_EINTR; + } else { + *out_err = ConvertResultToErrorCode(result); + } + *out_size = -1; + } + } + + Result HtcsManager::SendStart(u32 *out_task_id, os::NativeHandle *out_handle, const char *buffer, s64 size, s32 desc, s32 flags) { + R_RETURN(m_impl->SendStart(out_task_id, out_handle, buffer, size, desc, flags)); + } + + Result HtcsManager::SendLargeStart(u32 *out_task_id, os::NativeHandle *out_handle, const char **buffers, const s64 *sizes, s32 count, s32 desc, s32 flags) { + R_RETURN(m_impl->SendLargeStart(out_task_id, out_handle, buffers, sizes, count, desc, flags)); + } + + void HtcsManager::SendResults(s32 *out_err, s64 *out_size, u32 task_id, s32 desc) { + /* Invoke our implementation. */ + s32 err = -1; + s64 size = -1; + const Result result = m_impl->SendResults(std::addressof(err), std::addressof(size), task_id, desc); + + /* Set output. */ + if (R_SUCCEEDED(result)) { + *out_err = err; + if (err == 0) { + *out_size = size; + } else { + *out_size = -1; + } + } else { + if (htc::ResultTaskQueueNotAvailable::Includes(result)) { + *out_err = HTCS_EINTR; + } else { + *out_err = ConvertResultToErrorCode(result); + } + *out_size = -1; + } + } + + Result HtcsManager::StartSend(u32 *out_task_id, os::NativeHandle *out_handle, s32 desc, s64 size, s32 flags) { + R_RETURN(m_impl->StartSend(out_task_id, out_handle, desc, size, flags)); + } + + Result HtcsManager::ContinueSend(s64 *out_size, const char *buffer, s64 buffer_size, u32 task_id, s32 desc) { + /* Invoke our implementation. */ + s64 size = -1; + R_TRY_CATCH(m_impl->ContinueSend(std::addressof(size), buffer, buffer_size, task_id, desc)) { + R_CONVERT(htclow::ResultInvalidChannelState, tma::ResultUnknown()) + R_CONVERT(htc::ResultTaskCancelled, tma::ResultUnknown()) + } R_END_TRY_CATCH; + + /* Set output. */ + *out_size = size; + R_SUCCEED(); + } + + void HtcsManager::EndSend(s32 *out_err, s64 *out_size, u32 task_id, s32 desc) { + /* Invoke our implementation. */ + s32 err = -1; + s64 size = -1; + const Result result = m_impl->EndSend(std::addressof(err), std::addressof(size), task_id, desc); + + /* Set output. */ + if (R_SUCCEEDED(result)) { + *out_err = err; + if (err == 0) { + *out_size = size; + } else { + *out_size = -1; + } + } else { + if (htc::ResultTaskQueueNotAvailable::Includes(result)) { + *out_err = HTCS_EINTR; + } else { + *out_err = ConvertResultToErrorCode(result); + } + *out_size = -1; + } + } + + Result HtcsManager::StartRecv(u32 *out_task_id, os::NativeHandle *out_handle, s64 size, s32 desc, s32 flags) { + R_RETURN(m_impl->StartRecv(out_task_id, out_handle, size, desc, flags)); + } + + void HtcsManager::EndRecv(s32 *out_err, s64 *out_size, char *buffer, s64 buffer_size, u32 task_id, s32 desc) { + /* Invoke our implementation. */ + s32 err = -1; + s64 size = -1; + const Result result = m_impl->EndRecv(std::addressof(err), std::addressof(size), buffer, buffer_size, task_id, desc); + + /* Set output. */ + if (R_SUCCEEDED(result)) { + *out_err = err; + if (err == 0) { + *out_size = size; + } else { + *out_size = -1; + } + } else { + if (htc::ResultCancelled::Includes(result) || htc::ResultTaskQueueNotAvailable::Includes(result)) { + *out_err = 0; + } else { + *out_err = ConvertResultToErrorCode(result); + } + *out_size = -1; + } + } + + Result HtcsManager::StartSelect(u32 *out_task_id, os::NativeHandle *out_handle, Span<const int> read_handles, Span<const int> write_handles, Span<const int> exception_handles, s64 tv_sec, s64 tv_usec) { + /* Invoke our implementation. */ + R_TRY_CATCH(m_impl->StartSelect(out_task_id, out_handle, read_handles, write_handles, exception_handles, tv_sec, tv_usec)) { + R_CONVERT(htc::ResultTaskCancelled, tma::ResultUnknown()) + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + Result HtcsManager::EndSelect(s32 *out_err, s32 *out_count, Span<int> read_handles, Span<int> write_handles, Span<int> exception_handles, u32 task_id) { + /* Invoke our implementation. */ + s32 err = -1; + bool empty = false; + const Result result = m_impl->EndSelect(std::addressof(err), std::addressof(empty), read_handles, write_handles, exception_handles, task_id); + + /* Set output. */ + if (R_SUCCEEDED(result) && !empty) { + *out_err = err; + if (err == 0) { + const auto num_read = std::count_if(read_handles.begin(), read_handles.end(), [](int handle) { return handle != 0; }); + const auto num_write = std::count_if(write_handles.begin(), write_handles.end(), [](int handle) { return handle != 0; }); + const auto num_exception = std::count_if(exception_handles.begin(), exception_handles.end(), [](int handle) { return handle != 0; }); + *out_count = num_read + num_write + num_exception; + } else { + *out_count = -1; + } + } else { + if (R_SUCCEEDED(result)) { + *out_err = 0; + *out_count = 0; + } else { + *out_err = ConvertResultToErrorCode(err); + *out_count = -1; + } + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_manager.hpp new file mode 100644 index 00000000..dc34c8d0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_manager.hpp @@ -0,0 +1,67 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../../htclow/htclow_manager.hpp" + +namespace ams::htcs::impl { + + class HtcsManagerImpl; + + class HtcsManager { + private: + mem::StandardAllocator *m_allocator; + HtcsManagerImpl *m_impl; + public: + HtcsManager(mem::StandardAllocator *allocator, htclow::HtclowManager *htclow_manager); + ~HtcsManager(); + public: + os::EventType *GetServiceAvailabilityEvent(); + + bool IsServiceAvailable(); + public: + void Socket(s32 *out_err, s32 *out_desc, bool enable_disconnection_emulation); + void Close(s32 *out_err, s32 *out_res, s32 desc); + void Connect(s32 *out_err, s32 *out_res, const SockAddrHtcs &address, s32 desc); + void Bind(s32 *out_err, s32 *out_res, const SockAddrHtcs &address, s32 desc); + void Listen(s32 *out_err, s32 *out_res, s32 backlog_count, s32 desc); + void Recv(s32 *out_err, s64 *out_size, char *buffer, size_t size, s32 flags, s32 desc); + void Send(s32 *out_err, s64 *out_size, const char *buffer, size_t size, s32 flags, s32 desc); + void Shutdown(s32 *out_err, s32 *out_res, s32 how, s32 desc); + void Fcntl(s32 *out_err, s32 *out_res, s32 command, s32 value, s32 desc); + + Result AcceptStart(u32 *out_task_id, os::NativeHandle *out_handle, s32 desc); + void AcceptResults(s32 *out_err, s32 *out_desc, SockAddrHtcs *out_address, u32 task_id, s32 desc); + + Result RecvStart(u32 *out_task_id, os::NativeHandle *out_handle, s64 size, s32 desc, s32 flags); + void RecvResults(s32 *out_err, s64 *out_size, char *buffer, s64 buffer_size, u32 task_id, s32 desc); + + Result SendStart(u32 *out_task_id, os::NativeHandle *out_handle, const char *buffer, s64 size, s32 desc, s32 flags); + Result SendLargeStart(u32 *out_task_id, os::NativeHandle *out_handle, const char **buffers, const s64 *sizes, s32 count, s32 desc, s32 flags); + void SendResults(s32 *out_err, s64 *out_size, u32 task_id, s32 desc); + + Result StartSend(u32 *out_task_id, os::NativeHandle *out_handle, s32 desc, s64 size, s32 flags); + Result ContinueSend(s64 *out_size, const char *buffer, s64 buffer_size, u32 task_id, s32 desc); + void EndSend(s32 *out_err, s64 *out_size, u32 task_id, s32 desc); + + Result StartRecv(u32 *out_task_id, os::NativeHandle *out_handle, s64 size, s32 desc, s32 flags); + void EndRecv(s32 *out_err, s64 *out_size, char *buffer, s64 buffer_size, u32 task_id, s32 desc); + + Result StartSelect(u32 *out_task_id, os::NativeHandle *out_handle, Span<const int> read_handles, Span<const int> write_handles, Span<const int> exception_handles, s64 tv_sec, s64 tv_usec); + Result EndSelect(s32 *out_err, s32 *out_count, Span<int> read_handles, Span<int> write_handles, Span<int> exception_handles, u32 task_id); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_manager_holder.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_manager_holder.cpp new file mode 100644 index 00000000..0cbd1758 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_manager_holder.cpp @@ -0,0 +1,79 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_manager.hpp" + +namespace ams::htcs::impl::HtcsManagerHolder { + + namespace { + + constinit os::SdkMutex g_holder_mutex; + constinit int g_holder_reference_count = 0; + + mem::StandardAllocator g_allocator; + + constinit HtcsManager *g_manager = nullptr; + + alignas(os::MemoryPageSize) u8 g_heap_buffer[416_KB]; + + } + + void AddReference() { + std::scoped_lock lk(g_holder_mutex); + + if ((g_holder_reference_count++) == 0) { + /* Add reference to the htclow manager. */ + htclow::HtclowManagerHolder::AddReference(); + + /* Initialize the allocator for the manager. */ + g_allocator.Initialize(g_heap_buffer, sizeof(g_heap_buffer)); + + /* Allocate the manager. */ + g_manager = static_cast<HtcsManager *>(g_allocator.Allocate(sizeof(HtcsManager), alignof(HtcsManager))); + + /* Construct the manager. */ + std::construct_at(g_manager, std::addressof(g_allocator), htclow::HtclowManagerHolder::GetHtclowManager()); + } + + AMS_ASSERT(g_holder_reference_count > 0); + } + + void Release() { + std::scoped_lock lk(g_holder_mutex); + + AMS_ASSERT(g_holder_reference_count > 0); + + if ((--g_holder_reference_count) == 0) { + /* Destroy the manager. */ + std::destroy_at(g_manager); + g_allocator.Free(g_manager); + g_manager = nullptr; + + /* Finalize the allocator. */ + g_allocator.Finalize(); + + /* Release reference to the htclow manager. */ + htclow::HtclowManagerHolder::Release(); + } + } + + HtcsManager *GetHtcsManager() { + std::scoped_lock lk(g_holder_mutex); + + return g_manager; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_manager_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_manager_impl.cpp new file mode 100644 index 00000000..76000741 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_manager_impl.cpp @@ -0,0 +1,189 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_manager.hpp" +#include "htcs_manager_impl.hpp" + +namespace ams::htcs::impl { + + HtcsManagerImpl::HtcsManagerImpl(mem::StandardAllocator *allocator, htclow::HtclowManager *htclow_manager) + : m_allocator(allocator), + m_driver(htclow_manager, htclow::ModuleId::Htcs), + m_driver_manager(std::addressof(m_driver)), + m_rpc_client(m_allocator, std::addressof(m_driver), HtcsClientChannelId), + m_data_channel_manager(std::addressof(m_rpc_client), htclow_manager), + m_service(m_allocator, m_driver_manager.GetDriver(), std::addressof(m_rpc_client), std::addressof(m_data_channel_manager)), + m_monitor(m_allocator, m_driver_manager.GetDriver(), std::addressof(m_rpc_client), std::addressof(m_service)) + { + /* Start the monitor. */ + m_monitor.Start(); + } + + HtcsManagerImpl::~HtcsManagerImpl() { + /* Cancel our monitor. */ + m_monitor.Cancel(); + m_monitor.Wait(); + } + + os::EventType *HtcsManagerImpl::GetServiceAvailabilityEvent() { + return m_monitor.GetServiceAvailabilityEvent(); + } + + bool HtcsManagerImpl::IsServiceAvailable() { + return m_monitor.IsServiceAvailable(); + } + + Result HtcsManagerImpl::CreateSocket(s32 *out_err, s32 *out_desc, bool enable_disconnection_emulation) { + R_RETURN(m_service.CreateSocket(out_err, out_desc, enable_disconnection_emulation)); + } + + Result HtcsManagerImpl::DestroySocket(s32 desc) { + R_RETURN(m_service.DestroySocket(desc)); + } + + Result HtcsManagerImpl::Connect(s32 *out_err, s32 desc, const SockAddrHtcs &address) { + R_RETURN(m_service.Connect(out_err, desc, address)); + } + + Result HtcsManagerImpl::Bind(s32 *out_err, s32 desc, const SockAddrHtcs &address) { + R_RETURN(m_service.Bind(out_err, desc, address)); + } + + Result HtcsManagerImpl::Listen(s32 *out_err, s32 desc, s32 backlog_count) { + R_RETURN(m_service.Listen(out_err, desc, backlog_count)); + } + + Result HtcsManagerImpl::Receive(s32 *out_err, s64 *out_size, char *buffer, size_t size, s32 desc, s32 flags) { + R_RETURN(m_service.Receive(out_err, out_size, buffer, size, desc, flags)); + } + + Result HtcsManagerImpl::Send(s32 *out_err, s64 *out_size, const char *buffer, size_t size, s32 desc, s32 flags) { + R_RETURN(m_service.Send(out_err, out_size, buffer, size, desc, flags)); + } + + Result HtcsManagerImpl::Shutdown(s32 *out_err, s32 desc, s32 how) { + R_RETURN(m_service.Shutdown(out_err, desc, how)); + } + + Result HtcsManagerImpl::Fcntl(s32 *out_err, s32 *out_res, s32 desc, s32 command, s32 value) { + R_RETURN(m_service.Fcntl(out_err, out_res, desc, command, value)); + } + + Result HtcsManagerImpl::AcceptStart(u32 *out_task_id, os::NativeHandle *out_handle, s32 desc) { + R_RETURN(m_service.AcceptStart(out_task_id, out_handle, desc)); + } + + Result HtcsManagerImpl::AcceptResults(s32 *out_err, s32 *out_desc, SockAddrHtcs *out_address, u32 task_id, s32 desc) { + R_RETURN(m_service.AcceptResults(out_err, out_desc, out_address, task_id, desc)); + } + + Result HtcsManagerImpl::RecvStart(u32 *out_task_id, os::NativeHandle *out_handle, s64 size, s32 desc, s32 flags) { + R_RETURN(m_service.ReceiveSmallStart(out_task_id, out_handle, size, desc, flags)); + } + + Result HtcsManagerImpl::RecvResults(s32 *out_err, s64 *out_size, char *buffer, s64 buffer_size, u32 task_id, s32 desc) { + R_RETURN(m_service.ReceiveSmallResults(out_err, out_size, buffer, buffer_size, task_id, desc)); + } + + Result HtcsManagerImpl::SendStart(u32 *out_task_id, os::NativeHandle *out_handle, const char *buffer, s64 size, s32 desc, s32 flags) { + /* Start the send. */ + u32 task_id{}; + os::NativeHandle handle; + R_TRY(m_service.SendSmallStart(std::addressof(task_id), std::addressof(handle), desc, size, flags)); + + /* Continue the send. */ + s64 continue_size; + const Result result = m_service.SendSmallContinue(std::addressof(continue_size), buffer, size, task_id, desc); + if (R_SUCCEEDED(result) || htcs::ResultCompleted::Includes(result) || htc::ResultTaskQueueNotAvailable::Includes(result)) { + *out_task_id = task_id; + *out_handle = handle; + + R_SUCCEED(); + } else { + os::SystemEventType event; + os::AttachReadableHandleToSystemEvent(std::addressof(event), handle, true, os::EventClearMode_ManualClear); + + s32 err; + s64 rsize; + m_service.SendSmallResults(std::addressof(err), std::addressof(rsize), task_id, desc); + + os::DestroySystemEvent(std::addressof(event)); + + R_RETURN(result); + } + } + + Result HtcsManagerImpl::SendLargeStart(u32 *out_task_id, os::NativeHandle *out_handle, const char **buffers, const s64 *sizes, s32 count, s32 desc, s32 flags) { + /* NOTE: Nintendo aborts here, too. */ + AMS_UNUSED(out_task_id, out_handle, buffers, sizes, count, desc, flags); + AMS_ABORT("HtcsManagerImpl::SendLargeStart is not implemented"); + } + + Result HtcsManagerImpl::SendResults(s32 *out_err, s64 *out_size, u32 task_id, s32 desc) { + R_RETURN(m_service.SendSmallResults(out_err, out_size, task_id, desc)); + } + + Result HtcsManagerImpl::StartSend(u32 *out_task_id, os::NativeHandle *out_handle, s32 desc, s64 size, s32 flags) { + R_RETURN(m_service.SendStart(out_task_id, out_handle, desc, size, flags)); + } + + Result HtcsManagerImpl::ContinueSend(s64 *out_size, const char *buffer, s64 buffer_size, u32 task_id, s32 desc) { + R_RETURN(m_service.SendContinue(out_size, buffer, buffer_size, task_id, desc)); + } + + Result HtcsManagerImpl::EndSend(s32 *out_err, s64 *out_size, u32 task_id, s32 desc) { + R_RETURN(m_service.SendResults(out_err, out_size, task_id, desc)); + } + + Result HtcsManagerImpl::StartRecv(u32 *out_task_id, os::NativeHandle *out_handle, s64 size, s32 desc, s32 flags) { + R_RETURN(m_service.ReceiveStart(out_task_id, out_handle, size, desc, flags)); + } + + Result HtcsManagerImpl::EndRecv(s32 *out_err, s64 *out_size, char *buffer, s64 buffer_size, u32 task_id, s32 desc) { + R_RETURN(m_service.ReceiveResults(out_err, out_size, buffer, buffer_size, task_id, desc)); + } + + Result HtcsManagerImpl::StartSelect(u32 *out_task_id, os::NativeHandle *out_handle, Span<const int> read_handles, Span<const int> write_handles, Span<const int> exception_handles, s64 tv_sec, s64 tv_usec) { + /* Start the select. */ + u32 task_id{}; + os::NativeHandle handle = os::InvalidNativeHandle; + const Result result = m_service.SelectStart(std::addressof(task_id), std::addressof(handle), read_handles, write_handles, exception_handles, tv_sec, tv_usec); + + /* Ensure our state ends up clean. */ + if (htcs::ResultCancelled::Includes(result)) { + s32 err; + bool empty; + m_service.SelectEnd(std::addressof(err), std::addressof(empty), Span<int>{}, Span<int>{}, Span<int>{}, task_id); + + if (handle != os::InvalidNativeHandle) { + os::SystemEventType event; + os::AttachReadableHandleToSystemEvent(std::addressof(event), handle, true, os::EventClearMode_ManualClear); + + os::DestroySystemEvent(std::addressof(event)); + } + } else if (R_SUCCEEDED(result)) { + *out_task_id = task_id; + *out_handle = handle; + } + + R_RETURN(result); + } + + Result HtcsManagerImpl::EndSelect(s32 *out_err, bool *out_empty, Span<int> read_handles, Span<int> write_handles, Span<int> exception_handles, u32 task_id) { + R_RETURN(m_service.SelectEnd(out_err, out_empty, read_handles, write_handles, exception_handles, task_id)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_manager_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_manager_impl.hpp new file mode 100644 index 00000000..ba6917b8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_manager_impl.hpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../../htclow/htclow_manager.hpp" +#include "../../htc/server/driver/htc_htclow_driver.hpp" +#include "../../htc/server/driver/htc_driver_manager.hpp" +#include "../../htc/server/rpc/htc_rpc_client.hpp" +#include "rpc/htcs_data_channel_manager.hpp" +#include "htcs_service.hpp" +#include "htcs_monitor.hpp" + +namespace ams::htcs::impl { + + class HtcsManagerImpl { + private: + mem::StandardAllocator *m_allocator; + htc::server::driver::HtclowDriver m_driver; + htc::server::driver::DriverManager m_driver_manager; + htc::server::rpc::RpcClient m_rpc_client; + rpc::DataChannelManager m_data_channel_manager; + HtcsService m_service; + HtcsMonitor m_monitor; + public: + HtcsManagerImpl(mem::StandardAllocator *allocator, htclow::HtclowManager *htclow_manager); + ~HtcsManagerImpl(); + public: + os::EventType *GetServiceAvailabilityEvent(); + + bool IsServiceAvailable(); + public: + Result CreateSocket(s32 *out_err, s32 *out_desc, bool enable_disconnection_emulation); + Result DestroySocket(s32 desc); + Result Connect(s32 *out_err, s32 desc, const SockAddrHtcs &address); + Result Bind(s32 *out_err, s32 desc, const SockAddrHtcs &address); + Result Listen(s32 *out_err, s32 desc, s32 backlog_count); + Result Receive(s32 *out_err, s64 *out_size, char *buffer, size_t size, s32 desc, s32 flags); + Result Send(s32 *out_err, s64 *out_size, const char *buffer, size_t size, s32 desc, s32 flags); + Result Shutdown(s32 *out_err, s32 desc, s32 how); + Result Fcntl(s32 *out_err, s32 *out_res, s32 desc, s32 command, s32 value); + + Result AcceptStart(u32 *out_task_id, os::NativeHandle *out_handle, s32 desc); + Result AcceptResults(s32 *out_err, s32 *out_desc, SockAddrHtcs *out_address, u32 task_id, s32 desc); + + Result RecvStart(u32 *out_task_id, os::NativeHandle *out_handle, s64 size, s32 desc, s32 flags); + Result RecvResults(s32 *out_err, s64 *out_size, char *buffer, s64 buffer_size, u32 task_id, s32 desc); + + Result SendStart(u32 *out_task_id, os::NativeHandle *out_handle, const char *buffer, s64 size, s32 desc, s32 flags); + Result SendLargeStart(u32 *out_task_id, os::NativeHandle *out_handle, const char **buffers, const s64 *sizes, s32 count, s32 desc, s32 flags); + Result SendResults(s32 *out_err, s64 *out_size, u32 task_id, s32 desc); + + Result StartSend(u32 *out_task_id, os::NativeHandle *out_handle, s32 desc, s64 size, s32 flags); + Result ContinueSend(s64 *out_size, const char *buffer, s64 buffer_size, u32 task_id, s32 desc); + Result EndSend(s32 *out_err, s64 *out_size, u32 task_id, s32 desc); + + Result StartRecv(u32 *out_task_id, os::NativeHandle *out_handle, s64 size, s32 desc, s32 flags); + Result EndRecv(s32 *out_err, s64 *out_size, char *buffer, s64 buffer_size, u32 task_id, s32 desc); + + Result StartSelect(u32 *out_task_id, os::NativeHandle *out_handle, Span<const int> read_handles, Span<const int> write_handles, Span<const int> exception_handles, s64 tv_sec, s64 tv_usec); + Result EndSelect(s32 *out_err, bool *out_empty, Span<int> read_handles, Span<int> write_handles, Span<int> exception_handles, u32 task_id); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_monitor.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_monitor.cpp new file mode 100644 index 00000000..fe4ff1c3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_monitor.cpp @@ -0,0 +1,106 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_manager.hpp" +#include "htcs_manager_impl.hpp" + +namespace ams::htcs::impl { + + HtcsMonitor::HtcsMonitor(mem::StandardAllocator *allocator, htc::server::driver::IDriver *drv, htc::server::rpc::RpcClient *rc, HtcsService *srv) + : m_allocator(allocator), + m_driver(drv), + m_rpc_client(rc), + m_service(srv), + m_monitor_thread_stack(m_allocator->Allocate(os::MemoryPageSize, os::ThreadStackAlignment)), + m_mutex(), + m_cancel_event(os::EventClearMode_ManualClear), + m_service_availability_event(os::EventClearMode_ManualClear), + m_cancelled(false), + m_is_service_available(false) + { + /* ... */ + } + + HtcsMonitor::~HtcsMonitor() { + /* Free thread stack. */ + m_allocator->Free(m_monitor_thread_stack); + } + + void HtcsMonitor::Start() { + /* Create the monitor thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_monitor_thread), ThreadEntry, this, m_monitor_thread_stack, os::MemoryPageSize, AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtcsMonitor))); + + /* Set thread name. */ + os::SetThreadNamePointer(std::addressof(m_monitor_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtcsMonitor)); + + /* Start the monitor thread. */ + os::StartThread(std::addressof(m_monitor_thread)); + } + + void HtcsMonitor::Cancel() { + /* Cancel, and signal. */ + m_cancelled = true; + m_cancel_event.Signal(); + } + + void HtcsMonitor::Wait() { + /* Wait for the thread. */ + os::WaitThread(std::addressof(m_monitor_thread)); + os::DestroyThread(std::addressof(m_monitor_thread)); + } + + void HtcsMonitor::ThreadBody() { + /* Loop so long as we're not cancelled. */ + while (!m_cancelled) { + /* Open the rpc client. */ + m_rpc_client->Open(); + + /* Ensure we close, if something goes wrong. */ + auto client_guard = SCOPE_GUARD { m_rpc_client->Close(); }; + + /* Wait for the rpc server. */ + if (m_rpc_client->WaitAny(htclow::ChannelState_Connectable, m_cancel_event.GetBase()) != 0) { + break; + } + + /* Start the rpc client. */ + const Result start_result = m_rpc_client->Start(); + if (R_FAILED(start_result)) { + /* DEBUG */ + R_ABORT_UNLESS(start_result); + continue; + } + + /* We're available! */ + this->SetServiceAvailability(true); + client_guard.Cancel(); + + /* We're available, so we want to cleanup when we're done. */ + ON_SCOPE_EXIT { + m_rpc_client->Close(); + m_rpc_client->Cancel(); + m_rpc_client->Wait(); + this->SetServiceAvailability(false); + }; + + /* Wait to become disconnected. */ + if (m_rpc_client->WaitAny(htclow::ChannelState_Disconnected, m_cancel_event.GetBase()) != 0) { + break; + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_monitor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_monitor.hpp new file mode 100644 index 00000000..bf438da7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_monitor.hpp @@ -0,0 +1,73 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../../htc/server/driver/htc_i_driver.hpp" +#include "../../htc/server/rpc/htc_rpc_client.hpp" +#include "htcs_service.hpp" + +namespace ams::htcs::impl { + + class HtcsMonitor { + private: + mem::StandardAllocator *m_allocator; + htc::server::driver::IDriver *m_driver; + htc::server::rpc::RpcClient *m_rpc_client; + HtcsService *m_service; + void *m_monitor_thread_stack; + os::ThreadType m_monitor_thread; + os::SdkMutex m_mutex; + os::Event m_cancel_event; + os::Event m_service_availability_event; + bool m_cancelled; + bool m_is_service_available; + private: + static void ThreadEntry(void *arg) { + static_cast<HtcsMonitor *>(arg)->ThreadBody(); + } + + void ThreadBody(); + public: + HtcsMonitor(mem::StandardAllocator *allocator, htc::server::driver::IDriver *drv, htc::server::rpc::RpcClient *rc, HtcsService *srv); + ~HtcsMonitor(); + public: + void Start(); + void Cancel(); + void Wait(); + + os::EventType *GetServiceAvailabilityEvent() { return m_service_availability_event.GetBase(); } + + bool IsServiceAvailable() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Get availability. */ + return m_is_service_available; + } + private: + void SetServiceAvailability(bool available) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Set availability. */ + m_is_service_available = available; + + /* Signal availability change. */ + m_service_availability_event.Signal(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_service.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_service.cpp new file mode 100644 index 00000000..4b57f7a5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_service.cpp @@ -0,0 +1,413 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_service.hpp" +#include "rpc/htcs_rpc_tasks.hpp" +#include "htcs_util.hpp" + +namespace ams::htcs::impl { + + void HtcsService::WaitTask(u32 task_id) { + return m_rpc_client->Wait(task_id); + } + + Result HtcsService::CreateSocket(s32 *out_err, s32 *out_desc, bool enable_disconnection_emulation) { + /* Set disconnection emulation enabled. */ + m_driver->SetDisconnectionEmulationEnabled(enable_disconnection_emulation); + + /* Begin the task. */ + u32 task_id{}; + R_TRY(m_rpc_client->Begin<rpc::SocketTask>(std::addressof(task_id))); + + /* Wait for the task to complete. */ + this->WaitTask(task_id); + + /* Finish the task. */ + htcs::SocketError err; + s32 desc; + R_TRY(m_rpc_client->End<rpc::SocketTask>(task_id, std::addressof(err), std::addressof(desc))); + + /* Set output. */ + *out_err = err; + *out_desc = desc; + R_SUCCEED(); + } + + Result HtcsService::DestroySocket(s32 desc) { + /* Begin the task. */ + u32 task_id{}; + R_TRY(m_rpc_client->Begin<rpc::CloseTask>(std::addressof(task_id), desc)); + + /* Wait for the task to complete. */ + this->WaitTask(task_id); + + /* Cancel the socket. */ + m_rpc_client->CancelBySocket(desc); + + /* Finish the task. */ + htcs::SocketError err; + R_TRY(m_rpc_client->End<rpc::CloseTask>(task_id, std::addressof(err))); + + R_SUCCEED(); + } + + Result HtcsService::Connect(s32 *out_err, s32 desc, const SockAddrHtcs &address) { + /* Validate the address. */ + R_UNLESS(address.family == 0, htcs::ResultInvalidArgument()); + R_UNLESS(IsValidName(address.peer_name), htcs::ResultInvalidArgument()); + R_UNLESS(IsValidName(address.port_name), htcs::ResultInvalidArgument()); + + /* Begin the task. */ + u32 task_id{}; + R_TRY(m_rpc_client->Begin<rpc::ConnectTask>(std::addressof(task_id), desc, address.peer_name, address.port_name)); + + /* Wait for the task to complete. */ + this->WaitTask(task_id); + + /* Finish the task. */ + htcs::SocketError err; + R_TRY(m_rpc_client->End<rpc::ConnectTask>(task_id, std::addressof(err))); + + /* Set output. */ + *out_err = err; + R_SUCCEED(); + } + + Result HtcsService::Bind(s32 *out_err, s32 desc, const SockAddrHtcs &address) { + /* Validate the address. */ + R_UNLESS(address.family == 0, htcs::ResultInvalidArgument()); + R_UNLESS(IsValidName(address.peer_name), htcs::ResultInvalidArgument()); + R_UNLESS(IsValidName(address.port_name), htcs::ResultInvalidArgument()); + + /* Begin the task. */ + u32 task_id{}; + R_TRY(m_rpc_client->Begin<rpc::BindTask>(std::addressof(task_id), desc, address.peer_name, address.port_name)); + + /* Wait for the task to complete. */ + this->WaitTask(task_id); + + /* Finish the task. */ + htcs::SocketError err; + R_TRY(m_rpc_client->End<rpc::BindTask>(task_id, std::addressof(err))); + + /* Set output. */ + *out_err = err; + R_SUCCEED(); + } + + Result HtcsService::Listen(s32 *out_err, s32 desc, s32 backlog_count) { + /* Begin the task. */ + u32 task_id{}; + R_TRY(m_rpc_client->Begin<rpc::ListenTask>(std::addressof(task_id), desc, backlog_count)); + + /* Wait for the task to complete. */ + this->WaitTask(task_id); + + /* Finish the task. */ + htcs::SocketError err; + R_TRY(m_rpc_client->End<rpc::ListenTask>(task_id, std::addressof(err))); + + /* Set output. */ + *out_err = err; + R_SUCCEED(); + } + + Result HtcsService::Receive(s32 *out_err, s64 *out_size, char *buffer, s64 size, s32 desc, s32 flags) { + /* Begin the task. */ + u32 task_id{}; + R_TRY(m_rpc_client->Begin<rpc::ReceiveTask>(std::addressof(task_id), desc, size, static_cast<htcs::MessageFlag>(flags))); + + /* Wait for the task to complete. */ + this->WaitTask(task_id); + + /* Finish the task. */ + R_TRY(this->ReceiveResults(out_err, out_size, buffer, size, task_id, desc)); + + R_SUCCEED(); + } + + Result HtcsService::Send(s32 *out_err, s64 *out_size, const char *buffer, s64 size, s32 desc, s32 flags) { + /* Begin the task. */ + u32 task_id{}; + R_TRY(m_rpc_client->Begin<rpc::SendTask>(std::addressof(task_id), desc, size, static_cast<htcs::MessageFlag>(flags))); + + /* Send the data. */ + s64 cont_size; + const Result result = this->SendContinue(std::addressof(cont_size), buffer, size, task_id, desc); + if (R_FAILED(result)) { + R_RETURN(this->SendResults(out_err, out_size, task_id, desc)); + } + + /* Wait for the task to complete. */ + this->WaitTask(task_id); + + /* Finish the task. */ + R_TRY(this->SendResults(out_err, out_size, task_id, desc)); + + R_SUCCEED(); + } + + Result HtcsService::Shutdown(s32 *out_err, s32 desc, s32 how) { + /* Begin the task. */ + u32 task_id{}; + R_TRY(m_rpc_client->Begin<rpc::ShutdownTask>(std::addressof(task_id), desc, static_cast<htcs::ShutdownType>(how))); + + /* Wait for the task to complete. */ + this->WaitTask(task_id); + + /* Finish the task. */ + htcs::SocketError err; + R_TRY(m_rpc_client->End<rpc::ShutdownTask>(task_id, std::addressof(err))); + + /* Set output. */ + *out_err = err; + R_SUCCEED(); + } + + Result HtcsService::Fcntl(s32 *out_err, s32 *out_res, s32 desc, s32 command, s32 value) { + /* Begin the task. */ + u32 task_id{}; + R_TRY(m_rpc_client->Begin<rpc::FcntlTask>(std::addressof(task_id), desc, command, value)); + + /* Wait for the task to complete. */ + this->WaitTask(task_id); + + /* Finish the task. */ + htcs::SocketError err; + s32 res; + R_TRY(m_rpc_client->End<rpc::FcntlTask>(task_id, std::addressof(err), std::addressof(res))); + + /* Set output. */ + *out_err = err; + *out_res = res; + R_SUCCEED(); + } + + Result HtcsService::AcceptStart(u32 *out_task_id, os::NativeHandle *out_handle, s32 desc) { + /* Begin the task. */ + u32 task_id{}; + R_TRY(m_rpc_client->Begin<rpc::AcceptTask>(std::addressof(task_id), desc)); + + /* Detach the task. */ + *out_task_id = task_id; + *out_handle = m_rpc_client->DetachReadableHandle(task_id); + + R_SUCCEED(); + } + + Result HtcsService::AcceptResults(s32 *out_err, s32 *out_desc, SockAddrHtcs *out_address, u32 task_id, s32 desc) { + AMS_UNUSED(out_address); + + /* Finish the task. */ + htcs::SocketError err; + s32 ret_desc; + R_TRY(m_rpc_client->End<rpc::AcceptTask>(task_id, std::addressof(err), std::addressof(ret_desc), desc)); + + /* Set output. */ + *out_err = err; + *out_desc = ret_desc; + R_SUCCEED(); + } + + Result HtcsService::ReceiveSmallStart(u32 *out_task_id, os::NativeHandle *out_handle, s64 size, s32 desc, s32 flags) { + /* Begin the task. */ + u32 task_id{}; + R_TRY(m_rpc_client->Begin<rpc::ReceiveSmallTask>(std::addressof(task_id), desc, size, static_cast<htcs::MessageFlag>(flags))); + + /* Detach the task. */ + *out_task_id = task_id; + *out_handle = m_rpc_client->DetachReadableHandle(task_id); + + R_SUCCEED(); + } + + Result HtcsService::ReceiveSmallResults(s32 *out_err, s64 *out_size, char *buffer, s64 buffer_size, u32 task_id, s32 desc) { + AMS_UNUSED(desc); + + /* Continue the task. */ + m_rpc_client->ReceiveContinue<rpc::ReceiveSmallTask>(task_id, buffer, buffer_size); + + /* Finish the task. */ + htcs::SocketError err; + R_TRY(m_rpc_client->End<rpc::ReceiveSmallTask>(task_id, std::addressof(err), out_size)); + + /* Set output. */ + *out_err = err; + R_SUCCEED(); + } + + Result HtcsService::SendSmallStart(u32 *out_task_id, os::NativeHandle *out_handle, s32 desc, s64 size, s32 flags) { + /* Begin the task. */ + u32 task_id{}; + R_TRY(m_rpc_client->Begin<rpc::SendSmallTask>(std::addressof(task_id), desc, size, static_cast<htcs::MessageFlag>(flags))); + + /* Detach the task. */ + *out_task_id = task_id; + *out_handle = m_rpc_client->DetachReadableHandle(task_id); + + R_SUCCEED(); + } + + Result HtcsService::SendSmallContinue(s64 *out_size, const char *buffer, s64 buffer_size, u32 task_id, s32 desc) { + /* Verify the task. */ + R_TRY(m_rpc_client->VerifyTaskIdWithHandle<rpc::SendSmallTask>(task_id, desc)); + + /* Continue the task. */ + R_TRY(m_rpc_client->SendContinue<rpc::SendSmallTask>(task_id, buffer, buffer_size)); + + /* Set output. */ + *out_size = buffer_size; + R_SUCCEED(); + } + + Result HtcsService::SendSmallResults(s32 *out_err, s64 *out_size, u32 task_id, s32 desc) { + AMS_UNUSED(desc); + + /* Finish the task. */ + htcs::SocketError err; + R_TRY(m_rpc_client->End<rpc::SendSmallTask>(task_id, std::addressof(err), out_size)); + + /* Set output. */ + *out_err = err; + R_SUCCEED(); + } + + Result HtcsService::SendStart(u32 *out_task_id, os::NativeHandle *out_handle, s32 desc, s64 size, s32 flags) { + /* Begin the task. */ + u32 task_id{}; + R_TRY(m_rpc_client->Begin<rpc::SendTask>(std::addressof(task_id), desc, size, static_cast<htcs::MessageFlag>(flags))); + + /* Detach the task. */ + *out_task_id = task_id; + *out_handle = m_rpc_client->DetachReadableHandle(task_id); + + R_SUCCEED(); + } + + Result HtcsService::SendContinue(s64 *out_size, const char *buffer, s64 buffer_size, u32 task_id, s32 desc) { + /* Verify the task. */ + R_TRY(m_rpc_client->VerifyTaskIdWithHandle<rpc::SendTask>(task_id, desc)); + + /* Wait for the task to notify. */ + m_rpc_client->WaitNotification<rpc::SendTask>(task_id); + + /* Check the task status. */ + R_UNLESS(!m_rpc_client->IsCompleted<rpc::SendTask>(task_id), htcs::ResultCompleted()); + R_UNLESS(!m_rpc_client->IsCancelled<rpc::SendTask>(task_id), htcs::ResultCancelled()); + + /* Send the data. */ + if (buffer_size > 0) { + R_TRY(m_data_channel_manager->Send(buffer, buffer_size, task_id)); + } + + /* Set output. */ + *out_size = buffer_size; + R_SUCCEED(); + } + + Result HtcsService::SendResults(s32 *out_err, s64 *out_size, u32 task_id, s32 desc) { + /* Verify the task. */ + R_TRY(m_rpc_client->VerifyTaskIdWithHandle<rpc::SendTask>(task_id, desc)); + + /* Finish the task. */ + htcs::SocketError err; + R_TRY(m_rpc_client->End<rpc::SendTask>(task_id, std::addressof(err), out_size)); + + /* Set output. */ + *out_err = err; + R_SUCCEED(); + } + + Result HtcsService::ReceiveStart(u32 *out_task_id, os::NativeHandle *out_handle, s64 size, s32 desc, s32 flags) { + /* Begin the task. */ + u32 task_id{}; + R_TRY(m_rpc_client->Begin<rpc::ReceiveTask>(std::addressof(task_id), desc, size, static_cast<htcs::MessageFlag>(flags))); + + /* Detach the task. */ + *out_task_id = task_id; + *out_handle = m_rpc_client->DetachReadableHandle(task_id); + + R_SUCCEED(); + } + + Result HtcsService::ReceiveResults(s32 *out_err, s64 *out_size, char *buffer, s64 buffer_size, u32 task_id, s32 desc) { + /* Verify the task. */ + R_TRY(m_rpc_client->VerifyTaskIdWithHandle<rpc::ReceiveTask>(task_id, desc)); + + /* Get the result. */ + htcs::SocketError err{}; + s64 recv_size{}; + const Result result = m_rpc_client->GetResult<rpc::ReceiveTask>(task_id, std::addressof(err), std::addressof(recv_size)); + if (R_FAILED(result) || err != HTCS_ENONE) { + /* Finish the task. */ + R_TRY(m_rpc_client->End<rpc::ReceiveTask>(task_id, std::addressof(err), out_size)); + + /* Set output. */ + *out_err = err; + R_SUCCEED(); + } + + /* Check the size. */ + R_UNLESS(recv_size <= buffer_size, htcs::ResultInvalidArgument()); + + /* Perform remaining processing. */ + if (recv_size > 0) { + /* Receive data. */ + const Result recv_result = m_data_channel_manager->Receive(buffer, recv_size, task_id); + + /* Finish the task. */ + R_TRY(m_rpc_client->End<rpc::ReceiveTask>(task_id, std::addressof(err), out_size)); + + /* Check that our receive succeeded. */ + R_TRY(recv_result); + } else { + /* Finish the task. */ + R_TRY(m_rpc_client->End<rpc::ReceiveTask>(task_id, std::addressof(err), out_size)); + } + + /* Set output. */ + *out_err = err; + R_SUCCEED(); + } + + Result HtcsService::SelectStart(u32 *out_task_id, os::NativeHandle *out_handle, Span<const int> read_handles, Span<const int> write_handles, Span<const int> exception_handles, s64 tv_sec, s64 tv_usec) { + /* Begin the task. */ + u32 task_id{}; + R_TRY(m_rpc_client->Begin<rpc::SelectTask>(std::addressof(task_id), read_handles, write_handles, exception_handles, tv_sec, tv_usec)); + + /* Detach the task. */ + *out_task_id = task_id; + *out_handle = m_rpc_client->DetachReadableHandle(task_id); + + /* Check that the task isn't cancelled. */ + R_UNLESS(!m_rpc_client->IsCancelled<rpc::SelectTask>(task_id), htcs::ResultCancelled()); + + R_SUCCEED(); + } + + Result HtcsService::SelectEnd(s32 *out_err, bool *out_empty, Span<int> read_handles, Span<int> write_handles, Span<int> exception_handles, u32 task_id) { + /* Finish the task. */ + htcs::SocketError err; + bool empty; + R_TRY(m_rpc_client->End<rpc::SelectTask>(task_id, std::addressof(err), std::addressof(empty), read_handles, write_handles, exception_handles)); + + /* Set output. */ + *out_err = err; + *out_empty = empty; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_service.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_service.hpp new file mode 100644 index 00000000..bb7700fa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_service.hpp @@ -0,0 +1,67 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../../htc/server/driver/htc_i_driver.hpp" +#include "../../htc/server/rpc/htc_rpc_client.hpp" +#include "rpc/htcs_data_channel_manager.hpp" + +namespace ams::htcs::impl { + + class HtcsService { + private: + mem::StandardAllocator *m_allocator; + htc::server::driver::IDriver *m_driver; + htc::server::rpc::RpcClient *m_rpc_client; + rpc::DataChannelManager *m_data_channel_manager; + public: + HtcsService(mem::StandardAllocator *allocator, htc::server::driver::IDriver *drv, htc::server::rpc::RpcClient *rc, rpc::DataChannelManager *dcm) + : m_allocator(allocator), m_driver(drv), m_rpc_client(rc), m_data_channel_manager(dcm) { /* ... */ } + public: + Result CreateSocket(s32 *out_err, s32 *out_desc, bool enable_disconnection_emulation); + Result DestroySocket(s32 desc); + Result Connect(s32 *out_err, s32 desc, const SockAddrHtcs &address); + Result Bind(s32 *out_err, s32 desc, const SockAddrHtcs &address); + Result Listen(s32 *out_err, s32 desc, s32 backlog_count); + Result Receive(s32 *out_err, s64 *out_size, char *buffer, s64 size, s32 desc, s32 flags); + Result Send(s32 *out_err, s64 *out_size, const char *buffer, s64 size, s32 desc, s32 flags); + Result Shutdown(s32 *out_err, s32 desc, s32 how); + Result Fcntl(s32 *out_err, s32 *out_res, s32 desc, s32 command, s32 value); + + Result AcceptStart(u32 *out_task_id, os::NativeHandle *out_handle, s32 desc); + Result AcceptResults(s32 *out_err, s32 *out_desc, SockAddrHtcs *out_address, u32 task_id, s32 desc); + + Result ReceiveSmallStart(u32 *out_task_id, os::NativeHandle *out_handle, s64 size, s32 desc, s32 flags); + Result ReceiveSmallResults(s32 *out_err, s64 *out_size, char *buffer, s64 buffer_size, u32 task_id, s32 desc); + + Result SendSmallStart(u32 *out_task_id, os::NativeHandle *out_handle, s32 desc, s64 size, s32 flags); + Result SendSmallContinue(s64 *out_size, const char *buffer, s64 buffer_size, u32 task_id, s32 desc); + Result SendSmallResults(s32 *out_err, s64 *out_size, u32 task_id, s32 desc); + + Result SendStart(u32 *out_task_id, os::NativeHandle *out_handle, s32 desc, s64 size, s32 flags); + Result SendContinue(s64 *out_size, const char *buffer, s64 buffer_size, u32 task_id, s32 desc); + Result SendResults(s32 *out_err, s64 *out_size, u32 task_id, s32 desc); + + Result ReceiveStart(u32 *out_task_id, os::NativeHandle *out_handle, s64 size, s32 desc, s32 flags); + Result ReceiveResults(s32 *out_err, s64 *out_size, char *buffer, s64 buffer_size, u32 task_id, s32 desc); + + Result SelectStart(u32 *out_task_id, os::NativeHandle *out_handle, Span<const int> read_handles, Span<const int> write_handles, Span<const int> exception_handles, s64 tv_sec, s64 tv_usec); + Result SelectEnd(s32 *out_err, bool *out_empty, Span<int> read_handles, Span<int> write_handles, Span<int> exception_handles, u32 task_id); + private: + void WaitTask(u32 task_id); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_util.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_util.cpp new file mode 100644 index 00000000..27f9778d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_util.cpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_util.hpp" + +namespace ams::htcs::impl { + + s32 ConvertResultToErrorCode(const Result result) { + /* Convert success. */ + if (R_SUCCEEDED(result)) { + return 0; + } + + R_TRY_CATCH(result) { + R_CATCH(htclow::ResultNonBlockingReceiveFailed) { return HTCS_EWOULDBLOCK; } + R_CATCH(htcs::ResultInvalidHandle) { return HTCS_EBADF; } + R_CATCH(htc::ResultUnknown2001) { return HTCS_EINVAL; } + R_CATCH(htc::ResultUnknown2101) { return HTCS_EMFILE; } + R_CATCH(htc::ResultTaskCancelled) { return HTCS_EINTR; } + R_CATCH(htc::ResultInvalidTaskId) { return HTCS_EINTR; } + R_CATCH(htc::ResultCancelled) { return HTCS_EINTR; } + R_CATCH(htc::ResultTaskQueueNotAvailable) { return HTCS_ENETDOWN; } + R_CATCH(htclow::ResultConnectionFailure) { return HTCS_ENETDOWN; } + R_CATCH(htclow::ResultChannelNotExist) { return HTCS_ENOTCONN; } + R_CATCH_ALL() { return HTCS_EUNKNOWN; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + __builtin_unreachable(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_util.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_util.hpp new file mode 100644 index 00000000..3c5814b8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/htcs_util.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htcs::impl { + + s32 ConvertResultToErrorCode(const Result result); + + constexpr bool IsValidName(const char *name) { + static_assert(PeerNameBufferLength == PortNameBufferLength); + return util::Strnlen(name, PeerNameBufferLength) < PeerNameBufferLength; + } + + constexpr bool IsValidName(const HtcsPeerName &name) { + return IsValidName(name.name); + } + + constexpr bool IsValidName(const HtcsPortName &name) { + return IsValidName(name.name); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_data_channel_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_data_channel_manager.cpp new file mode 100644 index 00000000..03f4decf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_data_channel_manager.cpp @@ -0,0 +1,98 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_rpc_tasks.hpp" +#include "htcs_data_channel_manager.hpp" +#include "../../../htclow/htclow_channel.hpp" + +namespace ams::htcs::impl::rpc { + + Result DataChannelManager::Receive(void *buffer, s64 buffer_size, u32 task_id) { + /* Check that the buffer size is allowable. */ + R_UNLESS(util::IsIntValueRepresentable<size_t>(buffer_size), htcs::ResultInvalidSize()); + + /* Create an htclow channel. */ + htclow::Channel channel(m_htclow_manager); + + /* Open the channel. */ + R_ABORT_UNLESS(channel.Open(std::addressof(m_module), GetReceiveDataChannelId(task_id))); + + /* Ensure that we close the channel, when we're done. */ + ON_SCOPE_EXIT { channel.Close(); }; + + /* Set the channel config. */ + constexpr htclow::ChannelConfig BulkReceiveConfig = { + .flow_control_enabled = false, + .handshake_enabled = false, + .max_packet_size = 0x3E000, + }; + channel.SetConfig(BulkReceiveConfig); + + /* Set the receive buffer. */ + channel.SetReceiveBuffer(buffer, buffer_size); + + /* Connect the channel. */ + R_TRY(channel.Connect()); + + /* Ensure that we clean up when we're done. */ + ON_SCOPE_EXIT { channel.Shutdown(); }; + + /* Notify the receive task. */ + R_TRY(m_rpc_client->Notify<ReceiveTask>(task_id)); + + /* Wait to receive the data. */ + R_TRY(channel.WaitReceive(buffer_size)); + + R_SUCCEED(); + } + + Result DataChannelManager::Send(const void *buffer, s64 buffer_size, u32 task_id) { + /* Check that the buffer size is allowable. */ + R_UNLESS(util::IsIntValueRepresentable<size_t>(buffer_size), htcs::ResultInvalidSize()); + + /* Create an htclow channel. */ + htclow::Channel channel(m_htclow_manager); + + /* Open the channel. */ + R_ABORT_UNLESS(channel.Open(std::addressof(m_module), GetSendDataChannelId(task_id))); + + /* Ensure that we close the channel, when we're done. */ + ON_SCOPE_EXIT { channel.Close(); }; + + /* Set the channel config. */ + constexpr htclow::ChannelConfig BulkSendConfig = { + .flow_control_enabled = false, + .handshake_enabled = false, + .max_packet_size = 0x3E000, + }; + channel.SetConfig(BulkSendConfig); + + /* Set the send buffer. */ + channel.SetSendBufferWithData(buffer, buffer_size); + + /* Connect the channel. */ + R_TRY(channel.Connect()); + + /* Ensure that we clean up when we're done. */ + ON_SCOPE_EXIT { channel.Shutdown(); }; + + /* Wait to send the data. */ + R_TRY(channel.Flush()); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_data_channel_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_data_channel_manager.hpp new file mode 100644 index 00000000..135d0030 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_data_channel_manager.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../../../htclow/htclow_manager.hpp" +#include "../../../htc/server/rpc/htc_rpc_client.hpp" + +namespace ams::htcs::impl::rpc { + + class DataChannelManager { + private: + htc::server::rpc::RpcClient* m_rpc_client; + htclow::HtclowManager *m_htclow_manager; + htclow::Module m_module; + public: + DataChannelManager(htc::server::rpc::RpcClient *client, htclow::HtclowManager *htclow_manager) : m_rpc_client(client), m_htclow_manager(htclow_manager), m_module(htclow::ModuleId::Htcs) { /* ... */ } + public: + Result Receive(void *buffer, s64 buffer_size, u32 task_id); + Result Send(const void *buffer, s64 buffer_size, u32 task_id); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_accept_task.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_accept_task.cpp new file mode 100644 index 00000000..33b7f797 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_accept_task.cpp @@ -0,0 +1,89 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_rpc_tasks.hpp" + +namespace ams::htcs::impl::rpc { + + Result AcceptTask::SetArguments(s32 server_handle) { + /* Check that we're valid. */ + R_UNLESS(this->IsValid(), htcs::ResultInvalidTask()); + + /* Set our arguments. */ + m_server_handle = server_handle; + + R_SUCCEED(); + } + + void AcceptTask::Complete(htcs::SocketError err, s32 desc) { + /* Set our results. */ + m_err = err; + m_desc = desc; + + /* Complete. */ + HtcsSignalingTask::Complete(); + } + + Result AcceptTask::GetResult(htcs::SocketError *out_err, s32 *out_desc, s32 server_handle) const { + /* Check the server handle. */ + R_UNLESS(m_server_handle == server_handle, htcs::ResultInvalidServerHandle()); + + /* Sanity check our state. */ + AMS_ASSERT(this->GetTaskState() == htc::server::rpc::RpcTaskState::Completed); + + /* Set the output. */ + *out_err = m_err; + *out_desc = m_desc; + + R_SUCCEED(); + } + + Result AcceptTask::ProcessResponse(const char *data, size_t size) { + AMS_UNUSED(size); + + /* Convert the input to a packet. */ + auto *packet = reinterpret_cast<const HtcsRpcPacket *>(data); + + /* Complete the task. */ + this->Complete(static_cast<htcs::SocketError>(packet->params[0]), packet->params[1]); + + R_SUCCEED(); + } + + Result AcceptTask::CreateRequest(size_t *out, char *data, size_t size, u32 task_id) { + AMS_UNUSED(size); + + /* Create the packet. */ + auto *packet = reinterpret_cast<HtcsRpcPacket *>(data); + *packet = { + .protocol = HtcsProtocol, + .version = this->GetVersion(), + .category = HtcsPacketCategory::Request, + .type = HtcsPacketType::Accept, + .body_size = 0, + .task_id = task_id, + .params = { + m_server_handle, + }, + }; + + /* Set the output size. */ + *out = sizeof(*packet); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_bind_task.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_bind_task.cpp new file mode 100644 index 00000000..28b841f6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_bind_task.cpp @@ -0,0 +1,85 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_rpc_tasks.hpp" + +namespace ams::htcs::impl::rpc { + + Result BindTask::SetArguments(s32 handle, const HtcsPeerName &peer_name, const HtcsPortName &port_name) { + /* Set our arguments. */ + m_handle = handle; + m_peer_name = peer_name; + m_port_name = port_name; + + R_SUCCEED(); + } + + void BindTask::Complete(htcs::SocketError err) { + /* Set our results. */ + m_err = err; + + /* Complete. */ + HtcsTask::Complete(); + } + + Result BindTask::GetResult(htcs::SocketError *out_err) const { + /* Sanity check our state. */ + AMS_ASSERT(this->GetTaskState() == htc::server::rpc::RpcTaskState::Completed); + + /* Set the output. */ + *out_err = m_err; + + R_SUCCEED(); + } + + Result BindTask::ProcessResponse(const char *data, size_t size) { + AMS_UNUSED(size); + + /* Convert the input to a packet. */ + auto *packet = reinterpret_cast<const HtcsRpcPacket *>(data); + + /* Complete the task. */ + this->Complete(static_cast<htcs::SocketError>(packet->params[0])); + + R_SUCCEED(); + } + + Result BindTask::CreateRequest(size_t *out, char *data, size_t size, u32 task_id) { + AMS_UNUSED(size); + + /* Create the packet. */ + auto *packet = reinterpret_cast<HtcsRpcPacket *>(data); + *packet = { + .protocol = HtcsProtocol, + .version = this->GetVersion(), + .category = HtcsPacketCategory::Request, + .type = HtcsPacketType::Bind, + .body_size = sizeof(m_peer_name) + sizeof(m_port_name), + .task_id = task_id, + .params = { + m_handle, + }, + }; + std::memcpy(packet->data + 0, std::addressof(m_peer_name), sizeof(m_peer_name)); + std::memcpy(packet->data + sizeof(m_peer_name), std::addressof(m_port_name), sizeof(m_port_name)); + + /* Set the output size. */ + *out = sizeof(*packet) + sizeof(m_peer_name) + sizeof(m_port_name); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_close_task.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_close_task.cpp new file mode 100644 index 00000000..100e7b18 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_close_task.cpp @@ -0,0 +1,81 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_rpc_tasks.hpp" + +namespace ams::htcs::impl::rpc { + + Result CloseTask::SetArguments(s32 handle) { + /* Set our arguments. */ + m_handle = handle; + + R_SUCCEED(); + } + + void CloseTask::Complete(htcs::SocketError err) { + /* Set our results. */ + m_err = err; + + /* Complete. */ + HtcsTask::Complete(); + } + + Result CloseTask::GetResult(htcs::SocketError *out_err) const { + /* Sanity check our state. */ + AMS_ASSERT(this->GetTaskState() == htc::server::rpc::RpcTaskState::Completed); + + /* Set the output. */ + *out_err = m_err; + + R_SUCCEED(); + } + + Result CloseTask::ProcessResponse(const char *data, size_t size) { + AMS_UNUSED(size); + + /* Convert the input to a packet. */ + auto *packet = reinterpret_cast<const HtcsRpcPacket *>(data); + + /* Complete the task. */ + this->Complete(static_cast<htcs::SocketError>(packet->params[0])); + + R_SUCCEED(); + } + + Result CloseTask::CreateRequest(size_t *out, char *data, size_t size, u32 task_id) { + AMS_UNUSED(size); + + /* Create the packet. */ + auto *packet = reinterpret_cast<HtcsRpcPacket *>(data); + *packet = { + .protocol = HtcsProtocol, + .version = this->GetVersion(), + .category = HtcsPacketCategory::Request, + .type = HtcsPacketType::Close, + .body_size = 0, + .task_id = task_id, + .params = { + m_handle, + }, + }; + + /* Set the output size. */ + *out = sizeof(*packet); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_connect_task.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_connect_task.cpp new file mode 100644 index 00000000..c4f59f5c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_connect_task.cpp @@ -0,0 +1,85 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_rpc_tasks.hpp" + +namespace ams::htcs::impl::rpc { + + Result ConnectTask::SetArguments(s32 handle, const HtcsPeerName &peer_name, const HtcsPortName &port_name) { + /* Set our arguments. */ + m_handle = handle; + m_peer_name = peer_name; + m_port_name = port_name; + + R_SUCCEED(); + } + + void ConnectTask::Complete(htcs::SocketError err) { + /* Set our results. */ + m_err = err; + + /* Complete. */ + HtcsTask::Complete(); + } + + Result ConnectTask::GetResult(htcs::SocketError *out_err) const { + /* Sanity check our state. */ + AMS_ASSERT(this->GetTaskState() == htc::server::rpc::RpcTaskState::Completed); + + /* Set the output. */ + *out_err = m_err; + + R_SUCCEED(); + } + + Result ConnectTask::ProcessResponse(const char *data, size_t size) { + AMS_UNUSED(size); + + /* Convert the input to a packet. */ + auto *packet = reinterpret_cast<const HtcsRpcPacket *>(data); + + /* Complete the task. */ + this->Complete(static_cast<htcs::SocketError>(packet->params[0])); + + R_SUCCEED(); + } + + Result ConnectTask::CreateRequest(size_t *out, char *data, size_t size, u32 task_id) { + AMS_UNUSED(size); + + /* Create the packet. */ + auto *packet = reinterpret_cast<HtcsRpcPacket *>(data); + *packet = { + .protocol = HtcsProtocol, + .version = this->GetVersion(), + .category = HtcsPacketCategory::Request, + .type = HtcsPacketType::Connect, + .body_size = sizeof(m_peer_name) + sizeof(m_port_name), + .task_id = task_id, + .params = { + m_handle, + }, + }; + std::memcpy(packet->data + 0, std::addressof(m_peer_name), sizeof(m_peer_name)); + std::memcpy(packet->data + sizeof(m_peer_name), std::addressof(m_port_name), sizeof(m_port_name)); + + /* Set the output size. */ + *out = sizeof(*packet) + sizeof(m_peer_name) + sizeof(m_port_name); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_fcntl_task.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_fcntl_task.cpp new file mode 100644 index 00000000..d629ca97 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_fcntl_task.cpp @@ -0,0 +1,87 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_rpc_tasks.hpp" + +namespace ams::htcs::impl::rpc { + + Result FcntlTask::SetArguments(s32 handle, s32 command, s32 value) { + /* Set our arguments. */ + m_handle = handle; + m_command = command; + m_value = value; + + R_SUCCEED(); + } + + void FcntlTask::Complete(htcs::SocketError err, s32 res) { + /* Set our results. */ + m_err = err; + m_res = res; + + /* Complete. */ + HtcsTask::Complete(); + } + + Result FcntlTask::GetResult(htcs::SocketError *out_err, s32 *out_res) const { + /* Sanity check our state. */ + AMS_ASSERT(this->GetTaskState() == htc::server::rpc::RpcTaskState::Completed); + + /* Set the output. */ + *out_err = m_err; + *out_res = m_res; + + R_SUCCEED(); + } + + Result FcntlTask::ProcessResponse(const char *data, size_t size) { + AMS_UNUSED(size); + + /* Convert the input to a packet. */ + auto *packet = reinterpret_cast<const HtcsRpcPacket *>(data); + + /* Complete the task. */ + this->Complete(static_cast<htcs::SocketError>(packet->params[0]), packet->params[1]); + + R_SUCCEED(); + } + + Result FcntlTask::CreateRequest(size_t *out, char *data, size_t size, u32 task_id) { + AMS_UNUSED(size); + + /* Create the packet. */ + auto *packet = reinterpret_cast<HtcsRpcPacket *>(data); + *packet = { + .protocol = HtcsProtocol, + .version = this->GetVersion(), + .category = HtcsPacketCategory::Request, + .type = HtcsPacketType::Fcntl, + .body_size = 0, + .task_id = task_id, + .params = { + m_handle, + m_command, + m_value, + }, + }; + + /* Set the output size. */ + *out = sizeof(*packet); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_listen_task.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_listen_task.cpp new file mode 100644 index 00000000..d8e42808 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_listen_task.cpp @@ -0,0 +1,83 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_rpc_tasks.hpp" + +namespace ams::htcs::impl::rpc { + + Result ListenTask::SetArguments(s32 handle, s32 backlog) { + /* Set our arguments. */ + m_handle = handle; + m_backlog = backlog; + + R_SUCCEED(); + } + + void ListenTask::Complete(htcs::SocketError err) { + /* Set our results. */ + m_err = err; + + /* Complete. */ + HtcsTask::Complete(); + } + + Result ListenTask::GetResult(htcs::SocketError *out_err) const { + /* Sanity check our state. */ + AMS_ASSERT(this->GetTaskState() == htc::server::rpc::RpcTaskState::Completed); + + /* Set the output. */ + *out_err = m_err; + + R_SUCCEED(); + } + + Result ListenTask::ProcessResponse(const char *data, size_t size) { + AMS_UNUSED(size); + + /* Convert the input to a packet. */ + auto *packet = reinterpret_cast<const HtcsRpcPacket *>(data); + + /* Complete the task. */ + this->Complete(static_cast<htcs::SocketError>(packet->params[0])); + + R_SUCCEED(); + } + + Result ListenTask::CreateRequest(size_t *out, char *data, size_t size, u32 task_id) { + AMS_UNUSED(size); + + /* Create the packet. */ + auto *packet = reinterpret_cast<HtcsRpcPacket *>(data); + *packet = { + .protocol = HtcsProtocol, + .version = this->GetVersion(), + .category = HtcsPacketCategory::Request, + .type = HtcsPacketType::Listen, + .body_size = 0, + .task_id = task_id, + .params = { + m_handle, + m_backlog, + }, + }; + + /* Set the output size. */ + *out = sizeof(*packet); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_receive_small_task.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_receive_small_task.cpp new file mode 100644 index 00000000..a5544ac8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_receive_small_task.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_rpc_tasks.hpp" + +namespace ams::htcs::impl::rpc { + + Result ReceiveSmallTask::SetArguments(s32 handle, s64 size, htcs::MessageFlag flags) { + /* Check that we're valid. */ + R_UNLESS(this->IsValid(), htcs::ResultInvalidTask()); + + /* Set our arguments. */ + m_handle = handle; + m_size = size; + m_flags = flags; + + R_SUCCEED(); + } + + void ReceiveSmallTask::Complete(htcs::SocketError err, s64 size) { + /* Set our results. */ + m_err = err; + m_result_size = size; + + /* Complete. */ + HtcsSignalingTask::Complete(); + } + + Result ReceiveSmallTask::GetResult(htcs::SocketError *out_err, s64 *out_size) const { + /* Sanity check our state. */ + AMS_ASSERT(this->GetTaskState() == htc::server::rpc::RpcTaskState::Completed); + + /* Set the output. */ + *out_err = m_err; + *out_size = m_result_size; + + R_SUCCEED(); + } + + Result ReceiveSmallTask::ProcessResponse(const char *data, size_t size) { + AMS_UNUSED(size); + + /* Convert the input to a packet. */ + auto *packet = reinterpret_cast<const HtcsRpcPacket *>(data); + + /* Copy the data to our buffer. */ + std::memcpy(m_buffer, packet->data, packet->body_size); + + /* Complete the task. */ + this->Complete(static_cast<htcs::SocketError>(packet->params[0]), packet->body_size); + + R_SUCCEED(); + } + + Result ReceiveSmallTask::CreateRequest(size_t *out, char *data, size_t size, u32 task_id) { + AMS_UNUSED(size); + + /* Create the packet. */ + auto *packet = reinterpret_cast<HtcsRpcPacket *>(data); + *packet = { + .protocol = HtcsProtocol, + .version = this->GetVersion(), + .category = HtcsPacketCategory::Request, + .type = HtcsPacketType::Receive, + .body_size = 0, + .task_id = task_id, + .params = { + m_handle, + m_size, + static_cast<s64>(m_flags), + }, + }; + + /* Set the output size. */ + *out = sizeof(*packet); + + R_SUCCEED(); + } + + bool ReceiveSmallTask::IsReceiveBufferRequired() { + return true; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_receive_task.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_receive_task.cpp new file mode 100644 index 00000000..5eb12c2f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_receive_task.cpp @@ -0,0 +1,114 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_rpc_tasks.hpp" + +namespace ams::htcs::impl::rpc { + + Result ReceiveTask::SetArguments(s32 handle, s64 size, htcs::MessageFlag flags) { + /* Check that we're valid. */ + R_UNLESS(this->IsValid(), htcs::ResultInvalidTask()); + + /* Set our arguments. */ + m_handle = handle; + m_size = size; + m_flags = flags; + + R_SUCCEED(); + } + + void ReceiveTask::Complete(htcs::SocketError err, s64 size) { + /* Set our results. */ + m_err = err; + m_result_size = size; + + /* Complete. */ + HtcsSignalingTask::Complete(); + } + + Result ReceiveTask::GetResult(htcs::SocketError *out_err, s64 *out_size) const { + /* Sanity check our state. */ + AMS_ASSERT(this->GetTaskState() == htc::server::rpc::RpcTaskState::Completed); + + /* Set the output. */ + *out_err = m_err; + *out_size = m_result_size; + + R_SUCCEED(); + } + + Result ReceiveTask::ProcessResponse(const char *data, size_t size) { + AMS_UNUSED(size); + + /* Convert the input to a packet. */ + auto *packet = reinterpret_cast<const HtcsRpcPacket *>(data); + + /* Complete the task. */ + this->Complete(static_cast<htcs::SocketError>(packet->params[0]), packet->params[1]); + + R_SUCCEED(); + } + + Result ReceiveTask::CreateRequest(size_t *out, char *data, size_t size, u32 task_id) { + AMS_UNUSED(size); + + /* Create the packet. */ + auto *packet = reinterpret_cast<HtcsRpcPacket *>(data); + *packet = { + .protocol = HtcsProtocol, + .version = this->GetVersion(), + .category = HtcsPacketCategory::Request, + .type = HtcsPacketType::ReceiveLarge, + .body_size = 0, + .task_id = task_id, + .params = { + m_handle, + m_size, + static_cast<s64>(m_flags), + GetReceiveDataChannelId(task_id), + }, + }; + + /* Set the output size. */ + *out = sizeof(*packet); + + R_SUCCEED(); + } + + Result ReceiveTask::CreateNotification(size_t *out, char *data, size_t size, u32 task_id) { + AMS_UNUSED(size); + + /* Create the packet. */ + auto *packet = reinterpret_cast<HtcsRpcPacket *>(data); + *packet = { + .protocol = HtcsProtocol, + .version = this->GetVersion(), + .category = HtcsPacketCategory::Notification, + .type = HtcsPacketType::ReceiveLarge, + .body_size = 0, + .task_id = task_id, + .params = { + /* ... */ + }, + }; + + /* Set the output size. */ + *out = sizeof(*packet); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_select_task.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_select_task.cpp new file mode 100644 index 00000000..9ae737f6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_select_task.cpp @@ -0,0 +1,155 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_rpc_tasks.hpp" + +namespace ams::htcs::impl::rpc { + + Result SelectTask::SetArguments(Span<const int> read_handles, Span<const int> write_handles, Span<const int> exception_handles, s64 tv_sec, s64 tv_usec) { + /* Check that we're valid. */ + R_UNLESS(this->IsValid(), htcs::ResultInvalidTask()); + + /* Sanity check the spans. */ + AMS_ASSERT(read_handles.size() < static_cast<size_t>(SocketCountMax)); + AMS_ASSERT(write_handles.size() < static_cast<size_t>(SocketCountMax)); + AMS_ASSERT(exception_handles.size() < static_cast<size_t>(SocketCountMax)); + + /* Set our arguments. */ + m_read_handle_count = static_cast<s32>(read_handles.size()); + m_write_handle_count = static_cast<s32>(write_handles.size()); + m_exception_handle_count = static_cast<s32>(exception_handles.size()); + m_tv_sec = tv_sec; + m_tv_usec = tv_usec; + + /* Copy the handles. */ + std::memcpy(m_handles, read_handles.data(), read_handles.size_bytes()); + std::memcpy(m_handles + m_read_handle_count, write_handles.data(), write_handles.size_bytes()); + std::memcpy(m_handles + m_read_handle_count + m_write_handle_count, exception_handles.data(), exception_handles.size_bytes()); + + R_SUCCEED(); + } + + void SelectTask::Complete(htcs::SocketError err, s32 read_handle_count, s32 write_handle_count, s32 exception_handle_count, const void *body, s64 body_size) { + /* Sanity check the handle counts. */ + const auto handle_count = read_handle_count + write_handle_count + exception_handle_count; + AMS_ASSERT(0 <= read_handle_count && read_handle_count < SocketCountMax); + AMS_ASSERT(0 <= write_handle_count && write_handle_count < SocketCountMax); + AMS_ASSERT(0 <= exception_handle_count && exception_handle_count < SocketCountMax); + AMS_ASSERT(handle_count * static_cast<s64>(sizeof(s32)) == body_size); + AMS_UNUSED(handle_count, body_size); + + /* Set our results. */ + m_err = err; + m_out_read_handle_count = read_handle_count; + m_out_write_handle_count = write_handle_count; + m_out_exception_handle_count = exception_handle_count; + + /* Copy the handles. */ + std::memcpy(m_out_handles, static_cast<const s32 *>(body), sizeof(s32) * read_handle_count); + std::memcpy(m_out_handles + read_handle_count, static_cast<const s32 *>(body) + read_handle_count, sizeof(s32) * write_handle_count); + std::memcpy(m_out_handles + read_handle_count + write_handle_count, static_cast<const s32 *>(body) + read_handle_count + write_handle_count, sizeof(s32) * exception_handle_count); + + /* Complete. */ + HtcsSignalingTask::Complete(); + } + + Result SelectTask::GetResult(htcs::SocketError *out_err, bool *out_empty, Span<int> read_handles, Span<int> write_handles, Span<int> exception_handles) const { + /* Set the output error. */ + *out_err = m_err; + + /* Set the output empty value. */ + const bool empty = m_err == HTCS_ENONE && m_out_read_handle_count == 0 && m_out_write_handle_count == 0 && m_out_exception_handle_count == 0; + *out_empty = empty; + + /* Clear the output spans. */ + std::fill(read_handles.begin(), read_handles.end(), 0); + std::fill(write_handles.begin(), write_handles.end(), 0); + std::fill(exception_handles.begin(), exception_handles.end(), 0); + + /* Copy the handles. */ + if (m_err == HTCS_ENONE && !empty) { + const s32 * const out_read_start = m_out_handles; + const s32 * const out_read_end = out_read_start + m_out_read_handle_count; + const s32 * const out_write_start = out_read_end; + const s32 * const out_write_end = out_write_start + m_out_write_handle_count; + const s32 * const out_exception_start = out_write_end; + const s32 * const out_exception_end = out_exception_start + m_out_exception_handle_count; + std::copy(out_read_start, out_read_end, read_handles.begin()); + std::copy(out_write_start, out_write_end, write_handles.begin()); + std::copy(out_exception_start, out_exception_end, exception_handles.begin()); + } else { + const s32 * const read_start = m_handles; + const s32 * const read_end = read_start + m_read_handle_count; + const s32 * const write_start = read_end; + const s32 * const write_end = write_start + m_write_handle_count; + const s32 * const exception_start = write_end; + const s32 * const exception_end = exception_start + m_exception_handle_count; + std::copy(read_start, read_end, read_handles.begin()); + std::copy(write_start, write_end, write_handles.begin()); + std::copy(exception_start, exception_end, exception_handles.begin()); + } + + R_SUCCEED(); + } + + Result SelectTask::ProcessResponse(const char *data, size_t size) { + AMS_UNUSED(size); + + /* Convert the input to a packet. */ + auto *packet = reinterpret_cast<const HtcsRpcPacket *>(data); + + /* Complete the task. */ + this->Complete(static_cast<htcs::SocketError>(packet->params[0]), packet->params[1], packet->params[2], packet->params[3], packet->data, size - sizeof(*packet)); + + R_SUCCEED(); + } + + Result SelectTask::CreateRequest(size_t *out, char *data, size_t size, u32 task_id) { + AMS_UNUSED(size); + + /* Determine the body size. */ + const auto handle_count = m_read_handle_count + m_write_handle_count + m_exception_handle_count; + const s64 body_size = static_cast<s64>(handle_count * sizeof(s32)); + AMS_ASSERT(sizeof(HtcsRpcPacket) + body_size <= size); + + /* Create the packet. */ + auto *packet = reinterpret_cast<HtcsRpcPacket *>(data); + *packet = { + .protocol = HtcsProtocol, + .version = this->GetVersion(), + .category = HtcsPacketCategory::Request, + .type = HtcsPacketType::Select, + .body_size = body_size, + .task_id = task_id, + .params = { + m_read_handle_count, + m_write_handle_count, + m_exception_handle_count, + m_tv_sec, + m_tv_usec, + }, + }; + + /* Set the packet body. */ + std::memcpy(packet->data, m_handles, body_size); + + /* Set the output size. */ + *out = sizeof(*packet) + body_size; + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_send_small_task.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_send_small_task.cpp new file mode 100644 index 00000000..1f1f2285 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_send_small_task.cpp @@ -0,0 +1,137 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_rpc_tasks.hpp" + +namespace ams::htcs::impl::rpc { + + void SendSmallTask::SetBuffer(const void *buffer, s64 buffer_size) { + /* Sanity check the buffer size. */ + AMS_ASSERT(0 <= buffer_size && buffer_size <= static_cast<s64>(sizeof(m_buffer))); + + /* Set our buffer. */ + if (buffer_size > 0) { + std::memcpy(m_buffer, buffer, buffer_size); + } + m_buffer_size = buffer_size; + } + + void SendSmallTask::NotifyDataChannelReady() { + /* Notify. */ + this->Notify(); + + /* Signal our ready event. */ + m_ready_event.Signal(); + } + + void SendSmallTask::WaitNotification() { + /* Wait on our ready event. */ + m_ready_event.Wait(); + } + + Result SendSmallTask::SetArguments(s32 handle, s64 size, htcs::MessageFlag flags) { + /* Check that we're valid. */ + R_UNLESS(this->IsValid(), htcs::ResultInvalidTask()); + + /* Set our arguments. */ + m_handle = handle; + m_size = size; + m_flags = flags; + + R_SUCCEED(); + } + + void SendSmallTask::Complete(htcs::SocketError err, s64 size) { + /* Set our results. */ + m_err = err; + m_result_size = size; + + /* Signal our ready event. */ + m_ready_event.Signal(); + + /* Complete. */ + HtcsSignalingTask::Complete(); + } + + Result SendSmallTask::GetResult(htcs::SocketError *out_err, s64 *out_size) const { + /* Sanity check our state. */ + AMS_ASSERT(this->GetTaskState() == htc::server::rpc::RpcTaskState::Completed); + + /* Set the output. */ + *out_err = m_err; + *out_size = m_result_size; + + R_SUCCEED(); + } + + void SendSmallTask::Cancel(htc::server::rpc::RpcTaskCancelReason reason) { + /* Cancel the task. */ + HtcsSignalingTask::Cancel(reason); + + /* Signal our ready event. */ + m_ready_event.Signal(); + } + + Result SendSmallTask::ProcessResponse(const char *data, size_t size) { + AMS_UNUSED(size); + + /* Convert the input to a packet. */ + auto *packet = reinterpret_cast<const HtcsRpcPacket *>(data); + + /* Complete the task. */ + this->Complete(static_cast<htcs::SocketError>(packet->params[0]), this->GetSize()); + + R_SUCCEED(); + } + + Result SendSmallTask::CreateRequest(size_t *out, char *data, size_t size, u32 task_id) { + AMS_UNUSED(size); + + /* Sanity check our size. */ + AMS_ASSERT(sizeof(HtcsRpcPacket) + this->GetBufferSize() <= size); + + /* Create the packet. */ + auto *packet = reinterpret_cast<HtcsRpcPacket *>(data); + *packet = { + .protocol = HtcsProtocol, + .version = this->GetVersion(), + .category = HtcsPacketCategory::Request, + .type = HtcsPacketType::Send, + .body_size = this->GetSize(), + .task_id = task_id, + .params = { + m_handle, + m_size, + static_cast<s64>(m_flags), + }, + }; + + /* Set the body. */ + if (this->GetSize() > 0) { + std::memcpy(packet->data, this->GetBuffer(), this->GetSize()); + } + + /* Set the output size. */ + *out = sizeof(*packet) + this->GetSize(); + + R_SUCCEED(); + } + + bool SendSmallTask::IsSendBufferRequired() { + return this->GetSize() > 0; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_send_task.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_send_task.cpp new file mode 100644 index 00000000..94debd04 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_send_task.cpp @@ -0,0 +1,151 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_rpc_tasks.hpp" + +namespace ams::htcs::impl::rpc { + + void SendTask::SetBuffer(const void *buffer, s64 buffer_size) { + /* Set our buffer. */ + m_buffer = buffer; + m_buffer_size = buffer_size; + } + + void SendTask::NotifyDataChannelReady() { + /* Notify. */ + this->Notify(); + + /* Signal our ready event. */ + m_ready_event.Signal(); + } + + void SendTask::WaitNotification() { + /* Wait on our ready event. */ + m_ready_event.Wait(); + } + + Result SendTask::SetArguments(s32 handle, s64 size, htcs::MessageFlag flags) { + /* Check that we're valid. */ + R_UNLESS(this->IsValid(), htcs::ResultInvalidTask()); + + /* Set our arguments. */ + m_handle = handle; + m_size = size; + m_flags = flags; + + R_SUCCEED(); + } + + void SendTask::Complete(htcs::SocketError err, s64 size) { + /* Set our results. */ + m_err = err; + m_result_size = size; + + /* Signal our ready event. */ + m_ready_event.Signal(); + + /* Complete. */ + HtcsSignalingTask::Complete(); + } + + Result SendTask::GetResult(htcs::SocketError *out_err, s64 *out_size) const { + /* Sanity check our state. */ + AMS_ASSERT(this->GetTaskState() == htc::server::rpc::RpcTaskState::Completed); + + /* Set the output. */ + *out_err = m_err; + *out_size = m_result_size; + + R_SUCCEED(); + } + + void SendTask::Cancel(htc::server::rpc::RpcTaskCancelReason reason) { + /* Cancel the task. */ + HtcsSignalingTask::Cancel(reason); + + /* Signal our ready event. */ + m_ready_event.Signal(); + } + + Result SendTask::ProcessResponse(const char *data, size_t size) { + AMS_UNUSED(size); + + /* Convert the input to a packet. */ + auto *packet = reinterpret_cast<const HtcsRpcPacket *>(data); + + /* Complete the task. */ + this->Complete(static_cast<htcs::SocketError>(packet->params[0]), packet->params[1]); + + R_SUCCEED(); + } + + Result SendTask::CreateRequest(size_t *out, char *data, size_t size, u32 task_id) { + AMS_UNUSED(size); + + /* Create the packet. */ + auto *packet = reinterpret_cast<HtcsRpcPacket *>(data); + *packet = { + .protocol = HtcsProtocol, + .version = this->GetVersion(), + .category = HtcsPacketCategory::Request, + .type = HtcsPacketType::SendLarge, + .body_size = 0, + .task_id = task_id, + .params = { + m_handle, + m_size, + static_cast<s64>(m_flags), + GetSendDataChannelId(task_id), + }, + }; + + /* Set the output size. */ + *out = sizeof(*packet); + + R_SUCCEED(); + } + + Result SendTask::ProcessNotification(const char *data, size_t size) { + AMS_UNUSED(data, size); + + this->NotifyDataChannelReady(); + R_SUCCEED(); + } + + Result SendTask::CreateNotification(size_t *out, char *data, size_t size, u32 task_id) { + AMS_UNUSED(size); + + /* Create the packet. */ + auto *packet = reinterpret_cast<HtcsRpcPacket *>(data); + *packet = { + .protocol = HtcsProtocol, + .version = this->GetVersion(), + .category = HtcsPacketCategory::Notification, + .type = HtcsPacketType::SendLarge, + .body_size = 0, + .task_id = task_id, + .params = { + /* ... */ + }, + }; + + /* Set the output size. */ + *out = sizeof(*packet); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_shutdown_task.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_shutdown_task.cpp new file mode 100644 index 00000000..f212cc89 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_shutdown_task.cpp @@ -0,0 +1,83 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_rpc_tasks.hpp" + +namespace ams::htcs::impl::rpc { + + Result ShutdownTask::SetArguments(s32 handle, ShutdownType how) { + /* Set our arguments. */ + m_handle = handle; + m_how = how; + + R_SUCCEED(); + } + + void ShutdownTask::Complete(htcs::SocketError err) { + /* Set our results. */ + m_err = err; + + /* Complete. */ + HtcsTask::Complete(); + } + + Result ShutdownTask::GetResult(htcs::SocketError *out_err) const { + /* Sanity check our state. */ + AMS_ASSERT(this->GetTaskState() == htc::server::rpc::RpcTaskState::Completed); + + /* Set the output. */ + *out_err = m_err; + + R_SUCCEED(); + } + + Result ShutdownTask::ProcessResponse(const char *data, size_t size) { + AMS_UNUSED(size); + + /* Convert the input to a packet. */ + auto *packet = reinterpret_cast<const HtcsRpcPacket *>(data); + + /* Complete the task. */ + this->Complete(static_cast<htcs::SocketError>(packet->params[0])); + + R_SUCCEED(); + } + + Result ShutdownTask::CreateRequest(size_t *out, char *data, size_t size, u32 task_id) { + AMS_UNUSED(size); + + /* Create the packet. */ + auto *packet = reinterpret_cast<HtcsRpcPacket *>(data); + *packet = { + .protocol = HtcsProtocol, + .version = this->GetVersion(), + .category = HtcsPacketCategory::Request, + .type = HtcsPacketType::Shutdown, + .body_size = 0, + .task_id = task_id, + .params = { + m_handle, + static_cast<s64>(m_how), + }, + }; + + /* Set the output size. */ + *out = sizeof(*packet); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_signaling_task.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_signaling_task.cpp new file mode 100644 index 00000000..bcd7223c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_signaling_task.cpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_rpc_tasks.hpp" + +namespace ams::htcs::impl::rpc { + + namespace { + + constexpr int MaxEventCount = 0x22; + + constinit os::SdkMutex g_event_count_mutex; + constinit int g_event_count = 0; + + } + + HtcsSignalingTask::HtcsSignalingTask(HtcsTaskType type) : HtcsTask(type), m_is_valid(false) { + /* Acquire the exclusive right to create an event. */ + std::scoped_lock lk(g_event_count_mutex); + + /* Create an event. */ + if (AMS_LIKELY(g_event_count < MaxEventCount)) { + /* Make the event. */ + R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(m_system_event), os::EventClearMode_ManualClear, true)); + + /* Increment the event count. */ + ++g_event_count; + + /* Mark ourselves as valid. */ + m_is_valid = true; + } + } + + HtcsSignalingTask::~HtcsSignalingTask() { + /* If we have an event, we need to destroy it. */ + if (AMS_LIKELY(m_is_valid)) { + /* Acquire exclusive access to the event count. */ + std::scoped_lock lk(g_event_count_mutex); + + /* Destroy our event. */ + os::DestroySystemEvent(std::addressof(m_system_event)); + + /* Decrement the event count. */ + if ((--g_event_count) < 0) { + g_event_count = 0; + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_socket_task.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_socket_task.cpp new file mode 100644 index 00000000..1905ac2a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_socket_task.cpp @@ -0,0 +1,93 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_rpc_tasks.hpp" + +namespace ams::htcs::impl::rpc { + + namespace { + + constinit s16 g_protocol_version = HtcsMaxVersion; + + } + + HtcsTask::HtcsTask(HtcsTaskType type) : m_task_type(type), m_version(g_protocol_version) { + /* ... */ + } + + Result SocketTask::SetArguments() { + R_SUCCEED(); + } + + void SocketTask::Complete(htcs::SocketError err, s32 desc) { + /* Set our results. */ + m_err = err; + m_desc = desc; + + /* Complete. */ + HtcsTask::Complete(); + } + + Result SocketTask::GetResult(htcs::SocketError *out_err, s32 *out_desc) const { + /* Sanity check our state. */ + AMS_ASSERT(this->GetTaskState() == htc::server::rpc::RpcTaskState::Completed); + + /* Set the output. */ + *out_err = m_err; + *out_desc = m_desc; + + R_SUCCEED(); + } + + Result SocketTask::ProcessResponse(const char *data, size_t size) { + AMS_UNUSED(size); + + /* Convert the input to a packet. */ + auto *packet = reinterpret_cast<const HtcsRpcPacket *>(data); + + /* Update the global protocol version. */ + g_protocol_version = std::min(g_protocol_version, packet->version); + + /* Complete the task. */ + this->Complete(static_cast<htcs::SocketError>(packet->params[0]), packet->params[1]); + + R_SUCCEED(); + } + + Result SocketTask::CreateRequest(size_t *out, char *data, size_t size, u32 task_id) { + AMS_UNUSED(size); + + /* Create the packet. */ + auto *packet = reinterpret_cast<HtcsRpcPacket *>(data); + *packet = { + .protocol = HtcsProtocol, + .version = 3, + .category = HtcsPacketCategory::Request, + .type = HtcsPacketType::Socket, + .body_size = 0, + .task_id = task_id, + .params = { + /* ... */ + }, + }; + + /* Set the output size. */ + *out = sizeof(*packet); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_tasks.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_tasks.hpp new file mode 100644 index 00000000..5e4ec879 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/impl/rpc/htcs_rpc_tasks.hpp @@ -0,0 +1,473 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../../../htc/server/rpc/htc_rpc_tasks.hpp" + +namespace ams::htcs::impl::rpc { + + enum class HtcsTaskType { + Receive = 0, + Send = 1, + Shutdown = 2, + Close = 3, + Connect = 4, + Listen = 5, + Accept = 6, + Socket = 7, + Bind = 8, + Fcntl = 9, + ReceiveSmall = 10, + SendSmall = 11, + Select = 12, + }; + + constexpr inline s16 HtcsProtocol = 5; + constexpr inline const s16 HtcsMaxVersion = 4; + + enum class HtcsPacketCategory : s16 { + Request = 0, + Response = 1, + Notification = 2, + }; + + enum class HtcsPacketType : s16 { + Receive = 32, + Send = 33, + Shutdown = 34, + Close = 35, + Connect = 36, + Listen = 37, + Accept = 38, + Socket = 39, + Bind = 40, + Fcntl = 41, + ReceiveLarge = 42, + SendLarge = 43, + Select = 44, + }; + + struct HtcsRpcPacket { + s16 protocol; + s16 version; + HtcsPacketCategory category; + HtcsPacketType type; + s64 body_size; + u32 task_id{}; + s64 params[5]; + char data[]; + }; + static_assert(sizeof(HtcsRpcPacket) == 0x40); + + constexpr inline u16 ReceiveDataChannelIdBegin = htc::server::rpc::MaxRpcCount; + constexpr inline u16 ReceiveDataChannelIdEnd = ReceiveDataChannelIdBegin + htc::server::rpc::MaxRpcCount; + static_assert(ReceiveDataChannelIdEnd - ReceiveDataChannelIdBegin == htc::server::rpc::MaxRpcCount); + + constexpr inline u16 SendDataChannelIdBegin = ReceiveDataChannelIdEnd; + constexpr inline u16 SendDataChannelIdEnd = SendDataChannelIdBegin + htc::server::rpc::MaxRpcCount; + static_assert(SendDataChannelIdEnd - SendDataChannelIdBegin == htc::server::rpc::MaxRpcCount); + + constexpr inline u16 GetReceiveDataChannelId(u32 task_id) { + const u16 channel_id = task_id + ReceiveDataChannelIdBegin; + AMS_ASSERT(ReceiveDataChannelIdBegin <= channel_id && channel_id < ReceiveDataChannelIdEnd); + + return channel_id; + } + + constexpr inline u16 GetSendDataChannelId(u32 task_id) { + const u16 channel_id = task_id + SendDataChannelIdBegin; + AMS_ASSERT(SendDataChannelIdBegin <= channel_id && channel_id < SendDataChannelIdEnd); + + return channel_id; + } + + class HtcsTask : public htc::server::rpc::Task { + private: + HtcsTaskType m_task_type; + s16 m_version; + public: + HtcsTask(HtcsTaskType type); /* Defined in socket_task.cpp, for namespacing reasons. */ + + HtcsTaskType GetTaskType() const { return m_task_type; } + s16 GetVersion() const { return m_version; } + }; + + template<typename T> + concept IsHtcsTask = std::derived_from<T, HtcsTask>; + + class HtcsSignalingTask : public HtcsTask { + private: + os::SystemEventType m_system_event; + bool m_is_valid; + public: + HtcsSignalingTask(HtcsTaskType type); + virtual ~HtcsSignalingTask(); + + bool IsValid() const { return m_is_valid; } + + void Complete() { + os::SignalSystemEvent(std::addressof(m_system_event)); + HtcsTask::Complete(); + } + public: + virtual void Cancel(htc::server::rpc::RpcTaskCancelReason reason) override { + HtcsTask::Cancel(reason); + os::SignalSystemEvent(std::addressof(m_system_event)); + } + + virtual os::SystemEventType *GetSystemEvent() override { return std::addressof(m_system_event); } + }; + + class ReceiveTask : public HtcsSignalingTask { + public: + static constexpr inline HtcsTaskType TaskType = HtcsTaskType::Receive; + private: + s32 m_handle; + s64 m_size; + htcs::MessageFlag m_flags; + void *m_buffer; + s64 m_buffer_size; + htcs::SocketError m_err; + s64 m_result_size; + public: + ReceiveTask() : HtcsSignalingTask(TaskType) { /* ... */ } + + s32 GetHandle() const { return m_handle; } + s64 GetSize() const { return m_size; } + htcs::MessageFlag GetFlags() const { return m_flags; } + void *GetBuffer() const { return m_buffer; } + s64 GetBufferSize() const { return m_buffer_size; } + + s64 GetResultSize() const { + AMS_ASSERT(this->GetTaskState() == htc::server::rpc::RpcTaskState::Completed); + return m_result_size; + } + public: + Result SetArguments(s32 handle, s64 size, htcs::MessageFlag flags); + void Complete(htcs::SocketError err, s64 size); + Result GetResult(htcs::SocketError *out_err, s64 *out_size) const; + public: + virtual Result ProcessResponse(const char *data, size_t size) override; + virtual Result CreateRequest(size_t *out, char *data, size_t size, u32 task_id) override; + virtual Result CreateNotification(size_t *out, char *data, size_t size, u32 task_id) override; + }; + + class SendTask : public HtcsSignalingTask { + public: + static constexpr inline HtcsTaskType TaskType = HtcsTaskType::Send; + private: + os::Event m_ready_event; + s32 m_handle; + s64 m_size; + htcs::MessageFlag m_flags; + const void *m_buffer; + s64 m_buffer_size; + htcs::SocketError m_err; + s64 m_result_size; + public: + SendTask() : HtcsSignalingTask(TaskType), m_ready_event(os::EventClearMode_ManualClear) { /* ... */ } + + s32 GetHandle() const { return m_handle; } + s64 GetSize() const { return m_size; } + htcs::MessageFlag GetFlags() const { return m_flags; } + const void *GetBuffer() const { return m_buffer; } + s64 GetBufferSize() const { return m_buffer_size; } + + void SetBuffer(const void *buffer, s64 buffer_size); + void NotifyDataChannelReady(); + void WaitNotification(); + public: + Result SetArguments(s32 handle, s64 size, htcs::MessageFlag flags); + void Complete(htcs::SocketError err, s64 size); + Result GetResult(htcs::SocketError *out_err, s64 *out_size) const; + public: + virtual void Cancel(htc::server::rpc::RpcTaskCancelReason reason) override; + virtual Result ProcessResponse(const char *data, size_t size) override; + virtual Result CreateRequest(size_t *out, char *data, size_t size, u32 task_id) override; + virtual Result ProcessNotification(const char *data, size_t size) override; + virtual Result CreateNotification(size_t *out, char *data, size_t size, u32 task_id) override; + }; + + class ShutdownTask : public HtcsTask { + public: + static constexpr inline HtcsTaskType TaskType = HtcsTaskType::Shutdown; + private: + s32 m_handle; + ShutdownType m_how; + htcs::SocketError m_err; + public: + ShutdownTask() : HtcsTask(TaskType) { /* ... */ } + + s32 GetHandle() const { return m_handle; } + ShutdownType GetHow() const { return m_how; } + public: + Result SetArguments(s32 handle, ShutdownType how); + void Complete(htcs::SocketError err); + Result GetResult(htcs::SocketError *out_err) const; + public: + virtual Result ProcessResponse(const char *data, size_t size) override; + virtual Result CreateRequest(size_t *out, char *data, size_t size, u32 task_id) override; + }; + + class CloseTask : public HtcsTask { + public: + static constexpr inline HtcsTaskType TaskType = HtcsTaskType::Close; + private: + s32 m_handle; + htcs::SocketError m_err; + public: + CloseTask() : HtcsTask(TaskType) { /* ... */ } + + s32 GetHandle() const { return m_handle; } + public: + Result SetArguments(s32 handle); + void Complete(htcs::SocketError err); + Result GetResult(htcs::SocketError *out_err) const; + public: + virtual Result ProcessResponse(const char *data, size_t size) override; + virtual Result CreateRequest(size_t *out, char *data, size_t size, u32 task_id) override; + }; + + class ConnectTask : public HtcsTask { + public: + static constexpr inline HtcsTaskType TaskType = HtcsTaskType::Connect; + private: + s32 m_handle; + HtcsPeerName m_peer_name; + HtcsPortName m_port_name; + htcs::SocketError m_err; + public: + ConnectTask() : HtcsTask(TaskType) { /* ... */ } + + s32 GetHandle() const { return m_handle; } + const HtcsPeerName &GetPeerName() const { return m_peer_name; } + const HtcsPortName &GetPortName() const { return m_port_name; } + public: + Result SetArguments(s32 handle, const HtcsPeerName &peer_name, const HtcsPortName &port_name); + void Complete(htcs::SocketError err); + Result GetResult(htcs::SocketError *out_err) const; + public: + virtual Result ProcessResponse(const char *data, size_t size) override; + virtual Result CreateRequest(size_t *out, char *data, size_t size, u32 task_id) override; + }; + + class ListenTask : public HtcsTask { + public: + static constexpr inline HtcsTaskType TaskType = HtcsTaskType::Listen; + private: + s32 m_handle; + s32 m_backlog; + htcs::SocketError m_err; + public: + ListenTask() : HtcsTask(TaskType) { /* ... */ } + + s32 GetHandle() const { return m_handle; } + s32 GetBacklog() const { return m_backlog; } + public: + Result SetArguments(s32 handle, s32 backlog); + void Complete(htcs::SocketError err); + Result GetResult(htcs::SocketError *out_err) const; + public: + virtual Result ProcessResponse(const char *data, size_t size) override; + virtual Result CreateRequest(size_t *out, char *data, size_t size, u32 task_id) override; + }; + + class AcceptTask : public HtcsSignalingTask { + public: + static constexpr inline HtcsTaskType TaskType = HtcsTaskType::Accept; + private: + s32 m_server_handle; + htcs::SocketError m_err; + s32 m_desc; + public: + AcceptTask() : HtcsSignalingTask(TaskType) { /* ... */ } + + s32 GetServerHandle() const { return m_server_handle; } + public: + Result SetArguments(s32 server_handle); + void Complete(htcs::SocketError err, s32 desc); + Result GetResult(htcs::SocketError *out_err, s32 *out_desc, s32 server_handle) const; + public: + virtual Result ProcessResponse(const char *data, size_t size) override; + virtual Result CreateRequest(size_t *out, char *data, size_t size, u32 task_id) override; + }; + + class SocketTask : public HtcsTask { + public: + static constexpr inline HtcsTaskType TaskType = HtcsTaskType::Socket; + private: + htcs::SocketError m_err; + s32 m_desc; + public: + SocketTask() : HtcsTask(TaskType) { /* ... */ } + public: + Result SetArguments(); + void Complete(htcs::SocketError err, s32 desc); + Result GetResult(htcs::SocketError *out_err, s32 *out_desc) const; + public: + virtual Result ProcessResponse(const char *data, size_t size) override; + virtual Result CreateRequest(size_t *out, char *data, size_t size, u32 task_id) override; + }; + + class BindTask : public HtcsTask { + public: + static constexpr inline HtcsTaskType TaskType = HtcsTaskType::Bind; + private: + s32 m_handle; + HtcsPeerName m_peer_name; + HtcsPortName m_port_name; + htcs::SocketError m_err; + public: + BindTask() : HtcsTask(TaskType) { /* ... */ } + + s32 GetHandle() const { return m_handle; } + const HtcsPeerName &GetPeerName() const { return m_peer_name; } + const HtcsPortName &GetPortName() const { return m_port_name; } + public: + Result SetArguments(s32 handle, const HtcsPeerName &peer_name, const HtcsPortName &port_name); + void Complete(htcs::SocketError err); + Result GetResult(htcs::SocketError *out_err) const; + public: + virtual Result ProcessResponse(const char *data, size_t size) override; + virtual Result CreateRequest(size_t *out, char *data, size_t size, u32 task_id) override; + }; + + class FcntlTask : public HtcsTask { + public: + static constexpr inline HtcsTaskType TaskType = HtcsTaskType::Fcntl; + private: + s32 m_handle; + s32 m_command; + s32 m_value; + htcs::SocketError m_err; + s32 m_res; + public: + FcntlTask() : HtcsTask(TaskType) { /* ... */ } + + s32 GetHandle() const { return m_handle; } + s32 GetCommand() const { return m_command; } + s32 GetValue() const { return m_value; } + public: + Result SetArguments(s32 handle, s32 command, s32 value); + void Complete(htcs::SocketError err, s32 res); + Result GetResult(htcs::SocketError *out_err, s32 *out_res) const; + public: + virtual Result ProcessResponse(const char *data, size_t size) override; + virtual Result CreateRequest(size_t *out, char *data, size_t size, u32 task_id) override; + }; + + class ReceiveSmallTask : public HtcsSignalingTask { + public: + static constexpr inline HtcsTaskType TaskType = HtcsTaskType::ReceiveSmall; + private: + s32 m_handle; + s64 m_size; + htcs::MessageFlag m_flags; + char m_buffer[0xE000]; + htcs::SocketError m_err; + s64 m_result_size; + public: + ReceiveSmallTask() : HtcsSignalingTask(TaskType) { /* ... */ } + + s32 GetHandle() const { return m_handle; } + s64 GetSize() const { return m_size; } + htcs::MessageFlag GetFlags() const { return m_flags; } + void *GetBuffer() { return m_buffer; } + s64 GetBufferSize() const { return static_cast<s64>(sizeof(m_buffer)); } + + s64 GetResultSize() const { + AMS_ASSERT(this->GetTaskState() == htc::server::rpc::RpcTaskState::Completed); + return m_result_size; + } + public: + Result SetArguments(s32 handle, s64 size, htcs::MessageFlag flags); + void Complete(htcs::SocketError err, s64 size); + Result GetResult(htcs::SocketError *out_err, s64 *out_size) const; + public: + virtual Result ProcessResponse(const char *data, size_t size) override; + virtual Result CreateRequest(size_t *out, char *data, size_t size, u32 task_id) override; + virtual bool IsReceiveBufferRequired() override; + }; + + class SendSmallTask : public HtcsSignalingTask { + public: + static constexpr inline HtcsTaskType TaskType = HtcsTaskType::SendSmall; + private: + os::Event m_ready_event; + s32 m_handle; + s64 m_size; + htcs::MessageFlag m_flags; + char m_buffer[0xE000]; + s64 m_buffer_size; + htcs::SocketError m_err; + s64 m_result_size; + public: + SendSmallTask() : HtcsSignalingTask(TaskType), m_ready_event(os::EventClearMode_ManualClear) { /* ... */ } + + s32 GetHandle() const { return m_handle; } + s64 GetSize() const { return m_size; } + htcs::MessageFlag GetFlags() const { return m_flags; } + void *GetBuffer() { return m_buffer; } + s64 GetBufferSize() const { return m_buffer_size; } + + void SetBuffer(const void *buffer, s64 buffer_size); + void NotifyDataChannelReady(); + void WaitNotification(); + public: + Result SetArguments(s32 handle, s64 size, htcs::MessageFlag flags); + void Complete(htcs::SocketError err, s64 size); + Result GetResult(htcs::SocketError *out_err, s64 *out_size) const; + public: + virtual void Cancel(htc::server::rpc::RpcTaskCancelReason reason) override; + virtual Result ProcessResponse(const char *data, size_t size) override; + virtual Result CreateRequest(size_t *out, char *data, size_t size, u32 task_id) override; + virtual bool IsSendBufferRequired() override; + }; + + class SelectTask : public HtcsSignalingTask { + public: + static constexpr inline HtcsTaskType TaskType = HtcsTaskType::Select; + private: + s32 m_handles[SocketCountMax * 3]; + s32 m_read_handle_count; + s32 m_write_handle_count; + s32 m_exception_handle_count; + s64 m_tv_sec; + s64 m_tv_usec; + htcs::SocketError m_err; + s32 m_out_handles[SocketCountMax * 3]; + s32 m_out_read_handle_count; + s32 m_out_write_handle_count; + s32 m_out_exception_handle_count; + public: + SelectTask() : HtcsSignalingTask(TaskType) { /* ... */ } + + const s32 *GetHandles() const { return m_handles; } + s32 GetReadHandleCount() const { return m_read_handle_count; } + s32 GetWriteHandleCount() const { return m_write_handle_count; } + s32 GetExceptionHandleCount() const { return m_exception_handle_count; } + s64 GetTimeoutSeconds() const { return m_tv_sec; } + s64 GetTimeoutMicroSeconds() const { return m_tv_usec; } + public: + Result SetArguments(Span<const int> read_handles, Span<const int> write_handles, Span<const int> exception_handles, s64 tv_sec, s64 tv_usec); + void Complete(htcs::SocketError err, s32 read_handle_count, s32 write_handle_count, s32 exception_handle_count, const void *body, s64 body_size); + Result GetResult(htcs::SocketError *out_err, bool *out_empty, Span<int> read_handles, Span<int> write_handles, Span<int> exception_handles) const; + public: + virtual Result ProcessResponse(const char *data, size_t size) override; + virtual Result CreateRequest(size_t *out, char *data, size_t size, u32 task_id) override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_hipc_server.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_hipc_server.cpp new file mode 100644 index 00000000..c7b6511e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_hipc_server.cpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_manager_service_object.hpp" + +namespace ams::htcs::server { + + namespace { + + static constexpr inline size_t NumServers = 1; + static constexpr inline size_t MaxSessions = 63; + static constexpr inline sm::ServiceName ServiceName = sm::ServiceName::Encode("htcs"); + + struct ServerOptions { + static constexpr size_t PointerBufferSize = 0x80; + static constexpr size_t MaxDomains = 0x10; + static constexpr size_t MaxDomainObjects = 100; + static constexpr bool CanDeferInvokeRequest = false; + static constexpr bool CanManageMitmServers = false; + }; + + using ServerManager = sf::hipc::ServerManager<NumServers, ServerOptions, MaxSessions>; + + /* Service object. */ + ServerManager g_server_manager; + + /* Service object. */ + constinit sf::UnmanagedServiceObject<tma::IHtcsManager, ManagerServiceObject> g_htcs_service_object; + + } + + void Initialize() { + /* Add a reference to the htcs manager. */ + htcs::impl::HtcsManagerHolder::AddReference(); + } + + void RegisterHipcServer() { + /* Register the service. */ + R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_htcs_service_object.GetShared(), ServiceName, MaxSessions)); + } + + void LoopHipcServer() { + /* Loop, servicing services. */ + g_server_manager.LoopProcess(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_manager_service_object.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_manager_service_object.cpp new file mode 100644 index 00000000..6dfb7459 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_manager_service_object.cpp @@ -0,0 +1,100 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_manager_service_object.hpp" +#include "htcs_socket_service_object.hpp" +#include "htcs_service_object_allocator.hpp" +#include "../impl/htcs_manager.hpp" +#include "../impl/htcs_impl.hpp" + +namespace ams::htcs::server { + + namespace { + + class StaticAllocatorInitializer { + public: + StaticAllocatorInitializer() { + ServiceObjectAllocator::Initialize(lmem::CreateOption_ThreadSafe); + } + } g_static_allocator_initializer; + + } + + Result ManagerServiceObject::GetPeerNameAny(sf::Out<htcs::HtcsPeerName> out) { + *out = impl::GetPeerNameAny(); + R_SUCCEED(); + } + + Result ManagerServiceObject::GetDefaultHostName(sf::Out<htcs::HtcsPeerName> out) { + *out = impl::GetDefaultHostName(); + R_SUCCEED(); + } + + Result ManagerServiceObject::CreateSocketOld(sf::Out<s32> out_err, sf::Out<sf::SharedPointer<tma::ISocket>> out) { + R_RETURN(this->CreateSocket(out_err, out, false)); + } + + Result ManagerServiceObject::CreateSocket(sf::Out<s32> out_err, sf::Out<sf::SharedPointer<tma::ISocket>> out, bool enable_disconnection_emulation) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Create a new socket. */ + s32 desc; + manager->Socket(out_err.GetPointer(), std::addressof(desc), enable_disconnection_emulation); + + /* If an error occurred, we're done. */ + R_SUCCEED_IF(*out_err != 0); + + /* Create a new socket object. */ + *out = ServiceObjectFactory::CreateSharedEmplaced<tma::ISocket, SocketServiceObject>(this, desc); + + R_SUCCEED(); + } + + Result ManagerServiceObject::RegisterProcessId(const sf::ClientProcessId &client_pid) { + /* NOTE: Nintendo does nothing here. */ + AMS_UNUSED(client_pid); + R_SUCCEED(); + } + + Result ManagerServiceObject::MonitorManager(const sf::ClientProcessId &client_pid) { + /* NOTE: Nintendo does nothing here. */ + AMS_UNUSED(client_pid); + R_SUCCEED(); + } + + Result ManagerServiceObject::StartSelect(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, const sf::InMapAliasArray<s32> &read_handles, const sf::InMapAliasArray<s32> &write_handles, const sf::InMapAliasArray<s32> &exception_handles, s64 tv_sec, s64 tv_usec) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Start the select. */ + os::NativeHandle event_handle; + R_TRY(manager->StartSelect(out_task_id.GetPointer(), std::addressof(event_handle), read_handles.ToSpan(), write_handles.ToSpan(), exception_handles.ToSpan(), tv_sec, tv_usec)); + + /* Set the output event handle. */ + out_event.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result ManagerServiceObject::EndSelect(sf::Out<s32> out_err, sf::Out<s32> out_count, const sf::OutMapAliasArray<s32> &read_handles, const sf::OutMapAliasArray<s32> &write_handles, const sf::OutMapAliasArray<s32> &exception_handles, u32 task_id) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* End the select. */ + R_RETURN(manager->EndSelect(out_err.GetPointer(), out_count.GetPointer(), read_handles.ToSpan(), write_handles.ToSpan(), exception_handles.ToSpan(), task_id)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_manager_service_object.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_manager_service_object.hpp new file mode 100644 index 00000000..950771e5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_manager_service_object.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htcs::server { + + class ManagerServiceObject : public sf::ISharedObject { + public: + Result Socket(sf::Out<s32> out_err, sf::Out<s32> out_sock); + Result Close(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc); + Result Connect(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, const htcs::SockAddrHtcs &address); + Result Bind(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, const htcs::SockAddrHtcs &address); + Result Listen(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, s32 backlog_count); + Result Accept(sf::Out<s32> out_err, sf::Out<s32> out_res, sf::Out<htcs::SockAddrHtcs> out_address, s32 desc); + Result Recv(sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::OutBuffer &buffer, s32 desc, s32 flags); + Result Send(sf::Out<s32> out_err, sf::Out<s64> out_size, s32 desc, const sf::InBuffer &buffer, s32 flags); + Result Shutdown(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, s32 how); + Result Fcntl(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, s32 command, s32 value); + Result GetPeerNameAny(sf::Out<htcs::HtcsPeerName> out); + Result GetDefaultHostName(sf::Out<htcs::HtcsPeerName> out); + Result CreateSocketOld(sf::Out<s32> out_err, sf::Out<sf::SharedPointer<tma::ISocket>> out); + Result CreateSocket(sf::Out<s32> out_err, sf::Out<sf::SharedPointer<tma::ISocket>> out, bool enable_disconnection_emulation); + Result RegisterProcessId(const sf::ClientProcessId &client_pid); + Result MonitorManager(const sf::ClientProcessId &client_pid); + Result StartSelect(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, const sf::InMapAliasArray<s32> &read_handles, const sf::InMapAliasArray<s32> &write_handles, const sf::InMapAliasArray<s32> &exception_handles, s64 tv_sec, s64 tv_usec); + Result EndSelect(sf::Out<s32> out_err, sf::Out<s32> out_count, const sf::OutMapAliasArray<s32> &read_handles, const sf::OutMapAliasArray<s32> &write_handles, const sf::OutMapAliasArray<s32> &exception_handles, u32 task_id); + }; + static_assert(tma::IsIHtcsManager<ManagerServiceObject>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_manager_service_object_deprecated.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_manager_service_object_deprecated.cpp new file mode 100644 index 00000000..a32643ee --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_manager_service_object_deprecated.cpp @@ -0,0 +1,74 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_manager_service_object.hpp" +#include "htcs_socket_service_object.hpp" + +namespace ams::htcs::server { + + #define AMS_HTCS_MANAGER_DEPRECATED_API(...) ({ AMS_UNUSED(__VA_ARGS__); AMS_ABORT("Deprecated IHtcsManager API %s was called.\n", AMS_CURRENT_FUNCTION_NAME); }) + + Result ManagerServiceObject::Socket(sf::Out<s32> out_err, sf::Out<s32> out_sock) { + /* NOTE: This is a deprecated API, and Nintendo aborts when it is called. */ + AMS_HTCS_MANAGER_DEPRECATED_API(out_err, out_sock); + } + + Result ManagerServiceObject::Close(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc) { + /* NOTE: This is a deprecated API, and Nintendo aborts when it is called. */ + AMS_HTCS_MANAGER_DEPRECATED_API(out_err, out_res, desc); + } + + Result ManagerServiceObject::Connect(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, const htcs::SockAddrHtcs &address) { + /* NOTE: This is a deprecated API, and Nintendo aborts when it is called. */ + AMS_HTCS_MANAGER_DEPRECATED_API(out_err, out_res, desc, address); + } + + Result ManagerServiceObject::Bind(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, const htcs::SockAddrHtcs &address) { + /* NOTE: This is a deprecated API, and Nintendo aborts when it is called. */ + AMS_HTCS_MANAGER_DEPRECATED_API(out_err, out_res, desc, address); + } + + Result ManagerServiceObject::Listen(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, s32 backlog_count) { + /* NOTE: This is a deprecated API, and Nintendo aborts when it is called. */ + AMS_HTCS_MANAGER_DEPRECATED_API(out_err, out_res, desc, backlog_count); + } + + Result ManagerServiceObject::Accept(sf::Out<s32> out_err, sf::Out<s32> out_res, sf::Out<htcs::SockAddrHtcs> out_address, s32 desc) { + /* NOTE: This is a deprecated API, and Nintendo aborts when it is called. */ + AMS_HTCS_MANAGER_DEPRECATED_API(out_err, out_res, out_address, desc); + } + + Result ManagerServiceObject::Recv(sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::OutBuffer &buffer, s32 desc, s32 flags) { + /* NOTE: This is a deprecated API, and Nintendo aborts when it is called. */ + AMS_HTCS_MANAGER_DEPRECATED_API(out_err, out_size, buffer, desc, flags); + } + + Result ManagerServiceObject::Send(sf::Out<s32> out_err, sf::Out<s64> out_size, s32 desc, const sf::InBuffer &buffer, s32 flags) { + /* NOTE: This is a deprecated API, and Nintendo aborts when it is called. */ + AMS_HTCS_MANAGER_DEPRECATED_API(out_err, out_size, desc, buffer, flags); + } + + Result ManagerServiceObject::Shutdown(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, s32 how) { + /* NOTE: This is a deprecated API, and Nintendo aborts when it is called. */ + AMS_HTCS_MANAGER_DEPRECATED_API(out_err, out_res, desc, how); + } + + Result ManagerServiceObject::Fcntl(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 desc, s32 command, s32 value) { + /* NOTE: This is a deprecated API, and Nintendo aborts when it is called. */ + AMS_HTCS_MANAGER_DEPRECATED_API(out_err, out_res, desc, command, value); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_service_object_allocator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_service_object_allocator.hpp new file mode 100644 index 00000000..a5de0d98 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_service_object_allocator.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::htcs::server { + + struct ServiceObjectAllocatorTag; + using ServiceObjectAllocator = ams::sf::ExpHeapStaticAllocator<32_KB, ServiceObjectAllocatorTag>; + using ServiceObjectFactory = ams::sf::ObjectFactory<typename ServiceObjectAllocator::Policy>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_socket_service_object.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_socket_service_object.cpp new file mode 100644 index 00000000..059abfd4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_socket_service_object.cpp @@ -0,0 +1,333 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_socket_service_object.hpp" +#include "htcs_service_object_allocator.hpp" +#include "../impl/htcs_manager.hpp" + +namespace ams::htcs::server { + + SocketServiceObject::SocketServiceObject(ManagerServiceObject *manager, s32 desc) : m_manager(manager, true), m_desc(desc) { + /* ... */ + } + + SocketServiceObject::~SocketServiceObject() { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Close the underlying socket. */ + s32 dummy_err, dummy_res; + manager->Close(std::addressof(dummy_err), std::addressof(dummy_res), m_desc); + } + + Result SocketServiceObject::Close(sf::Out<s32> out_err, sf::Out<s32> out_res) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Close the underlying socket. */ + manager->Close(out_err.GetPointer(), out_res.GetPointer(), m_desc); + + R_SUCCEED(); + } + + Result SocketServiceObject::Connect(sf::Out<s32> out_err, sf::Out<s32> out_res, const htcs::SockAddrHtcs &address) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Perform the connect. */ + manager->Connect(out_err.GetPointer(), out_res.GetPointer(), address, m_desc); + + R_SUCCEED(); + } + + Result SocketServiceObject::Bind(sf::Out<s32> out_err, sf::Out<s32> out_res, const htcs::SockAddrHtcs &address) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Perform the bind. */ + manager->Bind(out_err.GetPointer(), out_res.GetPointer(), address, m_desc); + + R_SUCCEED(); + } + + Result SocketServiceObject::Listen(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 backlog_count) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Perform the listen. */ + manager->Listen(out_err.GetPointer(), out_res.GetPointer(), backlog_count, m_desc); + + R_SUCCEED(); + } + + Result SocketServiceObject::Recv(sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::OutAutoSelectBuffer &buffer, s32 flags) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Perform the recv. */ + manager->Recv(out_err.GetPointer(), out_size.GetPointer(), reinterpret_cast<char *>(buffer.GetPointer()), buffer.GetSize(), flags, m_desc); + + R_SUCCEED(); + } + + Result SocketServiceObject::Send(sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::InAutoSelectBuffer &buffer, s32 flags) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Perform the send. */ + manager->Send(out_err.GetPointer(), out_size.GetPointer(), reinterpret_cast<const char *>(buffer.GetPointer()), buffer.GetSize(), flags, m_desc); + + R_SUCCEED(); + } + + Result SocketServiceObject::Shutdown(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 how) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Perform the shutdown. */ + manager->Shutdown(out_err.GetPointer(), out_res.GetPointer(), how, m_desc); + + R_SUCCEED(); + } + + Result SocketServiceObject::Fcntl(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 command, s32 value) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Perform the fcntl. */ + manager->Fcntl(out_err.GetPointer(), out_res.GetPointer(), command, value, m_desc); + + R_SUCCEED(); + } + + Result SocketServiceObject::AcceptStart(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Start the accept. */ + os::NativeHandle event_handle; + R_TRY(manager->AcceptStart(out_task_id.GetPointer(), std::addressof(event_handle), m_desc)); + + /* Set the output event handle. */ + out_event.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result SocketServiceObject::AcceptResults(sf::Out<s32> out_err, sf::Out<sf::SharedPointer<tma::ISocket>> out, sf::Out<htcs::SockAddrHtcs> out_address, u32 task_id) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Get the accept results. */ + s32 desc = -1; + manager->AcceptResults(out_err.GetPointer(), std::addressof(desc), out_address.GetPointer(), task_id, m_desc); + + /* If an error occurred, we're done. */ + R_SUCCEED_IF(*out_err != 0); + + /* Create a new socket object. */ + *out = ServiceObjectFactory::CreateSharedEmplaced<tma::ISocket, SocketServiceObject>(m_manager.Get(), desc); + + R_SUCCEED(); + } + + Result SocketServiceObject::RecvStart(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, s32 mem_size, s32 flags) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Start the recv. */ + os::NativeHandle event_handle; + R_TRY(manager->RecvStart(out_task_id.GetPointer(), std::addressof(event_handle), mem_size, m_desc, flags)); + + /* Set the output event handle. */ + out_event.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result SocketServiceObject::RecvResults(sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::OutAutoSelectBuffer &buffer, u32 task_id) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Get the recv results. */ + manager->RecvResults(out_err.GetPointer(), out_size.GetPointer(), reinterpret_cast<char *>(buffer.GetPointer()), buffer.GetSize(), task_id, m_desc); + + R_SUCCEED(); + } + + Result SocketServiceObject::RecvLargeStart(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, s32 unaligned_size_start, s32 unaligned_size_end, s64 aligned_size, sf::CopyHandle &&mem_handle, s32 flags) { + /* Check that the transfer memory size is okay. */ + R_UNLESS(util::IsIntValueRepresentable<size_t>(aligned_size), htcs::ResultInvalidSize()); + + /* Attach the transfer memory. */ + os::TransferMemoryType tmem; + os::AttachTransferMemory(std::addressof(tmem), static_cast<size_t>(aligned_size), mem_handle.GetOsHandle(), mem_handle.IsManaged()); + mem_handle.Detach(); + ON_SCOPE_EXIT { os::DestroyTransferMemory(std::addressof(tmem)); }; + + /* Map the transfer memory. */ + void *address; + R_TRY(os::MapTransferMemory(std::addressof(address), std::addressof(tmem), os::MemoryPermission_None)); + ON_SCOPE_EXIT { os::UnmapTransferMemory(std::addressof(tmem)); }; + + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Start the large receive. */ + os::NativeHandle event_handle; + R_TRY(manager->RecvStart(out_task_id.GetPointer(), std::addressof(event_handle), unaligned_size_start + aligned_size + unaligned_size_end, m_desc, flags)); + + /* Set the output event handle. */ + out_event.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result SocketServiceObject::SendStartOld(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, const sf::InAutoSelectBuffer &buffer, s32 flags) { + R_RETURN(this->SendStart(out_task_id, out_event, sf::InNonSecureAutoSelectBuffer(buffer.GetPointer(), buffer.GetSize()), flags)); + } + + Result SocketServiceObject::SendLargeStart(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, const sf::InAutoSelectBuffer &start_buffer, const sf::InAutoSelectBuffer &end_buffer, sf::CopyHandle &&mem_handle, s64 aligned_size, s32 flags) { + /* Check that the sizes are okay. */ + R_UNLESS(util::IsIntValueRepresentable<s64>(start_buffer.GetSize()), htcs::ResultInvalidSize()); + R_UNLESS(util::IsIntValueRepresentable<s64>(end_buffer.GetSize()), htcs::ResultInvalidSize()); + R_UNLESS(util::IsIntValueRepresentable<size_t>(aligned_size), htcs::ResultInvalidSize()); + + /* Attach the transfer memory. */ + os::TransferMemoryType tmem; + os::AttachTransferMemory(std::addressof(tmem), static_cast<size_t>(aligned_size), mem_handle.GetOsHandle(), mem_handle.IsManaged()); + mem_handle.Detach(); + ON_SCOPE_EXIT { os::DestroyTransferMemory(std::addressof(tmem)); }; + + /* Map the transfer memory. */ + void *address; + R_TRY(os::MapTransferMemory(std::addressof(address), std::addressof(tmem), os::MemoryPermission_None)); + ON_SCOPE_EXIT { os::UnmapTransferMemory(std::addressof(tmem)); }; + + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Start the large send. */ + constexpr auto NumBuffers = 3; + const char *pointers[NumBuffers] = { reinterpret_cast<const char *>(start_buffer.GetPointer()), static_cast<const char *>(address), reinterpret_cast<const char *>(end_buffer.GetPointer()) }; + s64 sizes[NumBuffers] = { static_cast<s64>(start_buffer.GetSize()), aligned_size, static_cast<s64>(end_buffer.GetSize()) }; + + os::NativeHandle event_handle; + R_TRY(manager->SendLargeStart(out_task_id.GetPointer(), std::addressof(event_handle), pointers, sizes, NumBuffers, m_desc, flags)); + + /* Set the output event handle. */ + out_event.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result SocketServiceObject::SendResults(sf::Out<s32> out_err, sf::Out<s64> out_size, u32 task_id) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Get the send results. */ + manager->SendResults(out_err.GetPointer(), out_size.GetPointer(), task_id, m_desc); + + R_SUCCEED(); + } + + Result SocketServiceObject::StartSend(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, sf::Out<s64> out_max_size, s64 size, s32 flags) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Start the send. */ + os::NativeHandle event_handle; + R_TRY(manager->StartSend(out_task_id.GetPointer(), std::addressof(event_handle), m_desc, size, flags)); + + /* Set the output max size to the size. */ + *out_max_size = size; + + /* Set the output event handle. */ + out_event.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result SocketServiceObject::ContinueSendOld(sf::Out<s64> out_size, sf::Out<bool> out_wait, const sf::InAutoSelectBuffer &buffer, u32 task_id) { + R_RETURN(this->ContinueSend(out_size, out_wait, sf::InNonSecureAutoSelectBuffer(buffer.GetPointer(), buffer.GetSize()), task_id)); + } + + Result SocketServiceObject::EndSend(sf::Out<s32> out_err, sf::Out<s64> out_size, u32 task_id) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* End the send. */ + manager->EndSend(out_err.GetPointer(), out_size.GetPointer(), task_id, m_desc); + + R_SUCCEED(); + } + + Result SocketServiceObject::StartRecv(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, s64 size, s32 flags) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Start the recv. */ + os::NativeHandle event_handle; + R_TRY(manager->StartRecv(out_task_id.GetPointer(), std::addressof(event_handle), size, m_desc, flags)); + + /* Set the output event handle. */ + out_event.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result SocketServiceObject::EndRecv(sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::OutAutoSelectBuffer &buffer, u32 task_id) { + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* End the recv. */ + manager->EndRecv(out_err.GetPointer(), out_size.GetPointer(), reinterpret_cast<char *>(buffer.GetPointer()), buffer.GetSize(), task_id, m_desc); + + R_SUCCEED(); + } + + Result SocketServiceObject::SendStart(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, const sf::InNonSecureAutoSelectBuffer &buffer, s32 flags) { + /* Check that the sizes are okay. */ + R_UNLESS(util::IsIntValueRepresentable<s64>(buffer.GetSize()), htcs::ResultInvalidSize()); + + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Start the send. */ + os::NativeHandle event_handle; + R_TRY(manager->SendStart(out_task_id.GetPointer(), std::addressof(event_handle), reinterpret_cast<const char *>(buffer.GetPointer()), buffer.GetSize(), m_desc, flags)); + + /* Set the output event handle. */ + out_event.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result SocketServiceObject::ContinueSend(sf::Out<s64> out_size, sf::Out<bool> out_wait, const sf::InNonSecureAutoSelectBuffer &buffer, u32 task_id) { + /* Check that the sizes are okay. */ + R_UNLESS(util::IsIntValueRepresentable<s64>(buffer.GetSize()), htcs::ResultInvalidSize()); + + /* Get the htcs manager. */ + auto *manager = impl::HtcsManagerHolder::GetHtcsManager(); + + /* Continue the send. */ + R_TRY(manager->ContinueSend(out_size.GetPointer(), reinterpret_cast<const char *>(buffer.GetPointer()), buffer.GetSize(), task_id, m_desc)); + + /* We aren't doing a waiting send. */ + *out_wait = false; + R_SUCCEED(); + } + + Result SocketServiceObject::GetPrimitive(sf::Out<s32> out) { + /* Get our descriptor. */ + *out = m_desc; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_socket_service_object.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_socket_service_object.hpp new file mode 100644 index 00000000..46a3cfe5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_socket_service_object.hpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "htcs_manager_service_object.hpp" + +namespace ams::htcs::server { + + class SocketServiceObject { + private: + sf::SharedPointer<ManagerServiceObject> m_manager; + s32 m_desc; + public: + SocketServiceObject(ManagerServiceObject *manager, s32 desc); + ~SocketServiceObject(); + public: + Result Close(sf::Out<s32> out_err, sf::Out<s32> out_res); + Result Connect(sf::Out<s32> out_err, sf::Out<s32> out_res, const htcs::SockAddrHtcs &address); + Result Bind(sf::Out<s32> out_err, sf::Out<s32> out_res, const htcs::SockAddrHtcs &address); + Result Listen(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 backlog_count); + Result Accept(sf::Out<s32> out_err, sf::Out<sf::SharedPointer<tma::ISocket>> out, sf::Out<htcs::SockAddrHtcs> out_address); + Result Recv(sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::OutAutoSelectBuffer &buffer, s32 flags); + Result Send(sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::InAutoSelectBuffer &buffer, s32 flags); + Result Shutdown(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 how); + Result Fcntl(sf::Out<s32> out_err, sf::Out<s32> out_res, s32 command, s32 value); + Result AcceptStart(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event); + Result AcceptResults(sf::Out<s32> out_err, sf::Out<sf::SharedPointer<tma::ISocket>> out, sf::Out<htcs::SockAddrHtcs> out_address, u32 task_id); + Result RecvStart(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, s32 mem_size, s32 flags); + Result RecvResults(sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::OutAutoSelectBuffer &buffer, u32 task_id); + Result RecvLargeStart(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, s32 unaligned_size_start, s32 unaligned_size_end, s64 aligned_size, sf::CopyHandle &&mem_handle, s32 flags); + Result SendStartOld(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, const sf::InAutoSelectBuffer &buffer, s32 flags); + Result SendLargeStart(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, const sf::InAutoSelectBuffer &start_buffer, const sf::InAutoSelectBuffer &end_buffer, sf::CopyHandle &&mem_handle, s64 aligned_size, s32 flags); + Result SendResults(sf::Out<s32> out_err, sf::Out<s64> out_size, u32 task_id); + Result StartSend(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, sf::Out<s64> out_max_size, s64 size, s32 flags); + Result ContinueSendOld(sf::Out<s64> out_size, sf::Out<bool> out_wait, const sf::InAutoSelectBuffer &buffer, u32 task_id); + Result EndSend(sf::Out<s32> out_err, sf::Out<s64> out_size, u32 task_id); + Result StartRecv(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, s64 size, s32 flags); + Result EndRecv(sf::Out<s32> out_err, sf::Out<s64> out_size, const sf::OutAutoSelectBuffer &buffer, u32 task_id); + Result SendStart(sf::Out<u32> out_task_id, sf::OutCopyHandle out_event, const sf::InNonSecureAutoSelectBuffer &buffer, s32 flags); + Result ContinueSend(sf::Out<s64> out_size, sf::Out<bool> out_wait, const sf::InNonSecureAutoSelectBuffer &buffer, u32 task_id); + Result GetPrimitive(sf::Out<s32> out); + }; + static_assert(tma::IsISocket<SocketServiceObject>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_socket_service_object_deprecated.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_socket_service_object_deprecated.cpp new file mode 100644 index 00000000..9421e9d6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/htcs/server/htcs_socket_service_object_deprecated.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "htcs_manager_service_object.hpp" +#include "htcs_socket_service_object.hpp" + +namespace ams::htcs::server { + + #define AMS_HTCS_MANAGER_DEPRECATED_API(...) ({ AMS_UNUSED(__VA_ARGS__); AMS_ABORT("Deprecated IHtcsManager API %s was called.\n", AMS_CURRENT_FUNCTION_NAME); }) + + Result SocketServiceObject::Accept(sf::Out<s32> out_err, sf::Out<sf::SharedPointer<tma::ISocket>> out, sf::Out<htcs::SockAddrHtcs> out_address) { + /* NOTE: This is a deprecated API, and Nintendo aborts when it is called. */ + AMS_HTCS_MANAGER_DEPRECATED_API(out_err, out, out_address); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/i2c_bus_device_map.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/i2c_bus_device_map.inc new file mode 100644 index 00000000..7fb946a5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/i2c_bus_device_map.inc @@ -0,0 +1,72 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by i2cgen.py, do not edit manually. */ + +constexpr inline const I2cDeviceDefinition I2c1DeviceList[] = { + { DeviceCode_ClassicController, 0x52 }, + { DeviceCode_Nct72, 0x4C }, + { DeviceCode_Alc5639, 0x1C }, + { DeviceCode_Bq24193, 0x6B }, + { DeviceCode_Max17050, 0x36 }, + { DeviceCode_Bm92t30mwv, 0x18 }, +}; + +constexpr inline const I2cDeviceDefinition I2c2DeviceList[] = { + { DeviceCode_Ina226Vdd15v0Hb, 0x40 }, + { DeviceCode_Ina226VsysCpuDs, 0x41 }, + { DeviceCode_Ina226VsysGpuDs, 0x44 }, + { DeviceCode_Ina226VsysDdrDs, 0x45 }, + { DeviceCode_Ina226VsysAp, 0x46 }, + { DeviceCode_Ina226VsysBlDs, 0x47 }, + { DeviceCode_Bh1730, 0x29 }, + { DeviceCode_Ina226VsysCore, 0x48 }, + { DeviceCode_Ina226Soc1V8, 0x49 }, + { DeviceCode_Ina226Lpddr1V8, 0x4A }, + { DeviceCode_Ina226Reg1V32, 0x4B }, + { DeviceCode_Ina226Vdd3V3Sys, 0x4D }, + { DeviceCode_Ina226VddDdr0V6, 0x4E }, + { DeviceCode_HoagNfcIc, 0x08 }, +}; + +constexpr inline const I2cDeviceDefinition I2c3DeviceList[] = { + { DeviceCode_Ftm3bd56, 0x49 }, +}; + +constexpr inline const I2cDeviceDefinition I2c4DeviceList[] = { + { DeviceCode_HdmiDdc, 0x50 }, + { DeviceCode_HdmiScdc, 0x54 }, + { DeviceCode_HdmiHdcp, 0x3A }, +}; + +constexpr inline const I2cDeviceDefinition I2c5DeviceList[] = { + { DeviceCode_Max77620Rtc, 0x68 }, + { DeviceCode_Max77620Pmic, 0x3C }, + { DeviceCode_Max77621Cpu, 0x1B }, + { DeviceCode_Max77621Gpu, 0x1C }, + { DeviceCode_Fan53528, 0x52 }, + { DeviceCode_Max77812_3, 0x31 }, + { DeviceCode_Max77812_2, 0x33 }, + { DeviceCode_PmicUnknownAula_4_18, 0x18 }, +}; + +constexpr inline const I2cBusDefinition I2cBusList[] = { + { DeviceCode_I2c1, 0x7000C000, 0x100, SpeedMode_Standard, 70, I2c1DeviceList, util::size(I2c1DeviceList) }, + { DeviceCode_I2c2, 0x7000C400, 0x100, SpeedMode_Fast, 116, I2c2DeviceList, util::size(I2c2DeviceList) }, + { DeviceCode_I2c3, 0x7000C500, 0x100, SpeedMode_Fast, 124, I2c3DeviceList, util::size(I2c3DeviceList) }, + { DeviceCode_I2c4, 0x7000C700, 0x100, SpeedMode_Standard, 152, I2c4DeviceList, util::size(I2c4DeviceList) }, + { DeviceCode_I2c5, 0x7000D000, 0x100, SpeedMode_Fast, 85, I2c5DeviceList, util::size(I2c5DeviceList) }, +}; diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/i2c_driver_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/i2c_driver_api.cpp new file mode 100644 index 00000000..2b4dad7b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/i2c_driver_api.cpp @@ -0,0 +1,111 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/i2c_bus_manager.hpp" +#include "impl/i2c_device_property_manager.hpp" + +namespace ams::i2c::driver::board::nintendo::nx { + + namespace { + + struct I2cDeviceDefinition { + DeviceCode device_code; + u8 slave_address; + }; + + struct I2cBusDefinition { + DeviceCode device_code; + dd::PhysicalAddress registers_phys_addr; + size_t registers_size; + SpeedMode speed_mode; + os::InterruptName interrupt_name; + const I2cDeviceDefinition *devices; + size_t num_devices; + + constexpr bool IsPowerBus() const { + return this->device_code == DeviceCode_I2c5; + } + }; + + #include "i2c_bus_device_map.inc" + + void CheckSpeedMode(SpeedMode speed_mode) { + switch (speed_mode) { + case SpeedMode_Standard: break; + case SpeedMode_Fast: break; + case SpeedMode_FastPlus: break; + case SpeedMode_HighSpeed: break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void Initialize(impl::I2cBusAccessorManager &bus_manager, impl::I2cDevicePropertyManager &device_manager) { + /* Create an accessor for each bus. */ + for (const auto &bus_def : I2cBusList) { + /* Check that the speed mode is valid. */ + CheckSpeedMode(bus_def.speed_mode); + + /* Find the bus. */ + auto *bus = bus_manager.Find([&bus_def](const auto &it) { + return it.GetRegistersPhysicalAddress() == bus_def.registers_phys_addr; + }); + + /* If the bus doesn't exist, create it. */ + if (bus == nullptr) { + /* Allocate the bus. */ + bus = bus_manager.Allocate(); + + /* Initialize the bus. */ + bus->Initialize(bus_def.registers_phys_addr, bus_def.registers_size, bus_def.interrupt_name, bus_def.IsPowerBus(), bus_def.speed_mode); + + /* Register the bus. */ + i2c::driver::RegisterDriver(bus); + } + + /* Set the bus's device code. */ + bus->RegisterDeviceCode(bus_def.device_code); + + /* Allocate and register the devices for the bus. */ + for (size_t i = 0; i < bus_def.num_devices; ++i) { + /* Get the device definition. */ + const auto &entry = bus_def.devices[i]; + + /* Allocate the device. */ + I2cDeviceProperty *device = device_manager.Allocate(entry.slave_address, AddressingMode_SevenBit); + + /* Register the device with our bus. */ + bus->RegisterDevice(device); + + /* Register the device code with our driver. */ + R_ABORT_UNLESS(i2c::driver::RegisterDeviceCode(entry.device_code, device)); + } + } + } + + constinit util::TypedStorage<impl::I2cBusAccessorManager> g_bus_accessor_manager = {}; + constinit util::TypedStorage<impl::I2cDevicePropertyManager> g_device_manager = {}; + + } + + void Initialize() { + /* Initialize managers. */ + util::ConstructAt(g_bus_accessor_manager, ddsf::GetMemoryResource()); + util::ConstructAt(g_device_manager, ddsf::GetMemoryResource()); + + return Initialize(util::GetReference(g_bus_accessor_manager), util::GetReference(g_device_manager)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/impl/i2c_bus_accessor.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/impl/i2c_bus_accessor.cpp new file mode 100644 index 00000000..a59c6d65 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/impl/i2c_bus_accessor.cpp @@ -0,0 +1,773 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "i2c_bus_accessor.hpp" + +namespace ams::i2c::driver::board::nintendo::nx::impl { + + namespace { + + constexpr inline TimeSpan Timeout = TimeSpan::FromMilliSeconds(100); + + #define IO_PACKET_BITS_MASK(NAME) REG_NAMED_BITS_MASK (_IMPL_IO_PACKET_, NAME) + #define IO_PACKET_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (_IMPL_IO_PACKET_, NAME, VALUE) + #define IO_PACKET_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (_IMPL_IO_PACKET_, NAME, ENUM) + #define IO_PACKET_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(_IMPL_IO_PACKET_, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + + #define DEFINE_IO_PACKET_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (_IMPL_IO_PACKET_, NAME, __OFFSET__, __WIDTH__) + #define DEFINE_IO_PACKET_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (_IMPL_IO_PACKET_, NAME, __OFFSET__, ZERO, ONE) + #define DEFINE_IO_PACKET_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (_IMPL_IO_PACKET_, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) + #define DEFINE_IO_PACKET_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(_IMPL_IO_PACKET_, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) + #define DEFINE_IO_PACKET_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (_IMPL_IO_PACKET_, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + + DEFINE_IO_PACKET_REG_THREE_BIT_ENUM(HEADER_WORD0_PKT_TYPE, 0, REQUEST, RESPONSE, INTERRUPT, STOP, RSVD4, RSVD5, RSVD6, RSVD7); + DEFINE_IO_PACKET_REG_FOUR_BIT_ENUM(HEADER_WORD0_PROTOCOL, 4, RSVD0, I2C, RSVD2, RSVD3, RSVD4, RSVD5, RSVD6, RSVD7, RSVD8, RSVD9, RSVD10, RSVD11, RSVD12, RSVD13, RSVD14, RSVD15) + DEFINE_IO_PACKET_REG(HEADER_WORD0_CONTROLLER_ID, 12, 4); + DEFINE_IO_PACKET_REG(HEADER_WORD0_PKT_ID, 16, 8); + DEFINE_IO_PACKET_REG_TWO_BIT_ENUM(HEADER_WORD0_PROT_HDR_SZ, 28, 1_WORD, 2_WORD, 3_WORD, 4_WORD); + + DEFINE_IO_PACKET_REG(HEADER_WORD1_PAYLOAD_SIZE, 0, 12); + + DEFINE_IO_PACKET_REG(PROTOCOL_HEADER_SLAVE_ADDR, 0, 10); + DEFINE_IO_PACKET_REG(PROTOCOL_HEADER_HS_MASTER_ADDR, 12, 3); + DEFINE_IO_PACKET_REG_BIT_ENUM(PROTOCOL_HEADER_CONTINUE_XFER, 15, USE_REPEAT_START_TOP, CONTINUE); + DEFINE_IO_PACKET_REG_BIT_ENUM(PROTOCOL_HEADER_REPEAT_START_STOP, 16, STOP_CONDITION, REPEAT_START_CONDITION); + DEFINE_IO_PACKET_REG_BIT_ENUM(PROTOCOL_HEADER_IE, 17, DISABLE, ENABLE); + DEFINE_IO_PACKET_REG_BIT_ENUM(PROTOCOL_HEADER_ADDRESS_MODE, 18, SEVEN_BIT, TEN_BIT); + DEFINE_IO_PACKET_REG_BIT_ENUM(PROTOCOL_HEADER_READ_WRITE, 19, WRITE, READ); + DEFINE_IO_PACKET_REG_BIT_ENUM(PROTOCOL_HEADER_SEND_START_BYTE, 20, DISABLE, ENABLE); + DEFINE_IO_PACKET_REG_BIT_ENUM(PROTOCOL_HEADER_CONTINUE_ON_NACK, 21, DISABLE, ENABLE); + DEFINE_IO_PACKET_REG_BIT_ENUM(PROTOCOL_HEADER_HS_MODE, 22, DISABLE, ENABLE); + + } + + void I2cBusAccessor::Initialize(dd::PhysicalAddress reg_paddr, size_t reg_size, os::InterruptName intr, bool pb, SpeedMode sm) { + AMS_ASSERT(m_state == State::NotInitialized); + + m_is_power_bus = pb; + m_speed_mode = sm; + m_interrupt_name = intr; + m_registers_phys_addr = reg_paddr; + m_registers_size = reg_size; + m_state = State::Initializing; + } + + void I2cBusAccessor::RegisterDeviceCode(DeviceCode dc) { + AMS_ASSERT(m_state == State::Initializing); + + m_device_code = dc; + } + + void I2cBusAccessor::InitializeDriver() { + AMS_ASSERT(m_state == State::Initializing); + + m_registers = reinterpret_cast<volatile I2cRegisters *>(dd::QueryIoMapping(m_registers_phys_addr, m_registers_size)); + AMS_ABORT_UNLESS(m_registers != nullptr); + + m_state = State::Initialized; + } + + void I2cBusAccessor::FinalizeDriver() { + AMS_ASSERT(m_state == State::Initialized); + m_state = State::Initializing; + } + + Result I2cBusAccessor::InitializeDevice(I2cDeviceProperty *device) { + /* Check that the device is valid. */ + AMS_ASSERT(device != nullptr); + AMS_ASSERT(m_state == State::Initialized); + AMS_UNUSED(device); + + /* Acquire exclusive access. */ + std::scoped_lock lk(m_user_count_mutex); + + /* Increment our user count -- if we're already open, we're done. */ + AMS_ASSERT(m_user_count >= 0); + ++m_user_count; + R_SUCCEED_IF(m_user_count > 1); + + /* Initialize our interrupt event. */ + os::InitializeInterruptEvent(std::addressof(m_interrupt_event), m_interrupt_name, os::EventClearMode_ManualClear); + os::ClearInterruptEvent(std::addressof(m_interrupt_event)); + + /* If we're not power bus, perform power management init. */ + if (!m_is_power_bus) { + /* Initialize regulator library. */ + regulator::Initialize(); + + /* Try to open regulator session. */ + R_TRY(this->TryOpenRegulatorSession()); + + /* If we have a regulator session, set voltage to 2.9V. */ + if (m_has_regulator_session) { + /* NOTE: Nintendo does not check the result, here. */ + regulator::SetVoltageValue(std::addressof(m_regulator_session), 2'900'000u); + } + + /* Initialize clock/reset library. */ + clkrst::Initialize(); + } + + /* Execute initial config. */ + this->ExecuteInitialConfig(); + + /* If we have a regulator session, enable voltage. */ + if (!m_is_power_bus && m_has_regulator_session) { + /* Check whether voltage was already enabled. */ + const bool was_enabled = regulator::GetVoltageEnabled(std::addressof(m_regulator_session)); + + /* NOTE: Nintendo does not check the result of this call. */ + regulator::SetVoltageEnabled(std::addressof(m_regulator_session), true); + + /* If we enabled voltage, delay to give our enable time to take. */ + if (!was_enabled) { + os::SleepThread(TimeSpan::FromMicroSeconds(560)); + } + } + + R_SUCCEED(); + } + + void I2cBusAccessor::FinalizeDevice(I2cDeviceProperty *device) { + /* Check that the device is valid. */ + AMS_ASSERT(device != nullptr); + AMS_ASSERT(m_state == State::Initialized); + AMS_UNUSED(device); + + /* Acquire exclusive access. */ + std::scoped_lock lk(m_user_count_mutex); + + /* Increment our user count -- if we're not the last user, we're done. */ + AMS_ASSERT(m_user_count > 0); + --m_user_count; + if (m_user_count > 0) { + return; + } + + /* Finalize our interrupt event. */ + os::FinalizeInterruptEvent(std::addressof(m_interrupt_event)); + + /* If we have a regulator session, disable voltage. */ + if (m_has_regulator_session) { + /* NOTE: Nintendo does not check the result of this call. */ + regulator::SetVoltageEnabled(std::addressof(m_regulator_session), false); + } + + /* Finalize the clock/reset library. */ + clkrst::Finalize(); + + /* If we have a regulator session, close it. */ + if (m_has_regulator_session) { + regulator::CloseSession(std::addressof(m_regulator_session)); + m_has_regulator_session = false; + } + + /* Finalize the regulator library. */ + regulator::Finalize(); + } + + Result I2cBusAccessor::Send(I2cDeviceProperty *device, const void *src, size_t src_size, TransactionOption option) { + /* Check pre-conditions. */ + AMS_ASSERT(device != nullptr); + AMS_ASSERT(src != nullptr); + AMS_ASSERT(src_size > 0); + + if (m_is_power_bus) { + AMS_ASSERT(m_state == State::Initialized || m_state == State::Suspended); + } else { + AMS_ASSERT(m_state == State::Initialized); + } + + /* Send the data. */ + R_RETURN(this->Send(static_cast<const u8 *>(src), src_size, option, device->GetAddress(), device->GetAddressingMode())); + } + + Result I2cBusAccessor::Receive(void *dst, size_t dst_size, I2cDeviceProperty *device, TransactionOption option) { + /* Check pre-conditions. */ + AMS_ASSERT(device != nullptr); + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(dst_size > 0); + + if (m_is_power_bus) { + AMS_ASSERT(m_state == State::Initialized || m_state == State::Suspended); + } else { + AMS_ASSERT(m_state == State::Initialized); + } + + /* Send the data. */ + R_RETURN(this->Receive(static_cast<u8 *>(dst), dst_size, option, device->GetAddress(), device->GetAddressingMode())); + } + + void I2cBusAccessor::SuspendBus() { + /* Check that state is valid. */ + AMS_ASSERT(m_state == State::Initialized); + + /* Acquire exclusive access. */ + std::scoped_lock lk(m_user_count_mutex); + + /* If we need to, disable clock/voltage appropriately. */ + if (!m_is_power_bus && m_user_count > 0) { + /* Disable clock. */ + { + /* Open a clkrst session. */ + clkrst::ClkRstSession clkrst_session; + R_ABORT_UNLESS(clkrst::OpenSession(std::addressof(clkrst_session), m_device_code)); + ON_SCOPE_EXIT { clkrst::CloseSession(std::addressof(clkrst_session)); }; + + /* Set clock disabled for the session. */ + clkrst::SetClockDisabled(std::addressof(clkrst_session)); + } + + /* Disable voltage. */ + if (m_has_regulator_session) { + regulator::SetVoltageEnabled(std::addressof(m_regulator_session), false); + } + } + + /* Update state. */ + m_state = State::Suspended; + } + + void I2cBusAccessor::SuspendPowerBus() { + /* Check that state is valid. */ + AMS_ASSERT(m_state == State::Suspended); + + /* Acquire exclusive access. */ + std::scoped_lock lk(m_user_count_mutex); + + /* If we need to, disable clock/voltage appropriately. */ + if (m_is_power_bus && m_user_count > 0) { + /* Nothing should actually be done here. */ + } + + /* Update state. */ + m_state = State::PowerBusSuspended; + } + + void I2cBusAccessor::ResumeBus() { + /* Check that state is valid. */ + AMS_ASSERT(m_state == State::Suspended); + + /* Acquire exclusive access. */ + std::scoped_lock lk(m_user_count_mutex); + + /* If we need to, enable clock/voltage appropriately. */ + if (!m_is_power_bus && m_user_count > 0) { + /* Enable voltage. */ + if (m_has_regulator_session) { + /* Check whether voltage was already enabled. */ + const bool was_enabled = regulator::GetVoltageEnabled(std::addressof(m_regulator_session)); + + /* NOTE: Nintendo does not check the result of this call. */ + regulator::SetVoltageEnabled(std::addressof(m_regulator_session), true); + + /* If we enabled voltage, delay to give our enable time to take. */ + if (!was_enabled) { + os::SleepThread(TimeSpan::FromMicroSeconds(560)); + } + } + + /* Execute initial config, which will enable clock as relevant. */ + this->ExecuteInitialConfig(); + } + + /* Update state. */ + m_state = State::Initialized; + } + + void I2cBusAccessor::ResumePowerBus() { + /* Check that state is valid. */ + AMS_ASSERT(m_state == State::PowerBusSuspended); + + /* Acquire exclusive access. */ + std::scoped_lock lk(m_user_count_mutex); + + /* If we need to, enable clock/voltage appropriately. */ + if (m_is_power_bus && m_user_count > 0) { + /* Execute initial config, which will enable clock as relevant. */ + this->ExecuteInitialConfig(); + } + + /* Update state. */ + m_state = State::Suspended; + } + + Result I2cBusAccessor::TryOpenRegulatorSession() { + /* Ensure we track the session. */ + m_has_regulator_session = true; + auto s_guard = SCOPE_GUARD { m_has_regulator_session = false; }; + + /* Try to open the session. */ + R_TRY_CATCH(regulator::OpenSession(std::addressof(m_regulator_session), m_device_code)) { + R_CATCH(ddsf::ResultDeviceCodeNotFound) { + /* It's okay if the device isn't found, but we don't have a session if so. */ + m_has_regulator_session = false; + } + } R_END_TRY_CATCH; + + /* We opened (or not). */ + s_guard.Cancel(); + R_SUCCEED(); + } + + void I2cBusAccessor::ExecuteInitialConfig() { + /* Lock exclusive access to registers. */ + std::scoped_lock lk(m_register_mutex); + + /* Reset the controller. */ + this->ResetController(); + + /* Set clock registers. */ + this->SetClockRegisters(m_speed_mode); + + /* Set packet mode registers. */ + this->SetPacketModeRegisters(); + + /* Flush fifos. */ + this->FlushFifos(); + } + + Result I2cBusAccessor::Send(const u8 *src, size_t src_size, TransactionOption option, u16 slave_address, AddressingMode addressing_mode) { + /* Acquire exclusive access to the registers. */ + std::scoped_lock lk(m_register_mutex); + + /* Configure interrupt mask, clear interrupt status. */ + reg::Write(m_registers->interrupt_mask_register, I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_TFIFO_DATA_REQ_INT_EN, ENABLE), + I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_ARB_LOST_INT_EN, ENABLE), + I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_NOACK_INT_EN, ENABLE), + I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_PACKET_XFER_COMPLETE_INT_EN, ENABLE)); + + reg::Write(m_registers->interrupt_status_register, I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_ARB_LOST, SET), + I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_NOACK, SET), + I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_RFIFO_UNF, SET), + I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_TFIFO_OVF, SET), + I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_PACKET_XFER_COMPLETE, SET), + I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_ALL_PACKETS_XFER_COMPLETE, SET)); + + /* Write the header. */ + this->WriteHeader(Xfer_Write, src_size, option, slave_address, addressing_mode); + + /* Setup tracking variables for the data. */ + const u8 *cur = src; + size_t remaining = src_size; + + while (true) { + /* Get the number of empty bytes in the fifo status. */ + const u32 empty = reg::GetValue(m_registers->fifo_status, I2C_REG_BITS_MASK(FIFO_STATUS_TX_FIFO_EMPTY_CNT)); + + /* Write up to (empty) bytes to the fifo. */ + for (u32 i = 0; remaining > 0 && i < empty; ++i) { + /* Build the data word to send. */ + const size_t cur_bytes = std::min(remaining, sizeof(u32)); + + u32 word = 0; + for (size_t j = 0; j < cur_bytes; ++j) { + word |= cur[j] << (BITSIZEOF(u8) * j); + } + + /* Write the data word. */ + reg::Write(m_registers->tx_packet_fifo, word); + + /* Advance. */ + cur += cur_bytes; + remaining -= cur_bytes; + } + + /* If we're done, break. */ + if (remaining == 0) { + break; + } + + /* Wait for our current data to send. */ + os::ClearInterruptEvent(std::addressof(m_interrupt_event)); + if (!os::TimedWaitInterruptEvent(std::addressof(m_interrupt_event), Timeout)) { + /* We timed out. */ + this->HandleTransactionError(i2c::ResultBusBusy()); + + this->DisableInterruptMask(); + os::ClearInterruptEvent(std::addressof(m_interrupt_event)); + R_THROW(i2c::ResultTimeout()); + } + + /* Check and handle any errors. */ + R_TRY(this->CheckAndHandleError()); + } + + /* Configure interrupt mask to not care about tfifo data req. */ + reg::Write(m_registers->interrupt_mask_register, I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_ARB_LOST_INT_EN, ENABLE), + I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_NOACK_INT_EN, ENABLE), + I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_PACKET_XFER_COMPLETE_INT_EN, ENABLE)); + + /* Wait for the packet transfer to complete. */ + while (true) { + /* Check and handle any errors. */ + R_TRY(this->CheckAndHandleError()); + + /* Check if packet transfer is done. */ + if (reg::HasValue(m_registers->interrupt_status_register, I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_PACKET_XFER_COMPLETE, SET))) { + break; + } + + /* Wait for our the packet to transfer. */ + os::ClearInterruptEvent(std::addressof(m_interrupt_event)); + if (!os::TimedWaitInterruptEvent(std::addressof(m_interrupt_event), Timeout)) { + /* We timed out. */ + this->HandleTransactionError(i2c::ResultBusBusy()); + + this->DisableInterruptMask(); + os::ClearInterruptEvent(std::addressof(m_interrupt_event)); + R_THROW(i2c::ResultTimeout()); + } + } + + /* Check and handle any errors. */ + R_TRY(this->CheckAndHandleError()); + + /* We're done. */ + this->DisableInterruptMask(); + R_SUCCEED(); + } + + Result I2cBusAccessor::Receive(u8 *dst, size_t dst_size, TransactionOption option, u16 slave_address, AddressingMode addressing_mode) { + /* Acquire exclusive access to the registers. */ + std::scoped_lock lk(m_register_mutex); + + /* Configure interrupt mask, clear interrupt status. */ + reg::Write(m_registers->interrupt_mask_register, I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_RFIFO_DATA_REQ_INT_EN, ENABLE), + I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_ARB_LOST_INT_EN, ENABLE), + I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_NOACK_INT_EN, ENABLE), + I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_PACKET_XFER_COMPLETE_INT_EN, ENABLE)); + + reg::Write(m_registers->interrupt_status_register, I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_ARB_LOST, SET), + I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_NOACK, SET), + I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_RFIFO_UNF, SET), + I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_TFIFO_OVF, SET), + I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_PACKET_XFER_COMPLETE, SET), + I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_ALL_PACKETS_XFER_COMPLETE, SET)); + + /* Write the header. */ + this->WriteHeader(Xfer_Read, dst_size, option, slave_address, addressing_mode); + + /* Setup tracking variables for the data. */ + u8 *cur = dst; + size_t remaining = dst_size; + + while (remaining > 0) { + /* Wait for data to come in. */ + os::ClearInterruptEvent(std::addressof(m_interrupt_event)); + if (!os::TimedWaitInterruptEvent(std::addressof(m_interrupt_event), Timeout)) { + /* We timed out. */ + this->HandleTransactionError(i2c::ResultBusBusy()); + + this->DisableInterruptMask(); + os::ClearInterruptEvent(std::addressof(m_interrupt_event)); + R_THROW(i2c::ResultTimeout()); + } + + /* Check and handle any errors. */ + R_TRY(this->CheckAndHandleError()); + + /* Get the number of full bytes in the fifo status. */ + const u32 full = reg::GetValue(m_registers->fifo_status, I2C_REG_BITS_MASK(FIFO_STATUS_RX_FIFO_FULL_CNT)); + + /* Determine how many words we can read. */ + const size_t cur_words = std::min(util::DivideUp(remaining, sizeof(u32)), static_cast<size_t>(full)); + + /* Read the correct number of words from the fifo. */ + for (size_t i = 0; i < cur_words; ++i) { + /* Read the word from the fifo. */ + const u32 word = reg::Read(m_registers->rx_fifo); + + /* Copy bytes from the word. */ + const size_t cur_bytes = std::min(remaining, sizeof(u32)); + for (size_t j = 0; j < cur_bytes; ++j) { + cur[j] = (word >> (BITSIZEOF(u8) * j)) & 0xFF; + } + + /* Advance. */ + cur += cur_bytes; + remaining -= cur_bytes; + } + } + + /* We're done. */ + R_SUCCEED(); + } + + void I2cBusAccessor::WriteHeader(Xfer xfer, size_t size, TransactionOption option, u16 slave_address, AddressingMode addressing_mode) { + /* Parse interesting values from our arguments. */ + const bool is_read = xfer == Xfer_Read; + const bool is_7_bit = addressing_mode == AddressingMode_SevenBit; + const bool is_stop = (option & TransactionOption_StopCondition) != 0; + const bool is_hs = m_speed_mode == SpeedMode_HighSpeed; + const u32 slave_addr = ((static_cast<u32>(slave_address) & 0x7F) << 1) | (is_read ? 1 : 0); + + /* Flush fifos. */ + this->FlushFifos(); + + /* Enqueue the first header word. */ + reg::Write(m_registers->tx_packet_fifo, IO_PACKET_BITS_ENUM (HEADER_WORD0_PROT_HDR_SZ, 1_WORD), + IO_PACKET_BITS_VALUE(HEADER_WORD0_PKT_ID, 0), + IO_PACKET_BITS_VALUE(HEADER_WORD0_CONTROLLER_ID, 0), + IO_PACKET_BITS_ENUM (HEADER_WORD0_PROTOCOL, I2C), + IO_PACKET_BITS_ENUM (HEADER_WORD0_PKT_TYPE, REQUEST)); + + /* Enqueue the second header word. */ + reg::Write(m_registers->tx_packet_fifo, IO_PACKET_BITS_VALUE(HEADER_WORD1_PAYLOAD_SIZE, static_cast<u32>(size - 1))); + + /* Enqueue the protocol header word. */ + reg::Write(m_registers->tx_packet_fifo, IO_PACKET_BITS_ENUM_SEL(PROTOCOL_HEADER_HS_MODE, is_hs, ENABLE, DISABLE), + IO_PACKET_BITS_ENUM (PROTOCOL_HEADER_CONTINUE_ON_NACK, DISABLE), + IO_PACKET_BITS_ENUM (PROTOCOL_HEADER_SEND_START_BYTE, DISABLE), + IO_PACKET_BITS_ENUM_SEL(PROTOCOL_HEADER_READ_WRITE, is_read, READ, WRITE), + IO_PACKET_BITS_ENUM_SEL(PROTOCOL_HEADER_ADDRESS_MODE, is_7_bit, SEVEN_BIT, TEN_BIT), + IO_PACKET_BITS_ENUM (PROTOCOL_HEADER_IE, ENABLE), + IO_PACKET_BITS_ENUM_SEL(PROTOCOL_HEADER_REPEAT_START_STOP, is_stop, STOP_CONDITION, REPEAT_START_CONDITION), + IO_PACKET_BITS_ENUM (PROTOCOL_HEADER_CONTINUE_XFER, USE_REPEAT_START_TOP), + IO_PACKET_BITS_VALUE (PROTOCOL_HEADER_HS_MASTER_ADDR, 0), + IO_PACKET_BITS_VALUE (PROTOCOL_HEADER_SLAVE_ADDR, slave_addr)); + } + + void I2cBusAccessor::ResetController() const { + /* Reset the controller. */ + if (!m_is_power_bus) { + /* Open a clkrst session. */ + clkrst::ClkRstSession clkrst_session; + R_ABORT_UNLESS(clkrst::OpenSession(std::addressof(clkrst_session), m_device_code)); + ON_SCOPE_EXIT { clkrst::CloseSession(std::addressof(clkrst_session)); }; + + /* Reset the controller, setting clock rate to 408 MHz / 5 (to account for clock divisor). */ + /* NOTE: Nintendo does not check result for any of these calls. */ + clkrst::SetResetAsserted(std::addressof(clkrst_session)); + clkrst::SetClockRate(std::addressof(clkrst_session), 408'000'000 / (4 + 1)); + clkrst::SetResetDeasserted(std::addressof(clkrst_session)); + } + } + + void I2cBusAccessor::ClearBus() const { + /* Try to clear the bus up to three times. */ + constexpr int MaxRetryCount = 3; + constexpr int BusyLoopMicroSeconds = 1000; + + int try_count = 0; + bool need_retry; + do { + /* Update trackers. */ + ++try_count; + need_retry = false; + + /* Reset the controller. */ + this->ResetController(); + + /* Configure the sclk threshold for bus clear config. */ + reg::Write(m_registers->bus_clear_config, I2C_REG_BITS_VALUE(BUS_CLEAR_CONFIG_BC_SCLK_THRESHOLD, 9)); + + /* Set stop cond and terminate in bus clear config. */ + reg::ReadWrite(m_registers->bus_clear_config, I2C_REG_BITS_ENUM(BUS_CLEAR_CONFIG_BC_STOP_COND, STOP)); + reg::ReadWrite(m_registers->bus_clear_config, I2C_REG_BITS_ENUM(BUS_CLEAR_CONFIG_BC_TERMINATE, IMMEDIATE)); + + /* Set master config load, busy loop up to 1ms for it to take. */ + reg::ReadWrite(m_registers->config_load, I2C_REG_BITS_ENUM(CONFIG_LOAD_MSTR_CONFIG_LOAD, ENABLE)); + + const os::Tick start_tick_a = os::GetSystemTick(); + while (reg::HasValue(m_registers->config_load, I2C_REG_BITS_ENUM(CONFIG_LOAD_MSTR_CONFIG_LOAD, ENABLE))) { + if ((os::GetSystemTick() - start_tick_a).ToTimeSpan().GetMicroSeconds() > BusyLoopMicroSeconds) { + need_retry = true; + break; + } + } + + if (need_retry) { + continue; + } + + /* Set bus clear enable, wait up to 1ms for it to take. */ + reg::ReadWrite(m_registers->bus_clear_config, I2C_REG_BITS_ENUM(BUS_CLEAR_CONFIG_BC_ENABLE, ENABLE)); + + const os::Tick start_tick_b = os::GetSystemTick(); + while (reg::HasValue(m_registers->bus_clear_config, I2C_REG_BITS_ENUM(BUS_CLEAR_CONFIG_BC_ENABLE, ENABLE))) { + if ((os::GetSystemTick() - start_tick_b).ToTimeSpan().GetMicroSeconds() > BusyLoopMicroSeconds) { + need_retry = true; + break; + } + } + + if (need_retry) { + continue; + } + + /* Wait up to 1ms for the bus clear to complete. */ + const os::Tick start_tick_c = os::GetSystemTick(); + while (reg::HasValue(m_registers->bus_clear_status, I2C_REG_BITS_ENUM(BUS_CLEAR_STATUS_BC_STATUS, NOT_CLEARED))) { + if ((os::GetSystemTick() - start_tick_c).ToTimeSpan().GetMicroSeconds() > BusyLoopMicroSeconds) { + need_retry = true; + break; + } + } + + if (need_retry) { + continue; + } + } while (try_count < MaxRetryCount && need_retry); + } + + void I2cBusAccessor::SetClockRegisters(SpeedMode speed_mode) { + /* Determine parameters for the speed mode. */ + u32 t_high, t_low, clk_div, debounce, src_div; + bool high_speed = false; + + if (m_is_power_bus) { + t_high = 0x02; + t_low = 0x04; + clk_div = 0x05; + debounce = 0x02; + src_div = 0; /* unused */ + } else { + switch (speed_mode) { + case SpeedMode_Standard: + t_high = 0x02; + t_low = 0x04; + clk_div = 0x19; + debounce = 0x02; + src_div = 0x13; + break; + case SpeedMode_Fast: + t_high = 0x02; + t_low = 0x04; + clk_div = 0x19; + debounce = 0x02; + src_div = 0x04; + break; + case SpeedMode_FastPlus: + t_high = 0x02; + t_low = 0x04; + clk_div = 0x10; + debounce = 0x00; + src_div = 0x02; + break; + case SpeedMode_HighSpeed: + t_high = 0x03; + t_low = 0x08; + clk_div = 0x02; + debounce = 0x00; + src_div = 0x02; + high_speed = true; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + /* Write the clock divisors. */ + if (high_speed) { + reg::Write(m_registers->hs_interface_timing_0, I2C_REG_BITS_VALUE(HS_INTERFACE_TIMING_0_HS_THIGH, t_high), + I2C_REG_BITS_VALUE(HS_INTERFACE_TIMING_0_HS_TLOW, t_low)); + + reg::Write(m_registers->clk_divisor_register, I2C_REG_BITS_VALUE(CLK_DIVISOR_REGISTER_HSMODE, clk_div)); + } else { + reg::Write(m_registers->interface_timing_0, I2C_REG_BITS_VALUE(INTERFACE_TIMING_0_THIGH, t_high), + I2C_REG_BITS_VALUE(INTERFACE_TIMING_0_TLOW, t_low)); + + reg::Write(m_registers->clk_divisor_register, I2C_REG_BITS_VALUE(CLK_DIVISOR_REGISTER_STD_FAST_MODE, clk_div)); + } + + /* Configure debounce. */ + reg::Write(m_registers->cnfg, I2C_REG_BITS_VALUE(I2C_CNFG_DEBOUNCE_CNT, debounce)); + reg::Read(m_registers->cnfg); + + /* Set the clock rate, if we should. */ + if (!m_is_power_bus) { + /* Open a clkrst session. */ + clkrst::ClkRstSession clkrst_session; + R_ABORT_UNLESS(clkrst::OpenSession(std::addressof(clkrst_session), m_device_code)); + ON_SCOPE_EXIT { clkrst::CloseSession(std::addressof(clkrst_session)); }; + + /* Reset the controller, setting clock rate to 408 MHz / (src_div + 1). */ + /* NOTE: Nintendo does not check result for any of these calls. */ + clkrst::SetResetAsserted(std::addressof(clkrst_session)); + clkrst::SetClockRate(std::addressof(clkrst_session), 408'000'000 / (src_div + 1)); + clkrst::SetResetDeasserted(std::addressof(clkrst_session)); + } + } + + void I2cBusAccessor::SetPacketModeRegisters() { + /* Set packet mode enable. */ + reg::ReadWrite(m_registers->cnfg, I2C_REG_BITS_ENUM(I2C_CNFG_PACKET_MODE_EN, GO)); + + /* Set master config load. */ + reg::ReadWrite(m_registers->config_load, I2C_REG_BITS_ENUM(CONFIG_LOAD_MSTR_CONFIG_LOAD, ENABLE)); + + /* Set tx/fifo triggers to default (maximum values). */ + reg::Write(m_registers->fifo_control, I2C_REG_BITS_VALUE(FIFO_CONTROL_RX_FIFO_TRIG, 7), + I2C_REG_BITS_VALUE(FIFO_CONTROL_TX_FIFO_TRIG, 7)); + } + + Result I2cBusAccessor::FlushFifos() { + /* Flush the fifo. */ + reg::Write(m_registers->fifo_control, I2C_REG_BITS_VALUE(FIFO_CONTROL_RX_FIFO_TRIG, 7), + I2C_REG_BITS_VALUE(FIFO_CONTROL_TX_FIFO_TRIG, 7), + I2C_REG_BITS_ENUM (FIFO_CONTROL_RX_FIFO_FLUSH, SET), + I2C_REG_BITS_ENUM (FIFO_CONTROL_TX_FIFO_FLUSH, SET)); + + /* Wait up to 5 ms for the flush to complete. */ + int count = 0; + while (!reg::HasValue(m_registers->fifo_control, I2C_REG_BITS_ENUM(FIFO_CONTROL_FIFO_FLUSH, RX_UNSET_TX_UNSET))) { + R_UNLESS((++count < 5), i2c::ResultBusBusy()); + os::SleepThread(TimeSpan::FromMilliSeconds(1)); + } + + R_SUCCEED(); + } + + Result I2cBusAccessor::GetTransactionResult() const { + /* Get packet status/interrupt status. */ + volatile u32 packet_status = reg::Read(m_registers->packet_transfer_status); + volatile u32 interrupt_status = reg::Read(m_registers->interrupt_status_register); + + /* Check for ack. */ + R_UNLESS(reg::HasValue(packet_status, I2C_REG_BITS_ENUM(PACKET_TRANSFER_STATUS_NOACK_FOR_DATA, UNSET)), i2c::ResultNoAck()); + R_UNLESS(reg::HasValue(packet_status, I2C_REG_BITS_ENUM(PACKET_TRANSFER_STATUS_NOACK_FOR_ADDR, UNSET)), i2c::ResultNoAck()); + R_UNLESS(reg::HasValue(interrupt_status, I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_NOACK, UNSET)), i2c::ResultNoAck()); + + /* If we lost arbitration, we'll need to clear the bus. */ + auto clear_guard = SCOPE_GUARD { this->ClearBus(); }; + + /* Check for arb lost. */ + R_UNLESS(reg::HasValue(packet_status, I2C_REG_BITS_ENUM(PACKET_TRANSFER_STATUS_ARB_LOST, UNSET)), i2c::ResultBusBusy()); + R_UNLESS(reg::HasValue(interrupt_status, I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_ARB_LOST, UNSET)), i2c::ResultBusBusy()); + + clear_guard.Cancel(); + R_SUCCEED(); + } + + void I2cBusAccessor::HandleTransactionError(Result result) { + R_TRY_CATCH(result) { + R_CATCH(i2c::ResultNoAck, i2c::ResultBusBusy) { + /* Reset the controller. */ + this->ResetController(); + + /* Set clock registers. */ + this->SetClockRegisters(m_speed_mode); + + /* Set packet mode registers. */ + this->SetPacketModeRegisters(); + + /* Flush fifos. */ + this->FlushFifos(); + } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/impl/i2c_bus_accessor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/impl/i2c_bus_accessor.hpp new file mode 100644 index 00000000..f2c5046a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/impl/i2c_bus_accessor.hpp @@ -0,0 +1,137 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "i2c_i2c_registers.hpp" + +namespace ams::i2c::driver::board::nintendo::nx::impl { + + class I2cBusAccessor : public ::ams::i2c::driver::II2cDriver { + NON_COPYABLE(I2cBusAccessor); + NON_MOVEABLE(I2cBusAccessor); + AMS_DDSF_CASTABLE_TRAITS(ams::i2c::driver::board::nintendo::nx::impl::I2cBusAccessor, ::ams::i2c::driver::II2cDriver); + private: + enum class State { + NotInitialized = 0, + Initializing = 1, + Initialized = 2, + Suspended = 3, + PowerBusSuspended = 4, + }; + + enum Xfer { + Xfer_Write = 0, + Xfer_Read = 1, + }; + private: + volatile I2cRegisters *m_registers; + SpeedMode m_speed_mode; + os::InterruptEventType m_interrupt_event; + int m_user_count; + os::SdkMutex m_user_count_mutex; + os::SdkMutex m_register_mutex; + regulator::RegulatorSession m_regulator_session; + bool m_has_regulator_session; + State m_state; + os::SdkMutex m_transaction_order_mutex; + bool m_is_power_bus; + dd::PhysicalAddress m_registers_phys_addr; + size_t m_registers_size; + os::InterruptName m_interrupt_name; + DeviceCode m_device_code; + util::IntrusiveListNode m_bus_accessor_list_node; + public: + using BusAccessorListTraits = util::IntrusiveListMemberTraits<&I2cBusAccessor::m_bus_accessor_list_node>; + using BusAccessorList = typename BusAccessorListTraits::ListType; + friend class util::IntrusiveList<I2cBusAccessor, util::IntrusiveListMemberTraits<&I2cBusAccessor::m_bus_accessor_list_node>>; + public: + I2cBusAccessor() + : m_registers(nullptr), m_speed_mode(SpeedMode_Fast), m_user_count(0), m_user_count_mutex(), + m_register_mutex(), m_has_regulator_session(false), m_state(State::NotInitialized), m_transaction_order_mutex(), + m_is_power_bus(false), m_registers_phys_addr(0), m_registers_size(0), m_interrupt_name(), m_device_code(-1), m_bus_accessor_list_node() + { + /* ... */ + } + + void Initialize(dd::PhysicalAddress reg_paddr, size_t reg_size, os::InterruptName intr, bool pb, SpeedMode sm); + void RegisterDeviceCode(DeviceCode device_code); + + SpeedMode GetSpeedMode() const { return m_speed_mode; } + dd::PhysicalAddress GetRegistersPhysicalAddress() const { return m_registers_phys_addr; } + size_t GetRegistersSize() const { return m_registers_size; } + os::InterruptName GetInterruptName() const { return m_interrupt_name; } + private: + Result TryOpenRegulatorSession(); + + void ExecuteInitialConfig(); + + Result Send(const u8 *src, size_t src_size, TransactionOption option, u16 slave_address, AddressingMode addressing_mode); + Result Receive(u8 *dst, size_t dst_size, TransactionOption option, u16 slave_address, AddressingMode addressing_mode); + + void WriteHeader(Xfer xfer, size_t size, TransactionOption option, u16 slave_address, AddressingMode addressing_mode); + + void ResetController() const; + void ClearBus() const; + void SetClockRegisters(SpeedMode speed_mode); + void SetPacketModeRegisters(); + + Result FlushFifos(); + + Result GetTransactionResult() const; + void HandleTransactionError(Result result); + + void DisableInterruptMask() { + reg::Write(m_registers->interrupt_mask_register, 0); + reg::Read(m_registers->interrupt_mask_register); + } + + Result CheckAndHandleError() { + const Result result = this->GetTransactionResult(); + this->HandleTransactionError(result); + + if (R_FAILED(result)) { + this->DisableInterruptMask(); + os::ClearInterruptEvent(std::addressof(m_interrupt_event)); + } + + R_RETURN(result); + } + public: + virtual void InitializeDriver() override; + virtual void FinalizeDriver() override; + + virtual Result InitializeDevice(I2cDeviceProperty *device) override; + virtual void FinalizeDevice(I2cDeviceProperty *device) override; + + virtual Result Send(I2cDeviceProperty *device, const void *src, size_t src_size, TransactionOption option) override; + virtual Result Receive(void *dst, size_t dst_size, I2cDeviceProperty *device, TransactionOption option) override; + + virtual os::SdkMutex &GetTransactionOrderMutex() override { + return m_transaction_order_mutex; + } + + virtual void SuspendBus() override; + virtual void SuspendPowerBus() override; + + virtual void ResumeBus() override; + virtual void ResumePowerBus() override; + + virtual const DeviceCode &GetDeviceCode() const override { + return m_device_code; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/impl/i2c_bus_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/impl/i2c_bus_manager.hpp new file mode 100644 index 00000000..385fa96a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/impl/i2c_bus_manager.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "i2c_bus_accessor.hpp" +#include "i2c_i_allocator.hpp" + +namespace ams::i2c::driver::board::nintendo::nx::impl { + + class I2cBusAccessorManager : public IAllocator<I2cBusAccessor::BusAccessorList> { + public: + using IAllocator<I2cBusAccessor::BusAccessorList>::IAllocator; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/impl/i2c_device_property_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/impl/i2c_device_property_manager.hpp new file mode 100644 index 00000000..255b2d96 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/impl/i2c_device_property_manager.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "i2c_i_allocator.hpp" + +namespace ams::i2c::driver::board::nintendo::nx::impl { + + class I2cDevicePropertyManager : public IAllocator<I2cDeviceProperty::DevicePropertyList> { + public: + using IAllocator<I2cDeviceProperty::DevicePropertyList>::IAllocator; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/impl/i2c_i2c_registers.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/impl/i2c_i2c_registers.hpp new file mode 100644 index 00000000..beec8d8a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/impl/i2c_i2c_registers.hpp @@ -0,0 +1,66 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::i2c::driver::board::nintendo::nx::impl { + + struct I2cRegisters { + volatile u32 cnfg; + volatile u32 cmd_addr0; + volatile u32 cmd_addr1; + volatile u32 cmd_data1; + volatile u32 cmd_data2; + volatile u32 _14; + volatile u32 _18; + volatile u32 status; + volatile u32 sl_cnfg; + volatile u32 sl_rcvd; + volatile u32 sl_status; + volatile u32 sl_addr1; + volatile u32 sl_addr2; + volatile u32 tlow_sext; + volatile u32 _38; + volatile u32 sl_delay_count; + volatile u32 sl_int_mask; + volatile u32 sl_int_source; + volatile u32 sl_int_set; + volatile u32 _4c; + volatile u32 tx_packet_fifo; + volatile u32 rx_fifo; + volatile u32 packet_transfer_status; + volatile u32 fifo_control; + volatile u32 fifo_status; + volatile u32 interrupt_mask_register; + volatile u32 interrupt_status_register; + volatile u32 clk_divisor_register; + volatile u32 interrupt_source_register; + volatile u32 interrupt_set_register; + volatile u32 slv_tx_packet_fifo; + volatile u32 slv_rx_fifo; + volatile u32 slv_packet_status; + volatile u32 bus_clear_config; + volatile u32 bus_clear_status; + volatile u32 config_load; + volatile u32 _90; + volatile u32 interface_timing_0; + volatile u32 interface_timing_1; + volatile u32 hs_interface_timing_0; + volatile u32 hs_interface_timing_1; + }; + static_assert(sizeof(I2cRegisters) == 0xA4); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/impl/i2c_i_allocator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/impl/i2c_i_allocator.hpp new file mode 100644 index 00000000..d1284c4f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/board/nintendo/nx/impl/i2c_i_allocator.hpp @@ -0,0 +1,81 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::i2c::driver::board::nintendo::nx::impl { + + template<typename ListType> + class IAllocator { + NON_COPYABLE(IAllocator); + NON_MOVEABLE(IAllocator); + private: + using T = typename ListType::value_type; + private: + ams::MemoryResource *m_memory_resource; + ListType m_list; + mutable os::SdkMutex m_list_lock; + public: + IAllocator(ams::MemoryResource *mr) : m_memory_resource(mr), m_list(), m_list_lock() { /* ... */ } + + ~IAllocator() { + std::scoped_lock lk(m_list_lock); + + /* Remove all entries. */ + auto it = m_list.begin(); + while (it != m_list.end()) { + T *obj = std::addressof(*it); + it = m_list.erase(it); + + std::destroy_at(obj); + m_memory_resource->Deallocate(obj, sizeof(T)); + } + } + + template<typename ...Args> + T *Allocate(Args &&...args) { + std::scoped_lock lk(m_list_lock); + + /* Allocate space for the object. */ + void *storage = m_memory_resource->Allocate(sizeof(T), alignof(T)); + AMS_ABORT_UNLESS(storage != nullptr); + + /* Construct the object. */ + T *t = std::construct_at(static_cast<T *>(storage), std::forward<Args>(args)...); + + /* Link the object into our list. */ + m_list.push_back(*t); + + return t; + } + + template<typename F> + T *Find(F f) { + std::scoped_lock lk(m_list_lock); + + for (T &it : m_list) { + if (f(static_cast<const T &>(it))) { + return std::addressof(it); + } + } + + return nullptr; + } + + /* TODO: Support free */ + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/i2c_driver_bus_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/i2c_driver_bus_api.cpp new file mode 100644 index 00000000..fcdea948 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/i2c_driver_bus_api.cpp @@ -0,0 +1,85 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#include "impl/i2c_driver_core.hpp" + +namespace ams::i2c::driver { + + namespace { + + constexpr inline int DefaultRetryCount = 3; + constexpr inline TimeSpan DefaultRetryInterval = TimeSpan::FromMilliSeconds(5); + + Result OpenSessionImpl(I2cSession *out, I2cDeviceProperty *device) { + /* Construct the session. */ + auto *session = std::construct_at(std::addressof(impl::GetI2cSessionImpl(*out)), DefaultRetryCount, DefaultRetryInterval); + ON_RESULT_FAILURE { std::destroy_at(session); }; + + /* Open the session. */ + R_RETURN(session->Open(device, ddsf::AccessMode_ReadWrite)); + } + + } + + Result OpenSession(I2cSession *out, DeviceCode device_code) { + AMS_ASSERT(out != nullptr); + + /* Find the device. */ + I2cDeviceProperty *device = nullptr; + R_TRY(impl::FindDevice(std::addressof(device), device_code)); + AMS_ASSERT(device != nullptr); + + /* Open the session. */ + R_RETURN(OpenSessionImpl(out, device)); + } + + void CloseSession(I2cSession &session) { + std::destroy_at(std::addressof(impl::GetOpenI2cSessionImpl(session))); + } + + Result Send(I2cSession &session, const void *src, size_t src_size, TransactionOption option) { + AMS_ASSERT(src != nullptr); + AMS_ABORT_UNLESS(src_size > 0); + + R_RETURN(impl::GetOpenI2cSessionImpl(session).Send(src, src_size, option)); + } + + Result Receive(void *dst, size_t dst_size, I2cSession &session, TransactionOption option) { + AMS_ASSERT(dst != nullptr); + AMS_ABORT_UNLESS(dst_size > 0); + + R_RETURN(impl::GetOpenI2cSessionImpl(session).Receive(dst, dst_size, option)); + } + + Result ExecuteCommandList(void *dst, size_t dst_size, I2cSession &session, const void *src, size_t src_size) { + AMS_ASSERT(src != nullptr); + AMS_ASSERT(dst != nullptr); + + AMS_ABORT_UNLESS(src_size > 0); + AMS_ABORT_UNLESS(dst_size > 0); + + R_RETURN(impl::GetOpenI2cSessionImpl(session).ExecuteCommandList(dst, dst_size, src, src_size)); + } + + Result SetRetryPolicy(I2cSession &session, int max_retry_count, int retry_interval_us) { + AMS_ASSERT(max_retry_count > 0); + AMS_ASSERT(retry_interval_us > 0); + + R_RETURN(impl::GetOpenI2cSessionImpl(session).SetRetryPolicy(max_retry_count, retry_interval_us)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/i2c_driver_client_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/i2c_driver_client_api.cpp new file mode 100644 index 00000000..22acc84b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/i2c_driver_client_api.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#include "impl/i2c_driver_core.hpp" + +namespace ams::i2c::driver { + + void Initialize() { + return impl::InitializeDrivers(); + } + + void Finalize() { + return impl::FinalizeDrivers(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/i2c_driver_service_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/i2c_driver_service_api.cpp new file mode 100644 index 00000000..f24867fe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/i2c_driver_service_api.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#include "impl/i2c_driver_core.hpp" + +namespace ams::i2c::driver { + + void RegisterDriver(II2cDriver *driver) { + return impl::RegisterDriver(driver); + } + + void UnregisterDriver(II2cDriver *driver) { + return impl::UnregisterDriver(driver); + } + + Result RegisterDeviceCode(DeviceCode device_code, I2cDeviceProperty *device) { + R_RETURN(impl::RegisterDeviceCode(device_code, device)); + } + + bool UnregisterDeviceCode(DeviceCode device_code) { + return impl::UnregisterDeviceCode(device_code); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/impl/i2c_driver_core.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/impl/i2c_driver_core.cpp new file mode 100644 index 00000000..167b9894 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/impl/i2c_driver_core.cpp @@ -0,0 +1,131 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "i2c_driver_core.hpp" + +namespace ams::i2c::driver::impl { + + namespace { + + constinit os::SdkMutex g_init_mutex; + constinit int g_init_count = 0; + + i2c::driver::II2cDriver::List &GetI2cDriverList() { + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(i2c::driver::II2cDriver::List, s_driver_list); + return s_driver_list; + } + + ddsf::DeviceCodeEntryManager &GetDeviceCodeEntryManager() { + AMS_FUNCTION_LOCAL_STATIC(ddsf::DeviceCodeEntryManager, s_device_code_entry_manager, ddsf::GetDeviceCodeEntryHolderMemoryResource()); + + return s_device_code_entry_manager; + } + + } + + + void InitializeDrivers() { + std::scoped_lock lk(g_init_mutex); + + /* Initialize all registered drivers, if this is our first initialization. */ + if ((g_init_count++) == 0) { + for (auto &driver : GetI2cDriverList()) { + driver.SafeCastTo<II2cDriver>().InitializeDriver(); + } + } + } + + void FinalizeDrivers() { + std::scoped_lock lk(g_init_mutex); + + /* If we have no remaining sessions, close. */ + if ((--g_init_count) == 0) { + /* Reset all device code entries. */ + GetDeviceCodeEntryManager().Reset(); + + /* Finalize all drivers. */ + for (auto &driver : GetI2cDriverList()) { + driver.SafeCastTo<II2cDriver>().FinalizeDriver(); + } + } + } + + void RegisterDriver(II2cDriver *driver) { + AMS_ASSERT(driver != nullptr); + GetI2cDriverList().push_back(*driver); + } + + void UnregisterDriver(II2cDriver *driver) { + AMS_ASSERT(driver != nullptr); + if (driver->IsLinkedToList()) { + auto &list = GetI2cDriverList(); + list.erase(list.iterator_to(*driver)); + } + } + + Result RegisterDeviceCode(DeviceCode device_code, I2cDeviceProperty *device) { + AMS_ASSERT(device != nullptr); + R_TRY(GetDeviceCodeEntryManager().Add(device_code, device)); + R_SUCCEED(); + } + + bool UnregisterDeviceCode(DeviceCode device_code) { + return GetDeviceCodeEntryManager().Remove(device_code); + } + + Result FindDevice(I2cDeviceProperty **out, DeviceCode device_code) { + /* Validate output. */ + AMS_ASSERT(out != nullptr); + + /* Find the device. */ + ddsf::IDevice *device; + R_TRY(GetDeviceCodeEntryManager().FindDevice(std::addressof(device), device_code)); + + /* Set output. */ + *out = device->SafeCastToPointer<I2cDeviceProperty>(); + R_SUCCEED(); + } + + Result FindDeviceByBusIndexAndAddress(I2cDeviceProperty **out, i2c::I2cBus bus_index, u16 slave_address) { + /* Validate output. */ + AMS_ASSERT(out != nullptr); + + /* Convert the bus index to a device code. */ + const DeviceCode device_code = ConvertToDeviceCode(bus_index); + + /* Find the device. */ + bool found = false; + GetDeviceCodeEntryManager().ForEachEntry([&](ddsf::DeviceCodeEntry &entry) -> bool { + /* Convert the entry to an I2cDeviceProperty. */ + auto &device = entry.GetDevice().SafeCastTo<I2cDeviceProperty>(); + auto &driver = device.GetDriver().SafeCastTo<II2cDriver>(); + + /* Check if the device is the one we're looking for. */ + if (driver.GetDeviceCode() == device_code && device.GetAddress() == slave_address) { + found = true; + *out = std::addressof(device); + return false; + } + return true; + }); + + /* Check that we found the pad. */ + R_UNLESS(found, ddsf::ResultDeviceCodeNotFound()); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/impl/i2c_driver_core.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/impl/i2c_driver_core.hpp new file mode 100644 index 00000000..d1e8ea2a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/impl/i2c_driver_core.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::i2c::driver::impl { + + void InitializeDrivers(); + void FinalizeDrivers(); + + void RegisterDriver(II2cDriver *driver); + void UnregisterDriver(II2cDriver *driver); + + Result RegisterDeviceCode(DeviceCode device_code, I2cDeviceProperty *device); + bool UnregisterDeviceCode(DeviceCode device_code); + + Result FindDevice(I2cDeviceProperty **out, DeviceCode device_code); + Result FindDeviceByBusIndexAndAddress(I2cDeviceProperty **out, i2c::I2cBus bus_index, u16 slave_address); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/impl/i2c_i2c_session_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/impl/i2c_i2c_session_impl.cpp new file mode 100644 index 00000000..817e6ac4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/driver/impl/i2c_i2c_session_impl.cpp @@ -0,0 +1,205 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "i2c_driver_core.hpp" +#include "../../impl/i2c_command_list_format.hpp" + +namespace ams::i2c::driver::impl { + + namespace { + + constexpr TransactionOption EncodeTransactionOption(bool start, bool stop) { + return static_cast<TransactionOption>((start ? util::ToUnderlying(TransactionOption_StartCondition) : 0) | (stop ? util::ToUnderlying(TransactionOption_StopCondition) : 0)); + } + + } + + Result I2cSessionImpl::Open(I2cDeviceProperty *device, ddsf::AccessMode access_mode) { + AMS_ASSERT(device != nullptr); + + /* Check if we're the device's first session. */ + const bool first = !device->HasAnyOpenSession(); + + /* Open the session. */ + R_TRY(ddsf::OpenSession(device, this, access_mode)); + auto guard = SCOPE_GUARD { ddsf::CloseSession(this); }; + + /* If we're the first session, initialize the device. */ + if (first) { + R_TRY(device->GetDriver().SafeCastTo<II2cDriver>().InitializeDevice(device)); + } + + /* We're opened. */ + guard.Cancel(); + R_SUCCEED(); + } + + void I2cSessionImpl::Close() { + /* If we're not open, do nothing. */ + if (!this->IsOpen()) { + return; + } + + /* Get the device. */ + auto &device = this->GetDevice().SafeCastTo<I2cDeviceProperty>(); + + /* Close the session. */ + ddsf::CloseSession(this); + + /* If there are no remaining sessions, finalize the device. */ + if (!device.HasAnyOpenSession()) { + device.GetDriver().SafeCastTo<II2cDriver>().FinalizeDevice(std::addressof(device)); + } + } + + Result I2cSessionImpl::SendHandler(const u8 **cur_cmd, u8 **cur_dst) { + AMS_UNUSED(cur_dst); + + /* Read the header bytes. */ + const util::BitPack8 hdr0{*((*cur_cmd)++)}; + const util::BitPack8 hdr1{*((*cur_cmd)++)}; + + /* Decode the header. */ + const bool start = hdr0.Get<i2c::impl::SendCommandFormat::StartCondition>(); + const bool stop = hdr0.Get<i2c::impl::SendCommandFormat::StopCondition>(); + const size_t size = hdr1.Get<i2c::impl::SendCommandFormat::Size>(); + + /* Execute the transaction. */ + R_TRY(this->ExecuteTransactionWithRetry(nullptr, Command::Send, *cur_cmd, size, EncodeTransactionOption(start, stop))); + + /* Advance. */ + *cur_cmd += size; + + R_SUCCEED(); + } + + Result I2cSessionImpl::ReceiveHandler(const u8 **cur_cmd, u8 **cur_dst) { + /* Read the header bytes. */ + const util::BitPack8 hdr0{*((*cur_cmd)++)}; + const util::BitPack8 hdr1{*((*cur_cmd)++)}; + + /* Decode the header. */ + const bool start = hdr0.Get<i2c::impl::ReceiveCommandFormat::StartCondition>(); + const bool stop = hdr0.Get<i2c::impl::ReceiveCommandFormat::StopCondition>(); + const size_t size = hdr1.Get<i2c::impl::ReceiveCommandFormat::Size>(); + + /* Execute the transaction. */ + R_TRY(this->ExecuteTransactionWithRetry(*cur_dst, Command::Receive, nullptr, size, EncodeTransactionOption(start, stop))); + + /* Advance. */ + *cur_dst += size; + + R_SUCCEED(); + } + + Result I2cSessionImpl::ExtensionHandler(const u8 **cur_cmd, u8 **cur_dst) { + AMS_UNUSED(cur_dst); + + /* Read the header bytes. */ + const util::BitPack8 hdr0{*((*cur_cmd)++)}; + + /* Execute the subcommand. */ + switch (hdr0.Get<i2c::impl::CommonCommandFormat::SubCommandId>()) { + case i2c::impl::SubCommandId_Sleep: + { + const util::BitPack8 param{*((*cur_cmd)++)}; + + os::SleepThread(TimeSpan::FromMicroSeconds(param.Get<i2c::impl::SleepCommandFormat::MicroSeconds>())); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + R_SUCCEED(); + } + + Result I2cSessionImpl::ExecuteTransactionWithRetry(void *dst, Command command, const void *src, size_t size, TransactionOption option) { + /* Get the device. */ + auto &device = GetDevice().SafeCastTo<I2cDeviceProperty>(); + + /* Repeatedly try to execute the transaction. */ + int retry_count = 0; + while (true) { + /* Execute the transaction. */ + Result result; + switch (command) { + case Command::Send: result = device.GetDriver().SafeCastTo<II2cDriver>().Send(std::addressof(device), src, size, option); break; + case Command::Receive: result = device.GetDriver().SafeCastTo<II2cDriver>().Receive(dst, size, std::addressof(device), option); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* If we timed out, retry up to our max retry count. */ + R_TRY_CATCH(result) { + R_CATCH(i2c::ResultTimeout) { + if ((++retry_count) <= m_max_retry_count) { + os::SleepThread(m_retry_interval); + continue; + } + R_THROW(i2c::ResultBusBusy()); + } + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + } + + Result I2cSessionImpl::Send(const void *src, size_t src_size, TransactionOption option) { + /* Acquire exclusive access to the device. */ + std::scoped_lock lk(this->GetDevice().SafeCastTo<I2cDeviceProperty>().GetDriver().SafeCastTo<II2cDriver>().GetTransactionOrderMutex()); + + R_RETURN(this->ExecuteTransactionWithRetry(nullptr, Command::Send, src, src_size, option)); + } + + Result I2cSessionImpl::Receive(void *dst, size_t dst_size, TransactionOption option) { + /* Acquire exclusive access to the device. */ + std::scoped_lock lk(this->GetDevice().SafeCastTo<I2cDeviceProperty>().GetDriver().SafeCastTo<II2cDriver>().GetTransactionOrderMutex()); + + R_RETURN(this->ExecuteTransactionWithRetry(dst, Command::Receive, nullptr, dst_size, option)); + } + + Result I2cSessionImpl::ExecuteCommandList(void *dst, size_t dst_size, const void *src, size_t src_size) { + AMS_UNUSED(dst_size); + + /* Acquire exclusive access to the device. */ + std::scoped_lock lk(this->GetDevice().SafeCastTo<I2cDeviceProperty>().GetDriver().SafeCastTo<II2cDriver>().GetTransactionOrderMutex()); + + /* Prepare to process the command list. */ + const u8 * cur_u8 = static_cast<const u8 *>(src); + const u8 * const end_u8 = cur_u8 + src_size; + u8 * dst_u8 = static_cast<u8 *>(dst); + + /* Process commands. */ + while (cur_u8 < end_u8) { + const util::BitPack8 hdr{*cur_u8}; + + switch (hdr.Get<i2c::impl::CommonCommandFormat::CommandId>()) { + case i2c::impl::CommandId_Send: R_TRY(this->SendHandler(std::addressof(cur_u8), std::addressof(dst_u8))); break; + case i2c::impl::CommandId_Receive: R_TRY(this->ReceiveHandler(std::addressof(cur_u8), std::addressof(dst_u8))); break; + case i2c::impl::CommandId_Extension: R_TRY(this->ExtensionHandler(std::addressof(cur_u8), std::addressof(dst_u8))); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + R_SUCCEED(); + } + + Result I2cSessionImpl::SetRetryPolicy(int mr, int interval_us) { + m_max_retry_count = mr; + m_retry_interval = TimeSpan::FromMicroSeconds(interval_us); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/i2c_client_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/i2c_client_api.cpp new file mode 100644 index 00000000..b06b98ab --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/i2c_client_api.cpp @@ -0,0 +1,156 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::i2c { + + namespace { + + constinit os::SdkMutex g_init_mutex; + constinit int g_initialize_count = 0; + + constinit os::SdkMutex g_i2c_mutex; + ams::sf::SharedPointer<sf::IManager> g_i2c_manager; + constinit int g_i2c_count = 0; + + constinit os::SdkMutex g_i2c_pcv_mutex; + ams::sf::SharedPointer<sf::IManager> g_i2c_pcv_manager; + constinit int g_i2c_pcv_count = 0; + + i2c::sf::ISession *GetInterface(const I2cSession &session) { + AMS_ASSERT(session._session != nullptr); + return static_cast<i2c::sf::ISession *>(session._session); + } + + ams::sf::SharedPointer<sf::IManager> GetManager(DeviceCode device_code) { + if (IsPowerBusDeviceCode(device_code)) { + return g_i2c_pcv_manager; + } else { + return g_i2c_manager; + } + } + + } + + void InitializeWith(ams::sf::SharedPointer<i2c::sf::IManager> sp, ams::sf::SharedPointer<i2c::sf::IManager> sp_pcv) { + std::scoped_lock lk(g_init_mutex); + + AMS_ABORT_UNLESS(g_initialize_count == 0); + + { + std::scoped_lock lk(g_i2c_mutex); + g_i2c_manager = std::move(sp); + AMS_ABORT_UNLESS(g_i2c_count == 0); + g_i2c_count = 1; + } + + { + std::scoped_lock lk(g_i2c_pcv_mutex); + g_i2c_pcv_manager = std::move(sp_pcv); + AMS_ABORT_UNLESS(g_i2c_pcv_count == 0); + g_i2c_pcv_count = 1; + } + + g_initialize_count = 1; + } + + void InitializeEmpty() { + std::scoped_lock lk(g_init_mutex); + + ++g_initialize_count; + } + + void Finalize() { + std::scoped_lock lk(g_init_mutex); + + AMS_ASSERT(g_initialize_count > 0); + + if ((--g_initialize_count) == 0) { + { + std::scoped_lock lk(g_i2c_mutex); + AMS_ASSERT(g_i2c_count > 0); + if (g_i2c_count > 0) { + if ((--g_i2c_count) == 0) { + g_i2c_manager = nullptr; + } + } + } + { + std::scoped_lock lk(g_i2c_pcv_mutex); + AMS_ASSERT(g_i2c_pcv_count > 0); + if (g_i2c_pcv_count > 0) { + if ((--g_i2c_pcv_count) == 0) { + g_i2c_manager = nullptr; + } + } + } + } + } + + Result OpenSession(I2cSession *out, DeviceCode device_code) { + /* Get manager for the device. */ + auto manager = GetManager(device_code); + + /* Get the session. */ + ams::sf::SharedPointer<i2c::sf::ISession> session; + { + if (hos::GetVersion() >= hos::Version_6_0_0) { + R_TRY(manager->OpenSession2(std::addressof(session), device_code)); + } else { + R_TRY(manager->OpenSession(std::addressof(session), ConvertToI2cDevice(device_code))); + } + } + + /* Set output. */ + out->_session = session.Detach(); + + /* We succeeded. */ + R_SUCCEED(); + } + + void CloseSession(I2cSession &session) { + /* Close the session. */ + ams::sf::ReleaseSharedObject(GetInterface(session)); + session._session = nullptr; + } + + Result Send(const I2cSession &session, const void *src, size_t src_size, TransactionOption option) { + const ams::sf::InAutoSelectBuffer buf(src, src_size); + + R_RETURN(GetInterface(session)->Send(buf, option)); + } + + Result Receive(void *dst, size_t dst_size, const I2cSession &session, TransactionOption option) { + const ams::sf::OutAutoSelectBuffer buf(dst, dst_size); + + R_RETURN(GetInterface(session)->Receive(buf, option)); + } + + Result ExecuteCommandList(void *dst, size_t dst_size, const I2cSession &session, const void *src, size_t src_size) { + const ams::sf::OutAutoSelectBuffer buf(dst, dst_size); + const ams::sf::InPointerArray<i2c::I2cCommand> arr(static_cast<const i2c::I2cCommand *>(src), src_size); + + R_RETURN(GetInterface(session)->ExecuteCommandList(buf, arr)); + } + + void SetRetryPolicy(const I2cSession &session, int max_retry_count, int retry_interval_us) { + AMS_ASSERT(max_retry_count >= 0); + AMS_ASSERT(retry_interval_us >= 0); + + R_ABORT_UNLESS(GetInterface(session)->SetRetryPolicy(max_retry_count, retry_interval_us)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/i2c_command_list_formatter.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/i2c_command_list_formatter.cpp new file mode 100644 index 00000000..def92cdc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/i2c_command_list_formatter.cpp @@ -0,0 +1,101 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/i2c_command_list_format.hpp" + +namespace ams::i2c { + + Result CommandListFormatter::IsEnqueueAble(size_t sz) const { + R_UNLESS(m_command_list_length - m_current_index >= sz, i2c::ResultCommandListFull()); + R_SUCCEED(); + } + + Result CommandListFormatter::EnqueueReceiveCommand(i2c::TransactionOption option, size_t size) { + /* Check that we can enqueue the command. */ + constexpr size_t CommandLength = 2; + R_TRY(this->IsEnqueueAble(CommandLength)); + + /* Get the command list. */ + util::BitPack8 *cmd_list = static_cast<util::BitPack8 *>(m_command_list); + + /* Get references to the header. */ + auto &header0 = cmd_list[m_current_index++]; + auto &header1 = cmd_list[m_current_index++]; + + /* Set the header. */ + header0 = {}; + header0.Set<impl::CommonCommandFormat::CommandId>(impl::CommandId_Receive); + header0.Set<impl::ReceiveCommandFormat::StopCondition>((option & TransactionOption_StopCondition) != 0); + header0.Set<impl::ReceiveCommandFormat::StartCondition>((option & TransactionOption_StartCondition) != 0); + + header1 = {}; + header1.Set<impl::ReceiveCommandFormat::Size>(size); + + R_SUCCEED(); + } + + Result CommandListFormatter::EnqueueSendCommand(i2c::TransactionOption option, const void *src, size_t size) { + /* Check that we can enqueue the command. */ + constexpr size_t CommandLength = 2; + R_TRY(this->IsEnqueueAble(CommandLength + size)); + + /* Get the command list. */ + util::BitPack8 *cmd_list = static_cast<util::BitPack8 *>(m_command_list); + + /* Get references to the header. */ + auto &header0 = cmd_list[m_current_index++]; + auto &header1 = cmd_list[m_current_index++]; + + /* Set the header. */ + header0 = {}; + header0.Set<impl::CommonCommandFormat::CommandId>(impl::CommandId_Send); + header0.Set<impl::SendCommandFormat::StopCondition>((option & TransactionOption_StopCondition) != 0); + header0.Set<impl::SendCommandFormat::StartCondition>((option & TransactionOption_StartCondition) != 0); + + header1 = {}; + header1.Set<impl::SendCommandFormat::Size>(size); + + /* Copy the data we're sending. */ + std::memcpy(cmd_list + m_current_index, src, size); + m_current_index += size; + + R_SUCCEED(); + } + + Result CommandListFormatter::EnqueueSleepCommand(int us) { + /* Check that we can enqueue the command. */ + constexpr size_t CommandLength = 2; + R_TRY(this->IsEnqueueAble(CommandLength)); + + /* Get the command list. */ + util::BitPack8 *cmd_list = static_cast<util::BitPack8 *>(m_command_list); + + /* Get references to the header. */ + auto &header0 = cmd_list[m_current_index++]; + auto &header1 = cmd_list[m_current_index++]; + + /* Set the header. */ + header0 = {}; + header0.Set<impl::CommonCommandFormat::CommandId>(impl::CommandId_Extension); + header0.Set<impl::CommonCommandFormat::SubCommandId>(impl::SubCommandId_Sleep); + + header1 = {}; + header1.Set<impl::SleepCommandFormat::MicroSeconds>(us); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/impl/i2c_command_list_format.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/impl/i2c_command_list_format.hpp new file mode 100644 index 00000000..4892dfe7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/impl/i2c_command_list_format.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::i2c::impl { + + enum CommandId { + CommandId_Send = 0, + CommandId_Receive = 1, + CommandId_Extension = 2, + CommandId_Count = 3, + }; + + enum SubCommandId { + SubCommandId_Sleep = 0, + }; + + struct CommonCommandFormat { + using CommandId = util::BitPack8::Field<0, 2>; + using SubCommandId = util::BitPack8::Field<2, 6>; + }; + + struct ReceiveCommandFormat { + using StartCondition = util::BitPack8::Field<6, 1, bool>; + using StopCondition = util::BitPack8::Field<7, 1, bool>; + using Size = util::BitPack8::Field<0, 8>; + }; + + struct SendCommandFormat { + using StartCondition = util::BitPack8::Field<6, 1, bool>; + using StopCondition = util::BitPack8::Field<7, 1, bool>; + using Size = util::BitPack8::Field<0, 8>; + }; + + struct SleepCommandFormat { + using MicroSeconds = util::BitPack8::Field<0, 8>; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/server/i2c_server_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/server/i2c_server_api.cpp new file mode 100644 index 00000000..7b6e2e29 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/server/i2c_server_api.cpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "i2c_server_manager_impl.hpp" + +namespace ams::i2c::server { + + namespace { + + ams::sf::UnmanagedServiceObject<i2c::sf::IManager, i2c::server::ManagerImpl> g_manager_impl; + ams::sf::UnmanagedServiceObject<i2c::sf::IManager, i2c::server::ManagerImpl> g_pcv_manager_impl; + + } + + ams::sf::SharedPointer<i2c::sf::IManager> GetServiceObject() { + return g_manager_impl.GetShared(); + } + + ams::sf::SharedPointer<i2c::sf::IManager> GetServiceObjectPowerBus() { + return g_pcv_manager_impl.GetShared(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/server/i2c_server_manager_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/server/i2c_server_manager_impl.cpp new file mode 100644 index 00000000..94e85aff --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/server/i2c_server_manager_impl.cpp @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "i2c_server_manager_impl.hpp" + +namespace ams::i2c::server { + + ManagerImpl::ManagerImpl() { + m_heap_handle = lmem::CreateExpHeap(m_heap_buffer, sizeof(m_heap_buffer), lmem::CreateOption_None); + m_allocator.Attach(m_heap_handle); + } + + ManagerImpl::~ManagerImpl() { + lmem::DestroyExpHeap(m_heap_handle); + } + + Result ManagerImpl::OpenSessionForDev(ams::sf::Out<ams::sf::SharedPointer<i2c::sf::ISession>> out, s32 bus_idx, u16 slave_address, i2c::AddressingMode addressing_mode, i2c::SpeedMode speed_mode) { + /* TODO */ + AMS_UNUSED(out, bus_idx, slave_address, addressing_mode, speed_mode); + AMS_ABORT(); + } + + Result ManagerImpl::OpenSession(ams::sf::Out<ams::sf::SharedPointer<i2c::sf::ISession>> out, i2c::I2cDevice device) { + R_RETURN(this->OpenSession2(out, ConvertToDeviceCode(device))); + } + + Result ManagerImpl::HasDevice(ams::sf::Out<bool> out, i2c::I2cDevice device) { + /* TODO */ + AMS_UNUSED(out, device); + AMS_ABORT(); + } + + Result ManagerImpl::HasDeviceForDev(ams::sf::Out<bool> out, i2c::I2cDevice device) { + /* TODO */ + AMS_UNUSED(out, device); + AMS_ABORT(); + } + + Result ManagerImpl::OpenSession2(ams::sf::Out<ams::sf::SharedPointer<i2c::sf::ISession>> out, DeviceCode device_code) { + /* Allocate a session. */ + auto session = Factory::CreateSharedEmplaced<i2c::sf::ISession, SessionImpl>(std::addressof(m_allocator), this); + + /* Open the session. */ + R_TRY(session.GetImpl().OpenSession(device_code)); + + /* We succeeded. */ + *out = std::move(session); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/server/i2c_server_manager_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/server/i2c_server_manager_impl.hpp new file mode 100644 index 00000000..ca05071b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/server/i2c_server_manager_impl.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "i2c_server_session_impl.hpp" + +namespace ams::i2c::server { + + class ManagerImpl { + private: + using Allocator = ams::sf::ExpHeapAllocator; + using Factory = ams::sf::ObjectFactory<Allocator::Policy>; + private: + lmem::HeapHandle m_heap_handle; + Allocator m_allocator; + u8 m_heap_buffer[4_KB]; + public: + ManagerImpl(); + + ~ManagerImpl(); + public: + /* Actual commands. */ + Result OpenSessionForDev(ams::sf::Out<ams::sf::SharedPointer<i2c::sf::ISession>> out, s32 bus_idx, u16 slave_address, i2c::AddressingMode addressing_mode, i2c::SpeedMode speed_mode); + Result OpenSession(ams::sf::Out<ams::sf::SharedPointer<i2c::sf::ISession>> out, i2c::I2cDevice device); + Result HasDevice(ams::sf::Out<bool> out, i2c::I2cDevice device); + Result HasDeviceForDev(ams::sf::Out<bool> out, i2c::I2cDevice device); + Result OpenSession2(ams::sf::Out<ams::sf::SharedPointer<i2c::sf::ISession>> out, DeviceCode device_code); + }; + static_assert(i2c::sf::IsIManager<ManagerImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/server/i2c_server_session_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/server/i2c_server_session_impl.hpp new file mode 100644 index 00000000..78f18bbc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/i2c/server/i2c_server_session_impl.hpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::i2c::server { + + class ManagerImpl; + + class SessionImpl { + private: + ManagerImpl *m_parent; /* NOTE: this is an sf::SharedPointer<> in Nintendo's code. */ + i2c::driver::I2cSession m_internal_session; + bool m_has_session; + public: + explicit SessionImpl(ManagerImpl *p) : m_parent(p), m_has_session(false) { /* ... */ } + + ~SessionImpl() { + if (m_has_session) { + i2c::driver::CloseSession(m_internal_session); + } + } + + Result OpenSession(DeviceCode device_code) { + AMS_ABORT_UNLESS(!m_has_session); + + R_TRY(i2c::driver::OpenSession(std::addressof(m_internal_session), device_code)); + m_has_session = true; + R_SUCCEED(); + } + public: + /* Actual commands. */ + Result SendOld(const ams::sf::InBuffer &in_data, i2c::TransactionOption option) { + R_RETURN(i2c::driver::Send(m_internal_session, in_data.GetPointer(), in_data.GetSize(), option)); + } + + Result ReceiveOld(const ams::sf::OutBuffer &out_data, i2c::TransactionOption option) { + R_RETURN(i2c::driver::Receive(out_data.GetPointer(), out_data.GetSize(), m_internal_session, option)); + } + + Result ExecuteCommandListOld(const ams::sf::OutBuffer &rcv_buf, const ams::sf::InPointerArray<i2c::I2cCommand> &command_list){ + R_RETURN(i2c::driver::ExecuteCommandList(rcv_buf.GetPointer(), rcv_buf.GetSize(), m_internal_session, command_list.GetPointer(), command_list.GetSize() * sizeof(i2c::I2cCommand))); + } + + Result Send(const ams::sf::InAutoSelectBuffer &in_data, i2c::TransactionOption option) { + R_RETURN(i2c::driver::Send(m_internal_session, in_data.GetPointer(), in_data.GetSize(), option)); + } + + Result Receive(const ams::sf::OutAutoSelectBuffer &out_data, i2c::TransactionOption option) { + R_RETURN(i2c::driver::Receive(out_data.GetPointer(), out_data.GetSize(), m_internal_session, option)); + } + + Result ExecuteCommandList(const ams::sf::OutAutoSelectBuffer &rcv_buf, const ams::sf::InPointerArray<i2c::I2cCommand> &command_list) { + R_RETURN(i2c::driver::ExecuteCommandList(rcv_buf.GetPointer(), rcv_buf.GetSize(), m_internal_session, command_list.GetPointer(), command_list.GetSize() * sizeof(i2c::I2cCommand))); + } + + Result SetRetryPolicy(s32 max_retry_count, s32 retry_interval_us) { + R_RETURN(i2c::driver::SetRetryPolicy(m_internal_session, max_retry_count, retry_interval_us)); + } + }; + static_assert(i2c::sf::IsISession<SessionImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/init/init_libnx_shim.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/init/init_libnx_shim.os.horizon.cpp new file mode 100644 index 00000000..5a34821c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/init/init_libnx_shim.os.horizon.cpp @@ -0,0 +1,115 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +extern "C" { + + constinit u32 __nx_fs_num_sessions = 1; + constinit u32 __nx_applet_type = AppletType_None; + + constinit bool __nx_fsdev_support_cwd = false; + + extern int __system_argc; + extern char** __system_argv; + + alignas(16) constinit u8 __nx_exception_stack[::ams::os::MemoryPageSize]; + constinit u64 __nx_exception_stack_size = sizeof(__nx_exception_stack); + +} + +namespace ams { + + namespace hos { + + void InitializeForStratosphere(); + + } + + namespace init { + + void InitializeSystemModuleBeforeConstructors(); + + void InitializeSystemModule(); + void FinalizeSystemModule(); + + void Startup(); + + } + + void Main(); + +} + +namespace { + + constinit char *g_empty_argv = nullptr; + +} + +extern "C" void __libnx_exception_handler(ThreadExceptionDump *ctx) { + ::ams::CrashHandler(ctx); +} + +extern "C" void __libnx_initheap(void) { + /* Stratosphere system modules do not support newlib heap. */ +} + +extern "C" void __appInit(void) { + /* The very first thing all stratosphere code must do is initialize the os library. */ + ::ams::hos::InitializeForStratosphere(); + + /* Perform pre-C++ constructor init. */ + ::ams::init::InitializeSystemModuleBeforeConstructors(); +} + +extern "C" void __appExit(void) { + /* ... */ +} + +extern "C" void argvSetup(void) { + /* We don't use newlib argc/argv, so we can clear these. */ + __system_argc = 0; + __system_argv = std::addressof(g_empty_argv); +} + +extern "C" int main(int argc, char **argv) { + /* We don't use newlib argc/argv. */ + AMS_UNUSED(argc, argv); + + /* Perform remainder of logic with system module initialized. */ + { + ::ams::init::InitializeSystemModule(); + ON_SCOPE_EXIT { ::ams::init::FinalizeSystemModule(); }; + + /* Perform miscellaneous startup. */ + ::ams::init::Startup(); + + /* Invoke ams main. */ + ::ams::Main(); + } +} + +extern "C" WEAK_SYMBOL void *__libnx_alloc(size_t) { + AMS_ABORT("__libnx_alloc was called"); +} + +extern "C" WEAK_SYMBOL void *__libnx_aligned_alloc(size_t, size_t) { + AMS_ABORT("__libnx_aligned_alloc was called"); +} + +extern "C" WEAK_SYMBOL void __libnx_free(void *) { + AMS_ABORT("__libnx_free was called"); +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/init/init_malloc.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/init/init_malloc.cpp new file mode 100644 index 00000000..2d1850c1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/init/init_malloc.cpp @@ -0,0 +1,177 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +/* NOTE: If AMS_INIT_USE_STANDARD_ALLOCATOR_FOR_MALLOC is defined, the relevant os primitives should be hooked up. */ +#if defined(ATMOSPHERE_OS_HORIZON) + #define AMS_INIT_USE_STANDARD_ALLOCATOR_FOR_MALLOC +#elif defined(ATMOSPHERE_OS_WINDOWS) + //#define AMS_INIT_USE_STANDARD_ALLOCATOR_FOR_MALLOC +#elif defined(ATMOSPHERE_OS_LINUX) + //#define AMS_INIT_USE_STANDARD_ALLOCATOR_FOR_MALLOC +#elif defined(ATMOSPHERE_OS_MACOS) + //#define AMS_INIT_USE_STANDARD_ALLOCATOR_FOR_MALLOC +#else + #error "Unknown OS for enabling StandardAllocator backing impl for malloc" +#endif + +namespace ams::init { + + namespace { + + #if defined(AMS_INIT_USE_STANDARD_ALLOCATOR_FOR_MALLOC) + constinit void *g_malloc_region_address = nullptr; + constinit size_t g_malloc_region_size = 0; + + constinit util::TypedStorage<mem::StandardAllocator> g_malloc_allocator = {}; + #endif + + } + + void InitializeAllocator(void *address, size_t size, bool cache_enabled) { + #if defined(AMS_INIT_USE_STANDARD_ALLOCATOR_FOR_MALLOC) + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(g_malloc_region_size == 0); + AMS_ABORT_UNLESS(size > 0); + + /* Construct malloc allocator. */ + util::ConstructAt(g_malloc_allocator); + + /* Initialize allocator. */ + util::GetReference(g_malloc_allocator).Initialize(address, size, cache_enabled); + + /* Set malloc globals. */ + g_malloc_region_address = address; + g_malloc_region_size = size; + #else + AMS_UNUSED(address, size, cache_enabled); + #endif + } + + void InitializeAllocator(void *address, size_t size) { + return InitializeAllocator(address, size, false); + } + + void InitializeDefaultAllocator() { + /* TODO: What should default heap size be? This uses virtual address space memory. */ + return InitializeAllocator(nullptr, 128_MB, false); + } + + mem::StandardAllocator *GetAllocator() { + #if defined(AMS_INIT_USE_STANDARD_ALLOCATOR_FOR_MALLOC) + /* Check pre-conditions. */ + AMS_ASSERT(g_malloc_region_size > 0); + + return util::GetPointer(g_malloc_allocator); + #else + return nullptr; + #endif + } + +} + +#if defined(AMS_INIT_USE_STANDARD_ALLOCATOR_FOR_MALLOC) +extern "C" void *malloc(size_t size) { + /* We require that an allocator region exists. */ + if (::ams::init::g_malloc_region_size == 0) { + return nullptr; + } + + /* Try to allocate. */ + void *ptr = ::ams::util::GetReference(::ams::init::g_malloc_allocator).Allocate(size); + if (ptr == nullptr) { + errno = ENOMEM; + } + + return ptr; +} + +extern "C" void free(void *ptr) { + /* We require that an allocator region exists. */ + if (::ams::init::g_malloc_region_size == 0) { + return; + } + + if (ptr != nullptr) { + ::ams::util::GetReference(::ams::init::g_malloc_allocator).Free(ptr); + } +} + +extern "C" void *calloc(size_t num, size_t size) { + /* We require that an allocator region exists. */ + if (::ams::init::g_malloc_region_size == 0) { + return nullptr; + } + + /* Allocate the total needed space. */ + const size_t total = num * size; + void *ptr = std::malloc(total); + + /* Zero the memory if needed. */ + if (ptr != nullptr) { + std::memset(ptr, 0, total); + } else { + errno = ENOMEM; + } + + return ptr; +} + +extern "C" void *realloc(void *ptr, size_t new_size) { + /* We require that an allocator region exists. */ + if (::ams::init::g_malloc_region_size == 0) { + return nullptr; + } + + /* Try to reallocate. */ + void *r = ::ams::util::GetReference(::ams::init::g_malloc_allocator).Reallocate(ptr, new_size); + if (r == nullptr) { + errno = ENOMEM; + } + + return r; +} + +extern "C" void *aligned_alloc(size_t align, size_t size) { + /* We require that an allocator region exists. */ + if (::ams::init::g_malloc_region_size == 0) { + return nullptr; + } + + /* Try to allocate. */ + void *ptr = ::ams::util::GetReference(::ams::init::g_malloc_allocator).Allocate(size, align); + if (ptr == nullptr) { + errno = ENOMEM; + } + + return ptr; +} + +extern "C" size_t malloc_usable_size(void *ptr) { + /* We require that an allocator region exists. */ + if (::ams::init::g_malloc_region_size == 0) { + return 0; + } + + /* Try to get the usable size. */ + if (ptr == nullptr) { + errno = ENOMEM; + return 0; + } + + return ::ams::util::GetReference(::ams::init::g_malloc_allocator).GetSizeOf(ptr); +} +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/init/init_operator_new.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/init/init_operator_new.cpp new file mode 100644 index 00000000..8657f162 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/init/init_operator_new.cpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +WEAK_SYMBOL void *operator new(size_t size) { + return std::malloc(size); +} + +WEAK_SYMBOL void *operator new(size_t size, const std::nothrow_t &) noexcept { + return std::malloc(size); +} + +WEAK_SYMBOL void operator delete(void *p) noexcept { + return std::free(p); +} + +WEAK_SYMBOL void operator delete(void *p, size_t) noexcept { + return std::free(p); +} + +WEAK_SYMBOL void *operator new[](size_t size) { + return std::malloc(size); +} + +WEAK_SYMBOL void *operator new[](size_t size, const std::nothrow_t &) noexcept { + return std::malloc(size); +} + +WEAK_SYMBOL void operator delete[](void *p) noexcept { + return std::free(p); +} + +WEAK_SYMBOL void operator delete[](void *p, size_t) noexcept { + return std::free(p); +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/init/init_system_module.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/init/init_system_module.cpp new file mode 100644 index 00000000..0f64a107 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/init/init_system_module.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::init { + + WEAK_SYMBOL void InitializeSystemModuleBeforeConstructors() { + /* This should only be used in exceptional circumstances. */ + } + + WEAK_SYMBOL void InitializeSystemModule() { + /* TODO: What should we do here, if anything? */ + /* Nintendo does nndiagStartup(); nn::diag::InitializeSystemProcessAbortObserver(); */ + } + + WEAK_SYMBOL void FinalizeSystemModule() { + /* Do nothing by default. */ + } + + WEAK_SYMBOL void Startup() { + /* TODO: What should we do here, if anything? */ + /* Nintendo determines heap size and does init::InitializeAllocator, as relevant. */ + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/kvdb/kvdb_archive.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/kvdb/kvdb_archive.cpp new file mode 100644 index 00000000..fed6a78f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/kvdb/kvdb_archive.cpp @@ -0,0 +1,167 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::kvdb { + + namespace { + + /* Convenience definitions. */ + constexpr u8 ArchiveHeaderMagic[4] = {'I', 'M', 'K', 'V'}; + constexpr u8 ArchiveEntryMagic[4] = {'I', 'M', 'E', 'N'}; + + /* Archive types. */ + struct ArchiveHeader { + u8 magic[sizeof(ArchiveHeaderMagic)]; + u32 pad; + u32 entry_count; + + Result Validate() const { + R_UNLESS(std::memcmp(this->magic, ArchiveHeaderMagic, sizeof(ArchiveHeaderMagic)) == 0, kvdb::ResultInvalidKeyValue()); + R_SUCCEED(); + } + + static ArchiveHeader Make(size_t entry_count) { + ArchiveHeader header = {}; + std::memcpy(header.magic, ArchiveHeaderMagic, sizeof(ArchiveHeaderMagic)); + header.entry_count = static_cast<u32>(entry_count); + return header; + } + }; + static_assert(sizeof(ArchiveHeader) == 0xC && util::is_pod<ArchiveHeader>::value, "ArchiveHeader definition!"); + + struct ArchiveEntryHeader { + u8 magic[sizeof(ArchiveEntryMagic)]; + u32 key_size; + u32 value_size; + + Result Validate() const { + R_UNLESS(std::memcmp(this->magic, ArchiveEntryMagic, sizeof(ArchiveEntryMagic)) == 0, kvdb::ResultInvalidKeyValue()); + R_SUCCEED(); + } + + static ArchiveEntryHeader Make(size_t ksz, size_t vsz) { + ArchiveEntryHeader header = {}; + std::memcpy(header.magic, ArchiveEntryMagic, sizeof(ArchiveEntryMagic)); + header.key_size = ksz; + header.value_size = vsz; + return header; + } + }; + static_assert(sizeof(ArchiveEntryHeader) == 0xC && util::is_pod<ArchiveEntryHeader>::value, "ArchiveEntryHeader definition!"); + + } + + /* Reader functionality. */ + Result ArchiveReader::Peek(void *dst, size_t size) { + /* Bounds check. */ + R_UNLESS(m_offset + size <= m_buffer.GetSize(), kvdb::ResultInvalidKeyValue()); + R_UNLESS(m_offset < m_offset + size, kvdb::ResultInvalidKeyValue()); + + std::memcpy(dst, m_buffer.Get() + m_offset, size); + R_SUCCEED(); + } + + Result ArchiveReader::Read(void *dst, size_t size) { + R_TRY(this->Peek(dst, size)); + m_offset += size; + R_SUCCEED(); + } + + Result ArchiveReader::ReadEntryCount(size_t *out) { + /* This should only be called at the start of reading stream. */ + AMS_ABORT_UNLESS(m_offset == 0); + + /* Read and validate header. */ + ArchiveHeader header; + R_TRY(this->Read(std::addressof(header), sizeof(header))); + R_TRY(header.Validate()); + + *out = header.entry_count; + R_SUCCEED(); + } + + Result ArchiveReader::GetEntrySize(size_t *out_key_size, size_t *out_value_size) { + /* This should only be called after ReadEntryCount. */ + AMS_ABORT_UNLESS(m_offset != 0); + + /* Peek the next entry header. */ + ArchiveEntryHeader header; + R_TRY(this->Peek(std::addressof(header), sizeof(header))); + R_TRY(header.Validate()); + + *out_key_size = header.key_size; + *out_value_size = header.value_size; + R_SUCCEED(); + } + + Result ArchiveReader::ReadEntry(void *out_key, size_t key_size, void *out_value, size_t value_size) { + /* This should only be called after ReadEntryCount. */ + AMS_ABORT_UNLESS(m_offset != 0); + + /* Read the next entry header. */ + ArchiveEntryHeader header; + R_TRY(this->Read(std::addressof(header), sizeof(header))); + R_TRY(header.Validate()); + + /* Key size and Value size must be correct. */ + AMS_ABORT_UNLESS(key_size == header.key_size); + AMS_ABORT_UNLESS(value_size == header.value_size); + + R_ABORT_UNLESS(this->Read(out_key, key_size)); + R_ABORT_UNLESS(this->Read(out_value, value_size)); + R_SUCCEED(); + } + + /* Writer functionality. */ + Result ArchiveWriter::Write(const void *src, size_t size) { + /* Bounds check. */ + R_UNLESS(m_offset + size <= m_buffer.GetSize(), kvdb::ResultInvalidKeyValue()); + R_UNLESS(m_offset < m_offset + size, kvdb::ResultInvalidKeyValue()); + + std::memcpy(m_buffer.Get() + m_offset, src, size); + m_offset += size; + R_SUCCEED(); + } + + void ArchiveWriter::WriteHeader(size_t entry_count) { + /* This should only be called at start of write. */ + AMS_ABORT_UNLESS(m_offset == 0); + + ArchiveHeader header = ArchiveHeader::Make(entry_count); + R_ABORT_UNLESS(this->Write(std::addressof(header), sizeof(header))); + } + + void ArchiveWriter::WriteEntry(const void *key, size_t key_size, const void *value, size_t value_size) { + /* This should only be called after writing header. */ + AMS_ABORT_UNLESS(m_offset != 0); + + ArchiveEntryHeader header = ArchiveEntryHeader::Make(key_size, value_size); + R_ABORT_UNLESS(this->Write(std::addressof(header), sizeof(header))); + R_ABORT_UNLESS(this->Write(key, key_size)); + R_ABORT_UNLESS(this->Write(value, value_size)); + } + + /* Size helper functionality. */ + ArchiveSizeHelper::ArchiveSizeHelper() : m_size(sizeof(ArchiveHeader)) { + /* ... */ + } + + void ArchiveSizeHelper::AddEntry(size_t key_size, size_t value_size) { + m_size += sizeof(ArchiveEntryHeader) + key_size + value_size; + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/kvdb/kvdb_file_key_value_store.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/kvdb/kvdb_file_key_value_store.cpp new file mode 100644 index 00000000..955ae0ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/kvdb/kvdb_file_key_value_store.cpp @@ -0,0 +1,311 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::kvdb { + + /* Cache implementation. */ + void *FileKeyValueStore::Cache::Allocate(size_t size) { + if (m_backing_buffer_size - m_backing_buffer_free_offset < size) { + return nullptr; + } + ON_SCOPE_EXIT { m_backing_buffer_free_offset += size; }; + return m_backing_buffer + m_backing_buffer_free_offset; + } + + Result FileKeyValueStore::Cache::Initialize(void *buffer, size_t buffer_size, size_t capacity) { + m_backing_buffer = static_cast<u8 *>(buffer); + m_backing_buffer_size = buffer_size; + m_backing_buffer_free_offset = 0; + m_entries = nullptr; + m_count = 0; + m_capacity = capacity; + + /* If we have memory to work with, ensure it's at least enough for the cache entries. */ + if (m_backing_buffer != nullptr) { + m_entries = static_cast<decltype(m_entries)>(this->Allocate(sizeof(*m_entries) * m_capacity)); + R_UNLESS(m_entries != nullptr, kvdb::ResultBufferInsufficient()); + } + + R_SUCCEED(); + } + + void FileKeyValueStore::Cache::Invalidate() { + if (!this->HasEntries()) { + return; + } + + /* Reset the allocation pool. */ + m_backing_buffer_free_offset = 0; + m_count = 0; + m_entries = static_cast<decltype(m_entries)>(this->Allocate(sizeof(*m_entries) * m_capacity)); + AMS_ABORT_UNLESS(m_entries != nullptr); + } + + util::optional<size_t> FileKeyValueStore::Cache::TryGet(void *out_value, size_t max_out_size, const void *key, size_t key_size) { + if (!this->HasEntries()) { + return util::nullopt; + } + + /* Try to find the entry. */ + for (size_t i = 0; i < m_count; i++) { + const auto &entry = m_entries[i]; + if (entry.key_size == key_size && std::memcmp(entry.key, key, key_size) == 0) { + /* If we don't have enough space, fail to read from cache. */ + if (max_out_size < entry.value_size) { + return util::nullopt; + } + + std::memcpy(out_value, entry.value, entry.value_size); + return entry.value_size; + } + } + + return util::nullopt; + } + + util::optional<size_t> FileKeyValueStore::Cache::TryGetSize(const void *key, size_t key_size) { + if (!this->HasEntries()) { + return util::nullopt; + } + + /* Try to find the entry. */ + for (size_t i = 0; i < m_count; i++) { + const auto &entry = m_entries[i]; + if (entry.key_size == key_size && std::memcmp(entry.key, key, key_size) == 0) { + return entry.value_size; + } + } + + return util::nullopt; + } + + void FileKeyValueStore::Cache::Set(const void *key, size_t key_size, const void *value, size_t value_size) { + if (!this->HasEntries()) { + return; + } + + /* Ensure key size is small enough. */ + AMS_ABORT_UNLESS(key_size <= MaxKeySize); + + /* If we're at capacity, invalidate the cache. */ + if (m_count == m_capacity) { + this->Invalidate(); + } + + /* Allocate memory for the value. */ + void *value_buf = this->Allocate(value_size); + if (value_buf == nullptr) { + /* We didn't have enough memory for the value. Invalidating might get us enough memory. */ + this->Invalidate(); + value_buf = this->Allocate(value_size); + if (value_buf == nullptr) { + /* If we still don't have enough memory, just fail to put the value in the cache. */ + return; + } + } + + auto &entry = m_entries[m_count++]; + std::memcpy(entry.key, key, key_size); + entry.key_size = key_size; + entry.value = value_buf; + std::memcpy(entry.value, value, value_size); + entry.value_size = value_size; + } + + bool FileKeyValueStore::Cache::Contains(const void *key, size_t key_size) { + return this->TryGetSize(key, key_size).has_value(); + } + + /* Store functionality. */ + FileKeyValueStore::Path FileKeyValueStore::GetPath(const void *_key, size_t key_size) { + /* Format is "<dir>/<hex formatted key>.val" */ + FileKeyValueStore::Path key_path(m_dir_path.Get()); + key_path.Append('/'); + + /* Append hex formatted key. */ + const u8 *key = static_cast<const u8 *>(_key); + for (size_t i = 0; i < key_size; i++) { + key_path.AppendFormat("%02x", key[i]); + } + + /* Append extension. */ + key_path.Append(FileExtension); + + return key_path; + } + + Result FileKeyValueStore::GetKey(size_t *out_size, void *_out_key, size_t max_out_size, const FileKeyValueStore::FileName &file_name) { + /* Validate that the filename can be converted to a key. */ + /* TODO: Nintendo does not validate that the key is valid hex. Should we do this? */ + const size_t file_name_len = file_name.GetLength(); + const size_t key_name_len = file_name_len - FileExtensionLength; + R_UNLESS(file_name_len >= FileExtensionLength + 2, kvdb::ResultInvalidKeyValue()); + R_UNLESS(file_name.EqualsPostfix(FileExtension), kvdb::ResultInvalidKeyValue()); + R_UNLESS(util::IsAligned(key_name_len, 2), kvdb::ResultInvalidKeyValue()); + + /* Validate that we have space for the converted key. */ + const size_t key_size = key_name_len / 2; + R_UNLESS(key_size <= max_out_size, kvdb::ResultBufferInsufficient()); + + /* Convert the hex key back. */ + u8 *out_key = static_cast<u8 *>(_out_key); + for (size_t i = 0; i < key_size; i++) { + char substr[2 * sizeof(u8) + 1]; + file_name.GetSubString(substr, sizeof(substr), 2 * i, sizeof(substr) - 1); + out_key[i] = static_cast<u8>(std::strtoul(substr, nullptr, 0x10)); + } + + *out_size = key_size; + R_SUCCEED(); + } + + Result FileKeyValueStore::Initialize(const char *dir) { + R_RETURN(this->InitializeWithCache(dir, nullptr, 0, 0)); + } + + Result FileKeyValueStore::InitializeWithCache(const char *dir, void *cache_buffer, size_t cache_buffer_size, size_t cache_capacity) { + /* Ensure that the passed path is a directory. */ + fs::DirectoryEntryType entry_type; + R_TRY(fs::GetEntryType(std::addressof(entry_type), dir)); + R_UNLESS(entry_type == fs::DirectoryEntryType_Directory, fs::ResultPathNotFound()); + + /* Set path. */ + m_dir_path.Assign(dir); + + /* Initialize our cache. */ + R_TRY(m_cache.Initialize(cache_buffer, cache_buffer_size, cache_capacity)); + R_SUCCEED(); + } + + Result FileKeyValueStore::Get(size_t *out_size, void *out_value, size_t max_out_size, const void *key, size_t key_size) { + std::scoped_lock lk(m_lock); + + /* Ensure key size is small enough. */ + R_UNLESS(key_size <= MaxKeySize, kvdb::ResultOutOfKeyResource()); + + /* Try to get from cache. */ + { + auto size = m_cache.TryGet(out_value, max_out_size, key, key_size); + if (size) { + *out_size = *size; + R_SUCCEED(); + } + } + + /* Open the value file. */ + fs::FileHandle file; + R_TRY_CATCH(fs::OpenFile(std::addressof(file), this->GetPath(key, key_size), fs::OpenMode_Read)) { + R_CONVERT(fs::ResultPathNotFound, kvdb::ResultKeyNotFound()); + } R_END_TRY_CATCH; + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Get the value size. */ + s64 file_size; + R_TRY(fs::GetFileSize(std::addressof(file_size), file)); + + /* Ensure there's enough space for the value. */ + R_UNLESS(file_size <= static_cast<s64>(max_out_size), kvdb::ResultBufferInsufficient()); + + /* Read the value. */ + const size_t value_size = static_cast<size_t>(file_size); + R_TRY(fs::ReadFile(file, 0, out_value, value_size)); + *out_size = value_size; + + /* Cache the newly read value. */ + m_cache.Set(key, key_size, out_value, value_size); + R_SUCCEED(); + } + + Result FileKeyValueStore::GetSize(size_t *out_size, const void *key, size_t key_size) { + std::scoped_lock lk(m_lock); + + /* Ensure key size is small enough. */ + R_UNLESS(key_size <= MaxKeySize, kvdb::ResultOutOfKeyResource()); + + /* Try to get from cache. */ + { + auto size = m_cache.TryGetSize(key, key_size); + if (size) { + *out_size = *size; + R_SUCCEED(); + } + } + + /* Open the value file. */ + fs::FileHandle file; + R_TRY_CATCH(fs::OpenFile(std::addressof(file), this->GetPath(key, key_size), fs::OpenMode_Read)) { + R_CONVERT(fs::ResultPathNotFound, kvdb::ResultKeyNotFound()); + } R_END_TRY_CATCH; + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Get the value size. */ + s64 file_size; + R_TRY(fs::GetFileSize(std::addressof(file_size), file)); + + *out_size = static_cast<size_t>(file_size); + R_SUCCEED(); + } + + Result FileKeyValueStore::Set(const void *key, size_t key_size, const void *value, size_t value_size) { + std::scoped_lock lk(m_lock); + + /* Ensure key size is small enough. */ + R_UNLESS(key_size <= MaxKeySize, kvdb::ResultOutOfKeyResource()); + + /* When the cache contains the key being set, Nintendo invalidates the cache. */ + if (m_cache.Contains(key, key_size)) { + m_cache.Invalidate(); + } + + /* Delete the file, if it exists. Don't check result, since it's okay if it's already deleted. */ + auto key_path = this->GetPath(key, key_size); + fs::DeleteFile(key_path); + + /* Create the new value file. */ + R_TRY(fs::CreateFile(key_path, value_size)); + + /* Open the value file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), key_path, fs::OpenMode_Write)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Write the value file and flush. */ + R_TRY(fs::WriteFile(file, 0, value, value_size, fs::WriteOption::Flush)); + + R_SUCCEED(); + } + + Result FileKeyValueStore::Remove(const void *key, size_t key_size) { + std::scoped_lock lk(m_lock); + + /* Ensure key size is small enough. */ + R_UNLESS(key_size <= MaxKeySize, kvdb::ResultOutOfKeyResource()); + + /* When the cache contains the key being set, Nintendo invalidates the cache. */ + if (m_cache.Contains(key, key_size)) { + m_cache.Invalidate(); + } + + /* Remove the file. */ + R_TRY_CATCH(fs::DeleteFile(this->GetPath(key, key_size))) { + R_CONVERT(fs::ResultPathNotFound, kvdb::ResultKeyNotFound()) + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ldr/ldr_ams.os.horizon.c b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ldr/ldr_ams.os.horizon.c new file mode 100644 index 00000000..3a0dca19 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ldr/ldr_ams.os.horizon.c @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <switch.h> +#include "ldr_ams.os.horizon.h" + +static Result _ldrAtmosphereHasLaunchedBootProgram(Service *srv, bool *out, u64 program_id) { + u8 tmp = 0; + Result rc = serviceDispatchInOut(srv, 65000, program_id, tmp); + if (R_SUCCEEDED(rc) && out) *out = tmp & 1; + return rc; +} + +Result ldrDmntAtmosphereHasLaunchedBootProgram(bool *out, u64 program_id) { + return _ldrAtmosphereHasLaunchedBootProgram(ldrDmntGetServiceSession(), out, program_id); +} + +Result ldrPmAtmosphereHasLaunchedBootProgram(bool *out, u64 program_id) { + return _ldrAtmosphereHasLaunchedBootProgram(ldrPmGetServiceSession(), out, program_id); +} + +Result ldrPmAtmosphereGetProgramInfo(LoaderProgramInfo *out_program_info, CfgOverrideStatus *out_status, const NcmProgramLocation *loc, const LoaderProgramAttributes *attr) { + const struct { + LoaderProgramAttributes attr; + u16 pad1; + u32 pad2; + NcmProgramLocation loc; + } in = { *attr, 0, 0, *loc }; + return serviceDispatchInOut(ldrPmGetServiceSession(), 65001, in, *out_status, + .buffer_attrs = { SfBufferAttr_Out | SfBufferAttr_HipcPointer | SfBufferAttr_FixedSize }, + .buffers = { { out_program_info, sizeof(*out_program_info) } }, + ); +} + +Result ldrPmAtmospherePinProgram(u64 *out, const NcmProgramLocation *loc, const CfgOverrideStatus *status) { + const struct { + NcmProgramLocation loc; + CfgOverrideStatus status; + } in = { *loc, *status }; + return serviceDispatchInOut(ldrPmGetServiceSession(), 65002, in, *out); +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ldr/ldr_ams.os.horizon.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ldr/ldr_ams.os.horizon.h new file mode 100644 index 00000000..b5280711 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ldr/ldr_ams.os.horizon.h @@ -0,0 +1,27 @@ +/** + * @file ldr_ams.h + * @brief Loader (ldr:*) IPC wrapper for Atmosphere extensions. + * @author SciresM + * @copyright libnx Authors + */ +#pragma once +#include <switch.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + u64 keys_down; + u64 flags; +} CfgOverrideStatus; + +Result ldrPmAtmosphereHasLaunchedBootProgram(bool *out, u64 program_id); +Result ldrDmntAtmosphereHasLaunchedBootProgram(bool *out, u64 program_id); + +Result ldrPmAtmosphereGetProgramInfo(LoaderProgramInfo *out, CfgOverrideStatus *out_status, const NcmProgramLocation *loc, const LoaderProgramAttributes *attr); +Result ldrPmAtmospherePinProgram(u64 *out, const NcmProgramLocation *loc, const CfgOverrideStatus *status); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ldr/ldr_pm_api.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ldr/ldr_pm_api.os.horizon.cpp new file mode 100644 index 00000000..f461f9f7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ldr/ldr_pm_api.os.horizon.cpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ldr_ams.os.horizon.h" + +namespace ams::ldr::pm { + + /* Information API. */ + Result CreateProcess(os::NativeHandle *out, PinId pin_id, u32 flags, Handle reslimit, ldr::ProgramAttributes attrs) { + static_assert(sizeof(attrs) == sizeof(::LoaderProgramAttributes)); + R_RETURN(ldrPmCreateProcess(pin_id.value, flags, reslimit, reinterpret_cast<const ::LoaderProgramAttributes *>(std::addressof(attrs)), out)); + } + + Result GetProgramInfo(ProgramInfo *out, const ncm::ProgramLocation &loc, ldr::ProgramAttributes attrs) { + static_assert(sizeof(*out) == sizeof(LoaderProgramInfo)); + R_RETURN(ldrPmGetProgramInfo(reinterpret_cast<const NcmProgramLocation *>(std::addressof(loc)), reinterpret_cast<const ::LoaderProgramAttributes *>(std::addressof(attrs)), reinterpret_cast<LoaderProgramInfo *>(out))); + } + + Result PinProgram(PinId *out, const ncm::ProgramLocation &loc) { + static_assert(sizeof(*out) == sizeof(u64), "PinId definition!"); + R_RETURN(ldrPmPinProgram(reinterpret_cast<const NcmProgramLocation *>(std::addressof(loc)), reinterpret_cast<u64 *>(out))); + } + + Result UnpinProgram(PinId pin_id) { + R_RETURN(ldrPmUnpinProgram(pin_id.value)); + } + + Result HasLaunchedBootProgram(bool *out, ncm::ProgramId program_id) { + R_RETURN(ldrPmAtmosphereHasLaunchedBootProgram(out, static_cast<u64>(program_id))); + } + + Result AtmosphereGetProgramInfo(ProgramInfo *out, cfg::OverrideStatus *out_status, const ncm::ProgramLocation &loc, ldr::ProgramAttributes attrs) { + static_assert(sizeof(*out_status) == sizeof(CfgOverrideStatus), "CfgOverrideStatus definition!"); + static_assert(sizeof(*out) == sizeof(LoaderProgramInfo)); + R_RETURN(ldrPmAtmosphereGetProgramInfo(reinterpret_cast<LoaderProgramInfo *>(out), reinterpret_cast<CfgOverrideStatus *>(out_status), reinterpret_cast<const NcmProgramLocation *>(std::addressof(loc)), reinterpret_cast<const ::LoaderProgramAttributes *>(std::addressof(attrs)))); + } + + Result SetEnabledProgramVerification(bool enabled) { + R_RETURN(ldrPmSetEnabledProgramVerification(enabled)); + } + + Result AtmospherePinProgram(PinId *out, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status) { + static_assert(sizeof(*out) == sizeof(u64), "PinId definition!"); + static_assert(sizeof(status) == sizeof(CfgOverrideStatus), "CfgOverrideStatus definition!"); + R_RETURN(ldrPmAtmospherePinProgram(reinterpret_cast<u64 *>(out), reinterpret_cast<const NcmProgramLocation *>(std::addressof(loc)), reinterpret_cast<const CfgOverrideStatus *>(std::addressof(status)))); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ldr/ldr_shell_api.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ldr/ldr_shell_api.os.horizon.cpp new file mode 100644 index 00000000..9f067af1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ldr/ldr_shell_api.os.horizon.cpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ldr { + + Result InitializeForShell() { + R_RETURN(::ldrShellInitialize()); + } + + Result FinalizeForShell() { + ::ldrShellExit(); + R_SUCCEED(); + } + + Result SetProgramArgument(ncm::ProgramId program_id, const void *arg, size_t size) { + R_RETURN(::ldrShellSetProgramArguments(static_cast<u64>(program_id), arg, size)); + } + + Result FlushArguments() { + R_RETURN(::ldrShellFlushArguments()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/impl/lm_log_data_chunk.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/impl/lm_log_data_chunk.hpp new file mode 100644 index 00000000..ddc8fe25 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/impl/lm_log_data_chunk.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::lm::impl { + + enum LogDataChunkKey { + LogDataChunkKey_LogSessionBegin = 0, + LogDataChunkKey_LogSessionEnd = 1, + LogDataChunkKey_TextLog = 2, + LogDataChunkKey_LineNumber = 3, + LogDataChunkKey_FileName = 4, + LogDataChunkKey_FunctionName = 5, + LogDataChunkKey_ModuleName = 6, + LogDataChunkKey_ThreadName = 7, + LogDataChunkKey_LogPacketDropCount = 8, + LogDataChunkKey_UserSystemClock = 9, + LogDataChunkKey_ProcessName = 10, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/impl/lm_log_packet_header.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/impl/lm_log_packet_header.hpp new file mode 100644 index 00000000..fd97d2a8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/impl/lm_log_packet_header.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::lm::impl { + + constexpr inline size_t LogPacketHeaderSize = 0x18; + + class LogPacketHeader { + private: + u64 m_process_id; + u64 m_thread_id; + u8 m_flags; + u8 m_padding; + u8 m_severity; + u8 m_verbosity; + u32 m_payload_size; + public: + constexpr u64 GetProcessId() const { return m_process_id; } + constexpr void SetProcessId(u64 v) { m_process_id = v; } + + constexpr u64 GetThreadId() const { return m_thread_id; } + constexpr void SetThreadId(u64 v) { m_thread_id = v; } + + constexpr bool IsHead() const { return (m_flags & (1 << 0)) != 0; } + constexpr void SetHead(bool v) { m_flags = (m_flags & ~(1 << 0)) | ((v ? 1 : 0) << 0); } + + constexpr bool IsTail() const { return (m_flags & (1 << 1)) != 0; } + constexpr void SetTail(bool v) { m_flags = (m_flags & ~(1 << 1)) | ((v ? 1 : 0) << 1); } + + constexpr bool IsLittleEndian() const { return (m_flags & (1 << 2)) != 0; } + constexpr void SetLittleEndian(bool v) { m_flags = (m_flags & ~(1 << 2)) | ((v ? 1 : 0) << 2); } + + constexpr u8 GetSeverity() const { return m_severity; } + constexpr void SetSeverity(u8 v) { m_severity = v; } + + constexpr u8 GetVerbosity() const { return m_verbosity; } + constexpr void SetVerbosity(u8 v) { m_verbosity = v; } + + constexpr u32 GetPayloadSize() const { return m_payload_size; } + constexpr void SetPayloadSize(u32 v) { m_payload_size = v; } + }; + static_assert(sizeof(LogPacketHeader) == LogPacketHeaderSize); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter.hpp new file mode 100644 index 00000000..e151cd75 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter.hpp @@ -0,0 +1,74 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "lm_log_packet_transmitter_base.hpp" + +namespace ams::lm::impl { + + class LogPacketTransmitter : public LogPacketTransmitterBase { + public: + LogPacketTransmitter(void *buffer, size_t buffer_size, FlushFunction flush_func, u8 severity, u8 verbosity, u64 process_id, bool head, bool tail) + : LogPacketTransmitterBase(buffer, buffer_size, flush_func, severity, verbosity, process_id, head, tail) { /* ... */ } + + void PushLogSessionBegin() { + bool value = true; + this->PushDataChunk(LogDataChunkKey_LogSessionBegin, std::addressof(value), sizeof(value)); + } + + void PushLogSessionEnd() { + bool value = true; + this->PushDataChunk(LogDataChunkKey_LogSessionEnd, std::addressof(value), sizeof(value)); + } + + void PushTextLog(const char *str, size_t len) { + this->PushDataChunk(LogDataChunkKey_TextLog, str, len); + } + + void PushLineNumber(u32 line) { + this->PushDataChunk(LogDataChunkKey_LineNumber, std::addressof(line), sizeof(line)); + } + + void PushFileName(const char *str, size_t len) { + this->PushDataChunk(LogDataChunkKey_FileName, str, len); + } + + void PushFunctionName(const char *str, size_t len) { + this->PushDataChunk(LogDataChunkKey_FunctionName, str, len); + } + + void PushModuleName(const char *str, size_t len) { + this->PushDataChunk(LogDataChunkKey_ModuleName, str, len); + } + + void PushThreadName(const char *str, size_t len) { + this->PushDataChunk(LogDataChunkKey_ThreadName, str, len); + } + + void PushLogPacketDropCount(u64 count) { + this->PushDataChunk(LogDataChunkKey_LineNumber, std::addressof(count), sizeof(count)); + } + + void PushUserSystemClock(s64 posix_time) { + this->PushDataChunk(LogDataChunkKey_LineNumber, std::addressof(posix_time), sizeof(posix_time)); + } + + void PushProcessName(const char *str, size_t len) { + this->PushDataChunk(LogDataChunkKey_ProcessName, str, len); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter_base.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter_base.cpp new file mode 100644 index 00000000..7a9aa12d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter_base.cpp @@ -0,0 +1,166 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lm_log_packet_transmitter_base.hpp" + +namespace ams::lm::impl { + + LogPacketTransmitterBase::LogPacketTransmitterBase(void *buffer, size_t buffer_size, FlushFunction flush_func, u8 severity, u8 verbosity, u64 process_id, bool head, bool tail) { + /* Check pre-conditions. */ + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(buffer), alignof(LogPacketHeader))); + AMS_ASSERT(buffer_size >= LogPacketHeaderSize); + AMS_ASSERT(flush_func != nullptr); + + /* Construct log packet header. */ + m_header = std::construct_at(static_cast<LogPacketHeader *>(buffer)); + + /* Set fields. */ + m_start = static_cast<u8 *>(buffer); + m_end = m_start + buffer_size; + m_payload = m_start + LogPacketHeaderSize; + m_current = m_payload; + + m_is_tail = tail; + + m_flush_function = flush_func; + + /* Set header fields. */ + m_header->SetProcessId(process_id); + m_header->SetThreadId(os::GetThreadId(os::GetCurrentThread())); + m_header->SetHead(head); + m_header->SetLittleEndian(util::IsLittleEndian()); + m_header->SetSeverity(severity); + m_header->SetVerbosity(verbosity); + } + + bool LogPacketTransmitterBase::Flush(bool is_tail) { + /* Check if we're already flushed. */ + if (m_current == m_payload) { + return true; + } + + /* Flush the data. */ + m_header->SetTail(is_tail); + m_header->SetPayloadSize(static_cast<u32>(m_current - m_payload)); + const auto result = m_flush_function(m_start, static_cast<size_t>(m_current - m_start)); + m_header->SetHead(false); + + /* Reset. */ + m_current = m_payload; + + return result; + } + + size_t LogPacketTransmitterBase::GetRemainSize() { + return static_cast<size_t>(m_end - m_current); + } + + size_t LogPacketTransmitterBase::GetPushableDataSize(size_t uleb_size) { + const size_t remain = this->GetRemainSize(); + if (remain < uleb_size + 2) { + return 0; + } + + const size_t cmp = remain - uleb_size; + u64 mask = 0x7F; + size_t n; + for (n = 1; mask + n < cmp; ++n) { + mask |= mask << 7; + } + + return cmp - n; + } + + size_t LogPacketTransmitterBase::GetRequiredSizeToPushUleb128(u64 v) { + /* Determine bytes needed for uleb128 value. */ + size_t required = 0; + do { + ++required; + v >>= 7; + } while (v > 0); + + return required; + } + + void LogPacketTransmitterBase::PushUleb128(u64 v) { + const u32 Mask = 0x7F; + const u32 InverseMask = ~Mask; + do { + /* Check we're within bounds. */ + AMS_ASSERT(m_current < m_end); + + /* Write byte. */ + *(m_current++) = static_cast<u8>(v & Mask) | (((v & InverseMask) != 0) ? 0x80 : 0x00); + + /* Adjust remaining bit range. */ + v >>= 7; + } while (v > 0); + } + + void LogPacketTransmitterBase::PushDataChunkImpl(LogDataChunkKey key, const void *data, size_t data_size, bool is_text) { + /* Check pre-conditions. */ + AMS_ASSERT(data != nullptr); + + /* Push as much data as we can, until the chunk is complete. */ + const u8 *cur = static_cast<const u8 *>(data); + const u8 * const end = cur + data_size; + const size_t required_key = this->GetRequiredSizeToPushUleb128(key); + do { + /* Get the pushable size. */ + size_t pushable_size = this->GetPushableDataSize(required_key); + size_t required_size = is_text ? 4 : 1; + if (pushable_size < required_size) { + this->Flush(false); + pushable_size = this->GetPushableDataSize(required_key); + } + AMS_ASSERT(pushable_size >= required_size); + + /* Determine the current size. */ + size_t current_size = std::min<size_t>(pushable_size, end - cur); + if (is_text) { + const auto valid_size = diag::impl::GetValidSizeAsUtf8String(reinterpret_cast<const char *>(cur), current_size); + if (valid_size >= 0) { + current_size = static_cast<size_t>(valid_size); + } + } + + /* Push data. */ + this->PushUleb128(key); + this->PushUleb128(current_size); + this->PushData(cur, current_size); + + /* Advance. */ + cur = cur + current_size; + } while (cur < end); + + /* Check that we pushed all the data. */ + AMS_ASSERT(cur == end); + } + + void LogPacketTransmitterBase::PushData(const void *data, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(data != nullptr); + AMS_ASSERT(size <= this->GetRemainSize()); + + /* Push the data. */ + if (size > 0) { + std::memcpy(m_current, data, size); + m_current += size; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter_base.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter_base.hpp new file mode 100644 index 00000000..f80cd222 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/impl/lm_log_packet_transmitter_base.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "lm_log_packet_header.hpp" +#include "lm_log_data_chunk.hpp" + +namespace ams::lm::impl { + + class LogPacketTransmitterBase { + public: + using FlushFunction = bool (*)(const u8 *data, size_t size); + private: + LogPacketHeader *m_header; + u8 *m_start; + u8 *m_end; + u8 *m_payload; + u8 *m_current; + bool m_is_tail; + FlushFunction m_flush_function; + protected: + LogPacketTransmitterBase(void *buffer, size_t buffer_size, FlushFunction flush_func, u8 severity, u8 verbosity, u64 process_id, bool head, bool tail); + + ~LogPacketTransmitterBase() { + this->Flush(m_is_tail); + } + + void PushDataChunk(LogDataChunkKey key, const void *data, size_t size) { + this->PushDataChunkImpl(key, data, size, false); + } + + void PushDataChunk(LogDataChunkKey key, const char *str, size_t size) { + this->PushDataChunkImpl(key, str, size, true); + } + public: + bool Flush(bool is_tail); + private: + size_t GetRemainSize(); + size_t GetPushableDataSize(size_t uleb_size); + size_t GetRequiredSizeToPushUleb128(u64 v); + + void PushUleb128(u64 v); + void PushDataChunkImpl(LogDataChunkKey key, const void *data, size_t size, bool is_text); + void PushData(const void *data, size_t size); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/lm_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/lm_api.cpp new file mode 100644 index 00000000..ee4a22e7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/lm_api.cpp @@ -0,0 +1,121 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lm_remote_log_service.hpp" +#include "impl/lm_log_packet_header.hpp" +#include "impl/lm_log_packet_transmitter.hpp" + +namespace ams::diag::impl { + + void ReplaceDefaultLogObserver(LogObserver observer); + void ResetDefaultLogObserver(); + + void GetProcessNamePointer(const char **out, size_t *out_len); + +} + +namespace ams::lm { + + namespace { + + constinit sf::SharedPointer<ILogger> g_logger = nullptr; + + constexpr inline size_t TransmissionBufferSize = 1_KB; + constexpr inline size_t TransmissionBufferAlign = alignof(impl::LogPacketHeader); + + constinit os::SdkMutex g_transmission_buffer_mutex; + + alignas(TransmissionBufferAlign) constinit char g_transmission_buffer[TransmissionBufferSize]; + + using PushTextFunction = void (impl::LogPacketTransmitter::*)(const char *, size_t); + + bool LogPacketTransmitterFlushFunction(const u8 *data, size_t size) { + const Result result = g_logger->Log(sf::InAutoSelectBuffer(data, size)); + R_ABORT_UNLESS(result); + return R_SUCCEEDED(result); + } + + void InvokePushTextWithUtf8Sanitizing(impl::LogPacketTransmitter &transmitter, PushTextFunction push_func, const char *str) { + /* Get the string length. */ + const auto len = std::strlen(str); + + if (len == 0 || util::VerifyUtf8String(str, len)) { + (transmitter.*push_func)(str, len); + } else { + (transmitter.*push_func)("(Invalid UTF8 string)", sizeof("(Invalid UTF8 string)") - 1); + } + } + + void LogManagerLogObserver(const diag::LogMetaData &meta, const diag::LogBody &body, void *) { + /* Check pre-conditions. */ + AMS_ASSERT(!meta.use_default_locale_charset); + + /* Acquire access to the transmission buffer. */ + std::scoped_lock lk(g_transmission_buffer_mutex); + + /* Create transmitter. */ + impl::LogPacketTransmitter transmitter(g_transmission_buffer, TransmissionBufferSize, LogPacketTransmitterFlushFunction, static_cast<u8>(meta.severity), static_cast<u8>(meta.verbosity), 0, body.is_head, body.is_tail); + + /* Push head-only logs. */ + if (body.is_head) { + transmitter.PushUserSystemClock(os::GetSystemTick().ToTimeSpan().GetSeconds()); + transmitter.PushLineNumber(meta.source_info.line_number); + InvokePushTextWithUtf8Sanitizing(transmitter, &impl::LogPacketTransmitter::PushFileName, meta.source_info.file_name); + InvokePushTextWithUtf8Sanitizing(transmitter, &impl::LogPacketTransmitter::PushFunctionName, meta.source_info.function_name); + InvokePushTextWithUtf8Sanitizing(transmitter, &impl::LogPacketTransmitter::PushModuleName, meta.module_name); + InvokePushTextWithUtf8Sanitizing(transmitter, &impl::LogPacketTransmitter::PushThreadName, os::GetThreadNamePointer(os::GetCurrentThread())); + + const char *process_name; + size_t process_name_len; + diag::impl::GetProcessNamePointer(std::addressof(process_name), std::addressof(process_name_len)); + + transmitter.PushProcessName(process_name, process_name_len); + } + + /* Push the actual log. */ + transmitter.PushTextLog(body.message, body.message_size); + } + + } + + void Initialize() { + AMS_ABORT_UNLESS(g_logger == nullptr); + + /* Create the logger. */ + { + auto service = CreateLogService(); + R_ABORT_UNLESS(service->OpenLogger(std::addressof(g_logger), sf::ClientProcessId{})); + } + + /* Replace the default log observer. */ + diag::impl::ReplaceDefaultLogObserver(LogManagerLogObserver); + } + + void Finalize() { + AMS_ABORT_UNLESS(g_logger != nullptr); + + /* Reset the default log observer. */ + diag::impl::ResetDefaultLogObserver(); + + /* Destroy the logger. */ + g_logger = nullptr; + } + + void SetDestination(u32 destination) { + g_logger->SetDestination(destination); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/lm_remote_log_service.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/lm_remote_log_service.cpp new file mode 100644 index 00000000..614e897d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/lm_remote_log_service.cpp @@ -0,0 +1,72 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lm_remote_log_service.hpp" +#include "lm_service_name.hpp" + +namespace ams::lm { + + #if defined(ATMOSPHERE_OS_HORIZON) + namespace { + + struct LmRemoteLogServiceTag; + using RemoteAllocator = ams::sf::ExpHeapStaticAllocator<0x80, LmRemoteLogServiceTag>; + using RemoteObjectFactory = ams::sf::ObjectFactory<typename RemoteAllocator::Policy>; + + class StaticAllocatorInitializer { + public: + StaticAllocatorInitializer() { + RemoteAllocator::Initialize(lmem::CreateOption_None); + } + } g_static_allocator_initializer; + + } + + Result RemoteLogService::OpenLogger(sf::Out<sf::SharedPointer<::ams::lm::ILogger>> out, const sf::ClientProcessId &client_process_id) { + AMS_UNUSED(client_process_id); + + /* Send libnx command. */ + ::Service logger_srv; + { + u64 pid_placeholder = 0; + + #define NX_SERVICE_ASSUME_NON_DOMAIN + R_TRY(serviceDispatchIn(std::addressof(m_srv), 0, pid_placeholder, + .in_send_pid = true, + .out_num_objects = 1, + .out_objects = std::addressof(logger_srv), + )); + #undef NX_SERVICE_ASSUME_NON_DOMAIN + } + + /* Open logger. */ + out.SetValue(RemoteObjectFactory::CreateSharedEmplaced<::ams::lm::ILogger, RemoteLogger>(logger_srv)); + R_SUCCEED(); + } + + sf::SharedPointer<ILogService> CreateLogService() { + os::NativeHandle h; + R_ABORT_UNLESS(sm::GetServiceHandle(std::addressof(h), LogServiceName)); + + return RemoteObjectFactory::CreateSharedEmplaced<::ams::lm::ILogService, RemoteLogService>(h); + } + #else + sf::SharedPointer<ILogService> CreateLogService() { + AMS_ABORT("TODO"); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/lm_remote_log_service.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/lm_remote_log_service.hpp new file mode 100644 index 00000000..b646173c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/lm_remote_log_service.hpp @@ -0,0 +1,67 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "sf/lm_i_log_service.hpp" + +namespace ams::lm { + + #if defined(ATMOSPHERE_OS_HORIZON) + /* TODO: Real libnx primitives? */ + + #define NX_SERVICE_ASSUME_NON_DOMAIN + + class RemoteLogger { + private: + ::Service m_srv; + public: + RemoteLogger(::Service &s) : m_srv(s) { /* ... */ } + ~RemoteLogger() { ::serviceClose(std::addressof(m_srv)); } + public: + /* Actual commands. */ + Result Log(const sf::InAutoSelectBuffer &message) { + return serviceDispatch(std::addressof(m_srv), 0, + .buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcAutoSelect }, + .buffers = { { message.GetPointer(), message.GetSize() } }, + ); + } + + Result SetDestination(u32 destination) { + R_RETURN(serviceDispatchIn(std::addressof(m_srv), 1, destination)); + } + }; + static_assert(lm::IsILogger<RemoteLogger>); + + class RemoteLogService { + private: + ::Service m_srv; + public: + RemoteLogService(os::NativeHandle h) { + ::serviceCreate(std::addressof(m_srv), h); + } + ~RemoteLogService() { ::serviceClose(std::addressof(m_srv)); } + public: + /* Actual commands. */ + Result OpenLogger(sf::Out<sf::SharedPointer<::ams::lm::ILogger>> out, const sf::ClientProcessId &client_process_id); + }; + static_assert(lm::IsILogService<RemoteLogService>); + + #undef NX_SERVICE_ASSUME_NON_DOMAIN + #endif + + sf::SharedPointer<ILogService> CreateLogService(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/lm_service_name.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/lm_service_name.hpp new file mode 100644 index 00000000..601209e1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/lm_service_name.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::lm { + + constexpr inline const sm::ServiceName LogServiceName = sm::ServiceName::Encode("lm"); + + constexpr inline const sm::ServiceName LogGetterServiceName = sm::ServiceName::Encode("lm:get"); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/sf/lm_i_log_getter.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/sf/lm_i_log_getter.hpp new file mode 100644 index 00000000..e39dbb9d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/sf/lm_i_log_getter.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#define AMS_LM_I_LOG_GETTER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, StartLogging, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, StopLogging, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, GetLog, (const sf::OutAutoSelectBuffer &message, sf::Out<s64> out_size, sf::Out<u32> out_drop_count), (message, out_size, out_drop_count)) + +AMS_SF_DEFINE_INTERFACE(ams::lm, ILogGetter, AMS_LM_I_LOG_GETTER_INTERFACE_INFO, 0x565EA15C) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/sf/lm_i_log_service.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/sf/lm_i_log_service.hpp new file mode 100644 index 00000000..dcefb007 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/sf/lm_i_log_service.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#define AMS_LM_I_LOGGER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, Log, (const sf::InAutoSelectBuffer &message), (message)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, SetDestination, (u32 destination), (destination)) + +AMS_SF_DEFINE_INTERFACE(ams::lm, ILogger, AMS_LM_I_LOGGER_INTERFACE_INFO, 0x3E81DAD2) + +#define AMS_LM_I_LOG_SERVICE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, OpenLogger, (sf::Out<sf::SharedPointer<::ams::lm::ILogger>> out, const sf::ClientProcessId &client_process_id), (out, client_process_id)) + +AMS_SF_DEFINE_INTERFACE(ams::lm, ILogService, AMS_LM_I_LOG_SERVICE_INTERFACE_INFO, 0xE1D7F748) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_custom_sink_buffer.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_custom_sink_buffer.cpp new file mode 100644 index 00000000..5a2f7dc3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_custom_sink_buffer.cpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lm_custom_sink_buffer.hpp" + +namespace ams::lm::srv { + + bool CustomSinkBuffer::TryPush(const void *data, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(size <= m_buffer_size); + AMS_ASSERT(data || size == 0); + + /* If we have nothing to push, succeed. */ + if (size == 0) { + return true; + } + + /* Check that we can push the data. */ + if (size > m_buffer_size - m_used_buffer_size) { + return false; + } + + /* Push the data. */ + std::memcpy(m_buffer + m_used_buffer_size, data, size); + m_used_buffer_size += size; + + return true; + } + + bool CustomSinkBuffer::TryFlush() { + /* Check that we have data to flush. */ + if (m_used_buffer_size == 0) { + return false; + } + + /* Try to flush the data. */ + if (!m_flush_function(m_buffer, m_used_buffer_size)) { + return false; + } + + /* Clear our used size. */ + m_used_buffer_size = 0; + + return true; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_custom_sink_buffer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_custom_sink_buffer.hpp new file mode 100644 index 00000000..dc34af9e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_custom_sink_buffer.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::lm::srv { + + class CustomSinkBuffer { + NON_COPYABLE(CustomSinkBuffer); + NON_MOVEABLE(CustomSinkBuffer); + public: + using FlushFunction = bool (*)(const u8 *buffer, size_t buffer_size); + private: + u8 *m_buffer; + size_t m_buffer_size; + size_t m_used_buffer_size; + FlushFunction m_flush_function; + public: + constexpr explicit CustomSinkBuffer(u8 *buffer, size_t buffer_size, FlushFunction f) : m_buffer(buffer), m_buffer_size(buffer_size), m_used_buffer_size(0), m_flush_function(f) { + AMS_ASSERT(m_buffer != nullptr); + AMS_ASSERT(m_buffer_size > 0); + AMS_ASSERT(m_flush_function != nullptr); + } + + bool TryPush(const void *data, size_t size); + bool TryFlush(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_event_log_transmitter.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_event_log_transmitter.cpp new file mode 100644 index 00000000..b516db76 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_event_log_transmitter.cpp @@ -0,0 +1,117 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lm_event_log_transmitter.hpp" +#include "lm_log_buffer.hpp" +#include "../impl/lm_log_packet_header.hpp" +#include "../impl/lm_log_packet_transmitter.hpp" + +namespace ams::lm::srv { + + namespace { + + constexpr inline size_t TransmitterBufferAlign = alignof(impl::LogPacketHeader); + constexpr inline size_t TransmitterBufferSizeForSessionInfo = impl::LogPacketHeaderSize + 3; + constexpr inline size_t TransmitterBufferSizeForDropCount = impl::LogPacketHeaderSize + 10; + + bool DefaultFlushFunction(const u8 *data, size_t size) { + return LogBuffer::GetDefaultInstance().TryPush(data, size); + } + + } + + EventLogTransmitter &EventLogTransmitter::GetDefaultInstance() { + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(EventLogTransmitter, s_default_event_log_transmitter, DefaultFlushFunction); + + return s_default_event_log_transmitter; + } + + bool EventLogTransmitter::PushLogSessionBegin(u64 process_id) { + /* Create a transmitter. */ + alignas(TransmitterBufferAlign) u8 buffer[TransmitterBufferSizeForSessionInfo]; + impl::LogPacketTransmitter transmitter(buffer, sizeof(buffer), m_flush_function, static_cast<u8>(diag::LogSeverity_Info), 0, process_id, true, true); + + /* Push session begin. */ + transmitter.PushLogSessionBegin(); + + /* Flush the data. */ + const bool success = transmitter.Flush(true); + + /* Update drop count. */ + if (!success) { + ++m_log_packet_drop_count; + } + + return success; + } + + bool EventLogTransmitter::PushLogSessionEnd(u64 process_id) { + /* Create a transmitter. */ + alignas(TransmitterBufferAlign) u8 buffer[TransmitterBufferSizeForSessionInfo]; + impl::LogPacketTransmitter transmitter(buffer, sizeof(buffer), m_flush_function, static_cast<u8>(diag::LogSeverity_Info), 0, process_id, true, true); + + /* Push session end. */ + transmitter.PushLogSessionEnd(); + + /* Flush the data. */ + const bool success = transmitter.Flush(true); + + /* Update drop count. */ + if (!success) { + ++m_log_packet_drop_count; + } + + return success; + } + + bool EventLogTransmitter::PushLogPacketDropCountIfExists() { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_log_packet_drop_count_mutex); + + /* If we have no dropped packets, nothing to push. */ + if (m_log_packet_drop_count == 0) { + return true; + } + + /* Create a transmitter. */ + alignas(TransmitterBufferAlign) u8 buffer[TransmitterBufferSizeForDropCount]; + impl::LogPacketTransmitter transmitter(buffer, sizeof(buffer), m_flush_function, static_cast<u8>(diag::LogSeverity_Info), 0, 0, true, true); + + /* Push log packet drop count. */ + transmitter.PushLogPacketDropCount(m_log_packet_drop_count); + + /* Flush the data. */ + const bool success = transmitter.Flush(true); + + /* Update drop count. */ + if (success) { + m_log_packet_drop_count = 0; + } else { + ++m_log_packet_drop_count; + } + + return success; + } + + void EventLogTransmitter::IncreaseLogPacketDropCount() { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_log_packet_drop_count_mutex); + + /* Increase the dropped packet count. */ + ++m_log_packet_drop_count; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_event_log_transmitter.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_event_log_transmitter.hpp new file mode 100644 index 00000000..d9ec12b6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_event_log_transmitter.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::lm::srv { + + class EventLogTransmitter { + NON_COPYABLE(EventLogTransmitter); + NON_MOVEABLE(EventLogTransmitter); + public: + using FlushFunction = bool (*)(const u8 *data, size_t size); + private: + FlushFunction m_flush_function; + size_t m_log_packet_drop_count; + os::SdkMutex m_log_packet_drop_count_mutex; + public: + constexpr explicit EventLogTransmitter(FlushFunction f) : m_flush_function(f), m_log_packet_drop_count(0), m_log_packet_drop_count_mutex() { + AMS_ASSERT(f != nullptr); + } + + static EventLogTransmitter &GetDefaultInstance(); + + bool PushLogSessionBegin(u64 process_id); + bool PushLogSessionEnd(u64 process_id); + + bool PushLogPacketDropCountIfExists(); + + void IncreaseLogPacketDropCount(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_flush_thread.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_flush_thread.cpp new file mode 100644 index 00000000..238f7140 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_flush_thread.cpp @@ -0,0 +1,181 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lm_log_server_proxy.hpp" +#include "lm_sd_card_logger.hpp" +#include "lm_log_buffer.hpp" +#include "lm_event_log_transmitter.hpp" + +namespace ams::lm::srv { + + bool IsSleeping(); + + namespace { + + alignas(os::ThreadStackAlignment) u8 g_flush_thread_stack[8_KB]; + + constinit u8 g_fs_heap[32_KB]; + constinit lmem::HeapHandle g_fs_heap_handle; + + constinit os::ThreadType g_flush_thread = {}; + + os::Event g_stop_event(os::EventClearMode_ManualClear); + os::Event g_sd_logging_event(os::EventClearMode_ManualClear); + os::Event g_host_connection_event(os::EventClearMode_ManualClear); + + constinit std::unique_ptr<fs::IEventNotifier> g_sd_card_detection_event_notifier; + os::SystemEvent g_sd_card_detection_event; + + void *AllocateForFs(size_t size) { + return lmem::AllocateFromExpHeap(g_fs_heap_handle, size); + } + + void DeallocateForFs(void *ptr, size_t size) { + AMS_UNUSED(size); + return lmem::FreeToExpHeap(g_fs_heap_handle, ptr); + } + + void HostConnectionObserver(bool is_connected) { + /* Update the host connection event. */ + if (is_connected) { + g_host_connection_event.Signal(); + } else { + g_host_connection_event.Clear(); + + /* Potentially cancel the log buffer push. */ + if (!g_sd_logging_event.TryWait()) { + LogBuffer::GetDefaultInstance().CancelPush(); + } + } + } + + void SdLoggingObserver(bool is_available) { + /* Update the SD card logging event. */ + if (is_available) { + g_sd_logging_event.Signal(); + } else { + g_sd_logging_event.Clear(); + + /* Potentially cancel the log buffer push. */ + if (!g_host_connection_event.TryWait()) { + LogBuffer::GetDefaultInstance().CancelPush(); + } + } + } + + bool WaitForFlush() { + while (true) { + /* Wait for something to be signaled. */ + os::WaitAny(g_stop_event.GetBase(), g_host_connection_event.GetBase(), g_sd_logging_event.GetBase(), g_sd_card_detection_event.GetBase()); + + /* If we're stopping, no flush. */ + if (g_stop_event.TryWait()) { + return false; + } + + /* If host is connected/we're logging to sd, flush. */ + if (g_host_connection_event.TryWait() || g_sd_logging_event.TryWait()) { + return true; + } + + /* If the sd card is newly inserted, flush. */ + if (g_sd_card_detection_event.TryWait()) { + g_sd_card_detection_event.Clear(); + + if (fs::IsSdCardInserted()) { + return true; + } + } + } + } + + void FlushThreadFunction(void *) { + /* Initialize fs. */ + fs::InitializeWithMultiSessionForSystem(); + fs::SetEnabledAutoAbort(false); + + /* Create fs heap. */ + g_fs_heap_handle = lmem::CreateExpHeap(g_fs_heap, sizeof(g_fs_heap), lmem::CreateOption_None); + AMS_ABORT_UNLESS(g_fs_heap_handle != nullptr); + + /* Set fs allocator functions. */ + fs::SetAllocator(AllocateForFs, DeallocateForFs); + + /* Create SD card detection event notifier. */ + R_ABORT_UNLESS(fs::OpenSdCardDetectionEventNotifier(std::addressof(g_sd_card_detection_event_notifier))); + R_ABORT_UNLESS(g_sd_card_detection_event_notifier->BindEvent(g_sd_card_detection_event.GetBase(), os::EventClearMode_ManualClear)); + + /* Set connection observers. */ + SdCardLogger::GetInstance().SetLoggingObserver(SdLoggingObserver); + LogServerProxy::GetInstance().SetConnectionObserver(HostConnectionObserver); + + /* Do flush loop. */ + do { + if (LogBuffer::GetDefaultInstance().Flush()) { + EventLogTransmitter::GetDefaultInstance().PushLogPacketDropCountIfExists(); + } + } while (WaitForFlush()); + + /* Clear connection observer. */ + LogServerProxy::GetInstance().SetConnectionObserver(nullptr); + + /* Finalize the SD card logger. */ + SdCardLogger::GetInstance().Finalize(); + SdCardLogger::GetInstance().SetLoggingObserver(nullptr); + + /* Destroy the fs heap. */ + lmem::DestroyExpHeap(g_fs_heap_handle); + } + + } + + bool IsFlushAvailable() { + /* If we're sleeping, we can't flush. */ + if (IsSleeping()) { + return false; + } + + /* Try to wait for an event. */ + if (os::TryWaitAny(g_stop_event.GetBase(), g_host_connection_event.GetBase(), g_sd_logging_event.GetBase()) < 0) { + return false; + } + + /* Return whether we're not stopping. */ + return !os::TryWaitEvent(g_stop_event.GetBase()); + } + + void InitializeFlushThread() { + /* Create the flush thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(g_flush_thread), FlushThreadFunction, nullptr, g_flush_thread_stack, sizeof(g_flush_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(lm, Flush))); + os::SetThreadNamePointer(std::addressof(g_flush_thread), AMS_GET_SYSTEM_THREAD_NAME(lm, Flush)); + + /* Clear the stop event. */ + g_stop_event.Clear(); + + /* Start the flush thread. */ + os::StartThread(std::addressof(g_flush_thread)); + } + + void FinalizeFlushThread() { + /* Signal the flush thread to stop. */ + g_stop_event.Signal(); + + /* Wait for the flush thread to stop. */ + os::WaitThread(std::addressof(g_flush_thread)); + os::DestroyThread(std::addressof(g_flush_thread)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_ipc_server.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_ipc_server.cpp new file mode 100644 index 00000000..c019184e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_ipc_server.cpp @@ -0,0 +1,147 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../lm_service_name.hpp" +#include "lm_log_service_impl.hpp" +#include "lm_log_getter.hpp" + +namespace ams::lm::srv { + + namespace { + + constexpr inline size_t LogSessionCountMax = 42; + constexpr inline size_t LogGetterSessionCountMax = 1; + + constexpr inline size_t SessionCountMax = LogSessionCountMax + LogGetterSessionCountMax; + + constexpr inline size_t PortCountMax = 2; + + struct ServerManagerOptions { + static constexpr size_t PointerBufferSize = 0x400; + static constexpr size_t MaxDomains = 31; + static constexpr size_t MaxDomainObjects = 61; + static constexpr bool CanDeferInvokeRequest = false; + static constexpr bool CanManageMitmServers = false; + }; + + using ServerManager = sf::hipc::ServerManager<PortCountMax, ServerManagerOptions, SessionCountMax>; + + constinit util::TypedStorage<ServerManager> g_server_manager_storage = {}; + constinit ServerManager *g_server_manager = nullptr; + + constinit util::TypedStorage<psc::PmModule> g_pm_module_storage = {}; + constinit psc::PmModule *g_pm_module = nullptr; + constinit os::MultiWaitHolderType g_pm_module_holder = {}; + + constexpr const psc::PmModuleId PmModuleDependencies[] = { psc::PmModuleId_TmaHostIo, psc::PmModuleId_Fs }; + + /* Service objects. */ + constinit sf::UnmanagedServiceObject<lm::ILogService, lm::srv::LogServiceImpl> g_log_service_object; + constinit sf::UnmanagedServiceObject<lm::ILogGetter, lm::srv::LogGetter> g_log_getter_service_object; + + constinit std::atomic<bool> g_is_sleeping = false; + + } + + bool IsSleeping() { + return g_is_sleeping; + } + + void InitializeIpcServer() { + /* Check that we're not already initialized. */ + AMS_ABORT_UNLESS(g_server_manager == nullptr); + AMS_ABORT_UNLESS(g_pm_module == nullptr); + + /* Create and initialize the psc module. */ + g_pm_module = util::ConstructAt(g_pm_module_storage); + R_ABORT_UNLESS(g_pm_module->Initialize(psc::PmModuleId_Lm, PmModuleDependencies, util::size(PmModuleDependencies), os::EventClearMode_ManualClear)); + + /* Create the psc module multi wait holder. */ + os::InitializeMultiWaitHolder(std::addressof(g_pm_module_holder), g_pm_module->GetEventPointer()->GetBase()); + os::SetMultiWaitHolderUserData(std::addressof(g_pm_module_holder), psc::PmModuleId_Lm); + + /* Create the server manager. */ + g_server_manager = util::ConstructAt(g_server_manager_storage); + + /* Add the pm module holder. */ + g_server_manager->AddUserMultiWaitHolder(std::addressof(g_pm_module_holder)); + + /* Create services. */ + R_ABORT_UNLESS(g_server_manager->RegisterObjectForServer(g_log_service_object.GetShared(), LogServiceName, LogSessionCountMax)); + R_ABORT_UNLESS(g_server_manager->RegisterObjectForServer(g_log_getter_service_object.GetShared(), LogGetterServiceName, LogGetterSessionCountMax)); + + /* Start the server manager. */ + g_server_manager->ResumeProcessing(); + } + + void LoopIpcServer() { + /* Check that we're initialized. */ + AMS_ABORT_UNLESS(g_server_manager != nullptr); + + /* Loop forever, servicing the server. */ + auto prev_state = psc::PmState_Unknown; + while (true) { + /* Get the next signaled holder. */ + auto *signaled_holder = g_server_manager->WaitSignaled(); + if (signaled_holder != std::addressof(g_pm_module_holder)) { + /* If ipc, process. */ + R_ABORT_UNLESS(g_server_manager->Process(signaled_holder)); + } else { + /* If pm module, clear the event. */ + g_pm_module->GetEventPointer()->Clear(); + g_server_manager->AddUserMultiWaitHolder(signaled_holder); + + /* Get the power state. */ + psc::PmState pm_state; + psc::PmFlagSet pm_flags; + R_ABORT_UNLESS(g_pm_module->GetRequest(std::addressof(pm_state), std::addressof(pm_flags))); + + /* Handle the power state. */ + if (prev_state == psc::PmState_EssentialServicesAwake && pm_state == psc::PmState_MinimumAwake) { + g_is_sleeping = false; + } else if (prev_state == psc::PmState_MinimumAwake && pm_state == psc::PmState_SleepReady) { + g_is_sleeping = true; + } else if (pm_state == psc::PmState_ShutdownReady) { + g_is_sleeping = true; + } + + /* Set the previous state. */ + prev_state = pm_state; + + /* Acknowledge the state transition. */ + R_ABORT_UNLESS(g_pm_module->Acknowledge(pm_state, ResultSuccess())); + } + } + } + + void StopIpcServer() { + /* Check that we're initialized. */ + AMS_ABORT_UNLESS(g_server_manager != nullptr); + + /* Stop the server manager. */ + g_server_manager->RequestStopProcessing(); + } + + void FinalizeIpcServer() { + /* Check that we're initialized. */ + AMS_ABORT_UNLESS(g_server_manager != nullptr); + + /* Destroy the server manager. */ + std::destroy_at(g_server_manager); + g_server_manager = nullptr; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_buffer.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_buffer.cpp new file mode 100644 index 00000000..bc9b99e0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_buffer.cpp @@ -0,0 +1,209 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lm_log_buffer.hpp" +#include "lm_log_server_proxy.hpp" +#include "lm_sd_card_logger.hpp" +#include "lm_time_util.hpp" +#include "lm_log_packet_parser.hpp" + +namespace ams::lm::srv { + + namespace { + + void UpdateUserSystemClock(const u8 *data, size_t size) { + /* Get the current time. */ + const time::PosixTime current_time = GetCurrentTime(); + + /* Get the base time. */ + s64 base_time = current_time.value - os::GetSystemTick().ToTimeSpan().GetSeconds(); + + /* Modify the message timestamp. */ + LogPacketParser::ParsePacket(data, size, [](const impl::LogPacketHeader &header, const void *payload, size_t payload_size, void *arg) -> bool { + /* Check that we're a header message. */ + if (!header.IsHead()) { + return true; + } + + /* Find the timestamp data chunk. */ + return LogPacketParser::ParseDataChunk(payload, payload_size, [](impl::LogDataChunkKey key, const void *chunk, size_t chunk_size, void *arg) -> bool { + /* Convert the argument. */ + const s64 *p_base_time = static_cast<const s64 *>(arg); + + /* Modify user system clock. */ + if (key == impl::LogDataChunkKey_UserSystemClock) { + /* Get the time from the chunk. */ + s64 time; + AMS_ASSERT(chunk_size == sizeof(time)); + std::memcpy(std::addressof(time), chunk, chunk_size); + + /* Add the base time. */ + time += *p_base_time; + + /* Update the time in the chunk. */ + std::memcpy(const_cast<void *>(chunk), std::addressof(time), sizeof(time)); + } + + return true; + }, arg); + }, std::addressof(base_time)); + } + + bool DefaultFlushFunction(const u8 *data, size_t size) { + /* Declare persistent clock-updated state storage. */ + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(bool, s_is_user_system_clock_updated, false); + + /* Update clock. */ + if (!s_is_user_system_clock_updated) { + UpdateUserSystemClock(data, size); + s_is_user_system_clock_updated = true; + } + + /* Send the message. */ + const bool tma_success = LogServerProxy::GetInstance().Send(data, size); + const bool sd_success = SdCardLogger::GetInstance().Write(data, size); + const bool is_success = tma_success || sd_success; + + /* If we succeeded, wipe the current time. */ + s_is_user_system_clock_updated &= !is_success; + + return is_success; + } + + } + + LogBuffer &LogBuffer::GetDefaultInstance() { + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(u8, s_default_buffers[128_KB * 2]); + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(LogBuffer, s_default_log_buffer, s_default_buffers, sizeof(s_default_buffers), DefaultFlushFunction); + + return s_default_log_buffer; + } + + void LogBuffer::CancelPush() { + /* Acquire exclusive access to the push buffer. */ + std::scoped_lock lk(m_push_buffer_mutex); + + /* Cancel any pending pushes. */ + if (m_push_ready_wait_count > 0) { + m_push_canceled = true; + m_cv_push_ready.Broadcast(); + } + } + + bool LogBuffer::PushImpl(const void *data, size_t size, bool blocking) { + /* Check pre-conditions. */ + AMS_ASSERT(size <= m_buffer_size); + AMS_ASSERT(data != nullptr || size == 0); + + /* Check that we have data to push. */ + if (size == 0) { + return true; + } + + /* Wait to be able to push. */ + u8 *dst; + { + /* Acquire exclusive access to the push buffer. */ + std::scoped_lock lk(m_push_buffer_mutex); + + /* Wait for enough space to be available. */ + while (size > m_buffer_size - m_push_buffer->m_stored_size) { + /* Only block if we're allowed to. */ + if (!blocking) { + return false; + } + + /* Wait for push to be ready. */ + { + ++m_push_ready_wait_count; + m_cv_push_ready.Wait(m_push_buffer_mutex); + --m_push_ready_wait_count; + } + + /* Check if push was canceled. */ + if (m_push_canceled) { + if (m_push_ready_wait_count == 0) { + m_push_canceled = false; + } + + return false; + } + } + + /* Set the destination. */ + dst = m_push_buffer->m_head + m_push_buffer->m_stored_size; + + /* Advance the push buffer. */ + m_push_buffer->m_stored_size += size; + ++m_push_buffer->m_reference_count; + } + + /* Copy the data to the push buffer. */ + std::memcpy(dst, data, size); + + /* Close our push buffer reference, and signal that we can flush. */ + { + /* Acquire exclusive access to the push buffer. */ + std::scoped_lock lk(m_push_buffer_mutex); + + /* If there are no pending pushes, signal that we can flush. */ + if ((--m_push_buffer->m_reference_count) == 0) { + m_cv_flush_ready.Signal(); + } + } + + return true; + } + + bool LogBuffer::FlushImpl(bool blocking) { + /* Acquire exclusive access to the flush buffer. */ + std::scoped_lock lk(m_flush_buffer_mutex); + + /* If we don't have data to flush, wait for us to have data. */ + if (m_flush_buffer->m_stored_size == 0) { + /* Acquire exclusive access to the push buffer. */ + std::scoped_lock lk(m_push_buffer_mutex); + + /* Wait for there to be pushed data. */ + while (m_push_buffer->m_stored_size == 0 || m_push_buffer->m_reference_count != 0) { + /* Only block if we're allowed to. */ + if (!blocking) { + return false; + } + + /* Wait for us to be ready to flush. */ + m_cv_flush_ready.Wait(m_push_buffer_mutex); + } + + /* Swap the push buffer and the flush buffer pointers. */ + std::swap(m_push_buffer, m_flush_buffer); + + /* Signal that we can push. */ + m_cv_push_ready.Broadcast(); + } + + /* Flush any data. */ + if (!m_flush_function(m_flush_buffer->m_head, m_flush_buffer->m_stored_size)) { + return false; + } + + /* Reset the flush buffer. */ + m_flush_buffer->m_stored_size = 0; + + return true; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_buffer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_buffer.hpp new file mode 100644 index 00000000..1663c27a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_buffer.hpp @@ -0,0 +1,73 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::lm::srv { + + class LogBuffer { + NON_COPYABLE(LogBuffer); + NON_MOVEABLE(LogBuffer); + private: + struct BufferInfo { + u8 *m_head; + size_t m_stored_size; + size_t m_reference_count; + }; + public: + using FlushFunction = bool (*)(const u8 *data, size_t size); + private: + BufferInfo m_buffers[2]; + BufferInfo *m_push_buffer; + BufferInfo *m_flush_buffer; + size_t m_buffer_size; + FlushFunction m_flush_function; + os::SdkMutex m_push_buffer_mutex; + os::SdkMutex m_flush_buffer_mutex; + os::SdkConditionVariable m_cv_push_ready; + os::SdkConditionVariable m_cv_flush_ready; + bool m_push_canceled; + size_t m_push_ready_wait_count; + public: + constexpr explicit LogBuffer(u8 *buffer, size_t buffer_size, FlushFunction f) + : m_buffers{}, m_push_buffer(m_buffers + 0), m_flush_buffer(m_buffers + 1), + m_buffer_size(buffer_size / 2), m_flush_function(f), m_push_buffer_mutex{}, + m_flush_buffer_mutex{}, m_cv_push_ready{}, m_cv_flush_ready{}, + m_push_canceled(false), m_push_ready_wait_count(0) + { + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(buffer_size > 0); + AMS_ASSERT(f != nullptr); + + m_buffers[0].m_head = buffer; + m_buffers[1].m_head = buffer + (buffer_size / 2); + } + + static LogBuffer &GetDefaultInstance(); + + bool Push(const void *data, size_t size) { return this->PushImpl(data, size, true); } + bool TryPush(const void *data, size_t size) { return this->PushImpl(data, size, false); } + + void CancelPush(); + + bool Flush() { return this->FlushImpl(true); } + bool TryFlush() { return this->FlushImpl(false); } + private: + bool PushImpl(const void *data, size_t size, bool blocking); + bool FlushImpl(bool blocking); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_getter.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_getter.cpp new file mode 100644 index 00000000..64270d81 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_getter.cpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lm_log_getter.hpp" +#include "lm_log_getter_impl.hpp" + +namespace ams::lm::srv { + + extern bool g_is_logging_to_custom_sink; + + Result LogGetter::StartLogging() { + g_is_logging_to_custom_sink = true; + R_SUCCEED(); + } + + Result LogGetter::StopLogging() { + g_is_logging_to_custom_sink = false; + R_SUCCEED(); + } + + Result LogGetter::GetLog(const sf::OutAutoSelectBuffer &message, sf::Out<s64> out_size, sf::Out<u32> out_drop_count) { + /* Try to flush logs. */ + if (LogGetterImpl::GetBuffer().TryFlush()) { + *out_size = LogGetterImpl::GetLog(message.GetPointer(), message.GetSize(), out_drop_count.GetPointer()); + } else { + /* Otherwise, we got no data. */ + *out_size = 0; + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_getter.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_getter.hpp new file mode 100644 index 00000000..20467db2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_getter.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../sf/lm_i_log_getter.hpp" + +namespace ams::lm::srv { + + class LogGetter { + public: + Result StartLogging(); + Result StopLogging(); + Result GetLog(const sf::OutAutoSelectBuffer &message, sf::Out<s64> out_size, sf::Out<u32> out_drop_count); + }; + static_assert(lm::IsILogGetter<LogGetter>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_getter_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_getter_impl.cpp new file mode 100644 index 00000000..fc109cf8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_getter_impl.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lm_log_getter_impl.hpp" + +namespace ams::lm::srv { + + CustomSinkBuffer &LogGetterImpl::GetBuffer() { + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(u8, s_buffer[32_KB]); + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(CustomSinkBuffer, s_custom_sink_buffer, s_buffer, sizeof(s_buffer), FlushFunction); + + return s_custom_sink_buffer; + } + + s64 LogGetterImpl::GetLog(void *buffer, size_t buffer_size, u32 *out_drop_count) { + /* Check pre-condition. */ + AMS_ASSERT(buffer != nullptr); + + /* Determine how much we can get. */ + size_t min_size = s_buffer_size; + if (buffer_size < s_buffer_size) { + min_size = buffer_size; + IncreaseLogPacketDropCount(); + } + + /* Get the data. */ + std::memcpy(buffer, s_message, min_size); + + /* Set output drop count. */ + *out_drop_count = s_log_packet_drop_count; + s_log_packet_drop_count = 0; + + return min_size; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_getter_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_getter_impl.hpp new file mode 100644 index 00000000..4ccfe112 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_getter_impl.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "lm_custom_sink_buffer.hpp" + +namespace ams::lm::srv { + + class LogGetterImpl { + NON_COPYABLE(LogGetterImpl); + NON_MOVEABLE(LogGetterImpl); + private: + static constinit inline const u8 *s_message = nullptr; + static constinit inline size_t s_buffer_size = 0; + static constinit inline size_t s_log_packet_drop_count = 0; + private: + LogGetterImpl(); + public: + static CustomSinkBuffer &GetBuffer(); + static s64 GetLog(void *buffer, size_t buffer_size, u32 *out_drop_count); + + static void IncreaseLogPacketDropCount() { ++s_log_packet_drop_count; } + private: + static bool FlushFunction(const u8 *buffer, size_t buffer_size) { + s_message = buffer; + s_buffer_size = buffer_size; + return true; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_packet_parser.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_packet_parser.cpp new file mode 100644 index 00000000..1a0e95b3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_packet_parser.cpp @@ -0,0 +1,276 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lm_log_packet_parser.hpp" + +namespace ams::lm::srv { + + namespace { + + const u8 *ParseUleb128(u64 *out, const u8 *cur, const u8 *end) { + u64 value = 0; + size_t shift = 0; + while (cur < end && shift + 7 <= BITSIZEOF(u64)) { + value |= static_cast<u64>(*cur & 0x7F) << shift; + if ((*cur & 0x80) == 0) { + *out = value; + return cur; + } + + ++cur; + shift += 7; + } + + return end; + } + + } + + bool LogPacketParser::ParsePacket(const void *buffer, size_t buffer_size, ParsePacketCallback callback, void *arg) { + const u8 *cur = static_cast<const u8 *>(buffer); + const u8 *end = cur + buffer_size; + + while (cur < end) { + /* Check that we can parse a header. */ + size_t remaining_size = end - cur; + if (remaining_size < sizeof(impl::LogPacketHeader)) { + AMS_ASSERT(remaining_size >= sizeof(impl::LogPacketHeader)); + return false; + } + + /* Get the header. */ + impl::LogPacketHeader header; + std::memcpy(std::addressof(header), cur, sizeof(header)); + + /* Advance past the header. */ + cur += sizeof(header); + + /* Check that we can parse the payload. */ + const auto payload_size = header.GetPayloadSize(); + remaining_size = end - cur; + if (remaining_size < payload_size) { + AMS_ASSERT(remaining_size >= payload_size); + return false; + } + + /* Invoke the callback. */ + if (!callback(header, cur, payload_size, arg)) { + return false; + } + + /* Advance. */ + cur += payload_size; + } + + /* Check that we parsed all the data. */ + AMS_ASSERT(cur == end); + + return true; + } + + bool LogPacketParser::ParseDataChunk(const void *payload, size_t payload_size, ParseDataChunkCallback callback, void *arg) { + const u8 *cur = static_cast<const u8 *>(payload); + const u8 *end = cur + payload_size; + + while (cur < end) { + /* Get the key. */ + u64 key; + const auto key_last = ParseUleb128(std::addressof(key), cur, end); + if (key_last >= end) { + return false; + } + cur = key_last + 1; + + /* Get the size. */ + u64 size; + const auto size_last = ParseUleb128(std::addressof(size), cur, end); + if (size_last >= end) { + return false; + } + cur = size_last + 1; + + /* If we're in bounds, invoke the callback. */ + if (cur + size <= end && !callback(static_cast<impl::LogDataChunkKey>(key), cur, size, arg)) { + return false; + } + + cur += size; + } + + return true; + } + + bool LogPacketParser::FindDataChunk(const void **out, size_t *out_size, impl::LogDataChunkKey key, const void *buffer, size_t buffer_size) { + /* Create context for iteration. */ + struct FindDataChunkContext { + const void *chunk; + size_t chunk_size; + bool found; + impl::LogDataChunkKey key; + } context = { nullptr, 0, false, key }; + + /* Find the chunk. */ + LogPacketParser::ParsePacket(buffer, buffer_size, [](const impl::LogPacketHeader &header, const void *payload, size_t payload_size, void *arg) -> bool { + /* If the header isn't a header packet, continue. */ + if (!header.IsHead()) { + return true; + } + + return LogPacketParser::ParseDataChunk(payload, payload_size, [](impl::LogDataChunkKey cur_key, const void *chunk, size_t chunk_size, void *arg) -> bool { + /* Get the context. */ + auto *context = static_cast<FindDataChunkContext *>(arg); + + /* Check if we found the desired key. */ + if (context->key == cur_key) { + context->chunk = chunk; + context->chunk_size = chunk_size; + context->found = true; + return false; + } + + /* Otherwise, continue. */ + return true; + }, arg); + }, std::addressof(context)); + + /* Write the chunk we found. */ + if (context.found) { + *out = context.chunk; + *out_size = context.chunk_size; + return true; + } else { + return false; + } + } + + size_t LogPacketParser::ParseModuleName(char *dst, size_t dst_size, const void *buffer, size_t buffer_size) { + /* Check pre-conditions. */ + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(dst_size > 0); + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(buffer_size > 0); + + /* Find the relevant data chunk. */ + const void *chunk; + size_t chunk_size; + const bool found = LogPacketParser::FindDataChunk(std::addressof(chunk), std::addressof(chunk_size), impl::LogDataChunkKey_ModuleName, buffer, buffer_size); + if (!found || chunk_size == 0) { + dst[0] = '\x00'; + return 0; + } + + /* Copy as much of the module name as we can. */ + const size_t copy_size = std::min(chunk_size, dst_size - 1); + std::memcpy(dst, chunk, copy_size); + dst[copy_size] = '\x00'; + + return chunk_size; + } + + void LogPacketParser::ParseTextLogWithContext(const void *buffer, size_t buffer_size, ParseTextLogCallback callback, void *arg) { + /* Declare context for inter-call storage. */ + struct PreviousPacketContext { + u64 process_id; + u64 thread_id; + size_t carry_size; + bool ends_with_text_log; + }; + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(PreviousPacketContext, s_previous_packet_context); + + /* Get the packet header. */ + auto *header = static_cast<const impl::LogPacketHeader *>(buffer); + auto *payload = static_cast<const char *>(buffer) + impl::LogPacketHeaderSize; + auto payload_size = buffer_size - impl::LogPacketHeaderSize; + + /* Determine if the packet is a continuation. */ + const bool is_continuation = !header->IsHead() && header->GetProcessId() == s_previous_packet_context.process_id && header->GetThreadId() == s_previous_packet_context.thread_id; + + /* Require that the packet be a header or a continuation. */ + if (!header->IsHead() && !is_continuation) { + return; + } + + /* If the packet is a continuation, handle the leftover data. */ + if (is_continuation && s_previous_packet_context.carry_size > 0) { + /* Invoke the callback on what we can. */ + const size_t sendable = std::min(s_previous_packet_context.carry_size, payload_size); + if (s_previous_packet_context.ends_with_text_log) { + callback(payload, sendable, arg); + } + + /* Advance the leftover data. */ + s_previous_packet_context.carry_size -= sendable; + payload += sendable; + payload_size -= sendable; + } + + /* If we've sent the whole payload, we're done. */ + if (payload_size == 0) { + return; + } + + /* Parse the payload. */ + size_t carry_size = 0; + bool ends_with_text_log = false; + { + const u8 *cur = reinterpret_cast<const u8 *>(payload); + const u8 *end = cur + payload_size; + + while (cur < end) { + /* Get the key. */ + u64 key; + const auto key_last = ParseUleb128(std::addressof(key), cur, end); + if (key_last >= end) { + break; + } + cur = key_last + 1; + + /* Get the size. */ + u64 size; + const auto size_last = ParseUleb128(std::addressof(size), cur, end); + if (size_last >= end) { + break; + } + cur = size_last + 1; + + /* Process the data. */ + const bool is_text_log = static_cast<impl::LogDataChunkKey>(key) == impl::LogDataChunkKey_TextLog; + const size_t remaining = end - cur; + + if (size >= remaining) { + carry_size = size - remaining; + ends_with_text_log = is_text_log; + } + + if (is_text_log) { + const size_t sendable_size = std::min<size_t>(size, remaining); + callback(reinterpret_cast<const char *>(cur), sendable_size, arg); + } + + cur += size; + } + } + + /* If the packet isn't a tail packet, update the context. */ + if (!header->IsTail()) { + s_previous_packet_context.process_id = header->GetProcessId(); + s_previous_packet_context.thread_id = header->GetThreadId(); + s_previous_packet_context.carry_size = carry_size; + s_previous_packet_context.ends_with_text_log = ends_with_text_log; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_packet_parser.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_packet_parser.hpp new file mode 100644 index 00000000..db461836 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_packet_parser.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../impl/lm_log_data_chunk.hpp" +#include "../impl/lm_log_packet_header.hpp" + +namespace ams::lm::srv { + + class LogPacketParser { + public: + using ParsePacketCallback = bool (*)(const impl::LogPacketHeader &header, const void *payload, size_t payload_size, void *arg); + using ParseDataChunkCallback = bool (*)(impl::LogDataChunkKey key, const void *chunk, size_t chunk_size, void *arg); + using ParseTextLogCallback = void (*)(const char *txt, size_t size, void *arg); + public: + static bool ParsePacket(const void *buffer, size_t buffer_size, ParsePacketCallback callback, void *arg); + static bool ParseDataChunk(const void *payload, size_t payload_size, ParseDataChunkCallback callback, void *arg); + + static bool FindDataChunk(const void **out, size_t *out_size, impl::LogDataChunkKey key, const void *buffer, size_t buffer_size); + + static size_t ParseModuleName(char *dst, size_t dst_size, const void *buffer, size_t buffer_size); + + static void ParseTextLogWithContext(const void *buffer, size_t buffer_size, ParseTextLogCallback callback, void *arg); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_server_proxy.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_server_proxy.cpp new file mode 100644 index 00000000..9e5af366 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_server_proxy.cpp @@ -0,0 +1,218 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lm_log_server_proxy.hpp" + +namespace ams::lm::srv { + + namespace { + + constexpr inline const char PortName[] = "iywys@$LogManager"; + constexpr inline const int HtcsSessionCountMax = 2; + constexpr inline const TimeSpan PollingInterval = TimeSpan::FromSeconds(1); + + constinit u8 g_htcs_heap_buffer[2_KB]; + + constexpr inline const int InvalidHtcsSocket = -1; + + constexpr ALWAYS_INLINE bool IsValidHtcsSocket(int socket) { + return socket >= 0; + } + + static_assert(!IsValidHtcsSocket(InvalidHtcsSocket)); + + bool IsHtcEnabled() { + u8 enable_htc = 0; + settings::fwdbg::GetSettingsItemValue(&enable_htc, sizeof(enable_htc), "atmosphere", "enable_htc"); + return enable_htc != 0; + } + + } + + LogServerProxy::LogServerProxy() : m_cv_connected(), m_stop_event(os::EventClearMode_ManualClear), m_connection_mutex(), m_observer_mutex(), m_server_socket(InvalidHtcsSocket), m_client_socket(InvalidHtcsSocket), m_connection_observer(nullptr) { + /* ... */ + } + + void LogServerProxy::Start() { + /* Create thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_thread), [](void *_this) { static_cast<LogServerProxy *>(_this)->LoopAuto(); }, this, m_thread_stack, sizeof(m_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(lm, HtcsConnection))); + + /* Set thread name pointer. */ + os::SetThreadNamePointer(std::addressof(m_thread), AMS_GET_SYSTEM_THREAD_NAME(lm, HtcsConnection)); + + /* Clear stop event. */ + m_stop_event.Clear(); + + /* Start thread. */ + os::StartThread(std::addressof(m_thread)); + } + + void LogServerProxy::Stop() { + /* Signal to connection thread to stop. */ + m_stop_event.Signal(); + + /* Close client socket. */ + if (const int client_socket = m_client_socket; client_socket >= 0) { + htcs::Close(client_socket); + } + + /* Close server socket. */ + if (const int server_socket = m_server_socket; server_socket >= 0) { + htcs::Close(server_socket); + } + + /* Wait for the connection thread to exit. */ + os::WaitThread(std::addressof(m_thread)); + os::DestroyThread(std::addressof(m_thread)); + } + + bool LogServerProxy::IsConnected() { + /* Return whether there's a valid client socket. */ + return IsValidHtcsSocket(m_client_socket); + } + + void LogServerProxy::SetConnectionObserver(ConnectionObserver observer) { + /* Acquire exclusive access to observer data. */ + std::scoped_lock lk(m_observer_mutex); + + /* Set the observer. */ + m_connection_observer = observer; + } + + bool LogServerProxy::Send(const u8 *data, size_t size) { + /* Send as much data as we can, until it's all send. */ + size_t offset = 0; + while (this->IsConnected() && offset < size) { + /* Try to send the remaining data. */ + if (const auto result = htcs::Send(m_client_socket, data + offset, size - offset, 0); result >= 0) { + /* Advance. */ + offset += static_cast<size_t>(result); + } else { + /* We failed to send data, shutdown the socket. */ + htcs::Shutdown(m_client_socket, htcs::HTCS_SHUT_RDWR); + + /* Wait a second, before returning to the caller. */ + os::SleepThread(TimeSpan::FromSeconds(1)); + + return false; + } + } + + /* Return whether we sent all the data. */ + AMS_ASSERT(offset <= size); + return offset == size; + } + + void LogServerProxy::LoopAuto() { + /* If we're not using htcs, there's nothing to do. */ + if (!IsHtcEnabled()) { + return; + } + + /* Check that we have enough working memory. */ + const auto working_memory_size = htcs::GetWorkingMemorySize(HtcsSessionCountMax); + AMS_ABORT_UNLESS(working_memory_size <= sizeof(g_htcs_heap_buffer)); + + /* Initialize htcs for the duration that we loop. */ + htcs::InitializeForDisableDisconnectionEmulation(g_htcs_heap_buffer, working_memory_size); + ON_SCOPE_EXIT { htcs::Finalize(); }; + + /* Setup socket address. */ + htcs::SockAddrHtcs server_address; + server_address.family = htcs::HTCS_AF_HTCS; + server_address.peer_name = htcs::GetPeerNameAny(); + std::strcpy(server_address.port_name.name, PortName); + + /* Manage htcs connections until a stop is requested. */ + do { + /* Create a server socket. */ + const auto server_socket = htcs::Socket(); + if (!IsValidHtcsSocket(server_socket)) { + continue; + } + m_server_socket = server_socket; + + /* Ensure we cleanup the server socket when done. */ + ON_SCOPE_EXIT { + htcs::Close(server_socket); + m_server_socket = InvalidHtcsSocket; + }; + + /* Bind to the server socket. */ + if (htcs::Bind(server_socket, std::addressof(server_address)) != 0) { + continue; + } + + /* Listen on the server socket. */ + if (htcs::Listen(server_socket, 0) != 0) { + continue; + } + + /* Loop on clients, until we're asked to stop. */ + while (!m_stop_event.TryWait()) { + /* Accept a client socket. */ + const auto client_socket = htcs::Accept(server_socket, nullptr); + if (!IsValidHtcsSocket(client_socket)) { + break; + } + m_client_socket = client_socket; + + /* Note that we're connected. */ + this->InvokeConnectionObserver(true); + this->SignalConnection(); + + /* Ensure we cleanup the client socket when done. */ + ON_SCOPE_EXIT { + htcs::Close(client_socket); + m_client_socket = InvalidHtcsSocket; + + this->InvokeConnectionObserver(false); + }; + + /* Receive data (and do nothing with it), so long as we're connected. */ + u8 v; + while (htcs::Recv(client_socket, std::addressof(v), sizeof(v), 0) == sizeof(v)) { /* ... */ } + } + } while (!m_stop_event.TimedWait(PollingInterval)); + } + + void LogServerProxy::SignalConnection() { + /* Acquire exclusive access to observer data. */ + std::scoped_lock lk(m_connection_mutex); + + /* Broadcast to our connected cv. */ + m_cv_connected.Broadcast(); + } + + void LogServerProxy::InvokeConnectionObserver(bool connected) { + /* Acquire exclusive access to observer data. */ + std::scoped_lock lk(m_observer_mutex); + + /* If we have an observer, observe the connection state. */ + if (m_connection_observer) { + m_connection_observer(connected); + } + } + + void StartLogServerProxy() { + LogServerProxy::GetInstance().Start(); + } + + void StopLogServerProxy() { + LogServerProxy::GetInstance().Stop(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_server_proxy.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_server_proxy.hpp new file mode 100644 index 00000000..b21f951d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_server_proxy.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::lm::srv { + + class LogServerProxy { + AMS_SINGLETON_TRAITS(LogServerProxy); + public: + using ConnectionObserver = void (*)(bool connected); + private: + alignas(os::ThreadStackAlignment) u8 m_thread_stack[4_KB]; + os::ThreadType m_thread; + os::SdkConditionVariable m_cv_connected; + os::Event m_stop_event; + os::SdkMutex m_connection_mutex; + os::SdkMutex m_observer_mutex; + std::atomic<int> m_server_socket; + std::atomic<int> m_client_socket; + ConnectionObserver m_connection_observer; + public: + void Start(); + void Stop(); + + bool IsConnected(); + + void SetConnectionObserver(ConnectionObserver observer); + + bool Send(const u8 *data, size_t size); + private: + void LoopAuto(); + void SignalConnection(); + void InvokeConnectionObserver(bool connected); + }; + + void StartLogServerProxy(); + void StopLogServerProxy(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_service_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_service_impl.cpp new file mode 100644 index 00000000..5bd7734b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_service_impl.cpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lm_log_service_impl.hpp" +#include "lm_logger_impl.hpp" + +namespace ams::lm::srv { + + namespace { + + struct LoggerImplAllocatorTag; + using LoggerAllocator = ams::sf::ExpHeapStaticAllocator<3_KB, LoggerImplAllocatorTag>; + using LoggerObjectFactory = ams::sf::ObjectFactory<typename LoggerAllocator::Policy>; + + class StaticAllocatorInitializer { + public: + StaticAllocatorInitializer() { + LoggerAllocator::Initialize(lmem::CreateOption_None); + } + } g_static_allocator_initializer; + + } + + Result LogServiceImpl::OpenLogger(sf::Out<sf::SharedPointer<::ams::lm::ILogger>> out, const sf::ClientProcessId &client_process_id) { + /* Open logger. */ + out.SetValue(LoggerObjectFactory::CreateSharedEmplaced<::ams::lm::ILogger, LoggerImpl>(this, client_process_id.GetValue())); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_service_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_service_impl.hpp new file mode 100644 index 00000000..0e9c5793 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_log_service_impl.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../sf/lm_i_log_service.hpp" + +namespace ams::lm::srv { + + class LogServiceImpl { + public: + Result OpenLogger(sf::Out<sf::SharedPointer<::ams::lm::ILogger>> out, const sf::ClientProcessId &client_process_id); + }; + static_assert(lm::IsILogService<LogServiceImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_logger_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_logger_impl.cpp new file mode 100644 index 00000000..9b5cc8b4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_logger_impl.cpp @@ -0,0 +1,151 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lm_logger_impl.hpp" +#include "lm_event_log_transmitter.hpp" +#include "lm_log_buffer.hpp" +#include "lm_log_packet_parser.hpp" +#include "lm_log_getter_impl.hpp" +#include "../impl/lm_log_packet_header.hpp" + +namespace ams::lm::srv { + + bool IsFlushAvailable(); + + bool g_is_logging_to_custom_sink = false; + + namespace { + + constinit u32 g_log_destination = lm::LogDestination_TargetManager; + + bool SetProcessId(const sf::InAutoSelectBuffer &message, u64 process_id) { + /* Check the message. */ + AMS_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(message.GetPointer()), alignof(impl::LogPacketHeader))); + + /* Get a modifiable copy of the header. */ + auto *header = const_cast<impl::LogPacketHeader *>(reinterpret_cast<const impl::LogPacketHeader *>(message.GetPointer())); + + /* Check that the message size is correct. */ + if (impl::LogPacketHeaderSize + header->GetPayloadSize() != message.GetSize()) { + return false; + } + + /* Set the header's process id. */ + header->SetProcessId(process_id); + + return true; + } + + void PutLogToTargetManager(const sf::InAutoSelectBuffer &message) { + /* Try to push the message. */ + bool success; + if (IsFlushAvailable()) { + success = LogBuffer::GetDefaultInstance().Push(message.GetPointer(), message.GetSize()); + } else { + success = LogBuffer::GetDefaultInstance().TryPush(message.GetPointer(), message.GetSize()); + } + + /* If we fail, increment dropped packet count. */ + if (!success) { + EventLogTransmitter::GetDefaultInstance().IncreaseLogPacketDropCount(); + } + } + + void PutLogToUart(const sf::InAutoSelectBuffer &message) { + #if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING) + { + /* Get header. */ + auto *data = message.GetPointer(); + auto data_size = message.GetSize(); + const auto *header = reinterpret_cast<const impl::LogPacketHeader *>(data); + + /* Get the module name. */ + char module_name[0x10] = {}; + LogPacketParser::ParseModuleName(module_name, sizeof(module_name), data, data_size); + + /* Create log metadata. */ + const diag::LogMetaData log_meta = { + .module_name = module_name, + .severity = static_cast<diag::LogSeverity>(header->GetSeverity()), + .verbosity = header->GetVerbosity(), + }; + + LogPacketParser::ParseTextLogWithContext(message.GetPointer(), message.GetSize(), [](const char *txt, size_t size, void *arg) { + /* Get metadata. */ + const auto &meta = *static_cast<const diag::LogMetaData *>(arg); + + /* Put the message to uart. */ + diag::impl::PutImpl(meta, txt, size); + }, const_cast<diag::LogMetaData *>(std::addressof(log_meta))); + } + #else + { + AMS_UNUSED(message); + } + #endif + } + + void PutLogToCustomSink(const sf::InAutoSelectBuffer &message) { + LogPacketParser::ParseTextLogWithContext(message.GetPointer(), message.GetSize(), [](const char *txt, size_t size, void *) { + /* Try to push the message. */ + if (!LogGetterImpl::GetBuffer().TryPush(txt, size)) { + LogGetterImpl::IncreaseLogPacketDropCount(); + } + }, nullptr); + } + + } + + LoggerImpl::LoggerImpl(LogServiceImpl *parent, os::ProcessId process_id) : m_parent(parent), m_process_id(process_id.value) { + /* Log start of session for process. */ + EventLogTransmitter::GetDefaultInstance().PushLogSessionBegin(m_process_id); + } + + LoggerImpl::~LoggerImpl() { + /* Log end of session for process. */ + EventLogTransmitter::GetDefaultInstance().PushLogSessionEnd(m_process_id); + } + + Result LoggerImpl::Log(const sf::InAutoSelectBuffer &message) { + /* Try to set the log process id. */ + /* NOTE: Nintendo succeeds here, for whatever purpose, so we will as well. */ + R_UNLESS(SetProcessId(message, m_process_id), ResultSuccess()); + + /* If we should, log to target manager. */ + if (g_log_destination & lm::LogDestination_TargetManager) { + PutLogToTargetManager(message); + } + + /* If we should, log to uart. */ + if ((g_log_destination & lm::LogDestination_Uart) || (IsFlushAvailable() && (g_log_destination & lm::LogDestination_UartIfSleep))) { + PutLogToUart(message); + } + + /* If we should, log to custom sink. */ + if (g_is_logging_to_custom_sink) { + PutLogToCustomSink(message); + } + + R_SUCCEED(); + } + + Result LoggerImpl::SetDestination(u32 destination) { + /* Set the log destination. */ + g_log_destination = destination; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_logger_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_logger_impl.hpp new file mode 100644 index 00000000..4669d42a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_logger_impl.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "lm_log_service_impl.hpp" + +namespace ams::lm::srv { + + class LoggerImpl { + private: + LogServiceImpl *m_parent; + u64 m_process_id; + public: + explicit LoggerImpl(LogServiceImpl *parent, os::ProcessId process_id); + ~LoggerImpl(); + public: + Result Log(const sf::InAutoSelectBuffer &message); + Result SetDestination(u32 destination); + }; + static_assert(lm::IsILogger<LoggerImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_sd_card_logger.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_sd_card_logger.cpp new file mode 100644 index 00000000..6ba8c437 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_sd_card_logger.cpp @@ -0,0 +1,357 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lm_sd_card_logger.hpp" +#include "lm_time_util.hpp" + +namespace ams::lm::srv { + + namespace { + + constexpr const char SdCardMountName[] = "sdcard"; + constexpr const char LogFileExtension[] = "nxbinlog"; + + constexpr const char SettingName[] = "lm"; + constexpr const char SettingKeyLoggingEnabled[] = "enable_sd_card_logging"; + constexpr const char SettingKeyOutputDirectory[] = "sd_card_log_output_directory"; + + constexpr inline size_t LogFileHeaderSize = 8; + constexpr inline u32 LogFileHeaderMagic = util::ReverseFourCC<'p','h','p','h'>::Code; + constexpr inline u8 LogFileHeaderVersion = 1; + + struct LogFileHeader { + u32 magic; + u8 version; + u8 reserved[3]; + }; + static_assert(sizeof(LogFileHeader) == LogFileHeaderSize); + + constinit os::SdkMutex g_sd_card_logging_enabled_mutex; + constinit bool g_determined_sd_card_logging_enabled = false; + constinit bool g_sd_card_logging_enabled = false; + + constinit os::SdkMutex g_sd_card_detection_event_mutex; + constinit bool g_sd_card_inserted_cache = false; + constinit bool g_sd_card_detection_event_initialized = false; + constinit std::unique_ptr<fs::IEventNotifier> g_sd_card_detection_event_notifier; + os::SystemEvent g_sd_card_detection_event; + + bool GetSdCardLoggingEnabledImpl() { + bool enabled; + const auto size = settings::fwdbg::GetSettingsItemValue(std::addressof(enabled), sizeof(enabled), SettingName, SettingKeyLoggingEnabled); + if (size != sizeof(enabled)) { + AMS_ASSERT(size == sizeof(enabled)); + return false; + } + + return enabled; + } + + bool GetSdCardLoggingEnabled() { + if (AMS_UNLIKELY(!g_determined_sd_card_logging_enabled)) { + std::scoped_lock lk(g_sd_card_logging_enabled_mutex); + + if (AMS_LIKELY(!g_determined_sd_card_logging_enabled)) { + g_sd_card_logging_enabled = GetSdCardLoggingEnabledImpl(); + g_determined_sd_card_logging_enabled = true; + } + } + + return g_sd_card_logging_enabled; + } + + void EnsureSdCardDetectionEventInitialized() { + if (AMS_UNLIKELY(!g_sd_card_detection_event_initialized)) { + std::scoped_lock lk(g_sd_card_detection_event_mutex); + + if (AMS_LIKELY(!g_sd_card_detection_event_initialized)) { + /* Create SD card detection event notifier. */ + R_ABORT_UNLESS(fs::OpenSdCardDetectionEventNotifier(std::addressof(g_sd_card_detection_event_notifier))); + R_ABORT_UNLESS(g_sd_card_detection_event_notifier->BindEvent(g_sd_card_detection_event.GetBase(), os::EventClearMode_ManualClear)); + + /* Get initial inserted value. */ + g_sd_card_inserted_cache = fs::IsSdCardInserted(); + + g_sd_card_detection_event_initialized = true; + } + } + } + + void GetSdCardStatus(bool *out_inserted, bool *out_status_changed) { + /* Ensure that we can detect the sd card. */ + EnsureSdCardDetectionEventInitialized(); + + /* Check if there's a detection event. */ + const bool status_changed = g_sd_card_detection_event.TryWait(); + if (status_changed) { + g_sd_card_detection_event.Clear(); + + /* Update the inserted cache. */ + g_sd_card_inserted_cache = fs::IsSdCardInserted(); + } + + *out_inserted = g_sd_card_inserted_cache; + *out_status_changed = status_changed; + } + + bool GetSdCardLogOutputDirectory(char *dst, size_t size) { + /* Get the output directory size. */ + const auto value_size = settings::fwdbg::GetSettingsItemValueSize(SettingName, SettingKeyOutputDirectory); + if (value_size > size) { + AMS_ASSERT(value_size <= size); + return false; + } + + /* Get the output directory. */ + const auto read_size = settings::fwdbg::GetSettingsItemValue(dst, size, SettingName, SettingKeyOutputDirectory); + AMS_ASSERT(read_size == value_size); + + return read_size == value_size; + } + + bool EnsureLogDirectory(const char *dir) { + /* Generate the log directory path. */ + char path[0x80]; + const size_t len = util::SNPrintf(path, sizeof(path), "%s:/%s", SdCardMountName, dir); + if (len >= sizeof(path)) { + AMS_ASSERT(len < sizeof(path)); + return false; + } + + /* Ensure the directory. */ + /* NOTE: Nintendo does not perform recusrive directory ensure, only a single CreateDirectory level. */ + return R_SUCCEEDED(fs::EnsureDirectory(path)); + } + + bool MakeLogFilePathWithoutExtension(char *dst, size_t size, const char *dir) { + /* Get the current time. */ + const auto cur_time = time::ToCalendarTimeInUtc(lm::srv::GetCurrentTime()); + + /* Get the device serial number. */ + settings::system::SerialNumber serial_number; + settings::system::GetSerialNumber(std::addressof(serial_number)); + + /* Print the path. */ + const size_t len = util::SNPrintf(dst, size, "%s:/%s/%s_%04d%02d%02d%02d%02d%02d", SdCardMountName, dir, serial_number.str, cur_time.year, cur_time.month, cur_time.day, cur_time.hour, cur_time.minute, cur_time.second); + + AMS_ASSERT(len < size); + return len < size; + } + + bool GenerateLogFile(char *dst, size_t size, const char *dir) { + /* Generate the log file path. */ + char path_without_ext[0x80]; + if (!MakeLogFilePathWithoutExtension(path_without_ext, sizeof(path_without_ext), dir)) { + return false; + } + + /* Try to find an available log file path. */ + constexpr auto MaximumLogIndex = 99; + for (auto i = 1; i <= MaximumLogIndex; ++i) { + /* Print the current log file path. */ + const size_t len = (i == 1) ? util::SNPrintf(dst, size, "%s.%s", path_without_ext, LogFileExtension) : util::SNPrintf(dst, size, "%s_%d.%s", path_without_ext, i, LogFileExtension); + if (len >= size) { + AMS_ASSERT(len < size); + return false; + } + + /* Try to create the log file. */ + const auto result = fs::CreateFile(dst, 0); + if (R_SUCCEEDED(result)) { + return true; + } else if (fs::ResultPathAlreadyExists::Includes(result)) { + /* The log file already exists, so try the next index. */ + continue; + } else { + /* We failed to create a log file. */ + return false; + } + } + + /* We ran out of log file indices. */ + return false; + } + + Result WriteLogFileHeaderImpl(const char *path) { + /* Open the log file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Write | fs::OpenMode_AllowAppend)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Write the log file header. */ + const LogFileHeader header = { + .magic = LogFileHeaderMagic, + .version = LogFileHeaderVersion + }; + + R_RETURN(fs::WriteFile(file, 0, std::addressof(header), sizeof(header), fs::WriteOption::Flush)); + } + + bool WriteLogFileHeader(const char *path) { + return R_SUCCEEDED(WriteLogFileHeaderImpl(path)); + } + + Result WriteLogFileBodyImpl(const char *path, s64 offset, const u8 *data, size_t size) { + /* Open the log file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Write | fs::OpenMode_AllowAppend)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Write the data. */ + R_RETURN(fs::WriteFile(file, offset, data, size, fs::WriteOption::Flush)); + } + + bool WriteLogFileBody(const char *path, s64 offset, const u8 *data, size_t size) { + return R_SUCCEEDED(WriteLogFileBodyImpl(path, offset, data, size)); + } + + } + + SdCardLogger::SdCardLogger() : m_logging_observer_mutex(), m_is_enabled(false), m_is_sd_card_mounted(false), m_is_sd_card_status_unknown(false), m_log_file_offset(0), m_logging_observer(nullptr) { + /* ... */ + } + + bool SdCardLogger::GetEnabled() const { + return m_is_enabled; + } + + void SdCardLogger::SetEnabled(bool enabled) { + /* Only update if we need to. */ + if (m_is_enabled == enabled) { + return; + } + + /* Set enabled. */ + m_is_enabled = enabled; + + /* Invoke our observer. */ + std::scoped_lock lk(m_logging_observer_mutex); + + if (m_logging_observer) { + m_logging_observer(enabled); + } + } + + void SdCardLogger::SetLoggingObserver(LoggingObserver observer) { + std::scoped_lock lk(m_logging_observer_mutex); + + m_logging_observer = observer; + } + + bool SdCardLogger::Initialize() { + /* If we're already enabled, nothing to do. */ + if (this->GetEnabled()) { + return true; + } + + /* Get the sd card status. */ + bool inserted = false, status_changed = false; + GetSdCardStatus(std::addressof(inserted), std::addressof(status_changed)); + + /* Update whether status is known. */ + if (status_changed) { + m_is_sd_card_status_unknown = false; + } + + /* If the SD isn't inserted, we can't initialize. */ + if (!inserted) { + return false; + } + + /* If the status is unknown, we can't initialize. */ + if (m_is_sd_card_status_unknown) { + return false; + } + + /* Mount the SD card. */ + if (R_FAILED(fs::MountSdCard(SdCardMountName))) { + return false; + } + + /* Note that the SD card is mounted. */ + m_is_sd_card_mounted = true; + + /* Get the output directory. */ + char output_dir[0x80]; + if (!GetSdCardLogOutputDirectory(output_dir, sizeof(output_dir))) { + return false; + } + + /* Ensure the output directory exists. */ + if (!EnsureLogDirectory(output_dir)) { + return false; + } + + /* Ensure that a log file exists for us to write to. */ + if (!GenerateLogFile(m_log_file_path, sizeof(m_log_file_path), output_dir)) { + return false; + } + + /* Write the log file header. */ + if (!WriteLogFileHeader(m_log_file_path)) { + return false; + } + + /* Set our initial offset. */ + m_log_file_offset = LogFileHeaderSize; + + return true; + } + + void SdCardLogger::Finalize() { + this->SetEnabled(false); + if (m_is_sd_card_mounted) { + fs::Unmount(SdCardMountName); + m_is_sd_card_mounted = false; + } + } + + bool SdCardLogger::Write(const u8 *data, size_t size) { + /* Only write if sd card logging is enabled. */ + if (!GetSdCardLoggingEnabled()) { + return false; + } + + /* Ensure we keep our pre and post-conditions in check. */ + bool success = false; + ON_SCOPE_EXIT { + if (!success && m_is_sd_card_mounted) { + fs::Unmount(SdCardMountName); + m_is_sd_card_mounted = false; + m_is_sd_card_status_unknown = true; + } + this->SetEnabled(success); + }; + + /* Try to initialize. */ + if (!this->Initialize()) { + return false; + } + + /* Try to write the log file. */ + if (!WriteLogFileBody(m_log_file_path, m_log_file_offset, data, size)) { + return false; + } + + /* Advance. */ + m_log_file_offset += size; + + /* We succeeded. */ + success = true; + return true; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_sd_card_logger.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_sd_card_logger.hpp new file mode 100644 index 00000000..c009126e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_sd_card_logger.hpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::lm::srv { + + class SdCardLogger { + AMS_SINGLETON_TRAITS(SdCardLogger); + public: + using LoggingObserver = void (*)(bool available); + private: + os::SdkMutex m_logging_observer_mutex; + bool m_is_enabled; + bool m_is_sd_card_mounted; + bool m_is_sd_card_status_unknown; + char m_log_file_path[0x80]; + s64 m_log_file_offset; + LoggingObserver m_logging_observer; + public: + void Finalize(); + + void SetLoggingObserver(LoggingObserver observer); + + bool Write(const u8 *data, size_t size); + private: + bool GetEnabled() const; + void SetEnabled(bool enabled); + + bool Initialize(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_time_util.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_time_util.cpp new file mode 100644 index 00000000..68471a33 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_time_util.cpp @@ -0,0 +1,78 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lm_time_util.hpp" + +namespace ams::lm::srv { + + namespace { + + constinit std::atomic_bool g_is_time_invalid = false; + + constinit time::PosixTime InvalidPosixTime = { .value = 0 }; + + constexpr bool IsValidPosixTime(time::PosixTime time) { + return time.value > 0; + } + + void EnsureTimeInitialized() { + static constinit os::SdkMutex g_time_initialized_mutex; + static constinit bool g_time_initialized = false; + + if (AMS_UNLIKELY(!g_time_initialized)) { + std::scoped_lock lk(g_time_initialized_mutex); + + if (AMS_LIKELY(!g_time_initialized)) { + R_ABORT_UNLESS(time::Initialize()); + g_time_initialized = true; + } + } + } + + } + + time::PosixTime GetCurrentTime() { + /* Ensure that we can use time services. */ + EnsureTimeInitialized(); + + /* Repeatedly try to get a valid time. */ + for (auto wait_seconds = 1; wait_seconds <= 8; wait_seconds *= 2) { + /* Get the standard user system clock time. */ + time::PosixTime current_time{}; + if (R_FAILED(time::StandardUserSystemClock::GetCurrentTime(std::addressof(current_time)))) { + return InvalidPosixTime; + } + + /* If the time is valid, return it. */ + if (IsValidPosixTime(current_time)) { + return current_time; + } + + /* Check if we've failed to get a time in the past. */ + if (g_is_time_invalid) { + return InvalidPosixTime; + } + + /* Wait a bit before trying again. */ + os::SleepThread(TimeSpan::FromSeconds(wait_seconds)); + } + + /* We failed to get a valid time. */ + g_is_time_invalid = true; + return InvalidPosixTime; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_time_util.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_time_util.hpp new file mode 100644 index 00000000..0df1c2ae --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lm/srv/lm_time_util.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::lm::srv { + + time::PosixTime GetCurrentTime(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/impl/lmem_impl_common_heap.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/impl/lmem_impl_common_heap.cpp new file mode 100644 index 00000000..8a904208 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/impl/lmem_impl_common_heap.cpp @@ -0,0 +1,93 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lmem_impl_common_heap.hpp" + +namespace ams::lmem::impl { + + namespace { + + u32 g_fill_values[FillType_Count] = { + 0xC3C3C3C3, /* FillType_Unallocated */ + 0xF3F3F3F3, /* FillType_Allocated */ + 0xD3D3D3D3, /* FillType_Freed */ + }; + + } + + void InitializeHeapHead(HeapHead *out, u32 magic, void *start, void *end, u32 option) { + /* Call member constructors. */ + std::construct_at(std::addressof(out->list_node)); + std::construct_at(std::addressof(out->child_list)); + + /* Set fields. */ + out->magic = magic; + out->heap_start = start; + out->heap_end = end; + out->option = static_cast<u8>(option); + + /* Fill memory with pattern if needed. */ + FillUnallocatedMemory(out, start, GetPointerDifference(start, end)); + } + + void FinalizeHeap(HeapHead *heap) { + /* Nothing actually needs to be done here. */ + AMS_UNUSED(heap); + } + + bool ContainsAddress(HeapHandle handle, const void *address) { + const uintptr_t uptr_handle = reinterpret_cast<uintptr_t>(handle); + const uintptr_t uptr_start = reinterpret_cast<uintptr_t>(handle->heap_start); + const uintptr_t uptr_end = reinterpret_cast<uintptr_t>(handle->heap_end); + const uintptr_t uptr_addr = reinterpret_cast<uintptr_t>(address); + + if (uptr_start - sizeof(HeapHead) == uptr_handle) { + /* The heap head is at the start of the managed memory. */ + return uptr_handle <= uptr_addr && uptr_addr < uptr_end; + } else if (uptr_handle == uptr_end) { + /* The heap head is at the end of the managed memory. */ + return uptr_start <= uptr_addr && uptr_addr < uptr_end + sizeof(HeapHead); + } else { + /* Heap head is somewhere unrelated to managed memory. */ + return uptr_start <= uptr_addr && uptr_addr < uptr_end; + } + } + + size_t GetHeapTotalSize(HeapHandle handle) { + const uintptr_t uptr_start = reinterpret_cast<uintptr_t>(handle->heap_start); + const uintptr_t uptr_end = reinterpret_cast<uintptr_t>(handle->heap_end); + + if (ContainsAddress(handle, reinterpret_cast<const void *>(handle))) { + /* The heap metadata is contained within the heap, either before or after. */ + return static_cast<size_t>(uptr_end - uptr_start + sizeof(HeapHead)); + } else { + /* The heap metadata is not contained within the heap. */ + return static_cast<size_t>(uptr_end - uptr_start); + } + } + + u32 GetDebugFillValue(FillType type) { + return g_fill_values[type]; + } + + u32 SetDebugFillValue(FillType type, u32 value) { + const u32 old_value = g_fill_values[type]; + g_fill_values[type] = value; + return old_value; + } + + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/impl/lmem_impl_common_heap.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/impl/lmem_impl_common_heap.hpp new file mode 100644 index 00000000..f05361de --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/impl/lmem_impl_common_heap.hpp @@ -0,0 +1,98 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::lmem::impl { + + constexpr inline u32 ExpHeapMagic = util::ReverseFourCC<'E','X','P','H'>::Code; + constexpr inline u32 FrameHeapMagic = util::ReverseFourCC<'F','R','M','H'>::Code; + constexpr inline u32 UnitHeapMagic = util::ReverseFourCC<'U','N','T','H'>::Code; + + class ScopedHeapLock { + NON_COPYABLE(ScopedHeapLock); + NON_MOVEABLE(ScopedHeapLock); + private: + HeapHandle m_handle; + public: + explicit ScopedHeapLock(HeapHandle h) : m_handle(h) { + if (m_handle->option & CreateOption_ThreadSafe) { + os::LockSdkMutex(std::addressof(m_handle->mutex)); + } + } + + ~ScopedHeapLock() { + if (m_handle->option & CreateOption_ThreadSafe) { + os::UnlockSdkMutex(std::addressof(m_handle->mutex)); + } + } + }; + + ALWAYS_INLINE MemoryRange MakeMemoryRange(void *address, size_t size) { + return MemoryRange{ .address = reinterpret_cast<uintptr_t>(address), .size = size }; + } + + ALWAYS_INLINE void *GetHeapStartAddress(HeapHandle handle) { + return handle->heap_start; + } + + ALWAYS_INLINE size_t GetPointerDifference(const void *start, const void *end) { + return reinterpret_cast<uintptr_t>(end) - reinterpret_cast<uintptr_t>(start); + } + + constexpr ALWAYS_INLINE size_t GetPointerDifference(uintptr_t start, uintptr_t end) { + return end - start; + } + + void InitializeHeapHead(HeapHead *out, u32 magic, void *start, void *end, u32 option); + void FinalizeHeap(HeapHead *heap); + bool ContainsAddress(HeapHandle handle, const void *address); + size_t GetHeapTotalSize(HeapHandle handle); + + /* Debug Fill */ + u32 GetDebugFillValue(FillType type); + u32 SetDebugFillValue(FillType type, u32 value); + + inline void FillMemory(void *dst, u32 fill_value, size_t size) { + /* All heap blocks must be at least 32-bit aligned. */ + AMS_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(dst), alignof(u32))); + AMS_ASSERT(util::IsAligned(size, sizeof(u32))); + for (size_t i = 0; i < size / sizeof(fill_value); i++) { + reinterpret_cast<u32 *>(dst)[i] = fill_value; + } + } + + inline void FillUnallocatedMemory(HeapHead *heap, void *address, size_t size) { + if (heap->option & CreateOption_DebugFill) { + FillMemory(address, impl::GetDebugFillValue(FillType_Unallocated), size); + } + } + + inline void FillAllocatedMemory(HeapHead *heap, void *address, size_t size) { + if (heap->option & CreateOption_ZeroClear) { + FillMemory(address, 0, size); + } else if (heap->option & CreateOption_DebugFill) { + FillMemory(address, impl::GetDebugFillValue(FillType_Allocated), size); + } + } + + inline void FillFreedMemory(HeapHead *heap, void *address, size_t size) { + if (heap->option & CreateOption_DebugFill) { + FillMemory(address, impl::GetDebugFillValue(FillType_Freed), size); + } + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/impl/lmem_impl_exp_heap.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/impl/lmem_impl_exp_heap.cpp new file mode 100644 index 00000000..05e329a7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/impl/lmem_impl_exp_heap.cpp @@ -0,0 +1,645 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lmem_impl_exp_heap.hpp" + +namespace ams::lmem::impl { + + namespace { + + constexpr u16 FreeBlockMagic = 0x4652; /* FR */ + constexpr u16 UsedBlockMagic = 0x5544; /* UD */ + + constexpr u16 DefaultGroupId = 0x00; + constexpr u16 MaxGroupId = 0xFF; + + constexpr size_t MinimumAlignment = 4; + constexpr size_t MaximumPaddingalignment = 0x80; + + constexpr AllocationMode DefaultAllocationMode = AllocationMode_FirstFit; + + constexpr size_t MinimumFreeBlockSize = 4; + + struct MemoryRegion { + void *start; + void *end; + }; + + inline bool IsValidHeapHandle(HeapHandle handle) { + return handle->magic == ExpHeapMagic; + } + + [[maybe_unused]] inline ExpHeapHead *GetExpHeapHead(HeapHead *heap_head) { + return std::addressof(heap_head->impl_head.exp_heap_head); + } + + [[maybe_unused]] inline const ExpHeapHead *GetExpHeapHead(const HeapHead *heap_head) { + return std::addressof(heap_head->impl_head.exp_heap_head); + } + + [[maybe_unused]] inline HeapHead *GetHeapHead(ExpHeapHead *exp_heap_head) { + return util::GetParentPointer<&HeapHead::impl_head>(util::GetParentPointer<&ImplementationHeapHead::exp_heap_head>(exp_heap_head)); + } + + [[maybe_unused]] inline const HeapHead *GetHeapHead(const ExpHeapHead *exp_heap_head) { + return util::GetParentPointer<&HeapHead::impl_head>(util::GetParentPointer<&ImplementationHeapHead::exp_heap_head>(exp_heap_head)); + } + + inline void *GetExpHeapMemoryStart(ExpHeapHead *exp_heap_head) { + return reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(exp_heap_head) + sizeof(ImplementationHeapHead)); + } + + inline void *GetMemoryBlockStart(ExpHeapMemoryBlockHead *head) { + return reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(head) + sizeof(*head)); + } + + inline const void *GetMemoryBlockStart(const ExpHeapMemoryBlockHead *head) { + return reinterpret_cast<const void *>(reinterpret_cast<uintptr_t>(head) + sizeof(*head)); + } + + inline void *GetMemoryBlockEnd(ExpHeapMemoryBlockHead *head) { + return reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(GetMemoryBlockStart(head)) + head->block_size); + } + + inline const void *GetMemoryBlockEnd(const ExpHeapMemoryBlockHead *head) { + return reinterpret_cast<const void *>(reinterpret_cast<uintptr_t>(GetMemoryBlockStart(head)) + head->block_size); + } + + inline ExpHeapMemoryBlockHead *GetHeadForMemoryBlock(const void *block) { + return reinterpret_cast<ExpHeapMemoryBlockHead *>(reinterpret_cast<uintptr_t>(block) - sizeof(ExpHeapMemoryBlockHead)); + } + + inline bool IsValidUsedMemoryBlock(const HeapHead *heap, const void *block) { + /* Block must fall within the heap range. */ + if (heap != nullptr) { + if (block < heap->heap_start || heap->heap_end <= block) { + return false; + } + } + + /* Block magic must be used. */ + const ExpHeapMemoryBlockHead *head = GetHeadForMemoryBlock(block); + if (head->magic != UsedBlockMagic) { + return false; + } + + /* End of block must remain within the heap range. */ + if (heap != nullptr) { + if (reinterpret_cast<uintptr_t>(block) + head->block_size > reinterpret_cast<uintptr_t>(heap->heap_end)) { + return false; + } + } + + return true; + } + + inline u16 GetMemoryBlockAlignmentPadding(const ExpHeapMemoryBlockHead *block_head) { + return static_cast<u16>((block_head->attributes >> 8) & 0x7F); + } + + inline void SetMemoryBlockAlignmentPadding(ExpHeapMemoryBlockHead *block_head, u16 padding) { + block_head->attributes &= ~static_cast<decltype(block_head->attributes)>(0x7F << 8); + block_head->attributes |= static_cast<decltype(block_head->attributes)>(padding & 0x7F) << 8; + } + + inline u16 GetMemoryBlockGroupId(const ExpHeapMemoryBlockHead *block_head) { + return static_cast<u16>(block_head->attributes & 0xFF); + } + + inline void SetMemoryBlockGroupId(ExpHeapMemoryBlockHead *block_head, u16 group_id) { + block_head->attributes &= ~static_cast<decltype(block_head->attributes)>(0xFF); + block_head->attributes |= static_cast<decltype(block_head->attributes)>(group_id & 0xFF); + } + + inline AllocationDirection GetMemoryBlockAllocationDirection(const ExpHeapMemoryBlockHead *block_head) { + return static_cast<AllocationDirection>((block_head->attributes >> 15) & 1); + } + + inline void SetMemoryBlockAllocationDirection(ExpHeapMemoryBlockHead *block_head, AllocationDirection dir) { + block_head->attributes &= ~static_cast<decltype(block_head->attributes)>(0x8000); + block_head->attributes |= static_cast<decltype(block_head->attributes)>(dir) << 15; + } + + inline void GetMemoryBlockRegion(MemoryRegion *out, ExpHeapMemoryBlockHead *head) { + out->start = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(head) - GetMemoryBlockAlignmentPadding(head)); + out->end = GetMemoryBlockEnd(head); + } + + inline AllocationMode GetAllocationModeImpl(const ExpHeapHead *head) { + return static_cast<AllocationMode>(head->mode); + } + + inline void SetAllocationModeImpl(ExpHeapHead *head, AllocationMode mode) { + head->mode = mode; + } + + inline ExpHeapMemoryBlockHead *InitializeMemoryBlock(const MemoryRegion ®ion, u16 magic) { + /* Construct the block. */ + ExpHeapMemoryBlockHead *block = std::construct_at(reinterpret_cast<ExpHeapMemoryBlockHead *>(region.start)); + + /* Initialize all members. */ + block->magic = magic; + block->attributes = 0; + block->block_size = GetPointerDifference(GetMemoryBlockStart(block), region.end); + + return block; + } + + inline ExpHeapMemoryBlockHead *InitializeFreeMemoryBlock(const MemoryRegion ®ion) { + return InitializeMemoryBlock(region, FreeBlockMagic); + } + + inline ExpHeapMemoryBlockHead *InitializeUsedMemoryBlock(const MemoryRegion ®ion) { + return InitializeMemoryBlock(region, UsedBlockMagic); + } + + HeapHead *InitializeExpHeap(void *start, void *end, u32 option) { + HeapHead *heap_head = reinterpret_cast<HeapHead *>(start); + ExpHeapHead *exp_heap_head = GetExpHeapHead(heap_head); + + /* Initialize the parent heap. */ + InitializeHeapHead(heap_head, ExpHeapMagic, GetExpHeapMemoryStart(exp_heap_head), end, option); + + /* Call exp heap member constructors. */ + std::construct_at(std::addressof(exp_heap_head->free_list)); + std::construct_at(std::addressof(exp_heap_head->used_list)); + + /* Set exp heap fields. */ + exp_heap_head->group_id = DefaultGroupId; + exp_heap_head->use_alignment_margins = false; + SetAllocationModeImpl(exp_heap_head, DefaultAllocationMode); + + /* Initialize memory block. */ + { + MemoryRegion region{ .start = heap_head->heap_start, .end = heap_head->heap_end, }; + exp_heap_head->free_list.push_back(*InitializeFreeMemoryBlock(region)); + } + + return heap_head; + } + + bool CoalesceFreedRegion(ExpHeapHead *head, const MemoryRegion *region) { + auto prev_free_block_it = head->free_list.end(); + MemoryRegion free_region = *region; + + /* Locate the block. */ + for (auto it = head->free_list.begin(); it != head->free_list.end(); it++) { + ExpHeapMemoryBlockHead *cur_free_block = std::addressof(*it); + + if (cur_free_block < region->start) { + prev_free_block_it = it; + continue; + } + + /* Coalesce block after, if possible. */ + if (cur_free_block == region->end) { + free_region.end = GetMemoryBlockEnd(cur_free_block); + it = head->free_list.erase(it); + + /* Fill the memory with a pattern, for debug. */ + FillUnallocatedMemory(GetHeapHead(head), cur_free_block, sizeof(ExpHeapMemoryBlockHead)); + } + + break; + } + + /* We'll want to insert after the previous free block. */ + auto insertion_it = head->free_list.begin(); + if (prev_free_block_it != head->free_list.end()) { + /* There's a previous free block, so we want to insert as the next iterator. */ + if (GetMemoryBlockEnd(std::addressof(*prev_free_block_it)) == region->start) { + /* We can coalesce, so do so. */ + free_region.start = std::addressof(*prev_free_block_it); + insertion_it = head->free_list.erase(prev_free_block_it); + } else { + /* We can't coalesce, so just select the next iterator. */ + insertion_it = (++prev_free_block_it); + } + } + + /* Ensure region is big enough for a block. */ + /* NOTE: Nintendo does not check against minimum block size here, only header size. */ + /* We will check against minimum block size, to avoid the creation of zero-size blocks. */ + if (GetPointerDifference(free_region.start, free_region.end) < sizeof(ExpHeapMemoryBlockHead) + MinimumFreeBlockSize) { + return false; + } + + /* Fill the memory with a pattern, for debug. */ + FillFreedMemory(GetHeapHead(head), free_region.start, GetPointerDifference(free_region.start, free_region.end)); + + /* Insert the new memory block. */ + head->free_list.insert(insertion_it, *InitializeFreeMemoryBlock(free_region)); + + return true; + } + + void *ConvertFreeBlockToUsedBlock(ExpHeapHead *head, ExpHeapMemoryBlockHead *block_head, void *block, size_t size, AllocationDirection direction) { + /* Calculate freed memory regions. */ + MemoryRegion free_region_front; + GetMemoryBlockRegion(std::addressof(free_region_front), block_head); + MemoryRegion free_region_back{ .start = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(block) + size), .end = free_region_front.end, }; + + /* Adjust end of head region. */ + free_region_front.end = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(block) - sizeof(ExpHeapMemoryBlockHead)); + + /* Remove the old block. */ + auto old_block_it = head->free_list.erase(head->free_list.iterator_to(*block_head)); + + /* If the front margins are big enough (and we're allowed to do so), make a new block. */ + if ((GetPointerDifference(free_region_front.start, free_region_front.end) < sizeof(ExpHeapMemoryBlockHead) + MinimumFreeBlockSize) || + (direction == AllocationDirection_Front && !head->use_alignment_margins && GetPointerDifference(free_region_front.start, free_region_front.end) < MaximumPaddingalignment)) { + /* There isn't enough space for a new block, or else we're not allowed to make one. */ + free_region_front.end = free_region_front.start; + } else { + /* Make a new block! */ + head->free_list.insert(old_block_it, *InitializeFreeMemoryBlock(free_region_front)); + } + + /* If the back margins are big enough (and we're allowed to do so), make a new block. */ + if ((GetPointerDifference(free_region_back.start, free_region_back.end) < sizeof(ExpHeapMemoryBlockHead) + MinimumFreeBlockSize) || + (direction == AllocationDirection_Back && !head->use_alignment_margins && GetPointerDifference(free_region_back.start, free_region_back.end) < MaximumPaddingalignment)) { + /* There isn't enough space for a new block, or else we're not allowed to make one. */ + free_region_back.end = free_region_back.start; + } else { + /* Make a new block! */ + head->free_list.insert(old_block_it, *InitializeFreeMemoryBlock(free_region_back)); + } + + /* Fill the memory with a pattern, for debug. */ + FillAllocatedMemory(GetHeapHead(head), free_region_front.end, GetPointerDifference(free_region_front.end, free_region_back.start)); + + { + /* Create the used block */ + MemoryRegion used_region{ .start = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(block) - sizeof(ExpHeapMemoryBlockHead)), .end = free_region_back.start }; + + ExpHeapMemoryBlockHead *used_block = InitializeUsedMemoryBlock(used_region); + + /* Insert it into the used list. */ + head->used_list.push_back(*used_block); + SetMemoryBlockAllocationDirection(used_block, direction); + SetMemoryBlockAlignmentPadding(used_block, static_cast<u16>(GetPointerDifference(free_region_front.end, used_block))); + SetMemoryBlockGroupId(used_block, head->group_id); + } + + return block; + } + + void *AllocateFromHead(HeapHead *heap, size_t size, s32 alignment) { + ExpHeapHead *exp_heap_head = GetExpHeapHead(heap); + + const bool is_first_fit = GetAllocationModeImpl(exp_heap_head) == AllocationMode_FirstFit; + + /* Choose a block. */ + ExpHeapMemoryBlockHead *found_block_head = nullptr; + void *found_block = nullptr; + size_t best_size = std::numeric_limits<size_t>::max(); + + for (auto it = exp_heap_head->free_list.begin(); it != exp_heap_head->free_list.end(); it++) { + const uintptr_t absolute_block_start = reinterpret_cast<uintptr_t>(GetMemoryBlockStart(std::addressof(*it))); + const uintptr_t block_start = util::AlignUp(absolute_block_start, alignment); + const size_t block_offset = block_start - absolute_block_start; + + if (it->block_size >= size + block_offset && best_size > it->block_size) { + found_block_head = std::addressof(*it); + found_block = reinterpret_cast<void *>(block_start); + best_size = it->block_size; + + if (is_first_fit || best_size == size) { + break; + } + } + } + + /* If we didn't find a block, return nullptr. */ + if (found_block_head == nullptr) { + return nullptr; + } + + return ConvertFreeBlockToUsedBlock(exp_heap_head, found_block_head, found_block, size, AllocationDirection_Front); + } + + void *AllocateFromTail(HeapHead *heap, size_t size, s32 alignment) { + ExpHeapHead *exp_heap_head = GetExpHeapHead(heap); + + const bool is_first_fit = GetAllocationModeImpl(exp_heap_head) == AllocationMode_FirstFit; + + /* Choose a block. */ + ExpHeapMemoryBlockHead *found_block_head = nullptr; + void *found_block = nullptr; + size_t best_size = std::numeric_limits<size_t>::max(); + + for (auto it = exp_heap_head->free_list.rbegin(); it != exp_heap_head->free_list.rend(); it++) { + const uintptr_t absolute_block_start = reinterpret_cast<uintptr_t>(GetMemoryBlockStart(std::addressof(*it))); + const uintptr_t block_start = util::AlignUp(absolute_block_start, alignment); + const size_t block_offset = block_start - absolute_block_start; + + if (it->block_size >= size + block_offset && best_size > it->block_size) { + found_block_head = std::addressof(*it); + found_block = reinterpret_cast<void *>(block_start); + best_size = it->block_size; + + if (is_first_fit || best_size == size) { + break; + } + } + } + + /* If we didn't find a block, return nullptr. */ + if (found_block_head == nullptr) { + return nullptr; + } + + return ConvertFreeBlockToUsedBlock(exp_heap_head, found_block_head, found_block, size, AllocationDirection_Back); + } + + } + + HeapHandle CreateExpHeap(void *address, size_t size, u32 option) { + const uintptr_t uptr_end = util::AlignDown(reinterpret_cast<uintptr_t>(address) + size, MinimumAlignment); + const uintptr_t uptr_start = util::AlignUp(reinterpret_cast<uintptr_t>(address), MinimumAlignment); + + if (uptr_start > uptr_end || GetPointerDifference(uptr_start, uptr_end) < sizeof(ExpHeapMemoryBlockHead) + MinimumFreeBlockSize) { + return nullptr; + } + + return InitializeExpHeap(reinterpret_cast<void *>(uptr_start), reinterpret_cast<void *>(uptr_end), option); + } + + void DestroyExpHeap(HeapHandle handle) { + AMS_ASSERT(IsValidHeapHandle(handle)); + + FinalizeHeap(handle); + } + + MemoryRange AdjustExpHeap(HeapHandle handle) { + AMS_ASSERT(IsValidHeapHandle(handle)); + + HeapHead *heap_head = handle; + ExpHeapHead *exp_heap_head = GetExpHeapHead(heap_head); + + /* If there's no free blocks, we can't do anything. */ + if (exp_heap_head->free_list.empty()) { + return MakeMemoryRange(handle->heap_end, 0); + } + + /* Get the memory block end, make sure it really is the last block. */ + ExpHeapMemoryBlockHead *block = std::addressof(exp_heap_head->free_list.back()); + void * const block_start = GetMemoryBlockStart(block); + const size_t block_size = block->block_size; + void * const block_end = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(block_start) + block_size); + + if (block_end != handle->heap_end) { + return MakeMemoryRange(handle->heap_end, 0); + } + + /* Remove the memory block. */ + exp_heap_head->free_list.pop_back(); + + const size_t freed_size = block_size + sizeof(ExpHeapMemoryBlockHead); + heap_head->heap_end = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(heap_head->heap_end) - freed_size); + return MakeMemoryRange(heap_head->heap_end, freed_size); + } + + void *AllocateFromExpHeap(HeapHandle handle, size_t size, s32 alignment) { + AMS_ASSERT(IsValidHeapHandle(handle)); + + /* Fix up alignments less than 4. */ + if (alignment == 1 || alignment == 2) { + alignment = 4; + } else if (alignment == -1 || alignment == -2) { + alignment = -4; + } + + /* Ensure the alignment is valid. */ + const s32 abs_alignment = std::abs(alignment); + AMS_ASSERT((abs_alignment & (abs_alignment - 1)) == 0); + AMS_ASSERT(MinimumAlignment <= static_cast<size_t>(abs_alignment)); + AMS_UNUSED(abs_alignment); + + /* Fix size to be correctly aligned. */ + if (size == 0) { + size = 1; + } + size = util::AlignUp(size, MinimumAlignment); + + /* Allocate a memory block. */ + void *allocated_memory = nullptr; + if (alignment >= 0) { + allocated_memory = AllocateFromHead(handle, size, alignment); + } else { + allocated_memory = AllocateFromTail(handle, size, -alignment); + } + + return allocated_memory; + } + + void FreeToExpHeap(HeapHandle handle, void *mem_block) { + /* Ensure this is actually a valid heap and a valid memory block we allocated. */ + AMS_ASSERT(IsValidHeapHandle(handle)); + AMS_ASSERT(IsValidUsedMemoryBlock(handle, mem_block)); + + /* TODO: Nintendo does not allow FreeToExpHeap(nullptr). Should we? */ + + /* Get block pointers. */ + HeapHead *heap_head = handle; + ExpHeapHead *exp_heap_head = GetExpHeapHead(heap_head); + ExpHeapMemoryBlockHead *block = GetHeadForMemoryBlock(mem_block); + MemoryRegion region; + + /* Erase the heap from the used list, and coalesce it with adjacent blocks. */ + GetMemoryBlockRegion(std::addressof(region), block); + exp_heap_head->used_list.erase(exp_heap_head->used_list.iterator_to(*block)); + + /* Coalesce with adjacent blocks. */ + const bool coalesced = CoalesceFreedRegion(exp_heap_head, std::addressof(region)); + AMS_ASSERT(coalesced); + AMS_UNUSED(coalesced); + } + + size_t ResizeExpHeapMemoryBlock(HeapHandle handle, void *mem_block, size_t size) { + /* Ensure this is actually a valid heap and a valid memory block we allocated. */ + AMS_ASSERT(IsValidHeapHandle(handle)); + AMS_ASSERT(IsValidUsedMemoryBlock(handle, mem_block)); + + ExpHeapHead *exp_heap_head = GetExpHeapHead(handle); + ExpHeapMemoryBlockHead *block_head = GetHeadForMemoryBlock(mem_block); + const size_t original_block_size = block_head->block_size; + + /* It's possible that there's no actual resizing being done. */ + size = util::AlignUp(size, MinimumAlignment); + if (size == original_block_size) { + return size; + } + + /* We're resizing one way or the other. */ + if (size > original_block_size) { + /* We want to try to make the block bigger. */ + + /* Find the free block after this one. */ + void * const cur_block_end = GetMemoryBlockEnd(block_head); + ExpHeapMemoryBlockHead *next_block_head = nullptr; + + for (auto it = exp_heap_head->free_list.begin(); it != exp_heap_head->free_list.end(); it++) { + if (std::addressof(*it) == cur_block_end) { + next_block_head = std::addressof(*it); + break; + } + } + + /* If we can't get a big enough allocation using the next block, give up. */ + if (next_block_head == nullptr || size > original_block_size + sizeof(ExpHeapMemoryBlockHead) + next_block_head->block_size) { + return 0; + } + + /* Grow the block to encompass the next block. */ + { + /* Get block region. */ + MemoryRegion new_free_region; + GetMemoryBlockRegion(std::addressof(new_free_region), next_block_head); + + /* Remove the next block from the free list. */ + auto insertion_it = exp_heap_head->free_list.erase(exp_heap_head->free_list.iterator_to(*next_block_head)); + + /* Figure out the new block extents. */ + void *old_start = new_free_region.start; + new_free_region.start = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(mem_block) + size); + + /* Only maintain the new free region as a memory block candidate if it can hold a header. */ + /* NOTE: Nintendo does not check against minimum block size here, only header size. */ + /* We will check against minimum block size, to avoid the creation of zero-size blocks. */ + if (GetPointerDifference(new_free_region.start, new_free_region.end) < sizeof(ExpHeapMemoryBlockHead) + MinimumFreeBlockSize) { + new_free_region.start = new_free_region.end; + } + + /* Adjust block sizes. */ + block_head->block_size = GetPointerDifference(mem_block, new_free_region.start); + if (GetPointerDifference(new_free_region.start, new_free_region.end) >= sizeof(ExpHeapMemoryBlockHead) + MinimumFreeBlockSize) { + exp_heap_head->free_list.insert(insertion_it, *InitializeFreeMemoryBlock(new_free_region)); + } + + /* Fill the memory with a pattern, for debug. */ + FillAllocatedMemory(GetHeapHead(exp_heap_head), old_start, GetPointerDifference(old_start, new_free_region.start)); + } + } else { + /* We're shrinking the block. Nice and easy. */ + MemoryRegion new_free_region{ .start = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(mem_block)+ size), .end = GetMemoryBlockEnd(block_head) }; + + /* Try to free the new memory. */ + block_head->block_size = size; + if (!CoalesceFreedRegion(exp_heap_head, std::addressof(new_free_region))) { + /* We didn't shrink the block successfully, so restore the size. */ + block_head->block_size = original_block_size; + } + } + + return block_head->block_size; + } + + size_t GetExpHeapTotalFreeSize(HeapHandle handle) { + AMS_ASSERT(IsValidHeapHandle(handle)); + + size_t total_size = 0; + for (const auto &it : GetExpHeapHead(handle)->free_list) { + total_size += it.block_size; + } + return total_size; + } + + size_t GetExpHeapAllocatableSize(HeapHandle handle, s32 alignment) { + AMS_ASSERT(IsValidHeapHandle(handle)); + + /* Ensure alignment is positive. */ + alignment = std::abs(alignment); + + size_t max_size = std::numeric_limits<size_t>::min(); + size_t min_offset = std::numeric_limits<size_t>::max(); + for (const auto &it : GetExpHeapHead(handle)->free_list) { + const uintptr_t absolute_block_start = reinterpret_cast<uintptr_t>(GetMemoryBlockStart(std::addressof(it))); + const uintptr_t block_start = util::AlignUp(absolute_block_start, alignment); + const uintptr_t block_end = reinterpret_cast<uintptr_t>(GetMemoryBlockEnd(std::addressof(it))); + + if (block_start < block_end) { + const size_t block_size = GetPointerDifference(block_start, block_end); + const size_t offset = GetPointerDifference(absolute_block_start, block_start); + + if (block_size > max_size || (block_size == max_size && offset < min_offset)) { + max_size = block_size; + min_offset = offset; + } + } + } + + return max_size; + } + + AllocationMode GetExpHeapAllocationMode(HeapHandle handle) { + AMS_ASSERT(IsValidHeapHandle(handle)); + + return GetAllocationModeImpl(GetExpHeapHead(handle)); + } + + AllocationMode SetExpHeapAllocationMode(HeapHandle handle, AllocationMode new_mode) { + AMS_ASSERT(IsValidHeapHandle(handle)); + + ExpHeapHead *exp_heap_head = GetExpHeapHead(handle); + const AllocationMode old_mode = GetAllocationModeImpl(exp_heap_head); + SetAllocationModeImpl(exp_heap_head, new_mode); + return old_mode; + } + + u16 GetExpHeapGroupId(HeapHandle handle) { + AMS_ASSERT(IsValidHeapHandle(handle)); + + return GetExpHeapHead(handle)->group_id; + } + + u16 SetExpHeapGroupId(HeapHandle handle, u16 group_id) { + AMS_ASSERT(IsValidHeapHandle(handle)); + AMS_ASSERT(group_id <= MaxGroupId); + + ExpHeapHead *exp_heap_head = GetExpHeapHead(handle); + const u16 old_group_id = exp_heap_head->group_id; + exp_heap_head->group_id = group_id; + return old_group_id; + } + + void VisitExpHeapAllocatedBlocks(HeapHandle handle, HeapVisitor visitor, uintptr_t user_data) { + AMS_ASSERT(IsValidHeapHandle(handle)); + + for (auto &it : GetExpHeapHead(handle)->used_list) { + (*visitor)(GetMemoryBlockStart(std::addressof(it)), handle, user_data); + } + } + + size_t GetExpHeapMemoryBlockSize(const void *memory_block) { + AMS_ASSERT(IsValidUsedMemoryBlock(nullptr, memory_block)); + + return GetHeadForMemoryBlock(memory_block)->block_size; + } + + u16 GetExpHeapMemoryBlockGroupId(const void *memory_block) { + AMS_ASSERT(IsValidUsedMemoryBlock(nullptr, memory_block)); + + return GetMemoryBlockGroupId(GetHeadForMemoryBlock(memory_block)); + } + + AllocationDirection GetExpHeapMemoryBlockAllocationDirection(const void *memory_block) { + AMS_ASSERT(IsValidUsedMemoryBlock(nullptr, memory_block)); + + return GetMemoryBlockAllocationDirection(GetHeadForMemoryBlock(memory_block)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/impl/lmem_impl_exp_heap.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/impl/lmem_impl_exp_heap.hpp new file mode 100644 index 00000000..b58f163a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/impl/lmem_impl_exp_heap.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "lmem_impl_common_heap.hpp" + +namespace ams::lmem::impl { + + HeapHandle CreateExpHeap(void *address, size_t size, u32 option); + void DestroyExpHeap(HeapHandle handle); + MemoryRange AdjustExpHeap(HeapHandle handle); + + void *AllocateFromExpHeap(HeapHandle handle, size_t size, s32 alignment); + void FreeToExpHeap(HeapHandle handle, void *block); + + size_t ResizeExpHeapMemoryBlock(HeapHandle handle, void *block, size_t size); + + size_t GetExpHeapTotalFreeSize(HeapHandle handle); + size_t GetExpHeapAllocatableSize(HeapHandle handle, s32 alignment); + + AllocationMode GetExpHeapAllocationMode(HeapHandle handle); + AllocationMode SetExpHeapAllocationMode(HeapHandle handle, AllocationMode new_mode); + + bool GetExpHeapUseMarginsOfAlignment(HeapHandle handle); + bool SetExpHeapUseMarginsOfAlignment(HeapHandle handle, bool use_margins); + + u16 GetExpHeapGroupId(HeapHandle handle); + u16 SetExpHeapGroupId(HeapHandle handle, u16 group_id); + + size_t GetExpHeapMemoryBlockSize(const void *memory_block); + u16 GetExpHeapMemoryBlockGroupId(const void *memory_block); + AllocationDirection GetExpHeapMemoryBlockAllocationDirection(const void *memory_block); + + void VisitExpHeapAllocatedBlocks(HeapHandle handle, HeapVisitor visitor, uintptr_t user_data); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/impl/lmem_impl_unit_heap.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/impl/lmem_impl_unit_heap.cpp new file mode 100644 index 00000000..22bf7c3b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/impl/lmem_impl_unit_heap.cpp @@ -0,0 +1,263 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lmem_impl_unit_heap.hpp" + +namespace ams::lmem::impl { + + namespace { + + constexpr size_t MinimumAlignment = 4; + + constexpr inline bool IsValidHeapHandle(HeapHandle handle) { + return handle->magic == UnitHeapMagic; + } + + constexpr inline UnitHeapHead *GetUnitHeapHead(HeapHead *heap_head) { + return std::addressof(heap_head->impl_head.unit_heap_head); + } + + [[maybe_unused]] constexpr inline const UnitHeapHead *GetUnitHeapHead(const HeapHead *heap_head) { + return std::addressof(heap_head->impl_head.unit_heap_head); + } + + inline UnitHead *PopUnit(UnitHeapList *list) { + if (UnitHead *block = list->head; block != nullptr) { + list->head = block->next; + return block; + } else { + return nullptr; + } + } + + inline void PushUnit(UnitHeapList *list, UnitHead *block) { + block->next = list->head; + list->head = block; + } + + } + + HeapHandle CreateUnitHeap(void *address, size_t size, size_t unit_size, s32 alignment, u16 option, InfoPlacement info_placement, HeapCommonHead *heap_head) { + AMS_ASSERT(address != nullptr); + + /* Correct alignment, validate. */ + if (alignment == 1 || alignment == 2) { + alignment = 4; + } + AMS_ASSERT(util::IsAligned(alignment, MinimumAlignment)); + AMS_ASSERT(static_cast<s32>(MinimumAlignment) <= alignment); + AMS_ASSERT(unit_size >= sizeof(uintptr_t)); + + /* Setup heap metadata. */ + UnitHeapHead *unit_heap = nullptr; + void *heap_start = nullptr; + void *heap_end = nullptr; + if (heap_head == nullptr) { + /* Internal heap metadata. */ + if (info_placement == InfoPlacement_Head) { + heap_head = reinterpret_cast<HeapHead *>(util::AlignUp(address, MinimumAlignment)); + unit_heap = GetUnitHeapHead(heap_head); + heap_end = util::AlignDown(reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(address) + size), MinimumAlignment); + heap_start = util::AlignUp(reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(heap_head) + sizeof(HeapHead)), alignment); + } else if (info_placement == InfoPlacement_Tail) { + heap_end = util::AlignDown(reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(address) + size - sizeof(HeapHead)), MinimumAlignment); + heap_head = reinterpret_cast<HeapHead *>(heap_end); + unit_heap = GetUnitHeapHead(heap_head); + heap_start = util::AlignUp(address, alignment); + } else { + AMS_ASSERT(false); + } + } else { + /* External heap metadata. */ + unit_heap = GetUnitHeapHead(heap_head); + heap_end = util::AlignDown(reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(address) + size), MinimumAlignment); + heap_start = util::AlignUp(address, alignment); + } + + /* Correct unit size. */ + unit_size = util::AlignUp(unit_size, alignment); + + /* Don't allow a heap with start after end. */ + if (heap_start > heap_end) { + return nullptr; + } + + /* Don't allow a heap with no units. */ + size_t max_units = GetPointerDifference(heap_start, heap_end) / unit_size; + if (max_units == 0) { + return nullptr; + } + + /* Set real heap end. */ + heap_end = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(heap_start) + max_units * unit_size); + + /* Initialize the parent heap. */ + InitializeHeapHead(heap_head, UnitHeapMagic, heap_start, heap_end, option); + + /* Initialize the actual unit heap. */ + { + unit_heap->free_list.head = reinterpret_cast<UnitHead *>(heap_start); + unit_heap->unit_size = unit_size; + unit_heap->alignment = alignment; + unit_heap->num_units = 0; + + /* Create the new units. */ + UnitHead *cur_tail = unit_heap->free_list.head; + for (size_t i = 0; i < max_units - 1; i++) { + cur_tail->next = reinterpret_cast<UnitHead *>(reinterpret_cast<uintptr_t>(cur_tail) + unit_size); + cur_tail = cur_tail->next; + } + cur_tail->next = nullptr; + } + + /* Return the heap header as handle. */ + return heap_head; + } + + void DestroyUnitHeap(HeapHandle handle) { + AMS_ASSERT(IsValidHeapHandle(handle)); + + /* Validate that the heap has no living units. */ + UnitHeapHead *unit_heap = GetUnitHeapHead(handle); + if (unit_heap->free_list.head != nullptr) { + AMS_ASSERT(unit_heap->num_units == 0); + unit_heap->free_list.head = nullptr; + } + + FinalizeHeap(handle); + } + + void InvalidateUnitHeap(HeapHandle handle) { + AMS_ASSERT(IsValidHeapHandle(handle)); + GetUnitHeapHead(handle)->free_list.head = nullptr; + } + + void ExtendUnitHeap(HeapHandle handle, size_t size) { + AMS_ASSERT(IsValidHeapHandle(handle)); + + /* Find the current tail unit, and insert <end of heap> as next. */ + UnitHeapHead *unit_heap = GetUnitHeapHead(handle); + UnitHead *cur_tail; + if (unit_heap->free_list.head != nullptr) { + cur_tail = unit_heap->free_list.head; + + while (cur_tail->next != nullptr) { + cur_tail = cur_tail->next; + } + + cur_tail->next = reinterpret_cast<UnitHead *>(handle->heap_end); + cur_tail = cur_tail->next; + cur_tail->next = nullptr; + } else { + /* All units are allocated, so set the free list to be at the end of the heap area. */ + unit_heap->free_list.head = reinterpret_cast<UnitHead *>(handle->heap_end); + cur_tail = unit_heap->free_list.head; + cur_tail->next = nullptr; + } + + /* Calculate new unit extents. */ + void *new_units_start = handle->heap_end; + void *new_units_end = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(new_units_start) + size); + size_t num_new_units = GetPointerDifference(new_units_start, new_units_end) / unit_heap->unit_size; + AMS_ASSERT(num_new_units > 0); + + /* Create the new units. */ + for (size_t i = 0; i < num_new_units - 1; i++) { + cur_tail->next = reinterpret_cast<UnitHead *>(reinterpret_cast<uintptr_t>(cur_tail) + unit_heap->unit_size); + cur_tail = cur_tail->next; + } + cur_tail->next = nullptr; + + /* Note that the heap is bigger. */ + handle->heap_end = new_units_end; + } + + void *AllocateFromUnitHeap(HeapHandle handle) { + AMS_ASSERT(IsValidHeapHandle(handle)); + + /* Allocate a unit. */ + UnitHeapHead *unit_heap = GetUnitHeapHead(handle); + UnitHead *unit = PopUnit(std::addressof(unit_heap->free_list)); + if (unit != nullptr) { + /* Fill memory with pattern for debug, if needed. */ + FillAllocatedMemory(handle, unit, unit_heap->unit_size); + + /* Note that we allocated a unit. */ + unit_heap->num_units++; + } + + return unit; + } + + void FreeToUnitHeap(HeapHandle handle, void *block) { + AMS_ASSERT(IsValidHeapHandle(handle)); + + /* Allow Free(nullptr) to succeed. */ + if (block == nullptr) { + return; + } + + /* Fill memory with pattern for debug, if needed. */ + UnitHeapHead *unit_heap = GetUnitHeapHead(handle); + FillFreedMemory(handle, block, unit_heap->unit_size); + + /* Push the unit onto the free list. */ + PushUnit(std::addressof(unit_heap->free_list), static_cast<UnitHead *>(block)); + + /* Note that we freed a unit. */ + unit_heap->num_units--; + } + + size_t GetUnitHeapUnitSize(HeapHandle handle) { + AMS_ASSERT(IsValidHeapHandle(handle)); + return GetUnitHeapHead(handle)->unit_size; + } + + s32 GetUnitHeapAlignment(HeapHandle handle) { + AMS_ASSERT(IsValidHeapHandle(handle)); + return GetUnitHeapHead(handle)->alignment; + } + + size_t GetUnitHeapFreeCount(HeapHandle handle) { + AMS_ASSERT(IsValidHeapHandle(handle)); + + size_t count = 0; + for (UnitHead *cur = GetUnitHeapHead(handle)->free_list.head; cur != nullptr; cur = cur->next) { + count++; + } + return count; + } + + size_t GetUnitHeapUsedCount(HeapHandle handle) { + AMS_ASSERT(IsValidHeapHandle(handle)); + return GetUnitHeapHead(handle)->num_units; + } + + size_t GetUnitHeapRequiredSize(size_t unit_size, size_t unit_count, s32 alignment, bool internal_metadata) { + /* Nintendo does not round up alignment here, even though they do so in CreateUnitHeap. */ + /* We will round up alignment to return more accurate results. */ + if (alignment == 1 || alignment == 2) { + alignment = 4; + } + + AMS_ASSERT(util::IsAligned(alignment, MinimumAlignment)); + AMS_ASSERT(static_cast<s32>(MinimumAlignment) <= alignment); + AMS_ASSERT(unit_size >= sizeof(uintptr_t)); + + return (alignment - 1) + (unit_count * util::AlignUp(unit_size, alignment)) + (internal_metadata ? sizeof(HeapHead) : 0); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/impl/lmem_impl_unit_heap.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/impl/lmem_impl_unit_heap.hpp new file mode 100644 index 00000000..3466371f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/impl/lmem_impl_unit_heap.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "lmem_impl_common_heap.hpp" + +namespace ams::lmem::impl { + + HeapHandle CreateUnitHeap(void *address, size_t size, size_t unit_size, s32 alignment, u16 option, InfoPlacement info_placement, HeapCommonHead *heap_head); + void DestroyUnitHeap(HeapHandle handle); + + void InvalidateUnitHeap(HeapHandle handle); + void ExtendUnitHeap(HeapHandle handle, size_t size); + + void *AllocateFromUnitHeap(HeapHandle handle); + void FreeToUnitHeap(HeapHandle handle, void *block); + + size_t GetUnitHeapUnitSize(HeapHandle handle); + s32 GetUnitHeapAlignment(HeapHandle handle); + size_t GetUnitHeapFreeCount(HeapHandle handle); + size_t GetUnitHeapUsedCount(HeapHandle handle); + + size_t GetUnitHeapRequiredSize(size_t unit_size, size_t unit_count, s32 alignment, bool internal_metadata); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/lmem_common.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/lmem_common.cpp new file mode 100644 index 00000000..b187aa6f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/lmem_common.cpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/lmem_impl_common_heap.hpp" + +namespace ams::lmem { + + u32 GetDebugFillValue(FillType fill_type) { + return impl::GetDebugFillValue(fill_type); + } + + void SetDebugFillValue(FillType fill_type, u32 value) { + impl::SetDebugFillValue(fill_type, value); + } + + size_t GetTotalSize(HeapHandle handle) { + impl::ScopedHeapLock lk(handle); + return impl::GetHeapTotalSize(handle); + } + + void *GetStartAddress(HeapHandle handle) { + impl::ScopedHeapLock lk(handle); + return impl::GetHeapStartAddress(handle); + } + + bool ContainsAddress(HeapHandle handle, const void *address) { + impl::ScopedHeapLock lk(handle); + return impl::ContainsAddress(handle, address); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/lmem_exp_heap.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/lmem_exp_heap.cpp new file mode 100644 index 00000000..bcfdc106 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/lmem_exp_heap.cpp @@ -0,0 +1,115 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/lmem_impl_exp_heap.hpp" + +namespace ams::lmem { + + HeapHandle CreateExpHeap(void *address, size_t size, u32 option) { + HeapHandle handle = impl::CreateExpHeap(address, size, option); + if (option & CreateOption_ThreadSafe) { + os::InitializeSdkMutex(std::addressof(handle->mutex)); + } + return handle; + } + + void DestroyExpHeap(HeapHandle handle) { + impl::DestroyExpHeap(handle); + } + + MemoryRange AdjustExpHeap(HeapHandle handle) { + impl::ScopedHeapLock lk(handle); + return impl::AdjustExpHeap(handle); + } + + void *AllocateFromExpHeap(HeapHandle handle, size_t size) { + impl::ScopedHeapLock lk(handle); + return impl::AllocateFromExpHeap(handle, size, DefaultAlignment); + } + + void *AllocateFromExpHeap(HeapHandle handle, size_t size, s32 alignment) { + impl::ScopedHeapLock lk(handle); + return impl::AllocateFromExpHeap(handle, size, alignment); + } + + void FreeToExpHeap(HeapHandle handle, void *block) { + impl::ScopedHeapLock lk(handle); + impl::FreeToExpHeap(handle, block); + } + + size_t ResizeExpHeapMemoryBlock(HeapHandle handle, void *block, size_t size) { + impl::ScopedHeapLock lk(handle); + return impl::ResizeExpHeapMemoryBlock(handle, block, size); + } + + size_t GetExpHeapTotalFreeSize(HeapHandle handle) { + impl::ScopedHeapLock lk(handle); + return impl::GetExpHeapTotalFreeSize(handle); + } + + size_t GetExpHeapAllocatableSize(HeapHandle handle, s32 alignment) { + impl::ScopedHeapLock lk(handle); + return impl::GetExpHeapAllocatableSize(handle, alignment); + } + + AllocationMode GetExpHeapAllocationMode(HeapHandle handle) { + impl::ScopedHeapLock lk(handle); + return impl::GetExpHeapAllocationMode(handle); + } + + AllocationMode SetExpHeapAllocationMode(HeapHandle handle, AllocationMode new_mode) { + impl::ScopedHeapLock lk(handle); + return impl::SetExpHeapAllocationMode(handle, new_mode); + } + + bool GetExpHeapUseMarginsOfAlignment(HeapHandle handle) { + impl::ScopedHeapLock lk(handle); + return impl::GetExpHeapUseMarginsOfAlignment(handle); + } + + bool SetExpHeapUseMarginsOfAlignment(HeapHandle handle, bool use_margins) { + impl::ScopedHeapLock lk(handle); + return impl::SetExpHeapUseMarginsOfAlignment(handle, use_margins); + } + + u16 GetExpHeapGroupId(HeapHandle handle) { + impl::ScopedHeapLock lk(handle); + return impl::GetExpHeapGroupId(handle); + } + + u16 SetExpHeapGroupId(HeapHandle handle, u16 group_id) { + impl::ScopedHeapLock lk(handle); + return impl::SetExpHeapGroupId(handle, group_id); + } + + size_t GetExpHeapMemoryBlockSize(const void *memory_block) { + return impl::GetExpHeapMemoryBlockSize(memory_block); + } + + u16 GetExpHeapMemoryBlockGroupId(const void *memory_block) { + return impl::GetExpHeapMemoryBlockGroupId(memory_block); + } + + AllocationDirection GetExpHeapMemoryBlockAllocationDirection(const void *memory_block) { + return impl::GetExpHeapMemoryBlockAllocationDirection(memory_block); + } + + void VisitExpHeapAllocatedBlocks(HeapHandle handle, HeapVisitor visitor, uintptr_t user_data) { + impl::ScopedHeapLock lk(handle); + impl::VisitExpHeapAllocatedBlocks(handle, visitor, user_data); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/lmem_unit_heap.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/lmem_unit_heap.cpp new file mode 100644 index 00000000..1dfb6757 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lmem/lmem_unit_heap.cpp @@ -0,0 +1,93 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/lmem_impl_unit_heap.hpp" + +namespace ams::lmem { + + HeapHandle CreateUnitHeap(void *address, size_t size, size_t unit_size, u32 option) { + HeapHandle handle = impl::CreateUnitHeap(address, size, unit_size, DefaultAlignment, static_cast<u16>(option), InfoPlacement_Head, nullptr); + if (option & CreateOption_ThreadSafe) { + os::InitializeSdkMutex(std::addressof(handle->mutex)); + } + return handle; + } + + HeapHandle CreateUnitHeap(void *address, size_t size, size_t unit_size, u32 option, s32 alignment, InfoPlacement info_placement) { + HeapHandle handle = impl::CreateUnitHeap(address, size, unit_size, alignment, static_cast<u16>(option), info_placement, nullptr); + if (option & CreateOption_ThreadSafe) { + os::InitializeSdkMutex(std::addressof(handle->mutex)); + } + return handle; + } + + HeapHandle CreateUnitHeap(void *address, size_t size, size_t unit_size, u32 option, s32 alignment, HeapCommonHead *heap_head) { + HeapHandle handle = impl::CreateUnitHeap(address, size, unit_size, alignment, static_cast<u16>(option), InfoPlacement_Head, heap_head); + if (option & CreateOption_ThreadSafe) { + os::InitializeSdkMutex(std::addressof(handle->mutex)); + } + return handle; + } + + void DestroyUnitHeap(HeapHandle handle) { + impl::DestroyUnitHeap(handle); + } + + void InvalidateUnitHeap(HeapHandle handle) { + impl::ScopedHeapLock lk(handle); + impl::InvalidateUnitHeap(handle); + } + + void ExtendUnitHeap(HeapHandle handle, size_t size) { + impl::ScopedHeapLock lk(handle); + impl::ExtendUnitHeap(handle, size); + } + + void *AllocateFromUnitHeap(HeapHandle handle) { + impl::ScopedHeapLock lk(handle); + return impl::AllocateFromUnitHeap(handle); + } + + void FreeToUnitHeap(HeapHandle handle, void *block) { + impl::ScopedHeapLock lk(handle); + impl::FreeToUnitHeap(handle, block); + } + + size_t GetUnitHeapUnitSize(HeapHandle handle) { + /* Nintendo doesn't acquire a lock here. */ + return impl::GetUnitHeapUnitSize(handle); + } + + s32 GetUnitHeapAlignment(HeapHandle handle) { + /* Nintendo doesn't acquire a lock here. */ + return impl::GetUnitHeapAlignment(handle); + } + + size_t GetUnitHeapFreeCount(HeapHandle handle) { + impl::ScopedHeapLock lk(handle); + return impl::GetUnitHeapFreeCount(handle); + } + + size_t GetUnitHeapUsedCount(HeapHandle handle) { + impl::ScopedHeapLock lk(handle); + return impl::GetUnitHeapUsedCount(handle); + } + + size_t GetUnitHeapRequiredSize(size_t unit_size, size_t unit_count, s32 alignment, bool internal_metadata) { + return impl::GetUnitHeapRequiredSize(unit_size, unit_count, alignment, internal_metadata); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_add_on_content_location_resolver_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_add_on_content_location_resolver_impl.cpp new file mode 100644 index 00000000..1b89e4f7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_add_on_content_location_resolver_impl.cpp @@ -0,0 +1,193 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lr_add_on_content_location_resolver_impl.hpp" + +namespace ams::lr { + + namespace { + + constexpr const lr::Path EmptyPath = {}; + + template<size_t N> + Result ResolveAddOnContentPathImpl(Path *out, RedirectionAttributes *out_attr, const RegisteredStorages<ncm::DataId, N> &storages, ncm::DataId id) { + /* Find a storage that contains the given program id. */ + ncm::StorageId storage_id = ncm::StorageId::None; + R_UNLESS(storages.Find(std::addressof(storage_id), id), lr::ResultAddOnContentNotFound()); + + /* Obtain a content meta database for the storage id. */ + ncm::ContentMetaDatabase content_meta_database; + R_TRY(ncm::OpenContentMetaDatabase(std::addressof(content_meta_database), storage_id)); + + /* Find the latest data content info for the given program id. */ + ncm::ContentInfo data_content_info; + R_TRY(content_meta_database.GetLatestData(std::addressof(data_content_info), id)); + + /* Obtain a content storage for the storage id. */ + ncm::ContentStorage content_storage; + R_TRY(ncm::OpenContentStorage(std::addressof(content_storage), storage_id)); + + /* Get the path of the data content. */ + static_assert(sizeof(lr::Path) == sizeof(ncm::Path)); + content_storage.GetPath(reinterpret_cast<ncm::Path *>(out), data_content_info.GetId()); + + /* Get the redirection attributes. */ + *out_attr = RedirectionAttributes::Make(data_content_info.GetContentAttributes()); + + R_SUCCEED(); + } + + } + + Result AddOnContentLocationResolverImpl::ResolveAddOnContentPath(Path *out, RedirectionAttributes *out_attr, ncm::DataId id) { + /* Try to resolve using our registered storages. */ + if (const auto result = ResolveAddOnContentPathImpl(out, out_attr, m_registered_storages, id); R_SUCCEEDED(result) || !lr::ResultAddOnContentNotFound::Includes(result)) { + R_RETURN(result); + } + + /* If we failed to find the add-on content by storage, we should check if there's a registered path. */ + auto * const found = m_registered_paths.Find(id); + R_UNLESS(found != nullptr, lr::ResultAddOnContentNotFound()); + + /* Set the output path. */ + *out = found->redir_path.path; + *out_attr = found->redir_path.attributes; + R_SUCCEED(); + + } + + Result AddOnContentLocationResolverImpl::ResolveAddOnContentPath(sf::Out<Path> out, ncm::DataId id) { + RedirectionAttributes attr; + R_RETURN(this->ResolveAddOnContentPath(out.GetPointer(), std::addressof(attr), id)); + } + + Result AddOnContentLocationResolverImpl::RegisterAddOnContentStorageDeprecated(ncm::DataId id, ncm::StorageId storage_id) { + /* Register storage for the given program id. 2.0.0-8.1.0 did not require an owner application id. */ + R_UNLESS(m_registered_storages.Register(id, storage_id, ncm::InvalidApplicationId), lr::ResultTooManyRegisteredPaths()); + R_SUCCEED(); + } + + Result AddOnContentLocationResolverImpl::RegisterAddOnContentStorage(ncm::DataId id, ncm::ApplicationId application_id, ncm::StorageId storage_id) { + /* Register storage for the given program id and owner application. */ + R_UNLESS(m_registered_storages.Register(id, storage_id, application_id), lr::ResultTooManyRegisteredPaths()); + R_SUCCEED(); + } + + Result AddOnContentLocationResolverImpl::UnregisterAllAddOnContentPath() { + m_registered_storages.Clear(); + m_registered_paths.RemoveAll(); + m_registered_other_paths.RemoveAll(); + R_SUCCEED(); + } + + Result AddOnContentLocationResolverImpl::RefreshApplicationAddOnContent(const sf::InArray<ncm::ApplicationId> &ids) { + /* Clear all registered storages excluding the provided program ids. */ + m_registered_storages.ClearExcluding(reinterpret_cast<const ncm::ProgramId *>(ids.GetPointer()), ids.GetSize()); + + auto ShouldRefresh = [&ids](const ncm::DataId &, const OwnedPath &owned_path) { + for (size_t i = 0; i < ids.GetSize(); ++i) { + if (owned_path.owner_id == ids[i]) { + return false; + } + } + + return true; + }; + + m_registered_paths.RemoveIf(ShouldRefresh); + m_registered_other_paths.RemoveIf(ShouldRefresh); + + R_SUCCEED(); + } + + Result AddOnContentLocationResolverImpl::UnregisterApplicationAddOnContent(ncm::ApplicationId id) { + /* Remove entries belonging to the provided application. */ + m_registered_storages.UnregisterOwnerProgram(id); + + auto ShouldRefresh = [&id](const ncm::DataId &, const OwnedPath &owned_path) { + return owned_path.owner_id == id; + }; + + m_registered_paths.RemoveIf(ShouldRefresh); + m_registered_other_paths.RemoveIf(ShouldRefresh); + + R_SUCCEED(); + } + + Result AddOnContentLocationResolverImpl::GetRegisteredAddOnContentPaths(Path *out, RedirectionAttributes *out_attr, Path *out2, RedirectionAttributes *out_attr2, ncm::DataId id) { + /* Find a registered path. */ + auto * const found = m_registered_paths.Find(id); + if (found == nullptr) { + /* We have no registered path, so perform a normal resolution. */ + R_TRY(ResolveAddOnContentPathImpl(out, out_attr, m_registered_storages, id)); + + /* Clear the second output path. */ + *out2 = {}; + *out_attr2 = {}; + R_SUCCEED(); + } + + /* Set the output path. */ + *out = found->redir_path.path; + *out_attr = found->redir_path.attributes; + + /* If we have a second path, set it to output. */ + if (auto * const found2 = m_registered_other_paths.Find(id); found2 != nullptr) { + *out2 = found2->redir_path.path; + *out_attr2 = found2->redir_path.attributes; + } else { + *out2 = {}; + *out_attr2 = {}; + } + + R_SUCCEED(); + } + + Result AddOnContentLocationResolverImpl::GetRegisteredAddOnContentPaths(sf::Out<PathByMapAlias> out, sf::Out<PathByMapAlias> out2, ncm::DataId id) { + RedirectionAttributes attr, attr2; + R_RETURN(this->GetRegisteredAddOnContentPaths(out.GetPointer(), std::addressof(attr), out2.GetPointer(), std::addressof(attr2), id)); + } + + Result AddOnContentLocationResolverImpl::RegisterAddOnContentPath(ncm::DataId id, ncm::ApplicationId application_id, const lr::PathByMapAlias &path) { + R_RETURN(this->RegisterAddOnContentPaths(id, application_id, path, DefaultRedirectionAttributes, EmptyPath, DefaultRedirectionAttributes)); + } + + Result AddOnContentLocationResolverImpl::RegisterAddOnContentPaths(ncm::DataId id, ncm::ApplicationId application_id, const lr::PathByMapAlias &path, const lr::PathByMapAlias &path2) { + R_RETURN(this->RegisterAddOnContentPaths(id, application_id, path, DefaultRedirectionAttributes, path2, DefaultRedirectionAttributes)); + } + + Result AddOnContentLocationResolverImpl::RegisterAddOnContentPaths(ncm::DataId id, ncm::ApplicationId application_id, const Path &path, const RedirectionAttributes &attr, const Path &path2, const RedirectionAttributes &attr2) { + /* Check that it's possible for us to register the path. */ + /* NOTE: This check is technically incorrect, if the id is already registered. */ + R_UNLESS(!m_registered_paths.IsFull(), lr::ResultTooManyRegisteredPaths()); + + /* Check that the input path isn't empty. */ + R_UNLESS(path.str[0] != '\x00', lr::ResultInvalidPath()); + + /* Insert the path. */ + m_registered_paths.InsertOrAssign(id, OwnedPath{ { path, attr }, application_id }); + + /* If we have a second path, insert it. */ + if (path2.str[0] != '\x00') { + m_registered_other_paths.InsertOrAssign(id, OwnedPath { { path2, attr2 }, application_id }); + } else { + m_registered_other_paths.Remove(id); + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_add_on_content_location_resolver_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_add_on_content_location_resolver_impl.hpp new file mode 100644 index 00000000..32e30103 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_add_on_content_location_resolver_impl.hpp @@ -0,0 +1,66 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> +#include "lr_location_redirector.hpp" +#include "lr_registered_data.hpp" + +namespace ams::lr { + + class AddOnContentLocationResolverImpl { + private: + struct OwnedPath { + RedirectionPath redir_path; + ncm::ApplicationId owner_id; + }; + private: + /* Storage for RegisteredData entries by data id. */ + RegisteredStorages<ncm::DataId, 0x800> m_registered_storages; + ncm::BoundedMap<ncm::DataId, OwnedPath, 8> m_registered_paths; + ncm::BoundedMap<ncm::DataId, OwnedPath, 8> m_registered_other_paths; + private: + static ALWAYS_INLINE size_t GetStorageCapacity() { + const auto version = hos::GetVersion(); + if (version >= hos::Version_12_0_0) { + return 0x8; + } else if (version >= hos::Version_9_0_0) { + return 0x2; + } else { + return 0x800; + } + } + public: + AddOnContentLocationResolverImpl() : m_registered_storages(GetStorageCapacity()), m_registered_paths{}, m_registered_other_paths{} { /* ... */ } + + /* Actual commands. */ + Result ResolveAddOnContentPath(sf::Out<Path> out, ncm::DataId id); + Result RegisterAddOnContentStorageDeprecated(ncm::DataId id, ncm::StorageId storage_id); + Result RegisterAddOnContentStorage(ncm::DataId id, ncm::ApplicationId application_id, ncm::StorageId storage_id); + Result UnregisterAllAddOnContentPath(); + Result RefreshApplicationAddOnContent(const sf::InArray<ncm::ApplicationId> &ids); + Result UnregisterApplicationAddOnContent(ncm::ApplicationId id); + Result GetRegisteredAddOnContentPaths(sf::Out<PathByMapAlias> out, sf::Out<PathByMapAlias> out2, ncm::DataId id); + Result RegisterAddOnContentPath(ncm::DataId id, ncm::ApplicationId application_id, const PathByMapAlias &path); + Result RegisterAddOnContentPaths(ncm::DataId id, ncm::ApplicationId application_id, const PathByMapAlias &path, const PathByMapAlias &path2); + private: + Result ResolveAddOnContentPath(Path *out, RedirectionAttributes *out_attr, ncm::DataId id); + Result GetRegisteredAddOnContentPaths(Path *out, RedirectionAttributes *out_attr, Path *out2, RedirectionAttributes *out_attr2, ncm::DataId id); + Result RegisterAddOnContentPaths(ncm::DataId id, ncm::ApplicationId application_id, const Path &path, const RedirectionAttributes &attr, const Path &path2, const RedirectionAttributes &attr2); + }; + static_assert(lr::IsIAddOnContentLocationResolver<AddOnContentLocationResolverImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_api.cpp new file mode 100644 index 00000000..3d241e10 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_api.cpp @@ -0,0 +1,65 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lr_location_resolver_manager_factory.hpp" + +namespace ams::lr { + + namespace { + + constinit sf::SharedPointer<ILocationResolverManager> g_location_resolver_manager; + + } + + void Initialize() { + AMS_ASSERT(g_location_resolver_manager == nullptr); + g_location_resolver_manager = GetLocationResolverManagerService(); + } + + void Finalize() { + AMS_ASSERT(g_location_resolver_manager != nullptr); + g_location_resolver_manager = nullptr; + } + + + Result OpenLocationResolver(LocationResolver *out, ncm::StorageId storage_id) { + sf::SharedPointer<lr::ILocationResolver> lr; + R_TRY(g_location_resolver_manager->OpenLocationResolver(std::addressof(lr), storage_id)); + + *out = LocationResolver(std::move(lr)); + R_SUCCEED(); + } + + Result OpenRegisteredLocationResolver(RegisteredLocationResolver *out) { + sf::SharedPointer<lr::IRegisteredLocationResolver> lr; + R_TRY(g_location_resolver_manager->OpenRegisteredLocationResolver(std::addressof(lr))); + + *out = RegisteredLocationResolver(std::move(lr)); + R_SUCCEED(); + } + + Result OpenAddOnContentLocationResolver(AddOnContentLocationResolver *out) { + sf::SharedPointer<lr::IAddOnContentLocationResolver> lr; + R_TRY(g_location_resolver_manager->OpenAddOnContentLocationResolver(std::addressof(lr))); + + *out = AddOnContentLocationResolver(std::move(lr)); + R_SUCCEED(); + } + + Result RefreshLocationResolver(ncm::StorageId storage_id) { + R_RETURN(g_location_resolver_manager->RefreshLocationResolver(storage_id)); + } +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_content_location_resolver_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_content_location_resolver_impl.cpp new file mode 100644 index 00000000..2b918715 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_content_location_resolver_impl.cpp @@ -0,0 +1,221 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lr_content_location_resolver_impl.hpp" + +namespace ams::lr { + + ContentLocationResolverImpl::~ContentLocationResolverImpl() { + this->ClearRedirections(); + } + + /* Helper function. */ + void ContentLocationResolverImpl::GetContentStoragePath(Path *out, ncm::ContentId content_id) { + static_assert(sizeof(lr::Path) == sizeof(ncm::Path)); + m_content_storage.GetPath(reinterpret_cast<ncm::Path *>(out), content_id); + } + + Result ContentLocationResolverImpl::ResolveProgramPath(Path *out, RedirectionAttributes *out_attr, ncm::ProgramId id) { + /* Use a redirection if present. */ + R_SUCCEED_IF(m_program_redirector.FindRedirection(out, out_attr, id)); + + /* If we're not enabled, we can't resolve a program. */ + R_UNLESS(m_enabled, lr::ResultProgramNotFound()) + + /* Find the latest program content for the program id. */ + ncm::ContentInfo program_content_info; + R_TRY_CATCH(m_content_meta_database.GetLatestProgram(std::addressof(program_content_info), id)) { + R_CONVERT(ncm::ResultContentMetaNotFound, lr::ResultProgramNotFound()) + } R_END_TRY_CATCH; + + /* Obtain the content path and attributes. */ + this->GetContentStoragePath(out, program_content_info.GetId()); + *out_attr = RedirectionAttributes::Make(program_content_info.GetContentAttributes()); + + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) { + RedirectionAttributes attr; + R_RETURN(this->ResolveProgramPath(out.GetPointer(), std::addressof(attr), id)); + } + + Result ContentLocationResolverImpl::RedirectProgramPath(const Path &path, ncm::ProgramId id) { + m_program_redirector.SetRedirection(id, path, DefaultRedirectionAttributes); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::ResolveApplicationControlPath(sf::Out<Path> out, ncm::ProgramId id) { + RedirectionAttributes attr; + R_UNLESS(m_app_control_redirector.FindRedirection(out.GetPointer(), std::addressof(attr), id), lr::ResultControlNotFound()); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::ResolveApplicationHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) { + RedirectionAttributes attr; + R_UNLESS(m_html_docs_redirector.FindRedirection(out.GetPointer(), std::addressof(attr), id), lr::ResultHtmlDocumentNotFound()); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::ResolveDataPath(sf::Out<Path> out, ncm::DataId id) { + /* If we're not enabled, we can't resolve data. */ + R_UNLESS(m_enabled, lr::ResultDataNotFound()) + + /* Find the latest data content info for the program id. */ + ncm::ContentInfo data_content_info; + R_TRY(m_content_meta_database.GetLatestData(std::addressof(data_content_info), id)); + + /* Obtain the content path. */ + this->GetContentStoragePath(out.GetPointer(), data_content_info.GetId()); + + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId id) { + m_app_control_redirector.SetRedirection(id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::RedirectApplicationControlPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + m_app_control_redirector.SetRedirection(id, owner_id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) { + m_html_docs_redirector.SetRedirection(id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + m_html_docs_redirector.SetRedirection(id, owner_id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::ResolveApplicationLegalInformationPath(sf::Out<Path> out, ncm::ProgramId id) { + RedirectionAttributes attr; + R_UNLESS(m_legal_info_redirector.FindRedirection(out.GetPointer(), std::addressof(attr), id), lr::ResultLegalInformationNotFound()); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId id) { + m_legal_info_redirector.SetRedirection(id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + m_legal_info_redirector.SetRedirection(id, owner_id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::Refresh() { + /* Obtain content meta database and content storage objects for this resolver's storage. */ + ncm::ContentMetaDatabase meta_db; + ncm::ContentStorage storage; + R_TRY(ncm::OpenContentMetaDatabase(std::addressof(meta_db), m_storage_id)); + R_TRY(ncm::OpenContentStorage(std::addressof(storage), m_storage_id)); + + /* Store the acquired objects. */ + m_content_meta_database = std::move(meta_db); + m_content_storage = std::move(storage); + + /* Remove any existing redirections. */ + this->ClearRedirections(); + + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::RedirectApplicationProgramPathDeprecated(const Path &path, ncm::ProgramId id) { + m_program_redirector.SetRedirection(id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::RedirectApplicationProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + m_program_redirector.SetRedirection(id, owner_id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::ClearApplicationRedirectionDeprecated() { + this->ClearRedirections(RedirectionFlags_Application); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::ClearApplicationRedirection(const sf::InArray<ncm::ProgramId> &excluding_ids) { + this->ClearRedirections(excluding_ids.GetPointer(), excluding_ids.GetSize()); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::EraseProgramRedirection(ncm::ProgramId id) { + m_program_redirector.EraseRedirection(id); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::EraseApplicationControlRedirection(ncm::ProgramId id) { + m_app_control_redirector.EraseRedirection(id); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::EraseApplicationHtmlDocumentRedirection(ncm::ProgramId id) { + m_html_docs_redirector.EraseRedirection(id); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::EraseApplicationLegalInformationRedirection(ncm::ProgramId id) { + m_legal_info_redirector.EraseRedirection(id); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::ResolveProgramPathForDebug(sf::Out<Path> out, ncm::ProgramId id) { + /* Use a redirection if present. */ + RedirectionAttributes attr; + R_SUCCEED_IF(m_debug_program_redirector.FindRedirection(out.GetPointer(), std::addressof(attr), id)); + + /* If we're not enabled, we can't resolve a program. */ + R_UNLESS(m_enabled, lr::ResultDebugProgramNotFound()) + + /* Otherwise find the path for the program id. */ + R_TRY_CATCH(this->ResolveProgramPath(out.GetPointer(), std::addressof(attr), id)) { + R_CONVERT(lr::ResultProgramNotFound, lr::ResultDebugProgramNotFound()) + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::RedirectProgramPathForDebug(const Path &path, ncm::ProgramId id) { + m_debug_program_redirector.SetRedirection(id, path, DefaultRedirectionAttributes); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId id) { + m_debug_program_redirector.SetRedirection(id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + m_debug_program_redirector.SetRedirection(id, owner_id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::EraseProgramRedirectionForDebug(ncm::ProgramId id) { + m_debug_program_redirector.EraseRedirection(id); + R_SUCCEED(); + } + + Result ContentLocationResolverImpl::Disable() { + m_enabled = false; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_content_location_resolver_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_content_location_resolver_impl.hpp new file mode 100644 index 00000000..d57aaf67 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_content_location_resolver_impl.hpp @@ -0,0 +1,71 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include "lr_location_resolver_impl_base.hpp" +#include "lr_location_redirector.hpp" + +namespace ams::lr { + + class ContentLocationResolverImpl : public LocationResolverImplBase { + private: + ncm::StorageId m_storage_id; + bool m_enabled; + + /* Objects for this storage type. */ + ncm::ContentMetaDatabase m_content_meta_database; + ncm::ContentStorage m_content_storage; + public: + ContentLocationResolverImpl(ncm::StorageId storage_id, bool enabled) : m_storage_id(storage_id), m_enabled(enabled), m_content_meta_database(), m_content_storage() { /* ... */ } + + ~ContentLocationResolverImpl(); + private: + /* Helper functions. */ + void GetContentStoragePath(Path *out, ncm::ContentId content_id); + Result ResolveProgramPath(Path *out, RedirectionAttributes *out_attr, ncm::ProgramId id); + public: + /* Actual commands. */ + Result ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id); + Result RedirectProgramPath(const Path &path, ncm::ProgramId id); + Result ResolveApplicationControlPath(sf::Out<Path> out, ncm::ProgramId id); + Result ResolveApplicationHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id); + Result ResolveDataPath(sf::Out<Path> out, ncm::DataId id); + Result RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId id); + Result RedirectApplicationControlPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id); + Result RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id); + Result RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id); + Result ResolveApplicationLegalInformationPath(sf::Out<Path> out, ncm::ProgramId id); + Result RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId id); + Result RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id); + Result Refresh(); + Result RedirectApplicationProgramPathDeprecated(const Path &path, ncm::ProgramId id); + Result RedirectApplicationProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id); + Result ClearApplicationRedirectionDeprecated(); + Result ClearApplicationRedirection(const sf::InArray<ncm::ProgramId> &excluding_ids); + Result EraseProgramRedirection(ncm::ProgramId id); + Result EraseApplicationControlRedirection(ncm::ProgramId id); + Result EraseApplicationHtmlDocumentRedirection(ncm::ProgramId id); + Result EraseApplicationLegalInformationRedirection(ncm::ProgramId id); + Result ResolveProgramPathForDebug(sf::Out<Path> out, ncm::ProgramId id); + Result RedirectProgramPathForDebug(const Path &path, ncm::ProgramId id); + Result RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId id); + Result RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id); + Result EraseProgramRedirectionForDebug(ncm::ProgramId id); + Result Disable(); + }; + static_assert(lr::IsILocationResolver<ContentLocationResolverImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_location_redirector.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_location_redirector.cpp new file mode 100644 index 00000000..ff724a54 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_location_redirector.cpp @@ -0,0 +1,125 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lr_location_redirector.hpp" + +namespace ams::lr { + + class LocationRedirector::Redirection : public util::IntrusiveListBaseNode<Redirection> { + NON_COPYABLE(Redirection); + NON_MOVEABLE(Redirection); + private: + ncm::ProgramId m_program_id; + ncm::ProgramId m_owner_id; + Path m_path; + RedirectionAttributes m_attr; + u32 m_flags; + public: + Redirection(ncm::ProgramId program_id, ncm::ProgramId owner_id, const Path &path, const RedirectionAttributes &attr, u32 flags) : + m_program_id(program_id), m_owner_id(owner_id), m_path(path), m_attr(attr), m_flags(flags) { /* ... */ } + + ncm::ProgramId GetProgramId() const { + return m_program_id; + } + + ncm::ProgramId GetOwnerProgramId() const { + return m_owner_id; + } + + void GetPath(Path *out) const { + *out = m_path; + } + + void GetAttributes(RedirectionAttributes *out) const { + *out = m_attr; + } + + u32 GetFlags() const { + return m_flags; + } + + void SetFlags(u32 flags) { + m_flags = flags; + } + }; + + bool LocationRedirector::FindRedirection(Path *out, RedirectionAttributes *out_attr, ncm::ProgramId program_id) const { + /* Obtain the path of a matching redirection. */ + for (const auto &redirection : m_redirection_list) { + if (redirection.GetProgramId() == program_id) { + redirection.GetPath(out); + redirection.GetAttributes(out_attr); + return true; + } + } + return false; + } + + void LocationRedirector::SetRedirection(ncm::ProgramId program_id, const Path &path, const RedirectionAttributes &attr, u32 flags) { + this->SetRedirection(program_id, ncm::InvalidProgramId, path, attr, flags); + } + + void LocationRedirector::SetRedirection(ncm::ProgramId program_id, ncm::ProgramId owner_id, const Path &path, const RedirectionAttributes &attr, u32 flags) { + /* Remove any existing redirections for this program id. */ + this->EraseRedirection(program_id); + + /* Insert a new redirection into the list. */ + m_redirection_list.push_back(*(new Redirection(program_id, owner_id, path, attr, flags))); + } + + void LocationRedirector::EraseRedirection(ncm::ProgramId program_id) { + /* Remove any redirections with a matching program id. */ + for (auto it = m_redirection_list.begin(); it != m_redirection_list.end(); /* ... */) { + if (it->GetProgramId() == program_id) { + auto *redirection = std::addressof(*it); + it = m_redirection_list.erase(it); + delete redirection; + break; + } else { + ++it; + } + } + } + + void LocationRedirector::ClearRedirections(u32 flags) { + /* Remove any redirections with matching flags. */ + for (auto it = m_redirection_list.begin(); it != m_redirection_list.end(); /* ... */) { + if ((it->GetFlags() & flags) == flags) { + auto *redirection = std::addressof(*it); + it = m_redirection_list.erase(it); + delete redirection; + } else { + ++it; + } + } + } + + void LocationRedirector::ClearRedirectionsExcludingOwners(const ncm::ProgramId *excluding_ids, size_t num_ids) { + for (auto it = m_redirection_list.begin(); it != m_redirection_list.end();) { + /* Skip removal if the redirection has an excluded owner program id. */ + if (this->IsExcluded(it->GetOwnerProgramId(), excluding_ids, num_ids)) { + it++; + continue; + } + + /* Remove the redirection. */ + auto *redirection = std::addressof(*it); + it = m_redirection_list.erase(it); + delete redirection; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_location_redirector.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_location_redirector.hpp new file mode 100644 index 00000000..46d61184 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_location_redirector.hpp @@ -0,0 +1,80 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> + +namespace ams::lr { + + enum RedirectionFlags { + RedirectionFlags_None = (0 << 0), + RedirectionFlags_Application = (1 << 0), + }; + + /* TODO: Do any of these unknown fields exist? */ + struct RedirectionAttributes { + fs::ContentAttributes content_attributes; + u8 unknown[0xF]; + + static constexpr ALWAYS_INLINE RedirectionAttributes Make(fs::ContentAttributes attr) { + return { attr, }; + } + }; + static_assert(util::is_pod<RedirectionAttributes>::value); + static_assert(sizeof(RedirectionAttributes) == 0x10); + + constexpr inline const RedirectionAttributes DefaultRedirectionAttributes = RedirectionAttributes::Make(fs::ContentAttributes_None); + + struct RedirectionPath { + Path path; + RedirectionAttributes attributes; + }; + static_assert(util::is_pod<RedirectionPath>::value); + static_assert(sizeof(RedirectionPath) == 0x310); + + class LocationRedirector { + NON_COPYABLE(LocationRedirector); + NON_MOVEABLE(LocationRedirector); + private: + class Redirection; + private: + using RedirectionList = ams::util::IntrusiveListBaseTraits<Redirection>::ListType; + private: + RedirectionList m_redirection_list; + public: + LocationRedirector() : m_redirection_list() { /* ... */ } + ~LocationRedirector() { this->ClearRedirections(); } + + /* API. */ + bool FindRedirection(Path *out, RedirectionAttributes *out_attr, ncm::ProgramId program_id) const; + void SetRedirection(ncm::ProgramId program_id, const Path &path, const RedirectionAttributes &attr, u32 flags = RedirectionFlags_None); + void SetRedirection(ncm::ProgramId program_id, ncm::ProgramId owner_id, const Path &path, const RedirectionAttributes &attr, u32 flags = RedirectionFlags_None); + void EraseRedirection(ncm::ProgramId program_id); + void ClearRedirections(u32 flags = RedirectionFlags_None); + void ClearRedirectionsExcludingOwners(const ncm::ProgramId *excluding_ids, size_t num_ids); + private: + inline bool IsExcluded(const ncm::ProgramId id, const ncm::ProgramId *excluding_ids, size_t num_ids) const { + for (size_t i = 0; i < num_ids; i++) { + if (id == excluding_ids[i]) { + return true; + } + } + + return false; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_location_resolver_impl_base.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_location_resolver_impl_base.hpp new file mode 100644 index 00000000..08d9b440 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_location_resolver_impl_base.hpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> +#include "lr_location_redirector.hpp" + +namespace ams::lr { + + class LocationResolverImplBase { + NON_COPYABLE(LocationResolverImplBase); + NON_MOVEABLE(LocationResolverImplBase); + protected: + /* Location redirectors. */ + LocationRedirector m_program_redirector; + LocationRedirector m_debug_program_redirector; + LocationRedirector m_app_control_redirector; + LocationRedirector m_html_docs_redirector; + LocationRedirector m_legal_info_redirector; + protected: + LocationResolverImplBase() : m_program_redirector(), m_debug_program_redirector(), m_app_control_redirector(), m_html_docs_redirector(), m_legal_info_redirector() { /* ... */ } + protected: + /* Helper functions. */ + void ClearRedirections(u32 flags = RedirectionFlags_None) { + m_program_redirector.ClearRedirections(flags); + m_debug_program_redirector.ClearRedirections(flags); + m_app_control_redirector.ClearRedirections(flags); + m_html_docs_redirector.ClearRedirections(flags); + m_legal_info_redirector.ClearRedirections(flags); + } + + void ClearRedirections(const ncm::ProgramId *excluding_ids, size_t num_ids) { + m_program_redirector.ClearRedirectionsExcludingOwners(excluding_ids, num_ids); + m_debug_program_redirector.ClearRedirectionsExcludingOwners(excluding_ids, num_ids); + m_app_control_redirector.ClearRedirectionsExcludingOwners(excluding_ids, num_ids); + m_html_docs_redirector.ClearRedirectionsExcludingOwners(excluding_ids, num_ids); + m_legal_info_redirector.ClearRedirectionsExcludingOwners(excluding_ids, num_ids); + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_location_resolver_manager_factory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_location_resolver_manager_factory.cpp new file mode 100644 index 00000000..08918556 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_location_resolver_manager_factory.cpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <stratosphere/lr/lr_location_resolver_manager_impl.hpp> +#include "lr_remote_location_resolver_manager_impl.hpp" + +namespace ams::lr { + + namespace { + + class StaticAllocatorInitializer { + public: + StaticAllocatorInitializer() { + LocationResolverManagerAllocator::Initialize(lmem::CreateOption_None); + } + } g_static_allocator_initializer; + + } + + sf::SharedPointer<ILocationResolverManager> GetLocationResolverManagerService() { + #if defined(ATMOSPHERE_OS_HORIZON) + return LocationResolverManagerFactory::CreateSharedEmplaced<ILocationResolverManager, RemoteLocationResolverManagerImpl>(); + #else + return LocationResolverManagerFactory::CreateSharedEmplaced<ILocationResolverManager, LocationResolverManagerImpl>(); + #endif + } +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_location_resolver_manager_factory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_location_resolver_manager_factory.hpp new file mode 100644 index 00000000..1550fb12 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_location_resolver_manager_factory.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> + +namespace ams::lr { + + struct LocationResolverManagerAllocatorTag; + using LocationResolverManagerAllocator = sf::ExpHeapStaticAllocator<1_KB, LocationResolverManagerAllocatorTag>; + + using LocationResolverManagerFactory = sf::ObjectFactory<typename LocationResolverManagerAllocator::Policy>; + + sf::SharedPointer<ILocationResolverManager> GetLocationResolverManagerService(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_location_resolver_manager_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_location_resolver_manager_impl.cpp new file mode 100644 index 00000000..a7925fa9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_location_resolver_manager_impl.cpp @@ -0,0 +1,145 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <stratosphere/lr/lr_location_resolver_manager_impl.hpp> +#include "lr_content_location_resolver_impl.hpp" +#include "lr_redirect_only_location_resolver_impl.hpp" +#include "lr_add_on_content_location_resolver_impl.hpp" +#include "lr_registered_location_resolver_impl.hpp" + +namespace ams::lr { + + namespace { + + using ContentLocationResolverFactory = sf::ObjectFactory<sf::StdAllocationPolicy<std::allocator>>; + using RedirectOnlyLocationResolverFactory = sf::ObjectFactory<sf::StdAllocationPolicy<std::allocator>>; + + bool IsAcceptableStorageId(ncm::StorageId storage_id) { + if (ncm::IsInstallableStorage(storage_id)) { + return storage_id != ncm::StorageId::Any; + } else { + return storage_id == ncm::StorageId::Host || storage_id == ncm::StorageId::GameCard; + } + } + + } + + Result LocationResolverManagerImpl::OpenLocationResolver(sf::Out<sf::SharedPointer<ILocationResolver>> out, ncm::StorageId storage_id) { + std::scoped_lock lk(m_mutex); + + /* Find an existing resolver. */ + auto resolver = m_location_resolvers.Find(storage_id); + + /* No existing resolver is present, create one. */ + if (!resolver) { + if (storage_id == ncm::StorageId::Host) { + AMS_ABORT_UNLESS(m_location_resolvers.Insert(storage_id, RedirectOnlyLocationResolverFactory::CreateSharedEmplaced<ILocationResolver, RedirectOnlyLocationResolverImpl>())); + } else { + /* Get enabled. */ + auto *enabled = m_location_resolvers_enabled.Find(storage_id); + + /* Create the resolver. */ + auto content_resolver = ContentLocationResolverFactory::CreateSharedEmplaced<ILocationResolver, ContentLocationResolverImpl>(storage_id, enabled != nullptr ? *enabled : m_default_enabled); + R_TRY(content_resolver->Refresh()); + AMS_ABORT_UNLESS(m_location_resolvers.Insert(storage_id, std::move(content_resolver))); + } + + /* Acquire the newly-created resolver. */ + resolver = m_location_resolvers.Find(storage_id); + } + + /* Copy the output interface. */ + *out = *resolver; + R_SUCCEED(); + } + + Result LocationResolverManagerImpl::OpenRegisteredLocationResolver(sf::Out<sf::SharedPointer<IRegisteredLocationResolver>> out) { + std::scoped_lock lk(m_mutex); + + /* No existing resolver is present, create one. */ + if (!m_registered_location_resolver) { + m_registered_location_resolver = ContentLocationResolverFactory::CreateSharedEmplaced<IRegisteredLocationResolver, RegisteredLocationResolverImpl>(); + } + + /* Copy the output interface. */ + *out = m_registered_location_resolver; + R_SUCCEED(); + } + + Result LocationResolverManagerImpl::RefreshLocationResolver(ncm::StorageId storage_id) { + std::scoped_lock lk(m_mutex); + + /* Attempt to find an existing resolver. */ + auto resolver = m_location_resolvers.Find(storage_id); + R_UNLESS(resolver, lr::ResultUnknownStorageId()); + + /* Refresh the resolver. */ + if (storage_id != ncm::StorageId::Host) { + (*resolver)->Refresh(); + } + + R_SUCCEED(); + } + + Result LocationResolverManagerImpl::OpenAddOnContentLocationResolver(sf::Out<sf::SharedPointer<IAddOnContentLocationResolver>> out) { + std::scoped_lock lk(m_mutex); + + /* No existing resolver is present, create one. */ + if (!m_add_on_content_location_resolver) { + m_add_on_content_location_resolver = ContentLocationResolverFactory::CreateSharedEmplaced<IAddOnContentLocationResolver, AddOnContentLocationResolverImpl>(); + } + + /* Copy the output interface. */ + *out = m_add_on_content_location_resolver; + R_SUCCEED(); + } + + Result LocationResolverManagerImpl::SetEnabled(const sf::InMapAliasArray<ncm::StorageId> &storages) { + std::scoped_lock lk(m_mutex); + + /* If we're setting enabled, we're no longer enabled by default. */ + m_default_enabled = false; + + /* Create entries for each storage. */ + for (size_t i = 0; i < storages.GetSize(); ++i) { + /* Get the storage id. */ + const auto storage_id = storages[i]; + + /* Check that the storage id is acceptable. */ + R_UNLESS(IsAcceptableStorageId(storage_id), lr::ResultUnknownStorageId()); + + /* Set the storage id as enabled. */ + AMS_ABORT_UNLESS(m_location_resolvers_enabled.InsertOrAssign(storage_id, true)); + } + + /* Disable any open storages which shouldn't be enabled. */ + m_location_resolvers.ForEach([&](ncm::StorageId storage_id, sf::SharedPointer<ILocationResolver> &resolver) -> void { + /* Check if the storage id is contained in the input array. */ + for (size_t i = 0; i < storages.GetSize(); ++i) { + if (storages[i] == storage_id) { + /* The storage is enabled, so we can return. */ + return; + } + } + + /* The storage isn't enabled, so disable it. */ + R_ABORT_UNLESS(resolver->Disable()); + }); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_redirect_only_location_resolver_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_redirect_only_location_resolver_impl.cpp new file mode 100644 index 00000000..1b9e81a3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_redirect_only_location_resolver_impl.cpp @@ -0,0 +1,170 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lr_redirect_only_location_resolver_impl.hpp" + +namespace ams::lr { + + RedirectOnlyLocationResolverImpl::~RedirectOnlyLocationResolverImpl() { + /* Ensure entries are deallocated */ + this->ClearRedirections(); + } + + Result RedirectOnlyLocationResolverImpl::ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) { + RedirectionAttributes attr; + R_UNLESS(m_program_redirector.FindRedirection(out.GetPointer(), std::addressof(attr), id), lr::ResultProgramNotFound()); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::RedirectProgramPath(const Path &path, ncm::ProgramId id) { + m_program_redirector.SetRedirection(id, path, DefaultRedirectionAttributes); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::ResolveApplicationControlPath(sf::Out<Path> out, ncm::ProgramId id) { + RedirectionAttributes attr; + R_UNLESS(m_app_control_redirector.FindRedirection(out.GetPointer(), std::addressof(attr), id), lr::ResultControlNotFound()); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::ResolveApplicationHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) { + RedirectionAttributes attr; + R_UNLESS(m_html_docs_redirector.FindRedirection(out.GetPointer(), std::addressof(attr), id), lr::ResultHtmlDocumentNotFound()); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::ResolveDataPath(sf::Out<Path> out, ncm::DataId id) { + AMS_UNUSED(out, id); + R_THROW(lr::ResultDataNotFound()); + } + + Result RedirectOnlyLocationResolverImpl::RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId id) { + m_app_control_redirector.SetRedirection(id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::RedirectApplicationControlPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + m_app_control_redirector.SetRedirection(id, owner_id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) { + m_html_docs_redirector.SetRedirection(id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + m_html_docs_redirector.SetRedirection(id, owner_id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::ResolveApplicationLegalInformationPath(sf::Out<Path> out, ncm::ProgramId id) { + RedirectionAttributes attr; + R_UNLESS(m_legal_info_redirector.FindRedirection(out.GetPointer(), std::addressof(attr), id), lr::ResultLegalInformationNotFound()); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId id) { + m_legal_info_redirector.SetRedirection(id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + m_legal_info_redirector.SetRedirection(id, owner_id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::Refresh() { + this->ClearRedirections(); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::RedirectApplicationProgramPathDeprecated(const Path &path, ncm::ProgramId id) { + m_program_redirector.SetRedirection(id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::RedirectApplicationProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + m_program_redirector.SetRedirection(id, owner_id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::ClearApplicationRedirectionDeprecated() { + this->ClearRedirections(RedirectionFlags_Application); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::ClearApplicationRedirection(const sf::InArray<ncm::ProgramId> &excluding_ids) { + this->ClearRedirections(excluding_ids.GetPointer(), excluding_ids.GetSize()); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::EraseProgramRedirection(ncm::ProgramId id) { + m_program_redirector.EraseRedirection(id); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::EraseApplicationControlRedirection(ncm::ProgramId id) { + m_app_control_redirector.EraseRedirection(id); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::EraseApplicationHtmlDocumentRedirection(ncm::ProgramId id) { + m_html_docs_redirector.EraseRedirection(id); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::EraseApplicationLegalInformationRedirection(ncm::ProgramId id) { + m_legal_info_redirector.EraseRedirection(id); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::ResolveProgramPathForDebug(sf::Out<Path> out, ncm::ProgramId id) { + /* If a debug program redirection is present, use it. */ + RedirectionAttributes attr; + R_SUCCEED_IF(m_debug_program_redirector.FindRedirection(out.GetPointer(), std::addressof(attr), id)); + + /* Otherwise, try to find a normal program redirection. */ + R_UNLESS(m_program_redirector.FindRedirection(out.GetPointer(), std::addressof(attr), id), lr::ResultDebugProgramNotFound()); + + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::RedirectProgramPathForDebug(const Path &path, ncm::ProgramId id) { + m_debug_program_redirector.SetRedirection(id, path, DefaultRedirectionAttributes); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId id) { + m_debug_program_redirector.SetRedirection(id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + m_debug_program_redirector.SetRedirection(id, owner_id, path, DefaultRedirectionAttributes, RedirectionFlags_Application); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::EraseProgramRedirectionForDebug(ncm::ProgramId id) { + m_debug_program_redirector.EraseRedirection(id); + R_SUCCEED(); + } + + Result RedirectOnlyLocationResolverImpl::Disable() { + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_redirect_only_location_resolver_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_redirect_only_location_resolver_impl.hpp new file mode 100644 index 00000000..eaa03573 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_redirect_only_location_resolver_impl.hpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include "lr_location_resolver_impl_base.hpp" + +namespace ams::lr { + + class RedirectOnlyLocationResolverImpl : public LocationResolverImplBase { + public: + ~RedirectOnlyLocationResolverImpl(); + public: + /* Actual commands. */ + Result ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id); + Result RedirectProgramPath(const Path &path, ncm::ProgramId id); + Result ResolveApplicationControlPath(sf::Out<Path> out, ncm::ProgramId id); + Result ResolveApplicationHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id); + Result ResolveDataPath(sf::Out<Path> out, ncm::DataId id); + Result RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId id); + Result RedirectApplicationControlPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id); + Result RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id); + Result RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id); + Result ResolveApplicationLegalInformationPath(sf::Out<Path> out, ncm::ProgramId id); + Result RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId id); + Result RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id); + Result Refresh(); + Result RedirectApplicationProgramPathDeprecated(const Path &path, ncm::ProgramId id); + Result RedirectApplicationProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id); + Result ClearApplicationRedirectionDeprecated(); + Result ClearApplicationRedirection(const sf::InArray<ncm::ProgramId> &excluding_ids); + Result EraseProgramRedirection(ncm::ProgramId id); + Result EraseApplicationControlRedirection(ncm::ProgramId id); + Result EraseApplicationHtmlDocumentRedirection(ncm::ProgramId id); + Result EraseApplicationLegalInformationRedirection(ncm::ProgramId id); + Result ResolveProgramPathForDebug(sf::Out<Path> out, ncm::ProgramId id); + Result RedirectProgramPathForDebug(const Path &path, ncm::ProgramId id); + Result RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId id); + Result RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id); + Result EraseProgramRedirectionForDebug(ncm::ProgramId id); + Result Disable(); + }; + static_assert(lr::IsILocationResolver<RedirectOnlyLocationResolverImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_registered_data.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_registered_data.hpp new file mode 100644 index 00000000..03e2037e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_registered_data.hpp @@ -0,0 +1,146 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere/lr/lr_types.hpp> +#include "lr_location_redirector.hpp" + +namespace ams::lr { + + template<typename Key, typename Value, size_t NumEntries> + class RegisteredData { + NON_COPYABLE(RegisteredData); + NON_MOVEABLE(RegisteredData); + private: + struct Entry { + Value value; + ncm::ProgramId owner_id; + Key key; + bool is_valid; + }; + private: + Entry m_entries[NumEntries]; + size_t m_capacity; + private: + inline bool IsExcluded(const ncm::ProgramId id, const ncm::ProgramId *excluding_ids, size_t num_ids) const { + /* Try to find program id in exclusions. */ + for (size_t i = 0; i < num_ids; i++) { + if (id == excluding_ids[i]) { + return true; + } + } + + return false; + } + + inline void RegisterImpl(size_t i, const Key &key, const Value &value, const ncm::ProgramId owner_id) { + /* Populate entry. */ + Entry &entry = m_entries[i]; + entry.key = key; + entry.value = value; + entry.owner_id = owner_id; + entry.is_valid = true; + } + public: + RegisteredData(size_t capacity = NumEntries) : m_capacity(capacity) { + this->Clear(); + } + + bool Register(const Key &key, const Value &value, const ncm::ProgramId owner_id) { + /* Try to find an existing value. */ + for (size_t i = 0; i < this->GetCapacity(); i++) { + Entry &entry = m_entries[i]; + if (entry.is_valid && entry.key == key) { + this->RegisterImpl(i, key, value, owner_id); + return true; + } + } + + /* We didn't find an existing entry, so try to create a new one. */ + for (size_t i = 0; i < this->GetCapacity(); i++) { + Entry &entry = m_entries[i]; + if (!entry.is_valid) { + this->RegisterImpl(i, key, value, owner_id); + return true; + } + } + + return false; + } + + void Unregister(const Key &key) { + /* Invalidate entries with a matching key. */ + for (size_t i = 0; i < this->GetCapacity(); i++) { + Entry &entry = m_entries[i]; + if (entry.is_valid && entry.key == key) { + entry.is_valid = false; + } + } + } + + void UnregisterOwnerProgram(ncm::ProgramId owner_id) { + /* Invalidate entries with a matching owner id. */ + for (size_t i = 0; i < this->GetCapacity(); i++) { + Entry &entry = m_entries[i]; + if (entry.owner_id == owner_id) { + entry.is_valid = false; + } + } + } + + bool Find(Value *out, const Key &key) const { + /* Locate a matching entry. */ + for (size_t i = 0; i < this->GetCapacity(); i++) { + const Entry &entry = m_entries[i]; + if (entry.is_valid && entry.key == key) { + *out = entry.value; + return true; + } + } + + return false; + } + + void Clear() { + /* Invalidate all entries. */ + for (size_t i = 0; i < this->GetCapacity(); i++) { + m_entries[i].is_valid = false; + } + } + + void ClearExcluding(const ncm::ProgramId *ids, size_t num_ids) { + /* Invalidate all entries unless excluded. */ + for (size_t i = 0; i < this->GetCapacity(); i++) { + Entry &entry = m_entries[i]; + + if (!this->IsExcluded(entry.owner_id, ids, num_ids)) { + entry.is_valid = false; + } + } + } + + size_t GetCapacity() const { + return m_capacity; + } + }; + + template<typename Key, size_t NumEntries> + using RegisteredLocations = RegisteredData<Key, RedirectionPath, NumEntries>; + + template<typename Key, size_t NumEntries> + using RegisteredStorages = RegisteredData<Key, ncm::StorageId, NumEntries>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_registered_location_resolver_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_registered_location_resolver_impl.cpp new file mode 100644 index 00000000..a6114875 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_registered_location_resolver_impl.cpp @@ -0,0 +1,158 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lr_registered_location_resolver_impl.hpp" + +namespace ams::lr { + + namespace { + + template<size_t N> + bool ResolvePath(Path *out, const LocationRedirector &redirector, const RegisteredLocations<ncm::ProgramId, N> &locations, ncm::ProgramId id) { + /* Attempt to use a redirection if present. */ + RedirectionAttributes attr; + if (!redirector.FindRedirection(out, std::addressof(attr), id)) { + /* Otherwise try and use a registered location. */ + RedirectionPath redir_path; + if (!locations.Find(std::addressof(redir_path), id)) { + return false; + } + + /* Set the output path. */ + *out = redir_path.path; + } + return true; + } + + template<size_t N> + void RegisterPath(RegisteredLocations<ncm::ProgramId, N> &locations, ncm::ProgramId id, const Path& path, ncm::ProgramId owner_id) { + /* Create a redirection path. */ + const RedirectionPath redir_path = { path, DefaultRedirectionAttributes }; + + /* If we register successfully, we're good. */ + if (locations.Register(id, redir_path, owner_id)) { + return; + } + + /* Otherwise, clear and register (this should always succeed). */ + locations.Clear(); + locations.Register(id, redir_path, owner_id); + } + + } + + RegisteredLocationResolverImpl::~RegisteredLocationResolverImpl() { + /* Ensure entries are deallocated */ + this->ClearRedirections(); + } + + /* Helper function. */ + void RegisteredLocationResolverImpl::ClearRedirections(u32 flags) { + m_html_docs_redirector.ClearRedirections(flags); + m_program_redirector.ClearRedirections(flags); + } + + Result RegisteredLocationResolverImpl::RefreshImpl(const ncm::ProgramId *excluding_ids, size_t num_ids) { + /* On < 9.0.0, exclusion lists were not supported yet, so simply clear and return. */ + if (hos::GetVersion() < hos::Version_9_0_0) { + this->ClearRedirections(); + R_SUCCEED(); + } + + if (num_ids) { + /* If we have exclusion lists, explicitly clear our locations. */ + m_registered_program_locations.ClearExcluding(excluding_ids, num_ids); + m_registered_html_docs_locations.ClearExcluding(excluding_ids, num_ids); + } else { + /* If we don't, just perform a general clear (as pre 9.0.0 did). */ + this->ClearRedirections(); + } + + /* Clear redirectors using exclusion lists. */ + m_program_redirector.ClearRedirectionsExcludingOwners(excluding_ids, num_ids); + m_html_docs_redirector.ClearRedirectionsExcludingOwners(excluding_ids, num_ids); + R_SUCCEED(); + } + + Result RegisteredLocationResolverImpl::ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) { + R_UNLESS(ResolvePath(out.GetPointer(), m_program_redirector, m_registered_program_locations, id), lr::ResultProgramNotFound()); + R_SUCCEED(); + } + + Result RegisteredLocationResolverImpl::RegisterProgramPathDeprecated(const Path &path, ncm::ProgramId id) { + RegisterPath(m_registered_program_locations, id, path, ncm::InvalidProgramId); + R_SUCCEED(); + } + + Result RegisteredLocationResolverImpl::RegisterProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + RegisterPath(m_registered_program_locations, id, path, owner_id); + R_SUCCEED(); + } + + Result RegisteredLocationResolverImpl::UnregisterProgramPath(ncm::ProgramId id) { + m_registered_program_locations.Unregister(id); + R_SUCCEED(); + } + + Result RegisteredLocationResolverImpl::RedirectProgramPathDeprecated(const Path &path, ncm::ProgramId id) { + m_program_redirector.SetRedirection(id, path, DefaultRedirectionAttributes); + R_SUCCEED(); + } + + Result RegisteredLocationResolverImpl::RedirectProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + m_program_redirector.SetRedirection(id, owner_id, path, DefaultRedirectionAttributes); + R_SUCCEED(); + } + + Result RegisteredLocationResolverImpl::ResolveHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) { + R_UNLESS(ResolvePath(out.GetPointer(), m_html_docs_redirector, m_registered_html_docs_locations, id), lr::ResultHtmlDocumentNotFound()); + R_SUCCEED(); + } + + Result RegisteredLocationResolverImpl::RegisterHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) { + RegisterPath(m_registered_html_docs_locations, id, path, ncm::InvalidProgramId); + R_SUCCEED(); + } + + Result RegisteredLocationResolverImpl::RegisterHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + RegisterPath(m_registered_html_docs_locations, id, path, owner_id); + R_SUCCEED(); + } + + Result RegisteredLocationResolverImpl::UnregisterHtmlDocumentPath(ncm::ProgramId id) { + m_registered_html_docs_locations.Unregister(id); + R_SUCCEED(); + } + + Result RegisteredLocationResolverImpl::RedirectHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) { + m_html_docs_redirector.SetRedirection(id, path, DefaultRedirectionAttributes); + R_SUCCEED(); + } + + Result RegisteredLocationResolverImpl::RedirectHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + m_html_docs_redirector.SetRedirection(id, owner_id, path, DefaultRedirectionAttributes); + R_SUCCEED(); + } + + Result RegisteredLocationResolverImpl::Refresh() { + R_RETURN(this->RefreshImpl(nullptr, 0)); + } + + Result RegisteredLocationResolverImpl::RefreshExcluding(const sf::InArray<ncm::ProgramId> &ids) { + R_RETURN(this->RefreshImpl(ids.GetPointer(), ids.GetSize())); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_registered_location_resolver_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_registered_location_resolver_impl.hpp new file mode 100644 index 00000000..bb3c0841 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_registered_location_resolver_impl.hpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> +#include "lr_location_redirector.hpp" +#include "lr_registered_data.hpp" + +namespace ams::lr { + + class RegisteredLocationResolverImpl { + private: + static constexpr size_t MaxRegisteredLocationsDeprecated = 0x10; + static constexpr size_t MaxRegisteredLocations = 0x20; + static_assert(MaxRegisteredLocations >= MaxRegisteredLocationsDeprecated); + private: + static ALWAYS_INLINE size_t GetMaxRegisteredLocations() { + if (hos::GetVersion() >= hos::Version_9_0_0) { + return MaxRegisteredLocations; + } else { + return MaxRegisteredLocationsDeprecated; + } + } + private: + /* Redirection and registered location storage. */ + LocationRedirector m_program_redirector; + RegisteredLocations<ncm::ProgramId, MaxRegisteredLocations> m_registered_program_locations; + LocationRedirector m_html_docs_redirector; + RegisteredLocations<ncm::ProgramId, MaxRegisteredLocations> m_registered_html_docs_locations; + private: + /* Helper functions. */ + void ClearRedirections(u32 flags = RedirectionFlags_None); + Result RefreshImpl(const ncm::ProgramId *excluding_ids, size_t num_ids); + public: + RegisteredLocationResolverImpl() : m_program_redirector(), m_registered_program_locations(GetMaxRegisteredLocations()), m_html_docs_redirector(), m_registered_html_docs_locations(GetMaxRegisteredLocations()) { /* ... */ } + ~RegisteredLocationResolverImpl(); + public: + /* Actual commands. */ + Result ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id); + Result RegisterProgramPathDeprecated(const Path &path, ncm::ProgramId id); + Result RegisterProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id); + Result UnregisterProgramPath(ncm::ProgramId id); + Result RedirectProgramPathDeprecated(const Path &path, ncm::ProgramId id); + Result RedirectProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id); + Result ResolveHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id); + Result RegisterHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id); + Result RegisterHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id); + Result UnregisterHtmlDocumentPath(ncm::ProgramId id); + Result RedirectHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id); + Result RedirectHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id); + Result Refresh(); + Result RefreshExcluding(const sf::InArray<ncm::ProgramId> &ids); + }; + static_assert(lr::IsIRegisteredLocationResolver<RegisteredLocationResolverImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_remote_location_resolver_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_remote_location_resolver_impl.hpp new file mode 100644 index 00000000..c6120c07 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_remote_location_resolver_impl.hpp @@ -0,0 +1,166 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::lr { + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemoteLocationResolverImpl { + private: + ::LrLocationResolver m_srv; + public: + RemoteLocationResolverImpl(::LrLocationResolver &l) : m_srv(l) { /* ... */ } + + ~RemoteLocationResolverImpl() { ::serviceClose(std::addressof(m_srv.s)); } + public: + /* Actual commands. */ + Result ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) { + R_RETURN(::lrLrResolveProgramPath(std::addressof(m_srv), id.value, out->str)); + } + + Result RedirectProgramPath(const Path &path, ncm::ProgramId id) { + R_RETURN(::lrLrRedirectProgramPath(std::addressof(m_srv), id.value, path.str)); + } + + Result ResolveApplicationControlPath(sf::Out<Path> out, ncm::ProgramId id) { + R_RETURN(::lrLrResolveApplicationControlPath(std::addressof(m_srv), id.value, out->str)); + } + + Result ResolveApplicationHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) { + R_RETURN(::lrLrResolveApplicationHtmlDocumentPath(std::addressof(m_srv), id.value, out->str)); + } + + Result ResolveDataPath(sf::Out<Path> out, ncm::DataId id) { + R_RETURN(::lrLrResolveDataPath(std::addressof(m_srv), id.value, out->str)); + } + + Result RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId id) { + R_RETURN(::lrLrRedirectApplicationControlPath(std::addressof(m_srv), id.value, 0, path.str)); + } + + Result RedirectApplicationControlPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + R_RETURN(::lrLrRedirectApplicationControlPath(std::addressof(m_srv), id.value, owner_id.value, path.str)); + } + + Result RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) { + R_RETURN(::lrLrRedirectApplicationHtmlDocumentPath(std::addressof(m_srv), id.value, 0, path.str)); + } + + Result RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + R_RETURN(::lrLrRedirectApplicationHtmlDocumentPath(std::addressof(m_srv), id.value, owner_id.value, path.str)); + } + + Result ResolveApplicationLegalInformationPath(sf::Out<Path> out, ncm::ProgramId id) { + R_RETURN(::lrLrResolveApplicationLegalInformationPath(std::addressof(m_srv), id.value, out->str)); + } + + Result RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId id) { + R_RETURN(::lrLrRedirectApplicationLegalInformationPath(std::addressof(m_srv), id.value, 0, path.str)); + } + + Result RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + R_RETURN(::lrLrRedirectApplicationLegalInformationPath(std::addressof(m_srv), id.value, owner_id.value, path.str)); + } + + Result Refresh() { + R_RETURN(::lrLrRefresh(std::addressof(m_srv))); + } + + Result RedirectApplicationProgramPathDeprecated(const Path &path, ncm::ProgramId id) { + /* TODO: libnx bindings */ + AMS_UNUSED(path, id); + AMS_ABORT(); + } + + Result RedirectApplicationProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + /* TODO: libnx bindings */ + AMS_UNUSED(path, id, owner_id); + AMS_ABORT(); + } + + Result ClearApplicationRedirectionDeprecated() { + /* TODO: libnx bindings */ + AMS_ABORT(); + } + + Result ClearApplicationRedirection(const sf::InArray<ncm::ProgramId> &excluding_ids) { + /* TODO: libnx bindings */ + AMS_UNUSED(excluding_ids); + AMS_ABORT(); + } + + Result EraseProgramRedirection(ncm::ProgramId id) { + R_RETURN(::lrLrEraseProgramRedirection(std::addressof(m_srv), id.value)); + } + + Result EraseApplicationControlRedirection(ncm::ProgramId id) { + /* TODO: libnx bindings */ + AMS_UNUSED(id); + AMS_ABORT(); + } + + Result EraseApplicationHtmlDocumentRedirection(ncm::ProgramId id) { + /* TODO: libnx bindings */ + AMS_UNUSED(id); + AMS_ABORT(); + } + + Result EraseApplicationLegalInformationRedirection(ncm::ProgramId id) { + /* TODO: libnx bindings */ + AMS_UNUSED(id); + AMS_ABORT(); + } + + Result ResolveProgramPathForDebug(sf::Out<Path> out, ncm::ProgramId id) { + /* TODO: libnx bindings */ + AMS_UNUSED(out, id); + AMS_ABORT(); + } + + Result RedirectProgramPathForDebug(const Path &path, ncm::ProgramId id) { + /* TODO: libnx bindings */ + AMS_UNUSED(path, id); + AMS_ABORT(); + } + + Result RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId id) { + /* TODO: libnx bindings */ + AMS_UNUSED(path, id); + AMS_ABORT(); + } + + Result RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + /* TODO: libnx bindings */ + AMS_UNUSED(path, id, owner_id); + AMS_ABORT(); + } + + Result EraseProgramRedirectionForDebug(ncm::ProgramId id) { + /* TODO: libnx bindings */ + AMS_UNUSED(id); + AMS_ABORT(); + } + + Result Disable() { + /* TODO: libnx bindings */ + AMS_ABORT(); + } + }; + static_assert(lr::IsILocationResolver<RemoteLocationResolverImpl>); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_remote_location_resolver_manager_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_remote_location_resolver_manager_impl.hpp new file mode 100644 index 00000000..a543e71e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_remote_location_resolver_manager_impl.hpp @@ -0,0 +1,66 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "lr_location_resolver_manager_factory.hpp" +#include "lr_remote_location_resolver_impl.hpp" +#include "lr_remote_registered_location_resolver_impl.hpp" + +namespace ams::lr { + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemoteLocationResolverManagerImpl { + public: + RemoteLocationResolverManagerImpl() { R_ABORT_UNLESS(::lrInitialize()); } + + ~RemoteLocationResolverManagerImpl() { ::lrExit(); } + public: + /* Actual commands. */ + Result OpenLocationResolver(sf::Out<sf::SharedPointer<ILocationResolver>> out, ncm::StorageId storage_id) { + LrLocationResolver lr; + R_TRY(::lrOpenLocationResolver(static_cast<::NcmStorageId>(storage_id), std::addressof(lr))); + + *out = LocationResolverManagerFactory::CreateSharedEmplaced<ILocationResolver, RemoteLocationResolverImpl>(lr); + R_SUCCEED(); + } + + Result OpenRegisteredLocationResolver(sf::Out<sf::SharedPointer<IRegisteredLocationResolver>> out) { + LrRegisteredLocationResolver lr; + R_TRY(::lrOpenRegisteredLocationResolver(std::addressof(lr))); + + *out = LocationResolverManagerFactory::CreateSharedEmplaced<IRegisteredLocationResolver, RemoteRegisteredLocationResolverImpl>(lr); + R_SUCCEED(); + } + + Result RefreshLocationResolver(ncm::StorageId storage_id) { + AMS_UNUSED(storage_id); + AMS_ABORT("TODO: libnx binding"); + } + + Result OpenAddOnContentLocationResolver(sf::Out<sf::SharedPointer<IAddOnContentLocationResolver>> out) { + AMS_UNUSED(out); + AMS_ABORT("TODO: libnx binding"); + } + + Result SetEnabled(const sf::InMapAliasArray<ncm::StorageId> &storages) { + AMS_UNUSED(storages); + AMS_ABORT("TODO: libnx binding"); + } + }; + static_assert(lr::IsILocationResolverManager<RemoteLocationResolverManagerImpl>); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_remote_registered_location_resolver_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_remote_registered_location_resolver_impl.hpp new file mode 100644 index 00000000..27b7c8b8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/lr/lr_remote_registered_location_resolver_impl.hpp @@ -0,0 +1,116 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> + +namespace ams::lr { + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemoteRegisteredLocationResolverImpl { + private: + ::LrRegisteredLocationResolver m_srv; + public: + RemoteRegisteredLocationResolverImpl(::LrRegisteredLocationResolver &l) : m_srv(l) { /* ... */ } + + ~RemoteRegisteredLocationResolverImpl() { ::serviceClose(std::addressof(m_srv.s)); } + public: + /* Actual commands. */ + Result ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) { + R_RETURN(::lrRegLrResolveProgramPath(std::addressof(m_srv), id.value, out->str)); + } + + Result RegisterProgramPathDeprecated(const Path &path, ncm::ProgramId id) { + /* TODO: libnx bindings */ + AMS_UNUSED(path, id); + AMS_ABORT(); + } + + Result RegisterProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + /* TODO: libnx bindings */ + AMS_UNUSED(path, id, owner_id); + AMS_ABORT(); + } + + Result UnregisterProgramPath(ncm::ProgramId id) { + /* TODO: libnx bindings */ + AMS_UNUSED(id); + AMS_ABORT(); + } + + Result RedirectProgramPathDeprecated(const Path &path, ncm::ProgramId id) { + /* TODO: libnx bindings */ + AMS_UNUSED(path, id); + AMS_ABORT(); + } + + Result RedirectProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + /* TODO: libnx bindings */ + AMS_UNUSED(path, id, owner_id); + AMS_ABORT(); + } + + Result ResolveHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) { + /* TODO: libnx bindings */ + AMS_UNUSED(out, id); + AMS_ABORT(); + } + + Result RegisterHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) { + /* TODO: libnx bindings */ + AMS_UNUSED(path, id); + AMS_ABORT(); + } + + Result RegisterHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + /* TODO: libnx bindings */ + AMS_UNUSED(path, id, owner_id); + AMS_ABORT(); + } + + Result UnregisterHtmlDocumentPath(ncm::ProgramId id) { + /* TODO: libnx bindings */ + AMS_UNUSED(id); + AMS_ABORT(); + } + + Result RedirectHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) { + /* TODO: libnx bindings */ + AMS_UNUSED(path, id); + AMS_ABORT(); + } + + Result RedirectHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) { + /* TODO: libnx bindings */ + AMS_UNUSED(path, id, owner_id); + AMS_ABORT(); + } + + Result Refresh() { + /* TODO: libnx bindings */ + AMS_ABORT(); + } + + Result RefreshExcluding(const sf::InArray<ncm::ProgramId> &ids) { + /* TODO: libnx bindings */ + AMS_UNUSED(ids); + AMS_ABORT(); + } + }; + static_assert(lr::IsIRegisteredLocationResolver<RemoteRegisteredLocationResolverImpl>); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_cached_heap.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_cached_heap.cpp new file mode 100644 index 00000000..4a4bf2ae --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_cached_heap.cpp @@ -0,0 +1,119 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "mem_impl_heap_platform.hpp" +#include "mem_impl_heap_tls_heap_static.hpp" +#include "mem_impl_heap_tls_heap_central.hpp" + +namespace ams::mem::impl::heap { + + void *CachedHeap::Allocate(size_t n) { + return m_tls_heap_cache->Allocate(n); + } + + void *CachedHeap::Allocate(size_t n, size_t align) { + return m_tls_heap_cache->Allocate(n, align); + } + + size_t CachedHeap::GetAllocationSize(const void *ptr) { + return m_tls_heap_cache->GetAllocationSize(ptr); + } + + errno_t CachedHeap::Free(void *p) { + return m_tls_heap_cache->Free(p); + } + + errno_t CachedHeap::FreeWithSize(void *p, size_t size) { + return m_tls_heap_cache->FreeWithSize(p, size); + } + + errno_t CachedHeap::Reallocate(void *ptr, size_t size, void **p) { + return m_tls_heap_cache->Reallocate(ptr, size, p); + } + + errno_t CachedHeap::Shrink(void *ptr, size_t size) { + return m_tls_heap_cache->Shrink(ptr, size); + } + + void CachedHeap::ReleaseAllCache() { + if (m_tls_heap_cache) { + m_tls_heap_cache->ReleaseAllCache(); + } + } + + void CachedHeap::Finalize() { + if (m_tls_heap_cache) { + m_tls_heap_cache->Finalize(); + m_tls_heap_cache = nullptr; + } + } + + bool CachedHeap::CheckCache() { + bool cache = false; + + const auto err = this->Query(AllocQuery_CheckCache, std::addressof(cache)); + AMS_ASSERT(err == 0); + AMS_UNUSED(err); + + return cache; + } + + errno_t CachedHeap::QueryV(int _query, std::va_list vl) { + const AllocQuery query = static_cast<AllocQuery>(_query); + switch (query) { + case AllocQuery_CheckCache: + { + bool *out = va_arg(vl, bool *); + if (out) { + *out = (m_tls_heap_cache == nullptr) || m_tls_heap_cache->CheckCache(); + } + return 0; + } + case AllocQuery_ClearCache: + { + this->ReleaseAllCache(); + return 0; + } + case AllocQuery_FinalizeCache: + { + this->Finalize(); + return 0; + } + default: + return EINVAL; + } + } + + errno_t CachedHeap::Query(int query, ...) { + std::va_list vl; + va_start(vl, query); + auto err = this->QueryV(query, vl); + va_end(vl); + return err; + } + + void CachedHeap::Reset(TlsHeapCache *thc) { + this->Finalize(); + m_tls_heap_cache = thc; + } + + TlsHeapCache *CachedHeap::Release() { + TlsHeapCache *ret = m_tls_heap_cache; + m_tls_heap_cache = nullptr; + return ret; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_central_heap.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_central_heap.cpp new file mode 100644 index 00000000..35e043ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_central_heap.cpp @@ -0,0 +1,405 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "mem_impl_heap_platform.hpp" +#include "mem_impl_heap_tls_heap_static.hpp" +#include "mem_impl_heap_tls_heap_central.hpp" + +namespace ams::mem::impl::heap { + + errno_t CentralHeap::Initialize(void *start, size_t size, u32 option) { + /* Validate size. */ + if (size == 0 || !util::IsAligned(size, PageSize)) { + return EINVAL; + } + + /* Don't allow initializing twice. */ + if (m_start) { + return EEXIST; + } + + if (start) { + /* We were provided with a region to use as backing memory. */ + u8 *aligned_start = reinterpret_cast<u8 *>(util::AlignUp(reinterpret_cast<uintptr_t>(start), PageSize)); + u8 *aligned_end = reinterpret_cast<u8 *>(util::AlignDown(reinterpret_cast<uintptr_t>(start) + size, PageSize)); + if (aligned_start >= aligned_end) { + return EINVAL; + } + + m_start = aligned_start; + m_end = aligned_end; + m_option = option; + m_tls_heap_central = std::construct_at(reinterpret_cast<TlsHeapCentral *>(m_start)); + if (auto err = m_tls_heap_central->Initialize(m_start, m_end - m_start, false); err != 0) { + std::destroy_at(m_tls_heap_central); + m_tls_heap_central = nullptr; + AMS_ASSERT(err == 0); + return err; + } + m_use_virtual_memory = false; + } else { + /* We were not provided with a region to use as backing. */ + void *mem = nullptr; + if (auto err = AllocateVirtualMemory(std::addressof(mem), size); err != 0) { + return err; + } + if (!util::IsAligned(reinterpret_cast<uintptr_t>(mem), PageSize)) { + FreeVirtualMemory(mem, size); + size += PageSize; + if (auto err = AllocateVirtualMemory(std::addressof(mem), size); err != 0) { + return err; + } + } + m_start = static_cast<u8 *>(mem); + m_end = m_start + size; + m_option = option; + void *central = reinterpret_cast<void *>(util::AlignUp(reinterpret_cast<uintptr_t>(mem), PageSize)); + if (auto err = AllocatePhysicalMemory(central, sizeof(TlsHeapCentral)); err != 0) { + return err; + } + m_tls_heap_central = std::construct_at(static_cast<TlsHeapCentral *>(central)); + if (auto err = m_tls_heap_central->Initialize(central, size, true); err != 0) { + std::destroy_at(m_tls_heap_central); + m_tls_heap_central = nullptr; + AMS_ASSERT(err == 0); + return err; + } + m_use_virtual_memory = true; + } + + return 0; + } + + void CentralHeap::Finalize() { + if (m_tls_heap_central) { + std::destroy_at(m_tls_heap_central); + } + if (m_use_virtual_memory) { + mem::impl::physical_free(util::AlignUp(static_cast<void *>(m_start), PageSize), m_end - m_start); + mem::impl::virtual_free(m_start, m_end - m_start); + } + m_tls_heap_central = nullptr; + m_use_virtual_memory = false; + m_option = 0; + m_start = nullptr; + m_end = nullptr; + } + + void *CentralHeap::Allocate(size_t n, size_t align) { + if (!util::IsPowerOfTwo(align)) { + return nullptr; + } + if (n > MaxSize) { + return nullptr; + } + if (align > PageSize) { + return m_tls_heap_central->CacheLargeMemoryWithBigAlign(util::AlignUp(n, PageSize), align); + } + + const size_t real_size = TlsHeapStatic::GetRealSizeFromSizeAndAlignment(util::AlignUp(n, align), align); + const auto cls = TlsHeapStatic::GetClassFromSize(real_size); + if (!cls) { + return m_tls_heap_central->CacheLargeMemory(real_size); + } + if (real_size == 0) { + return nullptr; + } + AMS_ASSERT(static_cast<u32>(cls) < TlsHeapStatic::NumClassInfo); + return m_tls_heap_central->CacheSmallMemory(cls, align); + } + + size_t CentralHeap::GetAllocationSize(const void *ptr) { + const auto cls = m_tls_heap_central->GetClassFromPointer(ptr); + if (cls > 0) { + /* Check that the pointer has alignment from out allocator. */ + if (!util::IsAligned(reinterpret_cast<uintptr_t>(ptr), MinimumAlignment)) { + return 0; + } + AMS_ASSERT(static_cast<u32>(cls) < TlsHeapStatic::NumClassInfo); + return TlsHeapStatic::GetChunkSize(cls); + } else if (ptr != nullptr) { + return m_tls_heap_central->GetAllocationSize(ptr); + } else { + return 0; + } + } + + errno_t CentralHeap::Free(void *ptr) { + /* Allow Free(nullptr) */ + if (ptr == nullptr) { + return 0; + } + + /* Check that the pointer has alignment from out allocator. */ + if(!util::IsAligned(reinterpret_cast<uintptr_t>(ptr), MinimumAlignment)) { + AMS_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(ptr), MinimumAlignment)); + return EFAULT; + } + + const auto cls = m_tls_heap_central->GetClassFromPointer(ptr); + if (cls >= 0) { + AMS_ASSERT(static_cast<u32>(cls) < TlsHeapStatic::NumClassInfo); + if (cls) { + return m_tls_heap_central->UncacheSmallMemory(ptr); + } else { + return m_tls_heap_central->UncacheLargeMemory(ptr); + } + } else { + AMS_ASSERT(cls >= 0); + return EFAULT; + } + } + + errno_t CentralHeap::FreeWithSize(void *ptr, size_t size) { + if (TlsHeapStatic::GetClassFromSize(size)) { + return m_tls_heap_central->UncacheSmallMemory(ptr); + } else { + return m_tls_heap_central->UncacheLargeMemory(ptr); + } + } + + errno_t CentralHeap::Reallocate(void *ptr, size_t size, void **p) { + AMS_ASSERT(ptr != nullptr && size != 0); + if (!size) { + return EINVAL; + } + if (size > MaxSize) { + return ENOMEM; + } + + const auto cls_from_size = TlsHeapStatic::GetClassFromSize(size); + const auto cls_from_ptr = m_tls_heap_central->GetClassFromPointer(ptr); + if (cls_from_ptr) { + if (cls_from_ptr <= 0) { + return EFAULT; + } else if (cls_from_size && static_cast<s32>(cls_from_size) <= cls_from_ptr) { + *p = ptr; + return 0; + } else { + const size_t new_chunk_size = TlsHeapStatic::GetChunkSize(cls_from_ptr); + *p = this->Allocate(new_chunk_size); + if (*p) { + std::memcpy(*p, ptr, size); + return m_tls_heap_central->UncacheSmallMemory(ptr); + } else { + return ENOMEM; + } + } + } else if (cls_from_size) { + *p = this->Allocate(size); + if (*p) { + std::memcpy(*p, ptr, size); + return m_tls_heap_central->UncacheLargeMemory(ptr); + } else { + return ENOMEM; + } + } else { + return m_tls_heap_central->ReallocateLargeMemory(ptr, size, p); + } + } + + errno_t CentralHeap::Shrink(void *ptr, size_t size) { + AMS_ASSERT(ptr != nullptr && size != 0); + if (!size) { + return EINVAL; + } + if (size > MaxSize) { + return ENOMEM; + } + + const auto cls_from_size = TlsHeapStatic::GetClassFromSize(size); + const auto cls_from_ptr = m_tls_heap_central->GetClassFromPointer(ptr); + if (cls_from_ptr) { + if (cls_from_ptr <= 0) { + return EFAULT; + } else if (cls_from_size && static_cast<s32>(cls_from_size) <= cls_from_ptr) { + return 0; + } else { + return EINVAL; + } + } else if (cls_from_size) { + return m_tls_heap_central->ShrinkLargeMemory(ptr, PageSize); + } else { + return m_tls_heap_central->ShrinkLargeMemory(ptr, size); + } + } + + bool CentralHeap::MakeCache(CachedHeap *cached_heap) { + if (cached_heap == nullptr) { + return false; + } + + AMS_ASSERT(m_tls_heap_central != nullptr); + const auto cls = TlsHeapStatic::GetClassFromSize(sizeof(*cached_heap)); + void *tls_heap_cache = m_tls_heap_central->CacheSmallMemoryForSystem(cls); + if (tls_heap_cache == nullptr) { + return false; + } + + std::construct_at(static_cast<TlsHeapCache *>(tls_heap_cache), m_tls_heap_central, m_option); + if (m_tls_heap_central->AddThreadCache(reinterpret_cast<TlsHeapCache *>(tls_heap_cache)) != 0) { + m_tls_heap_central->UncacheSmallMemory(tls_heap_cache); + return false; + } + + cached_heap->Reset(reinterpret_cast<TlsHeapCache *>(tls_heap_cache)); + return true; + } + + errno_t CentralHeap::WalkAllocatedPointers(HeapWalkCallback callback, void *user_data) { + if (!callback || !m_tls_heap_central) { + return EINVAL; + } + return m_tls_heap_central->WalkAllocatedPointers(callback, user_data); + } + + errno_t CentralHeap::Query(int query, ...) { + std::va_list vl; + va_start(vl, query); + auto err = this->QueryV(query, vl); + va_end(vl); + return err; + } + + errno_t CentralHeap::QueryV(int _query, std::va_list vl) { + const AllocQuery query = static_cast<AllocQuery>(_query); + switch (query) { + case AllocQuery_Dump: + case AllocQuery_DumpJson: + { + auto dump_mode = static_cast<DumpMode>(va_arg(vl, int)); + auto fd = va_arg(vl, int); + if (m_tls_heap_central) { + m_tls_heap_central->Dump(dump_mode, fd, query == AllocQuery_DumpJson); + } + return 0; + } + case AllocQuery_PageSize: + { + size_t *out = va_arg(vl, size_t *); + if (out) { + *out = PageSize; + } + return 0; + } + case AllocQuery_AllocatedSize: + case AllocQuery_FreeSize: + case AllocQuery_SystemSize: + case AllocQuery_MaxAllocatableSize: + { + size_t *out = va_arg(vl, size_t *); + if (!out) { + return 0; + } + if (!m_tls_heap_central) { + *out = 0; + return 0; + } + TlsHeapMemStats stats; + m_tls_heap_central->GetMemStats(std::addressof(stats)); + switch (query) { + case AllocQuery_AllocatedSize: + default: + *out = stats.allocated_size; + break; + case AllocQuery_FreeSize: + *out = stats.free_size; + break; + case AllocQuery_SystemSize: + *out = stats.system_size; + break; + case AllocQuery_MaxAllocatableSize: + *out = stats.max_allocatable_size; + break; + } + return 0; + } + case AllocQuery_IsClean: + { + int *out = va_arg(vl, int *); + if (out) { + *out = !m_tls_heap_central || m_tls_heap_central->IsClean(); + } + return 0; + } + case AllocQuery_HeapHash: + { + HeapHash *out = va_arg(vl, HeapHash *); + if (out) { + if (m_tls_heap_central) { + m_tls_heap_central->CalculateHeapHash(out); + } else { + *out = {}; + } + } + return 0; + } + case AllocQuery_UnifyFreeList: + /* NOTE: Nintendo does not check that the ptr is not null for this query, even though they do for other queries. */ + m_tls_heap_central->IsClean(); + return 0; + case AllocQuery_SetColor: + { + /* NOTE: Nintendo does not check that the ptr is not null for this query, even though they do for other queries. */ + void *ptr = va_arg(vl, void *); + int color = va_arg(vl, int); + return m_tls_heap_central->SetColor(ptr, color); + } + case AllocQuery_GetColor: + { + /* NOTE: Nintendo does not check that the ptr is not null for this query, even though they do for other queries. */ + void *ptr = va_arg(vl, void *); + int *out = va_arg(vl, int *); + return m_tls_heap_central->GetColor(ptr, out); + } + case AllocQuery_SetName: + { + /* NOTE: Nintendo does not check that the ptr is not null for this query, even though they do for other queries. */ + void *ptr = va_arg(vl, void *); + const char *name = va_arg(vl, const char *); + return m_tls_heap_central->SetName(ptr, name); + } + case AllocQuery_GetName: + { + /* NOTE: Nintendo does not check that the ptr is not null for this query, even though they do for other queries. */ + void *ptr = va_arg(vl, void *); + char *dst = va_arg(vl, char *); + size_t dst_size = va_arg(vl, size_t); + return m_tls_heap_central->GetName(ptr, dst, dst_size); + } + case AllocQuery_FreeSizeMapped: + case AllocQuery_MaxAllocatableSizeMapped: + { + /* NOTE: Nintendo does not check that the ptr is not null for this query, even though they do for other queries. */ + size_t *out = va_arg(vl, size_t *); + size_t free_size; + size_t max_allocatable_size; + auto err = m_tls_heap_central->GetMappedMemStats(std::addressof(free_size), std::addressof(max_allocatable_size)); + if (err == 0) { + if (query == AllocQuery_FreeSizeMapped) { + *out = free_size; + } else { + *out = max_allocatable_size; + } + } + return err; + } + default: + return EINVAL; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_platform.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_platform.hpp new file mode 100644 index 00000000..dde43265 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_platform.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../mem_impl_platform.hpp" + +namespace ams::mem::impl::heap { + + using Prot = mem::impl::Prot; + + inline errno_t AllocateVirtualMemory(void **ptr, size_t size) { + return ::ams::mem::impl::virtual_alloc(ptr, size); + } + + inline errno_t FreeVirtualMemory(void *ptr, size_t size) { + return ::ams::mem::impl::virtual_free(ptr, size); + } + + inline errno_t AllocatePhysicalMemory(void *ptr, size_t size) { + return ::ams::mem::impl::physical_alloc(ptr, size, static_cast<Prot>(Prot_read | Prot_write)); + } + + inline errno_t FreePhysicalMemory(void *ptr, size_t size) { + return ::ams::mem::impl::physical_free(ptr, size); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_tls_heap_cache.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_tls_heap_cache.cpp new file mode 100644 index 00000000..d65c3ce2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_tls_heap_cache.cpp @@ -0,0 +1,557 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "mem_impl_heap_platform.hpp" +#include "mem_impl_heap_tls_heap_static.hpp" +#include "mem_impl_heap_tls_heap_cache.hpp" +#include "mem_impl_heap_tls_heap_central.hpp" + +namespace ams::mem::impl::heap { + + TlsHeapCache::TlsHeapCache(TlsHeapCentral *central, u32 option) { + /* Choose function impls based on option. */ + if ((option & HeapOption_DisableCache) != 0) { + m_allocate = AllocateImpl<false>; + m_allocate_aligned = AllocateAlignedImpl<false>; + m_free = FreeImpl<false>; + m_free_with_size = FreeWithSizeImpl<false>; + m_get_allocation_size = GetAllocationSizeImpl<false>; + m_reallocate = ReallocateImpl<false>; + m_shrink = ShrinkImpl<false>; + } else { + m_allocate = AllocateImpl<true>; + m_allocate_aligned = AllocateAlignedImpl<true>; + m_free = FreeImpl<true>; + m_free_with_size = FreeWithSizeImpl<true>; + m_get_allocation_size = GetAllocationSizeImpl<true>; + m_reallocate = ReallocateImpl<true>; + m_shrink = ShrinkImpl<true>; + } + + /* Generate random bytes to mangle pointers. */ + if (auto err = gen_random(std::addressof(m_mangle_val), sizeof(m_mangle_val)); err != 0) { + s64 epoch_time; + epochtime(std::addressof(epoch_time)); + m_mangle_val = reinterpret_cast<uintptr_t>(std::addressof(epoch_time)) ^ static_cast<u64>(epoch_time); + } + + /* Set member variables. */ + m_central = central; + m_total_heap_size = central->GetTotalHeapSize(); + m_heap_option = option; + m_total_cached_size = 0; + m_largest_class = 0; + + /* Setup chunks. */ + for (size_t i = 0; i < TlsHeapStatic::NumClassInfo; i++) { + m_small_mem_lists[i] = nullptr; + m_cached_size[i] = 0; + m_chunk_count[i] = 1; + } + + /* Set fixed chunk counts for particularly small chunks. */ + m_chunk_count[1] = MaxChunkCount; + m_chunk_count[2] = MaxChunkCount; + m_chunk_count[3] = MaxChunkCount; + m_chunk_count[4] = MaxChunkCount / 2; + m_chunk_count[5] = MaxChunkCount / 2; + m_chunk_count[6] = MaxChunkCount / 2; + m_chunk_count[7] = MaxChunkCount / 4; + m_chunk_count[8] = MaxChunkCount / 4; + m_chunk_count[9] = MaxChunkCount / 4; + } + + void TlsHeapCache::Finalize() { + /* Free all small mem lists. */ + this->ReleaseAllCache(); + + /* Remove this cache from the owner central heap. */ + m_central->RemoveThreadCache(this); + m_central->UncacheSmallMemory(this); + } + + bool TlsHeapCache::CheckCache() const { + for (size_t i = 0; i < util::size(m_small_mem_lists); i++) { + void *ptr = m_small_mem_lists[i]; + if (ptr) { + s64 depth = -static_cast<s64>(m_cached_size[i] / TlsHeapStatic::GetChunkSize(i)); + while (ptr) { + ptr = *reinterpret_cast<void **>(this->ManglePointer(ptr)); + if ((++depth) == 0) { + AMS_ASSERT(ptr == nullptr); + break; + } + } + } + } + + return true; + } + + void TlsHeapCache::ReleaseAllCache() { + for (size_t i = 0; i < util::size(m_small_mem_lists); i++) { + if (m_small_mem_lists[i]) { + m_central->UncacheSmallMemoryList(this, m_small_mem_lists[i]); + m_small_mem_lists[i] = nullptr; + m_cached_size[i] = 0; + } + } + + m_total_cached_size = 0; + m_largest_class = 0; + } + + template<> + void *TlsHeapCache::AllocateImpl<false>(TlsHeapCache *tls_heap_cache, size_t size) { + /* Validate allocation size. */ + if (size == 0 || size > MaxSize) { + return nullptr; + } + + if (const size_t cls = TlsHeapStatic::GetClassFromSize(size); cls != 0) { + AMS_ASSERT(static_cast<u32>(cls) < TlsHeapStatic::NumClassInfo); + return tls_heap_cache->m_central->CacheSmallMemory(cls); + } else { + /* If allocating a huge size, release our cache. */ + if (size >= tls_heap_cache->m_total_heap_size / 4) { + tls_heap_cache->ReleaseAllCache(); + } + return tls_heap_cache->m_central->CacheLargeMemory(size); + } + } + + template<> + void *TlsHeapCache::AllocateImpl<true>(TlsHeapCache *tls_heap_cache, size_t size) { + /* Validate allocation size. */ + if (size == 0 || size > MaxSize) { + return nullptr; + } + + if (size_t cls = TlsHeapStatic::GetClassFromSize(size); cls != 0) { + AMS_ASSERT(static_cast<u32>(cls) < TlsHeapStatic::NumClassInfo); + /* Allocate a chunk. */ + void *ptr = tls_heap_cache->m_small_mem_lists[cls]; + if (ptr == nullptr) { + const size_t prev_cls = cls; + size_t count = tls_heap_cache->m_chunk_count[cls]; + + size_t n = tls_heap_cache->m_central->CacheSmallMemoryList(tls_heap_cache, std::addressof(cls), count, std::addressof(ptr)); + if (n == 0) { + return nullptr; + } + + if (cls == prev_cls) { + if (count < MaxChunkCount) { + count++; + } + tls_heap_cache->m_chunk_count[cls] = std::max(count, n); + } else { + AMS_ASSERT(n == 1); + } + + const size_t csize = TlsHeapStatic::GetChunkSize(cls) * (n - 1); + tls_heap_cache->m_cached_size[cls] += csize; + if (tls_heap_cache->m_cached_size[cls] > tls_heap_cache->m_cached_size[tls_heap_cache->m_largest_class]) { + tls_heap_cache->m_largest_class = cls; + } + tls_heap_cache->m_total_cached_size += csize; + } + + /* Demangle our pointer, update free list. */ + ptr = tls_heap_cache->ManglePointer(ptr); + tls_heap_cache->m_small_mem_lists[cls] = *reinterpret_cast<void **>(ptr); + + return ptr; + } else { + /* If allocating a huge size, release our cache. */ + if (size >= tls_heap_cache->m_total_heap_size / 4) { + tls_heap_cache->ReleaseAllCache(); + } + return tls_heap_cache->m_central->CacheLargeMemory(size); + } + } + + template<> + void *TlsHeapCache::AllocateAlignedImpl<false>(TlsHeapCache *tls_heap_cache, size_t size, size_t align) { + /* Ensure valid alignment. */ + if (!util::IsPowerOfTwo(align)) { + return nullptr; + } + + /* NOTE: Nintendo does not check size == 0 here, despite doing so in Alloc */ + if (size > MaxSize) { + return nullptr; + } + + /* Handle big alignment. */ + if (align > TlsHeapStatic::PageSize) { + return tls_heap_cache->m_central->CacheLargeMemoryWithBigAlign(util::AlignUp(size, TlsHeapStatic::PageSize), align); + } + + const size_t real_size = TlsHeapStatic::GetRealSizeFromSizeAndAlignment(util::AlignUp(size, align), align); + + if (const size_t cls = TlsHeapStatic::GetClassFromSize(real_size); cls != 0) { + if (real_size == 0) { + return nullptr; + } + + AMS_ASSERT(static_cast<u32>(cls) < TlsHeapStatic::NumClassInfo); + return tls_heap_cache->m_central->CacheSmallMemory(cls, align); + } else { + /* If allocating a huge size, release our cache. */ + if (real_size >= tls_heap_cache->m_total_heap_size / 4) { + tls_heap_cache->ReleaseAllCache(); + } + return tls_heap_cache->m_central->CacheLargeMemory(real_size); + } + } + + template<> + void *TlsHeapCache::AllocateAlignedImpl<true>(TlsHeapCache *tls_heap_cache, size_t size, size_t align) { + /* Ensure valid alignment. */ + if (!util::IsPowerOfTwo(align)) { + return nullptr; + } + + /* NOTE: Nintendo does not check size == 0 here, despite doing so in Alloc */ + if (size > MaxSize) { + return nullptr; + } + + /* Handle big alignment. */ + if (align > TlsHeapStatic::PageSize) { + return tls_heap_cache->m_central->CacheLargeMemoryWithBigAlign(util::AlignUp(size, TlsHeapStatic::PageSize), align); + } + + const size_t real_size = TlsHeapStatic::GetRealSizeFromSizeAndAlignment(util::AlignUp(size, align), align); + + if (size_t cls = TlsHeapStatic::GetClassFromSize(real_size); cls != 0) { + if (real_size == 0) { + return nullptr; + } + + AMS_ASSERT(static_cast<u32>(cls) < TlsHeapStatic::NumClassInfo); + + /* Allocate a chunk. */ + void *ptr = tls_heap_cache->m_small_mem_lists[cls]; + if (ptr == nullptr) { + const size_t prev_cls = cls; + size_t count = tls_heap_cache->m_chunk_count[cls]; + + size_t n = tls_heap_cache->m_central->CacheSmallMemoryList(tls_heap_cache, std::addressof(cls), count, std::addressof(ptr), align); + if (n == 0) { + return nullptr; + } + + if (cls == prev_cls) { + if (count < MaxChunkCount) { + count++; + } + tls_heap_cache->m_chunk_count[cls] = std::max(count, n); + } else { + AMS_ASSERT(n == 1); + } + + const s32 csize = TlsHeapStatic::GetChunkSize(cls) * (n - 1); + tls_heap_cache->m_total_cached_size += csize; + tls_heap_cache->m_cached_size[cls] += csize; + if (tls_heap_cache->m_cached_size[cls] > tls_heap_cache->m_cached_size[tls_heap_cache->m_largest_class]) { + tls_heap_cache->m_largest_class = cls; + } + } + + /* Demangle our pointer, update free list. */ + ptr = tls_heap_cache->ManglePointer(ptr); + tls_heap_cache->m_small_mem_lists[cls] = *reinterpret_cast<void **>(ptr); + + return ptr; + } else { + /* If allocating a huge size, release our cache. */ + if (size >= tls_heap_cache->m_total_heap_size / 4) { + tls_heap_cache->ReleaseAllCache(); + } + return tls_heap_cache->m_central->CacheLargeMemory(size); + } + } + + template<> + errno_t TlsHeapCache::FreeImpl<false>(TlsHeapCache *tls_heap_cache, void *ptr) { + const auto cls = tls_heap_cache->m_central->GetClassFromPointer(ptr); + if (cls == 0) { + return tls_heap_cache->m_central->UncacheLargeMemory(ptr); + } + + AMS_ASSERT(static_cast<u32>(cls) < TlsHeapStatic::NumClassInfo); + + if (cls >= 0) { + return tls_heap_cache->m_central->UncacheSmallMemory(ptr); + } else if (ptr == nullptr) { + return 0; + } else { + return EFAULT; + } + } + + template<> + errno_t TlsHeapCache::FreeImpl<true>(TlsHeapCache *tls_heap_cache, void *ptr) { + const auto cls = tls_heap_cache->m_central->GetClassFromPointer(ptr); + if (cls == 0) { + return tls_heap_cache->m_central->UncacheLargeMemory(ptr); + } + + AMS_ASSERT(static_cast<u32>(cls) < TlsHeapStatic::NumClassInfo); + + if (cls >= 0) { + *reinterpret_cast<void **>(ptr) = tls_heap_cache->m_small_mem_lists[cls]; + tls_heap_cache->m_small_mem_lists[cls] = tls_heap_cache->ManglePointer(ptr); + + const s32 csize = TlsHeapStatic::GetChunkSize(cls); + tls_heap_cache->m_total_cached_size += csize; + tls_heap_cache->m_cached_size[cls] += csize; + if (tls_heap_cache->m_cached_size[cls] > tls_heap_cache->m_cached_size[tls_heap_cache->m_largest_class]) { + tls_heap_cache->m_largest_class = cls; + } + + errno_t err = 0; + if (!tls_heap_cache->m_central->CheckCachedSize(tls_heap_cache->m_total_cached_size)) { + tls_heap_cache->m_central->UncacheSmallMemoryList(tls_heap_cache, tls_heap_cache->m_small_mem_lists[tls_heap_cache->m_largest_class]); + tls_heap_cache->m_small_mem_lists[tls_heap_cache->m_largest_class] = nullptr; + tls_heap_cache->m_total_cached_size -= tls_heap_cache->m_cached_size[tls_heap_cache->m_largest_class]; + tls_heap_cache->m_cached_size[tls_heap_cache->m_largest_class] = 0; + + s32 largest_class = 0; + s32 biggest_size = -1; + for (size_t i = 0; i < TlsHeapStatic::NumClassInfo; i++) { + if (biggest_size < tls_heap_cache->m_cached_size[i]) { + biggest_size = tls_heap_cache->m_cached_size[i]; + largest_class = static_cast<s32>(i); + } + } + tls_heap_cache->m_largest_class = largest_class; + } + return err; + } else if (ptr == nullptr) { + return 0; + } else { + return EFAULT; + } + } + + template<> + errno_t TlsHeapCache::FreeWithSizeImpl<false>(TlsHeapCache *tls_heap_cache, void *ptr, size_t size) { + if (ptr == nullptr) { + return 0; + } + + const size_t cls = TlsHeapStatic::GetClassFromSize(size); + if (cls == 0) { + return tls_heap_cache->m_central->UncacheLargeMemory(ptr); + } else { + return tls_heap_cache->m_central->UncacheSmallMemory(ptr); + } + } + + template<> + errno_t TlsHeapCache::FreeWithSizeImpl<true>(TlsHeapCache *tls_heap_cache, void *ptr, size_t size) { + if (ptr == nullptr) { + return 0; + } + + const size_t cls = TlsHeapStatic::GetClassFromSize(size); + if (cls == 0) { + return tls_heap_cache->m_central->UncacheLargeMemory(ptr); + } else { + *reinterpret_cast<void **>(ptr) = tls_heap_cache->m_small_mem_lists[cls]; + tls_heap_cache->m_small_mem_lists[cls] = tls_heap_cache->ManglePointer(ptr); + + const s32 csize = TlsHeapStatic::GetChunkSize(cls); + tls_heap_cache->m_total_cached_size += csize; + tls_heap_cache->m_cached_size[cls] += csize; + if (tls_heap_cache->m_cached_size[cls] > tls_heap_cache->m_cached_size[tls_heap_cache->m_largest_class]) { + tls_heap_cache->m_largest_class = cls; + } + + errno_t err = 0; + if (!tls_heap_cache->m_central->CheckCachedSize(tls_heap_cache->m_total_cached_size)) { + tls_heap_cache->m_central->UncacheSmallMemoryList(tls_heap_cache, tls_heap_cache->m_small_mem_lists[tls_heap_cache->m_largest_class]); + tls_heap_cache->m_small_mem_lists[tls_heap_cache->m_largest_class] = nullptr; + tls_heap_cache->m_total_cached_size -= tls_heap_cache->m_cached_size[tls_heap_cache->m_largest_class]; + tls_heap_cache->m_cached_size[tls_heap_cache->m_largest_class] = 0; + + s32 largest_class = 0; + s32 biggest_size = -1; + for (size_t i = 0; i < TlsHeapStatic::NumClassInfo; i++) { + if (biggest_size < tls_heap_cache->m_cached_size[i]) { + biggest_size = tls_heap_cache->m_cached_size[i]; + largest_class = static_cast<s32>(i); + } + } + tls_heap_cache->m_largest_class = largest_class; + } + return err; + } + } + + template<> + size_t TlsHeapCache::GetAllocationSizeImpl<false>(TlsHeapCache *tls_heap_cache, const void *ptr) { + return tls_heap_cache->GetAllocationSizeCommonImpl(ptr); + } + + template<> + size_t TlsHeapCache::GetAllocationSizeImpl<true>(TlsHeapCache *tls_heap_cache, const void *ptr) { + return tls_heap_cache->GetAllocationSizeCommonImpl(ptr); + } + + size_t TlsHeapCache::GetAllocationSizeCommonImpl(const void *ptr) const { + const s32 cls = m_central->GetClassFromPointer(ptr); + if (cls > 0) { + if (!util::IsAligned(ptr, alignof(u64))) { + /* All pointers we allocate have alignment at least 8. */ + return 0; + } + + /* Validate class. */ + AMS_ASSERT(cls < static_cast<s32>(TlsHeapStatic::NumClassInfo)); + if (cls < 0) { + return 0; + } + + return TlsHeapStatic::GetChunkSize(cls); + } else if (ptr != nullptr) { + return m_central->GetAllocationSize(ptr); + } else { + return 0; + } + } + + template<> + errno_t TlsHeapCache::ReallocateImpl<false>(TlsHeapCache *tls_heap_cache, void *ptr, size_t size, void **p) { + AMS_ASSERT(ptr != nullptr && size != 0); + if (size > MaxSize) { + return ENOMEM; + } + + size_t alloc_size, copy_size; + + const s32 cls_from_size = TlsHeapStatic::GetClassFromSize(size); + const s32 cls_from_ptr = tls_heap_cache->m_central->GetClassFromPointer(ptr); + if (cls_from_ptr < 0) { + /* error case. */ + return EFAULT; + } else if (cls_from_size) { + if (cls_from_ptr > 0) { + if (cls_from_size <= cls_from_ptr) { + *p = ptr; + return 0; + } else { + alloc_size = size; + copy_size = TlsHeapStatic::GetChunkSize(cls_from_ptr); + } + } else /* if (cls_from_ptr == 0) */ { + alloc_size = size; + copy_size = size; + } + } else if (cls_from_ptr == 0) { + return tls_heap_cache->m_central->ReallocateLargeMemory(ptr, size, p); + } else /* if (cls_from_ptr > 0) */ { + alloc_size = size; + copy_size = TlsHeapStatic::GetChunkSize(cls_from_ptr); + } + + *p = AllocateImpl<false>(tls_heap_cache, alloc_size); + if (*p == nullptr) { + return ENOMEM; + } + std::memcpy(*p, ptr, copy_size); + return FreeImpl<false>(tls_heap_cache, ptr); + } + + template<> + errno_t TlsHeapCache::ReallocateImpl<true>(TlsHeapCache *tls_heap_cache, void *ptr, size_t size, void **p) { + AMS_ASSERT(ptr != nullptr && size != 0); + if (size > MaxSize) { + return ENOMEM; + } + + size_t alloc_size, copy_size; + + const s32 cls_from_size = TlsHeapStatic::GetClassFromSize(size); + const s32 cls_from_ptr = tls_heap_cache->m_central->GetClassFromPointer(ptr); + if (cls_from_ptr < 0) { + /* error case. */ + return EFAULT; + } else if (cls_from_size) { + if (cls_from_ptr > 0) { + if (cls_from_size <= cls_from_ptr) { + *p = ptr; + return 0; + } else { + alloc_size = size; + copy_size = TlsHeapStatic::GetChunkSize(cls_from_ptr); + } + } else /* if (cls_from_ptr == 0) */ { + alloc_size = size; + copy_size = size; + } + } else if (cls_from_ptr == 0) { + return tls_heap_cache->m_central->ReallocateLargeMemory(ptr, size, p); + } else /* if (cls_from_ptr > 0) */ { + alloc_size = size; + copy_size = TlsHeapStatic::GetChunkSize(cls_from_ptr); + } + + *p = AllocateImpl<true>(tls_heap_cache, alloc_size); + if (*p == nullptr) { + return ENOMEM; + } + std::memcpy(*p, ptr, copy_size); + return FreeImpl<true>(tls_heap_cache, ptr); + } + + template<> + errno_t TlsHeapCache::ShrinkImpl<false>(TlsHeapCache *tls_heap_cache, void *ptr, size_t size) { + return tls_heap_cache->ShrinkCommonImpl(ptr, size); + } + + template<> + errno_t TlsHeapCache::ShrinkImpl<true>(TlsHeapCache *tls_heap_cache, void *ptr, size_t size) { + return tls_heap_cache->ShrinkCommonImpl(ptr, size); + } + + errno_t TlsHeapCache::ShrinkCommonImpl(void *ptr, size_t size) const { + AMS_ASSERT(ptr != nullptr && size != 0); + if (size > MaxSize) { + return ENOMEM; + } + + const s32 cls_from_size = TlsHeapStatic::GetClassFromSize(size); + const s32 cls_from_ptr = m_central->GetClassFromPointer(ptr); + if (cls_from_ptr) { + if (cls_from_ptr <= 0) { + return EFAULT; + } else if (cls_from_size && cls_from_size <= cls_from_ptr) { + return 0; + } else { + return EINVAL; + } + } else if (cls_from_size) { + return m_central->ShrinkLargeMemory(ptr, TlsHeapStatic::PageSize); + } else { + return m_central->ShrinkLargeMemory(ptr, size); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_tls_heap_cache.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_tls_heap_cache.hpp new file mode 100644 index 00000000..31d4f8e6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_tls_heap_cache.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "mem_impl_heap_platform.hpp" +#include "mem_impl_heap_tls_heap_static.hpp" + +namespace ams::mem::impl::heap { + + class TlsHeapCentral; + + #define FOREACH_TLS_HEAP_CACHE_FUNC(HANDLER) \ + HANDLER(void *, Allocate, m_allocate, size_t size); \ + HANDLER(void *, AllocateAligned, m_allocate_aligned, size_t size, size_t align); \ + HANDLER(errno_t, Free, m_free, void *ptr); \ + HANDLER(errno_t, FreeWithSize, m_free_with_size, void *ptr, size_t size); \ + HANDLER(size_t, GetAllocationSize, m_get_allocation_size, const void *ptr); \ + HANDLER(errno_t, Reallocate, m_reallocate, void *ptr, size_t size, void **p); \ + HANDLER(errno_t, Shrink, m_shrink, void *ptr, size_t size); + + class TlsHeapCache { + public: + static constexpr size_t MaxChunkCount = BITSIZEOF(u64); + public: + #define TLS_HEAP_CACHE_DECLARE_TYPEDEF(RETURN, NAME, MEMBER_NAME, ...) \ + using NAME##Func = RETURN (*)(TlsHeapCache *, ## __VA_ARGS__) + + FOREACH_TLS_HEAP_CACHE_FUNC(TLS_HEAP_CACHE_DECLARE_TYPEDEF) + + #undef TLS_HEAP_CACHE_DECLARE_TYPEDEF + private: + #define TLS_HEAP_CACHE_DECLARE_MEMBER(RETURN, NAME, MEMBER_NAME, ...) \ + NAME##Func MEMBER_NAME; + + FOREACH_TLS_HEAP_CACHE_FUNC(TLS_HEAP_CACHE_DECLARE_MEMBER) + + #undef TLS_HEAP_CACHE_DECLARE_MEMBER + + uintptr_t m_mangle_val; + TlsHeapCentral *m_central; + size_t m_total_heap_size; + u32 m_heap_option; + s32 m_total_cached_size; + s32 m_largest_class; + void *m_small_mem_lists[TlsHeapStatic::NumClassInfo]; + s32 m_cached_size[TlsHeapStatic::NumClassInfo]; + u8 m_chunk_count[TlsHeapStatic::NumClassInfo]; + public: + TlsHeapCache(TlsHeapCentral *central, u32 option); + void Finalize(); + + void *ManglePointer(void *ptr) const { + return reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(ptr) ^ m_mangle_val); + } + + bool CheckCache() const; + void ReleaseAllCache(); + + public: + /* TODO: Better handler with type info to macro this? */ + ALWAYS_INLINE void *Allocate(size_t size) { return m_allocate(this, size); } + ALWAYS_INLINE void *Allocate(size_t size, size_t align) { return m_allocate_aligned(this, size, align); } + ALWAYS_INLINE errno_t Free(void *ptr) { return m_free(this, ptr); } + ALWAYS_INLINE errno_t FreeWithSize(void *ptr, size_t size) { return m_free_with_size(this, ptr, size); } + ALWAYS_INLINE size_t GetAllocationSize(const void *ptr) { return m_get_allocation_size(this, ptr); } + ALWAYS_INLINE errno_t Reallocate(void *ptr, size_t size, void **p) { return m_reallocate(this, ptr, size, p); } + ALWAYS_INLINE errno_t Shrink(void *ptr, size_t size) { return m_shrink(this, ptr, size); } + private: + #define TLS_HEAP_CACHE_DECLARE_TEMPLATE(RETURN, NAME, MEMBER_NAME, ...) \ + template<bool Cache> static RETURN NAME##Impl(TlsHeapCache *tls_heap_cache, ## __VA_ARGS__ ) + + FOREACH_TLS_HEAP_CACHE_FUNC(TLS_HEAP_CACHE_DECLARE_TEMPLATE) + + #undef TLS_HEAP_CACHE_DECLARE_TEMPLATE + + size_t GetAllocationSizeCommonImpl(const void *ptr) const; + errno_t ShrinkCommonImpl(void *ptr, size_t size) const; + }; + + #define TLS_HEAP_CACHE_DECLARE_INSTANTIATION(RETURN, NAME, MEMBER_NAME, ...) \ + template<> RETURN TlsHeapCache::NAME##Impl<false>(TlsHeapCache *tls_heap_cache, ##__VA_ARGS__); \ + template<> RETURN TlsHeapCache::NAME##Impl<true>(TlsHeapCache *tls_heap_cache, ##__VA_ARGS__) + + FOREACH_TLS_HEAP_CACHE_FUNC(TLS_HEAP_CACHE_DECLARE_INSTANTIATION) + + #undef FOREACH_TLS_HEAP_CACHE_FUNC + + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_tls_heap_central.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_tls_heap_central.cpp new file mode 100644 index 00000000..21680821 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_tls_heap_central.cpp @@ -0,0 +1,1504 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "mem_impl_heap_platform.hpp" +#include "mem_impl_heap_tls_heap_static.hpp" +#include "mem_impl_heap_tls_heap_central.hpp" + +namespace ams::mem::impl::heap { + + namespace { + + void InitializeSpanPage(SpanPage *sp) { + static_assert(SpanPage::MaxSpanCount <= BITSIZEOF(u64)); + constexpr size_t NumUnusedBits = BITSIZEOF(u64) - SpanPage::MaxSpanCount; + + sp->info.free_count = SpanPage::MaxSpanCount; + sp->info.is_sticky = 0; + sp->info.alloc_bitmap = (static_cast<u64>(1) << NumUnusedBits) - 1; + + ListClearLink(sp); + + sp->info.span_of_spanpage.start.u = 0; + sp->info.span_of_spanpage.num_pages = 0; + sp->info.span_of_spanpage.aux.small.objects = nullptr; + sp->info.span_of_spanpage.object_count = 0; + sp->info.span_of_spanpage.page_class = 0; + sp->info.span_of_spanpage.status = Span::Status_NotUsed; + sp->info.span_of_spanpage.id = 0; + + ListClearLink(std::addressof(sp->info.span_of_spanpage)); + } + + void RegisterSpan(SpanTable *span_table, Span *span) { + const size_t idx = TlsHeapStatic::GetPageIndex(span->start.u - reinterpret_cast<uintptr_t>(span_table)); + span->page_class = 0; + span_table->page_to_span[idx] = span; + span_table->page_to_span[idx + span->num_pages - 1] = span; + } + + void UnregisterSpan(SpanTable *span_table, Span *span) { + AMS_ASSERT(span->page_class == 0); + const size_t idx = TlsHeapStatic::GetPageIndex(span->start.u - reinterpret_cast<uintptr_t>(span_table)); + span_table->page_to_span[idx] = nullptr; + span_table->page_to_span[idx + span->num_pages - 1] = nullptr; + } + + void ChangeRangeOfSpan(SpanTable *span_table, Span *span, uintptr_t start, size_t new_pages) { + const size_t idx = TlsHeapStatic::GetPageIndex(span->start.u - reinterpret_cast<uintptr_t>(span_table)); + if (span->start.u == start) { + if (span->num_pages != 1) { + span_table->page_to_span[idx + span->num_pages - 1] = nullptr; + } + span_table->page_to_span[idx + new_pages - 1] = span; + span->num_pages = new_pages; + } else { + span_table->page_to_span[idx] = nullptr; + span_table->page_to_span[idx + span->num_pages - 1] = nullptr; + + const size_t new_idx = TlsHeapStatic::GetPageIndex(start - reinterpret_cast<uintptr_t>(span_table)); + span_table->page_to_span[new_idx] = span; + span_table->page_to_span[new_idx + new_pages - 1] = span; + + span->start.u = start; + span->num_pages = new_pages; + } + } + + void MigrateSpan(SpanTable *span_table, Span *from, Span *to) { + AMS_ASSERT(from != to); + + std::memcpy(to, from, sizeof(*from)); + + from->status = Span::Status_NotUsed; + + if (from->list_next) { + to->list_next = from->list_next; + from->list_next->list_prev = to; + from->list_next = nullptr; + } else { + to->list_next = nullptr; + } + + if (from->list_prev) { + to->list_prev = from->list_prev; + from->list_prev->list_next = to; + from->list_prev = nullptr; + } else { + to->list_prev = nullptr; + } + + const size_t idx = TlsHeapStatic::GetPageIndex(to->start.u - reinterpret_cast<uintptr_t>(span_table)); + if (from->page_class) { + for (size_t i = 0; i < to->num_pages; i++) { + span_table->page_to_span[idx + i] = to; + } + } else { + span_table->page_to_span[idx] = to; + span_table->page_to_span[idx + to->num_pages - 1] = to; + } + } + + bool IsNthSmallMemoryMarked(const Span *span, size_t n) { + return (span->aux.small.is_allocated[n / BITSIZEOF(u64)] & (1ull << (n % BITSIZEOF(u64)))) != 0; + } + + void MarkNthSmallMemory(Span *span, size_t n) { + span->aux.small.is_allocated[n / BITSIZEOF(u64)] |= (1ull << (n % BITSIZEOF(u64))); + } + + void UnmarkNthSmallMemory(Span *span, size_t n) { + span->aux.small.is_allocated[n / BITSIZEOF(u64)] &= ~(1ull << (n % BITSIZEOF(u64))); + } + + void *AllocateSmallMemory(Span *span) { + Span::SmallMemory *sm = span->aux.small.objects; + const size_t chunk_size = TlsHeapStatic::GetChunkSize(span->page_class); + const uintptr_t span_end = span->start.u + (span->num_pages * TlsHeapStatic::PageSize); + if (span->start.u <= reinterpret_cast<uintptr_t>(sm) && reinterpret_cast<uintptr_t>(sm) < span_end) { + const size_t idx = (reinterpret_cast<uintptr_t>(sm) - span->start.u) / chunk_size; + if (reinterpret_cast<uintptr_t>(sm) == (span->start.u + idx * chunk_size) && !IsNthSmallMemoryMarked(span, idx)) { + MarkNthSmallMemory(span, idx); + span->aux.small.objects = sm->next; + sm->next = nullptr; + span->object_count++; + return sm; + } + } else if (sm == nullptr) { + return nullptr; + } + + /* Data corruption error. */ + AMS_ASSERT(false); + span->aux.small.objects = nullptr; + return nullptr; + } + + struct MangledSmallMemory { + Span::SmallMemory *from; + Span::SmallMemory *to; + }; + + size_t AllocateSmallMemory(Span *span, TlsHeapCache *cache, size_t n, MangledSmallMemory *memlist) { + auto ManglePointer = [cache](void *ptr) ALWAYS_INLINE_LAMBDA { + return static_cast<Span::SmallMemory *>(cache->ManglePointer(ptr)); + }; + + Span::SmallMemory *sm = span->aux.small.objects; + if (sm) { + size_t count = 0; + memlist->from = ManglePointer(sm); + + const size_t chunk_size = TlsHeapStatic::GetChunkSize(span->page_class); + const uintptr_t span_end = span->start.u + (span->num_pages * TlsHeapStatic::PageSize); + while (span->start.u <= reinterpret_cast<uintptr_t>(sm) && reinterpret_cast<uintptr_t>(sm) < span_end) { + const size_t idx = (reinterpret_cast<uintptr_t>(sm) - span->start.u) / chunk_size; + if (span->start.u + idx * chunk_size != reinterpret_cast<uintptr_t>(sm)) { + break; + } + if (IsNthSmallMemoryMarked(span, idx)) { + break; + } + MarkNthSmallMemory(span, idx); + count++; + + Span::SmallMemory *next = sm->next; + sm->next = ManglePointer(next); + if (count >= n || next == nullptr) { + memlist->to = sm; + memlist->to->next = nullptr; + span->aux.small.objects = next; + span->object_count += count; + return count; + } + + sm = next; + } + + /* Data corruption error. */ + Span::SmallMemory *prev = span->aux.small.objects; + Span::SmallMemory *cur = span->aux.small.objects; + while (cur != sm) { + prev = cur; + cur = ManglePointer(cur->next); + } + + memlist->to = ManglePointer(prev); + memlist->to->next = nullptr; + span->aux.small.objects = nullptr; + span->object_count += count; + return count; + } else { + memlist->from = nullptr; + memlist->to = nullptr; + return 0; + } + } + + void ReleaseSmallMemory(Span *span, void *ptr) { + AMS_ASSERT(span->object_count > 0); + + const size_t span_ofs = reinterpret_cast<uintptr_t>(ptr) - span->start.u; + const size_t chunk_size = TlsHeapStatic::GetChunkSize(span->page_class); + const size_t span_idx = span_ofs / chunk_size; + if (span_ofs != (span_idx * chunk_size)) { + /* Invalid pointer. Do the best we can. */ + ptr = reinterpret_cast<void *>(span->start.u + span_idx * chunk_size); + } + if (IsNthSmallMemoryMarked(span, span_idx)) { + UnmarkNthSmallMemory(span, span_idx); + + Span::SmallMemory *sm = reinterpret_cast<Span::SmallMemory *>(ptr); + sm->next = span->aux.small.objects; + span->aux.small.objects = sm; + span->object_count--; + } else { + /* Double free error. */ + /* TODO: Anything? */ + } + } + + void SpanToSmallMemorySpan(SpanTable *span_table, Span *span, size_t cls) { + AMS_ASSERT(cls != 0 && span->page_class == 0); + + const size_t span_idx = TlsHeapStatic::GetPageIndex(span->start.u - reinterpret_cast<uintptr_t>(span_table)); + Span **table_entry = std::addressof(span_table->page_to_span[span_idx]); + + span->page_class = cls; + for (size_t i = 0; i < span->num_pages; i++) { + table_entry[i] = span; + } + + std::atomic_thread_fence(std::memory_order_release); + + std::memset(std::addressof(span_table->pageclass_cache[span_idx]), cls, span->num_pages); + } + + void SmallMemorySpanToSpan(SpanTable *span_table, Span *span) { + AMS_ASSERT(span->page_class != 0); + + const size_t span_idx = TlsHeapStatic::GetPageIndex(span->start.u - reinterpret_cast<uintptr_t>(span_table)); + Span **table_entry = std::addressof(span_table->page_to_span[span_idx]); + + for (size_t i = 0; i < span->num_pages; i++) { + table_entry[i] = nullptr; + } + span->page_class = 0; + + table_entry[0] = span; + table_entry[span->num_pages - 1] = span; + + std::atomic_thread_fence(std::memory_order_release); + + std::memset(std::addressof(span_table->pageclass_cache[span_idx]), 0, span->num_pages); + } + + void InitSmallMemorySpan(Span *span, size_t cls, bool for_system, int id) { + AMS_ASSERT(cls != 0); + span->page_class = cls; + + AMS_ASSERT(span->status == Span::Status_InUse); + if (for_system) { + span->status = Span::Status_InUseSystem; + } + + span->aux.small.objects = span->start.sm; + span->object_count = 0; + span->id = id; + + const size_t chunk_size = TlsHeapStatic::GetChunkSize(cls); + const size_t num_chunks = (span->num_pages * TlsHeapStatic::PageSize) / chunk_size; + AMS_ASSERT(num_chunks <= sizeof(span->aux.small.is_allocated) * BITSIZEOF(u8)); + + Span::SmallMemory *last = reinterpret_cast<Span::SmallMemory *>(span->start.u + num_chunks * chunk_size); + Span::SmallMemory *prev = reinterpret_cast<Span::SmallMemory *>(span->start.u); + for (Span::SmallMemory *cur = reinterpret_cast<Span::SmallMemory *>(span->start.u + chunk_size); cur != last; cur = reinterpret_cast<Span::SmallMemory *>(reinterpret_cast<uintptr_t>(cur) + chunk_size)) { + prev->next = cur; + prev = cur; + } + prev->next = nullptr; + std::memset(span->aux.small.is_allocated, 0, sizeof(span->aux.small.is_allocated)); + } + + } + + errno_t TlsHeapCentral::Initialize(void *start, size_t size, bool use_virtual_memory) { + AMS_ASSERT(size > 0); + AMS_ASSERT(TlsHeapStatic::IsPageAligned(start)); + AMS_ASSERT(TlsHeapStatic::IsPageAligned(size)); + + /* Clear lists. */ + ListClearLink(std::addressof(m_spanpage_list)); + ListClearLink(std::addressof(m_full_spanpage_list)); + for (size_t i = 0; i < util::size(m_freelists); i++) { + ListClearLink(std::addressof(m_freelists[i])); + } + for (size_t i = 0; i < util::size(m_freelists_bitmap); i++) { + m_freelists_bitmap[i] = 0; + } + for (size_t i = 0; i < util::size(m_smallmem_lists); i++) { + ListClearLink(std::addressof(m_smallmem_lists[i])); + } + + /* Setup span table. */ + const size_t total_pages = TlsHeapStatic::GetPageIndex(size); + const size_t n = total_pages * sizeof(Span *); + m_span_table.total_pages = total_pages; + m_span_table.page_to_span = reinterpret_cast<Span **>(static_cast<u8 *>(start) + sizeof(*this)); + m_span_table.pageclass_cache = static_cast<u8 *>(start) + sizeof(*this) + n; + + u8 *meta_end = m_span_table.pageclass_cache + total_pages; + size_t num_physical_page_flags; + if (use_virtual_memory) { + m_physical_page_flags = meta_end; + const uintptr_t phys_start = TlsHeapStatic::AlignDownPhysicalPage(reinterpret_cast<uintptr_t>(start)); + const uintptr_t phys_end = TlsHeapStatic::AlignUpPhysicalPage(reinterpret_cast<uintptr_t>(start) + size); + num_physical_page_flags = TlsHeapStatic::GetPhysicalPageIndex(phys_end - phys_start); + meta_end = TlsHeapStatic::AlignUpPage(meta_end + num_physical_page_flags); + } else { + m_physical_page_flags = nullptr; + num_physical_page_flags = 0; + meta_end = TlsHeapStatic::AlignUpPage(meta_end); + } + AMS_ASSERT(TlsHeapStatic::IsPageAligned(meta_end)); + + if (use_virtual_memory) { + const uintptr_t phys_end = TlsHeapStatic::AlignUpPhysicalPage(reinterpret_cast<uintptr_t>(meta_end) + TlsHeapStatic::PageSize); + size_t phys_size = phys_end - reinterpret_cast<uintptr_t>(start); + phys_size = std::min(phys_size, size); + if (auto err = AllocatePhysicalMemory(start, phys_size); err != 0) { + m_span_table.total_pages = 0; + return err; + } + std::memset(m_physical_page_flags, 0, num_physical_page_flags); + std::memset(m_physical_page_flags, 1, TlsHeapStatic::GetPhysicalPageIndex(phys_end) - TlsHeapStatic::GetPhysicalPageIndex(reinterpret_cast<uintptr_t>(start))); + } + + std::memset(m_span_table.page_to_span, 0, n); + std::memset(m_span_table.pageclass_cache, 0, total_pages); + + SpanPage *span_page = reinterpret_cast<SpanPage *>(meta_end); + InitializeSpanPage(span_page); + ListInsertAfter(std::addressof(m_spanpage_list), span_page); + + meta_end += TlsHeapStatic::PageSize; + AMS_ASSERT(TlsHeapStatic::IsPageAligned(meta_end)); + + /* Setup the spans. */ + Span *span = this->AllocateSpanFromSpanPage(span_page); + AMS_ASSERT(span != nullptr); + + Span *span_admin = this->AllocateSpanFromSpanPage(span_page); + AMS_ASSERT(span_admin != nullptr); + + span_page->info.is_sticky = 1; + + span->start.u = reinterpret_cast<uintptr_t>(meta_end); + span->num_pages = TlsHeapStatic::GetPageIndex(reinterpret_cast<uintptr_t>(start) + size - reinterpret_cast<uintptr_t>(meta_end)); + span->aux.small.objects = nullptr; + span->object_count = 0; + span->status = Span::Status_InFreeList; + span->id = 0; + + span_page->info.span_of_spanpage.start.u = reinterpret_cast<uintptr_t>(span_page); + span_page->info.span_of_spanpage.num_pages = 1; + span_page->info.span_of_spanpage.aux.small.objects = nullptr; + span_page->info.span_of_spanpage.object_count = 0; + span_page->info.span_of_spanpage.status = Span::Status_InUseSystem; + span_page->info.span_of_spanpage.id = 0; + + span_admin->start.u = reinterpret_cast<uintptr_t>(start); + span_admin->num_pages = TlsHeapStatic::GetPageIndex(reinterpret_cast<uintptr_t>(span_page) - reinterpret_cast<uintptr_t>(start)); + span_admin->aux.small.objects = nullptr; + span_admin->object_count = 0; + span_admin->status = Span::Status_InUseSystem; + span_admin->id = 0; + + RegisterSpan(std::addressof(m_span_table), span_admin); + RegisterSpan(std::addressof(m_span_table), std::addressof(span_page->info.span_of_spanpage)); + RegisterSpan(std::addressof(m_span_table), span); + + this->AddToFreeBlockList(span); + + m_num_threads = 1; + m_static_thread_quota = std::min<size_t>((m_span_table.total_pages * TlsHeapStatic::PageSize) / sizeof(void *), 2_MB); + m_dynamic_thread_quota = m_static_thread_quota; + m_use_virtual_memory = use_virtual_memory; + + return 0; + } + + bool TlsHeapCentral::IsClean() { + std::scoped_lock lk(m_lock); + + this->MakeFreeSpan(std::numeric_limits<size_t>::max()); + + Span *span = this->GetFirstSpan(); + Span *next = GetNextSpan(std::addressof(m_span_table), span); + if (next && next->status == Span::Status_InFreeList && GetNextSpan(std::addressof(m_span_table), next) == nullptr) { + return true; + } else { + return false; + } + } + + errno_t TlsHeapCentral::ReallocateLargeMemory(void *ptr, size_t size, void **p) { + if (!TlsHeapStatic::IsPageAligned(ptr)) { + return EFAULT; + } + + AMS_ASSERT(size <= MaxSize); + + /* NOTE: This function uses locks unsafely (unscoped) */ + + m_lock.Lock(); + + Span *ptr_span = GetSpanFromPointer(std::addressof(m_span_table), ptr); + if (!ptr_span) { + AMS_ASSERT(ptr_span != nullptr); + m_lock.Unlock(); + return EFAULT; + } + + const size_t num_pages = TlsHeapStatic::GetPageIndex(size + TlsHeapStatic::PageSize - 1); + if (ptr_span->num_pages != num_pages) { + if (ptr_span->num_pages >= num_pages) { + Span *span = this->AllocateSpanStruct(); + if (span != nullptr) { + span->start.u = ptr_span->start.u + (num_pages * TlsHeapStatic::PageSize); + span->num_pages = ptr_span->num_pages - num_pages; + span->id = 0; + span->status = Span::Status_InUse; + ChangeRangeOfSpan(std::addressof(m_span_table), ptr_span, ptr_span->start.u, num_pages); + RegisterSpan(std::addressof(m_span_table), span); + this->FreePagesImpl(span); + } + } else { + Span *next_span = GetNextSpan(std::addressof(m_span_table), ptr_span); + if (!next_span || next_span->status != Span::Status_InFreeList || next_span->num_pages < num_pages - ptr_span->num_pages) { + m_lock.Unlock(); + + m_lock.Lock(); + + Span *span = this->AllocatePagesImpl(num_pages); + if (span) { + *p = span->start.p; + std::memcpy(std::addressof(span->aux.large), std::addressof(ptr_span->aux.large), sizeof(ptr_span->aux.large)); + } else { + *p = nullptr; + } + + m_lock.Unlock(); + if (*p == nullptr) { + return ENOMEM; + } + + std::memcpy(*p, ptr, num_pages * TlsHeapStatic::PageSize); + + m_lock.Lock(); + this->FreePagesImpl(ptr_span); + m_lock.Unlock(); + + return 0; + } + + if (m_use_virtual_memory && this->AllocatePhysical(next_span->start.p, (num_pages - ptr_span->num_pages) * TlsHeapStatic::PageSize)) { + m_lock.Unlock(); + return ENOMEM; + } + + this->RemoveFromFreeBlockList(next_span); + if (next_span->num_pages == num_pages - ptr_span->num_pages) { + UnregisterSpan(std::addressof(m_span_table), next_span); + ChangeRangeOfSpan(std::addressof(m_span_table), ptr_span, ptr_span->start.u, num_pages); + SpanPage *sp = GetSpanPage(next_span); + this->FreeSpanToSpanPage(next_span, sp); + this->DestroySpanPageIfEmpty(sp, false); + } else { + const uintptr_t new_end = ptr_span->start.u + num_pages * TlsHeapStatic::PageSize; + const size_t new_num_pages = next_span->num_pages - (num_pages - ptr_span->num_pages); + ChangeRangeOfSpan(std::addressof(m_span_table), next_span, new_end, new_num_pages); + ChangeRangeOfSpan(std::addressof(m_span_table), ptr_span, ptr_span->start.u, num_pages); + this->MergeIntoFreeList(next_span); + } + } + } + + *p = ptr; + m_lock.Unlock(); + return 0; + } + + errno_t TlsHeapCentral::ShrinkLargeMemory(void *ptr, size_t size) { + if (!TlsHeapStatic::IsPageAligned(ptr)) { + return EFAULT; + } + + AMS_ASSERT(size <= MaxSize); + + std::scoped_lock lk(m_lock); + + Span *ptr_span = GetSpanFromPointer(std::addressof(m_span_table), ptr); + if (!ptr_span) { + AMS_ASSERT(ptr_span != nullptr); + return EFAULT; + } + + const size_t num_pages = TlsHeapStatic::GetPageIndex(size + TlsHeapStatic::PageSize - 1); + if (ptr_span->num_pages != num_pages) { + if (ptr_span->num_pages < num_pages) { + return EINVAL; + } + Span *span = this->AllocateSpanStruct(); + if (span != nullptr) { + span->start.u = ptr_span->start.u + (num_pages * TlsHeapStatic::PageSize); + span->num_pages = ptr_span->num_pages - num_pages; + span->id = 0; + span->status = Span::Status_InUse; + ChangeRangeOfSpan(std::addressof(m_span_table), ptr_span, ptr_span->start.u, num_pages); + RegisterSpan(std::addressof(m_span_table), span); + this->FreePagesImpl(span); + } + } + + return 0; + } + + void TlsHeapCentral::CalculateHeapHash(HeapHash *out) { + size_t alloc_count = 0; + size_t alloc_size = 0; + size_t hash = 0; + + { + std::scoped_lock lk(m_lock); + + for (Span *span = GetSpanFromPointer(std::addressof(m_span_table), this); span != nullptr; span = GetNextSpan(std::addressof(m_span_table), span)) { + if (span->status != Span::Status_InUse) { + continue; + } + + const size_t size = span->num_pages * TlsHeapStatic::PageSize; + + if (span->page_class == 0) { + alloc_count++; + alloc_size += size; + hash += std::hash<size_t>{}(size) + std::hash<size_t>{}(span->start.u); + } else { + const size_t chunk_size = TlsHeapStatic::GetChunkSize(span->page_class); + + const size_t n = size / chunk_size; + AMS_ASSERT(n <= TlsHeapStatic::PageSize); + + static_assert(util::IsAligned(TlsHeapStatic::PageSize, BITSIZEOF(FreeListAvailableWord))); + FreeListAvailableWord flags[TlsHeapStatic::PageSize / BITSIZEOF(FreeListAvailableWord)]; + std::memset(flags, 0, sizeof(flags)); + + for (Span::SmallMemory *sm = span->aux.small.objects; sm != nullptr; sm = sm->next) { + const size_t idx = (reinterpret_cast<uintptr_t>(sm) - span->start.u) / chunk_size; + flags[FreeListAvailableIndex(idx)] |= FreeListAvailableMask(idx); + } + + for (size_t i = 0; i < n; i++) { + if (!(flags[FreeListAvailableIndex(i)] & FreeListAvailableMask(i))) { + alloc_count++; + alloc_size += chunk_size; + hash += std::hash<size_t>{}(chunk_size) + std::hash<size_t>{}(span->start.u + chunk_size * n); + } + } + } + } + } + + out->alloc_count = alloc_count; + out->alloc_size = alloc_size; + out->hash = hash; + } + + SpanPage *TlsHeapCentral::AllocateSpanPage() { + Span *span = this->SearchFreeSpan(1); + if (span == nullptr) { + return nullptr; + } + AMS_ASSERT(span->page_class == 0); + + if (m_use_virtual_memory && this->AllocatePhysical(span->start.p, TlsHeapStatic::PageSize) != 0) { + return nullptr; + } + + SpanPage *sp = static_cast<SpanPage *>(span->start.p); + + InitializeSpanPage(sp); + Span *new_span = GetSpanPageSpan(sp); + if (span->num_pages == 1) { + this->RemoveFromFreeBlockList(span); + MigrateSpan(std::addressof(m_span_table), span, new_span); + AMS_ASSERT(new_span->num_pages == 1); + new_span->status = Span::Status_InUseSystem; + + SpanPage *sp_of_span = GetSpanPage(span); + this->FreeSpanToSpanPage(span, sp_of_span); + this->DestroySpanPageIfEmpty(sp_of_span, false); + } else { + new_span->start.u = span->start.u; + new_span->num_pages = 1; + new_span->status = Span::Status_InUseSystem; + new_span->id = 0; + + if (span->num_pages - 1 < FreeListCount) { + this->RemoveFromFreeBlockList(span); + } + ChangeRangeOfSpan(std::addressof(m_span_table), span, span->start.u + TlsHeapStatic::PageSize, span->num_pages - 1); + RegisterSpan(std::addressof(m_span_table), new_span); + + if (span->num_pages < FreeListCount) { + this->AddToFreeBlockList(span); + } + } + + ListInsertAfter(std::addressof(m_spanpage_list), sp); + return sp; + } + + Span *TlsHeapCentral::AllocateSpanFromSpanPage(SpanPage *sp) { + const size_t span_idx = __builtin_clzll(~sp->info.alloc_bitmap); + AMS_ASSERT(span_idx < SpanPage::MaxSpanCount); + + constexpr u64 TopBit = static_cast<u64>(1) << (BITSIZEOF(u64) - 1); + sp->info.alloc_bitmap |= (TopBit >> span_idx); + sp->info.free_count--; + + Span *span = std::addressof(sp->spans[span_idx]); + ListClearLink(span); + span->status = Span::Status_NotUsed; + span->page_class = 0; + span->object_count = 0; + + if (sp->info.free_count == 0) { + ListRemoveSelf(sp); + ListInsertAfter(std::addressof(m_full_spanpage_list), sp); + } + + return span; + } + + Span *TlsHeapCentral::SplitSpan(Span *span, size_t num_pages, Span *new_span) { + AMS_ASSERT(span->status == Span::Status_InFreeList); + AMS_ASSERT(span->num_pages > num_pages); + + const size_t remaining_pages = span->num_pages - num_pages; + + uintptr_t new_start, old_start; + if (num_pages < FreeListCount) { + new_start = span->start.u; + old_start = span->start.u + (num_pages * TlsHeapStatic::PageSize); + } else { + new_start = span->start.u + (remaining_pages * TlsHeapStatic::PageSize); + old_start = span->start.u; + } + + + if (remaining_pages >= FreeListCount) { + ChangeRangeOfSpan(std::addressof(m_span_table), span, old_start, remaining_pages); + } else { + this->RemoveFromFreeBlockList(span); + ChangeRangeOfSpan(std::addressof(m_span_table), span, old_start, remaining_pages); + this->AddToFreeBlockList(span); + } + + new_span->start.u = new_start; + new_span->num_pages = num_pages; + new_span->page_class = 0; + new_span->status = Span::Status_InUse; + new_span->id = 0; + new_span->aux.large_clear.zero = 0; + span->aux.large_clear.zero = 0; + + if (m_use_virtual_memory && this->AllocatePhysical(new_span->start.p, new_span->num_pages * TlsHeapStatic::PageSize) != 0) { + new_span->status = Span::Status_InFreeList; + this->MergeIntoFreeList(new_span); + return nullptr; + } + + RegisterSpan(std::addressof(m_span_table), new_span); + return new_span; + } + + void TlsHeapCentral::MergeFreeSpans(Span *span, Span *span_to_merge, uintptr_t start) { + const size_t total_pages = span->num_pages + span_to_merge->num_pages; + UnregisterSpan(std::addressof(m_span_table), span_to_merge); + SpanPage *span_page = GetSpanPage(span_to_merge); + this->FreeSpanToSpanPage(span_to_merge, span_page); + ChangeRangeOfSpan(std::addressof(m_span_table), span, start, total_pages); + } + + bool TlsHeapCentral::DestroySpanPageIfEmpty(SpanPage *sp, bool full) { + if (sp->info.is_sticky) { + if (!(full || sp->info.free_count > 0x10)) { + return false; + } + if (!sp->info.free_count) { + return false; + } + + size_t first = this->FreeListFirstNonEmpty(0); + + while (first < FreeListCount) { + for (Span *target = ListGetNext(std::addressof(m_freelists[first])); target; target = ListGetNext(target)) { + AMS_ASSERT(target->status == Span::Status_InFreeList); + + SpanPage *target_sp = GetSpanPage(target); + if (target_sp != sp) { + Span *new_span = this->AllocateSpanFromSpanPage(sp); + AMS_ASSERT(new_span != nullptr); + + MigrateSpan(std::addressof(m_span_table), target, new_span); + this->FreeSpanToSpanPage(target, target_sp); + this->DestroySpanPageIfEmpty(target_sp, full); + } + } + first = this->FreeListFirstNonEmpty(first + 1); + } + + return false; + } else if (sp->info.free_count == SpanPage::MaxSpanCount) { + if (!ListGetNext(sp) && !ListGetPrev(sp)) { + return false; + } + SpanPage *other_sp = GetSpanPage(this->GetFirstSpan()); + + SpanPage *target; + if (other_sp->info.free_count > 0x10) { + target = other_sp; + } else { + for (target = ListGetNext(std::addressof(m_spanpage_list)); target && (target == sp || !target->info.free_count); target = ListGetNext(target)) { + /* ... */ + } + if (!target) { + if (!other_sp->info.free_count) { + return false; + } + target = other_sp; + } + } + + Span *new_span = this->AllocateSpanFromSpanPage(target); + AMS_ASSERT(new_span != nullptr); + + MigrateSpan(std::addressof(m_span_table), GetSpanPageSpan(sp), new_span); + + ListRemoveSelf(sp); + this->FreePagesImpl(new_span); + return true; + } else { + return false; + } + } + + Span *TlsHeapCentral::GetFirstSpan() const { + Span *span = GetSpanFromPointer(std::addressof(m_span_table), reinterpret_cast<const void *>(this)); + AMS_ASSERT(span != nullptr); + return GetNextSpan(std::addressof(m_span_table), span); + } + + Span *TlsHeapCentral::MakeFreeSpan(size_t num_pages) { + while (true) { + SpanPage *sp; + for (sp = ListGetNext(std::addressof(m_spanpage_list)); sp && !this->DestroySpanPageIfEmpty(sp, true); sp = ListGetNext(sp)) { + /* ... */ + } + if (!sp) { + break; + } + if (Span *span = this->SearchFreeSpan(num_pages); span != nullptr) { + return span; + } + } + return nullptr; + } + + Span *TlsHeapCentral::SearchFreeSpan(size_t num_pages) const { + size_t start = FreeListCount - 1; + if (num_pages < FreeListCount) { + start = this->FreeListFirstNonEmpty(num_pages - 1); + if (start == FreeListCount) { + return nullptr; + } + } + + Span *cur = ListGetNext(std::addressof(m_freelists[start])); + Span *best = cur; + if (start == FreeListCount - 1) { + if (num_pages >= FreeListCount) { + best = nullptr; + while (cur) { + if (num_pages <= cur->num_pages) { + if (best) { + if (cur->num_pages >= best->num_pages) { + if (cur->num_pages == best->num_pages && cur->start.u < best->start.u) { + best = cur; + } + } else { + best = cur; + } + } else { + best = cur; + } + } + cur = ListGetNext(cur); + } + } else { + while (cur) { + if (cur->num_pages >= best->num_pages) { + if (cur->num_pages == best->num_pages && cur->start.u < best->start.u) { + best = cur; + } + } else { + best = cur; + } + cur = ListGetNext(cur); + } + } + } + + if (best != nullptr) { + AMS_ASSERT(best->status == Span::Status_InFreeList); + } + + return best; + } + + void TlsHeapCentral::FreeSpanToSpanPage(Span *span, SpanPage *sp) { + span->status = Span::Status_NotUsed; + + const size_t span_idx = span - sp->spans; + + constexpr u64 TopBit = static_cast<u64>(1) << (BITSIZEOF(u64) - 1); + sp->info.alloc_bitmap &= ~(TopBit >> span_idx); + if ((++(sp->info.free_count)) == 1) { + ListRemoveSelf(sp); + ListInsertAfter(std::addressof(m_spanpage_list), sp); + } + } + + void TlsHeapCentral::FreeSpanToSpanPage(Span *span) { + return this->FreeSpanToSpanPage(span, GetSpanPage(span)); + } + + void TlsHeapCentral::MergeIntoFreeList(Span *&span) { + AMS_ASSERT(!span->list_prev && !span->list_next); + AMS_ASSERT(span->status != Span::Status_InUse); + + Span *prev_span = GetPrevSpan(std::addressof(m_span_table), span); + Span *next_span = GetNextSpan(std::addressof(m_span_table), span); + const bool prev_free = prev_span && prev_span->status == Span::Status_InFreeList; + const bool prev_small = prev_span && prev_span->num_pages < FreeListCount; + const bool next_free = next_span && next_span->status == Span::Status_InFreeList; + const bool next_small = next_span && next_span->num_pages < FreeListCount; + + if (prev_free) { + if (next_free) { + if (prev_small) { + if (next_small) { + this->RemoveFromFreeBlockList(prev_span); + this->RemoveFromFreeBlockList(next_span); + this->MergeFreeSpans(prev_span, span, prev_span->start.u); + this->MergeFreeSpans(prev_span, next_span, prev_span->start.u); + this->AddToFreeBlockList(prev_span); + span = prev_span; + } else { + this->RemoveFromFreeBlockList(prev_span); + this->MergeFreeSpans(prev_span, span, prev_span->start.u); + this->MergeFreeSpans(next_span, prev_span, prev_span->start.u); + span = next_span; + } + } else { + this->RemoveFromFreeBlockList(next_span); + this->MergeFreeSpans(prev_span, span, prev_span->start.u); + this->MergeFreeSpans(prev_span, next_span, prev_span->start.u); + span = prev_span; + } + } else { + if (prev_small) { + this->RemoveFromFreeBlockList(prev_span); + this->MergeFreeSpans(prev_span, span, prev_span->start.u); + this->AddToFreeBlockList(prev_span); + } else { + this->MergeFreeSpans(prev_span, span, prev_span->start.u); + } + span = prev_span; + } + } else if (next_free) { + if (next_small) { + this->RemoveFromFreeBlockList(next_span); + this->MergeFreeSpans(span, next_span, span->start.u); + this->AddToFreeBlockList(span); + } else { + this->MergeFreeSpans(next_span, span, span->start.u); + span = next_span; + } + } else { + this->AddToFreeBlockList(span); + } + + AMS_ASSERT(GetSpanPageSpan(GetSpanPage(span)) != span); + AMS_ASSERT(span->status == Span::Status_InFreeList); + } + + errno_t TlsHeapCentral::AllocatePhysical(void *start, size_t size) { + /* TODO: Implement physical tls heap central logic. */ + AMS_UNUSED(start, size); + return 0; + } + + errno_t TlsHeapCentral::FreePhysical(void *start, size_t size) { + const uintptr_t start_alignup = TlsHeapStatic::AlignUpPhysicalPage(reinterpret_cast<uintptr_t>(start)); + const uintptr_t end_aligndown = TlsHeapStatic::AlignDownPhysicalPage(reinterpret_cast<uintptr_t>(start) + size); + if (end_aligndown <= start_alignup) { + return 0; + } + + const uintptr_t phys_heap_start = TlsHeapStatic::AlignDownPhysicalPage(reinterpret_cast<uintptr_t>(this)); + const uintptr_t i = TlsHeapStatic::GetPhysicalPageIndex(start_alignup - phys_heap_start); + const uintptr_t idx_end = TlsHeapStatic::GetPhysicalPageIndex(end_aligndown - phys_heap_start); + AMS_ASSERT(i < idx_end); + + if (i + 1 == idx_end) { + if (m_physical_page_flags[i]) { + m_physical_page_flags[i] = 2; + } + } else { + const void *set_flag = util::Memchr(std::addressof(m_physical_page_flags[i]), 1, idx_end - i); + if (set_flag) { + const uintptr_t set_idx = reinterpret_cast<const u8 *>(set_flag) - m_physical_page_flags; + const void *lst_flag = util::Memrchr(std::addressof(m_physical_page_flags[set_idx]), 1, idx_end - set_idx); + const uintptr_t lst_idx = (lst_flag) ? (reinterpret_cast<const u8 *>(lst_flag) - m_physical_page_flags + 1) : idx_end; + std::memset(std::addressof(m_physical_page_flags[set_idx]), 2, lst_idx - set_idx); + } + } + + return 0; + } + + Span *TlsHeapCentral::AllocatePagesImpl(size_t num_pages) { + if (num_pages >= m_span_table.total_pages / 4) { + this->MakeFreeSpan(std::numeric_limits<size_t>::max()); + } + + Span *span = this->SearchFreeSpan(num_pages); + if (span == nullptr) { + span = this->MakeFreeSpan(num_pages); + if (span == nullptr) { + return nullptr; + } + } + + AMS_ASSERT(span->status == Span::Status_InFreeList); + + if (num_pages == span->num_pages) { + if (m_use_virtual_memory && this->AllocatePhysical(span->start.p, span->num_pages * TlsHeapStatic::PageSize) != 0) { + return nullptr; + } else { + this->RemoveFromFreeBlockList(span); + span->status = Span::Status_InUse; + span->id = 0; + span->aux.large_clear.zero = 0; + return span; + } + } else { + /* Save the extents of the free span we found. */ + auto * const prev_ptr = span->start.p; + const size_t prev_pages = span->num_pages; + + /* Allocate a new span struct. */ + Span *new_span = this->AllocateSpanStruct(); + if (new_span == nullptr) { + return nullptr; + } + auto new_span_guard = SCOPE_GUARD { this->FreeSpanToSpanPage(new_span); }; + + /* Allocating the new span potentially invalidates the span we were looking at, so find the span for it in the table. */ + span = GetSpanFromPointer(std::addressof(m_span_table), prev_ptr); + const size_t cur_pages = span->num_pages; + + /* If the span was partially allocated, we need to find a new one that's big enough. */ + if (cur_pages != prev_pages) { + span = this->SearchFreeSpan(num_pages); + if (span == nullptr) { + return nullptr; + } + } + + /* If the span is big enough to split (span->num_pages > num_pages), we want to split it. */ + /* span->num_pages > num_pages is true if the span wasn't partially allocated (cur_pages == prev_pages) */ + /* OR if the new free span we found has num_pages > num_pages. Note that we know span->num_pages >= num_pages */ + /* so this > condition can be expressed as span->num_pages != num_pages. */ + if (cur_pages == prev_pages || num_pages != span->num_pages) { + /* We're going to use the new span for our split. */ + new_span_guard.Cancel(); + + return this->SplitSpan(span, num_pages, new_span); + } else if (m_use_virtual_memory && this->AllocatePhysical(span->start.p, span->num_pages * TlsHeapStatic::PageSize) != 0) { + return nullptr; + } else { + this->RemoveFromFreeBlockList(span); + span->status = Span::Status_InUse; + span->id = 0; + span->aux.large_clear.zero = 0; + return span; + } + } + } + + Span *TlsHeapCentral::AllocatePagesWithBigAlignImpl(size_t num_pages, size_t align) { + if (num_pages >= m_span_table.total_pages / 4) { + this->MakeFreeSpan(std::numeric_limits<size_t>::max()); + } + + Span *before_span = this->AllocateSpanStruct(); + if (!before_span) { + return nullptr; + } + + Span *after_span = this->AllocateSpanStruct(); + if (!after_span) { + this->FreeSpanToSpanPage(before_span); + return nullptr; + } + + AMS_ASSERT(align <= TlsHeapStatic::PageSize); + AMS_ASSERT(util::IsPowerOfTwo(align)); + + Span *span = this->SearchFreeSpan(num_pages); + if (span == nullptr) { + span = this->MakeFreeSpan(num_pages); + if (span == nullptr) { + this->FreeSpanToSpanPage(before_span); + this->FreeSpanToSpanPage(after_span); + return nullptr; + } + } + + AMS_ASSERT(span->status == Span::Status_InFreeList); + + const uintptr_t aligned_start = util::AlignUp(span->start.u, align); + if (m_use_virtual_memory && this->AllocatePhysical(reinterpret_cast<void *>(aligned_start), num_pages * TlsHeapStatic::PageSize) != 0) { + this->FreeSpanToSpanPage(before_span); + this->FreeSpanToSpanPage(after_span); + return nullptr; + } else if (aligned_start == span->start.u) { + /* We don't need the before span, but we do need the after span. */ + this->FreeSpanToSpanPage(before_span); + + after_span->start.u = aligned_start + num_pages * TlsHeapStatic::PageSize; + after_span->num_pages = span->num_pages - num_pages; + after_span->page_class = 0; + after_span->status = Span::Status_InFreeList; + after_span->id = 0; + + this->RemoveFromFreeBlockList(span); + + span->status = Span::Status_InUse; + span->aux.large_clear.zero = 0; + ChangeRangeOfSpan(std::addressof(m_span_table), span, aligned_start, num_pages); + + RegisterSpan(std::addressof(m_span_table), after_span); + this->MergeIntoFreeList(after_span); + + return span; + } else { + /* We need the before span. */ + before_span->start.u = span->start.u; + before_span->num_pages = TlsHeapStatic::GetPageIndex(aligned_start - span->start.u); + before_span->page_class = 0; + before_span->status = Span::Status_InFreeList; + before_span->id = 0; + + const size_t after_pages = span->num_pages - before_span->num_pages - num_pages; + if (after_pages) { + /* We need the after span. */ + after_span->start.u = aligned_start + num_pages * TlsHeapStatic::PageSize; + after_span->num_pages = after_pages; + after_span->page_class = 0; + after_span->status = Span::Status_InFreeList; + after_span->id = 0; + + this->RemoveFromFreeBlockList(span); + + span->status = Span::Status_InUse; + span->aux.large_clear.zero = 0; + + ChangeRangeOfSpan(std::addressof(m_span_table), span, aligned_start, num_pages); + + RegisterSpan(std::addressof(m_span_table), before_span); + RegisterSpan(std::addressof(m_span_table), after_span); + this->MergeIntoFreeList(before_span); + this->MergeIntoFreeList(after_span); + + return span; + } else { + /* We don't need the after span. */ + this->FreeSpanToSpanPage(after_span); + + this->RemoveFromFreeBlockList(span); + + span->status = Span::Status_InUse; + span->aux.large_clear.zero = 0; + + ChangeRangeOfSpan(std::addressof(m_span_table), span, aligned_start, num_pages); + + RegisterSpan(std::addressof(m_span_table), before_span); + this->MergeIntoFreeList(before_span); + + return span; + } + } + } + + void TlsHeapCentral::FreePagesImpl(Span *span) { + AMS_ASSERT(span && span->page_class == 0); + + if (span->status == Span::Status_InFreeList) { + /* Double free error. */ + } else { + span->status = Span::Status_InFreeList; + if (m_use_virtual_memory) { + const uintptr_t start = span->start.u; + const uintptr_t end = span->start.u + (span->num_pages * TlsHeapStatic::PageSize); + uintptr_t start_alignup = TlsHeapStatic::AlignUpPhysicalPage(start); + uintptr_t end_aligndown = TlsHeapStatic::AlignDownPhysicalPage(end); + + this->MergeIntoFreeList(span); + + const uintptr_t new_start = span->start.u; + const uintptr_t new_end = span->start.u + (span->num_pages * TlsHeapStatic::PageSize); + + if (start != start_alignup && new_start + TlsHeapStatic::PhysicalPageSize <= start_alignup) { + start_alignup -= TlsHeapStatic::PhysicalPageSize; + } + if (end != end_aligndown && end_aligndown + TlsHeapStatic::PhysicalPageSize <= new_end) { + end_aligndown += TlsHeapStatic::PhysicalPageSize; + } + + if (start_alignup < end_aligndown) { + const auto err = this->FreePhysical(reinterpret_cast<void *>(start_alignup), end_aligndown - start_alignup); + AMS_ASSERT(err == 0); + AMS_UNUSED(err); + } + } else { + this->MergeIntoFreeList(span); + } + } + } + + void *TlsHeapCentral::CacheSmallMemoryImpl(size_t cls, size_t align, bool for_system) { + AMS_ASSERT(cls != 0 && cls < TlsHeapStatic::NumClassInfo); + + Span *span = ListGetNext(std::addressof(m_smallmem_lists[cls])); + while (true) { + if (for_system) { + while (span && span->status != Span::Status_InUseSystem) { + span = ListGetNext(span); + } + } else { + while (span && (span->status == Span::Status_InUseSystem || span->id)) { + span = ListGetNext(span); + } + } + + if (!span) { + break; + } + + void *mem = AllocateSmallMemory(span); + if (mem) { + if (!span->aux.small.objects) { + ListRemoveSelf(span); + } + return mem; + } + + AMS_ASSERT(!span->aux.small.objects); + + Span *old = span; + span = ListGetNext(span); + ListRemoveSelf(old); + } + + + Span *new_span = this->AllocatePagesImpl(TlsHeapStatic::GetNumPages(cls)); + if (new_span) { + SpanToSmallMemorySpan(std::addressof(m_span_table), new_span, cls); + ListInsertAfter(std::addressof(m_smallmem_lists[cls]), new_span); + InitSmallMemorySpan(new_span, cls, for_system, 0); + + void *mem = AllocateSmallMemory(new_span); + if (!new_span->aux.small.objects) { + ListRemoveSelf(new_span); + } + return mem; + } else { + for (size_t cur_cls = cls; cur_cls < TlsHeapStatic::NumClassInfo; cur_cls++) { + if (align == 0 || util::IsAligned(TlsHeapStatic::GetChunkSize(cur_cls), align)) { + span = ListGetNext(std::addressof(m_smallmem_lists[cur_cls])); + if (for_system) { + while (span && span->status != Span::Status_InUseSystem) { + span = ListGetNext(span); + } + } else { + while (span && (span->status == Span::Status_InUseSystem)) { + span = ListGetNext(span); + } + } + if (!span) { + continue; + } + + void *mem = AllocateSmallMemory(span); + if (!mem) { + continue; + } + + if (!span->aux.small.objects) { + ListRemoveSelf(span); + } + return mem; + } + } + return nullptr; + } + } + + errno_t TlsHeapCentral::UncacheSmallMemoryImpl(void *ptr) { + Span *span = GetSpanFromPointer(std::addressof(m_span_table), ptr); + if (span && span->page_class) { + if (!span->aux.small.objects) { + ListInsertAfter(std::addressof(m_smallmem_lists[span->page_class]), span); + } + + ReleaseSmallMemory(span, ptr); + + if (!span->object_count) { + span->aux.small.objects = nullptr; + ListRemoveSelf(span); + AMS_ASSERT(span->page_class != 0); + SmallMemorySpanToSpan(std::addressof(m_span_table), span); + this->FreePagesImpl(span); + } + + return 0; + } else { + AMS_ASSERT(span); + AMS_ASSERT(span->page_class == 0); + return EFAULT; + } + } + + size_t TlsHeapCentral::CacheSmallMemoryListImpl(TlsHeapCache *cache, size_t *cls, size_t count, void **p, s32 cpu_id, size_t align) { + AMS_ASSERT(*cls != 0 && *cls < TlsHeapStatic::NumClassInfo); + + Span::SmallMemory head = {}; + Span::SmallMemory *hptr = std::addressof(head); + + Span *span = ListGetNext(std::addressof(m_smallmem_lists[*cls])); + size_t n = 0; + + while (span) { + if (span->status != Span::Status_InUseSystem && span->id == cpu_id) { + MangledSmallMemory memlist; + if (size_t num = AllocateSmallMemory(span, cache, count - n, std::addressof(memlist)); num != 0) { + hptr->next = memlist.from; + hptr = memlist.to; + memlist.to->next = nullptr; + n += num; + if (n >= count) { + if (!span->aux.small.objects) { + ListRemoveSelf(span); + } + *p = head.next; + return n; + } + } + + AMS_ASSERT(span->aux.small.objects); + + Span *old = span; + span = ListGetNext(span); + ListRemoveSelf(old); + } else { + span = ListGetNext(span); + } + } + + Span *new_span = this->AllocatePagesImpl(TlsHeapStatic::GetNumPages(*cls)); + if (new_span) { + SpanToSmallMemorySpan(std::addressof(m_span_table), new_span, *cls); + ListInsertAfter(std::addressof(m_smallmem_lists[*cls]), new_span); + InitSmallMemorySpan(new_span, *cls, false, cpu_id); + + MangledSmallMemory memlist; + size_t num = AllocateSmallMemory(new_span, cache, count - n, std::addressof(memlist)); + AMS_ASSERT(num > 0); + + hptr->next = memlist.from; + hptr = memlist.to; + memlist.to->next = nullptr; + n += num; + + if (!new_span->aux.small.objects) { + ListRemoveSelf(new_span); + } + + *p = head.next; + return n; + } else if (head.next) { + *p = head.next; + return n; + } else { + for (size_t cur_cls = *cls; cur_cls < TlsHeapStatic::NumClassInfo; cur_cls++) { + if (align == 0 || util::IsAligned(TlsHeapStatic::GetChunkSize(cur_cls), align)) { + span = ListGetNext(std::addressof(m_smallmem_lists[cur_cls])); + + while (span && (span->status == Span::Status_InUseSystem)) { + span = ListGetNext(span); + } + + if (!span) { + continue; + } + + void *mem = AllocateSmallMemory(span); + if (!mem) { + continue; + } + + if (!span->aux.small.objects) { + ListRemoveSelf(span); + } + + reinterpret_cast<Span::SmallMemory *>(mem)->next = nullptr; + *p = cache->ManglePointer(mem); + *cls = cur_cls; + return 1; + } + } + + *p = nullptr; + return 0; + } + } + + errno_t TlsHeapCentral::WalkAllocatedPointersImpl(HeapWalkCallback callback, void *user_data) { + errno_t err = ENOENT; + + for (Span *span = GetSpanFromPointer(std::addressof(m_span_table), this); span != nullptr; span = GetNextSpan(std::addressof(m_span_table), span)) { + if (span->status != Span::Status_InUse) { + continue; + } + + const size_t size = span->num_pages * TlsHeapStatic::PageSize; + + if (span->page_class != 0) { + static_assert(util::IsAligned(TlsHeapStatic::PageSize, BITSIZEOF(FreeListAvailableWord))); + FreeListAvailableWord flags[TlsHeapStatic::PageSize / BITSIZEOF(FreeListAvailableWord)]; + std::memset(flags, 0, sizeof(flags)); + + const size_t chunk_size = TlsHeapStatic::GetChunkSize(span->page_class); + const size_t n = size / chunk_size; + + AMS_ASSERT(n <= TlsHeapStatic::PageSize); + if (n <= TlsHeapStatic::PageSize) { + for (Span::SmallMemory *sm = span->aux.small.objects; sm != nullptr; sm = sm->next) { + const size_t idx = (reinterpret_cast<uintptr_t>(sm) - span->start.u) / chunk_size; + flags[FreeListAvailableIndex(idx)] |= FreeListAvailableMask(idx); + } + + for (size_t i = 0; i < n; i++) { + if (!(flags[FreeListAvailableIndex(i)] & FreeListAvailableMask(i))) { + if (s32 res = this->CallWalkCallback(callback, reinterpret_cast<void *>(span->start.u + chunk_size * i), chunk_size, user_data); res != 0) { + if (res >= 0) { + err = res; + break; + } else { + err = 0; + } + } + } + } + } else { + return EIO; + } + } else { + if (s32 res = this->CallWalkCallback(callback, span->start.p, size, user_data); res != 0) { + if (res >= 0) { + err = res; + break; + } else { + err = 0; + } + } + } + } + + return err; + } + + errno_t TlsHeapCentral::GetMappedMemStatsImpl(size_t *out_free_size, size_t *out_max_allocatable_size) { + if (!m_use_virtual_memory) { + return EOPNOTSUPP; + } + + /* TODO: Is this worth supporting? */ + AMS_UNUSED(out_free_size, out_max_allocatable_size); + return EOPNOTSUPP; + } + + errno_t TlsHeapCentral::GetMemStatsImpl(TlsHeapMemStats *out) { + size_t max_allocatable_size = 0; + size_t free_size = 0; + size_t system_size = 0; + size_t allocated_size = 0; + size_t num_free_regions = 0; + size_t num_free_spans = 0; + size_t wip_allocatable_size = 0; + + Span *span = GetSpanFromPointer(std::addressof(m_span_table), this); + while (span) { + const size_t size = span->num_pages * TlsHeapStatic::PageSize; + + if (span->status == Span::Status_InUse || span->status == Span::Status_InUseSystem) { + /* Found a usable span, so end our contiguous run. */ + max_allocatable_size = std::max(max_allocatable_size, wip_allocatable_size); + wip_allocatable_size = 0; + + if (span->status == Span::Status_InUseSystem) { + system_size += size; + } else if (span->page_class && span->aux.small.objects) { + const size_t chunk_size = TlsHeapStatic::GetChunkSize(span->page_class); + const size_t used_size = chunk_size * span->object_count; + allocated_size += used_size; + free_size += size - used_size; + } else { + allocated_size += size; + } + } else { + /* Free span. */ + free_size += size; + num_free_spans++; + if (wip_allocatable_size == 0) { + num_free_regions++; + } + wip_allocatable_size += size; + } + + span = GetNextSpan(std::addressof(m_span_table), span); + } + + max_allocatable_size = std::max(max_allocatable_size, wip_allocatable_size); + + bool sp_full = true; + for (SpanPage *sp = ListGetNext(std::addressof(m_spanpage_list)); sp != nullptr; sp = ListGetNext(sp)) { + if (sp->info.is_sticky == 0 && CanAllocateSpan(sp)) { + sp_full = false; + break; + } + } + + if (num_free_spans == 1 && allocated_size == 0) { + sp_full = false; + } + + if (sp_full && num_free_regions < 2 && max_allocatable_size >= TlsHeapStatic::PageSize) { + max_allocatable_size -= TlsHeapStatic::PageSize; + } + + if (max_allocatable_size < 3 * TlsHeapStatic::PageSize) { + max_allocatable_size = 0; + } + + out->max_allocatable_size = max_allocatable_size; + out->free_size = free_size; + out->allocated_size = allocated_size; + out->system_size = system_size; + + return 0; + } + + void TlsHeapCentral::DumpImpl(DumpMode dump_mode, int fd, bool json) { + AMS_UNUSED(dump_mode, fd, json); + AMS_ABORT("Not yet implemented"); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_tls_heap_central.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_tls_heap_central.hpp new file mode 100644 index 00000000..853599de --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_tls_heap_central.hpp @@ -0,0 +1,551 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "mem_impl_heap_platform.hpp" +#include "mem_impl_heap_tls_heap_static.hpp" +#include "mem_impl_heap_tls_heap_cache.hpp" + +namespace ams::mem::impl::heap { + + /* Simple intrusive list. */ + template<typename T> + struct ListHeader { + T *list_next; + }; + + template<typename T> + struct ListElement : public ListHeader<T> { + T *list_prev; + }; + + template<typename T> + constexpr inline void ListClearLink(ListHeader<T> *l) { + l->list_next = nullptr; + } + + template<typename T> + constexpr inline void ListClearLink(ListElement<T> *l) { + l->list_next = nullptr; + l->list_prev = nullptr; + } + + template<typename T> + constexpr inline T *ListGetNext(const ListHeader<T> *l) { + return l->list_next; + } + + template<typename T> + constexpr inline T *ListGetNext(const ListElement<T> *l) { + return l->list_next; + } + + template<typename T> + constexpr inline T *ListGetPrev(const ListElement<T> *l) { + return l->list_prev; + } + + template<typename T> + constexpr inline void ListInsertAfter(ListHeader<T> *hdr, T *e) { + e->list_next = hdr->list_next; + e->list_prev = static_cast<T *>(hdr); + + if (hdr->list_next != nullptr) { + hdr->list_next->list_prev = e; + } + hdr->list_next = e; + } + + template<typename T> + constexpr inline void ListRemoveSelf(T *e) { + if (e->list_next != nullptr) { + e->list_next->list_prev = e->list_prev; + } + if (e->list_prev != nullptr) { + e->list_prev->list_next = e->list_next; + } + e->list_next = nullptr; + e->list_prev = nullptr; + } + + struct Span : public ListElement<Span> { + struct SmallMemory { + SmallMemory *next; + }; + + enum Status : u8 { + Status_NotUsed = 0, + Status_InUse = 1, + Status_InFreeList = 2, + Status_InUseSystem = 3, + }; + + u16 object_count; + u8 page_class; + u8 status; + s32 id; + union { + uintptr_t u; + void *p; + SmallMemory *sm; + char *cp; + } start; + uintptr_t num_pages; + union { + struct { + SmallMemory *objects; + u64 is_allocated[8]; + } small; + struct { + u8 color[3]; + char name[0x10]; + } large; + struct { + u32 zero; + } large_clear; + } aux; + }; + + struct SpanPage : public ListElement<SpanPage> { + struct Info { + u64 alloc_bitmap; + u16 free_count; + u8 is_sticky; + Span span_of_spanpage; + } info; + Span spans[(TlsHeapStatic::PageSize - sizeof(Info) - sizeof(ListElement<SpanPage>)) / sizeof(Span)]; + + static constexpr size_t MaxSpanCount = sizeof(spans) / sizeof(spans[0]); + }; + static_assert(sizeof(SpanPage) <= TlsHeapStatic::PageSize); + + static constexpr ALWAYS_INLINE bool CanAllocateSpan(const SpanPage *span_page) { + return span_page->info.alloc_bitmap != ~(decltype(span_page->info.alloc_bitmap){}); + } + + struct SpanTable { + uintptr_t total_pages; + Span **page_to_span; + u8 *pageclass_cache; + }; + + struct TlsHeapMemStats { + size_t allocated_size; + size_t free_size; + size_t system_size; + size_t max_allocatable_size; + }; + + ALWAYS_INLINE Span *GetSpanFromPointer(const SpanTable *table, const void *ptr) { + const size_t idx = TlsHeapStatic::GetPageIndex(reinterpret_cast<uintptr_t>(ptr) - reinterpret_cast<uintptr_t>(table)); + if (idx < table->total_pages) { + return table->page_to_span[idx]; + } else { + return nullptr; + } + } + + ALWAYS_INLINE SpanPage *GetSpanPage(Span *span) { + return reinterpret_cast<SpanPage *>(TlsHeapStatic::AlignDownPage(reinterpret_cast<uintptr_t>(span))); + } + + ALWAYS_INLINE Span *GetSpanPageSpan(SpanPage *span_page) { + return std::addressof(span_page->info.span_of_spanpage); + } + + ALWAYS_INLINE Span *GetPrevSpan(const SpanTable *span_table, const Span *span) { + return GetSpanFromPointer(span_table, reinterpret_cast<const void *>(span->start.u - 1)); + } + + ALWAYS_INLINE Span *GetNextSpan(const SpanTable *span_table, const Span *span) { + return GetSpanFromPointer(span_table, reinterpret_cast<const void *>(span->start.u + span->num_pages * TlsHeapStatic::PageSize)); + } + + class TlsHeapCentral { + private: + using FreeListAvailableWord = u64; + + static constexpr size_t FreeListCount = 0x100; + static constexpr size_t NumFreeListBitmaps = FreeListCount / BITSIZEOF(FreeListAvailableWord); + + static constexpr ALWAYS_INLINE size_t FreeListAvailableIndex(size_t which) { + return which / BITSIZEOF(FreeListAvailableWord); + } + + static constexpr ALWAYS_INLINE size_t FreeListAvailableBit(size_t which) { + return which % BITSIZEOF(FreeListAvailableWord); + } + + static constexpr ALWAYS_INLINE FreeListAvailableWord FreeListAvailableMask(size_t which) { + return static_cast<FreeListAvailableWord>(1) << FreeListAvailableBit(which); + } + + static_assert(NumFreeListBitmaps * BITSIZEOF(FreeListAvailableWord) == FreeListCount); + private: + SpanTable m_span_table; + u8 *m_physical_page_flags; + s32 m_num_threads; + s32 m_static_thread_quota; + s32 m_dynamic_thread_quota; + bool m_use_virtual_memory; + os::SdkRecursiveMutex m_lock; + ListHeader<SpanPage> m_spanpage_list; + ListHeader<SpanPage> m_full_spanpage_list; + ListHeader<Span> m_freelists[FreeListCount]; + FreeListAvailableWord m_freelists_bitmap[NumFreeListBitmaps]; + ListHeader<Span> m_smallmem_lists[TlsHeapStatic::NumClassInfo]; + public: + TlsHeapCentral() : m_lock() { + m_span_table.total_pages = 0; + } + + errno_t Initialize(void *start, size_t size, bool use_virtual_memory); + bool IsClean(); + + errno_t ReallocateLargeMemory(void *ptr, size_t size, void **p); + errno_t ShrinkLargeMemory(void *ptr, size_t size); + + void CalculateHeapHash(HeapHash *out); + + errno_t AddThreadCache(TlsHeapCache *cache) { + AMS_UNUSED(cache); + + std::scoped_lock lk(m_lock); + + /* Add thread and recalculate. */ + m_num_threads++; + m_dynamic_thread_quota = this->GetTotalHeapSize() / (2 * m_num_threads); + + return 0; + } + + errno_t RemoveThreadCache(TlsHeapCache *cache) { + AMS_UNUSED(cache); + + std::scoped_lock lk(m_lock); + + /* Remove thread and recalculate. */ + m_num_threads--; + m_dynamic_thread_quota = this->GetTotalHeapSize() / (2 * m_num_threads); + + return 0; + } + + void *CacheLargeMemory(size_t size) { + std::scoped_lock lk(m_lock); + + const size_t num_pages = util::AlignUp(size, TlsHeapStatic::PageSize) / TlsHeapStatic::PageSize; + if (Span *span = this->AllocatePagesImpl(num_pages); span != nullptr) { + return span->start.p; + } else { + return nullptr; + } + } + + void *CacheLargeMemoryWithBigAlign(size_t size, size_t align) { + std::scoped_lock lk(m_lock); + + const size_t num_pages = util::AlignUp(size, TlsHeapStatic::PageSize) / TlsHeapStatic::PageSize; + + Span *span = nullptr; + if (align > TlsHeapStatic::PageSize) { + span = this->AllocatePagesWithBigAlignImpl(num_pages, align); + } else { + span = this->AllocatePagesImpl(num_pages); + } + + if (span != nullptr) { + return span->start.p; + } else { + return nullptr; + } + } + + void *CacheSmallMemory(size_t cls, size_t align = 0) { + std::scoped_lock lk(m_lock); + + return this->CacheSmallMemoryImpl(cls, align, false); + } + + void *CacheSmallMemoryForSystem(size_t cls) { + std::scoped_lock lk(m_lock); + + return this->CacheSmallMemoryImpl(cls, 0, true); + } + + size_t CacheSmallMemoryList(TlsHeapCache *cache, size_t *cls, size_t count, void **p, size_t align = 0) { + std::scoped_lock lk(m_lock); + + s32 cpu_id = 0; + if (*cls < 8) { + getcpu(std::addressof(cpu_id)); + } + + return this->CacheSmallMemoryListImpl(cache, cls, count, p, cpu_id, align); + } + + bool CheckCachedSize(s32 size) const { + return size < m_dynamic_thread_quota && size < m_static_thread_quota; + } + + void Dump(DumpMode dump_mode, int fd, bool json) { + std::scoped_lock lk(m_lock); + return this->DumpImpl(dump_mode, fd, json); + } + + size_t GetAllocationSize(const void *ptr) { + if (TlsHeapStatic::IsPageAligned(ptr)) { + Span *span = nullptr; + { + std::scoped_lock lk(m_lock); + span = GetSpanFromPointer(std::addressof(m_span_table), ptr); + } + if (span != nullptr) { + return span->num_pages * TlsHeapStatic::PageSize; + } else { + AMS_ASSERT(span != nullptr); + return 0; + } + } else { + /* TODO: Handle error? */ + return 0; + } + } + + s32 GetClassFromPointer(const void *ptr) { + std::atomic_thread_fence(std::memory_order_acquire); + + const size_t idx = (reinterpret_cast<uintptr_t>(ptr) - reinterpret_cast<uintptr_t>(this)) / TlsHeapStatic::PageSize; + if (idx < m_span_table.total_pages) { + if (ptr != nullptr) { + std::scoped_lock lk(m_lock); + Span *span = GetSpanFromPointer(std::addressof(m_span_table), ptr); + if (span != nullptr) { + AMS_ASSERT(span->page_class == m_span_table.pageclass_cache[idx]); + } else { + AMS_ASSERT(span != nullptr); + } + } + return m_span_table.pageclass_cache[idx]; + } else { + /* TODO: Handle error? */ + return -1; + } + } + + errno_t GetColor(const void *ptr, int *out) { + if (out == nullptr) { + return EINVAL; + } + + std::scoped_lock lk(m_lock); + if (Span *span = GetSpanFromPointer(std::addressof(m_span_table), ptr); span != nullptr && !span->page_class) { + *out = (span->aux.large.color[0] << 0) | (span->aux.large.color[1] << 8) | (span->aux.large.color[2] << 16); + return 0; + } else { + return EINVAL; + } + } + + errno_t SetColor(const void *ptr, int color) { + std::scoped_lock lk(m_lock); + if (Span *span = GetSpanFromPointer(std::addressof(m_span_table), ptr); span != nullptr && !span->page_class) { + span->aux.large.color[0] = (color >> 0) & 0xFF; + span->aux.large.color[1] = (color >> 8) & 0xFF; + span->aux.large.color[2] = (color >> 16) & 0xFF; + return 0; + } else { + return EINVAL; + } + } + + errno_t GetMappedMemStats(size_t *out_free_size, size_t *out_max_allocatable_size) { + std::scoped_lock lk(m_lock); + + return this->GetMappedMemStatsImpl(out_free_size, out_max_allocatable_size); + } + + errno_t GetMemStats(TlsHeapMemStats *out) { + std::scoped_lock lk(m_lock); + + return this->GetMemStatsImpl(out); + } + + errno_t GetName(const void *ptr, char *dst, size_t dst_size) { + std::scoped_lock lk(m_lock); + if (Span *span = GetSpanFromPointer(std::addressof(m_span_table), ptr); span != nullptr && !span->page_class) { + util::Strlcpy(dst, span->aux.large.name, dst_size); + return 0; + } else { + return EINVAL; + } + } + + errno_t SetName(const void *ptr, const char *name) { + std::scoped_lock lk(m_lock); + if (Span *span = GetSpanFromPointer(std::addressof(m_span_table), ptr); span != nullptr && !span->page_class) { + util::Strlcpy(span->aux.large.name, name, sizeof(span->aux.large.name)); + return 0; + } else { + return EINVAL; + } + } + + size_t GetTotalHeapSize() const { + return m_span_table.total_pages * TlsHeapStatic::PageSize; + } + + errno_t UncacheLargeMemory(void *ptr) { + if (TlsHeapStatic::IsPageAligned(ptr)) { + std::scoped_lock lk(m_lock); + if (Span *span = GetSpanFromPointer(std::addressof(m_span_table), ptr); span != nullptr) { + this->FreePagesImpl(span); + return 0; + } else { + return EFAULT; + } + } else { + return EFAULT; + } + } + + errno_t UncacheSmallMemory(void *ptr) { + std::scoped_lock lk(m_lock); + return this->UncacheSmallMemoryImpl(ptr); + } + + errno_t UncacheSmallMemoryList(TlsHeapCache *cache, void *ptr) { + std::scoped_lock lk(m_lock); + + while (true) { + if (ptr == nullptr) { + return 0; + } + ptr = cache->ManglePointer(ptr); + void *next = *reinterpret_cast<void **>(ptr); + if (auto err = this->UncacheSmallMemoryImpl(ptr); err != 0) { + return err; + } + ptr = next; + } + } + + errno_t WalkAllocatedPointers(HeapWalkCallback callback, void *user_data) { + /* Explicitly handle locking, as we will release the lock during callback. */ + m_lock.lock(); + ON_SCOPE_EXIT { m_lock.unlock(); }; + + return this->WalkAllocatedPointersImpl(callback, user_data); + } + private: + SpanPage *AllocateSpanPage(); + Span *AllocateSpanFromSpanPage(SpanPage *sp); + + Span *SplitSpan(Span *span, size_t num_pages, Span *new_span); + void MergeFreeSpans(Span *span, Span *span_to_merge, uintptr_t start); + + bool DestroySpanPageIfEmpty(SpanPage *sp, bool full); + Span *GetFirstSpan() const; + Span *MakeFreeSpan(size_t num_pages); + Span *SearchFreeSpan(size_t num_pages) const; + + void FreeSpanToSpanPage(Span *span, SpanPage *sp); + void FreeSpanToSpanPage(Span *span); + + void MergeIntoFreeList(Span *&span); + + errno_t AllocatePhysical(void *start, size_t size); + errno_t FreePhysical(void *start, size_t size); + private: + Span *AllocatePagesImpl(size_t num_pages); + Span *AllocatePagesWithBigAlignImpl(size_t num_pages, size_t align); + void FreePagesImpl(Span *span); + + void *CacheSmallMemoryImpl(size_t cls, size_t align, bool for_system); + errno_t UncacheSmallMemoryImpl(void *ptr); + + size_t CacheSmallMemoryListImpl(TlsHeapCache *cache, size_t *cls, size_t count, void **p, s32 cpu_id, size_t align); + + errno_t WalkAllocatedPointersImpl(HeapWalkCallback callback, void *user_data); + + errno_t GetMappedMemStatsImpl(size_t *out_free_size, size_t *out_max_allocatable_size); + errno_t GetMemStatsImpl(TlsHeapMemStats *out); + + void DumpImpl(DumpMode dump_mode, int fd, bool json); + private: + size_t FreeListFirstNonEmpty(size_t start) const { + if (start < FreeListCount) { + for (size_t i = FreeListAvailableIndex(start); i < util::size(m_freelists_bitmap); i++) { + const FreeListAvailableWord masked = m_freelists_bitmap[i] & ~(FreeListAvailableMask(start) - 1); + if (masked) { + const size_t b = __builtin_ctzll(masked); + const size_t res = i * BITSIZEOF(FreeListAvailableWord) + b; + AMS_ASSERT(res < FreeListCount); + return res; + } + start = (i + 1) * BITSIZEOF(FreeListAvailableWord); + } + } + return FreeListCount; + } + + ALWAYS_INLINE void AddToFreeBlockList(Span *span) { + AMS_ASSERT(GetSpanPageSpan(GetSpanPage(span)) != span); + AMS_ASSERT(span->status == Span::Status_InFreeList); + const size_t which = std::min(span->num_pages, FreeListCount) - 1; + ListInsertAfter(std::addressof(m_freelists[which]), span); + m_freelists_bitmap[FreeListAvailableIndex(which)] |= FreeListAvailableMask(which); + } + + ALWAYS_INLINE void RemoveFromFreeBlockList(Span *span) { + const size_t which = std::min(span->num_pages, FreeListCount) - 1; + ListRemoveSelf(span); + if (!ListGetNext(std::addressof(m_freelists[which]))) { + m_freelists_bitmap[FreeListAvailableIndex(which)] &= ~FreeListAvailableMask(which); + } + } + + Span *AllocateSpanStruct() { + SpanPage *sp = ListGetNext(std::addressof(m_spanpage_list)); + while (sp && (sp->info.is_sticky || !CanAllocateSpan(sp))) { + sp = ListGetNext(sp); + } + + if (sp == nullptr) { + sp = this->AllocateSpanPage(); + } + + if (sp != nullptr) { + return this->AllocateSpanFromSpanPage(sp); + } else { + return nullptr; + } + } + + s32 CallWalkCallback(HeapWalkCallback callback, void *ptr, size_t size, void *user_data) { + m_lock.unlock(); + int res = callback(ptr, size, user_data); + m_lock.lock(); + if (res) { + return 0; + } else { + return -1; + } + } + }; + + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_tls_heap_static.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_tls_heap_static.hpp new file mode 100644 index 00000000..abf918aa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/heap/mem_impl_heap_tls_heap_static.hpp @@ -0,0 +1,210 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "mem_impl_heap_platform.hpp" + +namespace ams::mem::impl::heap { + + class TlsHeapStatic { + public: + struct ClassInfo { + u16 num_pages; + u16 chunk_size; + }; + + static constexpr size_t NumClassInfo = 57; + + static constexpr size_t MaxSizeWithClass = 0xC00; + static constexpr size_t ChunkGranularity = 0x10; + static constexpr size_t PageSize = 4_KB; + static constexpr size_t PhysicalPageSize = 256_KB; + public: + static constexpr inline std::array<ClassInfo, NumClassInfo> ClassInfos = { + ClassInfo{ .num_pages = 0, .chunk_size = 0x000, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x010, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x020, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x030, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x040, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x050, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x060, }, + ClassInfo{ .num_pages = 2, .chunk_size = 0x070, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x080, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x090, }, + ClassInfo{ .num_pages = 2, .chunk_size = 0x0A0, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x0B0, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x0C0, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x0D0, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x0E0, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x0F0, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x100, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x110, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x120, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x130, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x140, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x150, }, + ClassInfo{ .num_pages = 2, .chunk_size = 0x160, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x170, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x180, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x190, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x1A0, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x1B0, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x1C0, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x1D0, }, + ClassInfo{ .num_pages = 2, .chunk_size = 0x1E0, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x200, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x210, }, + ClassInfo{ .num_pages = 2, .chunk_size = 0x220, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x240, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x260, }, + ClassInfo{ .num_pages = 2, .chunk_size = 0x270, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x280, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x2A0, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x2D0, }, + ClassInfo{ .num_pages = 2, .chunk_size = 0x2E0, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x300, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x330, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x360, }, + ClassInfo{ .num_pages = 2, .chunk_size = 0x380, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x3B0, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x400, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x450, }, + ClassInfo{ .num_pages = 2, .chunk_size = 0x490, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x4C0, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x550, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x600, }, + ClassInfo{ .num_pages = 2, .chunk_size = 0x660, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x6D0, }, + ClassInfo{ .num_pages = 1, .chunk_size = 0x800, }, + ClassInfo{ .num_pages = 3, .chunk_size = 0x990, }, + ClassInfo{ .num_pages = 2, .chunk_size = 0xAA0, }, + }; + + static constexpr inline std::array<size_t, MaxSizeWithClass / ChunkGranularity> SizeToClass = [] { + std::array<size_t, MaxSizeWithClass / ChunkGranularity> arr = {}; + arr[0] = 1; + for (size_t i = 1; i < arr.size(); i++) { + const size_t cur_size = i * ChunkGranularity; + for (size_t j = 0; j < ClassInfos.size(); j++) { + if (ClassInfos[j].chunk_size >= cur_size) { + arr[i] = j; + break; + } + } + } + return arr; + }(); + public: + static constexpr ALWAYS_INLINE size_t GetClassFromSize(size_t size) { + AMS_ASSERT(size <= MaxSize); + const size_t idx = util::AlignUp(size, ChunkGranularity) / ChunkGranularity; + if (idx < MaxSizeWithClass / ChunkGranularity) { + return SizeToClass[idx]; + } else { + return 0; + } + } + + static constexpr ALWAYS_INLINE size_t GetRealSizeFromSizeAndAlignment(size_t size, size_t align) { + AMS_ASSERT(size <= MaxSize); + const size_t idx = util::AlignUp(size, ChunkGranularity) / ChunkGranularity; + if (size == 0 || idx >= MaxSizeWithClass / ChunkGranularity) { + return size; + } + const auto cls = SizeToClass[idx]; + if (!cls) { + return PageSize; + } + AMS_ASSERT(align != 0); + const size_t mask = align - 1; + for (auto i = cls; i < ClassInfos.size(); i++) { + if ((ClassInfos[i].chunk_size & mask) == 0) { + return ClassInfos[i].chunk_size; + } + } + return PageSize; + } + + static constexpr ALWAYS_INLINE bool IsPageAligned(uintptr_t ptr) { + return util::IsAligned(ptr, PageSize); + } + + static ALWAYS_INLINE bool IsPageAligned(const void *ptr) { + return IsPageAligned(reinterpret_cast<uintptr_t>(ptr)); + } + + static constexpr ALWAYS_INLINE size_t GetPageIndex(uintptr_t ptr) { + return ptr / PageSize; + } + + static constexpr ALWAYS_INLINE size_t GetPhysicalPageIndex(uintptr_t ptr) { + return ptr / PhysicalPageSize; + } + + static constexpr ALWAYS_INLINE uintptr_t AlignUpPage(uintptr_t ptr) { + return util::AlignUp(ptr, PageSize); + } + + template<typename T> + static ALWAYS_INLINE T *AlignUpPage(T *ptr) { + static_assert(util::is_pod<T>::value); + static_assert(util::IsAligned(PageSize, alignof(T))); + return reinterpret_cast<T *>(AlignUpPage(reinterpret_cast<uintptr_t>(ptr))); + } + + static constexpr ALWAYS_INLINE uintptr_t AlignDownPage(uintptr_t ptr) { + return util::AlignDown(ptr, PageSize); + } + + template<typename T> + static ALWAYS_INLINE T *AlignDownPage(T *ptr) { + static_assert(util::is_pod<T>::value); + static_assert(util::IsAligned(PageSize, alignof(T))); + return reinterpret_cast<T *>(AlignDownPage(reinterpret_cast<uintptr_t>(ptr))); + } + + static constexpr ALWAYS_INLINE uintptr_t AlignUpPhysicalPage(uintptr_t ptr) { + return util::AlignUp(ptr, PhysicalPageSize); + } + + template<typename T> + static ALWAYS_INLINE T *AlignUpPhysicalPage(T *ptr) { + static_assert(util::is_pod<T>::value); + static_assert(util::IsAligned(PhysicalPageSize, alignof(T))); + return reinterpret_cast<T *>(AlignUpPhysicalPage(reinterpret_cast<uintptr_t>(ptr))); + } + + static constexpr ALWAYS_INLINE uintptr_t AlignDownPhysicalPage(uintptr_t ptr) { + return util::AlignDown(ptr, PhysicalPageSize); + } + + template<typename T> + static ALWAYS_INLINE T *AlignDownPhysicalPage(T *ptr) { + static_assert(util::is_pod<T>::value); + static_assert(util::IsAligned(PhysicalPageSize, alignof(T))); + return reinterpret_cast<T *>(AlignDownPhysicalPage(reinterpret_cast<uintptr_t>(ptr))); + } + + static constexpr ALWAYS_INLINE size_t GetChunkSize(size_t cls) { + return ClassInfos[cls].chunk_size; + } + + static constexpr ALWAYS_INLINE size_t GetNumPages(size_t cls) { + return ClassInfos[cls].num_pages; + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/mem_impl_platform.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/mem_impl_platform.hpp new file mode 100644 index 00000000..492cafdb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/mem_impl_platform.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::mem::impl { + + enum Prot { + Prot_none = (0 << 0), + Prot_read = (1 << 0), + Prot_write = (1 << 1), + Prot_exec = (1 << 2), + }; + + errno_t virtual_alloc(void **ptr, size_t size); + errno_t virtual_free(void *ptr, size_t size); + errno_t physical_alloc(void *ptr, size_t size, Prot prot); + errno_t physical_free(void *ptr, size_t size); + + size_t strlcpy(char *dst, const char *src, size_t size); + + errno_t gen_random(void *dst, size_t dst_size); + + errno_t epochtime(s64 *dst); + + errno_t getcpu(s32 *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/mem_impl_platform.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/mem_impl_platform.os.horizon.cpp new file mode 100644 index 00000000..e13d3096 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/mem_impl_platform.os.horizon.cpp @@ -0,0 +1,154 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "mem_impl_platform.hpp" + +namespace ams::mem::impl { + + namespace { + + ALWAYS_INLINE bool IsVirtualAddressMemoryEnabled() { + AMS_FUNCTION_LOCAL_STATIC(bool, s_virt_mem_enabled, os::IsVirtualAddressMemoryEnabled()); + return s_virt_mem_enabled; + } + + ALWAYS_INLINE errno_t ConvertResult(Result result) { + /* TODO: Actually implement this in a meaningful way. */ + if (R_FAILED(result)) { + return EINVAL; + } + return 0; + } + + ALWAYS_INLINE os::MemoryPermission ConvertToOsPermission(Prot prot) { + static_assert(static_cast<int>(Prot_read) == static_cast<int>(os::MemoryPermission_ReadOnly)); + static_assert(static_cast<int>(Prot_write) == static_cast<int>(os::MemoryPermission_WriteOnly)); + static_assert((util::ToUnderlying(Prot_read) | util::ToUnderlying(Prot_write)) == util::ToUnderlying(os::MemoryPermission_ReadWrite)); + return static_cast<os::MemoryPermission>(prot & (Prot_read | Prot_write)); + } + + } + + errno_t virtual_alloc(void **ptr, size_t size) { + /* Ensure size isn't too large. */ + if (size > mem::impl::MaxSize) { + return EINVAL; + } + + /* Allocate virtual memory. */ + uintptr_t addr; + if (IsVirtualAddressMemoryEnabled()) { + /* TODO: Support virtual address memory. */ + AMS_ABORT("Virtual address memory not supported yet"); + } else { + if (auto err = ConvertResult(os::AllocateMemoryBlock(std::addressof(addr), util::AlignUp(size, os::MemoryBlockUnitSize))); err != 0) { + return err; + } + + os::SetMemoryPermission(addr, size, os::MemoryPermission_None); + } + + /* Set the output pointer. */ + *ptr = reinterpret_cast<void *>(addr); + + return 0; + } + + errno_t virtual_free(void *ptr, size_t size) { + /* Ensure size isn't zero. */ + if (size == 0) { + return EINVAL; + } + + if (IsVirtualAddressMemoryEnabled()) { + /* TODO: Support virtual address memory. */ + AMS_ABORT("Virtual address memory not supported yet"); + } else { + os::FreeMemoryBlock(reinterpret_cast<uintptr_t>(ptr), util::AlignUp(size, os::MemoryBlockUnitSize)); + } + + return 0; + } + + errno_t physical_alloc(void *ptr, size_t size, Prot prot) { + /* Detect empty allocation. */ + const uintptr_t aligned_start = util::AlignDown(reinterpret_cast<uintptr_t>(ptr), os::MemoryPageSize); + const uintptr_t aligned_end = util::AlignUp(reinterpret_cast<uintptr_t>(ptr) + size, os::MemoryPageSize); + const size_t aligned_size = aligned_end - aligned_start; + if (aligned_end <= aligned_start) { + return 0; + } + + if (IsVirtualAddressMemoryEnabled()) { + /* TODO: Support virtual address memory. */ + AMS_ABORT("Virtual address memory not supported yet"); + } else { + os::SetMemoryPermission(aligned_start, aligned_size, ConvertToOsPermission(prot)); + } + + return 0; + } + + errno_t physical_free(void *ptr, size_t size) { + /* Detect empty allocation. */ + const uintptr_t aligned_start = util::AlignDown(reinterpret_cast<uintptr_t>(ptr), os::MemoryPageSize); + const uintptr_t aligned_end = util::AlignUp(reinterpret_cast<uintptr_t>(ptr) + size, os::MemoryPageSize); + const size_t aligned_size = aligned_end - aligned_start; + if (aligned_end <= aligned_start) { + return 0; + } + + if (IsVirtualAddressMemoryEnabled()) { + /* TODO: Support virtual address memory. */ + AMS_ABORT("Virtual address memory not supported yet"); + } else { + os::SetMemoryPermission(aligned_start, aligned_size, os::MemoryPermission_None); + } + + return 0; + } + + size_t strlcpy(char *dst, const char *src, size_t size) { + const size_t src_size = std::strlen(src); + if (src_size >= size) { + if (size) { + std::memcpy(dst, src, size - 1); + dst[size - 1] = 0; + } + } else { + std::memcpy(dst, src, src_size + 1); + } + return src_size; + } + + errno_t gen_random(void *dst, size_t dst_size) { + os::GenerateRandomBytes(dst, dst_size); + return 0; + } + + errno_t epochtime(s64 *dst) { + /* TODO: What is this calc? */ + auto ts = os::ConvertToTimeSpan(os::GetSystemTick()); + *dst = (ts.GetNanoSeconds() / INT64_C(100)) + INT64_C(0x8A09F909AE60000); + return 0; + } + + errno_t getcpu(s32 *out) { + *out = os::GetCurrentCoreNumber(); + return 0; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/mem_impl_platform.os.linux.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/mem_impl_platform.os.linux.cpp new file mode 100644 index 00000000..b7dd6b53 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/mem_impl_platform.os.linux.cpp @@ -0,0 +1,142 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <sys/mman.h> +#include "mem_impl_platform.hpp" + +namespace ams::mem::impl { + + errno_t virtual_alloc(void **ptr, size_t size) { + /* Ensure size is non-zero. */ + if (size == 0) { + return EINVAL; + } + + /* Allocate virtual memory. */ + if (const auto address = ::mmap(nullptr, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); address != MAP_FAILED) { + *ptr = reinterpret_cast<void *>(address); + return 0; + } else { + return errno; + } + } + + errno_t virtual_free(void *ptr, size_t size) { + /* Ensure pointer/size aren't zero. */ + if (ptr == nullptr || size == 0) { + return EINVAL; + } + + /* Free the memory. */ + if (::munmap(ptr, size) == 0) { + return 0; + } else { + return errno; + } + } + + errno_t physical_alloc(void *ptr, size_t size, Prot prot) { + /* Ensure pointer isn't zero. */ + if (ptr == nullptr) { + return EINVAL; + } + + /* Convert the protection. */ + int native_prot; + switch (util::ToUnderlying(prot)) { + case Prot_none: + native_prot = PROT_NONE; + break; + case Prot_read: + native_prot = PROT_READ; + break; + case Prot_write: + case Prot_write | Prot_read: + native_prot = PROT_READ | PROT_WRITE; + break; + case Prot_exec: + native_prot = PROT_EXEC; + break; + case Prot_exec | Prot_read: + native_prot = PROT_READ | PROT_EXEC; + break; + case Prot_exec | Prot_write: + case Prot_exec | Prot_write | Prot_read: + native_prot = PROT_READ | PROT_WRITE | PROT_EXEC; + break; + default: + return EINVAL; + } + + if (::mprotect(ptr, size, native_prot)) { + return 0; + } else { + return errno; + } + } + + errno_t physical_free(void *ptr, size_t size) { + /* Detect empty allocation. */ + const uintptr_t aligned_start = util::AlignUp(reinterpret_cast<uintptr_t>(ptr), os::MemoryPageSize); + const uintptr_t aligned_end = util::AlignDown(reinterpret_cast<uintptr_t>(ptr) + size, os::MemoryPageSize); + const size_t aligned_size = aligned_end - aligned_start; + if (aligned_end <= aligned_start) { + return 0; + } + + if (::mprotect(reinterpret_cast<void *>(aligned_start), aligned_size, PROT_NONE)) { + return 0; + } else { + return errno; + } + } + + size_t strlcpy(char *dst, const char *src, size_t size) { + const size_t src_size = std::strlen(src); + if (src_size >= size) { + if (size) { + std::memcpy(dst, src, size - 1); + dst[size - 1] = 0; + } + } else { + std::memcpy(dst, src, src_size + 1); + } + return src_size; + } + + errno_t gen_random(void *dst, size_t dst_size) { + /* TODO: CryptGenRandom? */ + os::GenerateRandomBytes(dst, dst_size); + return 0; + } + + errno_t epochtime(s64 *dst) { + /* TODO: Something more sane than this. */ + auto ts = os::ConvertToTimeSpan(os::GetSystemTick()); + *dst = (ts.GetNanoSeconds() / INT64_C(100)) + INT64_C(0x8A09F909AE60000); + return 0; + } + + errno_t getcpu(s32 *out) { + if (const auto core = sched_getcpu(); core >= 0) { + *out = core; + return 0; + } else { + return errno; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/mem_impl_platform.os.macos.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/mem_impl_platform.os.macos.cpp new file mode 100644 index 00000000..788b2618 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/mem_impl_platform.os.macos.cpp @@ -0,0 +1,177 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <sys/mman.h> +#include "mem_impl_platform.hpp" + +#if defined(ATMOSPHERE_ARCH_X64) +#include <cpuid.h> +#endif + +namespace ams::mem::impl { + + errno_t virtual_alloc(void **ptr, size_t size) { + /* Ensure size is non-zero. */ + if (size == 0) { + return EINVAL; + } + + /* Allocate virtual memory. */ + if (const auto address = ::mmap(nullptr, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); address != MAP_FAILED) { + *ptr = reinterpret_cast<void *>(address); + return 0; + } else { + return errno; + } + } + + errno_t virtual_free(void *ptr, size_t size) { + /* Ensure pointer/size aren't zero. */ + if (ptr == nullptr || size == 0) { + return EINVAL; + } + + /* Free the memory. */ + if (::munmap(ptr, size) == 0) { + return 0; + } else { + return errno; + } + } + + errno_t physical_alloc(void *ptr, size_t size, Prot prot) { + /* Ensure pointer isn't zero. */ + if (ptr == nullptr) { + return EINVAL; + } + + /* Convert the protection. */ + int native_prot; + switch (util::ToUnderlying(prot)) { + case Prot_none: + native_prot = PROT_NONE; + break; + case Prot_read: + native_prot = PROT_READ; + break; + case Prot_write: + case Prot_write | Prot_read: + native_prot = PROT_READ | PROT_WRITE; + break; + case Prot_exec: + native_prot = PROT_EXEC; + break; + case Prot_exec | Prot_read: + native_prot = PROT_READ | PROT_EXEC; + break; + case Prot_exec | Prot_write: + case Prot_exec | Prot_write | Prot_read: + native_prot = PROT_READ | PROT_WRITE | PROT_EXEC; + break; + default: + return EINVAL; + } + + if (::mprotect(ptr, size, native_prot)) { + return 0; + } else { + return errno; + } + } + + errno_t physical_free(void *ptr, size_t size) { + /* Detect empty allocation. */ + const uintptr_t aligned_start = util::AlignUp(reinterpret_cast<uintptr_t>(ptr), os::MemoryPageSize); + const uintptr_t aligned_end = util::AlignDown(reinterpret_cast<uintptr_t>(ptr) + size, os::MemoryPageSize); + const size_t aligned_size = aligned_end - aligned_start; + if (aligned_end <= aligned_start) { + return 0; + } + + if (::mprotect(reinterpret_cast<void *>(aligned_start), aligned_size, PROT_NONE)) { + return 0; + } else { + return errno; + } + } + + size_t strlcpy(char *dst, const char *src, size_t size) { + const size_t src_size = std::strlen(src); + if (src_size >= size) { + if (size) { + std::memcpy(dst, src, size - 1); + dst[size - 1] = 0; + } + } else { + std::memcpy(dst, src, src_size + 1); + } + return src_size; + } + + errno_t gen_random(void *dst, size_t dst_size) { + /* TODO: CryptGenRandom? */ + os::GenerateRandomBytes(dst, dst_size); + return 0; + } + + errno_t epochtime(s64 *dst) { + /* TODO: Something more sane than this. */ + auto ts = os::ConvertToTimeSpan(os::GetSystemTick()); + *dst = (ts.GetNanoSeconds() / INT64_C(100)) + INT64_C(0x8A09F909AE60000); + return 0; + } + + errno_t getcpu(s32 *out) { + if (__builtin_available(macOS 11.0, *)) { + /* On macOS 11.0+, we can use the exposed API they added. */ + size_t cpu_number = 0; + const auto res = pthread_cpu_number_np(std::addressof(cpu_number)); + AMS_ASSERT(res == 0); + AMS_UNUSED(res); + + *out = static_cast<s32>(cpu_number); + return 0; + } else { + #if defined(ATMOSPHERE_ARCH_X64) + { + uint32_t cpu_info[4]; + __cpuid_count(1, 0, cpu_info[0], cpu_info[1], cpu_info[2], cpu_info[3]); + + s32 core; + if ((cpu_info[3] & (1 << 9)) == 0) { + core = 0; + } else { + core = static_cast<s32>(cpu_info[1] >> 24); + } + + *out = core; + return 0; + } + #elif defined(ATMOSPHERE_ARCH_ARM64) + { + /* Read from TSD, per https://github.com/apple-oss-distributions/xnu/blob/e6231be02a03711ca404e5121a151b24afbff733/libsyscall/os/tsd.h#L68 */ + uint64_t p; + __asm__ __volatile__ ("mrs %0, TPIDR_EL0" : "=r" (p)); + *out = static_cast<s32>(p & 0xF); + return 0; + } + #else + AMS_ABORT("Unknown architecture to get current cpu on fallback path on macOS"); + #endif + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/mem_impl_platform.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/mem_impl_platform.os.windows.cpp new file mode 100644 index 00000000..cfa31163 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/impl/mem_impl_platform.os.windows.cpp @@ -0,0 +1,159 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <stratosphere/windows.hpp> +#include <VersionHelpers.h> +#include "mem_impl_platform.hpp" + +namespace ams::mem::impl { + + namespace { + + errno_t ConvertGetLastError(DWORD error, errno_t fallback = EIO) { + /* TODO: Implement this? */ + AMS_UNUSED(error); + return fallback; + } + + } + + errno_t virtual_alloc(void **ptr, size_t size) { + /* Ensure size is non-zero. */ + if (size == 0) { + return EINVAL; + } + + /* Allocate virtual memory. */ + if (void *mem = ::VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE); mem != nullptr) { + *ptr = mem; + return 0; + } else { + return ConvertGetLastError(::GetLastError()); + } + } + + errno_t virtual_free(void *ptr, size_t size) { + /* Ensure pointer/size aren't zero. */ + if (ptr == nullptr || size == 0) { + return EINVAL; + } + + /* Free the memory. */ + if (::VirtualFree(ptr, 0, MEM_RELEASE)) { + return 0; + } else { + return ConvertGetLastError(::GetLastError()); + } + } + + errno_t physical_alloc(void *ptr, size_t size, Prot prot) { + /* Ensure pointer isn't zero. */ + if (ptr == nullptr) { + return EINVAL; + } + + /* Convert the protection. */ + DWORD fl_protect; + switch (util::ToUnderlying(prot)) { + case Prot_none: + fl_protect = PAGE_NOACCESS; + break; + case Prot_read: + fl_protect = PAGE_READONLY; + break; + case Prot_write: + case Prot_write | Prot_read: + fl_protect = PAGE_READWRITE; + break; + case Prot_exec: + fl_protect = PAGE_EXECUTE; + break; + case Prot_exec | Prot_read: + fl_protect = PAGE_EXECUTE_READ; + break; + case Prot_exec | Prot_write: + case Prot_exec | Prot_write | Prot_read: + fl_protect = PAGE_EXECUTE_READWRITE; + break; + default: + return EINVAL; + } + + if (::VirtualAlloc(ptr, size, MEM_COMMIT, fl_protect)) { + return 0; + } else { + return ConvertGetLastError(::GetLastError()); + } + } + + errno_t physical_free(void *ptr, size_t size) { + /* Detect empty allocation. */ + const uintptr_t aligned_start = util::AlignUp(reinterpret_cast<uintptr_t>(ptr), os::MemoryPageSize); + const uintptr_t aligned_end = util::AlignDown(reinterpret_cast<uintptr_t>(ptr) + size, os::MemoryPageSize); + const size_t aligned_size = aligned_end - aligned_start; + if (aligned_end <= aligned_start) { + return 0; + } + + if (::VirtualFree(reinterpret_cast<LPVOID>(aligned_start), aligned_size, MEM_DECOMMIT)) { + return 0; + } else { + return ConvertGetLastError(::GetLastError()); + } + } + + size_t strlcpy(char *dst, const char *src, size_t size) { + const size_t src_size = std::strlen(src); + if (src_size >= size) { + if (size) { + std::memcpy(dst, src, size - 1); + dst[size - 1] = 0; + } + } else { + std::memcpy(dst, src, src_size + 1); + } + return src_size; + } + + errno_t gen_random(void *dst, size_t dst_size) { + /* TODO: CryptGenRandom? */ + os::GenerateRandomBytes(dst, dst_size); + return 0; + } + + errno_t epochtime(s64 *dst) { + /* TODO: Something more sane than this. */ + auto ts = os::ConvertToTimeSpan(os::GetSystemTick()); + *dst = (ts.GetNanoSeconds() / INT64_C(100)) + INT64_C(0x8A09F909AE60000); + return 0; + } + + errno_t getcpu(s32 *out) { + AMS_FUNCTION_LOCAL_STATIC(bool, s_has_processor_ex, ::IsWindows7OrGreater()); + + if (s_has_processor_ex) { + PROCESSOR_NUMBER pnum; + ::GetCurrentProcessorNumberEx(std::addressof(pnum)); + + *out = (pnum.Group << 8) | pnum.Number; + } else { + *out = ::GetCurrentProcessorNumber(); + } + + return 0; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/mem_standard_allocator.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/mem_standard_allocator.cpp new file mode 100644 index 00000000..65712ba9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mem/mem_standard_allocator.cpp @@ -0,0 +1,346 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/mem_impl_platform.hpp" +#include "impl/heap/mem_impl_heap_tls_heap_static.hpp" +#include "impl/heap/mem_impl_heap_tls_heap_cache.hpp" +#include "impl/heap/mem_impl_heap_tls_heap_central.hpp" + +namespace ams::mem { + + constexpr inline size_t DefaultAlignment = alignof(std::max_align_t); + constexpr inline size_t MinimumAllocatorSize = 16_KB; + + namespace { + + void ThreadDestroy(uintptr_t arg) { + if (arg) { + reinterpret_cast<impl::heap::TlsHeapCache *>(arg)->Finalize(); + } + } + + ALWAYS_INLINE impl::heap::CentralHeap *GetCentral(const impl::InternalCentralHeapStorage *storage) { + return reinterpret_cast<impl::heap::CentralHeap *>(const_cast<impl::InternalCentralHeapStorage *>(storage)); + } + + ALWAYS_INLINE impl::heap::CentralHeap *GetCentral(const impl::InternalCentralHeapStorage &storage) { + return GetCentral(std::addressof(storage)); + } + + ALWAYS_INLINE void GetCache(impl::heap::CentralHeap *central, os::TlsSlot slot) { + impl::heap::CachedHeap tmp_cache; + + if (central->MakeCache(std::addressof(tmp_cache))) { + impl::heap::TlsHeapCache *cache = tmp_cache.Release(); + os::SetTlsValue(slot, reinterpret_cast<uintptr_t>(cache)); + } + } + + struct InternalHash { + size_t allocated_count; + size_t allocated_size; + crypto::Sha1Generator sha1; + }; + + int InternalHashCallback(void *ptr, size_t size, void *user_data) { + InternalHash *hash = reinterpret_cast<InternalHash *>(user_data); + hash->sha1.Update(reinterpret_cast<void *>(std::addressof(ptr)), sizeof(ptr)); + hash->sha1.Update(reinterpret_cast<void *>(std::addressof(size)), sizeof(size)); + hash->allocated_count++; + hash->allocated_size += size; + return 1; + } + + } + + StandardAllocator::StandardAllocator() : m_initialized(false), m_enable_thread_cache(false), m_unused(0) { + static_assert(sizeof(impl::heap::CentralHeap) <= sizeof(m_central_heap_storage)); + std::construct_at(GetCentral(m_central_heap_storage)); + } + + StandardAllocator::StandardAllocator(void *mem, size_t size) : StandardAllocator() { + this->Initialize(mem, size); + } + + StandardAllocator::StandardAllocator(void *mem, size_t size, bool enable_cache) : StandardAllocator() { + this->Initialize(mem, size, enable_cache); + } + + void StandardAllocator::Initialize(void *mem, size_t size) { + this->Initialize(mem, size, false); + } + + void StandardAllocator::Initialize(void *mem, size_t size, bool enable_cache) { + AMS_ABORT_UNLESS(!m_initialized); + + const uintptr_t aligned_start = util::AlignUp(reinterpret_cast<uintptr_t>(mem), impl::heap::TlsHeapStatic::PageSize); + const uintptr_t aligned_end = util::AlignDown(reinterpret_cast<uintptr_t>(mem) + size, impl::heap::TlsHeapStatic::PageSize); + const size_t aligned_size = aligned_end - aligned_start; + + if (mem == nullptr) { + AMS_ABORT_UNLESS(os::IsVirtualAddressMemoryEnabled()); + AMS_ABORT_UNLESS(GetCentral(m_central_heap_storage)->Initialize(nullptr, size, 0) == 0); + } else { + AMS_ABORT_UNLESS(aligned_start < aligned_end); + AMS_ABORT_UNLESS(aligned_size >= MinimumAllocatorSize); + AMS_ABORT_UNLESS(GetCentral(m_central_heap_storage)->Initialize(reinterpret_cast<void *>(aligned_start), aligned_size, 0) == 0); + } + + m_enable_thread_cache = enable_cache; + if (m_enable_thread_cache) { + R_ABORT_UNLESS(os::AllocateTlsSlot(std::addressof(m_tls_slot), ThreadDestroy)); + } + + m_initialized = true; + } + + void StandardAllocator::Finalize() { + AMS_ABORT_UNLESS(m_initialized); + + if (m_enable_thread_cache) { + os::FreeTlsSlot(m_tls_slot); + } + + GetCentral(m_central_heap_storage)->Finalize(); + + m_initialized = false; + } + + void *StandardAllocator::Allocate(size_t size) { + AMS_ASSERT(m_initialized); + return this->Allocate(size, DefaultAlignment); + } + + void *StandardAllocator::Allocate(size_t size, size_t alignment) { + AMS_ASSERT(m_initialized); + + impl::heap::TlsHeapCache *heap_cache = nullptr; + if (m_enable_thread_cache) { + heap_cache = reinterpret_cast<impl::heap::TlsHeapCache *>(os::GetTlsValue(m_tls_slot)); + if (!heap_cache) { + GetCache(GetCentral(m_central_heap_storage), m_tls_slot); + heap_cache = reinterpret_cast<impl::heap::TlsHeapCache *>(os::GetTlsValue(m_tls_slot)); + } + } + + void *ptr = nullptr; + if (heap_cache) { + ptr = heap_cache->Allocate(size, alignment); + if (ptr) { + return ptr; + } + + impl::heap::CachedHeap cache; + cache.Reset(heap_cache); + cache.Query(impl::AllocQuery_FinalizeCache); + os::SetTlsValue(m_tls_slot, 0); + } + + return GetCentral(m_central_heap_storage)->Allocate(size, alignment); + } + + void StandardAllocator::Free(void *ptr) { + AMS_ASSERT(m_initialized); + + if (ptr == nullptr) { + return; + } + + if (m_enable_thread_cache) { + impl::heap::TlsHeapCache *heap_cache = reinterpret_cast<impl::heap::TlsHeapCache *>(os::GetTlsValue(m_tls_slot)); + if (heap_cache) { + heap_cache->Free(ptr); + return; + } + } + + const auto err = GetCentral(m_central_heap_storage)->Free(ptr); + AMS_ASSERT(err == 0); + AMS_UNUSED(err); + } + + void *StandardAllocator::Reallocate(void *ptr, size_t new_size) { + AMS_ASSERT(m_initialized); + + if (new_size > impl::MaxSize) { + return nullptr; + } + + if (ptr == nullptr) { + return this->Allocate(new_size); + } + + if (new_size == 0) { + this->Free(ptr); + return nullptr; + } + + size_t aligned_new_size = util::AlignUp(new_size, DefaultAlignment); + + + impl::heap::TlsHeapCache *heap_cache = nullptr; + if (m_enable_thread_cache) { + heap_cache = reinterpret_cast<impl::heap::TlsHeapCache *>(os::GetTlsValue(m_tls_slot)); + if (!heap_cache) { + GetCache(GetCentral(m_central_heap_storage), m_tls_slot); + heap_cache = reinterpret_cast<impl::heap::TlsHeapCache *>(os::GetTlsValue(m_tls_slot)); + } + } + + void *p = nullptr; + impl::errno_t err; + if (heap_cache) { + err = heap_cache->Reallocate(ptr, aligned_new_size, std::addressof(p)); + } else { + err = GetCentral(m_central_heap_storage)->Reallocate(ptr, aligned_new_size, std::addressof(p)); + } + + if (err == 0) { + return p; + } else { + return nullptr; + } + } + + size_t StandardAllocator::Shrink(void *ptr, size_t new_size) { + AMS_ASSERT(m_initialized); + + if (m_enable_thread_cache) { + impl::heap::TlsHeapCache *heap_cache = reinterpret_cast<impl::heap::TlsHeapCache *>(os::GetTlsValue(m_tls_slot)); + if (heap_cache) { + if (heap_cache->Shrink(ptr, new_size) == 0) { + return heap_cache->GetAllocationSize(ptr); + } else { + return 0; + } + } + } + + if (GetCentral(m_central_heap_storage)->Shrink(ptr, new_size) == 0) { + return GetCentral(m_central_heap_storage)->GetAllocationSize(ptr); + } else { + return 0; + } + } + + void StandardAllocator::ClearThreadCache() const { + if (m_enable_thread_cache) { + impl::heap::TlsHeapCache *heap_cache = reinterpret_cast<impl::heap::TlsHeapCache *>(os::GetTlsValue(m_tls_slot)); + impl::heap::CachedHeap cache; + cache.Reset(heap_cache); + cache.Query(impl::AllocQuery_ClearCache); + cache.Release(); + } + } + + void StandardAllocator::CleanUpManagementArea() const { + AMS_ASSERT(m_initialized); + + const auto err = GetCentral(m_central_heap_storage)->Query(impl::AllocQuery_UnifyFreeList); + AMS_ASSERT(err == 0); + AMS_UNUSED(err); + } + + size_t StandardAllocator::GetSizeOf(const void *ptr) const { + AMS_ASSERT(m_initialized); + + if (!util::IsAligned(reinterpret_cast<uintptr_t>(ptr), DefaultAlignment)) { + return 0; + } + + impl::heap::TlsHeapCache *heap_cache = nullptr; + if (m_enable_thread_cache) { + heap_cache = reinterpret_cast<impl::heap::TlsHeapCache *>(os::GetTlsValue(m_tls_slot)); + if (!heap_cache) { + GetCache(GetCentral(m_central_heap_storage), m_tls_slot); + heap_cache = reinterpret_cast<impl::heap::TlsHeapCache *>(os::GetTlsValue(m_tls_slot)); + } + } + + if (heap_cache) { + return heap_cache->GetAllocationSize(ptr); + } else { + return GetCentral(m_central_heap_storage)->GetAllocationSize(ptr); + } + } + + size_t StandardAllocator::GetTotalFreeSize() const { + size_t size = 0; + + auto err = GetCentral(m_central_heap_storage)->Query(impl::AllocQuery_FreeSizeMapped, std::addressof(size)); + if (err != 0) { + err = GetCentral(m_central_heap_storage)->Query(impl::AllocQuery_FreeSize, std::addressof(size)); + } + AMS_ASSERT(err == 0); + + return size; + } + + size_t StandardAllocator::GetAllocatableSize() const { + size_t size = 0; + + auto err = GetCentral(m_central_heap_storage)->Query(impl::AllocQuery_MaxAllocatableSizeMapped, std::addressof(size)); + if (err != 0) { + err = GetCentral(m_central_heap_storage)->Query(impl::AllocQuery_MaxAllocatableSize, std::addressof(size)); + } + AMS_ASSERT(err == 0); + + return size; + } + + void StandardAllocator::WalkAllocatedBlocks(WalkCallback callback, void *user_data) const { + AMS_ASSERT(m_initialized); + this->ClearThreadCache(); + GetCentral(m_central_heap_storage)->WalkAllocatedPointers(callback, user_data); + } + + void StandardAllocator::Dump() const { + AMS_ASSERT(m_initialized); + + size_t tmp; + auto err = GetCentral(m_central_heap_storage)->Query(impl::AllocQuery_MaxAllocatableSizeMapped, std::addressof(tmp)); + + if (err == 0) { + GetCentral(m_central_heap_storage)->Query(impl::AllocQuery_Dump, impl::DumpMode_Spans | impl::DumpMode_Pointers, 1); + } else { + GetCentral(m_central_heap_storage)->Query(impl::AllocQuery_Dump, impl::DumpMode_All, 1); + } + } + + StandardAllocator::AllocatorHash StandardAllocator::Hash() const { + AMS_ASSERT(m_initialized); + + AllocatorHash alloc_hash; + { + char temp_hash[crypto::Sha1Generator::HashSize]; + InternalHash internal_hash; + internal_hash.allocated_count = 0; + internal_hash.allocated_size = 0; + internal_hash.sha1.Initialize(); + + this->WalkAllocatedBlocks(InternalHashCallback, reinterpret_cast<void *>(std::addressof(internal_hash))); + + alloc_hash.allocated_count = internal_hash.allocated_count; + alloc_hash.allocated_size = internal_hash.allocated_size; + + internal_hash.sha1.GetHash(temp_hash, sizeof(temp_hash)); + std::memcpy(std::addressof(alloc_hash.hash), temp_hash, sizeof(alloc_hash.hash)); + } + return alloc_hash; + } + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mitm/mitm_pm.os.horizon.c b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mitm/mitm_pm.os.horizon.c new file mode 100644 index 00000000..adfabd4f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mitm/mitm_pm.os.horizon.c @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#define NX_SERVICE_ASSUME_NON_DOMAIN +#include "../service_guard.h" +#include "mitm_pm.os.horizon.h" + +static Service g_amsMitmPmSrv; + +NX_GENERATE_SERVICE_GUARD(amsMitmPm); + +Result _amsMitmPmInitialize(void) { + return smGetService(&g_amsMitmPmSrv, "mitm:pm"); +} + +void _amsMitmPmCleanup(void) { + serviceClose(&g_amsMitmPmSrv); +} + +Service *amsMitmPmGetServiceSession(void) { + return &g_amsMitmPmSrv; +} + +Result amsMitmPmPrepareLaunchProgram(u64 *out, u64 program_id, const CfgOverrideStatus *status, bool is_application) { + const struct { + u8 is_application; + u64 program_id; + CfgOverrideStatus status; + } in = { is_application ? 1 : 0, program_id, *status }; + + return serviceDispatchInOut(&g_amsMitmPmSrv, 65000, in, *out); +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mitm/mitm_pm.os.horizon.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mitm/mitm_pm.os.horizon.h new file mode 100644 index 00000000..2d8da12c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mitm/mitm_pm.os.horizon.h @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <switch.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + u64 keys_down; + u64 flags; +} CfgOverrideStatus; + +Result amsMitmPmInitialize(void); +void amsMitmPmExit(void); +Service *amsMitmPmGetServiceSession(void); + +Result amsMitmPmPrepareLaunchProgram(u64 *out, u64 program_id, const CfgOverrideStatus *status, bool is_application); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mitm/mitm_pm_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mitm/mitm_pm_api.cpp new file mode 100644 index 00000000..71064cca --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/mitm/mitm_pm_api.cpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#if defined(ATMOSPHERE_OS_HORIZON) +#include "mitm_pm.os.horizon.h" +#endif + +namespace ams::mitm::pm { + + /* PM API. */ + #if defined(ATMOSPHERE_OS_HORIZON) + void Initialize() { + R_ABORT_UNLESS(amsMitmPmInitialize()); + } + + void Finalize() { + amsMitmPmExit(); + } + + Result PrepareLaunchProgram(u64 *out, ncm::ProgramId program_id, const cfg::OverrideStatus &status, bool is_application) { + static_assert(sizeof(status) == sizeof(CfgOverrideStatus), "CfgOverrideStatus definition!"); + R_RETURN(amsMitmPmPrepareLaunchProgram(out, program_id.value, reinterpret_cast<const CfgOverrideStatus *>(std::addressof(status)), is_application)); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_api.cpp new file mode 100644 index 00000000..684a55a7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_api.cpp @@ -0,0 +1,131 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ncm_content_manager_factory.hpp" +#include "ncm_remote_content_manager_impl.hpp" + +namespace ams::ncm { + + namespace { + + constinit sf::SharedPointer<IContentManager> g_content_manager; + + #if defined(ATMOSPHERE_OS_HORIZON) + constinit util::TypedStorage<sf::UnmanagedServiceObject<IContentManager, RemoteContentManagerImpl>> g_remote_manager_storage = {}; + #endif + + } + + void Initialize() { + AMS_ASSERT(g_content_manager == nullptr); + #if defined(ATMOSPHERE_OS_HORIZON) + util::ConstructAt(g_remote_manager_storage); + g_content_manager = util::GetReference(g_remote_manager_storage).GetShared(); + #else + g_content_manager = CreateDefaultContentManager(ContentManagerConfig{}); + #endif + } + + void Finalize() { + AMS_ASSERT(g_content_manager != nullptr); + g_content_manager.Reset(); + #if defined(ATMOSPHERE_OS_HORIZON) + util::DestroyAt(g_remote_manager_storage); + #endif + } + + void InitializeWithObject(sf::SharedPointer<IContentManager> manager_object) { + AMS_ASSERT(g_content_manager == nullptr); + g_content_manager = manager_object; + AMS_ASSERT(g_content_manager != nullptr); + } + + /* Service API. */ + Result CreateContentStorage(StorageId storage_id) { + R_RETURN(g_content_manager->CreateContentStorage(storage_id)); + } + + Result CreateContentMetaDatabase(StorageId storage_id) { + R_RETURN(g_content_manager->CreateContentMetaDatabase(storage_id)); + } + + Result VerifyContentStorage(StorageId storage_id) { + R_RETURN(g_content_manager->VerifyContentStorage(storage_id)); + } + + Result VerifyContentMetaDatabase(StorageId storage_id) { + R_RETURN(g_content_manager->VerifyContentMetaDatabase(storage_id)); + } + + Result OpenContentStorage(ContentStorage *out, StorageId storage_id) { + sf::SharedPointer<IContentStorage> content_storage; + R_TRY(g_content_manager->OpenContentStorage(std::addressof(content_storage), storage_id)); + + *out = ContentStorage(std::move(content_storage)); + R_SUCCEED(); + } + + Result OpenContentMetaDatabase(ContentMetaDatabase *out, StorageId storage_id) { + sf::SharedPointer<IContentMetaDatabase> content_db; + R_TRY(g_content_manager->OpenContentMetaDatabase(std::addressof(content_db), storage_id)); + + *out = ContentMetaDatabase(std::move(content_db)); + R_SUCCEED(); + } + + Result CleanupContentMetaDatabase(StorageId storage_id) { + R_RETURN(g_content_manager->CleanupContentMetaDatabase(storage_id)); + } + + Result ActivateContentStorage(StorageId storage_id) { + R_RETURN(g_content_manager->ActivateContentStorage(storage_id)); + } + + Result InactivateContentStorage(StorageId storage_id) { + R_RETURN(g_content_manager->InactivateContentStorage(storage_id)); + } + + Result ActivateContentMetaDatabase(StorageId storage_id) { + /* On < 2.0.0, this command doesn't exist, and databases are activated as needed on open. */ + R_SUCCEED_IF(hos::GetVersion() < hos::Version_2_0_0); + R_RETURN(g_content_manager->ActivateContentMetaDatabase(storage_id)); + } + + Result InactivateContentMetaDatabase(StorageId storage_id) { + /* On < 2.0.0, this command doesn't exist. */ + R_SUCCEED_IF(hos::GetVersion() < hos::Version_2_0_0); + R_RETURN(g_content_manager->InactivateContentMetaDatabase(storage_id)); + } + + Result InvalidateRightsIdCache() { + R_RETURN(g_content_manager->InvalidateRightsIdCache()); + } + + Result ActivateFsContentStorage(fs::ContentStorageId fs_content_storage_id) { + R_RETURN(g_content_manager->ActivateFsContentStorage(fs_content_storage_id)); + } + + /* Deprecated API. */ + Result CloseContentStorageForcibly(StorageId storage_id) { + AMS_ABORT_UNLESS(hos::GetVersion() == hos::Version_1_0_0); + R_RETURN(g_content_manager->CloseContentStorageForcibly(storage_id)); + } + + Result CloseContentMetaDatabaseForcibly(StorageId storage_id) { + AMS_ABORT_UNLESS(hos::GetVersion() == hos::Version_1_0_0); + R_RETURN(g_content_manager->CloseContentMetaDatabaseForcibly(storage_id)); + } +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_id_utils.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_id_utils.cpp new file mode 100644 index 00000000..772f3e13 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_id_utils.cpp @@ -0,0 +1,90 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ncm { + + namespace { + + void GetStringFromBytes(char *dst, const void *src, size_t count) { + for (size_t i = 0; i < count; i++) { + util::SNPrintf(dst + 2 * i, 3, "%02x", static_cast<const u8 *>(src)[i]); + } + } + + bool GetBytesFromString(void *dst, size_t dst_size, const char *src, size_t src_size) { + /* Each byte is comprised of hex characters. */ + if (!util::IsAligned(src_size, 2) || (dst_size * 2 < src_size)) { + return false; + } + + /* Convert each character pair to a byte until we reach the end. */ + for (size_t i = 0; i < src_size; i += 2) { + char tmp[3]; + util::Strlcpy(tmp, src + i, sizeof(tmp)); + + char *err = nullptr; + reinterpret_cast<u8 *>(dst)[i / 2] = static_cast<u8>(std::strtoul(tmp, std::addressof(err), 16)); + if (*err != '\x00') { + return false; + } + } + + return true; + } + + } + + ContentIdString GetContentIdString(ContentId id) { + ContentIdString str; + GetStringFromContentId(str.data, sizeof(str), id); + return str; + } + + void GetStringFromContentId(char *dst, size_t dst_size, ContentId id) { + AMS_ABORT_UNLESS(dst_size > ContentIdStringLength); + GetStringFromBytes(dst, std::addressof(id), sizeof(id)); + } + + void GetStringFromRightsId(char *dst, size_t dst_size, fs::RightsId id) { + AMS_ABORT_UNLESS(dst_size > RightsIdStringLength); + GetStringFromBytes(dst, std::addressof(id), sizeof(id)); + } + + void GetTicketFileStringFromRightsId(char *dst, size_t dst_size, fs::RightsId id) { + AMS_ABORT_UNLESS(dst_size > TicketFileStringLength); + ContentIdString str; + GetStringFromRightsId(str.data, sizeof(str), id); + util::SNPrintf(dst, dst_size, "%s.tik", str.data); + } + + void GetCertificateFileStringFromRightsId(char *dst, size_t dst_size, fs::RightsId id) { + AMS_ABORT_UNLESS(dst_size > CertFileStringLength); + ContentIdString str; + GetStringFromRightsId(str.data, sizeof(str), id); + util::SNPrintf(dst, dst_size, "%s.cert", str.data); + } + + util::optional<ContentId> GetContentIdFromString(const char *str, size_t len) { + if (len < ContentIdStringLength) { + return util::nullopt; + } + + ContentId content_id; + return GetBytesFromString(std::addressof(content_id), sizeof(content_id), str, ContentIdStringLength) ? util::optional<ContentId>(content_id) : util::nullopt; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_info_utils.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_info_utils.cpp new file mode 100644 index 00000000..24bd0030 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_info_utils.cpp @@ -0,0 +1,90 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ncm { + + namespace { + + constexpr inline s64 EncryptionMetadataSize = 16_KB; + constexpr inline s64 ConcatenationFileSizeMax = 4_GB; + + constexpr s64 CalculateAdditionalContentSize(s64 file_size, s64 cluster_size) { + /* Account for the encryption header. */ + s64 size = EncryptionMetadataSize; + + /* Account for the file size splitting costs. */ + size += ((file_size / ConcatenationFileSizeMax) + 1) * cluster_size; + + /* Account for various overhead costs. */ + size += cluster_size * 3; + + return size; + } + + template<typename Handler> + Result ForEachContentInfo(const ContentMetaKey &key, ncm::ContentMetaDatabase *db, Handler handler) { + constexpr s32 MaxPerIteration = 0x10; + ContentInfo info_list[MaxPerIteration]; + s32 offset = 0; + while (true) { + /* List the content infos. */ + s32 count; + R_TRY(db->ListContentInfo(std::addressof(count), info_list, MaxPerIteration, key, offset)); + + /* Handle all that we listed. */ + for (s32 i = 0; i < count; i++) { + bool done = false; + R_TRY(handler(std::addressof(done), info_list[i])); + if (done) { + break; + } + } + + /* Check if we're done. */ + if (count != MaxPerIteration) { + break; + } + + offset += count; + } + + R_SUCCEED(); + } + + } + + s64 CalculateRequiredSize(s64 file_size, s64 cluster_size) { + return file_size + CalculateAdditionalContentSize(file_size, cluster_size); + } + + s64 CalculateRequiredSizeForExtension(s64 file_size, s64 cluster_size) { + return file_size + ((file_size / ConcatenationFileSizeMax) + 1) * cluster_size; + } + + Result EstimateRequiredSize(s64 *out_size, const ContentMetaKey &key, ncm::ContentMetaDatabase *db) { + s64 size = 0; + R_TRY(ForEachContentInfo(key, db, [&size](bool *out_done, const ContentInfo &info) -> Result { + size += CalculateRequiredSize(info.GetSize(), MaxClusterSize); + *out_done = false; + R_SUCCEED(); + })); + + *out_size = size; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_management_utils.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_management_utils.cpp new file mode 100644 index 00000000..1f6965b0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_management_utils.cpp @@ -0,0 +1,208 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ncm_fs_utils.hpp" + +namespace ams::ncm { + + namespace { + + constexpr inline size_t MaxPackagePathLength = 0x100; + + Result ConvertToFsCommonPath(char *dst, size_t dst_size, const char *package_root_path, const char *entry_path) { + char package_path[MaxPackagePathLength]; + + const size_t path_len = util::SNPrintf(package_path, sizeof(package_path), "%s%s", package_root_path, entry_path); + AMS_ABORT_UNLESS(path_len < MaxPackagePathLength); + + R_RETURN(fs::ConvertToFsCommonPath(dst, dst_size, package_path)); + } + + Result LoadContentMeta(ncm::AutoBuffer *out, const char *package_root_path, const fs::DirectoryEntry &entry) { + AMS_ABORT_UNLESS(impl::PathView(entry.name).HasSuffix(".cnmt.nca")); + + char path[MaxPackagePathLength]; + R_TRY(ConvertToFsCommonPath(path, sizeof(path), package_root_path, entry.name)); + + R_RETURN(ncm::TryReadContentMetaPath(out, path, ncm::ReadContentMetaPathWithoutExtendedDataOrDigest)); + } + + template<typename F> + Result ForEachFileInDirectory(const char *root_path, F f) { + /* Open the directory. */ + fs::DirectoryHandle dir; + R_TRY(fs::OpenDirectory(std::addressof(dir), root_path, fs::OpenDirectoryMode_File)); + ON_SCOPE_EXIT { fs::CloseDirectory(dir); }; + + while (true) { + /* Read the current entry. */ + s64 count; + fs::DirectoryEntry entry; + R_TRY(fs::ReadDirectory(std::addressof(count), std::addressof(entry), dir, 1)); + if (count == 0) { + break; + } + + /* Invoke our handler on the entry. */ + bool done; + R_TRY(f(std::addressof(done), entry)); + R_SUCCEED_IF(done); + } + + R_SUCCEED(); + } + + } + + Result ContentMetaDatabaseBuilder::BuildFromPackageContentMeta(void *buf, size_t size, const ContentInfo &meta_info) { + /* Create a reader for the content meta. */ + ncm::PackagedContentMetaReader package_meta_reader(buf, size); + + /* Allocate space to hold the converted meta. */ + const size_t meta_size = package_meta_reader.CalculateConvertContentMetaSize(); + std::unique_ptr<char[]> meta(new (std::nothrow) char[meta_size]); + + /* Convert the meta from packaged form to normal form. */ + package_meta_reader.ConvertToContentMeta(meta.get(), meta_size, meta_info); + ncm::ContentMetaReader meta_reader(meta.get(), meta_size); + + /* Insert the new metas into the database. */ + R_TRY(m_db->Set(package_meta_reader.GetKey(), meta_reader.GetData(), meta_reader.GetSize())); + + /* We're done. */ + R_SUCCEED(); + } + + Result ContentMetaDatabaseBuilder::BuildFromStorage(ContentStorage *storage) { + /* Get the total count of contents. */ + s32 total_count; + R_TRY(storage->GetContentCount(std::addressof(total_count))); + + /* Loop over all contents, looking for a package we can build from. */ + const size_t MaxContentIds = 64; + ContentId content_ids[MaxContentIds]; + for (s32 offset = 0; offset < total_count; /* ... */) { + /* List contents at the current offset. */ + s32 count; + R_TRY(storage->ListContentId(std::addressof(count), content_ids, MaxContentIds, offset)); + + /* Loop the contents we listed, looking for a correct one. */ + for (s32 i = 0; i < count; i++) { + /* Get the path for this content id. */ + auto &content_id = content_ids[i]; + ncm::Path path; + storage->GetPath(std::addressof(path), content_id); + + /* Read the content meta path, and build. */ + ncm::AutoBuffer package_meta; + if (R_SUCCEEDED(ncm::TryReadContentMetaPath(std::addressof(package_meta), path.str, ncm::ReadContentMetaPathWithoutExtendedDataOrDigest))) { + /* Get the size of the content. */ + s64 size; + R_TRY(storage->GetSize(std::addressof(size), content_id)); + + /* Build. */ + R_TRY(this->BuildFromPackageContentMeta(package_meta.Get(), package_meta.GetSize(), ContentInfo::Make(content_id, size, ContentInfo::DefaultContentAttributes, ContentType::Meta))); + } + } + + /* Advance. */ + offset += count; + } + + /* Commit our changes. */ + R_RETURN(m_db->Commit()); + } + + Result ContentMetaDatabaseBuilder::BuildFromPackage(const char *package_root_path) { + /* Build the database by writing every entry in the package. */ + R_TRY(ForEachFileInDirectory(package_root_path, [&](bool *done, const fs::DirectoryEntry &entry) -> Result { + /* Never early terminate. */ + *done = false; + + /* We have nothing to list if we're not looking at a meta. */ + R_SUCCEED_IF(!impl::PathView(entry.name).HasSuffix(".cnmt.nca")); + + /* Read the content meta path, and build. */ + ncm::AutoBuffer package_meta; + R_TRY(LoadContentMeta(std::addressof(package_meta), package_root_path, entry)); + + /* Try to parse a content id from the name. */ + auto content_id = GetContentIdFromString(entry.name, sizeof(entry.name)); + R_UNLESS(content_id, ncm::ResultInvalidPackageFormat()); + + /* Build using the meta. */ + R_RETURN(this->BuildFromPackageContentMeta(package_meta.Get(), package_meta.GetSize(), ContentInfo::Make(*content_id, entry.file_size, ContentInfo::DefaultContentAttributes, ContentType::Meta))); + })); + + /* Commit our changes. */ + R_RETURN(m_db->Commit()); + } + + Result ContentMetaDatabaseBuilder::Cleanup() { + /* This cleans up the content meta by removing all entries. */ + while (true) { + /* List as many keys as we can. */ + constexpr s32 MaxKeys = 64; + ContentMetaKey keys[MaxKeys]; + auto list_count = m_db->ListContentMeta(keys, MaxKeys); + + /* Remove the listed keys. */ + for (auto i = 0; i < list_count.written; i++) { + R_TRY(m_db->Remove(keys[i])); + } + + /* If there aren't more keys to read, we're done. */ + if (list_count.written < MaxKeys) { + break; + } + } + + /* Commit our deletions. */ + R_RETURN(m_db->Commit()); + } + + Result ListApplicationPackage(s32 *out_count, ApplicationId *out_ids, size_t max_out_ids, const char *package_root_path) { + size_t count = 0; + R_TRY(ForEachFileInDirectory(package_root_path, [&](bool *done, const fs::DirectoryEntry &entry) -> Result { + /* Never early terminate. */ + *done = false; + + /* We have nothing to list if we're not looking at a meta. */ + R_SUCCEED_IF(!impl::PathView(entry.name).HasSuffix(".cnmt.nca")); + + /* Read the content meta path, and build. */ + ncm::AutoBuffer package_meta; + R_TRY(LoadContentMeta(std::addressof(package_meta), package_root_path, entry)); + + /* Create a reader for the meta. */ + ncm::PackagedContentMetaReader package_meta_reader(package_meta.Get(), package_meta.GetSize()); + + /* Write the key to output if we're reading an application. */ + const auto &key = package_meta_reader.GetKey(); + if (key.type == ContentMetaType::Application) { + R_UNLESS(count < max_out_ids, ncm::ResultBufferInsufficient()); + + out_ids[count++] = { key.id }; + } + + R_SUCCEED(); + })); + + *out_count = static_cast<s32>(count); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_manager_factory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_manager_factory.cpp new file mode 100644 index 00000000..462ce5db --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_manager_factory.cpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ncm_content_manager_factory.hpp" + +namespace ams::ncm { + + sf::SharedPointer<IContentManager> CreateDefaultContentManager(const ContentManagerConfig &config) { + auto ref = sf::CreateSharedObjectEmplaced<IContentManager, ContentManagerImpl>(); + R_ABORT_UNLESS(ref.GetImpl().Initialize(config)); + return ref; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_manager_factory.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_manager_factory.hpp new file mode 100644 index 00000000..8d0176ae --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_manager_factory.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ncm { + + sf::SharedPointer<IContentManager> CreateDefaultContentManager(const ContentManagerConfig &config); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_manager_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_manager_impl.cpp new file mode 100644 index 00000000..dded90f3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_manager_impl.cpp @@ -0,0 +1,1211 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ncm_content_storage_impl.hpp" +#include "ncm_read_only_content_storage_impl.hpp" +#include "ncm_host_content_storage_impl.hpp" +#include "ncm_content_meta_database_impl.hpp" +#include "ncm_on_memory_content_meta_database_impl.hpp" +#include "ncm_fs_utils.hpp" + +namespace ams::ncm { + + namespace { + + alignas(os::MemoryPageSize) u8 g_system_content_meta_database_heap[512_KB]; + alignas(os::MemoryPageSize) u8 g_gamecard_content_meta_database_heap[512_KB]; + alignas(os::MemoryPageSize) u8 g_sd_and_user_content_meta_database_heap[2_MB + 512_KB]; + + ContentMetaMemoryResource g_system_content_meta_memory_resource(g_system_content_meta_database_heap, sizeof(g_system_content_meta_database_heap)); + ContentMetaMemoryResource g_gamecard_content_meta_memory_resource(g_gamecard_content_meta_database_heap, sizeof(g_gamecard_content_meta_database_heap)); + ContentMetaMemoryResource g_sd_and_user_content_meta_memory_resource(g_sd_and_user_content_meta_database_heap, sizeof(g_sd_and_user_content_meta_database_heap)); + + constexpr fs::SystemSaveDataId BuiltInSystemSaveDataId = 0x8000000000000120; + constexpr u64 BuiltInSystemSaveDataSize = 0x6c000; + constexpr u64 BuiltInSystemSaveDataJournalSize = 0x6c000; + constexpr u32 BuiltInSystemSaveDataFlags = fs::SaveDataFlags_KeepAfterResettingSystemSaveData | fs::SaveDataFlags_KeepAfterRefurbishment; + + constexpr SystemSaveDataInfo BuiltInSystemSystemSaveDataInfo = { + .id = BuiltInSystemSaveDataId, + .size = BuiltInSystemSaveDataSize, + .journal_size = BuiltInSystemSaveDataJournalSize, + .flags = BuiltInSystemSaveDataFlags, + .space_id = fs::SaveDataSpaceId::System + }; + + constexpr fs::SystemSaveDataId BuiltInUserSaveDataId = 0x8000000000000121; + constexpr u64 BuiltInUserSaveDataSize = 0x29e000; + constexpr u64 BuiltInUserSaveDataJournalSize = 0x29e000; + constexpr u32 BuiltInUserSaveDataFlags = 0; + + constexpr SystemSaveDataInfo BuiltInUserSystemSaveDataInfo = { + .id = BuiltInUserSaveDataId, + .size = BuiltInUserSaveDataSize, + .journal_size = BuiltInUserSaveDataJournalSize, + .flags = BuiltInUserSaveDataFlags, + .space_id = fs::SaveDataSpaceId::System + }; + + constexpr fs::SystemSaveDataId SdCardSaveDataId = 0x8000000000000124; + constexpr u64 SdCardSaveDataSize = 0xa08000; + constexpr u64 SdCardSaveDataJournalSize = 0xa08000; + constexpr u32 SdCardSaveDataFlags = 0; + + constexpr SystemSaveDataInfo SdCardSystemSaveDataInfo = { + .id = SdCardSaveDataId, + .size = SdCardSaveDataSize, + .journal_size = SdCardSaveDataJournalSize, + .flags = SdCardSaveDataFlags, + .space_id = fs::SaveDataSpaceId::SdSystem, + }; + + using RootPath = kvdb::BoundedString<32>; + + inline void ReplaceMountName(char *out_path, const char *mount_name, const char *path) { + std::strcpy(out_path, mount_name); + std::strcat(out_path, std::strchr(path, ':')); + } + + Result EnsureBuiltInSystemSaveDataFlags() { + u32 cur_flags = 0; + + /* Obtain the existing flags. */ + R_TRY(fs::GetSaveDataFlags(std::addressof(cur_flags), BuiltInSystemSaveDataId)); + + /* Update the flags if needed. */ + if (cur_flags != BuiltInSystemSaveDataFlags) { + R_TRY(fs::SetSaveDataFlags(BuiltInSystemSaveDataId, fs::SaveDataSpaceId::System, BuiltInSystemSaveDataFlags)); + } + R_SUCCEED(); + } + + ALWAYS_INLINE Result GetContentStorageNotActiveResult(StorageId storage_id) { + switch (storage_id) { + case StorageId::GameCard: R_THROW(ncm::ResultGameCardContentStorageNotActive()); + case StorageId::BuiltInSystem: R_THROW(ncm::ResultBuiltInSystemContentStorageNotActive()); + case StorageId::BuiltInUser: R_THROW(ncm::ResultBuiltInUserContentStorageNotActive()); + case StorageId::SdCard: R_THROW(ncm::ResultSdCardContentStorageNotActive()); + default: R_THROW(ncm::ResultUnknownContentStorageNotActive()); + } + } + + ALWAYS_INLINE Result GetContentMetaDatabaseNotActiveResult(StorageId storage_id) { + switch (storage_id) { + case StorageId::GameCard: R_THROW(ncm::ResultGameCardContentMetaDatabaseNotActive()); + case StorageId::BuiltInSystem: R_THROW(ncm::ResultBuiltInSystemContentMetaDatabaseNotActive()); + case StorageId::BuiltInUser: R_THROW(ncm::ResultBuiltInUserContentMetaDatabaseNotActive()); + case StorageId::SdCard: R_THROW(ncm::ResultSdCardContentMetaDatabaseNotActive()); + default: R_THROW(ncm::ResultUnknownContentMetaDatabaseNotActive()); + } + } + + ALWAYS_INLINE bool IsSignedSystemPartitionOnSdCardValid(const char *bis_mount_name) { + /* Signed system partition should never be checked on < 4.0.0, as it did not exist before then. */ + AMS_ABORT_UNLESS(hos::GetVersion() >= hos::Version_4_0_0); + + /* If we're importing from system on SD, make sure that the signed system partition is valid. */ + const auto version = hos::GetVersion(); + if (version >= hos::Version_8_0_0) { + /* On >= 8.0.0, a simpler method was added to check validity. */ + /* This also works on < 4.0.0 (though the system partition will never be on-sd there), */ + /* and so this will always return false. */ + char path[fs::MountNameLengthMax + 2 /* :/ */ + 1]; + util::SNPrintf(path, sizeof(path), "%s:/", bis_mount_name); + return fs::IsSignedSystemPartitionOnSdCardValid(path); + } else { + /* On 4.0.0-7.0.1, use the remote command to validate the system partition. */ + return fs::IsSignedSystemPartitionOnSdCardValidDeprecated(); + } + } + + Result EnsureAndMountSystemSaveData(const char *mount_name, const SystemSaveDataInfo &info) { + constexpr u64 OwnerId = 0; + + /* Don't create save if absent - We want to handle this case ourselves. */ + fs::DisableAutoSaveDataCreation(); + + /* Mount existing system save data if present, otherwise create it then mount. */ + R_TRY_CATCH(fs::MountSystemSaveData(mount_name, info.space_id, info.id)) { + R_CATCH(fs::ResultTargetNotFound) { + /* On 1.0.0, not all flags existed. Mask when appropriate. */ + constexpr u32 SaveDataFlags100Mask = fs::SaveDataFlags_KeepAfterResettingSystemSaveData; + const u32 flags = (hos::GetVersion() >= hos::Version_2_0_0) ? (info.flags) : (info.flags & SaveDataFlags100Mask); + R_TRY(fs::CreateSystemSaveData(info.space_id, info.id, OwnerId, info.size, info.journal_size, flags)); + R_TRY(fs::MountSystemSaveData(mount_name, info.space_id, info.id)); + } + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + } + + Result ContentManagerImpl::IntegratedContentStorageRoot::Create() { + /* Create all storages. */ + for (auto i = 0; i < m_num_roots; ++i) { + /* Get the current root. */ + const auto &root = m_roots[i]; + + /* If we should, skip. */ + if (!root.config.has_value() || root.config->skip_verify_and_create) { + continue; + } + + /* Mount the relevant content storage. */ + R_TRY(fs::MountContentStorage(root.mount_name, root.config->content_storage_id)); + ON_SCOPE_EXIT { fs::Unmount(root.mount_name); }; + + /* Ensure the content storage root's path exists. */ + R_TRY(fs::EnsureDirectory(root.path)); + + /* Initialize content and placeholder directories for the root. */ + R_TRY(ContentStorageImpl::InitializeBase(root.path)); + } + + R_SUCCEED(); + } + + Result ContentManagerImpl::IntegratedContentStorageRoot::Verify() { + /* Verify all storages. */ + for (auto i = 0; i < m_num_roots; ++i) { + /* Get the current root. */ + const auto &root = m_roots[i]; + + /* If we should, skip. */ + if (!root.config.has_value() || root.config->skip_verify_and_create) { + continue; + } + + /* Substitute the mount name in the root's path with a unique one. */ + char path[0x80]; + auto mount_name = impl::CreateUniqueMountName(); + ReplaceMountName(path, mount_name.str, root.path); + + /* Mount the relevant content storage. */ + R_TRY(fs::MountContentStorage(mount_name.str, root.config->content_storage_id)); + ON_SCOPE_EXIT { fs::Unmount(mount_name.str); }; + + /* Ensure the root, content and placeholder directories exist for the storage. */ + R_TRY(ContentStorageImpl::VerifyBase(path)); + } + + R_SUCCEED(); + } + + Result ContentManagerImpl::IntegratedContentStorageRoot::Open(sf::Out<sf::SharedPointer<IContentStorage>> out, RightsIdCache &rights_id_cache, RegisteredHostContent ®istered_host_content) { + /* Get the interface. */ + const bool has_intf = m_config->is_integrated ? m_integrated_content_storage != nullptr : m_roots->content_storage != nullptr; + + if (hos::GetVersion() >= hos::Version_2_0_0) { + /* Obtain the content storage if already active. */ + R_UNLESS(has_intf, GetContentStorageNotActiveResult(m_config->storage_id)); + } else { + /* 1.0.0 activates content storages as soon as they are opened. */ + if (!has_intf) { + R_TRY(this->Activate(rights_id_cache, registered_host_content)); + } + } + + *out = m_config->is_integrated ? m_integrated_content_storage : m_roots->content_storage; + R_SUCCEED(); + } + + Result ContentManagerImpl::IntegratedContentStorageRoot::Activate(RightsIdCache &rights_id_cache, RegisteredHostContent ®istered_host_content) { + /* If necessary, create the integrated storage. */ + if (m_config->is_integrated && !m_integrated_content_storage) { + m_integrated_content_storage = sf::CreateSharedObjectEmplaced<IContentStorage, IntegratedContentStorageImpl>(); + } + + /* Activate all storages. */ + for (auto i = 0; i < m_num_roots; ++i) { + /* Get the current root. */ + auto &root = m_roots[i]; + + /* If we should, skip. */ + if (root.config.has_value() && root.config->skip_activate) { + continue; + } + + /* Activate. */ + R_TRY(this->Activate(root, rights_id_cache, registered_host_content)); + + /* If we're integrated, add the storage. */ + if (m_config->is_integrated) { + /* Determine the index of the storage. */ + int index; + for (index = 0; index < m_config->num_content_storage_ids; ++index) { + if (m_config->content_storage_ids[index] == root.config->content_storage_id) { + break; + } + } + + /* Add the storage. */ + m_integrated_content_storage.GetImpl().Add(root.content_storage, index + 1); + } + } + + R_SUCCEED(); + } + + Result ContentManagerImpl::IntegratedContentStorageRoot::Inactivate(RegisteredHostContent ®istered_host_content) { + /* If we have an integrated storage, disable it. */ + if (m_integrated_content_storage != nullptr) { + R_TRY(m_integrated_content_storage->DisableForcibly()); + m_integrated_content_storage.Reset(); + } + + /* Disable and unmount all storages. */ + for (auto i = 0; i < m_num_roots; ++i) { + /* Get the current root. */ + auto &root = m_roots[i]; + + if (root.content_storage != nullptr) { + /* N doesn't bother checking the result of this. */ + root.content_storage->DisableForcibly(); + root.content_storage = nullptr; + + if (root.storage_id == StorageId::Host) { + registered_host_content.ClearPaths(); + } else { + fs::Unmount(root.mount_name); + } + } + } + + R_SUCCEED(); + } + + Result ContentManagerImpl::IntegratedContentStorageRoot::Activate(RightsIdCache &rights_id_cache, RegisteredHostContent ®istered_host_content, fs::ContentStorageId content_storage_id) { + /* Check that we have the desired storage id in some root. */ + ContentStorageRoot *root = this->GetRoot(content_storage_id); + R_UNLESS(root != nullptr, ncm::ResultUnknownStorage()); + + /* If necessary, create the integrated storage. */ + if (m_config->is_integrated && !m_integrated_content_storage) { + m_integrated_content_storage = sf::CreateSharedObjectEmplaced<IContentStorage, IntegratedContentStorageImpl>(); + } + + /* Activate. */ + R_TRY(this->Activate(*root, rights_id_cache, registered_host_content)); + + /* If we're integrated, add the storage. */ + if (m_config->is_integrated) { + /* Determine the index of the storage. */ + int index; + for (index = 0; index < m_config->num_content_storage_ids; ++index) { + if (m_config->content_storage_ids[index] == root->config->content_storage_id) { + break; + } + } + + /* Add the storage. */ + m_integrated_content_storage.GetImpl().Add(root->content_storage, index + 1); + } + + R_SUCCEED(); + } + + Result ContentManagerImpl::IntegratedContentStorageRoot::Activate(ContentStorageRoot &root, RightsIdCache &rights_id_cache, RegisteredHostContent ®istered_host_content) { + /* Check if the storage is already activated. */ + R_SUCCEED_IF(root.content_storage != nullptr); + + /* Handle based on whether or not we have a content storage config. */ + if (root.config.has_value()) { + /* Mount the content storage. */ + R_TRY(fs::MountContentStorage(root.mount_name, root.config->content_storage_id)); + ON_RESULT_FAILURE { fs::Unmount(root.mount_name); }; + + /* Create a content storage. */ + auto content_storage = sf::CreateSharedObjectEmplaced<IContentStorage, ContentStorageImpl>(); + + /* Initialize content storage with an appropriate path function. */ + switch (root.storage_id) { + case StorageId::BuiltInSystem: + R_TRY(content_storage.GetImpl().Initialize(root.path, MakeFlatContentFilePath, MakeFlatPlaceHolderFilePath, false, std::addressof(rights_id_cache))); + break; + case StorageId::SdCard: + R_TRY(content_storage.GetImpl().Initialize(root.path, MakeSha256HierarchicalContentFilePath_ForFat16KCluster, MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster, true, std::addressof(rights_id_cache))); + break; + default: + R_TRY(content_storage.GetImpl().Initialize(root.path, MakeSha256HierarchicalContentFilePath_ForFat16KCluster, MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster, false, std::addressof(rights_id_cache))); + break; + } + + root.content_storage = std::move(content_storage); + } else { + switch (root.storage_id) { + case ncm::StorageId::Host: + root.content_storage = sf::CreateSharedObjectEmplaced<IContentStorage, HostContentStorageImpl>(std::addressof(registered_host_content)); + break; + case ncm::StorageId::GameCard: + { + /* Get the gamecard handle. */ + fs::GameCardHandle handle{}; + R_TRY(fs::GetGameCardHandle(std::addressof(handle))); + + /* Mount the secure gamecard partition. */ + R_TRY(fs::MountGameCardPartition(root.mount_name, handle, fs::GameCardPartition::Secure)); + ON_RESULT_FAILURE { fs::Unmount(root.mount_name); }; + + /* Create the content storage. */ + auto content_storage = sf::CreateSharedObjectEmplaced<IContentStorage, ReadOnlyContentStorageImpl>(); + R_TRY(content_storage.GetImpl().Initialize(root.path, MakeFlatContentFilePath)); + root.content_storage = std::move(content_storage); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + R_SUCCEED(); + } + + Result ContentManagerImpl::IntegratedContentMetaDatabaseRoot::Create() { + /* Create all content meta databases. */ + for (auto i = 0; i < m_num_roots; ++i) { + /* Get the current root. */ + const auto &root = m_roots[i]; + + /* If we should, skip. */ + if (!root.save_data_info.has_value()) { + continue; + } + + /* Mount (and optionally create) save data for the root. */ + R_TRY(EnsureAndMountSystemSaveData(root.mount_name, *root.save_data_info)); + ON_SCOPE_EXIT { fs::Unmount(root.mount_name); }; + + /* Ensure the content meta database root's path exists. */ + R_TRY(fs::EnsureDirectory(root.path)); + + /* Commit our changes. */ + R_TRY(fs::CommitSaveData(root.mount_name)); + } + + R_SUCCEED(); + } + + Result ContentManagerImpl::IntegratedContentMetaDatabaseRoot::Verify() { + /* Verify all content meta databases. */ + for (auto i = 0; i < m_num_roots; ++i) { + /* Get the current root. */ + const auto &root = m_roots[i]; + + /* If we should, skip. */ + if (!root.save_data_info.has_value()) { + continue; + } + + /* Mount save data for non-existing content meta databases. */ + const bool mount = !root.content_meta_database; + if (mount) { + R_TRY(fs::MountSystemSaveData(root.mount_name, root.save_data_info->space_id, root.save_data_info->id)); + } + auto mount_guard = SCOPE_GUARD { if (mount) { fs::Unmount(root.mount_name); } }; + + /* Ensure the root path exists. */ + bool has_dir = false; + R_TRY(fs::HasDirectory(std::addressof(has_dir), root.path)); + R_UNLESS(has_dir, ncm::ResultInvalidContentMetaDatabase()); + } + + R_SUCCEED(); + } + + Result ContentManagerImpl::IntegratedContentMetaDatabaseRoot::Open(sf::Out<sf::SharedPointer<IContentMetaDatabase>> out) { + /* Get the interface. */ + const bool has_intf = m_config->is_integrated ? m_integrated_content_meta_database != nullptr : m_roots->content_meta_database != nullptr; + + if (hos::GetVersion() >= hos::Version_2_0_0) { + /* Obtain the content meta database if already active. */ + R_UNLESS(has_intf, GetContentMetaDatabaseNotActiveResult(m_config->storage_id)); + } else { + /* 1.0.0 activates content meta database as soon as they are opened. */ + if (!has_intf) { + R_TRY(this->Activate()); + } + } + + *out = m_config->is_integrated ? m_integrated_content_meta_database : m_roots->content_meta_database; + R_SUCCEED(); + } + + Result ContentManagerImpl::IntegratedContentMetaDatabaseRoot::Cleanup() { + /* Cleanup all content meta databases. */ + for (auto i = 0; i < m_num_roots; ++i) { + /* Get the current root. */ + const auto &root = m_roots[i]; + + /* If we should, skip. */ + if (!root.save_data_info.has_value()) { + continue; + } + + /* Delete save data for the content meta database. */ + R_TRY(fs::DeleteSaveData(root.save_data_info->space_id, root.save_data_info->id)); + } + + R_SUCCEED(); + } + + Result ContentManagerImpl::IntegratedContentMetaDatabaseRoot::Activate() { + /* If necessary, create the integrated meta database. */ + if (m_config->is_integrated && !m_integrated_content_meta_database) { + m_integrated_content_meta_database = sf::CreateSharedObjectEmplaced<IContentMetaDatabase, IntegratedContentMetaDatabaseImpl>(); + } + + /* Activate all meta databases. */ + for (auto i = 0; i < m_num_roots; ++i) { + /* Get the current root. */ + auto &root = m_roots[i]; + + /* If we should, skip. */ + if (root.storage_config.has_value() && root.storage_config->skip_activate) { + continue; + } + + /* Activate. */ + R_TRY(this->Activate(root)); + + /* If we're integrated, add the meta database. */ + if (m_config->is_integrated) { + /* Determine the index of the meta database. */ + int index; + for (index = 0; index < m_config->num_content_storage_ids; ++index) { + if (m_config->content_storage_ids[index] == root.storage_config->content_storage_id) { + break; + } + } + + /* Add the meta database. */ + m_integrated_content_meta_database.GetImpl().Add(root.content_meta_database, index + 1); + } + } + + R_SUCCEED(); + } + + Result ContentManagerImpl::IntegratedContentMetaDatabaseRoot::Inactivate() { + /* If we have an integrated meta database, disable it. */ + /* NOTE: Nintendo does not reset the integrated DB, presumably they just forgot... */ + /* ...this breaks this, hard, so we'll do the correct thing. */ + if (m_integrated_content_meta_database != nullptr) { + R_TRY(m_integrated_content_meta_database->DisableForcibly()); + m_integrated_content_meta_database.Reset(); + } + + /* Disable and unmount all storages. */ + for (auto i = 0; i < m_num_roots; ++i) { + /* Get the current root. */ + auto &root = m_roots[i]; + + if (root.content_meta_database != nullptr) { + /* N doesn't bother checking the result of this. */ + root.content_meta_database->DisableForcibly(); + root.content_meta_database = nullptr; + root.kvs = util::nullopt; + + /* Additionally, if we have savedata, unmount. */ + if (root.save_data_info.has_value()) { + fs::Unmount(root.mount_name); + } + } + } + + R_SUCCEED(); + } + + Result ContentManagerImpl::IntegratedContentMetaDatabaseRoot::Activate(ContentMetaDatabaseRoot &root) { + /* If the root is already activated, there's nothing to do. */ + R_SUCCEED_IF(root.content_meta_database != nullptr); + + /* Make a new kvs. */ + root.kvs.emplace(); + + /* If we should, mount save data for this route. */ + if (root.save_data_info.has_value()) { + /* Mount save data for this root. */ + R_TRY(fs::MountSystemSaveData(root.mount_name, root.save_data_info->space_id, root.save_data_info->id)); + ON_RESULT_FAILURE { fs::Unmount(root.mount_name); }; + + /* Initialize and load the key value store from the filesystem. */ + R_TRY(root.kvs->Initialize(root.path, root.max_content_metas, root.memory_resource)); + R_TRY(root.kvs->Load()); + + /* Create the content meta database. */ + root.content_meta_database = sf::CreateSharedObjectEmplaced<IContentMetaDatabase, ContentMetaDatabaseImpl>(std::addressof(*root.kvs), root.mount_name); + } else { + if (root.storage_id == StorageId::BuiltInSystem) { + /* Create a temporary mount name, and mount the partition. */ + auto tmp_mount_name = impl::CreateUniqueMountName(); + + switch (root.storage_config->content_storage_id) { + case fs::ContentStorageId::System: + R_TRY(fs::MountBis(tmp_mount_name.str, fs::BisPartitionId::System)); + break; + case fs::ContentStorageId::System0: + R_TRY(fs::MountBis(tmp_mount_name.str, fs::BisPartitionId::System0)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + ON_SCOPE_EXIT { fs::Unmount(tmp_mount_name.str); }; + + /* Initialize and load the key value store from the filesystem. */ + char path[fs::EntryNameLengthMax]; + util::SNPrintf(path, sizeof(path), "%s:/cnmtdb.arc", tmp_mount_name.str); + R_TRY(root.kvs->InitializeForReadOnlyArchiveFile(path, root.max_content_metas, root.memory_resource)); + R_TRY(root.kvs->Load()); + + /* Create an on memory content meta database. */ + root.content_meta_database = sf::CreateSharedObjectEmplaced<IContentMetaDatabase, OnMemoryContentMetaDatabaseImpl>(std::addressof(*root.kvs)); + } else { + /* Initialize the key value store. */ + R_TRY(root.kvs->Initialize(root.max_content_metas, root.memory_resource)); + + /* Create an on memory content meta database. */ + root.content_meta_database = sf::CreateSharedObjectEmplaced<IContentMetaDatabase, OnMemoryContentMetaDatabaseImpl>(std::addressof(*root.kvs)); + } + } + + R_SUCCEED(); + } + + Result ContentManagerImpl::IntegratedContentMetaDatabaseRoot::Activate(fs::ContentStorageId content_storage_id) { + /* Check that we have the desired storage id in some root. */ + ContentMetaDatabaseRoot *root = this->GetRoot(content_storage_id); + R_UNLESS(root != nullptr, ncm::ResultUnknownStorage()); + + /* If necessary, create the integrated meta database. */ + if (m_config->is_integrated && !m_integrated_content_meta_database) { + m_integrated_content_meta_database = sf::CreateSharedObjectEmplaced<IContentMetaDatabase, IntegratedContentMetaDatabaseImpl>(); + } + + /* Activate. */ + R_TRY(this->Activate(*root)); + + /* If we're integrated, add the storage. */ + if (m_config->is_integrated) { + /* Determine the index of the meta database. */ + int index; + for (index = 0; index < m_config->num_content_storage_ids; ++index) { + if (m_config->content_storage_ids[index] == root->storage_config->content_storage_id) { + break; + } + } + + /* Add the meta database. */ + m_integrated_content_meta_database.GetImpl().Add(root->content_meta_database, index + 1); + } + + R_SUCCEED(); + } + + ContentManagerImpl::~ContentManagerImpl() { + std::scoped_lock lk(m_mutex); + + /* Disable and unmount all content storage roots. */ + for (size_t i = 0; i < m_num_integrated_content_storage_entries; ++i) { + this->InactivateContentStorage(m_integrated_content_storage_roots[i].m_config->storage_id); + } + + /* Disable and unmount all content meta database roots. */ + for (size_t i = 0; i < m_num_integrated_content_meta_entries; ++i) { + this->InactivateContentMetaDatabase(m_integrated_content_meta_database_roots[i].m_config->storage_id); + } + } + + Result ContentManagerImpl::GetIntegratedContentStorageConfig(IntegratedContentStorageConfig **out, fs::ContentStorageId content_storage_id) { + for (size_t i = 0; i < m_num_integrated_configs; ++i) { + auto &integrated_config = m_integrated_configs[i]; + for (auto n = 0; n < integrated_config.num_content_storage_ids; n++) { + if (integrated_config.content_storage_ids[n] == content_storage_id) { + *out = std::addressof(integrated_config); + R_SUCCEED(); + } + } + } + + R_THROW(ncm::ResultUnknownStorage()); + } + + + Result ContentManagerImpl::GetIntegratedContentStorageRoot(IntegratedContentStorageRoot **out, StorageId id) { + /* Storage must not be StorageId::Any or StorageId::None. */ + R_UNLESS(IsUniqueStorage(id), ncm::ResultUnknownStorage()); + + /* Find a root with a matching storage id. */ + for (size_t i = 0; i < m_num_integrated_content_storage_entries; ++i) { + if (auto &root = m_integrated_content_storage_roots[i]; root.m_config->storage_id == id) { + *out = std::addressof(root); + R_SUCCEED(); + } + } + + R_THROW(ncm::ResultUnknownStorage()); + } + + Result ContentManagerImpl::GetIntegratedContentMetaDatabaseRoot(IntegratedContentMetaDatabaseRoot **out, StorageId id) { + /* Storage must not be StorageId::Any or StorageId::None. */ + R_UNLESS(IsUniqueStorage(id), ncm::ResultUnknownStorage()); + + /* Find a root with a matching storage id. */ + for (size_t i = 0; i < m_num_integrated_content_meta_entries; ++i) { + if (auto &root = m_integrated_content_meta_database_roots[i]; root.m_config->storage_id == id) { + *out = std::addressof(root); + R_SUCCEED(); + } + } + + R_THROW(ncm::ResultUnknownStorage()); + } + + + Result ContentManagerImpl::InitializeContentStorageRoot(ContentStorageRoot *out, StorageId storage_id, util::optional<ContentStorageConfig> config) { + out->storage_id = storage_id; + out->config = config; + out->content_storage = nullptr; + + /* Create a new mount name and copy it to out. */ + std::strcpy(out->mount_name, impl::CreateUniqueMountName().str); + util::SNPrintf(out->path, sizeof(out->path), "%s:/", out->mount_name); + + R_SUCCEED(); + } + + Result ContentManagerImpl::InitializeContentMetaDatabaseRoot(ContentMetaDatabaseRoot *out, StorageId storage_id, util::optional<ContentStorageConfig> storage_config) { + out->storage_id = storage_id; + out->storage_config = storage_config; + + /* Set the storage-specific info. */ + switch (storage_id) { + case ncm::StorageId::Host: + out->save_data_info = util::nullopt; + out->max_content_metas = HostMaxContentMetaCount; + out->memory_resource = std::addressof(g_sd_and_user_content_meta_memory_resource); + break; + case ncm::StorageId::GameCard: + out->save_data_info = util::nullopt; + out->max_content_metas = GameCardMaxContentMetaCount; + out->memory_resource = std::addressof(g_gamecard_content_meta_memory_resource); + break; + case ncm::StorageId::BuiltInSystem: + /* If we should, skip save data info. */ + if (storage_config.has_value() && (storage_config->content_storage_id != fs::ContentStorageId::System || storage_config->skip_verify_and_create)) { + out->save_data_info = util::nullopt; + } else { + /* Otherwise, use normal save data info. */ + out->save_data_info = BuiltInSystemSystemSaveDataInfo; + } + + out->max_content_metas = SystemMaxContentMetaCount; + out->memory_resource = std::addressof(g_system_content_meta_memory_resource); + break; + case ncm::StorageId::BuiltInUser: + out->save_data_info = BuiltInUserSystemSaveDataInfo; + out->max_content_metas = UserMaxContentMetaCount; + out->memory_resource = std::addressof(g_sd_and_user_content_meta_memory_resource); + break; + case ncm::StorageId::SdCard: + out->save_data_info = SdCardSystemSaveDataInfo; + out->max_content_metas = SdCardMaxContentMetaCount; + out->memory_resource = std::addressof(g_sd_and_user_content_meta_memory_resource); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Clear the kvs. */ + out->kvs = util::nullopt; + + /* Create a new mount name and copy it to out. */ + std::strcpy(out->mount_name, impl::CreateUniqueMountName().str); + out->mount_name[0] = '#'; + util::SNPrintf(out->path, sizeof(out->path), "%s:/meta", out->mount_name); + + R_SUCCEED(); + } + + Result ContentManagerImpl::InitializeIntegratedContentStorageRoot(IntegratedContentStorageRoot *out, const IntegratedContentStorageConfig *config, size_t root_idx, size_t root_count) { + /* Set config and roots. */ + out->m_config = config; + out->m_roots = std::addressof(m_content_storage_roots[root_idx]); + out->m_num_roots = root_count; + + R_SUCCEED(); + } + + Result ContentManagerImpl::InitializeIntegratedContentMetaDatabaseRoot(IntegratedContentMetaDatabaseRoot *out, const IntegratedContentStorageConfig *config, size_t root_idx, size_t root_count) { + /* Set config and roots. */ + out->m_config = config; + out->m_roots = std::addressof(m_content_meta_database_roots[root_idx]); + out->m_num_roots = root_count; + + R_SUCCEED(); + } + + Result ContentManagerImpl::ImportContentMetaDatabaseImpl(ContentMetaDatabaseRoot *root, const char *import_mount_name) { + /* Check that the root is system. */ + AMS_ABORT_UNLESS(root->storage_id == ncm::StorageId::BuiltInSystem); + + std::scoped_lock lk(m_mutex); + + /* Print the savedata path. */ + PathString savedata_db_path; + savedata_db_path.AssignFormat("%s/%s", root->path, "imkvdb.arc"); + + /* Print a path for the mounted partition. */ + PathString bis_db_path; + bis_db_path.AssignFormat("%s:/%s", import_mount_name, "cnmtdb.arc"); + + /* Mount the savedata. */ + R_TRY(fs::MountSystemSaveData(root->mount_name, root->save_data_info->space_id, root->save_data_info->id)); + ON_SCOPE_EXIT { fs::Unmount(root->mount_name); }; + + /* Ensure the path exists for us to import to. */ + R_TRY(fs::EnsureDirectory(root->path)); + + /* Copy the file from bis to our save. */ + R_TRY(impl::CopyFile(savedata_db_path, bis_db_path)); + + /* Commit the import. */ + R_RETURN(fs::CommitSaveData(root->mount_name)); + } + + Result ContentManagerImpl::BuildContentMetaDatabase(StorageId storage_id) { + if (hos::GetVersion() < hos::Version_5_0_0) { + /* On < 5.0.0, perform an actual build of the database. */ + R_RETURN(this->BuildContentMetaDatabaseImpl(storage_id)); + } else { + /* On 5.0.0+, building just performs an import. */ + R_RETURN(this->ImportContentMetaDatabase(storage_id, false)); + } + } + + Result ContentManagerImpl::BuildContentMetaDatabaseImpl(StorageId storage_id) { + /* Temporarily activate the database. */ + R_TRY(this->ActivateContentMetaDatabase(storage_id)); + ON_SCOPE_EXIT { this->InactivateContentMetaDatabase(storage_id); }; + + /* Open the content meta database and storage. */ + ContentMetaDatabase meta_db; + ContentStorage storage; + R_TRY(ncm::OpenContentMetaDatabase(std::addressof(meta_db), storage_id)); + R_TRY(ncm::OpenContentStorage(std::addressof(storage), storage_id)); + + /* Create a builder, and build. */ + ContentMetaDatabaseBuilder builder(std::addressof(meta_db)); + R_RETURN(builder.BuildFromStorage(std::addressof(storage))); + } + + Result ContentManagerImpl::ImportContentMetaDatabase(StorageId storage_id, bool from_signed_partition) { + /* Only support importing BuiltInSystem. */ + AMS_ABORT_UNLESS(storage_id == StorageId::BuiltInSystem); + + /* Obtain the integrated content meta database root. */ + IntegratedContentMetaDatabaseRoot *integrated_root; + R_TRY(this->GetIntegratedContentMetaDatabaseRoot(std::addressof(integrated_root), storage_id)); + + /* Obtain the root. */ + ContentMetaDatabaseRoot *root = integrated_root->GetRoot(fs::ContentStorageId::System); + R_UNLESS(root != nullptr, ncm::ResultUnknownStorage()); + + /* Get a mount name for the system partition. */ + auto bis_mount_name = impl::CreateUniqueMountName(); + + /* Mount the BIS partition that contains the database we're importing. */ + R_TRY(fs::MountBis(bis_mount_name.str, fs::BisPartitionId::System)); + ON_SCOPE_EXIT { fs::Unmount(bis_mount_name.str); }; + + /* If we're not importing from a signed partition (or the partition signature is valid), import. */ + if (!from_signed_partition || IsSignedSystemPartitionOnSdCardValid(bis_mount_name.str)) { + R_TRY(this->ImportContentMetaDatabaseImpl(root, bis_mount_name.str)); + } + + R_SUCCEED(); + } + + bool ContentManagerImpl::IsNeedRebuildSystemContentMetaDatabase() { + /* TODO: Should hos::GetVersion() >= hos::Version_17_0_0 be checked? */ + + /* If we do not actually have a content meta db, we should re-build. */ + if (R_FAILED(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem))) { + return true; + } + + /* We have a content meta db. Temporarily, activate it. */ + if (R_FAILED(this->ActivateContentMetaDatabase(StorageId::BuiltInSystem))) { + return true; + } + ON_SCOPE_EXIT { this->InactivateContentMetaDatabase(StorageId::BuiltInSystem); }; + + /* Open the content meta db. */ + ContentMetaDatabase meta_db; + R_ABORT_UNLESS(ncm::OpenContentMetaDatabase(std::addressof(meta_db), StorageId::BuiltInSystem)); + + /* List the meta db's contents. */ + const auto list_count = meta_db.ListContentMeta(nullptr, 0); + + /* We need to rebuild if the db has zero entries. */ + return list_count.total == 0; + } + + Result ContentManagerImpl::Initialize(const ContentManagerConfig &config) { + /* Initialize based on whether integrated content is enabled. */ + if (config.IsIntegratedSystemContentEnabled()) { + constexpr const IntegratedContentStorageConfig IntegratedConfigsForIntegratedSystemContent[] = { + { ncm::StorageId::BuiltInSystem, { fs::ContentStorageId::System, fs::ContentStorageId::System0 }, 2, true }, + { ncm::StorageId::BuiltInUser, { fs::ContentStorageId::User }, 1, false }, + { ncm::StorageId::SdCard, { fs::ContentStorageId::SdCard }, 1, false }, + { ncm::StorageId::GameCard, { }, 0, false }, + { ncm::StorageId::Host, { }, 0, false }, + }; + constexpr const ContentStorageConfig ContentStorageConfigsForIntegratedSystemContent[] = { + { .content_storage_id = fs::ContentStorageId::System, .skip_verify_and_create = true, .skip_activate = true, }, + { .content_storage_id = fs::ContentStorageId::System0, .skip_verify_and_create = true, .skip_activate = false, }, + { .content_storage_id = fs::ContentStorageId::User, .skip_verify_and_create = false, .skip_activate = false, }, + { .content_storage_id = fs::ContentStorageId::SdCard, .skip_verify_and_create = false, .skip_activate = false, }, + }; + constexpr const ncm::StorageId ActivatedStoragesForIntegratedSystemContent[] = { + ncm::StorageId::BuiltInSystem, + }; + + R_RETURN(this->Initialize(config, IntegratedConfigsForIntegratedSystemContent, util::size(IntegratedConfigsForIntegratedSystemContent), ContentStorageConfigsForIntegratedSystemContent, util::size(ContentStorageConfigsForIntegratedSystemContent), ActivatedStoragesForIntegratedSystemContent, util::size(ActivatedStoragesForIntegratedSystemContent))); + } else { + constexpr const IntegratedContentStorageConfig IntegratedConfigs[] = { + { ncm::StorageId::BuiltInSystem, { fs::ContentStorageId::System }, 1, false }, + { ncm::StorageId::BuiltInUser, { fs::ContentStorageId::User }, 1, false }, + { ncm::StorageId::SdCard, { fs::ContentStorageId::SdCard }, 1, false }, + { ncm::StorageId::GameCard, { }, 0, false }, + { ncm::StorageId::Host, { }, 0, false }, + }; + constexpr const ContentStorageConfig ContentStorageConfigs[] = { + { .content_storage_id = fs::ContentStorageId::System, .skip_verify_and_create = false, .skip_activate = false, }, + { .content_storage_id = fs::ContentStorageId::User, .skip_verify_and_create = false, .skip_activate = false, }, + { .content_storage_id = fs::ContentStorageId::SdCard, .skip_verify_and_create = false, .skip_activate = false, }, + }; + constexpr const ncm::StorageId ActivatedStorages[] = { + ncm::StorageId::BuiltInSystem, + }; + + R_RETURN(this->Initialize(config, IntegratedConfigs, util::size(IntegratedConfigs), ContentStorageConfigs, util::size(ContentStorageConfigs), ActivatedStorages, util::size(ActivatedStorages))); + } + } + + Result ContentManagerImpl::Initialize(const ContentManagerConfig &manager_config, const IntegratedContentStorageConfig *integrated_configs, size_t num_integrated_configs, const ContentStorageConfig *configs, size_t num_configs, const ncm::StorageId *activated_storages, size_t num_activated_storages) { + std::scoped_lock lk(m_mutex); + + /* Check if we've already initialized. */ + R_SUCCEED_IF(m_initialized); + + /* Set our configs. */ + for (size_t i = 0; i < num_integrated_configs; ++i) { + m_integrated_configs[i] = integrated_configs[i]; + } + m_num_integrated_configs = num_integrated_configs; + + for (size_t i = 0; i < num_configs; ++i) { + m_configs[i] = configs[i]; + } + m_num_configs = num_configs; + + /* Setup roots. */ + m_num_integrated_content_storage_entries = 0; + m_num_content_storage_entries = 0; + m_num_integrated_content_meta_entries = 0; + m_num_content_meta_entries = 0; + for (size_t i = 0; i < m_num_integrated_configs; ++i) { + /* Get the integrated config. */ + const auto &integrated_config = m_integrated_configs[i]; + + /* Set up storage and meta db roots. */ + size_t content_storage_root_idx = m_num_content_storage_entries; + size_t content_meta_root_idx = m_num_content_meta_entries; + if (integrated_config.num_content_storage_ids > 0) { + /* If we have content storage ids, set up storage/meta db roots for each. */ + for (auto n = 0; n < integrated_config.num_content_storage_ids; n++) { + /* Get the config. */ + const auto &config = this->GetContentStorageConfig(integrated_config.content_storage_ids[n]); + R_TRY(this->InitializeContentStorageRoot(std::addressof(m_content_storage_roots[m_num_content_storage_entries++]), integrated_config.storage_id, config)); + R_TRY(this->InitializeContentMetaDatabaseRoot(std::addressof(m_content_meta_database_roots[m_num_content_meta_entries++]), integrated_config.storage_id, config)); + } + } else { + /* If we have no content storage ids, set up a single storage/meta db root. */ + R_TRY(this->InitializeContentStorageRoot(std::addressof(m_content_storage_roots[m_num_content_storage_entries++]), integrated_config.storage_id, util::nullopt)); + R_TRY(this->InitializeContentMetaDatabaseRoot(std::addressof(m_content_meta_database_roots[m_num_content_meta_entries++]), integrated_config.storage_id, util::nullopt)); + } + + R_TRY(this->InitializeIntegratedContentStorageRoot(std::addressof(m_integrated_content_storage_roots[m_num_integrated_content_storage_entries++]), std::addressof(integrated_config), content_storage_root_idx, m_num_content_storage_entries - content_storage_root_idx)); + R_TRY(this->InitializeIntegratedContentMetaDatabaseRoot(std::addressof(m_integrated_content_meta_database_roots[m_num_integrated_content_meta_entries++]), std::addressof(integrated_config), content_meta_root_idx, m_num_content_meta_entries - content_meta_root_idx)); + } + + /* Activate storages. */ + for (size_t i = 0; i < num_activated_storages; i++) { + const auto storage_id = activated_storages[i]; + if (storage_id == ncm::StorageId::BuiltInSystem) { + R_TRY(this->InitializeStorageBuiltInSystem(manager_config)); + } else { + R_TRY(this->InitializeStorage(storage_id)); + } + } + + m_initialized = true; + R_SUCCEED(); + } + + Result ContentManagerImpl::InitializeStorageBuiltInSystem(const ContentManagerConfig &manager_config) { + /* Setup and activate the storage for BuiltInSystem. */ + if (R_FAILED(this->VerifyContentStorage(StorageId::BuiltInSystem))) { + R_TRY(this->CreateContentStorage(StorageId::BuiltInSystem)); + } + R_TRY(this->ActivateContentStorage(StorageId::BuiltInSystem)); + + /* NOTE: This logic is unofficial. */ + /* Beginning with 17.0.0+, save management behavior changed. The primary symptom of this is either verify fail */ + /* or an empty kvs, both of which we can fix by performing a rebuild. */ + if (this->IsNeedRebuildSystemContentMetaDatabase()) { + /* Clean up the system content meta database, to ensure creation can succeed. */ + this->CleanupContentMetaDatabase(StorageId::BuiltInSystem); + + /* Create the content meta database. */ + R_TRY(this->CreateContentMetaDatabase(StorageId::BuiltInSystem)); + + /* Rebuild the content meta database. */ + R_TRY(this->BuildContentMetaDatabaseImpl(StorageId::BuiltInSystem)); + } + + /* Setup the content meta database for system. */ + if (R_FAILED(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem))) { + R_TRY(this->CreateContentMetaDatabase(StorageId::BuiltInSystem)); + + /* Try to build or import a database, depending on our configuration. */ + if (manager_config.ShouldBuildDatabase()) { + /* If we should build the database, do so. */ + R_TRY(this->BuildContentMetaDatabase(StorageId::BuiltInSystem)); + R_TRY(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem)); + } else if (manager_config.ShouldImportDatabaseFromSignedSystemPartitionOnSd()) { + /* Otherwise if we should import the database from the SD, do so. */ + R_TRY(this->ImportContentMetaDatabase(StorageId::BuiltInSystem, true)); + R_TRY(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem)); + } + } + + /* Ensure correct flags on the BuiltInSystem save data. */ + /* NOTE: Nintendo does not check this succeeds, and it does on older system versions. */ + /* We will not check the error, either, even though this kind of defeats the call's purpose. */ + if (hos::GetVersion() >= hos::Version_2_0_0) { + EnsureBuiltInSystemSaveDataFlags(); + } + + /* Activate the content meta database. */ + R_TRY(this->ActivateContentMetaDatabase(StorageId::BuiltInSystem)); + + R_SUCCEED(); + } + + Result ContentManagerImpl::InitializeStorage(ncm::StorageId storage_id) { + /* Setup and activate the storage. */ + if (R_FAILED(this->VerifyContentStorage(storage_id))) { + R_TRY(this->CreateContentStorage(storage_id)); + } + R_TRY(this->ActivateContentStorage(StorageId::BuiltInSystem)); + + /* Setup the content meta database for system. */ + if (R_FAILED(this->VerifyContentMetaDatabase(storage_id))) { + R_TRY(this->CreateContentMetaDatabase(storage_id)); + } + R_TRY(this->ActivateContentMetaDatabase(storage_id)); + + R_SUCCEED(); + } + + Result ContentManagerImpl::CreateContentStorage(StorageId storage_id) { + std::scoped_lock lk(m_mutex); + + /* Obtain the integrated content storage root. */ + IntegratedContentStorageRoot *root; + R_TRY(this->GetIntegratedContentStorageRoot(std::addressof(root), storage_id)); + + R_RETURN(root->Create()); + } + + Result ContentManagerImpl::CreateContentMetaDatabase(StorageId storage_id) { + std::scoped_lock lk(m_mutex); + + /* Obtain the integrated content meta database root. */ + IntegratedContentMetaDatabaseRoot *root; + R_TRY(this->GetIntegratedContentMetaDatabaseRoot(std::addressof(root), storage_id)); + + R_RETURN(root->Create()); + } + + Result ContentManagerImpl::VerifyContentStorage(StorageId storage_id) { + std::scoped_lock lk(m_mutex); + + /* Obtain the integrated content storage root. */ + IntegratedContentStorageRoot *root; + R_TRY(this->GetIntegratedContentStorageRoot(std::addressof(root), storage_id)); + + R_RETURN(root->Verify()); + } + + Result ContentManagerImpl::VerifyContentMetaDatabase(StorageId storage_id) { + std::scoped_lock lk(m_mutex); + + /* Obtain the integrated content meta database root. */ + IntegratedContentMetaDatabaseRoot *root; + R_TRY(this->GetIntegratedContentMetaDatabaseRoot(std::addressof(root), storage_id)); + + R_RETURN(root->Verify()); + } + + Result ContentManagerImpl::OpenContentStorage(sf::Out<sf::SharedPointer<IContentStorage>> out, StorageId storage_id) { + std::scoped_lock lk(m_mutex); + + /* Obtain the integrated content storage root. */ + IntegratedContentStorageRoot *root; + R_TRY(this->GetIntegratedContentStorageRoot(std::addressof(root), storage_id)); + + R_RETURN(root->Open(out, m_rights_id_cache, m_registered_host_content)); + } + + Result ContentManagerImpl::OpenContentMetaDatabase(sf::Out<sf::SharedPointer<IContentMetaDatabase>> out, StorageId storage_id) { + std::scoped_lock lk(m_mutex); + + /* Obtain the integrated content meta database root. */ + IntegratedContentMetaDatabaseRoot *root; + R_TRY(this->GetIntegratedContentMetaDatabaseRoot(std::addressof(root), storage_id)); + + R_RETURN(root->Open(out)); + } + + Result ContentManagerImpl::CloseContentStorageForcibly(StorageId storage_id) { + R_RETURN(this->InactivateContentStorage(storage_id)); + } + + Result ContentManagerImpl::CloseContentMetaDatabaseForcibly(StorageId storage_id) { + R_RETURN(this->InactivateContentMetaDatabase(storage_id)); + } + + Result ContentManagerImpl::CleanupContentMetaDatabase(StorageId storage_id) { + std::scoped_lock lk(m_mutex); + + /* Obtain the integrated content meta database root. */ + IntegratedContentMetaDatabaseRoot *root; + R_TRY(this->GetIntegratedContentMetaDatabaseRoot(std::addressof(root), storage_id)); + + R_RETURN(root->Cleanup()); + } + + Result ContentManagerImpl::ActivateContentStorage(StorageId storage_id) { + std::scoped_lock lk(m_mutex); + + /* Obtain the integrated content storage root. */ + IntegratedContentStorageRoot *root; + R_TRY(this->GetIntegratedContentStorageRoot(std::addressof(root), storage_id)); + + R_RETURN(root->Activate(m_rights_id_cache, m_registered_host_content)); + } + + Result ContentManagerImpl::InactivateContentStorage(StorageId storage_id) { + std::scoped_lock lk(m_mutex); + + /* Obtain the integrated content storage root. */ + IntegratedContentStorageRoot *root; + R_TRY(this->GetIntegratedContentStorageRoot(std::addressof(root), storage_id)); + + R_RETURN(root->Inactivate(m_registered_host_content)); + } + + Result ContentManagerImpl::ActivateContentMetaDatabase(StorageId storage_id) { + std::scoped_lock lk(m_mutex); + + /* Obtain the integrated content meta database root. */ + IntegratedContentMetaDatabaseRoot *root; + R_TRY(this->GetIntegratedContentMetaDatabaseRoot(std::addressof(root), storage_id)); + + R_RETURN(root->Activate()); + } + + Result ContentManagerImpl::InactivateContentMetaDatabase(StorageId storage_id) { + std::scoped_lock lk(m_mutex); + + /* Obtain the integrated content meta database root. */ + IntegratedContentMetaDatabaseRoot *root; + R_TRY(this->GetIntegratedContentMetaDatabaseRoot(std::addressof(root), storage_id)); + + R_RETURN(root->Inactivate()); + } + + Result ContentManagerImpl::InvalidateRightsIdCache() { + m_rights_id_cache.Invalidate(); + R_SUCCEED(); + } + + Result ContentManagerImpl::GetMemoryReport(sf::Out<MemoryReport> out) { + /* Populate content meta resource states. */ + MemoryReport report = { + .system_content_meta_resource_state = { + .peak_total_alloc_size = g_system_content_meta_memory_resource.GetPeakTotalAllocationSize(), + .peak_alloc_size = g_system_content_meta_memory_resource.GetPeakAllocationSize(), + .allocatable_size = g_system_content_meta_memory_resource.GetAllocator()->GetAllocatableSize(), + .total_free_size = g_system_content_meta_memory_resource.GetAllocator()->GetTotalFreeSize(), + }, + .sd_and_user_content_meta_resource_state { + .peak_total_alloc_size = g_sd_and_user_content_meta_memory_resource.GetPeakTotalAllocationSize(), + .peak_alloc_size = g_sd_and_user_content_meta_memory_resource.GetPeakAllocationSize(), + .allocatable_size = g_sd_and_user_content_meta_memory_resource.GetAllocator()->GetAllocatableSize(), + .total_free_size = g_sd_and_user_content_meta_memory_resource.GetAllocator()->GetTotalFreeSize(), + }, + .gamecard_content_meta_resource_state { + .peak_total_alloc_size = g_gamecard_content_meta_memory_resource.GetPeakTotalAllocationSize(), + .peak_alloc_size = g_gamecard_content_meta_memory_resource.GetPeakAllocationSize(), + .allocatable_size = g_gamecard_content_meta_memory_resource.GetAllocator()->GetAllocatableSize(), + .total_free_size = g_gamecard_content_meta_memory_resource.GetAllocator()->GetTotalFreeSize(), + }, + .heap_resource_state = {}, + }; + + /* Populate heap memory resource state. */ + GetHeapState().GetMemoryResourceState(std::addressof(report.heap_resource_state)); + + /* Output the report. */ + out.SetValue(report); + R_SUCCEED(); + } + + Result ContentManagerImpl::ActivateFsContentStorage(fs::ContentStorageId content_storage_id) { + /* Get the integrated config for the storage. */ + IntegratedContentStorageConfig *integrated_config; + R_TRY(this->GetIntegratedContentStorageConfig(std::addressof(integrated_config), content_storage_id)); + + { + /* Obtain the integrated content meta database root. */ + IntegratedContentStorageRoot *root; + R_TRY(this->GetIntegratedContentStorageRoot(std::addressof(root), integrated_config->storage_id)); + + R_TRY(root->Activate(m_rights_id_cache, m_registered_host_content, content_storage_id)); + } + { + /* Obtain the integrated content meta database root. */ + IntegratedContentMetaDatabaseRoot *root; + R_TRY(this->GetIntegratedContentMetaDatabaseRoot(std::addressof(root), integrated_config->storage_id)); + + R_TRY(root->Activate(content_storage_id)); + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_meta.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_meta.cpp new file mode 100644 index 00000000..02f648a5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_meta.cpp @@ -0,0 +1,439 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ncm { + + namespace { + + void ConvertPackageContentMetaHeaderToContentMetaHeader(ContentMetaHeader *dst, const PackagedContentMetaHeader &src) { + /* Set destination. */ + *dst = { + .extended_header_size = src.extended_header_size, + .content_count = src.content_count, + .content_meta_count = src.content_meta_count, + .attributes = src.attributes, + .platform = src.platform, + }; + } + + void ConvertPackageContentMetaHeaderToInstallContentMetaHeader(InstallContentMetaHeader *dst, const PackagedContentMetaHeader &src) { + /* Set destination. */ + static_assert(std::is_same<InstallContentMetaHeader, PackagedContentMetaHeader>::value); + std::memcpy(dst, std::addressof(src), sizeof(*dst)); + } + + void ConvertInstallContentMetaHeaderToContentMetaHeader(ContentMetaHeader *dst, const InstallContentMetaHeader &src) { + /* Set destination. */ + *dst = { + .extended_header_size = src.extended_header_size, + .content_count = src.content_count, + .content_meta_count = src.content_meta_count, + .attributes = src.attributes, + .platform = src.platform, + }; + } + + Result FindDeltaIndex(s32 *out_index, const PatchMetaExtendedDataReader &reader, u32 src_version, u32 dst_version) { + /* Iterate over all deltas. */ + auto header = reader.GetHeader(); + for (s32 i = 0; i < static_cast<s32>(header->delta_count); i++) { + /* Check if the current delta matches the versions. */ + auto delta = reader.GetPatchDeltaHeader(i); + if ((src_version == 0 || delta->delta.source_version == src_version) && delta->delta.destination_version == dst_version) { + *out_index = i; + R_SUCCEED(); + } + } + + /* We didn't find the delta. */ + R_THROW(ncm::ResultDeltaNotFound()); + } + + s32 CountContentExceptForMeta(const PatchMetaExtendedDataReader &reader, s32 delta_index) { + /* Iterate over packaged content infos, checking for those which aren't metas. */ + s32 count = 0; + auto delta = reader.GetPatchDeltaHeader(delta_index); + for (s32 i = 0; i < static_cast<s32>(delta->content_count); i++) { + if (reader.GetPatchDeltaPackagedContentInfo(delta_index, i)->GetType() != ContentType::Meta) { + count++; + } + } + + return count; + } + + } + + size_t PackagedContentMetaReader::CalculateConvertInstallContentMetaSize() const { + /* Prepare the header. */ + const auto *header = this->GetHeader(); + + if ((header->type == ContentMetaType::SystemUpdate && this->GetExtendedHeaderSize() > 0) || header->type == ContentMetaType::Delta) { + /* Newer SystemUpdates and Deltas contain extended data. */ + return this->CalculateSizeImpl<InstallContentMetaHeader, InstallContentInfo>(header->extended_header_size, header->content_count + 1, header->content_meta_count, this->GetExtendedDataSize(), false); + } else if (header->type == ContentMetaType::Patch) { + /* Subtract the number of delta fragments for patches, include extended data. */ + return this->CalculateSizeImpl<InstallContentMetaHeader, InstallContentInfo>(header->extended_header_size, header->content_count - this->CountDeltaFragments() + 1, header->content_meta_count, this->GetExtendedDataSize(), false); + } + + /* No extended data or delta fragments by default. */ + return this->CalculateSizeImpl<InstallContentMetaHeader, InstallContentInfo>(header->extended_header_size, header->content_count + 1, header->content_meta_count, 0, false); + } + + size_t PackagedContentMetaReader::CountDeltaFragments() const { + size_t count = 0; + for (size_t i = 0; i < this->GetContentCount(); i++) { + if (this->GetContentInfo(i)->GetType() == ContentType::DeltaFragment) { + count++; + } + } + return count; + } + + size_t PackagedContentMetaReader::CalculateConvertContentMetaSize() const { + const auto *header = this->GetHeader(); + return this->CalculateSizeImpl<ContentMetaHeader, ContentInfo>(header->extended_header_size, header->content_count + 1, header->content_meta_count, 0, false); + } + + void PackagedContentMetaReader::ConvertToInstallContentMeta(void *dst, size_t size, const InstallContentInfo &meta) { + /* Ensure we have enough space to convert. */ + AMS_ABORT_UNLESS(size >= this->CalculateConvertInstallContentMetaSize()); + + /* Prepare for conversion. */ + const auto *packaged_header = this->GetHeader(); + uintptr_t dst_addr = reinterpret_cast<uintptr_t>(dst); + + /* Convert the header. */ + InstallContentMetaHeader header; + ConvertPackageContentMetaHeaderToInstallContentMetaHeader(std::addressof(header), *packaged_header); + header.content_count += 1; + + /* Don't include deltas. */ + if (packaged_header->type == ContentMetaType::Patch) { + header.content_count -= this->CountDeltaFragments(); + } + + /* Copy the header. */ + std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(header), sizeof(header)); + dst_addr += sizeof(header); + + /* Copy the extended header. */ + std::memcpy(reinterpret_cast<void *>(dst_addr), reinterpret_cast<void *>(this->GetExtendedHeaderAddress()), packaged_header->extended_header_size); + dst_addr += packaged_header->extended_header_size; + + /* Copy the top level meta. */ + std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(meta), sizeof(meta)); + dst_addr += sizeof(meta); + + /* Copy content infos. */ + for (size_t i = 0; i < this->GetContentCount(); i++) { + const auto *packaged_content_info = this->GetContentInfo(i); + + /* Don't copy any delta fragments. */ + if (packaged_header->type == ContentMetaType::Patch) { + if (packaged_content_info->GetType() == ContentType::DeltaFragment) { + continue; + } + } + + /* Create the install content info. */ + InstallContentInfo install_content_info = InstallContentInfo::Make(*packaged_content_info, packaged_header->type); + + /* Copy the info. */ + std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(install_content_info), sizeof(InstallContentInfo)); + dst_addr += sizeof(InstallContentInfo); + } + + /* Copy content meta infos. */ + for (size_t i = 0; i < this->GetContentMetaCount(); i++) { + std::memcpy(reinterpret_cast<void *>(dst_addr), this->GetContentMetaInfo(i), sizeof(ContentMetaInfo)); + dst_addr += sizeof(ContentMetaInfo); + } + } + + Result PackagedContentMetaReader::ConvertToFragmentOnlyInstallContentMeta(void *dst, size_t size, const InstallContentInfo &meta, u32 source_version) { + /* Ensure that we have enough space. */ + size_t required_size; + R_TRY(this->CalculateConvertFragmentOnlyInstallContentMetaSize(std::addressof(required_size), source_version)); + AMS_ABORT_UNLESS(size >= required_size); + + /* Find the delta index. */ + PatchMetaExtendedDataReader reader(this->GetExtendedData(), this->GetExtendedDataSize()); + s32 index; + R_TRY(FindDeltaIndex(std::addressof(index), reader, source_version, this->GetKey().version)); + auto delta = reader.GetPatchDeltaHeader(index); + + /* Prepare for conversion. */ + const auto *packaged_header = this->GetHeader(); + uintptr_t dst_addr = reinterpret_cast<uintptr_t>(dst); + + /* Convert the header. */ + InstallContentMetaHeader header; + ConvertPackageContentMetaHeaderToInstallContentMetaHeader(std::addressof(header), *packaged_header); + header.install_type = ContentInstallType::FragmentOnly; + + /* Set the content count. */ + auto fragment_count = CountContentExceptForMeta(reader, index); + header.content_count = static_cast<u16>(fragment_count) + 1; + + /* Copy the header. */ + std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(header), sizeof(header)); + dst_addr += sizeof(header); + + /* Copy the extended header. */ + std::memcpy(reinterpret_cast<void *>(dst_addr), reinterpret_cast<void *>(this->GetExtendedHeaderAddress()), packaged_header->extended_header_size); + dst_addr += packaged_header->extended_header_size; + + /* Copy the top level meta. */ + std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(meta), sizeof(meta)); + dst_addr += sizeof(meta); + + s32 count = 0; + for (s32 i = 0; i < static_cast<s32>(delta->content_count); i++) { + auto packaged_content_info = reader.GetPatchDeltaPackagedContentInfo(index, i); + if (packaged_content_info->GetType() != ContentType::Meta) { + /* Create the install content info. */ + InstallContentInfo install_content_info = InstallContentInfo::Make(*packaged_content_info, packaged_header->type); + + /* Copy the info. */ + std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(install_content_info), sizeof(InstallContentInfo)); + dst_addr += sizeof(InstallContentInfo); + + /* Increment the count. */ + count++; + } + } + + /* Assert that we copied the right number of infos. */ + AMS_ASSERT(count == fragment_count); + + R_SUCCEED(); + } + + Result PackagedContentMetaReader::CalculateConvertFragmentOnlyInstallContentMetaSize(size_t *out_size, u32 source_version) const { + /* Find the delta index. */ + PatchMetaExtendedDataReader reader(this->GetExtendedData(), this->GetExtendedDataSize()); + s32 index; + R_TRY(FindDeltaIndex(std::addressof(index), reader, source_version, this->GetKey().version)); + + /* Get the fragment count. */ + const auto fragment_count = CountContentExceptForMeta(reader, index); + + /* Recalculate. */ + *out_size = this->CalculateConvertFragmentOnlyInstallContentMetaSize(fragment_count); + R_SUCCEED(); + } + + void PackagedContentMetaReader::ConvertToContentMeta(void *dst, size_t size, const ContentInfo &meta) { + /* Ensure we have enough space to convert. */ + AMS_ABORT_UNLESS(size >= this->CalculateConvertContentMetaSize()); + + /* Prepare for conversion. */ + const auto *packaged_header = this->GetHeader(); + uintptr_t dst_addr = reinterpret_cast<uintptr_t>(dst); + + /* Convert the header. */ + ContentMetaHeader header; + ConvertPackageContentMetaHeaderToContentMetaHeader(std::addressof(header), *packaged_header); + header.content_count += 1; + + /* Don't include deltas. */ + if (packaged_header->type == ContentMetaType::Patch) { + header.content_count -= this->CountDeltaFragments(); + } + + /* Copy the header. */ + std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(header), sizeof(header)); + dst_addr += sizeof(header); + + /* Copy the extended header. */ + std::memcpy(reinterpret_cast<void *>(dst_addr), reinterpret_cast<void *>(this->GetExtendedHeaderAddress()), packaged_header->extended_header_size); + dst_addr += packaged_header->extended_header_size; + + /* Copy the top level meta. */ + std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(meta), sizeof(meta)); + dst_addr += sizeof(meta); + + /* Copy content infos. */ + for (size_t i = 0; i < this->GetContentCount(); i++) { + /* Don't copy any delta fragments. */ + if (packaged_header->type == ContentMetaType::Patch) { + if (this->GetContentInfo(i)->GetType() == ContentType::DeltaFragment) { + continue; + } + } + + /* Copy the current info. */ + std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(this->GetContentInfo(i)->info), sizeof(ContentInfo)); + dst_addr += sizeof(ContentInfo); + } + + /* Copy content meta infos. */ + for (size_t i = 0; i < this->GetContentMetaCount(); i++) { + std::memcpy(reinterpret_cast<void *>(dst_addr), this->GetContentMetaInfo(i), sizeof(ContentMetaInfo)); + dst_addr += sizeof(ContentMetaInfo); + } + } + + size_t InstallContentMetaReader::CalculateConvertSize() const { + return CalculateSizeImpl<ContentMetaHeader, ContentInfo>(this->GetExtendedHeaderSize(), this->GetContentCount(), this->GetContentMetaCount(), this->GetExtendedDataSize(), false); + } + + void InstallContentMetaReader::ConvertToContentMeta(void *dst, size_t size) const { + /* Ensure we have enough space to convert. */ + AMS_ABORT_UNLESS(size >= this->CalculateConvertSize()); + + /* Prepare for conversion. */ + const auto *install_header = this->GetHeader(); + uintptr_t dst_addr = reinterpret_cast<uintptr_t>(dst); + + /* Convert the header. */ + ContentMetaHeader header; + ConvertInstallContentMetaHeaderToContentMetaHeader(std::addressof(header), *install_header); + + /* Copy the header. */ + std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(header), sizeof(header)); + dst_addr += sizeof(header); + + /* Copy the extended header. */ + std::memcpy(reinterpret_cast<void *>(dst_addr), reinterpret_cast<void *>(this->GetExtendedHeaderAddress()), install_header->extended_header_size); + dst_addr += install_header->extended_header_size; + + /* Copy content infos. */ + for (size_t i = 0; i < this->GetContentCount(); i++) { + /* Copy the current info. */ + std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(this->GetContentInfo(i)->info), sizeof(ContentInfo)); + dst_addr += sizeof(ContentInfo); + } + + /* Copy content meta infos. */ + for (size_t i = 0; i < this->GetContentMetaCount(); i++) { + std::memcpy(reinterpret_cast<void *>(dst_addr), this->GetContentMetaInfo(i), sizeof(ContentMetaInfo)); + dst_addr += sizeof(ContentMetaInfo); + } + } + + Result MetaConverter::CountContentExceptForMeta(s32 *out, PatchMetaExtendedDataAccessor *accessor, const PatchDeltaHeader &header, s32 delta_index) { + /* Get the count. */ + s32 count = 0; + + for (auto i = 0; i < static_cast<int>(header.content_count); ++i) { + /* Get the delta content info. */ + PackagedContentInfo content_info; + R_TRY(accessor->GetPatchDeltaContentInfo(std::addressof(content_info), delta_index, i)); + + if (content_info.GetType() != ContentType::Meta) { + ++count; + } + } + + *out = count; + R_SUCCEED(); + } + + Result MetaConverter::FindDeltaIndex(s32 *out, PatchMetaExtendedDataAccessor *accessor, u32 source_version, u32 destination_version) { + /* Get the header. */ + PatchMetaExtendedDataHeader header; + header.delta_count = 0; + R_TRY(accessor->GetHeader(std::addressof(header))); + + /* Iterate over all deltas. */ + for (s32 i = 0; i < static_cast<s32>(header.delta_count); i++) { + /* Get the current patch delta header. */ + PatchDeltaHeader delta_header; + R_TRY(accessor->GetPatchDeltaHeader(std::addressof(delta_header), i)); + + /* Check if the current delta matches the versions. */ + if ((source_version == 0 || delta_header.delta.source_version == source_version) && delta_header.delta.destination_version == destination_version) { + *out = i; + R_SUCCEED(); + } + } + + /* We didn't find the delta. */ + R_THROW(ncm::ResultDeltaNotFound()); + } + + Result MetaConverter::GetFragmentOnlyInstallContentMeta(AutoBuffer *out, const InstallContentInfo &meta, const PackagedContentMetaReader &reader, PatchMetaExtendedDataAccessor *accessor, u32 source_version) { + /* Find the appropriate delta index. */ + s32 delta_index = 0; + R_TRY(FindDeltaIndex(std::addressof(delta_index), accessor, source_version, reader.GetHeader()->version)); + + /* Get the delta header. */ + PatchDeltaHeader delta_header; + R_TRY(accessor->GetPatchDeltaHeader(std::addressof(delta_header), delta_index)); + + /* Count content except for meta. */ + s32 fragment_count = 0; + R_TRY(CountContentExceptForMeta(std::addressof(fragment_count), accessor, delta_header, delta_index)); + + /* Determine the required size. */ + const size_t meta_size = reader.CalculateConvertFragmentOnlyInstallContentMetaSize(fragment_count); + + /* Initialize the out buffer. */ + R_TRY(out->Initialize(meta_size)); + + /* Prepare for conversion. */ + const auto *packaged_header = reader.GetHeader(); + uintptr_t dst_addr = reinterpret_cast<uintptr_t>(out->Get()); + + /* Convert the header. */ + InstallContentMetaHeader header; + ConvertPackageContentMetaHeaderToInstallContentMetaHeader(std::addressof(header), *packaged_header); + header.install_type = ContentInstallType::FragmentOnly; + + /* Set the content count. */ + header.content_count = static_cast<u16>(fragment_count) + 1; + + /* Copy the header. */ + std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(header), sizeof(header)); + dst_addr += sizeof(header); + + /* Copy the extended header. */ + std::memcpy(reinterpret_cast<void *>(dst_addr), reader.GetExtendedHeader<void>(), packaged_header->extended_header_size); + dst_addr += packaged_header->extended_header_size; + + /* Copy the top level meta. */ + std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(meta), sizeof(meta)); + dst_addr += sizeof(meta); + + s32 count = 0; + for (s32 i = 0; i < static_cast<s32>(delta_header.content_count); i++) { + /* Get the delta content info. */ + PackagedContentInfo content_info; + R_TRY(accessor->GetPatchDeltaContentInfo(std::addressof(content_info), delta_index, i)); + + if (content_info.GetType() != ContentType::Meta) { + /* Create the install content info. */ + InstallContentInfo install_content_info = InstallContentInfo::Make(content_info, packaged_header->type); + + /* Copy the info. */ + std::memcpy(reinterpret_cast<void *>(dst_addr), std::addressof(install_content_info), sizeof(InstallContentInfo)); + dst_addr += sizeof(InstallContentInfo); + + /* Increment the count. */ + count++; + } + } + + /* Assert that we copied the right number of infos. */ + AMS_ASSERT(count == fragment_count); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_meta_database_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_meta_database_impl.cpp new file mode 100644 index 00000000..4cf5175b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_meta_database_impl.cpp @@ -0,0 +1,567 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ncm_content_meta_database_impl.hpp" + +namespace ams::ncm { + + Result ContentMetaDatabaseImpl::GetContentInfoImpl(ContentInfo *out, const ContentMetaKey &key, ContentType type, util::optional<u8> id_offset) const { + R_TRY(this->EnsureEnabled()); + + /* Find the meta key. */ + const auto it = m_kvs->lower_bound(key); + R_UNLESS(it != m_kvs->end(), ncm::ResultContentMetaNotFound()); + R_UNLESS(it->GetKey().id == key.id, ncm::ResultContentMetaNotFound()); + + const auto found_key = it->GetKey(); + + /* Create a reader for this content meta. */ + const void *meta; + size_t meta_size; + R_TRY(this->GetContentMetaPointer(&meta, &meta_size, found_key)); + + ContentMetaReader reader(meta, meta_size); + + /* Find the content info. */ + const ContentInfo *content_info = nullptr; + if (id_offset) { + content_info = reader.GetContentInfo(type, *id_offset); + } else { + content_info = reader.GetContentInfo(type); + } + R_UNLESS(content_info != nullptr, ncm::ResultContentNotFound()); + + /* Save output. */ + *out = *content_info; + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::Set(const ContentMetaKey &key, const sf::InBuffer &value) { + R_TRY(this->EnsureEnabled()); + R_RETURN(m_kvs->Set(key, value.GetPointer(), value.GetSize())); + } + + Result ContentMetaDatabaseImpl::Get(sf::Out<u64> out_size, const ContentMetaKey &key, const sf::OutBuffer &out_value) { + R_TRY(this->EnsureEnabled()); + + /* Get the entry from our key-value store. */ + size_t size; + R_TRY_CATCH(m_kvs->Get(std::addressof(size), out_value.GetPointer(), out_value.GetSize(), key)) { + R_CONVERT(kvdb::ResultKeyNotFound, ncm::ResultContentMetaNotFound()) + } R_END_TRY_CATCH; + + out_size.SetValue(size); + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::Remove(const ContentMetaKey &key) { + R_TRY(this->EnsureEnabled()); + + R_TRY_CATCH(m_kvs->Remove(key)) { + R_CONVERT(kvdb::ResultKeyNotFound, ncm::ResultContentMetaNotFound()) + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::GetContentIdByType(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type) { + R_RETURN(this->GetContentIdImpl(out_content_id.GetPointer(), key, type, util::nullopt)); + } + + Result ContentMetaDatabaseImpl::ListContentInfo(sf::Out<s32> out_count, const sf::OutArray<ContentInfo> &out_info, const ContentMetaKey &key, s32 offset) { + R_UNLESS(offset >= 0, ncm::ResultInvalidOffset()); + R_TRY(this->EnsureEnabled()); + + /* Obtain the content meta for the given key. */ + const void *meta; + size_t meta_size; + R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + + /* Create a reader. */ + ContentMetaReader reader(meta, meta_size); + + /* Read content infos from the given offset up to the given count. */ + size_t count; + for (count = 0; count < out_info.GetSize() && count + offset < reader.GetContentCount(); count++) { + out_info[count] = *reader.GetContentInfo(offset + count); + } + + out_count.SetValue(count); + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::List(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType meta_type, ApplicationId application_id, u64 min, u64 max, ContentInstallType install_type) { + R_TRY(this->EnsureEnabled()); + + size_t entries_total = 0; + size_t entries_written = 0; + + /* Iterate over all entries. */ + for (auto &entry : *m_kvs) { + const ContentMetaKey key = entry.GetKey(); + + /* Check if this entry matches the given filters. */ + if (!((meta_type == ContentMetaType::Unknown || key.type == meta_type) && (min <= key.id && key.id <= max) && (install_type == ContentInstallType::Unknown || key.install_type == install_type))) { + continue; + } + + /* If application id is present, check if it matches the filter. */ + if (application_id != InvalidApplicationId) { + /* Obtain the content meta for the key. */ + const void *meta; + size_t meta_size; + R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + + /* Create a reader. */ + ContentMetaReader reader(meta, meta_size); + + /* Ensure application id matches, if present. */ + if (const auto entry_application_id = reader.GetApplicationId(key); entry_application_id && application_id != *entry_application_id) { + continue; + } + } + + /* Write the entry to the output buffer. */ + if (entries_written < out_info.GetSize()) { + out_info[entries_written++] = key; + } + entries_total++; + } + + out_entries_total.SetValue(entries_total); + out_entries_written.SetValue(entries_written); + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, u64 id) { + R_TRY(this->EnsureEnabled()); + + util::optional<ContentMetaKey> found_key = util::nullopt; + + /* Find the last key with the desired program id. */ + for (auto entry = m_kvs->lower_bound(ContentMetaKey::MakeUnknownType(id, 0)); entry != m_kvs->end(); entry++) { + /* No further entries will match the program id, discontinue. */ + if (entry->GetKey().id != id) { + break; + } + + /* We are only interested in keys with the Full content install type. */ + if (entry->GetKey().install_type == ContentInstallType::Full) { + found_key = entry->GetKey(); + } + } + + /* Check if the key is absent. */ + R_UNLESS(found_key, ncm::ResultContentMetaNotFound()); + + out_key.SetValue(*found_key); + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::ListApplication(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ApplicationContentMetaKey> &out_keys, ContentMetaType type) { + R_TRY(this->EnsureEnabled()); + + size_t entries_total = 0; + size_t entries_written = 0; + + /* Iterate over all entries. */ + for (auto &entry : *m_kvs) { + const ContentMetaKey key = entry.GetKey(); + + /* Check if this entry matches the given filters. */ + if (!(type == ContentMetaType::Unknown || key.type == type)) { + continue; + } + + /* Check if the entry has an application id. */ + ContentMetaReader reader(entry.GetValuePointer(), entry.GetValueSize()); + + if (const auto entry_application_id = reader.GetApplicationId(key); entry_application_id) { + /* Write the entry to the output buffer. */ + if (entries_written < out_keys.GetSize()) { + out_keys[entries_written++] = { key, *entry_application_id }; + } + entries_total++; + } + } + + out_entries_total.SetValue(entries_total); + out_entries_written.SetValue(entries_written); + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::Has(sf::Out<bool> out, const ContentMetaKey &key) { + R_TRY(this->EnsureEnabled()); + + *out = false; + + /* Check if key is present. */ + size_t size; + R_TRY_CATCH(m_kvs->GetValueSize(&size, key)) { + R_CONVERT(kvdb::ResultKeyNotFound, ResultSuccess()); + } R_END_TRY_CATCH; + + *out = true; + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::HasAll(sf::Out<bool> out, const sf::InArray<ContentMetaKey> &keys) { + R_TRY(this->EnsureEnabled()); + *out = false; + + /* Check if keys are present. */ + for (size_t i = 0; i < keys.GetSize(); i++) { + /* Check if we have the current key. */ + bool has; + R_TRY(this->Has(std::addressof(has), keys[i])); + + /* If we don't, then we can early return because we don't have all. */ + R_SUCCEED_IF(!has); + } + + *out = true; + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::GetSize(sf::Out<u64> out_size, const ContentMetaKey &key) { + R_TRY(this->EnsureEnabled()); + + /* Determine the content meta size for the key. */ + size_t size; + R_TRY(this->GetContentMetaSize(&size, key)); + + out_size.SetValue(size); + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::GetRequiredSystemVersion(sf::Out<u32> out_version, const ContentMetaKey &key) { + R_TRY(this->EnsureEnabled()); + + /* Only applications and patches have a required system version. */ + R_UNLESS(key.type == ContentMetaType::Application || key.type == ContentMetaType::Patch, ncm::ResultInvalidContentMetaKey()); + + /* Obtain the content meta for the key. */ + const void *meta; + size_t meta_size; + R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + + /* Create a reader. */ + ContentMetaReader reader(meta, meta_size); + + /* Obtain the required system version. */ + switch (key.type) { + case ContentMetaType::Application: + out_version.SetValue(reader.GetExtendedHeader<ApplicationMetaExtendedHeader>()->required_system_version); + break; + case ContentMetaType::Patch: + out_version.SetValue(reader.GetExtendedHeader<PatchMetaExtendedHeader>()->required_system_version); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::GetPatchContentMetaId(sf::Out<u64> out_patch_id, const ContentMetaKey &key) { + R_TRY(this->EnsureEnabled()); + + /* Only applications can have patches. */ + R_UNLESS(key.type == ContentMetaType::Application || key.type == ContentMetaType::AddOnContent, ncm::ResultInvalidContentMetaKey()); + + /* Obtain the content meta for the key. */ + const void *meta; + size_t meta_size; + R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + + /* Create a reader. */ + ContentMetaReader reader(meta, meta_size); + + /* Obtain the patch id. */ + switch (key.type) { + case ContentMetaType::Application: + out_patch_id.SetValue(reader.GetExtendedHeader<ApplicationMetaExtendedHeader>()->patch_id.value); + break; + case ContentMetaType::AddOnContent: + R_UNLESS(reader.GetExtendedHeaderSize() == sizeof(AddOnContentMetaExtendedHeader), ncm::ResultInvalidAddOnContentMetaExtendedHeader()); + out_patch_id.SetValue(reader.GetExtendedHeader<AddOnContentMetaExtendedHeader>()->data_patch_id.value); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::DisableForcibly() { + m_disabled = true; + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) { + R_TRY(this->EnsureEnabled()); + R_UNLESS(out_orphaned.GetSize() >= content_ids.GetSize(), ncm::ResultBufferInsufficient()); + + /* Default to orphaned for all content ids. */ + for (size_t i = 0; i < out_orphaned.GetSize(); i++) { + out_orphaned[i] = true; + } + + auto IsOrphanedContent = [](const sf::InArray<ContentId> &list, const ncm::ContentId &id) ALWAYS_INLINE_LAMBDA -> util::optional<size_t> { + /* Check if any input content ids match our found content id. */ + for (size_t i = 0; i < list.GetSize(); i++) { + if (list[i] == id) { + return util::make_optional(i); + } + } + + return util::nullopt; + }; + + /* Iterate over all entries. */ + for (auto &entry : *m_kvs) { + ContentMetaReader reader(entry.GetValuePointer(), entry.GetValueSize()); + + /* Check if any of this entry's content infos matches one of the content ids for lookup. */ + /* If they do, then the content id isn't orphaned. */ + for (size_t i = 0; i < reader.GetContentCount(); i++) { + if (auto found = IsOrphanedContent(content_ids, reader.GetContentInfo(i)->GetId()); found) { + out_orphaned[*found] = false; + } + } + } + + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::Commit() { + R_TRY(this->EnsureEnabled()); + + /* Save and commit. */ + R_TRY(m_kvs->Save()); + R_RETURN(fs::CommitSaveData(m_mount_name)); + } + + Result ContentMetaDatabaseImpl::HasContent(sf::Out<bool> out, const ContentMetaKey &key, const ContentId &content_id) { + /* Obtain the content meta for the key. */ + const void *meta; + size_t meta_size; + R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + + /* Create a reader. */ + ContentMetaReader reader(meta, meta_size); + + /* Optimistically suppose that we will find the content. */ + out.SetValue(true); + + /* Check if any content infos contain a matching id. */ + for (size_t i = 0; i < reader.GetContentCount(); i++) { + R_SUCCEED_IF(content_id == reader.GetContentInfo(i)->GetId()); + } + + /* We didn't find a content info. */ + out.SetValue(false); + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::ListContentMetaInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaInfo> &out_meta_info, const ContentMetaKey &key, s32 offset) { + R_UNLESS(offset >= 0, ncm::ResultInvalidOffset()); + R_TRY(this->EnsureEnabled()); + + /* Obtain the content meta for the key. */ + const void *meta; + size_t meta_size; + R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + + /* Create a reader. */ + ContentMetaReader reader(meta, meta_size); + + /* Read content meta infos from the given offset up to the given count. */ + size_t count; + for (count = 0; count < out_meta_info.GetSize() && count + offset < reader.GetContentMetaCount(); count++) { + out_meta_info[count] = *reader.GetContentMetaInfo(count + offset); + } + + /* Set the ouput value. */ + out_entries_written.SetValue(count); + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::GetAttributes(sf::Out<u8> out_attributes, const ContentMetaKey &key) { + R_TRY(this->EnsureEnabled()); + + /* Obtain the content meta for the key. */ + const void *meta; + size_t meta_size; + R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + + /* Create a reader. */ + ContentMetaReader reader(meta, meta_size); + + /* Set the ouput value. */ + out_attributes.SetValue(reader.GetHeader()->attributes); + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::GetRequiredApplicationVersion(sf::Out<u32> out_version, const ContentMetaKey &key) { + R_TRY(this->EnsureEnabled()); + + /* Obtain the content meta for the key. */ + const void *meta; + size_t meta_size; + R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + + /* Create a reader. */ + ContentMetaReader reader(meta, meta_size); + + /* Get the required version. */ + u32 required_version; + switch (key.type) { + case ContentMetaType::AddOnContent: + required_version = reader.GetExtendedHeader<AddOnContentMetaExtendedHeader>()->required_application_version; + break; + case ContentMetaType::Application: + /* As of 9.0.0, applications can be dependent on a specific base application version. */ + AMS_ABORT_UNLESS(hos::GetVersion() >= hos::Version_9_0_0); + required_version = reader.GetExtendedHeader<ApplicationMetaExtendedHeader>()->required_application_version; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Set the ouput value. */ + out_version.SetValue(required_version); + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::GetContentIdByTypeAndIdOffset(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type, u8 id_offset) { + R_RETURN(this->GetContentIdImpl(out_content_id.GetPointer(), key, type, util::make_optional(id_offset))); + } + + Result ContentMetaDatabaseImpl::GetCount(sf::Out<u32> out_count) { + R_TRY(this->EnsureEnabled()); + out_count.SetValue(m_kvs->GetCount()); + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::GetOwnerApplicationId(sf::Out<ApplicationId> out_id, const ContentMetaKey &key) { + R_TRY(this->EnsureEnabled()); + + /* Ensure this type of key has an owner. */ + R_UNLESS(key.type == ContentMetaType::Application || key.type == ContentMetaType::Patch || key.type == ContentMetaType::AddOnContent, ncm::ResultInvalidContentMetaKey()); + + /* Applications are their own owner. */ + if (key.type == ContentMetaType::Application) { + out_id.SetValue({key.id}); + R_SUCCEED(); + } + + /* Obtain the content meta for the key. */ + const void *meta; + size_t meta_size; + R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + + /* Create a reader. */ + ContentMetaReader reader(meta, meta_size); + + /* Get the owner application id. */ + ApplicationId owner_application_id; + switch (key.type) { + case ContentMetaType::Patch: + owner_application_id = reader.GetExtendedHeader<PatchMetaExtendedHeader>()->application_id; + break; + case ContentMetaType::AddOnContent: + owner_application_id = reader.GetExtendedHeader<AddOnContentMetaExtendedHeader>()->application_id; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Set the output value. */ + out_id.SetValue(owner_application_id); + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::GetContentAccessibilities(sf::Out<u8> out_accessibilities, const ContentMetaKey &key) { + R_TRY(this->EnsureEnabled()); + + /* Ensure this type of key is for an add-on content. */ + R_UNLESS(key.type == ContentMetaType::AddOnContent, ncm::ResultInvalidContentMetaKey()); + + /* Obtain the content meta for the key. */ + const void *meta; + size_t meta_size; + R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + + /* Create a reader. */ + ContentMetaReader reader(meta, meta_size); + + /* Set the ouput value. */ + out_accessibilities.SetValue(reader.GetExtendedHeader<AddOnContentMetaExtendedHeader>()->content_accessibilities); + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::GetContentInfoByType(sf::Out<ContentInfo> out_content_info, const ContentMetaKey &key, ContentType type) { + R_RETURN(this->GetContentInfoImpl(out_content_info.GetPointer(), key, type, util::nullopt)); + } + + Result ContentMetaDatabaseImpl::GetContentInfoByTypeAndIdOffset(sf::Out<ContentInfo> out_content_info, const ContentMetaKey &key, ContentType type, u8 id_offset) { + R_RETURN(this->GetContentInfoImpl(out_content_info.GetPointer(), key, type, util::make_optional(id_offset))); + } + + Result ContentMetaDatabaseImpl::GetPlatform(sf::Out<ncm::ContentMetaPlatform> out, const ContentMetaKey &key) { + R_TRY(this->EnsureEnabled()); + + /* Obtain the content meta for the key. */ + const void *meta; + size_t meta_size; + R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + + /* Create a reader. */ + ContentMetaReader reader(meta, meta_size); + + /* Set the ouput value. */ + out.SetValue(reader.GetHeader()->platform); + R_SUCCEED(); + } + + Result ContentMetaDatabaseImpl::HasAttributes(sf::Out<u8> out, u8 attribute_mask) { + R_TRY(this->EnsureEnabled()); + + /* Create a variable to hold the combined attributes. */ + u8 combined_attributes = 0; + + /* Iterate over all entries. */ + for (auto &entry : *m_kvs) { + /* Obtain the content meta for the key. */ + const void *meta; + size_t meta_size; + R_TRY(this->GetContentMetaPointer(&meta, &meta_size, entry.GetKey())); + + /* Create a reader. */ + ContentMetaReader reader(meta, meta_size); + + /* Accumulate the set attributes from the current entry. */ + combined_attributes |= (reader.GetHeader()->attributes) & attribute_mask; + + /* If all the attributes we're looking for have been found, we're done. */ + if ((combined_attributes & attribute_mask) == attribute_mask) { + break; + } + } + + /* Set the output. */ + *out = combined_attributes; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_meta_database_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_meta_database_impl.hpp new file mode 100644 index 00000000..b4e3803d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_meta_database_impl.hpp @@ -0,0 +1,71 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "ncm_content_meta_database_impl_base.hpp" + +namespace ams::ncm { + + class ContentMetaDatabaseImpl : public ContentMetaDatabaseImplBase { + public: + ContentMetaDatabaseImpl(ContentMetaKeyValueStore *kvs, const char *mount_name) : ContentMetaDatabaseImplBase(kvs, mount_name) { /* ... */ } + ContentMetaDatabaseImpl(ContentMetaKeyValueStore *kvs) : ContentMetaDatabaseImplBase(kvs) { /* ... */ } + private: + /* Helpers. */ + Result GetContentInfoImpl(ContentInfo *out, const ContentMetaKey &key, ContentType type, util::optional<u8> id_offset) const; + + Result GetContentIdImpl(ContentId *out, const ContentMetaKey &key, ContentType type, util::optional<u8> id_offset) const { + /* Get the content info. */ + ContentInfo content_info; + R_TRY(this->GetContentInfoImpl(std::addressof(content_info), key, type, id_offset)); + + /* Set the output id. */ + *out = content_info.GetId(); + R_SUCCEED(); + } + public: + /* Actual commands. */ + virtual Result Set(const ContentMetaKey &key, const sf::InBuffer &value) override; + virtual Result Get(sf::Out<u64> out_size, const ContentMetaKey &key, const sf::OutBuffer &out_value) override; + virtual Result Remove(const ContentMetaKey &key) override; + virtual Result GetContentIdByType(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type) override; + virtual Result ListContentInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentInfo> &out_info, const ContentMetaKey &key, s32 offset) override; + virtual Result List(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType meta_type, ApplicationId application_id, u64 min, u64 max, ContentInstallType install_type) override; + virtual Result GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, u64 id) override; + virtual Result ListApplication(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ApplicationContentMetaKey> &out_keys, ContentMetaType meta_type) override; + virtual Result Has(sf::Out<bool> out, const ContentMetaKey &key) override; + virtual Result HasAll(sf::Out<bool> out, const sf::InArray<ContentMetaKey> &keys) override; + virtual Result GetSize(sf::Out<u64> out_size, const ContentMetaKey &key) override; + virtual Result GetRequiredSystemVersion(sf::Out<u32> out_version, const ContentMetaKey &key) override; + virtual Result GetPatchContentMetaId(sf::Out<u64> out_patch_id, const ContentMetaKey &key) override; + virtual Result DisableForcibly() override; + virtual Result LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) override; + virtual Result Commit() override; + virtual Result HasContent(sf::Out<bool> out, const ContentMetaKey &key, const ContentId &content_id) override; + virtual Result ListContentMetaInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaInfo> &out_meta_info, const ContentMetaKey &key, s32 offset) override; + virtual Result GetAttributes(sf::Out<u8> out_attributes, const ContentMetaKey &key) override; + virtual Result GetRequiredApplicationVersion(sf::Out<u32> out_version, const ContentMetaKey &key) override; + virtual Result GetContentIdByTypeAndIdOffset(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type, u8 id_offset) override; + virtual Result GetCount(sf::Out<u32> out_count) override; + virtual Result GetOwnerApplicationId(sf::Out<ApplicationId> out_id, const ContentMetaKey &key) override; + virtual Result GetContentAccessibilities(sf::Out<u8> out_accessibilities, const ContentMetaKey &key) override; + virtual Result GetContentInfoByType(sf::Out<ContentInfo> out_content_info, const ContentMetaKey &key, ContentType type) override; + virtual Result GetContentInfoByTypeAndIdOffset(sf::Out<ContentInfo> out_content_info, const ContentMetaKey &key, ContentType type, u8 id_offset) override; + virtual Result GetPlatform(sf::Out<ncm::ContentMetaPlatform> out, const ContentMetaKey &key) override; + virtual Result HasAttributes(sf::Out<u8> out, u8 attr_mask) override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_meta_database_impl_base.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_meta_database_impl_base.hpp new file mode 100644 index 00000000..59d6130e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_meta_database_impl_base.hpp @@ -0,0 +1,88 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ncm { + + class ContentMetaDatabaseImplBase { + NON_COPYABLE(ContentMetaDatabaseImplBase); + NON_MOVEABLE(ContentMetaDatabaseImplBase); + protected: + using ContentMetaKeyValueStore = ams::kvdb::MemoryKeyValueStore<ContentMetaKey>; + protected: + ContentMetaKeyValueStore *m_kvs; + char m_mount_name[fs::MountNameLengthMax + 1]; + bool m_disabled; + protected: + ContentMetaDatabaseImplBase(ContentMetaKeyValueStore *kvs) : m_kvs(kvs), m_mount_name(), m_disabled(false) { /* ... */ } + + ContentMetaDatabaseImplBase(ContentMetaKeyValueStore *kvs, const char *mount_name) : ContentMetaDatabaseImplBase(kvs) { + std::strcpy(m_mount_name, mount_name); + } + protected: + /* Helpers. */ + Result EnsureEnabled() const { + R_UNLESS(!m_disabled, ncm::ResultInvalidContentMetaDatabase()); + R_SUCCEED(); + } + + Result GetContentMetaSize(size_t *out, const ContentMetaKey &key) const { + R_TRY_CATCH(m_kvs->GetValueSize(out, key)) { + R_CONVERT(kvdb::ResultKeyNotFound, ncm::ResultContentMetaNotFound()) + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + Result GetContentMetaPointer(const void **out_value_ptr, size_t *out_size, const ContentMetaKey &key) const { + R_TRY(this->GetContentMetaSize(out_size, key)); + R_RETURN(m_kvs->GetValuePointer(reinterpret_cast<const ContentMetaHeader **>(out_value_ptr), key)); + } + public: + /* Actual commands. */ + virtual Result Set(const ContentMetaKey &key, const sf::InBuffer &value) = 0; + virtual Result Get(sf::Out<u64> out_size, const ContentMetaKey &key, const sf::OutBuffer &out_value) = 0; + virtual Result Remove(const ContentMetaKey &key) = 0; + virtual Result GetContentIdByType(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type) = 0; + virtual Result ListContentInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentInfo> &out_info, const ContentMetaKey &key, s32 offset) = 0; + virtual Result List(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType meta_type, ApplicationId application_id, u64 min, u64 max, ContentInstallType install_type) = 0; + virtual Result GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, u64 id) = 0; + virtual Result ListApplication(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ApplicationContentMetaKey> &out_keys, ContentMetaType meta_type) = 0; + virtual Result Has(sf::Out<bool> out, const ContentMetaKey &key) = 0; + virtual Result HasAll(sf::Out<bool> out, const sf::InArray<ContentMetaKey> &keys) = 0; + virtual Result GetSize(sf::Out<u64> out_size, const ContentMetaKey &key) = 0; + virtual Result GetRequiredSystemVersion(sf::Out<u32> out_version, const ContentMetaKey &key) = 0; + virtual Result GetPatchContentMetaId(sf::Out<u64> out_patch_id, const ContentMetaKey &key) = 0; + virtual Result DisableForcibly() = 0; + virtual Result LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) = 0; + virtual Result Commit() = 0; + virtual Result HasContent(sf::Out<bool> out, const ContentMetaKey &key, const ContentId &content_id) = 0; + virtual Result ListContentMetaInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaInfo> &out_meta_info, const ContentMetaKey &key, s32 offset) = 0; + virtual Result GetAttributes(sf::Out<u8> out_attributes, const ContentMetaKey &key) = 0; + virtual Result GetRequiredApplicationVersion(sf::Out<u32> out_version, const ContentMetaKey &key) = 0; + virtual Result GetContentIdByTypeAndIdOffset(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type, u8 id_offset) = 0; + virtual Result GetCount(sf::Out<u32> out_count) = 0; + virtual Result GetOwnerApplicationId(sf::Out<ApplicationId> out_id, const ContentMetaKey &key) = 0; + virtual Result GetContentAccessibilities(sf::Out<u8> out_accessibilities, const ContentMetaKey &key) = 0; + virtual Result GetContentInfoByType(sf::Out<ContentInfo> out_content_info, const ContentMetaKey &key, ContentType type) = 0; + virtual Result GetContentInfoByTypeAndIdOffset(sf::Out<ContentInfo> out_content_info, const ContentMetaKey &key, ContentType type, u8 id_offset) = 0; + virtual Result GetPlatform(sf::Out<ncm::ContentMetaPlatform> out, const ContentMetaKey &key) = 0; + virtual Result HasAttributes(sf::Out<u8> out, u8 attr_mask) = 0; + }; + static_assert(ncm::IsIContentMetaDatabase<ContentMetaDatabaseImplBase>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_meta_type.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_meta_type.cpp new file mode 100644 index 00000000..6ee07c35 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_meta_type.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ncm { + + const char *GetContentMetaTypeString(ContentMetaType type) { + switch (type) { + case ContentMetaType::Application: return "Application"; + case ContentMetaType::Patch: return "Patch"; + case ContentMetaType::AddOnContent: return "AddOnContent"; + case ContentMetaType::DataPatch: return "DataPatch"; + default: return "(unknown)"; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_meta_utils.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_meta_utils.cpp new file mode 100644 index 00000000..1759f9cf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_meta_utils.cpp @@ -0,0 +1,291 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ncm_fs_utils.hpp" + +namespace ams::ncm { + + namespace { + + Result MountContentMetaByRemoteFileSystemProxy(const char *mount_name, const char *path, fs::ContentAttributes attr) { + R_RETURN(fs::MountContent(mount_name, path, attr, fs::ContentType_Meta)); + } + + constinit MountContentMetaFunction g_mount_content_meta_func = MountContentMetaByRemoteFileSystemProxy; + + } + + namespace impl { + + Result MountContentMetaImpl(const char *mount_name, const char *path, fs::ContentAttributes attr) { + R_RETURN(g_mount_content_meta_func(mount_name, path, attr)); + } + + } + + bool IsContentMetaFileName(const char *name) { + return impl::PathView(name).HasSuffix(".cnmt"); + } + + Result ReadContentMetaPathAlongWithExtendedDataAndDigest(AutoBuffer *out, const char *path, fs::ContentAttributes attr) { + /* Mount the content. */ + auto mount_name = impl::CreateUniqueMountName(); + R_TRY(impl::MountContentMetaImpl(mount_name.str, path, attr)); + ON_SCOPE_EXIT { fs::Unmount(mount_name.str); }; + + /* Open the root directory. */ + auto root_path = impl::GetRootDirectoryPath(mount_name); + fs::DirectoryHandle dir; + R_TRY(fs::OpenDirectory(std::addressof(dir), root_path.str, fs::OpenDirectoryMode_File)); + ON_SCOPE_EXIT { fs::CloseDirectory(dir); }; + + /* Loop directory reading until we find the entry we're looking for. */ + while (true) { + /* Read one entry, and finish when we fail to read. */ + fs::DirectoryEntry entry; + s64 num_read; + R_TRY(fs::ReadDirectory(std::addressof(num_read), std::addressof(entry), dir, 1)); + if (num_read == 0) { + break; + } + + /* If this is the content meta file, parse it. */ + if (IsContentMetaFileName(entry.name)) { + /* Create the file path. */ + impl::FilePathString file_path(root_path.str); + file_path.Append(entry.name); + + /* Open the content meta file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), file_path, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Get the meta size. */ + s64 file_size; + R_TRY(fs::GetFileSize(std::addressof(file_size), file)); + const size_t meta_size = static_cast<size_t>(file_size); + + /* Create a buffer for the meta. */ + R_TRY(out->Initialize(meta_size)); + + /* Read the meta into the buffer. */ + R_RETURN(fs::ReadFile(file, 0, out->Get(), meta_size)); + } + } + + R_THROW(ncm::ResultContentMetaNotFound()); + } + + Result ReadContentMetaPathAlongWithExtendedDataAndDigestSuppressingFsAbort(AutoBuffer *out, const char *path, fs::ContentAttributes attr) { + fs::ScopedAutoAbortDisabler aad; + R_RETURN(ReadContentMetaPathAlongWithExtendedDataAndDigest(out, path, attr)); + } + + Result ReadContentMetaPathWithoutExtendedDataOrDigest(AutoBuffer *out, const char *path, fs::ContentAttributes attr) { + /* Mount the content. */ + auto mount_name = impl::CreateUniqueMountName(); + R_TRY(impl::MountContentMetaImpl(mount_name.str, path, attr)); + ON_SCOPE_EXIT { fs::Unmount(mount_name.str); }; + + /* Open the root directory. */ + auto root_path = impl::GetRootDirectoryPath(mount_name); + fs::DirectoryHandle dir; + R_TRY(fs::OpenDirectory(std::addressof(dir), root_path.str, fs::OpenDirectoryMode_File)); + ON_SCOPE_EXIT { fs::CloseDirectory(dir); }; + + /* Loop directory reading until we find the entry we're looking for. */ + while (true) { + /* Read one entry, and finish when we fail to read. */ + fs::DirectoryEntry entry; + s64 num_read; + R_TRY(fs::ReadDirectory(std::addressof(num_read), std::addressof(entry), dir, 1)); + if (num_read == 0) { + break; + } + + /* If this is the content meta file, parse it. */ + if (IsContentMetaFileName(entry.name)) { + /* Create the file path. */ + impl::FilePathString file_path(root_path.str); + file_path.Append(entry.name); + + /* Open the content meta file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), file_path, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Get the meta size. */ + s64 file_size; + R_TRY(fs::GetFileSize(std::addressof(file_size), file)); + const size_t meta_file_size = static_cast<size_t>(file_size); + + /* Check that the meta size is large enough. */ + R_UNLESS(meta_file_size >= sizeof(PackagedContentMetaHeader), ncm::ResultInvalidContentMetaFileSize()); + + /* Read the header. */ + PackagedContentMetaHeader header; + size_t read_size = 0; + R_TRY(fs::ReadFile(std::addressof(read_size), file, 0, std::addressof(header), sizeof(header))); + + /* Check the right size was read. */ + R_UNLESS(read_size == sizeof(PackagedContentMetaHeader), ncm::ResultInvalidContentMetaFileSize()); + + /* Determine the meta size. */ + const size_t meta_size = PackagedContentMetaReader(std::addressof(header), sizeof(header)).GetExtendedDataOffset(); + + /* Create a buffer for the meta. */ + R_TRY(out->Initialize(meta_size)); + + /* Read the meta into the buffer. */ + R_RETURN(fs::ReadFile(file, 0, out->Get(), meta_size)); + } + } + + R_THROW(ncm::ResultContentMetaNotFound()); + } + + Result ReadContentMetaPathWithoutExtendedDataOrDigestSuppressingFsAbort(AutoBuffer *out, const char *path, fs::ContentAttributes attr) { + fs::ScopedAutoAbortDisabler aad; + R_RETURN(ReadContentMetaPathAlongWithExtendedDataAndDigest(out, path, attr)); + } + + Result TryReadContentMetaPath(fs::ContentAttributes *out_attr, AutoBuffer *out, const char *path, ReadContentMetaPathFunction func) { + /* Try with attributes = none. */ + fs::ContentAttributes attr = fs::ContentAttributes_None; + R_TRY_CATCH(func(out, path, attr)) { + R_CATCH(fs::ResultNcaHeaderSignature1VerificationFailed) { + /* On signature failure, try with attributes = all. */ + attr = fs::ContentAttributes_All; + R_TRY(func(out, path, attr)); + } + } R_END_TRY_CATCH; + + /* Set output attributes. */ + *out_attr = attr; + R_SUCCEED(); + } + + Result TryReadContentMetaPath(AutoBuffer *out, const char *path, ReadContentMetaPathFunction func) { + /* Try with attributes = none. */ + fs::ContentAttributes attr = fs::ContentAttributes_None; + R_TRY_CATCH(func(out, path, attr)) { + R_CATCH(fs::ResultNcaHeaderSignature1VerificationFailed) { + /* On signature failure, try with attributes = all. */ + attr = fs::ContentAttributes_All; + R_TRY(func(out, path, attr)); + } + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + Result ReadVariationContentMetaInfoList(s32 *out_count, std::unique_ptr<ContentMetaInfo[]> *out_meta_infos, const Path &path, fs::ContentAttributes attr, FirmwareVariationId firmware_variation_id) { + AutoBuffer meta; + R_TRY(ReadContentMetaPathAlongWithExtendedDataAndDigestSuppressingFsAbort(std::addressof(meta), path.str, attr)); + + /* Create a reader for the content meta. */ + PackagedContentMetaReader reader(meta.Get(), meta.GetSize()); + + /* Define a helper to output the base meta infos. */ + const auto ReadMetaInfoListFromBase = [&] () ALWAYS_INLINE_LAMBDA -> Result { + /* Output the base content meta info count. */ + *out_count = reader.GetContentMetaCount(); + + /* Create a buffer to hold the infos. NOTE: N does not check for nullptr before accessing. */ + std::unique_ptr<ContentMetaInfo[]> buffer(new (std::nothrow) ContentMetaInfo[reader.GetContentMetaCount()]); + AMS_ABORT_UNLESS(buffer != nullptr); + + /* Copy all base meta infos to output */ + for (size_t i = 0; i < reader.GetContentMetaCount(); i++) { + buffer[i] = *reader.GetContentMetaInfo(i); + } + + /* Write out the buffer we've populated. */ + *out_meta_infos = std::move(buffer); + R_SUCCEED(); + }; + + /* If there are no firmware variations to list, read meta infos from base. */ + R_UNLESS(reader.GetExtendedDataSize() != 0, ReadMetaInfoListFromBase()); + + SystemUpdateMetaExtendedDataReader extended_data_reader(reader.GetExtendedData(), reader.GetExtendedDataSize()); + util::optional<s32> firmware_variation_index = util::nullopt; + + /* NOTE: Atmosphere extension to support downgrading. */ + /* If all firmware variations refer to base, don't require the current variation be present. */ + bool force_refer_to_base = true; + + /* Find the input firmware variation id. */ + for (size_t i = 0; i < extended_data_reader.GetFirmwareVariationCount(); i++) { + if (*extended_data_reader.GetFirmwareVariationId(i) == firmware_variation_id) { + firmware_variation_index = i; + break; + } else { + /* Check if the current variation refers to base. */ + const FirmwareVariationInfo *cur_variation_info = extended_data_reader.GetFirmwareVariationInfo(i); + const bool cur_refers_to_base = extended_data_reader.GetHeader()->version == 1 || cur_variation_info->refer_to_base; + + /* We force referral to base on unsupported variation only if all supported variations refer to base. */ + force_refer_to_base &= cur_refers_to_base; + } + } + + /* We couldn't find the input firmware variation id. */ + if (!firmware_variation_index) { + /* Unless we can force a referral to base, the firmware isn't supported. */ + R_UNLESS(force_refer_to_base, ncm::ResultInvalidFirmwareVariation()); + + /* Force a referral to base. */ + R_RETURN(ReadMetaInfoListFromBase()); + } + + /* Obtain the variation info. */ + const FirmwareVariationInfo *variation_info = extended_data_reader.GetFirmwareVariationInfo(*firmware_variation_index); + + /* Refer to base if variation info says we should, or if version is 1. */ + const bool refer_to_base = extended_data_reader.GetHeader()->version == 1 || variation_info->refer_to_base; + R_UNLESS(!refer_to_base, ReadMetaInfoListFromBase()); + + /* Output the content meta count. */ + const u32 content_meta_count = variation_info->content_meta_count; + *out_count = content_meta_count; + + /* We're done if there are no content metas to list. */ + R_SUCCEED_IF(content_meta_count == 0); + + /* Allocate a buffer for the content meta infos. */ + std::unique_ptr<ContentMetaInfo[]> buffer(new (std::nothrow) ContentMetaInfo[content_meta_count]); + AMS_ABORT_UNLESS(buffer != nullptr); + + /* Get the content meta infos. */ + Span<const ContentMetaInfo> meta_infos; + extended_data_reader.GetContentMetaInfoList(std::addressof(meta_infos), content_meta_count); + + /* Copy the meta infos to the buffer. */ + for (size_t i = 0; i < content_meta_count; i++) { + buffer[i] = meta_infos[i]; + } + + /* Output the content meta info buffer. */ + *out_meta_infos = std::move(buffer); + R_SUCCEED(); + } + + void SetMountContentMetaFunction(MountContentMetaFunction func) { + g_mount_content_meta_func = func; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_storage_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_storage_impl.cpp new file mode 100644 index 00000000..db038750 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_storage_impl.cpp @@ -0,0 +1,936 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ncm_content_storage_impl.hpp" +#include "ncm_fs_utils.hpp" + +namespace ams::ncm { + + namespace { + + constexpr inline const char * const BaseContentDirectory = "/registered"; + + void MakeBaseContentDirectoryPath(PathString *out, const char *root_path) { + out->AssignFormat("%s%s", root_path, BaseContentDirectory); + } + + void MakeContentPath(PathString *out, ContentId id, MakeContentPathFunction func, const char *root_path) { + PathString path; + MakeBaseContentDirectoryPath(std::addressof(path), root_path); + func(out, id, path); + } + + Result EnsureContentDirectory(ContentId id, MakeContentPathFunction func, const char *root_path) { + PathString path; + MakeContentPath(std::addressof(path), id, func, root_path); + R_RETURN(fs::EnsureParentDirectory(path)); + } + + Result DeleteContentFile(ContentId id, MakeContentPathFunction func, const char *root_path) { + /* Create the content path. */ + PathString path; + MakeContentPath(std::addressof(path), id, func, root_path); + + /* Delete the content. */ + R_TRY_CATCH(fs::DeleteFile(path)) { + R_CONVERT(fs::ResultPathNotFound, ncm::ResultContentNotFound()) + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + template<typename F> + Result TraverseDirectory(bool *out_should_continue, const char *root_path, int max_level, F f) { + /* If the level is zero, we're done. */ + R_SUCCEED_IF(max_level <= 0); + + /* On 1.0.0, NotRequireFileSize was not a valid open mode. */ + const auto open_dir_mode = hos::GetVersion() >= hos::Version_2_0_0 ? (fs::OpenDirectoryMode_All | fs::OpenDirectoryMode_NotRequireFileSize) : (fs::OpenDirectoryMode_All); + + /* Retry traversal upon request. */ + bool retry_dir_read = true; + while (retry_dir_read) { + retry_dir_read = false; + + /* Open the directory at the given path. All entry types are allowed. */ + fs::DirectoryHandle dir; + R_TRY(fs::OpenDirectory(std::addressof(dir), root_path, open_dir_mode)); + ON_SCOPE_EXIT { fs::CloseDirectory(dir); }; + + while (true) { + /* Read a single directory entry. */ + fs::DirectoryEntry entry; + s64 entry_count; + R_TRY(fs::ReadDirectory(std::addressof(entry_count), std::addressof(entry), dir, 1)); + + /* Directory has no entries to process. */ + if (entry_count == 0) { + break; + } + + /* Path of the current entry. */ + PathString current_path; + current_path.AssignFormat("%s/%s", root_path, entry.name); + + /* Call the process function. */ + bool should_continue = true; + bool should_retry_dir_read = false; + R_TRY(f(std::addressof(should_continue), std::addressof(should_retry_dir_read), current_path, entry)); + + /* If the provided function wishes to terminate immediately, we should respect it. */ + if (!should_continue) { + *out_should_continue = false; + R_SUCCEED(); + } + + /* Mark for retry. */ + if (should_retry_dir_read) { + retry_dir_read = true; + break; + } + + /* If the entry is a directory, recurse. */ + if (entry.type == fs::DirectoryEntryType_Directory) { + R_TRY(TraverseDirectory(std::addressof(should_continue), current_path, max_level - 1, f)); + + if (!should_continue) { + *out_should_continue = false; + R_SUCCEED(); + } + } + } + } + + R_SUCCEED(); + } + + + template<typename F> + Result TraverseDirectory(const char *root_path, int max_level, F f) { + bool should_continue = false; + R_RETURN(TraverseDirectory(std::addressof(should_continue), root_path, max_level, f)); + } + + bool IsContentPath(const char *path) { + impl::PathView view(path); + + /* Ensure nca suffix. */ + if (!view.HasSuffix(".nca")) { + return false; + } + + /* File name should be the size of a content id plus the nca file extension. */ + auto file_name = view.GetFileName(); + if (file_name.length() != ContentIdStringLength + 4) { + return false; + } + + /* Ensure file name is comprised of hex characters. */ + for (size_t i = 0; i < ContentIdStringLength; i++) { + if (!std::isxdigit(static_cast<unsigned char>(file_name[i]))) { + return false; + } + } + + return true; + } + + bool IsPlaceHolderPath(const char *path) { + return IsContentPath(path); + } + + Result CleanDirectoryRecursively(const PathString &path) { + if (hos::GetVersion() >= hos::Version_3_0_0) { + R_TRY(fs::CleanDirectoryRecursively(path)); + } else { + /* CleanDirectoryRecursively didn't exist on < 3.0.0, so we will polyfill it. */ + /* We'll delete the directory, then recreate it. */ + R_TRY(fs::DeleteDirectoryRecursively(path)); + R_TRY(fs::CreateDirectory(path)); + } + R_SUCCEED(); + } + + } + + ContentStorageImpl::ContentIterator::~ContentIterator() { + for (size_t i = 0; i < m_depth; i++) { + fs::CloseDirectory(m_handles[i]); + } + } + + Result ContentStorageImpl::ContentIterator::Initialize(const char *root_path, size_t max_depth) { + /* Initialize tracking variables. */ + m_depth = 0; + m_max_depth = max_depth; + m_entry_count = 0; + + /* Create the base content directory path. */ + MakeBaseContentDirectoryPath(std::addressof(m_path), root_path); + + /* Open the base directory. */ + R_TRY(this->OpenCurrentDirectory()); + + R_SUCCEED(); + } + + Result ContentStorageImpl::ContentIterator::OpenCurrentDirectory() { + /* Determine valid directory mode (prior to 2.0.0, NotRequireFileSize was not valid). */ + const auto open_mode = hos::GetVersion() >= hos::Version_2_0_0 ? (fs::OpenDirectoryMode_All | fs::OpenDirectoryMode_NotRequireFileSize) : (fs::OpenDirectoryMode_All); + + /* Open the directory for our current path. */ + R_TRY(fs::OpenDirectory(std::addressof(m_handles[m_depth]), m_path, open_mode)); + + /* Increase our depth. */ + ++m_depth; + + R_SUCCEED(); + } + + Result ContentStorageImpl::ContentIterator::OpenDirectory(const char *dir) { + /* Set our current path. */ + m_path.Assign(dir); + + /* Open the directory. */ + R_RETURN(this->OpenCurrentDirectory()); + } + + Result ContentStorageImpl::ContentIterator::GetNext(util::optional<fs::DirectoryEntry> *out) { + /* Iterate until we get the next entry. */ + while (true) { + /* Ensure that we have entries loaded. */ + R_TRY(this->LoadEntries()); + + /* If we failed to load any entries, there's nothing to get. */ + if (m_entry_count <= 0) { + *out = util::nullopt; + R_SUCCEED(); + } + + /* Get the next entry. */ + const auto &entry = m_entries[--m_entry_count]; + + /* Process the current entry. */ + switch (entry.type) { + case fs::DirectoryEntryType_Directory: + /* If the entry if a directory, we want to recurse into it if we can. */ + if (m_depth < m_max_depth) { + /* Construct the full path for the subdirectory. */ + PathString entry_path; + entry_path.AssignFormat("%s/%s", m_path.Get(), entry.name); + + /* Open the subdirectory. */ + R_TRY(this->OpenDirectory(entry_path.Get())); + + } + break; + case fs::DirectoryEntryType_File: + /* Otherwise, if the entry is a file, return it. */ + *out = entry; + R_SUCCEED(); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + R_SUCCEED(); + } + + Result ContentStorageImpl::ContentIterator::LoadEntries() { + /* If we already have entries loaded, we don't need to do anything. */ + R_SUCCEED_IF(m_entry_count != 0); + + /* If we have no directories open, there's nothing for us to load. */ + if (m_depth == 0) { + m_entry_count = 0; + R_SUCCEED(); + } + + /* Determine the maximum entries that we can load. */ + const s64 max_entries = m_depth == m_max_depth ? MaxDirectoryEntries : 1; + + /* Read entries from the current directory. */ + s64 num_entries; + R_TRY(fs::ReadDirectory(std::addressof(num_entries), m_entries, m_handles[m_depth - 1], max_entries)); + + /* If we successfully read entries, load them. */ + if (num_entries > 0) { + /* Reverse the order of the loaded entries, for our future convenience. */ + for (fs::DirectoryEntry *start_entry = m_entries, *end_entry = m_entries + num_entries - 1; start_entry < end_entry; ++start_entry, --end_entry) { + std::swap(*start_entry, *end_entry); + } + + /* Set our entry count. */ + m_entry_count = num_entries; + + R_SUCCEED(); + } + + /* We didn't read any entries, so we need to advance to the next directory. */ + fs::CloseDirectory(m_handles[--m_depth]); + + /* Find the index of the parent directory's substring. */ + size_t i = m_path.GetLength() - 1; + while (m_path.Get()[i--] != '/') { + AMS_ABORT_UNLESS(i > 0); + } + + /* Set the path to the parent directory. */ + m_path = m_path.MakeSubString(0, i + 1); + + /* Try to load again from the parent directory. */ + R_RETURN(this->LoadEntries()); + } + + ContentStorageImpl::~ContentStorageImpl() { + this->InvalidateFileCache(); + } + + Result ContentStorageImpl::InitializeBase(const char *root_path) { + PathString path; + + /* Create the content directory. */ + MakeBaseContentDirectoryPath(std::addressof(path), root_path); + R_TRY(fs::EnsureDirectory(path)); + + /* Create the placeholder directory. */ + PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), root_path); + R_RETURN(fs::EnsureDirectory(path)); + } + + Result ContentStorageImpl::CleanupBase(const char *root_path) { + PathString path; + + /* Create the content directory. */ + MakeBaseContentDirectoryPath(std::addressof(path), root_path); + R_TRY(CleanDirectoryRecursively(path)); + + /* Create the placeholder directory. */ + PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), root_path); + R_RETURN(CleanDirectoryRecursively(path)); + } + + Result ContentStorageImpl::VerifyBase(const char *root_path) { + PathString path; + + /* Check if root directory exists. */ + bool has_dir; + R_TRY(fs::HasDirectory(std::addressof(has_dir), root_path)); + R_UNLESS(has_dir, ncm::ResultContentStorageBaseNotFound()); + + /* Check if content directory exists. */ + bool has_registered; + MakeBaseContentDirectoryPath(std::addressof(path), root_path); + R_TRY(fs::HasDirectory(std::addressof(has_registered), path)); + + /* Check if placeholder directory exists. */ + bool has_placeholder; + PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), root_path); + R_TRY(fs::HasDirectory(std::addressof(has_placeholder), path)); + + /* Convert findings to results. */ + R_UNLESS(has_registered || has_placeholder, ncm::ResultContentStorageBaseNotFound()); + R_UNLESS(has_registered, ncm::ResultInvalidContentStorageBase()); + R_UNLESS(has_placeholder, ncm::ResultInvalidContentStorageBase()); + + R_SUCCEED(); + } + + void ContentStorageImpl::InvalidateFileCache() { + if (m_cached_content_id != InvalidContentId) { + fs::CloseFile(m_cached_file_handle); + m_cached_content_id = InvalidContentId; + } + m_content_iterator = util::nullopt; + } + + Result ContentStorageImpl::OpenContentIdFile(ContentId content_id) { + /* If the file is the currently cached one, we've nothing to do. */ + R_SUCCEED_IF(m_cached_content_id == content_id); + + /* Close any cached file. */ + this->InvalidateFileCache(); + + /* Create the content path. */ + PathString path; + MakeContentPath(std::addressof(path), content_id, m_make_content_path_func, m_root_path); + + /* Open the content file and store to the cache. */ + R_TRY_CATCH(fs::OpenFile(std::addressof(m_cached_file_handle), path, fs::OpenMode_Read)) { + R_CONVERT(ams::fs::ResultPathNotFound, ncm::ResultContentNotFound()) + } R_END_TRY_CATCH; + + m_cached_content_id = content_id; + R_SUCCEED(); + } + + Result ContentStorageImpl::Initialize(const char *path, MakeContentPathFunction content_path_func, MakePlaceHolderPathFunction placeholder_path_func, bool delay_flush, RightsIdCache *rights_id_cache) { + R_TRY(this->EnsureEnabled()); + + /* Check paths exists for this content storage. */ + R_TRY(VerifyBase(path)); + + /* Initialize members. */ + m_root_path = PathString(path); + m_make_content_path_func = content_path_func; + m_placeholder_accessor.Initialize(std::addressof(m_root_path), placeholder_path_func, delay_flush); + m_rights_id_cache = rights_id_cache; + R_SUCCEED(); + } + + Result ContentStorageImpl::GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) { + R_TRY(this->EnsureEnabled()); + out.SetValue({util::GenerateUuid()}); + R_SUCCEED(); + } + + Result ContentStorageImpl::CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size) { + R_TRY(this->EnsureEnabled()); + R_TRY(EnsureContentDirectory(content_id, m_make_content_path_func, m_root_path)); + R_RETURN(m_placeholder_accessor.CreatePlaceHolderFile(placeholder_id, size)); + } + + Result ContentStorageImpl::DeletePlaceHolder(PlaceHolderId placeholder_id) { + R_TRY(this->EnsureEnabled()); + R_RETURN(m_placeholder_accessor.DeletePlaceHolderFile(placeholder_id)); + } + + Result ContentStorageImpl::HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) { + R_TRY(this->EnsureEnabled()); + + /* Create the placeholder path. */ + PathString placeholder_path; + m_placeholder_accessor.MakePath(std::addressof(placeholder_path), placeholder_id); + + /* Check if placeholder file exists. */ + bool has = false; + R_TRY(fs::HasFile(std::addressof(has), placeholder_path)); + out.SetValue(has); + R_SUCCEED(); + } + + Result ContentStorageImpl::WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, const sf::InBuffer &data) { + /* Ensure offset is valid. */ + R_UNLESS(offset >= 0, ncm::ResultInvalidOffset()); + R_TRY(this->EnsureEnabled()); + R_RETURN(m_placeholder_accessor.WritePlaceHolderFile(placeholder_id, offset, data.GetPointer(), data.GetSize())); + } + + Result ContentStorageImpl::Register(PlaceHolderId placeholder_id, ContentId content_id) { + this->InvalidateFileCache(); + R_TRY(this->EnsureEnabled()); + + /* Create the placeholder path. */ + PathString placeholder_path; + m_placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id); + + /* Create the content path. */ + PathString content_path; + MakeContentPath(std::addressof(content_path), content_id, m_make_content_path_func, m_root_path); + + /* Move the placeholder to the content path. */ + R_TRY_CATCH(fs::RenameFile(placeholder_path, content_path)) { + R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound()) + R_CONVERT(fs::ResultPathAlreadyExists, ncm::ResultContentAlreadyExists()) + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + Result ContentStorageImpl::Delete(ContentId content_id) { + R_TRY(this->EnsureEnabled()); + this->InvalidateFileCache(); + R_RETURN(DeleteContentFile(content_id, m_make_content_path_func, m_root_path)); + } + + Result ContentStorageImpl::Has(sf::Out<bool> out, ContentId content_id) { + R_TRY(this->EnsureEnabled()); + + /* Create the content path. */ + PathString content_path; + MakeContentPath(std::addressof(content_path), content_id, m_make_content_path_func, m_root_path); + + /* Check if the content file exists. */ + bool has = false; + R_TRY(fs::HasFile(std::addressof(has), content_path)); + out.SetValue(has); + R_SUCCEED(); + } + + Result ContentStorageImpl::GetPath(sf::Out<Path> out, ContentId content_id) { + R_TRY(this->EnsureEnabled()); + + /* Create the content path. */ + PathString content_path; + MakeContentPath(std::addressof(content_path), content_id, m_make_content_path_func, m_root_path); + + /* Substitute our mount name for the common mount name. */ + Path common_path; + R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), content_path)); + + out.SetValue(common_path); + R_SUCCEED(); + } + + Result ContentStorageImpl::GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id) { + R_TRY(this->EnsureEnabled()); + + /* Obtain the placeholder path. */ + PathString placeholder_path; + m_placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id); + + /* Substitute our mount name for the common mount name. */ + Path common_path; + R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), placeholder_path)); + + out.SetValue(common_path); + R_SUCCEED(); + } + + Result ContentStorageImpl::CleanupAllPlaceHolder() { + R_TRY(this->EnsureEnabled()); + + /* Clear the cache. */ + m_placeholder_accessor.InvalidateAll(); + + /* Obtain the placeholder base directory path. */ + PathString placeholder_dir; + PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(placeholder_dir), m_root_path); + + /* Cleanup the placeholder base directory. */ + CleanDirectoryRecursively(placeholder_dir); + R_SUCCEED(); + } + + Result ContentStorageImpl::ListPlaceHolder(sf::Out<s32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) { + R_TRY(this->EnsureEnabled()); + + /* Obtain the placeholder base directory path. */ + PathString placeholder_dir; + PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(placeholder_dir), m_root_path); + + const size_t max_entries = out_buf.GetSize(); + size_t entry_count = 0; + + /* Traverse the placeholder base directory finding valid placeholder files. */ + R_TRY(TraverseDirectory(placeholder_dir, m_placeholder_accessor.GetHierarchicalDirectoryDepth(), [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) -> Result { + AMS_UNUSED(current_path); + + *should_continue = true; + *should_retry_dir_read = false; + + /* We are only looking for files. */ + if (entry.type == fs::DirectoryEntryType_File) { + R_UNLESS(entry_count <= max_entries, ncm::ResultBufferInsufficient()); + + /* Get the placeholder id from the filename. */ + PlaceHolderId placeholder_id; + R_TRY(PlaceHolderAccessor::GetPlaceHolderIdFromFileName(std::addressof(placeholder_id), entry.name)); + + out_buf[entry_count++] = placeholder_id; + } + + R_SUCCEED(); + })); + + out_count.SetValue(static_cast<s32>(entry_count)); + R_SUCCEED(); + } + + Result ContentStorageImpl::GetContentCount(sf::Out<s32> out_count) { + R_TRY(this->EnsureEnabled()); + + /* Obtain the content base directory path. */ + PathString path; + MakeBaseContentDirectoryPath(std::addressof(path), m_root_path); + + const auto depth = GetHierarchicalContentDirectoryDepth(m_make_content_path_func); + size_t count = 0; + + /* Traverse the content base directory finding all files. */ + R_TRY(TraverseDirectory(path, depth, [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) -> Result { + AMS_UNUSED(current_path); + + *should_continue = true; + *should_retry_dir_read = false; + + /* Increment the count for each file found. */ + if (entry.type == fs::DirectoryEntryType_File) { + count++; + } + + R_SUCCEED(); + })); + + out_count.SetValue(static_cast<s32>(count)); + R_SUCCEED(); + } + + Result ContentStorageImpl::ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out, s32 offset) { + R_UNLESS(offset >= 0, ncm::ResultInvalidOffset()); + R_TRY(this->EnsureEnabled()); + + if (!m_content_iterator.has_value() || !m_last_content_offset.has_value() || m_last_content_offset != offset) { + /* Create and initialize the content cache. */ + m_content_iterator.emplace(); + R_TRY(m_content_iterator->Initialize(m_root_path, GetHierarchicalContentDirectoryDepth(m_make_content_path_func))); + + /* Advance to the desired offset. */ + for (auto current_offset = 0; current_offset < offset; /* ... */) { + /* Get the next directory entry. */ + util::optional<fs::DirectoryEntry> dir_entry; + R_TRY(m_content_iterator->GetNext(std::addressof(dir_entry))); + + /* If we run out of entries before reaching the desired offset, we're done. */ + if (!dir_entry) { + out_count.SetValue(0); + R_SUCCEED(); + } + + /* If the current entry is a valid content id, advance. */ + if (GetContentIdFromString(dir_entry->name, std::strlen(dir_entry->name))) { + ++current_offset; + } + } + } + + /* Iterate, reading as many entries as we can. */ + s32 count = 0; + while (count < static_cast<s32>(out.GetSize())) { + /* Get the next directory entry. */ + util::optional<fs::DirectoryEntry> dir_entry; + R_TRY(m_content_iterator->GetNext(std::addressof(dir_entry))); + + /* Don't continue if the directory entry is absent. */ + if (!dir_entry) { + break; + } + + /* Process the entry, if it's a valid content id. */ + if (auto content_id = GetContentIdFromString(dir_entry->name, std::strlen(dir_entry->name)); content_id.has_value()) { + /* Output the content id. */ + out[count++] = *content_id; + + /* Update our last content offset. */ + m_last_content_offset = offset + count; + } + } + + /* Set the output count. */ + *out_count = count; + + R_SUCCEED(); + } + + Result ContentStorageImpl::GetSizeFromContentId(sf::Out<s64> out_size, ContentId content_id) { + R_TRY(this->EnsureEnabled()); + + /* Create the content path. */ + PathString content_path; + MakeContentPath(std::addressof(content_path), content_id, m_make_content_path_func, m_root_path); + + /* Open the content file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), content_path, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Obtain the size of the content. */ + s64 file_size; + R_TRY(fs::GetFileSize(std::addressof(file_size), file)); + + out_size.SetValue(file_size); + R_SUCCEED(); + } + + Result ContentStorageImpl::DisableForcibly() { + m_disabled = true; + this->InvalidateFileCache(); + m_placeholder_accessor.InvalidateAll(); + R_SUCCEED(); + } + + Result ContentStorageImpl::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) { + R_TRY(this->EnsureEnabled()); + + /* Close any cached file. */ + this->InvalidateFileCache(); + + /* Ensure the future content directory exists. */ + R_TRY(EnsureContentDirectory(new_content_id, m_make_content_path_func, m_root_path)); + + /* Ensure the destination placeholder directory exists. */ + R_TRY(m_placeholder_accessor.EnsurePlaceHolderDirectory(placeholder_id)); + + /* Obtain the placeholder path. */ + PathString placeholder_path; + m_placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id); + + /* Make the old content path. */ + PathString content_path; + MakeContentPath(std::addressof(content_path), old_content_id, m_make_content_path_func, m_root_path); + + /* Move the content to the placeholder path. */ + R_TRY_CATCH(fs::RenameFile(content_path, placeholder_path)) { + R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound()) + R_CONVERT(fs::ResultPathAlreadyExists, ncm::ResultContentAlreadyExists()) + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + Result ContentStorageImpl::SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) { + R_TRY(this->EnsureEnabled()); + R_RETURN(m_placeholder_accessor.SetPlaceHolderFileSize(placeholder_id, size)); + } + + Result ContentStorageImpl::ReadContentIdFile(const sf::OutBuffer &buf, ContentId content_id, s64 offset) { + /* Ensure offset is valid. */ + R_UNLESS(offset >= 0, ncm::ResultInvalidOffset()); + R_TRY(this->EnsureEnabled()); + + /* Create the content path. */ + PathString content_path; + MakeContentPath(std::addressof(content_path), content_id, m_make_content_path_func, m_root_path); + + /* Open the content file. */ + R_TRY(this->OpenContentIdFile(content_id)); + + /* Read from the requested offset up to the requested size. */ + R_RETURN(fs::ReadFile(m_cached_file_handle, offset, buf.GetPointer(), buf.GetSize())); + } + + Result ContentStorageImpl::GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id) { + /* Obtain the regular rights id for the placeholder id. */ + ncm::RightsId rights_id; + R_TRY(this->GetRightsIdFromPlaceHolderIdDeprecated2(std::addressof(rights_id), placeholder_id)); + + /* Output the fs rights id. */ + out_rights_id.SetValue(rights_id.id); + R_SUCCEED(); + } + + Result ContentStorageImpl::GetRightsIdFromPlaceHolderIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) { + R_RETURN(this->GetRightsIdFromPlaceHolderId(out_rights_id, placeholder_id, fs::ContentAttributes_None)); + } + + Result ContentStorageImpl::GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, fs::ContentAttributes attr) { + R_TRY(this->EnsureEnabled()); + + /* Get the placeholder path. */ + Path path; + R_TRY(this->GetPlaceHolderPath(std::addressof(path), placeholder_id)); + + /* Get the rights id for the placeholder id. */ + R_RETURN(GetRightsId(out_rights_id.GetPointer(), path, attr)); + } + + Result ContentStorageImpl::GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) { + /* Obtain the regular rights id for the content id. */ + ncm::RightsId rights_id; + R_TRY(this->GetRightsIdFromContentIdDeprecated2(std::addressof(rights_id), content_id)); + + /* Output the fs rights id. */ + out_rights_id.SetValue(rights_id.id); + R_SUCCEED(); + } + + Result ContentStorageImpl::GetRightsIdFromContentIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id) { + R_RETURN(this->GetRightsIdFromContentId(out_rights_id, content_id, fs::ContentAttributes_None)); + } + + Result ContentStorageImpl::GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id, fs::ContentAttributes attr) { + R_TRY(this->EnsureEnabled()); + + /* Attempt to obtain the rights id from the cache. */ + if (m_rights_id_cache->Find(out_rights_id.GetPointer(), content_id)) { + R_SUCCEED(); + } + + /* Get the path of the content. */ + Path path; + R_TRY(this->GetPath(std::addressof(path), content_id)); + + /* Obtain the rights id for the content. */ + ncm::RightsId rights_id; + R_TRY(GetRightsId(std::addressof(rights_id), path, attr)); + + /* Store the rights id to the cache. */ + m_rights_id_cache->Store(content_id, rights_id); + + out_rights_id.SetValue(rights_id); + R_SUCCEED(); + } + + Result ContentStorageImpl::WriteContentForDebug(ContentId content_id, s64 offset, const sf::InBuffer &data) { + /* Ensure offset is valid. */ + R_UNLESS(offset >= 0, ncm::ResultInvalidOffset()); + R_TRY(this->EnsureEnabled()); + + /* This command is for development hardware only. */ + AMS_ABORT_UNLESS(spl::IsDevelopment()); + + /* Close any cached file. */ + this->InvalidateFileCache(); + + /* Make the content path. */ + PathString path; + MakeContentPath(std::addressof(path), content_id, m_make_content_path_func, m_root_path); + + /* Open the content file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), path.Get(), fs::OpenMode_Write)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Write the provided data to the file. */ + R_RETURN(fs::WriteFile(file, offset, data.GetPointer(), data.GetSize(), fs::WriteOption::Flush)); + } + + Result ContentStorageImpl::GetFreeSpaceSize(sf::Out<s64> out_size) { + R_RETURN(fs::GetFreeSpaceSize(out_size.GetPointer(), m_root_path)); + } + + Result ContentStorageImpl::GetTotalSpaceSize(sf::Out<s64> out_size) { + R_RETURN(fs::GetTotalSpaceSize(out_size.GetPointer(), m_root_path)); + } + + Result ContentStorageImpl::FlushPlaceHolder() { + m_placeholder_accessor.InvalidateAll(); + R_SUCCEED(); + } + + Result ContentStorageImpl::GetSizeFromPlaceHolderId(sf::Out<s64> out_size, PlaceHolderId placeholder_id) { + R_TRY(this->EnsureEnabled()); + + /* Attempt to get the placeholder file size. */ + bool found = false; + s64 file_size = 0; + R_TRY(m_placeholder_accessor.TryGetPlaceHolderFileSize(std::addressof(found), std::addressof(file_size), placeholder_id)); + + /* Set the output if placeholder file is found. */ + if (found) { + out_size.SetValue(file_size); + R_SUCCEED(); + } + + /* Get the path of the placeholder. */ + PathString placeholder_path; + m_placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id); + + /* Open the placeholder file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), placeholder_path, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Get the size of the placeholder file. */ + R_TRY(fs::GetFileSize(std::addressof(file_size), file)); + + out_size.SetValue(file_size); + R_SUCCEED(); + } + + Result ContentStorageImpl::RepairInvalidFileAttribute() { + /* Callback for TraverseDirectory */ + using PathChecker = bool (*)(const char *); + PathChecker path_checker = nullptr; + + /* Set the archive bit appropriately for content/placeholders. */ + auto fix_file_attributes = [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) { + *should_retry_dir_read = false; + *should_continue = true; + + if (entry.type == fs::DirectoryEntryType_Directory) { + if (path_checker(current_path)) { + if (R_SUCCEEDED(fs::SetConcatenationFileAttribute(current_path))) { + *should_retry_dir_read = true; + } + } + } + + R_SUCCEED(); + }; + + /* Fix content. */ + { + path_checker = IsContentPath; + PathString path; + MakeBaseContentDirectoryPath(std::addressof(path), m_root_path); + + R_TRY(TraverseDirectory(path, GetHierarchicalContentDirectoryDepth(m_make_content_path_func), fix_file_attributes)); + } + + /* Fix placeholders. */ + m_placeholder_accessor.InvalidateAll(); + { + path_checker = IsPlaceHolderPath; + PathString path; + PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), m_root_path); + + R_TRY(TraverseDirectory(path, GetHierarchicalContentDirectoryDepth(m_make_content_path_func), fix_file_attributes)); + } + + R_SUCCEED(); + } + + Result ContentStorageImpl::GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id, fs::ContentAttributes attr) { + R_TRY(this->EnsureEnabled()); + + /* Attempt to find the rights id in the cache. */ + if (m_rights_id_cache->Find(out_rights_id.GetPointer(), cache_content_id)) { + R_SUCCEED(); + } + + /* Get the placeholder path. */ + PathString placeholder_path; + m_placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id); + + /* Substitute mount name with the common mount name. */ + Path common_path; + R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), placeholder_path)); + + /* Get the rights id. */ + ncm::RightsId rights_id; + R_TRY(GetRightsId(std::addressof(rights_id), common_path, attr)); + m_rights_id_cache->Store(cache_content_id, rights_id); + + /* Set output. */ + out_rights_id.SetValue(rights_id); + R_SUCCEED(); + } + + Result ContentStorageImpl::RegisterPath(const ContentId &content_id, const Path &path) { + AMS_UNUSED(content_id, path); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result ContentStorageImpl::ClearRegisteredPath() { + R_THROW(ncm::ResultInvalidOperation()); + } + + Result ContentStorageImpl::GetProgramId(sf::Out<ncm::ProgramId> out, ContentId content_id, fs::ContentAttributes attr) { + R_TRY(this->EnsureEnabled()); + + /* Get the path of the content. */ + Path path; + R_TRY(this->GetPath(std::addressof(path), content_id)); + + /* Obtain the program id for the content. */ + ncm::ProgramId program_id; + R_TRY(fs::GetProgramId(std::addressof(program_id), path.str, attr)); + + out.SetValue(program_id); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_storage_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_storage_impl.hpp new file mode 100644 index 00000000..5f9ae690 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_storage_impl.hpp @@ -0,0 +1,111 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#include "ncm_content_storage_impl_base.hpp" +#include "ncm_placeholder_accessor.hpp" + +namespace ams::ncm { + + class ContentStorageImpl : public ContentStorageImplBase { + private: + class ContentIterator { + NON_COPYABLE(ContentIterator); + NON_MOVEABLE(ContentIterator); + + static constexpr size_t MaxDirectoryHandles = 0x8; + static constexpr size_t MaxDirectoryEntries = 0x10; + + public: + fs::DirectoryHandle m_handles[MaxDirectoryHandles]{}; + size_t m_depth{}; + size_t m_max_depth{}; + PathString m_path{}; + fs::DirectoryEntry m_entries[MaxDirectoryEntries]{}; + s64 m_entry_count{}; + public: + constexpr ContentIterator() { /* ... */ } + ~ContentIterator(); + + Result Initialize(const char *root_path, size_t max_depth); + Result GetNext(util::optional<fs::DirectoryEntry> *out); + private: + Result OpenCurrentDirectory(); + Result OpenDirectory(const char *dir); + Result LoadEntries(); + }; + static_assert(std::is_constructible<ContentIterator>::value); + protected: + PlaceHolderAccessor m_placeholder_accessor; + ContentId m_cached_content_id; + fs::FileHandle m_cached_file_handle; + RightsIdCache *m_rights_id_cache; + util::optional<ContentIterator> m_content_iterator; + util::optional<s32> m_last_content_offset; + public: + static Result InitializeBase(const char *root_path); + static Result CleanupBase(const char *root_path); + static Result VerifyBase(const char *root_path); + public: + ContentStorageImpl() : m_placeholder_accessor(), m_cached_content_id(InvalidContentId), m_cached_file_handle(), m_rights_id_cache(nullptr), m_content_iterator(util::nullopt), m_last_content_offset(util::nullopt) { /* ... */ } + ~ContentStorageImpl(); + + Result Initialize(const char *root_path, MakeContentPathFunction content_path_func, MakePlaceHolderPathFunction placeholder_path_func, bool delay_flush, RightsIdCache *rights_id_cache); + private: + /* Helpers. */ + Result OpenContentIdFile(ContentId content_id); + void InvalidateFileCache(); + public: + /* Actual commands. */ + virtual Result GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) override; + virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size) override; + virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) override; + virtual Result HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) override; + virtual Result WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, const sf::InBuffer &data) override; + virtual Result Register(PlaceHolderId placeholder_id, ContentId content_id) override; + virtual Result Delete(ContentId content_id) override; + virtual Result Has(sf::Out<bool> out, ContentId content_id) override; + virtual Result GetPath(sf::Out<Path> out, ContentId content_id) override; + virtual Result GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id) override; + virtual Result CleanupAllPlaceHolder() override; + virtual Result ListPlaceHolder(sf::Out<s32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) override; + virtual Result GetContentCount(sf::Out<s32> out_count) override; + virtual Result ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out_buf, s32 start_offset) override; + virtual Result GetSizeFromContentId(sf::Out<s64> out_size, ContentId content_id) override; + virtual Result DisableForcibly() override; + virtual Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) override; + virtual Result SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) override; + virtual Result ReadContentIdFile(const sf::OutBuffer &buf, ContentId content_id, s64 offset) override; + virtual Result GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id) override; + virtual Result GetRightsIdFromPlaceHolderIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) override; + virtual Result GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, fs::ContentAttributes attr) override; + virtual Result GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) override; + virtual Result GetRightsIdFromContentIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id) override; + virtual Result GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id, fs::ContentAttributes attr) override; + virtual Result WriteContentForDebug(ContentId content_id, s64 offset, const sf::InBuffer &data) override; + virtual Result GetFreeSpaceSize(sf::Out<s64> out_size) override; + virtual Result GetTotalSpaceSize(sf::Out<s64> out_size) override; + virtual Result FlushPlaceHolder() override; + virtual Result GetSizeFromPlaceHolderId(sf::Out<s64> out, PlaceHolderId placeholder_id) override; + virtual Result RepairInvalidFileAttribute() override; + virtual Result GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id, fs::ContentAttributes attr) override; + virtual Result RegisterPath(const ContentId &content_id, const Path &path) override; + virtual Result ClearRegisteredPath() override; + virtual Result GetProgramId(sf::Out<ncm::ProgramId> out, ContentId content_id, fs::ContentAttributes attr) override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_storage_impl_base.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_storage_impl_base.hpp new file mode 100644 index 00000000..945d4168 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_content_storage_impl_base.hpp @@ -0,0 +1,92 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ncm { + + class ContentStorageImplBase { + NON_COPYABLE(ContentStorageImplBase); + NON_MOVEABLE(ContentStorageImplBase); + protected: + PathString m_root_path; + MakeContentPathFunction m_make_content_path_func; + bool m_disabled; + protected: + ContentStorageImplBase() : m_root_path(), m_make_content_path_func(), m_disabled(false) { /* ... */ } + protected: + /* Helpers. */ + Result EnsureEnabled() const { + R_UNLESS(!m_disabled, ncm::ResultInvalidContentStorage()); + R_SUCCEED(); + } + + static Result GetRightsId(ncm::RightsId *out_rights_id, const Path &path, fs::ContentAttributes attr) { + if (hos::GetVersion() >= hos::Version_3_0_0) { + R_TRY(fs::GetRightsId(std::addressof(out_rights_id->id), std::addressof(out_rights_id->key_generation), path.str, attr)); + } else { + R_TRY(fs::GetRightsId(std::addressof(out_rights_id->id), path.str, attr)); + out_rights_id->key_generation = 0; + } + R_SUCCEED(); + } + public: + /* Actual commands. */ + virtual Result GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) = 0; + virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size) = 0; + virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) = 0; + virtual Result HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) = 0; + virtual Result WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, const sf::InBuffer &data) = 0; + virtual Result Register(PlaceHolderId placeholder_id, ContentId content_id) = 0; + virtual Result Delete(ContentId content_id) = 0; + virtual Result Has(sf::Out<bool> out, ContentId content_id) = 0; + virtual Result GetPath(sf::Out<Path> out, ContentId content_id) = 0; + virtual Result GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id) = 0; + virtual Result CleanupAllPlaceHolder() = 0; + virtual Result ListPlaceHolder(sf::Out<s32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) = 0; + virtual Result GetContentCount(sf::Out<s32> out_count) = 0; + virtual Result ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out_buf, s32 start_offset) = 0; + virtual Result GetSizeFromContentId(sf::Out<s64> out_size, ContentId content_id) = 0; + virtual Result DisableForcibly() = 0; + virtual Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) = 0; + virtual Result SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) = 0; + virtual Result ReadContentIdFile(const sf::OutBuffer &buf, ContentId content_id, s64 offset) = 0; + virtual Result GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id) = 0; + virtual Result GetRightsIdFromPlaceHolderIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) = 0; + virtual Result GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, fs::ContentAttributes attr) = 0; + virtual Result GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) = 0; + virtual Result GetRightsIdFromContentIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id) = 0; + virtual Result GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id, fs::ContentAttributes attr) = 0; + virtual Result WriteContentForDebug(ContentId content_id, s64 offset, const sf::InBuffer &data) = 0; + virtual Result GetFreeSpaceSize(sf::Out<s64> out_size) = 0; + virtual Result GetTotalSpaceSize(sf::Out<s64> out_size) = 0; + virtual Result FlushPlaceHolder() = 0; + virtual Result GetSizeFromPlaceHolderId(sf::Out<s64> out, PlaceHolderId placeholder_id) = 0; + virtual Result RepairInvalidFileAttribute() = 0; + virtual Result GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id, fs::ContentAttributes attr) = 0; + virtual Result RegisterPath(const ContentId &content_id, const Path &path) = 0; + virtual Result ClearRegisteredPath() = 0; + virtual Result GetProgramId(sf::Out<ncm::ProgramId> out, ContentId content_id, fs::ContentAttributes attr) = 0; + + /* 16.0.0 Alignment change hacks. */ + Result CreatePlaceHolder_AtmosphereAlignmentFix(ContentId content_id, PlaceHolderId placeholder_id, s64 size) { R_RETURN(this->CreatePlaceHolder(placeholder_id, content_id, size)); } + Result Register_AtmosphereAlignmentFix(ContentId content_id, PlaceHolderId placeholder_id) { R_RETURN(this->Register(placeholder_id, content_id)); } + Result RevertToPlaceHolder_AtmosphereAlignmentFix(ncm::ContentId old_content_id, ncm::ContentId new_content_id, ncm::PlaceHolderId placeholder_id) { R_RETURN(this->RevertToPlaceHolder(placeholder_id, old_content_id, new_content_id)); } + Result GetRightsIdFromPlaceHolderIdWithCacheDeprecated(sf::Out<ncm::RightsId> out_rights_id, ContentId cache_content_id, PlaceHolderId placeholder_id) { R_RETURN(this->GetRightsIdFromPlaceHolderIdWithCache(out_rights_id, placeholder_id, cache_content_id, fs::ContentAttributes_None)); } + }; + static_assert(ncm::IsIContentStorage<ContentStorageImplBase>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_extended_data_mapper.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_extended_data_mapper.hpp new file mode 100644 index 00000000..633ffd80 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_extended_data_mapper.hpp @@ -0,0 +1,430 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "ncm_file_mapper_file.hpp" +#include "ncm_fs_utils.hpp" + +namespace ams::ncm { + + namespace impl { + + template<typename T> + concept IsMappedMemorySpan = std::same_as<T, Span<u8>>; + + constexpr inline u64 InitialIdCounterValue = 0x12345; + + } + + class SingleCacheMapperBase : public IMapper { + private: + bool m_is_mapped; + MappedMemory m_mapped_memory; + size_t m_accessible_size; + bool m_is_using; + bool m_is_dirty; + u64 m_id_counter; + u8 *m_buffer; + size_t m_buffer_size; + public: + SingleCacheMapperBase(Span<u8> span) : m_is_mapped(false), m_mapped_memory{}, m_accessible_size(0), m_is_using(false), m_is_dirty(false), m_id_counter(impl::InitialIdCounterValue), m_buffer(span.data()), m_buffer_size(span.size_bytes()) { + /* ... */ + } + protected: + void Finalize() { + /* If we're unused and mapped, we should unmap. */ + if (!m_is_using && m_is_mapped) { + this->Unmap(); + } + } + private: + Result Unmap() { + /* Check pre-conditions. */ + AMS_ASSERT(m_is_mapped); + + /* If we're dirty, we'll need to flush the entry. */ + if (m_is_dirty) { + /* Unmap our memory. */ + MappedMemory mem = m_mapped_memory; + if (mem.offset + mem.buffer_size > m_accessible_size) { + mem.buffer_size = m_accessible_size - mem.offset; + } + + R_TRY(this->UnmapImpl(std::addressof(mem))); + } + + /* Set as dirty/not mapped. */ + m_is_dirty = false; + m_is_mapped = false; + R_SUCCEED(); + } + + Result GetMappedMemoryImpl(MappedMemory *out, size_t offset, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(m_is_mapped); + + /* Ensure the accessible size works. */ + const bool can_update = this->IsAccessibleSizeUpdatable(); + R_UNLESS((offset + size <= m_accessible_size || can_update), ncm::ResultMapperInvalidArgument()); + + /* Update our accessible size. */ + m_accessible_size = std::max<size_t>(m_accessible_size, size + offset); + + /* Set the output memory. */ + *out = m_mapped_memory; + out->buffer_size = std::min<size_t>(out->buffer_size, m_accessible_size - out->offset); + R_SUCCEED(); + } + public: + virtual Result GetMappedMemory(MappedMemory *out, size_t offset, size_t size) override final { + /* Ensure our memory is valid, if it's already mapped. */ + if (m_is_mapped) { + /* If we can re-use the previous mapping, do so. */ + if (m_mapped_memory.IsIncluded(offset, size)) { + /* If the memory is in use, we can't get a new mapping. */ + R_UNLESS(!m_is_using, ncm::ResultMapperBusy()); + + /* Get the output memory. */ + R_RETURN(this->GetMappedMemoryImpl(out, offset, size)); + } + + /* We don't have the correct data mapped, so we need to map. */ + R_TRY(this->Unmap()); + } + + /* Map. */ + R_TRY(this->MapImpl(std::addressof(m_mapped_memory), Span<u8>(m_buffer, m_buffer_size), offset, size)); + + /* Set our mapping id. */ + m_mapped_memory.id = m_id_counter++; + + /* Get the output memory. */ + R_RETURN(this->GetMappedMemoryImpl(out, offset, size)); + } + + virtual Result MarkUsing(u64 id) override final { + /* Check that the mapping is correct. */ + R_UNLESS(m_is_mapped, ncm::ResultMapperNotMapped()); + R_UNLESS(m_mapped_memory.id == id, ncm::ResultMapperNotMapped()); + + /* Mark as using. */ + m_is_using = true; + R_SUCCEED(); + } + + virtual Result UnmarkUsing(u64 id) override final { + /* Check that the mapping is correct. */ + R_UNLESS(m_is_mapped, ncm::ResultMapperNotMapped()); + R_UNLESS(m_mapped_memory.id == id, ncm::ResultMapperNotMapped()); + + /* Mark as not using. */ + m_is_using = false; + R_SUCCEED(); + } + + virtual Result MarkDirty(u64 id) override final { + /* Check that the mapping is correct. */ + R_UNLESS(m_is_mapped, ncm::ResultMapperNotMapped()); + R_UNLESS(m_mapped_memory.id == id, ncm::ResultMapperNotMapped()); + + /* Mark as dirty. */ + m_is_dirty = true; + R_SUCCEED(); + } + }; + + template<size_t MaxEntries> + class MultiCacheReadonlyMapperBase : public IMapper { + private: + struct Entry { + MappedMemory memory; + u64 lru_counter; + u32 use_count; + bool is_mapped; + u8 *buffer; + size_t buffer_size; + }; + private: + Entry m_entry_storages[MaxEntries]; + Entry * const m_entries; + size_t m_entry_count; + u64 m_id_counter; + u64 m_lru_counter; + size_t m_accessible_size; + public: + template<impl::IsMappedMemorySpan... Args> + MultiCacheReadonlyMapperBase(Args... args) : m_entries(m_entry_storages), m_entry_count(sizeof...(Args)), m_id_counter(impl::InitialIdCounterValue), m_lru_counter(1), m_accessible_size(0) { + /* Check the argument count is valid. */ + static_assert(sizeof...(Args) <= MaxEntries); + + /* Initialize entries. */ + auto InitializeEntry = [](Entry *entry, Span<u8> span) ALWAYS_INLINE_LAMBDA -> void { + *entry = {}; + entry->buffer = span.data(); + entry->buffer_size = span.size_bytes(); + }; + + Entry *cur_entry = m_entries; + (InitializeEntry(cur_entry++, args), ...); + } + + size_t GetSize() { + return m_accessible_size; + } + protected: + void SetSize(size_t size) { + m_accessible_size = size; + } + + void Finalize() { + /* Mark all entries as unmapped. */ + for (size_t i = 0; i < m_entry_count; ++i) { + /* We can't mark unmapped an entry which is in use. */ + if (m_entries[i].use_count > 0) { + break; + } + + if (m_entries[i].is_mapped) { + m_entries[i].is_mapped = false; + } + } + } + private: + Result GetMappedMemoryImpl(MappedMemory *out, const MappedMemory &src) { + /* Set the output memory. */ + *out = src; + out->buffer_size = std::min<size_t>(out->buffer_size, m_accessible_size - out->offset); + R_SUCCEED(); + } + public: + virtual Result GetMappedMemory(MappedMemory *out, size_t offset, size_t size) override final { + /* Try to find an entry which contains the desired region. */ + for (size_t i = 0; i < m_entry_count; ++i) { + if (m_entries[i].is_mapped && m_entries[i].memory.IsIncluded(offset, size)) { + R_RETURN(this->GetMappedMemoryImpl(out, m_entries[i].memory)); + } + } + + /* Find the oldest entry. */ + Entry *oldest = nullptr; + Entry *best_entry = nullptr; + for (size_t i = 0; i < m_entry_count; ++i) { + if (m_entries[i].is_mapped) { + if (m_entries[i].use_count == 0) { + if (oldest == nullptr || m_entries[i].lru_counter < oldest->lru_counter) { + oldest = std::addressof(m_entries[i]); + } + } + } else { + best_entry = std::addressof(m_entries[i]); + } + } + + /* If we didn't find a free entry, use the oldest. */ + best_entry = best_entry != nullptr ? best_entry : oldest; + R_UNLESS(best_entry != nullptr, ncm::ResultMapperBusy()); + + /* Ensure the best entry isn't mapped. */ + if (best_entry->is_mapped) { + best_entry->is_mapped = false; + } + + /* Map. */ + R_TRY(this->MapImpl(std::addressof(best_entry->memory), Span<u8>(best_entry->buffer, best_entry->buffer_size), offset, size)); + + /* Set our mapping id. */ + best_entry->memory.id = m_id_counter++; + + /* Get the output memory. */ + R_RETURN(this->GetMappedMemoryImpl(out, best_entry->memory)); + } + + virtual Result MarkUsing(u64 id) override final { + /* Try to unmark the entry. */ + for (size_t i = 0; i < m_entry_count; ++i) { + if (m_entries[i].memory.id == id) { + ++m_entries[i].use_count; + m_entries[i].lru_counter = m_lru_counter++; + R_SUCCEED(); + } + } + + /* We failed to unmark. */ + R_THROW(ncm::ResultMapperNotMapped()); + } + + virtual Result UnmarkUsing(u64 id) override final { + /* Try to unmark the entry. */ + for (size_t i = 0; i < m_entry_count; ++i) { + if (m_entries[i].memory.id == id) { + --m_entries[i].use_count; + R_SUCCEED(); + } + } + + /* We failed to unmark. */ + R_THROW(ncm::ResultMapperNotMapped()); + } + + virtual Result MarkDirty(u64) override final{ + R_THROW(ncm::ResultMapperNotSupported()); + } + }; + + template<typename CacheMapperBase> + class ExtendedDataMapperBase : public CacheMapperBase { + private: + static constexpr size_t MappingAlignment = 1_KB; + private: + util::optional<impl::MountNameString> m_mount_name = util::nullopt; + ncm::FileMapperFile m_file_mapper{}; + size_t m_extended_data_offset; + bool m_suppress_fs_auto_abort; + public: + template<typename... Args> + ExtendedDataMapperBase(Args &&... args) : CacheMapperBase(std::forward<Args>(args)...) { /* ... */ } + + virtual ~ExtendedDataMapperBase() override { + /* Finalize. */ + this->Finalize(); + } + + Result Initialize(const char *content_path, fs::ContentAttributes attr, bool suppress_fs_auto_abort) { + /* Set whether we should suppress fs aborts. */ + m_suppress_fs_auto_abort = suppress_fs_auto_abort; + + /* Suppress fs auto abort, if we need to. */ + auto disable_aborts = this->GetFsAutoAbortDisabler(); + + /* Mount the content. */ + auto mount_name = impl::CreateUniqueMountName(); + R_TRY(impl::MountContentMetaImpl(mount_name.str, content_path, attr)); + + /* Set our mount name. */ + m_mount_name.emplace(mount_name.str); + + /* Open the root directory. */ + auto root_path = impl::GetRootDirectoryPath(mount_name); + fs::DirectoryHandle dir; + R_TRY(fs::OpenDirectory(std::addressof(dir), root_path.str, fs::OpenDirectoryMode_File)); + ON_SCOPE_EXIT { fs::CloseDirectory(dir); }; + + /* Loop directory reading until we find the entry we're looking for. */ + while (true) { + /* Read one entry, and finish when we fail to read. */ + fs::DirectoryEntry entry; + s64 num_read; + R_TRY(fs::ReadDirectory(std::addressof(num_read), std::addressof(entry), dir, 1)); + if (num_read == 0) { + break; + } + + /* If this is the content meta file, parse it. */ + if (IsContentMetaFileName(entry.name)) { + /* Create the file path. */ + impl::FilePathString file_path(root_path.str); + file_path.Append(entry.name); + + /* Setup our file mapped. */ + R_TRY(m_file_mapper.Initialize(file_path, FileMapperFile::OpenMode::Read)); + + /* Read the extended header. */ + PackagedContentMetaHeader pkg_header; + R_TRY(m_file_mapper.Read(0, std::addressof(pkg_header), sizeof(pkg_header))); + + /* Set our extended data offset. */ + m_extended_data_offset = PackagedContentMetaReader(std::addressof(pkg_header), sizeof(pkg_header)).GetExtendedDataOffset(); + + const size_t accessible_size = m_file_mapper.GetFileSize() >= m_extended_data_offset; + R_UNLESS(accessible_size, ncm::ResultInvalidContentMetaFileSize()); + + /* Set our accessible size. */ + this->SetSize(accessible_size); + R_SUCCEED(); + } + } + + R_THROW(ncm::ResultContentMetaNotFound()); + } + + void Finalize() { + /* Suppress fs auto abort, if we need to. */ + auto disable_aborts = this->GetFsAutoAbortDisabler(); + + /* Finalize our implementation. */ + CacheMapperBase::Finalize(); + + /* Finalize our file mapper. */ + m_file_mapper.Finalize(); + + /* Finalize our mount name. */ + if (m_mount_name.has_value()) { + fs::Unmount(m_mount_name.value().Get()); + m_mount_name = util::nullopt; + } + } + protected: + virtual Result MapImpl(MappedMemory *out, Span<u8> data, size_t offset, size_t size) override final { + /* Suppress fs auto abort, if we need to. */ + auto disable_aborts = this->GetFsAutoAbortDisabler(); + + /* Get the requested map offset/size. */ + u8 *map_data = data.data(); + size_t map_size = data.size_bytes(); + + /* Align the mapping, and ensure it remains valid. */ + const size_t aligned_offset = util::AlignDown(offset, MappingAlignment); + R_UNLESS((offset + size) - aligned_offset <= map_size, ncm::ResultMapperInvalidArgument()); + + /* Read the data. */ + const size_t map_offset = m_extended_data_offset + aligned_offset; + if (map_offset + map_size >= m_file_mapper.GetFileSize()) { + map_size = m_file_mapper.GetFileSize() - map_offset; + } + R_TRY(m_file_mapper.Read(map_offset, map_data, map_size)); + + /* Create the output mapped memory. */ + *out = MappedMemory { + .id = 0, + .offset = aligned_offset, + .buffer = map_data, + .buffer_size = map_size, + }; + R_SUCCEED(); + } + + virtual Result UnmapImpl(MappedMemory *) override final { + R_THROW(ncm::ResultMapperNotSupported()); + } + + virtual bool IsAccessibleSizeUpdatable() override final { + return false; + } + private: + util::optional<fs::ScopedAutoAbortDisabler> GetFsAutoAbortDisabler() { + /* Create an abort disabler, if we should disable aborts. */ + util::optional<fs::ScopedAutoAbortDisabler> disable_abort{util::nullopt}; + if (m_suppress_fs_auto_abort) { + disable_abort.emplace(); + } + return disable_abort; + } + }; + + template<size_t N> + using MultiCacheReadonlyMapper = ExtendedDataMapperBase<MultiCacheReadonlyMapperBase<N>>; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_file_mapper_file.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_file_mapper_file.hpp new file mode 100644 index 00000000..7e8fb9ca --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_file_mapper_file.hpp @@ -0,0 +1,123 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ncm { + + class FileMapperFile { + public: + enum class OpenMode { + Read, + ReadWrite, + ReadWriteAppend, + }; + private: + const char *m_path; + OpenMode m_mode; + util::optional<fs::FileHandle> m_file; + size_t m_file_size; + size_t m_max_size; + public: + FileMapperFile() : m_file(util::nullopt) { /* ... */ } + + ~FileMapperFile() { + this->Finalize(); + } + + Result Initialize(const char *path, OpenMode mode) { + /* Set our path/mode. */ + m_path = path; + m_mode = mode; + + /* Ensure we're open. */ + R_TRY(this->EnsureOpen()); + + /* Get the file size. */ + s64 size; + R_TRY(fs::GetFileSize(std::addressof(size), m_file.value())); + + /* Set our file size/loaded size. */ + m_file_size = static_cast<size_t>(size); + m_max_size = static_cast<size_t>(size); + + R_SUCCEED(); + } + + void Finalize() { + /* If we have a file, close (and flush) it. */ + if (m_file.has_value()) { + if (m_mode != OpenMode::Read) { + R_ABORT_UNLESS(fs::FlushFile(m_file.value())); + } + fs::CloseFile(m_file.value()); + m_file = util::nullopt; + } + } + + size_t GetFileSize() const { return m_file_size; } + size_t GetMaxSize() const { return m_max_size; } + + Result Read(size_t offset, void *dst, size_t size) { + /* Determine the end offset. */ + const size_t end_offset = offset + size; + + /* Unless we're allowed to append, we need to have a big enough file. */ + if (m_mode != OpenMode::ReadWriteAppend) { + R_UNLESS(end_offset <= m_file_size, ncm::ResultMapperInvalidArgument()); + } + + /* Clear the output. */ + std::memset(dst, 0, size); + + /* Check that our offset is valid. */ + R_UNLESS(offset <= m_file_size, ncm::ResultMapperInvalidArgument()); + + /* Ensure we're open. */ + R_TRY(this->EnsureOpen()); + + /* Read what we can. */ + const size_t read_size = (offset + size >= m_file_size) ? (m_file_size - offset) : size; + AMS_ASSERT(read_size >= size); + + R_TRY(fs::ReadFile(m_file.value(), offset, dst, read_size)); + + /* Update our max size. */ + m_max_size = std::max<size_t>(m_max_size, offset + read_size); + + R_SUCCEED(); + } + private: + Result EnsureOpen() { + /* If we've opened, we're done. */ + R_SUCCEED_IF(m_file.has_value()); + + /* Open based on our mode. */ + fs::FileHandle file; + switch (m_mode) { + case OpenMode::Read: R_TRY(fs::OpenFile(std::addressof(file), m_path, fs::OpenMode_Read)); break; + case OpenMode::ReadWrite: R_TRY(fs::OpenFile(std::addressof(file), m_path, fs::OpenMode_ReadWrite)); break; + case OpenMode::ReadWriteAppend: R_TRY(fs::OpenFile(std::addressof(file), m_path, fs::OpenMode_All)); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Set our file. */ + m_file = file; + R_SUCCEED(); + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_fs_utils.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_fs_utils.cpp new file mode 100644 index 00000000..7a8aab32 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_fs_utils.cpp @@ -0,0 +1,90 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ncm_fs_utils.hpp" + +namespace ams::ncm::impl { + + namespace { + + constinit std::atomic<u32> g_mount_name_count; + + } + + bool PathView::HasPrefix(util::string_view prefix) const { + return m_path.compare(0, prefix.length(), prefix) == 0; + } + + bool PathView::HasSuffix(util::string_view suffix) const { + return m_path.compare(m_path.length() - suffix.length(), suffix.length(), suffix) == 0; + } + + util::string_view PathView::GetFileName() const { + auto pos = m_path.find_last_of("/"); + return pos != util::string_view::npos ? m_path.substr(pos + 1) : m_path; + } + + MountName CreateUniqueMountName() { + MountName name = {}; + util::SNPrintf(name.str, sizeof(name.str), "@ncm%08x", g_mount_name_count.fetch_add(1)); + return name; + } + + RootDirectoryPath GetRootDirectoryPath(const MountName &mount_name) { + RootDirectoryPath path = {}; + util::SNPrintf(path.str, sizeof(path.str), "%s:/", mount_name.str); + + return path; + } + + Result CopyFile(const char *dst_path, const char *src_path) { + fs::FileHandle src_file, dst_file; + + /* Open the source file and get its size. */ + R_TRY(fs::OpenFile(std::addressof(src_file), src_path, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(src_file); }; + + s64 file_size; + R_TRY(fs::GetFileSize(std::addressof(file_size), src_file)); + + /* Create the destination file. */ + R_TRY(fs::CreateFile(dst_path, file_size)); + + /* Open the destination file. */ + R_TRY(fs::OpenFile(std::addressof(dst_file), dst_path, fs::OpenMode_Write)); + ON_SCOPE_EXIT { fs::CloseFile(dst_file); }; + + /* Allocate a buffer with which to copy. */ + constexpr size_t BufferSize = 4_KB; + AutoBuffer buffer; + R_TRY(buffer.Initialize(BufferSize)); + + /* Repeatedly read until we've copied all the data. */ + s64 offset = 0; + while (offset < file_size) { + const size_t read_size = std::min(static_cast<size_t>(file_size - offset), buffer.GetSize()); + R_TRY(fs::ReadFile(src_file, offset, buffer.Get(), read_size)); + R_TRY(fs::WriteFile(dst_file, offset, buffer.Get(), read_size, fs::WriteOption::None)); + offset += read_size; + } + + /* Flush the destination file. */ + R_TRY(fs::FlushFile(dst_file)); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_fs_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_fs_utils.hpp new file mode 100644 index 00000000..e4140fab --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_fs_utils.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ncm::impl { + + using FilePathString = kvdb::BoundedString<64>; + + Result CopyFile(const char *dst_path, const char *src_path); + + class PathView { + private: + util::string_view m_path; + public: + PathView(util::string_view p) : m_path(p) { /* ...*/ } + bool HasPrefix(util::string_view prefix) const; + bool HasSuffix(util::string_view suffix) const; + util::string_view GetFileName() const; + }; + + struct MountName { + char str[fs::MountNameLengthMax + 1]; + }; + + using MountNameString = kvdb::BoundedString<sizeof(MountName{}.str)>; + + struct RootDirectoryPath { + char str[fs::MountNameLengthMax + 3]; /* mount name + :/ */ + }; + + MountName CreateUniqueMountName(); + RootDirectoryPath GetRootDirectoryPath(const MountName &mount_name); + + Result MountContentMetaImpl(const char *mount_name, const char *path, fs::ContentAttributes attr); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_host_content_storage_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_host_content_storage_impl.cpp new file mode 100644 index 00000000..592a2df3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_host_content_storage_impl.cpp @@ -0,0 +1,250 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ncm_host_content_storage_impl.hpp" + +namespace ams::ncm { + + Result HostContentStorageImpl::GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) { + AMS_UNUSED(out); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size) { + AMS_UNUSED(placeholder_id, content_id, size); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::DeletePlaceHolder(PlaceHolderId placeholder_id) { + AMS_UNUSED(placeholder_id); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) { + AMS_UNUSED(out, placeholder_id); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, const sf::InBuffer &data) { + AMS_UNUSED(placeholder_id, offset, data); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::Register(PlaceHolderId placeholder_id, ContentId content_id) { + AMS_UNUSED(placeholder_id, content_id); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::Delete(ContentId content_id) { + AMS_UNUSED(content_id); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::Has(sf::Out<bool> out, ContentId content_id) { + R_TRY(this->EnsureEnabled()); + + /* Attempt to locate the content. */ + Path path; + R_TRY_CATCH(m_registered_content->GetPath(std::addressof(path), content_id)) { + /* The content is absent, this is fine. */ + R_CATCH(ncm::ResultContentNotFound) { + out.SetValue(false); + R_SUCCEED(); + } + } R_END_TRY_CATCH; + + out.SetValue(true); + R_SUCCEED(); + } + + Result HostContentStorageImpl::GetPath(sf::Out<Path> out, ContentId content_id) { + R_TRY(this->EnsureEnabled()); + R_RETURN(m_registered_content->GetPath(out.GetPointer(), content_id)); + } + + Result HostContentStorageImpl::GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id) { + AMS_UNUSED(out, placeholder_id); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::CleanupAllPlaceHolder() { + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::ListPlaceHolder(sf::Out<s32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) { + AMS_UNUSED(out_count, out_buf); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::GetContentCount(sf::Out<s32> out_count) { + AMS_UNUSED(out_count); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out_buf, s32 offset) { + AMS_UNUSED(out_count, out_buf, offset); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::GetSizeFromContentId(sf::Out<s64> out_size, ContentId content_id) { + AMS_UNUSED(out_size, content_id); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result HostContentStorageImpl::DisableForcibly() { + m_disabled = true; + R_SUCCEED(); + } + + Result HostContentStorageImpl::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) { + AMS_UNUSED(placeholder_id, old_content_id, new_content_id); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) { + AMS_UNUSED(placeholder_id, size); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::ReadContentIdFile(const sf::OutBuffer &buf, ContentId content_id, s64 offset) { + AMS_UNUSED(buf, content_id, offset); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result HostContentStorageImpl::GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id) { + AMS_UNUSED(out_rights_id, placeholder_id); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::GetRightsIdFromPlaceHolderIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) { + AMS_UNUSED(out_rights_id, placeholder_id); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, fs::ContentAttributes attr) { + AMS_UNUSED(out_rights_id, placeholder_id, attr); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) { + /* Obtain the regular rights id for the content id. */ + ncm::RightsId rights_id; + R_TRY(this->GetRightsIdFromContentIdDeprecated2(std::addressof(rights_id), content_id)); + + /* Output the fs rights id. */ + out_rights_id.SetValue(rights_id.id); + R_SUCCEED(); + } + + Result HostContentStorageImpl::GetRightsIdFromContentIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id) { + R_RETURN(this->GetRightsIdFromContentId(out_rights_id, content_id, fs::ContentAttributes_None)); + } + + Result HostContentStorageImpl::GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id, fs::ContentAttributes attr) { + R_TRY(this->EnsureEnabled()); + + /* Get the content path. */ + Path path; + R_TRY(m_registered_content->GetPath(std::addressof(path), content_id)); + + /* Acquire the rights id for the content. */ + RightsId rights_id; + R_TRY_CATCH(GetRightsId(std::addressof(rights_id), path, attr)) { + /* The content is absent, output a blank rights id. */ + R_CATCH(fs::ResultTargetNotFound) { + out_rights_id.SetValue({}); + R_SUCCEED(); + } + } R_END_TRY_CATCH; + + /* Output the rights id. */ + out_rights_id.SetValue(rights_id); + R_SUCCEED(); + } + + Result HostContentStorageImpl::WriteContentForDebug(ContentId content_id, s64 offset, const sf::InBuffer &data) { + AMS_UNUSED(content_id, offset, data); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::GetFreeSpaceSize(sf::Out<s64> out_size) { + out_size.SetValue(0); + R_SUCCEED(); + } + + Result HostContentStorageImpl::GetTotalSpaceSize(sf::Out<s64> out_size) { + out_size.SetValue(0); + R_SUCCEED(); + } + + Result HostContentStorageImpl::FlushPlaceHolder() { + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::GetSizeFromPlaceHolderId(sf::Out<s64> out, PlaceHolderId placeholder_id) { + AMS_UNUSED(out, placeholder_id); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::RepairInvalidFileAttribute() { + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::GetRightsIdFromPlaceHolderIdWithCacheDeprecated(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id) { + AMS_UNUSED(out_rights_id, placeholder_id, cache_content_id); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id, fs::ContentAttributes attr) { + AMS_UNUSED(out_rights_id, placeholder_id, cache_content_id, attr); + R_THROW(ncm::ResultNotSupported()); + } + + Result HostContentStorageImpl::RegisterPath(const ContentId &content_id, const Path &path) { + AMS_ABORT_UNLESS(spl::IsDevelopment()); + R_RETURN(m_registered_content->RegisterPath(content_id, path)); + } + + Result HostContentStorageImpl::ClearRegisteredPath() { + AMS_ABORT_UNLESS(spl::IsDevelopment()); + m_registered_content->ClearPaths(); + R_SUCCEED(); + } + + Result HostContentStorageImpl::GetProgramId(sf::Out<ncm::ProgramId> out, ContentId content_id, fs::ContentAttributes attr) { + R_TRY(this->EnsureEnabled()); + + /* Get the content path. */ + Path path; + R_TRY(m_registered_content->GetPath(std::addressof(path), content_id)); + + /* Check for correct extension. */ + const auto path_len = std::strlen(path.str); + const char *extension = path.str + path_len - 1; + if (*extension == '/') { + --extension; + } + R_UNLESS(path_len >= 4 && std::memcmp(extension - 4, ".ncd", 4) == 0, ncm::ResultInvalidContentMetaDirectory()); + + /* Obtain the program id for the content. */ + ncm::ProgramId program_id; + R_TRY(fs::GetProgramId(std::addressof(program_id), path.str, attr)); + + out.SetValue(program_id); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_host_content_storage_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_host_content_storage_impl.hpp new file mode 100644 index 00000000..4a54063b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_host_content_storage_impl.hpp @@ -0,0 +1,90 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ncm { + + class HostContentStorageImpl { + protected: + RegisteredHostContent *m_registered_content; + bool m_disabled; + protected: + /* Helpers. */ + Result EnsureEnabled() const { + R_UNLESS(!m_disabled, ncm::ResultInvalidContentStorage()); + R_SUCCEED(); + } + + static Result GetRightsId(ncm::RightsId *out_rights_id, const Path &path, fs::ContentAttributes attr) { + if (hos::GetVersion() >= hos::Version_3_0_0) { + R_TRY(fs::GetRightsId(std::addressof(out_rights_id->id), std::addressof(out_rights_id->key_generation), path.str, attr)); + } else { + R_TRY(fs::GetRightsId(std::addressof(out_rights_id->id), path.str, attr)); + out_rights_id->key_generation = 0; + } + R_SUCCEED(); + } + public: + HostContentStorageImpl(RegisteredHostContent *registered_content) : m_registered_content(registered_content), m_disabled(false) { /* ... */ } + public: + /* Actual commands. */ + Result GeneratePlaceHolderId(sf::Out<PlaceHolderId> out); + Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size); + Result DeletePlaceHolder(PlaceHolderId placeholder_id); + Result HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id); + Result WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, const sf::InBuffer &data); + Result Register(PlaceHolderId placeholder_id, ContentId content_id); + Result Delete(ContentId content_id); + Result Has(sf::Out<bool> out, ContentId content_id); + Result GetPath(sf::Out<Path> out, ContentId content_id); + Result GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id); + Result CleanupAllPlaceHolder(); + Result ListPlaceHolder(sf::Out<s32> out_count, const sf::OutArray<PlaceHolderId> &out_buf); + Result GetContentCount(sf::Out<s32> out_count); + Result ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out_buf, s32 start_offset); + Result GetSizeFromContentId(sf::Out<s64> out_size, ContentId content_id); + Result DisableForcibly(); + Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id); + Result SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size); + Result ReadContentIdFile(const sf::OutBuffer &buf, ContentId content_id, s64 offset); + Result GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id); + Result GetRightsIdFromPlaceHolderIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id); + Result GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, fs::ContentAttributes attr); + Result GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id); + Result GetRightsIdFromContentIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id); + Result GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id, fs::ContentAttributes attr); + Result WriteContentForDebug(ContentId content_id, s64 offset, const sf::InBuffer &data); + Result GetFreeSpaceSize(sf::Out<s64> out_size); + Result GetTotalSpaceSize(sf::Out<s64> out_size); + Result FlushPlaceHolder(); + Result GetSizeFromPlaceHolderId(sf::Out<s64> out, PlaceHolderId placeholder_id); + Result RepairInvalidFileAttribute(); + Result GetRightsIdFromPlaceHolderIdWithCacheDeprecated(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id); + Result GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id, fs::ContentAttributes attr); + Result RegisterPath(const ContentId &content_id, const Path &path); + Result ClearRegisteredPath(); + Result GetProgramId(sf::Out<ncm::ProgramId> out, ContentId content_id, fs::ContentAttributes attr); + + /* 16.0.0 Alignment change hacks. */ + Result CreatePlaceHolder_AtmosphereAlignmentFix(ContentId content_id, PlaceHolderId placeholder_id, s64 size) { R_RETURN(this->CreatePlaceHolder(placeholder_id, content_id, size)); } + Result Register_AtmosphereAlignmentFix(ContentId content_id, PlaceHolderId placeholder_id) { R_RETURN(this->Register(placeholder_id, content_id)); } + Result RevertToPlaceHolder_AtmosphereAlignmentFix(ncm::ContentId old_content_id, ncm::ContentId new_content_id, ncm::PlaceHolderId placeholder_id) { R_RETURN(this->RevertToPlaceHolder(placeholder_id, old_content_id, new_content_id)); } + Result GetRightsIdFromPlaceHolderIdWithCacheDeprecated(sf::Out<ncm::RightsId> out_rights_id, ContentId cache_content_id, PlaceHolderId placeholder_id) { R_RETURN(this->GetRightsIdFromPlaceHolderIdWithCache(out_rights_id, placeholder_id, cache_content_id, fs::ContentAttributes_None)); } + }; + static_assert(ncm::IsIContentStorage<HostContentStorageImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_install_task_base.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_install_task_base.cpp new file mode 100644 index 00000000..231d1806 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_install_task_base.cpp @@ -0,0 +1,1403 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ncm_extended_data_mapper.hpp" + +namespace ams::ncm { + + namespace { + + bool Contains(const StorageContentMetaKey *keys, s32 num_keys, const ContentMetaKey &key, StorageId storage_id) { + for (s32 i = 0; i < num_keys; i++) { + const StorageContentMetaKey &storage_key = keys[i]; + + /* Check if the key matches the input key and storage id. */ + if (storage_key.key == key && storage_key.storage_id == storage_id) { + return true; + } + } + + return false; + } + + bool Contains(const ContentMetaKey *keys, s32 num_keys, const ContentMetaKey &key) { + for (s32 i = 0; i < num_keys; i++) { + /* Check if the key matches the input key. */ + if (keys[i] == key) { + return true; + } + } + + return false; + } + + bool IsExpectedKey(const ContentMetaKey &expected_key, const ContentMetaKey &actual_key) { + return expected_key.id == actual_key.id && + expected_key.version == actual_key.version && + expected_key.type == actual_key.type; + } + + } + + void InstallTaskBase::Cancel() { + std::scoped_lock lk(m_cancel_mutex); + m_cancel_requested = true; + } + + void InstallTaskBase::ResetCancel() { + std::scoped_lock lk(m_cancel_mutex); + m_cancel_requested = false; + } + + bool InstallTaskBase::IsCancelRequested() { + std::scoped_lock lk(m_cancel_mutex); + return m_cancel_requested; + } + + Result InstallTaskBase::Initialize(StorageId install_storage, InstallTaskDataBase *data, u32 config) { + R_UNLESS(IsInstallableStorage(install_storage), ncm::ResultUnknownStorage()); + + m_install_storage = install_storage; + m_data = data; + m_config = config; + + R_RETURN(data->GetProgress(std::addressof(m_progress))); + } + + Result InstallTaskBase::Prepare() { + R_TRY(this->SetLastResultOnFailure(this->PrepareImpl())); + R_SUCCEED(); + } + + Result InstallTaskBase::GetPreparedPlaceHolderPath(Path *out_path, u64 id, ContentMetaType meta_type, ContentType type) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->CountInstallContentMetaData(std::addressof(count))); + + /* Iterate over content meta. */ + util::optional<PlaceHolderId> placeholder_id; + util::optional<StorageId> storage_id; + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->GetInstallContentMetaData(std::addressof(content_meta), i)); + const InstallContentMetaReader reader = content_meta.GetReader(); + + /* Ensure content meta matches the key and meta type. */ + const auto key = reader.GetKey(); + + if (key.id != id || key.type != meta_type) { + continue; + } + + /* Attempt to find a content info for the type. */ + for (size_t j = 0; j < reader.GetContentCount(); j++) { + const auto content_info = reader.GetContentInfo(j); + if (content_info->GetType() == type) { + placeholder_id = content_info->GetPlaceHolderId(); + storage_id = content_info->GetStorageId(); + break; + } + } + } + + R_UNLESS(placeholder_id, ncm::ResultPlaceHolderNotFound()); + R_UNLESS(storage_id, ncm::ResultPlaceHolderNotFound()); + + /* Open the relevant content storage. */ + ContentStorage storage; + R_TRY(OpenContentStorage(std::addressof(storage), *storage_id)); + + /* Get the path. */ + storage.GetPlaceHolderPath(out_path, *placeholder_id); + + R_SUCCEED(); + } + + Result InstallTaskBase::CalculateRequiredSize(s64 *out_size) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(m_data->Count(std::addressof(count))); + + s64 required_size = 0; + /* Iterate over each entry. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(m_data->Get(std::addressof(content_meta), i)); + const auto reader = content_meta.GetReader(); + + /* Sum the sizes from the content infos. */ + for (size_t j = 0; j < reader.GetContentCount(); j++) { + const auto *content_info = reader.GetContentInfo(j); + + if (content_info->install_state == InstallState::NotPrepared) { + required_size += ncm::CalculateRequiredSize(content_info->GetSize()); + } + } + } + + *out_size = required_size; + R_SUCCEED(); + } + + Result InstallTaskBase::PrepareImpl() { + /* Reset the throughput. */ + this->ResetThroughputMeasurement(); + + /* Transition from NotPrepared to DataPrepared. */ + if (this->GetProgress().state == InstallProgressState::NotPrepared) { + R_TRY(this->PrepareInstallContentMetaData()); + R_TRY(this->PrepareDependency()); + R_TRY(this->CheckInstallable()); + this->SetProgressState(InstallProgressState::DataPrepared); + } + + /* Transition from DataPrepared to Prepared. */ + if (this->GetProgress().state == InstallProgressState::DataPrepared) { + R_TRY(this->PreparePlaceHolder()); + this->SetProgressState(InstallProgressState::Prepared); + } + + /* Signal prepare is completed. */ + R_RETURN(this->OnPrepareComplete()); + } + + Result InstallTaskBase::Cleanup() { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(m_data->Count(std::addressof(count))); + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Get the content meta. */ + InstallContentMeta content_meta; + R_TRY(m_data->Get(std::addressof(content_meta), i)); + + /* Cleanup the content meta. */ + /* N doesn't check the result of this. */ + this->CleanupOne(content_meta); + } + + /* Cleanup the data and progress. */ + R_TRY(m_data->Cleanup()); + this->CleanupProgress(); + + R_SUCCEED(); + } + + Result InstallTaskBase::CleanupOne(const InstallContentMeta &content_meta) { + /* Obtain a reader and get the storage id. */ + const auto reader = content_meta.GetReader(); + const auto storage_id = reader.GetStorageId(); + R_SUCCEED_IF(storage_id == StorageId::None); + + /* Open the relevant content storage. */ + ContentStorage content_storage; + R_TRY(ncm::OpenContentStorage(std::addressof(content_storage), storage_id)); + + /* Iterate over content infos. */ + for (size_t i = 0; i < reader.GetContentCount(); i++) { + auto *content_info = reader.GetContentInfo(i); + + /* Delete placeholders for Prepared or Installed content infos. */ + if (content_info->install_state == InstallState::Prepared || content_info->install_state == InstallState::Installed) { + content_storage.DeletePlaceHolder(content_info->placeholder_id); + } + } + + R_SUCCEED(); + } + + Result InstallTaskBase::ListContentMetaKey(s32 *out_keys_written, StorageContentMetaKey *out_keys, s32 out_keys_count, s32 offset, ListContentMetaKeyFilter filter) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(m_data->Count(std::addressof(count))); + + /* Offset exceeds keys that can be written. */ + if (count <= offset) { + *out_keys_written = 0; + R_SUCCEED(); + } + + if (filter == ListContentMetaKeyFilter::All) { + const s32 num_keys = std::min(count, offset + out_keys_count); + + /* Iterate over content meta. */ + for (s32 i = offset; i < num_keys; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(m_data->Get(std::addressof(content_meta), i)); + + /* Write output StorageContentMetaKey. */ + const auto reader = content_meta.GetReader(); + out_keys[i - offset] = { reader.GetKey(), reader.GetStorageId() }; + } + + /* Output the number of keys written. */ + *out_keys_written = num_keys - offset; + } else { + s32 keys_written = 0; + s32 cur_offset = 0; + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(m_data->Get(std::addressof(content_meta), i)); + + /* Create a reader and check if the content has been committed. */ + const auto reader = content_meta.GetReader(); + const bool committed = reader.GetHeader()->committed; + + /* Apply filter. */ + if ((filter == ListContentMetaKeyFilter::Committed && committed) || (filter == ListContentMetaKeyFilter::NotCommitted && !committed)) { + /* Write output StorageContentMetaKey if at a suitable offset. */ + if (cur_offset >= offset) { + out_keys[keys_written++] = { reader.GetKey(), reader.GetStorageId() }; + } + + /* Increment the current offset. */ + cur_offset++; + + /* We can't write any more output keys. */ + if (keys_written >= out_keys_count) { + break; + } + } + } + + /* Output the number of keys written. */ + *out_keys_written = keys_written; + } + + R_SUCCEED(); + } + + Result InstallTaskBase::ListApplicationContentMetaKey(s32 *out_keys_written, ApplicationContentMetaKey *out_keys, s32 out_keys_count, s32 offset) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(m_data->Count(std::addressof(count))); + + /* Offset exceeds keys that can be written. */ + if (count <= offset) { + *out_keys_written = 0; + R_SUCCEED(); + } + + /* Iterate over content meta. */ + const s32 max = std::min(count, offset + out_keys_count); + s32 keys_written = 0; + for (s32 i = offset; i < max; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(m_data->Get(std::addressof(content_meta), i)); + + /* Create a reader. */ + const auto reader = content_meta.GetReader(); + + /* Ensure this key has an application id. */ + const auto app_id = reader.GetApplicationId(); + if (!app_id) { + continue; + } + + /* Write output ApplicationContentMetaKey. */ + out_keys[keys_written++] = { reader.GetKey(), *app_id }; + } + + *out_keys_written = keys_written; + R_SUCCEED(); + } + + Result InstallTaskBase::Execute() { + R_TRY(this->SetLastResultOnFailure(this->ExecuteImpl())); + R_SUCCEED(); + } + + Result InstallTaskBase::ExecuteImpl() { + this->StartThroughputMeasurement(); + + /* Count the number of content meta entries. */ + s32 count; + R_TRY(m_data->Count(std::addressof(count))); + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(m_data->Get(std::addressof(content_meta), i)); + + /* Write all prepared content infos. */ + { + /* If we fail while writing, update (but don't check the result). */ + ON_RESULT_FAILURE { m_data->Update(content_meta, i); }; + + /* Create a writer. */ + const auto writer = content_meta.GetWriter(); + + /* Iterate over content infos. */ + for (size_t j = 0; j < writer.GetContentCount(); j++) { + auto *content_info = writer.GetWritableContentInfo(j); + + /* Write prepared content infos. */ + if (content_info->install_state == InstallState::Prepared) { + R_TRY(this->WritePlaceHolder(writer.GetKey(), content_info)); + content_info->install_state = InstallState::Installed; + } + } + } + + /* Update our data. */ + R_TRY(m_data->Update(content_meta, i)); + } + + /* Execution has finished, signal this and update the state. */ + R_TRY(this->OnExecuteComplete()); + + this->SetProgressState(InstallProgressState::Downloaded); + + R_SUCCEED(); + } + + Result InstallTaskBase::PrepareAndExecute() { + R_TRY(this->SetLastResultOnFailure(this->PrepareImpl())); + R_TRY(this->SetLastResultOnFailure(this->ExecuteImpl())); + R_SUCCEED(); + } + + Result InstallTaskBase::VerifyAllNotCommitted(const StorageContentMetaKey *keys, s32 num_keys) { + /* No keys to check. */ + R_SUCCEED_IF(keys == nullptr); + + /* Count the number of content meta entries. */ + s32 count; + R_TRY(m_data->Count(std::addressof(count))); + + s32 num_not_committed = 0; + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(m_data->Get(std::addressof(content_meta), i)); + + /* Create a reader. */ + const auto reader = content_meta.GetReader(); + + if (Contains(keys, num_keys, reader.GetKey(), reader.GetStorageId())) { + /* Ensure content meta isn't committed. */ + R_UNLESS(!reader.GetHeader()->committed, ncm::ResultListPartiallyNotCommitted()); + num_not_committed++; + } + } + + /* Ensure number of uncommitted keys equals the number of input keys. */ + R_UNLESS(num_not_committed == num_keys, ncm::ResultListPartiallyNotCommitted()); + R_SUCCEED(); + } + + Result InstallTaskBase::CommitImpl(const StorageContentMetaKey *keys, s32 num_keys) { + /* Ensure progress state is Downloaded. */ + R_UNLESS(this->GetProgress().state == InstallProgressState::Downloaded, ncm::ResultInvalidInstallTaskState()); + + /* Ensure keys aren't committed. */ + R_TRY(this->VerifyAllNotCommitted(keys, num_keys)); + + /* Count the number of content meta entries. */ + s32 count; + R_TRY(m_data->Count(std::addressof(count))); + + /* List of storages to commit. */ + StorageList commit_list; + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(m_data->Get(std::addressof(content_meta), i)); + + /* Create a reader. */ + const auto reader = content_meta.GetReader(); + const auto cur_key = reader.GetKey(); + const auto storage_id = reader.GetStorageId(); + const size_t convert_size = reader.CalculateConvertSize(); + + /* Skip content meta not contained in input keys. */ + if (keys != nullptr && !Contains(keys, num_keys, cur_key, storage_id)) { + continue; + } + + /* Skip already committed. This check is primarily for if keys is nullptr. */ + if (reader.GetHeader()->committed) { + continue; + } + + /* Commit the current meta. */ + { + /* Ensure that if something goes wrong during commit, we still try to update. */ + ON_RESULT_FAILURE { m_data->Update(content_meta, i); }; + + /* Open a writer. */ + const auto writer = content_meta.GetWriter(); + + /* Convert to content meta and store to a buffer. */ + std::unique_ptr<char[]> content_meta_buffer(new (std::nothrow) char[convert_size]); + R_UNLESS(content_meta_buffer != nullptr, ncm::ResultAllocationFailed()); + reader.ConvertToContentMeta(content_meta_buffer.get(), convert_size); + + /* Open the content storage for this meta. */ + ContentStorage content_storage; + R_TRY(OpenContentStorage(std::addressof(content_storage), storage_id)); + + /* Open the content meta database for this meta. */ + ContentMetaDatabase meta_db; + R_TRY(OpenContentMetaDatabase(std::addressof(meta_db), storage_id)); + + /* Iterate over content infos. */ + for (size_t j = 0; j < reader.GetContentCount(); j++) { + const auto *content_info = reader.GetContentInfo(j); + + /* Register non-existing content infos. */ + if (content_info->install_state != InstallState::AlreadyExists) { + R_TRY(content_storage.Register(content_info->placeholder_id, content_info->info.content_id)); + } + } + + /* Store the content meta. */ + R_TRY(meta_db.Set(reader.GetKey(), content_meta_buffer.get(), convert_size)); + + /* Mark as committed. */ + writer.GetWritableHeader()->committed = true; + + /* Mark storage id to be committed later. */ + commit_list.Push(reader.GetStorageId()); + } + + /* Try to update our data. */ + R_TRY(m_data->Update(content_meta, i)); + } + + /* Commit all applicable content meta databases. */ + for (s32 i = 0; i < commit_list.Count(); i++) { + ContentMetaDatabase meta_db; + R_TRY(OpenContentMetaDatabase(std::addressof(meta_db), commit_list[i])); + R_TRY(meta_db.Commit()); + } + + /* Change progress state to committed if keys are nullptr. */ + if (keys == nullptr) { + this->SetProgressState(InstallProgressState::Committed); + } + + R_SUCCEED(); + } + + Result InstallTaskBase::Commit(const StorageContentMetaKey *keys, s32 num_keys) { + ON_RESULT_FAILURE { this->SetProgressState(InstallProgressState::Fatal); }; + + R_RETURN(this->SetLastResultOnFailure(this->CommitImpl(keys, num_keys))); + } + + Result InstallTaskBase::IncludesExFatDriver(bool *out) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(m_data->Count(std::addressof(count))); + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(m_data->Get(std::addressof(content_meta), i)); + + /* Check if the attributes are set for including the exfat driver. */ + if (content_meta.GetReader().GetHeader()->attributes & ContentMetaAttribute_IncludesExFatDriver) { + *out = true; + R_SUCCEED(); + } + } + + *out = false; + R_SUCCEED(); + } + + Result InstallTaskBase::WritePlaceHolderBuffer(InstallContentInfo *content_info, const void *data, size_t data_size) { + R_UNLESS(!this->IsCancelRequested(), ncm::ResultWritePlaceHolderCancelled()); + + /* Open the content storage for the content info. */ + ContentStorage content_storage; + R_TRY(OpenContentStorage(std::addressof(content_storage), content_info->storage_id)); + + /* Write data to the placeholder. */ + R_TRY(content_storage.WritePlaceHolder(content_info->placeholder_id, content_info->written, data, data_size)); + content_info->written += data_size; + + /* Update progress/throughput if content info isn't temporary. */ + if (!content_info->is_temporary) { + this->IncrementProgress(data_size); + this->UpdateThroughputMeasurement(data_size); + } + + /* Update the hash for the new data. */ + m_sha256_generator.Update(data, data_size); + R_SUCCEED(); + } + + Result InstallTaskBase::WritePlaceHolder(const ContentMetaKey &key, InstallContentInfo *content_info) { + if (content_info->is_sha256_calculated) { + /* Update the hash with the buffered data. */ + m_sha256_generator.InitializeWithContext(std::addressof(content_info->context)); + m_sha256_generator.Update(content_info->buffered_data, content_info->buffered_data_size); + } else { + /* Initialize the generator. */ + m_sha256_generator.Initialize(); + } + + { + ON_SCOPE_EXIT { + /* Update this content info's sha256 data. */ + m_sha256_generator.GetContext(std::addressof(content_info->context)); + content_info->buffered_data_size = m_sha256_generator.GetBufferedDataSize(); + m_sha256_generator.GetBufferedData(content_info->buffered_data, m_sha256_generator.GetBufferedDataSize()); + content_info->is_sha256_calculated = true; + }; + + /* Perform the placeholder write. */ + R_TRY(this->OnWritePlaceHolder(key, content_info)); + } + + /* Compare generated hash to expected hash if verification required. */ + if (content_info->verify_digest) { + u8 hash[crypto::Sha256Generator::HashSize]; + m_sha256_generator.GetHash(hash, crypto::Sha256Generator::HashSize); + R_UNLESS(std::memcmp(hash, content_info->digest.data, crypto::Sha256Generator::HashSize) == 0, ncm::ResultInvalidContentHash()); + } + + if (hos::GetVersion() >= hos::Version_2_0_0 && !(m_config & InstallConfig_IgnoreTicket)) { + ncm::RightsId rights_id; + { + /* Open the content storage and obtain the rights id. */ + ncm::ContentStorage storage; + R_TRY(OpenContentStorage(std::addressof(storage), content_info->storage_id)); + R_TRY(storage.GetRightsId(std::addressof(rights_id), content_info->placeholder_id, content_info->GetContentAttributes())); + } + + /* Install a ticket if necessary. */ + if (this->IsNecessaryInstallTicket(rights_id.id)) { + R_TRY_CATCH(this->InstallTicket(rights_id.id, content_info->meta_type)) { + R_CATCH(ncm::ResultIgnorableInstallTicketFailure) { /* We can ignore the installation failure. */ } + } R_END_TRY_CATCH; + } + } + + R_SUCCEED(); + } + + bool InstallTaskBase::IsNecessaryInstallTicket(const fs::RightsId &rights_id) { + /* If the title has no rights, there's no ticket to install. */ + fs::RightsId empty_rights_id = {}; + if (std::memcmp(std::addressof(rights_id), std::addressof(empty_rights_id), sizeof(fs::RightsId)) == 0) { + return false; + } + + /* TODO: Support detecting if a title requires rights. */ + /* TODO: How should es be handled without undesired effects? */ + return false; + } + + Result InstallTaskBase::PreparePlaceHolder() { + size_t total_size = 0; + + /* Count the number of content meta entries. */ + s32 count; + R_TRY(m_data->Count(std::addressof(count))); + + for (s32 i = 0; i < count; i++) { + R_UNLESS(!this->IsCancelRequested(), ncm::ResultCreatePlaceHolderCancelled()); + + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(os::SdkMutex, s_placeholder_mutex); + std::scoped_lock lk(s_placeholder_mutex); + + InstallContentMeta content_meta; + R_TRY(m_data->Get(std::addressof(content_meta), i)); + + /* Update the data (and check result) when we are done. */ + { + ON_RESULT_FAILURE { m_data->Update(content_meta, i); }; + + /* Automatically choose a suitable storage id. */ + auto reader = content_meta.GetReader(); + StorageId storage_id = StorageId::None; + if (reader.GetStorageId() != StorageId::None) { + storage_id = reader.GetStorageId(); + } else { + StorageId install_storage = this->GetInstallStorage(); + R_TRY(ncm::SelectDownloadableStorage(std::addressof(storage_id), install_storage, reader.CalculateContentRequiredSize())); + } + + /* Open the relevant content storage. */ + ContentStorage content_storage; + R_TRY(ncm::OpenContentStorage(std::addressof(content_storage), storage_id)); + + /* Update the storage id in the header. */ + auto writer = content_meta.GetWriter(); + writer.SetStorageId(storage_id); + + for (size_t j = 0; j < writer.GetContentCount(); j++) { + R_UNLESS(!this->IsCancelRequested(), ncm::ResultCreatePlaceHolderCancelled()); + auto *content_info = writer.GetWritableContentInfo(j); + + /* Check if we have the content already exists. */ + bool has_content; + R_TRY(content_storage.Has(std::addressof(has_content), content_info->GetId())); + + if (has_content) { + /* Add the size of installed content infos to the total size. */ + if (content_info->install_state == InstallState::Installed) { + total_size += content_info->GetSize(); + } + + /* Update the install state. */ + content_info->install_state = InstallState::AlreadyExists; + + /* Continue. */ + continue; + } + + if (content_info->install_state == InstallState::NotPrepared) { + /* Generate a placeholder id. */ + const PlaceHolderId placeholder_id = content_storage.GeneratePlaceHolderId(); + + /* Update the placeholder id in the content info. */ + content_info->placeholder_id = placeholder_id; + + /* Create the placeholder. */ + R_TRY(content_storage.CreatePlaceHolder(placeholder_id, content_info->GetId(), content_info->GetSize())); + + /* Update the install state. */ + content_info->install_state = InstallState::Prepared; + } + + /* Update the storage id for the content info. */ + content_info->storage_id = storage_id; + + /* Add the size of this content info to the total size. */ + total_size += content_info->GetSize(); + } + } + + /* Try to update our data. */ + R_TRY(m_data->Update(content_meta, i)); + } + + this->SetTotalSize(total_size); + R_SUCCEED(); + } + + Result InstallTaskBase::WriteContentMetaToPlaceHolder(InstallContentInfo *out_install_content_info, ContentStorage *storage, const InstallContentMetaInfo &meta_info, util::optional<bool> is_temporary) { + /* Generate a placeholder id. */ + auto placeholder_id = storage->GeneratePlaceHolderId(); + + /* Create the placeholder. */ + R_TRY(storage->CreatePlaceHolder(placeholder_id, meta_info.content_id, meta_info.content_size)); + ON_RESULT_FAILURE { storage->DeletePlaceHolder(placeholder_id); }; + + /* Output install content info. */ + *out_install_content_info = this->MakeInstallContentInfoFrom(meta_info, placeholder_id, is_temporary); + + /* Write install content info. */ + R_TRY(this->WritePlaceHolder(meta_info.key, out_install_content_info)); + + /* Don't delete the placeholder. Set state to installed. */ + out_install_content_info->install_state = InstallState::Installed; + R_SUCCEED(); + } + + Result InstallTaskBase::PrepareContentMeta(const InstallContentMetaInfo &meta_info, util::optional<ContentMetaKey> expected_key, util::optional<u32> source_version) { + /* Open the BuiltInSystem content storage. */ + ContentStorage content_storage; + R_TRY(OpenContentStorage(std::addressof(content_storage), StorageId::BuiltInSystem)); + + /* Write content meta to a placeholder. */ + InstallContentInfo content_info; + R_TRY(this->WriteContentMetaToPlaceHolder(std::addressof(content_info), std::addressof(content_storage), meta_info, util::nullopt)); + + /* Get the path of the placeholder. */ + Path path; + content_storage.GetPlaceHolderPath(std::addressof(path), content_info.GetPlaceHolderId()); + + /* If we fail, delete the placeholder. */ + ON_RESULT_FAILURE { content_storage.DeletePlaceHolder(content_info.GetPlaceHolderId()); }; + + /* Create a new temporary InstallContentInfo if relevant. */ + const bool is_temporary = content_info.is_temporary; + if (is_temporary) { + content_info = { + .digest = content_info.digest, + .info = content_info.info, + .placeholder_id = content_info.GetPlaceHolderId(), + .meta_type = content_info.meta_type, + .verify_digest = content_info.verify_digest, + }; + } + + /* Retrieve the install content meta data. */ + AutoBuffer meta; + R_TRY(this->GetInstallContentMetaDataFromPath(std::addressof(meta), path, content_info, source_version)); + + /* Update the storage id if BuiltInSystem. */ + if (m_install_storage == StorageId::BuiltInSystem) { + InstallContentMetaWriter writer(meta.Get(), meta.GetSize()); + writer.SetStorageId(StorageId::BuiltInSystem); + } + + /* Validate the expected key if we have an expectation. */ + if (expected_key) { + InstallContentMetaReader reader(meta.Get(), meta.GetSize()); + R_UNLESS(IsExpectedKey(*expected_key, reader.GetKey()), ncm::ResultUnexpectedContentMetaPrepared()); + } + + /* Push the data. */ + R_TRY(m_data->Push(meta.Get(), meta.GetSize())); + + /* If the placeholder is temporary, delete it. */ + if (is_temporary) { + content_storage.DeletePlaceHolder(content_info.GetPlaceHolderId()); + } + + R_SUCCEED(); + } + + Result InstallTaskBase::PrepareContentMeta(ContentId content_id, s64 size, ContentMetaType meta_type, AutoBuffer *buffer) { + /* Create a reader. */ + PackagedContentMetaReader reader(buffer->Get(), buffer->GetSize()); + + /* Initialize the temporary buffer. */ + AutoBuffer tmp_buffer; + R_TRY(tmp_buffer.Initialize(reader.CalculateConvertInstallContentMetaSize())); + + /* Convert packaged content meta to install content meta. */ + reader.ConvertToInstallContentMeta(tmp_buffer.Get(), tmp_buffer.GetSize(), InstallContentInfo::Make(ContentInfo::Make(content_id, size, ContentInfo::DefaultContentAttributes, ContentType::Meta), meta_type)); + + /* Push the content meta. */ + m_data->Push(tmp_buffer.Get(), tmp_buffer.GetSize()); + R_SUCCEED(); + } + + void InstallTaskBase::PrepareAgain() { + this->SetProgressState(InstallProgressState::NotPrepared); + } + + Result InstallTaskBase::PrepareDependency() { + R_SUCCEED(); + } + + Result InstallTaskBase::PrepareSystemUpdateDependency() { + /* Cleanup on failure. */ + ON_RESULT_FAILURE { this->Cleanup(); }; + + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->CountInstallContentMetaData(std::addressof(count))); + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(m_data->Get(std::addressof(content_meta), i)); + + /* Create a reader. */ + const InstallContentMetaReader reader = content_meta.GetReader(); + + /* Skip non system update content metas. */ + if (reader.GetHeader()->type != ContentMetaType::SystemUpdate) { + continue; + } + + /* Get the content info. */ + const auto *content_info = reader.GetContentInfo(ContentType::Meta); + + /* List content meta infos. */ + std::unique_ptr<ContentMetaInfo[]> content_meta_infos; + s32 num_content_meta_infos; + R_TRY(this->ReadContentMetaInfoList(std::addressof(num_content_meta_infos), std::addressof(content_meta_infos), reader.GetKey(), content_info->GetContentAttributes())); + + /* Iterate over content meta infos. */ + for (s32 j = 0; j < num_content_meta_infos; j++) { + ContentMetaInfo &content_meta_info = content_meta_infos[j]; + const ContentMetaKey content_meta_info_key = content_meta_info.ToKey(); + + /* If exfat driver is not included or is required, prepare the content meta. */ + if (!(content_meta_info.attributes & ContentMetaAttribute_IncludesExFatDriver) || (m_config & InstallConfig_RequiresExFatDriver)) { + R_TRY(this->PrepareContentMetaIfLatest(content_meta_info_key)); + } + } + } + + R_SUCCEED(); + } + + Result InstallTaskBase::GetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo *out) { + /* Open the BuiltInSystem content meta database. */ + ContentMetaDatabase meta_db; + R_TRY(OpenContentMetaDatabase(std::addressof(meta_db), StorageId::BuiltInSystem)); + + /* Count the number of content meta entries. */ + s32 count; + R_TRY(m_data->Count(std::addressof(count))); + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->GetInstallContentMetaData(std::addressof(content_meta), i)); + + /* Create a reader. */ + const InstallContentMetaReader reader = content_meta.GetReader(); + + /* Skip non system update content metas. */ + if (reader.GetHeader()->type != ContentMetaType::SystemUpdate) { + continue; + } + + /* Get the content info. */ + const auto *content_info = reader.GetContentInfo(ContentType::Meta); + + /* List content meta infos. */ + std::unique_ptr<ContentMetaInfo[]> content_meta_infos; + s32 num_content_meta_infos; + R_TRY(this->ReadContentMetaInfoList(std::addressof(num_content_meta_infos), std::addressof(content_meta_infos), reader.GetKey(), content_info->GetContentAttributes())); + + /* Iterate over content meta infos. */ + for (s32 j = 0; j < num_content_meta_infos; j++) { + const ContentMetaInfo &content_meta_info = content_meta_infos[j]; + bool found = true; + + /* Get the latest key. */ + ContentMetaKey installed_key; + R_TRY_CATCH(meta_db.GetLatest(std::addressof(installed_key), content_meta_info.id)) { + R_CATCH(ncm::ResultContentMetaNotFound) { + /* Key doesn't exist, this is okay. */ + found = false; + } + } R_END_TRY_CATCH; + + /* Exfat driver included, but not required. */ + if (content_meta_info.attributes & ContentMetaAttribute_IncludesExFatDriver && !(m_config & InstallConfig_RequiresExFatDriver)) { + continue; + } + + /* No reboot is required if we're installing a version below. */ + if (found && content_meta_info.version <= installed_key.version) { + continue; + } + + /* If not rebootless, a reboot is required. */ + if (!(content_meta_info.attributes & ContentMetaAttribute_Rebootless)) { + *out = SystemUpdateTaskApplyInfo::RequireReboot; + R_SUCCEED(); + } + } + } + + *out = SystemUpdateTaskApplyInfo::RequireNoReboot; + R_SUCCEED(); + } + + Result InstallTaskBase::PrepareContentMetaIfLatest(const ContentMetaKey &key) { + /* Check if the key is newer than what is already installed. */ + bool newer_than_installed; + R_TRY(this->IsNewerThanInstalled(std::addressof(newer_than_installed), key)); + + if (newer_than_installed) { + /* Get and prepare install content meta info. */ + InstallContentMetaInfo install_content_meta_info; + R_TRY(this->GetInstallContentMetaInfo(std::addressof(install_content_meta_info), key)); + R_TRY(this->PrepareContentMeta(install_content_meta_info, key, util::nullopt)); + } + + R_SUCCEED(); + } + + Result InstallTaskBase::IsNewerThanInstalled(bool *out, const ContentMetaKey &key) { + /* Obtain a list of suitable storage ids. */ + auto storage_list = GetStorageList(m_install_storage); + + /* Iterate over storage ids. */ + for (s32 i = 0; i < storage_list.Count(); i++) { + /* Open the content meta database. */ + ContentMetaDatabase meta_db; + if (R_FAILED(OpenContentMetaDatabase(std::addressof(meta_db), storage_list[i]))) { + continue; + } + + /* Get the latest key. */ + ContentMetaKey latest_key; + R_TRY_CATCH(meta_db.GetLatest(std::addressof(latest_key), key.id)) { + R_CATCH(ncm::ResultContentMetaNotFound) { + /* Key doesn't exist, this is okay. */ + continue; + } + } R_END_TRY_CATCH; + + /* Check if installed key is newer. */ + if (latest_key.version >= key.version) { + *out = false; + R_SUCCEED(); + } + } + + /* Input key is newer. */ + *out = true; + R_SUCCEED(); + } + + Result InstallTaskBase::CountInstallContentMetaData(s32 *out_count) { + R_RETURN(m_data->Count(out_count)); + } + + Result InstallTaskBase::GetInstallContentMetaData(InstallContentMeta *out_content_meta, s32 index) { + R_RETURN(m_data->Get(out_content_meta, index)); + } + + Result InstallTaskBase::DeleteInstallContentMetaData(const ContentMetaKey *keys, s32 num_keys) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(this->CountInstallContentMetaData(std::addressof(count))); + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(this->GetInstallContentMetaData(std::addressof(content_meta), i)); + + /* Cleanup if the input keys contain this key. */ + if (Contains(keys, num_keys, content_meta.GetReader().GetKey())) { + R_TRY(this->CleanupOne(content_meta)); + } + } + + /* Delete the data if count < 1. */ + R_RETURN(m_data->Delete(keys, num_keys)); + } + + Result InstallTaskBase::GetInstallContentMetaDataFromPath(AutoBuffer *out, const Path &path, const InstallContentInfo &content_info, util::optional<u32> source_version) { + fs::ContentAttributes attr; + AutoBuffer meta; + R_TRY(TryReadContentMetaPath(std::addressof(attr), std::addressof(meta), path.str, ReadContentMetaPathWithoutExtendedDataOrDigestSuppressingFsAbort)); + + /* Create a reader. */ + PackagedContentMetaReader reader(meta.Get(), meta.GetSize()); + size_t meta_size; + + AutoBuffer install_meta_data; + if (source_version) { + /* Declare buffers. */ + constexpr size_t BufferCount = 2; + constexpr size_t BufferSize = 3_KB; + u8 buffers[BufferCount][BufferSize]; + + /* Create a mapper. */ + auto mapper = MultiCacheReadonlyMapper<4>(Span<u8>(buffers[0], sizeof(buffers[0])), Span<u8>(buffers[1], sizeof(buffers[1]))); + R_TRY(mapper.Initialize(path.str, static_cast<fs::ContentAttributes>(static_cast<u8>(attr) & fs::ContentAttributes_All), true)); + + /* Create an accessor. */ + auto accessor = PatchMetaExtendedDataAccessor{std::addressof(mapper)}; + + /* Convert to fragment only install meta. */ + R_TRY(MetaConverter::GetFragmentOnlyInstallContentMeta(std::addressof(install_meta_data), content_info, reader, std::addressof(accessor), source_version.value())); + } else { + /* Convert to install content meta. */ + meta_size = reader.CalculateConvertInstallContentMetaSize(); + R_TRY(install_meta_data.Initialize(meta_size)); + reader.ConvertToInstallContentMeta(install_meta_data.Get(), install_meta_data.GetSize(), content_info); + } + + /* Set output. */ + *out = std::move(install_meta_data); + + R_SUCCEED(); + } + + InstallContentInfo InstallTaskBase::MakeInstallContentInfoFrom(const InstallContentMetaInfo &info, const PlaceHolderId &placeholder_id, util::optional<bool> is_tmp) { + return { + .digest = info.digest, + .info = ContentInfo::Make(info.content_id, info.content_size, ContentInfo::DefaultContentAttributes, ContentType::Meta, 0), + .placeholder_id = placeholder_id, + .meta_type = info.key.type, + .install_state = InstallState::Prepared, + .verify_digest = info.verify_digest, + .storage_id = StorageId::BuiltInSystem, + .is_temporary = is_tmp ? *is_tmp : (m_install_storage != StorageId::BuiltInSystem), + }; + } + + InstallProgress InstallTaskBase::GetProgress() { + std::scoped_lock lk(m_progress_mutex); + return m_progress; + } + + void InstallTaskBase::ResetLastResult() { + this->SetLastResult(ResultSuccess()); + } + + void InstallTaskBase::SetTotalSize(s64 size) { + std::scoped_lock lk(m_progress_mutex); + m_progress.total_size = size; + } + + void InstallTaskBase::IncrementProgress(s64 size) { + std::scoped_lock lk(m_progress_mutex); + m_progress.installed_size += size; + } + + void InstallTaskBase::SetLastResult(Result last_result) { + std::scoped_lock lk(m_progress_mutex); + m_data->SetLastResult(last_result); + m_progress.SetLastResult(last_result); + } + + void InstallTaskBase::CleanupProgress() { + std::scoped_lock lk(m_progress_mutex); + m_progress = {}; + } + + InstallThroughput InstallTaskBase::GetThroughput() { + std::scoped_lock lk(m_throughput_mutex); + return m_throughput; + } + + void InstallTaskBase::ResetThroughputMeasurement() { + std::scoped_lock lk(m_throughput_mutex); + m_throughput = { .elapsed_time = TimeSpan() }; + m_throughput_start_time = TimeSpan(); + } + + void InstallTaskBase::StartThroughputMeasurement() { + std::scoped_lock lk(m_throughput_mutex); + m_throughput = { .elapsed_time = TimeSpan() }; + m_throughput_start_time = os::GetSystemTick().ToTimeSpan(); + } + + void InstallTaskBase::UpdateThroughputMeasurement(s64 throughput) { + std::scoped_lock lk(m_throughput_mutex); + + /* Update throughput only if start time has been set. */ + if (m_throughput_start_time.GetNanoSeconds() != 0) { + m_throughput.installed += throughput; + m_throughput.elapsed_time = os::GetSystemTick().ToTimeSpan() - m_throughput_start_time; + } + } + + Result InstallTaskBase::CalculateContentsSize(s64 *out_size, const ContentMetaKey &key, StorageId storage_id) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(m_data->Count(std::addressof(count))); + + /* Open the content storage. */ + ContentStorage content_storage; + R_TRY(ncm::OpenContentStorage(std::addressof(content_storage), storage_id)); + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(m_data->Get(std::addressof(content_meta), i)); + + /* Create a reader. */ + const InstallContentMetaReader reader = content_meta.GetReader(); + + /* Skip this content meta if the key doesn't match. */ + if (reader.GetKey() != key) { + continue; + } + + /* If the storage is unique and doesn't match, continue. */ + if (IsUniqueStorage(storage_id) && reader.GetStorageId() != storage_id) { + continue; + } + + /* Set the out size to 0. */ + *out_size = 0; + + /* Sum the sizes from the content infos. */ + for (size_t j = 0; j < reader.GetContentCount(); j++) { + const auto *content_info = reader.GetContentInfo(j); + + /* If this content info isn't prepared, continue. */ + if (content_info->install_state == InstallState::NotPrepared) { + continue; + } + + /* Check if this content info has a placeholder. */ + bool has_placeholder; + R_TRY(content_storage.HasPlaceHolder(std::addressof(has_placeholder), content_info->GetPlaceHolderId())); + + /* Add the placeholder size to the total. */ + if (has_placeholder) { + *out_size += content_info->GetSize(); + } + } + + /* No need to look for any further keys. */ + R_SUCCEED(); + } + + *out_size = 0; + R_SUCCEED(); + } + + Result InstallTaskBase::ReadContentMetaInfoList(s32 *out_count, std::unique_ptr<ContentMetaInfo[]> *out_meta_infos, const ContentMetaKey &key, fs::ContentAttributes attr) { + /* Get the install content meta info. */ + InstallContentMetaInfo install_content_meta_info; + R_TRY(this->GetInstallContentMetaInfo(std::addressof(install_content_meta_info), key)); + + /* Open the BuiltInSystem content storage. */ + ContentStorage content_storage; + R_TRY(ncm::OpenContentStorage(std::addressof(content_storage), StorageId::BuiltInSystem)); + + /* Write content meta to a placeholder. */ + InstallContentInfo content_info; + R_TRY(this->WriteContentMetaToPlaceHolder(std::addressof(content_info), std::addressof(content_storage), install_content_meta_info, true)); + + const PlaceHolderId placeholder_id = content_info.GetPlaceHolderId(); + + /* Get the path of the new placeholder. */ + Path path; + content_storage.GetPlaceHolderPath(std::addressof(path), placeholder_id); + + /* Read the variation list. */ + R_TRY(ReadVariationContentMetaInfoList(out_count, out_meta_infos, path, attr, m_firmware_variation_id)); + + /* Delete the placeholder. */ + content_storage.DeletePlaceHolder(placeholder_id); + R_SUCCEED(); + } + + Result InstallTaskBase::FindMaxRequiredApplicationVersion(u32 *out) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(m_data->Count(std::addressof(count))); + + u32 max_version = 0; + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(m_data->Get(std::addressof(content_meta), i)); + + /* Create a reader. */ + const InstallContentMetaReader reader = content_meta.GetReader(); + + /* Check if the meta type is for add on content. */ + if (reader.GetKey().type == ContentMetaType::AddOnContent) { + const auto *extended_header = reader.GetExtendedHeader<AddOnContentMetaExtendedHeader>(); + + /* Set the max version if higher. */ + max_version = std::max(max_version, extended_header->required_application_version); + } + } + + *out = max_version; + R_SUCCEED(); + } + + Result InstallTaskBase::ListOccupiedSize(s32 *out_written, InstallTaskOccupiedSize *out_list, s32 out_list_size, s32 offset) { + AMS_ABORT_UNLESS(offset >= 0); + + /* Count the number of content meta entries. */ + s32 data_count; + R_TRY(m_data->Count(std::addressof(data_count))); + + /* Iterate over content meta. */ + s32 count = 0; + for (s32 i = offset; i < data_count && count < out_list_size; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(m_data->Get(std::addressof(content_meta), i)); + + /* Create a reader. */ + const InstallContentMetaReader reader = content_meta.GetReader(); + const StorageId storage_id = reader.GetStorageId(); + + s64 total_size = 0; + + for (size_t j = 0; j < reader.GetContentCount(); j++) { + const auto *content_info = reader.GetContentInfo(j); + + /* Skip the content info if not prepared. */ + if (content_info->GetInstallState() == InstallState::NotPrepared) { + continue; + } + + /* Open the relevant content storage. */ + ContentStorage content_storage; + R_TRY_CATCH(ncm::OpenContentStorage(std::addressof(content_storage), storage_id)) { + R_CATCH(ncm::ResultContentStorageNotActive) { break; } + } R_END_TRY_CATCH; + + /* Check if this content info has a placeholder. */ + bool has_placeholder; + R_TRY(content_storage.HasPlaceHolder(std::addressof(has_placeholder), content_info->GetPlaceHolderId())); + + if (has_placeholder) { + total_size += content_info->GetSize(); + } + } + + /* Output this InstallTaskOccupiedSize. */ + out_list[count++] = { + .key = reader.GetKey(), + .size = total_size, + .storage_id = storage_id, + }; + } + + /* Write the out count. */ + *out_written = count; + + R_SUCCEED(); + } + + void InstallTaskBase::SetProgressState(InstallProgressState state) { + std::scoped_lock lk(m_progress_mutex); + m_data->SetState(state); + m_progress.state = state; + } + + Result InstallTaskBase::FindMaxRequiredSystemVersion(u32 *out) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(m_data->Count(std::addressof(count))); + + u32 max_version = 0; + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(m_data->Get(std::addressof(content_meta), i)); + + /* Create a reader. */ + const InstallContentMetaReader reader = content_meta.GetReader(); + + if (reader.GetHeader()->type == ContentMetaType::Application) { + const auto *extended_header = reader.GetExtendedHeader<ApplicationMetaExtendedHeader>(); + + /* Set the max version if higher. */ + if (extended_header->required_system_version >= max_version) { + max_version = extended_header->required_system_version; + } + } else if (reader.GetHeader()->type == ContentMetaType::Patch) { + const auto *extended_header = reader.GetExtendedHeader<PatchMetaExtendedHeader>(); + + /* Set the max version if higher. */ + if (extended_header->required_system_version >= max_version) { + max_version = extended_header->required_system_version; + } + } + } + + *out = max_version; + R_SUCCEED(); + } + + Result InstallTaskBase::CanContinue() { + switch (this->GetProgress().state) { + case InstallProgressState::NotPrepared: + case InstallProgressState::DataPrepared: + R_UNLESS(!this->IsCancelRequested(), ncm::ResultCreatePlaceHolderCancelled()); + break; + case InstallProgressState::Prepared: + R_UNLESS(!this->IsCancelRequested(), ncm::ResultWritePlaceHolderCancelled()); + break; + default: + break; + } + + R_SUCCEED(); + } + + Result InstallTaskBase::ListRightsIds(s32 *out_count, Span<RightsId> out_span, const ContentMetaKey &key, s32 offset) { + /* Count the number of content meta entries. */ + s32 count; + R_TRY(m_data->Count(std::addressof(count))); + + /* Ensure count is >= 1. */ + R_UNLESS(count >= 1, ncm::ResultContentMetaNotFound()); + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(m_data->Get(std::addressof(content_meta), i)); + + /* Create a reader. */ + const InstallContentMetaReader reader = content_meta.GetReader(); + + /* List rights ids if the reader's key matches ours. */ + if (reader.GetKey() == key) { + R_RETURN(this->ListRightsIdsByInstallContentMeta(out_count, out_span, content_meta, offset)); + } + } + + R_THROW(ncm::ResultContentMetaNotFound()); + } + + Result InstallTaskBase::ListRightsIdsByInstallContentMeta(s32 *out_count, Span<RightsId> out_span, const InstallContentMeta &content_meta, s32 offset) { + /* If the offset is greater than zero, we can't create a unique span of rights ids. */ + /* Thus, we have nothing to list. */ + if (offset > 0) { + *out_count = 0; + R_SUCCEED(); + } + + /* Create a reader. */ + const InstallContentMetaReader reader = content_meta.GetReader(); + + s32 count = 0; + for (size_t i = 0; i < reader.GetContentCount(); i++) { + const auto *content_info = reader.GetContentInfo(i); + + /* Skip meta content infos and already installed content infos. Also skip if the content meta has already been comitted. */ + if (content_info->GetType() == ContentType::Meta || content_info->GetInstallState() == InstallState::Installed || reader.GetHeader()->committed) { + continue; + } + + /* Open the relevant content storage. */ + ContentStorage content_storage; + R_TRY(ncm::OpenContentStorage(std::addressof(content_storage), content_info->storage_id)); + + /* Get the rights id. */ + RightsId rights_id; + R_TRY(content_storage.GetRightsId(std::addressof(rights_id), content_info->GetPlaceHolderId(), content_info->GetContentAttributes())); + + /* Skip empty rights ids. */ + if (rights_id.id == fs::InvalidRightsId) { + continue; + } + + /* Output the rights id. */ + out_span[count++] = rights_id; + } + + /* Sort and remove duplicate ids from the output span. */ + std::sort(out_span.begin(), out_span.end()); + *out_count = std::distance(out_span.begin(), std::unique(out_span.begin(), out_span.end())); + R_SUCCEED(); + } +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_install_task_data.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_install_task_data.cpp new file mode 100644 index 00000000..36287616 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_install_task_data.cpp @@ -0,0 +1,397 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ncm { + + namespace { + + using BoundedPath = kvdb::BoundedString<64>; + + constexpr inline bool Includes(const ContentMetaKey *keys, s32 count, const ContentMetaKey &key) { + for (s32 i = 0; i < count; i++) { + if (keys[i] == key) { + return true; + } + } + return false; + } + + } + + Result InstallTaskDataBase::Get(InstallContentMeta *out, s32 index) { + /* Determine the data size. */ + size_t data_size; + R_TRY(this->GetSize(std::addressof(data_size), index)); + + /* Create a buffer and read data into it. */ + std::unique_ptr<char[]> buffer(new (std::nothrow) char[data_size]); + R_UNLESS(buffer != nullptr, ncm::ResultAllocationFailed()); + R_TRY(this->Get(index, buffer.get(), data_size)); + + /* Output the buffer and size. */ + out->data = std::move(buffer); + out->size = data_size; + R_SUCCEED(); + } + + Result InstallTaskDataBase::Update(const InstallContentMeta &content_meta, s32 index) { + R_RETURN(this->Update(index, content_meta.data.get(), content_meta.size)); + } + + Result InstallTaskDataBase::Has(bool *out, u64 id) { + s32 count; + R_TRY(this->Count(std::addressof(count))); + + /* Iterate over each entry. */ + for (s32 i = 0; i < count; i++) { + InstallContentMeta content_meta; + R_TRY(this->Get(std::addressof(content_meta), i)); + + /* If the id matches we are successful. */ + if (content_meta.GetReader().GetKey().id == id) { + *out = true; + R_SUCCEED(); + } + } + + /* We didn't find the value. */ + *out = false; + R_SUCCEED(); + } + + Result MemoryInstallTaskData::GetProgress(InstallProgress *out_progress) { + /* Initialize install progress. */ + InstallProgress install_progress = { + .state = m_state, + }; + install_progress.SetLastResult(m_last_result); + + /* Only states after prepared are allowed. */ + if (m_state != InstallProgressState::NotPrepared && m_state != InstallProgressState::DataPrepared) { + for (auto &data_holder : m_data_list) { + const InstallContentMetaReader reader = data_holder.GetReader(); + + /* Sum the sizes from this entry's content infos. */ + for (size_t i = 0; i < reader.GetContentCount(); i++) { + const InstallContentInfo *content_info = reader.GetContentInfo(i); + install_progress.installed_size += content_info->GetSize(); + install_progress.total_size += content_info->GetSizeWritten(); + } + } + } + + *out_progress = install_progress; + R_SUCCEED(); + } + + Result MemoryInstallTaskData::GetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo *out_info) { + *out_info = m_system_update_task_apply_info; + R_SUCCEED(); + } + + Result MemoryInstallTaskData::SetState(InstallProgressState state) { + m_state = state; + R_SUCCEED(); + } + + Result MemoryInstallTaskData::SetLastResult(Result result) { + m_last_result = result; + R_SUCCEED(); + } + + Result MemoryInstallTaskData::SetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo info) { + m_system_update_task_apply_info = info; + R_SUCCEED(); + } + + Result MemoryInstallTaskData::Push(const void *data, size_t size) { + /* Allocate a new data holder. */ + auto holder = std::unique_ptr<DataHolder>(new (std::nothrow) DataHolder()); + R_UNLESS(holder != nullptr, ncm::ResultAllocationFailed()); + + /* Allocate memory for the content meta data. */ + holder->data = std::unique_ptr<char[]>(new (std::nothrow) char[size]); + R_UNLESS(holder->data != nullptr, ncm::ResultAllocationFailed()); + holder->size = size; + + /* Copy data to the data holder. */ + std::memcpy(holder->data.get(), data, size); + + /* Put the data holder into the data list. */ + m_data_list.push_back(*holder); + + /* Relinquish control over the memory allocated to the data holder. */ + holder.release(); + + R_SUCCEED(); + } + + Result MemoryInstallTaskData::Count(s32 *out) { + *out = m_data_list.size(); + R_SUCCEED(); + } + + Result MemoryInstallTaskData::GetSize(size_t *out_size, s32 index) { + /* Find the correct entry in the list. */ + s32 count = 0; + for (auto &data_holder : m_data_list) { + if (index == count++) { + *out_size = data_holder.size; + R_SUCCEED(); + } + } + /* Out of bounds indexing is an unrecoverable error. */ + AMS_ABORT(); + } + + Result MemoryInstallTaskData::Get(s32 index, void *out, size_t out_size) { + /* Find the correct entry in the list. */ + s32 count = 0; + for (auto &data_holder : m_data_list) { + if (index == count++) { + R_UNLESS(out_size >= data_holder.size, ncm::ResultBufferInsufficient()); + std::memcpy(out, data_holder.data.get(), data_holder.size); + R_SUCCEED(); + } + } + /* Out of bounds indexing is an unrecoverable error. */ + AMS_ABORT(); + } + + Result MemoryInstallTaskData::Update(s32 index, const void *data, size_t data_size) { + /* Find the correct entry in the list. */ + s32 count = 0; + for (auto &data_holder : m_data_list) { + if (index == count++) { + R_UNLESS(data_size == data_holder.size, ncm::ResultBufferInsufficient()); + std::memcpy(data_holder.data.get(), data, data_size); + R_SUCCEED(); + } + } + /* Out of bounds indexing is an unrecoverable error. */ + AMS_ABORT(); + } + Result MemoryInstallTaskData::Delete(const ContentMetaKey *keys, s32 num_keys) { + /* Iterate over keys. */ + for (s32 i = 0; i < num_keys; i++) { + const auto &key = keys[i]; + + /* Find and remove matching data from the list. */ + for (auto &data_holder : m_data_list) { + if (key == data_holder.GetReader().GetKey()) { + m_data_list.erase(m_data_list.iterator_to(data_holder)); + delete std::addressof(data_holder); + break; + } + } + } + + R_SUCCEED(); + } + + Result MemoryInstallTaskData::Cleanup() { + while (!m_data_list.empty()) { + auto *data_holder = std::addressof(m_data_list.front()); + m_data_list.pop_front(); + delete data_holder; + } + R_SUCCEED(); + } + + Result FileInstallTaskData::Create(const char *path, s32 max_entries) { + /* Create the file. */ + R_TRY(fs::CreateFile(path, 0)); + + /* Open the file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Write | fs::OpenMode_AllowAppend)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Create an initial header and write it to the file. */ + const Header header = MakeInitialHeader(max_entries); + R_RETURN(fs::WriteFile(file, 0, std::addressof(header), sizeof(Header), fs::WriteOption::Flush)); + } + + Result FileInstallTaskData::Initialize(const char *path) { + std::strncpy(m_path, path, sizeof(m_path)); + m_path[sizeof(m_path) - 1] = '\x00'; + R_RETURN(this->Read(std::addressof(m_header), sizeof(Header), 0)); + } + + Result FileInstallTaskData::GetProgress(InstallProgress *out_progress) { + /* Initialize install progress. */ + InstallProgress install_progress = { + .state = m_header.progress_state, + }; + install_progress.SetLastResult(m_header.last_result); + + /* Only states after prepared are allowed. */ + if (m_header.progress_state != InstallProgressState::NotPrepared && m_header.progress_state != InstallProgressState::DataPrepared) { + for (size_t i = 0; i < m_header.count; i++) { + /* Obtain the content meta for this entry. */ + InstallContentMeta content_meta; + R_TRY(InstallTaskDataBase::Get(std::addressof(content_meta), i)); + const InstallContentMetaReader reader = content_meta.GetReader(); + + /* Sum the sizes from this entry's content infos. */ + for (size_t j = 0; j < reader.GetContentCount(); j++) { + const InstallContentInfo *content_info = reader.GetContentInfo(j); + install_progress.installed_size += content_info->GetSize(); + install_progress.total_size += content_info->GetSizeWritten(); + } + } + } + + *out_progress = install_progress; + R_SUCCEED(); + } + + Result FileInstallTaskData::GetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo *out_info) { + *out_info = m_header.system_update_task_apply_info; + R_SUCCEED(); + } + + Result FileInstallTaskData::SetState(InstallProgressState state) { + m_header.progress_state = state; + R_RETURN(this->WriteHeader()); + } + + Result FileInstallTaskData::SetLastResult(Result result) { + m_header.last_result = result; + R_RETURN(this->WriteHeader()); + } + + Result FileInstallTaskData::SetSystemUpdateTaskApplyInfo(SystemUpdateTaskApplyInfo info) { + m_header.system_update_task_apply_info = info; + R_RETURN(this->WriteHeader()); + } + + Result FileInstallTaskData::Push(const void *data, size_t data_size) { + R_UNLESS(m_header.count < m_header.max_entries, ncm::ResultBufferInsufficient()); + + /* Create a new entry info. Data of the given size will be stored at the end of the file. */ + const EntryInfo entry_info = { m_header.last_data_offset, static_cast<s64>(data_size) }; + + /* Write the new entry info. */ + R_TRY(this->Write(std::addressof(entry_info), sizeof(EntryInfo), GetEntryInfoOffset(m_header.count))); + + /* Write the data to the offset in the entry info. */ + R_TRY(this->Write(data, data_size, entry_info.offset)); + + /* Update the header for the new entry. */ + m_header.last_data_offset += data_size; + m_header.count++; + + /* Write the updated header. */ + R_RETURN(this->WriteHeader()); + } + + Result FileInstallTaskData::Count(s32 *out) { + *out = m_header.count; + R_SUCCEED(); + } + + Result FileInstallTaskData::GetSize(size_t *out_size, s32 index) { + EntryInfo entry_info; + R_TRY(this->GetEntryInfo(std::addressof(entry_info), index)); + *out_size = entry_info.size; + R_SUCCEED(); + } + + Result FileInstallTaskData::Get(s32 index, void *out, size_t out_size) { + /* Obtain the entry info. */ + EntryInfo entry_info; + R_TRY(this->GetEntryInfo(std::addressof(entry_info), index)); + + /* Read the entry to the output buffer. */ + R_UNLESS(entry_info.size <= static_cast<s64>(out_size), ncm::ResultBufferInsufficient()); + R_RETURN(this->Read(out, out_size, entry_info.offset)); + } + + Result FileInstallTaskData::Update(s32 index, const void *data, size_t data_size) { + /* Obtain the entry info. */ + EntryInfo entry_info; + R_TRY(this->GetEntryInfo(std::addressof(entry_info), index)); + + /* Data size must match existing data size. */ + R_UNLESS(entry_info.size == static_cast<s64>(data_size), ncm::ResultBufferInsufficient()); + R_RETURN(this->Write(data, data_size, entry_info.offset)); + } + + Result FileInstallTaskData::Delete(const ContentMetaKey *keys, s32 num_keys) { + /* Create the path for the temporary data. */ + BoundedPath tmp_path(m_path); + tmp_path.Append(".tmp"); + + /* Create a new temporary install task data. */ + FileInstallTaskData install_task_data; + R_TRY(FileInstallTaskData::Create(tmp_path, m_header.max_entries)); + R_TRY(install_task_data.Initialize(tmp_path)); + + /* Get the number of entries. */ + s32 count; + R_TRY(this->Count(std::addressof(count))); + + /* Copy entries that are not excluded to the new install task data. */ + for (s32 i = 0; i < count; i++) { + InstallContentMeta content_meta; + R_TRY(InstallTaskDataBase::Get(std::addressof(content_meta), i)); + + /* Check if entry is excluded. If not, push it to our new install task data. */ + if (Includes(keys, num_keys, content_meta.GetReader().GetKey())) { + continue; + } + + /* NOTE: Nintendo doesn't check that this operation succeeds. */ + install_task_data.Push(content_meta.data.get(), content_meta.size); + } + + /* Change from our current data to the new data. */ + m_header = install_task_data.m_header; + R_TRY(fs::DeleteFile(m_path)); + R_RETURN(fs::RenameFile(tmp_path, m_path)); + } + + Result FileInstallTaskData::Cleanup() { + m_header = MakeInitialHeader(m_header.max_entries); + R_RETURN(this->WriteHeader()); + } + + Result FileInstallTaskData::GetEntryInfo(EntryInfo *out_entry_info, s32 index) { + AMS_ABORT_UNLESS(static_cast<u32>(index) < m_header.count); + R_RETURN(this->Read(out_entry_info, sizeof(EntryInfo), GetEntryInfoOffset(index))); + } + + Result FileInstallTaskData::Write(const void *data, size_t size, s64 offset) { + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), m_path, fs::OpenMode_Write | fs::OpenMode_AllowAppend)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + R_RETURN(fs::WriteFile(file, offset, data, size, fs::WriteOption::Flush)); + } + + Result FileInstallTaskData::Read(void *out, size_t out_size, s64 offset) { + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), m_path, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + R_RETURN(fs::ReadFile(file, offset, out, out_size)); + } + + Result FileInstallTaskData::WriteHeader() { + R_RETURN(this->Write(std::addressof(m_header), sizeof(Header), 0)); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_integrated_content_meta_database_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_integrated_content_meta_database_impl.cpp new file mode 100644 index 00000000..148471e8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_integrated_content_meta_database_impl.cpp @@ -0,0 +1,490 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ncm { + + Result IntegratedContentMetaDatabaseImpl::Set(const ContentMetaKey &key, const sf::InBuffer &value) { + AMS_UNUSED(key, value); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentMetaDatabaseImpl::Get(sf::Out<u64> out_size, const ContentMetaKey &key, const sf::OutBuffer &out_value) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentMetaNotFound()); + + /* Check each interface in turn. */ + R_RETURN(m_list.TryEach([&](const auto &data) { + /* Try the current interface. */ + R_RETURN(data.interface->Get(out_size, key, out_value)); + })); + } + + Result IntegratedContentMetaDatabaseImpl::Remove(const ContentMetaKey &key) { + AMS_UNUSED(key); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentMetaDatabaseImpl::GetContentIdByType(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentMetaNotFound()); + + /* Check each interface in turn. */ + R_RETURN(m_list.TryEach([&](const auto &data) { + /* Try the current interface. */ + R_RETURN(data.interface->GetContentIdByType(out_content_id, key, type)); + })); + } + + Result IntegratedContentMetaDatabaseImpl::ListContentInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentInfo> &out_info, const ContentMetaKey &key, s32 offset) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentMetaNotFound()); + + /* Check each interface in turn. */ + R_RETURN(m_list.TryEach([&](const auto &data) { + /* Check if the current interface has it. */ + bool has; + R_TRY(data.interface->Has(std::addressof(has), key)); + + /* If it doesn't, continue on. */ + R_UNLESS(has, ncm::ResultContentMetaNotFound()); + + + /* If it does, list the content infos. */ + R_RETURN(data.interface->ListContentInfo(out_entries_written, out_info, key, offset)); + })); + } + + Result IntegratedContentMetaDatabaseImpl::List(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType meta_type, ApplicationId application_id, u64 min, u64 max, ContentInstallType install_type) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* List on all databases. */ + s32 entries_total = 0; + s32 entries_written = 0; + R_TRY(m_list.ForAll([&](const auto &data) { + /* List on the current database. */ + s32 cur_total; + s32 cur_written; + R_TRY(data.interface->List(std::addressof(cur_total), std::addressof(cur_written), sf::OutArray<ContentMetaKey>{out_info.GetPointer() + entries_written, out_info.GetSize() - entries_written}, meta_type, application_id, min, max, install_type)); + + /* Add to the totals. */ + entries_total += cur_total; + entries_written += cur_written; + R_SUCCEED(); + })); + + /* Set output. */ + *out_entries_total = entries_total; + *out_entries_written = entries_written; + R_SUCCEED(); + } + Result IntegratedContentMetaDatabaseImpl::GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, u64 id) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentMetaNotFound()); + + /* Check each interface in turn. */ + R_RETURN(m_list.TryEach([&](const auto &data) { + /* Try the current interface. */ + R_RETURN(data.interface->GetLatestContentMetaKey(out_key, id)); + })); + } + + Result IntegratedContentMetaDatabaseImpl::ListApplication(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ApplicationContentMetaKey> &out_keys, ContentMetaType meta_type) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* List on all databases. */ + s32 entries_total = 0; + s32 entries_written = 0; + R_TRY(m_list.ForAll([&](const auto &data) { + /* List on the current database. */ + s32 cur_total; + s32 cur_written; + R_TRY(data.interface->ListApplication(std::addressof(cur_total), std::addressof(cur_written), sf::OutArray<ApplicationContentMetaKey>{out_keys.GetPointer() + entries_written, out_keys.GetSize() - entries_written}, meta_type)); + + /* Add to the totals. */ + entries_total += cur_total; + entries_written += cur_written; + R_SUCCEED(); + })); + + /* Set output. */ + *out_entries_total = entries_total; + *out_entries_written = entries_written; + R_SUCCEED(); + } + + Result IntegratedContentMetaDatabaseImpl::Has(sf::Out<bool> out, const ContentMetaKey &key) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* If we don't locate the content meta, set the output to false. */ + *out = false; + ON_RESULT_INCLUDED(ncm::ResultContentMetaNotFound) { *out = false; }; + + /* Check each interface in turn. */ + R_RETURN(m_list.TryEach([&](const auto &data) { + /* Check if the current interface has it. */ + R_TRY(data.interface->Has(out, key)); + + /* If it doesn't, continue on. */ + R_UNLESS(*out, ncm::ResultContentMetaNotFound()); + + /* If it does, we're done looking. */ + R_SUCCEED(); + })); + } + + Result IntegratedContentMetaDatabaseImpl::HasAll(sf::Out<bool> out, const sf::InArray<ContentMetaKey> &keys) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + *out = false; + + /* Check if keys are present. */ + for (size_t i = 0; i < keys.GetSize(); i++) { + /* Check if we have the current key. */ + bool has; + R_TRY(this->Has(std::addressof(has), keys[i])); + + /* If we don't, then we can early return because we don't have all. */ + R_SUCCEED_IF(!has); + } + + *out = true; + R_SUCCEED(); + } + + Result IntegratedContentMetaDatabaseImpl::GetSize(sf::Out<u64> out_size, const ContentMetaKey &key) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentMetaNotFound()); + + /* Check each interface in turn. */ + R_RETURN(m_list.TryEach([&](const auto &data) { + /* Try the current interface. */ + R_RETURN(data.interface->GetSize(out_size, key)); + })); + } + + Result IntegratedContentMetaDatabaseImpl::GetRequiredSystemVersion(sf::Out<u32> out_version, const ContentMetaKey &key) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentMetaNotFound()); + + /* Check each interface in turn. */ + R_RETURN(m_list.TryEach([&](const auto &data) { + /* Try the current interface. */ + R_RETURN(data.interface->GetRequiredSystemVersion(out_version, key)); + })); + } + + Result IntegratedContentMetaDatabaseImpl::GetPatchContentMetaId(sf::Out<u64> out_patch_id, const ContentMetaKey &key) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentMetaNotFound()); + + /* Check each interface in turn. */ + R_RETURN(m_list.TryEach([&](const auto &data) { + /* Try the current interface. */ + R_RETURN(data.interface->GetPatchContentMetaId(out_patch_id, key)); + })); + } + + Result IntegratedContentMetaDatabaseImpl::DisableForcibly() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + m_disabled = true; + R_SUCCEED(); + } + + Result IntegratedContentMetaDatabaseImpl::LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) { + AMS_UNUSED(out_orphaned, content_ids); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentMetaDatabaseImpl::Commit() { + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentMetaDatabaseImpl::HasContent(sf::Out<bool> out, const ContentMetaKey &key, const ContentId &content_id) { + /* Check each interface in turn. */ + R_RETURN(m_list.TryEach([&](const auto &data) { + /* Check if the current interface has it. */ + /* NOTE: Nintendo bug: Nintendo calls this->Has(), which is likely a copy paste error from ::HasAll... */ + bool has; + R_TRY(data.interface->Has(std::addressof(has), key)); + + /* If it doesn't, continue on. */ + R_UNLESS(has, ncm::ResultContentMetaNotFound()); + + /* If it does, list the content infos. */ + R_RETURN(data.interface->HasContent(out, key, content_id)); + })); + } + + Result IntegratedContentMetaDatabaseImpl::ListContentMetaInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaInfo> &out_meta_info, const ContentMetaKey &key, s32 offset) { + /* Check each interface in turn. */ + R_RETURN(m_list.TryEach([&](const auto &data) { + /* Check if the current interface has it. */ + bool has; + R_TRY(data.interface->Has(std::addressof(has), key)); + + /* If it doesn't, continue on. */ + R_UNLESS(has, ncm::ResultContentMetaNotFound()); + + /* If it does, list the content infos. */ + R_RETURN(data.interface->ListContentMetaInfo(out_entries_written, out_meta_info, key, offset)); + })); + } + + Result IntegratedContentMetaDatabaseImpl::GetAttributes(sf::Out<u8> out_attributes, const ContentMetaKey &key) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentMetaNotFound()); + + /* Check each interface in turn. */ + R_RETURN(m_list.TryEach([&](const auto &data) { + /* Try the current interface. */ + R_RETURN(data.interface->GetAttributes(out_attributes, key)); + })); + } + + Result IntegratedContentMetaDatabaseImpl::GetRequiredApplicationVersion(sf::Out<u32> out_version, const ContentMetaKey &key) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentMetaNotFound()); + + /* Check each interface in turn. */ + R_RETURN(m_list.TryEach([&](const auto &data) { + /* Try the current interface. */ + R_RETURN(data.interface->GetRequiredApplicationVersion(out_version, key)); + })); + } + + Result IntegratedContentMetaDatabaseImpl::GetContentIdByTypeAndIdOffset(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type, u8 id_offset) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentMetaNotFound()); + + /* Check each interface in turn. */ + R_RETURN(m_list.TryEach([&](const auto &data) { + /* Try the current interface. */ + R_RETURN(data.interface->GetContentIdByTypeAndIdOffset(out_content_id, key, type, id_offset)); + })); + } + + Result IntegratedContentMetaDatabaseImpl::GetCount(sf::Out<u32> out_count) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* List on all databases. */ + u32 total = 0; + R_TRY(m_list.ForAll([&](const auto &data) { + /* List on the current database. */ + u32 cur; + R_TRY(data.interface->GetCount(std::addressof(cur))); + + /* Add to the totals. */ + total += cur; + R_SUCCEED(); + })); + + /* Set output. */ + *out_count = total; + R_SUCCEED(); + } + + Result IntegratedContentMetaDatabaseImpl::GetOwnerApplicationId(sf::Out<ApplicationId> out_id, const ContentMetaKey &key) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentMetaNotFound()); + + /* Check each interface in turn. */ + R_RETURN(m_list.TryEach([&](const auto &data) { + /* Try the current interface. */ + R_RETURN(data.interface->GetOwnerApplicationId(out_id, key)); + })); + } + + Result IntegratedContentMetaDatabaseImpl::GetContentAccessibilities(sf::Out<u8> out_accessibilities, const ContentMetaKey &key) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentMetaNotFound()); + + /* Check each interface in turn. */ + R_RETURN(m_list.TryEach([&](const auto &data) { + /* Try the current interface. */ + R_RETURN(data.interface->GetContentAccessibilities(out_accessibilities, key)); + })); + } + + Result IntegratedContentMetaDatabaseImpl::GetContentInfoByType(sf::Out<ContentInfo> out_content_info, const ContentMetaKey &key, ContentType type) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentMetaNotFound()); + + /* Check each interface in turn. */ + R_RETURN(m_list.TryEach([&](const auto &data) { + /* Try the current interface. */ + R_RETURN(data.interface->GetContentInfoByType(out_content_info, key, type)); + })); + } + + Result IntegratedContentMetaDatabaseImpl::GetContentInfoByTypeAndIdOffset(sf::Out<ContentInfo> out_content_info, const ContentMetaKey &key, ContentType type, u8 id_offset) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentMetaNotFound()); + + /* Check each interface in turn. */ + R_RETURN(m_list.TryEach([&](const auto &data) { + /* Try the current interface. */ + R_RETURN(data.interface->GetContentInfoByTypeAndIdOffset(out_content_info, key, type, id_offset)); + })); + } + + Result IntegratedContentMetaDatabaseImpl::GetPlatform(sf::Out<ncm::ContentMetaPlatform> out, const ContentMetaKey &key) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentMetaNotFound()); + + /* Check each interface in turn. */ + R_RETURN(m_list.TryEach([&](const auto &data) { + /* Try the current interface. */ + R_RETURN(data.interface->GetPlatform(out, key)); + })); + } + + Result IntegratedContentMetaDatabaseImpl::HasAttributes(sf::Out<u8> out, u8 attr_mask) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Test whether we have the attributes on all databases. */ + u8 combined_attributes = 0; + R_TRY(m_list.ForAll([&](const auto &data) { + /* Check the current database. */ + u8 cur_attr = 0; + R_TRY(data.interface->HasAttributes(std::addressof(cur_attr), attr_mask)); + + /* Accumulate the attributes found in the current interface. */ + combined_attributes |= cur_attr; + R_SUCCEED(); + })); + + /* Set the output. */ + *out = combined_attributes; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_integrated_content_storage_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_integrated_content_storage_impl.cpp new file mode 100644 index 00000000..1853e6b8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_integrated_content_storage_impl.cpp @@ -0,0 +1,356 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ncm { + + Result IntegratedContentStorageImpl::GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) { + AMS_UNUSED(out); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentStorageImpl::CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size) { + AMS_UNUSED(placeholder_id, content_id, size); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentStorageImpl::DeletePlaceHolder(PlaceHolderId placeholder_id) { + AMS_UNUSED(placeholder_id); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentStorageImpl::HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) { + AMS_UNUSED(placeholder_id); + + /* Integrated storages cannot have placeholders. */ + *out = false; + R_SUCCEED(); + } + + Result IntegratedContentStorageImpl::WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, const sf::InBuffer &data) { + AMS_UNUSED(placeholder_id, offset, data); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentStorageImpl::Register(PlaceHolderId placeholder_id, ContentId content_id) { + AMS_UNUSED(placeholder_id, content_id); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentStorageImpl::Delete(ContentId content_id) { + AMS_UNUSED(content_id); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentStorageImpl::Has(sf::Out<bool> out, ContentId content_id) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* If we don't locate the content, set the output to false. */ + *out = false; + ON_RESULT_INCLUDED(ncm::ResultContentNotFound) { *out = false; }; + + /* Check each interface in turn. */ + R_TRY(m_list.TryEach([&](const auto &data) { + /* Check if the current interface has it. */ + R_TRY(data.interface->Has(out, content_id)); + + /* If it doesn't, continue on. */ + R_UNLESS(*out, ncm::ResultContentNotFound()); + + /* If it does, we're done looking. */ + R_SUCCEED(); + })); + + R_SUCCEED(); + } + + Result IntegratedContentStorageImpl::GetPath(sf::Out<Path> out, ContentId content_id) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentNotFound()); + + /* Check each interface in turn. */ + R_TRY(m_list.TryEach([&](const auto &data) { + /* Check if the current interface has it. */ + bool has; + R_TRY(data.interface->Has(std::addressof(has), content_id)); + + /* If it doesn't, continue on. */ + R_UNLESS(has, ncm::ResultContentNotFound()); + + + /* If it does, get the path. */ + R_RETURN(data.interface->GetPath(out, content_id)); + })); + + R_SUCCEED(); + } + + Result IntegratedContentStorageImpl::GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id) { + AMS_UNUSED(out, placeholder_id); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentStorageImpl::CleanupAllPlaceHolder() { + R_SUCCEED(); + } + + Result IntegratedContentStorageImpl::ListPlaceHolder(sf::Out<s32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) { + AMS_UNUSED(out_buf); + *out_count = 0; + R_SUCCEED(); + } + + Result IntegratedContentStorageImpl::GetContentCount(sf::Out<s32> out_count) { + *out_count = 0; + R_SUCCEED(); + } + + Result IntegratedContentStorageImpl::ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out_buf, s32 offset) { + AMS_UNUSED(out_buf, offset); + *out_count = 0; + R_SUCCEED(); + } + + Result IntegratedContentStorageImpl::GetSizeFromContentId(sf::Out<s64> out_size, ContentId content_id) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentNotFound()); + + /* Check each interface in turn. */ + R_TRY(m_list.TryEach([&](const auto &data) { + /* Check if the current interface has it. */ + bool has; + R_TRY(data.interface->Has(std::addressof(has), content_id)); + + /* If it doesn't, continue on. */ + R_UNLESS(has, ncm::ResultContentNotFound()); + + + /* If it does, get the size. */ + R_RETURN(data.interface->GetSizeFromContentId(out_size, content_id)); + })); + + R_SUCCEED(); + } + + Result IntegratedContentStorageImpl::DisableForcibly() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + m_disabled = true; + R_SUCCEED(); + } + + Result IntegratedContentStorageImpl::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) { + AMS_UNUSED(placeholder_id, old_content_id, new_content_id); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentStorageImpl::SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) { + AMS_UNUSED(placeholder_id, size); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentStorageImpl::ReadContentIdFile(const sf::OutBuffer &buf, ContentId content_id, s64 offset) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentNotFound()); + + /* Check each interface in turn. */ + R_TRY(m_list.TryEach([&](const auto &data) { + /* Check if the current interface has it. */ + bool has; + R_TRY(data.interface->Has(std::addressof(has), content_id)); + + /* If it doesn't, continue on. */ + R_UNLESS(has, ncm::ResultContentNotFound()); + + + /* If it does, read the file. */ + R_RETURN(data.interface->ReadContentIdFile(buf, content_id, offset)); + })); + + R_SUCCEED(); + } + + Result IntegratedContentStorageImpl::GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id) { + /* Obtain the regular rights id for the placeholder id. */ + ncm::RightsId rights_id; + R_TRY(this->GetRightsIdFromPlaceHolderIdDeprecated2(std::addressof(rights_id), placeholder_id)); + + /* Output the fs rights id. */ + out_rights_id.SetValue(rights_id.id); + R_SUCCEED(); + } + + Result IntegratedContentStorageImpl::GetRightsIdFromPlaceHolderIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) { + R_RETURN(this->GetRightsIdFromPlaceHolderId(out_rights_id, placeholder_id, fs::ContentAttributes_None)); + } + + Result IntegratedContentStorageImpl::GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, fs::ContentAttributes attr) { + AMS_UNUSED(out_rights_id, placeholder_id, attr); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentStorageImpl::GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) { + /* Obtain the regular rights id for the content id. */ + ncm::RightsId rights_id; + R_TRY(this->GetRightsIdFromContentIdDeprecated2(std::addressof(rights_id), content_id)); + + /* Output the fs rights id. */ + out_rights_id.SetValue(rights_id.id); + R_SUCCEED(); + } + + Result IntegratedContentStorageImpl::GetRightsIdFromContentIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id) { + R_RETURN(this->GetRightsIdFromContentId(out_rights_id, content_id, fs::ContentAttributes_None)); + } + + Result IntegratedContentStorageImpl::GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id, fs::ContentAttributes attr) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentNotFound()); + + /* Check each interface in turn. */ + R_TRY(m_list.TryEach([&](const auto &data) { + /* Check if the current interface has it. */ + bool has; + R_TRY(data.interface->Has(std::addressof(has), content_id)); + + /* If it doesn't, continue on. */ + R_UNLESS(has, ncm::ResultContentNotFound()); + + + /* If it does, read the file. */ + R_RETURN(data.interface->GetRightsIdFromContentId(out_rights_id, content_id, attr)); + })); + + R_SUCCEED(); + } + + Result IntegratedContentStorageImpl::WriteContentForDebug(ContentId content_id, s64 offset, const sf::InBuffer &data) { + AMS_UNUSED(content_id, offset, data); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentStorageImpl::GetFreeSpaceSize(sf::Out<s64> out_size) { + out_size.SetValue(0); + R_SUCCEED(); + } + + Result IntegratedContentStorageImpl::GetTotalSpaceSize(sf::Out<s64> out_size) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Determine the total size. */ + s64 total_size = 0; + R_TRY(m_list.ForAll([&](const auto &data) { + /* Get the current size. */ + s64 cur_size; + R_TRY(data.interface->GetTotalSpaceSize(std::addressof(cur_size))); + + /* Add to the total. */ + total_size += cur_size; + R_SUCCEED(); + })); + + *out_size = total_size; + R_SUCCEED(); + } + + Result IntegratedContentStorageImpl::FlushPlaceHolder() { + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentStorageImpl::GetSizeFromPlaceHolderId(sf::Out<s64> out, PlaceHolderId placeholder_id) { + AMS_UNUSED(out, placeholder_id); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentStorageImpl::RepairInvalidFileAttribute() { + R_SUCCEED(); + } + + Result IntegratedContentStorageImpl::GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id, fs::ContentAttributes attr) { + AMS_UNUSED(out_rights_id, placeholder_id, cache_content_id, attr); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentStorageImpl::RegisterPath(const ContentId &content_id, const Path &path) { + AMS_UNUSED(content_id, path); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentStorageImpl::ClearRegisteredPath() { + R_THROW(ncm::ResultInvalidOperation()); + } + + Result IntegratedContentStorageImpl::GetProgramId(sf::Out<ncm::ProgramId> out, ContentId content_id, fs::ContentAttributes attr) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're enabled. */ + R_TRY(this->EnsureEnabled()); + + /* Check that our list has interfaces to check. */ + R_UNLESS(m_list.GetCount() > 0, ncm::ResultContentNotFound()); + + /* Check each interface in turn. */ + R_TRY(m_list.TryEach([&](const auto &data) { + /* Check if the current interface has it. */ + bool has; + R_TRY(data.interface->Has(std::addressof(has), content_id)); + + /* If it doesn't, continue on. */ + R_UNLESS(has, ncm::ResultContentNotFound()); + + + /* If it does, read the file. */ + R_RETURN(data.interface->GetProgramId(out, content_id, attr)); + })); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_make_path.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_make_path.cpp new file mode 100644 index 00000000..63ce4403 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_make_path.cpp @@ -0,0 +1,151 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ncm { + + namespace { + + void MakeContentName(PathString *out, ContentId id) { + out->AssignFormat("%s.nca", GetContentIdString(id).data); + } + + void MakePlaceHolderName(PathString *out, PlaceHolderId id) { + auto &bytes = id.uuid.data; + char tmp[3]; + + /* Create a hex string from bytes. */ + for (size_t i = 0; i < sizeof(bytes); i++) { + util::SNPrintf(tmp, util::size(tmp), "%02x", bytes[i]); + out->Append(tmp); + } + + /* Append file extension. */ + out->Append(".nca"); + } + + u16 Get16BitSha256HashPrefix(ContentId id) { + u8 hash[crypto::Sha256Generator::HashSize]; + crypto::GenerateSha256(hash, sizeof(hash), std::addressof(id), sizeof(id)); + return static_cast<u16>(hash[0]) | (static_cast<u16>(hash[1]) << 8); + } + + u8 Get8BitSha256HashPrefix(ContentId id) { + u8 hash[crypto::Sha256Generator::HashSize]; + crypto::GenerateSha256(hash, sizeof(hash), std::addressof(id), sizeof(id)); + return hash[0]; + } + + u8 Get8BitSha256HashPrefix(PlaceHolderId id) { + u8 hash[crypto::Sha256Generator::HashSize]; + crypto::GenerateSha256(hash, sizeof(hash), std::addressof(id), sizeof(id)); + return hash[0]; + } + + } + + void MakeFlatContentFilePath(PathString *out, ContentId content_id, const char *root_path) { + /* Create the content name from the content id. */ + PathString content_name; + MakeContentName(std::addressof(content_name), content_id); + + /* Format the output path. */ + out->AssignFormat("%s/%s", root_path, content_name.Get()); + } + + void MakeSha256HierarchicalContentFilePath_ForFat4KCluster(PathString *out, ContentId content_id, const char *root_path) { + /* Hash the content id. */ + const u16 hash = Get16BitSha256HashPrefix(content_id); + const u32 hash_upper = (hash >> 10) & 0x3f; + const u32 hash_lower = (hash >> 4) & 0x3f; + + /* Create the content name from the content id. */ + PathString content_name; + MakeContentName(std::addressof(content_name), content_id); + + /* Format the output path. */ + out->AssignFormat("%s/%08X/%08X/%s", root_path, hash_upper, hash_lower, content_name.Get()); + } + + void MakeSha256HierarchicalContentFilePath_ForFat32KCluster(PathString *out, ContentId content_id, const char *root_path) { + /* Hash the content id. */ + const u32 hash = (Get16BitSha256HashPrefix(content_id) >> 6) & 0x3FF; + + /* Create the content name from the content id. */ + PathString content_name; + MakeContentName(std::addressof(content_name), content_id); + + /* Format the output path. */ + out->AssignFormat("%s/%08X/%s", root_path, hash, content_name.Get()); + } + + void MakeSha256HierarchicalContentFilePath_ForFat16KCluster(PathString *out, ContentId content_id, const char *root_path) { + /* Hash the content id. */ + const u32 hash_byte = static_cast<u32>(Get8BitSha256HashPrefix(content_id)); + + /* Create the content name from the content id. */ + PathString content_name; + MakeContentName(std::addressof(content_name), content_id); + + /* Format the output path. */ + out->AssignFormat("%s/%08X/%s", root_path, hash_byte, content_name.Get()); + } + + size_t GetHierarchicalContentDirectoryDepth(MakeContentPathFunction func) { + if (func == MakeFlatContentFilePath) { + return 1; + } else if (func == MakeSha256HierarchicalContentFilePath_ForFat4KCluster) { + return 3; + } else if (func == MakeSha256HierarchicalContentFilePath_ForFat16KCluster || + func == MakeSha256HierarchicalContentFilePath_ForFat32KCluster) { + return 2; + } else { + AMS_ABORT(); + } + } + + void MakeFlatPlaceHolderFilePath(PathString *out, PlaceHolderId placeholder_id, const char *root_path) { + /* Create the placeholder name from the placeholder id. */ + PathString placeholder_name; + MakePlaceHolderName(std::addressof(placeholder_name), placeholder_id); + + /* Format the output path. */ + out->AssignFormat("%s/%s", root_path, placeholder_name.Get()); + } + + void MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster(PathString *out, PlaceHolderId placeholder_id, const char *root_path) { + /* Hash the placeholder id. */ + const u32 hash_byte = static_cast<u32>(Get8BitSha256HashPrefix(placeholder_id)); + + /* Create the placeholder name from the placeholder id. */ + PathString placeholder_name; + MakePlaceHolderName(std::addressof(placeholder_name), placeholder_id); + + /* Format the output path. */ + out->AssignFormat("%s/%08X/%s", root_path, hash_byte, placeholder_name.Get()); + } + + size_t GetHierarchicalPlaceHolderDirectoryDepth(MakePlaceHolderPathFunction func) { + if (func == MakeFlatPlaceHolderFilePath) { + return 1; + } else if (func == MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster) { + return 2; + } else { + AMS_ABORT(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_memory_report.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_memory_report.cpp new file mode 100644 index 00000000..5bfa968b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_memory_report.cpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ncm { + + void HeapState::Initialize(lmem::HeapHandle heap_handle) { + std::scoped_lock lk(m_mutex); + m_heap_handle = heap_handle; + } + + void HeapState::Allocate(size_t size) { + std::scoped_lock lk(m_mutex); + m_total_alloc_size += size; + m_peak_total_alloc_size = std::max(m_total_alloc_size, m_peak_total_alloc_size); + m_peak_alloc_size = std::max(size, m_peak_alloc_size); + } + + void HeapState::Free(size_t size) { + std::scoped_lock lk(m_mutex); + m_total_alloc_size -= size; + } + + void HeapState::GetMemoryResourceState(MemoryResourceState *out) { + *out = {}; + std::scoped_lock lk(m_mutex); + out->peak_total_alloc_size = m_peak_total_alloc_size; + out->peak_alloc_size = m_peak_alloc_size; + out->total_free_size = lmem::GetExpHeapTotalFreeSize(m_heap_handle); + out->allocatable_size = lmem::GetExpHeapAllocatableSize(m_heap_handle, alignof(s32)); + } + + HeapState &GetHeapState() { + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(HeapState, s_heap_state); + + return s_heap_state; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_on_memory_content_meta_database_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_on_memory_content_meta_database_impl.cpp new file mode 100644 index 00000000..663f9f53 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_on_memory_content_meta_database_impl.cpp @@ -0,0 +1,100 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ncm_on_memory_content_meta_database_impl.hpp" + +namespace ams::ncm { + + Result OnMemoryContentMetaDatabaseImpl::List(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType meta_type, ApplicationId application_id, u64 min, u64 max, ContentInstallType install_type) { + /* NOTE: This function is *almost* identical to the ContentMetaDatabaseImpl equivalent. */ + /* The only difference is that the min max comparison is exclusive for OnMemoryContentMetaDatabaseImpl, */ + /* but inclusive for ContentMetaDatabaseImpl. This may or may not be a Nintendo bug? */ + R_TRY(this->EnsureEnabled()); + + size_t entries_total = 0; + size_t entries_written = 0; + + /* Iterate over all entries. */ + for (auto entry = m_kvs->begin(); entry != m_kvs->end(); entry++) { + const ContentMetaKey key = entry->GetKey(); + + /* Check if this entry matches the given filters. */ + if (!((meta_type == ContentMetaType::Unknown || key.type == meta_type) && (min < key.id && key.id < max) && (install_type == ContentInstallType::Unknown || key.install_type == install_type))) { + continue; + } + + /* If application id is present, check if it matches the filter. */ + if (application_id != InvalidApplicationId) { + /* Obtain the content meta for the key. */ + const void *meta; + size_t meta_size; + R_TRY(this->GetContentMetaPointer(&meta, &meta_size, key)); + + /* Create a reader. */ + ContentMetaReader reader(meta, meta_size); + + /* Ensure application id matches, if present. */ + if (const auto entry_application_id = reader.GetApplicationId(key); entry_application_id && application_id != *entry_application_id) { + continue; + } + } + + /* Write the entry to the output buffer. */ + if (entries_written < out_info.GetSize()) { + out_info[entries_written++] = key; + } + entries_total++; + } + + out_entries_total.SetValue(entries_total); + out_entries_written.SetValue(entries_written); + R_SUCCEED(); + } + + Result OnMemoryContentMetaDatabaseImpl::GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, u64 id) { + R_TRY(this->EnsureEnabled()); + + util::optional<ContentMetaKey> found_key = util::nullopt; + + /* Find the last key with the desired program id. */ + for (auto entry = m_kvs->lower_bound(ContentMetaKey::MakeUnknownType(id, 0)); entry != m_kvs->end(); entry++) { + /* No further entries will match the program id, discontinue. */ + if (entry->GetKey().id != id) { + break; + } + + /* On memory content database is interested in all keys. */ + found_key = entry->GetKey(); + } + + /* Check if the key is absent. */ + R_UNLESS(found_key, ncm::ResultContentMetaNotFound()); + + out_key.SetValue(*found_key); + R_SUCCEED(); + } + + Result OnMemoryContentMetaDatabaseImpl::LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) { + AMS_UNUSED(out_orphaned, content_ids); + R_THROW(ncm::ResultInvalidContentMetaDatabase()); + } + + Result OnMemoryContentMetaDatabaseImpl::Commit() { + R_TRY(this->EnsureEnabled()); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_on_memory_content_meta_database_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_on_memory_content_meta_database_impl.hpp new file mode 100644 index 00000000..a3b5e612 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_on_memory_content_meta_database_impl.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "ncm_content_meta_database_impl.hpp" + +namespace ams::ncm { + + class OnMemoryContentMetaDatabaseImpl : public ContentMetaDatabaseImpl { + public: + OnMemoryContentMetaDatabaseImpl(ams::kvdb::MemoryKeyValueStore<ContentMetaKey> *kvs) : ContentMetaDatabaseImpl(kvs) { /* ... */ } + public: + /* Actual commands. */ + virtual Result List(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType meta_type, ApplicationId application_id, u64 min, u64 max, ContentInstallType install_type) override; + virtual Result GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, u64 id) override; + virtual Result LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) override; + virtual Result Commit() override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_package_install_task.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_package_install_task.cpp new file mode 100644 index 00000000..f953b036 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_package_install_task.cpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ncm_fs_utils.hpp" + +namespace ams::ncm { + + bool PackageInstallTask::IsContentMetaContentName(const char *name) { + return impl::PathView(name).HasSuffix(".cnmt"); + } + + Result PackageInstallTask::Initialize(const char *package_root, StorageId storage_id, void *buffer, size_t buffer_size, bool ignore_ticket) { + R_RETURN(PackageInstallTaskBase::Initialize(package_root, buffer, buffer_size, storage_id, std::addressof(m_data), ignore_ticket ? InstallConfig_IgnoreTicket : InstallConfig_None)); + } + + Result PackageInstallTask::GetInstallContentMetaInfo(InstallContentMetaInfo *out_info, const ContentMetaKey &key) { + AMS_UNUSED(out_info, key); + R_THROW(ncm::ResultContentNotFound()); + } + + Result PackageInstallTask::PrepareInstallContentMetaData() { + /* Open the directory. */ + fs::DirectoryHandle dir; + R_TRY(fs::OpenDirectory(std::addressof(dir), this->GetPackageRootPath(), fs::OpenDirectoryMode_File)); + ON_SCOPE_EXIT { fs::CloseDirectory(dir); }; + + while (true) { + /* Read the current entry. */ + s64 count; + fs::DirectoryEntry entry; + R_TRY(fs::ReadDirectory(std::addressof(count), std::addressof(entry), dir, 1)); + + /* No more entries remain, we are done. */ + if (count == 0) { + break; + } + + /* Check if this entry is content meta. */ + if (this->IsContentMetaContentName(entry.name)) { + /* Prepare content meta if id is valid. */ + util::optional<ContentId> id = GetContentIdFromString(entry.name, strnlen(entry.name, fs::EntryNameLengthMax + 1)); + R_UNLESS(id, ncm::ResultInvalidPackageFormat()); + R_TRY(this->PrepareContentMeta(InstallContentMetaInfo::MakeUnverifiable(*id, entry.file_size), util::nullopt, util::nullopt)); + } + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_package_install_task_base.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_package_install_task_base.cpp new file mode 100644 index 00000000..faf47605 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_package_install_task_base.cpp @@ -0,0 +1,134 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ncm { + + Result PackageInstallTaskBase::Initialize(const char *package_root_path, void *buffer, size_t buffer_size, StorageId storage_id, InstallTaskDataBase *data, u32 config) { + R_TRY(InstallTaskBase::Initialize(storage_id, data, config)); + m_package_root.Assign(package_root_path); + m_buffer = buffer; + m_buffer_size = buffer_size; + R_SUCCEED(); + } + + Result PackageInstallTaskBase::OnWritePlaceHolder(const ContentMetaKey &key, InstallContentInfo *content_info) { + AMS_UNUSED(key); + + /* Get the file path. */ + PackagePath path; + if (content_info->GetType() == ContentType::Meta) { + this->CreateContentMetaPath(std::addressof(path), content_info->GetId()); + } else { + this->CreateContentPath(std::addressof(path), content_info->GetId()); + } + + /* Open the file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Continuously write the file to the placeholder until there is nothing left to write. */ + while (true) { + /* Read as much of the remainder of the file as possible. */ + size_t size_read; + R_TRY(fs::ReadFile(std::addressof(size_read), file, content_info->written, m_buffer, m_buffer_size)); + + /* There is nothing left to read. */ + if (size_read == 0) { + break; + } + + /* Write the placeholder. */ + R_TRY(this->WritePlaceHolderBuffer(content_info, m_buffer, size_read)); + } + + R_SUCCEED(); + } + + Result PackageInstallTaskBase::InstallTicket(const fs::RightsId &rights_id, ContentMetaType meta_type) { + AMS_UNUSED(meta_type); + + /* Read ticket from file. */ + s64 ticket_size; + std::unique_ptr<char[]> ticket; + { + fs::FileHandle ticket_file; + { + PackagePath ticket_path; + this->CreateTicketPath(std::addressof(ticket_path), rights_id); + R_TRY(fs::OpenFile(std::addressof(ticket_file), ticket_path, fs::OpenMode_Read)); + } + ON_SCOPE_EXIT { fs::CloseFile(ticket_file); }; + + R_TRY(fs::GetFileSize(std::addressof(ticket_size), ticket_file)); + + ticket.reset(new (std::nothrow) char[static_cast<size_t>(ticket_size)]); + R_UNLESS(ticket != nullptr, ncm::ResultAllocationFailed()); + R_TRY(fs::ReadFile(ticket_file, 0, ticket.get(), static_cast<size_t>(ticket_size))); + } + + + /* Read certificate from file. */ + s64 cert_size; + std::unique_ptr<char[]> cert; + { + fs::FileHandle cert_file; + { + PackagePath cert_path; + this->CreateCertificatePath(std::addressof(cert_path), rights_id); + R_TRY(fs::OpenFile(std::addressof(cert_file), cert_path, fs::OpenMode_Read)); + } + ON_SCOPE_EXIT { fs::CloseFile(cert_file); }; + + R_TRY(fs::GetFileSize(std::addressof(cert_size), cert_file)); + + cert.reset(new (std::nothrow) char[static_cast<size_t>(cert_size)]); + R_UNLESS(cert != nullptr, ncm::ResultAllocationFailed()); + R_TRY(fs::ReadFile(cert_file, 0, cert.get(), static_cast<size_t>(cert_size))); + } + + /* TODO: es::ImportTicket() */ + /* TODO: How should es be handled without undesired effects? */ + + R_SUCCEED(); + } + + void PackageInstallTaskBase::CreateContentPath(PackagePath *out_path, ContentId content_id) { + char str[ContentIdStringLength + 1] = {}; + GetStringFromContentId(str, sizeof(str), content_id); + out_path->AssignFormat("%s%s%s", m_package_root.Get(), str, ".nca"); + } + + void PackageInstallTaskBase::CreateContentMetaPath(PackagePath *out_path, ContentId content_id) { + char str[ContentIdStringLength + 1] = {}; + GetStringFromContentId(str, sizeof(str), content_id); + out_path->AssignFormat("%s%s%s", m_package_root.Get(), str, ".cnmt.nca"); + } + + void PackageInstallTaskBase::CreateTicketPath(PackagePath *out_path, fs::RightsId id) { + char str[RightsIdStringLength + 1] = {}; + GetStringFromRightsId(str, sizeof(str), id); + out_path->AssignFormat("%s%s%s", m_package_root.Get(), str, ".tik"); + } + + void PackageInstallTaskBase::CreateCertificatePath(PackagePath *out_path, fs::RightsId id) { + char str[RightsIdStringLength + 1] = {}; + GetStringFromRightsId(str, sizeof(str), id); + out_path->AssignFormat("%s%s%s", m_package_root.Get(), str, ".cert"); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_package_system_downgrade_task.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_package_system_downgrade_task.cpp new file mode 100644 index 00000000..f8abab57 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_package_system_downgrade_task.cpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ncm { + + Result PackageSystemDowngradeTask::PreCommit() { + constexpr size_t MaxContentMetas = 0x40; + + auto &data = this->GetInstallData(); + + /* Count the number of content meta entries. */ + s32 count; + R_TRY(data.Count(std::addressof(count))); + + /* Iterate over content meta. */ + for (s32 i = 0; i < count; i++) { + /* Obtain the content meta. */ + InstallContentMeta content_meta; + R_TRY(data.Get(std::addressof(content_meta), i)); + + /* Create a reader. */ + const auto reader = content_meta.GetReader(); + const auto key = reader.GetKey(); + + /* Obtain a list of suitable storage ids. */ + const auto storage_list = GetStorageList(this->GetInstallStorage()); + + /* Iterate over storage ids. */ + for (s32 i = 0; i < storage_list.Count(); i++) { + /* Open the content meta database. */ + ContentMetaDatabase meta_db; + if (R_FAILED(OpenContentMetaDatabase(std::addressof(meta_db), storage_list[i]))) { + continue; + } + + /* List keys matching the type and id of the install content meta key. */ + ncm::ContentMetaKey keys[MaxContentMetas]; + const auto count = meta_db.ListContentMeta(keys, MaxContentMetas, key.type, { key.id }); + + /* Remove matching keys. */ + for (auto i = 0; i < count.total; i++) { + meta_db.Remove(keys[i]); + } + } + } + + R_SUCCEED(); + } + + Result PackageSystemDowngradeTask::Commit() { + R_TRY(this->PreCommit()); + R_RETURN(InstallTaskBase::Commit()); + } + + Result PackageSystemDowngradeTask::PrepareContentMetaIfLatest(const ContentMetaKey &key) { + /* Get and prepare install content meta info. We aren't concerned if our key is older. */ + InstallContentMetaInfo install_content_meta_info; + R_TRY(this->GetInstallContentMetaInfo(std::addressof(install_content_meta_info), key)); + R_RETURN(this->PrepareContentMeta(install_content_meta_info, key, util::nullopt)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_package_system_update_task.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_package_system_update_task.cpp new file mode 100644 index 00000000..5332d7e6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_package_system_update_task.cpp @@ -0,0 +1,151 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ncm { + + PackageSystemUpdateTask::~PackageSystemUpdateTask() { + if (m_context_path.GetLength() > 0) { + fs::DeleteFile(m_context_path); + } + this->Inactivate(); + } + + void PackageSystemUpdateTask::Inactivate() { + if (m_gamecard_content_meta_database_active) { + InactivateContentMetaDatabase(StorageId::GameCard); + m_gamecard_content_meta_database_active = false; + } + } + + Result PackageSystemUpdateTask::Initialize(const char *package_root, const char *context_path, void *buffer, size_t buffer_size, bool requires_exfat_driver, FirmwareVariationId firmware_variation_id) { + /* Set the firmware variation id. */ + this->SetFirmwareVariationId(firmware_variation_id); + + /* Activate the game card content meta database. */ + R_TRY(ActivateContentMetaDatabase(StorageId::GameCard)); + m_gamecard_content_meta_database_active = true; + auto meta_db_guard = SCOPE_GUARD { this->Inactivate(); }; + + /* Open the game card content meta database. */ + OpenContentMetaDatabase(std::addressof(m_package_db), StorageId::GameCard); + + ContentMetaDatabaseBuilder builder(std::addressof(m_package_db)); + + /* Cleanup and build the content meta database. */ + R_TRY(builder.Cleanup()); + R_TRY(builder.BuildFromPackage(package_root)); + + /* Create a new context file. */ + fs::DeleteFile(context_path); + R_TRY(FileInstallTaskData::Create(context_path, GameCardMaxContentMetaCount)); + auto context_guard = SCOPE_GUARD { fs::DeleteFile(context_path); }; + + /* Initialize data. */ + R_TRY(m_data.Initialize(context_path)); + + /* Initialize PackageInstallTaskBase. */ + u32 config = !requires_exfat_driver ? InstallConfig_SystemUpdate : InstallConfig_SystemUpdate | InstallConfig_RequiresExFatDriver; + R_TRY(PackageInstallTaskBase::Initialize(package_root, buffer, buffer_size, StorageId::BuiltInSystem, std::addressof(m_data), config)); + + /* Cancel guards. */ + context_guard.Cancel(); + meta_db_guard.Cancel(); + + /* Set the context path. */ + m_context_path.Assign(context_path); + R_SUCCEED(); + } + + util::optional<ContentMetaKey> PackageSystemUpdateTask::GetSystemUpdateMetaKey() { + StorageContentMetaKey storage_keys[0x10]; + s32 ofs = 0; + + s32 count = 0; + do { + /* List content meta keys. */ + if (R_FAILED(this->ListContentMetaKey(std::addressof(count), storage_keys, util::size(storage_keys), ofs, ListContentMetaKeyFilter::All))) { + break; + } + + /* Add listed keys to the offset. */ + ofs += count; + + /* Check if any of these keys are for a SystemUpdate. */ + for (s32 i = 0; i < count; i++) { + const ContentMetaKey &key = storage_keys[i].key; + if (key.type == ContentMetaType::SystemUpdate) { + return key; + } + } + } while (count > 0); + + return util::nullopt; + } + + Result PackageSystemUpdateTask::GetInstallContentMetaInfo(InstallContentMetaInfo *out, const ContentMetaKey &key) { + /* Get the content info for the key. */ + ContentInfo info; + R_TRY(this->GetContentInfoOfContentMeta(std::addressof(info), key)); + + /* Create a new install content meta info. */ + *out = InstallContentMetaInfo::MakeUnverifiable(info.GetId(), info.GetSize(), key); + R_SUCCEED(); + } + + Result PackageSystemUpdateTask::PrepareInstallContentMetaData() { + /* Obtain a SystemUpdate key. */ + ContentMetaKey key; + auto list_count = m_package_db.ListContentMeta(std::addressof(key), 1, ContentMetaType::SystemUpdate); + R_UNLESS(list_count.written > 0, ncm::ResultSystemUpdateNotFoundInPackage()); + + /* Get the content info for the key. */ + ContentInfo info; + R_TRY(this->GetContentInfoOfContentMeta(std::addressof(info), key)); + + /* Prepare the content meta. */ + R_RETURN(this->PrepareContentMeta(InstallContentMetaInfo::MakeUnverifiable(info.GetId(), info.GetSize(), key), key, util::nullopt)); + } + + Result PackageSystemUpdateTask::PrepareDependency() { + R_RETURN(this->PrepareSystemUpdateDependency()); + } + + Result PackageSystemUpdateTask::GetContentInfoOfContentMeta(ContentInfo *out, const ContentMetaKey &key) { + s32 ofs = 0; + while (true) { + /* List content infos. */ + s32 count; + ContentInfo info; + R_TRY(m_package_db.ListContentInfo(std::addressof(count), std::addressof(info), 1, key, ofs++)); + + /* No content infos left to list. */ + if (count == 0) { + break; + } + + /* Check if the info is for meta content. */ + if (info.GetType() == ContentType::Meta) { + *out = info; + R_SUCCEED(); + } + } + + /* Not found. */ + R_THROW(ncm::ResultContentInfoNotFound()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_placeholder_accessor.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_placeholder_accessor.cpp new file mode 100644 index 00000000..4b392c21 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_placeholder_accessor.cpp @@ -0,0 +1,249 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ncm_placeholder_accessor.hpp" +#include "ncm_fs_utils.hpp" + +namespace ams::ncm { + + namespace { + + constexpr inline const char * const BasePlaceHolderDirectory = "/placehld"; + + constexpr inline const char * const PlaceHolderExtension = ".nca"; + constexpr inline size_t PlaceHolderExtensionLength = 4; + + constexpr inline size_t PlaceHolderFileNameLengthWithoutExtension = 2 * sizeof(PlaceHolderId); + constexpr inline size_t PlaceHolderFileNameLength = PlaceHolderFileNameLengthWithoutExtension + PlaceHolderExtensionLength; + + void MakeBasePlaceHolderDirectoryPath(PathString *out, const char *root_path) { + out->AssignFormat("%s%s", root_path, BasePlaceHolderDirectory); + } + + void MakePlaceHolderFilePath(PathString *out, PlaceHolderId id, MakePlaceHolderPathFunction func, const char *root_path) { + PathString path; + MakeBasePlaceHolderDirectoryPath(std::addressof(path), root_path); + func(out, id, path); + } + + } + + void PlaceHolderAccessor::MakePath(PathString *out_placeholder_path, PlaceHolderId placeholder_id) const { + MakePlaceHolderFilePath(out_placeholder_path, placeholder_id, m_make_placeholder_path_func, *m_root_path); + } + + void PlaceHolderAccessor::MakeBaseDirectoryPath(PathString *out, const char *root_path) { + MakeBasePlaceHolderDirectoryPath(out, root_path); + } + + Result PlaceHolderAccessor::EnsurePlaceHolderDirectory(PlaceHolderId placeholder_id) { + PathString path; + this->MakePath(std::addressof(path), placeholder_id); + R_RETURN(fs::EnsureParentDirectory(path)); + } + + Result PlaceHolderAccessor::GetPlaceHolderIdFromFileName(PlaceHolderId *out, const char *name) { + /* Ensure placeholder name is valid. */ + R_UNLESS(strnlen(name, PlaceHolderFileNameLength) == PlaceHolderFileNameLength, ncm::ResultInvalidPlaceHolderFile()); + R_UNLESS(strncmp(name + PlaceHolderFileNameLengthWithoutExtension, PlaceHolderExtension, PlaceHolderExtensionLength) == 0, ncm::ResultInvalidPlaceHolderFile()); + + /* Convert each character pair to a byte until we reach the end. */ + PlaceHolderId placeholder_id = {}; + for (size_t i = 0; i < sizeof(placeholder_id); i++) { + char tmp[3]; + util::Strlcpy(tmp, name + i * 2, sizeof(tmp)); + + char *err = nullptr; + reinterpret_cast<u8 *>(std::addressof(placeholder_id))[i] = static_cast<u8>(std::strtoul(tmp, std::addressof(err), 16)); + R_UNLESS(*err == '\x00', ncm::ResultInvalidPlaceHolderFile()); + } + + *out = placeholder_id; + R_SUCCEED(); + } + + Result PlaceHolderAccessor::Open(fs::FileHandle *out_handle, PlaceHolderId placeholder_id) { + /* Try to load from the cache. */ + R_SUCCEED_IF(this->LoadFromCache(out_handle, placeholder_id)); + + /* Make the path of the placeholder. */ + PathString placeholder_path; + this->MakePath(std::addressof(placeholder_path), placeholder_id); + + /* Open the placeholder file. */ + R_RETURN(fs::OpenFile(out_handle, placeholder_path, fs::OpenMode_Write)); + } + + bool PlaceHolderAccessor::LoadFromCache(fs::FileHandle *out_handle, PlaceHolderId placeholder_id) { + std::scoped_lock lk(m_cache_mutex); + + /* Attempt to find an entry in the cache. */ + CacheEntry *entry = this->FindInCache(placeholder_id); + if (!entry) { + return false; + } + + /* No cached entry found. */ + entry->id = InvalidPlaceHolderId; + *out_handle = entry->handle; + return true; + } + + void PlaceHolderAccessor::StoreToCache(PlaceHolderId placeholder_id, fs::FileHandle handle) { + std::scoped_lock lk(m_cache_mutex); + + /* Store placeholder id and file handle to a free entry. */ + CacheEntry *entry = this->GetFreeEntry(); + entry->id = placeholder_id; + entry->handle = handle; + entry->counter = m_cur_counter++; + } + + void PlaceHolderAccessor::Invalidate(CacheEntry *entry) { + /* Flush and close the cached entry's file. */ + if (entry != nullptr) { + fs::FlushFile(entry->handle); + fs::CloseFile(entry->handle); + entry->id = InvalidPlaceHolderId; + } + } + + PlaceHolderAccessor::CacheEntry *PlaceHolderAccessor::FindInCache(PlaceHolderId placeholder_id) { + /* Ensure placeholder id is valid. */ + if (placeholder_id == InvalidPlaceHolderId) { + return nullptr; + } + + /* Attempt to find a cache entry with the same placeholder id. */ + for (size_t i = 0; i < MaxCacheEntries; i++) { + if (placeholder_id == m_caches[i].id) { + return std::addressof(m_caches[i]); + } + } + return nullptr; + } + + PlaceHolderAccessor::CacheEntry *PlaceHolderAccessor::GetFreeEntry() { + /* Try to find an already free entry. */ + for (size_t i = 0; i < MaxCacheEntries; i++) { + if (m_caches[i].id == InvalidPlaceHolderId) { + return std::addressof(m_caches[i]); + } + } + + /* Get the oldest entry. */ + CacheEntry *entry = std::addressof(m_caches[0]); + for (size_t i = 1; i < MaxCacheEntries; i++) { + if (entry->counter < m_caches[i].counter) { + entry = std::addressof(m_caches[i]); + } + } + this->Invalidate(entry); + return entry; + } + + void PlaceHolderAccessor::GetPath(PathString *placeholder_path, PlaceHolderId placeholder_id) { + { + std::scoped_lock lock(m_cache_mutex); + this->Invalidate(this->FindInCache(placeholder_id)); + } + this->MakePath(placeholder_path, placeholder_id); + } + + Result PlaceHolderAccessor::CreatePlaceHolderFile(PlaceHolderId placeholder_id, s64 size) { + /* Ensure the destination directory exists. */ + R_TRY(this->EnsurePlaceHolderDirectory(placeholder_id)); + + /* Get the placeholder path. */ + PathString placeholder_path; + this->GetPath(std::addressof(placeholder_path), placeholder_id); + + /* Create the placeholder file. */ + R_TRY_CATCH(fs::CreateFile(placeholder_path, size, fs::CreateOption_BigFile)) { + R_CONVERT(fs::ResultPathAlreadyExists, ncm::ResultPlaceHolderAlreadyExists()) + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + Result PlaceHolderAccessor::DeletePlaceHolderFile(PlaceHolderId placeholder_id) { + /* Get the placeholder path. */ + PathString placeholder_path; + this->GetPath(std::addressof(placeholder_path), placeholder_id); + + /* Delete the placeholder file. */ + R_TRY_CATCH(fs::DeleteFile(placeholder_path)) { + R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound()) + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + Result PlaceHolderAccessor::WritePlaceHolderFile(PlaceHolderId placeholder_id, s64 offset, const void *buffer, size_t size) { + /* Open the placeholder file. */ + fs::FileHandle file; + R_TRY_CATCH(this->Open(std::addressof(file), placeholder_id)) { + R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound()) + } R_END_TRY_CATCH; + + /* Store opened files to the cache regardless of write failures. */ + ON_SCOPE_EXIT { this->StoreToCache(placeholder_id, file); }; + + /* Write data to the placeholder file. */ + R_RETURN(fs::WriteFile(file, offset, buffer, size, m_delay_flush ? fs::WriteOption::Flush : fs::WriteOption::None)); + } + + Result PlaceHolderAccessor::SetPlaceHolderFileSize(PlaceHolderId placeholder_id, s64 size) { + /* Open the placeholder file. */ + fs::FileHandle file; + R_TRY_CATCH(this->Open(std::addressof(file), placeholder_id)) { + R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound()) + } R_END_TRY_CATCH; + + /* Close the file on exit. */ + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Set the size of the placeholder file. */ + R_RETURN(fs::SetFileSize(file, size)); + } + + Result PlaceHolderAccessor::TryGetPlaceHolderFileSize(bool *found_in_cache, s64 *out_size, PlaceHolderId placeholder_id) { + /* Attempt to find the placeholder in the cache. */ + fs::FileHandle handle; + auto found = this->LoadFromCache(std::addressof(handle), placeholder_id); + + if (found) { + /* Renew the entry in the cache. */ + this->StoreToCache(placeholder_id, handle); + R_TRY(fs::GetFileSize(out_size, handle)); + *found_in_cache = true; + } else { + *found_in_cache = false; + } + + R_SUCCEED(); + } + + void PlaceHolderAccessor::InvalidateAll() { + /* Invalidate all cache entries. */ + for (auto &entry : m_caches) { + if (entry.id != InvalidPlaceHolderId) { + this->Invalidate(std::addressof(entry)); + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_placeholder_accessor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_placeholder_accessor.hpp new file mode 100644 index 00000000..96c9b5f5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_placeholder_accessor.hpp @@ -0,0 +1,78 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ncm { + + class PlaceHolderAccessor { + private: + struct CacheEntry { + PlaceHolderId id; + fs::FileHandle handle; + u64 counter; + }; + + static constexpr size_t MaxCacheEntries = 0x2; + private: + std::array<CacheEntry, MaxCacheEntries> m_caches; + PathString *m_root_path; + u64 m_cur_counter; + os::SdkMutex m_cache_mutex; + MakePlaceHolderPathFunction m_make_placeholder_path_func; + bool m_delay_flush; + private: + Result Open(fs::FileHandle *out_handle, PlaceHolderId placeholder_id); + bool LoadFromCache(fs::FileHandle *out_handle, PlaceHolderId placeholder_id); + void StoreToCache(PlaceHolderId placeholder_id, fs::FileHandle handle); + void Invalidate(CacheEntry *entry); + CacheEntry *FindInCache(PlaceHolderId placeholder_id); + CacheEntry *GetFreeEntry();; + public: + PlaceHolderAccessor() : m_root_path(nullptr), m_cur_counter(0), m_cache_mutex(), m_make_placeholder_path_func(nullptr), m_delay_flush(false) { + for (size_t i = 0; i < MaxCacheEntries; i++) { + m_caches[i].id = InvalidPlaceHolderId; + } + } + + ~PlaceHolderAccessor() { this->InvalidateAll(); } + + static void MakeBaseDirectoryPath(PathString *out, const char *root_path); + static Result GetPlaceHolderIdFromFileName(PlaceHolderId *out, const char *name); + public: + /* API. */ + void Initialize(PathString *root, MakePlaceHolderPathFunction path_func, bool delay_flush) { + m_root_path = root; + m_make_placeholder_path_func = path_func; + m_delay_flush = delay_flush; + } + + Result CreatePlaceHolderFile(PlaceHolderId placeholder_id, s64 size); + Result DeletePlaceHolderFile(PlaceHolderId placeholder_id); + Result WritePlaceHolderFile(PlaceHolderId placeholder_id, s64 offset, const void *buffer, size_t size); + Result SetPlaceHolderFileSize(PlaceHolderId placeholder_id, s64 size); + Result TryGetPlaceHolderFileSize(bool *out_found, s64 *out_size, PlaceHolderId placeholder_id); + + void GetPath(PathString *out_placeholder_path, PlaceHolderId placeholder_id); + void MakePath(PathString *out_placeholder_path, PlaceHolderId placeholder_id) const; + + void InvalidateAll(); + + Result EnsurePlaceHolderDirectory(PlaceHolderId placeholder_id); + size_t GetHierarchicalDirectoryDepth() const { return GetHierarchicalPlaceHolderDirectoryDepth(m_make_placeholder_path_func); } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_read_only_content_storage_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_read_only_content_storage_impl.cpp new file mode 100644 index 00000000..a25f23b1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_read_only_content_storage_impl.cpp @@ -0,0 +1,315 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ncm_read_only_content_storage_impl.hpp" +#include "ncm_fs_utils.hpp" + +namespace ams::ncm { + + namespace { + + void MakeContentPath(PathString *out, ContentId id, MakeContentPathFunction func, const char *root_path) { + return func(out, id, root_path); + } + + void MakeGameCardContentMetaPath(PathString *out, ContentId id, MakeContentPathFunction func, const char *root_path) { + /* Determine the content path. */ + PathString path; + func(std::addressof(path), id, root_path); + + /* Substitute the .nca extension with .cmnt.nca. */ + *out = path.MakeSubString(0, path.GetLength() - 4); + out->Append(".cnmt.nca"); + } + + Result OpenContentIdFileImpl(fs::FileHandle *out, ContentId id, MakeContentPathFunction func, const char *root_path) { + PathString path; + MakeContentPath(std::addressof(path), id, func, root_path); + + /* Open the content file. */ + /* If absent, make the path for game card content meta and open again. */ + R_TRY_CATCH(fs::OpenFile(out, path, fs::OpenMode_Read)) { + R_CATCH(fs::ResultPathNotFound) { + MakeGameCardContentMetaPath(std::addressof(path), id, func, root_path); + R_TRY(fs::OpenFile(out, path, fs::OpenMode_Read)); + } + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + } + + Result ReadOnlyContentStorageImpl::Initialize(const char *path, MakeContentPathFunction content_path_func) { + R_TRY(this->EnsureEnabled()); + m_root_path.Assign(path); + m_make_content_path_func = content_path_func; + R_SUCCEED(); + } + + Result ReadOnlyContentStorageImpl::GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) { + AMS_UNUSED(out); + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size) { + AMS_UNUSED(placeholder_id, content_id, size); + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::DeletePlaceHolder(PlaceHolderId placeholder_id) { + AMS_UNUSED(placeholder_id); + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) { + AMS_UNUSED(out, placeholder_id); + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, const sf::InBuffer &data) { + AMS_UNUSED(placeholder_id, offset, data); + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::Register(PlaceHolderId placeholder_id, ContentId content_id) { + AMS_UNUSED(placeholder_id, content_id); + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::Delete(ContentId content_id) { + AMS_UNUSED(content_id); + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::Has(sf::Out<bool> out, ContentId content_id) { + R_TRY(this->EnsureEnabled()); + + /* Make the content path. */ + PathString content_path; + MakeContentPath(std::addressof(content_path), content_id, m_make_content_path_func, m_root_path); + + /* Check if the file exists. */ + bool has; + R_TRY(fs::HasFile(std::addressof(has), content_path)); + + /* If the file is absent, make the path for game card content meta and check presence again. */ + if (!has) { + MakeGameCardContentMetaPath(std::addressof(content_path), content_id, m_make_content_path_func, m_root_path); + R_TRY(fs::HasFile(std::addressof(has), content_path)); + } + + out.SetValue(has); + R_SUCCEED(); + } + + Result ReadOnlyContentStorageImpl::GetPath(sf::Out<Path> out, ContentId content_id) { + R_TRY(this->EnsureEnabled()); + + /* Make the path for game card content meta. */ + PathString content_path; + MakeGameCardContentMetaPath(std::addressof(content_path), content_id, m_make_content_path_func, m_root_path); + + /* Check if the file exists. */ + bool has_file; + R_TRY(fs::HasFile(std::addressof(has_file), content_path)); + + /* If the file is absent, make the path for regular content. */ + if (!has_file) { + MakeContentPath(std::addressof(content_path), content_id, m_make_content_path_func, m_root_path); + } + + /* Substitute mount name with the common mount name. */ + Path common_path; + R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), content_path)); + + out.SetValue(common_path); + R_SUCCEED(); + } + + Result ReadOnlyContentStorageImpl::GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id) { + AMS_UNUSED(out, placeholder_id); + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::CleanupAllPlaceHolder() { + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::ListPlaceHolder(sf::Out<s32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) { + AMS_UNUSED(out_count, out_buf); + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::GetContentCount(sf::Out<s32> out_count) { + AMS_UNUSED(out_count); + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out_buf, s32 offset) { + AMS_UNUSED(out_count, out_buf, offset); + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::GetSizeFromContentId(sf::Out<s64> out_size, ContentId content_id) { + R_TRY(this->EnsureEnabled()); + + /* Open the file for the content id. */ + fs::FileHandle file; + R_TRY(OpenContentIdFileImpl(std::addressof(file), content_id, m_make_content_path_func, m_root_path)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Determine the file size. */ + s64 file_size; + R_TRY(fs::GetFileSize(std::addressof(file_size), file)); + + out_size.SetValue(file_size); + R_SUCCEED(); + } + + Result ReadOnlyContentStorageImpl::DisableForcibly() { + m_disabled = true; + R_SUCCEED(); + } + + Result ReadOnlyContentStorageImpl::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) { + AMS_UNUSED(placeholder_id, old_content_id, new_content_id); + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) { + AMS_UNUSED(placeholder_id, size); + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::ReadContentIdFile(const sf::OutBuffer &buf, ContentId content_id, s64 offset) { + /* Ensure offset is valid. */ + R_UNLESS(offset >= 0, ncm::ResultInvalidOffset()); + R_TRY(this->EnsureEnabled()); + + /* Open the file for the content id. */ + fs::FileHandle file; + R_TRY(OpenContentIdFileImpl(std::addressof(file), content_id, m_make_content_path_func, m_root_path)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Read from the given offset up to the given size. */ + R_TRY(fs::ReadFile(file, offset, buf.GetPointer(), buf.GetSize())); + + R_SUCCEED(); + } + + Result ReadOnlyContentStorageImpl::GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id) { + AMS_UNUSED(out_rights_id, placeholder_id); + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::GetRightsIdFromPlaceHolderIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) { + AMS_UNUSED(out_rights_id, placeholder_id); + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, fs::ContentAttributes attr) { + AMS_UNUSED(out_rights_id, placeholder_id, attr); + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) { + /* Obtain the regular rights id for the content id. */ + ncm::RightsId rights_id; + R_TRY(this->GetRightsIdFromContentIdDeprecated2(std::addressof(rights_id), content_id)); + + /* Output the fs rights id. */ + out_rights_id.SetValue(rights_id.id); + R_SUCCEED(); + } + + Result ReadOnlyContentStorageImpl::GetRightsIdFromContentIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id) { + R_RETURN(this->GetRightsIdFromContentId(out_rights_id, content_id, fs::ContentAttributes_None)); + } + + Result ReadOnlyContentStorageImpl::GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id, fs::ContentAttributes attr) { + R_TRY(this->EnsureEnabled()); + + /* Get the content path. */ + Path path; + R_TRY(this->GetPath(std::addressof(path), content_id)); + + /* Get the rights id. */ + ncm::RightsId rights_id; + R_TRY(GetRightsId(std::addressof(rights_id), path, attr)); + out_rights_id.SetValue(rights_id); + + R_SUCCEED(); + } + + Result ReadOnlyContentStorageImpl::WriteContentForDebug(ContentId content_id, s64 offset, const sf::InBuffer &data) { + AMS_UNUSED(content_id, offset, data); + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::GetFreeSpaceSize(sf::Out<s64> out_size) { + out_size.SetValue(0); + R_SUCCEED(); + } + + Result ReadOnlyContentStorageImpl::GetTotalSpaceSize(sf::Out<s64> out_size) { + out_size.SetValue(0); + R_SUCCEED(); + } + + Result ReadOnlyContentStorageImpl::FlushPlaceHolder() { + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::GetSizeFromPlaceHolderId(sf::Out<s64> out, PlaceHolderId placeholder_id) { + AMS_UNUSED(out, placeholder_id); + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::RepairInvalidFileAttribute() { + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id, fs::ContentAttributes attr) { + AMS_UNUSED(out_rights_id, placeholder_id, cache_content_id, attr); + R_THROW(ncm::ResultNotSupported()); + } + + Result ReadOnlyContentStorageImpl::RegisterPath(const ContentId &content_id, const Path &path) { + AMS_UNUSED(content_id, path); + R_THROW(ncm::ResultInvalidOperation()); + } + + Result ReadOnlyContentStorageImpl::ClearRegisteredPath() { + R_THROW(ncm::ResultInvalidOperation()); + } + + Result ReadOnlyContentStorageImpl::GetProgramId(sf::Out<ncm::ProgramId> out, ContentId content_id, fs::ContentAttributes attr) { + R_TRY(this->EnsureEnabled()); + + /* Get the path of the content. */ + Path path; + R_TRY(this->GetPath(std::addressof(path), content_id)); + + /* Obtain the program id for the content. */ + ncm::ProgramId program_id; + R_TRY(fs::GetProgramId(std::addressof(program_id), path.str, attr)); + + out.SetValue(program_id); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_read_only_content_storage_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_read_only_content_storage_impl.hpp new file mode 100644 index 00000000..c00a3ae1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_read_only_content_storage_impl.hpp @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "ncm_content_storage_impl_base.hpp" + +namespace ams::ncm { + + class ReadOnlyContentStorageImpl : public ContentStorageImplBase { + public: + Result Initialize(const char *root_path, MakeContentPathFunction content_path_func); + public: + /* Actual commands. */ + virtual Result GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) override; + virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size) override; + virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) override; + virtual Result HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) override; + virtual Result WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, const sf::InBuffer &data) override; + virtual Result Register(PlaceHolderId placeholder_id, ContentId content_id) override; + virtual Result Delete(ContentId content_id) override; + virtual Result Has(sf::Out<bool> out, ContentId content_id) override; + virtual Result GetPath(sf::Out<Path> out, ContentId content_id) override; + virtual Result GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id) override; + virtual Result CleanupAllPlaceHolder() override; + virtual Result ListPlaceHolder(sf::Out<s32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) override; + virtual Result GetContentCount(sf::Out<s32> out_count) override; + virtual Result ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out_buf, s32 start_offset) override; + virtual Result GetSizeFromContentId(sf::Out<s64> out_size, ContentId content_id) override; + virtual Result DisableForcibly() override; + virtual Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) override; + virtual Result SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) override; + virtual Result ReadContentIdFile(const sf::OutBuffer &buf, ContentId content_id, s64 offset) override; + virtual Result GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id) override; + virtual Result GetRightsIdFromPlaceHolderIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) override; + virtual Result GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, fs::ContentAttributes attr) override; + virtual Result GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) override; + virtual Result GetRightsIdFromContentIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id) override; + virtual Result GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id, fs::ContentAttributes attr) override; + virtual Result WriteContentForDebug(ContentId content_id, s64 offset, const sf::InBuffer &data) override; + virtual Result GetFreeSpaceSize(sf::Out<s64> out_size) override; + virtual Result GetTotalSpaceSize(sf::Out<s64> out_size) override; + virtual Result FlushPlaceHolder() override; + virtual Result GetSizeFromPlaceHolderId(sf::Out<s64> out, PlaceHolderId placeholder_id) override; + virtual Result RepairInvalidFileAttribute() override; + virtual Result GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id, fs::ContentAttributes attr) override; + virtual Result RegisterPath(const ContentId &content_id, const Path &path) override; + virtual Result ClearRegisteredPath() override; + virtual Result GetProgramId(sf::Out<ncm::ProgramId> out, ContentId content_id, fs::ContentAttributes attr) override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_registered_host_content.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_registered_host_content.cpp new file mode 100644 index 00000000..c4466bc9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_registered_host_content.cpp @@ -0,0 +1,90 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ncm { + + class RegisteredHostContent::RegisteredPath : public util::IntrusiveListBaseNode<RegisteredPath> { + NON_COPYABLE(RegisteredPath); + NON_MOVEABLE(RegisteredPath); + private: + ContentId m_content_id; + Path m_path; + public: + RegisteredPath(const ncm::ContentId &content_id, const Path &p) : m_content_id(content_id), m_path(p) { + /* ... */ + } + + ncm::ContentId GetContentId() const { + return m_content_id; + } + + void GetPath(Path *out) const { + *out = m_path; + } + + void SetPath(const Path &path) { + m_path = path; + } + }; + + RegisteredHostContent::~RegisteredHostContent() { + /* ... */ + } + + Result RegisteredHostContent::RegisterPath(const ncm::ContentId &content_id, const ncm::Path &path) { + std::scoped_lock lk(m_mutex); + + /* Replace the path of any existing entries. */ + for (auto ®istered_path : m_path_list) { + if (registered_path.GetContentId() == content_id) { + registered_path.SetPath(path); + R_SUCCEED(); + } + } + + /* Allocate a new registered path. TODO: Verify allocator. */ + RegisteredPath *registered_path = new RegisteredPath(content_id, path); + R_UNLESS(registered_path != nullptr, ncm::ResultBufferInsufficient()); + + /* Insert the path into the list. */ + m_path_list.push_back(*registered_path); + R_SUCCEED(); + } + + Result RegisteredHostContent::GetPath(Path *out, const ncm::ContentId &content_id) { + std::scoped_lock lk(m_mutex); + + /* Obtain the path of the content. */ + for (const auto ®istered_path : m_path_list) { + if (registered_path.GetContentId() == content_id) { + registered_path.GetPath(out); + R_SUCCEED(); + } + } + R_THROW(ncm::ResultContentNotFound()); + } + + void RegisteredHostContent::ClearPaths() { + /* Remove all paths. */ + for (auto it = m_path_list.begin(); it != m_path_list.end(); /* ... */) { + auto *obj = std::addressof(*it); + it = m_path_list.erase(it); + delete obj; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_remote_content_manager_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_remote_content_manager_impl.hpp new file mode 100644 index 00000000..e9ee891a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_remote_content_manager_impl.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "ncm_remote_content_storage_impl.hpp" +#include "ncm_remote_content_meta_database_impl.hpp" + +namespace ams::ncm { + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemoteContentManagerImpl { + private: + /* TODO: sf::ProxyObjectAllocator */ + using ObjectFactory = sf::ObjectFactory<sf::StdAllocationPolicy<std::allocator>>; + public: + RemoteContentManagerImpl() { R_ABORT_UNLESS(::ncmInitialize()); } + + ~RemoteContentManagerImpl() { ::ncmExit(); } + public: + Result CreateContentStorage(StorageId storage_id) { + R_RETURN(::ncmCreateContentStorage(static_cast<NcmStorageId>(storage_id))); + } + + Result CreateContentMetaDatabase(StorageId storage_id) { + R_RETURN(::ncmCreateContentMetaDatabase(static_cast<NcmStorageId>(storage_id))); + } + + Result VerifyContentStorage(StorageId storage_id) { + R_RETURN(::ncmVerifyContentStorage(static_cast<NcmStorageId>(storage_id))); + } + + Result VerifyContentMetaDatabase(StorageId storage_id) { + R_RETURN(::ncmVerifyContentMetaDatabase(static_cast<NcmStorageId>(storage_id))); + } + + Result OpenContentStorage(sf::Out<sf::SharedPointer<IContentStorage>> out, StorageId storage_id) { + NcmContentStorage cs; + R_TRY(::ncmOpenContentStorage(std::addressof(cs), static_cast<NcmStorageId>(storage_id))); + + out.SetValue(ObjectFactory::CreateSharedEmplaced<IContentStorage, RemoteContentStorageImpl>(cs)); + R_SUCCEED(); + } + + Result OpenContentMetaDatabase(sf::Out<sf::SharedPointer<IContentMetaDatabase>> out, StorageId storage_id) { + NcmContentMetaDatabase db; + R_TRY(::ncmOpenContentMetaDatabase(std::addressof(db), static_cast<NcmStorageId>(storage_id))); + + out.SetValue(ObjectFactory::CreateSharedEmplaced<IContentMetaDatabase, RemoteContentMetaDatabaseImpl>(db)); + R_SUCCEED(); + } + + Result CloseContentStorageForcibly(StorageId storage_id) { + R_RETURN(::ncmCloseContentStorageForcibly(static_cast<NcmStorageId>(storage_id))); + } + + Result CloseContentMetaDatabaseForcibly(StorageId storage_id) { + R_RETURN(::ncmCloseContentMetaDatabaseForcibly(static_cast<NcmStorageId>(storage_id))); + } + + Result CleanupContentMetaDatabase(StorageId storage_id) { + R_RETURN(::ncmCleanupContentMetaDatabase(static_cast<NcmStorageId>(storage_id))); + } + + Result ActivateContentStorage(StorageId storage_id) { + R_RETURN(::ncmActivateContentStorage(static_cast<NcmStorageId>(storage_id))); + } + + Result InactivateContentStorage(StorageId storage_id) { + R_RETURN(::ncmInactivateContentStorage(static_cast<NcmStorageId>(storage_id))); + } + + Result ActivateContentMetaDatabase(StorageId storage_id) { + R_RETURN(::ncmActivateContentMetaDatabase(static_cast<NcmStorageId>(storage_id))); + } + + Result InactivateContentMetaDatabase(StorageId storage_id) { + R_RETURN(::ncmInactivateContentMetaDatabase(static_cast<NcmStorageId>(storage_id))); + } + + Result InvalidateRightsIdCache() { + R_RETURN(::ncmInvalidateRightsIdCache()); + } + + Result GetMemoryReport(sf::Out<MemoryReport> out) { + /* TODO: libnx bindings */ + AMS_UNUSED(out); + AMS_ABORT(); + } + + Result ActivateFsContentStorage(fs::ContentStorageId fs_content_storage_id) { + R_RETURN(::ncmActivateFsContentStorage(static_cast<::FsContentStorageId>(util::ToUnderlying(fs_content_storage_id)))); + } + }; + static_assert(ncm::IsIContentManager<RemoteContentManagerImpl>); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_remote_content_meta_database_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_remote_content_meta_database_impl.hpp new file mode 100644 index 00000000..abba78c7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_remote_content_meta_database_impl.hpp @@ -0,0 +1,204 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ncm { + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemoteContentMetaDatabaseImpl { + private: + ::NcmContentMetaDatabase m_srv; + public: + RemoteContentMetaDatabaseImpl(::NcmContentMetaDatabase &db) : m_srv(db) { /* ... */ } + + ~RemoteContentMetaDatabaseImpl() { ::ncmContentMetaDatabaseClose(std::addressof(m_srv)); } + private: + ALWAYS_INLINE ::NcmContentMetaKey *Convert(ContentMetaKey *k) { + static_assert(sizeof(ContentMetaKey) == sizeof(::NcmContentMetaKey)); + return reinterpret_cast<::NcmContentMetaKey *>(k); + } + ALWAYS_INLINE const ::NcmContentMetaKey *Convert(const ContentMetaKey *k) { + static_assert(sizeof(ContentMetaKey) == sizeof(::NcmContentMetaKey)); + return reinterpret_cast<const ::NcmContentMetaKey *>(k); + } + + ALWAYS_INLINE const ::NcmContentMetaKey *Convert(const ContentMetaKey &k) { + static_assert(sizeof(ContentMetaKey) == sizeof(::NcmContentMetaKey)); + return reinterpret_cast<const ::NcmContentMetaKey *>(std::addressof(k)); + } + + ALWAYS_INLINE ::NcmApplicationContentMetaKey *Convert(ApplicationContentMetaKey *k) { + static_assert(sizeof(ApplicationContentMetaKey) == sizeof(::NcmApplicationContentMetaKey)); + return reinterpret_cast<::NcmApplicationContentMetaKey *>(k); + } + + ALWAYS_INLINE ::NcmContentInfo *Convert(ContentInfo *c) { + static_assert(sizeof(ContentInfo) == sizeof(::NcmContentInfo)); + return reinterpret_cast<::NcmContentInfo *>(c); + } + + ALWAYS_INLINE ::NcmContentId *Convert(ContentId *c) { + static_assert(sizeof(ContentId) == sizeof(::NcmContentId)); + return reinterpret_cast<::NcmContentId *>(c); + } + + ALWAYS_INLINE ::NcmContentId *Convert(ContentId &c) { + static_assert(sizeof(ContentId) == sizeof(::NcmContentId)); + return reinterpret_cast<::NcmContentId *>(std::addressof(c)); + } + + ALWAYS_INLINE const ::NcmContentId *Convert(const ContentId *c) { + static_assert(sizeof(ContentId) == sizeof(::NcmContentId)); + return reinterpret_cast<const ::NcmContentId *>(c); + } + + + ALWAYS_INLINE const ::NcmContentId *Convert(const ContentId &c) { + static_assert(sizeof(ContentId) == sizeof(::NcmContentId)); + return reinterpret_cast<const ::NcmContentId *>(std::addressof(c)); + } + public: + Result Set(const ContentMetaKey &key, const sf::InBuffer &value) { + R_RETURN(ncmContentMetaDatabaseSet(std::addressof(m_srv), Convert(key), value.GetPointer(), value.GetSize())); + } + + Result Get(sf::Out<u64> out_size, const ContentMetaKey &key, const sf::OutBuffer &out_value) { + R_RETURN(ncmContentMetaDatabaseGet(std::addressof(m_srv), Convert(key), out_size.GetPointer(), out_value.GetPointer(), out_value.GetSize())); + } + + Result Remove(const ContentMetaKey &key) { + R_RETURN(ncmContentMetaDatabaseRemove(std::addressof(m_srv), Convert(key))); + } + + Result GetContentIdByType(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type) { + R_RETURN(ncmContentMetaDatabaseGetContentIdByType(std::addressof(m_srv), Convert(out_content_id.GetPointer()), Convert(key), static_cast<::NcmContentType>(type))); + } + + Result ListContentInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentInfo> &out_info, const ContentMetaKey &key, s32 offset) { + R_RETURN(ncmContentMetaDatabaseListContentInfo(std::addressof(m_srv), out_entries_written.GetPointer(), Convert(out_info.GetPointer()), out_info.GetSize(), Convert(key), offset)); + } + + Result List(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType meta_type, ApplicationId application_id, u64 min, u64 max, ContentInstallType install_type) { + R_RETURN(ncmContentMetaDatabaseList(std::addressof(m_srv), out_entries_total.GetPointer(), out_entries_written.GetPointer(), Convert(out_info.GetPointer()), out_info.GetSize(), static_cast<::NcmContentMetaType>(meta_type), application_id.value, min, max, static_cast<::NcmContentInstallType>(install_type))); + } + + Result GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, u64 id) { + R_RETURN(ncmContentMetaDatabaseGetLatestContentMetaKey(std::addressof(m_srv), Convert(out_key.GetPointer()), static_cast<u64>(id))); + } + + Result ListApplication(sf::Out<s32> out_entries_total, sf::Out<s32> out_entries_written, const sf::OutArray<ApplicationContentMetaKey> &out_keys, ContentMetaType meta_type) { + R_RETURN(ncmContentMetaDatabaseListApplication(std::addressof(m_srv), out_entries_total.GetPointer(), out_entries_written.GetPointer(), Convert(out_keys.GetPointer()), out_keys.GetSize(), static_cast<::NcmContentMetaType>(meta_type))); + } + + Result Has(sf::Out<bool> out, const ContentMetaKey &key) { + R_RETURN(ncmContentMetaDatabaseHas(std::addressof(m_srv), out.GetPointer(), Convert(key))); + } + + Result HasAll(sf::Out<bool> out, const sf::InArray<ContentMetaKey> &keys) { + R_RETURN(ncmContentMetaDatabaseHasAll(std::addressof(m_srv), out.GetPointer(), Convert(keys.GetPointer()), keys.GetSize())); + } + + Result GetSize(sf::Out<u64> out_size, const ContentMetaKey &key) { + R_RETURN(ncmContentMetaDatabaseGetSize(std::addressof(m_srv), out_size.GetPointer(), Convert(key))); + } + + Result GetRequiredSystemVersion(sf::Out<u32> out_version, const ContentMetaKey &key) { + R_RETURN(ncmContentMetaDatabaseGetRequiredSystemVersion(std::addressof(m_srv), out_version.GetPointer(), Convert(key))); + } + + Result GetPatchContentMetaId(sf::Out<u64> out_patch_id, const ContentMetaKey &key) { + R_RETURN(ncmContentMetaDatabaseGetPatchContentMetaId(std::addressof(m_srv), out_patch_id.GetPointer(), Convert(key))); + } + + Result DisableForcibly() { + R_RETURN(ncmContentMetaDatabaseDisableForcibly(std::addressof(m_srv))); + } + + Result LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) { + R_RETURN(ncmContentMetaDatabaseLookupOrphanContent(std::addressof(m_srv), out_orphaned.GetPointer(), Convert(content_ids.GetPointer()), std::min(out_orphaned.GetSize(), content_ids.GetSize()))); + } + + Result Commit() { + R_RETURN(ncmContentMetaDatabaseCommit(std::addressof(m_srv))); + } + + Result HasContent(sf::Out<bool> out, const ContentMetaKey &key, const ContentId &content_id) { + R_RETURN(ncmContentMetaDatabaseHasContent(std::addressof(m_srv), out.GetPointer(), Convert(key), Convert(content_id))); + } + + Result ListContentMetaInfo(sf::Out<s32> out_entries_written, const sf::OutArray<ContentMetaInfo> &out_meta_info, const ContentMetaKey &key, s32 offset) { + R_RETURN(ncmContentMetaDatabaseListContentMetaInfo(std::addressof(m_srv), out_entries_written.GetPointer(), out_meta_info.GetPointer(), out_meta_info.GetSize(), Convert(key), offset)); + } + + Result GetAttributes(sf::Out<u8> out_attributes, const ContentMetaKey &key) { + static_assert(sizeof(ContentMetaAttribute) == sizeof(u8)); + R_RETURN(ncmContentMetaDatabaseGetAttributes(std::addressof(m_srv), Convert(key), out_attributes.GetPointer())); + } + + Result GetRequiredApplicationVersion(sf::Out<u32> out_version, const ContentMetaKey &key) { + R_RETURN(ncmContentMetaDatabaseGetRequiredApplicationVersion(std::addressof(m_srv), out_version.GetPointer(), Convert(key))); + } + + Result GetContentIdByTypeAndIdOffset(sf::Out<ContentId> out_content_id, const ContentMetaKey &key, ContentType type, u8 id_offset) { + R_RETURN(ncmContentMetaDatabaseGetContentIdByTypeAndIdOffset(std::addressof(m_srv), Convert(out_content_id.GetPointer()), Convert(key), static_cast<::NcmContentType>(type), id_offset)); + } + + Result GetCount(sf::Out<u32> out_count) { + /* TODO: libnx bindings */ + AMS_UNUSED(out_count); + AMS_ABORT(); + } + + Result GetOwnerApplicationId(sf::Out<ApplicationId> out_id, const ContentMetaKey &key) { + /* TODO: libnx bindings */ + AMS_UNUSED(out_id, key); + AMS_ABORT(); + } + + Result GetContentAccessibilities(sf::Out<u8> out_accessibilities, const ContentMetaKey &key) { + /* TODO: libnx bindings */ + AMS_UNUSED(out_accessibilities, key); + AMS_ABORT(); + } + + Result GetContentInfoByType(sf::Out<ContentInfo> out_content_info, const ContentMetaKey &key, ContentType type) { + /* TODO: libnx bindings */ + AMS_UNUSED(out_content_info, key, type); + AMS_ABORT(); + } + + Result GetContentInfoByTypeAndIdOffset(sf::Out<ContentInfo> out_content_info, const ContentMetaKey &key, ContentType type, u8 id_offset) { + /* TODO: libnx bindings */ + AMS_UNUSED(out_content_info, key, type, id_offset); + AMS_ABORT(); + } + + Result GetPlatform(sf::Out<ncm::ContentMetaPlatform> out, const ContentMetaKey &key) { + static_assert(sizeof(ncm::ContentMetaPlatform) == sizeof(u8)); + R_RETURN(ncmContentMetaDatabaseGetPlatform(std::addressof(m_srv), reinterpret_cast<u8 *>(out.GetPointer()), Convert(key))); + } + + Result HasAttributes(sf::Out<u8> out, u8 attr_mask) { + /* TODO: libnx bindings */ + AMS_UNUSED(out, attr_mask); + AMS_ABORT(); + } + }; + static_assert(ncm::IsIContentMetaDatabase<RemoteContentMetaDatabaseImpl>); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_remote_content_storage_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_remote_content_storage_impl.hpp new file mode 100644 index 00000000..87c88bbd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_remote_content_storage_impl.hpp @@ -0,0 +1,236 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ncm { + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemoteContentStorageImpl { + private: + ::NcmContentStorage m_srv; + public: + RemoteContentStorageImpl(::NcmContentStorage &cs) : m_srv(cs) { /* ... */ } + + ~RemoteContentStorageImpl() { ::ncmContentStorageClose(std::addressof(m_srv)); } + private: + ALWAYS_INLINE ::NcmPlaceHolderId *Convert(PlaceHolderId *p) { + static_assert(sizeof(PlaceHolderId) == sizeof(::NcmPlaceHolderId)); + return reinterpret_cast<::NcmPlaceHolderId *>(p); + } + + ALWAYS_INLINE ::NcmPlaceHolderId *Convert(PlaceHolderId &p) { + static_assert(sizeof(PlaceHolderId) == sizeof(::NcmPlaceHolderId)); + return reinterpret_cast<::NcmPlaceHolderId *>(std::addressof(p)); + } + + ALWAYS_INLINE ::NcmContentId *Convert(ContentId *c) { + static_assert(sizeof(ContentId) == sizeof(::NcmContentId)); + return reinterpret_cast<::NcmContentId *>(c); + } + + ALWAYS_INLINE ::NcmContentId *Convert(ContentId &c) { + static_assert(sizeof(ContentId) == sizeof(::NcmContentId)); + return reinterpret_cast<::NcmContentId *>(std::addressof(c)); + } + + ALWAYS_INLINE const ::NcmContentId *Convert(const ContentId *c) { + static_assert(sizeof(ContentId) == sizeof(::NcmContentId)); + return reinterpret_cast<const ::NcmContentId *>(c); + } + + ALWAYS_INLINE const ::NcmContentId *Convert(const ContentId &c) { + static_assert(sizeof(ContentId) == sizeof(::NcmContentId)); + return reinterpret_cast<const ::NcmContentId *>(std::addressof(c)); + } + + ALWAYS_INLINE ::FsContentAttributes Convert(fs::ContentAttributes attr) { + return static_cast<::FsContentAttributes>(static_cast<u8>(attr)); + } + public: + Result GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) { + R_RETURN(ncmContentStorageGeneratePlaceHolderId(std::addressof(m_srv), Convert(out.GetPointer()))); + } + + Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, s64 size) { + R_RETURN(ncmContentStorageCreatePlaceHolder(std::addressof(m_srv), Convert(content_id), Convert(placeholder_id), size)); + } + + Result DeletePlaceHolder(PlaceHolderId placeholder_id) { + R_RETURN(ncmContentStorageDeletePlaceHolder(std::addressof(m_srv), Convert(placeholder_id))); + } + + Result HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) { + R_RETURN(ncmContentStorageHasPlaceHolder(std::addressof(m_srv), out.GetPointer(), Convert(placeholder_id))); + } + + Result WritePlaceHolder(PlaceHolderId placeholder_id, s64 offset, const sf::InBuffer &data) { + R_RETURN(ncmContentStorageWritePlaceHolder(std::addressof(m_srv), Convert(placeholder_id), offset, data.GetPointer(), data.GetSize())); + } + + Result Register(PlaceHolderId placeholder_id, ContentId content_id) { + R_RETURN(ncmContentStorageRegister(std::addressof(m_srv), Convert(content_id), Convert(placeholder_id))); + } + + Result Delete(ContentId content_id) { + R_RETURN(ncmContentStorageDelete(std::addressof(m_srv), Convert(content_id))); + } + + Result Has(sf::Out<bool> out, ContentId content_id) { + R_RETURN(ncmContentStorageHas(std::addressof(m_srv), out.GetPointer(), Convert(content_id))); + } + + Result GetPath(sf::Out<Path> out, ContentId content_id) { + R_RETURN(ncmContentStorageGetPath(std::addressof(m_srv), out.GetPointer()->str, sizeof(out.GetPointer()->str), Convert(content_id))); + } + + Result GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id) { + R_RETURN(ncmContentStorageGetPlaceHolderPath(std::addressof(m_srv), out.GetPointer()->str, sizeof(out.GetPointer()->str), Convert(placeholder_id))); + } + + Result CleanupAllPlaceHolder() { + R_RETURN(ncmContentStorageCleanupAllPlaceHolder(std::addressof(m_srv))); + } + + Result ListPlaceHolder(sf::Out<s32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) { + R_RETURN(ncmContentStorageListPlaceHolder(std::addressof(m_srv), Convert(out_buf.GetPointer()), out_buf.GetSize(), out_count.GetPointer())); + } + + Result GetContentCount(sf::Out<s32> out_count) { + R_RETURN(ncmContentStorageGetContentCount(std::addressof(m_srv), out_count.GetPointer())); + } + + Result ListContentId(sf::Out<s32> out_count, const sf::OutArray<ContentId> &out_buf, s32 offset) { + R_RETURN(ncmContentStorageListContentId(std::addressof(m_srv), Convert(out_buf.GetPointer()), out_buf.GetSize(), out_count.GetPointer(), offset)); + } + + Result GetSizeFromContentId(sf::Out<s64> out_size, ContentId content_id) { + R_RETURN(ncmContentStorageGetSizeFromContentId(std::addressof(m_srv), out_size.GetPointer(), Convert(content_id))); + } + + Result DisableForcibly() { + R_RETURN(ncmContentStorageDisableForcibly(std::addressof(m_srv))); + } + + Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) { + R_RETURN(ncmContentStorageRevertToPlaceHolder(std::addressof(m_srv), Convert(placeholder_id), Convert(old_content_id), Convert(new_content_id))); + } + + Result SetPlaceHolderSize(PlaceHolderId placeholder_id, s64 size) { + R_RETURN(ncmContentStorageSetPlaceHolderSize(std::addressof(m_srv), Convert(placeholder_id), size)); + } + + Result ReadContentIdFile(const sf::OutBuffer &buf, ContentId content_id, s64 offset) { + R_RETURN(ncmContentStorageReadContentIdFile(std::addressof(m_srv), buf.GetPointer(), buf.GetSize(), Convert(content_id), offset)); + } + + Result GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id) { + ::NcmRightsId rights_id; + R_TRY(ncmContentStorageGetRightsIdFromPlaceHolderId(std::addressof(m_srv), std::addressof(rights_id), Convert(placeholder_id), Convert(fs::ContentAttributes_None))); + + static_assert(sizeof(*out_rights_id.GetPointer()) <= sizeof(rights_id)); + std::memcpy(out_rights_id.GetPointer(), std::addressof(rights_id), sizeof(*out_rights_id.GetPointer())); + R_SUCCEED(); + } + + Result GetRightsIdFromPlaceHolderIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) { + R_RETURN(this->GetRightsIdFromPlaceHolderId(out_rights_id, placeholder_id, fs::ContentAttributes_None)); + } + + Result GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, fs::ContentAttributes attr) { + ::NcmRightsId rights_id; + R_TRY(ncmContentStorageGetRightsIdFromPlaceHolderId(std::addressof(m_srv), std::addressof(rights_id), Convert(placeholder_id), Convert(attr))); + + static_assert(sizeof(*out_rights_id.GetPointer()) <= sizeof(rights_id)); + std::memcpy(out_rights_id.GetPointer(), std::addressof(rights_id), sizeof(*out_rights_id.GetPointer())); + R_SUCCEED(); + } + + Result GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) { + ::NcmRightsId rights_id; + R_TRY(ncmContentStorageGetRightsIdFromContentId(std::addressof(m_srv), std::addressof(rights_id), Convert(content_id), Convert(fs::ContentAttributes_None))); + + static_assert(sizeof(*out_rights_id.GetPointer()) <= sizeof(rights_id)); + std::memcpy(out_rights_id.GetPointer(), std::addressof(rights_id), sizeof(*out_rights_id.GetPointer())); + R_SUCCEED(); + } + + Result GetRightsIdFromContentIdDeprecated2(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id) { + R_RETURN(this->GetRightsIdFromContentId(out_rights_id, content_id, fs::ContentAttributes_None)); + } + + Result GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id, fs::ContentAttributes attr) { + ::NcmRightsId rights_id; + R_TRY(ncmContentStorageGetRightsIdFromContentId(std::addressof(m_srv), std::addressof(rights_id), Convert(content_id), Convert(attr))); + + static_assert(sizeof(*out_rights_id.GetPointer()) <= sizeof(rights_id)); + std::memcpy(out_rights_id.GetPointer(), std::addressof(rights_id), sizeof(*out_rights_id.GetPointer())); + R_SUCCEED(); + } + + Result WriteContentForDebug(ContentId content_id, s64 offset, const sf::InBuffer &data) { + R_RETURN(ncmContentStorageWriteContentForDebug(std::addressof(m_srv), Convert(content_id), offset, data.GetPointer(), data.GetSize())); + } + + Result GetFreeSpaceSize(sf::Out<s64> out_size) { + R_RETURN(ncmContentStorageGetFreeSpaceSize(std::addressof(m_srv), out_size.GetPointer())); + } + + Result GetTotalSpaceSize(sf::Out<s64> out_size) { + R_RETURN(ncmContentStorageGetTotalSpaceSize(std::addressof(m_srv), out_size.GetPointer())); + } + + Result FlushPlaceHolder() { + R_RETURN(ncmContentStorageFlushPlaceHolder(std::addressof(m_srv))); + } + + Result GetSizeFromPlaceHolderId(sf::Out<s64> out_size, PlaceHolderId placeholder_id) { + R_RETURN(ncmContentStorageGetSizeFromPlaceHolderId(std::addressof(m_srv), out_size.GetPointer(), Convert(placeholder_id))); + } + + Result RepairInvalidFileAttribute() { + R_RETURN(ncmContentStorageRepairInvalidFileAttribute(std::addressof(m_srv))); + } + + Result GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id, fs::ContentAttributes attr) { + static_assert(sizeof(::NcmRightsId) == sizeof(ncm::RightsId)); + ::NcmRightsId *out = reinterpret_cast<::NcmRightsId *>(out_rights_id.GetPointer()); + R_RETURN(ncmContentStorageGetRightsIdFromPlaceHolderIdWithCache(std::addressof(m_srv), out, Convert(placeholder_id), Convert(cache_content_id), Convert(attr))); + } + + Result RegisterPath(const ContentId &content_id, const Path &path) { + R_RETURN(ncmContentStorageRegisterPath(std::addressof(m_srv), Convert(content_id), path.str)); + } + + Result ClearRegisteredPath() { + R_RETURN(ncmContentStorageClearRegisteredPath(std::addressof(m_srv))); + } + + Result GetProgramId(sf::Out<ncm::ProgramId> out, ContentId content_id, fs::ContentAttributes attr) { + static_assert(sizeof(ncm::ProgramId) == sizeof(u64)); + R_RETURN(ncmContentStorageGetProgramId(std::addressof(m_srv), reinterpret_cast<u64 *>(out.GetPointer()), Convert(content_id), Convert(attr))); + } + + /* 16.0.0 Alignment change hacks. */ + Result CreatePlaceHolder_AtmosphereAlignmentFix(ContentId content_id, PlaceHolderId placeholder_id, s64 size) { R_RETURN(this->CreatePlaceHolder(placeholder_id, content_id, size)); } + Result Register_AtmosphereAlignmentFix(ContentId content_id, PlaceHolderId placeholder_id) { R_RETURN(this->Register(placeholder_id, content_id)); } + Result RevertToPlaceHolder_AtmosphereAlignmentFix(ncm::ContentId old_content_id, ncm::ContentId new_content_id, ncm::PlaceHolderId placeholder_id) { R_RETURN(this->RevertToPlaceHolder(placeholder_id, old_content_id, new_content_id)); } + Result GetRightsIdFromPlaceHolderIdWithCacheDeprecated(sf::Out<ncm::RightsId> out_rights_id, ContentId cache_content_id, PlaceHolderId placeholder_id) { R_RETURN(this->GetRightsIdFromPlaceHolderIdWithCache(out_rights_id, placeholder_id, cache_content_id, fs::ContentAttributes_None)); } + }; + static_assert(ncm::IsIContentStorage<RemoteContentStorageImpl>); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_storage_utils.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_storage_utils.cpp new file mode 100644 index 00000000..0435b6a9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_storage_utils.cpp @@ -0,0 +1,105 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ncm { + + Result SelectDownloadableStorage(StorageId *out_storage_id, StorageId storage_id, s64 required_size) { + auto list = GetStorageList(storage_id); + for (s32 i = 0; i < list.Count(); i++) { + auto candidate = list[i]; + + /* Open the content meta database. NOTE: This is unused. */ + ContentMetaDatabase content_meta_database; + if (R_FAILED(ncm::OpenContentMetaDatabase(std::addressof(content_meta_database), candidate))) { + continue; + } + + /* Open the content storage. */ + ContentStorage content_storage; + if (R_FAILED(ncm::OpenContentStorage(std::addressof(content_storage), candidate))) { + continue; + } + + /* Get the free space on this storage. */ + s64 free_space_size; + R_TRY(content_storage.GetFreeSpaceSize(std::addressof(free_space_size))); + + /* There must be more free space than is required. */ + if (free_space_size <= required_size) { + continue; + } + + /* Output the storage id. */ + *out_storage_id = storage_id; + R_SUCCEED(); + } + + R_THROW(ncm::ResultNotEnoughInstallSpace()); + } + + Result SelectPatchStorage(StorageId *out_storage_id, StorageId storage_id, PatchId patch_id) { + auto list = GetStorageList(storage_id); + u32 version = 0; + *out_storage_id = storage_id; + + for (s32 i = 0; i < list.Count(); i++) { + auto candidate = list[i]; + + /* Open the content meta database. */ + ContentMetaDatabase content_meta_database; + if (R_FAILED(ncm::OpenContentMetaDatabase(std::addressof(content_meta_database), candidate))) { + continue; + } + + /* Get the latest key. */ + ContentMetaKey key; + R_TRY_CATCH(content_meta_database.GetLatest(std::addressof(key), patch_id.value)) { + R_CATCH(ncm::ResultContentMetaNotFound) { continue; } + } R_END_TRY_CATCH; + + if (key.version > version) { + version = key.version; + *out_storage_id = candidate; + } + } + + R_SUCCEED(); + } + + const char *GetStorageIdString(StorageId storage_id) { + switch (storage_id) { + case StorageId::None: return "None"; + case StorageId::Host: return "Host"; + case StorageId::GameCard: return "GameCard"; + case StorageId::BuiltInSystem: return "BuiltInSystem"; + case StorageId::BuiltInUser: return "BuiltInUser"; + default: return "(unknown)"; + } + } + + const char *GetStorageIdStringForPlayReport(StorageId storage_id) { + switch (storage_id) { + case StorageId::None: return "None"; + case StorageId::Host: return "Host"; + case StorageId::Card: return "Card"; + case StorageId::BuildInSystem: return "BuildInSystem"; + case StorageId::BuildInUser: return "BuildInUser"; + default: return "(unknown)"; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_submission_package_install_task.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_submission_package_install_task.cpp new file mode 100644 index 00000000..6e1be6f2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ncm/ncm_submission_package_install_task.cpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ncm_fs_utils.hpp" + +namespace ams::ncm { + + class SubmissionPackageInstallTask::Impl { + private: + fs::FileHandleStorage m_storage; + util::optional<impl::MountName> m_mount_name; + public: + explicit Impl(fs::FileHandle file) : m_storage(file), m_mount_name(util::nullopt) { /* ... */ } + + ~Impl() { + if (m_mount_name) { + fs::fsa::Unregister(m_mount_name->str); + } + } + + Result Initialize() { + AMS_ASSERT(!m_mount_name); + + /* Allocate a partition file system. */ + auto partition_file_system = std::make_unique<fssystem::PartitionFileSystem>(); + R_UNLESS(partition_file_system != nullptr, ncm::ResultAllocationFailed()); + + /* Initialize the partition file system. */ + R_TRY(partition_file_system->Initialize(std::addressof(m_storage))); + + /* Create a mount name and register the file system. */ + auto mount_name = impl::CreateUniqueMountName(); + R_TRY(fs::fsa::Register(mount_name.str, std::move(partition_file_system))); + + /* Initialize members. */ + m_mount_name = mount_name; + R_SUCCEED(); + } + + const impl::MountName &GetMountName() const { + return *m_mount_name; + } + }; + + SubmissionPackageInstallTask::SubmissionPackageInstallTask() : m_impl(nullptr) { /* ... */ } + SubmissionPackageInstallTask::~SubmissionPackageInstallTask() { /* ... */ } + + Result SubmissionPackageInstallTask::Initialize(fs::FileHandle file, StorageId storage_id, void *buffer, size_t buffer_size, bool ignore_ticket) { + AMS_ASSERT(!m_impl); + + /* Allocate impl. */ + m_impl.reset(new (std::nothrow) Impl(file)); + R_UNLESS(m_impl != nullptr, ncm::ResultAllocationFailed()); + + /* Initialize impl. */ + R_TRY(m_impl->Initialize()); + + /* Initialize parent. N doesn't check the result. */ + PackageInstallTask::Initialize(impl::GetRootDirectoryPath(m_impl->GetMountName()).str, storage_id, buffer, buffer_size, ignore_ticket); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/nim/nim_network_install_manager_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/nim/nim_network_install_manager_api.cpp new file mode 100644 index 00000000..c7e3ac59 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/nim/nim_network_install_manager_api.cpp @@ -0,0 +1,74 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::nim { + + #if defined(ATMOSPHERE_OS_HORIZON) + namespace { + + constinit bool g_initialized; + + } + + /* Management. */ + void InitializeForNetworkInstallManager() { + AMS_ASSERT(!g_initialized); + R_ABORT_UNLESS(nimInitialize()); + g_initialized = true; + } + + void FinalizeForNetworkInstallManager() { + AMS_ASSERT(g_initialized); + nimExit(); + g_initialized = false; + } + + /* Service API. */ + Result DestroySystemUpdateTask(const SystemUpdateTaskId &id) { + static_assert(sizeof(SystemUpdateTaskId) == sizeof(::NimSystemUpdateTaskId)); + R_RETURN(nimDestroySystemUpdateTask(reinterpret_cast<const ::NimSystemUpdateTaskId *>(std::addressof(id)))); + } + + s32 ListSystemUpdateTask(SystemUpdateTaskId *out_list, size_t out_list_size) { + static_assert(sizeof(SystemUpdateTaskId) == sizeof(::NimSystemUpdateTaskId)); + + s32 count; + R_ABORT_UNLESS(nimListSystemUpdateTask(std::addressof(count), reinterpret_cast<::NimSystemUpdateTaskId *>(out_list), out_list_size)); + + return count; + } + #else + /* Management. */ + void InitializeForNetworkInstallManager() { + AMS_ABORT("TODO: nim logic on non-libnx platform"); + } + + void FinalizeForNetworkInstallManager() { + AMS_ABORT("TODO: nim logic on non-libnx platform"); + } + + /* Service API. */ + Result DestroySystemUpdateTask(const SystemUpdateTaskId &) { + AMS_ABORT("TODO: nim logic on non-libnx platform"); + } + + s32 ListSystemUpdateTask(SystemUpdateTaskId *, size_t) { + AMS_ABORT("TODO: nim logic on non-libnx platform"); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/nsd/impl/device/nsd_device.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/nsd/impl/device/nsd_device.cpp new file mode 100644 index 00000000..bcb62d1d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/nsd/impl/device/nsd_device.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::nsd::impl::device { + + namespace { + + constexpr const char SettingsName[] = "nsd"; + constexpr const char SettingsKey[] = "environment_identifier"; + + constinit os::SdkMutex g_environment_identifier_lock; + constinit EnvironmentIdentifier g_environment_identifier = {}; + constinit bool g_determined_environment_identifier = false; + + } + + const EnvironmentIdentifier &GetEnvironmentIdentifierFromSettings() { + if (AMS_UNLIKELY(!g_determined_environment_identifier)) { + std::scoped_lock lk(g_environment_identifier_lock); + + if (AMS_LIKELY(!g_determined_environment_identifier)) { + /* Check size. */ + AMS_ABORT_UNLESS(settings::fwdbg::GetSettingsItemValueSize(SettingsName, SettingsKey) != 0); + + /* Get value. */ + const size_t size = settings::fwdbg::GetSettingsItemValue(g_environment_identifier.value, EnvironmentIdentifier::Size, SettingsName, SettingsKey); + AMS_ABORT_UNLESS(size < EnvironmentIdentifier::Size); + AMS_ABORT_UNLESS(g_environment_identifier == EnvironmentIdentifierOfProductDevice || g_environment_identifier == EnvironmentIdentifierOfNotProductDevice); + + g_determined_environment_identifier = true; + } + } + + return g_environment_identifier; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_address_space_allocator.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_address_space_allocator.cpp new file mode 100644 index 00000000..a5f7e2ee --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_address_space_allocator.cpp @@ -0,0 +1,147 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_address_space_allocator.hpp" +#include "os_rng_manager.hpp" + +namespace ams::os::impl { + + namespace { + + constexpr inline u64 MaxProbabilityVariationInverseShift = 4; + + } + + u64 AddressSpaceAllocatorDefaultGenerateRandom(u64 max) { + /* Check that max is in range. */ + AMS_ASSERT(max + 1 <= (UINT64_C(1) << (BITSIZEOF(u64) - MaxProbabilityVariationInverseShift))); + + /* Generate random u64. */ + const u64 rand = GetRngManager().GenerateRandomU64(); + + /* Coerce into range. */ + AMS_ASSERT(max < std::numeric_limits<u64>::max()); + return rand % (max + 1); + } + + template<std::unsigned_integral AddressType, std::unsigned_integral SizeType> + AddressSpaceAllocatorBase<AddressType, SizeType>::AddressSpaceAllocatorBase(u64 start_address, u64 end_address, SizeType guard_size, const AddressSpaceAllocatorForbiddenRegion *forbidden_regions, size_t num_forbidden_regions) : m_critical_section(), m_forbidden_region_count(0) { + /* Check pre-conditions. */ + AMS_ASSERT(start_address >= guard_size); + AMS_ASSERT(end_address + guard_size >= end_address); + + /* Set member variables. */ + m_guard_page_count = util::DivideUp(guard_size, MemoryPageSize); + m_start_page = start_address / MemoryPageSize; + m_end_page = (end_address / MemoryPageSize) + m_guard_page_count; + + /* Check forbidden region count. */ + AMS_ASSERT(num_forbidden_regions <= MaxForbiddenRegions); + + /* Set forbidden regions. */ + for (size_t i = 0; i < num_forbidden_regions; ++i) { + if (const auto ®ion = forbidden_regions[i]; region.size > 0) { + /* Check region is valid. */ + AMS_ASSERT(util::IsAligned(region.address, MemoryPageSize)); + AMS_ASSERT(util::IsAligned(region.size, MemoryPageSize)); + AMS_ASSERT((region.address / MemoryPageSize) >= m_guard_page_count); + AMS_ASSERT(region.address < region.address + region.size); + + /* Set region. */ + const auto idx = m_forbidden_region_count++; + m_forbidden_region_start_pages[idx] = (region.address / MemoryPageSize) - m_guard_page_count; + m_forbidden_region_end_pages[idx] = ((region.address + region.size) / MemoryPageSize) + m_guard_page_count; + } + } + } + + template<std::unsigned_integral AddressType, std::unsigned_integral SizeType> + bool AddressSpaceAllocatorBase<AddressType, SizeType>::CheckGuardSpace(AddressType address, SizeType size, SizeType guard_size) { + return this->CheckFreeSpace(address - guard_size, guard_size) && this->CheckFreeSpace(address + size, guard_size); + } + + template<std::unsigned_integral AddressType, std::unsigned_integral SizeType> + bool AddressSpaceAllocatorBase<AddressType, SizeType>::GetNextNonOverlappedNodeUnsafe(AddressType page, SizeType page_count) { + /* Check pre-conditions. */ + AMS_ASSERT(page < page + page_count); + + return this->CheckFreeSpace(page * MemoryPageSize, page_count * MemoryPageSize); + } + + template<std::unsigned_integral AddressType, std::unsigned_integral SizeType> + AddressType AddressSpaceAllocatorBase<AddressType, SizeType>::AllocateSpace(SizeType size, SizeType align, SizeType align_offset, AddressSpaceGenerateRandomFunction generate_random) { + /* Check pre-conditions. */ + AMS_ASSERT(align > 0); + AMS_ASSERT((align_offset & ~(align - 1)) == 0); + AMS_ASSERT(util::IsAligned(align_offset, MemoryPageSize)); + AMS_ASSERT(util::IsAligned(align, MemoryPageSize)); + + /* Determine the page count. */ + const SizeType page_count = util::DivideUp(size, MemoryPageSize); + + /* Determine alignment page counts. */ + const SizeType align_offset_page_count = align_offset / MemoryPageSize; + const SizeType align_page_count = align / MemoryPageSize; + + /* Check page counts. */ + if (page_count + align_offset_page_count > m_end_page - m_guard_page_count) { + return 0; + } + + /* Determine the range to look in. */ + const AddressType rand_start = (align_offset_page_count <= m_start_page + m_guard_page_count) ? util::DivideUp(m_start_page + m_guard_page_count - align_offset_page_count, align_page_count) : 0; + const AddressType rand_end = (m_end_page - page_count - align_offset_page_count - m_guard_page_count) / align_page_count; + + /* Check that we can find a space. */ + if (rand_start > rand_end) { + return 0; + } + + /* Try to find a space up to 512 times. */ + for (size_t i = 0; i < 512; ++i) { + /* Acquire exclusive access before doing calculations. */ + std::scoped_lock lk(m_critical_section); + + /* Determine a random page. */ + const u64 random = generate_random(rand_end - rand_start); + + const AddressType target = ((random + rand_start) * align_page_count) + align_offset_page_count; + AMS_ASSERT(m_start_page <= target - m_guard_page_count && target + page_count + m_guard_page_count <= m_end_page); + + /* Check that the page is not forbidden. */ + bool forbidden = false; + for (size_t j = 0; j < m_forbidden_region_count; ++j) { + if (m_forbidden_region_start_pages[j] < target + page_count && target < m_forbidden_region_end_pages[j]) { + forbidden = true; + break; + } + } + + /* If the page is valid, use it. */ + if (!forbidden && this->GetNextNonOverlappedNodeUnsafe(target - m_guard_page_count, page_count + 2 * m_guard_page_count)) { + return target * MemoryPageSize; + } + } + + /* We failed to find space. */ + return 0; + } + + /* Instantiate template. */ + /* TODO: instantiate <u64, u64> on 32-bit? */ + template class AddressSpaceAllocatorBase<uintptr_t, size_t>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_address_space_allocator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_address_space_allocator.hpp new file mode 100644 index 00000000..d0a9f77d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_address_space_allocator.hpp @@ -0,0 +1,65 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_address_space_allocator_forbidden_region.hpp" + +namespace ams::os::impl { + + enum AddressAllocationResult { + AddressAllocationResult_Success, + AddressAllocationResult_OutOfMemory, + AddressAllocationResult_OutOfSpace, + }; + + template<std::unsigned_integral AddressType_, std::unsigned_integral SizeType_> + class AddressSpaceAllocatorBase { + NON_COPYABLE(AddressSpaceAllocatorBase); + NON_MOVEABLE(AddressSpaceAllocatorBase); + public: + using AddressType = AddressType_; + using SizeType = SizeType_; + private: + static constexpr size_t MaxForbiddenRegions = 2; + private: + InternalCriticalSection m_critical_section; + AddressType m_start_page; + AddressType m_end_page; + SizeType m_guard_page_count; + AddressType m_forbidden_region_start_pages[MaxForbiddenRegions]; + AddressType m_forbidden_region_end_pages[MaxForbiddenRegions]; + size_t m_forbidden_region_count; + public: + AddressSpaceAllocatorBase(u64 start_address, u64 end_address, SizeType guard_size, const AddressSpaceAllocatorForbiddenRegion *forbidden_regions, size_t num_forbidden_regions); + + AddressType AllocateSpace(SizeType size, SizeType align, SizeType align_offset, AddressSpaceGenerateRandomFunction generate_random); + + bool CheckGuardSpace(AddressType address, SizeType size, SizeType guard_size); + private: + bool GetNextNonOverlappedNodeUnsafe(AddressType page, SizeType page_count); + public: + virtual bool CheckFreeSpace(AddressType address, SizeType size) = 0; + }; + + u64 AddressSpaceAllocatorDefaultGenerateRandom(u64 max); + +} + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "os_address_space_allocator_impl.os.horizon.hpp" +#else + #include "os_address_space_allocator_impl.generic.hpp" +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_address_space_allocator_forbidden_region.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_address_space_allocator_forbidden_region.hpp new file mode 100644 index 00000000..bc6dea80 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_address_space_allocator_forbidden_region.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + struct AddressSpaceAllocatorForbiddenRegion { + u64 address; + u64 size; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_address_space_allocator_impl.generic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_address_space_allocator_impl.generic.hpp new file mode 100644 index 00000000..abcd1de5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_address_space_allocator_impl.generic.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class AddressSpaceAllocator final : public AddressSpaceAllocatorBase<uintptr_t, size_t> { + private: + using Base = AddressSpaceAllocatorBase<uintptr_t, size_t>; + public: + using Base::Base; + public: + virtual bool CheckFreeSpace(uintptr_t address, size_t size) override { + AMS_UNUSED(address, size); + return true; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_address_space_allocator_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_address_space_allocator_impl.os.horizon.hpp new file mode 100644 index 00000000..d6981188 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_address_space_allocator_impl.os.horizon.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class AddressSpaceAllocator final : public AddressSpaceAllocatorBase<uintptr_t, size_t> { + private: + using Base = AddressSpaceAllocatorBase<uintptr_t, size_t>; + public: + using Base::Base; + public: + virtual bool CheckFreeSpace(AddressType address, SizeType size) override { + /* Query the memory. */ + svc::MemoryInfo memory_info; + svc::PageInfo page_info; + const auto result = svc::QueryMemory(std::addressof(memory_info), std::addressof(page_info), address); + R_ASSERT(result); + AMS_UNUSED(result); + + return memory_info.state == svc::MemoryState_Free && address + size <= memory_info.base_address + memory_info.size; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_aslr_space_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_aslr_space_manager.hpp new file mode 100644 index 00000000..a2a8b520 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_aslr_space_manager.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_aslr_space_manager_types.hpp" +#include "os_resource_manager.hpp" + +namespace ams::os::impl { + + ALWAYS_INLINE AslrSpaceManager &GetAslrSpaceManager() { + return GetResourceManager().GetAslrSpaceManager(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_aslr_space_manager_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_aslr_space_manager_impl.os.horizon.hpp new file mode 100644 index 00000000..0e430549 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_aslr_space_manager_impl.os.horizon.hpp @@ -0,0 +1,95 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_address_space_allocator_forbidden_region.hpp" + +namespace ams::os::impl { + + class AslrSpaceManagerHorizonImpl { + NON_COPYABLE(AslrSpaceManagerHorizonImpl); + NON_MOVEABLE(AslrSpaceManagerHorizonImpl); + private: + static constexpr u64 AslrBase32Bit = 0x0000200000ul; + static constexpr u64 AslrSize32Bit = 0x003FE00000ul; + static constexpr u64 AslrBase64BitDeprecated = 0x0008000000ul; + static constexpr u64 AslrSize64BitDeprecated = 0x0078000000ul; + static constexpr u64 AslrBase64Bit = 0x0008000000ul; + static constexpr u64 AslrSize64Bit = 0x7FF8000000ul; + + static constexpr size_t ForbiddenRegionCount = 2; + private: + static u64 GetAslrInfo(os::NativeHandle process_handle, svc::InfoType type) { + u64 value; + R_ABORT_UNLESS(svc::GetInfo(std::addressof(value), type, process_handle, 0)); + + static_assert(std::same_as<size_t, uintptr_t>); + AMS_ASSERT(value <= std::numeric_limits<size_t>::max()); + + return static_cast<u64>(value & std::numeric_limits<size_t>::max()); + } + private: + AddressSpaceAllocatorForbiddenRegion m_forbidden_regions[ForbiddenRegionCount]; + public: + AslrSpaceManagerHorizonImpl() { + this->InitializeForbiddenRegions(svc::PseudoHandle::CurrentProcess); + } + + AslrSpaceManagerHorizonImpl(os::NativeHandle process_handle) { + this->InitializeForbiddenRegions(process_handle); + } + + const AddressSpaceAllocatorForbiddenRegion *GetForbiddenRegions() const { + return m_forbidden_regions; + } + + static size_t GetForbiddenRegionCount() { + return ForbiddenRegionCount; + } + + static u64 GetHeapSpaceBeginAddress(os::NativeHandle process_handle = svc::PseudoHandle::CurrentProcess) { + return GetAslrInfo(process_handle, svc::InfoType_HeapRegionAddress); + } + + static u64 GetHeapSpaceSize(os::NativeHandle process_handle = svc::PseudoHandle::CurrentProcess) { + return GetAslrInfo(process_handle, svc::InfoType_HeapRegionSize); + } + + static u64 GetAliasSpaceBeginAddress(os::NativeHandle process_handle = svc::PseudoHandle::CurrentProcess) { + return GetAslrInfo(process_handle, svc::InfoType_AliasRegionAddress); + } + + static u64 GetAliasSpaceSize(os::NativeHandle process_handle = svc::PseudoHandle::CurrentProcess) { + return GetAslrInfo(process_handle, svc::InfoType_AliasRegionSize); + } + + static u64 GetAslrSpaceBeginAddress(os::NativeHandle process_handle = svc::PseudoHandle::CurrentProcess) { + return GetAslrInfo(process_handle, svc::InfoType_AslrRegionAddress); + } + + static u64 GetAslrSpaceEndAddress(os::NativeHandle process_handle = svc::PseudoHandle::CurrentProcess) { + return GetAslrInfo(process_handle, svc::InfoType_AslrRegionAddress) + GetAslrInfo(process_handle, svc::InfoType_AslrRegionSize); + } + private: + void InitializeForbiddenRegions(os::NativeHandle process_handle) { + m_forbidden_regions[0] = { .address = GetHeapSpaceBeginAddress(process_handle), .size = GetHeapSpaceSize(process_handle) }; + m_forbidden_regions[1] = { .address = GetAliasSpaceBeginAddress(process_handle), .size = GetAliasSpaceSize(process_handle) }; + } + }; + + using AslrSpaceManagerImpl = AslrSpaceManagerHorizonImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_aslr_space_manager_impl.os.linux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_aslr_space_manager_impl.os.linux.hpp new file mode 100644 index 00000000..20bf70cb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_aslr_space_manager_impl.os.linux.hpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_address_space_allocator_forbidden_region.hpp" + +namespace ams::os::impl { + + class AslrSpaceManagerLinuxImpl { + NON_COPYABLE(AslrSpaceManagerLinuxImpl); + NON_MOVEABLE(AslrSpaceManagerLinuxImpl); + public: + constexpr AslrSpaceManagerLinuxImpl() = default; + + static constexpr ALWAYS_INLINE const AddressSpaceAllocatorForbiddenRegion *GetForbiddenRegions() { + return nullptr; + } + + static constexpr ALWAYS_INLINE size_t GetForbiddenRegionCount() { + return 0; + } + + static constexpr ALWAYS_INLINE u64 GetHeapSpaceBeginAddress() { + return 8_GB; + } + + static constexpr ALWAYS_INLINE u64 GetHeapSpaceSize() { + return 4_GB; + } + + static constexpr ALWAYS_INLINE u64 GetAliasSpaceBeginAddress() { + return 60_GB; + } + + static constexpr ALWAYS_INLINE u64 GetAliasSpaceSize() { + return 4_GB; + } + + static constexpr ALWAYS_INLINE u64 GetAslrSpaceBeginAddress() { + return 2_MB; + } + + static constexpr ALWAYS_INLINE u64 GetAslrSpaceEndAddress() { + return 64_GB; + } + }; + + using AslrSpaceManagerImpl = AslrSpaceManagerLinuxImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_aslr_space_manager_impl.os.macos.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_aslr_space_manager_impl.os.macos.hpp new file mode 100644 index 00000000..20bf70cb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_aslr_space_manager_impl.os.macos.hpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_address_space_allocator_forbidden_region.hpp" + +namespace ams::os::impl { + + class AslrSpaceManagerLinuxImpl { + NON_COPYABLE(AslrSpaceManagerLinuxImpl); + NON_MOVEABLE(AslrSpaceManagerLinuxImpl); + public: + constexpr AslrSpaceManagerLinuxImpl() = default; + + static constexpr ALWAYS_INLINE const AddressSpaceAllocatorForbiddenRegion *GetForbiddenRegions() { + return nullptr; + } + + static constexpr ALWAYS_INLINE size_t GetForbiddenRegionCount() { + return 0; + } + + static constexpr ALWAYS_INLINE u64 GetHeapSpaceBeginAddress() { + return 8_GB; + } + + static constexpr ALWAYS_INLINE u64 GetHeapSpaceSize() { + return 4_GB; + } + + static constexpr ALWAYS_INLINE u64 GetAliasSpaceBeginAddress() { + return 60_GB; + } + + static constexpr ALWAYS_INLINE u64 GetAliasSpaceSize() { + return 4_GB; + } + + static constexpr ALWAYS_INLINE u64 GetAslrSpaceBeginAddress() { + return 2_MB; + } + + static constexpr ALWAYS_INLINE u64 GetAslrSpaceEndAddress() { + return 64_GB; + } + }; + + using AslrSpaceManagerImpl = AslrSpaceManagerLinuxImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_aslr_space_manager_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_aslr_space_manager_impl.os.windows.hpp new file mode 100644 index 00000000..54068dce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_aslr_space_manager_impl.os.windows.hpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_address_space_allocator_forbidden_region.hpp" + +namespace ams::os::impl { + + class AslrSpaceManagerWindowsImpl { + NON_COPYABLE(AslrSpaceManagerWindowsImpl); + NON_MOVEABLE(AslrSpaceManagerWindowsImpl); + public: + constexpr AslrSpaceManagerWindowsImpl() = default; + + static constexpr ALWAYS_INLINE const AddressSpaceAllocatorForbiddenRegion *GetForbiddenRegions() { + return nullptr; + } + + static constexpr ALWAYS_INLINE size_t GetForbiddenRegionCount() { + return 0; + } + + static constexpr ALWAYS_INLINE u64 GetHeapSpaceBeginAddress() { + return 8_GB; + } + + static constexpr ALWAYS_INLINE u64 GetHeapSpaceSize() { + return 4_GB; + } + + static constexpr ALWAYS_INLINE u64 GetAliasSpaceBeginAddress() { + return 60_GB; + } + + static constexpr ALWAYS_INLINE u64 GetAliasSpaceSize() { + return 4_GB; + } + + static constexpr ALWAYS_INLINE u64 GetAslrSpaceBeginAddress() { + return 2_MB; + } + + static constexpr ALWAYS_INLINE u64 GetAslrSpaceEndAddress() { + return 64_GB; + } + }; + + using AslrSpaceManagerImpl = AslrSpaceManagerWindowsImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_aslr_space_manager_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_aslr_space_manager_types.hpp new file mode 100644 index 00000000..e87d0349 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_aslr_space_manager_types.hpp @@ -0,0 +1,117 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_address_space_allocator.hpp" + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "os_aslr_space_manager_impl.os.horizon.hpp" +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include "os_aslr_space_manager_impl.os.windows.hpp" +#elif defined(ATMOSPHERE_OS_LINUX) + #include "os_aslr_space_manager_impl.os.linux.hpp" +#elif defined(ATMOSPHERE_OS_MACOS) + #include "os_aslr_space_manager_impl.os.macos.hpp" +#else + #error "Unknown OS for AslrSpaceManagerImpl" +#endif + +namespace ams::os::impl { + + constexpr inline size_t AslrSpaceLargeAlign = 2_MB; + constexpr inline size_t AslrSpaceGuardSize = 4 * MemoryPageSize; + + template<typename Allocator, typename Impl> + class AslrSpaceManagerTemplate { + NON_COPYABLE(AslrSpaceManagerTemplate); + NON_MOVEABLE(AslrSpaceManagerTemplate); + private: + using AddressType = typename Allocator::AddressType; + using SizeType = typename Allocator::SizeType; + private: + Impl m_impl; + Allocator m_allocator; + public: + AslrSpaceManagerTemplate() : m_impl(), m_allocator(m_impl.GetAslrSpaceBeginAddress(), m_impl.GetAslrSpaceEndAddress(), AslrSpaceGuardSize, m_impl.GetForbiddenRegions(), m_impl.GetForbiddenRegionCount()) { + /* ... */ + } + + #if defined(ATMOSPHERE_OS_HORIZON) + AslrSpaceManagerTemplate(os::NativeHandle process_handle) : m_impl(process_handle), m_allocator(m_impl.GetAslrSpaceBeginAddress(process_handle), m_impl.GetAslrSpaceEndAddress(process_handle), AslrSpaceGuardSize, m_impl.GetForbiddenRegions(), m_impl.GetForbiddenRegionCount(), process_handle) { + /* ... */ + } + #endif + + AddressType AllocateSpace(SizeType size, SizeType align_offset, AddressSpaceGenerateRandomFunction generate_random) { + /* Try to allocate a large-aligned space, if we can. */ + if (align_offset || (size / AslrSpaceLargeAlign) != 0) { + if (auto large_align = m_allocator.AllocateSpace(size, AslrSpaceLargeAlign, align_offset & (AslrSpaceLargeAlign - 1), generate_random); large_align != 0) { + return large_align; + } + } + + /* Allocate a page-aligned space. */ + return m_allocator.AllocateSpace(size, MemoryPageSize, 0, generate_random); + } + + bool CheckGuardSpace(AddressType address, SizeType size) { + return m_allocator.CheckGuardSpace(address, size, AslrSpaceGuardSize); + } + + template<typename MapFunction, typename UnmapFunction> + Result MapAtRandomAddressWithCustomRandomGenerator(AddressType *out, MapFunction map_function, UnmapFunction unmap_function, SizeType size, SizeType align_offset, AddressSpaceGenerateRandomFunction generate_random) { + /* Try to map up to 64 times. */ + for (auto i = 0; i < 64; ++i) { + /* Reserve space to map the memory. */ + const uintptr_t map_address = this->AllocateSpace(size, align_offset, generate_random); + if (map_address == 0) { + break; + } + + /* Try to map. */ + R_TRY_CATCH(map_function(map_address, size)) { + /* If we failed to map at the address, retry. */ + R_CATCH(os::ResultInvalidCurrentMemoryState) { continue; } + } R_END_TRY_CATCH; + + /* Check guard space. */ + if (!this->CheckGuardSpace(map_address, size)) { + /* We don't have guard space, so unmap. */ + unmap_function(map_address, size); + + /* NOTE: Nintendo is missing this continue; this is almost certainly a bug. */ + /* This will cause them to incorrectly return success after unmapping if guard space is not present. */ + continue; + } + + /* We mapped successfully. */ + *out = map_address; + R_SUCCEED(); + } + + /* We failed to map. */ + R_THROW(os::ResultOutOfAddressSpace()); + } + + template<typename MapFunction, typename UnmapFunction> + Result MapAtRandomAddress(AddressType *out, MapFunction map_function, UnmapFunction unmap_function, SizeType size, SizeType align_offset) { + R_RETURN(this->MapAtRandomAddressWithCustomRandomGenerator(out, map_function, unmap_function, size, align_offset, os::impl::AddressSpaceAllocatorDefaultGenerateRandom)); + } + }; + + using AslrSpaceManager = AslrSpaceManagerTemplate<AddressSpaceAllocator, AslrSpaceManagerImpl>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_cache_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_cache_impl.hpp new file mode 100644 index 00000000..663f51bb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_cache_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "os_cache_impl.os.horizon.hpp" +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include "os_cache_impl.os.windows.hpp" +#elif defined(ATMOSPHERE_OS_LINUX) + #include "os_cache_impl.os.linux.hpp" +#elif defined(ATMOSPHERE_OS_MACOS) + #include "os_cache_impl.os.macos.hpp" +#else + #error "Unknown OS for CacheImpl" +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_cache_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_cache_impl.os.horizon.hpp new file mode 100644 index 00000000..c4c0528c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_cache_impl.os.horizon.hpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + inline void FlushDataCacheImpl(const void *addr, size_t size) { + #if defined(ATMOSPHERE_ARCH_ARM64) + { + /* Declare helper variables. */ + uintptr_t cache_type_register = 0; + uintptr_t cache_line_size = 0; + const uintptr_t end_addr = reinterpret_cast<uintptr_t>(addr) + size; + + /* Get the cache type register. */ + __asm__ __volatile__("mrs %[cache_type_register], ctr_el0" : [cache_type_register]"=r"(cache_type_register)); + + /* Calculate cache line size. */ + cache_line_size = 4 << ((cache_type_register >> 16) & 0xF); + + /* Get the thread local region. */ + auto * const tlr = svc::GetThreadLocalRegion(); + + /* Note to the kernel that we're performing cache maintenance, in case we get interrupted while touching cache lines. */ + tlr->cache_maintenance_flag = 1; + ON_SCOPE_EXIT { tlr->cache_maintenance_flag = 0; }; + + /* Iterate, flushing cache lines. */ + for (uintptr_t cur = reinterpret_cast<uintptr_t>(addr) & ~(cache_line_size - 1); cur < end_addr; cur += cache_line_size) { + __asm__ __volatile__ ("dc civac, %[cur]" :: [cur]"r"(cur)); + } + + /* Insert a memory barrier, now that memory has been flushed. */ + __asm__ __volatile__("dsb sy" ::: "memory"); + } + #else + const auto result = svc::FlushProcessDataCache(svc::PseudoHandle::CurrentProcess, reinterpret_cast<uintptr_t>(addr), size); + R_ASSERT(result); + AMS_UNUSED(result); + #endif + } + + inline void FlushEntireDataCacheImpl() { + svc::FlushEntireDataCache(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_cache_impl.os.linux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_cache_impl.os.linux.hpp new file mode 100644 index 00000000..d1cff2ea --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_cache_impl.os.linux.hpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + inline void FlushDataCacheImpl(const void *addr, size_t size) { + #if defined(ATMOSPHERE_ARCH_ARM64) + { + /* Declare helper variables. */ + uintptr_t cache_type_register = 0; + uintptr_t cache_line_size = 0; + const uintptr_t end_addr = reinterpret_cast<uintptr_t>(addr) + size; + + /* Get the cache type register. */ + __asm__ __volatile__("mrs %[cache_type_register], ctr_el0" : [cache_type_register]"=r"(cache_type_register)); + + /* Calculate cache line size. */ + cache_line_size = 4 << ((cache_type_register >> 16) & 0xF); + + /* Iterate, flushing cache lines. */ + for (uintptr_t cur = reinterpret_cast<uintptr_t>(addr) & ~(cache_line_size - 1); cur < end_addr; cur += cache_line_size) { + __asm__ __volatile__ ("dc civac, %[cur]" :: [cur]"r"(cur)); + } + + /* Insert a memory barrier, now that memory has been flushed. */ + __asm__ __volatile__("dsb sy" ::: "memory"); + } + #elif defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + /* Theoretically, do nothing here? */ + AMS_UNUSED(addr, size); + __asm__ __volatile__("" ::: "memory"); + #else + #error "Unknown architecture for linux DataCache" + #endif + } + + inline void FlushEntireDataCacheImpl() { + #if defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + /* Theoretically, do nothing here? */ + __asm__ __volatile__("" ::: "memory"); + #elif defined(ATMOSPHERE_ARCH_ARM64) + AMS_ABORT("FlushEntireDataCacheImpl called on arm64 linux"); + #else + #error "Unknown architecture for linux DataCache" + #endif + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_cache_impl.os.macos.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_cache_impl.os.macos.hpp new file mode 100644 index 00000000..4c6543ba --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_cache_impl.os.macos.hpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + inline void FlushDataCacheImpl(const void *addr, size_t size) { + #if defined(ATMOSPHERE_ARCH_ARM64) + { + /* Declare helper variables. */ + uintptr_t cache_type_register = 0; + uintptr_t cache_line_size = 0; + const uintptr_t end_addr = reinterpret_cast<uintptr_t>(addr) + size; + + /* Get the cache type register. */ + __asm__ __volatile__("mrs %[cache_type_register], ctr_el0" : [cache_type_register]"=r"(cache_type_register)); + + /* Calculate cache line size. */ + cache_line_size = 4 << ((cache_type_register >> 16) & 0xF); + + /* Iterate, flushing cache lines. */ + for (uintptr_t cur = reinterpret_cast<uintptr_t>(addr) & ~(cache_line_size - 1); cur < end_addr; cur += cache_line_size) { + __asm__ __volatile__ ("dc civac, %[cur]" :: [cur]"r"(cur)); + } + + /* Insert a memory barrier, now that memory has been flushed. */ + __asm__ __volatile__("dsb sy" ::: "memory"); + } + #elif defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + /* Theoretically, do nothing here? */ + AMS_UNUSED(addr, size); + __asm__ __volatile__("" ::: "memory"); + #else + #error "Unknown architecture for macOS DataCache" + #endif + } + + inline void FlushEntireDataCacheImpl() { + #if defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + /* Theoretically, do nothing here? */ + __asm__ __volatile__("" ::: "memory"); + #elif defined(ATMOSPHERE_ARCH_ARM64) + AMS_ABORT("FlushEntireDataCacheImpl called on arm64 macOS"); + #else + #error "Unknown architecture for macOS DataCache" + #endif + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_cache_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_cache_impl.os.windows.hpp new file mode 100644 index 00000000..03885080 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_cache_impl.os.windows.hpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + inline void FlushDataCacheImpl(const void *addr, size_t size) { + #if defined(ATMOSPHERE_ARCH_ARM64) + { + /* Declare helper variables. */ + uintptr_t cache_type_register = 0; + uintptr_t cache_line_size = 0; + const uintptr_t end_addr = reinterpret_cast<uintptr_t>(addr) + size; + + /* Get the cache type register. */ + __asm__ __volatile__("mrs %[cache_type_register], ctr_el0" : [cache_type_register]"=r"(cache_type_register)); + + /* Calculate cache line size. */ + cache_line_size = 4 << ((cache_type_register >> 16) & 0xF); + + /* Iterate, flushing cache lines. */ + for (uintptr_t cur = reinterpret_cast<uintptr_t>(addr) & ~(cache_line_size - 1); cur < end_addr; cur += cache_line_size) { + __asm__ __volatile__ ("dc civac, %[cur]" :: [cur]"r"(cur)); + } + + /* Insert a memory barrier, now that memory has been flushed. */ + __asm__ __volatile__("dsb sy" ::: "memory"); + } + #elif defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + /* Theoretically, do nothing here? */ + AMS_UNUSED(addr, size); + __asm__ __volatile__("" ::: "memory"); + #else + #error "Unknown architecture for windows DataCache" + #endif + } + + inline void FlushEntireDataCacheImpl() { + #if defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + /* Theoretically, do nothing here? */ + __asm__ __volatile__("" ::: "memory"); + #else + #error "Unknown architecture for windows DataCache" + #endif + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_debug_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_debug_impl.hpp new file mode 100644 index 00000000..d8001d30 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_debug_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "os_debug_impl.os.horizon.hpp" +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include "os_debug_impl.os.windows.hpp" +#elif defined(ATMOSPHERE_OS_LINUX) + #include "os_debug_impl.os.linux.hpp" +#elif defined(ATMOSPHERE_OS_MACOS) + #include "os_debug_impl.os.macos.hpp" +#else + #error "Unknown OS for DebugImpl" +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_debug_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_debug_impl.os.horizon.hpp new file mode 100644 index 00000000..e35a6092 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_debug_impl.os.horizon.hpp @@ -0,0 +1,89 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_thread_manager.hpp" + +namespace ams::os::impl { + + class DebugHorizonImpl { + public: + static uintptr_t GetCurrentStackPointer() { + uintptr_t v; + __asm__ __volatile__("mov %[v], sp" : [v]"=&r"(v) :: "memory"); + return v; + } + + static void GetCurrentStackInfo(uintptr_t *out_stack, size_t *out_size) { + /* Check pre-conditions. */ + AMS_ASSERT(out_stack != nullptr); + AMS_ASSERT(out_size != nullptr); + + /* Get the current thread. */ + auto *cur_thread = os::impl::GetCurrentThread(); + auto *cur_fiber = cur_thread->current_fiber; + + /* Get the current stack pointer. */ + uintptr_t cur_sp = GetCurrentStackPointer(); + + /* Determine current stack extents, TODO Fiber */ + uintptr_t stack_top = reinterpret_cast<uintptr_t>(cur_fiber == nullptr ? cur_thread->stack : /* TODO: cur_fiber->stack */ nullptr); + size_t stack_size = reinterpret_cast<size_t>(cur_fiber == nullptr ? cur_thread->stack_size : /* TODO: cur_fiber->stack_size */ 0); + + uintptr_t stack_bottom = stack_top + stack_size; + + /* TODO: User exception handler, check if stack is out of range and use exception stack. */ + + /* Check that the stack pointer is in bounds. */ + AMS_ABORT_UNLESS((stack_top <= cur_sp) && (cur_sp < stack_bottom)); + + /* Set the output. */ + *out_stack = stack_top; + *out_size = stack_size; + } + + static void QueryMemoryInfo(os::MemoryInfo *out) { + AMS_UNUSED(out); + AMS_ABORT("TODO: Horizon QueryMemoryInfo"); + } + + static Tick GetIdleTickCount() { + u64 value; + R_ABORT_UNLESS(svc::GetInfo(std::addressof(value), svc::InfoType_IdleTickCount, svc::InvalidHandle, static_cast<u64>(-1))); + + return os::Tick(value); + } + + static Tick GetThreadTickCount() { + u64 value; + R_ABORT_UNLESS(svc::GetInfo(std::addressof(value), svc::InfoType_ThreadTickCount, svc::PseudoHandle::CurrentThread, static_cast<u64>(-1))); + + return os::Tick(value); + } + + static int GetFreeThreadCount() { + u64 value; + R_ABORT_UNLESS(svc::GetInfo(std::addressof(value), svc::InfoType_FreeThreadCount, svc::PseudoHandle::CurrentProcess, 0)); + + AMS_ASSERT(value <= static_cast<u64>(std::numeric_limits<int>::max())); + + return static_cast<int>(value); + } + }; + + using DebugImpl = DebugHorizonImpl; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_debug_impl.os.linux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_debug_impl.os.linux.hpp new file mode 100644 index 00000000..08d3cb67 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_debug_impl.os.linux.hpp @@ -0,0 +1,66 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class DebugLinuxImpl { + public: + static void GetCurrentStackInfo(uintptr_t *out_stack, size_t *out_size) { + /* Check pre-conditions. */ + AMS_ASSERT(out_stack != nullptr); + AMS_ASSERT(out_size != nullptr); + + /* Get the current stack by pthread */ + pthread_attr_t attr; + pthread_attr_init(std::addressof(attr)); + ON_SCOPE_EXIT { pthread_attr_destroy(std::addressof(attr)); }; + + const auto getattr_res = pthread_getattr_np(pthread_self(), std::addressof(attr)); + AMS_ABORT_UNLESS(getattr_res == 0); + + /* Get the thread satck. */ + void *base = nullptr; + size_t size = 0; + const auto getstack_res = pthread_attr_getstack(std::addressof(attr), std::addressof(base), std::addressof(size)); + AMS_ABORT_UNLESS(getstack_res == 0); + + *out_stack = reinterpret_cast<uintptr_t>(base); + *out_size = size; + } + + static void QueryMemoryInfo(os::MemoryInfo *out) { + AMS_UNUSED(out); + AMS_ABORT("TODO: Linux QueryMemoryInfo"); + } + + static Tick GetIdleTickCount() { + return os::Tick(0); + } + + static Tick GetThreadTickCount() { + return os::Tick(0); + } + + static int GetFreeThreadCount() { + return 0; + } + }; + + using DebugImpl = DebugLinuxImpl; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_debug_impl.os.macos.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_debug_impl.os.macos.hpp new file mode 100644 index 00000000..1bb7fa0f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_debug_impl.os.macos.hpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class DebugMacosImpl { + public: + static void GetCurrentStackInfo(uintptr_t *out_stack, size_t *out_size) { + /* Check pre-conditions. */ + AMS_ASSERT(out_stack != nullptr); + AMS_ASSERT(out_size != nullptr); + + /* Get the current pthread. */ + const auto self = pthread_self(); + + /* Get the thread stack. */ + uintptr_t stack_bottom = reinterpret_cast<uintptr_t>(pthread_get_stackaddr_np(self)); + size_t stack_size = pthread_get_stacksize_np(self); + + uintptr_t stack_top = stack_bottom - stack_size; + + *out_stack = stack_top; + *out_size = stack_size; + } + + static void QueryMemoryInfo(os::MemoryInfo *out) { + AMS_UNUSED(out); + AMS_ABORT("TODO: macOS QueryMemoryInfo"); + } + + static Tick GetIdleTickCount() { + return os::Tick(0); + } + + static Tick GetThreadTickCount() { + return os::Tick(0); + } + + static int GetFreeThreadCount() { + return 0; + } + }; + + using DebugImpl = DebugMacosImpl; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_debug_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_debug_impl.os.windows.hpp new file mode 100644 index 00000000..b70c8d01 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_debug_impl.os.windows.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include <stratosphere/windows.hpp> + +namespace ams::os::impl { + + class DebugWindowsImpl { + public: + static void GetCurrentStackInfo(uintptr_t *out_stack, size_t *out_size) { + /* Check pre-conditions. */ + AMS_ASSERT(out_stack != nullptr); + AMS_ASSERT(out_size != nullptr); + + /* Get the current stack by NT_TIB */ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Warray-bounds" + auto *tib = reinterpret_cast<NT_TIB *>(::NtCurrentTeb()); + #pragma GCC diagnostic pop + + *out_stack = reinterpret_cast<uintptr_t>(tib->StackLimit); + *out_size = reinterpret_cast<uintptr_t>(tib->StackBase) - reinterpret_cast<uintptr_t>(tib->StackLimit); + } + + static void QueryMemoryInfo(os::MemoryInfo *out) { + AMS_UNUSED(out); + AMS_ABORT("TODO: Windows QueryMemoryInfo"); + } + + static Tick GetIdleTickCount() { + return os::Tick(0); + } + + static Tick GetThreadTickCount() { + return os::Tick(0); + } + + static int GetFreeThreadCount() { + return 0; + } + }; + + using DebugImpl = DebugWindowsImpl; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_disable_counter.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_disable_counter.os.horizon.hpp new file mode 100644 index 00000000..47577cb1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_disable_counter.os.horizon.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class CheckBusyMutexPermission { + public: + CheckBusyMutexPermission() { + /* In order to use the disable counter, we must support SynchronizePreemptionState. */ + u64 value; + R_ABORT_UNLESS(svc::GetInfo(std::addressof(value), svc::InfoType_IsSvcPermitted, 0, svc::SvcId_SynchronizePreemptionState)); + + /* Verify that it's supported. */ + AMS_ABORT_UNLESS(value != 0); + } + }; + + ALWAYS_INLINE void CallCheckBusyMutexPermission() { + AMS_FUNCTION_LOCAL_STATIC(CheckBusyMutexPermission, s_check); + AMS_UNUSED(s_check); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.hpp new file mode 100644 index 00000000..9ef69545 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_giant_lock_types.hpp" +#include "os_resource_manager.hpp" + +namespace ams::os::impl { + + ALWAYS_INLINE GiantLock &GetGiantLock() { + return GetResourceManager().GetGiantLock(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.horizon.hpp new file mode 100644 index 00000000..5abe5d14 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.horizon.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class GiantLockHorizonImpl{}; + + using GiantLock = GiantLockHorizonImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.linux.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.linux.cpp new file mode 100644 index 00000000..d16c3e0a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.linux.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_giant_lock.os.linux.hpp" +#include <sys/syscall.h> +#include <unistd.h> +#include <fcntl.h> + +namespace ams::os::impl { + + #define AMS_OS_IMPL_GIANT_LOCK_FILE_PATH "/tmp/.ams_os_giant_lock" + + void GiantLockLinuxImpl::Lock() { + m_fd = ::open(AMS_OS_IMPL_GIANT_LOCK_FILE_PATH, O_RDWR | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); + AMS_ABORT_UNLESS(m_fd >= 0); + + while (::lockf(m_fd, F_LOCK, 0) < 0) { + AMS_ABORT_UNLESS(errno == EINTR); + } + } + + void GiantLockLinuxImpl::Unlock() { + AMS_ASSERT(m_fd >= 0); + + while (::lockf(m_fd, F_ULOCK, 0) < 0) { + AMS_ABORT_UNLESS(errno == EINTR); + } + + while (::close(m_fd) < 0) { + AMS_ABORT_UNLESS(errno == EINTR); + } + + m_fd = os::InvalidNativeHandle; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.linux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.linux.hpp new file mode 100644 index 00000000..d5846424 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.linux.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class GiantLockLinuxImpl { + private: + s32 m_fd = -1; + public: + void Lock(); + void Unlock(); + + ALWAYS_INLINE void lock() { + this->Lock(); + } + + ALWAYS_INLINE void unlock() { + this->Unlock(); + } + }; + + using GiantLock = GiantLockLinuxImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.macos.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.macos.cpp new file mode 100644 index 00000000..f565a597 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.macos.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_giant_lock.os.macos.hpp" +#include <sys/syscall.h> +#include <unistd.h> +#include <fcntl.h> + +namespace ams::os::impl { + + #define AMS_OS_IMPL_GIANT_LOCK_FILE_PATH "/tmp/.ams_os_giant_lock" + + void GiantLockMacosImpl::Lock() { + m_fd = ::open(AMS_OS_IMPL_GIANT_LOCK_FILE_PATH, O_RDWR | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); + AMS_ABORT_UNLESS(m_fd >= 0); + + while (::lockf(m_fd, F_LOCK, 0) < 0) { + AMS_ABORT_UNLESS(errno == EINTR); + } + } + + void GiantLockMacosImpl::Unlock() { + AMS_ASSERT(m_fd >= 0); + + while (::lockf(m_fd, F_ULOCK, 0) < 0) { + AMS_ABORT_UNLESS(errno == EINTR); + } + + while (::close(m_fd) < 0) { + AMS_ABORT_UNLESS(errno == EINTR); + } + + m_fd = os::InvalidNativeHandle; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.macos.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.macos.hpp new file mode 100644 index 00000000..c079857d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.macos.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class GiantLockMacosImpl { + private: + s32 m_fd = -1; + public: + void Lock(); + void Unlock(); + + ALWAYS_INLINE void lock() { + this->Lock(); + } + + ALWAYS_INLINE void unlock() { + this->Unlock(); + } + }; + + using GiantLock = GiantLockMacosImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.windows.cpp new file mode 100644 index 00000000..1fb56adf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.windows.cpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <stratosphere/windows.hpp> +#include "os_giant_lock.os.windows.hpp" + +namespace ams::os::impl { + + GiantLockWindowsImpl::GiantLockWindowsImpl() { + const NativeHandle handle = ::CreateMutexA(nullptr, false, "AMS_OS_STRATOSPHERE_GIANT_LOCK"); + AMS_ASSERT(handle != InvalidNativeHandle); + + m_handle = handle; + } + + GiantLockWindowsImpl::~GiantLockWindowsImpl() { + const auto ret = ::CloseHandle(m_handle); + AMS_ASSERT(ret != 0); + AMS_UNUSED(ret); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.windows.hpp new file mode 100644 index 00000000..8776ae39 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock.os.windows.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class GiantLockWindowsImpl { + private: + NativeHandle m_handle; + public: + GiantLockWindowsImpl(); + ~GiantLockWindowsImpl(); + + ALWAYS_INLINE void lock() { + ::WaitForSingleObject(m_handle, INFINITE); + } + + ALWAYS_INLINE void unlock() { + ::ReleaseMutex(m_handle); + } + }; + + using GiantLock = GiantLockWindowsImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock_types.hpp new file mode 100644 index 00000000..734af2de --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_giant_lock_types.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "os_giant_lock.os.horizon.hpp" +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include "os_giant_lock.os.windows.hpp" +#elif defined(ATMOSPHERE_OS_LINUX) + #include "os_giant_lock.os.linux.hpp" +#elif defined(ATMOSPHERE_OS_MACOS) + #include "os_giant_lock.os.macos.hpp" +#else + #error "Unknown OS for GiantLock" +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_initialize.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_initialize.os.horizon.cpp new file mode 100644 index 00000000..fbea1a80 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_initialize.os.horizon.cpp @@ -0,0 +1,178 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_resource_manager.hpp" + +extern "C" { extern u8 __argdata__[]; } + +namespace ams::os { + + namespace { + + class MemoryArranger { + private: + uintptr_t m_address; + public: + constexpr MemoryArranger(uintptr_t address) : m_address(address) { /* ... */ } + + template<typename T> + T *Arrange() { + this->Align(alignof(T)); + return static_cast<T *>(this->Arrange(sizeof(T))); + } + + void *Arrange(size_t size) { + const auto address = m_address; + m_address += size; + return reinterpret_cast<void *>(address); + } + + void Align(size_t align) { + m_address = util::AlignUp(m_address, align); + } + + char *ArrangeCharArray(size_t size) { + return reinterpret_cast<char *>(Arrange(size)); + } + }; + + bool HasArguments(uintptr_t args_region) { + /* Check that the arguments region is read-write. */ + svc::MemoryInfo mi; + svc::PageInfo pi; + + if (R_FAILED(svc::QueryMemory(std::addressof(mi), std::addressof(pi), args_region))) { + return false; + } + + return mi.permission == svc::MemoryPermission_ReadWrite; + } + + const char *SkipSpace(const char *p, const char *end) { + while (p < end && std::isspace(*p)) { ++p; } + return p; + } + + const char *GetTokenEnd(const char *p, const char *end) { + while (p < end && !std::isspace(*p)) { ++p; } + return p; + } + + const char *GetQuotedTokenEnd(const char *p, const char *end) { + while (p < end && *p != '"') { ++p; } + return p; + } + + int MakeArgv(char **out_argv_buf, char *arg_buf, const char *cmd_line, size_t cmd_line_size, int arg_max) { + /* Prepare to parse arguments. */ + auto idx = 0; + auto src = cmd_line; + auto dst = arg_buf; + const auto end = src + cmd_line_size; + + /* Parse all tokens. */ + while (true) { + /* Advance past any spaces. */ + src = SkipSpace(src, end); + if (src >= end) { + break; + } + + /* Check that we don't have too many arguments. */ + if (idx >= arg_max) { + break; + } + + /* Find the start/end of the current argument token. */ + const char *arg_end; + const char *src_next; + if (*src == '"') { + ++src; + arg_end = GetQuotedTokenEnd(src, end); + src_next = arg_end + 1; + } else { + arg_end = GetTokenEnd(src, end); + src_next = arg_end; + } + + /* Determine token size. */ + const auto arg_size = arg_end - src; + + /* Set the argv pointer. */ + out_argv_buf[idx++] = dst; + + /* Copy the argument. */ + std::memcpy(dst, src, arg_size); + dst += arg_size; + + /* Null-terminate the argument token. */ + *(dst++) = '\x00'; + + /* Advance to next token. */ + src = src_next; + } + + /* Null terminate the final token. */ + *(dst++) = '\x00'; + + /* Null terminate argv. */ + out_argv_buf[idx] = nullptr; + + return idx; + } + + } + + void SetHostArgc(int argc); + void SetHostArgv(char **argv); + + void Initialize() { + /* Only allow os::Initialize to be called once. */ + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(bool, s_initialized, false); + if (s_initialized) { + return; + } + s_initialized = true; + + /* Initialize the global os resource manager. */ + os::impl::ResourceManagerHolder::InitializeResourceManagerInstance(); + + /* Setup host argc/argv as needed. */ + const uintptr_t args_region = reinterpret_cast<uintptr_t>(__argdata__); + if (HasArguments(args_region)) { + /* Create arguments memory arranger. */ + MemoryArranger arranger(args_region); + + /* Arrange. */ + const auto &header = *arranger.Arrange<ldr::ProgramArguments>(); + const char *cmd_line = arranger.ArrangeCharArray(header.arguments_size); + char *arg_buf = arranger.ArrangeCharArray(header.arguments_size + 2); + char **argv_buf = arranger.Arrange<char *>(); + + /* Determine extents. */ + const auto arg_buf_size = reinterpret_cast<uintptr_t>(argv_buf) - args_region; + const auto arg_max = (header.allocated_size - arg_buf_size) / sizeof(char *); + + /* Make argv. */ + const auto arg_count = MakeArgv(argv_buf, arg_buf, cmd_line, header.arguments_size, arg_max); + + /* Set host argc/argv. */ + os::SetHostArgc(arg_count); + os::SetHostArgv(argv_buf); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_initialize.os.linux.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_initialize.os.linux.cpp new file mode 100644 index 00000000..448169f9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_initialize.os.linux.cpp @@ -0,0 +1,79 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_resource_manager.hpp" + +namespace ams { + + void Main(); + + namespace init { + + void InitializeDefaultAllocator(); + + } + + /* TODO: This should probably instead be a custom init::Initialize*()? */ + namespace fs { + + void InitializeForHostTool(); + + } + +} + +namespace ams::os { + + void SetHostArgc(int argc); + void SetHostArgv(char **argv); + + [[gnu::constructor(101)]] void Initialize() { + /* Only allow os::Initialize to be called once. */ + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(bool, s_initialized, false); + if (s_initialized) { + return; + } + s_initialized = true; + + /* Initialize the global os resource manager. */ + os::impl::ResourceManagerHolder::InitializeResourceManagerInstance(); + + /* Initialize virtual address memory. */ + os::InitializeVirtualAddressMemory(); + + /* Ensure that the init library's allocator has been setup. */ + init::InitializeDefaultAllocator(); + } + +} + +extern "C" int main(int argc, char **argv) { + /* Ensure os library is initialized. */ + ::ams::os::Initialize(); + + /* Set argc/argv. */ + ::ams::os::SetHostArgc(argc); + ::ams::os::SetHostArgv(argv); + + /* Ensure filesystem library is initialized. */ + ::ams::fs::InitializeForHostTool(); + + /* Call main. */ + ::ams::Main(); + + /* TODO: Should we try to implement a custom exit here? */ + return 0; +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_initialize.os.macos.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_initialize.os.macos.cpp new file mode 100644 index 00000000..448169f9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_initialize.os.macos.cpp @@ -0,0 +1,79 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_resource_manager.hpp" + +namespace ams { + + void Main(); + + namespace init { + + void InitializeDefaultAllocator(); + + } + + /* TODO: This should probably instead be a custom init::Initialize*()? */ + namespace fs { + + void InitializeForHostTool(); + + } + +} + +namespace ams::os { + + void SetHostArgc(int argc); + void SetHostArgv(char **argv); + + [[gnu::constructor(101)]] void Initialize() { + /* Only allow os::Initialize to be called once. */ + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(bool, s_initialized, false); + if (s_initialized) { + return; + } + s_initialized = true; + + /* Initialize the global os resource manager. */ + os::impl::ResourceManagerHolder::InitializeResourceManagerInstance(); + + /* Initialize virtual address memory. */ + os::InitializeVirtualAddressMemory(); + + /* Ensure that the init library's allocator has been setup. */ + init::InitializeDefaultAllocator(); + } + +} + +extern "C" int main(int argc, char **argv) { + /* Ensure os library is initialized. */ + ::ams::os::Initialize(); + + /* Set argc/argv. */ + ::ams::os::SetHostArgc(argc); + ::ams::os::SetHostArgv(argv); + + /* Ensure filesystem library is initialized. */ + ::ams::fs::InitializeForHostTool(); + + /* Call main. */ + ::ams::Main(); + + /* TODO: Should we try to implement a custom exit here? */ + return 0; +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_initialize.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_initialize.os.windows.cpp new file mode 100644 index 00000000..0b279238 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_initialize.os.windows.cpp @@ -0,0 +1,107 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_resource_manager.hpp" + +namespace ams { + + void Main(); + + namespace init { + + void InitializeDefaultAllocator(); + + } + + /* TODO: This should probably instead be a custom init::Initialize*()? */ + namespace fs { + + void InitializeForHostTool(); + + } + +} + +namespace ams::os { + + void SetHostArgc(int argc); + void SetHostArgv(char **argv); + + namespace { + + void SetupWindowsConsole(DWORD which) { + /* Get handle to standard device. */ + const auto handle = ::GetStdHandle(which); + if (handle == INVALID_HANDLE_VALUE) { + return; + } + + /* Get the console mode. */ + DWORD mode; + if (!::GetConsoleMode(handle, std::addressof(mode))) { + return; + } + + /* Enable printing with ANSI escape codes. */ + mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + + /* Set console mode. */ + ::SetConsoleMode(handle, mode); + } + + } + + void Initialize() { + /* Only allow os::Initialize to be called once. */ + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(bool, s_initialized, false); + if (s_initialized) { + return; + } + s_initialized = true; + + /* Initialize the global os resource manager. */ + os::impl::ResourceManagerHolder::InitializeResourceManagerInstance(); + + /* Initialize virtual address memory. */ + os::InitializeVirtualAddressMemory(); + + /* Ensure that the init library's allocator has been setup. */ + init::InitializeDefaultAllocator(); + + /* Try to set up the windows console. */ + SetupWindowsConsole(STD_OUTPUT_HANDLE); + SetupWindowsConsole(STD_ERROR_HANDLE); + } + +} + +extern "C" int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { + /* Ensure os library is initialized. */ + ::ams::os::Initialize(); + + /* Set argc/argv. */ + ::ams::os::SetHostArgc(__argc); + ::ams::os::SetHostArgv(__argv); + + /* Ensure filesystem library is initialized. */ + ::ams::fs::InitializeForHostTool(); + + /* Call main. */ + ::ams::Main(); + + /* TODO: Should we try to implement a custom exit here? */ + return 0; +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_insecure_memory_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_insecure_memory_impl.hpp new file mode 100644 index 00000000..fe497279 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_insecure_memory_impl.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class InsecureMemoryImpl { + public: + static Result AllocateInsecureMemoryImpl(uintptr_t *out_address, size_t size); + static void FreeInsecureMemoryImpl(uintptr_t address, size_t size); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_insecure_memory_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_insecure_memory_impl.os.horizon.cpp new file mode 100644 index 00000000..27ffab70 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_insecure_memory_impl.os.horizon.cpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_insecure_memory_impl.hpp" +#include "os_aslr_space_manager.hpp" + +namespace ams::os::impl { + + Result InsecureMemoryImpl::AllocateInsecureMemoryImpl(uintptr_t *out_address, size_t size) { + /* Map at a random address. */ + R_RETURN(impl::GetAslrSpaceManager().MapAtRandomAddress(out_address, + [](uintptr_t map_address, size_t map_size) -> Result { + R_TRY_CATCH(svc::MapInsecurePhysicalMemory(map_address, map_size)) { + R_CONVERT(svc::ResultOutOfMemory, os::ResultOutOfMemory()) + R_CONVERT(svc::ResultInvalidCurrentMemory, os::ResultInvalidCurrentMemoryState()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + R_SUCCEED(); + }, + [](uintptr_t map_address, size_t map_size) -> void { + return InsecureMemoryImpl::FreeInsecureMemoryImpl(map_address, map_size); + }, + size, + 0 + )); + } + + void InsecureMemoryImpl::FreeInsecureMemoryImpl(uintptr_t address, size_t size) { + R_ABORT_UNLESS(svc::UnmapInsecurePhysicalMemory(address, size)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event.cpp new file mode 100644 index 00000000..4f897f21 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event.cpp @@ -0,0 +1,152 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_inter_process_event.hpp" +#include "os_inter_process_event_impl.hpp" +#include "os_multiple_wait_object_list.hpp" + +namespace ams::os::impl { + + namespace { + + inline void SetupInterProcessEventType(InterProcessEventType *event, NativeHandle read_handle, bool read_handle_managed, NativeHandle write_handle, bool write_handle_managed, EventClearMode clear_mode) { + /* Set handles. */ + event->readable_handle = read_handle; + event->is_readable_handle_managed = read_handle_managed; + event->writable_handle = write_handle; + event->is_writable_handle_managed = write_handle_managed; + + /* Set auto clear. */ + event->auto_clear = (clear_mode == EventClearMode_AutoClear); + + /* Create the waitlist node. */ + util::ConstructAt(event->multi_wait_object_list_storage); + + /* Set state. */ + event->state = InterProcessEventType::State_Initialized; + } + + } + + Result CreateInterProcessEvent(InterProcessEventType *event, EventClearMode clear_mode) { + NativeHandle rh, wh; + R_TRY(impl::InterProcessEventImpl::Create(std::addressof(wh), std::addressof(rh))); + + SetupInterProcessEventType(event, rh, true, wh, true, clear_mode); + R_SUCCEED(); + } + + void DestroyInterProcessEvent(InterProcessEventType *event) { + AMS_ASSERT(event->state == InterProcessEventType::State_Initialized); + + /* Clear the state. */ + event->state = InterProcessEventType::State_NotInitialized; + + /* Close handles if required. */ + if (event->is_readable_handle_managed) { + if (event->readable_handle != os::InvalidNativeHandle) { + impl::InterProcessEventImpl::Close(event->readable_handle); + } + event->is_readable_handle_managed = false; + } + + if (event->is_writable_handle_managed) { + if (event->writable_handle != os::InvalidNativeHandle) { + impl::InterProcessEventImpl::Close(event->writable_handle); + } + event->is_writable_handle_managed = false; + } + + /* Destroy the waitlist. */ + util::DestroyAt(event->multi_wait_object_list_storage); + } + + void AttachInterProcessEvent(InterProcessEventType *event, NativeHandle read_handle, bool read_handle_managed, NativeHandle write_handle, bool write_handle_managed, EventClearMode clear_mode) { + AMS_ASSERT(read_handle != os::InvalidNativeHandle || write_handle != os::InvalidNativeHandle); + + return SetupInterProcessEventType(event, read_handle, read_handle_managed, write_handle, write_handle_managed, clear_mode); + } + + NativeHandle DetachReadableHandleOfInterProcessEvent(InterProcessEventType *event) { + AMS_ASSERT(event->state == InterProcessEventType::State_Initialized); + + const NativeHandle handle = event->readable_handle; + + event->readable_handle = os::InvalidNativeHandle; + event->is_readable_handle_managed = false; + + return handle; + } + + NativeHandle DetachWritableHandleOfInterProcessEvent(InterProcessEventType *event) { + AMS_ASSERT(event->state == InterProcessEventType::State_Initialized); + + const NativeHandle handle = event->writable_handle; + + event->writable_handle = os::InvalidNativeHandle; + event->is_writable_handle_managed = false; + + return handle; + } + + void WaitInterProcessEvent(InterProcessEventType *event) { + AMS_ASSERT(event->state == InterProcessEventType::State_Initialized); + + return impl::InterProcessEventImpl::Wait(event->readable_handle, event->auto_clear); + } + + bool TryWaitInterProcessEvent(InterProcessEventType *event) { + AMS_ASSERT(event->state == InterProcessEventType::State_Initialized); + + return impl::InterProcessEventImpl::TryWait(event->readable_handle, event->auto_clear); + } + + bool TimedWaitInterProcessEvent(InterProcessEventType *event, TimeSpan timeout) { + AMS_ASSERT(event->state == InterProcessEventType::State_Initialized); + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + + return impl::InterProcessEventImpl::TimedWait(event->readable_handle, event->auto_clear, timeout); + } + + void SignalInterProcessEvent(InterProcessEventType *event) { + AMS_ASSERT(event->state != InterProcessEventType::State_NotInitialized); + + return impl::InterProcessEventImpl::Signal(event->writable_handle); + } + + void ClearInterProcessEvent(InterProcessEventType *event) { + AMS_ASSERT(event->state != InterProcessEventType::State_NotInitialized); + + auto handle = event->readable_handle; + if (handle == os::InvalidNativeHandle) { + handle = event->writable_handle; + } + return impl::InterProcessEventImpl::Clear(handle); + } + + NativeHandle GetReadableHandleOfInterProcessEvent(const InterProcessEventType *event) { + AMS_ASSERT(event->state != InterProcessEventType::State_NotInitialized); + + return event->readable_handle; + } + + NativeHandle GetWritableHandleOfInterProcessEvent(const InterProcessEventType *event) { + AMS_ASSERT(event->state != InterProcessEventType::State_NotInitialized); + + return event->writable_handle; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event.hpp new file mode 100644 index 00000000..75082359 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + Result CreateInterProcessEvent(InterProcessEventType *event, EventClearMode clear_mode); + void DestroyInterProcessEvent(InterProcessEventType *event); + + void AttachInterProcessEvent(InterProcessEventType *event, NativeHandle read_handle, bool read_handle_managed, NativeHandle write_handle, bool write_handle_managed, EventClearMode clear_mode); + + NativeHandle DetachReadableHandleOfInterProcessEvent(InterProcessEventType *event); + NativeHandle DetachWritableHandleOfInterProcessEvent(InterProcessEventType *event); + + void WaitInterProcessEvent(InterProcessEventType *event); + bool TryWaitInterProcessEvent(InterProcessEventType *event); + bool TimedWaitInterProcessEvent(InterProcessEventType *event, TimeSpan timeout); + + void SignalInterProcessEvent(InterProcessEventType *event); + void ClearInterProcessEvent(InterProcessEventType *event); + + NativeHandle GetReadableHandleOfInterProcessEvent(const InterProcessEventType *event); + NativeHandle GetWritableHandleOfInterProcessEvent(const InterProcessEventType *event); + + void InitializeMultiWaitHolder(MultiWaitHolderType *multi_wait_holder, InterProcessEventType *event); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.hpp new file mode 100644 index 00000000..10a251fb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "os_inter_process_event_impl.os.horizon.hpp" +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include "os_inter_process_event_impl.os.windows.hpp" +#elif defined(ATMOSPHERE_OS_LINUX) + #include "os_inter_process_event_impl.os.linux.hpp" +#elif defined(ATMOSPHERE_OS_MACOS) + #include "os_inter_process_event_impl.os.macos.hpp" +#else + #error "Unknown OS for ams::os::InterProcessEventImpl" +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.horizon.cpp new file mode 100644 index 00000000..5cae1c18 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.horizon.cpp @@ -0,0 +1,123 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_inter_process_event.hpp" +#include "os_inter_process_event_impl.os.horizon.hpp" +#include "os_timeout_helper.hpp" + +namespace ams::os::impl { + + Result InterProcessEventHorizonImpl::Create(NativeHandle *out_write, NativeHandle *out_read) { + /* Create the event handles. */ + svc::Handle wh, rh; + R_TRY_CATCH(svc::CreateEvent(std::addressof(wh), std::addressof(rh))) { + R_CONVERT(svc::ResultOutOfResource, os::ResultOutOfResource()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + *out_write = wh; + *out_read = rh; + R_SUCCEED(); + } + + void InterProcessEventHorizonImpl::Close(NativeHandle handle) { + if (handle != os::InvalidNativeHandle) { + R_ABORT_UNLESS(svc::CloseHandle(handle)); + } + } + + void InterProcessEventHorizonImpl::Signal(NativeHandle handle) { + R_ABORT_UNLESS(svc::SignalEvent(handle)); + } + + void InterProcessEventHorizonImpl::Clear(NativeHandle handle) { + R_ABORT_UNLESS(svc::ClearEvent(handle)); + } + + void InterProcessEventHorizonImpl::Wait(NativeHandle handle, bool auto_clear) { + while (true) { + /* Continuously wait, until success. */ + s32 index; + Result res = svc::WaitSynchronization(std::addressof(index), static_cast<svc::Handle *>(std::addressof(handle)), 1, svc::WaitInfinite); + if (R_SUCCEEDED(res)) { + /* Clear, if we must. */ + if (auto_clear) { + R_TRY_CATCH(svc::ResetSignal(handle)) { + /* Some other thread might have caught this before we did. */ + R_CATCH(svc::ResultInvalidState) { continue; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + } + + return; + } + + AMS_ASSERT(svc::ResultCancelled::Includes(res)); + } + } + + bool InterProcessEventHorizonImpl::TryWait(NativeHandle handle, bool auto_clear) { + /* If we're auto clear, just try to reset. */ + if (auto_clear) { + return R_SUCCEEDED(svc::ResetSignal(handle)); + } + + /* Not auto-clear. */ + while (true) { + /* Continuously wait, until success or timeout. */ + s32 index; + Result res = svc::WaitSynchronization(std::addressof(index), static_cast<svc::Handle *>(std::addressof(handle)), 1, 0); + + /* If we succeeded, we're signaled. */ + if (R_SUCCEEDED(res)) { + return true; + } + + /* If we timed out, we're not signaled. */ + if (svc::ResultTimedOut::Includes(res)) { + return false; + } + + AMS_ASSERT(svc::ResultCancelled::Includes(res)); + } + } + + bool InterProcessEventHorizonImpl::TimedWait(NativeHandle handle, bool auto_clear, TimeSpan timeout) { + TimeoutHelper timeout_helper(timeout); + + while (true) { + /* Continuously wait, until success. */ + s32 index; + Result res = svc::WaitSynchronization(std::addressof(index), static_cast<svc::Handle *>(std::addressof(handle)), 1, timeout_helper.GetTimeLeftOnTarget().GetNanoSeconds()); + if (R_SUCCEEDED(res)) { + /* Clear, if we must. */ + if (auto_clear) { + R_TRY_CATCH(svc::ResetSignal(handle)) { + /* Some other thread might have caught this before we did. */ + R_CATCH(svc::ResultInvalidState) { continue; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + } + + return true; + } + + if (svc::ResultTimedOut::Includes(res)) { + return false; + } + + AMS_ASSERT(svc::ResultCancelled::Includes(res)); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.horizon.hpp new file mode 100644 index 00000000..eb6d2b16 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.horizon.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class InterProcessEventHorizonImpl { + public: + static Result Create(NativeHandle *out_write, NativeHandle *out_read); + static void Close(NativeHandle handle); + static void Signal(NativeHandle handle); + static void Clear(NativeHandle handle); + static void Wait(NativeHandle handle, bool auto_clear); + static bool TryWait(NativeHandle handle, bool auto_clear); + static bool TimedWait(NativeHandle handle, bool auto_clear, TimeSpan timeout); + }; + + using InterProcessEventImpl = InterProcessEventHorizonImpl; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.linux.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.linux.cpp new file mode 100644 index 00000000..ae994499 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.linux.cpp @@ -0,0 +1,172 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_inter_process_event.hpp" +#include "os_inter_process_event_impl.os.linux.hpp" +#include "os_timeout_helper.hpp" + +#include <unistd.h> +#include <sys/eventfd.h> +#include <poll.h> + +namespace ams::os::impl { + + namespace { + + bool PollEvent(NativeHandle handle, s64 ns) { + struct pollfd pfd; + pfd.fd = handle; + pfd.events = POLLIN; + pfd.revents = 0; + + /* Determine timeout. */ + constexpr s64 NanoSecondsPerSecond = TimeSpan::FromSeconds(1).GetNanoSeconds(); + struct timespec ts = { .tv_sec = (ns / NanoSecondsPerSecond), .tv_nsec = ns % NanoSecondsPerSecond }; + + s32 res; + do { + res = ::ppoll(std::addressof(pfd), 1, ns >= 0 ? std::addressof(ts) : nullptr, nullptr); + } while (res < 0 && errno == EINTR); + + AMS_ASSERT(res == 0 || res == 1); + + const bool signaled = pfd.revents & POLLIN; + AMS_ASSERT(signaled == (res == 1)); + return signaled; + } + + } + + bool InterProcessEventLinuxImpl::ResetEventSignal(NativeHandle handle) { + u64 dummy; + s32 res; + do { + res = ::read(handle, std::addressof(dummy), sizeof(dummy)); + } while (res < 0 && errno == EINTR); + + AMS_ASSERT(res == sizeof(u64) || (res < 0 && errno == EAGAIN)); + if (res == sizeof(u64)) { + AMS_ASSERT(dummy > 0); + } + + return res == sizeof(u64); + } + + Result InterProcessEventLinuxImpl::CreateSingle(NativeHandle *out_event) { + /* Create the event handle. */ + os::NativeHandle event; + do { + event = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + } while (event < 0 && errno == EINTR); + R_UNLESS(event != os::InvalidNativeHandle, os::ResultOutOfResource()); + + /* Set the output. */ + *out_event = event; + R_SUCCEED(); + } + + Result InterProcessEventLinuxImpl::Create(NativeHandle *out_write, NativeHandle *out_read) { + /* Create the writable handle. */ + os::NativeHandle write; + R_TRY(CreateSingle(std::addressof(write))); + ON_RESULT_FAILURE { Close(write); }; + + /* Create the read handle. */ + os::NativeHandle read; + do { + read = ::dup(write); + } while (read < 0 && errno == EINTR); + R_UNLESS(read != os::InvalidNativeHandle, os::ResultOutOfResource()); + + /* Set the output. */ + *out_write = write; + *out_read = read; + R_SUCCEED(); + } + + void InterProcessEventLinuxImpl::Close(NativeHandle handle) { + if (handle != os::InvalidNativeHandle) { + s32 ret; + do { + ret = ::close(handle); + } while (ret < 0 && errno == EINTR); + AMS_ASSERT(ret == 0); + } + } + + void InterProcessEventLinuxImpl::Signal(NativeHandle handle) { + const u64 counter_add = 1; + + s32 ret; + do { + ret = ::write(handle, std::addressof(counter_add), sizeof(counter_add)); + } while (ret < 0 && errno == EINTR); + AMS_ASSERT(ret == sizeof(counter_add)); + } + + void InterProcessEventLinuxImpl::Clear(NativeHandle handle) { + ResetEventSignal(handle); + } + + void InterProcessEventLinuxImpl::Wait(NativeHandle handle, bool auto_clear) { + while (true) { + /* Continuously wait, until success. */ + auto ret = PollEvent(handle, -1); + AMS_ASSERT(ret); + AMS_UNUSED(ret); + + /* If we're not obligated to clear, we're done. */ + if (!auto_clear) { + return; + } + + /* Try to reset. */ + if (ResetEventSignal(handle)) { + return; + } + } + } + + bool InterProcessEventLinuxImpl::TryWait(NativeHandle handle, bool auto_clear) { + /* If we're auto clear, just try to reset. */ + if (auto_clear) { + return ResetEventSignal(handle); + } + + return PollEvent(handle, 0); + } + + bool InterProcessEventLinuxImpl::TimedWait(NativeHandle handle, bool auto_clear, TimeSpan timeout) { + TimeoutHelper timeout_helper(timeout); + + do { + if (const auto res = PollEvent(handle, timeout_helper.GetTimeLeftOnTarget().GetNanoSeconds()); res) { + /* If we're not obligated to clear, we're done. */ + if (!auto_clear) { + return true; + } + + /* Try to reset. */ + if (ResetEventSignal(handle)) { + return true; + } + } + } while (!timeout_helper.TimedOut()); + + return false; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.linux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.linux.hpp new file mode 100644 index 00000000..d1732423 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.linux.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class InterProcessEventLinuxImpl { + public: + static Result CreateSingle(NativeHandle *out_event); + static Result Create(NativeHandle *out_write, NativeHandle *out_read); + static void Close(NativeHandle handle); + static void Signal(NativeHandle handle); + static void Clear(NativeHandle handle); + static void Wait(NativeHandle handle, bool auto_clear); + static bool TryWait(NativeHandle handle, bool auto_clear); + static bool TimedWait(NativeHandle handle, bool auto_clear, TimeSpan timeout); + private: + static bool ResetEventSignal(NativeHandle handle); + }; + + using InterProcessEventImpl = InterProcessEventLinuxImpl; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.macos.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.macos.cpp new file mode 100644 index 00000000..4270eec8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.macos.cpp @@ -0,0 +1,175 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_inter_process_event.hpp" +#include "os_inter_process_event_impl.os.macos.hpp" +#include "os_timeout_helper.hpp" + +#include <unistd.h> +#include <fcntl.h> +#include <poll.h> + +namespace ams::os::impl { + + namespace { + + /* On macOS, the maximum size of a pipe buffer is 64_KB. */ + static constexpr size_t PipeBufferSizeMax = 64_KB; + + constinit char g_shared_pipe_read_buffer[PipeBufferSizeMax]; + + bool PollEvent(NativeHandle handle, s64 ns) { + struct pollfd pfd; + pfd.fd = handle; + pfd.events = POLLIN; + pfd.revents = 0; + + /* Determine timeout. */ + constexpr s64 NanoSecondsPerMilliSecond = TimeSpan::FromMilliSeconds(1).GetNanoSeconds(); + + /* TODO: Will macos ever support ppoll? */ + const int timeout = static_cast<int>(ns >= 0 ? (ns / NanoSecondsPerMilliSecond) : -1); + + s32 res; + do { + res = ::poll(std::addressof(pfd), 1, timeout); + } while (res < 0 && errno == EINTR); + + AMS_ASSERT(res == 0 || res == 1); + + const bool signaled = pfd.revents & POLLIN; + AMS_ASSERT(signaled == (res == 1)); + return signaled; + } + + } + + bool InterProcessEventMacosImpl::ResetEventSignal(NativeHandle handle) { + s32 res; + do { + res = ::read(handle, g_shared_pipe_read_buffer, sizeof(g_shared_pipe_read_buffer)); + } while (res < 0 && errno == EINTR); + + if (res > 0) { + AMS_ASSERT(res <= static_cast<s32>(sizeof(g_shared_pipe_read_buffer))); + } else { + AMS_ASSERT(res < 0); + AMS_ASSERT(errno == EAGAIN); + } + + return res > 0; + } + + Result InterProcessEventMacosImpl::Create(NativeHandle *out_write, NativeHandle *out_read) { + /* Create the handles. */ + os::NativeHandle handles[2]; + s32 res; + do { + res = ::pipe(handles); + } while (res < 0 && errno == EINTR); + R_UNLESS(res == 0, os::ResultOutOfResource()); + ON_RESULT_FAILURE { Close(handles[0]); Close(handles[1]); }; + + /* Set as non-blocking. */ + do { + res = ::fcntl(handles[0], F_SETFL, O_NONBLOCK); + } while (res < 0 && errno == EINTR); + R_UNLESS(res == 0, os::ResultOutOfResource()); + + do { + res = ::fcntl(handles[1], F_SETFL, O_NONBLOCK); + } while (res < 0 && errno == EINTR); + R_UNLESS(res == 0, os::ResultOutOfResource()); + + /* Set the output. */ + *out_read = handles[0]; + *out_write = handles[1]; + R_SUCCEED(); + } + + void InterProcessEventMacosImpl::Close(NativeHandle handle) { + if (handle != os::InvalidNativeHandle) { + s32 ret; + do { + ret = ::close(handle); + } while (ret < 0 && errno == EINTR); + AMS_ASSERT(ret == 0); + } + } + + void InterProcessEventMacosImpl::Signal(NativeHandle handle) { + const u8 data = 0xCC; + + s32 ret; + do { + ret = ::write(handle, std::addressof(data), sizeof(data)); + } while (ret < 0 && errno == EINTR); + AMS_ASSERT(ret == sizeof(data) || (ret < 0 && errno == EAGAIN)); + } + + void InterProcessEventMacosImpl::Clear(NativeHandle handle) { + ResetEventSignal(handle); + } + + void InterProcessEventMacosImpl::Wait(NativeHandle handle, bool auto_clear) { + while (true) { + /* Continuously wait, until success. */ + auto ret = PollEvent(handle, -1); + AMS_ASSERT(ret); + AMS_UNUSED(ret); + + /* If we're not obligated to clear, we're done. */ + if (!auto_clear) { + return; + } + + /* Try to reset. */ + if (ResetEventSignal(handle)) { + return; + } + } + } + + bool InterProcessEventMacosImpl::TryWait(NativeHandle handle, bool auto_clear) { + /* If we're auto clear, just try to reset. */ + if (auto_clear) { + return ResetEventSignal(handle); + } + + return PollEvent(handle, 0); + } + + bool InterProcessEventMacosImpl::TimedWait(NativeHandle handle, bool auto_clear, TimeSpan timeout) { + TimeoutHelper timeout_helper(timeout); + + do { + if (const auto res = PollEvent(handle, timeout_helper.GetTimeLeftOnTarget().GetNanoSeconds()); res) { + /* If we're not obligated to clear, we're done. */ + if (!auto_clear) { + return true; + } + + /* Try to reset. */ + if (ResetEventSignal(handle)) { + return true; + } + } + } while (!timeout_helper.TimedOut()); + + return false; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.macos.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.macos.hpp new file mode 100644 index 00000000..e4c9e67d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.macos.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class InterProcessEventMacosImpl { + public: + static Result Create(NativeHandle *out_write, NativeHandle *out_read); + static void Close(NativeHandle handle); + static void Signal(NativeHandle handle); + static void Clear(NativeHandle handle); + static void Wait(NativeHandle handle, bool auto_clear); + static bool TryWait(NativeHandle handle, bool auto_clear); + static bool TimedWait(NativeHandle handle, bool auto_clear, TimeSpan timeout); + private: + static bool ResetEventSignal(NativeHandle handle); + }; + + using InterProcessEventImpl = InterProcessEventMacosImpl; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.windows.cpp new file mode 100644 index 00000000..7041d95c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.windows.cpp @@ -0,0 +1,125 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <stratosphere/windows.hpp> +#include "os_inter_process_event.hpp" +#include "os_inter_process_event_impl.os.windows.hpp" +#include "os_giant_lock.hpp" +#include "os_timeout_helper.hpp" + +namespace ams::os::impl { + + bool InterProcessEventWindowsImpl::ResetEventSignal(NativeHandle handle) { + std::scoped_lock lk(GetGiantLock()); + + if (auto ret = ::WaitForSingleObject(handle, 0); ret == WAIT_OBJECT_0) { + ::ResetEvent(handle); + return true; + } else { + return false; + } + } + + Result InterProcessEventWindowsImpl::Create(NativeHandle *out_write, NativeHandle *out_read) { + /* Create the writable handle. */ + auto write = ::CreateEvent(nullptr, true, false, nullptr); + AMS_ASSERT(write != os::InvalidNativeHandle); + + /* Create the read handle. */ + os::NativeHandle read; + const auto cur_proc = ::GetCurrentProcess(); + const auto ret = ::DuplicateHandle(cur_proc, write, cur_proc, std::addressof(read), 0, false, DUPLICATE_SAME_ACCESS); + AMS_ASSERT(ret != 0); + AMS_UNUSED(ret); + + /* Set the output. */ + *out_write = write; + *out_read = read; + R_SUCCEED(); + } + + void InterProcessEventWindowsImpl::Close(NativeHandle handle) { + if (handle != os::InvalidNativeHandle) { + const auto ret = ::CloseHandle(handle); + AMS_ASSERT(ret != 0); + AMS_UNUSED(ret); + } + } + + void InterProcessEventWindowsImpl::Signal(NativeHandle handle) { + const auto ret = ::SetEvent(handle); + AMS_ASSERT(ret != 0); + AMS_UNUSED(ret); + } + + void InterProcessEventWindowsImpl::Clear(NativeHandle handle) { + const auto ret = ::ResetEvent(handle); + AMS_ASSERT(ret != 0); + AMS_UNUSED(ret); + } + + void InterProcessEventWindowsImpl::Wait(NativeHandle handle, bool auto_clear) { + while (true) { + /* Continuously wait, until success. */ + auto ret = ::WaitForSingleObject(handle, INFINITE); + AMS_ASSERT(ret == WAIT_OBJECT_0); + AMS_UNUSED(ret); + + /* If we're not obligated to clear, we're done. */ + if (!auto_clear) { + return; + } + + /* Try to reset. */ + if (ResetEventSignal(handle)) { + return; + } + } + } + + bool InterProcessEventWindowsImpl::TryWait(NativeHandle handle, bool auto_clear) { + /* If we're auto clear, just try to reset. */ + if (auto_clear) { + return ResetEventSignal(handle); + } + + const auto ret = ::WaitForSingleObject(handle, 0); + AMS_ASSERT((ret == WAIT_OBJECT_0) || (ret == WAIT_TIMEOUT)); + + return ret == WAIT_OBJECT_0; + } + + bool InterProcessEventWindowsImpl::TimedWait(NativeHandle handle, bool auto_clear, TimeSpan timeout) { + TimeoutHelper timeout_helper(timeout); + + do { + if (const auto res = ::WaitForSingleObject(handle, timeout_helper.GetTimeLeftOnTarget()); res == WAIT_OBJECT_0) { + /* If we're not obligated to clear, we're done. */ + if (!auto_clear) { + return true; + } + + /* Try to reset. */ + if (ResetEventSignal(handle)) { + return true; + } + } + } while (!timeout_helper.TimedOut()); + + return false; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.windows.hpp new file mode 100644 index 00000000..3b1e30a5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_inter_process_event_impl.os.windows.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class InterProcessEventWindowsImpl { + public: + static Result Create(NativeHandle *out_write, NativeHandle *out_read); + static void Close(NativeHandle handle); + static void Signal(NativeHandle handle); + static void Clear(NativeHandle handle); + static void Wait(NativeHandle handle, bool auto_clear); + static bool TryWait(NativeHandle handle, bool auto_clear); + static bool TimedWait(NativeHandle handle, bool auto_clear, TimeSpan timeout); + private: + static bool ResetEventSignal(NativeHandle handle); + }; + + using InterProcessEventImpl = InterProcessEventWindowsImpl; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_busy_mutex_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_busy_mutex_impl.os.horizon.hpp new file mode 100644 index 00000000..8e7e1019 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_busy_mutex_impl.os.horizon.hpp @@ -0,0 +1,172 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_disable_counter.os.horizon.hpp" + +namespace ams::os::impl { + + namespace { + + ALWAYS_INLINE void PrefetchForBusyMutex(u32 *p) { + /* Nintendo does PRFM pstl1keep. */ + __builtin_prefetch(p, 1); + } + + ALWAYS_INLINE void WaitForEventsForBusyMutex() { + __asm__ __volatile__("wfe" ::: "memory"); + } + + ALWAYS_INLINE u32 LoadExclusiveForBusyMutex(u32 *p) { + u32 v; + __asm__ __volatile__("ldaxr %w[v], %[p]" : [v]"=&r"(v) : [p]"Q"(*p) : "memory"); + return v; + } + + ALWAYS_INLINE bool StoreExclusiveForBusyMutex(u32 *p, u32 v) { + int result; + __asm__ __volatile__("stxr %w[result], %w[v], %[p]" : [result]"=&r"(result) : [v]"r"(v), [p]"Q"(*p) : "memory"); + return result == 0; + } + + ALWAYS_INLINE void ClearExclusiveForBusyMutex() { + __asm__ __volatile__("clrex" ::: "memory"); + } + + ALWAYS_INLINE void StoreUnlockValueForBusyMutex(u32 *p) { + __asm__ __volatile__("stlr wzr, %[p]" :: [p]"Q"(*p) : "memory"); + } + + } + + void InternalBusyMutexImpl::Lock() { + /* Get the thread local region. */ + auto * const tlr = svc::GetThreadLocalRegion(); + + /* Determine disable counters. */ + const auto cur_dc = tlr->disable_count; + AMS_ASSERT(cur_dc < std::numeric_limits<decltype(cur_dc)>::max()); + const auto next_dc = cur_dc + 1; + + /* Check that we're allowed to use busy mutexes. */ + CallCheckBusyMutexPermission(); + + /* Get pointer to our value. */ + u32 * const p = std::addressof(m_value); + + /* Pre-fetch the busy mutex. */ + PrefetchForBusyMutex(p); + + /* Acquire the busy mutex. */ + while (true) { + /* Set the updated disable counter. */ + tlr->disable_count = next_dc; + + /* Try to acquire. */ + const u32 v = LoadExclusiveForBusyMutex(p); + if (AMS_LIKELY(v == 0) && AMS_LIKELY(StoreExclusiveForBusyMutex(p, 1))) { + break; + } + + /* Reset the disable counter, since we failed to acquire. */ + tlr->disable_count = cur_dc; + + /* If we don't hold any other busy mutexes, acknowledge any interrupts that occurred while we tried to acquire the lock. */ + if (cur_dc == 0 && tlr->interrupt_flag) { + svc::SynchronizePreemptionState(); + } + + /* If the lock is held by another core, wait for it to be released. */ + if (v != 0) { + WaitForEventsForBusyMutex(); + } + } + } + + bool InternalBusyMutexImpl::TryLock() { + /* Get the thread local region. */ + auto * const tlr = svc::GetThreadLocalRegion(); + + /* Determine disable counters. */ + const auto cur_dc = tlr->disable_count; + AMS_ASSERT(cur_dc < std::numeric_limits<decltype(cur_dc)>::max()); + const auto next_dc = cur_dc + 1; + + /* Check that we're allowed to use busy mutexes. */ + CallCheckBusyMutexPermission(); + + /* Get pointer to our value. */ + u32 * const p = std::addressof(m_value); + + /* Pre-fetch the busy mutex. */ + PrefetchForBusyMutex(p); + + /* Try to acquire the busy mutex. */ + while (true) { + /* Set the updated disable counter. */ + tlr->disable_count = next_dc; + + /* Ensure we do whatever cleanup we need to. */ + auto release_guard = SCOPE_GUARD { + /* Reset disable counter. */ + tlr->disable_count = cur_dc; + + /* If we don't hold any other busy mutexes, acknowledge any interrupts that occurred while we tried to acquire the lock. */ + if (cur_dc == 0 && tlr->interrupt_flag) { + svc::SynchronizePreemptionState(); + } + }; + + /* Try to acquire. */ + const u32 v = LoadExclusiveForBusyMutex(p); + if (AMS_UNLIKELY(v != 0)) { + ClearExclusiveForBusyMutex(); + return false; + } + + if (AMS_LIKELY(StoreExclusiveForBusyMutex(p, 1))) { + /* We successfully acquired the busy mutex. */ + release_guard.Cancel(); + return true; + } + } + } + + void InternalBusyMutexImpl::Unlock() { + /* Get pointer to our value. */ + u32 * const p = std::addressof(m_value); + + /* Unlock the mutex. */ + StoreUnlockValueForBusyMutex(p); + + /* Get the thread local region. */ + auto * const tlr = svc::GetThreadLocalRegion(); + + /* Determine disable counters. */ + const auto cur_dc = tlr->disable_count; + AMS_ASSERT(cur_dc != 0); + const auto next_dc = cur_dc - 1; + + /* Decrement disable count. */ + tlr->disable_count = next_dc; + + /* If we don't hold any other busy mutexes, acknowledge any interrupts that occurred while we held the lock. */ + if (next_dc == 0 && tlr->interrupt_flag) { + svc::SynchronizePreemptionState(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_condition_variable_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_condition_variable_impl.os.horizon.cpp new file mode 100644 index 00000000..129b391d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_condition_variable_impl.os.horizon.cpp @@ -0,0 +1,56 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_timeout_helper.hpp" +#include "os_thread_manager.hpp" + +namespace ams::os::impl { + + void InternalConditionVariableImpl::Signal() { + ams::svc::SignalProcessWideKey(reinterpret_cast<uintptr_t>(std::addressof(m_value)), 1); + } + + void InternalConditionVariableImpl::Broadcast() { + ams::svc::SignalProcessWideKey(reinterpret_cast<uintptr_t>(std::addressof(m_value)), -1); + } + + void InternalConditionVariableImpl::Wait(InternalCriticalSection *cs) { + const auto cur_handle = GetCurrentThreadHandle(); + AMS_ASSERT((cs->Get()->m_thread_handle & ~ams::svc::HandleWaitMask) == cur_handle); + + R_ABORT_UNLESS(ams::svc::WaitProcessWideKeyAtomic(reinterpret_cast<uintptr_t>(std::addressof(cs->Get()->m_thread_handle)), reinterpret_cast<uintptr_t>(std::addressof(m_value)), cur_handle, -1)); + } + + ConditionVariableStatus InternalConditionVariableImpl::TimedWait(InternalCriticalSection *cs, const TimeoutHelper &timeout_helper) { + const auto cur_handle = GetCurrentThreadHandle(); + AMS_ASSERT((cs->Get()->m_thread_handle & ~ams::svc::HandleWaitMask) == cur_handle); + + const TimeSpan left = timeout_helper.GetTimeLeftOnTarget(); + if (left > 0) { + R_TRY_CATCH(ams::svc::WaitProcessWideKeyAtomic(reinterpret_cast<uintptr_t>(std::addressof(cs->Get()->m_thread_handle)), reinterpret_cast<uintptr_t>(std::addressof(m_value)), cur_handle, left.GetNanoSeconds())) { + R_CATCH(svc::ResultTimedOut) { + cs->Enter(); + return ConditionVariableStatus::TimedOut; + } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + return ConditionVariableStatus::Success; + } else { + return ConditionVariableStatus::TimedOut; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_condition_variable_impl.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_condition_variable_impl.os.windows.cpp new file mode 100644 index 00000000..2ffcc89c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_condition_variable_impl.os.windows.cpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <stratosphere/windows.hpp> +#include "os_internal_critical_section_impl.os.windows.hpp" +#include "os_timeout_helper.hpp" +#include "os_thread_manager.hpp" + +namespace ams::os::impl { + + struct WindowsConditionVariable { + CONDITION_VARIABLE cv; + }; + + void InternalConditionVariableImpl::Initialize() { + ::InitializeConditionVariable(std::addressof(util::GetReference(m_windows_cv_storage).cv)); + } + + void InternalConditionVariableImpl::Finalize() { + /* ... */ + } + + void InternalConditionVariableImpl::Signal() { + ::WakeConditionVariable(std::addressof(util::GetReference(m_windows_cv_storage).cv)); + } + + void InternalConditionVariableImpl::Broadcast() { + ::WakeAllConditionVariable(std::addressof(util::GetReference(m_windows_cv_storage).cv)); + } + + void InternalConditionVariableImpl::Wait(InternalCriticalSection *cs) { + ::SleepConditionVariableCS(std::addressof(util::GetReference(m_windows_cv_storage).cv), std::addressof(util::GetReference(cs->Get()->m_windows_critical_section_storage).cs), INFINITE); + } + + ConditionVariableStatus InternalConditionVariableImpl::TimedWait(InternalCriticalSection *cs, const TimeoutHelper &timeout_helper) { + const auto ret = ::SleepConditionVariableCS(std::addressof(util::GetReference(m_windows_cv_storage).cv), std::addressof(util::GetReference(cs->Get()->m_windows_critical_section_storage).cs), timeout_helper.GetTimeLeftOnTarget()); + if (ret == 0) { + if (::GetLastError() == ERROR_TIMEOUT) { + return ConditionVariableStatus::TimedOut; + } + } + + return ConditionVariableStatus::Success; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_condition_variable_impl.pthread.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_condition_variable_impl.pthread.cpp new file mode 100644 index 00000000..c18a98bf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_condition_variable_impl.pthread.cpp @@ -0,0 +1,73 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_timeout_helper.hpp" +#include "os_thread_manager.hpp" + +#if defined(AMS_OS_IMPL_USE_PTHREADS) +namespace ams::os::impl { + + namespace { + + } + + void InternalConditionVariableImpl::Initialize() { + /* TODO: What should be done here? */ + } + + void InternalConditionVariableImpl::Finalize() { + /* TODO: What should be done here? */ + } + + void InternalConditionVariableImpl::Signal() { + AMS_ABORT_UNLESS(pthread_cond_signal(std::addressof(m_pthread_cond)) == 0); + } + + void InternalConditionVariableImpl::Broadcast() { + AMS_ABORT_UNLESS(pthread_cond_broadcast(std::addressof(m_pthread_cond)) == 0); + } + + void InternalConditionVariableImpl::Wait(InternalCriticalSection *cs) { + AMS_ABORT_UNLESS(pthread_cond_wait(std::addressof(m_pthread_cond), std::addressof(cs->Get()->m_pthread_mutex)) == 0); + } + + ConditionVariableStatus InternalConditionVariableImpl::TimedWait(InternalCriticalSection *cs, const TimeoutHelper &timeout_helper) { + struct timespec ts; + const auto gettime_res = clock_gettime(CLOCK_REALTIME, std::addressof(ts)); + AMS_ASSERT(gettime_res == 0); + AMS_UNUSED(gettime_res); + + constexpr s64 NanoSecondsPerSecond = TimeSpan::FromSeconds(1).GetNanoSeconds(); + const s64 ns = timeout_helper.GetTimeLeftOnTarget().GetNanoSeconds(); + ts.tv_sec += (ns / NanoSecondsPerSecond); + ts.tv_nsec += (ns % NanoSecondsPerSecond); + if (ts.tv_nsec >= NanoSecondsPerSecond) { + ts.tv_sec += ts.tv_nsec / NanoSecondsPerSecond; + ts.tv_nsec %= NanoSecondsPerSecond; + } + + const auto res = pthread_cond_timedwait(std::addressof(m_pthread_cond), std::addressof(cs->Get()->m_pthread_mutex), std::addressof(ts)); + + if (res != 0) { + AMS_ABORT_UNLESS(res == ETIMEDOUT); + return ConditionVariableStatus::TimedOut; + } + + return ConditionVariableStatus::Success; + } + +} +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_critical_section_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_critical_section_impl.os.horizon.cpp new file mode 100644 index 00000000..dfda7b07 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_critical_section_impl.os.horizon.cpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_timeout_helper.hpp" +#include "os_thread_manager.hpp" + +namespace ams::os::impl { + + #if defined(ATMOSPHERE_ARCH_ARM64) + + void InternalCriticalSectionImpl::Enter() { + AMS_ASSERT(svc::GetThreadLocalRegion()->disable_count == 0); + + /* Use the libnx impl. */ + static_assert(std::is_same<decltype(m_thread_handle), ::Mutex>::value); + return ::mutexLock(std::addressof(m_thread_handle)); + } + + bool InternalCriticalSectionImpl::TryEnter() { + AMS_ASSERT(svc::GetThreadLocalRegion()->disable_count == 0); + + /* Use the libnx impl. */ + static_assert(std::is_same<decltype(m_thread_handle), ::Mutex>::value); + return ::mutexTryLock(std::addressof(m_thread_handle)); + } + + void InternalCriticalSectionImpl::Leave() { + AMS_ASSERT(svc::GetThreadLocalRegion()->disable_count == 0); + + /* Use the libnx impl. */ + static_assert(std::is_same<decltype(m_thread_handle), ::Mutex>::value); + return ::mutexUnlock(std::addressof(m_thread_handle)); + } + + bool InternalCriticalSectionImpl::IsLockedByCurrentThread() const { + /* Use the libnx impl. */ + static_assert(std::is_same<decltype(m_thread_handle), ::Mutex>::value); + return ::mutexIsLockedByCurrentThread(std::addressof(m_thread_handle)); + } + + #else + + #error "Architecture not yet supported for os::InternalCriticalSectionImpl" + + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_critical_section_impl.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_critical_section_impl.os.windows.cpp new file mode 100644 index 00000000..7acb2193 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_critical_section_impl.os.windows.cpp @@ -0,0 +1,65 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <stratosphere/windows.hpp> +#include "os_internal_critical_section_impl.os.windows.hpp" + +namespace ams::os::impl { + + void InternalCriticalSectionImpl::Initialize() { + ::InitializeCriticalSection(std::addressof(util::GetReference(m_windows_critical_section_storage).cs)); + } + + void InternalCriticalSectionImpl::Finalize() { + ::DeleteCriticalSection(std::addressof(util::GetReference(m_windows_critical_section_storage).cs)); + } + + void InternalCriticalSectionImpl::Enter() { + ::EnterCriticalSection(std::addressof(util::GetReference(m_windows_critical_section_storage).cs)); + } + + bool InternalCriticalSectionImpl::TryEnter() { + return ::TryEnterCriticalSection(std::addressof(util::GetReference(m_windows_critical_section_storage).cs)) != 0; + } + + void InternalCriticalSectionImpl::Leave() { + return ::LeaveCriticalSection(std::addressof(util::GetReference(m_windows_critical_section_storage).cs)); + } + + bool InternalCriticalSectionImpl::IsLockedByCurrentThread() const { + /* Get the cs. */ + CRITICAL_SECTION * const cs = std::addressof(util::GetReference(m_windows_critical_section_storage).cs); + + /* If the critical section has no owning thread, it's not locked. */ + if (cs->OwningThread == nullptr) { + return false; + } + + /* If it has an owner, TryLock() will succeed if and only if we own the critical section. */ + if (::TryEnterCriticalSection(cs) == 0) { + return false; + } + + /* We now hold the critical section. To avoid a race, check that we didn't just acquire it by chance. */ + const auto holds_lock = cs->RecursionCount > 1; + + /* Leave, since we just successfully entered. */ + ::LeaveCriticalSection(cs); + + return holds_lock; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_critical_section_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_critical_section_impl.os.windows.hpp new file mode 100644 index 00000000..d522a303 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_critical_section_impl.os.windows.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + struct WindowsCriticalSection { + mutable CRITICAL_SECTION cs; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_critical_section_impl.pthread.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_critical_section_impl.pthread.cpp new file mode 100644 index 00000000..e1d0b80e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_critical_section_impl.pthread.cpp @@ -0,0 +1,75 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#if defined(ATMOSPHERE_OS_LINUX) +#include <sys/syscall.h> +#include <unistd.h> +#endif + +#if defined(AMS_OS_IMPL_USE_PTHREADS) +namespace ams::os::impl { + + #if defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + namespace { + + #if defined(ATMOSPHERE_OS_LINUX) + + template<typename T> + concept IsGlibcPthreadMutexImplementationWithOwnerField = requires (T &t) { + { t.__data.__owner } -> std::convertible_to<int>; + }; + + static_assert(IsGlibcPthreadMutexImplementationWithOwnerField<pthread_mutex_t>); + + #endif + + + } + #endif + + void InternalCriticalSectionImpl::Initialize() { + /* TODO: What should be done here? */ + } + + void InternalCriticalSectionImpl::Finalize() { + /* TODO: What should be done here? */ + } + + void InternalCriticalSectionImpl::Enter() { + AMS_ABORT_UNLESS(pthread_mutex_lock(std::addressof(m_pthread_mutex)) == 0); + } + + bool InternalCriticalSectionImpl::TryEnter() { + return pthread_mutex_trylock(std::addressof(m_pthread_mutex)) == 0; + } + + void InternalCriticalSectionImpl::Leave() { + AMS_ABORT_UNLESS(pthread_mutex_unlock(std::addressof(m_pthread_mutex)) == 0); + } + + #if defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + bool InternalCriticalSectionImpl::IsLockedByCurrentThread() const { + /* TODO: This should be less of a terrible hack. */ + #if defined(ATMOSPHERE_OS_LINUX) + return m_pthread_mutex.__data.__owner == ::syscall(SYS_gettid); + #else + #error "Unknown OS/underlying implementation for pthread InternalCriticalSectionImpl::IsLockedByCurrentThread()" + #endif + } + #endif + +} +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_light_event_impl.os.generic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_light_event_impl.os.generic.hpp new file mode 100644 index 00000000..6d497464 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_light_event_impl.os.generic.hpp @@ -0,0 +1,241 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + namespace { + + constexpr inline u8 LightEventState_NotSignaled = -1; + constexpr inline u8 LightEventState_Invalid = 0; + constexpr inline u8 LightEventState_Signaled = 1; + + static_assert(LightEventState_NotSignaled == 0xFF); + + ALWAYS_INLINE bool AtomicAutoClearLightEvent(std::atomic<u8> &signal_state) { + u8 expected = LightEventState_Signaled; + return signal_state.compare_exchange_strong(expected, LightEventState_NotSignaled); + } + + ALWAYS_INLINE u32 GetBroadcastCounterUnsafe(u16 &low, u8 &high) { + const u32 upper = high; + return (upper << BITSIZEOF(low)) | low; + } + + ALWAYS_INLINE void IncrementBroadcastCounterUnsafe(u16 &low, u8 &high) { + if ((++low) == 0) { + ++high; + } + } + + } + + void InternalLightEventImpl::Initialize(bool signaled) { + /* Set broadcast counter. */ + m_counter_low = 0; + m_counter_high = 0; + + /* Set initial state. */ + m_signal_state = signaled ? LightEventState_Signaled : LightEventState_NotSignaled; + } + + void InternalLightEventImpl::Finalize() { + /* Set final state. */ + m_signal_state = LightEventState_Invalid; + } + + void InternalLightEventImpl::SignalWithAutoClear() { + /* Signal only if we need to. */ + if (m_signal_state.load(std::memory_order_acquire) == LightEventState_NotSignaled) { + /* Lock ourselves. */ + std::scoped_lock lk(m_cs); + + /* Set our state to signaled. */ + m_signal_state = LightEventState_Signaled; + + /* Signal to any waiters. */ + m_cv.Signal(); + } + } + + void InternalLightEventImpl::SignalWithManualClear() { + /* Signal only if we need to. */ + if (m_signal_state.load(std::memory_order_acquire) == LightEventState_NotSignaled) { + /* Lock ourselves. */ + std::scoped_lock lk(m_cs); + + /* Set our state to signaled. */ + m_signal_state = LightEventState_Signaled; + + /* Increment our broadcast counter. */ + IncrementBroadcastCounterUnsafe(m_counter_low, m_counter_high); + + /* Broadcast to any waiters. */ + m_cv.Broadcast(); + } + } + + void InternalLightEventImpl::Clear() { + /* Clear only if we need to. */ + if (m_signal_state.load(std::memory_order_acquire) == LightEventState_Signaled) { + /* Lock ourselves. */ + std::scoped_lock lk(m_cs); + + /* Set our state to signaled. */ + m_signal_state = LightEventState_NotSignaled; + } + } + + void InternalLightEventImpl::WaitWithAutoClear() { + /* Loop waiting. */ + while (true) { + /* Get the current state. */ + const auto state = m_signal_state.load(std::memory_order_acquire); + + /* If we're not signaled, stop looping so we can properly wait to be signaled. */ + if (state == LightEventState_NotSignaled) { + break; + } + + /* If we're signaled, try to reset. */ + if (state == LightEventState_Signaled && AtomicAutoClearLightEvent(m_signal_state)) { + return; + } + } + + /* Wait explicitly on the not-signaled event. */ + std::scoped_lock lk(m_cs); + + while (m_signal_state == LightEventState_NotSignaled) { + m_cv.Wait(std::addressof(m_cs)); + } + + /* Set our state to not signaled. */ + m_signal_state = LightEventState_NotSignaled; + } + + void InternalLightEventImpl::WaitWithManualClear() { + /* Loop waiting. */ + while (true) { + /* Get the current m_signal_state. */ + const auto state = m_signal_state.load(std::memory_order_acquire); + + /* If we're not signaled, stop looping so we can properly wait to be signaled. */ + if (state == LightEventState_NotSignaled) { + break; + } + + /* If we're signaled, we're done. */ + if (state == LightEventState_Signaled) { + return; + } + } + + /* Wait explicitly on the not-signaled event. */ + std::scoped_lock lk(m_cs); + + /* Get the broadcast counter. */ + const auto bc = GetBroadcastCounterUnsafe(m_counter_low, m_counter_high); + + while (m_signal_state == LightEventState_NotSignaled) { + /* Check if a broadcast has occurred. */ + if (bc != GetBroadcastCounterUnsafe(m_counter_low, m_counter_high)) { + break; + } + + m_cv.Wait(std::addressof(m_cs)); + } + } + + bool InternalLightEventImpl::TryWaitWithAutoClear() { + return m_signal_state.load(std::memory_order_acquire) == LightEventState_Signaled && AtomicAutoClearLightEvent(m_signal_state); + } + + bool InternalLightEventImpl::TryWaitWithManualClear() { + return m_signal_state.load(std::memory_order_acquire) == LightEventState_Signaled; + } + + bool InternalLightEventImpl::TimedWaitWithAutoClear(const TimeoutHelper &timeout_helper) { + /* Loop waiting. */ + while (true) { + /* Get the current state. */ + const auto state = m_signal_state.load(std::memory_order_acquire); + + /* If we're not signaled, stop looping so we can properly wait to be signaled. */ + if (state == LightEventState_NotSignaled) { + break; + } + + /* If we're signaled, try to reset. */ + if (state == LightEventState_Signaled && AtomicAutoClearLightEvent(m_signal_state)) { + return true; + } + } + + /* Wait explicitly on the not-signaled event. */ + std::scoped_lock lk(m_cs); + + while (m_signal_state == LightEventState_NotSignaled) { + if (m_cv.TimedWait(std::addressof(m_cs), timeout_helper) == ConditionVariableStatus::TimedOut) { + return false; + } + } + + /* Set our state to not signaled. */ + m_signal_state = LightEventState_NotSignaled; + + return true; + } + + bool InternalLightEventImpl::TimedWaitWithManualClear(const TimeoutHelper &timeout_helper) { + /* Loop waiting. */ + while (true) { + /* Get the current m_signal_state. */ + const auto state = m_signal_state.load(std::memory_order_acquire); + + /* If we're not signaled, stop looping so we can properly wait to be signaled. */ + if (state == LightEventState_NotSignaled) { + break; + } + + /* If we're signaled, we're done. */ + if (state == LightEventState_Signaled) { + return true; + } + } + + /* Wait explicitly on the not-signaled event. */ + std::scoped_lock lk(m_cs); + + /* Get the broadcast counter. */ + const auto bc = GetBroadcastCounterUnsafe(m_counter_low, m_counter_high); + + while (m_signal_state == LightEventState_NotSignaled) { + /* Check if a broadcast has occurred. */ + if (bc != GetBroadcastCounterUnsafe(m_counter_low, m_counter_high)) { + break; + } + + if (m_cv.TimedWait(std::addressof(m_cs), timeout_helper) == ConditionVariableStatus::TimedOut) { + return false; + } + } + + return true; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_light_event_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_light_event_impl.os.horizon.hpp new file mode 100644 index 00000000..eb17f2b3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_light_event_impl.os.horizon.hpp @@ -0,0 +1,327 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + namespace { + + constexpr inline s32 LightEventState_NotSignaledAndNoWaiter = 0; + constexpr inline s32 LightEventState_NotSignaledAndWaiter = 1; + constexpr inline s32 LightEventState_Signaled = 2; + + ALWAYS_INLINE uintptr_t GetAddressOfAtomicInteger(std::atomic<s32> *ptr) { + static_assert(sizeof(*ptr) == sizeof(s32)); + static_assert(alignof(*ptr) == alignof(s32)); + static_assert(std::atomic<s32>::is_always_lock_free); + + return reinterpret_cast<uintptr_t>(ptr); + } + + ALWAYS_INLINE bool SvcSignalToAddressForAutoClear(std::atomic<s32> *state, s32 expected) { + /* Signal. */ + R_TRY_CATCH(svc::SignalToAddress(GetAddressOfAtomicInteger(state), svc::SignalType_SignalAndModifyByWaitingCountIfEqual, expected, 1)) { + R_CATCH(svc::ResultInvalidState) { return false; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + return true; + } + + ALWAYS_INLINE bool SvcSignalToAddressForManualClear(std::atomic<s32> *state, s32 expected) { + /* Signal. */ + R_TRY_CATCH(svc::SignalToAddress(GetAddressOfAtomicInteger(state), svc::SignalType_SignalAndIncrementIfEqual, expected, -1)) { + R_CATCH(svc::ResultInvalidState) { return false; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + return true; + } + + ALWAYS_INLINE bool SvcWaitForAddressIfEqual(std::atomic<s32> *state) { + /* Wait. */ + R_TRY_CATCH(svc::WaitForAddress(GetAddressOfAtomicInteger(state), svc::ArbitrationType_WaitIfEqual, LightEventState_NotSignaledAndWaiter, -1ll)) { + R_CATCH(svc::ResultInvalidState) { return false; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + return true; + } + + ALWAYS_INLINE bool SvcWaitForAddressIfEqual(bool *out_timed_out, std::atomic<s32> *state, s64 timeout) { + /* Default to not timed out. */ + *out_timed_out = false; + + /* Wait. */ + R_TRY_CATCH(svc::WaitForAddress(GetAddressOfAtomicInteger(state), svc::ArbitrationType_WaitIfEqual, LightEventState_NotSignaledAndWaiter, timeout)) { + R_CATCH(svc::ResultInvalidState) { return false; } + R_CATCH(svc::ResultTimedOut) { *out_timed_out = true; return false; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + return true; + } + + ALWAYS_INLINE s32 LoadAcquireExclusiveForLightEvent(s32 *p) { + s32 v; + __asm__ __volatile__("ldaxr %w[v], %[p]" : [v]"=&r"(v) : [p]"Q"(*p) : "memory"); + return v; + } + + ALWAYS_INLINE bool StoreReleaseExclusiveForLightEvent(s32 *p, s32 v) { + int result; + __asm__ __volatile__("stlxr %w[result], %w[v], %[p]" : [result]"=&r"(result) : [v]"r"(v), [p]"Q"(*p) : "memory"); + return result == 0; + } + + ALWAYS_INLINE void ClearExclusiveForLightEvent() { + __asm__ __volatile__("clrex" ::: "memory"); + } + + ALWAYS_INLINE bool CompareAndSwap(std::atomic<s32> *state, s32 expected, s32 desired) { + /* NOTE: This works around gcc not emitting clrex on ldaxr fail. */ + s32 * const state_ptr = reinterpret_cast<s32 *>(GetAddressOfAtomicInteger(state)); + + while (true) { + if (AMS_UNLIKELY(LoadAcquireExclusiveForLightEvent(state_ptr) != expected)) { + ClearExclusiveForLightEvent(); + return false; + } + + if (AMS_LIKELY(StoreReleaseExclusiveForLightEvent(state_ptr, desired))) { + return true; + } + } + } + + } + + void InternalLightEventImpl::Initialize(bool signaled) { + /* Set initial state. */ + m_state.store(signaled ? LightEventState_Signaled : LightEventState_NotSignaledAndNoWaiter, std::memory_order_relaxed); + } + + void InternalLightEventImpl::Finalize() { + /* ... */ + } + + void InternalLightEventImpl::SignalWithAutoClear() { + /* Loop until signaled */ + while (true) { + /* Fence memory. */ + std::atomic_thread_fence(std::memory_order_seq_cst); + + /* Get the current state. */ + const auto state = m_state.load(std::memory_order_relaxed); + switch (state) { + case LightEventState_NotSignaledAndNoWaiter: + /* Try to signal. */ + if (CompareAndSwap(std::addressof(m_state), state, LightEventState_Signaled)) { + return; + } + break; + case LightEventState_NotSignaledAndWaiter: + /* Try to signal. */ + if (SvcSignalToAddressForAutoClear(std::addressof(m_state), state)) { + return; + } + break; + case LightEventState_Signaled: + /* If we're already signaled, we're done. */ + return; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + void InternalLightEventImpl::SignalWithManualClear() { + /* Loop until signaled */ + while (true) { + /* Fence memory. */ + std::atomic_thread_fence(std::memory_order_seq_cst); + + /* Get the current state. */ + const auto state = m_state.load(std::memory_order_relaxed); + switch (state) { + case LightEventState_NotSignaledAndNoWaiter: + /* Try to signal. */ + if (CompareAndSwap(std::addressof(m_state), state, LightEventState_Signaled)) { + return; + } + break; + case LightEventState_NotSignaledAndWaiter: + /* Try to signal. */ + if (SvcSignalToAddressForManualClear(std::addressof(m_state), state)) { + return; + } + break; + case LightEventState_Signaled: + /* If we're already signaled, we're done. */ + return; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + void InternalLightEventImpl::Clear() { + /* Fence memory. */ + std::atomic_thread_fence(std::memory_order_acquire); + ON_SCOPE_EXIT { std::atomic_thread_fence(std::memory_order_release); }; + + /* Change state from signaled to not-signaled. */ + CompareAndSwap(std::addressof(m_state), LightEventState_Signaled, LightEventState_NotSignaledAndNoWaiter); + } + + void InternalLightEventImpl::WaitWithAutoClear() { + /* When we're done, fence memory. */ + ON_SCOPE_EXIT { std::atomic_thread_fence(std::memory_order_seq_cst); }; + + /* Loop waiting. */ + while (true) { + /* Get the current state. */ + const auto state = m_state.load(std::memory_order_acquire); + switch (state) { + case LightEventState_NotSignaledAndNoWaiter: + /* Change state to have waiter. */ + if (!CompareAndSwap(std::addressof(m_state), state, LightEventState_NotSignaledAndWaiter)) { + break; + } + [[fallthrough]]; + case LightEventState_NotSignaledAndWaiter: + /* Wait for the address. */ + if (SvcWaitForAddressIfEqual(std::addressof(m_state))) { + return; + } + break; + case LightEventState_Signaled: + /* Change state to no-waiters. */ + if (CompareAndSwap(std::addressof(m_state), state, LightEventState_NotSignaledAndNoWaiter)) { + return; + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + void InternalLightEventImpl::WaitWithManualClear() { + /* When we're done, fence memory. */ + ON_SCOPE_EXIT { std::atomic_thread_fence(std::memory_order_seq_cst); }; + + /* Loop waiting. */ + while (true) { + /* Get the current state. */ + const auto state = m_state.load(std::memory_order_acquire); + switch (state) { + case LightEventState_NotSignaledAndNoWaiter: + /* Change state to have waiter. */ + if (!CompareAndSwap(std::addressof(m_state), state, LightEventState_NotSignaledAndWaiter)) { + break; + } + [[fallthrough]]; + case LightEventState_NotSignaledAndWaiter: + /* Wait for the address. */ + SvcWaitForAddressIfEqual(std::addressof(m_state)); + return; + case LightEventState_Signaled: + /* We're signaled, so return. */ + return; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + bool InternalLightEventImpl::TryWaitWithAutoClear() { + /* When we're done, fence memory. */ + ON_SCOPE_EXIT { std::atomic_thread_fence(std::memory_order_seq_cst); }; + + return CompareAndSwap(std::addressof(m_state), LightEventState_Signaled, LightEventState_NotSignaledAndNoWaiter); + } + + bool InternalLightEventImpl::TryWaitWithManualClear() { + /* When we're done, fence memory. */ + ON_SCOPE_EXIT { std::atomic_thread_fence(std::memory_order_seq_cst); }; + + return m_state.load(std::memory_order_acquire) == LightEventState_Signaled; + } + + bool InternalLightEventImpl::TimedWaitWithAutoClear(const TimeoutHelper &timeout_helper) { + /* When we're done, fence memory. */ + ON_SCOPE_EXIT { std::atomic_thread_fence(std::memory_order_seq_cst); }; + + /* Loop waiting. */ + while (true) { + /* Get the current state. */ + const auto state = m_state.load(std::memory_order_acquire); + switch (state) { + case LightEventState_NotSignaledAndNoWaiter: + /* Change state to have waiter. */ + if (!CompareAndSwap(std::addressof(m_state), state, LightEventState_NotSignaledAndWaiter)) { + break; + } + [[fallthrough]]; + case LightEventState_NotSignaledAndWaiter: + /* Wait for the address, checking timeout. */ + { + bool timed_out; + if (SvcWaitForAddressIfEqual(std::addressof(timed_out), std::addressof(m_state), timeout_helper.GetTimeLeftOnTarget().GetNanoSeconds())) { + return true; + } + if (timed_out) { + return false; + } + } + break; + case LightEventState_Signaled: + /* Try to clear. */ + if (CompareAndSwap(std::addressof(m_state), LightEventState_Signaled, LightEventState_NotSignaledAndNoWaiter)) { + return true; + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + bool InternalLightEventImpl::TimedWaitWithManualClear(const TimeoutHelper &timeout_helper) { + /* When we're done, fence memory. */ + ON_SCOPE_EXIT { std::atomic_thread_fence(std::memory_order_seq_cst); }; + + /* Loop waiting. */ + while (true) { + /* Get the current state. */ + const auto state = m_state.load(std::memory_order_acquire); + switch (state) { + case LightEventState_NotSignaledAndNoWaiter: + /* Change state to have waiter. */ + if (!CompareAndSwap(std::addressof(m_state), state, LightEventState_NotSignaledAndWaiter)) { + break; + } + [[fallthrough]]; + case LightEventState_NotSignaledAndWaiter: + /* Wait and check for timeout. */ + { + bool timed_out; + SvcWaitForAddressIfEqual(std::addressof(timed_out), std::addressof(m_state), timeout_helper.GetTimeLeftOnTarget().GetNanoSeconds()); + return !timed_out; + } + break; + case LightEventState_Signaled: + /* We're signaled, so return. */ + return true; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_rw_busy_mutex_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_rw_busy_mutex_impl.os.horizon.hpp new file mode 100644 index 00000000..9d74be31 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_rw_busy_mutex_impl.os.horizon.hpp @@ -0,0 +1,254 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_disable_counter.os.horizon.hpp" + +namespace ams::os::impl { + + namespace { + + ALWAYS_INLINE void PrefetchForBusyMutex(u32 *p) { + /* Nintendo does PRFM pstl1keep. */ + __builtin_prefetch(p, 1); + } + + ALWAYS_INLINE void SendEventLocalForBusyMutex() { + __asm__ __volatile__("sevl" ::: "memory"); + } + + ALWAYS_INLINE void WaitForEventsForBusyMutex() { + __asm__ __volatile__("wfe" ::: "memory"); + } + + ALWAYS_INLINE u32 LoadAcquireExclusiveForBusyMutex(u32 *p) { + u32 v; + __asm__ __volatile__("ldaxr %w[v], %[p]" : [v]"=&r"(v) : [p]"Q"(*p) : "memory"); + return v; + } + + ALWAYS_INLINE u32 LoadExclusiveForBusyMutex(u32 *p) { + u32 v; + __asm__ __volatile__("ldxr %w[v], %[p]" : [v]"=&r"(v) : [p]"Q"(*p) : "memory"); + return v; + } + + ALWAYS_INLINE bool StoreReleaseExclusiveForBusyMutex(u32 *p, u32 v) { + int result; + __asm__ __volatile__("stlxr %w[result], %w[v], %[p]" : [result]"=&r"(result) : [v]"r"(v), [p]"Q"(*p) : "memory"); + return result == 0; + } + + ALWAYS_INLINE bool StoreExclusiveForBusyMutex(u32 *p, u32 v) { + int result; + __asm__ __volatile__("stxr %w[result], %w[v], %[p]" : [result]"=&r"(result) : [v]"r"(v), [p]"Q"(*p) : "memory"); + return result == 0; + } + + ALWAYS_INLINE void StoreReleaseWriteLockValueForBusyMutex(u32 *p) { + u8 * const p8 = InternalReaderWriterBusyMutexValue::GetWriterCurrentPointer(p); + + u8 v; + __asm__ __volatile__("ldrb %w[v], %[p8]\n" + "add %w[v], %w[v], #1\n" + "stlrb %w[v], %[p8]\n" + : [v]"=&r"(v) + : [p8]"Q"(*p8) + : "memory"); + } + + } + + void InternalReaderWriterBusyMutexImpl::AcquireReadLock() { + /* Get the thread local region. */ + auto * const tlr = svc::GetThreadLocalRegion(); + + /* Determine disable counters. */ + const auto cur_dc = tlr->disable_count; + AMS_ABORT_UNLESS(cur_dc < std::numeric_limits<decltype(cur_dc)>::max()); + const auto next_dc = cur_dc + 1; + + /* Check that we're allowed to use busy mutexes. */ + CallCheckBusyMutexPermission(); + + /* Get pointer to our value. */ + u32 * const p = std::addressof(m_value); + + /* Pre-fetch the busy mutex. */ + PrefetchForBusyMutex(p); + + /* Acquire the read-lock for the mutex. */ + while (true) { + /* Set the updated disable counter. */ + tlr->disable_count = next_dc; + + /* Try to acquire. */ + const u32 v = LoadAcquireExclusiveForBusyMutex(p); + + /* We can only acquire read lock if not write-locked. */ + const bool write_locked = InternalReaderWriterBusyMutexValue::IsWriteLocked(v); + if (AMS_LIKELY(!write_locked)) { + /* Check that we don't overflow the reader count. */ + const u32 new_v = v + 1; + AMS_ABORT_UNLESS(InternalReaderWriterBusyMutexValue::GetReaderCount(new_v) != 0); + + /* Try to store our updated lock value. */ + if (AMS_LIKELY(StoreExclusiveForBusyMutex(p, new_v))) { + break; + } + } + + /* Reset the disable counter, since we failed to acquire. */ + tlr->disable_count = cur_dc; + + /* If we don't hold any other busy mutexes, acknowledge any interrupts that occurred while we tried to acquire the lock. */ + if (cur_dc == 0 && tlr->interrupt_flag) { + svc::SynchronizePreemptionState(); + } + + /* If the lock is held by another core, wait for it to be released. */ + if (write_locked) { + WaitForEventsForBusyMutex(); + } + } + } + + void InternalReaderWriterBusyMutexImpl::ReleaseReadLock() { + /* Release the read lock. */ + { + /* Get pointer to our value. */ + u32 * const p = std::addressof(m_value); + + u32 v; + do { + /* Get and validate the current value. */ + v = LoadExclusiveForBusyMutex(p); + AMS_ABORT_UNLESS(InternalReaderWriterBusyMutexValue::GetReaderCount(v) != 0); + } while (!StoreReleaseExclusiveForBusyMutex(p, v - 1)); + } + + /* Get the thread local region. */ + auto * const tlr = svc::GetThreadLocalRegion(); + + /* Determine disable counters. */ + const auto cur_dc = tlr->disable_count; + AMS_ASSERT(cur_dc != 0); + const auto next_dc = cur_dc - 1; + + /* Decrement disable count. */ + tlr->disable_count = next_dc; + + /* If we don't hold any other busy mutexes, acknowledge any interrupts that occurred while we held the lock. */ + if (next_dc == 0 && tlr->interrupt_flag) { + svc::SynchronizePreemptionState(); + } + } + + void InternalReaderWriterBusyMutexImpl::AcquireWriteLock() { + /* Get the thread local region. */ + auto * const tlr = svc::GetThreadLocalRegion(); + + /* Determine disable counters. */ + const auto cur_dc = tlr->disable_count; + AMS_ABORT_UNLESS(cur_dc < std::numeric_limits<decltype(cur_dc)>::max()); + const auto next_dc = cur_dc + 1; + + /* Check that we're allowed to use busy mutexes. */ + CallCheckBusyMutexPermission(); + + /* Get pointer to our value. */ + u32 * const p = std::addressof(m_value); + + /* Pre-fetch the busy mutex. */ + PrefetchForBusyMutex(p); + + /* Acquire the read-lock for the mutex. */ + while (true) { + /* Set the updated disable counter. */ + tlr->disable_count = next_dc; + + /* Try to acquire. */ + const u32 v = LoadAcquireExclusiveForBusyMutex(p); + + /* Check that we can write lock. */ + AMS_ABORT_UNLESS(static_cast<u8>(InternalReaderWriterBusyMutexValue::GetWriterNext(v) - InternalReaderWriterBusyMutexValue::GetWriterCurrent(v)) < InternalReaderWriterBusyMutexValue::WriterCountMax); + + /* Determine our write-lock number. */ + const u32 new_v = InternalReaderWriterBusyMutexValue::IncrementWriterNext(v); + + /* Try to store our updated lock value. */ + if (AMS_UNLIKELY(!StoreExclusiveForBusyMutex(p, new_v))) { + /* Reset the disable counter, since we failed to acquire. */ + tlr->disable_count = cur_dc; + + /* If we don't hold any other busy mutexes, acknowledge any interrupts that occurred while we tried to acquire the lock. */ + if (cur_dc == 0 && tlr->interrupt_flag) { + svc::SynchronizePreemptionState(); + } + + continue; + } + + /* Wait until the lock is truly acquired. */ + if (InternalReaderWriterBusyMutexValue::GetReaderCount(new_v) != 0 || InternalReaderWriterBusyMutexValue::GetWriterNext(v) != InternalReaderWriterBusyMutexValue::GetWriterCurrent(new_v)) { + /* Send an event, so that we can immediately wait without fail. */ + SendEventLocalForBusyMutex(); + + while (true) { + /* Wait for a lock update. */ + WaitForEventsForBusyMutex(); + + /* Get the updated value. */ + const u32 cur_v = LoadAcquireExclusiveForBusyMutex(p); + if (InternalReaderWriterBusyMutexValue::GetReaderCount(cur_v) == 0 && InternalReaderWriterBusyMutexValue::GetWriterNext(v) == InternalReaderWriterBusyMutexValue::GetWriterCurrent(cur_v)) { + break; + } + } + } + + /* We've acquired the write lock. */ + break; + } + } + + void InternalReaderWriterBusyMutexImpl::ReleaseWriteLock() { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(InternalReaderWriterBusyMutexValue::IsWriteLocked(m_value)); + + /* Get pointer to our value. */ + u32 * const p = std::addressof(m_value); + + /* Release the write lock. */ + StoreReleaseWriteLockValueForBusyMutex(p); + + /* Get the thread local region. */ + auto * const tlr = svc::GetThreadLocalRegion(); + + /* Determine disable counters. */ + const auto cur_dc = tlr->disable_count; + AMS_ASSERT(cur_dc != 0); + const auto next_dc = cur_dc - 1; + + /* Decrement disable count. */ + tlr->disable_count = next_dc; + + /* If we don't hold any other busy mutexes, acknowledge any interrupts that occurred while we held the lock. */ + if (next_dc == 0 && tlr->interrupt_flag) { + svc::SynchronizePreemptionState(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_rw_busy_mutex_impl.os.linux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_rw_busy_mutex_impl.os.linux.hpp new file mode 100644 index 00000000..14b82cff --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_rw_busy_mutex_impl.os.linux.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + /* TODO */ + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_rw_busy_mutex_impl.os.macos.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_rw_busy_mutex_impl.os.macos.hpp new file mode 100644 index 00000000..14b82cff --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_rw_busy_mutex_impl.os.macos.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + /* TODO */ + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_rw_busy_mutex_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_rw_busy_mutex_impl.os.windows.hpp new file mode 100644 index 00000000..14b82cff --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_internal_rw_busy_mutex_impl.os.windows.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + /* TODO */ + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_impl.hpp new file mode 100644 index 00000000..0980592f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_impl.hpp @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "os_interrupt_event_target_impl.os.horizon.hpp" +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include "os_interrupt_event_target_impl.os.windows.hpp" +#elif defined(ATMOSPHERE_OS_LINUX) + #include "os_interrupt_event_target_impl.os.linux.hpp" +#elif defined(ATMOSPHERE_OS_MACOS) + #include "os_interrupt_event_target_impl.os.macos.hpp" +#else + #error "Unknown OS for ams::os::InterruptEventImpl" +#endif + +namespace ams::os::impl { + + class InterruptEventImpl { + private: + InterruptEventTargetImpl m_impl; + public: + explicit InterruptEventImpl(InterruptName name, EventClearMode clear_mode) : m_impl(name, clear_mode) { /* ... */ } + + void Clear() { + return m_impl.Clear(); + } + + void Wait() { + return m_impl.Wait(); + } + + bool TryWait() { + return m_impl.TryWait(); + } + + bool TimedWait(TimeSpan timeout) { + return m_impl.TimedWait(timeout); + } + + TriBool IsSignaled() { + return m_impl.IsSignaled(); + } + + NativeHandle GetHandle() const { + return m_impl.GetHandle(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_target_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_target_impl.os.horizon.cpp new file mode 100644 index 00000000..80ed60db --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_target_impl.os.horizon.cpp @@ -0,0 +1,114 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_interrupt_event_target_impl.os.horizon.hpp" +#include "os_timeout_helper.hpp" + +namespace ams::os::impl { + + InterruptEventHorizonImpl::InterruptEventHorizonImpl(InterruptName name, EventClearMode clear_mode) { + m_manual_clear = (clear_mode == EventClearMode_ManualClear); + + auto interrupt_type = m_manual_clear ? svc::InterruptType_Level : svc::InterruptType_Edge; + svc::Handle handle; + R_ABORT_UNLESS(svc::CreateInterruptEvent(std::addressof(handle), static_cast<s32>(name), interrupt_type)); + + m_handle = handle; + } + + InterruptEventHorizonImpl::~InterruptEventHorizonImpl() { + R_ABORT_UNLESS(svc::CloseHandle(m_handle)); + } + + void InterruptEventHorizonImpl::Clear() { + R_ABORT_UNLESS(svc::ClearEvent(m_handle)); + } + + void InterruptEventHorizonImpl::Wait() { + while (true) { + /* Continuously wait, until success. */ + s32 index; + Result res = svc::WaitSynchronization(std::addressof(index), std::addressof(m_handle), 1, svc::WaitInfinite); + if (R_SUCCEEDED(res)) { + /* Clear, if we must. */ + if (!m_manual_clear) { + R_TRY_CATCH(svc::ResetSignal(m_handle)) { + /* Some other thread might have caught this before we did. */ + R_CATCH(svc::ResultInvalidState) { continue; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + } + + return; + } + + AMS_ASSERT(svc::ResultCancelled::Includes(res)); + } + } + + bool InterruptEventHorizonImpl::TryWait() { + /* If we're auto clear, just try to reset. */ + if (!m_manual_clear) { + return R_SUCCEEDED(svc::ResetSignal(m_handle)); + } + + /* Not auto-clear. */ + while (true) { + /* Continuously wait, until success or timeout. */ + s32 index; + Result res = svc::WaitSynchronization(std::addressof(index), std::addressof(m_handle), 1, 0); + + /* If we succeeded, we're signaled. */ + if (R_SUCCEEDED(res)) { + return true; + } + + /* If we timed out, we're not signaled. */ + if (svc::ResultTimedOut::Includes(res)) { + return false; + } + + AMS_ASSERT(svc::ResultCancelled::Includes(res)); + } + } + + bool InterruptEventHorizonImpl::TimedWait(TimeSpan timeout) { + TimeoutHelper timeout_helper(timeout); + + while (true) { + /* Continuously wait, until success. */ + s32 index; + Result res = svc::WaitSynchronization(std::addressof(index), std::addressof(m_handle), 1, timeout_helper.GetTimeLeftOnTarget().GetNanoSeconds()); + if (R_SUCCEEDED(res)) { + /* Clear, if we must. */ + if (!m_manual_clear) { + R_TRY_CATCH(svc::ResetSignal(m_handle)) { + /* Some other thread might have caught this before we did. */ + R_CATCH(svc::ResultInvalidState) { continue; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + } + + return true; + } + + if (svc::ResultTimedOut::Includes(res)) { + return false; + } + + AMS_ASSERT(svc::ResultCancelled::Includes(res)); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_target_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_target_impl.os.horizon.hpp new file mode 100644 index 00000000..672ed910 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_target_impl.os.horizon.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class InterruptEventHorizonImpl { + private: + svc::Handle m_handle; + bool m_manual_clear; + public: + explicit InterruptEventHorizonImpl(InterruptName name, EventClearMode mode); + ~InterruptEventHorizonImpl(); + + void Clear(); + void Wait(); + bool TryWait(); + bool TimedWait(TimeSpan timeout); + + TriBool IsSignaled() { + return TriBool::Undefined; + } + + NativeHandle GetHandle() const { + return m_handle; + } + }; + + using InterruptEventTargetImpl = InterruptEventHorizonImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_target_impl.os.linux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_target_impl.os.linux.hpp new file mode 100644 index 00000000..3ccfba29 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_target_impl.os.linux.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class InterruptEventLinuxImpl { + private: + NativeHandle m_handle; + bool m_manual_clear; + public: + explicit InterruptEventLinuxImpl(InterruptName name, EventClearMode mode) { + AMS_UNUSED(name, mode); + AMS_ABORT("TODO"); + } + + ~InterruptEventLinuxImpl() { + AMS_ABORT("TODO"); + } + + void Clear() { AMS_ABORT("TODO"); } + void Wait() { AMS_ABORT("TODO"); } + bool TryWait() { AMS_ABORT("TODO"); } + bool TimedWait(TimeSpan) { AMS_ABORT("TODO"); } + + TriBool IsSignaled() { + return TriBool::Undefined; + } + + NativeHandle GetHandle() const { + return m_handle; + } + }; + + using InterruptEventTargetImpl = InterruptEventLinuxImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_target_impl.os.macos.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_target_impl.os.macos.hpp new file mode 100644 index 00000000..7d0bd28f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_target_impl.os.macos.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class InterruptEventMacosImpl { + private: + NativeHandle m_handle; + bool m_manual_clear; + public: + explicit InterruptEventMacosImpl(InterruptName name, EventClearMode mode) { + AMS_UNUSED(name, mode); + AMS_ABORT("TODO"); + } + + ~InterruptEventMacosImpl() { + AMS_ABORT("TODO"); + } + + void Clear() { AMS_ABORT("TODO"); } + void Wait() { AMS_ABORT("TODO"); } + bool TryWait() { AMS_ABORT("TODO"); } + bool TimedWait(TimeSpan) { AMS_ABORT("TODO"); } + + TriBool IsSignaled() { + return TriBool::Undefined; + } + + NativeHandle GetHandle() const { + return m_handle; + } + }; + + using InterruptEventTargetImpl = InterruptEventMacosImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_target_impl.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_target_impl.os.windows.cpp new file mode 100644 index 00000000..1b33aeb9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_target_impl.os.windows.cpp @@ -0,0 +1,105 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_interrupt_event_target_impl.os.windows.hpp" +#include "os_timeout_helper.hpp" +#include "os_giant_lock.hpp" + +namespace ams::os::impl { + + InterruptEventWindowsImpl::InterruptEventWindowsImpl(InterruptName name, EventClearMode clear_mode) { + const NativeHandle handle = ::OpenEvent(EVENT_MODIFY_STATE | SYNCHRONIZE, FALSE, reinterpret_cast<LPCTSTR>(name)); + AMS_ASSERT(handle != InvalidNativeHandle); + + m_handle = handle; + m_manual_clear = (clear_mode == EventClearMode_ManualClear); + } + + InterruptEventWindowsImpl::~InterruptEventWindowsImpl() { + const auto ret = ::CloseHandle(m_handle); + AMS_ASSERT(ret != 0); + AMS_UNUSED(ret); + } + + bool InterruptEventWindowsImpl::ResetEventSignal() { + std::scoped_lock lk(GetGiantLock()); + + if (auto ret = ::WaitForSingleObject(m_handle, 0); ret == WAIT_OBJECT_0) { + ::ResetEvent(m_handle); + return true; + } else { + return false; + } + } + + void InterruptEventWindowsImpl::Clear() { + const auto ret = ::ResetEvent(m_handle); + AMS_ASSERT(ret != 0); + AMS_UNUSED(ret); + } + + void InterruptEventWindowsImpl::Wait() { + while (true) { + /* Continuously wait, until success. */ + auto ret = ::WaitForSingleObject(m_handle, INFINITE); + AMS_ASSERT(ret == WAIT_OBJECT_0); + AMS_UNUSED(ret); + + /* If we're not obligated to clear, we're done. */ + if (m_manual_clear) { + return; + } + + /* Try to reset. */ + if (this->ResetEventSignal()) { + return; + } + } + } + + bool InterruptEventWindowsImpl::TryWait() { + /* If we're auto clear, just try to reset. */ + if (!m_manual_clear) { + return this->ResetEventSignal(); + } + + const auto ret = ::WaitForSingleObject(m_handle, 0); + AMS_ASSERT((ret == WAIT_OBJECT_0) || (ret == WAIT_TIMEOUT)); + + return ret == WAIT_OBJECT_0; + } + + bool InterruptEventWindowsImpl::TimedWait(TimeSpan timeout) { + TimeoutHelper timeout_helper(timeout); + + do { + if (const auto res = ::WaitForSingleObject(m_handle, timeout_helper.GetTimeLeftOnTarget()); res == WAIT_OBJECT_0) { + /* If we're not obligated to clear, we're done. */ + if (m_manual_clear) { + return true; + } + + /* Try to reset. */ + if (this->ResetEventSignal()) { + return true; + } + } + } while (!timeout_helper.TimedOut()); + + return false; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_target_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_target_impl.os.windows.hpp new file mode 100644 index 00000000..cca5d90a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_interrupt_event_target_impl.os.windows.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class InterruptEventWindowsImpl { + private: + NativeHandle m_handle; + bool m_manual_clear; + public: + explicit InterruptEventWindowsImpl(InterruptName name, EventClearMode mode); + ~InterruptEventWindowsImpl(); + + void Clear(); + void Wait(); + bool TryWait(); + bool TimedWait(TimeSpan timeout); + + TriBool IsSignaled() { + return TriBool::Undefined; + } + + NativeHandle GetHandle() const { + return m_handle; + } + private: + bool ResetEventSignal(); + }; + + using InterruptEventTargetImpl = InterruptEventWindowsImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_io_region_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_io_region_impl.hpp new file mode 100644 index 00000000..7c265005 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_io_region_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class IoRegionImpl { + public: + static Result CreateIoRegion(NativeHandle *out, NativeHandle io_pool_handle, uintptr_t address, size_t size, MemoryMapping mapping, MemoryPermission permission); + + static Result MapIoRegion(void **out, NativeHandle handle, size_t size, MemoryPermission perm); + static void UnmapIoRegion(NativeHandle handle, void *address, size_t size); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_io_region_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_io_region_impl.os.horizon.cpp new file mode 100644 index 00000000..a798f888 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_io_region_impl.os.horizon.cpp @@ -0,0 +1,90 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_io_region_impl.hpp" +#include "os_aslr_space_manager_types.hpp" +#include "os_aslr_space_manager.hpp" + +namespace ams::os::impl { + + namespace { + + constexpr svc::MemoryPermission ConvertToSvcMemoryPermission(os::MemoryPermission perm) { + switch (perm) { + case os::MemoryPermission_None: return svc::MemoryPermission_None; + case os::MemoryPermission_ReadOnly: return svc::MemoryPermission_Read; + case os::MemoryPermission_WriteOnly: return svc::MemoryPermission_Write; + case os::MemoryPermission_ReadWrite: return svc::MemoryPermission_ReadWrite; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + constexpr svc::MemoryMapping ConvertToSvcMemoryMapping(os::MemoryMapping mapping) { + static_assert(std::same_as<svc::MemoryMapping, os::MemoryMapping>); + return mapping; + } + + } + + Result IoRegionImpl::CreateIoRegion(NativeHandle *out, NativeHandle io_pool_handle, uintptr_t address, size_t size, MemoryMapping mapping, MemoryPermission permission) { + /* Convert mapping/permission. */ + const auto svc_mapping = ConvertToSvcMemoryMapping(mapping); + const auto svc_perm = ConvertToSvcMemoryPermission(permission); + + /* Create the io region. */ + /* TODO: Result conversion/abort on unexpected result? */ + svc::Handle handle; + R_TRY(svc::CreateIoRegion(std::addressof(handle), io_pool_handle, address, size, svc_mapping, svc_perm)); + + *out = handle; + R_SUCCEED(); + } + + Result IoRegionImpl::MapIoRegion(void **out, NativeHandle handle, size_t size, MemoryPermission perm) { + /* Convert permission. */ + const auto svc_perm = ConvertToSvcMemoryPermission(perm); + + /* Map at a random address. */ + uintptr_t mapped_address; + R_TRY(impl::GetAslrSpaceManager().MapAtRandomAddress(std::addressof(mapped_address), + [handle, svc_perm](uintptr_t map_address, size_t map_size) -> Result { + R_TRY_CATCH(svc::MapIoRegion(handle, map_address, map_size, svc_perm)) { + /* TODO: What's the correct result for these? */ + // R_CONVERT(svc::ResultInvalidHandle, os::ResultInvalidHandle()) + // R_CONVERT(svc::ResultInvalidSize, os::Result???()) + // R_CONVERT(svc::ResultInvalidState, os::Result???()) + R_CONVERT(svc::ResultInvalidCurrentMemory, os::ResultInvalidCurrentMemoryState()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + R_SUCCEED(); + }, + [handle](uintptr_t map_address, size_t map_size) -> void { + return IoRegionImpl::UnmapIoRegion(handle, reinterpret_cast<void *>(map_address), map_size); + }, + size, + 0 + )); + + /* Return the address we mapped at. */ + *out = reinterpret_cast<void *>(mapped_address); + R_SUCCEED(); + } + + void IoRegionImpl::UnmapIoRegion(NativeHandle handle, void *address, size_t size) { + R_ABORT_UNLESS(svc::UnmapIoRegion(handle, reinterpret_cast<uintptr_t>(address), size)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_io_region_impl.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_io_region_impl.os.windows.cpp new file mode 100644 index 00000000..65e07002 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_io_region_impl.os.windows.cpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_io_region_impl.hpp" + +namespace ams::os::impl { + + Result IoRegionImpl::CreateIoRegion(NativeHandle *out, NativeHandle io_pool_handle, uintptr_t address, size_t size, MemoryMapping mapping, MemoryPermission permission) { + AMS_UNUSED(out, io_pool_handle, address, size, mapping, permission); + R_THROW(os::ResultNotSupported()); + } + + Result IoRegionImpl::MapIoRegion(void **out, NativeHandle handle, size_t size, MemoryPermission perm) { + AMS_UNUSED(out, handle, size, perm); + R_THROW(os::ResultNotSupported()); + } + + void IoRegionImpl::UnmapIoRegion(NativeHandle handle, void *address, size_t size) { + AMS_UNUSED(handle, address, size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_attribute_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_attribute_impl.hpp new file mode 100644 index 00000000..2bbda482 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_attribute_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + void SetMemoryAttributeImpl(uintptr_t address, size_t size, MemoryAttribute attr); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_attribute_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_attribute_impl.os.horizon.cpp new file mode 100644 index 00000000..23ec7028 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_attribute_impl.os.horizon.cpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::os::impl { + + void SetMemoryAttributeImpl(uintptr_t address, size_t size, MemoryAttribute attr) { + /* Determine svc arguments. */ + u32 svc_mask = svc::MemoryAttribute_Uncached; + u32 svc_attr = 0; + + switch (attr) { + case os::MemoryAttribute_Normal: svc_attr = 0; break; + case os::MemoryAttribute_Uncached: svc_attr = svc::MemoryAttribute_Uncached; break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Loop, setting attribute. */ + auto cur_address = address; + auto remaining = size; + while (remaining > 0) { + /* Query the memory. */ + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + R_ABORT_UNLESS(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), cur_address)); + + /* Determine the current size. */ + const size_t cur_size = std::min<size_t>(mem_info.base_address + mem_info.size - cur_address, remaining); + + /* Set the attribute, if necessary. */ + if (mem_info.attribute != svc_attr) { + if (const auto res = svc::SetMemoryAttribute(address, size, svc_mask, svc_attr); R_FAILED(res)) { + /* NOTE: Nintendo logs here. */ + R_ABORT_UNLESS(res); + } + } + + /* Advance. */ + cur_address += cur_size; + remaining -= cur_size; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_attribute_impl.os.linux.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_attribute_impl.os.linux.cpp new file mode 100644 index 00000000..285806af --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_attribute_impl.os.linux.cpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::os::impl { + + void SetMemoryAttributeImpl(uintptr_t address, size_t size, MemoryAttribute attr) { + /* TODO: Should this do anything? */ + AMS_UNUSED(address, size, attr); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_attribute_impl.os.macos.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_attribute_impl.os.macos.cpp new file mode 100644 index 00000000..285806af --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_attribute_impl.os.macos.cpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::os::impl { + + void SetMemoryAttributeImpl(uintptr_t address, size_t size, MemoryAttribute attr) { + /* TODO: Should this do anything? */ + AMS_UNUSED(address, size, attr); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_attribute_impl.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_attribute_impl.os.windows.cpp new file mode 100644 index 00000000..285806af --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_attribute_impl.os.windows.cpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::os::impl { + + void SetMemoryAttributeImpl(uintptr_t address, size_t size, MemoryAttribute attr) { + /* TODO: Should this do anything? */ + AMS_UNUSED(address, size, attr); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager.cpp new file mode 100644 index 00000000..a2011917 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager.cpp @@ -0,0 +1,221 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_memory_heap_manager.hpp" +#include "os_thread_manager.hpp" + +namespace ams::os::impl { + + Result MemoryHeapManager::SetHeapSize(size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(util::IsAligned(size, MemoryHeapUnitSize)); + + /* Acquire locks. */ + std::scoped_lock lk1(util::GetReference(::ams::os::impl::GetCurrentThread()->cs_thread)); + std::scoped_lock lk2(m_cs); + + /* If we need to, expand the heap. */ + if (size > m_heap_size) { + /* Set the new heap size. */ + uintptr_t address = 0; + R_TRY(m_impl.SetHeapSize(std::addressof(address), size)); + R_UNLESS(address != 0, os::ResultOutOfMemory()); + + /* Check that the new heap address is consistent. */ + if (m_heap_size == 0) { + AMS_ASSERT(util::IsAligned(address, MemoryHeapUnitSize)); + } else { + AMS_ASSERT(address == m_heap_address); + } + + /* Set up the new heap address. */ + this->AddToFreeSpaceUnsafe(address + m_heap_size, size - m_heap_size); + + /* Set our heap address. */ + m_heap_address = address; + m_heap_size = size; + } else if (size < m_heap_size) { + /* We're shrinking the heap, so we need to remove memory blocks. */ + const uintptr_t end_address = m_heap_address + size; + const size_t remove_size = m_heap_size - size; + + /* Get the end of the heap. */ + auto it = m_free_memory_list.end(); + --it; + R_UNLESS(it != m_free_memory_list.end(), os::ResultBusy()); + + /* Check that the block can be decommitted. */ + R_UNLESS(it->GetAddress() <= end_address, os::ResultBusy()); + R_UNLESS(it->GetSize() >= remove_size, os::ResultBusy()); + + /* Adjust the last node. */ + if (const size_t node_size = it->GetSize() - remove_size; node_size == 0) { + m_free_memory_list.erase(it); + it->Clean(); + } else { + it->SetSize(node_size); + } + + /* Set the reduced heap size. */ + uintptr_t address = 0; + R_ABORT_UNLESS(m_impl.SetHeapSize(std::addressof(address), size)); + + /* Set our heap address. */ + m_heap_size = size; + if (size == 0) { + m_heap_address = 0; + } + } + + R_SUCCEED(); + } + + Result MemoryHeapManager::AllocateFromHeap(uintptr_t *out_address, size_t size) { + /* Acquire locks. */ + std::scoped_lock lk1(util::GetReference(::ams::os::impl::GetCurrentThread()->cs_thread)); + std::scoped_lock lk2(m_cs); + + /* Find free space. */ + auto it = this->FindFreeSpaceUnsafe(size); + R_UNLESS(it != m_free_memory_list.end(), os::ResultOutOfMemory()); + + /* If necessary, split the memory block. */ + if (it->GetSize() > size) { + this->SplitFreeMemoryNodeUnsafe(it, size); + } + + /* Remove the block. */ + m_free_memory_list.erase(it); + it->Clean(); + + /* Increment the used heap size. */ + m_used_heap_size += size; + + /* Set the output address. */ + *out_address = it->GetAddress(); + + R_SUCCEED(); + } + + void MemoryHeapManager::ReleaseToHeap(uintptr_t address, size_t size) { + /* Acquire locks. */ + std::scoped_lock lk1(util::GetReference(::ams::os::impl::GetCurrentThread()->cs_thread)); + std::scoped_lock lk2(m_cs); + + /* Check pre-condition. */ + AMS_ABORT_UNLESS(this->IsRegionAllocatedMemoryUnsafe(address, size)); + + /* Restore the permissions on the memory. */ + os::SetMemoryPermission(address, size, MemoryPermission_ReadWrite); + os::SetMemoryAttribute(address, size, MemoryAttribute_Normal); + + /* Add the memory back to our free list. */ + this->AddToFreeSpaceUnsafe(address, size); + + /* Decrement the used heap size. */ + m_used_heap_size -= size; + } + + MemoryHeapManager::FreeMemoryList::iterator MemoryHeapManager::FindFreeSpaceUnsafe(size_t size) { + /* Find the best fit candidate. */ + auto best = m_free_memory_list.end(); + + for (auto it = m_free_memory_list.begin(); it != m_free_memory_list.end(); ++it) { + if (const size_t node_size = it->GetSize(); node_size >= size) { + if (best == m_free_memory_list.end() || node_size < best->GetSize()) { + best = it; + } + } + } + + return best; + } + + MemoryHeapManager::FreeMemoryList::iterator MemoryHeapManager::ConcatenatePreviousFreeMemoryNodeUnsafe(FreeMemoryList::iterator node) { + /* Get the previous node. */ + auto prev = node; + --prev; + + /* If there's no previous, we're done. */ + if (prev == m_free_memory_list.end() || node == m_free_memory_list.end()) { + return node; + } + + /* Otherwise, if the previous isn't contiguous, we can't merge. */ + if (prev->GetAddress() + prev->GetSize() != node->GetAddress()) { + return node; + } + + /* Otherwise, increase the size of the previous node, and remove the current node. */ + prev->SetSize(prev->GetSize() + node->GetSize()); + m_free_memory_list.erase(node); + node->Clean(); + + return prev; + } + + void MemoryHeapManager::SplitFreeMemoryNodeUnsafe(FreeMemoryList::iterator it, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(it->GetSize() > size); + AMS_ASSERT(util::IsAligned(size, MemoryBlockUnitSize)); + + /* Create new node. */ + auto *new_node = std::construct_at(reinterpret_cast<FreeMemoryNode *>(it->GetAddress() + size)); + new_node->SetSize(it->GetSize() - size); + + /* Set the old node's size. */ + it->SetSize(size); + + /* Insert the new node. */ + m_free_memory_list.insert(++it, *new_node); + } + + void MemoryHeapManager::AddToFreeSpaceUnsafe(uintptr_t address, size_t size) { + /* Create new node. */ + auto *new_node = std::construct_at(reinterpret_cast<FreeMemoryNode *>(address)); + new_node->SetSize(size); + + /* Find the appropriate place to insert the node. */ + auto it = m_free_memory_list.begin(); + for (/* ... */; it != m_free_memory_list.end(); ++it) { + if (address < it->GetAddress()) { + break; + } + } + + /* Insert the new node. */ + it = m_free_memory_list.insert(it, *new_node); + + /* Perform coalescing as relevant. */ + it = this->ConcatenatePreviousFreeMemoryNodeUnsafe(it); + this->ConcatenatePreviousFreeMemoryNodeUnsafe(++it); + } + + bool MemoryHeapManager::IsRegionAllocatedMemoryUnsafe(uintptr_t address, size_t size) { + /* Look for a node containing the region. */ + for (auto it = m_free_memory_list.begin(); it != m_free_memory_list.end(); ++it) { + const uintptr_t node_address = it->GetAddress(); + const size_t node_size = it->GetSize(); + + if (node_address < address + size && address < node_address + node_size) { + return false; + } + } + + return true; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager.hpp new file mode 100644 index 00000000..b932e896 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_memory_heap_manager_types.hpp" +#include "os_resource_manager.hpp" + +namespace ams::os::impl { + + ALWAYS_INLINE MemoryHeapManager &GetMemoryHeapManager() { + return GetResourceManager().GetMemoryHeapManager(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager_impl.os.horizon.hpp new file mode 100644 index 00000000..b44caf1a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager_impl.os.horizon.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class MemoryHeapManagerHorizonImpl { + public: + Result SetHeapSize(uintptr_t *out, size_t size) { + R_TRY_CATCH(svc::SetHeapSize(out, size)) { + R_CONVERT(svc::ResultOutOfMemory, os::ResultOutOfMemory()) + R_CONVERT(svc::ResultLimitReached, os::ResultOutOfMemory()) + R_CONVERT(svc::ResultOutOfResource, os::ResultOutOfMemory()) + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + }; + + using MemoryHeapManagerImpl = MemoryHeapManagerHorizonImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager_impl.os.linux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager_impl.os.linux.hpp new file mode 100644 index 00000000..18632f27 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager_impl.os.linux.hpp @@ -0,0 +1,78 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include <sys/mman.h> + +namespace ams::os::impl { + + class MemoryHeapManagerLinuxImpl { + NON_COPYABLE(MemoryHeapManagerLinuxImpl); + NON_MOVEABLE(MemoryHeapManagerLinuxImpl); + private: + uintptr_t m_real_reserved_address; + size_t m_real_reserved_size; + uintptr_t m_aligned_reserved_heap_address; + size_t m_aligned_reserved_heap_size; + size_t m_committed_size; + public: + MemoryHeapManagerLinuxImpl() : m_real_reserved_address(0), m_real_reserved_size(0), m_aligned_reserved_heap_address(0), m_aligned_reserved_heap_size(0), m_committed_size(0) { + /* Reserve a 32 GB region of virtual address space. */ + constexpr size_t TargetReservedSize = 32_GB; + const auto reserved = ::mmap(nullptr, TargetReservedSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + AMS_ABORT_UNLESS(reserved != MAP_FAILED); + + m_real_reserved_address = reinterpret_cast<uintptr_t>(reserved); + m_real_reserved_size = TargetReservedSize; + + m_aligned_reserved_heap_address = util::AlignUp(m_real_reserved_address, MemoryHeapUnitSize); + m_aligned_reserved_heap_size = m_real_reserved_size - MemoryHeapUnitSize; + } + + Result SetHeapSize(uintptr_t *out, size_t size) { + /* Check that we have a reserved address. */ + R_UNLESS(m_real_reserved_address != 0, os::ResultOutOfMemory()); + + /* If necessary, commit the new memory. */ + if (size > m_committed_size) { + R_UNLESS(this->CommitMemory(size), os::ResultOutOfMemory()); + } else if (size < m_committed_size) { + /* Otherwise, decommit. */ + this->DecommitMemory(m_aligned_reserved_heap_address + size, m_committed_size - size); + } + + /* Set the committed size. */ + m_committed_size = size; + + /* Set the out address. */ + *out = m_aligned_reserved_heap_address; + R_SUCCEED(); + } + private: + bool CommitMemory(size_t size) { + const auto res = ::mprotect(reinterpret_cast<void *>(m_aligned_reserved_heap_address), size, PROT_READ | PROT_WRITE); + return res == 0; + } + + void DecommitMemory(uintptr_t address, size_t size) { + const auto reserved = ::mmap(reinterpret_cast<void *>(address), size, PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + AMS_ABORT_UNLESS(reserved != MAP_FAILED); + } + }; + + using MemoryHeapManagerImpl = MemoryHeapManagerLinuxImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager_impl.os.macos.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager_impl.os.macos.hpp new file mode 100644 index 00000000..7a40c4a8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager_impl.os.macos.hpp @@ -0,0 +1,78 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include <sys/mman.h> + +namespace ams::os::impl { + + class MemoryHeapManagerMacosImpl { + NON_COPYABLE(MemoryHeapManagerMacosImpl); + NON_MOVEABLE(MemoryHeapManagerMacosImpl); + private: + uintptr_t m_real_reserved_address; + size_t m_real_reserved_size; + uintptr_t m_aligned_reserved_heap_address; + size_t m_aligned_reserved_heap_size; + size_t m_committed_size; + public: + MemoryHeapManagerMacosImpl() : m_real_reserved_address(0), m_real_reserved_size(0), m_aligned_reserved_heap_address(0), m_aligned_reserved_heap_size(0), m_committed_size(0) { + /* Reserve a 32 GB region of virtual address space. */ + constexpr size_t TargetReservedSize = 32_GB; + const auto reserved = ::mmap(nullptr, TargetReservedSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + AMS_ABORT_UNLESS(reserved != MAP_FAILED); + + m_real_reserved_address = reinterpret_cast<uintptr_t>(reserved); + m_real_reserved_size = TargetReservedSize; + + m_aligned_reserved_heap_address = util::AlignUp(m_real_reserved_address, MemoryHeapUnitSize); + m_aligned_reserved_heap_size = m_real_reserved_size - MemoryHeapUnitSize; + } + + Result SetHeapSize(uintptr_t *out, size_t size) { + /* Check that we have a reserved address. */ + R_UNLESS(m_real_reserved_address != 0, os::ResultOutOfMemory()); + + /* If necessary, commit the new memory. */ + if (size > m_committed_size) { + R_UNLESS(this->CommitMemory(size), os::ResultOutOfMemory()); + } else if (size < m_committed_size) { + /* Otherwise, decommit. */ + this->DecommitMemory(m_aligned_reserved_heap_address + size, m_committed_size - size); + } + + /* Set the committed size. */ + m_committed_size = size; + + /* Set the out address. */ + *out = m_aligned_reserved_heap_address; + R_SUCCEED(); + } + private: + bool CommitMemory(size_t size) { + const auto res = ::mprotect(reinterpret_cast<void *>(m_aligned_reserved_heap_address), size, PROT_READ | PROT_WRITE); + return res == 0; + } + + void DecommitMemory(uintptr_t address, size_t size) { + const auto reserved = ::mmap(reinterpret_cast<void *>(address), size, PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + AMS_ABORT_UNLESS(reserved != MAP_FAILED); + } + }; + + using MemoryHeapManagerImpl = MemoryHeapManagerMacosImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager_impl.os.windows.hpp new file mode 100644 index 00000000..ea4fc405 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager_impl.os.windows.hpp @@ -0,0 +1,132 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include <stratosphere/windows.hpp> + +namespace ams::os::impl { + + class MemoryHeapManagerWindowsImpl { + NON_COPYABLE(MemoryHeapManagerWindowsImpl); + NON_MOVEABLE(MemoryHeapManagerWindowsImpl); + private: + LPVOID m_real_reserved_address; + size_t m_real_reserved_size; + LPVOID m_aligned_reserved_heap_address; + size_t m_aligned_reserved_heap_size; + size_t m_committed_size; + public: + MemoryHeapManagerWindowsImpl() : m_real_reserved_address(nullptr), m_real_reserved_size(0), m_aligned_reserved_heap_address(nullptr), m_aligned_reserved_heap_size(0), m_committed_size(0) { + /* Define target size. */ + constexpr size_t TargetReservedSize = 32_GB; + + /* Allocate appropriate amount of virtual space. */ + size_t reserved_size = 0; + size_t reserved_addend = TargetReservedSize; + while (reserved_addend >= MemoryHeapUnitSize) { + if (this->ReserveVirtualSpace(0, reserved_size + reserved_addend)) { + this->ReleaseVirtualSpace(); + + reserved_size += reserved_addend; + if (reserved_size >= TargetReservedSize) { + break; + } + } + + reserved_addend /= 2; + } + + /* Reserve virtual space. */ + AMS_ABORT_UNLESS(this->ReserveVirtualSpace(0, reserved_size)); + } + + Result SetHeapSize(uintptr_t *out, size_t size) { + /* Check that we have a reserved address. */ + R_UNLESS(m_real_reserved_address != nullptr, os::ResultOutOfMemory()); + + /* If necessary, commit the new memory. */ + if (size > m_committed_size) { + R_UNLESS(this->CommitMemory(size), os::ResultOutOfMemory()); + } else if (size < m_committed_size) { + /* Otherwise, decommit. */ + this->DecommitMemory(reinterpret_cast<uintptr_t>(m_aligned_reserved_heap_address) + size, m_committed_size - size); + } + + /* Set the committed size. */ + m_committed_size = size; + + /* Set the out address. */ + *out = reinterpret_cast<uintptr_t>(m_aligned_reserved_heap_address); + R_SUCCEED(); + } + private: + bool ReserveVirtualSpace(uintptr_t address, size_t size) { + AMS_ABORT_UNLESS(m_real_reserved_address == nullptr); + AMS_ABORT_UNLESS(m_real_reserved_size == 0); + + size_t reserve_size = util::AlignUp(size, MemoryHeapUnitSize); + if constexpr (constexpr size_t VirtualAllocUnitSize = 64_KB; MemoryHeapUnitSize > VirtualAllocUnitSize) { + reserve_size += MemoryHeapUnitSize - VirtualAllocUnitSize; + } + + LPVOID res = ::VirtualAlloc(reinterpret_cast<LPVOID>(address), reserve_size, MEM_RESERVE, PAGE_READWRITE); + if (res == nullptr) { + return false; + } + + m_real_reserved_address = res; + m_real_reserved_size = reserve_size; + + m_aligned_reserved_heap_address = reinterpret_cast<LPVOID>(util::AlignUp(reinterpret_cast<uintptr_t>(m_real_reserved_address), MemoryHeapUnitSize)); + m_aligned_reserved_heap_size = size; + + return true; + } + + void ReleaseVirtualSpace() { + if (m_real_reserved_address != nullptr) { + auto res = ::VirtualFree(m_real_reserved_address, 0, MEM_RELEASE); + AMS_ASSERT(res); + AMS_UNUSED(res); + + m_real_reserved_address = nullptr; + m_real_reserved_size = 0; + + m_aligned_reserved_heap_address = nullptr; + m_aligned_reserved_heap_size = 0; + } + } + + bool CommitMemory(size_t size) { + LPVOID address = ::VirtualAlloc(m_aligned_reserved_heap_address, static_cast<SIZE_T>(size), MEM_COMMIT, PAGE_READWRITE); + if (address == nullptr) { + return false; + } + + AMS_ABORT_UNLESS(address == m_aligned_reserved_heap_address); + return true; + } + + void DecommitMemory(uintptr_t address, size_t size) { + auto res = ::VirtualFree(reinterpret_cast<LPVOID>(address), static_cast<SIZE_T>(size), MEM_DECOMMIT); + AMS_ASSERT(res); + AMS_UNUSED(res); + } + }; + + using MemoryHeapManagerImpl = MemoryHeapManagerWindowsImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager_types.hpp new file mode 100644 index 00000000..9f990a1c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_heap_manager_types.hpp @@ -0,0 +1,84 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "os_memory_heap_manager_impl.os.horizon.hpp" +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include "os_memory_heap_manager_impl.os.windows.hpp" +#elif defined(ATMOSPHERE_OS_LINUX) + #include "os_memory_heap_manager_impl.os.linux.hpp" +#elif defined(ATMOSPHERE_OS_MACOS) + #include "os_memory_heap_manager_impl.os.macos.hpp" +#else + #error "Unknown OS for MemoryHeapManagerImpl" +#endif + +namespace ams::os::impl { + + class MemoryHeapManager; + + class FreeMemoryNode { + private: + friend class MemoryHeapManager; + private: + util::IntrusiveListNode m_node; + size_t m_size; + public: + ALWAYS_INLINE uintptr_t GetAddress() const { return reinterpret_cast<uintptr_t>(this); } + ALWAYS_INLINE size_t GetSize() const { return m_size; } + ALWAYS_INLINE void SetSize(size_t size) { m_size = size; } + ALWAYS_INLINE void Clean() { std::memset(reinterpret_cast<void *>(this), 0, sizeof(FreeMemoryNode)); } + }; + static_assert(sizeof(FreeMemoryNode) == sizeof(util::IntrusiveListNode) + sizeof(size_t)); + + class MemoryHeapManager { + NON_COPYABLE(MemoryHeapManager); + NON_MOVEABLE(MemoryHeapManager); + private: + using FreeMemoryList = typename util::IntrusiveListMemberTraits<&FreeMemoryNode::m_node>::ListType; + private: + uintptr_t m_heap_address; + size_t m_heap_size; + size_t m_used_heap_size; + FreeMemoryList m_free_memory_list; + InternalCriticalSection m_cs; + MemoryHeapManagerImpl m_impl; + public: + MemoryHeapManager() : m_heap_address(0), m_heap_size(0), m_used_heap_size(0) { /* ... */ } + + Result SetHeapSize(size_t size); + + Result AllocateFromHeap(uintptr_t *out_address, size_t size); + void ReleaseToHeap(uintptr_t address, size_t size); + + bool IsRegionInMemoryHeap(uintptr_t address, size_t size) const { + return m_heap_address <= address && (address + size) <= (m_heap_address + m_heap_size); + } + + uintptr_t GetHeapAddress() const { return m_heap_address; } + size_t GetHeapSize() const { return m_heap_size; } + size_t GetUsedHeapSize() const { return m_used_heap_size; } + private: + FreeMemoryList::iterator FindFreeSpaceUnsafe(size_t size); + FreeMemoryList::iterator ConcatenatePreviousFreeMemoryNodeUnsafe(FreeMemoryList::iterator node); + void SplitFreeMemoryNodeUnsafe(FreeMemoryList::iterator it, size_t size); + void AddToFreeSpaceUnsafe(uintptr_t address, size_t size); + bool IsRegionAllocatedMemoryUnsafe(uintptr_t address, size_t size); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_permission_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_permission_impl.hpp new file mode 100644 index 00000000..d21d168a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_permission_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + void SetMemoryPermissionImpl(uintptr_t address, size_t size, MemoryPermission perm); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_permission_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_permission_impl.os.horizon.cpp new file mode 100644 index 00000000..bd258b8d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_permission_impl.os.horizon.cpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::os::impl { + + namespace { + + void SetMemoryPermissionBySvc(uintptr_t start, size_t size, svc::MemoryPermission perm) { + uintptr_t cur_address = start; + size_t remaining_size = size; + while (remaining_size > 0) { + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + R_ABORT_UNLESS(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), cur_address)); + + size_t cur_size = std::min(mem_info.base_address + mem_info.size - cur_address, remaining_size); + + if (mem_info.permission != perm) { + R_ABORT_UNLESS(svc::SetMemoryPermission(cur_address, cur_size, perm)); + } + + cur_address += cur_size; + remaining_size -= cur_size; + } + } + + } + + void SetMemoryPermissionImpl(uintptr_t address, size_t size, MemoryPermission perm) { + switch (perm) { + case MemoryPermission_None: + return SetMemoryPermissionBySvc(address, size, svc::MemoryPermission_None); + case MemoryPermission_ReadOnly: + return SetMemoryPermissionBySvc(address, size, svc::MemoryPermission_Read); + case MemoryPermission_ReadWrite: + return SetMemoryPermissionBySvc(address, size, svc::MemoryPermission_ReadWrite); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_permission_impl.os.linux.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_permission_impl.os.linux.cpp new file mode 100644 index 00000000..d75a4891 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_permission_impl.os.linux.cpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <sys/mman.h> + +namespace ams::os::impl { + + void SetMemoryPermissionImpl(uintptr_t address, size_t size, MemoryPermission perm) { + switch (perm) { + case MemoryPermission_None: + { + auto res = ::mprotect(reinterpret_cast<void *>(address), size, PROT_NONE); + AMS_ABORT_UNLESS(res); + AMS_UNUSED(res); + } + break; + case MemoryPermission_ReadOnly: + { + auto res = ::mprotect(reinterpret_cast<void *>(address), size, PROT_READ); + AMS_ABORT_UNLESS(res); + AMS_UNUSED(res); + } + break; + case MemoryPermission_ReadWrite: + { + auto res = ::mprotect(reinterpret_cast<void *>(address), size, PROT_READ | PROT_WRITE); + AMS_ABORT_UNLESS(res); + AMS_UNUSED(res); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_permission_impl.os.macos.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_permission_impl.os.macos.cpp new file mode 100644 index 00000000..d75a4891 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_permission_impl.os.macos.cpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <sys/mman.h> + +namespace ams::os::impl { + + void SetMemoryPermissionImpl(uintptr_t address, size_t size, MemoryPermission perm) { + switch (perm) { + case MemoryPermission_None: + { + auto res = ::mprotect(reinterpret_cast<void *>(address), size, PROT_NONE); + AMS_ABORT_UNLESS(res); + AMS_UNUSED(res); + } + break; + case MemoryPermission_ReadOnly: + { + auto res = ::mprotect(reinterpret_cast<void *>(address), size, PROT_READ); + AMS_ABORT_UNLESS(res); + AMS_UNUSED(res); + } + break; + case MemoryPermission_ReadWrite: + { + auto res = ::mprotect(reinterpret_cast<void *>(address), size, PROT_READ | PROT_WRITE); + AMS_ABORT_UNLESS(res); + AMS_UNUSED(res); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_permission_impl.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_permission_impl.os.windows.cpp new file mode 100644 index 00000000..eaed1f80 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_memory_permission_impl.os.windows.cpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <stratosphere/windows.hpp> + +namespace ams::os::impl { + + void SetMemoryPermissionImpl(uintptr_t address, size_t size, MemoryPermission perm) { + DWORD old; + + uintptr_t cur_address = address; + size_t remaining = size; + while (remaining > 0) { + const size_t cur_size = std::min<size_t>(remaining, 2_GB); + switch (perm) { + case MemoryPermission_None: + { + auto res = ::VirtualProtect(reinterpret_cast<LPVOID>(cur_address), static_cast<DWORD>(cur_size), PAGE_NOACCESS, std::addressof(old)); + AMS_ABORT_UNLESS(res); + AMS_UNUSED(res); + } + break; + case MemoryPermission_ReadOnly: + { + auto res = ::VirtualProtect(reinterpret_cast<LPVOID>(cur_address), static_cast<DWORD>(cur_size), PAGE_READONLY, std::addressof(old)); + AMS_ABORT_UNLESS(res); + AMS_UNUSED(res); + } + break; + case MemoryPermission_ReadWrite: + { + auto res = ::VirtualProtect(reinterpret_cast<LPVOID>(cur_address), static_cast<DWORD>(cur_size), PAGE_READWRITE, std::addressof(old)); + AMS_ABORT_UNLESS(res); + AMS_UNUSED(res); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + cur_address += cur_size; + remaining -= cur_size; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_message_queue_helper.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_message_queue_helper.hpp new file mode 100644 index 00000000..2e1813b2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_message_queue_helper.hpp @@ -0,0 +1,118 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + template<typename T> + concept IsMessageQueueType = requires(T &t) { + { t.buffer } -> std::convertible_to<uintptr_t *>; + { t.offset } -> std::convertible_to<s32>; + { t.count } -> std::same_as<decltype(t.offset) &>; + { t.capacity } -> std::same_as<decltype(t.count) &>; + }; + + template<typename T> requires IsMessageQueueType<T> + class MessageQueueHelper { + public: + static ALWAYS_INLINE bool IsMessageQueueFull(const T *mq) { + return mq->count >= mq->capacity; + } + + static ALWAYS_INLINE bool IsMessageQueueEmpty(const T *mq) { + return mq->count == 0; + } + + static void EnqueueUnsafe(T *mq, uintptr_t data) { + /* Ensure our limits are correct. */ + auto count = mq->count; + const auto capacity = mq->capacity; + AMS_ASSERT(count < capacity); + + /* Determine where we're writing. */ + auto ind = mq->offset + count; + if (ind >= capacity) { + ind -= capacity; + } + AMS_ASSERT(0 <= ind && ind < capacity); + + /* Write the data. */ + mq->buffer[ind] = data; + ++count; + + /* Update tracking. */ + mq->count = count; + } + + static uintptr_t DequeueUnsafe(T *mq) { + /* Ensure our limits are correct. */ + auto count = mq->count; + auto offset = mq->offset; + const auto capacity = mq->capacity; + AMS_ASSERT(count > 0); + AMS_ASSERT(offset >= 0 && offset < capacity); + + /* Get the data. */ + auto data = mq->buffer[offset++]; + + /* Calculate new tracking variables. */ + if (offset >= capacity) { + offset -= capacity; + } + --count; + + /* Update tracking. */ + mq->offset = offset; + mq->count = count; + + return data; + } + + static void JamUnsafe(T *mq, uintptr_t data) { + /* Ensure our limits are correct. */ + auto count = mq->count; + const auto capacity = mq->capacity; + AMS_ASSERT(count < capacity); + + /* Determine where we're writing. */ + auto offset = mq->offset - 1; + if (offset < 0) { + offset += capacity; + } + AMS_ASSERT(0 <= offset && offset < capacity); + + /* Write the data. */ + mq->buffer[offset] = data; + ++count; + + /* Update tracking. */ + mq->offset = offset; + mq->count = count; + } + + static uintptr_t PeekUnsafe(const T *mq) { + /* Ensure our limits are correct. */ + const auto count = mq->count; + const auto offset = mq->offset; + AMS_ASSERT(count > 0); + AMS_UNUSED(count); + + return mq->buffer[offset]; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_base.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_base.hpp new file mode 100644 index 00000000..9e2bdee1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_base.hpp @@ -0,0 +1,81 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class MultiWaitObjectList; + class MultiWaitImpl; + + class MultiWaitHolderBase { + private: + MultiWaitImpl *m_multi_wait = nullptr; + public: + util::IntrusiveListNode m_multi_wait_node; + util::IntrusiveListNode m_object_list_node; + public: + /* Gets whether the held object is currently signaled. */ + virtual TriBool IsSignaled() const = 0; + + /* Adds to multi wait's object list, returns is signaled. */ + virtual TriBool AddToObjectList() = 0; + + /* Removes from the multi wait's object list. */ + virtual void RemoveFromObjectList() = 0; + + /* Gets whether waitable has a native handle, writes to output if it does. */ + virtual bool GetNativeHandle(os::NativeHandle *) const = 0; + + /* Gets the amount of time remaining until this wakes up. */ + virtual TimeSpan GetAbsoluteTimeToWakeup() const { + return TimeSpan::FromNanoSeconds(std::numeric_limits<s64>::max()); + } + + /* Interface with multi wait. */ + ALWAYS_INLINE void SetMultiWait(MultiWaitImpl *m) { + m_multi_wait = m; + } + + ALWAYS_INLINE MultiWaitImpl *GetMultiWait() const { + return m_multi_wait; + } + + ALWAYS_INLINE bool IsLinked() const { return m_multi_wait != nullptr; } + ALWAYS_INLINE bool IsNotLinked() const { return m_multi_wait == nullptr; } + }; + + class MultiWaitHolderOfUserWaitObject : public MultiWaitHolderBase { + public: + /* All user objects have no handle to wait on. */ + virtual bool GetNativeHandle(os::NativeHandle *) const override final { + return false; + } + }; + + class MultiWaitHolderOfNativeWaitObject : public MultiWaitHolderBase { + public: + /* All native objects have native handles, and thus don't have object list semantics. */ + virtual TriBool AddToObjectList() override final { + return TriBool::Undefined; + } + + virtual void RemoveFromObjectList() override final { + /* ... */ + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_impl.hpp new file mode 100644 index 00000000..eb863e07 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_impl.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "os_multiple_wait_holder_of_native_handle.hpp" +#include "os_multiple_wait_holder_of_event.hpp" +#include "os_multiple_wait_holder_of_inter_process_event.hpp" +#include "os_multiple_wait_holder_of_interrupt_event.hpp" +#include "os_multiple_wait_holder_of_timer_event.hpp" +#include "os_multiple_wait_holder_of_thread.hpp" +#include "os_multiple_wait_holder_of_semaphore.hpp" +#include "os_multiple_wait_holder_of_message_queue.hpp" + +namespace ams::os::impl { + + struct MultiWaitHolderImpl { + union { + util::TypedStorage<MultiWaitHolderOfNativeHandle> holder_of_native_handle_storage; + util::TypedStorage<MultiWaitHolderOfEvent> holder_of_event_storage; + util::TypedStorage<MultiWaitHolderOfInterProcessEvent> holder_of_inter_process_event_storage; + util::TypedStorage<MultiWaitHolderOfInterruptEvent> holder_of_interrupt_event_storage; + util::TypedStorage<MultiWaitHolderOfTimerEvent> holder_of_timer_event_storage; + util::TypedStorage<MultiWaitHolderOfThread> holder_of_thread_storage; + util::TypedStorage<MultiWaitHolderOfSemaphore> holder_of_semaphore_storage; + util::TypedStorage<MultiWaitHolderOfMessageQueueNotFull> holder_of_mq_not_full_storage; + util::TypedStorage<MultiWaitHolderOfMessageQueueNotEmpty> holder_of_mq_not_empty_storage; + }; + }; + + #define CHECK_HOLDER(T) \ + static_assert(std::is_base_of<::ams::os::impl::MultiWaitHolderBase, T>::value && std::is_trivially_destructible<T>::value, #T) + + CHECK_HOLDER(MultiWaitHolderOfNativeHandle); + CHECK_HOLDER(MultiWaitHolderOfEvent); + CHECK_HOLDER(MultiWaitHolderOfInterProcessEvent); + CHECK_HOLDER(MultiWaitHolderOfInterruptEvent); + CHECK_HOLDER(MultiWaitHolderOfTimerEvent); + CHECK_HOLDER(MultiWaitHolderOfThread); + CHECK_HOLDER(MultiWaitHolderOfSemaphore); + CHECK_HOLDER(MultiWaitHolderOfMessageQueueNotFull); + CHECK_HOLDER(MultiWaitHolderOfMessageQueueNotEmpty); + + #undef CHECK_HOLDER + + static_assert(std::is_trivial<MultiWaitHolderImpl>::value && std::is_trivially_destructible<MultiWaitHolderImpl>::value); + static_assert(sizeof(MultiWaitHolderImpl) == sizeof(os::MultiWaitHolderType::impl_storage)); +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_event.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_event.hpp new file mode 100644 index 00000000..b1099f18 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_event.hpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "os_multiple_wait_holder_base.hpp" +#include "os_multiple_wait_object_list.hpp" + +namespace ams::os::impl { + + class MultiWaitHolderOfEvent : public MultiWaitHolderOfUserWaitObject { + private: + EventType *m_event; + private: + ALWAYS_INLINE TriBool IsSignaledUnsafe() const { + return m_event->signaled ? TriBool::True : TriBool::False; + } + public: + explicit MultiWaitHolderOfEvent(EventType *e) : m_event(e) { /* ... */ } + + /* IsSignaled, Link, Unlink implemented. */ + virtual TriBool IsSignaled() const override { + std::scoped_lock lk(GetReference(m_event->cs_event)); + return this->IsSignaledUnsafe(); + } + + virtual TriBool AddToObjectList() override { + std::scoped_lock lk(GetReference(m_event->cs_event)); + + GetReference(m_event->multi_wait_object_list_storage).PushBackToList(*this); + return this->IsSignaledUnsafe(); + } + + virtual void RemoveFromObjectList() override { + std::scoped_lock lk(GetReference(m_event->cs_event)); + + GetReference(m_event->multi_wait_object_list_storage).EraseFromList(*this); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_inter_process_event.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_inter_process_event.hpp new file mode 100644 index 00000000..ab44f6b1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_inter_process_event.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "os_multiple_wait_holder_base.hpp" +#include "os_inter_process_event.hpp" + +namespace ams::os::impl { + + class MultiWaitHolderOfInterProcessEvent : public MultiWaitHolderOfNativeWaitObject { + private: + InterProcessEventType *m_event; + public: + explicit MultiWaitHolderOfInterProcessEvent(InterProcessEventType *e) : m_event(e) { /* ... */ } + + /* IsSignaled, GetHandle both implemented. */ + virtual TriBool IsSignaled() const override { + return TriBool::Undefined; + } + + virtual bool GetNativeHandle(os::NativeHandle *out) const override { + *out = m_event->readable_handle; + return true; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_interrupt_event.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_interrupt_event.cpp new file mode 100644 index 00000000..8f7b4371 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_interrupt_event.cpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_multiple_wait_holder_of_interrupt_event.hpp" +#include "os_interrupt_event_impl.hpp" + +namespace ams::os::impl { + + bool MultiWaitHolderOfInterruptEvent::GetNativeHandle(os::NativeHandle *out) const { + *out = GetReference(m_event->impl).GetHandle(); + return true; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_interrupt_event.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_interrupt_event.hpp new file mode 100644 index 00000000..13a92e50 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_interrupt_event.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "os_multiple_wait_holder_base.hpp" + +namespace ams::os::impl { + + class MultiWaitHolderOfInterruptEvent : public MultiWaitHolderOfNativeWaitObject { + private: + InterruptEventType *m_event; + public: + explicit MultiWaitHolderOfInterruptEvent(InterruptEventType *e) : m_event(e) { /* ... */ } + + /* IsSignaled, GetHandle both implemented. */ + virtual TriBool IsSignaled() const override { + return TriBool::Undefined; + } + + virtual bool GetNativeHandle(os::NativeHandle *out) const override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_message_queue.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_message_queue.hpp new file mode 100644 index 00000000..826026b7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_message_queue.hpp @@ -0,0 +1,82 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "os_multiple_wait_holder_base.hpp" +#include "os_multiple_wait_object_list.hpp" + +namespace ams::os::impl { + + class MultiWaitHolderOfMessageQueueNotEmpty : public MultiWaitHolderOfUserWaitObject { + private: + MessageQueueType *m_mq; + private: + ALWAYS_INLINE TriBool IsSignaledUnsafe() const { + return m_mq->count > 0 ? TriBool::True : TriBool::False; + } + public: + explicit MultiWaitHolderOfMessageQueueNotEmpty(MessageQueueType *mq) : m_mq(mq) { /* ... */ } + + /* IsSignaled, Link, Unlink implemented. */ + virtual TriBool IsSignaled() const override { + std::scoped_lock lk(GetReference(m_mq->cs_queue)); + return this->IsSignaledUnsafe(); + } + + virtual TriBool AddToObjectList() override { + std::scoped_lock lk(GetReference(m_mq->cs_queue)); + + GetReference(m_mq->waitlist_not_empty).PushBackToList(*this); + return this->IsSignaledUnsafe(); + } + + virtual void RemoveFromObjectList() override { + std::scoped_lock lk(GetReference(m_mq->cs_queue)); + + GetReference(m_mq->waitlist_not_empty).EraseFromList(*this); + } + }; + + class MultiWaitHolderOfMessageQueueNotFull : public MultiWaitHolderOfUserWaitObject { + private: + MessageQueueType *m_mq; + private: + ALWAYS_INLINE TriBool IsSignaledUnsafe() const { + return m_mq->count < m_mq->capacity ? TriBool::True : TriBool::False; + } + public: + explicit MultiWaitHolderOfMessageQueueNotFull(MessageQueueType *mq) : m_mq(mq) { /* ... */ } + + /* IsSignaled, Link, Unlink implemented. */ + virtual TriBool IsSignaled() const override { + std::scoped_lock lk(GetReference(m_mq->cs_queue)); + return this->IsSignaledUnsafe(); + } + + virtual TriBool AddToObjectList() override { + std::scoped_lock lk(GetReference(m_mq->cs_queue)); + + GetReference(m_mq->waitlist_not_full).PushBackToList(*this); + return this->IsSignaledUnsafe(); + } + + virtual void RemoveFromObjectList() override { + std::scoped_lock lk(GetReference(m_mq->cs_queue)); + + GetReference(m_mq->waitlist_not_full).EraseFromList(*this); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_native_handle.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_native_handle.hpp new file mode 100644 index 00000000..8c76babe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_native_handle.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "os_multiple_wait_holder_base.hpp" + +namespace ams::os::impl { + + class MultiWaitHolderOfNativeHandle : public MultiWaitHolderOfNativeWaitObject { + private: + NativeHandle m_handle; + public: + explicit MultiWaitHolderOfNativeHandle(NativeHandle h) : m_handle(h) { /* ... */ } + + /* IsSignaled, GetHandle both implemented. */ + virtual TriBool IsSignaled() const override { + return TriBool::Undefined; + } + + virtual bool GetNativeHandle(os::NativeHandle *out) const override { + *out = m_handle; + return true; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_semaphore.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_semaphore.hpp new file mode 100644 index 00000000..31ed6014 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_semaphore.hpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "os_multiple_wait_holder_base.hpp" +#include "os_multiple_wait_object_list.hpp" + +namespace ams::os::impl { + + class MultiWaitHolderOfSemaphore : public MultiWaitHolderOfUserWaitObject { + private: + SemaphoreType *m_semaphore; + private: + TriBool IsSignaledImpl() const { + return m_semaphore->count > 0 ? TriBool::True : TriBool::False; + } + public: + explicit MultiWaitHolderOfSemaphore(SemaphoreType *s) : m_semaphore(s) { /* ... */ } + + /* IsSignaled, Link, Unlink implemented. */ + virtual TriBool IsSignaled() const override { + std::scoped_lock lk(GetReference(m_semaphore->cs_sema)); + return this->IsSignaledImpl(); + } + + virtual TriBool AddToObjectList() override { + std::scoped_lock lk(GetReference(m_semaphore->cs_sema)); + + GetReference(m_semaphore->waitlist).PushBackToList(*this); + return this->IsSignaledImpl(); + } + + virtual void RemoveFromObjectList() override { + std::scoped_lock lk(GetReference(m_semaphore->cs_sema)); + + GetReference(m_semaphore->waitlist).EraseFromList(*this); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_thread.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_thread.hpp new file mode 100644 index 00000000..5ba44682 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_thread.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "os_multiple_wait_holder_base.hpp" + +namespace ams::os::impl { + + class MultiWaitHolderOfThread : public MultiWaitHolderOfUserWaitObject { + private: + ThreadType *m_thread; + private: + TriBool IsSignaledImpl() const { + return m_thread->state == ThreadType::State_Terminated ? TriBool::True : TriBool::False; + } + public: + explicit MultiWaitHolderOfThread(ThreadType *t) : m_thread(t) { /* ... */ } + + /* IsSignaled, Link, Unlink implemented. */ + virtual TriBool IsSignaled() const override { + std::scoped_lock lk(GetReference(m_thread->cs_thread)); + return this->IsSignaledImpl(); + } + + virtual TriBool AddToObjectList() override { + std::scoped_lock lk(GetReference(m_thread->cs_thread)); + + GetReference(m_thread->waitlist).PushBackToList(*this); + return this->IsSignaledImpl(); + } + + virtual void RemoveFromObjectList() override { + std::scoped_lock lk(GetReference(m_thread->cs_thread)); + + GetReference(m_thread->waitlist).EraseFromList(*this); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_timer_event.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_timer_event.hpp new file mode 100644 index 00000000..51708919 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_holder_of_timer_event.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_timer_event_helper.hpp" +#include "os_multiple_wait_holder_base.hpp" +#include "os_multiple_wait_object_list.hpp" + +namespace ams::os::impl { + + class MultiWaitHolderOfTimerEvent : public MultiWaitHolderOfUserWaitObject { + private: + TimerEventType *m_event; + private: + TriBool IsSignaledUnsafe() const { + TimeSpan cur_time = this->GetMultiWait()->GetCurrTime(); + + os::impl::UpdateSignalStateAndRecalcNextTimeToWakeupUnsafe(m_event, cur_time); + + return m_event->signaled ? TriBool::True : TriBool::False; + } + public: + explicit MultiWaitHolderOfTimerEvent(TimerEventType *e) : m_event(e) { /* ... */ } + + /* IsSignaled, Link, Unlink implemented. */ + virtual TriBool IsSignaled() const override { + std::scoped_lock lk(GetReference(m_event->cs_timer_event)); + + return this->IsSignaledUnsafe(); + } + + virtual TriBool AddToObjectList() override { + std::scoped_lock lk(GetReference(m_event->cs_timer_event)); + + GetReference(m_event->multi_wait_object_list_storage).PushBackToList(*this); + + return this->IsSignaledUnsafe(); + } + + virtual void RemoveFromObjectList() override { + std::scoped_lock lk(GetReference(m_event->cs_timer_event)); + + GetReference(m_event->multi_wait_object_list_storage).EraseFromList(*this); + } + + /* Gets the amount of time remaining until this wakes up. */ + virtual TimeSpan GetAbsoluteTimeToWakeup() const override { + std::scoped_lock lk(GetReference(m_event->cs_timer_event)); + + return m_event->timer_state != TimerEventType::TimerState_Stop ? GetReference(m_event->next_time_to_wakeup) + : TimeSpan::FromNanoSeconds(std::numeric_limits<s64>::max()); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_impl.cpp new file mode 100644 index 00000000..91b279c6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_impl.cpp @@ -0,0 +1,265 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_multiple_wait_impl.hpp" +#include "os_multiple_wait_object_list.hpp" +#include "os_tick_manager.hpp" + +namespace ams::os::impl { + + template<bool AllowReply> + Result MultiWaitImpl::WaitAnyImpl(MultiWaitHolderBase **out, bool infinite, TimeSpan timeout, NativeHandle reply_target) { + /* Prepare for processing. */ + m_signaled_holder = nullptr; + m_target_impl.SetCurrentThreadHandleForCancelWait(); + + /* Add each holder to the object list, and try to find one that's signaled. */ + MultiWaitHolderBase *signaled_holder = this->AddToEachObjectListAndCheckObjectState(); + + /* When we're done, cleanup and set output. */ + ON_SCOPE_EXIT { + /* Remove each holder from the current object list. */ + this->RemoveFromEachObjectList(); + + /* Clear cancel wait. */ + m_target_impl.ClearCurrentThreadHandleForCancelWait(); + + /* Set output holder. */ + *out = signaled_holder; + }; + + /* Check if we've been signaled. */ + { + std::scoped_lock lk(m_cs_wait); + if (m_signaled_holder != nullptr) { + signaled_holder = m_signaled_holder; + } + } + + /* Process object array. */ + if (signaled_holder != nullptr) { + /* If we have a signaled holder and we're allowed to reply, try to do so. */ + if constexpr (AllowReply) { + /* Try to reply to the reply target. */ + if (reply_target != os::InvalidNativeHandle) { + ON_RESULT_FAILURE { signaled_holder = nullptr; }; + + s32 index; + R_TRY(m_target_impl.TimedReplyAndReceive(std::addressof(index), nullptr, 0, 0, reply_target, TimeSpan::FromNanoSeconds(0))); + } + } + } else { + /* If there's no signaled holder, wait for one to be signaled. */ + R_TRY(this->InternalWaitAnyImpl<AllowReply>(std::addressof(signaled_holder), infinite, timeout, reply_target)); + } + + R_SUCCEED(); + } + + template<bool AllowReply> + Result MultiWaitImpl::InternalWaitAnyImpl(MultiWaitHolderBase **out, bool infinite, TimeSpan timeout, NativeHandle reply_target) { + /* Build the objects array. */ + NativeHandle object_handles[MaximumHandleCount]; + MultiWaitHolderBase *objects[MaximumHandleCount]; + + const s32 count = this->ConstructObjectsArray(object_handles, objects, MaximumHandleCount); + + /* Determine the appropriate end time for our wait. */ + const TimeSpan end_time = infinite ? TimeSpan::FromNanoSeconds(std::numeric_limits<s64>::max()) : os::impl::GetCurrentTick().ToTimeSpan() + timeout; + + /* Loop, waiting until we're done. */ + while (true) { + /* Update the current time for our loop. */ + m_current_time = os::impl::GetCurrentTick().ToTimeSpan(); + + /* Determine which object has the minimum wakeup time. */ + TimeSpan min_timeout = 0; + MultiWaitHolderBase *min_timeout_object = this->RecalcMultiWaitTimeout(std::addressof(min_timeout), end_time); + + /* Perform the wait using native apis. */ + s32 index = WaitInvalid; + Result wait_result = ResultSuccess(); + if (infinite && min_timeout_object == nullptr) { + /* If we're performing an infinite wait, just do the appropriate wait or reply/receive. */ + if constexpr (AllowReply) { + wait_result = m_target_impl.ReplyAndReceive(std::addressof(index), object_handles, MaximumHandleCount, count, reply_target); + } else { + wait_result = m_target_impl.WaitAny(std::addressof(index), object_handles, MaximumHandleCount, count); + } + } else { + /* We need to do our wait with a timeout. */ + if constexpr (AllowReply) { + wait_result = m_target_impl.TimedReplyAndReceive(std::addressof(index), object_handles, MaximumHandleCount, count, reply_target, min_timeout); + } else { + if (count != 0 || min_timeout != 0) { + wait_result = m_target_impl.TimedWaitAny(std::addressof(index), object_handles, MaximumHandleCount, count, min_timeout); + } else { + index = WaitTimedOut; + } + } + } + + /* Process the result of our wait. */ + switch (index) { + case WaitInvalid: + /* If an invalid wait was performed, just return no signaled holder. */ + { + *out = nullptr; + R_RETURN(wait_result); + } + break; + case WaitCancelled: + /* If the wait was canceled, it might be because a non-native waitable was signaled. Check and return it, if this is the case. */ + { + std::scoped_lock lk(m_cs_wait); + + if (m_signaled_holder) { + *out = m_signaled_holder; + R_RETURN(wait_result); + } + } + break; + case WaitTimedOut: + /* If we timed out, this might have been because a timer is now signaled. */ + if (min_timeout_object != nullptr) { + /* Update our current time. */ + m_current_time = GetCurrentTick().ToTimeSpan(); + + /* Check if the minimum timeout object is now signaled. */ + if (min_timeout_object->IsSignaled() == TriBool::True) { + std::scoped_lock lk(m_cs_wait); + + /* Set our signaled holder (and the output) as the newly signaled minimum timeout object. */ + m_signaled_holder = min_timeout_object; + *out = min_timeout_object; + R_RETURN(wait_result); + } + } else { + /* If we have no minimum timeout object but we timed out, just return no signaled holder. */ + *out = nullptr; + R_RETURN(wait_result); + } + break; + default: /* 0 - 0x3F, valid. */ + { + /* Sanity check that the returned index is within the range of our objects array. */ + AMS_ASSERT(0 <= index && index < count); + + std::scoped_lock lk(m_cs_wait); + + /* Set our signaled holder (and the output) as the newly signaled object. */ + m_signaled_holder = objects[index]; + *out = objects[index]; + R_RETURN(wait_result); + } + break; + } + + /* We're going to be looping again; prevent ourselves from replying to the same object twice. */ + if constexpr (AllowReply) { + reply_target = os::InvalidNativeHandle; + } + } + } + + MultiWaitHolderBase *MultiWaitImpl::WaitAnyImpl(bool infinite, TimeSpan timeout) { + MultiWaitHolderBase *holder = nullptr; + + const Result wait_result = this->WaitAnyImpl<false>(std::addressof(holder), infinite, timeout, os::InvalidNativeHandle); + R_ASSERT(wait_result); + AMS_UNUSED(wait_result); + + return holder; + } + + Result MultiWaitImpl::ReplyAndReceive(MultiWaitHolderBase **out, NativeHandle reply_target) { + R_RETURN(this->WaitAnyImpl<true>(out, true, TimeSpan::FromNanoSeconds(std::numeric_limits<s64>::max()), reply_target)); + } + + s32 MultiWaitImpl::ConstructObjectsArray(NativeHandle out_handles[], MultiWaitHolderBase *out_objects[], s32 num) { + /* Add all objects with a native handle to the output array. */ + s32 count = 0; + + for (MultiWaitHolderBase &holder_base : m_multi_wait_list) { + os::NativeHandle handle = os::InvalidNativeHandle; + if (holder_base.GetNativeHandle(std::addressof(handle))) { + AMS_ABORT_UNLESS(count < num); + + out_handles[count] = handle; + out_objects[count] = std::addressof(holder_base); + + ++count; + } + } + + return count; + } + + MultiWaitHolderBase *MultiWaitImpl::AddToEachObjectListAndCheckObjectState() { + /* Add each holder to the current object list, checking for the first signaled object. */ + MultiWaitHolderBase *signaled_holder = nullptr; + + for (MultiWaitHolderBase &holder_base : m_multi_wait_list) { + if (const TriBool is_signaled = holder_base.AddToObjectList(); signaled_holder == nullptr && is_signaled == TriBool::True) { + signaled_holder = std::addressof(holder_base); + } + } + + return signaled_holder; + } + + void MultiWaitImpl::RemoveFromEachObjectList() { + /* Remove each holder from the current object list. */ + for (MultiWaitHolderBase &holder_base : m_multi_wait_list) { + holder_base.RemoveFromObjectList(); + } + } + + MultiWaitHolderBase *MultiWaitImpl::RecalcMultiWaitTimeout(TimeSpan *out_min_timeout, TimeSpan end_time) { + /* Find the holder with the minimum end time. */ + MultiWaitHolderBase *min_timeout_holder = nullptr; + TimeSpan min_time = end_time; + + for (MultiWaitHolderBase &holder_base : m_multi_wait_list) { + if (const TimeSpan cur_time = holder_base.GetAbsoluteTimeToWakeup(); cur_time < min_time) { + min_timeout_holder = std::addressof(holder_base); + min_time = cur_time; + } + } + + /* If the minimum time is under the current time, we can't wait; otherwise, get the time to the minimum end time. */ + if (min_time < m_current_time) { + *out_min_timeout = 0; + } else { + *out_min_timeout = min_time - m_current_time; + } + + return min_timeout_holder; + } + + void MultiWaitImpl::NotifyAndWakeupThread(MultiWaitHolderBase *holder_base) { + std::scoped_lock lk(m_cs_wait); + + /* If we don't have a signaled holder, set our signaled holder. */ + if (m_signaled_holder == nullptr) { + m_signaled_holder = holder_base; + + /* Cancel any ongoing waits. */ + m_target_impl.CancelWait(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_impl.hpp new file mode 100644 index 00000000..0007ab76 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_impl.hpp @@ -0,0 +1,118 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "os_multiple_wait_holder_base.hpp" + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "os_multiple_wait_target_impl.os.horizon.hpp" +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include "os_multiple_wait_target_impl.os.windows.hpp" +#elif defined(ATMOSPHERE_OS_LINUX) + #include "os_multiple_wait_target_impl.os.linux.hpp" +#elif defined(ATMOSPHERE_OS_MACOS) + #include "os_multiple_wait_target_impl.os.macos.hpp" +#else + #error "Unknown OS for ams::os::MultiWaitTargetImpl" +#endif + +namespace ams::os::impl { + + class MultiWaitImpl { + public: + static constexpr size_t MaximumHandleCount = MultiWaitTargetImpl::MaximumHandleCount; + static constexpr s32 WaitInvalid = -3; + static constexpr s32 WaitCancelled = -2; + static constexpr s32 WaitTimedOut = -1; + using MultiWaitList = util::IntrusiveListMemberTraitsByNonConstexprOffsetOf<&MultiWaitHolderBase::m_multi_wait_node>::ListType; + private: + MultiWaitList m_multi_wait_list; + MultiWaitHolderBase *m_signaled_holder; + TimeSpan m_current_time; + InternalCriticalSection m_cs_wait; + MultiWaitTargetImpl m_target_impl; + private: + template<bool AllowReply> + Result WaitAnyImpl(MultiWaitHolderBase **out, bool infinite, TimeSpan timeout, NativeHandle reply_target); + + template<bool AllowReply> + Result InternalWaitAnyImpl(MultiWaitHolderBase **out, bool infinite, TimeSpan timeout, NativeHandle reply_target); + + s32 ConstructObjectsArray(NativeHandle out_handles[], MultiWaitHolderBase *out_objects[], s32 num); + + MultiWaitHolderBase *AddToEachObjectListAndCheckObjectState(); + void RemoveFromEachObjectList(); + + MultiWaitHolderBase *RecalcMultiWaitTimeout(TimeSpan *out_min_timeout, TimeSpan end_time); + + MultiWaitHolderBase *WaitAnyImpl(bool infinite, TimeSpan timeout); + public: + /* Wait. */ + MultiWaitHolderBase *WaitAny() { + return this->WaitAnyImpl(true, TimeSpan::FromNanoSeconds(std::numeric_limits<s64>::max())); + } + + MultiWaitHolderBase *TryWaitAny() { + return this->WaitAnyImpl(false, TimeSpan(0)); + } + + MultiWaitHolderBase *TimedWaitAny(TimeSpan ts) { + return this->WaitAnyImpl(false, ts); + } + + Result ReplyAndReceive(MultiWaitHolderBase **out, NativeHandle reply_target); + + /* List management. */ + bool IsListEmpty() const { + return m_multi_wait_list.empty(); + } + + bool IsListNotEmpty() const { + return !m_multi_wait_list.empty(); + } + + void PushBackToList(MultiWaitHolderBase &holder_base) { + m_multi_wait_list.push_back(holder_base); + } + + void EraseFromList(MultiWaitHolderBase &holder_base) { + m_multi_wait_list.erase(m_multi_wait_list.iterator_to(holder_base)); + } + + void EraseAllFromList() { + while (!m_multi_wait_list.empty()) { + m_multi_wait_list.front().SetMultiWait(nullptr); + m_multi_wait_list.pop_front(); + } + } + + void MoveAllFromOther(MultiWaitImpl &other) { + /* Set ourselves as multi wait for all of the other's holders. */ + for (auto &w : other.m_multi_wait_list) { + w.SetMultiWait(this); + } + + m_multi_wait_list.splice(m_multi_wait_list.end(), other.m_multi_wait_list); + } + + /* Other. */ + TimeSpan GetCurrTime() const { + return m_current_time; + } + + void NotifyAndWakeupThread(MultiWaitHolderBase *holder_base); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_object_list.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_object_list.hpp new file mode 100644 index 00000000..04c90f0d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_object_list.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "os_multiple_wait_holder_base.hpp" +#include "os_multiple_wait_impl.hpp" + +namespace ams::os::impl { + + class MultiWaitObjectList { + public: + using ListType = util::IntrusiveListMemberTraitsByNonConstexprOffsetOf<&MultiWaitHolderBase::m_object_list_node>::ListType; + private: + ListType m_object_list; + public: + void WakeupAllMultiWaitThreadsUnsafe() { + for (MultiWaitHolderBase &holder_base : m_object_list) { + holder_base.GetMultiWait()->NotifyAndWakeupThread(std::addressof(holder_base)); + } + } + + void BroadcastToUpdateObjectStateUnsafe() { + for (MultiWaitHolderBase &holder_base : m_object_list) { + holder_base.GetMultiWait()->NotifyAndWakeupThread(nullptr); + } + } + + bool IsEmpty() const { + return m_object_list.empty(); + } + + void PushBackToList(MultiWaitHolderBase &holder_base) { + m_object_list.push_back(holder_base); + } + + void EraseFromList(MultiWaitHolderBase &holder_base) { + m_object_list.erase(m_object_list.iterator_to(holder_base)); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.horizon.cpp new file mode 100644 index 00000000..a45f3d57 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.horizon.cpp @@ -0,0 +1,75 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_multiple_wait_holder_base.hpp" +#include "os_multiple_wait_impl.hpp" + +namespace ams::os::impl { + + Result MultiWaitHorizonImpl::WaitSynchronizationN(s32 *out_index, s32 num, NativeHandle arr[], s32 array_size, s64 ns) { + AMS_UNUSED(array_size); + + AMS_ASSERT(!(num == 0 && ns == 0)); + s32 index = MultiWaitImpl::WaitInvalid; + + R_TRY_CATCH(svc::WaitSynchronization(std::addressof(index), static_cast<const svc::Handle *>(arr), num, ns)) { + R_CATCH(svc::ResultTimedOut) { index = MultiWaitImpl::WaitTimedOut; } + R_CATCH(svc::ResultCancelled) { index = MultiWaitImpl::WaitCancelled; } + /* All other results are critical errors. */ + /* svc::ResultThreadTerminating */ + /* svc::ResultInvalidHandle. */ + /* svc::ResultInvalidPointer */ + /* svc::ResultOutOfRange */ + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + *out_index = index; + R_SUCCEED(); + } + + Result MultiWaitHorizonImpl::ReplyAndReceiveN(s32 *out_index, s32 num, NativeHandle arr[], s32 array_size, s64 ns, NativeHandle reply_target) { + AMS_UNUSED(array_size); + + /* NOTE: Nintendo does not initialize this value, which seems like it can cause incorrect behavior. */ + s32 index = MultiWaitImpl::WaitInvalid; + static_assert(MultiWaitImpl::WaitInvalid != -1); + + R_TRY_CATCH(svc::ReplyAndReceive(std::addressof(index), arr, num, reply_target, ns)) { + R_CATCH(svc::ResultTimedOut) { *out_index = MultiWaitImpl::WaitTimedOut; R_THROW(R_CURRENT_RESULT); } + R_CATCH(svc::ResultCancelled) { *out_index = MultiWaitImpl::WaitCancelled; R_THROW(R_CURRENT_RESULT); } + R_CATCH(svc::ResultSessionClosed) { + if (index == -1) { + *out_index = MultiWaitImpl::WaitInvalid; + R_THROW(os::ResultSessionClosedForReply()); + } else { + *out_index = index; + R_THROW(os::ResultSessionClosedForReceive()); + } + } + R_CATCH(svc::ResultReceiveListBroken) { + *out_index = index; + R_THROW(os::ResultReceiveListBroken()); + } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + *out_index = index; + R_SUCCEED(); + } + + void MultiWaitHorizonImpl::CancelWait() { + R_ABORT_UNLESS(svc::CancelSynchronization(m_handle)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.horizon.hpp new file mode 100644 index 00000000..c8d3134c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.horizon.hpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_thread_manager.hpp" + +namespace ams::os::impl { + + class MultiWaitHorizonImpl { + public: + static constexpr size_t MaximumHandleCount = static_cast<size_t>(ams::svc::ArgumentHandleCountMax); + private: + NativeHandle m_handle; + private: + Result WaitSynchronizationN(s32 *out_index, s32 num, NativeHandle arr[], s32 array_size, s64 ns); + Result ReplyAndReceiveN(s32 *out_index, s32 num, NativeHandle arr[], s32 array_size, s64 ns, NativeHandle reply_target); + public: + constexpr MultiWaitHorizonImpl() : m_handle(os::InvalidNativeHandle) { /* ... */ } + + void CancelWait(); + + Result WaitAny(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num) { + R_RETURN(this->WaitSynchronizationN(out_index, num, arr, array_size, svc::WaitInfinite)); + } + + Result TryWaitAny(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num) { + R_RETURN(this->WaitSynchronizationN(out_index, num, arr, array_size, 0)); + } + + Result TimedWaitAny(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num, TimeSpan ts) { + s64 timeout = ts.GetNanoSeconds(); + if (timeout < 0) { + timeout = 0; + } + R_RETURN(this->WaitSynchronizationN(out_index, num, arr, array_size, timeout)); + } + + Result ReplyAndReceive(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num, NativeHandle reply_target) { + R_RETURN(this->ReplyAndReceiveN(out_index, num, arr, array_size, std::numeric_limits<s64>::max(), reply_target)); + } + + Result TimedReplyAndReceive(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num, NativeHandle reply_target, TimeSpan ts) { + R_RETURN(this->ReplyAndReceiveN(out_index, num, arr, array_size, ts.GetNanoSeconds(), reply_target)); + } + + void SetCurrentThreadHandleForCancelWait() { + m_handle = GetCurrentThreadHandle(); + } + + void ClearCurrentThreadHandleForCancelWait() { + m_handle = os::InvalidNativeHandle; + } + }; + + using MultiWaitTargetImpl = MultiWaitHorizonImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.linux.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.linux.cpp new file mode 100644 index 00000000..83750fbe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.linux.cpp @@ -0,0 +1,101 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_multiple_wait_holder_base.hpp" +#include "os_multiple_wait_impl.hpp" +#include "os_timeout_helper.hpp" +#include "os_inter_process_event_impl.os.linux.hpp" + +#include <poll.h> + +namespace ams::os::impl { + + MultiWaitLinuxImpl::MultiWaitLinuxImpl() { + R_ABORT_UNLESS(InterProcessEventLinuxImpl::CreateSingle(std::addressof(m_cancel_event))); + } + + MultiWaitLinuxImpl::~MultiWaitLinuxImpl() { + InterProcessEventLinuxImpl::Close(m_cancel_event); + + m_cancel_event = InvalidNativeHandle; + } + + void MultiWaitLinuxImpl::CancelWait() { + InterProcessEventLinuxImpl::Signal(m_cancel_event); + } + + Result MultiWaitLinuxImpl::PollNativeHandlesImpl(s32 *out_index, s32 num, NativeHandle arr[], s32 array_size, s64 ns) { + /* Check that we can add our cancel handle to the wait. */ + AMS_ABORT_UNLESS(array_size <= static_cast<s32>(MaximumHandleCount)); + AMS_UNUSED(array_size); + + /* Create poll fds. */ + struct pollfd pfds[MaximumHandleCount + 1]; + for (auto i = 0; i < num; ++i) { + pfds[i].fd = arr[i]; + pfds[i].events = POLLIN; + pfds[i].revents = 0; + } + + pfds[num].fd = m_cancel_event; + pfds[num].events = POLLIN; + pfds[num].revents = 0; + + /* Determine timeout. */ + constexpr s64 NanoSecondsPerSecond = TimeSpan::FromSeconds(1).GetNanoSeconds(); + struct timespec ts = { .tv_sec = (ns / NanoSecondsPerSecond), .tv_nsec = ns % NanoSecondsPerSecond }; + + /* Wait. */ + while (true) { + const auto ret = ::ppoll(pfds, num + 1, ns >= 0 ? std::addressof(ts) : nullptr, nullptr); + if (ret < 0) { + /* Treat EINTR like a cancellation event; this will lead to a re-poll if nothing is signaled. */ + AMS_ABORT_UNLESS(errno == EINTR); + + *out_index = MultiWaitImpl::WaitCancelled; + R_SUCCEED(); + } + + /* Determine what event was polled. */ + if (ret == 0) { + *out_index = MultiWaitImpl::WaitTimedOut; + R_SUCCEED(); + } else if (pfds[num].revents != 0) { + *out_index = MultiWaitImpl::WaitCancelled; + + /* Reset our cancel event. */ + InterProcessEventLinuxImpl::Clear(m_cancel_event); + + R_SUCCEED(); + } else { + for (auto i = 0; i < num; ++i) { + if (pfds[i].revents != 0) { + *out_index = i; + R_SUCCEED(); + } + } + + AMS_ABORT("This should be impossible?"); + } + } + } + + Result MultiWaitLinuxImpl::ReplyAndReceiveImpl(s32 *out_index, s32 num, NativeHandle arr[], s32 array_size, s64 ns, NativeHandle reply_target) { + AMS_UNUSED(out_index, num, arr, array_size, ns, reply_target); + R_ABORT_UNLESS(os::ResultNotImplemented()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.linux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.linux.hpp new file mode 100644 index 00000000..ba3c9777 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.linux.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_thread_manager.hpp" + +namespace ams::os::impl { + + class MultiWaitLinuxImpl { + public: + /* TODO: This can potentially be higher. */ + static constexpr size_t MaximumHandleCount = 64; + private: + NativeHandle m_cancel_event; + private: + Result PollNativeHandlesImpl(s32 *out_index, s32 num, NativeHandle arr[], s32 array_size, s64 ns); + Result ReplyAndReceiveImpl(s32 *out_index, s32 num, NativeHandle arr[], s32 array_size, s64 ns, NativeHandle reply_target); + public: + MultiWaitLinuxImpl(); + ~MultiWaitLinuxImpl(); + + void CancelWait(); + + Result WaitAny(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num) { + R_RETURN(this->PollNativeHandlesImpl(out_index, num, arr, array_size, static_cast<s64>(-1))); + } + + Result TryWaitAny(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num) { + R_RETURN(this->PollNativeHandlesImpl(out_index, num, arr, array_size, 0)); + } + + Result TimedWaitAny(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num, TimeSpan ts) { + R_RETURN(this->PollNativeHandlesImpl(out_index, num, arr, array_size, ts.GetNanoSeconds())); + } + + Result ReplyAndReceive(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num, NativeHandle reply_target) { + R_RETURN(this->ReplyAndReceiveImpl(out_index, num, arr, array_size, std::numeric_limits<s64>::max(), reply_target)); + } + + Result TimedReplyAndReceive(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num, NativeHandle reply_target, TimeSpan ts) { + R_RETURN(this->ReplyAndReceiveImpl(out_index, num, arr, array_size, ts.GetNanoSeconds(), reply_target)); + } + + void SetCurrentThreadHandleForCancelWait() { + /* ... */ + } + + void ClearCurrentThreadHandleForCancelWait() { + /* ... */ + } + }; + + using MultiWaitTargetImpl = MultiWaitLinuxImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.macos.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.macos.cpp new file mode 100644 index 00000000..31d92d5b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.macos.cpp @@ -0,0 +1,105 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_multiple_wait_holder_base.hpp" +#include "os_multiple_wait_impl.hpp" +#include "os_timeout_helper.hpp" +#include "os_inter_process_event_impl.os.macos.hpp" + +#include <poll.h> + +namespace ams::os::impl { + + MultiWaitMacosImpl::MultiWaitMacosImpl() { + R_ABORT_UNLESS(InterProcessEventMacosImpl::Create(std::addressof(m_cancel_write_handle), std::addressof(m_cancel_read_handle))); + } + + MultiWaitMacosImpl::~MultiWaitMacosImpl() { + InterProcessEventMacosImpl::Close(m_cancel_write_handle); + InterProcessEventMacosImpl::Close(m_cancel_read_handle); + + m_cancel_write_handle = InvalidNativeHandle; + m_cancel_read_handle = InvalidNativeHandle; + } + + void MultiWaitMacosImpl::CancelWait() { + InterProcessEventMacosImpl::Signal(m_cancel_write_handle); + } + + Result MultiWaitMacosImpl::PollNativeHandlesImpl(s32 *out_index, s32 num, NativeHandle arr[], s32 array_size, s64 ns) { + /* Check that we can add our cancel handle to the wait. */ + AMS_ABORT_UNLESS(array_size <= static_cast<s32>(MaximumHandleCount)); + AMS_UNUSED(array_size); + + /* Create poll fds. */ + struct pollfd pfds[MaximumHandleCount + 1]; + for (auto i = 0; i < num; ++i) { + pfds[i].fd = arr[i]; + pfds[i].events = POLLIN; + pfds[i].revents = 0; + } + + pfds[num].fd = m_cancel_read_handle; + pfds[num].events = POLLIN; + pfds[num].revents = 0; + + /* Determine timeout. */ + constexpr s64 NanoSecondsPerMilliSecond = TimeSpan::FromMilliSeconds(1).GetNanoSeconds(); + + /* TODO: Will macos ever support ppoll? */ + const int timeout = static_cast<int>(ns >= 0 ? (ns / NanoSecondsPerMilliSecond) : -1); + + /* Wait. */ + while (true) { + const auto ret = ::poll(pfds, num + 1, timeout); + if (ret < 0) { + /* Treat EINTR like a cancellation event; this will lead to a re-poll if nothing is signaled. */ + AMS_ABORT_UNLESS(errno == EINTR); + + *out_index = MultiWaitImpl::WaitCancelled; + R_SUCCEED(); + } + + /* Determine what event was polled. */ + if (ret == 0) { + *out_index = MultiWaitImpl::WaitTimedOut; + R_SUCCEED(); + } else if (pfds[num].revents != 0) { + *out_index = MultiWaitImpl::WaitCancelled; + + /* Reset our cancel event. */ + InterProcessEventMacosImpl::Clear(m_cancel_read_handle); + + R_SUCCEED(); + } else { + for (auto i = 0; i < num; ++i) { + if (pfds[i].revents != 0) { + *out_index = i; + R_SUCCEED(); + } + } + + AMS_ABORT("This should be impossible?"); + } + } + } + + Result MultiWaitMacosImpl::ReplyAndReceiveImpl(s32 *out_index, s32 num, NativeHandle arr[], s32 array_size, s64 ns, NativeHandle reply_target) { + AMS_UNUSED(out_index, num, arr, array_size, ns, reply_target); + R_ABORT_UNLESS(os::ResultNotImplemented()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.macos.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.macos.hpp new file mode 100644 index 00000000..02a628e4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.macos.hpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_thread_manager.hpp" + +namespace ams::os::impl { + + class MultiWaitMacosImpl { + public: + /* TODO: This can potentially be higher. */ + static constexpr size_t MaximumHandleCount = 64; + private: + NativeHandle m_cancel_read_handle; + NativeHandle m_cancel_write_handle; + private: + Result PollNativeHandlesImpl(s32 *out_index, s32 num, NativeHandle arr[], s32 array_size, s64 ns); + Result ReplyAndReceiveImpl(s32 *out_index, s32 num, NativeHandle arr[], s32 array_size, s64 ns, NativeHandle reply_target); + public: + MultiWaitMacosImpl(); + ~MultiWaitMacosImpl(); + + void CancelWait(); + + Result WaitAny(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num) { + R_RETURN(this->PollNativeHandlesImpl(out_index, num, arr, array_size, static_cast<s64>(-1))); + } + + Result TryWaitAny(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num) { + R_RETURN(this->PollNativeHandlesImpl(out_index, num, arr, array_size, 0)); + } + + Result TimedWaitAny(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num, TimeSpan ts) { + R_RETURN(this->PollNativeHandlesImpl(out_index, num, arr, array_size, ts.GetNanoSeconds())); + } + + Result ReplyAndReceive(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num, NativeHandle reply_target) { + R_RETURN(this->ReplyAndReceiveImpl(out_index, num, arr, array_size, std::numeric_limits<s64>::max(), reply_target)); + } + + Result TimedReplyAndReceive(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num, NativeHandle reply_target, TimeSpan ts) { + R_RETURN(this->ReplyAndReceiveImpl(out_index, num, arr, array_size, ts.GetNanoSeconds(), reply_target)); + } + + void SetCurrentThreadHandleForCancelWait() { + /* ... */ + } + + void ClearCurrentThreadHandleForCancelWait() { + /* ... */ + } + }; + + using MultiWaitTargetImpl = MultiWaitMacosImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.windows.cpp new file mode 100644 index 00000000..a40aff73 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.windows.cpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_multiple_wait_holder_base.hpp" +#include "os_multiple_wait_impl.hpp" +#include "os_timeout_helper.hpp" + +namespace ams::os::impl { + + Result MultiWaitWindowsImpl::WaitForMultipleObjectsImpl(s32 *out_index, s32 num, NativeHandle arr[], s32 array_size, DWORD wait_ms) { + /* Check that we can add our cancel handle to the wait. */ + AMS_ABORT_UNLESS(num + 1 <= array_size); + AMS_UNUSED(array_size); + + /* Add our cancel handle. */ + arr[num] = m_cancel_event; + + /* Wait. */ + auto result = ::WaitForMultipleObjects(num + 1, arr, FALSE, wait_ms); + if (result == WAIT_TIMEOUT) { + *out_index = MultiWaitImpl::WaitTimedOut; + } else { + const s32 index = result - WAIT_OBJECT_0; + AMS_ASSERT((0 <= index) && (index <= num)); + if (index == num) { + *out_index = MultiWaitImpl::WaitCancelled; + } else { + *out_index = index; + } + } + + R_SUCCEED(); + } + + Result MultiWaitWindowsImpl::TimedWaitAny(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num, TimeSpan ts) { + impl::TimeoutHelper timeout(ts); + + do { + s32 idx; + R_TRY(WaitForMultipleObjectsImpl(std::addressof(idx), num, arr, array_size, timeout.GetTimeLeftOnTarget())); + if (idx != MultiWaitImpl::WaitTimedOut) { + *out_index = idx; + R_SUCCEED(); + } + } while (!timeout.TimedOut()); + + *out_index = MultiWaitImpl::WaitTimedOut; + R_SUCCEED(); + } + + Result MultiWaitWindowsImpl::ReplyAndReceiveImpl(s32 *out_index, s32 num, NativeHandle arr[], s32 array_size, s64 ns, NativeHandle reply_target) { + AMS_UNUSED(out_index, num, arr, array_size, ns, reply_target); + R_ABORT_UNLESS(os::ResultNotImplemented()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.windows.hpp new file mode 100644 index 00000000..5e4200c2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_multiple_wait_target_impl.os.windows.hpp @@ -0,0 +1,75 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_thread_manager.hpp" + +namespace ams::os::impl { + + class MultiWaitWindowsImpl { + public: + static constexpr size_t MaximumHandleCount = static_cast<size_t>(MAXIMUM_WAIT_OBJECTS); + private: + os::NativeHandle m_cancel_event; + private: + Result WaitForMultipleObjectsImpl(s32 *out_index, s32 num, NativeHandle arr[], s32 array_size, DWORD wait_ms); + Result ReplyAndReceiveImpl(s32 *out_index, s32 num, NativeHandle arr[], s32 array_size, s64 ns, NativeHandle reply_target); + public: + MultiWaitWindowsImpl() { + m_cancel_event = ::CreateEvent(nullptr, false, false, nullptr); + AMS_ASSERT(m_cancel_event != os::InvalidNativeHandle); + } + + ~MultiWaitWindowsImpl() { + const auto ret = ::CloseHandle(m_cancel_event); + AMS_ASSERT(ret != 0); + AMS_UNUSED(ret); + } + + void CancelWait() { + ::SetEvent(m_cancel_event); + } + + Result WaitAny(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num) { + R_RETURN(this->WaitForMultipleObjectsImpl(out_index, num, arr, array_size, INFINITE)); + } + + Result TryWaitAny(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num) { + R_RETURN(this->WaitForMultipleObjectsImpl(out_index, num, arr, array_size, 0)); + } + + Result TimedWaitAny(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num, TimeSpan ts); + + Result ReplyAndReceive(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num, NativeHandle reply_target) { + R_RETURN(this->ReplyAndReceiveImpl(out_index, num, arr, array_size, std::numeric_limits<s64>::max(), reply_target)); + } + + Result TimedReplyAndReceive(s32 *out_index, NativeHandle arr[], s32 array_size, s32 num, NativeHandle reply_target, TimeSpan ts) { + R_RETURN(this->ReplyAndReceiveImpl(out_index, num, arr, array_size, ts.GetNanoSeconds(), reply_target)); + } + + void SetCurrentThreadHandleForCancelWait() { + /* ... */ + } + + void ClearCurrentThreadHandleForCancelWait() { + /* ... */ + } + }; + + using MultiWaitTargetImpl = MultiWaitWindowsImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_mutex_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_mutex_impl.hpp new file mode 100644 index 00000000..5557d39f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_mutex_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + void PushAndCheckLockLevel(const MutexType *mutex); + void PopAndCheckLockLevel(const MutexType *mutex); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_native_handle_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_native_handle_impl.hpp new file mode 100644 index 00000000..bb5fc5c2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_native_handle_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "os_native_handle_impl.os.horizon.hpp" +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include "os_native_handle_impl.os.windows.hpp" +#elif defined(ATMOSPHERE_OS_LINUX) + #include "os_native_handle_impl.os.linux.hpp" +#elif defined(ATMOSPHERE_OS_MACOS) + #include "os_native_handle_impl.os.macos.hpp" +#else + #error "Unknown OS for ams::os::NativeHandleImpl" +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_native_handle_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_native_handle_impl.os.horizon.hpp new file mode 100644 index 00000000..5eacbd88 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_native_handle_impl.os.horizon.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class NativeHandleHorizonImpl { + public: + static ALWAYS_INLINE void Close(NativeHandle handle) { + R_ABORT_UNLESS(svc::CloseHandle(handle)); + } + }; + + using NativeHandleImpl = NativeHandleHorizonImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_native_handle_impl.os.linux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_native_handle_impl.os.linux.hpp new file mode 100644 index 00000000..8cc4e334 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_native_handle_impl.os.linux.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include <unistd.h> + +namespace ams::os::impl { + + class NativeHandleLinuxImpl { + public: + static ALWAYS_INLINE void Close(NativeHandle handle) { + s32 ret; + do { + ret = ::close(handle); + } while (ret < 0 && errno == EINTR); + + AMS_ASSERT(ret == 0); + } + }; + + using NativeHandleImpl = NativeHandleLinuxImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_native_handle_impl.os.macos.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_native_handle_impl.os.macos.hpp new file mode 100644 index 00000000..b0ca8b54 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_native_handle_impl.os.macos.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include <unistd.h> + +namespace ams::os::impl { + + class NativeHandleMacosImpl { + public: + static ALWAYS_INLINE void Close(NativeHandle handle) { + s32 ret; + do { + ret = ::close(handle); + } while (ret < 0 && errno == EINTR); + + AMS_ASSERT(ret == 0); + } + }; + + using NativeHandleImpl = NativeHandleMacosImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_native_handle_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_native_handle_impl.os.windows.hpp new file mode 100644 index 00000000..6451a4ca --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_native_handle_impl.os.windows.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include <stratosphere/windows.hpp> + +namespace ams::os::impl { + + class NativeHandleWindowsImpl { + public: + static ALWAYS_INLINE void Close(NativeHandle handle) { + const auto ret = ::CloseHandle(handle); + AMS_ASSERT(ret != 0); + AMS_UNUSED(ret); + } + }; + + using NativeHandleImpl = NativeHandleWindowsImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_code_memory_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_code_memory_impl.hpp new file mode 100644 index 00000000..e48933de --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_code_memory_impl.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class ProcessCodeMemoryImpl { + public: + static Result Map(u64 *out, NativeHandle handle, const ProcessMemoryRegion *regions, size_t num_regions, AddressSpaceGenerateRandomFunction generate_random); + static Result Unmap(NativeHandle handle, u64 process_code_address, const ProcessMemoryRegion *regions, size_t num_regions); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_code_memory_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_code_memory_impl.os.horizon.cpp new file mode 100644 index 00000000..adb77c3d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_code_memory_impl.os.horizon.cpp @@ -0,0 +1,142 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_process_code_memory_impl.hpp" +#include "os_aslr_space_manager.hpp" + +namespace ams::os::impl { + + namespace { + + class ProcessAddressSpaceAllocator final : public AddressSpaceAllocatorBase<u64, u64> { + private: + using Base = AddressSpaceAllocatorBase<u64, u64>; + private: + NativeHandle m_handle; + public: + ProcessAddressSpaceAllocator(u64 start_address, u64 end_address, SizeType guard_size, const AddressSpaceAllocatorForbiddenRegion *forbidden_regions, size_t num_forbidden_regions, NativeHandle handle) : Base(start_address, end_address, guard_size, forbidden_regions, num_forbidden_regions), m_handle(handle) { + /* ... */ + } + public: + virtual bool CheckFreeSpace(AddressType address, SizeType size) override { + /* Query the memory. */ + svc::MemoryInfo memory_info; + svc::PageInfo page_info; + R_ABORT_UNLESS(svc::QueryProcessMemory(std::addressof(memory_info), std::addressof(page_info), m_handle, address)); + + return memory_info.state == svc::MemoryState_Free && address + size <= memory_info.base_address + memory_info.size; + } + }; + + using ProcessAslrSpaceManager = AslrSpaceManagerTemplate<ProcessAddressSpaceAllocator, AslrSpaceManagerImpl>; + + size_t GetTotalProcessMemoryRegionSize(const ProcessMemoryRegion *regions, size_t num_regions) { + size_t total = 0; + + for (size_t i = 0; i < num_regions; ++i) { + total += regions[i].size; + } + + return total; + } + + } + + Result ProcessCodeMemoryImpl::Map(u64 *out, NativeHandle handle, const ProcessMemoryRegion *regions, size_t num_regions, AddressSpaceGenerateRandomFunction generate_random) { + /* Get the total process memory region size. */ + const size_t total_size = GetTotalProcessMemoryRegionSize(regions, num_regions); + + /* Create an aslr space manager for the process. */ + auto process_aslr_space_manager = ProcessAslrSpaceManager(handle); + + /* Map at a random address. */ + u64 mapped_address; + R_TRY(process_aslr_space_manager.MapAtRandomAddressWithCustomRandomGenerator(std::addressof(mapped_address), + [handle, regions, num_regions](u64 map_address, u64 map_size) -> Result { + AMS_UNUSED(map_size); + + /* Map the regions in order. */ + u64 mapped_size = 0; + for (size_t i = 0; i < num_regions; ++i) { + /* If we fail, unmap up to where we've mapped. */ + ON_RESULT_FAILURE { R_ABORT_UNLESS(ProcessCodeMemoryImpl::Unmap(handle, map_address, regions, i)); }; + + /* Map the current region. */ + R_TRY_CATCH(svc::MapProcessCodeMemory(handle, map_address + mapped_size, regions[i].address, regions[i].size)) { + R_CONVERT(svc::ResultOutOfResource, os::ResultOutOfResource()) + R_CATCH(svc::ResultInvalidCurrentMemory) { + /* Check if the process memory is invalid. */ + const u64 last_address = regions[i].address + regions[i].size - 1; + u64 cur_address = regions[i].address; + while (cur_address <= last_address) { + svc::MemoryInfo memory_info; + svc::PageInfo page_info; + R_ABORT_UNLESS(svc::QueryProcessMemory(std::addressof(memory_info), std::addressof(page_info), handle, cur_address)); + + R_UNLESS(memory_info.state == svc::MemoryState_Normal, os::ResultInvalidProcessMemory()); + R_UNLESS(memory_info.permission == svc::MemoryPermission_ReadWrite, os::ResultInvalidProcessMemory()); + R_UNLESS(memory_info.attribute == static_cast<svc::MemoryAttribute>(0), os::ResultInvalidProcessMemory()); + + cur_address = memory_info.base_address + memory_info.size; + } + + R_THROW(os::ResultInvalidCurrentMemoryState()); + } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + mapped_size += regions[i].size; + } + + R_SUCCEED(); + }, + [handle, regions, num_regions](u64 map_address, u64 map_size) -> void { + AMS_UNUSED(map_size); + R_ABORT_UNLESS(ProcessCodeMemoryImpl::Unmap(handle, map_address, regions, num_regions)); + }, + total_size, + regions[0].address, /* NOTE: This seems like a Nintendo bug, if the caller passed no regions. */ + generate_random + )); + + /* Set the output address. */ + *out = mapped_address; + R_SUCCEED(); + } + + Result ProcessCodeMemoryImpl::Unmap(NativeHandle handle, u64 process_code_address, const ProcessMemoryRegion *regions, size_t num_regions) { + /* Get the total process memory region size. */ + const size_t total_size = GetTotalProcessMemoryRegionSize(regions, num_regions); + + /* Unmap each region in order. */ + size_t cur_offset = total_size; + for (size_t i = 0; i < num_regions; ++i) { + /* We want to unmap in reverse order. */ + const auto &cur_region = regions[num_regions - 1 - i]; + + /* Subtract to update the current offset. */ + cur_offset -= cur_region.size; + + /* Unmap. */ + R_TRY_CATCH(svc::UnmapProcessCodeMemory(handle, process_code_address + cur_offset, cur_region.address, cur_region.size)) { + R_CONVERT(svc::ResultOutOfResource, os::ResultOutOfResource()) + R_CONVERT(svc::ResultInvalidCurrentMemory, os::ResultInvalidCurrentMemoryState()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_handle_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_handle_impl.hpp new file mode 100644 index 00000000..c5417336 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_handle_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "os_process_handle_impl.os.horizon.hpp" +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include "os_process_handle_impl.os.windows.hpp" +#elif defined(ATMOSPHERE_OS_LINUX) + #include "os_process_handle_impl.os.linux.hpp" +#elif defined(ATMOSPHERE_OS_MACOS) + #include "os_process_handle_impl.os.macos.hpp" +#else + #error "Unknown OS for ams::os::ProcessHandleImpl" +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_handle_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_handle_impl.os.horizon.hpp new file mode 100644 index 00000000..9add0c2b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_handle_impl.os.horizon.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class ProcessHandleHorizonImpl { + public: + static consteval NativeHandle GetCurrentProcessHandle() { + return svc::PseudoHandle::CurrentProcess; + } + + static ALWAYS_INLINE Result GetProcessId(ProcessId *out, NativeHandle handle) { + R_RETURN(svc::GetProcessId(std::addressof(out->value), handle)); + } + + static ALWAYS_INLINE Result GetProgramId(ncm::ProgramId *out, NativeHandle handle) { + R_RETURN(svc::GetInfo(std::addressof(out->value), svc::InfoType_ProgramId, handle, 0)); + } + }; + + using ProcessHandleImpl = ProcessHandleHorizonImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_handle_impl.os.linux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_handle_impl.os.linux.hpp new file mode 100644 index 00000000..1192cb9d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_handle_impl.os.linux.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class ProcessHandleLinuxImpl { + public: + static consteval NativeHandle GetCurrentProcessHandle() { + /* TODO: PseudoHandle define? */ + return -2; + } + + static ALWAYS_INLINE Result GetProcessId(ProcessId *out, NativeHandle handle) { + AMS_UNUSED(out, handle); + AMS_ABORT("TODO"); + } + + static ALWAYS_INLINE Result GetProgramId(ncm::ProgramId *out, NativeHandle handle) { + AMS_UNUSED(out, handle); + AMS_ABORT("TODO"); + } + }; + + using ProcessHandleImpl = ProcessHandleLinuxImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_handle_impl.os.macos.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_handle_impl.os.macos.hpp new file mode 100644 index 00000000..4b2d958a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_handle_impl.os.macos.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class ProcessHandleMacosImpl { + public: + static consteval NativeHandle GetCurrentProcessHandle() { + /* TODO: PseudoHandle define? */ + return -2; + } + + static ALWAYS_INLINE Result GetProcessId(ProcessId *out, NativeHandle handle) { + AMS_UNUSED(out, handle); + AMS_ABORT("TODO"); + } + + static ALWAYS_INLINE Result GetProgramId(ncm::ProgramId *out, NativeHandle handle) { + AMS_UNUSED(out, handle); + AMS_ABORT("TODO"); + } + }; + + using ProcessHandleImpl = ProcessHandleMacosImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_handle_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_handle_impl.os.windows.hpp new file mode 100644 index 00000000..78fa4047 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_handle_impl.os.windows.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include <stratosphere/windows.hpp> + +namespace ams::os::impl { + + class ProcessHandleWindowsImpl { + public: + static ALWAYS_INLINE NativeHandle GetCurrentProcessHandle() { + return ::GetCurrentProcess(); + } + + static ALWAYS_INLINE Result GetProcessId(ProcessId *out, NativeHandle handle) { + AMS_UNUSED(out, handle); + AMS_ABORT("TODO"); + } + + static ALWAYS_INLINE Result GetProgramId(ncm::ProgramId *out, NativeHandle handle) { + AMS_UNUSED(out, handle); + AMS_ABORT("TODO"); + } + }; + + using ProcessHandleImpl = ProcessHandleWindowsImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_memory_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_memory_impl.hpp new file mode 100644 index 00000000..e3d4b263 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_memory_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class ProcessMemoryImpl { + public: + static Result Map(void **out, NativeHandle handle, u64 process_address, size_t size, AddressSpaceGenerateRandomFunction generate_random); + static void Unmap(void *mapped_memory, NativeHandle handle, u64 process_address, size_t size); + + static Result SetMemoryPermission(NativeHandle handle, u64 process_address, u64 size, MemoryPermission perm); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_memory_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_memory_impl.os.horizon.cpp new file mode 100644 index 00000000..686cc699 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_process_memory_impl.os.horizon.cpp @@ -0,0 +1,76 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_process_memory_impl.hpp" +#include "os_aslr_space_manager.hpp" + +namespace ams::os::impl { + + namespace { + + svc::MemoryPermission ConvertToSvcMemoryPermission(os::MemoryPermission perm) { + switch (perm) { + case os::MemoryPermission_None: return svc::MemoryPermission_None; + case os::MemoryPermission_ReadOnly: return svc::MemoryPermission_Read; + case os::MemoryPermission_ReadWrite: return svc::MemoryPermission_ReadWrite; + case os::MemoryPermission_ReadExecute: return svc::MemoryPermission_ReadExecute; + case os::MemoryPermission_ExecuteOnly: return svc::MemoryPermission_Execute; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + } + + Result ProcessMemoryImpl::Map(void **out, NativeHandle handle, u64 process_address, size_t size, AddressSpaceGenerateRandomFunction generate_random) { + /* Map at a random address. */ + uintptr_t mapped_address; + R_TRY(impl::GetAslrSpaceManager().MapAtRandomAddressWithCustomRandomGenerator(std::addressof(mapped_address), + [handle, process_address](uintptr_t map_address, size_t map_size) -> Result { + R_TRY_CATCH(svc::MapProcessMemory(map_address, handle, process_address, map_size)) { + R_CONVERT(svc::ResultOutOfResource, os::ResultOutOfResource()) + R_CONVERT(svc::ResultInvalidCurrentMemory, os::ResultInvalidCurrentMemoryState()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + R_SUCCEED(); + }, + [handle, process_address](uintptr_t map_address, size_t map_size) -> void { + return ProcessMemoryImpl::Unmap(reinterpret_cast<void *>(map_address), handle, process_address, map_size); + }, + size, + process_address, + generate_random + )); + + /* Return the address we mapped at. */ + *out = reinterpret_cast<void *>(mapped_address); + R_SUCCEED(); + } + + void ProcessMemoryImpl::Unmap(void *mapped_memory, NativeHandle handle, u64 process_address, size_t size) { + R_ABORT_UNLESS(svc::UnmapProcessMemory(reinterpret_cast<uintptr_t>(mapped_memory), handle, process_address, size)); + } + + Result ProcessMemoryImpl::SetMemoryPermission(NativeHandle handle, u64 process_address, u64 size, MemoryPermission perm) { + /* Set the process memory permission. */ + R_TRY_CATCH(svc::SetProcessMemoryPermission(handle, process_address, size, ConvertToSvcMemoryPermission(perm))) { + R_CONVERT(svc::ResultOutOfResource, os::ResultOutOfResource()) + R_CONVERT(svc::ResultInvalidCurrentMemory, os::ResultInvalidCurrentMemoryState()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_random_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_random_impl.hpp new file mode 100644 index 00000000..cbc28d56 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_random_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + void InitializeRandomImpl(util::TinyMT *mt); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_random_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_random_impl.os.horizon.cpp new file mode 100644 index 00000000..dffe3311 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_random_impl.os.horizon.cpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::os::impl { + + void InitializeRandomImpl(util::TinyMT *mt) { + /* Retrieve entropy from kernel. */ + u32 seed[4]; + static_assert(util::size(seed) == util::TinyMT::NumStateWords); + + /* Nintendo does not check the result of these invocations, but we will for safety. */ + /* Nintendo uses entropy values 0, 1 to seed the public TinyMT random, and values */ + /* 2, 3 to seed os::detail::RngManager's private TinyMT random. */ + R_ABORT_UNLESS(svc::GetInfo(reinterpret_cast<u64 *>(seed + 0), svc::InfoType_RandomEntropy, svc::InvalidHandle, 0)); + R_ABORT_UNLESS(svc::GetInfo(reinterpret_cast<u64 *>(seed + 2), svc::InfoType_RandomEntropy, svc::InvalidHandle, 1)); + + mt->Initialize(seed, util::size(seed)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_random_impl.os.linux.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_random_impl.os.linux.cpp new file mode 100644 index 00000000..9923f918 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_random_impl.os.linux.cpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::os::impl { + + void InitializeRandomImpl(util::TinyMT *mt) { + /* Initialize twister based on system tick. */ + mt->Initialize(os::GetSystemTick().GetInt64Value()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_random_impl.os.macos.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_random_impl.os.macos.cpp new file mode 100644 index 00000000..9923f918 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_random_impl.os.macos.cpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::os::impl { + + void InitializeRandomImpl(util::TinyMT *mt) { + /* Initialize twister based on system tick. */ + mt->Initialize(os::GetSystemTick().GetInt64Value()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_random_impl.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_random_impl.os.windows.cpp new file mode 100644 index 00000000..9923f918 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_random_impl.os.windows.cpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::os::impl { + + void InitializeRandomImpl(util::TinyMT *mt) { + /* Initialize twister based on system tick. */ + mt->Initialize(os::GetSystemTick().GetInt64Value()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_resource_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_resource_manager.cpp new file mode 100644 index 00000000..3f637ad5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_resource_manager.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_resource_manager.hpp" + +namespace ams::os::impl { + + constinit util::TypedStorage<OsResourceManager> ResourceManagerHolder::s_resource_manager_storage = {}; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_resource_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_resource_manager.hpp new file mode 100644 index 00000000..d717a37c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_resource_manager.hpp @@ -0,0 +1,75 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_rng_manager_impl.hpp" +#include "os_thread_manager_types.hpp" +#include "os_stack_guard_manager_types.hpp" +#include "os_tick_manager_impl.hpp" +#include "os_aslr_space_manager_types.hpp" +#include "os_tls_manager_types.hpp" +#include "os_giant_lock_types.hpp" +#include "os_memory_heap_manager_types.hpp" +#include "os_vamm_manager_types.hpp" + +namespace ams::os::impl { + + class OsResourceManager { + private: + RngManager m_rng_manager{}; + AslrSpaceManager m_aslr_space_manager{}; + StackGuardManager m_stack_guard_manager; + ThreadManager m_thread_manager{}; + //TlsManager m_tls_manager{}; + TickManager m_tick_manager{}; + MemoryHeapManager m_memory_heap_manager; + VammManager m_vamm_manager; + GiantLock m_giant_lock{}; + public: + OsResourceManager() = default; + + constexpr ALWAYS_INLINE RngManager &GetRngManager() { return m_rng_manager; } + constexpr ALWAYS_INLINE AslrSpaceManager &GetAslrSpaceManager() { return m_aslr_space_manager; } + constexpr ALWAYS_INLINE ThreadManager &GetThreadManager() { return m_thread_manager; } + constexpr ALWAYS_INLINE StackGuardManager &GetStackGuardManager() { return m_stack_guard_manager; } + //constexpr ALWAYS_INLINE TlsManager &GetTlsManager() { return m_tls_manager; } + constexpr ALWAYS_INLINE TickManager &GetTickManager() { return m_tick_manager; } + constexpr ALWAYS_INLINE MemoryHeapManager &GetMemoryHeapManager() { return m_memory_heap_manager; } + constexpr ALWAYS_INLINE VammManager &GetVammManager() { return m_vamm_manager; } + constexpr ALWAYS_INLINE GiantLock &GetGiantLock() { return m_giant_lock; } + }; + + class ResourceManagerHolder { + private: + static constinit util::TypedStorage<OsResourceManager> s_resource_manager_storage; + private: + constexpr ResourceManagerHolder() { /* ... */ } + public: + static ALWAYS_INLINE void InitializeResourceManagerInstance() { + /* Construct the resource manager instance. */ + util::ConstructAt(s_resource_manager_storage); + } + + static ALWAYS_INLINE OsResourceManager &GetResourceManagerInstance() { + return GetReference(s_resource_manager_storage); + } + }; + + ALWAYS_INLINE OsResourceManager &GetResourceManager() { + return ResourceManagerHolder::GetResourceManagerInstance(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager.hpp new file mode 100644 index 00000000..e0c1ea6e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_resource_manager.hpp" + +namespace ams::os::impl { + + ALWAYS_INLINE RngManager &GetRngManager() { + return GetResourceManager().GetRngManager(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager_impl.cpp new file mode 100644 index 00000000..79f105ac --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager_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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_rng_manager_impl.hpp" + +namespace ams::os::impl { + + u64 RngManager::GenerateRandomU64() { + std::scoped_lock lk(m_lock); + + if (AMS_UNLIKELY(!m_initialized)) { + this->Initialize(); + } + + return m_mt.GenerateRandomU64(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager_impl.hpp new file mode 100644 index 00000000..f8406023 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager_impl.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class RngManager { + private: + util::TinyMT m_mt; + os::SdkMutex m_lock; + bool m_initialized; + private: + void Initialize(); + public: + RngManager() : m_mt(), m_lock(), m_initialized() { /* ... */ } + public: + u64 GenerateRandomU64(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager_impl.os.horizon.cpp new file mode 100644 index 00000000..51ce0430 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager_impl.os.horizon.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_rng_manager_impl.hpp" + +namespace ams::os::impl { + + void RngManager::Initialize() { + /* Retrieve entropy from kernel. */ + u32 seed[4]; + static_assert(util::size(seed) == util::TinyMT::NumStateWords); + + /* Nintendo does not check the result of these invocations, but we will for safety. */ + /* Nintendo uses entropy values 0, 1 to seed the public TinyMT random, and values */ + /* 2, 3 to seed os::detail::RngManager's private TinyMT random. */ + R_ABORT_UNLESS(svc::GetInfo(reinterpret_cast<u64 *>(seed + 0), svc::InfoType_RandomEntropy, svc::InvalidHandle, 2)); + R_ABORT_UNLESS(svc::GetInfo(reinterpret_cast<u64 *>(seed + 2), svc::InfoType_RandomEntropy, svc::InvalidHandle, 3)); + + m_mt.Initialize(seed, util::size(seed)); + + /* Note that we've initialized. */ + m_initialized = true; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager_impl.os.linux.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager_impl.os.linux.cpp new file mode 100644 index 00000000..91094ec9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager_impl.os.linux.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_rng_manager_impl.hpp" + +namespace ams::os::impl { + + void RngManager::Initialize() { + /* Initialize twister based on system tick. */ + m_mt.Initialize(os::GetSystemTick().GetInt64Value()); + + /* Note that we've initialized. */ + m_initialized = true; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager_impl.os.macos.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager_impl.os.macos.cpp new file mode 100644 index 00000000..91094ec9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager_impl.os.macos.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_rng_manager_impl.hpp" + +namespace ams::os::impl { + + void RngManager::Initialize() { + /* Initialize twister based on system tick. */ + m_mt.Initialize(os::GetSystemTick().GetInt64Value()); + + /* Note that we've initialized. */ + m_initialized = true; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager_impl.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager_impl.os.windows.cpp new file mode 100644 index 00000000..91094ec9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rng_manager_impl.os.windows.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_rng_manager_impl.hpp" + +namespace ams::os::impl { + + void RngManager::Initialize() { + /* Initialize twister based on system tick. */ + m_mt.Initialize(os::GetSystemTick().GetInt64Value()); + + /* Note that we've initialized. */ + m_initialized = true; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rw_lock_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rw_lock_impl.hpp new file mode 100644 index 00000000..7fa63e0e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rw_lock_impl.hpp @@ -0,0 +1,185 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "os_rw_lock_target_impl.os.horizon.hpp" +#else + #include "os_rw_lock_target_impl.os.generic.hpp" +#endif + +namespace ams::os::impl { + + static_assert(alignof(ReaderWriterLockType) == sizeof(u64) || alignof(ReaderWriterLockType) == sizeof(u32)); + constexpr inline bool IsReaderWriterLockGuaranteedAlignment = alignof(ReaderWriterLockType) == sizeof(u64); + + struct ReaderWriterLockCounter { + using ReadLockCount = util::BitPack32::Field< 0, BITSIZEOF(u16) - 1, u32>; + using WriteLocked = util::BitPack32::Field< ReadLockCount::Next, 1, u32>; + using ReadLockWaiterCount = util::BitPack32::Field< WriteLocked::Next, BITSIZEOF(u8), u32>; + using WriteLockWaiterCount = util::BitPack32::Field<ReadLockWaiterCount::Next, BITSIZEOF(u8), u32>; + }; + static_assert(ReaderWriterLockCounter::WriteLockWaiterCount::Next == BITSIZEOF(u32)); + + ALWAYS_INLINE void ClearReadLockCount(ReaderWriterLockType::LockCount &lc) { + lc.counter.Set<ReaderWriterLockCounter::ReadLockCount>(0); + } + + ALWAYS_INLINE void ClearWriteLocked(ReaderWriterLockType::LockCount &lc) { + lc.counter.Set<ReaderWriterLockCounter::WriteLocked>(0); + } + + ALWAYS_INLINE void ClearReadLockWaiterCount(ReaderWriterLockType::LockCount &lc) { + lc.counter.Set<ReaderWriterLockCounter::ReadLockWaiterCount>(0); + } + + ALWAYS_INLINE void ClearWriteLockWaiterCount(ReaderWriterLockType::LockCount &lc) { + lc.counter.Set<ReaderWriterLockCounter::WriteLockWaiterCount>(0); + } + + ALWAYS_INLINE void ClearWriteLockCount(ReaderWriterLockType *rw_lock) { + if constexpr (IsReaderWriterLockGuaranteedAlignment) { + rw_lock->lock_count.aligned.write_lock_count = 0; + } else { + if (reinterpret_cast<uintptr_t>(std::addressof(rw_lock->lock_count)) & sizeof(u32)) { + rw_lock->lock_count.not_aligned.write_lock_count = 0; + } else { + rw_lock->lock_count.aligned.write_lock_count = 0; + } + } + } + + ALWAYS_INLINE ReaderWriterLockType::LockCount &GetLockCount(ReaderWriterLockType *rw_lock) { + if constexpr (IsReaderWriterLockGuaranteedAlignment) { + return rw_lock->lock_count.aligned.c; + } else { + if (reinterpret_cast<uintptr_t>(std::addressof(rw_lock->lock_count)) & sizeof(u32)) { + return rw_lock->lock_count.not_aligned.c; + } else { + return rw_lock->lock_count.aligned.c; + } + } + } + + ALWAYS_INLINE const ReaderWriterLockType::LockCount &GetLockCount(const ReaderWriterLockType *rw_lock) { + if constexpr (IsReaderWriterLockGuaranteedAlignment) { + return rw_lock->lock_count.aligned.c; + } else { + if (reinterpret_cast<uintptr_t>(std::addressof(rw_lock->lock_count)) & sizeof(u32)) { + return rw_lock->lock_count.not_aligned.c; + } else { + return rw_lock->lock_count.aligned.c; + } + } + } + + ALWAYS_INLINE u32 GetReadLockCount(const ReaderWriterLockType::LockCount &lc) { + return lc.counter.Get<ReaderWriterLockCounter::ReadLockCount>(); + } + + ALWAYS_INLINE u32 GetWriteLocked(const ReaderWriterLockType::LockCount &lc) { + return lc.counter.Get<ReaderWriterLockCounter::WriteLocked>(); + } + + ALWAYS_INLINE u32 GetReadLockWaiterCount(const ReaderWriterLockType::LockCount &lc) { + return lc.counter.Get<ReaderWriterLockCounter::ReadLockWaiterCount>(); + } + + ALWAYS_INLINE u32 GetWriteLockWaiterCount(const ReaderWriterLockType::LockCount &lc) { + return lc.counter.Get<ReaderWriterLockCounter::WriteLockWaiterCount>(); + } + + ALWAYS_INLINE u32 &GetWriteLockCount(ReaderWriterLockType &rw_lock) { + if constexpr (IsReaderWriterLockGuaranteedAlignment) { + return rw_lock.lock_count.aligned.write_lock_count; + } else { + if (reinterpret_cast<uintptr_t>(std::addressof(rw_lock.lock_count)) & sizeof(u32)) { + return rw_lock.lock_count.not_aligned.write_lock_count; + } else { + return rw_lock.lock_count.aligned.write_lock_count; + } + } + } + + ALWAYS_INLINE const u32 &GetWriteLockCount(const ReaderWriterLockType &rw_lock) { + if constexpr (IsReaderWriterLockGuaranteedAlignment) { + return rw_lock.lock_count.aligned.write_lock_count; + } else { + if (reinterpret_cast<uintptr_t>(std::addressof(rw_lock.lock_count)) & sizeof(u32)) { + return rw_lock.lock_count.not_aligned.write_lock_count; + } else { + return rw_lock.lock_count.aligned.write_lock_count; + } + } + } + + ALWAYS_INLINE void IncReadLockCount(ReaderWriterLockType::LockCount &lc) { + const u32 read_lock_count = lc.counter.Get<ReaderWriterLockCounter::ReadLockCount>(); + AMS_ASSERT(read_lock_count < ReaderWriterLockCountMax); + lc.counter.Set<ReaderWriterLockCounter::ReadLockCount>(read_lock_count + 1); + } + + ALWAYS_INLINE void DecReadLockCount(ReaderWriterLockType::LockCount &lc) { + const u32 read_lock_count = lc.counter.Get<ReaderWriterLockCounter::ReadLockCount>(); + AMS_ASSERT(read_lock_count > 0); + lc.counter.Set<ReaderWriterLockCounter::ReadLockCount>(read_lock_count - 1); + } + + ALWAYS_INLINE void IncReadLockWaiterCount(ReaderWriterLockType::LockCount &lc) { + const u32 read_lock_waiter_count = lc.counter.Get<ReaderWriterLockCounter::ReadLockWaiterCount>(); + AMS_ASSERT(read_lock_waiter_count < ReaderWriterLockWaiterCountMax); + lc.counter.Set<ReaderWriterLockCounter::ReadLockWaiterCount>(read_lock_waiter_count + 1); + } + + ALWAYS_INLINE void DecReadLockWaiterCount(ReaderWriterLockType::LockCount &lc) { + const u32 read_lock_waiter_count = lc.counter.Get<ReaderWriterLockCounter::ReadLockWaiterCount>(); + AMS_ASSERT(read_lock_waiter_count > 0); + lc.counter.Set<ReaderWriterLockCounter::ReadLockWaiterCount>(read_lock_waiter_count - 1); + } + + ALWAYS_INLINE void IncWriteLockWaiterCount(ReaderWriterLockType::LockCount &lc) { + const u32 write_lock_waiter_count = lc.counter.Get<ReaderWriterLockCounter::WriteLockWaiterCount>(); + AMS_ASSERT(write_lock_waiter_count < ReaderWriterLockWaiterCountMax); + lc.counter.Set<ReaderWriterLockCounter::WriteLockWaiterCount>(write_lock_waiter_count + 1); + } + + ALWAYS_INLINE void DecWriteLockWaiterCount(ReaderWriterLockType::LockCount &lc) { + const u32 write_lock_waiter_count = lc.counter.Get<ReaderWriterLockCounter::WriteLockWaiterCount>(); + AMS_ASSERT(write_lock_waiter_count > 0); + lc.counter.Set<ReaderWriterLockCounter::WriteLockWaiterCount>(write_lock_waiter_count - 1); + } + + + ALWAYS_INLINE void IncWriteLockCount(ReaderWriterLockType &rw_lock) { + u32 &write_lock_count = GetWriteLockCount(rw_lock); + AMS_ASSERT(write_lock_count < ReaderWriterLockCountMax); + ++write_lock_count; + } + + ALWAYS_INLINE void DecWriteLockCount(ReaderWriterLockType &rw_lock) { + u32 &write_lock_count = GetWriteLockCount(rw_lock); + AMS_ASSERT(write_lock_count > 0); + --write_lock_count; + } + + ALWAYS_INLINE void SetWriteLocked(ReaderWriterLockType::LockCount &lc) { + lc.counter.Set<ReaderWriterLockCounter::WriteLocked>(1); + } + + using ReaderWriterLockImpl = ReaderWriterLockTargetImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rw_lock_target_impl.os.generic.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rw_lock_target_impl.os.generic.cpp new file mode 100644 index 00000000..e76dd6b9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rw_lock_target_impl.os.generic.cpp @@ -0,0 +1,238 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_rw_lock_impl.hpp" +#include "os_thread_manager.hpp" + +namespace ams::os::impl { + + void ReaderWriterLockHorizonImpl::AcquireReadLock(os::ReaderWriterLockType *rw_lock) { + /* Acquire exclusive access. */ + std::scoped_lock lk(util::GetReference(GetLockCount(rw_lock).cs_storage)); + + /* Get the lock into a state where we can safely increment the read lock count. */ + if (rw_lock->owner_thread == impl::GetCurrentThread()) { + /* If we're the owner thread, we should hold the write lock. */ + AMS_ASSERT(GetWriteLocked(GetLockCount(rw_lock)) == 1); + } else { + /* Wait until we're not write locked, and there are no write lock waiters. */ + while (true) { + /* Check if we're write locked. */ + if (GetWriteLocked(GetLockCount(rw_lock)) != 1) { + /* We're not, so check if we have write lock waiters. */ + if (GetWriteLockWaiterCount(GetLockCount(rw_lock)) == 0) { + break; + } + } + + /* We're write locked, or we have write lock waiters. */ + IncReadLockWaiterCount(GetLockCount(rw_lock)); + util::GetReference(rw_lock->cv_read_lock._storage).Wait(util::GetPointer(GetLockCount(rw_lock).cs_storage)); + DecReadLockWaiterCount(GetLockCount(rw_lock)); + } + + /* Verify we're in the desired state. */ + AMS_ASSERT(GetWriteLockCount(*rw_lock) == 0); + AMS_ASSERT(rw_lock->owner_thread == nullptr); + } + + /* Increment the read lock count. */ + IncReadLockCount(GetLockCount(rw_lock)); + } + + bool ReaderWriterLockHorizonImpl::TryAcquireReadLock(os::ReaderWriterLockType *rw_lock) { + /* Acquire exclusive access. */ + std::scoped_lock lk(util::GetReference(GetLockCount(rw_lock).cs_storage)); + + /* Try to get the lock into a state where we can safely increment the read lock count. */ + if (rw_lock->owner_thread == impl::GetCurrentThread()) { + /* If we're the owner thread, we should hold the write lock. */ + AMS_ASSERT(GetWriteLocked(GetLockCount(rw_lock)) == 1); + } else { + /* We can only read lock if we're not write locked, and there are no write lock waiters. */ + if (GetWriteLocked(GetLockCount(rw_lock)) == 1) { + return false; + } + if (GetWriteLockWaiterCount(GetLockCount(rw_lock)) != 0) { + return false; + } + + /* Verify we're in the desired state. */ + AMS_ASSERT(GetWriteLockCount(*rw_lock) == 0); + AMS_ASSERT(rw_lock->owner_thread == nullptr); + } + + /* Increment the read lock count. */ + IncReadLockCount(GetLockCount(rw_lock)); + return true; + } + + void ReaderWriterLockHorizonImpl::ReleaseReadLock(os::ReaderWriterLockType *rw_lock) { + /* Acquire exclusive access. */ + std::scoped_lock lk(util::GetReference(GetLockCount(rw_lock).cs_storage)); + + /* Sanity check that we hold the read lock. */ + AMS_ASSERT(GetReadLockCount(GetLockCount(rw_lock)) > 0); + + /* Decrement the read lock count. */ + DecReadLockCount(GetLockCount(rw_lock)); + + /* If we're the owner of the write lock, we may need to check if this causes a full release/signal to waiters. */ + if (rw_lock->owner_thread == impl::GetCurrentThread()) { + /* Sanity check that we're write locked. */ + AMS_ASSERT(GetWriteLocked(GetLockCount(rw_lock)) == 1); + + /* If we've called ReleaseWriteLock and have no remaining read locks, we may need to signal/broadcast. */ + if (GetWriteLockCount(*rw_lock) == 0 && GetReadLockCount(GetLockCount(rw_lock)) == 0) { + /* Clear lock owner. */ + rw_lock->owner_thread = nullptr; + + /* Clear write locked. */ + ClearWriteLocked(GetLockCount(rw_lock)); + + /* If we have lock waiters, signal them. */ + if (GetWriteLockWaiterCount(GetLockCount(rw_lock)) != 0) { + util::GetReference(rw_lock->cv_write_lock._storage).Signal(); + } else if (GetReadLockWaiterCount(GetLockCount(rw_lock)) != 0) { + util::GetReference(rw_lock->cv_read_lock._storage).Broadcast(); + } + } + } else { + /* Sanity check that our read lock release was fine. */ + AMS_ASSERT(GetWriteLockCount(*rw_lock) == 0); + AMS_ASSERT(GetWriteLocked(GetLockCount(rw_lock)) == 0); + AMS_ASSERT(rw_lock->owner_thread == nullptr); + + /* We need to signal if there are no remaining read locks. */ + if (GetReadLockCount(GetLockCount(rw_lock)) == 0) { + if (GetWriteLockWaiterCount(GetLockCount(rw_lock)) != 0) { + util::GetReference(rw_lock->cv_write_lock._storage).Signal(); + } + } + } + } + + void ReaderWriterLockHorizonImpl::AcquireWriteLock(os::ReaderWriterLockType *rw_lock) { + /* Acquire exclusive access. */ + std::scoped_lock lk(util::GetReference(GetLockCount(rw_lock).cs_storage)); + + /* Get the lock into a state where we can safely increment the read lock count. */ + if (rw_lock->owner_thread == impl::GetCurrentThread()) { + /* If we're the owner thread, we should hold the write lock. */ + AMS_ASSERT(GetWriteLocked(GetLockCount(rw_lock)) == 1); + + /* Increment the write lock count. */ + IncWriteLockCount(*rw_lock); + } else { + /* Wait until we're not read locked and not write locked. */ + while (true) { + /* Check if we're read locked. */ + if (GetReadLockCount(GetLockCount(rw_lock)) == 0) { + /* We're not, so check if we're write locked. */ + if (GetWriteLocked(GetLockCount(rw_lock)) != 1) { + break; + } + } + + /* We're write locked or read locked. */ + IncWriteLockWaiterCount(GetLockCount(rw_lock)); + util::GetReference(rw_lock->cv_write_lock._storage).Wait(util::GetPointer(GetLockCount(rw_lock).cs_storage)); + DecWriteLockWaiterCount(GetLockCount(rw_lock)); + } + + /* Verify we're in the desired state. */ + AMS_ASSERT(GetWriteLockCount(*rw_lock) == 0); + AMS_ASSERT(rw_lock->owner_thread == nullptr); + + /* Increment the write lock count. */ + IncWriteLockCount(*rw_lock); + + /* Set write locked. */ + SetWriteLocked(GetLockCount(rw_lock)); + + /* Set ourselves as the owner. */ + rw_lock->owner_thread = impl::GetCurrentThread(); + } + } + + bool ReaderWriterLockHorizonImpl::TryAcquireWriteLock(os::ReaderWriterLockType *rw_lock) { + /* Acquire exclusive access. */ + std::scoped_lock lk(util::GetReference(GetLockCount(rw_lock).cs_storage)); + + /* Get the lock into a state where we can safely increment the read lock count. */ + if (rw_lock->owner_thread == impl::GetCurrentThread()) { + /* If we're the owner thread, we should hold the write lock. */ + AMS_ASSERT(GetWriteLocked(GetLockCount(rw_lock)) == 1); + + /* Increment the write lock count. */ + IncWriteLockCount(*rw_lock); + } else { + /* Check if we're read locked. */ + if (GetReadLockCount(GetLockCount(rw_lock)) != 0) { + return false; + } + /* We're not, so check if we're write locked. */ + if (GetWriteLocked(GetLockCount(rw_lock)) == 1) { + return false; + } + + /* Verify we're in the desired state. */ + AMS_ASSERT(GetWriteLockCount(*rw_lock) == 0); + AMS_ASSERT(rw_lock->owner_thread == nullptr); + + /* Increment the write lock count. */ + IncWriteLockCount(*rw_lock); + + /* Set write locked. */ + SetWriteLocked(GetLockCount(rw_lock)); + + /* Set ourselves as the owner. */ + rw_lock->owner_thread = impl::GetCurrentThread(); + } + + return true; + } + + void ReaderWriterLockHorizonImpl::ReleaseWriteLock(os::ReaderWriterLockType *rw_lock) { + /* Acquire exclusive access. */ + std::scoped_lock lk(util::GetReference(GetLockCount(rw_lock).cs_storage)); + + /* Sanity check our state. */ + AMS_ASSERT(GetWriteLockCount(*rw_lock) > 0); + AMS_ASSERT(GetWriteLocked(GetLockCount(rw_lock)) != 0); + AMS_ASSERT(rw_lock->owner_thread == impl::GetCurrentThread()); + + /* Decrement the write lock count. */ + DecWriteLockCount(*rw_lock); + + /* If we have no remaining write locks, we may need to signal/release. */ + if (GetWriteLockCount(*rw_lock) == 0 && GetReadLockCount(GetLockCount(rw_lock)) == 0) { + /* Clear lock owner. */ + rw_lock->owner_thread = nullptr; + + /* Clear write locked. */ + ClearWriteLocked(GetLockCount(rw_lock)); + + /* If we have lock waiters, signal them. */ + if (GetWriteLockWaiterCount(GetLockCount(rw_lock)) != 0) { + util::GetReference(rw_lock->cv_write_lock._storage).Signal(); + } else if (GetReadLockWaiterCount(GetLockCount(rw_lock)) != 0) { + util::GetReference(rw_lock->cv_read_lock._storage).Broadcast(); + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rw_lock_target_impl.os.generic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rw_lock_target_impl.os.generic.hpp new file mode 100644 index 00000000..d71e6b32 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rw_lock_target_impl.os.generic.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class ReaderWriterLockHorizonImpl { + private: + using LockCount = os::ReaderWriterLockType::LockCount; + public: + static void AcquireReadLock(os::ReaderWriterLockType *rw_lock); + static bool TryAcquireReadLock(os::ReaderWriterLockType *rw_lock); + static void ReleaseReadLock(os::ReaderWriterLockType *rw_lock); + + static void AcquireWriteLock(os::ReaderWriterLockType *rw_lock); + static bool TryAcquireWriteLock(os::ReaderWriterLockType *rw_lock); + static void ReleaseWriteLock(os::ReaderWriterLockType *rw_lock); + }; + + using ReaderWriterLockTargetImpl = ReaderWriterLockHorizonImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rw_lock_target_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rw_lock_target_impl.os.horizon.cpp new file mode 100644 index 00000000..a90ac69e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rw_lock_target_impl.os.horizon.cpp @@ -0,0 +1,347 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_rw_lock_impl.hpp" +#include "os_thread_manager.hpp" + +namespace ams::os::impl { + + #define ATOMIC_COMPARE_EXCHANGE_LOCK_COUNT(dst_ref, expected_ref, desired_ref, success, fail) \ + (__atomic_compare_exchange(reinterpret_cast<u64 *>(std::addressof(dst_ref)), reinterpret_cast<u64 *>(std::addressof(expected_ref)), reinterpret_cast<u64 *>(std::addressof(desired_ref)), true, success, fail)) + + + void ReaderWriterLockHorizonImpl::AcquireReadLockWriteLocked(os::ReaderWriterLockType *rw_lock) { + AMS_ASSERT(GetWriteLocked(GetLockCount(rw_lock)) == 1); + AMS_ASSERT((GetThreadHandle(GetLockCount(rw_lock)) | svc::HandleWaitMask) == (impl::GetCurrentThreadHandle() | svc::HandleWaitMask)); + alignas(alignof(u64)) LockCount expected = GetLockCount(rw_lock); + alignas(alignof(u64)) LockCount lock_count; + do { + lock_count = expected; + IncReadLockCount(lock_count); + } while (!ATOMIC_COMPARE_EXCHANGE_LOCK_COUNT(GetLockCount(rw_lock), expected, lock_count, __ATOMIC_RELAXED, __ATOMIC_RELAXED)); + } + + void ReaderWriterLockHorizonImpl::ReleaseReadLockWriteLocked(os::ReaderWriterLockType *rw_lock) { + AMS_ASSERT(GetWriteLocked(GetLockCount(rw_lock)) == 1); + AMS_ASSERT((GetThreadHandle(GetLockCount(rw_lock)) | svc::HandleWaitMask) == (impl::GetCurrentThreadHandle() | svc::HandleWaitMask)); + alignas(alignof(u64)) LockCount expected = GetLockCount(rw_lock); + alignas(alignof(u64)) LockCount lock_count; + do { + lock_count = expected; + DecReadLockCount(lock_count); + } while (!ATOMIC_COMPARE_EXCHANGE_LOCK_COUNT(GetLockCount(rw_lock), expected, lock_count, __ATOMIC_RELAXED, __ATOMIC_RELAXED)); + return ReleaseWriteLockImpl(rw_lock); + } + + void ReaderWriterLockHorizonImpl::ReleaseWriteLockImpl(os::ReaderWriterLockType *rw_lock) { + alignas(alignof(u64)) LockCount expected = GetLockCount(rw_lock); + alignas(alignof(u64)) LockCount lock_count = expected; + AMS_ASSERT(GetWriteLocked(lock_count) == 1); + if (GetReadLockCount(lock_count) == 0 && GetWriteLockCount(*rw_lock) == 0) { + rw_lock->owner_thread = nullptr; + + if (GetWriteLockWaiterCount(lock_count) > 0) { + GetReference(rw_lock->cv_write_lock._storage).Signal(); + } else if (GetReadLockWaiterCount(lock_count) > 0) { + GetReference(rw_lock->cv_read_lock._storage).Broadcast(); + } + + do { + ClearWriteLocked(lock_count); + SetThreadHandle(lock_count, (GetThreadHandle(lock_count) & svc::HandleWaitMask) ? GetThreadHandle(lock_count) : svc::InvalidHandle); + } while(!ATOMIC_COMPARE_EXCHANGE_LOCK_COUNT(GetLockCount(rw_lock), expected, lock_count, __ATOMIC_RELEASE, __ATOMIC_RELAXED)); + + if (GetThreadHandle(lock_count)) { + R_ABORT_UNLESS(svc::ArbitrateUnlock(GetThreadHandleAddress(GetLockCount(rw_lock)))); + } + } + } + + void ReaderWriterLockHorizonImpl::AcquireReadLock(os::ReaderWriterLockType *rw_lock) { + AMS_ASSERT(::ams::svc::GetThreadLocalRegion()->disable_count == 0); + + auto *cur_thread = impl::GetCurrentThread(); + if (rw_lock->owner_thread == cur_thread) { + return AcquireReadLockWriteLocked(rw_lock); + } else { + const u32 cur_handle = cur_thread->thread_impl->handle; + bool arbitrated = false; + bool got_read_lock, needs_arbitrate_lock; + + alignas(alignof(u64)) LockCount lock_count; + alignas(alignof(u64)) LockCount expected; + while (true) { + expected = GetLockCount(rw_lock); + do { + lock_count = expected; + AMS_ASSERT(GetWriteLocked(lock_count) == 0 || GetThreadHandle(lock_count) != svc::InvalidHandle); + + if (GetWriteLocked(lock_count) != 0 || GetWriteLockWaiterCount(lock_count) != 0) { + if (!arbitrated) { + IncReadLockWaiterCount(lock_count); + } + const u32 h = GetThreadHandle(lock_count); + got_read_lock = false; + needs_arbitrate_lock = h != 0; + SetThreadHandle(lock_count, needs_arbitrate_lock ? (h | svc::HandleWaitMask) : cur_handle); + } else { + if (arbitrated) { + DecReadLockWaiterCount(lock_count); + } + IncReadLockCount(lock_count); + got_read_lock = true; + needs_arbitrate_lock = false; + } + } while (!ATOMIC_COMPARE_EXCHANGE_LOCK_COUNT(GetLockCount(rw_lock), expected, lock_count, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)); + + if (!needs_arbitrate_lock) { + break; + } + + R_ABORT_UNLESS(svc::ArbitrateLock(GetThreadHandle(lock_count) & ~svc::HandleWaitMask, GetThreadHandleAddress(GetLockCount(rw_lock)), cur_handle)); + + const u32 new_handle = GetThreadHandle(GetLockCount(rw_lock)); + if ((new_handle | svc::HandleWaitMask) == (cur_handle | svc::HandleWaitMask)) { + lock_count = GetLockCount(rw_lock); + break; + } + + arbitrated = true; + } + + if (got_read_lock) { + return; + } + + expected = lock_count; + do { + while (GetWriteLockWaiterCount(lock_count)) { + GetReference(rw_lock->cv_read_lock._storage).Wait(GetPointer(GetLockCount(rw_lock).cs_storage)); + expected = GetLockCount(rw_lock); + lock_count = expected; + } + DecReadLockWaiterCount(lock_count); + IncReadLockCount(lock_count); + SetThreadHandle(lock_count, (GetThreadHandle(lock_count) & svc::HandleWaitMask) ? GetThreadHandle(lock_count) : svc::InvalidHandle); + } while(!ATOMIC_COMPARE_EXCHANGE_LOCK_COUNT(GetLockCount(rw_lock), expected, lock_count, __ATOMIC_RELEASE, __ATOMIC_RELAXED)); + if (GetThreadHandle(lock_count) & svc::HandleWaitMask) { + R_ABORT_UNLESS(svc::ArbitrateUnlock(GetThreadHandleAddress(GetLockCount(rw_lock)))); + } + } + } + + bool ReaderWriterLockHorizonImpl::TryAcquireReadLock(os::ReaderWriterLockType *rw_lock) { + AMS_ASSERT(::ams::svc::GetThreadLocalRegion()->disable_count == 0); + + auto *cur_thread = impl::GetCurrentThread(); + if (rw_lock->owner_thread == cur_thread) { + AcquireReadLockWriteLocked(rw_lock); + return true; + } else { + alignas(alignof(u64)) LockCount expected = GetLockCount(rw_lock); + alignas(alignof(u64)) LockCount lock_count; + while (true) { + lock_count = expected; + AMS_ASSERT(GetWriteLocked(lock_count) == 0 || GetThreadHandle(lock_count) != 0); + if (GetWriteLocked(lock_count) == 0 && GetWriteLockWaiterCount(lock_count) == 0) { + IncReadLockCount(lock_count); + if (ATOMIC_COMPARE_EXCHANGE_LOCK_COUNT(GetLockCount(rw_lock), expected, lock_count, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) { + return true; + } + } else { + return false; + } + } + } + } + + void ReaderWriterLockHorizonImpl::ReleaseReadLock(os::ReaderWriterLockType *rw_lock) { + AMS_ASSERT(::ams::svc::GetThreadLocalRegion()->disable_count == 0); + + AMS_ASSERT(GetReadLockCount(GetLockCount(rw_lock)) > 0); + auto *cur_thread = impl::GetCurrentThread(); + if (rw_lock->owner_thread == cur_thread) { + return ReleaseReadLockWriteLocked(rw_lock); + } else { + alignas(alignof(u64)) LockCount expected = GetLockCount(rw_lock); + alignas(alignof(u64)) LockCount lock_count; + while (true) { + lock_count = expected; + DecReadLockCount(lock_count); + if (GetReadLockCount(lock_count) == 0 && GetWriteLockWaiterCount(lock_count) != 0) { + break; + } + if (ATOMIC_COMPARE_EXCHANGE_LOCK_COUNT(GetLockCount(rw_lock), expected, lock_count, __ATOMIC_RELEASE, __ATOMIC_RELAXED)) { + return; + } + } + + AMS_ASSERT(GetWriteLocked(lock_count) == 0); + + const u32 cur_handle = cur_thread->thread_impl->handle; + do { + do { + const u32 h = GetThreadHandle(lock_count); + SetThreadHandle(lock_count, h != 0 ? (h | svc::HandleWaitMask) : cur_handle); + } while (!ATOMIC_COMPARE_EXCHANGE_LOCK_COUNT(GetLockCount(rw_lock), expected, lock_count, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)); + + if (GetThreadHandle(lock_count) == cur_handle) { + break; + } + + R_ABORT_UNLESS(svc::ArbitrateLock(GetThreadHandle(lock_count) & ~svc::HandleWaitMask, GetThreadHandleAddress(GetLockCount(rw_lock)), cur_handle)); + expected = GetLockCount(rw_lock); + } while ((GetThreadHandle(GetLockCount(rw_lock)) | svc::HandleWaitMask) != (cur_handle | svc::HandleWaitMask)); + + do { + lock_count = expected; + AMS_ASSERT(GetReadLockCount(lock_count) == 1 && GetWriteLockWaiterCount(lock_count) > 0); + DecReadLockCount(lock_count); + } while (!ATOMIC_COMPARE_EXCHANGE_LOCK_COUNT(GetLockCount(rw_lock), expected, lock_count, __ATOMIC_RELAXED, __ATOMIC_RELAXED)); + + GetReference(rw_lock->cv_write_lock._storage).Signal(); + + do { + lock_count = expected; + AMS_ASSERT(GetReadLockCount(lock_count) == 0 && GetWriteLockWaiterCount(lock_count) > 0); + SetThreadHandle(lock_count, (GetThreadHandle(lock_count) & svc::HandleWaitMask) ? GetThreadHandle(lock_count) : svc::InvalidHandle); + } while(!ATOMIC_COMPARE_EXCHANGE_LOCK_COUNT(GetLockCount(rw_lock), expected, lock_count, __ATOMIC_RELEASE, __ATOMIC_RELAXED)); + + if (GetThreadHandle(lock_count) & svc::HandleWaitMask) { + R_ABORT_UNLESS(svc::ArbitrateUnlock(GetThreadHandleAddress(GetLockCount(rw_lock)))); + } + } + } + + void ReaderWriterLockHorizonImpl::AcquireWriteLock(os::ReaderWriterLockType *rw_lock) { + AMS_ASSERT(::ams::svc::GetThreadLocalRegion()->disable_count == 0); + + auto *cur_thread = impl::GetCurrentThread(); + const u32 cur_handle = cur_thread->thread_impl->handle; + if (rw_lock->owner_thread == cur_thread) { + AMS_ASSERT(GetWriteLocked(GetLockCount(rw_lock)) == 1); + AMS_ASSERT((GetThreadHandle(GetLockCount(rw_lock)) | svc::HandleWaitMask) == (cur_handle | svc::HandleWaitMask)); + IncWriteLockCount(*rw_lock); + } else { + alignas(alignof(u64)) LockCount expected; + alignas(alignof(u64)) LockCount lock_count; + bool arbitrated = false; + bool got_write_lock, needs_arbitrate_lock; + + while (true) { + expected = GetLockCount(rw_lock); + do { + lock_count = expected; + AMS_ASSERT(GetWriteLocked(lock_count) == 0 || GetThreadHandle(lock_count) != svc::InvalidHandle); + if (GetReadLockCount(lock_count) > 0 || GetThreadHandle(lock_count) != svc::InvalidHandle) { + if (!arbitrated) { + IncWriteLockWaiterCount(lock_count); + } + const u32 h = GetThreadHandle(lock_count); + got_write_lock = false; + needs_arbitrate_lock = h != 0; + SetThreadHandle(lock_count, needs_arbitrate_lock ? (h | svc::HandleWaitMask) : cur_handle); + } else { + if (arbitrated) { + DecWriteLockWaiterCount(lock_count); + } + SetWriteLocked(lock_count); + got_write_lock = true; + needs_arbitrate_lock = false; + } + } while (!ATOMIC_COMPARE_EXCHANGE_LOCK_COUNT(GetLockCount(rw_lock), expected, lock_count, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)); + + if (got_write_lock) { + break; + } + + if (needs_arbitrate_lock) { + R_ABORT_UNLESS(svc::ArbitrateLock(GetThreadHandle(lock_count) & ~svc::HandleWaitMask, GetThreadHandleAddress(GetLockCount(rw_lock)), cur_handle)); + arbitrated = true; + expected = GetLockCount(rw_lock); + if ((GetThreadHandle(expected) | svc::HandleWaitMask) != (cur_handle | svc::HandleWaitMask)) { + continue; + } + } + + do { + lock_count = expected; + + AMS_ASSERT(GetWriteLocked(lock_count) == 0); + while (GetReadLockCount(lock_count) > 0) { + GetReference(rw_lock->cv_write_lock._storage).Wait(GetPointer(GetLockCount(rw_lock).cs_storage)); + expected = GetLockCount(rw_lock); + lock_count = expected; + } + AMS_ASSERT(GetWriteLocked(lock_count) == 0); + + DecWriteLockWaiterCount(lock_count); + SetWriteLocked(lock_count); + } while (!ATOMIC_COMPARE_EXCHANGE_LOCK_COUNT(GetLockCount(rw_lock), expected, lock_count, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)); + + break; + } + + AMS_ASSERT(GetWriteLockCount(*rw_lock) == 0); + IncWriteLockCount(*rw_lock); + rw_lock->owner_thread = cur_thread; + } + } + + bool ReaderWriterLockHorizonImpl::TryAcquireWriteLock(os::ReaderWriterLockType *rw_lock) { + AMS_ASSERT(::ams::svc::GetThreadLocalRegion()->disable_count == 0); + + auto *cur_thread = impl::GetCurrentThread(); + if (rw_lock->owner_thread == cur_thread) { + AMS_ASSERT(GetWriteLocked(GetLockCount(rw_lock)) == 1); + IncWriteLockCount(*rw_lock); + return true; + } else { + const u32 cur_handle = cur_thread->thread_impl->handle; + alignas(alignof(u64)) LockCount expected = GetLockCount(rw_lock); + alignas(alignof(u64)) LockCount lock_count; + + do { + lock_count = expected; + AMS_ASSERT(GetWriteLocked(lock_count) == 0 || GetThreadHandle(lock_count) != svc::InvalidHandle); + + if (GetReadLockCount(lock_count) > 0 || GetThreadHandle(lock_count) != svc::InvalidHandle) { + return false; + } + + SetWriteLocked(lock_count); + SetThreadHandle(lock_count, cur_handle); + } while (!ATOMIC_COMPARE_EXCHANGE_LOCK_COUNT(GetLockCount(rw_lock), expected, lock_count, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)); + + + AMS_ASSERT(GetWriteLockCount(*rw_lock) == 0); + IncWriteLockCount(*rw_lock); + rw_lock->owner_thread = cur_thread; + return true; + } + } + + void ReaderWriterLockHorizonImpl::ReleaseWriteLock(os::ReaderWriterLockType *rw_lock) { + AMS_ASSERT(::ams::svc::GetThreadLocalRegion()->disable_count == 0); + + AMS_ASSERT(GetWriteLockCount(*rw_lock) > 0); + AMS_ASSERT(GetWriteLocked(GetLockCount(rw_lock)) != 0); + + DecWriteLockCount(*rw_lock); + return ReleaseWriteLockImpl(rw_lock); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rw_lock_target_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rw_lock_target_impl.os.horizon.hpp new file mode 100644 index 00000000..30c82548 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_rw_lock_target_impl.os.horizon.hpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class ReaderWriterLockHorizonImpl { + private: + using LockCount = os::ReaderWriterLockType::LockCount; + private: + static ALWAYS_INLINE u32 GetThreadHandle(const LockCount &lc) { + return GetReference(lc.cs_storage).Get()->m_thread_handle; + } + + static ALWAYS_INLINE uintptr_t GetThreadHandleAddress(LockCount &lc) { + return reinterpret_cast<uintptr_t>(std::addressof(GetReference(lc.cs_storage).Get()->m_thread_handle)); + } + + static ALWAYS_INLINE void SetThreadHandle(LockCount &lc, u32 handle) { + GetReference(lc.cs_storage).Get()->m_thread_handle = handle; + } + + static void AcquireReadLockWriteLocked(os::ReaderWriterLockType *rw_lock); + static void ReleaseReadLockWriteLocked(os::ReaderWriterLockType *rw_lock); + static void ReleaseWriteLockImpl(os::ReaderWriterLockType *rw_lock); + public: + static void AcquireReadLock(os::ReaderWriterLockType *rw_lock); + static bool TryAcquireReadLock(os::ReaderWriterLockType *rw_lock); + static void ReleaseReadLock(os::ReaderWriterLockType *rw_lock); + + static void AcquireWriteLock(os::ReaderWriterLockType *rw_lock); + static bool TryAcquireWriteLock(os::ReaderWriterLockType *rw_lock); + static void ReleaseWriteLock(os::ReaderWriterLockType *rw_lock); + }; + + using ReaderWriterLockTargetImpl = ReaderWriterLockHorizonImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_shared_memory_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_shared_memory_impl.hpp new file mode 100644 index 00000000..6cc5e89f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_shared_memory_impl.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class SharedMemoryImpl { + public: + static Result Create(NativeHandle *out, size_t size, MemoryPermission my_perm, MemoryPermission other_perm); + static void Close(NativeHandle handle); + + static Result Map(void **out, NativeHandle handle, size_t size, MemoryPermission perm); + static void Unmap(NativeHandle handle, void *address, size_t size); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_shared_memory_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_shared_memory_impl.os.horizon.cpp new file mode 100644 index 00000000..94450ef2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_shared_memory_impl.os.horizon.cpp @@ -0,0 +1,87 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_shared_memory_impl.hpp" +#include "os_aslr_space_manager.hpp" + +namespace ams::os::impl { + + namespace { + + svc::MemoryPermission ConvertToSvcMemoryPermission(os::MemoryPermission perm) { + switch (perm) { + case os::MemoryPermission_None: return svc::MemoryPermission_None; + case os::MemoryPermission_ReadOnly: return svc::MemoryPermission_Read; + case os::MemoryPermission_WriteOnly: return svc::MemoryPermission_Write; + case os::MemoryPermission_ReadWrite: return svc::MemoryPermission_ReadWrite; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + } + + Result SharedMemoryImpl::Create(NativeHandle *out, size_t size, MemoryPermission my_perm, MemoryPermission other_perm) { + /* Convert memory permissions. */ + const auto svc_my_perm = ConvertToSvcMemoryPermission(my_perm); + const auto svc_other_perm = ConvertToSvcMemoryPermission(other_perm); + + /* Create the memory. */ + svc::Handle handle; + R_TRY_CATCH(svc::CreateSharedMemory(std::addressof(handle), size, svc_my_perm, svc_other_perm)) { + R_CONVERT(svc::ResultOutOfHandles, os::ResultOutOfHandles()) + R_CONVERT(svc::ResultOutOfResource, os::ResultOutOfResource()) + R_CONVERT(svc::ResultLimitReached, os::ResultOutOfMemory()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + *out = handle; + R_SUCCEED(); + } + + void SharedMemoryImpl::Close(NativeHandle handle) { + R_ABORT_UNLESS(svc::CloseHandle(handle)); + } + + Result SharedMemoryImpl::Map(void **out, NativeHandle handle, size_t size, MemoryPermission perm) { + /* Convert memory permission. */ + const auto svc_perm = ConvertToSvcMemoryPermission(perm); + + /* Map at a random address. */ + uintptr_t mapped_address; + R_TRY(impl::GetAslrSpaceManager().MapAtRandomAddress(std::addressof(mapped_address), + [handle, svc_perm](uintptr_t map_address, size_t map_size) -> Result { + R_TRY_CATCH(svc::MapSharedMemory(handle, map_address, map_size, svc_perm)) { + R_CONVERT(svc::ResultInvalidCurrentMemory, os::ResultInvalidCurrentMemoryState()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + R_SUCCEED(); + }, + [handle](uintptr_t map_address, size_t map_size) -> void { + return SharedMemoryImpl::Unmap(handle, reinterpret_cast<void *>(map_address), map_size); + }, + size, + 0 + )); + + /* Return the address we mapped at. */ + *out = reinterpret_cast<void *>(mapped_address); + R_SUCCEED(); + } + + void SharedMemoryImpl::Unmap(NativeHandle handle, void *address, size_t size) { + R_ABORT_UNLESS(svc::UnmapSharedMemory(handle, reinterpret_cast<uintptr_t>(address), size)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_stack_guard_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_stack_guard_manager.hpp new file mode 100644 index 00000000..5a6b0dc9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_stack_guard_manager.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_resource_manager.hpp" + +namespace ams::os::impl { + + ALWAYS_INLINE StackGuardManager &GetStackGuardManager() { + return GetResourceManager().GetStackGuardManager(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_stack_guard_manager_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_stack_guard_manager_impl.os.horizon.hpp new file mode 100644 index 00000000..12d5f851 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_stack_guard_manager_impl.os.horizon.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class StackGuardManagerHorizonImpl { + private: + static u64 GetStackInfo(svc::InfoType type) { + u64 value; + R_ABORT_UNLESS(svc::GetInfo(std::addressof(value), type, svc::PseudoHandle::CurrentProcess, 0)); + AMS_ASSERT(value <= std::numeric_limits<size_t>::max()); + return static_cast<u64>(static_cast<size_t>(value)); + } + public: + static u64 GetStackGuardBeginAddress() { return GetStackInfo(svc::InfoType_StackRegionAddress); } + static u64 GetStackGuardEndAddress() { return GetStackInfo(svc::InfoType_StackRegionSize); } + }; + + using StackGuardManagerImpl = StackGuardManagerHorizonImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_stack_guard_manager_impl.os.linux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_stack_guard_manager_impl.os.linux.hpp new file mode 100644 index 00000000..47136ef2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_stack_guard_manager_impl.os.linux.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class StackGuardManagerLinuxImpl { + public: + static u64 GetStackGuardBeginAddress() { return 256_MB; } + static u64 GetStackGuardEndAddress() { return 256_MB + 1_GB; } + }; + + using StackGuardManagerImpl = StackGuardManagerLinuxImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_stack_guard_manager_impl.os.macos.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_stack_guard_manager_impl.os.macos.hpp new file mode 100644 index 00000000..6e16610b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_stack_guard_manager_impl.os.macos.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class StackGuardManagerMacosImpl { + public: + static u64 GetStackGuardBeginAddress() { return 256_MB; } + static u64 GetStackGuardEndAddress() { return 256_MB + 1_GB; } + }; + + using StackGuardManagerImpl = StackGuardManagerMacosImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_stack_guard_manager_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_stack_guard_manager_impl.os.windows.hpp new file mode 100644 index 00000000..45773bd4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_stack_guard_manager_impl.os.windows.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class StackGuardManagerWindowsImpl { + public: + static u64 GetStackGuardBeginAddress() { return 256_MB; } + static u64 GetStackGuardEndAddress() { return 256_MB + 1_GB; } + }; + + using StackGuardManagerImpl = StackGuardManagerWindowsImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_stack_guard_manager_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_stack_guard_manager_types.hpp new file mode 100644 index 00000000..59f7cfbe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_stack_guard_manager_types.hpp @@ -0,0 +1,81 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_address_space_allocator.hpp" + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "os_stack_guard_manager_impl.os.horizon.hpp" +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include "os_stack_guard_manager_impl.os.windows.hpp" +#elif defined(ATMOSPHERE_OS_LINUX) + #include "os_stack_guard_manager_impl.os.linux.hpp" +#elif defined(ATMOSPHERE_OS_MACOS) + #include "os_stack_guard_manager_impl.os.macos.hpp" +#else + #error "Unknown OS for StackGuardManagerImpl" +#endif + +namespace ams::os::impl { + + constexpr inline size_t StackGuardSize = 4 * os::MemoryPageSize; + + class StackGuardManager { + private: + StackGuardManagerImpl m_impl; + AddressSpaceAllocator m_allocator; + public: + StackGuardManager() : m_impl(), m_allocator(m_impl.GetStackGuardBeginAddress(), m_impl.GetStackGuardEndAddress(), StackGuardSize, nullptr, 0) { + /* ... */ + } + + void *AllocateStackGuardSpace(size_t size) { + return reinterpret_cast<void *>(m_allocator.AllocateSpace(size, os::MemoryPageSize, 0, os::impl::AddressSpaceAllocatorDefaultGenerateRandom)); + } + + bool CheckGuardSpace(uintptr_t address, size_t size) { + return m_allocator.CheckGuardSpace(address, size, StackGuardSize); + } + + AddressAllocationResult MapAtRandomAddress(void **out, bool (*map)(const void *, const void *, size_t), void (*unmap)(const void *, const void *, size_t), const void *address, size_t size) { + /* Try to map up to 0x40 times. */ + constexpr int TryCountMax = 0x40; + for (auto i = 0; i < TryCountMax; ++i) { + /* Get stack guard space. */ + void * const space = this->AllocateStackGuardSpace(size); + if (space == nullptr) { + return AddressAllocationResult_OutOfMemory; + } + + /* Try to map. */ + if (map(space, address, size)) { + /* Check that the guard space is still there. */ + if (this->CheckGuardSpace(reinterpret_cast<uintptr_t>(space), size)) { + *out = space; + return AddressAllocationResult_Success; + } else { + /* We need to retry. */ + unmap(space, address, size); + } + } + } + + /* We failed. */ + return AddressAllocationResult_OutOfSpace; + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager.cpp new file mode 100644 index 00000000..9e48402e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager.cpp @@ -0,0 +1,298 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_thread_manager.hpp" +#include "os_multiple_wait_impl.hpp" +#include "os_multiple_wait_holder_base.hpp" +#include "os_multiple_wait_holder_impl.hpp" +#include "os_multiple_wait_object_list.hpp" +#include "os_utility.hpp" + +namespace ams::os::impl { + + void SetupThreadObjectUnsafe(ThreadType *thread, void *platform, ThreadFunction function, void *arg, void *stack, size_t stack_size, s32 priority) { + /* Clear the thread object. */ + std::memset(thread, 0, sizeof(*thread)); + + /* Setup objects. */ + util::ConstructAt(thread->cs_thread); + util::ConstructAt(thread->cv_thread); + + util::ConstructAt(thread->all_threads_node); + util::ConstructAt(thread->waitlist); + + /* Set member variables. */ + thread->magic = os::ThreadType::Magic; + thread->stack_is_aliased = false; + thread->auto_registered = false; + thread->version = 0; + thread->function = function; + thread->argument = arg; + thread->original_stack = stack; + thread->stack = stack; + thread->stack_size = stack_size; + thread->base_priority = priority; + thread->suspend_count = 0; + thread->initial_fiber = nullptr; + thread->current_fiber = nullptr; + thread->name_buffer[0] = '\x00'; + thread->name_pointer = thread->name_buffer; + + /* Set platform variables. */ + #if defined(AMS_OS_IMPL_USE_PTHREADS) + util::ConstructAt(thread->cs_pthread_exit); + util::ConstructAt(thread->cv_pthread_exit); + thread->exited_pthread = false; + + std::memset(thread->tls_value_array, 0, sizeof(thread->tls_value_array)); + AMS_UNUSED(platform); + #elif defined(ATMOSPHERE_OS_HORIZON) + std::memset(std::addressof(thread->sdk_internal_tls), 0, sizeof(thread->sdk_internal_tls)); + thread->thread_impl = (platform != nullptr) ? static_cast<ThreadType::ThreadImpl *>(platform) : std::addressof(thread->thread_impl_storage); + #else + std::memset(thread->tls_value_array, 0, sizeof(thread->tls_value_array)); + AMS_UNUSED(platform); + #endif + + /* Mark initialized. */ + thread->state = ThreadType::State_Initialized; + } + + void ThreadManager::InvokeThread(ThreadType *thread) { + auto &manager = GetThreadManager(); + + manager.SetCurrentThread(thread); + manager.NotifyThreadNameChanged(thread); + + { + GetReference(thread->cs_thread).Lock(); + while (thread->state == ThreadType::State_Initialized) { + GetReference(thread->cv_thread).Wait(GetPointer(thread->cs_thread)); + } + const auto new_state = thread->state; + GetReference(thread->cs_thread).Unlock(); + + if (new_state == ThreadType::State_Started) { + thread->function(thread->argument); + } + } + + manager.CleanupThread(); + } + + ThreadManager::ThreadManager() : m_impl(std::addressof(m_main_thread)), m_total_thread_stack_size(0), m_num_created_threads(0) { + m_total_thread_stack_size = 0; + m_num_created_threads = 0; + + m_main_thread.state = ThreadType::State_Started; + + this->SetCurrentThread(std::addressof(m_main_thread)); + this->PlaceThreadObjectUnderThreadManagerSafe(std::addressof(m_main_thread)); + } + + void ThreadManager::CleanupThread(ThreadType *thread) { + /* TODO: TLS Manager->InvokeTlsDestructors(); */ + + std::scoped_lock lk(GetReference(thread->cs_thread)); + + thread->state = ThreadType::State_Terminated; + + GetReference(thread->cv_thread).Broadcast(); + GetReference(thread->waitlist).WakeupAllMultiWaitThreadsUnsafe(); + } + + void ThreadManager::CleanupThread() { + return this->CleanupThread(this->GetCurrentThread()); + } + + bool ThreadManager::CreateAliasStackUnsafe(ThreadType *thread) { + void *alias_stack; + if (m_impl.MapAliasStack(std::addressof(alias_stack), thread->stack, thread->stack_size)) { + thread->stack_is_aliased = true; + thread->stack = alias_stack; + return true; + } else { + return false; + } + } + + void ThreadManager::DeleteAliasStackUnsafe(ThreadType *thread) { + AMS_ABORT_UNLESS(m_impl.UnmapAliasStack(thread->stack, thread->original_stack, thread->stack_size)); + + thread->stack_is_aliased = false; + thread->stack = thread->original_stack; + } + + Result ThreadManager::CreateThread(ThreadType *thread, ThreadFunction function, void *argument, void *stack, size_t stack_size, s32 priority, s32 ideal_core) { + SetupThreadObjectUnsafe(thread, nullptr, function, argument, stack, stack_size, priority); + AMS_ABORT_UNLESS(this->CreateAliasStackUnsafe(thread)); + ON_RESULT_FAILURE { + this->DeleteAliasStackUnsafe(thread); + thread->state = ThreadType::State_NotInitialized; + }; + + R_TRY(m_impl.CreateThread(thread, ideal_core)); + + this->PlaceThreadObjectUnderThreadManagerSafe(thread); + + R_SUCCEED(); + } + + Result ThreadManager::CreateThread(ThreadType *thread, ThreadFunction function, void *argument, void *stack, size_t stack_size, s32 priority) { + R_RETURN(this->CreateThread(thread, function, argument, stack, stack_size, priority, m_impl.GetDefaultCoreNumber())); + } + + void ThreadManager::DestroyThread(ThreadType *thread) { + { + std::scoped_lock lk(GetReference(thread->cs_thread)); + + if (thread->state == ThreadType::State_Initialized) { + thread->state = ThreadType::State_DestroyedBeforeStarted; + m_impl.StartThread(thread); + GetReference(thread->cv_thread).Signal(); + } + } + + m_impl.WaitForThreadExit(thread); + + this->DestroyThreadObject(thread); + } + + void ThreadManager::DestroyThreadObject(ThreadType *thread) { + { + std::scoped_lock lk(GetReference(thread->cs_thread)); + + { + std::scoped_lock tlk(m_cs); + this->EraseFromAllThreadsListUnsafe(thread); + } + + if (thread->stack_is_aliased) { + this->DeleteAliasStackUnsafe(thread); + } + + m_impl.DestroyThreadUnsafe(thread); + + thread->state = ThreadType::State_NotInitialized; + + thread->name_buffer[0] = '\x00'; + thread->magic = 0xCCCC; + + util::DestroyAt(thread->waitlist); + } + util::DestroyAt(thread->cs_thread); + util::DestroyAt(thread->cv_thread); + } + + void ThreadManager::StartThread(ThreadType *thread) { + std::scoped_lock lk(GetReference(thread->cs_thread)); + + AMS_ASSERT(thread->state == ThreadType::State_Initialized); + + m_impl.StartThread(thread); + thread->state = ThreadType::State_Started; + + GetReference(thread->cv_thread).Signal(); + } + + void ThreadManager::WaitThread(ThreadType *thread) { + m_impl.WaitForThreadExit(thread); + + { + std::scoped_lock lk(GetReference(thread->cs_thread)); + + if (thread->stack_is_aliased) { + this->DeleteAliasStackUnsafe(thread); + } + } + } + + bool ThreadManager::TryWaitThread(ThreadType *thread) { + const bool result = m_impl.TryWaitForThreadExit(thread); + + if (result) { + std::scoped_lock lk(GetReference(thread->cs_thread)); + + if (thread->stack_is_aliased) { + this->DeleteAliasStackUnsafe(thread); + } + } + + return result; + } + + s32 ThreadManager::SuspendThread(ThreadType *thread) { + std::scoped_lock lk(GetReference(thread->cs_thread)); + + const auto prev_suspend_count = thread->suspend_count; + AMS_ASSERT(prev_suspend_count < ThreadSuspendCountMax); + thread->suspend_count = prev_suspend_count + 1; + + if (prev_suspend_count == 0) { + m_impl.SuspendThreadUnsafe(thread); + } + return prev_suspend_count; + } + + s32 ThreadManager::ResumeThread(ThreadType *thread) { + std::scoped_lock lk(GetReference(thread->cs_thread)); + + const auto prev_suspend_count = thread->suspend_count; + if (prev_suspend_count > 0) { + thread->suspend_count = prev_suspend_count - 1; + if (prev_suspend_count == 1) { + m_impl.ResumeThreadUnsafe(thread); + } + } + return prev_suspend_count; + } + + #if !defined(ATMOSPHERE_OS_HORIZON) + void ThreadManager::SetZeroToAllThreadsTlsSafe(int slot) { + std::scoped_lock lk(m_cs); + + for (auto it = m_all_threads_list.begin(); it != m_all_threads_list.end(); ++it) { + it->tls_value_array[slot] = 0; + } + } + #endif + + /* TODO void ThreadManager::GetThreadContext(ThreadContextInfo *out_context, const ThreadType *thread); */ + + namespace { + + constexpr inline const char MainThreadName[] = "MainThread"; + constexpr inline const char ThreadNamePrefix[] = "Thread_0x"; + + } + + void ThreadManager::SetInitialThreadNameUnsafe(ThreadType *thread) { + if (thread == std::addressof(m_main_thread)) { + static_assert(sizeof(thread->name_buffer) >= sizeof(MainThreadName)); + static_assert(MainThreadName[sizeof(MainThreadName) - 1] == '\x00'); + std::memcpy(thread->name_buffer, MainThreadName, sizeof(MainThreadName)); + } else { + constexpr size_t ThreadNamePrefixSize = sizeof(ThreadNamePrefix) - 1; + const u64 func = reinterpret_cast<u64>(thread->function); + static_assert(ThreadNamePrefixSize + sizeof(func) * 2 + 1 <= sizeof(thread->name_buffer)); + std::memcpy(thread->name_buffer, ThreadNamePrefix, ThreadNamePrefixSize); + os::impl::ExpandUnsignedValueToAscii(thread->name_buffer + ThreadNamePrefixSize, func); + } + + thread->name_pointer = thread->name_buffer; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager.hpp new file mode 100644 index 00000000..6721cfa2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_thread_manager_types.hpp" +#include "os_resource_manager.hpp" + +namespace ams::os::impl { + + constexpr inline s32 CoreAffinityMaskBitWidth = BITSIZEOF(u64); + + ALWAYS_INLINE ThreadManager &GetThreadManager() { + return GetResourceManager().GetThreadManager(); + } + + ALWAYS_INLINE ThreadType *GetCurrentThread() { + return GetThreadManager().GetCurrentThread(); + } + + #if !defined(AMS_OS_IMPL_USE_PTHREADS) + ALWAYS_INLINE NativeHandle GetCurrentThreadHandle() { + #if defined(ATMOSPHERE_OS_HORIZON) + return ::threadGetCurHandle(); + #else + return GetCurrentThread()->native_handle; + #endif + } + #endif + + void SetupThreadObjectUnsafe(ThreadType *thread, void *platform, ThreadFunction function, void *arg, void *stack, size_t stack_size, s32 priority); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.os.horizon.cpp new file mode 100644 index 00000000..83dae07e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.os.horizon.cpp @@ -0,0 +1,235 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_thread_manager_impl.os.horizon.hpp" +#include "os_thread_manager.hpp" + +namespace ams::os::impl { + + thread_local ThreadType *g_current_thread_pointer; + + namespace { + + s32 ConvertToHorizonPriority(s32 user_priority) { + const s32 horizon_priority = user_priority + UserThreadPriorityOffset; + AMS_ASSERT(HighestTargetThreadPriority <= horizon_priority && horizon_priority <= LowestTargetThreadPriority); + return horizon_priority; + } + + s32 ConvertToUserPriority(s32 horizon_priority) { + AMS_ASSERT(HighestTargetThreadPriority <= horizon_priority && horizon_priority <= LowestTargetThreadPriority); + return horizon_priority - UserThreadPriorityOffset; + } + + void InvokeThread(uintptr_t _thread) { + ThreadType *thread = reinterpret_cast<ThreadType *>(_thread); + + /* Set the thread's id. */ + u64 thread_id; + R_ABORT_UNLESS(svc::GetThreadId(std::addressof(thread_id), thread->thread_impl->handle)); + thread->thread_id = thread_id; + + /* Invoke the thread. */ + ThreadManager::InvokeThread(thread); + } + + } + + ThreadManagerHorizonImpl::ThreadManagerHorizonImpl(ThreadType *main_thread) { + /* Get the thread impl object from libnx. */ + ThreadType::ThreadImpl *thread_impl = ::threadGetSelf(); + auto * const original_thread_impl = thread_impl; + + /* Hack around libnx's main thread, to ensure stratosphere thread type consistency. */ + { + auto *tlr = reinterpret_cast<uintptr_t *>(svc::GetThreadLocalRegion()); + for (size_t i = sizeof(svc::ThreadLocalRegion) / sizeof(uintptr_t); i > 0; --i) { + if (auto *candidate = reinterpret_cast<ThreadType::ThreadImpl *>(tlr[i - 1]); candidate == thread_impl) { + ThreadType::ThreadImpl *embedded_thread = std::addressof(main_thread->thread_impl_storage); + + *embedded_thread = *thread_impl; + + if (embedded_thread->next) { + embedded_thread->next->prev_next = std::addressof(embedded_thread->next); + } + + thread_impl = embedded_thread; + tlr[i-1] = reinterpret_cast<uintptr_t>(thread_impl); + break; + } + } + } + + /* Get the thread priority. */ + s32 horizon_priority; + R_ABORT_UNLESS(svc::GetThreadPriority(std::addressof(horizon_priority), thread_impl->handle)); + + SetupThreadObjectUnsafe(main_thread, thread_impl, nullptr, nullptr, thread_impl->stack_mirror, thread_impl->stack_sz, ConvertToUserPriority(horizon_priority)); + + /* Fix up the thread impl. */ + *thread_impl = *original_thread_impl; + + /* Set the thread id. */ + u64 thread_id; + R_ABORT_UNLESS(svc::GetThreadId(std::addressof(thread_id), thread_impl->handle)); + main_thread->thread_id = thread_id; + + /* NOTE: Here Nintendo would set the thread pointer in TLS. */ + } + + Result ThreadManagerHorizonImpl::CreateThread(ThreadType *thread, s32 ideal_core) { + /* Note: Here Nintendo would set the stack args to point to ThreadManager::InvokeThread. */ + + s32 count = 0; + while (true) { + R_TRY_CATCH(::threadCreate(thread->thread_impl, reinterpret_cast<::ThreadFunc>(reinterpret_cast<void *>(&InvokeThread)), thread, thread->stack, thread->stack_size, ConvertToHorizonPriority(thread->base_priority), ideal_core)) { + R_CATCH(svc::ResultOutOfResource) { + if ((++count) < 10) { + os::SleepThread(TimeSpan::FromMilliSeconds(10)); + continue; + } + R_THROW(os::ResultOutOfResource()); + } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + R_SUCCEED(); + } + } + + void ThreadManagerHorizonImpl::DestroyThreadUnsafe(ThreadType *thread) { + R_ABORT_UNLESS(::threadClose(thread->thread_impl)); + } + + void ThreadManagerHorizonImpl::StartThread(const ThreadType *thread) { + R_ABORT_UNLESS(::threadStart(thread->thread_impl)); + } + + void ThreadManagerHorizonImpl::WaitForThreadExit(ThreadType *thread) { + const svc::Handle handle = thread->thread_impl->handle; + + while (true) { + s32 index; + R_TRY_CATCH(svc::WaitSynchronization(std::addressof(index), std::addressof(handle), 1, svc::WaitInfinite)) { + R_CATCH(svc::ResultCancelled) { continue; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + return; + } + } + + bool ThreadManagerHorizonImpl::TryWaitForThreadExit(ThreadType *thread) { + const svc::Handle handle = thread->thread_impl->handle; + + while (true) { + /* Continuously wait, until success or timeout. */ + s32 index; + Result res = svc::WaitSynchronization(std::addressof(index), std::addressof(handle), 1, 0); + + /* If we succeeded, we're signaled. */ + if (R_SUCCEEDED(res)) { + return true; + } + + /* If we timed out, we're not signaled. */ + if (svc::ResultTimedOut::Includes(res)) { + return false; + } + + AMS_ABORT_UNLESS(svc::ResultCancelled::Includes(res)); + } + } + + void ThreadManagerHorizonImpl::YieldThread() { + if (hos::GetVersion() >= hos::Version_4_0_0) { + svc::SleepThread(svc::YieldType_WithCoreMigration); + } else { + svc::SleepThread(svc::YieldType_WithoutCoreMigration); + } + } + + bool ThreadManagerHorizonImpl::ChangePriority(ThreadType *thread, s32 priority) { + auto res = svc::SetThreadPriority(thread->thread_impl->handle, ConvertToHorizonPriority(priority)); + if (svc::ResultInvalidPriority::Includes(res)) { + AMS_ABORT("Invalid thread priority"); + } + + return R_SUCCEEDED(res); + } + + s32 ThreadManagerHorizonImpl::GetCurrentPriority(const ThreadType *thread) const { + s32 priority; + R_ABORT_UNLESS(svc::GetThreadPriority(std::addressof(priority), thread->thread_impl->handle)); + + return ConvertToUserPriority(priority); + } + + ThreadId ThreadManagerHorizonImpl::GetThreadId(const ThreadType *thread) const { + return thread->thread_id; + } + + void ThreadManagerHorizonImpl::SuspendThreadUnsafe(ThreadType *thread) { + R_ABORT_UNLESS(svc::SetThreadActivity(thread->thread_impl->handle, svc::ThreadActivity_Paused)); + } + + void ThreadManagerHorizonImpl::ResumeThreadUnsafe(ThreadType *thread) { + R_ABORT_UNLESS(svc::SetThreadActivity(thread->thread_impl->handle, svc::ThreadActivity_Runnable)); + } + + /* TODO: void GetThreadContextUnsafe(ThreadContextInfo *out_context, const ThreadType *thread); */ + + s32 ThreadManagerHorizonImpl::GetCurrentCoreNumber() const { + return svc::GetCurrentProcessorNumber(); + } + + void ThreadManagerHorizonImpl::SetThreadCoreMask(ThreadType *thread, s32 ideal_core, u64 affinity_mask) const { + R_ABORT_UNLESS(svc::SetThreadCoreMask(thread->thread_impl->handle, ideal_core, affinity_mask)); + } + + void ThreadManagerHorizonImpl::GetThreadCoreMask(s32 *out_ideal_core, u64 *out_affinity_mask, const ThreadType *thread) const { + s32 ideal_core; + u64 affinity_mask; + + R_ABORT_UNLESS(svc::GetThreadCoreMask(std::addressof(ideal_core), std::addressof(affinity_mask), thread->thread_impl->handle)); + + if (out_ideal_core) { + *out_ideal_core = ideal_core; + } + if (out_affinity_mask) { + *out_affinity_mask = affinity_mask; + } + } + + u64 ThreadManagerHorizonImpl::GetThreadAvailableCoreMask() const { + u64 core_mask; + R_ABORT_UNLESS(svc::GetInfo(std::addressof(core_mask), svc::InfoType_CoreMask, svc::PseudoHandle::CurrentProcess, 0)); + return core_mask; + } + + bool ThreadManagerHorizonImpl::MapAliasStack(void **out, void *stack, size_t size) { + /* TODO: This will need to be real, if we ever stop using libnx threads. */ + AMS_UNUSED(stack, size); + *out = stack; + return true; + } + + bool ThreadManagerHorizonImpl::UnmapAliasStack(void *alias_stack, void *original_stack, size_t size) { + /* TODO: This will need to be real, if we ever stop using libnx threads. */ + AMS_UNUSED(alias_stack, original_stack, size); + return true; + } + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.os.horizon.hpp new file mode 100644 index 00000000..8f13cc01 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.os.horizon.hpp @@ -0,0 +1,84 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + constexpr inline s32 TargetThreadPriorityRangeSize = svc::LowestThreadPriority - svc::HighestThreadPriority + 1; + constexpr inline s32 ReservedThreadPriorityRangeSize = svc::SystemThreadPriorityHighest; + constexpr inline s32 SystemThreadPriorityRangeSize = TargetThreadPriorityRangeSize - ReservedThreadPriorityRangeSize; + constexpr inline s32 UserThreadPriorityOffset = 28; + + constexpr inline s32 HighestTargetThreadPriority = 0; + constexpr inline s32 LowestTargetThreadPriority = TargetThreadPriorityRangeSize - 1; + + extern thread_local ThreadType *g_current_thread_pointer; + + class ThreadManagerHorizonImpl { + NON_COPYABLE(ThreadManagerHorizonImpl); + NON_MOVEABLE(ThreadManagerHorizonImpl); + public: + explicit ThreadManagerHorizonImpl(ThreadType *main_thread); + + Result CreateThread(ThreadType *thread, s32 ideal_core); + void DestroyThreadUnsafe(ThreadType *thread); + void StartThread(const ThreadType *thread); + void WaitForThreadExit(ThreadType *thread); + bool TryWaitForThreadExit(ThreadType *thread); + void YieldThread(); + bool ChangePriority(ThreadType *thread, s32 priority); + s32 GetCurrentPriority(const ThreadType *thread) const; + ThreadId GetThreadId(const ThreadType *thread) const; + + void SuspendThreadUnsafe(ThreadType *thread); + void ResumeThreadUnsafe(ThreadType *thread); + + /* TODO: void GetThreadContextUnsafe(ThreadContextInfo *out_context, const ThreadType *thread); */ + + void NotifyThreadNameChangedImpl(const ThreadType *thread) const { AMS_UNUSED(thread); } + + void SetCurrentThread(ThreadType *thread) const { + g_current_thread_pointer = thread; + } + + ThreadType *GetCurrentThread() const { + return g_current_thread_pointer; + } + + s32 GetCurrentCoreNumber() const; + s32 GetDefaultCoreNumber() const { return svc::IdealCoreUseProcessValue; } + + void SetThreadCoreMask(ThreadType *thread, s32 ideal_core, u64 affinity_mask) const; + void GetThreadCoreMask(s32 *out_ideal_core, u64 *out_affinity_mask, const ThreadType *thread) const; + u64 GetThreadAvailableCoreMask() const; + + bool MapAliasStack(void **out, void *stack, size_t size); + bool UnmapAliasStack(void *alias_stack, void *original_stack, size_t size); + + NORETURN void ExitProcessImpl() { + svc::ExitProcess(); + AMS_ABORT("Process was exited"); + } + + NORETURN void QuickExit() { + AMS_ABORT("TODO: make QuickExit properly a thing"); + } + }; + + using ThreadManagerImpl = ThreadManagerHorizonImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.os.windows.cpp new file mode 100644 index 00000000..0e43caca --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.os.windows.cpp @@ -0,0 +1,262 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_thread_manager_impl.os.windows.hpp" +#include "os_thread_manager.hpp" + +namespace ams::os::impl { + + namespace { + + constexpr size_t DefaultStackSize = 1_MB; + + class ScopedSaveLastError { + NON_COPYABLE(ScopedSaveLastError); + NON_MOVEABLE(ScopedSaveLastError); + private: + DWORD m_last_error; + public: + ALWAYS_INLINE ScopedSaveLastError() : m_last_error(::GetLastError()) { /* ... */ } + ALWAYS_INLINE ~ScopedSaveLastError() { ::SetLastError(m_last_error); } + }; + + unsigned __stdcall InvokeThread(void *arg) { + ThreadType *thread = static_cast<ThreadType *>(arg); + + /* Invoke the thread. */ + ThreadManager::InvokeThread(thread); + + return 0; + } + + os::NativeHandle DuplicateThreadHandle(os::NativeHandle thread_handle) { + /* Get the thread's windows handle. */ + os::NativeHandle windows_handle = os::InvalidNativeHandle; + AMS_ABORT_UNLESS(::DuplicateHandle(::GetCurrentProcess(), thread_handle, ::GetCurrentProcess(), std::addressof(windows_handle), 0, 0, DUPLICATE_SAME_ACCESS)); + + return windows_handle; + } + + os::ThreadType *DynamicAllocateAndRegisterThreadType() { + /* Get the thread manager. */ + auto &thread_manager = GetThreadManager(); + + /* Allocate a thread. */ + auto *thread = thread_manager.AllocateThreadType(); + AMS_ABORT_UNLESS(thread != nullptr); + + /* Setup the thread object. */ + SetupThreadObjectUnsafe(thread, nullptr, nullptr, nullptr, nullptr, 0, DefaultThreadPriority); + thread->state = ThreadType::State_Started; + thread->auto_registered = true; + + /* Set the thread's windows handle. */ + thread->native_handle = DuplicateThreadHandle(::GetCurrentThread()); + thread->ideal_core = ::GetCurrentProcessorNumber(); + thread->affinity_mask = thread_manager.GetThreadAvailableCoreMask(); + + /* Place the object under the thread manager. */ + thread_manager.PlaceThreadObjectUnderThreadManagerSafe(thread); + + return thread; + } + + } + + ThreadManagerWindowsImpl::ThreadManagerWindowsImpl(ThreadType *main_thread) : m_tls_index(::TlsAlloc()) { + /* Verify tls index is valid. */ + AMS_ASSERT(m_tls_index != TLS_OUT_OF_INDEXES); + + /* Setup the main thread object. */ + SetupThreadObjectUnsafe(main_thread, nullptr, nullptr, nullptr, nullptr, DefaultStackSize, DefaultThreadPriority); + + + /* Setup the main thread's windows handle information. */ + main_thread->native_handle = DuplicateThreadHandle(::GetCurrentThread()); + main_thread->ideal_core = ::GetCurrentProcessorNumber(); + main_thread->affinity_mask = this->GetThreadAvailableCoreMask(); + } + + Result ThreadManagerWindowsImpl::CreateThread(ThreadType *thread, s32 ideal_core) { + /* Create the thread. */ + os::NativeHandle thread_handle = reinterpret_cast<os::NativeHandle>(_beginthreadex(nullptr, thread->stack_size, &InvokeThread, thread, CREATE_SUSPENDED, 0)); + AMS_ASSERT(thread_handle != os::InvalidNativeHandle); + + /* Set the thread's windows handle information. */ + thread->native_handle = thread_handle; + thread->ideal_core = ideal_core; + thread->affinity_mask = this->GetThreadAvailableCoreMask(); + + R_SUCCEED(); + } + + void ThreadManagerWindowsImpl::DestroyThreadUnsafe(ThreadType *thread) { + /* Close the thread's handle. */ + const auto ret = ::CloseHandle(thread->native_handle); + AMS_ASSERT(ret); + AMS_UNUSED(ret); + + thread->native_handle = os::InvalidNativeHandle; + } + + void ThreadManagerWindowsImpl::StartThread(const ThreadType *thread) { + ScopedSaveLastError save_error; + + /* Resume the thread. */ + const auto ret = ::ResumeThread(thread->native_handle); + AMS_ASSERT(ret == 1); + AMS_UNUSED(ret); + } + + void ThreadManagerWindowsImpl::WaitForThreadExit(ThreadType *thread) { + ::WaitForSingleObject(thread->native_handle, INFINITE); + } + + bool ThreadManagerWindowsImpl::TryWaitForThreadExit(ThreadType *thread) { + return ::WaitForSingleObject(thread->native_handle, 0) == 0; + } + + void ThreadManagerWindowsImpl::YieldThread() { + ::Sleep(0); + } + + bool ThreadManagerWindowsImpl::ChangePriority(ThreadType *thread, s32 priority) { + AMS_UNUSED(thread, priority); + return true; + } + + s32 ThreadManagerWindowsImpl::GetCurrentPriority(const ThreadType *thread) const { + return thread->base_priority; + } + + ThreadId ThreadManagerWindowsImpl::GetThreadId(const ThreadType *thread) const { + ScopedSaveLastError save_error; + + const auto thread_id = ::GetThreadId(thread->native_handle); + AMS_ASSERT(thread_id != 0); + + return thread_id; + } + + void ThreadManagerWindowsImpl::SuspendThreadUnsafe(ThreadType *thread) { + ScopedSaveLastError save_error; + + const auto ret = ::SuspendThread(thread->native_handle); + AMS_ASSERT(ret == 0); + AMS_UNUSED(ret); + } + + void ThreadManagerWindowsImpl::ResumeThreadUnsafe(ThreadType *thread) { + ScopedSaveLastError save_error; + + const auto ret = ::ResumeThread(thread->native_handle); + AMS_ASSERT(ret == 1); + AMS_UNUSED(ret); + } + + /* TODO: void ThreadManagerWindowsImpl::GetThreadContextUnsafe(ThreadContextInfo *out_context, const ThreadType *thread); */ + + void ThreadManagerWindowsImpl::NotifyThreadNameChangedImpl(const ThreadType *thread) const { + /* TODO */ + AMS_UNUSED(thread); + } + + void ThreadManagerWindowsImpl::SetCurrentThread(ThreadType *thread) const { + ScopedSaveLastError save_error; + + ::TlsSetValue(m_tls_index, thread); + } + + ThreadType *ThreadManagerWindowsImpl::GetCurrentThread() const { + ScopedSaveLastError save_error; + + /* Get the thread from tls index. */ + ThreadType *thread = static_cast<ThreadType *>(static_cast<void *>(::TlsGetValue(m_tls_index))); + + /* If the thread's TLS isn't set, we need to find it (and set tls) or make it. */ + if (thread == nullptr) { + /* Try to find the thread. */ + thread = GetThreadManager().FindThreadTypeById(::GetCurrentThreadId()); + if (thread == nullptr) { + /* Create the thread. */ + thread = DynamicAllocateAndRegisterThreadType(); + } + + /* Set the thread's TLS. */ + this->SetCurrentThread(thread); + } + + return thread; + } + + s32 ThreadManagerWindowsImpl::GetCurrentCoreNumber() const { + ScopedSaveLastError save_error; + + return ::GetCurrentProcessorNumber(); + } + + void ThreadManagerWindowsImpl::SetThreadCoreMask(ThreadType *thread, s32 ideal_core, u64 affinity_mask) const { + ScopedSaveLastError save_error; + + /* If we should use the default, set the actual ideal core. */ + if (ideal_core == IdealCoreUseDefault) { + affinity_mask = this->GetThreadAvailableCoreMask(); + ideal_core = util::CountTrailingZeros(affinity_mask); + affinity_mask = static_cast<decltype(affinity_mask)>(1) << ideal_core; + } + + /* Lock the thread. */ + std::scoped_lock lk(util::GetReference(thread->cs_thread)); + + /* Set the thread's affinity mask. */ + const auto old = ::SetThreadAffinityMask(thread->native_handle, affinity_mask); + AMS_ABORT_UNLESS(old != 0); + + /* Set the ideal core. */ + if (ideal_core != IdealCoreNoUpdate) { + thread->ideal_core = ideal_core; + } + + /* Set the tracked affinity mask. */ + thread->affinity_mask = affinity_mask; + } + + void ThreadManagerWindowsImpl::GetThreadCoreMask(s32 *out_ideal_core, u64 *out_affinity_mask, const ThreadType *thread) const { + ScopedSaveLastError save_error; + + /* Lock the thread. */ + std::scoped_lock lk(util::GetReference(thread->cs_thread)); + + /* Set the output. */ + if (out_ideal_core != nullptr) { + *out_ideal_core = thread->ideal_core; + } + if (out_affinity_mask != nullptr) { + *out_affinity_mask = thread->affinity_mask; + } + } + + u64 ThreadManagerWindowsImpl::GetThreadAvailableCoreMask() const { + ScopedSaveLastError save_error; + + /* Get the process's affinity mask. */ + u64 process_affinity, system_affinity; + AMS_ABORT_UNLESS(::GetProcessAffinityMask(::GetCurrentProcess(), std::addressof(process_affinity), std::addressof(system_affinity)) != 0); + + return process_affinity; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.os.windows.hpp new file mode 100644 index 00000000..1b47aae2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.os.windows.hpp @@ -0,0 +1,79 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class ThreadManagerWindowsImpl { + NON_COPYABLE(ThreadManagerWindowsImpl); + NON_MOVEABLE(ThreadManagerWindowsImpl); + private: + u32 m_tls_index; + public: + explicit ThreadManagerWindowsImpl(ThreadType *main_thread); + + Result CreateThread(ThreadType *thread, s32 ideal_core); + void DestroyThreadUnsafe(ThreadType *thread); + void StartThread(const ThreadType *thread); + void WaitForThreadExit(ThreadType *thread); + bool TryWaitForThreadExit(ThreadType *thread); + void YieldThread(); + bool ChangePriority(ThreadType *thread, s32 priority); + s32 GetCurrentPriority(const ThreadType *thread) const; + ThreadId GetThreadId(const ThreadType *thread) const; + + void SuspendThreadUnsafe(ThreadType *thread); + void ResumeThreadUnsafe(ThreadType *thread); + + /* TODO: void GetThreadContextUnsafe(ThreadContextInfo *out_context, const ThreadType *thread); */ + + void NotifyThreadNameChangedImpl(const ThreadType *thread) const; + + void SetCurrentThread(ThreadType *thread) const; + + ThreadType *GetCurrentThread() const; + + s32 GetCurrentCoreNumber() const; + s32 GetDefaultCoreNumber() const { return 0; } + + void SetThreadCoreMask(ThreadType *thread, s32 ideal_core, u64 affinity_mask) const; + void GetThreadCoreMask(s32 *out_ideal_core, u64 *out_affinity_mask, const ThreadType *thread) const; + u64 GetThreadAvailableCoreMask() const; + + bool MapAliasStack(void **out, void *stack, size_t size) { + AMS_UNUSED(stack, size); + *out = nullptr; + return true; + } + + bool UnmapAliasStack(void *alias_stack, void *original_stack, size_t size) { + AMS_UNUSED(alias_stack, original_stack, size); + return true; + } + + NORETURN void ExitProcessImpl() { + AMS_ABORT("TODO: Just exit?"); + } + + NORETURN void QuickExit() { + AMS_ABORT("TODO: Just exit?"); + } + }; + + using ThreadManagerImpl = ThreadManagerWindowsImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.pthread.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.pthread.cpp new file mode 100644 index 00000000..827eab09 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.pthread.cpp @@ -0,0 +1,20 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#if defined(AMS_OS_IMPL_USE_PTHREADS) + #include "os_thread_manager_impl.pthread.inc" +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.pthread.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.pthread.hpp new file mode 100644 index 00000000..dfc867cb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.pthread.hpp @@ -0,0 +1,79 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class ThreadManagerPthreadImpl { + NON_COPYABLE(ThreadManagerPthreadImpl); + NON_MOVEABLE(ThreadManagerPthreadImpl); + private: + pthread_key_t m_tls_key; + public: + explicit ThreadManagerPthreadImpl(ThreadType *main_thread); + + Result CreateThread(ThreadType *thread, s32 ideal_core); + void DestroyThreadUnsafe(ThreadType *thread); + void StartThread(const ThreadType *thread); + void WaitForThreadExit(ThreadType *thread); + bool TryWaitForThreadExit(ThreadType *thread); + void YieldThread(); + bool ChangePriority(ThreadType *thread, s32 priority); + s32 GetCurrentPriority(const ThreadType *thread) const; + ThreadId GetThreadId(const ThreadType *thread) const; + + void SuspendThreadUnsafe(ThreadType *thread); + void ResumeThreadUnsafe(ThreadType *thread); + + /* TODO: void GetThreadContextUnsafe(ThreadContextInfo *out_context, const ThreadType *thread); */ + + void NotifyThreadNameChangedImpl(const ThreadType *thread) const; + + void SetCurrentThread(ThreadType *thread) const; + + ThreadType *GetCurrentThread() const; + + s32 GetCurrentCoreNumber() const; + s32 GetDefaultCoreNumber() const { return 0; } + + void SetThreadCoreMask(ThreadType *thread, s32 ideal_core, u64 affinity_mask) const; + void GetThreadCoreMask(s32 *out_ideal_core, u64 *out_affinity_mask, const ThreadType *thread) const; + u64 GetThreadAvailableCoreMask() const; + + bool MapAliasStack(void **out, void *stack, size_t size) { + AMS_UNUSED(stack, size); + *out = nullptr; + return true; + } + + bool UnmapAliasStack(void *alias_stack, void *original_stack, size_t size) { + AMS_UNUSED(alias_stack, original_stack, size); + return true; + } + + NORETURN void ExitProcessImpl() { + AMS_ABORT("TODO: Just exit?"); + } + + NORETURN void QuickExit() { + AMS_ABORT("TODO: Just exit?"); + } + }; + + using ThreadManagerImpl = ThreadManagerPthreadImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.pthread.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.pthread.inc new file mode 100644 index 00000000..afbb4495 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_impl.pthread.inc @@ -0,0 +1,365 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_thread_manager_impl.pthread.hpp" +#include "os_thread_manager.hpp" + +#include <pthread.h> + +#if defined(ATMOSPHERE_OS_LINUX) +#include <sched.h> +#elif defined(ATMOSPHERE_OS_MACOS) +#include <sys/types.h> +#include <sys/sysctl.h> +#include <mach/mach.h> + +namespace { + + struct cpu_set_t { + uint64_t affinity_mask; + }; + + ALWAYS_INLINE void CPU_ZERO(cpu_set_t *cs) { cs->affinity_mask = 0; } + ALWAYS_INLINE void CPU_SET(int core, cpu_set_t *cs) { cs->affinity_mask |= (UINT64_C(1) << core); } + ALWAYS_INLINE bool CPU_ISSET(int core, cpu_set_t *cs) { return cs->affinity_mask & (UINT64_C(1) << core); } + + constexpr size_t CPU_SETSIZE = BITSIZEOF(cpu_set_t{}.affinity_mask); + + int sched_getaffinity(pid_t pid, size_t cpu_size, cpu_set_t *cs) { + /* Ignore the process id/cpu size arguments. */ + static_cast<void>(pid); + static_cast<void>(cpu_size); + + /* Get the core count. */ + int32_t core_count = 0; + size_t size = sizeof(core_count); + if (const auto ret = ::sysctlbyname("machdep.cpu.core_count", std::addressof(core_count), std::addressof(size), 0, 0); ret != 0) { + return ret; + } + + /* Set our cpu set structure. */ + cs->affinity_mask = 0; + for (auto i = 0; i < core_count; ++i) { + cs->affinity_mask |= (UINT64_C(1) << i); + } + + return 0; + } + + int pthread_setaffinity_np(pthread_t thread, size_t cpu_size, cpu_set_t *cs) { + /* Ignore the cpu size argument. */ + static_cast<void>(cpu_size); + + /* If the thread is allowed to be on more than one core, we'll ignore it. */ + /* TODO: Do this properly? */ + if (const auto pc = std::popcount(cs->affinity_mask); pc == 0 || pc > 1) { + return 0; + } + + /* Create policy to bind to the core. */ + thread_affinity_policy_data_t policy = { std::countr_zero(cs->affinity_mask) }; + + /* Get the underlying mach thread. */ + thread_port_t mach_thread = pthread_mach_thread_np(thread); + + /* Set the policy. */ + thread_policy_set(mach_thread, THREAD_AFFINITY_POLICY, reinterpret_cast<thread_policy_t>(std::addressof(policy)), 1); + + return 0; + } + +} +#else +#error "Unknown OS for pthread CoreId get" +#endif + +namespace ams::os::impl { + + namespace { + + constexpr size_t DefaultStackSize = 1_MB; + + void *InvokeThread(void *arg) { + ThreadType *thread = static_cast<ThreadType *>(arg); + + /* Invoke the thread. */ + ThreadManager::InvokeThread(thread); + + /* Set exit state. */ + { + std::scoped_lock lk(util::GetReference(thread->cs_pthread_exit)); + AMS_ASSERT(thread->exited_pthread == false); + + thread->exited_pthread = true; + util::GetReference(thread->cv_pthread_exit).Broadcast(); + } + + return nullptr; + } + + os::ThreadType *DynamicAllocateAndRegisterThreadType() { + /* Get the thread manager. */ + auto &thread_manager = GetThreadManager(); + + /* Allocate a thread. */ + auto *thread = thread_manager.AllocateThreadType(); + AMS_ABORT_UNLESS(thread != nullptr); + + /* Setup the thread object. */ + SetupThreadObjectUnsafe(thread, nullptr, nullptr, nullptr, nullptr, 0, DefaultThreadPriority); + thread->state = ThreadType::State_Started; + thread->auto_registered = true; + + /* Set the thread's pthread handle. */ + thread->pthread = pthread_self(); + thread->ideal_core = thread_manager.GetCurrentCoreNumber(); + thread->affinity_mask = thread_manager.GetThreadAvailableCoreMask(); + + /* Place the object under the thread manager. */ + thread_manager.PlaceThreadObjectUnderThreadManagerSafe(thread); + + return thread; + } + + } + + ThreadManagerPthreadImpl::ThreadManagerPthreadImpl(ThreadType *main_thread) { + /* Create tls slot for thread pointer. */ + AMS_ABORT_UNLESS(pthread_key_create(std::addressof(m_tls_key), nullptr) == 0); + + /* Setup the main thread object. */ + SetupThreadObjectUnsafe(main_thread, nullptr, nullptr, nullptr, nullptr, DefaultStackSize, DefaultThreadPriority); + + /* Setup the main thread's pthread information. */ + main_thread->pthread = pthread_self(); + main_thread->ideal_core = this->GetCurrentCoreNumber(); + main_thread->affinity_mask = this->GetThreadAvailableCoreMask(); + } + + Result ThreadManagerPthreadImpl::CreateThread(ThreadType *thread, s32 ideal_core) { + /* Create the assert. */ + /* TODO: Check for failure properly. */ + pthread_t pthread; + const auto res = pthread_create(std::addressof(pthread), nullptr, &InvokeThread, thread); + AMS_ASSERT(res == 0); + AMS_UNUSED(res); + + /* Set the thread's pthread handle information. */ + thread->pthread = pthread; + thread->ideal_core = ideal_core; + thread->affinity_mask = this->GetThreadAvailableCoreMask(); + + R_SUCCEED(); + } + + void ThreadManagerPthreadImpl::DestroyThreadUnsafe(ThreadType *thread) { + /* The thread must have exited. */ + { + std::scoped_lock lk(util::GetReference(thread->cs_pthread_exit)); + AMS_ABORT_UNLESS(thread->exited_pthread); + } + + /* Join the thread. */ + const auto ret = pthread_join(thread->pthread, nullptr); + AMS_ASSERT(ret == 0); + AMS_UNUSED(ret); + } + + void ThreadManagerPthreadImpl::StartThread(const ThreadType *thread) { + /* Nothing is actually needed here, because pthreads cannot start suspended. */ + /* TODO: Should we add a condvar/mutex for thread start? */ + AMS_UNUSED(thread); + } + + void ThreadManagerPthreadImpl::WaitForThreadExit(ThreadType *thread) { + /* Wait for the thread to exit. */ + { + std::scoped_lock lk(util::GetReference(thread->cs_pthread_exit)); + while (!thread->exited_pthread) { + util::GetReference(thread->cv_pthread_exit).Wait(util::GetPointer(thread->cs_pthread_exit)); + } + } + } + + bool ThreadManagerPthreadImpl::TryWaitForThreadExit(ThreadType *thread) { + /* Check if the thread has exited. */ + std::scoped_lock lk(util::GetReference(thread->cs_pthread_exit)); + + return thread->exited_pthread; + } + + void ThreadManagerPthreadImpl::YieldThread() { + /* NOTE: pthread_yield() is deprecated. */ + const auto ret = sched_yield(); + AMS_ASSERT(ret == 0); + AMS_UNUSED(ret); + } + + bool ThreadManagerPthreadImpl::ChangePriority(ThreadType *thread, s32 priority) { + /* TODO: Should we set the thread's niceness value? */ + AMS_UNUSED(thread, priority); + return true; + } + + s32 ThreadManagerPthreadImpl::GetCurrentPriority(const ThreadType *thread) const { + return thread->base_priority; + } + + ThreadId ThreadManagerPthreadImpl::GetThreadId(const ThreadType *thread) const { + #if defined(AMS_OS_IMPL_USE_PTHREADID_NP_FOR_THREAD_ID) + ThreadId tid; + const auto ret = pthread_threadid_np(thread->pthread, std::addressof(tid)); + AMS_ABORT_UNLESS(ret == 0); + + return tid; + #else + return thread->pthread; + #endif + } + + void ThreadManagerPthreadImpl::SuspendThreadUnsafe(ThreadType *thread) { + AMS_UNUSED(thread); + AMS_ABORT("TODO: Linux SuspendThread Signal/Pause impl?"); + } + + void ThreadManagerPthreadImpl::ResumeThreadUnsafe(ThreadType *thread) { + AMS_UNUSED(thread); + AMS_ABORT("TODO: ResumeThread Signal/Pause impl?"); + } + + void ThreadManagerPthreadImpl::NotifyThreadNameChangedImpl(const ThreadType *thread) const { + /* TODO */ + AMS_UNUSED(thread); + } + + void ThreadManagerPthreadImpl::SetCurrentThread(ThreadType *thread) const { + const auto ret = pthread_setspecific(m_tls_key, thread); + AMS_ASSERT(ret == 0); + AMS_UNUSED(ret); + } + + ThreadType *ThreadManagerPthreadImpl::GetCurrentThread() const { + /* Get the thread from tls index. */ + ThreadType *thread = static_cast<ThreadType *>(pthread_getspecific(m_tls_key)); + + /* If the thread's TLS isn't set, we need to find it (and set tls) or make it. */ + if (thread == nullptr) { + /* Get the current thread id. */ + ThreadId self_tid; + pthread_t self_thread = pthread_self(); + #if defined(AMS_OS_IMPL_USE_PTHREADID_NP_FOR_THREAD_ID) + const auto ret = pthread_threadid_np(self_thread, std::addressof(self_tid)); + AMS_ABORT_UNLESS(ret == 0); + #else + self_tid = self_thread; + #endif + + /* Try to find the thread. */ + thread = GetThreadManager().FindThreadTypeById(self_tid); + if (thread == nullptr) { + /* Create the thread. */ + thread = DynamicAllocateAndRegisterThreadType(); + } + + /* Set the thread's TLS. */ + this->SetCurrentThread(thread); + } + + return thread; + } + + s32 ThreadManagerPthreadImpl::GetCurrentCoreNumber() const { + #if defined(ATMOSPHERE_OS_LINUX) + const auto core = sched_getcpu(); + AMS_ABORT_UNLESS(core >= 0); + return core; + #elif defined(ATMOSPHERE_OS_MACOS) + return 0; + #else + AMS_ABORT("TODO: Unknown OS GetCurrentCoreNumber() under pthreads"); + #endif + } + + void ThreadManagerPthreadImpl::SetThreadCoreMask(ThreadType *thread, s32 ideal_core, u64 affinity_mask) const { + /* If we should use the default, set the actual ideal core. */ + if (ideal_core == IdealCoreUseDefault) { + affinity_mask = this->GetThreadAvailableCoreMask(); + ideal_core = util::CountTrailingZeros(affinity_mask); + affinity_mask = static_cast<decltype(affinity_mask)>(1) << ideal_core; + } + + /* Lock the thread. */ + std::scoped_lock lk(util::GetReference(thread->cs_thread)); + + /* Build the cpu affinity. */ + cpu_set_t cpuset; + CPU_ZERO(std::addressof(cpuset)); + for (size_t i = 0; i < std::min<size_t>(BITSIZEOF(affinity_mask), CPU_SETSIZE); ++i) { + if ((static_cast<decltype(affinity_mask)>(1) << i) & affinity_mask) { + CPU_SET(i, std::addressof(cpuset)); + } + } + + /* Set the cpu affinity. */ + const auto ret = pthread_setaffinity_np(thread->pthread, sizeof(cpuset), std::addressof(cpuset)); + AMS_ABORT_UNLESS(ret == 0); + + /* Set the ideal core. */ + if (ideal_core != IdealCoreNoUpdate) { + thread->ideal_core = ideal_core; + } + + /* Set the tracked affinity mask. */ + thread->affinity_mask = affinity_mask; + } + + void ThreadManagerPthreadImpl::GetThreadCoreMask(s32 *out_ideal_core, u64 *out_affinity_mask, const ThreadType *thread) const { + /* Lock the thread. */ + std::scoped_lock lk(util::GetReference(thread->cs_thread)); + + /* Set the output. */ + if (out_ideal_core != nullptr) { + *out_ideal_core = thread->ideal_core; + } + if (out_affinity_mask != nullptr) { + *out_affinity_mask = thread->affinity_mask; + } + } + + u64 ThreadManagerPthreadImpl::GetThreadAvailableCoreMask() const { + #if defined(ATMOSPHERE_OS_LINUX) || defined(ATMOSPHERE_OS_MACOS) + cpu_set_t cpuset; + CPU_ZERO(std::addressof(cpuset)); + + const auto ret = sched_getaffinity(0, sizeof(cpuset), std::addressof(cpuset)); + AMS_ASSERT(ret == 0); + AMS_UNUSED(ret); + + u64 mask = 0; + for (size_t i = 0; i < std::min<size_t>(BITSIZEOF(mask), CPU_SETSIZE); ++i) { + if (CPU_ISSET(i, std::addressof(cpuset))) { + mask |= static_cast<decltype(mask)>(1) << i; + } + } + + AMS_ASSERT(mask != 0); + return mask; + #else + AMS_ABORT("TODO: Unknown OS GetThreadAvailableCoreMask() under pthreads"); + #endif + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_types.hpp new file mode 100644 index 00000000..72306673 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_thread_manager_types.hpp @@ -0,0 +1,177 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#if defined(AMS_OS_IMPL_USE_PTHREADS) + #include "os_thread_manager_impl.pthread.hpp" +#elif defined(ATMOSPHERE_OS_HORIZON) + #include "os_thread_manager_impl.os.horizon.hpp" +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include "os_thread_manager_impl.os.windows.hpp" +#else + #error "Unknown OS for ThreadManagerImpl" +#endif + +namespace ams::os::impl { + + class ThreadManager { + NON_COPYABLE(ThreadManager); + NON_MOVEABLE(ThreadManager); + private: + class ThreadListTraits { + public: + using ListType = util::IntrusiveList<ThreadType, ThreadListTraits>; + private: + friend class util::IntrusiveList<ThreadType, ThreadListTraits>; + + static util::IntrusiveListNode &GetNode(ThreadType &parent) { + return GetReference(parent.all_threads_node); + } + + static util::IntrusiveListNode const &GetNode(ThreadType const &parent) { + return GetReference(parent.all_threads_node); + } + + static constexpr size_t Offset = AMS_OFFSETOF(ThreadType, all_threads_node); + + static ThreadType &GetParent(util::IntrusiveListNode &node) { + return *reinterpret_cast<ThreadType *>(reinterpret_cast<char *>(std::addressof(node)) - Offset); + } + + static ThreadType const &GetParent(util::IntrusiveListNode const &node) { + return *reinterpret_cast<const ThreadType *>(reinterpret_cast<const char *>(std::addressof(node)) - Offset); + } + }; + + using AllThreadsList = ThreadListTraits::ListType; + private: + ThreadManagerImpl m_impl; + ThreadType m_main_thread; + InternalCriticalSection m_cs; + AllThreadsList m_all_threads_list; + size_t m_total_thread_stack_size; + s32 m_num_created_threads; + public: + ThreadManager(); + + void CleanupThread(); + void CleanupThread(ThreadType *thread); + s32 GetThreadCountForDebug() const { return m_num_created_threads; } + + Result CreateThread(ThreadType *thread, ThreadFunction function, void *argument, void *stack, size_t stack_size, s32 priority, s32 ideal_core); + Result CreateThread(ThreadType *thread, ThreadFunction function, void *argument, void *stack, size_t stack_size, s32 priority); + + void DestroyThread(ThreadType *thread); + void DestroyThreadObject(ThreadType *thread); + void StartThread(ThreadType *thread); + void WaitThread(ThreadType *thread); + bool TryWaitThread(ThreadType *thread); + + void YieldThread() { return m_impl.YieldThread(); } + + bool ChangePriority(ThreadType *thread, s32 priority) { return m_impl.ChangePriority(thread, priority); } + s32 GetCurrentPriority(const ThreadType *thread) const { return m_impl.GetCurrentPriority(thread); } + ThreadType *GetCurrentThread() const { return m_impl.GetCurrentThread(); } + + s32 SuspendThread(ThreadType *thread); + s32 ResumeThread(ThreadType *thread); + + void ExitProcess() { return m_impl.ExitProcessImpl(); } + void QuickExit() { return m_impl.QuickExit(); } + /* TODO void GetThreadContext(ThreadContextInfo *out_context, const ThreadType *thread); */ + + void SetInitialThreadNameUnsafe(ThreadType *thread); + + void NotifyThreadNameChanged(const ThreadType *thread) const { return m_impl.NotifyThreadNameChangedImpl(thread); } + void SetCurrentThread(ThreadType *thread) const { return m_impl.SetCurrentThread(thread); } + s32 GetCurrentCoreNumber() const { return m_impl.GetCurrentCoreNumber(); } + void SetThreadCoreMask(ThreadType *thread, s32 ideal_core, u64 affinity_mask) const { return m_impl.SetThreadCoreMask(thread, ideal_core, affinity_mask); } + void GetThreadCoreMask(s32 *out_ideal_core, u64 *out_affinity_mask, const ThreadType *thread) const { return m_impl.GetThreadCoreMask(out_ideal_core, out_affinity_mask, thread); } + u64 GetThreadAvailableCoreMask() const { return m_impl.GetThreadAvailableCoreMask(); } + + void SetZeroToAllThreadsTlsSafe(int slot); + + void PushBackToAllThreadsListUnsafe(ThreadType *thread) { + m_all_threads_list.push_back(*thread); + ++m_num_created_threads; + m_total_thread_stack_size += thread->stack_size; + } + + void EraseFromAllThreadsListUnsafe(ThreadType *thread) { + m_all_threads_list.erase(m_all_threads_list.iterator_to(*thread)); + --m_num_created_threads; + m_total_thread_stack_size -= thread->stack_size; + } + + void PushBackToAllThreadsListSafe(ThreadType *thread) { + std::scoped_lock lk(m_cs); + this->PushBackToAllThreadsListUnsafe(thread); + } + + void EraseFromAllThreadsListSafe(ThreadType *thread) { + std::scoped_lock lk(m_cs); + this->EraseFromAllThreadsListUnsafe(thread); + } + + void PlaceThreadObjectUnderThreadManagerSafe(ThreadType *thread) { + SetInitialThreadNameUnsafe(thread); + { + std::scoped_lock lk(m_cs); + this->PushBackToAllThreadsListUnsafe(thread); + } + } + + ThreadType *AllocateThreadType() const { + return static_cast<ThreadType *>(ams::Malloc(sizeof(ThreadType))); + } + + void FreeThreadType(ThreadType *thread) const { + ams::Free(thread); + } + + const ThreadType *GetMainThread() const { + return std::addressof(m_main_thread); + } + + size_t GetTotalThreadStackSize() const { + return m_total_thread_stack_size; + } + + ThreadId GetThreadId(const ThreadType *thread) { + return m_impl.GetThreadId(thread); + } + + ThreadType *FindThreadTypeById(ThreadId id) { + std::scoped_lock lk(m_cs); + + for (auto rit = m_all_threads_list.rbegin(); rit != m_all_threads_list.rend(); ++rit) { + auto * const thread = std::addressof(*rit); + if (this->GetThreadId(thread) == id) { + return thread; + } + } + + return nullptr; + } + private: + bool CreateAliasStackUnsafe(ThreadType *thread); + void DeleteAliasStackUnsafe(ThreadType *thread); + public: + static void InvokeThread(ThreadType *thread); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tick_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tick_manager.hpp new file mode 100644 index 00000000..d368897a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tick_manager.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_resource_manager.hpp" + +namespace ams::os::impl { + + ALWAYS_INLINE TickManager &GetTickManager() { + return GetResourceManager().GetTickManager(); + } + + ALWAYS_INLINE Tick GetCurrentTick() { + return GetTickManager().GetTick(); + } + + ALWAYS_INLINE Tick GetCurrentTickOrdered() { + return GetTickManager().GetSystemTickOrdered(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tick_manager_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tick_manager_impl.cpp new file mode 100644 index 00000000..dc198b6c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tick_manager_impl.cpp @@ -0,0 +1,85 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_tick_manager.hpp" + +namespace ams::os::impl { + + TimeSpan TickManager::ConvertToTimeSpan(Tick tick) const { + /* Get the tick value. */ + const s64 tick_val = tick.GetInt64Value(); + + /* Get the tick frequency. */ + const s64 tick_freq = GetTickFrequency(); + AMS_AUDIT(tick_freq < MaxTickFrequency); + + /* Clamp tick to range. */ + if (tick_val > GetMaxTick()) { + return TimeSpan::FromNanoSeconds(std::numeric_limits<s64>::max()); + } else if (tick_val < -GetMaxTick()) { + return TimeSpan::FromNanoSeconds(std::numeric_limits<s64>::min()); + } else { + /* Convert to timespan. */ + constexpr s64 NanoSecondsPerSecond = TimeSpan::FromSeconds(1).GetNanoSeconds(); + const s64 seconds = tick_val / tick_freq; + const s64 frac = tick_val % tick_freq; + const TimeSpan ts = TimeSpan::FromSeconds(seconds) + TimeSpan::FromNanoSeconds(frac * NanoSecondsPerSecond / tick_freq); + + constexpr TimeSpan ZeroTS = TimeSpan::FromNanoSeconds(0); + AMS_ASSERT(!((tick_val > 0 && ts < ZeroTS) || (tick_val < 0 && ts > ZeroTS))); + AMS_UNUSED(ZeroTS); + + return ts; + } + } + + Tick TickManager::ConvertToTick(TimeSpan ts) const { + /* Get the TimeSpan in nanoseconds. */ + const s64 ns = ts.GetNanoSeconds(); + + /* Clamp ns to range. */ + if (ns > GetMaxTimeSpanNs()) { + return Tick(std::numeric_limits<s64>::max()); + } else if (ns < -GetMaxTimeSpanNs()) { + return Tick(std::numeric_limits<s64>::min()); + } else { + /* Get the tick frequency. */ + const s64 tick_freq = GetTickFrequency(); + AMS_AUDIT(tick_freq < MaxTickFrequency); + + /* Convert to tick. */ + constexpr s64 NanoSecondsPerSecond = TimeSpan::FromSeconds(1).GetNanoSeconds(); + const bool negative = ns < 0; + s64 seconds = ns / NanoSecondsPerSecond; + s64 frac = ns % NanoSecondsPerSecond; + + /* If negative, negate seconds/frac. */ + if (negative) { + seconds = -seconds; + frac = -frac; + } + + /* Calculate the tick, and invert back to negative if needed. */ + s64 tick = (seconds * tick_freq) + ((frac * tick_freq + NanoSecondsPerSecond - 1) / NanoSecondsPerSecond); + if (negative) { + tick = -tick; + } + + return Tick(tick); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tick_manager_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tick_manager_impl.hpp new file mode 100644 index 00000000..558f5a4e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tick_manager_impl.hpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "os_tick_manager_impl.os.horizon.hpp" +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include "os_tick_manager_impl.os.windows.hpp" +#elif defined(ATMOSPHERE_OS_LINUX) + #include "os_tick_manager_impl.std_chrono.hpp" +#elif defined(ATMOSPHERE_OS_MACOS) + #include "os_tick_manager_impl.std_chrono.hpp" +#else + #error "Unknown OS for TickManagerImpl" +#endif + +namespace ams::os::impl { + + /* Tick frequency must be less than INT64_MAX / 1 second. */ + static constexpr s64 MaxTickFrequency = (std::numeric_limits<s64>::max() / TimeSpan::FromSeconds(1).GetNanoSeconds()) - 1; + + class TickManager { + private: + TickManagerImpl m_impl; + public: + #if defined(ATMOSPHERE_OS_HORIZON) + constexpr TickManager() : m_impl() { /* ... */ } + #else + TickManager() : m_impl() { /* ... */ } + #endif + + ALWAYS_INLINE Tick GetTick() const { + return m_impl.GetTick(); + } + + ALWAYS_INLINE Tick GetSystemTickOrdered() const { + return m_impl.GetSystemTickOrdered(); + } + + ALWAYS_INLINE s64 GetTickFrequency() const { + return m_impl.GetTickFrequency(); + } + + ALWAYS_INLINE s64 GetMaxTick() const { + return m_impl.GetMaxTick(); + } + + ALWAYS_INLINE s64 GetMaxTimeSpanNs() const { + return m_impl.GetMaxTimeSpanNs(); + } + + TimeSpan ConvertToTimeSpan(Tick tick) const; + Tick ConvertToTick(TimeSpan ts) const; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tick_manager_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tick_manager_impl.os.horizon.hpp new file mode 100644 index 00000000..5933db70 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tick_manager_impl.os.horizon.hpp @@ -0,0 +1,66 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class TickManagerImpl { + public: + constexpr TickManagerImpl() { /* ... */ } + + ALWAYS_INLINE Tick GetTick() const { + s64 tick; + #if defined(ATMOSPHERE_ARCH_ARM64) + __asm__ __volatile__("mrs %[tick], cntpct_el0" : [tick]"=&r"(tick) :: "memory"); + #else + #error "Unknown Architecture for TickManagerImpl::GetTick" + #endif + return Tick(tick); + } + + ALWAYS_INLINE Tick GetSystemTickOrdered() const { + s64 tick; + #if defined(ATMOSPHERE_ARCH_ARM64) + __asm__ __volatile__("dsb ish\n" + "isb\n" + "mrs %[tick], cntpct_el0\n" + "isb" + : [tick]"=&r"(tick) + : + : "memory"); + #else + #error "Unknown Architecture for TickManagerImpl::GetSystemTickOrdered" + #endif + return Tick(tick); + } + + static constexpr ALWAYS_INLINE s64 GetTickFrequency() { + return static_cast<s64>(::ams::svc::TicksPerSecond); + } + + static constexpr ALWAYS_INLINE s64 GetMaxTick() { + static_assert(GetTickFrequency() <= TimeSpan::FromSeconds(1).GetNanoSeconds()); + return (std::numeric_limits<s64>::max() / TimeSpan::FromSeconds(1).GetNanoSeconds()) * GetTickFrequency(); + } + + static constexpr ALWAYS_INLINE s64 GetMaxTimeSpanNs() { + static_assert(GetTickFrequency() <= TimeSpan::FromSeconds(1).GetNanoSeconds()); + return TimeSpan::FromNanoSeconds(std::numeric_limits<s64>::max()).GetNanoSeconds(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tick_manager_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tick_manager_impl.os.windows.hpp new file mode 100644 index 00000000..88cccf0c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tick_manager_impl.os.windows.hpp @@ -0,0 +1,90 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_OS_WINDOWS) +#include <stratosphere/windows.hpp> +#endif + +#include <mmsystem.h> + +namespace ams::os::impl { + + class TickManagerImpl { + private: + s64 m_tick_frequency; + TimeSpan m_max_time; + s64 m_max_tick; + public: + TickManagerImpl() { + /* Get the tick frequency. */ + ::timeBeginPeriod(1); + + LARGE_INTEGER freq; + ::QueryPerformanceFrequency(std::addressof(freq)); + m_tick_frequency = static_cast<s64>(freq.QuadPart); + + /* Set maximums. */ + constexpr s64 TickFrequencyForNanoSecondResolution = TimeSpan::FromSeconds(1).GetNanoSeconds(); + if (m_tick_frequency <= TickFrequencyForNanoSecondResolution) { + m_max_tick = m_tick_frequency * (std::numeric_limits<s64>::max() / TickFrequencyForNanoSecondResolution); + m_max_time = TimeSpan::FromNanoSeconds(std::numeric_limits<s64>::max()); + } else { + m_max_tick = std::numeric_limits<s64>::max(); + m_max_time = TimeSpan::FromSeconds(std::numeric_limits<s64>::max() / m_tick_frequency); + } + } + + ~TickManagerImpl() { + ::timeEndPeriod(1); + } + + ALWAYS_INLINE Tick GetTick() const { + LARGE_INTEGER freq; + ::QueryPerformanceCounter(std::addressof(freq)); + return Tick(static_cast<s64>(freq.QuadPart)); + } + + ALWAYS_INLINE Tick GetSystemTickOrdered() const { + LARGE_INTEGER freq; + + PerformOrderingForGetSystemTickOrdered(); + ::QueryPerformanceCounter(std::addressof(freq)); + PerformOrderingForGetSystemTickOrdered(); + + return Tick(static_cast<s64>(freq.QuadPart)); + } + + ALWAYS_INLINE s64 GetTickFrequency() const { + return m_tick_frequency; + } + + ALWAYS_INLINE s64 GetMaxTick() const { + return m_max_tick; + } + + ALWAYS_INLINE s64 GetMaxTimeSpanNs() const { + return m_max_time.GetNanoSeconds(); + } + private: + static ALWAYS_INLINE void PerformOrderingForGetSystemTickOrdered() { + int a = 0, b, c = 0, d; + __asm__ __volatile__("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "0"(a), "2"(c) : "memory"); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tick_manager_impl.std_chrono.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tick_manager_impl.std_chrono.hpp new file mode 100644 index 00000000..950a5815 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tick_manager_impl.std_chrono.hpp @@ -0,0 +1,72 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include <chrono> + +namespace ams::os::impl { + + class TickManagerImpl { + private: + using StandardClock = typename std::conditional<std::chrono::high_resolution_clock::is_steady, std::chrono::high_resolution_clock, std::chrono::steady_clock>::type; + using TimePoint = std::chrono::time_point<StandardClock>; + private: + TimePoint m_start_time; + public: + TickManagerImpl() : m_start_time(StandardClock::now()) { /* ... */ } + + ALWAYS_INLINE Tick GetTick() const { + return Tick(static_cast<s64>((StandardClock::now() - m_start_time).count())); + } + + ALWAYS_INLINE Tick GetSystemTickOrdered() const { + PerformOrderingForGetSystemTickOrdered(true); + ON_SCOPE_EXIT { PerformOrderingForGetSystemTickOrdered(false); }; + + return Tick(static_cast<s64>((StandardClock::now() - m_start_time).count())); + } + + static constexpr ALWAYS_INLINE s64 GetTickFrequency() { + return static_cast<s64>(StandardClock::period::den) / static_cast<s64>(StandardClock::period::num); + } + + static constexpr ALWAYS_INLINE s64 GetMaxTick() { + static_assert(GetTickFrequency() <= TimeSpan::FromSeconds(1).GetNanoSeconds()); + return (std::numeric_limits<s64>::max() / TimeSpan::FromSeconds(1).GetNanoSeconds()) * GetTickFrequency(); + } + + static constexpr ALWAYS_INLINE s64 GetMaxTimeSpanNs() { + static_assert(GetTickFrequency() <= TimeSpan::FromSeconds(1).GetNanoSeconds()); + return TimeSpan::FromNanoSeconds(std::numeric_limits<s64>::max()).GetNanoSeconds(); + } + private: + static ALWAYS_INLINE void PerformOrderingForGetSystemTickOrdered(bool before) { + #if defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + int a = 0, b, c = 0, d; + __asm__ __volatile__("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "0"(a), "2"(c) : "memory"); + AMS_UNUSED(before); + #elif defined(ATMOSPHERE_ARCH_ARM64) + if (before) { + __asm__ __volatile__("dsb ish" ::: "memory"); + } + __asm__ __volatile__("isb" ::: "memory"); + #else + #error "Unknown architecture for std::chrono TickManager ordering." + #endif + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper.cpp new file mode 100644 index 00000000..c26a67be --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper.cpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_timeout_helper.hpp" + +namespace ams::os::impl { + + TargetTimeSpan TimeoutHelper::GetTimeLeftOnTarget() const { + /* If the absolute tick is zero, we're expired. */ + if (m_absolute_end_tick.GetInt64Value() == 0) { + return 0; + } + + /* Check if we've expired. */ + const Tick cur_tick = impl::GetTickManager().GetTick(); + if (cur_tick >= m_absolute_end_tick) { + return 0; + } + + /* Return the converted difference as a timespan. */ + return TimeoutHelperImpl::ConvertToImplTime(m_absolute_end_tick - cur_tick); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper.hpp new file mode 100644 index 00000000..75613495 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper.hpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_tick_manager.hpp" + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "os_timeout_helper_impl.os.horizon.hpp" +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include "os_timeout_helper_impl.os.windows.hpp" +#elif defined(ATMOSPHERE_OS_LINUX) + #include "os_timeout_helper_impl.os.linux.hpp" +#elif defined(ATMOSPHERE_OS_MACOS) + #include "os_timeout_helper_impl.os.macos.hpp" +#else + #error "Unknown OS for ams::os::TimeoutHelper" +#endif + +namespace ams::os::impl { + + class TimeoutHelper { + private: + Tick m_absolute_end_tick; + public: + explicit TimeoutHelper(TimeSpan timeout) { + if (timeout == 0) { + /* If timeout is zero, don't do relative tick calculations. */ + m_absolute_end_tick = Tick(0); + } else { + const auto &tick_manager = impl::GetTickManager(); + + const u64 cur_tick = tick_manager.GetTick().GetInt64Value(); + const u64 timeout_tick = tick_manager.ConvertToTick(timeout).GetInt64Value(); + const u64 end_tick = cur_tick + timeout_tick + 1; + + m_absolute_end_tick = Tick(std::min<u64>(std::numeric_limits<s64>::max(), end_tick)); + } + } + + static void Sleep(TimeSpan tm) { + TimeoutHelperImpl::Sleep(tm); + } + + bool TimedOut() const { + if (m_absolute_end_tick.GetInt64Value() == 0) { + return true; + } + + const Tick cur_tick = impl::GetTickManager().GetTick(); + + return cur_tick >= m_absolute_end_tick; + } + + TargetTimeSpan GetTimeLeftOnTarget() const; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.horizon.cpp new file mode 100644 index 00000000..4f37393d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.horizon.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_timeout_helper_impl.os.horizon.hpp" +#include "os_thread_manager.hpp" + +namespace ams::os::impl { + + void TimeoutHelperImpl::Sleep(TimeSpan tm) { + if (tm == TimeSpan(0)) { + GetThreadManager().YieldThread(); + } else { + ams::svc::SleepThread(tm.GetNanoSeconds()); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.horizon.hpp new file mode 100644 index 00000000..05236996 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.horizon.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_tick_manager.hpp" + +namespace ams::os::impl { + + using TargetTimeSpan = ::ams::TimeSpan; + + class TimeoutHelperImpl { + public: + static TargetTimeSpan ConvertToImplTime(Tick tick) { + return impl::GetTickManager().ConvertToTimeSpan(tick); + } + + static void Sleep(TimeSpan tm); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.linux.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.linux.cpp new file mode 100644 index 00000000..5f036bd3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.linux.cpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_timeout_helper_impl.os.linux.hpp" + +#include <time.h> + +namespace ams::os::impl { + + void TimeoutHelperImpl::Sleep(TimeSpan tm) { + /* If asked to sleep for no time, do nothing */ + if (tm == 0) { + return; + } + + constexpr s64 NanoSecondsPerSecond = TimeSpan::FromSeconds(1).GetNanoSeconds(); + const s64 ns = tm.GetNanoSeconds(); + struct timespec ts = { .tv_sec = (ns / NanoSecondsPerSecond), .tv_nsec = ns % NanoSecondsPerSecond }; + + while (true) { + const auto ret = ::nanosleep(std::addressof(ts), std::addressof(ts)); + if (ret == 0) { + break; + } + + AMS_ASSERT(errno == EINTR); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.linux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.linux.hpp new file mode 100644 index 00000000..05236996 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.linux.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_tick_manager.hpp" + +namespace ams::os::impl { + + using TargetTimeSpan = ::ams::TimeSpan; + + class TimeoutHelperImpl { + public: + static TargetTimeSpan ConvertToImplTime(Tick tick) { + return impl::GetTickManager().ConvertToTimeSpan(tick); + } + + static void Sleep(TimeSpan tm); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.macos.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.macos.cpp new file mode 100644 index 00000000..c03b9391 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.macos.cpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_timeout_helper_impl.os.macos.hpp" + +#include <time.h> + +namespace ams::os::impl { + + void TimeoutHelperImpl::Sleep(TimeSpan tm) { + /* If asked to sleep for no time, do nothing */ + if (tm == 0) { + return; + } + + constexpr s64 NanoSecondsPerSecond = TimeSpan::FromSeconds(1).GetNanoSeconds(); + const s64 ns = tm.GetNanoSeconds(); + struct timespec ts = { .tv_sec = (ns / NanoSecondsPerSecond), .tv_nsec = ns % NanoSecondsPerSecond }; + + while (true) { + const auto ret = ::nanosleep(std::addressof(ts), std::addressof(ts)); + if (ret == 0) { + break; + } + + AMS_ASSERT(errno == EINTR); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.macos.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.macos.hpp new file mode 100644 index 00000000..05236996 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.macos.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_tick_manager.hpp" + +namespace ams::os::impl { + + using TargetTimeSpan = ::ams::TimeSpan; + + class TimeoutHelperImpl { + public: + static TargetTimeSpan ConvertToImplTime(Tick tick) { + return impl::GetTickManager().ConvertToTimeSpan(tick); + } + + static void Sleep(TimeSpan tm); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.windows.cpp new file mode 100644 index 00000000..5a666161 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.windows.cpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_timeout_helper_impl.os.windows.hpp" +#include "os_thread_manager.hpp" + +namespace ams::os::impl { + + void TimeoutHelperImpl::Sleep(TimeSpan tm) { + /* If asked to sleep for no time, do nothing */ + if (tm == 0) { + return; + } + + /* Get the end tick. */ + auto &tick_manager = GetTickManager(); + u64 tick_current = tick_manager.GetTick().GetInt64Value(); + u64 tick_timeout = tick_manager.ConvertToTick(tm).GetInt64Value(); + u64 tick_end = tick_current + tick_timeout + 1; + + const auto end = os::Tick(std::min<u64>(std::numeric_limits<s64>::max(), tick_end)); + while (true) { + const auto tick = tick_manager.GetTick(); + if (tick >= end) { + return; + } + + ::Sleep(ConvertToImplTime(end - tick)); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.windows.hpp new file mode 100644 index 00000000..c6b7ce28 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timeout_helper_impl.os.windows.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_tick_manager.hpp" + +namespace ams::os::impl { + + using TargetTimeSpan = DWORD; + + class TimeoutHelperImpl { + public: + static TargetTimeSpan ConvertToImplTime(Tick tick) { + constexpr s64 MaxTime = std::numeric_limits<s64>::max() - TimeSpan::FromMilliSeconds(1).GetNanoSeconds(); + constexpr auto Ratio = TimeSpan::FromMilliSeconds(1); + constexpr auto NanoS = TimeSpan::FromNanoSeconds(1); + + const auto time = TimeSpan::FromNanoSeconds(std::min<s64>(MaxTime, impl::GetTickManager().ConvertToTimeSpan(tick).GetNanoSeconds())); + + return static_cast<TargetTimeSpan>((time + Ratio - NanoS).GetMilliSeconds()); + } + + static void Sleep(TimeSpan tm); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timer_event_helper.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timer_event_helper.cpp new file mode 100644 index 00000000..e484a4c0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timer_event_helper.cpp @@ -0,0 +1,73 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_timer_event_helper.hpp" + +namespace ams::os::impl { + + TimeSpan SaturatedAdd(TimeSpan t1, TimeSpan t2) { + AMS_ASSERT(t1 >= 0); + AMS_ASSERT(t2 >= 0); + + const u64 u1 = t1.GetNanoSeconds(); + const u64 u2 = t2.GetNanoSeconds(); + + const u64 sum = u1 + u2; + return TimeSpan::FromNanoSeconds(std::min<u64>(std::numeric_limits<s64>::max(), sum)); + } + + void StopTimerUnsafe(TimerEventType *event) { + GetReference(event->next_time_to_wakeup) = 0; + event->timer_state = TimerEventType::TimerState_Stop; + } + + bool UpdateSignalStateAndRecalcNextTimeToWakeupUnsafe(TimerEventType *event, TimeSpan cur_time) { + TimeSpan next_time = GetReference(event->next_time_to_wakeup); + + switch (event->timer_state) { + case TimerEventType::TimerState_Stop: + break; + case TimerEventType::TimerState_OneShot: + if (next_time < cur_time) { + event->signaled = true; + StopTimerUnsafe(event); + return true; + } + break; + case TimerEventType::TimerState_Periodic: + if (next_time < cur_time) { + event->signaled = true; + + next_time = SaturatedAdd(next_time, GetReference(event->interval)); + if (next_time < cur_time) { + const u64 elapsed_from_first = (cur_time - GetReference(event->first)).GetNanoSeconds(); + const u64 interval = GetReference(event->interval).GetNanoSeconds(); + + const u64 t = std::min<u64>(std::numeric_limits<s64>::max(), ((elapsed_from_first / interval) + 1) * interval); + next_time = SaturatedAdd(TimeSpan::FromNanoSeconds(t), GetReference(event->first)); + } + + GetReference(event->next_time_to_wakeup) = next_time; + return true; + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + return false; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timer_event_helper.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timer_event_helper.hpp new file mode 100644 index 00000000..cd799d67 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_timer_event_helper.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os { + + struct TimerEventType; + + namespace impl { + + TimeSpan SaturatedAdd(TimeSpan t1, TimeSpan t2); + void StopTimerUnsafe(TimerEventType *event); + bool UpdateSignalStateAndRecalcNextTimeToWakeupUnsafe(TimerEventType *event, TimeSpan cur_time); + + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tls_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tls_manager.cpp new file mode 100644 index 00000000..765c506b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tls_manager.cpp @@ -0,0 +1,142 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_tls_manager.hpp" +#include "os_thread_manager.hpp" + +namespace ams::os::impl { + + /* TODO: Should we migrate away from libnx to this on NX at some point? */ + #if !defined(ATMOSPHERE_OS_HORIZON) + namespace { + + constexpr auto TryCallDestructorCount = 4; + + void DefaultTlsDestructor(uintptr_t) { + /* ... */ + } + + } + + void TlsManager::InvokeTlsDestructors() { + /* Get the curent thread. */ + auto * const thread = impl::GetCurrentThread(); + + /* Call all destructors. */ + for (int slot = 0; slot < static_cast<int>(TotalTlsSlotCountMax); ++slot) { + if (const auto destructor = m_destructors[slot]; destructor != nullptr) { + destructor(thread->tls_value_array[slot]); + } + + thread->tls_value_array[slot] = 0; + } + + /* Verify all tls values are wiped, trying up to four times. */ + for (auto i = 0; i < TryCallDestructorCount; ++i) { + bool all_cleared = true; + for (auto slot = 0; slot < static_cast<int>(TotalTlsSlotCountMax); ++slot) { + if (thread->tls_value_array[slot] != 0) { + all_cleared = false; + + if (const auto destructor = m_destructors[slot]; destructor != nullptr) { + destructor(thread->tls_value_array[slot]); + } + + thread->tls_value_array[slot] = 0; + } + } + + if (all_cleared) { + break; + } + } + } + + bool TlsManager::AllocateTlsSlot(TlsSlot *out, TlsDestructor destructor, bool sdk) { + /* Decide on a slot. */ + int slot; + { + /* Lock appropriately. */ + std::scoped_lock lk(util::GetReference(impl::GetCurrentThread()->cs_thread)); + std::scoped_lock lk2(m_cs); + + /* If we're out of tls slots, fail. */ + if (!sdk && m_used >= static_cast<int>(TlsSlotCountMax)) { + return false; + } + + /* Get a slot. */ + slot = SearchUnusedTlsSlotUnsafe(sdk); + + /* Set the destructor. */ + m_destructors[slot] = (destructor != nullptr) ? destructor : DefaultTlsDestructor; + + /* Increment our used count, if the slot was a user slot. */ + if (!sdk) { + ++m_used; + } + } + + /* Zero the slot in all threads. */ + GetThreadManager().SetZeroToAllThreadsTlsSafe(slot); + + /* Set the output slot's value. */ + out->_value = slot; + return true; + } + + void TlsManager::FreeTlsSlot(TlsSlot slot) { + /* Get the slot's index. */ + const auto slot_idx = slot._value; + + /* Lock appropriately. */ + std::scoped_lock lk(util::GetReference(impl::GetCurrentThread()->cs_thread)); + std::scoped_lock lk2(m_cs); + + /* Verify the slot is allocated. */ + AMS_ABORT_UNLESS(m_destructors[slot_idx] != nullptr); + + /* Free the slot. */ + m_destructors[slot_idx] = nullptr; + + /* Decrement our used count, if the slot was a user slot. */ + if (slot_idx < TlsSlotCountMax) { + --m_used; + } + } + + int TlsManager::SearchUnusedTlsSlotUnsafe(bool sdk) { + if (sdk) { + /* Search backwards for an unused slot. */ + for (int slot = static_cast<int>(TotalTlsSlotCountMax) - 1; slot >= static_cast<int>(TlsSlotCountMax + SdkInternalTlsCount); --slot) { + if (m_destructors[slot] == nullptr) { + return slot; + } + } + AMS_ABORT("Failed to allocate SdkTlsSlot"); + } else { + /* Search forwards for an unused slot. */ + for (int slot = 0; slot < static_cast<int>(TlsSlotCountMax); ++slot) { + if (m_destructors[slot] == nullptr) { + return slot; + } + } + AMS_ABORT("Failed to allocate TlsSlot"); + } + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tls_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tls_manager.hpp new file mode 100644 index 00000000..a36ff0fa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tls_manager.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_resource_manager.hpp" + +namespace ams::os::impl { + + ALWAYS_INLINE TlsManager &GetTlsManager() { + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(TlsManager, s_tls_manager); + return s_tls_manager; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tls_manager_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tls_manager_types.hpp new file mode 100644 index 00000000..d59f92de --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_tls_manager_types.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + static constexpr size_t TotalTlsSlotCountMax = TlsSlotCountMax + SdkTlsSlotCountMax; + + class TlsManager { + NON_COPYABLE(TlsManager); + NON_MOVEABLE(TlsManager); + private: + TlsDestructor m_destructors[TotalTlsSlotCountMax]; + InternalCriticalSection m_cs; + int m_used; + public: + consteval TlsManager() : m_destructors(), m_cs(), m_used() { /* ... */ } + + void InvokeTlsDestructors(); + bool AllocateTlsSlot(TlsSlot *out, TlsDestructor destructor, bool sdk); + void FreeTlsSlot(TlsSlot slot); + + int GetUsedTlsSlots() const { return m_used; } + + static consteval int GetMaxTlsSlots() { return TotalTlsSlotCountMax; } + private: + int SearchUnusedTlsSlotUnsafe(bool sdk); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_transfer_memory_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_transfer_memory_impl.hpp new file mode 100644 index 00000000..e7a19ab6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_transfer_memory_impl.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class TransferMemoryImpl { + public: + static Result Create(NativeHandle *out, void *address, size_t size, MemoryPermission perm); + static void Close(NativeHandle handle); + + static Result Map(void **out, NativeHandle handle, size_t size, MemoryPermission owner_perm); + static void Unmap(NativeHandle handle, void *address, size_t size); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_transfer_memory_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_transfer_memory_impl.os.horizon.cpp new file mode 100644 index 00000000..459c6942 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_transfer_memory_impl.os.horizon.cpp @@ -0,0 +1,88 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_transfer_memory_impl.hpp" +#include "os_aslr_space_manager.hpp" + +namespace ams::os::impl { + + namespace { + + svc::MemoryPermission ConvertToSvcMemoryPermission(os::MemoryPermission perm) { + switch (perm) { + case os::MemoryPermission_None: return svc::MemoryPermission_None; + case os::MemoryPermission_ReadOnly: return svc::MemoryPermission_Read; + case os::MemoryPermission_WriteOnly: return svc::MemoryPermission_Write; + case os::MemoryPermission_ReadWrite: return svc::MemoryPermission_ReadWrite; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + } + + Result TransferMemoryImpl::Create(NativeHandle *out, void *address, size_t size, MemoryPermission perm) { + /* Convert memory permission. */ + const auto svc_perm = ConvertToSvcMemoryPermission(perm); + + /* Create the memory. */ + svc::Handle handle; + R_TRY_CATCH(svc::CreateTransferMemory(std::addressof(handle), reinterpret_cast<uintptr_t>(address), size, svc_perm)) { + R_CONVERT(svc::ResultOutOfHandles, os::ResultOutOfHandles()) + R_CONVERT(svc::ResultOutOfResource, os::ResultOutOfTransferMemory()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + *out = handle; + R_SUCCEED(); + } + + void TransferMemoryImpl::Close(NativeHandle handle) { + R_ABORT_UNLESS(svc::CloseHandle(handle)); + } + + Result TransferMemoryImpl::Map(void **out, NativeHandle handle, size_t size, MemoryPermission owner_perm) { + /* Convert memory permission. */ + const auto svc_owner_perm = ConvertToSvcMemoryPermission(owner_perm); + + /* Map at a random address. */ + uintptr_t mapped_address; + R_TRY(impl::GetAslrSpaceManager().MapAtRandomAddress(std::addressof(mapped_address), + [handle, svc_owner_perm](uintptr_t map_address, size_t map_size) -> Result { + R_TRY_CATCH(svc::MapTransferMemory(handle, map_address, map_size, svc_owner_perm)) { + R_CONVERT(svc::ResultInvalidHandle, os::ResultInvalidHandle()) + R_CONVERT(svc::ResultInvalidSize, os::ResultInvalidTransferMemorySize()) + R_CONVERT(svc::ResultInvalidState, os::ResultInvalidTransferMemoryState()) + R_CONVERT(svc::ResultInvalidCurrentMemory, os::ResultInvalidCurrentMemoryState()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + R_SUCCEED(); + }, + [handle](uintptr_t map_address, size_t map_size) -> void { + return TransferMemoryImpl::Unmap(handle, reinterpret_cast<void *>(map_address), map_size); + }, + size, + 0 + )); + + /* Return the address we mapped at. */ + *out = reinterpret_cast<void *>(mapped_address); + R_SUCCEED(); + } + + void TransferMemoryImpl::Unmap(NativeHandle handle, void *address, size_t size) { + R_ABORT_UNLESS(svc::UnmapTransferMemory(handle, reinterpret_cast<uintptr_t>(address), size)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_unsafe_memory_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_unsafe_memory_impl.hpp new file mode 100644 index 00000000..978a4a71 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_unsafe_memory_impl.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class UnsafeMemoryImpl { + public: + static Result AllocateUnsafeMemoryImpl(uintptr_t *out_address, size_t size); + static Result FreeUnsafeMemoryImpl(uintptr_t address, size_t size); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_unsafe_memory_impl.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_unsafe_memory_impl.os.horizon.cpp new file mode 100644 index 00000000..cb1f87f8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_unsafe_memory_impl.os.horizon.cpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_unsafe_memory_impl.hpp" +#include "os_aslr_space_manager.hpp" + +namespace ams::os::impl { + + Result UnsafeMemoryImpl::AllocateUnsafeMemoryImpl(uintptr_t *out_address, size_t size) { + /* Map at a random address. */ + R_RETURN(impl::GetAslrSpaceManager().MapAtRandomAddress(out_address, + [](uintptr_t map_address, size_t map_size) -> Result { + R_TRY_CATCH(svc::MapPhysicalMemoryUnsafe(map_address, map_size)) { + R_CONVERT(svc::ResultOutOfResource, os::ResultOutOfResource()) + R_CONVERT(svc::ResultOutOfMemory, os::ResultOutOfMemory()) + R_CONVERT(svc::ResultInvalidCurrentMemory, os::ResultInvalidCurrentMemoryState()) + R_CONVERT(svc::ResultLimitReached, os::ResultOutOfMemory()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + R_SUCCEED(); + }, + [](uintptr_t map_address, size_t map_size) -> void { + R_ABORT_UNLESS(UnsafeMemoryImpl::FreeUnsafeMemoryImpl(map_address, map_size)); + }, + size, + 0 + )); + } + + Result UnsafeMemoryImpl::FreeUnsafeMemoryImpl(uintptr_t address, size_t size) { + R_TRY_CATCH(svc::UnmapPhysicalMemoryUnsafe(address, size)) { + R_CONVERT(svc::ResultOutOfResource, os::ResultOutOfResource()) + R_CONVERT(svc::ResultOutOfMemory, os::ResultOutOfResource()) + R_CONVERT(svc::ResultInvalidCurrentMemory, os::ResultBusy()) + R_CONVERT(svc::ResultInvalidMemoryRegion, os::ResultInvalidParameter()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_utility.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_utility.cpp new file mode 100644 index 00000000..43046458 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_utility.cpp @@ -0,0 +1,104 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_utility.hpp" + +namespace ams::os::impl { + + namespace { + + constexpr void ExpandUnsignedValueToAsciiImpl(char *dst, u64 value) { + /* Determine size needed. */ + constexpr size_t SizeNeeded = (BITSIZEOF(value) / 4) * sizeof(char); + + /* Write null-terminator. */ + dst[SizeNeeded] = '\x00'; + + /* Write characters. */ + for (size_t i = 0; i < SizeNeeded; ++i) { + /* Determine current character. */ + const auto nybble = (value & 0xF); + const char cur_c = (nybble < 10) ? ('0' + nybble) : ('A' + nybble - 10); + + /* Write current character. */ + dst[SizeNeeded - 1 - i] = cur_c; + + /* Shift to next nybble. */ + value >>= 4; + } + } + + } + + void ExpandUnsignedValueToAscii(char *dst, u64 value) { + return ExpandUnsignedValueToAsciiImpl(dst, value); + } + + /* Test correctness. */ + #if 0 + namespace { + + consteval bool TestExpandUnsignedValueToAsciiImpl(const char *expected, u64 value) { + /* Create buffer. */ + char buffer[17] = { + '\xCC', '\xCC', '\xCC', '\xCC', '\xCC', '\xCC', '\xCC', '\xCC', '\xCC', '\xCC', '\xCC', '\xCC', '\xCC', '\xCC', '\xCC', '\xCC', '\xCC' + }; + + /* Validate buffer is initially garbage. */ + for (size_t i = 0; i < util::size(buffer); ++i) { + if (buffer[i] != '\xCC') { + return false; + } + } + + /* Expand the value into the buffer. */ + ExpandUnsignedValueToAsciiImpl(buffer, value); + + /* Verify the result. */ + for (size_t i = 0; i < util::size(buffer); ++i) { + if (buffer[i] != expected[i]) { + return false; + } + } + + /* Verify null-termination. */ + if (buffer[util::size(buffer) - 1] != 0) { + return false; + } + + return true; + } + + #define DO_TEST_EXPAND_UNSIGNED_VALUE_TO_ASCII_IMPL(v) static_assert(TestExpandUnsignedValueToAsciiImpl( #v , UINT64_C(0x##v))) + + DO_TEST_EXPAND_UNSIGNED_VALUE_TO_ASCII_IMPL(0000000000000000); + DO_TEST_EXPAND_UNSIGNED_VALUE_TO_ASCII_IMPL(FFFFFFFFFFFFFFFF); + DO_TEST_EXPAND_UNSIGNED_VALUE_TO_ASCII_IMPL(FFFFFFFF00000000); + DO_TEST_EXPAND_UNSIGNED_VALUE_TO_ASCII_IMPL(00000000FFFFFFFF); + DO_TEST_EXPAND_UNSIGNED_VALUE_TO_ASCII_IMPL(FFFF0000FFFF0000); + DO_TEST_EXPAND_UNSIGNED_VALUE_TO_ASCII_IMPL(0000FFFF0000FFFF); + DO_TEST_EXPAND_UNSIGNED_VALUE_TO_ASCII_IMPL(F0F0F0F0F0F0F0F0); + DO_TEST_EXPAND_UNSIGNED_VALUE_TO_ASCII_IMPL(0F0F0F0F0F0F0F0F); + DO_TEST_EXPAND_UNSIGNED_VALUE_TO_ASCII_IMPL(0123456789ABCDEF); + DO_TEST_EXPAND_UNSIGNED_VALUE_TO_ASCII_IMPL(FEDCBA9876543210); + DO_TEST_EXPAND_UNSIGNED_VALUE_TO_ASCII_IMPL(A5A5A5A5A5A5A5A5); + + #undef DO_TEST_EXPAND_UNSIGNED_VALUE_TO_ASCII_IMPL + + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_utility.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_utility.hpp new file mode 100644 index 00000000..50645205 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_utility.hpp @@ -0,0 +1,22 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +namespace ams::os::impl { + + void ExpandUnsignedValueToAscii(char *dst, u64 value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager.cpp new file mode 100644 index 00000000..7bdfbb2d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager.cpp @@ -0,0 +1,391 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "os_vamm_manager.hpp" + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "os_vamm_manager_impl.os.horizon.hpp" +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include "os_vamm_manager_impl.os.windows.hpp" +#elif defined(ATMOSPHERE_OS_LINUX) + #include "os_vamm_manager_impl.os.linux.hpp" +#elif defined(ATMOSPHERE_OS_MACOS) + #include "os_vamm_manager_impl.os.macos.hpp" +#else + #error "Unknown OS for VammManagerImpl" +#endif + + +namespace ams::os::impl { + + namespace { + + class AddressRegion : public util::IntrusiveRedBlackTreeBaseNode<AddressRegion> { + private: + uintptr_t m_address; + size_t m_size; + public: + ALWAYS_INLINE AddressRegion(uintptr_t a, size_t s) : m_address(a), m_size(s) { /* ... */ } + + constexpr ALWAYS_INLINE uintptr_t GetAddressBegin() const { return m_address; } + constexpr ALWAYS_INLINE uintptr_t GetAddressEnd() const { return m_address + m_size; } + constexpr ALWAYS_INLINE size_t GetSize() const { return m_size; } + + constexpr ALWAYS_INLINE bool IsContained(uintptr_t address) const { + if (address < m_address) { + return false; + } else if (address < this->GetAddressEnd()) { + return true; + } else { + return false; + } + } + + constexpr ALWAYS_INLINE bool IsContained(uintptr_t address, size_t size) const { + const uintptr_t end = address + size; + + if (!(address <= end)) { + return false; + } + if (!(this->GetAddressBegin() <= address)) { + return false; + } + if (!(end <= this->GetAddressEnd())) { + return false; + } + + return true; + } + }; + + struct AddressRegionCompare { + using RedBlackKeyType = uintptr_t; + + static constexpr ALWAYS_INLINE int Compare(const RedBlackKeyType a, const RedBlackKeyType &b) { + if (a < b) { + return -1; + } else if (a > b) { + return 1; + } else { + return 0; + } + } + + static constexpr ALWAYS_INLINE int Compare(const RedBlackKeyType &a, const AddressRegion &b) { + return Compare(a, b.GetAddressBegin()); + } + + static constexpr ALWAYS_INLINE int Compare(const AddressRegion &a, const AddressRegion &b) { + return Compare(a.GetAddressBegin(), b.GetAddressBegin()); + } + }; + + using AddressRegionTree = util::IntrusiveRedBlackTreeBaseTraits<AddressRegion>::TreeType<AddressRegionCompare>; + + class DynamicUnitHeap { + private: + static constexpr size_t PhysicalMemoryUnitSize = MemoryPageSize; + private: + uintptr_t m_start; + uintptr_t m_limit; + uintptr_t m_end; + util::TypedStorage<lmem::HeapCommonHead> m_head; + lmem::HeapHandle m_heap; + public: + DynamicUnitHeap(uintptr_t address, size_t size, size_t unit_size) : m_start(address), m_end(address + size) { + /* Allocate the start of our buffer. */ + VammManagerImpl::AllocatePhysicalMemoryImpl(m_start, PhysicalMemoryUnitSize); + + /* Set our current limit. */ + m_limit = m_start + PhysicalMemoryUnitSize; + + /* Initialize our heap. */ + m_heap = lmem::CreateUnitHeap(reinterpret_cast<void *>(m_start), PhysicalMemoryUnitSize, unit_size, lmem::CreateOption_None, alignof(u64), util::GetPointer(m_head)); + } + + void *Allocate() { + void *alloc = lmem::AllocateFromUnitHeap(m_heap); + if (alloc == nullptr) { + this->Extend(); + alloc = lmem::AllocateFromUnitHeap(m_heap); + } + return alloc; + } + + void Free(void *p) { + lmem::FreeToUnitHeap(m_heap, p); + } + private: + void Extend() { + AMS_ABORT_UNLESS(m_limit < m_end); + + if (R_SUCCEEDED(VammManagerImpl::AllocatePhysicalMemoryImpl(m_limit, PhysicalMemoryUnitSize))) { + m_limit += PhysicalMemoryUnitSize; + lmem::ExtendUnitHeap(m_heap, PhysicalMemoryUnitSize); + } + } + }; + + } + + class AddressRegionManager { + private: + static constexpr size_t UnitHeapRegionSize = 1_GB - 2_MB; + private: + uintptr_t m_start; + size_t m_size; + AddressRegionTree m_tree; + DynamicUnitHeap m_heap; + public: + AddressRegionManager(uintptr_t start, size_t size) : m_start(start), m_size(size), m_heap(start, UnitHeapRegionSize, sizeof(AddressRegion)) { + /* Insert a block in the tree for our heap. */ + m_tree.insert(*(new (m_heap.Allocate()) AddressRegion(m_start, UnitHeapRegionSize))); + + /* Insert a zero-size block in the tree at the end of our heap. */ + m_tree.insert(*(new (m_heap.Allocate()) AddressRegion(m_start + size, 0))); + } + + AddressAllocationResult Allocate(AddressRegion **out, size_t size) { + /* Allocate a region. */ + void *p = m_heap.Allocate(); + if (p == nullptr) { + return AddressAllocationResult_OutOfMemory; + } + + /* Determine alignment for the specified size. */ + const size_t align = SelectAlignment(size); + + /* Iterate, looking for an appropriate region. */ + auto *region = std::addressof(m_tree.back()); + while (true) { + /* Get the previous region. */ + auto *prev = region->GetPrev(); + if (prev == nullptr) { + break; + } + + /* Get the space between prev and the current region. */ + const uintptr_t space_start = prev->GetAddressEnd() + MemoryPageSize; + const uintptr_t space_end = region->GetAddressBegin() - MemoryPageSize; + const size_t space_size = space_end - space_start; + + /* If there's enough space in the region, consider it further. */ + if (space_size >= size) { + /* Determine the allocation region extents. */ + const uintptr_t alloc_start = util::AlignUp(space_start, align); + const uintptr_t alloc_end = alloc_start + size; + + /* If the allocation works, use it. */ + if (alloc_end <= space_end) { + auto *address_region = new (p) AddressRegion(alloc_start, size); + m_tree.insert(*address_region); + *out = address_region; + return AddressAllocationResult_Success; + } + } + + /* Otherwise, continue. */ + region = prev; + } + + /* We ran out of space to allocate. */ + return AddressAllocationResult_OutOfSpace; + } + + void Free(AddressRegion *region) { + m_tree.erase(m_tree.iterator_to(*region)); + m_heap.Free(region); + } + + bool IsAlreadyAllocated(uintptr_t address, size_t size) const { + /* Find the first region >= our address. */ + auto region = std::addressof(*(m_tree.nfind_key(address))); + if (region == nullptr) { + return false; + } + + /* If the address matches, return whether the region is contained. */ + if (region->GetAddressBegin() == address) { + return size <= region->GetSize(); + } + + /* Otherwise, check the previous entry. */ + if (region = region->GetPrev(); region == nullptr) { + return false; + } + + return region->IsContained(address, size); + } + + AddressRegion *Find(uintptr_t address) const { + return std::addressof(*(m_tree.find_key(address))); + } + private: + static constexpr size_t SelectAlignment(size_t size) { + if (size < 4_MB) { + if (size < 2_MB) { + return 64_KB; + } else { + return 2_MB; + } + } else { + if (size < 32_MB) { + return 4_MB; + } else if (size < 1_GB) { + return 32_MB; + } else { + return 1_GB; + } + } + } + }; + + namespace { + + constinit util::TypedStorage<AddressRegionManager> g_address_region_manager_storage = {}; + + } + + VammManager::VammManager() : m_lock(), m_region_manager(nullptr) { + /* Get the reserved region. */ + VammManagerImpl::GetReservedRegionImpl(std::addressof(m_reserved_region_start), std::addressof(m_reserved_region_size)); + } + + void VammManager::InitializeIfEnabled() { + /* Acquire exclusive/writer access. */ + std::scoped_lock lk(m_lock); + + /* Initialize, if we haven't already. */ + if (m_region_manager == nullptr && IsVirtualAddressMemoryEnabled()) { + m_region_manager = util::ConstructAt(g_address_region_manager_storage, m_reserved_region_start, m_reserved_region_size); + } + } + + Result VammManager::AllocateAddressRegion(uintptr_t *out, size_t size) { + /* Allocate an address. */ + uintptr_t address; + { + /* Lock access to our region manager. */ + std::scoped_lock lk(m_lock); + AMS_ASSERT(m_region_manager != nullptr); + + /* Allocate an address region. */ + AddressRegion *region; + switch (m_region_manager->Allocate(std::addressof(region), size)) { + case AddressAllocationResult_Success: + address = region->GetAddressBegin(); + break; + case AddressAllocationResult_OutOfSpace: + R_THROW(os::ResultOutOfVirtualAddressSpace()); + default: + R_THROW(os::ResultOutOfMemory()); + } + } + + /* Set the output. */ + *out = address; + R_SUCCEED(); + } + + Result VammManager::AllocateMemory(uintptr_t *out, size_t size) { + /* Allocate an address. */ + uintptr_t address; + { + /* Lock access to our region manager. */ + std::scoped_lock lk(m_lock); + AMS_ASSERT(m_region_manager != nullptr); + + /* Allocate an address region. */ + AddressRegion *region; + switch (m_region_manager->Allocate(std::addressof(region), size)) { + case AddressAllocationResult_Success: + address = region->GetAddressBegin(); + break; + case AddressAllocationResult_OutOfSpace: + R_THROW(os::ResultOutOfVirtualAddressSpace()); + default: + R_THROW(os::ResultOutOfMemory()); + } + ON_RESULT_FAILURE { m_region_manager->Free(region); }; + + /* Allocate memory at the region. */ + R_TRY(VammManagerImpl::AllocatePhysicalMemoryImpl(address, size)); + } + + /* Set the output. */ + *out = address; + R_SUCCEED(); + } + + Result VammManager::AllocateMemoryPages(uintptr_t address, size_t size) { + /* Acquire read access to our region manager. */ + std::shared_lock lk(m_lock); + AMS_ASSERT(m_region_manager != nullptr); + + /* Check that the region was previously allocated by a call to AllocateAddressRegion. */ + R_UNLESS(m_region_manager->IsAlreadyAllocated(address, size), os::ResultInvalidParameter()); + + /* Allocate the memory. */ + R_RETURN(VammManagerImpl::AllocatePhysicalMemoryImpl(address, size)); + } + + Result VammManager::FreeAddressRegion(uintptr_t address) { + /* Lock access to our region manager. */ + std::scoped_lock lk(m_lock); + AMS_ASSERT(m_region_manager != nullptr); + + /* Verify the region can be freed. */ + auto *region = m_region_manager->Find(address); + R_UNLESS(region != nullptr, os::ResultInvalidParameter()); + + /* Free any memory present at the address. */ + R_TRY(VammManagerImpl::FreePhysicalMemoryImpl(address, region->GetSize())); + + /* Free the region. */ + m_region_manager->Free(region); + R_SUCCEED(); + } + + Result VammManager::FreeMemoryPages(uintptr_t address, size_t size) { + /* Acquire read access to our region manager. */ + std::shared_lock lk(m_lock); + AMS_ASSERT(m_region_manager != nullptr); + + /* Check that the region was previously allocated by a call to AllocateAddressRegion. */ + R_UNLESS(m_region_manager->IsAlreadyAllocated(address, size), os::ResultInvalidParameter()); + + /* Free the memory. */ + R_RETURN(VammManagerImpl::FreePhysicalMemoryImpl(address, size)); + } + + VirtualAddressMemoryResourceUsage VammManager::GetVirtualAddressMemoryResourceUsage() { + const size_t assigned_size = VammManagerImpl::GetExtraSystemResourceAssignedSize(); + const size_t used_size = VammManagerImpl::GetExtraSystemResourceUsedSize(); + + /* Decide on an actual used size. */ + const size_t reported_used_size = std::min<size_t>(assigned_size, used_size + 512_KB); + + return VirtualAddressMemoryResourceUsage { + .assigned_size = assigned_size, + .used_size = reported_used_size, + }; + } + + bool VammManager::IsVirtualAddressMemoryEnabled() { + return VammManagerImpl::IsVirtualAddressMemoryEnabled(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager.hpp new file mode 100644 index 00000000..03e2fbb6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "os_resource_manager.hpp" + +namespace ams::os::impl { + + ALWAYS_INLINE VammManager &GetVammManager() { + return GetResourceManager().GetVammManager(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager_impl.os.horizon.hpp new file mode 100644 index 00000000..fb1519f8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager_impl.os.horizon.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class VammManagerHorizonImpl { + public: + static void GetReservedRegionImpl(uintptr_t *out_start, uintptr_t *out_size) { + u64 start, size, extra_size; + R_ABORT_UNLESS(svc::GetInfo(std::addressof(start), svc::InfoType_AliasRegionAddress, svc::PseudoHandle::CurrentProcess, 0)); + R_ABORT_UNLESS(svc::GetInfo(std::addressof(size), svc::InfoType_AliasRegionSize, svc::PseudoHandle::CurrentProcess, 0)); + R_ABORT_UNLESS(svc::GetInfo(std::addressof(extra_size), svc::InfoType_AliasRegionExtraSize, svc::PseudoHandle::CurrentProcess, 0)); + *out_start = start; + *out_size = size - extra_size; + } + + static Result AllocatePhysicalMemoryImpl(uintptr_t address, size_t size) { + R_TRY_CATCH(svc::MapPhysicalMemory(address, size)) { + R_CONVERT(svc::ResultOutOfResource, os::ResultOutOfResource()) + R_CONVERT(svc::ResultOutOfMemory, os::ResultOutOfMemory()) + R_CONVERT(svc::ResultLimitReached, os::ResultOutOfMemory()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + R_SUCCEED(); + } + + static Result FreePhysicalMemoryImpl(uintptr_t address, size_t size) { + R_TRY_CATCH(svc::UnmapPhysicalMemory(address, size)) { + R_CONVERT(svc::ResultInvalidCurrentMemory, os::ResultBusy()) + R_CONVERT(svc::ResultOutOfResource, os::ResultOutOfResource()) + R_CONVERT(svc::ResultOutOfMemory, os::ResultOutOfMemory()) + R_CONVERT(svc::ResultLimitReached, os::ResultOutOfMemory()) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + R_SUCCEED(); + } + + static size_t GetExtraSystemResourceAssignedSize() { + u64 v; + return R_SUCCEEDED(svc::GetInfo(std::addressof(v), svc::InfoType_SystemResourceSizeTotal, svc::PseudoHandle::CurrentProcess, 0)) ? v : 0; + } + + static size_t GetExtraSystemResourceUsedSize() { + u64 v; + return R_SUCCEEDED(svc::GetInfo(std::addressof(v), svc::InfoType_SystemResourceSizeUsed, svc::PseudoHandle::CurrentProcess, 0)) ? v : 0; + } + + static bool IsVirtualAddressMemoryEnabled() { + return GetExtraSystemResourceAssignedSize() > 0; + } + }; + + using VammManagerImpl = VammManagerHorizonImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager_impl.os.linux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager_impl.os.linux.hpp new file mode 100644 index 00000000..a15c838b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager_impl.os.linux.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include <sys/mman.h> + +namespace ams::os::impl { + + class VammManagerLinuxImpl { + public: + static void GetReservedRegionImpl(uintptr_t *out_start, uintptr_t *out_size) { + /* Reserve a 64 GB region of virtual address space. */ + constexpr size_t ReservedRegionSize = 64_GB; + const auto reserved = ::mmap(nullptr, ReservedRegionSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + AMS_ABORT_UNLESS(reserved != MAP_FAILED); + + *out_start = reinterpret_cast<uintptr_t>(reserved); + *out_size = ReservedRegionSize; + } + + static Result AllocatePhysicalMemoryImpl(uintptr_t address, size_t size) { + R_UNLESS(::mprotect(reinterpret_cast<void *>(address), size, PROT_READ | PROT_WRITE) == 0, os::ResultOutOfMemory()); + R_SUCCEED(); + } + + static Result FreePhysicalMemoryImpl(uintptr_t address, size_t size) { + const auto reserved = ::mmap(reinterpret_cast<void *>(address), size, PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + R_UNLESS(reserved != MAP_FAILED, os::ResultBusy()); + R_SUCCEED(); + } + + static consteval size_t GetExtraSystemResourceAssignedSize() { + return 0; + } + + static consteval size_t GetExtraSystemResourceUsedSize() { + return 0; + } + + static consteval bool IsVirtualAddressMemoryEnabled() { + return true; + } + }; + + using VammManagerImpl = VammManagerLinuxImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager_impl.os.macos.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager_impl.os.macos.hpp new file mode 100644 index 00000000..f5b5855c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager_impl.os.macos.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include <sys/mman.h> + +namespace ams::os::impl { + + class VammManagerMacosImpl { + public: + static void GetReservedRegionImpl(uintptr_t *out_start, uintptr_t *out_size) { + /* Reserve a 64 GB region of virtual address space. */ + constexpr size_t ReservedRegionSize = 64_GB; + const auto reserved = ::mmap(nullptr, ReservedRegionSize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + AMS_ABORT_UNLESS(reserved != MAP_FAILED); + + *out_start = reinterpret_cast<uintptr_t>(reserved); + *out_size = ReservedRegionSize; + } + + static Result AllocatePhysicalMemoryImpl(uintptr_t address, size_t size) { + R_UNLESS(::mprotect(reinterpret_cast<void *>(address), size, PROT_READ | PROT_WRITE) == 0, os::ResultOutOfMemory()); + R_SUCCEED(); + } + + static Result FreePhysicalMemoryImpl(uintptr_t address, size_t size) { + const auto reserved = ::mmap(reinterpret_cast<void *>(address), size, PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + R_UNLESS(reserved != MAP_FAILED, os::ResultBusy()); + R_SUCCEED(); + } + + static consteval size_t GetExtraSystemResourceAssignedSize() { + return 0; + } + + static consteval size_t GetExtraSystemResourceUsedSize() { + return 0; + } + + static consteval bool IsVirtualAddressMemoryEnabled() { + return true; + } + }; + + using VammManagerImpl = VammManagerMacosImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager_impl.os.windows.hpp new file mode 100644 index 00000000..edee21b9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager_impl.os.windows.hpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class VammManagerWindowsImpl { + public: + static void GetReservedRegionImpl(uintptr_t *out_start, uintptr_t *out_size) { + /* Reserve a 64 GB region of virtual address space. */ + constexpr size_t ReservedRegionSize = 64_GB; + const auto reserved = ::VirtualAlloc(nullptr, ReservedRegionSize, MEM_RESERVE, PAGE_READWRITE); + AMS_ABORT_UNLESS(reserved != nullptr); + + *out_start = reinterpret_cast<uintptr_t>(reserved); + *out_size = ReservedRegionSize; + } + + static Result AllocatePhysicalMemoryImpl(uintptr_t address, size_t size) { + R_UNLESS(::VirtualAlloc(reinterpret_cast<LPVOID>(address), size, MEM_COMMIT, PAGE_READWRITE) != nullptr, os::ResultOutOfMemory()); + R_SUCCEED(); + } + + static Result FreePhysicalMemoryImpl(uintptr_t address, size_t size) { + R_UNLESS(::VirtualFree(reinterpret_cast<LPVOID>(address), size, MEM_DECOMMIT), os::ResultBusy()); + R_SUCCEED(); + } + + static consteval size_t GetExtraSystemResourceAssignedSize() { + return 0; + } + + static consteval size_t GetExtraSystemResourceUsedSize() { + return 0; + } + + static consteval bool IsVirtualAddressMemoryEnabled() { + return true; + } + }; + + using VammManagerImpl = VammManagerWindowsImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager_types.hpp new file mode 100644 index 00000000..d6b0c1d9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/impl/os_vamm_manager_types.hpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::os::impl { + + class AddressRegionManager; + + class VammManager { + NON_COPYABLE(VammManager); + NON_MOVEABLE(VammManager); + private: + uintptr_t m_reserved_region_start; + uintptr_t m_reserved_region_size; + ReaderWriterLock m_lock; + AddressRegionManager *m_region_manager; + public: + VammManager(); + + void InitializeIfEnabled(); + + Result AllocateAddressRegion(uintptr_t *out, size_t size); + Result AllocateMemory(uintptr_t *out, size_t size); + Result AllocateMemoryPages(uintptr_t address, size_t size); + + Result FreeAddressRegion(uintptr_t address); + Result FreeMemoryPages(uintptr_t address, size_t size); + + VirtualAddressMemoryResourceUsage GetVirtualAddressMemoryResourceUsage(); + public: + static bool IsVirtualAddressMemoryEnabled(); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_argument.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_argument.cpp new file mode 100644 index 00000000..df83598d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_argument.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::os { + + namespace { + + struct CommandLineParameter { + int argc; + char **argv; + }; + + constinit const char *g_command_line_parameter_argv[2] = { "", nullptr }; + constinit CommandLineParameter g_command_line_parameter = { + 1, + const_cast<char **>(g_command_line_parameter_argv), + }; + + } + + void SetHostArgc(int argc) { + g_command_line_parameter.argc = argc; + } + + void SetHostArgv(char **argv) { + g_command_line_parameter.argv = argv; + } + + int GetHostArgc() { + return g_command_line_parameter.argc; + } + + char **GetHostArgv() { + return g_command_line_parameter.argv; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_barrier.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_barrier.cpp new file mode 100644 index 00000000..0e4a04c6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_barrier.cpp @@ -0,0 +1,120 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::os { + + namespace { + + ALWAYS_INLINE bool IsBarrierInitialized(const BarrierType *barrier) { + return barrier->max_threads != 0; + } + + ALWAYS_INLINE u64 GetBarrierBaseCounterImpl(const BarrierType *barrier) { + /* Check pre-conditions. */ + #if defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + AMS_ASSERT(util::GetReference(barrier->cs_barrier).IsLockedByCurrentThread()); + #endif + + /* Convert two u32s to u64. */ + return (static_cast<u64>(barrier->base_counter_lower) << 0) | (static_cast<u64>(barrier->base_counter_upper) << BITSIZEOF(barrier->base_counter_lower)); + } + + ALWAYS_INLINE void SetBarrierBaseCounterImpl(BarrierType *barrier, u64 value) { + /* Check pre-conditions. */ + #if defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + AMS_ASSERT(util::GetReference(barrier->cs_barrier).IsLockedByCurrentThread()); + #endif + + /* Store as two u32s. */ + barrier->base_counter_lower = static_cast<u32>(value >> 0); + barrier->base_counter_upper = static_cast<u32>(value >> BITSIZEOF(barrier->base_counter_lower)); + } + + } + + void InitializeBarrier(BarrierType *barrier, int num_threads) { + /* Check pre-conditions. */ + AMS_ASSERT(num_threads >= 1); + + /* Construct objects. */ + util::ConstructAt(barrier->cs_barrier); + util::ConstructAt(barrier->cv_gathered); + + /* Set member variables. */ + barrier->max_threads = num_threads; + barrier->waiting_threads = 0; + barrier->base_counter_lower = 0; + barrier->base_counter_upper = 0; + } + + void FinalizeBarrier(BarrierType *barrier) { + /* Check pre-conditions. */ + AMS_ASSERT(IsBarrierInitialized(barrier)); + AMS_ASSERT(barrier->waiting_threads == 0); + + /* Clear max threads. */ + barrier->max_threads = 0; + + /* Destroy objects. */ + util::DestroyAt(barrier->cs_barrier); + util::DestroyAt(barrier->cv_gathered); + } + + void AwaitBarrier(BarrierType *barrier) { + /* Check pre-conditions. */ + AMS_ASSERT(IsBarrierInitialized(barrier)); + + /* Await the barrier. */ + { + /* Acquire exclusive access to the barrier. */ + auto &cs = util::GetReference(barrier->cs_barrier); + std::scoped_lock lk(cs); + + /* Read barrier state. */ + const u64 base_counter = GetBarrierBaseCounterImpl(barrier); + const auto max_threads = barrier->max_threads; + auto waiting_threads = barrier->waiting_threads; + + /* Determine next base counter. */ + const u64 done_base_counter = base_counter + max_threads; + + /* Increment waiting threads. */ + ++waiting_threads; + + /* Check if all threads have synchronized. */ + if (waiting_threads >= max_threads) { + /* They have, so reset waiting thread count. */ + barrier->waiting_threads = 0; + + /* Set the updated base counter. */ + SetBarrierBaseCounterImpl(barrier, done_base_counter); + + /* Broadcast to our cv. */ + util::GetReference(barrier->cv_gathered).Broadcast(); + } else { + /* More threads are needed, so update waiting thread count. */ + barrier->waiting_threads = waiting_threads; + + /* Wait for remaining threads to await. */ + while (GetBarrierBaseCounterImpl(barrier) < done_base_counter) { + util::GetReference(barrier->cv_gathered).Wait(std::addressof(cs)); + } + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_busy_mutex.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_busy_mutex.cpp new file mode 100644 index 00000000..cbd6807c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_busy_mutex.cpp @@ -0,0 +1,85 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_thread_manager.hpp" + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "impl/os_internal_busy_mutex_impl.os.horizon.hpp" +#else + /* Generic implementation already included in <stratosphere.hpp> */ +#endif + +namespace ams::os { + + void InitializeBusyMutex(BusyMutexType *mutex) { + /* Create object. */ + util::ConstructAt(mutex->_storage); + + /* Set member variables. */ + mutex->owner_thread = nullptr; + + /* Mark initialized. */ + mutex->state = BusyMutexType::State_Initialized; + } + + void FinalizeBusyMutex(BusyMutexType *mutex) { + /* Check pre-conditions. */ + AMS_ASSERT(mutex->state == BusyMutexType::State_Initialized); + AMS_ASSERT(!util::GetReference(mutex->_storage).IsLocked()); + + /* Mark not intialized. */ + mutex->state = MutexType::State_NotInitialized; + + /* Destroy object. */ + util::DestroyAt(mutex->_storage); + } + + void LockBusyMutex(BusyMutexType *mutex) { + /* Check pre-conditions. */ + AMS_ASSERT(mutex->state == BusyMutexType::State_Initialized); + + /* Lock mutex. */ + util::GetReference(mutex->_storage).Lock(); + + /* Set owner thread. */ + mutex->owner_thread = impl::GetCurrentThread(); + } + + bool TryLockBusyMutex(BusyMutexType *mutex) { + /* Check pre-conditions. */ + AMS_ASSERT(mutex->state == BusyMutexType::State_Initialized); + + /* Try to lock mutex. */ + const bool locked = util::GetReference(mutex->_storage).TryLock(); + + /* Set owner thread. */ + if (locked) { + mutex->owner_thread = impl::GetCurrentThread(); + } + + return locked; + } + + void UnlockBusyMutex(BusyMutexType *mutex) { + /* Check pre-conditions. */ + AMS_ASSERT(mutex->state == BusyMutexType::State_Initialized); + AMS_ASSERT(util::GetReference(mutex->_storage).IsLocked() && mutex->owner_thread == impl::GetCurrentThread()); + + /* Unlock. */ + util::GetReference(mutex->_storage).Unlock(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_cache.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_cache.cpp new file mode 100644 index 00000000..65a27c22 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_cache.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_cache_impl.hpp" + +namespace ams::os { + + void FlushDataCache(const void *addr, size_t size) { + return impl::FlushDataCacheImpl(addr, size); + } + + void FlushEntireDataCache() { + return impl::FlushEntireDataCacheImpl(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_condition_variable.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_condition_variable.cpp new file mode 100644 index 00000000..f763798a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_condition_variable.cpp @@ -0,0 +1,107 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_thread_manager.hpp" +#include "impl/os_timeout_helper.hpp" +#include "impl/os_mutex_impl.hpp" + +namespace ams::os { + + void InitializeConditionVariable(ConditionVariableType *cv) { + /* Construct object. */ + util::ConstructAt(cv->_storage); + + /* Mark initialized. */ + cv->state = ConditionVariableType::State_Initialized; + } + + void FinalizeConditionVariable(ConditionVariableType *cv) { + AMS_ASSERT(cv->state == ConditionVariableType::State_Initialized); + + /* Mark not initialized. */ + cv->state = ConditionVariableType::State_NotInitialized; + + /* Destroy objects. */ + util::DestroyAt(cv->_storage); + } + + void SignalConditionVariable(ConditionVariableType *cv) { + AMS_ASSERT(cv->state == ConditionVariableType::State_Initialized); + + GetReference(cv->_storage).Signal(); + } + + void BroadcastConditionVariable(ConditionVariableType *cv) { + AMS_ASSERT(cv->state == ConditionVariableType::State_Initialized); + + GetReference(cv->_storage).Broadcast(); + } + + void WaitConditionVariable(ConditionVariableType *cv, MutexType *m) { + AMS_ASSERT(cv->state == ConditionVariableType::State_Initialized); + + AMS_ASSERT(m->state == MutexType::State_Initialized); + AMS_ASSERT(m->owner_thread == impl::GetCurrentThread()); + AMS_ASSERT(m->nest_count == 1); + + impl::PopAndCheckLockLevel(m); + + if ((--m->nest_count) == 0) { + m->owner_thread = nullptr; + } + + GetReference(cv->_storage).Wait(GetPointer(m->_storage)); + + impl::PushAndCheckLockLevel(m); + + ++m->nest_count; + m->owner_thread = impl::GetCurrentThread(); + } + + ConditionVariableStatus TimedWaitConditionVariable(ConditionVariableType *cv, MutexType *m, TimeSpan timeout) { + AMS_ASSERT(cv->state == ConditionVariableType::State_Initialized); + + AMS_ASSERT(m->state == MutexType::State_Initialized); + AMS_ASSERT(m->owner_thread == impl::GetCurrentThread()); + AMS_ASSERT(m->nest_count == 1); + + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + + impl::PopAndCheckLockLevel(m); + + if ((--m->nest_count) == 0) { + m->owner_thread = nullptr; + } + + ConditionVariableStatus status; + if (timeout == TimeSpan(0)) { + GetReference(m->_storage).Leave(); + GetReference(m->_storage).Enter(); + status = ConditionVariableStatus::TimedOut; + } else { + impl::TimeoutHelper timeout_helper(timeout); + status = GetReference(cv->_storage).TimedWait(GetPointer(m->_storage), timeout_helper); + } + + impl::PushAndCheckLockLevel(m); + + ++m->nest_count; + m->owner_thread = impl::GetCurrentThread(); + + return status; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_debug.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_debug.cpp new file mode 100644 index 00000000..57bb08ee --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_debug.cpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_debug_impl.hpp" + +namespace ams::os { + + void GetCurrentStackInfo(uintptr_t *out_stack, size_t *out_size) { + /* Get the current stack info. */ + uintptr_t stack_top = 0; + size_t stack_size = 0; + impl::DebugImpl::GetCurrentStackInfo(std::addressof(stack_top), std::addressof(stack_size)); + + /* Basic sanity check. */ + uintptr_t sp = reinterpret_cast<uintptr_t>(std::addressof(stack_top)); + AMS_ASSERT((stack_top <= sp) && (sp < (stack_top + stack_size))); + AMS_UNUSED(sp); + + /* Set the output. */ + if (out_stack != nullptr) { + *out_stack = stack_top; + } + if (out_size != nullptr) { + *out_size = stack_size; + } + } + + void QueryMemoryInfo(MemoryInfo *out) { + return impl::DebugImpl::QueryMemoryInfo(out); + } + + Tick GetIdleTickCount() { + return impl::DebugImpl::GetIdleTickCount(); + } + + int GetFreeThreadCount() { + return impl::DebugImpl::GetFreeThreadCount(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_event.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_event.cpp new file mode 100644 index 00000000..bf2c7479 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_event.cpp @@ -0,0 +1,171 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_timeout_helper.hpp" +#include "impl/os_multiple_wait_object_list.hpp" +#include "impl/os_multiple_wait_holder_impl.hpp" + +namespace ams::os { + + namespace { + + ALWAYS_INLINE u64 GetBroadcastCounterUnsafe(EventType *event) { + const u64 upper = event->broadcast_counter_high; + return (upper << BITSIZEOF(event->broadcast_counter_low)) | event->broadcast_counter_low; + } + + ALWAYS_INLINE void IncrementBroadcastCounterUnsafe(EventType *event) { + if ((++event->broadcast_counter_low) == 0) { + ++event->broadcast_counter_high; + } + } + + } + + void InitializeEvent(EventType *event, bool signaled, EventClearMode clear_mode) { + /* Initialize internal variables. */ + util::ConstructAt(event->cs_event); + util::ConstructAt(event->cv_signaled); + + /* Initialize the multi wait object list. */ + util::ConstructAt(event->multi_wait_object_list_storage); + + /* Initialize member variables. */ + event->signaled = signaled; + event->initially_signaled = signaled; + event->clear_mode = static_cast<u8>(clear_mode); + event->broadcast_counter_low = 0; + event->broadcast_counter_high = 0; + + /* Mark initialized. */ + event->state = EventType::State_Initialized; + } + + void FinalizeEvent(EventType *event) { + AMS_ASSERT(event->state == EventType::State_Initialized); + + /* Mark uninitialized. */ + event->state = EventType::State_NotInitialized; + + /* Destroy objects. */ + util::DestroyAt(event->multi_wait_object_list_storage); + util::DestroyAt(event->cv_signaled); + util::DestroyAt(event->cs_event); + } + + void SignalEvent(EventType *event) { + AMS_ASSERT(event->state == EventType::State_Initialized); + + std::scoped_lock lk(GetReference(event->cs_event)); + + /* If we're already signaled, nothing more to do. */ + if (event->signaled) { + return; + } + + event->signaled = true; + + /* Signal! */ + if (event->clear_mode == EventClearMode_ManualClear) { + /* If we're manual clear, increment counter and wake all. */ + IncrementBroadcastCounterUnsafe(event); + GetReference(event->cv_signaled).Broadcast(); + } else { + /* If we're auto clear, signal one thread, which will clear. */ + GetReference(event->cv_signaled).Signal(); + } + + /* Wake up whatever manager, if any. */ + GetReference(event->multi_wait_object_list_storage).WakeupAllMultiWaitThreadsUnsafe(); + } + + void WaitEvent(EventType *event) { + AMS_ASSERT(event->state == EventType::State_Initialized); + + std::scoped_lock lk(GetReference(event->cs_event)); + + const auto cur_counter = GetBroadcastCounterUnsafe(event); + while (!event->signaled) { + if (cur_counter != GetBroadcastCounterUnsafe(event)) { + break; + } + GetReference(event->cv_signaled).Wait(GetPointer(event->cs_event)); + } + + if (event->clear_mode == EventClearMode_AutoClear) { + event->signaled = false; + } + } + + bool TryWaitEvent(EventType *event) { + AMS_ASSERT(event->state == EventType::State_Initialized); + + std::scoped_lock lk(GetReference(event->cs_event)); + + const bool signaled = event->signaled; + if (event->clear_mode == EventClearMode_AutoClear) { + event->signaled = false; + } + + return signaled; + } + + bool TimedWaitEvent(EventType *event, TimeSpan timeout) { + AMS_ASSERT(event->state == EventType::State_Initialized); + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + + { + impl::TimeoutHelper timeout_helper(timeout); + std::scoped_lock lk(GetReference(event->cs_event)); + + const auto cur_counter = GetBroadcastCounterUnsafe(event); + while (!event->signaled) { + if (cur_counter != GetBroadcastCounterUnsafe(event)) { + break; + } + + auto wait_res = GetReference(event->cv_signaled).TimedWait(GetPointer(event->cs_event), timeout_helper); + if (wait_res == ConditionVariableStatus::TimedOut) { + return false; + } + } + + if (event->clear_mode == EventClearMode_AutoClear) { + event->signaled = false; + } + } + + return true; + } + + void ClearEvent(EventType *event) { + AMS_ASSERT(event->state == EventType::State_Initialized); + + std::scoped_lock lk(GetReference(event->cs_event)); + + /* Clear the signaled state. */ + event->signaled = false; + } + + void InitializeMultiWaitHolder(MultiWaitHolderType *multi_wait_holder, EventType *event) { + AMS_ASSERT(event->state == EventType::State_Initialized); + + util::ConstructAt(GetReference(multi_wait_holder->impl_storage).holder_of_event_storage, event); + + multi_wait_holder->user_data = 0; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_insecure_memory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_insecure_memory.cpp new file mode 100644 index 00000000..f03ecd3e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_insecure_memory.cpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_insecure_memory_impl.hpp" + +namespace ams::os { + + Result AllocateInsecureMemory(uintptr_t *out_address, size_t size) { + /* Check arguments. */ + AMS_ASSERT(size > 0); + AMS_ASSERT(util::IsAligned(size, os::MemoryPageSize)); + + /* Allocate memory. */ + R_RETURN(impl::InsecureMemoryImpl::AllocateInsecureMemoryImpl(out_address, size)); + } + + void FreeInsecureMemory(uintptr_t address, size_t size) { + /* Check arguments. */ + AMS_ASSERT(util::IsAligned(address, os::MemoryPageSize)); + AMS_ASSERT(util::IsAligned(size, os::MemoryPageSize)); + + /* Free memory. */ + impl::InsecureMemoryImpl::FreeInsecureMemoryImpl(address, size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_interrupt_event.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_interrupt_event.cpp new file mode 100644 index 00000000..64bb5a32 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_interrupt_event.cpp @@ -0,0 +1,78 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_interrupt_event_impl.hpp" +#include "impl/os_multiple_wait_holder_impl.hpp" +#include "impl/os_multiple_wait_object_list.hpp" + +namespace ams::os { + + void InitializeInterruptEvent(InterruptEventType *event, InterruptName name, EventClearMode clear_mode) { + /* Initialize member variables. */ + event->clear_mode = static_cast<u8>(clear_mode); + + /* Initialize implementation. */ + util::ConstructAt(event->impl, name, clear_mode); + + /* Mark initialized. */ + event->state = InterruptEventType::State_Initialized; + } + + void FinalizeInterruptEvent(InterruptEventType *event) { + AMS_ASSERT(event->state == InterruptEventType::State_Initialized); + + /* Mark uninitialized. */ + event->state = InterruptEventType::State_NotInitialized; + + /* Destroy objects. */ + util::DestroyAt(event->impl); + } + + void WaitInterruptEvent(InterruptEventType *event) { + AMS_ASSERT(event->state == InterruptEventType::State_Initialized); + return GetReference(event->impl).Wait(); + } + + bool TryWaitInterruptEvent(InterruptEventType *event) { + AMS_ASSERT(event->state == InterruptEventType::State_Initialized); + return GetReference(event->impl).TryWait(); + } + + bool TimedWaitInterruptEvent(InterruptEventType *event, TimeSpan timeout) { + AMS_ASSERT(event->state == InterruptEventType::State_Initialized); + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + return GetReference(event->impl).TimedWait(timeout); + } + + void ClearInterruptEvent(InterruptEventType *event) { + AMS_ASSERT(event->state == InterruptEventType::State_Initialized); + return GetReference(event->impl).Clear(); + } + + NativeHandle GetInterruptEventHandle(const InterruptEventType *event) { + AMS_ASSERT(event->state == InterruptEventType::State_Initialized); + return GetReference(event->impl).GetHandle(); + } + + void InitializeMultiWaitHolder(MultiWaitHolderType *multi_wait_holder, InterruptEventType *event) { + AMS_ASSERT(event->state == InterruptEventType::State_Initialized); + + util::ConstructAt(GetReference(multi_wait_holder->impl_storage).holder_of_interrupt_event_storage, event); + + multi_wait_holder->user_data = 0; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_io_region.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_io_region.cpp new file mode 100644 index 00000000..04eab10c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_io_region.cpp @@ -0,0 +1,187 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_io_region_impl.hpp" + +namespace ams::os { + + namespace { + + void InitializeIoRegion(IoRegionType *io_region, NativeHandle handle, size_t size, bool managed) { + /* Set state. */ + io_region->state = IoRegionType::State_Initialized; + + /* Set member variables. */ + io_region->handle = handle; + io_region->size = size; + io_region->handle_managed = managed; + io_region->mapped_address = nullptr; + + /* Create critical section. */ + util::ConstructAt(io_region->cs_io_region); + } + + } + + Result CreateIoRegion(IoRegionType *io_region, NativeHandle io_pool_handle, uintptr_t address, size_t size, MemoryMapping mapping, MemoryPermission permission) { + /* Check pre-conditions. */ + AMS_ASSERT(io_region != nullptr); + AMS_ASSERT(io_pool_handle != os::InvalidNativeHandle); + AMS_ASSERT(util::IsAligned(address, os::MemoryPageSize)); + AMS_ASSERT(util::IsAligned(size, os::MemoryPageSize)); + AMS_ASSERT(mapping == MemoryMapping_IoRegister || mapping == MemoryMapping_Uncached || mapping == MemoryMapping_Memory); + AMS_ASSERT(permission == MemoryPermission_ReadOnly || permission == MemoryPermission_ReadWrite); + + /* If we fail to create, ensure we reset the state. */ + auto state_guard = SCOPE_GUARD { io_region->state = IoRegionType::State_NotInitialized; }; + + /* Create the io region. */ + NativeHandle handle; + R_TRY(impl::IoRegionImpl::CreateIoRegion(std::addressof(handle), io_pool_handle, address, size, mapping, permission)); + + /* Setup the object. */ + InitializeIoRegion(io_region, handle, size, true); + + state_guard.Cancel(); + R_SUCCEED(); + } + + void AttachIoRegionHandle(IoRegionType *io_region, size_t size, NativeHandle handle, bool managed) { + /* Check pre-conditions. */ + AMS_ASSERT(io_region != nullptr); + AMS_ASSERT(util::IsAligned(size, os::MemoryPageSize)); + AMS_ASSERT(handle != os::InvalidNativeHandle); + + /* Setup the object. */ + InitializeIoRegion(io_region, handle, size, managed); + } + + os::NativeHandle DetachIoRegionHandle(IoRegionType *io_region) { + /* Check pre-conditions. */ + AMS_ASSERT(io_region->state != IoRegionType::State_NotInitialized); + + /* Acquire exclusive access to the io region. */ + std::scoped_lock lk(util::GetReference(io_region->cs_io_region)); + + /* Check that we can detach. */ + AMS_ASSERT(io_region->state == IoRegionType::State_Initialized); + + /* Set state as detached. */ + io_region->state = IoRegionType::State_Detached; + + /* Detach the handle. */ + const auto handle = io_region->handle; + + io_region->handle = os::InvalidNativeHandle; + io_region->handle_managed = false; + + return handle; + } + + void DestroyIoRegion(IoRegionType *io_region) { + /* Check pre-conditions. */ + AMS_ASSERT(io_region->state != IoRegionType::State_NotInitialized); + + /* Acquire exclusive access to the io region. */ + std::scoped_lock lk(util::GetReference(io_region->cs_io_region)); + + /* Check that we can destroy. */ + AMS_ASSERT(io_region->state == IoRegionType::State_Initialized); + + /* If managed, close the handle. */ + if (io_region->handle_managed) { + os::CloseNativeHandle(io_region->handle); + } + + /* Clear members. */ + io_region->handle = os::InvalidNativeHandle; + io_region->handle_managed = false; + io_region->mapped_address = nullptr; + io_region->size = 0; + + /* Mark not initialized. */ + io_region->state = IoRegionType::State_NotInitialized; + } + + NativeHandle GetIoRegionHandle(const IoRegionType *io_region) { + /* Check pre-conditions. */ + AMS_ASSERT(io_region->state != IoRegionType::State_NotInitialized); + + /* Acquire exclusive access to the io region. */ + std::scoped_lock lk(util::GetReference(io_region->cs_io_region)); + + /* Get the handle. */ + return io_region->handle; + } + + Result MapIoRegion(void **out, IoRegionType *io_region, MemoryPermission perm) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(io_region != nullptr); + AMS_ASSERT(io_region->state != IoRegionType::State_NotInitialized); + AMS_ASSERT(perm == MemoryPermission_ReadOnly || perm == MemoryPermission_ReadWrite); + + /* Acquire exclusive access to the io region. */ + std::scoped_lock lk(util::GetReference(io_region->cs_io_region)); + + /* Check that we can map. */ + AMS_ASSERT(io_region->state == IoRegionType::State_Initialized); + + /* Get the size. */ + const size_t size = io_region->size; + AMS_ASSERT(size == io_region->size); + + /* Map. */ + void *mapped_address; + R_TRY(impl::IoRegionImpl::MapIoRegion(std::addressof(mapped_address), io_region->handle, size, perm)); + + /* Set mapped address. */ + io_region->mapped_address = mapped_address; + + /* Set mapped. */ + io_region->state = IoRegionType::State_Mapped; + + /* Set the output address. */ + *out = mapped_address; + + R_SUCCEED(); + } + + void UnmapIoRegion(IoRegionType *io_region) { + /* Check pre-conditions. */ + AMS_ASSERT(io_region->state != IoRegionType::State_NotInitialized); + + /* Acquire exclusive access to the io region. */ + std::scoped_lock lk(util::GetReference(io_region->cs_io_region)); + + /* Check that we can unmap. */ + AMS_ASSERT(io_region->state == IoRegionType::State_Mapped); + + /* Get the size. */ + const size_t size = io_region->size; + AMS_ASSERT(size == io_region->size); + + /* Unmap the io region. */ + impl::IoRegionImpl::UnmapIoRegion(io_region->handle, io_region->mapped_address, size); + + /* Clear mapped address. */ + io_region->mapped_address = nullptr; + + /* Set not-mapped. */ + io_region->state = IoRegionType::State_NotInitialized; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_light_event.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_light_event.cpp new file mode 100644 index 00000000..40496a39 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_light_event.cpp @@ -0,0 +1,107 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_timeout_helper.hpp" + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "impl/os_internal_light_event_impl.os.horizon.hpp" +#else + #include "impl/os_internal_light_event_impl.os.generic.hpp" +#endif + +namespace ams::os { + bool is_auto_clear; + bool is_initialized; + + impl::InternalLightEventStorage storage; + + void InitializeLightEvent(LightEventType *event, bool signaled, EventClearMode clear_mode) { + /* Set member variables. */ + event->is_auto_clear = clear_mode == EventClearMode_AutoClear; + event->is_initialized = true; + + /* Create object. */ + util::ConstructAt(event->storage, signaled); + } + + void FinalizeLightEvent(LightEventType *event) { + /* Set not-initialized. */ + event->is_initialized = false; + + /* Destroy object. */ + util::DestroyAt(event->storage); + } + + void SignalLightEvent(LightEventType *event) { + /* Check pre-conditions. */ + AMS_ASSERT(event->is_initialized); + + /* Signal. */ + if (event->is_auto_clear) { + return util::GetReference(event->storage).SignalWithAutoClear(); + } else { + return util::GetReference(event->storage).SignalWithManualClear(); + } + } + + void WaitLightEvent(LightEventType *event) { + /* Check pre-conditions. */ + AMS_ASSERT(event->is_initialized); + + /* Wait. */ + if (event->is_auto_clear) { + return util::GetReference(event->storage).WaitWithAutoClear(); + } else { + return util::GetReference(event->storage).WaitWithManualClear(); + } + } + + bool TryWaitLightEvent(LightEventType *event) { + /* Check pre-conditions. */ + AMS_ASSERT(event->is_initialized); + + /* Wait. */ + if (event->is_auto_clear) { + return util::GetReference(event->storage).TryWaitWithAutoClear(); + } else { + return util::GetReference(event->storage).TryWaitWithManualClear(); + } + } + + bool TimedWaitLightEvent(LightEventType *event, TimeSpan timeout) { + /* Check pre-conditions. */ + AMS_ASSERT(event->is_initialized); + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + + /* Create timeout helper. */ + impl::TimeoutHelper timeout_helper(timeout); + + /* Wait. */ + if (event->is_auto_clear) { + return util::GetReference(event->storage).TimedWaitWithAutoClear(timeout_helper); + } else { + return util::GetReference(event->storage).TimedWaitWithManualClear(timeout_helper); + } + } + + void ClearLightEvent(LightEventType *event) { + /* Check pre-conditions. */ + AMS_ASSERT(event->is_initialized); + + return util::GetReference(event->storage).Clear(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_light_message_queue.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_light_message_queue.cpp new file mode 100644 index 00000000..ab389fbc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_light_message_queue.cpp @@ -0,0 +1,320 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_timeout_helper.hpp" +#include "impl/os_message_queue_helper.hpp" + +namespace ams::os { + + namespace { + + using MessageQueueHelper = impl::MessageQueueHelper<LightMessageQueueType>; + + template<bool ClearEvent, auto EnqueueFunction> + bool TryEnqueueLightMessageQueueImpl(LightMessageQueueType *mq, uintptr_t data) { + /* Perform the enqueue. */ + { + /* Acquire exclusive access to the queue. */ + std::scoped_lock lk(util::GetReference(mq->mutex_queue)); + + /* Check that we can enqueue. */ + if (MessageQueueHelper::IsMessageQueueFull(mq)) { + /* If we should, clear the event. */ + if constexpr (ClearEvent) { + util::GetReference(mq->ev_not_full).Clear(); + } + + /* We can't enqueue because we're full. */ + return false; + } + + /* Enqueue the data. */ + EnqueueFunction(mq, data); + } + + /* Signal that we're not empty. */ + util::GetReference(mq->ev_not_empty).SignalWithManualClear(); + + return true; + } + + template<bool ClearEvent> + bool TrySendLightMessageQueueImpl(LightMessageQueueType *mq, uintptr_t data) { + return TryEnqueueLightMessageQueueImpl<ClearEvent, MessageQueueHelper::EnqueueUnsafe>(mq, data); + } + + template<bool ClearEvent> + bool TryJamLightMessageQueueImpl(LightMessageQueueType *mq, uintptr_t data) { + return TryEnqueueLightMessageQueueImpl<ClearEvent, MessageQueueHelper::JamUnsafe>(mq, data); + } + + template<bool ClearEvent> + bool TryReceiveLightMessageQueueImpl(uintptr_t *out, LightMessageQueueType *mq) { + /* Perform the receive. */ + { + /* Acquire exclusive access to the queue. */ + std::scoped_lock lk(util::GetReference(mq->mutex_queue)); + + /* Check that we can receive. */ + if (MessageQueueHelper::IsMessageQueueEmpty(mq)) { + /* If we should, clear the event. */ + if constexpr (ClearEvent) { + util::GetReference(mq->ev_not_empty).Clear(); + } + + /* We can't enqueue because we're full. */ + return false; + } + + /* Receive the data. */ + *out = MessageQueueHelper::DequeueUnsafe(mq); + } + + /* Signal that we're not full. */ + util::GetReference(mq->ev_not_full).SignalWithManualClear(); + + return true; + } + + template<bool ClearEvent> + bool TryPeekLightMessageQueueImpl(uintptr_t *out, const LightMessageQueueType *mq) { + /* Perform the peek. */ + { + /* Acquire exclusive access to the queue. */ + std::scoped_lock lk(util::GetReference(mq->mutex_queue)); + + /* Check that we can peek. */ + if (MessageQueueHelper::IsMessageQueueEmpty(mq)) { + /* If we should, clear the event. */ + if constexpr (ClearEvent) { + util::GetReference(mq->ev_not_empty).Clear(); + } + + /* We can't enqueue because we're full. */ + return false; + } + + /* Peek the data. */ + *out = MessageQueueHelper::PeekUnsafe(mq); + } + + return true; + } + + } + + void InitializeLightMessageQueue(LightMessageQueueType *mq, uintptr_t *buffer, size_t count) { + /* Check pre-conditions. */ + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(count >= 1); + + /* Construct objects. */ + util::ConstructAt(mq->mutex_queue); + util::ConstructAt(mq->ev_not_empty, false); + util::ConstructAt(mq->ev_not_full, true); + + /* Set member variables. */ + mq->buffer = buffer; + mq->capacity = static_cast<s32>(count); + mq->count = 0; + mq->offset = 0; + + /* Mark initialized. */ + mq->state = LightMessageQueueType::State_Initialized; + } + + void FinalizeLightMessageQueue(LightMessageQueueType *mq) { + /* Check pre-conditions. */ + AMS_ASSERT(mq->state == LightMessageQueueType::State_Initialized); + + /* Mark uninitialized. */ + mq->state = LightMessageQueueType::State_NotInitialized; + + /* Destroy objects. */ + util::DestroyAt(mq->ev_not_empty); + util::DestroyAt(mq->ev_not_full); + util::DestroyAt(mq->mutex_queue); + } + + /* Sending (FIFO functionality) */ + void SendLightMessageQueue(LightMessageQueueType *mq, uintptr_t data) { + /* Check pre-conditions. */ + AMS_ASSERT(mq->state == LightMessageQueueType::State_Initialized); + + /* Repeatedly try to send. */ + while (!TrySendLightMessageQueueImpl<true>(mq, data)) { + /* Wait until we can try to send again. */ + util::GetReference(mq->ev_not_full).WaitWithManualClear(); + } + } + + bool TrySendLightMessageQueue(LightMessageQueueType *mq, uintptr_t data) { + /* Check pre-conditions. */ + AMS_ASSERT(mq->state == LightMessageQueueType::State_Initialized); + + /* Try to send. */ + return TrySendLightMessageQueueImpl<false>(mq, data); + } + + bool TimedSendLightMessageQueue(LightMessageQueueType *mq, uintptr_t data, TimeSpan timeout) { + /* Check pre-conditions. */ + AMS_ASSERT(mq->state == LightMessageQueueType::State_Initialized); + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + + /* Create timeout helper. */ + impl::TimeoutHelper timeout_helper(timeout); + + /* Repeatedly try to send. */ + while (!TrySendLightMessageQueueImpl<true>(mq, data)) { + /* Check if we're timed out. */ + if (timeout_helper.TimedOut()) { + return false; + } + + /* Wait until we can try to send again. */ + util::GetReference(mq->ev_not_full).TimedWaitWithManualClear(timeout_helper); + } + + return true; + } + + /* Jamming (LIFO functionality) */ + void JamLightMessageQueue(LightMessageQueueType *mq, uintptr_t data) { + /* Check pre-conditions. */ + AMS_ASSERT(mq->state == LightMessageQueueType::State_Initialized); + + /* Repeatedly try to jam. */ + while (!TryJamLightMessageQueueImpl<true>(mq, data)) { + /* Wait until we can try to jam again. */ + util::GetReference(mq->ev_not_full).WaitWithManualClear(); + } + } + + bool TryJamLightMessageQueue(LightMessageQueueType *mq, uintptr_t data) { + /* Check pre-conditions. */ + AMS_ASSERT(mq->state == LightMessageQueueType::State_Initialized); + + /* Try to jam. */ + return TryJamLightMessageQueueImpl<false>(mq, data); + } + + bool TimedJamLightMessageQueue(LightMessageQueueType *mq, uintptr_t data, TimeSpan timeout) { + /* Check pre-conditions. */ + AMS_ASSERT(mq->state == LightMessageQueueType::State_Initialized); + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + + /* Create timeout helper. */ + impl::TimeoutHelper timeout_helper(timeout); + + /* Repeatedly try to jam. */ + while (!TryJamLightMessageQueueImpl<true>(mq, data)) { + /* Check if we're timed out. */ + if (timeout_helper.TimedOut()) { + return false; + } + + /* Wait until we can try to jam again. */ + util::GetReference(mq->ev_not_full).TimedWaitWithManualClear(timeout_helper); + } + + return true; + } + + /* Receive functionality */ + void ReceiveLightMessageQueue(uintptr_t *out, LightMessageQueueType *mq) { + /* Check pre-conditions. */ + AMS_ASSERT(mq->state == LightMessageQueueType::State_Initialized); + + /* Repeatedly try to receive. */ + while (!TryReceiveLightMessageQueueImpl<true>(out, mq)) { + /* Wait until we can try to receive again. */ + util::GetReference(mq->ev_not_empty).WaitWithManualClear(); + } + } + + bool TryReceiveLightMessageQueue(uintptr_t *out, LightMessageQueueType *mq) { + /* Check pre-conditions. */ + AMS_ASSERT(mq->state == LightMessageQueueType::State_Initialized); + + /* Try to receive. */ + return TryReceiveLightMessageQueueImpl<false>(out, mq); + } + + bool TimedReceiveLightMessageQueue(uintptr_t *out, LightMessageQueueType *mq, TimeSpan timeout) { + /* Check pre-conditions. */ + AMS_ASSERT(mq->state == LightMessageQueueType::State_Initialized); + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + + /* Create timeout helper. */ + impl::TimeoutHelper timeout_helper(timeout); + + /* Repeatedly try to receive. */ + while (!TryReceiveLightMessageQueueImpl<true>(out, mq)) { + /* Check if we're timed out. */ + if (timeout_helper.TimedOut()) { + return false; + } + + /* Wait until we can try to receive again. */ + util::GetReference(mq->ev_not_empty).TimedWaitWithManualClear(timeout_helper); + } + + return true; + } + + /* Peek functionality */ + void PeekLightMessageQueue(uintptr_t *out, const LightMessageQueueType *mq) { + /* Check pre-conditions. */ + AMS_ASSERT(mq->state == LightMessageQueueType::State_Initialized); + + /* Repeatedly try to peek. */ + while (!TryPeekLightMessageQueueImpl<true>(out, mq)) { + /* Wait until we can try to peek again. */ + util::GetReference(mq->ev_not_empty).WaitWithManualClear(); + } + } + + bool TryPeekLightMessageQueue(uintptr_t *out, const LightMessageQueueType *mq) { + /* Check pre-conditions. */ + AMS_ASSERT(mq->state == LightMessageQueueType::State_Initialized); + + /* Try to peek. */ + return TryPeekLightMessageQueueImpl<false>(out, mq); + } + + bool TimedPeekLightMessageQueue(uintptr_t *out, const LightMessageQueueType *mq, TimeSpan timeout) { + /* Check pre-conditions. */ + AMS_ASSERT(mq->state == LightMessageQueueType::State_Initialized); + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + + /* Create timeout helper. */ + impl::TimeoutHelper timeout_helper(timeout); + + /* Repeatedly try to peek. */ + while (!TryPeekLightMessageQueueImpl<true>(out, mq)) { + /* Check if we're timed out. */ + if (timeout_helper.TimedOut()) { + return false; + } + + /* Wait until we can try to peek again. */ + util::GetReference(mq->ev_not_empty).TimedWaitWithManualClear(timeout_helper); + } + + return true; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_light_semaphore.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_light_semaphore.cpp new file mode 100644 index 00000000..f02bbdf1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_light_semaphore.cpp @@ -0,0 +1,179 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_timeout_helper.hpp" + +namespace ams::os { + + void InitializeLightSemaphore(LightSemaphoreType *sema, s32 count, s32 max_count) { + /* Check pre-conditions. */ + AMS_ASSERT(max_count >= 1); + AMS_ASSERT(0 <= count && count <= max_count); + + /* Setup objects. */ + util::ConstructAt(sema->mutex); + util::ConstructAt(sema->ev_not_zero, count > 0); + + /* Set member variables. */ + sema->count = count; + sema->max_count = max_count; + + /* Mark initialized. */ + sema->state = LightSemaphoreType::State_Initialized; + } + + void FinalizeLightSemaphore(LightSemaphoreType *sema) { + /* Check pre-conditions. */ + AMS_ASSERT(sema->state == LightSemaphoreType::State_Initialized); + + /* Mark uninitialized. */ + sema->state = LightSemaphoreType::State_NotInitialized; + + /* Destroy objects. */ + util::DestroyAt(sema->mutex); + util::DestroyAt(sema->ev_not_zero); + } + + void AcquireLightSemaphore(LightSemaphoreType *sema) { + /* Check pre-conditions. */ + AMS_ASSERT(sema->state == LightSemaphoreType::State_Initialized); + + /* Repeatedly try to acquire the semaphore. */ + while (true) { + /* Try to acquire the semaphore. */ + { + /* Acquire exclusive access to the semaphore. */ + std::scoped_lock lk(util::GetReference(sema->mutex)); + + /* If we can, acquire. */ + if (sema->count > 0) { + --sema->count; + return; + } + + /* Ensure that we can wait once we're unlocked. */ + util::GetReference(sema->ev_not_zero).Clear(); + } + + /* Wait until we can try again. */ + util::GetReference(sema->ev_not_zero).WaitWithManualClear(); + } + } + + bool TryAcquireLightSemaphore(LightSemaphoreType *sema) { + /* Check pre-conditions. */ + AMS_ASSERT(sema->state == LightSemaphoreType::State_Initialized); + + /* Acquire exclusive access to the semaphore. */ + std::scoped_lock lk(util::GetReference(sema->mutex)); + + /* Try to acquire. */ + if (sema->count > 0) { + --sema->count; + return true; + } else { + return false; + } + } + + bool TimedAcquireLightSemaphore(LightSemaphoreType *sema, TimeSpan timeout) { + /* Check pre-conditions. */ + AMS_ASSERT(sema->state == LightSemaphoreType::State_Initialized); + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + + /* Create timeout helper. */ + impl::TimeoutHelper timeout_helper(timeout); + + /* Repeatedly try to acquire the semaphore. */ + while (true) { + /* Try to acquire the semaphore. */ + { + /* Acquire exclusive access to the semaphore. */ + std::scoped_lock lk(util::GetReference(sema->mutex)); + + /* If we can, acquire. */ + if (sema->count > 0) { + --sema->count; + return true; + } + + /* Ensure that we can wait once we're unlocked. */ + util::GetReference(sema->ev_not_zero).Clear(); + } + + /* Check if we're timed out. */ + if (timeout_helper.TimedOut()) { + return false; + } + + /* Wait until we can try again. */ + util::GetReference(sema->ev_not_zero).TimedWaitWithManualClear(timeout_helper); + } + } + + void ReleaseLightSemaphore(LightSemaphoreType *sema) { + /* Check pre-conditions. */ + AMS_ASSERT(sema->state == LightSemaphoreType::State_Initialized); + + /* Release the semaphore. */ + { + /* Acquire exclusive access to the semaphore. */ + std::scoped_lock lk(util::GetReference(sema->mutex)); + + /* Check that we can release. */ + AMS_ASSERT(sema->count + 1 <= sema->max_count); + + /* Release. */ + ++sema->count; + } + + /* Signal that we released. */ + util::GetReference(sema->ev_not_zero).SignalWithManualClear(); + } + + void ReleaseLightSemaphore(LightSemaphoreType *sema, s32 count) { + /* Check pre-conditions. */ + AMS_ASSERT(sema->state == LightSemaphoreType::State_Initialized); + AMS_ASSERT(count >= 1); + + /* Release the semaphore. */ + { + /* Acquire exclusive access to the semaphore. */ + std::scoped_lock lk(util::GetReference(sema->mutex)); + + /* Check that we can release. */ + AMS_ASSERT(sema->count + count <= sema->max_count); + + /* Release. */ + sema->count += count; + } + + /* Signal that we released. */ + util::GetReference(sema->ev_not_zero).SignalWithManualClear(); + } + + s32 GetCurrentLightSemaphoreCount(const LightSemaphoreType *sema) { + /* Check pre-conditions. */ + AMS_ASSERT(sema->state == LightSemaphoreType::State_Initialized); + + /* Acquire exclusive access to the semaphore. */ + std::scoped_lock lk(util::GetReference(sema->mutex)); + + /* Get the count. */ + return sema->count; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_memory_attribute.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_memory_attribute.cpp new file mode 100644 index 00000000..c6c8f8ed --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_memory_attribute.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_memory_attribute_impl.hpp" + +namespace ams::os { + + void SetMemoryAttribute(uintptr_t address, size_t size, MemoryAttribute attr) { + /* Check pre-conditions. */ + AMS_ASSERT(util::IsAligned(address, MemoryPageSize)); + AMS_ASSERT(util::IsAligned(size, MemoryPageSize)); + + return impl::SetMemoryAttributeImpl(address, size, attr); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_memory_heap.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_memory_heap.cpp new file mode 100644 index 00000000..5b41789f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_memory_heap.cpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_memory_heap_manager.hpp" + +namespace ams::os { + + Result SetMemoryHeapSize(size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(util::IsAligned(size, MemoryHeapUnitSize)); + + /* Set the heap size. */ + R_RETURN(impl::GetMemoryHeapManager().SetHeapSize(size)); + } + + uintptr_t GetMemoryHeapAddress() { + return impl::GetMemoryHeapManager().GetHeapAddress(); + } + + size_t GetMemoryHeapSize() { + return impl::GetMemoryHeapManager().GetHeapSize(); + } + + Result AllocateMemoryBlock(uintptr_t *out_address, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(size > 0); + AMS_ASSERT(util::IsAligned(size, MemoryBlockUnitSize)); + + /* Allocate from heap. */ + R_RETURN(impl::GetMemoryHeapManager().AllocateFromHeap(out_address, size)); + } + + void FreeMemoryBlock(uintptr_t address, size_t size) { + /* Get memory heap manager. */ + auto &manager = impl::GetMemoryHeapManager(); + + /* Check pre-conditions. */ + AMS_ASSERT(util::IsAligned(address, MemoryBlockUnitSize)); + AMS_ASSERT(size > 0); + AMS_ASSERT(util::IsAligned(size, MemoryBlockUnitSize)); + AMS_ABORT_UNLESS(manager.IsRegionInMemoryHeap(address, size)); + + /* Release the memory block. */ + manager.ReleaseToHeap(address, size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_memory_permission.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_memory_permission.cpp new file mode 100644 index 00000000..a5bf19c2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_memory_permission.cpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_memory_permission_impl.hpp" + +namespace ams::os { + + void SetMemoryPermission(uintptr_t address, size_t size, MemoryPermission perm) { + return impl::SetMemoryPermissionImpl(address, size, perm); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_message_queue.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_message_queue.cpp new file mode 100644 index 00000000..6373b674 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_message_queue.cpp @@ -0,0 +1,338 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_timeout_helper.hpp" +#include "impl/os_multiple_wait_object_list.hpp" +#include "impl/os_multiple_wait_holder_impl.hpp" +#include "impl/os_message_queue_helper.hpp" + +namespace ams::os { + + namespace { + + using MessageQueueHelper = impl::MessageQueueHelper<MessageQueueType>; + + } + + void InitializeMessageQueue(MessageQueueType *mq, uintptr_t *buffer, size_t count) { + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(count >= 1); + + /* Setup objects. */ + util::ConstructAt(mq->cs_queue); + util::ConstructAt(mq->cv_not_full); + util::ConstructAt(mq->cv_not_empty); + + /* Setup wait lists. */ + util::ConstructAt(mq->waitlist_not_empty); + util::ConstructAt(mq->waitlist_not_full); + + /* Set member variables. */ + mq->buffer = buffer; + mq->capacity = static_cast<s32>(count); + mq->count = 0; + mq->offset = 0; + + /* Mark initialized. */ + mq->state = MessageQueueType::State_Initialized; + } + + void FinalizeMessageQueue(MessageQueueType *mq) { + AMS_ASSERT(mq->state == MessageQueueType::State_Initialized); + + AMS_ASSERT(GetReference(mq->waitlist_not_empty).IsEmpty()); + AMS_ASSERT(GetReference(mq->waitlist_not_full).IsEmpty()); + + /* Mark uninitialized. */ + mq->state = MessageQueueType::State_NotInitialized; + + /* Destroy wait lists. */ + util::DestroyAt(mq->waitlist_not_empty); + util::DestroyAt(mq->waitlist_not_full); + + /* Destroy objects. */ + util::DestroyAt(mq->cv_not_empty); + util::DestroyAt(mq->cv_not_full); + util::DestroyAt(mq->cs_queue); + } + + /* Sending (FIFO functionality) */ + void SendMessageQueue(MessageQueueType *mq, uintptr_t data) { + AMS_ASSERT(mq->state == MessageQueueType::State_Initialized); + + { + /* Acquire mutex, wait sendable. */ + std::scoped_lock lk(GetReference(mq->cs_queue)); + + while (MessageQueueHelper::IsMessageQueueFull(mq)) { + GetReference(mq->cv_not_full).Wait(GetPointer(mq->cs_queue)); + } + + /* Send, signal. */ + MessageQueueHelper::EnqueueUnsafe(mq, data); + GetReference(mq->cv_not_empty).Broadcast(); + GetReference(mq->waitlist_not_empty).WakeupAllMultiWaitThreadsUnsafe(); + } + } + + bool TrySendMessageQueue(MessageQueueType *mq, uintptr_t data) { + AMS_ASSERT(mq->state == MessageQueueType::State_Initialized); + + { + /* Acquire mutex, check sendable. */ + std::scoped_lock lk(GetReference(mq->cs_queue)); + + if (MessageQueueHelper::IsMessageQueueFull(mq)) { + return false; + } + + /* Send, signal. */ + MessageQueueHelper::EnqueueUnsafe(mq, data); + GetReference(mq->cv_not_empty).Broadcast(); + GetReference(mq->waitlist_not_empty).WakeupAllMultiWaitThreadsUnsafe(); + } + + return true; + } + + bool TimedSendMessageQueue(MessageQueueType *mq, uintptr_t data, TimeSpan timeout) { + AMS_ASSERT(mq->state == MessageQueueType::State_Initialized); + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + + { + /* Acquire mutex, wait sendable. */ + impl::TimeoutHelper timeout_helper(timeout); + std::scoped_lock lk(GetReference(mq->cs_queue)); + + while (MessageQueueHelper::IsMessageQueueFull(mq)) { + if (timeout_helper.TimedOut()) { + return false; + } + GetReference(mq->cv_not_full).TimedWait(GetPointer(mq->cs_queue), timeout_helper); + } + + /* Send, signal. */ + MessageQueueHelper::EnqueueUnsafe(mq, data); + GetReference(mq->cv_not_empty).Broadcast(); + GetReference(mq->waitlist_not_empty).WakeupAllMultiWaitThreadsUnsafe(); + } + + return true; + } + + /* Jamming (LIFO functionality) */ + void JamMessageQueue(MessageQueueType *mq, uintptr_t data) { + AMS_ASSERT(mq->state == MessageQueueType::State_Initialized); + + { + /* Acquire mutex, wait sendable. */ + std::scoped_lock lk(GetReference(mq->cs_queue)); + + while (MessageQueueHelper::IsMessageQueueFull(mq)) { + GetReference(mq->cv_not_full).Wait(GetPointer(mq->cs_queue)); + } + + /* Send, signal. */ + MessageQueueHelper::JamUnsafe(mq, data); + GetReference(mq->cv_not_empty).Broadcast(); + GetReference(mq->waitlist_not_empty).WakeupAllMultiWaitThreadsUnsafe(); + } + } + + bool TryJamMessageQueue(MessageQueueType *mq, uintptr_t data) { + AMS_ASSERT(mq->state == MessageQueueType::State_Initialized); + + { + /* Acquire mutex, check sendable. */ + std::scoped_lock lk(GetReference(mq->cs_queue)); + + if (MessageQueueHelper::IsMessageQueueFull(mq)) { + return false; + } + + /* Send, signal. */ + MessageQueueHelper::JamUnsafe(mq, data); + GetReference(mq->cv_not_empty).Broadcast(); + GetReference(mq->waitlist_not_empty).WakeupAllMultiWaitThreadsUnsafe(); + } + + return true; + } + + bool TimedJamMessageQueue(MessageQueueType *mq, uintptr_t data, TimeSpan timeout) { + AMS_ASSERT(mq->state == MessageQueueType::State_Initialized); + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + + { + /* Acquire mutex, wait sendable. */ + impl::TimeoutHelper timeout_helper(timeout); + std::scoped_lock lk(GetReference(mq->cs_queue)); + + while (MessageQueueHelper::IsMessageQueueFull(mq)) { + if (timeout_helper.TimedOut()) { + return false; + } + GetReference(mq->cv_not_full).TimedWait(GetPointer(mq->cs_queue), timeout_helper); + } + + /* Send, signal. */ + MessageQueueHelper::JamUnsafe(mq, data); + GetReference(mq->cv_not_empty).Broadcast(); + GetReference(mq->waitlist_not_empty).WakeupAllMultiWaitThreadsUnsafe(); + } + + return true; + } + + /* Receive functionality */ + void ReceiveMessageQueue(uintptr_t *out, MessageQueueType *mq) { + AMS_ASSERT(mq->state == MessageQueueType::State_Initialized); + + { + /* Acquire mutex, wait receivable. */ + std::scoped_lock lk(GetReference(mq->cs_queue)); + + while (MessageQueueHelper::IsMessageQueueEmpty(mq)) { + GetReference(mq->cv_not_empty).Wait(GetPointer(mq->cs_queue)); + } + + /* Receive, signal. */ + *out = MessageQueueHelper::DequeueUnsafe(mq); + GetReference(mq->cv_not_full).Broadcast(); + GetReference(mq->waitlist_not_full).WakeupAllMultiWaitThreadsUnsafe(); + } + } + + bool TryReceiveMessageQueue(uintptr_t *out, MessageQueueType *mq) { + AMS_ASSERT(mq->state == MessageQueueType::State_Initialized); + + { + /* Acquire mutex, check receivable. */ + std::scoped_lock lk(GetReference(mq->cs_queue)); + + if (MessageQueueHelper::IsMessageQueueEmpty(mq)) { + return false; + } + + /* Receive, signal. */ + *out = MessageQueueHelper::DequeueUnsafe(mq); + GetReference(mq->cv_not_full).Broadcast(); + GetReference(mq->waitlist_not_full).WakeupAllMultiWaitThreadsUnsafe(); + } + + return true; + } + + bool TimedReceiveMessageQueue(uintptr_t *out, MessageQueueType *mq, TimeSpan timeout) { + AMS_ASSERT(mq->state == MessageQueueType::State_Initialized); + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + + { + /* Acquire mutex, wait receivable. */ + impl::TimeoutHelper timeout_helper(timeout); + std::scoped_lock lk(GetReference(mq->cs_queue)); + + while (MessageQueueHelper::IsMessageQueueEmpty(mq)) { + if (timeout_helper.TimedOut()) { + return false; + } + GetReference(mq->cv_not_empty).TimedWait(GetPointer(mq->cs_queue), timeout_helper); + } + + /* Receive, signal. */ + *out = MessageQueueHelper::DequeueUnsafe(mq); + GetReference(mq->cv_not_full).Broadcast(); + GetReference(mq->waitlist_not_full).WakeupAllMultiWaitThreadsUnsafe(); + } + + return true; + } + + /* Peek functionality */ + void PeekMessageQueue(uintptr_t *out, const MessageQueueType *mq) { + AMS_ASSERT(mq->state == MessageQueueType::State_Initialized); + + { + /* Acquire mutex, wait receivable. */ + std::scoped_lock lk(GetReference(mq->cs_queue)); + + while (MessageQueueHelper::IsMessageQueueEmpty(mq)) { + GetReference(mq->cv_not_empty).Wait(GetPointer(mq->cs_queue)); + } + + /* Peek. */ + *out = MessageQueueHelper::PeekUnsafe(mq); + } + } + + bool TryPeekMessageQueue(uintptr_t *out, const MessageQueueType *mq) { + AMS_ASSERT(mq->state == MessageQueueType::State_Initialized); + + { + /* Acquire mutex, check receivable. */ + std::scoped_lock lk(GetReference(mq->cs_queue)); + + if (MessageQueueHelper::IsMessageQueueEmpty(mq)) { + return false; + } + + /* Peek. */ + *out = MessageQueueHelper::PeekUnsafe(mq); + } + + return true; + } + + bool TimedPeekMessageQueue(uintptr_t *out, const MessageQueueType *mq, TimeSpan timeout) { + AMS_ASSERT(mq->state == MessageQueueType::State_Initialized); + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + + { + /* Acquire mutex, wait receivable. */ + impl::TimeoutHelper timeout_helper(timeout); + std::scoped_lock lk(GetReference(mq->cs_queue)); + + while (MessageQueueHelper::IsMessageQueueEmpty(mq)) { + if (timeout_helper.TimedOut()) { + return false; + } + GetReference(mq->cv_not_empty).TimedWait(GetPointer(mq->cs_queue), timeout_helper); + } + + /* Peek. */ + *out = MessageQueueHelper::PeekUnsafe(mq); + } + + return true; + } + + void InitializeMultiWaitHolder(MultiWaitHolderType *multi_wait_holder, MessageQueueType *mq, MessageQueueWaitType type) { + AMS_ASSERT(mq->state == MessageQueueType::State_Initialized); + + switch (type) { + case MessageQueueWaitType::ForNotFull: + util::ConstructAt(GetReference(multi_wait_holder->impl_storage).holder_of_mq_not_full_storage, mq); + break; + case MessageQueueWaitType::ForNotEmpty: + util::ConstructAt(GetReference(multi_wait_holder->impl_storage).holder_of_mq_not_empty_storage, mq); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + multi_wait_holder->user_data = 0; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_multiple_wait.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_multiple_wait.cpp new file mode 100644 index 00000000..d0be1012 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_multiple_wait.cpp @@ -0,0 +1,155 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_multiple_wait_impl.hpp" +#include "impl/os_multiple_wait_holder_base.hpp" +#include "impl/os_multiple_wait_holder_impl.hpp" + +namespace ams::os { + + namespace { + + ALWAYS_INLINE impl::MultiWaitImpl &GetMultiWaitImpl(MultiWaitType *multi_wait) { + return GetReference(multi_wait->impl_storage); + } + + ALWAYS_INLINE MultiWaitHolderType *CastToMultiWaitHolder(impl::MultiWaitHolderBase *base) { + return reinterpret_cast<MultiWaitHolderType *>(base); + } + + } + + void InitializeMultiWait(MultiWaitType *multi_wait) { + /* Initialize storage. */ + util::ConstructAt(multi_wait->impl_storage); + + /* Mark initialized. */ + multi_wait->state = MultiWaitType::State_Initialized; + } + + void FinalizeMultiWait(MultiWaitType *multi_wait) { + auto &impl = GetMultiWaitImpl(multi_wait); + + AMS_ASSERT(multi_wait->state == MultiWaitType::State_Initialized); + AMS_ASSERT(impl.IsListEmpty()); + AMS_UNUSED(impl); + + /* Mark not initialized. */ + multi_wait->state = MultiWaitType::State_NotInitialized; + + /* Destroy. */ + util::DestroyAt(multi_wait->impl_storage); + } + + MultiWaitHolderType *WaitAny(MultiWaitType *multi_wait) { + auto &impl = GetMultiWaitImpl(multi_wait); + + AMS_ASSERT(multi_wait->state == MultiWaitType::State_Initialized); + AMS_ASSERT(impl.IsListNotEmpty()); + + auto *holder = CastToMultiWaitHolder(impl.WaitAny()); + AMS_ASSERT(holder != nullptr); + return holder; + } + + MultiWaitHolderType *TryWaitAny(MultiWaitType *multi_wait) { + auto &impl = GetMultiWaitImpl(multi_wait); + + AMS_ASSERT(multi_wait->state == MultiWaitType::State_Initialized); + AMS_ASSERT(impl.IsListNotEmpty()); + + auto *holder = CastToMultiWaitHolder(impl.TryWaitAny()); + return holder; + } + + MultiWaitHolderType *TimedWaitAny(MultiWaitType *multi_wait, TimeSpan timeout) { + auto &impl = GetMultiWaitImpl(multi_wait); + + AMS_ASSERT(multi_wait->state == MultiWaitType::State_Initialized); + AMS_ASSERT(impl.IsListNotEmpty()); + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + + auto *holder = CastToMultiWaitHolder(impl.TimedWaitAny(timeout)); + return holder; + } + + void FinalizeMultiWaitHolder(MultiWaitHolderType *holder) { + auto *holder_base = reinterpret_cast<impl::MultiWaitHolderBase *>(GetPointer(holder->impl_storage)); + + AMS_ASSERT(holder_base->IsNotLinked()); + + /* Destroy. */ + static_assert(std::is_trivially_destructible<impl::MultiWaitHolderBase>::value); + /* std::destroy_at(holder_base); */ + AMS_UNUSED(holder_base); + } + + void LinkMultiWaitHolder(MultiWaitType *multi_wait, MultiWaitHolderType *holder) { + auto &impl = GetMultiWaitImpl(multi_wait); + auto *holder_base = reinterpret_cast<impl::MultiWaitHolderBase *>(GetPointer(holder->impl_storage)); + + AMS_ASSERT(multi_wait->state == MultiWaitType::State_Initialized); + AMS_ASSERT(holder_base->IsNotLinked()); + + impl.PushBackToList(*holder_base); + holder_base->SetMultiWait(std::addressof(impl)); + } + + void UnlinkMultiWaitHolder(MultiWaitHolderType *holder) { + auto *holder_base = reinterpret_cast<impl::MultiWaitHolderBase *>(GetPointer(holder->impl_storage)); + + /* Don't allow unlinking of an unlinked holder. */ + AMS_ABORT_UNLESS(holder_base->IsLinked()); + + holder_base->GetMultiWait()->EraseFromList(*holder_base); + holder_base->SetMultiWait(nullptr); + } + + void UnlinkAllMultiWaitHolder(MultiWaitType *multi_wait) { + auto &impl = GetMultiWaitImpl(multi_wait); + + AMS_ASSERT(multi_wait->state == MultiWaitType::State_Initialized); + + return impl.EraseAllFromList(); + } + + void MoveAllMultiWaitHolder(MultiWaitType *_dst, MultiWaitType *_src) { + auto &dst = GetMultiWaitImpl(_dst); + auto &src = GetMultiWaitImpl(_src); + + AMS_ASSERT(_dst->state == MultiWaitType::State_Initialized); + AMS_ASSERT(_src->state == MultiWaitType::State_Initialized); + + return dst.MoveAllFromOther(src); + } + + void SetMultiWaitHolderUserData(MultiWaitHolderType *holder, uintptr_t user_data) { + holder->user_data = user_data; + } + + uintptr_t GetMultiWaitHolderUserData(const MultiWaitHolderType *holder) { + return holder->user_data; + } + + void InitializeMultiWaitHolder(MultiWaitHolderType *holder, NativeHandle handle) { + AMS_ASSERT(handle != os::InvalidNativeHandle); + + util::ConstructAt(GetReference(holder->impl_storage).holder_of_native_handle_storage, handle); + + holder->user_data = 0; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_mutex.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_mutex.cpp new file mode 100644 index 00000000..2282e87b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_mutex.cpp @@ -0,0 +1,160 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_thread_manager.hpp" +#include "impl/os_mutex_impl.hpp" + +namespace ams::os { + + namespace impl { + + #ifdef ATMOSPHERE_BUILD_FOR_AUDITING + + void PushAndCheckLockLevel(const MutexType *mutex) { + /* If auditing isn't specified, don't bother. */ + if (mutex->lock_level == 0) { + return; + } + + /* TODO: Implement mutex level auditing. */ + } + + void PopAndCheckLockLevel(const MutexType *mutex) { + /* If auditing isn't specified, don't bother. */ + if (mutex->lock_level == 0) { + return; + } + + /* TODO: Implement mutex level auditing. */ + } + + #else + + void PushAndCheckLockLevel(const MutexType *mutex) { + AMS_UNUSED(mutex); + } + + void PopAndCheckLockLevel(const MutexType *mutex) { + AMS_UNUSED(mutex); + } + + #endif + + } + + namespace { + + ALWAYS_INLINE void AfterLockMutex(MutexType *mutex, ThreadType *cur_thread) { + AMS_ASSERT(mutex->nest_count < MutexRecursiveLockCountMax); + + impl::PushAndCheckLockLevel(mutex); + + ++mutex->nest_count; + mutex->owner_thread = cur_thread; + } + + } + + void InitializeMutex(MutexType *mutex, bool recursive, int lock_level) { + AMS_ASSERT((lock_level == 0) || (MutexLockLevelMin <= lock_level && lock_level <= MutexLockLevelMax)); + + /* Create object. */ + util::ConstructAt(mutex->_storage); + + /* Set member variables. */ + mutex->is_recursive = recursive; + mutex->lock_level = lock_level; + mutex->nest_count = 0; + mutex->owner_thread = nullptr; + + /* Mark initialized. */ + mutex->state = MutexType::State_Initialized; + } + + void FinalizeMutex(MutexType *mutex) { + AMS_ASSERT(mutex->state == MutexType::State_Initialized); + + /* Mark not intialized. */ + mutex->state = MutexType::State_NotInitialized; + + /* Destroy object. */ + util::DestroyAt(mutex->_storage); + } + + void LockMutex(MutexType *mutex) { + AMS_ASSERT(mutex->state == MutexType::State_Initialized); + + ThreadType *current = impl::GetCurrentThread(); + + if (!mutex->is_recursive) { + AMS_ASSERT(mutex->owner_thread != current); + GetReference(mutex->_storage).Enter(); + } else { + if (mutex->owner_thread == current) { + AMS_ASSERT(mutex->nest_count >= 1); + } else { + GetReference(mutex->_storage).Enter(); + } + } + + AfterLockMutex(mutex, current); + } + + bool TryLockMutex(MutexType *mutex) { + AMS_ASSERT(mutex->state == MutexType::State_Initialized); + + ThreadType *current = impl::GetCurrentThread(); + + if (!mutex->is_recursive) { + AMS_ASSERT(mutex->owner_thread != current); + if (!GetReference(mutex->_storage).TryEnter()) { + return false; + } + } else { + if (mutex->owner_thread == current) { + AMS_ASSERT(mutex->nest_count >= 1); + } else { + if (!GetReference(mutex->_storage).TryEnter()) { + return false; + } + } + } + + AfterLockMutex(mutex, current); + + return true; + } + + void UnlockMutex(MutexType *mutex) { + AMS_ASSERT(mutex->state == MutexType::State_Initialized); + AMS_ASSERT(mutex->nest_count > 0); + AMS_ASSERT(mutex->owner_thread == impl::GetCurrentThread()); + + impl::PopAndCheckLockLevel(mutex); + + if ((--mutex->nest_count) == 0) { + mutex->owner_thread = nullptr; + GetReference(mutex->_storage).Leave(); + } + } + + bool IsMutexLockedByCurrentThread(const MutexType *mutex) { + AMS_ASSERT(mutex->state == MutexType::State_Initialized); + + return mutex->owner_thread == impl::GetCurrentThread(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_native_handle_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_native_handle_api.cpp new file mode 100644 index 00000000..00905763 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_native_handle_api.cpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_native_handle_impl.hpp" + +namespace ams::os { + + void CloseNativeHandle(NativeHandle handle) { + if (handle != os::InvalidNativeHandle) { + impl::NativeHandleImpl::Close(handle); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_process_code_memory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_process_code_memory.cpp new file mode 100644 index 00000000..497f798c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_process_code_memory.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_process_code_memory_impl.hpp" + +namespace ams::os { + + Result MapProcessCodeMemory(u64 *out, NativeHandle handle, const ProcessMemoryRegion *regions, size_t num_regions, AddressSpaceGenerateRandomFunction generate_random) { + R_RETURN(::ams::os::impl::ProcessCodeMemoryImpl::Map(out, handle, regions, num_regions, generate_random)); + } + + Result UnmapProcessCodeMemory(NativeHandle handle, u64 process_code_address, const ProcessMemoryRegion *regions, size_t num_regions) { + R_RETURN(::ams::os::impl::ProcessCodeMemoryImpl::Unmap(handle, process_code_address, regions, num_regions)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_process_handle_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_process_handle_api.cpp new file mode 100644 index 00000000..d1f822a8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_process_handle_api.cpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_process_handle_impl.hpp" + +namespace ams { + + namespace dd { + + dd::ProcessHandle __attribute__((const)) GetCurrentProcessHandle() { + return ::ams::os::impl::ProcessHandleImpl::GetCurrentProcessHandle(); + } + + } + + namespace os { + + NativeHandle __attribute__((const)) GetCurrentProcessHandle() { + return ::ams::os::impl::ProcessHandleImpl::GetCurrentProcessHandle(); + } + + Result GetProcessId(os::ProcessId *out, NativeHandle handle) { + R_RETURN(::ams::os::impl::ProcessHandleImpl::GetProcessId(out, handle)); + } + + Result GetProgramId(ncm::ProgramId *out, NativeHandle handle) { + R_RETURN(::ams::os::impl::ProcessHandleImpl::GetProgramId(out, handle)); + } + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_process_memory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_process_memory.cpp new file mode 100644 index 00000000..8cdcbf8c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_process_memory.cpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_process_memory_impl.hpp" + +namespace ams::os { + + Result MapProcessMemory(void **out, NativeHandle handle, u64 process_address, size_t process_size, AddressSpaceGenerateRandomFunction generate_random) { + R_RETURN(::ams::os::impl::ProcessMemoryImpl::Map(out, handle, process_address, process_size, generate_random)); + } + + void UnmapProcessMemory(void *mapped_memory, NativeHandle handle, u64 process_address, size_t process_size) { + return ::ams::os::impl::ProcessMemoryImpl::Unmap(mapped_memory, handle, process_address, process_size); + } + + Result SetProcessMemoryPermission(NativeHandle handle, u64 process_address, u64 process_size, MemoryPermission perm) { + R_RETURN(::ams::os::impl::ProcessMemoryImpl::SetMemoryPermission(handle, process_address, process_size, perm)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_random.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_random.cpp new file mode 100644 index 00000000..2e602d31 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_random.cpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_random_impl.hpp" + +namespace ams::os { + + namespace { + + constinit util::TinyMT g_random{util::ConstantInitialize}; + constinit os::SdkMutex g_random_mutex; + constinit bool g_initialized_random; + + template<typename T> + inline T GenerateRandomTImpl(T max) { + static_assert(std::is_integral<T>::value && std::is_unsigned<T>::value); + const T EffectiveMax = (std::numeric_limits<T>::max() / max) * max; + T cur_rnd; + while (true) { + os::GenerateRandomBytes(std::addressof(cur_rnd), sizeof(T)); + if (cur_rnd < EffectiveMax) { + return cur_rnd % max; + } + } + } + + } + + void GenerateRandomBytes(void *dst, size_t size) { + std::scoped_lock lk(g_random_mutex); + + if (AMS_UNLIKELY(!g_initialized_random)) { + impl::InitializeRandomImpl(std::addressof(g_random)); + g_initialized_random = true; + } + + g_random.GenerateRandomBytes(dst, size); + } + + u32 GenerateRandomU32(u32 max) { + return GenerateRandomTImpl<u32>(max); + } + + u64 GenerateRandomU64(u64 max) { + return GenerateRandomTImpl<u64>(max); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_rw_busy_mutex.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_rw_busy_mutex.cpp new file mode 100644 index 00000000..76839e64 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_rw_busy_mutex.cpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_thread_manager.hpp" + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "impl/os_internal_rw_busy_mutex_impl.os.horizon.hpp" +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include "impl/os_internal_rw_busy_mutex_impl.os.windows.hpp" +#elif defined(ATMOSPHERE_OS_LINUX) + #include "impl/os_internal_rw_busy_mutex_impl.os.linux.hpp" +#elif defined(ATMOSPHERE_OS_MACOS) + #include "impl/os_internal_rw_busy_mutex_impl.os.macos.hpp" +#else + #error "Unknown OS for ams::os::impl::InternalReaderWriterBusyMutexImpl" +#endif + +namespace ams::os { + + void InitalizeReaderWriterLockBusyMutex(ReaderWriterBusyMutexType *rw_mutex) { + /* Create object. */ + util::ConstructAt(rw_mutex->_storage); + } + + void AcquireReadLockBusyMutex(ReaderWriterBusyMutexType *rw_mutex) { + /* Acquire read lock. */ + util::GetReference(rw_mutex->_storage).AcquireReadLock(); + } + + void ReleaseReadLockBusyMutex(ReaderWriterBusyMutexType *rw_mutex) { + /* Release read lock. */ + util::GetReference(rw_mutex->_storage).ReleaseReadLock(); + } + + void AcquireWriteLockBusyMutex(ReaderWriterBusyMutexType *rw_mutex) { + /* Acquire write lock. */ + util::GetReference(rw_mutex->_storage).AcquireWriteLock(); + } + + void ReleaseWriteLockBusyMutex(ReaderWriterBusyMutexType *rw_mutex) { + /* Release write lock. */ + util::GetReference(rw_mutex->_storage).ReleaseWriteLock(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_rw_lock.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_rw_lock.cpp new file mode 100644 index 00000000..9153ed6e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_rw_lock.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_thread_manager.hpp" +#include "impl/os_rw_lock_impl.hpp" + +namespace ams::os { + + void InitalizeReaderWriterLock(ReaderWriterLockType *rw_lock) { + /* Create objects. */ + util::ConstructAt(impl::GetLockCount(rw_lock).cs_storage); + util::ConstructAt(rw_lock->cv_read_lock._storage); + util::ConstructAt(rw_lock->cv_write_lock._storage); + + /* Set member variables. */ + impl::ClearReadLockCount(impl::GetLockCount(rw_lock)); + impl::ClearWriteLocked(impl::GetLockCount(rw_lock)); + impl::ClearReadLockWaiterCount(impl::GetLockCount(rw_lock)); + impl::ClearWriteLockWaiterCount(impl::GetLockCount(rw_lock)); + impl::ClearWriteLockCount(rw_lock); + rw_lock->owner_thread = nullptr; + + /* Mark initialized. */ + rw_lock->state = ReaderWriterLockType::State_Initialized; + } + + void FinalizeReaderWriterLock(ReaderWriterLockType *rw_lock) { + AMS_ASSERT(rw_lock->state == ReaderWriterLockType::State_Initialized); + + /* Don't allow finalizing a locked lock. */ + AMS_ASSERT(impl::GetReadLockCount(impl::GetLockCount(rw_lock)) == 0); + AMS_ASSERT(impl::GetWriteLocked(impl::GetLockCount(rw_lock)) == 0); + + /* Mark not initialized. */ + rw_lock->state = ReaderWriterLockType::State_NotInitialized; + + /* Destroy objects. */ + util::DestroyAt(rw_lock->cv_write_lock._storage); + util::DestroyAt(rw_lock->cv_read_lock._storage); + util::DestroyAt(impl::GetLockCount(rw_lock).cs_storage); + } + + void AcquireReadLock(ReaderWriterLockType *rw_lock) { + AMS_ASSERT(rw_lock->state == ReaderWriterLockType::State_Initialized); + return impl::ReaderWriterLockImpl::AcquireReadLock(rw_lock); + } + + bool TryAcquireReadLock(ReaderWriterLockType *rw_lock) { + AMS_ASSERT(rw_lock->state == ReaderWriterLockType::State_Initialized); + return impl::ReaderWriterLockImpl::TryAcquireReadLock(rw_lock); + } + + void ReleaseReadLock(ReaderWriterLockType *rw_lock) { + AMS_ASSERT(rw_lock->state == ReaderWriterLockType::State_Initialized); + return impl::ReaderWriterLockImpl::ReleaseReadLock(rw_lock); + } + + void AcquireWriteLock(ReaderWriterLockType *rw_lock) { + AMS_ASSERT(rw_lock->state == ReaderWriterLockType::State_Initialized); + return impl::ReaderWriterLockImpl::AcquireWriteLock(rw_lock); + } + + bool TryAcquireWriteLock(ReaderWriterLockType *rw_lock) { + AMS_ASSERT(rw_lock->state == ReaderWriterLockType::State_Initialized); + return impl::ReaderWriterLockImpl::TryAcquireWriteLock(rw_lock); + } + + void ReleaseWriteLock(ReaderWriterLockType *rw_lock) { + AMS_ASSERT(rw_lock->state == ReaderWriterLockType::State_Initialized); + AMS_ABORT_UNLESS(rw_lock->owner_thread == impl::GetCurrentThread()); + return impl::ReaderWriterLockImpl::ReleaseWriteLock(rw_lock); + } + + bool IsReadLockHeld(const ReaderWriterLockType *rw_lock) { + AMS_ASSERT(rw_lock->state == ReaderWriterLockType::State_Initialized); + return impl::GetReadLockCount(impl::GetLockCount(rw_lock)) != 0; + } + + bool IsWriteLockHeldByCurrentThread(const ReaderWriterLockType *rw_lock) { + AMS_ASSERT(rw_lock->state == ReaderWriterLockType::State_Initialized); + return rw_lock->owner_thread == impl::GetCurrentThread() && impl::GetWriteLockCount(*rw_lock) != 0; + } + + bool IsReaderWriterLockOwnerThread(const ReaderWriterLockType *rw_lock) { + AMS_ASSERT(rw_lock->state == ReaderWriterLockType::State_Initialized); + return rw_lock->owner_thread == impl::GetCurrentThread(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_sdk_condition_variable.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_sdk_condition_variable.cpp new file mode 100644 index 00000000..3c752443 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_sdk_condition_variable.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_thread_manager.hpp" +#include "impl/os_timeout_helper.hpp" + +namespace ams::os { + + void SdkConditionVariableType::Wait(SdkMutexType &mutex) { + /* Check that we own the mutex. */ + AMS_ABORT_UNLESS(os::IsSdkMutexLockedByCurrentThread(std::addressof(mutex))); + + /* Wait on the mutex. */ + GetReference(this->_storage).Wait(GetPointer(mutex._storage)); + } + + bool SdkConditionVariableType::TimedWait(SdkMutexType &mutex, TimeSpan timeout) { + /* Check preconditions. */ + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + AMS_ABORT_UNLESS(os::IsSdkMutexLockedByCurrentThread(std::addressof(mutex))); + + /* Handle zero timeout by unlocking and re-locking. */ + if (timeout == TimeSpan(0)) { + GetReference(mutex._storage).Leave(); + GetReference(mutex._storage).Enter(); + return false; + } + + /* Handle timed wait. */ + impl::TimeoutHelper timeout_helper(timeout); + auto status = GetReference(this->_storage).TimedWait(GetPointer(mutex._storage), timeout_helper); + return status == ConditionVariableStatus::Success; + } + + void SdkConditionVariableType::Wait(SdkRecursiveMutexType &mutex) { + /* Check preconditions. */ + AMS_ABORT_UNLESS(os::IsSdkRecursiveMutexLockedByCurrentThread(std::addressof(mutex))); + AMS_ABORT_UNLESS(mutex.recursive_count == 1); + + /* Decrement the mutex's recursive count. */ + --mutex.recursive_count; + + /* Wait on the mutex. */ + GetReference(this->_storage).Wait(GetPointer(mutex._storage)); + + /* Increment the mutex's recursive count. */ + ++mutex.recursive_count; + + /* Check that the mutex's recursive count is valid. */ + AMS_ABORT_UNLESS(mutex.recursive_count != 0); + } + + bool SdkConditionVariableType::TimedWait(SdkRecursiveMutexType &mutex, TimeSpan timeout) { + /* Check preconditions. */ + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + AMS_ABORT_UNLESS(os::IsSdkRecursiveMutexLockedByCurrentThread(std::addressof(mutex))); + + /* Handle zero timeout by unlocking and re-locking. */ + if (timeout == TimeSpan(0)) { + /* NOTE: Nintendo doesn't check recursive_count here...seems really suspicious? */ + /* Not sure that this is correct, or if they just forgot to check. */ + GetReference(mutex._storage).Leave(); + GetReference(mutex._storage).Enter(); + return false; + } + + /* Check that the mutex is held exactly once. */ + AMS_ABORT_UNLESS(mutex.recursive_count == 1); + + /* Decrement the mutex's recursive count. */ + --mutex.recursive_count; + + /* Create timeout helper. */ + impl::TimeoutHelper timeout_helper(timeout); + + /* Perform timed wait. */ + auto status = GetReference(this->_storage).TimedWait(GetPointer(mutex._storage), timeout_helper); + + /* Increment the mutex's recursive count. */ + ++mutex.recursive_count; + + /* Check that the mutex's recursive count is valid. */ + AMS_ABORT_UNLESS(mutex.recursive_count != 0); + + /* Return whether we succeeded. */ + return status == ConditionVariableStatus::Success; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_sdk_mutex.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_sdk_mutex.cpp new file mode 100644 index 00000000..31c88802 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_sdk_mutex.cpp @@ -0,0 +1,86 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#if !defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) +#include "impl/os_thread_manager.hpp" +#endif + +namespace ams::os { + + void InitializeSdkMutex(SdkMutexType *mutex) { + /* Initialize the critical section. */ + GetReference(mutex->_storage).Initialize(); + + /* If the underlying critical section can't readily be checked, set owner thread. */ + #if !defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + mutex->owner_thread = nullptr; + #endif + } + + bool IsSdkMutexLockedByCurrentThread(const SdkMutexType *mutex) { + #if defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + /* Check whether the critical section is held. */ + return GetReference(mutex->_storage).IsLockedByCurrentThread(); + #else + /* Check if the current thread is owner. */ + return mutex->owner_thread == os::impl::GetCurrentThread(); + #endif + } + + void LockSdkMutex(SdkMutexType *mutex) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(!IsSdkMutexLockedByCurrentThread(mutex)); + + /* Enter the critical section. */ + GetReference(mutex->_storage).Enter(); + + /* If necessary, set owner thread. */ + #if !defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + mutex->owner_thread = os::impl::GetCurrentThread(); + #endif + } + + bool TryLockSdkMutex(SdkMutexType *mutex) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(!IsSdkMutexLockedByCurrentThread(mutex)); + + /* Try to enter the critical section. */ + const bool res = GetReference(mutex->_storage).TryEnter(); + + /* If necessary, set owner thread. */ + #if !defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + if (res) { + mutex->owner_thread = os::impl::GetCurrentThread(); + } + #endif + + return res; + } + + void UnlockSdkMutex(SdkMutexType *mutex) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsSdkMutexLockedByCurrentThread(mutex)); + + /* If necessary, clear owner thread. */ + #if !defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + mutex->owner_thread = nullptr; + #endif + + /* Leave the critical section. */ + GetReference(mutex->_storage).Leave(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_sdk_recursive_mutex.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_sdk_recursive_mutex.cpp new file mode 100644 index 00000000..0ecc2524 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_sdk_recursive_mutex.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#if !defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) +#include "impl/os_thread_manager.hpp" +#endif + +namespace ams::os { + + void InitializeSdkRecursiveMutex(SdkRecursiveMutexType *rmutex) { + /* Initialize the critical section. */ + GetReference(rmutex->_storage).Initialize(); + + /* Set recursive count. */ + rmutex->recursive_count = 0; + + /* If the underlying critical section can't readily be checked, set owner thread. */ + #if !defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + rmutex->owner_thread = nullptr; + #endif + } + + bool IsSdkRecursiveMutexLockedByCurrentThread(const SdkRecursiveMutexType *rmutex) { + #if defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + /* Check whether the critical section is held. */ + return GetReference(rmutex->_storage).IsLockedByCurrentThread(); + #else + /* Check if the current thread is owner. */ + return rmutex->owner_thread == os::impl::GetCurrentThread(); + #endif + } + + void LockSdkRecursiveMutex(SdkRecursiveMutexType *rmutex) { + /* If we don't hold the mutex, enter the critical section. */ + if (!IsSdkRecursiveMutexLockedByCurrentThread(rmutex)) { + GetReference(rmutex->_storage).Enter(); + + /* If necessary, set owner thread. */ + #if !defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + rmutex->owner_thread = os::impl::GetCurrentThread(); + #endif + } + + /* Increment (and check) recursive count. */ + ++rmutex->recursive_count; + AMS_ABORT_UNLESS(rmutex->recursive_count != 0); + } + + bool TryLockSdkRecursiveMutex(SdkRecursiveMutexType *rmutex) { + /* If we don't hold the mutex, try to enter the critical section. */ + if (!IsSdkRecursiveMutexLockedByCurrentThread(rmutex)) { + if (!GetReference(rmutex->_storage).TryEnter()) { + return false; + } + + /* If necessary, set owner thread. */ + #if !defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + rmutex->owner_thread = os::impl::GetCurrentThread(); + #endif + } + + /* Increment (and check) recursive count. */ + ++rmutex->recursive_count; + AMS_ABORT_UNLESS(rmutex->recursive_count != 0); + + return true; + } + + void UnlockSdkRecursiveMutex(SdkRecursiveMutexType *rmutex) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsSdkRecursiveMutexLockedByCurrentThread(rmutex)); + + /* Decrement recursive count, and leave critical section if we no longer hold the mutex. */ + if ((--rmutex->recursive_count) == 0) { + /* If necessary, clear owner thread. */ + #if !defined(AMS_OS_INTERNAL_CRITICAL_SECTION_IMPL_CAN_CHECK_LOCKED_BY_CURRENT_THREAD) + rmutex->owner_thread = nullptr; + #endif + + GetReference(rmutex->_storage).Leave(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_sdk_reply_and_receive.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_sdk_reply_and_receive.cpp new file mode 100644 index 00000000..44a49b29 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_sdk_reply_and_receive.cpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_multiple_wait_impl.hpp" +#include "impl/os_multiple_wait_holder_base.hpp" +#include "impl/os_multiple_wait_holder_impl.hpp" + +namespace ams::os { + + namespace { + + ALWAYS_INLINE impl::MultiWaitImpl &GetMultiWaitImpl(MultiWaitType *multi_wait) { + return GetReference(multi_wait->impl_storage); + } + + ALWAYS_INLINE MultiWaitHolderType *CastToMultiWaitHolder(impl::MultiWaitHolderBase *base) { + return reinterpret_cast<MultiWaitHolderType *>(base); + } + + } + + Result SdkReplyAndReceive(os::MultiWaitHolderType **out, NativeHandle reply_target, MultiWaitType *multi_wait) { + auto &impl = GetMultiWaitImpl(multi_wait); + + AMS_ASSERT(multi_wait->state == MultiWaitType::State_Initialized); + AMS_ASSERT(impl.IsListNotEmpty()); + + impl::MultiWaitHolderBase *holder_base = nullptr; + ON_SCOPE_EXIT { *out = CastToMultiWaitHolder(holder_base); }; + + R_RETURN(impl.ReplyAndReceive(std::addressof(holder_base), reply_target)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_sdk_thread_local_storage_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_sdk_thread_local_storage_api.cpp new file mode 100644 index 00000000..ec81ecad --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_sdk_thread_local_storage_api.cpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_tls_manager.hpp" + +namespace ams::os { + + #if defined(ATMOSPHERE_OS_HORIZON) + /* TODO: Nintendo reserves half the TLS slots for SDK usage. */ + /* We don't have that ability...how should this work? */ + Result SdkAllocateTlsSlot(TlsSlot *out, TlsDestructor destructor) { + R_RETURN(os::AllocateTlsSlot(out, destructor)); + } + #else + Result SdkAllocateTlsSlot(TlsSlot *out, TlsDestructor destructor) { + R_UNLESS(impl::GetTlsManager().AllocateTlsSlot(out, destructor, true), os::ResultOutOfResource()); + R_SUCCEED(); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_semaphore.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_semaphore.cpp new file mode 100644 index 00000000..be470cf6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_semaphore.cpp @@ -0,0 +1,153 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_multiple_wait_object_list.hpp" +#include "impl/os_multiple_wait_holder_impl.hpp" +#include "impl/os_timeout_helper.hpp" + +namespace ams::os { + + void InitializeSemaphore(SemaphoreType *sema, s32 count, s32 max_count) { + AMS_ASSERT(max_count >= 1); + AMS_ASSERT(0 <= count && count <= max_count); + + /* Setup objects. */ + util::ConstructAt(sema->cs_sema); + util::ConstructAt(sema->cv_not_zero); + + /* Setup wait lists. */ + util::ConstructAt(sema->waitlist); + + /* Set member variables. */ + sema->count = count; + sema->max_count = max_count; + + /* Mark initialized. */ + sema->state = SemaphoreType::State_Initialized; + } + + void FinalizeSemaphore(SemaphoreType *sema) { + AMS_ASSERT(sema->state == SemaphoreType::State_Initialized); + + AMS_ASSERT(GetReference(sema->waitlist).IsEmpty()); + + /* Mark uninitialized. */ + sema->state = SemaphoreType::State_NotInitialized; + + /* Destroy wait lists. */ + util::DestroyAt(sema->waitlist); + + /* Destroy objects. */ + util::DestroyAt(sema->cv_not_zero); + util::DestroyAt(sema->cs_sema); + } + + void AcquireSemaphore(SemaphoreType *sema) { + AMS_ASSERT(sema->state == SemaphoreType::State_Initialized); + + { + std::scoped_lock lk(GetReference(sema->cs_sema)); + + while (sema->count == 0) { + GetReference(sema->cv_not_zero).Wait(GetPointer(sema->cs_sema)); + } + + --sema->count; + } + } + + bool TryAcquireSemaphore(SemaphoreType *sema) { + AMS_ASSERT(sema->state == SemaphoreType::State_Initialized); + + { + std::scoped_lock lk(GetReference(sema->cs_sema)); + + if (sema->count == 0) { + return false; + } + + --sema->count; + } + + return true; + } + + bool TimedAcquireSemaphore(SemaphoreType *sema, TimeSpan timeout) { + AMS_ASSERT(sema->state == SemaphoreType::State_Initialized); + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + + { + impl::TimeoutHelper timeout_helper(timeout); + std::scoped_lock lk(GetReference(sema->cs_sema)); + + while (sema->count == 0) { + if (timeout_helper.TimedOut()) { + return false; + } + GetReference(sema->cv_not_zero).TimedWait(GetPointer(sema->cs_sema), timeout_helper); + } + + --sema->count; + } + + return true; + } + + void ReleaseSemaphore(SemaphoreType *sema) { + AMS_ASSERT(sema->state == SemaphoreType::State_Initialized); + + { + std::scoped_lock lk(GetReference(sema->cs_sema)); + + AMS_ASSERT(sema->count + 1 <= sema->max_count); + + ++sema->count; + + GetReference(sema->cv_not_zero).Signal(); + GetReference(sema->waitlist).WakeupAllMultiWaitThreadsUnsafe(); + } + } + + void ReleaseSemaphore(SemaphoreType *sema, s32 count) { + AMS_ASSERT(sema->state == SemaphoreType::State_Initialized); + + { + std::scoped_lock lk(GetReference(sema->cs_sema)); + + AMS_ASSERT(sema->count + count <= sema->max_count); + + sema->count += count; + + GetReference(sema->cv_not_zero).Broadcast(); + GetReference(sema->waitlist).WakeupAllMultiWaitThreadsUnsafe(); + } + } + + s32 GetCurrentSemaphoreCount(const SemaphoreType *sema) { + AMS_ASSERT(sema->state == SemaphoreType::State_Initialized); + + return sema->count; + } + + void InitializeMultiWaitHolder(MultiWaitHolderType *multi_wait_holder, SemaphoreType *sema) { + AMS_ASSERT(sema->state == SemaphoreType::State_Initialized); + + util::ConstructAt(GetReference(multi_wait_holder->impl_storage).holder_of_semaphore_storage, sema); + + multi_wait_holder->user_data = 0; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_shared_memory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_shared_memory.cpp new file mode 100644 index 00000000..e16ecb0b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_shared_memory.cpp @@ -0,0 +1,157 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_thread_manager.hpp" +#include "impl/os_shared_memory_impl.hpp" + +namespace ams::os { + + namespace { + + void SetupSharedMemoryType(SharedMemoryType *shared_memory, size_t size, NativeHandle handle, bool managed) { + /* Set members. */ + shared_memory->handle = handle; + shared_memory->size = size; + shared_memory->address = nullptr; + shared_memory->allocated = false; + + /* Set managed. */ + shared_memory->handle_managed = managed; + + /* Create the critical section. */ + util::ConstructAt(shared_memory->cs_shared_memory); + } + + } + + Result CreateSharedMemory(SharedMemoryType *shared_memory, size_t size, MemoryPermission my_perm, MemoryPermission other_perm) { + /* Check pre-conditions. */ + AMS_ASSERT(size > 0); + AMS_ASSERT(util::IsAligned(size, MemoryPageSize)); + + /* Create the memory. */ + NativeHandle handle; + R_TRY(impl::SharedMemoryImpl::Create(std::addressof(handle), size, my_perm, other_perm)); + + /* Setup the object. */ + SetupSharedMemoryType(shared_memory, size, handle, true); + + R_SUCCEED(); + } + + void AttachSharedMemory(SharedMemoryType *shared_memory, size_t size, NativeHandle handle, bool managed) { + /* Check pre-conditions. */ + AMS_ASSERT(size > 0); + AMS_ASSERT(util::IsAligned(size, MemoryPageSize)); + AMS_ASSERT(handle != os::InvalidNativeHandle); + + /* Setup the object. */ + SetupSharedMemoryType(shared_memory, size, handle, managed); + } + + void DestroySharedMemory(SharedMemoryType *shared_memory) { + /* Unmap the shared memory, if required. */ + if (shared_memory->state == SharedMemoryType::State_Mapped) { + UnmapSharedMemory(shared_memory); + } + + /* Check the state. */ + AMS_ASSERT(shared_memory->state == SharedMemoryType::State_Initialized); + + /* Set state to not initialized. */ + shared_memory->state = SharedMemoryType::State_NotInitialized; + + /* Close the handle, if it's managed. */ + if (shared_memory->handle_managed) { + impl::SharedMemoryImpl::Close(shared_memory->handle); + } + shared_memory->handle_managed = false; + + /* Clear members. */ + shared_memory->address = nullptr; + shared_memory->size = 0; + shared_memory->handle = os::InvalidNativeHandle; + + /* Destroy the critical section. */ + util::DestroyAt(shared_memory->cs_shared_memory); + } + + void *MapSharedMemory(SharedMemoryType *shared_memory, MemoryPermission perm) { + /* Lock the current thread, and then the shared memory. */ + std::scoped_lock thread_lk(util::GetReference(impl::GetCurrentThread()->cs_thread)); + std::scoped_lock lk(util::GetReference(shared_memory->cs_shared_memory)); + + /* Ensure we're in a mappable state. */ + AMS_ASSERT(shared_memory->state == SharedMemoryType::State_Initialized); + + /* Try to map. */ + void *mapped_address; + if (R_FAILED(impl::SharedMemoryImpl::Map(std::addressof(mapped_address), shared_memory->handle, shared_memory->size, perm))) { + return nullptr; + } + + /* Set fields now that we've mapped successfully. */ + shared_memory->allocated = true; + shared_memory->address = mapped_address; + shared_memory->state = SharedMemoryType::State_Mapped; + + return mapped_address; + } + + void UnmapSharedMemory(SharedMemoryType *shared_memory) { + /* Lock the memory. */ + std::scoped_lock lk(util::GetReference(shared_memory->cs_shared_memory)); + + /* If the memory isn't mapped, we can't unmap it. */ + if (shared_memory->state != SharedMemoryType::State_Mapped) { + return; + } + + /* Unmap the memory. */ + impl::SharedMemoryImpl::Unmap(shared_memory->handle, shared_memory->address, shared_memory->size); + + /* Unmapped memory is necessarily not allocated. */ + if (shared_memory->allocated) { + shared_memory->allocated = false; + } + + /* Clear the address. */ + shared_memory->address = nullptr; + shared_memory->state = SharedMemoryType::State_Initialized; + } + + void *GetSharedMemoryAddress(const SharedMemoryType *shared_memory) { + /* Check pre-conditions. */ + AMS_ASSERT(shared_memory->state == SharedMemoryType::State_Initialized || shared_memory->state == SharedMemoryType::State_Mapped); + + return shared_memory->address; + } + + size_t GetSharedMemorySize(const SharedMemoryType *shared_memory) { + /* Check pre-conditions. */ + AMS_ASSERT(shared_memory->state == SharedMemoryType::State_Initialized || shared_memory->state == SharedMemoryType::State_Mapped); + + return shared_memory->size; + } + + NativeHandle GetSharedMemoryHandle(const SharedMemoryType *shared_memory) { + /* Check pre-conditions. */ + AMS_ASSERT(shared_memory->state == SharedMemoryType::State_Initialized || shared_memory->state == SharedMemoryType::State_Mapped); + + return shared_memory->handle; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_system_event.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_system_event.cpp new file mode 100644 index 00000000..7c4e6676 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_system_event.cpp @@ -0,0 +1,134 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_multiple_wait_holder_impl.hpp" +#include "impl/os_inter_process_event.hpp" +#include "impl/os_timeout_helper.hpp" + +namespace ams::os { + + Result CreateSystemEvent(SystemEventType *event, EventClearMode clear_mode, bool inter_process) { + if (inter_process) { + R_TRY(impl::CreateInterProcessEvent(std::addressof(event->inter_process_event), clear_mode)); + event->state = SystemEventType::State_InitializedAsInterProcessEvent; + } else { + InitializeEvent(std::addressof(event->event), false, clear_mode); + event->state = SystemEventType::State_InitializedAsEvent; + } + R_SUCCEED(); + } + + void DestroySystemEvent(SystemEventType *event) { + auto state = event->state; + event->state = SystemEventType::State_NotInitialized; + + switch (state) { + case SystemEventType::State_InitializedAsInterProcessEvent: impl::DestroyInterProcessEvent(std::addressof(event->inter_process_event)); break; + case SystemEventType::State_InitializedAsEvent: FinalizeEvent(std::addressof(event->event)); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void AttachSystemEvent(SystemEventType *event, NativeHandle read_handle, bool read_handle_managed, NativeHandle write_handle, bool write_handle_managed, EventClearMode clear_mode) { + AMS_ASSERT(read_handle != os::InvalidNativeHandle || write_handle != os::InvalidNativeHandle); + impl::AttachInterProcessEvent(std::addressof(event->inter_process_event), read_handle, read_handle_managed, write_handle, write_handle_managed, clear_mode); + event->state = SystemEventType::State_InitializedAsInterProcessEvent; + } + + void AttachReadableHandleToSystemEvent(SystemEventType *event, NativeHandle read_handle, bool manage_read_handle, EventClearMode clear_mode) { + return AttachSystemEvent(event, read_handle, manage_read_handle, os::InvalidNativeHandle, false, clear_mode); + } + + void AttachWritableHandleToSystemEvent(SystemEventType *event, NativeHandle write_handle, bool manage_write_handle, EventClearMode clear_mode) { + return AttachSystemEvent(event, os::InvalidNativeHandle, false, write_handle, manage_write_handle, clear_mode); + } + + NativeHandle DetachReadableHandleOfSystemEvent(SystemEventType *event) { + AMS_ASSERT(event->state == SystemEventType::State_InitializedAsInterProcessEvent); + return impl::DetachReadableHandleOfInterProcessEvent(std::addressof(event->inter_process_event)); + } + + NativeHandle DetachWritableHandleOfSystemEvent(SystemEventType *event) { + AMS_ASSERT(event->state == SystemEventType::State_InitializedAsInterProcessEvent); + return impl::DetachWritableHandleOfInterProcessEvent(std::addressof(event->inter_process_event)); + } + + NativeHandle GetReadableHandleOfSystemEvent(const SystemEventType *event) { + AMS_ASSERT(event->state == SystemEventType::State_InitializedAsInterProcessEvent); + return impl::GetReadableHandleOfInterProcessEvent(std::addressof(event->inter_process_event)); + } + + NativeHandle GetWritableHandleOfSystemEvent(const SystemEventType *event) { + AMS_ASSERT(event->state == SystemEventType::State_InitializedAsInterProcessEvent); + return impl::GetWritableHandleOfInterProcessEvent(std::addressof(event->inter_process_event)); + + } + + void SignalSystemEvent(SystemEventType *event) { + switch (event->state) { + case SystemEventType::State_InitializedAsInterProcessEvent: return impl::SignalInterProcessEvent(std::addressof(event->inter_process_event)); + case SystemEventType::State_InitializedAsEvent: return SignalEvent(std::addressof(event->event)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void WaitSystemEvent(SystemEventType *event) { + switch (event->state) { + case SystemEventType::State_InitializedAsInterProcessEvent: return impl::WaitInterProcessEvent(std::addressof(event->inter_process_event)); + case SystemEventType::State_InitializedAsEvent: return WaitEvent(std::addressof(event->event)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + bool TryWaitSystemEvent(SystemEventType *event) { + switch (event->state) { + case SystemEventType::State_InitializedAsInterProcessEvent: return impl::TryWaitInterProcessEvent(std::addressof(event->inter_process_event)); + case SystemEventType::State_InitializedAsEvent: return TryWaitEvent(std::addressof(event->event)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + bool TimedWaitSystemEvent(SystemEventType *event, TimeSpan timeout) { + AMS_ASSERT(timeout.GetNanoSeconds() >= 0); + + switch (event->state) { + case SystemEventType::State_InitializedAsInterProcessEvent: return impl::TimedWaitInterProcessEvent(std::addressof(event->inter_process_event), timeout); + case SystemEventType::State_InitializedAsEvent: return TimedWaitEvent(std::addressof(event->event), timeout); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void ClearSystemEvent(SystemEventType *event) { + switch (event->state) { + case SystemEventType::State_InitializedAsInterProcessEvent: return impl::ClearInterProcessEvent(std::addressof(event->inter_process_event)); + case SystemEventType::State_InitializedAsEvent: return ClearEvent(std::addressof(event->event)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void InitializeMultiWaitHolder(MultiWaitHolderType *multi_wait_holder, SystemEventType *event) { + switch (event->state) { + case SystemEventType::State_InitializedAsInterProcessEvent: + util::ConstructAt(GetReference(multi_wait_holder->impl_storage).holder_of_inter_process_event_storage, std::addressof(event->inter_process_event)); + break; + case SystemEventType::State_InitializedAsEvent: + util::ConstructAt(GetReference(multi_wait_holder->impl_storage).holder_of_event_storage, std::addressof(event->event)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_thread.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_thread.cpp new file mode 100644 index 00000000..6b397189 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_thread.cpp @@ -0,0 +1,206 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_thread_manager.hpp" +#include "impl/os_timeout_helper.hpp" +#include "impl/os_multiple_wait_holder_impl.hpp" + +namespace ams::os { + + namespace { + + size_t CheckThreadNameLength(const char *name) { + const char *cur = name; + for (size_t len = 0; len < ThreadNameLengthMax; ++len) { + if (*(cur++) == 0) { + return len; + } + } + + AMS_ABORT("ThreadNameLength too large"); + } + + void ValidateThreadArguments(ThreadType *thread, void *stack, size_t stack_size, s32 priority) { + AMS_ASSERT(stack != nullptr); + AMS_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(stack), ThreadStackAlignment)); + AMS_ASSERT(stack_size > 0); + AMS_ASSERT(util::IsAligned(stack_size, ThreadStackAlignment)); + + AMS_UNUSED(thread, stack, stack_size, priority); + } + + } + + Result CreateThread(ThreadType *thread, ThreadFunction function, void *argument, void *stack, size_t stack_size, s32 priority, s32 ideal_core) { + ValidateThreadArguments(thread, stack, stack_size, priority); + AMS_ASSERT(GetThreadAvailableCoreMask() & (1ul << ideal_core)); + R_RETURN(impl::GetThreadManager().CreateThread(thread, function, argument, stack, stack_size, priority, ideal_core)); + } + + Result CreateThread(ThreadType *thread, ThreadFunction function, void *argument, void *stack, size_t stack_size, s32 priority) { + ValidateThreadArguments(thread, stack, stack_size, priority); + R_RETURN(impl::GetThreadManager().CreateThread(thread, function, argument, stack, stack_size, priority)); + } + + void DestroyThread(ThreadType *thread) { + auto &manager = impl::GetThreadManager(); + + AMS_ASSERT(thread->state == ThreadType::State_Initialized || thread->state == ThreadType::State_Started || thread->state == ThreadType::State_Terminated); + AMS_ASSERT(thread != manager.GetMainThread()); + + manager.DestroyThread(thread); + } + + void StartThread(ThreadType *thread) { + AMS_ASSERT(thread->state == ThreadType::State_Initialized); + impl::GetThreadManager().StartThread(thread); + } + + ThreadType *GetCurrentThread() { + return impl::GetCurrentThread(); + } + + void WaitThread(ThreadType *thread) { + AMS_ASSERT(thread != impl::GetCurrentThread()); + AMS_ASSERT(thread->state == ThreadType::State_Initialized || thread->state == ThreadType::State_Started || thread->state == ThreadType::State_Terminated); + + return impl::GetThreadManager().WaitThread(thread); + } + + bool TryWaitThread(ThreadType *thread) { + AMS_ASSERT(thread->state == ThreadType::State_Initialized || thread->state == ThreadType::State_Started || thread->state == ThreadType::State_Terminated); + return impl::GetThreadManager().TryWaitThread(thread); + } + + void YieldThread() { + return impl::GetThreadManager().YieldThread(); + } + + void SleepThread(TimeSpan time) { + impl::TimeoutHelper::Sleep(time); + } + + s32 SuspendThread(ThreadType *thread) { + AMS_ASSERT(thread->state == ThreadType::State_Started); + AMS_ASSERT(thread != impl::GetCurrentThread()); + + return impl::GetThreadManager().SuspendThread(thread); + } + + s32 ResumeThread(ThreadType *thread) { + AMS_ASSERT(thread->state == ThreadType::State_Started); + + return impl::GetThreadManager().ResumeThread(thread); + } + + s32 GetThreadSuspendCount(const ThreadType *thread) { + return thread->suspend_count; + } + + /* TODO: void GetThreadContext(ThreadContextInfo *out_context, const ThreadType *thread); */ + + s32 ChangeThreadPriority(ThreadType *thread, s32 priority) { + AMS_ASSERT(thread->state == ThreadType::State_Initialized || thread->state == ThreadType::State_DestroyedBeforeStarted || thread->state == ThreadType::State_Started || thread->state == ThreadType::State_Terminated); + { + std::scoped_lock lk(GetReference(thread->cs_thread)); + + const s32 prev_prio = thread->base_priority; + + const bool success = impl::GetThreadManager().ChangePriority(thread, priority); + AMS_ASSERT(success); + AMS_UNUSED(success); + + thread->base_priority = priority; + + return prev_prio; + } + } + + s32 GetThreadPriority(const ThreadType *thread) { + AMS_ASSERT(thread->state == ThreadType::State_Initialized || thread->state == ThreadType::State_DestroyedBeforeStarted || thread->state == ThreadType::State_Started || thread->state == ThreadType::State_Terminated); + return thread->base_priority; + } + + s32 GetThreadCurrentPriority(const ThreadType *thread) { + AMS_ASSERT(thread->state == ThreadType::State_Initialized || thread->state == ThreadType::State_DestroyedBeforeStarted || thread->state == ThreadType::State_Started || thread->state == ThreadType::State_Terminated); + return impl::GetThreadManager().GetCurrentPriority(thread); + } + + void SetThreadName(ThreadType *thread, const char *name) { + AMS_ASSERT(thread->state == ThreadType::State_Initialized || thread->state == ThreadType::State_DestroyedBeforeStarted || thread->state == ThreadType::State_Started || thread->state == ThreadType::State_Terminated); + + if (name == nullptr) { + impl::GetThreadManager().SetInitialThreadNameUnsafe(thread); + return; + } + + const size_t name_size = CheckThreadNameLength(name) + 1; + std::memcpy(thread->name_buffer, name, name_size); + SetThreadNamePointer(thread, thread->name_buffer); + } + + void SetThreadNamePointer(ThreadType *thread, const char *name) { + AMS_ASSERT(thread->state == ThreadType::State_Initialized || thread->state == ThreadType::State_DestroyedBeforeStarted || thread->state == ThreadType::State_Started || thread->state == ThreadType::State_Terminated); + + if (name == nullptr) { + impl::GetThreadManager().SetInitialThreadNameUnsafe(thread); + return; + } + + thread->name_pointer = name; + impl::GetThreadManager().NotifyThreadNameChanged(thread); + } + + const char *GetThreadNamePointer(const ThreadType *thread) { + AMS_ASSERT(thread->state == ThreadType::State_Initialized || thread->state == ThreadType::State_DestroyedBeforeStarted || thread->state == ThreadType::State_Started || thread->state == ThreadType::State_Terminated); + + return thread->name_pointer; + } + + s32 GetCurrentCoreNumber() { + return impl::GetThreadManager().GetCurrentCoreNumber(); + } + + s32 GetCurrentProcessorNumber() { + return GetCurrentCoreNumber(); + } + + void SetThreadCoreMask(ThreadType *thread, s32 ideal_core, u64 affinity_mask) { + AMS_ASSERT(ideal_core == IdealCoreDontCare || ideal_core == IdealCoreUseDefault || ideal_core == IdealCoreNoUpdate || (0 <= ideal_core && ideal_core < impl::CoreAffinityMaskBitWidth)); + if (ideal_core != IdealCoreUseDefault) { + AMS_ASSERT(affinity_mask != 0); + AMS_ASSERT((affinity_mask & ~GetThreadAvailableCoreMask()) == 0); + } + if (ideal_core >= 0) { + AMS_ASSERT((affinity_mask & (1ul << ideal_core)) != 0); + } + + return impl::GetThreadManager().SetThreadCoreMask(thread, ideal_core, affinity_mask); + } + + void GetThreadCoreMask(s32 *out_ideal_core, u64 *out_affinity_mask, const ThreadType *thread) { + return impl::GetThreadManager().GetThreadCoreMask(out_ideal_core, out_affinity_mask, thread); + } + + u64 GetThreadAvailableCoreMask() { + return impl::GetThreadManager().GetThreadAvailableCoreMask(); + } + + ThreadId GetThreadId(const ThreadType *thread) { + return impl::GetThreadManager().GetThreadId(thread); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_thread_local_storage_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_thread_local_storage_api.cpp new file mode 100644 index 00000000..3e362a1a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_thread_local_storage_api.cpp @@ -0,0 +1,72 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_thread_manager.hpp" +#include "impl/os_tls_manager.hpp" + +namespace ams::os { + + #if defined(ATMOSPHERE_OS_HORIZON) + namespace { + + using LibnxTlsDestructor = void (*)(void *); + + } + + Result AllocateTlsSlot(TlsSlot *out, TlsDestructor destructor) { + s32 slot = ::threadTlsAlloc(reinterpret_cast<LibnxTlsDestructor>(reinterpret_cast<void *>(destructor))); + R_UNLESS(slot >= 0, os::ResultOutOfResource()); + + *out = { static_cast<u32>(slot) }; + R_SUCCEED(); + } + + void FreeTlsSlot(TlsSlot slot) { + ::threadTlsFree(static_cast<s32>(slot._value)); + } + + uintptr_t GetTlsValue(TlsSlot slot) { + return reinterpret_cast<uintptr_t>(::threadTlsGet(static_cast<s32>(slot._value))); + } + + void SetTlsValue(TlsSlot slot, uintptr_t value) { + ::threadTlsSet(static_cast<s32>(slot._value), reinterpret_cast<void *>(value)); + } + #else + + Result AllocateTlsSlot(TlsSlot *out, TlsDestructor destructor) { + R_UNLESS(impl::GetTlsManager().AllocateTlsSlot(out, destructor, false), os::ResultOutOfResource()); + R_SUCCEED(); + } + + void FreeTlsSlot(TlsSlot slot) { + AMS_ASSERT(slot._value < impl::TotalTlsSlotCountMax); + impl::GetTlsManager().FreeTlsSlot(slot); + } + + uintptr_t GetTlsValue(TlsSlot slot) { + AMS_ASSERT(slot._value < impl::TotalTlsSlotCountMax); + return impl::GetCurrentThread()->tls_value_array[slot._value]; + } + + void SetTlsValue(TlsSlot slot, uintptr_t value) { + AMS_ASSERT(slot._value < impl::TotalTlsSlotCountMax); + impl::GetCurrentThread()->tls_value_array[slot._value] = value; + } + + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_tick.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_tick.cpp new file mode 100644 index 00000000..11621e9c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_tick.cpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_tick_manager.hpp" + +namespace ams::os { + + Tick GetSystemTick() { + return impl::GetTickManager().GetTick(); + } + + Tick GetSystemTickOrdered() { + return impl::GetTickManager().GetSystemTickOrdered(); + } + + s64 GetSystemTickFrequency() { + return impl::GetTickManager().GetTickFrequency(); + } + + TimeSpan ConvertToTimeSpan(Tick tick) { + return impl::GetTickManager().ConvertToTimeSpan(tick); + } + + Tick ConvertToTick(TimeSpan ts) { + return impl::GetTickManager().ConvertToTick(ts); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_timer_event.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_timer_event.cpp new file mode 100644 index 00000000..6f941856 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_timer_event.cpp @@ -0,0 +1,263 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_timer_event_helper.hpp" +#include "impl/os_tick_manager.hpp" +#include "impl/os_timeout_helper.hpp" +#include "impl/os_multiple_wait_object_list.hpp" +#include "impl/os_multiple_wait_holder_impl.hpp" + +namespace ams::os { + + namespace { + + ALWAYS_INLINE u64 GetBroadcastCounterUnsafe(TimerEventType *event) { + const u64 upper = event->broadcast_counter_high; + return (upper << BITSIZEOF(event->broadcast_counter_low)) | event->broadcast_counter_low; + } + + ALWAYS_INLINE void IncrementBroadcastCounterUnsafe(TimerEventType *event) { + if ((++event->broadcast_counter_low) == 0) { + ++event->broadcast_counter_high; + } + } + + ALWAYS_INLINE void SignalTimerEventImplUnsafe(TimerEventType *event) { + /* Set signaled. */ + event->signaled = true; + + /* Signal! */ + if (event->clear_mode == EventClearMode_ManualClear) { + /* If we're manual clear, increment counter and wake all. */ + IncrementBroadcastCounterUnsafe(event); + } else { + /* If we're auto clear, signal one thread, which will clear. */ + GetReference(event->cv_signaled).Signal(); + } + + /* Wake up whatever manager, if any. */ + GetReference(event->multi_wait_object_list_storage).WakeupAllMultiWaitThreadsUnsafe(); + } + + } + + void InitializeTimerEvent(TimerEventType *event, EventClearMode clear_mode) { + /* Initialize internal variables. */ + util::ConstructAt(event->cs_timer_event); + util::ConstructAt(event->cv_signaled); + + /* Initialize the multi wait object list. */ + util::ConstructAt(event->multi_wait_object_list_storage); + + /* Initialize member variables. */ + event->clear_mode = static_cast<u8>(clear_mode); + event->signaled = false; + event->timer_state = TimerEventType::TimerState_Stop; + event->broadcast_counter_low = 0; + event->broadcast_counter_high = 0; + + GetReference(event->next_time_to_wakeup) = 0; + GetReference(event->first) = 0; + GetReference(event->interval) = 0; + + /* Mark initialized. */ + event->state = TimerEventType::State_Initialized; + } + + void FinalizeTimerEvent(TimerEventType *event) { + + /* Mark uninitialized. */ + event->state = TimerEventType::State_NotInitialized; + + /* Destroy objects. */ + util::DestroyAt(event->multi_wait_object_list_storage); + util::DestroyAt(event->cv_signaled); + util::DestroyAt(event->cs_timer_event); + } + + void StartOneShotTimerEvent(TimerEventType *event, TimeSpan first_time) { + AMS_ASSERT(event->state == TimerEventType::State_Initialized); + AMS_ASSERT(first_time >= 0); + + { + std::scoped_lock lk(GetReference(event->cs_timer_event)); + + /* Get the current time. */ + TimeSpan cur_time = impl::GetCurrentTick().ToTimeSpan(); + + /* Set tracking timespans. */ + GetReference(event->next_time_to_wakeup) = impl::SaturatedAdd(cur_time, first_time); + GetReference(event->first) = first_time; + GetReference(event->interval) = 0; + + /* Set state to OneShot. */ + event->timer_state = TimerEventType::TimerState_OneShot; + + /* Signal. */ + GetReference(event->cv_signaled).Broadcast(); + + /* Wake up whatever manager, if any. */ + GetReference(event->multi_wait_object_list_storage).WakeupAllMultiWaitThreadsUnsafe(); + } + } + + void StartPeriodicTimerEvent(TimerEventType *event, TimeSpan first_time, TimeSpan interval) { + AMS_ASSERT(event->state == TimerEventType::State_Initialized); + AMS_ASSERT(first_time >= 0); + AMS_ASSERT(interval > 0); + + { + std::scoped_lock lk(GetReference(event->cs_timer_event)); + + /* Get the current time. */ + TimeSpan cur_time = impl::GetCurrentTick().ToTimeSpan(); + + /* Set tracking timespans. */ + GetReference(event->next_time_to_wakeup) = impl::SaturatedAdd(cur_time, first_time); + GetReference(event->first) = first_time; + GetReference(event->interval) = interval; + + /* Set state to Periodic. */ + event->timer_state = TimerEventType::TimerState_Periodic; + + /* Signal. */ + GetReference(event->cv_signaled).Broadcast(); + + /* Wake up whatever manager, if any. */ + GetReference(event->multi_wait_object_list_storage).WakeupAllMultiWaitThreadsUnsafe(); + } + } + + void StopTimerEvent(TimerEventType *event) { + AMS_ASSERT(event->state == TimerEventType::State_Initialized); + + { + std::scoped_lock lk(GetReference(event->cs_timer_event)); + + /* Stop the event. */ + impl::StopTimerUnsafe(event); + + /* Signal. */ + GetReference(event->cv_signaled).Broadcast(); + + /* Wake up whatever manager, if any. */ + GetReference(event->multi_wait_object_list_storage).WakeupAllMultiWaitThreadsUnsafe(); + } + } + + void WaitTimerEvent(TimerEventType *event) { + AMS_ASSERT(event->state == TimerEventType::State_Initialized); + + { + std::scoped_lock lk(GetReference(event->cs_timer_event)); + + const auto cur_counter = GetBroadcastCounterUnsafe(event); + while (!event->signaled) { + if (cur_counter != GetBroadcastCounterUnsafe(event)) { + break; + } + + /* Update. */ + auto cur_time = impl::GetCurrentTick().ToTimeSpan(); + if (impl::UpdateSignalStateAndRecalcNextTimeToWakeupUnsafe(event, cur_time)) { + SignalTimerEventImplUnsafe(event); + break; + } + + /* Check state. */ + if (event->timer_state == TimerEventType::TimerState_Stop) { + GetReference(event->cv_signaled).Wait(GetPointer(event->cs_timer_event)); + } else { + TimeSpan next_time = GetReference(event->next_time_to_wakeup); + impl::TimeoutHelper helper(next_time - cur_time); + GetReference(event->cv_signaled).TimedWait(GetPointer(event->cs_timer_event), helper); + } + } + + if (event->clear_mode == EventClearMode_AutoClear) { + event->signaled = false; + } + } + } + + bool TryWaitTimerEvent(TimerEventType *event) { + AMS_ASSERT(event->state == TimerEventType::State_Initialized); + + { + std::scoped_lock lk(GetReference(event->cs_timer_event)); + + /* Update. */ + auto cur_time = impl::GetCurrentTick().ToTimeSpan(); + if (impl::UpdateSignalStateAndRecalcNextTimeToWakeupUnsafe(event, cur_time)) { + SignalTimerEventImplUnsafe(event); + } + + bool prev = event->signaled; + + if (event->clear_mode == EventClearMode_AutoClear) { + event->signaled = false; + } + + return prev; + } + } + + void SignalTimerEvent(TimerEventType *event) { + AMS_ASSERT(event->state == TimerEventType::State_Initialized); + + { + std::scoped_lock lk(GetReference(event->cs_timer_event)); + + /* If we're signaled, nothing to do. */ + if (event->signaled) { + return; + } + + /* Update. */ + auto cur_time = impl::GetCurrentTick().ToTimeSpan(); + impl::UpdateSignalStateAndRecalcNextTimeToWakeupUnsafe(event, cur_time); + + /* Signal. */ + SignalTimerEventImplUnsafe(event); + } + } + + void ClearTimerEvent(TimerEventType *event) { + AMS_ASSERT(event->state == TimerEventType::State_Initialized); + + { + std::scoped_lock lk(GetReference(event->cs_timer_event)); + + /* Update. */ + auto cur_time = impl::GetCurrentTick().ToTimeSpan(); + if (impl::UpdateSignalStateAndRecalcNextTimeToWakeupUnsafe(event, cur_time)) { + SignalTimerEventImplUnsafe(event); + } + + /* Clear. */ + event->signaled = false; + } + } + + void InitializeMultiWaitHolder(MultiWaitHolderType *multi_wait_holder, TimerEventType *event) { + AMS_ASSERT(event->state == EventType::State_Initialized); + + util::ConstructAt(GetReference(multi_wait_holder->impl_storage).holder_of_timer_event_storage, event); + + multi_wait_holder->user_data = 0; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_transfer_memory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_transfer_memory.cpp new file mode 100644 index 00000000..3051d45f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_transfer_memory.cpp @@ -0,0 +1,153 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_thread_manager.hpp" +#include "impl/os_transfer_memory_impl.hpp" + +namespace ams::os { + + namespace { + + inline void SetupTransferMemoryType(TransferMemoryType *tmem, size_t size, NativeHandle handle, bool managed) { + /* Set members. */ + tmem->handle = handle; + tmem->size = size; + tmem->address = nullptr; + tmem->allocated = false; + + /* Set managed. */ + tmem->handle_managed = managed; + + /* Create the critical section. */ + util::ConstructAt(tmem->cs_transfer_memory); + } + + } + + Result CreateTransferMemory(TransferMemoryType *tmem, void *address, size_t size, MemoryPermission perm) { + /* Validate pre-conditions. */ + AMS_ASSERT(size > 0); + AMS_ASSERT(util::IsAligned(size, os::MemoryPageSize)); + AMS_ASSERT(address != nullptr); + AMS_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(address), os::MemoryPageSize)); + + /* Create the memory. */ + NativeHandle handle; + R_TRY(impl::TransferMemoryImpl::Create(std::addressof(handle), address, size, perm)); + + /* Setup the object. */ + SetupTransferMemoryType(tmem, size, handle, true); + + R_SUCCEED(); + } + + void AttachTransferMemory(TransferMemoryType *tmem, size_t size, NativeHandle handle, bool managed) { + AMS_ASSERT(size > 0); + AMS_ASSERT(util::IsAligned(size, os::MemoryPageSize)); + AMS_ASSERT(handle != os::InvalidNativeHandle); + + /* Setup the object. */ + SetupTransferMemoryType(tmem, size, handle, managed); + } + + NativeHandle DetachTransferMemory(TransferMemoryType *tmem) { + AMS_ASSERT(tmem->state == TransferMemoryType::State_Created); + + /* Set state to detached. */ + tmem->state = TransferMemoryType::State_Detached; + + /* Clear handle. */ + const NativeHandle handle = tmem->handle; + + tmem->handle = os::InvalidNativeHandle; + tmem->handle_managed = false; + + return handle; + } + + void DestroyTransferMemory(TransferMemoryType *tmem) { + /* Unmap the transfer memory, if required. */ + if (tmem->state == TransferMemoryType::State_Mapped) { + UnmapTransferMemory(tmem); + } + + /* Check the state is valid. */ + AMS_ASSERT(tmem->state == TransferMemoryType::State_Created || tmem->state == TransferMemoryType::State_Detached); + + /* Set state to not initialized. */ + tmem->state = TransferMemoryType::State_NotInitialized; + + /* Close the handle, if it's managed. */ + if (tmem->handle_managed) { + impl::TransferMemoryImpl::Close(tmem->handle); + } + tmem->handle_managed = false; + + /* Clear members. */ + tmem->address = nullptr; + tmem->size = 0; + tmem->handle = os::InvalidNativeHandle; + + /* Destroy the critical section. */ + util::DestroyAt(tmem->cs_transfer_memory); + } + + Result MapTransferMemory(void **out, TransferMemoryType *tmem, MemoryPermission owner_perm) { + /* Lock the current thread, and then the transfer memory. */ + std::scoped_lock thread_lk(GetReference(impl::GetCurrentThread()->cs_thread)); + std::scoped_lock lk(GetReference(tmem->cs_transfer_memory)); + + /* Ensure we're in a mappable state. */ + AMS_ASSERT(tmem->state == TransferMemoryType::State_Created); + + /* Map. */ + void *mapped_address; + R_TRY(impl::TransferMemoryImpl::Map(std::addressof(mapped_address), tmem->handle, tmem->size, owner_perm)); + + /* Set fields now that we've mapped. */ + tmem->allocated = true; + tmem->address = mapped_address; + tmem->state = TransferMemoryType::State_Mapped; + + /* Set output address. */ + *out = mapped_address; + + R_SUCCEED(); + } + + void UnmapTransferMemory(TransferMemoryType *tmem) { + /* Lock the memory. */ + std::scoped_lock lk(GetReference(tmem->cs_transfer_memory)); + + /* If the memory isn't mapped, we can't unmap it. */ + if (tmem->state != TransferMemoryType::State_Mapped) { + return; + } + + /* Unmap the memory. */ + impl::TransferMemoryImpl::Unmap(tmem->handle, tmem->address, tmem->size); + + /* Unmapped memory is necessarily not allocated. */ + if (tmem->allocated) { + tmem->allocated = false; + } + + /* Clear the address. */ + tmem->address = nullptr; + tmem->state = TransferMemoryType::State_Created; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_unsafe_memory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_unsafe_memory.cpp new file mode 100644 index 00000000..2b0431c8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_unsafe_memory.cpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_unsafe_memory_impl.hpp" + +namespace ams::os { + + Result AllocateUnsafeMemory(uintptr_t *out_address, size_t size) { + /* Check arguments. */ + AMS_ASSERT(size > 0); + AMS_ASSERT(util::IsAligned(size, os::MemoryPageSize)); + + /* Allocate memory. */ + R_RETURN(impl::UnsafeMemoryImpl::AllocateUnsafeMemoryImpl(out_address, size)); + } + + Result FreeUnsafeMemory(uintptr_t address, size_t size) { + /* Check arguments. */ + AMS_ASSERT(util::IsAligned(address, os::MemoryPageSize)); + AMS_ASSERT(util::IsAligned(size, os::MemoryPageSize)); + + /* Free memory. */ + R_RETURN(impl::UnsafeMemoryImpl::FreeUnsafeMemoryImpl(address, size)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_virtual_address_memory.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_virtual_address_memory.cpp new file mode 100644 index 00000000..f36bf155 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/os/os_virtual_address_memory.cpp @@ -0,0 +1,67 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/os_vamm_manager.hpp" + +namespace ams::os { + + void InitializeVirtualAddressMemory() { + return impl::GetVammManager().InitializeIfEnabled(); + } + + Result AllocateAddressRegion(uintptr_t *out, size_t size) { + AMS_ASSERT(size > 0); + AMS_ASSERT(util::IsAligned(size, MemoryPageSize)); + + R_RETURN(impl::GetVammManager().AllocateAddressRegion(out, size)); + } + + Result AllocateMemory(uintptr_t *out, size_t size) { + AMS_ASSERT(size > 0); + AMS_ASSERT(util::IsAligned(size, MemoryPageSize)); + + R_RETURN(impl::GetVammManager().AllocateMemory(out, size)); + } + + Result AllocateMemoryPages(uintptr_t address, size_t size) { + AMS_ASSERT(size > 0); + AMS_ASSERT(util::IsAligned(size, MemoryPageSize)); + AMS_ASSERT(util::IsAligned(address, MemoryPageSize)); + + R_RETURN(impl::GetVammManager().AllocateMemoryPages(address, size)); + } + + Result FreeAddressRegion(uintptr_t address) { + R_RETURN(impl::GetVammManager().FreeAddressRegion(address)); + } + + Result FreeMemoryPages(uintptr_t address, size_t size) { + AMS_ASSERT(size > 0); + AMS_ASSERT(util::IsAligned(size, MemoryPageSize)); + AMS_ASSERT(util::IsAligned(address, MemoryPageSize)); + + R_RETURN(impl::GetVammManager().FreeMemoryPages(address, size)); + } + + VirtualAddressMemoryResourceUsage GetVirtualAddressMemoryResourceUsage() { + return impl::GetVammManager().GetVirtualAddressMemoryResourceUsage(); + } + + bool IsVirtualAddressMemoryEnabled() { + return impl::VammManager::IsVirtualAddressMemoryEnabled(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_info.generic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_info.generic.hpp new file mode 100644 index 00000000..82ce6960 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_info.generic.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "osdbg_types.hpp" + +namespace ams::osdbg::impl { + + class ThreadInfoGenericImpl { + public: + static Result FillWithCurrentInfo(ThreadInfo *info) { + /* TODO */ + AMS_UNUSED(info); + R_THROW(osdbg::ResultCannotGetThreadInfo()); + } + }; + + using ThreadInfoImpl = ThreadInfoGenericImpl; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_info.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_info.os.horizon.cpp new file mode 100644 index 00000000..53c29107 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_info.os.horizon.cpp @@ -0,0 +1,176 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "osdbg_thread_info.os.horizon.hpp" +#include "osdbg_thread_type.os.horizon.hpp" +#include "osdbg_thread_local_region.os.horizon.hpp" +#include "../../os/impl/os_thread_manager_impl.os.horizon.hpp" + +namespace ams::osdbg::impl { + + namespace { + + s32 ConvertToUserPriority(s32 horizon_priority) { + return horizon_priority - os::impl::UserThreadPriorityOffset; + } + + s32 GetCurrentThreadPriorityImpl(const ThreadInfo *info) { + u64 dummy; + u32 horizon_priority; + if (R_FAILED(svc::GetDebugThreadParam(std::addressof(dummy), std::addressof(horizon_priority), info->_debug_handle, info->_debug_info_create_thread.thread_id, svc::DebugThreadParam_Priority))) { + return info->_base_priority; + } + + return ConvertToUserPriority(static_cast<s32>(horizon_priority)); + } + + void FillWithCurrentInfoImpl(ThreadInfo *info, const auto &thread_type_impl) { + /* Set fields. */ + info->_base_priority = thread_type_impl._base_priority; + info->_current_priority = GetCurrentThreadPriorityImpl(info); + info->_stack_size = thread_type_impl._stack_size; + info->_stack = thread_type_impl._stack; + info->_argument = thread_type_impl._argument; + info->_function = thread_type_impl._thread_function; + info->_name_pointer = thread_type_impl._name_pointer; + } + + } + + Result ThreadInfoHorizonImpl::FillWithCurrentInfo(ThreadInfo *info) { + /* Detect lp64. */ + const bool is_lp64 = IsLp64(info); + + /* Ensure that we have a thread type. */ + if (info->_thread_type == nullptr) { + /* Ensure we exit with correct thread type. */ + auto thread_guard = SCOPE_GUARD { info->_thread_type = nullptr; }; + + /* Set the target thread type. */ + GetTargetThreadType(info); + + /* If it's still nullptr, we failed to get the thread type. */ + R_UNLESS(info->_thread_type != nullptr, osdbg::ResultCannotGetThreadInfo()); + + /* Check that the thread type is valid. */ + R_UNLESS(info->_thread_type_type != ThreadTypeType_Unknown, osdbg::ResultUnsupportedThreadVersion()); + + /* We successfully got the thread type. */ + thread_guard.Cancel(); + } + + /* Read and process the thread type. */ + ThreadTypeCommon thread_type; + + switch (info->_thread_type_type) { + case ThreadTypeType_Nintendo: + if (is_lp64) { + /* Read in the thread type. */ + R_TRY(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(thread_type.lp64)), info->_debug_handle, reinterpret_cast<uintptr_t>(info->_thread_type), sizeof(thread_type.lp64))); + + /* Process different versions. */ + switch (thread_type.lp64._version) { + case 0x0000: + case 0xFFFF: + FillWithCurrentInfoImpl(info, thread_type.lp64_v0); + break; + case 0x0001: + FillWithCurrentInfoImpl(info, thread_type.lp64); + break; + default: + R_THROW(osdbg::ResultUnsupportedThreadVersion()); + } + } else { + /* Read in the thread type. */ + R_TRY(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(thread_type.ilp32)), info->_debug_handle, reinterpret_cast<uintptr_t>(info->_thread_type), sizeof(thread_type.ilp32))); + + /* Process different versions. */ + switch (thread_type.ilp32._version) { + case 0x0000: + case 0xFFFF: + FillWithCurrentInfoImpl(info, thread_type.ilp32_v0); + break; + case 0x0001: + FillWithCurrentInfoImpl(info, thread_type.ilp32); + break; + default: + R_THROW(osdbg::ResultUnsupportedThreadVersion()); + } + } + break; + case ThreadTypeType_Stratosphere: + { + /* Read in the thread type. */ + R_TRY(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(thread_type.stratosphere)), info->_debug_handle, reinterpret_cast<uintptr_t>(info->_thread_type), sizeof(thread_type.stratosphere))); + + /* Set fields. */ + const auto &thread_type_impl = thread_type.stratosphere; + + /* Check that our thread version is valid. */ + R_UNLESS(thread_type_impl.version == 0x0000 || thread_type_impl.version == 0xFFFF, osdbg::ResultUnsupportedThreadVersion()); + + info->_base_priority = thread_type_impl.base_priority; + info->_current_priority = GetCurrentThreadPriorityImpl(info); + info->_stack_size = thread_type_impl.stack_size; + info->_stack = reinterpret_cast<uintptr_t>(thread_type_impl.stack); + info->_argument = reinterpret_cast<uintptr_t>(thread_type_impl.argument); + info->_function = reinterpret_cast<uintptr_t>(thread_type_impl.function); + info->_name_pointer = reinterpret_cast<uintptr_t>(thread_type_impl.name_pointer); + } + break; + case ThreadTypeType_Libnx: + { + /* Read in the thread type. */ + R_TRY(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(thread_type.libnx)), info->_debug_handle, reinterpret_cast<uintptr_t>(info->_thread_type), sizeof(thread_type.libnx))); + + /* Set fields. */ + const auto &thread_type_impl = thread_type.libnx; + + /* NOTE: libnx does not store/track base priority anywhere. */ + info->_base_priority = -1; + info->_current_priority = GetCurrentThreadPriorityImpl(info); + if (info->_current_priority != info->_base_priority) { + info->_base_priority = info->_current_priority; + } + info->_stack_size = thread_type_impl.stack_sz; + info->_stack = reinterpret_cast<uintptr_t>(thread_type_impl.stack_mirror); + + /* Parse thread entry args. */ + { + LibnxThreadEntryArgs thread_entry_args; + + if (R_SUCCEEDED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(thread_entry_args)), info->_debug_handle, info->_stack + info->_stack_size, sizeof(LibnxThreadEntryArgs)))) { + info->_argument = thread_entry_args.arg; + info->_function = thread_entry_args.entry; + } else { + /* Failed to read the argument/function. */ + info->_argument = 0; + info->_function = 0; + } + } + + /* Libnx threads don't have names. */ + info->_name_pointer = 0; + } + break; + default: + R_THROW(osdbg::ResultUnsupportedThreadVersion()); + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_info.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_info.os.horizon.hpp new file mode 100644 index 00000000..e43fec1e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_info.os.horizon.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "osdbg_types.hpp" + +namespace ams::osdbg::impl { + + class ThreadInfoHorizonImpl { + public: + static Result FillWithCurrentInfo(ThreadInfo *info); + }; + + using ThreadInfoImpl = ThreadInfoHorizonImpl; + + constexpr inline bool IsLp64(const ThreadInfo *info) { + const auto as = info->_debug_info_create_process.flags & svc::CreateProcessFlag_AddressSpaceMask; + return as == svc::CreateProcessFlag_AddressSpace64Bit || as == svc::CreateProcessFlag_AddressSpace64BitDeprecated; + } + + constexpr inline bool Is64BitArch(const ThreadInfo *info) { + return (info->_debug_info_create_process.flags & svc::CreateProcessFlag_Is64Bit); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_local_region.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_local_region.os.horizon.cpp new file mode 100644 index 00000000..4cad7a7b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_local_region.os.horizon.cpp @@ -0,0 +1,142 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "osdbg_thread_info.os.horizon.hpp" +#include "osdbg_thread_type.os.horizon.hpp" +#include "osdbg_thread_local_region.os.horizon.hpp" + +namespace ams::osdbg::impl { + + namespace { + + Result GetThreadTypePointerFromThreadLocalRegion(uintptr_t *out, ThreadInfo *info) { + /* Detect lp64. */ + const bool is_lp64 = IsLp64(info); + + /* Get the thread local region. */ + const auto *tlr_address = GetTargetThreadLocalRegion(info); + + /* Read the thread local region. */ + ThreadLocalRegionCommon tlr; + R_TRY(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(tlr)), info->_debug_handle, reinterpret_cast<uintptr_t>(tlr_address), sizeof(tlr))); + + /* Detect libnx vs nintendo via magic number. */ + if (tlr.libnx.thread_vars.magic == LibnxThreadVars::Magic) { + info->_thread_type_type = ThreadTypeType_Libnx; + *out = reinterpret_cast<uintptr_t>(tlr.libnx.thread_vars.thread_ptr); + } else { + info->_thread_type_type = ThreadTypeType_Nintendo; + *out = is_lp64 ? tlr.lp64.p_thread_type : tlr.ilp32.p_thread_type; + } + + R_SUCCEED(); + } + + Result GetThreadArgumentAndStackPointer(u64 *out_arg, u64 *out_sp, ThreadInfo *info) { + /* Read the thread context. */ + svc::ThreadContext thread_context; + R_TRY(svc::GetDebugThreadContext(std::addressof(thread_context), info->_debug_handle, info->_debug_info_create_thread.thread_id, svc::ThreadContextFlag_General | svc::ThreadContextFlag_Control)); + + /* Argument is in r0. */ + *out_arg = thread_context.r[0]; + + /* Stack pointer varies by architecture. */ + if (Is64BitArch(info)) { + *out_sp = thread_context.sp; + } else { + *out_sp = thread_context.r[13]; + } + + R_SUCCEED(); + } + + void DetectStratosphereThread(ThreadInfo *info) { + /* Stratosphere threads are initially misdetected as libnx threads. */ + if (info->_thread_type_type != ThreadTypeType_Libnx || info->_thread_type == nullptr) { + return; + } + + /* Convert to a parent pointer. */ + os::ThreadType *stratosphere_ptr = util::GetParentPointer<&os::ThreadType::thread_impl_storage>(std::addressof(info->_thread_type->libnx)); + + /* Read the magic. */ + u16 magic; + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(magic)), info->_debug_handle, reinterpret_cast<uintptr_t>(std::addressof(stratosphere_ptr->magic)), sizeof(magic)))) { + return; + } + + /* Check the magic. */ + if (magic == os::ThreadType::Magic) { + info->_thread_type_type = ThreadTypeType_Stratosphere; + info->_thread_type = reinterpret_cast<ThreadTypeCommon *>(stratosphere_ptr); + } + } + + } + + void GetTargetThreadType(ThreadInfo *info) { + /* Ensure we exit with correct state. */ + auto type_guard = SCOPE_GUARD { + info->_thread_type = nullptr; + info->_thread_type_type = ThreadTypeType_Unknown; + }; + + /* Read the thread type pointer. */ + uintptr_t tlr_thread_type; + if (R_FAILED(GetThreadTypePointerFromThreadLocalRegion(std::addressof(tlr_thread_type), info))) { + return; + } + + /* Handle the case where we have a thread type. */ + if (tlr_thread_type != 0) { + info->_thread_type = reinterpret_cast<ThreadTypeCommon *>(tlr_thread_type); + DetectStratosphereThread(info); + type_guard.Cancel(); + return; + } + + /* Otherwise, the thread is just created, and we should read its context. */ + u64 arg, sp; + if (R_FAILED(GetThreadArgumentAndStackPointer(std::addressof(arg), std::addressof(sp), info))) { + return; + } + + /* We may have been bamboozled into thinking a nintendo thread was a libnx thread, so check that. */ + /* Nintendo threads have argument=ThreadType, libnx threads have argument=ThreadEntryArgs. */ + if (info->_thread_type_type == ThreadTypeType_Nintendo && sp == arg) { + /* It's a libnx thread, so we should parse the entry args. */ + info->_thread_type_type = ThreadTypeType_Libnx; + + /* Read the entry args. */ + LibnxThreadEntryArgs entry_args; + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(entry_args)), info->_debug_handle, arg, sizeof(entry_args)))) { + return; + } + + info->_thread_type = reinterpret_cast<ThreadTypeCommon *>(entry_args.t); + } else { + info->_thread_type_type = ThreadTypeType_Nintendo; + info->_thread_type = reinterpret_cast<ThreadTypeCommon *>(arg); + } + + /* If we got the thread type, we don't need to reset our state. */ + if (info->_thread_type != nullptr) { + type_guard.Cancel(); + DetectStratosphereThread(info); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_local_region.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_local_region.os.horizon.hpp new file mode 100644 index 00000000..b29d39ab --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_local_region.os.horizon.hpp @@ -0,0 +1,100 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "osdbg_types.hpp" + +namespace ams::osdbg::impl { + + struct ThreadLocalRegionLp64 { + u32 message_buffer[0x100 / sizeof(u32)]; + volatile u16 disable_counter; + volatile u16 interrupt_flag; + u32 reserved0; + u64 reserved[15]; + u64 tls[10]; + u64 locale_ptr; + s64 _errno_val; + u64 thread_data; + u64 eh_globals; + u64 thread_pointer; + u64 p_thread_type; + }; + static_assert(sizeof(ThreadLocalRegionLp64) == sizeof(svc::ThreadLocalRegion)); + static_assert(AMS_OFFSETOF(ThreadLocalRegionLp64, tls) == 0x180); + + + struct ThreadLocalRegionIlp32 { + u32 message_buffer[0x100 / sizeof(u32)]; + volatile u16 disable_counter; + volatile u16 interrupt_flag; + u32 reserved[(0xC0 - 0x4) / sizeof(u32)]; + u32 tls[10]; + u32 locale_ptr; + s32 _errno_val; + u32 thread_data; + u32 eh_globals; + u32 thread_pointer; + u32 p_thread_type; + }; + static_assert(sizeof(ThreadLocalRegionIlp32) == sizeof(svc::ThreadLocalRegion)); + static_assert(AMS_OFFSETOF(ThreadLocalRegionIlp32, tls) == 0x1C0); + + struct LibnxThreadVars { + static constexpr u32 Magic = util::ReverseFourCC<'!','T','V','$'>::Code; + + u32 magic; + ::Handle handle; + ::Thread *thread_ptr; + void *reent; + void *tls_tp; + }; + static_assert(sizeof(LibnxThreadVars) == 0x20); + + struct ThreadLocalRegionLibnx { + u32 message_buffer[0x100 / sizeof(u32)]; + volatile u16 disable_counter; + volatile u16 interrupt_flag; + u32 reserved0; + u64 tls[(0x200 - sizeof(LibnxThreadVars) - 0x108) / sizeof(u64)]; + LibnxThreadVars thread_vars; + }; + static_assert(sizeof(ThreadLocalRegionLibnx) == sizeof(svc::ThreadLocalRegion)); + static_assert(AMS_OFFSETOF(ThreadLocalRegionLibnx, thread_vars) == 0x200 - sizeof(LibnxThreadVars)); + + struct LibnxThreadEntryArgs { + u64 t; + u64 entry; + u64 arg; + u64 reent; + u64 tls; + u64 padding; + }; + + union ThreadLocalRegionCommon { + ThreadLocalRegionIlp32 ilp32; + ThreadLocalRegionLp64 lp64; + ThreadLocalRegionLibnx libnx; + }; + static_assert(sizeof(ThreadLocalRegionCommon) == sizeof(svc::ThreadLocalRegion)); + + inline ThreadLocalRegionCommon *GetTargetThreadLocalRegion(ThreadInfo *info) { + return reinterpret_cast<ThreadLocalRegionCommon *>(info->_debug_info_create_thread.tls_address); + } + + void GetTargetThreadType(ThreadInfo *info); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_type.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_type.os.horizon.hpp new file mode 100644 index 00000000..3b1e89b1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_thread_type.os.horizon.hpp @@ -0,0 +1,149 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "osdbg_types.hpp" + +namespace ams::osdbg::impl { + + /* Check that our values are the same as Nintendo's. */ + static_assert(os::TlsSlotCountMax == 16); + static_assert(os::SdkTlsSlotCountMax == 16); + static_assert(os::ThreadNameLengthMax == 32); + + struct ThreadTypeIlp32 { + AlignedStorageIlp32<0, 2, alignof(u32)> _all_threads_node; + AlignedStorageIlp32<0, 2, alignof(u32)> _multi_wait_object_list; + u32 _padding[4]; + u8 _state; + bool _stack_is_aliased; + bool _auto_registered; + u8 _suspend_count; + s16 _base_priority; + u16 _version; + u32 _original_stack; + u32 _stack; + u32 _stack_size; + u32 _argument; + u32 _thread_function; + u32 _current_fiber; + u32 _initial_fiber; + u32 _tls_value_array[os::TlsSlotCountMax + os::SdkTlsSlotCountMax]; + char _name_buffer[os::ThreadNameLengthMax]; + u32 _name_pointer; + AlignedStorageIlp32<4, 0, alignof(u32)> _cs_thread; + AlignedStorageIlp32<4, 0, alignof(u32)> _cv_thread; + AlignedStorageIlp32<4, 0, alignof(u32)> _handle; + u32 _lock_history; + u32 _thread_id_low; + u32 _thread_id_high; + }; + static_assert(sizeof(ThreadTypeIlp32) == 0x100); + + struct ThreadTypeIlp32Version0 { + AlignedStorageIlp32<0, 2, alignof(u32)> _all_threads_node; + AlignedStorageIlp32<0, 2, alignof(u32)> _multi_wait_object_list; + u32 _padding[4]; + u8 _state; + bool _stack_is_aliased; + bool _auto_registered; + u8 _padding1; + s32 _base_priority; + u32 _original_stack; + u32 _stack; + u32 _stack_size; + u32 _argument; + u32 _thread_function; + u32 _current_fiber; + u32 _initial_fiber; + u32 _lock_history; + u32 _tls_value_array[os::TlsSlotCountMax + os::SdkTlsSlotCountMax]; + char _name_buffer[os::ThreadNameLengthMax]; + u32 _name_pointer; + AlignedStorageIlp32<4, 0, alignof(u32)> _cs_thread; + AlignedStorageIlp32<4, 0, alignof(u32)> _cv_thread; + AlignedStorageIlp32<4, 0, alignof(u32)> _handle; + char _padding2[8]; + }; + static_assert(sizeof(ThreadTypeIlp32Version0) == 0x100); + + struct ThreadTypeLp64 { + AlignedStorageLp64<0, 2, alignof(u64)> _all_threads_node; + AlignedStorageLp64<0, 2, alignof(u64)> _multi_wait_object_list; + u64 _padding[4]; + u8 _state; + bool _stack_is_aliased; + bool _auto_registered; + u8 _suspend_count; + s16 _base_priority; + u16 _version; + u64 _original_stack; + u64 _stack; + u64 _stack_size; + u64 _argument; + u64 _thread_function; + u64 _current_fiber; + u64 _initial_fiber; + u64 _tls_value_array[os::TlsSlotCountMax + os::SdkTlsSlotCountMax]; + char _name_buffer[os::ThreadNameLengthMax]; + u64 _name_pointer; + AlignedStorageLp64<4, 0, alignof(u32)> _cs_thread; + AlignedStorageLp64<4, 0, alignof(u32)> _cv_thread; + AlignedStorageLp64<4, 0, alignof(u32)> _handle; + u32 _lock_history; + u64 thread_id; + }; + static_assert(sizeof(ThreadTypeLp64) == 0x1C0); + + struct ThreadTypeLp64Version0 { + AlignedStorageLp64<0, 2, alignof(u64)> _all_threads_node; + AlignedStorageLp64<0, 2, alignof(u64)> _multi_wait_object_list; + u64 _padding[4]; + u8 _state; + bool _stack_is_aliased; + bool _auto_registered; + u8 _suspend_count; + s16 _base_priority; + u16 _version; + u64 _original_stack; + u64 _stack; + u64 _stack_size; + u64 _argument; + u64 _thread_function; + u64 _current_fiber; + u64 _initial_fiber; + u32 _lock_history; + u32 _padding2; + u64 _tls_value_array[os::TlsSlotCountMax + os::SdkTlsSlotCountMax]; + char _name_buffer[os::ThreadNameLengthMax]; + u64 _name_pointer; + AlignedStorageLp64<4, 0, alignof(u32)> _cs_thread; + AlignedStorageLp64<4, 0, alignof(u32)> _cv_thread; + AlignedStorageLp64<4, 0, alignof(u32)> _handle; + u32 _padding3; + }; + static_assert(sizeof(ThreadTypeLp64Version0) == 0x1C0); + + union ThreadTypeCommon { + ThreadTypeIlp32 ilp32; + ThreadTypeLp64 lp64; + ThreadTypeIlp32Version0 ilp32_v0; + ThreadTypeLp64Version0 lp64_v0; + os::ThreadType stratosphere; + ::Thread libnx; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_types.hpp new file mode 100644 index 00000000..a579ff44 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/impl/osdbg_types.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::osdbg::impl { + + template<size_t Size, int NumPointers, size_t Alignment> + struct AlignedStorageIlp32 { + alignas(Alignment) std::byte _storage[Size + NumPointers * sizeof(u32)]; + }; + + template<size_t Size, int NumPointers, size_t Alignment> + struct AlignedStorageLp64 { + alignas(Alignment) std::byte _storage[Size + NumPointers * sizeof(u64)]; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/osdbg_thread.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/osdbg_thread.cpp new file mode 100644 index 00000000..555c02b1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/osdbg/osdbg_thread.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "impl/osdbg_thread_info.os.horizon.hpp" +#else + #include "impl/osdbg_thread_info.generic.hpp" +#endif + +namespace ams::osdbg { + + Result InitializeThreadInfo(ThreadInfo *thread_info, os::NativeHandle debug_handle, const osdbg::DebugInfoCreateProcess *create_process, const osdbg::DebugInfoCreateThread *create_thread) { + /* Set basic fields. */ + thread_info->_thread_type = nullptr; + thread_info->_thread_type_type = ThreadTypeType_Unknown; + thread_info->_debug_handle = debug_handle; + #if defined(ATMOSPHERE_OS_HORIZON) + thread_info->_debug_info_create_process = *create_process; + thread_info->_debug_info_create_thread = *create_thread; + #else + AMS_UNUSED(create_process, create_thread); + #endif + + /* Update the current info. */ + R_RETURN(impl::ThreadInfoImpl::FillWithCurrentInfo(thread_info)); + } + + Result UpdateThreadInfo(ThreadInfo *thread_info) { + /* Update the current info. */ + R_RETURN(impl::ThreadInfoImpl::FillWithCurrentInfo(thread_info)); + } + + Result GetThreadName(char *dst, const ThreadInfo *thread_info) { + #if defined(ATMOSPHERE_OS_HORIZON) + /* Read the name. */ + if (const auto name_pointer = GetThreadNamePointer(thread_info); name_pointer != 0) { + R_RETURN(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(dst), thread_info->_debug_handle, name_pointer, os::ThreadNameLengthMax)); + } else { + /* Special-case libnx threads. */ + if (thread_info->_thread_type_type == ThreadTypeType_Libnx) { + util::TSNPrintf(dst, os::ThreadNameLengthMax, "libnx Thread_0x%010" PRIx64 "", reinterpret_cast<uintptr_t>(thread_info->_thread_type)); + } else { + util::TSNPrintf(dst, os::ThreadNameLengthMax, "Thread_0x%010" PRIx64 "", reinterpret_cast<uintptr_t>(thread_info->_thread_type)); + } + + R_SUCCEED(); + } + #else + util::TSNPrintf(dst, os::ThreadNameLengthMax, "Thread_0x%010" PRIx64 "", static_cast<u64>(reinterpret_cast<uintptr_t>(thread_info->_thread_type))); + R_SUCCEED(); + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/patcher/patcher_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/patcher/patcher_api.cpp new file mode 100644 index 00000000..5d493b3e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/patcher/patcher_api.cpp @@ -0,0 +1,287 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +/* IPS Patching adapted from Luma3DS (https://github.com/AuroraWright/Luma3DS/blob/master/sysmodules/loader/source/patcher.c) */ + +namespace ams::patcher { + + namespace { + + /* Convenience definitions. */ + constexpr const char IpsHeadMagic[5] = {'P', 'A', 'T', 'C', 'H'}; + constexpr const char IpsTailMagic[3] = {'E', 'O', 'F'}; + constexpr const char Ips32HeadMagic[5] = {'I', 'P', 'S', '3', '2'}; + constexpr const char Ips32TailMagic[4] = {'E', 'E', 'O', 'F'}; + constexpr const char *IpsFileExtension = ".ips"; + constexpr size_t IpsFileExtensionLength = util::Strlen(IpsFileExtension); + constexpr size_t ModuleIpsPatchLength = 2 * sizeof(ro::ModuleId) + IpsFileExtensionLength; + + /* Global data. */ + constinit os::SdkMutex g_apply_patch_lock; + constinit u8 g_patch_read_buffer[os::MemoryPageSize]; + + /* Helpers. */ + inline u8 ConvertHexNybble(const char nybble) { + if ('0' <= nybble && nybble <= '9') { + return nybble - '0'; + } else if ('a' <= nybble && nybble <= 'f') { + return nybble - 'a' + 0xa; + } else { + return nybble - 'A' + 0xA; + } + } + + bool ParseModuleIdFromPath(ro::ModuleId *out_module_id, const char *name, size_t name_len, size_t extension_len) { + /* Validate name is hex module id. */ + for (unsigned int i = 0; i < name_len - extension_len; i++) { + if (!std::isxdigit(static_cast<unsigned char>(name[i]))) { + return false; + } + } + + /* Read module id from name. */ + std::memset(out_module_id, 0, sizeof(*out_module_id)); + for (unsigned int name_ofs = 0, id_ofs = 0; name_ofs < name_len - extension_len && id_ofs < sizeof(*out_module_id); id_ofs++) { + out_module_id->data[id_ofs] |= ConvertHexNybble(name[name_ofs++]) << 4; + out_module_id->data[id_ofs] |= ConvertHexNybble(name[name_ofs++]); + } + + return true; + } + + bool MatchesModuleId(const char *name, size_t name_len, size_t extension_len, const ro::ModuleId *module_id) { + /* Get module id. */ + ro::ModuleId module_id_from_name; + if (!ParseModuleIdFromPath(std::addressof(module_id_from_name), name, name_len, extension_len)) { + return false; + } + + return std::memcmp(std::addressof(module_id_from_name), module_id, sizeof(*module_id)) == 0; + } + + bool IsIpsFileForModule(const char *name, const ro::ModuleId *module_id) { + const size_t name_len = std::strlen(name); + + /* The path must be correct size for a module id (with trailing zeroes optionally trimmed) + ".ips". */ + if (!(IpsFileExtensionLength < name_len && name_len <= ModuleIpsPatchLength)) { + return false; + } + + /* The path must be an even number of characters to conform. */ + if (!util::IsAligned(name_len, 2)) { + return false; + } + + /* The path needs to end with .ips. */ + if (std::strcmp(name + name_len - IpsFileExtensionLength, IpsFileExtension) != 0) { + return false; + } + + /* The path needs to match the module id. */ + return MatchesModuleId(name, name_len, IpsFileExtensionLength, module_id); + } + + inline bool IsIpsTail(bool is_ips32, u8 *buffer) { + if (is_ips32) { + return std::memcmp(buffer, Ips32TailMagic, sizeof(Ips32TailMagic)) == 0; + } else { + return std::memcmp(buffer, IpsTailMagic, sizeof(IpsTailMagic)) == 0; + } + } + + inline u32 GetIpsPatchOffset(bool is_ips32, u8 *buffer) { + if (is_ips32) { + return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | (buffer[3]); + } else { + return (buffer[0] << 16) | (buffer[1] << 8) | (buffer[2]); + } + } + + inline u32 GetIpsPatchSize(bool is_ips32, u8 *buffer) { + AMS_UNUSED(is_ips32); + return (buffer[0] << 8) | (buffer[1]); + } + + void ApplyIpsPatch(u8 *mapped_module, size_t mapped_size, size_t protected_size, size_t offset, bool is_ips32, fs::FileHandle file) { + /* Validate offset/protected size. */ + AMS_ABORT_UNLESS(offset <= protected_size); + + s64 file_offset = sizeof(IpsHeadMagic); + auto ReadData = [&](void *dst, size_t size) ALWAYS_INLINE_LAMBDA { + R_ABORT_UNLESS(fs::ReadFile(file, file_offset, dst, size)); + file_offset += size; + }; + + u8 buffer[sizeof(Ips32TailMagic)]; + while (true) { + ReadData(buffer, is_ips32 ? sizeof(Ips32TailMagic) : sizeof(IpsTailMagic)); + + if (IsIpsTail(is_ips32, buffer)) { + break; + } + + /* Offset of patch. */ + u32 patch_offset = GetIpsPatchOffset(is_ips32, buffer); + + /* Size of patch. */ + ReadData(buffer, 2); + u32 patch_size = GetIpsPatchSize(is_ips32, buffer); + + /* Check for RLE encoding. */ + if (patch_size == 0) { + /* Size of RLE. */ + ReadData(buffer, 2); + + u32 rle_size = (buffer[0] << 8) | (buffer[1]); + + /* Value for RLE. */ + ReadData(buffer, 1); + + /* Ensure we don't write to protected region. */ + if (patch_offset < protected_size) { + if (patch_offset + rle_size > protected_size) { + const u32 diff = protected_size - patch_offset; + patch_offset += diff; + rle_size -= diff; + } else { + continue; + } + } + + /* Adjust offset, if relevant. */ + patch_offset -= offset; + + /* Apply patch. */ + if (patch_offset + rle_size > mapped_size) { + AMS_ABORT_UNLESS(patch_offset <= mapped_size); + rle_size = mapped_size - patch_offset; + } + std::memset(mapped_module + patch_offset, buffer[0], rle_size); + } else { + /* Ensure we don't write to protected region. */ + if (patch_offset < protected_size) { + if (patch_offset + patch_size > protected_size) { + const u32 diff = protected_size - patch_offset; + patch_offset += diff; + patch_size -= diff; + file_offset += diff; + } else { + file_offset += patch_size; + continue; + } + } + + /* Adjust offset, if relevant. */ + patch_offset -= offset; + + /* Apply patch. */ + u32 read_size = patch_size; + if (patch_offset + read_size > mapped_size) { + AMS_ABORT_UNLESS(patch_offset <= mapped_size); + read_size = mapped_size - patch_offset; + } + { + size_t remaining = read_size; + size_t copy_offset = patch_offset; + while (remaining > 0) { + const size_t cur_read = std::min(remaining, sizeof(g_patch_read_buffer)); + ReadData(g_patch_read_buffer, cur_read); + std::memcpy(mapped_module + copy_offset, g_patch_read_buffer, cur_read); + remaining -= cur_read; + copy_offset += cur_read; + } + } + if (patch_size > read_size) { + file_offset += patch_size - read_size; + } + } + } + } + + } + + void LocateAndApplyIpsPatchesToModule(const char *mount_name, const char *patch_dir_name, size_t protected_size, size_t offset, const ro::ModuleId *module_id, u8 *mapped_module, size_t mapped_size) { + /* Ensure only one thread tries to apply patches at a time. */ + std::scoped_lock lk(g_apply_patch_lock); + + /* Inspect all patches from /atmosphere/<patch_dir>/<*>/<*>.ips */ + char path[fs::EntryNameLengthMax + 1]; + util::SNPrintf(path, sizeof(path), "%s:/atmosphere/%s", mount_name, patch_dir_name); + const size_t patches_dir_path_len = std::strlen(path); + + /* Open the patch directory. */ + fs::DirectoryHandle patches_dir; + if (R_FAILED(fs::OpenDirectory(std::addressof(patches_dir), path, fs::OpenDirectoryMode_Directory))) { + return; + } + ON_SCOPE_EXIT { fs::CloseDirectory(patches_dir); }; + + /* Iterate over the patches directory to find patch subdirectories. */ + while (true) { + /* Read the next entry. */ + s64 count; + fs::DirectoryEntry entry; + if (R_FAILED(fs::ReadDirectory(std::addressof(count), std::addressof(entry), patches_dir, 1)) || count == 0) { + break; + } + + /* Print the path for this directory. */ + util::SNPrintf(path + patches_dir_path_len, sizeof(path) - patches_dir_path_len, "/%s", entry.name); + const size_t patch_dir_path_len = patches_dir_path_len + 1 + std::strlen(entry.name); + + /* Open the patch directory. */ + fs::DirectoryHandle patch_dir; + if (R_FAILED(fs::OpenDirectory(std::addressof(patch_dir), path, fs::OpenDirectoryMode_File))) { + continue; + } + ON_SCOPE_EXIT { fs::CloseDirectory(patch_dir); }; + + /* Iterate over files in the patch directory. */ + while (true) { + if (R_FAILED(fs::ReadDirectory(std::addressof(count), std::addressof(entry), patch_dir, 1)) || count == 0) { + break; + } + + /* Check if this file is an ips. */ + if (!IsIpsFileForModule(entry.name, module_id)) { + continue; + } + + /* Print the path for this file. */ + util::SNPrintf(path + patch_dir_path_len, sizeof(path) - patch_dir_path_len, "/%s", entry.name); + + /* Open the file. */ + fs::FileHandle file; + if (R_FAILED(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Read))) { + continue; + } + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Read the header. */ + u8 header[sizeof(IpsHeadMagic)]; + if (R_SUCCEEDED(fs::ReadFile(file, 0, header, sizeof(header)))) { + if (std::memcmp(header, IpsHeadMagic, sizeof(header)) == 0) { + ApplyIpsPatch(mapped_module, mapped_size, protected_size, offset, false, file); + } else if (std::memcmp(header, Ips32HeadMagic, sizeof(header)) == 0) { + ApplyIpsPatch(mapped_module, mapped_size, protected_size, offset, true, file); + } + } + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/pgl_remote_event_observer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/pgl_remote_event_observer.hpp new file mode 100644 index 00000000..96f45f6e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/pgl_remote_event_observer.hpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::pgl { + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemoteEventObserver { + NON_COPYABLE(RemoteEventObserver); + NON_MOVEABLE(RemoteEventObserver); + private: + ::PglEventObserver m_observer; + public: + constexpr RemoteEventObserver(const ::PglEventObserver &o) : m_observer(o) { /* ... */ } + ~RemoteEventObserver() { + ::pglEventObserverClose(std::addressof(m_observer)); + } + + Result GetProcessEventHandle(ams::sf::OutCopyHandle out) { + ::Event ev; + ON_RESULT_SUCCESS { out.SetValue(ev.revent, true); }; + + R_RETURN(::pglEventObserverGetProcessEvent(std::addressof(m_observer), std::addressof(ev))); + } + + Result GetProcessEventInfo(ams::sf::Out<pm::ProcessEventInfo> out) { + static_assert(sizeof(*out.GetPointer()) == sizeof(::PmProcessEventInfo)); + R_RETURN(::pglEventObserverGetProcessEventInfo(std::addressof(m_observer), reinterpret_cast<::PmProcessEventInfo *>(out.GetPointer()))); + } + + Result GetProcessEventHandle(ams::tipc::OutCopyHandle out) { + ::Event ev; + ON_RESULT_SUCCESS { out.SetValue(ev.revent); }; + + R_RETURN(::pglEventObserverGetProcessEvent(std::addressof(m_observer), std::addressof(ev))); + } + + Result GetProcessEventInfo(ams::tipc::Out<pm::ProcessEventInfo> out) { + static_assert(sizeof(*out.GetPointer()) == sizeof(::PmProcessEventInfo)); + R_RETURN(::pglEventObserverGetProcessEventInfo(std::addressof(m_observer), reinterpret_cast<::PmProcessEventInfo *>(out.GetPointer()))); + } + }; + static_assert(pgl::sf::IsIEventObserver<RemoteEventObserver>); + static_assert(pgl::tipc::IsIEventObserver<RemoteEventObserver>); + #endif + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/pgl_shell_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/pgl_shell_api.cpp new file mode 100644 index 00000000..8bbd0da0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/pgl_shell_api.cpp @@ -0,0 +1,208 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pgl_remote_event_observer.hpp" + +namespace ams::pgl { + + namespace { + + struct PglEventObserverAllocator; + using RemoteAllocator = ams::sf::ExpHeapStaticAllocator<1_KB, PglEventObserverAllocator>; + using RemoteObjectFactory = ams::sf::ObjectFactory<typename RemoteAllocator::Policy>; + + class StaticAllocatorInitializer { + public: + StaticAllocatorInitializer() { + RemoteAllocator::Initialize(lmem::CreateOption_None); + } + } g_static_allocator_initializer; + + template<typename T, typename... Args> + T *AllocateFromStaticExpHeap(Args &&... args) { + T * const object = static_cast<T *>(RemoteAllocator::Allocate(sizeof(T))); + if (AMS_LIKELY(object != nullptr)) { + std::construct_at(object, std::forward<Args>(args)...); + } + return object; + } + + template<typename T> + void FreeToStaticExpHeap(T *object) { + return RemoteAllocator::Deallocate(object, sizeof(T)); + } + + template<typename T, typename... Args> requires std::derived_from<T, impl::EventObserverInterface> + EventObserver::UniquePtr MakeUniqueFromStaticExpHeap(Args &&... args) { + return EventObserver::UniquePtr{AllocateFromStaticExpHeap<T>(std::forward<Args>(args)...)}; + } + + } + + void EventObserver::Deleter::operator()(impl::EventObserverInterface *obj) { + FreeToStaticExpHeap(obj); + } + + #if defined(ATMOSPHERE_OS_HORIZON) + Result Initialize() { + R_RETURN(::pglInitialize()); + } + + void Finalize() { + return pglExit(); + } + + Result LaunchProgram(os::ProcessId *out, const ncm::ProgramLocation &loc, u32 process_flags, u8 pgl_flags) { + static_assert(sizeof(*out) == sizeof(u64)); + static_assert(sizeof(loc) == sizeof(::NcmProgramLocation)); + R_RETURN(::pglLaunchProgram(reinterpret_cast<u64 *>(out), reinterpret_cast<const ::NcmProgramLocation *>(std::addressof(loc)), process_flags, pgl_flags)); + } + + Result TerminateProcess(os::ProcessId process_id) { + R_RETURN(::pglTerminateProcess(static_cast<u64>(process_id))); + } + + Result LaunchProgramFromHost(os::ProcessId *out, const char *content_path, u32 process_flags) { + static_assert(sizeof(*out) == sizeof(u64)); + R_RETURN(::pglLaunchProgramFromHost(reinterpret_cast<u64 *>(out), content_path, process_flags)); + } + + Result GetHostContentMetaInfo(pgl::ContentMetaInfo *out, const char *content_path) { + static_assert(sizeof(*out) == sizeof(::PglContentMetaInfo)); + R_RETURN(::pglGetHostContentMetaInfo(reinterpret_cast<::PglContentMetaInfo *>(out), content_path)); + } + + Result GetApplicationProcessId(os::ProcessId *out) { + static_assert(sizeof(*out) == sizeof(u64)); + R_RETURN(::pglGetApplicationProcessId(reinterpret_cast<u64 *>(out))); + } + + Result BoostSystemMemoryResourceLimit(u64 size) { + R_RETURN(::pglBoostSystemMemoryResourceLimit(size)); + } + + Result IsProcessTracked(bool *out, os::ProcessId process_id) { + R_RETURN(::pglIsProcessTracked(out, static_cast<u64>(process_id))); + } + + Result EnableApplicationCrashReport(bool enabled) { + R_RETURN(::pglEnableApplicationCrashReport(enabled)); + } + + Result IsApplicationCrashReportEnabled(bool *out) { + R_RETURN(::pglIsApplicationCrashReportEnabled(out)); + } + + Result EnableApplicationAllThreadDumpOnCrash(bool enabled) { + R_RETURN(::pglEnableApplicationAllThreadDumpOnCrash(enabled)); + } + + Result TriggerApplicationSnapShotDumper(const char *arg, SnapShotDumpType dump_type) { + R_RETURN(::pglTriggerApplicationSnapShotDumper(static_cast<::PglSnapShotDumpType>(dump_type), arg)); + } + + Result GetEventObserver(pgl::EventObserver *out) { + ::PglEventObserver obs; + R_TRY(::pglGetEventObserver(std::addressof(obs))); + + if (hos::GetVersion() >= hos::Version_12_0_0) { + auto observer_holder = MakeUniqueFromStaticExpHeap<impl::EventObserverByTipc<RemoteEventObserver>>(obs); + R_UNLESS(observer_holder != nullptr, pgl::ResultOutOfMemory()); + + *out = pgl::EventObserver(std::move(observer_holder)); + } else { + auto remote_observer = RemoteObjectFactory::CreateSharedEmplaced<pgl::sf::IEventObserver, RemoteEventObserver>(obs); + R_UNLESS(remote_observer != nullptr, pgl::ResultOutOfMemory()); + + auto observer_holder = MakeUniqueFromStaticExpHeap<impl::EventObserverByCmif>(std::move(remote_observer)); + R_UNLESS(observer_holder != nullptr, pgl::ResultOutOfMemory()); + + *out = pgl::EventObserver(std::move(observer_holder)); + } + + R_SUCCEED(); + } + #else + Result Initialize() { + AMS_ABORT("TODO"); + } + + void Finalize() { + AMS_ABORT("TODO"); + } + + Result LaunchProgram(os::ProcessId *out, const ncm::ProgramLocation &loc, u32 process_flags, u8 pgl_flags) { + AMS_UNUSED(out, loc, process_flags, pgl_flags); + AMS_ABORT("TODO"); + } + + Result TerminateProcess(os::ProcessId process_id) { + AMS_UNUSED(process_id); + AMS_ABORT("TODO"); + } + + Result LaunchProgramFromHost(os::ProcessId *out, const char *content_path, u32 process_flags) { + AMS_UNUSED(out, content_path, process_flags); + AMS_ABORT("TODO"); + } + + Result GetHostContentMetaInfo(pgl::ContentMetaInfo *out, const char *content_path) { + AMS_UNUSED(out, content_path); + AMS_ABORT("TODO"); + } + + Result GetApplicationProcessId(os::ProcessId *out) { + AMS_UNUSED(out); + AMS_ABORT("TODO"); + } + + Result BoostSystemMemoryResourceLimit(u64 size) { + AMS_UNUSED(size); + AMS_ABORT("TODO"); + } + + Result IsProcessTracked(bool *out, os::ProcessId process_id) { + AMS_UNUSED(out, process_id); + AMS_ABORT("TODO"); + } + + Result EnableApplicationCrashReport(bool enabled) { + AMS_UNUSED(enabled); + AMS_ABORT("TODO"); + } + + Result IsApplicationCrashReportEnabled(bool *out) { + AMS_UNUSED(out); + AMS_ABORT("TODO"); + } + + Result EnableApplicationAllThreadDumpOnCrash(bool enabled) { + AMS_UNUSED(enabled); + AMS_ABORT("TODO"); + } + + Result TriggerApplicationSnapShotDumper(const char *arg, SnapShotDumpType dump_type) { + AMS_UNUSED(arg, dump_type); + AMS_ABORT("TODO"); + } + + Result GetEventObserver(pgl::EventObserver *out) { + AMS_UNUSED(out); + AMS_ABORT("TODO"); + } + #endif + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_api.cpp new file mode 100644 index 00000000..77c4580d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_api.cpp @@ -0,0 +1,207 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pgl_srv_shell.hpp" +#include "pgl_srv_shell_event_observer.hpp" +#include "pgl_srv_tipc_utils.hpp" + +namespace ams::pgl::srv { + + namespace { + + /* pgl. */ + enum PortIndex { + PortIndex_Shell, + PortIndex_Count, + }; + + constexpr sm::ServiceName ShellServiceName = sm::ServiceName::Encode("pgl"); + constexpr size_t ShellMaxSessions = 8; /* Official maximum is 6. */ + + using CmifServerManager = ams::sf::hipc::ServerManager<PortIndex_Count>; + + constexpr size_t ObserverMaxSessions = 4; + + using ShellPortMeta = ams::tipc::PortMeta<ShellMaxSessions + ObserverMaxSessions, pgl::tipc::IShellInterface, pgl::srv::ShellInterfaceTipc, ams::tipc::SingletonAllocator>; + + using TipcServerManager = ams::tipc::ServerManager<ShellPortMeta>; + + /* NOTE: Nintendo reserves only 0x2000 bytes for heap, which is used "mostly" to allocate shell event observers. */ + /* However, we would like very much for homebrew sysmodules to be able to subscribe to events if they so choose */ + /* And so we will use a larger heap (32 KB). Note that we reduce the heap size for tipc, where objects are */ + /* allocated statically. */ + /* We should have a smaller memory footprint than N in the end, regardless. */ + + struct CmifGlobals { + u8 heap_memory[32_KB]; + lmem::HeapHandle heap_handle; + ams::sf::ExpHeapAllocator server_allocator; + ams::sf::UnmanagedServiceObject<pgl::sf::IShellInterface, pgl::srv::ShellInterfaceCmif> shell_interface{std::addressof(server_allocator)}; + CmifServerManager server_manager; + }; + + struct TipcGlobals { + u8 heap_memory[24_KB]; + lmem::HeapHandle heap_handle; + TipcServerManager server_manager; + ams::tipc::SlabAllocator<ams::tipc::ServiceObject<pgl::tipc::IEventObserver, pgl::srv::ShellEventObserverTipc>, ObserverMaxSessions> observer_allocator; + }; + + constinit union { + util::TypedStorage<CmifGlobals> cmif; + util::TypedStorage<TipcGlobals> tipc; + } g_globals; + + ALWAYS_INLINE CmifGlobals &GetGlobalsForCmif() { + return GetReference(g_globals.cmif); + } + + ALWAYS_INLINE TipcGlobals &GetGlobalsForTipc() { + return GetReference(g_globals.tipc); + } + + ALWAYS_INLINE bool UseTipcServer() { + return hos::GetVersion() >= hos::Version_12_0_0; + } + + ALWAYS_INLINE lmem::HeapHandle GetHeapHandle() { + if (UseTipcServer()) { + return GetGlobalsForTipc().heap_handle; + } else { + return GetGlobalsForCmif().heap_handle; + } + } + + template<typename T> + ALWAYS_INLINE void InitializeHeapImpl(util::TypedStorage<T> &globals_storage) { + /* Construct the globals object. */ + util::ConstructAt(globals_storage); + + /* Get reference to the globals. */ + auto &globals = GetReference(globals_storage); + + /* Set the heap handle. */ + globals.heap_handle = lmem::CreateExpHeap(globals.heap_memory, sizeof(globals.heap_memory), lmem::CreateOption_ThreadSafe); + + /* If we should, setup the server allocator. */ + if constexpr (requires (T &t) { t.server_allocator; }) { + globals.server_allocator.Attach(globals.heap_handle); + } + } + + + void RegisterServiceSession() { + /* Register "pgl" with the appropriate server manager. */ + if (UseTipcServer()) { + /* Get the globals. */ + auto &globals = GetGlobalsForTipc(); + + /* Initialize the server manager. */ + globals.server_manager.Initialize(); + + /* Register the pgl service. */ + globals.server_manager.RegisterPort(ShellServiceName, ShellMaxSessions); + } else { + /* Get the globals. */ + auto &globals = GetGlobalsForCmif(); + + /* Register the shell server with the cmif server manager. */ + R_ABORT_UNLESS(globals.server_manager.RegisterObjectForServer(globals.shell_interface.GetShared(), ShellServiceName, ShellMaxSessions)); + } + } + + void LoopProcessServer() { + /* Loop processing for the appropriate server manager. */ + if (UseTipcServer()) { + GetGlobalsForTipc().server_manager.LoopAuto(); + } else { + GetGlobalsForCmif().server_manager.LoopProcess(); + } + } + + } + + void InitializeHeap() { + /* Initialize the heap (and construct the globals object) for the appropriate ipc protocol. */ + if (UseTipcServer()) { + /* We're servicing via tipc. */ + InitializeHeapImpl(g_globals.tipc); + } else { + /* We're servicing via cmif. */ + InitializeHeapImpl(g_globals.cmif); + } + } + + void *Allocate(size_t size) { + return lmem::AllocateFromExpHeap(GetHeapHandle(), size); + } + + void Deallocate(void *p, size_t size) { + AMS_UNUSED(size); + return lmem::FreeToExpHeap(GetHeapHandle(), p); + } + + void StartServer() { + /* Enable extra application threads, if we should. */ + { + u8 enable_application_extra_thread; + const size_t sz = settings::fwdbg::GetSettingsItemValue(std::addressof(enable_application_extra_thread), sizeof(enable_application_extra_thread), "application_extra_thread", "enable_application_extra_thread"); + if (sz == sizeof(enable_application_extra_thread) && enable_application_extra_thread != 0) { + /* NOTE: Nintendo does not check that this succeeds. */ + pm::shell::BoostApplicationThreadResourceLimit(); + } + } + + /* Enable extra system threads, if we should. */ + { + u8 enable_system_extra_thread; + const size_t sz = settings::fwdbg::GetSettingsItemValue(std::addressof(enable_system_extra_thread), sizeof(enable_system_extra_thread), "application_extra_thread", "enable_system_extra_thread"); + if (sz == sizeof(enable_system_extra_thread) && enable_system_extra_thread != 0) { + /* NOTE: Nintendo does not check that this succeeds. */ + pm::shell::BoostSystemThreadResourceLimit(); + } + } + + /* Register service session. */ + RegisterServiceSession(); + + /* Start the Process Tracking thread. */ + pgl::srv::InitializeProcessControlTask(); + + /* Loop process. */ + LoopProcessServer(); + } + + Result AllocateShellEventObserverForTipc(os::NativeHandle *out) { + /* Get the shell event observer allocator. */ + auto &allocator = GetGlobalsForTipc().observer_allocator; + + /* Allocate an object. */ + auto *object = allocator.Allocate(); + R_UNLESS(object != nullptr, pgl::ResultOutOfMemory()); + + /* Set the object's deleter. */ + object->SetDeleter(std::addressof(allocator)); + + /* Add the session to the server manager. */ + /* NOTE: If this fails, the object will be leaked. */ + /* TODO: Should we avoid leaking the object? Nintendo does not. */ + R_TRY(GetGlobalsForTipc().server_manager.AddSession(out, object)); + + R_SUCCEED(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell.cpp new file mode 100644 index 00000000..577f5956 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell.cpp @@ -0,0 +1,499 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pgl_srv_shell.hpp" +#include "pgl_srv_shell_event_observer.hpp" + +namespace ams::pgl::srv { + + namespace { + + constexpr inline size_t ProcessDataCount = 0x20; + + struct ProcessData { + os::ProcessId process_id; + u32 flags; + }; + static_assert(util::is_pod<ProcessData>::value); + + enum ProcessDataFlag : u32 { + ProcessDataFlag_None = 0, + ProcessDataFlag_DetailedCrashReportAllowed = (1 << 0), + ProcessDataFlag_DetailedCrashReportEnabled = (1 << 1), + ProcessDataFlag_HasLogOption = (1 << 2), + ProcessDataFlag_OutputAllLog = (1 << 3), + ProcessDataFlag_EnableCrashReportScreenShot = (1 << 4), + }; + + constinit bool g_is_production = true; + constinit bool g_enable_crash_report_screenshot = true; + constinit bool g_enable_jit_debug = false; + + constexpr inline size_t ProcessControlTaskStackSize = 8_KB; + constinit os::ThreadType g_process_control_task_thread = {}; + alignas(os::ThreadStackAlignment) constinit u8 g_process_control_task_stack[ProcessControlTaskStackSize]; + + constinit os::SdkMutex g_observer_list_mutex; + constinit util::IntrusiveListBaseTraits<ShellEventObserverHolder>::ListType g_observer_list; + + constinit os::SdkMutex g_process_data_mutex; + constinit ProcessData g_process_data[ProcessDataCount] = {}; + + constinit os::ProcessId g_crashed_process_id = os::InvalidProcessId; + constinit os::ProcessId g_creport_process_id = os::InvalidProcessId; + constinit os::ProcessId g_ssd_process_id = os::InvalidProcessId; + + ProcessData *FindProcessData(os::ProcessId process_id) { + for (auto &data : g_process_data) { + if (data.process_id == process_id) { + return std::addressof(data); + } + } + return nullptr; + } + + u32 ConvertToProcessDataFlags(u8 pgl_flags) { + if ((pgl_flags & pgl::LaunchFlags_EnableDetailedCrashReport) == 0) { + /* If we shouldn't generate detailed crash reports, set no flags. */ + return ProcessDataFlag_None; + } else { + /* We can and should generate detailed crash reports. */ + u32 data_flags = ProcessDataFlag_DetailedCrashReportAllowed | ProcessDataFlag_DetailedCrashReportEnabled; + + /* If we should enable crash report screenshots, check the correct flag. */ + if (g_enable_crash_report_screenshot) { + const u32 test_flag = g_is_production ? pgl::LaunchFlags_EnableCrashReportScreenShotForProduction : pgl::LaunchFlags_EnableCrashReportScreenShotForDevelop; + if ((pgl_flags & test_flag) != 0) { + data_flags |= ProcessDataFlag_EnableCrashReportScreenShot; + } + } + + return data_flags; + } + } + + util::optional<os::ProcessId> GetRunningApplicationProcessId() { + os::ProcessId process_id; + if (R_SUCCEEDED(pm::shell::GetApplicationProcessIdForShell(std::addressof(process_id)))) { + return process_id; + } else { + return util::nullopt; + } + } + + s32 ConvertDumpTypeToArgument(SnapShotDumpType dump_type) { + switch (dump_type) { + case SnapShotDumpType::None: return -1; + case SnapShotDumpType::Auto: return 0; + case SnapShotDumpType::Full: return 1; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + bool GetSnapShotDumpOutputAllLog(os::ProcessId process_id) { + /* Check if we have an option set for the process. */ + { + std::scoped_lock lk(g_process_data_mutex); + if (ProcessData *data = FindProcessData(process_id); data != nullptr) { + if ((data->flags & ProcessDataFlag_HasLogOption) != 0) { + return ((data->flags & ProcessDataFlag_OutputAllLog) != 0); + } + } + } + + /* If we don't have an option for the process, fall back to settings. */ + u8 log_option; + const size_t option_size = settings::fwdbg::GetSettingsItemValue(std::addressof(log_option), sizeof(log_option), "snap_shot_dump", "output_all_log"); + return (option_size == sizeof(log_option) && log_option != 0); + } + + size_t CreateSnapShotDumpArguments(char *dst, size_t dst_size, os::ProcessId process_id, SnapShotDumpType dump_type, const char *str_arg) { + const s32 dump_arg = ConvertDumpTypeToArgument(dump_type); + const s32 log_arg = GetSnapShotDumpOutputAllLog(process_id) ? 1 : 0; + if (str_arg != nullptr) { + return util::SNPrintf(dst, dst_size, "D %010llu \"%s\" -log %d -dump %d", static_cast<unsigned long long>(static_cast<u64>(process_id)), str_arg, log_arg, dump_arg); + } else { + return util::SNPrintf(dst, dst_size, "D %010llu -log %d -dump %d", static_cast<unsigned long long>(static_cast<u64>(process_id)), log_arg, dump_arg); + } + } + + Result TriggerSnapShotDumper(os::ProcessId process_id, SnapShotDumpType dump_type, const char *arg) { + /* Create the arguments. */ + char process_arguments[800]; + const size_t arg_len = CreateSnapShotDumpArguments(process_arguments, sizeof(process_arguments), process_id, dump_type, arg); + + /* Set the arguments. */ + R_TRY(ldr::SetProgramArgument(ncm::SystemDebugAppletId::SnapShotDumper, process_arguments, arg_len + 1)); + + /* Launch the process. */ + os::ProcessId ssd_process_id = os::InvalidProcessId; + R_TRY(pm::shell::LaunchProgram(std::addressof(ssd_process_id), ncm::ProgramLocation::Make(ncm::SystemDebugAppletId::SnapShotDumper, ncm::StorageId::BuiltInSystem), pm::LaunchFlags_None)); + + /* Set the globals. */ + g_crashed_process_id = process_id; + g_ssd_process_id = ssd_process_id; + R_SUCCEED(); + } + + bool ShouldSnapShotAutoDump() { + bool dump; + const size_t sz = settings::fwdbg::GetSettingsItemValue(std::addressof(dump), sizeof(dump), "snap_shot_dump", "auto_dump"); + return sz == sizeof(dump) && dump; + } + + bool ShouldSnapShotFullDump() { + bool dump; + const size_t sz = settings::fwdbg::GetSettingsItemValue(std::addressof(dump), sizeof(dump), "snap_shot_dump", "full_dump"); + return sz == sizeof(dump) && dump; + } + + SnapShotDumpType GetSnapShotDumpType() { + if (ShouldSnapShotAutoDump()) { + if (ShouldSnapShotFullDump()) { + return SnapShotDumpType::Full; + } else { + return SnapShotDumpType::Auto; + } + } else { + return SnapShotDumpType::None; + } + } + + void TriggerSnapShotDumper(os::ProcessId process_id) { + TriggerSnapShotDumper(process_id, GetSnapShotDumpType(), nullptr); + } + + s32 GetCrashReportDetailedArgument(u32 data_flags) { + if (((data_flags & ProcessDataFlag_DetailedCrashReportAllowed) != 0) && ((data_flags & ProcessDataFlag_DetailedCrashReportEnabled) != 0)) { + return 1; + } else { + return 0; + } + } + + s32 GetCrashReportScreenShotArgument(u32 data_flags) { + if (settings::system::GetErrorReportSharePermission() == settings::system::ErrorReportSharePermission_Granted) { + return ((data_flags & ProcessDataFlag_EnableCrashReportScreenShot) != 0) ? 1 : 0; + } else { + return 0; + } + } + + void TriggerCrashReport(os::ProcessId process_id) { + /* If the program that crashed is creport, we should just terminate both processes and return. */ + if (process_id == g_creport_process_id) { + TerminateProcess(g_crashed_process_id); + TerminateProcess(g_creport_process_id); + g_crashed_process_id = os::InvalidProcessId; + g_creport_process_id = os::InvalidProcessId; + return; + } + + /* Get the data flags for the process. */ + u32 data_flags; + { + std::scoped_lock lk(g_process_data_mutex); + if (auto *data = FindProcessData(process_id); data != nullptr) { + data_flags = data->flags; + } else { + data_flags = ProcessDataFlag_None; + } + } + + /* Generate arguments. */ + char arguments[0x40]; + const size_t len = util::SNPrintf(arguments, sizeof(arguments), "%" PRId64 " %d %d %d", static_cast<s64>(static_cast<u64>(process_id)), GetCrashReportDetailedArgument(data_flags), GetCrashReportScreenShotArgument(data_flags), g_enable_jit_debug); + if (R_FAILED(ldr::SetProgramArgument(ncm::SystemProgramId::Creport, arguments, len + 1))) { + return; + } + + /* Launch creport. */ + os::ProcessId creport_process_id; + if (R_FAILED(pm::shell::LaunchProgram(std::addressof(creport_process_id), ncm::ProgramLocation::Make(ncm::SystemProgramId::Creport, ncm::StorageId::BuiltInSystem), pm::LaunchFlags_None))) { + return; + } + + /* Set the globals. */ + g_crashed_process_id = process_id; + g_creport_process_id = creport_process_id; + } + + void HandleException(os::ProcessId process_id) { + if (g_enable_jit_debug) { + /* If jit debug is enabled, we want to try to launch snap shot dumper. */ + ProcessData *data = nullptr; + { + std::scoped_lock lk(g_process_data_mutex); + data = FindProcessData(process_id); + } + + /* If we're tracking the process, we can launch dumper. Otherwise we should just terminate. */ + if (data != nullptr) { + TriggerSnapShotDumper(process_id); + } else { + TerminateProcess(process_id); + } + } else { + /* Otherwise, we want to launch creport. */ + TriggerCrashReport(process_id); + } + } + + void HandleExit(os::ProcessId process_id) { + std::scoped_lock lk(g_process_data_mutex); + if (auto *data = FindProcessData(process_id); data != nullptr) { + data->process_id = os::InvalidProcessId; + } + } + + void OnProcessEvent(const pm::ProcessEventInfo &event_info) { + /* Determine if we're tracking the process. */ + ProcessData *data = nullptr; + { + std::scoped_lock lk(g_process_data_mutex); + data = FindProcessData(event_info.process_id); + } + + /* If we are, we're going to want to notify our listeners. */ + if (data != nullptr) { + /* If we closed the process, note that. */ + if (static_cast<pm::ProcessEvent>(event_info.event) == pm::ProcessEvent::Exited) { + HandleExit(event_info.process_id); + } + + /* Notify all observers. */ + std::scoped_lock lk(g_observer_list_mutex); + for (auto &observer : g_observer_list) { + observer.Notify(event_info); + } + } + + /* If the process crashed, handle that. */ + if (static_cast<pm::ProcessEvent>(event_info.event) == pm::ProcessEvent::Exception) { + HandleException(event_info.process_id); + } + } + + void ProcessControlTask(void *) { + /* Get the process event event from pm. */ + os::SystemEvent process_event; + R_ABORT_UNLESS(pm::shell::GetProcessEventEvent(std::addressof(process_event))); + + while (true) { + /* Wait for an event to come in, and clear our signal. */ + process_event.Wait(); + process_event.Clear(); + + bool continue_getting_event = true; + while (continue_getting_event) { + /* Try to get an event info. */ + pm::ProcessEventInfo event_info; + if (R_FAILED(pm::shell::GetProcessEventInfo(std::addressof(event_info)))) { + break; + } + + /* Process the event. */ + switch (static_cast<pm::ProcessEvent>(event_info.event)) { + case pm::ProcessEvent::None: + continue_getting_event = false; + break; + case pm::ProcessEvent::Exited: + { + /* If SnapShotDumper terminated, trigger a crash report. */ + if (event_info.process_id == g_ssd_process_id && g_crashed_process_id != os::InvalidProcessId) { + TriggerCrashReport(g_crashed_process_id); + } + } + [[fallthrough]]; + case pm::ProcessEvent::Started: + case pm::ProcessEvent::Exception: + case pm::ProcessEvent::DebugRunning: + case pm::ProcessEvent::DebugBreak: + OnProcessEvent(event_info); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + } + + } + + void InitializeProcessControlTask() { + /* Create the task thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(g_process_control_task_thread), ProcessControlTask, nullptr, g_process_control_task_stack, sizeof(g_process_control_task_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(pgl, ProcessControlTask))); + os::SetThreadNamePointer(std::addressof(g_process_control_task_thread), AMS_GET_SYSTEM_THREAD_NAME(pgl, ProcessControlTask)); + + /* Retrieve settings. */ + settings::fwdbg::GetSettingsItemValue(std::addressof(g_enable_jit_debug), sizeof(g_enable_jit_debug), "jit_debug", "enable_jit_debug"); + settings::fwdbg::GetSettingsItemValue(std::addressof(g_enable_crash_report_screenshot), sizeof(g_enable_crash_report_screenshot), "creport", "crash_screen_shot"); + g_is_production = !settings::fwdbg::IsDebugModeEnabled(); + + /* Clear all process data. */ + { + for (size_t i = 0; i < util::size(g_process_data); i++) { + g_process_data[i].process_id = os::InvalidProcessId; + } + } + + /* Start the thread. */ + os::StartThread(std::addressof(g_process_control_task_thread)); + } + + void RegisterShellEventObserver(ShellEventObserverHolder *holder) { + std::scoped_lock lk(g_observer_list_mutex); + + g_observer_list.push_back(*holder); + } + + void UnregisterShellEventObserver(ShellEventObserverHolder *holder) { + std::scoped_lock lk(g_observer_list_mutex); + + for (auto &observer : g_observer_list) { + if (std::addressof(observer) == holder) { + g_observer_list.erase(g_observer_list.iterator_to(observer)); + break; + } + } + } + + Result LaunchProgram(os::ProcessId *out, const ncm::ProgramLocation &loc, u32 pm_flags, u8 pgl_flags) { + /* Convert the input flags to the internal format. */ + const u32 data_flags = ConvertToProcessDataFlags(pgl_flags); + + /* If jit debug is enabled, we want to be signaled on crash. */ + if (g_enable_jit_debug) { + pm_flags |= pm::LaunchFlags_SignalOnException; + } + + /* Launch the process. */ + os::ProcessId process_id; + R_TRY(pm::shell::LaunchProgram(std::addressof(process_id), loc, pm_flags & pm::LaunchFlagsMask)); + + /* Create a ProcessData for the process. */ + { + std::scoped_lock lk(g_process_data_mutex); + ProcessData *new_data = FindProcessData(os::InvalidProcessId); + AMS_ABORT_UNLESS(new_data != nullptr); + + new_data->process_id = process_id; + new_data->flags = data_flags; + } + + /* We succeeded. */ + *out = process_id; + R_SUCCEED(); + } + + Result TerminateProcess(os::ProcessId process_id) { + /* Ask PM to terminate the process. */ + R_RETURN(pm::shell::TerminateProcess(process_id)); + } + + Result GetApplicationProcessId(os::ProcessId *out) { + /* Get the application process id. */ + auto application_process_id = GetRunningApplicationProcessId(); + R_UNLESS(application_process_id, pgl::ResultApplicationNotRunning()); + + /* Return the id. */ + *out = *application_process_id; + R_SUCCEED(); + } + + Result BoostSystemMemoryResourceLimit(u64 size) { + /* Ask PM to boost the limit. */ + R_RETURN(pm::shell::BoostSystemMemoryResourceLimit(size)); + } + + bool IsProcessTracked(os::ProcessId process_id) { + /* Check whether a ProcessData exists for the process. */ + std::scoped_lock lk(g_process_data_mutex); + return FindProcessData(process_id) != nullptr; + } + + void EnableApplicationCrashReport(bool enabled) { + /* Get the application process id. */ + auto application_process_id = GetRunningApplicationProcessId(); + if (application_process_id) { + /* Find the data for the application process. */ + std::scoped_lock lk(g_process_data_mutex); + ProcessData *data = FindProcessData(*application_process_id); + + /* It's okay if we aren't tracking the process. */ + if (data != nullptr) { + /* Set or clear the flag. */ + if (enabled) { + data->flags |= ProcessDataFlag_DetailedCrashReportEnabled; + } else { + data->flags &= ~ProcessDataFlag_DetailedCrashReportEnabled; + } + } + } + } + + bool IsApplicationCrashReportEnabled() { + /* Get the application process id. */ + auto application_process_id = GetRunningApplicationProcessId(); + if (!application_process_id) { + return false; + } + + /* Find the data for the process. */ + std::scoped_lock lk(g_process_data_mutex); + if (ProcessData *data = FindProcessData(*application_process_id); data != nullptr) { + return (data->flags & ProcessDataFlag_DetailedCrashReportEnabled) != 0; + } else { + return false; + } + } + + void EnableApplicationAllThreadDumpOnCrash(bool enabled) { + /* Get the application process id. */ + auto application_process_id = GetRunningApplicationProcessId(); + if (application_process_id) { + /* Find the data for the application process. */ + std::scoped_lock lk(g_process_data_mutex); + ProcessData *data = FindProcessData(*application_process_id); + + /* It's okay if we aren't tracking the process. */ + if (data != nullptr) { + /* Set or clear the flag. */ + if (enabled) { + data->flags |= ProcessDataFlag_OutputAllLog; + } else { + data->flags &= ~ProcessDataFlag_OutputAllLog; + } + + /* NOTE: Here Nintendo releases the lock, re-takes the lock, and re-finds the process data. */ + /* This is unnecessary and less efficient, so we will not bother. */ + + /* Note that the flag bit has a meaningful value. */ + data->flags |= ProcessDataFlag_HasLogOption; + } + } + } + + Result TriggerApplicationSnapShotDumper(SnapShotDumpType dump_type, const char *arg) { + /* Try to get the application process id. */ + os::ProcessId process_id; + R_TRY(pm::shell::GetApplicationProcessIdForShell(std::addressof(process_id))); + + /* Launch the snapshot dumper, clearing the global tracker process id. */ + ON_SCOPE_EXIT { g_ssd_process_id = os::InvalidProcessId; }; + R_RETURN(TriggerSnapShotDumper(process_id, dump_type, arg)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell.hpp new file mode 100644 index 00000000..11228d78 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::pgl::srv { + + void InitializeProcessControlTask(); + + class ShellEventObserverHolder; + + void RegisterShellEventObserver(ShellEventObserverHolder *holder); + void UnregisterShellEventObserver(ShellEventObserverHolder *holder); + + Result LaunchProgram(os::ProcessId *out, const ncm::ProgramLocation &loc, u32 pm_flags, u8 pgl_flags); + Result TerminateProcess(os::ProcessId process_id); + Result GetApplicationProcessId(os::ProcessId *out); + Result BoostSystemMemoryResourceLimit(u64 size); + bool IsProcessTracked(os::ProcessId process_id); + void EnableApplicationCrashReport(bool enabled); + bool IsApplicationCrashReportEnabled(); + void EnableApplicationAllThreadDumpOnCrash(bool enabled); + Result TriggerApplicationSnapShotDumper(SnapShotDumpType dump_type, const char *arg); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell_event_observer.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell_event_observer.cpp new file mode 100644 index 00000000..047123d3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell_event_observer.cpp @@ -0,0 +1,86 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pgl_srv_shell_event_observer.hpp" +#include "pgl_srv_shell.hpp" + +namespace ams::pgl::srv { + + ShellEventObserverImpl::ShellEventObserverImpl() : m_message_queue(m_queue_buffer, QueueCapacity), m_event(os::EventClearMode_AutoClear, true) { + m_heap_handle = lmem::CreateUnitHeap(m_event_info_data, sizeof(m_event_info_data), sizeof(m_event_info_data[0]), lmem::CreateOption_ThreadSafe, 8, GetPointer(m_heap_head)); + + RegisterShellEventObserver(util::ConstructAt(m_holder, this)); + } + + ShellEventObserverImpl::~ShellEventObserverImpl() { + UnregisterShellEventObserver(GetPointer(m_holder)); + util::DestroyAt(m_holder); + } + + Result ShellEventObserverImpl::PopEventInfo(pm::ProcessEventInfo *out) { + /* Receive an info from the queue. */ + uintptr_t info_address; + R_UNLESS(m_message_queue.TryReceive(std::addressof(info_address)), pgl::ResultNotAvailable()); + pm::ProcessEventInfo *info = reinterpret_cast<pm::ProcessEventInfo *>(info_address); + + /* Set the output. */ + *out = *info; + + /* Free the received info. */ + lmem::FreeToUnitHeap(m_heap_handle, info); + + R_SUCCEED(); + } + + void ShellEventObserverImpl::Notify(const pm::ProcessEventInfo &info) { + /* Allocate a new info. */ + auto allocated = reinterpret_cast<pm::ProcessEventInfo *>(lmem::AllocateFromUnitHeap(m_heap_handle)); + if (!allocated) { + return; + } + + /* Set it to the notification. */ + *allocated = info; + + /* Try to send it. */ + if (!m_message_queue.TrySend(reinterpret_cast<uintptr_t>(allocated))) { + lmem::FreeToUnitHeap(m_heap_handle, allocated); + return; + } + + /* Notify that we have a new info available. */ + m_event.Signal(); + } + + Result ShellEventObserverCmif::GetProcessEventHandle(ams::sf::OutCopyHandle out) { + out.SetValue(this->GetEvent().GetReadableHandle(), false); + R_SUCCEED(); + } + + Result ShellEventObserverCmif::GetProcessEventInfo(ams::sf::Out<pm::ProcessEventInfo> out) { + R_RETURN(this->PopEventInfo(out.GetPointer())); + } + + Result ShellEventObserverTipc::GetProcessEventHandle(ams::tipc::OutCopyHandle out) { + out.SetValue(this->GetEvent().GetReadableHandle()); + R_SUCCEED(); + } + + Result ShellEventObserverTipc::GetProcessEventInfo(ams::tipc::Out<pm::ProcessEventInfo> out) { + R_RETURN(this->PopEventInfo(out.GetPointer())); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell_event_observer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell_event_observer.hpp new file mode 100644 index 00000000..8fc52d0e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell_event_observer.hpp @@ -0,0 +1,75 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::pgl::srv { + + class IShellEventObserver { + public: + virtual void Notify(const pm::ProcessEventInfo &info) = 0; + }; + + class ShellEventObserverHolder : public util::IntrusiveListBaseNode<ShellEventObserverHolder> { + private: + IShellEventObserver *m_observer; + public: + explicit ShellEventObserverHolder(IShellEventObserver *observer) : m_observer(observer) { /* ... */ } + + void Notify(const pm::ProcessEventInfo &info) { + m_observer->Notify(info); + } + }; + + class ShellEventObserverImpl : public IShellEventObserver { + private: + static constexpr size_t QueueCapacity = 0x20; + private: + os::MessageQueue m_message_queue; + uintptr_t m_queue_buffer[QueueCapacity]; + os::SystemEvent m_event; + util::TypedStorage<lmem::HeapCommonHead> m_heap_head; + lmem::HeapHandle m_heap_handle; + pm::ProcessEventInfo m_event_info_data[QueueCapacity]; + util::TypedStorage<ShellEventObserverHolder> m_holder; + public: + ShellEventObserverImpl(); + ~ShellEventObserverImpl(); + + os::SystemEvent &GetEvent() { + return m_event; + } + + Result PopEventInfo(pm::ProcessEventInfo *out); + + virtual void Notify(const pm::ProcessEventInfo &info) override final; + }; + + class ShellEventObserverCmif : public ShellEventObserverImpl { + public: + Result GetProcessEventHandle(ams::sf::OutCopyHandle out); + Result GetProcessEventInfo(ams::sf::Out<pm::ProcessEventInfo> out); + }; + static_assert(pgl::sf::IsIEventObserver<ShellEventObserverCmif>); + + class ShellEventObserverTipc : public ShellEventObserverImpl { + public: + Result GetProcessEventHandle(ams::tipc::OutCopyHandle out); + Result GetProcessEventInfo(ams::tipc::Out<pm::ProcessEventInfo> out); + }; + static_assert(pgl::tipc::IsIEventObserver<ShellEventObserverTipc>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell_host_utils.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell_host_utils.cpp new file mode 100644 index 00000000..e1e6cb7d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell_host_utils.cpp @@ -0,0 +1,357 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pgl_srv_shell_host_utils.hpp" +#include "pgl_srv_shell.hpp" + +namespace ams::pgl::srv { + + namespace { + + constexpr inline char HostPackageMountName[] = "HostPackageRead"; + static_assert(sizeof(HostPackageMountName) - 1 <= fs::MountNameLengthMax); + + struct CaseInsensitiveCharTraits : public std::char_traits<char> { + static constexpr char to_upper(char c) { + return std::toupper(static_cast<unsigned char>(c)); + } + static constexpr bool eq(char c1, char c2) { + return to_upper(c1) == to_upper(c2); + } + static constexpr bool lt(char c1, char c2) { + return to_upper(c1) < to_upper(c2); + } + static constexpr int compare(const char *s1, const char *s2, size_t n) { + while ( n-- != 0 ) { + if ( to_upper(*s1) < to_upper(*s2) ) return -1; + if ( to_upper(*s1) > to_upper(*s2) ) return 1; + ++s1; ++s2; + } + return 0; + } + static constexpr const char *find(const char *s, int n, char a) { + auto const ua (to_upper(a)); + while ( n-- != 0 ) + { + if (to_upper(*s) == ua) + return s; + s++; + } + return nullptr; + } + }; + + using PathView = util::basic_string_view<char, CaseInsensitiveCharTraits>; + + enum class ExtensionType { + None = 0, + Nsp = 1, + Nspd = 2, + }; + + bool HasSuffix(const char *str, const char *suffix) { + const size_t suffix_len = std::strlen(suffix); + const size_t str_len = std::strlen(str); + if (suffix_len > str_len) { + return false; + } + return (PathView(str).substr(str_len - suffix_len) == PathView(suffix)); + } + + class HostPackageReader { + NON_COPYABLE(HostPackageReader); + NON_MOVEABLE(HostPackageReader); + private: + char m_content_path[fs::EntryNameLengthMax] = {}; + ExtensionType m_extension_type = ExtensionType::None; + char m_mount_name[fs::MountNameLengthMax] = {}; + bool m_is_mounted = false; + ncm::AutoBuffer m_content_meta_buffer; + ncm::ProgramId m_program_id = ncm::InvalidProgramId; + u32 m_program_version = 0; + ncm::ContentMetaType m_content_meta_type = static_cast<ncm::ContentMetaType>(0); + u8 m_program_index = 0; + public: + HostPackageReader() : m_content_meta_buffer() { /* ... */ } + ~HostPackageReader() { + if (m_is_mounted) { + fs::Unmount(m_mount_name); + } + } + + Result Initialize(const char *package, const char *mount) { + /* Copy in the content path. */ + R_UNLESS(strlen(package) <= sizeof(m_content_path) - 1, pgl::ResultBufferNotEnough()); + std::strcpy(m_content_path, package); + + /* Set the extension type. */ + R_TRY(this->SetExtensionType()); + + /* Copy in mount name. */ + R_UNLESS(strlen(mount) <= sizeof(m_mount_name) - 1, pgl::ResultBufferNotEnough()); + std::strcpy(m_mount_name, mount); + + /* Mount the package. */ + R_TRY(fs::MountApplicationPackage(m_mount_name, m_content_path)); + m_is_mounted = true; + + /* Set the content meta buffer. */ + R_TRY(this->SetContentMetaBuffer()); + + /* Ensure we have a content meta buffer. */ + R_UNLESS(m_content_meta_buffer.Get() != nullptr, pgl::ResultContentMetaNotFound()); + + R_SUCCEED(); + } + + Result ReadProgramInfo() { + /* First, read the program index. */ + R_TRY(this->GetProgramIndex(std::addressof(m_program_index))); + + /* Next, create a key for the rest of the fields. */ + const auto key = ncm::PackagedContentMetaReader(m_content_meta_buffer.Get(), m_content_meta_buffer.GetSize()).GetKey(); + + /* Set fields. */ + m_program_id = {key.id}; + m_program_version = key.version; + m_content_meta_type = key.type; + R_SUCCEED(); + } + + Result GetContentPath(lr::Path *out, ncm::ContentType type, util::optional<u8> index) const { + switch (m_extension_type) { + case ExtensionType::Nsp: R_RETURN(this->GetContentPathInNsp(out, type, index)); + case ExtensionType::Nspd: R_RETURN(this->GetContentPathInNspd(out, type, index)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + ncm::ProgramId GetProgramId() const { + return m_program_id; + } + + u32 GetProgramVersion() const { + return m_program_version; + } + + ncm::ContentMetaType GetContentMetaType() const { + return m_content_meta_type; + } + + u8 GetProgramIndex() const { + return m_program_index; + } + private: + Result GetContentPathInNsp(lr::Path *out, ncm::ContentType type, util::optional<u8> index) const { + /* Create a reader. */ + auto reader = ncm::PackagedContentMetaReader(m_content_meta_buffer.Get(), m_content_meta_buffer.GetSize()); + + /* Get the content info. */ + const ncm::PackagedContentInfo *content_info = nullptr; + if (index) { + content_info = reader.GetContentInfo(type, *index); + } else { + content_info = reader.GetContentInfo(type); + } + R_UNLESS(content_info != nullptr, pgl::ResultApplicationContentNotFound()); + + /* Get the content id string. */ + ncm::ContentIdString id_str; + ncm::GetStringFromContentId(id_str.data, sizeof(id_str.data), content_info->GetId()); + + /* Get the file name. */ + char file_name[ncm::ContentIdStringLength + 5]; + const size_t len = util::SNPrintf(file_name, sizeof(file_name), "%s.nca", id_str.data); + R_UNLESS(len + 1 == sizeof(file_name), pgl::ResultBufferNotEnough()); + + /* Ensure we have the content. */ + bool has_content; + R_TRY(this->SearchContent(std::addressof(has_content), out, file_name, fs::OpenDirectoryMode_File)); + R_UNLESS(has_content, pgl::ResultApplicationContentNotFound()); + + R_SUCCEED(); + } + + Result GetContentPathInNspd(lr::Path *out, ncm::ContentType type, util::optional<u8> index) const { + AMS_UNUSED(index); + + /* Get the content name. */ + const char *content_name = nullptr; + switch (type) { + case ncm::ContentType::Program: content_name = "program"; break; + case ncm::ContentType::Control: content_name = "control"; break; + case ncm::ContentType::HtmlDocument: content_name = "htmlDocument"; break; + case ncm::ContentType::LegalInformation: content_name = "legalInformation"; break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Get the file name. */ + /* NSPD does not support indexed content, so we always use 0 as the index. */ + char file_name[0x20]; + const size_t len = util::SNPrintf(file_name, sizeof(file_name), "%s%d.ncd", content_name, 0); + R_UNLESS(len + 1 <= sizeof(file_name), pgl::ResultBufferNotEnough()); + + /* Ensure we have the content. */ + bool has_content; + R_TRY(this->SearchContent(std::addressof(has_content), out, file_name, fs::OpenDirectoryMode_Directory)); + R_UNLESS(has_content, pgl::ResultApplicationContentNotFound()); + + R_SUCCEED(); + } + + Result GetProgramIndex(u8 *out) { + /* Nspd programs do not have indices. */ + if (m_extension_type == ExtensionType::Nspd) { + *out = 0; + R_SUCCEED(); + } + + /* Create a reader. */ + auto reader = ncm::PackagedContentMetaReader(m_content_meta_buffer.Get(), m_content_meta_buffer.GetSize()); + + /* Get the program content info. */ + auto program_content_info = reader.GetContentInfo(ncm::ContentType::Program); + R_UNLESS(program_content_info, pgl::ResultApplicationContentNotFound()); + + /* Return the index. */ + *out = program_content_info->GetIdOffset(); + R_SUCCEED(); + } + + Result SetExtensionType() { + /* First, clear the suffix if the path is a program ncd. */ + if (HasSuffix(m_content_path, "program0.ncd/")) { + m_content_path[strnlen(m_content_path, sizeof(m_content_path)) - std::strlen("program0.ncd/")] = 0; + } + + if (HasSuffix(m_content_path, ".nsp")) { + m_extension_type = ExtensionType::Nsp; + R_SUCCEED(); + } else if (HasSuffix(m_content_path, ".nspd") || HasSuffix(m_content_path, ".nspd/")) { + m_extension_type = ExtensionType::Nspd; + R_SUCCEED(); + } else { + R_THROW(fs::ResultPathNotFound()); + } + } + + Result SetContentMetaBuffer() { + constexpr const char ContentMetaFileExtension[] = ".cnmt.nca"; + constexpr const char ContentMetaDirectoryExtension[] = "meta0.ncd"; + + /* Find the Content meta path. */ + bool has_content = false; + lr::Path meta_path; + switch (m_extension_type) { + case ExtensionType::Nsp: R_TRY(this->SearchContent(std::addressof(has_content), std::addressof(meta_path), ContentMetaFileExtension, fs::OpenDirectoryMode_File)); break; + case ExtensionType::Nspd: R_TRY(this->SearchContent(std::addressof(has_content), std::addressof(meta_path), ContentMetaDirectoryExtension, fs::OpenDirectoryMode_Directory)); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + R_UNLESS(has_content, pgl::ResultContentMetaNotFound()); + + /* Read the content meta buffer. */ + R_RETURN(ncm::TryReadContentMetaPath(std::addressof(m_content_meta_buffer), meta_path.str, ncm::ReadContentMetaPathWithoutExtendedDataOrDigest)); + } + + Result SearchContent(bool *out, lr::Path *out_path, const char *extension, fs::OpenDirectoryMode mode) const { + /* Generate the root directory path. */ + char root_dir[sizeof(m_mount_name) + 2]; + util::SNPrintf(root_dir, sizeof(root_dir), "%s:/", m_mount_name); + + /* Open the root directory. */ + fs::DirectoryHandle dir; + R_TRY(fs::OpenDirectory(std::addressof(dir), root_dir, mode)); + ON_SCOPE_EXIT { fs::CloseDirectory(dir); }; + + /* Iterate over directory entries. */ + while (true) { + fs::DirectoryEntry entry; + s64 count; + R_TRY(fs::ReadDirectory(std::addressof(count), std::addressof(entry), dir, 1)); + if (count == 0) { + break; + } + + /* Check if we match the suffix. */ + if (HasSuffix(entry.name, extension)) { + *out = true; + if (out_path) { + const size_t len = util::SNPrintf(out_path->str, sizeof(out_path->str), "%s/%s", m_content_path, entry.name); + R_UNLESS(len + 1 < sizeof(out_path->str), pgl::ResultBufferNotEnough()); + if (entry.type == fs::DirectoryEntryType_Directory) { + out_path->str[len] = '/'; + out_path->str[len + 1] = 0; + } + } + + R_SUCCEED(); + } + } + + /* We didn't find a match. */ + *out = false; + R_SUCCEED(); + } + }; + + } + + Result LaunchProgramFromHost(os::ProcessId *out, const char *package_path, u32 pm_flags) { + /* Read the package. */ + HostPackageReader reader; + R_TRY(reader.Initialize(package_path, HostPackageMountName)); + + /* Read the program info. */ + R_TRY(reader.ReadProgramInfo()); + + /* Open a host location resolver. */ + lr::LocationResolver host_resolver; + R_TRY(lr::OpenLocationResolver(std::addressof(host_resolver), ncm::StorageId::Host)); + + /* Get the content path. */ + lr::Path content_path; + R_TRY(reader.GetContentPath(std::addressof(content_path), ncm::ContentType::Program, reader.GetProgramIndex())); + + /* Erase the program redirection. */ + R_TRY(host_resolver.EraseProgramRedirection(reader.GetProgramId())); + + /* Redirect the program path to point to the new path. */ + host_resolver.RedirectProgramPath(content_path, reader.GetProgramId()); + + /* Launch the program. */ + R_RETURN(pgl::srv::LaunchProgram(out, ncm::ProgramLocation::Make(reader.GetProgramId(), ncm::StorageId::Host), pm_flags, pgl::LaunchFlags_None)); + } + + Result GetHostContentMetaInfo(pgl::ContentMetaInfo *out, const char *package_path) { + /* Read the package. */ + HostPackageReader reader; + R_TRY(reader.Initialize(package_path, HostPackageMountName)); + + /* Read the program info. */ + R_TRY(reader.ReadProgramInfo()); + + /* Get the content meta info. */ + *out = { + .id = reader.GetProgramId().value, + .version = reader.GetProgramVersion(), + .content_type = ncm::ContentType::Program, + .id_offset = reader.GetProgramIndex(), + }; + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell_host_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell_host_utils.hpp new file mode 100644 index 00000000..263f813b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell_host_utils.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::pgl::srv { + + Result LaunchProgramFromHost(os::ProcessId *out, const char *content_path, u32 pm_flags); + Result GetHostContentMetaInfo(pgl::ContentMetaInfo *out, const char *content_path); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell_interface.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell_interface.cpp new file mode 100644 index 00000000..52e43a5e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_shell_interface.cpp @@ -0,0 +1,177 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pgl_srv_shell.hpp" +#include "pgl_srv_shell_event_observer.hpp" +#include "pgl_srv_shell_host_utils.hpp" +#include "pgl_srv_tipc_utils.hpp" + +namespace ams::pgl::srv { + + Result ShellInterfaceCommon::LaunchProgramImpl(os::ProcessId *out, const ncm::ProgramLocation &loc, u32 pm_flags, u8 pgl_flags) { + R_RETURN(pgl::srv::LaunchProgram(out, loc, pm_flags, pgl_flags)); + } + + Result ShellInterfaceCommon::TerminateProcessImpl(os::ProcessId process_id) { + R_RETURN(pgl::srv::TerminateProcess(process_id)); + } + + Result ShellInterfaceCommon::LaunchProgramFromHostImpl(os::ProcessId *out, const void *content_path, size_t content_path_size, u32 pm_flags) { + AMS_UNUSED(content_path_size); + R_RETURN(pgl::srv::LaunchProgramFromHost(out, static_cast<const char *>(content_path), pm_flags)); + } + + Result ShellInterfaceCommon::GetHostContentMetaInfoImpl(pgl::ContentMetaInfo *out, const void *content_path, size_t content_path_size) { + AMS_UNUSED(content_path_size); + R_RETURN(pgl::srv::GetHostContentMetaInfo(out, static_cast<const char *>(content_path))); + } + + Result ShellInterfaceCommon::GetApplicationProcessIdImpl(os::ProcessId *out) { + R_RETURN(pgl::srv::GetApplicationProcessId(out)); + } + + Result ShellInterfaceCommon::BoostSystemMemoryResourceLimitImpl(u64 size) { + R_RETURN(pgl::srv::BoostSystemMemoryResourceLimit(size)); + } + + Result ShellInterfaceCommon::IsProcessTrackedImpl(bool *out, os::ProcessId process_id) { + *out = pgl::srv::IsProcessTracked(process_id); + R_SUCCEED(); + } + + Result ShellInterfaceCommon::EnableApplicationCrashReportImpl(bool enabled) { + pgl::srv::EnableApplicationCrashReport(enabled); + R_SUCCEED(); + } + + Result ShellInterfaceCommon::IsApplicationCrashReportEnabledImpl(bool *out) { + *out = pgl::srv::IsApplicationCrashReportEnabled(); + R_SUCCEED(); + } + + Result ShellInterfaceCommon::EnableApplicationAllThreadDumpOnCrashImpl(bool enabled) { + pgl::srv::EnableApplicationAllThreadDumpOnCrash(enabled); + R_SUCCEED(); + } + + Result ShellInterfaceCommon::TriggerApplicationSnapShotDumperImpl(SnapShotDumpType dump_type, const void *arg, size_t arg_size) { + AMS_UNUSED(arg_size); + R_RETURN(pgl::srv::TriggerApplicationSnapShotDumper(dump_type, static_cast<const char *>(arg))); + } + + Result ShellInterfaceCmif::LaunchProgram(ams::sf::Out<os::ProcessId> out, const ncm::ProgramLocation &loc, u32 pm_flags, u8 pgl_flags) { + R_RETURN(this->LaunchProgramImpl(out.GetPointer(), loc, pm_flags, pgl_flags)); + } + + Result ShellInterfaceCmif::TerminateProcess(os::ProcessId process_id) { + R_RETURN(this->TerminateProcessImpl(process_id)); + } + + Result ShellInterfaceCmif::LaunchProgramFromHost(ams::sf::Out<os::ProcessId> out, const ams::sf::InBuffer &content_path, u32 pm_flags) { + R_RETURN(this->LaunchProgramFromHostImpl(out.GetPointer(), content_path.GetPointer(), content_path.GetSize(), pm_flags)); + } + + Result ShellInterfaceCmif::GetHostContentMetaInfo(ams::sf::Out<pgl::ContentMetaInfo> out, const ams::sf::InBuffer &content_path) { + R_RETURN(this->GetHostContentMetaInfoImpl(out.GetPointer(), content_path.GetPointer(), content_path.GetSize())); + } + + Result ShellInterfaceCmif::GetApplicationProcessId(ams::sf::Out<os::ProcessId> out) { + R_RETURN(this->GetApplicationProcessIdImpl(out.GetPointer())); + } + + Result ShellInterfaceCmif::BoostSystemMemoryResourceLimit(u64 size) { + R_RETURN(this->BoostSystemMemoryResourceLimitImpl(size)); + } + + Result ShellInterfaceCmif::IsProcessTracked(ams::sf::Out<bool> out, os::ProcessId process_id) { + R_RETURN(this->IsProcessTrackedImpl(out.GetPointer(), process_id)); + } + + Result ShellInterfaceCmif::EnableApplicationCrashReport(bool enabled) { + R_RETURN(this->EnableApplicationCrashReportImpl(enabled)); + } + + Result ShellInterfaceCmif::IsApplicationCrashReportEnabled(ams::sf::Out<bool> out) { + R_RETURN(this->IsApplicationCrashReportEnabledImpl(out.GetPointer())); + } + + Result ShellInterfaceCmif::EnableApplicationAllThreadDumpOnCrash(bool enabled) { + R_RETURN(this->EnableApplicationAllThreadDumpOnCrashImpl(enabled)); + } + + Result ShellInterfaceCmif::TriggerApplicationSnapShotDumper(SnapShotDumpType dump_type, const ams::sf::InBuffer &arg) { + R_RETURN(this->TriggerApplicationSnapShotDumperImpl(dump_type, arg.GetPointer(), arg.GetSize())); + } + + Result ShellInterfaceCmif::GetShellEventObserver(ams::sf::Out<ams::sf::SharedPointer<pgl::sf::IEventObserver>> out) { + /* Allocate a new interface. */ + auto session = ObjectFactory::CreateSharedEmplaced<pgl::sf::IEventObserver, ShellEventObserverCmif>(m_allocator); + R_UNLESS(session != nullptr, pgl::ResultOutOfMemory()); + + *out = std::move(session); + R_SUCCEED(); + } + + Result ShellInterfaceCmif::Command21NotImplemented(ams::sf::Out<u64> out, u32 in, const ams::sf::InBuffer &buf1, const ams::sf::InBuffer &buf2) { + AMS_UNUSED(out, in, buf1, buf2); + R_THROW(pgl::ResultNotImplemented()); + } + + Result ShellInterfaceTipc::LaunchProgram(ams::tipc::Out<os::ProcessId> out, const ncm::ProgramLocation loc, u32 pm_flags, u8 pgl_flags) { + R_RETURN(this->LaunchProgramImpl(out.GetPointer(), loc, pm_flags, pgl_flags)); + } + + Result ShellInterfaceTipc::TerminateProcess(os::ProcessId process_id) { + R_RETURN(this->TerminateProcessImpl(process_id)); + } + + Result ShellInterfaceTipc::LaunchProgramFromHost(ams::tipc::Out<os::ProcessId> out, const ams::tipc::InBuffer content_path, u32 pm_flags) { + R_RETURN(this->LaunchProgramFromHostImpl(out.GetPointer(), content_path.GetPointer(), content_path.GetSize(), pm_flags)); + } + + Result ShellInterfaceTipc::GetHostContentMetaInfo(ams::tipc::Out<pgl::ContentMetaInfo> out, const ams::tipc::InBuffer content_path) { + R_RETURN(this->GetHostContentMetaInfoImpl(out.GetPointer(), content_path.GetPointer(), content_path.GetSize())); + } + + Result ShellInterfaceTipc::GetApplicationProcessId(ams::tipc::Out<os::ProcessId> out) { + R_RETURN(this->GetApplicationProcessIdImpl(out.GetPointer())); + } + + Result ShellInterfaceTipc::BoostSystemMemoryResourceLimit(u64 size) { + R_RETURN(this->BoostSystemMemoryResourceLimitImpl(size)); + } + + Result ShellInterfaceTipc::IsProcessTracked(ams::tipc::Out<bool> out, os::ProcessId process_id) { + R_RETURN(this->IsProcessTrackedImpl(out.GetPointer(), process_id)); + } + + Result ShellInterfaceTipc::EnableApplicationCrashReport(bool enabled) { + R_RETURN(this->EnableApplicationCrashReportImpl(enabled)); + } + + Result ShellInterfaceTipc::IsApplicationCrashReportEnabled(ams::tipc::Out<bool> out) { + R_RETURN(this->IsApplicationCrashReportEnabledImpl(out.GetPointer())); + } + + Result ShellInterfaceTipc::EnableApplicationAllThreadDumpOnCrash(bool enabled) { + R_RETURN(this->EnableApplicationAllThreadDumpOnCrashImpl(enabled)); + } + + Result ShellInterfaceTipc::GetShellEventObserver(ams::tipc::OutMoveHandle out) { + R_RETURN(pgl::srv::AllocateShellEventObserverForTipc(out.GetPointer())); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_tipc_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_tipc_utils.hpp new file mode 100644 index 00000000..c4ec6fc8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pgl/srv/pgl_srv_tipc_utils.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::pgl::srv { + + Result AllocateShellEventObserverForTipc(os::NativeHandle *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_board_driver_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_board_driver_api.cpp new file mode 100644 index 00000000..84ff1993 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_board_driver_api.cpp @@ -0,0 +1,138 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pinmux_pad_index.hpp" +#include "pinmux_board_driver_api.hpp" +#include "pinmux_platform_pads.hpp" + +namespace ams::pinmux::driver::board::nintendo::nx { + + namespace { + + constinit bool g_initialized = false; + + #include "pinmux_initial_pad_config_icosa.inc" + #include "pinmux_initial_pad_config_hoag.inc" + #include "pinmux_initial_pad_config_iowa.inc" + #include "pinmux_initial_pad_config_calcio.inc" + #include "pinmux_initial_pad_config_aula.inc" + + #include "pinmux_initial_drive_pad_config.inc" + #include "pinmux_initial_drive_pad_config_hoag.inc" + + } + + bool IsInitialized() { + return g_initialized; + } + + void Initialize() { + InitializePlatformPads(); + g_initialized = true; + } + + void Finalize() { + /* ... */ + } + + void SetInitialConfig() { + const PinmuxPadConfig *configs = nullptr; + size_t num_configs = 0; + bool is_mariko = false; + switch (spl::GetHardwareType()) { + case spl::HardwareType::Icosa: + configs = PinmuxPadConfigsIcosa; + num_configs = NumPinmuxPadConfigsIcosa; + is_mariko = false; + break; + case spl::HardwareType::Hoag: + configs = PinmuxPadConfigsHoag; + num_configs = NumPinmuxPadConfigsHoag; + is_mariko = true; + break; + case spl::HardwareType::Iowa: + configs = PinmuxPadConfigsIowa; + num_configs = NumPinmuxPadConfigsIowa; + is_mariko = true; + break; + case spl::HardwareType::Calcio: + configs = PinmuxPadConfigsCalcio; + num_configs = NumPinmuxPadConfigsCalcio; + is_mariko = true; + break; + case spl::HardwareType::Aula: + configs = PinmuxPadConfigsAula; + num_configs = NumPinmuxPadConfigsAula; + is_mariko = true; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + AMS_ABORT_UNLESS(configs != nullptr); + + for (size_t i = 0; i < num_configs; ++i) { + UpdateSinglePinmuxPad(configs[i]); + } + + if (is_mariko) { + UpdateSinglePinmuxPad({ PinmuxPadIndex_Sdmmc2Clk, 0x2000, 0x2000 }); + UpdateSinglePinmuxPad({ PinmuxPadIndex_Sdmmc2Cmd, 0x2000, 0x2000 }); + UpdateSinglePinmuxPad({ PinmuxPadIndex_Sdmmc2Dat0, 0x2000, 0x2000 }); + UpdateSinglePinmuxPad({ PinmuxPadIndex_Sdmmc2Dat1, 0x2000, 0x2000 }); + UpdateSinglePinmuxPad({ PinmuxPadIndex_Sdmmc2Dat2, 0x2000, 0x2000 }); + UpdateSinglePinmuxPad({ PinmuxPadIndex_Sdmmc2Dat3, 0x2000, 0x2000 }); + UpdateSinglePinmuxPad({ PinmuxPadIndex_Sdmmc2Dat4, 0x2000, 0x2000 }); + UpdateSinglePinmuxPad({ PinmuxPadIndex_Sdmmc2Dat5, 0x2000, 0x2000 }); + UpdateSinglePinmuxPad({ PinmuxPadIndex_Sdmmc2Dat6, 0x2000, 0x2000 }); + UpdateSinglePinmuxPad({ PinmuxPadIndex_Sdmmc2Dat7, 0x2000, 0x2000 }); + } + } + + void SetInitialDrivePadConfig() { + const PinmuxDrivePadConfig *configs = nullptr; + size_t num_configs = 0; + switch (spl::GetHardwareType()) { + case spl::HardwareType::Icosa: + configs = PinmuxDrivePadConfigs; + num_configs = NumPinmuxDrivePadConfigs; + break; + case spl::HardwareType::Hoag: + configs = PinmuxDrivePadConfigsHoag; + num_configs = NumPinmuxDrivePadConfigsHoag; + break; + case spl::HardwareType::Iowa: + configs = PinmuxDrivePadConfigs; + num_configs = NumPinmuxDrivePadConfigs; + break; + case spl::HardwareType::Calcio: + configs = PinmuxDrivePadConfigs; + num_configs = NumPinmuxDrivePadConfigs; + break; + case spl::HardwareType::Aula: + configs = PinmuxDrivePadConfigs; + num_configs = NumPinmuxDrivePadConfigs; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + AMS_ABORT_UNLESS(configs != nullptr); + + for (size_t i = 0; i < num_configs; ++i) { + UpdateSinglePinmuxDrivePad(configs[i]); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_board_driver_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_board_driver_api.hpp new file mode 100644 index 00000000..ed8f0f35 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_board_driver_api.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::pinmux::driver::board::nintendo::nx { + + bool IsInitialized(); + + void Initialize(); + void Finalize(); + + void SetInitialConfig(); + void SetInitialDrivePadConfig(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_drive_pad_characters.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_drive_pad_characters.inc new file mode 100644 index 00000000..ace0b677 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_drive_pad_characters.inc @@ -0,0 +1,170 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by pinmux_character_gen.py, do not edit manually. */ + +constexpr inline const PinmuxDrivePadCharacter PinmuxDrivePadCharacters[] = { + { 0x08E4, 0x01F1F000, "AlsProxInt" }, + { 0x08E8, 0x01F1F000, "ApReady" }, + { 0x08EC, 0x01F1F000, "ApWakeBt" }, + { 0x08F0, 0x01F1F000, "ApWakeNfc" }, + { 0x08F4, 0x01F1F000, "AudMclk" }, + { 0x08F8, 0x01F1F000, "BattBcl" }, + { 0x08FC, 0x01F1F000, "BtRst" }, + { 0x0900, 0x01F1F000, "BtWakeAp" }, + { 0x0904, 0x01F1F000, "ButtonHome" }, + { 0x0908, 0x01F1F000, "ButtonPowerOn" }, + { 0x090C, 0x01F1F000, "ButtonSlideSw" }, + { 0x0910, 0x01F1F000, "ButtonVolDown" }, + { 0x0914, 0x01F1F000, "ButtonVolUp" }, + { 0x0918, 0x01F1F000, "Cam1Mclk" }, + { 0x091C, 0x01F1F000, "Cam1Pwdn" }, + { 0x0920, 0x01F1F000, "Cam1Strobe" }, + { 0x0924, 0x01F1F000, "Cam2Mclk" }, + { 0x0928, 0x01F1F000, "Cam2Pwdn" }, + { 0x092C, 0x01F1F000, "CamAfEn" }, + { 0x0930, 0x01F1F000, "CamFlashEn" }, + { 0x0934, 0x01F1F000, "CamI2cScl" }, + { 0x0938, 0x01F1F000, "CamI2cSda" }, + { 0x093C, 0x01F1F000, "CamRst" }, + { 0x0940, 0x01F1F000, "Clk32kIn" }, + { 0x0944, 0x01F1F000, "Clk32kOut" }, + { 0x0948, 0x01F1F000, "ClkReq" }, + { 0x094C, 0x01F1F000, "CorePwrReq" }, + { 0x0950, 0x01F1F000, "CpuPwrReq" }, + { 0x0954, 0xF0000000, "Dap1Din" }, + { 0x0958, 0xF0000000, "Dap1Dout" }, + { 0x095C, 0xF0000000, "Dap1Fs" }, + { 0x0960, 0xF0000000, "Dap1Sclk" }, + { 0x0964, 0xF0000000, "Dap2Din" }, + { 0x0968, 0xF0000000, "Dap2Dout" }, + { 0x096C, 0xF0000000, "Dap2Fs" }, + { 0x0970, 0xF0000000, "Dap2Sclk" }, + { 0x0974, 0x01F1F000, "Dap4Din" }, + { 0x0978, 0x01F1F000, "Dap4Dout" }, + { 0x097C, 0x01F1F000, "Dap4Fs" }, + { 0x0980, 0x01F1F000, "Dap4Sclk" }, + { 0x0984, 0x01F1F000, "Dmic1Clk" }, + { 0x0988, 0x01F1F000, "Dmic1Dat" }, + { 0x098C, 0x01F1F000, "Dmic2Clk" }, + { 0x0990, 0x01F1F000, "Dmic2Dat" }, + { 0x0994, 0x01F1F000, "Dmic3Clk" }, + { 0x0998, 0x01F1F000, "Dmic3Dat" }, + { 0x099C, 0x01F1F000, "DpHpd" }, + { 0x09A0, 0x01F1F000, "DvfsClk" }, + { 0x09A4, 0x01F1F000, "DvfsPwm" }, + { 0x09A8, 0x01F1F000, "Gen1I2cScl" }, + { 0x09AC, 0x01F1F000, "Gen1I2cSda" }, + { 0x09B0, 0x01F1F000, "Gen2I2cScl" }, + { 0x09B4, 0x01F1F000, "Gen2I2cSda" }, + { 0x09B8, 0x01F1F000, "Gen3I2cScl" }, + { 0x09BC, 0x01F1F000, "Gen3I2cSda" }, + { 0x09C0, 0x01F1F000, "GpioPa6" }, + { 0x09C4, 0x01F1F000, "GpioPcc7" }, + { 0x09C8, 0x01F1F000, "GpioPe6" }, + { 0x09CC, 0x01F1F000, "GpioPe7" }, + { 0x09D0, 0x01F1F000, "GpioPh6" }, + { 0x09D4, 0xF0000000, "GpioPk0" }, + { 0x09D8, 0xF0000000, "GpioPk1" }, + { 0x09DC, 0xF0000000, "GpioPk2" }, + { 0x09E0, 0xF0000000, "GpioPk3" }, + { 0x09E4, 0xF0000000, "GpioPk4" }, + { 0x09E8, 0xF0000000, "GpioPk5" }, + { 0x09EC, 0xF0000000, "GpioPk6" }, + { 0x09F0, 0xF0000000, "GpioPk7" }, + { 0x09F4, 0xF0000000, "GpioPl0" }, + { 0x09F8, 0xF0000000, "GpioPl1" }, + { 0x09FC, 0x07F7F000, "GpioPz0" }, + { 0x0A00, 0x07F7F000, "GpioPz1" }, + { 0x0A04, 0x07F7F000, "GpioPz2" }, + { 0x0A08, 0x07F7F000, "GpioPz3" }, + { 0x0A0C, 0x07F7F000, "GpioPz4" }, + { 0x0A10, 0x07F7F000, "GpioPz5" }, + { 0x0A14, 0x01F1F000, "GpioX1Aud" }, + { 0x0A18, 0x01F1F000, "GpioX3Aud" }, + { 0x0A1C, 0x01F1F000, "GpsEn" }, + { 0x0A20, 0x01F1F000, "GpsRst" }, + { 0x0A24, 0x01F1F000, "HdmiCec" }, + { 0x0A28, 0x01F1F000, "HdmiIntDpHpd" }, + { 0x0A2C, 0x01F1F000, "JtagRtck" }, + { 0x0A30, 0x01F1F000, "LcdBlEn" }, + { 0x0A34, 0x01F1F000, "LcdBlPwm" }, + { 0x0A38, 0x01F1F000, "LcdGpio1" }, + { 0x0A3C, 0x01F1F000, "LcdGpio2" }, + { 0x0A40, 0x01F1F000, "LcdRst" }, + { 0x0A44, 0x01F1F000, "LcdTe" }, + { 0x0A48, 0x01F1F000, "ModemWakeAp" }, + { 0x0A4C, 0x01F1F000, "MotionInt" }, + { 0x0A50, 0x01F1F000, "NfcEn" }, + { 0x0A54, 0x01F1F000, "NfcInt" }, + { 0x0A58, 0x01F1F000, "PexL0ClkReqN" }, + { 0x0A5C, 0x01F1F000, "PexL0RstN" }, + { 0x0A60, 0x01F1F000, "PexL1ClkreqN" }, + { 0x0A64, 0x01F1F000, "PexL1RstN" }, + { 0x0A68, 0x01F1F000, "PexWakeN" }, + { 0x0A6C, 0x01F1F000, "PwrI2cScl" }, + { 0x0A70, 0x01F1F000, "PwrI2cSda" }, + { 0x0A74, 0x01F1F000, "PwrIntN" }, + { 0x0A78, 0x07F7F000, "QspiComp" }, + { 0x0A90, 0xF0000000, "QspiSck" }, + { 0x0A94, 0x01F1F000, "SataLedActive" }, + { 0x0A98, 0xF7F7F000, "Sdmmc1Pad" }, + { 0x0AB0, 0xF7F7F000, "Sdmmc3Pad" }, + { 0x0AC8, 0x01F1F000, "Shutdown" }, + { 0x0ACC, 0x01F1F000, "SpdifIn" }, + { 0x0AD0, 0x01F1F000, "SpdifOut" }, + { 0x0AD4, 0xF0000000, "Spi1Cs0" }, + { 0x0AD8, 0xF0000000, "Spi1Cs1" }, + { 0x0ADC, 0xF0000000, "Spi1Miso" }, + { 0x0AE0, 0xF0000000, "Spi1Mosi" }, + { 0x0AE4, 0xF0000000, "Spi1Sck" }, + { 0x0AE8, 0xF0000000, "Spi2Cs0" }, + { 0x0AEC, 0xF0000000, "Spi2Cs1" }, + { 0x0AF0, 0xF0000000, "Spi2Miso" }, + { 0x0AF4, 0xF0000000, "Spi2Mosi" }, + { 0x0AF8, 0xF0000000, "Spi2Sck" }, + { 0x0AFC, 0xF0000000, "Spi4Cs0" }, + { 0x0B00, 0xF0000000, "Spi4Miso" }, + { 0x0B04, 0xF0000000, "Spi4Mosi" }, + { 0x0B08, 0xF0000000, "Spi4Sck" }, + { 0x0B0C, 0x01F1F000, "TempAlert" }, + { 0x0B10, 0x01F1F000, "TouchClk" }, + { 0x0B14, 0x01F1F000, "TouchInt" }, + { 0x0B18, 0x01F1F000, "TouchRst" }, + { 0x0B1C, 0x01F1F000, "Uart1Cts" }, + { 0x0B20, 0x01F1F000, "Uart1Rts" }, + { 0x0B24, 0x01F1F000, "Uart1Rx" }, + { 0x0B28, 0x01F1F000, "Uart1Tx" }, + { 0x0B2C, 0x01F1F000, "Uart2Cts" }, + { 0x0B30, 0x01F1F000, "Uart2Rts" }, + { 0x0B34, 0x01F1F000, "Uart2Rx" }, + { 0x0B38, 0x01F1F000, "Uart2Tx" }, + { 0x0B3C, 0x01F1F000, "Uart3Cts" }, + { 0x0B40, 0x01F1F000, "Uart3Rts" }, + { 0x0B44, 0x01F1F000, "Uart3Rx" }, + { 0x0B48, 0x01F1F000, "Uart3Tx" }, + { 0x0B4C, 0x01F1F000, "Uart4Cts" }, + { 0x0B50, 0x01F1F000, "Uart4Rts" }, + { 0x0B54, 0x01F1F000, "Uart4Rx" }, + { 0x0B58, 0x01F1F000, "Uart4Tx" }, + { 0x0B5C, 0x01F1F000, "UsbVbusEn0" }, + { 0x0B60, 0x01F1F000, "UsbVbusEn1" }, + { 0x0B64, 0x01F1F000, "WifiEn" }, + { 0x0B68, 0x01F1F000, "WifiRst" }, + { 0x0B6C, 0x01F1F000, "WifiWakeAp" }, +}; + +constexpr inline size_t NumPinmuxDrivePadCharacters = util::size(PinmuxDrivePadCharacters); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_drive_pad_config.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_drive_pad_config.inc new file mode 100644 index 00000000..dcb9ada9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_drive_pad_config.inc @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by drive_pad_initial_config.py, do not edit manually. */ + +constexpr inline const PinmuxDrivePadConfig PinmuxDrivePadConfigs[] = { + { PinmuxDrivePadIndex_AudMclk, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Cam1Mclk, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Cam2Mclk, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_CamAfEn, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_CamFlashEn, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_CamI2cScl, 0x0001F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_CamI2cSda, 0x0001F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dap4Din, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dap4Dout, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dap4Fs, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dap4Sclk, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dmic1Clk, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dmic1Dat, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dmic2Clk, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dmic2Dat, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dmic3Clk, 0x01F1F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dmic3Dat, 0x01F1F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_DvfsClk, 0x01F1F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_DvfsPwm, 0x01404000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Gen1I2cScl, 0x0001F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Gen1I2cSda, 0x0001F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Gen2I2cScl, 0x0001F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Gen2I2cSda, 0x0001F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Gen3I2cScl, 0x00007000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Gen3I2cSda, 0x00007000, 0x01F1F000 }, + { PinmuxDrivePadIndex_GpioPz0, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_GpioPz1, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_GpioX1Aud, 0x01404000, 0x01F1F000 }, + { PinmuxDrivePadIndex_GpioX3Aud, 0x01404000, 0x01F1F000 }, + { PinmuxDrivePadIndex_PwrI2cScl, 0x0001F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_PwrI2cSda, 0x0001F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_TouchClk, 0x01414000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Uart3Cts, 0x01404000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Uart3Rts, 0x01404000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Uart3Rx, 0x01404000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Uart3Tx, 0x01404000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Spi1Cs0, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Spi1Cs1, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Spi1Miso, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Spi1Mosi, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Spi1Sck, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Spi2Cs0, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Spi2Cs1, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Spi2Miso, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Spi2Mosi, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Spi2Sck, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Sdmmc3Pad, 0x51212000, 0xF1F1F000 }, +}; + +constexpr inline const size_t NumPinmuxDrivePadConfigs = util::size(PinmuxDrivePadConfigs); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_drive_pad_config_hoag.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_drive_pad_config_hoag.inc new file mode 100644 index 00000000..22484797 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_drive_pad_config_hoag.inc @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by drive_pad_initial_config.py, do not edit manually. */ + +constexpr inline const PinmuxDrivePadConfig PinmuxDrivePadConfigsHoag[] = { + { PinmuxDrivePadIndex_AudMclk, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Cam1Mclk, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Cam2Mclk, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_CamAfEn, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_CamFlashEn, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_CamI2cScl, 0x0001F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_CamI2cSda, 0x0001F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dap4Din, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dap4Dout, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dap4Fs, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dap4Sclk, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dmic1Clk, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dmic1Dat, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dmic2Clk, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dmic2Dat, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dmic3Clk, 0x01F1F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Dmic3Dat, 0x01F1F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_DvfsClk, 0x01F1F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_DvfsPwm, 0x01404000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Gen1I2cScl, 0x0001F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Gen1I2cSda, 0x0001F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Gen2I2cScl, 0x00004000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Gen2I2cSda, 0x00004000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Gen3I2cScl, 0x00007000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Gen3I2cSda, 0x00007000, 0x01F1F000 }, + { PinmuxDrivePadIndex_GpioPz0, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_GpioPz1, 0x01010000, 0x01F1F000 }, + { PinmuxDrivePadIndex_GpioX1Aud, 0x01404000, 0x01F1F000 }, + { PinmuxDrivePadIndex_GpioX3Aud, 0x01404000, 0x01F1F000 }, + { PinmuxDrivePadIndex_PwrI2cScl, 0x0001F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_PwrI2cSda, 0x0001F000, 0x01F1F000 }, + { PinmuxDrivePadIndex_TouchClk, 0x01414000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Uart3Cts, 0x01404000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Uart3Rts, 0x01404000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Uart3Rx, 0x01404000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Uart3Tx, 0x01404000, 0x01F1F000 }, + { PinmuxDrivePadIndex_Spi1Cs0, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Spi1Cs1, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Spi1Miso, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Spi1Mosi, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Spi1Sck, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Spi2Cs0, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Spi2Cs1, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Spi2Miso, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Spi2Mosi, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Spi2Sck, 0x00000000, 0xF0000000 }, + { PinmuxDrivePadIndex_Sdmmc3Pad, 0x51212000, 0xF1F1F000 }, +}; + +constexpr inline const size_t NumPinmuxDrivePadConfigsHoag = util::size(PinmuxDrivePadConfigsHoag); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_pad_config_aula.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_pad_config_aula.inc new file mode 100644 index 00000000..9e249bcb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_pad_config_aula.inc @@ -0,0 +1,196 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by pinmux_initial_config.py, do not edit manually. */ + +constexpr inline const PinmuxPadConfig PinmuxPadConfigsAula[] = { +{ PinmuxPadIndex_AudMclk, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Dap1Din, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_Dap1Dout, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Dap1Fs, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Dap1Sclk, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Gen3I2cScl, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Gen3I2cSda, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_PexL0ClkreqN, 0x00000020, 0x0000027F }, +{ PinmuxPadIndex_PexL0RstN, 0x00000000, 0x0000027F }, +{ PinmuxPadIndex_PexL1ClkreqN, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_PexL1RstN, 0x00000000, 0x0000027F }, +{ PinmuxPadIndex_PexWakeN, 0x00000020, 0x0000027F }, +{ PinmuxPadIndex_Sdmmc1Clk, 0x00000040, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Cmd, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat0, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat1, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat2, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat3, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Clk, 0x00000040, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Cmd, 0x00000040, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat0, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat1, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat2, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat3, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat4, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat5, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat6, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat7, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Shutdown, 0x00000000, 0x00000078 }, +{ PinmuxPadIndex_LcdGpio2, 0x00000001, 0x0000007F }, +{ PinmuxPadIndex_PwrI2cScl, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_PwrI2cSda, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Clk32kIn, 0x00000020, 0x00000078 }, +{ PinmuxPadIndex_Clk32kOut, 0x00000020, 0x0000007F }, +{ PinmuxPadIndex_CorePwrReq, 0x00000000, 0x00000078 }, +{ PinmuxPadIndex_PwrIntN, 0x00000020, 0x00000078 }, +{ PinmuxPadIndex_Gen1I2cScl, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Gen1I2cSda, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Gen2I2cScl, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Gen2I2cSda, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Uart2Rx, 0x00000020, 0x0000007F }, +{ PinmuxPadIndex_Uart2Rts, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart2Cts, 0x00000020, 0x0000007F }, +{ PinmuxPadIndex_Uart1Tx, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart1Rx, 0x00000030, 0x0000007F }, +{ PinmuxPadIndex_Uart1Rts, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart1Cts, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_JtagRtck, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_GpioPl1, 0x00000030, 0x0000007F }, +{ PinmuxPadIndex_Spi4Mosi, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Spi4Miso, 0x00000030, 0x0000007F }, +{ PinmuxPadIndex_Spi4Sck, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Spi4Cs0, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart3Rx, 0x00000020, 0x0000007F }, +{ PinmuxPadIndex_Uart3Rts, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart3Cts, 0x00000030, 0x0000007F }, +{ PinmuxPadIndex_Uart4Tx, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart4Rx, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_Uart4Rts, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart4Cts, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_Dap2Din, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_Dap2Dout, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Dap2Fs, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Dap2Sclk, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_HdmiIntDpHpd, 0x00000020, 0x0000027F }, +{ PinmuxPadIndex_GpioX1Aud, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioX3Aud, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_Dmic1Clk, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dmic1Dat, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dmic2Clk, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dmic2Dat, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dmic3Clk, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioPe6, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_CamI2cSda, 0x00000034, 0x0000027F }, +{ PinmuxPadIndex_Cam2Mclk, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_CamFlashEn, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Cam1Pwdn, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_SataLedActive, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_AlsProxInt, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_TempAlert, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_MotionInt, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_TouchRst, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_TouchInt, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ButtonPowerOn, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_ButtonVolUp, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ButtonVolDown, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ButtonSlideSw, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_ButtonHome, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_LcdRst, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_ApReady, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioPz0, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_GpioPz1, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPz3, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPz4, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dap4Sclk, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Uart2Tx, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_GpioPk0, 0x0000004C, 0x0000007F }, +{ PinmuxPadIndex_GpioPk1, 0x0000004C, 0x0000007F }, +{ PinmuxPadIndex_GpioPk2, 0x00000044, 0x0000007F }, +{ PinmuxPadIndex_GpioPk3, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioPk4, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPk5, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioPk6, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_Uart3Tx, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_WifiEn, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_WifiRst, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_WifiWakeAp, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_ApWakeBt, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_BtRst, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_BtWakeAp, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_GpioPh6, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_ApWakeNfc, 0x00000004, 0x0000007F }, +{ PinmuxPadIndex_SpdifIn, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_DvfsPwm, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_DvfsClk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Mosi, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Miso, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Sck, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Cs0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Cs1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dmic3Dat, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPe7, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_CamI2cScl, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Cam1Mclk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_CamRst, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_CamAfEn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Cam2Pwdn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Cam1Strobe, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPa6, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Clkb, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Dqs, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Dqsb, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Clk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Cmd, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Dat0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Dat1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Dat2, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Dat3, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_TouchClk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_ModemWakeAp, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_LcdTe, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_LcdBlPwm, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_LcdBlEn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_LcdGpio1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPz2, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPz5, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_ClkReq, 0x00000000, 0x00000000 }, +{ PinmuxPadIndex_CpuPwrReq, 0x00000000, 0x00000000 }, +{ PinmuxPadIndex_Dap4Din, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dap4Dout, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dap4Fs, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPk7, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPl0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Mosi, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Miso, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Sck, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Cs0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Cs1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_NfcEn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_NfcInt, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpsEn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpsRst, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiIo0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiIo1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiSck, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiCsN, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiIo2, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiIo3, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPcc7, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_SpdifOut, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_UsbVbusEn0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_UsbVbusEn1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_DpHpd0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_HdmiCec, 0x00000005, 0x00000007 }, +}; + +constexpr inline const size_t NumPinmuxPadConfigsAula = util::size(PinmuxPadConfigsAula); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_pad_config_calcio.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_pad_config_calcio.inc new file mode 100644 index 00000000..fae9aa8a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_pad_config_calcio.inc @@ -0,0 +1,196 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by pinmux_initial_config.py, do not edit manually. */ + +constexpr inline const PinmuxPadConfig PinmuxPadConfigsCalcio[] = { +{ PinmuxPadIndex_PexL0ClkreqN, 0x00000020, 0x0000027F }, +{ PinmuxPadIndex_PexL0RstN, 0x00000000, 0x0000027F }, +{ PinmuxPadIndex_PexL1ClkreqN, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_PexL1RstN, 0x00000000, 0x0000027F }, +{ PinmuxPadIndex_PexWakeN, 0x00000020, 0x0000027F }, +{ PinmuxPadIndex_Sdmmc1Clk, 0x00000040, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Cmd, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat0, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat1, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat2, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat3, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Shutdown, 0x00000000, 0x00000078 }, +{ PinmuxPadIndex_LcdGpio2, 0x00000001, 0x0000007F }, +{ PinmuxPadIndex_PwrI2cScl, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_PwrI2cSda, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Clk32kIn, 0x00000020, 0x00000078 }, +{ PinmuxPadIndex_Clk32kOut, 0x00000001, 0x0000007F }, +{ PinmuxPadIndex_CorePwrReq, 0x00000000, 0x00000078 }, +{ PinmuxPadIndex_PwrIntN, 0x00000020, 0x00000078 }, +{ PinmuxPadIndex_Gen1I2cScl, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Gen1I2cSda, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Uart1Tx, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart1Rx, 0x00000030, 0x0000007F }, +{ PinmuxPadIndex_Uart1Rts, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart1Cts, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_JtagRtck, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_Uart4Tx, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart4Rx, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_Uart4Rts, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart4Cts, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_Dap2Din, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_Dap2Dout, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Dap2Fs, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Dap2Sclk, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_HdmiCec, 0x00000240, 0x0000027F }, +{ PinmuxPadIndex_Dmic1Clk, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dmic1Dat, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dmic2Clk, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dmic2Dat, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dmic3Clk, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioPe6, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_Gen3I2cSda, 0x00000204, 0x0000027F }, +{ PinmuxPadIndex_Cam1Pwdn, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_TempAlert, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ButtonPowerOn, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_ButtonVolUp, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ButtonVolDown, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ButtonHome, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ApReady, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioPz1, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPz3, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPk0, 0x0000004C, 0x0000007F }, +{ PinmuxPadIndex_GpioPk1, 0x0000004C, 0x0000007F }, +{ PinmuxPadIndex_GpioPk2, 0x00000044, 0x0000007F }, +{ PinmuxPadIndex_GpioPk4, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPk6, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_Spi1Mosi, 0x0000000C, 0x0000007F }, +{ PinmuxPadIndex_Spi1Miso, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_Spi1Sck, 0x0000000C, 0x0000007F }, +{ PinmuxPadIndex_WifiEn, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_WifiRst, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_WifiWakeAp, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_ApWakeBt, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_BtRst, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_BtWakeAp, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_GpioPh6, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ApWakeNfc, 0x00000004, 0x0000007F }, +{ PinmuxPadIndex_DpHpd0, 0x00000004, 0x0000007F }, +{ PinmuxPadIndex_HdmiIntDpHpd, 0x00000024, 0x0000027F }, +{ PinmuxPadIndex_AudMclk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_DvfsPwm, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_DvfsClk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioX1Aud, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioX3Aud, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dap1Din, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dap1Dout, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dap1Fs, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dap1Sclk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Mosi, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Miso, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Sck, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Cs0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Cs1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dmic3Dat, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPe7, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Gen3I2cScl, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_CamI2cScl, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_CamI2cSda, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Cam1Mclk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Cam2Mclk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_CamRst, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_CamAfEn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_CamFlashEn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Cam2Pwdn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Cam1Strobe, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_SataLedActive, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPa6, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Clk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Clkb, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Cmd, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Dat0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Dat1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Dat2, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Dat3, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Dat4, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Dat5, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Dat6, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Dat7, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Dqs, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Dqsb, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Clk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Cmd, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Dat0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Dat1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Dat2, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Dat3, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_AlsProxInt, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_MotionInt, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_TouchRst, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_TouchClk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_TouchInt, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_ModemWakeAp, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_ButtonSlideSw, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_LcdTe, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_LcdBlPwm, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_LcdBlEn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_LcdRst, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_LcdGpio1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPz0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPz2, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPz4, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPz5, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_ClkReq, 0x00000000, 0x00000018 }, +{ PinmuxPadIndex_CpuPwrReq, 0x00000000, 0x00000018 }, +{ PinmuxPadIndex_Dap4Din, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dap4Dout, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dap4Fs, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dap4Sclk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Gen2I2cScl, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Gen2I2cSda, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Uart2Tx, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Uart2Rx, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Uart2Rts, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Uart2Cts, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPk3, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPk5, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPk7, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPl0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPl1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Cs0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Cs1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi4Mosi, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi4Miso, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi4Sck, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi4Cs0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Uart3Tx, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Uart3Rx, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Uart3Rts, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Uart3Cts, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_NfcEn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_NfcInt, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpsEn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpsRst, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiIo0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiIo1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiSck, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiCsN, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiIo2, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiIo3, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPcc7, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_SpdifOut, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_SpdifIn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_UsbVbusEn0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_UsbVbusEn1, 0x00000005, 0x00000007 }, +}; + +constexpr inline const size_t NumPinmuxPadConfigsCalcio = util::size(PinmuxPadConfigsCalcio); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_pad_config_hoag.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_pad_config_hoag.inc new file mode 100644 index 00000000..05eba920 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_pad_config_hoag.inc @@ -0,0 +1,196 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by pinmux_initial_config.py, do not edit manually. */ + +constexpr inline const PinmuxPadConfig PinmuxPadConfigsHoag[] = { +{ PinmuxPadIndex_AudMclk, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Dap1Din, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_Dap1Dout, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Dap1Fs, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Dap1Sclk, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Gen3I2cScl, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Gen3I2cSda, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_PexL0ClkreqN, 0x00000020, 0x0000027F }, +{ PinmuxPadIndex_PexL0RstN, 0x00000000, 0x0000027F }, +{ PinmuxPadIndex_PexL1ClkreqN, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_PexL1RstN, 0x00000000, 0x0000027F }, +{ PinmuxPadIndex_PexWakeN, 0x00000020, 0x0000027F }, +{ PinmuxPadIndex_Sdmmc1Clk, 0x00000040, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Cmd, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat0, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat1, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat2, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat3, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Clk, 0x00000040, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Cmd, 0x00000040, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat0, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat1, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat2, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat3, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat4, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat5, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat6, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat7, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_TouchClk, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_Shutdown, 0x00000000, 0x00000078 }, +{ PinmuxPadIndex_LcdBlPwm, 0x00000001, 0x00000067 }, +{ PinmuxPadIndex_LcdGpio2, 0x00000001, 0x0000007F }, +{ PinmuxPadIndex_PwrI2cScl, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_PwrI2cSda, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Clk32kIn, 0x00000020, 0x00000078 }, +{ PinmuxPadIndex_Clk32kOut, 0x00000001, 0x0000007F }, +{ PinmuxPadIndex_CorePwrReq, 0x00000000, 0x00000078 }, +{ PinmuxPadIndex_PwrIntN, 0x00000020, 0x00000078 }, +{ PinmuxPadIndex_Gen1I2cScl, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Gen1I2cSda, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Gen2I2cScl, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Gen2I2cSda, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Uart1Tx, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart1Rx, 0x00000030, 0x0000007F }, +{ PinmuxPadIndex_Uart1Rts, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart1Cts, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_JtagRtck, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_GpioPl1, 0x00000030, 0x0000007F }, +{ PinmuxPadIndex_Uart3Tx, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart3Rx, 0x00000020, 0x0000007F }, +{ PinmuxPadIndex_Uart3Rts, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart3Cts, 0x00000030, 0x0000007F }, +{ PinmuxPadIndex_Uart4Tx, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart4Rx, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_Uart4Rts, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart4Cts, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_Dap2Din, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_Dap2Dout, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Dap2Fs, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Dap2Sclk, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_GpioX1Aud, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioX3Aud, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_Dmic1Clk, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dmic1Dat, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dmic2Clk, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dmic2Dat, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dmic3Clk, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioPe6, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_GpioPe7, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_CamI2cSda, 0x00000034, 0x0000027F }, +{ PinmuxPadIndex_Cam2Mclk, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_CamFlashEn, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Cam1Pwdn, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_Cam2Pwdn, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Cam1Strobe, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_SataLedActive, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_AlsProxInt, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_TempAlert, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_MotionInt, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_TouchRst, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_TouchInt, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ButtonPowerOn, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_ButtonVolUp, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ButtonVolDown, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ButtonSlideSw, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_ButtonHome, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_LcdBlEn, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_LcdRst, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_LcdGpio1, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ApReady, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioPz0, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_GpioPz1, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPz3, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPz4, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dap4Dout, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dap4Fs, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_Dap4Sclk, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioPk0, 0x0000004C, 0x0000007F }, +{ PinmuxPadIndex_GpioPk1, 0x0000004C, 0x0000007F }, +{ PinmuxPadIndex_GpioPk2, 0x00000044, 0x0000007F }, +{ PinmuxPadIndex_GpioPk4, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPk5, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioPk6, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPk7, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Spi4Mosi, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_Spi4Miso, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_Spi4Sck, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_Spi4Cs0, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_WifiEn, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_WifiRst, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_WifiWakeAp, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_ApWakeBt, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_BtRst, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_BtWakeAp, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_GpioPh6, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_ApWakeNfc, 0x00000004, 0x0000007F }, +{ PinmuxPadIndex_NfcEn, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_NfcInt, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_UsbVbusEn1, 0x00000204, 0x00000267 }, +{ PinmuxPadIndex_DvfsPwm, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_DvfsClk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Mosi, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Miso, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Sck, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Cs0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Cs1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dmic3Dat, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_CamI2cScl, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Cam1Mclk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_CamRst, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_CamAfEn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPa6, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Clkb, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Dqs, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Dqsb, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Clk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Cmd, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Dat0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Dat1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Dat2, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Dat3, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_ModemWakeAp, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_LcdTe, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPz2, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPz5, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_ClkReq, 0x00000000, 0x00000000 }, +{ PinmuxPadIndex_CpuPwrReq, 0x00000000, 0x00000000 }, +{ PinmuxPadIndex_Dap4Din, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Uart2Tx, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Uart2Rx, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Uart2Rts, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Uart2Cts, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPk3, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPl0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Mosi, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Miso, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Sck, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Cs0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Cs1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpsEn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpsRst, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiIo0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiIo1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiSck, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiCsN, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiIo2, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiIo3, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPcc7, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_SpdifOut, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_SpdifIn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_UsbVbusEn0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_DpHpd0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_HdmiIntDpHpd, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_HdmiCec, 0x00000005, 0x00000007 }, +}; + +constexpr inline const size_t NumPinmuxPadConfigsHoag = util::size(PinmuxPadConfigsHoag); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_pad_config_icosa.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_pad_config_icosa.inc new file mode 100644 index 00000000..7e2d31a8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_pad_config_icosa.inc @@ -0,0 +1,183 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by pinmux_initial_config.py, do not edit manually. */ + +constexpr inline const PinmuxPadConfig PinmuxPadConfigsIcosa[] = { +{ PinmuxPadIndex_AudMclk, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_Dap1Din, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_Dap1Dout, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_Dap1Fs, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_Dap1Sclk, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_Gen3I2cScl, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Gen3I2cSda, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_PexL0ClkreqN, 0x00000020, 0x0000027F }, +{ PinmuxPadIndex_PexL0RstN, 0x00000000, 0x00000267 }, +{ PinmuxPadIndex_PexL1ClkreqN, 0x00000020, 0x0000027F }, +{ PinmuxPadIndex_PexL1RstN, 0x00000000, 0x00000267 }, +{ PinmuxPadIndex_PexWakeN, 0x00000020, 0x0000027F }, +{ PinmuxPadIndex_Sdmmc1Clk, 0x00000048, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Cmd, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat0, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat1, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat2, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat3, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Shutdown, 0x00000000, 0x00000078 }, +{ PinmuxPadIndex_LcdBlPwm, 0x00000001, 0x00000067 }, +{ PinmuxPadIndex_LcdGpio2, 0x00000001, 0x0000007F }, +{ PinmuxPadIndex_PwrI2cScl, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_PwrI2cSda, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Clk32kIn, 0x00000020, 0x00000078 }, +{ PinmuxPadIndex_Clk32kOut, 0x00000020, 0x0000007F }, +{ PinmuxPadIndex_GpioPz5, 0x00000030, 0x0000007F }, +{ PinmuxPadIndex_CorePwrReq, 0x00000000, 0x00000078 }, +{ PinmuxPadIndex_CpuPwrReq, 0x00000000, 0x00000060 }, +{ PinmuxPadIndex_PwrIntN, 0x00000030, 0x00000078 }, +{ PinmuxPadIndex_Gen1I2cScl, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Gen1I2cSda, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Gen2I2cScl, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Gen2I2cSda, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Uart2Rx, 0x00000020, 0x0000007F }, +{ PinmuxPadIndex_Uart2Rts, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_Uart2Cts, 0x00000020, 0x0000007F }, +{ PinmuxPadIndex_Uart1Tx, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_Uart1Rx, 0x00000030, 0x0000007F }, +{ PinmuxPadIndex_Uart1Rts, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_Uart1Cts, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_JtagRtck, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_GpioPl1, 0x00000030, 0x0000007F }, +{ PinmuxPadIndex_Spi4Mosi, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_Spi4Miso, 0x00000030, 0x0000007F }, +{ PinmuxPadIndex_Spi4Sck, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_Spi4Cs0, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_Uart3Rx, 0x00000020, 0x0000007F }, +{ PinmuxPadIndex_Uart3Rts, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_Uart3Cts, 0x00000020, 0x0000007F }, +{ PinmuxPadIndex_Uart4Tx, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_Uart4Rx, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_Uart4Rts, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_Uart4Cts, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_QspiIo0, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_QspiIo1, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_QspiSck, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_QspiCsN, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_Dap2Din, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_Dap2Dout, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_Dap2Fs, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_Dap2Sclk, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_HdmiIntDpHpd, 0x00000020, 0x0000027F }, +{ PinmuxPadIndex_DvfsClk, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioX1Aud, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioX3Aud, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_Dmic3Clk, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dmic3Dat, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioPe6, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_CamI2cSda, 0x00000034, 0x0000027F }, +{ PinmuxPadIndex_Cam1Mclk, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_Cam2Mclk, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_CamFlashEn, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Cam1Pwdn, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_SataLedActive, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Sdmmc3Clk, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc3Dat0, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc3Dat1, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc3Dat2, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc3Dat3, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_TempAlert, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_MotionInt, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_TouchRst, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_TouchInt, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ButtonPowerOn, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ButtonVolUp, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ButtonVolDown, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ButtonSlideSw, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ButtonHome, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_LcdBlEn, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_LcdRst, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_ApReady, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioPz0, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPz1, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPz2, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_GpioPz3, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPz4, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dap4Din, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dap4Sclk, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Uart2Tx, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_GpioPk0, 0x0000004C, 0x0000007F }, +{ PinmuxPadIndex_GpioPk1, 0x0000004C, 0x0000007F }, +{ PinmuxPadIndex_GpioPk2, 0x00000044, 0x0000007F }, +{ PinmuxPadIndex_GpioPk3, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioPk4, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPk5, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioPk6, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPk7, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPl0, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Uart3Tx, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_WifiEn, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_WifiRst, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_WifiWakeAp, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_ApWakeBt, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_BtRst, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_BtWakeAp, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_GpioPh6, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_ApWakeNfc, 0x00000004, 0x0000007F }, +{ PinmuxPadIndex_NfcEn, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_NfcInt, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_SpdifOut, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_SpdifIn, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_UsbVbusEn0, 0x00000004, 0x00000267 }, +{ PinmuxPadIndex_DvfsPwm, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Mosi, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Miso, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Sck, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Cs0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Cs1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dmic1Clk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dmic1Dat, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dmic2Clk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dmic2Dat, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPe7, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_CamI2cScl, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_CamRst, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_CamAfEn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Cam2Pwdn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Cam1Strobe, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPa6, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Cmd, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_AlsProxInt, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_TouchClk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_ModemWakeAp, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_LcdTe, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_LcdGpio1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_ClkReq, 0x00000000, 0x00000000 }, +{ PinmuxPadIndex_Dap4Dout, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dap4Fs, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Mosi, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Miso, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Sck, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Cs0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Cs1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpsEn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpsRst, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiIo2, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiIo3, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPcc7, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_UsbVbusEn1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_DpHpd0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_HdmiCec, 0x00000005, 0x00000007 }, +}; + +constexpr inline const size_t NumPinmuxPadConfigsIcosa = util::size(PinmuxPadConfigsIcosa); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_pad_config_iowa.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_pad_config_iowa.inc new file mode 100644 index 00000000..c7d1a7f3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_initial_pad_config_iowa.inc @@ -0,0 +1,196 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by pinmux_initial_config.py, do not edit manually. */ + +constexpr inline const PinmuxPadConfig PinmuxPadConfigsIowa[] = { +{ PinmuxPadIndex_AudMclk, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Dap1Din, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_Dap1Dout, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Dap1Fs, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Dap1Sclk, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Gen3I2cScl, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Gen3I2cSda, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_PexL0ClkreqN, 0x00000020, 0x0000027F }, +{ PinmuxPadIndex_PexL0RstN, 0x00000000, 0x0000027F }, +{ PinmuxPadIndex_PexL1ClkreqN, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_PexL1RstN, 0x00000000, 0x0000027F }, +{ PinmuxPadIndex_PexWakeN, 0x00000020, 0x0000027F }, +{ PinmuxPadIndex_Sdmmc1Clk, 0x00000040, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Cmd, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat0, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat1, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat2, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc1Dat3, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Clk, 0x00000040, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Cmd, 0x00000040, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat0, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat1, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat2, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat3, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat4, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat5, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat6, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc2Dat7, 0x00000050, 0x0000007F }, +{ PinmuxPadIndex_Shutdown, 0x00000000, 0x00000078 }, +{ PinmuxPadIndex_LcdBlPwm, 0x00000001, 0x00000067 }, +{ PinmuxPadIndex_LcdGpio2, 0x00000001, 0x0000007F }, +{ PinmuxPadIndex_PwrI2cScl, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_PwrI2cSda, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Clk32kIn, 0x00000020, 0x00000078 }, +{ PinmuxPadIndex_Clk32kOut, 0x00000020, 0x0000007F }, +{ PinmuxPadIndex_GpioPz5, 0x00000030, 0x0000007F }, +{ PinmuxPadIndex_CorePwrReq, 0x00000000, 0x00000078 }, +{ PinmuxPadIndex_PwrIntN, 0x00000020, 0x00000078 }, +{ PinmuxPadIndex_Gen1I2cScl, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Gen1I2cSda, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Gen2I2cScl, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Gen2I2cSda, 0x00000040, 0x0000027F }, +{ PinmuxPadIndex_Uart2Rx, 0x00000020, 0x0000007F }, +{ PinmuxPadIndex_Uart2Rts, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart2Cts, 0x00000020, 0x0000007F }, +{ PinmuxPadIndex_Uart1Tx, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart1Rx, 0x00000030, 0x0000007F }, +{ PinmuxPadIndex_Uart1Rts, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart1Cts, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_JtagRtck, 0x00000000, 0x00000067 }, +{ PinmuxPadIndex_GpioPl1, 0x00000030, 0x0000007F }, +{ PinmuxPadIndex_Spi4Mosi, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Spi4Miso, 0x00000030, 0x0000007F }, +{ PinmuxPadIndex_Spi4Sck, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Spi4Cs0, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart3Rx, 0x00000020, 0x0000007F }, +{ PinmuxPadIndex_Uart3Rts, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart3Cts, 0x00000020, 0x0000007F }, +{ PinmuxPadIndex_Uart4Tx, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart4Rx, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_Uart4Rts, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Uart4Cts, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_Dap2Din, 0x00000028, 0x0000007F }, +{ PinmuxPadIndex_Dap2Dout, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Dap2Fs, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_Dap2Sclk, 0x00000000, 0x0000007F }, +{ PinmuxPadIndex_HdmiIntDpHpd, 0x00000020, 0x0000027F }, +{ PinmuxPadIndex_DvfsClk, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioX1Aud, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioX3Aud, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_Dmic3Clk, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioPe6, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_CamI2cSda, 0x00000034, 0x0000027F }, +{ PinmuxPadIndex_Cam1Mclk, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_Cam2Mclk, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_CamFlashEn, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Cam1Pwdn, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_SataLedActive, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Sdmmc3Clk, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc3Dat0, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc3Dat1, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc3Dat2, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_Sdmmc3Dat3, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_AlsProxInt, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_TempAlert, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_MotionInt, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_TouchRst, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_TouchInt, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ButtonPowerOn, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_ButtonVolUp, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ButtonVolDown, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_ButtonSlideSw, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_ButtonHome, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_LcdBlEn, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_LcdRst, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_ApReady, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioPz0, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_GpioPz1, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPz2, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_GpioPz3, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPz4, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dap4Din, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Dap4Sclk, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Uart2Tx, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_GpioPk0, 0x0000004C, 0x0000007F }, +{ PinmuxPadIndex_GpioPk1, 0x0000004C, 0x0000007F }, +{ PinmuxPadIndex_GpioPk2, 0x00000044, 0x0000007F }, +{ PinmuxPadIndex_GpioPk3, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioPk4, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPk5, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_GpioPk6, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_GpioPl0, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_Uart3Tx, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_WifiEn, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_WifiRst, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_WifiWakeAp, 0x0000002C, 0x0000007F }, +{ PinmuxPadIndex_ApWakeBt, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_BtRst, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_BtWakeAp, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_GpioPh6, 0x00000024, 0x0000007F }, +{ PinmuxPadIndex_ApWakeNfc, 0x00000004, 0x0000007F }, +{ PinmuxPadIndex_NfcEn, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_NfcInt, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_SpdifOut, 0x00000034, 0x0000007F }, +{ PinmuxPadIndex_SpdifIn, 0x00000004, 0x00000067 }, +{ PinmuxPadIndex_UsbVbusEn0, 0x00000004, 0x00000267 }, +{ PinmuxPadIndex_DvfsPwm, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Mosi, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Miso, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Sck, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Cs0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi2Cs1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dmic1Clk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dmic1Dat, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dmic2Clk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dmic2Dat, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dmic3Dat, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPe7, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_CamI2cScl, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_CamRst, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_CamAfEn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Cam2Pwdn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Cam1Strobe, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPa6, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Clkb, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Dqs, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc2Dqsb, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Sdmmc3Cmd, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_TouchClk, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_ModemWakeAp, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_LcdTe, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_LcdGpio1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_ClkReq, 0x00000000, 0x00000000 }, +{ PinmuxPadIndex_CpuPwrReq, 0x00000000, 0x00000000 }, +{ PinmuxPadIndex_Dap4Dout, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Dap4Fs, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPk7, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Mosi, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Miso, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Sck, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Cs0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_Spi1Cs1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpsEn, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpsRst, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiIo0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiIo1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiSck, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiCsN, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiIo2, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_QspiIo3, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_GpioPcc7, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_UsbVbusEn1, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_DpHpd0, 0x00000005, 0x00000007 }, +{ PinmuxPadIndex_HdmiCec, 0x00000005, 0x00000007 }, +}; + +constexpr inline const size_t NumPinmuxPadConfigsIowa = util::size(PinmuxPadConfigsIowa); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_pad_characters.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_pad_characters.inc new file mode 100644 index 00000000..966dd573 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_pad_characters.inc @@ -0,0 +1,197 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by pinmux_character_gen.py, do not edit manually. */ + +constexpr inline const PinmuxPadCharacter PinmuxPadCharacters[] = { + { 0x3000, 0x072FF, 0x01, "Sdmmc1Clk" }, + { 0x3004, 0x072FF, 0x02, "Sdmmc1Cmd" }, + { 0x3008, 0x072FF, 0x02, "Sdmmc1Dat3" }, + { 0x300C, 0x072FF, 0x02, "Sdmmc1Dat2" }, + { 0x3010, 0x072FF, 0x02, "Sdmmc1Dat1" }, + { 0x3014, 0x072FF, 0x01, "Sdmmc1Dat0" }, + { 0x301C, 0x072FF, 0x01, "Sdmmc3Clk" }, + { 0x3020, 0x072FF, 0x01, "Sdmmc3Cmd" }, + { 0x3024, 0x072FF, 0x01, "Sdmmc3Dat0" }, + { 0x3028, 0x072FF, 0x01, "Sdmmc3Dat1" }, + { 0x302C, 0x072FF, 0x01, "Sdmmc3Dat2" }, + { 0x3030, 0x072FF, 0x01, "Sdmmc3Dat3" }, + { 0x3038, 0x01DFF, 0x01, "PexL0RstN" }, + { 0x303C, 0x01DFF, 0x01, "PexL0ClkreqN" }, + { 0x3040, 0x01DFF, 0x01, "PexWakeN" }, + { 0x3044, 0x01DFF, 0x01, "PexL1RstN" }, + { 0x3048, 0x01DFF, 0x01, "PexL1ClkreqN" }, + { 0x304C, 0x019FF, 0x01, "SataLedActive" }, + { 0x3050, 0x1F2FF, 0x01, "Spi1Mosi" }, + { 0x3054, 0x1F2FF, 0x01, "Spi1Miso" }, + { 0x3058, 0x1F2FF, 0x01, "Spi1Sck" }, + { 0x305C, 0x1F2FF, 0x01, "Spi1Cs0" }, + { 0x3060, 0x1F2FF, 0x01, "Spi1Cs1" }, + { 0x3064, 0x072FF, 0x02, "Spi2Mosi" }, + { 0x3068, 0x072FF, 0x02, "Spi2Miso" }, + { 0x306C, 0x072FF, 0x02, "Spi2Sck" }, + { 0x3070, 0x072FF, 0x02, "Spi2Cs0" }, + { 0x3074, 0x072FF, 0x01, "Spi2Cs1" }, + { 0x3078, 0x1F2FF, 0x01, "Spi4Mosi" }, + { 0x307C, 0x1F2FF, 0x01, "Spi4Miso" }, + { 0x3080, 0x1F2FF, 0x01, "Spi4Sck" }, + { 0x3084, 0x1F2FF, 0x01, "Spi4Cs0" }, + { 0x3088, 0x072FF, 0x01, "QspiSck" }, + { 0x308C, 0x072FF, 0x01, "QspiCsN" }, + { 0x3090, 0x072FF, 0x01, "QspiIo0" }, + { 0x3094, 0x072FF, 0x01, "QspiIo1" }, + { 0x3098, 0x072FF, 0x01, "QspiIo2" }, + { 0x309C, 0x072FF, 0x01, "QspiIo3" }, + { 0x30A4, 0x019FF, 0x02, "Dmic1Clk" }, + { 0x30A8, 0x019FF, 0x02, "Dmic1Dat" }, + { 0x30AC, 0x019FF, 0x02, "Dmic2Clk" }, + { 0x30B0, 0x019FF, 0x02, "Dmic2Dat" }, + { 0x30B4, 0x019FF, 0x02, "Dmic3Clk" }, + { 0x30B8, 0x019FF, 0x02, "Dmic3Dat" }, + { 0x30BC, 0x01DFF, 0x01, "Gen1I2cScl" }, + { 0x30C0, 0x01DFF, 0x01, "Gen1I2cSda" }, + { 0x30C4, 0x01DFF, 0x01, "Gen2I2cScl" }, + { 0x30C8, 0x01DFF, 0x01, "Gen2I2cSda" }, + { 0x30CC, 0x01DFF, 0x01, "Gen3I2cScl" }, + { 0x30D0, 0x01DFF, 0x01, "Gen3I2cSda" }, + { 0x30D4, 0x01DFF, 0x02, "CamI2cScl" }, + { 0x30D8, 0x01DFF, 0x02, "CamI2cSda" }, + { 0x30DC, 0x01DFF, 0x01, "PwrI2cScl" }, + { 0x30E0, 0x01DFF, 0x01, "PwrI2cSda" }, + { 0x30E4, 0x019FF, 0x01, "Uart1Tx" }, + { 0x30E8, 0x019FF, 0x01, "Uart1Rx" }, + { 0x30EC, 0x019FF, 0x01, "Uart1Rts" }, + { 0x30F0, 0x019FF, 0x01, "Uart1Cts" }, + { 0x30F4, 0x019FF, 0x00, "Uart2Tx" }, + { 0x30F8, 0x019FF, 0x00, "Uart2Rx" }, + { 0x30FC, 0x019FF, 0x02, "Uart2Rts" }, + { 0x3100, 0x019FF, 0x02, "Uart2Cts" }, + { 0x3104, 0x019FF, 0x02, "Uart3Tx" }, + { 0x3108, 0x019FF, 0x02, "Uart3Rx" }, + { 0x310C, 0x019FF, 0x02, "Uart3Rts" }, + { 0x3110, 0x019FF, 0x02, "Uart3Cts" }, + { 0x3114, 0x019FF, 0x02, "Uart4Tx" }, + { 0x3118, 0x019FF, 0x02, "Uart4Rx" }, + { 0x311C, 0x019FF, 0x02, "Uart4Rts" }, + { 0x3120, 0x019FF, 0x02, "Uart4Cts" }, + { 0x3124, 0x072FF, 0x01, "Dap1Fs" }, + { 0x3128, 0x072FF, 0x01, "Dap1Din" }, + { 0x312C, 0x072FF, 0x01, "Dap1Dout" }, + { 0x3130, 0x072FF, 0x01, "Dap1Sclk" }, + { 0x3134, 0x072FF, 0x01, "Dap2Fs" }, + { 0x3138, 0x072FF, 0x01, "Dap2Din" }, + { 0x313C, 0x072FF, 0x01, "Dap2Dout" }, + { 0x3140, 0x072FF, 0x01, "Dap2Sclk" }, + { 0x3144, 0x072FF, 0x01, "Dap4Fs" }, + { 0x3148, 0x072FF, 0x01, "Dap4Din" }, + { 0x314C, 0x072FF, 0x01, "Dap4Dout" }, + { 0x3150, 0x072FF, 0x01, "Dap4Sclk" }, + { 0x3154, 0x072FF, 0x01, "Cam1Mclk" }, + { 0x3158, 0x072FF, 0x01, "Cam2Mclk" }, + { 0x315C, 0x072FF, 0x01, "JtagRtck" }, + { 0x3160, 0x0118C, 0xFF, "Clk32kIn" }, + { 0x3164, 0x072FF, 0x02, "Clk32kOut" }, + { 0x3168, 0x01DFF, 0x01, "BattBcl" }, + { 0x316C, 0x011CC, 0xFF, "ClkReq" }, + { 0x3170, 0x011CC, 0xFF, "CpuPwrReq" }, + { 0x3174, 0x011CC, 0xFF, "PwrIntN" }, + { 0x3178, 0x011CC, 0xFF, "Shutdown" }, + { 0x317C, 0x011CC, 0xFF, "CorePwrReq" }, + { 0x3180, 0x019FF, 0x01, "AudMclk" }, + { 0x3184, 0x019FF, 0x00, "DvfsPwm" }, + { 0x3188, 0x019FF, 0x00, "DvfsClk" }, + { 0x318C, 0x019FF, 0x00, "GpioX1Aud" }, + { 0x3190, 0x019FF, 0x00, "GpioX3Aud" }, + { 0x3194, 0x01DFF, 0x00, "GpioPcc7" }, + { 0x3198, 0x01DFF, 0x01, "HdmiCec" }, + { 0x319C, 0x01DFF, 0x01, "HdmiIntDpHpd" }, + { 0x31A0, 0x019FF, 0x01, "SpdifOut" }, + { 0x31A4, 0x019FF, 0x01, "SpdifIn" }, + { 0x31A8, 0x01DFF, 0x01, "UsbVbusEn0" }, + { 0x31AC, 0x01DFF, 0x01, "UsbVbusEn1" }, + { 0x31B0, 0x019FF, 0x01, "DpHpd0" }, + { 0x31B4, 0x019FF, 0x00, "WifiEn" }, + { 0x31B8, 0x019FF, 0x00, "WifiRst" }, + { 0x31BC, 0x019FF, 0x00, "WifiWakeAp" }, + { 0x31C0, 0x019FF, 0x00, "ApWakeBt" }, + { 0x31C4, 0x019FF, 0x00, "BtRst" }, + { 0x31C8, 0x019FF, 0x00, "BtWakeAp" }, + { 0x31CC, 0x019FF, 0x00, "ApWakeNfc" }, + { 0x31D0, 0x019FF, 0x00, "NfcEn" }, + { 0x31D4, 0x019FF, 0x00, "NfcInt" }, + { 0x31D8, 0x019FF, 0x00, "GpsEn" }, + { 0x31DC, 0x019FF, 0x00, "GpsRst" }, + { 0x31E0, 0x019FF, 0x01, "CamRst" }, + { 0x31E4, 0x019FF, 0x02, "CamAfEn" }, + { 0x31E8, 0x019FF, 0x02, "CamFlashEn" }, + { 0x31EC, 0x019FF, 0x01, "Cam1Pwdn" }, + { 0x31F0, 0x019FF, 0x01, "Cam2Pwdn" }, + { 0x31F4, 0x019FF, 0x01, "Cam1Strobe" }, + { 0x31F8, 0x019FF, 0x01, "LcdTe" }, + { 0x31FC, 0x019FF, 0x03, "LcdBlPwm" }, + { 0x3200, 0x019FF, 0x00, "LcdBlEn" }, + { 0x3204, 0x019FF, 0x00, "LcdRst" }, + { 0x3208, 0x019FF, 0x01, "LcdGpio1" }, + { 0x320C, 0x019FF, 0x02, "LcdGpio2" }, + { 0x3210, 0x019FF, 0x00, "ApReady" }, + { 0x3214, 0x019FF, 0x00, "TouchRst" }, + { 0x3218, 0x019FF, 0x01, "TouchClk" }, + { 0x321C, 0x019FF, 0x00, "ModemWakeAp" }, + { 0x3220, 0x019FF, 0x00, "TouchInt" }, + { 0x3224, 0x019FF, 0x00, "MotionInt" }, + { 0x3228, 0x019FF, 0x00, "AlsProxInt" }, + { 0x322C, 0x019FF, 0x00, "TempAlert" }, + { 0x3230, 0x019FF, 0x00, "ButtonPowerOn" }, + { 0x3234, 0x019FF, 0x00, "ButtonVolUp" }, + { 0x3238, 0x019FF, 0x00, "ButtonVolDown" }, + { 0x323C, 0x019FF, 0x00, "ButtonSlideSw" }, + { 0x3240, 0x019FF, 0x00, "ButtonHome" }, + { 0x3244, 0x019FF, 0x01, "GpioPa6" }, + { 0x3248, 0x019FF, 0x00, "GpioPe6" }, + { 0x324C, 0x019FF, 0x00, "GpioPe7" }, + { 0x3250, 0x019FF, 0x00, "GpioPh6" }, + { 0x3254, 0x072FF, 0x02, "GpioPk0" }, + { 0x3258, 0x072FF, 0x02, "GpioPk1" }, + { 0x325C, 0x072FF, 0x02, "GpioPk2" }, + { 0x3260, 0x072FF, 0x02, "GpioPk3" }, + { 0x3264, 0x072FF, 0x01, "GpioPk4" }, + { 0x3268, 0x072FF, 0x01, "GpioPk5" }, + { 0x326C, 0x072FF, 0x01, "GpioPk6" }, + { 0x3270, 0x072FF, 0x01, "GpioPk7" }, + { 0x3274, 0x072FF, 0x00, "GpioPl0" }, + { 0x3278, 0x072FF, 0x01, "GpioPl1" }, + { 0x327C, 0x072FF, 0x01, "GpioPz0" }, + { 0x3280, 0x072FF, 0x02, "GpioPz1" }, + { 0x3284, 0x072FF, 0x02, "GpioPz2" }, + { 0x3288, 0x072FF, 0x01, "GpioPz3" }, + { 0x328C, 0x072FF, 0x01, "GpioPz4" }, + { 0x3290, 0x072FF, 0x01, "GpioPz5" }, + { 0x3294, 0x1F2FF, 0x02, "Sdmmc2Dat0" }, + { 0x3298, 0x1F2FF, 0x02, "Sdmmc2Dat1" }, + { 0x329C, 0x1F2FF, 0x02, "Sdmmc2Dat2" }, + { 0x32A0, 0x1F2FF, 0x02, "Sdmmc2Dat3" }, + { 0x32A4, 0x1F2FF, 0x02, "Sdmmc2Dat4" }, + { 0x32A8, 0x1F2FF, 0x02, "Sdmmc2Dat5" }, + { 0x32AC, 0x1F2FF, 0x02, "Sdmmc2Dat6" }, + { 0x32B0, 0x1F2FF, 0x02, "Sdmmc2Dat7" }, + { 0x32B4, 0x1F2FF, 0x02, "Sdmmc2Clk" }, + { 0x32B8, 0x1F2FF, 0x00, "Sdmmc2Clkb" }, + { 0x32BC, 0x1F2FF, 0x02, "Sdmmc2Cmd" }, + { 0x32C0, 0x1F2FF, 0x00, "Sdmmc2Dqs" }, + { 0x32C4, 0x1F2FF, 0x00, "Sdmmc2Dqsb" }, +}; + +constexpr inline size_t NumPinmuxPadCharacters = util::size(PinmuxPadCharacters); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_pad_index.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_pad_index.hpp new file mode 100644 index 00000000..ee56492c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_pad_index.hpp @@ -0,0 +1,348 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by pinmux_character_gen.py, do not edit manually. */ + +#pragma once + +enum PinmuxPadIndex { + PinmuxPadIndex_Sdmmc1Clk = 0, + PinmuxPadIndex_Sdmmc1Cmd = 1, + PinmuxPadIndex_Sdmmc1Dat3 = 2, + PinmuxPadIndex_Sdmmc1Dat2 = 3, + PinmuxPadIndex_Sdmmc1Dat1 = 4, + PinmuxPadIndex_Sdmmc1Dat0 = 5, + PinmuxPadIndex_Sdmmc3Clk = 6, + PinmuxPadIndex_Sdmmc3Cmd = 7, + PinmuxPadIndex_Sdmmc3Dat0 = 8, + PinmuxPadIndex_Sdmmc3Dat1 = 9, + PinmuxPadIndex_Sdmmc3Dat2 = 10, + PinmuxPadIndex_Sdmmc3Dat3 = 11, + PinmuxPadIndex_PexL0RstN = 12, + PinmuxPadIndex_PexL0ClkreqN = 13, + PinmuxPadIndex_PexWakeN = 14, + PinmuxPadIndex_PexL1RstN = 15, + PinmuxPadIndex_PexL1ClkreqN = 16, + PinmuxPadIndex_SataLedActive = 17, + PinmuxPadIndex_Spi1Mosi = 18, + PinmuxPadIndex_Spi1Miso = 19, + PinmuxPadIndex_Spi1Sck = 20, + PinmuxPadIndex_Spi1Cs0 = 21, + PinmuxPadIndex_Spi1Cs1 = 22, + PinmuxPadIndex_Spi2Mosi = 23, + PinmuxPadIndex_Spi2Miso = 24, + PinmuxPadIndex_Spi2Sck = 25, + PinmuxPadIndex_Spi2Cs0 = 26, + PinmuxPadIndex_Spi2Cs1 = 27, + PinmuxPadIndex_Spi4Mosi = 28, + PinmuxPadIndex_Spi4Miso = 29, + PinmuxPadIndex_Spi4Sck = 30, + PinmuxPadIndex_Spi4Cs0 = 31, + PinmuxPadIndex_QspiSck = 32, + PinmuxPadIndex_QspiCsN = 33, + PinmuxPadIndex_QspiIo0 = 34, + PinmuxPadIndex_QspiIo1 = 35, + PinmuxPadIndex_QspiIo2 = 36, + PinmuxPadIndex_QspiIo3 = 37, + PinmuxPadIndex_Dmic1Clk = 38, + PinmuxPadIndex_Dmic1Dat = 39, + PinmuxPadIndex_Dmic2Clk = 40, + PinmuxPadIndex_Dmic2Dat = 41, + PinmuxPadIndex_Dmic3Clk = 42, + PinmuxPadIndex_Dmic3Dat = 43, + PinmuxPadIndex_Gen1I2cScl = 44, + PinmuxPadIndex_Gen1I2cSda = 45, + PinmuxPadIndex_Gen2I2cScl = 46, + PinmuxPadIndex_Gen2I2cSda = 47, + PinmuxPadIndex_Gen3I2cScl = 48, + PinmuxPadIndex_Gen3I2cSda = 49, + PinmuxPadIndex_CamI2cScl = 50, + PinmuxPadIndex_CamI2cSda = 51, + PinmuxPadIndex_PwrI2cScl = 52, + PinmuxPadIndex_PwrI2cSda = 53, + PinmuxPadIndex_Uart1Tx = 54, + PinmuxPadIndex_Uart1Rx = 55, + PinmuxPadIndex_Uart1Rts = 56, + PinmuxPadIndex_Uart1Cts = 57, + PinmuxPadIndex_Uart2Tx = 58, + PinmuxPadIndex_Uart2Rx = 59, + PinmuxPadIndex_Uart2Rts = 60, + PinmuxPadIndex_Uart2Cts = 61, + PinmuxPadIndex_Uart3Tx = 62, + PinmuxPadIndex_Uart3Rx = 63, + PinmuxPadIndex_Uart3Rts = 64, + PinmuxPadIndex_Uart3Cts = 65, + PinmuxPadIndex_Uart4Tx = 66, + PinmuxPadIndex_Uart4Rx = 67, + PinmuxPadIndex_Uart4Rts = 68, + PinmuxPadIndex_Uart4Cts = 69, + PinmuxPadIndex_Dap1Fs = 70, + PinmuxPadIndex_Dap1Din = 71, + PinmuxPadIndex_Dap1Dout = 72, + PinmuxPadIndex_Dap1Sclk = 73, + PinmuxPadIndex_Dap2Fs = 74, + PinmuxPadIndex_Dap2Din = 75, + PinmuxPadIndex_Dap2Dout = 76, + PinmuxPadIndex_Dap2Sclk = 77, + PinmuxPadIndex_Dap4Fs = 78, + PinmuxPadIndex_Dap4Din = 79, + PinmuxPadIndex_Dap4Dout = 80, + PinmuxPadIndex_Dap4Sclk = 81, + PinmuxPadIndex_Cam1Mclk = 82, + PinmuxPadIndex_Cam2Mclk = 83, + PinmuxPadIndex_JtagRtck = 84, + PinmuxPadIndex_Clk32kIn = 85, + PinmuxPadIndex_Clk32kOut = 86, + PinmuxPadIndex_BattBcl = 87, + PinmuxPadIndex_ClkReq = 88, + PinmuxPadIndex_CpuPwrReq = 89, + PinmuxPadIndex_PwrIntN = 90, + PinmuxPadIndex_Shutdown = 91, + PinmuxPadIndex_CorePwrReq = 92, + PinmuxPadIndex_AudMclk = 93, + PinmuxPadIndex_DvfsPwm = 94, + PinmuxPadIndex_DvfsClk = 95, + PinmuxPadIndex_GpioX1Aud = 96, + PinmuxPadIndex_GpioX3Aud = 97, + PinmuxPadIndex_GpioPcc7 = 98, + PinmuxPadIndex_HdmiCec = 99, + PinmuxPadIndex_HdmiIntDpHpd = 100, + PinmuxPadIndex_SpdifOut = 101, + PinmuxPadIndex_SpdifIn = 102, + PinmuxPadIndex_UsbVbusEn0 = 103, + PinmuxPadIndex_UsbVbusEn1 = 104, + PinmuxPadIndex_DpHpd0 = 105, + PinmuxPadIndex_WifiEn = 106, + PinmuxPadIndex_WifiRst = 107, + PinmuxPadIndex_WifiWakeAp = 108, + PinmuxPadIndex_ApWakeBt = 109, + PinmuxPadIndex_BtRst = 110, + PinmuxPadIndex_BtWakeAp = 111, + PinmuxPadIndex_ApWakeNfc = 112, + PinmuxPadIndex_NfcEn = 113, + PinmuxPadIndex_NfcInt = 114, + PinmuxPadIndex_GpsEn = 115, + PinmuxPadIndex_GpsRst = 116, + PinmuxPadIndex_CamRst = 117, + PinmuxPadIndex_CamAfEn = 118, + PinmuxPadIndex_CamFlashEn = 119, + PinmuxPadIndex_Cam1Pwdn = 120, + PinmuxPadIndex_Cam2Pwdn = 121, + PinmuxPadIndex_Cam1Strobe = 122, + PinmuxPadIndex_LcdTe = 123, + PinmuxPadIndex_LcdBlPwm = 124, + PinmuxPadIndex_LcdBlEn = 125, + PinmuxPadIndex_LcdRst = 126, + PinmuxPadIndex_LcdGpio1 = 127, + PinmuxPadIndex_LcdGpio2 = 128, + PinmuxPadIndex_ApReady = 129, + PinmuxPadIndex_TouchRst = 130, + PinmuxPadIndex_TouchClk = 131, + PinmuxPadIndex_ModemWakeAp = 132, + PinmuxPadIndex_TouchInt = 133, + PinmuxPadIndex_MotionInt = 134, + PinmuxPadIndex_AlsProxInt = 135, + PinmuxPadIndex_TempAlert = 136, + PinmuxPadIndex_ButtonPowerOn = 137, + PinmuxPadIndex_ButtonVolUp = 138, + PinmuxPadIndex_ButtonVolDown = 139, + PinmuxPadIndex_ButtonSlideSw = 140, + PinmuxPadIndex_ButtonHome = 141, + PinmuxPadIndex_GpioPa6 = 142, + PinmuxPadIndex_GpioPe6 = 143, + PinmuxPadIndex_GpioPe7 = 144, + PinmuxPadIndex_GpioPh6 = 145, + PinmuxPadIndex_GpioPk0 = 146, + PinmuxPadIndex_GpioPk1 = 147, + PinmuxPadIndex_GpioPk2 = 148, + PinmuxPadIndex_GpioPk3 = 149, + PinmuxPadIndex_GpioPk4 = 150, + PinmuxPadIndex_GpioPk5 = 151, + PinmuxPadIndex_GpioPk6 = 152, + PinmuxPadIndex_GpioPk7 = 153, + PinmuxPadIndex_GpioPl0 = 154, + PinmuxPadIndex_GpioPl1 = 155, + PinmuxPadIndex_GpioPz0 = 156, + PinmuxPadIndex_GpioPz1 = 157, + PinmuxPadIndex_GpioPz2 = 158, + PinmuxPadIndex_GpioPz3 = 159, + PinmuxPadIndex_GpioPz4 = 160, + PinmuxPadIndex_GpioPz5 = 161, + PinmuxPadIndex_Sdmmc2Dat0 = 162, + PinmuxPadIndex_Sdmmc2Dat1 = 163, + PinmuxPadIndex_Sdmmc2Dat2 = 164, + PinmuxPadIndex_Sdmmc2Dat3 = 165, + PinmuxPadIndex_Sdmmc2Dat4 = 166, + PinmuxPadIndex_Sdmmc2Dat5 = 167, + PinmuxPadIndex_Sdmmc2Dat6 = 168, + PinmuxPadIndex_Sdmmc2Dat7 = 169, + PinmuxPadIndex_Sdmmc2Clk = 170, + PinmuxPadIndex_Sdmmc2Clkb = 171, + PinmuxPadIndex_Sdmmc2Cmd = 172, + PinmuxPadIndex_Sdmmc2Dqs = 173, + PinmuxPadIndex_Sdmmc2Dqsb = 174, +}; + +enum PinmuxDrivePadIndex { + PinmuxDrivePadIndex_AlsProxInt = 0, + PinmuxDrivePadIndex_ApReady = 1, + PinmuxDrivePadIndex_ApWakeBt = 2, + PinmuxDrivePadIndex_ApWakeNfc = 3, + PinmuxDrivePadIndex_AudMclk = 4, + PinmuxDrivePadIndex_BattBcl = 5, + PinmuxDrivePadIndex_BtRst = 6, + PinmuxDrivePadIndex_BtWakeAp = 7, + PinmuxDrivePadIndex_ButtonHome = 8, + PinmuxDrivePadIndex_ButtonPowerOn = 9, + PinmuxDrivePadIndex_ButtonSlideSw = 10, + PinmuxDrivePadIndex_ButtonVolDown = 11, + PinmuxDrivePadIndex_ButtonVolUp = 12, + PinmuxDrivePadIndex_Cam1Mclk = 13, + PinmuxDrivePadIndex_Cam1Pwdn = 14, + PinmuxDrivePadIndex_Cam1Strobe = 15, + PinmuxDrivePadIndex_Cam2Mclk = 16, + PinmuxDrivePadIndex_Cam2Pwdn = 17, + PinmuxDrivePadIndex_CamAfEn = 18, + PinmuxDrivePadIndex_CamFlashEn = 19, + PinmuxDrivePadIndex_CamI2cScl = 20, + PinmuxDrivePadIndex_CamI2cSda = 21, + PinmuxDrivePadIndex_CamRst = 22, + PinmuxDrivePadIndex_Clk32kIn = 23, + PinmuxDrivePadIndex_Clk32kOut = 24, + PinmuxDrivePadIndex_ClkReq = 25, + PinmuxDrivePadIndex_CorePwrReq = 26, + PinmuxDrivePadIndex_CpuPwrReq = 27, + PinmuxDrivePadIndex_Dap1Din = 28, + PinmuxDrivePadIndex_Dap1Dout = 29, + PinmuxDrivePadIndex_Dap1Fs = 30, + PinmuxDrivePadIndex_Dap1Sclk = 31, + PinmuxDrivePadIndex_Dap2Din = 32, + PinmuxDrivePadIndex_Dap2Dout = 33, + PinmuxDrivePadIndex_Dap2Fs = 34, + PinmuxDrivePadIndex_Dap2Sclk = 35, + PinmuxDrivePadIndex_Dap4Din = 36, + PinmuxDrivePadIndex_Dap4Dout = 37, + PinmuxDrivePadIndex_Dap4Fs = 38, + PinmuxDrivePadIndex_Dap4Sclk = 39, + PinmuxDrivePadIndex_Dmic1Clk = 40, + PinmuxDrivePadIndex_Dmic1Dat = 41, + PinmuxDrivePadIndex_Dmic2Clk = 42, + PinmuxDrivePadIndex_Dmic2Dat = 43, + PinmuxDrivePadIndex_Dmic3Clk = 44, + PinmuxDrivePadIndex_Dmic3Dat = 45, + PinmuxDrivePadIndex_DpHpd = 46, + PinmuxDrivePadIndex_DvfsClk = 47, + PinmuxDrivePadIndex_DvfsPwm = 48, + PinmuxDrivePadIndex_Gen1I2cScl = 49, + PinmuxDrivePadIndex_Gen1I2cSda = 50, + PinmuxDrivePadIndex_Gen2I2cScl = 51, + PinmuxDrivePadIndex_Gen2I2cSda = 52, + PinmuxDrivePadIndex_Gen3I2cScl = 53, + PinmuxDrivePadIndex_Gen3I2cSda = 54, + PinmuxDrivePadIndex_GpioPa6 = 55, + PinmuxDrivePadIndex_GpioPcc7 = 56, + PinmuxDrivePadIndex_GpioPe6 = 57, + PinmuxDrivePadIndex_GpioPe7 = 58, + PinmuxDrivePadIndex_GpioPh6 = 59, + PinmuxDrivePadIndex_GpioPk0 = 60, + PinmuxDrivePadIndex_GpioPk1 = 61, + PinmuxDrivePadIndex_GpioPk2 = 62, + PinmuxDrivePadIndex_GpioPk3 = 63, + PinmuxDrivePadIndex_GpioPk4 = 64, + PinmuxDrivePadIndex_GpioPk5 = 65, + PinmuxDrivePadIndex_GpioPk6 = 66, + PinmuxDrivePadIndex_GpioPk7 = 67, + PinmuxDrivePadIndex_GpioPl0 = 68, + PinmuxDrivePadIndex_GpioPl1 = 69, + PinmuxDrivePadIndex_GpioPz0 = 70, + PinmuxDrivePadIndex_GpioPz1 = 71, + PinmuxDrivePadIndex_GpioPz2 = 72, + PinmuxDrivePadIndex_GpioPz3 = 73, + PinmuxDrivePadIndex_GpioPz4 = 74, + PinmuxDrivePadIndex_GpioPz5 = 75, + PinmuxDrivePadIndex_GpioX1Aud = 76, + PinmuxDrivePadIndex_GpioX3Aud = 77, + PinmuxDrivePadIndex_GpsEn = 78, + PinmuxDrivePadIndex_GpsRst = 79, + PinmuxDrivePadIndex_HdmiCec = 80, + PinmuxDrivePadIndex_HdmiIntDpHpd = 81, + PinmuxDrivePadIndex_JtagRtck = 82, + PinmuxDrivePadIndex_LcdBlEn = 83, + PinmuxDrivePadIndex_LcdBlPwm = 84, + PinmuxDrivePadIndex_LcdGpio1 = 85, + PinmuxDrivePadIndex_LcdGpio2 = 86, + PinmuxDrivePadIndex_LcdRst = 87, + PinmuxDrivePadIndex_LcdTe = 88, + PinmuxDrivePadIndex_ModemWakeAp = 89, + PinmuxDrivePadIndex_MotionInt = 90, + PinmuxDrivePadIndex_NfcEn = 91, + PinmuxDrivePadIndex_NfcInt = 92, + PinmuxDrivePadIndex_PexL0ClkReqN = 93, + PinmuxDrivePadIndex_PexL0RstN = 94, + PinmuxDrivePadIndex_PexL1ClkreqN = 95, + PinmuxDrivePadIndex_PexL1RstN = 96, + PinmuxDrivePadIndex_PexWakeN = 97, + PinmuxDrivePadIndex_PwrI2cScl = 98, + PinmuxDrivePadIndex_PwrI2cSda = 99, + PinmuxDrivePadIndex_PwrIntN = 100, + PinmuxDrivePadIndex_QspiComp = 101, + PinmuxDrivePadIndex_QspiSck = 102, + PinmuxDrivePadIndex_SataLedActive = 103, + PinmuxDrivePadIndex_Sdmmc1Pad = 104, + PinmuxDrivePadIndex_Sdmmc3Pad = 105, + PinmuxDrivePadIndex_Shutdown = 106, + PinmuxDrivePadIndex_SpdifIn = 107, + PinmuxDrivePadIndex_SpdifOut = 108, + PinmuxDrivePadIndex_Spi1Cs0 = 109, + PinmuxDrivePadIndex_Spi1Cs1 = 110, + PinmuxDrivePadIndex_Spi1Miso = 111, + PinmuxDrivePadIndex_Spi1Mosi = 112, + PinmuxDrivePadIndex_Spi1Sck = 113, + PinmuxDrivePadIndex_Spi2Cs0 = 114, + PinmuxDrivePadIndex_Spi2Cs1 = 115, + PinmuxDrivePadIndex_Spi2Miso = 116, + PinmuxDrivePadIndex_Spi2Mosi = 117, + PinmuxDrivePadIndex_Spi2Sck = 118, + PinmuxDrivePadIndex_Spi4Cs0 = 119, + PinmuxDrivePadIndex_Spi4Miso = 120, + PinmuxDrivePadIndex_Spi4Mosi = 121, + PinmuxDrivePadIndex_Spi4Sck = 122, + PinmuxDrivePadIndex_TempAlert = 123, + PinmuxDrivePadIndex_TouchClk = 124, + PinmuxDrivePadIndex_TouchInt = 125, + PinmuxDrivePadIndex_TouchRst = 126, + PinmuxDrivePadIndex_Uart1Cts = 127, + PinmuxDrivePadIndex_Uart1Rts = 128, + PinmuxDrivePadIndex_Uart1Rx = 129, + PinmuxDrivePadIndex_Uart1Tx = 130, + PinmuxDrivePadIndex_Uart2Cts = 131, + PinmuxDrivePadIndex_Uart2Rts = 132, + PinmuxDrivePadIndex_Uart2Rx = 133, + PinmuxDrivePadIndex_Uart2Tx = 134, + PinmuxDrivePadIndex_Uart3Cts = 135, + PinmuxDrivePadIndex_Uart3Rts = 136, + PinmuxDrivePadIndex_Uart3Rx = 137, + PinmuxDrivePadIndex_Uart3Tx = 138, + PinmuxDrivePadIndex_Uart4Cts = 139, + PinmuxDrivePadIndex_Uart4Rts = 140, + PinmuxDrivePadIndex_Uart4Rx = 141, + PinmuxDrivePadIndex_Uart4Tx = 142, + PinmuxDrivePadIndex_UsbVbusEn0 = 143, + PinmuxDrivePadIndex_UsbVbusEn1 = 144, + PinmuxDrivePadIndex_WifiEn = 145, + PinmuxDrivePadIndex_WifiRst = 146, + PinmuxDrivePadIndex_WifiWakeAp = 147, +}; diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_platform_pads.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_platform_pads.cpp new file mode 100644 index 00000000..c7c9de2c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_platform_pads.cpp @@ -0,0 +1,640 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pinmux_pad_index.hpp" +#include "pinmux_board_driver_api.hpp" +#include "pinmux_platform_pads.hpp" + +namespace ams::pinmux::driver::board::nintendo::nx { + + namespace { + + uintptr_t g_apb_misc_virtual_address = dd::QueryIoMapping(0x70000000, 0x4000); + + enum PinmuxPadMask : u32 { + PinmuxPadMask_Pm = 0x3, + PinmuxPadMask_Pupd = 0xC, + PinmuxPadMask_Tristate = 0x10, + PinmuxPadMask_Park = 0x20, + PinmuxPadMask_EInput = 0x40, + PinmuxPadMask_Lock = 0x80, + PinmuxPadMask_ELpdr = 0x100, + PinmuxPadMask_EHsm = 0x200, + PinmuxPadMask_EIoHv = 0x400, + PinmuxPadMask_EOd = 0x800, + PinmuxPadMask_ESchmt = 0x1000, + PinmuxPadMask_DrvType = 0x6000, + PinmuxPadMask_Preemp = 0x8000, + PinmuxPadMask_IoReset = 0x10000, + }; + + enum PinmuxPadBitOffset : u32 { + PinmuxPadBitOffset_Pm = 0x0, + PinmuxPadBitOffset_Pupd = 0x2, + PinmuxPadBitOffset_Tristate = 0x4, + PinmuxPadBitOffset_Park = 0x5, + PinmuxPadBitOffset_EInput = 0x6, + PinmuxPadBitOffset_Lock = 0x7, + PinmuxPadBitOffset_ELpdr = 0x8, + PinmuxPadBitOffset_EHsm = 0x9, + PinmuxPadBitOffset_EIoHv = 0xA, + PinmuxPadBitOffset_EOd = 0xB, + PinmuxPadBitOffset_ESchmt = 0xC, + PinmuxPadBitOffset_DrvType = 0xD, + PinmuxPadBitOffset_Preemp = 0xF, + PinmuxPadBitOffset_IoReset = 0x10, + }; + + enum PinmuxOptBitMask : u32 { + PinmuxOptBitMask_Pm = 0x7, + PinmuxOptBitMask_Pupd = 0x18, + PinmuxOptBitMask_Dir = 0x60, + PinmuxOptBitMask_Lock = 0x80, + PinmuxOptBitMask_IoReset = 0x100, + PinmuxOptBitMask_IoHv = 0x200, + PinmuxOptBitMask_Park = 0x400, + PinmuxOptBitMask_Lpdr = 0x800, + PinmuxOptBitMask_Hsm = 0x1000, + PinmuxOptBitMask_Schmt = 0x2000, + PinmuxOptBitMask_DrvType = 0xC000, + PinmuxOptBitMask_Preemp = 0x10000, + }; + + enum PinmuxOptBitOffset { + PinmuxOptBitOffset_Pm = 0x0, + PinmuxOptBitOffset_Pupd = 0x3, + PinmuxOptBitOffset_Dir = 0x5, + PinmuxOptBitOffset_Lock = 0x7, + PinmuxOptBitOffset_IoReset = 0x8, + PinmuxOptBitOffset_IoHv = 0x9, + PinmuxOptBitOffset_Park = 0xA, + PinmuxOptBitOffset_Lpdr = 0xB, + PinmuxOptBitOffset_Hsm = 0xC, + PinmuxOptBitOffset_Schmt = 0xD, + PinmuxOptBitOffset_DrvType = 0xE, + PinmuxOptBitOffset_Preemp = 0x10, + }; + + enum PinmuxDrivePadMask : u32{ + PinmuxDrivePadMask_DrvDn = 0x0001F000, + PinmuxDrivePadMask_DrvUp = 0x01F00000, + PinmuxDrivePadMask_CzDrvDn = 0x0007F000, + PinmuxDrivePadMask_CzDrvUp = 0x07F00000, + PinmuxDrivePadMask_SlwR = 0x30000000, + PinmuxDrivePadMask_SlwF = 0xC0000000, + }; + + enum PinmuxDrivePadBitOffset : u32 { + PinmuxDrivePadBitOffset_DrvDn = 12, + PinmuxDrivePadBitOffset_DrvUp = 20, + PinmuxDrivePadBitOffset_CzDrvDn = 12, + PinmuxDrivePadBitOffset_CzDrvUp = 20, + PinmuxDrivePadBitOffset_SlwR = 28, + PinmuxDrivePadBitOffset_SlwF = 30, + }; + + enum PinmuxDriveOptBitMask : u32 { + PinmuxDriveOptBitMask_DrvDn = 0x0001F000, + PinmuxDriveOptBitMask_DrvUp = 0x01F00000, + PinmuxDriveOptBitMask_CzDrvDn = 0x0007F000, + PinmuxDriveOptBitMask_CzDrvUp = 0x07F00000, + PinmuxDriveOptBitMask_SlwR = 0x30000000, + PinmuxDriveOptBitMask_SlwF = 0xC0000000, + }; + + enum PinmuxDriveOptBitOffset : u32 { + PinmuxDriveOptBitOffset_DrvDn = 12, + PinmuxDriveOptBitOffset_DrvUp = 20, + PinmuxDriveOptBitOffset_CzDrvDn = 12, + PinmuxDriveOptBitOffset_CzDrvUp = 20, + PinmuxDriveOptBitOffset_SlwR = 28, + PinmuxDriveOptBitOffset_SlwF = 30, + }; + + enum PinmuxOpt : u32 { + /* Pm */ + PinmuxOpt_Gpio = 0x4, + PinmuxOpt_Unused = 0x5, + + /* Pupd */ + PinmuxOpt_NoPupd = 0x0, + PinmuxOpt_PullDown = 0x8, + PinmuxOpt_PullUp = 0x10, + + /* Dir */ + PinmuxOpt_Output = 0x0, + PinmuxOpt_Input = 0x20, + PinmuxOpt_Bidirection = 0x40, + PinmuxOpt_OpenDrain = 0x60, + + /* Lock */ + PinmuxOpt_Unlock = 0x0, + PinmuxOpt_Lock = 0x80, + + /* IoReset */ + PinmuxOpt_DisableIoReset = 0x0, + PinmuxOpt_EnableIoReset = 0x100, + + /* IoHv */ + PinmuxOpt_NormalVoltage = 0x0, + PinmuxOpt_HighVoltage = 0x200, + + /* Park */ + PinmuxOpt_ResetOnLowPower = 0x0, + PinmuxOpt_ParkOnLowPower = 0x400, + + /* Lpdr */ + PinmuxOpt_DisableBaseDriver = 0x0, + PinmuxOpt_EnableBaseDriver = 0x800, + + /* Hsm */ + PinmuxOpt_DisableHighSpeedMode = 0x0, + PinmuxOpt_EnableHighSpeedMode = 0x1000, + + /* Schmt */ + PinmuxOpt_CmosMode = 0x0, + PinmuxOpt_SchmittTrigger = 0x2000, + + /* DrvType */ + PinmuxOpt_DrvType1X = 0x0, + PinmuxOpt_DrvType2X = 0x4000, + PinmuxOpt_DrvType3X = 0x8000, + PinmuxOpt_DrvType4X = 0xC000, + + /* Preemp */ + PinmuxOpt_DisablePreemp = 0x0, + PinmuxOpt_EnablePreemp = 0x10000, + }; + + enum PinmuxPadPm : u32 { + PinmuxPadPm_Default = 0xFFFFFFFF, + PinmuxPadPm_Pm0 = 0x0, + PinmuxPadPm_Pm1 = 0x1, + PinmuxPadPm_Pm2 = 0x2, + PinmuxPadPm_Pm3 = 0x3, + PinmuxPadPm_Safe = 0x4, + }; + + struct PinmuxPadCharacter { + u32 reg_offset; + u32 reg_mask; + u8 safe_func; + const char *pad_name; + }; + + struct PinmuxDrivePadCharacter { + u32 reg_offset; + u32 reg_mask; + const char *pad_name; + }; + + #include "pinmux_pad_characters.inc" + #include "pinmux_drive_pad_characters.inc" + + class PinmuxPad { + private: + u32 m_reg_address; + u32 m_reg_mask; + u32 m_reg_value; + u8 m_safe_func; + const char *m_pad_name; + private: + bool IsValidRegisterAddress() const { + return m_reg_address - 0x70003000 <= 0x2C4; + } + + uintptr_t GetRegisterAddress() const { + return g_apb_misc_virtual_address + (m_reg_address - 0x70000000); + } + + bool UpdateBits(u32 value, u32 offset, u32 mask) { + if ((m_reg_mask & mask) != 0) { + if ((value & (mask >> offset)) != ((m_reg_value & mask) >> offset)) { + m_reg_value = (m_reg_value & ~mask) | ((value << offset) & mask); + } + return true; + } else { + return false; + } + } + + u32 ReadReg() const { + if (this->IsValidRegisterAddress()) { + return reg::Read(this->GetRegisterAddress()); + } else { + return 0; + } + } + + void WriteReg() const { + if (this->IsValidRegisterAddress()) { + reg::Write(this->GetRegisterAddress(), m_reg_value); + } + } + + bool IsLocked() const { + return (m_reg_value & PinmuxPadMask_Lock) != 0; + } + + void UpdatePm(u8 v) { + if (v != 0xFF) { + if (v == PinmuxPadPm_Safe) { + v = m_safe_func; + } + this->UpdateBits(v, PinmuxPadBitOffset_Pm, PinmuxPadMask_Pm); + } + } + + void UpdatePupd(u8 v) { + if (v != 0xFF) { + this->UpdateBits(v, PinmuxPadBitOffset_Pupd, PinmuxPadMask_Pupd); + } + } + + void UpdateTristate(u8 v) { + if (v != 0xFF) { + this->UpdateBits(v, PinmuxPadBitOffset_Tristate, PinmuxPadMask_Tristate); + } + } + + void UpdateEInput(u8 v) { + if (v != 0xFF) { + this->UpdateBits(v, PinmuxPadBitOffset_EInput, PinmuxPadMask_EInput); + } + } + + void UpdateEOd(u8 v) { + if (v != 0xFF) { + this->UpdateBits(v, PinmuxPadBitOffset_EOd, PinmuxPadMask_EOd); + } + } + + void UpdateLock(u8 v) { + if (v != 0xFF) { + this->UpdateBits(v, PinmuxPadBitOffset_Lock, PinmuxPadMask_Lock); + } + } + + void UpdateIoReset(u8 v) { + if (v != 0xFF) { + this->UpdateBits(v, PinmuxPadBitOffset_IoReset, PinmuxPadMask_IoReset); + } + } + + void UpdatePark(u8 v) { + if (v != 0xFF) { + this->UpdateBits(v, PinmuxPadBitOffset_Park, PinmuxPadMask_Park); + } + } + + void UpdateELpdr(u8 v) { + if (v != 0xFF) { + this->UpdateBits(v, PinmuxPadBitOffset_ELpdr, PinmuxPadMask_ELpdr); + } + } + + void UpdateEHsm(u8 v) { + if (v != 0xFF) { + this->UpdateBits(v, PinmuxPadBitOffset_EHsm, PinmuxPadMask_EHsm); + } + } + + void UpdateEIoHv(u8 v) { + if (v != 0xFF) { + this->UpdateBits(v, PinmuxPadBitOffset_EIoHv, PinmuxPadMask_EIoHv); + } + } + + void UpdateESchmt(u8 v) { + if (v != 0xFF) { + this->UpdateBits(v, PinmuxPadBitOffset_ESchmt, PinmuxPadMask_ESchmt); + } + } + + void UpdatePreemp(u8 v) { + if (v != 0xFF) { + this->UpdateBits(v, PinmuxPadBitOffset_Preemp, PinmuxPadMask_Preemp); + } + } + + void UpdateDrvType(u8 v) { + if (v != 0xFF) { + this->UpdateBits(v, PinmuxPadBitOffset_DrvType, PinmuxPadMask_DrvType); + } + } + public: + constexpr PinmuxPad() : m_reg_address(), m_reg_mask(), m_reg_value(), m_safe_func(), m_pad_name() { /* ... */ } + + void UpdatePinmuxPad(u32 config, u32 config_mask) { + /* Update register value. */ + m_reg_value = this->ReadReg(); + + /* Check if we're locked. */ + if (this->IsLocked()) { + return; + } + + /* Update PM. */ + if ((config_mask & PinmuxOptBitMask_Pm) != 0) { + const auto opt = (config & PinmuxOptBitMask_Pm); + u8 pm = PinmuxPadPm_Safe; + if (opt != PinmuxOpt_Gpio) { + if (opt == PinmuxOpt_Unused) { + this->UpdatePupd(true); + this->UpdateTristate(true); + this->UpdateEInput(0); + } else if (opt <= PinmuxOpt_Unused) { + pm = opt >> PinmuxOptBitOffset_Pm; + } + } + this->UpdatePm(pm); + } + + /* Update pupd. */ + if ((config_mask & PinmuxOptBitMask_Pupd) != 0) { + const auto opt = (config & PinmuxOptBitMask_Pupd); + if (opt == PinmuxOpt_NoPupd || opt == PinmuxOpt_PullDown || opt == PinmuxOpt_PullUp) { + this->UpdatePupd(opt >> PinmuxOptBitOffset_Pupd); + } + } + + /* Update direction. */ + if ((config_mask & PinmuxOptBitMask_Dir) != 0) { + const auto opt = (config & PinmuxOptBitMask_Dir); + if (opt == PinmuxOpt_Output) { + this->UpdateTristate(false); + this->UpdateEInput(false); + if ((m_reg_mask & PinmuxPadMask_EOd) != 0) { + this->UpdateEOd(false); + } + } else if (opt == PinmuxOpt_Input) { + this->UpdateTristate(true); + this->UpdateEInput(true); + if ((m_reg_mask & PinmuxPadMask_EOd) != 0) { + this->UpdateEOd(false); + } + } else if (opt == PinmuxOpt_Bidirection) { + this->UpdateTristate(false); + this->UpdateEInput(true); + if ((m_reg_mask & PinmuxPadMask_EOd) != 0) { + this->UpdateEOd(false); + } + } else if (opt == PinmuxOpt_OpenDrain) { + this->UpdateTristate(false); + this->UpdateEInput(true); + this->UpdateEOd(true); + } + } + + /* Update Lock. */ + if ((config_mask & PinmuxOptBitMask_Lock) != 0) { + const auto opt = (config & PinmuxOptBitMask_Lock); + this->UpdateLock(opt != 0); + } + + /* Update IoReset. */ + if ((config_mask & PinmuxOptBitMask_IoReset) != 0) { + const auto opt = (config & PinmuxOptBitMask_IoReset); + this->UpdateIoReset(opt != 0); + } + + /* Update Park. */ + if ((config_mask & PinmuxOptBitMask_Park) != 0) { + const auto opt = (config & PinmuxOptBitMask_Park); + this->UpdatePark(opt != 0); + } + + /* Update Lpdr. */ + if ((config_mask & PinmuxOptBitMask_Lpdr) != 0) { + const auto opt = (config & PinmuxOptBitMask_Lpdr); + this->UpdateELpdr(opt != 0); + } + + /* Update Hsm. */ + if ((config_mask & PinmuxOptBitMask_Hsm) != 0) { + const auto opt = (config & PinmuxOptBitMask_Hsm); + this->UpdateEHsm(opt != 0); + } + + /* Update IoHv. */ + if ((config_mask & PinmuxOptBitMask_IoHv) != 0) { + const auto opt = (config & PinmuxOptBitMask_IoHv); + this->UpdateEIoHv(opt != 0); + } + + /* Update Schmt. */ + if ((config_mask & PinmuxOptBitMask_Schmt) != 0) { + const auto opt = (config & PinmuxOptBitMask_Schmt); + this->UpdateESchmt(opt != 0); + } + + /* Update Preemp. */ + if ((config_mask & PinmuxOptBitMask_Preemp) != 0) { + const auto opt = (config & PinmuxOptBitMask_Preemp); + this->UpdatePreemp(opt != 0); + } + + /* Update drive type. */ + if ((config_mask & PinmuxOptBitMask_DrvType) != 0) { + const auto opt = (config & PinmuxOptBitMask_DrvType); + this->UpdateDrvType(opt >> PinmuxOptBitOffset_DrvType); + } + + /* Write the updated register value. */ + this->WriteReg(); + } + + void SetCharacter(const PinmuxPadCharacter &character) { + m_reg_address = character.reg_offset + 0x70000000; + m_reg_mask = character.reg_mask; + m_safe_func = character.safe_func; + m_reg_value = this->ReadReg(); + m_pad_name = character.pad_name; + + if ((m_reg_mask & m_reg_value & PinmuxPadMask_Park) != 0) { + m_reg_value &= ~(PinmuxPadMask_Park); + } + + this->WriteReg(); + } + }; + + class PinmuxDrivePad { + private: + u32 m_reg_address; + u32 m_reg_mask; + u32 m_reg_value; + const char *m_pad_name; + private: + bool IsValidRegisterAddress() const { + return m_reg_address - 0x700008E4 <= 0x288; + } + + uintptr_t GetRegisterAddress() const { + return g_apb_misc_virtual_address + (m_reg_address - 0x70000000); + } + + bool UpdateBits(u32 value, u32 offset, u32 mask) { + if ((m_reg_mask & mask) != 0) { + if ((value & (mask >> offset)) != ((m_reg_value & mask) >> offset)) { + m_reg_value = (m_reg_value & ~mask) | ((value << offset) & mask); + } + return true; + } else { + return false; + } + } + + u32 ReadReg() const { + if (this->IsValidRegisterAddress()) { + return reg::Read(this->GetRegisterAddress()); + } else { + return 0; + } + } + + void WriteReg() const { + if (this->IsValidRegisterAddress()) { + reg::Write(this->GetRegisterAddress(), m_reg_value); + } + } + + bool IsCzDrvDn() const { + return (m_reg_mask & PinmuxDrivePadMask_CzDrvDn) == PinmuxDrivePadMask_CzDrvDn; + } + + bool IsCzDrvUp() const { + return (m_reg_mask & PinmuxDrivePadMask_CzDrvUp) == PinmuxDrivePadMask_CzDrvUp; + } + + void UpdateDrvDn(u8 v) { + if (v != 0xFF) { + this->UpdateBits(v, PinmuxDrivePadBitOffset_DrvDn, PinmuxDrivePadMask_DrvDn); + } + } + + void UpdateDrvUp(u8 v) { + if (v != 0xFF) { + this->UpdateBits(v, PinmuxDrivePadBitOffset_DrvUp, PinmuxDrivePadMask_DrvUp); + } + } + + void UpdateCzDrvDn(u8 v) { + if (v != 0xFF) { + this->UpdateBits(v, PinmuxDrivePadBitOffset_CzDrvDn, PinmuxDrivePadMask_CzDrvDn); + } + } + + void UpdateCzDrvUp(u8 v) { + if (v != 0xFF) { + this->UpdateBits(v, PinmuxDrivePadBitOffset_CzDrvUp, PinmuxDrivePadMask_CzDrvUp); + } + } + + void UpdateSlwR(u8 v) { + if (v != 0xFF) { + this->UpdateBits(v, PinmuxDrivePadBitOffset_SlwR, PinmuxDrivePadMask_SlwR); + } + } + + void UpdateSlwF(u8 v) { + if (v != 0xFF) { + this->UpdateBits(v, PinmuxDrivePadBitOffset_SlwF, PinmuxDrivePadMask_SlwF); + } + } + public: + constexpr PinmuxDrivePad() : m_reg_address(), m_reg_mask(), m_reg_value(), m_pad_name() { /* ... */ } + + void UpdatePinmuxDrivePad(u32 config, u32 config_mask) { + /* Update register value. */ + m_reg_value = this->ReadReg(); + + /* Update drvdn. */ + if ((config_mask & PinmuxDriveOptBitMask_DrvDn) != 0) { + if (this->IsCzDrvDn()) { + const auto opt = (config & PinmuxDriveOptBitMask_CzDrvDn); + this->UpdateCzDrvDn(opt >> PinmuxDriveOptBitOffset_CzDrvDn); + } else { + const auto opt = (config & PinmuxDriveOptBitMask_DrvDn); + this->UpdateDrvDn(opt >> PinmuxDriveOptBitOffset_DrvDn); + } + } + + /* Update drvup. */ + if ((config_mask & PinmuxDriveOptBitMask_DrvUp) != 0) { + if (this->IsCzDrvUp()) { + const auto opt = (config & PinmuxDriveOptBitMask_CzDrvUp); + this->UpdateCzDrvUp(opt >> PinmuxDriveOptBitOffset_CzDrvUp); + } else { + const auto opt = (config & PinmuxDriveOptBitMask_DrvUp); + this->UpdateDrvUp(opt >> PinmuxDriveOptBitOffset_DrvUp); + } + } + + /* Update slwr */ + if ((config_mask & PinmuxDriveOptBitMask_SlwR) != 0) { + const auto opt = (config & PinmuxDriveOptBitMask_SlwR); + this->UpdateSlwR(opt >> PinmuxDriveOptBitOffset_SlwR); + } + + /* Update slwf */ + if ((config_mask & PinmuxDriveOptBitMask_SlwR) != 0) { + const auto opt = (config & PinmuxDriveOptBitMask_SlwF); + this->UpdateSlwF(opt >> PinmuxDriveOptBitOffset_SlwF); + } + + /* Write the updated register value. */ + this->WriteReg(); + } + + void SetCharacter(const PinmuxDrivePadCharacter &character) { + m_reg_address = character.reg_offset + 0x70000000; + m_reg_mask = character.reg_mask; + m_reg_value = this->ReadReg(); + m_pad_name = character.pad_name; + } + }; + + constinit std::array<PinmuxPad, NumPinmuxPadCharacters> g_pinmux_pads{}; + constinit std::array<PinmuxDrivePad, NumPinmuxDrivePadCharacters> g_pinmux_drive_pads{}; + + } + + void InitializePlatformPads() { + /* Initialize all pads. */ + for (size_t i = 0; i < NumPinmuxPadCharacters; ++i) { + g_pinmux_pads[i].SetCharacter(PinmuxPadCharacters[i]); + } + + /* Update all drive pads. */ + for (size_t i = 0; i < NumPinmuxDrivePadCharacters; ++i) { + g_pinmux_drive_pads[i].SetCharacter(PinmuxDrivePadCharacters[i]); + } + } + + void UpdateSinglePinmuxPad(const PinmuxPadConfig &config) { + if (IsInitialized()) { + g_pinmux_pads[config.index].UpdatePinmuxPad(config.option, config.option_mask); + } + } + + void UpdateSinglePinmuxDrivePad(const PinmuxDrivePadConfig &config) { + if (IsInitialized()) { + g_pinmux_drive_pads[config.index].UpdatePinmuxDrivePad(config.option, config.option_mask); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_platform_pads.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_platform_pads.hpp new file mode 100644 index 00000000..6ec386b7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/board/nintendo/nx/pinmux_platform_pads.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::pinmux::driver::board::nintendo::nx { + + struct PinmuxPadConfig { + u32 index; + u32 option; + u32 option_mask; + }; + + struct PinmuxDrivePadConfig { + u32 index; + u32 option; + u32 option_mask; + }; + + void InitializePlatformPads(); + + void UpdateSinglePinmuxPad(const PinmuxPadConfig &config); + void UpdateSinglePinmuxDrivePad(const PinmuxDrivePadConfig &config); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/pinmux_driver_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/pinmux_driver_api.cpp new file mode 100644 index 00000000..b1679c83 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/pinmux_driver_api.cpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pinmux_select_board_impl.hpp" + +namespace ams::pinmux::driver { + + namespace { + + constinit os::SdkMutex g_init_mutex; + constinit int g_init_count = 0; + + } + + void Initialize() { + std::scoped_lock lk(g_init_mutex); + + if ((g_init_count++) == 0) { + if (!board::IsInitialized()) { + board::Initialize(); + } + } + } + + void Finalize() { + std::scoped_lock lk(g_init_mutex); + + if ((--g_init_count) == 0) { + AMS_ASSERT(board::IsInitialized()); + board::Finalize(); + } + } + + void SetInitialConfig() { + board::SetInitialConfig(); + } + + void SetInitialDrivePadConfig() { + board::SetInitialDrivePadConfig(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/pinmux_select_board_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/pinmux_select_board_impl.hpp new file mode 100644 index 00000000..0d52cf0e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pinmux/driver/pinmux_select_board_impl.hpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + + #include "board/nintendo/nx/pinmux_board_driver_api.hpp" + namespace ams::pinmux::driver::board { + using namespace ams::pinmux::driver::board::nintendo::nx; + } + +#else + + // TODO: #error "Unknown board for pinmux driver" + + namespace ams::pinmux::driver::board { + + inline void Initialize() { + AMS_ABORT("TODO"); + } + + inline void Finalize() { + AMS_ABORT("TODO"); + } + + inline bool IsInitialized() { + AMS_ABORT("TODO"); + } + + inline void SetInitialConfig() { + AMS_ABORT("TODO"); + } + + inline void SetInitialDrivePadConfig() { + AMS_ABORT("TODO"); + } + + } + +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_ams.os.horizon.c b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_ams.os.horizon.c new file mode 100644 index 00000000..b7b3d249 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_ams.os.horizon.c @@ -0,0 +1,92 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <switch.h> +#include "pm_ams.os.horizon.h" + +Result pminfoAtmosphereGetProcessId(u64 *out_pid, u64 program_id) { + return serviceDispatchInOut(pminfoGetServiceSession(), 65000, program_id, *out_pid); +} + +Result pminfoAtmosphereHasLaunchedBootProgram(bool *out, u64 program_id) { + u8 tmp = 0; + Result rc = serviceDispatchInOut(pminfoGetServiceSession(), 65001, program_id, tmp); + if (R_SUCCEEDED(rc) && out) *out = tmp & 1; + return rc; +} + +Result pminfoAtmosphereGetProcessInfo(NcmProgramLocation *loc_out, CfgOverrideStatus *status_out, u64 pid) { + struct { + NcmProgramLocation loc; + CfgOverrideStatus status; + } out; + + Result rc = serviceDispatchInOut(pminfoGetServiceSession(), 65002, pid, out); + + if (R_SUCCEEDED(rc)) { + if (loc_out) *loc_out = out.loc; + if (status_out) *status_out = out.status; + } + + return rc; +} + +Result pmdmntAtmosphereGetProcessInfo(Handle* handle_out, NcmProgramLocation *loc_out, CfgOverrideStatus *status_out, u64 pid) { + Handle tmp_handle; + + struct { + NcmProgramLocation loc; + CfgOverrideStatus status; + } out; + + Result rc = serviceDispatchInOut(pmdmntGetServiceSession(), 65000, pid, out, + .out_handle_attrs = { SfOutHandleAttr_HipcCopy }, + .out_handles = &tmp_handle, + ); + + if (R_SUCCEEDED(rc)) { + if (handle_out) { + *handle_out = tmp_handle; + } else { + svcCloseHandle(tmp_handle); + } + + if (loc_out) *loc_out = out.loc; + if (status_out) *status_out = out.status; + } + + return rc; +} + +Result pmdmntAtmosphereGetCurrentLimitInfo(u64 *out_cur, u64 *out_lim, u32 group, u32 resource) { + const struct { + u32 group; + u32 resource; + } in = { group, resource }; + struct { + u64 cur; + u64 lim; + } out; + + Result rc = serviceDispatchInOut(pmdmntGetServiceSession(), 65001, in, out); + + if (R_SUCCEEDED(rc)) { + if (out_cur) *out_cur = out.cur; + if (out_lim) *out_lim = out.lim; + } + + return rc; +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_ams.os.horizon.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_ams.os.horizon.h new file mode 100644 index 00000000..099752ee --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_ams.os.horizon.h @@ -0,0 +1,33 @@ +/** + * @file pm_ams.h + * @brief Process Manager (pm:*) IPC wrapper for Atmosphere extensions. + * @author SciresM + * @copyright libnx Authors + */ +#pragma once + +#if defined(ATMOSPHERE_OS_HORIZON) + +#include <switch.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + u64 keys_held; + u64 flags; +} CfgOverrideStatus; + +Result pminfoAtmosphereGetProcessId(u64 *out_pid, u64 program_id); +Result pminfoAtmosphereHasLaunchedBootProgram(bool *out, u64 program_id); +Result pminfoAtmosphereGetProcessInfo(NcmProgramLocation *loc_out, CfgOverrideStatus *status_out, u64 pid); + +Result pmdmntAtmosphereGetProcessInfo(Handle *out, NcmProgramLocation *loc_out, CfgOverrideStatus *status_out, u64 pid); +Result pmdmntAtmosphereGetCurrentLimitInfo(u64 *out_cur, u64 *out_lim, u32 group, u32 resource); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_boot_mode_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_boot_mode_api.cpp new file mode 100644 index 00000000..20cb16e4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_boot_mode_api.cpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::pm::bm { + + /* Boot Mode API. */ + /* Both functions should be weakly linked, so that they can be overridden by ams::boot2 as needed. */ + #if defined(ATMOSPHERE_OS_HORIZON) + BootMode WEAK_SYMBOL GetBootMode() { + PmBootMode boot_mode = PmBootMode_Normal; + R_ABORT_UNLESS(pmbmGetBootMode(std::addressof(boot_mode))); + return static_cast<BootMode>(boot_mode); + } + + void WEAK_SYMBOL SetMaintenanceBoot() { + R_ABORT_UNLESS(pmbmSetMaintenanceBoot()); + } + #else + BootMode WEAK_SYMBOL GetBootMode() { + AMS_ABORT("TODO"); + } + + void WEAK_SYMBOL SetMaintenanceBoot() { + AMS_ABORT("TODO"); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_dmnt_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_dmnt_api.cpp new file mode 100644 index 00000000..559e2147 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_dmnt_api.cpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#include "pm_ams.os.horizon.h" + +namespace ams::pm::dmnt { + + /* Debug Monitor API. */ + #if defined(ATMOSPHERE_OS_HORIZON) + Result StartProcess(os::ProcessId process_id) { + R_RETURN(pmdmntStartProcess(static_cast<u64>(process_id))); + } + + Result GetProgramId(ncm::ProgramId *out_program_id, os::ProcessId process_id) { + R_RETURN(pmdmntGetProgramId(reinterpret_cast<u64 *>(out_program_id), static_cast<u64>(process_id))); + } + + Result GetProcessId(os::ProcessId *out_process_id, const ncm::ProgramId program_id) { + R_RETURN(pmdmntGetProcessId(reinterpret_cast<u64 *>(out_process_id), static_cast<u64>(program_id))); + } + + Result GetApplicationProcessId(os::ProcessId *out_process_id) { + R_RETURN(pmdmntGetApplicationProcessId(reinterpret_cast<u64 *>(out_process_id))); + } + + Result HookToCreateApplicationProcess(os::NativeHandle *out_handle) { + Event evt; + R_TRY(pmdmntHookToCreateApplicationProcess(std::addressof(evt))); + *out_handle = evt.revent; + R_SUCCEED(); + } + + Result HookToCreateProcess(os::NativeHandle *out_handle, const ncm::ProgramId program_id) { + Event evt; + R_TRY(pmdmntHookToCreateProcess(std::addressof(evt), static_cast<u64>(program_id))); + *out_handle = evt.revent; + R_SUCCEED(); + } + + Result AtmosphereGetProcessInfo(os::NativeHandle *out_handle, ncm::ProgramLocation *out_loc, cfg::OverrideStatus *out_status, os::ProcessId process_id) { + *out_handle = os::InvalidNativeHandle; + *out_loc = {}; + *out_status = {}; + static_assert(sizeof(*out_status) == sizeof(CfgOverrideStatus)); + R_RETURN(pmdmntAtmosphereGetProcessInfo(out_handle, reinterpret_cast<NcmProgramLocation *>(out_loc), reinterpret_cast<CfgOverrideStatus *>(out_status), static_cast<u64>(process_id))); + } + + Result AtmosphereGetCurrentLimitInfo(u64 *out_current_value, u64 *out_limit_value, ResourceLimitGroup group, svc::LimitableResource resource) { + *out_current_value = 0; + *out_limit_value = 0; + R_RETURN(pmdmntAtmosphereGetCurrentLimitInfo(out_current_value, out_limit_value, group, resource)); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_info_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_info_api.cpp new file mode 100644 index 00000000..9fdf4a86 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_info_api.cpp @@ -0,0 +1,72 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pm_ams.os.horizon.h" + +namespace ams::pm::info { + + /* Information API. */ + #if defined(ATMOSPHERE_OS_HORIZON) + Result GetProgramId(ncm::ProgramId *out_program_id, os::ProcessId process_id) { + R_RETURN(pminfoGetProgramId(reinterpret_cast<u64 *>(out_program_id), static_cast<u64>(process_id))); + } + + Result GetProcessId(os::ProcessId *out_process_id, ncm::ProgramId program_id) { + R_RETURN(pminfoAtmosphereGetProcessId(reinterpret_cast<u64 *>(out_process_id), static_cast<u64>(program_id))); + } + + Result GetAppletResourceLimitCurrentValue(pm::ResourceLimitValue *out) { + static_assert(sizeof(pm::ResourceLimitValue) == sizeof(::PmResourceLimitValues)); + R_RETURN(pminfoGetAppletCurrentResourceLimitValues(reinterpret_cast<PmResourceLimitValues *>(out))); + } + + Result GetAppletResourceLimitPeakValue(pm::ResourceLimitValue *out) { + static_assert(sizeof(pm::ResourceLimitValue) == sizeof(::PmResourceLimitValues)); + R_RETURN(pminfoGetAppletPeakResourceLimitValues(reinterpret_cast<PmResourceLimitValues *>(out))); + } + + Result GetProcessInfo(ncm::ProgramLocation *out_loc, cfg::OverrideStatus *out_status, os::ProcessId process_id) { + *out_loc = {}; + *out_status = {}; + static_assert(sizeof(*out_status) == sizeof(CfgOverrideStatus)); + R_RETURN(pminfoAtmosphereGetProcessInfo(reinterpret_cast<NcmProgramLocation *>(out_loc), reinterpret_cast<CfgOverrideStatus *>(out_status), static_cast<u64>(process_id))); + } + + bool HasLaunchedBootProgram(ncm::ProgramId program_id) { + bool has_launched = false; + R_ABORT_UNLESS(HasLaunchedBootProgram(std::addressof(has_launched), program_id)); + return has_launched; + } + + + Result IsHblProcessId(bool *out, os::ProcessId process_id) { + ncm::ProgramLocation loc; + cfg::OverrideStatus override_status; + R_TRY(GetProcessInfo(std::addressof(loc), std::addressof(override_status), process_id)); + + *out = override_status.IsHbl(); + R_SUCCEED(); + } + + Result IsHblProgramId(bool *out, ncm::ProgramId program_id) { + os::ProcessId process_id; + R_TRY(GetProcessId(std::addressof(process_id), program_id)); + + R_RETURN(IsHblProcessId(out, process_id)); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_info_api_weak.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_info_api_weak.cpp new file mode 100644 index 00000000..f641629d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_info_api_weak.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pm_ams.os.horizon.h" + +namespace ams::pm::info { + + /* Information API. */ + #if defined(ATMOSPHERE_OS_HORIZON) + Result WEAK_SYMBOL HasLaunchedBootProgram(bool *out, ncm::ProgramId program_id) { + bool has_launched = false; + R_TRY(pminfoAtmosphereHasLaunchedBootProgram(std::addressof(has_launched), static_cast<u64>(program_id))); + *out = has_launched; + R_SUCCEED(); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_shell_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_shell_api.cpp new file mode 100644 index 00000000..247d88cb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pm/pm_shell_api.cpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pm_ams.os.horizon.h" + +namespace ams::pm::shell { + + /* Shell API. */ + #if defined(ATMOSPHERE_OS_HORIZON) + Result WEAK_SYMBOL LaunchProgram(os::ProcessId *out, const ncm::ProgramLocation &loc, u32 launch_flags) { + static_assert(sizeof(ncm::ProgramLocation) == sizeof(NcmProgramLocation)); + static_assert(alignof(ncm::ProgramLocation) == alignof(NcmProgramLocation)); + R_RETURN(pmshellLaunchProgram(launch_flags, reinterpret_cast<const NcmProgramLocation *>(std::addressof(loc)), reinterpret_cast<u64 *>(out))); + } + + Result TerminateProcess(os::ProcessId process_id) { + R_RETURN(::pmshellTerminateProcess(static_cast<u64>(process_id))); + } + + Result GetProcessEventEvent(os::SystemEvent *out) { + ::Event evt; + R_TRY(::pmshellGetProcessEventHandle(std::addressof(evt))); + out->Attach(evt.revent, true, svc::InvalidHandle, false, os::EventClearMode_ManualClear); + R_SUCCEED(); + } + + Result GetProcessEventInfo(ProcessEventInfo *out) { + static_assert(sizeof(*out) == sizeof(::PmProcessEventInfo)); + R_RETURN(::pmshellGetProcessEventInfo(reinterpret_cast<::PmProcessEventInfo *>(out))); + } + + Result GetApplicationProcessIdForShell(os::ProcessId *out) { + static_assert(sizeof(*out) == sizeof(u64)); + R_RETURN(::pmshellGetApplicationProcessIdForShell(reinterpret_cast<u64 *>(out))); + } + + Result BoostSystemMemoryResourceLimit(u64 size) { + R_RETURN(::pmshellBoostSystemMemoryResourceLimit(size)); + } + + Result BoostApplicationThreadResourceLimit() { + R_RETURN(::pmshellBoostApplicationThreadResourceLimit()); + } + + Result BoostSystemThreadResourceLimit() { + R_RETURN(::pmshellBoostSystemThreadResourceLimit()); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/driver/impl/powctl_charger_parameters.board.nintendo_nx.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/driver/impl/powctl_charger_parameters.board.nintendo_nx.cpp new file mode 100644 index 00000000..fd0ef3ba --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/driver/impl/powctl_charger_parameters.board.nintendo_nx.cpp @@ -0,0 +1,77 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::powctl::driver::impl { + + namespace { + + constexpr inline const PowerState AcceptablePowerStates[] = { + PowerState::FullAwake, + PowerState::MinimumAwake, + PowerState::SleepCharge, + PowerState::SleepDischarge, + PowerState::ShutdownChargeMain, + }; + + constexpr inline const PowerState AcceptablePowerStatesForNotAwakeCharge[] = { + PowerState::SleepCharge, + PowerState::ShutdownChargeMain, + }; + + constexpr inline const int Max = std::numeric_limits<int>::max(); + constexpr inline const int Min = std::numeric_limits<int>::min(); + + constexpr inline const float FloatMax = std::numeric_limits<float>::max(); + constexpr inline const float FloatMin = -FloatMax; + + constexpr inline const UnknownParameterX UnknownXTableForBatteryVersion2[] = { + { 20000, 4320, 95.0, 100.4 }, + { 30000, 4304, 94.0, 99.7 }, + { 40000, 4288, 93.0, 98.4 }, + { 50000, 4272, 92.0, 97.0 }, + { 60000, 4256, 90.0, 95.7 }, + { 80000, 4240, 89.0, 94.2 }, + { 100000, 4224, 88.0, 93.0 }, + { Max, 4192, 85.0, 90.0 }, + }; + + /* Include automatically extracted charger parameters. */ + #include "powctl_charger_parameters.board.nintendo_nx.inc" + + } + + const ChargeParameters &GetChargeParameters() { + /* Get the battery version. */ + u8 battery_version; + if (R_FAILED(cal::GetBatteryVersion(std::addressof(battery_version)))) { + battery_version = 0; + } + + if (battery_version == 2) { + return ChargeParametersForBatteryVersion2; + } else if (battery_version == 1) { + return ChargeParametersForBatteryVersion1; + } else { + if (spl::GetHardwareType() == spl::HardwareType::Aula) { + return ChargeParametersForBatteryVersion0ForAula; + } else { + return ChargeParametersForBatteryVersion0; + } + } + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/driver/impl/powctl_charger_parameters.board.nintendo_nx.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/driver/impl/powctl_charger_parameters.board.nintendo_nx.inc new file mode 100644 index 00000000..5b5a0604 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/driver/impl/powctl_charger_parameters.board.nintendo_nx.inc @@ -0,0 +1,80 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by charger_parameters.py, do not edit manually. */ + +constexpr inline const ChargeParametersRule ChargeParametersRulesForBatteryVersion0[] = { + { BatteryTemperatureLevel::TooLow, Min, 3320, Min, Max, FloatMin, 3.0, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, false, 4208, 512, 0, 0 }, + { BatteryTemperatureLevel::TooLow, 3320, Max, Min, Max, 3.0, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, false, 4208, 768, 0, 0 }, + { BatteryTemperatureLevel::Low, Min, 3320, Min, Max, FloatMin, 3.0, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, true, 4208, 512, 0, 0 }, + { BatteryTemperatureLevel::Low, 3320, Max, Min, Max, 3.0, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, true, 4208, 768, 0, 0 }, + { BatteryTemperatureLevel::Medium, Min, 3320, Min, Max, FloatMin, 3.0, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, true, 4208, 512, 0, 0 }, + { BatteryTemperatureLevel::Medium, 3320, Max, Min, Max, 3.0, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, true, 4208, 2048, 0, 0 }, + { BatteryTemperatureLevel::High, Min, 3320, Min, Max, FloatMin, 3.0, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, true, 3952, 512, 0, 0 }, + { BatteryTemperatureLevel::High, 3320, 4050, Min, Max, 3.0, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, true, 3952, 2048, 0, 0 }, + { BatteryTemperatureLevel::High, 4050, Max, Min, Max, 3.0, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, false, 4208, 2048, 0, 0 }, + { BatteryTemperatureLevel::TooHigh, Min, 3320, Min, Max, FloatMin, 3.0, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, false, 3952, 512, 0, 0 }, + { BatteryTemperatureLevel::TooHigh, 3320, 4050, Min, Max, 3.0, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, false, 3952, 2048, 0, 0 }, + { BatteryTemperatureLevel::TooHigh, 4050, Max, Min, Max, 3.0, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, false, 4208, 2048, 0, 0 }, +}; + +constexpr inline const ChargeParametersRule ChargeParametersRulesForBatteryVersion1[] = { + { BatteryTemperatureLevel::TooLow, Min, Max, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, false, 4208, 576, 0, 0 }, + { BatteryTemperatureLevel::Low, Min, Max, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, true, 4208, 576, 0, 0 }, + { BatteryTemperatureLevel::Medium, Min, Max, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, true, 4208, 1536, 0, 0 }, + { BatteryTemperatureLevel::High, Min, 3984, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, true, 3984, 1536, 0, 0 }, + { BatteryTemperatureLevel::High, 3984, Max, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, false, 4208, 1536, 0, 0 }, + { BatteryTemperatureLevel::TooHigh, Min, 3984, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, false, 3984, 1536, 0, 0 }, + { BatteryTemperatureLevel::TooHigh, 3984, Max, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, false, 4208, 1536, 0, 0 }, +}; + +constexpr inline const ChargeParametersRule ChargeParametersRulesForBatteryVersion2[] = { + { BatteryTemperatureLevel::TooLow, Min, Max, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, false, 4320, 640, 0, 0 }, + { BatteryTemperatureLevel::Low, Min, Max, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, true, 4320, 640, 0, 0 }, + { BatteryTemperatureLevel::Medium, Min, Max, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, true, 4320, 1664, 0, 0 }, + { BatteryTemperatureLevel::High, Min, 4080, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, true, 4080, 1664, 0, 0 }, + { BatteryTemperatureLevel::High, 4080, Max, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, false, 4320, 1664, 0, 0 }, + { BatteryTemperatureLevel::TooHigh, Min, 4080, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, false, 4080, 1664, 0, 0 }, + { BatteryTemperatureLevel::TooHigh, 4080, Max, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, false, 4320, 1664, 0, 0 }, +}; + +constexpr inline const ChargeParametersRule ChargeParametersRulesForBatteryVersion0ForAula[] = { + { BatteryTemperatureLevel::TooLow, Min, Max, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, false, 4208, 768, 0, 0 }, + { BatteryTemperatureLevel::Low, Min, Max, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, true, 4208, 768, 0, 0 }, + { BatteryTemperatureLevel::Medium, Min, Max, 2049, Max, FloatMin, FloatMax, FloatMin, 23.0, AcceptablePowerStatesForNotAwakeCharge, util::size(AcceptablePowerStatesForNotAwakeCharge), true, true, 4000, 3072, 40, 112 }, + { BatteryTemperatureLevel::Medium, Min, Max, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, true, 4208, 2048, 0, 0 }, + { BatteryTemperatureLevel::High, Min, 4050, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, true, 3952, 2048, 0, 0 }, + { BatteryTemperatureLevel::High, 4050, Max, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, false, 4208, 2048, 0, 0 }, + { BatteryTemperatureLevel::TooHigh, Min, 4050, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, false, 3952, 2048, 0, 0 }, + { BatteryTemperatureLevel::TooHigh, 4050, Max, Min, Max, FloatMin, FloatMax, FloatMin, FloatMax, AcceptablePowerStates, util::size(AcceptablePowerStates), false, false, 4208, 2048, 0, 0 }, +}; + +constexpr inline const ChargeParameters ChargeParametersForBatteryVersion0 = { + 4, 17, 51, 60, 512, 4208, nullptr, 0, 95.0, 99.0, ChargeParametersRulesForBatteryVersion0, util::size(ChargeParametersRulesForBatteryVersion0) +}; + +constexpr inline const ChargeParameters ChargeParametersForBatteryVersion1 = { + 1, 19, 48, 59, 1536, 4208, nullptr, 0, 95.0, 99.0, ChargeParametersRulesForBatteryVersion1, util::size(ChargeParametersRulesForBatteryVersion1) +}; + +constexpr inline const ChargeParameters ChargeParametersForBatteryVersion2 = { + 1, 19, 48, 59, 1664, 4320, UnknownXTableForBatteryVersion2, util::size(UnknownXTableForBatteryVersion2), 95.0, 100.4, ChargeParametersRulesForBatteryVersion2, util::size(ChargeParametersRulesForBatteryVersion2) +}; + +constexpr inline const ChargeParameters ChargeParametersForBatteryVersion0ForAula = { + 4, 17, 51, 60, 512, 4208, nullptr, 0, 95.0, 99.0, ChargeParametersRulesForBatteryVersion0ForAula, util::size(ChargeParametersRulesForBatteryVersion0ForAula) +}; + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_battery_driver.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_battery_driver.cpp new file mode 100644 index 00000000..2b433008 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_battery_driver.cpp @@ -0,0 +1,495 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../../../powctl_device_management.hpp" +#include "powctl_retry_helper.hpp" +#include "powctl_battery_driver.hpp" +#include "powctl_max17050_driver.hpp" + +namespace ams::powctl::impl::board::nintendo::nx { + + namespace { + + constinit util::optional<BatteryDevice> g_battery_device; + + constinit Max17050Driver g_max17050_driver; + + ALWAYS_INLINE Max17050Driver &GetMax17050Driver() { + return g_max17050_driver; + } + + constexpr inline const double SenseResistorValue = 0.005; + + } + + BatteryDevice::BatteryDevice(bool ev) : m_use_event_handler(ev), m_event_handler() { + if (m_use_event_handler) { + /* Create the system event. */ + os::CreateSystemEvent(std::addressof(m_system_event), os::EventClearMode_ManualClear, true); + + /* Create the handler. */ + m_event_handler.emplace(this); + + /* Register the event handler. */ + powctl::impl::RegisterInterruptHandler(std::addressof(*m_event_handler)); + } + } + + /* Generic API. */ + void BatteryDriver::InitializeDriver() { + /* Initialize gpio library. */ + gpio::Initialize(); + + /* Create battery device. */ + g_battery_device.emplace(this->IsEventHandlerEnabled()); + + /* Initialize the Max17050Driver. */ + { + size_t battery_vendor_size; + char battery_vendor[0x18] = {}; + if (R_FAILED(cal::GetBatteryVendor(std::addressof(battery_vendor_size), battery_vendor, sizeof(battery_vendor)))) { + battery_vendor[7] = 'A'; + battery_vendor_size = 0; + } + + u8 battery_version = 0; + if (R_FAILED(cal::GetBatteryVersion(std::addressof(battery_version)))) { + battery_version = 0; + } + + GetMax17050Driver().Initialize(battery_vendor, battery_version); + } + + /* Register our device. */ + this->RegisterDevice(std::addressof(*g_battery_device)); + + /* Register the charger device's code. */ + R_ABORT_UNLESS(powctl::impl::RegisterDeviceCode(powctl::DeviceCode_Max17050, std::addressof(*g_battery_device))); + + } + + void BatteryDriver::FinalizeDriver() { + /* Unregister the charger device code. */ + powctl::impl::UnregisterDeviceCode(powctl::DeviceCode_Max17050); + + /* Unregister our device. */ + this->UnregisterDevice(std::addressof(*g_battery_device)); + + /* Finalize Max17050Driver. */ + GetMax17050Driver().Finalize(); + + /* Destroy the charger device. */ + g_battery_device = util::nullopt; + + /* Finalize gpio library. */ + gpio::Finalize(); + } + + Result BatteryDriver::GetDeviceSystemEvent(os::SystemEventType **out, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Check that we support event handlers. */ + R_UNLESS(this->IsEventHandlerEnabled(), powctl::ResultNotAvailable()); + + *out = device->SafeCastTo<BatteryDevice>().GetSystemEvent(); + R_SUCCEED(); + } + + Result BatteryDriver::SetDeviceInterruptEnabled(IDevice *device, bool enable) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Set the interrupt enable. */ + device->SafeCastTo<BatteryDevice>().SetInterruptEnabled(enable); + + R_SUCCEED(); + } + + Result BatteryDriver::GetDeviceErrorStatus(u32 *out, IDevice *device) { + /* TODO */ + AMS_UNUSED(out, device); + AMS_ABORT(); + } + + Result BatteryDriver::SetDeviceErrorStatus(IDevice *device, u32 status) { + /* TODO */ + AMS_UNUSED(device, status); + AMS_ABORT(); + } + + Result BatteryDriver::GetBatteryChargePercentage(float *out_percent, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out_percent != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Get the value. */ + double percent{}; + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().GetChargePercentage(std::addressof(percent))); + + /* Set output. */ + *out_percent = percent; + R_SUCCEED(); + } + + Result BatteryDriver::GetBatteryVoltageFuelGaugePercentage(float *out_percent, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out_percent != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Get the value. */ + double percent{}; + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().GetVoltageFuelGaugePercentage(std::addressof(percent))); + + /* Set output. */ + *out_percent = percent; + R_SUCCEED(); + } + + Result BatteryDriver::GetBatteryFullCapacity(int *out_mah, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out_mah != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Get the value. */ + double mah{}; + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().GetFullCapacity(std::addressof(mah), SenseResistorValue)); + + /* Set output. */ + *out_mah = mah; + R_SUCCEED(); + } + + Result BatteryDriver::GetBatteryRemainingCapacity(int *out_mah, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out_mah != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Get the value. */ + double mah{}; + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().GetRemainingCapacity(std::addressof(mah), SenseResistorValue)); + + /* Set output. */ + *out_mah = mah; + R_SUCCEED(); + } + + Result BatteryDriver::SetBatteryChargePercentageMinimumAlertThreshold(IDevice *device, float percentage) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().SetChargePercentageMinimumAlertThreshold(percentage)); + + R_SUCCEED(); + } + + Result BatteryDriver::SetBatteryChargePercentageMaximumAlertThreshold(IDevice *device, float percentage) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().SetChargePercentageMaximumAlertThreshold(percentage)); + + R_SUCCEED(); + } + + Result BatteryDriver::SetBatteryVoltageFuelGaugePercentageMinimumAlertThreshold(IDevice *device, float percentage) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().SetVoltageFuelGaugePercentageMinimumAlertThreshold(percentage)); + + R_SUCCEED(); + } + + Result BatteryDriver::SetBatteryVoltageFuelGaugePercentageMaximumAlertThreshold(IDevice *device, float percentage) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().SetVoltageFuelGaugePercentageMaximumAlertThreshold(percentage)); + + R_SUCCEED(); + } + + Result BatteryDriver::SetBatteryFullChargeThreshold(IDevice *device, float percentage) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().SetFullChargeThreshold(percentage)); + + R_SUCCEED(); + } + + Result BatteryDriver::GetBatteryAverageCurrent(int *out_ma, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out_ma != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Get the value. */ + double ma{}; + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().GetAverageCurrent(std::addressof(ma), SenseResistorValue)); + + /* Set output. */ + *out_ma = ma; + R_SUCCEED(); + } + + Result BatteryDriver::GetBatteryCurrent(int *out_ma, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out_ma != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Get the value. */ + double ma{}; + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().GetCurrent(std::addressof(ma), SenseResistorValue)); + + /* Set output. */ + *out_ma = ma; + R_SUCCEED(); + } + + Result BatteryDriver::GetBatteryInternalState(void *dst, size_t *out_size, IDevice *device, size_t dst_size) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(dst != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(out_size != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(dst_size == sizeof(max17050::InternalState), powctl::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(reinterpret_cast<uintptr_t>(dst), alignof(max17050::InternalState)), powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().ReadInternalState()); + GetMax17050Driver().GetInternalState(static_cast<max17050::InternalState *>(dst)); + + R_SUCCEED(); + } + + Result BatteryDriver::SetBatteryInternalState(IDevice *device, const void *src, size_t src_size) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(src != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(src_size == sizeof(max17050::InternalState), powctl::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(reinterpret_cast<uintptr_t>(src), alignof(max17050::InternalState)), powctl::ResultInvalidArgument()); + + GetMax17050Driver().SetInternalState(*static_cast<const max17050::InternalState *>(src)); + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().WriteInternalState()); + + R_SUCCEED(); + } + + Result BatteryDriver::GetBatteryNeedToRestoreParameters(bool *out, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Get the value. */ + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().GetNeedToRestoreParameters(out)); + + R_SUCCEED(); + } + + Result BatteryDriver::SetBatteryNeedToRestoreParameters(IDevice *device, bool en) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Set the value. */ + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().SetNeedToRestoreParameters(en)); + + R_SUCCEED(); + } + + Result BatteryDriver::IsBatteryI2cShutdownEnabled(bool *out, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Get the value. */ + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().IsI2cShutdownEnabled(out)); + + R_SUCCEED(); + } + + Result BatteryDriver::SetBatteryI2cShutdownEnabled(IDevice *device, bool en) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Set the value. */ + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().SetI2cShutdownEnabled(en)); + + R_SUCCEED(); + } + + Result BatteryDriver::IsBatteryPresent(bool *out, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Get the battery status. */ + u16 status; + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().GetStatus(std::addressof(status))); + + /* Set output. */ + *out = (status & 0x0008) == 0; + R_SUCCEED(); + } + + Result BatteryDriver::GetBatteryCycles(int *out, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Get the battery cycles. */ + u16 cycles{}; + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().GetCycles(std::addressof(cycles))); + + /* Set output. */ + *out = cycles; + R_SUCCEED(); + } + + Result BatteryDriver::SetBatteryCycles(IDevice *device, int cycles) { + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(cycles == 0, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().ResetCycles()); + + R_SUCCEED(); + } + + Result BatteryDriver::GetBatteryAge(float *out_percent, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out_percent != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Get the value. */ + double percent{}; + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().GetAge(std::addressof(percent))); + + /* Set output. */ + *out_percent = percent; + R_SUCCEED(); + } + + Result BatteryDriver::GetBatteryTemperature(float *out_c, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out_c != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Get the value. */ + double temp{}; + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().GetTemperature(std::addressof(temp))); + + /* Set output. */ + *out_c = temp; + R_SUCCEED(); + } + + Result BatteryDriver::GetBatteryMaximumTemperature(float *out_c, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out_c != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Get the value. */ + u8 max_temp{}; + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().GetMaximumTemperature(std::addressof(max_temp))); + + /* Set output. */ + *out_c = static_cast<float>(max_temp); + R_SUCCEED(); + } + + Result BatteryDriver::SetBatteryTemperatureMinimumAlertThreshold(IDevice *device, float c) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().SetTemperatureMinimumAlertThreshold(c)); + + R_SUCCEED(); + } + + Result BatteryDriver::SetBatteryTemperatureMaximumAlertThreshold(IDevice *device, float c) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().SetTemperatureMaximumAlertThreshold(c)); + + R_SUCCEED(); + } + + Result BatteryDriver::GetBatteryVCell(int *out_mv, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out_mv != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Get the value. */ + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().GetVCell(out_mv)); + + R_SUCCEED(); + } + + Result BatteryDriver::GetBatteryAverageVCell(int *out_mv, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out_mv != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Get the value. */ + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().GetAverageVCell(out_mv)); + + R_SUCCEED(); + } + + Result BatteryDriver::GetBatteryAverageVCellTime(TimeSpan *out, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Get the value. */ + double ms{}; + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().GetAverageVCellTime(std::addressof(ms))); + + /* Set output. */ + *out = TimeSpan::FromMicroSeconds(static_cast<s64>(ms * 1000.0)); + R_SUCCEED(); + } + + Result BatteryDriver::SetBatteryVoltageMinimumAlertThreshold(IDevice *device, int mv) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().SetVoltageMinimumAlertThreshold(mv)); + + R_SUCCEED(); + } + + Result BatteryDriver::GetBatteryOpenCircuitVoltage(int *out_mv, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out_mv != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Get the value. */ + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().GetOpenCircuitVoltage(out_mv)); + + R_SUCCEED(); + } + + Result BatteryDriver::SetBatteryVoltageMaximumAlertThreshold(IDevice *device, int mv) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetMax17050Driver().SetVoltageMaximumAlertThreshold(mv)); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_battery_driver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_battery_driver.hpp new file mode 100644 index 00000000..bebd58f5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_battery_driver.hpp @@ -0,0 +1,148 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../../../powctl_i_power_control_driver.hpp" +#include "powctl_interrupt_event_handler.hpp" + +namespace ams::powctl::impl::board::nintendo::nx { + + class BatteryDevice : public powctl::impl::IDevice { + NON_COPYABLE(BatteryDevice); + NON_MOVEABLE(BatteryDevice); + AMS_DDSF_CASTABLE_TRAITS(ams::powctl::impl::board::nintendo::nx::BatteryDevice, ::ams::powctl::impl::IDevice); + private: + bool m_use_event_handler; + util::optional<BatteryInterruptEventHandler> m_event_handler; + os::SystemEventType m_system_event; + public: + BatteryDevice(bool ev); + + os::SystemEventType *GetSystemEvent() { return std::addressof(m_system_event); } + + void SetInterruptEnabled(bool en) { + if (m_use_event_handler) { + m_event_handler->SetInterruptEnabled(en); + } + } + }; + + class BatteryDriver : public IPowerControlDriver { + NON_COPYABLE(BatteryDriver); + NON_MOVEABLE(BatteryDriver); + AMS_DDSF_CASTABLE_TRAITS(ams::powctl::impl::board::nintendo::nx::BatteryDriver, ::ams::powctl::impl::IPowerControlDriver); + public: + BatteryDriver(bool ev) : IPowerControlDriver(ev) { /* ... */ } + + /* Generic API. */ + virtual void InitializeDriver() override; + virtual void FinalizeDriver() override; + + virtual Result GetDeviceSystemEvent(os::SystemEventType **out, IDevice *device) override; + virtual Result SetDeviceInterruptEnabled(IDevice *device, bool enable) override; + + virtual Result GetDeviceErrorStatus(u32 *out, IDevice *device) override; + virtual Result SetDeviceErrorStatus(IDevice *device, u32 status) override; + + /* Battery API. */ + virtual Result GetBatteryChargePercentage(float *out_percent, IDevice *device) override; + virtual Result GetBatteryVoltageFuelGaugePercentage(float *out_percent, IDevice *device) override; + + virtual Result GetBatteryFullCapacity(int *out_mah, IDevice *device) override; + virtual Result GetBatteryRemainingCapacity(int *out_mah, IDevice *device) override; + + virtual Result SetBatteryChargePercentageMinimumAlertThreshold(IDevice *device, float percentage) override; + virtual Result SetBatteryChargePercentageMaximumAlertThreshold(IDevice *device, float percentage) override; + + virtual Result SetBatteryVoltageFuelGaugePercentageMinimumAlertThreshold(IDevice *device, float percentage) override; + virtual Result SetBatteryVoltageFuelGaugePercentageMaximumAlertThreshold(IDevice *device, float percentage) override; + + virtual Result SetBatteryFullChargeThreshold(IDevice *device, float percentage) override; + + virtual Result GetBatteryAverageCurrent(int *out_ma, IDevice *device) override; + virtual Result GetBatteryCurrent(int *out_ma, IDevice *device) override; + + virtual Result GetBatteryInternalState(void *dst, size_t *out_size, IDevice *device, size_t dst_size) override; + virtual Result SetBatteryInternalState(IDevice *device, const void *src, size_t src_size) override; + + virtual Result GetBatteryNeedToRestoreParameters(bool *out, IDevice *device) override; + virtual Result SetBatteryNeedToRestoreParameters(IDevice *device, bool en) override; + + virtual Result IsBatteryI2cShutdownEnabled(bool *out, IDevice *device) override; + virtual Result SetBatteryI2cShutdownEnabled(IDevice *device, bool en) override; + + virtual Result IsBatteryPresent(bool *out, IDevice *device) override; + + virtual Result GetBatteryCycles(int *out, IDevice *device) override; + virtual Result SetBatteryCycles(IDevice *device, int cycles) override; + + virtual Result GetBatteryAge(float *out_percent, IDevice *device) override; + + virtual Result GetBatteryTemperature(float *out_c, IDevice *device) override; + virtual Result GetBatteryMaximumTemperature(float *out_c, IDevice *device) override; + + virtual Result SetBatteryTemperatureMinimumAlertThreshold(IDevice *device, float c) override; + virtual Result SetBatteryTemperatureMaximumAlertThreshold(IDevice *device, float c) override; + + virtual Result GetBatteryVCell(int *out_mv, IDevice *device) override; + virtual Result GetBatteryAverageVCell(int *out_mv, IDevice *device) override; + + virtual Result GetBatteryAverageVCellTime(TimeSpan *out, IDevice *device) override; + + virtual Result SetBatteryVoltageMinimumAlertThreshold(IDevice *device, int mv) override; + + virtual Result GetBatteryOpenCircuitVoltage(int *out_mv, IDevice *device) override; + + virtual Result SetBatteryVoltageMaximumAlertThreshold(IDevice *device, int mv) override; + + /* Unsupported Charger API. */ + virtual Result GetChargerChargeCurrentState(ChargeCurrentState *out, IDevice *device) override { AMS_UNUSED(out, device); R_THROW(powctl::ResultNotSupported()); } + virtual Result SetChargerChargeCurrentState(IDevice *device, ChargeCurrentState state) override { AMS_UNUSED(device, state); R_THROW(powctl::ResultNotSupported()); } + + virtual Result GetChargerFastChargeCurrentLimit(int *out_ma, IDevice *device) override { AMS_UNUSED(out_ma, device); R_THROW(powctl::ResultNotSupported()); } + virtual Result SetChargerFastChargeCurrentLimit(IDevice *device, int ma) override { AMS_UNUSED(device, ma); R_THROW(powctl::ResultNotSupported()); } + + virtual Result GetChargerChargeVoltageLimit(int *out_mv, IDevice *device) override { AMS_UNUSED(out_mv, device); R_THROW(powctl::ResultNotSupported()); } + virtual Result SetChargerChargeVoltageLimit(IDevice *device, int mv) override { AMS_UNUSED(device, mv); R_THROW(powctl::ResultNotSupported()); } + + virtual Result SetChargerChargerConfiguration(IDevice *device, ChargerConfiguration cfg) override { AMS_UNUSED(device, cfg); R_THROW(powctl::ResultNotSupported()); } + + virtual Result IsChargerHiZEnabled(bool *out, IDevice *device) override { AMS_UNUSED(out, device); R_THROW(powctl::ResultNotSupported()); } + virtual Result SetChargerHiZEnabled(IDevice *device, bool en) override { AMS_UNUSED(device, en); R_THROW(powctl::ResultNotSupported()); } + + virtual Result GetChargerInputCurrentLimit(int *out_ma, IDevice *device) override { AMS_UNUSED(out_ma, device); R_THROW(powctl::ResultNotSupported()); } + virtual Result SetChargerInputCurrentLimit(IDevice *device, int ma) override { AMS_UNUSED(device, ma); R_THROW(powctl::ResultNotSupported()); } + + virtual Result SetChargerInputVoltageLimit(IDevice *device, int mv) override { AMS_UNUSED(device, mv); R_THROW(powctl::ResultNotSupported()); } + + virtual Result SetChargerBoostModeCurrentLimit(IDevice *device, int ma) override { AMS_UNUSED(device, ma); R_THROW(powctl::ResultNotSupported()); } + + virtual Result GetChargerChargerStatus(ChargerStatus *out, IDevice *device) override { AMS_UNUSED(out, device); R_THROW(powctl::ResultNotSupported()); } + + virtual Result IsChargerWatchdogTimerEnabled(bool *out, IDevice *device) override { AMS_UNUSED(out, device); R_THROW(powctl::ResultNotSupported()); } + virtual Result SetChargerWatchdogTimerEnabled(IDevice *device, bool en) override { AMS_UNUSED(device, en); R_THROW(powctl::ResultNotSupported()); } + + virtual Result SetChargerWatchdogTimerTimeout(IDevice *device, TimeSpan timeout) override { AMS_UNUSED(device, timeout); R_THROW(powctl::ResultNotSupported()); } + virtual Result ResetChargerWatchdogTimer(IDevice *device) override { AMS_UNUSED(device); R_THROW(powctl::ResultNotSupported()); } + + virtual Result GetChargerBatteryCompensation(int *out_mo, IDevice *device) override { AMS_UNUSED(out_mo, device); R_THROW(powctl::ResultNotSupported()); } + virtual Result SetChargerBatteryCompensation(IDevice *device, int mo) override { AMS_UNUSED(device, mo); R_THROW(powctl::ResultNotSupported()); } + + virtual Result GetChargerVoltageClamp(int *out_mv, IDevice *device) override { AMS_UNUSED(out_mv, device); R_THROW(powctl::ResultNotSupported()); } + virtual Result SetChargerVoltageClamp(IDevice *device, int mv) override { AMS_UNUSED(device, mv); R_THROW(powctl::ResultNotSupported()); } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_board_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_board_impl.cpp new file mode 100644 index 00000000..d874b0ac --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_board_impl.cpp @@ -0,0 +1,73 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../../../powctl_device_management.hpp" +#include "powctl_board_impl.hpp" +#include "powctl_battery_driver.hpp" +#include "powctl_charger_driver.hpp" + +namespace ams::powctl::impl::board::nintendo::nx { + + namespace { + + constinit util::optional<ChargerDriver> g_charger_driver; + constinit util::optional<BatteryDriver> g_battery_driver; + + void InitializeChargerDriver(bool use_event_handlers) { + /* Create the charger driver. */ + g_charger_driver.emplace(use_event_handlers); + + /* Register the driver. */ + powctl::impl::RegisterDriver(std::addressof(*g_charger_driver)); + } + + void InitializeBatteryDriver(bool use_event_handlers) { + /* Create the battery driver. */ + g_battery_driver.emplace(use_event_handlers); + + /* Register the driver. */ + powctl::impl::RegisterDriver(std::addressof(*g_battery_driver)); + } + + void FinalizeChargerDriver() { + /* Unregister the driver. */ + powctl::impl::UnregisterDriver(std::addressof(*g_charger_driver)); + + /* Destroy the battery driver. */ + g_charger_driver = util::nullopt; + } + + void FinalizeBatteryDriver() { + /* Unregister the driver. */ + powctl::impl::UnregisterDriver(std::addressof(*g_battery_driver)); + + /* Destroy the battery driver. */ + g_battery_driver = util::nullopt; + } + + } + + void Initialize(bool use_event_handlers) { + InitializeChargerDriver(use_event_handlers); + InitializeBatteryDriver(use_event_handlers); + } + + void Finalize() { + FinalizeBatteryDriver(); + FinalizeChargerDriver(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_board_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_board_impl.hpp new file mode 100644 index 00000000..799ba1e6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_board_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#include "powctl_interrupt_event_handler.hpp" + +namespace ams::powctl::impl::board::nintendo::nx { + + void Initialize(bool use_event_handlers); + void Finalize(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_bq24193_driver.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_bq24193_driver.cpp new file mode 100644 index 00000000..74d58275 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_bq24193_driver.cpp @@ -0,0 +1,414 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "powctl_bq24193_driver.hpp" + +namespace ams::powctl::impl::board::nintendo::nx { + + namespace bq24193 { + + constexpr inline u8 InputSourceControl = 0x00; + constexpr inline u8 PowerOnConfiguration = 0x01; + constexpr inline u8 ChargeCurrentControl = 0x02; + constexpr inline u8 PreChargeTerminationCurrentControl = 0x03; + constexpr inline u8 ChargeVoltageControl = 0x04; + constexpr inline u8 ChargeTerminationTimerControl = 0x05; + constexpr inline u8 IrCompensationThermalRegulationControl = 0x06; + constexpr inline u8 MiscOperationControl = 0x07; + constexpr inline u8 SystemStatus = 0x08; + constexpr inline u8 Fault = 0x09; + constexpr inline u8 VendorPartRevisionStatus = 0x0A; + + constexpr u8 EncodePreChargeCurrentLimit(int ma) { + constexpr int Minimum = 128; + constexpr int Maximum = 2048; + ma = std::max(std::min(ma, Maximum), Minimum); + + return static_cast<u8>((static_cast<u32>(ma - Minimum) >> 7) << 4); + } + + constexpr u8 EncodeTerminationCurrentLimit(int ma) { + constexpr int Minimum = 128; + constexpr int Maximum = 2048; + ma = std::max(std::min(ma, Maximum), Minimum); + + return static_cast<u8>((static_cast<u32>(ma - Minimum) >> 7) << 0); + } + + constexpr u8 EncodeMinimumSystemVoltageLimit(int mv) { + constexpr int Minimum = 3000; + constexpr int Maximum = 3700; + mv = std::max(std::min(mv, Maximum), Minimum); + + return static_cast<u8>(((mv - Minimum) / 100) << 1); + } + + constexpr u8 EncodeFastChargeCurrentLimit(int ma) { + constexpr int Minimum = 512; + constexpr int Maximum = 4544; + ma = std::max(std::min(ma, Maximum), Minimum); + + return static_cast<u8>((static_cast<u32>(ma - Minimum) >> 6) << 2); + } + + constexpr int DecodeFastChargeCurrentLimit(u8 reg) { + constexpr int Minimum = 512; + + return Minimum + (static_cast<u32>(reg & 0xFC) << 4); + } + + static_assert(DecodeFastChargeCurrentLimit(EncodeFastChargeCurrentLimit(512)) == 512); + static_assert(DecodeFastChargeCurrentLimit(EncodeFastChargeCurrentLimit(4544)) == 4544); + static_assert(DecodeFastChargeCurrentLimit(EncodeFastChargeCurrentLimit(576)) == 576); + + constexpr u8 EncodeChargeVoltageLimit(int mv) { + constexpr int Minimum = 3504; + constexpr int Maximum = 4400; + mv = std::max(std::min(mv, Maximum), Minimum); + + return static_cast<u8>((static_cast<u32>(mv - Minimum) >> 4) << 2); + } + + constexpr int DecodeChargeVoltageLimit(u8 reg) { + constexpr int Minimum = 3504; + constexpr int Maximum = 4400; + + return std::min<int>(Maximum, Minimum + (static_cast<u32>(reg & 0xFC) << 2)); + } + + static_assert(DecodeChargeVoltageLimit(EncodeChargeVoltageLimit(3504)) == 3504); + static_assert(DecodeChargeVoltageLimit(EncodeChargeVoltageLimit(4400)) == 4400); + static_assert(DecodeChargeVoltageLimit(EncodeChargeVoltageLimit(3520)) == 3520); + + constexpr u8 EncodeChargerConfiguration(bq24193::ChargerConfiguration cfg) { + switch (cfg) { + case ChargerConfiguration_ChargeDisable: return 0x00; + case ChargerConfiguration_ChargeBattery: return 0x10; + case ChargerConfiguration_Otg: return 0x20; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + constexpr u8 EncodeWatchdogTimerSetting(int seconds) { + if (seconds == 0) { + return 0x00; + } else if (seconds < 80) { + return 0x10; + } else if (seconds < 160) { + return 0x20; + } else { + return 0x30; + } + } + + constexpr u8 EncodeBatteryCompensation(int mo) { + constexpr int Minimum = 0; + constexpr int Maximum = 70; + mo = std::max(std::min(mo, Maximum), Minimum); + + return static_cast<u8>((static_cast<u32>(mo - Minimum) / 10) << 5); + } + + constexpr int DecodeBatteryCompensation(u8 reg) { + constexpr int Minimum = 0; + + return Minimum + (static_cast<u32>(reg & 0xE0) >> 5) * 10; + } + + static_assert(DecodeBatteryCompensation(EncodeBatteryCompensation(0)) == 0); + static_assert(DecodeBatteryCompensation(EncodeBatteryCompensation(70)) == 70); + static_assert(DecodeBatteryCompensation(EncodeBatteryCompensation(30)) == 30); + + constexpr u8 EncodeVoltageClamp(int mv) { + constexpr int Minimum = 0; + constexpr int Maximum = 112; + mv = std::max(std::min(mv, Maximum), Minimum); + + return static_cast<u8>((static_cast<u32>(mv - Minimum) >> 4) << 2); + } + + constexpr int DecodeVoltageClamp(u8 reg) { + constexpr int Minimum = 0; + + return Minimum + (static_cast<u32>(reg & 0x1C) << 2); + } + + static_assert(DecodeVoltageClamp(EncodeVoltageClamp(0)) == 0); + static_assert(DecodeVoltageClamp(EncodeVoltageClamp(112)) == 112); + static_assert(DecodeVoltageClamp(EncodeVoltageClamp(64)) == 64); + + constexpr u8 EncodeInputCurrentLimit(int ma) { + if (ma < 150) { + return 0; + } else if (ma < 500) { + return 1; + } else if (ma < 900) { + return 2; + } else if (ma < 1200) { + return 3; + } else if (ma < 1500) { + return 4; + } else if (ma < 2000) { + return 5; + } else if (ma < 3000) { + return 6; + } else{ + return 7; + } + } + + constexpr int DecodeInputCurrentLimit(u8 reg) { + switch (reg & 0x07) { + case 0: return 100; + case 1: return 150; + case 2: return 500; + case 3: return 900; + case 4: return 1200; + case 5: return 1500; + case 6: return 2000; + case 7: return 3000; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + static_assert(DecodeInputCurrentLimit(EncodeInputCurrentLimit(100)) == 100); + static_assert(DecodeInputCurrentLimit(EncodeInputCurrentLimit(150)) == 150); + static_assert(DecodeInputCurrentLimit(EncodeInputCurrentLimit(500)) == 500); + static_assert(DecodeInputCurrentLimit(EncodeInputCurrentLimit(900)) == 900); + static_assert(DecodeInputCurrentLimit(EncodeInputCurrentLimit(1200)) == 1200); + static_assert(DecodeInputCurrentLimit(EncodeInputCurrentLimit(1500)) == 1500); + static_assert(DecodeInputCurrentLimit(EncodeInputCurrentLimit(2000)) == 2000); + static_assert(DecodeInputCurrentLimit(EncodeInputCurrentLimit(3000)) == 3000); + + static_assert(DecodeInputCurrentLimit(EncodeInputCurrentLimit(0)) == 100); + static_assert(DecodeInputCurrentLimit(EncodeInputCurrentLimit(9999)) == 3000); + + static_assert(DecodeInputCurrentLimit(EncodeInputCurrentLimit(149)) == 100); + static_assert(DecodeInputCurrentLimit(EncodeInputCurrentLimit(151)) == 150); + + constexpr u8 EncodeInputVoltageLimit(int mv) { + constexpr int Minimum = 3880; + constexpr int Maximum = 5080; + mv = std::max(std::min(mv, Maximum), Minimum); + + return static_cast<u8>(((static_cast<u32>(mv - Minimum) / 80) & 0xF) << 3); + } + + constexpr u8 EncodeBoostModeCurrentLimit(int ma) { + return ma >= 1300 ? 1 : 0; + } + + constexpr bq24193::ChargerStatus DecodeChargerStatus(u8 reg) { + switch (reg & 0x30) { + case 0x00: return bq24193::ChargerStatus_NotCharging; + case 0x10: return bq24193::ChargerStatus_PreCharge; + case 0x20: return bq24193::ChargerStatus_FastCharging; + case 0x30: return bq24193::ChargerStatus_ChargeTerminationDone; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + } + + namespace { + + ALWAYS_INLINE Result ReadWriteRegister(const i2c::I2cSession &session, u8 address, u8 mask, u8 value) { + /* Read the current value. */ + u8 cur_val; + R_TRY(i2c::ReadSingleRegister(session, address, std::addressof(cur_val))); + + /* Update the value. */ + const u8 new_val = (cur_val & ~mask) | (value & mask); + R_TRY(i2c::WriteSingleRegister(session, address, new_val)); + + R_SUCCEED(); + } + + } + + Result Bq24193Driver::InitializeSession() { + /* Set fast charge current limit. */ + R_TRY(this->SetFastChargeCurrentLimit(512)); + + /* Disable force 20 percent charge. */ + R_TRY(this->SetForce20PercentChargeCurrent(false)); + + /* Set pre-charge current limit. */ + R_TRY(this->SetPreChargeCurrentLimit(128)); + + /* Set termination current limit. */ + R_TRY(this->SetTerminationCurrentLimit(128)); + + /* Set minimum system voltage limit. */ + R_TRY(this->SetMinimumSystemVoltageLimit(3000)); + + /* Set watchdog timer setting. */ + R_TRY(this->SetWatchdogTimerSetting(0)); + + /* Disable charging safety timer. */ + R_TRY(this->SetChargingSafetyTimerEnabled(false)); + + /* Reset the watchdog timer. */ + R_TRY(this->ResetWatchdogTimer()); + + R_SUCCEED(); + } + + Result Bq24193Driver::SetPreChargeCurrentLimit(int ma) { + R_RETURN(ReadWriteRegister(m_i2c_session, bq24193::PreChargeTerminationCurrentControl, 0xF0, bq24193::EncodePreChargeCurrentLimit(ma))); + } + + Result Bq24193Driver::SetTerminationCurrentLimit(int ma) { + R_RETURN(ReadWriteRegister(m_i2c_session, bq24193::PreChargeTerminationCurrentControl, 0x0F, bq24193::EncodeTerminationCurrentLimit(ma))); + } + + Result Bq24193Driver::SetMinimumSystemVoltageLimit(int mv) { + R_RETURN(ReadWriteRegister(m_i2c_session, bq24193::PowerOnConfiguration, 0x0E, bq24193::EncodeMinimumSystemVoltageLimit(mv))); + } + + Result Bq24193Driver::SetChargingSafetyTimerEnabled(bool en) { + R_RETURN(ReadWriteRegister(m_i2c_session, bq24193::ChargeTerminationTimerControl, 0x08, en ? 0x08 : 0x00)); + } + + Result Bq24193Driver::GetForce20PercentChargeCurrent(bool *out) { + /* Get the register. */ + u8 val; + R_TRY(i2c::ReadSingleRegister(m_i2c_session, bq24193::ChargeCurrentControl, std::addressof(val))); + + /* Extract the value. */ + *out = (val & 0x01) != 0; + R_SUCCEED(); + } + + Result Bq24193Driver::SetForce20PercentChargeCurrent(bool en) { + R_RETURN(ReadWriteRegister(m_i2c_session, bq24193::ChargeCurrentControl, 0x01, en ? 0x01 : 0x00)); + } + + Result Bq24193Driver::GetFastChargeCurrentLimit(int *out_ma) { + /* Get the register. */ + u8 val; + R_TRY(i2c::ReadSingleRegister(m_i2c_session, bq24193::ChargeCurrentControl, std::addressof(val))); + + /* Extract the value. */ + *out_ma = bq24193::DecodeFastChargeCurrentLimit(val); + R_SUCCEED(); + } + + Result Bq24193Driver::SetFastChargeCurrentLimit(int ma) { + R_RETURN(ReadWriteRegister(m_i2c_session, bq24193::ChargeCurrentControl, 0xFC, bq24193::EncodeFastChargeCurrentLimit(ma))); + } + + Result Bq24193Driver::GetChargeVoltageLimit(int *out_mv) { + /* Get the register. */ + u8 val; + R_TRY(i2c::ReadSingleRegister(m_i2c_session, bq24193::ChargeVoltageControl, std::addressof(val))); + + /* Extract the value. */ + *out_mv = bq24193::DecodeChargeVoltageLimit(val); + R_SUCCEED(); + } + + Result Bq24193Driver::SetChargeVoltageLimit(int mv) { + R_RETURN(ReadWriteRegister(m_i2c_session, bq24193::ChargeVoltageControl, 0xFC, bq24193::EncodeChargeVoltageLimit(mv))); + } + + Result Bq24193Driver::SetChargerConfiguration(bq24193::ChargerConfiguration cfg) { + R_RETURN(ReadWriteRegister(m_i2c_session, bq24193::PowerOnConfiguration, 0x30, bq24193::EncodeChargerConfiguration(cfg))); + } + + Result Bq24193Driver::IsHiZEnabled(bool *out) { + /* Get the register. */ + u8 val; + R_TRY(i2c::ReadSingleRegister(m_i2c_session, bq24193::InputSourceControl, std::addressof(val))); + + /* Extract the value. */ + *out = (val & 0x80) != 0; + R_SUCCEED(); + } + + Result Bq24193Driver::SetHiZEnabled(bool en) { + R_RETURN(ReadWriteRegister(m_i2c_session, bq24193::InputSourceControl, 0x80, en ? 0x80 : 0x00)); + } + + Result Bq24193Driver::GetInputCurrentLimit(int *out_ma) { + /* Get the register. */ + u8 val; + R_TRY(i2c::ReadSingleRegister(m_i2c_session, bq24193::InputSourceControl, std::addressof(val))); + + /* Extract the value. */ + *out_ma = bq24193::DecodeInputCurrentLimit(val); + R_SUCCEED(); + } + + Result Bq24193Driver::SetInputCurrentLimit(int ma) { + R_RETURN(ReadWriteRegister(m_i2c_session, bq24193::InputSourceControl, 0x07, bq24193::EncodeInputCurrentLimit(ma))); + } + + Result Bq24193Driver::SetInputVoltageLimit(int mv) { + R_RETURN(ReadWriteRegister(m_i2c_session, bq24193::InputSourceControl, 0x78, bq24193::EncodeInputVoltageLimit(mv))); + } + + Result Bq24193Driver::SetBoostModeCurrentLimit(int ma) { + R_RETURN(ReadWriteRegister(m_i2c_session, bq24193::PowerOnConfiguration, 0x01, bq24193::EncodeBoostModeCurrentLimit(ma))); + } + + Result Bq24193Driver::GetChargerStatus(bq24193::ChargerStatus *out) { + /* Get the register. */ + u8 val; + R_TRY(i2c::ReadSingleRegister(m_i2c_session, bq24193::SystemStatus, std::addressof(val))); + + /* Extract the value. */ + *out = bq24193::DecodeChargerStatus(val); + R_SUCCEED(); + } + + Result Bq24193Driver::ResetWatchdogTimer() { + R_RETURN(ReadWriteRegister(m_i2c_session, bq24193::PowerOnConfiguration, 0x40, 0x40)); + } + + Result Bq24193Driver::SetWatchdogTimerSetting(int seconds) { + R_RETURN(ReadWriteRegister(m_i2c_session, bq24193::ChargeTerminationTimerControl, 0x30, bq24193::EncodeWatchdogTimerSetting(seconds))); + } + + Result Bq24193Driver::GetBatteryCompensation(int *out_mo) { + /* Get the register. */ + u8 val; + R_TRY(i2c::ReadSingleRegister(m_i2c_session, bq24193::IrCompensationThermalRegulationControl, std::addressof(val))); + + /* Extract the value. */ + *out_mo = bq24193::DecodeBatteryCompensation(val); + R_SUCCEED(); + } + + Result Bq24193Driver::SetBatteryCompensation(int mo) { + R_RETURN(ReadWriteRegister(m_i2c_session, bq24193::IrCompensationThermalRegulationControl, 0xE0, bq24193::EncodeBatteryCompensation(mo))); + } + + Result Bq24193Driver::GetVoltageClamp(int *out_mv) { + /* Get the register. */ + u8 val; + R_TRY(i2c::ReadSingleRegister(m_i2c_session, bq24193::IrCompensationThermalRegulationControl, std::addressof(val))); + + /* Extract the value. */ + *out_mv = bq24193::DecodeVoltageClamp(val); + R_SUCCEED(); + } + + Result Bq24193Driver::SetVoltageClamp(int mv) { + R_RETURN(ReadWriteRegister(m_i2c_session, bq24193::IrCompensationThermalRegulationControl, 0x1C, bq24193::EncodeVoltageClamp(mv))); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_bq24193_driver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_bq24193_driver.hpp new file mode 100644 index 00000000..f050bf94 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_bq24193_driver.hpp @@ -0,0 +1,115 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::powctl::impl::board::nintendo::nx { + + namespace bq24193 { + + enum ChargerConfiguration { + ChargerConfiguration_ChargeDisable = 0, + ChargerConfiguration_ChargeBattery = 1, + ChargerConfiguration_Otg = 2, + }; + + enum ChargerStatus { + ChargerStatus_NotCharging = 0, + ChargerStatus_PreCharge = 1, + ChargerStatus_FastCharging = 2, + ChargerStatus_ChargeTerminationDone = 3, + }; + + } + + class Bq24193Driver { + private: + os::SdkMutex m_mutex; + int m_init_count; + i2c::I2cSession m_i2c_session; + private: + Result InitializeSession(); + public: + constexpr Bq24193Driver() : m_mutex(), m_init_count(0), m_i2c_session() { + /* ... */ + } + + void Initialize() { + std::scoped_lock lk(m_mutex); + if ((m_init_count++) == 0) { + /* Initialize i2c library. */ + i2c::InitializeEmpty(); + + /* Open session. */ + R_ABORT_UNLESS(i2c::OpenSession(std::addressof(m_i2c_session), i2c::DeviceCode_Bq24193)); + + /* Initialize session. */ + R_ABORT_UNLESS(this->InitializeSession()); + } + } + + void Finalize() { + std::scoped_lock lk(m_mutex); + if ((--m_init_count) == 0) { + /* Close session. */ + i2c::CloseSession(m_i2c_session); + + /* Finalize i2c library. */ + i2c::Finalize(); + } + } + + Result SetPreChargeCurrentLimit(int ma); + Result SetTerminationCurrentLimit(int ma); + + Result SetMinimumSystemVoltageLimit(int mv); + + Result SetChargingSafetyTimerEnabled(bool en); + + Result GetForce20PercentChargeCurrent(bool *out); + Result SetForce20PercentChargeCurrent(bool en); + + Result GetFastChargeCurrentLimit(int *out_ma); + Result SetFastChargeCurrentLimit(int ma); + + Result GetChargeVoltageLimit(int *out_mv); + Result SetChargeVoltageLimit(int mv); + + Result SetChargerConfiguration(bq24193::ChargerConfiguration cfg); + + Result IsHiZEnabled(bool *out); + Result SetHiZEnabled(bool en); + + Result GetInputCurrentLimit(int *out_ma); + Result SetInputCurrentLimit(int ma); + + Result SetInputVoltageLimit(int mv); + + Result SetBoostModeCurrentLimit(int ma); + + Result GetChargerStatus(bq24193::ChargerStatus *out); + + Result ResetWatchdogTimer(); + Result SetWatchdogTimerSetting(int seconds); + + Result GetBatteryCompensation(int *out_mo); + Result SetBatteryCompensation(int mo); + + Result GetVoltageClamp(int *out_mv); + Result SetVoltageClamp(int mv); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_charger_driver.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_charger_driver.cpp new file mode 100644 index 00000000..0cf1a620 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_charger_driver.cpp @@ -0,0 +1,377 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../../../powctl_device_management.hpp" +#include "powctl_retry_helper.hpp" +#include "powctl_charger_driver.hpp" +#include "powctl_bq24193_driver.hpp" + +namespace ams::powctl::impl::board::nintendo::nx { + + namespace { + + constinit util::optional<ChargerDevice> g_charger_device; + + constinit Bq24193Driver g_bq24193_driver; + + ALWAYS_INLINE Bq24193Driver &GetBq24193Driver() { + return g_bq24193_driver; + } + + } + + ChargerDevice::ChargerDevice(bool ev) : m_gpio_pad_session(), m_watchdog_timer_enabled(false), m_watchdog_timer_timeout(0), m_use_event_handler(ev), m_event_handler() { + if (m_use_event_handler) { + /* Create the system event. */ + os::CreateSystemEvent(std::addressof(m_system_event), os::EventClearMode_ManualClear, true); + + /* Create the handler. */ + m_event_handler.emplace(this); + + /* Register the event handler. */ + powctl::impl::RegisterInterruptHandler(std::addressof(*m_event_handler)); + } + } + + /* Generic API. */ + void ChargerDriver::InitializeDriver() { + /* Initialize Bq24193Driver */ + GetBq24193Driver().Initialize(); + + /* Initialize gpio library. */ + gpio::Initialize(); + + /* Create charger device. */ + g_charger_device.emplace(this->IsEventHandlerEnabled()); + + /* Open the device's gpio session. */ + R_ABORT_UNLESS(gpio::OpenSession(g_charger_device->GetPadSession(), gpio::DeviceCode_BattChgEnableN)); + + /* Configure the gpio session as output. */ + gpio::SetDirection(g_charger_device->GetPadSession(), gpio::Direction_Output); + + /* Register our device. */ + this->RegisterDevice(std::addressof(*g_charger_device)); + + /* Register the charger device's code. */ + R_ABORT_UNLESS(powctl::impl::RegisterDeviceCode(powctl::DeviceCode_Bq24193, std::addressof(*g_charger_device))); + } + + void ChargerDriver::FinalizeDriver() { + /* Unregister the charger device code. */ + powctl::impl::UnregisterDeviceCode(powctl::DeviceCode_Bq24193); + + /* Unregister our device. */ + this->UnregisterDevice(std::addressof(*g_charger_device)); + + /* Close the device's gpio session. */ + gpio::CloseSession(g_charger_device->GetPadSession()); + + /* Destroy the charger device. */ + g_charger_device = util::nullopt; + + /* Finalize gpio library. */ + gpio::Finalize(); + + /* Finalize Bq24193Driver. */ + GetBq24193Driver().Finalize(); + } + + Result ChargerDriver::GetDeviceSystemEvent(os::SystemEventType **out, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Check that we support event handlers. */ + R_UNLESS(this->IsEventHandlerEnabled(), powctl::ResultNotAvailable()); + + *out = device->SafeCastTo<ChargerDevice>().GetSystemEvent(); + R_SUCCEED(); + } + + Result ChargerDriver::SetDeviceInterruptEnabled(IDevice *device, bool enable) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* Set the interrupt enable. */ + device->SafeCastTo<ChargerDevice>().SetInterruptEnabled(enable); + + R_SUCCEED(); + } + + Result ChargerDriver::GetDeviceErrorStatus(u32 *out, IDevice *device) { + /* TODO */ + AMS_UNUSED(out, device); + AMS_ABORT(); + } + + Result ChargerDriver::SetDeviceErrorStatus(IDevice *device, u32 status) { + /* TODO */ + AMS_UNUSED(device, status); + AMS_ABORT(); + } + + /* Charger API. */ + Result ChargerDriver::GetChargerChargeCurrentState(ChargeCurrentState *out, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + /* NOTE: Nintendo doesn't hold the mutex while doing the gpio:: call here, for some reason. */ + + /* Check if we're not charging. */ + if (gpio::GetValue(device->SafeCastTo<ChargerDevice>().GetPadSession()) == gpio::GpioValue_High) { + *out = ChargeCurrentState_NotCharging; + } else { + /* Get force 20 percent charge state. */ + bool force_20_percent = false; + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetBq24193Driver().GetForce20PercentChargeCurrent(std::addressof(force_20_percent))); + + /* Set output appropriately. */ + if (force_20_percent) { + *out = ChargeCurrentState_ChargingForce20Percent; + } else { + *out = ChargeCurrentState_Charging; + } + } + + R_SUCCEED(); + } + + Result ChargerDriver::SetChargerChargeCurrentState(IDevice *device, ChargeCurrentState state) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + std::scoped_lock lk(this->GetMutex()); + + switch (state) { + case ChargeCurrentState_NotCharging: + gpio::SetValue(device->SafeCastTo<ChargerDevice>().GetPadSession(), gpio::GpioValue_High); + break; + case ChargeCurrentState_ChargingForce20Percent: + case ChargeCurrentState_Charging: + gpio::SetValue(device->SafeCastTo<ChargerDevice>().GetPadSession(), gpio::GpioValue_Low); + AMS_POWCTL_DRIVER_R_TRY_WITH_RETRY(GetBq24193Driver().SetForce20PercentChargeCurrent(state == ChargeCurrentState_ChargingForce20Percent)); + break; + case ChargeCurrentState_Unknown: + R_THROW(powctl::ResultInvalidArgument()); + } + + R_SUCCEED(); + } + + Result ChargerDriver::GetChargerFastChargeCurrentLimit(int *out_ma, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out_ma != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetBq24193Driver().GetFastChargeCurrentLimit(out_ma)); + R_SUCCEED(); + } + + Result ChargerDriver::SetChargerFastChargeCurrentLimit(IDevice *device, int ma) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetBq24193Driver().SetFastChargeCurrentLimit(ma)); + R_SUCCEED(); + } + + Result ChargerDriver::GetChargerChargeVoltageLimit(int *out_mv, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out_mv != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetBq24193Driver().GetChargeVoltageLimit(out_mv)); + R_SUCCEED(); + } + + Result ChargerDriver::SetChargerChargeVoltageLimit(IDevice *device, int mv) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetBq24193Driver().SetChargeVoltageLimit(mv)); + R_SUCCEED(); + } + + Result ChargerDriver::SetChargerChargerConfiguration(IDevice *device, ChargerConfiguration cfg) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + bq24193::ChargerConfiguration bq_cfg; + switch (cfg) { + case ChargerConfiguration_ChargeDisable: bq_cfg = bq24193::ChargerConfiguration_ChargeDisable; break; + case ChargerConfiguration_ChargeBattery: bq_cfg = bq24193::ChargerConfiguration_ChargeBattery; break; + case ChargerConfiguration_Otg: bq_cfg = bq24193::ChargerConfiguration_Otg; break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetBq24193Driver().SetChargerConfiguration(bq_cfg)); + R_SUCCEED(); + } + + Result ChargerDriver::IsChargerHiZEnabled(bool *out, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetBq24193Driver().IsHiZEnabled(out)); + R_SUCCEED(); + } + + Result ChargerDriver::SetChargerHiZEnabled(IDevice *device, bool en) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetBq24193Driver().SetHiZEnabled(en)); + R_SUCCEED(); + } + + Result ChargerDriver::GetChargerInputCurrentLimit(int *out_ma, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out_ma != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetBq24193Driver().GetInputCurrentLimit(out_ma)); + R_SUCCEED(); + } + + Result ChargerDriver::SetChargerInputCurrentLimit(IDevice *device, int ma) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetBq24193Driver().SetInputCurrentLimit(ma)); + R_SUCCEED(); + } + + Result ChargerDriver::SetChargerInputVoltageLimit(IDevice *device, int mv) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetBq24193Driver().SetInputVoltageLimit(mv)); + R_SUCCEED(); + } + + Result ChargerDriver::SetChargerBoostModeCurrentLimit(IDevice *device, int ma) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetBq24193Driver().SetBoostModeCurrentLimit(ma)); + R_SUCCEED(); + } + + Result ChargerDriver::GetChargerChargerStatus(ChargerStatus *out, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + bq24193::ChargerStatus bq_status = static_cast<bq24193::ChargerStatus>(~0); + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetBq24193Driver().GetChargerStatus(std::addressof(bq_status))); + + switch (bq_status) { + case bq24193::ChargerStatus_NotCharging: + *out = ChargerStatus_NotCharging; + break; + case bq24193::ChargerStatus_PreCharge: + case bq24193::ChargerStatus_FastCharging: + *out = ChargerStatus_Charging; + break; + case bq24193::ChargerStatus_ChargeTerminationDone: + *out = ChargerStatus_ChargeTerminationDone; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + R_SUCCEED(); + } + + Result ChargerDriver::IsChargerWatchdogTimerEnabled(bool *out, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + *out = device->SafeCastTo<ChargerDevice>().IsWatchdogTimerEnabled(); + R_SUCCEED(); + } + + Result ChargerDriver::SetChargerWatchdogTimerEnabled(IDevice *device, bool en) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + auto &charger_device = device->SafeCastTo<ChargerDevice>(); + + if (en) { + std::scoped_lock lk(this->GetMutex()); + AMS_POWCTL_DRIVER_R_TRY_WITH_RETRY(GetBq24193Driver().ResetWatchdogTimer()); + AMS_POWCTL_DRIVER_R_TRY_WITH_RETRY(GetBq24193Driver().SetWatchdogTimerSetting(charger_device.GetWatchdogTimerTimeout().GetSeconds())); + } else { + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetBq24193Driver().SetWatchdogTimerSetting(0)); + } + + charger_device.SetWatchdogTimerEnabled(en); + R_SUCCEED(); + } + + Result ChargerDriver::SetChargerWatchdogTimerTimeout(IDevice *device, TimeSpan timeout) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + device->SafeCastTo<ChargerDevice>().SetWatchdogTimerTimeout(timeout); + R_SUCCEED(); + } + + Result ChargerDriver::ResetChargerWatchdogTimer(IDevice *device) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetBq24193Driver().ResetWatchdogTimer()); + R_SUCCEED(); + } + + Result ChargerDriver::GetChargerBatteryCompensation(int *out_mo, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out_mo != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetBq24193Driver().GetBatteryCompensation(out_mo)); + R_SUCCEED(); + } + + Result ChargerDriver::SetChargerBatteryCompensation(IDevice *device, int mo) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetBq24193Driver().SetBatteryCompensation(mo)); + R_SUCCEED(); + } + + Result ChargerDriver::GetChargerVoltageClamp(int *out_mv, IDevice *device) { + /* Validate arguments. */ + R_UNLESS(out_mv != nullptr, powctl::ResultInvalidArgument()); + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetBq24193Driver().GetVoltageClamp(out_mv)); + R_SUCCEED(); + } + + Result ChargerDriver::SetChargerVoltageClamp(IDevice *device, int mv) { + /* Validate arguments. */ + R_UNLESS(device != nullptr, powctl::ResultInvalidArgument()); + + AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(GetBq24193Driver().SetVoltageClamp(mv)); + R_SUCCEED(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_charger_driver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_charger_driver.hpp new file mode 100644 index 00000000..d1227e0d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_charger_driver.hpp @@ -0,0 +1,160 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../../../powctl_i_power_control_driver.hpp" +#include "powctl_interrupt_event_handler.hpp" + +namespace ams::powctl::impl::board::nintendo::nx { + + class ChargerDevice : public powctl::impl::IDevice { + NON_COPYABLE(ChargerDevice); + NON_MOVEABLE(ChargerDevice); + AMS_DDSF_CASTABLE_TRAITS(ams::powctl::impl::board::nintendo::nx::ChargerDevice, ::ams::powctl::impl::IDevice); + private: + gpio::GpioPadSession m_gpio_pad_session; + bool m_watchdog_timer_enabled; + TimeSpan m_watchdog_timer_timeout; + bool m_use_event_handler; + util::optional<ChargerInterruptEventHandler> m_event_handler; + os::SystemEventType m_system_event; + public: + ChargerDevice(bool ev); + + bool IsWatchdogTimerEnabled() const { return m_watchdog_timer_enabled; } + void SetWatchdogTimerEnabled(bool en) { m_watchdog_timer_enabled = en; } + + TimeSpan GetWatchdogTimerTimeout() const { return m_watchdog_timer_timeout; } + void SetWatchdogTimerTimeout(TimeSpan ts) { m_watchdog_timer_timeout = ts; } + + gpio::GpioPadSession *GetPadSession() { return std::addressof(m_gpio_pad_session); } + + os::SystemEventType *GetSystemEvent() { return std::addressof(m_system_event); } + + void SetInterruptEnabled(bool en) { + if (m_use_event_handler) { + m_event_handler->SetInterruptEnabled(en); + } + } + }; + + class ChargerDriver : public IPowerControlDriver { + NON_COPYABLE(ChargerDriver); + NON_MOVEABLE(ChargerDriver); + AMS_DDSF_CASTABLE_TRAITS(ams::powctl::impl::board::nintendo::nx::ChargerDriver, ::ams::powctl::impl::IPowerControlDriver); + public: + ChargerDriver(bool ev) : IPowerControlDriver(ev) { /* ... */ } + + /* Generic API. */ + virtual void InitializeDriver() override; + virtual void FinalizeDriver() override; + + virtual Result GetDeviceSystemEvent(os::SystemEventType **out, IDevice *device) override; + virtual Result SetDeviceInterruptEnabled(IDevice *device, bool enable) override; + + virtual Result GetDeviceErrorStatus(u32 *out, IDevice *device) override; + virtual Result SetDeviceErrorStatus(IDevice *device, u32 status) override; + + /* Charger API. */ + virtual Result GetChargerChargeCurrentState(ChargeCurrentState *out, IDevice *device) override; + virtual Result SetChargerChargeCurrentState(IDevice *device, ChargeCurrentState state) override; + + virtual Result GetChargerFastChargeCurrentLimit(int *out_ma, IDevice *device) override; + virtual Result SetChargerFastChargeCurrentLimit(IDevice *device, int ma) override; + + virtual Result GetChargerChargeVoltageLimit(int *out_mv, IDevice *device) override; + virtual Result SetChargerChargeVoltageLimit(IDevice *device, int mv) override; + + virtual Result SetChargerChargerConfiguration(IDevice *device, ChargerConfiguration cfg) override; + + virtual Result IsChargerHiZEnabled(bool *out, IDevice *device) override; + virtual Result SetChargerHiZEnabled(IDevice *device, bool en) override; + + virtual Result GetChargerInputCurrentLimit(int *out_ma, IDevice *device) override; + virtual Result SetChargerInputCurrentLimit(IDevice *device, int ma) override; + + virtual Result SetChargerInputVoltageLimit(IDevice *device, int mv) override; + + virtual Result SetChargerBoostModeCurrentLimit(IDevice *device, int ma) override; + + virtual Result GetChargerChargerStatus(ChargerStatus *out, IDevice *device) override; + + virtual Result IsChargerWatchdogTimerEnabled(bool *out, IDevice *device) override; + virtual Result SetChargerWatchdogTimerEnabled(IDevice *device, bool en) override; + + virtual Result SetChargerWatchdogTimerTimeout(IDevice *device, TimeSpan timeout) override; + virtual Result ResetChargerWatchdogTimer(IDevice *device) override; + + virtual Result GetChargerBatteryCompensation(int *out_mo, IDevice *device) override; + virtual Result SetChargerBatteryCompensation(IDevice *device, int mo) override; + + virtual Result GetChargerVoltageClamp(int *out_mv, IDevice *device) override; + virtual Result SetChargerVoltageClamp(IDevice *device, int mv) override; + + /* Unsupported Battery API. */ + virtual Result GetBatteryChargePercentage(float *out_percent, IDevice *device) override { AMS_UNUSED(out_percent, device); R_THROW(powctl::ResultNotSupported()); } + + virtual Result GetBatteryVoltageFuelGaugePercentage(float *out_percent, IDevice *device) override { AMS_UNUSED(out_percent, device); R_THROW(powctl::ResultNotSupported()); } + + virtual Result GetBatteryFullCapacity(int *out_mah, IDevice *device) override { AMS_UNUSED(out_mah, device); R_THROW(powctl::ResultNotSupported()); } + virtual Result GetBatteryRemainingCapacity(int *out_mah, IDevice *device) override { AMS_UNUSED(out_mah, device); R_THROW(powctl::ResultNotSupported()); } + + virtual Result SetBatteryChargePercentageMinimumAlertThreshold(IDevice *device, float percentage) override { AMS_UNUSED(device, percentage); R_THROW(powctl::ResultNotSupported()); } + virtual Result SetBatteryChargePercentageMaximumAlertThreshold(IDevice *device, float percentage) override { AMS_UNUSED(device, percentage); R_THROW(powctl::ResultNotSupported()); } + + virtual Result SetBatteryVoltageFuelGaugePercentageMinimumAlertThreshold(IDevice *device, float percentage) override { AMS_UNUSED(device, percentage); R_THROW(powctl::ResultNotSupported()); } + virtual Result SetBatteryVoltageFuelGaugePercentageMaximumAlertThreshold(IDevice *device, float percentage) override { AMS_UNUSED(device, percentage); R_THROW(powctl::ResultNotSupported()); } + + virtual Result SetBatteryFullChargeThreshold(IDevice *device, float percentage) override { AMS_UNUSED(device, percentage); R_THROW(powctl::ResultNotSupported()); } + + virtual Result GetBatteryAverageCurrent(int *out_ma, IDevice *device) override { AMS_UNUSED(out_ma, device); R_THROW(powctl::ResultNotSupported()); } + virtual Result GetBatteryCurrent(int *out_ma, IDevice *device) override { AMS_UNUSED(out_ma, device); R_THROW(powctl::ResultNotSupported()); } + + virtual Result GetBatteryInternalState(void *dst, size_t *out_size, IDevice *device, size_t dst_size) override { AMS_UNUSED(dst, out_size, device, dst_size); R_THROW(powctl::ResultNotSupported()); } + virtual Result SetBatteryInternalState(IDevice *device, const void *src, size_t src_size) override { AMS_UNUSED(device, src, src_size); R_THROW(powctl::ResultNotSupported()); } + + virtual Result GetBatteryNeedToRestoreParameters(bool *out, IDevice *device) override { AMS_UNUSED(out, device); R_THROW(powctl::ResultNotSupported()); } + virtual Result SetBatteryNeedToRestoreParameters(IDevice *device, bool en) override { AMS_UNUSED(device, en); R_THROW(powctl::ResultNotSupported()); } + + virtual Result IsBatteryI2cShutdownEnabled(bool *out, IDevice *device) override { AMS_UNUSED(out, device); R_THROW(powctl::ResultNotSupported()); } + virtual Result SetBatteryI2cShutdownEnabled(IDevice *device, bool en) override { AMS_UNUSED(device, en); R_THROW(powctl::ResultNotSupported()); } + + virtual Result IsBatteryPresent(bool *out, IDevice *device) override { AMS_UNUSED(out, device); R_THROW(powctl::ResultNotSupported()); } + + virtual Result GetBatteryCycles(int *out, IDevice *device) override { AMS_UNUSED(out, device); R_THROW(powctl::ResultNotSupported()); } + virtual Result SetBatteryCycles(IDevice *device, int cycles) override { AMS_UNUSED(device, cycles); R_THROW(powctl::ResultNotSupported()); } + + virtual Result GetBatteryAge(float *out_percent, IDevice *device) override { AMS_UNUSED(out_percent, device); R_THROW(powctl::ResultNotSupported()); } + + virtual Result GetBatteryTemperature(float *out_c, IDevice *device) override { AMS_UNUSED(out_c, device); R_THROW(powctl::ResultNotSupported()); } + virtual Result GetBatteryMaximumTemperature(float *out_c, IDevice *device) override { AMS_UNUSED(out_c, device); R_THROW(powctl::ResultNotSupported()); } + + virtual Result SetBatteryTemperatureMinimumAlertThreshold(IDevice *device, float c) override { AMS_UNUSED(device, c); R_THROW(powctl::ResultNotSupported()); } + virtual Result SetBatteryTemperatureMaximumAlertThreshold(IDevice *device, float c) override { AMS_UNUSED(device, c); R_THROW(powctl::ResultNotSupported()); } + + virtual Result GetBatteryVCell(int *out_mv, IDevice *device) override { AMS_UNUSED(out_mv, device); R_THROW(powctl::ResultNotSupported()); } + virtual Result GetBatteryAverageVCell(int *out_mv, IDevice *device) override { AMS_UNUSED(out_mv, device); R_THROW(powctl::ResultNotSupported()); } + + virtual Result GetBatteryAverageVCellTime(TimeSpan *out, IDevice *device) override { AMS_UNUSED(out, device); R_THROW(powctl::ResultNotSupported()); } + + virtual Result SetBatteryVoltageMinimumAlertThreshold(IDevice *device, int mv) override { AMS_UNUSED(device, mv); R_THROW(powctl::ResultNotSupported()); } + + virtual Result GetBatteryOpenCircuitVoltage(int *out_mv, IDevice *device) override { AMS_UNUSED(out_mv, device); R_THROW(powctl::ResultNotSupported()); } + + virtual Result SetBatteryVoltageMaximumAlertThreshold(IDevice *device, int mv) override { AMS_UNUSED(device, mv); R_THROW(powctl::ResultNotSupported()); } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_interrupt_event_handler.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_interrupt_event_handler.cpp new file mode 100644 index 00000000..b92e26c3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_interrupt_event_handler.cpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "powctl_interrupt_event_handler.hpp" + +namespace ams::powctl::impl::board::nintendo::nx { + + void ChargerInterruptEventHandler::SignalEvent(IDevice *device) { + /* TODO */ + AMS_UNUSED(device); + AMS_ABORT(); + } + + void BatteryInterruptEventHandler::SignalEvent(IDevice *device) { + /* TODO */ + AMS_UNUSED(device); + AMS_ABORT(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_interrupt_event_handler.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_interrupt_event_handler.hpp new file mode 100644 index 00000000..3c6d0c43 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_interrupt_event_handler.hpp @@ -0,0 +1,104 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../../../powctl_i_power_control_driver.hpp" + +namespace ams::powctl::impl::board::nintendo::nx { + + template<typename Derived> + class InterruptEventHandler : public ddsf::IEventHandler { + private: + IDevice *m_device; + gpio::GpioPadSession m_gpio_session; + os::SystemEventType m_gpio_system_event; + os::SdkMutex m_mutex; + public: + InterruptEventHandler(IDevice *dv) : IEventHandler(), m_device(dv), m_mutex() { + /* Initialize the gpio session. */ + Derived::Initialize(std::addressof(m_gpio_session), std::addressof(m_gpio_system_event)); + + /* Initialize ourselves as an event handler. */ + IEventHandler::Initialize(std::addressof(m_gpio_system_event)); + } + + os::SystemEventType *GetSystemEvent() { + return std::addressof(m_gpio_system_event); + } + + void SetInterruptEnabled(bool en) { + std::scoped_lock lk(m_mutex); + + gpio::SetInterruptEnable(std::addressof(m_gpio_session), en); + } + + virtual void HandleEvent() override final { + /* Acquire exclusive access to ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Clear our interrupt status. */ + gpio::ClearInterruptStatus(std::addressof(m_gpio_session)); + + /* Clear our system event. */ + os::ClearSystemEvent(std::addressof(m_gpio_system_event)); + + /* Signal the event. */ + static_cast<Derived *>(this)->SignalEvent(m_device); + } + }; + + class ChargerInterruptEventHandler : public InterruptEventHandler<ChargerInterruptEventHandler> { + friend class InterruptEventHandler<ChargerInterruptEventHandler>; + private: + static void Initialize(gpio::GpioPadSession *session, os::SystemEventType *event) { + /* Open the gpio session. */ + R_ABORT_UNLESS(gpio::OpenSession(session, gpio::DeviceCode_Bq24190Irq)); + + /* Configure the gpio session. */ + gpio::SetDirection(session, gpio::Direction_Input); + gpio::SetInterruptMode(session, gpio::InterruptMode_FallingEdge); + gpio::SetInterruptEnable(session, true); + + /* Bind the interrupt event. */ + R_ABORT_UNLESS(gpio::BindInterrupt(event, session)); + } + + void SignalEvent(IDevice *device); + public: + ChargerInterruptEventHandler(IDevice *dv) : InterruptEventHandler<ChargerInterruptEventHandler>(dv) { /* ... */ } + }; + + class BatteryInterruptEventHandler : public InterruptEventHandler<BatteryInterruptEventHandler> { + friend class InterruptEventHandler<BatteryInterruptEventHandler>; + private: + static void Initialize(gpio::GpioPadSession *session, os::SystemEventType *event) { + /* Open the gpio session. */ + R_ABORT_UNLESS(gpio::OpenSession(session, gpio::DeviceCode_BattMgicIrq)); + + /* Configure the gpio session. */ + gpio::SetDirection(session, gpio::Direction_Input); + gpio::SetInterruptMode(session, gpio::InterruptMode_LowLevel); + + /* Bind the interrupt event. */ + R_ABORT_UNLESS(gpio::BindInterrupt(event, session)); + } + + void SignalEvent(IDevice *device); + public: + BatteryInterruptEventHandler(IDevice *dv) : InterruptEventHandler<BatteryInterruptEventHandler>(dv) { /* ... */ } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_max17050_custom_parameters.inc b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_max17050_custom_parameters.inc new file mode 100644 index 00000000..c2ecec1e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_max17050_custom_parameters.inc @@ -0,0 +1,200 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* NOTE: This file is auto-generated by max17050_parameters_gen.py, do not edit manually. */ + +constexpr inline const CustomParameters CustomParameters0A = { + .relaxcfg = 0x203B, + .rcomp0 = 0x0053, + .tempco = 0x1C22, + .ichgterm = 0x0333, + .tgain = 0xE1F6, + .toff = 0x2BF2, + .vempty = 0xA05F, + .qresidual00 = 0x5786, + .qresidual10 = 0x3184, + .qresidual20 = 0x1E00, + .qresidual30 = 0x1502, + .fullcap = 0x2476, + .vffullcap = 0x2476, + .modeltbl = { + 0x9FF0, 0xAD30, 0xB5D0, 0xB9C0, 0xBAD0, 0xBBE0, 0xBC30, 0xBC90, + 0xBCE0, 0xBD40, 0xBE70, 0xC0E0, 0xC4E0, 0xC890, 0xCC90, 0xD0F0, + 0x0170, 0x0480, 0x0590, 0x0BE0, 0x0A00, 0x3C00, 0x3810, 0x3A00, + 0x3A30, 0x19F0, 0x0EF0, 0x0AF0, 0x0BD0, 0x07F0, 0x06F0, 0x06F0, + 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, + 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, + }, + .fullsocthr = 0x5F00, + .iavgempty = 0x1D2A, +}; + +constexpr inline const CustomParameters CustomParameters0R = { + .relaxcfg = 0x203B, + .rcomp0 = 0x0048, + .tempco = 0x2034, + .ichgterm = 0x0333, + .tgain = 0xE1F6, + .toff = 0x2BF2, + .vempty = 0xA05F, + .qresidual00 = 0x5A00, + .qresidual10 = 0x3B00, + .qresidual20 = 0x0F80, + .qresidual30 = 0x0B02, + .fullcap = 0x2466, + .vffullcap = 0x2466, + .modeltbl = { + 0x9C50, 0xAD90, 0xB270, 0xB6A0, 0xB8F0, 0xBB10, 0xBC00, 0xBD00, + 0xBD70, 0xBE70, 0xBF50, 0xC1F0, 0xC380, 0xC590, 0xC8E0, 0xD0B0, + 0x00D0, 0x0150, 0x0300, 0x0D00, 0x0E00, 0x1900, 0x2AC0, 0x2830, + 0x1760, 0x18F0, 0x0DF0, 0x0BC0, 0x0DF0, 0x0BF0, 0x06F0, 0x06F0, + 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, + 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, + }, + .fullsocthr = 0x5F00, + .iavgempty = 0x1D2A, +}; + +constexpr inline const CustomParameters CustomParameters0M = { + .relaxcfg = 0x203B, + .rcomp0 = 0x0085, + .tempco = 0x1625, + .ichgterm = 0x0333, + .tgain = 0xE1F6, + .toff = 0x2BF2, + .vempty = 0xA05F, + .qresidual00 = 0x3100, + .qresidual10 = 0x1B00, + .qresidual20 = 0x1000, + .qresidual30 = 0x0C81, + .fullcap = 0x227A, + .vffullcap = 0x227A, + .modeltbl = { + 0xA340, 0xB840, 0xB900, 0xBB70, 0xBC90, 0xBD20, 0xBDC0, 0xBEA0, + 0xBF70, 0xC030, 0xC210, 0xC3F0, 0xC800, 0xC9E0, 0xCCA0, 0xD090, + 0x0160, 0x3800, 0x0800, 0x1E00, 0x2550, 0x3060, 0x15D0, 0x1810, + 0x1490, 0x0B80, 0x0BF0, 0x0AF0, 0x0CB0, 0x06F0, 0x09D0, 0x09D0, + 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, + 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, + }, + .fullsocthr = 0x5F00, + .iavgempty = 0x1D2A, +}; + +constexpr inline const CustomParameters CustomParameters1 = { + .relaxcfg = 0x203B, + .rcomp0 = 0x0040, + .tempco = 0x1624, + .ichgterm = 0x0333, + .tgain = 0xE1F6, + .toff = 0x2BF2, + .vempty = 0xA05F, + .qresidual00 = 0x4690, + .qresidual10 = 0x2605, + .qresidual20 = 0x1605, + .qresidual30 = 0x0F05, + .fullcap = 0x1AE4, + .vffullcap = 0x1AE4, + .modeltbl = { + 0x8B50, 0x9C20, 0xACF0, 0xB160, 0xB3A0, 0xB5B0, 0xB950, 0xBBE0, + 0xBDC0, 0xBEF0, 0xC140, 0xC250, 0xC600, 0xC960, 0xCCE0, 0xD060, + 0x0070, 0x00F0, 0x0440, 0x0400, 0x0500, 0x0400, 0x0D00, 0x3270, + 0x0FB0, 0x0AF0, 0x10F0, 0x0CE0, 0x09E0, 0x07F0, 0x06F0, 0x06F0, + 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, + 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, + }, + .fullsocthr = 0x5F00, + .iavgempty = 0x1584, +}; + +constexpr inline const CustomParameters CustomParameters2A = { + .relaxcfg = 0x203B, + .rcomp0 = 0x004A, + .tempco = 0x1D23, + .ichgterm = 0x0333, + .tgain = 0xE1F6, + .toff = 0x2BF2, + .vempty = 0xA05F, + .qresidual00 = 0x4000, + .qresidual10 = 0x1E80, + .qresidual20 = 0x0D83, + .qresidual30 = 0x0783, + .fullcap = 0x1C20, + .vffullcap = 0x1C20, + .modeltbl = { + 0x8040, 0x9A30, 0xB430, 0xB770, 0xBAB0, 0xBBC0, 0xBD00, 0xBE50, + 0xBF70, 0xC0D0, 0xC300, 0xC590, 0xC960, 0xCD40, 0xD1F0, 0xD5C0, + 0x0040, 0x0060, 0x0510, 0x0D30, 0x16C0, 0x2160, 0x1380, 0x1A10, + 0x0EC0, 0x0CE0, 0x08F0, 0x0940, 0x0920, 0x06F0, 0x06C0, 0x06C0, + 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, + 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, + }, + .fullsocthr = 0x5500, + .iavgempty = 0x1680, +}; + +constexpr inline const CustomParameters CustomParameters2R = { + .relaxcfg = 0x203B, + .rcomp0 = 0x004C, + .tempco = 0x2D32, + .ichgterm = 0x0333, + .tgain = 0xE1F6, + .toff = 0x2BF2, + .vempty = 0xA05F, + .qresidual00 = 0x5900, + .qresidual10 = 0x2900, + .qresidual20 = 0x1100, + .qresidual30 = 0x0B00, + .fullcap = 0x1CCE, + .vffullcap = 0x1CCE, + .modeltbl = { + 0x8E10, 0x9FC0, 0xA880, 0xB750, 0xBA10, 0xBB30, 0xBD20, 0xBE80, + 0xC0A0, 0xC350, 0xC670, 0xC8C0, 0xCCF0, 0xD050, 0xD140, 0xD5F0, + 0x0020, 0x00D0, 0x0200, 0x0E00, 0x1300, 0x1B00, 0x1930, 0x1150, + 0x0BF0, 0x07E0, 0x0AD0, 0x06F0, 0x07F0, 0x0EF0, 0x04F0, 0x04F0, + 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, + 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, + }, + .fullsocthr = 0x5500, + .iavgempty = 0x170B, +}; + +constexpr inline const CustomParameters CustomParameters2M = { + .relaxcfg = 0x203B, + .rcomp0 = 0x0049, + .tempco = 0x222A, + .ichgterm = 0x0333, + .tgain = 0xE1F6, + .toff = 0x2BF2, + .vempty = 0xA05F, + .qresidual00 = 0x4F00, + .qresidual10 = 0x2680, + .qresidual20 = 0x1205, + .qresidual30 = 0x0C87, + .fullcap = 0x1C68, + .vffullcap = 0x1C68, + .modeltbl = { + 0x8E40, 0xB570, 0xB8F0, 0xBB00, 0xBC20, 0xBCC0, 0xBE30, 0xBFE0, + 0xC200, 0xC400, 0xC720, 0xCB50, 0xCF00, 0xD100, 0xD480, 0xD5C0, + 0x00C0, 0x0C00, 0x0A10, 0x1800, 0x2C00, 0x1C10, 0x12D0, 0x09F0, + 0x0AF0, 0x0850, 0x09F0, 0x06F0, 0x06B0, 0x07E0, 0x01D0, 0x01D0, + 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, + 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, + }, + .fullsocthr = 0x5500, + .iavgempty = 0x16B9, +}; + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_max17050_driver.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_max17050_driver.cpp new file mode 100644 index 00000000..76d041cd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_max17050_driver.cpp @@ -0,0 +1,758 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "powctl_max17050_driver.hpp" + +namespace ams::powctl::impl::board::nintendo::nx { + + namespace max17050 { + + constexpr inline u8 Status = 0x00; + constexpr inline u8 VAlrtThreshold = 0x01; + constexpr inline u8 TAlrtThreshold = 0x02; + constexpr inline u8 SocAlrtThreshold = 0x03; + constexpr inline u8 AtRate = 0x04; + constexpr inline u8 RemCapRep = 0x05; + constexpr inline u8 SocRep = 0x06; + constexpr inline u8 Age = 0x07; + constexpr inline u8 Temperature = 0x08; + constexpr inline u8 VCell = 0x09; + constexpr inline u8 Current = 0x0A; + constexpr inline u8 AverageCurrent = 0x0B; + + constexpr inline u8 SocMix = 0x0D; + constexpr inline u8 SocAv = 0x0E; + constexpr inline u8 RemCapMix = 0x0F; + constexpr inline u8 FullCap = 0x10; + constexpr inline u8 Tte = 0x11; + constexpr inline u8 QResidual00 = 0x12; + constexpr inline u8 FullSocThr = 0x13; + + + constexpr inline u8 AverageTemp = 0x16; + constexpr inline u8 Cycles = 0x17; + constexpr inline u8 DesignCap = 0x18; + constexpr inline u8 AverageVCell = 0x19; + constexpr inline u8 MaxMinTemp = 0x1A; + constexpr inline u8 MaxMinVoltage = 0x1B; + constexpr inline u8 MaxMinCurrent = 0x1C; + constexpr inline u8 Config = 0x1D; + constexpr inline u8 IChgTerm = 0x1E; + constexpr inline u8 RemCapAv = 0x1F; + + constexpr inline u8 Version = 0x21; + constexpr inline u8 QResidual10 = 0x22; + constexpr inline u8 FullCapNom = 0x23; + constexpr inline u8 TempNom = 0x24; + constexpr inline u8 TempLim = 0x25; + + constexpr inline u8 Ain = 0x27; + constexpr inline u8 LearnCfg = 0x28; + constexpr inline u8 FilterCfg = 0x29; + constexpr inline u8 RelaxCfg = 0x2A; + constexpr inline u8 MiscCfg = 0x2B; + constexpr inline u8 TGain = 0x2C; + constexpr inline u8 TOff = 0x2D; + constexpr inline u8 CGain = 0x2E; + constexpr inline u8 COff = 0x2F; + + + constexpr inline u8 QResidual20 = 0x32; + + + constexpr inline u8 FullCap0 = 0x35; + constexpr inline u8 IAvgEmpty = 0x36; + constexpr inline u8 FCtc = 0x37; + constexpr inline u8 RComp0 = 0x38; + constexpr inline u8 TempCo = 0x39; + constexpr inline u8 VEmpty = 0x3A; + + + constexpr inline u8 FStat = 0x3D; + constexpr inline u8 Timer = 0x3E; + constexpr inline u8 ShdnTimer = 0x3F; + + + constexpr inline u8 QResidual30 = 0x42; + + + constexpr inline u8 DQAcc = 0x45; + constexpr inline u8 DPAcc = 0x46; + + constexpr inline u8 SocVf0 = 0x48; + + constexpr inline u8 Qh0 = 0x4C; + constexpr inline u8 Qh = 0x4D; + + constexpr inline u8 SocVfAccess = 0x60; + + constexpr inline u8 ModelAccess0 = 0x62; + constexpr inline u8 ModelAccess1 = 0x63; + + constexpr inline u8 ModelChrTblStart = 0x80; + constexpr inline u8 ModelChrTblEnd = 0xB0; + + + constexpr inline u8 VFocV = 0xFB; + constexpr inline u8 SocVf = 0xFF; + + constexpr inline size_t ModelChrTblSize = ModelChrTblEnd - ModelChrTblStart; + + namespace { + + struct CustomParameters { + u16 relaxcfg; + u16 rcomp0; + u16 tempco; + u16 ichgterm; + u16 tgain; + u16 toff; + u16 vempty; + u16 qresidual00; + u16 qresidual10; + u16 qresidual20; + u16 qresidual30; + u16 fullcap; + u16 vffullcap; + u16 modeltbl[ModelChrTblSize]; + u16 fullsocthr; + u16 iavgempty; + }; + + #include "powctl_max17050_custom_parameters.inc" + + const CustomParameters &GetCustomParameters(const char *battery_vendor, u8 battery_version) { + if (battery_version == 2) { + if (battery_vendor[7] == 'M') { + return CustomParameters2M; + } else if (battery_vendor[7] == 'R') { + return CustomParameters2R; + } else /* if (battery_vendor[7] == 'A') */ { + return CustomParameters2A; + } + } else if (battery_version == 1) { + return CustomParameters1; + } else /* if (battery_version == 0) */ { + if (battery_vendor[7] == 'M') { + return CustomParameters0M; + } else if (battery_vendor[7] == 'R') { + return CustomParameters0R; + } else /* if (battery_vendor[7] == 'A') */ { + return CustomParameters0A; + } + } + } + + } + + } + + namespace { + + ALWAYS_INLINE Result ReadWriteRegister(const i2c::I2cSession &session, u8 address, u16 mask, u16 value) { + /* Read the current value. */ + u16 cur_val; + R_TRY(i2c::ReadSingleRegister(session, address, std::addressof(cur_val))); + + /* Update the value. */ + const u16 new_val = (cur_val & ~mask) | (value & mask); + R_TRY(i2c::WriteSingleRegister(session, address, new_val)); + + R_SUCCEED(); + } + + ALWAYS_INLINE Result ReadRegister(const i2c::I2cSession &session, u8 address, u16 *out) { + R_RETURN(i2c::ReadSingleRegister(session, address, out)); + } + + ALWAYS_INLINE Result WriteRegister(const i2c::I2cSession &session, u8 address, u16 val) { + R_RETURN(i2c::WriteSingleRegister(session, address, val)); + } + + ALWAYS_INLINE bool WriteValidateRegister(const i2c::I2cSession &session, u8 address, u16 val) { + /* Write the value. */ + R_ABORT_UNLESS(WriteRegister(session, address, val)); + + /* Give it time to take. */ + os::SleepThread(TimeSpan::FromMilliSeconds(3)); + + /* Read it back. */ + u16 new_val; + R_ABORT_UNLESS(ReadRegister(session, address, std::addressof(new_val))); + + return new_val == val; + } + + ALWAYS_INLINE Result ReadWriteValidateRegister(const i2c::I2cSession &session, u8 address, u16 mask, u16 value) { + /* Read the current value. */ + u16 cur_val; + R_TRY(i2c::ReadSingleRegister(session, address, std::addressof(cur_val))); + + /* Update the value. */ + const u16 new_val = (cur_val & ~mask) | (value & mask); + while (!WriteValidateRegister(session, address, new_val)) { /* ... */ } + + R_SUCCEED(); + } + + double CoerceToDouble(u64 value) { + static_assert(sizeof(value) == sizeof(double)); + + double d; + __builtin_memcpy(std::addressof(d), std::addressof(value), sizeof(d)); + return d; + } + + double ExponentiateTwoToPower(s16 exponent, double scale) { + if (exponent >= 1024) { + exponent = exponent - 1023; + scale = scale * 8.98846567e307; + if (exponent >= 1024) { + exponent = std::min<s16>(exponent, 2046) - 1023; + scale = scale * 8.98846567e307; + } + } else if (exponent <= -1023) { + exponent = exponent + 969; + scale = scale * 2.00416836e-292; + if (exponent <= -1023) { + exponent = std::max<s16>(exponent, -1991) + 969; + scale = scale * 2.00416836e-292; + } + } + return scale * CoerceToDouble(static_cast<u64>(exponent + 1023) << 52); + } + + } + + Result Max17050Driver::InitializeSession(const char *battery_vendor, u8 battery_version) { + /* Get the custom parameters. */ + const auto ¶ms = max17050::GetCustomParameters(battery_vendor, battery_version); + + /* We only want to write the parameters on power on reset. */ + R_SUCCEED_IF(!this->IsPowerOnReset()); + + /* Set that we need to restore parameters. */ + R_TRY(this->SetNeedToRestoreParameters(true)); + + /* Wait for our configuration to take. */ + os::SleepThread(TimeSpan::FromMilliSeconds(500)); + + /* Write initial config. */ + R_TRY(WriteRegister(m_i2c_session, max17050::Config, 0x7210)); + + /* Write initial filter config. */ + R_TRY(WriteRegister(m_i2c_session, max17050::FilterCfg, 0x8784)); + + /* Write relax config. */ + R_TRY(WriteRegister(m_i2c_session, max17050::RelaxCfg, params.relaxcfg)); + + /* Write initial learn config. */ + R_TRY(WriteRegister(m_i2c_session, max17050::LearnCfg, 0x2603)); + + /* Write fullsocthr. */ + R_TRY(WriteRegister(m_i2c_session, max17050::FullSocThr, params.fullsocthr)); + + /* Write iavgempty. */ + R_TRY(WriteRegister(m_i2c_session, max17050::IAvgEmpty, params.iavgempty)); + + /* Unlock model table, write model table. */ + do { + R_TRY(this->UnlockModelTable()); + R_TRY(this->SetModelTable(params.modeltbl)); + } while (!this->IsModelTableSet(params.modeltbl)); + + /* Lock the model table, trying up to ten times. */ + { + size_t i = 0; + while (true) { + ++i; + + R_TRY(this->LockModelTable()); + + if (this->IsModelTableLocked()) { + break; + } + + R_SUCCEED_IF(i >= 10); + } + } + + /* Write and validate rcomp0 */ + while (!WriteValidateRegister(m_i2c_session, max17050::RComp0, params.rcomp0)) { /* ... */ } + + /* Write and validate tempco */ + while (!WriteValidateRegister(m_i2c_session, max17050::TempCo, params.tempco)) { /* ... */ } + + /* Write ichgterm. */ + R_TRY(WriteRegister(m_i2c_session, max17050::IChgTerm, params.ichgterm)); + + /* Write tgain. */ + R_TRY(WriteRegister(m_i2c_session, max17050::TGain, params.tgain)); + + /* Write toff. */ + R_TRY(WriteRegister(m_i2c_session, max17050::TOff, params.toff)); + + /* Write and validate vempty. */ + while (!WriteValidateRegister(m_i2c_session, max17050::VEmpty, params.vempty)) { /* ... */ } + + /* Write and validate qresidual. */ + while (!WriteValidateRegister(m_i2c_session, max17050::QResidual00, params.qresidual00)) { /* ... */ } + while (!WriteValidateRegister(m_i2c_session, max17050::QResidual10, params.qresidual10)) { /* ... */ } + while (!WriteValidateRegister(m_i2c_session, max17050::QResidual20, params.qresidual20)) { /* ... */ } + while (!WriteValidateRegister(m_i2c_session, max17050::QResidual30, params.qresidual30)) { /* ... */ } + + /* Write capacity parameters. */ + while (!WriteValidateRegister(m_i2c_session, max17050::FullCap, params.fullcap)) { /* ... */ } + R_TRY(WriteRegister(m_i2c_session, max17050::DesignCap, params.vffullcap)); + while (!WriteValidateRegister(m_i2c_session, max17050::FullCapNom, params.vffullcap)) { /* ... */ } + + /* Give some time for configuration to take. */ + os::SleepThread(TimeSpan::FromMilliSeconds(350)); + + /* Write vfsoc to vfsoc0, qh, to qh0. */ + u16 vfsoc, qh; + { + R_TRY(ReadRegister(m_i2c_session, max17050::SocVf, std::addressof(vfsoc))); + R_TRY(this->UnlockVoltageFuelGauge()); + while (!WriteValidateRegister(m_i2c_session, max17050::SocVf0, vfsoc)) { /* ... */ } + R_TRY(ReadRegister(m_i2c_session, max17050::Qh, std::addressof(qh))); + R_TRY(WriteRegister(m_i2c_session, max17050::Qh0, qh)); + R_TRY(this->LockVoltageFuelGauge()); + } + + /* Reset cycles. */ + while (!WriteValidateRegister(m_i2c_session, max17050::Cycles, 0x0060)) { /* ... */ } + + /* Load new capacity parameters. */ + const u16 remcap = static_cast<u16>((vfsoc * params.vffullcap) / 0x6400); + const u16 repcap = static_cast<u16>(remcap * (params.fullcap / params.vffullcap)); + const u16 dpacc = 0x0C80; + const u16 dqacc = params.vffullcap / 0x10; + while (!WriteValidateRegister(m_i2c_session, max17050::RemCapMix, remcap)) { /* ... */ } + while (!WriteValidateRegister(m_i2c_session, max17050::RemCapRep, repcap)) { /* ... */ } + while (!WriteValidateRegister(m_i2c_session, max17050::DPAcc, dpacc)) { /* ... */ } + while (!WriteValidateRegister(m_i2c_session, max17050::DQAcc, dqacc)) { /* ... */ } + + /* Write capacity parameters. */ + while (!WriteValidateRegister(m_i2c_session, max17050::FullCap, params.fullcap)) { /* ... */ } + R_TRY(WriteRegister(m_i2c_session, max17050::DesignCap, params.vffullcap)); + while (!WriteValidateRegister(m_i2c_session, max17050::FullCapNom, params.vffullcap)) { /* ... */ } + + /* Write soc rep. */ + R_TRY(WriteRegister(m_i2c_session, max17050::SocRep, vfsoc)); + + /* Clear power on reset. */ + R_TRY(ReadWriteValidateRegister(m_i2c_session, max17050::Status, 0x0002, 0x0000)); + + /* Set cgain. */ + R_TRY(WriteRegister(m_i2c_session, max17050::CGain, 0x7FFF)); + + R_SUCCEED(); + } + + Result Max17050Driver::SetMaximumShutdownTimerThreshold() { + R_RETURN(WriteRegister(m_i2c_session, max17050::ShdnTimer, 0xE000)); + } + + Result Max17050Driver::SetAlertByChargePercentage() { + R_RETURN(ReadWriteRegister(m_i2c_session, max17050::MiscCfg, 0x0003, 0x0000)); + } + + Result Max17050Driver::SetAlertByVoltageFuelGaugePercentage() { + R_RETURN(ReadWriteRegister(m_i2c_session, max17050::MiscCfg, 0x0003, 0x0003)); + } + + bool Max17050Driver::IsPowerOnReset() { + /* Get the register. */ + u16 val; + R_ABORT_UNLESS(ReadRegister(m_i2c_session, max17050::Status, std::addressof(val))); + + /* Extract the value. */ + return (val & 0x0002) != 0; + } + + Result Max17050Driver::LockVoltageFuelGauge() { + R_RETURN(WriteRegister(m_i2c_session, max17050::SocVfAccess, 0x0000)); + } + + Result Max17050Driver::UnlockVoltageFuelGauge() { + R_RETURN(WriteRegister(m_i2c_session, max17050::SocVfAccess, 0x0080)); + } + + Result Max17050Driver::LockModelTable() { + R_TRY(WriteRegister(m_i2c_session, max17050::ModelAccess0, 0x0000)); + R_TRY(WriteRegister(m_i2c_session, max17050::ModelAccess1, 0x0000)); + R_SUCCEED(); + } + + Result Max17050Driver::UnlockModelTable() { + R_TRY(WriteRegister(m_i2c_session, max17050::ModelAccess0, 0x0059)); + R_TRY(WriteRegister(m_i2c_session, max17050::ModelAccess1, 0x00C4)); + R_SUCCEED(); + } + + bool Max17050Driver::IsModelTableLocked() { + for (size_t i = 0; i < max17050::ModelChrTblSize; ++i) { + u16 val; + R_ABORT_UNLESS(ReadRegister(m_i2c_session, max17050::ModelChrTblStart + i, std::addressof(val))); + + if (val != 0) { + return false; + } + } + + return true; + } + + Result Max17050Driver::SetModelTable(const u16 *model_table) { + for (size_t i = 0; i < max17050::ModelChrTblSize; ++i) { + R_TRY(WriteRegister(m_i2c_session, max17050::ModelChrTblStart + i, model_table[i])); + } + + R_SUCCEED(); + } + + bool Max17050Driver::IsModelTableSet(const u16 *model_table) { + for (size_t i = 0; i < max17050::ModelChrTblSize; ++i) { + u16 val; + R_ABORT_UNLESS(ReadRegister(m_i2c_session, max17050::ModelChrTblStart + i, std::addressof(val))); + + if (val != model_table[i]) { + return false; + } + } + + return true; + } + + Result Max17050Driver::ReadInternalState() { + R_TRY(ReadRegister(m_i2c_session, max17050::RComp0, std::addressof(m_internal_state.rcomp0))); + R_TRY(ReadRegister(m_i2c_session, max17050::TempCo, std::addressof(m_internal_state.tempco))); + R_TRY(ReadRegister(m_i2c_session, max17050::FullCap, std::addressof(m_internal_state.fullcap))); + R_TRY(ReadRegister(m_i2c_session, max17050::Cycles, std::addressof(m_internal_state.cycles))); + R_TRY(ReadRegister(m_i2c_session, max17050::FullCapNom, std::addressof(m_internal_state.fullcapnom))); + R_TRY(ReadRegister(m_i2c_session, max17050::IAvgEmpty, std::addressof(m_internal_state.iavgempty))); + R_TRY(ReadRegister(m_i2c_session, max17050::QResidual00, std::addressof(m_internal_state.qresidual00))); + R_TRY(ReadRegister(m_i2c_session, max17050::QResidual10, std::addressof(m_internal_state.qresidual10))); + R_TRY(ReadRegister(m_i2c_session, max17050::QResidual20, std::addressof(m_internal_state.qresidual20))); + R_TRY(ReadRegister(m_i2c_session, max17050::QResidual30, std::addressof(m_internal_state.qresidual30))); + R_SUCCEED(); + } + + Result Max17050Driver::WriteInternalState() { + while (!WriteValidateRegister(m_i2c_session, max17050::RComp0, m_internal_state.rcomp0)) { /* ... */ } + while (!WriteValidateRegister(m_i2c_session, max17050::TempCo, m_internal_state.tempco)) { /* ... */ } + while (!WriteValidateRegister(m_i2c_session, max17050::FullCapNom, m_internal_state.fullcapnom)) { /* ... */ } + while (!WriteValidateRegister(m_i2c_session, max17050::IAvgEmpty, m_internal_state.iavgempty)) { /* ... */ } + while (!WriteValidateRegister(m_i2c_session, max17050::QResidual00, m_internal_state.qresidual00)) { /* ... */ } + while (!WriteValidateRegister(m_i2c_session, max17050::QResidual10, m_internal_state.qresidual10)) { /* ... */ } + while (!WriteValidateRegister(m_i2c_session, max17050::QResidual20, m_internal_state.qresidual20)) { /* ... */ } + while (!WriteValidateRegister(m_i2c_session, max17050::QResidual30, m_internal_state.qresidual30)) { /* ... */ } + + os::SleepThread(TimeSpan::FromMilliSeconds(350)); + + u16 fullcap0, socmix; + R_TRY(ReadRegister(m_i2c_session, max17050::FullCap0, std::addressof(fullcap0))); + R_TRY(ReadRegister(m_i2c_session, max17050::SocMix, std::addressof(socmix))); + + while (!WriteValidateRegister(m_i2c_session, max17050::RemCapMix, static_cast<u16>((fullcap0 * socmix) / 0x6400))) { /* ... */ } + while (!WriteValidateRegister(m_i2c_session, max17050::FullCap, m_internal_state.fullcap)) { /* ... */ } + while (!WriteValidateRegister(m_i2c_session, max17050::DPAcc, 0x0C80)) { /* ... */ } + while (!WriteValidateRegister(m_i2c_session, max17050::DQAcc, m_internal_state.fullcapnom / 0x10)) { /* ... */ } + + os::SleepThread(TimeSpan::FromMilliSeconds(350)); + + while (!WriteValidateRegister(m_i2c_session, max17050::Cycles, m_internal_state.cycles)) { /* ... */ } + if (m_internal_state.cycles >= 0x100) { + while (!WriteValidateRegister(m_i2c_session, max17050::LearnCfg, 0x2673)) { /* ... */ } + } + + R_SUCCEED(); + } + + Result Max17050Driver::GetChargePercentage(double *out) { + /* Validate parameters. */ + AMS_ABORT_UNLESS(out != nullptr); + + /* Read the value. */ + u16 val; + R_TRY(ReadRegister(m_i2c_session, max17050::SocRep, std::addressof(val))); + + /* Set output. */ + *out = static_cast<double>(val) * 0.00390625; + R_SUCCEED(); + } + + Result Max17050Driver::GetVoltageFuelGaugePercentage(double *out) { + /* Validate parameters. */ + AMS_ABORT_UNLESS(out != nullptr); + + /* Read the value. */ + u16 val; + R_TRY(ReadRegister(m_i2c_session, max17050::SocVf, std::addressof(val))); + + /* Set output. */ + *out = static_cast<double>(val) * 0.00390625; + R_SUCCEED(); + } + + Result Max17050Driver::GetFullCapacity(double *out, double sense_resistor) { + /* Validate parameters. */ + AMS_ABORT_UNLESS(out != nullptr); + AMS_ABORT_UNLESS(sense_resistor > 0.0); + + /* Read the values. */ + u16 cgain, fullcap; + R_TRY(ReadRegister(m_i2c_session, max17050::CGain, std::addressof(cgain))); + R_TRY(ReadRegister(m_i2c_session, max17050::FullCap, std::addressof(fullcap))); + + /* Set output. */ + *out = ((static_cast<double>(fullcap) * 0.005) / sense_resistor) / (static_cast<double>(cgain) * 0.0000610351562); + R_SUCCEED(); + } + + Result Max17050Driver::GetRemainingCapacity(double *out, double sense_resistor) { + /* Validate parameters. */ + AMS_ABORT_UNLESS(out != nullptr); + AMS_ABORT_UNLESS(sense_resistor > 0.0); + + /* Read the values. */ + u16 cgain, remcap; + R_TRY(ReadRegister(m_i2c_session, max17050::CGain, std::addressof(cgain))); + R_TRY(ReadRegister(m_i2c_session, max17050::RemCapRep, std::addressof(remcap))); + + /* Set output. */ + *out = ((static_cast<double>(remcap) * 0.005) / sense_resistor) / (static_cast<double>(cgain) * 0.0000610351562); + R_SUCCEED(); + } + + Result Max17050Driver::SetChargePercentageMinimumAlertThreshold(int percentage) { + R_TRY(this->SetAlertByChargePercentage()); + R_RETURN(ReadWriteRegister(m_i2c_session, max17050::SocAlrtThreshold, 0x00FF, static_cast<u8>(percentage))); + } + + Result Max17050Driver::SetChargePercentageMaximumAlertThreshold(int percentage) { + R_TRY(this->SetAlertByChargePercentage()); + R_RETURN(ReadWriteRegister(m_i2c_session, max17050::SocAlrtThreshold, 0xFF00, static_cast<u16>(static_cast<u8>(percentage)) << 8)); + } + + Result Max17050Driver::SetVoltageFuelGaugePercentageMinimumAlertThreshold(int percentage) { + R_TRY(this->SetAlertByVoltageFuelGaugePercentage()); + R_RETURN(ReadWriteRegister(m_i2c_session, max17050::SocAlrtThreshold, 0x00FF, static_cast<u8>(percentage))); + } + + Result Max17050Driver::SetVoltageFuelGaugePercentageMaximumAlertThreshold(int percentage) { + R_TRY(this->SetAlertByVoltageFuelGaugePercentage()); + R_RETURN(ReadWriteRegister(m_i2c_session, max17050::SocAlrtThreshold, 0xFF00, static_cast<u16>(static_cast<u8>(percentage)) << 8)); + } + + Result Max17050Driver::SetFullChargeThreshold(double percentage) { + /* Convert percentage from double to signed fixed-point with 8 fractional bits. */ + const u16 val = static_cast<u16>(static_cast<s16>(percentage * (1 << 8))); + + /* Set the threshold. */ + R_RETURN(WriteRegister(m_i2c_session, max17050::FullSocThr, val)); + } + + Result Max17050Driver::GetAverageCurrent(double *out, double sense_resistor) { + /* Validate parameters. */ + AMS_ABORT_UNLESS(out != nullptr); + AMS_ABORT_UNLESS(sense_resistor > 0.0); + + /* Read the values. */ + u16 cgain, coff, avg_current; + R_TRY(ReadRegister(m_i2c_session, max17050::CGain, std::addressof(cgain))); + R_TRY(ReadRegister(m_i2c_session, max17050::COff, std::addressof(coff))); + R_TRY(ReadRegister(m_i2c_session, max17050::AverageCurrent, std::addressof(avg_current))); + + /* Set output. */ + *out = (((static_cast<double>(avg_current) - (static_cast<double>(coff) + static_cast<double>(coff))) / (static_cast<double>(cgain) * 0.0000610351562)) * 1.5625) / (sense_resistor * 1000.0); + R_SUCCEED(); + } + + Result Max17050Driver::GetCurrent(double *out, double sense_resistor) { + /* Validate parameters. */ + AMS_ABORT_UNLESS(out != nullptr); + AMS_ABORT_UNLESS(sense_resistor > 0.0); + + /* Read the values. */ + u16 cgain, coff, current; + R_TRY(ReadRegister(m_i2c_session, max17050::CGain, std::addressof(cgain))); + R_TRY(ReadRegister(m_i2c_session, max17050::COff, std::addressof(coff))); + R_TRY(ReadRegister(m_i2c_session, max17050::Current, std::addressof(current))); + + /* Set output. */ + *out = (((static_cast<double>(current) - (static_cast<double>(coff) + static_cast<double>(coff))) / (static_cast<double>(cgain) * 0.0000610351562)) * 1.5625) / (sense_resistor * 1000.0); + R_SUCCEED(); + } + + Result Max17050Driver::GetNeedToRestoreParameters(bool *out) { + /* Get the register. */ + u16 val; + R_TRY(ReadRegister(m_i2c_session, max17050::MiscCfg, std::addressof(val))); + + /* Extract the value. */ + *out = (val & 0x8000) != 0; + R_SUCCEED(); + } + + Result Max17050Driver::SetNeedToRestoreParameters(bool en) { + R_RETURN(ReadWriteRegister(m_i2c_session, max17050::MiscCfg, 0x8000, en ? 0x8000 : 0)); + } + + Result Max17050Driver::IsI2cShutdownEnabled(bool *out) { + /* Get the register. */ + u16 val; + R_TRY(ReadRegister(m_i2c_session, max17050::Config, std::addressof(val))); + + /* Extract the value. */ + *out = (val & 0x0040) != 0; + R_SUCCEED(); + } + + Result Max17050Driver::SetI2cShutdownEnabled(bool en) { + R_RETURN(ReadWriteRegister(m_i2c_session, max17050::Config, 0x0040, en ? 0x0040 : 0)); + } + + Result Max17050Driver::GetStatus(u16 *out) { + /* Validate parameters. */ + AMS_ABORT_UNLESS(out != nullptr); + + R_RETURN(ReadRegister(m_i2c_session, max17050::Status, out)); + } + + Result Max17050Driver::GetCycles(u16 *out) { + /* Get the register. */ + u16 val; + R_TRY(ReadRegister(m_i2c_session, max17050::Cycles, std::addressof(val))); + + /* Extract the value. */ + *out = std::max<u16>(val, 0x60) - 0x60; + R_SUCCEED(); + } + + Result Max17050Driver::ResetCycles() { + R_RETURN(WriteRegister(m_i2c_session, max17050::Cycles, 0x0060)); + } + + Result Max17050Driver::GetAge(double *out) { + /* Validate parameters. */ + AMS_ABORT_UNLESS(out != nullptr); + + /* Read the value. */ + u16 val; + R_TRY(ReadRegister(m_i2c_session, max17050::Age, std::addressof(val))); + + /* Set output. */ + *out = static_cast<double>(val) * 0.00390625; + R_SUCCEED(); + } + + Result Max17050Driver::GetTemperature(double *out) { + /* Validate parameters. */ + AMS_ABORT_UNLESS(out != nullptr); + + /* Read the value. */ + u16 val; + R_TRY(ReadRegister(m_i2c_session, max17050::Temperature, std::addressof(val))); + + /* Set output. */ + *out = static_cast<double>(val) * 0.00390625; + R_SUCCEED(); + } + + Result Max17050Driver::GetMaximumTemperature(u8 *out) { + /* Validate parameters. */ + AMS_ABORT_UNLESS(out != nullptr); + + /* Read the value. */ + u16 val; + R_TRY(ReadRegister(m_i2c_session, max17050::MaxMinTemp, std::addressof(val))); + + /* Set output. */ + *out = static_cast<u8>(val >> 8); + R_SUCCEED(); + } + + Result Max17050Driver::SetTemperatureMinimumAlertThreshold(int c) { + R_RETURN(ReadWriteRegister(m_i2c_session, max17050::TAlrtThreshold, 0x00FF, static_cast<u8>(c))); + } + + Result Max17050Driver::SetTemperatureMaximumAlertThreshold(int c) { + R_RETURN(ReadWriteRegister(m_i2c_session, max17050::TAlrtThreshold, 0xFF00, static_cast<u16>(static_cast<u8>(c)) << 8)); + } + + Result Max17050Driver::GetVCell(int *out) { + /* Validate parameters. */ + AMS_ABORT_UNLESS(out != nullptr); + + /* Read the value. */ + u16 val; + R_TRY(ReadRegister(m_i2c_session, max17050::VCell, std::addressof(val))); + + /* Set output. */ + *out = (625 * (val >> 3)) / 1000; + R_SUCCEED(); + } + + Result Max17050Driver::GetAverageVCell(int *out) { + /* Validate parameters. */ + AMS_ABORT_UNLESS(out != nullptr); + + /* Read the value. */ + u16 val; + R_TRY(ReadRegister(m_i2c_session, max17050::AverageVCell, std::addressof(val))); + + /* Set output. */ + *out = (625 * (val >> 3)) / 1000; + R_SUCCEED(); + } + + Result Max17050Driver::GetAverageVCellTime(double *out) { + /* Validate parameters. */ + AMS_ABORT_UNLESS(out != nullptr); + + /* Read the value. */ + u16 val; + R_TRY(ReadRegister(m_i2c_session, max17050::FilterCfg, std::addressof(val))); + + /* Set output. */ + *out = 175.8 * ExponentiateTwoToPower(6 + ((val >> 4) & 7), 1.0); + R_SUCCEED(); + } + + Result Max17050Driver::GetOpenCircuitVoltage(int *out) { + /* Validate parameters. */ + AMS_ABORT_UNLESS(out != nullptr); + + /* Read the value. */ + u16 val; + R_TRY(ReadRegister(m_i2c_session, max17050::VFocV, std::addressof(val))); + + /* Set output. */ + *out = (1250 * (val >> 4)) / 1000; + R_SUCCEED(); + } + + Result Max17050Driver::SetVoltageMinimumAlertThreshold(int mv) { + R_RETURN(ReadWriteRegister(m_i2c_session, max17050::VAlrtThreshold, 0x00FF, static_cast<u8>(util::DivideUp(mv, 20)))); + } + + Result Max17050Driver::SetVoltageMaximumAlertThreshold(int mv) { + R_RETURN(ReadWriteRegister(m_i2c_session, max17050::VAlrtThreshold, 0xFF00, static_cast<u16>(static_cast<u8>(mv / 20)) << 8)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_max17050_driver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_max17050_driver.hpp new file mode 100644 index 00000000..0041354d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_max17050_driver.hpp @@ -0,0 +1,150 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::powctl::impl::board::nintendo::nx { + + namespace max17050 { + + struct InternalState { + u16 rcomp0; + u16 tempco; + u16 fullcap; + u16 cycles; + u16 fullcapnom; + u16 iavgempty; + u16 qresidual00; + u16 qresidual10; + u16 qresidual20; + u16 qresidual30; + }; + + } + + class Max17050Driver { + private: + os::SdkMutex m_mutex; + int m_init_count; + i2c::I2cSession m_i2c_session; + max17050::InternalState m_internal_state; + private: + Result InitializeSession(const char *battery_vendor, u8 battery_version); + Result SetMaximumShutdownTimerThreshold(); + + Result SetAlertByChargePercentage(); + Result SetAlertByVoltageFuelGaugePercentage(); + + bool IsPowerOnReset(); + Result LockVoltageFuelGauge(); + Result UnlockVoltageFuelGauge(); + Result LockModelTable(); + Result UnlockModelTable(); + bool IsModelTableLocked(); + Result SetModelTable(const u16 *model_table); + bool IsModelTableSet(const u16 *model_table); + public: + constexpr Max17050Driver() : m_mutex(), m_init_count(0), m_i2c_session(), m_internal_state() { + /* ... */ + } + + void Initialize(const char *battery_vendor, u8 battery_version) { + std::scoped_lock lk(m_mutex); + if ((m_init_count++) == 0) { + /* Initialize i2c library. */ + i2c::InitializeEmpty(); + + /* Open session. */ + R_ABORT_UNLESS(i2c::OpenSession(std::addressof(m_i2c_session), i2c::DeviceCode_Max17050)); + + /* Initialize session. */ + R_ABORT_UNLESS(this->InitializeSession(battery_vendor, battery_version)); + + /* Set shutdown timer threshold to the maximum value. */ + R_ABORT_UNLESS(this->SetMaximumShutdownTimerThreshold()); + } + } + + void Finalize() { + std::scoped_lock lk(m_mutex); + if ((--m_init_count) == 0) { + /* Close session. */ + i2c::CloseSession(m_i2c_session); + + /* Finalize i2c library. */ + i2c::Finalize(); + } + } + + Result ReadInternalState(); + Result WriteInternalState(); + + void GetInternalState(max17050::InternalState *dst) { + *dst = m_internal_state; + } + + void SetInternalState(const max17050::InternalState &src) { + m_internal_state = src; + } + + Result GetChargePercentage(double *out); + Result GetVoltageFuelGaugePercentage(double *out); + + Result GetFullCapacity(double *out, double sense_resistor); + Result GetRemainingCapacity(double *out, double sense_resistor); + + Result SetChargePercentageMinimumAlertThreshold(int percentage); + Result SetChargePercentageMaximumAlertThreshold(int percentage); + + Result SetVoltageFuelGaugePercentageMinimumAlertThreshold(int percentage); + Result SetVoltageFuelGaugePercentageMaximumAlertThreshold(int percentage); + + Result SetFullChargeThreshold(double percentage); + + Result GetAverageCurrent(double *out, double sense_resistor); + Result GetCurrent(double *out, double sense_resistor); + + Result GetNeedToRestoreParameters(bool *out); + Result SetNeedToRestoreParameters(bool en); + + Result IsI2cShutdownEnabled(bool *out); + Result SetI2cShutdownEnabled(bool en); + + Result GetStatus(u16 *out); + + Result GetCycles(u16 *out); + Result ResetCycles(); + + Result GetAge(double *out); + + Result GetTemperature(double *out); + + Result GetMaximumTemperature(u8 *out); + + Result SetTemperatureMinimumAlertThreshold(int c); + Result SetTemperatureMaximumAlertThreshold(int c); + + Result GetVCell(int *out); + Result GetAverageVCell(int *out); + Result GetAverageVCellTime(double *out); + + Result GetOpenCircuitVoltage(int *out); + + Result SetVoltageMinimumAlertThreshold(int mv); + Result SetVoltageMaximumAlertThreshold(int mv); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_retry_helper.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_retry_helper.hpp new file mode 100644 index 00000000..60e56c21 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/board/nintendo/nx/powctl_retry_helper.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::powctl::impl { + + constexpr inline const TimeSpan PowerControlRetryTimeout = TimeSpan::FromSeconds(10); + constexpr inline const TimeSpan PowerControlRetryInterval = TimeSpan::FromMilliSeconds(20); + + #define AMS_POWCTL_DRIVER_R_TRY_WITH_RETRY(__EXPR__) \ + ({ \ + TimeSpan __powctl_retry_current_time = 0; \ + while (true) { \ + const Result __powctl_retry_result = ( __EXPR__ ); \ + if (R_SUCCEEDED(__powctl_retry_result)) { \ + break; \ + } \ + \ + __powctl_retry_current_time += PowerControlRetryInterval; \ + R_UNLESS(__powctl_retry_current_time < PowerControlRetryTimeout, __powctl_retry_result); \ + \ + os::SleepThread(PowerControlRetryInterval); \ + } \ + }) + + #define AMS_POWCTL_DRIVER_LOCKED_R_TRY_WITH_RETRY(__EXPR__) AMS_POWCTL_DRIVER_R_TRY_WITH_RETRY( ({ std::scoped_lock lk(this->GetMutex()); ( __EXPR__ ); }) ) + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/powctl_device_management.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/powctl_device_management.cpp new file mode 100644 index 00000000..bf4a7cee --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/powctl_device_management.cpp @@ -0,0 +1,152 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "powctl_device_management.hpp" + +namespace ams::powctl::impl { + + namespace { + + constinit os::ThreadType g_interrupt_thread = {}; + + constexpr inline size_t InterruptThreadStackSize = os::MemoryPageSize; + alignas(os::MemoryPageSize) u8 g_interrupt_thread_stack[InterruptThreadStackSize]; + + IPowerControlDriver::List &GetDriverList() { + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(IPowerControlDriver::List, s_driver_list); + return s_driver_list; + } + + ddsf::EventHandlerManager &GetInterruptHandlerManager() { + AMS_FUNCTION_LOCAL_STATIC(ddsf::EventHandlerManager, s_interrupt_handler_manager); + + return s_interrupt_handler_manager; + } + + ddsf::DeviceCodeEntryManager &GetDeviceCodeEntryManager() { + class DeviceCodeEntryManagerWithUnitHeap { + private: + u8 m_heap_memory[2_KB]; + sf::UnitHeapMemoryResource m_memory_resource; + util::TypedStorage<ddsf::DeviceCodeEntryManager> m_manager; + public: + DeviceCodeEntryManagerWithUnitHeap() { + /* Initialize the memory resource. */ + m_memory_resource.Attach(lmem::CreateUnitHeap(m_heap_memory, sizeof(m_heap_memory), sizeof(ddsf::DeviceCodeEntryHolder), lmem::CreateOption_ThreadSafe)); + + /* Construct the entry manager. */ + util::ConstructAt(m_manager, std::addressof(m_memory_resource)); + } + + ALWAYS_INLINE operator ddsf::DeviceCodeEntryManager &() { + return util::GetReference(m_manager); + } + }; + AMS_FUNCTION_LOCAL_STATIC(DeviceCodeEntryManagerWithUnitHeap, s_device_code_entry_manager_holder); + + return s_device_code_entry_manager_holder; + } + + void InterruptThreadFunction(void *arg) { + AMS_UNUSED(arg); + GetInterruptHandlerManager().LoopAuto(); + } + + } + + void InitializeDrivers() { + /* Ensure the event handler manager is initialized. */ + GetInterruptHandlerManager().Initialize(); + + /* Initialize all registered drivers. */ + for (auto &driver : GetDriverList()) { + driver.SafeCastTo<IPowerControlDriver>().InitializeDriver(); + } + + /* Create the interrupt thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(g_interrupt_thread), InterruptThreadFunction, nullptr, g_interrupt_thread_stack, InterruptThreadStackSize, AMS_GET_SYSTEM_THREAD_PRIORITY(powctl, InterruptHandler))); + os::SetThreadNamePointer(std::addressof(g_interrupt_thread), AMS_GET_SYSTEM_THREAD_NAME(powctl, InterruptHandler)); + os::StartThread(std::addressof(g_interrupt_thread)); + + /* Wait for the interrupt thread to enter the loop. */ + GetInterruptHandlerManager().WaitLoopEnter(); + } + + void FinalizeDrivers() { + /* Request the interrupt thread stop. */ + GetInterruptHandlerManager().RequestStop(); + os::WaitThread(std::addressof(g_interrupt_thread)); + os::DestroyThread(std::addressof(g_interrupt_thread)); + + /* Reset all device code entries. */ + GetDeviceCodeEntryManager().Reset(); + + /* Finalize all registered drivers. */ + for (auto &driver : GetDriverList()) { + driver.SafeCastTo<IPowerControlDriver>().FinalizeDriver(); + } + + /* Finalize the interrupt handler manager. */ + GetInterruptHandlerManager().Finalize(); + } + + void RegisterDriver(IPowerControlDriver *driver) { + AMS_ASSERT(driver != nullptr); + GetDriverList().push_back(*driver); + } + + void UnregisterDriver(IPowerControlDriver *driver) { + AMS_ASSERT(driver != nullptr); + if (driver->IsLinkedToList()) { + auto &list = GetDriverList(); + list.erase(list.iterator_to(*driver)); + } + } + + Result RegisterDeviceCode(DeviceCode device_code, IDevice *device) { + AMS_ASSERT(device != nullptr); + R_TRY(GetDeviceCodeEntryManager().Add(device_code, device)); + R_SUCCEED(); + } + + bool UnregisterDeviceCode(DeviceCode device_code) { + return GetDeviceCodeEntryManager().Remove(device_code); + } + + void RegisterInterruptHandler(ddsf::IEventHandler *handler) { + AMS_ASSERT(handler != nullptr); + GetInterruptHandlerManager().RegisterHandler(handler); + } + + void UnregisterInterruptHandler(ddsf::IEventHandler *handler) { + AMS_ASSERT(handler != nullptr); + GetInterruptHandlerManager().UnregisterHandler(handler); + } + + Result FindDevice(powctl::impl::IDevice **out, DeviceCode device_code) { + /* Validate output. */ + AMS_ASSERT(out != nullptr); + + /* Find the device. */ + ddsf::IDevice *device; + R_TRY(GetDeviceCodeEntryManager().FindDevice(std::addressof(device), device_code)); + + /* Set output. */ + *out = device->SafeCastToPointer<powctl::impl::IDevice>(); + R_SUCCEED(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/powctl_device_management.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/powctl_device_management.hpp new file mode 100644 index 00000000..020beb26 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/powctl_device_management.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "powctl_i_power_control_driver.hpp" + +namespace ams::powctl::impl { + + void InitializeDrivers(); + void FinalizeDrivers(); + + void RegisterDriver(IPowerControlDriver *driver); + void UnregisterDriver(IPowerControlDriver *driver); + + Result RegisterDeviceCode(DeviceCode device_code, IDevice *device); + bool UnregisterDeviceCode(DeviceCode device_code); + + void RegisterInterruptHandler(ddsf::IEventHandler *handler); + void UnregisterInterruptHandler(ddsf::IEventHandler *handler); + + Result FindDevice(IDevice **out, DeviceCode device_code); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/powctl_i_power_control_driver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/powctl_i_power_control_driver.hpp new file mode 100644 index 00000000..34fc9203 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/powctl_i_power_control_driver.hpp @@ -0,0 +1,145 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::powctl::impl { + + class IDevice : public ::ams::ddsf::IDevice { + NON_COPYABLE(IDevice); + NON_MOVEABLE(IDevice); + AMS_DDSF_CASTABLE_TRAITS(ams::powctl::impl::IDevice, ::ams::ddsf::IDevice); + public: + IDevice() : ddsf::IDevice(false) { /* ... */ } + virtual ~IDevice() { /* ... */ } + }; + + class IPowerControlDriver : public ::ams::ddsf::IDriver { + NON_COPYABLE(IPowerControlDriver); + NON_MOVEABLE(IPowerControlDriver); + AMS_DDSF_CASTABLE_TRAITS(ams::powctl::impl::IPowerControlDriver, ::ams::ddsf::IDriver); + private: + bool m_event_handler_enabled; + os::SdkMutex m_mutex; + protected: + constexpr bool IsEventHandlerEnabled() const { + return m_event_handler_enabled; + } + + ALWAYS_INLINE os::SdkMutex &GetMutex() { + return m_mutex; + } + public: + IPowerControlDriver(bool ev) : IDriver(), m_event_handler_enabled(ev), m_mutex() { /* ... */ } + virtual ~IPowerControlDriver() { /* ... */ } + + virtual void InitializeDriver() = 0; + virtual void FinalizeDriver() = 0; + + virtual Result GetDeviceSystemEvent(os::SystemEventType **out, IDevice *device) = 0; + virtual Result SetDeviceInterruptEnabled(IDevice *device, bool enable) = 0; + + /* TODO: Eventually implement proper error status enum? */ + virtual Result GetDeviceErrorStatus(u32 *out, IDevice *device) = 0; + virtual Result SetDeviceErrorStatus(IDevice *device, u32 status) = 0; + + virtual Result GetBatteryChargePercentage(float *out_percent, IDevice *device) = 0; + + virtual Result GetBatteryVoltageFuelGaugePercentage(float *out_percent, IDevice *device) = 0; + + virtual Result GetBatteryFullCapacity(int *out_mah, IDevice *device) = 0; + virtual Result GetBatteryRemainingCapacity(int *out_mah, IDevice *device) = 0; + + virtual Result SetBatteryChargePercentageMinimumAlertThreshold(IDevice *device, float percentage) = 0; + virtual Result SetBatteryChargePercentageMaximumAlertThreshold(IDevice *device, float percentage) = 0; + virtual Result SetBatteryVoltageFuelGaugePercentageMinimumAlertThreshold(IDevice *device, float percentage) = 0; + virtual Result SetBatteryVoltageFuelGaugePercentageMaximumAlertThreshold(IDevice *device, float percentage) = 0; + + virtual Result SetBatteryFullChargeThreshold(IDevice *device, float percentage) = 0; + + virtual Result GetChargerChargeCurrentState(ChargeCurrentState *out, IDevice *device) = 0; + virtual Result SetChargerChargeCurrentState(IDevice *device, ChargeCurrentState state) = 0; + + virtual Result GetChargerFastChargeCurrentLimit(int *out_ma, IDevice *device) = 0; + virtual Result SetChargerFastChargeCurrentLimit(IDevice *device, int ma) = 0; + + virtual Result GetChargerChargeVoltageLimit(int *out_mv, IDevice *device) = 0; + virtual Result SetChargerChargeVoltageLimit(IDevice *device, int mv) = 0; + + virtual Result SetChargerChargerConfiguration(IDevice *device, ChargerConfiguration cfg) = 0; + + virtual Result IsChargerHiZEnabled(bool *out, IDevice *device) = 0; + virtual Result SetChargerHiZEnabled(IDevice *device, bool en) = 0; + + virtual Result GetBatteryAverageCurrent(int *out_ma, IDevice *device) = 0; + virtual Result GetBatteryCurrent(int *out_ma, IDevice *device) = 0; + + virtual Result GetChargerInputCurrentLimit(int *out_ma, IDevice *device) = 0; + virtual Result SetChargerInputCurrentLimit(IDevice *device, int ma) = 0; + + virtual Result SetChargerInputVoltageLimit(IDevice *device, int mv) = 0; + + virtual Result SetChargerBoostModeCurrentLimit(IDevice *device, int ma) = 0; + + virtual Result GetBatteryInternalState(void *dst, size_t *out_size, IDevice *device, size_t dst_size) = 0; + virtual Result SetBatteryInternalState(IDevice *device, const void *src, size_t src_size) = 0; + + virtual Result GetBatteryNeedToRestoreParameters(bool *out, IDevice *device) = 0; + virtual Result SetBatteryNeedToRestoreParameters(IDevice *device, bool en) = 0; + + virtual Result IsBatteryI2cShutdownEnabled(bool *out, IDevice *device) = 0; + virtual Result SetBatteryI2cShutdownEnabled(IDevice *device, bool en) = 0; + + virtual Result IsBatteryPresent(bool *out, IDevice *device) = 0; + + virtual Result GetChargerChargerStatus(ChargerStatus *out, IDevice *device) = 0; + + virtual Result GetBatteryCycles(int *out, IDevice *device) = 0; + virtual Result SetBatteryCycles(IDevice *device, int cycles) = 0; + + virtual Result GetBatteryAge(float *out_percent, IDevice *device) = 0; + + virtual Result GetBatteryTemperature(float *out_c, IDevice *device) = 0; + virtual Result GetBatteryMaximumTemperature(float *out_c, IDevice *device) = 0; + + virtual Result SetBatteryTemperatureMinimumAlertThreshold(IDevice *device, float c) = 0; + virtual Result SetBatteryTemperatureMaximumAlertThreshold(IDevice *device, float c) = 0; + + virtual Result GetBatteryVCell(int *out_mv, IDevice *device) = 0; + virtual Result GetBatteryAverageVCell(int *out_mv, IDevice *device) = 0; + + virtual Result GetBatteryAverageVCellTime(TimeSpan *out, IDevice *device) = 0; + + virtual Result SetBatteryVoltageMinimumAlertThreshold(IDevice *device, int mv) = 0; + + virtual Result GetBatteryOpenCircuitVoltage(int *out_mv, IDevice *device) = 0; + + virtual Result SetBatteryVoltageMaximumAlertThreshold(IDevice *device, int mv) = 0; + + virtual Result IsChargerWatchdogTimerEnabled(bool *out, IDevice *device) = 0; + virtual Result SetChargerWatchdogTimerEnabled(IDevice *device, bool en) = 0; + + virtual Result SetChargerWatchdogTimerTimeout(IDevice *device, TimeSpan timeout) = 0; + virtual Result ResetChargerWatchdogTimer(IDevice *device) = 0; + + virtual Result GetChargerBatteryCompensation(int *out_mo, IDevice *device) = 0; + virtual Result SetChargerBatteryCompensation(IDevice *device, int mo) = 0; + + virtual Result GetChargerVoltageClamp(int *out_mv, IDevice *device) = 0; + virtual Result SetChargerVoltageClamp(IDevice *device, int mv) = 0; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/powctl_select_board_driver.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/powctl_select_board_driver.hpp new file mode 100644 index 00000000..d37d9a9c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/impl/powctl_select_board_driver.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "powctl_i_power_control_driver.hpp" + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + + #include "board/nintendo/nx/powctl_board_impl.hpp" + + namespace ams::powctl::impl::board { + using namespace ams::powctl::impl::board::nintendo::nx; + } + +#else + + // TODO #error "Unknown board for ams::powctl::impl" + + namespace ams::powctl::impl::board { + + inline void Initialize(bool) { + AMS_ABORT("TODO"); + } + + inline void Finalize() { + AMS_ABORT("TODO"); + } + + } + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/powctl_battery_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/powctl_battery_api.cpp new file mode 100644 index 00000000..539ceb04 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/powctl_battery_api.cpp @@ -0,0 +1,467 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/powctl_i_power_control_driver.hpp" +#include "impl/powctl_device_management.hpp" + +namespace ams::powctl { + + namespace { + + impl::SessionImpl &GetOpenSessionImpl(Session &session) { + /* AMS_ASSERT(session.has_session); */ + auto &impl = GetReference(session.impl_storage); + AMS_ASSERT(impl.IsOpen()); + return impl; + } + + } + + Result GetBatteryChargePercentage(float *out_percent, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetBatteryChargePercentage(out_percent, std::addressof(device))); + } + + Result GetBatteryVoltageFuelGaugePercentage(float *out_percent, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetBatteryVoltageFuelGaugePercentage(out_percent, std::addressof(device))); + } + + Result GetBatteryFullCapacity(int *out_mah, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetBatteryFullCapacity(out_mah, std::addressof(device))); + } + + Result GetBatteryRemainingCapacity(int *out_mah, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetBatteryRemainingCapacity(out_mah, std::addressof(device))); + } + + Result SetBatteryChargePercentageMinimumAlertThreshold(Session &session, float percentage) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetBatteryChargePercentageMinimumAlertThreshold(std::addressof(device), percentage)); + } + + Result SetBatteryChargePercentageMaximumAlertThreshold(Session &session, float percentage) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetBatteryChargePercentageMaximumAlertThreshold(std::addressof(device), percentage)); + } + + Result SetBatteryVoltageFuelGaugePercentageMinimumAlertThreshold(Session &session, float percentage) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetBatteryVoltageFuelGaugePercentageMinimumAlertThreshold(std::addressof(device), percentage)); + } + + Result SetBatteryVoltageFuelGaugePercentageMaximumAlertThreshold(Session &session, float percentage) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetBatteryVoltageFuelGaugePercentageMaximumAlertThreshold(std::addressof(device), percentage)); + } + + Result SetBatteryFullChargeThreshold(Session &session, float percentage) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetBatteryFullChargeThreshold(std::addressof(device), percentage)); + } + + Result GetBatteryAverageCurrent(int *out_ma, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetBatteryAverageCurrent(out_ma, std::addressof(device))); + } + + Result GetBatteryCurrent(int *out_ma, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetBatteryCurrent(out_ma, std::addressof(device))); + } + + Result GetBatteryInternalState(void *dst, size_t *out_size, Session &session, size_t dst_size) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetBatteryInternalState(dst, out_size, std::addressof(device), dst_size)); + } + + Result SetBatteryInternalState(Session &session, const void *src, size_t src_size) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetBatteryInternalState(std::addressof(device), src, src_size)); + } + + Result GetBatteryNeedToRestoreParameters(bool *out, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetBatteryNeedToRestoreParameters(out, std::addressof(device))); + } + + Result SetBatteryNeedToRestoreParameters(Session &session, bool en) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetBatteryNeedToRestoreParameters(std::addressof(device), en)); + } + + Result IsBatteryI2cShutdownEnabled(bool *out, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().IsBatteryI2cShutdownEnabled(out, std::addressof(device))); + } + + Result SetBatteryI2cShutdownEnabled(Session &session, bool en) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetBatteryI2cShutdownEnabled(std::addressof(device), en)); + } + + Result IsBatteryPresent(bool *out, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().IsBatteryPresent(out, std::addressof(device))); + } + + Result GetBatteryCycles(int *out, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetBatteryCycles(out, std::addressof(device))); + } + + Result SetBatteryCycles(Session &session, int cycles) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetBatteryCycles(std::addressof(device), cycles)); + } + + Result GetBatteryAge(float *out_percent, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetBatteryAge(out_percent, std::addressof(device))); + } + + Result GetBatteryTemperature(float *out_c, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetBatteryTemperature(out_c, std::addressof(device))); + } + + Result GetBatteryMaximumTemperature(float *out_c, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetBatteryMaximumTemperature(out_c, std::addressof(device))); + } + + Result SetBatteryTemperatureMinimumAlertThreshold(Session &session, float c) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetBatteryTemperatureMinimumAlertThreshold(std::addressof(device), c)); + } + + Result SetBatteryTemperatureMaximumAlertThreshold(Session &session, float c) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetBatteryTemperatureMaximumAlertThreshold(std::addressof(device), c)); + } + + Result GetBatteryVCell(int *out_mv, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetBatteryVCell(out_mv, std::addressof(device))); + } + + Result GetBatteryAverageVCell(int *out_mv, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetBatteryAverageVCell(out_mv, std::addressof(device))); + } + + Result GetBatteryAverageVCellTime(TimeSpan *out, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetBatteryAverageVCellTime(out, std::addressof(device))); + } + + Result GetBatteryOpenCircuitVoltage(int *out_mv, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetBatteryOpenCircuitVoltage(out_mv, std::addressof(device))); + } + + Result SetBatteryVoltageMinimumAlertThreshold(Session &session, int mv) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetBatteryVoltageMinimumAlertThreshold(std::addressof(device), mv)); + } + + Result SetBatteryVoltageMaximumAlertThreshold(Session &session, int mv) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetBatteryVoltageMaximumAlertThreshold(std::addressof(device), mv)); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/powctl_charger_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/powctl_charger_api.cpp new file mode 100644 index 00000000..b408bf84 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/powctl_charger_api.cpp @@ -0,0 +1,329 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/powctl_i_power_control_driver.hpp" +#include "impl/powctl_device_management.hpp" + +namespace ams::powctl { + + namespace { + + impl::SessionImpl &GetOpenSessionImpl(Session &session) { + /* AMS_ASSERT(session.has_session); */ + auto &impl = GetReference(session.impl_storage); + AMS_ASSERT(impl.IsOpen()); + return impl; + } + + } + + Result GetChargerChargeCurrentState(ChargeCurrentState *out, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetChargerChargeCurrentState(out, std::addressof(device))); + } + + Result SetChargerChargeCurrentState(Session &session, ChargeCurrentState state) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetChargerChargeCurrentState(std::addressof(device), state)); + } + + Result GetChargerFastChargeCurrentLimit(int *out_ma, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetChargerFastChargeCurrentLimit(out_ma, std::addressof(device))); + } + + Result SetChargerFastChargeCurrentLimit(Session &session, int ma) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetChargerFastChargeCurrentLimit(std::addressof(device), ma)); + } + + Result GetChargerChargeVoltageLimit(int *out_mv, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetChargerChargeVoltageLimit(out_mv, std::addressof(device))); + } + + Result SetChargerChargeVoltageLimit(Session &session, int mv) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetChargerChargeVoltageLimit(std::addressof(device), mv)); + } + + Result SetChargerChargerConfiguration(Session &session, ChargerConfiguration cfg) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetChargerChargerConfiguration(std::addressof(device), cfg)); + } + + Result IsChargerHiZEnabled(bool *out, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().IsChargerHiZEnabled(out, std::addressof(device))); + } + + Result SetChargerHiZEnabled(Session &session, bool en) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetChargerHiZEnabled(std::addressof(device), en)); + } + + Result GetChargerInputCurrentLimit(int *out_ma, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetChargerInputCurrentLimit(out_ma, std::addressof(device))); + } + + Result SetChargerInputCurrentLimit(Session &session, int ma) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetChargerInputCurrentLimit(std::addressof(device), ma)); + } + + Result SetChargerInputVoltageLimit(Session &session, int mv) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetChargerInputVoltageLimit(std::addressof(device), mv)); + } + + Result SetChargerBoostModeCurrentLimit(Session &session, int ma) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetChargerBoostModeCurrentLimit(std::addressof(device), ma)); + } + + Result GetChargerChargerStatus(ChargerStatus *out, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetChargerChargerStatus(out, std::addressof(device))); + } + + Result IsChargerWatchdogTimerEnabled(bool *out, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().IsChargerWatchdogTimerEnabled(out, std::addressof(device))); + } + + Result SetChargerWatchdogTimerEnabled(Session &session, bool en) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetChargerWatchdogTimerEnabled(std::addressof(device), en)); + } + + Result SetChargerWatchdogTimerTimeout(Session &session, TimeSpan timeout) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetChargerWatchdogTimerTimeout(std::addressof(device), timeout)); + } + + Result ResetChargerWatchdogTimer(Session &session); + + Result GetChargerBatteryCompensation(int *out_mo, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetChargerBatteryCompensation(out_mo, std::addressof(device))); + } + + Result SetChargerBatteryCompensation(Session &session, int mo) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetChargerBatteryCompensation(std::addressof(device), mo)); + } + + Result GetChargerVoltageClamp(int *out_mv, Session &session) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Read), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().GetChargerVoltageClamp(out_mv, std::addressof(device))); + } + + Result SetChargerVoltageClamp(Session &session, int mv) { + /* Get the session impl. */ + auto &impl = GetOpenSessionImpl(session); + + /* Check the access mode. */ + R_UNLESS(impl.CheckAccess(ddsf::AccessMode_Write), ddsf::ResultPermissionDenied()); + + /* Get the device. */ + auto &device = impl.GetDevice().SafeCastTo<impl::IDevice>(); + + /* Call into the driver. */ + R_RETURN(device.GetDriver().SafeCastTo<impl::IPowerControlDriver>().SetChargerVoltageClamp(std::addressof(device), mv)); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/powctl_driver_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/powctl_driver_api.cpp new file mode 100644 index 00000000..4fb98b86 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/powctl_driver_api.cpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/powctl_select_board_driver.hpp" +#include "impl/powctl_device_management.hpp" + +namespace ams::powctl { + + void Initialize(bool enable_interrupt_handlers) { + /* Initialize the board driver. */ + impl::board::Initialize(enable_interrupt_handlers); + + /* Initialize drivers. */ + impl::InitializeDrivers(); + + } + + void Finalize() { + /* Finalize drivers. */ + impl::FinalizeDrivers(); + + /* Finalize the board driver. */ + impl::board::Finalize(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/powctl_session_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/powctl_session_api.cpp new file mode 100644 index 00000000..09939e6a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/powctl/powctl_session_api.cpp @@ -0,0 +1,89 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/powctl_device_management.hpp" + +namespace ams::powctl { + + namespace { + + ddsf::AccessMode SanitizeAccessMode(ddsf::AccessMode access_mode) { + switch (access_mode) { + case ddsf::AccessMode_Read: + case ddsf::AccessMode_Write: + case ddsf::AccessMode_ReadWrite: + case ddsf::AccessMode_WriteShared: + case ddsf::AccessMode_ReadWriteShared: + return access_mode; + default: + return ddsf::AccessMode_None; + } + } + + impl::SessionImpl &GetSessionImpl(Session &session) { + return GetReference(session.impl_storage); + } + + void DestroySession(Session &session) { + std::destroy_at(std::addressof(GetSessionImpl(session))); + session.has_session = false; + } + + void DestroySessionIfNecessary(Session &session) { + if (session.has_session) { + DestroySession(session); + } + } + + void CloseSessionIfOpen(Session &session) { + if (session.has_session && GetSessionImpl(session).IsOpen()) { + DestroySession(session); + } + } + + } + + Result OpenSession(Session *out, DeviceCode device_code, ddsf::AccessMode access_mode) { + /* Validate input. */ + AMS_ASSERT(out != nullptr); + access_mode = SanitizeAccessMode(access_mode); + + /* Find the target device. */ + impl::IDevice *device = nullptr; + R_TRY(impl::FindDevice(std::addressof(device), device_code)); + + /* Clean up the session if we have one. */ + DestroySessionIfNecessary(*out); + + /* Construct the session. */ + auto *session = std::construct_at(std::addressof(GetSessionImpl(*out))); + auto guard = SCOPE_GUARD { DestroySessionIfNecessary(*out); }; + + /* Try to open the session. */ + R_TRY(ddsf::OpenSession(device, session, access_mode)); + + /* We opened the session! */ + guard.Cancel(); + R_SUCCEED(); + } + + void CloseSession(Session &session) { + /* This seems extremely unnecessary/duplicate, but it's what Nintendo does. */ + CloseSessionIfOpen(session); + DestroySession(session); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/psc/psc_pm_module.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/psc/psc_pm_module.os.horizon.cpp new file mode 100644 index 00000000..35642443 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/psc/psc_pm_module.os.horizon.cpp @@ -0,0 +1,90 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "psc_remote_pm_module.hpp" + +namespace ams::psc { + + /* TODO: Nintendo uses sf::ShimLobraryObjectHolder here, we should similarly consider switching. */ + namespace { + + struct PscRemotePmModuleTag; + using RemoteAllocator = ams::sf::ExpHeapStaticAllocator<2_KB, PscRemotePmModuleTag>; + using RemoteObjectFactory = ams::sf::ObjectFactory<typename RemoteAllocator::Policy>; + + class StaticAllocatorInitializer { + public: + StaticAllocatorInitializer() { + RemoteAllocator::Initialize(lmem::CreateOption_None); + } + } g_static_allocator_initializer; + + } + + PmModule::PmModule() : m_intf(nullptr), m_initialized(false), m_module_id(PmModuleId_Reserved0), m_reserved(0) { /* ... */ } + + PmModule::~PmModule() { + if (m_initialized) { + m_intf = nullptr; + os::DestroySystemEvent(m_system_event.GetBase()); + } + } + + Result PmModule::Initialize(const PmModuleId mid, const PmModuleId *dependencies, u32 dependency_count, os::EventClearMode clear_mode) { + R_UNLESS(!m_initialized, psc::ResultAlreadyInitialized()); + + static_assert(sizeof(*dependencies) == sizeof(u32)); + ::PscPmModule module; + R_TRY(::pscmGetPmModule(std::addressof(module), static_cast<::PscPmModuleId>(mid), reinterpret_cast<const u32 *>(dependencies), dependency_count, clear_mode == os::EventClearMode_AutoClear)); + + m_intf = RemoteObjectFactory::CreateSharedEmplaced<psc::sf::IPmModule, RemotePmModule>(module); + m_system_event.AttachReadableHandle(module.event.revent, false, clear_mode); + m_initialized = true; + R_SUCCEED(); + } + + Result PmModule::Finalize() { + R_UNLESS(m_initialized, psc::ResultNotInitialized()); + + R_TRY(m_intf->Finalize()); + m_intf = nullptr; + os::DestroySystemEvent(m_system_event.GetBase()); + m_initialized = false; + R_SUCCEED(); + } + + Result PmModule::GetRequest(PmState *out_state, PmFlagSet *out_flags) { + R_UNLESS(m_initialized, psc::ResultNotInitialized()); + + R_RETURN(m_intf->GetRequest(out_state, out_flags)); + } + + Result PmModule::Acknowledge(PmState state, Result res) { + R_ABORT_UNLESS(res); + R_UNLESS(m_initialized, psc::ResultNotInitialized()); + + if (hos::GetVersion() >= hos::Version_5_1_0) { + R_RETURN(m_intf->AcknowledgeEx(state)); + } else { + R_RETURN(m_intf->Acknowledge()); + } + } + + os::SystemEvent *PmModule::GetEventPointer() { + return std::addressof(m_system_event); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/psc/psc_remote_pm_module.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/psc/psc_remote_pm_module.hpp new file mode 100644 index 00000000..48d9d7f1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/psc/psc_remote_pm_module.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::psc { + + class RemotePmModule { + NON_COPYABLE(RemotePmModule); + NON_MOVEABLE(RemotePmModule); + private: + ::PscPmModule m_module; + public: + constexpr RemotePmModule(const ::PscPmModule &m) : m_module(m) { /* ... */ } + ~RemotePmModule() { + ::pscPmModuleClose(std::addressof(m_module)); + } + + Result Initialize(ams::sf::OutCopyHandle out, psc::PmModuleId module_id, const ams::sf::InBuffer &child_list) { + /* NOTE: This functionality is already implemented by the libnx command we use to instantiate the PscPmModule. */ + AMS_UNUSED(out, module_id, child_list); + AMS_ABORT(); + } + + Result GetRequest(ams::sf::Out<PmState> out_state, ams::sf::Out<PmFlagSet> out_flags) { + static_assert(sizeof(PmState) == sizeof(::PscPmState)); + static_assert(sizeof(PmFlagSet) == sizeof(u32)); + R_RETURN(::pscPmModuleGetRequest(std::addressof(m_module), reinterpret_cast<::PscPmState *>(out_state.GetPointer()), reinterpret_cast<u32 *>(out_flags.GetPointer()))); + } + + Result Acknowledge() { + /* NOTE: libnx does not separate acknowledge/acknowledgeEx. */ + R_RETURN(::pscPmModuleAcknowledge(std::addressof(m_module), static_cast<::PscPmState>(0))); + } + + Result Finalize() { + R_RETURN(::pscPmModuleFinalize(std::addressof(m_module))); + } + + Result AcknowledgeEx(PmState state) { + static_assert(sizeof(state) == sizeof(::PscPmState)); + R_RETURN(::pscPmModuleAcknowledge(std::addressof(m_module), static_cast<::PscPmState>(state))); + } + }; + static_assert(psc::sf::IsIPmModule<RemotePmModule>); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/board/nintendo/nx/impl/pwm_impl_pwm_driver_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/board/nintendo/nx/impl/pwm_impl_pwm_driver_api.cpp new file mode 100644 index 00000000..9244d072 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/board/nintendo/nx/impl/pwm_impl_pwm_driver_api.cpp @@ -0,0 +1,66 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pwm_impl_pwm_driver_api.hpp" +#include "pwm_pwm_driver_impl.hpp" + +namespace ams::pwm::driver::board::nintendo::nx::impl { + + namespace { + + constexpr inline const dd::PhysicalAddress PwmRegistersPhysicalAddress = 0x7000A000; + constexpr inline size_t PwmRegistersSize = 0x100; + + constexpr const ChannelDefinition SupportedChannels[] = { + { pwm::DeviceCode_LcdBacklight, 0 }, + { pwm::DeviceCode_CpuFan, 1 }, + }; + + } + + Result InitializePwmDriver() { + /* Get the memory resource with which to allocate our driver/devices. */ + auto *memory_resource = ddsf::GetMemoryResource(); + + /* Allocate storage for our driver. */ + auto *driver_storage = memory_resource->Allocate(sizeof(PwmDriverImpl), alignof(PwmDriverImpl)); + AMS_ABORT_UNLESS(driver_storage != nullptr); + + /* Create our driver. */ + auto *driver = std::construct_at(static_cast<PwmDriverImpl *>(driver_storage), PwmRegistersPhysicalAddress, PwmRegistersSize, SupportedChannels, util::size(SupportedChannels)); + + /* Register our driver. */ + pwm::driver::RegisterDriver(driver); + + /* Create our devices. */ + for (const auto &entry : SupportedChannels) { + auto *device_storage = memory_resource->Allocate(sizeof(PwmDriverImpl), alignof(PwmDriverImpl)); + AMS_ABORT_UNLESS(device_storage != nullptr); + + /* Create our driver. */ + auto *device = std::construct_at(static_cast<PwmDeviceImpl *>(device_storage), entry.channel_id); + + /* Register the device with our driver. */ + driver->RegisterDevice(device); + + /* Register the device code with our driver. */ + R_ABORT_UNLESS(pwm::driver::RegisterDeviceCode(entry.device_code, device)); + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/board/nintendo/nx/impl/pwm_impl_pwm_driver_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/board/nintendo/nx/impl/pwm_impl_pwm_driver_api.hpp new file mode 100644 index 00000000..18c7138c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/board/nintendo/nx/impl/pwm_impl_pwm_driver_api.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::pwm::driver::board::nintendo::nx::impl { + + struct ChannelDefinition { + DeviceCode device_code; + int channel_id; + }; + + Result InitializePwmDriver(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/board/nintendo/nx/impl/pwm_pwm_driver_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/board/nintendo/nx/impl/pwm_pwm_driver_impl.cpp new file mode 100644 index 00000000..cc4049e1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/board/nintendo/nx/impl/pwm_pwm_driver_impl.cpp @@ -0,0 +1,263 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pwm_pwm_driver_impl.hpp" + +namespace ams::pwm::driver::board::nintendo::nx::impl { + + namespace { + + constexpr inline u32 PwmClockRateHz = 45'333'333; + + constexpr inline TimeSpan DefaultChannelPeriod = TimeSpan::FromMilliSeconds(10); + + constexpr inline int MaxDuty = 0x100; + + template<typename T> + T DivideRoundUp(T a, T b) { + return (a + (b / 2)) / b; + } + + } + + PwmDriverImpl::PwmDriverImpl(dd::PhysicalAddress paddr, size_t sz, const ChannelDefinition *c, size_t nc) : m_registers_phys_addr(paddr), m_registers_size(sz), m_channels(c), m_num_channels(nc), m_registers(0) { + /* ... */ + } + + void PwmDriverImpl::PowerOn() { + /* Initialize pcv driver. */ + pcv::Initialize(); + + /* Setup clock/power for pwm. */ + R_ABORT_UNLESS(pcv::SetReset(pcv::Module_Pwm, true)); + R_ABORT_UNLESS(pcv::SetClockEnabled(pcv::Module_Pwm, true)); + R_ABORT_UNLESS(pcv::SetClockRate(pcv::Module_Pwm, PwmClockRateHz)); + R_ABORT_UNLESS(pcv::SetReset(pcv::Module_Pwm, false)); + } + + void PwmDriverImpl::PowerOff() { + /* Disable clock and hold pwm in reset. */ + /* NOTE: Nintendo does not check this succeeds. */ + pcv::SetClockEnabled(pcv::Module_Pwm, false); + pcv::SetReset(pcv::Module_Pwm, true); + + /* Finalize pcv driver. */ + pcv::Finalize(); + } + + void PwmDriverImpl::InitializeDriver() { + /* Get the registers virtual address. */ + m_registers = dd::QueryIoMapping(m_registers_phys_addr, m_registers_size); + AMS_ABORT_UNLESS(m_registers != 0); + + /* Setup power to pwm. */ + this->PowerOn(); + } + + void PwmDriverImpl::FinalizeDriver() { + /* Shut down power to pwm. */ + this->PowerOff(); + } + + Result PwmDriverImpl::InitializeDevice(IPwmDevice *device) { + /* Validate the device. */ + AMS_ASSERT(device != nullptr); + + /* Configure initial settings. */ + /* NOTE: None of these results are checked. */ + this->SetEnabled(device, false); + this->SetScale(device, 0.0); + this->SetPeriod(device, DefaultChannelPeriod); + R_SUCCEED(); + } + + void PwmDriverImpl::FinalizeDevice(IPwmDevice *device) { + /* Validate the device. */ + AMS_ASSERT(device != nullptr); + + /* Nothing to do here. */ + AMS_UNUSED(device); + } + + Result PwmDriverImpl::SetPeriod(IPwmDevice *device, TimeSpan period) { + /* Validate the device. */ + AMS_ASSERT(device != nullptr); + + /* Verify the period is valid. */ + const auto ns = period.GetNanoSeconds(); + R_UNLESS(ns > 0, pwm::ResultInvalidArgument()); + + /* Convert the ns to a desired frequency (rounding up). */ + const auto hz = DivideRoundUp(TimeSpan::FromSeconds(1).GetNanoSeconds(), ns); + R_UNLESS(hz > 0, pwm::ResultInvalidArgument()); + + /* Convert the frequency to a pfm value. */ + const u32 pfm = std::min<u32>(std::max<u32>(DivideRoundUp<u64>(PwmClockRateHz, hz * 256), 1) - 1, 0x1FFF); + + /* Acquire exclusive access to the device registers. */ + std::scoped_lock lk(device->SafeCastTo<PwmDeviceImpl>()); + + /* Update the period. */ + reg::ReadWrite(this->GetRegistersFor(device) + PWM_CONTROLLER_PWM_CSR, PWM_REG_BITS_VALUE(PWM_CSR_PFM, pfm)); + + R_SUCCEED(); + } + + Result PwmDriverImpl::GetPeriod(TimeSpan *out, IPwmDevice *device) { + /* Validate the device. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(device != nullptr); + + /* Get the pfm value. */ + const u32 pfm = reg::GetValue(this->GetRegistersFor(device) + PWM_CONTROLLER_PWM_CSR, PWM_REG_BITS_MASK(PWM_CSR_PFM)); + + /* Convert it to a frequency. */ + /* pfm = ((ClockRate / (hz * 256)) - 1) -> hz = (ClockRate / ((pfm + 1) * 256)) */ + const auto hz = DivideRoundUp<s64>(PwmClockRateHz, (pfm + 1) * 256); + + /* Convert the frequency to a period. */ + const auto ns = DivideRoundUp(TimeSpan::FromSeconds(1).GetNanoSeconds(), hz); + + /* Set the output. */ + *out = TimeSpan::FromNanoSeconds(ns); + R_SUCCEED(); + } + + Result PwmDriverImpl::SetDuty(IPwmDevice *device, int duty) { + /* Validate the device. */ + AMS_ASSERT(device != nullptr); + + /* Validate the duty. */ + R_UNLESS(0 <= duty && duty <= MaxDuty, pwm::ResultInvalidArgument()); + + /* Acquire exclusive access to the device registers. */ + std::scoped_lock lk(device->SafeCastTo<PwmDeviceImpl>()); + + /* Update the duty. */ + reg::ReadWrite(this->GetRegistersFor(device) + PWM_CONTROLLER_PWM_CSR, PWM_REG_BITS_VALUE(PWM_CSR_PWM, static_cast<u32>(duty))); + + R_SUCCEED(); + } + + Result PwmDriverImpl::GetDuty(int *out, IPwmDevice *device) { + /* Validate the device. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(device != nullptr); + + /* Get the duty. */ + *out = static_cast<int>(reg::GetValue(this->GetRegistersFor(device) + PWM_CONTROLLER_PWM_CSR, PWM_REG_BITS_MASK(PWM_CSR_PWM))); + R_SUCCEED(); + } + + Result PwmDriverImpl::SetScale(IPwmDevice *device, double scale) { + /* Validate the device. */ + AMS_ASSERT(device != nullptr); + + /* Convert the scale to a duty. */ + const int duty = static_cast<int>(((scale * 256.0) / 100.0) + 0.5); + + /* Validate the duty. */ + R_UNLESS(0 <= duty && duty <= MaxDuty, pwm::ResultInvalidArgument()); + + /* Acquire exclusive access to the device registers. */ + std::scoped_lock lk(device->SafeCastTo<PwmDeviceImpl>()); + + /* Update the duty. */ + reg::ReadWrite(this->GetRegistersFor(device) + PWM_CONTROLLER_PWM_CSR, PWM_REG_BITS_VALUE(PWM_CSR_PWM, static_cast<u32>(duty))); + + R_SUCCEED(); + } + + Result PwmDriverImpl::GetScale(double *out, IPwmDevice *device) { + /* Validate the device. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(device != nullptr); + + /* Get the duty. */ + const int duty = static_cast<int>(reg::GetValue(this->GetRegistersFor(device) + PWM_CONTROLLER_PWM_CSR, PWM_REG_BITS_MASK(PWM_CSR_PWM))); + + /* Convert to scale. */ + *out = (static_cast<double>(duty) * 100.0) / 256.0; + R_SUCCEED(); + } + + Result PwmDriverImpl::SetEnabled(IPwmDevice *device, bool en) { + /* Validate the device. */ + AMS_ASSERT(device != nullptr); + + /* Acquire exclusive access to the device registers. */ + std::scoped_lock lk(device->SafeCastTo<PwmDeviceImpl>()); + + /* Update the enable. */ + reg::ReadWrite(this->GetRegistersFor(device) + PWM_CONTROLLER_PWM_CSR, PWM_REG_BITS_ENUM_SEL(PWM_CSR_ENB, en, ENABLE, DISABLE)); + + R_SUCCEED(); + } + + Result PwmDriverImpl::GetEnabled(bool *out, IPwmDevice *device) { + /* Validate the device. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(device != nullptr); + + /* Get the enable. */ + *out = reg::HasValue(this->GetRegistersFor(device) + PWM_CONTROLLER_PWM_CSR, PWM_REG_BITS_ENUM(PWM_CSR_ENB, ENABLE)); + R_SUCCEED(); + } + + Result PwmDriverImpl::Suspend() { + /* Suspend each device. */ + this->ForEachDevice([&](ddsf::IDevice &device) -> bool { + /* Convert the device to a pwm device. */ + auto &pwm_device = device.SafeCastTo<PwmDeviceImpl>(); + + /* Cache the suspend value. */ + pwm_device.SetSuspendValue(reg::Read(this->GetRegistersFor(pwm_device) + PWM_CONTROLLER_PWM_CSR)); + + /* Acquire exclusive access to the device. */ + std::scoped_lock lk(pwm_device); + + /* Disable the device. */ + reg::ReadWrite(this->GetRegistersFor(pwm_device) + PWM_CONTROLLER_PWM_CSR, PWM_REG_BITS_ENUM(PWM_CSR_ENB, DISABLE)); + + /* Continue to the next device. */ + return true; + }); + + /* Disable clock to pwm. */ + R_RETURN(pcv::SetClockEnabled(pcv::Module_Pwm, false)); + } + + void PwmDriverImpl::Resume() { + /* Power on. */ + this->PowerOn(); + + /* Resume each device. */ + this->ForEachDevice([&](ddsf::IDevice &device) -> bool { + /* Convert the device to a pwm device. */ + auto &pwm_device = device.SafeCastTo<PwmDeviceImpl>(); + + /* Acquire exclusive access to the device. */ + std::scoped_lock lk(pwm_device); + + /* Write the device's suspend value. */ + reg::Write(this->GetRegistersFor(pwm_device) + PWM_CONTROLLER_PWM_CSR, pwm_device.GetSuspendValue()); + + /* Continue to the next device. */ + return true; + }); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/board/nintendo/nx/impl/pwm_pwm_driver_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/board/nintendo/nx/impl/pwm_pwm_driver_impl.hpp new file mode 100644 index 00000000..e98e1756 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/board/nintendo/nx/impl/pwm_pwm_driver_impl.hpp @@ -0,0 +1,85 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "pwm_impl_pwm_driver_api.hpp" + +namespace ams::pwm::driver::board::nintendo::nx::impl { + + class PwmDeviceImpl : public ::ams::pwm::driver::IPwmDevice { + NON_COPYABLE(PwmDeviceImpl); + NON_MOVEABLE(PwmDeviceImpl); + AMS_DDSF_CASTABLE_TRAITS(ams::pwm::driver::board::nintendo::nx::impl::PwmDeviceImpl, ::ams::pwm::driver::IPwmDevice); + private: + os::SdkMutex m_suspend_mutex; + u32 m_suspend_value; + public: + PwmDeviceImpl(int channel) : IPwmDevice(channel), m_suspend_mutex(), m_suspend_value() { /* ... */ } + + void SetSuspendValue(u32 v) { m_suspend_value = v; } + u32 GetSuspendValue() const { return m_suspend_value; } + + void lock() { return m_suspend_mutex.lock(); } + void unlock() { return m_suspend_mutex.unlock(); } + }; + + class PwmDriverImpl : public ::ams::pwm::driver::IPwmDriver { + NON_COPYABLE(PwmDriverImpl); + NON_MOVEABLE(PwmDriverImpl); + AMS_DDSF_CASTABLE_TRAITS(ams::pwm::driver::board::nintendo::nx::impl::PwmDriverImpl, ::ams::pwm::driver::IPwmDriver); + private: + dd::PhysicalAddress m_registers_phys_addr; + size_t m_registers_size; + const ChannelDefinition *m_channels; + size_t m_num_channels; + uintptr_t m_registers; + private: + ALWAYS_INLINE uintptr_t GetRegistersFor(IPwmDevice &device) { + return m_registers + PWM_CONTROLLER_PWM_CHANNEL_OFFSET(device.GetChannelIndex()); + } + + ALWAYS_INLINE uintptr_t GetRegistersFor(IPwmDevice *device) { + return this->GetRegistersFor(*device); + } + + void PowerOn(); + void PowerOff(); + public: + PwmDriverImpl(dd::PhysicalAddress paddr, size_t sz, const ChannelDefinition *c, size_t nsc); + + virtual void InitializeDriver() override; + virtual void FinalizeDriver() override; + + virtual Result InitializeDevice(IPwmDevice *device) override; + virtual void FinalizeDevice(IPwmDevice *device) override; + + virtual Result SetPeriod(IPwmDevice *device, TimeSpan period) override; + virtual Result GetPeriod(TimeSpan *out, IPwmDevice *device) override; + + virtual Result SetDuty(IPwmDevice *device, int duty) override; + virtual Result GetDuty(int *out, IPwmDevice *device) override; + + virtual Result SetScale(IPwmDevice *device, double scale) override; + virtual Result GetScale(double *out, IPwmDevice *device) override; + + virtual Result SetEnabled(IPwmDevice *device, bool en) override; + virtual Result GetEnabled(bool *out, IPwmDevice *device) override; + + virtual Result Suspend() override; + virtual void Resume() override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/board/nintendo/nx/pwm_driver_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/board/nintendo/nx/pwm_driver_api.cpp new file mode 100644 index 00000000..79f79862 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/board/nintendo/nx/pwm_driver_api.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/pwm_impl_pwm_driver_api.hpp" + +namespace ams::pwm::driver::board::nintendo::nx { + + void Initialize() { + R_ABORT_UNLESS(impl::InitializePwmDriver()); + /* TODO: R_ABORT_UNLESS(impl::InitializePmcDriver()); */ + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/impl/pwm_channel_session_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/impl/pwm_channel_session_impl.cpp new file mode 100644 index 00000000..82dcee59 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/impl/pwm_channel_session_impl.cpp @@ -0,0 +1,124 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pwm_driver_core.hpp" +#include "pwm_channel_session_impl.hpp" + +namespace ams::pwm::driver::impl { + + Result ChannelSessionImpl::Open(IPwmDevice *device, ddsf::AccessMode access_mode) { + AMS_ASSERT(device != nullptr); + + /* Check if we're the device's first session. */ + const bool first = !device->HasAnyOpenSession(); + + /* Open the session. */ + R_TRY(ddsf::OpenSession(device, this, access_mode)); + auto guard = SCOPE_GUARD { ddsf::CloseSession(this); }; + + /* If we're the first session, initialize the device. */ + if (first) { + R_TRY(device->GetDriver().SafeCastTo<IPwmDriver>().InitializeDevice(device)); + } + + /* We're opened. */ + guard.Cancel(); + R_SUCCEED(); + } + + void ChannelSessionImpl::Close() { + /* If we're not open, do nothing. */ + if (!this->IsOpen()) { + return; + } + + /* Get the device. */ + auto &device = this->GetDevice().SafeCastTo<IPwmDevice>(); + + /* Close the session. */ + ddsf::CloseSession(this); + + /* If there are no remaining sessions, finalize the device. */ + if (!device.HasAnyOpenSession()) { + device.GetDriver().SafeCastTo<IPwmDriver>().FinalizeDevice(std::addressof(device)); + } + } + + Result ChannelSessionImpl::SetPeriod(TimeSpan period) { + /* Get the device. */ + IPwmDevice &device = this->GetDevice().SafeCastTo<IPwmDevice>(); + + /* Invoke the driver handler. */ + R_RETURN(device.GetDriver().SafeCastTo<IPwmDriver>().SetPeriod(std::addressof(device), period)); + } + + Result ChannelSessionImpl::GetPeriod(TimeSpan *out) { + /* Get the device. */ + IPwmDevice &device = this->GetDevice().SafeCastTo<IPwmDevice>(); + + /* Invoke the driver handler. */ + R_RETURN(device.GetDriver().SafeCastTo<IPwmDriver>().GetPeriod(out, std::addressof(device))); + } + + Result ChannelSessionImpl::SetDuty(int duty) { + /* Get the device. */ + IPwmDevice &device = this->GetDevice().SafeCastTo<IPwmDevice>(); + + /* Invoke the driver handler. */ + R_RETURN(device.GetDriver().SafeCastTo<IPwmDriver>().SetDuty(std::addressof(device), duty)); + } + + Result ChannelSessionImpl::GetDuty(int *out) { + /* Get the device. */ + IPwmDevice &device = this->GetDevice().SafeCastTo<IPwmDevice>(); + + /* Invoke the driver handler. */ + R_RETURN(device.GetDriver().SafeCastTo<IPwmDriver>().GetDuty(out, std::addressof(device))); + } + + Result ChannelSessionImpl::SetEnabled(bool en) { + /* Get the device. */ + IPwmDevice &device = this->GetDevice().SafeCastTo<IPwmDevice>(); + + /* Invoke the driver handler. */ + R_RETURN(device.GetDriver().SafeCastTo<IPwmDriver>().SetEnabled(std::addressof(device), en)); + } + + Result ChannelSessionImpl::GetEnabled(bool *out) { + /* Get the device. */ + IPwmDevice &device = this->GetDevice().SafeCastTo<IPwmDevice>(); + + /* Invoke the driver handler. */ + R_RETURN(device.GetDriver().SafeCastTo<IPwmDriver>().GetEnabled(out, std::addressof(device))); + } + + Result ChannelSessionImpl::SetScale(double scale) { + /* Get the device. */ + IPwmDevice &device = this->GetDevice().SafeCastTo<IPwmDevice>(); + + /* Invoke the driver handler. */ + R_RETURN(device.GetDriver().SafeCastTo<IPwmDriver>().SetScale(std::addressof(device), scale)); + } + + Result ChannelSessionImpl::GetScale(double *out) { + /* Get the device. */ + IPwmDevice &device = this->GetDevice().SafeCastTo<IPwmDevice>(); + + /* Invoke the driver handler. */ + R_RETURN(device.GetDriver().SafeCastTo<IPwmDriver>().GetScale(out, std::addressof(device))); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/impl/pwm_channel_session_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/impl/pwm_channel_session_impl.hpp new file mode 100644 index 00000000..5a597e27 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/impl/pwm_channel_session_impl.hpp @@ -0,0 +1,77 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::pwm::driver::impl { + + class ChannelSessionImpl : public ::ams::ddsf::ISession { + NON_COPYABLE(ChannelSessionImpl); + NON_MOVEABLE(ChannelSessionImpl); + AMS_DDSF_CASTABLE_TRAITS(ams::pwm::driver::impl::ChannelSessionImpl, ::ams::ddsf::ISession); + public: + ChannelSessionImpl() { /* ... */ } + + ~ChannelSessionImpl() { + this->Close(); + } + + Result Open(IPwmDevice *device, ddsf::AccessMode access_mode); + void Close(); + + Result SetPeriod(TimeSpan period); + Result GetPeriod(TimeSpan *out); + + Result SetDuty(int duty); + Result GetDuty(int *out); + + Result SetEnabled(bool en); + Result GetEnabled(bool *out); + + Result SetScale(double scale); + Result GetScale(double *out); + }; + static_assert( sizeof(ChannelSessionImpl) <= ChannelSessionSize); + static_assert(alignof(ChannelSessionImpl) <= ChannelSessionAlign); + + struct alignas(ChannelSessionAlign) ChannelSessionImplPadded { + ChannelSessionImpl _impl; + u8 _padding[ChannelSessionSize - sizeof(ChannelSessionImpl)]; + }; + static_assert( sizeof(ChannelSessionImplPadded) == ChannelSessionSize); + static_assert(alignof(ChannelSessionImplPadded) == ChannelSessionAlign); + + ALWAYS_INLINE ChannelSessionImpl &GetChannelSessionImpl(ChannelSession &session) { + return GetReference(session._impl)._impl; + } + + ALWAYS_INLINE const ChannelSessionImpl &GetChannelSessionImpl(const ChannelSession &session) { + return GetReference(session._impl)._impl; + } + + ALWAYS_INLINE ChannelSessionImpl &GetOpenChannelSessionImpl(ChannelSession &session) { + auto &ref = GetReference(session._impl)._impl; + AMS_ASSERT(ref.IsOpen()); + return ref; + } + + ALWAYS_INLINE const ChannelSessionImpl &GetOpenChannelSessionImpl(const ChannelSession &session) { + const auto &ref = GetReference(session._impl)._impl; + AMS_ASSERT(ref.IsOpen()); + return ref; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/impl/pwm_driver_core.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/impl/pwm_driver_core.cpp new file mode 100644 index 00000000..bb5eacbe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/impl/pwm_driver_core.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pwm_driver_core.hpp" + +namespace ams::pwm::driver::impl { + + namespace { + + constinit os::SdkMutex g_init_mutex; + constinit int g_init_count = 0; + + pwm::driver::IPwmDriver::List &GetPwmDriverList() { + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(pwm::driver::IPwmDriver::List, s_driver_list); + return s_driver_list; + } + + ddsf::DeviceCodeEntryManager &GetDeviceCodeEntryManager() { + AMS_FUNCTION_LOCAL_STATIC(ddsf::DeviceCodeEntryManager, s_device_code_entry_manager, ddsf::GetDeviceCodeEntryHolderMemoryResource()); + + return s_device_code_entry_manager; + } + + } + + + void InitializeDrivers() { + std::scoped_lock lk(g_init_mutex); + + /* Initialize all registered drivers, if this is our first initialization. */ + if ((g_init_count++) == 0) { + for (auto &driver : GetPwmDriverList()) { + driver.SafeCastTo<IPwmDriver>().InitializeDriver(); + } + } + } + + void FinalizeDrivers() { + std::scoped_lock lk(g_init_mutex); + + /* If we have no remaining sessions, close. */ + if ((--g_init_count) == 0) { + /* Reset all device code entries. */ + GetDeviceCodeEntryManager().Reset(); + + /* Finalize all drivers. */ + for (auto &driver : GetPwmDriverList()) { + driver.SafeCastTo<IPwmDriver>().FinalizeDriver(); + } + } + } + + void RegisterDriver(IPwmDriver *driver) { + AMS_ASSERT(driver != nullptr); + GetPwmDriverList().push_back(*driver); + } + + void UnregisterDriver(IPwmDriver *driver) { + AMS_ASSERT(driver != nullptr); + if (driver->IsLinkedToList()) { + auto &list = GetPwmDriverList(); + list.erase(list.iterator_to(*driver)); + } + } + + Result RegisterDeviceCode(DeviceCode device_code, IPwmDevice *device) { + AMS_ASSERT(device != nullptr); + R_TRY(GetDeviceCodeEntryManager().Add(device_code, device)); + R_SUCCEED(); + } + + bool UnregisterDeviceCode(DeviceCode device_code) { + return GetDeviceCodeEntryManager().Remove(device_code); + } + + Result FindDevice(IPwmDevice **out, DeviceCode device_code) { + /* Validate output. */ + AMS_ASSERT(out != nullptr); + + /* Find the device. */ + ddsf::IDevice *device; + R_TRY(GetDeviceCodeEntryManager().FindDevice(std::addressof(device), device_code)); + + /* Set output. */ + *out = device->SafeCastToPointer<IPwmDevice>(); + R_SUCCEED(); + } + + Result FindDeviceByChannelIndex(IPwmDevice **out, int channel) { + /* Validate output. */ + AMS_ASSERT(out != nullptr); + + /* Find the device. */ + bool found = false; + GetDeviceCodeEntryManager().ForEachEntry([&](ddsf::DeviceCodeEntry &entry) -> bool { + /* Convert the entry to an IPwmDevice. */ + auto &device = entry.GetDevice().SafeCastTo<IPwmDevice>(); + + /* Check if the device is the one we're looking for. */ + if (device.GetChannelIndex() == channel) { + found = true; + *out = std::addressof(device); + return false; + } + return true; + }); + + /* Check that we found the pad. */ + R_UNLESS(found, ddsf::ResultDeviceCodeNotFound()); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/impl/pwm_driver_core.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/impl/pwm_driver_core.hpp new file mode 100644 index 00000000..f5b34e69 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/impl/pwm_driver_core.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::pwm::driver::impl { + + void InitializeDrivers(); + void FinalizeDrivers(); + + void RegisterDriver(IPwmDriver *driver); + void UnregisterDriver(IPwmDriver *driver); + + Result RegisterDeviceCode(DeviceCode device_code, IPwmDevice *device); + bool UnregisterDeviceCode(DeviceCode device_code); + + Result FindDevice(IPwmDevice **out, DeviceCode device_code); + Result FindDeviceByChannelIndex(IPwmDevice **out, int channel); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/pwm_driver_channel_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/pwm_driver_channel_api.cpp new file mode 100644 index 00000000..b0e900a9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/pwm_driver_channel_api.cpp @@ -0,0 +1,93 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#include "impl/pwm_driver_core.hpp" +#include "impl/pwm_channel_session_impl.hpp" + +namespace ams::pwm::driver { + + namespace { + + Result OpenSessionImpl(ChannelSession *out, IPwmDevice *device) { + /* Construct the session. */ + auto *session = std::construct_at(std::addressof(impl::GetChannelSessionImpl(*out))); + ON_RESULT_FAILURE { std::destroy_at(session); }; + + /* Open the session. */ + R_RETURN(session->Open(device, ddsf::AccessMode_ReadWrite)); + } + + } + + Result OpenSession(ChannelSession *out, DeviceCode device_code) { + AMS_ASSERT(out != nullptr); + + /* Find the device. */ + IPwmDevice *device = nullptr; + R_TRY(impl::FindDevice(std::addressof(device), device_code)); + AMS_ASSERT(device != nullptr); + + /* Open the session. */ + R_TRY(OpenSessionImpl(out, device)); + + R_SUCCEED(); + } + + void CloseSession(ChannelSession &session) { + std::destroy_at(std::addressof(impl::GetOpenChannelSessionImpl(session))); + } + + void SetPeriod(ChannelSession &session, TimeSpan period) { + R_ABORT_UNLESS(impl::GetOpenChannelSessionImpl(session).SetPeriod(period)); + } + + TimeSpan GetPeriod(ChannelSession &session) { + TimeSpan out_val; + R_ABORT_UNLESS(impl::GetOpenChannelSessionImpl(session).GetPeriod(std::addressof(out_val))); + return out_val; + } + + void SetDuty(ChannelSession &session, int duty) { + R_ABORT_UNLESS(impl::GetOpenChannelSessionImpl(session).SetDuty(duty)); + } + + int GetDuty(ChannelSession &session) { + int out_val; + R_ABORT_UNLESS(impl::GetOpenChannelSessionImpl(session).GetDuty(std::addressof(out_val))); + return out_val; + } + + void SetEnabled(ChannelSession &session, bool en) { + R_ABORT_UNLESS(impl::GetOpenChannelSessionImpl(session).SetEnabled(en)); + } + + bool GetEnabled(ChannelSession &session) { + bool out_val; + R_ABORT_UNLESS(impl::GetOpenChannelSessionImpl(session).GetEnabled(std::addressof(out_val))); + return out_val; + } + + void SetScale(ChannelSession &session, double scale) { + R_ABORT_UNLESS(impl::GetOpenChannelSessionImpl(session).SetScale(scale)); + } + + double GetScale(ChannelSession &session) { + double out_val; + R_ABORT_UNLESS(impl::GetOpenChannelSessionImpl(session).GetScale(std::addressof(out_val))); + return out_val; + } +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/pwm_driver_client_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/pwm_driver_client_api.cpp new file mode 100644 index 00000000..88dc2fdf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/pwm_driver_client_api.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#include "impl/pwm_driver_core.hpp" + +namespace ams::pwm::driver { + + void Initialize() { + return impl::InitializeDrivers(); + } + + void Finalize() { + return impl::FinalizeDrivers(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/pwm_driver_service_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/pwm_driver_service_api.cpp new file mode 100644 index 00000000..d86e2c6e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/driver/pwm_driver_service_api.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#include "impl/pwm_driver_core.hpp" + +namespace ams::pwm::driver { + + void RegisterDriver(IPwmDriver *driver) { + return impl::RegisterDriver(driver); + } + + void UnregisterDriver(IPwmDriver *driver) { + return impl::UnregisterDriver(driver); + } + + Result RegisterDeviceCode(DeviceCode device_code, IPwmDevice *device) { + R_RETURN(impl::RegisterDeviceCode(device_code, device)); + } + + bool UnregisterDeviceCode(DeviceCode device_code) { + return impl::UnregisterDeviceCode(device_code); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/pwm_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/pwm_api.cpp new file mode 100644 index 00000000..1aa0a7e5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/pwm_api.cpp @@ -0,0 +1,117 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::pwm { + + namespace { + + constinit os::SdkMutex g_init_mutex; + constinit int g_initialize_count = 0; + + ams::sf::SharedPointer<pwm::sf::IManager> g_pwm_manager; + + pwm::sf::IChannelSession *GetInterface(const ChannelSession &session) { + AMS_ASSERT(session._session != nullptr); + return static_cast<pwm::sf::IChannelSession *>(session._session); + } + } + + void InitializeWith(ams::sf::SharedPointer<pwm::sf::IManager> sp) { + std::scoped_lock lk(g_init_mutex); + + AMS_ABORT_UNLESS(g_initialize_count == 0); + + g_pwm_manager = sp; + + g_initialize_count = 1; + } + + void Finalize() { + std::scoped_lock lk(g_init_mutex); + + AMS_ASSERT(g_initialize_count > 0); + + if ((--g_initialize_count) == 0) { + g_pwm_manager.Reset(); + } + } + + Result OpenSession(ChannelSession *out, DeviceCode device_code) { + /* Get the session. */ + ams::sf::SharedPointer<pwm::sf::IChannelSession> session; + { + if (hos::GetVersion() >= hos::Version_6_0_0) { + R_TRY(g_pwm_manager->OpenSession2(std::addressof(session), device_code)); + } else { + R_TRY(g_pwm_manager->OpenSession(std::addressof(session), ConvertToChannelName(device_code))); + } + } + + /* Set output. */ + out->_session = session.Detach(); + + /* We succeeded. */ + R_SUCCEED(); + } + + void CloseSession(ChannelSession &session) { + /* Close the session. */ + ams::sf::ReleaseSharedObject(GetInterface(session)); + session._session = nullptr; + } + + void SetPeriod(ChannelSession &session, TimeSpan period) { + R_ABORT_UNLESS(GetInterface(session)->SetPeriod(period)); + } + + TimeSpan GetPeriod(ChannelSession &session) { + TimeSpanType out_val; + R_ABORT_UNLESS(GetInterface(session)->GetPeriod(std::addressof(out_val))); + return out_val; + } + + void SetDuty(ChannelSession &session, int duty) { + R_ABORT_UNLESS(GetInterface(session)->SetDuty(duty)); + } + + int GetDuty(ChannelSession &session) { + int out_val; + R_ABORT_UNLESS(GetInterface(session)->GetDuty(std::addressof(out_val))); + return out_val; + } + + void SetEnabled(ChannelSession &session, bool en) { + R_ABORT_UNLESS(GetInterface(session)->SetEnabled(en)); + } + + bool GetEnabled(ChannelSession &session) { + bool out_val; + R_ABORT_UNLESS(GetInterface(session)->GetEnabled(std::addressof(out_val))); + return out_val; + } + + void SetScale(ChannelSession &session, double scale) { + R_ABORT_UNLESS(GetInterface(session)->SetScale(scale)); + } + + double GetScale(ChannelSession &session) { + double out_val; + R_ABORT_UNLESS(GetInterface(session)->GetScale(std::addressof(out_val))); + return out_val; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/server/pwm_server_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/server/pwm_server_api.cpp new file mode 100644 index 00000000..eed5d561 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/server/pwm_server_api.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pwm_server_manager_impl.hpp" + +namespace ams::pwm::server { + + namespace { + + ams::sf::UnmanagedServiceObject<pwm::sf::IManager, pwm::server::ManagerImpl> g_manager_impl; + + } + + ams::sf::SharedPointer<pwm::sf::IManager> GetServiceObject() { + return g_manager_impl.GetShared(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/server/pwm_server_channel_session_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/server/pwm_server_channel_session_impl.hpp new file mode 100644 index 00000000..de76e67f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/server/pwm_server_channel_session_impl.hpp @@ -0,0 +1,88 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::pwm::server { + + class ManagerImpl; + + class ChannelSessionImpl { + private: + ManagerImpl *m_parent; /* NOTE: this is an sf::SharedPointer<> in Nintendo's code. */ + pwm::driver::ChannelSession m_internal_session; + bool m_has_session; + public: + explicit ChannelSessionImpl(ManagerImpl *p) : m_parent(p), m_has_session(false) { /* ... */ } + + ~ChannelSessionImpl() { + if (m_has_session) { + pwm::driver::CloseSession(m_internal_session); + } + } + + Result OpenSession(DeviceCode device_code) { + AMS_ABORT_UNLESS(!m_has_session); + + R_TRY(pwm::driver::OpenSession(std::addressof(m_internal_session), device_code)); + m_has_session = true; + R_SUCCEED(); + } + public: + /* Actual commands. */ + Result SetPeriod(TimeSpanType period) { + pwm::driver::SetPeriod(m_internal_session, period); + R_SUCCEED(); + } + + Result GetPeriod(ams::sf::Out<TimeSpanType> out) { + out.SetValue(pwm::driver::GetPeriod(m_internal_session)); + R_SUCCEED(); + } + + Result SetDuty(int duty) { + pwm::driver::SetDuty(m_internal_session, duty); + R_SUCCEED(); + } + + Result GetDuty(ams::sf::Out<int> out) { + out.SetValue(pwm::driver::GetDuty(m_internal_session)); + R_SUCCEED(); + } + + Result SetEnabled(bool enabled) { + pwm::driver::SetEnabled(m_internal_session, enabled); + R_SUCCEED(); + } + + Result GetEnabled(ams::sf::Out<bool> out) { + out.SetValue(pwm::driver::GetEnabled(m_internal_session)); + R_SUCCEED(); + } + + Result SetScale(double scale) { + pwm::driver::SetScale(m_internal_session, scale); + R_SUCCEED(); + } + + Result GetScale(ams::sf::Out<double> out) { + out.SetValue(pwm::driver::GetScale(m_internal_session)); + R_SUCCEED(); + } + }; + static_assert(pwm::sf::IsIChannelSession<ChannelSessionImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/server/pwm_server_manager_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/server/pwm_server_manager_impl.cpp new file mode 100644 index 00000000..4eb9fd51 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/server/pwm_server_manager_impl.cpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pwm_server_manager_impl.hpp" + +namespace ams::pwm::server { + + ManagerImpl::ManagerImpl() { + m_heap_handle = lmem::CreateExpHeap(m_heap_buffer, sizeof(m_heap_buffer), lmem::CreateOption_None); + m_allocator.Attach(m_heap_handle); + } + + ManagerImpl::~ManagerImpl() { + lmem::DestroyExpHeap(m_heap_handle); + } + + Result ManagerImpl::OpenSessionForDev(ams::sf::Out<ams::sf::SharedPointer<pwm::sf::IChannelSession>> out, int channel) { + /* TODO */ + AMS_UNUSED(out, channel); + AMS_ABORT(); + } + + Result ManagerImpl::OpenSession(ams::sf::Out<ams::sf::SharedPointer<pwm::sf::IChannelSession>> out, pwm::ChannelName channel_name) { + R_RETURN(this->OpenSession2(out, ConvertToDeviceCode(channel_name))); + } + + Result ManagerImpl::OpenSession2(ams::sf::Out<ams::sf::SharedPointer<pwm::sf::IChannelSession>> out, DeviceCode device_code) { + /* Allocate a session. */ + auto session = Factory::CreateSharedEmplaced<pwm::sf::IChannelSession, ChannelSessionImpl>(std::addressof(m_allocator), this); + + /* Open the session. */ + R_TRY(session.GetImpl().OpenSession(device_code)); + + /* We succeeded. */ + *out = std::move(session); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/server/pwm_server_manager_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/server/pwm_server_manager_impl.hpp new file mode 100644 index 00000000..4ad11694 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/pwm/server/pwm_server_manager_impl.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "pwm_server_channel_session_impl.hpp" + +namespace ams::pwm::server { + + class ManagerImpl { + private: + using Allocator = ams::sf::ExpHeapAllocator; + using Factory = ams::sf::ObjectFactory<Allocator::Policy>; + private: + lmem::HeapHandle m_heap_handle; + Allocator m_allocator; + u8 m_heap_buffer[4_KB]; + public: + ManagerImpl(); + + ~ManagerImpl(); + public: + /* Actual commands. */ + Result OpenSessionForDev(ams::sf::Out<ams::sf::SharedPointer<pwm::sf::IChannelSession>> out, int channel); + Result OpenSession(ams::sf::Out<ams::sf::SharedPointer<pwm::sf::IChannelSession>> out, pwm::ChannelName channel_name); + Result OpenSession2(ams::sf::Out<ams::sf::SharedPointer<pwm::sf::IChannelSession>> out, DeviceCode device_code); + }; + static_assert(pwm::sf::IsIManager<ManagerImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ro/impl/ro_ro_exception_info.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ro/impl/ro_ro_exception_info.os.horizon.cpp new file mode 100644 index 00000000..03956139 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/ro/impl/ro_ro_exception_info.os.horizon.cpp @@ -0,0 +1,111 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::ro::impl { + + namespace { + + bool SearchSegmentHead(uintptr_t *out, uintptr_t target, svc::MemoryState state) { + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + bool success = false; + + for (uintptr_t cur = target; cur <= target; cur = mem_info.base_address - 1) { + R_ABORT_UNLESS(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), cur)); + + if (mem_info.state != state || mem_info.permission != svc::MemoryPermission_ReadExecute) { + break; + } + + *out = mem_info.base_address; + success = true; + } + + return success; + } + + bool SearchSegmentTail(uintptr_t *out, uintptr_t target, svc::MemoryState state) { + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + bool success = false; + + for (uintptr_t cur = target; cur >= target; cur = mem_info.base_address + mem_info.size) { + R_ABORT_UNLESS(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), cur)); + + if (mem_info.state != state) { + break; + } + + *out = mem_info.base_address + mem_info.size - 1; + success = true; + } + + return success; + } + + bool QueryModule(uintptr_t *out_address, size_t *out_size, uintptr_t pc) { + /* Query the program counter. */ + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + R_ABORT_UNLESS(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), pc)); + + /* Check memory info. */ + if (mem_info.permission != svc::MemoryPermission_ReadExecute) { + return false; + } + + if (mem_info.state != svc::MemoryState_Code && mem_info.state != svc::MemoryState_AliasCode) { + return false; + } + + /* Find head/tail. */ + uintptr_t head = 0, tail = 0; + AMS_ABORT_UNLESS(SearchSegmentHead(std::addressof(head), pc, mem_info.state)); + AMS_ABORT_UNLESS(SearchSegmentTail(std::addressof(tail), pc, mem_info.state)); + AMS_ABORT_UNLESS(SearchSegmentTail(std::addressof(tail), tail + 1, mem_info.state == svc::MemoryState_Code ? svc::MemoryState_CodeData : svc::MemoryState_AliasCodeData)); + + /* Set output. */ + *out_address = head; + *out_size = tail + 1 - head; + + return true; + } + + } + + bool GetExceptionInfo(ExceptionInfo *out, uintptr_t pc) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + + /* Find the module. */ + if (!QueryModule(std::addressof(out->module_address), std::addressof(out->module_size), pc)) { + return false; + } + + /* Validate the module. */ + rocrt::ModuleHeaderLocation *loc = reinterpret_cast<rocrt::ModuleHeaderLocation *>(out->module_address); + rocrt::ModuleHeader *header = rocrt::GetModuleHeader(loc); + AMS_ABORT_UNLESS(header->signature == rocrt::ModuleHeaderVersion); + + /* Set the exception info. */ + out->info_offset = loc->header_offset + header->exception_info_start_offset; + out->info_size = header->exception_info_end_offset - header->exception_info_start_offset; + + return true; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/scs/scs_command_processor.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/scs/scs_command_processor.cpp new file mode 100644 index 00000000..1e51b05d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/scs/scs_command_processor.cpp @@ -0,0 +1,159 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::scs { + + namespace { + + struct ResponseError { + ResponseHeader header; + u32 result; + }; + + struct ResponseProgramExited { + ResponseHeader header; + u64 process_id; + }; + + struct ResponseProgramLaunched { + ResponseHeader header; + u64 process_id; + }; + + constinit os::SdkMutex g_htcs_send_mutex; + + } + + std::scoped_lock<os::SdkMutex> CommandProcessor::MakeSendGuardBlock() { + return std::scoped_lock<os::SdkMutex>{g_htcs_send_mutex}; + } + + void CommandProcessor::Send(s32 socket, const void *data, size_t size) { + htcs::Send(socket, data, size, 0); + } + + void CommandProcessor::SendSuccess(s32 socket, const CommandHeader &header) { + /* Build the response. */ + const ResponseHeader response = { + .id = header.id, + .response = Response_Success, + .body_size = 0, + }; + + /* Send the response. */ + auto lk = MakeSendGuardBlock(); + Send(socket, std::addressof(response), sizeof(response)); + } + + void CommandProcessor::SendErrorResult(s32 socket, const CommandHeader &header, Result result) { + return SendErrorResult(socket, header.id, result); + } + + void CommandProcessor::SendErrorResult(s32 socket, u64 id, Result result) { + /* Build the response. */ + const ResponseError response = { + .header = { + .id = id, + .response = Response_Error, + .body_size = sizeof(response) - sizeof(response.header), + }, + .result = result.GetValue(), + }; + + /* Send the response. */ + auto lk = MakeSendGuardBlock(); + Send(socket, std::addressof(response), sizeof(response)); + } + + void CommandProcessor::SendExited(s32 socket, u64 id, u64 process_id) { + /* Build the response. */ + const ResponseProgramExited response = { + .header = { + .id = id, + .response = Response_ProgramExited, + .body_size = sizeof(response) - sizeof(response.header), + }, + .process_id = process_id, + }; + + /* Send the response. */ + auto lk = MakeSendGuardBlock(); + Send(socket, std::addressof(response), sizeof(response)); + } + + void CommandProcessor::SendJitDebug(s32 socket, u64 id) { + /* Build the response. */ + const ResponseHeader response = { + .id = id, + .response = Response_JitDebug, + .body_size = 0, + }; + + /* Send the response. */ + auto lk = MakeSendGuardBlock(); + Send(socket, std::addressof(response), sizeof(response)); + } + + void CommandProcessor::SendLaunched(s32 socket, u64 id, u64 process_id) { + /* Build the response. */ + const ResponseProgramLaunched response = { + .header = { + .id = id, + .response = Response_ProgramLaunched, + .body_size = sizeof(response) - sizeof(response.header), + }, + .process_id = process_id, + }; + + /* Send the response. */ + auto lk = MakeSendGuardBlock(); + Send(socket, std::addressof(response), sizeof(response)); + } + + void CommandProcessor::OnProcessStart(u64 id, s32 socket, os::ProcessId process_id) { + SendLaunched(socket, id, process_id.value); + } + + void CommandProcessor::OnProcessExit(u64 id, s32 socket, os::ProcessId process_id) { + SendExited(socket, id, process_id.value); + } + + void CommandProcessor::OnProcessJitDebug(u64 id, s32 socket, os::ProcessId process_id) { + AMS_UNUSED(process_id); + + SendJitDebug(socket, id); + } + + void CommandProcessor::Initialize() { + /* Register our process event handlers. */ + scs::RegisterCommonProcessEventHandler(OnProcessStart, OnProcessExit, OnProcessJitDebug); + } + + bool CommandProcessor::ProcessCommand(const CommandHeader &header, const u8 *body, s32 socket) { + AMS_UNUSED(body); + + switch (header.command) { + /* TODO: Support commands. */ + default: + SendErrorResult(socket, header, scs::ResultUnknownCommand()); + break; + } + + return true; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/scs/scs_server_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/scs/scs_server_manager.cpp new file mode 100644 index 00000000..b7413c44 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/scs/scs_server_manager.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::scs { + + namespace { + + ServerManager g_server_manager; + + } + + ServerManager *GetServerManager() { + return std::addressof(g_server_manager); + } + + void StartServer() { + /* Start the server. */ + g_server_manager.ResumeProcessing(); + + /* Loop processing the server. */ + g_server_manager.LoopProcess(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/scs/scs_shell.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/scs/scs_shell.cpp new file mode 100644 index 00000000..841b36e4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/scs/scs_shell.cpp @@ -0,0 +1,398 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::scs { + + namespace { + + struct SocketInfo { + u64 id; + s32 socket; + }; + + struct ProgramInfo { + os::ProcessId process_id; + u64 id; + s32 socket; + s32 info_id; + bool started; + bool jit_debug; + bool launched_by_cs; + }; + + constexpr inline auto MaxSocketInfo = 2; + constexpr inline auto MaxProgramInfo = 64; + + class SocketInfoManager { + private: + SocketInfo m_infos[MaxSocketInfo]{}; + int m_count{}; + public: + constexpr SocketInfoManager() = default; + + void Initialize() { + /* Clear our count. */ + m_count = 0; + } + + Result Register(s32 socket, u64 id) { + /* Check that the socket isn't already registered. */ + for (auto i = 0; i < m_count; ++i) { + R_UNLESS(m_infos[i].socket != socket, scs::ResultNoSocket()); + } + + /* Check that we can allocate a new socket info. */ + if (m_count >= MaxSocketInfo) { + /* NOTE: Nintendo aborts with this result here. */ + R_ABORT_UNLESS(scs::ResultOutOfResource()); + } + + /* Create the new socket info. */ + m_infos[m_count++] = { + .id = id, + .socket = socket, + }; + + R_SUCCEED(); + } + + void Unregister(s32 socket) { + /* Unregister the socket, if it's registered. */ + for (auto i = 0; i < m_count; ++i) { + if (m_infos[i].socket == socket) { + /* Ensure that the valid socket infos remain in bounds. */ + std::memcpy(m_infos + i, m_infos + i + 1, (m_count - (i + 1)) * sizeof(*m_infos)); + + /* Note that we now have one fewer socket info. */ + --m_count; + + break; + } + } + } + + void InvokeHandler(ProcessEventHandler handler, os::ProcessId process_id) { + /* Invoke the handler on all our sockets. */ + for (auto i = 0; i < m_count; ++i) { + handler(m_infos[i].id, m_infos[i].socket, process_id); + } + } + }; + + class ProgramInfoManager { + private: + s32 m_next_info_id{}; + ProgramInfo m_infos[MaxProgramInfo]{}; + int m_count{}; + public: + constexpr ProgramInfoManager() = default; + + void Initialize() { + /* Reset our next id. */ + m_next_info_id = 1; + + /* Clear our count. */ + m_count = 0; + } + + const ProgramInfo *Find(os::ProcessId process_id) const { + /* Find a matching program. */ + for (auto i = 0; i < m_count; ++i) { + if (m_infos[i].process_id == process_id) { + return std::addressof(m_infos[i]); + } + } + return nullptr; + } + + bool Register(os::ProcessId process_id) { + /* Allocate an info id. */ + const auto info_id = m_next_info_id++; + + /* Check that we have space for the program. */ + if (m_count >= MaxProgramInfo) { + return false; + } + + /* Create the new program info. */ + m_infos[m_count++] = { + .process_id = process_id, + .id = 0, + .socket = 0, + .info_id = info_id, + .started = false, + .jit_debug = false, + .launched_by_cs = false, + }; + + return true; + } + + void Unregister(os::ProcessId process_id) { + /* Unregister the program, if it's registered. */ + for (auto i = 0; i < m_count; ++i) { + if (m_infos[i].process_id == process_id) { + /* Ensure that the valid program infos remain in bounds. */ + std::memcpy(m_infos + i, m_infos + i + 1, (m_count - (i + 1)) * sizeof(*m_infos)); + + /* Note that we now have one fewer program info. */ + --m_count; + + break; + } + } + } + + void SetStarted(os::ProcessId process_id) { + /* Start the program. */ + for (auto i = 0; i < m_count; ++i) { + if (m_infos[i].process_id == process_id) { + m_infos[i].started = true; + break; + } + } + } + + void SetJitDebug(os::ProcessId process_id) { + /* Set the program as jit debug. */ + for (auto i = 0; i < m_count; ++i) { + if (m_infos[i].process_id == process_id) { + m_infos[i].jit_debug = true; + break; + } + } + } + }; + + alignas(os::ThreadStackAlignment) constinit u8 g_thread_stack[os::MemoryPageSize]; + constinit os::ThreadType g_thread = {}; + + constinit ProcessEventHandler g_common_start_handler; + constinit ProcessEventHandler g_common_exit_handler; + constinit ProcessEventHandler g_common_jit_debug_handler; + + constinit SocketInfoManager g_socket_info_manager; + constinit ProgramInfoManager g_program_info_manager; + + constinit os::SdkMutex g_manager_mutex; + + void ProcessExitEvent(const pm::ProcessEventInfo &event_info) { + /* Unregister the target environment definition. */ + htc::tenv::UnregisterDefinitionFilePath(event_info.process_id); + + /* Unregister program info. */ + ProgramInfo program_info; + bool found = false; + { + std::scoped_lock lk(g_manager_mutex); + + if (const ProgramInfo *pi = g_program_info_manager.Find(event_info.process_id); pi != nullptr) { + program_info = *pi; + found = true; + + g_program_info_manager.Unregister(event_info.process_id); + } + } + + /* If we found the program, handle callbacks. */ + if (found) { + /* Invoke the common exit handler. */ + if (program_info.launched_by_cs) { + g_common_exit_handler(program_info.id, program_info.socket, program_info.process_id); + } + + /* Notify the process event. */ + if (program_info.started) { + std::scoped_lock lk(g_manager_mutex); + + g_socket_info_manager.InvokeHandler(g_common_exit_handler, program_info.process_id); + } + } + } + + void ProcessStartedEvent(const pm::ProcessEventInfo &event_info) { + /* Start the program (registering it, if needed). */ + { + std::scoped_lock lk(g_manager_mutex); + + if (g_program_info_manager.Find(event_info.process_id) == nullptr) { + AMS_ABORT_UNLESS(g_program_info_manager.Register(event_info.process_id)); + } + + g_program_info_manager.SetStarted(event_info.process_id); + } + + /* Handle callbacks. */ + { + std::scoped_lock lk(g_manager_mutex); + + g_socket_info_manager.InvokeHandler(g_common_start_handler, event_info.process_id); + } + } + + void ProcessExceptionEvent(const pm::ProcessEventInfo &event_info) { + /* Find the program info. */ + ProgramInfo program_info; + bool found = false; + { + std::scoped_lock lk(g_manager_mutex); + + if (const ProgramInfo *pi = g_program_info_manager.Find(event_info.process_id); pi != nullptr) { + program_info = *pi; + found = true; + } + + /* Set the program as jit debug. */ + g_program_info_manager.SetJitDebug(event_info.process_id); + } + + /* If we found the program, handle callbacks. */ + if (found) { + /* Invoke the common exception handler. */ + if (program_info.launched_by_cs) { + g_common_jit_debug_handler(program_info.id, program_info.socket, program_info.process_id); + } + + /* Notify the process event. */ + if (program_info.started) { + std::scoped_lock lk(g_manager_mutex); + + g_socket_info_manager.InvokeHandler(g_common_jit_debug_handler, program_info.process_id); + } + } + } + + void EventHandlerThread(void *) { + /* Get event observer. */ + pgl::EventObserver observer; + R_ABORT_UNLESS(pgl::GetEventObserver(std::addressof(observer))); + + /* Get the observer's event. */ + os::SystemEventType shell_event; + R_ABORT_UNLESS(observer.GetSystemEvent(std::addressof(shell_event))); + + /* Loop handling events. */ + while (true) { + /* Wait for an event to come in. */ + os::WaitSystemEvent(std::addressof(shell_event)); + + /* Loop processing event infos. */ + while (true) { + /* Get the next event info. */ + pm::ProcessEventInfo event_info; + if (R_FAILED(observer.GetProcessEventInfo(std::addressof(event_info)))) { + break; + } + + /* Process the event. */ + switch (event_info.GetProcessEvent()) { + case pm::ProcessEvent::Exited: + ProcessExitEvent(event_info); + break; + case pm::ProcessEvent::Started: + ProcessStartedEvent(event_info); + break; + case pm::ProcessEvent::Exception: + ProcessExceptionEvent(event_info); + break; + default: + break; + } + } + } + } + + void StartEventHandlerThread() { + /* Create the handler thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(g_thread), EventHandlerThread, nullptr, g_thread_stack, sizeof(g_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(scs, ShellEventHandler))); + + /* Set the handler thread's name. */ + os::SetThreadNamePointer(std::addressof(g_thread), AMS_GET_SYSTEM_THREAD_NAME(scs, ShellEventHandler)); + + /* Start the handler thread. */ + os::StartThread(std::addressof(g_thread)); + } + + Result PrepareToLaunchProgram(ncm::ProgramId program_id, const void *args, size_t args_size) { + /* Set the arguments. */ + R_TRY_CATCH(ldr::SetProgramArgument(program_id, args, args_size)) { + R_CATCH(ldr::ResultArgumentCountOverflow) { + /* There are too many arguments already registered. Flush the arguments queue. */ + R_TRY(ldr::FlushArguments()); + + /* Try again. */ + R_TRY(ldr::SetProgramArgument(program_id, args, args_size)); + } + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + void FlushProgramArgument(ncm::ProgramId program_id) { + /* Ensure there are no arguments for the program. */ + ldr::SetProgramArgument(program_id, "", 1); + } + + } + + void InitializeShell() { + /* Initialize our managers. */ + g_socket_info_manager.Initialize(); + g_program_info_manager.Initialize(); + + /* Start our event handler. */ + StartEventHandlerThread(); + } + + void RegisterCommonProcessEventHandler(ProcessEventHandler on_start, ProcessEventHandler on_exit, ProcessEventHandler on_jit_debug) { + g_common_start_handler = on_start; + g_common_exit_handler = on_exit; + g_common_jit_debug_handler = on_jit_debug; + } + + Result RegisterSocket(s32 socket, u64 id) { + /* Acquire exclusive access to the socket info manager. */ + std::scoped_lock lk(g_manager_mutex); + + /* Register the socket. */ + R_RETURN(g_socket_info_manager.Register(socket, id)); + } + + void UnregisterSocket(s32 socket) { + /* Acquire exclusive access to the socket info manager. */ + std::scoped_lock lk(g_manager_mutex); + + /* Unregister the socket. */ + return g_socket_info_manager.Unregister(socket); + } + + Result LaunchProgram(os::ProcessId *out, const ncm::ProgramLocation &loc, const void *args, size_t args_size, u32 process_flags) { + /* Set up the arguments. */ + PrepareToLaunchProgram(loc.program_id, args, args_size); + + /* Ensure arguments are managed correctly. */ + ON_SCOPE_EXIT { FlushProgramArgument(loc.program_id); }; + + /* Launch the program. */ + R_TRY(pgl::LaunchProgram(out, loc, process_flags | pm::LaunchFlags_SignalOnExit, 0)); + + R_SUCCEED(); + } + + Result SubscribeProcessEvent(s32 socket, bool is_register, u64 id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/scs/scs_shell_server.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/scs/scs_shell_server.cpp new file mode 100644 index 00000000..af977b9b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/scs/scs_shell_server.cpp @@ -0,0 +1,153 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::scs { + + namespace { + + s32 CreateSocket() { + while (true) { + /* Try to create a socket. */ + if (const auto desc = htcs::Socket(); desc >= 0) { + return desc; + } + + /* Wait 100ms before trying again. */ + os::SleepThread(TimeSpan::FromMilliSeconds(100)); + } + } + + s32 AcceptSocket(s32 listen_socket) { + htcs::SockAddrHtcs temp; + return htcs::Accept(listen_socket, std::addressof(temp)); + } + + s32 Bind(s32 socket, const htcs::HtcsPortName &port_name) { + /* Set up the bind address. */ + htcs::SockAddrHtcs addr; + addr.family = htcs::HTCS_AF_HTCS; + addr.peer_name = htcs::GetPeerNameAny(); + std::strcpy(addr.port_name.name, port_name.name); + + /* Bind. */ + return htcs::Bind(socket, std::addressof(addr)); + } + + htcs::ssize_t Receive(s32 socket, void *buffer, size_t size) { + u8 *dst = static_cast<u8 *>(buffer); + size_t received = 0; + + while (received < size) { + const auto ret = htcs::Recv(socket, dst + received, size - received, 0); + if (ret <= 0) { + return ret; + } + + received += ret; + } + + return static_cast<htcs::ssize_t>(received); + } + + bool ReceiveCommand(CommandHeader *header, void *buffer, size_t buffer_size, s32 socket) { + /* Receive the header. */ + if (Receive(socket, header, sizeof(*header)) != sizeof(*header)) { + return false; + } + + /* Check that the body will fit in the buffer. */ + if (header->body_size >= buffer_size) { + return false; + } + + /* Receive the body. */ + if(Receive(socket, buffer, header->body_size) != static_cast<htcs::ssize_t>(header->body_size)) { + return false; + } + + return true; + } + + } + + void ShellServer::Initialize(const char *port_name, void *stack, size_t stack_size, CommandProcessor *command_processor) { + /* Set our variables. */ + m_command_processor = command_processor; + std::strcpy(m_port_name.name, port_name); + + /* Create our thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_thread), ThreadEntry, this, stack, stack_size, AMS_GET_SYSTEM_THREAD_PRIORITY(scs, ShellServer))); + + /* Set our thread's name. */ + os::SetThreadNamePointer(std::addressof(m_thread), AMS_GET_SYSTEM_THREAD_NAME(scs, ShellServer)); + } + + void ShellServer::Start() { + os::StartThread(std::addressof(m_thread)); + } + + void ShellServer::DoShellServer() { + /* Loop servicing the shell server. */ + while (true) { + /* Create a socket to listen on. */ + const auto listen_socket = CreateSocket(); + ON_SCOPE_EXIT { htcs::Close(listen_socket); }; + + /* Bind to the listen socket. */ + if (Bind(listen_socket, m_port_name) != 0) { + continue; + } + + /* Loop processing on our bound socket. */ + while (true) { + /* Listen on the socket. */ + if (const s32 listen_result = htcs::Listen(listen_socket, 0); listen_result != 0) { + /* TODO: logging. */ + break; + } + + /* Accept a socket. */ + const s32 socket = AcceptSocket(listen_socket); + if (socket <= 0) { + /* TODO: logging. */ + continue; + } + + /* Ensure that the socket is cleaned up when we're done with it. */ + ON_SCOPE_EXIT { + UnregisterSocket(socket); + htcs::Close(socket); + }; + + /* Loop servicing the socket. */ + while (true) { + /* Receive a command header. */ + CommandHeader header; + if (!ReceiveCommand(std::addressof(header), m_buffer, sizeof(m_buffer), socket)) { + break; + } + + /* Process the command. */ + if (!m_command_processor->ProcessCommand(header, m_buffer, socket)) { + break; + } + } + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/scs/scs_tenv.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/scs/scs_tenv.cpp new file mode 100644 index 00000000..a175fba2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/scs/scs_tenv.cpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::scs { + + namespace { + + alignas(os::MemoryPageSize) constinit u8 g_tenv_heap_storage[48_KB]; + constinit lmem::HeapHandle g_tenv_heap_handle = nullptr; + constinit os::SdkMutex g_mutex; + + void InitializeExpHeap() { + std::scoped_lock lk(g_mutex); + g_tenv_heap_handle = lmem::CreateExpHeap(g_tenv_heap_storage, sizeof(g_tenv_heap_storage), lmem::CreateOption_None); + } + + void *Allocate(size_t size) { + std::scoped_lock lk(g_mutex); + void *mem = lmem::AllocateFromExpHeap(g_tenv_heap_handle, size); + return mem; + } + + void Deallocate(void *p, size_t size) { + AMS_UNUSED(size); + + std::scoped_lock lk(g_mutex); + lmem::FreeToExpHeap(g_tenv_heap_handle, p); + } + + } + + void InitializeTenvServiceManager() { + /* Initialize the tenv heap. */ + InitializeExpHeap(); + + /* Initialize the tenv library. */ + htc::tenv::Initialize(Allocate, Deallocate); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/service_guard.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/service_guard.h new file mode 100644 index 00000000..d7ce9a7a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/service_guard.h @@ -0,0 +1,65 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include <switch/types.h> +#include <switch/result.h> +#include <switch/kernel/mutex.h> +#include <switch/sf/service.h> +#include <switch/services/sm.h> + +typedef struct ServiceGuard { + Mutex mutex; + u32 refCount; +} ServiceGuard; + +NX_INLINE bool serviceGuardBeginInit(ServiceGuard* g) +{ + mutexLock(&g->mutex); + return (g->refCount++) == 0; +} + +NX_INLINE Result serviceGuardEndInit(ServiceGuard* g, Result rc, void (*cleanupFunc)(void)) +{ + if (R_FAILED(rc)) { + cleanupFunc(); + --g->refCount; + } + mutexUnlock(&g->mutex); + return rc; +} + +NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void)) +{ + mutexLock(&g->mutex); + if (g->refCount && (--g->refCount) == 0) + cleanupFunc(); + mutexUnlock(&g->mutex); +} + +#define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \ +\ +static ServiceGuard g_##name##Guard; \ +NX_INLINE Result _##name##Initialize _paramdecl; \ +static void _##name##Cleanup(void); \ +\ +Result name##Initialize _paramdecl \ +{ \ + Result rc = 0; \ + if (serviceGuardBeginInit(&g_##name##Guard)) \ + rc = _##name##Initialize _parampass; \ + return serviceGuardEndInit(&g_##name##Guard, rc, _##name##Cleanup); \ +} \ +\ +void name##Exit(void) \ +{ \ + serviceGuardExit(&g_##name##Guard, _##name##Cleanup); \ +} + +#define NX_GENERATE_SERVICE_GUARD(name) NX_GENERATE_SERVICE_GUARD_PARAMS(name, (void), ()) + +#ifdef __cplusplus +} +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_configuration_id_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_configuration_id_impl.cpp new file mode 100644 index 00000000..bd21de01 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_configuration_id_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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "settings_serial_number_impl.hpp" + +namespace ams::settings::impl { + + Result GetConfigurationId1(settings::factory::ConfigurationId1 *out) { + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(sizeof(*out) == sizeof(::SetCalConfigurationId1)); + R_RETURN(::setcalGetConfigurationId1(reinterpret_cast<::SetCalConfigurationId1 *>(out))); + #else + AMS_UNUSED(out); + AMS_ABORT("TODO"); + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_configuration_id_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_configuration_id_impl.hpp new file mode 100644 index 00000000..8af41ea5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_configuration_id_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::settings::impl { + + Result GetConfigurationId1(settings::factory::ConfigurationId1 *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_error_report_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_error_report_impl.cpp new file mode 100644 index 00000000..81f56bdb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_error_report_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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "settings_error_report_impl.hpp" + +namespace ams::settings::impl { + + Result GetErrorReportSharePermission(s32 *out) { + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(sizeof(*out) == sizeof(::SetSysErrorReportSharePermission)); + R_RETURN(::setsysGetErrorReportSharePermission(reinterpret_cast<::SetSysErrorReportSharePermission *>(out))); + #else + AMS_UNUSED(out); + AMS_ABORT("TODO"); + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_error_report_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_error_report_impl.hpp new file mode 100644 index 00000000..58a5cb4a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_error_report_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::settings::impl { + + Result GetErrorReportSharePermission(s32 *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_firmware_version_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_firmware_version_impl.cpp new file mode 100644 index 00000000..b0a12062 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_firmware_version_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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "settings_firmware_version_impl.hpp" + +namespace ams::settings::impl { + + Result GetFirmwareVersion(settings::system::FirmwareVersion *out) { + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(sizeof(*out) == sizeof(::SetSysFirmwareVersion)); + R_RETURN(::setsysGetFirmwareVersion(reinterpret_cast<::SetSysFirmwareVersion *>(out))); + #else + AMS_UNUSED(out); + AMS_ABORT("TODO"); + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_firmware_version_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_firmware_version_impl.hpp new file mode 100644 index 00000000..9cd7b0b0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_firmware_version_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::settings::impl { + + Result GetFirmwareVersion(settings::system::FirmwareVersion *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_key_value_store.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_key_value_store.cpp new file mode 100644 index 00000000..d15e42c1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_key_value_store.cpp @@ -0,0 +1,1588 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "settings_key_value_store.hpp" +#include "settings_spl.hpp" +#include "settings_system_data.hpp" + +namespace ams::settings::impl { + + namespace { + constexpr fs::SystemSaveDataId SystemSaveDataId = 0x8000000000000051; + constexpr u32 SystemSaveDataFlags = fs::SaveDataFlags_KeepAfterRefurbishment | fs::SaveDataFlags_KeepAfterResettingSystemSaveData; + constexpr s64 SystemSaveDataSize = 544_KB; + constexpr s64 SystemSaveDataJournalSize = 544_KB; + + constexpr inline const char FwdbgSystemDataMountName[] = "FwdbgSettingsD"; + constexpr inline const char PfCfgSystemDataMountName[] = "PfcfgSettingsD"; + constexpr inline const char SystemSaveDataMountName[] = "FwdbgSettingsS"; + + constexpr inline const char SettingsNameSeparator = '!'; + + /* Type forward declarations. */ + class MapKey; + struct MapValue; + template<typename T> + class Allocator; + using Map = std::map<MapKey, MapValue, std::less<MapKey>, Allocator<std::pair<const MapKey, MapValue>>>; + + /* Function forward declarations. */ + void FreeMapValueToHeap(const MapValue &value); + void *AllocateFromHeap(size_t size); + void FreeToHeap(void *block, size_t size); + lmem::HeapHandle &GetHeapHandle(); + Result GetKeyValueStoreMap(Map **out); + Result GetKeyValueStoreMap(Map **out, bool force_load); + Result GetKeyValueStoreMapForciblyForDebug(Map **out); + Result LoadKeyValueStoreMap(Map *out); + Result LoadKeyValueStoreMap(Map *out, SplHardwareType hardware_type); + template<typename T> + Result LoadKeyValueStoreMapCurrent(Map *out, T &data); + template<typename T> + Result LoadKeyValueStoreMapDefault(Map *out, T &data); + template<typename T, typename F> + Result LoadKeyValueStoreMapEntries(Map *out, T &data, F load); + template<typename T, typename F> + Result LoadKeyValueStoreMapEntry(Map *out, T &data, s64 &offset, F load); + Result LoadKeyValueStoreMapForDebug(Map *out, SystemSaveData *system_save_data, SystemSaveData *fwdbg_system_data, SystemSaveData *pfcfg_system_data); + template<typename T> + Result ReadData(T &data, s64 &offset, void *buffer, size_t size); + template<typename T> + Result ReadDataToHeap(T &data, s64 &offset, void **buffer, size_t size); + template<typename T> + Result ReadAllBytes(T *data, u64 *out_count, char * const out_buffer, size_t out_buffer_size); + template<typename T> + Result SaveKeyValueStoreMapCurrent(T &data, const Map &map); + + struct SystemDataTag { + struct Fwdbg{}; + struct PfCfg{}; + }; + + class MapKey { + public: + static constexpr size_t MaxKeySize = sizeof(SettingsName) + sizeof(SettingsItemKey); + private: + char *m_chars; + size_t m_count; + public: + MapKey(const char * const chars) : m_chars(nullptr), m_count(0) { + AMS_ASSERT(chars != nullptr); + this->Assign(chars, util::Strnlen(chars, MaxKeySize)); + } + + MapKey(const char * const chars, s32 count) : m_chars(nullptr), m_count(0) { + AMS_ASSERT(chars != nullptr); + AMS_ASSERT(count >= 0); + this->Assign(chars, count); + } + + MapKey(MapKey &&other) : m_chars(nullptr), m_count(0) { + std::swap(m_chars, other.m_chars); + std::swap(m_count, other.m_count); + } + + MapKey(const MapKey &other) : m_chars(nullptr), m_count(0) { + this->Assign(other.GetString(), other.GetCount()); + } + + ~MapKey() { + this->Reset(); + } + + MapKey &Append(char c) { + const char chars[2] = { c, 0 }; + return this->Append(chars, 1); + } + + MapKey &Append(const char * const chars, s32 count) { + AMS_ASSERT(chars != nullptr); + AMS_ASSERT(count >= 0); + + /* Allocate the new key. */ + const size_t new_count = m_count + count; + char *new_heap = static_cast<char *>(AllocateFromHeap(new_count)); + + /* Copy the existing string to the new heap. */ + std::memcpy(new_heap, this->GetString(), this->GetCount()); + + /* Copy the string to append to the new heap. */ + std::memcpy(new_heap + this->GetCount(), chars, count); + + /* Null-terminate the new string. */ + new_heap[new_count - 1] = '\x00'; + + /* Reset and update the key. */ + this->Reset(); + m_count = new_count; + m_chars = new_heap; + return *this; + } + + MapKey &Assign(const char * const chars, s32 count) { + AMS_ASSERT(chars != nullptr); + AMS_ASSERT(count >= 0); + + /* Reset the key. */ + this->Reset(); + + /* Update the count and allocate the buffer. */ + m_count = count + 1; + m_chars = static_cast<char *>(AllocateFromHeap(m_count)); + + /* Copy the characters to the buffer. */ + std::memcpy(m_chars, chars, count); + m_chars[count] = '\x00'; + return *this; + } + + size_t Find(const MapKey &other) const { + return std::search(this->GetString(), this->GetString() + this->GetCount(), other.GetString(), other.GetString() + other.GetCount()) - this->GetString(); + } + + s32 GetCount() const { + return static_cast<s32>(m_count) - 1; + } + + const char *GetString() const { + return m_chars; + } + private: + void Reset() { + if (m_chars != nullptr) { + FreeToHeap(m_chars, m_count); + m_chars = nullptr; + m_count = 0; + } + } + }; + + inline bool operator<(const MapKey &lhs, const MapKey &rhs) { + return std::strncmp(lhs.GetString(), rhs.GetString(), std::max(lhs.GetCount(), rhs.GetCount())) < 0; + } + + MapKey MakeMapKey(const SettingsName &name, const SettingsItemKey &item_key) { + /* Create a map key. */ + MapKey key(name.value, util::Strnlen(name.value, util::size(name.value))); + + /* Append the settings name separator followed by the item key. */ + key.Append(SettingsNameSeparator); + key.Append(item_key.value, util::Strnlen(item_key.value, util::size(item_key.value))); + + /* Output the map key. */ + return key; + } + + struct MapValue { + public: + u8 type; + size_t current_value_size; + size_t default_value_size; + void *current_value; + void *default_value; + }; + static_assert(sizeof(MapValue) == 0x28); + + template<typename T> + class Allocator { + public: + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + public: + Allocator() noexcept = default; + ~Allocator() noexcept = default; + + Allocator(const Allocator &) noexcept = default; + Allocator(Allocator &&) noexcept = default; + + T *allocate(size_t n) noexcept { + return static_cast<T *>(AllocateFromHeap(sizeof(T) * n)); + } + + void deallocate(T *p, size_t n) noexcept { + FreeToHeap(p, sizeof(T) * n); + } + private: + Allocator &operator=(const Allocator &) noexcept = default; + Allocator &operator=(Allocator &&) noexcept = default; + }; + + template<class T, class U> + constexpr inline bool operator==(const Allocator<T> &, const Allocator<U> &) { + return true; + } + + constexpr inline size_t MapKeyBufferSize = MapKey::MaxKeySize * 2; + constexpr inline size_t MapEntryBufferSize = 0x40 + sizeof(Map::value_type); + + constexpr inline size_t HeapMemorySize = 512_KB; + + constinit os::SdkMutex g_key_value_store_mutex; + + void ClearKeyValueStoreMap(Map &map) { + /* Free all values to the heap. */ + for (const auto &kv_pair : map) { + FreeMapValueToHeap(kv_pair.second); + } + + /* Clear the map. */ + map.clear(); + } + + bool CompareValue(const void *lhs, size_t lhs_size, const void *rhs, size_t rhs_size) { + /* Check if both buffers are the same. */ + if (lhs == rhs) { + return true; + } + + /* If the value sizes don't match, return false. */ + if (lhs_size != rhs_size) { + return false; + } + + /* If the value sizes are 0, they are considered to match. */ + if (lhs_size == 0) { + return true; + } + + /* Compare the two values if they are non-null. */ + return lhs != nullptr && rhs != nullptr && std::memcmp(lhs, rhs, lhs_size) == 0; + } + + void FreeMapValueToHeap(const MapValue &map_value) { + /* Free the current value. */ + if (map_value.current_value != nullptr && map_value.current_value != map_value.default_value) { + FreeToHeap(map_value.current_value, map_value.current_value_size); + } + + /* Free the default value. */ + if (map_value.default_value != nullptr) { + FreeToHeap(map_value.default_value, map_value.default_value_size); + } + } + + void FreeToHeap(void *block, size_t size) { + AMS_UNUSED(size); + lmem::FreeToExpHeap(GetHeapHandle(), block); + } + + void *AllocateFromHeap(size_t size) { + return lmem::AllocateFromExpHeap(GetHeapHandle(), size); + } + + size_t GetHeapAllocatableSize() { + return lmem::GetExpHeapAllocatableSize(GetHeapHandle(), sizeof(void *)); + } + + lmem::HeapHandle &GetHeapHandle() { + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(u8, s_heap_memory[HeapMemorySize]); + AMS_FUNCTION_LOCAL_STATIC(lmem::HeapHandle, s_heap_handle, lmem::CreateExpHeap(s_heap_memory, sizeof(s_heap_memory), lmem::CreateOption_ThreadSafe)); + + return s_heap_handle; + } + + Result GetKeyValueStoreMap(Map **out) { + /* Check preconditions. */ + AMS_ASSERT(out != nullptr); + + /* Get the map. */ + R_RETURN(GetKeyValueStoreMap(out, false)); + } + + Result GetKeyValueStoreMap(Map **out, bool force_load) { + /* Check preconditions. */ + AMS_ASSERT(out != nullptr); + + /* Declare static instance variables. */ + AMS_FUNCTION_LOCAL_STATIC(Map, s_map); + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(bool, s_is_map_loaded, false); + + /* Get pointer to the map. */ + Map * const map = std::addressof(s_map); + + /* TODO: Mutex? */ + /* Load the map, if we haven't already. */ + if (AMS_UNLIKELY(!s_is_map_loaded)) { + /* Attempt to load the map, allowing for failure if acceptable. */ + const auto result = LoadKeyValueStoreMap(map); + + if (!force_load) { + R_TRY(result); + } + + /* Note that the map is loaded. */ + s_is_map_loaded = true; + } + + /* Set the output pointer. */ + *out = map; + R_SUCCEED(); + } + + Result GetKeyValueStoreMapForciblyForDebug(Map **out) { + /* Check preconditions. */ + AMS_ASSERT(out != nullptr); + + /* Get the map. */ + R_RETURN(GetKeyValueStoreMap(out, true)); + } + + Result GetMapValueOfKeyValueStoreItemForDebug(MapValue *out, const KeyValueStoreItemForDebug &item) { + /* Check preconditions. */ + AMS_ASSERT(out != nullptr); + + /* Create the map value. */ + MapValue map_value = { + .type = item.type, + .current_value_size = item.current_value_size, + .default_value_size = item.default_value_size, + .current_value = nullptr, + .default_value = nullptr, + }; + + /* Ensure we free any buffers we allocate, if we fail. */ + ON_SCOPE_EXIT { FreeMapValueToHeap(map_value); }; + + /* If the default value size is > 0, copy it to the map value. */ + if (map_value.default_value_size > 0) { + /* Allocate the default value if there is sufficient memory available. */ + R_UNLESS(GetHeapAllocatableSize() >= map_value.default_value_size, settings::ResultSettingsItemValueAllocationFailed()); + map_value.default_value = AllocateFromHeap(map_value.default_value_size); + AMS_ASSERT(map_value.default_value != nullptr); + + /* Copy the default value from the item. */ + std::memcpy(map_value.default_value, item.default_value, map_value.default_value_size); + } + + /* If the current value and the default values are identical, set the map value to the default value. */ + if (CompareValue(item.current_value, item.current_value_size, item.default_value, item.default_value_size)) { + map_value.current_value_size = map_value.default_value_size; + map_value.current_value = map_value.default_value; + } else if (map_value.current_value_size > 0) { + /* Allocate the current value if there is sufficient memory available. */ + R_UNLESS(GetHeapAllocatableSize() >= map_value.current_value_size, settings::ResultSettingsItemValueAllocationFailed()); + map_value.current_value = AllocateFromHeap(map_value.current_value_size); + AMS_ASSERT(map_value.current_value != nullptr); + + /* Copy the current value from the item. */ + std::memcpy(map_value.current_value, item.current_value, map_value.current_value_size); + } + + /* Set the output map value. */ + *out = map_value; + + /* Ensure we don't free the value buffers we returned. */ + map_value.current_value = nullptr; + map_value.default_value = nullptr; + R_SUCCEED(); + } + + template<typename T> + const char *GetSystemDataMountName(); + + template<> + const char *GetSystemDataMountName<SystemDataTag::Fwdbg>() { + return FwdbgSystemDataMountName; + } + + template<> + const char *GetSystemDataMountName<SystemDataTag::PfCfg>() { + return PfCfgSystemDataMountName; + } + + template<typename T> + Result GetSystemData(SystemData **out_data, ncm::SystemDataId id) { + /* Check pre-conditions. */ + AMS_ASSERT(out_data != nullptr); + + /* Declare static instance variables. */ + AMS_FUNCTION_LOCAL_STATIC(SystemData, s_data, id, GetSystemDataMountName<T>()); + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(bool, s_mounted, false); + + /* Get pointer to the system data. */ + SystemData *data = std::addressof(s_data); + + /* TODO: Mutex? */ + /* Mount the system data, if we haven't already. */ + if (AMS_UNLIKELY(!s_mounted)) { + /* Mount the system data. */ + R_TRY(data->Mount()); + + /* Note that we mounted. */ + s_mounted = true; + } + + /* Set the output pointer. */ + *out_data = data; + R_SUCCEED(); + } + + Result GetSystemSaveData(SystemSaveData **out_data, bool create_save) { + /* Check pre-conditions. */ + AMS_ASSERT(out_data != nullptr); + + /* Declare static instance variables. */ + AMS_FUNCTION_LOCAL_STATIC(SystemSaveData, s_data, SystemSaveDataId, SystemSaveDataSize, SystemSaveDataJournalSize, SystemSaveDataFlags, SystemSaveDataMountName); + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(bool, s_mounted, false); + + /* Get pointer to the system save data. */ + SystemSaveData *data = std::addressof(s_data); + + /* Mount the system data, if we haven't already. */ + if (AMS_UNLIKELY(!s_mounted)) { + /* Mount the system data. */ + R_TRY(data->Mount(create_save)); + + /* Note that we mounted. */ + s_mounted = true; + } + + /* Set the output pointer. */ + *out_data = data; + R_SUCCEED(); + } + + Result LoadKeyValueStoreMap(Map *out) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + + /* Clear the key value store map. */ + ClearKeyValueStoreMap(*out); + + /* Get the firmware debug system data. */ + SystemData *system_data = nullptr; + R_TRY(GetSystemData<SystemDataTag::Fwdbg>(std::addressof(system_data), ncm::SystemDataId::FirmwareDebugSettings)); + AMS_ASSERT(system_data != nullptr); + + /* Load the default keys/values for the firmware debug system data. */ + R_TRY(LoadKeyValueStoreMapDefault(out, *system_data)); + + /* Load the keys/values based on the hardware type. */ + R_TRY(LoadKeyValueStoreMap(out, GetSplHardwareType())); + + if (IsSplDevelopment()) { + /* Get the system save data. */ + SystemSaveData *system_save_data = nullptr; + R_SUCCEED_IF(R_FAILED(GetSystemSaveData(std::addressof(system_save_data), false))); + AMS_ASSERT(system_save_data != nullptr); + + /* Attempt to load the current keys/values from the system save data. */ + if (const auto result = LoadKeyValueStoreMapCurrent(out, *system_save_data); R_FAILED(result)) { + /* Reset all values to their defaults. */ + for (auto &kv_pair : *out) { + MapValue &map_value = kv_pair.second; + + /* Free the current value. */ + if (map_value.current_value != nullptr && map_value.current_value != map_value.default_value) { + FreeToHeap(map_value.current_value, map_value.current_value_size); + } + + /* Reset the current value to the default value. */ + map_value.current_value_size = map_value.default_value_size; + map_value.current_value = map_value.default_value; + } + + /* Log failure to load system save data. TODO: Make this a warning. */ + AMS_LOG("[firmware debug settings] Warning: Failed to load the system save data. (%08x, %d%03d-%04d)\n", result.GetInnerValue(), 2, result.GetModule(), result.GetDescription()); + } + } + + R_SUCCEED(); + } + + Result LoadKeyValueStoreMap(Map *out, SplHardwareType hardware_type) { + SystemData *data = nullptr; + + /* Get the platform configuration system data for the hardware type. */ + switch (hardware_type) { + case SplHardwareType_None: + R_SUCCEED(); + case SplHardwareType_Icosa: + R_TRY(GetSystemData<SystemDataTag::PfCfg>(std::addressof(data), ncm::SystemDataId::PlatformConfigIcosa)); + break; + case SplHardwareType_IcosaMariko: + R_TRY(GetSystemData<SystemDataTag::PfCfg>(std::addressof(data), ncm::SystemDataId::PlatformConfigIcosaMariko)); + break; + case SplHardwareType_Copper: + R_TRY(GetSystemData<SystemDataTag::PfCfg>(std::addressof(data), ncm::SystemDataId::PlatformConfigCopper)); + break; + case SplHardwareType_Hoag: + R_TRY(GetSystemData<SystemDataTag::PfCfg>(std::addressof(data), ncm::SystemDataId::PlatformConfigHoag)); + break; + case SplHardwareType_Calcio: + R_TRY(GetSystemData<SystemDataTag::PfCfg>(std::addressof(data), ncm::SystemDataId::PlatformConfigCalcio)); + break; + case SplHardwareType_Aula: + R_TRY(GetSystemData<SystemDataTag::PfCfg>(std::addressof(data), ncm::SystemDataId::PlatformConfigAula)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Ensure data is not null. */ + AMS_ASSERT(data != nullptr); + + /* Load the key value store map. */ + R_RETURN(LoadKeyValueStoreMapDefault(out, *data)); + } + + template<typename T> + Result LoadKeyValueStoreMapCurrent(Map *out, T &data) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + + /* Open the data for reading. */ + R_TRY(data.OpenToRead()); + ON_SCOPE_EXIT { data.Close(); }; + + /* Load the map entries. */ + R_TRY(LoadKeyValueStoreMapEntries(out, data, [](Map &map, const MapKey &key, u8 type, const void *value_buffer, u32 value_size) -> Result { + AMS_UNUSED(type); + /* Find the key in the map. */ + if (auto it = map.find(key); it != map.end()) { + MapValue &map_value = it->second; + size_t current_value_size = value_size; + void *current_value_buffer = nullptr; + + if (current_value_size > 0) { + /* Ensure there is sufficient memory for the value. */ + R_UNLESS(GetHeapAllocatableSize() >= current_value_size, settings::ResultSettingsItemValueAllocationFailed()); + + /* Allocate the value buffer. */ + current_value_buffer = AllocateFromHeap(current_value_size); + AMS_ASSERT(current_value_buffer != nullptr); + + /* Copy the value to the current value buffer. */ + std::memcpy(current_value_buffer, value_buffer, current_value_size); + } + + /* Replace the current value buffer with a new one. */ + std::swap(map_value.current_value_size, current_value_size); + std::swap(map_value.current_value, current_value_buffer); + + /* Free the old buffer if it is no longer in use. */ + if (current_value_buffer != nullptr && current_value_buffer != map_value.default_value) { + FreeToHeap(current_value_buffer, current_value_size); + } + } + + R_SUCCEED(); + })); + + R_SUCCEED(); + } + + template<typename T> + Result LoadKeyValueStoreMapDefault(Map *out, T &data) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + + /* Open the data for reading. */ + R_TRY(data.OpenToRead()); + ON_SCOPE_EXIT { data.Close(); }; + + /* Load the map entries. */ + R_TRY(LoadKeyValueStoreMapEntries(out, data, [](Map &map, const MapKey &key, u8 type, const void *value_buffer, u32 value_size) -> Result { + /* Ensure there is sufficient memory for two keys. */ + R_UNLESS(GetHeapAllocatableSize() >= MapKeyBufferSize, settings::ResultSettingsItemKeyAllocationFailed()); + + /* Copy the map key. */ + MapKey default_key = key; + void *default_value_buffer = nullptr; + + ON_SCOPE_EXIT { + /* Free the value buffer if allocated. */ + if (default_value_buffer != nullptr) { + FreeToHeap(default_value_buffer, value_size); + } + }; + + if (value_size > 0) { + /* Ensure there is sufficient memory for the value. */ + R_UNLESS(GetHeapAllocatableSize() >= value_size, settings::ResultSettingsItemValueAllocationFailed()); + + /* Allocate the value buffer. */ + default_value_buffer = AllocateFromHeap(value_size); + AMS_ASSERT(default_value_buffer != nullptr); + + /* Copy the value to the new value buffer. */ + std::memcpy(default_value_buffer, value_buffer, value_size); + } + + /* Create the map value. */ + MapValue default_value { + .type = type, + .current_value_size = value_size, + .default_value_size = value_size, + .current_value = default_value_buffer, + .default_value = default_value_buffer, + }; + + /* Ensure there is sufficient memory for the value. */ + R_UNLESS(GetHeapAllocatableSize() >= MapEntryBufferSize, settings::ResultSettingsItemValueAllocationFailed()); + + /* Insert the value into the map. */ + map[std::move(default_key)] = default_value; + R_SUCCEED(); + })); + + R_SUCCEED(); + } + + template<typename T, typename F> + Result LoadKeyValueStoreMapEntries(Map *out, T &data, F load) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + + /* Read the number of entries. */ + s64 offset = 0; + u32 total_size = 0; + R_TRY(ReadData(data, offset, std::addressof(total_size), sizeof(total_size))); + + /* Iterate through all entries. NOTE: The offset is updated within LoadKeyValueStoreMapEntry. */ + while (offset < total_size) { + R_TRY(LoadKeyValueStoreMapEntry(out, data, offset, load)); + } + + R_SUCCEED(); + } + + template<typename T, typename F> + Result LoadKeyValueStoreMapEntry(Map *out, T &data, s64 &offset, F load) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + + /* Read the size of the key. */ + u32 key_size = 0; + R_TRY(ReadData(data, offset, std::addressof(key_size), sizeof(key_size))); + AMS_ASSERT(key_size > 1); + + /* Ensure there is sufficient memory for this key. */ + R_UNLESS(GetHeapAllocatableSize() >= key_size, settings::ResultSettingsItemKeyAllocationFailed()); + + /* Read the key. */ + void *key_buffer = nullptr; + R_TRY(ReadDataToHeap(data, offset, std::addressof(key_buffer), key_size)); + AMS_ASSERT(key_buffer != nullptr); + ON_SCOPE_EXIT { FreeToHeap(key_buffer, key_size); }; + + /* Ensure there is sufficient memory for two keys. */ + R_UNLESS(GetHeapAllocatableSize() >= MapKeyBufferSize, settings::ResultSettingsItemKeyAllocationFailed()); + + const MapKey key(static_cast<const char *>(key_buffer), key_size - 1); + + /* Read the type from the data. */ + u8 type = 0; + R_TRY(ReadData(data, offset, std::addressof(type), sizeof(type))); + + /* Read the size of the value. */ + u32 value_size = 0; + R_TRY(ReadData(data, offset, std::addressof(value_size), sizeof(value_size))); + + void *value_buffer = nullptr; + ON_SCOPE_EXIT { + if (value_buffer != nullptr) { + FreeToHeap(value_buffer, value_size); + } + }; + + if (value_size > 0) { + /* Ensure there is sufficient memory for the value. */ + R_UNLESS(GetHeapAllocatableSize() >= value_size, settings::ResultSettingsItemValueAllocationFailed()); + + /* Read the value to the buffer. */ + R_TRY(ReadDataToHeap(data, offset, std::addressof(value_buffer), value_size)); + } + + /* Load the value. */ + R_RETURN(load(*out, key, type, value_buffer, value_size)); + } + + Result LoadKeyValueStoreMapForDebug(Map *out, SystemSaveData *system_save_data, SystemSaveData *fwdbg_system_save_data, SystemSaveData *pfcfg_system_save_data) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(system_save_data != nullptr); + AMS_ASSERT(fwdbg_system_save_data != nullptr); + AMS_ASSERT(pfcfg_system_save_data != nullptr); + + /* Clear the map. */ + ClearKeyValueStoreMap(*out); + + /* Load the default keys/values for the firmware debug system save data. */ + R_TRY(LoadKeyValueStoreMapDefault(out, *fwdbg_system_save_data)); + + /* Load the default keys/values for the platform configuration system save data. */ + R_TRY(LoadKeyValueStoreMapDefault(out, *pfcfg_system_save_data)); + + /* Load the current values for the system save data. */ + R_TRY(LoadKeyValueStoreMapCurrent(out, *system_save_data)); + R_SUCCEED(); + } + + template<typename T> + Result ReadData(T &data, s64 &offset, void *buffer, size_t size) { + AMS_ASSERT(buffer != nullptr); + + /* Read the data. */ + R_TRY(data.Read(offset, buffer, size)); + + /* Increment the offset. */ + offset += static_cast<s64>(size); + R_SUCCEED(); + } + + template<typename T> + Result ReadDataToHeap(T &data, s64 &offset, void **buffer, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(size > 0); + + /* Allocate a buffer from the heap. */ + *buffer = AllocateFromHeap(size); + AMS_ASSERT(*buffer != nullptr); + + /* Ensure we free the buffer if we fail. */ + auto alloc_guard = SCOPE_GUARD { FreeToHeap(*buffer, size); *buffer = nullptr; }; + + /* Read data to the buffer. */ + R_TRY(ReadData(data, offset, *buffer, size)); + + /* We succeeded. */ + alloc_guard.Cancel(); + R_SUCCEED(); + } + + template<typename T> + Result ReadAllBytes(T *data, u64 *out_count, char * const out_buffer, size_t out_buffer_size) { + /* Check preconditions. */ + AMS_ASSERT(data != nullptr); + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_buffer != nullptr); + + /* Open data for reading. */ + R_TRY(data->OpenToRead()); + ON_SCOPE_EXIT { data->Close(); }; + + /* Read the data size. */ + u32 size = 0; + R_TRY(data->Read(0, std::addressof(size), sizeof(size))); + + /* Ensure the data size does not exceed the buffer size. */ + size = std::min(size, static_cast<u32>(out_buffer_size)); + + /* Read the data. */ + R_TRY(data->Read(0, out_buffer, size)); + + /* Set the count. */ + *out_count = size; + R_SUCCEED(); + } + + Result ReadSystemDataFirmwareDebug(u64 *out_count, char * const out_buffer, size_t out_buffer_size) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_buffer); + + /* Attempt to get the firmware debug system data. */ + SystemData *system_data = nullptr; + if (R_SUCCEEDED(GetSystemData<SystemDataTag::Fwdbg>(std::addressof(system_data), ncm::SystemDataId::FirmwareDebugSettings))) { + AMS_ASSERT(system_data != nullptr); + + /* Read the data. */ + R_TRY(ReadAllBytes(system_data, out_count, out_buffer, out_buffer_size)); + } else { + /* Set the output count to 0. */ + *out_count = 0; + } + + R_SUCCEED(); + } + + Result ReadSystemDataPlatformConfiguration(u64 *out_count, char * const out_buffer, size_t out_buffer_size) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_buffer); + + ncm::SystemDataId system_data_id; + + switch (GetSplHardwareType()) { + case SplHardwareType_None: + *out_count = 0; + R_SUCCEED(); + case SplHardwareType_Icosa: + system_data_id = ncm::SystemDataId::PlatformConfigIcosa; + break; + case SplHardwareType_IcosaMariko: + system_data_id = ncm::SystemDataId::PlatformConfigIcosaMariko; + break; + case SplHardwareType_Copper: + system_data_id = ncm::SystemDataId::PlatformConfigCopper; + break; + case SplHardwareType_Hoag: + system_data_id = ncm::SystemDataId::PlatformConfigHoag; + break; + case SplHardwareType_Calcio: + system_data_id = ncm::SystemDataId::PlatformConfigCalcio; + break; + case SplHardwareType_Aula: + system_data_id = ncm::SystemDataId::PlatformConfigAula; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Attempt to get the platform configuration system data. */ + SystemData *system_data = nullptr; + if (R_SUCCEEDED(GetSystemData<SystemDataTag::PfCfg>(std::addressof(system_data), system_data_id))) { + AMS_ASSERT(system_data != nullptr); + + /* Read the data. */ + R_TRY(ReadAllBytes(system_data, out_count, out_buffer, out_buffer_size)); + } else { + /* Set the output count to 0. */ + *out_count = 0; + } + + R_SUCCEED(); + } + + Result ReadSystemSaveData(u64 *out_count, char * const out_buffer, size_t out_buffer_size) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_buffer); + + /* Attempt to get the system save data. */ + SystemSaveData *system_save_data = nullptr; + if (R_SUCCEEDED(GetSystemSaveData(std::addressof(system_save_data), false))) { + AMS_ASSERT(system_save_data != nullptr); + + /* Read the data. */ + R_TRY(ReadAllBytes(system_save_data, out_count, out_buffer, out_buffer_size)); + } else { + /* Set the output count to 0. */ + *out_count = 0; + } + + R_SUCCEED(); + } + + Result SaveKeyValueStoreMap(const Map &map) { + /* Get the system save data. */ + SystemSaveData *system_save_data = nullptr; + R_TRY(GetSystemSaveData(std::addressof(system_save_data), false)); + AMS_ASSERT(system_save_data != nullptr); + + /* Save the current values of the key value store map. */ + R_RETURN(SaveKeyValueStoreMapCurrent(*system_save_data, map)); + } + + template<typename T, typename F> + Result SaveKeyValueStoreMap(T &data, const Map &map, F test) { + /* Create the save data if necessary. */ + R_TRY_CATCH(data.Create(HeapMemorySize)) { + R_CATCH(fs::ResultPathAlreadyExists) { /* It's okay if the save data already exists. */ } + } R_END_TRY_CATCH; + + { + /* Open the save data for writing. */ + R_TRY(data.OpenToWrite()); + ON_SCOPE_EXIT { + /* Flush and close the save data. NOTE: Nintendo only does this if SetFileSize succeeds. */ + R_ABORT_UNLESS(data.Flush()); + data.Close(); + }; + + /* Set the file size of the save data. */ + R_TRY(data.SetFileSize(HeapMemorySize)); + + /* Write the data size, which includes itself. */ + u32 data_size = sizeof(data_size); + R_TRY(data.Write(0, std::addressof(data_size), sizeof(data_size))); + + /* Set the current offset to after the data size. */ + s64 current_offset = sizeof(data_size); + + /* Iterate through map entries. */ + for (const auto &kv_pair : map) { + /* Declare variables for test. */ + u8 type = 0; + const void *value_buffer = nullptr; + u32 value_size = 0; + + /* Test if the map value varies from the default. */ + if (test(std::addressof(type), std::addressof(value_buffer), std::addressof(value_size), kv_pair.second)) { + R_TRY(SaveKeyValueStoreMapEntry(data, current_offset, kv_pair.first, type, value_buffer, value_size)); + } + } + + /* Write the updated save data size. */ + data_size = static_cast<u32>(current_offset); + R_TRY(data.Write(0, std::addressof(data_size), sizeof(data_size))); + } + + /* Commit the save data. */ + R_RETURN(data.Commit(false)); + } + + template<typename T> + Result SaveKeyValueStoreMapCurrent(T &data, const Map &map) { + /* Save the current values in the map to the data. */ + R_RETURN(SaveKeyValueStoreMap(data, map, [](u8 *out_type, const void **out_value_buffer, u32 *out_value_size, const MapValue &map_value) -> bool { + /* Check preconditions. */ + AMS_ASSERT(out_type != nullptr); + AMS_ASSERT(out_value_buffer != nullptr); + AMS_ASSERT(out_value_size != nullptr); + + /* Check if the current value matches the default value. */ + if (CompareValue(map_value.current_value, map_value.current_value_size, map_value.default_value, map_value.default_value_size)) { + return false; + } + + /* Output the map value type, current value and current value size. */ + *out_type = map_value.type; + *out_value_buffer = map_value.current_value; + *out_value_size = map_value.current_value_size; + return true; + })); + } + + template<typename T> + Result SaveKeyValueStoreMapDefault(T &data, const Map &map) { + /* Save the default values in the map to the data. */ + R_RETURN(SaveKeyValueStoreMap(data, map, [](u8 *out_type, const void **out_value_buffer, u32 *out_value_size, const MapValue &map_value) -> bool { + /* Check preconditions. */ + AMS_ASSERT(out_type != nullptr); + AMS_ASSERT(out_value_buffer != nullptr); + AMS_ASSERT(out_value_size != nullptr); + + /* Output the map value type, default value and default value size. */ + *out_type = map_value.type; + *out_value_buffer = map_value.default_value; + *out_value_size = map_value.default_value_size; + return true; + })); + } + + Result SaveKeyValueStoreMapDefaultForDebug(SystemSaveData &data, const Map &map) { + R_RETURN(SaveKeyValueStoreMapDefault(data, map)); + } + + template<typename T> + Result SaveKeyValueStoreMapEntry(T &data, s64 &offset, const MapKey &key, u8 type, const void *value_buffer, u32 value_size) { + /* Write the key size and increment the offset. */ + const u32 key_size = key.GetCount() + 1; + R_TRY(data.Write(offset, std::addressof(key_size), sizeof(key_size))); + offset += static_cast<s64>(sizeof(key_size)); + + /* Write the key string and increment the offset. */ + R_TRY(data.Write(offset, key.GetString(), key_size)); + offset += static_cast<s64>(key_size); + + /* Write the type and increment the offset. */ + R_TRY(data.Write(offset, std::addressof(type), sizeof(type))); + offset += static_cast<s64>(sizeof(type)); + + /* Write the value size and increment the offset. */ + R_TRY(data.Write(offset, std::addressof(value_size), sizeof(value_size))); + offset += static_cast<s64>(sizeof(value_size)); + + /* If the value is larger than 0, write it to the data. */ + if (value_size > 0) { + /* Check preconditions. */ + AMS_ASSERT(value_buffer != nullptr); + + R_TRY(data.Write(offset, value_buffer, value_size)); + offset += static_cast<s64>(value_size); + } + + R_SUCCEED(); + } + + } + + Result KeyValueStore::CreateKeyIterator(KeyValueStoreKeyIterator *out) { + /* Check preconditions. */ + AMS_ASSERT(out != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Ensure there is sufficient memory for two keys. */ + R_UNLESS(GetHeapAllocatableSize() >= MapKeyBufferSize, settings::ResultSettingsItemKeyAllocationFailed()); + + /* Create a map key from the key value store's name. */ + MapKey map_key_header(m_name.value); + + /* Append the settings name separator. */ + map_key_header.Append(SettingsNameSeparator); + + /* Define the item map key. */ + const MapKey *item_map_key = nullptr; + + /* Find an item map key with the name as a prefix. */ + for (const auto &kv_pair : *map) { + const MapKey &map_key = kv_pair.first; + + /* Check if the name map key is smaller than the current map key, and the current map key contains the name map key. */ + if (map_key_header < map_key && map_key.Find(map_key_header)) { + item_map_key = std::addressof(map_key); + break; + } + } + + /* Ensure we have located an item map key. */ + R_UNLESS(item_map_key != nullptr, settings::ResultSettingsItemNotFound()); + + /* Ensure there is sufficient memory for the item map key. */ + const size_t item_map_key_size = item_map_key->GetCount() + 1; + R_UNLESS(GetHeapAllocatableSize() >= item_map_key_size, settings::ResultSettingsItemKeyIteratorAllocationFailed()); + + /* Allocate the key buffer. */ + char *buffer = static_cast<char *>(AllocateFromHeap(item_map_key_size)); + AMS_ASSERT(buffer != nullptr); + + /* Copy the item map key's string to the buffer. */ + std::memcpy(buffer, item_map_key->GetString(), item_map_key_size); + + /* Output the iterator. */ + *out = { + .header_size = static_cast<size_t>(map_key_header.GetCount()), + .entire_size = item_map_key_size, + .map_key = buffer, + }; + R_SUCCEED(); + } + + Result KeyValueStore::GetValue(u64 *out_count, char *out_buffer, size_t out_buffer_size, const SettingsItemKey &item_key) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_buffer != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Ensure there is sufficient memory for two keys. */ + R_UNLESS(GetHeapAllocatableSize() >= MapKeyBufferSize, settings::ResultSettingsItemKeyAllocationFailed()); + + /* Find the key in the map. */ + const Map::const_iterator it = map->find(MakeMapKey(m_name, item_key)); + R_UNLESS(it != map->end(), settings::ResultSettingsItemNotFound()); + + /* Get the map value from the iterator. */ + const MapValue &map_value = it->second; + + /* Calculate the current value size. */ + const size_t current_value_size = std::min(map_value.current_value_size, out_buffer_size); + + /* If the current value size is > 0, copy to the output buffer. */ + if (current_value_size > 0) { + AMS_ASSERT(map_value.current_value != nullptr); + std::memcpy(out_buffer, map_value.current_value, current_value_size); + } + + /* Set the output count. */ + *out_count = current_value_size; + R_SUCCEED(); + } + + Result KeyValueStore::GetValueSize(u64 *out_value_size, const SettingsItemKey &item_key) { + /* Check preconditions. */ + AMS_ASSERT(out_value_size != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Ensure there is sufficient memory for two keys. */ + R_UNLESS(GetHeapAllocatableSize() >= MapKeyBufferSize, settings::ResultSettingsItemKeyAllocationFailed()); + + /* Find the key in the map. */ + const Map::const_iterator it = map->find(MakeMapKey(m_name, item_key)); + R_UNLESS(it != map->end(), settings::ResultSettingsItemNotFound()); + + /* Output the value size. */ + *out_value_size = it->second.current_value_size; + R_SUCCEED(); + } + + Result KeyValueStore::ResetValue(const SettingsItemKey &item_key) { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Ensure there is sufficient memory for two keys. */ + R_UNLESS(GetHeapAllocatableSize() >= MapKeyBufferSize, settings::ResultSettingsItemKeyAllocationFailed()); + + /* Find the key in the map. */ + const Map::iterator it = map->find(MakeMapKey(m_name, item_key)); + R_UNLESS(it != map->end(), settings::ResultSettingsItemNotFound()); + + /* Get the map value from the iterator. */ + MapValue &map_value = it->second; + + /* Succeed if the map value has already been reset. */ + R_SUCCEED_IF(map_value.current_value == map_value.default_value); + + /* Store the previous value and its size. */ + size_t prev_value_size = map_value.current_value_size; + void *prev_value = map_value.current_value; + + /* Reset the current value to default. */ + map_value.current_value_size = map_value.default_value_size; + map_value.current_value = map_value.default_value; + + /* If we fail, revert the map. */ + ON_RESULT_FAILURE { + /* Revert to the previous value. */ + map_value.current_value_size = prev_value_size; + map_value.current_value = prev_value; + + SaveKeyValueStoreMap(*map); + }; + + /* Attempt to save the key value store map. */ + R_TRY(SaveKeyValueStoreMap(*map)); + + /* If present, free the previous value. */ + if (prev_value != nullptr && prev_value != map_value.default_value) { + FreeToHeap(prev_value, prev_value_size); + } + + R_SUCCEED(); + } + + Result KeyValueStore::SetValue(const SettingsItemKey &item_key, const void *buffer, size_t buffer_size) { + /* Check preconditions. */ + AMS_ASSERT(buffer != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Ensure there is sufficient memory for two keys. */ + R_UNLESS(GetHeapAllocatableSize() >= MapKeyBufferSize, settings::ResultSettingsItemKeyAllocationFailed()); + + /* Find the key in the map. */ + const Map::iterator it = map->find(MakeMapKey(m_name, item_key)); + R_UNLESS(it != map->end(), settings::ResultSettingsItemNotFound()); + + /* Get the map value from the iterator. */ + MapValue &map_value = it->second; + + /* Succeed if the map value is already set to the new value. */ + R_SUCCEED_IF(CompareValue(map_value.current_value, map_value.current_value_size, buffer, buffer_size)); + + /* Define the value buffer and size variables. */ + size_t value_size = buffer_size; + void *value_buffer = nullptr; + + /* Set the value buffer to the default value if the new value is the same. */ + if (CompareValue(map_value.default_value, map_value.default_value_size, buffer, buffer_size)) { + value_buffer = map_value.default_value; + } else if (buffer_size > 0) { + /* Allocate the new value if there is sufficient memory available. */ + R_UNLESS(GetHeapAllocatableSize() >= value_size, settings::ResultSettingsItemValueAllocationFailed()); + value_buffer = AllocateFromHeap(value_size); + AMS_ASSERT(value_buffer != nullptr); + + /* Copy the value to the value buffer. */ + std::memcpy(value_buffer, buffer, value_size); + } + + /* When we're done, free the unused value buffer. */ + ON_SCOPE_EXIT { + if (value_buffer != nullptr && value_buffer != map_value.default_value) { + FreeToHeap(value_buffer, value_size); + } + }; + + /* Swap the current value with the new value. */ + std::swap(map_value.current_value_size, value_size); + std::swap(map_value.current_value, value_buffer); + + /* If we fail, revert. */ + ON_RESULT_FAILURE { + std::swap(map_value.current_value_size, value_size); + std::swap(map_value.current_value, value_buffer); + + SaveKeyValueStoreMap(*map); + }; + + /* Attempt to save the key value store map. */ + R_RETURN(SaveKeyValueStoreMap(*map)); + } + + Result AddKeyValueStoreItemForDebug(const KeyValueStoreItemForDebug * const items, size_t items_count) { + /* Check preconditions. */ + AMS_ASSERT(items != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMapForciblyForDebug(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Iterate through each item. */ + for (size_t i = 0; i < items_count; i++) { + const KeyValueStoreItemForDebug &item = items[i]; + + /* Create a map value for our scope. */ + MapValue map_value = {}; + ON_SCOPE_EXIT { FreeMapValueToHeap(map_value); }; + + /* Get the map value for the item. */ + R_TRY(GetMapValueOfKeyValueStoreItemForDebug(std::addressof(map_value), item)); + + /* Ensure there is sufficient memory for two keys. */ + R_UNLESS(GetHeapAllocatableSize() >= MapKeyBufferSize, settings::ResultSettingsItemKeyAllocationFailed()); + + /* Create the map key. */ + MapKey map_key(item.key); + + /* Replace the existing value in the map if it already exists. */ + if (const Map::iterator it = map->find(map_key); it != map->end()) { + /* Free the existing map value. */ + FreeMapValueToHeap(it->second); + + /* Replace the existing map value. */ + it->second = map_value; + } else { + /* Ensure there is sufficient memory for the value. */ + R_UNLESS(GetHeapAllocatableSize() >= MapEntryBufferSize, settings::ResultSettingsItemValueAllocationFailed()); + + /* Assign the map value to the map key in the map. */ + (*map)[std::move(map_key)] = map_value; + } + + /* Ensure we don't free the value buffers we added. */ + map_value.current_value = nullptr; + map_value.default_value = nullptr; + } + + R_SUCCEED(); + } + + Result AdvanceKeyValueStoreKeyIterator(KeyValueStoreKeyIterator *out) { + /* Check preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(out->header_size > 0); + AMS_ASSERT(out->header_size < out->entire_size); + AMS_ASSERT(out->map_key != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Ensure there is sufficient memory for two keys. */ + R_UNLESS(GetHeapAllocatableSize() >= MapKeyBufferSize, settings::ResultSettingsItemKeyAllocationFailed()); + + /* Locate the iterator's current key. */ + Map::const_iterator it = map->find(MapKey(out->map_key, static_cast<s32>(out->entire_size) - 1)); + R_UNLESS(it != map->end(), settings::ResultNotFoundSettingsItemKeyIterator()); + + /* Increment the iterator, ensuring we aren't at the end of the map. */ + R_UNLESS((++it) != map->end(), settings::ResultStopIteration()); + + /* Get the map key. */ + const MapKey &map_key = it->first; + + /* Ensure the advanced iterator retains the required name. */ + R_UNLESS(std::strncmp(map_key.GetString(), out->map_key, out->header_size) == 0, settings::ResultStopIteration()); + + /* Ensure there is sufficient memory for the map key. */ + const size_t map_key_size = map_key.GetCount() + 1; + R_UNLESS(GetHeapAllocatableSize() >= map_key_size, settings::ResultSettingsItemKeyIteratorAllocationFailed()); + + /* Free the iterator's old map key. */ + FreeToHeap(out->map_key, out->entire_size); + + /* Allocate the new map key. */ + char *buffer = static_cast<char *>(AllocateFromHeap(map_key_size)); + AMS_ASSERT(buffer != nullptr); + + /* Copy the new map key to the buffer. */ + std::memcpy(buffer, map_key.GetString(), map_key_size); + + /* Set the output map key. */ + out->entire_size = map_key_size; + out->map_key = buffer; + + R_SUCCEED(); + } + + Result DestroyKeyValueStoreKeyIterator(KeyValueStoreKeyIterator *out) { + /* Check preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(out->header_size > 0); + AMS_ASSERT(out->header_size < out->entire_size); + AMS_ASSERT(out->map_key != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Free the key to the heap. */ + FreeToHeap(out->map_key, out->entire_size); + + /* Reset the name and key. */ + out->header_size = 0; + out->entire_size = 0; + out->map_key = nullptr; + R_SUCCEED(); + } + + Result GetKeyValueStoreItemCountForDebug(u64 *out_count) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Output the item count. */ + *out_count = map->size(); + R_SUCCEED(); + } + + Result GetKeyValueStoreItemForDebug(u64 *out_count, KeyValueStoreItemForDebug * const out_items, size_t out_items_count) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_items != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Define the count variable. */ + size_t count = 0; + + /* Iterate through each value in the map and output kvs items. */ + for (const auto &kv_pair : *map) { + /* Get the current map value. */ + const MapValue &map_value = kv_pair.second; + + /* Break if the count exceeds the output items count. */ + if (count >= out_items_count) { + break; + } + + /* Get the current item. */ + KeyValueStoreItemForDebug &item = out_items[count++]; + + /* Copy the map key and value to the item. */ + item.key = kv_pair.first.GetString(); + item.type = map_value.type; + item.current_value_size = map_value.current_value_size; + item.default_value_size = map_value.default_value_size; + item.current_value = map_value.current_value; + item.default_value = map_value.default_value; + } + + /* Set the output count. */ + *out_count = count; + R_SUCCEED(); + } + + Result GetKeyValueStoreKeyIteratorKey(u64 *out_count, char *out_buffer, size_t out_buffer_size, const KeyValueStoreKeyIterator &iterator) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_buffer != nullptr); + AMS_ASSERT(iterator.header_size > 0); + AMS_ASSERT(iterator.header_size < iterator.entire_size); + AMS_ASSERT(iterator.map_key != nullptr); + + /* Copy the key from the iterator to the output buffer. */ + const size_t key_size = std::min(out_buffer_size, std::min(iterator.entire_size - iterator.header_size, SettingsItemKeyLengthMax + 1)); + std::strncpy(out_buffer, iterator.map_key + iterator.header_size, key_size); + + /* Set the end of the key to null. */ + if (key_size > 0) { + out_buffer[key_size - 1] = '\x00'; + } + + /* Output the key size. */ + *out_count = key_size; + R_SUCCEED(); + } + + Result GetKeyValueStoreKeyIteratorKeySize(u64 *out_count, const KeyValueStoreKeyIterator &iterator) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(iterator.header_size > 0); + AMS_ASSERT(iterator.header_size < iterator.entire_size); + AMS_ASSERT(iterator.map_key != nullptr); + + /* Output the key size. */ + *out_count = std::min(iterator.entire_size - iterator.header_size, SettingsItemKeyLengthMax + 1); + R_SUCCEED(); + } + + Result ReadKeyValueStoreFirmwareDebug(u64 *out_count, char * const out_buffer, size_t out_buffer_size) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_buffer != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Read the firmware debug system data. */ + R_RETURN(ReadSystemDataFirmwareDebug(out_count, out_buffer, out_buffer_size)); + } + + Result ReadKeyValueStorePlatformConfiguration(u64 *out_count, char * const out_buffer, size_t out_buffer_size) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_buffer != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Read the platform configuration system data. */ + R_RETURN(ReadSystemDataPlatformConfiguration(out_count, out_buffer, out_buffer_size)); + } + + Result ReadKeyValueStoreSaveData(u64 *out_count, char * const out_buffer, size_t out_buffer_size) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_buffer != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Read the system save data. */ + R_RETURN(ReadSystemSaveData(out_count, out_buffer, out_buffer_size)); + } + + Result ReloadKeyValueStoreForDebug(SystemSaveData *system_save_data, SystemSaveData *fwdbg_system_data, SystemSaveData *pfcfg_system_data) { + /* Check preconditions. */ + AMS_ASSERT(system_save_data != nullptr); + AMS_ASSERT(fwdbg_system_data != nullptr); + AMS_ASSERT(pfcfg_system_data != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMapForciblyForDebug(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Load the key value store map. */ + R_RETURN(LoadKeyValueStoreMapForDebug(map, system_save_data, fwdbg_system_data, pfcfg_system_data)); + } + + Result ReloadKeyValueStoreForDebug() { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Load the key value store map. */ + R_RETURN(LoadKeyValueStoreMap(map)); + } + + Result ResetKeyValueStoreSaveData() { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Reset all values in the map. */ + for (auto &kv_pair : *map) { + /* Get the map value. */ + MapValue &map_value = kv_pair.second; + + /* If the current value isn't the default value, reset it. */ + if (map_value.current_value != map_value.default_value) { + /* Store the previous value and size. */ + size_t prev_value_size = map_value.current_value_size; + void *prev_value = map_value.current_value; + + /* Reset the current value to the default value. */ + map_value.current_value_size = map_value.default_value_size; + map_value.current_value = map_value.default_value; + + /* Free the current value if present. */ + if (prev_value != nullptr) { + FreeToHeap(prev_value, prev_value_size); + } + } + } + + /* Save the key value store map. */ + R_RETURN(SaveKeyValueStoreMap(*map)); + } + + Result SaveKeyValueStoreAllForDebug(SystemSaveData *data) { + /* Check preconditions. */ + AMS_ASSERT(data != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Save the key value store map default values. */ + R_TRY(SaveKeyValueStoreMapDefaultForDebug(*data, *map)); + + /* Save the key value store map. */ + R_RETURN(SaveKeyValueStoreMap(*map)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_key_value_store.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_key_value_store.hpp new file mode 100644 index 00000000..3c8a31f6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_key_value_store.hpp @@ -0,0 +1,67 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "settings_system_save_data.hpp" + +namespace ams::settings::impl { + + struct KeyValueStoreItemForDebug { + const char *key; + u8 type; + size_t current_value_size; + size_t default_value_size; + void *current_value; + void *default_value; + }; + static_assert(sizeof(KeyValueStoreItemForDebug) == 0x30); + + struct KeyValueStoreKeyIterator { + size_t header_size; + size_t entire_size; + char *map_key; + }; + static_assert(sizeof(KeyValueStoreKeyIterator) == 0x18); + + class KeyValueStore { + private: + const SettingsName &m_name; + public: + explicit KeyValueStore(const SettingsName &name) : m_name(name) { /* ... */ } + + Result CreateKeyIterator(KeyValueStoreKeyIterator *out); + Result GetValue(u64 *out_count, char *out_buffer, size_t out_buffer_size, const SettingsItemKey &item_key); + Result GetValueSize(u64 *out_value_size, const SettingsItemKey &item_key); + Result ResetValue(const SettingsItemKey &item_key); + Result SetValue(const SettingsItemKey &item_key, const void *buffer, size_t buffer_size); + }; + + Result AddKeyValueStoreItemForDebug(const KeyValueStoreItemForDebug * const items, size_t items_count); + Result AdvanceKeyValueStoreKeyIterator(KeyValueStoreKeyIterator *out); + Result DestroyKeyValueStoreKeyIterator(KeyValueStoreKeyIterator *out); + Result GetKeyValueStoreItemCountForDebug(u64 *out_count); + Result GetKeyValueStoreItemForDebug(u64 *out_count, KeyValueStoreItemForDebug * const out_items, size_t out_items_count); + Result GetKeyValueStoreKeyIteratorKey(u64 *out_count, char *out_buffer, size_t out_buffer_size, const KeyValueStoreKeyIterator &iterator); + Result GetKeyValueStoreKeyIteratorKeySize(u64 *out_count, const KeyValueStoreKeyIterator &iterator); + Result ReadKeyValueStoreFirmwareDebug(u64 *out_count, char * const out_buffer, size_t out_buffer_size); + Result ReadKeyValueStorePlatformConfiguration(u64 *out_count, char * const out_buffer, size_t out_buffer_size); + Result ReadKeyValueStoreSaveData(u64 *out_count, char * const out_buffer, size_t out_buffer_size); + Result ReloadKeyValueStoreForDebug(SystemSaveData *system_save_data, SystemSaveData *fwdbg_system_data, SystemSaveData *pfcfg_system_data); + Result ReloadKeyValueStoreForDebug(); + Result ResetKeyValueStoreSaveData(); + Result SaveKeyValueStoreAllForDebug(SystemSaveData *data); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_platform_region_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_platform_region_impl.cpp new file mode 100644 index 00000000..f359decc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_platform_region_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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "settings_platform_region_impl.hpp" + +namespace ams::settings::impl { + + Result GetPlatformRegion(s32 *out) { + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(sizeof(*out) == sizeof(::SetSysPlatformRegion)); + R_RETURN(::setsysGetPlatformRegion(reinterpret_cast<::SetSysPlatformRegion *>(out))); + #else + AMS_UNUSED(out); + AMS_ABORT("TODO"); + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_platform_region_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_platform_region_impl.hpp new file mode 100644 index 00000000..45b8c165 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_platform_region_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::settings::impl { + + Result GetPlatformRegion(s32 *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_product_model_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_product_model_impl.cpp new file mode 100644 index 00000000..ed56dd8b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_product_model_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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "settings_product_model_impl.hpp" + +namespace ams::settings::impl { + + Result GetProductModel(s32 *out) { + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(sizeof(*out) == sizeof(::SetSysProductModel)); + R_RETURN(::setsysGetProductModel(reinterpret_cast<::SetSysProductModel *>(out))); + #else + AMS_UNUSED(out); + AMS_ABORT("TODO"); + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_product_model_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_product_model_impl.hpp new file mode 100644 index 00000000..1588209e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_product_model_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::settings::impl { + + Result GetProductModel(s32 *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_region_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_region_impl.cpp new file mode 100644 index 00000000..18053610 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_region_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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "settings_region_impl.hpp" + +namespace ams::settings::impl { + + Result GetRegionCode(s32 *out) { + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(sizeof(*out) == sizeof(::SetRegion)); + R_RETURN(::setGetRegionCode(reinterpret_cast<::SetRegion *>(out))); + #else + AMS_UNUSED(out); + AMS_ABORT("TODO"); + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_region_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_region_impl.hpp new file mode 100644 index 00000000..02ebb515 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_region_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::settings::impl { + + Result GetRegionCode(s32 *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_serial_number_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_serial_number_impl.cpp new file mode 100644 index 00000000..66ceba15 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_serial_number_impl.cpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "settings_serial_number_impl.hpp" + +namespace ams::settings::impl { + + Result GetSerialNumber(settings::factory::SerialNumber *out) { + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(sizeof(*out) == sizeof(::SetCalSerialNumber)); + R_RETURN(::setcalGetSerialNumber(reinterpret_cast<::SetCalSerialNumber *>(out))); + #else + AMS_UNUSED(out); + AMS_ABORT("TODO"); + #endif + } + + Result GetSerialNumber(settings::system::SerialNumber *out) { + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(sizeof(*out) == sizeof(::SetSysSerialNumber)); + R_RETURN(::setsysGetSerialNumber(reinterpret_cast<::SetSysSerialNumber *>(out))); + #else + AMS_UNUSED(out); + AMS_ABORT("TODO"); + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_serial_number_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_serial_number_impl.hpp new file mode 100644 index 00000000..ed7406a5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_serial_number_impl.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::settings::impl { + + Result GetSerialNumber(settings::factory::SerialNumber *out); + Result GetSerialNumber(settings::system::SerialNumber *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_spl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_spl.cpp new file mode 100644 index 00000000..23669b67 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_spl.cpp @@ -0,0 +1,106 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "settings_spl.hpp" + +namespace ams::settings::impl { + + namespace { + + struct SplConfig { + bool is_development; + SplHardwareType hardware_type; + bool is_quest; + u64 device_id_low; + }; + + constexpr SplHardwareType ConvertToSplHardwareType(spl::HardwareType type) { + switch (type) { + case spl::HardwareType::Icosa: + return SplHardwareType_Icosa; + case spl::HardwareType::Copper: + return SplHardwareType_Copper; + case spl::HardwareType::Hoag: + return SplHardwareType_Hoag; + case spl::HardwareType::Iowa: + return SplHardwareType_IcosaMariko; + case spl::HardwareType::Calcio: + return SplHardwareType_Calcio; + case spl::HardwareType::Aula: + return SplHardwareType_Aula; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + constexpr bool IsSplRetailInteractiveDisplayStateEnabled(spl::RetailInteractiveDisplayState quest_state) { + switch (quest_state) { + case spl::RetailInteractiveDisplayState_Disabled: + return false; + case spl::RetailInteractiveDisplayState_Enabled: + return true; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + SplConfig GetSplConfig() { + class SplConfigHolder { + NON_COPYABLE(SplConfigHolder); + NON_MOVEABLE(SplConfigHolder); + private: + SplConfig m_config; + public: + SplConfigHolder() { + /* Initialize spl. */ + spl::Initialize(); + ON_SCOPE_EXIT { spl::Finalize(); }; + + /* Create the config. */ + m_config = { + .is_development = spl::IsDevelopment(), + .hardware_type = ConvertToSplHardwareType(spl::GetHardwareType()), + .is_quest = IsSplRetailInteractiveDisplayStateEnabled(spl::GetRetailInteractiveDisplayState()), + .device_id_low = spl::GetDeviceIdLow(), + }; + } + + ALWAYS_INLINE operator SplConfig() { + return m_config; + } + }; + + AMS_FUNCTION_LOCAL_STATIC(SplConfigHolder, s_config_holder); + return s_config_holder; + } + + } + + bool IsSplDevelopment() { + return GetSplConfig().is_development; + } + + SplHardwareType GetSplHardwareType() { + return GetSplConfig().hardware_type; + } + + bool IsSplRetailInteractiveDisplayStateEnabled() { + return GetSplConfig().is_quest; + } + + u64 GetSplDeviceIdLow() { + return GetSplConfig().device_id_low; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_spl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_spl.hpp new file mode 100644 index 00000000..83f65837 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_spl.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::settings::impl { + + enum SplHardwareType : u8 { + SplHardwareType_None = 0x0, + SplHardwareType_Icosa = 0x1, + SplHardwareType_IcosaMariko = 0x2, + SplHardwareType_Copper = 0x3, + SplHardwareType_Hoag = 0x4, + SplHardwareType_Calcio = 0x5, + SplHardwareType_Aula = 0x6, + }; + + bool IsSplDevelopment(); + SplHardwareType GetSplHardwareType(); + bool IsSplRetailInteractiveDisplayStateEnabled(); + u64 GetSplDeviceIdLow(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_static_object.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_static_object.hpp new file mode 100644 index 00000000..4311fc93 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_static_object.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::settings::impl { + + template<typename T, typename Tag> + class StaticObject final { + NON_COPYABLE(StaticObject); + NON_MOVEABLE(StaticObject); + private: + StaticObject(); + public: + static T &Get() { + AMS_FUNCTION_LOCAL_STATIC(T, s_object); + + return s_object; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_system_data.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_system_data.cpp new file mode 100644 index 00000000..204a370c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_system_data.cpp @@ -0,0 +1,170 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "settings_static_object.hpp" +#include "settings_system_data.hpp" + +namespace ams::settings::impl { + + namespace { + + constexpr inline const char MountNameSeparator[] = ":"; + constexpr inline const char DirectoryNameSeparator[] = "/"; + constexpr inline const char SystemDataFileName[] = "file"; + + class LazyFileAccessor final { + NON_COPYABLE(LazyFileAccessor); + NON_MOVEABLE(LazyFileAccessor); + private: + bool m_is_cached; + fs::FileHandle m_file; + s64 m_offset; + size_t m_size; + u8 m_buffer[16_KB]; + public: + LazyFileAccessor() : m_is_cached(false), m_file(), m_offset(), m_size(), m_buffer() { + /* ... */ + } + + Result Open(const char *path, int mode); + void Close(); + Result Read(s64 offset, void *dst, size_t size); + private: + bool GetCache(s64 offset, void *dst, size_t size); + }; + + Result LazyFileAccessor::Open(const char *path, int mode) { + /* Open our file. */ + R_RETURN(fs::OpenFile(std::addressof(m_file), path, mode)); + } + + void LazyFileAccessor::Close() { + /* Close our file. */ + fs::CloseFile(m_file); + + /* Reset our state. */ + m_is_cached = false; + m_file = {}; + m_size = 0; + m_offset = 0; + } + + bool LazyFileAccessor::GetCache(s64 offset, void *dst, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(dst != nullptr); + + /* Check that we're cached. */ + if (!m_is_cached) { + return false; + } + + /* Check that offset is within cache. */ + if (offset < m_offset) { + return false; + } + + /* Check that the read remains within range. */ + const size_t offset_in_cache = offset - m_offset; + if (m_size < offset_in_cache + size) { + return false; + } + + /* Copy the cached data. */ + std::memcpy(dst, m_buffer + offset_in_cache, size); + return true; + } + + Result LazyFileAccessor::Read(s64 offset, void *dst, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(dst != nullptr); + + /* Try to read from cache. */ + R_SUCCEED_IF(this->GetCache(offset, dst, size)); + + /* If the read is too big for the cache, read the data directly. */ + if (size > sizeof(m_buffer)) { + R_RETURN(fs::ReadFile(m_file, offset, dst, size)); + } + + /* Get the file size. */ + s64 file_size; + R_TRY(fs::GetFileSize(std::addressof(file_size), m_file)); + + /* If the file is too small, read the data directly. */ + if (file_size < offset + static_cast<s64>(size)) { + R_RETURN(fs::ReadFile(m_file, offset, dst, size)); + } + + /* Determine the read size. */ + const size_t read_size = std::min<size_t>(file_size - offset, sizeof(m_buffer)); + + /* Read into the cache. */ + if (read_size > 0) { + R_TRY(fs::ReadFile(m_file, offset, m_buffer, read_size)); + } + + /* Update cache statement. */ + m_offset = offset; + m_size = read_size; + m_is_cached = true; + + /* Get the data from the cache. */ + const bool succeeded = this->GetCache(offset, dst, size); + AMS_ASSERT(succeeded); + AMS_UNUSED(succeeded); + + R_SUCCEED(); + } + + LazyFileAccessor &GetLazyFileAccessor() { + return StaticObject<LazyFileAccessor, void>::Get(); + } + + } + + void SystemData::SetSystemDataId(ncm::SystemDataId id) { + m_system_data_id = id; + } + + void SystemData::SetMountName(const char *name) { + util::Strlcpy(m_mount_name, name, sizeof(m_mount_name)); + + int pos = 0; + pos += util::Strlcpy(m_file_path + pos, name, sizeof(m_mount_name)); + pos += util::Strlcpy(m_file_path + pos, MountNameSeparator, sizeof(MountNameSeparator)); + pos += util::Strlcpy(m_file_path + pos, DirectoryNameSeparator, sizeof(DirectoryNameSeparator)); + pos += util::Strlcpy(m_file_path + pos, SystemDataFileName, sizeof(SystemDataFileName)); + } + + Result SystemData::Mount() { + R_RETURN(fs::MountSystemData(m_mount_name, m_system_data_id)); + } + + Result SystemData::OpenToRead() { + R_RETURN(GetLazyFileAccessor().Open(m_file_path, fs::OpenMode_Read)); + } + + void SystemData::Close() { + return GetLazyFileAccessor().Close(); + } + + Result SystemData::Read(s64 offset, void *dst, size_t size) { + R_RETURN(GetLazyFileAccessor().Read(offset, dst, size)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_system_data.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_system_data.hpp new file mode 100644 index 00000000..204316c1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_system_data.hpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::settings::impl { + + class SystemData { + NON_COPYABLE(SystemData); + NON_MOVEABLE(SystemData); + private: + static constexpr size_t FileNameLengthMax = 31; + private: + ncm::SystemDataId m_system_data_id; + char m_mount_name[fs::MountNameLengthMax + 1]; + char m_file_path[fs::MountNameLengthMax + 1 + 1 + FileNameLengthMax + 1]; + public: + SystemData() : m_system_data_id(), m_mount_name(), m_file_path() { /* ... */ } + + SystemData(ncm::SystemDataId id, const char *mn) : SystemData() { + this->SetSystemDataId(id); + this->SetMountName(mn); + } + + void SetSystemDataId(ncm::SystemDataId id); + void SetMountName(const char *name); + Result Mount(); + Result OpenToRead(); + void Close(); + Result Read(s64 offset, void *dst, size_t size); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_system_save_data.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_system_save_data.cpp new file mode 100644 index 00000000..3f4f8b8c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_system_save_data.cpp @@ -0,0 +1,503 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "settings_static_object.hpp" +#include "settings_system_save_data.hpp" + +namespace ams::settings::impl { + + namespace { + + constexpr s64 LazyWriterDelayMilliSeconds = 500; + + constexpr inline const char *MountNameSeparator = ":"; + constexpr inline const char *DirectoryNameSeparator = "/"; + constexpr inline const char *SystemSaveDataFileName = "file"; + + class LazyFileAccessor final { + NON_COPYABLE(LazyFileAccessor); + NON_MOVEABLE(LazyFileAccessor); + private: + static constexpr size_t FileNameLengthMax = 31; + private: + bool m_is_activated; + bool m_is_busy; + bool m_is_cached; + bool m_is_modified; + bool m_is_file_size_changed; + char m_mount_name[fs::MountNameLengthMax + 1]; + char m_file_path[fs::MountNameLengthMax + 1 + 1 + FileNameLengthMax + 1]; + int m_open_mode; + s64 m_file_size; + s64 m_offset; + size_t m_size; + u8 m_buffer[512_KB]; + os::Mutex m_mutex; + os::TimerEvent m_timer_event; + os::ThreadType m_thread; + alignas(os::ThreadStackAlignment) u8 m_thread_stack[4_KB]; + public: + LazyFileAccessor() : m_is_activated(false), m_is_busy(false), m_is_cached(false), m_is_modified(false), m_is_file_size_changed(false), m_mount_name{}, m_file_path{}, m_open_mode(0), m_file_size(0), m_offset(0), m_size(0), m_mutex(false), m_timer_event(os::EventClearMode_AutoClear), m_thread{} { + std::memset(m_buffer, 0, sizeof(m_buffer)); + std::memset(m_thread_stack, 0, sizeof(m_thread_stack)); + } + + Result Activate(); + Result Commit(const char *name, bool synchronous); + Result Create(const char *name, s64 size); + Result Open(const char *name, int mode); + void Close(); + Result Read(s64 offset, void *dst, size_t size); + Result Write(s64 offset, const void *src, size_t size); + Result SetFileSize(s64 size); + private: + static void ThreadFunc(void *arg); + + template<size_t N> + static int CreateFilePath(char (&path)[N], const char *name); + + static bool AreEqual(const void *lhs, const void *rhs, size_t size); + + void SetMountName(const char *name); + bool CompareMountName(const char *name) const; + void InvokeWriteBackLoop(); + Result CommitSynchronously(); + }; + + LazyFileAccessor &GetLazyFileAccessor() { + return StaticObject<LazyFileAccessor, void>::Get(); + } + + Result LazyFileAccessor::Activate() { + std::scoped_lock lk(m_mutex); + + if (!m_is_activated) { + /* Create and start the lazy writer thread. */ + R_TRY(os::CreateThread(std::addressof(m_thread), LazyFileAccessor::ThreadFunc, this, m_thread_stack, sizeof(m_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(settings, LazyWriter))); + os::SetThreadNamePointer(std::addressof(m_thread), AMS_GET_SYSTEM_THREAD_NAME(settings, LazyWriter)); + os::StartThread(std::addressof(m_thread)); + m_is_activated = true; + } + + R_SUCCEED(); + } + + Result LazyFileAccessor::Commit(const char *name, bool synchronous) { + AMS_ASSERT(name != nullptr); + AMS_UNUSED(name); + std::scoped_lock lk(m_mutex); + + AMS_ASSERT(m_is_activated); + AMS_ASSERT(!m_is_busy); + AMS_ASSERT(this->CompareMountName(name)); + + if (synchronous) { + /* Stop the timer and commit synchronously. */ + m_timer_event.Stop(); + R_TRY(this->CommitSynchronously()); + } else { + /* Start the timer to write. */ + m_timer_event.StartOneShot(TimeSpan::FromMilliSeconds(LazyWriterDelayMilliSeconds)); + } + + R_SUCCEED(); + } + + Result LazyFileAccessor::Create(const char *name, s64 size) { + AMS_ASSERT(name != nullptr); + AMS_ASSERT(size >= 0); + + std::scoped_lock lk(m_mutex); + + AMS_ASSERT(m_is_activated); + AMS_ASSERT(!m_is_busy); + + if (m_is_cached) { + /* Stop the timer and commit synchronously. */ + m_timer_event.Stop(); + R_TRY(this->CommitSynchronously()); + + /* Reset the current state. */ + m_is_cached = false; + this->SetMountName(""); + m_open_mode = 0; + m_file_size = 0; + + /* Clear the buffer. */ + std::memset(m_buffer, 0, sizeof(m_buffer)); + } + + /* Create the save file. */ + this->CreateFilePath(m_file_path, name); + R_TRY(fs::CreateFile(m_file_path, size)); + + /* Initialize the accessor. */ + m_is_cached = true; + m_is_modified = true; + + this->SetMountName(name); + + m_open_mode = fs::OpenMode_Write; + m_file_size = size; + m_offset = 0; + m_size = size; + + /* Start the timer to write. */ + m_timer_event.StartOneShot(TimeSpan::FromMilliSeconds(LazyWriterDelayMilliSeconds)); + R_SUCCEED(); + } + + Result LazyFileAccessor::Open(const char *name, int mode) { + AMS_ASSERT(name != nullptr); + + std::scoped_lock lk(m_mutex); + + AMS_ASSERT(m_is_activated); + AMS_ASSERT(!m_is_busy); + + bool caches = true; + + if (m_is_cached) { + /* Check if the current mount matches the requested mount. */ + if (this->CompareMountName(name)) { + /* Check if the mode matches, or the existing mode is read-only. */ + if (m_open_mode == mode || m_open_mode == fs::OpenMode_Read) { + m_is_busy = true; + m_open_mode = mode; + R_SUCCEED(); + } + caches = false; + } + + /* Stop the timer and commit synchronously. */ + m_timer_event.Stop(); + R_TRY(this->CommitSynchronously()); + } + + if (caches) { + /* Create the save file if needed. */ + this->CreateFilePath(m_file_path, name); + + /* Open the save file. */ + fs::FileHandle file = {}; + R_TRY(fs::OpenFile(std::addressof(file), m_file_path, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Get the save size. */ + s64 file_size = 0; + R_TRY(fs::GetFileSize(std::addressof(file_size), file)); + AMS_ASSERT(0 <= file_size && file_size <= static_cast<s64>(sizeof(m_buffer))); + R_UNLESS(file_size <= static_cast<s64>(sizeof(m_buffer)), settings::ResultTooLargeSystemSaveData()); + + /* Read the save file. */ + R_TRY(fs::ReadFile(file, 0, m_buffer, static_cast<size_t>(file_size))); + + m_is_cached = true; + this->SetMountName(name); + m_file_size = file_size; + } + + m_is_busy = true; + m_open_mode = mode; + R_SUCCEED(); + } + + void LazyFileAccessor::Close() { + std::scoped_lock lk(m_mutex); + + AMS_ASSERT(m_is_activated); + AMS_ASSERT(m_is_busy); + + m_is_busy = false; + } + + Result LazyFileAccessor::Read(s64 offset, void *dst, size_t size) { + AMS_ASSERT(offset >= 0); + AMS_ASSERT(dst != nullptr); + + std::scoped_lock lk(m_mutex); + + AMS_ASSERT(m_is_activated); + AMS_ASSERT(m_is_busy); + AMS_ASSERT(m_is_cached); + AMS_ASSERT((m_open_mode & fs::OpenMode_Read) != 0); + AMS_ASSERT(offset + static_cast<s64>(size) <= m_file_size); + + std::memcpy(dst, m_buffer + offset, size); + + R_SUCCEED(); + } + + Result LazyFileAccessor::Write(s64 offset, const void *src, size_t size) { + AMS_ASSERT(offset >= 0); + AMS_ASSERT(src != nullptr); + + std::scoped_lock lk(m_mutex); + + AMS_ASSERT(m_is_activated); + AMS_ASSERT(m_is_busy); + AMS_ASSERT(m_is_cached); + AMS_ASSERT((m_open_mode & ::ams::fs::OpenMode_Write) != 0); + + s64 end = offset + static_cast<s64>(size); + AMS_ASSERT(end <= static_cast<s64>(m_size)); + + /* Succeed if there's nothing to write. */ + R_SUCCEED_IF(this->AreEqual(m_buffer + offset, src, size)); + + /* Copy to dst. */ + std::memcpy(m_buffer + offset, src, size); + + /* Update offset and size. */ + if (m_is_modified) { + end = std::max(end, m_offset + static_cast<s64>(m_size)); + m_offset = std::min(m_offset, offset); + m_size = static_cast<size_t>(end - m_offset); + } else { + m_is_modified = true; + m_offset = offset; + m_size = size; + } + + R_SUCCEED(); + } + + Result LazyFileAccessor::SetFileSize(s64 size) { + std::scoped_lock lk(m_mutex); + + const s64 prev_file_size = m_file_size; + + /* If the existing file size exceeds the new file size, reset the state or truncate. */ + if (m_file_size >= size) { + if (m_is_modified) { + /* If the current offset exceeds the new file size, reset the state. */ + if (m_offset >= size) { + m_is_modified = false; + m_offset = 0; + m_size = 0; + } else if (m_offset + static_cast<s64>(m_size) > size) { + /* Truncate the buffer for the new file size. */ + m_size = size - m_offset; + } + } + } else { + /* If unmodified, mark as modified and move the offset to the current end of the file. */ + if (!m_is_modified) { + m_is_modified = true; + m_offset = m_file_size; + } + + /* Zero-initialize the expanded file segment. */ + std::memset(m_buffer + m_file_size, 0, size - m_file_size); + + /* Update the current buffer size. */ + m_size = size - m_offset; + } + + /* Update state. */ + m_is_file_size_changed = prev_file_size != size; + m_file_size = size; + R_SUCCEED(); + } + + void LazyFileAccessor::ThreadFunc(void *arg) { + AMS_ASSERT(arg != nullptr); + reinterpret_cast<LazyFileAccessor *>(arg)->InvokeWriteBackLoop(); + } + + template<size_t N> + int LazyFileAccessor::CreateFilePath(char (&path)[N], const char *name) { + AMS_ASSERT(name != nullptr); + + const auto len = static_cast<int>(N); + + s32 pos = util::Strlcpy(path, name, len); + pos += util::Strlcpy(path + pos, MountNameSeparator, len - pos); + pos += util::Strlcpy(path + pos, DirectoryNameSeparator, len - pos); + pos += util::Strlcpy(path + pos, SystemSaveDataFileName, len - pos); + return pos; + } + + bool LazyFileAccessor::AreEqual(const void *lhs, const void *rhs, size_t size) { + AMS_ASSERT(lhs != nullptr); + AMS_ASSERT(rhs != nullptr); + + auto lhs8 = static_cast<const u8 *>(lhs); + auto rhs8 = static_cast<const u8 *>(rhs); + for (size_t i = 0; i < size; i++) { + if (*(lhs8++) != *(rhs8++)) { + return false; + } + } + + return true; + } + + void LazyFileAccessor::SetMountName(const char *name) { + AMS_ASSERT(name != nullptr); + + util::Strlcpy(m_mount_name, name, sizeof(m_mount_name)); + } + + bool LazyFileAccessor::CompareMountName(const char *name) const { + return util::Strncmp(m_mount_name, name, sizeof(m_mount_name)) == 0; + } + + void LazyFileAccessor::InvokeWriteBackLoop() { + while (true) { + m_timer_event.Wait(); + + std::scoped_lock lk(m_mutex); + if (!m_is_busy) { + R_ABORT_UNLESS(this->CommitSynchronously()); + } + } + } + + Result LazyFileAccessor::CommitSynchronously() { + /* If we have data cached/modified, we need to commit. */ + if (m_is_cached && (m_is_file_size_changed || m_is_modified)) { + /* Get the save file path. */ + this->CreateFilePath(m_file_path, m_mount_name); + + { + /* Open the save file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), m_file_path, fs::OpenMode_Write)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* If we need to, update the file size. */ + if (m_is_file_size_changed) { + R_TRY(fs::SetFileSize(file, m_file_size)); + m_is_file_size_changed = false; + } + + /* If we need to, write the file data. */ + if (m_is_modified) { + /* Write to the file. */ + R_TRY(fs::WriteFile(file, m_offset, m_buffer + m_offset, m_size, fs::WriteOption::None)); + R_TRY(fs::FlushFile(file)); + + /* Reset state. */ + m_is_modified = false; + m_offset = 0; + m_size = 0; + } + } + + /* Commit the savedata. */ + R_TRY(fs::CommitSaveData(m_mount_name)); + } + + R_SUCCEED(); + } + + } + + void SystemSaveData::SetSystemSaveDataId(u64 id) { + m_system_save_data_id = id; + } + + void SystemSaveData::SetTotalSize(s64 size) { + m_total_size = size; + } + + void SystemSaveData::SetJournalSize(s64 size) { + m_journal_size = size; + } + + void SystemSaveData::SetFlags(u32 flags) { + m_flags = flags; + } + + void SystemSaveData::SetMountName(const char *name) { + AMS_ASSERT(name != nullptr); + + util::Strlcpy(m_mount_name, name, sizeof(m_mount_name)); + } + + Result SystemSaveData::Mount(bool create_save) { + /* Activate the file accessor. */ + R_TRY(GetLazyFileAccessor().Activate()); + + /* Don't allow automatic save data creation. */ + fs::DisableAutoSaveDataCreation(); + + /* Attempt to get the flags of existing save data. */ + u32 cur_flags = 0; + const auto flags_result = fs::GetSaveDataFlags(std::addressof(cur_flags), m_save_data_space_id, m_system_save_data_id); + + /* If the save data exists, ensure flags are correct. */ + if (R_SUCCEEDED(flags_result)) { + if (cur_flags != m_flags) { + R_TRY(fs::SetSaveDataFlags(m_system_save_data_id, m_save_data_space_id, m_flags)); + } + } else { + /* Unless we can create save data, return the error we got when retrieving flags. */ + R_UNLESS(create_save, flags_result); + + /* Create the save data. */ + R_TRY(fs::CreateSystemSaveData(m_save_data_space_id, m_system_save_data_id, ncm::SystemProgramId::Settings.value, m_total_size, m_journal_size, m_flags)); + } + + /* Mount the save data. */ + R_RETURN(fs::MountSystemSaveData(m_mount_name, m_save_data_space_id, m_system_save_data_id)); + } + + Result SystemSaveData::Commit(bool synchronous) { + R_RETURN(GetLazyFileAccessor().Commit(m_mount_name, synchronous)); + } + + Result SystemSaveData::Create(s64 size) { + R_RETURN(GetLazyFileAccessor().Create(m_mount_name, size)); + } + + Result SystemSaveData::OpenToRead() { + R_RETURN(GetLazyFileAccessor().Open(m_mount_name, fs::OpenMode_Read)); + } + + Result SystemSaveData::OpenToWrite() { + R_RETURN(GetLazyFileAccessor().Open(m_mount_name, fs::OpenMode_Write)); + } + + void SystemSaveData::Close() { + GetLazyFileAccessor().Close(); + } + + Result SystemSaveData::Read(s64 offset, void *buf, size_t size) { + AMS_ASSERT(offset >= 0); + AMS_ASSERT(buf != nullptr); + + R_RETURN(GetLazyFileAccessor().Read(offset, buf, size)); + } + + Result SystemSaveData::Write(s64 offset, const void *buf, size_t size) { + AMS_ASSERT(offset >= 0); + AMS_ASSERT(buf != nullptr); + + R_RETURN(GetLazyFileAccessor().Write(offset, buf, size)); + } + + Result SystemSaveData::Flush() { + /* N doesn't do anything here. */ + R_SUCCEED(); + } + + Result SystemSaveData::SetFileSize(s64 size) { + R_RETURN(GetLazyFileAccessor().SetFileSize(size)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_system_save_data.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_system_save_data.hpp new file mode 100644 index 00000000..4d01172d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/impl/settings_system_save_data.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::settings::impl { + + class SystemSaveData { + NON_COPYABLE(SystemSaveData); + private: + fs::SystemSaveDataId m_system_save_data_id; + fs::SaveDataSpaceId m_save_data_space_id; + s64 m_total_size; + s64 m_journal_size; + u32 m_flags; + char m_mount_name[fs::MountNameLengthMax + 1]; + public: + SystemSaveData() : m_system_save_data_id(0), m_save_data_space_id(fs::SaveDataSpaceId::System), m_total_size(0), m_journal_size(0), m_flags(0) { /* ... */ } + + SystemSaveData(u64 id, s64 total_size, s64 journal_size, u32 flags, const char *mn) : SystemSaveData() { + this->SetSystemSaveDataId(id); + this->SetTotalSize(total_size); + this->SetJournalSize(journal_size); + this->SetFlags(flags); + this->SetMountName(mn); + } + + void SetSystemSaveDataId(u64 id); + void SetTotalSize(s64 size); + void SetJournalSize(s64 size); + void SetFlags(u32 flags); + void SetMountName(const char *name); + + Result Mount(bool create_save); + Result Commit(bool synchronous); + Result Create(s64 size); + Result OpenToRead(); + Result OpenToWrite(); + void Close(); + Result Read(s64 offset, void *buf, size_t size); + Result Write(s64 offset, const void *buf, size_t size); + Result Flush(); + Result SetFileSize(s64 size); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_configuration_id.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_configuration_id.cpp new file mode 100644 index 00000000..a13c6f59 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_configuration_id.cpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/settings_configuration_id_impl.hpp" + +namespace ams::settings::factory { + + void GetConfigurationId1(ConfigurationId1 *out) { + R_ABORT_UNLESS(settings::impl::GetConfigurationId1(out)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_error_report.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_error_report.cpp new file mode 100644 index 00000000..e7b4a50c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_error_report.cpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/settings_error_report_impl.hpp" + +namespace ams::settings::system { + + ErrorReportSharePermission GetErrorReportSharePermission() { + s32 perm = 0; + R_ABORT_UNLESS(settings::impl::GetErrorReportSharePermission(std::addressof(perm))); + return static_cast<ErrorReportSharePermission>(perm); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_firmware_version.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_firmware_version.cpp new file mode 100644 index 00000000..513c1ad7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_firmware_version.cpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/settings_firmware_version_impl.hpp" + +namespace ams::settings::system { + + void GetFirmwareVersion(FirmwareVersion *out) { + R_ABORT_UNLESS(settings::impl::GetFirmwareVersion(out)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_fwdbg_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_fwdbg_api.cpp new file mode 100644 index 00000000..360309b8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_fwdbg_api.cpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::settings::fwdbg { + + #if defined(ATMOSPHERE_OS_HORIZON) + bool IsDebugModeEnabled() { + bool value = false; + R_ABORT_UNLESS(::setsysGetDebugModeFlag(std::addressof(value))); + return value; + } + + size_t WEAK_SYMBOL GetSettingsItemValueSize(const char *name, const char *key) { + u64 size = 0; + R_ABORT_UNLESS(setsysGetSettingsItemValueSize(name, key, &size)); + return size; + } + + size_t WEAK_SYMBOL GetSettingsItemValue(void *dst, size_t dst_size, const char *name, const char *key) { + u64 size = 0; + R_ABORT_UNLESS(setsysGetSettingsItemValue(name, key, dst, dst_size, &size)); + return size; + } + #else + bool IsDebugModeEnabled() { + AMS_ABORT("TODO"); + } + + size_t WEAK_SYMBOL GetSettingsItemValueSize(const char *name, const char *key) { + AMS_UNUSED(name, key); + AMS_ABORT("TODO"); + } + + size_t WEAK_SYMBOL GetSettingsItemValue(void *dst, size_t dst_size, const char *name, const char *key) { + AMS_UNUSED(dst, dst_size, name, key); + AMS_ABORT("TODO"); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_platform_region.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_platform_region.cpp new file mode 100644 index 00000000..928e6684 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_platform_region.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/settings_platform_region_impl.hpp" + +namespace ams::settings::system { + + PlatformRegion GetPlatformRegion() { + if (hos::GetVersion() >= hos::Version_9_0_0) { + s32 region = 0; + R_ABORT_UNLESS(settings::impl::GetPlatformRegion(std::addressof(region))); + return static_cast<PlatformRegion>(region); + } else { + return PlatformRegion_Global; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_product_model.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_product_model.cpp new file mode 100644 index 00000000..b4851ad2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_product_model.cpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/settings_product_model_impl.hpp" + +namespace ams::settings::system { + + ProductModel GetProductModel() { + s32 model = 0; + R_ABORT_UNLESS(settings::impl::GetProductModel(std::addressof(model))); + return static_cast<ProductModel>(model); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_region.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_region.cpp new file mode 100644 index 00000000..45742cef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_region.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/settings_region_impl.hpp" + +namespace ams::settings::system { + + void GetRegionCode(RegionCode *out) { + AMS_ABORT_UNLESS(out != nullptr); + s32 value = 0; + R_ABORT_UNLESS(settings::impl::GetRegionCode(std::addressof(value))); + *out = static_cast<RegionCode>(value); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_serial_number.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_serial_number.cpp new file mode 100644 index 00000000..806b7621 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/settings/settings_serial_number.cpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/settings_serial_number_impl.hpp" + +namespace ams::settings::factory { + + Result GetSerialNumber(SerialNumber *out) { + R_TRY_CATCH(settings::impl::GetSerialNumber(out)) { + /* It's not a fatal error if the calib filesystem is corrupted. */ + R_CATCH_RETHROW(settings::ResultCalibrationDataFileSystemCorrupted) + R_CATCH_RETHROW(settings::ResultCalibrationDataCrcError) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + R_SUCCEED(); + } + +} + +namespace ams::settings::system { + + void GetSerialNumber(SerialNumber *out) { + R_ABORT_UNLESS(settings::impl::GetSerialNumber(out)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/cmif/sf_cmif_domain_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/cmif/sf_cmif_domain_manager.cpp new file mode 100644 index 00000000..2ad69583 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/cmif/sf_cmif_domain_manager.cpp @@ -0,0 +1,157 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::sf::cmif { + + ServerDomainManager::Domain::~Domain() { + while (!m_entries.empty()) { + Entry *entry = std::addressof(m_entries.front()); + { + std::scoped_lock lk(m_manager->m_entry_owner_lock); + AMS_ABORT_UNLESS(entry->owner == this); + entry->owner = nullptr; + } + entry->object.Reset(); + m_entries.pop_front(); + m_manager->m_entry_manager.FreeEntry(entry); + } + } + + void ServerDomainManager::Domain::DisposeImpl() { + ServerDomainManager *manager = m_manager; + std::destroy_at(this); + manager->FreeDomain(this); + } + + Result ServerDomainManager::Domain::ReserveIds(DomainObjectId *out_ids, size_t count) { + for (size_t i = 0; i < count; i++) { + Entry *entry = m_manager->m_entry_manager.AllocateEntry(); + R_UNLESS(entry != nullptr, sf::cmif::ResultOutOfDomainEntries()); + AMS_ABORT_UNLESS(entry->owner == nullptr); + out_ids[i] = m_manager->m_entry_manager.GetId(entry); + } + R_SUCCEED(); + } + + void ServerDomainManager::Domain::ReserveSpecificIds(const DomainObjectId *ids, size_t count) { + m_manager->m_entry_manager.AllocateSpecificEntries(ids, count); + } + + void ServerDomainManager::Domain::UnreserveIds(const DomainObjectId *ids, size_t count) { + for (size_t i = 0; i < count; i++) { + Entry *entry = m_manager->m_entry_manager.GetEntry(ids[i]); + AMS_ABORT_UNLESS(entry != nullptr); + AMS_ABORT_UNLESS(entry->owner == nullptr); + m_manager->m_entry_manager.FreeEntry(entry); + } + } + + void ServerDomainManager::Domain::RegisterObject(DomainObjectId id, ServiceObjectHolder &&obj) { + Entry *entry = m_manager->m_entry_manager.GetEntry(id); + AMS_ABORT_UNLESS(entry != nullptr); + { + std::scoped_lock lk(m_manager->m_entry_owner_lock); + AMS_ABORT_UNLESS(entry->owner == nullptr); + entry->owner = this; + m_entries.push_back(*entry); + } + entry->object = std::move(obj); + } + + ServiceObjectHolder ServerDomainManager::Domain::UnregisterObject(DomainObjectId id) { + ServiceObjectHolder obj; + Entry *entry = m_manager->m_entry_manager.GetEntry(id); + if (entry == nullptr) { + return ServiceObjectHolder(); + } + { + std::scoped_lock lk(m_manager->m_entry_owner_lock); + if (entry->owner != this) { + return ServiceObjectHolder(); + } + entry->owner = nullptr; + obj = std::move(entry->object); + m_entries.erase(m_entries.iterator_to(*entry)); + } + m_manager->m_entry_manager.FreeEntry(entry); + return obj; + } + + ServiceObjectHolder ServerDomainManager::Domain::GetObject(DomainObjectId id) { + Entry *entry = m_manager->m_entry_manager.GetEntry(id); + if (entry == nullptr) { + return ServiceObjectHolder(); + } + + { + std::scoped_lock lk(m_manager->m_entry_owner_lock); + if (entry->owner != this) { + return ServiceObjectHolder(); + } + } + return entry->object.Clone(); + } + + ServerDomainManager::EntryManager::EntryManager(DomainEntryStorage *entry_storage, size_t entry_count) : m_lock() { + m_entries = reinterpret_cast<Entry *>(entry_storage); + m_num_entries = entry_count; + for (size_t i = 0; i < m_num_entries; i++) { + m_free_list.push_back(*std::construct_at(m_entries + i)); + } + } + + ServerDomainManager::EntryManager::~EntryManager() { + for (size_t i = 0; i < m_num_entries; i++) { + std::destroy_at(m_entries + i); + } + } + + ServerDomainManager::Entry *ServerDomainManager::EntryManager::AllocateEntry() { + std::scoped_lock lk(m_lock); + + if (m_free_list.empty()) { + return nullptr; + } + + Entry *e = std::addressof(m_free_list.front()); + m_free_list.pop_front(); + return e; + } + + void ServerDomainManager::EntryManager::FreeEntry(Entry *entry) { + std::scoped_lock lk(m_lock); + AMS_ABORT_UNLESS(entry->owner == nullptr); + AMS_ABORT_UNLESS(!entry->object); + m_free_list.push_front(*entry); + } + + void ServerDomainManager::EntryManager::AllocateSpecificEntries(const DomainObjectId *ids, size_t count) { + std::scoped_lock lk(m_lock); + + /* Allocate new IDs. */ + for (size_t i = 0; i < count; i++) { + const auto id = ids[i]; + Entry *entry = this->GetEntry(id); + if (id != InvalidDomainObjectId) { + AMS_ABORT_UNLESS(entry != nullptr); + AMS_ABORT_UNLESS(entry->owner == nullptr); + m_free_list.erase(m_free_list.iterator_to(*entry)); + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/cmif/sf_cmif_domain_service_object.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/cmif/sf_cmif_domain_service_object.cpp new file mode 100644 index 00000000..edda86f5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/cmif/sf_cmif_domain_service_object.cpp @@ -0,0 +1,226 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::sf::cmif { + + Result DomainServiceObjectDispatchTable::ProcessMessage(ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data) const { + R_RETURN(this->ProcessMessageImpl(ctx, static_cast<DomainServiceObject *>(ctx.srv_obj)->GetServerDomain(), in_raw_data)); + } + + #if AMS_SF_MITM_SUPPORTED + Result DomainServiceObjectDispatchTable::ProcessMessageForMitm(ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data) const { + R_RETURN(this->ProcessMessageForMitmImpl(ctx, static_cast<DomainServiceObject *>(ctx.srv_obj)->GetServerDomain(), in_raw_data)); + } + #endif + + Result DomainServiceObjectDispatchTable::ProcessMessageImpl(ServiceDispatchContext &ctx, ServerDomainBase *domain, const cmif::PointerAndSize &in_raw_data) const { + const CmifDomainInHeader *in_header = reinterpret_cast<const CmifDomainInHeader *>(in_raw_data.GetPointer()); + R_UNLESS(in_raw_data.GetSize() >= sizeof(*in_header), sf::cmif::ResultInvalidHeaderSize()); + const cmif::PointerAndSize in_domain_raw_data = cmif::PointerAndSize(in_raw_data.GetAddress() + sizeof(*in_header), in_raw_data.GetSize() - sizeof(*in_header)); + + const DomainObjectId target_object_id = DomainObjectId{in_header->object_id}; + switch (in_header->type) { + case CmifDomainRequestType_SendMessage: + { + auto target_object = domain->GetObject(target_object_id); + R_UNLESS(static_cast<bool>(target_object), sf::cmif::ResultTargetNotFound()); + R_UNLESS(in_header->data_size + in_header->num_in_objects * sizeof(DomainObjectId) <= in_domain_raw_data.GetSize(), sf::cmif::ResultInvalidHeaderSize()); + const cmif::PointerAndSize in_message_raw_data = cmif::PointerAndSize(in_domain_raw_data.GetAddress(), in_header->data_size); + DomainObjectId in_object_ids[8]; + R_UNLESS(in_header->num_in_objects <= util::size(in_object_ids), sf::cmif::ResultInvalidNumInObjects()); + std::memcpy(in_object_ids, reinterpret_cast<DomainObjectId *>(in_message_raw_data.GetAddress() + in_message_raw_data.GetSize()), sizeof(DomainObjectId) * in_header->num_in_objects); + DomainServiceObjectProcessor domain_processor(domain, in_object_ids, in_header->num_in_objects); + if (ctx.processor == nullptr) { + ctx.processor = std::addressof(domain_processor); + } else { + ctx.processor->SetImplementationProcessor(std::addressof(domain_processor)); + } + ctx.srv_obj = target_object.GetServiceObjectUnsafe(); + R_RETURN(target_object.ProcessMessage(ctx, in_message_raw_data)); + } + case CmifDomainRequestType_Close: + /* TODO: N doesn't error check here. Should we? */ + domain->UnregisterObject(target_object_id); + R_SUCCEED(); + default: + R_THROW(sf::cmif::ResultInvalidInHeader()); + } + } + + #if AMS_SF_MITM_SUPPORTED + Result DomainServiceObjectDispatchTable::ProcessMessageForMitmImpl(ServiceDispatchContext &ctx, ServerDomainBase *domain, const cmif::PointerAndSize &in_raw_data) const { + const CmifDomainInHeader *in_header = reinterpret_cast<const CmifDomainInHeader *>(in_raw_data.GetPointer()); + R_UNLESS(in_raw_data.GetSize() >= sizeof(*in_header), sf::cmif::ResultInvalidHeaderSize()); + const cmif::PointerAndSize in_domain_raw_data = cmif::PointerAndSize(in_raw_data.GetAddress() + sizeof(*in_header), in_raw_data.GetSize() - sizeof(*in_header)); + + const DomainObjectId target_object_id = DomainObjectId{in_header->object_id}; + switch (in_header->type) { + case CmifDomainRequestType_SendMessage: + { + auto target_object = domain->GetObject(target_object_id); + + /* Mitm. If we don't have a target object, we should forward to let the server handle. */ + if (!target_object) { + R_RETURN(ctx.session->ForwardRequest(ctx)); + } + + R_UNLESS(in_header->data_size + in_header->num_in_objects * sizeof(DomainObjectId) <= in_domain_raw_data.GetSize(), sf::cmif::ResultInvalidHeaderSize()); + const cmif::PointerAndSize in_message_raw_data = cmif::PointerAndSize(in_domain_raw_data.GetAddress(), in_header->data_size); + DomainObjectId in_object_ids[8]; + R_UNLESS(in_header->num_in_objects <= util::size(in_object_ids), sf::cmif::ResultInvalidNumInObjects()); + std::memcpy(in_object_ids, reinterpret_cast<DomainObjectId *>(in_message_raw_data.GetAddress() + in_message_raw_data.GetSize()), sizeof(DomainObjectId) * in_header->num_in_objects); + DomainServiceObjectProcessor domain_processor(domain, in_object_ids, in_header->num_in_objects); + if (ctx.processor == nullptr) { + ctx.processor = std::addressof(domain_processor); + } else { + ctx.processor->SetImplementationProcessor(std::addressof(domain_processor)); + } + ctx.srv_obj = target_object.GetServiceObjectUnsafe(); + R_RETURN(target_object.ProcessMessage(ctx, in_message_raw_data)); + } + case CmifDomainRequestType_Close: + { + auto target_object = domain->GetObject(target_object_id); + + /* If the object is not in the domain, tell the server to close it. */ + if (!target_object) { + R_RETURN(ctx.session->ForwardRequest(ctx)); + } + + /* If the object is in the domain, close our copy of it. Mitm objects are required to close their associated domain id, so this shouldn't cause desynch. */ + domain->UnregisterObject(target_object_id); + R_SUCCEED(); + } + default: + R_THROW(sf::cmif::ResultInvalidInHeader()); + } + } + #endif + + Result DomainServiceObjectProcessor::PrepareForProcess(const ServiceDispatchContext &ctx, const ServerMessageRuntimeMetadata runtime_metadata) const { + /* Validate in object count. */ + R_UNLESS(m_impl_metadata.GetInObjectCount() == this->GetInObjectCount(), sf::cmif::ResultInvalidNumInObjects()); + + /* Nintendo reserves domain object IDs here. We do this later, to support mitm semantics. */ + + /* Pass onwards. */ + R_RETURN(m_impl_processor->PrepareForProcess(ctx, runtime_metadata)); + } + + Result DomainServiceObjectProcessor::GetInObjects(ServiceObjectHolder *in_objects) const { + for (size_t i = 0; i < this->GetInObjectCount(); i++) { + in_objects[i] = m_domain->GetObject(m_in_object_ids[i]); + } + R_SUCCEED(); + } + + HipcRequest DomainServiceObjectProcessor::PrepareForReply(const cmif::ServiceDispatchContext &ctx, PointerAndSize &out_raw_data, const ServerMessageRuntimeMetadata runtime_metadata) { + /* Call into impl processor, get request. */ + PointerAndSize raw_data; + HipcRequest request = m_impl_processor->PrepareForReply(ctx, raw_data, runtime_metadata); + + /* Write out header. */ + constexpr size_t out_header_size = sizeof(CmifDomainOutHeader); + const size_t impl_out_data_total_size = this->GetImplOutDataTotalSize(); + AMS_ABORT_UNLESS(out_header_size + impl_out_data_total_size + sizeof(DomainObjectId) * this->GetOutObjectCount() <= raw_data.GetSize()); + *reinterpret_cast<CmifDomainOutHeader *>(raw_data.GetPointer()) = CmifDomainOutHeader{ .num_out_objects = static_cast<u32>(this->GetOutObjectCount()), }; + + /* Set output raw data. */ + out_raw_data = cmif::PointerAndSize(raw_data.GetAddress() + out_header_size, raw_data.GetSize() - out_header_size); + m_out_object_ids = reinterpret_cast<DomainObjectId *>(out_raw_data.GetAddress() + impl_out_data_total_size); + + return request; + } + + void DomainServiceObjectProcessor::PrepareForErrorReply(const cmif::ServiceDispatchContext &ctx, PointerAndSize &out_raw_data, const ServerMessageRuntimeMetadata runtime_metadata) { + /* Call into impl processor, get request. */ + PointerAndSize raw_data; + m_impl_processor->PrepareForErrorReply(ctx, raw_data, runtime_metadata); + + /* Write out header. */ + constexpr size_t out_header_size = sizeof(CmifDomainOutHeader); + const size_t impl_out_headers_size = this->GetImplOutHeadersSize(); + AMS_ABORT_UNLESS(out_header_size + impl_out_headers_size <= raw_data.GetSize()); + *reinterpret_cast<CmifDomainOutHeader *>(raw_data.GetPointer()) = CmifDomainOutHeader{ .num_out_objects = 0, }; + + /* Set output raw data. */ + out_raw_data = cmif::PointerAndSize(raw_data.GetAddress() + out_header_size, raw_data.GetSize() - out_header_size); + + /* Nintendo unreserves domain entries here, but we haven't reserved them yet. */ + } + + void DomainServiceObjectProcessor::SetOutObjects(const cmif::ServiceDispatchContext &ctx, const HipcRequest &response, ServiceObjectHolder *out_objects, DomainObjectId *selected_ids) { + AMS_UNUSED(ctx, response); + + const size_t num_out_objects = this->GetOutObjectCount(); + + /* Copy input object IDs from command impl (normally these are Invalid, in mitm they should be set). */ + DomainObjectId object_ids[8]; + bool is_reserved[8]; + for (size_t i = 0; i < num_out_objects; i++) { + object_ids[i] = selected_ids[i]; + is_reserved[i] = false; + } + + /* Reserve object IDs as necessary. */ + { + DomainObjectId reservations[8]; + { + size_t num_unreserved_ids = 0; + DomainObjectId specific_ids[8]; + size_t num_specific_ids = 0; + for (size_t i = 0; i < num_out_objects; i++) { + /* In the mitm case, we must not reserve IDs in use by other objects, so mitm objects will set this. */ + if (object_ids[i] == InvalidDomainObjectId) { + num_unreserved_ids++; + } else { + specific_ids[num_specific_ids++] = object_ids[i]; + } + } + /* TODO: Can we make this error non-fatal? It isn't for N, since they can reserve IDs earlier due to not having to worry about mitm. */ + R_ABORT_UNLESS(m_domain->ReserveIds(reservations, num_unreserved_ids)); + m_domain->ReserveSpecificIds(specific_ids, num_specific_ids); + } + + size_t reservation_index = 0; + for (size_t i = 0; i < num_out_objects; i++) { + if (object_ids[i] == InvalidDomainObjectId) { + object_ids[i] = reservations[reservation_index++]; + is_reserved[i] = true; + } + } + } + + /* Actually set out objects. */ + for (size_t i = 0; i < num_out_objects; i++) { + if (!out_objects[i]) { + if (is_reserved[i]) { + m_domain->UnreserveIds(object_ids + i, 1); + } + object_ids[i] = InvalidDomainObjectId; + continue; + } + m_domain->RegisterObject(object_ids[i], std::move(out_objects[i])); + } + + /* Set out object IDs in message. */ + for (size_t i = 0; i < num_out_objects; i++) { + m_out_object_ids[i] = object_ids[i]; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/cmif/sf_cmif_inline_context.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/cmif/sf_cmif_inline_context.cpp new file mode 100644 index 00000000..af6447e5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/cmif/sf_cmif_inline_context.cpp @@ -0,0 +1,113 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::sf { + + namespace cmif { + + namespace { + + #if !defined(ATMOSPHERE_COMPILER_CLANG) + ALWAYS_INLINE util::AtomicRef<uintptr_t> GetAtomicSfInlineContext(os::ThreadType *thread = os::GetCurrentThread()) { + uintptr_t * const p = std::addressof(os::GetSdkInternalTlsArray(thread)->sf_inline_context); + + return util::AtomicRef<uintptr_t>(*p); + } + #else + ALWAYS_INLINE util::Atomic<uintptr_t> &GetAtomicSfInlineContext(os::ThreadType *thread = os::GetCurrentThread()) { + uintptr_t * const p = std::addressof(os::GetSdkInternalTlsArray(thread)->sf_inline_context); + static_assert(sizeof(std::atomic<uintptr_t>) == sizeof(uintptr_t)); + static_assert(sizeof(util::Atomic<uintptr_t>) == sizeof(std::atomic<uintptr_t>)); + + return *reinterpret_cast<util::Atomic<uintptr_t> *>(p); + } + #endif + + ALWAYS_INLINE void OnSetInlineContext(os::ThreadType *thread) { + #if defined(ATMOSPHERE_OS_HORIZON) + /* Ensure that libnx receives the priority value. */ + ::fsSetPriority(static_cast<::FsPriority>(::ams::sf::GetFsInlineContext(thread))); + #else + AMS_UNUSED(thread); + #endif + } + + } + + InlineContext GetInlineContext() { + /* Get the context. */ + uintptr_t thread_context = GetAtomicSfInlineContext().Load(); + + /* Copy it out. */ + InlineContext ctx; + static_assert(sizeof(ctx) <= sizeof(thread_context)); + std::memcpy(std::addressof(ctx), std::addressof(thread_context), sizeof(ctx)); + return ctx; + } + + InlineContext SetInlineContext(InlineContext ctx) { + /* Get current thread. */ + os::ThreadType * const cur_thread = os::GetCurrentThread(); + ON_SCOPE_EXIT { OnSetInlineContext(cur_thread); }; + + /* Create the new context. */ + static_assert(sizeof(ctx) <= sizeof(uintptr_t)); + uintptr_t new_context_value = 0; + std::memcpy(std::addressof(new_context_value), std::addressof(ctx), sizeof(ctx)); + + /* Get the old context. */ + uintptr_t old_context_value = GetAtomicSfInlineContext(cur_thread).Exchange(new_context_value); + + /* Convert and copy it out. */ + InlineContext old_ctx; + std::memcpy(std::addressof(old_ctx), std::addressof(old_context_value), sizeof(old_ctx)); + return old_ctx; + } + + } + + namespace { + + #if !defined(ATMOSPHERE_COMPILER_CLANG) + ALWAYS_INLINE util::AtomicRef<u8> GetAtomicFsInlineContext(os::ThreadType *thread) { + uintptr_t * const p = std::addressof(os::GetSdkInternalTlsArray(thread)->sf_inline_context); + + return util::AtomicRef<u8>(*reinterpret_cast<u8 *>(p)); + } + #else + ALWAYS_INLINE util::Atomic<u8> &GetAtomicFsInlineContext(os::ThreadType *thread) { + uintptr_t * const p = std::addressof(os::GetSdkInternalTlsArray(thread)->sf_inline_context); + static_assert(sizeof(std::atomic<u8>) == sizeof(u8)); + static_assert(sizeof(util::Atomic<u8>) == sizeof(std::atomic<u8>)); + + return *reinterpret_cast<util::Atomic<u8> *>(reinterpret_cast<u8 *>(p)); + } + #endif + + } + + u8 GetFsInlineContext(os::ThreadType *thread) { + return GetAtomicFsInlineContext(thread).Load(); + } + + u8 SetFsInlineContext(os::ThreadType *thread, u8 ctx) { + ON_SCOPE_EXIT { cmif::OnSetInlineContext(thread); }; + + return GetAtomicFsInlineContext(thread).Exchange(ctx); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/cmif/sf_cmif_service_dispatch.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/cmif/sf_cmif_service_dispatch.cpp new file mode 100644 index 00000000..7013f98f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/cmif/sf_cmif_service_dispatch.cpp @@ -0,0 +1,171 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::sf::cmif { + + namespace { + + constexpr inline u32 InHeaderMagic = util::FourCC<'S','F','C','I'>::Code; + constexpr inline u32 OutHeaderMagic = util::FourCC<'S','F','C','O'>::Code; + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(InHeaderMagic == CMIF_IN_HEADER_MAGIC); + static_assert(OutHeaderMagic == CMIF_OUT_HEADER_MAGIC); + #endif + + ALWAYS_INLINE decltype(ServiceCommandMeta::handler) FindCommandHandlerByBinarySearch(const ServiceCommandMeta *entries, const size_t entry_count, const u32 cmd_id, const hos::Version hos_version) { + /* Binary search for the handler. */ + ssize_t lo = 0; + ssize_t hi = entry_count - 1; + while (lo <= hi) { + const size_t mid = (lo + hi) / 2; + if (entries[mid].cmd_id < cmd_id) { + lo = mid + 1; + } else if (entries[mid].cmd_id > cmd_id) { + hi = mid - 1; + } else { + /* Find start. */ + size_t start = mid; + while (start > 0 && entries[start - 1].cmd_id == cmd_id) { + --start; + } + + /* Find end. */ + size_t end = mid + 1; + while (end < entry_count && entries[end].cmd_id == cmd_id) { + ++end; + } + + for (size_t idx = start; idx < end; ++idx) { + if (entries[idx].MatchesVersion(hos_version)) { + return entries[idx].GetHandler(); + } + } + + break; + } + } + + return nullptr; + } + + ALWAYS_INLINE decltype(ServiceCommandMeta::handler) FindCommandHandlerByLinearSearch(const ServiceCommandMeta *entries, const size_t entry_count, const u32 cmd_id, const hos::Version hos_version) { + for (size_t i = 0; i < entry_count; ++i) { + if (entries[i].Matches(cmd_id, hos_version)) { + return entries[i].GetHandler(); + break; + } + } + + return nullptr; + } + + ALWAYS_INLINE decltype(ServiceCommandMeta::handler) FindCommandHandler(const ServiceCommandMeta *entries, const size_t entry_count, const u32 cmd_id, const hos::Version hos_version) { + if (entry_count >= 8) { + return FindCommandHandlerByBinarySearch(entries, entry_count, cmd_id, hos_version); + } else { + return FindCommandHandlerByLinearSearch(entries, entry_count, cmd_id, hos_version); + } + } + + } + + Result impl::ServiceDispatchTableBase::ProcessMessageImpl(ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data, const ServiceCommandMeta *entries, const size_t entry_count, u32 interface_id_for_debug) const { + /* Get versioning info. */ + const auto hos_version = hos::GetVersion(); + const u32 max_cmif_version = hos_version >= hos::Version_5_0_0 ? 1 : 0; + + /* Parse the CMIF in header. */ + const CmifInHeader *in_header = reinterpret_cast<const CmifInHeader *>(in_raw_data.GetPointer()); + R_UNLESS(in_raw_data.GetSize() >= sizeof(*in_header), sf::cmif::ResultInvalidHeaderSize()); + R_UNLESS(in_header->magic == InHeaderMagic && in_header->version <= max_cmif_version, sf::cmif::ResultInvalidInHeader()); + const cmif::PointerAndSize in_message_raw_data = cmif::PointerAndSize(in_raw_data.GetAddress() + sizeof(*in_header), in_raw_data.GetSize() - sizeof(*in_header)); + const u32 cmd_id = in_header->command_id; + + /* Find a handler. */ + const auto cmd_handler = FindCommandHandler(entries, entry_count, cmd_id, hos_version); + R_UNLESS(cmd_handler != nullptr, sf::cmif::ResultUnknownCommandId()); + + /* Invoke handler. */ + CmifOutHeader *out_header = nullptr; + Result command_result = cmd_handler(&out_header, ctx, in_message_raw_data); + + /* Forward any meta-context change result. */ + if (sf::impl::ResultRequestContextChanged::Includes(command_result)) { + R_RETURN(command_result); + } + + /* Otherwise, ensure that we're able to write the output header. */ + if (out_header == nullptr) { + AMS_ABORT_UNLESS(R_FAILED(command_result)); + R_RETURN(command_result); + } + + /* Write output header to raw data. */ + *out_header = CmifOutHeader{OutHeaderMagic, 0, command_result.GetValue(), interface_id_for_debug}; + + R_SUCCEED(); + } + + #if AMS_SF_MITM_SUPPORTED + Result impl::ServiceDispatchTableBase::ProcessMessageForMitmImpl(ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data, const ServiceCommandMeta *entries, const size_t entry_count, u32 interface_id_for_debug) const { + /* Get versioning info. */ + const auto hos_version = hos::GetVersion(); + const u32 max_cmif_version = hos_version >= hos::Version_5_0_0 ? 1 : 0; + + /* Parse the CMIF in header. */ + const CmifInHeader *in_header = reinterpret_cast<const CmifInHeader *>(in_raw_data.GetPointer()); + R_UNLESS(in_raw_data.GetSize() >= sizeof(*in_header), sf::cmif::ResultInvalidHeaderSize()); + R_UNLESS(in_header->magic == InHeaderMagic && in_header->version <= max_cmif_version, sf::cmif::ResultInvalidInHeader()); + const cmif::PointerAndSize in_message_raw_data = cmif::PointerAndSize(in_raw_data.GetAddress() + sizeof(*in_header), in_raw_data.GetSize() - sizeof(*in_header)); + const u32 cmd_id = in_header->command_id; + + /* Find a handler. */ + const auto cmd_handler = FindCommandHandler(entries, entry_count, cmd_id, hos_version); + + /* If we didn't find a handler, forward the request. */ + if (cmd_handler == nullptr) { + R_RETURN(ctx.session->ForwardRequest(ctx)); + } + + /* Invoke handler. */ + CmifOutHeader *out_header = nullptr; + Result command_result = cmd_handler(&out_header, ctx, in_message_raw_data); + + /* If we should, forward the request to the forward session. */ + if (sm::mitm::ResultShouldForwardToSession::Includes(command_result)) { + R_RETURN(ctx.session->ForwardRequest(ctx)); + } + + /* Forward any meta-context change result. */ + if (sf::impl::ResultRequestContextChanged::Includes(command_result)) { + R_RETURN(command_result); + } + + /* Otherwise, ensure that we're able to write the output header. */ + if (out_header == nullptr) { + AMS_ABORT_UNLESS(R_FAILED(command_result)); + R_RETURN(command_result); + } + + /* Write output header to raw data. */ + *out_header = CmifOutHeader{OutHeaderMagic, 0, command_result.GetValue(), interface_id_for_debug}; + + R_SUCCEED(); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/cmif/sf_cmif_service_object_holder.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/cmif/sf_cmif_service_object_holder.cpp new file mode 100644 index 00000000..9da12f8d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/cmif/sf_cmif_service_object_holder.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::sf::cmif { + + Result ServiceObjectHolder::ProcessMessage(ServiceDispatchContext &ctx, const cmif::PointerAndSize &in_raw_data) const { + const auto ProcessHandler = m_dispatch_meta->ProcessHandler; + const auto *DispatchTable = m_dispatch_meta->DispatchTable; + return (DispatchTable->*ProcessHandler)(ctx, in_raw_data); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_api.os.generic.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_api.os.generic.cpp new file mode 100644 index 00000000..a6fd71d3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_api.os.generic.cpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::sf::hipc { + + void AttachMultiWaitHolderForAccept(os::MultiWaitHolderType *, os::NativeHandle) { + AMS_ABORT("TODO: Generic ams::sf::hipc::AttachMultiWaitHolderForAccept"); + } + + void AttachMultiWaitHolderForReply(os::MultiWaitHolderType *, os::NativeHandle) { + AMS_ABORT("TODO: Generic ams::sf::hipc::AttachMultiWaitHolderForAccept"); + } + + Result Receive(ReceiveResult *, os::NativeHandle, const cmif::PointerAndSize &) { + AMS_ABORT("TODO: Generic ams::sf::hipc::Receive(ReceiveResult *, os::NativeHandle, const cmif::PointerAndSize &)"); + } + + Result Receive(bool *, os::NativeHandle, const cmif::PointerAndSize &) { + AMS_ABORT("TODO: Generic ams::sf::hipc::Receive(bool *, os::NativeHandle, const cmif::PointerAndSize &)"); + } + + Result Reply(os::NativeHandle, const cmif::PointerAndSize &) { + AMS_ABORT("TODO: Generic ams::sf::hipc::Reply"); + } + + Result CreateSession(os::NativeHandle *, os::NativeHandle *) { + AMS_ABORT("TODO: Generic ams::sf::hipc::CreateSession"); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_api.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_api.os.horizon.cpp new file mode 100644 index 00000000..20dc6dd8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_api.os.horizon.cpp @@ -0,0 +1,94 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::sf::hipc { + + namespace { + + ALWAYS_INLINE Result ReceiveImpl(os::NativeHandle session_handle, void *message_buf, size_t message_buf_size) { + s32 unused_index; + if (message_buf == hipc::GetMessageBufferOnTls()) { + /* Consider: AMS_ABORT_UNLESS(message_buf_size == TlsMessageBufferSize); */ + R_RETURN(svc::ReplyAndReceive(&unused_index, &session_handle, 1, svc::InvalidHandle, std::numeric_limits<u64>::max())); + } else { + R_RETURN(svc::ReplyAndReceiveWithUserBuffer(&unused_index, reinterpret_cast<uintptr_t>(message_buf), message_buf_size, &session_handle, 1, svc::InvalidHandle, std::numeric_limits<u64>::max())); + } + } + + ALWAYS_INLINE Result ReplyImpl(os::NativeHandle session_handle, void *message_buf, size_t message_buf_size) { + s32 unused_index; + if (message_buf == hipc::GetMessageBufferOnTls()) { + /* Consider: AMS_ABORT_UNLESS(message_buf_size == TlsMessageBufferSize); */ + R_RETURN(svc::ReplyAndReceive(&unused_index, &session_handle, 0, session_handle, 0)); + } else { + R_RETURN(svc::ReplyAndReceiveWithUserBuffer(&unused_index, reinterpret_cast<uintptr_t>(message_buf), message_buf_size, &session_handle, 0, session_handle, 0)); + } + } + + } + + void AttachMultiWaitHolderForAccept(os::MultiWaitHolderType *holder, os::NativeHandle port) { + return os::InitializeMultiWaitHolder(holder, port); + } + + void AttachMultiWaitHolderForReply(os::MultiWaitHolderType *holder, os::NativeHandle request) { + return os::InitializeMultiWaitHolder(holder, request); + } + + Result Receive(ReceiveResult *out_recv_result, os::NativeHandle session_handle, const cmif::PointerAndSize &message_buffer) { + R_TRY_CATCH(ReceiveImpl(session_handle, message_buffer.GetPointer(), message_buffer.GetSize())) { + R_CATCH(svc::ResultSessionClosed) { + *out_recv_result = ReceiveResult::Closed; + R_SUCCEED(); + } + R_CATCH(svc::ResultReceiveListBroken) { + *out_recv_result = ReceiveResult::NeedsRetry; + R_SUCCEED(); + } + } R_END_TRY_CATCH; + *out_recv_result = ReceiveResult::Success; + R_SUCCEED(); + } + + Result Receive(bool *out_closed, os::NativeHandle session_handle, const cmif::PointerAndSize &message_buffer) { + R_TRY_CATCH(ReceiveImpl(session_handle, message_buffer.GetPointer(), message_buffer.GetSize())) { + R_CATCH(svc::ResultSessionClosed) { + *out_closed = true; + R_SUCCEED(); + } + } R_END_TRY_CATCH; + *out_closed = false; + R_SUCCEED(); + } + + Result Reply(os::NativeHandle session_handle, const cmif::PointerAndSize &message_buffer) { + R_TRY_CATCH(ReplyImpl(session_handle, message_buffer.GetPointer(), message_buffer.GetSize())) { + R_CONVERT(svc::ResultTimedOut, ResultSuccess()) + R_CONVERT(svc::ResultSessionClosed, ResultSuccess()) + } R_END_TRY_CATCH; + /* ReplyImpl should *always* return an error. */ + AMS_ABORT_UNLESS(false); + } + + Result CreateSession(os::NativeHandle *out_server_handle, os::NativeHandle *out_client_handle) { + R_TRY_CATCH(svc::CreateSession(out_server_handle, out_client_handle, 0, 0)) { + R_CONVERT(svc::ResultOutOfResource, sf::hipc::ResultOutOfSessions()); + } R_END_TRY_CATCH; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_mitm_query_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_mitm_query_api.cpp new file mode 100644 index 00000000..2f7dd5e7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_mitm_query_api.cpp @@ -0,0 +1,80 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sf_hipc_mitm_query_api.hpp" + +#if AMS_SF_MITM_SUPPORTED + +#define AMS_SF_HIPC_IMPL_I_MITM_QUERY_SERVICE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 65000, void, ShouldMitm, (sf::Out<bool> out, const sm::MitmProcessInfo &client_info), (out, client_info)) + +AMS_SF_DEFINE_INTERFACE(ams::sf::hipc::impl, IMitmQueryService, AMS_SF_HIPC_IMPL_I_MITM_QUERY_SERVICE_INTERFACE_INFO, 0xEC6BE3FF) + +namespace ams::sf::hipc::impl { + + namespace { + + class MitmQueryService { + private: + const ServerManagerBase::MitmQueryFunction m_query_function; + public: + MitmQueryService(ServerManagerBase::MitmQueryFunction qf) : m_query_function(qf) { /* ... */ } + + void ShouldMitm(sf::Out<bool> out, const sm::MitmProcessInfo &client_info) { + *out = m_query_function(client_info); + } + }; + static_assert(IsIMitmQueryService<MitmQueryService>); + + /* Globals. */ + constinit os::SdkMutex g_query_server_lock; + constinit bool g_constructed_server = false; + constinit bool g_registered_any = false; + + void QueryServerProcessThreadMain(void *query_server) { + reinterpret_cast<ServerManagerBase *>(query_server)->LoopProcess(); + } + + alignas(os::ThreadStackAlignment) constinit u8 g_server_process_thread_stack[16_KB]; + constinit os::ThreadType g_query_server_process_thread; + + constexpr size_t MaxServers = 0; + util::TypedStorage<sf::hipc::ServerManager<MaxServers>> g_query_server_storage; + + } + + void RegisterMitmQueryHandle(os::NativeHandle query_handle, ServerManagerBase::MitmQueryFunction query_func) { + std::scoped_lock lk(g_query_server_lock); + + if (AMS_UNLIKELY(!g_constructed_server)) { + util::ConstructAt(g_query_server_storage); + g_constructed_server = true; + } + + /* TODO: Better object factory? */ + R_ABORT_UNLESS(GetReference(g_query_server_storage).RegisterSession(query_handle, cmif::ServiceObjectHolder(sf::CreateSharedObjectEmplaced<IMitmQueryService, MitmQueryService>(query_func)))); + + if (AMS_UNLIKELY(!g_registered_any)) { + R_ABORT_UNLESS(os::CreateThread(std::addressof(g_query_server_process_thread), &QueryServerProcessThreadMain, GetPointer(g_query_server_storage), g_server_process_thread_stack, sizeof(g_server_process_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(mitm_sf, QueryServerProcessThread))); + os::SetThreadNamePointer(std::addressof(g_query_server_process_thread), AMS_GET_SYSTEM_THREAD_NAME(mitm_sf, QueryServerProcessThread)); + os::StartThread(std::addressof(g_query_server_process_thread)); + g_registered_any = true; + } + } + +} + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_mitm_query_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_mitm_query_api.hpp new file mode 100644 index 00000000..1b4a08f9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_mitm_query_api.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::sf::hipc::impl { + + #if AMS_SF_MITM_SUPPORTED + void RegisterMitmQueryHandle(os::NativeHandle query_handle, ServerManagerBase::MitmQueryFunction query_func); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_server_domain_session_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_server_domain_session_manager.cpp new file mode 100644 index 00000000..c1e6bdb6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_server_domain_session_manager.cpp @@ -0,0 +1,207 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sf_i_hipc_manager.hpp" + +namespace ams::sf::hipc { + + namespace impl { + + class HipcManagerImpl { + private: + ServerDomainSessionManager *m_manager; + ServerSession *m_session; + #if AMS_SF_MITM_SUPPORTED + const bool m_is_mitm_session; + #endif + private: + Result CloneCurrentObjectImpl(sf::OutMoveHandle &out_client_handle, ServerSessionManager *tagged_manager) { + /* Clone the object. */ + cmif::ServiceObjectHolder &&clone = m_session->m_srv_obj_holder.Clone(); + R_UNLESS(clone, sf::hipc::ResultDomainObjectNotFound()); + + /* Create new session handles. */ + os::NativeHandle server_handle, client_handle; + R_ABORT_UNLESS(hipc::CreateSession(std::addressof(server_handle), std::addressof(client_handle))); + + /* Register with manager. */ + #if AMS_SF_MITM_SUPPORTED + if (!m_is_mitm_session) { + #endif + R_ABORT_UNLESS(tagged_manager->RegisterSession(server_handle, std::move(clone))); + #if AMS_SF_MITM_SUPPORTED + } else { + /* Check that we can create a mitm session. */ + AMS_ABORT_UNLESS(ServerManagerBase::CanAnyManageMitmServers()); + + /* Clone the forward service. */ + std::shared_ptr<::Service> new_forward_service = ServerSession::CreateForwardService(); + R_ABORT_UNLESS(serviceClone(util::GetReference(m_session->m_forward_service).get(), new_forward_service.get())); + R_ABORT_UNLESS(tagged_manager->RegisterMitmSession(server_handle, std::move(clone), std::move(new_forward_service))); + } + #endif + + /* Set output client handle. */ + out_client_handle.SetValue(client_handle, false); + R_SUCCEED(); + } + public: + #if AMS_SF_MITM_SUPPORTED + explicit HipcManagerImpl(ServerDomainSessionManager *m, ServerSession *s) : m_manager(m), m_session(s), m_is_mitm_session(s->IsMitmSession()) { /* ... */ } + #else + explicit HipcManagerImpl(ServerDomainSessionManager *m, ServerSession *s) : m_manager(m), m_session(s) { /* ... */ } + #endif + + Result ConvertCurrentObjectToDomain(sf::Out<cmif::DomainObjectId> out) { + /* Allocate a domain. */ + auto domain = m_manager->AllocateDomainServiceObject(); + R_UNLESS(domain, sf::hipc::ResultOutOfDomains()); + + /* Set up the new domain object. */ + cmif::DomainObjectId object_id = cmif::InvalidDomainObjectId; + #if AMS_SF_MITM_SUPPORTED + if (m_is_mitm_session) { + /* Check that we can create a mitm session. */ + AMS_ABORT_UNLESS(ServerManagerBase::CanAnyManageMitmServers()); + + /* Make a new shared pointer to manage the allocated domain. */ + SharedPointer<cmif::MitmDomainServiceObject> cmif_domain(static_cast<cmif::MitmDomainServiceObject *>(domain), false); + + /* Convert the remote session to domain. */ + AMS_ABORT_UNLESS(util::GetReference(m_session->m_forward_service)->own_handle); + R_TRY(serviceConvertToDomain(util::GetReference(m_session->m_forward_service).get())); + + /* The object ID reservation cannot fail here, as that would cause desynchronization from target domain. */ + object_id = cmif::DomainObjectId{util::GetReference(m_session->m_forward_service)->object_id}; + domain->ReserveSpecificIds(std::addressof(object_id), 1); + + /* Register the object. */ + domain->RegisterObject(object_id, std::move(m_session->m_srv_obj_holder)); + + /* Set the new object holder. */ + m_session->m_srv_obj_holder = cmif::ServiceObjectHolder(std::move(cmif_domain)); + } else { + #else + { + #endif + /* Make a new shared pointer to manage the allocated domain. */ + SharedPointer<cmif::DomainServiceObject> cmif_domain(domain, false); + + /* Reserve a new object in the domain. */ + R_TRY(domain->ReserveIds(std::addressof(object_id), 1)); + + /* Register the object. */ + domain->RegisterObject(object_id, std::move(m_session->m_srv_obj_holder)); + + /* Set the new object holder. */ + m_session->m_srv_obj_holder = cmif::ServiceObjectHolder(std::move(cmif_domain)); + } + + /* Return the allocated id. */ + AMS_ABORT_UNLESS(object_id != cmif::InvalidDomainObjectId); + *out = object_id; + R_SUCCEED(); + } + + Result CopyFromCurrentDomain(sf::OutMoveHandle out, cmif::DomainObjectId object_id) { + /* Get domain. */ + auto domain = m_session->m_srv_obj_holder.GetServiceObject<cmif::DomainServiceObject>(); + R_UNLESS(domain != nullptr, sf::hipc::ResultTargetNotDomain()); + + /* Get domain object. */ + auto &&object = domain->GetObject(object_id); + #if AMS_SF_MITM_SUPPORTED + if (!object) { + R_UNLESS(m_is_mitm_session, sf::hipc::ResultDomainObjectNotFound()); + + /* Check that we can create a mitm session. */ + AMS_ABORT_UNLESS(ServerManagerBase::CanAnyManageMitmServers()); + + os::NativeHandle handle; + R_TRY(cmifCopyFromCurrentDomain(util::GetReference(m_session->m_forward_service)->session, object_id.value, std::addressof(handle))); + + out.SetValue(handle, false); + R_SUCCEED(); + } + #else + R_UNLESS(!!(object), sf::hipc::ResultDomainObjectNotFound()); + #endif + + #if AMS_SF_MITM_SUPPORTED + if (!m_is_mitm_session || (ServerManagerBase::CanAnyManageMitmServers() && object_id.value != serviceGetObjectId(util::GetReference(m_session->m_forward_service).get()))) { + #else + { + #endif + /* Create new session handles. */ + os::NativeHandle server_handle, client_handle; + R_ABORT_UNLESS(hipc::CreateSession(std::addressof(server_handle), std::addressof(client_handle))); + + /* Register. */ + R_ABORT_UNLESS(m_manager->RegisterSession(server_handle, std::move(object))); + + /* Set output client handle. */ + out.SetValue(client_handle, false); + #if AMS_SF_MITM_SUPPORTED + } else { + /* Check that we can create a mitm session. */ + AMS_ABORT_UNLESS(ServerManagerBase::CanAnyManageMitmServers()); + + /* Copy from the target domain. */ + os::NativeHandle new_forward_target; + R_TRY(cmifCopyFromCurrentDomain(util::GetReference(m_session->m_forward_service)->session, object_id.value, std::addressof(new_forward_target))); + + /* Create new session handles. */ + os::NativeHandle server_handle, client_handle; + R_ABORT_UNLESS(hipc::CreateSession(std::addressof(server_handle), std::addressof(client_handle))); + + /* Register. */ + std::shared_ptr<::Service> new_forward_service = ServerSession::CreateForwardService(); + serviceCreate(new_forward_service.get(), new_forward_target); + R_ABORT_UNLESS(m_manager->RegisterMitmSession(server_handle, std::move(object), std::move(new_forward_service))); + + /* Set output client handle. */ + out.SetValue(client_handle, false); + #endif + } + + R_SUCCEED(); + } + + Result CloneCurrentObject(sf::OutMoveHandle out) { + R_RETURN(this->CloneCurrentObjectImpl(out, m_manager)); + } + + void QueryPointerBufferSize(sf::Out<u16> out) { + out.SetValue(m_session->m_pointer_buffer.GetSize()); + } + + Result CloneCurrentObjectEx(sf::OutMoveHandle out, u32 tag) { + R_RETURN(this->CloneCurrentObjectImpl(out, m_manager->GetSessionManagerByTag(tag))); + } + }; + static_assert(IsIHipcManager<HipcManagerImpl>); + + } + + Result ServerDomainSessionManager::DispatchManagerRequest(ServerSession *session, const cmif::PointerAndSize &in_message, const cmif::PointerAndSize &out_message) { + /* Make a stack object, and pass a shared pointer to it to DispatchRequest. */ + /* Note: This is safe, as no additional references to the hipc manager can ever be stored. */ + /* The shared pointer to stack object is definitely gross, though. */ + UnmanagedServiceObject<impl::IHipcManager, impl::HipcManagerImpl> hipc_manager(this, session); + R_RETURN(this->DispatchRequest(cmif::ServiceObjectHolder(hipc_manager.GetShared()), session, in_message, out_message)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_server_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_server_manager.cpp new file mode 100644 index 00000000..a67ae480 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_server_manager.cpp @@ -0,0 +1,198 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sf_hipc_mitm_query_api.hpp" + +namespace ams::sf::hipc { + + #if AMS_SF_MITM_SUPPORTED + Result ServerManagerBase::InstallMitmServerImpl(os::NativeHandle *out_port_handle, sm::ServiceName service_name, ServerManagerBase::MitmQueryFunction query_func) { + /* Install the Mitm. */ + os::NativeHandle query_handle = os::InvalidNativeHandle; + R_TRY(sm::mitm::InstallMitm(out_port_handle, std::addressof(query_handle), service_name)); + + /* Register the query handle. */ + impl::RegisterMitmQueryHandle(query_handle, query_func); + + /* Clear future declarations if any, now that our query handler is present. */ + R_ABORT_UNLESS(sm::mitm::ClearFutureMitm(service_name)); + + R_SUCCEED(); + } + #endif + + void ServerManagerBase::RegisterServerSessionToWait(ServerSession *session) { + session->m_has_received = false; + + /* Set user data tag. */ + os::SetMultiWaitHolderUserData(session, static_cast<uintptr_t>(UserDataTag::Session)); + + this->LinkToDeferredList(session); + } + + void ServerManagerBase::LinkToDeferredList(os::MultiWaitHolderType *holder) { + std::scoped_lock lk(m_deferred_list_mutex); + os::LinkMultiWaitHolder(std::addressof(m_deferred_list), holder); + m_notify_event.Signal(); + } + + void ServerManagerBase::LinkDeferred() { + std::scoped_lock lk(m_deferred_list_mutex); + os::MoveAllMultiWaitHolder(std::addressof(m_multi_wait), std::addressof(m_deferred_list)); + } + + os::MultiWaitHolderType *ServerManagerBase::WaitSignaled() { + std::scoped_lock lk(m_selection_mutex); + while (true) { + this->LinkDeferred(); + auto selected = os::WaitAny(std::addressof(m_multi_wait)); + if (selected == std::addressof(m_request_stop_event_holder)) { + return nullptr; + } else if (selected == std::addressof(m_notify_event_holder)) { + m_notify_event.Clear(); + } else { + os::UnlinkMultiWaitHolder(selected); + return selected; + } + } + } + + void ServerManagerBase::ResumeProcessing() { + m_request_stop_event.Clear(); + } + + void ServerManagerBase::RequestStopProcessing() { + m_request_stop_event.Signal(); + } + + void ServerManagerBase::AddUserMultiWaitHolder(os::MultiWaitHolderType *holder) { + const auto user_data_tag = static_cast<UserDataTag>(os::GetMultiWaitHolderUserData(holder)); + AMS_ABORT_UNLESS(user_data_tag != UserDataTag::Server); + AMS_ABORT_UNLESS(user_data_tag != UserDataTag::Session); + #if AMS_SF_MITM_SUPPORTED + AMS_ABORT_UNLESS(user_data_tag != UserDataTag::MitmServer); + #endif + this->LinkToDeferredList(holder); + } + + Result ServerManagerBase::ProcessForServer(os::MultiWaitHolderType *holder) { + AMS_ABORT_UNLESS(static_cast<UserDataTag>(os::GetMultiWaitHolderUserData(holder)) == UserDataTag::Server); + + Server *server = static_cast<Server *>(holder); + ON_SCOPE_EXIT { this->LinkToDeferredList(server); }; + + /* Create new session. */ + if (server->m_static_object) { + R_RETURN(this->AcceptSession(server->m_port_handle, server->m_static_object.Clone())); + } else { + R_RETURN(this->OnNeedsToAccept(server->m_index, server)); + } + } + + #if AMS_SF_MITM_SUPPORTED + Result ServerManagerBase::ProcessForMitmServer(os::MultiWaitHolderType *holder) { + AMS_ABORT_UNLESS(static_cast<UserDataTag>(os::GetMultiWaitHolderUserData(holder)) == UserDataTag::MitmServer); + + Server *server = static_cast<Server *>(holder); + ON_SCOPE_EXIT { this->LinkToDeferredList(server); }; + + /* Create resources for new session. */ + R_RETURN(this->OnNeedsToAccept(server->m_index, server)); + } + #endif + + Result ServerManagerBase::ProcessForSession(os::MultiWaitHolderType *holder) { + AMS_ABORT_UNLESS(static_cast<UserDataTag>(os::GetMultiWaitHolderUserData(holder)) == UserDataTag::Session); + + ServerSession *session = static_cast<ServerSession *>(holder); + + cmif::PointerAndSize tls_message(hipc::GetMessageBufferOnTls(), hipc::TlsMessageBufferSize); + if (this->CanDeferInvokeRequest()) { + const cmif::PointerAndSize &saved_message = session->m_saved_message; + AMS_ABORT_UNLESS(tls_message.GetSize() == saved_message.GetSize()); + + if (!session->m_has_received) { + R_TRY(this->ReceiveRequest(session, tls_message)); + session->m_has_received = true; + std::memcpy(saved_message.GetPointer(), tls_message.GetPointer(), tls_message.GetSize()); + } else { + /* We were deferred and are re-receiving, so just memcpy. */ + std::memcpy(tls_message.GetPointer(), saved_message.GetPointer(), tls_message.GetSize()); + } + + /* Treat a meta "Context Invalidated" message as a success. */ + R_TRY_CATCH(this->ProcessRequest(session, tls_message)) { + R_CONVERT(sf::impl::ResultRequestInvalidated, ResultSuccess()); + } R_END_TRY_CATCH; + } else { + if (!session->m_has_received) { + R_TRY(this->ReceiveRequest(session, tls_message)); + session->m_has_received = true; + + #if AMS_SF_MITM_SUPPORTED + if (this->CanManageMitmServers()) { + const cmif::PointerAndSize &saved_message = session->m_saved_message; + AMS_ABORT_UNLESS(tls_message.GetSize() == saved_message.GetSize()); + + std::memcpy(saved_message.GetPointer(), tls_message.GetPointer(), tls_message.GetSize()); + } + #endif + } + + R_TRY_CATCH(this->ProcessRequest(session, tls_message)) { + R_CATCH(sf::ResultRequestDeferred) { AMS_ABORT("Request Deferred on server which does not support deferral"); } + R_CATCH(sf::impl::ResultRequestInvalidated) { AMS_ABORT("Request Invalidated on server which does not support deferral"); } + } R_END_TRY_CATCH; + } + + R_SUCCEED(); + } + + Result ServerManagerBase::Process(os::MultiWaitHolderType *holder) { + switch (static_cast<UserDataTag>(os::GetMultiWaitHolderUserData(holder))) { + case UserDataTag::Server: + R_RETURN(this->ProcessForServer(holder)); + case UserDataTag::Session: + R_RETURN(this->ProcessForSession(holder)); + #if AMS_SF_MITM_SUPPORTED + case UserDataTag::MitmServer: + AMS_ABORT_UNLESS(this->CanManageMitmServers()); + R_RETURN(this->ProcessForMitmServer(holder)); + #endif + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + bool ServerManagerBase::WaitAndProcessImpl() { + if (auto *signaled_holder = this->WaitSignaled(); signaled_holder != nullptr) { + R_ABORT_UNLESS(this->Process(signaled_holder)); + return true; + } else { + return false; + } + } + + void ServerManagerBase::WaitAndProcess() { + this->WaitAndProcessImpl(); + } + + void ServerManagerBase::LoopProcess() { + while (this->WaitAndProcessImpl()) { + /* ... */ + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_server_session_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_server_session_manager.cpp new file mode 100644 index 00000000..692ed092 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_hipc_server_session_manager.cpp @@ -0,0 +1,346 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::sf::hipc { + + namespace { + + #if AMS_SF_MITM_SUPPORTED + constexpr inline void PreProcessCommandBufferForMitm(const cmif::ServiceDispatchContext &ctx, const cmif::PointerAndSize &pointer_buffer, uintptr_t cmd_buffer) { + /* TODO: Less gross method of editing command buffer? */ + if (ctx.request.meta.send_pid) { + constexpr u64 MitmProcessIdTag = 0xFFFE000000000000ul; + constexpr u64 OldProcessIdMask = 0x0000FFFFFFFFFFFFul; + u64 *process_id = reinterpret_cast<u64 *>(cmd_buffer + sizeof(HipcHeader) + sizeof(HipcSpecialHeader)); + *process_id = (MitmProcessIdTag) | (*process_id & OldProcessIdMask); + } + + if (ctx.request.meta.num_recv_statics) { + /* TODO: Can we do this without gross bit-hackery? */ + reinterpret_cast<HipcHeader *>(cmd_buffer)->recv_static_mode = 2; + const uintptr_t old_recv_list_entry = reinterpret_cast<uintptr_t>(ctx.request.data.recv_list); + const size_t old_recv_list_offset = old_recv_list_entry - util::AlignDown(old_recv_list_entry, TlsMessageBufferSize); + *reinterpret_cast<HipcRecvListEntry *>(cmd_buffer + old_recv_list_offset) = hipcMakeRecvStatic(pointer_buffer.GetPointer(), pointer_buffer.GetSize()); + } + } + #endif + + } + + #if AMS_SF_MITM_SUPPORTED + Result ServerSession::ForwardRequest(const cmif::ServiceDispatchContext &ctx) const { + AMS_ABORT_UNLESS(ServerManagerBase::CanAnyManageMitmServers()); + AMS_ABORT_UNLESS(this->IsMitmSession()); + + /* TODO: Support non-TLS messages? */ + AMS_ABORT_UNLESS(m_saved_message.GetPointer() != nullptr); + AMS_ABORT_UNLESS(m_saved_message.GetSize() == TlsMessageBufferSize); + + /* Get TLS message buffer. */ + u32 * const message_buffer = static_cast<u32 *>(hipc::GetMessageBufferOnTls()); + + /* Copy saved TLS in. */ + std::memcpy(message_buffer, m_saved_message.GetPointer(), m_saved_message.GetSize()); + + /* Prepare buffer. */ + PreProcessCommandBufferForMitm(ctx, m_pointer_buffer, reinterpret_cast<uintptr_t>(message_buffer)); + + /* Dispatch forwards. */ + R_TRY(svc::SendSyncRequest(util::GetReference(m_forward_service)->session)); + + /* Parse, to ensure we catch any copy handles and close them. */ + { + const auto response = hipcParseResponse(message_buffer); + if (response.num_copy_handles) { + ctx.handles_to_close->num_handles = response.num_copy_handles; + for (size_t i = 0; i < response.num_copy_handles; i++) { + ctx.handles_to_close->handles[i] = response.copy_handles[i]; + } + } + } + + R_SUCCEED(); + } + #endif + + void ServerSessionManager::DestroySession(ServerSession *session) { + /* Destroy object. */ + std::destroy_at(session); + + /* Free object memory. */ + this->FreeSession(session); + } + + void ServerSessionManager::CloseSessionImpl(ServerSession *session) { + const auto session_handle = session->m_session_handle; + os::FinalizeMultiWaitHolder(session); + this->DestroySession(session); + os::CloseNativeHandle(session_handle); + } + + Result ServerSessionManager::RegisterSessionImpl(ServerSession *session_memory, os::NativeHandle session_handle, cmif::ServiceObjectHolder &&obj) { + /* Create session object. */ + std::construct_at(session_memory, session_handle, std::forward<cmif::ServiceObjectHolder>(obj)); + + /* Assign session resources. */ + session_memory->m_pointer_buffer = this->GetSessionPointerBuffer(session_memory); + session_memory->m_saved_message = this->GetSessionSavedMessageBuffer(session_memory); + + /* Register to wait list. */ + this->RegisterServerSessionToWait(session_memory); + R_SUCCEED(); + } + + Result ServerSessionManager::AcceptSessionImpl(ServerSession *session_memory, os::NativeHandle port_handle, cmif::ServiceObjectHolder &&obj) { + /* Create session handle. */ + os::NativeHandle session_handle; + #if defined(ATMOSPHERE_OS_HORIZON) + R_TRY(svc::AcceptSession(std::addressof(session_handle), port_handle)); + #else + AMS_UNUSED(port_handle); + AMS_ABORT("TODO"); + #endif + + auto session_guard = SCOPE_GUARD { os::CloseNativeHandle(session_handle); }; + + /* Register session. */ + R_TRY(this->RegisterSessionImpl(session_memory, session_handle, std::forward<cmif::ServiceObjectHolder>(obj))); + + session_guard.Cancel(); + R_SUCCEED(); + } + + #if AMS_SF_MITM_SUPPORTED + Result ServerSessionManager::RegisterMitmSessionImpl(ServerSession *session_memory, os::NativeHandle mitm_session_handle, cmif::ServiceObjectHolder &&obj, std::shared_ptr<::Service> &&fsrv) { + AMS_ABORT_UNLESS(ServerManagerBase::CanAnyManageMitmServers()); + + /* Create session object. */ + std::construct_at(session_memory, mitm_session_handle, std::forward<cmif::ServiceObjectHolder>(obj), std::forward<std::shared_ptr<::Service>>(fsrv)); + + /* Assign session resources. */ + session_memory->m_pointer_buffer = this->GetSessionPointerBuffer(session_memory); + session_memory->m_saved_message = this->GetSessionSavedMessageBuffer(session_memory); + + /* Validate session pointer buffer. */ + AMS_ABORT_UNLESS(session_memory->m_pointer_buffer.GetSize() >= util::GetReference(session_memory->m_forward_service)->pointer_buffer_size); + session_memory->m_pointer_buffer = cmif::PointerAndSize(session_memory->m_pointer_buffer.GetAddress(), util::GetReference(session_memory->m_forward_service)->pointer_buffer_size); + + /* Register to wait list. */ + this->RegisterServerSessionToWait(session_memory); + R_SUCCEED(); + } + + Result ServerSessionManager::AcceptMitmSessionImpl(ServerSession *session_memory, os::NativeHandle mitm_port_handle, cmif::ServiceObjectHolder &&obj, std::shared_ptr<::Service> &&fsrv) { + AMS_ABORT_UNLESS(ServerManagerBase::CanAnyManageMitmServers()); + + /* Create session handle. */ + os::NativeHandle mitm_session_handle; + R_TRY(svc::AcceptSession(std::addressof(mitm_session_handle), mitm_port_handle)); + + auto session_guard = SCOPE_GUARD { os::CloseNativeHandle(mitm_session_handle); }; + + /* Register session. */ + R_TRY(this->RegisterMitmSessionImpl(session_memory, mitm_session_handle, std::forward<cmif::ServiceObjectHolder>(obj), std::forward<std::shared_ptr<::Service>>(fsrv))); + + session_guard.Cancel(); + R_SUCCEED(); + } + #endif + + Result ServerSessionManager::RegisterSession(os::NativeHandle session_handle, cmif::ServiceObjectHolder &&obj) { + /* We don't actually care about what happens to the session. It'll get linked. */ + ServerSession *session_ptr = nullptr; + R_RETURN(this->RegisterSession(std::addressof(session_ptr), session_handle, std::forward<cmif::ServiceObjectHolder>(obj))); + } + + Result ServerSessionManager::AcceptSession(os::NativeHandle port_handle, cmif::ServiceObjectHolder &&obj) { + /* We don't actually care about what happens to the session. It'll get linked. */ + ServerSession *session_ptr = nullptr; + R_RETURN(this->AcceptSession(std::addressof(session_ptr), port_handle, std::forward<cmif::ServiceObjectHolder>(obj))); + } + + #if AMS_SF_MITM_SUPPORTED + Result ServerSessionManager::RegisterMitmSession(os::NativeHandle mitm_session_handle, cmif::ServiceObjectHolder &&obj, std::shared_ptr<::Service> &&fsrv) { + /* We don't actually care about what happens to the session. It'll get linked. */ + ServerSession *session_ptr = nullptr; + R_RETURN(this->RegisterMitmSession(std::addressof(session_ptr), mitm_session_handle, std::forward<cmif::ServiceObjectHolder>(obj), std::forward<std::shared_ptr<::Service>>(fsrv))); + } + + Result ServerSessionManager::AcceptMitmSession(os::NativeHandle mitm_port_handle, cmif::ServiceObjectHolder &&obj, std::shared_ptr<::Service> &&fsrv) { + /* We don't actually care about what happens to the session. It'll get linked. */ + ServerSession *session_ptr = nullptr; + R_RETURN(this->AcceptMitmSession(std::addressof(session_ptr), mitm_port_handle, std::forward<cmif::ServiceObjectHolder>(obj), std::forward<std::shared_ptr<::Service>>(fsrv))); + } + #endif + + Result ServerSessionManager::ReceiveRequestImpl(ServerSession *session, const cmif::PointerAndSize &message) { + const cmif::PointerAndSize &pointer_buffer = session->m_pointer_buffer; + + /* If the receive list is odd, we may need to receive repeatedly. */ + while (true) { + if (pointer_buffer.GetPointer()) { + hipcMakeRequestInline(message.GetPointer(), + .type = CmifCommandType_Invalid, + .num_recv_statics = HIPC_AUTO_RECV_STATIC, + ).recv_list[0] = hipcMakeRecvStatic(pointer_buffer.GetPointer(), pointer_buffer.GetSize()); + } else { + hipcMakeRequestInline(message.GetPointer(), + .type = CmifCommandType_Invalid, + ); + } + hipc::ReceiveResult recv_result; + R_TRY(hipc::Receive(std::addressof(recv_result), session->m_session_handle, message)); + switch (recv_result) { + case hipc::ReceiveResult::Success: + session->m_is_closed = false; + R_SUCCEED(); + case hipc::ReceiveResult::Closed: + session->m_is_closed = true; + R_SUCCEED(); + case hipc::ReceiveResult::NeedsRetry: + continue; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + namespace { + + ALWAYS_INLINE u32 GetCmifCommandType(const cmif::PointerAndSize &message) { + HipcHeader hdr = {}; + __builtin_memcpy(std::addressof(hdr), message.GetPointer(), sizeof(hdr)); + return hdr.type; + } + + } + + Result ServerSessionManager::ProcessRequest(ServerSession *session, const cmif::PointerAndSize &message) { + if (session->m_is_closed) { + this->CloseSessionImpl(session); + R_SUCCEED(); + } + + switch (GetCmifCommandType(message)) { + case CmifCommandType_Close: + { + this->CloseSessionImpl(session); + R_SUCCEED(); + } + default: + { + R_TRY_CATCH(this->ProcessRequestImpl(session, message, message)) { + R_CATCH_RETHROW(sf::impl::ResultRequestContextChanged) /* A meta message changing the request context has been sent. */ + R_CATCH_ALL() { + /* All other results indicate something went very wrong. */ + this->CloseSessionImpl(session); + R_SUCCEED(); + } + } R_END_TRY_CATCH; + + /* We succeeded, so we can process future messages on this session. */ + this->RegisterServerSessionToWait(session); + R_SUCCEED(); + } + } + } + + Result ServerSessionManager::ProcessRequestImpl(ServerSession *session, const cmif::PointerAndSize &in_message, const cmif::PointerAndSize &out_message) { + /* TODO: Inline context support, retrieve from raw data + 0xC. */ + const auto cmif_command_type = GetCmifCommandType(in_message); + + const auto GetInlineContext = [&]() -> cmif::InlineContext { + cmif::InlineContext ret = {}; + switch (cmif_command_type) { + case CmifCommandType_RequestWithContext: + case CmifCommandType_ControlWithContext: + if (in_message.GetSize() >= 0x10) { + static_assert(sizeof(cmif::InlineContext) == 4); + std::memcpy(std::addressof(ret), static_cast<u8 *>(in_message.GetPointer()) + 0xC, sizeof(ret)); + } + break; + default: + break; + } + return ret; + }; + + cmif::ScopedInlineContextChanger sicc(GetInlineContext()); + switch (cmif_command_type) { + case CmifCommandType_Request: + case CmifCommandType_RequestWithContext: + R_RETURN(this->DispatchRequest(session->m_srv_obj_holder.Clone(), session, in_message, out_message)); + case CmifCommandType_Control: + case CmifCommandType_ControlWithContext: + R_RETURN(this->DispatchManagerRequest(session, in_message, out_message)); + default: + R_THROW(sf::hipc::ResultUnknownCommandType()); + } + } + + Result ServerSessionManager::DispatchManagerRequest(ServerSession *session, const cmif::PointerAndSize &in_message, const cmif::PointerAndSize &out_message) { + /* This will get overridden by ... WithDomain class. */ + AMS_UNUSED(session, in_message, out_message); + R_THROW(sf::ResultNotSupported()); + } + + Result ServerSessionManager::DispatchRequest(cmif::ServiceObjectHolder &&obj_holder, ServerSession *session, const cmif::PointerAndSize &in_message, const cmif::PointerAndSize &out_message) { + /* Create request context. */ + cmif::HandlesToClose handles_to_close = {}; + cmif::ServiceDispatchContext dispatch_ctx = { + .srv_obj = obj_holder.GetServiceObjectUnsafe(), + .manager = this, + .session = session, + .processor = nullptr, /* Filled in by template implementations. */ + .handles_to_close = std::addressof(handles_to_close), + .pointer_buffer = session->m_pointer_buffer, + .in_message_buffer = in_message, + .out_message_buffer = out_message, + .request = hipcParseRequest(in_message.GetPointer()), + }; + + /* Validate message sizes. */ + const uintptr_t in_message_buffer_end = in_message.GetAddress() + in_message.GetSize(); + const uintptr_t in_raw_addr = reinterpret_cast<uintptr_t>(dispatch_ctx.request.data.data_words); + const size_t in_raw_size = dispatch_ctx.request.meta.num_data_words * sizeof(u32); + /* Note: Nintendo does not validate this size before subtracting 0x10 from it. This is not exploitable. */ + R_UNLESS(in_raw_size >= 0x10, sf::hipc::ResultInvalidRequestSize()); + R_UNLESS(in_raw_addr + in_raw_size <= in_message_buffer_end, sf::hipc::ResultInvalidRequestSize()); + const size_t recv_list_size = dispatch_ctx.request.meta.num_recv_statics == HIPC_AUTO_RECV_STATIC ? 1 : dispatch_ctx.request.meta.num_recv_statics; + const uintptr_t recv_list_end = reinterpret_cast<uintptr_t>(dispatch_ctx.request.data.recv_list + recv_list_size); + R_UNLESS(recv_list_end <= in_message_buffer_end, sf::hipc::ResultInvalidRequestSize()); + + /* CMIF has 0x10 of padding in raw data, and requires 0x10 alignment. */ + const cmif::PointerAndSize in_raw_data(util::AlignUp(in_raw_addr, 0x10), in_raw_size - 0x10); + + /* Invoke command handler. */ + R_TRY(obj_holder.ProcessMessage(dispatch_ctx, in_raw_data)); + + /* Reply. */ + { + ON_SCOPE_EXIT { + for (size_t i = 0; i < handles_to_close.num_handles; i++) { + os::CloseNativeHandle(handles_to_close.handles[i]); + } + }; + R_TRY(hipc::Reply(session->m_session_handle, out_message)); + } + + R_SUCCEED(); + } + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_i_hipc_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_i_hipc_manager.hpp new file mode 100644 index 00000000..a14ca3af --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/hipc/sf_i_hipc_manager.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#pragma once + +#define AMS_SF_HIPC_IMPL_I_HIPC_MANAGER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, ConvertCurrentObjectToDomain, (ams::sf::Out<ams::sf::cmif::DomainObjectId> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, CopyFromCurrentDomain, (ams::sf::OutMoveHandle out, ams::sf::cmif::DomainObjectId object_id), (out, object_id)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, CloneCurrentObject, (ams::sf::OutMoveHandle out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 3, void, QueryPointerBufferSize, (ams::sf::Out<u16> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, CloneCurrentObjectEx, (ams::sf::OutMoveHandle out, u32 tag), (out, tag)) + +AMS_SF_DEFINE_INTERFACE(ams::sf::hipc::impl, IHipcManager, AMS_SF_HIPC_IMPL_I_HIPC_MANAGER_INTERFACE_INFO, 0xEC6BE3FF) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/sf_default_allocation_policy.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/sf_default_allocation_policy.cpp new file mode 100644 index 00000000..e81a7588 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/sf_default_allocation_policy.cpp @@ -0,0 +1,163 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::sf { + + namespace { + + struct DefaultAllocatorImpl { + os::SdkMutexType tls_lock; + std::atomic_bool tls_allocated; + os::TlsSlot current_mr_tls_slot; + MemoryResource *default_mr; + + void EnsureCurrentMemoryResourceTlsSlotInitialized() { + if (!tls_allocated.load(std::memory_order_acquire)) { + os::LockSdkMutex(std::addressof(tls_lock)); + if (!tls_allocated.load(std::memory_order_relaxed)) { + R_ABORT_UNLESS(os::SdkAllocateTlsSlot(std::addressof(current_mr_tls_slot), nullptr)); + tls_allocated.store(true, std::memory_order_release); + } + os::UnlockSdkMutex(std::addressof(tls_lock)); + } + } + + MemoryResource *GetDefaultMemoryResource() { + return default_mr; + } + + MemoryResource *SetDefaultMemoryResource(MemoryResource *mr) { + return util::Exchange(std::addressof(default_mr), mr); + } + + MemoryResource *GetCurrentMemoryResource() { + EnsureCurrentMemoryResourceTlsSlotInitialized(); + return reinterpret_cast<MemoryResource *>(os::GetTlsValue(current_mr_tls_slot)); + } + + MemoryResource *SetCurrentMemoryResource(MemoryResource *mr) { + EnsureCurrentMemoryResourceTlsSlotInitialized(); + auto ret = reinterpret_cast<MemoryResource *>(os::GetTlsValue(current_mr_tls_slot)); + os::SetTlsValue(current_mr_tls_slot, reinterpret_cast<uintptr_t>(mr)); + return ret; + } + + MemoryResource *GetCurrentEffectiveMemoryResourceImpl() { + if (auto mr = GetCurrentMemoryResource(); mr != nullptr) { + return mr; + } + if (auto mr = GetGlobalDefaultMemoryResource(); mr != nullptr) { + return mr; + } + return nullptr; + } + }; + + constinit DefaultAllocatorImpl g_default_allocator_impl = {}; + + inline void *DefaultAllocate(size_t size, size_t align) { + AMS_UNUSED(align); + return ::operator new(size, std::nothrow); + } + + inline void DefaultDeallocate(void *ptr, size_t size, size_t align) { + AMS_UNUSED(size, align); + return ::operator delete(ptr, std::nothrow); + } + + class NewDeleteMemoryResource final : public MemoryResource { + private: + virtual void *AllocateImpl(size_t size, size_t alignment) override { + return DefaultAllocate(size, alignment); + } + + virtual void DeallocateImpl(void *buffer, size_t size, size_t alignment) override { + return DefaultDeallocate(buffer, size, alignment); + } + + virtual bool IsEqualImpl(const MemoryResource &resource) const override { + return this == std::addressof(resource); + } + }; + + constinit NewDeleteMemoryResource g_new_delete_memory_resource; + + } + + namespace impl { + + void *DefaultAllocateImpl(size_t size, size_t align, size_t offset) { + auto mr = g_default_allocator_impl.GetCurrentEffectiveMemoryResourceImpl(); + auto h = mr != nullptr ? mr->allocate(size, align) : DefaultAllocate(size, align); + if (h == nullptr) { + return nullptr; + } + + *static_cast<MemoryResource **>(h) = mr; + return static_cast<u8 *>(h) + offset; + } + + void DefaultDeallocateImpl(void *ptr, size_t size, size_t align, size_t offset) { + if (ptr == nullptr) { + return; + } + auto h = static_cast<u8 *>(ptr) - offset; + if (auto mr = *reinterpret_cast<MemoryResource **>(h); mr != nullptr) { + return mr->deallocate(h, size, align); + } else { + return DefaultDeallocate(h, size, align); + } + } + + } + + MemoryResource *GetGlobalDefaultMemoryResource() { + return g_default_allocator_impl.GetDefaultMemoryResource(); + } + + MemoryResource *GetCurrentEffectiveMemoryResource() { + if (auto mr = g_default_allocator_impl.GetCurrentEffectiveMemoryResourceImpl(); mr != nullptr) { + return mr; + } + return GetNewDeleteMemoryResource(); + } + + MemoryResource *GetCurrentMemoryResource() { + return g_default_allocator_impl.GetCurrentMemoryResource(); + } + + MemoryResource *GetNewDeleteMemoryResource() { + return std::addressof(g_new_delete_memory_resource); + } + + MemoryResource *SetGlobalDefaultMemoryResource(MemoryResource *mr) { + return g_default_allocator_impl.SetDefaultMemoryResource(mr); + } + + MemoryResource *SetCurrentMemoryResource(MemoryResource *mr) { + return g_default_allocator_impl.SetCurrentMemoryResource(mr); + } + + ScopedCurrentMemoryResourceSetter::ScopedCurrentMemoryResourceSetter(MemoryResource *mr) : m_prev(g_default_allocator_impl.GetCurrentMemoryResource()) { + os::SetTlsValue(g_default_allocator_impl.current_mr_tls_slot, reinterpret_cast<uintptr_t>(mr)); + } + + ScopedCurrentMemoryResourceSetter::~ScopedCurrentMemoryResourceSetter() { + os::SetTlsValue(g_default_allocator_impl.current_mr_tls_slot, reinterpret_cast<uintptr_t>(m_prev)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/sf_interface_id_for_debug_enforcement.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/sf_interface_id_for_debug_enforcement.os.horizon.cpp new file mode 100644 index 00000000..05bf0994 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sf/sf_interface_id_for_debug_enforcement.os.horizon.cpp @@ -0,0 +1,203 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../capsrv/server/decodersrv/decodersrv_decoder_control_service.hpp" +#include "../lm/sf/lm_i_log_getter.hpp" +#include "../lm/sf/lm_i_log_service.hpp" +#include "../sf/hipc/sf_i_hipc_manager.hpp" +#include "../sprofile/srv/sprofile_srv_i_service_getter.hpp" + +namespace { + + constexpr u32 GenerateInterfaceIdFromName(const char *s) { + /* Get the interface length. */ + const auto len = ams::util::Strlen(s); + + /* Calculate the sha256. */ + u8 hash[ams::crypto::Sha256Generator::HashSize] = {}; + ams::crypto::GenerateSha256(hash, sizeof(hash), s, len); + + /* Read it out as little endian. */ + u32 id = 0; + for (size_t i = 0; i < sizeof(id); ++i) { + id |= static_cast<u32>(hash[i]) << (BITSIZEOF(u8) * i); + } + + return id; + } + + static_assert(GenerateInterfaceIdFromName("nn::sf::hipc::detail::IHipcManager") == 0xEC6BE3FF); + + constexpr void ConvertAtmosphereNameToNintendoName(char *dst, const char *src) { + /* Determine src len. */ + const auto len = ams::util::Strlen(src); + const auto *s = src; + + /* Atmosphere names begin with ams::, Nintendo names begin with nn::. */ + AMS_ASSUME(src[0] == 'a'); + AMS_ASSUME(src[1] == 'm'); + AMS_ASSERT(src[2] == 's'); + dst[0] = 'n'; + dst[1] = 'n'; + src += 3; + dst += 2; + + /* Copy over. */ + while ((src - s) < len) { + /* Atmosphere uses ::impl:: instead of ::detail::, ::IDeprecated* for deprecated services. */ + if (src[0] == ':' && src[1] == ':' && src[2] == 'i' && src[3] == 'm' && src[4] == 'p' && src[5] == 'l' && src[6] == ':' && src[7] == ':') { + dst[0] = ':'; + dst[1] = ':'; + dst[2] = 'd'; + dst[3] = 'e'; + dst[4] = 't'; + dst[5] = 'a'; + dst[6] = 'i'; + dst[7] = 'l'; + + src += 6; /* ::impl */ + dst += 8; /* ::detail */ + } else if (src[0] == ':' && src[1] == ':' && src[2] == 'I' && src[3] == 'D' && src[4] == 'e' && src[5] == 'p' && src[6] == 'r' && src[7] == 'e' && src[8] == 'c' && src[9] == 'a' && src[10] == 't' && src[11] == 'e' && src[12] == 'd') { + dst[0] = ':'; + dst[1] = ':'; + dst[2] = 'I'; + + src += 13; /* ::IDeprecated */ + dst += 3; /* ::I */ + } else { + *(dst++) = *(src++); + } + } + + *dst = 0; + } + + constexpr u32 GenerateInterfaceIdFromAtmosphereName(const char *ams) { + char nn[0x100] = {}; + ConvertAtmosphereNameToNintendoName(nn, ams); + + return GenerateInterfaceIdFromName(nn); + } + + static_assert(GenerateInterfaceIdFromAtmosphereName("ams::sf::hipc::impl::IHipcManager") == GenerateInterfaceIdFromName("nn::sf::hipc::detail::IHipcManager")); + +} + +#define AMS_IMPL_CHECK_INTERFACE_ID(AMS_INTF) \ + static_assert(AMS_INTF::InterfaceIdForDebug == GenerateInterfaceIdFromAtmosphereName( #AMS_INTF ), #AMS_INTF) + + +AMS_IMPL_CHECK_INTERFACE_ID(ams::capsrv::sf::IDecoderControlService); + +AMS_IMPL_CHECK_INTERFACE_ID(ams::erpt::sf::IAttachment); +AMS_IMPL_CHECK_INTERFACE_ID(ams::erpt::sf::IContext); +AMS_IMPL_CHECK_INTERFACE_ID(ams::erpt::sf::IManager); +AMS_IMPL_CHECK_INTERFACE_ID(ams::erpt::sf::IReport); +AMS_IMPL_CHECK_INTERFACE_ID(ams::erpt::sf::ISession); + +static_assert(::ams::fatal::impl::IPrivateService::InterfaceIdForDebug == GenerateInterfaceIdFromName("nn::fatalsrv::IPrivateService")); // TODO: FIX-TO-MATCH +static_assert(::ams::fatal::impl::IService::InterfaceIdForDebug == GenerateInterfaceIdFromName("nn::fatalsrv::IService")); // TODO: FIX-TO-MATCH + +AMS_IMPL_CHECK_INTERFACE_ID(ams::fssrv::sf::IDirectory); +AMS_IMPL_CHECK_INTERFACE_ID(ams::fssrv::sf::IFile); +AMS_IMPL_CHECK_INTERFACE_ID(ams::fssrv::sf::IFileSystem); +AMS_IMPL_CHECK_INTERFACE_ID(ams::fssrv::sf::IStorage); +AMS_IMPL_CHECK_INTERFACE_ID(ams::fssrv::sf::IDeviceOperator); +AMS_IMPL_CHECK_INTERFACE_ID(ams::fssrv::sf::IEventNotifier); +AMS_IMPL_CHECK_INTERFACE_ID(ams::fssrv::sf::IFileSystemProxy); +AMS_IMPL_CHECK_INTERFACE_ID(ams::fssrv::sf::IFileSystemProxyForLoader); +AMS_IMPL_CHECK_INTERFACE_ID(ams::fssrv::sf::IProgramRegistry); + +static_assert(::ams::gpio::sf::IManager::InterfaceIdForDebug == GenerateInterfaceIdFromName("nn::gpio::IManager")); // TODO: FIX-TO-MATCH +static_assert(::ams::gpio::sf::IPadSession::InterfaceIdForDebug == GenerateInterfaceIdFromName("nn::gpio::IPadSession")); // TODO: FIX-TO-MATCH + +AMS_IMPL_CHECK_INTERFACE_ID(ams::htc::tenv::IService); +AMS_IMPL_CHECK_INTERFACE_ID(ams::htc::tenv::IServiceManager); + +static_assert(::ams::i2c::sf::IManager::InterfaceIdForDebug == GenerateInterfaceIdFromName("nn::i2c::IManager")); // TODO: FIX-TO-MATCH +static_assert(::ams::i2c::sf::ISession::InterfaceIdForDebug == GenerateInterfaceIdFromName("nn::i2c::ISession")); // TODO: FIX-TO-MATCH + +AMS_IMPL_CHECK_INTERFACE_ID(ams::ldr::impl::IDebugMonitorInterface); +AMS_IMPL_CHECK_INTERFACE_ID(ams::ldr::impl::IProcessManagerInterface); +AMS_IMPL_CHECK_INTERFACE_ID(ams::ldr::impl::IShellInterface); + +AMS_IMPL_CHECK_INTERFACE_ID(ams::lr::IAddOnContentLocationResolver); +AMS_IMPL_CHECK_INTERFACE_ID(ams::lr::ILocationResolver); +AMS_IMPL_CHECK_INTERFACE_ID(ams::lr::ILocationResolverManager); +AMS_IMPL_CHECK_INTERFACE_ID(ams::lr::IRegisteredLocationResolver); + +AMS_IMPL_CHECK_INTERFACE_ID(ams::lm::ILogGetter); +AMS_IMPL_CHECK_INTERFACE_ID(ams::lm::ILogger); +AMS_IMPL_CHECK_INTERFACE_ID(ams::lm::ILogService); + +AMS_IMPL_CHECK_INTERFACE_ID(ams::ncm::IContentManager); +AMS_IMPL_CHECK_INTERFACE_ID(ams::ncm::IContentMetaDatabase); +AMS_IMPL_CHECK_INTERFACE_ID(ams::ncm::IContentStorage); + +AMS_IMPL_CHECK_INTERFACE_ID(ams::ns::impl::IAsyncResult); + +//AMS_IMPL_CHECK_INTERFACE_ID(ams::pgl::sf::IEventObserver); +//AMS_IMPL_CHECK_INTERFACE_ID(ams::pgl::sf::IShellInterface); + +AMS_IMPL_CHECK_INTERFACE_ID(ams::pm::impl::IBootModeInterface); +AMS_IMPL_CHECK_INTERFACE_ID(ams::pm::impl::IDebugMonitorInterface); +AMS_IMPL_CHECK_INTERFACE_ID(ams::pm::impl::IDeprecatedDebugMonitorInterface); +AMS_IMPL_CHECK_INTERFACE_ID(ams::pm::impl::IInformationInterface); +AMS_IMPL_CHECK_INTERFACE_ID(ams::pm::impl::IShellInterface); +AMS_IMPL_CHECK_INTERFACE_ID(ams::pm::impl::IDeprecatedShellInterface); + +AMS_IMPL_CHECK_INTERFACE_ID(ams::psc::sf::IPmModule); +AMS_IMPL_CHECK_INTERFACE_ID(ams::psc::sf::IPmService); + +static_assert(::ams::pwm::sf::IChannelSession::InterfaceIdForDebug == GenerateInterfaceIdFromName("nn::pwm::IChannelSession")); // TODO: FIX-TO-MATCH +static_assert(::ams::pwm::sf::IManager::InterfaceIdForDebug == GenerateInterfaceIdFromName("nn::pwm::IManager")); // TODO: FIX-TO-MATCH + +AMS_IMPL_CHECK_INTERFACE_ID(ams::ro::impl::IDebugMonitorInterface); +AMS_IMPL_CHECK_INTERFACE_ID(ams::ro::impl::IRoInterface); + +//AMS_IMPL_CHECK_INTERFACE_ID(ams::sf::hipc::impl::IMitmQueryService); +AMS_IMPL_CHECK_INTERFACE_ID(ams::sf::hipc::impl::IHipcManager); + +AMS_IMPL_CHECK_INTERFACE_ID(ams::spl::impl::ICryptoInterface); +AMS_IMPL_CHECK_INTERFACE_ID(ams::spl::impl::IDeprecatedGeneralInterface); +AMS_IMPL_CHECK_INTERFACE_ID(ams::spl::impl::IDeviceUniqueDataInterface); +AMS_IMPL_CHECK_INTERFACE_ID(ams::spl::impl::IEsInterface); +AMS_IMPL_CHECK_INTERFACE_ID(ams::spl::impl::IFsInterface); +AMS_IMPL_CHECK_INTERFACE_ID(ams::spl::impl::IGeneralInterface); +AMS_IMPL_CHECK_INTERFACE_ID(ams::spl::impl::IManuInterface); +AMS_IMPL_CHECK_INTERFACE_ID(ams::spl::impl::IRandomInterface); +AMS_IMPL_CHECK_INTERFACE_ID(ams::spl::impl::ISslInterface); + +AMS_IMPL_CHECK_INTERFACE_ID(ams::sprofile::srv::IProfileControllerForDebug); +AMS_IMPL_CHECK_INTERFACE_ID(ams::sprofile::srv::IProfileImporter); +AMS_IMPL_CHECK_INTERFACE_ID(ams::sprofile::srv::IProfileReader); +AMS_IMPL_CHECK_INTERFACE_ID(ams::sprofile::srv::IProfileUpdateObserver); +AMS_IMPL_CHECK_INTERFACE_ID(ams::sprofile::srv::ISprofileServiceForBgAgent); +AMS_IMPL_CHECK_INTERFACE_ID(ams::sprofile::srv::ISprofileServiceForSystemProcess); +AMS_IMPL_CHECK_INTERFACE_ID(ams::sprofile::srv::IServiceGetter); + +AMS_IMPL_CHECK_INTERFACE_ID(ams::tma::IDirectoryAccessor); +AMS_IMPL_CHECK_INTERFACE_ID(ams::tma::IFileAccessor); +AMS_IMPL_CHECK_INTERFACE_ID(ams::tma::IFileManager); +AMS_IMPL_CHECK_INTERFACE_ID(ams::tma::IDeprecatedFileManager); +AMS_IMPL_CHECK_INTERFACE_ID(ams::tma::IHtcsManager); +AMS_IMPL_CHECK_INTERFACE_ID(ams::tma::IHtcManager); +AMS_IMPL_CHECK_INTERFACE_ID(ams::tma::ISocket); + +AMS_IMPL_CHECK_INTERFACE_ID(ams::usb::ds::IDsEndpoint); +AMS_IMPL_CHECK_INTERFACE_ID(ams::usb::ds::IDsInterface); +AMS_IMPL_CHECK_INTERFACE_ID(ams::usb::ds::IDsService); +AMS_IMPL_CHECK_INTERFACE_ID(ams::usb::ds::IDsRootSession); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_ams.os.horizon.c b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_ams.os.horizon.c new file mode 100644 index 00000000..b1065048 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_ams.os.horizon.c @@ -0,0 +1,142 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#define NX_SERVICE_ASSUME_NON_DOMAIN +#include "../service_guard.h" +#include "sm_ams.os.horizon.h" + +static Result _smAtmosphereCmdHas(bool *out, SmServiceName name, u32 cmd_id) { + u8 tmp = 0; + Result rc = tipcDispatchInOut(smGetServiceSessionTipc(), cmd_id, name, tmp); + if (R_SUCCEEDED(rc) && out) *out = tmp & 1; + return rc; +} + +static Result _smAtmosphereCmdInServiceNameNoOut(SmServiceName name, TipcService *srv, u32 cmd_id) { + return tipcDispatchIn(srv, cmd_id, name); +} + +static Result _smAtmosphereDetachClient(TipcService *srv) { + return tipcDispatch(srv, 4, .in_send_pid = true); +} + +Result smAtmosphereHasService(bool *out, SmServiceName name) { + return _smAtmosphereCmdHas(out, name, 65100); +} + +Result smAtmosphereWaitService(SmServiceName name) { + return _smAtmosphereCmdInServiceNameNoOut(name, smGetServiceSessionTipc(), 65101); +} + +Result smAtmosphereHasMitm(bool *out, SmServiceName name) { + return _smAtmosphereCmdHas(out, name, 65004); +} + +Result smAtmosphereWaitMitm(SmServiceName name) { + return _smAtmosphereCmdInServiceNameNoOut(name, smGetServiceSessionTipc(), 65005); +} + +static TipcService g_smAtmosphereMitmSrv; + +NX_GENERATE_SERVICE_GUARD(smAtmosphereMitm); + +Result _smAtmosphereMitmInitialize(void) { + return smAtmosphereOpenSession(&g_smAtmosphereMitmSrv); +} + +void _smAtmosphereMitmCleanup(void) { + smAtmosphereCloseSession(&g_smAtmosphereMitmSrv); +} + +TipcService* smAtmosphereMitmGetServiceSession(void) { + return &g_smAtmosphereMitmSrv; +} + +Result smAtmosphereOpenSession(TipcService *out) { + Handle sm_handle; + Result rc = svcConnectToNamedPort(&sm_handle, "sm:"); + while (R_VALUE(rc) == KERNELRESULT(NotFound)) { + svcSleepThread(50000000ul); + rc = svcConnectToNamedPort(&sm_handle, "sm:"); + } + + if (R_SUCCEEDED(rc)) { + tipcCreate(out, sm_handle); + } + + if (R_SUCCEEDED(rc)) { + rc = tipcDispatch(out, 0, .in_send_pid = true); + } + + return rc; +} + +void smAtmosphereCloseSession(TipcService *srv) { + Result rc = _smAtmosphereDetachClient(srv); + if (R_FAILED(rc)) { + svcBreak(BreakReason_Panic, (uintptr_t)&rc, sizeof(rc)); + } + tipcClose(srv); +} + +Result smAtmosphereMitmInstall(TipcService *fwd_srv, Handle *handle_out, Handle *query_out, SmServiceName name) { + Handle tmp_handles[2]; + Result rc = tipcDispatchIn(fwd_srv, 65000, name, + .out_handle_attrs = { SfOutHandleAttr_HipcMove, SfOutHandleAttr_HipcMove }, + .out_handles = tmp_handles, + ); + + if (R_SUCCEEDED(rc)) { + *handle_out = tmp_handles[0]; + *query_out = tmp_handles[1]; + } + + return rc; +} + +Result smAtmosphereMitmUninstall(SmServiceName name) { + return _smAtmosphereCmdInServiceNameNoOut(name, smGetServiceSessionTipc(), 65001); +} + +Result smAtmosphereMitmDeclareFuture(SmServiceName name) { + return _smAtmosphereCmdInServiceNameNoOut(name, smGetServiceSessionTipc(), 65006); +} + +Result smAtmosphereMitmClearFuture(SmServiceName name) { + return _smAtmosphereCmdInServiceNameNoOut(name, smGetServiceSessionTipc(), 65007); +} + +Result smAtmosphereMitmAcknowledgeSession(Service *srv_out, void *_out, SmServiceName name) { + struct { + u64 process_id; + u64 program_id; + u64 keys_held; + u64 flags; + } *out = _out; + _Static_assert(sizeof(*out) == 0x20, "sizeof(*out) == 0x20"); + + Handle tmp_handle; + + Result rc = tipcDispatchInOut(&g_smAtmosphereMitmSrv, 65003, name, *out, + .out_handle_attrs = { SfOutHandleAttr_HipcMove }, + .out_handles = &tmp_handle, + ); + + if (R_SUCCEEDED(rc)) { + serviceCreate(srv_out, tmp_handle); + } + + return rc; +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_ams.os.horizon.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_ams.os.horizon.h new file mode 100644 index 00000000..778b07a1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_ams.os.horizon.h @@ -0,0 +1,39 @@ +/** + * @file sm_ams.h + * @brief Service manager (sm) IPC wrapper for Atmosphere extensions. + * @author SciresM + * @copyright libnx Authors + */ +#pragma once + +#if defined(ATMOSPHERE_OS_HORIZON) + +#include <switch.h> + +#ifdef __cplusplus +extern "C" { +#endif + +Result smAtmosphereHasService(bool *out, SmServiceName name); +Result smAtmosphereWaitService(SmServiceName name); +Result smAtmosphereHasMitm(bool *out, SmServiceName name); +Result smAtmosphereWaitMitm(SmServiceName name); + +Result smAtmosphereMitmInitialize(void); +void smAtmosphereMitmExit(void); +TipcService *smAtmosphereMitmGetServiceSession(); + +Result smAtmosphereOpenSession(TipcService *out); +void smAtmosphereCloseSession(TipcService *srv); + +Result smAtmosphereMitmInstall(TipcService *fwd_srv, Handle *handle_out, Handle *query_out, SmServiceName name); +Result smAtmosphereMitmUninstall(SmServiceName name); +Result smAtmosphereMitmDeclareFuture(SmServiceName name); +Result smAtmosphereMitmClearFuture(SmServiceName name); +Result smAtmosphereMitmAcknowledgeSession(Service *srv_out, void *info_out, SmServiceName name); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_api.cpp new file mode 100644 index 00000000..4487bbc9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_api.cpp @@ -0,0 +1,106 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sm_utils.hpp" + +namespace ams::sm { + + #if defined(ATMOSPHERE_OS_HORIZON) + namespace { + + constinit int g_ref_count = 0; + constinit os::SdkMutex g_mutex; + + } + + /* Initialization. */ + Result Initialize() { + std::scoped_lock lk(g_mutex); + + if (g_ref_count > 0) { + ++g_ref_count; + } else { + R_TRY(::smInitialize()); + g_ref_count = 1; + } + + R_SUCCEED(); + } + + Result Finalize() { + /* NOTE: Nintendo does nothing here. */ + R_SUCCEED(); + } + + /* Ordinary SM API. */ + Result GetServiceHandle(os::NativeHandle *out, ServiceName name) { + R_RETURN(smGetServiceOriginal(out, impl::ConvertName(name))); + } + + Result RegisterService(os::NativeHandle *out, ServiceName name, size_t max_sessions, bool is_light) { + R_RETURN(smRegisterService(out, impl::ConvertName(name), is_light, static_cast<int>(max_sessions))); + } + + Result UnregisterService(ServiceName name) { + R_RETURN(smUnregisterService(impl::ConvertName(name))); + } + + /* Atmosphere extensions. */ + Result HasService(bool *out, ServiceName name) { + R_RETURN(smAtmosphereHasService(out, impl::ConvertName(name))); + } + + Result WaitService(ServiceName name) { + R_RETURN(smAtmosphereWaitService(impl::ConvertName(name))); + } + #else + Result Initialize() { + R_SUCCEED(); + } + + Result Finalize() { + R_SUCCEED(); + } + + /* Ordinary SM API. */ + Result GetServiceHandle(os::NativeHandle *out, ServiceName name) { + AMS_UNUSED(out, name); + AMS_ABORT("TODO?"); + } + + Result RegisterService(os::NativeHandle *out, ServiceName name, size_t max_sessions, bool is_light) { + AMS_UNUSED(out, name, max_sessions, is_light); + AMS_ABORT("TODO?"); + } + + Result UnregisterService(ServiceName name) { + AMS_UNUSED(name); + AMS_ABORT("TODO?"); + } + + /* Atmosphere extensions. */ + Result HasService(bool *out, ServiceName name) { + AMS_UNUSED(out, name); + AMS_ABORT("TODO?"); + } + + Result WaitService(ServiceName name) { + AMS_UNUSED(name); + AMS_ABORT("TODO?"); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_manager_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_manager_api.cpp new file mode 100644 index 00000000..6def5da8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_manager_api.cpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sm_utils.hpp" + +#if defined(ATMOSPHERE_OS_HORIZON) +#include "smm_ams.os.horizon.h" +#endif + +namespace ams::sm::manager { + + #if defined(ATMOSPHERE_OS_HORIZON) + /* Manager API. */ + Result RegisterProcess(os::ProcessId process_id, ncm::ProgramId program_id, cfg::OverrideStatus status, const void *acid, size_t acid_size, const void *aci, size_t aci_size) { + static_assert(sizeof(status) == sizeof(CfgOverrideStatus), "CfgOverrideStatus definition"); + R_RETURN(smManagerAtmosphereRegisterProcess(static_cast<u64>(process_id), static_cast<u64>(program_id), reinterpret_cast<const CfgOverrideStatus *>(std::addressof(status)), acid, acid_size, aci, aci_size)); + } + + Result UnregisterProcess(os::ProcessId process_id) { + R_RETURN(smManagerUnregisterProcess(static_cast<u64>(process_id))); + } + + /* Atmosphere extensions. */ + Result EndInitialDefers() { + R_RETURN(smManagerAtmosphereEndInitialDefers()); + } + + Result HasMitm(bool *out, ServiceName name) { + R_RETURN(smManagerAtmosphereHasMitm(out, impl::ConvertName(name))); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_mitm_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_mitm_api.cpp new file mode 100644 index 00000000..7efd46ea --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_mitm_api.cpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sm_utils.hpp" + +namespace ams::sm::mitm { + + #if defined(ATMOSPHERE_OS_HORIZON) + #if AMS_SF_MITM_SUPPORTED + /* Mitm API. */ + Result InstallMitm(os::NativeHandle *out_port, os::NativeHandle *out_query, ServiceName name) { + R_RETURN(impl::DoWithPerThreadSession([&](TipcService *fwd) { + R_RETURN(smAtmosphereMitmInstall(fwd, out_port, out_query, impl::ConvertName(name))); + })); + } + + Result UninstallMitm(ServiceName name) { + R_RETURN(smAtmosphereMitmUninstall(impl::ConvertName(name))); + } + + Result DeclareFutureMitm(ServiceName name) { + R_RETURN(smAtmosphereMitmDeclareFuture(impl::ConvertName(name))); + } + + Result ClearFutureMitm(ServiceName name) { + R_RETURN(smAtmosphereMitmClearFuture(impl::ConvertName(name))); + } + + Result AcknowledgeSession(Service *out_service, MitmProcessInfo *out_info, ServiceName name) { + return impl::DoWithMitmAcknowledgementSession([&]() { + R_RETURN(smAtmosphereMitmAcknowledgeSession(out_service, reinterpret_cast<void *>(out_info), impl::ConvertName(name))); + }); + } + + Result HasMitm(bool *out, ServiceName name) { + R_RETURN(smAtmosphereHasMitm(out, impl::ConvertName(name))); + } + + Result WaitMitm(ServiceName name) { + R_RETURN(smAtmosphereWaitMitm(impl::ConvertName(name))); + } + #endif + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_utils.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_utils.cpp new file mode 100644 index 00000000..22f2f5b1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_utils.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sm_utils.hpp" + +namespace ams::sm::impl { + + namespace { + + /* Globals. */ + constinit os::SdkRecursiveMutex g_mitm_ack_session_mutex; + constinit os::SdkRecursiveMutex g_per_thread_session_mutex; + + } + + /* Utilities. */ + os::SdkRecursiveMutex &GetMitmAcknowledgementSessionMutex() { + return g_mitm_ack_session_mutex; + } + + os::SdkRecursiveMutex &GetPerThreadSessionMutex() { + return g_per_thread_session_mutex; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_utils.hpp new file mode 100644 index 00000000..5b4d3fce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/sm_utils.hpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) +#include "sm_ams.os.horizon.h" +#endif + +namespace ams::sm::impl { + + #if defined(ATMOSPHERE_OS_HORIZON) + /* Utilities. */ + os::SdkRecursiveMutex &GetMitmAcknowledgementSessionMutex(); + os::SdkRecursiveMutex &GetPerThreadSessionMutex(); + + template<typename F> + Result DoWithMitmAcknowledgementSession(F f) { + std::scoped_lock lk(GetMitmAcknowledgementSessionMutex()); + { + R_ABORT_UNLESS(smAtmosphereMitmInitialize()); + ON_SCOPE_EXIT { smAtmosphereMitmExit(); }; + + R_RETURN(f()); + } + } + + template<typename F> + Result DoWithPerThreadSession(F f) { + TipcService srv; + { + std::scoped_lock lk(GetPerThreadSessionMutex()); + R_ABORT_UNLESS(smAtmosphereOpenSession(std::addressof(srv))); + } + { + ON_SCOPE_EXIT { smAtmosphereCloseSession(std::addressof(srv)); }; + R_RETURN(f(std::addressof(srv))); + } + } + + constexpr ALWAYS_INLINE SmServiceName ConvertName(sm::ServiceName name) { + static_assert(sizeof(SmServiceName) == sizeof(sm::ServiceName)); + return std::bit_cast<SmServiceName>(name); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/smm_ams.os.horizon.c b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/smm_ams.os.horizon.c new file mode 100644 index 00000000..1238a501 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/smm_ams.os.horizon.c @@ -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 <http://www.gnu.org/licenses/>. + */ +#include "smm_ams.os.horizon.h" + +Result smManagerAtmosphereEndInitialDefers(void) { + return tipcDispatch(smManagerTipcGetServiceSession(), 65000); +} + +Result smManagerAtmosphereRegisterProcess(u64 pid, u64 tid, const CfgOverrideStatus *status, const void *acid_sac, size_t acid_sac_size, const void *aci_sac, size_t aci_sac_size) { + const struct { + u64 pid; + u64 tid; + CfgOverrideStatus status; + } in = { pid, tid, *status }; + return tipcDispatchIn(smManagerTipcGetServiceSession(), 65002, in, + .buffer_attrs = { + SfBufferAttr_In | SfBufferAttr_HipcMapAlias, + SfBufferAttr_In | SfBufferAttr_HipcMapAlias, + }, + .buffers = { + { acid_sac, acid_sac_size }, + { aci_sac, aci_sac_size }, + }, + ); +} + +static Result _smManagerAtmosphereCmdHas(bool *out, SmServiceName name, u32 cmd_id) { + u8 tmp; + Result rc = tipcDispatchInOut(smManagerTipcGetServiceSession(), cmd_id, name, tmp); + if (R_SUCCEEDED(rc) && out) *out = tmp & 1; + return rc; +} + +Result smManagerAtmosphereHasMitm(bool *out, SmServiceName name) { + return _smManagerAtmosphereCmdHas(out, name, 65001); +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/smm_ams.os.horizon.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/smm_ams.os.horizon.h new file mode 100644 index 00000000..51a66853 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sm/smm_ams.os.horizon.h @@ -0,0 +1,30 @@ +/** + * @file smm_ams.h + * @brief Service manager manager (sm:m) IPC wrapper for Atmosphere extensions. + * @author SciresM + * @copyright libnx Authors + */ +#pragma once + +#if defined(ATMOSPHERE_OS_HORIZON) + +#include <switch.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + u64 keys_held; + u64 flags; +} CfgOverrideStatus; + +Result smManagerAtmosphereEndInitialDefers(void); +Result smManagerAtmosphereRegisterProcess(u64 pid, u64 tid, const CfgOverrideStatus *status, const void *acid_sac, size_t acid_sac_size, const void *aci_sac, size_t aci_sac_size); +Result smManagerAtmosphereHasMitm(bool *out, SmServiceName name); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/socket/impl/socket_allocator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/socket/impl/socket_allocator.hpp new file mode 100644 index 00000000..ef215f19 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/socket/impl/socket_allocator.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::socket::impl { + + constexpr inline auto MinimumHeapAlignment = 0x10; + + void *Alloc(size_t size); + void *Calloc(size_t num, size_t size); + void Free(void *ptr); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/socket/impl/socket_api.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/socket/impl/socket_api.hpp new file mode 100644 index 00000000..85ffb55e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/socket/impl/socket_api.hpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::socket::impl { + + Errno GetLastError(); + void SetLastError(Errno err); + + bool HeapIsAvailable(int generation); + int GetHeapGeneration(); + + u32 InetHtonl(u32 host); + u16 InetHtons(u16 host); + u32 InetNtohl(u32 net); + u16 InetNtohs(u16 net); + + Result Initialize(const Config &config); + Result Finalize(); + + Result InitializeAllocatorForInternal(void *buffer, size_t size); + + ssize_t RecvFrom(s32 desc, void *buffer, size_t buffer_size, MsgFlag flags, SockAddr *out_address, SockLenT *out_addr_len); + ssize_t Recv(s32 desc, void *buffer, size_t buffer_size, MsgFlag flags); + + ssize_t SendTo(s32 desc, const void *buffer, size_t buffer_size, MsgFlag flags, const SockAddr *address, SockLenT len); + ssize_t Send(s32 desc, const void *buffer, size_t buffer_size, MsgFlag flags); + + s32 Shutdown(s32 desc, ShutdownMethod how); + + s32 Socket(Family domain, Type type, Protocol protocol); + s32 SocketExempt(Family domain, Type type, Protocol protocol); + + s32 Accept(s32 desc, SockAddr *out_address, SockLenT *out_addr_len); + s32 Bind(s32 desc, const SockAddr *address, SockLenT len); + + s32 Connect(s32 desc, const SockAddr *address, SockLenT len); + + s32 GetSockName(s32 desc, SockAddr *out_address, SockLenT *out_addr_len); + s32 SetSockOpt(s32 desc, Level level, Option option_name, const void *option_value, SockLenT option_size); + + s32 Listen(s32 desc, s32 backlog); + + s32 Close(s32 desc); +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/socket/impl/socket_api.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/socket/impl/socket_api.os.horizon.cpp new file mode 100644 index 00000000..97afc515 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/socket/impl/socket_api.os.horizon.cpp @@ -0,0 +1,593 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "socket_api.hpp" +#include "socket_allocator.hpp" + +extern "C" { + +#include <switch/services/bsd.h> + +} + +namespace ams::socket::impl { + + namespace { + + constinit bool g_initialized = false; + + constinit os::SdkMutex g_heap_mutex; + + constinit lmem::HeapHandle g_heap_handle = nullptr; + constinit int g_heap_generation = -1; + + ALWAYS_INLINE bool IsInitialized() { + return g_initialized; + } + + } + + void *Alloc(size_t size) { + std::scoped_lock lk(g_heap_mutex); + + AMS_ASSERT(g_heap_generation > 0); + + void *ptr = nullptr; + + if (!g_heap_handle) { + socket::impl::SetLastError(Errno::EOpNotSupp); + } else if ((ptr = lmem::AllocateFromExpHeap(g_heap_handle, size)) == nullptr) { + socket::impl::SetLastError(Errno::ENoMem); + } + + return ptr; + } + + void *Calloc(size_t num, size_t size) { + std::scoped_lock lk(g_heap_mutex); + + AMS_ASSERT(g_heap_generation > 0); + + void *ptr = nullptr; + + if (!g_heap_handle) { + socket::impl::SetLastError(Errno::EOpNotSupp); + } else if ((ptr = lmem::AllocateFromExpHeap(g_heap_handle, size * num)) == nullptr) { + socket::impl::SetLastError(Errno::ENoMem); + } else { + std::memset(ptr, 0, size * num); + } + + return ptr; + } + + void Free(void *ptr) { + std::scoped_lock lk(g_heap_mutex); + + AMS_ASSERT(g_heap_generation > 0); + + if (!g_heap_handle) { + socket::impl::SetLastError(Errno::EOpNotSupp); + } else if (ptr != nullptr) { + lmem::FreeToExpHeap(g_heap_handle, ptr); + } + } + + bool HeapIsAvailable(int generation) { + std::scoped_lock lk(g_heap_mutex); + + return g_heap_handle && g_heap_generation == generation; + } + + int GetHeapGeneration() { + std::scoped_lock lk(g_heap_mutex); + + return g_heap_generation; + } + + Errno GetLastError() { + if (AMS_LIKELY(IsInitialized())) { + return static_cast<Errno>(errno); + } else { + return Errno::EInval; + } + } + + void SetLastError(Errno err) { + if (AMS_LIKELY(IsInitialized())) { + errno = static_cast<int>(err); + } + } + + u32 InetHtonl(u32 host) { + return util::ConvertToBigEndian(host); + } + + u16 InetHtons(u16 host) { + return util::ConvertToBigEndian(host); + } + + u32 InetNtohl(u32 net) { + return util::ConvertFromBigEndian(net); + } + + u16 InetNtohs(u16 net) { + return util::ConvertFromBigEndian(net); + } + + namespace { + + void InitializeHeapImpl(void *buffer, size_t size) { + /* NOTE: Nintendo uses both CreateOption_ThreadSafe *and* a global heap mutex. */ + /* This is unnecessary, and using a single SdkMutex is more performant, since we're not recursive. */ + std::scoped_lock lk(g_heap_mutex); + + g_heap_handle = lmem::CreateExpHeap(buffer, size, lmem::CreateOption_None); + } + + Result InitializeCommon(const Config &config) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(!IsInitialized()); + AMS_ABORT_UNLESS(config.GetMemoryPool() != nullptr); + AMS_ABORT_UNLESS(1 <= config.GetConcurrencyCountMax() && config.GetConcurrencyCountMax() <= ConcurrencyLimitMax); + if (!config.IsSmbpClient()) { AMS_ABORT_UNLESS(config.GetAllocatorPoolSize() < config.GetMemoryPoolSize()); } + AMS_ABORT_UNLESS(util::IsAligned(config.GetMemoryPoolSize(), os::MemoryPageSize)); + AMS_ABORT_UNLESS(util::IsAligned(config.GetAllocatorPoolSize(), os::MemoryPageSize)); + AMS_ABORT_UNLESS(config.GetAllocatorPoolSize() >= 4_KB); + if (!config.IsSystemClient()) { + R_UNLESS(config.GetMemoryPoolSize() >= socket::MinSocketMemoryPoolSize, socket::ResultInsufficientProvidedMemory()); + } + + const size_t transfer_memory_size = config.GetMemoryPoolSize() - config.GetAllocatorPoolSize(); + if (!config.IsSmbpClient()) { + R_UNLESS(transfer_memory_size >= socket::MinMemHeapAllocatorSize, socket::ResultInsufficientProvidedMemory()); + } else { + R_UNLESS(config.GetMemoryPoolSize() >= socket::MinimumSharedMbufPoolReservation, socket::ResultInsufficientProvidedMemory()); + } + + /* Initialize the allocator heap. */ + InitializeHeapImpl(static_cast<u8 *>(config.GetMemoryPool()) + transfer_memory_size, config.GetAllocatorPoolSize()); + + /* Initialize libnx. */ + { + const ::BsdInitConfig libnx_config = { + .version = config.GetVersion(), + .tmem_buffer = config.GetMemoryPool(), + .tmem_buffer_size = transfer_memory_size, + + .tcp_tx_buf_size = static_cast<u32>(config.GetTcpInitialSendBufferSize()), + .tcp_rx_buf_size = static_cast<u32>(config.GetTcpInitialReceiveBufferSize()), + .tcp_tx_buf_max_size = static_cast<u32>(config.GetTcpAutoSendBufferSizeMax()), + .tcp_rx_buf_max_size = static_cast<u32>(config.GetTcpAutoReceiveBufferSizeMax()), + + .udp_tx_buf_size = static_cast<u32>(config.GetUdpSendBufferSize()), + .udp_rx_buf_size = static_cast<u32>(config.GetUdpReceiveBufferSize()), + + .sb_efficiency = static_cast<u32>(config.GetSocketBufferEfficiency()), + }; + + const auto service_type = config.IsSystemClient() ? (1 << 1) : (1 << 0); + + R_ABORT_UNLESS(sm::Initialize()); + R_ABORT_UNLESS(::bsdInitialize(std::addressof(libnx_config), static_cast<u32>(config.GetConcurrencyCountMax()), service_type)); + } + + /* Set the heap generation. */ + g_heap_generation = (g_heap_generation + 1) % MinimumHeapAlignment; + + /* TODO: socket::resolver::EnableResolverCalls()? Not necessary in our case (htc), but consider calling it. */ + + g_initialized = true; + + R_SUCCEED(); + } + + ALWAYS_INLINE struct sockaddr *ConvertForLibnx(SockAddr *addr) { + static_assert(sizeof(SockAddr) == sizeof(struct sockaddr)); + static_assert(alignof(SockAddr) == alignof(struct sockaddr)); + return reinterpret_cast<struct sockaddr *>(addr); + } + + ALWAYS_INLINE const struct sockaddr *ConvertForLibnx(const SockAddr *addr) { + static_assert(sizeof(SockAddr) == sizeof(struct sockaddr)); + static_assert(alignof(SockAddr) == alignof(struct sockaddr)); + return reinterpret_cast<const struct sockaddr *>(addr); + } + + static_assert(std::same_as<SockLenT, socklen_t>); + + Errno TranslateResultToBsdErrorImpl(const Result &result) { + if (R_SUCCEEDED(result)) { + return Errno::ESuccess; + } else if (svc::ResultInvalidCurrentMemory::Includes(result) || svc::ResultOutOfAddressSpace::Includes(result)) { + return Errno::EFault; + } else if (sf::hipc::ResultCommunicationError::Includes(result)) { + return Errno::EL3Hlt; + } else if (sf::hipc::ResultOutOfResource::Includes(result)) { + return Errno::EAgain; + } else { + R_ABORT_UNLESS(result); + return static_cast<Errno>(-1); + } + } + + ALWAYS_INLINE void TranslateResultToBsdError(Errno &bsd_error, int &result) { + Errno translate_error = Errno::ESuccess; + if ((translate_error = TranslateResultToBsdErrorImpl(static_cast<::ams::Result>(::g_bsdResult))) != Errno::ESuccess) { + bsd_error = translate_error; + result = -1; + } + } + + } + + Result Initialize(const Config &config) { + R_RETURN(InitializeCommon(config)); + } + + Result Finalize() { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* TODO: If we support statistics, kill the statistics thread. */ + + /* TODO: socket::resolver::DisableResolverCalls()? */ + + /* Finalize libnx. */ + ::bsdExit(); + + /* Finalize the heap. */ + lmem::HeapHandle heap_handle; + { + std::scoped_lock lk(g_heap_mutex); + + heap_handle = g_heap_handle; + g_heap_handle = nullptr; + } + lmem::DestroyExpHeap(heap_handle); + + R_SUCCEED(); + } + + Result InitializeAllocatorForInternal(void *buffer, size_t size) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(util::IsAligned(size, os::MemoryPageSize)); + AMS_ABORT_UNLESS(size >= 4_KB); + + InitializeHeapImpl(buffer, size); + R_SUCCEED(); + } + + ssize_t RecvFrom(s32 desc, void *buffer, size_t buffer_size, MsgFlag flags, SockAddr *out_address, SockLenT *out_addr_len) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Check input. */ + if (buffer_size == 0) { + return 0; + } else if (buffer == nullptr) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } else if (buffer_size > std::numeric_limits<u32>::max()) { + socket::impl::SetLastError(Errno::EFault); + return -1; + } + + /* If this is just a normal receive call, perform a normal receive. */ + if (out_address == nullptr || out_addr_len == nullptr || *out_addr_len == 0) { + return impl::Recv(desc, buffer, buffer_size, flags); + } + + /* Perform the call. */ + socklen_t length; + Errno error = Errno::ESuccess; + int result = ::bsdRecvFrom(desc, buffer, buffer_size, static_cast<int>(flags), ConvertForLibnx(out_address), std::addressof(length)); + TranslateResultToBsdError(error, result); + + if (result >= 0) { + *out_addr_len = length; + } else { + socket::impl::SetLastError(error); + } + + return result; + } + + ssize_t Recv(s32 desc, void *buffer, size_t buffer_size, MsgFlag flags) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Check input. */ + if (buffer_size == 0) { + return 0; + } else if (buffer == nullptr) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } else if (buffer_size > std::numeric_limits<u32>::max()) { + socket::impl::SetLastError(Errno::EFault); + return -1; + } + + /* Perform the call. */ + Errno error = Errno::ESuccess; + int result = ::bsdRecv(desc, buffer, buffer_size, static_cast<int>(flags)); + TranslateResultToBsdError(error, result); + + if (result < 0) { + socket::impl::SetLastError(error); + } + + return result; + } + + ssize_t SendTo(s32 desc, const void *buffer, size_t buffer_size, MsgFlag flags, const SockAddr *address, SockLenT len) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* If this is a normal send, perform a normal send. */ + if (address == nullptr || len == 0) { + return impl::Send(desc, buffer, buffer_size, flags); + } + + /* Check input. */ + if (buffer_size == 0) { + return 0; + } else if (buffer == nullptr) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } else if (buffer_size > std::numeric_limits<u32>::max()) { + socket::impl::SetLastError(Errno::EFault); + return -1; + } + + /* Perform the call. */ + Errno error = Errno::ESuccess; + int result = ::bsdSendTo(desc, buffer, buffer_size, static_cast<int>(flags), ConvertForLibnx(address), len); + TranslateResultToBsdError(error, result); + + if (result < 0) { + socket::impl::SetLastError(error); + } + + return result; + } + + ssize_t Send(s32 desc, const void *buffer, size_t buffer_size, MsgFlag flags) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Check input. */ + if (buffer_size == 0) { + return 0; + } else if (buffer == nullptr) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } else if (buffer_size > std::numeric_limits<u32>::max()) { + socket::impl::SetLastError(Errno::EFault); + return -1; + } + + /* Perform the call. */ + Errno error = Errno::ESuccess; + int result = ::bsdSend(desc, buffer, buffer_size, static_cast<int>(flags)); + TranslateResultToBsdError(error, result); + + if (result < 0) { + socket::impl::SetLastError(error); + } + + return result; + } + + s32 Shutdown(s32 desc, ShutdownMethod how) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Perform the call. */ + Errno error = Errno::ESuccess; + int result = ::bsdShutdown(desc, static_cast<int>(how)); + TranslateResultToBsdError(error, result); + + if (result < 0) { + socket::impl::SetLastError(error); + } + + return result; + } + + s32 Socket(Family domain, Type type, Protocol protocol) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Perform the call. */ + Errno error = Errno::ESuccess; + int result = ::bsdSocket(static_cast<int>(domain), static_cast<int>(type), static_cast<int>(protocol)); + TranslateResultToBsdError(error, result); + + if (result < 0) { + socket::impl::SetLastError(error); + } + + return result; + } + + s32 SocketExempt(Family domain, Type type, Protocol protocol) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Perform the call. */ + Errno error = Errno::ESuccess; + int result = ::bsdSocketExempt(static_cast<int>(domain), static_cast<int>(type), static_cast<int>(protocol)); + TranslateResultToBsdError(error, result); + + if (result < 0) { + socket::impl::SetLastError(error); + } + + return result; + } + + s32 Accept(s32 desc, SockAddr *out_address, SockLenT *out_addr_len) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Check input. */ + if (out_address == nullptr && out_addr_len != nullptr && *out_addr_len != 0) { + socket::impl::SetLastError(Errno::EFault); + return -1; + } + + socklen_t addrlen = static_cast<socklen_t>((out_address && out_addr_len) ? *out_addr_len : 0); + + /* Perform the call. */ + Errno error = Errno::ESuccess; + int result = ::bsdAccept(desc, ConvertForLibnx(out_address), std::addressof(addrlen)); + TranslateResultToBsdError(error, result); + + if (result >= 0) { + if (out_addr_len != nullptr) { + *out_addr_len = addrlen; + } + } else { + socket::impl::SetLastError(error); + } + + return result; + } + s32 Bind(s32 desc, const SockAddr *address, SockLenT len) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Check input. */ + if (address == nullptr || len == 0) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } + + /* Perform the call. */ + Errno error = Errno::ESuccess; + int result = ::bsdBind(desc, ConvertForLibnx(address), len); + TranslateResultToBsdError(error, result); + + if (result < 0) { + socket::impl::SetLastError(error); + } + + return result; + } + + s32 Connect(s32 desc, const SockAddr *address, SockLenT len) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Check input. */ + if (address == nullptr || len == 0) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } + + /* Perform the call. */ + Errno error = Errno::ESuccess; + int result = ::bsdConnect(desc, ConvertForLibnx(address), len); + TranslateResultToBsdError(error, result); + + if (result < 0) { + socket::impl::SetLastError(error); + } + + return result; + } + + s32 GetSockName(s32 desc, SockAddr *out_address, SockLenT *out_addr_len) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Check input. */ + if (out_address == nullptr || out_addr_len == nullptr || *out_addr_len == 0) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } + + /* Perform the call. */ + socklen_t length; + Errno error = Errno::ESuccess; + int result = ::bsdGetSockName(desc, ConvertForLibnx(out_address), std::addressof(length)); + TranslateResultToBsdError(error, result); + + if (result >= 0) { + *out_addr_len = length; + } else { + socket::impl::SetLastError(error); + } + + return result; + } + + s32 SetSockOpt(s32 desc, Level level, Option option_name, const void *option_value, SockLenT option_size) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Check input. */ + if (option_value == nullptr) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } + + /* Perform the call. */ + Errno error = Errno::ESuccess; + int result = ::bsdSetSockOpt(desc, static_cast<int>(level), static_cast<int>(option_name), option_value, option_size); + TranslateResultToBsdError(error, result); + + if (result < 0) { + socket::impl::SetLastError(error); + } + + return result; + } + + s32 Listen(s32 desc, s32 backlog) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Perform the call. */ + Errno error = Errno::ESuccess; + int result = ::bsdListen(desc, backlog); + TranslateResultToBsdError(error, result); + + if (result < 0) { + socket::impl::SetLastError(error); + } + + return result; + } + + s32 Close(s32 desc) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Perform the call. */ + Errno error = Errno::ESuccess; + int result = ::bsdClose(desc); + TranslateResultToBsdError(error, result); + + if (result < 0) { + socket::impl::SetLastError(error); + } + + return result; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/socket/impl/socket_api.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/socket/impl/socket_api.os.windows.cpp new file mode 100644 index 00000000..10fdd319 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/socket/impl/socket_api.os.windows.cpp @@ -0,0 +1,741 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "socket_api.hpp" +#include "socket_allocator.hpp" + +#include <ws2tcpip.h> + +#include <stratosphere/socket/impl/socket_platform_types_translation.hpp> + +namespace ams::socket::impl { + + extern PosixWinSockConverter g_posix_winsock_converter; + + namespace { + + constinit util::Atomic<int> g_init_counter = 0; + + ALWAYS_INLINE bool IsInitialized() { + return g_init_counter > 0; + } + + class FcntlState { + private: + FcntlFlag m_flags[MaxSocketsPerClient]{}; + os::SdkRecursiveMutex m_mutexes[MaxSocketsPerClient]{}; + public: + constexpr FcntlState() = default; + public: + void ClearFlag(int fd, FcntlFlag flag) { + std::scoped_lock lk(m_mutexes[fd]); + + m_flags[fd] &= ~flag; + } + + void ClearFlags(int fd) { + std::scoped_lock lk(m_mutexes[fd]); + + m_flags[fd] = FcntlFlag::None; + } + + FcntlFlag GetFlags(int fd) { + std::scoped_lock lk(m_mutexes[fd]); + + return m_flags[fd]; + } + + int GetFlagsInt(int fd) { + return static_cast<int>(this->GetFlags(fd)); + } + + os::SdkRecursiveMutex &GetSocketLock(int fd) { + return m_mutexes[fd]; + } + + bool IsFlagClear(int fd, FcntlFlag flag) { + return !this->IsFlagSet(fd, flag); + } + + bool IsFlagSet(int fd, FcntlFlag flag) { + std::scoped_lock lk(m_mutexes[fd]); + + return (m_flags[fd] & flag) != static_cast<FcntlFlag>(0); + } + + bool IsSocketBlocking(int fd) { + return !this->IsSocketNonBlocking(fd); + } + + bool IsSocketNonBlocking(int fd) { + return this->IsFlagSet(fd, FcntlFlag::O_NonBlock); + } + + void SetFlag(int fd, FcntlFlag flag) { + std::scoped_lock lk(m_mutexes[fd]); + + m_flags[fd] |= flag; + } + }; + + constinit FcntlState g_fcntl_state; + + void TransmuteWsaError() { + switch (::WSAGetLastError()) { + case WSAEFAULT: ::WSASetLastError(WSAEINVAL); break; + case WSAENOTSOCK: ::WSASetLastError(WSAEBADF); break; + case WSAETIMEDOUT: ::WSASetLastError(WSAEWOULDBLOCK); break; + } + } + + template<std::integral T> + void TransmuteWsaError(T res) { + if (static_cast<decltype(SOCKET_ERROR)>(res) == SOCKET_ERROR) { + TransmuteWsaError(); + } + } + + } + + #define AMS_SOCKET_IMPL_SCOPED_MAKE_NON_BLOCKING(_cond, _fd) \ + /* If the socket is blocking and we need to make it non-blocking, do so. */ \ + int nonblock_##__LINE__ = 1; \ + bool set_nonblock_##__LINE__ = false; \ + if (_cond && g_fcntl_state.IsSocketBlocking(_fd)) { \ + if (const auto res = ::ioctlsocket(handle, FIONBIO, reinterpret_cast<u_long *>(std::addressof( nonblock_##__LINE__ ))); res == SOCKET_ERROR) { \ + TransmuteWsaError(); \ + return res; \ + } \ + \ + set_nonblock_##__LINE__ = true; \ + } \ + \ + ON_SCOPE_EXIT { \ + /* Preserve last error. */ \ + const auto last_err = socket::impl::GetLastError(); \ + ON_SCOPE_EXIT { socket::impl::SetLastError(last_err); }; \ + \ + /* Restore non-blocking state. */ \ + if (set_nonblock_##__LINE__) { \ + nonblock_##__LINE__ = 0; \ + \ + while (true) { \ + const auto restore_res = ::ioctlsocket(handle, FIONBIO, reinterpret_cast<u_long *>(std::addressof( nonblock_##__LINE__ ))); \ + TransmuteWsaError(restore_res); \ + if (!(restore_res == SOCKET_ERROR && socket::impl::GetLastError() == Errno::EInProgress)) { \ + break; \ + } \ + \ + os::SleepThread(TimeSpan::FromMilliSeconds(1)); \ + } \ + } \ + } + + #define AMS_SOCKET_IMPL_DO_WITH_TRANSMUTE(expr) ({ const auto res = (expr); TransmuteWsaError(res); res; }) + + + void *Alloc(size_t size) { + return ::std::malloc(size); + } + + void *Calloc(size_t num, size_t size) { + const size_t total_size = size * num; + void *buf = Alloc(size); + if (buf != nullptr) { + std::memset(buf, 0, total_size); + } + return buf; + } + + void Free(void *ptr) { + return ::std::free(ptr); + } + + Errno GetLastError() { + if (AMS_LIKELY(IsInitialized())) { + return MapErrnoValue(::WSAGetLastError()); + } else { + return Errno::EInval; + } + } + + void SetLastError(Errno err) { + if (AMS_LIKELY(IsInitialized())) { + ::WSASetLastError(MapErrnoValue(err)); + } + } + + u32 InetHtonl(u32 host) { + return ::htonl(host); + } + + u16 InetHtons(u16 host) { + return ::htons(host); + } + + u32 InetNtohl(u32 net) { + return ::ntohl(net); + } + + u16 InetNtohs(u16 net) { + return ::ntohs(net); + } + + Result Initialize(const Config &config) { + AMS_UNUSED(config); + + /* Increment init counter. */ + ++g_init_counter; + + /* Initialize winsock. */ + WSADATA wsa_data; + WORD wVersionRequested = MAKEWORD(2, 2); + + const auto res = ::WSAStartup(wVersionRequested, std::addressof(wsa_data)); + AMS_ABORT_UNLESS(res == 0); + + /* Initialize time services. */ + R_ABORT_UNLESS(time::Initialize()); + + R_SUCCEED(); + } + + Result Finalize() { + /* Check pre-conditions. */ + --g_init_counter; + AMS_ABORT_UNLESS(g_init_counter >= 0); + + /* Cleanup WSA. */ + ::WSACleanup(); + + /* Finalize time services. */ + time::Finalize(); + + /* Release all posix handles. */ + g_posix_winsock_converter.ReleaseAllPosixHandles(); + + R_SUCCEED(); + } + + ssize_t RecvFromInternal(s32 desc, void *buffer, size_t buffer_size, MsgFlag flags, SockAddr *out_address, SockLenT *out_addr_len) { + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast<SOCKET>(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } + + /* Convert the sockaddr. */ + sockaddr sa = {}; + socklen_t addr_len = sizeof(sa); + + /* Perform the call. */ + const auto res = ::recvfrom(handle, static_cast<char *>(buffer), static_cast<int>(buffer_size), MapMsgFlagValue(flags), std::addressof(sa), std::addressof(addr_len)); + if (res == SOCKET_ERROR) { + if (::WSAGetLastError() == WSAESHUTDOWN) { + ::WSASetLastError(WSAENETDOWN); + } else { + TransmuteWsaError(); + } + } + + /* Set output. */ + if (out_address != nullptr && out_addr_len != nullptr) { + if (addr_len > static_cast<socklen_t>(sizeof(*out_address))) { + addr_len = sizeof(*out_address); + } + + if (*out_addr_len != 0) { + if (static_cast<socklen_t>(*out_addr_len) > addr_len) { + *out_addr_len = addr_len; + } + + SockAddr sa_pl = {}; + CopyFromPlatform(reinterpret_cast<SockAddrIn *>(std::addressof(sa_pl)), reinterpret_cast<const sockaddr_in *>(std::addressof(sa))); + std::memcpy(out_address, std::addressof(sa_pl), *out_addr_len); + } + } + + return res; + } + + ssize_t RecvFrom(s32 desc, void *buffer, size_t buffer_size, MsgFlag flags, SockAddr *out_address, SockLenT *out_addr_len) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* If the flags have DontWait set, clear WaitAll. */ + if ((flags & MsgFlag::Msg_DontWait) == MsgFlag::Msg_DontWait) { + flags &= ~MsgFlag::Msg_WaitAll; + } + + /* If the flags haev WaitAll set but the socket is non-blocking, clear WaitAll. */ + if ((flags & MsgFlag::Msg_WaitAll) == MsgFlag::Msg_WaitAll && g_fcntl_state.IsSocketNonBlocking(desc)) { + flags &= ~MsgFlag::Msg_WaitAll; + } + + /* Check input. */ + if (handle == static_cast<SOCKET>(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } else if (buffer_size == 0) { + return 0; + } else if (buffer == nullptr) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } else if (buffer_size > std::numeric_limits<u32>::max()) { + socket::impl::SetLastError(Errno::EFault); + return -1; + } + + /* Handle blocking vs non-blocking. */ + if ((flags & MsgFlag::Msg_DontWait) == MsgFlag::Msg_DontWait) { + return RecvFromInternal(desc, buffer, buffer_size, flags, out_address, out_addr_len); + } else { + /* Lock the socket. */ + std::scoped_lock lk(g_fcntl_state.GetSocketLock(desc)); + + /* Clear don't wait from the flags. */ + flags &= MsgFlag::Msg_DontWait; + + /* If the socket is blocking, we need to make it non-blocking. */ + AMS_SOCKET_IMPL_SCOPED_MAKE_NON_BLOCKING(true, desc); + + /* Do the recv from. */ + return RecvFromInternal(desc, buffer, buffer_size, flags, out_address, out_addr_len); + } + } + + ssize_t Recv(s32 desc, void *buffer, size_t buffer_size, MsgFlag flags) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast<SOCKET>(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } else if (buffer_size == 0) { + return 0; + } else if (buffer == nullptr) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } else if (buffer_size > std::numeric_limits<u32>::max()) { + socket::impl::SetLastError(Errno::EFault); + return -1; + } + + /* If the socket is blocking, we need to make it non-blocking. */ + AMS_SOCKET_IMPL_SCOPED_MAKE_NON_BLOCKING(((flags & MsgFlag::Msg_DontWait) == MsgFlag::Msg_DontWait), desc); + + /* Perform the call. */ + return AMS_SOCKET_IMPL_DO_WITH_TRANSMUTE(::recv(handle, static_cast<char *>(buffer), static_cast<int>(buffer_size), MapMsgFlagValue(flags & ~MsgFlag::Msg_DontWait))); + } + + ssize_t SendTo(s32 desc, const void *buffer, size_t buffer_size, MsgFlag flags, const SockAddr *address, SockLenT len) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast<SOCKET>(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } + + /* Clear don't wait from flags. */ + flags &= ~MsgFlag::Msg_DontWait; + + /* Convert the sockaddr. */ + sockaddr sa = {}; + socket::impl::CopyToPlatform(reinterpret_cast<sockaddr_in *>(std::addressof(sa)), reinterpret_cast<const SockAddrIn *>(address)); + + /* Perform the call. */ + const auto res = ::sendto(handle, static_cast<const char *>(buffer), static_cast<int>(buffer_size), MapMsgFlagValue(flags), address != nullptr ? std::addressof(sa) : nullptr, static_cast<socklen_t>(len)); + if (res == SOCKET_ERROR) { + if (::WSAGetLastError() == WSAESHUTDOWN) { + ::WSASetLastError(109); + } else { + TransmuteWsaError(); + } + } + + return res; + } + + ssize_t Send(s32 desc, const void *buffer, size_t buffer_size, MsgFlag flags) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast<SOCKET>(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } + + /* Perform the call. */ + return AMS_SOCKET_IMPL_DO_WITH_TRANSMUTE(::send(handle, static_cast<const char *>(buffer), static_cast<int>(buffer_size), MapMsgFlagValue(flags))); + } + + s32 Shutdown(s32 desc, ShutdownMethod how) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast<SOCKET>(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } + + /* Perform the call. */ + const auto res = ::shutdown(handle, MapShutdownMethodValue(how)); + g_posix_winsock_converter.SetShutdown(desc, true); + TransmuteWsaError(res); + return res; + } + + s32 Socket(Family domain, Type type, Protocol protocol, bool exempt) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + const auto res = ::socket(MapFamilyValue(domain), MapTypeValue(type), MapProtocolValue(protocol)); + TransmuteWsaError(res); + + s32 posix_socket = -1; + if (res != static_cast<typename std::remove_cv<decltype(res)>::type>(SOCKET_ERROR)) { + if (posix_socket = g_posix_winsock_converter.AcquirePosixHandle(res, exempt); posix_socket < 0) { + /* Preserve last error. */ + const auto last_err = socket::impl::GetLastError(); + ON_SCOPE_EXIT { socket::impl::SetLastError(last_err); }; + + /* Close the socket. */ + ::closesocket(res); + } + } + + return posix_socket; + } + + s32 Socket(Family domain, Type type, Protocol protocol) { + return Socket(domain, type, protocol, false); + } + + s32 SocketExempt(Family domain, Type type, Protocol protocol) { + return Socket(domain, type, protocol, true); + } + + s32 Accept(s32 desc, SockAddr *out_address, SockLenT *out_addr_len) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast<SOCKET>(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } + + /* Check shutdown. */ + bool is_shutdown = false; + if (const auto res = g_posix_winsock_converter.GetShutdown(is_shutdown, desc); res == SOCKET_ERROR || (res == 0 && is_shutdown)) { + socket::impl::SetLastError(Errno::EConnAborted); + return -1; + } + + /* Accept. */ + sockaddr sa = {}; + socklen_t sa_len = sizeof(sa); + const auto res = ::accept(handle, std::addressof(sa), std::addressof(sa_len)); + if (res == static_cast<typename std::remove_cv<decltype(res)>::type>(SOCKET_ERROR)) { + if (::WSAGetLastError() == WSAEOPNOTSUPP) { + ::WSASetLastError(WSAEINVAL); + } else { + TransmuteWsaError(); + } + } + + /* Set output. */ + if (out_address != nullptr && out_addr_len != nullptr) { + if (sa_len > static_cast<socklen_t>(sizeof(*out_address))) { + sa_len = sizeof(*out_address); + } + + if (*out_addr_len != 0) { + if (static_cast<socklen_t>(*out_addr_len) > sa_len) { + *out_addr_len = sa_len; + } + + SockAddr sa_pl = {}; + CopyFromPlatform(reinterpret_cast<SockAddrIn *>(std::addressof(sa_pl)), reinterpret_cast<const sockaddr_in *>(std::addressof(sa))); + std::memcpy(out_address, std::addressof(sa_pl), *out_addr_len); + } + + *out_addr_len = sa_len; + } + + if (res == static_cast<typename std::remove_cv<decltype(res)>::type>(SOCKET_ERROR)) { + return res; + } + + s32 fd = -1; + bool is_exempt = false; + if (g_posix_winsock_converter.GetSocketExempt(is_exempt, desc) == 0) { + fd = g_posix_winsock_converter.AcquirePosixHandle(res, is_exempt); + } + + if (fd < 0) { + /* Preserve last error. */ + const auto last_err = socket::impl::GetLastError(); + ON_SCOPE_EXIT { socket::impl::SetLastError(last_err); }; + + ::closesocket(res); + + return SOCKET_ERROR; + } + + return fd; + } + + s32 Bind(s32 desc, const SockAddr *address, SockLenT len) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast<SOCKET>(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } else if (address == nullptr) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } + + /* Convert the sockaddr. */ + sockaddr sa = {}; + socket::impl::CopyToPlatform(reinterpret_cast<sockaddr_in *>(std::addressof(sa)), reinterpret_cast<const SockAddrIn *>(address)); + + return AMS_SOCKET_IMPL_DO_WITH_TRANSMUTE(::bind(handle, std::addressof(sa), static_cast<socklen_t>(len))); + } + + s32 Connect(s32 desc, const SockAddr *address, SockLenT len) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast<SOCKET>(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } + + /* Convert the sockaddr. */ + sockaddr sa = {}; + if (address != nullptr) { + if (reinterpret_cast<const SockAddrIn *>(address)->sin_port == 0) { + socket::impl::SetLastError(Errno::EAddrNotAvail); + return -1; + } + + socket::impl::CopyToPlatform(reinterpret_cast<sockaddr_in *>(std::addressof(sa)), reinterpret_cast<const SockAddrIn *>(address)); + } + + const auto res = ::connect(handle, address != nullptr ? std::addressof(sa) : nullptr, len); + if (res == SOCKET_ERROR) { + const auto wsa_err = ::WSAGetLastError(); + if (wsa_err == WSAEWOULDBLOCK) { + ::WSASetLastError(WSAEINPROGRESS); + } else if (wsa_err != WSAETIMEDOUT) { + TransmuteWsaError(); + } + } + + return res; + } + + s32 GetSockName(s32 desc, SockAddr *out_address, SockLenT *out_addr_len) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast<SOCKET>(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } + + /* We may end up preserving the last wsa error. */ + const auto last_err = ::WSAGetLastError(); + + /* Do the call. */ + sockaddr sa = {}; + + auto res = ::getsockname(handle, out_address != nullptr ? std::addressof(sa) : nullptr, reinterpret_cast<socklen_t *>(out_addr_len)); + if (res == SOCKET_ERROR) { + if (::WSAGetLastError() == WSAEINVAL) { + ::WSASetLastError(last_err); + + sa = {}; + res = 0; + } else { + TransmuteWsaError(); + } + } + + /* Copy out. */ + if (out_address != nullptr) { + CopyFromPlatform(reinterpret_cast<SockAddrIn *>(out_address), reinterpret_cast<const sockaddr_in *>(std::addressof(sa))); + } + + return res; + } + + s32 SetSockOpt(s32 desc, Level level, Option option_name, const void *option_value, SockLenT option_size) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast<SOCKET>(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } + + union SocketOptionValue { + linger option_linger; + DWORD option_timeout_ms; + DWORD option_exempt; + }; + + SocketOptionValue sockopt_value = {}; + socklen_t option_value_length = option_size; + const char *p_option_value = nullptr; + + switch (option_name) { + case Option::So_Linger: + case Option::So_Nn_Linger: + { + if (option_value_length < static_cast<socklen_t>(sizeof(sockopt_value.option_linger))) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } + option_value_length = sizeof(sockopt_value.option_linger); + CopyToPlatform(std::addressof(sockopt_value.option_linger), reinterpret_cast<const Linger *>(option_value)); + p_option_value = reinterpret_cast<const char *>(std::addressof(sockopt_value.option_linger)); + } + break; + case Option::So_SndTimeo: + case Option::So_RcvTimeo: + { + if (option_value_length < static_cast<socklen_t>(sizeof(sockopt_value.option_timeout_ms))) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } + option_value_length = sizeof(sockopt_value.option_timeout_ms); + sockopt_value.option_timeout_ms = (reinterpret_cast<const TimeVal *>(option_value)->tv_sec * 1000) + (reinterpret_cast<const TimeVal *>(option_value)->tv_usec / 1000); + p_option_value = reinterpret_cast<const char *>(std::addressof(sockopt_value.option_timeout_ms)); + } + break; + case Option::So_Nn_Shutdown_Exempt: + { + if (option_value_length < static_cast<socklen_t>(sizeof(sockopt_value.option_exempt))) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } + + return g_posix_winsock_converter.SetSocketExempt(desc, *reinterpret_cast<const decltype(sockopt_value.option_exempt) *>(option_value) != 0); + } + break; + default: + p_option_value = reinterpret_cast<const char *>(option_value); + break; + } + + return AMS_SOCKET_IMPL_DO_WITH_TRANSMUTE(::setsockopt(handle, MapLevelValue(level), MapOptionValue(level, option_name), p_option_value, option_value_length)); + } + + s32 Listen(s32 desc, s32 backlog) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Convert socket. */ + SOCKET handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + + /* Check input. */ + if (handle == static_cast<SOCKET>(socket::InvalidSocket)) { + socket::impl::SetLastError(Errno::EBadf); + return -1; + } + + /* Check shutdown. */ + bool is_shutdown = false; + if (const auto res = g_posix_winsock_converter.GetShutdown(is_shutdown, desc); res == SOCKET_ERROR || (res == 0 && is_shutdown)) { + socket::impl::SetLastError(Errno::EInval); + return -1; + } + + return AMS_SOCKET_IMPL_DO_WITH_TRANSMUTE(::listen(handle, backlog)); + } + + s32 Close(s32 desc) { + /* Check pre-conditions. */ + AMS_ABORT_UNLESS(IsInitialized()); + + /* Check that we can close. */ + static constinit os::SdkMutex s_close_lock; + SOCKET handle = static_cast<SOCKET>(socket::InvalidSocket); + { + std::scoped_lock lk(s_close_lock); + + handle = g_posix_winsock_converter.PosixToWinsockSocket(desc); + if (handle == static_cast<SOCKET>(socket::InvalidSocket)) { + return SOCKET_ERROR; + } + + g_posix_winsock_converter.ReleasePosixHandle(desc); + } + + /* Do the close. */ + const auto res = ::closesocket(handle); + g_fcntl_state.ClearFlags(desc); + + return res; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/socket/impl/socket_platform_types_translation.os.windows.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/socket/impl/socket_platform_types_translation.os.windows.cpp new file mode 100644 index 00000000..b03f8a58 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/socket/impl/socket_platform_types_translation.os.windows.cpp @@ -0,0 +1,776 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "socket_api.hpp" + +#include <ws2tcpip.h> + +#include <stratosphere/socket/impl/socket_platform_types_translation.hpp> + +namespace ams::socket::impl { + + ///* TODO: Custom sys/* headers, probably. */ + #define AF_LINK 18 + + #define MSG_TRUNC 0x10 + #define MSG_CTRUNC 0x20 + #define MSG_DONTWAIT 0x80 + + #define SHUT_RD 0 + #define SHUT_WR 1 + #define SHUT_RDWR 2 + + #define O_NONBLOCK 4 + + #define TCP_MAXSEG 4 + + PosixWinSockConverter g_posix_winsock_converter; + + s32 PosixWinSockConverter::AcquirePosixHandle(SOCKET winsock, bool exempt) { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_mutex); + + /* Get initial index. */ + const auto initial_index = GetInitialIndex(winsock); + + /* Try to find an open index. */ + for (auto posix = initial_index; posix < static_cast<int>(MaxSocketsPerClient); ++posix) { + if (m_data[posix].winsock == static_cast<SOCKET>(INVALID_SOCKET)) { + m_data[posix].winsock = winsock; + m_data[posix].exempt = exempt; + return posix; + } + } + + for (auto posix = 0; posix < initial_index; ++posix) { + if (m_data[posix].winsock == static_cast<SOCKET>(INVALID_SOCKET)) { + m_data[posix].winsock = winsock; + m_data[posix].exempt = exempt; + return posix; + } + } + + /* We're out of open handles. */ + socket::impl::SetLastError(Errno::EMFile); + return SOCKET_ERROR; + } + + s32 PosixWinSockConverter::GetShutdown(bool &shutdown, s32 posix) { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_mutex); + + /* Check input. */ + if (static_cast<u32>(posix) >= MaxSocketsPerClient || m_data[posix].winsock == static_cast<SOCKET>(INVALID_SOCKET)) { + socket::impl::SetLastError(Errno::EBadf); + return SOCKET_ERROR; + } + + /* Set the output. */ + shutdown = m_data[posix].shutdown; + return 0; + } + + s32 PosixWinSockConverter::GetSocketExempt(bool &exempt, s32 posix) { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_mutex); + + /* Check input. */ + if (static_cast<u32>(posix) >= MaxSocketsPerClient || m_data[posix].winsock == static_cast<SOCKET>(INVALID_SOCKET)) { + socket::impl::SetLastError(Errno::EBadf); + return SOCKET_ERROR; + } + + /* Set the output. */ + exempt = m_data[posix].exempt; + return 0; + } + + SOCKET PosixWinSockConverter::PosixToWinsockSocket(s32 posix) { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_mutex); + + /* Check input. */ + if (static_cast<u32>(posix) >= MaxSocketsPerClient || m_data[posix].winsock == static_cast<SOCKET>(INVALID_SOCKET)) { + socket::impl::SetLastError(Errno::EBadf); + return SOCKET_ERROR; + } + + return m_data[posix].winsock; + } + + void PosixWinSockConverter::ReleaseAllPosixHandles() { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_mutex); + + for (size_t i = 0; i < MaxSocketsPerClient; ++i) { + m_data[i] = SocketData{}; + } + } + + void PosixWinSockConverter::ReleasePosixHandle(s32 posix) { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_mutex); + + AMS_ASSERT(static_cast<u32>(posix) < MaxSocketsPerClient); + + m_data[posix] = SocketData{}; + } + + s32 PosixWinSockConverter::SetShutdown(s32 posix, bool shutdown) { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_mutex); + + /* Check input. */ + if (static_cast<u32>(posix) >= MaxSocketsPerClient || m_data[posix].winsock == static_cast<SOCKET>(INVALID_SOCKET)) { + socket::impl::SetLastError(Errno::EBadf); + return SOCKET_ERROR; + } + + /* Set the shutdown. */ + m_data[posix].shutdown = shutdown; + return 0; + } + + s32 PosixWinSockConverter::SetSocketExempt(s32 posix, bool exempt) { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_mutex); + + /* Check input. */ + if (static_cast<u32>(posix) >= MaxSocketsPerClient || m_data[posix].winsock == static_cast<SOCKET>(INVALID_SOCKET)) { + socket::impl::SetLastError(Errno::EBadf); + return SOCKET_ERROR; + } + + /* Set the exempt. */ + m_data[posix].exempt = exempt; + return 0; + } + + s32 PosixWinSockConverter::WinsockToPosixSocket(SOCKET winsock) { + /* Acquire exclusive access. */ + std::scoped_lock lk(m_mutex); + + /* Get initial index. */ + const auto initial_index = GetInitialIndex(winsock); + + /* Try to find an open index. */ + for (auto posix = initial_index; posix < static_cast<int>(MaxSocketsPerClient); ++posix) { + if (m_data[posix].winsock == winsock) { + return posix; + } + } + + for (auto posix = 0; posix < initial_index; ++posix) { + if (m_data[posix].winsock == winsock) { + return posix; + } + } + + /* We failed to find the posix handle. */ + return -1; + } + + s32 MapProtocolValue(Protocol protocol) { + s32 mapped = -1; + + switch (protocol) { + case Protocol::IpProto_Ip: mapped = IPPROTO_IP; break; + case Protocol::IpProto_Icmp: mapped = IPPROTO_ICMP; break; + case Protocol::IpProto_Tcp: mapped = IPPROTO_TCP; break; + case Protocol::IpProto_Udp: mapped = IPPROTO_UDP; break; + case Protocol::IpProto_None: mapped = IPPROTO_NONE; break; + case Protocol::IpProto_UdpLite: mapped = IPPROTO_UDP; break; + case Protocol::IpProto_Raw: mapped = IPPROTO_RAW; break; + case Protocol::IpProto_Max: mapped = IPPROTO_MAX; break; + default: + AMS_SDK_LOG("WARNING: Invalid ams::Socket::Protocol %d\n", static_cast<int>(protocol)); + break; + } + + if (mapped == -1) { + AMS_SDK_LOG("WARNING: ams::Socket::Protocol %d is not supported by Win32/Win64.\n", static_cast<int>(protocol)); + } + + return mapped; + } + + Protocol MapProtocolValue(s32 protocol) { + Protocol mapped = Protocol::IpProto_None; + + switch (protocol) { + case IPPROTO_IP: mapped = Protocol::IpProto_Ip; break; + case IPPROTO_ICMP: mapped = Protocol::IpProto_Icmp; break; + case IPPROTO_TCP: mapped = Protocol::IpProto_Tcp; break; + case IPPROTO_UDP: mapped = Protocol::IpProto_Udp; break; + case IPPROTO_NONE: mapped = Protocol::IpProto_None; break; + case IPPROTO_RAW: mapped = Protocol::IpProto_Raw; break; + case IPPROTO_MAX: mapped = Protocol::IpProto_Max; break; + default: + AMS_SDK_LOG("WARNING: Invalid socket protocol %d\n", static_cast<int>(protocol)); + break; + } + + return mapped; + } + + s32 MapTypeValue(Type type) { + s32 mapped = -1; + + switch (type) { + case Type::Sock_Default: mapped = 0; break; + case Type::Sock_Stream: mapped = SOCK_STREAM; break; + case Type::Sock_Dgram: mapped = SOCK_DGRAM; break; + case Type::Sock_Raw: mapped = SOCK_RAW; break; + case Type::Sock_SeqPacket: mapped = SOCK_SEQPACKET; break; + case Type::Sock_NonBlock: mapped = -1; break; + default: + AMS_SDK_LOG("WARNING: Invalid ams::Socket::Type %d\n", static_cast<int>(type)); + break; + } + + if (mapped == -1) { + AMS_SDK_LOG("WARNING: ams::Socket::Type %d is not supported by Win32/Win64.\n", static_cast<int>(type)); + } + + return mapped; + } + + Type MapTypeValue(s32 type) { + Type mapped = Type::Sock_Default; + + switch (type) { + case 0: mapped = Type::Sock_Default; break; + case SOCK_STREAM: mapped = Type::Sock_Stream; break; + case SOCK_DGRAM: mapped = Type::Sock_Dgram; break; + case SOCK_RAW: mapped = Type::Sock_Raw; break; + case SOCK_SEQPACKET: mapped = Type::Sock_SeqPacket; break; + default: + AMS_SDK_LOG("WARNING: Invalid socket type %d\n", static_cast<int>(type)); + break; + } + + return mapped; + } + + s8 MapFamilyValue(Family family) { + s8 mapped = -1; + + switch (family) { + case Family::Af_Unspec: mapped = AF_UNSPEC; break; + case Family::Af_Inet: mapped = AF_INET; break; + case Family::Af_Route: mapped = -1; break; + case Family::Af_Link: mapped = AF_LINK; break; + case Family::Af_Inet6: mapped = AF_INET6; break; + case Family::Af_Max: mapped = AF_MAX; break; + default: + AMS_SDK_LOG("WARNING: Invalid ams::Socket::Family %d\n", static_cast<int>(family)); + break; + } + + if (mapped == -1) { + AMS_SDK_LOG("WARNING: ams::Socket::Family %d is not supported by Win32/Win64.\n", static_cast<int>(family)); + } + + return mapped; + } + + Family MapFamilyValue(s8 family) { + Family mapped = Family::Af_Unspec; + + switch (family) { + case AF_UNSPEC:mapped = Family::Af_Unspec; break; + case AF_INET: mapped = Family::Af_Inet; break; + case AF_LINK: mapped = Family::Af_Link; break; + case AF_INET6: mapped = Family::Af_Inet6; break; + case AF_MAX: mapped = Family::Af_Max; break; + default: + AMS_SDK_LOG("WARNING: Invalid socket family %d\n", static_cast<int>(family)); + break; + } + + return mapped; + } + + s32 MapMsgFlagValue(MsgFlag flag) { + s32 mapped = 0; + + if ((flag & MsgFlag::Msg_Oob) != MsgFlag::Msg_None) { mapped |= MSG_OOB; } + if ((flag & MsgFlag::Msg_Peek) != MsgFlag::Msg_None) { mapped |= MSG_PEEK; } + if ((flag & MsgFlag::Msg_DontRoute) != MsgFlag::Msg_None) { mapped |= MSG_DONTROUTE; } + if ((flag & MsgFlag::Msg_Trunc) != MsgFlag::Msg_None) { mapped |= MSG_TRUNC; } + if ((flag & MsgFlag::Msg_CTrunc) != MsgFlag::Msg_None) { mapped |= MSG_CTRUNC; } + if ((flag & MsgFlag::Msg_WaitAll) != MsgFlag::Msg_None) { mapped |= MSG_WAITALL; } + if ((flag & MsgFlag::Msg_DontWait) != MsgFlag::Msg_None) { mapped |= MSG_DONTWAIT; } + + return mapped; + } + + MsgFlag MapMsgFlagValue(s32 flag) { + MsgFlag mapped = MsgFlag::Msg_None; + + if (flag & MSG_OOB) { mapped |= MsgFlag::Msg_Oob; } + if (flag & MSG_PEEK) { mapped |= MsgFlag::Msg_Peek; } + if (flag & MSG_DONTROUTE) { mapped |= MsgFlag::Msg_DontRoute; } + if (flag & MSG_TRUNC) { mapped |= MsgFlag::Msg_Trunc; } + if (flag & MSG_CTRUNC) { mapped |= MsgFlag::Msg_CTrunc; } + if (flag & MSG_WAITALL) { mapped |= MsgFlag::Msg_WaitAll; } + if (flag & MSG_DONTWAIT) { mapped |= MsgFlag::Msg_DontWait; } + + return mapped; + } + + u32 MapAddrInfoFlagValue(AddrInfoFlag flag) { + u32 mapped = 0; + + if ((flag & AddrInfoFlag::Ai_Passive) != AddrInfoFlag::Ai_None) { mapped |= AI_PASSIVE; } + if ((flag & AddrInfoFlag::Ai_CanonName) != AddrInfoFlag::Ai_None) { mapped |= AI_CANONNAME; } + if ((flag & AddrInfoFlag::Ai_NumericHost) != AddrInfoFlag::Ai_None) { mapped |= AI_NUMERICHOST; } + if ((flag & AddrInfoFlag::Ai_NumericServ) != AddrInfoFlag::Ai_None) { mapped |= AI_NUMERICSERV; } + if ((flag & AddrInfoFlag::Ai_AddrConfig) != AddrInfoFlag::Ai_None) { mapped |= AI_ADDRCONFIG; } + + return mapped; + } + + AddrInfoFlag MapAddrInfoFlagValue(u32 flag) { + AddrInfoFlag mapped = AddrInfoFlag::Ai_None; + + if (flag & AI_PASSIVE) { mapped |= AddrInfoFlag::Ai_Passive; } + if (flag & AI_CANONNAME) { mapped |= AddrInfoFlag::Ai_CanonName; } + if (flag & AI_NUMERICHOST) { mapped |= AddrInfoFlag::Ai_NumericHost; } + if (flag & AI_NUMERICSERV) { mapped |= AddrInfoFlag::Ai_NumericServ; } + if (flag & AI_ADDRCONFIG) { mapped |= AddrInfoFlag::Ai_AddrConfig; } + + return mapped; + } + + u32 MapShutdownMethodValue(ShutdownMethod how) { + u32 mapped = -1; + + switch (how) { + case ShutdownMethod::Shut_Rd: mapped = SHUT_RD; break; + case ShutdownMethod::Shut_Wr: mapped = SHUT_WR; break; + case ShutdownMethod::Shut_RdWr: mapped = SHUT_RDWR; break; + default: + AMS_SDK_LOG("WARNING: Invalid ams::Socket::ShutdownMethod %d\n", static_cast<int>(how)); + break; + } + + return mapped; + } + + ShutdownMethod MapShutdownMethodValue(u32 how) { + ShutdownMethod mapped = static_cast<ShutdownMethod>(-1); + + switch (how) { + case SHUT_RD: mapped = ShutdownMethod::Shut_Rd; break; + case SHUT_WR: mapped = ShutdownMethod::Shut_Wr; break; + case SHUT_RDWR: mapped = ShutdownMethod::Shut_RdWr; break; + default: + AMS_SDK_LOG("WARNING: Invalid socket shutdown %d\n", static_cast<int>(how)); + break; + } + + return mapped; + } + + u32 MapFcntlFlagValue(FcntlFlag flag) { + u32 mapped = 0; + + switch (flag) { + case FcntlFlag::O_NonBlock: mapped = O_NONBLOCK; break; + default: + AMS_SDK_LOG("WARNING: Invalid ams::Socket::FcntlFlag %d\n", static_cast<int>(flag)); + break; + } + + return mapped; + } + + FcntlFlag MapFcntlFlagValue(u32 flag) { + FcntlFlag mapped = FcntlFlag::None; + + switch (flag) { + case O_NONBLOCK: mapped = FcntlFlag::O_NonBlock; break; + default: + AMS_SDK_LOG("WARNING: Invalid socket fcntl flag %d\n", static_cast<int>(flag)); + break; + } + + return mapped; + } + + s32 MapLevelValue(Level level) { + s32 mapped = -1; + + switch (level) { + case Level::Sol_Socket: mapped = SOL_SOCKET; break; + case Level::Sol_Ip: mapped = IPPROTO_IP; break; + case Level::Sol_Icmp: mapped = IPPROTO_ICMP; break; + case Level::Sol_Tcp: mapped = IPPROTO_TCP; break; + case Level::Sol_Udp: mapped = IPPROTO_UDP; break; + case Level::Sol_UdpLite: mapped = IPPROTO_UDP; break; + default: + AMS_SDK_LOG("WARNING: Invalid ams::Socket::Level %d\n", static_cast<int>(level)); + break; + } + + return mapped; + } + + Level MapLevelValue(s32 level) { + Level mapped = static_cast<Level>(0); + + switch (level) { + case SOL_SOCKET: mapped = Level::Sol_Socket; break; + case IPPROTO_IP: mapped = Level::Sol_Ip; break; + case IPPROTO_ICMP: mapped = Level::Sol_Icmp; break; + case IPPROTO_TCP: mapped = Level::Sol_Tcp; break; + case IPPROTO_UDP: mapped = Level::Sol_Udp; break; + default: + AMS_SDK_LOG("WARNING: Invalid socket level %d\n", static_cast<int>(level)); + break; + } + + return mapped; + } + + s32 MapOptionValue(Level level, Option option) { + s32 mapped = -1; + + switch (level) { + case Level::Sol_Socket: + switch (option) { + case Option::So_Debug: mapped = SO_DEBUG; break; + case Option::So_AcceptConn: mapped = SO_ACCEPTCONN; break; + case Option::So_ReuseAddr: mapped = SO_REUSEADDR; break; + case Option::So_KeepAlive: mapped = SO_KEEPALIVE; break; + case Option::So_DontRoute: mapped = SO_DONTROUTE; break; + case Option::So_Broadcast: mapped = SO_BROADCAST; break; + case Option::So_UseLoopback: mapped = SO_USELOOPBACK; break; + case Option::So_Linger: mapped = SO_LINGER; break; + case Option::So_OobInline: mapped = SO_OOBINLINE; break; + case Option::So_ReusePort: mapped = -1; break; + case Option::So_SndBuf: mapped = SO_SNDBUF; break; + case Option::So_RcvBuf: mapped = SO_RCVBUF; break; + case Option::So_SndLoWat: mapped = SO_SNDLOWAT; break; + case Option::So_RcvLoWat: mapped = SO_RCVLOWAT; break; + case Option::So_SndTimeo: mapped = SO_SNDTIMEO; break; + case Option::So_RcvTimeo: mapped = SO_RCVTIMEO; break; + case Option::So_Error: mapped = SO_ERROR; break; + case Option::So_Type: mapped = SO_TYPE; break; + case Option::So_Label: mapped = -1; break; + case Option::So_PeerLabel: mapped = -1; break; + case Option::So_ListenQLimit: mapped = -1; break; + case Option::So_ListenQLen: mapped = -1; break; + case Option::So_ListenIncQLen: mapped = -1; break; + case Option::So_SetFib: mapped = -1; break; + case Option::So_User_Cookie: mapped = -1; break; + case Option::So_Protocol: mapped = -1; break; + case Option::So_Vendor: mapped = -1; break; + case Option::So_Nn_Linger: mapped = -1; break; + case Option::So_Nn_Shutdown_Exempt: mapped = -1; break; + default: + AMS_SDK_LOG("WARNING: Invalid ams::Socket::Option %d for Level::Sol_Socket\n", static_cast<int>(option)); + break; + } + break; + case Level::Sol_Ip: + switch (option) { + case Option::Ip_Options: mapped = IP_OPTIONS; break; + case Option::Ip_HdrIncl: mapped = IP_HDRINCL; break; + case Option::Ip_Tos: mapped = IP_TOS; break; + case Option::Ip_Ttl: mapped = IP_TTL; break; + case Option::Ip_RecvOpts: mapped = -1; break; + case Option::Ip_Multicast_If: mapped = IP_MULTICAST_IF; break; + case Option::Ip_Multicast_Ttl: mapped = IP_MULTICAST_TTL; break; + case Option::Ip_Multicast_Loop: mapped = IP_MULTICAST_LOOP; break; + case Option::Ip_Add_Membership: mapped = IP_ADD_MEMBERSHIP; break; + case Option::Ip_Drop_Membership: mapped = IP_DROP_MEMBERSHIP; break; + case Option::Ip_Multicast_Vif: mapped = -1; break; + case Option::Ip_Rsvp_On: mapped = -1; break; + case Option::Ip_Rsvp_Off: mapped = -1; break; + case Option::Ip_Rsvp_Vif_On: mapped = -1; break; + case Option::Ip_Rsvp_Vif_Off: mapped = -1; break; + case Option::Ip_PortRange: mapped = -1; break; + case Option::Ip_Faith: mapped = -1; break; + case Option::Ip_OnesBcast: mapped = -1; break; + case Option::Ip_BindAny: mapped = -1; break; + case Option::Ip_RecvTtl: mapped = -1; break; + case Option::Ip_MinTtl: mapped = -1; break; + case Option::Ip_DontFrag: mapped = -1; break; + case Option::Ip_RecvTos: mapped = -1; break; + case Option::Ip_Add_Source_Membership: mapped = IP_ADD_SOURCE_MEMBERSHIP; break; + case Option::Ip_Drop_Source_Membership: mapped = IP_DROP_SOURCE_MEMBERSHIP; break; + case Option::Ip_Block_Source: mapped = IP_BLOCK_SOURCE; break; + case Option::Ip_Unblock_Source: mapped = IP_UNBLOCK_SOURCE; break; + default: + AMS_SDK_LOG("WARNING: Invalid ams::Socket::Option %d for Level::Sol_Ip\n", static_cast<int>(option)); + break; + } + break; + case Level::Sol_Tcp: + switch (option) { + case Option::Tcp_NoDelay: mapped = TCP_NODELAY; break; + case Option::Tcp_MaxSeg: mapped = TCP_MAXSEG; break; + case Option::Tcp_NoPush: mapped = -1; break; + case Option::Tcp_NoOpt: mapped = -1; break; + case Option::Tcp_Md5Sig: mapped = -1; break; + case Option::Tcp_Info: mapped = -1; break; + case Option::Tcp_Congestion: mapped = -1; break; + case Option::Tcp_KeepInit: mapped = -1; break; + case Option::Tcp_KeepIdle: mapped = -1; break; + case Option::Tcp_KeepIntvl: mapped = -1; break; + case Option::Tcp_KeepCnt: mapped = -1; break; + case Option::Tcp_Vendor: mapped = -1; break; + default: + AMS_SDK_LOG("WARNING: Invalid ams::Socket::Option %d for Level::Sol_Tcp\n", static_cast<int>(option)); + break; + } + break; + default: + AMS_SDK_LOG("WARNING: Invalid option level %d\n", static_cast<int>(level)); + break; + } + + if (mapped == -1) { + AMS_SDK_LOG("WARNING: ams::Socket::Option %d is not supported by Win32/Win64.\n", static_cast<int>(option)); + } + + return mapped; + } + + Option MapOptionValue(s32 level, s32 option) { + Option mapped = static_cast<Option>(0); + + switch (level) { + case SOL_SOCKET: + switch (option) { + case SO_DEBUG: mapped = Option::So_Debug; break; + case SO_ACCEPTCONN: mapped = Option::So_AcceptConn; break; + case SO_REUSEADDR: mapped = Option::So_ReuseAddr; break; + case SO_KEEPALIVE: mapped = Option::So_KeepAlive; break; + case SO_DONTROUTE: mapped = Option::So_DontRoute; break; + case SO_BROADCAST: mapped = Option::So_Broadcast; break; + case SO_USELOOPBACK: mapped = Option::So_UseLoopback; break; + case SO_LINGER: mapped = Option::So_Linger; break; + case SO_OOBINLINE: mapped = Option::So_OobInline; break; + case SO_SNDBUF: mapped = Option::So_SndBuf; break; + case SO_RCVBUF: mapped = Option::So_RcvBuf; break; + case SO_SNDLOWAT: mapped = Option::So_SndLoWat; break; + case SO_RCVLOWAT: mapped = Option::So_RcvLoWat; break; + case SO_SNDTIMEO: mapped = Option::So_SndTimeo; break; + case SO_RCVTIMEO: mapped = Option::So_RcvTimeo; break; + case SO_ERROR: mapped = Option::So_Error; break; + case SO_TYPE: mapped = Option::So_Type; break; + default: + AMS_SDK_LOG("WARNING: Invalid socket option %d for SOL_SOCKET\n", static_cast<int>(option)); + break; + } + break; + case IPPROTO_IP: + switch (option) { + case IP_OPTIONS: mapped = Option::Ip_Options; break; + case IP_HDRINCL: mapped = Option::Ip_HdrIncl; break; + case IP_TOS: mapped = Option::Ip_Tos; break; + case IP_TTL: mapped = Option::Ip_Ttl; break; + case IP_MULTICAST_IF: mapped = Option::Ip_Multicast_If; break; + case IP_MULTICAST_TTL: mapped = Option::Ip_Multicast_Ttl; break; + case IP_MULTICAST_LOOP: mapped = Option::Ip_Multicast_Loop; break; + case IP_ADD_MEMBERSHIP: mapped = Option::Ip_Add_Membership; break; + case IP_DROP_MEMBERSHIP: mapped = Option::Ip_Drop_Membership; break; + case IP_ADD_SOURCE_MEMBERSHIP: mapped = Option::Ip_Add_Source_Membership; break; + case IP_DROP_SOURCE_MEMBERSHIP: mapped = Option::Ip_Drop_Source_Membership; break; + case IP_BLOCK_SOURCE: mapped = Option::Ip_Block_Source; break; + case IP_UNBLOCK_SOURCE: mapped = Option::Ip_Unblock_Source; break; + default: + AMS_SDK_LOG("WARNING: Invalid socket option %d for SOL_IP\n", static_cast<int>(option)); + break; + } + break; + case IPPROTO_TCP: + switch (option) { + case TCP_NODELAY: mapped = Option::Tcp_NoDelay; break; + case TCP_MAXSEG: mapped = Option::Tcp_MaxSeg; break; + default: + AMS_SDK_LOG("WARNING: Invalid ams::Socket::Option %d for SOL_TCP\n", static_cast<int>(option)); + break; + } + break; + default: + AMS_SDK_LOG("WARNING: Invalid option level %d\n", static_cast<int>(level)); + break; + } + + return mapped; + } + + s32 MapErrnoValue(Errno error) { + s32 mapped = -1; + + switch (error) { + case Errno::EIntr: mapped = WSAEINTR; break; + case Errno::EBadf: mapped = WSAEBADF; break; + case Errno::EWouldBlock: mapped = WSAEWOULDBLOCK; break; + case Errno::EAcces: mapped = WSAEACCES; break; + case Errno::EFault: mapped = WSAEFAULT; break; + case Errno::EInval: mapped = WSAEINVAL; break; + case Errno::EMFile: mapped = WSAEMFILE; break; + case Errno::EPipe: mapped = 109; break; + case Errno::ENameTooLong: mapped = WSAENAMETOOLONG; break; + case Errno::ENotEmpty: mapped = WSAENOTEMPTY; break; + case Errno::ELoop: mapped = WSAELOOP; break; + case Errno::ERemote: mapped = WSAEREMOTE; break; + case Errno::EUsers: mapped = WSAEUSERS; break; + case Errno::ENotSock: mapped = WSAENOTSOCK; break; + case Errno::EDestAddrReq: mapped = WSAEDESTADDRREQ; break; + case Errno::EMsgSize: mapped = WSAEMSGSIZE; break; + case Errno::EPrototype: mapped = WSAEPROTOTYPE; break; + case Errno::ENoProtoOpt: mapped = WSAENOPROTOOPT; break; + case Errno::EProtoNoSupport: mapped = WSAEPROTONOSUPPORT; break; + case Errno::ESocktNoSupport: mapped = WSAESOCKTNOSUPPORT; break; + case Errno::EOpNotSupp: mapped = WSAEOPNOTSUPP; break; + case Errno::EPfNoSupport: mapped = WSAEPFNOSUPPORT; break; + case Errno::EAfNoSupport: mapped = WSAEAFNOSUPPORT; break; + case Errno::EAddrInUse: mapped = WSAEADDRINUSE; break; + case Errno::EAddrNotAvail: mapped = WSAEADDRNOTAVAIL; break; + case Errno::ENetDown: mapped = WSAENETDOWN; break; + case Errno::ENetUnreach: mapped = WSAENETUNREACH; break; + case Errno::ENetReset: mapped = WSAENETRESET; break; + case Errno::EConnAborted: mapped = WSAECONNABORTED; break; + case Errno::EConnReset: mapped = WSAECONNRESET; break; + case Errno::ENoBufs: mapped = WSAENOBUFS; break; + case Errno::EIsConn: mapped = WSAEISCONN; break; + case Errno::ENotConn: mapped = WSAENOTCONN; break; + case Errno::EShutDown: mapped = WSAESHUTDOWN; break; + case Errno::ETooManyRefs: mapped = WSAETOOMANYREFS; break; + case Errno::ETimedOut: mapped = WSAETIMEDOUT; break; + case Errno::EConnRefused: mapped = WSAECONNREFUSED; break; + case Errno::EHostDown: mapped = WSAEHOSTDOWN; break; + case Errno::EHostUnreach: mapped = WSAEHOSTUNREACH; break; + case Errno::EAlready: mapped = WSAEALREADY; break; + case Errno::EInProgress: mapped = WSAEINPROGRESS; break; + case Errno::EStale: mapped = WSAESTALE; break; + case Errno::EDQuot: mapped = WSAEDQUOT; break; + case Errno::ECanceled: mapped = WSAECANCELLED; break; + case Errno::EProcLim: mapped = WSAEPROCLIM; break; + default: mapped = static_cast<s32>(error); break; + } + + return mapped; + } + + Errno MapErrnoValue(s32 error) { + Errno mapped = Errno::ESuccess; + + switch (error) { + case WSAEBADF: mapped = Errno::EBadf; break; + case WSAEACCES: mapped = Errno::EAcces; break; + case WSAEFAULT: mapped = Errno::EFault; break; + case WSAEINVAL: mapped = Errno::EInval; break; + case WSAEMFILE: mapped = Errno::EMFile; break; + case WSAEWOULDBLOCK: mapped = Errno::EWouldBlock; break; + case WSAEINPROGRESS: mapped = Errno::EInProgress; break; + case WSAEALREADY: mapped = Errno::EAlready; break; + case WSAENOTSOCK: mapped = Errno::ENotSock; break; + case WSAEDESTADDRREQ: mapped = Errno::EDestAddrReq; break; + case WSAEMSGSIZE: mapped = Errno::EMsgSize; break; + case WSAEPROTOTYPE: mapped = Errno::EPrototype; break; + case WSAENOPROTOOPT: mapped = Errno::ENoProtoOpt; break; + case WSAEPROTONOSUPPORT: mapped = Errno::EProtoNoSupport; break; + case WSAESOCKTNOSUPPORT: mapped = Errno::ESocktNoSupport; break; + case WSAEOPNOTSUPP: mapped = Errno::EOpNotSupp; break; + case WSAEPFNOSUPPORT: mapped = Errno::EPfNoSupport; break; + case WSAEAFNOSUPPORT: mapped = Errno::EAfNoSupport; break; + case WSAEADDRINUSE: mapped = Errno::EAddrInUse; break; + case WSAEADDRNOTAVAIL: mapped = Errno::EAddrNotAvail; break; + case WSAENETDOWN: mapped = Errno::ENetDown; break; + case WSAENETUNREACH: mapped = Errno::ENetUnreach; break; + case WSAENETRESET: mapped = Errno::ENetReset; break; + case WSAECONNABORTED: mapped = Errno::EConnAborted; break; + case WSAECONNRESET: mapped = Errno::EConnReset; break; + case WSAENOBUFS: mapped = Errno::ENoBufs; break; + case WSAEISCONN: mapped = Errno::EIsConn; break; + case WSAENOTCONN: mapped = Errno::ENotConn; break; + case WSAESHUTDOWN: mapped = Errno::EShutDown; break; + case WSAETOOMANYREFS: mapped = Errno::ETooManyRefs; break; + case WSAETIMEDOUT: mapped = Errno::ETimedOut; break; + case WSAECONNREFUSED: mapped = Errno::EConnRefused; break; + case WSAELOOP: mapped = Errno::ELoop; break; + case WSAENAMETOOLONG: mapped = Errno::ENameTooLong; break; + case WSAEHOSTDOWN: mapped = Errno::EHostDown; break; + case WSAEHOSTUNREACH: mapped = Errno::EHostUnreach; break; + case WSAENOTEMPTY: mapped = Errno::ENotEmpty; break; + case WSAEPROCLIM: mapped = Errno::EProcLim; break; + case WSAEUSERS: mapped = Errno::EUsers; break; + case WSAEDQUOT: mapped = Errno::EDQuot; break; + case WSAESTALE: mapped = Errno::EStale; break; + case WSAEREMOTE: mapped = Errno::ERemote; break; + case WSAECANCELLED: mapped = Errno::ECanceled; break; + case WSAEINTR: mapped = Errno::EIntr; break; + case 109: mapped = Errno::EPipe; break; + default: mapped = static_cast<Errno>(error); break; + } + + return mapped; + } + + void CopyToPlatform(sockaddr_in *dst, const SockAddrIn *src) { + if (src != nullptr) { + dst->sin_family = MapFamilyValue(src->sin_family); + dst->sin_port = src->sin_port; + std::memcpy(std::addressof(dst->sin_addr), std::addressof(src->sin_addr), sizeof(dst->sin_addr)); + std::memcpy(dst->sin_zero, src->sin_zero, sizeof(dst->sin_zero)); + } + } + + void CopyFromPlatform(SockAddrIn *dst, const sockaddr_in *src) { + if (dst != nullptr) { + dst->sin_family = MapFamilyValue(static_cast<s8>(src->sin_family)); + dst->sin_port = src->sin_port; + std::memcpy(std::addressof(dst->sin_addr), std::addressof(src->sin_addr), sizeof(dst->sin_addr)); + std::memcpy(dst->sin_zero, src->sin_zero, sizeof(dst->sin_zero)); + } + } + + void CopyToPlatform(timeval *dst, const TimeVal *src) { + if (src != nullptr) { + dst->tv_sec = src->tv_sec; + dst->tv_usec = src->tv_usec; + } + } + + void CopyFromPlatform(TimeVal *dst, const timeval *src) { + if (dst != nullptr) { + dst->tv_sec = src->tv_sec; + dst->tv_usec = src->tv_usec; + } + } + + void CopyToPlatform(linger *dst, const Linger *src) { + if (src != nullptr) { + dst->l_onoff = static_cast<u_short>(src->l_onoff); + dst->l_linger = static_cast<u_short>(src->l_linger); + } + } + + void CopyFromPlatform(Linger *dst, const linger *src) { + if (dst != nullptr) { + dst->l_onoff = src->l_onoff; + dst->l_linger = src->l_linger; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/socket/socket_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/socket/socket_api.cpp new file mode 100644 index 00000000..edc10a64 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/socket/socket_api.cpp @@ -0,0 +1,113 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "impl/socket_api.hpp" + +namespace ams::socket { + + Errno GetLastError() { + return impl::GetLastError(); + } + + void SetLastError(Errno err) { + return impl::SetLastError(err); + } + + u32 InetHtonl(u32 host) { + return impl::InetHtonl(host); + } + + u16 InetHtons(u16 host) { + return impl::InetHtons(host); + } + + u32 InetNtohl(u32 net) { + return impl::InetNtohl(net); + } + + u16 InetNtohs(u16 net) { + return impl::InetNtohs(net); + } + + Result Initialize(const Config &config) { + R_RETURN(impl::Initialize(config)); + } + + Result Finalize() { + R_RETURN(impl::Finalize()); + } + + Result InitializeAllocatorForInternal(void *buffer, size_t size) { + R_RETURN(impl::InitializeAllocatorForInternal(buffer, size)); + } + + ssize_t RecvFrom(s32 desc, void *buffer, size_t buffer_size, MsgFlag flags, SockAddr *out_address, SockLenT *out_addr_len){ + return impl::RecvFrom(desc, buffer, buffer_size, flags, out_address, out_addr_len); + } + + ssize_t Recv(s32 desc, void *buffer, size_t buffer_size, MsgFlag flags) { + return impl::Recv(desc, buffer, buffer_size, flags); + } + + ssize_t SendTo(s32 desc, const void *buffer, size_t buffer_size, MsgFlag flags, const SockAddr *address, SockLenT len) { + return impl::SendTo(desc, buffer, buffer_size, flags, address, len); + } + + ssize_t Send(s32 desc, const void *buffer, size_t buffer_size, MsgFlag flags) { + return impl::Send(desc, buffer, buffer_size, flags); + } + + s32 Shutdown(s32 desc, ShutdownMethod how) { + return impl::Shutdown(desc, how); + } + + s32 Socket(Family domain, Type type, Protocol protocol) { + return impl::Socket(domain, type, protocol); + } + + s32 SocketExempt(Family domain, Type type, Protocol protocol) { + return impl::SocketExempt(domain, type, protocol); + } + + s32 Accept(s32 desc, SockAddr *out_address, SockLenT *out_addr_len) { + return impl::Accept(desc, out_address, out_addr_len); + } + + s32 Bind(s32 desc, const SockAddr *address, SockLenT len) { + return impl::Bind(desc, address, len); + } + + s32 Connect(s32 desc, const SockAddr *address, SockLenT len) { + return impl::Connect(desc, address, len); + } + + s32 GetSockName(s32 desc, SockAddr *out_address, SockLenT *out_addr_len) { + return impl::GetSockName(desc, out_address, out_addr_len); + } + + s32 SetSockOpt(s32 desc, Level level, Option option_name, const void *option_value, SockLenT option_size) { + return impl::SetSockOpt(desc, level, option_name, option_value, option_size); + } + + s32 Listen(s32 desc, s32 backlog) { + return impl::Listen(desc, backlog); + } + + s32 Close(s32 desc) { + return impl::Close(desc); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/impl/spl_api_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/impl/spl_api_impl.cpp new file mode 100644 index 00000000..c5c450b1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/impl/spl_api_impl.cpp @@ -0,0 +1,980 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "spl_ctr_drbg.hpp" +#include "spl_device_address_mapper.hpp" +#include "spl_key_slot_cache.hpp" + +namespace ams::hos { + + void InitializeVersionInternal(bool allow_approximate); + +} + +namespace ams::spl::impl { + + namespace { + + /* Drbg type. */ + using Drbg = CtrDrbg<crypto::AesEncryptor128, AesKeySize, false>; + + /* Convenient defines. */ + #if defined(ATMOSPHERE_OS_HORIZON) + constexpr size_t DeviceAddressSpaceAlign = 4_MB; + + constexpr u32 WorkBufferBase = 0x80000000u; + constexpr u32 ComputeAesInMapBase = 0x90000000u; + constexpr u32 ComputeAesOutMapBase = 0xC0000000u; + constexpr size_t ComputeAesSizeMax = static_cast<size_t>(ComputeAesOutMapBase - ComputeAesInMapBase); + #endif + + constexpr size_t DeviceUniqueDataIvSize = 0x10; + constexpr size_t DeviceUniqueDataPaddingSize = 0x08; + constexpr size_t DeviceUniqueDataDeviceIdSize = 0x08; + constexpr size_t DeviceUniqueDataGmacSize = 0x10; + + constexpr size_t DeviceUniqueDataPlainMetaDataSize = DeviceUniqueDataIvSize + DeviceUniqueDataGmacSize; + constexpr size_t DeviceUniqueDataMetaDataSize = DeviceUniqueDataPlainMetaDataSize + DeviceUniqueDataPaddingSize + DeviceUniqueDataDeviceIdSize; + + constexpr size_t Rsa2048BlockSize = 0x100; + constexpr size_t LabelDigestSizeMax = 0x20; + + constexpr size_t WorkBufferSizeMax = 0x800; + + constexpr const KeySource KeyGenerationSource = { + .data = { 0x89, 0x61, 0x5E, 0xE0, 0x5C, 0x31, 0xB6, 0x80, 0x5F, 0xE5, 0x8F, 0x3D, 0xA2, 0x4F, 0x7A, 0xA8 } + }; + + constexpr const KeySource AesKeyDecryptionSource = { + .data = { 0x11, 0x70, 0x24, 0x2B, 0x48, 0x69, 0x11, 0xF1, 0x11, 0xB0, 0x0C, 0x47, 0x7C, 0xC3, 0xEF, 0x7E } + }; + + constexpr s32 PhysicalAesKeySlotCount = 6; + + /* KeySlot management. */ + constinit AesKeySlotCache g_aes_keyslot_cache; + constinit util::optional<AesKeySlotCacheEntry> g_aes_keyslot_cache_entry[PhysicalAesKeySlotCount] = {}; + + constinit bool g_is_physical_keyslot_allowed = false; + constinit bool g_is_modern_device_unique_data = true; + + constexpr inline bool IsVirtualAesKeySlot(s32 keyslot) { + return AesKeySlotMin <= keyslot && keyslot <= AesKeySlotMax; + } + + constexpr inline bool IsPhysicalAesKeySlot(s32 keyslot) { + return keyslot < PhysicalAesKeySlotCount; + } + + constexpr inline s32 GetVirtualAesKeySlotIndex(s32 keyslot) { + AMS_ASSERT(IsVirtualAesKeySlot(keyslot)); + return keyslot - AesKeySlotMin; + } + + constexpr inline s32 MakeVirtualAesKeySlot(s32 index) { + const s32 virt_slot = index + AesKeySlotMin; + AMS_ASSERT(IsVirtualAesKeySlot(virt_slot)); + return virt_slot; + } + + enum class AesKeySlotContentType { + None = 0, + AesKey = 1, + PreparedKey = 2, + }; + + struct AesKeySlotContents { + AesKeySlotContentType type; + union { + struct { + AccessKey access_key; + KeySource key_source; + } aes_key; + struct { + AccessKey access_key; + } prepared_key; + }; + }; + + constinit bool g_is_aes_keyslot_allocated[AesKeySlotCount]; + constinit AesKeySlotContents g_aes_keyslot_contents[AesKeySlotCount] = {}; + constinit AesKeySlotContents g_aes_physical_keyslot_contents_for_backwards_compatibility[PhysicalAesKeySlotCount] = {}; + + void ClearPhysicalAesKeySlot(s32 keyslot) { + AMS_ASSERT(IsPhysicalAesKeySlot(keyslot)); + + AccessKey access_key = {}; + KeySource key_source = {}; + smc::LoadAesKey(keyslot, access_key, key_source); + } + + s32 GetPhysicalAesKeySlot(s32 keyslot, bool load) { + s32 phys_slot = -1; + AesKeySlotContents *contents = nullptr; + + if (g_is_physical_keyslot_allowed && IsPhysicalAesKeySlot(keyslot)) { + /* On 1.0.0, we allow the use of physical keyslots. */ + phys_slot = keyslot; + contents = std::addressof(g_aes_physical_keyslot_contents_for_backwards_compatibility[phys_slot]); + + /* If the physical slot is already loaded, we're good. */ + if (g_aes_keyslot_cache.FindPhysical(phys_slot)) { + return phys_slot; + } + } else { + /* This should be a virtual keyslot. */ + AMS_ASSERT(IsVirtualAesKeySlot(keyslot)); + + /* Try to find a physical slot in the cache. */ + if (g_aes_keyslot_cache.Find(std::addressof(phys_slot), keyslot)) { + return phys_slot; + } + + /* Allocate a physical slot. */ + phys_slot = g_aes_keyslot_cache.Allocate(keyslot); + contents = std::addressof(g_aes_keyslot_contents[GetVirtualAesKeySlotIndex(keyslot)]); + } + + /* Ensure the contents of the keyslot. */ + if (load) { + switch (contents->type) { + case AesKeySlotContentType::None: + ClearPhysicalAesKeySlot(phys_slot); + break; + case AesKeySlotContentType::AesKey: + R_ABORT_UNLESS(smc::ConvertResult(smc::LoadAesKey(phys_slot, contents->aes_key.access_key, contents->aes_key.key_source))); + break; + case AesKeySlotContentType::PreparedKey: + R_ABORT_UNLESS(smc::ConvertResult(smc::LoadPreparedAesKey(phys_slot, contents->prepared_key.access_key))); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + return phys_slot; + } + + /* Type definitions. */ + class ScopedAesKeySlot { + private: + s32 m_slot_index; + bool m_allocated; + public: + ScopedAesKeySlot() : m_slot_index(-1), m_allocated(false) { + /* ... */ + } + ~ScopedAesKeySlot() { + if (m_allocated) { + DeallocateAesKeySlot(m_slot_index); + } + } + + s32 GetIndex() const { + return m_slot_index; + } + + Result Allocate() { + R_TRY(AllocateAesKeySlot(std::addressof(m_slot_index))); + m_allocated = true; + R_SUCCEED(); + } + }; + + struct SeLinkedListEntry { + u32 num_entries; + u32 address; + u32 size; + }; + + struct SeCryptContext { + SeLinkedListEntry in; + SeLinkedListEntry out; + }; + + /* Global variables. */ + alignas(os::MemoryPageSize) constinit u8 g_work_buffer[WorkBufferSizeMax]; + constinit util::TypedStorage<Drbg> g_drbg = {}; + constinit os::InterruptName g_interrupt_name; + constinit os::InterruptEventType g_interrupt = {}; + constinit util::TypedStorage<os::SystemEvent> g_aes_keyslot_available_event = {}; + constinit os::SdkMutex g_operation_lock; + constinit dd::DeviceAddressSpaceType g_device_address_space = {}; + + #if defined(ATMOSPHERE_OS_HORIZON) + constinit u32 g_work_buffer_mapped_address; + #else + constinit uintptr_t g_work_buffer_mapped_address; + #endif + + constinit BootReasonValue g_boot_reason; + constinit bool g_is_boot_reason_initialized; + + /* Initialization functionality. */ + void InitializeAsyncOperation() { + #if defined(ATMOSPHERE_OS_HORIZON) + u64 interrupt_number; + impl::GetConfig(std::addressof(interrupt_number), ConfigItem::SecurityEngineInterruptNumber); + g_interrupt_name = static_cast<os::InterruptName>(interrupt_number); + + os::InitializeInterruptEvent(std::addressof(g_interrupt), g_interrupt_name, os::EventClearMode_AutoClear); + #else + AMS_UNUSED(g_interrupt_name, g_interrupt); + #endif + } + + void InitializeDeviceAddressSpace() { + #if defined(ATMOSPHERE_OS_HORIZON) + /* Create device address space. */ + R_ABORT_UNLESS(dd::CreateDeviceAddressSpace(std::addressof(g_device_address_space), 0, (1ul << 32))); + + /* Attach to the security engine. */ + R_ABORT_UNLESS(dd::AttachDeviceAddressSpace(std::addressof(g_device_address_space), dd::DeviceName_Se)); + + /* Map work buffer into the device. */ + const uintptr_t work_buffer_address = reinterpret_cast<uintptr_t>(g_work_buffer); + g_work_buffer_mapped_address = WorkBufferBase + (work_buffer_address % DeviceAddressSpaceAlign); + + R_ABORT_UNLESS(dd::MapDeviceAddressSpaceAligned(std::addressof(g_device_address_space), dd::GetCurrentProcessHandle(), work_buffer_address, dd::DeviceAddressSpaceMemoryRegionAlignment, g_work_buffer_mapped_address, dd::MemoryPermission_ReadWrite)); + #else + /* Just set the work buffer address directly. */ + AMS_UNUSED(g_device_address_space); + g_work_buffer_mapped_address = reinterpret_cast<uintptr_t>(g_work_buffer); + #endif + } + + void InitializeCtrDrbg() { + u8 seed[Drbg::SeedSize]; + AMS_ABORT_UNLESS(smc::GenerateRandomBytes(seed, sizeof(seed)) == smc::Result::Success); + + util::ConstructAt(g_drbg); + util::GetReference(g_drbg).Initialize(seed, sizeof(seed), nullptr, 0, nullptr, 0); + } + + void InitializeKeySlots() { + const auto fw_ver = hos::GetVersion(); + g_is_physical_keyslot_allowed = fw_ver < hos::Version_2_0_0; + g_is_modern_device_unique_data = fw_ver >= hos::Version_5_0_0; + + for (s32 i = 0; i < PhysicalAesKeySlotCount; i++) { + g_aes_keyslot_cache_entry[i].emplace(i); + g_aes_keyslot_cache.AddEntry(std::addressof(g_aes_keyslot_cache_entry[i].value())); + } + + util::ConstructAt(g_aes_keyslot_available_event, os::EventClearMode_ManualClear, true); + util::GetReference(g_aes_keyslot_available_event).Signal(); + } + + void WaitOperation() { + #if defined(ATMOSPHERE_OS_HORIZON) + os::WaitInterruptEvent(std::addressof(g_interrupt)); + #endif + } + + smc::Result WaitAndGetResult(smc::AsyncOperationKey op_key) { + WaitOperation(); + + smc::Result async_res; + if (const smc::Result res = smc::GetResult(std::addressof(async_res), op_key); res != smc::Result::Success) { + return res; + } + + return async_res; + } + + smc::Result WaitAndGetResultData(void *dst, size_t size, smc::AsyncOperationKey op_key) { + WaitOperation(); + + smc::Result async_res; + if (const smc::Result res = smc::GetResultData(std::addressof(async_res), dst, size, op_key); res != smc::Result::Success) { + return res; + } + + return async_res; + } + + smc::Result DecryptAes(void *dst, s32 keyslot, const void *src) { + struct DecryptAesLayout { + SeCryptContext crypt_ctx; + u8 padding[8]; + u8 in_buffer[crypto::AesEncryptor128::BlockSize]; + u8 out_buffer[crypto::AesEncryptor128::BlockSize]; + }; + + #if defined(ATMOSPHERE_OS_HORIZON) + auto &layout = *reinterpret_cast<DecryptAesLayout *>(g_work_buffer); + + layout.crypt_ctx.in.num_entries = 0; + layout.crypt_ctx.in.address = g_work_buffer_mapped_address + AMS_OFFSETOF(DecryptAesLayout, in_buffer); + layout.crypt_ctx.in.size = sizeof(layout.in_buffer); + layout.crypt_ctx.out.num_entries = 0; + layout.crypt_ctx.out.address = g_work_buffer_mapped_address + AMS_OFFSETOF(DecryptAesLayout, out_buffer); + layout.crypt_ctx.out.size = sizeof(layout.out_buffer); + + std::memcpy(layout.in_buffer, src, sizeof(layout.in_buffer)); + + os::FlushDataCache(std::addressof(layout), sizeof(layout)); + { + std::scoped_lock lk(g_operation_lock); + + smc::AsyncOperationKey op_key; + const IvCtr iv_ctr = {}; + const u32 mode = smc::GetComputeAesMode(smc::CipherMode::CbcDecrypt, GetPhysicalAesKeySlot(keyslot, true)); + const u32 dst_ll_addr = g_work_buffer_mapped_address + AMS_OFFSETOF(DecryptAesLayout, crypt_ctx.out); + const u32 src_ll_addr = g_work_buffer_mapped_address + AMS_OFFSETOF(DecryptAesLayout, crypt_ctx.in); + + smc::Result res = smc::ComputeAes(std::addressof(op_key), dst_ll_addr, mode, iv_ctr, src_ll_addr, sizeof(layout.out_buffer)); + if (res != smc::Result::Success) { + return res; + } + + res = WaitAndGetResult(op_key); + if (res != smc::Result::Success) { + return res; + } + } + os::FlushDataCache(std::addressof(layout.out_buffer), sizeof(layout.out_buffer)); + std::memcpy(dst, layout.out_buffer, sizeof(layout.out_buffer)); + #else + { + /* Set up buffers. */ + u8 in_buffer[crypto::AesEncryptor128::BlockSize]; + u8 out_buffer[crypto::AesEncryptor128::BlockSize]; + std::memcpy(in_buffer, src, sizeof(in_buffer)); + + std::scoped_lock lk(g_operation_lock); + + /* On generic os, we don't worry about the security engine. */ + smc::AsyncOperationKey op_key; + const IvCtr iv_ctr = {}; + const u32 mode = smc::GetComputeAesMode(smc::CipherMode::CbcDecrypt, GetPhysicalAesKeySlot(keyslot, true)); + smc::Result res = smc::ComputeAes(std::addressof(op_key), reinterpret_cast<uintptr_t>(out_buffer), mode, iv_ctr, reinterpret_cast<uintptr_t>(in_buffer), sizeof(in_buffer)); + if (res != smc::Result::Success) { + return res; + } + + res = WaitAndGetResult(op_key); + if (res != smc::Result::Success) { + return res; + } + + std::memcpy(dst, out_buffer, sizeof(out_buffer)); + } + #endif + + return smc::Result::Success; + } + + Result GenerateRandomBytesImpl(void *out, size_t size) { + AMS_ASSERT(size <= Drbg::RequestSizeMax); + + if (!util::GetReference(g_drbg).Generate(out, size, nullptr, 0)) { + /* We need to reseed. */ + { + u8 seed[Drbg::SeedSize]; + + if (smc::Result res = smc::GenerateRandomBytes(seed, sizeof(seed)); res != smc::Result::Success) { + R_RETURN(smc::ConvertResult(res)); + } + + util::GetReference(g_drbg).Reseed(seed, sizeof(seed), nullptr, 0); + } + util::GetReference(g_drbg).Generate(out, size, nullptr, 0); + } + + R_SUCCEED(); + } + + Result DecryptAndStoreDeviceUniqueKey(const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source, u32 option) { + struct DecryptAndStoreDeviceUniqueKeyLayout { + u8 data[DeviceUniqueDataMetaDataSize + 2 * Rsa2048BlockSize + 0x10]; + }; + auto &layout = *reinterpret_cast<DecryptAndStoreDeviceUniqueKeyLayout *>(g_work_buffer); + + /* Validate size. */ + R_UNLESS(src_size <= sizeof(DecryptAndStoreDeviceUniqueKeyLayout), spl::ResultInvalidBufferSize()); + + std::memcpy(layout.data, src, src_size); + + if (g_is_modern_device_unique_data) { + R_RETURN(smc::ConvertResult(smc::DecryptDeviceUniqueData(layout.data, src_size, access_key, key_source, static_cast<smc::DeviceUniqueDataMode>(option)))); + } else { + R_RETURN(smc::ConvertResult(smc::DecryptAndStoreGcKey(layout.data, src_size, access_key, key_source, option))); + } + } + + Result ModularExponentiateWithStorageKey(void *out, size_t out_size, const void *base, size_t base_size, const void *mod, size_t mod_size, smc::ModularExponentiateWithStorageKeyMode mode) { + struct ModularExponentiateWithStorageKeyLayout { + u8 base[Rsa2048BlockSize]; + u8 mod[Rsa2048BlockSize]; + }; + auto &layout = *reinterpret_cast<ModularExponentiateWithStorageKeyLayout *>(g_work_buffer); + + /* Validate sizes. */ + R_UNLESS(base_size <= sizeof(layout.base), spl::ResultInvalidBufferSize()); + R_UNLESS(mod_size <= sizeof(layout.mod), spl::ResultInvalidBufferSize()); + R_UNLESS(out_size <= sizeof(g_work_buffer), spl::ResultInvalidBufferSize()); + + /* Copy data into work buffer. */ + const size_t base_ofs = sizeof(layout.base) - base_size; + const size_t mod_ofs = sizeof(layout.mod) - mod_size; + + std::memset(layout.base, 0, sizeof(layout.base)); + std::memset(layout.mod, 0, sizeof(layout.mod)); + + std::memcpy(layout.base + base_ofs, base, base_size); + std::memcpy(layout.mod + mod_ofs, mod, mod_size); + + /* Do exp mod operation. */ + { + std::scoped_lock lk(g_operation_lock); + + smc::AsyncOperationKey op_key; + smc::Result res = smc::ModularExponentiateWithStorageKey(std::addressof(op_key), layout.base, layout.mod, mode); + if (res != smc::Result::Success) { + R_RETURN(smc::ConvertResult(res)); + } + + res = WaitAndGetResultData(g_work_buffer, out_size, op_key); + if (res != smc::Result::Success) { + R_RETURN(smc::ConvertResult(res)); + } + } + + /* Copy result. */ + if (out != g_work_buffer) { + std::memcpy(out, g_work_buffer, out_size); + } + + R_SUCCEED(); + } + + Result PrepareEsDeviceUniqueKey(AccessKey *out_access_key, const void *base, size_t base_size, const void *mod, size_t mod_size, const void *label_digest, size_t label_digest_size, smc::EsDeviceUniqueKeyType type, u32 generation) { + struct PrepareEsDeviceUniqueKeyLayout { + u8 base[Rsa2048BlockSize]; + u8 mod[Rsa2048BlockSize]; + }; + auto &layout = *reinterpret_cast<PrepareEsDeviceUniqueKeyLayout *>(g_work_buffer); + + /* Validate sizes. */ + R_UNLESS(base_size <= sizeof(layout.base), spl::ResultInvalidBufferSize()); + R_UNLESS(mod_size <= sizeof(layout.mod), spl::ResultInvalidBufferSize()); + R_UNLESS(label_digest_size <= LabelDigestSizeMax, spl::ResultInvalidBufferSize()); + + /* Copy data into work buffer. */ + const size_t base_ofs = sizeof(layout.base) - base_size; + const size_t mod_ofs = sizeof(layout.mod) - mod_size; + + std::memset(layout.base, 0, sizeof(layout.base)); + std::memset(layout.mod, 0, sizeof(layout.mod)); + + std::memcpy(layout.base + base_ofs, base, base_size); + std::memcpy(layout.mod + mod_ofs, mod, mod_size); + + /* Do exp mod operation. */ + { + std::scoped_lock lk(g_operation_lock); + + smc::AsyncOperationKey op_key; + smc::Result res = smc::PrepareEsDeviceUniqueKey(std::addressof(op_key), layout.base, layout.mod, label_digest, label_digest_size, smc::GetPrepareEsDeviceUniqueKeyOption(type, generation)); + if (res != smc::Result::Success) { + R_RETURN(smc::ConvertResult(res)); + } + + res = WaitAndGetResultData(g_work_buffer, sizeof(*out_access_key), op_key); + if (res != smc::Result::Success) { + R_RETURN(smc::ConvertResult(res)); + } + } + + std::memcpy(out_access_key, g_work_buffer, sizeof(*out_access_key)); + R_SUCCEED(); + } + + } + + /* Initialization. */ + void Initialize() { + /* Initialize async operation. */ + InitializeAsyncOperation(); + + /* Initialize device address space for the SE. */ + InitializeDeviceAddressSpace(); + + /* Initialize the Drbg. */ + InitializeCtrDrbg(); + + /* Initialize the keyslot cache. */ + InitializeKeySlots(); + } + + /* General. */ + Result GetConfig(u64 *out, ConfigItem key) { + /* Nintendo explicitly blacklists package2 hash, which must be gotten via bespoke api. */ + R_UNLESS(key != ConfigItem::Package2Hash, spl::ResultInvalidArgument()); + + smc::Result res = smc::GetConfig(out, 1, key); + + /* Nintendo has some special handling here for hardware type/hardware state. */ + if (key == ConfigItem::HardwareType && res == smc::Result::InvalidArgument) { + *out = static_cast<u64>(HardwareType::Icosa); + res = smc::Result::Success; + } + if (key == ConfigItem::HardwareState && res == smc::Result::InvalidArgument) { + *out = HardwareState_Development; + res = smc::Result::Success; + } + + R_RETURN(smc::ConvertResult(res)); + } + + Result ModularExponentiate(void *out, size_t out_size, const void *base, size_t base_size, const void *exp, size_t exp_size, const void *mod, size_t mod_size) { + struct ModularExponentiateLayout { + u8 base[Rsa2048BlockSize]; + u8 exp[Rsa2048BlockSize]; + u8 mod[Rsa2048BlockSize]; + }; + auto &layout = *reinterpret_cast<ModularExponentiateLayout *>(g_work_buffer); + + /* Validate sizes. */ + R_UNLESS(base_size <= sizeof(layout.base), spl::ResultInvalidBufferSize()); + R_UNLESS(exp_size <= sizeof(layout.exp), spl::ResultInvalidBufferSize()); + R_UNLESS(mod_size <= sizeof(layout.mod), spl::ResultInvalidBufferSize()); + R_UNLESS(out_size <= sizeof(g_work_buffer), spl::ResultInvalidBufferSize()); + + /* Copy data into work buffer. */ + const size_t base_ofs = sizeof(layout.base) - base_size; + const size_t mod_ofs = sizeof(layout.mod) - mod_size; + + std::memset(layout.base, 0, sizeof(layout.base)); + std::memset(layout.mod, 0, sizeof(layout.mod)); + + std::memcpy(layout.base + base_ofs, base, base_size); + std::memcpy(layout.mod + mod_ofs, mod, mod_size); + + std::memcpy(layout.exp, exp, exp_size); + + /* Do exp mod operation. */ + { + std::scoped_lock lk(g_operation_lock); + + smc::AsyncOperationKey op_key; + smc::Result res = smc::ModularExponentiate(std::addressof(op_key), layout.base, layout.exp, exp_size, layout.mod); + if (res != smc::Result::Success) { + R_RETURN(smc::ConvertResult(res)); + } + + res = WaitAndGetResultData(g_work_buffer, out_size, op_key); + if (res != smc::Result::Success) { + R_RETURN(smc::ConvertResult(res)); + } + } + + std::memcpy(out, g_work_buffer, out_size); + R_SUCCEED(); + } + + Result SetConfig(ConfigItem key, u64 value) { + R_TRY(smc::ConvertResult(smc::SetConfig(key, value))); + + /* Work around for temporary version. */ + if (key == ConfigItem::ExosphereApiVersion) { + hos::InitializeVersionInternal(false); + } + R_SUCCEED(); + } + + Result GenerateRandomBytes(void *out, size_t size) { + for (size_t offset = 0; offset < size; offset += Drbg::RequestSizeMax) { + R_TRY(GenerateRandomBytesImpl(static_cast<u8 *>(out) + offset, std::min(size - offset, Drbg::RequestSizeMax))); + } + + R_SUCCEED(); + } + + Result IsDevelopment(bool *out) { + u64 hardware_state; + R_TRY(impl::GetConfig(std::addressof(hardware_state), ConfigItem::HardwareState)); + + *out = (hardware_state == HardwareState_Development); + R_SUCCEED(); + } + + Result SetBootReason(BootReasonValue boot_reason) { + R_UNLESS(!g_is_boot_reason_initialized, spl::ResultBootReasonAlreadyInitialized()); + + g_boot_reason = boot_reason; + g_is_boot_reason_initialized = true; + R_SUCCEED(); + } + + Result GetBootReason(BootReasonValue *out) { + R_UNLESS(g_is_boot_reason_initialized, spl::ResultBootReasonNotInitialized()); + + *out = g_boot_reason; + R_SUCCEED(); + } + + /* Crypto. */ + Result GenerateAesKek(AccessKey *out_access_key, const KeySource &key_source, u32 generation, u32 option) { + R_RETURN(smc::ConvertResult(smc::GenerateAesKek(out_access_key, key_source, generation, option))); + } + + Result LoadAesKey(s32 keyslot, const AccessKey &access_key, const KeySource &key_source) { + /* Ensure we can load into the slot. */ + const s32 phys_slot = GetPhysicalAesKeySlot(keyslot, false); + R_TRY(smc::ConvertResult(smc::LoadAesKey(phys_slot, access_key, key_source))); + + /* Update our contents. */ + const s32 index = GetVirtualAesKeySlotIndex(keyslot); + + g_aes_keyslot_contents[index].type = AesKeySlotContentType::AesKey; + g_aes_keyslot_contents[index].aes_key.access_key = access_key; + g_aes_keyslot_contents[index].aes_key.key_source = key_source; + + R_SUCCEED(); + } + + Result GenerateAesKey(AesKey *out_key, const AccessKey &access_key, const KeySource &key_source) { + ScopedAesKeySlot keyslot_holder; + R_TRY(keyslot_holder.Allocate()); + + R_TRY(LoadAesKey(keyslot_holder.GetIndex(), access_key, KeyGenerationSource)); + + R_RETURN(smc::ConvertResult(DecryptAes(out_key, keyslot_holder.GetIndex(), std::addressof(key_source)))); + } + + Result DecryptAesKey(AesKey *out_key, const KeySource &key_source, u32 generation, u32 option) { + AccessKey access_key; + R_TRY(GenerateAesKek(std::addressof(access_key), AesKeyDecryptionSource, generation, option)); + + R_RETURN(GenerateAesKey(out_key, access_key, key_source)); + } + + Result ComputeCtr(void *dst, size_t dst_size, s32 keyslot, const void *src, size_t src_size, const IvCtr &iv_ctr) { + /* Succeed immediately if there's nothing to compute. */ + R_SUCCEED_IF(src_size == 0); + + /* Validate sizes. */ + R_UNLESS(src_size <= dst_size, spl::ResultInvalidBufferSize()); + R_UNLESS(util::IsAligned(src_size, AesBlockSize), spl::ResultInvalidBufferSize()); + + #if defined(ATMOSPHERE_OS_HORIZON) + /* We can only map 4_MB aligned buffers for the SE, so determine where to map our buffers. */ + const uintptr_t src_addr = reinterpret_cast<uintptr_t>(src); + const uintptr_t dst_addr = reinterpret_cast<uintptr_t>(dst); + + const uintptr_t src_addr_aligned = util::AlignDown(src_addr, dd::DeviceAddressSpaceMemoryRegionAlignment); + const uintptr_t dst_addr_aligned = util::AlignDown(dst_addr, dd::DeviceAddressSpaceMemoryRegionAlignment); + + const size_t src_size_aligned = util::AlignUp(src_addr + src_size, dd::DeviceAddressSpaceMemoryRegionAlignment) - src_addr_aligned; + const size_t dst_size_aligned = util::AlignUp(dst_addr + dst_size, dd::DeviceAddressSpaceMemoryRegionAlignment) - dst_addr_aligned; + + const u32 src_se_map_addr = ComputeAesInMapBase + (src_addr_aligned % DeviceAddressSpaceAlign); + const u32 dst_se_map_addr = ComputeAesOutMapBase + (dst_addr_aligned % DeviceAddressSpaceAlign); + + const u32 src_se_addr = ComputeAesInMapBase + (src_addr % DeviceAddressSpaceAlign); + const u32 dst_se_addr = ComputeAesOutMapBase + (dst_addr % DeviceAddressSpaceAlign); + + /* Validate aligned sizes. */ + R_UNLESS(src_size_aligned <= ComputeAesSizeMax, spl::ResultInvalidBufferSize()); + R_UNLESS(dst_size_aligned <= ComputeAesSizeMax, spl::ResultInvalidBufferSize()); + + /* Helpers for mapping/unmapping. */ + DeviceAddressMapper src_mapper(std::addressof(g_device_address_space), src_addr_aligned, src_size_aligned, src_se_map_addr, dd::MemoryPermission_ReadOnly); + DeviceAddressMapper dst_mapper(std::addressof(g_device_address_space), dst_addr_aligned, dst_size_aligned, dst_se_map_addr, dd::MemoryPermission_WriteOnly); + + /* Setup SE linked list entries. */ + auto &crypt_ctx = *reinterpret_cast<SeCryptContext *>(g_work_buffer); + crypt_ctx.in.num_entries = 0; + crypt_ctx.in.address = src_se_addr; + crypt_ctx.in.size = src_size; + crypt_ctx.out.num_entries = 0; + crypt_ctx.out.address = dst_se_addr; + crypt_ctx.out.size = dst_size; + + os::FlushDataCache(std::addressof(crypt_ctx), sizeof(crypt_ctx)); + os::FlushDataCache(src, src_size); + os::FlushDataCache(dst, dst_size); + { + std::scoped_lock lk(g_operation_lock); + + const u32 mode = smc::GetComputeAesMode(smc::CipherMode::Ctr, GetPhysicalAesKeySlot(keyslot, true)); + const u32 dst_ll_addr = g_work_buffer_mapped_address + AMS_OFFSETOF(SeCryptContext, out); + const u32 src_ll_addr = g_work_buffer_mapped_address + AMS_OFFSETOF(SeCryptContext, in); + + smc::AsyncOperationKey op_key; + smc::Result res = smc::ComputeAes(std::addressof(op_key), dst_ll_addr, mode, iv_ctr, src_ll_addr, src_size); + if (res != smc::Result::Success) { + R_RETURN(smc::ConvertResult(res)); + } + + res = WaitAndGetResult(op_key); + if (res != smc::Result::Success) { + R_RETURN(smc::ConvertResult(res)); + } + } + os::FlushDataCache(dst, dst_size); + #else + { + std::scoped_lock lk(g_operation_lock); + + const u32 mode = smc::GetComputeAesMode(smc::CipherMode::Ctr, GetPhysicalAesKeySlot(keyslot, true)); + + /* On generic os, we don't worry about the security engine. */ + smc::AsyncOperationKey op_key; + smc::Result res = smc::ComputeAes(std::addressof(op_key), reinterpret_cast<uintptr_t>(dst), mode, iv_ctr, reinterpret_cast<uintptr_t>(src), src_size); + if (res != smc::Result::Success) { + R_RETURN(smc::ConvertResult(res)); + } + + res = WaitAndGetResult(op_key); + if (res != smc::Result::Success) { + R_RETURN(smc::ConvertResult(res)); + } + } + #endif + + R_SUCCEED(); + } + + Result ComputeCmac(Cmac *out_cmac, s32 keyslot, const void *data, size_t size) { + R_UNLESS(size <= sizeof(g_work_buffer), spl::ResultInvalidBufferSize()); + + std::memcpy(g_work_buffer, data, size); + R_RETURN(smc::ConvertResult(smc::ComputeCmac(out_cmac, GetPhysicalAesKeySlot(keyslot, true), g_work_buffer, size))); + } + + Result AllocateAesKeySlot(s32 *out_keyslot) { + /* Find an unused keyslot. */ + for (s32 i = 0; i < AesKeySlotCount; ++i) { + if (!g_is_aes_keyslot_allocated[i]) { + g_is_aes_keyslot_allocated[i] = true; + g_aes_keyslot_contents[i].type = AesKeySlotContentType::None; + *out_keyslot = MakeVirtualAesKeySlot(i); + R_SUCCEED(); + } + } + + util::GetReference(g_aes_keyslot_available_event).Clear(); + R_THROW(spl::ResultNoAvailableKeySlot()); + } + + Result DeallocateAesKeySlot(s32 keyslot) { + /* Only virtual keyslots can be freed. */ + R_UNLESS(IsVirtualAesKeySlot(keyslot), spl::ResultInvalidKeySlot()); + + /* Check that the virtual keyslot is allocated. */ + const s32 index = GetVirtualAesKeySlotIndex(keyslot); + R_UNLESS(g_is_aes_keyslot_allocated[index], spl::ResultInvalidKeySlot()); + + /* Clear the physical keyslot, if we're cached. */ + s32 phys_slot; + if (g_aes_keyslot_cache.Release(std::addressof(phys_slot), keyslot)) { + ClearPhysicalAesKeySlot(phys_slot); + } + + /* Clear the virtual keyslot. */ + g_aes_keyslot_contents[index].type = AesKeySlotContentType::None; + g_is_aes_keyslot_allocated[index] = false; + + util::GetReference(g_aes_keyslot_available_event).Signal(); + R_SUCCEED(); + } + + Result TestAesKeySlot(s32 *out_index, bool *out_virtual, s32 keyslot) { + if (g_is_physical_keyslot_allowed && IsPhysicalAesKeySlot(keyslot)) { + *out_index = keyslot; + *out_virtual = false; + R_SUCCEED(); + } + + R_UNLESS(IsVirtualAesKeySlot(keyslot), spl::ResultInvalidKeySlot()); + + const s32 index = GetVirtualAesKeySlotIndex(keyslot); + R_UNLESS(g_is_aes_keyslot_allocated[index], spl::ResultInvalidKeySlot()); + + *out_index = index; + *out_virtual = true; + R_SUCCEED(); + } + + os::SystemEvent *GetAesKeySlotAvailableEvent() { + return util::GetPointer(g_aes_keyslot_available_event); + } + + /* RSA. */ + Result DecryptDeviceUniqueData(void *dst, size_t dst_size, const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source, u32 option) { + struct DecryptDeviceUniqueDataLayout { + u8 data[Rsa2048BlockSize + DeviceUniqueDataMetaDataSize]; + }; + auto &layout = *reinterpret_cast<DecryptDeviceUniqueDataLayout *>(g_work_buffer); + + /* Validate size. */ + R_UNLESS(src_size >= DeviceUniqueDataMetaDataSize, spl::ResultInvalidBufferSize()); + R_UNLESS(src_size <= sizeof(DecryptDeviceUniqueDataLayout), spl::ResultInvalidBufferSize()); + + std::memcpy(layout.data, src, src_size); + + smc::Result smc_res; + size_t copy_size = 0; + if (g_is_modern_device_unique_data) { + copy_size = std::min(dst_size, src_size - DeviceUniqueDataMetaDataSize); + smc_res = smc::DecryptDeviceUniqueData(layout.data, src_size, access_key, key_source, static_cast<smc::DeviceUniqueDataMode>(option)); + } else { + smc_res = smc::DecryptDeviceUniqueData(std::addressof(copy_size), layout.data, src_size, access_key, key_source, option); + copy_size = std::min(dst_size, copy_size); + } + + if (smc_res == smc::Result::Success) { + std::memcpy(dst, layout.data, copy_size); + } + + R_RETURN(smc::ConvertResult(smc_res)); + } + + /* SSL */ + Result DecryptAndStoreSslClientCertKey(const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source) { + R_RETURN(DecryptAndStoreDeviceUniqueKey(src, src_size, access_key, key_source, static_cast<u32>(smc::DeviceUniqueDataMode::DecryptAndStoreSslKey))); + } + + Result ModularExponentiateWithSslClientCertKey(void *out, size_t out_size, const void *base, size_t base_size, const void *mod, size_t mod_size) { + R_RETURN(ModularExponentiateWithStorageKey(out, out_size, base, base_size, mod, mod_size, smc::ModularExponentiateWithStorageKeyMode::Ssl)); + } + + /* ES */ + Result LoadEsDeviceKey(const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source, u32 option) { + if (g_is_modern_device_unique_data) { + R_RETURN(DecryptAndStoreDeviceUniqueKey(src, src_size, access_key, key_source, option)); + } else { + struct LoadEsDeviceKeyLayout { + u8 data[DeviceUniqueDataMetaDataSize + 2 * Rsa2048BlockSize + 0x10]; + }; + auto &layout = *reinterpret_cast<LoadEsDeviceKeyLayout *>(g_work_buffer); + + /* Validate size. */ + R_UNLESS(src_size <= sizeof(layout.data), spl::ResultInvalidBufferSize()); + + std::memcpy(layout.data, src, src_size); + + R_RETURN(smc::ConvertResult(smc::LoadEsDeviceKey(layout.data, src_size, access_key, key_source, option))); + } + } + + Result PrepareEsTitleKey(AccessKey *out_access_key, const void *base, size_t base_size, const void *mod, size_t mod_size, const void *label_digest, size_t label_digest_size, u32 generation) { + R_RETURN(PrepareEsDeviceUniqueKey(out_access_key, base, base_size, mod, mod_size, label_digest, label_digest_size, smc::EsDeviceUniqueKeyType::TitleKey, generation)); + } + + Result PrepareCommonEsTitleKey(AccessKey *out_access_key, const KeySource &key_source, u32 generation) { + R_RETURN(smc::ConvertResult(smc::PrepareCommonEsTitleKey(out_access_key, key_source, generation))); + } + + Result DecryptAndStoreDrmDeviceCertKey(const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source) { + R_RETURN(DecryptAndStoreDeviceUniqueKey(src, src_size, access_key, key_source, static_cast<u32>(smc::DeviceUniqueDataMode::DecryptAndStoreDrmDeviceCertKey))); + } + + Result ModularExponentiateWithDrmDeviceCertKey(void *out, size_t out_size, const void *base, size_t base_size, const void *mod, size_t mod_size) { + R_RETURN(ModularExponentiateWithStorageKey(out, out_size, base, base_size, mod, mod_size, smc::ModularExponentiateWithStorageKeyMode::DrmDeviceCert)); + } + + Result PrepareEsArchiveKey(AccessKey *out_access_key, const void *base, size_t base_size, const void *mod, size_t mod_size, const void *label_digest, size_t label_digest_size, u32 generation) { + R_RETURN(PrepareEsDeviceUniqueKey(out_access_key, base, base_size, mod, mod_size, label_digest, label_digest_size, smc::EsDeviceUniqueKeyType::ArchiveKey, generation)); + } + + Result PrepareEsUnknown2Key(AccessKey *out_access_key, const void *base, size_t base_size, const void *mod, size_t mod_size, const void *label_digest, size_t label_digest_size, u32 generation) { + R_RETURN(PrepareEsDeviceUniqueKey(out_access_key, base, base_size, mod, mod_size, label_digest, label_digest_size, smc::EsDeviceUniqueKeyType::Unknown2, generation)); + } + + /* FS */ + Result DecryptAndStoreGcKey(const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source, u32 option) { + R_RETURN(DecryptAndStoreDeviceUniqueKey(src, src_size, access_key, key_source, option)); + } + + Result DecryptGcMessage(u32 *out_size, void *dst, size_t dst_size, const void *base, size_t base_size, const void *mod, size_t mod_size, const void *label_digest, size_t label_digest_size) { + /* Validate sizes. */ + R_UNLESS(dst_size <= sizeof(g_work_buffer), spl::ResultInvalidBufferSize()); + R_UNLESS(label_digest_size == LabelDigestSizeMax, spl::ResultInvalidBufferSize()); + + /* Nintendo doesn't check this result code, but we will. */ + R_TRY(ModularExponentiateWithStorageKey(g_work_buffer, Rsa2048BlockSize, base, base_size, mod, mod_size, smc::ModularExponentiateWithStorageKeyMode::Gc)); + + const auto data_size = crypto::DecodeRsa2048OaepSha256(dst, dst_size, label_digest, label_digest_size, g_work_buffer, Rsa2048BlockSize); + R_UNLESS(data_size > 0, spl::ResultDecryptionFailed()); + + *out_size = static_cast<u32>(data_size); + R_SUCCEED(); + } + + Result GenerateSpecificAesKey(AesKey *out_key, const KeySource &key_source, u32 generation, u32 which) { + R_RETURN(smc::ConvertResult(smc::GenerateSpecificAesKey(out_key, key_source, generation, which))); + } + + Result LoadPreparedAesKey(s32 keyslot, const AccessKey &access_key) { + /* Ensure we can load into the slot. */ + const s32 phys_slot = GetPhysicalAesKeySlot(keyslot, false); + R_TRY(smc::ConvertResult(smc::LoadPreparedAesKey(phys_slot, access_key))); + + /* Update our contents. */ + const s32 index = GetVirtualAesKeySlotIndex(keyslot); + + g_aes_keyslot_contents[index].type = AesKeySlotContentType::PreparedKey; + g_aes_keyslot_contents[index].prepared_key.access_key = access_key; + + R_SUCCEED(); + } + + Result GetPackage2Hash(void *dst, const size_t size) { + u64 hash[4]; + R_UNLESS(size >= sizeof(hash), spl::ResultInvalidBufferSize()); + + const smc::Result smc_res = smc::GetConfig(hash, 4, ConfigItem::Package2Hash); + if (smc_res != smc::Result::Success) { + R_RETURN(smc::ConvertResult(smc_res)); + } + + std::memcpy(dst, hash, sizeof(hash)); + R_SUCCEED(); + } + + /* Manu. */ + Result ReencryptDeviceUniqueData(void *dst, size_t dst_size, const void *src, size_t src_size, const AccessKey &access_key_dec, const KeySource &source_dec, const AccessKey &access_key_enc, const KeySource &source_enc, u32 option) { + struct ReencryptDeviceUniqueDataLayout { + u8 data[DeviceUniqueDataMetaDataSize + 2 * Rsa2048BlockSize + 0x10]; + AccessKey access_key_dec; + KeySource source_dec; + AccessKey access_key_enc; + KeySource source_enc; + }; + auto &layout = *reinterpret_cast<ReencryptDeviceUniqueDataLayout *>(g_work_buffer); + + /* Validate size. */ + R_UNLESS(src_size > DeviceUniqueDataMetaDataSize, spl::ResultInvalidBufferSize()); + R_UNLESS(src_size <= sizeof(layout.data), spl::ResultInvalidBufferSize()); + + std::memcpy(layout.data, src, src_size); + layout.access_key_dec = access_key_dec; + layout.source_dec = source_dec; + layout.access_key_enc = access_key_enc; + layout.source_enc = source_enc; + + const smc::Result smc_res = smc::ReencryptDeviceUniqueData(layout.data, src_size, layout.access_key_dec, layout.source_dec, layout.access_key_enc, layout.source_enc, option); + if (smc_res == smc::Result::Success) { + std::memcpy(dst, layout.data, std::min(dst_size, src_size)); + } + + R_RETURN(smc::ConvertResult(smc_res)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/impl/spl_ctr_drbg.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/impl/spl_ctr_drbg.hpp new file mode 100644 index 00000000..b615f02c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/impl/spl_ctr_drbg.hpp @@ -0,0 +1,277 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::spl::impl { + + constexpr inline int BitsPerByte = BITSIZEOF(u8); + + /* Nintendo implements CTR_DRBG for their csrng. We will do the same. */ + template<typename BlockCipher, size_t KeySize, bool UseDerivation> + class CtrDrbg { + public: + static constexpr int KeyLen = KeySize * BitsPerByte; + static constexpr int OutLen = BlockCipher::BlockSize * BitsPerByte; + static constexpr int SeedLen = KeyLen + OutLen; + static constexpr int MaxNumberOfBitsPerRequest = (1 << 19); + static constexpr int ReseedInterval = 0x7FFFFFF0; + + static constexpr size_t OutSize = OutLen / BitsPerByte; + static constexpr size_t SeedSize = SeedLen / BitsPerByte; + static constexpr size_t RequestSizeMax = MaxNumberOfBitsPerRequest / BitsPerByte; + + static_assert(SeedSize % OutSize == 0); + private: + class Bcc { + private: + u8 *m_buffer; + const BlockCipher *m_cipher; + size_t m_offset; + public: + Bcc(u8 *buffer, const BlockCipher *cipher) : m_buffer(buffer), m_cipher(cipher), m_offset(0) { /* ... */ } + + void Process(const void *data, size_t size) { + const u8 *data_8 = static_cast<const u8 *>(data); + size_t remaining = size; + + while (m_offset + remaining >= OutSize) { + const size_t xor_size = OutSize - m_offset; + + Xor(m_buffer + m_offset, data_8, xor_size); + m_cipher->EncryptBlock(m_buffer, OutSize, m_buffer, OutSize); + + data_8 += xor_size; + remaining -= xor_size; + + m_offset = 0; + } + + Xor(m_buffer + m_offset, data_8, remaining); + m_offset += remaining; + } + + void Flush() { + if (m_offset != 0) { + m_cipher->EncryptBlock(m_buffer, OutSize, m_buffer, OutSize); + m_offset = 0; + } + } + }; + private: + BlockCipher m_block_cipher; + u8 m_v[OutSize]; + u8 m_key[KeySize]; + u8 m_work1[SeedSize]; + u8 m_work2[SeedSize]; + int m_reseed_counter; + private: + static void Xor(void *dst, const void *src, size_t size) { + const u8 *src_u8 = static_cast<const u8 *>(src); + u8 *dst_u8 = static_cast<u8 *>(dst); + + for (size_t i = 0; i < size; i++) { + dst_u8[i] ^= src_u8[i]; + } + } + + static void Increment(void *v) { + u8 *v_8 = static_cast<u8 *>(v); + + for (int i = OutSize - 1; i >= 0; --i) { + if ((++v_8[i]) != 0) { + break; + } + } + } + private: + void DeriveSeed(void *seed, const void *a, size_t a_size, const void *b, size_t b_size, const void *c, size_t c_size) { + /* Determine sizes. */ + const u32 in_size = a_size + b_size + c_size; + const u32 out_size = SeedSize; + + /* Create header/footer. */ + u32 header[2]; + util::StoreBigEndian(header + 0, in_size); + util::StoreBigEndian(header + 1, out_size); + const u8 footer = 0x80; + + /* Create seed as 000102... */ + u8 *seed_8 = static_cast<u8 *>(seed); + for (size_t i = 0; i < KeySize; ++i) { + seed_8[i] = i; + } + + /* Initialize block cipher. */ + m_block_cipher.Initialize(seed_8, KeySize); + + /* Perform derivation. */ + for (u32 block = 0; block < SeedSize / OutSize; ++block) { + /* Create the block index value. */ + u32 block_value; + util::StoreBigEndian(std::addressof(block_value), block); + + /* Get the target block. */ + u8 *target = seed_8 + block * OutSize; + std::memset(target, 0, OutSize); + + /* Create block processor. */ + Bcc bcc(target, std::addressof(m_block_cipher)); + + /* Process block value. */ + bcc.Process(std::addressof(block_value), sizeof(block_value)); + bcc.Flush(); + + /* Process header/data. */ + bcc.Process(header, sizeof(header)); + bcc.Process(a, a_size); + bcc.Process(b, b_size); + bcc.Process(c, c_size); + bcc.Process(footer, std::addressof(footer)); + bcc.Flush(); + } + + /* Initialize block cipher. */ + m_block_cipher.Initialize(seed_8, KeySize); + + /* Encrypt seed. */ + m_block_cipher.EncryptBlock(seed_8, OutSize, seed_8 + KeySize, OutSize); + for (size_t offset = 0; offset < SeedSize - OutSize; offset += OutSize) { + m_block_cipher.EncryptBlock(seed_8 + offset + OutSize, OutSize, seed_8 + offset, OutSize); + } + } + + void UpdateStates(void *key, void *v, const void *provided_data) { + /* Initialize block cipher. */ + m_block_cipher.Initialize(key, KeySize); + + /* Update work. */ + for (size_t offset = 0; offset < SeedSize; offset += OutSize) { + Increment(v); + m_block_cipher.EncryptBlock(std::addressof(m_work2[offset]), OutSize, v, OutSize); + } + + /* Xor work with provided data. */ + Xor(m_work2, provided_data, SeedSize); + + /* Copy to key/v. */ + std::memcpy(key, m_work2 + 0, KeySize); + std::memcpy(v, m_work2 + KeySize, OutSize); + } + public: + constexpr CtrDrbg() = default; + + void Initialize(const void *entropy, size_t entropy_size, const void *nonce, size_t nonce_size, const void *personalization, size_t personalization_size) { + /* Handle init. */ + if constexpr (UseDerivation) { + this->DeriveSeed(m_work1, entropy, entropy_size, nonce, nonce_size, personalization, personalization_size); + } else { + AMS_ASSERT(entropy_size == SeedSize); + AMS_ASSERT(nonce_size == 0); + AMS_ASSERT(personalization_size <= SeedSize); + AMS_UNUSED(entropy_size, nonce, nonce_size); + + std::memcpy(m_work1, entropy, SeedSize); + Xor(m_work1, personalization, personalization_size); + } + + /* Clear key/v. */ + std::memset(m_key, 0, sizeof(m_key)); + std::memset(m_v, 0, sizeof(m_v)); + + /* Set key/v. */ + this->UpdateStates(m_key, m_v, m_work1); + + /* Set reseed counter. */ + m_reseed_counter = 1; + } + + void Reseed(const void *entropy, size_t entropy_size, const void *addl, size_t addl_size) { + /* Handle init. */ + if constexpr (UseDerivation) { + this->DeriveSeed(m_work1, entropy, entropy_size, addl, addl_size, nullptr, 0); + } else { + AMS_ASSERT(entropy_size == SeedSize); + AMS_ASSERT(addl_size <= SeedSize); + AMS_UNUSED(entropy_size); + + std::memcpy(m_work1, entropy, SeedSize); + Xor(m_work1, addl, addl_size); + } + + /* Set key/v. */ + this->UpdateStates(m_key, m_v, m_work1); + + /* Set reseed counter. */ + m_reseed_counter = 1; + } + + bool Generate(void *out, size_t size, const void *addl, size_t addl_size) { + /* Check that the request is small enough. */ + if (size > RequestSizeMax) { + return false; + } + + /* Check if we need reseed. */ + if (m_reseed_counter > ReseedInterval) { + return false; + } + + /* Clear work buffer. */ + std::memset(m_work1, 0, sizeof(m_work1)); + + /* Process additional input, if we have any. */ + if (addl_size > 0) { + if constexpr (UseDerivation) { + this->DeriveSeed(m_work1, addl, addl_size, nullptr, 0, nullptr, 0); + } else { + AMS_ASSERT(addl_size <= SeedSize); + std::memcpy(m_work1, addl, addl_size); + } + + /* Set key/v. */ + this->UpdateStates(m_key, m_v, m_work1); + } + + /* Get buffer and aligned size. */ + u8 *out_8 = static_cast<u8 *>(out); + const size_t aligned_size = util::AlignDown(size, OutSize); + + /* Generate ctr bytes. */ + m_block_cipher.Initialize(m_key, KeySize); + for (size_t offset = 0; offset < aligned_size; offset += OutSize) { + Increment(m_v); + m_block_cipher.EncryptBlock(out_8 + offset, OutSize, m_v, OutSize); + } + + /* Handle any unaligned data. */ + if (size > aligned_size) { + u8 temp[OutSize]; + Increment(m_v); + m_block_cipher.EncryptBlock(temp, sizeof(temp), m_v, OutSize); + std::memcpy(out_8 + aligned_size, temp, size - aligned_size); + } + + /* Set key/v. */ + this->UpdateStates(m_key, m_v, m_work1); + + /* Increment reseed counter. */ + ++m_reseed_counter; + return true; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/impl/spl_device_address_mapper.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/impl/spl_device_address_mapper.hpp new file mode 100644 index 00000000..90aba8f2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/impl/spl_device_address_mapper.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::spl::impl { + + class DeviceAddressMapper { + private: + dd::DeviceAddressSpaceType *m_das; + u64 m_process_address; + size_t m_size; + dd::DeviceVirtualAddress m_device_address; + public: + DeviceAddressMapper(dd::DeviceAddressSpaceType *das, u64 process_address, size_t size, dd::DeviceVirtualAddress device_address, dd::MemoryPermission permission) + : m_das(das), m_process_address(process_address), m_size(size), m_device_address(device_address) + { + #if defined(ATMOSPHERE_OS_HORIZON) + R_ABORT_UNLESS(dd::MapDeviceAddressSpaceAligned(m_das, dd::GetCurrentProcessHandle(), m_process_address, m_size, m_device_address, permission)); + #else + AMS_UNUSED(permission); + #endif + } + + ~DeviceAddressMapper() { + #if defined(ATMOSPHERE_OS_HORIZON) + dd::UnmapDeviceAddressSpace(m_das, dd::GetCurrentProcessHandle(), m_process_address, m_size, m_device_address); + #endif + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/impl/spl_key_slot_cache.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/impl/spl_key_slot_cache.hpp new file mode 100644 index 00000000..640f9c45 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/impl/spl_key_slot_cache.hpp @@ -0,0 +1,138 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::spl::impl { + + class AesKeySlotCacheEntry : public util::IntrusiveListBaseNode<AesKeySlotCacheEntry> { + NON_COPYABLE(AesKeySlotCacheEntry); + NON_MOVEABLE(AesKeySlotCacheEntry); + private: + friend class AesKeySlotCache; + public: + static constexpr size_t KeySize = crypto::AesDecryptor128::KeySize; + private: + const s32 m_aes_keyslot_index; + s32 m_virtual_aes_keyslot; + public: + explicit AesKeySlotCacheEntry(s32 idx) : m_aes_keyslot_index(idx), m_virtual_aes_keyslot(-1) { /* ... */ } + + bool Contains(s32 virtual_slot) const { + return virtual_slot == m_virtual_aes_keyslot; + } + + s32 GetPhysicalAesKeySlotIndex() const { return m_aes_keyslot_index; } + + s32 GetVirtualAesKeySlotIndex() const { return m_virtual_aes_keyslot; } + + void SetVirtualAesKeySlot(s32 virtual_slot) { + m_virtual_aes_keyslot = virtual_slot; + } + + void ClearVirtualAesKeySlot() { + m_virtual_aes_keyslot = -1; + } + }; + + class AesKeySlotCache { + NON_COPYABLE(AesKeySlotCache); + NON_MOVEABLE(AesKeySlotCache); + private: + using AesKeySlotCacheEntryList = util::IntrusiveListBaseTraits<AesKeySlotCacheEntry>::ListType; + private: + AesKeySlotCacheEntryList m_mru_list; + public: + constexpr AesKeySlotCache() : m_mru_list() { /* ... */ } + + s32 Allocate(s32 virtual_slot) { + return this->AllocateFromLru(virtual_slot); + } + + bool Find(s32 *out, s32 virtual_slot) { + for (auto it = m_mru_list.begin(); it != m_mru_list.end(); ++it) { + if (it->Contains(virtual_slot)) { + *out = it->GetPhysicalAesKeySlotIndex(); + + this->UpdateMru(it); + return true; + } + } + + return false; + } + + bool Release(s32 *out, s32 virtual_slot) { + for (auto it = m_mru_list.begin(); it != m_mru_list.end(); ++it) { + if (it->Contains(virtual_slot)) { + *out = it->GetPhysicalAesKeySlotIndex(); + it->ClearVirtualAesKeySlot(); + + this->UpdateLru(it); + return true; + } + } + + return false; + } + + bool FindPhysical(s32 physical_slot) { + for (auto it = m_mru_list.begin(); it != m_mru_list.end(); ++it) { + if (it->GetPhysicalAesKeySlotIndex() == physical_slot) { + this->UpdateMru(it); + + if (it->GetVirtualAesKeySlotIndex() == physical_slot) { + return true; + } else { + it->SetVirtualAesKeySlot(physical_slot); + return false; + } + } + } + AMS_ABORT(); + } + + void AddEntry(AesKeySlotCacheEntry *entry) { + m_mru_list.push_front(*entry); + } + private: + s32 AllocateFromLru(s32 virtual_slot) { + AMS_ASSERT(!m_mru_list.empty()); + + auto it = m_mru_list.rbegin(); + it->SetVirtualAesKeySlot(virtual_slot); + + auto *entry = std::addressof(*it); + m_mru_list.pop_back(); + m_mru_list.push_front(*entry); + + return entry->GetPhysicalAesKeySlotIndex(); + } + + void UpdateMru(AesKeySlotCacheEntryList::iterator it) { + auto *entry = std::addressof(*it); + m_mru_list.erase(it); + m_mru_list.push_front(*entry); + } + + void UpdateLru(AesKeySlotCacheEntryList::iterator it) { + auto *entry = std::addressof(*it); + m_mru_list.erase(it); + m_mru_list.push_back(*entry); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/smc/spl_secure_monitor_api.os.generic.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/smc/spl_secure_monitor_api.os.generic.cpp new file mode 100644 index 00000000..966912ad --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/smc/spl_secure_monitor_api.os.generic.cpp @@ -0,0 +1,530 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <exosphere/pkg1.hpp> + +namespace ams::spl::smc { + + #define SMC_R_SUCCEEEDED(res) (res == smc::Result::Success) + #define SMC_R_FAILED(res) (res != smc::Result::Success) + + #define SMC_R_TRY(res_expr) ({ const auto _tmp_r_try_rc = (res_expr); if (SMC_R_FAILED(_tmp_r_try_rc)) { return _tmp_r_try_rc; } }) + #define SMC_R_UNLESS(cond, RES) ({ if (!(cond)) { return smc::Result::RES; }}) + + namespace { + + enum SealKey { + SealKey_LoadAesKey = 0, + SealKey_DecryptDeviceUniqueData = 1, + SealKey_ImportLotusKey = 2, + SealKey_ImportEsDeviceKey = 3, + SealKey_ReencryptDeviceUniqueData = 4, + SealKey_ImportSslKey = 5, + SealKey_ImportEsClientCertKey = 6, + + SealKey_Count, + }; + + enum KeyType { + KeyType_Default = 0, + KeyType_NormalOnly = 1, + KeyType_RecoveryOnly = 2, + KeyType_NormalAndRecovery = 3, + + KeyType_Count, + }; + + enum EsCommonKeyType { + EsCommonKeyType_TitleKey = 0, + EsCommonKeyType_ArchiveKey = 1, + EsCommonKeyType_Unknown2 = 2, + + EsCommonKeyType_Count, + }; + + struct GenerateAesKekOption { + using IsDeviceUnique = util::BitPack32::Field<0, 1, bool>; + using KeyTypeIndex = util::BitPack32::Field<1, 4, KeyType>; + using SealKeyIndex = util::BitPack32::Field<5, 3, SealKey>; + using Reserved = util::BitPack32::Field<8, 24, u32>; + }; + + struct ComputeAesOption { + using KeySlot = util::BitPack32::Field<0, 3, int>; + using CipherModeIndex = util::BitPack32::Field<4, 2, CipherMode>; + }; + + constexpr const u8 KeyTypeSources[KeyType_Count][crypto::AesEncryptor128::KeySize] = { + [KeyType_Default] = { 0x4D, 0x87, 0x09, 0x86, 0xC4, 0x5D, 0x20, 0x72, 0x2F, 0xBA, 0x10, 0x53, 0xDA, 0x92, 0xE8, 0xA9 }, + [KeyType_NormalOnly] = { 0x25, 0x03, 0x31, 0xFB, 0x25, 0x26, 0x0B, 0x79, 0x8C, 0x80, 0xD2, 0x69, 0x98, 0xE2, 0x22, 0x77 }, + [KeyType_RecoveryOnly] = { 0x76, 0x14, 0x1D, 0x34, 0x93, 0x2D, 0xE1, 0x84, 0x24, 0x7B, 0x66, 0x65, 0x55, 0x04, 0x65, 0x81 }, + [KeyType_NormalAndRecovery] = { 0xAF, 0x3D, 0xB7, 0xF3, 0x08, 0xA2, 0xD8, 0xA2, 0x08, 0xCA, 0x18, 0xA8, 0x69, 0x46, 0xC9, 0x0B }, + }; + + constexpr const u8 SealKeyMasks[SealKey_Count][crypto::AesEncryptor128::KeySize] = { + [SealKey_LoadAesKey] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + [SealKey_DecryptDeviceUniqueData] = { 0xA2, 0xAB, 0xBF, 0x9C, 0x92, 0x2F, 0xBB, 0xE3, 0x78, 0x79, 0x9B, 0xC0, 0xCC, 0xEA, 0xA5, 0x74 }, + [SealKey_ImportLotusKey] = { 0x57, 0xE2, 0xD9, 0x45, 0xE4, 0x92, 0xF4, 0xFD, 0xC3, 0xF9, 0x86, 0x38, 0x89, 0x78, 0x9F, 0x3C }, + [SealKey_ImportEsDeviceKey] = { 0xE5, 0x4D, 0x9A, 0x02, 0xF0, 0x4F, 0x5F, 0xA8, 0xAD, 0x76, 0x0A, 0xF6, 0x32, 0x95, 0x59, 0xBB }, + [SealKey_ReencryptDeviceUniqueData] = { 0x59, 0xD9, 0x31, 0xF4, 0xA7, 0x97, 0xB8, 0x14, 0x40, 0xD6, 0xA2, 0x60, 0x2B, 0xED, 0x15, 0x31 }, + [SealKey_ImportSslKey] = { 0xFD, 0x6A, 0x25, 0xE5, 0xD8, 0x38, 0x7F, 0x91, 0x49, 0xDA, 0xF8, 0x59, 0xA8, 0x28, 0xE6, 0x75 }, + [SealKey_ImportEsClientCertKey] = { 0x89, 0x96, 0x43, 0x9A, 0x7C, 0xD5, 0x59, 0x55, 0x24, 0xD5, 0x24, 0x18, 0xAB, 0x6C, 0x04, 0x61 }, + }; + + constexpr const u8 EsCommonKeySources[EsCommonKeyType_Count][AesKeySize] = { + [EsCommonKeyType_TitleKey] = { 0x1E, 0xDC, 0x7B, 0x3B, 0x60, 0xE6, 0xB4, 0xD8, 0x78, 0xB8, 0x17, 0x15, 0x98, 0x5E, 0x62, 0x9B }, + [EsCommonKeyType_ArchiveKey] = { 0x3B, 0x78, 0xF2, 0x61, 0x0F, 0x9D, 0x5A, 0xE2, 0x7B, 0x4E, 0x45, 0xAF, 0xCB, 0x0B, 0x67, 0x4D }, + [EsCommonKeyType_Unknown2] = { 0x42, 0x64, 0x0B, 0xE3, 0x5F, 0xC6, 0xBE, 0x47, 0xC7, 0xB4, 0x84, 0xC5, 0xEB, 0x63, 0xAA, 0x02 }, + }; + + constexpr u64 InvalidAsyncKey = 0; + + constinit os::SdkMutex g_crypto_lock; + constinit u64 g_async_key = InvalidAsyncKey; + constinit u8 g_async_result_buffer[1_KB]; + + u64 GenerateRandomU64() { + u64 v = -1; + crypto::GenerateCryptographicallyRandomBytes(std::addressof(v), sizeof(v)); + return v; + } + + constinit u8 g_master_keys[pkg1::KeyGeneration_Max][crypto::AesEncryptor128::KeySize]{}; + constinit u8 g_device_keys[pkg1::KeyGeneration_Max][crypto::AesEncryptor128::KeySize]{}; + + class KeySlotManager { + private: + u8 m_key_slot_contents[pkg1::AesKeySlot_Count][crypto::AesEncryptor256::KeySize]; + public: + constexpr KeySlotManager() : m_key_slot_contents{} { /* ... */ } + public: + const u8 *GetKey(s32 slot) const { + return m_key_slot_contents[slot]; + } + + void LoadAesKey(s32 slot, const AccessKey &access_key, const KeySource &key_source) { + crypto::AesDecryptor128 aes; + aes.Initialize(std::addressof(access_key), sizeof(access_key)); + aes.DecryptBlock(m_key_slot_contents[slot], crypto::AesEncryptor128::KeySize, std::addressof(key_source), sizeof(key_source)); + } + + void LoadPreparedAesKey(s32 slot, const AccessKey &access_key) { + this->SetAesKey128(slot, std::addressof(access_key), sizeof(access_key)); + } + + s32 PrepareDeviceMasterKey(s32 generation) { + constexpr s32 Slot = pkg1::AesKeySlot_Smc; + this->SetAesKey128(Slot, g_device_keys[generation], crypto::AesEncryptor128::KeySize); + return Slot; + } + + s32 PrepareMasterKey(s32 generation) { + constexpr s32 Slot = pkg1::AesKeySlot_Smc; + this->SetAesKey128(Slot, g_master_keys[generation], crypto::AesEncryptor128::KeySize); + return Slot; + } + + void SetAesKey128(s32 slot, const void *key, size_t key_size) { + std::memcpy(m_key_slot_contents[slot], key, key_size); + } + + void SetEncryptedAesKey128(s32 dst, s32 src, const void *key_source, size_t key_source_size) { + crypto::AesDecryptor128 aes; + aes.Initialize(this->GetKey(src), crypto::AesDecryptor128::KeySize); + aes.DecryptBlock(m_key_slot_contents[dst], crypto::AesEncryptor128::KeySize, key_source, key_source_size); + } + + void DecryptAes128(void *dst, size_t dst_size, s32 slot, const void *src, size_t src_size) { + crypto::AesDecryptor128 aes; + aes.Initialize(this->GetKey(slot), crypto::AesDecryptor128::KeySize); + aes.DecryptBlock(dst, dst_size, src, src_size); + } + }; + + constexpr bool IsUserAesKeySlot(s32 slot) { + return pkg1::IsUserAesKeySlot(slot); + } + + constinit KeySlotManager g_key_slot_manager; + + void DecryptWithEsCommonKey(void *dst, size_t dst_size, const void *src, size_t src_size, EsCommonKeyType type, int generation) { + /* Validate pre-conditions. */ + AMS_ASSERT(dst_size == crypto::AesEncryptor128::KeySize); + AMS_ASSERT(src_size == crypto::AesEncryptor128::KeySize); + AMS_ASSERT(0 <= type && type < EsCommonKeyType_Count); + + /* Prepare the master key for the generation. */ + const int slot = g_key_slot_manager.PrepareMasterKey(generation); + + /* Derive the es common key. */ + g_key_slot_manager.SetEncryptedAesKey128(pkg1::AesKeySlot_Smc, slot, EsCommonKeySources[type], crypto::AesEncryptor128::KeySize); + + /* Decrypt the input using the common key. */ + g_key_slot_manager.DecryptAes128(dst, dst_size, pkg1::AesKeySlot_Smc, src, src_size); + } + + } + + void PresetInternalKey(const AesKey *key, u32 generation, bool device) { + if (device) { + std::memcpy(g_device_keys[generation], key, sizeof(*key)); + } else { + std::memcpy(g_master_keys[generation], key, sizeof(*key)); + } + } + + //Result SetConfig(AsyncOperationKey *out_op, spl::ConfigItem key, const u64 *value, size_t num_qwords, const void *sign) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast<u64>(FunctionId::SetConfig); + // args.r[1] = static_cast<u64>(key); + // args.r[2] = reinterpret_cast<u64>(sign); + // + // for (size_t i = 0; i < std::min(static_cast<size_t>(4), num_qwords); i++) { + // args.r[3 + i] = value[i]; + // } + // svc::CallSecureMonitor(std::addressof(args)); + // + // out_op->value = args.r[1]; + // return static_cast<Result>(args.r[0]); + //} + + //Result GetConfig(u64 *out, size_t num_qwords, spl::ConfigItem key) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast<u64>(FunctionId::GetConfig); + // args.r[1] = static_cast<u64>(key); + // svc::CallSecureMonitor(std::addressof(args)); + // + // for (size_t i = 0; i < std::min(static_cast<size_t>(4), num_qwords); i++) { + // out[i] = args.r[1 + i]; + // } + // return static_cast<Result>(args.r[0]); + //} + + Result GetResult(Result *out, AsyncOperationKey op) { + SMC_R_UNLESS(g_async_key != InvalidAsyncKey, NoAsyncOperation); + SMC_R_UNLESS(g_async_key == op.value, InvalidAsyncOperation); + + g_async_key = InvalidAsyncKey; + + *out = smc::Result::Success; + return smc::Result::Success; + } + + Result GetResultData(Result *out, void *out_buf, size_t out_buf_size, AsyncOperationKey op) { + SMC_R_UNLESS(g_async_key != InvalidAsyncKey, NoAsyncOperation); + SMC_R_UNLESS(g_async_key == op.value, InvalidAsyncOperation); + SMC_R_UNLESS(out_buf_size <= sizeof(g_async_result_buffer), InvalidArgument); + + g_async_key = InvalidAsyncKey; + std::memcpy(out_buf, g_async_result_buffer, out_buf_size); + + *out = smc::Result::Success; + return smc::Result::Success; + } + + //Result ModularExponentiate(AsyncOperationKey *out_op, const void *base, const void *exp, size_t exp_size, const void *mod) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast<u64>(FunctionId::ModularExponentiate); + // args.r[1] = reinterpret_cast<u64>(base); + // args.r[2] = reinterpret_cast<u64>(exp); + // args.r[3] = reinterpret_cast<u64>(mod); + // args.r[4] = exp_size; + // svc::CallSecureMonitor(std::addressof(args)); + // + // out_op->value = args.r[1]; + // return static_cast<Result>(args.r[0]); + //} + + Result GenerateRandomBytes(void *out, size_t size) { + crypto::GenerateCryptographicallyRandomBytes(out, size); + return smc::Result::Success; + } + + Result GenerateAesKek(AccessKey *out, const KeySource &source, u32 user_generation, u32 option_value) { + std::scoped_lock lk(g_crypto_lock); + + const int pkg1_generation = std::max<int>(static_cast<int>(user_generation) - 1, pkg1::KeyGeneration_1_0_0); + + const util::BitPack32 option = { option_value }; + const bool is_device_unique = option.Get<GenerateAesKekOption::IsDeviceUnique>(); + const auto key_type = option.Get<GenerateAesKekOption::KeyTypeIndex>(); + const auto seal_key = option.Get<GenerateAesKekOption::SealKeyIndex>(); + const u32 reserved = option.Get<GenerateAesKekOption::Reserved>(); + + /* Validate arguments. */ + SMC_R_UNLESS(reserved == 0, InvalidArgument); + + if (is_device_unique) { + SMC_R_UNLESS(pkg1::IsValidDeviceUniqueKeyGeneration(pkg1_generation), InvalidArgument); + } else { + SMC_R_UNLESS(pkg1_generation < pkg1::KeyGeneration_Max, InvalidArgument); + } + + SMC_R_UNLESS(0 <= key_type && key_type < KeyType_Count, InvalidArgument); + SMC_R_UNLESS(0 <= seal_key && seal_key < SealKey_Count, InvalidArgument); + + /* Here N might check if key type is normal or recovery only, but we're not going to enforce that. */ + u8 static_source[crypto::AesEncryptor128::KeySize]; + + /* Derive the static source. */ + for (size_t i = 0; i < sizeof(static_source); ++i) { + static_source[i] = KeyTypeSources[key_type][i] ^ SealKeyMasks[seal_key][i]; + } + + /* Get the slot. */ + const int slot = is_device_unique ? g_key_slot_manager.PrepareDeviceMasterKey(pkg1_generation) : g_key_slot_manager.PrepareMasterKey(pkg1_generation); + + /* Derive a static generation kek. */ + g_key_slot_manager.SetEncryptedAesKey128(pkg1::AesKeySlot_Smc, slot, static_source, sizeof(static_source)); + + /* Decrypt the input using the static-derived key. */ + g_key_slot_manager.DecryptAes128(out, sizeof(*out), pkg1::AesKeySlot_Smc, std::addressof(source), sizeof(source)); + + return smc::Result::Success; + } + + Result LoadAesKey(u32 keyslot, const AccessKey &access_key, const KeySource &source) { + std::scoped_lock lk(g_crypto_lock); + + /* Check args. */ + SMC_R_UNLESS(IsUserAesKeySlot(keyslot), InvalidArgument); + + /* Unseal the access key. */ + g_key_slot_manager.SetAesKey128(pkg1::AesKeySlot_Smc, std::addressof(access_key), sizeof(access_key)); + + /* Derive the key. */ + g_key_slot_manager.SetEncryptedAesKey128(keyslot, pkg1::AesKeySlot_Smc, std::addressof(source), sizeof(source)); + + return smc::Result::Success; + } + + Result ComputeAes(AsyncOperationKey *out_op, u64 dst_addr, u32 mode, const IvCtr &iv_ctr, u64 src_addr, size_t size) { + std::scoped_lock lk(g_crypto_lock); + + /* Check size. */ + SMC_R_UNLESS(util::IsAligned(size, crypto::AesEncryptor128::BlockSize), InvalidArgument); + + const util::BitPack32 option = { mode }; + const int slot = option.Get<ComputeAesOption::KeySlot>(); + const auto cipher_mode = option.Get<ComputeAesOption::CipherModeIndex>(); + SMC_R_UNLESS(IsUserAesKeySlot(slot), InvalidArgument); + + /* Set a random async key. */ + g_async_key = GenerateRandomU64(); + + switch (cipher_mode) { + case CipherMode::CbcEncrypt: crypto::EncryptAes128Cbc(reinterpret_cast<void *>(dst_addr), size, g_key_slot_manager.GetKey(slot), crypto::AesEncryptor128::KeySize, iv_ctr.data, sizeof(iv_ctr.data), reinterpret_cast<const void *>(src_addr), size); break; + case CipherMode::CbcDecrypt: crypto::DecryptAes128Cbc(reinterpret_cast<void *>(dst_addr), size, g_key_slot_manager.GetKey(slot), crypto::AesEncryptor128::KeySize, iv_ctr.data, sizeof(iv_ctr.data), reinterpret_cast<const void *>(src_addr), size); break; + case CipherMode::Ctr: crypto::EncryptAes128Ctr(reinterpret_cast<void *>(dst_addr), size, g_key_slot_manager.GetKey(slot), crypto::AesEncryptor128::KeySize, iv_ctr.data, sizeof(iv_ctr.data), reinterpret_cast<const void *>(src_addr), size); break; + default: + return smc::Result::InvalidArgument; + } + + *out_op = AsyncOperationKey{g_async_key}; + return smc::Result::Success; + } + + //Result GenerateSpecificAesKey(AesKey *out_key, const KeySource &source, u32 generation, u32 which) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast<u64>(FunctionId::GenerateSpecificAesKey); + // args.r[1] = source.data64[0]; + // args.r[2] = source.data64[1]; + // args.r[3] = generation; + // args.r[4] = which; + // svc::CallSecureMonitor(std::addressof(args)); + // + // out_key->data64[0] = args.r[1]; + // out_key->data64[1] = args.r[2]; + // return static_cast<Result>(args.r[0]); + //} + + //Result ComputeCmac(Cmac *out_mac, u32 keyslot, const void *data, size_t size) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast<u64>(FunctionId::ComputeCmac); + // args.r[1] = keyslot; + // args.r[2] = reinterpret_cast<u64>(data); + // args.r[3] = size; + // svc::CallSecureMonitor(std::addressof(args)); + // + // out_mac->data64[0] = args.r[1]; + // out_mac->data64[1] = args.r[2]; + // return static_cast<Result>(args.r[0]); + //} + + //Result ReencryptDeviceUniqueData(void *data, size_t size, const AccessKey &access_key_dec, const KeySource &source_dec, const AccessKey &access_key_enc, const KeySource &source_enc, u32 option) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast<u64>(FunctionId::ReencryptDeviceUniqueData); + // args.r[1] = reinterpret_cast<u64>(std::addressof(access_key_dec)); + // args.r[2] = reinterpret_cast<u64>(std::addressof(access_key_enc)); + // args.r[3] = option; + // args.r[4] = reinterpret_cast<u64>(data); + // args.r[5] = size; + // args.r[6] = reinterpret_cast<u64>(std::addressof(source_dec)); + // args.r[7] = reinterpret_cast<u64>(std::addressof(source_enc)); + // svc::CallSecureMonitor(std::addressof(args)); + // + // return static_cast<Result>(args.r[0]); + //} + + //Result DecryptDeviceUniqueData(void *data, size_t size, const AccessKey &access_key, const KeySource &source, DeviceUniqueDataMode mode) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast<u64>(FunctionId::DecryptDeviceUniqueData); + // args.r[1] = access_key.data64[0]; + // args.r[2] = access_key.data64[1]; + // args.r[3] = static_cast<u32>(mode); + // args.r[4] = reinterpret_cast<u64>(data); + // args.r[5] = size; + // args.r[6] = source.data64[0]; + // args.r[7] = source.data64[1]; + // svc::CallSecureMonitor(std::addressof(args)); + // + // return static_cast<Result>(args.r[0]); + //} + + //Result ModularExponentiateWithStorageKey(AsyncOperationKey *out_op, const void *base, const void *mod, ModularExponentiateWithStorageKeyMode mode) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast<u64>(FunctionId::ModularExponentiateWithStorageKey); + // args.r[1] = reinterpret_cast<u64>(base); + // args.r[2] = reinterpret_cast<u64>(mod); + // args.r[3] = static_cast<u32>(mode); + // svc::CallSecureMonitor(std::addressof(args)); + // + // out_op->value = args.r[1]; + // return static_cast<Result>(args.r[0]); + //} + + //Result PrepareEsDeviceUniqueKey(AsyncOperationKey *out_op, const void *base, const void *mod, const void *label_digest, size_t label_digest_size, u32 option) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast<u64>(FunctionId::PrepareEsDeviceUniqueKey); + // args.r[1] = reinterpret_cast<u64>(base); + // args.r[2] = reinterpret_cast<u64>(mod); + // std::memset(std::addressof(args.r[3]), 0, 4 * sizeof(args.r[3])); + // std::memcpy(std::addressof(args.r[3]), label_digest, std::min(static_cast<size_t>(4 * sizeof(args.r[3])), label_digest_size)); + // args.r[7] = option; + // svc::CallSecureMonitor(std::addressof(args)); + // + // out_op->value = args.r[1]; + // return static_cast<Result>(args.r[0]); + //} + + Result LoadPreparedAesKey(u32 keyslot, const AccessKey &access_key) { + std::scoped_lock lk(g_crypto_lock); + + /* Check args. */ + SMC_R_UNLESS(IsUserAesKeySlot(keyslot), InvalidArgument); + + /* Unseal the key. */ + g_key_slot_manager.SetAesKey128(keyslot, std::addressof(access_key), sizeof(access_key)); + + return smc::Result::Success; + } + + Result PrepareCommonEsTitleKey(AccessKey *out, const KeySource &source, u32 generation) { + /* Decode arguments. */ + const int pkg1_gen = std::max<int>(pkg1::KeyGeneration_1_0_0, static_cast<int>(generation) - 1); + + /* Validate arguments. */ + SMC_R_UNLESS(pkg1_gen < pkg1::KeyGeneration_Max, InvalidArgument); + + /* Derive the key. */ + u8 key[crypto::AesEncryptor128::KeySize]; + DecryptWithEsCommonKey(key, sizeof(key), std::addressof(source), sizeof(source), EsCommonKeyType_TitleKey, pkg1_gen); + + /* Copy the access key to the output. */ + std::memcpy(out, key, sizeof(key)); + + return smc::Result::Success; + } + + // + ///* Deprecated functions. */ + //Result LoadEsDeviceKey(const void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast<u64>(FunctionId::LoadEsDeviceKey); + // args.r[1] = access_key.data64[0]; + // args.r[2] = access_key.data64[1]; + // args.r[3] = option; + // args.r[4] = reinterpret_cast<u64>(data); + // args.r[5] = size; + // args.r[6] = source.data64[0]; + // args.r[7] = source.data64[1]; + // svc::CallSecureMonitor(std::addressof(args)); + // + // return static_cast<Result>(args.r[0]); + //} + + //Result DecryptDeviceUniqueData(size_t *out_size, void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast<u64>(FunctionId::DecryptDeviceUniqueData); + // args.r[1] = access_key.data64[0]; + // args.r[2] = access_key.data64[1]; + // args.r[3] = option; + // args.r[4] = reinterpret_cast<u64>(data); + // args.r[5] = size; + // args.r[6] = source.data64[0]; + // args.r[7] = source.data64[1]; + // svc::CallSecureMonitor(std::addressof(args)); + // + // *out_size = static_cast<size_t>(args.r[1]); + // return static_cast<Result>(args.r[0]); + //} + + //Result DecryptAndStoreGcKey(const void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option) { + // svc::SecureMonitorArguments args; + // + // args.r[0] = static_cast<u64>(FunctionId::DecryptAndStoreGcKey); + // args.r[1] = access_key.data64[0]; + // args.r[2] = access_key.data64[1]; + // args.r[3] = option; + // args.r[4] = reinterpret_cast<u64>(data); + // args.r[5] = size; + // args.r[6] = source.data64[0]; + // args.r[7] = source.data64[1]; + // svc::CallSecureMonitor(std::addressof(args)); + // + // return static_cast<Result>(args.r[0]); + //} + + Result AtmosphereCopyToIram(uintptr_t, const void *, size_t ) { + AMS_ABORT("AtmosphereCopyToIram not supported on generic SecureMonitor api."); + } + + Result AtmosphereCopyFromIram(void *, uintptr_t, size_t) { + AMS_ABORT("AtmosphereCopyToIram not supported on generic SecureMonitor api."); + } + + Result AtmosphereReadWriteRegister(uint64_t, uint32_t, uint32_t, uint32_t *) { + AMS_ABORT("AtmosphereReadWriteRegister not supported on generic SecureMonitor api."); + } + + Result AtmosphereGetEmummcConfig(void *out_config, void *out_paths, u32 storage_id) { + /* TODO: We actually probably should support this one on generic? */ + AMS_UNUSED(out_config, out_paths, storage_id); + AMS_ABORT("AtmosphereGetEmummcConfig not supported on generic SecureMonitor api."); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/smc/spl_secure_monitor_api.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/smc/spl_secure_monitor_api.os.horizon.cpp new file mode 100644 index 00000000..129d7c19 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/smc/spl_secure_monitor_api.os.horizon.cpp @@ -0,0 +1,367 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::spl::smc { + + Result SetConfig(AsyncOperationKey *out_op, spl::ConfigItem key, const u64 *value, size_t num_qwords, const void *sign) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::SetConfig); + args.r[1] = static_cast<u64>(key); + args.r[2] = reinterpret_cast<u64>(sign); + + for (size_t i = 0; i < std::min(static_cast<size_t>(4), num_qwords); i++) { + args.r[3 + i] = value[i]; + } + svc::CallSecureMonitor(std::addressof(args)); + + out_op->value = args.r[1]; + return static_cast<Result>(args.r[0]); + } + + Result GetConfig(u64 *out, size_t num_qwords, spl::ConfigItem key) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::GetConfig); + args.r[1] = static_cast<u64>(key); + svc::CallSecureMonitor(std::addressof(args)); + + for (size_t i = 0; i < std::min(static_cast<size_t>(4), num_qwords); i++) { + out[i] = args.r[1 + i]; + } + return static_cast<Result>(args.r[0]); + } + + Result GetResult(Result *out, AsyncOperationKey op) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::GetResult); + args.r[1] = op.value; + svc::CallSecureMonitor(std::addressof(args)); + + *out = static_cast<Result>(args.r[1]); + return static_cast<Result>(args.r[0]); + } + + Result GetResultData(Result *out, void *out_buf, size_t out_buf_size, AsyncOperationKey op) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::GetResultData); + args.r[1] = op.value; + args.r[2] = reinterpret_cast<u64>(out_buf); + args.r[3] = out_buf_size; + svc::CallSecureMonitor(std::addressof(args)); + + *out = static_cast<Result>(args.r[1]); + return static_cast<Result>(args.r[0]); + } + + Result ModularExponentiate(AsyncOperationKey *out_op, const void *base, const void *exp, size_t exp_size, const void *mod) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::ModularExponentiate); + args.r[1] = reinterpret_cast<u64>(base); + args.r[2] = reinterpret_cast<u64>(exp); + args.r[3] = reinterpret_cast<u64>(mod); + args.r[4] = exp_size; + svc::CallSecureMonitor(std::addressof(args)); + + out_op->value = args.r[1]; + return static_cast<Result>(args.r[0]); + } + + Result GenerateRandomBytes(void *out, size_t size) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::GenerateRandomBytes); + args.r[1] = size; + svc::CallSecureMonitor(std::addressof(args)); + + if (args.r[0] == static_cast<u64>(Result::Success) && (size <= sizeof(args) - sizeof(args.r[0]))) { + std::memcpy(out, std::addressof(args.r[1]), size); + } + return static_cast<Result>(args.r[0]); + } + + Result GenerateAesKek(AccessKey *out, const KeySource &source, u32 generation, u32 option) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::GenerateAesKek); + args.r[1] = source.data64[0]; + args.r[2] = source.data64[1]; + args.r[3] = generation; + args.r[4] = option; + svc::CallSecureMonitor(std::addressof(args)); + + out->data64[0] = args.r[1]; + out->data64[1] = args.r[2]; + return static_cast<Result>(args.r[0]); + } + + Result LoadAesKey(u32 keyslot, const AccessKey &access_key, const KeySource &source) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::LoadAesKey); + args.r[1] = keyslot; + args.r[2] = access_key.data64[0]; + args.r[3] = access_key.data64[1]; + args.r[4] = source.data64[0]; + args.r[5] = source.data64[1]; + svc::CallSecureMonitor(std::addressof(args)); + + return static_cast<Result>(args.r[0]); + } + + Result ComputeAes(AsyncOperationKey *out_op, u64 dst_addr, u32 mode, const IvCtr &iv_ctr, u64 src_addr, size_t size) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::ComputeAes); + args.r[1] = mode; + args.r[2] = iv_ctr.data64[0]; + args.r[3] = iv_ctr.data64[1]; + args.r[4] = static_cast<u32>(src_addr); + args.r[5] = static_cast<u32>(dst_addr); + args.r[6] = size; + svc::CallSecureMonitor(std::addressof(args)); + + out_op->value = args.r[1]; + return static_cast<Result>(args.r[0]); + } + + Result GenerateSpecificAesKey(AesKey *out_key, const KeySource &source, u32 generation, u32 which) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::GenerateSpecificAesKey); + args.r[1] = source.data64[0]; + args.r[2] = source.data64[1]; + args.r[3] = generation; + args.r[4] = which; + svc::CallSecureMonitor(std::addressof(args)); + + out_key->data64[0] = args.r[1]; + out_key->data64[1] = args.r[2]; + return static_cast<Result>(args.r[0]); + } + + Result ComputeCmac(Cmac *out_mac, u32 keyslot, const void *data, size_t size) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::ComputeCmac); + args.r[1] = keyslot; + args.r[2] = reinterpret_cast<u64>(data); + args.r[3] = size; + svc::CallSecureMonitor(std::addressof(args)); + + out_mac->data64[0] = args.r[1]; + out_mac->data64[1] = args.r[2]; + return static_cast<Result>(args.r[0]); + } + + Result ReencryptDeviceUniqueData(void *data, size_t size, const AccessKey &access_key_dec, const KeySource &source_dec, const AccessKey &access_key_enc, const KeySource &source_enc, u32 option) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::ReencryptDeviceUniqueData); + args.r[1] = reinterpret_cast<u64>(std::addressof(access_key_dec)); + args.r[2] = reinterpret_cast<u64>(std::addressof(access_key_enc)); + args.r[3] = option; + args.r[4] = reinterpret_cast<u64>(data); + args.r[5] = size; + args.r[6] = reinterpret_cast<u64>(std::addressof(source_dec)); + args.r[7] = reinterpret_cast<u64>(std::addressof(source_enc)); + svc::CallSecureMonitor(std::addressof(args)); + + return static_cast<Result>(args.r[0]); + } + + Result DecryptDeviceUniqueData(void *data, size_t size, const AccessKey &access_key, const KeySource &source, DeviceUniqueDataMode mode) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::DecryptDeviceUniqueData); + args.r[1] = access_key.data64[0]; + args.r[2] = access_key.data64[1]; + args.r[3] = static_cast<u32>(mode); + args.r[4] = reinterpret_cast<u64>(data); + args.r[5] = size; + args.r[6] = source.data64[0]; + args.r[7] = source.data64[1]; + svc::CallSecureMonitor(std::addressof(args)); + + return static_cast<Result>(args.r[0]); + } + + Result ModularExponentiateWithStorageKey(AsyncOperationKey *out_op, const void *base, const void *mod, ModularExponentiateWithStorageKeyMode mode) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::ModularExponentiateWithStorageKey); + args.r[1] = reinterpret_cast<u64>(base); + args.r[2] = reinterpret_cast<u64>(mod); + args.r[3] = static_cast<u32>(mode); + svc::CallSecureMonitor(std::addressof(args)); + + out_op->value = args.r[1]; + return static_cast<Result>(args.r[0]); + } + + Result PrepareEsDeviceUniqueKey(AsyncOperationKey *out_op, const void *base, const void *mod, const void *label_digest, size_t label_digest_size, u32 option) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::PrepareEsDeviceUniqueKey); + args.r[1] = reinterpret_cast<u64>(base); + args.r[2] = reinterpret_cast<u64>(mod); + std::memset(std::addressof(args.r[3]), 0, 4 * sizeof(args.r[3])); + std::memcpy(std::addressof(args.r[3]), label_digest, std::min(static_cast<size_t>(4 * sizeof(args.r[3])), label_digest_size)); + args.r[7] = option; + svc::CallSecureMonitor(std::addressof(args)); + + out_op->value = args.r[1]; + return static_cast<Result>(args.r[0]); + } + + Result LoadPreparedAesKey(u32 keyslot, const AccessKey &access_key) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::LoadPreparedAesKey); + args.r[1] = keyslot; + args.r[2] = access_key.data64[0]; + args.r[3] = access_key.data64[1]; + svc::CallSecureMonitor(std::addressof(args)); + + return static_cast<Result>(args.r[0]); + } + + Result PrepareCommonEsTitleKey(AccessKey *out, const KeySource &source, u32 generation) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::PrepareCommonEsTitleKey); + args.r[1] = source.data64[0]; + args.r[2] = source.data64[1]; + args.r[3] = generation; + svc::CallSecureMonitor(std::addressof(args)); + + out->data64[0] = args.r[1]; + out->data64[1] = args.r[2]; + return static_cast<Result>(args.r[0]); + } + + + /* Deprecated functions. */ + Result LoadEsDeviceKey(const void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::LoadEsDeviceKey); + args.r[1] = access_key.data64[0]; + args.r[2] = access_key.data64[1]; + args.r[3] = option; + args.r[4] = reinterpret_cast<u64>(data); + args.r[5] = size; + args.r[6] = source.data64[0]; + args.r[7] = source.data64[1]; + svc::CallSecureMonitor(std::addressof(args)); + + return static_cast<Result>(args.r[0]); + } + + Result DecryptDeviceUniqueData(size_t *out_size, void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::DecryptDeviceUniqueData); + args.r[1] = access_key.data64[0]; + args.r[2] = access_key.data64[1]; + args.r[3] = option; + args.r[4] = reinterpret_cast<u64>(data); + args.r[5] = size; + args.r[6] = source.data64[0]; + args.r[7] = source.data64[1]; + svc::CallSecureMonitor(std::addressof(args)); + + *out_size = static_cast<size_t>(args.r[1]); + return static_cast<Result>(args.r[0]); + } + + Result DecryptAndStoreGcKey(const void *data, size_t size, const AccessKey &access_key, const KeySource &source, u32 option) { + svc::SecureMonitorArguments args; + + args.r[0] = static_cast<u64>(FunctionId::DecryptAndStoreGcKey); + args.r[1] = access_key.data64[0]; + args.r[2] = access_key.data64[1]; + args.r[3] = option; + args.r[4] = reinterpret_cast<u64>(data); + args.r[5] = size; + args.r[6] = source.data64[0]; + args.r[7] = source.data64[1]; + svc::CallSecureMonitor(std::addressof(args)); + + return static_cast<Result>(args.r[0]); + } + + /* Atmosphere functions. */ + namespace { + + enum class IramCopyDirection { + FromIram = 0, + ToIram = 1, + }; + + inline Result AtmosphereIramCopy(uintptr_t dram_address, uintptr_t iram_address, size_t size, IramCopyDirection direction) { + svc::SecureMonitorArguments args; + args.r[0] = static_cast<u64>(FunctionId::AtmosphereIramCopy); + args.r[1] = dram_address; + args.r[2] = iram_address; + args.r[3] = size; + args.r[4] = static_cast<u64>(direction); + svc::CallSecureMonitor(std::addressof(args)); + + return static_cast<Result>(args.r[0]); + } + + } + + Result AtmosphereCopyToIram(uintptr_t iram_dst, const void *dram_src, size_t size) { + return AtmosphereIramCopy(reinterpret_cast<uintptr_t>(dram_src), iram_dst, size, IramCopyDirection::ToIram); + } + + Result AtmosphereCopyFromIram(void *dram_dst, uintptr_t iram_src, size_t size) { + return AtmosphereIramCopy(reinterpret_cast<uintptr_t>(dram_dst), iram_src, size, IramCopyDirection::FromIram); + } + + Result AtmosphereReadWriteRegister(uint64_t address, uint32_t mask, uint32_t value, uint32_t *out_value) { + svc::SecureMonitorArguments args; + args.r[0] = static_cast<u64>(FunctionId::AtmosphereReadWriteRegister); + args.r[1] = address; + args.r[2] = mask; + args.r[3] = value; + svc::CallSecureMonitor(std::addressof(args)); + + *out_value = static_cast<uint32_t>(args.r[1]); + return static_cast<Result>(args.r[0]); + } + + Result AtmosphereGetEmummcConfig(void *out_config, void *out_paths, u32 storage_id) { + const u64 paths = reinterpret_cast<u64>(out_paths); + AMS_ABORT_UNLESS(util::IsAligned(paths, os::MemoryPageSize)); + + svc::SecureMonitorArguments args = {}; + args.r[0] = static_cast<u64>(FunctionId::AtmosphereGetEmummcConfig); + args.r[1] = storage_id; + args.r[2] = paths; + svc::CallSecureMonitor(std::addressof(args)); + + std::memcpy(out_config, std::addressof(args.r[1]), sizeof(args) - sizeof(args.r[0])); + return static_cast<Result>(args.r[0]); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/spl_api.os.generic.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/spl_api.os.generic.cpp new file mode 100644 index 00000000..326731eb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/spl_api.os.generic.cpp @@ -0,0 +1,135 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::spl { + + namespace { + + bool InitializeImpl() { + /* Initialize implementation api. */ + impl::Initialize(); + return true; + } + + void EnsureInitialized() { + AMS_FUNCTION_LOCAL_STATIC(bool, s_initialized, InitializeImpl()); + AMS_ABORT_UNLESS(s_initialized); + } + + Result WaitAvailableKeySlotAndExecute(auto f) { + os::SystemEvent *event = nullptr; + while (true) { + R_TRY_CATCH(static_cast<::ams::Result>(f())) { + R_CATCH(spl::ResultNoAvailableKeySlot) { + if (event == nullptr) { + event = impl::GetAesKeySlotAvailableEvent(); + } + event->Wait(); + continue; + } + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + } + + } + + Result AllocateAesKeySlot(s32 *out_slot) { + EnsureInitialized(); + + R_RETURN(WaitAvailableKeySlotAndExecute([&]() -> Result { + R_RETURN(impl::AllocateAesKeySlot(out_slot)); + })); + } + + Result DeallocateAesKeySlot(s32 slot) { + EnsureInitialized(); + + R_RETURN(impl::DeallocateAesKeySlot(slot)); + } + + Result GenerateAesKek(AccessKey *out_access_key, const void *key_source, size_t key_source_size, s32 generation, u32 option) { + EnsureInitialized(); + + /* Check key size (assumed valid). */ + AMS_ASSERT(key_source_size == sizeof(KeySource)); + AMS_UNUSED(key_source_size); + + /* AccessKey *out_access_key, const KeySource &key_source, u32 generation, u32 option */ + R_RETURN(impl::GenerateAesKek(out_access_key, *static_cast<const KeySource *>(key_source), generation, option)); + } + + Result LoadAesKey(s32 slot, const AccessKey &access_key, const void *key_source, size_t key_source_size) { + EnsureInitialized(); + + AMS_ASSERT(key_source_size == sizeof(KeySource)); + AMS_UNUSED(key_source_size); + + R_RETURN(impl::LoadAesKey(slot, access_key, *static_cast<const KeySource *>(key_source))); + } + + Result GenerateAesKey(void *dst, size_t dst_size, const AccessKey &access_key, const void *key_source, size_t key_source_size) { + EnsureInitialized(); + + AMS_ASSERT(dst_size >= sizeof(AesKey)); + AMS_ASSERT(key_source_size == sizeof(KeySource)); + AMS_UNUSED(dst_size, key_source_size); + + R_RETURN(WaitAvailableKeySlotAndExecute([&]() -> Result { + R_RETURN(impl::GenerateAesKey(static_cast<AesKey *>(dst), access_key, *static_cast<const KeySource *>(key_source))); + })); + } + + Result ComputeCtr(void *dst, size_t dst_size, s32 slot, const void *src, size_t src_size, const void *iv, size_t iv_size) { + EnsureInitialized(); + + AMS_ASSERT(iv_size >= sizeof(IvCtr)); + AMS_UNUSED(iv_size); + AMS_ASSERT(dst_size >= src_size); + + R_RETURN(impl::ComputeCtr(dst, dst_size, slot, src, src_size, *static_cast<const IvCtr *>(iv))); + } + + Result DecryptAesKey(void *dst, size_t dst_size, const void *key_source, size_t key_source_size, s32 generation, u32 option) { + EnsureInitialized(); + + AMS_ASSERT(dst_size >= crypto::AesEncryptor128::KeySize); + AMS_ASSERT(key_source_size == sizeof(KeySource)); + AMS_UNUSED(dst_size, key_source_size); + + R_RETURN(WaitAvailableKeySlotAndExecute([&]() -> Result { + R_RETURN(impl::DecryptAesKey(static_cast<AesKey *>(dst), *static_cast<const KeySource *>(key_source), static_cast<u32>(generation), option)); + })); + } + + Result LoadPreparedAesKey(s32 slot, const AccessKey &access_key) { + EnsureInitialized(); + + R_RETURN(impl::LoadPreparedAesKey(slot, access_key)); + } + + Result PrepareCommonEsTitleKey(AccessKey *out, const void *key_source, const size_t key_source_size, int generation) { + EnsureInitialized(); + + AMS_ASSERT(key_source_size == sizeof(KeySource)); + AMS_UNUSED(key_source_size); + + R_RETURN(impl::PrepareCommonEsTitleKey(out, *static_cast<const KeySource *>(key_source), generation)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/spl_api.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/spl_api.os.horizon.cpp new file mode 100644 index 00000000..2e7da9e9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/spl/spl_api.os.horizon.cpp @@ -0,0 +1,298 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::spl { + + namespace { + + enum class InitializeMode { + None, + General, + Crypto, + Ssl, + Es, + Fs, + Manu + }; + + constinit os::SdkMutex g_mutex; + constinit s32 g_initialize_count = 0; + constinit InitializeMode g_initialize_mode = InitializeMode::None; + + Result AllocateAesKeySlotImpl(s32 *out) { + R_RETURN(serviceDispatchOut(splCryptoGetServiceSession(), 21, *out)); + } + + Result DeallocateAesKeySlotImpl(s32 slot) { + R_RETURN(serviceDispatchIn(splCryptoGetServiceSession(), 22, slot)); + } + + Result GetAesKeySlotAvailableEventImpl(os::NativeHandle *out) { + R_RETURN(serviceDispatch(splCryptoGetServiceSession(), 23, + .out_handle_attrs = { SfOutHandleAttr_HipcCopy }, + .out_handles = out, + )); + } + + void GetAesKeySlotAvailableEvent(os::SystemEvent *out) { + /* Get event handle. */ + os::NativeHandle handle; + R_ABORT_UNLESS(GetAesKeySlotAvailableEventImpl(std::addressof(handle))); + + /* Attach to event. */ + out->AttachReadableHandle(handle, true, os::EventClearMode_ManualClear); + } + + template<typename F> + Result WaitAvailableKeySlotAndExecute(F f) { + os::SystemEvent event; + auto is_event_initialized = false; + while (true) { + R_TRY_CATCH(static_cast<::ams::Result>(f())) { + R_CATCH(spl::ResultNoAvailableKeySlot) { + if (!is_event_initialized) { + GetAesKeySlotAvailableEvent(std::addressof(event)); + is_event_initialized = true; + } + event.Wait(); + continue; + } + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + } + + template<typename F> + void Initialize(InitializeMode mode, F f) { + std::scoped_lock lk(g_mutex); + + AMS_ASSERT(g_initialize_count >= 0); + AMS_ABORT_UNLESS(mode != InitializeMode::None); + + if (g_initialize_count == 0) { + AMS_ABORT_UNLESS(g_initialize_mode == InitializeMode::None); + f(); + g_initialize_mode = mode; + } else { + AMS_ABORT_UNLESS(g_initialize_mode == mode); + } + + ++g_initialize_count; + } + + } + + void Initialize() { + return Initialize(InitializeMode::General, [&]() { + R_ABORT_UNLESS(splInitialize()); + }); + } + + void InitializeForCrypto() { + return Initialize(InitializeMode::Crypto, [&]() { + R_ABORT_UNLESS(splCryptoInitialize()); + }); + } + + void InitializeForSsl() { + return Initialize(InitializeMode::Ssl, [&]() { + R_ABORT_UNLESS(splSslInitialize()); + }); + } + + void InitializeForEs() { + return Initialize(InitializeMode::Es, [&]() { + R_ABORT_UNLESS(splEsInitialize()); + }); + } + + void InitializeForFs() { + return Initialize(InitializeMode::Fs, [&]() { + R_ABORT_UNLESS(splFsInitialize()); + }); + } + + void InitializeForManu() { + return Initialize(InitializeMode::Manu, [&]() { + R_ABORT_UNLESS(splManuInitialize()); + }); + } + + void Finalize() { + std::scoped_lock lk(g_mutex); + AMS_ASSERT(g_initialize_count > 0); + AMS_ABORT_UNLESS(g_initialize_mode != InitializeMode::None); + + if ((--g_initialize_count) == 0) { + switch (g_initialize_mode) { + case InitializeMode::General: splExit(); break; + case InitializeMode::Crypto: splCryptoExit(); break; + case InitializeMode::Ssl: splSslExit(); break; + case InitializeMode::Es: splEsExit(); break; + case InitializeMode::Fs: splFsExit(); break; + case InitializeMode::Manu: splManuExit(); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + g_initialize_mode = InitializeMode::None; + } + } + + Result AllocateAesKeySlot(s32 *out_slot) { + R_RETURN(WaitAvailableKeySlotAndExecute([&]() -> Result { + R_RETURN(AllocateAesKeySlotImpl(out_slot)); + })); + } + + Result DeallocateAesKeySlot(s32 slot) { + R_RETURN(DeallocateAesKeySlotImpl(slot)); + } + + Result GenerateAesKek(AccessKey *access_key, const void *key_source, size_t key_source_size, s32 generation, u32 option) { + AMS_ASSERT(key_source_size == sizeof(KeySource)); + AMS_UNUSED(key_source_size); + + R_RETURN(splCryptoGenerateAesKek(key_source, generation, option, static_cast<void *>(access_key))); + } + + Result LoadAesKey(s32 slot, const AccessKey &access_key, const void *key_source, size_t key_source_size) { + AMS_ASSERT(key_source_size == sizeof(KeySource)); + AMS_UNUSED(key_source_size); + + R_RETURN(splCryptoLoadAesKey(std::addressof(access_key), key_source, static_cast<u32>(slot))); + } + + Result GenerateAesKey(void *dst, size_t dst_size, const AccessKey &access_key, const void *key_source, size_t key_source_size) { + AMS_ASSERT(dst_size >= crypto::AesEncryptor128::KeySize); + AMS_ASSERT(key_source_size == sizeof(KeySource)); + AMS_UNUSED(dst_size, key_source_size); + + R_RETURN(WaitAvailableKeySlotAndExecute([&]() -> Result { + R_RETURN(splCryptoGenerateAesKey(std::addressof(access_key), key_source, dst)); + })); + } + + Result GenerateSpecificAesKey(void *dst, size_t dst_size, const void *key_source, size_t key_source_size, s32 generation, u32 option) { + AMS_ASSERT(dst_size >= crypto::AesEncryptor128::KeySize); + AMS_ASSERT(key_source_size == sizeof(KeySource)); + AMS_UNUSED(dst_size, key_source_size); + + R_RETURN(splFsGenerateSpecificAesKey(key_source, static_cast<u32>(generation), option, dst)); + } + + Result ComputeCtr(void *dst, size_t dst_size, s32 slot, const void *src, size_t src_size, const void *iv, size_t iv_size) { + AMS_ASSERT(iv_size >= 0x10); + AMS_ASSERT(dst_size >= src_size); + AMS_UNUSED(dst_size, iv_size); + + R_RETURN(splCryptoCryptAesCtr(src, dst, src_size, static_cast<s32>(slot), iv)); + } + + Result DecryptAesKey(void *dst, size_t dst_size, const void *key_source, size_t key_source_size, s32 generation, u32 option) { + AMS_ASSERT(dst_size >= crypto::AesEncryptor128::KeySize); + AMS_ASSERT(key_source_size == sizeof(KeySource)); + AMS_UNUSED(dst_size, key_source_size); + + R_RETURN(WaitAvailableKeySlotAndExecute([&]() -> Result { + R_RETURN(splCryptoDecryptAesKey(key_source, static_cast<u32>(generation), option, dst)); + })); + } + + Result GetConfig(u64 *out, ConfigItem item) { + R_RETURN(splGetConfig(static_cast<::SplConfigItem>(item), out)); + } + + Result SetConfig(ConfigItem item, u64 v) { + R_RETURN(splSetConfig(static_cast<::SplConfigItem>(item), v)); + } + + bool IsDevelopment() { + bool is_dev; + R_ABORT_UNLESS(splIsDevelopment(std::addressof(is_dev))); + return is_dev; + } + + MemoryArrangement GetMemoryArrangement() { + u64 mode = 0; + R_ABORT_UNLESS(spl::GetConfig(std::addressof(mode), spl::ConfigItem::MemoryMode)); + switch (mode & 0x3F) { + case 2: + return MemoryArrangement_StandardForAppletDev; + case 3: + return MemoryArrangement_StandardForSystemDev; + case 17: + return MemoryArrangement_Expanded; + case 18: + return MemoryArrangement_ExpandedForAppletDev; + default: + return MemoryArrangement_Standard; + } + } + + Result SetBootReason(BootReasonValue boot_reason) { + static_assert(sizeof(boot_reason) == sizeof(u32)); + + u32 v; + std::memcpy(std::addressof(v), std::addressof(boot_reason), sizeof(v)); + + R_RETURN(splSetBootReason(v)); + } + + Result GetBootReason(BootReasonValue *out) { + static_assert(sizeof(*out) == sizeof(u32)); + + u32 v; + R_TRY(splGetBootReason(std::addressof(v))); + + std::memcpy(out, std::addressof(v), sizeof(*out)); + R_SUCCEED(); + } + + SocType GetSocType() { + switch (GetHardwareType()) { + case HardwareType::Icosa: + case HardwareType::Copper: + return SocType_Erista; + case HardwareType::Hoag: + case HardwareType::Iowa: + case HardwareType::Aula: + return SocType_Mariko; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + Result GetPackage2Hash(void *dst, size_t dst_size) { + AMS_ASSERT(dst_size >= crypto::Sha256Generator::HashSize); + AMS_UNUSED(dst_size); + R_RETURN(splFsGetPackage2Hash(dst)); + } + + Result GenerateRandomBytes(void *out, size_t buffer_size) { + R_RETURN(splGetRandomBytes(out, buffer_size)); + } + + Result LoadPreparedAesKey(s32 slot, const AccessKey &access_key) { + if (g_initialize_mode == InitializeMode::Fs) { + R_RETURN(splFsLoadTitlekey(std::addressof(access_key), static_cast<u32>(slot))); + } else { + /* TODO: libnx binding not available. */ + /* R_RETURN(splEsLoadTitlekey(std::addressof(access_key), static_cast<u32>(slot))); */ + AMS_ABORT_UNLESS(false); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_api.cpp new file mode 100644 index 00000000..d76d4122 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_api.cpp @@ -0,0 +1,128 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sprofile_srv_profile_manager.hpp" +#include "sprofile_srv_service_for_bg_agent.hpp" +#include "sprofile_srv_service_for_system_process.hpp" +#include "sprofile_srv_service_getter.hpp" + +namespace ams::sprofile::srv { + + namespace { + + constexpr const ProfileManager::SaveDataInfo SaveDataInfo = { + .id = 0x8000000000000220, + .mount_name = "sprof", + .size = 0x1C0000, + .journal_size = 0x80000, + .flags = fs::SaveDataFlags_KeepAfterResettingSystemSaveData, + }; + + constexpr const sm::ServiceName ServiceNameForBgAgent = sm::ServiceName::Encode("sprof:bg"); + constexpr const sm::ServiceName ServiceNameForSystemProcess = sm::ServiceName::Encode("sprof:sp"); + + constexpr inline size_t BgAgentSessionCountMax = 2; + constexpr inline size_t SystemProcessSessionCountMax = 10; + + constexpr inline size_t SessionCountMax = BgAgentSessionCountMax + SystemProcessSessionCountMax; + + constexpr inline size_t PortCountMax = 2; + + struct ServerManagerOptions { + static constexpr size_t PointerBufferSize = 0x0; + static constexpr size_t MaxDomains = SessionCountMax; /* NOTE: Official is 9 */ + static constexpr size_t MaxDomainObjects = 16; /* NOTE: Official is 14 */ + static constexpr bool CanDeferInvokeRequest = false; + static constexpr bool CanManageMitmServers = false; + }; + + using ServerManager = sf::hipc::ServerManager<PortCountMax, ServerManagerOptions, SessionCountMax>; + + constinit util::TypedStorage<ProfileManager> g_profile_manager = {}; + + constinit util::TypedStorage<sf::UnmanagedServiceObject<ISprofileServiceForBgAgent, ServiceForBgAgent>> g_bg_service_object = {}; + constinit util::TypedStorage<sf::UnmanagedServiceObject<ISprofileServiceForSystemProcess, ServiceForSystemProcess>> g_sp_service_object = {}; + + constinit util::TypedStorage<sf::UnmanagedServiceObject<IServiceGetter, ServiceGetter>> g_bg_service_getter = {}; + constinit util::TypedStorage<sf::UnmanagedServiceObject<IServiceGetter, ServiceGetter>> g_sp_service_getter = {}; + + constinit util::TypedStorage<ServerManager> g_server_manager = {}; + + alignas(os::ThreadStackAlignment) constinit u8 g_ipc_thread_stack[0x3000]; + constinit u8 g_heap[16_KB]; + + constinit os::ThreadType g_ipc_thread = {}; + + constinit lmem::HeapHandle g_heap_handle = nullptr; + constinit sf::ExpHeapMemoryResource g_sf_memory_resource; + + void IpcServerThreadFunction(void *) { + /* Get the server manager. */ + auto &server_manager = util::GetReference(g_server_manager); + + /* Resume processing. */ + server_manager.ResumeProcessing(); + + /* Loop processing. */ + server_manager.LoopProcess(); + } + + } + + void Initialize() { + /* Initialize heap. */ + g_heap_handle = lmem::CreateExpHeap(g_heap, sizeof(g_heap), lmem::CreateOption_ThreadSafe); + + /* Attach the memory resource to heap. */ + g_sf_memory_resource.Attach(g_heap_handle); + + /* Create the profile manager. */ + util::ConstructAt(g_profile_manager, SaveDataInfo); + + /* Process profile manager savedata. */ + util::GetReference(g_profile_manager).InitializeSaveData(); + + /* Create the service objects. */ + util::ConstructAt(g_bg_service_object, std::addressof(g_sf_memory_resource), util::GetPointer(g_profile_manager)); + util::ConstructAt(g_sp_service_object, std::addressof(g_sf_memory_resource), util::GetPointer(g_profile_manager)); + + /* Create the service getters. */ + util::ConstructAt(g_bg_service_getter, util::GetReference(g_bg_service_object).GetShared(), util::GetReference(g_sp_service_object).GetShared()); + util::ConstructAt(g_sp_service_getter, nullptr, util::GetReference(g_sp_service_object).GetShared()); + + /* Create the server manager. */ + util::ConstructAt(g_server_manager); + + /* Create services. */ + if (hos::GetVersion() >= hos::Version_14_0_0) { + R_ABORT_UNLESS(util::GetReference(g_server_manager).RegisterObjectForServer(util::GetReference(g_bg_service_getter).GetShared(), ServiceNameForBgAgent, BgAgentSessionCountMax)); + R_ABORT_UNLESS(util::GetReference(g_server_manager).RegisterObjectForServer(util::GetReference(g_sp_service_getter).GetShared(), ServiceNameForSystemProcess, SystemProcessSessionCountMax)); + } else { + R_ABORT_UNLESS(util::GetReference(g_server_manager).RegisterObjectForServer(util::GetReference(g_bg_service_object).GetShared(), ServiceNameForBgAgent, BgAgentSessionCountMax)); + R_ABORT_UNLESS(util::GetReference(g_server_manager).RegisterObjectForServer(util::GetReference(g_sp_service_object).GetShared(), ServiceNameForSystemProcess, SystemProcessSessionCountMax)); + } + } + + void StartIpcServer() { + /* Create the ipc server thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(g_ipc_thread), IpcServerThreadFunction, nullptr, g_ipc_thread_stack, sizeof(g_ipc_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(sprofile, IpcServer))); + os::SetThreadNamePointer(std::addressof(g_ipc_thread), AMS_GET_SYSTEM_THREAD_NAME(sprofile, IpcServer)); + + /* Start the ipc server thread. */ + os::StartThread(std::addressof(g_ipc_thread)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_fs_utils.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_fs_utils.cpp new file mode 100644 index 00000000..225d9d03 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_fs_utils.cpp @@ -0,0 +1,92 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sprofile_srv_profile_manager.hpp" + +namespace ams::sprofile::srv { + + Result ReadFile(const char *path, void *dst, size_t size, s64 offset) { + /* Open the file. */ + fs::FileHandle file; + R_TRY_CATCH(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Read)) { + R_CATCH_RETHROW(fs::ResultPathNotFound) /* It's okay if the file doesn't exist. */ + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Read the file. */ + size_t read_size; + R_TRY(fs::ReadFile(std::addressof(read_size), file, offset, dst, size)); + + /* Check the size was correct. */ + AMS_ABORT_UNLESS(size == read_size); + + R_SUCCEED(); + } + + Result WriteFile(const char *path, const void *src, size_t size) { + /* Create the file. */ + R_TRY_CATCH(fs::CreateFile(path, size)) { + R_CATCH(fs::ResultPathAlreadyExists) { /* It's okay if the file already exists. */ } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + /* Open the file. */ + fs::FileHandle file; + R_ABORT_UNLESS(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Write)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Set the file size. */ + R_ABORT_UNLESS(fs::SetFileSize(file, size)); + + /* Write the file. */ + R_RETURN(fs::WriteFile(file, 0, src, size, fs::WriteOption::Flush)); + } + + Result MoveFile(const char *src_path, const char *dst_path) { + /* Require that the source path is a file. */ + { + fs::DirectoryEntryType type; + R_ABORT_UNLESS(fs::GetEntryType(std::addressof(type), src_path)); + AMS_ABORT_UNLESS(type == fs::DirectoryEntryType_File); + } + + /* Delete the destination file. */ + R_TRY_CATCH(fs::DeleteFile(dst_path)) { + R_CATCH(fs::ResultPathNotFound) { /* It's okay if the dst path doesn't exist. */ } + } R_END_TRY_CATCH; + + /* Move the source file to the destination file. */ + R_TRY(fs::RenameFile(src_path, dst_path)); + + R_SUCCEED(); + } + + Result DeleteFile(const char *path) { + R_TRY_CATCH(fs::DeleteFile(path)) { + R_CATCH(fs::ResultPathNotFound) { /* It's okay if the file doesn't exist. */ } + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + Result EnsureDirectory(const char *path) { + R_TRY_CATCH(fs::CreateDirectory(path)) { + R_CATCH(fs::ResultPathAlreadyExists) { /* It's okay if the directory already exists. */ } + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_fs_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_fs_utils.hpp new file mode 100644 index 00000000..a14be750 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_fs_utils.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::sprofile::srv { + + Result ReadFile(const char *path, void *dst, size_t size, s64 offset); + Result WriteFile(const char *path, const void *src, size_t size); + Result MoveFile(const char *src_path, const char *dst_path); + Result DeleteFile(const char *path); + + Result EnsureDirectory(const char *path); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_profile_controller_for_debug.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_profile_controller_for_debug.hpp new file mode 100644 index 00000000..7d51c7df --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_profile_controller_for_debug.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#define AMS_SPROFILE_I_PROFILE_CONTROLLER_FOR_DEBUG_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 2000, Result, Reset, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 2001, Result, GetRaw, (sf::Out<u8> out_type, sf::Out<u64> out_value, sprofile::Identifier profile, sprofile::Identifier key), (out_type, out_value, profile, key)) + + +AMS_SF_DEFINE_INTERFACE(ams::sprofile::srv, IProfileControllerForDebug, AMS_SPROFILE_I_PROFILE_CONTROLLER_FOR_DEBUG_INTERFACE_INFO, 0xA8C14F64) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_profile_importer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_profile_importer.hpp new file mode 100644 index 00000000..8d0c2b32 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_profile_importer.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "sprofile_srv_types.hpp" + +#define AMS_SPROFILE_I_PROFILE_IMPORTER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, ImportProfile, (const sprofile::srv::ProfileDataForImportData &import), (import)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, Commit, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, ImportMetadata, (const sprofile::srv::ProfileMetadataForImportMetadata &import), (import)) \ + +AMS_SF_DEFINE_INTERFACE(ams::sprofile::srv, IProfileImporter, AMS_SPROFILE_I_PROFILE_IMPORTER_INTERFACE_INFO, 0x1629C4E6) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_profile_reader.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_profile_reader.hpp new file mode 100644 index 00000000..e9a48faa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_profile_reader.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#define AMS_SPROFILE_I_PROFILE_READER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GetSigned64, (sf::Out<s64> out, sprofile::Identifier profile, sprofile::Identifier key), (out, profile, key)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, GetUnsigned64, (sf::Out<u64> out, sprofile::Identifier profile, sprofile::Identifier key), (out, profile, key)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, GetSigned32, (sf::Out<s32> out, sprofile::Identifier profile, sprofile::Identifier key), (out, profile, key)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, GetUnsigned32, (sf::Out<u32> out, sprofile::Identifier profile, sprofile::Identifier key), (out, profile, key)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, GetByte, (sf::Out<u8> out, sprofile::Identifier profile, sprofile::Identifier key), (out, profile, key)) + +AMS_SF_DEFINE_INTERFACE(ams::sprofile::srv, IProfileReader, AMS_SPROFILE_I_PROFILE_READER_INTERFACE_INFO, 0x97090D4D) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_profile_update_observer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_profile_update_observer.hpp new file mode 100644 index 00000000..ab68ad3b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_profile_update_observer.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#define AMS_SPROFILE_I_PROFILE_UPDATE_OBSERVER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, Listen, (sprofile::Identifier profile), (profile)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, Unlisten, (sprofile::Identifier profile), (profile)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, GetEventHandle, (ams::sf::OutCopyHandle out), (out)) + +AMS_SF_DEFINE_INTERFACE(ams::sprofile::srv, IProfileUpdateObserver, AMS_SPROFILE_I_PROFILE_UPDATE_OBSERVER_INTERFACE_INFO, 0xB52A765C) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_service_for_bg_agent.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_service_for_bg_agent.hpp new file mode 100644 index 00000000..5edce1d7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_service_for_bg_agent.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "sprofile_srv_i_profile_importer.hpp" + +#define AMS_SPROFILE_I_SPROFILE_SERVICE_FOR_BG_AGENT_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 100, Result, OpenProfileImporter, (sf::Out<sf::SharedPointer<::ams::sprofile::srv::IProfileImporter>> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 200, Result, GetImportableProfileUrls, (sf::Out<u32> out_count, const sf::OutArray<sprofile::srv::ProfileUrl> &out, const sprofile::srv::ProfileMetadataForImportMetadata &arg), (out_count, out, arg)) \ + AMS_SF_METHOD_INFO(C, H, 201, Result, IsUpdateNeeded, (sf::Out<bool> out, sprofile::Identifier revision_key), (out, revision_key)) \ + AMS_SF_METHOD_INFO(C, H, 2000, Result, Reset, (), ()) + +AMS_SF_DEFINE_INTERFACE(ams::sprofile::srv, ISprofileServiceForBgAgent, AMS_SPROFILE_I_SPROFILE_SERVICE_FOR_BG_AGENT_INTERFACE_INFO, 0xCCD828EC) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_service_for_system_process.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_service_for_system_process.hpp new file mode 100644 index 00000000..d05e76c4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_service_for_system_process.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "sprofile_srv_i_profile_reader.hpp" +#include "sprofile_srv_i_profile_update_observer.hpp" +#include "sprofile_srv_i_profile_controller_for_debug.hpp" + +#define AMS_SPROFILE_I_SPROFILE_SERVICE_FOR_SYSTEM_PROCESS_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 100, Result, OpenProfileReader, (sf::Out<sf::SharedPointer<::ams::sprofile::srv::IProfileReader>> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 101, Result, OpenProfileUpdateObserver, (sf::Out<sf::SharedPointer<::ams::sprofile::srv::IProfileUpdateObserver>> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 900, Result, OpenProfileControllerForDebug, (sf::Out<sf::SharedPointer<::ams::sprofile::srv::IProfileControllerForDebug>> out), (out)) + +AMS_SF_DEFINE_INTERFACE(ams::sprofile::srv, ISprofileServiceForSystemProcess, AMS_SPROFILE_I_SPROFILE_SERVICE_FOR_SYSTEM_PROCESS_INTERFACE_INFO, 0x919612FB) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_service_getter.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_service_getter.hpp new file mode 100644 index 00000000..ce2f0652 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_i_service_getter.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "sprofile_srv_i_service_for_system_process.hpp" +#include "sprofile_srv_i_service_for_bg_agent.hpp" + +#define AMS_SPROFILE_I_SERVICE_GETTER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GetServiceForSystemProcess, (sf::Out<sf::SharedPointer<sprofile::srv::ISprofileServiceForSystemProcess>> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, GetServiceForBgAgent, (sf::Out<sf::SharedPointer<sprofile::srv::ISprofileServiceForBgAgent>> out), (out)) + +AMS_SF_DEFINE_INTERFACE(ams::sprofile::srv, IServiceGetter, AMS_SPROFILE_I_SERVICE_GETTER_INTERFACE_INFO, 0x2CFB8417) \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_controller_for_debug_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_controller_for_debug_impl.cpp new file mode 100644 index 00000000..1bf965f2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_controller_for_debug_impl.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sprofile_srv_profile_manager.hpp" +#include "sprofile_srv_profile_controller_for_debug_impl.hpp" + +namespace ams::sprofile::srv { + + Result ProfileControllerForDebugImpl::Reset() { + R_RETURN(m_manager->ResetSaveData()); + } + + Result ProfileControllerForDebugImpl::GetRaw(sf::Out<u8> out_type, sf::Out<u64> out_value, sprofile::Identifier profile, sprofile::Identifier key) { + R_RETURN(m_manager->GetRaw(out_type.GetPointer(), out_value.GetPointer(), profile, key)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_controller_for_debug_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_controller_for_debug_impl.hpp new file mode 100644 index 00000000..edb9d9eb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_controller_for_debug_impl.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "sprofile_srv_i_profile_controller_for_debug.hpp" + +namespace ams::sprofile::srv { + + class ProfileManager; + + class ProfileControllerForDebugImpl { + private: + ProfileManager *m_manager; + public: + ProfileControllerForDebugImpl(ProfileManager *manager) : m_manager(manager) { /* ... */ } + public: + Result Reset(); + Result GetRaw(sf::Out<u8> out_type, sf::Out<u64> out_value, sprofile::Identifier profile, sprofile::Identifier key); + }; + static_assert(IsIProfileControllerForDebug<ProfileControllerForDebugImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_importer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_importer.hpp new file mode 100644 index 00000000..20df0fbe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_importer.hpp @@ -0,0 +1,167 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "sprofile_srv_types.hpp" + +namespace ams::sprofile::srv { + + class ProfileImporter { + private: + struct ImportingProfile { + Identifier identifier_0; + Identifier identifier_1; + bool is_new_import; + }; + private: + bool m_committed; + bool m_imported_metadata; + int m_importing_count; + util::optional<ProfileMetadata> m_metadata; + ImportingProfile m_importing_profiles[50]; + util::BitFlagSet<50> m_is_profile_importable; + Identifier m_revision_key; + public: + ProfileImporter(const util::optional<ProfileMetadata> &meta) : m_committed(false), m_imported_metadata(false), m_importing_count(0), m_metadata(util::nullopt), m_is_profile_importable(), m_revision_key() { + if (meta.has_value()) { + m_metadata = *meta; + } + } + public: + bool HasProfile(Identifier id0, Identifier id1) { + /* Require that we have metadata. */ + if (m_metadata.has_value()) { + for (auto i = 0u; i < std::min<size_t>(m_metadata->num_entries, util::size(m_metadata->entries)); ++i) { + const auto &entry = m_metadata->entries[i]; + if (entry.identifier_0 == id0 && entry.identifier_1 == id1) { + return true; + } + } + } + + /* We don't have the desired profile. */ + return false; + } + + bool CanImportMetadata() { + /* We can only import metadata if we haven't already imported metadata. */ + return !m_imported_metadata; + } + + void ImportMetadata(const ProfileMetadata &meta) { + /* Set that we've imported metadata. */ + m_imported_metadata = true; + + /* Import the service revision key. */ + m_revision_key = meta.revision_key; + + /* Set importing count. */ + m_importing_count = static_cast<int>(std::min<size_t>(meta.num_entries, util::size(meta.entries))); + + /* Set all profiles as importable. */ + for (auto i = 0; i < m_importing_count; ++i) { + m_is_profile_importable[i] = true; + } + + /* Determine import status for all profiles. */ + for (auto i = 0; i < m_importing_count; ++i) { + const auto &import_entry = meta.entries[i]; + + const bool is_new_import = !this->HasProfile(import_entry.identifier_0, import_entry.identifier_1); + + m_importing_profiles[i] = { + .identifier_0 = import_entry.identifier_0, + .identifier_1 = import_entry.identifier_1, + .is_new_import = is_new_import, + }; + m_is_profile_importable[i] = is_new_import; + } + } + + bool CanImportProfile(Identifier profile) { + /* Require that we imported metadata. */ + if (m_imported_metadata) { + /* Find the specified profile. */ + for (auto i = 0; i < m_importing_count; ++i) { + if (m_importing_profiles[i].identifier_0 == profile) { + /* Require the profile be importable. */ + return m_is_profile_importable[i]; + } + } + } + + /* We can't import the desired profile. */ + return false; + } + + void OnImportProfile(Identifier profile) { + /* Set the profile as not importable (as it's imported). */ + for (auto i = 0; i < m_importing_count; ++i) { + if (m_importing_profiles[i].identifier_0 == profile) { + m_is_profile_importable[i] = false; + break; + } + } + } + + bool CanCommit() { + /* We can't commit if we've already committed. */ + if (m_committed) { + return false; + } + + /* We need metadata in order to commit. */ + if (!m_imported_metadata) { + return false; + } + + /* We need to have imported everything we intended to import. */ + return m_is_profile_importable.IsAllOff(); + } + + int GetImportingCount() const { return m_importing_count; } + const ImportingProfile &GetImportingProfile(int i) const { return m_importing_profiles[i]; } + + Identifier GetRevisionKey() const { return m_revision_key; } + + Result CleanupOrphanedProfiles(auto cleanup_impl) const { + /* Cleanup any orphaned profiles in our metadata. */ + if (m_metadata.has_value()) { + for (auto i = 0u; i < std::min<size_t>(m_metadata->num_entries, util::size(m_metadata->entries)); ++i) { + const auto &entry = m_metadata->entries[i]; + if (!this->IsImportingProfile(entry.identifier_0)) { + R_TRY(cleanup_impl(entry.identifier_0)); + } + } + } + + R_SUCCEED(); + } + private: + bool IsImportingProfile(Identifier profile) const { + /* Check if we're importing the desired profile. */ + for (auto i = 0; i < m_importing_count; ++i) { + if (m_importing_profiles[i].identifier_0 == profile) { + return true; + } + } + + /* We're not importing the desired profile. */ + return false; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_importer_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_importer_impl.cpp new file mode 100644 index 00000000..7e64a3a4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_importer_impl.cpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sprofile_srv_profile_manager.hpp" +#include "sprofile_srv_profile_importer_impl.hpp" + +namespace ams::sprofile::srv { + + Result ProfileImporterImpl::ImportProfile(const sprofile::srv::ProfileDataForImportData &import) { + R_RETURN(m_manager->ImportProfile(import)); + } + + Result ProfileImporterImpl::Commit() { + R_RETURN(m_manager->Commit()); + } + + Result ProfileImporterImpl::ImportMetadata(const sprofile::srv::ProfileMetadataForImportMetadata &import) { + R_RETURN(m_manager->ImportMetadata(import)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_importer_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_importer_impl.hpp new file mode 100644 index 00000000..fce94f2b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_importer_impl.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "sprofile_srv_i_profile_importer.hpp" + +namespace ams::sprofile::srv { + + class ProfileManager; + + class ProfileImporterImpl { + private: + ProfileManager *m_manager; + public: + ProfileImporterImpl(ProfileManager *manager) : m_manager(manager) { /* ... */ } + + ~ProfileImporterImpl() { + m_manager->CloseProfileImporter(); + } + public: + Result ImportProfile(const sprofile::srv::ProfileDataForImportData &import); + Result Commit(); + Result ImportMetadata(const sprofile::srv::ProfileMetadataForImportMetadata &import); + }; + static_assert(IsIProfileImporter<ProfileImporterImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_manager.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_manager.cpp new file mode 100644 index 00000000..0167371b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_manager.cpp @@ -0,0 +1,558 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sprofile_srv_profile_manager.hpp" +#include "sprofile_srv_fs_utils.hpp" + +namespace ams::sprofile::srv { + + namespace { + + constexpr const char PrimaryDirectoryName[] = "primary"; + constexpr const char TemporaryDirectoryName[] = "temp"; + + Result CreateSaveData(const ProfileManager::SaveDataInfo &save_data_info) { + R_TRY_CATCH(fs::CreateSystemSaveData(save_data_info.id, save_data_info.size, save_data_info.journal_size, save_data_info.flags)) { + R_CATCH(fs::ResultPathAlreadyExists) { /* Nintendo accepts already-existing savedata here. */ } + } R_END_TRY_CATCH; + R_SUCCEED(); + } + + void SafePrint(char *dst, size_t dst_size, const char *fmt, ...) __attribute__((format(printf, 3, 4))); + + void SafePrint(char *dst, size_t dst_size, const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + const size_t len = util::TVSNPrintf(dst, dst_size, fmt, vl); + va_end(vl); + + AMS_ABORT_UNLESS(len < dst_size); + } + + void CreateMetadataPathImpl(char *dst, size_t dst_size, const char *mount, const char *dir) { + SafePrint(dst, dst_size, "%s:/%s/metadata", mount, dir); + } + + void CreatePrimaryMetadataPath(char *dst, size_t dst_size, const char *mount) { + CreateMetadataPathImpl(dst, dst_size, mount, PrimaryDirectoryName); + } + + void CreateTemporaryMetadataPath(char *dst, size_t dst_size, const char *mount) { + CreateMetadataPathImpl(dst, dst_size, mount, TemporaryDirectoryName); + } + + void CreateProfilePathImpl(char *dst, size_t dst_size, const char *mount, const char *dir, const Identifier &id) { + SafePrint(dst, dst_size, "%s:/%s/profiles/%02x%02x%02x%02x%02x%02x%02x", mount, dir, id.data[0], id.data[1], id.data[2], id.data[3], id.data[4], id.data[5], id.data[6]); + } + + void CreatePrimaryProfilePath(char *dst, size_t dst_size, const char *mount, const Identifier &id) { + CreateProfilePathImpl(dst, dst_size, mount, PrimaryDirectoryName, id); + } + + void CreateTemporaryProfilePath(char *dst, size_t dst_size, const char *mount, const Identifier &id) { + CreateProfilePathImpl(dst, dst_size, mount, TemporaryDirectoryName, id); + } + + void CreateDirectoryPathImpl(char *dst, size_t dst_size, const char *mount, const char *dir) { + SafePrint(dst, dst_size, "%s:/%s", mount, dir); + } + + void CreatePrimaryDirectoryPath(char *dst, size_t dst_size, const char *mount) { + CreateDirectoryPathImpl(dst, dst_size, mount, PrimaryDirectoryName); + } + + void CreateTemporaryDirectoryPath(char *dst, size_t dst_size, const char *mount) { + CreateDirectoryPathImpl(dst, dst_size, mount, TemporaryDirectoryName); + } + + void CreateProfileDirectoryPathImpl(char *dst, size_t dst_size, const char *mount, const char *dir) { + SafePrint(dst, dst_size, "%s:/%s/profiles", mount, dir); + } + + void CreatePrimaryProfileDirectoryPath(char *dst, size_t dst_size, const char *mount) { + CreateProfileDirectoryPathImpl(dst, dst_size, mount, PrimaryDirectoryName); + } + + void CreateTemporaryProfileDirectoryPath(char *dst, size_t dst_size, const char *mount) { + CreateProfileDirectoryPathImpl(dst, dst_size, mount, TemporaryDirectoryName); + } + + } + + ProfileManager::ProfileManager(const SaveDataInfo &save_data_info) + : m_save_data_info(save_data_info), m_save_file_mounted(false), m_profile_importer(util::nullopt), + m_profile_metadata(util::nullopt), m_service_profile(util::nullopt), m_update_observer_manager() + { + /* ... */ + } + + void ProfileManager::InitializeSaveData() { + /* Acquire locks. */ + std::scoped_lock lk1(m_general_mutex); + std::scoped_lock lk2(m_fs_mutex); + + /* Ensure the savedata exists. */ + if (R_SUCCEEDED(CreateSaveData(m_save_data_info))) { + m_save_file_mounted = R_SUCCEEDED(fs::MountSystemSaveData(m_save_data_info.mount_name, m_save_data_info.id)); + } + } + + Result ProfileManager::ResetSaveData() { + /* Acquire locks. */ + std::scoped_lock lk1(m_service_profile_mutex); + std::scoped_lock lk2(m_profile_metadata_mutex); + std::scoped_lock lk3(m_general_mutex); + std::scoped_lock lk4(m_fs_mutex); + + /* Unmount save file. */ + fs::Unmount(m_save_data_info.mount_name); + m_save_file_mounted = false; + + /* Delete save file. */ + R_TRY(fs::DeleteSystemSaveData(fs::SaveDataSpaceId::System, m_save_data_info.id, fs::InvalidUserId)); + + /* Unload profile. */ + m_profile_metadata = util::nullopt; + m_service_profile = util::nullopt; + + /* Create the save data. */ + R_TRY(CreateSaveData(m_save_data_info)); + + /* Try to mount the save file. */ + const auto result = fs::MountSystemSaveData(m_save_data_info.mount_name, m_save_data_info.id); + m_save_file_mounted = R_SUCCEEDED(result); + + R_RETURN(result); + } + + Result ProfileManager::OpenProfileImporter() { + /* Acquire locks. */ + std::scoped_lock lk1(m_profile_metadata_mutex); + std::scoped_lock lk2(m_profile_importer_mutex); + std::scoped_lock lk3(m_general_mutex); + + /* Check that we don't already have an importer. */ + R_UNLESS(!m_profile_importer.has_value(), sprofile::ResultInvalidState()); + + /* Try to load profile metadata. NOTE: result is not checked, it is okay if this fails. */ + this->LoadPrimaryMetadataImpl(); + + /* Create importer. */ + m_profile_importer.emplace(m_profile_metadata); + R_SUCCEED(); + } + + void ProfileManager::CloseProfileImporterImpl() { + /* Check pre-conditions. */ + AMS_ASSERT(m_profile_importer_mutex.IsLockedByCurrentThread()); + AMS_ASSERT(m_general_mutex.IsLockedByCurrentThread()); + AMS_ASSERT(m_fs_mutex.IsLockedByCurrentThread()); + + if (m_profile_importer.has_value()) { + /* Unmount save file. */ + fs::Unmount(m_save_data_info.mount_name); + m_save_file_mounted = false; + + /* Re-mount save file. */ + R_ABORT_UNLESS(fs::MountSystemSaveData(m_save_data_info.mount_name, m_save_data_info.id)); + m_save_file_mounted = true; + + /* Reset our importer. */ + m_profile_importer = util::nullopt; + } + } + + void ProfileManager::CloseProfileImporter() { + /* Acquire locks. */ + std::scoped_lock lk1(m_profile_importer_mutex); + std::scoped_lock lk2(m_general_mutex); + std::scoped_lock lk3(m_fs_mutex); + + /* Close our importer. */ + this->CloseProfileImporterImpl(); + } + + Result ProfileManager::ImportProfile(const sprofile::srv::ProfileDataForImportData &import) { + /* Acquire locks. */ + std::scoped_lock lk1(m_profile_importer_mutex); + std::scoped_lock lk2(m_fs_mutex); + + /* Check that we have an importer. */ + R_UNLESS(m_profile_importer.has_value(), sprofile::ResultInvalidState()); + + /* Check that the metadata we're importing is a valid version. */ + R_UNLESS(IsValidProfileFormatVersion(import.header.version), sprofile::ResultInvalidDataVersion()); + + /* Check that the metadata we're importing has a valid hash. */ + { + crypto::Md5Generator md5; + md5.Initialize(); + + md5.Update(std::addressof(import.header), sizeof(import.header)); + md5.Update(std::addressof(import.data), sizeof(import.data) - sizeof(import.data.entries[0]) * (util::size(import.data.entries) - std::min<size_t>(import.data.num_entries, util::size(import.data.entries)))); + + u8 hash[crypto::Md5Generator::HashSize]; + md5.GetHash(hash, sizeof(hash)); + + R_UNLESS(crypto::IsSameBytes(hash, import.hash, sizeof(hash)), sprofile::ResultInvalidDataHash()); + } + + /* Succeed if we already have the profile. */ + R_SUCCEED_IF(m_profile_importer->HasProfile(import.header.identifier_0, import.header.identifier_1)); + + /* Check that we're importing the profile. */ + R_UNLESS(m_profile_importer->CanImportProfile(import.header.identifier_0), sprofile::ResultInvalidState()); + + /* Create temporary directories. */ + R_TRY(this->EnsureTemporaryDirectories()); + + /* Create profile. */ + char path[0x30]; + CreateTemporaryProfilePath(path, sizeof(path), m_save_data_info.mount_name, import.header.identifier_0); + R_TRY(WriteFile(path, std::addressof(import.data), sizeof(import.data))); + + /* Set profile imported. */ + m_profile_importer->OnImportProfile(import.header.identifier_0); + R_SUCCEED(); + } + + Result ProfileManager::Commit() { + /* Acquire locks. */ + std::scoped_lock lk1(m_service_profile_mutex); + std::scoped_lock lk2(m_profile_metadata_mutex); + std::scoped_lock lk3(m_profile_importer_mutex); + std::scoped_lock lk4(m_general_mutex); + std::scoped_lock lk5(m_fs_mutex); + + /* Check that we have an importer. */ + R_UNLESS(m_profile_importer.has_value(), sprofile::ResultInvalidState()); + + /* Commit, and if we fail remount our save. */ + { + /* If we fail, close our importer. */ + ON_RESULT_FAILURE { this->CloseProfileImporterImpl(); }; + + /* Check that we can commit the importer. */ + R_UNLESS(m_profile_importer->CanCommit(), sprofile::ResultInvalidState()); + + /* Commit newly imported profiles. */ + R_TRY(this->CommitImportedProfiles()); + + /* Cleanup orphaned profiles. */ + R_TRY(this->CleanupOrphanedProfiles()); + + /* Commit the save file. */ + R_TRY(fs::CommitSaveData(m_save_data_info.mount_name)); + } + + /* NOTE: Here nintendo generates an "sprofile_update_profile" sreport with the new and old revision keys. */ + + /* Handle tasks for when we've committed (including notifying update observers). */ + this->OnCommitted(); + + R_SUCCEED(); + } + + Result ProfileManager::ImportMetadata(const sprofile::srv::ProfileMetadataForImportMetadata &import) { + /* Acquire locks. */ + std::scoped_lock lk1(m_profile_importer_mutex); + std::scoped_lock lk2(m_fs_mutex); + + /* Check that we can import metadata. */ + R_UNLESS(m_profile_importer.has_value(), sprofile::ResultInvalidState()); + R_UNLESS(m_profile_importer->CanImportMetadata(), sprofile::ResultInvalidState()); + + /* Check that the metadata we're importing is a valid version. */ + R_UNLESS(IsValidProfileFormatVersion(import.header.version), sprofile::ResultInvalidMetadataVersion()); + + /* Check that the metadata we're importing has a valid hash. */ + { + crypto::Md5Generator md5; + md5.Initialize(); + + md5.Update(std::addressof(import.header), sizeof(import.header)); + md5.Update(std::addressof(import.metadata), sizeof(import.metadata)); + md5.Update(std::addressof(import.profile_urls), sizeof(import.profile_urls[0]) * std::min<size_t>(import.metadata.num_entries, util::size(import.metadata.entries))); + + u8 hash[crypto::Md5Generator::HashSize]; + md5.GetHash(hash, sizeof(hash)); + + R_UNLESS(crypto::IsSameBytes(hash, import.hash, sizeof(hash)), sprofile::ResultInvalidMetadataHash()); + } + + /* Create temporary directories. */ + R_TRY(this->EnsureTemporaryDirectories()); + + /* Create metadata. */ + char path[0x30]; + CreateTemporaryMetadataPath(path, sizeof(path), m_save_data_info.mount_name); + R_TRY(WriteFile(path, std::addressof(import.metadata), sizeof(import.metadata))); + + /* Import the metadata. */ + m_profile_importer->ImportMetadata(import.metadata); + R_SUCCEED(); + } + + Result ProfileManager::LoadPrimaryMetadataImpl() { + /* Check pre-conditions. */ + AMS_ASSERT(m_profile_metadata_mutex.IsLockedByCurrentThread()); + AMS_ASSERT(m_general_mutex.IsLockedByCurrentThread()); + + /* If we don't have metadata, load it. */ + if (!m_profile_metadata.has_value()) { + /* Emplace our metadata. */ + m_profile_metadata.emplace(); + ON_RESULT_FAILURE { m_profile_metadata = util::nullopt; }; + + /* Read profile metadata. */ + char path[0x30]; + CreatePrimaryMetadataPath(path, sizeof(path), m_save_data_info.mount_name); + R_TRY(ReadFile(path, std::addressof(*m_profile_metadata), sizeof(*m_profile_metadata), 0)); + } + + /* We now have loaded metadata. */ + R_SUCCEED(); + } + + Result ProfileManager::LoadPrimaryMetadata(ProfileMetadata *out) { + /* Acquire locks. */ + std::scoped_lock lk1(m_profile_metadata_mutex); + std::scoped_lock lk2(m_general_mutex); + + /* Load our metadata. */ + R_TRY(this->LoadPrimaryMetadataImpl()); + + /* Set the output. */ + *out = *m_profile_metadata; + R_SUCCEED(); + } + + Result ProfileManager::LoadProfile(Identifier profile) { + /* Check if we already have the profile. */ + if (m_service_profile.has_value()) { + R_SUCCEED_IF(m_service_profile->name == profile); + } + + /* If we fail past this point, we want to have no profile. */ + auto prof_guard = SCOPE_GUARD { m_service_profile = util::nullopt; }; + + /* Create profile path. */ + char path[0x30]; + CreatePrimaryProfilePath(path, sizeof(path), m_save_data_info.mount_name, profile); + + /* Load the profile. */ + m_service_profile = {}; + R_TRY(ReadFile(path, std::addressof(m_service_profile->data), sizeof(m_service_profile->data), 0)); + + /* We succeeded. */ + prof_guard.Cancel(); + R_SUCCEED(); + } + + Result ProfileManager::GetDataEntry(ProfileDataEntry *out, Identifier profile, Identifier key) { + /* Acquire locks. */ + std::scoped_lock lk1(m_service_profile_mutex); + std::scoped_lock lk2(m_general_mutex); + + /* Load the desired profile. */ + if (R_SUCCEEDED(this->LoadProfile(profile))) { + /* Find the specified key. */ + for (auto i = 0u; i < std::min<size_t>(m_service_profile->data.num_entries, util::size(m_service_profile->data.entries)); ++i) { + if (m_service_profile->data.entries[i].key == key) { + *out = m_service_profile->data.entries[i]; + R_SUCCEED(); + } + } + } + + R_THROW(sprofile::ResultKeyNotFound()); + } + + Result ProfileManager::GetSigned64(s64 *out, Identifier profile, Identifier key) { + /* Get the data entry. */ + ProfileDataEntry entry; + R_TRY(this->GetDataEntry(std::addressof(entry), profile, key)); + + /* Check the type. */ + R_UNLESS(entry.type == ValueType_S64, sprofile::ResultInvalidDataType()); + + /* Set the output value. */ + *out = entry.value_s64; + R_SUCCEED(); + } + + Result ProfileManager::GetUnsigned64(u64 *out, Identifier profile, Identifier key) { + /* Get the data entry. */ + ProfileDataEntry entry; + R_TRY(this->GetDataEntry(std::addressof(entry), profile, key)); + + /* Check the type. */ + R_UNLESS(entry.type == ValueType_U64, sprofile::ResultInvalidDataType()); + + /* Set the output value. */ + *out = entry.value_u64; + R_SUCCEED(); + } + + Result ProfileManager::GetSigned32(s32 *out, Identifier profile, Identifier key) { + /* Get the data entry. */ + ProfileDataEntry entry; + R_TRY(this->GetDataEntry(std::addressof(entry), profile, key)); + + /* Check the type. */ + R_UNLESS(entry.type == ValueType_S32, sprofile::ResultInvalidDataType()); + + /* Set the output value. */ + *out = entry.value_s32; + R_SUCCEED(); + } + + Result ProfileManager::GetUnsigned32(u32 *out, Identifier profile, Identifier key) { + /* Get the data entry. */ + ProfileDataEntry entry; + R_TRY(this->GetDataEntry(std::addressof(entry), profile, key)); + + /* Check the type. */ + R_UNLESS(entry.type == ValueType_U32, sprofile::ResultInvalidDataType()); + + /* Set the output value. */ + *out = entry.value_u32; + R_SUCCEED(); + } + + Result ProfileManager::GetByte(u8 *out, Identifier profile, Identifier key) { + /* Get the data entry. */ + ProfileDataEntry entry; + R_TRY(this->GetDataEntry(std::addressof(entry), profile, key)); + + /* Check the type. */ + R_UNLESS(entry.type == ValueType_Byte, sprofile::ResultInvalidDataType()); + + /* Set the output value. */ + *out = entry.value_u8; + R_SUCCEED(); + } + + Result ProfileManager::GetRaw(u8 *out_type, u64 *out_value, Identifier profile, Identifier key) { + /* Get the data entry. */ + ProfileDataEntry entry; + R_TRY(this->GetDataEntry(std::addressof(entry), profile, key)); + + /* Set the output type and value. */ + *out_type = entry.type; + *out_value = entry.value_u64; + R_SUCCEED(); + } + + Result ProfileManager::CommitImportedProfiles() { + /* Ensure primary directories. */ + R_TRY(this->EnsurePrimaryDirectories()); + + /* Declare re-usable paths. */ + char tmp_path[0x30]; + char pri_path[0x30]; + + /* Move the metadata. */ + { + CreateTemporaryMetadataPath(tmp_path, sizeof(tmp_path), m_save_data_info.mount_name); + CreatePrimaryMetadataPath(pri_path, sizeof(pri_path), m_save_data_info.mount_name); + R_TRY(MoveFile(tmp_path, pri_path)); + } + + /* Move all newly imported profiles. */ + for (auto i = 0; i < m_profile_importer->GetImportingCount(); ++i) { + const auto &profile = m_profile_importer->GetImportingProfile(i); + + if (profile.is_new_import) { + CreateTemporaryProfilePath(tmp_path, sizeof(tmp_path), m_save_data_info.mount_name, profile.identifier_0); + CreatePrimaryProfilePath(pri_path, sizeof(pri_path), m_save_data_info.mount_name, profile.identifier_0); + R_TRY(MoveFile(tmp_path, pri_path)); + } + } + + R_SUCCEED(); + } + + Result ProfileManager::CleanupOrphanedProfiles() { + /* Check pre-conditions. */ + AMS_ASSERT(m_profile_importer.has_value()); + + /* Declare re-usable path. */ + char pri_path[0x30]; + + /* Cleanup the profiles. */ + R_RETURN(m_profile_importer->CleanupOrphanedProfiles([&](Identifier profile) ALWAYS_INLINE_LAMBDA -> Result { + CreatePrimaryProfilePath(pri_path, sizeof(pri_path), m_save_data_info.mount_name, profile); + R_RETURN(DeleteFile(pri_path)); + })); + } + + void ProfileManager::OnCommitted() { + /* TODO: Here, Nintendo sets the erpt ServiceProfileRevisionKey to the current revision key. */ + + /* If we need to, invalidate the loaded service profile. */ + if (m_service_profile.has_value()) { + for (auto i = 0; i < m_profile_importer->GetImportingCount(); ++i) { + if (m_service_profile->name == m_profile_importer->GetImportingProfile(i).identifier_0) { + m_service_profile = util::nullopt; + break; + } + } + } + + /* Reset profile metadata. */ + m_profile_metadata = util::nullopt; + + /* Invoke any listeners. */ + for (auto i = 0; i < m_profile_importer->GetImportingCount(); ++i) { + const auto &profile = m_profile_importer->GetImportingProfile(i); + + if (profile.is_new_import) { + m_update_observer_manager.OnUpdate(profile.identifier_0); + } + } + + /* Reset profile importer. */ + m_profile_importer = util::nullopt; + } + + Result ProfileManager::EnsurePrimaryDirectories() { + /* Ensure the primary directories. */ + char path[0x30]; + + CreatePrimaryDirectoryPath(path, sizeof(path), m_save_data_info.mount_name); + R_TRY(EnsureDirectory(path)); + + CreatePrimaryProfileDirectoryPath(path, sizeof(path), m_save_data_info.mount_name); + R_TRY(EnsureDirectory(path)); + + R_SUCCEED(); + } + + Result ProfileManager::EnsureTemporaryDirectories() { + /* Ensure the temporary directories. */ + char path[0x30]; + + CreateTemporaryDirectoryPath(path, sizeof(path), m_save_data_info.mount_name); + R_TRY(EnsureDirectory(path)); + + CreateTemporaryProfileDirectoryPath(path, sizeof(path), m_save_data_info.mount_name); + R_TRY(EnsureDirectory(path)); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_manager.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_manager.hpp new file mode 100644 index 00000000..40484b99 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_manager.hpp @@ -0,0 +1,87 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "sprofile_srv_types.hpp" +#include "sprofile_srv_profile_update_observer_impl.hpp" +#include "sprofile_srv_profile_importer.hpp" + +namespace ams::sprofile::srv { + + class ProfileManager { + public: + struct SaveDataInfo { + u64 id; + const char *mount_name; + size_t size; + size_t journal_size; + u32 flags; + }; + private: + private: + os::SdkMutex m_general_mutex{}; + os::SdkMutex m_fs_mutex{}; + SaveDataInfo m_save_data_info; + bool m_save_file_mounted; + util::optional<ProfileImporter> m_profile_importer; + os::SdkMutex m_profile_importer_mutex{}; + util::optional<ProfileMetadata> m_profile_metadata; + os::SdkMutex m_profile_metadata_mutex{}; + util::optional<ServiceProfile> m_service_profile; + os::SdkMutex m_service_profile_mutex{}; + ProfileUpdateObserverManager m_update_observer_manager; + public: + ProfileManager(const SaveDataInfo &save_data_info); + public: + void InitializeSaveData(); + Result ResetSaveData(); + + Result OpenProfileImporter(); + void CloseProfileImporter(); + + Result ImportProfile(const sprofile::srv::ProfileDataForImportData &data); + Result Commit(); + Result ImportMetadata(const sprofile::srv::ProfileMetadataForImportMetadata &data); + + Result LoadPrimaryMetadata(ProfileMetadata *out); + + Result GetSigned64(s64 *out, Identifier profile, Identifier key); + Result GetUnsigned64(u64 *out, Identifier profile, Identifier key); + Result GetSigned32(s32 *out, Identifier profile, Identifier key); + Result GetUnsigned32(u32 *out, Identifier profile, Identifier key); + Result GetByte(u8 *out, Identifier profile, Identifier key); + + Result GetRaw(u8 *out_type, u64 *out_value, Identifier profile, Identifier key); + + ProfileUpdateObserverManager &GetUpdateObserverManager() { return m_update_observer_manager; } + private: + Result CommitImportedProfiles(); + Result CleanupOrphanedProfiles(); + + Result LoadPrimaryMetadataImpl(); + void CloseProfileImporterImpl(); + + void OnCommitted(); + + Result GetDataEntry(ProfileDataEntry *out, Identifier profile, Identifier key); + + Result LoadProfile(Identifier profile); + + Result EnsurePrimaryDirectories(); + Result EnsureTemporaryDirectories(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_reader_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_reader_impl.cpp new file mode 100644 index 00000000..b601764a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_reader_impl.cpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sprofile_srv_profile_manager.hpp" +#include "sprofile_srv_profile_reader_impl.hpp" + +namespace ams::sprofile::srv { + + Result ProfileReaderImpl::GetSigned64(sf::Out<s64> out, sprofile::Identifier profile, sprofile::Identifier key) { + R_RETURN(m_manager->GetSigned64(out.GetPointer(), profile, key)); + } + + Result ProfileReaderImpl::GetUnsigned64(sf::Out<u64> out, sprofile::Identifier profile, sprofile::Identifier key) { + R_RETURN(m_manager->GetUnsigned64(out.GetPointer(), profile, key)); + } + + Result ProfileReaderImpl::GetSigned32(sf::Out<s32> out, sprofile::Identifier profile, sprofile::Identifier key) { + R_RETURN(m_manager->GetSigned32(out.GetPointer(), profile, key)); + } + + Result ProfileReaderImpl::GetUnsigned32(sf::Out<u32> out, sprofile::Identifier profile, sprofile::Identifier key) { + R_RETURN(m_manager->GetUnsigned32(out.GetPointer(), profile, key)); + } + + Result ProfileReaderImpl::GetByte(sf::Out<u8> out, sprofile::Identifier profile, sprofile::Identifier key) { + R_RETURN(m_manager->GetByte(out.GetPointer(), profile, key)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_reader_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_reader_impl.hpp new file mode 100644 index 00000000..4768538e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_reader_impl.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "sprofile_srv_i_profile_reader.hpp" + +namespace ams::sprofile::srv { + + class ProfileManager; + + class ProfileReaderImpl { + private: + ProfileManager *m_manager; + public: + ProfileReaderImpl(ProfileManager *manager) : m_manager(manager) { /* ... */ } + public: + Result GetSigned64(sf::Out<s64> out, sprofile::Identifier profile, sprofile::Identifier key); + Result GetUnsigned64(sf::Out<u64> out, sprofile::Identifier profile, sprofile::Identifier key); + Result GetSigned32(sf::Out<s32> out, sprofile::Identifier profile, sprofile::Identifier key); + Result GetUnsigned32(sf::Out<u32> out, sprofile::Identifier profile, sprofile::Identifier key); + Result GetByte(sf::Out<u8> out, sprofile::Identifier profile, sprofile::Identifier key); + }; + static_assert(IsIProfileReader<ProfileReaderImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_update_observer_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_update_observer_impl.cpp new file mode 100644 index 00000000..332c1583 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_update_observer_impl.cpp @@ -0,0 +1,75 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sprofile_srv_profile_manager.hpp" +#include "sprofile_srv_profile_update_observer_impl.hpp" + +namespace ams::sprofile::srv { + + namespace { + + class AutoUnregisterObserver : public ProfileUpdateObserverImpl { + private: + ProfileUpdateObserverManager *m_manager; + public: + AutoUnregisterObserver(ProfileUpdateObserverManager *manager) : m_manager(manager) { /* ... */ } + virtual ~AutoUnregisterObserver() { m_manager->CloseObserver(this); } + }; + static_assert(sprofile::srv::IsIProfileUpdateObserver<AutoUnregisterObserver>); + + } + + Result ProfileUpdateObserverManager::OpenObserver(sf::Out<sf::SharedPointer<::ams::sprofile::srv::IProfileUpdateObserver>> &out, MemoryResource *memory_resource) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we can allocate. */ + R_UNLESS(m_observer_count < MaxObservers, sprofile::ResultMaxObservers()); + + /* Allocate an object. */ + auto obj = sf::ObjectFactory<sf::MemoryResourceAllocationPolicy>::CreateSharedEmplaced<IProfileUpdateObserver, AutoUnregisterObserver>(memory_resource, this); + R_UNLESS(obj != nullptr, sprofile::ResultAllocationFailed()); + + /* Register the observer. */ + m_observers[m_observer_count++] = std::addressof(obj.GetImpl()); + + /* Return the object. */ + *out = std::move(obj); + R_SUCCEED(); + } + + void ProfileUpdateObserverManager::CloseObserver(ProfileUpdateObserverImpl *observer) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Find the observer. */ + int index = -1; + for (auto i = 0; i < m_observer_count; ++i) { + if (m_observers[i] == observer) { + index = i; + break; + } + } + AMS_ABORT_UNLESS(index != -1); + + /* Remove from our list. */ + m_observers[index] = m_observers[--m_observer_count]; + + /* Sanity check. */ + AMS_ABORT_UNLESS(m_observer_count >= 0); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_update_observer_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_update_observer_impl.hpp new file mode 100644 index 00000000..f54d230d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_update_observer_impl.hpp @@ -0,0 +1,105 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "sprofile_srv_i_profile_update_observer.hpp" + +namespace ams::sprofile::srv { + + class ProfileUpdateObserverImpl { + public: + static constexpr auto MaxProfiles = 4; + private: + os::SystemEvent m_event; + Identifier m_profiles[MaxProfiles]; + int m_profile_count; + os::SdkMutex m_mutex; + public: + ProfileUpdateObserverImpl() : m_event(os::EventClearMode_ManualClear, true), m_profile_count(0), m_mutex() { /* ... */ } + virtual ~ProfileUpdateObserverImpl() { /* ... */ } + public: + os::SystemEvent &GetEvent() { return m_event; } + const os::SystemEvent &GetEvent() const { return m_event; } + public: + Result Listen(Identifier profile) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check if we can listen. */ + R_UNLESS(m_profile_count < MaxProfiles, sprofile::ResultMaxListeners()); + + /* Check if we're already listening. */ + for (auto i = 0; i < m_profile_count; ++i) { + R_UNLESS(m_profiles[i] != profile, sprofile::ResultAlreadyListening()); + } + + /* Add the profile. */ + m_profiles[m_profile_count++] = profile; + R_SUCCEED(); + } + + Result Unlisten(Identifier profile) { + /* Check that we're listening. */ + for (auto i = 0; i < m_profile_count; ++i) { + if (m_profiles[i] == profile) { + m_profiles[i] = m_profiles[--m_profile_count]; + AMS_ABORT_UNLESS(m_profile_count >= 0); + R_SUCCEED(); + } + } + + R_THROW(sprofile::ResultNotListening()); + } + + Result GetEventHandle(sf::OutCopyHandle out) { + out.SetValue(m_event.GetReadableHandle(), false); + R_SUCCEED(); + } + public: + void OnUpdate(Identifier profile) { + for (auto i = 0; i < m_profile_count; ++i) { + if (m_profiles[i] == profile) { + m_event.Signal(); + break; + } + } + } + }; + static_assert(sprofile::srv::IsIProfileUpdateObserver<ProfileUpdateObserverImpl>); + + class ProfileUpdateObserverManager { + public: + static constexpr auto MaxObservers = 10; + private: + ProfileUpdateObserverImpl *m_observers[MaxObservers]; + int m_observer_count; + os::SdkMutex m_mutex; + public: + ProfileUpdateObserverManager() : m_observer_count(0), m_mutex() { /* ... */ } + public: + Result OpenObserver(sf::Out<sf::SharedPointer<::ams::sprofile::srv::IProfileUpdateObserver>> &out, MemoryResource *memory_resource); + void CloseObserver(ProfileUpdateObserverImpl *observer); + + void OnUpdate(Identifier profile) { + std::scoped_lock lk(m_mutex); + + for (auto i = 0; i < m_observer_count; ++i) { + m_observers[i]->OnUpdate(profile); + } + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_bg_agent.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_bg_agent.cpp new file mode 100644 index 00000000..c8e4e503 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_bg_agent.cpp @@ -0,0 +1,98 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sprofile_srv_profile_manager.hpp" +#include "sprofile_srv_service_for_bg_agent.hpp" +#include "sprofile_srv_profile_importer_impl.hpp" +#include "sprofile_srv_fs_utils.hpp" + +namespace ams::sprofile::srv { + + Result ServiceForBgAgent::OpenProfileImporter(sf::Out<sf::SharedPointer<::ams::sprofile::srv::IProfileImporter>> out) { + /* Allocate an object. */ + auto obj = sf::ObjectFactory<sf::MemoryResourceAllocationPolicy>::CreateSharedEmplaced<IProfileImporter, ProfileImporterImpl>(m_memory_resource, m_profile_manager); + R_UNLESS(obj != nullptr, sprofile::ResultAllocationFailed()); + + /* Confirm that we can begin an import. */ + R_TRY(m_profile_manager->OpenProfileImporter()); + + /* Return the object. */ + *out = std::move(obj); + R_SUCCEED(); + } + + Result ServiceForBgAgent::GetImportableProfileUrls(sf::Out<u32> out_count, const sf::OutArray<sprofile::srv::ProfileUrl> &out, const sprofile::srv::ProfileMetadataForImportMetadata &arg) { + /* Check size. */ + R_UNLESS(out.GetSize() >= arg.metadata.num_entries, sprofile::ResultInvalidArgument()); + + /* Load primary metadata. */ + sprofile::srv::ProfileMetadata primary_metadata; + R_TRY_CATCH(m_profile_manager->LoadPrimaryMetadata(std::addressof(primary_metadata))) { + R_CATCH(fs::ResultPathNotFound) { + /* It's okay if we have no primary metadata -- this means that all profiles are importable. */ + primary_metadata.num_entries = 0; + } + } R_END_TRY_CATCH; + + /* We want to return the set of profiles that can be imported, which is just the profiles we don't already have. */ + u32 count = 0; + for (u32 i = 0; i < arg.metadata.num_entries; ++i) { + const auto &arg_entry = arg.metadata.entries[i]; + + /* Check if we have the entry. */ + bool have_entry = false; + for (u32 j = 0; j < primary_metadata.num_entries; ++j) { + const auto &pri_entry = primary_metadata.entries[j]; + + if (pri_entry.identifier_0 == arg_entry.identifier_0 && pri_entry.identifier_1 == arg_entry.identifier_1) { + have_entry = true; + break; + } + } + + /* If we don't already have the entry, it's importable -- copy it out. */ + if (!have_entry) { + out[count++] = arg.profile_urls[i]; + } + } + + /* Set output count. */ + *out_count = count; + R_SUCCEED(); + + } + + Result ServiceForBgAgent::IsUpdateNeeded(sf::Out<bool> out, Identifier revision_key) { + /* Load primary metadata. */ + bool loaded_metadata = true; + sprofile::srv::ProfileMetadata primary_metadata; + R_TRY_CATCH(m_profile_manager->LoadPrimaryMetadata(std::addressof(primary_metadata))) { + R_CATCH(fs::ResultPathNotFound) { + /* If we have no metadata, we don't have a revision key. */ + loaded_metadata = false; + } + } R_END_TRY_CATCH; + + /* Determine if update is needed. */ + *out = !(loaded_metadata && revision_key == primary_metadata.revision_key); + R_SUCCEED(); + } + + Result ServiceForBgAgent::Reset() { + R_RETURN(m_profile_manager->ResetSaveData()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_bg_agent.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_bg_agent.hpp new file mode 100644 index 00000000..43d30896 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_bg_agent.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "sprofile_srv_i_service_for_bg_agent.hpp" + +namespace ams::sprofile::srv { + + class ProfileManager; + + class ServiceForBgAgent { + private: + MemoryResource *m_memory_resource; + ProfileManager *m_profile_manager; + public: + constexpr ServiceForBgAgent(MemoryResource *mr, ProfileManager *pm) : m_memory_resource(mr), m_profile_manager(pm) { /* ... */ } + public: + Result OpenProfileImporter(sf::Out<sf::SharedPointer<::ams::sprofile::srv::IProfileImporter>> out); + Result GetImportableProfileUrls(sf::Out<u32> out_count, const sf::OutArray<sprofile::srv::ProfileUrl> &out, const sprofile::srv::ProfileMetadataForImportMetadata &arg); + Result IsUpdateNeeded(sf::Out<bool> out, Identifier revision_key); + Result Reset(); + }; + static_assert(sprofile::srv::IsISprofileServiceForBgAgent<ServiceForBgAgent>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_system_process.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_system_process.cpp new file mode 100644 index 00000000..e569bd14 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_system_process.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sprofile_srv_profile_manager.hpp" +#include "sprofile_srv_service_for_system_process.hpp" +#include "sprofile_srv_profile_reader_impl.hpp" +#include "sprofile_srv_profile_controller_for_debug_impl.hpp" + +namespace ams::sprofile::srv { + + Result ServiceForSystemProcess::OpenProfileReader(sf::Out<sf::SharedPointer<::ams::sprofile::srv::IProfileReader>> out) { + /* Allocate an object. */ + auto obj = sf::ObjectFactory<sf::MemoryResourceAllocationPolicy>::CreateSharedEmplaced<IProfileReader, ProfileReaderImpl>(m_memory_resource, m_profile_manager); + R_UNLESS(obj != nullptr, sprofile::ResultAllocationFailed()); + + /* Return the object. */ + *out = std::move(obj); + R_SUCCEED(); + } + + Result ServiceForSystemProcess::OpenProfileUpdateObserver(sf::Out<sf::SharedPointer<::ams::sprofile::srv::IProfileUpdateObserver>> out) { + R_RETURN(m_profile_manager->GetUpdateObserverManager().OpenObserver(out, m_memory_resource)); + } + + Result ServiceForSystemProcess::OpenProfileControllerForDebug(sf::Out<sf::SharedPointer<::ams::sprofile::srv::IProfileControllerForDebug>> out) { + /* Require debug mode in order to open a debug controller. */ + R_UNLESS(settings::fwdbg::IsDebugModeEnabled(), sprofile::ResultNotPermitted()); + + /* Allocate an object. */ + auto obj = sf::ObjectFactory<sf::MemoryResourceAllocationPolicy>::CreateSharedEmplaced<IProfileControllerForDebug, ProfileControllerForDebugImpl>(m_memory_resource, m_profile_manager); + R_UNLESS(obj != nullptr, sprofile::ResultAllocationFailed()); + + /* Return the object. */ + *out = std::move(obj); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_system_process.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_system_process.hpp new file mode 100644 index 00000000..d5186fbf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_system_process.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "sprofile_srv_i_service_for_system_process.hpp" + +namespace ams::sprofile::srv { + + class ProfileManager; + + class ServiceForSystemProcess { + private: + MemoryResource *m_memory_resource; + ProfileManager *m_profile_manager; + public: + constexpr ServiceForSystemProcess(MemoryResource *mr, ProfileManager *pm) : m_memory_resource(mr), m_profile_manager(pm) { /* ... */ } + public: + Result OpenProfileReader(sf::Out<sf::SharedPointer<::ams::sprofile::srv::IProfileReader>> out); + Result OpenProfileUpdateObserver(sf::Out<sf::SharedPointer<::ams::sprofile::srv::IProfileUpdateObserver>> out); + Result OpenProfileControllerForDebug(sf::Out<sf::SharedPointer<::ams::sprofile::srv::IProfileControllerForDebug>> out); + }; + static_assert(sprofile::srv::IsISprofileServiceForSystemProcess<ServiceForSystemProcess>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_getter.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_getter.cpp new file mode 100644 index 00000000..577ef8b9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_getter.cpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sprofile_srv_service_getter.hpp" + +namespace ams::sprofile::srv { + + Result ServiceGetter::GetServiceForSystemProcess(sf::Out<sf::SharedPointer<sprofile::srv::ISprofileServiceForSystemProcess>> out) { + /* Check that we have a service-for-system-process. */ + R_UNLESS(m_service_for_system_process != nullptr, sprofile::ResultNotPermitted()); + + /* Set the output. */ + *out = m_service_for_system_process; + R_SUCCEED(); + } + + Result ServiceGetter::GetServiceForBgAgent(sf::Out<sf::SharedPointer<sprofile::srv::ISprofileServiceForBgAgent>> out) { + /* Check that we have a service-for-bg-agent. */ + R_UNLESS(m_service_for_bg_agent != nullptr, sprofile::ResultNotPermitted()); + + /* Set the output. */ + *out = m_service_for_bg_agent; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_getter.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_getter.hpp new file mode 100644 index 00000000..d741abc7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_getter.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "sprofile_srv_i_service_getter.hpp" + +namespace ams::sprofile::srv { + + class ServiceGetter { + private: + sf::SharedPointer<::ams::sprofile::srv::ISprofileServiceForBgAgent> m_service_for_bg_agent; + sf::SharedPointer<::ams::sprofile::srv::ISprofileServiceForSystemProcess> m_service_for_system_process; + public: + constexpr ServiceGetter(sf::SharedPointer<::ams::sprofile::srv::ISprofileServiceForBgAgent> bg, sf::SharedPointer<::ams::sprofile::srv::ISprofileServiceForSystemProcess> sp) : m_service_for_bg_agent(bg), m_service_for_system_process(sp) { /* ... */ } + public: + Result GetServiceForSystemProcess(sf::Out<sf::SharedPointer<sprofile::srv::ISprofileServiceForSystemProcess>> out); + Result GetServiceForBgAgent(sf::Out<sf::SharedPointer<sprofile::srv::ISprofileServiceForBgAgent>> out); + }; + static_assert(sprofile::srv::IsIServiceGetter<ServiceGetter>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_types.hpp new file mode 100644 index 00000000..d20610a1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_types.hpp @@ -0,0 +1,152 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::sprofile::srv { + + constexpr inline const u32 ProfileFormatVersion = 1; + + constexpr inline bool IsValidProfileFormatVersion(u32 version) { + return version == ProfileFormatVersion; + } + + enum ValueType : u8 { + ValueType_Byte = 0, + ValueType_U32 = 1, + ValueType_S32 = 2, + ValueType_U64 = 3, + ValueType_S64 = 4, + }; + + struct ProfileDataEntry { + Identifier key; + ValueType type; + union { + s64 value_s64; + u64 value_u64; + s32 value_s32; + u32 value_u32; + u8 value_u8; + }; + }; + static_assert(util::is_pod<ProfileDataEntry>::value); + static_assert(sizeof(ProfileDataEntry) == 0x10); + + static_assert(AMS_OFFSETOF(ProfileDataEntry, key) == 0x00); + static_assert(AMS_OFFSETOF(ProfileDataEntry, type) == 0x07); + static_assert(AMS_OFFSETOF(ProfileDataEntry, value_s64) == 0x08); + + struct ProfileData { + u32 num_entries; + u8 unk_04[0x0C]; + u8 unk_10[0x20]; + ProfileDataEntry entries[(0x4000 - 0x30) / sizeof(ProfileDataEntry)]; + }; + static_assert(util::is_pod<ProfileData>::value); + static_assert(sizeof(ProfileData) == 0x4000); + + static_assert(AMS_OFFSETOF(ProfileData, num_entries) == 0x00); + static_assert(AMS_OFFSETOF(ProfileData, unk_04) == 0x04); + static_assert(AMS_OFFSETOF(ProfileData, unk_10) == 0x10); + static_assert(AMS_OFFSETOF(ProfileData, entries) == 0x30); + + struct ServiceProfile { + Identifier name; + ProfileData data; + }; + static_assert(util::is_pod<ServiceProfile>::value); + static_assert(sizeof(ServiceProfile) == 0x4008); + + static_assert(AMS_OFFSETOF(ServiceProfile, name) == 0x00); + static_assert(AMS_OFFSETOF(ServiceProfile, data) == 0x08); + + struct ProfileDataForImportData : public sf::LargeData, public sf::PrefersMapAliasTransferMode { + struct { + Identifier identifier_0; + Identifier identifier_1; + u8 unk_0E[2]; + u32 version; + u8 unk_14[0x1C]; + } header; + u8 hash[crypto::Md5Generator::HashSize]; + ProfileData data; + u8 unk_4040[0x4400 - 0x4040]; + }; + static_assert(util::is_pod<ProfileDataForImportData>::value); + static_assert(sizeof(ProfileDataForImportData) == 0x4400); + + static_assert(AMS_OFFSETOF(ProfileDataForImportData, header) == 0x00); + static_assert(AMS_OFFSETOF(ProfileDataForImportData, hash) == 0x30); + static_assert(AMS_OFFSETOF(ProfileDataForImportData, data) == 0x40); + static_assert(AMS_OFFSETOF(ProfileDataForImportData, unk_4040) == 0x4040); + + struct ProfileMetadataEntry { + Identifier identifier_0; + Identifier identifier_1; + u8 unk_0E[0x32]; + }; + static_assert(util::is_pod<ProfileMetadataEntry>::value); + static_assert(sizeof(ProfileMetadataEntry) == 0x40); + + static_assert(AMS_OFFSETOF(ProfileMetadataEntry, identifier_0) == 0x00); + static_assert(AMS_OFFSETOF(ProfileMetadataEntry, identifier_1) == 0x07); + static_assert(AMS_OFFSETOF(ProfileMetadataEntry, unk_0E) == 0x0E); + + struct ProfileUrl : public sf::PrefersMapAliasTransferMode { + char url[0x100]; + }; + static_assert(util::is_pod<ProfileUrl>::value); + static_assert(sizeof(ProfileUrl) == 0x100); + + struct ProfileMetadata { + u32 num_entries; + u32 unk_04; + Identifier revision_key; + u8 unk_0F[0x1]; + u8 unk_10[0x30]; + ProfileMetadataEntry entries[50]; + }; + static_assert(util::is_pod<ProfileMetadata>::value); + static_assert(sizeof(ProfileMetadata) == 0xCC0); + + static_assert(AMS_OFFSETOF(ProfileMetadata, num_entries) == 0x00); + static_assert(AMS_OFFSETOF(ProfileMetadata, unk_04) == 0x04); + static_assert(AMS_OFFSETOF(ProfileMetadata, revision_key) == 0x08); + static_assert(AMS_OFFSETOF(ProfileMetadata, unk_0F) == 0x0F); + static_assert(AMS_OFFSETOF(ProfileMetadata, unk_10) == 0x10); + static_assert(AMS_OFFSETOF(ProfileMetadata, entries) == 0x40); + + struct ProfileMetadataForImportMetadata : public sf::LargeData, public sf::PrefersMapAliasTransferMode { + struct { + u32 version; + u8 unk_04[0x1C]; + } header; + u8 hash[crypto::Md5Generator::HashSize]; + ProfileMetadata metadata; + ProfileUrl profile_urls[50]; + u8 unk_3EF0[0x8000 - 0x3EF0]; + }; + static_assert(util::is_pod<ProfileMetadataForImportMetadata>::value); + static_assert(sizeof(ProfileMetadataForImportMetadata) == 0x8000); + + static_assert(AMS_OFFSETOF(ProfileMetadataForImportMetadata, header) == 0x00); + static_assert(AMS_OFFSETOF(ProfileMetadataForImportMetadata, hash) == 0x20); + static_assert(AMS_OFFSETOF(ProfileMetadataForImportMetadata, metadata) == 0x30); + static_assert(AMS_OFFSETOF(ProfileMetadataForImportMetadata, profile_urls) == 0xCF0); + static_assert(AMS_OFFSETOF(ProfileMetadataForImportMetadata, unk_3EF0) == 0x3EF0); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/impl/util/time_impl_util_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/impl/util/time_impl_util_api.cpp new file mode 100644 index 00000000..a610b145 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/impl/util/time_impl_util_api.cpp @@ -0,0 +1,257 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::time::impl::util { + + namespace { + + constexpr inline const int DaysPerMonth[12] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + static_assert(std::accumulate(std::begin(DaysPerMonth), std::end(DaysPerMonth), 0) == 365); + + constexpr inline const std::array<int, 12> SumDaysPerMonth = [] { + std::array<int, 12> days = {}; + for (size_t i = 1; i < days.size(); ++i) { + days[i] = days[i - 1] + DaysPerMonth[i - 1]; + } + return days; + }(); + static_assert(SumDaysPerMonth[ 0] == 0); + static_assert(SumDaysPerMonth[11] + DaysPerMonth[11] == 365); + + constexpr bool IsLeapYearImpl(int year) { + if ((year % 400) == 0) { + return true; + } else if ((year % 100) == 0) { + return false; + } else if ((year % 4) == 0) { + return true; + } else { + return false; + } + } + + constexpr int DateToDaysImpl(int year, int month, int day) { + /* Lightly validate input. */ + AMS_ASSERT(year > 0); + AMS_ASSERT(month > 0); + AMS_ASSERT(day > 0); + + /* Adjust months within range. */ + year += month / 12; + month %= 12; + if (month == 0) { + month = 12; + } + AMS_ASSERT(1 <= month && month <= 12); + + /* Calculate days. */ + int res = (year - 1) * 365; + res += (year / 4) - (year / 100) + (year / 400); + res += SumDaysPerMonth[month - 1] + day; + + /* Subtract leap day, if it hasn't happened yet. */ + if (month < 3 && IsLeapYearImpl(year)) { + res -= 1; + } + + /* Subtract the current day. */ + res -= 1; + + return res; + } + + constexpr void DaysToDateImpl(int *out_year, int *out_month, int *out_day, int days) { + /* Lightly validate input. */ + AMS_ASSERT(days > 0); + + /* Declare unit conversion factors. */ + constexpr int DaysPerYear = 365; + constexpr int DaysPerFourYears = DaysPerYear * 4 + 1; + constexpr int DaysPerCentury = DaysPerFourYears * 25 - 1; + constexpr int DaysPerFourCenturies = DaysPerCentury * 4 + 1; + + /* Adjust date. */ + days -= 59; + days += 365; + + /* Determine various units. */ + int four_centuries = days / DaysPerFourCenturies; + int four_centuries_rem = days % DaysPerFourCenturies; + if (four_centuries_rem < 0) { + four_centuries_rem += DaysPerFourCenturies; + --four_centuries; + } + + int centuries = four_centuries_rem / DaysPerCentury; + int centuries_rem = four_centuries_rem % DaysPerCentury; + + int four_years = centuries_rem / DaysPerFourYears; + int four_years_rem = centuries_rem % DaysPerFourYears; + + int years = four_years_rem / DaysPerYear; + int years_rem = four_years_rem % DaysPerYear; + + /* Adjust into range. */ + int year = 400 * four_centuries + 100 * centuries + 4 * four_years + years; + int month = (5 * years_rem + 2) / 153; + int day = years_rem - (153 * month + 2) / 5 + 1; + + /* Adjust in case we fell into a pathological case. */ + if (years == 4 || centuries == 4) { + month = 11; + day = 29; + year -= 1; + } + + /* Adjust month. */ + if (month <= 9) { + month += 3; + } else { + month -= 9; + year += 1; + } + + /* Set output. */ + if (out_year) { + *out_year = year; + } + if (out_month) { + *out_month = month; + } + if (out_day) { + *out_day = day; + } + } + + constexpr inline int EpochDays = DateToDaysImpl(1970, 1, 1); + + static_assert([]() -> bool { + int year{}, month{}, day{}; + + DaysToDateImpl(std::addressof(year), std::addressof(month), std::addressof(day), EpochDays); + + return year == 1970 && month == 1 && day == 1; + }()); + + } + + Result GetSpanBetween(s64 *out, const SteadyClockTimePoint &from, const SteadyClockTimePoint &to) { + AMS_ASSERT(out != nullptr); + + R_UNLESS(out != nullptr, time::ResultInvalidPointer()); + R_UNLESS(from.source_id == to.source_id, time::ResultNotComparable()); + + R_UNLESS(ams::util::TrySubtractWithoutOverflow(out, to.value, from.value), time::ResultOverflowed()); + R_SUCCEED(); + } + + bool IsValidDate(int year, int month, int day) { + return 1 <= year && 1 <= month && month <= 12 && 1 <= day && day <= GetDaysInMonth(year, month); + } + + bool IsLeapYear(int year) { + AMS_ASSERT(year > 0); + + return IsLeapYearImpl(year); + } + + int GetDaysInMonth(int year, int month) { + /* Check pre-conditions. */ + AMS_ASSERT(year > 0); + AMS_ASSERT(1 <= month && month <= 12); + + if (month == 2 && IsLeapYear(year)) { + return DaysPerMonth[month - 1] + 1; + } else { + return DaysPerMonth[month - 1]; + } + } + + int DateToDays(int year, int month, int day) { + return DateToDaysImpl(year, month, day); + } + + void DaysToDate(int *out_year, int *out_month, int *out_day, int days) { + DaysToDateImpl(out_year, out_month, out_day, days); + } + + CalendarTime ToCalendarTimeInUtc(const PosixTime &posix_time) { + constexpr s64 SecondsPerDay = TimeSpan::FromDays(1).GetSeconds(); + constexpr s64 SecondsPerHour = TimeSpan::FromHours(1).GetSeconds(); + constexpr s64 SecondsPerMinute = TimeSpan::FromMinutes(1).GetSeconds(); + + /* Get year/month/day. */ + int year, month, day; + DaysToDate(std::addressof(year), std::addressof(month), std::addressof(day), static_cast<int>(posix_time.value / SecondsPerDay) + EpochDays); + + /* Handle negative posix times. */ + s64 posix_abs = posix_time.value >= 0 ? posix_time.value : -1 * posix_time.value; + s64 posix_rem = posix_abs % SecondsPerDay; + if (posix_time.value < 0) { + posix_rem *= -1; + } + + /* Adjust remainder if negative. */ + if (posix_rem < 0) { + if ((--day) <= 0) { + if ((--month) <= 0) { + --year; + month = 12; + } + day = time::impl::util::GetDaysInMonth(year, month); + } + + posix_rem += SecondsPerDay; + } + + const int hour = posix_rem / SecondsPerHour; + posix_rem %= SecondsPerHour; + + const int minute = posix_rem / SecondsPerMinute; + posix_rem %= SecondsPerMinute; + + const int second = posix_rem; + + return CalendarTime { + .year = static_cast<s16>(year), + .month = static_cast<s8>(month), + .day = static_cast<s8>(day), + .hour = static_cast<s8>(hour), + .minute = static_cast<s8>(minute), + .second = static_cast<s8>(second), + }; + } + + PosixTime ToPosixTimeFromUtc(const CalendarTime &calendar_time) { + /* Validate pre-conditions. */ + AMS_ASSERT(IsValidDate(calendar_time.year, calendar_time.month, calendar_time.day)); + AMS_ASSERT(0 <= calendar_time.hour && calendar_time.hour <= 23); + AMS_ASSERT(0 <= calendar_time.minute && calendar_time.minute <= 59); + AMS_ASSERT(0 <= calendar_time.second && calendar_time.second <= 59); + + /* Extract/convert fields. */ + const s64 days = static_cast<s64>(time::impl::util::DateToDays(calendar_time.year, calendar_time.month, calendar_time.day)) - EpochDays; + const s64 hours = calendar_time.hour; + const s64 minutes = calendar_time.minute; + const s64 seconds = calendar_time.second; + + return PosixTime { .value = ((((days * 24) + hours) * 60) + minutes) * 60 + seconds }; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/time_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/time_api.cpp new file mode 100644 index 00000000..bc2cda3b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/time_api.cpp @@ -0,0 +1,122 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) +extern "C" { + + extern TimeServiceType __nx_time_service_type; + +} +#endif + +namespace ams::time { + + namespace { + + enum InitializeMode { + InitializeMode_None, + InitializeMode_Normal, + InitializeMode_Menu, + InitializeMode_System, + InitializeMode_Repair, + InitializeMode_SystemUser, + }; + + constinit u32 g_initialize_count = 0; + constinit InitializeMode g_initialize_mode = InitializeMode_None; + + constinit os::SdkMutex g_initialize_mutex; + + Result InitializeImpl(InitializeMode mode) { + std::scoped_lock lk(g_initialize_mutex); + + if (g_initialize_count > 0) { + AMS_ABORT_UNLESS(mode == g_initialize_mode); + g_initialize_count++; + R_SUCCEED(); + } + + #if defined(ATMOSPHERE_OS_HORIZON) + switch (mode) { + case InitializeMode_Normal: __nx_time_service_type = ::TimeServiceType_User; break; + case InitializeMode_Menu: __nx_time_service_type = ::TimeServiceType_Menu; break; + case InitializeMode_System: __nx_time_service_type = ::TimeServiceType_System; break; + case InitializeMode_Repair: __nx_time_service_type = ::TimeServiceType_Repair; break; + case InitializeMode_SystemUser: __nx_time_service_type = ::TimeServiceType_SystemUser; break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + R_TRY(::timeInitialize()); + #else + // TODO: Real AMS_ABORT("TODO"); + #endif + + g_initialize_count++; + g_initialize_mode = mode; + R_SUCCEED(); + } + + } + + Result Initialize() { + R_RETURN(InitializeImpl(InitializeMode_Normal)); + } + + Result InitializeForSystem() { + R_RETURN(InitializeImpl(InitializeMode_System)); + } + + Result InitializeForSystemUser() { + if (hos::GetVersion() >= hos::Version_9_0_0) { + R_RETURN(InitializeImpl(InitializeMode_SystemUser)); + } else { + R_RETURN(InitializeImpl(InitializeMode_Normal)); + } + } + + Result Finalize() { + std::scoped_lock lk(g_initialize_mutex); + + if (g_initialize_count > 0) { + if ((--g_initialize_count) == 0) { + #if defined(ATMOSPHERE_OS_HORIZON) + ::timeExit(); + #else + // TODO: Real AMS_ABORT("TODO"); + #endif + g_initialize_mode = InitializeMode_None; + } + } + + R_SUCCEED(); + } + + bool IsInitialized() { + std::scoped_lock lk(g_initialize_mutex); + + return g_initialize_count > 0; + } + + bool IsValidDate(int year, int month, int day) { + return impl::util::IsValidDate(year, month, day); + } + + Result GetElapsedSecondsBetween(s64 *out, const SteadyClockTimePoint &from, const SteadyClockTimePoint &to) { + R_RETURN(impl::util::GetSpanBetween(out, from, to)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/time_calendar_time.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/time_calendar_time.cpp new file mode 100644 index 00000000..f3c9fa69 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/time_calendar_time.cpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::time { + + bool CalendarTime::IsValid() const { + return time::IsValidDate(this->year, this->month, this->day); + } + + CalendarTime &CalendarTime::operator+=(const TimeSpan &ts) { + *this = ToCalendarTimeInUtc(ToPosixTimeFromUtc(*this) + ts); + return *this; + } + + CalendarTime &CalendarTime::operator-=(const TimeSpan &ts) { + *this = ToCalendarTimeInUtc(ToPosixTimeFromUtc(*this) - ts); + return *this; + } + + CalendarTime operator+(const CalendarTime &lhs, const TimeSpan &rhs) { + return ToCalendarTimeInUtc(ToPosixTimeFromUtc(lhs) + rhs); + } + + CalendarTime operator-(const CalendarTime &lhs, const TimeSpan &rhs) { + return ToCalendarTimeInUtc(ToPosixTimeFromUtc(lhs) - rhs); + } + + TimeSpan operator-(const CalendarTime &lhs, const CalendarTime &rhs) { + return ToPosixTimeFromUtc(lhs) - ToPosixTimeFromUtc(rhs); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/time_standard_network_system_clock.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/time_standard_network_system_clock.cpp new file mode 100644 index 00000000..695d94bf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/time_standard_network_system_clock.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::time { + + Result StandardNetworkSystemClock::GetCurrentTime(PosixTime *out) { + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(sizeof(*out) == sizeof(u64)); + R_RETURN(::timeGetCurrentTime(::TimeType_NetworkSystemClock, reinterpret_cast<u64 *>(out))); + #else + AMS_UNUSED(out); + AMS_ABORT("TODO"); + #endif + } + + StandardNetworkSystemClock::time_point StandardNetworkSystemClock::now() { + PosixTime posix_time = {}; + if (R_FAILED(GetCurrentTime(std::addressof(posix_time)))) { + posix_time.value = 0; + } + + return time_point(duration(posix_time.value)); + } + + std::time_t StandardNetworkSystemClock::to_time_t(const StandardNetworkSystemClock::time_point &t) { + return static_cast<std::time_t>(std::chrono::duration_cast<std::chrono::seconds>(t.time_since_epoch()).count()); + } + + StandardNetworkSystemClock::time_point StandardNetworkSystemClock::from_time_t(std::time_t t) { + return time_point(duration(t)); + } + + /* TODO: Result StandardNetworkSystemClock::GetSystemClockContext(SystemClockContext *out); */ + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/time_standard_steady_clock.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/time_standard_steady_clock.cpp new file mode 100644 index 00000000..869f8f71 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/time_standard_steady_clock.cpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::time { + + Result GetStandardSteadyClockCurrentTimePoint(SteadyClockTimePoint *out) { + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(sizeof(*out) == sizeof(::TimeSteadyClockTimePoint)); + R_RETURN(::timeGetStandardSteadyClockTimePoint(reinterpret_cast<::TimeSteadyClockTimePoint *>(out))); + #else + AMS_UNUSED(out); + AMS_ABORT("TODO"); + #endif + } + + TimeSpan GetStandardSteadyClockInternalOffset() { + TimeSpanType offset; + + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(sizeof(TimeSpanType) == sizeof(s64)); + R_ABORT_UNLESS(::timeGetStandardSteadyClockInternalOffset(reinterpret_cast<s64 *>(std::addressof(offset)))); + #else + AMS_UNUSED(offset); + AMS_ABORT("TODO"); + #endif + + return offset; + } + + Result StandardSteadyClock::GetCurrentTimePoint(SteadyClockTimePoint *out) { + R_RETURN(GetStandardSteadyClockCurrentTimePoint(out)); + } + + StandardSteadyClock::time_point StandardSteadyClock::now() { + SteadyClockTimePoint steady_clock_time_point = {0, util::InvalidUuid}; + if (R_FAILED(StandardSteadyClock::GetCurrentTimePoint(std::addressof(steady_clock_time_point)))) { + steady_clock_time_point.value = 0; + steady_clock_time_point.source_id = util::InvalidUuid; + } + + return StandardSteadyClock::time_point(StandardSteadyClock::duration(steady_clock_time_point.value)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/time_standard_user_system_clock.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/time_standard_user_system_clock.cpp new file mode 100644 index 00000000..a5137d75 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/time_standard_user_system_clock.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::time { + + Result StandardUserSystemClock::GetCurrentTime(PosixTime *out) { + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(sizeof(*out) == sizeof(u64)); + R_RETURN(::timeGetCurrentTime(::TimeType_UserSystemClock, reinterpret_cast<u64 *>(out))); + #else + AMS_UNUSED(out); + AMS_ABORT("TODO"); + #endif + } + + StandardUserSystemClock::time_point StandardUserSystemClock::now() { + PosixTime posix_time = {}; + if (R_FAILED(GetCurrentTime(std::addressof(posix_time)))) { + posix_time.value = 0; + } + + return time_point(duration(posix_time.value)); + } + + std::time_t StandardUserSystemClock::to_time_t(const StandardUserSystemClock::time_point &t) { + return static_cast<std::time_t>(std::chrono::duration_cast<std::chrono::seconds>(t.time_since_epoch()).count()); + } + + StandardUserSystemClock::time_point StandardUserSystemClock::from_time_t(std::time_t t) { + return time_point(duration(t)); + } + + /* TODO: Result StandardUserSystemClock::GetSystemClockContext(SystemClockContext *out); */ + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/time_timezone_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/time_timezone_api.cpp new file mode 100644 index 00000000..d7afed06 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/time/time_timezone_api.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::time { + + CalendarTime ToCalendarTimeInUtc(const PosixTime &posix_time) { + return impl::util::ToCalendarTimeInUtc(posix_time); + } + + PosixTime ToPosixTimeFromUtc(const CalendarTime &calendar_time) { + return impl::util::ToPosixTimeFromUtc(calendar_time); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_api.cpp new file mode 100644 index 00000000..0be30741 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_api.cpp @@ -0,0 +1,579 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include <stratosphere/updater.hpp> + +#include "updater_bis_save.hpp" +#include "updater_files.hpp" +#include "updater_paths.hpp" + +namespace ams::updater { + + namespace { + + /* Validation Prototypes. */ + Result ValidateWorkBuffer(const void *work_buffer, size_t work_buffer_size); + + /* Configuration Prototypes. */ + bool HasEks(BootImageUpdateType boot_image_update_type); + bool HasAutoRcmPreserve(BootImageUpdateType boot_image_update_type); + ncm::ContentMetaType GetContentMetaType(BootModeType mode); + + /* Verification Prototypes. */ + Result GetVerificationState(VerificationState *out, void *work_buffer, size_t work_buffer_size); + Result VerifyBootImages(ncm::SystemDataId data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type); + Result VerifyBootImagesNormal(ncm::SystemDataId data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type); + Result VerifyBootImagesSafe(ncm::SystemDataId data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type); + + /* Update Prototypes. */ + Result SetVerificationNeeded(BootModeType mode, void *work_buffer, size_t work_buffer_size, bool needed); + Result UpdateBootImagesNormal(ncm::SystemDataId data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type); + Result UpdateBootImagesSafe(ncm::SystemDataId data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type); + + /* Package helpers. */ + Result ValidateBctFileHash(Boot0Accessor &accessor, Boot0Partition which, const void *stored_hash, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type); + Result GetPackage2Hash(void *dst_hash, size_t package2_size, void *work_buffer, size_t work_buffer_size, Package2Type which); + Result WritePackage2(void *work_buffer, size_t work_buffer_size, Package2Type which, BootImageUpdateType boot_image_update_type); + Result CompareHash(const void *lhs, const void *rhs, size_t size); + + /* Implementations. */ + Result ValidateWorkBuffer(const void *work_buffer, size_t work_buffer_size) { + R_UNLESS(work_buffer_size >= BctSize + EksSize, updater::ResultTooSmallWorkBuffer()); + R_UNLESS(util::IsAligned(work_buffer, os::MemoryPageSize), updater::ResultNotAlignedWorkBuffer()); + R_UNLESS(util::IsAligned(work_buffer_size, 0x200), updater::ResultNotAlignedWorkBuffer()); + R_SUCCEED(); + } + + bool HasEks(BootImageUpdateType boot_image_update_type) { + switch (boot_image_update_type) { + case BootImageUpdateType::Erista: + return true; + case BootImageUpdateType::Mariko: + return false; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + bool HasAutoRcmPreserve(BootImageUpdateType boot_image_update_type) { + switch (boot_image_update_type) { + case BootImageUpdateType::Erista: + return true; + case BootImageUpdateType::Mariko: + return false; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + ncm::ContentMetaType GetContentMetaType(BootModeType mode) { + switch (mode) { + case BootModeType::Normal: + return ncm::ContentMetaType::BootImagePackage; + case BootModeType::Safe: + return ncm::ContentMetaType::BootImagePackageSafe; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + Result GetVerificationState(VerificationState *out, void *work_buffer, size_t work_buffer_size) { + /* Always set output to true before doing anything else. */ + out->needs_verify_normal = true; + out->needs_verify_safe = true; + + /* Ensure work buffer is big enough for us to do what we want to do. */ + R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size)); + + /* Initialize boot0 save accessor. */ + BisSave save; + R_TRY(save.Initialize(work_buffer, work_buffer_size)); + ON_SCOPE_EXIT { save.Finalize(); }; + + /* Load save from NAND. */ + R_TRY(save.Load()); + + /* Read data from save. */ + out->needs_verify_normal = save.GetNeedsVerification(BootModeType::Normal); + out->needs_verify_safe = save.GetNeedsVerification(BootModeType::Safe); + R_SUCCEED(); + } + + Result VerifyBootImagesAndRepairIfNeeded(bool *out_repaired, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) { + /* Get system data id for boot images (819/81A/81B/81C). */ + ncm::SystemDataId bip_data_id = {}; + R_TRY(GetBootImagePackageId(std::addressof(bip_data_id), mode, work_buffer, work_buffer_size)); + + /* Verify the boot images in NAND. */ + R_TRY_CATCH(VerifyBootImages(bip_data_id, mode, work_buffer, work_buffer_size, boot_image_update_type)) { + R_CATCH(ResultNeedsRepairBootImages) { + /* Perform repair. */ + *out_repaired = true; + R_TRY(UpdateBootImagesFromPackage(bip_data_id, mode, work_buffer, work_buffer_size, boot_image_update_type)); + } + } R_END_TRY_CATCH; + + /* We've either just verified or just repaired. Either way, we don't need to verify any more. */ + R_RETURN(SetVerificationNeeded(mode, work_buffer, work_buffer_size, false)); + } + + Result VerifyBootImages(ncm::SystemDataId data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) { + switch (mode) { + case BootModeType::Normal: + R_RETURN(VerifyBootImagesNormal(data_id, work_buffer, work_buffer_size, boot_image_update_type)); + case BootModeType::Safe: + R_RETURN(VerifyBootImagesSafe(data_id, work_buffer, work_buffer_size, boot_image_update_type)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + Result VerifyBootImagesNormal(ncm::SystemDataId data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) { + /* Ensure work buffer is big enough for us to do what we want to do. */ + R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size)); + + /* Mount the boot image package. */ + const char *mount_name = GetMountName(); + R_TRY_CATCH(fs::MountSystemData(mount_name, data_id)) { + R_CONVERT(fs::ResultTargetNotFound, updater::ResultBootImagePackageNotFound()) + } R_END_TRY_CATCH; + ON_SCOPE_EXIT { fs::Unmount(mount_name); }; + + /* Read and validate hashes of boot images. */ + { + size_t size; + u8 nand_hash[crypto::Sha256Generator::HashSize]; + u8 file_hash[crypto::Sha256Generator::HashSize]; + + Boot0Accessor boot0_accessor; + R_TRY(boot0_accessor.Initialize()); + ON_SCOPE_EXIT { boot0_accessor.Finalize(); }; + + /* Detect the use of custom public key. */ + /* If custom public key is present, we want to validate BCT Sub but not Main */ + bool custom_public_key = false; + R_TRY(boot0_accessor.DetectCustomPublicKey(std::addressof(custom_public_key), work_buffer, boot_image_update_type)); + + /* Compare BCT hashes. */ + if (!custom_public_key) { + R_TRY(boot0_accessor.GetHash(nand_hash, BctSize, work_buffer, work_buffer_size, Boot0Partition::BctNormalMain)); + R_TRY(ValidateBctFileHash(boot0_accessor, Boot0Partition::BctNormalMain, nand_hash, work_buffer, work_buffer_size, boot_image_update_type)); + } + + /* Compare BCT Sub hashes. */ + R_TRY(boot0_accessor.GetHash(nand_hash, BctSize, work_buffer, work_buffer_size, Boot0Partition::BctNormalSub)); + R_TRY(ValidateBctFileHash(boot0_accessor, Boot0Partition::BctNormalSub, nand_hash, work_buffer, work_buffer_size, boot_image_update_type)); + + /* Compare Package1 Normal/Sub hashes. */ + R_TRY(GetFileHash(std::addressof(size), file_hash, GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size)); + + R_TRY(boot0_accessor.GetHash(nand_hash, size, work_buffer, work_buffer_size, Boot0Partition::Package1NormalMain)); + R_TRY(CompareHash(file_hash, nand_hash, sizeof(file_hash))); + + R_TRY(boot0_accessor.GetHash(nand_hash, size, work_buffer, work_buffer_size, Boot0Partition::Package1NormalSub)); + R_TRY(CompareHash(file_hash, nand_hash, sizeof(file_hash))); + + /* Compare Package2 Normal/Sub hashes. */ + R_TRY(GetFileHash(std::addressof(size), file_hash, GetPackage2Path(boot_image_update_type), work_buffer, work_buffer_size)); + + R_TRY(GetPackage2Hash(nand_hash, size, work_buffer, work_buffer_size, Package2Type::NormalMain)); + R_TRY(CompareHash(file_hash, nand_hash, sizeof(file_hash))); + + R_TRY(GetPackage2Hash(nand_hash, size, work_buffer, work_buffer_size, Package2Type::NormalSub)); + R_TRY(CompareHash(file_hash, nand_hash, sizeof(file_hash))); + } + + R_SUCCEED(); + } + + Result VerifyBootImagesSafe(ncm::SystemDataId data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) { + /* Ensure work buffer is big enough for us to do what we want to do. */ + R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size)); + + /* Mount the boot image package. */ + const char *mount_name = GetMountName(); + R_TRY_CATCH(fs::MountSystemData(mount_name, data_id)) { + R_CONVERT(fs::ResultTargetNotFound, updater::ResultBootImagePackageNotFound()) + } R_END_TRY_CATCH; + ON_SCOPE_EXIT { fs::Unmount(mount_name); }; + + /* Read and validate hashes of boot images. */ + { + size_t size; + u8 nand_hash[crypto::Sha256Generator::HashSize]; + u8 file_hash[crypto::Sha256Generator::HashSize]; + + Boot0Accessor boot0_accessor; + R_TRY(boot0_accessor.Initialize()); + ON_SCOPE_EXIT { boot0_accessor.Finalize(); }; + + Boot1Accessor boot1_accessor; + R_TRY(boot1_accessor.Initialize()); + ON_SCOPE_EXIT { boot1_accessor.Finalize(); }; + + /* Detect the use of custom public key. */ + /* If custom public key is present, we want to validate BCT Sub but not Main */ + bool custom_public_key = false; + R_TRY(boot0_accessor.DetectCustomPublicKey(std::addressof(custom_public_key), work_buffer, boot_image_update_type)); + + /* Compare BCT hashes. */ + if (!custom_public_key) { + R_TRY(boot0_accessor.GetHash(nand_hash, BctSize, work_buffer, work_buffer_size, Boot0Partition::BctSafeMain)); + R_TRY(ValidateBctFileHash(boot0_accessor, Boot0Partition::BctSafeMain, nand_hash, work_buffer, work_buffer_size, boot_image_update_type)); + } + + /* Compare BCT Sub hashes. */ + R_TRY(boot0_accessor.GetHash(nand_hash, BctSize, work_buffer, work_buffer_size, Boot0Partition::BctSafeSub)); + R_TRY(ValidateBctFileHash(boot0_accessor, Boot0Partition::BctSafeSub, nand_hash, work_buffer, work_buffer_size, boot_image_update_type)); + + /* Compare Package1 Normal/Sub hashes. */ + R_TRY(GetFileHash(std::addressof(size), file_hash, GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size)); + + R_TRY(boot1_accessor.GetHash(nand_hash, size, work_buffer, work_buffer_size, Boot1Partition::Package1SafeMain)); + R_TRY(CompareHash(file_hash, nand_hash, sizeof(file_hash))); + + R_TRY(boot1_accessor.GetHash(nand_hash, size, work_buffer, work_buffer_size, Boot1Partition::Package1SafeSub)); + R_TRY(CompareHash(file_hash, nand_hash, sizeof(file_hash))); + + /* Compare Package2 Normal/Sub hashes. */ + R_TRY(GetFileHash(std::addressof(size), file_hash, GetPackage2Path(boot_image_update_type), work_buffer, work_buffer_size)); + + R_TRY(GetPackage2Hash(nand_hash, size, work_buffer, work_buffer_size, Package2Type::SafeMain)); + R_TRY(CompareHash(file_hash, nand_hash, sizeof(file_hash))); + + R_TRY(GetPackage2Hash(nand_hash, size, work_buffer, work_buffer_size, Package2Type::SafeSub)); + R_TRY(CompareHash(file_hash, nand_hash, sizeof(file_hash))); + } + + R_SUCCEED(); + } + + Result UpdateBootImagesNormal(ncm::SystemDataId data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) { + /* Ensure work buffer is big enough for us to do what we want to do. */ + R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size)); + + /* Mount the boot image package. */ + const char *mount_name = GetMountName(); + R_TRY_CATCH(fs::MountSystemData(mount_name, data_id)) { + R_CONVERT(fs::ResultTargetNotFound, updater::ResultBootImagePackageNotFound()) + } R_END_TRY_CATCH; + ON_SCOPE_EXIT { fs::Unmount(mount_name); }; + + { + Boot0Accessor boot0_accessor; + R_TRY(boot0_accessor.Initialize()); + ON_SCOPE_EXIT { boot0_accessor.Finalize(); }; + + /* Detect the use of custom public key. */ + /* If custom public key is present, we want to update BCT Sub but not Main */ + bool custom_public_key = false; + R_TRY(boot0_accessor.DetectCustomPublicKey(std::addressof(custom_public_key), work_buffer, boot_image_update_type)); + + /* Write Package1 sub. */ + R_TRY(boot0_accessor.Clear(work_buffer, work_buffer_size, Boot0Partition::Package1NormalSub)); + R_TRY(boot0_accessor.Write(GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size, Boot0Partition::Package1NormalSub)); + + /* Write Package2 sub. */ + R_TRY(WritePackage2(work_buffer, work_buffer_size, Package2Type::NormalSub, boot_image_update_type)); + + /* Write BCT sub + BCT main, in that order. */ + { + void *bct = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(work_buffer) + 0); + void *work = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(work_buffer) + BctSize); + + size_t size; + R_TRY(ReadFile(std::addressof(size), bct, BctSize, GetBctPath(boot_image_update_type))); + if (HasEks(boot_image_update_type)) { + R_TRY(boot0_accessor.UpdateEks(bct, work)); + } + + /* Only preserve autorcm if on a unit with unpatched rcm bug. */ + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + if (HasAutoRcmPreserve(boot_image_update_type) && !exosphere::IsRcmBugPatched()) { + R_TRY(boot0_accessor.PreserveAutoRcm(bct, work, Boot0Partition::BctNormalSub)); + R_TRY(boot0_accessor.Write(bct, BctSize, Boot0Partition::BctNormalSub)); + if (!custom_public_key) { + R_TRY(boot0_accessor.PreserveAutoRcm(bct, work, Boot0Partition::BctNormalMain)); + R_TRY(boot0_accessor.Write(bct, BctSize, Boot0Partition::BctNormalMain)); + } + } else { + #else + { + #endif + R_TRY(boot0_accessor.Write(bct, BctSize, Boot0Partition::BctNormalSub)); + if (!custom_public_key) { + R_TRY(boot0_accessor.Write(bct, BctSize, Boot0Partition::BctNormalMain)); + } + } + } + + /* Write Package2 main. */ + R_TRY(WritePackage2(work_buffer, work_buffer_size, Package2Type::NormalMain, boot_image_update_type)); + + /* Write Package1 main. */ + R_TRY(boot0_accessor.Clear(work_buffer, work_buffer_size, Boot0Partition::Package1NormalMain)); + R_TRY(boot0_accessor.Write(GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size, Boot0Partition::Package1NormalMain)); + } + + R_SUCCEED(); + } + + Result UpdateBootImagesSafe(ncm::SystemDataId data_id, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) { + /* Ensure work buffer is big enough for us to do what we want to do. */ + R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size)); + + /* Mount the boot image package. */ + const char *mount_name = GetMountName(); + R_TRY_CATCH(fs::MountSystemData(mount_name, data_id)) { + R_CONVERT(fs::ResultTargetNotFound, updater::ResultBootImagePackageNotFound()) + } R_END_TRY_CATCH; + ON_SCOPE_EXIT { fs::Unmount(mount_name); }; + + { + Boot0Accessor boot0_accessor; + R_TRY(boot0_accessor.Initialize()); + ON_SCOPE_EXIT { boot0_accessor.Finalize(); }; + + Boot1Accessor boot1_accessor; + R_TRY(boot1_accessor.Initialize()); + ON_SCOPE_EXIT { boot1_accessor.Finalize(); }; + + /* Detect the use of custom public key. */ + /* If custom public key is present, we want to update BCT Sub but not Main */ + bool custom_public_key = false; + R_TRY(boot0_accessor.DetectCustomPublicKey(std::addressof(custom_public_key), work_buffer, boot_image_update_type)); + + /* Write Package1 sub. */ + R_TRY(boot1_accessor.Clear(work_buffer, work_buffer_size, Boot1Partition::Package1SafeSub)); + R_TRY(boot1_accessor.Write(GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size, Boot1Partition::Package1SafeSub)); + + /* Write Package2 sub. */ + R_TRY(WritePackage2(work_buffer, work_buffer_size, Package2Type::SafeSub, boot_image_update_type)); + + /* Write BCT sub + BCT main, in that order. */ + { + void *bct = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(work_buffer) + 0); + void *work = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(work_buffer) + BctSize); + + size_t size; + R_TRY(ReadFile(std::addressof(size), bct, BctSize, GetBctPath(boot_image_update_type))); + if (HasEks(boot_image_update_type)) { + R_TRY(boot0_accessor.UpdateEks(bct, work)); + } + /* Only preserve autorcm if on a unit with unpatched rcm bug. */ + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + if (HasAutoRcmPreserve(boot_image_update_type) && !exosphere::IsRcmBugPatched()) { + R_TRY(boot0_accessor.PreserveAutoRcm(bct, work, Boot0Partition::BctSafeSub)); + R_TRY(boot0_accessor.Write(bct, BctSize, Boot0Partition::BctSafeSub)); + if (!custom_public_key) { + R_TRY(boot0_accessor.PreserveAutoRcm(bct, work, Boot0Partition::BctSafeMain)); + R_TRY(boot0_accessor.Write(bct, BctSize, Boot0Partition::BctSafeMain)); + } + } else { + #else + { + #endif + R_TRY(boot0_accessor.Write(bct, BctSize, Boot0Partition::BctSafeSub)); + if (!custom_public_key) { + R_TRY(boot0_accessor.Write(bct, BctSize, Boot0Partition::BctSafeMain)); + } + } + } + + /* Write Package2 main. */ + R_TRY(WritePackage2(work_buffer, work_buffer_size, Package2Type::SafeMain, boot_image_update_type)); + + /* Write Package1 main. */ + R_TRY(boot1_accessor.Clear(work_buffer, work_buffer_size, Boot1Partition::Package1SafeMain)); + R_TRY(boot1_accessor.Write(GetPackage1Path(boot_image_update_type), work_buffer, work_buffer_size, Boot1Partition::Package1SafeMain)); + } + + R_SUCCEED(); + } + + Result SetVerificationNeeded(BootModeType mode, void *work_buffer, size_t work_buffer_size, bool needed) { + /* Ensure work buffer is big enough for us to do what we want to do. */ + R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size)); + + /* Initialize boot0 save accessor. */ + BisSave save; + R_TRY(save.Initialize(work_buffer, work_buffer_size)); + ON_SCOPE_EXIT { save.Finalize(); }; + + /* Load save from NAND. */ + R_TRY(save.Load()); + + /* Set whether we need to verify, then save to nand. */ + save.SetNeedsVerification(mode, needed); + R_TRY(save.Save()); + + R_SUCCEED(); + } + + Result ValidateBctFileHash(Boot0Accessor &accessor, Boot0Partition which, const void *stored_hash, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) { + /* Ensure work buffer is big enough for us to do what we want to do. */ + R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size)); + + void *bct = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(work_buffer) + 0); + void *work = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(work_buffer) + BctSize); + + size_t size; + R_TRY(ReadFile(std::addressof(size), bct, BctSize, GetBctPath(boot_image_update_type))); + if (HasEks(boot_image_update_type)) { + R_TRY(accessor.UpdateEks(bct, work)); + } + if (HasAutoRcmPreserve(boot_image_update_type)) { + R_TRY(accessor.PreserveAutoRcm(bct, work, which)); + } + + u8 file_hash[crypto::Sha256Generator::HashSize]; + crypto::GenerateSha256(file_hash, sizeof(file_hash), bct, BctSize); + + R_RETURN(CompareHash(file_hash, stored_hash, sizeof(file_hash))); + } + + Result GetPackage2Hash(void *dst_hash, size_t package2_size, void *work_buffer, size_t work_buffer_size, Package2Type which) { + Package2Accessor accessor(which); + R_TRY(accessor.Initialize()); + ON_SCOPE_EXIT { accessor.Finalize(); }; + + R_RETURN(accessor.GetHash(dst_hash, package2_size, work_buffer, work_buffer_size, Package2Partition::Package2)); + } + + Result WritePackage2(void *work_buffer, size_t work_buffer_size, Package2Type which, BootImageUpdateType boot_image_update_type) { + Package2Accessor accessor(which); + R_TRY(accessor.Initialize()); + ON_SCOPE_EXIT { accessor.Finalize(); }; + + R_RETURN(accessor.Write(GetPackage2Path(boot_image_update_type), work_buffer, work_buffer_size, Package2Partition::Package2)); + } + + Result CompareHash(const void *lhs, const void *rhs, size_t size) { + R_UNLESS(crypto::IsSameBytes(lhs, rhs, size), updater::ResultNeedsRepairBootImages()); + R_SUCCEED(); + } + + } + + BootImageUpdateType GetBootImageUpdateType(spl::HardwareType hw_type) { + switch (hw_type) { + case spl::HardwareType::Icosa: + case spl::HardwareType::Copper: + return BootImageUpdateType::Erista; + case spl::HardwareType::Hoag: + case spl::HardwareType::Iowa: + case spl::HardwareType::Calcio: + case spl::HardwareType::Aula: + return BootImageUpdateType::Mariko; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + BootImageUpdateType GetBootImageUpdateType(int boot_image_update_type) { + switch (boot_image_update_type) { + case 0: + return BootImageUpdateType::Erista; + case 1: + return BootImageUpdateType::Mariko; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + Result GetBootImagePackageId(ncm::SystemDataId *out_data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size) { + /* Ensure we can read content metas. */ + constexpr size_t MaxContentMetas = 0x40; + AMS_ABORT_UNLESS(work_buffer_size >= sizeof(ncm::ContentMetaKey) * MaxContentMetas); + + /* Open NAND System meta database, list contents. */ + ncm::ContentMetaDatabase db; + R_TRY(ncm::OpenContentMetaDatabase(std::addressof(db), ncm::StorageId::BuiltInSystem)); + + ncm::ContentMetaKey *keys = reinterpret_cast<ncm::ContentMetaKey *>(work_buffer); + const auto content_meta_type = GetContentMetaType(mode); + + auto count = db.ListContentMeta(keys, MaxContentMetas, content_meta_type); + R_UNLESS(count.total > 0, updater::ResultBootImagePackageNotFound()); + + /* Output is sorted, return the lowest valid exfat entry. */ + if (count.total > 1) { + for (auto i = 0; i < count.total; i++) { + u8 attr; + R_TRY(db.GetAttributes(std::addressof(attr), keys[i])); + + if (attr & ncm::ContentMetaAttribute_IncludesExFatDriver) { + out_data_id->value = keys[i].id; + R_SUCCEED(); + } + } + } + + /* If there's only one entry or no exfat entries, return that entry. */ + out_data_id->value = keys[0].id; + R_SUCCEED(); + } + + Result MarkVerifyingRequired(BootModeType mode, void *work_buffer, size_t work_buffer_size) { + R_RETURN(SetVerificationNeeded(mode, work_buffer, work_buffer_size, true)); + } + + Result MarkVerified(BootModeType mode, void *work_buffer, size_t work_buffer_size) { + R_RETURN(SetVerificationNeeded(mode, work_buffer, work_buffer_size, false)); + } + + Result UpdateBootImagesFromPackage(ncm::SystemDataId data_id, BootModeType mode, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) { + switch (mode) { + case BootModeType::Normal: + R_RETURN(UpdateBootImagesNormal(data_id, work_buffer, work_buffer_size, boot_image_update_type)); + case BootModeType::Safe: + R_RETURN(UpdateBootImagesSafe(data_id, work_buffer, work_buffer_size, boot_image_update_type)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + Result VerifyBootImagesAndRepairIfNeeded(bool *out_repaired_normal, bool *out_repaired_safe, void *work_buffer, size_t work_buffer_size, BootImageUpdateType boot_image_update_type) { + /* Always set output to false before doing anything else. */ + *out_repaired_normal = false; + *out_repaired_safe = false; + + /* Ensure work buffer is big enough for us to do what we want to do. */ + R_TRY(ValidateWorkBuffer(work_buffer, work_buffer_size)); + + /* Get verification state from NAND. */ + VerificationState verification_state; + R_TRY(GetVerificationState(std::addressof(verification_state), work_buffer, work_buffer_size)); + + /* If we don't need to verify anything, we're done. */ + if (!verification_state.needs_verify_normal && !verification_state.needs_verify_safe) { + R_SUCCEED(); + } + + /* Get a session to ncm. */ + ncm::Initialize(); + ON_SCOPE_EXIT { ncm::Finalize(); }; + + /* Verify normal, verify safe as needed. */ + if (verification_state.needs_verify_normal) { + R_TRY_CATCH(VerifyBootImagesAndRepairIfNeeded(out_repaired_normal, BootModeType::Normal, work_buffer, work_buffer_size, boot_image_update_type)) { + R_CATCH(ResultBootImagePackageNotFound) { /* Nintendo considers failure to locate bip a success. TODO: don't do that? */ } + } R_END_TRY_CATCH; + } + + if (verification_state.needs_verify_safe) { + R_TRY_CATCH(VerifyBootImagesAndRepairIfNeeded(out_repaired_safe, BootModeType::Safe, work_buffer, work_buffer_size, boot_image_update_type)) { + R_CATCH(ResultBootImagePackageNotFound) { /* Nintendo considers failure to locate bip a success. TODO: don't do that? */ } + } R_END_TRY_CATCH; + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_bis_management.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_bis_management.cpp new file mode 100644 index 00000000..354829ed --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_bis_management.cpp @@ -0,0 +1,198 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "updater_bis_management.hpp" + +namespace ams::updater { + + namespace { + + /* Recognize special public key (https://gist.github.com/SciresM/16b63ac1d80494522bdba2c57995257c). */ + /* P = 19 */ + /* Q = 1696986749729493925354392349339746171297507422986462747526968361144447230710192316397327889522451749459854070558277878297255552508603806832852079596337539247651161831569525505882103311631577368514276343192042634740927726070847704397913856975832811679847928433261678072951551065705680482548543833651752439700272736498378724153330763357721354498194000536297732323628263256733931353143625854828275237159155585342783077681713929284136658773985266864804093157854331138230313706015557050002740810464618031715670281442110238274404626065924786185264268216336867948322976979393032640085259926883014490947373494538254895109731 */ + /* N = 0xFF696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696959 */ + /* E = 0x10001 */ + /* D = 6512128715229088976470211610075969347035078304643231077895577077900787352712063823560162578441773733649014439616165727455431015055675770987914713980812453585413988983206576233689754710500864883529402371292948326392791238474661859182717295176679567362482790015587820446999760239570255254879359445627372805817473978644067558931078225451477635089763009580492462097185005355990612929951162366081631888011031830742459571000203341001926135389508196521518687349554188686396554248868403128728646457247197407637887195043486221295751496667162366700477934591694110831002874992896076061627516220934290742867397720103040314639313 */ + + constexpr const u8 CustomPublicKey[0x100] = { + 0x59, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0xFF, + }; + + } + + Result BisAccessor::Initialize() { + R_RETURN(fs::OpenBisPartition(std::addressof(m_storage), m_partition_id)); + } + + void BisAccessor::Finalize() { + /* ... */ + } + + Result BisAccessor::Read(void *dst, size_t size, u64 offset) { + AMS_ABORT_UNLESS((offset % SectorAlignment) == 0); + R_RETURN(m_storage->Read(static_cast<u32>(offset), dst, size)); + } + + Result BisAccessor::Write(u64 offset, const void *src, size_t size) { + AMS_ABORT_UNLESS((offset % SectorAlignment) == 0); + R_RETURN(m_storage->Write(static_cast<u32>(offset), src, size)); + } + + Result BisAccessor::Write(u64 offset, size_t size, const char *bip_path, void *work_buffer, size_t work_buffer_size) { + AMS_ABORT_UNLESS((offset % SectorAlignment) == 0); + AMS_ABORT_UNLESS((work_buffer_size % SectorAlignment) == 0); + + fs::FileHandle file; + R_TRY_CATCH(fs::OpenFile(std::addressof(file), bip_path, fs::OpenMode_Read)) { + R_CONVERT(fs::ResultPathNotFound, updater::ResultInvalidBootImagePackage()) + } R_END_TRY_CATCH; + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + size_t written = 0; + while (true) { + std::memset(work_buffer, 0, work_buffer_size); + size_t read_size; + R_TRY(fs::ReadFile(std::addressof(read_size), file, written, work_buffer, work_buffer_size, fs::ReadOption())); + AMS_ABORT_UNLESS(written + read_size <= size); + + size_t aligned_size = ((read_size + SectorAlignment - 1) / SectorAlignment) * SectorAlignment; + R_TRY(this->Write(offset + written, work_buffer, aligned_size)); + written += read_size; + + if (read_size != work_buffer_size) { + break; + } + } + R_SUCCEED(); + } + + Result BisAccessor::Clear(u64 offset, u64 size, void *work_buffer, size_t work_buffer_size) { + AMS_ABORT_UNLESS((offset % SectorAlignment) == 0); + AMS_ABORT_UNLESS((work_buffer_size % SectorAlignment) == 0); + + std::memset(work_buffer, 0, work_buffer_size); + + size_t written = 0; + while (written < size) { + size_t cur_write_size = std::min<size_t>(work_buffer_size, size - written); + R_TRY(this->Write(offset + written, work_buffer, cur_write_size)); + written += cur_write_size; + } + R_SUCCEED(); + } + + Result BisAccessor::GetHash(void *dst, u64 offset, u64 size, u64 hash_size, void *work_buffer, size_t work_buffer_size) { + AMS_ABORT_UNLESS((offset % SectorAlignment) == 0); + AMS_ABORT_UNLESS((work_buffer_size % SectorAlignment) == 0); + + crypto::Sha256Generator generator; + generator.Initialize(); + + size_t total_read = 0; + while (total_read < hash_size) { + size_t cur_read_size = std::min<size_t>(work_buffer_size, size - total_read); + size_t cur_update_size = std::min<size_t>(cur_read_size, hash_size - total_read); + R_TRY(this->Read(work_buffer, cur_read_size, offset + total_read)); + generator.Update(work_buffer, cur_update_size); + + total_read += cur_read_size; + } + generator.GetHash(dst, hash_size); + + R_SUCCEED(); + } + + size_t Boot0Accessor::GetBootloaderVersion(void *bct) { + u32 version = *reinterpret_cast<u32 *>(reinterpret_cast<uintptr_t>(bct) + BctVersionOffset); + AMS_ABORT_UNLESS(version <= BctVersionMax); + return static_cast<size_t>(version); + } + + size_t Boot0Accessor::GetEksIndex(size_t bootloader_version) { + AMS_ABORT_UNLESS(bootloader_version <= BctVersionMax); + return (bootloader_version > 0) ? bootloader_version - 1 : 0; + } + + void Boot0Accessor::CopyEks(void *dst_bct, const void *src_eks, size_t eks_index) { + std::memcpy(reinterpret_cast<u8 *>(dst_bct) + BctEksOffset, reinterpret_cast<const u8 *>(src_eks) + eks_index * EksEntrySize, EksBlobSize); + } + + Result Boot0Accessor::UpdateEks(void *dst_bct, void *eks_work_buffer) { + size_t read_size; + R_TRY(this->Read(&read_size, eks_work_buffer, EksSize, Boot0Partition::Eks)); + + R_RETURN(this->UpdateEksManually(dst_bct, eks_work_buffer)); + } + + Result Boot0Accessor::UpdateEksManually(void *dst_bct, const void *src_eks) { + this->CopyEks(dst_bct, src_eks, GetEksIndex(GetBootloaderVersion(dst_bct))); + R_SUCCEED(); + } + + Result Boot0Accessor::PreserveAutoRcm(void *dst_bct, void *work_buffer, Boot0Partition which) { + std::memset(work_buffer, 0, BctSize); + + size_t read_size; + R_TRY(this->Read(&read_size, work_buffer, BctSize, which)); + + /* NOTE: AutoRcm is only viable on Erista, so hardcode erista offsets. */ + void *dst_pubk = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(dst_bct) + BctPubkOffsetErista); + void *src_pubk = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(work_buffer) + BctPubkOffsetErista); + std::memcpy(dst_pubk, src_pubk, BctPubkSize); + + R_SUCCEED(); + } + + Result Boot0Accessor::DetectCustomPublicKey(bool *out, void *work_buffer, BootImageUpdateType boot_image_update_type) { + std::memset(work_buffer, 0, BctSize); + + const size_t pubk_offset = GetBctPubkOffset(boot_image_update_type); + + size_t read_size; + R_TRY(this->Read(&read_size, work_buffer, BctSize, Boot0Partition::BctNormalMain)); + + if (std::memcmp(reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(work_buffer) + pubk_offset), CustomPublicKey, sizeof(CustomPublicKey)) != 0) { + *out = false; + R_SUCCEED(); + } + + R_TRY(this->Read(&read_size, work_buffer, BctSize, Boot0Partition::BctSafeMain)); + + if (std::memcmp(reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(work_buffer) + pubk_offset), CustomPublicKey, sizeof(CustomPublicKey)) != 0) { + *out = false; + R_SUCCEED(); + } + + *out = true; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_bis_management.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_bis_management.hpp new file mode 100644 index 00000000..8a6e978e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_bis_management.hpp @@ -0,0 +1,243 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> + +namespace ams::updater { + + class BisAccessor { + NON_COPYABLE(BisAccessor); + public: + static constexpr size_t SectorAlignment = 0x200; + private: + std::unique_ptr<fs::IStorage> m_storage; + const fs::BisPartitionId m_partition_id; + public: + explicit BisAccessor(fs::BisPartitionId id) : m_partition_id(id) { /* ... */ } + + public: + Result Initialize(); + void Finalize(); + protected: + Result Read(void *dst, size_t size, u64 offset); + Result Write(u64 offset, const void *src, size_t size); + Result Write(u64 offset, size_t size, const char *bip_path, void *work_buffer, size_t work_buffer_size); + Result Clear(u64 offset, u64 size, void *work_buffer, size_t work_buffer_size); + Result GetHash(void *dst, u64 offset, u64 size, u64 hash_size, void *work_buffer, size_t work_buffer_size); + }; + + template<typename EnumType> + struct OffsetSizeEntry { + EnumType which; + u64 offset; + size_t size; + }; + + enum class Boot0Partition { + BctNormalMain, + BctSafeMain, + BctNormalSub, + BctSafeSub, + BctSave, + Package1NormalMain, + Package1NormalSub, + Eks, + Count, + }; + + enum class Boot1Partition { + Package1SafeMain, + Package1SafeSub, + Package1RepairMain, + Package1RepairSub, + Count, + }; + + enum class Package2Partition { + BootConfig, + Package2, + Count, + }; + + struct Boot0Meta { + using EnumType = Boot0Partition; + using OffsetSizeType = OffsetSizeEntry<EnumType>; + + static constexpr size_t NumEntries = static_cast<size_t>(EnumType::Count); + static constexpr OffsetSizeType Entries[NumEntries] = { + {Boot0Partition::BctNormalMain, 0 * BctSize, BctSize}, + {Boot0Partition::BctSafeMain, 1 * BctSize, BctSize}, + {Boot0Partition::BctNormalSub, 2 * BctSize, BctSize}, + {Boot0Partition::BctSafeSub, 3 * BctSize, BctSize}, + {Boot0Partition::BctSave, 63 * BctSize, BctSize}, + {Boot0Partition::Package1NormalMain, 0x100000, 0x40000}, + {Boot0Partition::Package1NormalSub, 0x140000, 0x40000}, + {Boot0Partition::Eks, 0x180000, EksSize}, + }; + }; + + struct Boot1Meta { + using EnumType = Boot1Partition; + using OffsetSizeType = OffsetSizeEntry<EnumType>; + + static constexpr size_t NumEntries = static_cast<size_t>(EnumType::Count); + static constexpr OffsetSizeType Entries[NumEntries] = { + {Boot1Partition::Package1SafeMain, 0x00000, 0x40000}, + {Boot1Partition::Package1SafeSub, 0x40000, 0x40000}, + {Boot1Partition::Package1RepairMain, 0x80000, 0x40000}, + {Boot1Partition::Package1RepairSub, 0xC0000, 0x40000}, + }; + }; + + struct Package2Meta { + using EnumType = Package2Partition; + using OffsetSizeType = OffsetSizeEntry<EnumType>; + + static constexpr size_t NumEntries = static_cast<size_t>(EnumType::Count); + static constexpr OffsetSizeType Entries[NumEntries] = { + {Package2Partition::BootConfig, 0x0000, 0x004000}, + {Package2Partition::Package2, 0x4000, 0x7FC000}, + }; + }; + + template<typename Meta> + class PartitionAccessor : public BisAccessor { + NON_COPYABLE(PartitionAccessor); + public: + using EnumType = typename Meta::EnumType; + using OffsetSizeType = typename Meta::OffsetSizeType; + public: + explicit PartitionAccessor(fs::BisPartitionId id) : BisAccessor(id) { /* ... */ } + private: + constexpr const OffsetSizeType *FindEntry(EnumType which) { + const OffsetSizeType *entry = nullptr; + for (size_t i = 0; i < Meta::NumEntries; i++) { + if (Meta::Entries[i].which == which) { + entry = std::addressof(Meta::Entries[i]); + break; + } + } + + AMS_ABORT_UNLESS(entry != nullptr); + return entry; + } + public: + Result Read(size_t *out_size, void *dst, size_t size, EnumType which) { + const auto entry = FindEntry(which); + AMS_ABORT_UNLESS(size >= entry->size); + + ON_RESULT_SUCCESS { *out_size = entry->size; }; + + R_RETURN(BisAccessor::Read(dst, entry->size, entry->offset)); + } + + Result Write(const void *src, size_t size, EnumType which) { + const auto entry = FindEntry(which); + AMS_ABORT_UNLESS(size <= entry->size); + AMS_ABORT_UNLESS((size % BisAccessor::SectorAlignment) == 0); + R_RETURN(BisAccessor::Write(entry->offset, src, size)); + } + + Result Write(const char *bip_path, void *work_buffer, size_t work_buffer_size, EnumType which) { + const auto entry = FindEntry(which); + R_RETURN(BisAccessor::Write(entry->offset, entry->size, bip_path, work_buffer, work_buffer_size)); + } + + Result Clear(void *work_buffer, size_t work_buffer_size, EnumType which) { + const auto entry = FindEntry(which); + R_RETURN(BisAccessor::Clear(entry->offset, entry->size, work_buffer, work_buffer_size)); + } + + Result GetHash(void *dst, u64 hash_size, void *work_buffer, size_t work_buffer_size, EnumType which) { + const auto entry = FindEntry(which); + R_RETURN(BisAccessor::GetHash(dst, entry->offset, entry->size, hash_size, work_buffer, work_buffer_size)); + } + }; + + enum class Package2Type { + NormalMain, + NormalSub, + SafeMain, + SafeSub, + RepairMain, + RepairSub, + }; + + static constexpr fs::BisPartitionId GetPackage2StorageId(Package2Type which) { + switch (which) { + case Package2Type::NormalMain: + return fs::BisPartitionId::BootConfigAndPackage2Part1; + case Package2Type::NormalSub: + return fs::BisPartitionId::BootConfigAndPackage2Part2; + case Package2Type::SafeMain: + return fs::BisPartitionId::BootConfigAndPackage2Part3; + case Package2Type::SafeSub: + return fs::BisPartitionId::BootConfigAndPackage2Part4; + case Package2Type::RepairMain: + return fs::BisPartitionId::BootConfigAndPackage2Part5; + case Package2Type::RepairSub: + return fs::BisPartitionId::BootConfigAndPackage2Part6; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + class Boot0Accessor : public PartitionAccessor<Boot0Meta> { + public: + static constexpr fs::BisPartitionId PartitionId = fs::BisPartitionId::BootPartition1Root; + static constexpr size_t BctPubkOffsetErista = 0x210; + static constexpr size_t BctPubkOffsetMariko = 0x10; + static constexpr size_t BctPubkSize = 0x100; + static constexpr size_t BctEksOffset = 0x450; + static constexpr size_t BctVersionOffset = 0x2330; + static constexpr size_t BctVersionMax = 0x20; + public: + Boot0Accessor() : PartitionAccessor<Boot0Meta>(PartitionId) { } + private: + static size_t GetBootloaderVersion(void *bct); + static size_t GetEksIndex(size_t bootloader_version); + static void CopyEks(void *dst_bct, const void *src_eks, size_t eks_index); + + static size_t GetBctPubkOffset(BootImageUpdateType boot_image_update_type) { + switch (boot_image_update_type) { + case BootImageUpdateType::Erista: + return BctPubkOffsetErista; + case BootImageUpdateType::Mariko: + return BctPubkOffsetMariko; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + public: + Result UpdateEks(void *dst_bct, void *eks_work_buffer); + Result UpdateEksManually(void *dst_bct, const void *src_eks); + Result PreserveAutoRcm(void *dst_bct, void *work_buffer, Boot0Partition which); + + Result DetectCustomPublicKey(bool *out, void *work_buffer, BootImageUpdateType boot_image_update_type); + }; + + class Boot1Accessor : public PartitionAccessor<Boot1Meta> { + public: + static constexpr fs::BisPartitionId PartitionId = fs::BisPartitionId::BootPartition2Root; + public: + Boot1Accessor() : PartitionAccessor<Boot1Meta>(PartitionId) { } + }; + + class Package2Accessor : public PartitionAccessor<Package2Meta> { + public: + Package2Accessor(Package2Type which) : PartitionAccessor<Package2Meta>(GetPackage2StorageId(which)) { } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_bis_save.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_bis_save.cpp new file mode 100644 index 00000000..666ab79f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_bis_save.cpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "updater_bis_save.hpp" + +namespace ams::updater { + + size_t BisSave::GetVerificationFlagOffset(BootModeType mode) { + switch (mode) { + case BootModeType::Normal: + return 0; + case BootModeType::Safe: + return 1; + default: + return 2; + } + } + + Result BisSave::Initialize(void *work_buffer, size_t work_buffer_size) { + AMS_ABORT_UNLESS(work_buffer_size >= SaveSize); + AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast<uintptr_t>(work_buffer), os::MemoryPageSize)); + AMS_ABORT_UNLESS(util::IsAligned(work_buffer_size, 0x200)); + + R_TRY(m_accessor.Initialize()); + m_save_buffer = work_buffer; + R_SUCCEED(); + } + + void BisSave::Finalize() { + m_accessor.Finalize(); + } + + Result BisSave::Load() { + size_t read_size; + R_RETURN(m_accessor.Read(std::addressof(read_size), m_save_buffer, SaveSize, Boot0Partition::BctSave)); + } + + Result BisSave::Save() { + R_RETURN(m_accessor.Write(m_save_buffer, SaveSize, Boot0Partition::BctSave)); + } + + bool BisSave::GetNeedsVerification(BootModeType mode) { + return reinterpret_cast<const u8 *>(m_save_buffer)[GetVerificationFlagOffset(mode)] != 0; + } + + void BisSave::SetNeedsVerification(BootModeType mode, bool needs_verification) { + reinterpret_cast<u8 *>(m_save_buffer)[GetVerificationFlagOffset(mode)] = needs_verification ? 1 : 0; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_bis_save.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_bis_save.hpp new file mode 100644 index 00000000..301c3b14 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_bis_save.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include "updater_bis_management.hpp" + +namespace ams::updater { + + class BisSave { + public: + static constexpr size_t SaveSize = BctSize; + private: + Boot0Accessor m_accessor; + void *m_save_buffer; + public: + BisSave() : m_accessor(), m_save_buffer(nullptr) { } + private: + static size_t GetVerificationFlagOffset(BootModeType mode); + public: + Result Initialize(void *work_buffer, size_t work_buffer_size); + void Finalize(); + + Result Load(); + Result Save(); + bool GetNeedsVerification(BootModeType mode); + void SetNeedsVerification(BootModeType mode, bool needs_verification); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_files.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_files.cpp new file mode 100644 index 00000000..15cf0df7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_files.cpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "updater_files.hpp" + +namespace ams::updater { + + Result ReadFile(size_t *out_size, void *dst, size_t dst_size, const char *path) { + /* Open the file. */ + fs::FileHandle file; + R_TRY_CATCH(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Read)) { + R_CONVERT(fs::ResultPathNotFound, updater::ResultInvalidBootImagePackage()) + } R_END_TRY_CATCH; + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + std::memset(dst, 0, dst_size); + R_RETURN(fs::ReadFile(out_size, file, 0, dst, dst_size, fs::ReadOption())); + } + + Result GetFileHash(size_t *out_size, void *dst_hash, const char *path, void *work_buffer, size_t work_buffer_size) { + /* Open the file. */ + fs::FileHandle file; + R_TRY_CATCH(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Read)) { + R_CONVERT(fs::ResultPathNotFound, updater::ResultInvalidBootImagePackage()) + } R_END_TRY_CATCH; + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Read in chunks, hashing as we go. */ + crypto::Sha256Generator generator; + generator.Initialize(); + + size_t total_size = 0; + while (true) { + size_t size; + R_TRY(fs::ReadFile(std::addressof(size), file, total_size, work_buffer, work_buffer_size, fs::ReadOption())); + + generator.Update(work_buffer, size); + total_size += size; + + if (size != work_buffer_size) { + break; + } + } + + generator.GetHash(dst_hash, crypto::Sha256Generator::HashSize); + *out_size = total_size; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_files.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_files.hpp new file mode 100644 index 00000000..13a62bf1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_files.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::updater { + + /* File helpers. */ + Result ReadFile(size_t *out_size, void *dst, size_t dst_size, const char *path); + Result GetFileHash(size_t *out_size, void *dst_hash, const char *path, void *work_buffer, size_t work_buffer_size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_paths.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_paths.cpp new file mode 100644 index 00000000..010ef68c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_paths.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "updater_paths.hpp" + +namespace ams::updater { + + namespace { + + /* Actual paths. */ + constexpr const char *BootImagePackageMountName = "bip"; + constexpr const char *BctPathNx = "bip:/nx/bct"; + constexpr const char *Package1PathNx = "bip:/nx/package1"; + constexpr const char *Package2PathNx = "bip:/nx/package2"; + constexpr const char *BctPathA = "bip:/a/bct"; + constexpr const char *Package1PathA = "bip:/a/package1"; + constexpr const char *Package2PathA = "bip:/a/package2"; + + const char *ChooseCandidatePath(const char * const *candidates, size_t num_candidates) { + AMS_ABORT_UNLESS(num_candidates > 0); + + for (size_t i = 0; i < num_candidates; i++) { + fs::DirectoryEntryType type; + if (R_FAILED(fs::GetEntryType(std::addressof(type), candidates[i]))) { + continue; + } + + if (type != fs::DirectoryEntryType_File) { + continue; + } + + return candidates[i]; + } + + /* Nintendo just uses the last candidate if they all fail...should we abort? */ + return candidates[num_candidates - 1]; + } + + } + + const char *GetMountName() { + return BootImagePackageMountName; + } + + + const char *GetBctPath(BootImageUpdateType boot_image_update_type) { + switch (boot_image_update_type) { + case BootImageUpdateType::Erista: + { + constexpr const char *candidates[] = {BctPathNx}; + return ChooseCandidatePath(candidates, util::size(candidates)); + } + case BootImageUpdateType::Mariko: + { + constexpr const char *candidates[] = {BctPathA, BctPathNx}; + return ChooseCandidatePath(candidates, util::size(candidates)); + } + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + const char *GetPackage1Path(BootImageUpdateType boot_image_update_type) { + switch (boot_image_update_type) { + case BootImageUpdateType::Erista: + { + constexpr const char *candidates[] = {Package1PathNx}; + return ChooseCandidatePath(candidates, util::size(candidates)); + } + case BootImageUpdateType::Mariko: + { + constexpr const char *candidates[] = {Package1PathA, Package1PathNx}; + return ChooseCandidatePath(candidates, util::size(candidates)); + } + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + const char *GetPackage2Path(BootImageUpdateType boot_image_update_type) { + switch (boot_image_update_type) { + case BootImageUpdateType::Erista: + { + constexpr const char *candidates[] = {Package2PathNx}; + return ChooseCandidatePath(candidates, util::size(candidates)); + } + case BootImageUpdateType::Mariko: + { + constexpr const char *candidates[] = {Package2PathA, Package2PathNx}; + return ChooseCandidatePath(candidates, util::size(candidates)); + } + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + +} + + + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_paths.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_paths.hpp new file mode 100644 index 00000000..f7a39d7e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/updater/updater_paths.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::updater { + + /* Path functionality. */ + const char *GetMountName(); + const char *GetBctPath(BootImageUpdateType boot_image_update_type); + const char *GetPackage1Path(BootImageUpdateType boot_image_update_type); + const char *GetPackage2Path(BootImageUpdateType boot_image_update_type); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/impl/usb_util.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/impl/usb_util.hpp new file mode 100644 index 00000000..30b38ebc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/impl/usb_util.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::usb::impl { + + constexpr int GetEndpointIndex(u8 address) { + int idx = address & UsbEndpointAddressMask_EndpointNumber; + if ((address & UsbEndpointAddressMask_Dir) == UsbEndpointAddressMask_DirDevicetoHost) { + idx += 0x10; + } + return idx; + } + + template<typename T> + class ScopedRefCount { + NON_COPYABLE(ScopedRefCount); + NON_MOVEABLE(ScopedRefCount); + private: + T &m_obj; + public: + ALWAYS_INLINE ScopedRefCount(T &o) : m_obj(o) { + ++m_obj; + } + + ALWAYS_INLINE ~ScopedRefCount() { + --m_obj; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_device.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_device.cpp new file mode 100644 index 00000000..15a01d11 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_device.cpp @@ -0,0 +1,815 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "usb_remote_ds_root_session.hpp" +#include "usb_remote_ds_service.hpp" +#include "impl/usb_util.hpp" + +namespace ams::usb { + + Result DsClient::Initialize(ComplexId complex_id) { + /* Clear interfaces. */ + for (size_t i = 0; i < util::size(m_interfaces); ++i) { + m_interfaces[i] = nullptr; + } + + /* Initialize heap. */ + m_heap_handle = lmem::CreateExpHeap(m_heap_buffer, sizeof(m_heap_buffer), lmem::CreateOption_None); + R_UNLESS(m_heap_handle != nullptr, usb::ResultMemAllocFailure()); + + /* Attach our allocator. */ + m_allocator.Attach(m_heap_handle); + + /* Connect to usb:ds. */ + /* NOTE: Here, Nintendo does m_domain.InitializeByDomain<...>(...); m_domain.SetSessionCount(1); */ + { + #if defined(ATMOSPHERE_OS_HORIZON) + os::NativeHandle h; + R_TRY(sm::GetServiceHandle(std::addressof(h), sm::ServiceName::Encode("usb:ds"))); + + ::Service srv; + ::serviceCreate(&srv, h); + R_ABORT_UNLESS(serviceConvertToDomain(std::addressof(srv))); + + using Allocator = decltype(m_allocator); + using ObjectFactory = sf::ObjectFactory<Allocator::Policy>; + + if (hos::GetVersion() >= hos::Version_11_0_0) { + m_root_session = ObjectFactory::CreateSharedEmplaced<ds::IDsRootSession, RemoteDsRootSession>(std::addressof(m_allocator), srv, std::addressof(m_allocator)); + + R_TRY(m_root_session->GetService(std::addressof(m_ds_service))); + } else { + m_ds_service = ObjectFactory::CreateSharedEmplaced<ds::IDsService, RemoteDsService>(std::addressof(m_allocator), srv, std::addressof(m_allocator)); + } + #else + AMS_ABORT("TODO"); + #endif + } + + /* Bind the client process. */ + R_TRY(m_ds_service->Bind(complex_id, sf::CopyHandle(dd::GetCurrentProcessHandle(), false))); + + /* Get the state change event. */ + sf::NativeHandle event_handle; + R_TRY(m_ds_service->GetStateChangeEvent(std::addressof(event_handle))); + + /* Attach the state change event handle to our event. */ + os::AttachReadableHandleToSystemEvent(std::addressof(m_state_change_event), event_handle.GetOsHandle(), event_handle.IsManaged(), os::EventClearMode_ManualClear); + event_handle.Detach(); + + /* Mark ourselves as initialized. */ + m_is_initialized = true; + + R_SUCCEED(); + } + + Result DsClient::Finalize() { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Disable and finalize all interfaces. */ + R_TRY(this->DisableDevice()); + for (size_t i = 0; i < util::size(m_interfaces); ++i) { + if (m_interfaces[i] != nullptr) { + R_TRY(m_interfaces[i]->Finalize()); + } + } + + /* Check our reference count .*/ + R_UNLESS(m_reference_count <= 1, usb::ResultResourceBusy()); + + /* Finalize members. */ + m_is_initialized = false; + os::DestroySystemEvent(std::addressof(m_state_change_event)); + lmem::DestroyExpHeap(m_heap_handle); + m_heap_handle = nullptr; + + /* Destroy interface objects. */ + m_ds_service = nullptr; + m_root_session = nullptr; + + R_SUCCEED(); + } + + bool DsClient::IsInitialized() { + return m_is_initialized; + } + + Result DsClient::EnableDevice() { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Enable all interfaces. */ + if (hos::GetVersion() < hos::Version_11_0_0) { + for (size_t i = 0; i < util::size(m_interfaces); ++i) { + if (m_interfaces[i] != nullptr) { + R_TRY(m_interfaces[i]->Enable()); + } + } + } + + /* Enable the device. */ + R_TRY(m_ds_service->Enable()); + + /* Mark disabled. */ + m_is_enabled = true; + R_SUCCEED(); + } + + Result DsClient::DisableDevice() { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Disable the device. */ + R_TRY(m_ds_service->Disable()); + + /* Disable all interfaces. */ + if (hos::GetVersion() < hos::Version_11_0_0) { + for (size_t i = 0; i < util::size(m_interfaces); ++i) { + if (m_interfaces[i] != nullptr) { + R_TRY(m_interfaces[i]->Disable()); + } + } + } + + /* Mark disabled. */ + m_is_enabled = false; + R_SUCCEED(); + } + + os::SystemEventType *DsClient::GetStateChangeEvent() { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + return m_is_initialized ? std::addressof(m_state_change_event) : nullptr; + } + + Result DsClient::GetState(UsbState *out) { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Check that we have a service. */ + AMS_ABORT_UNLESS(m_ds_service != nullptr); + + R_RETURN(m_ds_service->GetState(out)); + } + + Result DsClient::ClearDeviceData() { + R_RETURN(m_ds_service->ClearDeviceData()); + } + + Result DsClient::AddUsbStringDescriptor(u8 *out_index, UsbStringDescriptor *desc) { + R_RETURN(m_ds_service->AddUsbStringDescriptor(out_index, sf::InBuffer(reinterpret_cast<const u8 *>(desc), sizeof(*desc)))); + } + + Result DsClient::DeleteUsbStringDescriptor(u8 index) { + R_RETURN(m_ds_service->DeleteUsbStringDescriptor(index)); + } + + Result DsClient::SetUsbDeviceDescriptor(UsbDeviceDescriptor *desc, UsbDeviceSpeed speed) { + R_RETURN(m_ds_service->SetUsbDeviceDescriptor(sf::InBuffer(reinterpret_cast<const u8 *>(desc), sizeof(*desc)), speed)); + } + + Result DsClient::SetBinaryObjectStore(u8 *data, int size) { + R_RETURN(m_ds_service->SetBinaryObjectStore(sf::InBuffer(reinterpret_cast<const u8 *>(data), size))); + } + + Result DsClient::AddInterface(DsInterface *intf, sf::SharedPointer<ds::IDsInterface> *out_srv, uint8_t bInterfaceNumber) { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Check that we have a service. */ + AMS_ABORT_UNLESS(m_ds_service != nullptr); + + /* Register the interface. */ + R_TRY(m_ds_service->RegisterInterface(out_srv, bInterfaceNumber)); + + /* Set interface. */ + m_interfaces[bInterfaceNumber] = intf; + + R_SUCCEED(); + } + + Result DsClient::DeleteInterface(uint8_t bInterfaceNumber) { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Check that we have the interface. */ + R_UNLESS(m_interfaces[bInterfaceNumber] != nullptr, usb::ResultOperationDenied()); + + /* Clear the interface. */ + m_interfaces[bInterfaceNumber] = nullptr; + + R_SUCCEED(); + } + + Result DsInterface::Initialize(DsClient *client, u8 bInterfaceNumber) { + /* Check that we haven't already initialized. */ + R_UNLESS(!m_is_initialized, usb::ResultAlreadyInitialized()); + + /* Set our client. */ + m_client = client; + + /* Clear all endpoints. */ + for (size_t i = 0; i < util::size(m_endpoints); ++i) { + m_endpoints[i] = nullptr; + } + + /* Set our interface number. */ + R_UNLESS(bInterfaceNumber < util::size(m_client->m_interfaces), usb::ResultInvalidParameter()); + m_interface_num = bInterfaceNumber; + + /* Add the interface. */ + R_TRY(m_client->AddInterface(this, std::addressof(m_interface), m_interface_num)); + + /* Ensure we cleanup if we fail after this. */ + auto intf_guard = SCOPE_GUARD { m_client->DeleteInterface(m_interface_num); m_interface = nullptr; }; + + /* Get events. */ + sf::NativeHandle setup_event_handle; + sf::NativeHandle ctrl_in_event_handle; + sf::NativeHandle ctrl_out_event_handle; + R_TRY(m_interface->GetSetupEvent(std::addressof(setup_event_handle))); + R_TRY(m_interface->GetCtrlInCompletionEvent(std::addressof(ctrl_in_event_handle))); + R_TRY(m_interface->GetCtrlOutCompletionEvent(std::addressof(ctrl_out_event_handle))); + + /* Attach events. */ + os::AttachReadableHandleToSystemEvent(std::addressof(m_setup_event), setup_event_handle.GetOsHandle(), setup_event_handle.IsManaged(), os::EventClearMode_ManualClear); + os::AttachReadableHandleToSystemEvent(std::addressof(m_ctrl_in_completion_event), ctrl_in_event_handle.GetOsHandle(), ctrl_in_event_handle.IsManaged(), os::EventClearMode_ManualClear); + os::AttachReadableHandleToSystemEvent(std::addressof(m_ctrl_out_completion_event), ctrl_out_event_handle.GetOsHandle(), ctrl_out_event_handle.IsManaged(), os::EventClearMode_ManualClear); + + setup_event_handle.Detach(); + ctrl_in_event_handle.Detach(); + ctrl_out_event_handle.Detach(); + + /* Increment our client's reference count. */ + ++m_client->m_reference_count; + + /* Set ourselves as initialized. */ + m_is_initialized = true; + + intf_guard.Cancel(); + R_SUCCEED(); + } + + Result DsInterface::Finalize() { + /* Validate that we have a service. */ + AMS_ABORT_UNLESS(m_interface != nullptr); + + /* We must be disabled. */ + R_UNLESS(!m_client->m_is_enabled, usb::ResultResourceBusy()); + + /* Finalize all endpoints. */ + for (size_t i = 0; i < util::size(m_endpoints); ++i) { + if (m_endpoints[i] != nullptr) { + R_TRY(m_endpoints[i]->Finalize()); + } + } + + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Check our reference count .*/ + R_UNLESS(m_reference_count <= 1, usb::ResultResourceBusy()); + + /* Finalize members. */ + m_is_initialized = false; + os::DestroySystemEvent(std::addressof(m_setup_event)); + os::DestroySystemEvent(std::addressof(m_ctrl_in_completion_event)); + os::DestroySystemEvent(std::addressof(m_ctrl_out_completion_event)); + + /* Delete ourselves from our cleint. */ + m_client->DeleteInterface(m_interface_num); + + /* Destroy our service. */ + m_interface = nullptr; + + /* Close our reference to our client. */ + --m_client->m_reference_count; + m_client = nullptr; + + R_SUCCEED(); + } + + Result DsInterface::AppendConfigurationData(UsbDeviceSpeed speed, void *data, u32 size) { + R_RETURN(m_interface->AppendConfigurationData(m_interface_num, speed, sf::InBuffer(data, size))); + } + + bool DsInterface::IsInitialized() { + return m_is_initialized; + } + + os::SystemEventType *DsInterface::GetSetupEvent() { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + return m_is_initialized ? std::addressof(m_setup_event) : nullptr; + } + + Result DsInterface::GetSetupPacket(UsbCtrlRequest *out) { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Check that we have a service. */ + AMS_ABORT_UNLESS(m_interface != nullptr); + + R_RETURN(m_interface->GetSetupPacket(sf::OutBuffer(out, sizeof(*out)))); + } + + Result DsInterface::Enable() { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* If we're already enabled, nothing to do. */ + R_SUCCEED_IF(m_client->m_is_enabled); + + /* Check that we have a service. */ + AMS_ABORT_UNLESS(m_interface != nullptr); + + /* Perform the enable. */ + R_TRY(m_interface->Enable()); + + R_SUCCEED(); + } + + Result DsInterface::Disable() { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* If we're already disabled, nothing to do. */ + R_SUCCEED_IF(!m_client->m_is_enabled); + + /* Check that we have a service. */ + AMS_ABORT_UNLESS(m_interface != nullptr); + + /* Perform the disable. */ + R_TRY(m_interface->Disable()); + + R_SUCCEED(); + } + + Result DsInterface::AddEndpoint(DsEndpoint *ep, u8 bEndpointAddress, sf::SharedPointer<ds::IDsEndpoint> *out) { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Check that we're not already enabled. */ + R_UNLESS(!m_client->m_is_enabled, usb::ResultOperationDenied()); + + /* Check that we have a service. */ + AMS_ABORT_UNLESS(m_interface != nullptr); + + /* Register the endpoint. */ + R_TRY(m_interface->RegisterEndpoint(bEndpointAddress, out)); + + /* Set the endpoint. */ + m_endpoints[impl::GetEndpointIndex(bEndpointAddress)] = ep; + + R_SUCCEED(); + } + + Result DsInterface::DeleteEndpoint(u8 bEndpointAddress) { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Check that we're disabled and have the endpoint. */ + const auto index = impl::GetEndpointIndex(bEndpointAddress); + R_UNLESS(!m_client->m_is_enabled, usb::ResultOperationDenied()); + R_UNLESS(m_endpoints[index] != nullptr, usb::ResultOperationDenied()); + + /* Clear the endpoint. */ + m_endpoints[index] = nullptr; + + R_SUCCEED(); + } + + Result DsInterface::CtrlIn(u32 *out_transferred, void *dst, u32 size) { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Check that we're enabled. */ + R_UNLESS(m_client->m_is_enabled, usb::ResultOperationDenied()); + + /* Check that the data is aligned. */ + R_UNLESS(usb::IsDmaAligned(reinterpret_cast<u64>(dst)), usb::ResultAlignmentError()); + + /* If we should, flush cache. */ + if (size != 0) { + dd::FlushDataCache(dst, size); + } + + /* Check that we have a service. */ + AMS_ABORT_UNLESS(m_interface != nullptr); + + /* Perform the transfer. */ + u32 urb_id; + R_TRY(m_interface->CtrlInAsync(std::addressof(urb_id), reinterpret_cast<u64>(dst), size)); + + /* Wait for control to finish. */ + os::WaitSystemEvent(std::addressof(m_ctrl_in_completion_event)); + os::ClearSystemEvent(std::addressof(m_ctrl_in_completion_event)); + + /* Get the urb report. */ + R_ABORT_UNLESS(m_interface->GetCtrlInUrbReport(std::addressof(m_report))); + + /* Check the report is for our urb. */ + R_UNLESS(m_report.count == 1, usb::ResultInternalStateError()); + R_UNLESS(m_report.reports[0].id == urb_id, usb::ResultInternalStateError()); + + /* Set output bytes. */ + if (out_transferred != nullptr) { + *out_transferred = m_report.reports[0].transferred_size; + } + + /* Handle the report. */ + switch (m_report.reports[0].status) { + case UrbStatus_Cancelled: + R_THROW(usb::ResultInterrupted()); + case UrbStatus_Failed: + R_THROW(usb::ResultTransactionError()); + case UrbStatus_Finished: + R_SUCCEED(); + default: + R_THROW(usb::ResultInternalStateError()); + } + } + + Result DsInterface::CtrlOut(u32 *out_transferred, void *dst, u32 size) { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Check that we're enabled. */ + R_UNLESS(m_client->m_is_enabled, usb::ResultOperationDenied()); + + /* Check that the data is aligned. */ + R_UNLESS(usb::IsDmaAligned(reinterpret_cast<u64>(dst)), usb::ResultAlignmentError()); + + /* If we should, invalidate cache. */ + if (size != 0) { + dd::InvalidateDataCache(dst, size); + } + + /* Check that we have a service. */ + AMS_ABORT_UNLESS(m_interface != nullptr); + + /* Perform the transfer. */ + u32 urb_id; + R_TRY(m_interface->CtrlOutAsync(std::addressof(urb_id), reinterpret_cast<u64>(dst), size)); + + /* Wait for control to finish. */ + os::WaitSystemEvent(std::addressof(m_ctrl_out_completion_event)); + os::ClearSystemEvent(std::addressof(m_ctrl_out_completion_event)); + + /* Ensure that cache remains consistent. */ + ON_SCOPE_EXIT { + if (size != 0) { + dd::InvalidateDataCache(dst, size); + } + }; + + /* Get the urb report. */ + R_ABORT_UNLESS(m_interface->GetCtrlOutUrbReport(std::addressof(m_report))); + + /* Check the report is for our urb. */ + R_UNLESS(m_report.count == 1, usb::ResultInternalStateError()); + R_UNLESS(m_report.reports[0].id == urb_id, usb::ResultInternalStateError()); + + /* Set output bytes. */ + if (out_transferred != nullptr) { + *out_transferred = m_report.reports[0].transferred_size; + } + + /* Handle the report. */ + switch (m_report.reports[0].status) { + case UrbStatus_Cancelled: + R_THROW(usb::ResultInterrupted()); + case UrbStatus_Failed: + R_THROW(usb::ResultTransactionError()); + case UrbStatus_Finished: + R_SUCCEED(); + default: + R_THROW(usb::ResultInternalStateError()); + } + } + + Result DsInterface::CtrlRead(u32 *out_transferred, void *dst, u32 size) { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Do the data transfer. */ + Result result = this->CtrlOut(out_transferred, dst, size); + + /* Do the status transfer. */ + if (R_SUCCEEDED(result)) { + result = this->CtrlIn(nullptr, nullptr, 0); + } + + /* If we fail, stall. */ + if (R_FAILED(result)) { + result = this->CtrlStall(); + } + + R_RETURN(result); + } + + Result DsInterface::CtrlWrite(u32 *out_transferred, void *dst, u32 size) { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Do the data transfer. */ + Result result = this->CtrlIn(out_transferred, dst, size); + + /* Do the status transfer. */ + if (R_SUCCEEDED(result)) { + result = this->CtrlOut(nullptr, nullptr, 0); + } + + /* If we failed, stall. */ + if (R_FAILED(result)) { + result = this->CtrlStall(); + } + + R_RETURN(result); + } + + Result DsInterface::CtrlDone() { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Do the status transfer. */ + Result result = this->CtrlIn(nullptr, nullptr, 0); + + /* If we failed, stall. */ + if (R_FAILED(result)) { + result = this->CtrlStall(); + } + + R_RETURN(result); + } + + Result DsInterface::CtrlStall() { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Check that we're enabled. */ + R_UNLESS(m_client->m_is_enabled, usb::ResultOperationDenied()); + + /* Check that we have a service. */ + AMS_ABORT_UNLESS(m_interface != nullptr); + + R_RETURN(m_interface->CtrlStall()); + } + + Result DsEndpoint::Initialize(DsInterface *interface, u8 bEndpointAddress) { + /* Check that the interface is valid. */ + AMS_ABORT_UNLESS(interface != nullptr); + + /* Check that we're not already initialized. */ + R_UNLESS(!m_is_initialized, usb::ResultAlreadyInitialized()); + + /* Set our interface. */ + m_interface = interface; + + /* Add the endpoint. */ + R_TRY(m_interface->AddEndpoint(this, bEndpointAddress, std::addressof(m_endpoint))); + + /* Set our address. */ + m_address = bEndpointAddress; + + /* Ensure we clean up if we fail after this. */ + auto ep_guard = SCOPE_GUARD { m_interface->DeleteEndpoint(m_address); m_endpoint = nullptr; }; + + /* Get completion event. */ + sf::NativeHandle event_handle; + R_TRY(m_endpoint->GetCompletionEvent(std::addressof(event_handle))); + + /* Increment our interface's reference count. */ + ++m_interface->m_reference_count; + ++m_interface->m_client->m_reference_count; + + /* Attach our event. */ + os::AttachReadableHandleToSystemEvent(std::addressof(m_completion_event), event_handle.GetOsHandle(), event_handle.IsManaged(), os::EventClearMode_ManualClear); + event_handle.Detach(); + + /* Mark initialized. */ + m_is_initialized = true; + + ep_guard.Cancel(); + R_SUCCEED(); + } + + Result DsEndpoint::Finalize() { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Cancel any pending transactions. */ + m_endpoint->Cancel(); + + /* Wait for us to be at one reference count. */ + while (m_reference_count > 1) { + os::SleepThread(TimeSpan::FromMilliSeconds(25)); + } + + /* Destroy our event. */ + os::DestroySystemEvent(std::addressof(m_completion_event)); + + /* Decrement our interface's reference count. */ + --m_interface->m_reference_count; + --m_interface->m_client->m_reference_count; + + /* Delete ourselves. */ + R_TRY(m_interface->DeleteEndpoint(m_address)); + + /* Clear ourselves. */ + m_interface = nullptr; + m_endpoint = nullptr; + + /* Mark uninitialized. */ + m_is_initialized = false; + + R_SUCCEED(); + } + + bool DsEndpoint::IsInitialized() { + return m_is_initialized; + } + + Result DsEndpoint::PostBuffer(u32 *out_transferred, void *buf, u32 size) { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Post buffer. */ + u32 urb_id; + R_TRY(this->PostBufferAsync(std::addressof(urb_id), buf, size)); + + /* Wait for completion. */ + os::WaitSystemEvent(std::addressof(m_completion_event)); + os::ClearSystemEvent(std::addressof(m_completion_event)); + + /* Get URB report. */ + UrbReport report; + AMS_ABORT_UNLESS(m_endpoint != nullptr); + R_ABORT_UNLESS(m_endpoint->GetUrbReport(std::addressof(report))); + + /* Check the report is for our urb. */ + R_UNLESS(report.count == 1, usb::ResultInternalStateError()); + R_UNLESS(report.reports[0].id == urb_id, usb::ResultInternalStateError()); + + /* Set output bytes. */ + if (out_transferred != nullptr) { + *out_transferred = report.reports[0].transferred_size; + } + + /* Handle the report. */ + switch (report.reports[0].status) { + case UrbStatus_Cancelled: + R_THROW(usb::ResultInterrupted()); + case UrbStatus_Failed: + R_THROW(usb::ResultTransactionError()); + case UrbStatus_Finished: + R_SUCCEED(); + default: + R_THROW(usb::ResultInternalStateError()); + } + } + + Result DsEndpoint::PostBufferAsync(u32 *out_urb_id, void *buf, u32 size) { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Check that the buffer is DMA aligned. */ + R_UNLESS(usb::IsDmaAligned(reinterpret_cast<u64>(buf)), usb::ResultAlignmentError()); + + /* Check that we have a service. */ + AMS_ABORT_UNLESS(m_endpoint != nullptr); + + /* Post */ + u32 urb_id = 0; + R_TRY(m_endpoint->PostBufferAsync(std::addressof(urb_id), reinterpret_cast<u64>(buf), size)); + + *out_urb_id = urb_id; + R_SUCCEED(); + } + + os::SystemEventType *DsEndpoint::GetCompletionEvent() { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + return m_is_initialized ? std::addressof(m_completion_event) : nullptr; + } + + Result DsEndpoint::GetUrbReport(UrbReport *out) { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Check that we have a service. */ + AMS_ABORT_UNLESS(m_endpoint != nullptr); + + R_RETURN(m_endpoint->GetUrbReport(out)); + } + + Result DsEndpoint::Cancel() { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Check that we have a service. */ + AMS_ABORT_UNLESS(m_endpoint != nullptr); + + R_RETURN(m_endpoint->Cancel()); + } + + Result DsEndpoint::SetZeroLengthTransfer(bool zlt) { + /* Create a scoped reference. */ + impl::ScopedRefCount scoped_ref(m_reference_count); + + /* Check that we're initialized. */ + R_UNLESS(m_is_initialized, usb::ResultNotInitialized()); + + /* Check that we have a service. */ + AMS_ABORT_UNLESS(m_endpoint != nullptr); + + R_RETURN(m_endpoint->SetZlt(zlt)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_endpoint.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_endpoint.cpp new file mode 100644 index 00000000..ef284423 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_endpoint.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "usb_remote_ds_endpoint.hpp" + +namespace ams::usb { + + #if defined(ATMOSPHERE_OS_HORIZON) + Result RemoteDsEndpoint::PostBufferAsync(sf::Out<u32> out_urb_id, u64 address, u32 size) { + const struct { + u32 size; + u64 address; + } in = { size, address }; + + serviceAssumeDomain(std::addressof(m_srv)); + R_RETURN(serviceDispatchInOut(std::addressof(m_srv), 0, in, *out_urb_id)); + } + + Result RemoteDsEndpoint::Cancel() { + serviceAssumeDomain(std::addressof(m_srv)); + R_RETURN(serviceDispatch(std::addressof(m_srv), 1)); + } + + Result RemoteDsEndpoint::GetCompletionEvent(sf::OutCopyHandle out) { + serviceAssumeDomain(std::addressof(m_srv)); + + os::NativeHandle event_handle; + R_TRY((serviceDispatch(std::addressof(m_srv), 2, + .out_handle_attrs = { SfOutHandleAttr_HipcCopy }, + .out_handles = std::addressof(event_handle), + ))); + + out.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result RemoteDsEndpoint::GetUrbReport(sf::Out<usb::UrbReport> out) { + serviceAssumeDomain(std::addressof(m_srv)); + R_RETURN(serviceDispatchOut(std::addressof(m_srv), 3, *out)); + } + + Result RemoteDsEndpoint::Stall() { + serviceAssumeDomain(std::addressof(m_srv)); + R_RETURN(serviceDispatch(std::addressof(m_srv), 4)); + } + + Result RemoteDsEndpoint::SetZlt(bool zlt) { + const u8 in = zlt ? 1 : 0; + + serviceAssumeDomain(std::addressof(m_srv)); + R_RETURN(serviceDispatchIn(std::addressof(m_srv), 5, in)); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_endpoint.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_endpoint.hpp new file mode 100644 index 00000000..88e94f23 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_endpoint.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::usb { + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemoteDsEndpoint { + private: + Service m_srv; + public: + RemoteDsEndpoint(Service &srv) : m_srv(srv) { /* ... */ } + virtual ~RemoteDsEndpoint() { serviceClose(std::addressof(m_srv)); } + public: + Result PostBufferAsync(sf::Out<u32> out_urb_id, u64 address, u32 size); + Result Cancel(); + Result GetCompletionEvent(sf::OutCopyHandle out); + Result GetUrbReport(sf::Out<usb::UrbReport> out); + Result Stall(); + Result SetZlt(bool zlt); + }; + static_assert(ds::IsIDsEndpoint<RemoteDsEndpoint>); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_interface.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_interface.cpp new file mode 100644 index 00000000..aadcb08e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_interface.cpp @@ -0,0 +1,156 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "usb_remote_ds_interface.hpp" +#include "usb_remote_ds_endpoint.hpp" + +namespace ams::usb { + + #if defined(ATMOSPHERE_OS_HORIZON) + Result RemoteDsInterface::RegisterEndpoint(u8 endpoint_address, sf::Out<sf::SharedPointer<usb::ds::IDsEndpoint>> out) { + Service srv; + + serviceAssumeDomain(std::addressof(m_srv)); + R_TRY(serviceDispatchIn(std::addressof(m_srv), 0, endpoint_address, + .out_num_objects = 1, + .out_objects = std::addressof(srv), + )); + + *out = ObjectFactory::CreateSharedEmplaced<ds::IDsEndpoint, RemoteDsEndpoint>(m_allocator, srv); + + R_SUCCEED(); + } + + Result RemoteDsInterface::GetSetupEvent(sf::OutCopyHandle out) { + serviceAssumeDomain(std::addressof(m_srv)); + + os::NativeHandle event_handle; + R_TRY((serviceDispatch(std::addressof(m_srv), 1, + .out_handle_attrs = { SfOutHandleAttr_HipcCopy }, + .out_handles = std::addressof(event_handle), + ))); + + out.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result RemoteDsInterface::GetSetupPacket(const sf::OutBuffer &out) { + serviceAssumeDomain(std::addressof(m_srv)); + return serviceDispatch(std::addressof(m_srv), 2, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + .buffers = { { out.GetPointer(), out.GetSize() } }, + ); + } + + Result RemoteDsInterface::CtrlInAsync(sf::Out<u32> out_urb_id, u64 address, u32 size) { + const struct { + u32 size; + u64 address; + } in = { size, address }; + + serviceAssumeDomain(std::addressof(m_srv)); + R_RETURN(serviceDispatchInOut(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 3 : 5, in, *out_urb_id)); + } + + Result RemoteDsInterface::CtrlOutAsync(sf::Out<u32> out_urb_id, u64 address, u32 size) { + const struct { + u32 size; + u64 address; + } in = { size, address }; + + serviceAssumeDomain(std::addressof(m_srv)); + R_RETURN(serviceDispatchInOut(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 4 : 6, in, *out_urb_id)); + } + + Result RemoteDsInterface::GetCtrlInCompletionEvent(sf::OutCopyHandle out) { + serviceAssumeDomain(std::addressof(m_srv)); + + os::NativeHandle event_handle; + R_TRY((serviceDispatch(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 5 : 7, + .out_handle_attrs = { SfOutHandleAttr_HipcCopy }, + .out_handles = std::addressof(event_handle), + ))); + + out.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result RemoteDsInterface::GetCtrlInUrbReport(sf::Out<usb::UrbReport> out) { + serviceAssumeDomain(std::addressof(m_srv)); + R_RETURN(serviceDispatchOut(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 6 : 8, *out)); + } + + Result RemoteDsInterface::GetCtrlOutCompletionEvent(sf::OutCopyHandle out) { + serviceAssumeDomain(std::addressof(m_srv)); + + os::NativeHandle event_handle; + R_TRY((serviceDispatch(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 7 : 9, + .out_handle_attrs = { SfOutHandleAttr_HipcCopy }, + .out_handles = std::addressof(event_handle), + ))); + + out.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result RemoteDsInterface::GetCtrlOutUrbReport(sf::Out<usb::UrbReport> out) { + serviceAssumeDomain(std::addressof(m_srv)); + R_RETURN(serviceDispatchOut(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 8 : 10, *out)); + } + + Result RemoteDsInterface::CtrlStall() { + serviceAssumeDomain(std::addressof(m_srv)); + R_RETURN(serviceDispatch(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 9 : 11)); + } + + Result RemoteDsInterface::AppendConfigurationData(u8 bInterfaceNumber, usb::UsbDeviceSpeed device_speed, const sf::InBuffer &data) { + if (hos::GetVersion() >= hos::Version_11_0_0) { + serviceAssumeDomain(std::addressof(m_srv)); + return serviceDispatchIn(std::addressof(m_srv), 10, device_speed, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, + .buffers = { { data.GetPointer(), data.GetSize() } }, + ); + } else { + const struct { + u8 bInterfaceNumber; + usb::UsbDeviceSpeed device_speed; + } in = { bInterfaceNumber, device_speed }; + + serviceAssumeDomain(std::addressof(m_srv)); + return serviceDispatchIn(std::addressof(m_srv), 12, in, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, + .buffers = { { data.GetPointer(), data.GetSize() } }, + ); + } + } + + Result RemoteDsInterface::Enable() { + R_SUCCEED_IF(hos::GetVersion() >= hos::Version_11_0_0); + + serviceAssumeDomain(std::addressof(m_srv)); + R_RETURN(serviceDispatch(std::addressof(m_srv), 3)); + } + + Result RemoteDsInterface::Disable() { + R_SUCCEED_IF(hos::GetVersion() >= hos::Version_11_0_0); + + serviceAssumeDomain(std::addressof(m_srv)); + R_RETURN(serviceDispatch(std::addressof(m_srv), 3)); + } + #endif + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_interface.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_interface.hpp new file mode 100644 index 00000000..fd767be0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_interface.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::usb { + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemoteDsInterface { + private: + using Allocator = sf::ExpHeapAllocator; + using ObjectFactory = sf::ObjectFactory<Allocator::Policy>; + private: + Service m_srv; + Allocator *m_allocator; + public: + RemoteDsInterface(Service &srv, sf::ExpHeapAllocator *allocator) : m_srv(srv), m_allocator(allocator) { /* ... */ } + virtual ~RemoteDsInterface() { serviceClose(std::addressof(m_srv)); } + public: + Result RegisterEndpoint(u8 endpoint_address, sf::Out<sf::SharedPointer<usb::ds::IDsEndpoint>> out); + Result GetSetupEvent(sf::OutCopyHandle out); + Result GetSetupPacket(const sf::OutBuffer & out); + Result CtrlInAsync(sf::Out<u32> out_urb_id, u64 address, u32 size); + Result CtrlOutAsync(sf::Out<u32> out_urb_id, u64 address, u32 size); + Result GetCtrlInCompletionEvent(sf::OutCopyHandle out); + Result GetCtrlInUrbReport(sf::Out<usb::UrbReport> out); + Result GetCtrlOutCompletionEvent(sf::OutCopyHandle out); + Result GetCtrlOutUrbReport(sf::Out<usb::UrbReport> out); + Result CtrlStall(); + Result AppendConfigurationData(u8 bInterfaceNumber, usb::UsbDeviceSpeed device_speed, const sf::InBuffer &data); + Result Enable(); + Result Disable(); + }; + static_assert(ds::IsIDsInterface<RemoteDsInterface>); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_root_session.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_root_session.cpp new file mode 100644 index 00000000..04f01c6e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_root_session.cpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "usb_remote_ds_root_session.hpp" +#include "usb_remote_ds_service.hpp" + +namespace ams::usb { + + #if defined(ATMOSPHERE_OS_HORIZON) + Result RemoteDsRootSession::GetService(sf::Out<sf::SharedPointer<usb::ds::IDsService>> out) { + Service srv; + + serviceAssumeDomain(std::addressof(m_srv)); + R_TRY(serviceDispatch(std::addressof(m_srv), 0, .out_num_objects = 1, .out_objects = std::addressof(srv))); + + *out = ObjectFactory::CreateSharedEmplaced<ds::IDsService, RemoteDsService>(m_allocator, srv, m_allocator); + + R_SUCCEED(); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_root_session.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_root_session.hpp new file mode 100644 index 00000000..9a17459e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_root_session.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::usb { + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemoteDsRootSession { + private: + using Allocator = sf::ExpHeapAllocator; + using ObjectFactory = sf::ObjectFactory<Allocator::Policy>; + private: + Service m_srv; + Allocator *m_allocator; + public: + RemoteDsRootSession(Service &srv, sf::ExpHeapAllocator *allocator) : m_srv(srv), m_allocator(allocator) { /* ... */ } + virtual ~RemoteDsRootSession() { serviceClose(std::addressof(m_srv)); } + public: + Result GetService(sf::Out<sf::SharedPointer<usb::ds::IDsService>> out); + }; + static_assert(ds::IsIDsRootSession<RemoteDsRootSession>); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_service.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_service.cpp new file mode 100644 index 00000000..817563fe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_service.cpp @@ -0,0 +1,121 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "usb_remote_ds_service.hpp" +#include "usb_remote_ds_interface.hpp" + +namespace ams::usb { + + #if defined(ATMOSPHERE_OS_HORIZON) + Result RemoteDsService::Bind(usb::ComplexId complex_id, sf::CopyHandle &&process_h) { + if (hos::GetVersion() >= hos::Version_11_0_0) { + serviceAssumeDomain(std::addressof(m_srv)); + R_TRY(serviceDispatchIn(std::addressof(m_srv), 0, complex_id, + .in_num_handles = 1, + .in_handles = { process_h.GetOsHandle() } + )); + } else { + serviceAssumeDomain(std::addressof(m_srv)); + R_TRY(serviceDispatchIn(std::addressof(m_srv), 0, complex_id)); + + serviceAssumeDomain(std::addressof(m_srv)); + R_TRY(serviceDispatch(std::addressof(m_srv), 1, + .in_num_handles = 1, + .in_handles = { process_h.GetOsHandle() }) + ); + } + + R_SUCCEED(); + } + + Result RemoteDsService::RegisterInterface(sf::Out<sf::SharedPointer<usb::ds::IDsInterface>> out, u8 bInterfaceNumber) { + Service srv; + + serviceAssumeDomain(std::addressof(m_srv)); + R_TRY(serviceDispatchIn(std::addressof(m_srv), (hos::GetVersion() >= hos::Version_11_0_0 ? 1 : 2), bInterfaceNumber, + .out_num_objects = 1, + .out_objects = std::addressof(srv), + )); + + *out = ObjectFactory::CreateSharedEmplaced<ds::IDsInterface, RemoteDsInterface>(m_allocator, srv, m_allocator); + + R_SUCCEED(); + } + + Result RemoteDsService::GetStateChangeEvent(sf::OutCopyHandle out) { + serviceAssumeDomain(std::addressof(m_srv)); + + os::NativeHandle event_handle; + R_TRY((serviceDispatch(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 2 : 3, + .out_handle_attrs = { SfOutHandleAttr_HipcCopy }, + .out_handles = std::addressof(event_handle), + ))); + + out.SetValue(event_handle, true); + R_SUCCEED(); + } + + Result RemoteDsService::GetState(sf::Out<usb::UsbState> out) { + serviceAssumeDomain(std::addressof(m_srv)); + R_RETURN(serviceDispatchOut(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 3 : 4, *out)); + } + + Result RemoteDsService::ClearDeviceData() { + serviceAssumeDomain(std::addressof(m_srv)); + R_RETURN(serviceDispatch(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 4 : 5)); + } + + Result RemoteDsService::AddUsbStringDescriptor(sf::Out<u8> out, const sf::InBuffer &desc) { + serviceAssumeDomain(std::addressof(m_srv)); + return serviceDispatchOut(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 5 : 6, *out, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, + .buffers = { { desc.GetPointer(), desc.GetSize() } }, + ); + } + + Result RemoteDsService::DeleteUsbStringDescriptor(u8 index) { + serviceAssumeDomain(std::addressof(m_srv)); + R_RETURN(serviceDispatchIn(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 6 : 7, index)); + } + + Result RemoteDsService::SetUsbDeviceDescriptor(const sf::InBuffer &desc, usb::UsbDeviceSpeed speed) { + serviceAssumeDomain(std::addressof(m_srv)); + return serviceDispatchIn(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 7 : 8, speed, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, + .buffers = { { desc.GetPointer(), desc.GetSize() } }, + ); + } + + Result RemoteDsService::SetBinaryObjectStore(const sf::InBuffer &bos) { + serviceAssumeDomain(std::addressof(m_srv)); + return serviceDispatch(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 8 : 9, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, + .buffers = { { bos.GetPointer(), bos.GetSize() } }, + ); + } + + Result RemoteDsService::Enable() { + serviceAssumeDomain(std::addressof(m_srv)); + R_RETURN(serviceDispatch(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 9 : 10)); + } + + Result RemoteDsService::Disable() { + serviceAssumeDomain(std::addressof(m_srv)); + R_RETURN(serviceDispatch(std::addressof(m_srv), hos::GetVersion() >= hos::Version_11_0_0 ? 10 : 11)); + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_service.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_service.hpp new file mode 100644 index 00000000..35102529 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/usb/usb_remote_ds_service.hpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::usb { + + #if defined(ATMOSPHERE_OS_HORIZON) + class RemoteDsService { + private: + using Allocator = sf::ExpHeapAllocator; + using ObjectFactory = sf::ObjectFactory<Allocator::Policy>; + private: + Service m_srv; + Allocator *m_allocator; + public: + RemoteDsService(Service &srv, sf::ExpHeapAllocator *allocator) : m_srv(srv), m_allocator(allocator) { /* ... */ } + virtual ~RemoteDsService() { serviceClose(std::addressof(m_srv)); } + public: + Result Bind(usb::ComplexId complex_id, sf::CopyHandle &&process_h); + Result RegisterInterface(sf::Out<sf::SharedPointer<usb::ds::IDsInterface>> out, u8 bInterfaceNumber); + Result GetStateChangeEvent(sf::OutCopyHandle out); + Result GetState(sf::Out<usb::UsbState> out); + Result ClearDeviceData(); + Result AddUsbStringDescriptor(sf::Out<u8> out, const sf::InBuffer &desc); + Result DeleteUsbStringDescriptor(u8 index); + Result SetUsbDeviceDescriptor(const sf::InBuffer &desc, usb::UsbDeviceSpeed speed); + Result SetBinaryObjectStore(const sf::InBuffer &bos); + Result Enable(); + Result Disable(); + }; + static_assert(ds::IsIDsService<RemoteDsService>); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/ini.c b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/ini.c new file mode 100644 index 00000000..426430c9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/ini.c @@ -0,0 +1,269 @@ +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include <stdio.h> +#include <ctype.h> +#include <string.h> + +#include "ini.h" + +#if !INI_USE_STACK +#include <stdlib.h> +#endif + +#define MAX_SECTION 72 +#define MAX_NAME 72 + +/* Used by ini_parse_string() to keep track of string parsing state. */ +typedef struct { + const char* ptr; + size_t num_left; +} ini_parse_string_ctx; + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char* rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to null at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +static char* find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; + } +#endif + return (char*)s; +} + +/* Version of strncpy that ensures dest (size bytes) is null-terminated. */ +static char* strncpy0(char* dest, const char* src, size_t size) +{ + strncpy(dest, src, size - 1); + dest[size - 1] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; + int max_line = INI_MAX_LINE; +#else + char* line; + int max_line = INI_INITIAL_ALLOC; +#endif +#if INI_ALLOW_REALLOC + char* new_line; + int offset; +#endif + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + +#if !INI_USE_STACK + line = (char*)malloc(INI_INITIAL_ALLOC); + if (!line) { + return -2; + } +#endif + +#if INI_HANDLER_LINENO +#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) +#else +#define HANDLER(u, s, n, v) handler(u, s, n, v) +#endif + + /* Scan through stream line by line */ + while (reader(line, max_line, stream) != NULL) { +#if INI_ALLOW_REALLOC + offset = strlen(line); + while (offset == max_line - 1 && line[offset - 1] != '\n') { + max_line *= 2; + if (max_line > INI_MAX_LINE) + max_line = INI_MAX_LINE; + new_line = realloc(line, max_line); + if (!new_line) { + free(line); + return -2; + } + line = new_line; + if (reader(line + offset, max_line - offset, stream) == NULL) + break; + if (max_line >= INI_MAX_LINE) + break; + offset += strlen(line + offset); + } +#endif + + lineno++; + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = lskip(rstrip(start)); + + if (strchr(INI_START_COMMENT_PREFIXES, *start)) { + /* Start-of-line comment */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!HANDLER(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = end + 1; +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(value, NULL); + if (*end) + *end = '\0'; +#endif + value = lskip(value); + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!HANDLER(user, section, name, value) && !error) + error = lineno; + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ + error = lineno; + } + } + +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif + } + +#if !INI_USE_STACK + free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, ini_handler handler, void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} + +/* An ini_reader function to read the next line from a string buffer. This + is the fgets() equivalent used by ini_parse_string(). */ +static char* ini_reader_string(char* str, int num, void* stream) { + ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; + const char* ctx_ptr = ctx->ptr; + size_t ctx_num_left = ctx->num_left; + char* strp = str; + char c; + + if (ctx_num_left == 0 || num < 2) + return NULL; + + while (num > 1 && ctx_num_left != 0) { + c = *ctx_ptr++; + ctx_num_left--; + *strp++ = c; + if (c == '\n') + break; + num--; + } + + *strp = '\0'; + ctx->ptr = ctx_ptr; + ctx->num_left = ctx_num_left; + return str; +} + +/* See documentation in header file. */ +int ini_parse_string(const char* string, ini_handler handler, void* user) { + ini_parse_string_ctx ctx; + + ctx.ptr = string; + ctx.num_left = strlen(string); + return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, + user); +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/ini.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/ini.h new file mode 100644 index 00000000..047235e8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/ini.h @@ -0,0 +1,130 @@ +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#ifndef __INI_H__ +#define __INI_H__ + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdio.h> + +/* Nonzero if ini_handler callback should accept lineno parameter. */ +#ifndef INI_HANDLER_LINENO +#define INI_HANDLER_LINENO 0 +#endif + +/* Typedef for prototype of handler function. */ +#if INI_HANDLER_LINENO +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value, + int lineno); +#else +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value); +#endif + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +int ini_parse(const char* filename, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O (see also + ini_parse_string). */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user); + +/* Same as ini_parse(), but takes a zero-terminated string with the INI data +instead of a file. Useful for parsing INI data from a network socket or +already in memory. */ +int ini_parse_string(const char* string, ini_handler handler, void* user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See http://code.google.com/p/inih/issues/detail?id=21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Chars that begin a start-of-line comment. Per Python configparser, allow + both ; and # comments at the start of a line by default. */ +#ifndef INI_START_COMMENT_PREFIXES +#define INI_START_COMMENT_PREFIXES ";#" +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +#define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +#define INI_INLINE_COMMENT_PREFIXES ";" +#endif + +/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Maximum line length for any line in INI file (stack or heap). Note that + this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 0x480 +#endif + +/* Nonzero to allow heap line buffer to grow via realloc(), zero for a + fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is + zero. */ +#ifndef INI_ALLOW_REALLOC +#define INI_ALLOW_REALLOC 0 +#endif + +/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK + is zero. */ +#ifndef INI_INITIAL_ALLOC +#define INI_INITIAL_ALLOC 200 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +#define INI_STOP_ON_FIRST_ERROR 0 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __INI_H__ */ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/lz4.c b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/lz4.c new file mode 100644 index 00000000..a2272cfb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/lz4.c @@ -0,0 +1,2526 @@ +/* + LZ4 - Fast LZ compression algorithm + Copyright (C) 2011-2020, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/lz4/lz4 +*/ + +/*-************************************ +* Tuning parameters +**************************************/ +/* + * LZ4_HEAPMODE : + * Select how default compression functions will allocate memory for their hash table, + * in memory stack (0:default, fastest), or in memory heap (1:requires malloc()). + */ +#ifndef LZ4_HEAPMODE +# define LZ4_HEAPMODE 0 +#endif + +/* + * LZ4_ACCELERATION_DEFAULT : + * Select "acceleration" for LZ4_compress_fast() when parameter value <= 0 + */ +#define LZ4_ACCELERATION_DEFAULT 1 +/* + * LZ4_ACCELERATION_MAX : + * Any "acceleration" value higher than this threshold + * get treated as LZ4_ACCELERATION_MAX instead (fix #876) + */ +#define LZ4_ACCELERATION_MAX 65537 + + +/*-************************************ +* CPU Feature Detection +**************************************/ +/* LZ4_FORCE_MEMORY_ACCESS + * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. + * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. + * The below switch allow to select different access method for improved performance. + * Method 0 (default) : use `memcpy()`. Safe and portable. + * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable). + * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. + * Method 2 : direct access. This method is portable but violate C standard. + * It can generate buggy code on targets which assembly generation depends on alignment. + * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6) + * See https://fastcompression.blogspot.fr/2015/08/accessing-unaligned-memory.html for details. + * Prefer these methods in priority order (0 > 1 > 2) + */ +#ifndef LZ4_FORCE_MEMORY_ACCESS /* can be defined externally */ +# if defined(__GNUC__) && \ + ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) \ + || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) +# define LZ4_FORCE_MEMORY_ACCESS 2 +# elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || defined(__GNUC__) +# define LZ4_FORCE_MEMORY_ACCESS 1 +# endif +#endif + +/* + * LZ4_FORCE_SW_BITCOUNT + * Define this parameter if your target system or compiler does not support hardware bit count + */ +#if defined(_MSC_VER) && defined(_WIN32_WCE) /* Visual Studio for WinCE doesn't support Hardware bit count */ +# undef LZ4_FORCE_SW_BITCOUNT /* avoid double def */ +# define LZ4_FORCE_SW_BITCOUNT +#endif + + + +/*-************************************ +* Dependency +**************************************/ +/* + * LZ4_SRC_INCLUDED: + * Amalgamation flag, whether lz4.c is included + */ +#ifndef LZ4_SRC_INCLUDED +# define LZ4_SRC_INCLUDED 1 +#endif + +#ifndef LZ4_STATIC_LINKING_ONLY +#define LZ4_STATIC_LINKING_ONLY +#endif + +#ifndef LZ4_DISABLE_DEPRECATE_WARNINGS +#define LZ4_DISABLE_DEPRECATE_WARNINGS /* due to LZ4_decompress_safe_withPrefix64k */ +#endif + +#define LZ4_STATIC_LINKING_ONLY /* LZ4_DISTANCE_MAX */ +#include "lz4.h" +/* see also "memory routines" below */ + + +/*-************************************ +* Compiler Options +**************************************/ +#if defined(_MSC_VER) && (_MSC_VER >= 1400) /* Visual Studio 2005+ */ +# include <intrin.h> /* only present in VS2005+ */ +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +#endif /* _MSC_VER */ + +#ifndef LZ4_FORCE_INLINE +# ifdef _MSC_VER /* Visual Studio */ +# define LZ4_FORCE_INLINE static __forceinline +# else +# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ +# ifdef __GNUC__ +# define LZ4_FORCE_INLINE static inline __attribute__((always_inline)) +# else +# define LZ4_FORCE_INLINE static inline +# endif +# else +# define LZ4_FORCE_INLINE static +# endif /* __STDC_VERSION__ */ +# endif /* _MSC_VER */ +#endif /* LZ4_FORCE_INLINE */ + +/* LZ4_FORCE_O2 and LZ4_FORCE_INLINE + * gcc on ppc64le generates an unrolled SIMDized loop for LZ4_wildCopy8, + * together with a simple 8-byte copy loop as a fall-back path. + * However, this optimization hurts the decompression speed by >30%, + * because the execution does not go to the optimized loop + * for typical compressible data, and all of the preamble checks + * before going to the fall-back path become useless overhead. + * This optimization happens only with the -O3 flag, and -O2 generates + * a simple 8-byte copy loop. + * With gcc on ppc64le, all of the LZ4_decompress_* and LZ4_wildCopy8 + * functions are annotated with __attribute__((optimize("O2"))), + * and also LZ4_wildCopy8 is forcibly inlined, so that the O2 attribute + * of LZ4_wildCopy8 does not affect the compression speed. + */ +#if defined(__PPC64__) && defined(__LITTLE_ENDIAN__) && defined(__GNUC__) && !defined(__clang__) +# define LZ4_FORCE_O2 __attribute__((optimize("O2"))) +# undef LZ4_FORCE_INLINE +# define LZ4_FORCE_INLINE static __inline __attribute__((optimize("O2"),always_inline)) +#else +# define LZ4_FORCE_O2 +#endif + +#if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__) +# define expect(expr,value) (__builtin_expect ((expr),(value)) ) +#else +# define expect(expr,value) (expr) +#endif + +#ifndef likely +#define likely(expr) expect((expr) != 0, 1) +#endif +#ifndef unlikely +#define unlikely(expr) expect((expr) != 0, 0) +#endif + +/* Should the alignment test prove unreliable, for some reason, + * it can be disabled by setting LZ4_ALIGN_TEST to 0 */ +#ifndef LZ4_ALIGN_TEST /* can be externally provided */ +# define LZ4_ALIGN_TEST 1 +#endif + + +/*-************************************ +* Memory routines +**************************************/ +#ifdef LZ4_USER_MEMORY_FUNCTIONS +/* memory management functions can be customized by user project. + * Below functions must exist somewhere in the Project + * and be available at link time */ +void* LZ4_malloc(size_t s); +void* LZ4_calloc(size_t n, size_t s); +void LZ4_free(void* p); +# define ALLOC(s) LZ4_malloc(s) +# define ALLOC_AND_ZERO(s) LZ4_calloc(1,s) +# define FREEMEM(p) LZ4_free(p) +#else +# include <stdlib.h> /* malloc, calloc, free */ +# define ALLOC(s) malloc(s) +# define ALLOC_AND_ZERO(s) calloc(1,s) +# define FREEMEM(p) free(p) +#endif + +#include <string.h> /* memset, memcpy */ +#define MEM_INIT(p,v,s) memset((p),(v),(s)) + + +/*-************************************ +* Common Constants +**************************************/ +#define MINMATCH 4 + +#define WILDCOPYLENGTH 8 +#define LASTLITERALS 5 /* see ../doc/lz4_Block_format.md#parsing-restrictions */ +#define MFLIMIT 12 /* see ../doc/lz4_Block_format.md#parsing-restrictions */ +#define MATCH_SAFEGUARD_DISTANCE ((2*WILDCOPYLENGTH) - MINMATCH) /* ensure it's possible to write 2 x wildcopyLength without overflowing output buffer */ +#define FASTLOOP_SAFE_DISTANCE 64 +static const int LZ4_minLength = (MFLIMIT+1); + +#define KB *(1 <<10) +#define MB *(1 <<20) +#define GB *(1U<<30) + +#define LZ4_DISTANCE_ABSOLUTE_MAX 65535 +#if (LZ4_DISTANCE_MAX > LZ4_DISTANCE_ABSOLUTE_MAX) /* max supported by LZ4 format */ +# error "LZ4_DISTANCE_MAX is too big : must be <= 65535" +#endif + +#define ML_BITS 4 +#define ML_MASK ((1U<<ML_BITS)-1) +#define RUN_BITS (8-ML_BITS) +#define RUN_MASK ((1U<<RUN_BITS)-1) + + +/*-************************************ +* Error detection +**************************************/ +#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=1) +# include <assert.h> +#else +# ifndef assert +# define assert(condition) ((void)0) +# endif +#endif + +#define LZ4_STATIC_ASSERT(c) { enum { LZ4_static_assert = 1/(int)(!!(c)) }; } /* use after variable declarations */ + +#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=2) +# include <stdio.h> + static int g_debuglog_enable = 1; +# define DEBUGLOG(l, ...) { \ + if ((g_debuglog_enable) && (l<=LZ4_DEBUG)) { \ + fprintf(stderr, __FILE__ ": "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " \n"); \ + } } +#else +# define DEBUGLOG(l, ...) {} /* disabled */ +#endif + +static int LZ4_isAligned(const void* ptr, size_t alignment) +{ + return ((size_t)ptr & (alignment -1)) == 0; +} + + +/*-************************************ +* Types +**************************************/ +#include <limits.h> +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# include <stdint.h> + typedef uint8_t BYTE; + typedef uint16_t U16; + typedef uint32_t U32; + typedef int32_t S32; + typedef uint64_t U64; + typedef uintptr_t uptrval; +#else +# if UINT_MAX != 4294967295UL +# error "LZ4 code (when not C++ or C99) assumes that sizeof(int) == 4" +# endif + typedef unsigned char BYTE; + typedef unsigned short U16; + typedef unsigned int U32; + typedef signed int S32; + typedef unsigned long long U64; + typedef size_t uptrval; /* generally true, except OpenVMS-64 */ +#endif + +#if defined(__x86_64__) + typedef U64 reg_t; /* 64-bits in x32 mode */ +#else + typedef size_t reg_t; /* 32-bits in x32 mode */ +#endif + +typedef enum { + notLimited = 0, + limitedOutput = 1, + fillOutput = 2 +} limitedOutput_directive; + + +/*-************************************ +* Reading and writing into memory +**************************************/ + +/** + * LZ4 relies on memcpy with a constant size being inlined. In freestanding + * environments, the compiler can't assume the implementation of memcpy() is + * standard compliant, so it can't apply its specialized memcpy() inlining + * logic. When possible, use __builtin_memcpy() to tell the compiler to analyze + * memcpy() as if it were standard compliant, so it can inline it in freestanding + * environments. This is needed when decompressing the Linux Kernel, for example. + */ +#if defined(__GNUC__) && (__GNUC__ >= 4) +#define LZ4_memcpy(dst, src, size) __builtin_memcpy(dst, src, size) +#else +#define LZ4_memcpy(dst, src, size) memcpy(dst, src, size) +#endif + +static unsigned LZ4_isLittleEndian(void) +{ + const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ + return one.c[0]; +} + + +#if defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==2) +/* lie to the compiler about data alignment; use with caution */ + +static U16 LZ4_read16(const void* memPtr) { return *(const U16*) memPtr; } +static U32 LZ4_read32(const void* memPtr) { return *(const U32*) memPtr; } +static reg_t LZ4_read_ARCH(const void* memPtr) { return *(const reg_t*) memPtr; } + +static void LZ4_write16(void* memPtr, U16 value) { *(U16*)memPtr = value; } +static void LZ4_write32(void* memPtr, U32 value) { *(U32*)memPtr = value; } + +#elif defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==1) + +/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ +/* currently only defined for gcc and icc */ +typedef union { U16 u16; U32 u32; reg_t uArch; } __attribute__((packed)) LZ4_unalign; + +static U16 LZ4_read16(const void* ptr) { return ((const LZ4_unalign*)ptr)->u16; } +static U32 LZ4_read32(const void* ptr) { return ((const LZ4_unalign*)ptr)->u32; } +static reg_t LZ4_read_ARCH(const void* ptr) { return ((const LZ4_unalign*)ptr)->uArch; } + +static void LZ4_write16(void* memPtr, U16 value) { ((LZ4_unalign*)memPtr)->u16 = value; } +static void LZ4_write32(void* memPtr, U32 value) { ((LZ4_unalign*)memPtr)->u32 = value; } + +#else /* safe and portable access using memcpy() */ + +static U16 LZ4_read16(const void* memPtr) +{ + U16 val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; +} + +static U32 LZ4_read32(const void* memPtr) +{ + U32 val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; +} + +static reg_t LZ4_read_ARCH(const void* memPtr) +{ + reg_t val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; +} + +static void LZ4_write16(void* memPtr, U16 value) +{ + LZ4_memcpy(memPtr, &value, sizeof(value)); +} + +static void LZ4_write32(void* memPtr, U32 value) +{ + LZ4_memcpy(memPtr, &value, sizeof(value)); +} + +#endif /* LZ4_FORCE_MEMORY_ACCESS */ + + +static U16 LZ4_readLE16(const void* memPtr) +{ + if (LZ4_isLittleEndian()) { + return LZ4_read16(memPtr); + } else { + const BYTE* p = (const BYTE*)memPtr; + return (U16)((U16)p[0] + (p[1]<<8)); + } +} + +static void LZ4_writeLE16(void* memPtr, U16 value) +{ + if (LZ4_isLittleEndian()) { + LZ4_write16(memPtr, value); + } else { + BYTE* p = (BYTE*)memPtr; + p[0] = (BYTE) value; + p[1] = (BYTE)(value>>8); + } +} + +/* customized variant of memcpy, which can overwrite up to 8 bytes beyond dstEnd */ +LZ4_FORCE_INLINE +void LZ4_wildCopy8(void* dstPtr, const void* srcPtr, void* dstEnd) +{ + BYTE* d = (BYTE*)dstPtr; + const BYTE* s = (const BYTE*)srcPtr; + BYTE* const e = (BYTE*)dstEnd; + + do { LZ4_memcpy(d,s,8); d+=8; s+=8; } while (d<e); +} + +static const unsigned inc32table[8] = {0, 1, 2, 1, 0, 4, 4, 4}; +static const int dec64table[8] = {0, 0, 0, -1, -4, 1, 2, 3}; + + +#ifndef LZ4_FAST_DEC_LOOP +# if defined __i386__ || defined _M_IX86 || defined __x86_64__ || defined _M_X64 +# define LZ4_FAST_DEC_LOOP 1 +# elif defined(__aarch64__) && defined(__APPLE__) +# define LZ4_FAST_DEC_LOOP 1 +# elif defined(__aarch64__) && !defined(__clang__) + /* On non-Apple aarch64, we disable this optimization for clang because + * on certain mobile chipsets, performance is reduced with clang. For + * more information refer to https://github.com/lz4/lz4/pull/707 */ +# define LZ4_FAST_DEC_LOOP 1 +# else +# define LZ4_FAST_DEC_LOOP 0 +# endif +#endif + +#if LZ4_FAST_DEC_LOOP + +LZ4_FORCE_INLINE void +LZ4_memcpy_using_offset_base(BYTE* dstPtr, const BYTE* srcPtr, BYTE* dstEnd, const size_t offset) +{ + assert(srcPtr + offset == dstPtr); + if (offset < 8) { + LZ4_write32(dstPtr, 0); /* silence an msan warning when offset==0 */ + dstPtr[0] = srcPtr[0]; + dstPtr[1] = srcPtr[1]; + dstPtr[2] = srcPtr[2]; + dstPtr[3] = srcPtr[3]; + srcPtr += inc32table[offset]; + LZ4_memcpy(dstPtr+4, srcPtr, 4); + srcPtr -= dec64table[offset]; + dstPtr += 8; + } else { + LZ4_memcpy(dstPtr, srcPtr, 8); + dstPtr += 8; + srcPtr += 8; + } + + LZ4_wildCopy8(dstPtr, srcPtr, dstEnd); +} + +/* customized variant of memcpy, which can overwrite up to 32 bytes beyond dstEnd + * this version copies two times 16 bytes (instead of one time 32 bytes) + * because it must be compatible with offsets >= 16. */ +LZ4_FORCE_INLINE void +LZ4_wildCopy32(void* dstPtr, const void* srcPtr, void* dstEnd) +{ + BYTE* d = (BYTE*)dstPtr; + const BYTE* s = (const BYTE*)srcPtr; + BYTE* const e = (BYTE*)dstEnd; + + do { LZ4_memcpy(d,s,16); LZ4_memcpy(d+16,s+16,16); d+=32; s+=32; } while (d<e); +} + +/* LZ4_memcpy_using_offset() presumes : + * - dstEnd >= dstPtr + MINMATCH + * - there is at least 8 bytes available to write after dstEnd */ +LZ4_FORCE_INLINE void +LZ4_memcpy_using_offset(BYTE* dstPtr, const BYTE* srcPtr, BYTE* dstEnd, const size_t offset) +{ + BYTE v[8]; + + assert(dstEnd >= dstPtr + MINMATCH); + + switch(offset) { + case 1: + MEM_INIT(v, *srcPtr, 8); + break; + case 2: + LZ4_memcpy(v, srcPtr, 2); + LZ4_memcpy(&v[2], srcPtr, 2); + LZ4_memcpy(&v[4], v, 4); + break; + case 4: + LZ4_memcpy(v, srcPtr, 4); + LZ4_memcpy(&v[4], srcPtr, 4); + break; + default: + LZ4_memcpy_using_offset_base(dstPtr, srcPtr, dstEnd, offset); + return; + } + + LZ4_memcpy(dstPtr, v, 8); + dstPtr += 8; + while (dstPtr < dstEnd) { + LZ4_memcpy(dstPtr, v, 8); + dstPtr += 8; + } +} +#endif + + +/*-************************************ +* Common functions +**************************************/ +static unsigned LZ4_NbCommonBytes (reg_t val) +{ + assert(val != 0); + if (LZ4_isLittleEndian()) { + if (sizeof(val) == 8) { +# if defined(_MSC_VER) && (_MSC_VER >= 1800) && (defined(_M_AMD64) && !defined(_M_ARM64EC)) && !defined(LZ4_FORCE_SW_BITCOUNT) +/*-************************************************************************************************* +* ARM64EC is a Microsoft-designed ARM64 ABI compatible with AMD64 applications on ARM64 Windows 11. +* The ARM64EC ABI does not support AVX/AVX2/AVX512 instructions, nor their relevant intrinsics +* including _tzcnt_u64. Therefore, we need to neuter the _tzcnt_u64 code path for ARM64EC. +****************************************************************************************************/ +# if defined(__clang__) && (__clang_major__ < 10) + /* Avoid undefined clang-cl intrinics issue. + * See https://github.com/lz4/lz4/pull/1017 for details. */ + return (unsigned)__builtin_ia32_tzcnt_u64(val) >> 3; +# else + /* x64 CPUS without BMI support interpret `TZCNT` as `REP BSF` */ + return (unsigned)_tzcnt_u64(val) >> 3; +# endif +# elif defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanForward64(&r, (U64)val); + return (unsigned)r >> 3; +# elif (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_ctzll((U64)val) >> 3; +# else + const U64 m = 0x0101010101010101ULL; + val ^= val - 1; + return (unsigned)(((U64)((val & (m - 1)) * m)) >> 56); +# endif + } else /* 32 bits */ { +# if defined(_MSC_VER) && (_MSC_VER >= 1400) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r; + _BitScanForward(&r, (U32)val); + return (unsigned)r >> 3; +# elif (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(__TINYC__) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_ctz((U32)val) >> 3; +# else + const U32 m = 0x01010101; + return (unsigned)((((val - 1) ^ val) & (m - 1)) * m) >> 24; +# endif + } + } else /* Big Endian CPU */ { + if (sizeof(val)==8) { +# if (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(__TINYC__) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_clzll((U64)val) >> 3; +# else +#if 1 + /* this method is probably faster, + * but adds a 128 bytes lookup table */ + static const unsigned char ctz7_tab[128] = { + 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + }; + U64 const mask = 0x0101010101010101ULL; + U64 const t = (((val >> 8) - mask) | val) & mask; + return ctz7_tab[(t * 0x0080402010080402ULL) >> 57]; +#else + /* this method doesn't consume memory space like the previous one, + * but it contains several branches, + * that may end up slowing execution */ + static const U32 by32 = sizeof(val)*4; /* 32 on 64 bits (goal), 16 on 32 bits. + Just to avoid some static analyzer complaining about shift by 32 on 32-bits target. + Note that this code path is never triggered in 32-bits mode. */ + unsigned r; + if (!(val>>by32)) { r=4; } else { r=0; val>>=by32; } + if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; } + r += (!val); + return r; +#endif +# endif + } else /* 32 bits */ { +# if (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_clz((U32)val) >> 3; +# else + val >>= 8; + val = ((((val + 0x00FFFF00) | 0x00FFFFFF) + val) | + (val + 0x00FF0000)) >> 24; + return (unsigned)val ^ 3; +# endif + } + } +} + + +#define STEPSIZE sizeof(reg_t) +LZ4_FORCE_INLINE +unsigned LZ4_count(const BYTE* pIn, const BYTE* pMatch, const BYTE* pInLimit) +{ + const BYTE* const pStart = pIn; + + if (likely(pIn < pInLimit-(STEPSIZE-1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { + pIn+=STEPSIZE; pMatch+=STEPSIZE; + } else { + return LZ4_NbCommonBytes(diff); + } } + + while (likely(pIn < pInLimit-(STEPSIZE-1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { pIn+=STEPSIZE; pMatch+=STEPSIZE; continue; } + pIn += LZ4_NbCommonBytes(diff); + return (unsigned)(pIn - pStart); + } + + if ((STEPSIZE==8) && (pIn<(pInLimit-3)) && (LZ4_read32(pMatch) == LZ4_read32(pIn))) { pIn+=4; pMatch+=4; } + if ((pIn<(pInLimit-1)) && (LZ4_read16(pMatch) == LZ4_read16(pIn))) { pIn+=2; pMatch+=2; } + if ((pIn<pInLimit) && (*pMatch == *pIn)) pIn++; + return (unsigned)(pIn - pStart); +} + + +#ifndef LZ4_COMMONDEFS_ONLY +/*-************************************ +* Local Constants +**************************************/ +static const int LZ4_64Klimit = ((64 KB) + (MFLIMIT-1)); +static const U32 LZ4_skipTrigger = 6; /* Increase this value ==> compression run slower on incompressible data */ + + +/*-************************************ +* Local Structures and types +**************************************/ +typedef enum { clearedTable = 0, byPtr, byU32, byU16 } tableType_t; + +/** + * This enum distinguishes several different modes of accessing previous + * content in the stream. + * + * - noDict : There is no preceding content. + * - withPrefix64k : Table entries up to ctx->dictSize before the current blob + * blob being compressed are valid and refer to the preceding + * content (of length ctx->dictSize), which is available + * contiguously preceding in memory the content currently + * being compressed. + * - usingExtDict : Like withPrefix64k, but the preceding content is somewhere + * else in memory, starting at ctx->dictionary with length + * ctx->dictSize. + * - usingDictCtx : Everything concerning the preceding content is + * in a separate context, pointed to by ctx->dictCtx. + * ctx->dictionary, ctx->dictSize, and table entries + * in the current context that refer to positions + * preceding the beginning of the current compression are + * ignored. Instead, ctx->dictCtx->dictionary and ctx->dictCtx + * ->dictSize describe the location and size of the preceding + * content, and matches are found by looking in the ctx + * ->dictCtx->hashTable. + */ +typedef enum { noDict = 0, withPrefix64k, usingExtDict, usingDictCtx } dict_directive; +typedef enum { noDictIssue = 0, dictSmall } dictIssue_directive; + + +/*-************************************ +* Local Utils +**************************************/ +int LZ4_versionNumber (void) { return LZ4_VERSION_NUMBER; } +const char* LZ4_versionString(void) { return LZ4_VERSION_STRING; } +int LZ4_compressBound(int isize) { return LZ4_COMPRESSBOUND(isize); } +int LZ4_sizeofState(void) { return LZ4_STREAMSIZE; } + + +/*-**************************************** +* Internal Definitions, used only in Tests +*******************************************/ +#if defined (__cplusplus) +extern "C" { +#endif + +int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize); + +int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, + int compressedSize, int maxOutputSize, + const void* dictStart, size_t dictSize); + +#if defined (__cplusplus) +} +#endif + +/*-****************************** +* Compression functions +********************************/ +LZ4_FORCE_INLINE U32 LZ4_hash4(U32 sequence, tableType_t const tableType) +{ + if (tableType == byU16) + return ((sequence * 2654435761U) >> ((MINMATCH*8)-(LZ4_HASHLOG+1))); + else + return ((sequence * 2654435761U) >> ((MINMATCH*8)-LZ4_HASHLOG)); +} + +LZ4_FORCE_INLINE U32 LZ4_hash5(U64 sequence, tableType_t const tableType) +{ + const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG+1 : LZ4_HASHLOG; + if (LZ4_isLittleEndian()) { + const U64 prime5bytes = 889523592379ULL; + return (U32)(((sequence << 24) * prime5bytes) >> (64 - hashLog)); + } else { + const U64 prime8bytes = 11400714785074694791ULL; + return (U32)(((sequence >> 24) * prime8bytes) >> (64 - hashLog)); + } +} + +LZ4_FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType) +{ + if ((sizeof(reg_t)==8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType); + return LZ4_hash4(LZ4_read32(p), tableType); +} + +LZ4_FORCE_INLINE void LZ4_clearHash(U32 h, void* tableBase, tableType_t const tableType) +{ + switch (tableType) + { + default: /* fallthrough */ + case clearedTable: { /* illegal! */ assert(0); return; } + case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = NULL; return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = 0; return; } + case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = 0; return; } + } +} + +LZ4_FORCE_INLINE void LZ4_putIndexOnHash(U32 idx, U32 h, void* tableBase, tableType_t const tableType) +{ + switch (tableType) + { + default: /* fallthrough */ + case clearedTable: /* fallthrough */ + case byPtr: { /* illegal! */ assert(0); return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = idx; return; } + case byU16: { U16* hashTable = (U16*) tableBase; assert(idx < 65536); hashTable[h] = (U16)idx; return; } + } +} + +LZ4_FORCE_INLINE void LZ4_putPositionOnHash(const BYTE* p, U32 h, + void* tableBase, tableType_t const tableType, + const BYTE* srcBase) +{ + switch (tableType) + { + case clearedTable: { /* illegal! */ assert(0); return; } + case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = p; return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = (U32)(p-srcBase); return; } + case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = (U16)(p-srcBase); return; } + } +} + +LZ4_FORCE_INLINE void LZ4_putPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + LZ4_putPositionOnHash(p, h, tableBase, tableType, srcBase); +} + +/* LZ4_getIndexOnHash() : + * Index of match position registered in hash table. + * hash position must be calculated by using base+index, or dictBase+index. + * Assumption 1 : only valid if tableType == byU32 or byU16. + * Assumption 2 : h is presumed valid (within limits of hash table) + */ +LZ4_FORCE_INLINE U32 LZ4_getIndexOnHash(U32 h, const void* tableBase, tableType_t tableType) +{ + LZ4_STATIC_ASSERT(LZ4_MEMORY_USAGE > 2); + if (tableType == byU32) { + const U32* const hashTable = (const U32*) tableBase; + assert(h < (1U << (LZ4_MEMORY_USAGE-2))); + return hashTable[h]; + } + if (tableType == byU16) { + const U16* const hashTable = (const U16*) tableBase; + assert(h < (1U << (LZ4_MEMORY_USAGE-1))); + return hashTable[h]; + } + assert(0); return 0; /* forbidden case */ +} + +static const BYTE* LZ4_getPositionOnHash(U32 h, const void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + if (tableType == byPtr) { const BYTE* const* hashTable = (const BYTE* const*) tableBase; return hashTable[h]; } + if (tableType == byU32) { const U32* const hashTable = (const U32*) tableBase; return hashTable[h] + srcBase; } + { const U16* const hashTable = (const U16*) tableBase; return hashTable[h] + srcBase; } /* default, to ensure a return */ +} + +LZ4_FORCE_INLINE const BYTE* +LZ4_getPosition(const BYTE* p, + const void* tableBase, tableType_t tableType, + const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + return LZ4_getPositionOnHash(h, tableBase, tableType, srcBase); +} + +LZ4_FORCE_INLINE void +LZ4_prepareTable(LZ4_stream_t_internal* const cctx, + const int inputSize, + const tableType_t tableType) { + /* If the table hasn't been used, it's guaranteed to be zeroed out, and is + * therefore safe to use no matter what mode we're in. Otherwise, we figure + * out if it's safe to leave as is or whether it needs to be reset. + */ + if ((tableType_t)cctx->tableType != clearedTable) { + assert(inputSize >= 0); + if ((tableType_t)cctx->tableType != tableType + || ((tableType == byU16) && cctx->currentOffset + (unsigned)inputSize >= 0xFFFFU) + || ((tableType == byU32) && cctx->currentOffset > 1 GB) + || tableType == byPtr + || inputSize >= 4 KB) + { + DEBUGLOG(4, "LZ4_prepareTable: Resetting table in %p", cctx); + MEM_INIT(cctx->hashTable, 0, LZ4_HASHTABLESIZE); + cctx->currentOffset = 0; + cctx->tableType = (U32)clearedTable; + } else { + DEBUGLOG(4, "LZ4_prepareTable: Re-use hash table (no reset)"); + } + } + + /* Adding a gap, so all previous entries are > LZ4_DISTANCE_MAX back, + * is faster than compressing without a gap. + * However, compressing with currentOffset == 0 is faster still, + * so we preserve that case. + */ + if (cctx->currentOffset != 0 && tableType == byU32) { + DEBUGLOG(5, "LZ4_prepareTable: adding 64KB to currentOffset"); + cctx->currentOffset += 64 KB; + } + + /* Finally, clear history */ + cctx->dictCtx = NULL; + cctx->dictionary = NULL; + cctx->dictSize = 0; +} + +/** LZ4_compress_generic() : + * inlined, to ensure branches are decided at compilation time. + * Presumed already validated at this stage: + * - source != NULL + * - inputSize > 0 + */ +LZ4_FORCE_INLINE int LZ4_compress_generic_validated( + LZ4_stream_t_internal* const cctx, + const char* const source, + char* const dest, + const int inputSize, + int* inputConsumed, /* only written when outputDirective == fillOutput */ + const int maxOutputSize, + const limitedOutput_directive outputDirective, + const tableType_t tableType, + const dict_directive dictDirective, + const dictIssue_directive dictIssue, + const int acceleration) +{ + int result; + const BYTE* ip = (const BYTE*) source; + + U32 const startIndex = cctx->currentOffset; + const BYTE* base = (const BYTE*) source - startIndex; + const BYTE* lowLimit; + + const LZ4_stream_t_internal* dictCtx = (const LZ4_stream_t_internal*) cctx->dictCtx; + const BYTE* const dictionary = + dictDirective == usingDictCtx ? dictCtx->dictionary : cctx->dictionary; + const U32 dictSize = + dictDirective == usingDictCtx ? dictCtx->dictSize : cctx->dictSize; + const U32 dictDelta = (dictDirective == usingDictCtx) ? startIndex - dictCtx->currentOffset : 0; /* make indexes in dictCtx comparable with index in current context */ + + int const maybe_extMem = (dictDirective == usingExtDict) || (dictDirective == usingDictCtx); + U32 const prefixIdxLimit = startIndex - dictSize; /* used when dictDirective == dictSmall */ + const BYTE* const dictEnd = dictionary ? dictionary + dictSize : dictionary; + const BYTE* anchor = (const BYTE*) source; + const BYTE* const iend = ip + inputSize; + const BYTE* const mflimitPlusOne = iend - MFLIMIT + 1; + const BYTE* const matchlimit = iend - LASTLITERALS; + + /* the dictCtx currentOffset is indexed on the start of the dictionary, + * while a dictionary in the current context precedes the currentOffset */ + const BYTE* dictBase = (dictionary == NULL) ? NULL : + (dictDirective == usingDictCtx) ? + dictionary + dictSize - dictCtx->currentOffset : + dictionary + dictSize - startIndex; + + BYTE* op = (BYTE*) dest; + BYTE* const olimit = op + maxOutputSize; + + U32 offset = 0; + U32 forwardH; + + DEBUGLOG(5, "LZ4_compress_generic_validated: srcSize=%i, tableType=%u", inputSize, tableType); + assert(ip != NULL); + /* If init conditions are not met, we don't have to mark stream + * as having dirty context, since no action was taken yet */ + if (outputDirective == fillOutput && maxOutputSize < 1) { return 0; } /* Impossible to store anything */ + if ((tableType == byU16) && (inputSize>=LZ4_64Klimit)) { return 0; } /* Size too large (not within 64K limit) */ + if (tableType==byPtr) assert(dictDirective==noDict); /* only supported use case with byPtr */ + assert(acceleration >= 1); + + lowLimit = (const BYTE*)source - (dictDirective == withPrefix64k ? dictSize : 0); + + /* Update context state */ + if (dictDirective == usingDictCtx) { + /* Subsequent linked blocks can't use the dictionary. */ + /* Instead, they use the block we just compressed. */ + cctx->dictCtx = NULL; + cctx->dictSize = (U32)inputSize; + } else { + cctx->dictSize += (U32)inputSize; + } + cctx->currentOffset += (U32)inputSize; + cctx->tableType = (U32)tableType; + + if (inputSize<LZ4_minLength) goto _last_literals; /* Input too small, no compression (all literals) */ + + /* First Byte */ + LZ4_putPosition(ip, cctx->hashTable, tableType, base); + ip++; forwardH = LZ4_hashPosition(ip, tableType); + + /* Main Loop */ + for ( ; ; ) { + const BYTE* match; + BYTE* token; + const BYTE* filledIp; + + /* Find a match */ + if (tableType == byPtr) { + const BYTE* forwardIp = ip; + int step = 1; + int searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + assert(ip < mflimitPlusOne); + + match = LZ4_getPositionOnHash(h, cctx->hashTable, tableType, base); + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType, base); + + } while ( (match+LZ4_DISTANCE_MAX < ip) + || (LZ4_read32(match) != LZ4_read32(ip)) ); + + } else { /* byU32, byU16 */ + + const BYTE* forwardIp = ip; + int step = 1; + int searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + U32 const current = (U32)(forwardIp - base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex <= current); + assert(forwardIp - base < (ptrdiff_t)(2 GB - 1)); + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + assert(ip < mflimitPlusOne); + + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + assert(tableType == byU32); + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + matchIndex += dictDelta; /* make dictCtx index comparable with current context */ + lowLimit = dictionary; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; + } + } else if (dictDirective == usingExtDict) { + if (matchIndex < startIndex) { + DEBUGLOG(7, "extDict candidate: matchIndex=%5u < startIndex=%5u", matchIndex, startIndex); + assert(startIndex - matchIndex >= MINMATCH); + assert(dictBase); + match = dictBase + matchIndex; + lowLimit = dictionary; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; + } + } else { /* single continuous memory segment */ + match = base + matchIndex; + } + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + + DEBUGLOG(7, "candidate at pos=%u (offset=%u \n", matchIndex, current - matchIndex); + if ((dictIssue == dictSmall) && (matchIndex < prefixIdxLimit)) { continue; } /* match outside of valid area */ + assert(matchIndex < current); + if ( ((tableType != byU16) || (LZ4_DISTANCE_MAX < LZ4_DISTANCE_ABSOLUTE_MAX)) + && (matchIndex+LZ4_DISTANCE_MAX < current)) { + continue; + } /* too far */ + assert((current - matchIndex) <= LZ4_DISTANCE_MAX); /* match now expected within distance */ + + if (LZ4_read32(match) == LZ4_read32(ip)) { + if (maybe_extMem) offset = current - matchIndex; + break; /* match found */ + } + + } while(1); + } + + /* Catch up */ + filledIp = ip; + while (((ip>anchor) & (match > lowLimit)) && (unlikely(ip[-1]==match[-1]))) { ip--; match--; } + + /* Encode Literals */ + { unsigned const litLength = (unsigned)(ip - anchor); + token = op++; + if ((outputDirective == limitedOutput) && /* Check output buffer overflow */ + (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit)) ) { + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + if ((outputDirective == fillOutput) && + (unlikely(op + (litLength+240)/255 /* litlen */ + litLength /* literals */ + 2 /* offset */ + 1 /* token */ + MFLIMIT - MINMATCH /* min last literals so last match is <= end - MFLIMIT */ > olimit))) { + op--; + goto _last_literals; + } + if (litLength >= RUN_MASK) { + int len = (int)(litLength - RUN_MASK); + *token = (RUN_MASK<<ML_BITS); + for(; len >= 255 ; len-=255) *op++ = 255; + *op++ = (BYTE)len; + } + else *token = (BYTE)(litLength<<ML_BITS); + + /* Copy Literals */ + LZ4_wildCopy8(op, anchor, op+litLength); + op+=litLength; + DEBUGLOG(6, "seq.start:%i, literals=%u, match.start:%i", + (int)(anchor-(const BYTE*)source), litLength, (int)(ip-(const BYTE*)source)); + } + +_next_match: + /* at this stage, the following variables must be correctly set : + * - ip : at start of LZ operation + * - match : at start of previous pattern occurrence; can be within current prefix, or within extDict + * - offset : if maybe_ext_memSegment==1 (constant) + * - lowLimit : must be == dictionary to mean "match is within extDict"; must be == source otherwise + * - token and *token : position to write 4-bits for match length; higher 4-bits for literal length supposed already written + */ + + if ((outputDirective == fillOutput) && + (op + 2 /* offset */ + 1 /* token */ + MFLIMIT - MINMATCH /* min last literals so last match is <= end - MFLIMIT */ > olimit)) { + /* the match was too close to the end, rewind and go to last literals */ + op = token; + goto _last_literals; + } + + /* Encode Offset */ + if (maybe_extMem) { /* static test */ + DEBUGLOG(6, " with offset=%u (ext if > %i)", offset, (int)(ip - (const BYTE*)source)); + assert(offset <= LZ4_DISTANCE_MAX && offset > 0); + LZ4_writeLE16(op, (U16)offset); op+=2; + } else { + DEBUGLOG(6, " with offset=%u (same segment)", (U32)(ip - match)); + assert(ip-match <= LZ4_DISTANCE_MAX); + LZ4_writeLE16(op, (U16)(ip - match)); op+=2; + } + + /* Encode MatchLength */ + { unsigned matchCode; + + if ( (dictDirective==usingExtDict || dictDirective==usingDictCtx) + && (lowLimit==dictionary) /* match within extDict */ ) { + const BYTE* limit = ip + (dictEnd-match); + assert(dictEnd > match); + if (limit > matchlimit) limit = matchlimit; + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, limit); + ip += (size_t)matchCode + MINMATCH; + if (ip==limit) { + unsigned const more = LZ4_count(limit, (const BYTE*)source, matchlimit); + matchCode += more; + ip += more; + } + DEBUGLOG(6, " with matchLength=%u starting in extDict", matchCode+MINMATCH); + } else { + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, matchlimit); + ip += (size_t)matchCode + MINMATCH; + DEBUGLOG(6, " with matchLength=%u", matchCode+MINMATCH); + } + + if ((outputDirective) && /* Check output buffer overflow */ + (unlikely(op + (1 + LASTLITERALS) + (matchCode+240)/255 > olimit)) ) { + if (outputDirective == fillOutput) { + /* Match description too long : reduce it */ + U32 newMatchCode = 15 /* in token */ - 1 /* to avoid needing a zero byte */ + ((U32)(olimit - op) - 1 - LASTLITERALS) * 255; + ip -= matchCode - newMatchCode; + assert(newMatchCode < matchCode); + matchCode = newMatchCode; + if (unlikely(ip <= filledIp)) { + /* We have already filled up to filledIp so if ip ends up less than filledIp + * we have positions in the hash table beyond the current position. This is + * a problem if we reuse the hash table. So we have to remove these positions + * from the hash table. + */ + const BYTE* ptr; + DEBUGLOG(5, "Clearing %u positions", (U32)(filledIp - ip)); + for (ptr = ip; ptr <= filledIp; ++ptr) { + U32 const h = LZ4_hashPosition(ptr, tableType); + LZ4_clearHash(h, cctx->hashTable, tableType); + } + } + } else { + assert(outputDirective == limitedOutput); + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + } + if (matchCode >= ML_MASK) { + *token += ML_MASK; + matchCode -= ML_MASK; + LZ4_write32(op, 0xFFFFFFFF); + while (matchCode >= 4*255) { + op+=4; + LZ4_write32(op, 0xFFFFFFFF); + matchCode -= 4*255; + } + op += matchCode / 255; + *op++ = (BYTE)(matchCode % 255); + } else + *token += (BYTE)(matchCode); + } + /* Ensure we have enough space for the last literals. */ + assert(!(outputDirective == fillOutput && op + 1 + LASTLITERALS > olimit)); + + anchor = ip; + + /* Test end of chunk */ + if (ip >= mflimitPlusOne) break; + + /* Fill table */ + LZ4_putPosition(ip-2, cctx->hashTable, tableType, base); + + /* Test next position */ + if (tableType == byPtr) { + + match = LZ4_getPosition(ip, cctx->hashTable, tableType, base); + LZ4_putPosition(ip, cctx->hashTable, tableType, base); + if ( (match+LZ4_DISTANCE_MAX >= ip) + && (LZ4_read32(match) == LZ4_read32(ip)) ) + { token=op++; *token=0; goto _next_match; } + + } else { /* byU32, byU16 */ + + U32 const h = LZ4_hashPosition(ip, tableType); + U32 const current = (U32)(ip-base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex < current); + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + matchIndex += dictDelta; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; /* required for match length counter */ + } + } else if (dictDirective==usingExtDict) { + if (matchIndex < startIndex) { + assert(dictBase); + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; /* required for match length counter */ + } + } else { /* single memory segment */ + match = base + matchIndex; + } + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + assert(matchIndex < current); + if ( ((dictIssue==dictSmall) ? (matchIndex >= prefixIdxLimit) : 1) + && (((tableType==byU16) && (LZ4_DISTANCE_MAX == LZ4_DISTANCE_ABSOLUTE_MAX)) ? 1 : (matchIndex+LZ4_DISTANCE_MAX >= current)) + && (LZ4_read32(match) == LZ4_read32(ip)) ) { + token=op++; + *token=0; + if (maybe_extMem) offset = current - matchIndex; + DEBUGLOG(6, "seq.start:%i, literals=%u, match.start:%i", + (int)(anchor-(const BYTE*)source), 0, (int)(ip-(const BYTE*)source)); + goto _next_match; + } + } + + /* Prepare next loop */ + forwardH = LZ4_hashPosition(++ip, tableType); + + } + +_last_literals: + /* Encode Last Literals */ + { size_t lastRun = (size_t)(iend - anchor); + if ( (outputDirective) && /* Check output buffer overflow */ + (op + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > olimit)) { + if (outputDirective == fillOutput) { + /* adapt lastRun to fill 'dst' */ + assert(olimit >= op); + lastRun = (size_t)(olimit-op) - 1/*token*/; + lastRun -= (lastRun + 256 - RUN_MASK) / 256; /*additional length tokens*/ + } else { + assert(outputDirective == limitedOutput); + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + } + DEBUGLOG(6, "Final literal run : %i literals", (int)lastRun); + if (lastRun >= RUN_MASK) { + size_t accumulator = lastRun - RUN_MASK; + *op++ = RUN_MASK << ML_BITS; + for(; accumulator >= 255 ; accumulator-=255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRun<<ML_BITS); + } + LZ4_memcpy(op, anchor, lastRun); + ip = anchor + lastRun; + op += lastRun; + } + + if (outputDirective == fillOutput) { + *inputConsumed = (int) (((const char*)ip)-source); + } + result = (int)(((char*)op) - dest); + assert(result > 0); + DEBUGLOG(5, "LZ4_compress_generic: compressed %i bytes into %i bytes", inputSize, result); + return result; +} + +/** LZ4_compress_generic() : + * inlined, to ensure branches are decided at compilation time; + * takes care of src == (NULL, 0) + * and forward the rest to LZ4_compress_generic_validated */ +LZ4_FORCE_INLINE int LZ4_compress_generic( + LZ4_stream_t_internal* const cctx, + const char* const src, + char* const dst, + const int srcSize, + int *inputConsumed, /* only written when outputDirective == fillOutput */ + const int dstCapacity, + const limitedOutput_directive outputDirective, + const tableType_t tableType, + const dict_directive dictDirective, + const dictIssue_directive dictIssue, + const int acceleration) +{ + DEBUGLOG(5, "LZ4_compress_generic: srcSize=%i, dstCapacity=%i", + srcSize, dstCapacity); + + if ((U32)srcSize > (U32)LZ4_MAX_INPUT_SIZE) { return 0; } /* Unsupported srcSize, too large (or negative) */ + if (srcSize == 0) { /* src == NULL supported if srcSize == 0 */ + if (outputDirective != notLimited && dstCapacity <= 0) return 0; /* no output, can't write anything */ + DEBUGLOG(5, "Generating an empty block"); + assert(outputDirective == notLimited || dstCapacity >= 1); + assert(dst != NULL); + dst[0] = 0; + if (outputDirective == fillOutput) { + assert (inputConsumed != NULL); + *inputConsumed = 0; + } + return 1; + } + assert(src != NULL); + + return LZ4_compress_generic_validated(cctx, src, dst, srcSize, + inputConsumed, /* only written into if outputDirective == fillOutput */ + dstCapacity, outputDirective, + tableType, dictDirective, dictIssue, acceleration); +} + + +int LZ4_compress_fast_extState(void* state, const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ + LZ4_stream_t_internal* const ctx = & LZ4_initStream(state, sizeof(LZ4_stream_t)) -> internal_donotuse; + assert(ctx != NULL); + if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT; + if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX; + if (maxOutputSize >= LZ4_compressBound(inputSize)) { + if (inputSize < LZ4_64Klimit) { + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + if (inputSize < LZ4_64Klimit) { + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } +} + +/** + * LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. It is only safe + * to call if the state buffer is known to be correctly initialized already + * (see comment in lz4.h on LZ4_resetStream_fast() for a definition of + * "correctly initialized"). + */ +int LZ4_compress_fast_extState_fastReset(void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration) +{ + LZ4_stream_t_internal* ctx = &((LZ4_stream_t*)state)->internal_donotuse; + if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT; + if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX; + + if (dstCapacity >= LZ4_compressBound(srcSize)) { + if (srcSize < LZ4_64Klimit) { + const tableType_t tableType = byU16; + LZ4_prepareTable(ctx, srcSize, tableType); + if (ctx->currentOffset) { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, dictSmall, acceleration); + } else { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + LZ4_prepareTable(ctx, srcSize, tableType); + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + if (srcSize < LZ4_64Klimit) { + const tableType_t tableType = byU16; + LZ4_prepareTable(ctx, srcSize, tableType); + if (ctx->currentOffset) { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, dictSmall, acceleration); + } else { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + LZ4_prepareTable(ctx, srcSize, tableType); + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } +} + + +int LZ4_compress_fast(const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ + int result; +#if (LZ4_HEAPMODE) + LZ4_stream_t* ctxPtr = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + if (ctxPtr == NULL) return 0; +#else + LZ4_stream_t ctx; + LZ4_stream_t* const ctxPtr = &ctx; +#endif + result = LZ4_compress_fast_extState(ctxPtr, source, dest, inputSize, maxOutputSize, acceleration); + +#if (LZ4_HEAPMODE) + FREEMEM(ctxPtr); +#endif + return result; +} + + +int LZ4_compress_default(const char* src, char* dst, int srcSize, int maxOutputSize) +{ + return LZ4_compress_fast(src, dst, srcSize, maxOutputSize, 1); +} + + +/* Note!: This function leaves the stream in an unclean/broken state! + * It is not safe to subsequently use the same state with a _fastReset() or + * _continue() call without resetting it. */ +static int LZ4_compress_destSize_extState (LZ4_stream_t* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize) +{ + void* const s = LZ4_initStream(state, sizeof (*state)); + assert(s != NULL); (void)s; + + if (targetDstSize >= LZ4_compressBound(*srcSizePtr)) { /* compression success is guaranteed */ + return LZ4_compress_fast_extState(state, src, dst, *srcSizePtr, targetDstSize, 1); + } else { + if (*srcSizePtr < LZ4_64Klimit) { + return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, byU16, noDict, noDictIssue, 1); + } else { + tableType_t const addrMode = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, addrMode, noDict, noDictIssue, 1); + } } +} + + +int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize) +{ +#if (LZ4_HEAPMODE) + LZ4_stream_t* ctx = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + if (ctx == NULL) return 0; +#else + LZ4_stream_t ctxBody; + LZ4_stream_t* ctx = &ctxBody; +#endif + + int result = LZ4_compress_destSize_extState(ctx, src, dst, srcSizePtr, targetDstSize); + +#if (LZ4_HEAPMODE) + FREEMEM(ctx); +#endif + return result; +} + + + +/*-****************************** +* Streaming functions +********************************/ + +LZ4_stream_t* LZ4_createStream(void) +{ + LZ4_stream_t* const lz4s = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); + LZ4_STATIC_ASSERT(LZ4_STREAMSIZE >= sizeof(LZ4_stream_t_internal)); /* A compilation error here means LZ4_STREAMSIZE is not large enough */ + DEBUGLOG(4, "LZ4_createStream %p", lz4s); + if (lz4s == NULL) return NULL; + LZ4_initStream(lz4s, sizeof(*lz4s)); + return lz4s; +} + +static size_t LZ4_stream_t_alignment(void) +{ +#if LZ4_ALIGN_TEST + typedef struct { char c; LZ4_stream_t t; } t_a; + return sizeof(t_a) - sizeof(LZ4_stream_t); +#else + return 1; /* effectively disabled */ +#endif +} + +LZ4_stream_t* LZ4_initStream (void* buffer, size_t size) +{ + DEBUGLOG(5, "LZ4_initStream"); + if (buffer == NULL) { return NULL; } + if (size < sizeof(LZ4_stream_t)) { return NULL; } + if (!LZ4_isAligned(buffer, LZ4_stream_t_alignment())) return NULL; + MEM_INIT(buffer, 0, sizeof(LZ4_stream_t_internal)); + return (LZ4_stream_t*)buffer; +} + +/* resetStream is now deprecated, + * prefer initStream() which is more general */ +void LZ4_resetStream (LZ4_stream_t* LZ4_stream) +{ + DEBUGLOG(5, "LZ4_resetStream (ctx:%p)", LZ4_stream); + MEM_INIT(LZ4_stream, 0, sizeof(LZ4_stream_t_internal)); +} + +void LZ4_resetStream_fast(LZ4_stream_t* ctx) { + LZ4_prepareTable(&(ctx->internal_donotuse), 0, byU32); +} + +int LZ4_freeStream (LZ4_stream_t* LZ4_stream) +{ + if (!LZ4_stream) return 0; /* support free on NULL */ + DEBUGLOG(5, "LZ4_freeStream %p", LZ4_stream); + FREEMEM(LZ4_stream); + return (0); +} + + +#define HASH_UNIT sizeof(reg_t) +int LZ4_loadDict (LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize) +{ + LZ4_stream_t_internal* dict = &LZ4_dict->internal_donotuse; + const tableType_t tableType = byU32; + const BYTE* p = (const BYTE*)dictionary; + const BYTE* const dictEnd = p + dictSize; + const BYTE* base; + + DEBUGLOG(4, "LZ4_loadDict (%i bytes from %p into %p)", dictSize, dictionary, LZ4_dict); + + /* It's necessary to reset the context, + * and not just continue it with prepareTable() + * to avoid any risk of generating overflowing matchIndex + * when compressing using this dictionary */ + LZ4_resetStream(LZ4_dict); + + /* We always increment the offset by 64 KB, since, if the dict is longer, + * we truncate it to the last 64k, and if it's shorter, we still want to + * advance by a whole window length so we can provide the guarantee that + * there are only valid offsets in the window, which allows an optimization + * in LZ4_compress_fast_continue() where it uses noDictIssue even when the + * dictionary isn't a full 64k. */ + dict->currentOffset += 64 KB; + + if (dictSize < (int)HASH_UNIT) { + return 0; + } + + if ((dictEnd - p) > 64 KB) p = dictEnd - 64 KB; + base = dictEnd - dict->currentOffset; + dict->dictionary = p; + dict->dictSize = (U32)(dictEnd - p); + dict->tableType = (U32)tableType; + + while (p <= dictEnd-HASH_UNIT) { + LZ4_putPosition(p, dict->hashTable, tableType, base); + p+=3; + } + + return (int)dict->dictSize; +} + +void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const LZ4_stream_t* dictionaryStream) +{ + const LZ4_stream_t_internal* dictCtx = (dictionaryStream == NULL) ? NULL : + &(dictionaryStream->internal_donotuse); + + DEBUGLOG(4, "LZ4_attach_dictionary (%p, %p, size %u)", + workingStream, dictionaryStream, + dictCtx != NULL ? dictCtx->dictSize : 0); + + if (dictCtx != NULL) { + /* If the current offset is zero, we will never look in the + * external dictionary context, since there is no value a table + * entry can take that indicate a miss. In that case, we need + * to bump the offset to something non-zero. + */ + if (workingStream->internal_donotuse.currentOffset == 0) { + workingStream->internal_donotuse.currentOffset = 64 KB; + } + + /* Don't actually attach an empty dictionary. + */ + if (dictCtx->dictSize == 0) { + dictCtx = NULL; + } + } + workingStream->internal_donotuse.dictCtx = dictCtx; +} + + +static void LZ4_renormDictT(LZ4_stream_t_internal* LZ4_dict, int nextSize) +{ + assert(nextSize >= 0); + if (LZ4_dict->currentOffset + (unsigned)nextSize > 0x80000000) { /* potential ptrdiff_t overflow (32-bits mode) */ + /* rescale hash table */ + U32 const delta = LZ4_dict->currentOffset - 64 KB; + const BYTE* dictEnd = LZ4_dict->dictionary + LZ4_dict->dictSize; + int i; + DEBUGLOG(4, "LZ4_renormDictT"); + for (i=0; i<LZ4_HASH_SIZE_U32; i++) { + if (LZ4_dict->hashTable[i] < delta) LZ4_dict->hashTable[i]=0; + else LZ4_dict->hashTable[i] -= delta; + } + LZ4_dict->currentOffset = 64 KB; + if (LZ4_dict->dictSize > 64 KB) LZ4_dict->dictSize = 64 KB; + LZ4_dict->dictionary = dictEnd - LZ4_dict->dictSize; + } +} + + +int LZ4_compress_fast_continue (LZ4_stream_t* LZ4_stream, + const char* source, char* dest, + int inputSize, int maxOutputSize, + int acceleration) +{ + const tableType_t tableType = byU32; + LZ4_stream_t_internal* const streamPtr = &LZ4_stream->internal_donotuse; + const char* dictEnd = streamPtr->dictSize ? (const char*)streamPtr->dictionary + streamPtr->dictSize : NULL; + + DEBUGLOG(5, "LZ4_compress_fast_continue (inputSize=%i, dictSize=%u)", inputSize, streamPtr->dictSize); + + LZ4_renormDictT(streamPtr, inputSize); /* fix index overflow */ + if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT; + if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX; + + /* invalidate tiny dictionaries */ + if ( (streamPtr->dictSize < 4) /* tiny dictionary : not enough for a hash */ + && (dictEnd != source) /* prefix mode */ + && (inputSize > 0) /* tolerance : don't lose history, in case next invocation would use prefix mode */ + && (streamPtr->dictCtx == NULL) /* usingDictCtx */ + ) { + DEBUGLOG(5, "LZ4_compress_fast_continue: dictSize(%u) at addr:%p is too small", streamPtr->dictSize, streamPtr->dictionary); + /* remove dictionary existence from history, to employ faster prefix mode */ + streamPtr->dictSize = 0; + streamPtr->dictionary = (const BYTE*)source; + dictEnd = source; + } + + /* Check overlapping input/dictionary space */ + { const char* const sourceEnd = source + inputSize; + if ((sourceEnd > (const char*)streamPtr->dictionary) && (sourceEnd < dictEnd)) { + streamPtr->dictSize = (U32)(dictEnd - sourceEnd); + if (streamPtr->dictSize > 64 KB) streamPtr->dictSize = 64 KB; + if (streamPtr->dictSize < 4) streamPtr->dictSize = 0; + streamPtr->dictionary = (const BYTE*)dictEnd - streamPtr->dictSize; + } + } + + /* prefix mode : source data follows dictionary */ + if (dictEnd == source) { + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) + return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, dictSmall, acceleration); + else + return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, noDictIssue, acceleration); + } + + /* external dictionary mode */ + { int result; + if (streamPtr->dictCtx) { + /* We depend here on the fact that dictCtx'es (produced by + * LZ4_loadDict) guarantee that their tables contain no references + * to offsets between dictCtx->currentOffset - 64 KB and + * dictCtx->currentOffset - dictCtx->dictSize. This makes it safe + * to use noDictIssue even when the dict isn't a full 64 KB. + */ + if (inputSize > 4 KB) { + /* For compressing large blobs, it is faster to pay the setup + * cost to copy the dictionary's tables into the active context, + * so that the compression loop is only looking into one table. + */ + LZ4_memcpy(streamPtr, streamPtr->dictCtx, sizeof(*streamPtr)); + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingDictCtx, noDictIssue, acceleration); + } + } else { /* small data <= 4 KB */ + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, dictSmall, acceleration); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); + } + } + streamPtr->dictionary = (const BYTE*)source; + streamPtr->dictSize = (U32)inputSize; + return result; + } +} + + +/* Hidden debug function, to force-test external dictionary mode */ +int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize) +{ + LZ4_stream_t_internal* streamPtr = &LZ4_dict->internal_donotuse; + int result; + + LZ4_renormDictT(streamPtr, srcSize); + + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { + result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, dictSmall, 1); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, noDictIssue, 1); + } + + streamPtr->dictionary = (const BYTE*)source; + streamPtr->dictSize = (U32)srcSize; + + return result; +} + + +/*! LZ4_saveDict() : + * If previously compressed data block is not guaranteed to remain available at its memory location, + * save it into a safer place (char* safeBuffer). + * Note : no need to call LZ4_loadDict() afterwards, dictionary is immediately usable, + * one can therefore call LZ4_compress_fast_continue() right after. + * @return : saved dictionary size in bytes (necessarily <= dictSize), or 0 if error. + */ +int LZ4_saveDict (LZ4_stream_t* LZ4_dict, char* safeBuffer, int dictSize) +{ + LZ4_stream_t_internal* const dict = &LZ4_dict->internal_donotuse; + + DEBUGLOG(5, "LZ4_saveDict : dictSize=%i, safeBuffer=%p", dictSize, safeBuffer); + + if ((U32)dictSize > 64 KB) { dictSize = 64 KB; } /* useless to define a dictionary > 64 KB */ + if ((U32)dictSize > dict->dictSize) { dictSize = (int)dict->dictSize; } + + if (safeBuffer == NULL) assert(dictSize == 0); + if (dictSize > 0) { + const BYTE* const previousDictEnd = dict->dictionary + dict->dictSize; + assert(dict->dictionary); + memmove(safeBuffer, previousDictEnd - dictSize, dictSize); + } + + dict->dictionary = (const BYTE*)safeBuffer; + dict->dictSize = (U32)dictSize; + + return dictSize; +} + + + +/*-******************************* + * Decompression functions + ********************************/ + +typedef enum { endOnOutputSize = 0, endOnInputSize = 1 } endCondition_directive; +typedef enum { decode_full_block = 0, partial_decode = 1 } earlyEnd_directive; + +#undef MIN +#define MIN(a,b) ( (a) < (b) ? (a) : (b) ) + +/* Read the variable-length literal or match length. + * + * ip - pointer to use as input. + * lencheck - end ip. Return an error if ip advances >= lencheck. + * loop_check - check ip >= lencheck in body of loop. Returns loop_error if so. + * initial_check - check ip >= lencheck before start of loop. Returns initial_error if so. + * error (output) - error code. Should be set to 0 before call. + */ +typedef enum { loop_error = -2, initial_error = -1, ok = 0 } variable_length_error; +LZ4_FORCE_INLINE unsigned +read_variable_length(const BYTE**ip, const BYTE* lencheck, + int loop_check, int initial_check, + variable_length_error* error) +{ + U32 length = 0; + U32 s; + if (initial_check && unlikely((*ip) >= lencheck)) { /* overflow detection */ + *error = initial_error; + return length; + } + do { + s = **ip; + (*ip)++; + length += s; + if (loop_check && unlikely((*ip) >= lencheck)) { /* overflow detection */ + *error = loop_error; + return length; + } + } while (s==255); + + return length; +} + +/*! LZ4_decompress_generic() : + * This generic decompression function covers all use cases. + * It shall be instantiated several times, using different sets of directives. + * Note that it is important for performance that this function really get inlined, + * in order to remove useless branches during compilation optimization. + */ +LZ4_FORCE_INLINE int +LZ4_decompress_generic( + const char* const src, + char* const dst, + int srcSize, + int outputSize, /* If endOnInput==endOnInputSize, this value is `dstCapacity` */ + + endCondition_directive endOnInput, /* endOnOutputSize, endOnInputSize */ + earlyEnd_directive partialDecoding, /* full, partial */ + dict_directive dict, /* noDict, withPrefix64k, usingExtDict */ + const BYTE* const lowPrefix, /* always <= dst, == dst when no prefix */ + const BYTE* const dictStart, /* only if dict==usingExtDict */ + const size_t dictSize /* note : = 0 if noDict */ + ) +{ + if ((src == NULL) || (outputSize < 0)) { return -1; } + + { const BYTE* ip = (const BYTE*) src; + const BYTE* const iend = ip + srcSize; + + BYTE* op = (BYTE*) dst; + BYTE* const oend = op + outputSize; + BYTE* cpy; + + const BYTE* const dictEnd = (dictStart == NULL) ? NULL : dictStart + dictSize; + + const int safeDecode = (endOnInput==endOnInputSize); + const int checkOffset = ((safeDecode) && (dictSize < (int)(64 KB))); + + + /* Set up the "end" pointers for the shortcut. */ + const BYTE* const shortiend = iend - (endOnInput ? 14 : 8) /*maxLL*/ - 2 /*offset*/; + const BYTE* const shortoend = oend - (endOnInput ? 14 : 8) /*maxLL*/ - 18 /*maxML*/; + + const BYTE* match; + size_t offset; + unsigned token; + size_t length; + + + DEBUGLOG(5, "LZ4_decompress_generic (srcSize:%i, dstSize:%i)", srcSize, outputSize); + + /* Special cases */ + assert(lowPrefix <= op); + if ((endOnInput) && (unlikely(outputSize==0))) { + /* Empty output buffer */ + if (partialDecoding) return 0; + return ((srcSize==1) && (*ip==0)) ? 0 : -1; + } + if ((!endOnInput) && (unlikely(outputSize==0))) { return (*ip==0 ? 1 : -1); } + if ((endOnInput) && unlikely(srcSize==0)) { return -1; } + + /* Currently the fast loop shows a regression on qualcomm arm chips. */ +#if LZ4_FAST_DEC_LOOP + if ((oend - op) < FASTLOOP_SAFE_DISTANCE) { + DEBUGLOG(6, "skip fast decode loop"); + goto safe_decode; + } + + /* Fast loop : decode sequences as long as output < iend-FASTLOOP_SAFE_DISTANCE */ + while (1) { + /* Main fastloop assertion: We can always wildcopy FASTLOOP_SAFE_DISTANCE */ + assert(oend - op >= FASTLOOP_SAFE_DISTANCE); + if (endOnInput) { assert(ip < iend); } + token = *ip++; + length = token >> ML_BITS; /* literal length */ + + assert(!endOnInput || ip <= iend); /* ip < iend before the increment */ + + /* decode literal length */ + if (length == RUN_MASK) { + variable_length_error error = ok; + length += read_variable_length(&ip, iend-RUN_MASK, (int)endOnInput, (int)endOnInput, &error); + if (error == initial_error) { goto _output_error; } + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */ + if ((safeDecode) && unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */ + + /* copy literals */ + cpy = op+length; + LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); + if (endOnInput) { /* LZ4_decompress_safe() */ + if ((cpy>oend-32) || (ip+length>iend-32)) { goto safe_literal_copy; } + LZ4_wildCopy32(op, ip, cpy); + } else { /* LZ4_decompress_fast() */ + if (cpy>oend-8) { goto safe_literal_copy; } + LZ4_wildCopy8(op, ip, cpy); /* LZ4_decompress_fast() cannot copy more than 8 bytes at a time : + * it doesn't know input length, and only relies on end-of-block properties */ + } + ip += length; op = cpy; + } else { + cpy = op+length; + if (endOnInput) { /* LZ4_decompress_safe() */ + DEBUGLOG(7, "copy %u bytes in a 16-bytes stripe", (unsigned)length); + /* We don't need to check oend, since we check it once for each loop below */ + if (ip > iend-(16 + 1/*max lit + offset + nextToken*/)) { goto safe_literal_copy; } + /* Literals can only be 14, but hope compilers optimize if we copy by a register size */ + LZ4_memcpy(op, ip, 16); + } else { /* LZ4_decompress_fast() */ + /* LZ4_decompress_fast() cannot copy more than 8 bytes at a time : + * it doesn't know input length, and relies on end-of-block properties */ + LZ4_memcpy(op, ip, 8); + if (length > 8) { LZ4_memcpy(op+8, ip+8, 8); } + } + ip += length; op = cpy; + } + + /* get offset */ + offset = LZ4_readLE16(ip); ip+=2; + match = op - offset; + assert(match <= op); + + /* get matchlength */ + length = token & ML_MASK; + + if (length == ML_MASK) { + variable_length_error error = ok; + if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) { goto _output_error; } /* Error : offset outside buffers */ + length += read_variable_length(&ip, iend - LASTLITERALS + 1, (int)endOnInput, 0, &error); + if (error != ok) { goto _output_error; } + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) { goto _output_error; } /* overflow detection */ + length += MINMATCH; + if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { + goto safe_match_copy; + } + } else { + length += MINMATCH; + if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { + goto safe_match_copy; + } + + /* Fastpath check: Avoids a branch in LZ4_wildCopy32 if true */ + if ((dict == withPrefix64k) || (match >= lowPrefix)) { + if (offset >= 8) { + assert(match >= lowPrefix); + assert(match <= op); + assert(op + 18 <= oend); + + LZ4_memcpy(op, match, 8); + LZ4_memcpy(op+8, match+8, 8); + LZ4_memcpy(op+16, match+16, 2); + op += length; + continue; + } } } + + if (checkOffset && (unlikely(match + dictSize < lowPrefix))) { goto _output_error; } /* Error : offset outside buffers */ + /* match starting within external dictionary */ + if ((dict==usingExtDict) && (match < lowPrefix)) { + if (unlikely(op+length > oend-LASTLITERALS)) { + if (partialDecoding) { + DEBUGLOG(7, "partialDecoding: dictionary match, close to dstEnd"); + length = MIN(length, (size_t)(oend-op)); + } else { + goto _output_error; /* end-of-block condition violated */ + } } + + if (length <= (size_t)(lowPrefix-match)) { + /* match fits entirely within external dictionary : just copy */ + memmove(op, dictEnd - (lowPrefix-match), length); + op += length; + } else { + /* match stretches into both external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix - match); + size_t const restSize = length - copySize; + LZ4_memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */ + BYTE* const endOfMatch = op + restSize; + const BYTE* copyFrom = lowPrefix; + while (op < endOfMatch) { *op++ = *copyFrom++; } + } else { + LZ4_memcpy(op, lowPrefix, restSize); + op += restSize; + } } + continue; + } + + /* copy match within block */ + cpy = op + length; + + assert((op <= oend) && (oend-op >= 32)); + if (unlikely(offset<16)) { + LZ4_memcpy_using_offset(op, match, cpy, offset); + } else { + LZ4_wildCopy32(op, match, cpy); + } + + op = cpy; /* wildcopy correction */ + } + safe_decode: +#endif + + /* Main Loop : decode remaining sequences where output < FASTLOOP_SAFE_DISTANCE */ + while (1) { + token = *ip++; + length = token >> ML_BITS; /* literal length */ + + assert(!endOnInput || ip <= iend); /* ip < iend before the increment */ + + /* A two-stage shortcut for the most common case: + * 1) If the literal length is 0..14, and there is enough space, + * enter the shortcut and copy 16 bytes on behalf of the literals + * (in the fast mode, only 8 bytes can be safely copied this way). + * 2) Further if the match length is 4..18, copy 18 bytes in a similar + * manner; but we ensure that there's enough space in the output for + * those 18 bytes earlier, upon entering the shortcut (in other words, + * there is a combined check for both stages). + */ + if ( (endOnInput ? length != RUN_MASK : length <= 8) + /* strictly "less than" on input, to re-enter the loop with at least one byte */ + && likely((endOnInput ? ip < shortiend : 1) & (op <= shortoend)) ) { + /* Copy the literals */ + LZ4_memcpy(op, ip, endOnInput ? 16 : 8); + op += length; ip += length; + + /* The second stage: prepare for match copying, decode full info. + * If it doesn't work out, the info won't be wasted. */ + length = token & ML_MASK; /* match length */ + offset = LZ4_readLE16(ip); ip += 2; + match = op - offset; + assert(match <= op); /* check overflow */ + + /* Do not deal with overlapping matches. */ + if ( (length != ML_MASK) + && (offset >= 8) + && (dict==withPrefix64k || match >= lowPrefix) ) { + /* Copy the match. */ + LZ4_memcpy(op + 0, match + 0, 8); + LZ4_memcpy(op + 8, match + 8, 8); + LZ4_memcpy(op +16, match +16, 2); + op += length + MINMATCH; + /* Both stages worked, load the next token. */ + continue; + } + + /* The second stage didn't work out, but the info is ready. + * Propel it right to the point of match copying. */ + goto _copy_match; + } + + /* decode literal length */ + if (length == RUN_MASK) { + variable_length_error error = ok; + length += read_variable_length(&ip, iend-RUN_MASK, (int)endOnInput, (int)endOnInput, &error); + if (error == initial_error) { goto _output_error; } + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */ + if ((safeDecode) && unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */ + } + + /* copy literals */ + cpy = op+length; +#if LZ4_FAST_DEC_LOOP + safe_literal_copy: +#endif + LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); + if ( ((endOnInput) && ((cpy>oend-MFLIMIT) || (ip+length>iend-(2+1+LASTLITERALS))) ) + || ((!endOnInput) && (cpy>oend-WILDCOPYLENGTH)) ) + { + /* We've either hit the input parsing restriction or the output parsing restriction. + * In the normal scenario, decoding a full block, it must be the last sequence, + * otherwise it's an error (invalid input or dimensions). + * In partialDecoding scenario, it's necessary to ensure there is no buffer overflow. + */ + if (partialDecoding) { + /* Since we are partial decoding we may be in this block because of the output parsing + * restriction, which is not valid since the output buffer is allowed to be undersized. + */ + assert(endOnInput); + DEBUGLOG(7, "partialDecoding: copying literals, close to input or output end") + DEBUGLOG(7, "partialDecoding: literal length = %u", (unsigned)length); + DEBUGLOG(7, "partialDecoding: remaining space in dstBuffer : %i", (int)(oend - op)); + DEBUGLOG(7, "partialDecoding: remaining space in srcBuffer : %i", (int)(iend - ip)); + /* Finishing in the middle of a literals segment, + * due to lack of input. + */ + if (ip+length > iend) { + length = (size_t)(iend-ip); + cpy = op + length; + } + /* Finishing in the middle of a literals segment, + * due to lack of output space. + */ + if (cpy > oend) { + cpy = oend; + assert(op<=oend); + length = (size_t)(oend-op); + } + } else { + /* We must be on the last sequence because of the parsing limitations so check + * that we exactly regenerate the original size (must be exact when !endOnInput). + */ + if ((!endOnInput) && (cpy != oend)) { goto _output_error; } + /* We must be on the last sequence (or invalid) because of the parsing limitations + * so check that we exactly consume the input and don't overrun the output buffer. + */ + if ((endOnInput) && ((ip+length != iend) || (cpy > oend))) { + DEBUGLOG(6, "should have been last run of literals") + DEBUGLOG(6, "ip(%p) + length(%i) = %p != iend (%p)", ip, (int)length, ip+length, iend); + DEBUGLOG(6, "or cpy(%p) > oend(%p)", cpy, oend); + goto _output_error; + } + } + memmove(op, ip, length); /* supports overlapping memory regions; only matters for in-place decompression scenarios */ + ip += length; + op += length; + /* Necessarily EOF when !partialDecoding. + * When partialDecoding, it is EOF if we've either + * filled the output buffer or + * can't proceed with reading an offset for following match. + */ + if (!partialDecoding || (cpy == oend) || (ip >= (iend-2))) { + break; + } + } else { + LZ4_wildCopy8(op, ip, cpy); /* may overwrite up to WILDCOPYLENGTH beyond cpy */ + ip += length; op = cpy; + } + + /* get offset */ + offset = LZ4_readLE16(ip); ip+=2; + match = op - offset; + + /* get matchlength */ + length = token & ML_MASK; + + _copy_match: + if (length == ML_MASK) { + variable_length_error error = ok; + length += read_variable_length(&ip, iend - LASTLITERALS + 1, (int)endOnInput, 0, &error); + if (error != ok) goto _output_error; + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error; /* overflow detection */ + } + length += MINMATCH; + +#if LZ4_FAST_DEC_LOOP + safe_match_copy: +#endif + if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) goto _output_error; /* Error : offset outside buffers */ + /* match starting within external dictionary */ + if ((dict==usingExtDict) && (match < lowPrefix)) { + if (unlikely(op+length > oend-LASTLITERALS)) { + if (partialDecoding) length = MIN(length, (size_t)(oend-op)); + else goto _output_error; /* doesn't respect parsing restriction */ + } + + if (length <= (size_t)(lowPrefix-match)) { + /* match fits entirely within external dictionary : just copy */ + memmove(op, dictEnd - (lowPrefix-match), length); + op += length; + } else { + /* match stretches into both external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix - match); + size_t const restSize = length - copySize; + LZ4_memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */ + BYTE* const endOfMatch = op + restSize; + const BYTE* copyFrom = lowPrefix; + while (op < endOfMatch) *op++ = *copyFrom++; + } else { + LZ4_memcpy(op, lowPrefix, restSize); + op += restSize; + } } + continue; + } + assert(match >= lowPrefix); + + /* copy match within block */ + cpy = op + length; + + /* partialDecoding : may end anywhere within the block */ + assert(op<=oend); + if (partialDecoding && (cpy > oend-MATCH_SAFEGUARD_DISTANCE)) { + size_t const mlen = MIN(length, (size_t)(oend-op)); + const BYTE* const matchEnd = match + mlen; + BYTE* const copyEnd = op + mlen; + if (matchEnd > op) { /* overlap copy */ + while (op < copyEnd) { *op++ = *match++; } + } else { + LZ4_memcpy(op, match, mlen); + } + op = copyEnd; + if (op == oend) { break; } + continue; + } + + if (unlikely(offset<8)) { + LZ4_write32(op, 0); /* silence msan warning when offset==0 */ + op[0] = match[0]; + op[1] = match[1]; + op[2] = match[2]; + op[3] = match[3]; + match += inc32table[offset]; + LZ4_memcpy(op+4, match, 4); + match -= dec64table[offset]; + } else { + LZ4_memcpy(op, match, 8); + match += 8; + } + op += 8; + + if (unlikely(cpy > oend-MATCH_SAFEGUARD_DISTANCE)) { + BYTE* const oCopyLimit = oend - (WILDCOPYLENGTH-1); + if (cpy > oend-LASTLITERALS) { goto _output_error; } /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ + if (op < oCopyLimit) { + LZ4_wildCopy8(op, match, oCopyLimit); + match += oCopyLimit - op; + op = oCopyLimit; + } + while (op < cpy) { *op++ = *match++; } + } else { + LZ4_memcpy(op, match, 8); + if (length > 16) { LZ4_wildCopy8(op+8, match+8, cpy); } + } + op = cpy; /* wildcopy correction */ + } + + /* end of decoding */ + if (endOnInput) { + DEBUGLOG(5, "decoded %i bytes", (int) (((char*)op)-dst)); + return (int) (((char*)op)-dst); /* Nb of output bytes decoded */ + } else { + return (int) (((const char*)ip)-src); /* Nb of input bytes read */ + } + + /* Overflow error detected */ + _output_error: + return (int) (-(((const char*)ip)-src))-1; + } +} + + +/*===== Instantiate the API decoding functions. =====*/ + +LZ4_FORCE_O2 +int LZ4_decompress_safe(const char* source, char* dest, int compressedSize, int maxDecompressedSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, + endOnInputSize, decode_full_block, noDict, + (BYTE*)dest, NULL, 0); +} + +LZ4_FORCE_O2 +int LZ4_decompress_safe_partial(const char* src, char* dst, int compressedSize, int targetOutputSize, int dstCapacity) +{ + dstCapacity = MIN(targetOutputSize, dstCapacity); + return LZ4_decompress_generic(src, dst, compressedSize, dstCapacity, + endOnInputSize, partial_decode, + noDict, (BYTE*)dst, NULL, 0); +} + +LZ4_FORCE_O2 +int LZ4_decompress_fast(const char* source, char* dest, int originalSize) +{ + return LZ4_decompress_generic(source, dest, 0, originalSize, + endOnOutputSize, decode_full_block, withPrefix64k, + (BYTE*)dest - 64 KB, NULL, 0); +} + +/*===== Instantiate a few more decoding cases, used more than once. =====*/ + +LZ4_FORCE_O2 /* Exported, an obsolete API function. */ +int LZ4_decompress_safe_withPrefix64k(const char* source, char* dest, int compressedSize, int maxOutputSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, decode_full_block, withPrefix64k, + (BYTE*)dest - 64 KB, NULL, 0); +} + +/* Another obsolete API function, paired with the previous one. */ +int LZ4_decompress_fast_withPrefix64k(const char* source, char* dest, int originalSize) +{ + /* LZ4_decompress_fast doesn't validate match offsets, + * and thus serves well with any prefixed dictionary. */ + return LZ4_decompress_fast(source, dest, originalSize); +} + +LZ4_FORCE_O2 +static int LZ4_decompress_safe_withSmallPrefix(const char* source, char* dest, int compressedSize, int maxOutputSize, + size_t prefixSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, decode_full_block, noDict, + (BYTE*)dest-prefixSize, NULL, 0); +} + +LZ4_FORCE_O2 +int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, + int compressedSize, int maxOutputSize, + const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, decode_full_block, usingExtDict, + (BYTE*)dest, (const BYTE*)dictStart, dictSize); +} + +LZ4_FORCE_O2 +static int LZ4_decompress_fast_extDict(const char* source, char* dest, int originalSize, + const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, 0, originalSize, + endOnOutputSize, decode_full_block, usingExtDict, + (BYTE*)dest, (const BYTE*)dictStart, dictSize); +} + +/* The "double dictionary" mode, for use with e.g. ring buffers: the first part + * of the dictionary is passed as prefix, and the second via dictStart + dictSize. + * These routines are used only once, in LZ4_decompress_*_continue(). + */ +LZ4_FORCE_INLINE +int LZ4_decompress_safe_doubleDict(const char* source, char* dest, int compressedSize, int maxOutputSize, + size_t prefixSize, const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, decode_full_block, usingExtDict, + (BYTE*)dest-prefixSize, (const BYTE*)dictStart, dictSize); +} + +LZ4_FORCE_INLINE +int LZ4_decompress_fast_doubleDict(const char* source, char* dest, int originalSize, + size_t prefixSize, const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, 0, originalSize, + endOnOutputSize, decode_full_block, usingExtDict, + (BYTE*)dest-prefixSize, (const BYTE*)dictStart, dictSize); +} + +/*===== streaming decompression functions =====*/ + +LZ4_streamDecode_t* LZ4_createStreamDecode(void) +{ + LZ4_streamDecode_t* lz4s = (LZ4_streamDecode_t*) ALLOC_AND_ZERO(sizeof(LZ4_streamDecode_t)); + LZ4_STATIC_ASSERT(LZ4_STREAMDECODESIZE >= sizeof(LZ4_streamDecode_t_internal)); /* A compilation error here means LZ4_STREAMDECODESIZE is not large enough */ + return lz4s; +} + +int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream) +{ + if (LZ4_stream == NULL) { return 0; } /* support free on NULL */ + FREEMEM(LZ4_stream); + return 0; +} + +/*! LZ4_setStreamDecode() : + * Use this function to instruct where to find the dictionary. + * This function is not necessary if previous data is still available where it was decoded. + * Loading a size of 0 is allowed (same effect as no dictionary). + * @return : 1 if OK, 0 if error + */ +int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + lz4sd->prefixSize = (size_t)dictSize; + if (dictSize) { + assert(dictionary != NULL); + lz4sd->prefixEnd = (const BYTE*) dictionary + dictSize; + } else { + lz4sd->prefixEnd = (const BYTE*) dictionary; + } + lz4sd->externalDict = NULL; + lz4sd->extDictSize = 0; + return 1; +} + +/*! LZ4_decoderRingBufferSize() : + * when setting a ring buffer for streaming decompression (optional scenario), + * provides the minimum size of this ring buffer + * to be compatible with any source respecting maxBlockSize condition. + * Note : in a ring buffer scenario, + * blocks are presumed decompressed next to each other. + * When not enough space remains for next block (remainingSize < maxBlockSize), + * decoding resumes from beginning of ring buffer. + * @return : minimum ring buffer size, + * or 0 if there is an error (invalid maxBlockSize). + */ +int LZ4_decoderRingBufferSize(int maxBlockSize) +{ + if (maxBlockSize < 0) return 0; + if (maxBlockSize > LZ4_MAX_INPUT_SIZE) return 0; + if (maxBlockSize < 16) maxBlockSize = 16; + return LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize); +} + +/* +*_continue() : + These decoding functions allow decompression of multiple blocks in "streaming" mode. + Previously decoded blocks must still be available at the memory position where they were decoded. + If it's not possible, save the relevant part of decoded data into a safe buffer, + and indicate where it stands using LZ4_setStreamDecode() +*/ +LZ4_FORCE_O2 +int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int compressedSize, int maxOutputSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + int result; + + if (lz4sd->prefixSize == 0) { + /* The first call, no dictionary yet. */ + assert(lz4sd->extDictSize == 0); + result = LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)result; + lz4sd->prefixEnd = (BYTE*)dest + result; + } else if (lz4sd->prefixEnd == (BYTE*)dest) { + /* They're rolling the current segment. */ + if (lz4sd->prefixSize >= 64 KB - 1) + result = LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize); + else if (lz4sd->extDictSize == 0) + result = LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize, + lz4sd->prefixSize); + else + result = LZ4_decompress_safe_doubleDict(source, dest, compressedSize, maxOutputSize, + lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize += (size_t)result; + lz4sd->prefixEnd += result; + } else { + /* The buffer wraps around, or they're switching to another buffer. */ + lz4sd->extDictSize = lz4sd->prefixSize; + lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; + result = LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize, + lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)result; + lz4sd->prefixEnd = (BYTE*)dest + result; + } + + return result; +} + +LZ4_FORCE_O2 +int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int originalSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + int result; + assert(originalSize >= 0); + + if (lz4sd->prefixSize == 0) { + assert(lz4sd->extDictSize == 0); + result = LZ4_decompress_fast(source, dest, originalSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)originalSize; + lz4sd->prefixEnd = (BYTE*)dest + originalSize; + } else if (lz4sd->prefixEnd == (BYTE*)dest) { + if (lz4sd->prefixSize >= 64 KB - 1 || lz4sd->extDictSize == 0) + result = LZ4_decompress_fast(source, dest, originalSize); + else + result = LZ4_decompress_fast_doubleDict(source, dest, originalSize, + lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize += (size_t)originalSize; + lz4sd->prefixEnd += originalSize; + } else { + lz4sd->extDictSize = lz4sd->prefixSize; + lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; + result = LZ4_decompress_fast_extDict(source, dest, originalSize, + lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)originalSize; + lz4sd->prefixEnd = (BYTE*)dest + originalSize; + } + + return result; +} + + +/* +Advanced decoding functions : +*_usingDict() : + These decoding functions work the same as "_continue" ones, + the dictionary must be explicitly provided within parameters +*/ + +int LZ4_decompress_safe_usingDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize) +{ + if (dictSize==0) + return LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize); + if (dictStart+dictSize == dest) { + if (dictSize >= 64 KB - 1) { + return LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize); + } + assert(dictSize >= 0); + return LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize, (size_t)dictSize); + } + assert(dictSize >= 0); + return LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize, dictStart, (size_t)dictSize); +} + +int LZ4_decompress_fast_usingDict(const char* source, char* dest, int originalSize, const char* dictStart, int dictSize) +{ + if (dictSize==0 || dictStart+dictSize == dest) + return LZ4_decompress_fast(source, dest, originalSize); + assert(dictSize >= 0); + return LZ4_decompress_fast_extDict(source, dest, originalSize, dictStart, (size_t)dictSize); +} + + +/*=************************************************* +* Obsolete Functions +***************************************************/ +/* obsolete compression functions */ +int LZ4_compress_limitedOutput(const char* source, char* dest, int inputSize, int maxOutputSize) +{ + return LZ4_compress_default(source, dest, inputSize, maxOutputSize); +} +int LZ4_compress(const char* src, char* dest, int srcSize) +{ + return LZ4_compress_default(src, dest, srcSize, LZ4_compressBound(srcSize)); +} +int LZ4_compress_limitedOutput_withState (void* state, const char* src, char* dst, int srcSize, int dstSize) +{ + return LZ4_compress_fast_extState(state, src, dst, srcSize, dstSize, 1); +} +int LZ4_compress_withState (void* state, const char* src, char* dst, int srcSize) +{ + return LZ4_compress_fast_extState(state, src, dst, srcSize, LZ4_compressBound(srcSize), 1); +} +int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_stream, const char* src, char* dst, int srcSize, int dstCapacity) +{ + return LZ4_compress_fast_continue(LZ4_stream, src, dst, srcSize, dstCapacity, 1); +} +int LZ4_compress_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize) +{ + return LZ4_compress_fast_continue(LZ4_stream, source, dest, inputSize, LZ4_compressBound(inputSize), 1); +} + +/* +These decompression functions are deprecated and should no longer be used. +They are only provided here for compatibility with older user programs. +- LZ4_uncompress is totally equivalent to LZ4_decompress_fast +- LZ4_uncompress_unknownOutputSize is totally equivalent to LZ4_decompress_safe +*/ +int LZ4_uncompress (const char* source, char* dest, int outputSize) +{ + return LZ4_decompress_fast(source, dest, outputSize); +} +int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize) +{ + return LZ4_decompress_safe(source, dest, isize, maxOutputSize); +} + +/* Obsolete Streaming functions */ + +int LZ4_sizeofStreamState(void) { return LZ4_STREAMSIZE; } + +int LZ4_resetStreamState(void* state, char* inputBuffer) +{ + (void)inputBuffer; + LZ4_resetStream((LZ4_stream_t*)state); + return 0; +} + +void* LZ4_create (char* inputBuffer) +{ + (void)inputBuffer; + return LZ4_createStream(); +} + +char* LZ4_slideInputBuffer (void* state) +{ + /* avoid const char * -> char * conversion warning */ + return (char *)(uptrval)((LZ4_stream_t*)state)->internal_donotuse.dictionary; +} + +#endif /* LZ4_COMMONDEFS_ONLY */ diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/lz4.h b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/lz4.h new file mode 100644 index 00000000..7c401f65 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/lz4.h @@ -0,0 +1,785 @@ +/* + * LZ4 - Fast LZ compression algorithm + * Header File + * Copyright (C) 2011-2020, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/lz4/lz4 +*/ +#if defined (__cplusplus) +extern "C" { +#endif + +#ifndef LZ4_H_2983827168210 +#define LZ4_H_2983827168210 + +/* --- Dependency --- */ +#include <stddef.h> /* size_t */ + + +/** + Introduction + + LZ4 is lossless compression algorithm, providing compression speed >500 MB/s per core, + scalable with multi-cores CPU. It features an extremely fast decoder, with speed in + multiple GB/s per core, typically reaching RAM speed limits on multi-core systems. + + The LZ4 compression library provides in-memory compression and decompression functions. + It gives full buffer control to user. + Compression can be done in: + - a single step (described as Simple Functions) + - a single step, reusing a context (described in Advanced Functions) + - unbounded multiple steps (described as Streaming compression) + + lz4.h generates and decodes LZ4-compressed blocks (doc/lz4_Block_format.md). + Decompressing such a compressed block requires additional metadata. + Exact metadata depends on exact decompression function. + For the typical case of LZ4_decompress_safe(), + metadata includes block's compressed size, and maximum bound of decompressed size. + Each application is free to encode and pass such metadata in whichever way it wants. + + lz4.h only handle blocks, it can not generate Frames. + + Blocks are different from Frames (doc/lz4_Frame_format.md). + Frames bundle both blocks and metadata in a specified manner. + Embedding metadata is required for compressed data to be self-contained and portable. + Frame format is delivered through a companion API, declared in lz4frame.h. + The `lz4` CLI can only manage frames. +*/ + +/*^*************************************************************** +* Export parameters +*****************************************************************/ +/* +* LZ4_DLL_EXPORT : +* Enable exporting of functions when building a Windows DLL +* LZ4LIB_VISIBILITY : +* Control library symbols visibility. +*/ +#ifndef LZ4LIB_VISIBILITY +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4LIB_VISIBILITY __attribute__ ((visibility ("default"))) +# else +# define LZ4LIB_VISIBILITY +# endif +#endif +#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1) +# define LZ4LIB_API __declspec(dllexport) LZ4LIB_VISIBILITY +#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1) +# define LZ4LIB_API __declspec(dllimport) LZ4LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ +#else +# define LZ4LIB_API LZ4LIB_VISIBILITY +#endif + +/*------ Version ------*/ +#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */ +#define LZ4_VERSION_MINOR 9 /* for new (non-breaking) interface capabilities */ +#define LZ4_VERSION_RELEASE 3 /* for tweaks, bug-fixes, or development */ + +#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE) + +#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE +#define LZ4_QUOTE(str) #str +#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str) +#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) + +LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version */ +LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; useful to check dll version */ + + +/*-************************************ +* Tuning parameter +**************************************/ +#define LZ4_MEMORY_USAGE_MIN 10 +#define LZ4_MEMORY_USAGE_DEFAULT 14 +#define LZ4_MEMORY_USAGE_MAX 20 + +/*! + * LZ4_MEMORY_USAGE : + * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; ) + * Increasing memory usage improves compression ratio, at the cost of speed. + * Reduced memory usage may improve speed at the cost of ratio, thanks to better cache locality. + * Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache + */ +#ifndef LZ4_MEMORY_USAGE +# define LZ4_MEMORY_USAGE LZ4_MEMORY_USAGE_DEFAULT +#endif + +#if (LZ4_MEMORY_USAGE < LZ4_MEMORY_USAGE_MIN) +# error "LZ4_MEMORY_USAGE is too small !" +#endif + +#if (LZ4_MEMORY_USAGE > LZ4_MEMORY_USAGE_MAX) +# error "LZ4_MEMORY_USAGE is too large !" +#endif + +/*-************************************ +* Simple Functions +**************************************/ +/*! LZ4_compress_default() : + * Compresses 'srcSize' bytes from buffer 'src' + * into already allocated 'dst' buffer of size 'dstCapacity'. + * Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize). + * It also runs faster, so it's a recommended setting. + * If the function cannot compress 'src' into a more limited 'dst' budget, + * compression stops *immediately*, and the function result is zero. + * In which case, 'dst' content is undefined (invalid). + * srcSize : max supported value is LZ4_MAX_INPUT_SIZE. + * dstCapacity : size of buffer 'dst' (which must be already allocated) + * @return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity) + * or 0 if compression fails + * Note : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer). + */ +LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity); + +/*! LZ4_decompress_safe() : + * compressedSize : is the exact complete size of the compressed block. + * dstCapacity : is the size of destination buffer (which must be already allocated), presumed an upper bound of decompressed size. + * @return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity) + * If destination buffer is not large enough, decoding will stop and output an error code (negative value). + * If the source stream is detected malformed, the function will stop decoding and return a negative result. + * Note 1 : This function is protected against malicious data packets : + * it will never writes outside 'dst' buffer, nor read outside 'source' buffer, + * even if the compressed block is maliciously modified to order the decoder to do these actions. + * In such case, the decoder stops immediately, and considers the compressed block malformed. + * Note 2 : compressedSize and dstCapacity must be provided to the function, the compressed block does not contain them. + * The implementation is free to send / store / derive this information in whichever way is most beneficial. + * If there is a need for a different format which bundles together both compressed data and its metadata, consider looking at lz4frame.h instead. + */ +LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity); + + +/*-************************************ +* Advanced Functions +**************************************/ +#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */ +#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16) + +/*! LZ4_compressBound() : + Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible) + This function is primarily useful for memory allocation purposes (destination buffer size). + Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example). + Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize) + inputSize : max supported value is LZ4_MAX_INPUT_SIZE + return : maximum output size in a "worst case" scenario + or 0, if input size is incorrect (too large or negative) +*/ +LZ4LIB_API int LZ4_compressBound(int inputSize); + +/*! LZ4_compress_fast() : + Same as LZ4_compress_default(), but allows selection of "acceleration" factor. + The larger the acceleration value, the faster the algorithm, but also the lesser the compression. + It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed. + An acceleration value of "1" is the same as regular LZ4_compress_default() + Values <= 0 will be replaced by LZ4_ACCELERATION_DEFAULT (currently == 1, see lz4.c). + Values > LZ4_ACCELERATION_MAX will be replaced by LZ4_ACCELERATION_MAX (currently == 65537, see lz4.c). +*/ +LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + + +/*! LZ4_compress_fast_extState() : + * Same as LZ4_compress_fast(), using an externally allocated memory space for its state. + * Use LZ4_sizeofState() to know how much memory must be allocated, + * and allocate it on 8-bytes boundaries (using `malloc()` typically). + * Then, provide this buffer as `void* state` to compression function. + */ +LZ4LIB_API int LZ4_sizeofState(void); +LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + + +/*! LZ4_compress_destSize() : + * Reverse the logic : compresses as much data as possible from 'src' buffer + * into already allocated buffer 'dst', of size >= 'targetDestSize'. + * This function either compresses the entire 'src' content into 'dst' if it's large enough, + * or fill 'dst' buffer completely with as much data as possible from 'src'. + * note: acceleration parameter is fixed to "default". + * + * *srcSizePtr : will be modified to indicate how many bytes where read from 'src' to fill 'dst'. + * New value is necessarily <= input value. + * @return : Nb bytes written into 'dst' (necessarily <= targetDestSize) + * or 0 if compression fails. + * + * Note : from v1.8.2 to v1.9.1, this function had a bug (fixed un v1.9.2+): + * the produced compressed content could, in specific circumstances, + * require to be decompressed into a destination buffer larger + * by at least 1 byte than the content to decompress. + * If an application uses `LZ4_compress_destSize()`, + * it's highly recommended to update liblz4 to v1.9.2 or better. + * If this can't be done or ensured, + * the receiving decompression function should provide + * a dstCapacity which is > decompressedSize, by at least 1 byte. + * See https://github.com/lz4/lz4/issues/859 for details + */ +LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePtr, int targetDstSize); + + +/*! LZ4_decompress_safe_partial() : + * Decompress an LZ4 compressed block, of size 'srcSize' at position 'src', + * into destination buffer 'dst' of size 'dstCapacity'. + * Up to 'targetOutputSize' bytes will be decoded. + * The function stops decoding on reaching this objective. + * This can be useful to boost performance + * whenever only the beginning of a block is required. + * + * @return : the number of bytes decoded in `dst` (necessarily <= targetOutputSize) + * If source stream is detected malformed, function returns a negative result. + * + * Note 1 : @return can be < targetOutputSize, if compressed block contains less data. + * + * Note 2 : targetOutputSize must be <= dstCapacity + * + * Note 3 : this function effectively stops decoding on reaching targetOutputSize, + * so dstCapacity is kind of redundant. + * This is because in older versions of this function, + * decoding operation would still write complete sequences. + * Therefore, there was no guarantee that it would stop writing at exactly targetOutputSize, + * it could write more bytes, though only up to dstCapacity. + * Some "margin" used to be required for this operation to work properly. + * Thankfully, this is no longer necessary. + * The function nonetheless keeps the same signature, in an effort to preserve API compatibility. + * + * Note 4 : If srcSize is the exact size of the block, + * then targetOutputSize can be any value, + * including larger than the block's decompressed size. + * The function will, at most, generate block's decompressed size. + * + * Note 5 : If srcSize is _larger_ than block's compressed size, + * then targetOutputSize **MUST** be <= block's decompressed size. + * Otherwise, *silent corruption will occur*. + */ +LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity); + + +/*-********************************************* +* Streaming Compression Functions +***********************************************/ +typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */ + +LZ4LIB_API LZ4_stream_t* LZ4_createStream(void); +LZ4LIB_API int LZ4_freeStream (LZ4_stream_t* streamPtr); + +/*! LZ4_resetStream_fast() : v1.9.0+ + * Use this to prepare an LZ4_stream_t for a new chain of dependent blocks + * (e.g., LZ4_compress_fast_continue()). + * + * An LZ4_stream_t must be initialized once before usage. + * This is automatically done when created by LZ4_createStream(). + * However, should the LZ4_stream_t be simply declared on stack (for example), + * it's necessary to initialize it first, using LZ4_initStream(). + * + * After init, start any new stream with LZ4_resetStream_fast(). + * A same LZ4_stream_t can be re-used multiple times consecutively + * and compress multiple streams, + * provided that it starts each new stream with LZ4_resetStream_fast(). + * + * LZ4_resetStream_fast() is much faster than LZ4_initStream(), + * but is not compatible with memory regions containing garbage data. + * + * Note: it's only useful to call LZ4_resetStream_fast() + * in the context of streaming compression. + * The *extState* functions perform their own resets. + * Invoking LZ4_resetStream_fast() before is redundant, and even counterproductive. + */ +LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr); + +/*! LZ4_loadDict() : + * Use this function to reference a static dictionary into LZ4_stream_t. + * The dictionary must remain available during compression. + * LZ4_loadDict() triggers a reset, so any previous data will be forgotten. + * The same dictionary will have to be loaded on decompression side for successful decoding. + * Dictionary are useful for better compression of small data (KB range). + * While LZ4 accept any input as dictionary, + * results are generally better when using Zstandard's Dictionary Builder. + * Loading a size of 0 is allowed, and is the same as reset. + * @return : loaded dictionary size, in bytes (necessarily <= 64 KB) + */ +LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize); + +/*! LZ4_compress_fast_continue() : + * Compress 'src' content using data from previously compressed blocks, for better compression ratio. + * 'dst' buffer must be already allocated. + * If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster. + * + * @return : size of compressed block + * or 0 if there is an error (typically, cannot fit into 'dst'). + * + * Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block. + * Each block has precise boundaries. + * Each block must be decompressed separately, calling LZ4_decompress_*() with relevant metadata. + * It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together. + * + * Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory ! + * + * Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB. + * Make sure that buffers are separated, by at least one byte. + * This construction ensures that each block only depends on previous block. + * + * Note 4 : If input buffer is a ring-buffer, it can have any size, including < 64 KB. + * + * Note 5 : After an error, the stream status is undefined (invalid), it can only be reset or freed. + */ +LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_saveDict() : + * If last 64KB data cannot be guaranteed to remain available at its current memory location, + * save it into a safer place (char* safeBuffer). + * This is schematically equivalent to a memcpy() followed by LZ4_loadDict(), + * but is much faster, because LZ4_saveDict() doesn't need to rebuild tables. + * @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error. + */ +LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize); + + +/*-********************************************** +* Streaming Decompression Functions +* Bufferless synchronous API +************************************************/ +typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* tracking context */ + +/*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() : + * creation / destruction of streaming decompression tracking context. + * A tracking context can be re-used multiple times. + */ +LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void); +LZ4LIB_API int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream); + +/*! LZ4_setStreamDecode() : + * An LZ4_streamDecode_t context can be allocated once and re-used multiple times. + * Use this function to start decompression of a new stream of blocks. + * A dictionary can optionally be set. Use NULL or size 0 for a reset order. + * Dictionary is presumed stable : it must remain accessible and unmodified during next decompression. + * @return : 1 if OK, 0 if error + */ +LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize); + +/*! LZ4_decoderRingBufferSize() : v1.8.2+ + * Note : in a ring buffer scenario (optional), + * blocks are presumed decompressed next to each other + * up to the moment there is not enough remaining space for next block (remainingSize < maxBlockSize), + * at which stage it resumes from beginning of ring buffer. + * When setting such a ring buffer for streaming decompression, + * provides the minimum size of this ring buffer + * to be compatible with any source respecting maxBlockSize condition. + * @return : minimum ring buffer size, + * or 0 if there is an error (invalid maxBlockSize). + */ +LZ4LIB_API int LZ4_decoderRingBufferSize(int maxBlockSize); +#define LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize) (65536 + 14 + (maxBlockSize)) /* for static allocation; maxBlockSize presumed valid */ + +/*! LZ4_decompress_*_continue() : + * These decoding functions allow decompression of consecutive blocks in "streaming" mode. + * A block is an unsplittable entity, it must be presented entirely to a decompression function. + * Decompression functions only accepts one block at a time. + * The last 64KB of previously decoded data *must* remain available and unmodified at the memory position where they were decoded. + * If less than 64KB of data has been decoded, all the data must be present. + * + * Special : if decompression side sets a ring buffer, it must respect one of the following conditions : + * - Decompression buffer size is _at least_ LZ4_decoderRingBufferSize(maxBlockSize). + * maxBlockSize is the maximum size of any single block. It can have any value > 16 bytes. + * In which case, encoding and decoding buffers do not need to be synchronized. + * Actually, data can be produced by any source compliant with LZ4 format specification, and respecting maxBlockSize. + * - Synchronized mode : + * Decompression buffer size is _exactly_ the same as compression buffer size, + * and follows exactly same update rule (block boundaries at same positions), + * and decoding function is provided with exact decompressed size of each block (exception for last block of the stream), + * _then_ decoding & encoding ring buffer can have any size, including small ones ( < 64 KB). + * - Decompression buffer is larger than encoding buffer, by a minimum of maxBlockSize more bytes. + * In which case, encoding and decoding buffers do not need to be synchronized, + * and encoding ring buffer can have any size, including small ones ( < 64 KB). + * + * Whenever these conditions are not possible, + * save the last 64KB of decoded data into a safe buffer where it can't be modified during decompression, + * then indicate where this data is saved using LZ4_setStreamDecode(), before decompressing next block. +*/ +LZ4LIB_API int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int srcSize, int dstCapacity); + + +/*! LZ4_decompress_*_usingDict() : + * These decoding functions work the same as + * a combination of LZ4_setStreamDecode() followed by LZ4_decompress_*_continue() + * They are stand-alone, and don't need an LZ4_streamDecode_t structure. + * Dictionary is presumed stable : it must remain accessible and unmodified during decompression. + * Performance tip : Decompression speed can be substantially increased + * when dst == dictStart + dictSize. + */ +LZ4LIB_API int LZ4_decompress_safe_usingDict (const char* src, char* dst, int srcSize, int dstCapcity, const char* dictStart, int dictSize); + +#endif /* LZ4_H_2983827168210 */ + + +/*^************************************* + * !!!!!! STATIC LINKING ONLY !!!!!! + ***************************************/ + +/*-**************************************************************************** + * Experimental section + * + * Symbols declared in this section must be considered unstable. Their + * signatures or semantics may change, or they may be removed altogether in the + * future. They are therefore only safe to depend on when the caller is + * statically linked against the library. + * + * To protect against unsafe usage, not only are the declarations guarded, + * the definitions are hidden by default + * when building LZ4 as a shared/dynamic library. + * + * In order to access these declarations, + * define LZ4_STATIC_LINKING_ONLY in your application + * before including LZ4's headers. + * + * In order to make their implementations accessible dynamically, you must + * define LZ4_PUBLISH_STATIC_FUNCTIONS when building the LZ4 library. + ******************************************************************************/ + +#ifdef LZ4_STATIC_LINKING_ONLY + +#ifndef LZ4_STATIC_3504398509 +#define LZ4_STATIC_3504398509 + +#ifdef LZ4_PUBLISH_STATIC_FUNCTIONS +#define LZ4LIB_STATIC_API LZ4LIB_API +#else +#define LZ4LIB_STATIC_API +#endif + + +/*! LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. + * It is only safe to call if the state buffer is known to be correctly initialized already + * (see above comment on LZ4_resetStream_fast() for a definition of "correctly initialized"). + * From a high level, the difference is that + * this function initializes the provided state with a call to something like LZ4_resetStream_fast() + * while LZ4_compress_fast_extState() starts with a call to LZ4_resetStream(). + */ +LZ4LIB_STATIC_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_attach_dictionary() : + * This is an experimental API that allows + * efficient use of a static dictionary many times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a + * working LZ4_stream_t, this function introduces a no-copy setup mechanism, + * in which the working stream references the dictionary stream in-place. + * + * Several assumptions are made about the state of the dictionary stream. + * Currently, only streams which have been prepared by LZ4_loadDict() should + * be expected to work. + * + * Alternatively, the provided dictionaryStream may be NULL, + * in which case any existing dictionary stream is unset. + * + * If a dictionary is provided, it replaces any pre-existing stream history. + * The dictionary contents are the only history that can be referenced and + * logically immediately precede the data compressed in the first subsequent + * compression call. + * + * The dictionary will only remain attached to the working stream through the + * first compression call, at the end of which it is cleared. The dictionary + * stream (and source buffer) must remain in-place / accessible / unchanged + * through the completion of the first compression call on the stream. + */ +LZ4LIB_STATIC_API void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const LZ4_stream_t* dictionaryStream); + + +/*! In-place compression and decompression + * + * It's possible to have input and output sharing the same buffer, + * for highly constrained memory environments. + * In both cases, it requires input to lay at the end of the buffer, + * and decompression to start at beginning of the buffer. + * Buffer size must feature some margin, hence be larger than final size. + * + * |<------------------------buffer--------------------------------->| + * |<-----------compressed data--------->| + * |<-----------decompressed size------------------>| + * |<----margin---->| + * + * This technique is more useful for decompression, + * since decompressed size is typically larger, + * and margin is short. + * + * In-place decompression will work inside any buffer + * which size is >= LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize). + * This presumes that decompressedSize > compressedSize. + * Otherwise, it means compression actually expanded data, + * and it would be more efficient to store such data with a flag indicating it's not compressed. + * This can happen when data is not compressible (already compressed, or encrypted). + * + * For in-place compression, margin is larger, as it must be able to cope with both + * history preservation, requiring input data to remain unmodified up to LZ4_DISTANCE_MAX, + * and data expansion, which can happen when input is not compressible. + * As a consequence, buffer size requirements are much higher, + * and memory savings offered by in-place compression are more limited. + * + * There are ways to limit this cost for compression : + * - Reduce history size, by modifying LZ4_DISTANCE_MAX. + * Note that it is a compile-time constant, so all compressions will apply this limit. + * Lower values will reduce compression ratio, except when input_size < LZ4_DISTANCE_MAX, + * so it's a reasonable trick when inputs are known to be small. + * - Require the compressor to deliver a "maximum compressed size". + * This is the `dstCapacity` parameter in `LZ4_compress*()`. + * When this size is < LZ4_COMPRESSBOUND(inputSize), then compression can fail, + * in which case, the return code will be 0 (zero). + * The caller must be ready for these cases to happen, + * and typically design a backup scheme to send data uncompressed. + * The combination of both techniques can significantly reduce + * the amount of margin required for in-place compression. + * + * In-place compression can work in any buffer + * which size is >= (maxCompressedSize) + * with maxCompressedSize == LZ4_COMPRESSBOUND(srcSize) for guaranteed compression success. + * LZ4_COMPRESS_INPLACE_BUFFER_SIZE() depends on both maxCompressedSize and LZ4_DISTANCE_MAX, + * so it's possible to reduce memory requirements by playing with them. + */ + +#define LZ4_DECOMPRESS_INPLACE_MARGIN(compressedSize) (((compressedSize) >> 8) + 32) +#define LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize) ((decompressedSize) + LZ4_DECOMPRESS_INPLACE_MARGIN(decompressedSize)) /**< note: presumes that compressedSize < decompressedSize. note2: margin is overestimated a bit, since it could use compressedSize instead */ + +#ifndef LZ4_DISTANCE_MAX /* history window size; can be user-defined at compile time */ +# define LZ4_DISTANCE_MAX 65535 /* set to maximum value by default */ +#endif + +#define LZ4_COMPRESS_INPLACE_MARGIN (LZ4_DISTANCE_MAX + 32) /* LZ4_DISTANCE_MAX can be safely replaced by srcSize when it's smaller */ +#define LZ4_COMPRESS_INPLACE_BUFFER_SIZE(maxCompressedSize) ((maxCompressedSize) + LZ4_COMPRESS_INPLACE_MARGIN) /**< maxCompressedSize is generally LZ4_COMPRESSBOUND(inputSize), but can be set to any lower value, with the risk that compression can fail (return code 0(zero)) */ + +#endif /* LZ4_STATIC_3504398509 */ +#endif /* LZ4_STATIC_LINKING_ONLY */ + + + +#ifndef LZ4_H_98237428734687 +#define LZ4_H_98237428734687 + +/*-************************************************************ + * Private Definitions + ************************************************************** + * Do not use these definitions directly. + * They are only exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`. + * Accessing members will expose user code to API and/or ABI break in future versions of the library. + **************************************************************/ +#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2) +#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE) +#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */ + +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# include <stdint.h> + typedef int8_t LZ4_i8; + typedef uint8_t LZ4_byte; + typedef uint16_t LZ4_u16; + typedef uint32_t LZ4_u32; +#else + typedef signed char LZ4_i8; + typedef unsigned char LZ4_byte; + typedef unsigned short LZ4_u16; + typedef unsigned int LZ4_u32; +#endif + +typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; +struct LZ4_stream_t_internal { + LZ4_u32 hashTable[LZ4_HASH_SIZE_U32]; + LZ4_u32 currentOffset; + LZ4_u32 tableType; + const LZ4_byte* dictionary; + const LZ4_stream_t_internal* dictCtx; + LZ4_u32 dictSize; +}; + +typedef struct { + const LZ4_byte* externalDict; + size_t extDictSize; + const LZ4_byte* prefixEnd; + size_t prefixSize; +} LZ4_streamDecode_t_internal; + + +/*! LZ4_stream_t : + * Do not use below internal definitions directly ! + * Declare or allocate an LZ4_stream_t instead. + * LZ4_stream_t can also be created using LZ4_createStream(), which is recommended. + * The structure definition can be convenient for static allocation + * (on stack, or as part of larger structure). + * Init this structure with LZ4_initStream() before first use. + * note : only use this definition in association with static linking ! + * this definition is not API/ABI safe, and may change in future versions. + */ +#define LZ4_STREAMSIZE ((1UL << LZ4_MEMORY_USAGE) + 32) /* static size, for inter-version compatibility */ +#define LZ4_STREAMSIZE_VOIDP (LZ4_STREAMSIZE / sizeof(void*)) +union LZ4_stream_u { + void* table[LZ4_STREAMSIZE_VOIDP]; + LZ4_stream_t_internal internal_donotuse; +}; /* previously typedef'd to LZ4_stream_t */ + + +/*! LZ4_initStream() : v1.9.0+ + * An LZ4_stream_t structure must be initialized at least once. + * This is automatically done when invoking LZ4_createStream(), + * but it's not when the structure is simply declared on stack (for example). + * + * Use LZ4_initStream() to properly initialize a newly declared LZ4_stream_t. + * It can also initialize any arbitrary buffer of sufficient size, + * and will @return a pointer of proper type upon initialization. + * + * Note : initialization fails if size and alignment conditions are not respected. + * In which case, the function will @return NULL. + * Note2: An LZ4_stream_t structure guarantees correct alignment and size. + * Note3: Before v1.9.0, use LZ4_resetStream() instead + */ +LZ4LIB_API LZ4_stream_t* LZ4_initStream (void* buffer, size_t size); + + +/*! LZ4_streamDecode_t : + * information structure to track an LZ4 stream during decompression. + * init this structure using LZ4_setStreamDecode() before first use. + * note : only use in association with static linking ! + * this definition is not API/ABI safe, + * and may change in a future version ! + */ +#define LZ4_STREAMDECODESIZE_U64 (4 + ((sizeof(void*)==16) ? 2 : 0) /*AS-400*/ ) +#define LZ4_STREAMDECODESIZE (LZ4_STREAMDECODESIZE_U64 * sizeof(unsigned long long)) +union LZ4_streamDecode_u { + unsigned long long table[LZ4_STREAMDECODESIZE_U64]; + LZ4_streamDecode_t_internal internal_donotuse; +} ; /* previously typedef'd to LZ4_streamDecode_t */ + + + +/*-************************************ +* Obsolete Functions +**************************************/ + +/*! Deprecation warnings + * + * Deprecated functions make the compiler generate a warning when invoked. + * This is meant to invite users to update their source code. + * Should deprecation warnings be a problem, it is generally possible to disable them, + * typically with -Wno-deprecated-declarations for gcc + * or _CRT_SECURE_NO_WARNINGS in Visual. + * + * Another method is to define LZ4_DISABLE_DEPRECATE_WARNINGS + * before including the header file. + */ +#ifdef LZ4_DISABLE_DEPRECATE_WARNINGS +# define LZ4_DEPRECATED(message) /* disable deprecation warnings */ +#else +# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ +# define LZ4_DEPRECATED(message) [[deprecated(message)]] +# elif defined(_MSC_VER) +# define LZ4_DEPRECATED(message) __declspec(deprecated(message)) +# elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 45)) +# define LZ4_DEPRECATED(message) __attribute__((deprecated(message))) +# elif defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 31) +# define LZ4_DEPRECATED(message) __attribute__((deprecated)) +# else +# pragma message("WARNING: LZ4_DEPRECATED needs custom implementation for this compiler") +# define LZ4_DEPRECATED(message) /* disabled */ +# endif +#endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */ + +/*! Obsolete compression functions (since v1.7.3) */ +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* src, char* dest, int srcSize); +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* src, char* dest, int srcSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize); + +/*! Obsolete decompression functions (since v1.8.0) */ +LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize); +LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize); + +/* Obsolete streaming functions (since v1.7.0) + * degraded functionality; do not use! + * + * In order to perform streaming compression, these functions depended on data + * that is no longer tracked in the state. They have been preserved as well as + * possible: using them will still produce a correct output. However, they don't + * actually retain any history between compression calls. The compression ratio + * achieved will therefore be no better than compressing each chunk + * independently. + */ +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStreamState(void); +LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state); + +/*! Obsolete streaming decoding functions (since v1.7.0) */ +LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize); +LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize); + +/*! Obsolete LZ4_decompress_fast variants (since v1.9.0) : + * These functions used to be faster than LZ4_decompress_safe(), + * but this is no longer the case. They are now slower. + * This is because LZ4_decompress_fast() doesn't know the input size, + * and therefore must progress more cautiously into the input buffer to not read beyond the end of block. + * On top of that `LZ4_decompress_fast()` is not protected vs malformed or malicious inputs, making it a security liability. + * As a consequence, LZ4_decompress_fast() is strongly discouraged, and deprecated. + * + * The last remaining LZ4_decompress_fast() specificity is that + * it can decompress a block without knowing its compressed size. + * Such functionality can be achieved in a more secure manner + * by employing LZ4_decompress_safe_partial(). + * + * Parameters: + * originalSize : is the uncompressed size to regenerate. + * `dst` must be already allocated, its size must be >= 'originalSize' bytes. + * @return : number of bytes read from source buffer (== compressed size). + * The function expects to finish at block's end exactly. + * If the source stream is detected malformed, the function stops decoding and returns a negative result. + * note : LZ4_decompress_fast*() requires originalSize. Thanks to this information, it never writes past the output buffer. + * However, since it doesn't know its 'src' size, it may read an unknown amount of input, past input buffer bounds. + * Also, since match offsets are not validated, match reads from 'src' may underflow too. + * These issues never happen if input (compressed) data is correct. + * But they may happen if input data is invalid (error or intentional tampering). + * As a consequence, use these functions in trusted environments with trusted data **only**. + */ +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe() instead") +LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize); +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_continue() instead") +LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize); +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_usingDict() instead") +LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize); + +/*! LZ4_resetStream() : + * An LZ4_stream_t structure must be initialized at least once. + * This is done with LZ4_initStream(), or LZ4_resetStream(). + * Consider switching to LZ4_initStream(), + * invoking LZ4_resetStream() will trigger deprecation warnings in the future. + */ +LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr); + + +#endif /* LZ4_H_98237428734687 */ + + +#if defined (__cplusplus) +} +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/util_compression.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/util_compression.cpp new file mode 100644 index 00000000..2f5704f4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/util_compression.cpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "lz4.h" + +namespace ams::util { + + /* Compression utilities. */ + int CompressLZ4(void *dst, size_t dst_size, const void *src, size_t src_size) { + /* Size checks. */ + AMS_ABORT_UNLESS(dst_size <= std::numeric_limits<int>::max()); + AMS_ABORT_UNLESS(src_size <= std::numeric_limits<int>::max()); + + /* This is just a thin wrapper around LZ4. */ + return LZ4_compress_default(reinterpret_cast<const char *>(src), reinterpret_cast<char *>(dst), static_cast<int>(src_size), static_cast<int>(dst_size)); + } + + /* Decompression utilities. */ + int DecompressLZ4(void *dst, size_t dst_size, const void *src, size_t src_size) { + /* Size checks. */ + AMS_ABORT_UNLESS(dst_size <= std::numeric_limits<int>::max()); + AMS_ABORT_UNLESS(src_size <= std::numeric_limits<int>::max()); + + /* This is just a thin wrapper around LZ4. */ + return LZ4_decompress_safe(reinterpret_cast<const char *>(src), reinterpret_cast<char *>(dst), static_cast<int>(src_size), static_cast<int>(dst_size)); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/util_ini.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/util_ini.cpp new file mode 100644 index 00000000..3da84029 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/util_ini.cpp @@ -0,0 +1,126 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <stratosphere.hpp> +#include "ini.h" + +namespace ams::util::ini { + + /* Ensure that types are the same for Handler vs ini_handler. */ + static_assert(std::is_same<Handler, ::ini_handler>::value, "Bad ini::Handler definition!"); + + namespace { + + struct FileContext { + fs::FileHandle file; + s64 offset; + s64 num_left; + + explicit FileContext(fs::FileHandle f) : file(f), offset(0) { + R_ABORT_UNLESS(fs::GetFileSize(std::addressof(this->num_left), this->file)); + } + }; + + struct IFileContext { + fs::fsa::IFile *file; + s64 offset; + s64 num_left; + + explicit IFileContext(fs::fsa::IFile *f) : file(f), offset(0) { + R_ABORT_UNLESS(file->GetSize(std::addressof(this->num_left))); + } + }; + + char *ini_reader_file_handle(char *str, int num, void *stream) { + FileContext *ctx = static_cast<FileContext *>(stream); + + if (ctx->num_left == 0 || num < 2) { + return nullptr; + } + + /* Read as many bytes as we can. */ + s64 cur_read = std::min<s64>(num - 1, ctx->num_left); + R_ABORT_UNLESS(fs::ReadFile(ctx->file, ctx->offset, str, cur_read, fs::ReadOption())); + + /* Only "read" up to the first \n. */ + size_t offset = cur_read; + for (auto i = 0; i < cur_read; i++) { + if (str[i] == '\n') { + offset = i + 1; + break; + } + } + + /* Ensure null termination. */ + str[offset] = '\0'; + + /* Update context. */ + ctx->offset += offset; + ctx->num_left -= offset; + + return str; + } + + char *ini_reader_ifile(char *str, int num, void *stream) { + IFileContext *ctx = static_cast<IFileContext *>(stream); + + if (ctx->num_left == 0 || num < 2) { + return nullptr; + } + + /* Read as many bytes as we can. */ + s64 cur_read = std::min<s64>(num - 1, ctx->num_left); + size_t read; + R_ABORT_UNLESS(ctx->file->Read(std::addressof(read), ctx->offset, str, cur_read, fs::ReadOption())); + AMS_ABORT_UNLESS(static_cast<s64>(read) == cur_read); + + /* Only "read" up to the first \n. */ + size_t offset = cur_read; + for (auto i = 0; i < cur_read; i++) { + if (str[i] == '\n') { + offset = i + 1; + break; + } + } + + /* Ensure null termination. */ + str[offset] = '\0'; + + /* Update context. */ + ctx->offset += offset; + ctx->num_left -= offset; + + return str; + } + + } + + /* Utilities for dealing with INI file configuration. */ + int ParseString(const char *ini_str, void *user_ctx, Handler h) { + return ini_parse_string(ini_str, h, user_ctx); + } + + int ParseFile(fs::FileHandle file, void *user_ctx, Handler h) { + FileContext ctx(file); + return ini_parse_stream(ini_reader_file_handle, std::addressof(ctx), h, user_ctx); + } + + int ParseFile(fs::fsa::IFile *file, void *user_ctx, Handler h) { + IFileContext ctx(file); + return ini_parse_stream(ini_reader_ifile, std::addressof(ctx), h, user_ctx); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/util_uuid_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/util_uuid_api.cpp new file mode 100644 index 00000000..3030c290 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/util/util_uuid_api.cpp @@ -0,0 +1,111 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::util { + + namespace { + + struct UuidImpl { + util::BitPack32 data[4]; + + using TimeLow = util::BitPack32::Field<0, BITSIZEOF(u32), u32>; + + using TimeMid = util::BitPack32::Field<0, BITSIZEOF(u16), u16>; + using TimeHighAndVersion = util::BitPack32::Field<TimeMid::Next, BITSIZEOF(u16), u16>; + using Version = util::BitPack32::Field<TimeMid::Next + 12, 4, u16>; + + static_assert(TimeHighAndVersion::Next == Version::Next); + + using ClockSeqHiAndReserved = util::BitPack32::Field<0, BITSIZEOF(u8), u8>; + using Reserved = util::BitPack32::Field<6, 2, u8>; + using ClockSeqLow = util::BitPack32::Field<ClockSeqHiAndReserved::Next, BITSIZEOF(u8), u8>; + using NodeLow = util::BitPack32::Field<ClockSeqLow::Next, BITSIZEOF(u16), u16>; + + static_assert(ClockSeqHiAndReserved::Next == Reserved::Next); + + using NodeHigh = util::BitPack32::Field<0, BITSIZEOF(u32), u32>; + + + inline Uuid Convert() const { + /* Convert the fields from native endian to big endian. */ + util::BitPack32 converted[4] = {util::BitPack32{0}, util::BitPack32{0}, util::BitPack32{0}, util::BitPack32{0}}; + + converted[0].Set<TimeLow>(util::ConvertToBigEndian(this->data[0].Get<TimeLow>())); + + converted[1].Set<TimeMid>(util::ConvertToBigEndian(this->data[1].Get<TimeMid>())); + converted[1].Set<TimeHighAndVersion>(util::ConvertToBigEndian(this->data[1].Get<TimeHighAndVersion>())); + + + converted[2].Set<ClockSeqHiAndReserved>(util::ConvertToBigEndian(this->data[2].Get<ClockSeqHiAndReserved>())); + converted[2].Set<ClockSeqLow>(util::ConvertToBigEndian(this->data[2].Get<ClockSeqLow>())); + + u64 node_lo = static_cast<u64>(this->data[2].Get<NodeLow>()); + u64 node_hi = static_cast<u64>(this->data[3].Get<NodeHigh>()); + u64 node = util::ConvertToBigEndian48(static_cast<u64>((node_hi << BITSIZEOF(u16)) | (node_lo))); + + constexpr u64 NodeLoMask = (UINT64_C(1) << BITSIZEOF(u16)) - 1u; + constexpr u64 NodeHiMask = (UINT64_C(1) << BITSIZEOF(u32)) - 1u; + + converted[2].Set<NodeLow>(static_cast<u16>(node & NodeLoMask)); + converted[3].Set<NodeHigh>(static_cast<u32>((node >> BITSIZEOF(u16)) & NodeHiMask)); + + Uuid uuid; + std::memcpy(uuid.data, converted, sizeof(uuid.data)); + return uuid; + } + }; + static_assert(sizeof(UuidImpl) == sizeof(Uuid)); + + ALWAYS_INLINE Uuid GenerateUuidVersion4() { + constexpr u16 Version = 0x4; + constexpr u8 Reserved = 0x1; + + /* Generate a random uuid. */ + UuidImpl uuid = {util::BitPack32{0}, util::BitPack32{0}, util::BitPack32{0}, util::BitPack32{0}}; + os::GenerateRandomBytes(uuid.data, sizeof(uuid.data)); + + /* Set version and reserved. */ + uuid.data[1].Set<UuidImpl::Version>(Version); + uuid.data[2].Set<UuidImpl::Reserved>(Reserved); + + /* Return the uuid. */ + return uuid.Convert(); + } + + } + + Uuid GenerateUuid() { + return GenerateUuidVersion4(); + } + + Uuid GenerateUuidVersion5(const void *sha1_hash) { + constexpr u16 Version = 0x5; + constexpr u8 Reserved = 0x1; + + /* Generate a uuid from a SHA1 hash. */ + UuidImpl uuid = {util::BitPack32{0}, util::BitPack32{0}, util::BitPack32{0}, util::BitPack32{0}}; + std::memcpy(uuid.data, sha1_hash, sizeof(uuid.data)); + + /* Set version and reserved. */ + uuid.data[1].Set<UuidImpl::Version>(Version); + uuid.data[2].Set<UuidImpl::Reserved>(Reserved); + + /* Return the uuid. */ + return uuid.Convert(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/wec/wec_api.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/wec/wec_api.cpp new file mode 100644 index 00000000..36b06396 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/source/wec/wec_api.cpp @@ -0,0 +1,113 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::wec { + + /* TODO: How much of this should be namespaced under BOARD_NINTENDO_NX? */ + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + namespace { + + constexpr inline dd::PhysicalAddress ApbdevPmc = 0x7000E400; + + constinit bool g_initialized = false; + + void UpdateControlBit(dd::PhysicalAddress phys_addr, u32 mask, bool flag) { + dd::ReadModifyWriteIoRegister(phys_addr, flag ? ~0u : 0u, mask); + dd::ReadIoRegister(phys_addr); + } + + void Initialize(bool blink) { + /* Initialize WAKE_DEBOUNCE_EN. */ + dd::WriteIoRegister(ApbdevPmc + APBDEV_PMC_WAKE_DEBOUNCE_EN, 0); + dd::ReadIoRegister(ApbdevPmc + APBDEV_PMC_WAKE_DEBOUNCE_EN); + + /* Initialize BLINK_TIMER. */ + dd::WriteIoRegister(ApbdevPmc + APBDEV_PMC_BLINK_TIMER, 0x08008800); + dd::ReadIoRegister(ApbdevPmc + APBDEV_PMC_BLINK_TIMER); + + /* Set control configs. */ + UpdateControlBit(ApbdevPmc + APBDEV_PMC_CNTRL, 0x0800, true); + UpdateControlBit(ApbdevPmc + APBDEV_PMC_CNTRL, 0x0400, false); + UpdateControlBit(ApbdevPmc + APBDEV_PMC_CNTRL, 0x0200, true); + UpdateControlBit(ApbdevPmc + APBDEV_PMC_CNTRL, 0x0100, false); + UpdateControlBit(ApbdevPmc + APBDEV_PMC_CNTRL, 0x0040, false); + UpdateControlBit(ApbdevPmc + APBDEV_PMC_CNTRL, 0x0020, false); + UpdateControlBit(ApbdevPmc + APBDEV_PMC_CNTRL2, 0x4000, true); + UpdateControlBit(ApbdevPmc + APBDEV_PMC_CNTRL2, 0x0200, false); + UpdateControlBit(ApbdevPmc + APBDEV_PMC_CNTRL2, 0x0001, true); + + /* Update blink bit in APBDEV_PMC_CNTRL. */ + UpdateControlBit(ApbdevPmc + APBDEV_PMC_CNTRL, 0x0080, blink); + + /* Update blink bit in APBDEV_PMC_DPD_PADS_ORIDE. */ + UpdateControlBit(ApbdevPmc + APBDEV_PMC_DPD_PADS_ORIDE, 0x100000, blink); + } + + } + + void Initialize() { + /* Set initial wake configuration. */ + if (!g_initialized) { + Initialize(false); + g_initialized = true; + } + } + + void ClearWakeEvents() { + /* TODO */ + AMS_ABORT(); + } + + void WecRestoreForExitSuspend() { + /* TODO */ + AMS_ABORT(); + } + + void SetWakeEventLevel(wec::WakeEvent event, wec::WakeEventLevel level) { + if (g_initialized && event < wec::WakeEvent_Count) { + /* Determine the event index. */ + const bool which = static_cast<u32>(event) < BITSIZEOF(u32); + const u32 index = static_cast<u32>(event) & (BITSIZEOF(u32) - 1); + + /* Get the level and auto_mask offsets. */ + u32 level_ofs = which ? APBDEV_PMC_WAKE_LVL : APBDEV_PMC_WAKE2_LVL; + u32 auto_mask_ofs = which ? APBDEV_PMC_AUTO_WAKE_LVL_MASK : APBDEV_PMC_AUTO_WAKE2_LVL_MASK; + + /* If the level isn't auto, swap the offsets. */ + if (level != wec::WakeEventLevel_Auto) { + std::swap(level_ofs, auto_mask_ofs); + } + + /* Clear the bit in the level register. */ + UpdateControlBit(ApbdevPmc + level_ofs, (1u << index), false); + + /* Set or clear the bit in the auto mask register. */ + UpdateControlBit(ApbdevPmc + auto_mask_ofs, (1u << index), level != wec::WakeEventLevel_Low); + } + } + + void SetWakeEventEnabled(wec::WakeEvent event, bool en) { + if (g_initialized && event < wec::WakeEvent_Count) { + /* Set or clear the relevant enabled bit. */ + const u32 offset = static_cast<u32>(event) < BITSIZEOF(u32) ? APBDEV_PMC_WAKE_MASK : APBDEV_PMC_WAKE2_MASK; + const u32 index = static_cast<u32>(event) & (BITSIZEOF(u32) - 1); + UpdateControlBit(ApbdevPmc + offset, (1u << index), en); + } + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/stratosphere.specs b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/stratosphere.specs new file mode 100644 index 00000000..d1ba47ae --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libstratosphere/stratosphere.specs @@ -0,0 +1,4 @@ +%rename link pre_old_link + +*link: +%(pre_old_link) -T %:getenv(ATMOSPHERE_LIBRARIES_DIR /libstratosphere/discard-ehframe.ld) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours.hpp new file mode 100644 index 00000000..f454f581 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> + +#include <vapours/allocator.hpp> +#include <vapours/device_code.hpp> +#include <vapours/timespan.hpp> +#include <vapours/span.hpp> + +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) +#include <vapours/tegra.hpp> +#endif + +#include <vapours/crypto.hpp> +#include <vapours/svc.hpp> + +#include <vapours/ams/ams_fatal_error_context.hpp> + +#include <vapours/dd.hpp> +#include <vapours/sdmmc.hpp> \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/allocator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/allocator.hpp new file mode 100644 index 00000000..3828d4a0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/allocator.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams { + + constexpr inline size_t DefaultAlignment = /*alignof(max_align_t)*/ 0x8; + + using AllocateFunction = void *(*)(size_t); + using AllocateFunctionWithUserData = void *(*)(size_t, void *); + using AlignedAllocateFunction = void *(*)(size_t, size_t); + using AlignedAllocateFunctionWithUserData = void *(*)(size_t, size_t, void *); + using DeallocateFunction = void (*)(void *, size_t); + using FreeFunction = void (*)(void *); + using FreeFunctionWithUserData = void (*)(void *, void *); + + class MemoryResource { + public: + ALWAYS_INLINE void *allocate(size_t size, size_t alignment = DefaultAlignment) { + return this->AllocateImpl(size, alignment); + } + ALWAYS_INLINE void deallocate(void *buffer, size_t size, size_t alignment = DefaultAlignment) { + this->DeallocateImpl(buffer, size, alignment); + } + ALWAYS_INLINE bool is_equal(const MemoryResource &resource) const { + return this->IsEqualImpl(resource); + } + ALWAYS_INLINE void *Allocate(size_t size, size_t alignment = DefaultAlignment) { + return this->AllocateImpl(size, alignment); + } + ALWAYS_INLINE void Deallocate(void *buffer, size_t size, size_t alignment = DefaultAlignment) { + this->DeallocateImpl(buffer, size, alignment); + } + ALWAYS_INLINE bool IsEqual(const MemoryResource &resource) const { + return this->IsEqualImpl(resource); + } + public: + constexpr ~MemoryResource() = default; + protected: + virtual void *AllocateImpl(size_t size, size_t alignment) = 0; + virtual void DeallocateImpl(void *buffer, size_t size, size_t alignment) = 0; + virtual bool IsEqualImpl(const MemoryResource &resource) const = 0; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/ams/ams_api_version.h b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/ams/ams_api_version.h new file mode 100644 index 00000000..7c9eba66 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/ams/ams_api_version.h @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#define ATMOSPHERE_RELEASE_VERSION_MAJOR 1 +#define ATMOSPHERE_RELEASE_VERSION_MINOR 9 +#define ATMOSPHERE_RELEASE_VERSION_MICRO 5 + +#define ATMOSPHERE_RELEASE_VERSION ATMOSPHERE_RELEASE_VERSION_MAJOR, ATMOSPHERE_RELEASE_VERSION_MINOR, ATMOSPHERE_RELEASE_VERSION_MICRO + +#define ATMOSPHERE_SUPPORTED_HOS_VERSION_MAJOR 20 +#define ATMOSPHERE_SUPPORTED_HOS_VERSION_MINOR 5 +#define ATMOSPHERE_SUPPORTED_HOS_VERSION_MICRO 0 diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/ams/ams_fatal_error_context.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/ams/ams_fatal_error_context.hpp new file mode 100644 index 00000000..036e42a3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/ams/ams_fatal_error_context.hpp @@ -0,0 +1,66 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/includes.hpp> +#include <vapours/defines.hpp> + +namespace ams::impl { + + struct FatalErrorContext { + static constexpr size_t MaxStackTrace = 0x20; + static constexpr size_t MaxStackDumpSize = 0x100; + static constexpr size_t ThreadLocalSize = 0x100; + static constexpr size_t NumGprs = 29; + static constexpr uintptr_t StdAbortMagicAddress = 0x8; + static constexpr u64 StdAbortMagicValue = 0xA55AF00DDEADCAFEul; + static constexpr u32 StdAbortErrorDesc = 0xFFE; + static constexpr u32 StackOverflowErrorDesc = 0xFFD; + static constexpr u32 KernelPanicDesc = 0xF00; + static constexpr u32 DataAbortErrorDesc = 0x101; + static constexpr u32 Magic = util::FourCC<'A', 'F', 'E', '2'>::Code; + + u32 magic; + u32 error_desc; + u64 program_id; + union { + u64 gprs[32]; + struct { + u64 _gprs[29]; + u64 fp; + u64 lr; + u64 sp; + }; + }; + u64 pc; + u64 module_base; + u32 pstate; + u32 afsr0; + u32 afsr1; + u32 esr; + u64 far; + u64 report_identifier; /* Normally just system tick. */ + u64 stack_trace_size; + u64 stack_dump_size; + u64 stack_trace[MaxStackTrace]; + u8 stack_dump[MaxStackDumpSize]; + u8 tls[ThreadLocalSize]; + }; + + static_assert(sizeof(FatalErrorContext) == 0x450); + static_assert(std::is_standard_layout<FatalErrorContext>::value); + static_assert(std::is_trivial<FatalErrorContext>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/ams/ams_target_firmware.h b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/ams/ams_target_firmware.h new file mode 100644 index 00000000..0055d222 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/ams/ams_target_firmware.h @@ -0,0 +1,194 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#define ATMOSPHERE_TARGET_FIRMWARE_WITH_REVISION(major, minor, micro, rev) ((major << 24) | (minor << 16) | (micro << 8) | (rev)) + +#define ATMOSPHERE_TARGET_FIRMWARE(major, minor, micro) ATMOSPHERE_TARGET_FIRMWARE_WITH_REVISION(major, minor, micro, 0) + +#define ATMOSPHERE_TARGET_FIRMWARE_1_0_0 ATMOSPHERE_TARGET_FIRMWARE( 1, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_2_0_0 ATMOSPHERE_TARGET_FIRMWARE( 2, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_2_1_0 ATMOSPHERE_TARGET_FIRMWARE( 2, 1, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_2_2_0 ATMOSPHERE_TARGET_FIRMWARE( 2, 2, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_2_3_0 ATMOSPHERE_TARGET_FIRMWARE( 2, 3, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_3_0_0 ATMOSPHERE_TARGET_FIRMWARE( 3, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_3_0_1 ATMOSPHERE_TARGET_FIRMWARE( 3, 0, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_3_0_2 ATMOSPHERE_TARGET_FIRMWARE( 3, 0, 2) +#define ATMOSPHERE_TARGET_FIRMWARE_4_0_0 ATMOSPHERE_TARGET_FIRMWARE( 4, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_4_0_1 ATMOSPHERE_TARGET_FIRMWARE( 4, 0, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_4_1_0 ATMOSPHERE_TARGET_FIRMWARE( 4, 1, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_5_0_0 ATMOSPHERE_TARGET_FIRMWARE( 5, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_5_0_1 ATMOSPHERE_TARGET_FIRMWARE( 5, 0, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_5_0_2 ATMOSPHERE_TARGET_FIRMWARE( 5, 0, 2) +#define ATMOSPHERE_TARGET_FIRMWARE_5_1_0 ATMOSPHERE_TARGET_FIRMWARE( 5, 1, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_6_0_0 ATMOSPHERE_TARGET_FIRMWARE( 6, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_6_0_1 ATMOSPHERE_TARGET_FIRMWARE( 6, 0, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_6_1_0 ATMOSPHERE_TARGET_FIRMWARE( 6, 1, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_6_2_0 ATMOSPHERE_TARGET_FIRMWARE( 6, 2, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_7_0_0 ATMOSPHERE_TARGET_FIRMWARE( 7, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_7_0_1 ATMOSPHERE_TARGET_FIRMWARE( 7, 0, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_8_0_0 ATMOSPHERE_TARGET_FIRMWARE( 8, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_8_0_1 ATMOSPHERE_TARGET_FIRMWARE( 8, 0, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_8_1_0 ATMOSPHERE_TARGET_FIRMWARE( 8, 1, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_8_1_1 ATMOSPHERE_TARGET_FIRMWARE( 8, 1, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_9_0_0 ATMOSPHERE_TARGET_FIRMWARE( 9, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_9_0_1 ATMOSPHERE_TARGET_FIRMWARE( 9, 0, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_9_1_0 ATMOSPHERE_TARGET_FIRMWARE( 9, 1, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_9_2_0 ATMOSPHERE_TARGET_FIRMWARE( 9, 2, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_10_0_0 ATMOSPHERE_TARGET_FIRMWARE(10, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_10_0_1 ATMOSPHERE_TARGET_FIRMWARE(10, 0, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_10_0_2 ATMOSPHERE_TARGET_FIRMWARE(10, 0, 2) +#define ATMOSPHERE_TARGET_FIRMWARE_10_0_3 ATMOSPHERE_TARGET_FIRMWARE(10, 0, 3) +#define ATMOSPHERE_TARGET_FIRMWARE_10_0_4 ATMOSPHERE_TARGET_FIRMWARE(10, 0, 4) +#define ATMOSPHERE_TARGET_FIRMWARE_10_1_0 ATMOSPHERE_TARGET_FIRMWARE(10, 1, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_10_1_1 ATMOSPHERE_TARGET_FIRMWARE(10, 1, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_10_2_0 ATMOSPHERE_TARGET_FIRMWARE(10, 2, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_11_0_0 ATMOSPHERE_TARGET_FIRMWARE(11, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_11_0_1 ATMOSPHERE_TARGET_FIRMWARE(11, 0, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_12_0_0 ATMOSPHERE_TARGET_FIRMWARE(12, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_12_0_1 ATMOSPHERE_TARGET_FIRMWARE(12, 0, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_12_0_2 ATMOSPHERE_TARGET_FIRMWARE(12, 0, 2) +#define ATMOSPHERE_TARGET_FIRMWARE_12_0_3 ATMOSPHERE_TARGET_FIRMWARE(12, 0, 3) +#define ATMOSPHERE_TARGET_FIRMWARE_12_1_0 ATMOSPHERE_TARGET_FIRMWARE(12, 1, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_13_0_0 ATMOSPHERE_TARGET_FIRMWARE(13, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_13_1_0 ATMOSPHERE_TARGET_FIRMWARE(13, 1, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_13_2_0 ATMOSPHERE_TARGET_FIRMWARE(13, 2, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_13_2_1 ATMOSPHERE_TARGET_FIRMWARE(13, 2, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_14_0_0 ATMOSPHERE_TARGET_FIRMWARE(14, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_14_1_0 ATMOSPHERE_TARGET_FIRMWARE(14, 1, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_14_1_1 ATMOSPHERE_TARGET_FIRMWARE(14, 1, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_14_1_2 ATMOSPHERE_TARGET_FIRMWARE(14, 1, 2) +#define ATMOSPHERE_TARGET_FIRMWARE_15_0_0 ATMOSPHERE_TARGET_FIRMWARE(15, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_15_0_1 ATMOSPHERE_TARGET_FIRMWARE(15, 0, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_16_0_0 ATMOSPHERE_TARGET_FIRMWARE(16, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_16_0_1 ATMOSPHERE_TARGET_FIRMWARE(16, 0, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_16_0_2 ATMOSPHERE_TARGET_FIRMWARE(16, 0, 2) +#define ATMOSPHERE_TARGET_FIRMWARE_16_0_3 ATMOSPHERE_TARGET_FIRMWARE(16, 0, 3) +#define ATMOSPHERE_TARGET_FIRMWARE_16_1_0 ATMOSPHERE_TARGET_FIRMWARE(16, 1, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_17_0_0 ATMOSPHERE_TARGET_FIRMWARE(17, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_17_0_1 ATMOSPHERE_TARGET_FIRMWARE(17, 0, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_18_0_0 ATMOSPHERE_TARGET_FIRMWARE(18, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_18_0_1 ATMOSPHERE_TARGET_FIRMWARE(18, 0, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_18_1_0 ATMOSPHERE_TARGET_FIRMWARE(18, 1, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_19_0_0 ATMOSPHERE_TARGET_FIRMWARE(19, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_19_0_1 ATMOSPHERE_TARGET_FIRMWARE(19, 0, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_20_0_0 ATMOSPHERE_TARGET_FIRMWARE(20, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_20_0_1 ATMOSPHERE_TARGET_FIRMWARE(20, 0, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_20_1_0 ATMOSPHERE_TARGET_FIRMWARE(20, 1, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_20_1_1 ATMOSPHERE_TARGET_FIRMWARE(20, 1, 1) +#define ATMOSPHERE_TARGET_FIRMWARE_20_1_5 ATMOSPHERE_TARGET_FIRMWARE(20, 1, 5) +#define ATMOSPHERE_TARGET_FIRMWARE_20_2_0 ATMOSPHERE_TARGET_FIRMWARE(20, 2, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_20_3_0 ATMOSPHERE_TARGET_FIRMWARE(20, 3, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_20_4_0 ATMOSPHERE_TARGET_FIRMWARE(20, 4, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_20_5_0 ATMOSPHERE_TARGET_FIRMWARE(20, 5, 0) + +#define ATMOSPHERE_TARGET_FIRMWARE_CURRENT ATMOSPHERE_TARGET_FIRMWARE_20_5_0 + +#define ATMOSPHERE_TARGET_FIRMWARE_MIN ATMOSPHERE_TARGET_FIRMWARE(0, 0, 0) +#define ATMOSPHERE_TARGET_FIRMWARE_MAX ATMOSPHERE_TARGET_FIRMWARE_CURRENT + +#ifdef __cplusplus + +namespace ams { + + enum TargetFirmware : u32 { + TargetFirmware_Min = ATMOSPHERE_TARGET_FIRMWARE_MIN, + + TargetFirmware_1_0_0 = ATMOSPHERE_TARGET_FIRMWARE_1_0_0, + TargetFirmware_2_0_0 = ATMOSPHERE_TARGET_FIRMWARE_2_0_0, + TargetFirmware_2_1_0 = ATMOSPHERE_TARGET_FIRMWARE_2_1_0, + TargetFirmware_2_2_0 = ATMOSPHERE_TARGET_FIRMWARE_2_2_0, + TargetFirmware_2_3_0 = ATMOSPHERE_TARGET_FIRMWARE_2_3_0, + TargetFirmware_3_0_0 = ATMOSPHERE_TARGET_FIRMWARE_3_0_0, + TargetFirmware_3_0_1 = ATMOSPHERE_TARGET_FIRMWARE_3_0_1, + TargetFirmware_3_0_2 = ATMOSPHERE_TARGET_FIRMWARE_3_0_2, + TargetFirmware_4_0_0 = ATMOSPHERE_TARGET_FIRMWARE_4_0_0, + TargetFirmware_4_0_1 = ATMOSPHERE_TARGET_FIRMWARE_4_0_1, + TargetFirmware_4_1_0 = ATMOSPHERE_TARGET_FIRMWARE_4_1_0, + TargetFirmware_5_0_0 = ATMOSPHERE_TARGET_FIRMWARE_5_0_0, + TargetFirmware_5_0_1 = ATMOSPHERE_TARGET_FIRMWARE_5_0_1, + TargetFirmware_5_0_2 = ATMOSPHERE_TARGET_FIRMWARE_5_0_2, + TargetFirmware_5_1_0 = ATMOSPHERE_TARGET_FIRMWARE_5_1_0, + TargetFirmware_6_0_0 = ATMOSPHERE_TARGET_FIRMWARE_6_0_0, + TargetFirmware_6_0_1 = ATMOSPHERE_TARGET_FIRMWARE_6_0_1, + TargetFirmware_6_1_0 = ATMOSPHERE_TARGET_FIRMWARE_6_1_0, + TargetFirmware_6_2_0 = ATMOSPHERE_TARGET_FIRMWARE_6_2_0, + TargetFirmware_7_0_0 = ATMOSPHERE_TARGET_FIRMWARE_7_0_0, + TargetFirmware_7_0_1 = ATMOSPHERE_TARGET_FIRMWARE_7_0_1, + TargetFirmware_8_0_0 = ATMOSPHERE_TARGET_FIRMWARE_8_0_0, + TargetFirmware_8_0_1 = ATMOSPHERE_TARGET_FIRMWARE_8_0_1, + TargetFirmware_8_1_0 = ATMOSPHERE_TARGET_FIRMWARE_8_1_0, + TargetFirmware_8_1_1 = ATMOSPHERE_TARGET_FIRMWARE_8_1_1, + TargetFirmware_9_0_0 = ATMOSPHERE_TARGET_FIRMWARE_9_0_0, + TargetFirmware_9_0_1 = ATMOSPHERE_TARGET_FIRMWARE_9_0_1, + TargetFirmware_9_1_0 = ATMOSPHERE_TARGET_FIRMWARE_9_1_0, + TargetFirmware_9_2_0 = ATMOSPHERE_TARGET_FIRMWARE_9_2_0, + TargetFirmware_10_0_0 = ATMOSPHERE_TARGET_FIRMWARE_10_0_0, + TargetFirmware_10_0_1 = ATMOSPHERE_TARGET_FIRMWARE_10_0_1, + TargetFirmware_10_0_2 = ATMOSPHERE_TARGET_FIRMWARE_10_0_2, + TargetFirmware_10_0_3 = ATMOSPHERE_TARGET_FIRMWARE_10_0_3, + TargetFirmware_10_0_4 = ATMOSPHERE_TARGET_FIRMWARE_10_0_4, + TargetFirmware_10_1_0 = ATMOSPHERE_TARGET_FIRMWARE_10_1_0, + TargetFirmware_10_1_1 = ATMOSPHERE_TARGET_FIRMWARE_10_1_1, + TargetFirmware_10_2_0 = ATMOSPHERE_TARGET_FIRMWARE_10_2_0, + TargetFirmware_11_0_0 = ATMOSPHERE_TARGET_FIRMWARE_11_0_0, + TargetFirmware_11_0_1 = ATMOSPHERE_TARGET_FIRMWARE_11_0_1, + TargetFirmware_12_0_0 = ATMOSPHERE_TARGET_FIRMWARE_12_0_0, + TargetFirmware_12_0_1 = ATMOSPHERE_TARGET_FIRMWARE_12_0_1, + TargetFirmware_12_0_2 = ATMOSPHERE_TARGET_FIRMWARE_12_0_2, + TargetFirmware_12_0_3 = ATMOSPHERE_TARGET_FIRMWARE_12_0_3, + TargetFirmware_12_1_0 = ATMOSPHERE_TARGET_FIRMWARE_12_1_0, + TargetFirmware_13_0_0 = ATMOSPHERE_TARGET_FIRMWARE_13_0_0, + TargetFirmware_13_1_0 = ATMOSPHERE_TARGET_FIRMWARE_13_1_0, + TargetFirmware_13_2_0 = ATMOSPHERE_TARGET_FIRMWARE_13_2_0, + TargetFirmware_13_2_1 = ATMOSPHERE_TARGET_FIRMWARE_13_2_1, + TargetFirmware_14_0_0 = ATMOSPHERE_TARGET_FIRMWARE_14_0_0, + TargetFirmware_14_1_0 = ATMOSPHERE_TARGET_FIRMWARE_14_1_0, + TargetFirmware_14_1_1 = ATMOSPHERE_TARGET_FIRMWARE_14_1_1, + TargetFirmware_14_1_2 = ATMOSPHERE_TARGET_FIRMWARE_14_1_2, + TargetFirmware_15_0_0 = ATMOSPHERE_TARGET_FIRMWARE_15_0_0, + TargetFirmware_15_0_1 = ATMOSPHERE_TARGET_FIRMWARE_15_0_1, + TargetFirmware_16_0_0 = ATMOSPHERE_TARGET_FIRMWARE_16_0_0, + TargetFirmware_16_0_1 = ATMOSPHERE_TARGET_FIRMWARE_16_0_1, + TargetFirmware_16_0_2 = ATMOSPHERE_TARGET_FIRMWARE_16_0_2, + TargetFirmware_16_0_3 = ATMOSPHERE_TARGET_FIRMWARE_16_0_3, + TargetFirmware_16_1_0 = ATMOSPHERE_TARGET_FIRMWARE_16_1_0, + TargetFirmware_17_0_0 = ATMOSPHERE_TARGET_FIRMWARE_17_0_0, + TargetFirmware_17_0_1 = ATMOSPHERE_TARGET_FIRMWARE_17_0_1, + TargetFirmware_18_0_0 = ATMOSPHERE_TARGET_FIRMWARE_18_0_0, + TargetFirmware_18_0_1 = ATMOSPHERE_TARGET_FIRMWARE_18_0_1, + TargetFirmware_18_1_0 = ATMOSPHERE_TARGET_FIRMWARE_18_1_0, + TargetFirmware_19_0_0 = ATMOSPHERE_TARGET_FIRMWARE_19_0_0, + TargetFirmware_19_0_1 = ATMOSPHERE_TARGET_FIRMWARE_19_0_1, + TargetFirmware_20_0_0 = ATMOSPHERE_TARGET_FIRMWARE_20_0_0, + TargetFirmware_20_0_1 = ATMOSPHERE_TARGET_FIRMWARE_20_0_1, + TargetFirmware_20_1_0 = ATMOSPHERE_TARGET_FIRMWARE_20_1_0, + TargetFirmware_20_1_1 = ATMOSPHERE_TARGET_FIRMWARE_20_1_1, + TargetFirmware_20_1_5 = ATMOSPHERE_TARGET_FIRMWARE_20_1_5, + TargetFirmware_20_2_0 = ATMOSPHERE_TARGET_FIRMWARE_20_2_0, + TargetFirmware_20_3_0 = ATMOSPHERE_TARGET_FIRMWARE_20_3_0, + TargetFirmware_20_4_0 = ATMOSPHERE_TARGET_FIRMWARE_20_4_0, + TargetFirmware_20_5_0 = ATMOSPHERE_TARGET_FIRMWARE_20_5_0, + + TargetFirmware_Current = ATMOSPHERE_TARGET_FIRMWARE_CURRENT, + + TargetFirmware_Max = ATMOSPHERE_TARGET_FIRMWARE_MAX, + }; + static_assert(TargetFirmware_Current <= TargetFirmware_Max); + +} + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/ams_version.h b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/ams_version.h new file mode 100644 index 00000000..1622caeb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/ams_version.h @@ -0,0 +1,19 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include "ams/ams_api_version.h" +#include "ams/ams_target_firmware.h" diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/assert.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/assert.hpp new file mode 100644 index 00000000..d901f2d9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/assert.hpp @@ -0,0 +1,133 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> + +namespace ams { + + class Result; + + namespace os { + + struct UserExceptionInfo; + + } + + namespace impl { + + NORETURN void UnexpectedDefaultImpl(const char *func, const char *file, int line); + + } + +} + +namespace ams::diag { + + enum AssertionType { + AssertionType_Audit, + AssertionType_Assert, + }; + + struct LogMessage; + + struct AssertionInfo { + AssertionType type; + const LogMessage *message; + const char *expr; + const char *func; + const char *file; + int line; + }; + + enum AbortReason { + AbortReason_Audit, + AbortReason_Assert, + AbortReason_Abort, + AbortReason_UnexpectedDefault, + }; + + struct AbortInfo { + AbortReason reason; + const LogMessage *message; + const char *expr; + const char *func; + const char *file; + int line; + }; + + void OnAssertionFailure(AssertionType type, const char *expr, const char *func, const char *file, int line, const char *format, ...) __attribute__((format(printf, 6, 7))); + void OnAssertionFailure(AssertionType type, const char *expr, const char *func, const char *file, int line); + + NORETURN void AbortImpl(const char *expr, const char *func, const char *file, int line); + NORETURN void AbortImpl(const char *expr, const char *func, const char *file, int line, const char *format, ...) __attribute__((format(printf, 5, 6))); + NORETURN void AbortImpl(const char *expr, const char *func, const char *file, int line, const ::ams::Result *result, const char *format, ...) __attribute__((format(printf, 6, 7))); + + NORETURN void AbortImpl(const char *expr, const char *func, const char *file, int line, const ::ams::Result *result, const ::ams::os::UserExceptionInfo *exception_info, const char *fmt, ...) __attribute__((format(printf, 7, 8))); + + NORETURN void VAbortImpl(const char *expr, const char *func, const char *file, int line, const ::ams::Result *result, const ::ams::os::UserExceptionInfo *exception_info, const char *fmt, std::va_list vl); + +} + +#ifdef AMS_ENABLE_DETAILED_ASSERTIONS +#define AMS_CALL_ASSERT_FAIL_IMPL(type, expr, ...) ::ams::diag::OnAssertionFailure(type, expr, __PRETTY_FUNCTION__, __FILE__, __LINE__, ## __VA_ARGS__) +#define AMS_CALL_ABORT_IMPL(expr, ...) ::ams::diag::AbortImpl(expr, __PRETTY_FUNCTION__, __FILE__, __LINE__, ## __VA_ARGS__) +#define AMS_UNREACHABLE_DEFAULT_CASE() default: ::ams::impl::UnexpectedDefaultImpl(__PRETTY_FUNCTION__, __FILE__, __LINE__) +#else +#define AMS_CALL_ASSERT_FAIL_IMPL(type, expr, ...) ::ams::diag::OnAssertionFailure(type, "", "", "", 0) +#define AMS_CALL_ABORT_IMPL(expr, ...) ::ams::diag::AbortImpl("", "", "", 0); AMS_UNUSED(expr, ## __VA_ARGS__) +#define AMS_UNREACHABLE_DEFAULT_CASE() default: ::ams::impl::UnexpectedDefaultImpl("", "", 0) +#endif + +#ifdef AMS_ENABLE_ASSERTIONS +#define AMS_ASSERT_IMPL(type, expr, ...) \ + { \ + if (std::is_constant_evaluated()) { \ + AMS_ASSUME(static_cast<bool>(expr)); \ + } else { \ + if (const bool __tmp_ams_assert_val = static_cast<bool>(expr); (!__tmp_ams_assert_val)) { \ + AMS_CALL_ASSERT_FAIL_IMPL(type, #expr, ## __VA_ARGS__); \ + } \ + } \ + } +#elif defined(AMS_PRESERVE_ASSERTION_EXPRESSIONS) +#define AMS_ASSERT_IMPL(type, expr, ...) AMS_UNUSED(expr, ## __VA_ARGS__) +#else +#define AMS_ASSERT_IMPL(type, expr, ...) static_cast<void>(0) +#endif + +#define AMS_ASSERT(expr, ...) AMS_ASSERT_IMPL(::ams::diag::AssertionType_Assert, expr, ## __VA_ARGS__) + + +#ifdef AMS_BUILD_FOR_AUDITING +#define AMS_AUDIT(expr, ...) AMS_ASSERT_IMPL(::ams::diag::AssertionType_Audit, expr, ## __VA_ARGS__) +#elif defined(AMS_PRESERVE_AUDIT_EXPRESSIONS) +#define AMS_AUDIT(expr, ...) AMS_UNUSED(expr, ## __VA_ARGS__) +#else +#define AMS_AUDIT(expr, ...) static_cast<void>(0) +#endif + +#define AMS_ABORT(...) AMS_CALL_ABORT_IMPL("", ## __VA_ARGS__) + +#define AMS_ABORT_UNLESS(expr, ...) \ + { \ + if (std::is_constant_evaluated()) { \ + AMS_ASSUME(static_cast<bool>(expr)); \ + } else { \ + if (const bool __tmp_ams_assert_val = static_cast<bool>(expr); AMS_UNLIKELY(!__tmp_ams_assert_val)) { \ + AMS_CALL_ABORT_IMPL(#expr, ##__VA_ARGS__); \ + } \ + } \ + } diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/common.hpp new file mode 100644 index 00000000..7fcc1daa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/common.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/includes.hpp> +#include <vapours/defines.hpp> +#include <vapours/compiler.hpp> + +#if defined(AMS_FORCE_DISABLE_DETAILED_ASSERTIONS) && defined(AMS_FORCE_ENABLE_DETAILED_ASSERTIONS) + #error "Invalid detailed assertions state" +#endif + +#ifdef AMS_BUILD_FOR_AUDITING + + #define AMS_BUILD_FOR_DEBUGGING + + #if !defined(AMS_FORCE_DISABLE_DETAILED_ASSERTIONS) + #define AMS_ENABLE_DETAILED_ASSERTIONS + #endif + +#endif + +#ifdef AMS_BUILD_FOR_DEBUGGING + + #define AMS_ENABLE_ASSERTIONS + + #if !defined(AMS_ENABLE_DETAILED_ASSERTIONS) && !defined(AMS_FORCE_DISABLE_DETAILED_ASSERTIONS) + + #if !defined(ATMOSPHERE_IS_EXOSPHERE) || defined(AMS_FORCE_ENABLE_DETAILED_ASSERTIONS) + + #define AMS_ENABLE_DETAILED_ASSERTIONS + + #endif + + #endif + + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/compiler.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/compiler.hpp new file mode 100644 index 00000000..90efa405 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/compiler.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/includes.hpp> +#include <vapours/defines.hpp> + +#if defined(ATMOSPHERE_COMPILER_CLANG) +#include <vapours/impl/compiler_impl.clang.hpp> +#elif defined(ATMOSPHERE_COMPILER_GCC) +#include <vapours/impl/compiler_impl.gcc.hpp> +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto.hpp new file mode 100644 index 00000000..205bbb7e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/defines.hpp> + +#include <vapours/crypto/crypto_memory_compare.hpp> +#include <vapours/crypto/crypto_memory_clear.hpp> +#include <vapours/crypto/crypto_md5_generator.hpp> +#include <vapours/crypto/crypto_sha1_generator.hpp> +#include <vapours/crypto/crypto_sha256_generator.hpp> +#include <vapours/crypto/crypto_sha3_generator.hpp> +#include <vapours/crypto/crypto_aes_encryptor.hpp> +#include <vapours/crypto/crypto_aes_decryptor.hpp> +#include <vapours/crypto/crypto_aes_cbc_encryptor_decryptor.hpp> +#include <vapours/crypto/crypto_aes_ccm_encryptor_decryptor.hpp> +#include <vapours/crypto/crypto_aes_ctr_encryptor_decryptor.hpp> +#include <vapours/crypto/crypto_aes_xts_encryptor_decryptor.hpp> +#include <vapours/crypto/crypto_aes_gcm_encryptor.hpp> +#include <vapours/crypto/crypto_aes_128_cmac_generator.hpp> +#include <vapours/crypto/crypto_rsa_pkcs1_sha256_verifier.hpp> +#include <vapours/crypto/crypto_rsa_pss_sha256_verifier.hpp> +#include <vapours/crypto/crypto_rsa_oaep_sha256_decoder.hpp> +#include <vapours/crypto/crypto_rsa_oaep_sha256_decryptor.hpp> +#include <vapours/crypto/crypto_rsa_oaep_sha256_encryptor.hpp> +#include <vapours/crypto/crypto_hmac_sha1_generator.hpp> +#include <vapours/crypto/crypto_hmac_sha256_generator.hpp> +#include <vapours/crypto/crypto_csrng.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_128_cmac_generator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_128_cmac_generator.hpp new file mode 100644 index 00000000..c252263e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_128_cmac_generator.hpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_aes_encryptor.hpp> +#include <vapours/crypto/crypto_cmac_generator.hpp> + +namespace ams::crypto { + + class Aes128CmacGenerator { + NON_COPYABLE(Aes128CmacGenerator); + NON_MOVEABLE(Aes128CmacGenerator); + public: + static constexpr size_t MacSize = AesEncryptor128::BlockSize; + private: + AesEncryptor128 m_aes; + CmacGenerator<AesEncryptor128> m_cmac_generator; + public: + Aes128CmacGenerator() { /* ... */ } + + void Initialize(const void *key, size_t key_size) { + AMS_ASSERT(key_size == AesEncryptor128::KeySize); + + m_aes.Initialize(key, key_size); + m_cmac_generator.Initialize(std::addressof(m_aes)); + } + + void Update(const void *data, size_t size) { + m_cmac_generator.Update(data, size); + } + + void GetMac(void *dst, size_t size) { + m_cmac_generator.GetMac(dst, size); + } + }; + + ALWAYS_INLINE void GenerateAes128Cmac(void *dst, size_t dst_size, const void *data, size_t data_size, const void *key, size_t key_size) { + Aes128CmacGenerator cmac_generator; + + cmac_generator.Initialize(key, key_size); + cmac_generator.Update(data, data_size); + cmac_generator.GetMac(dst, dst_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_cbc_encryptor_decryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_cbc_encryptor_decryptor.hpp new file mode 100644 index 00000000..6fe5c390 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_cbc_encryptor_decryptor.hpp @@ -0,0 +1,81 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_aes_encryptor.hpp> +#include <vapours/crypto/crypto_cbc_encryptor.hpp> +#include <vapours/crypto/crypto_cbc_decryptor.hpp> + +namespace ams::crypto { + + namespace impl { + + template<template<typename> typename _CbcImpl, typename _AesImpl> + class AesCbcCryptor { + NON_COPYABLE(AesCbcCryptor); + NON_MOVEABLE(AesCbcCryptor); + private: + using AesImpl = _AesImpl; + using CbcImpl = _CbcImpl<AesImpl>; + public: + static constexpr size_t KeySize = AesImpl::KeySize; + static constexpr size_t BlockSize = CbcImpl::BlockSize; + static constexpr size_t IvSize = CbcImpl::BlockSize; + private: + AesImpl m_aes_impl; + CbcImpl m_cbc_impl; + public: + AesCbcCryptor() { /* ... */ } + + void Initialize(const void *key, size_t key_size, const void *iv, size_t iv_size) { + AMS_ASSERT(key_size == KeySize); + AMS_ASSERT(iv_size == IvSize); + + m_aes_impl.Initialize(key, key_size); + m_cbc_impl.Initialize(std::addressof(m_aes_impl), iv, iv_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_cbc_impl.Update(dst, dst_size, src, src_size); + } + + size_t GetBufferedDataSize() const { + return m_cbc_impl.GetBufferedDataSize(); + } + }; + + } + + using Aes128CbcEncryptor = impl::AesCbcCryptor<CbcEncryptor, AesEncryptor128>; + using Aes192CbcEncryptor = impl::AesCbcCryptor<CbcEncryptor, AesEncryptor192>; + using Aes256CbcEncryptor = impl::AesCbcCryptor<CbcEncryptor, AesEncryptor256>; + + using Aes128CbcDecryptor = impl::AesCbcCryptor<CbcDecryptor, AesDecryptor128>; + using Aes192CbcDecryptor = impl::AesCbcCryptor<CbcDecryptor, AesDecryptor192>; + using Aes256CbcDecryptor = impl::AesCbcCryptor<CbcDecryptor, AesDecryptor256>; + + size_t EncryptAes128Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + size_t EncryptAes192Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + size_t EncryptAes256Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + + size_t DecryptAes128Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + size_t DecryptAes192Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + size_t DecryptAes256Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_ccm_encryptor_decryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_ccm_encryptor_decryptor.hpp new file mode 100644 index 00000000..33d3b702 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_ccm_encryptor_decryptor.hpp @@ -0,0 +1,115 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_aes_encryptor.hpp> +#include <vapours/crypto/crypto_ccm_encryptor.hpp> +#include <vapours/crypto/crypto_ccm_decryptor.hpp> + +namespace ams::crypto { + + namespace impl { + + template<template<typename> typename _CcmImpl, typename _AesImpl> + class AesCcmCryptor { + NON_COPYABLE(AesCcmCryptor); + NON_MOVEABLE(AesCcmCryptor); + private: + using AesImpl = _AesImpl; + using CcmImpl = _CcmImpl<AesImpl>; + public: + static constexpr size_t KeySize = CcmImpl::KeySize; + static constexpr size_t BlockSize = CcmImpl::BlockSize; + static constexpr size_t MaxMacSize = CcmImpl::MaxMacSize; + static constexpr size_t MaxNonceSize = CcmImpl::MaxNonceSize; + private: + AesImpl m_aes_impl; + CcmImpl m_ccm_impl; + public: + AesCcmCryptor() { /* ... */ } + + void Initialize(const void *key, size_t key_size, const void *nonce, size_t nonce_size, s64 aad_size, s64 data_size, size_t mac_size) { + AMS_ASSERT(key_size == KeySize); + AMS_ASSERT(nonce_size <= MaxNonceSize); + AMS_ASSERT(mac_size <= MaxMacSize); + + m_aes_impl.Initialize(key, key_size); + m_ccm_impl.Initialize(std::addressof(m_aes_impl), nonce, nonce_size, aad_size, data_size, mac_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_ccm_impl.Update(dst, dst_size, src, src_size); + } + + void UpdateAad(const void *aad, size_t aad_size) { + return m_ccm_impl.UpdateAad(aad, aad_size); + } + + void GetMac(void *dst, size_t dst_size) { + return m_ccm_impl.GetMac(dst, dst_size); + } + }; + + } + + using Aes128CcmEncryptor = impl::AesCcmCryptor<CcmEncryptor, AesEncryptor128>; + using Aes128CcmDecryptor = impl::AesCcmCryptor<CcmDecryptor, AesEncryptor128>; + + inline size_t EncryptAes128Ccm(void *dst, size_t dst_size, void *mac, size_t mac_buf_size, const void *key, size_t key_size, const void *nonce, size_t nonce_size, const void *src, size_t src_size, const void *aad, size_t aad_size, size_t mac_size) { + /* Create encryptor. */ + Aes128CcmEncryptor ccm; + ccm.Initialize(key, key_size, nonce, nonce_size, aad_size, src_size, mac_size); + + /* Process aad. */ + if (aad_size > 0) { + ccm.UpdateAad(aad, aad_size); + } + + /* Process data. */ + size_t processed = 0; + if (src_size > 0) { + processed = ccm.Update(dst, dst_size, src, src_size); + } + + /* Get mac. */ + ccm.GetMac(mac, mac_buf_size); + return processed; + } + + inline size_t DecryptAes128Ccm(void *dst, size_t dst_size, void *mac, size_t mac_buf_size, const void *key, size_t key_size, const void *nonce, size_t nonce_size, const void *src, size_t src_size, const void *aad, size_t aad_size, size_t mac_size) { + /* Create decryptor. */ + Aes128CcmDecryptor ccm; + ccm.Initialize(key, key_size, nonce, nonce_size, aad_size, src_size, mac_size); + + /* Process aad. */ + if (aad_size > 0) { + ccm.UpdateAad(aad, aad_size); + } + + /* Process data. */ + size_t processed = 0; + if (src_size > 0) { + processed = ccm.Update(dst, dst_size, src, src_size); + } + + /* Get mac. */ + ccm.GetMac(mac, mac_buf_size); + return processed; + } +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_ctr_encryptor_decryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_ctr_encryptor_decryptor.hpp new file mode 100644 index 00000000..3fa27856 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_ctr_encryptor_decryptor.hpp @@ -0,0 +1,94 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_aes_encryptor.hpp> +#include <vapours/crypto/crypto_ctr_encryptor.hpp> +#include <vapours/crypto/crypto_ctr_decryptor.hpp> + +namespace ams::crypto { + + namespace impl { + + template<template<typename> typename _CtrImpl, typename _AesImpl> + class AesCtrCryptor { + NON_COPYABLE(AesCtrCryptor); + NON_MOVEABLE(AesCtrCryptor); + private: + using AesImpl = _AesImpl; + using CtrImpl = _CtrImpl<AesImpl>; + public: + static constexpr size_t KeySize = AesImpl::KeySize; + static constexpr size_t BlockSize = CtrImpl::BlockSize; + static constexpr size_t IvSize = CtrImpl::BlockSize; + private: + AesImpl m_aes_impl; + CtrImpl m_ctr_impl; + public: + AesCtrCryptor() { /* ... */ } + + void Initialize(const void *key, size_t key_size, const void *iv, size_t iv_size) { + this->Initialize(key, key_size, iv, iv_size, 0); + } + + void Initialize(const void *key, size_t key_size, const void *iv, size_t iv_size, s64 offset) { + AMS_ASSERT(key_size == KeySize); + AMS_ASSERT(iv_size == IvSize); + AMS_ASSERT(offset >= 0); + + m_aes_impl.Initialize(key, key_size); + m_ctr_impl.Initialize(std::addressof(m_aes_impl), iv, iv_size, offset); + } + + void SwitchMessage(const void *iv, size_t iv_size) { + return m_ctr_impl.SwitchMessage(iv, iv_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_ctr_impl.Update(dst, dst_size, src, src_size); + } + }; + + } + + using Aes128CtrEncryptor = impl::AesCtrCryptor<CtrEncryptor, AesEncryptor128>; + using Aes192CtrEncryptor = impl::AesCtrCryptor<CtrEncryptor, AesEncryptor192>; + using Aes256CtrEncryptor = impl::AesCtrCryptor<CtrEncryptor, AesEncryptor256>; + + using Aes128CtrDecryptor = impl::AesCtrCryptor<CtrDecryptor, AesEncryptor128>; + using Aes192CtrDecryptor = impl::AesCtrCryptor<CtrDecryptor, AesEncryptor192>; + using Aes256CtrDecryptor = impl::AesCtrCryptor<CtrDecryptor, AesEncryptor256>; + + size_t EncryptAes128Ctr(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + size_t EncryptAes192Ctr(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + size_t EncryptAes256Ctr(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + + size_t DecryptAes128Ctr(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + size_t DecryptAes192Ctr(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + size_t DecryptAes256Ctr(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + + size_t EncryptAes128CtrPartial(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, s64 offset, const void *src, size_t src_size); + size_t EncryptAes192CtrPartial(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, s64 offset, const void *src, size_t src_size); + size_t EncryptAes256CtrPartial(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, s64 offset, const void *src, size_t src_size); + + size_t DecryptAes128CtrPartial(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, s64 offset, const void *src, size_t src_size); + size_t DecryptAes192CtrPartial(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, s64 offset, const void *src, size_t src_size); + size_t DecryptAes256CtrPartial(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, s64 offset, const void *src, size_t src_size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_decryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_decryptor.hpp new file mode 100644 index 00000000..3983450e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_decryptor.hpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_aes_impl.hpp> + +namespace ams::crypto { + + template<size_t _KeySize> + class AesDecryptor { + NON_COPYABLE(AesDecryptor); + NON_MOVEABLE(AesDecryptor); + private: + using Impl = impl::AesImpl<_KeySize>; + public: + static constexpr size_t KeySize = Impl::KeySize; + static constexpr size_t BlockSize = Impl::BlockSize; + static constexpr size_t RoundKeySize = Impl::RoundKeySize; + private: + Impl m_impl; + public: + AesDecryptor() { /* ... */ } + + void Initialize(const void *key, size_t key_size) { + m_impl.Initialize(key, key_size, false); + } + + void DecryptBlock(void *dst, size_t dst_size, const void *src, size_t src_size) const { + return m_impl.DecryptBlock(dst, dst_size, src, src_size); + } + + const u8 *GetRoundKey() const { + return m_impl.GetRoundKey(); + } + }; + + using AesDecryptor128 = AesDecryptor<16>; + using AesDecryptor192 = AesDecryptor<24>; + using AesDecryptor256 = AesDecryptor<32>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_encryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_encryptor.hpp new file mode 100644 index 00000000..d305acc8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_encryptor.hpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_aes_impl.hpp> + +namespace ams::crypto { + + template<size_t _KeySize> + class AesEncryptor { + NON_COPYABLE(AesEncryptor); + NON_MOVEABLE(AesEncryptor); + private: + using Impl = impl::AesImpl<_KeySize>; + public: + static constexpr size_t KeySize = Impl::KeySize; + static constexpr size_t BlockSize = Impl::BlockSize; + static constexpr size_t RoundKeySize = Impl::RoundKeySize; + private: + Impl m_impl; + public: + AesEncryptor() { /* ... */ } + + void Initialize(const void *key, size_t key_size) { + m_impl.Initialize(key, key_size, true); + } + + void EncryptBlock(void *dst, size_t dst_size, const void *src, size_t src_size) const { + return m_impl.EncryptBlock(dst, dst_size, src, src_size); + } + + const u8 *GetRoundKey() const { + return m_impl.GetRoundKey(); + } + }; + + using AesEncryptor128 = AesEncryptor<16>; + using AesEncryptor192 = AesEncryptor<24>; + using AesEncryptor256 = AesEncryptor<32>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_gcm_encryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_gcm_encryptor.hpp new file mode 100644 index 00000000..f93cdfaf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_gcm_encryptor.hpp @@ -0,0 +1,73 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_aes_encryptor.hpp> +#include <vapours/crypto/crypto_gcm_encryptor.hpp> + +namespace ams::crypto { + + namespace impl { + + template<typename _AesImpl> + class AesGcmEncryptor { + NON_COPYABLE(AesGcmEncryptor); + NON_MOVEABLE(AesGcmEncryptor); + private: + using AesImpl = _AesImpl; + using GcmImpl = GcmEncryptor<AesImpl>; + public: + static constexpr size_t KeySize = AesImpl::KeySize; + static constexpr size_t BlockSize = AesImpl::BlockSize; + static constexpr size_t MacSize = AesImpl::BlockSize; + private: + AesImpl m_aes_impl; + GcmImpl m_gcm_impl; + public: + AesGcmEncryptor() { /* ... */ } + + void Initialize(const void *key, size_t key_size, const void *iv, size_t iv_size) { + m_aes_impl.Initialize(key, key_size); + m_gcm_impl.Initialize(std::addressof(m_aes_impl), iv, iv_size); + } + + void Reset(const void *iv, size_t iv_size) { + m_gcm_impl.Reset(iv, iv_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_gcm_impl.Update(dst, dst_size, src, src_size); + } + + void UpdateAad(const void *aad, size_t aad_size) { + return m_gcm_impl.UpdateAad(aad, aad_size); + } + + void GetMac(void *dst, size_t dst_size) { + return m_gcm_impl.GetMac(dst, dst_size); + } + }; + + } + + using Aes128GcmEncryptor = impl::AesGcmEncryptor<AesEncryptor128>; + /* TODO: Validate AAD/GMAC is same for non-128 bit key using Aes192GcmEncryptor = impl::AesGcmEncryptor<AesEncryptor192>; */ + /* TODO: Validate AAD/GMAC is same for non-128 bit key using Aes256GcmEncryptor = impl::AesGcmEncryptor<AesEncryptor256>; */ + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_xts_encryptor_decryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_xts_encryptor_decryptor.hpp new file mode 100644 index 00000000..0a6fd55c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_aes_xts_encryptor_decryptor.hpp @@ -0,0 +1,169 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_aes_encryptor.hpp> +#include <vapours/crypto/crypto_xts_encryptor.hpp> +#include <vapours/crypto/crypto_xts_decryptor.hpp> + +namespace ams::crypto { + + namespace impl { + + template<template<typename> typename _XtsImpl, typename _AesImpl1, typename _AesImpl2> + class AesXtsCryptor { + NON_COPYABLE(AesXtsCryptor); + NON_MOVEABLE(AesXtsCryptor); + private: + using AesImpl1 = _AesImpl1; + using AesImpl2 = _AesImpl2; + using XtsImpl = _XtsImpl<AesImpl1>; + public: + static constexpr size_t KeySize = AesImpl1::KeySize; + static constexpr size_t BlockSize = AesImpl1::BlockSize; + static constexpr size_t IvSize = AesImpl1::BlockSize; + + static_assert(AesImpl1::KeySize == AesImpl2::KeySize); + static_assert(AesImpl1::BlockSize == AesImpl2::BlockSize); + private: + AesImpl1 m_aes_impl_1; + AesImpl2 m_aes_impl_2; + XtsImpl m_xts_impl; + public: + AesXtsCryptor() { /* ... */ } + + void Initialize(const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size) { + AMS_ASSERT(key_size == KeySize); + AMS_ASSERT(iv_size == IvSize); + + m_aes_impl_1.Initialize(key1, key_size); + m_aes_impl_2.Initialize(key2, key_size); + m_xts_impl.Initialize(std::addressof(m_aes_impl_1), std::addressof(m_aes_impl_2), iv, iv_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_xts_impl.Update(dst, dst_size, src, src_size); + } + + size_t Finalize(void *dst, size_t dst_size) { + return m_xts_impl.Finalize(dst, dst_size); + } + }; + + } + + using Aes128XtsEncryptor = impl::AesXtsCryptor<XtsEncryptor, AesEncryptor128, AesEncryptor128>; + using Aes192XtsEncryptor = impl::AesXtsCryptor<XtsEncryptor, AesEncryptor192, AesEncryptor192>; + using Aes256XtsEncryptor = impl::AesXtsCryptor<XtsEncryptor, AesEncryptor256, AesEncryptor256>; + + using Aes128XtsDecryptor = impl::AesXtsCryptor<XtsDecryptor, AesDecryptor128, AesEncryptor128>; + using Aes192XtsDecryptor = impl::AesXtsCryptor<XtsDecryptor, AesDecryptor192, AesEncryptor192>; + using Aes256XtsDecryptor = impl::AesXtsCryptor<XtsDecryptor, AesDecryptor256, AesEncryptor256>; + + inline size_t EncryptAes128Xts(void *dst, size_t dst_size, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + u8 *dst_u8 = static_cast<u8 *>(dst); + const u8 *src_u8 = static_cast<const u8 *>(src); + + Aes128XtsEncryptor xts; + xts.Initialize(key1, key2, key_size, iv, iv_size); + + size_t processed = xts.Update(dst_u8, dst_size, src_u8, src_size); + dst_u8 += processed; + dst_size -= processed; + + processed += xts.Finalize(dst_u8, dst_size); + return processed; + } + + inline size_t EncryptAes192Xts(void *dst, size_t dst_size, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + u8 *dst_u8 = static_cast<u8 *>(dst); + const u8 *src_u8 = static_cast<const u8 *>(src); + + Aes192XtsEncryptor xts; + xts.Initialize(key1, key2, key_size, iv, iv_size); + + size_t processed = xts.Update(dst_u8, dst_size, src_u8, src_size); + dst_u8 += processed; + dst_size -= processed; + + processed += xts.Finalize(dst_u8, dst_size); + return processed; + } + + inline size_t EncryptAes256Xts(void *dst, size_t dst_size, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + u8 *dst_u8 = static_cast<u8 *>(dst); + const u8 *src_u8 = static_cast<const u8 *>(src); + + Aes256XtsEncryptor xts; + xts.Initialize(key1, key2, key_size, iv, iv_size); + + size_t processed = xts.Update(dst_u8, dst_size, src_u8, src_size); + dst_u8 += processed; + dst_size -= processed; + + processed += xts.Finalize(dst_u8, dst_size); + return processed; + } + + inline size_t DecryptAes128Xts(void *dst, size_t dst_size, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + u8 *dst_u8 = static_cast<u8 *>(dst); + const u8 *src_u8 = static_cast<const u8 *>(src); + + Aes128XtsDecryptor xts; + xts.Initialize(key1, key2, key_size, iv, iv_size); + + size_t processed = xts.Update(dst_u8, dst_size, src_u8, src_size); + dst_u8 += processed; + dst_size -= processed; + + processed += xts.Finalize(dst_u8, dst_size); + return processed; + } + + inline size_t DecryptAes192Xts(void *dst, size_t dst_size, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + u8 *dst_u8 = static_cast<u8 *>(dst); + const u8 *src_u8 = static_cast<const u8 *>(src); + + Aes192XtsDecryptor xts; + xts.Initialize(key1, key2, key_size, iv, iv_size); + + size_t processed = xts.Update(dst_u8, dst_size, src_u8, src_size); + dst_u8 += processed; + dst_size -= processed; + + processed += xts.Finalize(dst_u8, dst_size); + return processed; + } + + inline size_t DecryptAes256Xts(void *dst, size_t dst_size, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + u8 *dst_u8 = static_cast<u8 *>(dst); + const u8 *src_u8 = static_cast<const u8 *>(src); + + Aes256XtsDecryptor xts; + xts.Initialize(key1, key2, key_size, iv, iv_size); + + size_t processed = xts.Update(dst_u8, dst_size, src_u8, src_size); + dst_u8 += processed; + dst_size -= processed; + + processed += xts.Finalize(dst_u8, dst_size); + return processed; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_cbc_decryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_cbc_decryptor.hpp new file mode 100644 index 00000000..b9fca26b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_cbc_decryptor.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_cbc_mode_impl.hpp> + +namespace ams::crypto { + + /* TODO: C++20 BlockCipher concept */ + + template<typename BlockCipher> + class CbcDecryptor { + NON_COPYABLE(CbcDecryptor); + NON_MOVEABLE(CbcDecryptor); + private: + using Impl = impl::CbcModeImpl<BlockCipher>; + public: + static constexpr size_t KeySize = Impl::KeySize; + static constexpr size_t BlockSize = Impl::BlockSize; + static constexpr size_t IvSize = Impl::IvSize; + private: + Impl m_impl; + public: + CbcDecryptor() { /* ... */ } + + void Initialize(const BlockCipher *cipher, const void *iv, size_t iv_size) { + m_impl.Initialize(cipher, iv, iv_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_impl.UpdateDecryption(dst, dst_size, src, src_size); + } + + size_t GetBufferedDataSize() const { + return m_impl.GetBufferedDataSize(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_cbc_encryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_cbc_encryptor.hpp new file mode 100644 index 00000000..96c07619 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_cbc_encryptor.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_cbc_mode_impl.hpp> + +namespace ams::crypto { + + /* TODO: C++20 BlockCipher concept */ + + template<typename BlockCipher> + class CbcEncryptor { + NON_COPYABLE(CbcEncryptor); + NON_MOVEABLE(CbcEncryptor); + private: + using Impl = impl::CbcModeImpl<BlockCipher>; + public: + static constexpr size_t KeySize = Impl::KeySize; + static constexpr size_t BlockSize = Impl::BlockSize; + static constexpr size_t IvSize = Impl::IvSize; + private: + Impl m_impl; + public: + CbcEncryptor() { /* ... */ } + + void Initialize(const BlockCipher *cipher, const void *iv, size_t iv_size) { + m_impl.Initialize(cipher, iv, iv_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_impl.UpdateEncryption(dst, dst_size, src, src_size); + } + + size_t GetBufferedDataSize() const { + return m_impl.GetBufferedDataSize(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_ccm_decryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_ccm_decryptor.hpp new file mode 100644 index 00000000..b8b9454d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_ccm_decryptor.hpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_ccm_mode_impl.hpp> + +namespace ams::crypto { + + template<typename BlockCipher> + class CcmDecryptor { + NON_COPYABLE(CcmDecryptor); + NON_MOVEABLE(CcmDecryptor); + private: + using Impl = impl::CcmModeImpl<BlockCipher>; + public: + static constexpr size_t KeySize = Impl::KeySize; + static constexpr size_t BlockSize = Impl::BlockSize; + static constexpr size_t MaxMacSize = BlockSize; + static constexpr size_t MaxNonceSize = 13; + private: + Impl m_impl; + public: + CcmDecryptor() { /* ... */ } + + void Initialize(const BlockCipher *cipher, const void *nonce, size_t nonce_size, s64 aad_size, s64 data_size, size_t mac_size) { + m_impl.Initialize(cipher, nonce, nonce_size, aad_size, data_size, mac_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_impl.UpdateDecryption(dst, dst_size, src, src_size); + } + + void UpdateAad(const void *aad, size_t aad_size) { + return m_impl.UpdateAad(aad, aad_size); + } + + void GetMac(void *dst, size_t dst_size) { + return m_impl.GetMac(dst, dst_size); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_ccm_encryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_ccm_encryptor.hpp new file mode 100644 index 00000000..20704ff6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_ccm_encryptor.hpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_ccm_mode_impl.hpp> + +namespace ams::crypto { + + template<typename BlockCipher> + class CcmEncryptor { + NON_COPYABLE(CcmEncryptor); + NON_MOVEABLE(CcmEncryptor); + private: + using Impl = impl::CcmModeImpl<BlockCipher>; + public: + static constexpr size_t KeySize = Impl::KeySize; + static constexpr size_t BlockSize = Impl::BlockSize; + static constexpr size_t MaxMacSize = BlockSize; + static constexpr size_t MaxNonceSize = 13; + private: + Impl m_impl; + public: + CcmEncryptor() { /* ... */ } + + void Initialize(const BlockCipher *cipher, const void *nonce, size_t nonce_size, s64 aad_size, s64 data_size, size_t mac_size) { + m_impl.Initialize(cipher, nonce, nonce_size, aad_size, data_size, mac_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_impl.UpdateEncryption(dst, dst_size, src, src_size); + } + + void UpdateAad(const void *aad, size_t aad_size) { + return m_impl.UpdateAad(aad, aad_size); + } + + void GetMac(void *dst, size_t dst_size) { + return m_impl.GetMac(dst, dst_size); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_cmac_generator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_cmac_generator.hpp new file mode 100644 index 00000000..052dab42 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_cmac_generator.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_cmac_impl.hpp> + +namespace ams::crypto { + + template<typename BlockCipher> + class CmacGenerator { + NON_COPYABLE(CmacGenerator); + NON_MOVEABLE(CmacGenerator); + private: + using Impl = impl::CmacImpl<BlockCipher>; + public: + static constexpr size_t MacSize = BlockCipher::BlockSize; + private: + Impl m_impl; + public: + CmacGenerator() { /* ... */ } + + void Initialize(const BlockCipher *cipher) { + return m_impl.Initialize(cipher); + } + + void Update(const void *data, size_t size) { + return m_impl.Update(data, size); + } + + void GetMac(void *dst, size_t dst_size) { + return m_impl.GetMac(dst, dst_size); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_csrng.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_csrng.hpp new file mode 100644 index 00000000..40e2bf3c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_csrng.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> + +namespace ams::crypto { + + void GenerateCryptographicallyRandomBytes(void *dst, size_t dst_size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_ctr_decryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_ctr_decryptor.hpp new file mode 100644 index 00000000..f6761e65 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_ctr_decryptor.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_ctr_mode_impl.hpp> + +namespace ams::crypto { + + /* TODO: C++20 BlockCipher concept */ + + template<typename BlockCipher> + class CtrDecryptor { + NON_COPYABLE(CtrDecryptor); + NON_MOVEABLE(CtrDecryptor); + private: + using Impl = impl::CtrModeImpl<BlockCipher>; + public: + static constexpr size_t KeySize = Impl::KeySize; + static constexpr size_t BlockSize = Impl::BlockSize; + static constexpr size_t IvSize = Impl::IvSize; + private: + Impl m_impl; + public: + CtrDecryptor() { /* ... */ } + + void Initialize(const BlockCipher *cipher, const void *iv, size_t iv_size) { + m_impl.Initialize(cipher, iv, iv_size); + } + + void Initialize(const BlockCipher *cipher, const void *iv, size_t iv_size, s64 offset) { + m_impl.Initialize(cipher, iv, iv_size, offset); + } + + void SwitchMessage(const void *iv, size_t iv_size) { + m_impl.SwitchMessage(iv, iv_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_impl.Update(dst, dst_size, src, src_size); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_ctr_encryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_ctr_encryptor.hpp new file mode 100644 index 00000000..a05508d6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_ctr_encryptor.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_ctr_mode_impl.hpp> + +namespace ams::crypto { + + /* TODO: C++20 BlockCipher concept */ + + template<typename BlockCipher> + class CtrEncryptor { + NON_COPYABLE(CtrEncryptor); + NON_MOVEABLE(CtrEncryptor); + private: + using Impl = impl::CtrModeImpl<BlockCipher>; + public: + static constexpr size_t KeySize = Impl::KeySize; + static constexpr size_t BlockSize = Impl::BlockSize; + static constexpr size_t IvSize = Impl::IvSize; + private: + Impl m_impl; + public: + CtrEncryptor() { /* ... */ } + + void Initialize(const BlockCipher *cipher, const void *iv, size_t iv_size) { + m_impl.Initialize(cipher, iv, iv_size); + } + + void Initialize(const BlockCipher *cipher, const void *iv, size_t iv_size, s64 offset) { + m_impl.Initialize(cipher, iv, iv_size, offset); + } + + void SwitchMessage(const void *iv, size_t iv_size) { + m_impl.SwitchMessage(iv, iv_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_impl.Update(dst, dst_size, src, src_size); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_gcm_encryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_gcm_encryptor.hpp new file mode 100644 index 00000000..88bac399 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_gcm_encryptor.hpp @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_gcm_mode_impl.hpp> + +namespace ams::crypto { + + /* TODO: C++20 BlockCipher concept */ + + template<typename BlockCipher> + class GcmEncryptor { + NON_COPYABLE(GcmEncryptor); + NON_MOVEABLE(GcmEncryptor); + private: + using Impl = impl::GcmModeImpl<BlockCipher>; + public: + static constexpr size_t KeySize = Impl::KeySize; + static constexpr size_t BlockSize = Impl::BlockSize; + static constexpr size_t MacSize = Impl::MacSize; + private: + Impl m_impl; + public: + GcmEncryptor() { /* ... */ } + + void Initialize(const BlockCipher *cipher, const void *iv, size_t iv_size) { + m_impl.Initialize(cipher); + m_impl.Reset(iv, iv_size); + } + + void Reset(const void *iv, size_t iv_size) { + m_impl.Reset(iv, iv_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_impl.Update(dst, dst_size, src, src_size); + } + + void UpdateAad(const void *aad, size_t aad_size) { + return m_impl.UpdateAad(aad, aad_size); + } + + void GetMac(void *dst, size_t dst_size) { + return m_impl.GetMac(dst, dst_size); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_hmac_generator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_hmac_generator.hpp new file mode 100644 index 00000000..bfa00f7a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_hmac_generator.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_hmac_impl.hpp> + +namespace ams::crypto { + + template<typename Hash> /* requires HashFunction<Hash> */ + class HmacGenerator { + NON_COPYABLE(HmacGenerator); + NON_MOVEABLE(HmacGenerator); + private: + using Impl = impl::HmacImpl<Hash>; + public: + static constexpr size_t HashSize = Impl::HashSize; + static constexpr size_t BlockSize = Impl::BlockSize; + private: + Impl m_impl; + public: + HmacGenerator() { /* ... */ } + + void Initialize(const void *key, size_t key_size) { + return m_impl.Initialize(key, key_size); + } + + void Update(const void *data, size_t size) { + return m_impl.Update(data, size); + } + + void GetMac(void *dst, size_t dst_size) { + return m_impl.GetMac(dst, dst_size); + } + }; +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_hmac_sha1_generator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_hmac_sha1_generator.hpp new file mode 100644 index 00000000..dbd42f47 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_hmac_sha1_generator.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/crypto/crypto_sha1_generator.hpp> +#include <vapours/crypto/crypto_hmac_generator.hpp> + +namespace ams::crypto { + + using HmacSha1Generator = HmacGenerator<Sha1Generator>; + + void GenerateHmacSha1(void *dst, size_t dst_size, const void *data, size_t data_size, const void *key, size_t key_size); + + ALWAYS_INLINE void GenerateHmacSha1Mac(void *dst, size_t dst_size, const void *data, size_t data_size, const void *key, size_t key_size) { + return GenerateHmacSha1(dst, dst_size, data, data_size, key, key_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_hmac_sha256_generator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_hmac_sha256_generator.hpp new file mode 100644 index 00000000..914f459e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_hmac_sha256_generator.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/crypto/crypto_sha256_generator.hpp> +#include <vapours/crypto/crypto_hmac_generator.hpp> + +namespace ams::crypto { + + using HmacSha256Generator = HmacGenerator<Sha256Generator>; + + void GenerateHmacSha256(void *dst, size_t dst_size, const void *data, size_t data_size, const void *key, size_t key_size); + + ALWAYS_INLINE void GenerateHmacSha256Mac(void *dst, size_t dst_size, const void *data, size_t data_size, const void *key, size_t key_size) { + return GenerateHmacSha256(dst, dst_size, data, data_size, key, key_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_md5_generator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_md5_generator.hpp new file mode 100644 index 00000000..f156fc8e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_md5_generator.hpp @@ -0,0 +1,67 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_md5_impl.hpp> + +namespace ams::crypto { + + class Md5Generator { + NON_COPYABLE(Md5Generator); + NON_MOVEABLE(Md5Generator); + private: + using Impl = impl::Md5Impl; + public: + static constexpr size_t HashSize = Impl::HashSize; + static constexpr size_t BlockSize = Impl::BlockSize; + + static constexpr inline const u8 Asn1Identifier[] = { + 0x30, 0x20, /* Sequence, size 0x20 */ + 0x30, 0x0C, /* Sequence, size 0x0C */ + 0x06, 0x08, /* Object Identifier */ + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, /* MD5 */ + 0x05, 0x00, /* Null */ + 0x04, 0x10, /* Octet string, size 0x10 */ + }; + static constexpr size_t Asn1IdentifierSize = util::size(Asn1Identifier); + private: + Impl m_impl; + public: + Md5Generator() { /* ... */ } + + void Initialize() { + m_impl.Initialize(); + } + + void Update(const void *data, size_t size) { + m_impl.Update(data, size); + } + + void GetHash(void *dst, size_t size) { + m_impl.GetHash(dst, size); + } + }; + + void GenerateMd5(void *dst, size_t dst_size, const void *src, size_t src_size); + + ALWAYS_INLINE void GenerateMd5Hash(void *dst, size_t dst_size, const void *src, size_t src_size) { + return GenerateMd5(dst, dst_size, src, src_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_memory_clear.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_memory_clear.hpp new file mode 100644 index 00000000..e198579f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_memory_clear.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> + +namespace ams::crypto { + + void ClearMemory(void *mem, size_t size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_memory_compare.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_memory_compare.hpp new file mode 100644 index 00000000..153ed27c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_memory_compare.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> + +namespace ams::crypto { + + bool IsSameBytes(const void *lhs, const void *rhs, size_t size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_calculator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_calculator.hpp new file mode 100644 index 00000000..5e8b62cf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_calculator.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_bignum.hpp> + +namespace ams::crypto { + + template<size_t ModulusSize, size_t ExponentSize> + class RsaCalculator { + NON_COPYABLE(RsaCalculator); + NON_MOVEABLE(RsaCalculator); + public: + static constexpr inline size_t RequiredWorkBufferSize = 0x10 * ModulusSize; + private: + impl::StaticBigNum<ModulusSize * BITSIZEOF(u8)> m_modulus; + impl::StaticBigNum<ExponentSize * BITSIZEOF(u8)> m_exponent; + public: + RsaCalculator() { /* ... */ } + ~RsaCalculator() { m_exponent.ClearToZero(); } + + bool Initialize(const void *mod, size_t mod_size, const void *exp, size_t exp_size) { + if (!m_modulus.Import(mod, mod_size) || m_modulus.IsZero()) { + return false; + } + if (!m_exponent.Import(exp, exp_size) || m_exponent.IsZero()) { + return false; + } + return true; + } + + bool ExpMod(void *dst, const void *src, size_t size, void *work_buf, size_t work_buf_size) { + AMS_ASSERT(work_buf_size >= RequiredWorkBufferSize); + + return m_modulus.ExpMod(dst, src, size, m_exponent, static_cast<u32 *>(work_buf), work_buf_size); + } + + bool ExpMod(void *dst, const void *src, size_t size) { + u32 work_buf[RequiredWorkBufferSize / sizeof(u32)]; + return this->ExpMod(dst, src, size, work_buf, sizeof(work_buf)); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_decryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_decryptor.hpp new file mode 100644 index 00000000..fb3a7021 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_decryptor.hpp @@ -0,0 +1,140 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_rsa_calculator.hpp> +#include <vapours/crypto/impl/crypto_rsa_oaep_impl.hpp> + +namespace ams::crypto { + + template<size_t ModulusSize, typename Hash> requires impl::HashFunction<Hash> + class RsaOaepDecryptor { + NON_COPYABLE(RsaOaepDecryptor); + NON_MOVEABLE(RsaOaepDecryptor); + public: + static constexpr size_t HashSize = Hash::HashSize; + static constexpr size_t BlockSize = ModulusSize; + static constexpr size_t MaximumExponentSize = ModulusSize; + static constexpr size_t RequiredWorkBufferSize = RsaCalculator<ModulusSize, MaximumExponentSize>::RequiredWorkBufferSize; + private: + enum class State { + None, + Initialized, + Done, + }; + private: + RsaCalculator<ModulusSize, MaximumExponentSize> m_calculator; + Hash m_hash; + bool m_set_label_digest; + u8 m_label_digest[HashSize]; + State m_state; + public: + RsaOaepDecryptor() : m_set_label_digest(false), m_state(State::None) { std::memset(m_label_digest, 0, sizeof(m_label_digest)); } + + ~RsaOaepDecryptor() { + ClearMemory(m_label_digest, sizeof(m_label_digest)); + } + + bool Initialize(const void *mod, size_t mod_size, const void *exp, size_t exp_size) { + m_hash.Initialize(); + m_set_label_digest = false; + if (m_calculator.Initialize(mod, mod_size, exp, exp_size)) { + m_state = State::Initialized; + return true; + } else { + return false; + } + } + + void UpdateLabel(const void *data, size_t size) { + AMS_ASSERT(m_state == State::Initialized); + + m_hash.Update(data, size); + } + + void SetLabelDigest(const void *digest, size_t digest_size) { + AMS_ASSERT(m_state == State::Initialized); + AMS_ABORT_UNLESS(digest_size == sizeof(m_label_digest)); + + std::memcpy(m_label_digest, digest, digest_size); + m_set_label_digest = true; + } + + size_t Decrypt(void *dst, size_t dst_size, const void *src, size_t src_size) { + AMS_ASSERT(m_state == State::Initialized); + + impl::RsaOaepImpl<Hash> impl; + u8 message[BlockSize]; + ON_SCOPE_EXIT { ClearMemory(message, sizeof(message)); }; + + if (!m_calculator.ExpMod(message, src, src_size)) { + std::memset(dst, 0, dst_size); + return false; + } + + if (!m_set_label_digest) { + m_hash.GetHash(m_label_digest, sizeof(m_label_digest)); + } + + ON_SCOPE_EXIT { m_state = State::Done; }; + + return impl.Decode(dst, dst_size, m_label_digest, sizeof(m_label_digest), message, sizeof(message)); + } + + size_t Decrypt(void *dst, size_t dst_size, const void *src, size_t src_size, void *work_buf, size_t work_buf_size) { + AMS_ASSERT(m_state == State::Initialized); + ON_SCOPE_EXIT { m_state = State::Done; }; + + impl::RsaOaepImpl<Hash> impl; + u8 message[BlockSize]; + ON_SCOPE_EXIT { ClearMemory(message, sizeof(message)); }; + + if (!m_calculator.ExpMod(message, src, src_size, work_buf, work_buf_size)) { + return false; + } + + if (!m_set_label_digest) { + m_hash.GetHash(m_label_digest, sizeof(m_label_digest)); + m_set_label_digest = true; + } + + return impl.Decode(dst, dst_size, m_label_digest, sizeof(m_label_digest), message, sizeof(message)); + } + + static size_t Decrypt(void *dst, size_t dst_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, const void *lab, size_t lab_size) { + RsaOaepDecryptor<ModulusSize, Hash> crypt; + if (!crypt.Initialize(mod, mod_size, exp, exp_size)) { + return 0; + } + crypt.UpdateLabel(lab, lab_size); + return crypt.Decrypt(dst, dst_size, msg, msg_size); + } + + static size_t Decrypt(void *dst, size_t dst_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, const void *lab, size_t lab_size, void *work_buf, size_t work_buf_size) { + RsaOaepDecryptor<ModulusSize, Hash> crypt; + if (!crypt.Initialize(mod, mod_size, exp, exp_size)) { + return 0; + } + crypt.UpdateLabel(lab, lab_size); + return crypt.Decrypt(dst, dst_size, msg, msg_size, work_buf, work_buf_size); + } + + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_encryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_encryptor.hpp new file mode 100644 index 00000000..af673b73 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_encryptor.hpp @@ -0,0 +1,137 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_rsa_calculator.hpp> +#include <vapours/crypto/impl/crypto_rsa_oaep_impl.hpp> + +namespace ams::crypto { + + template<size_t ModulusSize, typename Hash> requires impl::HashFunction<Hash> + class RsaOaepEncryptor { + NON_COPYABLE(RsaOaepEncryptor); + NON_MOVEABLE(RsaOaepEncryptor); + public: + static constexpr size_t HashSize = Hash::HashSize; + static constexpr size_t BlockSize = ModulusSize; + static constexpr size_t MaximumExponentSize = 3; + static constexpr size_t RequiredWorkBufferSize = RsaCalculator<ModulusSize, MaximumExponentSize>::RequiredWorkBufferSize; + private: + enum class State { + None, + Initialized, + Done, + }; + private: + RsaCalculator<ModulusSize, MaximumExponentSize> m_calculator; + Hash m_hash; + bool m_set_label_digest; + u8 m_label_digest[HashSize]; + State m_state; + public: + RsaOaepEncryptor() : m_set_label_digest(false), m_state(State::None) { std::memset(m_label_digest, 0, sizeof(m_label_digest)); } + + ~RsaOaepEncryptor() { + ClearMemory(m_label_digest, sizeof(m_label_digest)); + } + + bool Initialize(const void *mod, size_t mod_size, const void *exp, size_t exp_size) { + m_hash.Initialize(); + m_set_label_digest = false; + if (m_calculator.Initialize(mod, mod_size, exp, exp_size)) { + m_state = State::Initialized; + return true; + } else { + return false; + } + } + + void UpdateLabel(const void *data, size_t size) { + AMS_ASSERT(m_state == State::Initialized); + + m_hash.Update(data, size); + } + + void SetLabelDigest(const void *digest, size_t digest_size) { + AMS_ASSERT(m_state == State::Initialized); + AMS_ABORT_UNLESS(digest_size == sizeof(m_label_digest)); + + std::memcpy(m_label_digest, digest, digest_size); + m_set_label_digest = true; + } + + bool Encrypt(void *dst, size_t dst_size, const void *src, size_t src_size, const void *salt, size_t salt_size) { + AMS_ASSERT(m_state == State::Initialized); + + impl::RsaOaepImpl<Hash> impl; + if (!m_set_label_digest) { + m_hash.GetHash(m_label_digest, sizeof(m_label_digest)); + } + + impl.Encode(dst, dst_size, m_label_digest, sizeof(m_label_digest), src, src_size, salt, salt_size); + + if (!m_calculator.ExpMod(dst, dst, dst_size)) { + std::memset(dst, 0, dst_size); + return false; + } + + m_state = State::Done; + return true; + } + + bool Encrypt(void *dst, size_t dst_size, const void *src, size_t src_size, const void *salt, size_t salt_size, void *work, size_t work_size) { + AMS_ASSERT(m_state == State::Initialized); + + impl::RsaOaepImpl<Hash> impl; + if (!m_set_label_digest) { + m_hash.GetHash(m_label_digest, sizeof(m_label_digest)); + } + + impl.Encode(dst, dst_size, m_label_digest, sizeof(m_label_digest), src, src_size, salt, salt_size); + + if (!m_calculator.ExpMod(dst, dst, dst_size, work, work_size)) { + std::memset(dst, 0, dst_size); + return false; + } + + m_state = State::Done; + return true; + } + + static bool Encrypt(void *dst, size_t dst_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, const void *seed, size_t seed_size, const void *lab, size_t lab_size) { + RsaOaepEncryptor<ModulusSize, Hash> oaep; + if (!oaep.Initialize(mod, mod_size, exp, exp_size)) { + return false; + } + oaep.UpdateLabel(lab, lab_size); + return oaep.Encrypt(dst, dst_size, msg, msg_size, seed, seed_size); + } + + static bool Encrypt(void *dst, size_t dst_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, const void *seed, size_t seed_size, const void *lab, size_t lab_size, void *work, size_t work_size) { + RsaOaepEncryptor<ModulusSize, Hash> oaep; + if (!oaep.Initialize(mod, mod_size, exp, exp_size)) { + return false; + } + oaep.UpdateLabel(lab, lab_size); + return oaep.Encrypt(dst, dst_size, msg, msg_size, seed, seed_size, work, work_size); + } + + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decoder.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decoder.hpp new file mode 100644 index 00000000..0ba8024d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decoder.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_rsa_oaep_impl.hpp> +#include <vapours/crypto/crypto_sha256_generator.hpp> + +namespace ams::crypto { + + inline size_t DecodeRsa2048OaepSha256(void *dst, size_t dst_size, const void *label_digest, size_t label_digest_size, const void *src, size_t src_size) { + constexpr size_t BlockSize = 2048 / BITSIZEOF(u8); + AMS_ABORT_UNLESS(src_size == BlockSize); + + impl::RsaOaepImpl<Sha256Generator> oaep; + u8 enc[BlockSize]; + ON_SCOPE_EXIT { ClearMemory(enc, sizeof(enc)); }; + + std::memcpy(enc, src, src_size); + return oaep.Decode(dst, dst_size, label_digest, label_digest_size, enc, sizeof(enc)); + } + + inline size_t DecodeRsa4096OaepSha256(void *dst, size_t dst_size, const void *label_digest, size_t label_digest_size, const void *src, size_t src_size) { + constexpr size_t BlockSize = 4096 / BITSIZEOF(u8); + AMS_ABORT_UNLESS(src_size == BlockSize); + + impl::RsaOaepImpl<Sha256Generator> oaep; + u8 enc[BlockSize]; + ON_SCOPE_EXIT { ClearMemory(enc, sizeof(enc)); }; + + std::memcpy(enc, src, src_size); + return oaep.Decode(dst, dst_size, label_digest, label_digest_size, enc, sizeof(enc)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decryptor.hpp new file mode 100644 index 00000000..34ef77bb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_decryptor.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_rsa_calculator.hpp> +#include <vapours/crypto/crypto_rsa_oaep_decryptor.hpp> +#include <vapours/crypto/crypto_sha256_generator.hpp> + +namespace ams::crypto { + + namespace impl { + + template<size_t Bits> + using RsaNOaepSha256Decryptor = ::ams::crypto::RsaOaepDecryptor<Bits / BITSIZEOF(u8), ::ams::crypto::Sha256Generator>; + + } + + using Rsa2048OaepSha256Decryptor = ::ams::crypto::impl::RsaNOaepSha256Decryptor<2048>; + using Rsa4096OaepSha256Decryptor = ::ams::crypto::impl::RsaNOaepSha256Decryptor<4096>; + + inline size_t DecryptRsa2048OaepSha256(void *dst, size_t dst_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, const void *lab, size_t lab_size) { + return Rsa2048OaepSha256Decryptor::Decrypt(dst, dst_size, mod, mod_size, exp, exp_size, msg, msg_size, lab, lab_size); + } + + inline size_t DecryptRsa2048OaepSha256(void *dst, size_t dst_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, const void *lab, size_t lab_size, void *work_buf, size_t work_buf_size) { + return Rsa2048OaepSha256Decryptor::Decrypt(dst, dst_size, mod, mod_size, exp, exp_size, msg, msg_size, lab, lab_size, work_buf, work_buf_size); + } + + inline size_t DecryptRsa4096OaepSha256(void *dst, size_t dst_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, const void *lab, size_t lab_size) { + return Rsa4096OaepSha256Decryptor::Decrypt(dst, dst_size, mod, mod_size, exp, exp_size, msg, msg_size, lab, lab_size); + } + + inline size_t DecryptRsa4096OaepSha256(void *dst, size_t dst_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, const void *lab, size_t lab_size, void *work_buf, size_t work_buf_size) { + return Rsa4096OaepSha256Decryptor::Decrypt(dst, dst_size, mod, mod_size, exp, exp_size, msg, msg_size, lab, lab_size, work_buf, work_buf_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_encryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_encryptor.hpp new file mode 100644 index 00000000..4141cd38 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_oaep_sha256_encryptor.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_rsa_calculator.hpp> +#include <vapours/crypto/crypto_rsa_oaep_encryptor.hpp> +#include <vapours/crypto/crypto_sha256_generator.hpp> + +namespace ams::crypto { + + namespace impl { + + template<size_t Bits> + using RsaNOaepSha256Encryptor = ::ams::crypto::RsaOaepEncryptor<Bits / BITSIZEOF(u8), ::ams::crypto::Sha256Generator>; + + } + + using Rsa2048OaepSha256Encryptor = ::ams::crypto::impl::RsaNOaepSha256Encryptor<2048>; + using Rsa4096OaepSha256Encryptor = ::ams::crypto::impl::RsaNOaepSha256Encryptor<4096>; + + inline size_t EncryptRsa2048OaepSha256(void *dst, size_t dst_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, const void *salt, size_t salt_size, const void *lab, size_t lab_size) { + return Rsa2048OaepSha256Encryptor::Encrypt(dst, dst_size, mod, mod_size, exp, exp_size, msg, msg_size, salt, salt_size, lab, lab_size); + } + + inline size_t EncryptRsa2048OaepSha256(void *dst, size_t dst_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, const void *salt, size_t salt_size, const void *lab, size_t lab_size, void *work_buf, size_t work_buf_size) { + return Rsa2048OaepSha256Encryptor::Encrypt(dst, dst_size, mod, mod_size, exp, exp_size, msg, msg_size, salt, salt_size, lab, lab_size, work_buf, work_buf_size); + } + + inline size_t EncryptRsa4096OaepSha256(void *dst, size_t dst_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, const void *salt, size_t salt_size, const void *lab, size_t lab_size) { + return Rsa4096OaepSha256Encryptor::Encrypt(dst, dst_size, mod, mod_size, exp, exp_size, msg, msg_size, salt, salt_size, lab, lab_size); + } + + inline size_t EncryptRsa4096OaepSha256(void *dst, size_t dst_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, const void *salt, size_t salt_size, const void *lab, size_t lab_size, void *work_buf, size_t work_buf_size) { + return Rsa4096OaepSha256Encryptor::Encrypt(dst, dst_size, mod, mod_size, exp, exp_size, msg, msg_size, salt, salt_size, lab, lab_size, work_buf, work_buf_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_pkcs1_sha256_verifier.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_pkcs1_sha256_verifier.hpp new file mode 100644 index 00000000..5e17019d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_pkcs1_sha256_verifier.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_rsa_calculator.hpp> +#include <vapours/crypto/crypto_rsa_pkcs1_verifier.hpp> +#include <vapours/crypto/crypto_sha256_generator.hpp> + +namespace ams::crypto { + + namespace impl { + + template<size_t Bits> + using RsaNPkcs1Sha256Verifier = ::ams::crypto::RsaPkcs1Verifier<Bits / BITSIZEOF(u8), ::ams::crypto::Sha256Generator>; + + } + + using Rsa2048Pkcs1Sha256Verifier = ::ams::crypto::impl::RsaNPkcs1Sha256Verifier<2048>; + using Rsa4096Pkcs1Sha256Verifier = ::ams::crypto::impl::RsaNPkcs1Sha256Verifier<4096>; + + inline bool VerifyRsa2048Pkcs1Sha256(const void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size) { + return Rsa2048Pkcs1Sha256Verifier::Verify(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size); + } + + inline bool VerifyRsa2048Pkcs1Sha256(const void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, void *work_buf, size_t work_buf_size) { + return Rsa2048Pkcs1Sha256Verifier::Verify(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size, work_buf, work_buf_size); + } + + inline bool VerifyRsa4096Pkcs1Sha256(const void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size) { + return Rsa4096Pkcs1Sha256Verifier::Verify(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size); + } + + inline bool VerifyRsa4096Pkcs1Sha256(const void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, void *work_buf, size_t work_buf_size) { + return Rsa4096Pkcs1Sha256Verifier::Verify(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size, work_buf, work_buf_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_pkcs1_verifier.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_pkcs1_verifier.hpp new file mode 100644 index 00000000..dbf0fd19 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_pkcs1_verifier.hpp @@ -0,0 +1,115 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_rsa_calculator.hpp> +#include <vapours/crypto/impl/crypto_rsa_pkcs1_impl.hpp> + +namespace ams::crypto { + + template<size_t _ModulusSize, impl::HashFunction Hash> + class RsaPkcs1Verifier { + NON_COPYABLE(RsaPkcs1Verifier); + NON_MOVEABLE(RsaPkcs1Verifier); + public: + static constexpr size_t HashSize = Hash::HashSize; + static constexpr size_t ModulusSize = _ModulusSize; + static constexpr size_t SignatureSize = ModulusSize; + static constexpr size_t MaximumExponentSize = 3; + static constexpr size_t RequiredWorkBufferSize = RsaCalculator<ModulusSize, MaximumExponentSize>::RequiredWorkBufferSize; + private: + enum class State { + None, + Initialized, + Done, + }; + private: + RsaCalculator<ModulusSize, MaximumExponentSize> m_calculator; + Hash m_hash; + State m_state; + public: + RsaPkcs1Verifier() : m_state(State::None) { /* ... */ } + + bool Initialize(const void *mod, size_t mod_size, const void *exp, size_t exp_size) { + m_hash.Initialize(); + if (m_calculator.Initialize(mod, mod_size, exp, exp_size)) { + m_state = State::Initialized; + return true; + } else { + return false; + } + } + + void Update(const void *data, size_t size) { + AMS_ASSERT(m_state == State::Initialized); + return m_hash.Update(data, size); + } + + bool Verify(const void *signature, size_t size) { + AMS_ASSERT(m_state == State::Initialized); + AMS_ASSERT(size == SignatureSize); + AMS_UNUSED(size); + ON_SCOPE_EXIT { m_state = State::Done; }; + + impl::RsaPkcs1Impl<Hash> impl; + u8 message[SignatureSize]; + + return m_calculator.ExpMod(message, signature, SignatureSize) && impl.CheckPad(message, sizeof(message), std::addressof(m_hash)); + } + + bool Verify(const void *signature, size_t size, void *work_buf, size_t work_buf_size) { + AMS_ASSERT(m_state == State::Initialized); + AMS_ASSERT(size == SignatureSize); + AMS_UNUSED(size); + ON_SCOPE_EXIT { m_state = State::Done; }; + + impl::RsaPkcs1Impl<Hash> impl; + u8 message[SignatureSize]; + + return m_calculator.ExpMod(message, signature, SignatureSize, work_buf, work_buf_size) && impl.CheckPad(message, sizeof(message), std::addressof(m_hash)); + } + + void GetHash(void *dst, size_t dst_size) { + AMS_ASSERT(m_state == State::Done); + + if (m_state == State::Done) { + m_hash.GetHash(dst, dst_size); + } + } + + static bool Verify(const void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size) { + RsaPkcs1Verifier<ModulusSize, Hash> verifier; + if (!verifier.Initialize(mod, mod_size, exp, exp_size)) { + return false; + } + verifier.Update(msg, msg_size); + return verifier.Verify(sig, sig_size); + } + + static bool Verify(const void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, void *work_buf, size_t work_buf_size) { + RsaPkcs1Verifier<ModulusSize, Hash> verifier; + if (!verifier.Initialize(mod, mod_size, exp, exp_size)) { + return false; + } + verifier.Update(msg, msg_size); + return verifier.Verify(sig, sig_size, work_buf, work_buf_size); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_pss_sha256_verifier.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_pss_sha256_verifier.hpp new file mode 100644 index 00000000..8ef1e248 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_pss_sha256_verifier.hpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_rsa_calculator.hpp> +#include <vapours/crypto/crypto_rsa_pss_verifier.hpp> +#include <vapours/crypto/crypto_sha256_generator.hpp> + +namespace ams::crypto { + + namespace impl { + + template<size_t Bits> + using RsaNPssSha256Verifier = ::ams::crypto::RsaPssVerifier<Bits / BITSIZEOF(u8), ::ams::crypto::Sha256Generator>; + + } + + using Rsa2048PssSha256Verifier = ::ams::crypto::impl::RsaNPssSha256Verifier<2048>; + using Rsa4096PssSha256Verifier = ::ams::crypto::impl::RsaNPssSha256Verifier<4096>; + + inline bool VerifyRsa2048PssSha256(const void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size) { + return Rsa2048PssSha256Verifier::Verify(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size); + } + + inline bool VerifyRsa2048PssSha256(const void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, void *work_buf, size_t work_buf_size) { + return Rsa2048PssSha256Verifier::Verify(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size, work_buf, work_buf_size); + } + + inline bool VerifyRsa2048PssSha256WithHash(const void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *hash, size_t hash_size) { + return Rsa2048PssSha256Verifier::VerifyWithHash(sig, sig_size, mod, mod_size, exp, exp_size, hash, hash_size); + } + + inline bool VerifyRsa2048PssSha256WithHash(const void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *hash, size_t hash_size, void *work_buf, size_t work_buf_size) { + return Rsa2048PssSha256Verifier::VerifyWithHash(sig, sig_size, mod, mod_size, exp, exp_size, hash, hash_size, work_buf, work_buf_size); + } + + inline bool VerifyRsa4096PssSha256(const void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size) { + return Rsa4096PssSha256Verifier::Verify(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size); + } + + inline bool VerifyRsa4096PssSha256(const void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, void *work_buf, size_t work_buf_size) { + return Rsa4096PssSha256Verifier::Verify(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size, work_buf, work_buf_size); + } + + inline bool VerifyRsa4096PssSha256WithHash(const void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *hash, size_t hash_size) { + return Rsa4096PssSha256Verifier::VerifyWithHash(sig, sig_size, mod, mod_size, exp, exp_size, hash, hash_size); + } + + inline bool VerifyRsa4096PssSha256WithHash(const void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *hash, size_t hash_size, void *work_buf, size_t work_buf_size) { + return Rsa4096PssSha256Verifier::VerifyWithHash(sig, sig_size, mod, mod_size, exp, exp_size, hash, hash_size, work_buf, work_buf_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_pss_verifier.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_pss_verifier.hpp new file mode 100644 index 00000000..409dc2f6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_rsa_pss_verifier.hpp @@ -0,0 +1,177 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_rsa_calculator.hpp> +#include <vapours/crypto/impl/crypto_rsa_pss_impl.hpp> + +namespace ams::crypto { + + template<size_t _ModulusSize, typename Hash> requires impl::HashFunction<Hash> + class RsaPssVerifier { + NON_COPYABLE(RsaPssVerifier); + NON_MOVEABLE(RsaPssVerifier); + public: + static constexpr size_t HashSize = Hash::HashSize; + static constexpr size_t SaltSize = Hash::HashSize; + static constexpr size_t ModulusSize = _ModulusSize; + static constexpr size_t SignatureSize = ModulusSize; + static constexpr size_t MaximumExponentSize = 3; + static constexpr size_t RequiredWorkBufferSize = RsaCalculator<ModulusSize, MaximumExponentSize>::RequiredWorkBufferSize; + private: + enum class State { + None, + Initialized, + Done, + }; + private: + RsaCalculator<ModulusSize, MaximumExponentSize> m_calculator; + Hash m_hash; + State m_state; + public: + RsaPssVerifier() : m_state(State::None) { /* ... */ } + ~RsaPssVerifier() { } + + bool Initialize(const void *mod, size_t mod_size, const void *exp, size_t exp_size) { + m_hash.Initialize(); + if (m_calculator.Initialize(mod, mod_size, exp, exp_size)) { + m_state = State::Initialized; + return true; + } else { + return false; + } + } + + void Update(const void *data, size_t size) { + AMS_ASSERT(m_state == State::Initialized); + return m_hash.Update(data, size); + } + + bool Verify(const void *signature, size_t size) { + AMS_ASSERT(m_state == State::Initialized); + AMS_ASSERT(size == SignatureSize); + AMS_UNUSED(size); + ON_SCOPE_EXIT { m_state = State::Done; }; + + impl::RsaPssImpl<Hash> impl; + u8 message[SignatureSize]; + ON_SCOPE_EXIT { ClearMemory(message, sizeof(message)); }; + + if (!m_calculator.ExpMod(message, signature, SignatureSize)) { + return false; + } + + u8 calc_hash[Hash::HashSize]; + m_hash.GetHash(calc_hash, sizeof(calc_hash)); + ON_SCOPE_EXIT { ClearMemory(calc_hash, sizeof(calc_hash)); }; + + return impl.Verify(message, sizeof(message), calc_hash, sizeof(calc_hash)); + } + + bool Verify(const void *signature, size_t size, void *work_buf, size_t work_buf_size) { + AMS_ASSERT(m_state == State::Initialized); + AMS_ASSERT(size == SignatureSize); + AMS_UNUSED(size); + ON_SCOPE_EXIT { m_state = State::Done; }; + + impl::RsaPssImpl<Hash> impl; + u8 message[SignatureSize]; + ON_SCOPE_EXIT { ClearMemory(message, sizeof(message)); }; + + if (!m_calculator.ExpMod(message, signature, SignatureSize, work_buf, work_buf_size)) { + return false; + } + + u8 calc_hash[Hash::HashSize]; + m_hash.GetHash(calc_hash, sizeof(calc_hash)); + ON_SCOPE_EXIT { ClearMemory(calc_hash, sizeof(calc_hash)); }; + + return impl.Verify(message, sizeof(message), calc_hash, sizeof(calc_hash)); + } + + bool VerifyWithHash(const void *signature, size_t size, const void *hash, size_t hash_size) { + AMS_ASSERT(m_state == State::Initialized); + AMS_ASSERT(size == SignatureSize); + AMS_UNUSED(size); + ON_SCOPE_EXIT { m_state = State::Done; }; + + impl::RsaPssImpl<Hash> impl; + u8 message[SignatureSize]; + ON_SCOPE_EXIT { ClearMemory(message, sizeof(message)); }; + + if (!m_calculator.ExpMod(message, signature, SignatureSize)) { + return false; + } + + return impl.Verify(message, sizeof(message), static_cast<const u8 *>(hash), hash_size); + } + + bool VerifyWithHash(const void *signature, size_t size, const void *hash, size_t hash_size, void *work_buf, size_t work_buf_size) { + AMS_ASSERT(m_state == State::Initialized); + AMS_ASSERT(size == SignatureSize); + AMS_UNUSED(size); + ON_SCOPE_EXIT { m_state = State::Done; }; + + impl::RsaPssImpl<Hash> impl; + u8 message[SignatureSize]; + ON_SCOPE_EXIT { ClearMemory(message, sizeof(message)); }; + + if (!m_calculator.ExpMod(message, signature, SignatureSize, work_buf, work_buf_size)) { + return false; + } + + return impl.Verify(message, sizeof(message), static_cast<const u8 *>(hash), hash_size); + } + + static bool Verify(const void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size) { + RsaPssVerifier<ModulusSize, Hash> verifier; + if (!verifier.Initialize(mod, mod_size, exp, exp_size)) { + return false; + } + verifier.Update(msg, msg_size); + return verifier.Verify(sig, sig_size); + } + + static bool Verify(const void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *msg, size_t msg_size, void *work_buf, size_t work_buf_size) { + RsaPssVerifier<ModulusSize, Hash> verifier; + if (!verifier.Initialize(mod, mod_size, exp, exp_size)) { + return false; + } + verifier.Update(msg, msg_size); + return verifier.Verify(sig, sig_size, work_buf, work_buf_size); + } + + static bool VerifyWithHash(const void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *hash, size_t hash_size) { + RsaPssVerifier<ModulusSize, Hash> verifier; + if (!verifier.Initialize(mod, mod_size, exp, exp_size)) { + return false; + } + return verifier.VerifyWithHash(sig, sig_size, hash, hash_size); + } + + static bool VerifyWithHash(const void *sig, size_t sig_size, const void *mod, size_t mod_size, const void *exp, size_t exp_size, const void *hash, size_t hash_size, void *work_buf, size_t work_buf_size) { + RsaPssVerifier<ModulusSize, Hash> verifier; + if (!verifier.Initialize(mod, mod_size, exp, exp_size)) { + return false; + } + return verifier.VerifyWithHash(sig, sig_size, hash, hash_size, work_buf, work_buf_size); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_sha1_generator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_sha1_generator.hpp new file mode 100644 index 00000000..eaca09ed --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_sha1_generator.hpp @@ -0,0 +1,67 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_sha1_impl.hpp> + +namespace ams::crypto { + + class Sha1Generator { + NON_COPYABLE(Sha1Generator); + NON_MOVEABLE(Sha1Generator); + private: + using Impl = impl::Sha1Impl; + public: + static constexpr size_t HashSize = Impl::HashSize; + static constexpr size_t BlockSize = Impl::BlockSize; + + static constexpr inline u8 Asn1Identifier[] = { + 0x30, 0x21, /* Sequence, size 0x21 */ + 0x30, 0x09, /* Sequence, size 0x09 */ + 0x06, 0x05, /* Object Identifier */ + 0x2B, 0x0E, 0x03, 0x02, 0x1A, /* SHA-1 */ + 0x05, 0x00, /* Null */ + 0x04, 0x14, /* Octet string, size 0x14 */ + }; + static constexpr size_t Asn1IdentifierSize = util::size(Asn1Identifier); + private: + Impl m_impl; + public: + Sha1Generator() { /* ... */ } + + void Initialize() { + m_impl.Initialize(); + } + + void Update(const void *data, size_t size) { + m_impl.Update(data, size); + } + + void GetHash(void *dst, size_t size) { + m_impl.GetHash(dst, size); + } + }; + + void GenerateSha1(void *dst, size_t dst_size, const void *src, size_t src_size); + + ALWAYS_INLINE void GenerateSha1Hash(void *dst, size_t dst_size, const void *src, size_t src_size) { + return GenerateSha1(dst, dst_size, src, src_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_sha256_generator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_sha256_generator.hpp new file mode 100644 index 00000000..9ea6466b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_sha256_generator.hpp @@ -0,0 +1,101 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_sha256_impl.hpp> +#include <vapours/crypto/impl/crypto_sha256_impl_constexpr.hpp> + +namespace ams::crypto { + + struct Sha256Context { + u32 intermediate_hash[impl::Sha256Impl::HashSize / sizeof(u32)]; + u64 bits_consumed; + }; + + class Sha256Generator { + NON_COPYABLE(Sha256Generator); + NON_MOVEABLE(Sha256Generator); + private: + using Impl = impl::Sha256Impl; + public: + static constexpr size_t HashSize = Impl::HashSize; + static constexpr size_t BlockSize = Impl::BlockSize; + + static constexpr inline u8 Asn1Identifier[] = { + 0x30, 0x31, /* Sequence, size 0x31 */ + 0x30, 0x0D, /* Sequence, size 0x0D */ + 0x06, 0x09, /* Object Identifier */ + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, /* SHA-256 */ + 0x05, 0x00, /* Null */ + 0x04, 0x20, /* Octet string, size 0x20 */ + }; + static constexpr size_t Asn1IdentifierSize = util::size(Asn1Identifier); + private: + Impl m_impl{}; + public: + Sha256Generator() = default; + + void Initialize() { + m_impl.Initialize(); + } + + void Update(const void *data, size_t size) { + m_impl.Update(data, size); + } + + void GetHash(void *dst, size_t size) { + m_impl.GetHash(dst, size); + } + + void InitializeWithContext(const Sha256Context *context) { + m_impl.InitializeWithContext(context); + } + + size_t GetContext(Sha256Context *context) const { + return m_impl.GetContext(context); + } + + size_t GetBufferedDataSize() const { + return m_impl.GetBufferedDataSize(); + } + + void GetBufferedData(void *dst, size_t dst_size) const { + return m_impl.GetBufferedData(dst, dst_size); + } + }; + + void GenerateSha256(void *dst, size_t dst_size, const void *src, size_t src_size); + + ALWAYS_INLINE void GenerateSha256Hash(void *dst, size_t dst_size, const void *src, size_t src_size) { + return GenerateSha256(dst, dst_size, src, src_size); + } + + template<typename T, typename = typename std::enable_if<std::same_as<T, u8> || std::same_as<T, s8> || std::same_as<T, char> || std::same_as<T, unsigned char>>::type> + constexpr ALWAYS_INLINE void GenerateSha256(u8 *dst, size_t dst_size, const T *src, size_t src_size) { + if (std::is_constant_evaluated()) { + impl::Sha256CompileTimeImpl sha; + sha.Initialize(); + sha.Update(src, src_size); + sha.GetHash(dst, dst_size); + } else { + return GenerateSha256(static_cast<void *>(dst), dst_size, static_cast<const void *>(src), src_size); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_sha3_generator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_sha3_generator.hpp new file mode 100644 index 00000000..c2bcee03 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_sha3_generator.hpp @@ -0,0 +1,125 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_sha3_impl.hpp> + +namespace ams::crypto { + + struct Sha3Context { + u32 hash_size; + u32 buffered_bytes; + u64 internal_state[25]; + }; + + namespace impl { + + template<size_t HashSize> + struct Sha3Asn1IdentifierByte; + + template<> struct Sha3Asn1IdentifierByte<224 / BITSIZEOF(u8)> { static constexpr u8 Value = 0x07; }; + template<> struct Sha3Asn1IdentifierByte<256 / BITSIZEOF(u8)> { static constexpr u8 Value = 0x08; }; + template<> struct Sha3Asn1IdentifierByte<384 / BITSIZEOF(u8)> { static constexpr u8 Value = 0x09; }; + template<> struct Sha3Asn1IdentifierByte<512 / BITSIZEOF(u8)> { static constexpr u8 Value = 0x0A; }; + + } + + template <size_t _HashSize> + class Sha3Generator { + NON_COPYABLE(Sha3Generator); + NON_MOVEABLE(Sha3Generator); + private: + using Impl = impl::Sha3Impl<_HashSize>; + public: + static constexpr size_t HashSize = Impl::HashSize; + static constexpr size_t BlockSize = Impl::BlockSize; + + static constexpr inline u8 Asn1Identifier[] = { + 0x30, 0x31, /* Sequence, size 0x31 */ + 0x30, 0x0D, /* Sequence, size 0x0D */ + 0x06, 0x09, /* Object Identifier */ + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, impl::Sha3Asn1IdentifierByte<HashSize>::Value, /* SHA3-*** */ + 0x05, 0x00, /* Null */ + 0x04, 0x20, /* Octet string, size 0x20 */ + }; + static constexpr size_t Asn1IdentifierSize = util::size(Asn1Identifier); + private: + Impl m_impl; + public: + Sha3Generator() { /* ... */ } + + void Initialize() { + m_impl.Initialize(); + } + + void Update(const void *data, size_t size) { + m_impl.Update(data, size); + } + + void GetHash(void *dst, size_t size) { + m_impl.GetHash(dst, size); + } + + void InitializeWithContext(const Sha3Context *context) { + m_impl.InitializeWithContext(context); + } + + void GetContext(Sha3Context *context) const { + m_impl.GetContext(context); + } + }; + + using Sha3224Generator = Sha3Generator<224 / BITSIZEOF(u8)>; + using Sha3256Generator = Sha3Generator<256 / BITSIZEOF(u8)>; + using Sha3384Generator = Sha3Generator<384 / BITSIZEOF(u8)>; + using Sha3512Generator = Sha3Generator<512 / BITSIZEOF(u8)>; + + inline void GenerateSha3224(void *dst, size_t dst_size, const void *src, size_t src_size) { + Sha3224Generator generator; + + generator.Initialize(); + generator.Update(src, src_size); + generator.GetHash(dst, dst_size); + } + + inline void GenerateSha3256(void *dst, size_t dst_size, const void *src, size_t src_size) { + Sha3256Generator generator; + + generator.Initialize(); + generator.Update(src, src_size); + generator.GetHash(dst, dst_size); + } + + inline void GenerateSha3384(void *dst, size_t dst_size, const void *src, size_t src_size) { + Sha3384Generator generator; + + generator.Initialize(); + generator.Update(src, src_size); + generator.GetHash(dst, dst_size); + } + + inline void GenerateSha3512(void *dst, size_t dst_size, const void *src, size_t src_size) { + Sha3512Generator generator; + + generator.Initialize(); + generator.Update(src, src_size); + generator.GetHash(dst, dst_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_xts_decryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_xts_decryptor.hpp new file mode 100644 index 00000000..70b6d560 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_xts_decryptor.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_xts_mode_impl.hpp> + +namespace ams::crypto { + + /* TODO: C++20 BlockCipher concept */ + + template<typename BlockCipher> + class XtsDecryptor { + NON_COPYABLE(XtsDecryptor); + NON_MOVEABLE(XtsDecryptor); + private: + using Impl = impl::XtsModeImpl; + public: + static constexpr size_t BlockSize = Impl::BlockSize; + static constexpr size_t IvSize = Impl::IvSize; + private: + Impl m_impl; + public: + XtsDecryptor() { /* ... */ } + + template<typename BlockCipher2> + void Initialize(const BlockCipher *cipher1, const BlockCipher2 *cipher2, const void *iv, size_t iv_size) { + m_impl.InitializeDecryption(cipher1, cipher2, iv, iv_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_impl.template Update<BlockCipher>(dst, dst_size, src, src_size); + } + + size_t Finalize(void *dst, size_t dst_size) { + return m_impl.FinalizeDecryption(dst, dst_size); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_xts_encryptor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_xts_encryptor.hpp new file mode 100644 index 00000000..9c6511f4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/crypto_xts_encryptor.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_xts_mode_impl.hpp> + +namespace ams::crypto { + + /* TODO: C++20 BlockCipher concept */ + + template<typename BlockCipher> + class XtsEncryptor { + NON_COPYABLE(XtsEncryptor); + NON_MOVEABLE(XtsEncryptor); + private: + using Impl = impl::XtsModeImpl; + public: + static constexpr size_t BlockSize = Impl::BlockSize; + static constexpr size_t IvSize = Impl::IvSize; + private: + Impl m_impl; + public: + XtsEncryptor() { /* ... */ } + + template<typename BlockCipher2> + void Initialize(const BlockCipher *cipher1, const BlockCipher2 *cipher2, const void *iv, size_t iv_size) { + m_impl.InitializeEncryption(cipher1, cipher2, iv, iv_size); + } + + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return m_impl.template Update<BlockCipher>(dst, dst_size, src, src_size); + } + + size_t Finalize(void *dst, size_t dst_size) { + return m_impl.FinalizeEncryption(dst, dst_size); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_aes_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_aes_impl.hpp new file mode 100644 index 00000000..06668777 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_aes_impl.hpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_block_cipher.hpp> + + +namespace ams::crypto::impl { + + template<size_t _KeySize> + class AesImpl { + public: + static constexpr size_t KeySize = _KeySize; + static constexpr size_t BlockSize = 16; + static constexpr s32 RoundCount = (KeySize / 4) + 6; + static constexpr size_t RoundKeySize = BlockSize * (RoundCount + 1); + private: + #ifdef ATMOSPHERE_IS_EXOSPHERE + int m_slot; + #endif + #ifdef ATMOSPHERE_IS_STRATOSPHERE + u32 m_round_keys[RoundKeySize / sizeof(u32)]; + #endif + public: + ~AesImpl(); + + void Initialize(const void *key, size_t key_size, bool is_encrypt); + void EncryptBlock(void *dst, size_t dst_size, const void *src, size_t src_size) const; + void DecryptBlock(void *dst, size_t dst_size, const void *src, size_t src_size) const; + + #ifdef ATMOSPHERE_IS_STRATOSPHERE + const u8 *GetRoundKey() const { + return reinterpret_cast<const u8 *>(m_round_keys); + } + #endif + }; + + static_assert(BlockCipher<AesImpl<16>>); + static_assert(BlockCipher<AesImpl<24>>); + static_assert(BlockCipher<AesImpl<32>>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_bignum.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_bignum.hpp new file mode 100644 index 00000000..fb8dcbd1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_bignum.hpp @@ -0,0 +1,164 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_memory_compare.hpp> +#include <vapours/crypto/crypto_memory_clear.hpp> + +namespace ams::crypto::impl { + + class BigNum { + NON_COPYABLE(BigNum); + NON_MOVEABLE(BigNum); + public: + using HalfWord = u16; + using Word = u32; + using DoubleWord = u64; + + static constexpr size_t MaxBits = 4096; + static constexpr size_t BitsPerWord = sizeof(Word) * CHAR_BIT; + static constexpr Word MaxWord = std::numeric_limits<Word>::max(); + static constexpr Word MaxHalfWord = std::numeric_limits<HalfWord>::max(); + + class WordAllocator { + NON_COPYABLE(WordAllocator); + NON_MOVEABLE(WordAllocator); + public: + class Allocation { + NON_COPYABLE(Allocation); + NON_MOVEABLE(Allocation); + private: + friend class WordAllocator; + private: + WordAllocator *m_allocator; + Word *m_buffer; + size_t m_count; + private: + constexpr ALWAYS_INLINE Allocation(WordAllocator *a, Word *w, size_t c) : m_allocator(a), m_buffer(w), m_count(c) { /* ... */ } + public: + ALWAYS_INLINE ~Allocation() { if (m_allocator) { m_allocator->Free(m_buffer, m_count); } } + + constexpr ALWAYS_INLINE Word *GetBuffer() const { return m_buffer; } + constexpr ALWAYS_INLINE size_t GetCount() const { return m_count; } + constexpr ALWAYS_INLINE bool IsValid() const { return m_buffer != nullptr; } + }; + + friend class Allocation; + private: + Word *m_buffer; + size_t m_count; + size_t m_max_count; + size_t m_min_count; + private: + ALWAYS_INLINE void Free(void *words, size_t num) { + m_buffer -= num; + m_count += num; + + AMS_ASSERT(words == m_buffer); + AMS_UNUSED(words); + } + public: + constexpr ALWAYS_INLINE WordAllocator(Word *buf, size_t c) : m_buffer(buf), m_count(c), m_max_count(c), m_min_count(c) { /* ... */ } + + ALWAYS_INLINE Allocation Allocate(size_t num) { + if (num <= m_count) { + Word *allocated = m_buffer; + + m_buffer += num; + m_count -= num; + m_min_count = std::min(m_count, m_min_count); + + return Allocation(this, allocated, num); + } else { + return Allocation(nullptr, nullptr, 0); + } + } + + constexpr ALWAYS_INLINE size_t GetMaxUsedSize() const { + return (m_max_count - m_min_count) * sizeof(Word); + } + }; + private: + Word *m_words; + size_t m_num_words; + size_t m_max_words; + private: + static void ImportImpl(Word *out, size_t out_size, const u8 *src, size_t src_size); + static void ExportImpl(u8 *out, size_t out_size, const Word *src, size_t src_size); + public: + constexpr BigNum() : m_words(), m_num_words(), m_max_words() { /* ... */ } + ~BigNum() { /* ... */ } + + constexpr void ReserveStatic(Word *buf, size_t capacity) { + m_words = buf; + m_max_words = capacity; + } + + bool Import(const void *src, size_t src_size); + void Export(void *dst, size_t dst_size); + + size_t GetSize() const; + + bool IsZero() const { + return m_num_words == 0; + } + + bool ExpMod(void *dst, const void *src, size_t size, const BigNum &exp, u32 *work_buf, size_t work_buf_size) const; + void ClearToZero(); + void UpdateCount(); + public: + /* Utility. */ + static bool IsZero(const Word *w, size_t num_words); + static int Compare(const Word *lhs, const Word *rhs, size_t num_words); + static size_t CountWords(const Word *w, size_t num_words); + static size_t CountSignificantBits(Word w); + static void ClearToZero(Word *w, size_t num_words); + static void SetToWord(Word *w, size_t num_words, Word v); + static void Copy(Word *dst, const Word *src, size_t num_words); + + /* Arithmetic. */ + static bool ExpMod(Word *dst, const Word *src, const Word *exp, size_t exp_num_words, const Word *mod, size_t mod_num_words, WordAllocator *allocator); + static bool MultMod(Word *dst, const Word *src, const Word *mult, const Word *mod, size_t num_words, WordAllocator *allocator); + static bool Mod(Word *dst, const Word *src, size_t src_words, const Word *mod, size_t mod_words, WordAllocator *allocator); + static bool DivMod(Word *quot, Word *rem, const Word *top, size_t top_words, const Word *bot, size_t bot_words, WordAllocator *allocator); + static bool Mult(Word *dst, const Word *lhs, const Word *rhs, size_t num_words, WordAllocator *allocator); + + static Word LeftShift(Word *dst, const Word *w, size_t num_words, const size_t shift); + static Word RightShift(Word *dst, const Word *w, size_t num_words, const size_t shift); + static Word Add(Word *dst, const Word *lhs, const Word *rhs, size_t num_words); + static Word Sub(Word *dst, const Word *lhs, const Word *rhs, size_t num_words); + static Word MultAdd(Word *dst, const Word *w, size_t num_words, Word mult); + static Word MultSub(Word *dst, const Word *w, const Word *v, size_t num_words, Word mult); + }; + + template<size_t Bits> + class StaticBigNum : public BigNum { + public: + static constexpr size_t NumBits = Bits; + static constexpr size_t NumWords = util::AlignUp(NumBits, BitsPerWord) / BitsPerWord; + static constexpr size_t NumBytes = NumWords * sizeof(Word); + private: + Word m_word_buf[NumWords]; + public: + constexpr StaticBigNum() : m_word_buf() { + this->ReserveStatic(m_word_buf, NumWords); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_block_cipher.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_block_cipher.hpp new file mode 100644 index 00000000..0e6ac760 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_block_cipher.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_memory_compare.hpp> +#include <vapours/crypto/crypto_memory_clear.hpp> + +namespace ams::crypto::impl { + + template<typename T> + concept BlockCipher = requires(T &t, const void *cv, void *v, size_t sz, bool b) { + { T::BlockSize } -> std::convertible_to<size_t>; + { t.EncryptBlock(v, sz, cv, sz) } -> std::same_as<void>; + { t.DecryptBlock(v, sz, cv, sz) } -> std::same_as<void>; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_cbc_mac_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_cbc_mac_impl.hpp new file mode 100644 index 00000000..a8cfdce4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_cbc_mac_impl.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_memory_clear.hpp> +#include <vapours/crypto/crypto_aes_encryptor.hpp> + +namespace ams::crypto::impl { + + class CbcMacImpl { + NON_COPYABLE(CbcMacImpl); + NON_MOVEABLE(CbcMacImpl); + public: + static constexpr size_t BlockSize = 0x10; + private: + enum State { + State_None, + State_Initialized, + State_Done, + }; + private: + u8 m_mac[BlockSize]; + u8 m_buffer[BlockSize]; + size_t m_buffered_bytes; + const void *m_cipher_context; + void (*m_cipher_function)(void *dst, const void *src, const void *ctx); + State m_state; + public: + CbcMacImpl() : m_buffered_bytes(0), m_state(State_None) { /* ... */ } + + ~CbcMacImpl() { + ClearMemory(this, sizeof(*this)); + } + + template<typename BlockCipher> + void Initialize(const BlockCipher *block_cipher) { + static_assert(BlockCipher::BlockSize == BlockSize); + + /* Set our context. */ + m_cipher_context = block_cipher; + m_cipher_function = &EncryptBlockCallback<BlockCipher>; + m_buffered_bytes = 0; + + std::memset(m_mac, 0, sizeof(m_mac)); + + m_state = State_Initialized; + } + + template<typename BlockCipher> + void Update(const void *data, size_t size) { + this->UpdateGeneric(data, size); + } + + template<typename BlockCipher> + void ProcessBlocks(const void *data, size_t size) { + this->ProcessBlocksGeneric(data, size); + } + + size_t GetBlockSize() const { + return BlockSize; + } + + size_t GetBufferedDataSize() const { + return m_buffered_bytes; + } + + void UpdateGeneric(const void *data, size_t size); + void ProcessBlocksGeneric(const void *data, size_t num_blocks); + void ProcessPartialData(const void *data, size_t size); + void ProcessRemainingData(const void *data, size_t size); + + void GetMac(void *mac, size_t mac_size); + void MaskBufferedData(const void *data, size_t size); + private: + void ProcessBlock(const void *data); + + template<typename BlockCipher> + static void EncryptBlockCallback(void *dst, const void *src, const void *cipher) { + static_assert(BlockCipher::BlockSize == BlockSize); + static_cast<const BlockCipher *>(cipher)->EncryptBlock(dst, BlockCipher::BlockSize, src, BlockCipher::BlockSize); + } + }; + + template<> + void CbcMacImpl::Update<AesEncryptor128>(const void *data, size_t size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_cbc_mode_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_cbc_mode_impl.hpp new file mode 100644 index 00000000..eccd232f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_cbc_mode_impl.hpp @@ -0,0 +1,200 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_memory_clear.hpp> +#include <vapours/crypto/crypto_aes_encryptor.hpp> + +namespace ams::crypto::impl { + + template<typename BlockCipher> + class CbcModeImpl { + NON_COPYABLE(CbcModeImpl); + NON_MOVEABLE(CbcModeImpl); + public: + static constexpr size_t KeySize = BlockCipher::KeySize; + static constexpr size_t BlockSize = BlockCipher::BlockSize; + static constexpr size_t IvSize = BlockCipher::BlockSize; + private: + enum State { + State_None, + State_Initialized, + }; + private: + const BlockCipher *m_block_cipher; + u8 m_iv[IvSize]; + u8 m_buffer[BlockSize]; + size_t m_buffered_bytes; + State m_state; + public: + CbcModeImpl() : m_state(State_None) { /* ... */ } + + ~CbcModeImpl() { + ClearMemory(this, sizeof(*this)); + } + + void Initialize(const BlockCipher *block_cipher, const void *iv, size_t iv_size) { + AMS_ASSERT(iv_size == IvSize); + AMS_UNUSED(iv_size); + + m_block_cipher = block_cipher; + std::memcpy(m_iv, iv, IvSize); + m_buffered_bytes = 0; + + m_state = State_Initialized; + } + + size_t GetBufferedDataSize() const { + return m_buffered_bytes; + } + + size_t UpdateEncryption(void *dst, size_t dst_size, const void *src, size_t src_size) { + AMS_ASSERT(dst_size >= ((src_size + this->GetBufferedDataSize()) / BlockSize) * BlockSize); + AMS_ASSERT(m_state == State_Initialized); + + return this->Update(dst, dst_size, src, src_size, [&] (u8 *d, u8 *i, const u8 *s, size_t n) ALWAYS_INLINE_LAMBDA { + this->EncryptBlocks(d, i, s, n); + }); + } + + size_t UpdateDecryption(void *dst, size_t dst_size, const void *src, size_t src_size) { + AMS_ASSERT(dst_size >= ((src_size + this->GetBufferedDataSize()) / BlockSize) * BlockSize); + AMS_ASSERT(m_state == State_Initialized); + + return this->Update(dst, dst_size, src, src_size, [&] (u8 *d, u8 *i, const u8 *s, size_t n) ALWAYS_INLINE_LAMBDA { + this->DecryptBlocks(d, i, s, n); + }); + } + private: + size_t Update(void *_dst, size_t dst_size, const void *_src, size_t src_size, auto ProcessBlocks) { + AMS_UNUSED(dst_size); + + u8 *dst = static_cast<u8 *>(_dst); + const u8 *src = static_cast<const u8 *>(_src); + size_t remaining = src_size; + size_t processed = 0; + + if (m_buffered_bytes > 0) { + const size_t copy_size = std::min(BlockSize - m_buffered_bytes, remaining); + + std::memcpy(m_buffer + m_buffered_bytes, src, copy_size); + src += copy_size; + remaining -= copy_size; + m_buffered_bytes += copy_size; + + if (m_buffered_bytes == BlockSize) { + ProcessBlocks(dst, m_iv, m_buffer, 1); + processed += BlockSize; + dst += BlockSize; + + m_buffered_bytes = 0; + } + } + + if (remaining >= BlockSize) { + const size_t num_blocks = remaining / BlockSize; + + ProcessBlocks(dst, m_iv, src, num_blocks); + + const size_t processed_size = num_blocks * BlockSize; + dst += processed_size; + src += processed_size; + remaining -= processed_size; + processed += processed_size; + } + + if (remaining > 0) { + std::memcpy(m_buffer, src, remaining); + m_buffered_bytes = remaining; + } + + return processed; + } + + void EncryptBlocks(u8 *dst, u8 *iv, const u8 *src, size_t num_blocks) { + const u8 *cur_iv = iv; + + u8 block[BlockSize]; + while (num_blocks--) { + for (size_t i = 0; i < BlockSize; ++i) { + block[i] = src[i] ^ cur_iv[i]; + } + + m_block_cipher->EncryptBlock(dst, BlockSize, block, BlockSize); + + cur_iv = dst; + src += BlockSize; + dst += BlockSize; + } + + if (iv != cur_iv) { + std::memcpy(iv, cur_iv, BlockSize); + } + } + + void DecryptBlocks(u8 *dst, u8 *iv, const u8 *src, size_t num_blocks) { + u8 next_iv[BlockSize]; + std::memcpy(next_iv, src + ((num_blocks - 1) * BlockSize), BlockSize); + + if (src == dst) { + src = src + ((num_blocks - 1) * BlockSize); + dst = dst + ((num_blocks - 1) * BlockSize); + + const u8 *cur_iv = (num_blocks == 1) ? iv : src - BlockSize; + + while (num_blocks-- > 1) { + m_block_cipher->DecryptBlock(dst, BlockSize, src, BlockSize); + + for (size_t i = 0; i < BlockSize; ++i) { + dst[i] ^= cur_iv[i]; + } + + cur_iv -= BlockSize; + src -= BlockSize; + dst -= BlockSize; + } + + m_block_cipher->DecryptBlock(dst, BlockSize, src, BlockSize); + + for (size_t i = 0; i < BlockSize; ++i) { + dst[i] ^= iv[i]; + } + } else { + const u8 *cur_iv = iv; + + while (num_blocks-- > 0) { + m_block_cipher->DecryptBlock(dst, BlockSize, src, BlockSize); + + for (size_t i = 0; i < BlockSize; ++i) { + dst[i] ^= cur_iv[i]; + } + + cur_iv = src; + src += BlockSize; + dst += BlockSize; + } + } + + std::memcpy(iv, next_iv, BlockSize); + } + }; + + /* TODO: Optimized AES cbc impl specializations. */ + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_ccm_mode_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_ccm_mode_impl.hpp new file mode 100644 index 00000000..6c3d7aa8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_ccm_mode_impl.hpp @@ -0,0 +1,274 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_memory_clear.hpp> +#include <vapours/crypto/impl/crypto_ctr_mode_impl.hpp> +#include <vapours/crypto/impl/crypto_cbc_mac_impl.hpp> + +namespace ams::crypto::impl { + + template<typename BlockCipher> + class CcmModeImpl { + NON_COPYABLE(CcmModeImpl); + NON_MOVEABLE(CcmModeImpl); + public: + static constexpr size_t KeySize = BlockCipher::KeySize; + static constexpr size_t BlockSize = BlockCipher::BlockSize; + private: + enum State { + State_None, + State_ProcessingAad, + State_ProcessingData, + State_DataInputDone, + State_Done, + }; + private: + u8 m_mac[BlockSize]; + s64 m_given_data_size; + s64 m_given_aad_size; + size_t m_given_mac_size; + s64 m_processed_data_size; + s64 m_processed_aad_size; + State m_state; + CtrModeImpl<BlockCipher> m_ctr_mode_impl; + CbcMacImpl m_cbc_mac_impl; + public: + CcmModeImpl() : m_state(State_None) { /* ... */ } + + ~CcmModeImpl() { + ClearMemory(this, sizeof(*this)); + } + + void Initialize(const BlockCipher *cipher, const void *nonce, size_t nonce_size, s64 aad_size, s64 data_size, size_t mac_size) { + /* Check pre-conditions. */ + AMS_ASSERT(7 <= nonce_size && nonce_size <= 13); + AMS_ASSERT(4 <= mac_size && mac_size <= 16 && (mac_size % 2) == 0); + AMS_ASSERT(aad_size >= 0); + AMS_ASSERT(data_size >= 0); + if (nonce_size == 7) { + AMS_ASSERT(data_size <= std::numeric_limits<s64>::max()); + } else { + AMS_ASSERT(data_size < (INT64_C(1) << ((15 - nonce_size) * 8))); + } + + /* Set various size fields. */ + m_given_aad_size = aad_size; + m_given_data_size = data_size; + m_given_mac_size = mac_size; + m_processed_aad_size = 0; + m_processed_data_size = 0; + + /* Make the initial counter. */ + u8 tmp[BlockSize]; + MakeInitialCounter(tmp, nonce, nonce_size); + + /* Encrypt the block. */ + cipher->EncryptBlock(m_mac, BlockSize, tmp, BlockSize); + + /* Initialize our ctr mode impl. */ + m_ctr_mode_impl.Initialize(cipher, tmp, BlockSize); + m_ctr_mode_impl.IncrementCounter(); + + /* Make the header block. */ + MakeHeaderBlock(tmp, nonce, nonce_size, aad_size, data_size, mac_size); + + /* Initialize our cbc mac impl. */ + m_cbc_mac_impl.Initialize(cipher); + m_cbc_mac_impl.template Update<BlockCipher>(tmp, BlockSize); + + /* Process aad size block. */ + if (aad_size > 0) { + this->ProcessEncodedAadSize(); + m_state = State_ProcessingAad; + } else { + m_state = State_ProcessingData; + } + } + + void UpdateAad(const void *aad, size_t aad_size) { + /* Check pre-conditions. */ + AMS_ASSERT(m_state == State_ProcessingAad); + AMS_ASSERT(m_processed_aad_size + static_cast<s64>(aad_size) <= m_given_aad_size); + + /* Update on the aad. */ + m_cbc_mac_impl.template Update<BlockCipher>(aad, aad_size); + m_processed_aad_size += aad_size; + + /* Check if we're done with aad. */ + if (m_processed_aad_size == m_given_aad_size) { + /* Pad the aad to block size. */ + this->ProcessPadding(); + + /* Update our state. */ + if (m_given_data_size > 0) { + m_state = State_ProcessingData; + } else { + m_state = State_DataInputDone; + } + } + } + + size_t UpdateEncryption(void *dst, size_t dst_size, const void *src, size_t src_size) { + /* Check pre-conditions. */ + AMS_ASSERT(m_state == State_ProcessingData); + AMS_ASSERT(m_processed_data_size + static_cast<s64>(src_size) <= m_given_data_size); + AMS_ASSERT(dst_size >= src_size); + + /* Update mac on decrypted data. */ + m_cbc_mac_impl.template Update<BlockCipher>(src, src_size); + + /* Encrypt. */ + const size_t processed = m_ctr_mode_impl.Update(dst, dst_size, src, src_size); + m_processed_data_size += src_size; + + /* Check if we're done with data. */ + if (m_processed_data_size == m_given_data_size) { + /* Pad the data to block size. */ + this->ProcessPadding(); + + m_state = State_DataInputDone; + } + + return processed; + } + + size_t UpdateDecryption(void *dst, size_t dst_size, const void *src, size_t src_size) { + /* Check pre-conditions. */ + AMS_ASSERT(m_state == State_ProcessingData); + AMS_ASSERT(m_processed_data_size + static_cast<s64>(src_size) <= m_given_data_size); + AMS_ASSERT(dst_size >= src_size); + + /* Decrypt. */ + const size_t processed = m_ctr_mode_impl.Update(dst, dst_size, src, src_size); + m_processed_data_size += src_size; + + /* Update mac on decrypted data. */ + m_cbc_mac_impl.template Update<BlockCipher>(dst, dst_size); + + /* Check if we're done with data. */ + if (m_processed_data_size == m_given_data_size) { + /* Pad the data to block size. */ + this->ProcessPadding(); + + m_state = State_DataInputDone; + } + + return processed; + } + + void GetMac(void *mac, size_t mac_size) { + /* Check pre-conditions. */ + AMS_ASSERT(m_state == State_DataInputDone || m_state == State_Done); + AMS_ASSERT(mac_size >= m_given_mac_size); + AMS_UNUSED(mac_size); + + /* Generate the mac, if we haven't already. */ + if (m_state == State_DataInputDone) { + this->GenerateMac(); + m_state = State_Done; + } + + /* Copy out the mac. */ + std::memcpy(mac, m_mac, m_given_mac_size); + } + private: + void MakeInitialCounter(void *dst, const void *nonce, size_t nonce_size) { + /* Clear the counter. */ + u8 *ctr = static_cast<u8 *>(dst); + std::memset(ctr, 0, BlockSize); + + /* Set the nonce. */ + ctr[0] = (((BlockSize - 1 - nonce_size) & 0xFF) - 1) & 0x07; + std::memcpy(ctr + 1, nonce, nonce_size); + } + + void MakeHeaderBlock(void *dst, const void *nonce, size_t nonce_size, s64 aad_size, s64 data_size, size_t mac_size) { + /* Clear the block. */ + u8 *hdr = static_cast<u8 *>(dst); + std::memset(hdr, 0, BlockSize); + + /* Encode the flags. */ + hdr[0] = (((BlockSize - 1 - nonce_size) & 0xFF) - 1) & 0x07; + hdr[0] |= (((mac_size - 2) / 2) & 0x07) << 3; + hdr[0] |= (aad_size > 0) ? 0x40 : 0x00; + + /* Encode the data size. */ + for (size_t i = 0; i < sizeof(s64); ++i) { + hdr[BlockSize - 1 - i] = static_cast<u64>(data_size) >> (BITSIZEOF(u8) * i); + } + + /* Copy the nonce. */ + std::memcpy(hdr + 1, nonce, nonce_size); + } + + void ProcessEncodedAadSize() { + u8 encoded_aad[10]; + size_t encoded_aad_size; + + if (m_given_aad_size < ((1 << 16) - (1 << 8))) { + encoded_aad[0] = (m_given_aad_size >> (BITSIZEOF(u8) * 1)) & 0xFF; + encoded_aad[1] = (m_given_aad_size >> (BITSIZEOF(u8) * 0)) & 0xFF; + encoded_aad_size = 2; + } else if (m_given_aad_size <= 0xFFFFFFFFu) { + encoded_aad[0] = 0xFF; + encoded_aad[1] = 0xFE; + encoded_aad[2] = (m_given_aad_size >> (BITSIZEOF(u8) * 3)) & 0xFF; + encoded_aad[3] = (m_given_aad_size >> (BITSIZEOF(u8) * 2)) & 0xFF; + encoded_aad[4] = (m_given_aad_size >> (BITSIZEOF(u8) * 1)) & 0xFF; + encoded_aad[5] = (m_given_aad_size >> (BITSIZEOF(u8) * 0)) & 0xFF; + encoded_aad_size = 6; + } else { + encoded_aad[0] = 0xFF; + encoded_aad[1] = 0xFE; + encoded_aad[2] = (static_cast<u64>(m_given_aad_size) >> (BITSIZEOF(u8) * 7)) & 0xFF; + encoded_aad[3] = (static_cast<u64>(m_given_aad_size) >> (BITSIZEOF(u8) * 6)) & 0xFF; + encoded_aad[4] = (static_cast<u64>(m_given_aad_size) >> (BITSIZEOF(u8) * 5)) & 0xFF; + encoded_aad[5] = (static_cast<u64>(m_given_aad_size) >> (BITSIZEOF(u8) * 4)) & 0xFF; + encoded_aad[6] = (static_cast<u64>(m_given_aad_size) >> (BITSIZEOF(u8) * 3)) & 0xFF; + encoded_aad[7] = (static_cast<u64>(m_given_aad_size) >> (BITSIZEOF(u8) * 2)) & 0xFF; + encoded_aad[8] = (static_cast<u64>(m_given_aad_size) >> (BITSIZEOF(u8) * 1)) & 0xFF; + encoded_aad[9] = (static_cast<u64>(m_given_aad_size) >> (BITSIZEOF(u8) * 0)) & 0xFF; + encoded_aad_size = 10; + } + + m_cbc_mac_impl.template Update<BlockCipher>(encoded_aad, encoded_aad_size); + } + + void ProcessPadding() { + /* Process any remaining padding. */ + if (const auto buffered = m_cbc_mac_impl.GetBufferedDataSize(); buffered > 0) { + u8 zeros[BlockSize] = {}; + m_cbc_mac_impl.template Update<BlockCipher>(zeros, BlockSize - buffered); + } + } + + void GenerateMac() { + /* Get the cbc mac. */ + u8 tmp[BlockSize]; + m_cbc_mac_impl.GetMac(tmp, BlockSize); + + /* Xor into our mac. */ + for (size_t i = 0; i < BlockSize; ++i) { + m_mac[i] ^= tmp[i]; + } + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_cmac_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_cmac_impl.hpp new file mode 100644 index 00000000..fb237540 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_cmac_impl.hpp @@ -0,0 +1,128 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_hash_function.hpp> +#include <vapours/crypto/impl/crypto_cbc_mac_impl.hpp> +#include <vapours/crypto/crypto_memory_clear.hpp> + +namespace ams::crypto::impl { + + template<typename BlockCipher> + class CmacImpl { + NON_COPYABLE(CmacImpl); + NON_MOVEABLE(CmacImpl); + public: + static constexpr size_t BlockSize = BlockCipher::BlockSize; + static constexpr size_t MacSize = BlockSize; + static_assert(BlockSize == 0x10); /* TODO: Should this be supported? */ + private: + enum State { + State_None = 0, + State_Initialized = 1, + State_Done = 2, + }; + private: + CbcMacImpl m_cbc_mac_impl; + u8 m_sub_key[BlockSize]; + State m_state; + public: + CmacImpl() : m_state(State_None) { /* ... */ } + ~CmacImpl() { + /* Clear everything. */ + ClearMemory(this, sizeof(*this)); + } + + void Initialize(const BlockCipher *cipher); + void Update(const void *data, size_t data_size); + void GetMac(void *dst, size_t dst_size); + private: + static void MultiplyOneOverGF128(u8 *data) { + /* Determine the carry bit. */ + const u8 carry = data[0] & 0x80; + + /* Shift all bytes by one bit. */ + for (size_t i = 0; i < BlockSize - 1; ++i) { + data[i] = (data[i] << 1) | (data[i + 1] >> 7); + } + data[BlockSize - 1] <<= 1; + + /* Adjust based on carry. */ + if (carry) { + data[BlockSize - 1] ^= 0x87; + } + } + }; + + template<typename BlockCipher> + inline void CmacImpl<BlockCipher>::Initialize(const BlockCipher *cipher) { + /* Clear the key storage. */ + std::memset(m_sub_key, 0, sizeof(m_sub_key)); + + /* Set the key storage. */ + cipher->EncryptBlock(m_sub_key, BlockSize, m_sub_key, BlockSize); + MultiplyOneOverGF128(m_sub_key); + + /* Initialize the cbc-mac impl. */ + m_cbc_mac_impl.Initialize(cipher); + + /* Mark initialized. */ + m_state = State_Initialized; + } + + template<typename BlockCipher> + inline void CmacImpl<BlockCipher>::Update(const void *data, size_t data_size) { + AMS_ASSERT(m_state == State_Initialized); + + m_cbc_mac_impl.template Update<BlockCipher>(data, data_size); + } + + template<typename BlockCipher> + inline void CmacImpl<BlockCipher>::GetMac(void *dst, size_t dst_size) { + AMS_ASSERT(m_state == State_Initialized || m_state == State_Done); + AMS_ASSERT(dst_size >= MacSize); + AMS_UNUSED(dst_size); + + /* If we're not already finalized, get the final mac. */ + if (m_state == State_Initialized) { + /* Process padding as needed. */ + if (m_cbc_mac_impl.GetBufferedDataSize() != BlockSize) { + /* Determine the remaining size. */ + const size_t remaining = BlockSize - m_cbc_mac_impl.GetBufferedDataSize(); + + /* Update with padding. */ + static constexpr u8 s_padding[BlockSize] = { 0x80, /* ... */ }; + m_cbc_mac_impl.template Update<BlockCipher>(s_padding, remaining); + + /* Update our subkey. */ + MultiplyOneOverGF128(m_sub_key); + } + + /* Mask the subkey. */ + m_cbc_mac_impl.MaskBufferedData(m_sub_key, BlockSize); + + /* Set our state as done. */ + m_state = State_Done; + } + + /* Get the mac. */ + m_cbc_mac_impl.GetMac(dst, dst_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_ctr_mode_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_ctr_mode_impl.hpp new file mode 100644 index 00000000..b1125056 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_ctr_mode_impl.hpp @@ -0,0 +1,184 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_memory_clear.hpp> +#include <vapours/crypto/crypto_aes_encryptor.hpp> + +namespace ams::crypto::impl { + + template<typename BlockCipher> + class CtrModeImpl { + NON_COPYABLE(CtrModeImpl); + NON_MOVEABLE(CtrModeImpl); + public: + static constexpr size_t KeySize = BlockCipher::KeySize; + static constexpr size_t BlockSize = BlockCipher::BlockSize; + static constexpr size_t IvSize = BlockCipher::BlockSize; + private: + enum State { + State_None, + State_Initialized, + }; + private: + const BlockCipher *m_block_cipher; + u8 m_counter[IvSize]; + u8 m_encrypted_counter[BlockSize]; + size_t m_buffer_offset; + State m_state; + public: + CtrModeImpl() : m_state(State_None) { /* ... */ } + + ~CtrModeImpl() { + ClearMemory(this, sizeof(*this)); + } + + void Initialize(const BlockCipher *block_cipher, const void *iv, size_t iv_size) { + this->Initialize(block_cipher, iv, iv_size, 0); + } + + void Initialize(const BlockCipher *block_cipher, const void *iv, size_t iv_size, s64 offset) { + AMS_ASSERT(iv_size == IvSize); + AMS_ASSERT(offset >= 0); + + m_block_cipher = block_cipher; + m_state = State_Initialized; + + this->SwitchMessage(iv, iv_size); + + if (offset >= 0) { + u64 ctr_offset = offset / BlockSize; + if (ctr_offset > 0) { + this->IncrementCounter(ctr_offset); + } + + if (size_t remaining = static_cast<size_t>(offset % BlockSize); remaining != 0) { + m_block_cipher->EncryptBlock(m_encrypted_counter, sizeof(m_encrypted_counter), m_counter, sizeof(m_counter)); + this->IncrementCounter(); + + m_buffer_offset = remaining; + } + } + } + + void SwitchMessage(const void *iv, size_t iv_size) { + AMS_ASSERT(m_state == State_Initialized); + AMS_ASSERT(iv_size == IvSize); + + std::memcpy(m_counter, iv, iv_size); + m_buffer_offset = 0; + } + + void IncrementCounter() { + for (s32 i = IvSize - 1; i >= 0; --i) { + if (++m_counter[i] != 0) { + break; + } + } + } + + size_t Update(void *_dst, size_t dst_size, const void *_src, size_t src_size) { + AMS_ASSERT(m_state == State_Initialized); + AMS_ASSERT(dst_size >= src_size); + AMS_UNUSED(dst_size); + + u8 *dst = static_cast<u8 *>(_dst); + const u8 *src = static_cast<const u8 *>(_src); + size_t remaining = src_size; + + if (m_buffer_offset > 0) { + const size_t xor_size = std::min(BlockSize - m_buffer_offset, remaining); + + const u8 *ctr = m_encrypted_counter + m_buffer_offset; + for (size_t i = 0; i < xor_size; i++) { + dst[i] = src[i] ^ ctr[i]; + } + + src += xor_size; + dst += xor_size; + remaining -= xor_size; + m_buffer_offset += xor_size; + + if (m_buffer_offset == BlockSize) { + m_buffer_offset = 0; + } + } + + if (remaining >= BlockSize) { + const size_t num_blocks = remaining / BlockSize; + + this->ProcessBlocks(dst, src, num_blocks); + + const size_t processed_size = num_blocks * BlockSize; + dst += processed_size; + src += processed_size; + remaining -= processed_size; + } + + if (remaining > 0) { + this->ProcessBlock(dst, src, remaining); + m_buffer_offset = remaining; + } + + return src_size; + } + private: + void IncrementCounter(u64 count) { + u64 _block[IvSize / sizeof(u64)] = {}; + util::StoreBigEndian(std::addressof(_block[(IvSize / sizeof(u64)) - 1]), count); + + u16 acc = 0; + const u8 *block = reinterpret_cast<const u8 *>(_block); + for (s32 i = IvSize - 1; i >= 0; --i) { + acc += (m_counter[i] + block[i]); + m_counter[i] = acc & 0xFF; + acc >>= 8; + } + } + + void ProcessBlock(u8 *dst, const u8 *src, size_t src_size) { + m_block_cipher->EncryptBlock(m_encrypted_counter, BlockSize, m_counter, IvSize); + this->IncrementCounter(); + + for (size_t i = 0; i < src_size; i++) { + dst[i] = src[i] ^ m_encrypted_counter[i]; + } + } + + void ProcessBlocks(u8 *dst, const u8 *src, size_t num_blocks); + }; + + template<typename BlockCipher> + inline void CtrModeImpl<BlockCipher>::ProcessBlocks(u8 *dst, const u8 *src, size_t num_blocks) { + while (num_blocks--) { + this->ProcessBlock(dst, src, BlockSize); + dst += BlockSize; + src += BlockSize; + } + } + + template<> void CtrModeImpl<AesEncryptor128>::ProcessBlocks(u8 *dst, const u8 *src, size_t num_blocks); + + /* TODO: Optimized x64 CTR-192/256? */ + #if defined(ATMOSPHERE_ARCH_ARM64) + template<> void CtrModeImpl<AesEncryptor192>::ProcessBlocks(u8 *dst, const u8 *src, size_t num_blocks); + template<> void CtrModeImpl<AesEncryptor256>::ProcessBlocks(u8 *dst, const u8 *src, size_t num_blocks); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_gcm_mode_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_gcm_mode_impl.hpp new file mode 100644 index 00000000..30bca730 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_gcm_mode_impl.hpp @@ -0,0 +1,106 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_memory_clear.hpp> +#include <vapours/crypto/crypto_aes_encryptor.hpp> + +namespace ams::crypto::impl { + + template<typename BlockCipher> + class GcmModeImpl { + NON_COPYABLE(GcmModeImpl); + NON_MOVEABLE(GcmModeImpl); + public: + static constexpr size_t KeySize = BlockCipher::KeySize; + static constexpr size_t BlockSize = BlockCipher::BlockSize; + static constexpr size_t MacSize = BlockCipher::BlockSize; + private: + enum State { + State_None, + State_Initialized, + State_ProcessingAad, + State_Encrypting, + State_Decrypting, + State_Done, + }; + + struct Block128 { + u64 hi; + u64 lo; + + ALWAYS_INLINE void Clear() { + this->hi = 0; + this->lo = 0; + } + }; + static_assert(util::is_pod<Block128>::value); + static_assert(sizeof(Block128) == 0x10); + + union Block { + Block128 block_128; + u32 block_32[4]; + u8 block_8[16]; + }; + static_assert(util::is_pod<Block>::value); + static_assert(sizeof(Block) == 0x10); + + using CipherFunction = void (*)(void *dst_block, const void *src_block, const void *ctx); + private: + State m_state; + const BlockCipher *m_block_cipher; + CipherFunction m_cipher_func; + u8 m_pad[sizeof(u64)]; + Block m_block_x; + Block m_block_y; + Block m_block_ek; + Block m_block_ek0; + Block m_block_tmp; + size_t m_aad_size; + size_t m_msg_size; + u32 m_aad_remaining; + u32 m_msg_remaining; + u32 m_counter; + Block m_h_mult_blocks[16]; + public: + GcmModeImpl() : m_state(State_None) { /* ... */ } + + ~GcmModeImpl() { + ClearMemory(this, sizeof(*this)); + } + + void Initialize(const BlockCipher *block_cipher); + + void Reset(const void *iv, size_t iv_size); + + void UpdateAad(const void *aad, size_t aad_size); + size_t UpdateEncrypt(void *dst, size_t dst_size, const void *src, size_t src_size); + size_t UpdateDecrypt(void *dst, size_t dst_size, const void *src, size_t src_size); + + void GetMac(void *dst, size_t dst_size); + private: + static void ProcessBlock(void *dst_block, const void *src_block, const void *ctx) { + static_cast<const BlockCipher *>(ctx)->EncryptBlock(dst_block, BlockSize, src_block, BlockSize); + } + + void InitializeHashKey(); + void ComputeMac(bool encrypt); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_hash_function.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_hash_function.hpp new file mode 100644 index 00000000..72cd00a5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_hash_function.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_memory_compare.hpp> +#include <vapours/crypto/crypto_memory_clear.hpp> + +namespace ams::crypto::impl { + + template<typename T> + concept HashFunction = requires(T &t, const void *cv, void *v, size_t sz) { + { T::HashSize } -> std::convertible_to<size_t>; + { T::BlockSize } -> std::convertible_to<size_t>; + { t.Initialize() } -> std::same_as<void>; + { t.Update(cv, sz) } -> std::same_as<void>; + { t.GetHash(v, sz) } -> std::same_as<void>; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_hmac_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_hmac_impl.hpp new file mode 100644 index 00000000..4fc8acab --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_hmac_impl.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_hash_function.hpp> +#include <vapours/crypto/crypto_memory_clear.hpp> + +namespace ams::crypto::impl { + + template<HashFunction Hash> + class HmacImpl { + NON_COPYABLE(HmacImpl); + NON_MOVEABLE(HmacImpl); + public: + static constexpr size_t MacSize = Hash::HashSize; + static constexpr size_t BlockSize = Hash::BlockSize; + private: + static constexpr u32 IpadMagic = 0x36363636; + static constexpr u32 OpadMagic = 0x5c5c5c5c; + + static constexpr u32 IpadMagicXorOpadMagic = IpadMagic ^ OpadMagic; + static_assert(IpadMagicXorOpadMagic == 0x6a6a6a6a); + private: + enum State { + State_None = 0, + State_Initialized = 1, + State_Done = 2, + }; + private: + Hash m_hash_function; + u32 m_key[BlockSize / sizeof(u32)]; + u32 m_mac[MacSize / sizeof(u32)]; + State m_state; + public: + HmacImpl() : m_state(State_None) { /* ... */ } + ~HmacImpl() { + static_assert(AMS_OFFSETOF(HmacImpl, m_hash_function) == 0); + + /* Clear everything except for the hash function. */ + ClearMemory(reinterpret_cast<u8 *>(this) + sizeof(m_hash_function), sizeof(*this) - sizeof(m_hash_function)); + } + + void Initialize(const void *key, size_t key_size); + void Update(const void *data, size_t data_size); + void GetMac(void *dst, size_t dst_size); + }; + + template<HashFunction Hash> + inline void HmacImpl<Hash>::Initialize(const void *key, size_t key_size) { + /* Clear the key storage. */ + std::memset(m_key, 0, sizeof(m_key)); + + /* Set the key storage. */ + if (key_size > BlockSize) { + m_hash_function.Initialize(); + m_hash_function.Update(key, key_size); + m_hash_function.GetHash(m_key, m_hash_function.HashSize); + } else { + std::memcpy(m_key, key, key_size); + } + + /* Xor the key with the ipad. */ + for (size_t i = 0; i < util::size(m_key); i++) { + m_key[i] ^= IpadMagic; + } + + /* Update the hash function with the xor'd key. */ + m_hash_function.Initialize(); + m_hash_function.Update(m_key, BlockSize); + + /* Mark initialized. */ + m_state = State_Initialized; + } + + template<HashFunction Hash> + inline void HmacImpl<Hash>::Update(const void *data, size_t data_size) { + AMS_ASSERT(m_state == State_Initialized); + + m_hash_function.Update(data, data_size); + } + + template<HashFunction Hash> + inline void HmacImpl<Hash>::GetMac(void *dst, size_t dst_size) { + AMS_ASSERT(m_state == State_Initialized || m_state == State_Done); + AMS_ASSERT(dst_size >= MacSize); + AMS_UNUSED(dst_size); + + /* If we're not already finalized, get the final mac. */ + if (m_state == State_Initialized) { + /* Get the hash of ((key ^ ipad) || data). */ + m_hash_function.GetHash(m_mac, MacSize); + + /* Xor the key with the opad. */ + for (size_t i = 0; i < util::size(m_key); i++) { + m_key[i] ^= IpadMagicXorOpadMagic; + } + + /* Calculate the final mac as hash of ((key ^ opad) || hash((key ^ ipad) || data)) */ + m_hash_function.Initialize(); + m_hash_function.Update(m_key, BlockSize); + m_hash_function.Update(m_mac, MacSize); + m_hash_function.GetHash(m_mac, MacSize); + + /* Set our state as done. */ + m_state = State_Done; + } + + std::memcpy(dst, m_mac, MacSize); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_md5_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_md5_impl.hpp new file mode 100644 index 00000000..4d37abf7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_md5_impl.hpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_hash_function.hpp> +#include <vapours/crypto/crypto_memory_clear.hpp> + + +namespace ams::crypto::impl { + + class Md5Impl { + public: + static constexpr size_t HashSize = 0x10; + static constexpr size_t BlockSize = 0x40; + private: + enum State { + State_None = 0, + State_Initialized = 1, + State_Done = 2, + }; + private: + union { + struct { + u32 a, b, c, d; + } p; + u32 state[4]; + } m_x; + alignas(8) u8 m_y[BlockSize]; + size_t m_size; + State m_state; + public: + Md5Impl() : m_state(State_None) { /* ... */ } + ~Md5Impl() { ClearMemory(this, sizeof(*this)); } + + void Initialize(); + void Update(const void *data, size_t size); + void GetHash(void *dst, size_t size); + private: + void ProcessBlock(); + void ProcessLastBlock(); + }; + + static_assert(HashFunction<Md5Impl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_oaep_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_oaep_impl.hpp new file mode 100644 index 00000000..b45ac962 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_oaep_impl.hpp @@ -0,0 +1,163 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_hash_function.hpp> + +namespace ams::crypto::impl { + + template<HashFunction Hash> + class RsaOaepImpl { + NON_COPYABLE(RsaOaepImpl); + NON_MOVEABLE(RsaOaepImpl); + public: + static constexpr size_t HashSize = Hash::HashSize; + private: + static constexpr u8 HeadMagic = 0x00; + private: + static void ComputeHashWithPadding(void *dst, Hash *hash, const void *salt, size_t salt_size) { + /* Initialize our buffer. */ + u8 buf[8 + HashSize]; + std::memset(buf, 0, 8); + hash->GetHash(buf + 8, HashSize); + ON_SCOPE_EXIT { ClearMemory(buf, sizeof(buf)); }; + + + /* Calculate our hash. */ + hash->Initialize(); + hash->Update(buf, sizeof(buf)); + hash->Update(salt, salt_size); + hash->GetHash(dst, HashSize); + } + + static void ApplyMGF1(u8 *dst, size_t dst_size, const void *src, size_t src_size) { + u8 buf[HashSize]; + ON_SCOPE_EXIT { ClearMemory(buf, sizeof(buf)); }; + + const size_t required_iters = (dst_size + HashSize - 1) / HashSize; + for (size_t i = 0; i < required_iters; i++) { + Hash hash; + hash.Initialize(); + hash.Update(src, src_size); + + const u32 tmp = util::ConvertToBigEndian(static_cast<u32>(i)); + hash.Update(std::addressof(tmp), sizeof(tmp)); + + hash.GetHash(buf, HashSize); + + const size_t start = HashSize * i; + const size_t end = std::min(dst_size, start + HashSize); + for (size_t j = start; j < end; j++) { + dst[j] ^= buf[j - start]; + } + } + } + public: + RsaOaepImpl() { /* ... */ } + + void Encode(void *dst, size_t dst_size, Hash *hash, const void *src, size_t src_size, const void *salt, size_t salt_size) { + u8 label_digest[HashSize]; + ON_SCOPE_EXIT { ClearMemory(label_digest, HashSize); }; + + hash->GetHash(label_digest, HashSize); + return this->Encode(dst, dst_size, label_digest, sizeof(label_digest), src, src_size, salt, salt_size); + } + + void Encode(void *dst, size_t dst_size, const void *label_digest, size_t label_digest_size, const void *src, size_t src_size, const void *salt, size_t salt_size) { + /* Check our preconditions. */ + AMS_ASSERT(dst_size >= 2 * HashSize + 2 + src_size); + AMS_ASSERT(salt_size > 0); + AMS_ASSERT(salt_size == HashSize); + AMS_ASSERT(label_digest_size == HashSize); + AMS_UNUSED(salt_size, label_digest_size); + + u8 *buf = static_cast<u8 *>(dst); + buf[0] = HeadMagic; + + u8 *seed = buf + 1; + std::memcpy(seed, salt, HashSize); + + u8 *db = seed + HashSize; + std::memcpy(db, label_digest, HashSize); + std::memset(db + HashSize, 0, dst_size - 2 * HashSize - 2 - src_size); + + u8 *msg = buf + dst_size - src_size - 1; + *(msg++) = 0x01; + std::memcpy(msg, src, src_size); + + ApplyMGF1(db, dst_size - (1 + HashSize), seed, HashSize); + ApplyMGF1(seed, HashSize, db, dst_size - (1 + HashSize)); + } + + size_t Decode(void *dst, size_t dst_size, const void *label_digest, size_t label_digest_size, u8 *buf, size_t buf_size) { + /* Check our preconditions. */ + AMS_ABORT_UNLESS(dst_size > 0); + AMS_ABORT_UNLESS(buf_size >= 2 * HashSize + 3); + AMS_ABORT_UNLESS(label_digest_size == HashSize); + AMS_UNUSED(label_digest_size); + + /* Validate sanity byte. */ + bool is_valid = buf[0] == HeadMagic; + + /* Decrypt seed and masked db. */ + size_t db_len = buf_size - HashSize - 1; + u8 *seed = buf + 1; + u8 *db = seed + HashSize; + ApplyMGF1(seed, HashSize, db, db_len); + ApplyMGF1(db, db_len, seed, HashSize); + + /* Check the label digest. */ + is_valid &= IsSameBytes(label_digest, db, HashSize); + + /* Skip past the label digest. */ + db += HashSize; + db_len -= HashSize; + + /* Verify that DB is of the form 0000...0001 < message > */ + s32 msg_ofs = 0; + { + int looking_for_one = 1; + int invalid_db_padding = 0; + int is_zero; + int is_one; + for (size_t i = 0; i < db_len; /* ... */) { + is_zero = (db[i] == 0); + is_one = (db[i] == 1); + msg_ofs += (looking_for_one & is_one) * (static_cast<s32>(++i)); + looking_for_one &= ~is_one; + invalid_db_padding |= (looking_for_one & ~is_zero); + } + + is_valid &= (invalid_db_padding == 0); + } + + /* If we're invalid, return zero size. */ + const size_t valid_msg_size = db_len - msg_ofs; + const size_t msg_size = std::min(dst_size, static_cast<size_t>(is_valid) * valid_msg_size); + + /* Copy to output. */ + std::memcpy(dst, db + msg_ofs, msg_size); + + /* Return copied size. */ + return msg_size; + } + + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_pkcs1_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_pkcs1_impl.hpp new file mode 100644 index 00000000..20a4154c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_pkcs1_impl.hpp @@ -0,0 +1,94 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_hash_function.hpp> + +namespace ams::crypto::impl { + + template<HashFunction Hash> + class RsaPkcs1Impl { + NON_COPYABLE(RsaPkcs1Impl); + NON_MOVEABLE(RsaPkcs1Impl); + public: + static constexpr size_t HashSize = Hash::HashSize; + public: + RsaPkcs1Impl() { /* ... */ } + ~RsaPkcs1Impl() { /* ... */ } + + void BuildPad(void *out_block, size_t block_size, Hash *hash) { + AMS_ASSERT(block_size >= 2 + 1 + sizeof(Hash::Asn1Identifier) + HashSize); + + u8 *dst = static_cast<u8 *>(out_block); + *(dst++) = 0x00; + *(dst++) = 0x01; + + const size_t pad_len = block_size - (2 + 1 + sizeof(Hash::Asn1Identifier) + HashSize); + std::memset(dst, 0xFF, pad_len); + dst += pad_len; + + *(dst++) = 0x00; + + std::memcpy(dst, Hash::Asn1Identifier, sizeof(Hash::Asn1Identifier)); + dst += sizeof(Hash::Asn1Identifier); + + hash->GetHash(dst, HashSize); + } + + bool CheckPad(const u8 *src, size_t block_size, Hash *hash) { + /* Check that block size is minimally big enough. */ + if (block_size < 2 + 1 + sizeof(Hash::Asn1Identifier) + HashSize) { + return false; + } + + /* Check that the padding if correctly of form 0001FF..FF00 */ + if (*(src++) != 0x00) { + return false; + } + if (*(src++) != 0x01) { + return false; + } + + const size_t pad_len = block_size - (2 + 1 + sizeof(Hash::Asn1Identifier) + HashSize); + for (size_t i = 0; i < pad_len; ++i) { + if (*(src++) != 0xFF) { + return false; + } + } + + if (*(src++) != 0x00) { + return false; + } + + /* Check that the asn1 identifier matches. */ + if (std::memcmp(src, Hash::Asn1Identifier, sizeof(Hash::Asn1Identifier)) != 0) { + return false; + } + + src += sizeof(Hash::Asn1Identifier); + + /* Check the hash. */ + u8 calc_hash[HashSize]; + hash->GetHash(calc_hash, sizeof(calc_hash)); + + return std::memcmp(calc_hash, src, HashSize) == 0; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_pss_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_pss_impl.hpp new file mode 100644 index 00000000..73f7ada9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_rsa_pss_impl.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_hash_function.hpp> + +namespace ams::crypto::impl { + + template<HashFunction Hash> + class RsaPssImpl { + NON_COPYABLE(RsaPssImpl); + NON_MOVEABLE(RsaPssImpl); + public: + static constexpr size_t HashSize = Hash::HashSize; + private: + static constexpr u8 TailMagic = 0xBC; + private: + static void ComputeHashWithPadding(void *dst, const u8 *user_hash, size_t user_hash_size, const void *salt, size_t salt_size) { + AMS_ASSERT(user_hash_size == HashSize); + AMS_UNUSED(user_hash_size); + + /* Initialize our buffer. */ + u8 buf[8 + HashSize]; + std::memset(buf, 0, 8); + std::memcpy(buf + 8, user_hash, HashSize); + ON_SCOPE_EXIT { ClearMemory(buf, sizeof(buf)); }; + + + /* Calculate our hash. */ + Hash hash; + hash.Initialize(); + hash.Update(buf, sizeof(buf)); + hash.Update(salt, salt_size); + hash.GetHash(dst, HashSize); + } + + static void ApplyMGF1(u8 *dst, size_t dst_size, const void *src, size_t src_size) { + u8 buf[HashSize]; + ON_SCOPE_EXIT { ClearMemory(buf, sizeof(buf)); }; + + const size_t required_iters = (dst_size + HashSize - 1) / HashSize; + for (size_t i = 0; i < required_iters; i++) { + Hash hash; + hash.Initialize(); + hash.Update(src, src_size); + + const u32 tmp = util::ConvertToBigEndian(static_cast<u32>(i)); + hash.Update(std::addressof(tmp), sizeof(tmp)); + + hash.GetHash(buf, HashSize); + + const size_t start = HashSize * i; + const size_t end = std::min(dst_size, start + HashSize); + for (size_t j = start; j < end; j++) { + dst[j] ^= buf[j - start]; + } + } + } + public: + RsaPssImpl() { /* ... */ } + + bool Verify(u8 *buf, size_t size, const u8 *hash, size_t hash_size) { + /* Validate sanity byte. */ + bool is_valid = buf[size - 1] == TailMagic; + + /* Decrypt maskedDB */ + const size_t db_len = size - HashSize - 1; + u8 *db = buf; + u8 *h = db + db_len; + ApplyMGF1(db, db_len, h, HashSize); + + /* Apply lmask. */ + db[0] &= 0x7F; + + /* Verify that DB is of the form 0000...0001 */ + s32 salt_ofs = 0; + { + int looking_for_one = 1; + int invalid_db_padding = 0; + int is_zero; + int is_one; + for (size_t i = 0; i < db_len; /* ... */) { + is_zero = (db[i] == 0); + is_one = (db[i] == 1); + salt_ofs += (looking_for_one & is_one) * (static_cast<s32>(++i)); + looking_for_one &= ~is_one; + invalid_db_padding |= (looking_for_one & ~is_zero); + } + + is_valid &= (invalid_db_padding == 0); + } + + /* Verify salt. */ + const u8 *salt = db + salt_ofs; + const size_t salt_size = db_len - salt_ofs; + is_valid &= (salt_size != 0); + is_valid &= (salt_size != db_len); + + /* Verify hash. */ + u8 cmp_hash[HashSize]; + ON_SCOPE_EXIT { ClearMemory(cmp_hash, sizeof(cmp_hash)); }; + + ComputeHashWithPadding(cmp_hash, hash, hash_size, salt, salt_size); + is_valid &= IsSameBytes(cmp_hash, h, HashSize); + + /* Succeed if all our checks succeeded. */ + return is_valid; + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_sha1_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_sha1_impl.hpp new file mode 100644 index 00000000..4404c824 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_sha1_impl.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_hash_function.hpp> +#include <vapours/crypto/crypto_memory_clear.hpp> + + +namespace ams::crypto::impl { + + class Sha1Impl { + public: + static constexpr size_t HashSize = 0x14; + static constexpr size_t BlockSize = 0x40; + private: + enum State { + State_None, + State_Initialized, + State_Done, + }; + private: + u32 m_intermediate_hash[HashSize / sizeof(u32)]; + u8 m_buffer[BlockSize]; + size_t m_buffered_bytes; + u64 m_bits_consumed; + State m_state; + public: + Sha1Impl() : m_state(State_None) { /* ... */ } + ~Sha1Impl() { + ClearMemory(this, sizeof(*this)); + } + + void Initialize(); + void Update(const void *data, size_t size); + void GetHash(void *dst, size_t size); + private: + void ProcessBlock(const void *data); + void ProcessBlocks(const u8 *data, size_t block_count); + void ProcessLastBlock(); + }; + + static_assert(HashFunction<Sha1Impl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_sha256_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_sha256_impl.hpp new file mode 100644 index 00000000..663121df --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_sha256_impl.hpp @@ -0,0 +1,77 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_hash_function.hpp> +#include <vapours/crypto/crypto_memory_clear.hpp> + +namespace ams::crypto { + + struct Sha256Context; + +} + +namespace ams::crypto::impl { + + class Sha256Impl { + public: + static constexpr size_t HashSize = 0x20; + static constexpr size_t BlockSize = 0x40; + private: + enum State { + State_None, + State_Initialized, + State_Done, + }; + private: + u32 m_intermediate_hash[HashSize / sizeof(u32)]; + u8 m_buffer[BlockSize]; + size_t m_buffered_bytes; + u64 m_bits_consumed; + State m_state; + public: + Sha256Impl() : m_state(State_None) { /* ... */ } + ~Sha256Impl() { + ClearMemory(this, sizeof(*this)); + } + + void Initialize(); + void Update(const void *data, size_t size); + void GetHash(void *dst, size_t size); + + void InitializeWithContext(const Sha256Context *context); + size_t GetContext(Sha256Context *context) const; + + size_t GetBufferedDataSize() const { return m_buffered_bytes; } + + void GetBufferedData(void *dst, size_t dst_size) const { + AMS_ASSERT(dst_size >= this->GetBufferedDataSize()); + AMS_UNUSED(dst_size); + + std::memcpy(dst, m_buffer, m_buffered_bytes); + } + private: + void ProcessBlock(const void *data); + void ProcessBlocks(const u8 *data, size_t block_count); + void ProcessLastBlock(); + }; + + static_assert(HashFunction<Sha256Impl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_sha256_impl_constexpr.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_sha256_impl_constexpr.hpp new file mode 100644 index 00000000..99bb41a8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_sha256_impl_constexpr.hpp @@ -0,0 +1,296 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> + +namespace ams::crypto::impl { + + class Sha256CompileTimeImpl { + public: + static constexpr size_t HashSize = 0x20; + static constexpr size_t BlockSize = 0x40; + private: + enum State { + State_None, + State_Initialized, + State_Done, + }; + private: + u32 m_intermediate_hash[HashSize / sizeof(u32)]; + u8 m_buffer[BlockSize]; + size_t m_buffered_bytes; + u64 m_bits_consumed; + State m_state; + public: + constexpr Sha256CompileTimeImpl() : m_intermediate_hash(), m_buffer(), m_buffered_bytes(), m_bits_consumed(), m_state(State_None) { + /* ... */ + } + + constexpr void Initialize() { + /* Reset buffered bytes/bits. */ + m_buffered_bytes = 0; + m_bits_consumed = 0; + + /* Set intermediate hash. */ + m_intermediate_hash[0] = 0x6A09E667; + m_intermediate_hash[1] = 0xBB67AE85; + m_intermediate_hash[2] = 0x3C6EF372; + m_intermediate_hash[3] = 0xA54FF53A; + m_intermediate_hash[4] = 0x510E527F; + m_intermediate_hash[5] = 0x9B05688C; + m_intermediate_hash[6] = 0x1F83D9AB; + m_intermediate_hash[7] = 0x5BE0CD19; + + /* Set state. */ + m_state = State_Initialized; + } + + template<typename T, typename = typename std::enable_if<std::same_as<T, u8> || std::same_as<T, s8> || std::same_as<T, char> || std::same_as<T, unsigned char>>::type> + constexpr void Update(const T *data, size_t size) { + static_assert(sizeof(T) == 1); + + /* Verify we're in a state to update. */ + AMS_ASSERT(m_state == State_Initialized); + + /* Advance our input bit count. */ + m_bits_consumed += BITSIZEOF(u8) * (((m_buffered_bytes + size) / BlockSize) * BlockSize); + + /* Process anything we have buffered. */ + size_t remaining = size; + + if (m_buffered_bytes > 0) { + const size_t copy_size = std::min(BlockSize - m_buffered_bytes, remaining); + for (size_t i = 0; i < copy_size; ++i) { + m_buffer[m_buffered_bytes + i] = static_cast<u8>(data[i]); + } + + data += copy_size; + remaining -= copy_size; + m_buffered_bytes += copy_size; + + /* Process a block, if we filled one. */ + if (m_buffered_bytes == BlockSize) { + this->ProcessBlock(m_buffer); + m_buffered_bytes = 0; + } + } + + /* Process blocks, if we have any. */ + while (remaining >= BlockSize) { + u8 block[BlockSize] = {}; + for (size_t i = 0; i < BlockSize; ++i) { + block[i] = static_cast<u8>(data[i]); + } + this->ProcessBlock(block); + + data += BlockSize; + remaining -= BlockSize; + } + + /* Copy any leftover data to our buffer. */ + if (remaining > 0) { + m_buffered_bytes = remaining; + for (size_t i = 0; i < remaining; ++i) { + m_buffer[i] = static_cast<u8>(data[i]); + } + } + } + + constexpr void GetHash(u8 *dst, size_t size) { + /* Verify we're in a state to get hash. */ + AMS_ASSERT(m_state == State_Initialized || m_state == State_Done); + AMS_ASSERT(size >= HashSize); + AMS_UNUSED(size); + + /* If we need to, process the last block. */ + if (m_state == State_Initialized) { + this->ProcessLastBlock(); + m_state = State_Done; + } + + /* Copy the output hash. */ + for (size_t i = 0; i < HashSize / sizeof(u32); ++i) { + const u32 v = m_intermediate_hash[i]; + + dst[sizeof(u32) * i + 3] = static_cast<u8>(v >> (BITSIZEOF(u8) * 0)); + dst[sizeof(u32) * i + 2] = static_cast<u8>(v >> (BITSIZEOF(u8) * 1)); + dst[sizeof(u32) * i + 1] = static_cast<u8>(v >> (BITSIZEOF(u8) * 2)); + dst[sizeof(u32) * i + 0] = static_cast<u8>(v >> (BITSIZEOF(u8) * 3)); + } + } + + constexpr size_t GetBufferedDataSize() const { return m_buffered_bytes; } + + constexpr void GetBufferedData(u8 *dst, size_t dst_size) const { + AMS_ASSERT(dst_size >= this->GetBufferedDataSize()); + AMS_UNUSED(dst_size); + + for (size_t i = 0; i < m_buffered_bytes; ++i) { + dst[i] = m_buffer[i]; + } + } + private: + static constexpr ALWAYS_INLINE u32 Choose(u32 x, u32 y, u32 z) { + return (x & y) ^ ((~x) & z); + } + + static constexpr ALWAYS_INLINE u32 Majority(u32 x, u32 y, u32 z) { + return (x & y) ^ (x & z) ^ (y & z); + } + + static constexpr ALWAYS_INLINE u32 LargeSigma0(u32 x) { + return util::RotateRight<u32>(x, 2) ^ util::RotateRight<u32>(x, 13) ^ util::RotateRight<u32>(x, 22); + } + + static constexpr ALWAYS_INLINE u32 LargeSigma1(u32 x) { + return util::RotateRight<u32>(x, 6) ^ util::RotateRight<u32>(x, 11) ^ util::RotateRight<u32>(x, 25); + } + + static constexpr ALWAYS_INLINE u32 SmallSigma0(u32 x) { + return util::RotateRight<u32>(x, 7) ^ util::RotateRight<u32>(x, 18) ^ (x >> 3); + } + + static constexpr ALWAYS_INLINE u32 SmallSigma1(u32 x) { + return util::RotateRight<u32>(x, 17) ^ util::RotateRight<u32>(x, 19) ^ (x >> 10); + } + + constexpr void ProcessBlock(const u8 *data) { + /* Load work variables. */ + u32 a = m_intermediate_hash[0]; + u32 b = m_intermediate_hash[1]; + u32 c = m_intermediate_hash[2]; + u32 d = m_intermediate_hash[3]; + u32 e = m_intermediate_hash[4]; + u32 f = m_intermediate_hash[5]; + u32 g = m_intermediate_hash[6]; + u32 h = m_intermediate_hash[7]; + u32 tmp[2]{}; + size_t i = 0; + + /* Copy the input. */ + u32 w[64]{}; + for (size_t i = 0; i < BlockSize / sizeof(u32); ++i) { + u32 v = 0; + v |= static_cast<u32>(data[sizeof(u32) * i + 0]) << (BITSIZEOF(u8) * 3); + v |= static_cast<u32>(data[sizeof(u32) * i + 1]) << (BITSIZEOF(u8) * 2); + v |= static_cast<u32>(data[sizeof(u32) * i + 2]) << (BITSIZEOF(u8) * 1); + v |= static_cast<u32>(data[sizeof(u32) * i + 3]) << (BITSIZEOF(u8) * 0); + + w[i] = v; + } + + /* Initialize the rest of w. */ + for (i = BlockSize / sizeof(u32); i < util::size(w); ++i) { + const u32 *prev = w + (i - BlockSize / sizeof(u32)); + w[i] = prev[0] + SmallSigma0(prev[1]) + prev[9] + SmallSigma1(prev[14]); + } + + /* Perform rounds. */ + { + const u32 RoundConstants[0x40] = { + 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, + 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, + 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, + 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, + 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, + 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, + 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, + 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, + 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, + 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, + 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, + 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, + 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, + 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, + 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, + 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2, + }; + + for (i = 0; i < 64; ++i) { + tmp[0] = h + LargeSigma1(e) + Choose(e, f, g) + RoundConstants[i] + w[i]; + tmp[1] = LargeSigma0(a) + Majority(a, b, c); + + h = g; + g = f; + f = e; + e = d + tmp[0]; + d = c; + c = b; + b = a; + a = tmp[0] + tmp[1]; + } + } + + /* Update intermediate hash. */ + m_intermediate_hash[0] += a; + m_intermediate_hash[1] += b; + m_intermediate_hash[2] += c; + m_intermediate_hash[3] += d; + m_intermediate_hash[4] += e; + m_intermediate_hash[5] += f; + m_intermediate_hash[6] += g; + m_intermediate_hash[7] += h; + } + + constexpr void ProcessLastBlock() { + /* Setup the final block. */ + constexpr const auto BlockSizeWithoutSizeField = BlockSize - sizeof(u64); + + /* Increment our bits consumed. */ + m_bits_consumed += BITSIZEOF(u8) * m_buffered_bytes; + + /* Add 0x80 terminator. */ + m_buffer[m_buffered_bytes++] = 0x80; + + /* If we can process the size field directly, do so, otherwise set up to process it. */ + if (m_buffered_bytes <= BlockSizeWithoutSizeField) { + /* Clear up to size field. */ + for (size_t i = 0; i < BlockSizeWithoutSizeField - m_buffered_bytes; ++i) { + m_buffer[m_buffered_bytes + i] = 0; + } + } else { + /* Consume full block */ + for (size_t i = 0; i < BlockSize - m_buffered_bytes; ++i) { + m_buffer[m_buffered_bytes + i] = 0; + } + + this->ProcessBlock(m_buffer); + + /* Clear up to size field. */ + for (size_t i = 0; i < BlockSizeWithoutSizeField; ++i) { + m_buffer[i] = 0; + } + } + + /* Store the size field. */ + m_buffer[BlockSizeWithoutSizeField + 0] = static_cast<u8>((m_bits_consumed >> (BITSIZEOF(u8) * 7)) & 0xFF); + m_buffer[BlockSizeWithoutSizeField + 1] = static_cast<u8>((m_bits_consumed >> (BITSIZEOF(u8) * 6)) & 0xFF); + m_buffer[BlockSizeWithoutSizeField + 2] = static_cast<u8>((m_bits_consumed >> (BITSIZEOF(u8) * 5)) & 0xFF); + m_buffer[BlockSizeWithoutSizeField + 3] = static_cast<u8>((m_bits_consumed >> (BITSIZEOF(u8) * 4)) & 0xFF); + m_buffer[BlockSizeWithoutSizeField + 4] = static_cast<u8>((m_bits_consumed >> (BITSIZEOF(u8) * 3)) & 0xFF); + m_buffer[BlockSizeWithoutSizeField + 5] = static_cast<u8>((m_bits_consumed >> (BITSIZEOF(u8) * 2)) & 0xFF); + m_buffer[BlockSizeWithoutSizeField + 6] = static_cast<u8>((m_bits_consumed >> (BITSIZEOF(u8) * 1)) & 0xFF); + m_buffer[BlockSizeWithoutSizeField + 7] = static_cast<u8>((m_bits_consumed >> (BITSIZEOF(u8) * 0)) & 0xFF); + + /* Process the final block. */ + this->ProcessBlock(m_buffer); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_sha3_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_sha3_impl.hpp new file mode 100644 index 00000000..009ffcd7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_sha3_impl.hpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/impl/crypto_hash_function.hpp> +#include <vapours/crypto/crypto_memory_clear.hpp> + +namespace ams::crypto { + + struct Sha3Context; + +} + +namespace ams::crypto::impl { + + template<size_t _HashSize> + class Sha3Impl { + public: + static constexpr size_t InternalStateSize = 200; + static constexpr size_t HashSize = _HashSize; + static constexpr size_t BlockSize = InternalStateSize - 2 * HashSize; + private: + enum State { + State_None, + State_Initialized, + State_Done, + }; + private: + u64 m_internal_state[InternalStateSize / sizeof(u64)]; + size_t m_buffered_bytes; + State m_state; + public: + Sha3Impl() : m_state(State_None) { /* ... */ } + ~Sha3Impl() { + ClearMemory(this, sizeof(*this)); + } + + void Initialize(); + void Update(const void *data, size_t size); + void GetHash(void *dst, size_t size); + + void InitializeWithContext(const Sha3Context *context); + void GetContext(Sha3Context *context) const; + private: + void ProcessBlock(); + void ProcessLastBlock(); + }; + + static_assert(HashFunction<Sha3Impl<224 / BITSIZEOF(u8)>>); + static_assert(HashFunction<Sha3Impl<256 / BITSIZEOF(u8)>>); + static_assert(HashFunction<Sha3Impl<384 / BITSIZEOF(u8)>>); + static_assert(HashFunction<Sha3Impl<512 / BITSIZEOF(u8)>>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_xts_mode_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_xts_mode_impl.hpp new file mode 100644 index 00000000..033fc549 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/crypto/impl/crypto_xts_mode_impl.hpp @@ -0,0 +1,138 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> +#include <vapours/crypto/crypto_memory_clear.hpp> +#include <vapours/crypto/crypto_aes_encryptor.hpp> + +namespace ams::crypto::impl { + + class XtsModeImpl { + NON_COPYABLE(XtsModeImpl); + NON_MOVEABLE(XtsModeImpl); + public: + /* TODO: More generic support. */ + static constexpr size_t BlockSize = 16; + static constexpr size_t IvSize = 16; + private: + enum State { + State_None, + State_Initialized, + State_Processing, + State_Done + }; + private: + u8 m_buffer[BlockSize]; + u8 m_tweak[BlockSize]; + u8 m_last_block[BlockSize]; + size_t m_num_buffered; + const void *m_cipher_ctx; + void (*m_cipher_func)(void *dst_block, const void *src_block, const void *cipher_ctx); + State m_state; + public: + XtsModeImpl() : m_num_buffered(0), m_state(State_None) { /* ... */ } + + ~XtsModeImpl() { + ClearMemory(this, sizeof(*this)); + } + private: + template<typename BlockCipher> + static void EncryptBlockCallback(void *dst_block, const void *src_block, const void *cipher) { + return static_cast<const BlockCipher *>(cipher)->EncryptBlock(dst_block, BlockCipher::BlockSize, src_block, BlockCipher::BlockSize); + } + + template<typename BlockCipher> + static void DecryptBlockCallback(void *dst_block, const void *src_block, const void *cipher) { + return static_cast<const BlockCipher *>(cipher)->DecryptBlock(dst_block, BlockCipher::BlockSize, src_block, BlockCipher::BlockSize); + } + + template<typename BlockCipher> + void Initialize(const BlockCipher *cipher, const void *tweak, size_t tweak_size) { + AMS_ASSERT(tweak_size == IvSize); + AMS_UNUSED(tweak_size); + + cipher->EncryptBlock(m_tweak, IvSize, tweak, IvSize); + + m_num_buffered = 0; + m_state = State_Initialized; + } + + void ProcessBlock(u8 *dst, const u8 *src); + public: + template<typename BlockCipher1, typename BlockCipher2> + void InitializeEncryption(const BlockCipher1 *cipher1, const BlockCipher2 *cipher2, const void *tweak, size_t tweak_size) { + static_assert(BlockCipher1::BlockSize == BlockSize); + static_assert(BlockCipher2::BlockSize == BlockSize); + + m_cipher_ctx = cipher1; + m_cipher_func = EncryptBlockCallback<BlockCipher1>; + + this->Initialize(cipher2, tweak, tweak_size); + } + + template<typename BlockCipher1, typename BlockCipher2> + void InitializeDecryption(const BlockCipher1 *cipher1, const BlockCipher2 *cipher2, const void *tweak, size_t tweak_size) { + static_assert(BlockCipher1::BlockSize == BlockSize); + static_assert(BlockCipher2::BlockSize == BlockSize); + + m_cipher_ctx = cipher1; + m_cipher_func = DecryptBlockCallback<BlockCipher1>; + + this->Initialize(cipher2, tweak, tweak_size); + } + + template<typename BlockCipher> + size_t Update(void *dst, size_t dst_size, const void *src, size_t src_size) { + return this->UpdateGeneric(dst, dst_size, src, src_size); + } + + template<typename BlockCipher> + size_t ProcessBlocks(u8 *dst, const u8 *src, size_t num_blocks) { + return this->ProcessBlocksGeneric(dst, src, num_blocks); + } + + size_t GetBufferedDataSize() const { + return m_num_buffered; + } + + constexpr size_t GetBlockSize() const { + return BlockSize; + } + + size_t FinalizeEncryption(void *dst, size_t dst_size); + size_t FinalizeDecryption(void *dst, size_t dst_size); + + size_t UpdateGeneric(void *dst, size_t dst_size, const void *src, size_t src_size); + size_t ProcessBlocksGeneric(u8 *dst, const u8 *src, size_t num_blocks); + + size_t ProcessPartialData(u8 *dst, const u8 *src, size_t size); + size_t ProcessRemainingData(u8 *dst, const u8 *src, size_t size); + }; + + #if defined(ATMOSPHERE_ARCH_ARM64) + template<> size_t XtsModeImpl::Update<AesEncryptor128>(void *dst, size_t dst_size, const void *src, size_t src_size); + template<> size_t XtsModeImpl::Update<AesEncryptor192>(void *dst, size_t dst_size, const void *src, size_t src_size); + template<> size_t XtsModeImpl::Update<AesEncryptor256>(void *dst, size_t dst_size, const void *src, size_t src_size); + + template<> size_t XtsModeImpl::Update<AesDecryptor128>(void *dst, size_t dst_size, const void *src, size_t src_size); + template<> size_t XtsModeImpl::Update<AesDecryptor192>(void *dst, size_t dst_size, const void *src, size_t src_size); + template<> size_t XtsModeImpl::Update<AesDecryptor256>(void *dst, size_t dst_size, const void *src, size_t src_size); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/dd.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/dd.hpp new file mode 100644 index 00000000..1c0c3971 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/dd.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/results.hpp> + +#include <vapours/dd/dd_common_types.hpp> +#include <vapours/dd/dd_io_mapping.hpp> +#include <vapours/dd/dd_cache.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/dd/dd_cache.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/dd/dd_cache.hpp new file mode 100644 index 00000000..74a9d75d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/dd/dd_cache.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/dd/dd_common_types.hpp> + +namespace ams::dd { + + void InvalidateDataCache(void *addr, size_t size); + void StoreDataCache(void *addr, size_t size); + void FlushDataCache(void *addr, size_t size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/dd/dd_common_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/dd/dd_common_types.hpp new file mode 100644 index 00000000..b9f8fa3f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/dd/dd_common_types.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/results.hpp> + +namespace ams::dd { + + using PhysicalAddress = u64; + using DeviceVirtualAddress = u64; + + #if defined(ATMOSPHERE_OS_HORIZON) + static_assert(std::same_as<PhysicalAddress, ams::svc::PhysicalAddress>); + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/dd/dd_io_mapping.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/dd/dd_io_mapping.hpp new file mode 100644 index 00000000..3d5db3d3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/dd/dd_io_mapping.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/dd/dd_common_types.hpp> + +namespace ams::dd { + + uintptr_t QueryIoMapping(dd::PhysicalAddress phys_addr, size_t size); + + u32 ReadIoRegister(dd::PhysicalAddress phys_addr); + void WriteIoRegister(dd::PhysicalAddress phys_addr, u32 value); + u32 ReadModifyWriteIoRegister(PhysicalAddress phys_addr, u32 value, u32 mask); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/defines.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/defines.hpp new file mode 100644 index 00000000..9f8eb5ff --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/defines.hpp @@ -0,0 +1,137 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/includes.hpp> + +/* Any broadly useful language defines should go here. */ + +#if defined(ATMOSPHERE_OS_WINDOWS) + #if defined(ATMOSPHERE_ARCH_ARM64) || defined(ATMOSPHERE_ARCH_X64) + static_assert(sizeof(size_t) == sizeof(uint64_t)); + + #define PRIuZ PRIu64 + #define PRIxZ PRIx64 + #define PRIXZ PRIX64 + #elif defined(ATMOSPHERE_ARCH_ARM) || defined(ATMOSPHERE_ARCH_X86) + static_assert(sizeof(size_t) == sizeof(uint32_t)); + + #define PRIuZ PRIu32 + #define PRIxZ PRIx32 + #define PRIXZ PRIX32 + #endif +#else + #define PRIuZ "zu" + #define PRIxZ "zx" + #define PRIXZ "zX" +#endif + +#if defined(__clang__) + #define ATMOSPHERE_COMPILER_CLANG +#elif defined(__GNUG__) || defined(__GNUC__) + #define ATMOSPHERE_COMPILER_GCC +#else + #error "Unknown compiler!" +#endif + +#define NON_COPYABLE(cls) \ + cls(const cls&) = delete; \ + cls& operator=(const cls&) = delete + +#define NON_MOVEABLE(cls) \ + cls(cls&&) = delete; \ + cls& operator=(cls&&) = delete + +#define ALIGNED(algn) __attribute__((aligned(algn))) +#define NORETURN __attribute__((noreturn)) +#define WEAK_SYMBOL __attribute__((weak)) +#define ALWAYS_INLINE_LAMBDA __attribute__((always_inline)) +#define ALWAYS_INLINE inline __attribute__((always_inline)) +#define NOINLINE __attribute__((noinline)) + +#define CONCATENATE_IMPL(s1, s2) s1##s2 +#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2) + +#define BITSIZEOF(x) (sizeof(x) * CHAR_BIT) + +#define STRINGIZE(x) STRINGIZE_IMPL(x) +#define STRINGIZE_IMPL(x) #x + +#ifndef PACKED +#define PACKED __attribute__((packed)) +#endif + +#ifdef __COUNTER__ +#define ANONYMOUS_VARIABLE(pref) CONCATENATE(pref, __COUNTER__) +#else +#define ANONYMOUS_VARIABLE(pref) CONCATENATE(pref, __LINE__) +#endif + +#define AMS_PREDICT(expr, value, _probability) __builtin_expect_with_probability(expr, value, ({ \ + constexpr double probability = _probability; \ + static_assert(0.0 <= probability); \ + static_assert(probability <= 1.0); \ + probability; \ + })) + +#define AMS_PREDICT_TRUE(expr, probability) AMS_PREDICT(!!(expr), 1, probability) +#define AMS_PREDICT_FALSE(expr, probability) AMS_PREDICT(!!(expr), 0, probability) + +#define AMS_LIKELY(expr) AMS_PREDICT_TRUE(expr, 1.0) +#define AMS_UNLIKELY(expr) AMS_PREDICT_FALSE(expr, 1.0) + +#define AMS_ASSUME(expr) do { if (!static_cast<bool>((expr))) { __builtin_unreachable(); } } while (0) + +#define AMS_CURRENT_FUNCTION_NAME __FUNCTION__ + +#if defined(__cplusplus) + +namespace ams::impl { + + template<typename... ArgTypes> + constexpr ALWAYS_INLINE void UnusedImpl(ArgTypes &&... args) { + (static_cast<void>(args), ...); + } + +} + +#endif + +#define AMS_UNUSED(...) ::ams::impl::UnusedImpl(__VA_ARGS__) + +#define AMS_INFINITE_LOOP() do { __asm__ __volatile__("" ::: "memory"); } while (1) + +#define AMS__NARG__(...) AMS__NARG_I_(__VA_ARGS__,AMS__RSEQ_N()) +#define AMS__NARG_I_(...) AMS__ARG_N(__VA_ARGS__) +#define AMS__ARG_N( \ + _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \ + _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \ + _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \ + _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \ + _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \ + _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \ + _61,_62,_63,N,...) N +#define AMS__RSEQ_N() \ + 63,62,61,60, \ + 59,58,57,56,55,54,53,52,51,50, \ + 49,48,47,46,45,44,43,42,41,40, \ + 39,38,37,36,35,34,33,32,31,30, \ + 29,28,27,26,25,24,23,22,21,20, \ + 19,18,17,16,15,14,13,12,11,10, \ + 9,8,7,6,5,4,3,2,1,0 + +#define AMS__VMACRO_(name, n) name##_##n +#define AMS__VMACRO(name, n) AMS__VMACRO_(name, n) +#define AMS_VMACRO(func, ...) AMS__VMACRO(func, AMS__NARG__(__VA_ARGS__)) (__VA_ARGS__) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/device_code.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/device_code.hpp new file mode 100644 index 00000000..831cf372 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/device_code.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams { + + namespace impl { + + using DeviceCodeType = u32; + + } + + /* TODO: Better understand device code components. */ + class DeviceCode { + private: + impl::DeviceCodeType m_inner_value; + public: + constexpr DeviceCode(impl::DeviceCodeType v) : m_inner_value(v) { /* ... */ } + + constexpr impl::DeviceCodeType GetInternalValue() const { return m_inner_value; } + + constexpr bool operator==(const DeviceCode &rhs) const { + return this->GetInternalValue() == rhs.GetInternalValue(); + } + + constexpr bool operator!=(const DeviceCode &rhs) const { + return !(*this == rhs); + } + }; + + constexpr inline const DeviceCode InvalidDeviceCode(0); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/freebsd/tree.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/freebsd/tree.hpp new file mode 100644 index 00000000..cf0c3f45 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/freebsd/tree.hpp @@ -0,0 +1,628 @@ +/* $NetBSD: tree.h,v 1.8 2004/03/28 19:38:30 provos Exp $ */ +/* $OpenBSD: tree.h,v 1.7 2002/10/17 21:51:54 art Exp $ */ +/* $FreeBSD$ */ +/*- + * Copyright 2002 Niels Provos <provos@citi.umich.edu> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util/util_type_traits.hpp> + +AMS_PRAGMA_BEGIN_OPTIMIZE("-O3") + +/* + * This file defines data structures for red-black trees. + * + * A red-black tree is a binary search tree with the node color as an + * extra attribute. It fulfills a set of conditions: + * - every search path from the root to a leaf consists of the + * same number of black nodes, + * - each red node (except for the root) has a black parent, + * - each leaf node is black. + * + * Every operation on a red-black tree is bounded as O(lg n). + * The maximum height of a red-black tree is 2lg (n+1). + */ + +namespace ams::freebsd { + + enum class RBColor { + RB_BLACK = 0, + RB_RED = 1, + }; + + #pragma pack(push, 4) + template<typename T> + class RBEntry { + private: + T *m_rbe_left ; + T *m_rbe_right; + T *m_rbe_parent; + RBColor m_rbe_color; + public: + constexpr ALWAYS_INLINE explicit RBEntry(util::ConstantInitializeTag) : m_rbe_left(nullptr), m_rbe_right(nullptr), m_rbe_parent(nullptr), m_rbe_color(RBColor::RB_BLACK) { /* ... */ } + explicit ALWAYS_INLINE RBEntry() { /* ... */ } + + [[nodiscard]] constexpr ALWAYS_INLINE T *Left() { return m_rbe_left; } + [[nodiscard]] constexpr ALWAYS_INLINE const T *Left() const { return m_rbe_left; } + + constexpr ALWAYS_INLINE void SetLeft(T *e) { m_rbe_left = e; } + + [[nodiscard]] constexpr ALWAYS_INLINE T *Right() { return m_rbe_right; } + [[nodiscard]] constexpr ALWAYS_INLINE const T *Right() const { return m_rbe_right; } + + constexpr ALWAYS_INLINE void SetRight(T *e) { m_rbe_right = e; } + + [[nodiscard]] constexpr ALWAYS_INLINE T *Parent() { return m_rbe_parent; } + [[nodiscard]] constexpr ALWAYS_INLINE const T *Parent() const { return m_rbe_parent; } + + constexpr ALWAYS_INLINE void SetParent(T *e) { m_rbe_parent = e; } + + [[nodiscard]] constexpr ALWAYS_INLINE bool IsBlack() const { return m_rbe_color == RBColor::RB_BLACK; } + [[nodiscard]] constexpr ALWAYS_INLINE bool IsRed() const { return m_rbe_color == RBColor::RB_RED; } + [[nodiscard]] constexpr ALWAYS_INLINE RBColor Color() const { return m_rbe_color; } + + constexpr ALWAYS_INLINE void SetColor(RBColor c) { m_rbe_color = c; } + }; + #pragma pack(pop) + + template<typename T> struct CheckRBEntry { static constexpr bool value = false; }; + template<typename T> struct CheckRBEntry<RBEntry<T>> { static constexpr bool value = true; }; + + template<typename T> + concept IsRBEntry = CheckRBEntry<T>::value; + + template<typename T> + concept HasRBEntry = requires (T &t, const T &ct) { + { t.GetRBEntry() } -> std::same_as< RBEntry<T> &>; + { ct.GetRBEntry() } -> std::same_as<const RBEntry<T> &>; + }; + + template<typename T> requires HasRBEntry<T> + class RBHead { + private: + T *m_rbh_root = nullptr; + public: + [[nodiscard]] constexpr ALWAYS_INLINE T *Root() { return m_rbh_root; } + [[nodiscard]] constexpr ALWAYS_INLINE const T *Root() const { return m_rbh_root; } + constexpr ALWAYS_INLINE void SetRoot(T *root) { m_rbh_root = root; } + + [[nodiscard]] constexpr ALWAYS_INLINE bool IsEmpty() const { return this->Root() == nullptr; } + }; + + template<typename T> requires HasRBEntry<T> [[nodiscard]] constexpr ALWAYS_INLINE RBEntry<T> &RB_ENTRY( T *t) { return t->GetRBEntry(); } + template<typename T> requires HasRBEntry<T> [[nodiscard]] constexpr ALWAYS_INLINE const RBEntry<T> &RB_ENTRY(const T *t) { return t->GetRBEntry(); } + + template<typename T> requires HasRBEntry<T> [[nodiscard]] constexpr ALWAYS_INLINE T *RB_LEFT( T *t) { return RB_ENTRY(t).Left(); } + template<typename T> requires HasRBEntry<T> [[nodiscard]] constexpr ALWAYS_INLINE const T *RB_LEFT(const T *t) { return RB_ENTRY(t).Left(); } + + template<typename T> requires HasRBEntry<T> [[nodiscard]] constexpr ALWAYS_INLINE T *RB_RIGHT( T *t) { return RB_ENTRY(t).Right(); } + template<typename T> requires HasRBEntry<T> [[nodiscard]] constexpr ALWAYS_INLINE const T *RB_RIGHT(const T *t) { return RB_ENTRY(t).Right(); } + + template<typename T> requires HasRBEntry<T> [[nodiscard]] constexpr ALWAYS_INLINE T *RB_PARENT( T *t) { return RB_ENTRY(t).Parent(); } + template<typename T> requires HasRBEntry<T> [[nodiscard]] constexpr ALWAYS_INLINE const T *RB_PARENT(const T *t) { return RB_ENTRY(t).Parent(); } + + template<typename T> requires HasRBEntry<T> constexpr ALWAYS_INLINE void RB_SET_LEFT(T *t, T *e) { RB_ENTRY(t).SetLeft(e); } + template<typename T> requires HasRBEntry<T> constexpr ALWAYS_INLINE void RB_SET_RIGHT(T *t, T *e) { RB_ENTRY(t).SetRight(e); } + template<typename T> requires HasRBEntry<T> constexpr ALWAYS_INLINE void RB_SET_PARENT(T *t, T *e) { RB_ENTRY(t).SetParent(e); } + + template<typename T> requires HasRBEntry<T> [[nodiscard]] constexpr ALWAYS_INLINE bool RB_IS_BLACK(const T *t) { return RB_ENTRY(t).IsBlack(); } + template<typename T> requires HasRBEntry<T> [[nodiscard]] constexpr ALWAYS_INLINE bool RB_IS_RED(const T *t) { return RB_ENTRY(t).IsRed(); } + + template<typename T> requires HasRBEntry<T> [[nodiscard]] constexpr ALWAYS_INLINE RBColor RB_COLOR(const T *t) { return RB_ENTRY(t).Color(); } + + template<typename T> requires HasRBEntry<T> constexpr ALWAYS_INLINE void RB_SET_COLOR(T *t, RBColor c) { RB_ENTRY(t).SetColor(c); } + + template<typename T> requires HasRBEntry<T> + constexpr ALWAYS_INLINE void RB_SET(T *elm, T *parent) { + auto &rb_entry = RB_ENTRY(elm); + rb_entry.SetParent(parent); + rb_entry.SetLeft(nullptr); + rb_entry.SetRight(nullptr); + rb_entry.SetColor(RBColor::RB_RED); + } + + template<typename T> requires HasRBEntry<T> + constexpr ALWAYS_INLINE void RB_SET_BLACKRED(T *black, T *red) { + RB_SET_COLOR(black, RBColor::RB_BLACK); + RB_SET_COLOR(red, RBColor::RB_RED); + } + + template<typename T> requires HasRBEntry<T> + constexpr ALWAYS_INLINE void RB_ROTATE_LEFT(RBHead<T> &head, T *elm, T *&tmp) { + tmp = RB_RIGHT(elm); + if (RB_SET_RIGHT(elm, RB_LEFT(tmp)); RB_RIGHT(elm) != nullptr) { + RB_SET_PARENT(RB_LEFT(tmp), elm); + } + + if (RB_SET_PARENT(tmp, RB_PARENT(elm)); RB_PARENT(tmp) != nullptr) { + if (elm == RB_LEFT(RB_PARENT(elm))) { + RB_SET_LEFT(RB_PARENT(elm), tmp); + } else { + RB_SET_RIGHT(RB_PARENT(elm), tmp); + } + } else { + head.SetRoot(tmp); + } + + RB_SET_LEFT(tmp, elm); + RB_SET_PARENT(elm, tmp); + } + + template<typename T> requires HasRBEntry<T> + constexpr ALWAYS_INLINE void RB_ROTATE_RIGHT(RBHead<T> &head, T *elm, T *&tmp) { + tmp = RB_LEFT(elm); + if (RB_SET_LEFT(elm, RB_RIGHT(tmp)); RB_LEFT(elm) != nullptr) { + RB_SET_PARENT(RB_RIGHT(tmp), elm); + } + + if (RB_SET_PARENT(tmp, RB_PARENT(elm)); RB_PARENT(tmp) != nullptr) { + if (elm == RB_LEFT(RB_PARENT(elm))) { + RB_SET_LEFT(RB_PARENT(elm), tmp); + } else { + RB_SET_RIGHT(RB_PARENT(elm), tmp); + } + } else { + head.SetRoot(tmp); + } + + RB_SET_RIGHT(tmp, elm); + RB_SET_PARENT(elm, tmp); + } + + template <typename T> requires HasRBEntry<T> + constexpr void RB_REMOVE_COLOR(RBHead<T> &head, T *parent, T *elm) { + T *tmp; + while ((elm == nullptr || RB_IS_BLACK(elm)) && elm != head.Root()) { + if (RB_LEFT(parent) == elm) { + tmp = RB_RIGHT(parent); + if (RB_IS_RED(tmp)) { + RB_SET_BLACKRED(tmp, parent); + RB_ROTATE_LEFT(head, parent, tmp); + tmp = RB_RIGHT(parent); + } + + if ((RB_LEFT(tmp) == nullptr || RB_IS_BLACK(RB_LEFT(tmp))) && + (RB_RIGHT(tmp) == nullptr || RB_IS_BLACK(RB_RIGHT(tmp)))) { + RB_SET_COLOR(tmp, RBColor::RB_RED); + elm = parent; + parent = RB_PARENT(elm); + } else { + if (RB_RIGHT(tmp) == nullptr || RB_IS_BLACK(RB_RIGHT(tmp))) { + T *oleft; + if ((oleft = RB_LEFT(tmp)) != nullptr) { + RB_SET_COLOR(oleft, RBColor::RB_BLACK); + } + + RB_SET_COLOR(tmp, RBColor::RB_RED); + RB_ROTATE_RIGHT(head, tmp, oleft); + tmp = RB_RIGHT(parent); + } + + RB_SET_COLOR(tmp, RB_COLOR(parent)); + RB_SET_COLOR(parent, RBColor::RB_BLACK); + if (RB_RIGHT(tmp)) { + RB_SET_COLOR(RB_RIGHT(tmp), RBColor::RB_BLACK); + } + + RB_ROTATE_LEFT(head, parent, tmp); + elm = head.Root(); + break; + } + } else { + tmp = RB_LEFT(parent); + if (RB_IS_RED(tmp)) { + RB_SET_BLACKRED(tmp, parent); + RB_ROTATE_RIGHT(head, parent, tmp); + tmp = RB_LEFT(parent); + } + + if ((RB_LEFT(tmp) == nullptr || RB_IS_BLACK(RB_LEFT(tmp))) && + (RB_RIGHT(tmp) == nullptr || RB_IS_BLACK(RB_RIGHT(tmp)))) { + RB_SET_COLOR(tmp, RBColor::RB_RED); + elm = parent; + parent = RB_PARENT(elm); + } else { + if (RB_LEFT(tmp) == nullptr || RB_IS_BLACK(RB_LEFT(tmp))) { + T *oright; + if ((oright = RB_RIGHT(tmp)) != nullptr) { + RB_SET_COLOR(oright, RBColor::RB_BLACK); + } + + RB_SET_COLOR(tmp, RBColor::RB_RED); + RB_ROTATE_LEFT(head, tmp, oright); + tmp = RB_LEFT(parent); + } + + RB_SET_COLOR(tmp, RB_COLOR(parent)); + RB_SET_COLOR(parent, RBColor::RB_BLACK); + + if (RB_LEFT(tmp)) { + RB_SET_COLOR(RB_LEFT(tmp), RBColor::RB_BLACK); + } + + RB_ROTATE_RIGHT(head, parent, tmp); + elm = head.Root(); + break; + } + } + } + + if (elm) { + RB_SET_COLOR(elm, RBColor::RB_BLACK); + } + } + + template <typename T> requires HasRBEntry<T> + constexpr T *RB_REMOVE(RBHead<T> &head, T *elm) { + T *child = nullptr; + T *parent = nullptr; + T *old = elm; + RBColor color = RBColor::RB_BLACK; + + if (RB_LEFT(elm) == nullptr) { + child = RB_RIGHT(elm); + } else if (RB_RIGHT(elm) == nullptr) { + child = RB_LEFT(elm); + } else { + T *left; + elm = RB_RIGHT(elm); + while ((left = RB_LEFT(elm)) != nullptr) { + elm = left; + } + + child = RB_RIGHT(elm); + parent = RB_PARENT(elm); + color = RB_COLOR(elm); + + if (child) { + RB_SET_PARENT(child, parent); + } + + if (parent) { + if (RB_LEFT(parent) == elm) { + RB_SET_LEFT(parent, child); + } else { + RB_SET_RIGHT(parent, child); + } + } else { + head.SetRoot(child); + } + + if (RB_PARENT(elm) == old) { + parent = elm; + } + + elm->SetRBEntry(old->GetRBEntry()); + + if (RB_PARENT(old)) { + if (RB_LEFT(RB_PARENT(old)) == old) { + RB_SET_LEFT(RB_PARENT(old), elm); + } else { + RB_SET_RIGHT(RB_PARENT(old), elm); + } + } else { + head.SetRoot(elm); + } + + RB_SET_PARENT(RB_LEFT(old), elm); + + if (RB_RIGHT(old)) { + RB_SET_PARENT(RB_RIGHT(old), elm); + } + + if (parent) { + left = parent; + } + + if (color == RBColor::RB_BLACK) { + RB_REMOVE_COLOR(head, parent, child); + } + + return old; + } + + parent = RB_PARENT(elm); + color = RB_COLOR(elm); + + if (child) { + RB_SET_PARENT(child, parent); + } + if (parent) { + if (RB_LEFT(parent) == elm) { + RB_SET_LEFT(parent, child); + } else { + RB_SET_RIGHT(parent, child); + } + } else { + head.SetRoot(child); + } + + if (color == RBColor::RB_BLACK) { + RB_REMOVE_COLOR(head, parent, child); + } + + return old; + } + + template<typename T> requires HasRBEntry<T> + constexpr void RB_INSERT_COLOR(RBHead<T> &head, T *elm) { + T *parent = nullptr, *tmp = nullptr; + while ((parent = RB_PARENT(elm)) != nullptr && RB_IS_RED(parent)) { + T *gparent = RB_PARENT(parent); + if (parent == RB_LEFT(gparent)) { + tmp = RB_RIGHT(gparent); + if (tmp && RB_IS_RED(tmp)) { + RB_SET_COLOR(tmp, RBColor::RB_BLACK); + RB_SET_BLACKRED(parent, gparent); + elm = gparent; + continue; + } + + if (RB_RIGHT(parent) == elm) { + RB_ROTATE_LEFT(head, parent, tmp); + tmp = parent; + parent = elm; + elm = tmp; + } + + RB_SET_BLACKRED(parent, gparent); + RB_ROTATE_RIGHT(head, gparent, tmp); + } else { + tmp = RB_LEFT(gparent); + if (tmp && RB_IS_RED(tmp)) { + RB_SET_COLOR(tmp, RBColor::RB_BLACK); + RB_SET_BLACKRED(parent, gparent); + elm = gparent; + continue; + } + + if (RB_LEFT(parent) == elm) { + RB_ROTATE_RIGHT(head, parent, tmp); + tmp = parent; + parent = elm; + elm = tmp; + } + + RB_SET_BLACKRED(parent, gparent); + RB_ROTATE_LEFT(head, gparent, tmp); + } + } + + RB_SET_COLOR(head.Root(), RBColor::RB_BLACK); + } + + template <typename T, typename Compare> requires HasRBEntry<T> + constexpr ALWAYS_INLINE T *RB_INSERT(RBHead<T> &head, T *elm, Compare cmp) { + T *parent = nullptr; + T *tmp = head.Root(); + int comp = 0; + + while (tmp) { + parent = tmp; + comp = cmp(elm, parent); + if (comp < 0) { + tmp = RB_LEFT(tmp); + } else if (comp > 0) { + tmp = RB_RIGHT(tmp); + } else { + return tmp; + } + } + + RB_SET(elm, parent); + + if (parent != nullptr) { + if (comp < 0) { + RB_SET_LEFT(parent, elm); + } else { + RB_SET_RIGHT(parent, elm); + } + } else { + head.SetRoot(elm); + } + + RB_INSERT_COLOR(head, elm); + return nullptr; + } + + template<typename T, typename Compare> requires HasRBEntry<T> + constexpr ALWAYS_INLINE T *RB_FIND(RBHead<T> &head, T *elm, Compare cmp) { + T *tmp = head.Root(); + + while (tmp) { + const int comp = cmp(elm, tmp); + if (comp < 0) { + tmp = RB_LEFT(tmp); + } else if (comp > 0) { + tmp = RB_RIGHT(tmp); + } else { + return tmp; + } + } + + return nullptr; + } + + template<typename T, typename Compare> requires HasRBEntry<T> + constexpr ALWAYS_INLINE T *RB_NFIND(RBHead<T> &head, T *elm, Compare cmp) { + T *tmp = head.Root(); + T* res = nullptr; + + while (tmp) { + const int comp = cmp(elm, tmp); + if (comp < 0) { + res = tmp; + tmp = RB_LEFT(tmp); + } else if (comp > 0) { + tmp = RB_RIGHT(tmp); + } else { + return tmp; + } + } + + return res; + } + + template<typename T, typename U, typename Compare> requires HasRBEntry<T> + constexpr ALWAYS_INLINE T *RB_FIND_KEY(RBHead<T> &head, const U &key, Compare cmp) { + T *tmp = head.Root(); + + while (tmp) { + const int comp = cmp(key, tmp); + if (comp < 0) { + tmp = RB_LEFT(tmp); + } else if (comp > 0) { + tmp = RB_RIGHT(tmp); + } else { + return tmp; + } + } + + return nullptr; + } + + template<typename T, typename U, typename Compare> requires HasRBEntry<T> + constexpr ALWAYS_INLINE T *RB_NFIND_KEY(RBHead<T> &head, const U &key, Compare cmp) { + T *tmp = head.Root(); + T* res = nullptr; + + while (tmp) { + const int comp = cmp(key, tmp); + if (comp < 0) { + res = tmp; + tmp = RB_LEFT(tmp); + } else if (comp > 0) { + tmp = RB_RIGHT(tmp); + } else { + return tmp; + } + } + + return res; + } + + template<typename T, typename Compare> requires HasRBEntry<T> + constexpr ALWAYS_INLINE T *RB_FIND_EXISTING(RBHead<T> &head, T *elm, Compare cmp) { + T *tmp = head.Root(); + + while (true) { + const int comp = cmp(elm, tmp); + if (comp < 0) { + tmp = RB_LEFT(tmp); + } else if (comp > 0) { + tmp = RB_RIGHT(tmp); + } else { + return tmp; + } + } + } + + template<typename T, typename U, typename Compare> requires HasRBEntry<T> + constexpr ALWAYS_INLINE T *RB_FIND_EXISTING_KEY(RBHead<T> &head, const U &key, Compare cmp) { + T *tmp = head.Root(); + + while (true) { + const int comp = cmp(key, tmp); + if (comp < 0) { + tmp = RB_LEFT(tmp); + } else if (comp > 0) { + tmp = RB_RIGHT(tmp); + } else { + return tmp; + } + } + } + + template<typename T> requires HasRBEntry<T> + constexpr ALWAYS_INLINE T *RB_NEXT(T *elm) { + if (RB_RIGHT(elm)) { + elm = RB_RIGHT(elm); + while (RB_LEFT(elm)) { + elm = RB_LEFT(elm); + } + } else { + if (RB_PARENT(elm) && (elm == RB_LEFT(RB_PARENT(elm)))) { + elm = RB_PARENT(elm); + } else { + while (RB_PARENT(elm) && (elm == RB_RIGHT(RB_PARENT(elm)))) { + elm = RB_PARENT(elm); + } + elm = RB_PARENT(elm); + } + } + return elm; + } + + template<typename T> requires HasRBEntry<T> + constexpr ALWAYS_INLINE T *RB_PREV(T *elm) { + if (RB_LEFT(elm)) { + elm = RB_LEFT(elm); + while (RB_RIGHT(elm)) { + elm = RB_RIGHT(elm); + } + } else { + if (RB_PARENT(elm) && (elm == RB_RIGHT(RB_PARENT(elm)))) { + elm = RB_PARENT(elm); + } else { + while (RB_PARENT(elm) && (elm == RB_LEFT(RB_PARENT(elm)))) { + elm = RB_PARENT(elm); + } + elm = RB_PARENT(elm); + } + } + return elm; + } + + template<typename T> requires HasRBEntry<T> + constexpr ALWAYS_INLINE T *RB_MIN(RBHead<T> &head) { + T *tmp = head.Root(); + T *parent = nullptr; + + while (tmp) { + parent = tmp; + tmp = RB_LEFT(tmp); + } + + return parent; + } + + template<typename T> requires HasRBEntry<T> + constexpr ALWAYS_INLINE T *RB_MAX(RBHead<T> &head) { + T *tmp = head.Root(); + T *parent = nullptr; + + while (tmp) { + parent = tmp; + tmp = RB_RIGHT(tmp); + } + + return parent; + } + + +} + +AMS_PRAGMA_END_OPTIMIZE() diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/impl/compiler_impl.clang.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/impl/compiler_impl.clang.hpp new file mode 100644 index 00000000..febf74d3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/impl/compiler_impl.clang.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/includes.hpp> +#include <vapours/defines.hpp> + +#define AMS_PRAGMA(X) \ + _Pragma(#X) + +#define AMS_PRAGMA_BEGIN_OPTIMIZE(X) +#define AMS_PRAGMA_END_OPTIMIZE() + +#define AMS_PRAGMA_BEGIN_PACK(n) \ + AMS_PRAGMA(pack(push, n)) + +#define AMS_PRAGMA_END_PACK() \ + AMS_PRAGMA(pack(pop)) + +#define AMS_CONCEPTS_REQUIRES_IF_SUPPORTED(__EXPR__) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/impl/compiler_impl.gcc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/impl/compiler_impl.gcc.hpp new file mode 100644 index 00000000..3cab082b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/impl/compiler_impl.gcc.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/includes.hpp> +#include <vapours/defines.hpp> + +#define AMS_PRAGMA(X) \ + _Pragma(#X) + +#define AMS_PRAGMA_BEGIN_OPTIMIZE(X) \ + AMS_PRAGMA(GCC push_options) \ + AMS_PRAGMA(GCC optimize(X)) + +#define AMS_PRAGMA_END_OPTIMIZE() \ + AMS_PRAGMA(GCC pop_options) + +#define AMS_PRAGMA_BEGIN_PACK(n) \ + AMS_PRAGMA(pack(push, n)) + +#define AMS_PRAGMA_END_PACK() \ + AMS_PRAGMA(pack(pop)) + +#define AMS_CONCEPTS_REQUIRES_IF_SUPPORTED(__EXPR__) requires (__EXPR__) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/includes.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/includes.hpp new file mode 100644 index 00000000..8034710b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/includes.hpp @@ -0,0 +1,78 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +/* Unconditionally include type-traits as first header. */ +#include <type_traits> + +/* C headers. */ +#include <cstdint> +#include <cstdarg> +#include <cstdlib> +#include <cstddef> +#include <cstdio> +#include <cstring> +#include <climits> +#include <cctype> +#include <cinttypes> + +/* C++ headers. */ +#include <concepts> +#include <algorithm> +#include <iterator> +#include <limits> +#include <random> +#include <atomic> +#include <utility> +#include <functional> +#include <tuple> +#include <array> +#include <bit> +#include <span> + +/* Stratosphere/Troposphere want additional libstdc++ headers and libnx, + * others do not. */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) || defined(ATMOSPHERE_IS_TROPOSPHERE) + +#include <memory> +#include <mutex> +#include <shared_mutex> +#include <map> +#include <unordered_map> +#include <set> + +#if defined(ATMOSPHERE_OS_HORIZON) && defined(ATMOSPHERE_BOARD_NINTENDO_NX) + +/* Libnx. */ +#include <switch.h> + +#else + +/* Non-switch code can't include libnx. */ +#include "types.hpp" + +#endif + +#else + +/* Non-EL0 code can't include libnx. */ +#include "types.hpp" + +#endif /* defined(ATMOSPHERE_IS_STRATOSPHERE) || defined(ATMOSPHERE_IS_TROPOSPHERE) */ + +/* Atmosphere meta. */ +#include <vapours/ams_version.h> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/literals.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/literals.hpp new file mode 100644 index 00000000..701fbcc6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/literals.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams { inline namespace literals { + + constexpr ALWAYS_INLINE u64 operator ""_KB(unsigned long long n) { + return static_cast<u64>(n) * UINT64_C(1024); + } + + constexpr ALWAYS_INLINE u64 operator ""_MB(unsigned long long n) { + return operator ""_KB(n) * UINT64_C(1024); + } + + constexpr ALWAYS_INLINE u64 operator ""_GB(unsigned long long n) { + return operator ""_MB(n) * UINT64_C(1024); + } + +} } diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/reg.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/reg.hpp new file mode 100644 index 00000000..077d7b13 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/reg.hpp @@ -0,0 +1,261 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::reg { + + template<typename T> + concept UnsignedNonConstIntegral = std::unsigned_integral<T> && !std::is_const<T>::value; + + using BitsValue = std::tuple<u16, u16, u32>; + using BitsMask = std::tuple<u16, u16>; + + constexpr ALWAYS_INLINE u32 GetOffset(const BitsMask v) { return static_cast<u32>(std::get<0>(v)); } + constexpr ALWAYS_INLINE u32 GetOffset(const BitsValue v) { return static_cast<u32>(std::get<0>(v)); } + constexpr ALWAYS_INLINE u32 GetWidth(const BitsMask v) { return static_cast<u32>(std::get<1>(v)); } + constexpr ALWAYS_INLINE u32 GetWidth(const BitsValue v) { return static_cast<u32>(std::get<1>(v)); } + constexpr ALWAYS_INLINE u32 GetValue(const BitsValue v) { return static_cast<u32>(std::get<2>(v)); } + + constexpr ALWAYS_INLINE ::ams::reg::BitsValue GetValue(const BitsMask m, const u32 v) { + return ::ams::reg::BitsValue{GetOffset(m), GetWidth(m), v}; + } + + constexpr ALWAYS_INLINE u32 EncodeMask(const BitsMask v) { + return (~0u >> (BITSIZEOF(u32) - GetWidth(v))) << GetOffset(v); + } + + constexpr ALWAYS_INLINE u32 EncodeMask(const BitsValue v) { + return (~0u >> (BITSIZEOF(u32) - GetWidth(v))) << GetOffset(v); + } + + constexpr ALWAYS_INLINE u32 EncodeValue(const BitsValue v) { + return ((GetValue(v) << GetOffset(v)) & EncodeMask(v)); + } + + template<typename... Values> requires ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...)) + constexpr ALWAYS_INLINE u32 Encode(const Values... values) { + return (EncodeValue(values) | ...); + } + + template<typename... Masks> requires ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...)) + constexpr ALWAYS_INLINE u32 EncodeMask(const Masks... masks) { + return (EncodeMask(masks) | ...); + } + + template<typename IntType> requires UnsignedNonConstIntegral<IntType> + constexpr ALWAYS_INLINE IntType GetField(const IntType &value, const BitsMask mask) { return (value & EncodeMask(mask)) >> GetOffset(mask); } + + template<typename IntType> requires UnsignedNonConstIntegral<IntType> + constexpr ALWAYS_INLINE void SetField(IntType &value, const BitsValue v) { value = (value & ~EncodeMask(v)) | EncodeValue(v); } + + template<typename IntType> requires UnsignedNonConstIntegral<IntType> + ALWAYS_INLINE void Write(volatile IntType *reg, std::type_identity_t<IntType> val) { *reg = val; } + + template<typename IntType> requires UnsignedNonConstIntegral<IntType> + ALWAYS_INLINE void Write(volatile IntType ®, std::type_identity_t<IntType> val) { reg = val; } + + ALWAYS_INLINE void Write(uintptr_t reg, u32 val) { Write(reinterpret_cast<volatile u32 *>(reg), val); } + + template<typename IntType, typename... Values> requires UnsignedNonConstIntegral<IntType> && ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...)) + ALWAYS_INLINE void Write(volatile IntType *reg, const Values... values) { return Write(reg, static_cast<IntType>((EncodeValue(values) | ...))); } + + template<typename IntType, typename... Values> requires UnsignedNonConstIntegral<IntType> && ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...)) + ALWAYS_INLINE void Write(volatile IntType ®, const Values... values) { return Write(reg, static_cast<IntType>((EncodeValue(values) | ...))); } + + template<typename... Values> requires ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...)) + ALWAYS_INLINE void Write(uintptr_t reg, const Values... values) { return Write(reg, (EncodeValue(values) | ...)); } + + template<typename IntType> requires UnsignedNonConstIntegral<IntType> + ALWAYS_INLINE IntType Read(volatile IntType *reg) { return *reg; } + + template<typename IntType> requires UnsignedNonConstIntegral<IntType> + ALWAYS_INLINE IntType Read(volatile IntType ®) { return reg; } + + ALWAYS_INLINE u32 Read(uintptr_t reg) { return Read(reinterpret_cast<volatile u32 *>(reg)); } + + template<typename IntType> requires UnsignedNonConstIntegral<IntType> + ALWAYS_INLINE IntType Read(volatile IntType *reg, std::type_identity_t<IntType> mask) { return *reg & mask; } + + template<typename IntType> requires UnsignedNonConstIntegral<IntType> + ALWAYS_INLINE IntType Read(volatile IntType ®, std::type_identity_t<IntType> mask) { return reg & mask; } + + ALWAYS_INLINE u32 Read(uintptr_t reg, u32 mask) { return Read(reinterpret_cast<volatile u32 *>(reg), mask); } + + template<typename IntType, typename... Masks> requires UnsignedNonConstIntegral<IntType> && ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...)) + ALWAYS_INLINE IntType Read(volatile IntType *reg, const Masks... masks) { return Read(reg, static_cast<IntType>((EncodeMask(masks) | ...))); } + + template<typename IntType, typename... Masks> requires UnsignedNonConstIntegral<IntType> && ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...)) + ALWAYS_INLINE IntType Read(volatile IntType ®, const Masks... masks) { return Read(reg, static_cast<IntType>((EncodeMask(masks) | ...))); } + + template<typename... Masks> requires ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...)) + ALWAYS_INLINE u32 Read(uintptr_t reg, const Masks... masks) { return Read(reg, (EncodeMask(masks) | ...)); } + + template<typename IntType, typename... Values> requires UnsignedNonConstIntegral<IntType> && ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...)) + ALWAYS_INLINE bool HasValue(volatile IntType *reg, const Values... values) { return Read(reg, static_cast<IntType>((EncodeMask(values) | ...))) == static_cast<IntType>(Encode(values...)); } + + template<typename IntType, typename... Values> requires UnsignedNonConstIntegral<IntType> && ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...)) + ALWAYS_INLINE bool HasValue(volatile IntType ®, const Values... values) { return Read(reg, static_cast<IntType>((EncodeMask(values) | ...))) == static_cast<IntType>(Encode(values...)); } + + template<typename... Values> requires ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...)) + ALWAYS_INLINE bool HasValue(uintptr_t reg, const Values... values) { return Read(reg, (EncodeMask(values) | ...)) == Encode(values...); } + + template<typename IntType> requires UnsignedNonConstIntegral<IntType> + ALWAYS_INLINE IntType GetValue(volatile IntType *reg, const BitsMask mask) { return Read(reg, mask) >> GetOffset(mask); } + + template<typename IntType> requires UnsignedNonConstIntegral<IntType> + ALWAYS_INLINE IntType GetValue(volatile IntType ®, const BitsMask mask) { return Read(reg, mask) >> GetOffset(mask); } + + ALWAYS_INLINE u32 GetValue(uintptr_t reg, const BitsMask mask) { return Read(reg, mask) >> GetOffset(mask); } + + template<typename IntType> requires UnsignedNonConstIntegral<IntType> + ALWAYS_INLINE void ReadWrite(volatile IntType *reg, std::type_identity_t<IntType> val, std::type_identity_t<IntType> mask) { *reg = (*reg & (~mask)) | (val & mask); } + + template<typename IntType> requires UnsignedNonConstIntegral<IntType> + ALWAYS_INLINE void ReadWrite(volatile IntType ®, std::type_identity_t<IntType> val, std::type_identity_t<IntType> mask) { reg = ( reg & (~mask)) | (val & mask); } + + ALWAYS_INLINE void ReadWrite(uintptr_t reg, u32 val, u32 mask) { ReadWrite(reinterpret_cast<volatile u32 *>(reg), val, mask); } + + template<typename IntType, typename... Values> requires UnsignedNonConstIntegral<IntType> && ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...)) + ALWAYS_INLINE void ReadWrite(volatile IntType *reg, const Values... values) { return ReadWrite(reg, static_cast<IntType>((EncodeValue(values) | ...)), static_cast<IntType>((EncodeMask(values) | ...))); } + + template<typename IntType, typename... Values> requires UnsignedNonConstIntegral<IntType> && ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...)) + ALWAYS_INLINE void ReadWrite(volatile IntType ®, const Values... values) { return ReadWrite(reg, static_cast<IntType>((EncodeValue(values) | ...)), static_cast<IntType>((EncodeMask(values) | ...))); } + + template<typename... Values> requires ((sizeof...(Values) > 0) && (std::is_same<Values, BitsValue>::value && ...)) + ALWAYS_INLINE void ReadWrite(uintptr_t reg, const Values... values) { return ReadWrite(reg, (EncodeValue(values) | ...), (EncodeMask(values) | ...)); } + + template<typename IntType> requires UnsignedNonConstIntegral<IntType> + ALWAYS_INLINE void SetBits(volatile IntType *reg, std::type_identity_t<IntType> mask) { *reg = *reg | mask; } + + template<typename IntType> requires UnsignedNonConstIntegral<IntType> + ALWAYS_INLINE void SetBits(volatile IntType ®, std::type_identity_t<IntType> mask) { reg = reg | mask; } + + ALWAYS_INLINE void SetBits(uintptr_t reg, u32 mask) { SetBits(reinterpret_cast<volatile u32 *>(reg), mask); } + + template<typename IntType, typename... Masks> requires UnsignedNonConstIntegral<IntType> && ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...)) + ALWAYS_INLINE void SetBits(volatile IntType *reg, const Masks... masks) { return SetBits(reg, static_cast<IntType>((EncodeMask(masks) | ...))); } + + template<typename IntType, typename... Masks> requires UnsignedNonConstIntegral<IntType> && ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...)) + ALWAYS_INLINE void SetBits(volatile IntType ®, const Masks... masks) { return SetBits(reg, static_cast<IntType>((EncodeMask(masks) | ...))); } + + template<typename... Masks> requires ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...)) + ALWAYS_INLINE void SetBits(uintptr_t reg, const Masks... masks) { return SetBits(reg, (EncodeMask(masks) | ...)); } + + template<typename IntType> requires UnsignedNonConstIntegral<IntType> + ALWAYS_INLINE void ClearBits(volatile IntType *reg, std::type_identity_t<IntType> mask) { *reg = *reg & ~mask; } + + template<typename IntType> requires UnsignedNonConstIntegral<IntType> + ALWAYS_INLINE void ClearBits(volatile IntType ®, std::type_identity_t<IntType> mask) { reg = reg & ~mask; } + + ALWAYS_INLINE void ClearBits(uintptr_t reg, u32 mask) { ClearBits(reinterpret_cast<volatile u32 *>(reg), mask); } + + template<typename IntType, typename... Masks> requires UnsignedNonConstIntegral<IntType> && ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...)) + ALWAYS_INLINE void ClearBits(volatile IntType *reg, const Masks... masks) { return ClearBits(reg, static_cast<IntType>((EncodeMask(masks) | ...))); } + + template<typename IntType, typename... Masks> requires UnsignedNonConstIntegral<IntType> && ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...)) + ALWAYS_INLINE void ClearBits(volatile IntType ®, const Masks... masks) { return ClearBits(reg, static_cast<IntType>((EncodeMask(masks) | ...))); } + + template<typename... Masks> requires ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...)) + ALWAYS_INLINE void ClearBits(uintptr_t reg, const Masks... masks) { return ClearBits(reg, (EncodeMask(masks) | ...)); } + + template<typename IntType> requires UnsignedNonConstIntegral<IntType> + ALWAYS_INLINE void MaskBits(volatile IntType *reg, std::type_identity_t<IntType> mask) { *reg = *reg & mask; } + + template<typename IntType> requires UnsignedNonConstIntegral<IntType> + ALWAYS_INLINE void MaskBits(volatile IntType ®, std::type_identity_t<IntType> mask) { reg = reg & mask; } + + ALWAYS_INLINE void MaskBits(uintptr_t reg, u32 mask) { MaskBits(reinterpret_cast<volatile u32 *>(reg), mask); } + + template<typename IntType, typename... Masks> requires UnsignedNonConstIntegral<IntType> && ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...)) + ALWAYS_INLINE void MaskBits(volatile IntType *reg, const Masks... masks) { return MaskBits(reg, static_cast<IntType>((EncodeMask(masks) | ...))); } + + template<typename IntType, typename... Masks> requires UnsignedNonConstIntegral<IntType> && ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...)) + ALWAYS_INLINE void MaskBits(volatile IntType ®, const Masks... masks) { return MaskBits(reg, static_cast<IntType>((EncodeMask(masks) | ...))); } + + template<typename... Masks> requires ((sizeof...(Masks) > 0) && (std::is_same<Masks, BitsMask>::value && ...)) + ALWAYS_INLINE void MaskBits(uintptr_t reg, const Masks... masks) { return MaskBits(reg, (EncodeMask(masks) | ...)); } + + #define REG_BITS_MASK(OFFSET, WIDTH) ::ams::reg::BitsMask{OFFSET, WIDTH} + #define REG_BITS_VALUE(OFFSET, WIDTH, VALUE) ::ams::reg::BitsValue{OFFSET, WIDTH, VALUE} + + #define REG_BITS_VALUE_FROM_MASK(MASK, VALUE) ::ams::reg::GetValue(MASK, VALUE) + + #define REG_NAMED_BITS_MASK(PREFIX, NAME) REG_BITS_MASK(PREFIX##_##NAME##_OFFSET, PREFIX##_##NAME##_WIDTH) + #define REG_NAMED_BITS_VALUE(PREFIX, NAME, VALUE) REG_BITS_VALUE(PREFIX##_##NAME##_OFFSET, PREFIX##_##NAME##_WIDTH, VALUE) + #define REG_NAMED_BITS_ENUM(PREFIX, NAME, ENUM) REG_BITS_VALUE(PREFIX##_##NAME##_OFFSET, PREFIX##_##NAME##_WIDTH, PREFIX##_##NAME##_##ENUM) + + #define REG_NAMED_BITS_ENUM_SEL(PREFIX, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_BITS_VALUE(PREFIX##_##NAME##_OFFSET, PREFIX##_##NAME##_WIDTH, (__COND__) ? PREFIX##_##NAME##_##TRUE_ENUM : PREFIX##_##NAME##_##FALSE_ENUM) + + #define REG_DEFINE_NAMED_REG(PREFIX, NAME, __OFFSET__, __WIDTH__) \ + constexpr inline u32 PREFIX##_##NAME##_OFFSET = __OFFSET__; \ + constexpr inline u32 PREFIX##_##NAME##_WIDTH = __WIDTH__ + + #define REG_DEFINE_NAMED_BIT_ENUM(PREFIX, NAME, __OFFSET__, ZERO, ONE) \ + REG_DEFINE_NAMED_REG(PREFIX, NAME, __OFFSET__, 1); \ + \ + enum PREFIX##_##NAME { \ + PREFIX##_##NAME##_##ZERO = 0, \ + PREFIX##_##NAME##_##ONE = 1, \ + }; + + #define REG_DEFINE_NAMED_TWO_BIT_ENUM(PREFIX, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) \ + REG_DEFINE_NAMED_REG(PREFIX, NAME, __OFFSET__, 2); \ + \ + enum PREFIX##_##NAME { \ + PREFIX##_##NAME##_##ZERO = 0, \ + PREFIX##_##NAME##_##ONE = 1, \ + PREFIX##_##NAME##_##TWO = 2, \ + PREFIX##_##NAME##_##THREE = 3, \ + }; + + #define REG_DEFINE_NAMED_THREE_BIT_ENUM(PREFIX, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) \ + REG_DEFINE_NAMED_REG(PREFIX, NAME, __OFFSET__, 3); \ + \ + enum PREFIX##_##NAME { \ + PREFIX##_##NAME##_##ZERO = 0, \ + PREFIX##_##NAME##_##ONE = 1, \ + PREFIX##_##NAME##_##TWO = 2, \ + PREFIX##_##NAME##_##THREE = 3, \ + PREFIX##_##NAME##_##FOUR = 4, \ + PREFIX##_##NAME##_##FIVE = 5, \ + PREFIX##_##NAME##_##SIX = 6, \ + PREFIX##_##NAME##_##SEVEN = 7, \ + }; + + #define REG_DEFINE_NAMED_FOUR_BIT_ENUM(PREFIX, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) \ + REG_DEFINE_NAMED_REG(PREFIX, NAME, __OFFSET__, 4); \ + \ + enum PREFIX##_##NAME { \ + PREFIX##_##NAME##_##ZERO = 0, \ + PREFIX##_##NAME##_##ONE = 1, \ + PREFIX##_##NAME##_##TWO = 2, \ + PREFIX##_##NAME##_##THREE = 3, \ + PREFIX##_##NAME##_##FOUR = 4, \ + PREFIX##_##NAME##_##FIVE = 5, \ + PREFIX##_##NAME##_##SIX = 6, \ + PREFIX##_##NAME##_##SEVEN = 7, \ + PREFIX##_##NAME##_##EIGHT = 8, \ + PREFIX##_##NAME##_##NINE = 9, \ + PREFIX##_##NAME##_##TEN = 10, \ + PREFIX##_##NAME##_##ELEVEN = 11, \ + PREFIX##_##NAME##_##TWELVE = 12, \ + PREFIX##_##NAME##_##THIRTEEN = 13, \ + PREFIX##_##NAME##_##FOURTEEN = 14, \ + PREFIX##_##NAME##_##FIFTEEN = 15, \ + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results.hpp new file mode 100644 index 00000000..53b35823 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results.hpp @@ -0,0 +1,77 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util.hpp> + +/* Utilities. */ +#include <vapours/results/results_common.hpp> + +/* Official. */ +#include <vapours/results/cal_results.hpp> +#include <vapours/results/capsrv_results.hpp> +#include <vapours/results/creport_results.hpp> +#include <vapours/results/cs_results.hpp> +#include <vapours/results/dd_results.hpp> +#include <vapours/results/ddsf_results.hpp> +#include <vapours/results/debug_results.hpp> +#include <vapours/results/dmnt_results.hpp> +#include <vapours/results/erpt_results.hpp> +#include <vapours/results/err_results.hpp> +#include <vapours/results/fatal_results.hpp> +#include <vapours/results/fs_results.hpp> +#include <vapours/results/gpio_results.hpp> +#include <vapours/results/hipc_results.hpp> +#include <vapours/results/htc_results.hpp> +#include <vapours/results/htcfs_results.hpp> +#include <vapours/results/htclow_results.hpp> +#include <vapours/results/htcs_results.hpp> +#include <vapours/results/i2c_results.hpp> +#include <vapours/results/kvdb_results.hpp> +#include <vapours/results/loader_results.hpp> +#include <vapours/results/lr_results.hpp> +#include <vapours/results/ncm_results.hpp> +#include <vapours/results/nim_results.hpp> +#include <vapours/results/ns_results.hpp> +#include <vapours/results/os_results.hpp> +#include <vapours/results/osdbg_results.hpp> +#include <vapours/results/pcv_results.hpp> +#include <vapours/results/pgl_results.hpp> +#include <vapours/results/pm_results.hpp> +#include <vapours/results/powctl_results.hpp> +#include <vapours/results/psc_results.hpp> +#include <vapours/results/pwm_results.hpp> +#include <vapours/results/ro_results.hpp> +#include <vapours/results/scs_results.hpp> +#include <vapours/results/sdmmc_results.hpp> +#include <vapours/results/settings_results.hpp> +#include <vapours/results/sf_results.hpp> +#include <vapours/results/sm_results.hpp> +#include <vapours/results/socket_results.hpp> +#include <vapours/results/spl_results.hpp> +#include <vapours/results/sprofile_results.hpp> +#include <vapours/results/svc_results.hpp> +#include <vapours/results/time_results.hpp> +#include <vapours/results/tipc_results.hpp> +#include <vapours/results/tma_results.hpp> +#include <vapours/results/updater_results.hpp> +#include <vapours/results/usb_results.hpp> +#include <vapours/results/vi_results.hpp> + +/* Unofficial. */ +#include <vapours/results/exosphere_results.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/cal_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/cal_results.hpp new file mode 100644 index 00000000..cb770ef5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/cal_results.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> +#include <vapours/results/powctl_results.hpp> + +namespace ams::cal { + + using powctl::ResultCalibrationDataCrcError; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/capsrv_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/capsrv_results.hpp new file mode 100644 index 00000000..f1b36c3b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/capsrv_results.hpp @@ -0,0 +1,94 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/results/results_common.hpp> + + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::capsrv, 206); + +namespace ams::capsrv { + + R_DEFINE_ERROR_RANGE(AlbumError, 2, 99); + R_DEFINE_ERROR_RESULT(AlbumWorkMemoryError, 3); + + R_DEFINE_ERROR_RESULT(AlbumAlreadyOpened, 7); + R_DEFINE_ERROR_RESULT(AlbumOutOfRange, 8); + + R_DEFINE_ERROR_RANGE(AlbumInvalidFileId, 10, 19); + R_DEFINE_ERROR_RESULT(AlbumInvalidApplicationId, 11); + R_DEFINE_ERROR_RESULT(AlbumInvalidTimestamp, 12); + R_DEFINE_ERROR_RESULT(AlbumInvalidStorage, 13); + R_DEFINE_ERROR_RESULT(AlbumInvalidFileContents, 14); + + R_DEFINE_ERROR_RESULT(AlbumIsNotMounted, 21); + R_DEFINE_ERROR_RESULT(AlbumIsFull, 22); + R_DEFINE_ERROR_RESULT(AlbumFileNotFound, 23); + R_DEFINE_ERROR_RESULT(AlbumInvalidFileData, 24); + R_DEFINE_ERROR_RESULT(AlbumFileCountLimit, 25); + R_DEFINE_ERROR_RESULT(AlbumFileNoThumbnail, 26); + + R_DEFINE_ERROR_RESULT(AlbumReadBufferShortage, 30); + + R_DEFINE_ERROR_RANGE(AlbumFileSystemError, 90, 99); + R_DEFINE_ERROR_RANGE(AlbumAccessCorrupted, 94, 96); + R_DEFINE_ERROR_RESULT(AlbumDestinationAccessCorrupted, 96); + + R_DEFINE_ERROR_RANGE(ControlError, 800, 899); + R_DEFINE_ERROR_RESULT(ControlResourceLimit, 820); + R_DEFINE_ERROR_RESULT(ControlNotOpened, 822); + + R_DEFINE_ERROR_RESULT(NotSupported, 1023); + + R_DEFINE_ERROR_RANGE(InternalError, 1024, 2047); + R_DEFINE_ERROR_RESULT(InternalJpegEncoderError, 1210); + R_DEFINE_ERROR_RESULT(InternalJpegOutBufferShortage, 1211); + R_DEFINE_ERROR_RESULT(InternalJpegWorkMemoryShortage, 1212); + + R_DEFINE_ERROR_RANGE(InternalFileDataVerificationError, 1300, 1399); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationEmptyFileData, 1301); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationExifExtractionFailed, 1302); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationExifAnalyzationFailed, 1303); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationDateTimeExtractionFailed, 1304); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationInvalidDateTimeLength, 1305); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationInconsistentDateTime, 1306); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationMakerNoteExtractionFailed, 1307); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationInconsistentApplicationId, 1308); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationInconsistentSignature, 1309); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationUnsupportedOrientation, 1310); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationInvalidDataDimension, 1311); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationInconsistentOrientation, 1312); + + R_DEFINE_ERROR_RANGE(InternalAlbumLimitationError, 1400, 1499); + R_DEFINE_ERROR_RESULT(InternalAlbumLimitationFileCountLimit, 1401); + + R_DEFINE_ERROR_RANGE(InternalSignatureError, 1500, 1599); + R_DEFINE_ERROR_RESULT(InternalSignatureExifExtractionFailed, 1501); + R_DEFINE_ERROR_RESULT(InternalSignatureMakerNoteExtractionFailed, 1502); + + R_DEFINE_ERROR_RANGE(InternalAlbumSessionError, 1700, 1799); + R_DEFINE_ERROR_RESULT(InternalAlbumLimitationSessionCountLimit, 1701); + + R_DEFINE_ERROR_RANGE(InternalAlbumTemporaryFileError, 1900, 1999); + R_DEFINE_ERROR_RESULT(InternalAlbumTemporaryFileCountLimit, 1901); + R_DEFINE_ERROR_RESULT(InternalAlbumTemporaryFileCreateError, 1902); + R_DEFINE_ERROR_RESULT(InternalAlbumTemporaryFileCreateRetryCountLimit, 1903); + R_DEFINE_ERROR_RESULT(InternalAlbumTemporaryFileOpenError, 1904); + R_DEFINE_ERROR_RESULT(InternalAlbumTemporaryFileGetFileSizeError, 1905); + R_DEFINE_ERROR_RESULT(InternalAlbumTemporaryFileSetFileSizeError, 1906); + R_DEFINE_ERROR_RESULT(InternalAlbumTemporaryFileReadFileError, 1907); + R_DEFINE_ERROR_RESULT(InternalAlbumTemporaryFileWriteFileError, 1908); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/creport_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/creport_results.hpp new file mode 100644 index 00000000..f7d424e6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/creport_results.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::creport, 168); + +namespace ams::creport { + + R_DEFINE_ERROR_RESULT(UndefinedInstruction, 0); + R_DEFINE_ERROR_RESULT(InstructionAbort, 1); + R_DEFINE_ERROR_RESULT(DataAbort, 2); + R_DEFINE_ERROR_RESULT(AlignmentFault, 3); + R_DEFINE_ERROR_RESULT(DebuggerAttached, 4); + R_DEFINE_ERROR_RESULT(BreakPoint, 5); + R_DEFINE_ERROR_RESULT(UserBreak, 6); + R_DEFINE_ERROR_RESULT(DebuggerBreak, 7); + R_DEFINE_ERROR_RESULT(UndefinedSystemCall, 8); + R_DEFINE_ERROR_RESULT(MemorySystemError, 9); + + R_DEFINE_ERROR_RESULT(IncompleteReport, 99); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/cs_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/cs_results.hpp new file mode 100644 index 00000000..8ff32ac7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/cs_results.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::cs, 204); + +namespace ams::cs { + + R_DEFINE_ERROR_RESULT(UnknownCommand, 2); + R_DEFINE_ERROR_RESULT(OutOfResource, 4); + R_DEFINE_ERROR_RESULT(NoSocket, 7); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/dd_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/dd_results.hpp new file mode 100644 index 00000000..dd1f042b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/dd_results.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::dd, 6); + +namespace ams::dd { + + R_DEFINE_ERROR_RESULT(EndOfQuery, 1); + R_DEFINE_ERROR_RESULT(InvalidCurrentMemory, 2); + R_DEFINE_ERROR_RESULT(NotSingleRegion, 3); + R_DEFINE_ERROR_RESULT(InvalidMemoryState, 4); + R_DEFINE_ERROR_RESULT(OutOfMemory, 5); + R_DEFINE_ERROR_RESULT(OutOfResource, 6); + R_DEFINE_ERROR_RESULT(NotSupported, 7); + R_DEFINE_ERROR_RESULT(InvalidHandle, 8); + + R_DEFINE_ERROR_RESULT(InternalError, 1023); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/ddsf_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/ddsf_results.hpp new file mode 100644 index 00000000..35725429 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/ddsf_results.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::ddsf, 30); + +namespace ams::ddsf { + + R_DEFINE_ERROR_RESULT(OutOfResource, 1); + R_DEFINE_ERROR_RESULT(NotSupported, 2); + R_DEFINE_ERROR_RESULT(InvalidArgument, 3); + R_DEFINE_ERROR_RESULT(PermissionDenied, 4); + R_DEFINE_ERROR_RESULT(AccessModeDenied, 5); + R_DEFINE_ERROR_RESULT(DeviceCodeNotFound, 6); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/debug_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/debug_results.hpp new file mode 100644 index 00000000..6db0d722 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/debug_results.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::dbg, 183); + +namespace ams::dbg { + + R_DEFINE_ERROR_RESULT(CannotDebug, 1); + R_DEFINE_ERROR_RESULT(AlreadyAttached, 2); + R_DEFINE_ERROR_RESULT(Cancelled, 3); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/dmnt_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/dmnt_results.hpp new file mode 100644 index 00000000..f6203c9b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/dmnt_results.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::dmnt, 13); + +namespace ams::dmnt { + + R_DEFINE_ERROR_RESULT(Unknown, 1); + R_DEFINE_ERROR_RESULT(DebuggingDisabled, 2); + + /* Atmosphere extension. */ + // namespace cheat { + + R_DEFINE_ABSTRACT_ERROR_RANGE_NS(cheat, CheatError, 6500, 6599); + R_DEFINE_ERROR_RESULT_NS(cheat, CheatNotAttached, 6500); + R_DEFINE_ERROR_RESULT_NS(cheat, CheatNullBuffer, 6501); + R_DEFINE_ERROR_RESULT_NS(cheat, CheatInvalidBuffer, 6502); + R_DEFINE_ERROR_RESULT_NS(cheat, CheatUnknownId, 6503); + R_DEFINE_ERROR_RESULT_NS(cheat, CheatOutOfResource, 6504); + R_DEFINE_ERROR_RESULT_NS(cheat, CheatInvalid, 6505); + R_DEFINE_ERROR_RESULT_NS(cheat, CheatCannotDisable, 6506); + + R_DEFINE_ABSTRACT_ERROR_RANGE_NS(cheat, FrozenAddressError, 6600, 6699); + R_DEFINE_ERROR_RESULT_NS(cheat, FrozenAddressInvalidWidth, 6600); + R_DEFINE_ERROR_RESULT_NS(cheat, FrozenAddressAlreadyExists, 6601); + R_DEFINE_ERROR_RESULT_NS(cheat, FrozenAddressNotFound, 6602); + R_DEFINE_ERROR_RESULT_NS(cheat, FrozenAddressOutOfResource, 6603); + + R_DEFINE_ABSTRACT_ERROR_RANGE_NS(cheat, VirtualMachineError, 6700, 6799); + R_DEFINE_ERROR_RESULT_NS(cheat, VirtualMachineInvalidConditionDepth, 6700); + + // } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/erpt_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/erpt_results.hpp new file mode 100644 index 00000000..10c1b9aa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/erpt_results.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::erpt, 147); + +namespace ams::erpt { + + R_DEFINE_ERROR_RESULT(NotInitialized, 1); + R_DEFINE_ERROR_RESULT(AlreadyInitialized, 2); + R_DEFINE_ERROR_RESULT(OutOfArraySpace, 3); + R_DEFINE_ERROR_RESULT(OutOfFieldSpace, 4); + R_DEFINE_ERROR_RESULT(OutOfMemory, 5); + R_DEFINE_ERROR_RESULT(NotSupported, 6); + R_DEFINE_ERROR_RESULT(InvalidArgument, 7); + R_DEFINE_ERROR_RESULT(NotFound, 8); + R_DEFINE_ERROR_RESULT(FieldCategoryMismatch, 9); + R_DEFINE_ERROR_RESULT(FieldTypeMismatch, 10); + R_DEFINE_ERROR_RESULT(AlreadyExists, 11); + R_DEFINE_ERROR_RESULT(CorruptJournal, 12); + R_DEFINE_ERROR_RESULT(CategoryNotFound, 13); + R_DEFINE_ERROR_RESULT(RequiredContextMissing, 14); + R_DEFINE_ERROR_RESULT(RequiredFieldMissing, 15); + R_DEFINE_ERROR_RESULT(FormatterError, 16); + R_DEFINE_ERROR_RESULT(InvalidPowerState, 17); + R_DEFINE_ERROR_RESULT(ArrayFieldTooLarge, 18); + R_DEFINE_ERROR_RESULT(AlreadyOwned, 19); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/err_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/err_results.hpp new file mode 100644 index 00000000..6dd43c88 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/err_results.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::err, 162); + +namespace ams::err { + + R_DEFINE_ERROR_RESULT(ApplicationAbort, 1); + R_DEFINE_ERROR_RESULT(SystemProgramAbort, 2); + + R_DEFINE_ERROR_RESULT(ForcedShutdownDetected, 4); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/exosphere_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/exosphere_results.hpp new file mode 100644 index 00000000..be02b877 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/exosphere_results.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +/* Please note: These results are all custom, and not official. */ +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::exosphere, 444); + +namespace ams::exosphere { + + /* Result 1-1000 reserved for Atmosphere. */ + R_DEFINE_ERROR_RESULT(NotPresent, 1); + R_DEFINE_ERROR_RESULT(VersionMismatch, 2); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/fatal_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/fatal_results.hpp new file mode 100644 index 00000000..23d75720 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/fatal_results.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::fatal, 163); + +namespace ams::fatal { + + R_DEFINE_ERROR_RESULT(AllocationFailed, 1); + R_DEFINE_ERROR_RESULT(NullGraphicsBuffer, 2); + R_DEFINE_ERROR_RESULT(AlreadyThrown, 3); + R_DEFINE_ERROR_RESULT(TooManyEvents, 4); + R_DEFINE_ERROR_RESULT(InRepairWithoutVolHeld, 5); + R_DEFINE_ERROR_RESULT(InRepairWithoutTimeReviserCartridge, 6); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/fs_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/fs_results.hpp new file mode 100644 index 00000000..104a299f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/fs_results.hpp @@ -0,0 +1,606 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::fs, 2); + +namespace ams::fs { + + R_DEFINE_ERROR_RANGE(HandledByAllProcess, 0, 999); + R_DEFINE_ERROR_RESULT(PathNotFound, 1); + R_DEFINE_ERROR_RESULT(PathAlreadyExists, 2); + + R_DEFINE_ERROR_RESULT(TargetLocked, 7); + R_DEFINE_ERROR_RESULT(DirectoryNotEmpty, 8); + + R_DEFINE_ERROR_RANGE (NotEnoughFreeSpace, 30, 45); + R_DEFINE_ERROR_RANGE(NotEnoughFreeSpaceBis, 34, 38); + R_DEFINE_ERROR_RESULT(NotEnoughFreeSpaceBisCalibration, 35); + R_DEFINE_ERROR_RESULT(NotEnoughFreeSpaceBisSafe, 36); + R_DEFINE_ERROR_RESULT(NotEnoughFreeSpaceBisUser, 37); + R_DEFINE_ERROR_RESULT(NotEnoughFreeSpaceBisSystem, 38); + R_DEFINE_ERROR_RESULT(NotEnoughFreeSpaceSdCard, 39); + + R_DEFINE_ERROR_RESULT(UnsupportedSdkVersion, 50); + + R_DEFINE_ERROR_RESULT(MountNameAlreadyExists, 60); + + R_DEFINE_ERROR_RANGE(HandledBySystemProcess, 1000, 2999); + R_DEFINE_ERROR_RESULT(PartitionNotFound, 1001); + R_DEFINE_ERROR_RESULT(TargetNotFound, 1002); + R_DEFINE_ERROR_RESULT(NcaExternalKeyNotFound, 1004); + + R_DEFINE_ERROR_RANGE(SdCardAccessFailed, 2000, 2499); + R_DEFINE_ERROR_RESULT(SdCardNotPresent, 2001); + + R_DEFINE_ERROR_RANGE(GameCardAccessFailed, 2500, 2999); + R_DEFINE_ERROR_RESULT(GameCardPreconditionViolation, 2503); + + R_DEFINE_ERROR_RANGE(GameCardCardAccessFailure, 2530, 2559); + R_DEFINE_ERROR_RANGE(CameCardWrongCard, 2543, 2546); + R_DEFINE_ERROR_RESULT(GameCardInitialDataMismatch, 2544); + R_DEFINE_ERROR_RESULT(GameCardInitialNotFilledWithZero, 2545); + R_DEFINE_ERROR_RESULT(GameCardKekIndexMismatch, 2546); + + R_DEFINE_ERROR_RESULT(GameCardInvalidCardHeader, 2554); + R_DEFINE_ERROR_RESULT(GameCardInvalidT1CardCertificate, 2555); + R_DEFINE_ERROR_RESULT(GameCardInvalidCa10Certificate, 2557); + + R_DEFINE_ERROR_RANGE(GameCardSplFailure, 2665, 2669); + R_DEFINE_ERROR_RESULT(GameCardSplDecryptAesKeyFailure, 2666); + + R_DEFINE_ERROR_RESULT(NotImplemented, 3001); + R_DEFINE_ERROR_RESULT(UnsupportedVersion, 3002); + R_DEFINE_ERROR_RESULT(OutOfRange, 3005); + + R_DEFINE_ERROR_RESULT(SystemPartitionNotReady, 3100); + + R_DEFINE_ERROR_RANGE(AllocationMemoryFailed, 3200, 3499); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInFatFileSystemA, 3201); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInFatFileSystemC, 3203); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInFatFileSystemD, 3204); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInFatFileSystemE, 3205); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInFatFileSystemF, 3206); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInFatFileSystemH, 3208); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInFileSystemAccessorA, 3211); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInFileSystemAccessorB, 3212); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInApplicationA, 3213); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInBcatSaveDataA, 3214); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInBisA, 3215); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInBisB, 3216); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInBisC, 3217); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInCodeA, 3218); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInContentA, 3219); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInContentStorageA, 3220); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInContentStorageB, 3221); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInDataA, 3222); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInDataB, 3223); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInDeviceSaveDataA, 3224); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGameCardA, 3225); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGameCardB, 3226); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGameCardC, 3227); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGameCardD, 3228); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInHostA, 3229); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInHostB, 3230); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInHostC, 3231); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInImageDirectoryA, 3232); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInLogoA, 3233); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInRomA, 3234); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInRomB, 3235); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInRomC, 3236); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInRomD, 3237); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInRomE, 3238); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInRomF, 3239); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSaveDataManagementA, 3242); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSaveDataThumbnailA, 3243); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSdCardA, 3244); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSdCardB, 3245); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSystemSaveDataA, 3246); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInRomFsFileSystemA, 3247); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInRomFsFileSystemB, 3248); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInRomFsFileSystemC, 3249); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGuidPartitionTableA, 3251); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInDeviceDetectionEventManagerA, 3252); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSaveDataFileSystemServiceImplA, 3253); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInFileSystemProxyCoreImplB, 3254); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSdCardProxyFileSystemCreatorA, 3255); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInNcaFileSystemServiceImplA, 3256); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInNcaFileSystemServiceImplB, 3257); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInProgramRegistryManagerA, 3258); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSdmmcStorageServiceA, 3259); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInBuiltInStorageCreatorA, 3260); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInBuiltInStorageCreatorB, 3261); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInBuiltInStorageCreatorC, 3262); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedFatFileSystemWithBufferA, 3264); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInFatFileSystemCreatorA, 3265); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInFatFileSystemCreatorB, 3266); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGameCardFileSystemCreatorA, 3267); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGameCardFileSystemCreatorB, 3268); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGameCardFileSystemCreatorC, 3269); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGameCardFileSystemCreatorD, 3270); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGameCardFileSystemCreatorE, 3271); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGameCardFileSystemCreatorF, 3272); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGameCardManagerA, 3273); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGameCardManagerB, 3274); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGameCardManagerC, 3275); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGameCardManagerD, 3276); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGameCardManagerE, 3277); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGameCardManagerF, 3278); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInLocalFileSystemCreatorA, 3279); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInPartitionFileSystemCreatorA, 3280); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInRomFileSystemCreatorA, 3281); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSaveDataFileSystemCreatorA, 3282); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSaveDataFileSystemCreatorB, 3283); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSaveDataFileSystemCreatorC, 3284); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSaveDataFileSystemCreatorD, 3285); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSaveDataFileSystemCreatorE, 3286); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInStorageOnNcaCreatorA, 3288); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInStorageOnNcaCreatorB, 3289); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSubDirectoryFileSystemCreatorA, 3290); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInTargetManagerFileSystemCreatorA, 3291); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSaveDataIndexerA, 3292); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSaveDataIndexerB, 3293); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInFileSystemBuddyHeapA, 3294); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInFileSystemBufferManagerA, 3295); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInBlockCacheBufferedStorageA, 3296); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInBlockCacheBufferedStorageB, 3297); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInDuplexStorageA, 3298); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInIntegrityVerificationStorageA, 3304); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInIntegrityVerificationStorageB, 3305); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInJournalStorageA, 3306); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInJournalStorageB, 3307); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSaveDataFileSystemCoreA, 3310); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSaveDataFileSystemCoreB, 3311); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInAesXtsFileA, 3312); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInAesXtsFileB, 3313); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInAesXtsFileC, 3314); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInAesXtsFileD, 3315); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInAesXtsFileSystemA, 3316); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInConcatenationFileSystemA, 3319); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInConcatenationFileSystemB, 3320); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInDirectorySaveDataFileSystemA, 3321); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInLocalFileSystemA, 3322); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInLocalFileSystemB, 3323); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInNcaFileSystemDriverI, 3341); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInPartitionFileSystemA, 3347); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInPartitionFileSystemB, 3348); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInPartitionFileSystemC, 3349); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInPartitionFileSystemMetaA, 3350); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInPartitionFileSystemMetaB, 3351); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInRomFsFileSystemD, 3352); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSubdirectoryFileSystemA, 3355); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInTmFileSystemA, 3356); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInTmFileSystemB, 3357); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInProxyFileSystemA, 3359); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInProxyFileSystemB, 3360); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSaveDataExtraDataAccessorCacheManagerA, 3362); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInNcaReaderA, 3363); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInRegisterA, 3365); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInRegisterB, 3366); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInPathNormalizer, 3367); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInDbmRomKeyValueStorage, 3375); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInDbmHierarchicalRomFileTable, 3376); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInRomFsFileSystemE, 3377); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInISaveFileSystemA, 3378); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInISaveFileSystemB, 3379); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInRomOnFileA, 3380); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInRomOnFileB, 3381); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInRomOnFileC, 3382); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInAesXtsFileE, 3383); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInAesXtsFileF, 3384); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInAesXtsFileG, 3385); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInReadOnlyFileSystemA, 3386); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInEncryptedFileSystemCreatorA, 3394); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInAesCtrCounterExtendedStorageA, 3399); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInAesCtrCounterExtendedStorageB, 3400); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSdmmcStorageServiceB, 3406); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInFileSystemInterfaceAdapterA, 3407); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGameCardFileSystemCreatorG, 3408); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGameCardFileSystemCreatorH, 3409); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInAesXtsFileSystemB, 3410); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInBufferedStorageA, 3411); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInIntegrityRomFsStorageA, 3412); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSaveDataFileSystemServiceImplB, 3416); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedNew, 3420); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInFileSystemProxyImplA, 3421); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedMakeUnique, 3422); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedAllocateShared, 3423); + R_DEFINE_ERROR_RESULT(AllocationPooledBufferNotEnoughSize, 3424); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInWriteThroughCacheStorageA, 3428); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSaveDataTransferManagerA, 3429); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInSaveDataTransferManagerB, 3430); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInHtcFileSystemA, 3431); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInHtcFileSystemB, 3432); + R_DEFINE_ERROR_RESULT(AllocationMemoryFailedInGameCardManagerG, 3433); + + R_DEFINE_ERROR_RANGE(Internal, 3000, 7999); + R_DEFINE_ERROR_RANGE(MmcAccessFailed, 3500, 3999); + + R_DEFINE_ERROR_RANGE(DataCorrupted, 4000, 4999); + R_DEFINE_ERROR_RANGE(RomCorrupted, 4001, 4299); + R_DEFINE_ERROR_RESULT(UnsupportedRomVersion, 4002); + + R_DEFINE_ERROR_RANGE(AesCtrCounterExtendedStorageCorrupted, 4011, 4019); + R_DEFINE_ERROR_RESULT(InvalidAesCtrCounterExtendedEntryOffset, 4012); + R_DEFINE_ERROR_RESULT(InvalidAesCtrCounterExtendedTableSize, 4013); + R_DEFINE_ERROR_RESULT(InvalidAesCtrCounterExtendedGeneration, 4014); + R_DEFINE_ERROR_RESULT(InvalidAesCtrCounterExtendedOffset, 4015); + + R_DEFINE_ERROR_RANGE(IndirectStorageCorrupted, 4021, 4029); + R_DEFINE_ERROR_RESULT(InvalidIndirectEntryOffset, 4022); + R_DEFINE_ERROR_RESULT(InvalidIndirectEntryStorageIndex, 4023); + R_DEFINE_ERROR_RESULT(InvalidIndirectStorageSize, 4024); + R_DEFINE_ERROR_RESULT(InvalidIndirectVirtualOffset, 4025); + R_DEFINE_ERROR_RESULT(InvalidIndirectPhysicalOffset, 4026); + R_DEFINE_ERROR_RESULT(InvalidIndirectStorageIndex, 4027); + + R_DEFINE_ERROR_RANGE(BucketTreeCorrupted, 4031, 4039); + R_DEFINE_ERROR_RESULT(InvalidBucketTreeSignature, 4032); + R_DEFINE_ERROR_RESULT(InvalidBucketTreeEntryCount, 4033); + R_DEFINE_ERROR_RESULT(InvalidBucketTreeNodeEntryCount, 4034); + R_DEFINE_ERROR_RESULT(InvalidBucketTreeNodeOffset, 4035); + R_DEFINE_ERROR_RESULT(InvalidBucketTreeEntryOffset, 4036); + R_DEFINE_ERROR_RESULT(InvalidBucketTreeEntrySetOffset, 4037); + R_DEFINE_ERROR_RESULT(InvalidBucketTreeNodeIndex, 4038); + R_DEFINE_ERROR_RESULT(InvalidBucketTreeVirtualOffset, 4039); + + R_DEFINE_ERROR_RANGE(RomNcaCorrupted, 4041, 4139); + R_DEFINE_ERROR_RANGE(RomNcaFileSystemCorrupted, 4051, 4069); + R_DEFINE_ERROR_RESULT(InvalidRomNcaFileSystemType, 4052); + R_DEFINE_ERROR_RESULT(InvalidRomAcidFileSize, 4053); + R_DEFINE_ERROR_RESULT(InvalidRomAcidSize, 4054); + R_DEFINE_ERROR_RESULT(InvalidRomAcid, 4055); + R_DEFINE_ERROR_RESULT(RomAcidVerificationFailed, 4056); + R_DEFINE_ERROR_RESULT(InvalidRomNcaSignature, 4057); + R_DEFINE_ERROR_RESULT(RomNcaHeaderSignature1VerificationFailed, 4058); + R_DEFINE_ERROR_RESULT(RomNcaHeaderSignature2VerificationFailed, 4059); + R_DEFINE_ERROR_RESULT(RomNcaFsHeaderHashVerificationFailed, 4060); + R_DEFINE_ERROR_RESULT(InvalidRomNcaKeyIndex, 4061); + R_DEFINE_ERROR_RESULT(InvalidRomNcaFsHeaderHashType, 4062); + R_DEFINE_ERROR_RESULT(InvalidRomNcaFsHeaderEncryptionType, 4063); + + R_DEFINE_ERROR_RANGE(RomNcaHierarchicalSha256StorageCorrupted, 4071, 4079); + R_DEFINE_ERROR_RESULT(InvalidRomHierarchicalSha256BlockSize, 4072); + R_DEFINE_ERROR_RESULT(InvalidRomHierarchicalSha256LayerCount, 4073); + R_DEFINE_ERROR_RESULT(RomHierarchicalSha256BaseStorageTooLarge, 4074); + R_DEFINE_ERROR_RESULT(RomHierarchicalSha256HashVerificationFailed, 4075); + + R_DEFINE_ERROR_RESULT(RomNcaInvalidHierarchicalIntegrityVerificationLayerCount, 4081); + R_DEFINE_ERROR_RESULT(RomNcaInvalidNcaIndirectStorageOutOfRange, 4082); + R_DEFINE_ERROR_RESULT(RomNcaInvalidCompressionInfo, 4083); + R_DEFINE_ERROR_RESULT(RomNcaInvalidPatchMetaDataHashType, 4084); + R_DEFINE_ERROR_RESULT(RomNcaInvalidIntegrityLayerInfoOffset, 4085); + R_DEFINE_ERROR_RESULT(RomNcaInvalidPatchMetaDataHashDataSize, 4086); + R_DEFINE_ERROR_RESULT(RomNcaInvalidPatchMetaDataHashDataOffset, 4087); + R_DEFINE_ERROR_RESULT(RomNcaInvalidPatchMetaDataHashDataHash, 4088); + R_DEFINE_ERROR_RESULT(RomNcaInvalidSparseMetaDataHashType, 4089); + R_DEFINE_ERROR_RESULT(RomNcaInvalidSparseMetaDataHashDataSize, 4090); + R_DEFINE_ERROR_RESULT(RomNcaInvalidSparseMetaDataHashDataOffset, 4091); + R_DEFINE_ERROR_RESULT(RomNcaInvalidSparseMetaDataHashDataHash, 4092); + + R_DEFINE_ERROR_RANGE(RomIntegrityVerificationStorageCorrupted, 4141, 4179); + R_DEFINE_ERROR_RESULT(IncorrectRomIntegrityVerificationMagic, 4142); + R_DEFINE_ERROR_RESULT(InvalidRomZeroHash, 4143); + R_DEFINE_ERROR_RESULT(RomNonRealDataVerificationFailed, 4144); + R_DEFINE_ERROR_RESULT(InvalidRomHierarchicalIntegrityVerificationLayerCount, 4145); + + R_DEFINE_ERROR_RANGE(RomRealDataVerificationFailed, 4151, 4159); + R_DEFINE_ERROR_RESULT(ClearedRomRealDataVerificationFailed, 4152); + R_DEFINE_ERROR_RESULT(UnclearedRomRealDataVerificationFailed, 4153); + + R_DEFINE_ERROR_RANGE(RomPartitionFileSystemCorrupted, 4181, 4199); + R_DEFINE_ERROR_RESULT(InvalidRomSha256PartitionHashTarget, 4182); + R_DEFINE_ERROR_RESULT(RomSha256PartitionHashVerificationFailed, 4183); + R_DEFINE_ERROR_RESULT(RomPartitionSignatureVerificationFailed, 4184); + R_DEFINE_ERROR_RESULT(RomSha256PartitionSignatureVerificationFailed, 4185); + R_DEFINE_ERROR_RESULT(InvalidRomPartitionEntryOffset, 4186); + R_DEFINE_ERROR_RESULT(InvalidRomSha256PartitionMetaDataSize, 4187); + + R_DEFINE_ERROR_RANGE(RomBuiltInStorageCorrupted, 4201, 4219); + R_DEFINE_ERROR_RESULT(RomGptHeaderVerificationFailed, 4202); + + R_DEFINE_ERROR_RANGE(RomHostFileSystemCorrupted, 4241, 4259); + R_DEFINE_ERROR_RESULT(RomHostEntryCorrupted, 4242); + R_DEFINE_ERROR_RESULT(RomHostFileDataCorrupted, 4243); + R_DEFINE_ERROR_RESULT(RomHostFileCorrupted, 4244); + R_DEFINE_ERROR_RESULT(InvalidRomHostHandle, 4245); + + R_DEFINE_ERROR_RANGE(RomDatabaseCorrupted, 4261, 4279); + R_DEFINE_ERROR_RESULT(InvalidRomAllocationTableBlock, 4262); + R_DEFINE_ERROR_RESULT(InvalidRomKeyValueListElementIndex, 4263); + + R_DEFINE_ERROR_RANGE(SaveDataCorrupted, 4301, 4499); + R_DEFINE_ERROR_RANGE(NcaCorrupted, 4501, 4599); + R_DEFINE_ERROR_RESULT(NcaBaseStorageOutOfRangeA, 4508); + R_DEFINE_ERROR_RESULT(NcaBaseStorageOutOfRangeB, 4509); + R_DEFINE_ERROR_RESULT(NcaBaseStorageOutOfRangeC, 4510); + R_DEFINE_ERROR_RESULT(NcaBaseStorageOutOfRangeD, 4511); + + R_DEFINE_ERROR_RESULT_CLASS_IMPL(NcaFileSystemCorrupted, 4512, 4529); + R_DEFINE_ERROR_RESULT(InvalidNcaFileSystemType, 4512); + R_DEFINE_ERROR_RESULT(InvalidAcidFileSize, 4513); + R_DEFINE_ERROR_RESULT(InvalidAcidSize, 4514); + R_DEFINE_ERROR_RESULT(InvalidAcid, 4515); + R_DEFINE_ERROR_RESULT(AcidVerificationFailed, 4516); + R_DEFINE_ERROR_RESULT(InvalidNcaSignature, 4517); + R_DEFINE_ERROR_RESULT(NcaHeaderSignature1VerificationFailed, 4518); + R_DEFINE_ERROR_RESULT(NcaHeaderSignature2VerificationFailed, 4519); + R_DEFINE_ERROR_RESULT(NcaFsHeaderHashVerificationFailed, 4520); + R_DEFINE_ERROR_RESULT(InvalidNcaKeyIndex, 4521); + R_DEFINE_ERROR_RESULT(InvalidNcaFsHeaderHashType, 4522); + R_DEFINE_ERROR_RESULT(InvalidNcaFsHeaderEncryptionType, 4523); + R_DEFINE_ERROR_RESULT(InvalidNcaPatchInfoIndirectSize, 4524); + R_DEFINE_ERROR_RESULT(InvalidNcaPatchInfoAesCtrExSize, 4525); + R_DEFINE_ERROR_RESULT(InvalidNcaPatchInfoAesCtrExOffset, 4526); + R_DEFINE_ERROR_RESULT(InvalidNcaId, 4527); + R_DEFINE_ERROR_RESULT(InvalidNcaHeader, 4528); + R_DEFINE_ERROR_RESULT(InvalidNcaFsHeader, 4529); + + R_DEFINE_ERROR_RESULT(NcaBaseStorageOutOfRangeE, 4530); + + R_DEFINE_ERROR_RANGE(NcaHierarchicalSha256StorageCorrupted, 4531, 4539); + R_DEFINE_ERROR_RESULT(InvalidHierarchicalSha256BlockSize, 4532); + R_DEFINE_ERROR_RESULT(InvalidHierarchicalSha256LayerCount, 4533); + R_DEFINE_ERROR_RESULT(HierarchicalSha256BaseStorageTooLarge, 4534); + R_DEFINE_ERROR_RESULT(HierarchicalSha256HashVerificationFailed, 4535); + + /* TODO: Range? */ + R_DEFINE_ERROR_RESULT(InvalidNcaHierarchicalIntegrityVerificationLayerCount, 4541); + R_DEFINE_ERROR_RESULT(InvalidNcaIndirectStorageOutOfRange, 4542); + R_DEFINE_ERROR_RESULT(InvalidNcaHeader1SignatureKeyGeneration, 4543); + + /* TODO: Range? */ + R_DEFINE_ERROR_RESULT(InvalidCompressedStorageSize, 4547); + R_DEFINE_ERROR_RESULT(InvalidNcaMetaDataHashDataSize, 4548); + R_DEFINE_ERROR_RESULT(InvalidNcaMetaDataHashDataHash, 4549); + + R_DEFINE_ERROR_RANGE(IntegrityVerificationStorageCorrupted, 4601, 4639); + R_DEFINE_ERROR_RESULT(IncorrectIntegrityVerificationMagic, 4602); + R_DEFINE_ERROR_RESULT(InvalidZeroHash, 4603); + R_DEFINE_ERROR_RESULT(NonRealDataVerificationFailed, 4604); + R_DEFINE_ERROR_RESULT(InvalidHierarchicalIntegrityVerificationLayerCount, 4605); + + R_DEFINE_ERROR_RANGE(RealDataVerificationFailed, 4611, 4619); + R_DEFINE_ERROR_RESULT(ClearedRealDataVerificationFailed, 4612); + R_DEFINE_ERROR_RESULT(UnclearedRealDataVerificationFailed, 4613); + + R_DEFINE_ERROR_RANGE(PartitionFileSystemCorrupted, 4641, 4659); + R_DEFINE_ERROR_RESULT(InvalidSha256PartitionHashTarget, 4642); + R_DEFINE_ERROR_RESULT(Sha256PartitionHashVerificationFailed, 4643); + R_DEFINE_ERROR_RESULT(PartitionSignatureVerificationFailed, 4644); + R_DEFINE_ERROR_RESULT(Sha256PartitionSignatureVerificationFailed, 4645); + R_DEFINE_ERROR_RESULT(InvalidPartitionEntryOffset, 4646); + R_DEFINE_ERROR_RESULT(InvalidSha256PartitionMetaDataSize, 4647); + + R_DEFINE_ERROR_RANGE(BuiltInStorageCorrupted, 4661, 4679); + R_DEFINE_ERROR_RESULT(GptHeaderVerificationFailed, 4662); + + R_DEFINE_ERROR_RANGE(FatFileSystemCorrupted, 4681, 4699); + R_DEFINE_ERROR_RESULT(ExFatUnavailable, 4685); + + R_DEFINE_ERROR_RANGE(HostFileSystemCorrupted, 4701, 4719); + R_DEFINE_ERROR_RESULT(HostEntryCorrupted, 4702); + R_DEFINE_ERROR_RESULT(HostFileDataCorrupted, 4703); + R_DEFINE_ERROR_RESULT(HostFileCorrupted, 4704); + R_DEFINE_ERROR_RESULT(InvalidHostHandle, 4705); + + R_DEFINE_ERROR_RANGE(DatabaseCorrupted, 4721, 4739); + R_DEFINE_ERROR_RESULT(InvalidAllocationTableBlock, 4722); + R_DEFINE_ERROR_RESULT(InvalidKeyValueListElementIndex, 4723); + + R_DEFINE_ERROR_RANGE(AesXtsFileSystemCorrupted, 4741, 4759); + R_DEFINE_ERROR_RANGE(SaveDataTransferDataCorrupted, 4761, 4769); + R_DEFINE_ERROR_RANGE(SignedSystemPartitionDataCorrupted, 4771, 4779); + + R_DEFINE_ERROR_RESULT(GameCardLogoDataCorrupted, 4781); + + R_DEFINE_ERROR_RANGE(Unexpected, 5000, 5999); + R_DEFINE_ERROR_RESULT(UnexpectedInLocalFileSystemA, 5305); + R_DEFINE_ERROR_RESULT(UnexpectedInLocalFileSystemB, 5306); + R_DEFINE_ERROR_RESULT(UnexpectedInLocalFileSystemC, 5307); + R_DEFINE_ERROR_RESULT(UnexpectedInLocalFileSystemD, 5308); + R_DEFINE_ERROR_RESULT(UnexpectedInLocalFileSystemE, 5309); + R_DEFINE_ERROR_RESULT(UnexpectedInLocalFileSystemF, 5310); + R_DEFINE_ERROR_RESULT(UnexpectedInPathOnExecutionDirectoryA, 5312); + R_DEFINE_ERROR_RESULT(UnexpectedInPathOnExecutionDirectoryB, 5313); + R_DEFINE_ERROR_RESULT(UnexpectedInPathOnExecutionDirectoryC, 5314); + R_DEFINE_ERROR_RESULT(UnexpectedInAesCtrStorageA, 5315); + R_DEFINE_ERROR_RESULT(UnexpectedInAesXtsStorageA, 5316); + R_DEFINE_ERROR_RESULT(UnexpectedInFindFileSystemA, 5319); + R_DEFINE_ERROR_RESULT(UnexpectedInCompressedStorageA, 5324); + R_DEFINE_ERROR_RESULT(UnexpectedInCompressedStorageB, 5325); + R_DEFINE_ERROR_RESULT(UnexpectedInCompressedStorageC, 5326); + R_DEFINE_ERROR_RESULT(UnexpectedInCompressedStorageD, 5327); + R_DEFINE_ERROR_RESULT(UnexpectedInPathA, 5328); + + R_DEFINE_ERROR_RANGE(PreconditionViolation, 6000, 6499); + R_DEFINE_ERROR_RANGE(InvalidArgument, 6001, 6199); + R_DEFINE_ERROR_RANGE(InvalidPath, 6002, 6029); + R_DEFINE_ERROR_RESULT(TooLongPath, 6003); + R_DEFINE_ERROR_RESULT(InvalidCharacter, 6004); + R_DEFINE_ERROR_RESULT(InvalidPathFormat, 6005); + R_DEFINE_ERROR_RESULT(DirectoryUnobtainable, 6006); + R_DEFINE_ERROR_RESULT(NotNormalized, 6007); + + R_DEFINE_ERROR_RANGE(InvalidPathForOperation, 6030, 6059); + R_DEFINE_ERROR_RESULT(DirectoryNotDeletable, 6031); + R_DEFINE_ERROR_RESULT(DirectoryNotRenamable, 6032); + R_DEFINE_ERROR_RESULT(IncompatiblePath, 6033); + R_DEFINE_ERROR_RESULT(RenameToOtherFileSystem, 6034); + + R_DEFINE_ERROR_RESULT(InvalidOffset, 6061); + R_DEFINE_ERROR_RESULT(InvalidSize, 6062); + R_DEFINE_ERROR_RESULT(NullptrArgument, 6063); + R_DEFINE_ERROR_RESULT(InvalidAlignment, 6064); + R_DEFINE_ERROR_RESULT(InvalidMountName, 6065); + + R_DEFINE_ERROR_RESULT(ExtensionSizeTooLarge, 6066); + R_DEFINE_ERROR_RESULT(ExtensionSizeInvalid, 6067); + + R_DEFINE_ERROR_RESULT(InvalidOpenMode, 6072); + R_DEFINE_ERROR_RESULT(TooLargeSize, 6073); + + R_DEFINE_ERROR_RANGE(InvalidEnumValue, 6080, 6099); + R_DEFINE_ERROR_RESULT(InvalidSaveDataState, 6081); + R_DEFINE_ERROR_RESULT(InvalidSaveDataSpaceId, 6082); + + R_DEFINE_ERROR_RANGE(InvalidOperationForOpenMode, 6200, 6299); + R_DEFINE_ERROR_RESULT(FileExtensionWithoutOpenModeAllowAppend, 6201); + R_DEFINE_ERROR_RESULT(ReadNotPermitted, 6202); + R_DEFINE_ERROR_RESULT(WriteNotPermitted, 6203); + + R_DEFINE_ERROR_RANGE(UnsupportedOperation, 6300, 6399); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForNotResizableSubStorage, 6302); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForResizableSubStorage, 6303); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForMemoryStorage, 6304); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForMemoryStorage, 6305); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForFileStorage, 6306); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForFileHandleStorage, 6307); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForSwitchStorage, 6308); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForStorageServiceObjectAdapter, 6309); + R_DEFINE_ERROR_RESULT(UnsupportedWriteForAesCtrCounterExtendedStorage, 6310); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForAesCtrCounterExtendedStorage, 6311); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForAesCtrCounterExtendedStorage, 6312); + R_DEFINE_ERROR_RESULT(UnsupportedWriteForAesCtrStorageExternal, 6313); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForAesCtrStorageExternal, 6314); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForAesCtrStorage, 6315); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForHierarchicalIntegrityVerificationStorage, 6316); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForHierarchicalIntegrityVerificationStorage, 6317); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForIntegrityVerificationStorage, 6318); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForWritableIntegrityVerificationStorage, 6319); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForIntegrityVerificationStorage, 6320); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForBlockCacheBufferedStorage, 6321); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForWritableBlockCacheBufferedStorage, 6322); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForBlockCacheBufferedStorage, 6323); + R_DEFINE_ERROR_RESULT(UnsupportedWriteForIndirectStorage, 6324); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForIndirectStorage, 6325); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForIndirectStorage, 6326); + R_DEFINE_ERROR_RESULT(UnsupportedWriteForZeroStorage, 6327); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForZeroStorage, 6328); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForHierarchicalSha256Storage, 6329); + R_DEFINE_ERROR_RESULT(UnsupportedWriteForReadOnlyBlockCacheStorage, 6330); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForReadOnlyBlockCacheStorage, 6331); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForIntegrityRomFsStorage, 6332); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForDuplexStorage, 6333); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForDuplexStorage, 6334); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForHierarchicalDuplexStorage, 6335); + R_DEFINE_ERROR_RESULT(UnsupportedGetSizeForRemapStorage, 6336); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForRemapStorage, 6337); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForRemapStorage, 6338); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForIntegritySaveDataStorage, 6339); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForIntegritySaveDataStorage, 6340); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForJournalIntegritySaveDataStorage, 6341); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForJournalIntegritySaveDataStorage, 6342); + R_DEFINE_ERROR_RESULT(UnsupportedGetSizeForJournalStorage, 6343); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForJournalStorage, 6344); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForJournalStorage, 6345); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForUnionStorage, 6346); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForAllocationTableStorage, 6347); + R_DEFINE_ERROR_RESULT(UnsupportedReadForWriteOnlyGameCardStorage, 6348); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForWriteOnlyGameCardStorage, 6349); + R_DEFINE_ERROR_RESULT(UnsupportedWriteForReadOnlyGameCardStorage, 6350); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForReadOnlyGameCardStorage, 6351); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForReadOnlyGameCardStorage, 6352); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForSdmmcStorage, 6353); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForSdmmcStorage, 6354); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForFatFile, 6355); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForStorageFile, 6356); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForInternalStorageConcatenationFile, 6357); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForInternalStorageConcatenationFile, 6358); + R_DEFINE_ERROR_RESULT(UnsupportedQueryEntryForConcatenationFileSystem, 6359); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForConcatenationFile, 6360); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForZeroBitmapFile, 6361); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForFileServiceObjectAdapter, 6362); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForAesXtsFile, 6363); + R_DEFINE_ERROR_RESULT(UnsupportedWriteForRomFsFileSystem, 6364); + R_DEFINE_ERROR_RESULT(UnsupportedCommitProvisionallyForRomFsFileSystem, 6365); + R_DEFINE_ERROR_RESULT(UnsupportedGetTotalSpaceSizeForRomFsFileSystem, 6366); + R_DEFINE_ERROR_RESULT(UnsupportedWriteForRomFsFile, 6367); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForRomFsFile, 6368); + R_DEFINE_ERROR_RESULT(UnsupportedWriteForReadOnlyFileSystem, 6369); + R_DEFINE_ERROR_RESULT(UnsupportedCommitProvisionallyForReadOnlyFileSystem, 6370); + R_DEFINE_ERROR_RESULT(UnsupportedGetTotalSpaceSizeForReadOnlyFileSystem, 6371); + R_DEFINE_ERROR_RESULT(UnsupportedWriteForReadOnlyFile, 6372); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForReadOnlyFile, 6373); + R_DEFINE_ERROR_RESULT(UnsupportedWriteForPartitionFileSystem, 6374); + R_DEFINE_ERROR_RESULT(UnsupportedCommitProvisionallyForPartitionFileSystem, 6375); + R_DEFINE_ERROR_RESULT(UnsupportedWriteForPartitionFile, 6376); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForPartitionFile, 6377); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForTmFileSystemFile, 6378); + R_DEFINE_ERROR_RESULT(UnsupportedWriteForSaveDataInternalStorageFileSystem, 6379); + R_DEFINE_ERROR_RESULT(UnsupportedCommitProvisionallyForApplicationTemporaryFileSystem, 6382); + R_DEFINE_ERROR_RESULT(UnsupportedCommitProvisionallyForSaveDataFileSystem, 6383); + R_DEFINE_ERROR_RESULT(UnsupportedCommitProvisionallyForDirectorySaveDataFileSystem, 6384); + R_DEFINE_ERROR_RESULT(UnsupportedWriteForZeroBitmapHashStorageFile, 6385); + R_DEFINE_ERROR_RESULT(UnsupportedSetSizeForZeroBitmapHashStorageFile, 6386); + R_DEFINE_ERROR_RESULT(UnsupportedWriteForCompressedStorage, 6387); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForCompressedStorage, 6388); + R_DEFINE_ERROR_RESULT(UnsupportedOperateRangeForRegionSwitchStorage, 6397); + + + R_DEFINE_ERROR_RANGE(PermissionDenied, 6400, 6449); + R_DEFINE_ERROR_RESULT(PermissionDeniedForCreateHostFileSystem, 6403); + + R_DEFINE_ERROR_RESULT(PortAcceptableCountLimited, 6450); + R_DEFINE_ERROR_RESULT(NcaExternalKeyUnregistered, 6451); + R_DEFINE_ERROR_RESULT(NcaExternalKeyInconsistent, 6452); + R_DEFINE_ERROR_RESULT(NeedFlush, 6454); + R_DEFINE_ERROR_RESULT(FileNotClosed, 6455); + R_DEFINE_ERROR_RESULT(DirectoryNotClosed, 6456); + R_DEFINE_ERROR_RESULT(WriteModeFileNotClosed, 6457); + R_DEFINE_ERROR_RESULT(AllocatorAlreadyRegistered, 6458); + R_DEFINE_ERROR_RESULT(DefaultAllocatorUsed, 6459); + R_DEFINE_ERROR_RESULT(AllocatorAlignmentViolation, 6461); + R_DEFINE_ERROR_RESULT(UserNotExist, 6465); + + R_DEFINE_ERROR_RANGE(NotFound, 6600, 6699); + R_DEFINE_ERROR_RESULT(ProgramInfoNotFound, 6605); + + R_DEFINE_ERROR_RANGE(OutOfResource, 6700, 6799); + R_DEFINE_ERROR_RESULT(BufferAllocationFailed, 6705); + R_DEFINE_ERROR_RESULT(MappingTableFull, 6706); + R_DEFINE_ERROR_RESULT(OpenCountLimit, 6709); + + R_DEFINE_ERROR_RANGE(MappingFailed, 6800, 6899); + R_DEFINE_ERROR_RESULT(MapFull, 6811); + + R_DEFINE_ERROR_RANGE(BadState, 6900, 6999); + R_DEFINE_ERROR_RESULT(NotInitialized, 6902); + R_DEFINE_ERROR_RESULT(NotMounted, 6905); + + + R_DEFINE_ERROR_RANGE(DbmNotFound, 7901, 7904); + R_DEFINE_ERROR_RESULT(DbmKeyNotFound, 7902); + R_DEFINE_ERROR_RESULT(DbmFileNotFound, 7903); + R_DEFINE_ERROR_RESULT(DbmDirectoryNotFound, 7904); + + R_DEFINE_ERROR_RESULT(DbmAlreadyExists, 7906); + R_DEFINE_ERROR_RESULT(DbmKeyFull, 7907); + R_DEFINE_ERROR_RESULT(DbmDirectoryEntryFull, 7908); + R_DEFINE_ERROR_RESULT(DbmFileEntryFull, 7909); + + R_DEFINE_ERROR_RANGE(DbmFindFinished, 7910, 7912); + R_DEFINE_ERROR_RESULT(DbmFindKeyFinished, 7911); + R_DEFINE_ERROR_RESULT(DbmIterationFinished, 7912); + + R_DEFINE_ERROR_RESULT(DbmInvalidOperation, 7914); + R_DEFINE_ERROR_RESULT(DbmInvalidPathFormat, 7915); + R_DEFINE_ERROR_RESULT(DbmDirectoryNameTooLong, 7916); + R_DEFINE_ERROR_RESULT(DbmFileNameTooLong, 7917); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/gpio_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/gpio_results.hpp new file mode 100644 index 00000000..a228c812 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/gpio_results.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::gpio, 102); + +namespace ams::gpio { + + R_DEFINE_ERROR_RESULT(AlreadyBound, 1); + R_DEFINE_ERROR_RESULT(AlreadyOpen, 2); + R_DEFINE_ERROR_RESULT(DeviceNotFound, 3); + R_DEFINE_ERROR_RESULT(InvalidArgument, 4); + + R_DEFINE_ERROR_RESULT(NotOpen, 6); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/hipc_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/hipc_results.hpp new file mode 100644 index 00000000..8e557b5a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/hipc_results.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::sf::hipc, 11); + +namespace ams::sf::hipc { + + R_DEFINE_ABSTRACT_ERROR_RANGE(OutOfResource, 100, 299); + R_DEFINE_ERROR_RESULT(OutOfSessionMemory, 102); + R_DEFINE_ERROR_RANGE (OutOfSessions, 131, 139); + R_DEFINE_ERROR_RESULT(PointerBufferTooSmall, 141); + + R_DEFINE_ERROR_RESULT(OutOfDomains, 200); + + R_DEFINE_ABSTRACT_ERROR_RANGE(CommunicationError, 300, 349); + R_DEFINE_ERROR_RESULT(SessionClosed, 301); + + R_DEFINE_ERROR_RESULT(InvalidRequestSize, 402); + R_DEFINE_ERROR_RESULT(UnknownCommandType, 403); + + R_DEFINE_ERROR_RESULT(InvalidCmifRequest, 420); + + R_DEFINE_ERROR_RESULT(TargetNotDomain, 491); + R_DEFINE_ERROR_RESULT(DomainObjectNotFound, 492); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/htc_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/htc_results.hpp new file mode 100644 index 00000000..ba3a8741 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/htc_results.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::htc, 18); + +namespace ams::htc { + + R_DEFINE_ERROR_RESULT(ConnectionFailure, 1); + R_DEFINE_ERROR_RESULT(NotFound, 2); + R_DEFINE_ERROR_RESULT(NotEnoughBuffer, 3); + + R_DEFINE_ERROR_RESULT(Cancelled, 101); + + R_DEFINE_ERROR_RESULT(Unknown, 1023); + + R_DEFINE_ERROR_RESULT(Unknown2001, 2001); + R_DEFINE_ERROR_RESULT(InvalidTaskId, 2003); + R_DEFINE_ERROR_RESULT(InvalidSize, 2011); + R_DEFINE_ERROR_RESULT(TaskCancelled, 2021); + R_DEFINE_ERROR_RESULT(TaskNotCompleted, 2022); + R_DEFINE_ERROR_RESULT(TaskQueueNotAvailable, 2033); + + R_DEFINE_ERROR_RESULT(Unknown2101, 2101); + R_DEFINE_ERROR_RESULT(OutOfRpcTask, 2102); + R_DEFINE_ERROR_RESULT(InvalidCategory, 2123); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/htcfs_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/htcfs_results.hpp new file mode 100644 index 00000000..cf96274e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/htcfs_results.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::htcfs, 31); + +namespace ams::htcfs { + + R_DEFINE_ERROR_RESULT(InvalidArgument, 3); + + + R_DEFINE_ERROR_RANGE(ConnectionFailure, 100, 199); + R_DEFINE_ERROR_RESULT(HtclowChannelClosed, 101); + + R_DEFINE_ERROR_RANGE(UnexpectedResponse, 110, 119); + R_DEFINE_ERROR_RESULT(UnexpectedResponseProtocolId, 111); + R_DEFINE_ERROR_RESULT(UnexpectedResponseProtocolVersion, 112); + R_DEFINE_ERROR_RESULT(UnexpectedResponsePacketCategory, 113); + R_DEFINE_ERROR_RESULT(UnexpectedResponsePacketType, 114); + R_DEFINE_ERROR_RESULT(UnexpectedResponseBodySize, 115); + R_DEFINE_ERROR_RESULT(UnexpectedResponseBody, 116); + + R_DEFINE_ERROR_RANGE(InternalError, 200, 299); + R_DEFINE_ERROR_RESULT(InvalidSize, 201); + R_DEFINE_ERROR_RESULT(UnknownError, 211); + R_DEFINE_ERROR_RESULT(UnsupportedProtocolVersion, 212); + R_DEFINE_ERROR_RESULT(InvalidRequest, 213); + R_DEFINE_ERROR_RESULT(InvalidHandle, 214); + R_DEFINE_ERROR_RESULT(OutOfHandle, 215); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/htclow_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/htclow_results.hpp new file mode 100644 index 00000000..9f6dfa52 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/htclow_results.hpp @@ -0,0 +1,77 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::htclow, 29); + +namespace ams::htclow { + + R_DEFINE_ERROR_RESULT(ConnectionFailure, 1); + R_DEFINE_ERROR_RESULT(UnknownDriverType, 3); + R_DEFINE_ERROR_RESULT(NonBlockingReceiveFailed, 5); + R_DEFINE_ERROR_RESULT(ChannelWaitCancelled, 8); + R_DEFINE_ERROR_RESULT(ChannelAlreadyExist, 9); + R_DEFINE_ERROR_RESULT(ChannelNotExist, 10); + + R_DEFINE_ERROR_RESULT(OutOfChannel, 151); + R_DEFINE_ERROR_RESULT(OutOfTask, 152); + + R_DEFINE_ERROR_RESULT(InvalidChannelState, 200); + R_DEFINE_ERROR_RESULT(InvalidChannelStateDisconnected, 201); + + R_DEFINE_ERROR_RANGE(InternalError, 1000, 2999); + R_DEFINE_ERROR_RESULT(Overflow, 1001); + R_DEFINE_ERROR_RESULT(OutOfMemory, 1002); + R_DEFINE_ERROR_RESULT(InvalidArgument, 1003); + R_DEFINE_ERROR_RESULT(ProtocolError, 1004); + R_DEFINE_ERROR_RESULT(Cancelled, 1005); + + R_DEFINE_ERROR_RANGE(MuxError, 1100, 1199); + R_DEFINE_ERROR_RESULT(ChannelBufferOverflow, 1101); + R_DEFINE_ERROR_RESULT(ChannelBufferHasNotEnoughData, 1102); + R_DEFINE_ERROR_RESULT(ChannelVersionNotMatched, 1103); + R_DEFINE_ERROR_RESULT(ChannelStateTransitionError, 1104); + R_DEFINE_ERROR_RESULT(ChannelReceiveBufferEmpty, 1106); + R_DEFINE_ERROR_RESULT(ChannelSequenceIdNotMatched, 1107); + R_DEFINE_ERROR_RESULT(ChannelCannotDiscard, 1108); + + R_DEFINE_ERROR_RANGE(DriverError, 1200, 1999); + R_DEFINE_ERROR_RESULT(DriverOpened, 1201); + + R_DEFINE_ERROR_RANGE(SocketDriverError, 1300, 1399); + R_DEFINE_ERROR_RESULT(SocketSocketExemptError, 1301); + R_DEFINE_ERROR_RESULT(SocketBindError, 1302); + R_DEFINE_ERROR_RESULT(SocketListenError, 1304); + R_DEFINE_ERROR_RESULT(SocketAcceptError, 1305); + R_DEFINE_ERROR_RESULT(SocketReceiveError, 1306); + R_DEFINE_ERROR_RESULT(SocketSendError, 1307); + R_DEFINE_ERROR_RESULT(SocketReceiveFromError, 1308); + R_DEFINE_ERROR_RESULT(SocketSendToError, 1309); + R_DEFINE_ERROR_RESULT(SocketSetSockOptError, 1310); + R_DEFINE_ERROR_RESULT(SocketGetSockNameError, 1311); + + R_DEFINE_ERROR_RANGE(UsbDriverError, 1400, 1499); + R_DEFINE_ERROR_RESULT(UsbDriverUnknownError, 1401); + R_DEFINE_ERROR_RESULT(UsbDriverBusyError, 1402); + R_DEFINE_ERROR_RESULT(UsbDriverReceiveError, 1403); + R_DEFINE_ERROR_RESULT(UsbDriverSendError, 1404); + + R_DEFINE_ERROR_RESULT(HtcctrlError, 2000); /* TODO: Range? */ + R_DEFINE_ERROR_RESULT(HtcctrlStateTransitionNotAllowed, 2001); + R_DEFINE_ERROR_RESULT(HtcctrlReceiveUnexpectedPacket, 2002); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/htcs_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/htcs_results.hpp new file mode 100644 index 00000000..0f782d52 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/htcs_results.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::htcs, 4); + +namespace ams::htcs { + + R_DEFINE_ERROR_RESULT(InvalidHandle, 9); + + R_DEFINE_ERROR_RESULT(InvalidArgument, 2001); + R_DEFINE_ERROR_RESULT(InvalidServerHandle, 2003); + R_DEFINE_ERROR_RESULT(InvalidSize, 2014); + R_DEFINE_ERROR_RESULT(Cancelled, 2021); + R_DEFINE_ERROR_RESULT(Completed, 2023); + R_DEFINE_ERROR_RESULT(InvalidTask, 2103); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/i2c_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/i2c_results.hpp new file mode 100644 index 00000000..2a0e0602 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/i2c_results.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::i2c, 101); + +namespace ams::i2c { + + R_DEFINE_ERROR_RESULT(NoAck, 1); + R_DEFINE_ERROR_RESULT(BusBusy, 2); + R_DEFINE_ERROR_RESULT(CommandListFull, 3); + + R_DEFINE_ERROR_RESULT(UnknownDevice, 5); + + R_DEFINE_ERROR_RESULT(Timeout, 253); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/kvdb_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/kvdb_results.hpp new file mode 100644 index 00000000..109a81bb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/kvdb_results.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::kvdb, 20); + +namespace ams::kvdb { + + R_DEFINE_ERROR_RESULT(OutOfKeyResource, 1); + R_DEFINE_ERROR_RESULT(KeyNotFound, 2); + R_DEFINE_ERROR_RESULT(AllocationFailed, 4); + R_DEFINE_ERROR_RESULT(InvalidKeyValue, 5); + R_DEFINE_ERROR_RESULT(BufferInsufficient, 6); + + R_DEFINE_ERROR_RESULT(InvalidFilesystemState, 8); + R_DEFINE_ERROR_RESULT(NotCreated, 9); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/loader_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/loader_results.hpp new file mode 100644 index 00000000..25e3debe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/loader_results.hpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::ldr, 9); + +namespace ams::ldr { + + R_DEFINE_ERROR_RESULT(ArgumentOverflow, 1); + R_DEFINE_ERROR_RESULT(ArgumentCountOverflow, 2); + R_DEFINE_ERROR_RESULT(MetaOverflow, 3); + R_DEFINE_ERROR_RESULT(InvalidMeta, 4); + R_DEFINE_ERROR_RESULT(InvalidNso, 5); + R_DEFINE_ERROR_RESULT(InvalidPath, 6); + R_DEFINE_ERROR_RESULT(MaxProcess, 7); + R_DEFINE_ERROR_RESULT(NotPinned, 8); + R_DEFINE_ERROR_RESULT(InvalidProgramId, 9); + R_DEFINE_ERROR_RESULT(InvalidVersion, 10); + R_DEFINE_ERROR_RESULT(InvalidAcidSignature, 11); + R_DEFINE_ERROR_RESULT(InvalidNcaSignature, 12); + + R_DEFINE_ERROR_RESULT(InvalidProgramAttributes, 14); + + R_DEFINE_ERROR_RESULT(OutOfAddressSpace, 51); + R_DEFINE_ERROR_RESULT(InvalidNroImage, 52); + R_DEFINE_ERROR_RESULT(InvalidNrrImage, 53); + R_DEFINE_ERROR_RESULT(NotAuthorized, 54); + R_DEFINE_ERROR_RESULT(MaxModule, 55); + R_DEFINE_ERROR_RESULT(MaxRegistration, 56); + R_DEFINE_ERROR_RESULT(NroAlreadyLoaded, 57); + + R_DEFINE_ERROR_RESULT(InvalidAddress, 81); + R_DEFINE_ERROR_RESULT(InvalidSize, 82); + R_DEFINE_ERROR_RESULT(InvalidCurrentMemory, 83); + R_DEFINE_ERROR_RESULT(NotLoaded, 84); + R_DEFINE_ERROR_RESULT(NotRegistered, 85); + R_DEFINE_ERROR_RESULT(InvalidSession, 86); + R_DEFINE_ERROR_RESULT(InvalidProcess, 87); + + R_DEFINE_ERROR_RESULT(UnknownCapability, 100); + R_DEFINE_ERROR_RESULT(InvalidCapabilityKernelFlags, 103); + R_DEFINE_ERROR_RESULT(InvalidCapabilitySyscallMask, 104); + R_DEFINE_ERROR_RESULT(InvalidCapabilityMapRange, 106); + R_DEFINE_ERROR_RESULT(InvalidCapabilityMapPage, 107); + R_DEFINE_ERROR_RESULT(InvalidCapabilityMapRegion, 110); + R_DEFINE_ERROR_RESULT(InvalidCapabilityInterruptPair, 111); + R_DEFINE_ERROR_RESULT(InvalidCapabilityApplicationType, 113); + R_DEFINE_ERROR_RESULT(InvalidCapabilityKernelVersion, 114); + R_DEFINE_ERROR_RESULT(InvalidCapabilityHandleTable, 115); + R_DEFINE_ERROR_RESULT(InvalidCapabilityDebugFlags, 116); + + R_DEFINE_ERROR_RESULT(InternalError, 200); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/lr_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/lr_results.hpp new file mode 100644 index 00000000..a139cce6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/lr_results.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::lr, 8); + +namespace ams::lr { + + R_DEFINE_ERROR_RESULT(ProgramNotFound, 2); + R_DEFINE_ERROR_RESULT(DataNotFound, 3); + R_DEFINE_ERROR_RESULT(UnknownStorageId, 4); + R_DEFINE_ERROR_RESULT(HtmlDocumentNotFound, 6); + R_DEFINE_ERROR_RESULT(AddOnContentNotFound, 7); + R_DEFINE_ERROR_RESULT(ControlNotFound, 8); + R_DEFINE_ERROR_RESULT(LegalInformationNotFound, 9); + R_DEFINE_ERROR_RESULT(DebugProgramNotFound, 10); + + R_DEFINE_ERROR_RESULT(TooManyRegisteredPaths, 90); + + R_DEFINE_ERROR_RESULT(InvalidPath, 140); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/ncm_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/ncm_results.hpp new file mode 100644 index 00000000..2092cf0c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/ncm_results.hpp @@ -0,0 +1,87 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::ncm, 5); + +namespace ams::ncm { + + R_DEFINE_ERROR_RESULT(InvalidContentStorageBase, 1); + R_DEFINE_ERROR_RESULT(PlaceHolderAlreadyExists, 2); + R_DEFINE_ERROR_RESULT(PlaceHolderNotFound, 3); + R_DEFINE_ERROR_RESULT(ContentAlreadyExists, 4); + R_DEFINE_ERROR_RESULT(ContentNotFound, 5); + R_DEFINE_ERROR_RESULT(ContentMetaNotFound, 7); + R_DEFINE_ERROR_RESULT(AllocationFailed, 8); + R_DEFINE_ERROR_RESULT(UnknownStorage, 12); + + R_DEFINE_ERROR_RESULT(InvalidContentStorage, 100); + R_DEFINE_ERROR_RESULT(InvalidContentMetaDatabase, 110); + R_DEFINE_ERROR_RESULT(InvalidPackageFormat, 130); + R_DEFINE_ERROR_RESULT(InvalidContentHash, 140); + + R_DEFINE_ERROR_RESULT(InvalidInstallTaskState, 160); + R_DEFINE_ERROR_RESULT(InvalidPlaceHolderFile, 170); + R_DEFINE_ERROR_RESULT(BufferInsufficient, 180); + R_DEFINE_ERROR_RESULT(NotSupported, 190); + R_DEFINE_ERROR_RESULT(NotEnoughInstallSpace, 200); + R_DEFINE_ERROR_RESULT(SystemUpdateNotFoundInPackage, 210); + R_DEFINE_ERROR_RESULT(ContentInfoNotFound, 220); + R_DEFINE_ERROR_RESULT(DeltaNotFound, 237); + R_DEFINE_ERROR_RESULT(InvalidContentMetaKey, 240); + R_DEFINE_ERROR_RESULT(FragmentIndicatorNotFound, 242); + R_DEFINE_ERROR_RESULT(IgnorableInstallTicketFailure, 280); + + R_DEFINE_ERROR_RESULT(ContentStorageBaseNotFound, 310); + R_DEFINE_ERROR_RESULT(ListPartiallyNotCommitted, 330); + R_DEFINE_ERROR_RESULT(UnexpectedContentMetaPrepared, 360); + R_DEFINE_ERROR_RESULT(InvalidFirmwareVariation, 380); + R_DEFINE_ERROR_RESULT(InvalidContentMetaFileSize, 390); + R_DEFINE_ERROR_RESULT(InvalidAddOnContentMetaExtendedHeader, 400); + + R_DEFINE_ERROR_RESULT(InvalidContentMetaDirectory, 430); + + R_DEFINE_ERROR_RANGE(ContentStorageNotActive, 250, 258); + R_DEFINE_ERROR_RESULT(GameCardContentStorageNotActive, 251); + R_DEFINE_ERROR_RESULT(BuiltInSystemContentStorageNotActive, 252); + R_DEFINE_ERROR_RESULT(BuiltInUserContentStorageNotActive, 253); + R_DEFINE_ERROR_RESULT(SdCardContentStorageNotActive, 254); + R_DEFINE_ERROR_RESULT(UnknownContentStorageNotActive, 258); + + R_DEFINE_ERROR_RANGE(ContentMetaDatabaseNotActive, 260, 268); + R_DEFINE_ERROR_RESULT(GameCardContentMetaDatabaseNotActive, 261); + R_DEFINE_ERROR_RESULT(BuiltInSystemContentMetaDatabaseNotActive, 262); + R_DEFINE_ERROR_RESULT(BuiltInUserContentMetaDatabaseNotActive, 263); + R_DEFINE_ERROR_RESULT(SdCardContentMetaDatabaseNotActive, 264); + R_DEFINE_ERROR_RESULT(UnknownContentMetaDatabaseNotActive, 268); + + R_DEFINE_ERROR_RANGE(InstallTaskCancelled, 290, 299); + R_DEFINE_ERROR_RESULT(CreatePlaceHolderCancelled, 291); + R_DEFINE_ERROR_RESULT(WritePlaceHolderCancelled, 292); + + /* TODO: Range */ + R_DEFINE_ERROR_RESULT(MapperBusy, 1010); + R_DEFINE_ERROR_RESULT(MapperInvalidArgument, 1030); + R_DEFINE_ERROR_RESULT(MapperNotSupported, 1040); + R_DEFINE_ERROR_RESULT(MapperNotMapped, 1050); + + R_DEFINE_ERROR_RESULT(InvalidOperation, 8180); + R_DEFINE_ERROR_RANGE(InvalidArgument, 8181, 8191); + R_DEFINE_ERROR_RESULT(InvalidOffset, 8182); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/nim_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/nim_results.hpp new file mode 100644 index 00000000..a006b4a4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/nim_results.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::nim, 137); + +namespace ams::nim { + + R_DEFINE_ERROR_RESULT(HttpConnectionCanceled, 70); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/ns_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/ns_results.hpp new file mode 100644 index 00000000..34bf8309 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/ns_results.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::ns, 16); + +namespace ams::ns { + + R_DEFINE_ERROR_RESULT(Canceled, 90); + R_DEFINE_ERROR_RESULT(OutOfMaxRunningTask, 110); + R_DEFINE_ERROR_RESULT(CardUpdateNotSetup, 270); + R_DEFINE_ERROR_RESULT(CardUpdateNotPrepared, 280); + R_DEFINE_ERROR_RESULT(CardUpdateAlreadySetup, 290); + R_DEFINE_ERROR_RESULT(PrepareCardUpdateAlreadyRequested, 460); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/os_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/os_results.hpp new file mode 100644 index 00000000..680a12db --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/os_results.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::os, 3); + +namespace ams::os { + + R_DEFINE_ERROR_RESULT(Busy, 4); + + R_DEFINE_ERROR_RESULT(InvalidParameter, 7); + R_DEFINE_ERROR_RESULT(OutOfMemory, 8); + R_DEFINE_ERROR_RESULT(OutOfResource, 9); + + R_DEFINE_ERROR_RESULT(OutOfVirtualAddressSpace, 12); + R_DEFINE_ERROR_RESULT(ResourceLimit, 13); + + R_DEFINE_ERROR_RESULT(OutOfHandles, 500); + R_DEFINE_ERROR_RESULT(InvalidHandle, 501); + R_DEFINE_ERROR_RESULT(InvalidCurrentMemoryState, 502); + R_DEFINE_ERROR_RESULT(InvalidTransferMemoryState, 503); + R_DEFINE_ERROR_RESULT(InvalidTransferMemorySize, 504); + R_DEFINE_ERROR_RESULT(OutOfTransferMemory, 505); + R_DEFINE_ERROR_RESULT(OutOfAddressSpace, 506); + + R_DEFINE_ERROR_RESULT(SessionClosedForReceive, 510); + R_DEFINE_ERROR_RESULT(SessionClosedForReply, 511); + R_DEFINE_ERROR_RESULT(ReceiveListBroken, 512); + R_DEFINE_ERROR_RESULT(InvalidProcessMemory, 513); + + R_DEFINE_ERROR_RESULT(NotImplemented, 1000); + R_DEFINE_ERROR_RESULT(NotSupported, 1001); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/osdbg_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/osdbg_results.hpp new file mode 100644 index 00000000..0ced0875 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/osdbg_results.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::osdbg, 7); + +namespace ams::osdbg { + + R_DEFINE_ERROR_RESULT(CannotGetThreadInfo, 1); + R_DEFINE_ERROR_RESULT(UnsupportedThreadVersion, 2); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/pcv_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/pcv_results.hpp new file mode 100644 index 00000000..daf05f28 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/pcv_results.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::pcv, 133); + +namespace ams::pcv { + + R_DEFINE_ERROR_RESULT(IllegalRequest, 16); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/pgl_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/pgl_results.hpp new file mode 100644 index 00000000..af4176de --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/pgl_results.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::pgl, 228); + +namespace ams::pgl { + + R_DEFINE_ERROR_RESULT(NotImplemented, 1); + R_DEFINE_ERROR_RESULT(NotAvailable, 2); + R_DEFINE_ERROR_RESULT(ApplicationNotRunning, 3); + R_DEFINE_ERROR_RESULT(BufferNotEnough, 4); + R_DEFINE_ERROR_RESULT(ApplicationContentNotFound, 5); + R_DEFINE_ERROR_RESULT(ContentMetaNotFound, 6); + R_DEFINE_ERROR_RESULT(OutOfMemory, 7); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/pm_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/pm_results.hpp new file mode 100644 index 00000000..4e4c0e24 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/pm_results.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::pm, 15); + +namespace ams::pm { + + R_DEFINE_ERROR_RESULT(ProcessNotFound, 1); + R_DEFINE_ERROR_RESULT(AlreadyStarted, 2); + R_DEFINE_ERROR_RESULT(NotTerminated, 3); + R_DEFINE_ERROR_RESULT(DebugHookInUse, 4); + R_DEFINE_ERROR_RESULT(ApplicationRunning, 5); + R_DEFINE_ERROR_RESULT(InvalidSize, 6); + R_DEFINE_ERROR_RESULT(Unknown7, 7); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/powctl_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/powctl_results.hpp new file mode 100644 index 00000000..98413f67 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/powctl_results.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::powctl, 198); + +namespace ams::powctl { + + R_DEFINE_ERROR_RESULT(NotSupported, 1); + R_DEFINE_ERROR_RESULT(InvalidArgument, 2); + R_DEFINE_ERROR_RESULT(NotAvailable, 3); + + R_DEFINE_ERROR_RESULT(CalibrationDataCrcError, 101); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/psc_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/psc_results.hpp new file mode 100644 index 00000000..16d8e307 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/psc_results.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::psc, 138); + +namespace ams::psc { + + R_DEFINE_ERROR_RESULT(AlreadyInitialized, 2); + R_DEFINE_ERROR_RESULT(NotInitialized, 3); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/pwm_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/pwm_results.hpp new file mode 100644 index 00000000..247a5f31 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/pwm_results.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::pwm, 189); + +namespace ams::pwm { + + R_DEFINE_ERROR_RESULT(InvalidArgument, 2); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/results_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/results_common.hpp new file mode 100644 index 00000000..67d81faa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/results_common.hpp @@ -0,0 +1,548 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams { + + const char *GetResultName(int module, int description); + + namespace result::impl { + + #if defined(AMS_AUTO_GENERATE_RESULT_NAMES) + struct DummyNameHolder { + static constexpr bool Exists = false; + static constexpr const char *Name = "unknown"; + }; + + template<int Module> + struct ResultNameSpaceExistsImpl { + static constexpr bool Exists = false; + + template<int Description> + using NameHolder = DummyNameHolder; + }; + #endif + + class ResultTraits { + public: + using BaseType = u32; + static_assert(std::is_same<BaseType, ::Result>::value, "std::is_same<BaseType, ::Result>::value"); + static constexpr BaseType SuccessValue = BaseType(); + static constexpr BaseType ModuleBits = 9; + static constexpr BaseType DescriptionBits = 13; + static constexpr BaseType ReservedBits = 10; + static_assert(ModuleBits + DescriptionBits + ReservedBits == sizeof(BaseType) * CHAR_BIT, "ModuleBits + DescriptionBits + ReservedBits == sizeof(BaseType) * CHAR_BIT"); + private: + static constexpr ALWAYS_INLINE BaseType GetBitsValue(BaseType v, int ofs, int num) { + return (v >> ofs) & ~(~BaseType() << num); + } + public: + static constexpr ALWAYS_INLINE BaseType MakeValue(BaseType module, BaseType description) { + return (module) | (description << ModuleBits); + } + + template<BaseType module, BaseType description> + struct MakeStaticValue : public std::integral_constant<BaseType, MakeValue(module, description)> { + static_assert(module < (1 << ModuleBits), "Invalid Module"); + static_assert(description < (1 << DescriptionBits), "Invalid Description"); + }; + + static constexpr ALWAYS_INLINE BaseType GetModuleFromValue(BaseType value) { + return GetBitsValue(value, 0, ModuleBits); + } + + static constexpr ALWAYS_INLINE BaseType GetDescriptionFromValue(BaseType value) { + return GetBitsValue(value, ModuleBits, DescriptionBits); + } + + static constexpr ALWAYS_INLINE BaseType GetReservedFromValue(BaseType value) { + return GetBitsValue(value, ModuleBits + DescriptionBits, ReservedBits); + } + + static constexpr ALWAYS_INLINE BaseType MaskReservedFromValue(BaseType value) { + return value & ~(~(~BaseType() << ReservedBits) << (ModuleBits + DescriptionBits)); + } + + static constexpr ALWAYS_INLINE BaseType MergeValueWithReserved(BaseType value, BaseType reserved) { + return (value << 0) | (reserved << (ModuleBits + DescriptionBits)); + } + }; + + /* Use CRTP for Results. */ + class ResultBase { + public: + using BaseType = typename ResultTraits::BaseType; + static constexpr BaseType SuccessValue = ResultTraits::SuccessValue; + public: + constexpr ALWAYS_INLINE BaseType GetModule(this auto const &self) { return ResultTraits::GetModuleFromValue(self.GetValue()); } + constexpr ALWAYS_INLINE BaseType GetDescription(this auto const &self) { return ResultTraits::GetDescriptionFromValue(self.GetValue()); } + }; + + class ResultInternalAccessor; + + } + + class ResultSuccess; + + class Result final : public result::impl::ResultBase { + friend class result::impl::ResultInternalAccessor; + public: + using Base = typename result::impl::ResultBase; + private: + typename Base::BaseType m_value; + private: + /* TODO: Maybe one-day, the result constructor. */ + public: + Result() { /* ... */ } + + /* TODO: It sure would be nice to make this private. */ + constexpr ALWAYS_INLINE Result(typename Base::BaseType v) : m_value(v) { static_assert(std::is_same<typename Base::BaseType, ::Result>::value); } + + constexpr ALWAYS_INLINE operator ResultSuccess() const; + static constexpr ALWAYS_INLINE bool CanAccept(Result) { return true; } + + constexpr ALWAYS_INLINE bool IsSuccess() const { return m_value == Base::SuccessValue; } + constexpr ALWAYS_INLINE bool IsFailure() const { return !this->IsSuccess(); } + constexpr ALWAYS_INLINE typename Base::BaseType GetModule() const { return Base::GetModule(); } + constexpr ALWAYS_INLINE typename Base::BaseType GetDescription() const { return Base::GetDescription(); } + + constexpr ALWAYS_INLINE typename Base::BaseType GetInnerValue() const { return ::ams::result::impl::ResultTraits::MaskReservedFromValue(m_value); } + + constexpr ALWAYS_INLINE typename Base::BaseType GetValue() const { return m_value; } + }; + static_assert(sizeof(Result) == sizeof(Result::Base::BaseType), "sizeof(Result) == sizeof(Result::Base::BaseType)"); + static_assert(std::is_trivially_destructible<Result>::value, "std::is_trivially_destructible<Result>::value"); + + ALWAYS_INLINE const char *GetResultName(const Result &result) { + return GetResultName(result.GetModule(), result.GetDescription()); + } + + namespace result::impl { + + class ResultInternalAccessor { + public: + static constexpr ALWAYS_INLINE Result MakeResult(ResultTraits::BaseType value) { + return Result(value); + } + + static constexpr ALWAYS_INLINE ResultTraits::BaseType GetReserved(Result result) { + return ResultTraits::GetReservedFromValue(result.m_value); + } + + static constexpr ALWAYS_INLINE Result MergeReserved(Result result, ResultTraits::BaseType reserved) { + return Result(ResultTraits::MergeValueWithReserved(ResultTraits::MaskReservedFromValue(result.m_value), reserved)); + } + }; + + constexpr ALWAYS_INLINE Result MakeResult(ResultTraits::BaseType value) { + return ResultInternalAccessor::MakeResult(value); + } + + } + + class ResultSuccess final : public result::impl::ResultBase { + public: + using Base = typename result::impl::ResultBase; + public: + constexpr ALWAYS_INLINE operator Result() const { return result::impl::MakeResult(Base::SuccessValue); } + static constexpr ALWAYS_INLINE bool CanAccept(Result result) { return result.IsSuccess(); } + + constexpr ALWAYS_INLINE bool IsSuccess() const { return true; } + constexpr ALWAYS_INLINE bool IsFailure() const { return !this->IsSuccess(); } + + constexpr ALWAYS_INLINE typename Base::BaseType GetValue() const { return Base::SuccessValue; } + }; + + namespace result::impl { + + NORETURN NOINLINE void OnResultAssertion(const char *file, int line, const char *func, const char *expr, Result result); + NORETURN NOINLINE void OnResultAssertion(Result result); + NORETURN NOINLINE void OnResultAbort(const char *file, int line, const char *func, const char *expr, Result result); + NORETURN NOINLINE void OnResultAbort(Result result); + + } + + constexpr ALWAYS_INLINE Result::operator ResultSuccess() const { + if (!ResultSuccess::CanAccept(*this)) { + result::impl::OnResultAbort(*this); + } + return ResultSuccess(); + } + + namespace result::impl { + + template<ResultTraits::BaseType _Module, ResultTraits::BaseType _Description> + class ResultErrorBase : public ResultBase { + public: + using Base = typename result::impl::ResultBase; + static constexpr typename Base::BaseType Module = _Module; + static constexpr typename Base::BaseType Description = _Description; + static constexpr typename Base::BaseType Value = ResultTraits::MakeStaticValue<Module, Description>::value; + static_assert(Value != Base::SuccessValue, "Value != Base::SuccessValue"); + public: + constexpr ALWAYS_INLINE operator Result() const { return MakeResult(Value); } + constexpr ALWAYS_INLINE operator ResultSuccess() const { + OnResultAbort(Value); + __builtin_unreachable(); + return ResultSuccess(); + } + + constexpr ALWAYS_INLINE bool IsSuccess() const { return false; } + constexpr ALWAYS_INLINE bool IsFailure() const { return !this->IsSuccess(); } + + constexpr ALWAYS_INLINE typename Base::BaseType GetValue() const { return Value; } + }; + + template<ResultTraits::BaseType _Module, ResultTraits::BaseType DescStart, ResultTraits::BaseType DescEnd> + class ResultErrorRangeBase { + private: + /* NOTE: GCC does not optimize the module/description comparisons into one check (as of 10/1/2021) */ + /* and so this optimizes result comparisons to get the same codegen as Nintendo does. */ + static constexpr bool UseDirectValueComparison = true; + public: + static constexpr ResultTraits::BaseType Module = _Module; + static constexpr ResultTraits::BaseType DescriptionStart = DescStart; + static constexpr ResultTraits::BaseType DescriptionEnd = DescEnd; + static_assert(DescriptionStart <= DescriptionEnd, "DescriptionStart <= DescriptionEnd"); + static constexpr typename ResultTraits::BaseType StartValue = ResultTraits::MakeStaticValue<Module, DescriptionStart>::value; + static constexpr typename ResultTraits::BaseType EndValue = ResultTraits::MakeStaticValue<Module, DescriptionEnd>::value; + public: + static constexpr ALWAYS_INLINE bool Includes(Result result) { + if constexpr (UseDirectValueComparison) { + const auto inner_value = result.GetInnerValue(); + if constexpr (StartValue == EndValue) { + return inner_value == StartValue; + } else { + return StartValue <= inner_value && inner_value <= EndValue; + } + } else { + return result.GetModule() == Module && DescriptionStart <= result.GetDescription() && result.GetDescription() <= DescriptionEnd; + } + } + }; + + } + + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) && defined(ATMOSPHERE_ARCH_ARM64) && defined(ATMOSPHERE_IS_STRATOSPHERE) + namespace diag::impl { + + void FatalErrorByResultForNx(Result result) noexcept NORETURN; + + } + #endif + +} + +/* Macros for defining new results. */ +#if defined(AMS_AUTO_GENERATE_RESULT_NAMES) +#define R_DEFINE_NAMESPACE_RESULT_MODULE(nmspc, value) \ + namespace nmspc { \ + \ + namespace result_impl { \ + static constexpr inline ::ams::result::impl::ResultTraits::BaseType ResultModuleId = value; \ + \ + template<int Description> \ + struct ResultNameHolderImpl { static constexpr bool Exists = false; }; \ + } \ + \ + } \ + \ + namespace ams::result::impl { \ + \ + template<> struct ResultNameSpaceExistsImpl<value> { \ + static constexpr bool Exists = true; \ + \ + template<int Description> \ + using NameHolder = nmspc::result_impl::ResultNameHolderImpl<Description>; \ + }; \ + \ + } +#else +#define R_DEFINE_NAMESPACE_RESULT_MODULE(nmspc, value) \ + namespace nmspc { \ + \ + namespace result_impl { \ + static constexpr inline ::ams::result::impl::ResultTraits::BaseType ResultModuleId = value; \ + } \ + \ + } +#endif + +#define R_CURRENT_NAMESPACE_RESULT_MODULE result_impl::ResultModuleId +#define R_NAMESPACE_MODULE_ID(nmspc) nmspc::R_CURRENT_NAMESPACE_RESULT_MODULE + +#define R_MAKE_NAMESPACE_RESULT(nmspc, desc) static_cast<::ams::Result>(::ams::result::impl::ResultTraits::MakeValue(R_NAMESPACE_MODULE_ID(nmspc), desc)) + +#if defined(AMS_AUTO_GENERATE_RESULT_NAMES) +#define R_DEFINE_ERROR_RESULT_NAME_HOLDER_IMPL(name, desc_start, desc_end) \ + template<> struct result_impl::ResultNameHolderImpl<desc_start> { static constexpr bool Exists = true; static constexpr const char *Name = #name; }; +#else +#define R_DEFINE_ERROR_RESULT_NAME_HOLDER_IMPL(name, desc_start, desc_end) +#endif + +#define R_DEFINE_ERROR_RESULT_CLASS_IMPL(name, desc_start, desc_end) \ + class Result##name final : public ::ams::result::impl::ResultErrorBase<R_CURRENT_NAMESPACE_RESULT_MODULE, desc_start>, public ::ams::result::impl::ResultErrorRangeBase<R_CURRENT_NAMESPACE_RESULT_MODULE, desc_start, desc_end> {} + +#define R_DEFINE_ERROR_RESULT_IMPL(name, desc_start, desc_end) \ + R_DEFINE_ERROR_RESULT_NAME_HOLDER_IMPL(name, desc_start, desc_end) \ + R_DEFINE_ERROR_RESULT_CLASS_IMPL(name, desc_start, desc_end) + +#define R_DEFINE_ABSTRACT_ERROR_RESULT_IMPL(name, desc_start, desc_end) \ + class Result##name final : public ::ams::result::impl::ResultErrorRangeBase<R_CURRENT_NAMESPACE_RESULT_MODULE, desc_start, desc_end> {} + +#define R_DEFINE_ERROR_RESULT(name, desc) R_DEFINE_ERROR_RESULT_IMPL(name, desc, desc) +#define R_DEFINE_ERROR_RANGE(name, start, end) R_DEFINE_ERROR_RESULT_IMPL(name, start, end) + +#define R_DEFINE_ABSTRACT_ERROR_RESULT(name, desc) R_DEFINE_ABSTRACT_ERROR_RESULT_IMPL(name, desc, desc) +#define R_DEFINE_ABSTRACT_ERROR_RANGE(name, start, end) R_DEFINE_ABSTRACT_ERROR_RESULT_IMPL(name, start, end) + + +#define R_DEFINE_ERROR_RESULT_NS(ns, name, desc) namespace ns { R_DEFINE_ERROR_RESULT_CLASS_IMPL(name, desc, desc); } R_DEFINE_ERROR_RESULT_NAME_HOLDER_IMPL(name, desc, desc) +#define R_DEFINE_ERROR_RANGE_NS(ns, name, start, end) namespace ns { R_DEFINE_ERROR_RESULT_CLASS_IMPL(name, start, end); } R_DEFINE_ERROR_RESULT_NAME_HOLDER_IMPL(name, start, end) + +#define R_DEFINE_ABSTRACT_ERROR_RESULT_NS(ns, name, desc) namespace ns { R_DEFINE_ABSTRACT_ERROR_RESULT_IMPL(name, desc, desc); } +#define R_DEFINE_ABSTRACT_ERROR_RANGE_NS(ns, name, start, end) namespace ns { R_DEFINE_ABSTRACT_ERROR_RESULT_IMPL(name, start, end); } + +/* Remove libnx macros, replace with our own. */ +#ifndef R_SUCCEEDED +#error "R_SUCCEEDED not defined." +#endif + +#undef R_SUCCEEDED + +#ifndef R_FAILED +#error "R_FAILED not defined" +#endif + +#undef R_FAILED + +#define R_SUCCEEDED(res) (static_cast<::ams::Result>(res).IsSuccess()) +#define R_FAILED(res) (static_cast<::ams::Result>(res).IsFailure()) + + +/* NOTE: The following are experimental and cannot be safely used yet. */ +/* =================================================================== */ +constinit inline ::ams::Result __TmpCurrentResultReference = ::ams::ResultSuccess(); + +namespace ams::result::impl { + + template<auto EvaluateResult, class F> + class ScopedResultGuard { + NON_COPYABLE(ScopedResultGuard); + NON_MOVEABLE(ScopedResultGuard); + private: + Result &m_ref; + F m_f; + public: + constexpr ALWAYS_INLINE ScopedResultGuard(Result &ref, F f) : m_ref(ref), m_f(std::move(f)) { } + constexpr ALWAYS_INLINE ~ScopedResultGuard() { if (EvaluateResult(m_ref)) { m_f(); } } + }; + + template<auto EvaluateResult> + class ResultReferenceForScopedResultGuard { + private: + Result &m_ref; + public: + constexpr ALWAYS_INLINE ResultReferenceForScopedResultGuard(Result &r) : m_ref(r) { /* ... */ } + + constexpr ALWAYS_INLINE operator Result &() const { return m_ref; } + }; + + template<auto EvaluateResult, typename F> + constexpr ALWAYS_INLINE ScopedResultGuard<EvaluateResult, F> operator+(ResultReferenceForScopedResultGuard<EvaluateResult> ref, F&& f) { + return ScopedResultGuard<EvaluateResult, F>(static_cast<Result &>(ref), std::forward<F>(f)); + } + + constexpr ALWAYS_INLINE bool EvaluateResultSuccess(const ::ams::Result &r) { return R_SUCCEEDED(r); } + constexpr ALWAYS_INLINE bool EvaluateResultFailure(const ::ams::Result &r) { return R_FAILED(r); } + + template<typename R> + constexpr ALWAYS_INLINE bool EvaluateResultIncludedImplForSuccessCompatibility(const ::ams::Result &r) { + if constexpr (std::same_as<R, ::ams::ResultSuccess>) { + return R_SUCCEEDED(r); + } else { + return R::Includes(r); + } + } + + template<typename... Rs> + constexpr ALWAYS_INLINE bool EvaluateAnyResultIncludes(const ::ams::Result &r) { return (EvaluateResultIncludedImplForSuccessCompatibility<Rs>(r) || ...); } + + template<typename... Rs> + constexpr ALWAYS_INLINE bool EvaluateResultNotIncluded(const ::ams::Result &r) { return !EvaluateAnyResultIncludes<Rs...>(r); } + +} + +#define AMS_DECLARE_CURRENT_RESULT_REFERENCE_AND_STORAGE(COUNTER_VALUE) \ + [[maybe_unused]] constexpr bool HasPrevRef_##COUNTER_VALUE = std::same_as<decltype(__TmpCurrentResultReference), Result &>; \ + [[maybe_unused]] auto &PrevRef_##COUNTER_VALUE = __TmpCurrentResultReference; \ + [[maybe_unused]] Result __tmp_result_##COUNTER_VALUE = ResultSuccess(); \ + ::ams::Result &__TmpCurrentResultReference = HasPrevRef_##COUNTER_VALUE ? PrevRef_##COUNTER_VALUE : __tmp_result_##COUNTER_VALUE + +#define ON_RESULT_RETURN_IMPL(...) \ + static_assert(std::same_as<decltype(__TmpCurrentResultReference), Result &>); \ + auto ANONYMOUS_VARIABLE(RESULT_GUARD_STATE_) = ::ams::result::impl::ResultReferenceForScopedResultGuard<__VA_ARGS__>(__TmpCurrentResultReference) + [&]() ALWAYS_INLINE_LAMBDA + +#define ON_RESULT_FAILURE_2 ON_RESULT_RETURN_IMPL(::ams::result::impl::EvaluateResultFailure) + +#define ON_RESULT_FAILURE \ + AMS_DECLARE_CURRENT_RESULT_REFERENCE_AND_STORAGE(__COUNTER__); \ + ON_RESULT_FAILURE_2 + +#define ON_RESULT_SUCCESS_2 ON_RESULT_RETURN_IMPL(::ams::result::impl::EvaluateResultSuccess) + +#define ON_RESULT_SUCCESS \ + AMS_DECLARE_CURRENT_RESULT_REFERENCE_AND_STORAGE(__COUNTER__); \ + ON_RESULT_SUCCESS_2 + +#define ON_RESULT_INCLUDED_2(...) ON_RESULT_RETURN_IMPL(::ams::result::impl::EvaluateAnyResultIncludes<__VA_ARGS__>) + +#define ON_RESULT_INCLUDED(...) \ + AMS_DECLARE_CURRENT_RESULT_REFERENCE_AND_STORAGE(__COUNTER__); \ + ON_RESULT_INCLUDED_2(__VA_ARGS__) + +#define ON_RESULT_NOT_INCLUDED_2(...) ON_RESULT_RETURN_IMPL(::ams::result::impl::EvaluateResultNotIncluded<__VA_ARGS__>) + +#define ON_RESULT_NOT_INCLUDED(...) \ + AMS_DECLARE_CURRENT_RESULT_REFERENCE_AND_STORAGE(__COUNTER__); \ + ON_RESULT_NOT_INCLUDED_2(__VA_ARGS__) + +#define ON_RESULT_FAILURE_BESIDES(...) ON_RESULT_NOT_INCLUDED(::ams::ResultSuccess, ## __VA_ARGS__) + +#define ON_RESULT_FAILURE_BESIDES_2(...) ON_RESULT_NOT_INCLUDED_2(::ams::ResultSuccess, ## __VA_ARGS__) + +/* =================================================================== */ + +/// Returns a result. +#define R_RETURN(res_expr) \ + { \ + const ::ams::Result _tmp_r_throw_rc = (res_expr); \ + if constexpr (std::same_as<decltype(__TmpCurrentResultReference), ::ams::Result &>) { __TmpCurrentResultReference = _tmp_r_throw_rc; } \ + return _tmp_r_throw_rc; \ + } + +/// Returns ResultSuccess() +#define R_SUCCEED() R_RETURN(::ams::ResultSuccess()) + +/// Throws a result. +#define R_THROW(res_expr) R_RETURN(res_expr) + +/// Evaluates an expression that returns a result, and returns the result if it would fail. +#define R_TRY(res_expr) \ + { \ + if (const auto _tmp_r_try_rc = (res_expr); R_FAILED(_tmp_r_try_rc)) { \ + R_THROW(_tmp_r_try_rc); \ + } \ + } + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) && defined(ATMOSPHERE_IS_STRATOSPHERE) && !defined(AMS_ENABLE_DETAILED_ASSERTIONS) && !defined(AMS_BUILD_FOR_DEBUGGING) && !defined(AMS_BUILD_FOR_AUDITING) + #define AMS_CALL_ON_RESULT_ASSERTION_IMPL(cond, val) do { ::ams::diag::impl::FatalErrorByResultForNx(val); AMS_INFINITE_LOOP(); AMS_ASSUME(false); } while (false) + #define AMS_CALL_ON_RESULT_ABORT_IMPL(cond, val) do { ::ams::diag::impl::FatalErrorByResultForNx(val); AMS_INFINITE_LOOP(); AMS_ASSUME(false); } while (false) +#elif defined(ATMOSPHERE_OS_HORIZON) + #define AMS_CALL_ON_RESULT_ASSERTION_IMPL(cond, val) AMS_CALL_ASSERT_FAIL_IMPL(::ams::diag::AssertionType_Assert, "ams::Result::IsSuccess()", "Failed: %s\n Module: %d\n Description: %d\n InnerValue: 0x%08" PRIX32, cond, val.GetModule(), val.GetDescription(), static_cast<::ams::Result>(val).GetInnerValue()) + #define AMS_CALL_ON_RESULT_ABORT_IMPL(cond, val) AMS_CALL_ABORT_IMPL("ams::Result::IsSuccess()", "Failed: %s\n Module: %d\n Description: %d\n InnerValue: 0x%08" PRIX32, cond, static_cast<::ams::Result>(val).GetModule(), static_cast<::ams::Result>(val).GetDescription(), static_cast<::ams::Result>(val).GetInnerValue()) +#else + #define AMS_CALL_ON_RESULT_ASSERTION_IMPL(cond, val) AMS_CALL_ASSERT_FAIL_IMPL(::ams::diag::AssertionType_Assert, "ams::Result::IsSuccess()", "Failed: %s\n Module: %d\n Description: %d\n InnerValue: 0x%08" PRIX32 "\n Name: %s", cond, val.GetModule(), val.GetDescription(), static_cast<::ams::Result>(val).GetInnerValue(), ::ams::GetResultName(static_cast<::ams::Result>(val))) + #define AMS_CALL_ON_RESULT_ABORT_IMPL(cond, val) AMS_CALL_ABORT_IMPL("ams::Result::IsSuccess()", "Failed: %s\n Module: %d\n Description: %d\n InnerValue: 0x%08" PRIX32 "\n Name: %s", cond, static_cast<::ams::Result>(val).GetModule(), static_cast<::ams::Result>(val).GetDescription(), static_cast<::ams::Result>(val).GetInnerValue(), ::ams::GetResultName(static_cast<::ams::Result>(val))) +#endif + +/// Evaluates an expression that returns a result, and asserts the result if it would fail. +#ifdef AMS_ENABLE_ASSERTIONS +#define R_ASSERT(res_expr) \ + { \ + if (const auto _tmp_r_assert_rc = (res_expr); AMS_UNLIKELY(R_FAILED(_tmp_r_assert_rc))) { \ + AMS_CALL_ON_RESULT_ASSERTION_IMPL(#res_expr, _tmp_r_assert_rc); \ + } \ + } +#else +#define R_ASSERT(res_expr) AMS_UNUSED((res_expr)); +#endif + +/// Evaluates an expression that returns a result, and aborts if the result would fail. +#define R_ABORT_UNLESS(res_expr) \ + { \ + if (const auto _tmp_r_abort_rc = (res_expr); AMS_UNLIKELY(R_FAILED(_tmp_r_abort_rc))) { \ + AMS_CALL_ON_RESULT_ABORT_IMPL(#res_expr, _tmp_r_abort_rc); \ + } \ + } + +/// Evaluates a boolean expression, and returns a result unless that expression is true. +#define R_UNLESS(expr, res) \ + { \ + if (!(expr)) { \ + R_THROW(res); \ + } \ + } + +/// Evaluates a boolean expression, and succeeds if that expression is true. +#define R_SUCCEED_IF(expr) R_UNLESS(!(expr), ResultSuccess()) + +/// Helpers for pattern-matching on a result expression, if the result would fail. +#define R_CURRENT_RESULT _tmp_r_try_catch_current_result + +#define R_TRY_CATCH(res_expr) \ + { \ + const auto R_CURRENT_RESULT = (res_expr); \ + if (R_FAILED(R_CURRENT_RESULT)) { \ + if (false) + +#define R_CATCH(...) \ + } else if (::ams::result::impl::EvaluateAnyResultIncludes<__VA_ARGS__>(R_CURRENT_RESULT)) { \ + if (true) + +#define R_CATCH_MODULE(__module__) \ + } else if ((R_CURRENT_RESULT).GetModule() == ::ams::R_NAMESPACE_MODULE_ID(__module__)) { \ + if (true) + +#define R_CONVERT(catch_type, convert_type) \ + R_CATCH(catch_type) { R_THROW(static_cast<::ams::Result>(convert_type)); } + +#define R_CATCH_ALL() \ + } else if (R_FAILED(R_CURRENT_RESULT)) { \ + if (true) + +#define R_CONVERT_ALL(convert_type) \ + R_CATCH_ALL() { R_THROW(static_cast<::ams::Result>(convert_type)); } + +#define R_CATCH_RETHROW(catch_type) \ + R_CONVERT(catch_type, R_CURRENT_RESULT) + +#define R_END_TRY_CATCH \ + else if (R_FAILED(R_CURRENT_RESULT)) { \ + R_THROW(R_CURRENT_RESULT); \ + } \ + } \ + } + +#define R_END_TRY_CATCH_WITH_ASSERT \ + else { \ + R_ASSERT(R_CURRENT_RESULT); \ + } \ + } \ + } + + +#define R_END_TRY_CATCH_WITH_ABORT_UNLESS \ + else { \ + R_ABORT_UNLESS(R_CURRENT_RESULT); \ + } \ + } \ + } + + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/ro_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/ro_results.hpp new file mode 100644 index 00000000..c187749d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/ro_results.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::ro, 22); + +namespace ams::ro { + + R_DEFINE_ERROR_RANGE(RoError, 1, 1023); + R_DEFINE_ERROR_RESULT(OutOfAddressSpace, 2); + R_DEFINE_ERROR_RESULT(AlreadyLoaded, 3); + R_DEFINE_ERROR_RESULT(InvalidNro, 4); + + R_DEFINE_ERROR_RESULT(InvalidNrr, 6); + R_DEFINE_ERROR_RESULT(TooManyNro, 7); + R_DEFINE_ERROR_RESULT(TooManyNrr, 8); + R_DEFINE_ERROR_RESULT(NotAuthorized, 9); + R_DEFINE_ERROR_RESULT(InvalidNrrKind, 10); + + R_DEFINE_ERROR_RESULT(InternalError, 1023); + + R_DEFINE_ERROR_RESULT(InvalidAddress, 1025); + R_DEFINE_ERROR_RESULT(InvalidSize, 1026); + + R_DEFINE_ERROR_RESULT(NotLoaded, 1028); + R_DEFINE_ERROR_RESULT(NotRegistered, 1029); + R_DEFINE_ERROR_RESULT(InvalidSession, 1030); + R_DEFINE_ERROR_RESULT(InvalidProcess, 1031); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/scs_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/scs_results.hpp new file mode 100644 index 00000000..59dcfe81 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/scs_results.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> +#include <vapours/results/cs_results.hpp> + +namespace ams::scs { + + using ams::cs::ResultUnknownCommand; + using ams::cs::ResultOutOfResource; + using ams::cs::ResultNoSocket; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/sdmmc_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/sdmmc_results.hpp new file mode 100644 index 00000000..3477907c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/sdmmc_results.hpp @@ -0,0 +1,105 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::sdmmc, 24); + +namespace ams::sdmmc { + + R_DEFINE_ERROR_RESULT(NoDevice, 1); + R_DEFINE_ERROR_RESULT(NotActivated, 2); + R_DEFINE_ERROR_RESULT(DeviceRemoved, 3); + R_DEFINE_ERROR_RESULT(NotAwakened, 4); + + R_DEFINE_ERROR_RANGE(CommunicationError, 32, 126); + R_DEFINE_ERROR_RANGE(CommunicationNotAttained, 33, 46); + R_DEFINE_ERROR_RESULT(ResponseIndexError, 34); + R_DEFINE_ERROR_RESULT(ResponseEndBitError, 35); + R_DEFINE_ERROR_RESULT(ResponseCrcError, 36); + R_DEFINE_ERROR_RESULT(ResponseTimeoutError, 37); + R_DEFINE_ERROR_RESULT(DataEndBitError, 38); + R_DEFINE_ERROR_RESULT(DataCrcError, 39); + R_DEFINE_ERROR_RESULT(DataTimeoutError, 40); + R_DEFINE_ERROR_RESULT(AutoCommandResponseIndexError, 41); + R_DEFINE_ERROR_RESULT(AutoCommandResponseEndBitError, 42); + R_DEFINE_ERROR_RESULT(AutoCommandResponseCrcError, 43); + R_DEFINE_ERROR_RESULT(AutoCommandResponseTimeoutError, 44); + R_DEFINE_ERROR_RESULT(CommandCompleteSoftwareTimeout, 45); + R_DEFINE_ERROR_RESULT(TransferCompleteSoftwareTimeout, 46); + R_DEFINE_ERROR_RANGE(DeviceStatusHasError, 48, 70); + R_DEFINE_ERROR_RESULT(DeviceStatusAddressOutOfRange, 49); + R_DEFINE_ERROR_RESULT(DeviceStatusAddressMisaligned, 50); + R_DEFINE_ERROR_RESULT(DeviceStatusBlockLenError, 51); + R_DEFINE_ERROR_RESULT(DeviceStatusEraseSeqError, 52); + R_DEFINE_ERROR_RESULT(DeviceStatusEraseParam, 53); + R_DEFINE_ERROR_RESULT(DeviceStatusWpViolation, 54); + R_DEFINE_ERROR_RESULT(DeviceStatusLockUnlockFailed, 55); + R_DEFINE_ERROR_RESULT(DeviceStatusComCrcError, 56); + R_DEFINE_ERROR_RESULT(DeviceStatusIllegalCommand, 57); + R_DEFINE_ERROR_RESULT(DeviceStatusDeviceEccFailed, 58); + R_DEFINE_ERROR_RESULT(DeviceStatusCcError, 59); + R_DEFINE_ERROR_RESULT(DeviceStatusError, 60); + R_DEFINE_ERROR_RESULT(DeviceStatusCidCsdOverwrite, 61); + R_DEFINE_ERROR_RESULT(DeviceStatusWpEraseSkip, 62); + R_DEFINE_ERROR_RESULT(DeviceStatusEraseReset, 63); + R_DEFINE_ERROR_RESULT(DeviceStatusSwitchError, 64); + R_DEFINE_ERROR_RESULT(UnexpectedDeviceState, 72); + R_DEFINE_ERROR_RESULT(UnexpectedDeviceCsdValue, 73); + R_DEFINE_ERROR_RESULT(AbortTransactionSoftwareTimeout, 74); + R_DEFINE_ERROR_RESULT(CommandInhibitCmdSoftwareTimeout, 75); + R_DEFINE_ERROR_RESULT(CommandInhibitDatSoftwareTimeout, 76); + R_DEFINE_ERROR_RESULT(BusySoftwareTimeout, 77); + R_DEFINE_ERROR_RESULT(IssueTuningCommandSoftwareTimeout, 78); + R_DEFINE_ERROR_RESULT(TuningFailed, 79); + R_DEFINE_ERROR_RESULT(MmcInitializationSoftwareTimeout, 80); + R_DEFINE_ERROR_RESULT(MmcNotSupportExtendedCsd, 81); + R_DEFINE_ERROR_RESULT(UnexpectedMmcExtendedCsdValue, 82); + R_DEFINE_ERROR_RESULT(MmcEraseSoftwareTimeout, 83); + R_DEFINE_ERROR_RESULT(SdCardValidationError, 84); + R_DEFINE_ERROR_RESULT(SdCardInitializationSoftwareTimeout, 85); + R_DEFINE_ERROR_RESULT(SdCardGetValidRcaSoftwareTimeout, 86); + R_DEFINE_ERROR_RESULT(UnexpectedSdCardAcmdDisabled, 87); + R_DEFINE_ERROR_RESULT(SdCardNotSupportSwitchFunctionStatus, 88); + R_DEFINE_ERROR_RESULT(UnexpectedSdCardSwitchFunctionStatus, 89); + R_DEFINE_ERROR_RESULT(SdCardNotSupportAccessMode, 90); + R_DEFINE_ERROR_RESULT(SdCardNot4BitBusWidthAtUhsIMode, 91); + R_DEFINE_ERROR_RESULT(SdCardNotSupportSdr104AndSdr50, 92); + R_DEFINE_ERROR_RESULT(SdCardCannotSwitchAccessMode, 93); + R_DEFINE_ERROR_RESULT(SdCardFailedSwitchAccessMode, 94); + R_DEFINE_ERROR_RESULT(SdCardUnacceptableCurrentConsumption, 95); + R_DEFINE_ERROR_RESULT(SdCardNotReadyToVoltageSwitch, 96); + R_DEFINE_ERROR_RESULT(SdCardNotCompleteVoltageSwitch, 97); + + R_DEFINE_ERROR_RANGE(HostControllerUnexpected, 128, 158); + R_DEFINE_ERROR_RESULT(InternalClockStableSoftwareTimeout, 129); + R_DEFINE_ERROR_RESULT(SdHostStandardUnknownAutoCmdError, 130); + R_DEFINE_ERROR_RESULT(SdHostStandardUnknownError, 131); + R_DEFINE_ERROR_RESULT(SdmmcDllCalibrationSoftwareTimeout, 132); + R_DEFINE_ERROR_RESULT(SdmmcDllApplicationSoftwareTimeout, 133); + R_DEFINE_ERROR_RESULT(SdHostStandardFailSwitchTo1_8V, 134); + R_DEFINE_ERROR_RESULT(DriveStrengthCalibrationNotCompleted, 135); + R_DEFINE_ERROR_RESULT(DriveStrengthCalibrationSoftwareTimeout, 136); + R_DEFINE_ERROR_RESULT(SdmmcCompShortToGnd, 137); + R_DEFINE_ERROR_RESULT(SdmmcCompOpen, 138); + + R_DEFINE_ERROR_RANGE(InternalError, 160, 190); + R_DEFINE_ERROR_RESULT(NoWaitedInterrupt, 161); + R_DEFINE_ERROR_RESULT(WaitInterruptSoftwareTimeout, 162); + + R_DEFINE_ERROR_RESULT(NotImplemented, 201); +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/settings_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/settings_results.hpp new file mode 100644 index 00000000..828b489b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/settings_results.hpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::settings, 105); + +namespace ams::settings { + + R_DEFINE_ERROR_RESULT(SettingsItemNotFound, 11); + R_DEFINE_ERROR_RESULT(StopIteration, 21); + + R_DEFINE_ERROR_RANGE(InternalError, 100, 149); + R_DEFINE_ERROR_RESULT(SettingsItemKeyAllocationFailed, 101); + R_DEFINE_ERROR_RESULT(SettingsItemValueAllocationFailed, 102); + R_DEFINE_ERROR_RESULT(SettingsItemKeyIteratorAllocationFailed, 111); + R_DEFINE_ERROR_RESULT(TooLargeSystemSaveData, 141); + + R_DEFINE_ERROR_RANGE(InvalidArgument, 200, 399); + R_DEFINE_ERROR_RESULT(NullSettingsName, 201); + R_DEFINE_ERROR_RESULT(NullSettingsItemKey, 202); + R_DEFINE_ERROR_RESULT(NullSettingsItemValue, 203); + R_DEFINE_ERROR_RESULT(NullSettingsItemKeyBuffer, 204); + R_DEFINE_ERROR_RESULT(NullSettingsItemValueBuffer, 205); + + R_DEFINE_ERROR_RESULT(EmptySettingsName, 221); + R_DEFINE_ERROR_RESULT(EmptySettingsItemKey, 222); + + R_DEFINE_ERROR_RESULT(TooLongSettingsName, 241); + R_DEFINE_ERROR_RESULT(TooLongSettingsItemKey, 242); + + R_DEFINE_ERROR_RESULT(InvalidFormatSettingsName, 261); + R_DEFINE_ERROR_RESULT(InvalidFormatSettingsItemKey, 262); + R_DEFINE_ERROR_RESULT(InvalidFormatSettingsItemValue, 263); + + R_DEFINE_ERROR_RESULT(NotFoundSettingsItemKeyIterator, 281); + + R_DEFINE_ERROR_RANGE(CalibrationDataError, 580, 599); + R_DEFINE_ERROR_RESULT(CalibrationDataFileSystemCorrupted, 581); + R_DEFINE_ERROR_RESULT(CalibrationDataCrcError, 582); + R_DEFINE_ERROR_RESULT(CalibrationDataShaError, 583); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/sf_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/sf_results.hpp new file mode 100644 index 00000000..825ac175 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/sf_results.hpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::sf, 10); + +namespace ams::sf { + + R_DEFINE_ERROR_RESULT(NotSupported, 1); + R_DEFINE_ERROR_RESULT(PreconditionViolation, 3); + + //namespace cmif { + + R_DEFINE_ERROR_RESULT_NS(cmif, InvalidHeaderSize, 202); + R_DEFINE_ERROR_RESULT_NS(cmif, InvalidInHeader, 211); + R_DEFINE_ERROR_RESULT_NS(cmif, UnknownCommandId, 221); + R_DEFINE_ERROR_RESULT_NS(cmif, InvalidOutRawSize, 232); + R_DEFINE_ERROR_RESULT_NS(cmif, InvalidNumInObjects, 235); + R_DEFINE_ERROR_RESULT_NS(cmif, InvalidNumOutObjects, 236); + R_DEFINE_ERROR_RESULT_NS(cmif, InvalidInObject, 239); + + R_DEFINE_ERROR_RESULT_NS(cmif, TargetNotFound, 261); + + R_DEFINE_ERROR_RESULT_NS(cmif, OutOfDomainEntries, 301); + + //} + + //namespace impl { + + R_DEFINE_ABSTRACT_ERROR_RANGE_NS(impl, RequestContextChanged, 800, 899); + R_DEFINE_ABSTRACT_ERROR_RANGE_NS(impl, RequestInvalidated, 801, 809); + R_DEFINE_ERROR_RESULT_NS(impl, RequestInvalidatedByUser, 802); + + //} + + R_DEFINE_ABSTRACT_ERROR_RANGE(RequestDeferred, 811, 819); + R_DEFINE_ERROR_RESULT(RequestDeferredByUser, 812); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/sm_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/sm_results.hpp new file mode 100644 index 00000000..7b29894b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/sm_results.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::sm, 21); + +namespace ams::sm { + + R_DEFINE_ERROR_RESULT(OutOfProcesses, 1); + R_DEFINE_ERROR_RESULT(InvalidClient, 2); + R_DEFINE_ERROR_RESULT(OutOfSessions, 3); + R_DEFINE_ERROR_RESULT(AlreadyRegistered, 4); + R_DEFINE_ERROR_RESULT(OutOfServices, 5); + R_DEFINE_ERROR_RESULT(InvalidServiceName, 6); + R_DEFINE_ERROR_RESULT(NotRegistered, 7); + R_DEFINE_ERROR_RESULT(NotAllowed, 8); + R_DEFINE_ERROR_RESULT(TooLargeAccessControl, 9); + + /* Results 1000-2000 used as extension for Atmosphere Mitm. */ + //namespace mitm { + + R_DEFINE_ERROR_RESULT_NS(mitm, ShouldForwardToSession, 1000); + R_DEFINE_ERROR_RESULT_NS(mitm, ProcessNotAssociated, 1100); + + //} + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/socket_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/socket_results.hpp new file mode 100644 index 00000000..f83cbaee --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/socket_results.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::socket, 27); + +namespace ams::socket { + + R_DEFINE_ERROR_RESULT(InsufficientProvidedMemory, 1); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/spl_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/spl_results.hpp new file mode 100644 index 00000000..12c1199b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/spl_results.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::spl, 26); + +namespace ams::spl { + + R_DEFINE_ERROR_RANGE(SecureMonitorError, 0, 99); + R_DEFINE_ERROR_RESULT(SecureMonitorNotSupported, 1); + R_DEFINE_ERROR_RESULT(SecureMonitorInvalidArgument, 2); + R_DEFINE_ERROR_RESULT(SecureMonitorBusy, 3); + R_DEFINE_ERROR_RESULT(SecureMonitorNoAsyncOperation, 4); + R_DEFINE_ERROR_RESULT(SecureMonitorInvalidAsyncOperation, 5); + R_DEFINE_ERROR_RESULT(SecureMonitorNotPermitted, 6); + R_DEFINE_ERROR_RESULT(SecureMonitorNotInitialized, 7); + + R_DEFINE_ERROR_RESULT(InvalidBufferSize, 100); + R_DEFINE_ERROR_RESULT(UnexpectedSecureMonitorResult, 101); + R_DEFINE_ERROR_RESULT(DecryptionFailed, 102); + R_DEFINE_ERROR_RESULT(InvalidDeviceUniqueDataType, 103); + R_DEFINE_ERROR_RESULT(NoAvailableKeySlot, 104); + R_DEFINE_ERROR_RESULT(InvalidKeySlot, 105); + R_DEFINE_ERROR_RESULT(BootReasonAlreadyInitialized, 106); + R_DEFINE_ERROR_RESULT(BootReasonNotInitialized, 107); + R_DEFINE_ERROR_RESULT(InvalidArgument, 108); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/sprofile_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/sprofile_results.hpp new file mode 100644 index 00000000..4000bd8f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/sprofile_results.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::sprofile, 246); + +namespace ams::sprofile { + + R_DEFINE_ERROR_RESULT(InvalidArgument, 100); + R_DEFINE_ERROR_RESULT(InvalidState, 101); + + R_DEFINE_ERROR_RESULT(NotPermitted, 303); + + R_DEFINE_ERROR_RESULT(AllocationFailed, 401); + + R_DEFINE_ERROR_RESULT(KeyNotFound, 600); + R_DEFINE_ERROR_RESULT(InvalidDataType, 601); + + R_DEFINE_ERROR_RESULT(MaxListeners, 620); + R_DEFINE_ERROR_RESULT(AlreadyListening, 621); + R_DEFINE_ERROR_RESULT(NotListening, 622); + R_DEFINE_ERROR_RESULT(MaxObservers, 623); + + R_DEFINE_ERROR_RESULT(InvalidMetadataVersion, 3210); + R_DEFINE_ERROR_RESULT(InvalidMetadataHash, 3211); + R_DEFINE_ERROR_RESULT(InvalidDataVersion, 3230); + R_DEFINE_ERROR_RESULT(InvalidDataHash, 3231); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/svc_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/svc_results.hpp new file mode 100644 index 00000000..7a01b569 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/svc_results.hpp @@ -0,0 +1,81 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::svc, 1); + +namespace ams::svc { + + R_DEFINE_ERROR_RESULT(OutOfSessions, 7); + + R_DEFINE_ERROR_RESULT(InvalidArgument, 14); + + R_DEFINE_ERROR_RESULT(NotImplemented, 33); + + R_DEFINE_ERROR_RESULT(StopProcessingException, 54); + + R_DEFINE_ERROR_RESULT(NoSynchronizationObject, 57); + + R_DEFINE_ERROR_RESULT(TerminationRequested, 59); + + R_DEFINE_ERROR_RESULT(NoEvent, 70); + + R_DEFINE_ERROR_RESULT(InvalidSize, 101); + R_DEFINE_ERROR_RESULT(InvalidAddress, 102); + R_DEFINE_ERROR_RESULT(OutOfResource, 103); + R_DEFINE_ERROR_RESULT(OutOfMemory, 104); + R_DEFINE_ERROR_RESULT(OutOfHandles, 105); + R_DEFINE_ERROR_RESULT(InvalidCurrentMemory, 106); + + R_DEFINE_ERROR_RESULT(InvalidNewMemoryPermission, 108); + + R_DEFINE_ERROR_RESULT(InvalidMemoryRegion, 110); + + R_DEFINE_ERROR_RESULT(InvalidPriority, 112); + R_DEFINE_ERROR_RESULT(InvalidCoreId, 113); + R_DEFINE_ERROR_RESULT(InvalidHandle, 114); + R_DEFINE_ERROR_RESULT(InvalidPointer, 115); + R_DEFINE_ERROR_RESULT(InvalidCombination, 116); + R_DEFINE_ERROR_RESULT(TimedOut, 117); + R_DEFINE_ERROR_RESULT(Cancelled, 118); + R_DEFINE_ERROR_RESULT(OutOfRange, 119); + R_DEFINE_ERROR_RESULT(InvalidEnumValue, 120); + R_DEFINE_ERROR_RESULT(NotFound, 121); + R_DEFINE_ERROR_RESULT(Busy, 122); + R_DEFINE_ERROR_RESULT(SessionClosed, 123); + R_DEFINE_ERROR_RESULT(NotHandled, 124); + R_DEFINE_ERROR_RESULT(InvalidState, 125); + R_DEFINE_ERROR_RESULT(ReservedUsed, 126); + R_DEFINE_ERROR_RESULT(NotSupported, 127); + R_DEFINE_ERROR_RESULT(Debug, 128); + R_DEFINE_ERROR_RESULT(NoThread, 129); + R_DEFINE_ERROR_RESULT(UnknownThread, 130); + R_DEFINE_ERROR_RESULT(PortClosed, 131); + R_DEFINE_ERROR_RESULT(LimitReached, 132); + R_DEFINE_ERROR_RESULT(InvalidMemoryPool, 133); + + R_DEFINE_ERROR_RESULT(ReceiveListBroken, 258); + R_DEFINE_ERROR_RESULT(OutOfAddressSpace, 259); + R_DEFINE_ERROR_RESULT(MessageTooLarge, 260); + + R_DEFINE_ERROR_RESULT(InvalidProcessId, 517); + R_DEFINE_ERROR_RESULT(InvalidThreadId, 518); + R_DEFINE_ERROR_RESULT(InvalidId, 519); + R_DEFINE_ERROR_RESULT(ProcessTerminated, 520); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/time_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/time_results.hpp new file mode 100644 index 00000000..c83941fe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/time_results.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::time, 116); + +namespace ams::time { + + R_DEFINE_ERROR_RESULT(NotInitialized, 0); + + R_DEFINE_ERROR_RESULT(NotComparable, 200); + R_DEFINE_ERROR_RESULT(Overflowed, 201); + + R_DEFINE_ABSTRACT_ERROR_RANGE(InvalidArgument, 900, 919); + R_DEFINE_ERROR_RESULT(InvalidPointer, 901); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/tipc_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/tipc_results.hpp new file mode 100644 index 00000000..c381c87e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/tipc_results.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::tipc, 35); + +namespace ams::tipc { + + R_DEFINE_ERROR_RESULT(InvalidMethod, 10); + R_DEFINE_ERROR_RESULT(InvalidMessageFormat, 15); + + R_DEFINE_ERROR_RESULT(RequestDeferred, 100); + R_DEFINE_ERROR_RESULT(SessionClosed, 101); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/tma_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/tma_results.hpp new file mode 100644 index 00000000..ce464687 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/tma_results.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::tma, 12); + +namespace ams::tma { + + R_DEFINE_ERROR_RESULT(Unknown, 1); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/updater_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/updater_results.hpp new file mode 100644 index 00000000..0ff0e135 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/updater_results.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::updater, 158); + +namespace ams::updater { + + R_DEFINE_ERROR_RESULT(BootImagePackageNotFound, 2); + R_DEFINE_ERROR_RESULT(InvalidBootImagePackage, 3); + R_DEFINE_ERROR_RESULT(TooSmallWorkBuffer, 4); + R_DEFINE_ERROR_RESULT(NotAlignedWorkBuffer, 5); + R_DEFINE_ERROR_RESULT(NeedsRepairBootImages, 6); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/usb_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/usb_results.hpp new file mode 100644 index 00000000..a4185b16 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/usb_results.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::usb, 140); + +namespace ams::usb { + + R_DEFINE_ERROR_RESULT(NotInitialized, 0); + R_DEFINE_ERROR_RESULT(AlreadyInitialized, 1); + + R_DEFINE_ERROR_RANGE(InvalidParameter, 100, 199); + R_DEFINE_ERROR_RESULT(AlignmentError, 103); + + R_DEFINE_ERROR_RESULT(OperationDenied, 201); + R_DEFINE_ERROR_RESULT(MemAllocFailure, 202); + R_DEFINE_ERROR_RESULT(ResourceBusy, 206); + R_DEFINE_ERROR_RESULT(InternalStateError, 207); + + R_DEFINE_ERROR_RESULT(TransactionError, 401); + R_DEFINE_ERROR_RESULT(Interrupted, 409); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/vi_results.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/vi_results.hpp new file mode 100644 index 00000000..3df6c3fc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/results/vi_results.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/results/results_common.hpp> + +R_DEFINE_NAMESPACE_RESULT_MODULE(ams::vi, 114); + +namespace ams::vi { + + R_DEFINE_ERROR_RESULT(OperationFailed, 1); + R_DEFINE_ERROR_RESULT(NotSupported, 6); + R_DEFINE_ERROR_RESULT(NotFound, 7); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/sdmmc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/sdmmc.hpp new file mode 100644 index 00000000..25a8d95c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/sdmmc.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/results.hpp> +#include <vapours/util.hpp> +#include <vapours/svc.hpp> + +#include <vapours/sdmmc/sdmmc_build_config.hpp> +#include <vapours/sdmmc/sdmmc_common.hpp> +#include <vapours/sdmmc/sdmmc_mmc.hpp> +#include <vapours/sdmmc/sdmmc_sd_card.hpp> +#include <vapours/sdmmc/sdmmc_gc_asic.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/sdmmc/sdmmc_build_config.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/sdmmc/sdmmc_build_config.hpp new file mode 100644 index 00000000..80a2526e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/sdmmc/sdmmc_build_config.hpp @@ -0,0 +1,72 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/results.hpp> +#include <vapours/util.hpp> +#include <vapours/svc.hpp> +#include <vapours/dd.hpp> + +#if defined(ATMOSPHERE_IS_EXOSPHERE) + + //#define AMS_SDMMC_THREAD_SAFE + //#define AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS + //#define AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL + //#define AMS_SDMMC_USE_DEVICE_DETECTOR + //#define AMS_SDMMC_USE_LOGGER + //#define AMS_SDMMC_USE_OS_EVENTS + //#define AMS_SDMMC_USE_OS_TIMER + #define AMS_SDMMC_USE_UTIL_TIMER + #define AMS_SDMMC_ENABLE_MMC_HS400 + #define AMS_SDMMC_ENABLE_SD_UHS_I + #define AMS_SDMMC_SET_PLLC4_BASE + //#define AMS_SDMMC_USE_SD_CARD_DETECTOR + +#elif defined(ATMOSPHERE_IS_MESOSPHERE) + + //#define AMS_SDMMC_THREAD_SAFE + //#define AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS + //#define AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL + //#define AMS_SDMMC_USE_DEVICE_DETECTOR + //#define AMS_SDMMC_USE_LOGGER + //#define AMS_SDMMC_USE_OS_EVENTS + //#define AMS_SDMMC_USE_OS_TIMER + #define AMS_SDMMC_USE_UTIL_TIMER + //#define AMS_SDMMC_ENABLE_MMC_HS400 + //#define AMS_SDMMC_ENABLE_SD_UHS_I + //#define AMS_SDMMC_SET_PLLC4_BASE + //#define AMS_SDMMC_USE_SD_CARD_DETECTOR + +#elif defined(ATMOSPHERE_IS_STRATOSPHERE) + + #define AMS_SDMMC_THREAD_SAFE + #define AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS + #define AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL + #define AMS_SDMMC_USE_DEVICE_DETECTOR + #define AMS_SDMMC_USE_LOGGER + #define AMS_SDMMC_USE_OS_EVENTS + #define AMS_SDMMC_USE_OS_TIMER + //#define AMS_SDMMC_USE_UTIL_TIMER + #define AMS_SDMMC_ENABLE_MMC_HS400 + #define AMS_SDMMC_ENABLE_SD_UHS_I + #define AMS_SDMMC_SET_PLLC4_BASE + #define AMS_SDMMC_USE_SD_CARD_DETECTOR + +#else + #error "Unknown execution context for ams::sdmmc!" +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/sdmmc/sdmmc_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/sdmmc/sdmmc_common.hpp new file mode 100644 index 00000000..94c9f2ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/sdmmc/sdmmc_common.hpp @@ -0,0 +1,114 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/sdmmc/sdmmc_build_config.hpp> + +namespace ams::sdmmc { + + enum BusPower { + BusPower_Off = 0, + BusPower_1_8V = 1, + BusPower_3_3V = 2, + }; + + enum BusWidth { + BusWidth_1Bit = 0, + BusWidth_4Bit = 1, + BusWidth_8Bit = 2, + }; + + enum SpeedMode { + SpeedMode_MmcIdentification = 0, + SpeedMode_MmcLegacySpeed = 1, + SpeedMode_MmcHighSpeed = 2, + SpeedMode_MmcHs200 = 3, + SpeedMode_MmcHs400 = 4, + SpeedMode_SdCardIdentification = 5, + SpeedMode_SdCardDefaultSpeed = 6, + SpeedMode_SdCardHighSpeed = 7, + SpeedMode_SdCardSdr12 = 8, + SpeedMode_SdCardSdr25 = 9, + SpeedMode_SdCardSdr50 = 10, + SpeedMode_SdCardSdr104 = 11, + SpeedMode_SdCardDdr50 = 12, + SpeedMode_GcAsicFpgaSpeed = 13, + SpeedMode_GcAsicSpeed = 14, + }; + + enum Port { + Port_Mmc0 = 0, + Port_SdCard0 = 1, + Port_GcAsic0 = 2, + }; + + struct ErrorInfo { + u32 num_activation_failures; + u32 num_activation_error_corrections; + u32 num_read_write_failures; + u32 num_read_write_error_corrections; + }; + + struct DataTransfer { + void *buffer; + size_t buffer_size; + size_t block_size; + u32 num_blocks; + bool is_read; + }; + + using DeviceDetectionEventCallback = void (*)(void *); + + constexpr inline size_t SectorSize = 0x200; + + constexpr inline size_t DeviceCidSize = 0x10; + constexpr inline size_t DeviceCsdSize = 0x10; + + constexpr inline size_t BufferDeviceVirtualAddressAlignment = alignof(ams::dd::DeviceVirtualAddress); + static_assert(BufferDeviceVirtualAddressAlignment >= 8); + + void Initialize(Port port); + void Finalize(Port port); + +#if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + void SwitchToPcvClockResetControl(); +#endif + +#if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void RegisterDeviceVirtualAddress(Port port, uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address); + void UnregisterDeviceVirtualAddress(Port port, uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address); +#endif + + void ChangeCheckTransferInterval(Port port, u32 ms); + void SetDefaultCheckTransferInterval(Port port); + + Result Activate(Port port); + void Deactivate(Port port); + + Result Read(void *dst, size_t dst_size, Port port, u32 sector_index, u32 num_sectors); + Result Write(Port port, u32 sector_index, u32 num_sectors, const void *src, size_t src_size); + + Result CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width, Port port); + + Result GetDeviceSpeedMode(SpeedMode *out, Port port); + Result GetDeviceMemoryCapacity(u32 *out_num_sectors, Port port); + Result GetDeviceStatus(u32 *out_device_status, Port port); + Result GetDeviceCid(void *out, size_t out_size, Port port); + Result GetDeviceCsd(void *out, size_t out_size, Port port); + + void GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size, Port port); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/sdmmc/sdmmc_gc_asic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/sdmmc/sdmmc_gc_asic.hpp new file mode 100644 index 00000000..931ca7b6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/sdmmc/sdmmc_gc_asic.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/sdmmc/sdmmc_build_config.hpp> + +namespace ams::sdmmc { + + constexpr inline size_t GcAsicOperationSize = 0x40; + + void PutGcAsicToSleep(Port port); + Result AwakenGcAsic(Port port); + Result WriteGcAsicOperation(Port port, const void *op_buf, size_t op_buf_size); + Result FinishGcAsicOperation(Port port); + Result AbortGcAsicOperation(Port port); + Result SleepGcAsic(Port port); + Result UpdateGcAsicKey(Port port); + + void SignalGcRemovedEvent(Port port); + void ClearGcRemovedEvent(Port port); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/sdmmc/sdmmc_mmc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/sdmmc/sdmmc_mmc.hpp new file mode 100644 index 00000000..13e71cd2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/sdmmc/sdmmc_mmc.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/sdmmc/sdmmc_build_config.hpp> + + +namespace ams::sdmmc { + + enum MmcPartition { + MmcPartition_UserData = 0, + MmcPartition_BootPartition1 = 1, + MmcPartition_BootPartition2 = 2, + MmcPartition_Unknown = 3, + }; + + constexpr inline size_t MmcExtendedCsdSize = 0x200; + constexpr inline size_t MmcWorkBufferSize = MmcExtendedCsdSize; + + void SetMmcWorkBuffer(Port port, void *buffer, size_t buffer_size); + void PutMmcToSleep(Port port); + void AwakenMmc(Port port); + Result SelectMmcPartition(Port port, MmcPartition mmc_partition); + Result EraseMmc(Port port); + Result GetMmcBootPartitionCapacity(u32 *out_num_sectors, Port port); + Result GetMmcExtendedCsd(void *out_buffer, size_t buffer_size, Port port); + + Result CheckMmcConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width, Port port); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/sdmmc/sdmmc_sd_card.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/sdmmc/sdmmc_sd_card.hpp new file mode 100644 index 00000000..d0dc551f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/sdmmc/sdmmc_sd_card.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/sdmmc/sdmmc_build_config.hpp> + +namespace ams::sdmmc { + + enum SdCardSwitchFunction { + SdCardSwitchFunction_CheckSupportedFunction = 0, + SdCardSwitchFunction_CheckDefault = 1, + SdCardSwitchFunction_CheckHighSpeed = 2, + SdCardSwitchFunction_CheckSdr50 = 3, + SdCardSwitchFunction_CheckSdr104 = 4, + SdCardSwitchFunction_CheckDdr50 = 5, + }; + + constexpr inline size_t SdCardScrSize = 0x08; + constexpr inline size_t SdCardSwitchFunctionStatusSize = 0x40; + constexpr inline size_t SdCardSdStatusSize = 0x40; + + constexpr inline size_t SdCardWorkBufferSize = SdCardSdStatusSize; + + void SetSdCardWorkBuffer(Port port, void *buffer, size_t buffer_size); + void PutSdCardToSleep(Port port); + void AwakenSdCard(Port port); + Result GetSdCardProtectedAreaCapacity(u32 *out_num_sectors, Port port); + Result GetSdCardScr(void *dst, size_t dst_size, Port port); + Result GetSdCardSwitchFunctionStatus(void *dst, size_t dst_size, Port port, SdCardSwitchFunction switch_function); + Result GetSdCardCurrentConsumption(u16 *out_current_consumption, Port port, SpeedMode speed_mode); + Result GetSdCardSdStatus(void *dst, size_t dst_size, Port port); + + Result CheckSdCardConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width, Port port); + + bool IsSdCardInserted(Port port); + bool IsSdCardRemoved(Port port); + + void RegisterSdCardDetectionEventCallback(Port port, DeviceDetectionEventCallback callback, void *arg); + void UnregisterSdCardDetectionEventCallback(Port port); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/span.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/span.hpp new file mode 100644 index 00000000..4f6ae448 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/span.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams { + + template<typename T> + using Span = std::span<T>; + + template<typename T> + constexpr Span<T> MakeSpan(T *ptr, size_t size) { return { ptr, size }; } + + template <typename T> + constexpr Span<T> MakeSpan(T *begin, T *end) { return { begin, end }; } + + template<typename T, size_t Size> + constexpr Span<T> MakeSpan(T (&arr)[Size]) { return Span<T>(arr); } + + template<typename T, size_t Size> + constexpr Span<T> MakeSpan(std::array<T, Size> &arr) { return Span<T>(arr); } + + template<typename T, size_t Size> + constexpr Span<T> MakeSpan(const std::array<T, Size> &arr) { return Span<const T>(arr); } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc.hpp new file mode 100644 index 00000000..657a6f5d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/results.hpp> + +#if defined(ATMOSPHERE_OS_HORIZON) +#include <vapours/svc/svc_types.hpp> +#include <vapours/svc/svc_definitions.hpp> +#include <vapours/svc/svc_memory_map.hpp> +#include <vapours/svc/svc_version.hpp> +#include <vapours/svc/ipc/svc_message_buffer.hpp> +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/arch/arm/svc_thread_local_region.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/arch/arm/svc_thread_local_region.hpp new file mode 100644 index 00000000..1822eaaf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/arch/arm/svc_thread_local_region.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/svc_types_common.hpp> + +namespace ams::svc::arch::arm { + + constexpr inline size_t NumTlsSlots = 16; + constexpr inline size_t MessageBufferSize = 0x100; + + struct ThreadLocalRegion { + u32 message_buffer[MessageBufferSize / sizeof(u32)]; + volatile u16 disable_count; + volatile u16 interrupt_flag; + /* TODO: Should we bother adding the Nintendo aarch32 thread local context here? */ + uintptr_t TODO[(0x200 - 0x104) / sizeof(uintptr_t)]; + }; + + ALWAYS_INLINE ThreadLocalRegion *GetThreadLocalRegion() { + ThreadLocalRegion *tlr; + __asm__ __volatile__("mrc p15, 0, %[tlr], c13, c0, 3" : [tlr]"=&r"(tlr) :: "memory"); + return tlr; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/arch/arm64/svc_thread_local_region.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/arch/arm64/svc_thread_local_region.hpp new file mode 100644 index 00000000..e32c9a57 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/arch/arm64/svc_thread_local_region.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/svc_types_common.hpp> + +namespace ams::svc::arch::arm64 { + + constexpr inline size_t NumTlsSlots = 16; + constexpr inline size_t MessageBufferSize = 0x100; + + struct ThreadLocalRegion { + u32 message_buffer[MessageBufferSize / sizeof(u32)]; + volatile u16 disable_count; + volatile u16 interrupt_flag; + volatile u8 cache_maintenance_flag; + /* TODO: How should we handle libnx vs Nintendo user thread local space? */ + uintptr_t TODO[(0x200 - 0x108) / sizeof(uintptr_t)]; + }; + static_assert(__builtin_offsetof(ThreadLocalRegion, disable_count) == 0x100); + static_assert(__builtin_offsetof(ThreadLocalRegion, interrupt_flag) == 0x102); + static_assert(__builtin_offsetof(ThreadLocalRegion, cache_maintenance_flag) == 0x104); + + ALWAYS_INLINE ThreadLocalRegion *GetThreadLocalRegion() { + ThreadLocalRegion *tlr; + __asm__ __volatile__("mrs %[tlr], tpidrro_el0" : [tlr]"=&r"(tlr)); + return tlr; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/board/generic/svc_device_name.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/board/generic/svc_device_name.hpp new file mode 100644 index 00000000..53b59d3b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/board/generic/svc_device_name.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/svc_types_common.hpp> + +namespace ams::svc::board::generic { + + enum DeviceName { + /* If there is no smmu, there are no device names. */ + DeviceName_Count = 0, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/board/nintendo/nx/svc_device_name.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/board/nintendo/nx/svc_device_name.hpp new file mode 100644 index 00000000..d033ce27 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/board/nintendo/nx/svc_device_name.hpp @@ -0,0 +1,80 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/svc_types_common.hpp> + +namespace ams::svc::board::nintendo::nx { + + enum DeviceName { + DeviceName_Afi = 0, + DeviceName_Avpc = 1, + DeviceName_Dc = 2, + DeviceName_Dcb = 3, + DeviceName_Hc = 4, + DeviceName_Hda = 5, + DeviceName_Isp2 = 6, + DeviceName_MsencNvenc = 7, + DeviceName_Nv = 8, + DeviceName_Nv2 = 9, + DeviceName_Ppcs = 10, + DeviceName_Sata = 11, + DeviceName_Vi = 12, + DeviceName_Vic = 13, + DeviceName_XusbHost = 14, + DeviceName_XusbDev = 15, + DeviceName_Tsec = 16, + DeviceName_Ppcs1 = 17, + DeviceName_Dc1 = 18, + DeviceName_Sdmmc1a = 19, + DeviceName_Sdmmc2a = 20, + DeviceName_Sdmmc3a = 21, + DeviceName_Sdmmc4a = 22, + DeviceName_Isp2b = 23, + DeviceName_Gpu = 24, + DeviceName_Gpub = 25, + DeviceName_Ppcs2 = 26, + DeviceName_Nvdec = 27, + DeviceName_Ape = 28, + DeviceName_Se = 29, + DeviceName_Nvjpg = 30, + DeviceName_Hc1 = 31, + DeviceName_Se1 = 32, + DeviceName_Axiap = 33, + DeviceName_Etr = 34, + DeviceName_Tsecb = 35, + DeviceName_Tsec1 = 36, + DeviceName_Tsecb1 = 37, + DeviceName_Nvdec1 = 38, + + DeviceName_Count, + }; + + namespace impl { + + constexpr inline const size_t RequiredNonSecureSystemMemorySizeVi = 0x2280 * 4_KB; + constexpr inline const size_t RequiredNonSecureSystemMemorySizeViFatal = 0x200 * 4_KB; + constexpr inline const size_t RequiredNonSecureSystemMemorySizeNvservices = 0x704 * 4_KB; + constexpr inline const size_t RequiredNonSecureSystemMemorySizeMisc = 0x80 * 4_KB; + + } + + constexpr inline const size_t RequiredNonSecureSystemMemorySize = impl::RequiredNonSecureSystemMemorySizeVi + + impl::RequiredNonSecureSystemMemorySizeNvservices + + impl::RequiredNonSecureSystemMemorySizeMisc; + + constexpr inline const size_t RequiredNonSecureSystemMemorySizeWithFatal = RequiredNonSecureSystemMemorySize + impl::RequiredNonSecureSystemMemorySizeViFatal; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/board/nintendo/nx/svc_hardware_constants.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/board/nintendo/nx/svc_hardware_constants.hpp new file mode 100644 index 00000000..bd7b48bb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/board/nintendo/nx/svc_hardware_constants.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/svc_types_common.hpp> + +namespace ams::svc::board::nintendo::nx { + + constexpr inline const s64 TicksPerSecond = 19'200'000; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/board/nintendo/nx/svc_io_pool_type.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/board/nintendo/nx/svc_io_pool_type.hpp new file mode 100644 index 00000000..c1e6a09e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/board/nintendo/nx/svc_io_pool_type.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/svc_types_common.hpp> + +namespace ams::svc::board::nintendo::nx { + + enum IoPoolType : u32 { + IoPoolType_PcieA2 = 0, /* NOTE: Name is not official. */ + + IoPoolType_Count = 1, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/board/qemu/virt/svc_hardware_constants.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/board/qemu/virt/svc_hardware_constants.hpp new file mode 100644 index 00000000..5160803a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/board/qemu/virt/svc_hardware_constants.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/svc_types_common.hpp> + +namespace ams::svc::board::qemu::virt { + + constexpr inline const s64 TicksPerSecond = 19'200'000; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_code_generator.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_code_generator.hpp new file mode 100644 index 00000000..e33b88a5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_code_generator.hpp @@ -0,0 +1,280 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/codegen/impl/svc_codegen_impl_common.hpp> + +namespace ams::svc::codegen::impl { + + #define SVC_CODEGEN_FOR_I_FROM_0_TO_64(HANDLER, ...) \ + HANDLER( 0, ## __VA_ARGS__); HANDLER( 1, ## __VA_ARGS__); HANDLER( 2, ## __VA_ARGS__); HANDLER( 3, ## __VA_ARGS__); \ + HANDLER( 4, ## __VA_ARGS__); HANDLER( 5, ## __VA_ARGS__); HANDLER( 6, ## __VA_ARGS__); HANDLER( 7, ## __VA_ARGS__); \ + HANDLER( 8, ## __VA_ARGS__); HANDLER( 9, ## __VA_ARGS__); HANDLER(10, ## __VA_ARGS__); HANDLER(11, ## __VA_ARGS__); \ + HANDLER(12, ## __VA_ARGS__); HANDLER(13, ## __VA_ARGS__); HANDLER(14, ## __VA_ARGS__); HANDLER(15, ## __VA_ARGS__); \ + HANDLER(16, ## __VA_ARGS__); HANDLER(17, ## __VA_ARGS__); HANDLER(18, ## __VA_ARGS__); HANDLER(19, ## __VA_ARGS__); \ + HANDLER(20, ## __VA_ARGS__); HANDLER(21, ## __VA_ARGS__); HANDLER(22, ## __VA_ARGS__); HANDLER(23, ## __VA_ARGS__); \ + HANDLER(24, ## __VA_ARGS__); HANDLER(25, ## __VA_ARGS__); HANDLER(26, ## __VA_ARGS__); HANDLER(27, ## __VA_ARGS__); \ + HANDLER(28, ## __VA_ARGS__); HANDLER(29, ## __VA_ARGS__); HANDLER(30, ## __VA_ARGS__); HANDLER(31, ## __VA_ARGS__); \ + HANDLER(32, ## __VA_ARGS__); HANDLER(33, ## __VA_ARGS__); HANDLER(34, ## __VA_ARGS__); HANDLER(35, ## __VA_ARGS__); \ + HANDLER(36, ## __VA_ARGS__); HANDLER(37, ## __VA_ARGS__); HANDLER(38, ## __VA_ARGS__); HANDLER(39, ## __VA_ARGS__); \ + HANDLER(40, ## __VA_ARGS__); HANDLER(41, ## __VA_ARGS__); HANDLER(42, ## __VA_ARGS__); HANDLER(43, ## __VA_ARGS__); \ + HANDLER(44, ## __VA_ARGS__); HANDLER(45, ## __VA_ARGS__); HANDLER(46, ## __VA_ARGS__); HANDLER(47, ## __VA_ARGS__); \ + HANDLER(48, ## __VA_ARGS__); HANDLER(49, ## __VA_ARGS__); HANDLER(50, ## __VA_ARGS__); HANDLER(51, ## __VA_ARGS__); \ + HANDLER(52, ## __VA_ARGS__); HANDLER(53, ## __VA_ARGS__); HANDLER(54, ## __VA_ARGS__); HANDLER(55, ## __VA_ARGS__); \ + HANDLER(56, ## __VA_ARGS__); HANDLER(57, ## __VA_ARGS__); HANDLER(58, ## __VA_ARGS__); HANDLER(59, ## __VA_ARGS__); \ + HANDLER(60, ## __VA_ARGS__); HANDLER(61, ## __VA_ARGS__); HANDLER(62, ## __VA_ARGS__); HANDLER(63, ## __VA_ARGS__); + + + class Aarch64CodeGenerator { + private: + struct RegisterPair { + size_t First; + size_t Second; + }; + + template<size_t ...Registers> + struct RegisterPairHelper; + + template<size_t First, size_t Second, size_t ...Rest> + struct RegisterPairHelper<First, Second, Rest...> { + static constexpr size_t PairCount = 1 + RegisterPairHelper<Rest...>::PairCount; + static constexpr std::array<RegisterPair, PairCount> Pairs = [] { + std::array<RegisterPair, PairCount> pairs = {}; + pairs[0] = RegisterPair{First, Second}; + if constexpr (RegisterPairHelper<Rest...>::PairCount) { + for (size_t i = 0; i < RegisterPairHelper<Rest...>::PairCount; i++) { + pairs[1+i] = RegisterPairHelper<Rest...>::Pairs[i]; + } + } + return pairs; + }(); + }; + + template<size_t First, size_t Second> + struct RegisterPairHelper<First, Second> { + static constexpr size_t PairCount = 1; + static constexpr std::array<RegisterPair, PairCount> Pairs = { RegisterPair{First, Second} }; + }; + + template<size_t First> + struct RegisterPairHelper<First> { + static constexpr size_t PairCount = 0; + static constexpr std::array<RegisterPair, 0> Pairs = {}; + }; + + template<size_t Reg> + static ALWAYS_INLINE void ClearRegister() { + __asm__ __volatile__("mov x%c[r], xzr" :: [r]"i"(Reg) : "memory"); + } + + template<size_t Reg> + static ALWAYS_INLINE void SaveRegister() { + __asm__ __volatile__("str x%c[r], [sp, -16]!" :: [r]"i"(Reg) : "memory"); + } + + template<size_t Reg> + static ALWAYS_INLINE void RestoreRegister() { + __asm__ __volatile__("ldr x%c[r], [sp], 16" :: [r]"i"(Reg) : "memory"); + } + + template<size_t Reg0, size_t Reg1> + static ALWAYS_INLINE void SaveRegisterPair() { + __asm__ __volatile__("stp x%c[r0], x%c[r1], [sp, -16]!" :: [r0]"i"(Reg0), [r1]"i"(Reg1) : "memory"); + } + + template<size_t Reg0, size_t Reg1> + static ALWAYS_INLINE void RestoreRegisterPair() { + __asm__ __volatile__("ldp x%c[r0], x%c[r1], [sp], 16" :: [r0]"i"(Reg0), [r1]"i"(Reg1) : "memory"); + } + + template<size_t First, size_t... Rest> + static ALWAYS_INLINE void SaveRegistersImpl() { + #define SVC_CODEGEN_HANDLER(n) \ + do { if constexpr ((63 - n) < Pairs.size()) { SaveRegisterPair<Pairs[(63 - n)].First, Pairs[(63 - n)].Second>(); } } while (0) + + if constexpr (sizeof...(Rest) % 2 == 1) { + /* Even number of registers. */ + constexpr auto Pairs = RegisterPairHelper<First, Rest...>::Pairs; + static_assert(Pairs.size() <= 8); + SVC_CODEGEN_FOR_I_FROM_0_TO_64(SVC_CODEGEN_HANDLER) + } else if constexpr (sizeof...(Rest) > 0) { + /* Odd number of registers. */ + constexpr auto Pairs = RegisterPairHelper<Rest...>::Pairs; + static_assert(Pairs.size() <= 8); + SVC_CODEGEN_FOR_I_FROM_0_TO_64(SVC_CODEGEN_HANDLER) + + SaveRegister<First>(); + } else { + /* Only one register. */ + SaveRegister<First>(); + } + + #undef SVC_CODEGEN_HANDLER + } + + template<size_t First, size_t... Rest> + static ALWAYS_INLINE void RestoreRegistersImpl() { + #define SVC_CODEGEN_HANDLER(n) \ + do { if constexpr (n < Pairs.size()) { RestoreRegisterPair<Pairs[n].First, Pairs[n].Second>(); } } while (0) + + if constexpr (sizeof...(Rest) % 2 == 1) { + /* Even number of registers. */ + constexpr auto Pairs = RegisterPairHelper<First, Rest...>::Pairs; + static_assert(Pairs.size() <= 8); + SVC_CODEGEN_FOR_I_FROM_0_TO_64(SVC_CODEGEN_HANDLER) + } else if constexpr (sizeof...(Rest) > 0) { + /* Odd number of registers. */ + RestoreRegister<First>(); + + constexpr auto Pairs = RegisterPairHelper<Rest...>::Pairs; + static_assert(Pairs.size() <= 8); + SVC_CODEGEN_FOR_I_FROM_0_TO_64(SVC_CODEGEN_HANDLER) + } else { + /* Only one register. */ + RestoreRegister<First>(); + } + + #undef SVC_CODEGEN_HANDLER + } + + public: + template<size_t... Registers> + static ALWAYS_INLINE void SaveRegisters() { + if constexpr (sizeof...(Registers) > 0) { + SaveRegistersImpl<Registers...>(); + } + } + + template<size_t... Registers> + static ALWAYS_INLINE void RestoreRegisters() { + if constexpr (sizeof...(Registers) > 0) { + RestoreRegistersImpl<Registers...>(); + } + } + + template<size_t... Registers> + static ALWAYS_INLINE void ClearRegisters() { + static_assert(sizeof...(Registers) <= 8); + (ClearRegister<Registers>(), ...); + } + + template<size_t Size> + static ALWAYS_INLINE void AllocateStackSpace() { + if constexpr (Size > 0) { + __asm__ __volatile__("sub sp, sp, %c[size]" :: [size]"i"(util::AlignUp(Size, 16)) : "memory"); + } + } + + template<size_t Size> + static ALWAYS_INLINE void FreeStackSpace() { + if constexpr (Size > 0) { + __asm__ __volatile__("add sp, sp, %c[size]" :: [size]"i"(util::AlignUp(Size, 16)) : "memory"); + } + } + + template<size_t Dst, size_t Src> + static ALWAYS_INLINE void MoveRegister() { + __asm__ __volatile__("mov x%c[dst], x%c[src]" :: [dst]"i"(Dst), [src]"i"(Src) : "memory"); + } + + template<size_t Reg> + static ALWAYS_INLINE void ConvertToBoolean() { + __asm__ __volatile__("and x%c[reg], x%c[reg], #1" :: [reg]"i"(Reg) : "memory"); + } + + template<size_t Reg, size_t Offset, size_t Size> + static ALWAYS_INLINE void LoadFromStack() { + if constexpr (Size == 4) { + __asm__ __volatile__("ldr w%c[r], [sp, %c[offset]]" :: [r]"i"(Reg), [offset]"i"(Offset) : "memory"); + } else if constexpr (Size == 8) { + __asm__ __volatile__("ldr x%c[r], [sp, %c[offset]]" :: [r]"i"(Reg), [offset]"i"(Offset) : "memory"); + } else { + static_assert(false, "Invalid Size"); + } + } + + template<size_t Reg0, size_t Reg1, size_t Offset, size_t Size> + static ALWAYS_INLINE void LoadPairFromStack() { + if constexpr (Size == 4) { + __asm__ __volatile__("ldp w%c[r0], w%c[r1], [sp, %c[offset]]" :: [r0]"i"(Reg0), [r1]"i"(Reg1), [offset]"i"(Offset) : "memory"); + } else if constexpr (Size == 8) { + __asm__ __volatile__("ldp x%c[r0], x%c[r1], [sp, %c[offset]]" :: [r0]"i"(Reg0), [r1]"i"(Reg1), [offset]"i"(Offset) : "memory"); + } else { + static_assert(false, "Invalid Size"); + } + } + + template<size_t Reg, size_t Offset, size_t Size> + static ALWAYS_INLINE void StoreToStack() { + if constexpr (Size == 4) { + __asm__ __volatile__("str w%c[r], [sp, %c[offset]]" :: [r]"i"(Reg), [offset]"i"(Offset) : "memory"); + } else if constexpr (Size == 8) { + __asm__ __volatile__("str x%c[r], [sp, %c[offset]]" :: [r]"i"(Reg), [offset]"i"(Offset) : "memory"); + } else { + static_assert(false, "Invalid Size"); + } + } + + template<size_t Reg0, size_t Reg1, size_t Offset, size_t Size> + static ALWAYS_INLINE void StorePairToStack() { + if constexpr (Size == 4) { + __asm__ __volatile__("stp w%c[r0], w%c[r1], [sp, %c[offset]]" :: [r0]"i"(Reg0), [r1]"i"(Reg1), [offset]"i"(Offset) : "memory"); + } else if constexpr (Size == 8) { + __asm__ __volatile__("stp x%c[r0], x%c[r1], [sp, %c[offset]]" :: [r0]"i"(Reg0), [r1]"i"(Reg1), [offset]"i"(Offset) : "memory"); + } else { + static_assert(false, "Invalid Size"); + } + } + + template<size_t Dst, size_t Low, size_t High> + static ALWAYS_INLINE void Pack() { + __asm__ __volatile__("orr x%c[dst], x%c[low], x%c[high], lsl #32" :: [dst]"i"(Dst), [low]"i"(Low), [high]"i"(High) : "memory"); + } + + template<size_t Low, size_t High, size_t Src> + static ALWAYS_INLINE void Unpack() { + if constexpr (Src != Low) { + MoveRegister<Src, Low>(); + } + + __asm__ __volatile__("lsr x%c[high], x%c[src], #32" :: [high]"i"(High), [src]"i"(Src) : "memory"); + } + + template<size_t Dst, size_t Offset> + static ALWAYS_INLINE void LoadStackAddress() { + if constexpr (Offset > 0) { + __asm__ __volatile__("add x%c[dst], sp, %c[offset]" :: [dst]"i"(Dst), [offset]"i"(Offset) : "memory"); + } else if constexpr (Offset == 0) { + __asm__ __volatile__("mov x%c[dst], sp" :: [dst]"i"(Dst) : "memory"); + } + } + }; + + class Aarch32CodeGenerator { + /* TODO */ + }; + + template<typename CodeGenerator, auto MetaCode> + static ALWAYS_INLINE void GenerateCodeForMetaCode() { + constexpr size_t NumOperations = MetaCode.GetNumOperations(); + static_assert(NumOperations <= 64); + #define SVC_CODEGEN_HANDLER(n) do { if constexpr (n < NumOperations) { constexpr auto Operation = MetaCode.GetOperation(n); GenerateCodeForOperation<CodeGenerator, Operation>(); } } while (0) + SVC_CODEGEN_FOR_I_FROM_0_TO_64(SVC_CODEGEN_HANDLER) + #undef SVC_CODEGEN_HANDLER + } + + #undef SVC_CODEGEN_FOR_I_FROM_0_TO_64 + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_common.hpp new file mode 100644 index 00000000..a1e60d92 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_common.hpp @@ -0,0 +1,196 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/svc/svc_types.hpp> + +namespace ams::svc::codegen::impl { + + template<typename T> + constexpr inline bool IsIntegral = std::is_integral<T>::value; + + template<> + constexpr inline bool IsIntegral<::ams::svc::Address> = true; + + template<> + constexpr inline bool IsIntegral<::ams::svc::Size> = true; + + template<typename T> + constexpr inline bool IsKUserPointer = std::is_base_of<ams::kern::svc::impl::KUserPointerTag, T>::value; + + template<typename T> + constexpr inline bool IsIntegralOrUserPointer = IsIntegral<T> || IsUserPointer<T> || IsKUserPointer<T>; + + template<size_t... Is1, size_t... Is2> + constexpr std::index_sequence<Is1..., Is2...> IndexSequenceCat(std::index_sequence<Is1...>, std::index_sequence<Is2...>) { + return std::index_sequence<Is1..., Is2...>{}; + } + + template<size_t... Is> + constexpr inline std::array<size_t, sizeof...(Is)> ConvertToArray(std::index_sequence<Is...>) { + return std::array<size_t, sizeof...(Is)>{ Is... }; + } + + template<auto Function> + class FunctionTraits { + private: + template<typename R, typename... A> + static R GetReturnTypeImpl(R(*)(A...)); + + template<typename R, typename... A> + static std::tuple<A...> GetArgsImpl(R(*)(A...)); + public: + using ReturnType = decltype(GetReturnTypeImpl(Function)); + using ArgsType = decltype(GetArgsImpl(Function)); + }; + + enum class CodeGenerationKind { + SvcInvocationToKernelProcedure, + PrepareForKernelProcedureToSvcInvocation, + KernelProcedureToSvcInvocation, + Invalid, + }; + + enum class ArgumentType { + In, + Out, + InUserPointer, + OutUserPointer, + Invalid, + }; + + template<typename T> + constexpr inline ArgumentType GetArgumentType = [] { + static_assert(!std::is_reference<T>::value, "SVC ABI: Reference types not allowed."); + static_assert(sizeof(T) <= sizeof(uint64_t), "SVC ABI: Type too large"); + if constexpr (std::is_pointer<T>::value) { + static_assert(!std::is_const<typename std::remove_pointer<T>::type>::value, "SVC ABI: Output (T*) must not be const"); + return ArgumentType::Out; + } else if constexpr (IsUserPointer<T> || IsKUserPointer<T>) { + if constexpr (T::IsInput) { + return ArgumentType::InUserPointer; + } else { + return ArgumentType::OutUserPointer; + } + } else { + return ArgumentType::In; + } + }(); + + template<size_t RS, size_t RC, size_t ARC, size_t PC> + struct AbiType { + static constexpr size_t RegisterSize = RS; + static constexpr size_t RegisterCount = RC; + static constexpr size_t ArgumentRegisterCount = ARC; + static constexpr size_t PointerSize = PC; + + template<typename T> + static constexpr size_t GetSize() { + if constexpr (std::is_same<T, ::ams::svc::Address>::value || std::is_same<T, ::ams::svc::Size>::value || IsUserPointer<T> || IsKUserPointer<T>) { + return PointerSize; + } else if constexpr(std::is_pointer<T>::value) { + /* Out parameter. */ + return GetSize<typename std::remove_pointer<T>::type>(); + } else if constexpr (std::is_same<T, void>::value) { + return 0; + } else { + return sizeof(T); + } + } + + template<typename T> + static constexpr inline size_t Size = GetSize<T>(); + }; + + using Aarch64Lp64Abi = AbiType<8, 8, 8, 8>; + using Aarch64Ilp32Abi = AbiType<8, 8, 8, 4>; + using Aarch32Ilp32Abi = AbiType<4, 4, 4, 4>; + + using Aarch64SvcInvokeAbi = AbiType<8, 8, 8, 8>; + using Aarch32SvcInvokeAbi = AbiType<4, 8, 4, 4>; + + struct Abi { + size_t register_size; + size_t register_count; + size_t pointer_size; + + template<typename AbiType> + static constexpr Abi Convert() { return { AbiType::RegisterSize, AbiType::RegisterCount, AbiType::PointerSize }; } + }; + + template<typename AbiType, typename ArgType> + constexpr inline bool IsPassedByPointer = [] { + if (GetArgumentType<ArgType> != ArgumentType::In) { + return true; + } + + return (!IsIntegral<ArgType> && AbiType::template Size<ArgType> > AbiType::RegisterSize); + }(); + + template<size_t N> + class RegisterAllocator { + public: + std::array<bool, N> m_map; + public: + constexpr explicit RegisterAllocator() : m_map() { /* ... */ } + + constexpr bool IsAllocated(size_t i) const { return m_map[i]; } + constexpr bool IsFree(size_t i) const { return !this->IsAllocated(i); } + + constexpr void Allocate(size_t i) { + if (this->IsAllocated(i)) { + std::abort(); + } + + m_map[i] = true; + } + + constexpr bool TryAllocate(size_t i) { + if (this->IsAllocated(i)) { + return false; + } + + m_map[i] = true; + return true; + } + + constexpr size_t AllocateFirstFree() { + for (size_t i = 0; i < N; i++) { + if (!this->IsAllocated(i)) { + m_map[i] = true; + return i; + } + } + + std::abort(); + } + + constexpr void Free(size_t i) { + if (!this->IsAllocated(i)) { + std::abort(); + } + + m_map[i] = false; + } + + constexpr size_t GetRegisterCount() const { + return N; + } + }; + + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_kernel_svc_wrapper.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_kernel_svc_wrapper.hpp new file mode 100644 index 00000000..d784abdd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_kernel_svc_wrapper.hpp @@ -0,0 +1,585 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/codegen/impl/svc_codegen_impl_common.hpp> +#include <vapours/svc/codegen/impl/svc_codegen_impl_parameter.hpp> +#include <vapours/svc/codegen/impl/svc_codegen_impl_layout.hpp> +#include <vapours/svc/codegen/impl/svc_codegen_impl_meta_code.hpp> +#include <vapours/svc/codegen/impl/svc_codegen_impl_layout_conversion.hpp> +#include <vapours/svc/codegen/impl/svc_codegen_impl_code_generator.hpp> + +namespace ams::svc::codegen::impl { + + template<typename, typename, typename, typename, typename> + class KernelSvcWrapperHelperImpl; + + template<typename _SvcAbiType, typename _UserAbiType, typename _KernelAbiType, typename ReturnType, typename... ArgumentTypes> + class KernelSvcWrapperHelperImpl<_SvcAbiType, _UserAbiType, _KernelAbiType, ReturnType, std::tuple<ArgumentTypes...>> { + private: + static constexpr bool TryToPerformCoalescingOptimizations = true; + + template<MetaCode::OperationKind PairKind, MetaCode::OperationKind SingleKind, size_t InvalidRegisterId, size_t MaxStackIndex> + static constexpr void CoalesceOperations(MetaCodeGenerator &out_mcg, const std::array<size_t, MaxStackIndex> stack_modified, size_t stack_top) { + enum class State { WaitingForRegister, ParsingRegister, ParsedRegister, EmittingCode }; + State cur_state = State::WaitingForRegister; + size_t num_regs = 0; + size_t registers[2] = { InvalidRegisterId, InvalidRegisterId }; + size_t widths[2] = {}; + size_t index = 0; + size_t store_base = 0; + while (index < stack_top) { + if (cur_state == State::WaitingForRegister) { + while (stack_modified[index] == InvalidRegisterId && index < stack_top) { + index++; + } + cur_state = State::ParsingRegister; + } else if (cur_state == State::ParsingRegister) { + const size_t start_index = index; + if (num_regs == 0) { + store_base = start_index; + } + const size_t reg = stack_modified[index]; + registers[num_regs] = reg; + while (index < stack_top && index < start_index + KernelAbiType::RegisterSize && stack_modified[index] == reg) { + widths[num_regs]++; + index++; + } + num_regs++; + cur_state = State::ParsedRegister; + } else if (cur_state == State::ParsedRegister) { + if (num_regs == 2 || stack_modified[index] == InvalidRegisterId) { + cur_state = State::EmittingCode; + } else { + cur_state = State::ParsingRegister; + } + } else if (cur_state == State::EmittingCode) { + /* Emit an operation! */ + MetaCode::Operation st_op = {}; + + if (num_regs == 2) { + if (registers[0] == registers[1]) { + std::abort(); + } + if (widths[0] == widths[1]) { + st_op.kind = PairKind; + st_op.num_parameters = 4; + st_op.parameters[0] = registers[0]; + st_op.parameters[1] = registers[1]; + st_op.parameters[2] = store_base; + st_op.parameters[3] = widths[0]; + } else { + std::abort(); + } + } else if (num_regs == 1) { + st_op.kind = SingleKind; + st_op.num_parameters = 3; + st_op.parameters[0] = registers[0]; + st_op.parameters[1] = store_base; + st_op.parameters[2] = widths[0]; + } else { + std::abort(); + } + + out_mcg.AddOperationDirectly(st_op); + + /* Go back to beginning of parse. */ + for (size_t i = 0; i < num_regs; i++) { + registers[i] = InvalidRegisterId; + widths[i] = 0; + } + num_regs = 0; + cur_state = State::WaitingForRegister; + } else { + std::abort(); + } + } + + if (cur_state == State::ParsedRegister) { + /* Emit an operation! */ + if (num_regs == 2 && widths[0] == widths[1]) { + MetaCode::Operation st_op = {}; + st_op.kind = PairKind; + st_op.num_parameters = 4; + st_op.parameters[0] = registers[0]; + st_op.parameters[1] = registers[1]; + st_op.parameters[2] = store_base; + st_op.parameters[3] = widths[0]; + out_mcg.AddOperationDirectly(st_op); + } else { + for (size_t i = 0; i < num_regs; i++) { + MetaCode::Operation st_op = {}; + st_op.kind = SingleKind; + st_op.num_parameters = 3; + st_op.parameters[0] = registers[i]; + st_op.parameters[1] = store_base; + st_op.parameters[2] = widths[i]; + + store_base += widths[i]; + out_mcg.AddOperationDirectly(st_op); + } + } + } + } + + /* Basic optimization of store coalescing. */ + template<typename Conversion, typename... OperationTypes, size_t N> + static constexpr bool TryPrepareForKernelProcedureToSvcInvocationCoalescing(std::tuple<OperationTypes...>, MetaCodeGenerator &out_mcg, RegisterAllocator<N> &out_allocator) { + /* For debugging, allow ourselves to disable these optimizations. */ + if constexpr (!TryToPerformCoalescingOptimizations) { + return false; + } + + /* Generate expected code. */ + MetaCodeGenerator mcg; + RegisterAllocator<N> allocator = out_allocator; + (Conversion::template GenerateCode<OperationTypes, CodeGenerationKind::PrepareForKernelProcedureToSvcInvocation>(mcg, allocator), ...); + MetaCode mc = mcg.GetMetaCode(); + + /* This is a naive optimization pass. */ + /* We want to reorder code of the form: */ + /* - Store to Stack sequence 0... */ + /* - Load Stack Address 0 */ + /* - Store to Stack 1... */ + /* - Load Stack Address 1 */ + /* Into the form: */ + /* - Store to stack Sequence 0 + 1... */ + /* - Load Stack Address 0 + 1... */ + /* But only if they are semantically equivalent. */ + + /* We'll do a simple, naive pass to check if any registers are stored to stack that are modified. */ + /* This shouldn't happen in any cases we care about, so we can probably get away with it. */ + /* TODO: Eventually this should be e.g. operation.ModifiesRegister() / operation.CanReorderBefore() */ + /* However, this will be more work, and if it's not necessary it can be put off until it is. */ + constexpr size_t MaxStackIndex = 0x100; + constexpr size_t InvalidRegisterId = N; + bool register_modified[N] = {}; + std::array<size_t, N> stack_address_loaded = {}; + for (size_t i = 0; i < N; i++) { stack_address_loaded[i] = MaxStackIndex; } + std::array<size_t, MaxStackIndex> stack_modified = {}; + for (size_t i = 0; i < MaxStackIndex; i++) { stack_modified[i] = InvalidRegisterId; } + size_t stack_top = 0; + for (size_t i = 0; i < mc.GetNumOperations(); i++) { + const auto mco = mc.GetOperation(i); + if (mco.kind == MetaCode::OperationKind::StoreToStack) { + if (register_modified[mco.parameters[0]]) { + return false; + } + const size_t offset = mco.parameters[1]; + const size_t width = mco.parameters[2] == 0 ? KernelAbiType::RegisterSize : mco.parameters[2]; + for (size_t j = 0; j < width; j++) { + const size_t index = offset + j; + if (index >= MaxStackIndex) { + std::abort(); + } + if (stack_modified[index] != InvalidRegisterId) { + return false; + } + stack_modified[index] = mco.parameters[0]; + stack_top = std::max(index + 1, stack_top); + } + } else if (mco.kind == MetaCode::OperationKind::LoadStackAddress) { + if (stack_address_loaded[mco.parameters[0]] != MaxStackIndex) { + return false; + } + if (register_modified[mco.parameters[0]]) { + return false; + } + if (mco.parameters[1] >= MaxStackIndex) { + std::abort(); + } + stack_address_loaded[mco.parameters[0]] = mco.parameters[1]; + register_modified[mco.parameters[0]] = true; + } else { + /* TODO: Better operation reasoning process. */ + return false; + } + } + + /* Looks like we can reorder! */ + /* Okay, let's do this the naive way, too. */ + constexpr auto PairKind = MetaCode::OperationKind::StorePairToStack; + constexpr auto SingleKind = MetaCode::OperationKind::StoreToStack; + CoalesceOperations<PairKind, SingleKind, InvalidRegisterId>(out_mcg, stack_modified, stack_top); + for (size_t i = 0; i < N; i++) { + if (stack_address_loaded[i] != MaxStackIndex) { + MetaCode::Operation load_op = {}; + load_op.kind = MetaCode::OperationKind::LoadStackAddress; + load_op.num_parameters = 2; + load_op.parameters[0] = i; + load_op.parameters[1] = stack_address_loaded[i]; + out_mcg.AddOperationDirectly(load_op); + } + } + + /* Ensure the out allocator state is correct. */ + out_allocator = allocator; + + return true; + } + + /* Basic optimization of load coalescing. */ + template<typename Conversion, typename... OperationTypes, size_t N> + static constexpr bool TryKernelProcedureToSvcInvocationCoalescing(std::tuple<OperationTypes...>, MetaCodeGenerator &out_mcg, RegisterAllocator<N> &out_allocator) { + /* For debugging, allow ourselves to disable these optimizations. */ + if constexpr (!TryToPerformCoalescingOptimizations) { + return false; + } + + /* Generate expected code. */ + MetaCodeGenerator mcg; + RegisterAllocator<N> allocator = out_allocator; + (Conversion::template GenerateCode<OperationTypes, CodeGenerationKind::KernelProcedureToSvcInvocation>(mcg, allocator), ...); + MetaCode mc = mcg.GetMetaCode(); + + /* This is a naive optimization pass. */ + /* We want to coalesce all sequential stack loads, if possible. */ + /* But only if they are semantically equivalent. */ + + /* We'll do a simple, naive pass to check if any registers are used after being loaded from stack that. */ + /* This shouldn't happen in any cases we care about, so we can probably get away with it. */ + /* TODO: Eventually this should be e.g. operation.ModifiesRegister() / operation.CanReorderBefore() */ + /* However, this will be more work, and if it's not necessary it can be put off until it is. */ + constexpr size_t MaxStackIndex = 0x100; + constexpr size_t InvalidRegisterId = N; + bool register_modified[N] = {}; + std::array<size_t, N> stack_offset_loaded = {}; + for (size_t i = 0; i < N; i++) { stack_offset_loaded[i] = MaxStackIndex; } + std::array<size_t, MaxStackIndex> stack_modified = {}; + for (size_t i = 0; i < MaxStackIndex; i++) { stack_modified[i] = InvalidRegisterId; } + size_t stack_top = 0; + for (size_t i = 0; i < mc.GetNumOperations(); i++) { + const auto mco = mc.GetOperation(i); + if (mco.kind == MetaCode::OperationKind::Unpack) { + if (register_modified[mco.parameters[0]] || register_modified[mco.parameters[1]] || register_modified[mco.parameters[2]]) { + return false; + } + register_modified[mco.parameters[0]] = true; + register_modified[mco.parameters[1]] = true; + } else if (mco.kind == MetaCode::OperationKind::LoadFromStack) { + if (stack_offset_loaded[mco.parameters[0]] != MaxStackIndex) { + return false; + } + if (register_modified[mco.parameters[0]] != false) { + return false; + } + if (mco.parameters[1] >= MaxStackIndex) { + std::abort(); + } + stack_offset_loaded[mco.parameters[0]] = mco.parameters[1]; + register_modified[mco.parameters[0]] = true; + + const size_t offset = mco.parameters[1]; + const size_t width = mco.parameters[2] == 0 ? KernelAbiType::RegisterSize : mco.parameters[2]; + for (size_t j = 0; j < width; j++) { + const size_t index = offset + j; + if (index >= MaxStackIndex) { + std::abort(); + } + if (stack_modified[index] != InvalidRegisterId) { + return false; + } + stack_modified[index] = mco.parameters[0]; + stack_top = std::max(index + 1, stack_top); + } + } else { + /* TODO: Better operation reasoning process. */ + return false; + } + } + + /* Any operations that don't load from stack, we can just re-add. */ + for (size_t i = 0; i < mc.GetNumOperations(); i++) { + const auto mco = mc.GetOperation(i); + if (mco.kind != MetaCode::OperationKind::LoadFromStack) { + out_mcg.AddOperationDirectly(mco); + } + } + constexpr auto PairKind = MetaCode::OperationKind::LoadPairFromStack; + constexpr auto SingleKind = MetaCode::OperationKind::LoadFromStack; + CoalesceOperations<PairKind, SingleKind, InvalidRegisterId>(out_mcg, stack_modified, stack_top); + + /* Ensure the out allocator state is correct. */ + out_allocator = allocator; + + return true; + } + + template<typename Conversion, size_t ParameterIndex = 0> + static constexpr void SanitizeInputBooleans(MetaCodeGenerator &mcg) { + /* Get the input layout. */ + constexpr auto InputLayout = Conversion::LayoutForSvc.GetInputLayout(); + + /* Check if we're done. */ + if constexpr (ParameterIndex < InputLayout.GetNumParameters()) { + /* Get the relevant parameter. */ + constexpr auto Param = InputLayout.GetParameter(ParameterIndex); + + /* Handle the case where the parameter is a boolean. */ + if constexpr (Param.IsBoolean()) { + /* Boolean parameters should have one location. */ + static_assert(Param.GetNumLocations() == 1); + + /* Get the location. */ + constexpr auto Loc = Param.GetLocation(0); + + /* TODO: Support boolean parameters passed-by-stack. */ + static_assert(Loc.GetStorage() == Storage::Register); + + /* Convert the input to boolean. */ + mcg.template ConvertToBoolean<Loc.GetIndex()>(); + } + + /* Handle the next parameter. */ + if constexpr (ParameterIndex + 1 < InputLayout.GetNumParameters()) { + SanitizeInputBooleans<Conversion, ParameterIndex + 1>(mcg); + } + } + } + + template<typename... T> + struct TypeIndexFilter { + + template<auto UseArray, typename X, typename Y> + struct Helper; + + template<auto UseArray, size_t...Index> + struct Helper<UseArray, std::tuple<>, std::index_sequence<Index...>> { + using Type = std::tuple<>; + }; + + template<auto UseArray, typename HeadType, typename... TailType, size_t HeadIndex, size_t... TailIndex> + struct Helper<UseArray, std::tuple<HeadType, TailType...>, std::index_sequence<HeadIndex, TailIndex...>> { + + using LastHeadType = std::tuple<HeadType>; + using LastNullType = std::tuple<>; + + using LastType = typename std::conditional<!UseArray[HeadIndex], LastHeadType, LastNullType>::type; + + using NextTailType = std::tuple<TailType...>; + using NextTailSequence = std::index_sequence<TailIndex...>; + + using NextType = typename std::conditional<!UseArray[HeadIndex], + decltype(std::tuple_cat(std::declval<LastHeadType>(), std::declval<typename Helper<UseArray, NextTailType, NextTailSequence>::Type>())), + decltype(std::tuple_cat(std::declval<LastNullType>(), std::declval<typename Helper<UseArray, NextTailType, NextTailSequence>::Type>())) + >::type; + + using Type = typename std::conditional<sizeof...(TailType) == 0, LastType, NextType>::type; + + }; + + template<auto UseArray> + using FilteredTupleType = typename Helper<UseArray, std::tuple<T...>, decltype(std::make_index_sequence<sizeof...(T)>())>::Type; + }; + + template<auto Allocator, typename FirstOperation, typename...OtherOperations> + static constexpr auto GetModifiedOperations(std::tuple<FirstOperation, OtherOperations...>) { + constexpr size_t ModifyRegister = [] { + auto allocator = Allocator; + return allocator.AllocateFirstFree(); + }(); + + using ModifiedFirstOperation = typename FirstOperation::template ModifiedType<ModifyRegister>; + using NewMoveOperation = typename LayoutConversionBase::template OperationMove<FirstOperation::RegisterSize, FirstOperation::PassedSize, FirstOperation::ProcedureIndex, ModifyRegister>; + return std::tuple<ModifiedFirstOperation, OtherOperations..., NewMoveOperation>{}; + } + + template<typename Conversion, auto Allocator, typename FirstOperation, typename... OtherOperations> + static constexpr auto GenerateBeforeOperations(MetaCodeGenerator &mcg, std::tuple<FirstOperation, OtherOperations...> ops) -> RegisterAllocator<Allocator.GetRegisterCount()> { + constexpr size_t NumOperations = 1 + sizeof...(OtherOperations); + using OperationsTuple = decltype(ops); + using FilterHelper = TypeIndexFilter<FirstOperation, OtherOperations...>; + + constexpr auto ProcessOperation = []<typename Operation>(MetaCodeGenerator &pr_mcg, auto &allocator) { + if (Conversion::template CanGenerateCode<Operation, CodeGenerationKind::SvcInvocationToKernelProcedure>(allocator)) { + Conversion::template GenerateCode<Operation, CodeGenerationKind::SvcInvocationToKernelProcedure>(pr_mcg, allocator); + return true; + } + return false; + }; + + constexpr auto ProcessResults = []<auto AllocatorVal, auto ProcessOp, typename... Operations>(std::tuple<Operations...>) { + auto allocator = AllocatorVal; + MetaCodeGenerator pr_mcg; + auto use_array = std::array<bool, NumOperations>{ ProcessOp.template operator()<Operations>(pr_mcg, allocator)... }; + return std::make_tuple(use_array, allocator, pr_mcg); + }.template operator()<Allocator, ProcessOperation>(OperationsTuple{}); + + constexpr auto CanGenerate = std::get<0>(ProcessResults); + constexpr auto AfterAllocator = std::get<1>(ProcessResults); + constexpr auto GeneratedCode = std::get<2>(ProcessResults).GetMetaCode(); + + for (size_t i = 0; i < GeneratedCode.GetNumOperations(); i++) { + mcg.AddOperationDirectly(GeneratedCode.GetOperation(i)); + } + + using FilteredOperations = typename FilterHelper::FilteredTupleType<CanGenerate>; + static_assert(std::tuple_size<FilteredOperations>::value <= NumOperations); + if constexpr (std::tuple_size<FilteredOperations>::value > 0) { + if constexpr (std::tuple_size<FilteredOperations>::value != NumOperations) { + return GenerateBeforeOperations<Conversion, AfterAllocator>(mcg, FilteredOperations{}); + } else { + /* No progress was made, so we need to make a change. */ + constexpr auto ModifiedOperations = GetModifiedOperations<AfterAllocator>(FilteredOperations{}); + return GenerateBeforeOperations<Conversion, AfterAllocator>(mcg, ModifiedOperations); + } + } else { + return AfterAllocator; + } + } + + static constexpr MetaCode GenerateOriginalBeforeMetaCode() { + MetaCodeGenerator mcg; + RegisterAllocator<KernelAbiType::RegisterCount> allocator; + static_assert(SvcAbiType::RegisterCount == KernelAbiType::RegisterCount); + + /* Reserve registers used by the input layout. */ + constexpr auto InitialAllocator = [] { + RegisterAllocator<KernelAbiType::RegisterCount> initial_allocator; + for (size_t i = 0; i < SvcAbiType::RegisterCount; i++) { + if (Conversion::LayoutForSvc.GetInputLayout().UsesRegister(i)) { + initial_allocator.Allocate(i); + } + } + return initial_allocator; + }(); + + /* Save every register that needs to be preserved to the stack. */ + if constexpr (Conversion::NumPreserveRegisters > 0) { + [&mcg]<size_t... Is>(std::index_sequence<Is...>) { + mcg.template SaveRegisters<Is...>(); + }(typename Conversion::PreserveRegisters{}); + } + + /* Allocate space on the stack for parameters that need it. */ + if constexpr (UsedStackSpace > 0) { + mcg.template AllocateStackSpace<UsedStackSpace>(); + } + + /* Sanitize all input booleans. */ + SanitizeInputBooleans<Conversion>(mcg); + + /* Generate code for before operations. */ + if constexpr (Conversion::NumBeforeOperations > 0) { + allocator = GenerateBeforeOperations<Conversion, InitialAllocator>(mcg, typename Conversion::BeforeOperations{}); + } else { + allocator = InitialAllocator; + } + + /* Generate code for after operations. */ + if constexpr (Conversion::NumAfterOperations > 0) { + if (!TryPrepareForKernelProcedureToSvcInvocationCoalescing<Conversion>(typename Conversion::AfterOperations{}, mcg, allocator)) { + /* We're not eligible for the straightforward optimization. */ + [&mcg, &allocator]<size_t... Is>(std::index_sequence<Is...>) { + (Conversion::template GenerateCode<typename std::tuple_element<Is, typename Conversion::AfterOperations>::type, CodeGenerationKind::PrepareForKernelProcedureToSvcInvocation>(mcg, allocator), ...); + }(std::make_index_sequence<Conversion::NumAfterOperations>()); + } + } + + return mcg.GetMetaCode(); + } + public: + using SvcAbiType = _SvcAbiType; + using UserAbiType = _UserAbiType; + using KernelAbiType = _KernelAbiType; + + using Conversion = LayoutConversion<SvcAbiType, UserAbiType, KernelAbiType, ReturnType, ArgumentTypes...>; + + static constexpr size_t UsedStackSpace = Conversion::NonAbiUsedStackIndices * KernelAbiType::RegisterSize; + + static constexpr MetaCode OriginalBeforeMetaCode = [] { + return GenerateOriginalBeforeMetaCode(); + }(); + + static constexpr MetaCode OriginalAfterMetaCode = [] { + MetaCodeGenerator mcg; + RegisterAllocator<KernelAbiType::RegisterCount> allocator; + static_assert(SvcAbiType::RegisterCount == KernelAbiType::RegisterCount); + + /* Generate code for after operations. */ + if constexpr (Conversion::NumAfterOperations > 0) { + if (!TryKernelProcedureToSvcInvocationCoalescing<Conversion>(typename Conversion::AfterOperations{}, mcg, allocator)) { + [&mcg, &allocator]<size_t... Is>(std::index_sequence<Is...>) { + (Conversion::template GenerateCode<typename std::tuple_element<Is, typename Conversion::AfterOperations>::type, CodeGenerationKind::KernelProcedureToSvcInvocation>(mcg, allocator), ...); + }(std::make_index_sequence<Conversion::NumAfterOperations>()); + } + } + + /* Allocate space on the stack for parameters that need it. */ + if constexpr (UsedStackSpace > 0) { + mcg.template FreeStackSpace<UsedStackSpace>(); + } + + if constexpr (Conversion::NumClearRegisters > 0) { + [&mcg]<size_t... Is>(std::index_sequence<Is...>) { + mcg.template ClearRegisters<Is...>(); + }(typename Conversion::ClearRegisters{}); + } + + /* Restore registers we previously saved to the stack. */ + if constexpr (Conversion::NumPreserveRegisters > 0) { + [&mcg]<size_t... Is>(std::index_sequence<Is...>) { + mcg.template RestoreRegisters<Is...>(); + }(typename Conversion::PreserveRegisters{}); + } + + return mcg.GetMetaCode(); + }(); + + /* TODO: Implement meta code optimization via separate layer. */ + /* Right now some basic optimizations are just implemented by the above generators. */ + static constexpr MetaCode OptimizedBeforeMetaCode = OriginalBeforeMetaCode; + static constexpr MetaCode OptimizedAfterMetaCode = OriginalAfterMetaCode; + }; + + template<typename _SvcAbiType, typename _UserAbiType, typename _KernelAbiType, auto Function> + class KernelSvcWrapperHelper { + private: + using Traits = FunctionTraits<Function>; + public: + using Impl = KernelSvcWrapperHelperImpl<_SvcAbiType, _UserAbiType, _KernelAbiType, typename Traits::ReturnType, typename Traits::ArgsType>; + using ReturnType = typename Traits::ReturnType; + + static constexpr bool IsAarch64Kernel = std::is_same<_KernelAbiType, Aarch64Lp64Abi>::value; + static constexpr bool IsAarch32Kernel = std::is_same<_KernelAbiType, Aarch32Ilp32Abi>::value; + static_assert(IsAarch64Kernel || IsAarch32Kernel); + + using CodeGenerator = typename std::conditional<IsAarch64Kernel, Aarch64CodeGenerator, Aarch32CodeGenerator>::type; + + static constexpr auto BeforeMetaCode = Impl::OptimizedBeforeMetaCode; + static constexpr auto AfterMetaCode = Impl::OptimizedAfterMetaCode; + +/* Set omit-frame-pointer to prevent GCC from emitting MOV X29, SP instructions. */ +#pragma GCC push_options +#pragma GCC optimize ("-O2") +#pragma GCC optimize ("omit-frame-pointer") + + static ALWAYS_INLINE ReturnType WrapSvcFunction() { + /* Generate appropriate assembly. */ + GenerateCodeForMetaCode<CodeGenerator, BeforeMetaCode>(); + ON_SCOPE_EXIT { GenerateCodeForMetaCode<CodeGenerator, AfterMetaCode>(); }; + + /* Cast the generated function to the generic funciton pointer type. */ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wcast-function-type" + return reinterpret_cast<ReturnType (*)()>(Function)(); + #pragma GCC diagnostic pop + } + +#pragma GCC pop_options + }; + + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_layout.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_layout.hpp new file mode 100644 index 00000000..e22a3a3c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_layout.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/codegen/impl/svc_codegen_impl_common.hpp> +#include <vapours/svc/codegen/impl/svc_codegen_impl_parameter.hpp> + +namespace ams::svc::codegen::impl { + + class ParameterLayout { + public: + static constexpr size_t MaxParameters = 8; + private: + static constexpr size_t InvalidIndex = std::numeric_limits<size_t>::max(); + public: + /* ABI parameters. */ + Abi m_abi; + + /* Parameter storage. */ + size_t m_num_parameters; + Parameter m_parameters[MaxParameters]; + public: + constexpr explicit ParameterLayout(Abi a) + : m_abi(a), m_num_parameters(0), m_parameters() + { /* ... */ } + + constexpr void AddSingle(Parameter::Identifier id, ArgumentType type, size_t ts, size_t ps, bool p, bool b, Storage s, size_t idx) { + for (size_t i = 0; i < m_num_parameters; i++) { + if (m_parameters[i].Is(id)) { + m_parameters[i].AddLocation(Location(s, idx)); + return; + } + } + m_parameters[m_num_parameters++] = Parameter(id, type, ts, ps, p, b, Location(s, idx)); + } + + constexpr size_t Add(Parameter::Identifier id, ArgumentType type, size_t ts, size_t ps, bool p, bool b, Storage s, size_t i) { + size_t required_registers = 0; + + while (required_registers * m_abi.register_size < ps) { + this->AddSingle(id, type, ts, ps, p, b, s, i++); + required_registers++; + } + + return required_registers; + } + + constexpr bool UsesLocation(Location l) const { + for (size_t i = 0; i < m_num_parameters; i++) { + if (m_parameters[i].UsesLocation(l)) { + return true; + } + } + return false; + } + + constexpr bool UsesRegister(size_t i) const { + return this->UsesLocation(Location(Storage::Register, i)); + } + + constexpr bool IsRegisterFree(size_t i) const { + return !(this->UsesRegister(i)); + } + + constexpr size_t GetNumParameters() const { + return m_num_parameters; + } + + constexpr Parameter GetParameter(size_t i) const { + return m_parameters[i]; + } + + constexpr bool HasParameter(Parameter::Identifier id) const { + for (size_t i = 0; i < m_num_parameters; i++) { + if (m_parameters[i].Is(id)) { + return true; + } + } + return false; + } + + constexpr Parameter GetParameter(Parameter::Identifier id) const { + for (size_t i = 0; i < m_num_parameters; i++) { + if (m_parameters[i].Is(id)) { + return m_parameters[i]; + } + } + + AMS_ASSUME(false); + } + }; + + class ProcedureLayout { + public: + Abi m_abi; + ParameterLayout m_input; + ParameterLayout m_output; + private: + template<typename AbiType, typename ArgType> + constexpr void ProcessArgument(size_t i, size_t &NGRN, size_t &NSAA) { + /* We currently don't implement support for floating point types. */ + static_assert(!std::is_floating_point<ArgType>::value); + static_assert(!std::is_same<ArgType, void>::value); + + constexpr size_t ArgumentTypeSize = AbiType::template Size<ArgType>; + constexpr bool PassedByPointer = IsPassedByPointer<AbiType, ArgType>; + constexpr bool IsBoolean = std::same_as<ArgType, bool>; + constexpr size_t ArgumentPassSize = PassedByPointer ? AbiType::PointerSize : ArgumentTypeSize; + + /* TODO: Is there ever a case where this is not the correct alignment? */ + constexpr size_t ArgumentAlignment = ArgumentPassSize; + + /* Ensure NGRN is aligned. */ + if constexpr (ArgumentAlignment > AbiType::RegisterSize) { + NGRN += (NGRN & 1); + } + + /* TODO: We don't support splitting arguments between registers and stack, but AAPCS32 does. */ + /* Is this a problem? Nintendo seems to not ever do this. */ + + auto id = Parameter::Identifier("FunctionParameter", i); + + /* Allocate integral types specially per aapcs. */ + constexpr ArgumentType Type = GetArgumentType<ArgType>; + const size_t registers_available = AbiType::RegisterCount - NGRN; + if constexpr (!PassedByPointer && IsIntegralOrUserPointer<ArgType> && ArgumentTypeSize > AbiType::RegisterSize) { + if (registers_available >= 2) { + m_input.Add(id, Type, ArgumentTypeSize, ArgumentPassSize, PassedByPointer, IsBoolean, Storage::Register, NGRN); + NGRN += 2; + } else { + /* Argument went on stack, so stop allocating arguments in registers. */ + NGRN = AbiType::RegisterCount; + + NSAA += (NSAA & 1); + m_input.Add(id, Type, ArgumentTypeSize, ArgumentPassSize, PassedByPointer, IsBoolean, Storage::Stack, NSAA); + NSAA += 2; + } + } else { + if (ArgumentPassSize <= AbiType::RegisterSize * registers_available) { + NGRN += m_input.Add(id, Type, ArgumentTypeSize, ArgumentPassSize, PassedByPointer, IsBoolean, Storage::Register, NGRN); + } else { + /* Argument went on stack, so stop allocating arguments in registers. */ + NGRN = AbiType::RegisterCount; + + /* TODO: Stack pointer alignment is only ensured for aapcs64. */ + /* What should we do here? */ + + NSAA += m_input.Add(id, Type, ArgumentTypeSize, ArgumentPassSize, PassedByPointer, IsBoolean, Storage::Stack, NSAA); + } + } + } + public: + constexpr explicit ProcedureLayout(Abi a) : m_abi(a), m_input(a), m_output(a) { /* ... */ } + + template<typename AbiType, typename ReturnType, typename... ArgumentTypes> + static constexpr ProcedureLayout Create() { + ProcedureLayout layout(Abi::Convert<AbiType>()); + + /* 1. The Next General-purpose Register Number (NGRN) is set to zero. */ + [[maybe_unused]] size_t NGRN = 0; + + /* 2. The next stacked argument address (NSAA) is set to the current stack-pointer value (SP). */ + [[maybe_unused]] size_t NSAA = 0; /* Should be considered an offset from stack pointer. */ + + /* 3. Handle the return type. */ + /* TODO: It's unclear how to handle the non-integral and too-large case. */ + if constexpr (!std::is_same<ReturnType, void>::value) { + constexpr size_t ReturnTypeSize = AbiType::template Size<ReturnType>; + layout.m_output.Add(Parameter::Identifier("ReturnType"), ArgumentType::Invalid, ReturnTypeSize, ReturnTypeSize, false, false /* TODO */, Storage::Register, 0); + static_assert(IsIntegral<ReturnType> || ReturnTypeSize <= AbiType::RegisterSize); + } + + /* Process all arguments, in order. */ + size_t i = 0; + (layout.ProcessArgument<AbiType, ArgumentTypes>(i++, NGRN, NSAA), ...); + + return layout; + } + + constexpr ParameterLayout GetInputLayout() const { + return m_input; + } + + constexpr ParameterLayout GetOutputLayout() const { + return m_output; + } + + constexpr Parameter GetParameter(Parameter::Identifier id) const { + if (m_input.HasParameter(id)) { + return m_input.GetParameter(id); + } else { + return m_output.GetParameter(id); + } + } + }; + + class SvcInvocationLayout { + public: + Abi m_abi; + ParameterLayout m_input; + ParameterLayout m_output; + private: + template<typename F> + constexpr void ForEachInputArgument(ParameterLayout param_layout, F f) { + /* We want to iterate over the parameters in sorted order. */ + std::array<size_t, ParameterLayout::MaxParameters> map = {}; + const size_t num_parameters = param_layout.GetNumParameters(); + for (size_t i = 0; i < num_parameters; i++) { + map[i] = i; + } + for (size_t i = 1; i < num_parameters; i++) { + for (size_t j = i; j > 0 && param_layout.GetParameter(map[j-1]).GetLocation(0) > param_layout.GetParameter(map[j]).GetLocation(0); j--) { + std::swap(map[j], map[j-1]); + } + } + + for (size_t i = 0; i < param_layout.GetNumParameters(); i++) { + const auto Parameter = param_layout.GetParameter(map[i]); + if (Parameter.GetArgumentType() == ArgumentType::In && !Parameter.IsPassedByPointer()) { + f(Parameter); + } + } + for (size_t i = 0; i < param_layout.GetNumParameters(); i++) { + const auto Parameter = param_layout.GetParameter(map[i]); + if (Parameter.GetArgumentType() == ArgumentType::InUserPointer) { + f(Parameter); + } + } + for (size_t i = 0; i < param_layout.GetNumParameters(); i++) { + const auto Parameter = param_layout.GetParameter(map[i]); + if (Parameter.GetArgumentType() == ArgumentType::OutUserPointer) { + f(Parameter); + } + } + } + + template<typename F> + constexpr void ForEachInputPointerArgument(ParameterLayout param_layout, F f) { + for (size_t i = 0; i < param_layout.GetNumParameters(); i++) { + const auto Parameter = param_layout.GetParameter(i); + if (Parameter.GetArgumentType() == ArgumentType::In && Parameter.IsPassedByPointer()) { + f(Parameter); + } + } + } + + template<typename F> + constexpr void ForEachOutputArgument(ParameterLayout param_layout, F f) { + for (size_t i = 0; i < param_layout.GetNumParameters(); i++) { + const auto Parameter = param_layout.GetParameter(i); + if (Parameter.GetArgumentType() == ArgumentType::Out) { + f(Parameter); + } + } + } + + template<size_t N> + static constexpr void AddRegisterParameter(ParameterLayout &dst_layout, RegisterAllocator<N> ®_allocator, Parameter param) { + for (size_t i = 0; i < param.GetNumLocations(); i++) { + const auto location = param.GetLocation(i); + if (location.GetStorage() == Storage::Register) { + reg_allocator.Allocate(location.GetIndex()); + dst_layout.AddSingle(param.GetIdentifier(), param.GetArgumentType(), param.GetTypeSize(), param.GetPassedSize(), param.IsPassedByPointer(), param.IsBoolean(), Storage::Register, location.GetIndex()); + } + } + } + + template<size_t N> + static constexpr void AddStackParameter(ParameterLayout &dst_layout, RegisterAllocator<N> ®_allocator, Parameter param) { + for (size_t i = 0; i < param.GetNumLocations(); i++) { + const auto location = param.GetLocation(i); + if (location.GetStorage() == Storage::Stack) { + const size_t free_reg = reg_allocator.AllocateFirstFree(); + dst_layout.AddSingle(param.GetIdentifier(), param.GetArgumentType(), param.GetTypeSize(), param.GetPassedSize(), param.IsPassedByPointer(), param.IsBoolean(), Storage::Register, free_reg); + } + } + } + + template<typename AbiType, size_t N> + static constexpr void AddIndirectParameter(ParameterLayout &dst_layout, RegisterAllocator<N> ®_allocator, Parameter param) { + const size_t type_size = param.GetTypeSize(); + for (size_t sz = 0; sz < type_size; sz += AbiType::RegisterSize) { + const size_t free_reg = reg_allocator.AllocateFirstFree(); + dst_layout.AddSingle(param.GetIdentifier(), param.GetArgumentType(), type_size, type_size, false, param.IsBoolean(), Storage::Register, free_reg); + } + } + public: + constexpr explicit SvcInvocationLayout(Abi a) : m_abi(a), m_input(a), m_output(a) { /* ... */ } + + template<typename AbiType> + static constexpr SvcInvocationLayout Create(ProcedureLayout procedure_layout) { + SvcInvocationLayout layout(Abi::Convert<AbiType>()); + RegisterAllocator<AbiType::RegisterCount> input_register_allocator, output_register_allocator; + + /* Input first wants to map in register -> register */ + layout.ForEachInputArgument(procedure_layout.GetInputLayout(), [&](Parameter parameter) { + AddRegisterParameter(layout.m_input, input_register_allocator, parameter); + }); + + /* And then input wants to map in stack -> stack */ + layout.ForEachInputArgument(procedure_layout.GetInputLayout(), [&](Parameter parameter) { + AddStackParameter(layout.m_input, input_register_allocator, parameter); + }); + + /* And then input wants to map in indirects -> register */ + layout.ForEachInputPointerArgument(procedure_layout.GetInputLayout(), [&](Parameter parameter) { + AddIndirectParameter<AbiType>(layout.m_input, input_register_allocator, parameter); + }); + + /* Handle the return type. */ + if (procedure_layout.GetOutputLayout().GetNumParameters() > 0) { + if (procedure_layout.GetOutputLayout().GetNumParameters() != 1) { + std::abort(); + } + const auto return_param = procedure_layout.GetOutputLayout().GetParameter(0); + if (return_param.GetIdentifier() != Parameter::Identifier("ReturnType")) { + std::abort(); + } + AddRegisterParameter(layout.m_output, output_register_allocator, return_param); + } + + /* Handle other outputs. */ + layout.ForEachOutputArgument(procedure_layout.GetInputLayout(), [&](Parameter parameter) { + AddIndirectParameter<AbiType>(layout.m_output, output_register_allocator, parameter); + }); + + return layout; + } + + constexpr ParameterLayout GetInputLayout() const { + return m_input; + } + + constexpr ParameterLayout GetOutputLayout() const { + return m_output; + } + }; + + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_layout_conversion.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_layout_conversion.hpp new file mode 100644 index 00000000..ab1ee69f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_layout_conversion.hpp @@ -0,0 +1,483 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/codegen/impl/svc_codegen_impl_common.hpp> +#include <vapours/svc/codegen/impl/svc_codegen_impl_parameter.hpp> +#include <vapours/svc/codegen/impl/svc_codegen_impl_layout.hpp> +#include <vapours/svc/codegen/impl/svc_codegen_impl_meta_code.hpp> + +namespace ams::svc::codegen::impl { + + class LayoutConversionBase { + public: + enum class OperationKind { + Move, + LoadAndStore, + PackAndUnpack, + Scatter, + Invalid, + }; + + class OperationMoveImpl; + class OperationLoadAndStoreImpl; + class OperationPackAndUnpackImpl; + class OperationScatterImpl; + + class OperationBase{}; + + template<OperationKind _Kind, size_t RS, size_t PS, size_t SO, size_t PIdx, size_t... SIdx> + class Operation : public OperationBase { + public: + static constexpr OperationKind Kind = _Kind; + static constexpr size_t RegisterSize = RS; + static constexpr size_t PassedSize = PS; + static constexpr size_t StackOffset = SO; + static constexpr size_t ProcedureIndex = PIdx; + + static constexpr size_t NumSvcIndices = sizeof...(SIdx); + static constexpr std::array<size_t, sizeof...(SIdx)> SvcIndices = { SIdx... }; + static constexpr std::index_sequence<SIdx...> SvcIndexSequence = {}; + + template<size_t I> + static constexpr size_t SvcIndex = SvcIndices[I]; + + template<typename F> + static void ForEachSvcIndex(F f) { + (f(SIdx), ...); + } + + using ImplType = typename std::conditional<Kind == OperationKind::Move, OperationMoveImpl, + typename std::conditional<Kind == OperationKind::LoadAndStore, OperationLoadAndStoreImpl, + typename std::conditional<Kind == OperationKind::PackAndUnpack, OperationPackAndUnpackImpl, + typename std::conditional<Kind == OperationKind::Scatter, OperationScatterImpl, + void>::type>::type>::type>::type; + + template<size_t NPIdx> + using ModifiedType = Operation<Kind, RS, PS, SO, NPIdx, SIdx...>; + }; + + template<size_t RS, size_t PS, size_t PIdx, size_t SIdx> + using OperationMove = Operation<OperationKind::Move, RS, PS, 0, PIdx, SIdx>; + + template<size_t RS, size_t PS, size_t PIdx, size_t SIdx> + using OperationLoadAndStore = Operation<OperationKind::LoadAndStore, RS, PS, 0, PIdx, SIdx>; + + template<size_t RS, size_t PS, size_t PIdx, size_t SIdx0, size_t SIdx1> + using OperationPackAndUnpack = Operation<OperationKind::PackAndUnpack, RS, PS, 0, PIdx, SIdx0, SIdx1>; + + template<size_t RS, size_t PS, size_t SO, size_t PIdx, size_t... SIdx> + using OperationScatter = Operation<OperationKind::Scatter, RS, PS, SO, PIdx, SIdx...>; + + class OperationMoveImpl { + public: + template<typename Operation, size_t N> + static constexpr bool CanGenerateCodeForSvcInvocationToKernelProcedure(RegisterAllocator<N> allocator) { + static_assert(Operation::Kind == OperationKind::Move); + allocator.Free(Operation::template SvcIndex<0>); + return allocator.TryAllocate(Operation::ProcedureIndex); + } + + template<typename Operation, size_t N> + static constexpr void GenerateCodeForSvcInvocationToKernelProcedure(MetaCodeGenerator &mcg, RegisterAllocator<N> &allocator) { + static_assert(Operation::Kind == OperationKind::Move); + allocator.Free(Operation::template SvcIndex<0>); + allocator.Allocate(Operation::ProcedureIndex); + mcg.template MoveRegister<Operation::ProcedureIndex, Operation::template SvcIndex<0>>(); + } + }; + + class OperationLoadAndStoreImpl { + public: + template<typename Operation, size_t N> + static constexpr bool CanGenerateCodeForSvcInvocationToKernelProcedure(RegisterAllocator<N> allocator) { + static_assert(Operation::Kind == OperationKind::LoadAndStore); + allocator.Free(Operation::template SvcIndex<0>); + return true; + } + + template<typename Operation, size_t N> + static constexpr void GenerateCodeForSvcInvocationToKernelProcedure(MetaCodeGenerator &mcg, RegisterAllocator<N> &allocator) { + static_assert(Operation::Kind == OperationKind::LoadAndStore); + allocator.Free(Operation::template SvcIndex<0>); + constexpr size_t StackOffset = Operation::ProcedureIndex * Operation::RegisterSize; + mcg.template StoreToStack<Operation::template SvcIndex<0>, StackOffset>(); + } + }; + + class OperationPackAndUnpackImpl { + public: + template<typename Operation, size_t N> + static constexpr bool CanGenerateCodeForSvcInvocationToKernelProcedure(RegisterAllocator<N> allocator) { + static_assert(Operation::Kind == OperationKind::PackAndUnpack); + allocator.Free(Operation::template SvcIndex<0>); + allocator.Free(Operation::template SvcIndex<1>); + return allocator.TryAllocate(Operation::ProcedureIndex); + } + + template<typename Operation, size_t N> + static constexpr void GenerateCodeForSvcInvocationToKernelProcedure(MetaCodeGenerator &mcg, RegisterAllocator<N> &allocator) { + static_assert(Operation::Kind == OperationKind::PackAndUnpack); + allocator.Free(Operation::template SvcIndex<0>); + allocator.Free(Operation::template SvcIndex<1>); + allocator.Allocate(Operation::ProcedureIndex); + mcg.template Pack<Operation::ProcedureIndex, Operation::template SvcIndex<0>, Operation::template SvcIndex<1>>(); + } + + template<typename Operation> + static constexpr void GenerateCodeForPrepareForKernelProcedureToSvcInvocation(MetaCodeGenerator &mcg) { + static_assert(Operation::Kind == OperationKind::PackAndUnpack); + AMS_UNUSED(mcg); + } + + template<typename Operation> + static constexpr void GenerateCodeForKernelProcedureToSvcInvocation(MetaCodeGenerator &mcg) { + static_assert(Operation::Kind == OperationKind::PackAndUnpack); + mcg.template Unpack<Operation::template SvcIndex<0>, Operation::template SvcIndex<1>, Operation::ProcedureIndex>(); + } + }; + + class OperationScatterImpl { + public: + template<typename Operation, size_t N> + static constexpr bool CanGenerateCodeForSvcInvocationToKernelProcedure(RegisterAllocator<N> allocator) { + static_assert(Operation::Kind == OperationKind::Scatter); + [&allocator]<size_t... SvcIndex>(std::index_sequence<SvcIndex...>) { + (allocator.Free(SvcIndex), ...); + }(Operation::SvcIndexSequence); + return allocator.TryAllocate(Operation::ProcedureIndex); + } + + template<typename Operation, size_t N> + static constexpr void GenerateCodeForSvcInvocationToKernelProcedure(MetaCodeGenerator &mcg, RegisterAllocator<N> &allocator) { + static_assert(Operation::Kind == OperationKind::Scatter); + [&allocator]<size_t... SvcIndex>(std::index_sequence<SvcIndex...>) { + (allocator.Free(SvcIndex), ...); + }(Operation::SvcIndexSequence); + allocator.Allocate(Operation::ProcedureIndex); + + [&mcg]<size_t... Is>(std::index_sequence<Is...>) { + (mcg.template StoreToStack<Operation::template SvcIndex<Is>, Operation::StackOffset + Operation::RegisterSize * (Is), Operation::RegisterSize>(), ...); + }(std::make_index_sequence<Operation::NumSvcIndices>()); + + mcg.template LoadStackAddress<Operation::ProcedureIndex, Operation::StackOffset>(); + } + + template<typename Operation> + static constexpr void GenerateCodeForPrepareForKernelProcedureToSvcInvocation(MetaCodeGenerator &mcg) { + static_assert(Operation::Kind == OperationKind::Scatter); + + [&mcg]<size_t... Is>(std::index_sequence<Is...>) { + (mcg.template StoreToStack<Operation::template SvcIndex<Is>, Operation::StackOffset + Operation::RegisterSize * (Is), Operation::RegisterSize>(), ...); + }(std::make_index_sequence<Operation::NumSvcIndices>()); + + mcg.template LoadStackAddress<Operation::ProcedureIndex, Operation::StackOffset>(); + } + + template<typename Operation> + static constexpr void GenerateCodeForKernelProcedureToSvcInvocation(MetaCodeGenerator &mcg) { + static_assert(Operation::Kind == OperationKind::Scatter); + + [&mcg]<size_t... Is>(std::index_sequence<Is...>) { + (mcg.template LoadFromStack<Operation::template SvcIndex<Is>, Operation::StackOffset + Operation::RegisterSize * (Is), Operation::RegisterSize>(), ...); + }(std::make_index_sequence<Operation::NumSvcIndices>()); + } + }; + }; + + template<typename _SvcAbiType, typename _UserAbiType, typename _KernelAbiType, typename ReturnType, typename... ArgumentTypes> + class LayoutConversion { + public: + using SvcAbiType = _SvcAbiType; + using UserAbiType = _UserAbiType; + using KernelAbiType = _KernelAbiType; + + static constexpr auto LayoutForUser = ProcedureLayout::Create<UserAbiType, ReturnType, ArgumentTypes...>(); + static constexpr auto LayoutForSvc = SvcInvocationLayout::Create<SvcAbiType>(LayoutForUser); + static constexpr auto LayoutForKernel = ProcedureLayout::Create<KernelAbiType, ReturnType, ArgumentTypes...>(); + private: + template<bool Input, size_t ParameterIndex = 0, size_t Used = 0> + static constexpr size_t DetermineUsedStackIndices() { + [[maybe_unused]] constexpr auto Procedure = LayoutForKernel; + [[maybe_unused]] constexpr ParameterLayout Svc = Input ? LayoutForSvc.GetInputLayout() : LayoutForSvc.GetOutputLayout(); + + if constexpr (ParameterIndex >= Svc.GetNumParameters()) { + /* Base case: we're done. */ + return Used; + } else { + /* We're processing more parameters. */ + constexpr Parameter SvcParam = Svc.GetParameter(ParameterIndex); + constexpr Parameter ProcedureParam = Procedure.GetParameter(SvcParam.GetIdentifier()); + + if constexpr (SvcParam.IsPassedByPointer() == ProcedureParam.IsPassedByPointer()) { + /* We're not scattering, so stack won't be used. */ + return DetermineUsedStackIndices<Input, ParameterIndex + 1, Used>(); + } else { + /* We're scattering, and so we're using stack. */ + static_assert(ProcedureParam.GetNumLocations() == 1); + + constexpr size_t IndicesPerRegister = KernelAbiType::RegisterSize / SvcAbiType::RegisterSize; + static_assert(IndicesPerRegister > 0); + + constexpr size_t RequiredCount = util::AlignUp(SvcParam.GetNumLocations(), IndicesPerRegister) / IndicesPerRegister; + + return DetermineUsedStackIndices<Input, ParameterIndex + 1, Used + RequiredCount>(); + } + } + } + + static constexpr size_t AbiUsedStackIndices = [] { + constexpr auto KernLayout = LayoutForKernel.GetInputLayout(); + + size_t used = 0; + for (size_t i = 0; i < KernLayout.GetNumParameters(); i++) { + const auto Param = KernLayout.GetParameter(i); + for (size_t j = 0; j < Param.GetNumLocations(); j++) { + const auto Loc = Param.GetLocation(j); + if (Loc.GetStorage() == Storage::Stack) { + used = std::max(used, Loc.GetIndex() + 1); + } + } + } + + return used; + }(); + + static constexpr size_t BeforeUsedStackIndices = DetermineUsedStackIndices<true>(); + static constexpr size_t AfterUsedStackIndices = DetermineUsedStackIndices<false>(); + + template<bool Input, size_t ParameterIndex, size_t LocationIndex> + static constexpr auto ZipMoveOperations() { + constexpr auto Procedure = LayoutForKernel; + constexpr ParameterLayout Svc = Input ? LayoutForSvc.GetInputLayout() : LayoutForSvc.GetOutputLayout(); + + static_assert(ParameterIndex < Svc.GetNumParameters()); + + constexpr Parameter SvcParam = Svc.GetParameter(ParameterIndex); + constexpr Parameter ProcedureParam = Procedure.GetParameter(SvcParam.GetIdentifier()); + + static_assert(SvcParam.IsPassedByPointer() == ProcedureParam.IsPassedByPointer()); + static_assert(SvcParam.GetNumLocations() == ProcedureParam.GetNumLocations()); + + if constexpr (LocationIndex >= SvcParam.GetNumLocations()) { + /* Base case: we're done. */ + return std::tuple<>{}; + } else { + constexpr Location SvcLoc = SvcParam.GetLocation(LocationIndex); + constexpr Location ProcedureLoc = ProcedureParam.GetLocation(LocationIndex); + + if constexpr (SvcLoc == ProcedureLoc) { + /* No need to emit an operation if we're not changing where we are. */ + return ZipMoveOperations<Input, ParameterIndex, LocationIndex + 1>(); + } else { + /* Svc location needs to be in a register. */ + static_assert(SvcLoc.GetStorage() == Storage::Register); + + constexpr size_t Size = KernelAbiType::RegisterSize; + + if constexpr (ProcedureLoc.GetStorage() == Storage::Register) { + using OperationType = LayoutConversionBase::OperationMove<Size, Size, ProcedureLoc.GetIndex(), SvcLoc.GetIndex()>; + constexpr auto cur_op = std::make_tuple(OperationType{}); + return std::tuple_cat(cur_op, ZipMoveOperations<Input, ParameterIndex, LocationIndex + 1>()); + } else { + using OperationType = LayoutConversionBase::OperationLoadAndStore<Size, Size, ProcedureLoc.GetIndex(), SvcLoc.GetIndex()>; + constexpr auto cur_op = std::make_tuple(OperationType{}); + return std::tuple_cat(cur_op, ZipMoveOperations<Input, ParameterIndex, LocationIndex + 1>()); + } + } + } + } + + template<bool Input, size_t ParameterIndex, size_t StackIndex> + static constexpr auto DetermineConversionOperations() { + [[maybe_unused]] constexpr auto Procedure = LayoutForKernel; + [[maybe_unused]] constexpr ParameterLayout Svc = Input ? LayoutForSvc.GetInputLayout() : LayoutForSvc.GetOutputLayout(); + [[maybe_unused]] constexpr std::array<size_t, Svc.GetNumParameters()> ParameterMap = []<auto CapturedSvc>(){ + /* We want to iterate over the parameters in sorted order. */ + std::array<size_t, CapturedSvc.GetNumParameters()> map{}; + const size_t num_parameters = CapturedSvc.GetNumParameters(); + for (size_t i = 0; i < num_parameters; i++) { + map[i] = i; + } + for (size_t i = 1; i < num_parameters; i++) { + for (size_t j = i; j > 0 && CapturedSvc.GetParameter(map[j-1]).GetLocation(0) > CapturedSvc.GetParameter(map[j]).GetLocation(0); j--) { + std::swap(map[j], map[j-1]); + } + } + return map; + }.template operator()<Svc>(); + + if constexpr (ParameterIndex >= Svc.GetNumParameters()) { + /* Base case: we're done. */ + if constexpr (Input) { + static_assert(StackIndex == BeforeUsedStackIndices + AbiUsedStackIndices); + } else { + static_assert(StackIndex == AfterUsedStackIndices + BeforeUsedStackIndices + AbiUsedStackIndices); + } + return std::tuple<>{}; + } else { + /* We're processing more parameters. */ + constexpr Parameter SvcParam = Svc.GetParameter(ParameterMap[ParameterIndex]); + constexpr Parameter ProcedureParam = Procedure.GetParameter(SvcParam.GetIdentifier()); + + if constexpr (SvcParam.IsPassedByPointer() == ProcedureParam.IsPassedByPointer()) { + if constexpr (SvcParam.GetNumLocations() == ProcedureParam.GetNumLocations()) { + /* Normal moves and loads/stores. */ + return std::tuple_cat(ZipMoveOperations<Input, ParameterMap[ParameterIndex], 0>(), DetermineConversionOperations<Input, ParameterIndex + 1, StackIndex>()); + } else { + /* We're packing. */ + /* Make sure we're handling the 2 -> 1 case. */ + static_assert(SvcParam.GetNumLocations() == 2); + static_assert(ProcedureParam.GetNumLocations() == 1); + + constexpr Location ProcedureLoc = ProcedureParam.GetLocation(0); + constexpr Location SvcLoc0 = SvcParam.GetLocation(0); + constexpr Location SvcLoc1 = SvcParam.GetLocation(1); + static_assert(ProcedureLoc.GetStorage() == Storage::Register); + static_assert(SvcLoc0.GetStorage() == Storage::Register); + static_assert(SvcLoc1.GetStorage() == Storage::Register); + + constexpr size_t Size = KernelAbiType::RegisterSize; + + using OperationType = LayoutConversionBase::OperationPackAndUnpack<Size, Size, ProcedureLoc.GetIndex(), SvcLoc0.GetIndex(), SvcLoc1.GetIndex()>; + + constexpr auto cur_op = std::make_tuple(OperationType{}); + + return std::tuple_cat(cur_op, DetermineConversionOperations<Input, ParameterIndex + 1, StackIndex>()); + } + } else { + /* One operation, since we're scattering. */ + static_assert(ProcedureParam.GetNumLocations() == 1); + constexpr Location ProcedureLoc = ProcedureParam.GetLocation(0); + + constexpr size_t IndicesPerRegister = KernelAbiType::RegisterSize / SvcAbiType::RegisterSize; + static_assert(IndicesPerRegister > 0); + + constexpr size_t RequiredCount = util::AlignUp(SvcParam.GetNumLocations(), IndicesPerRegister) / IndicesPerRegister; + + if constexpr (ProcedureLoc.GetStorage() == Storage::Register) { + /* Scattering. In register during kernel call. */ + constexpr size_t RegisterSize = SvcAbiType::RegisterSize; + constexpr size_t PassedSize = ProcedureParam.GetTypeSize(); + + constexpr auto SvcIndexSequence = []<auto CapturedSvcParam, size_t... Is>(std::index_sequence<Is...>) { + return std::index_sequence<CapturedSvcParam.GetLocation(Is).GetIndex()...>{}; + }.template operator()<SvcParam>(std::make_index_sequence<SvcParam.GetNumLocations()>()); + + constexpr auto OperationValue = []<auto CapturedProcedureLoc, size_t... Is>(std::index_sequence<Is...>) { + return LayoutConversionBase::OperationScatter<RegisterSize, PassedSize, StackIndex * KernelAbiType::RegisterSize, CapturedProcedureLoc.GetIndex(), Is...>{}; + }.template operator()<ProcedureLoc>(SvcIndexSequence); + + constexpr auto cur_op = std::make_tuple(OperationValue); + + return std::tuple_cat(cur_op, DetermineConversionOperations<Input, ParameterIndex + 1, StackIndex + RequiredCount>()); + } else { + /* TODO: How should on-stack-during-kernel-call be handled? */ + static_assert(ProcedureLoc.GetStorage() == Storage::Register); + } + } + } + } + + static constexpr size_t PreserveRegisterStartIndex = SvcAbiType::ArgumentRegisterCount; + static constexpr size_t PreserveRegisterEndIndex = std::min(KernelAbiType::ArgumentRegisterCount, SvcAbiType::RegisterCount); + static constexpr size_t ClearRegisterStartIndex = 0; + static constexpr size_t ClearRegisterEndIndex = std::min(KernelAbiType::ArgumentRegisterCount, SvcAbiType::RegisterCount); + + template<size_t Index> + static constexpr bool ShouldPreserveRegister = (PreserveRegisterStartIndex <= Index && Index < PreserveRegisterEndIndex) && + LayoutForSvc.GetInputLayout().IsRegisterFree(Index) && LayoutForSvc.GetOutputLayout().IsRegisterFree(Index); + + template<size_t Index> + static constexpr bool ShouldClearRegister = (ClearRegisterStartIndex <= Index && Index < ClearRegisterEndIndex) && + LayoutForSvc.GetOutputLayout().IsRegisterFree(Index) && !ShouldPreserveRegister<Index>; + + template<size_t Index = PreserveRegisterStartIndex> + static constexpr auto DeterminePreserveRegisters() { + static_assert(PreserveRegisterStartIndex <= Index && Index <= PreserveRegisterEndIndex); + + if constexpr (Index >= PreserveRegisterEndIndex) { + /* Base case: we're done. */ + return std::index_sequence<>{}; + } else { + if constexpr (ShouldPreserveRegister<Index>) { + /* Preserve this register. */ + return IndexSequenceCat(std::index_sequence<Index>{}, DeterminePreserveRegisters<Index + 1>()); + } else { + /* We don't need to preserve register, so we can skip onwards. */ + return IndexSequenceCat(std::index_sequence<>{}, DeterminePreserveRegisters<Index + 1>()); + } + } + } + + template<size_t Index = ClearRegisterStartIndex> + static constexpr auto DetermineClearRegisters() { + static_assert(ClearRegisterStartIndex <= Index && Index <= ClearRegisterEndIndex); + + if constexpr (Index >= ClearRegisterEndIndex) { + /* Base case: we're done. */ + return std::index_sequence<>{}; + } else { + if constexpr (ShouldClearRegister<Index>) { + /* Clear this register. */ + return IndexSequenceCat(std::index_sequence<Index>{}, DetermineClearRegisters<Index + 1>()); + } else { + /* We don't need to preserve register, so we can skip onwards. */ + return IndexSequenceCat(std::index_sequence<>{}, DetermineClearRegisters<Index + 1>()); + } + } + } + public: + static constexpr size_t NonAbiUsedStackIndices = AfterUsedStackIndices + BeforeUsedStackIndices; + using BeforeOperations = decltype(DetermineConversionOperations<true, 0, AbiUsedStackIndices>()); + using AfterOperations = decltype(DetermineConversionOperations<false, 0, AbiUsedStackIndices + BeforeUsedStackIndices>()); + + static constexpr size_t NumBeforeOperations = std::tuple_size<BeforeOperations>::value; + static constexpr size_t NumAfterOperations = std::tuple_size<AfterOperations>::value; + + using PreserveRegisters = decltype(DeterminePreserveRegisters()); + using ClearRegisters = decltype(DetermineClearRegisters()); + + static constexpr size_t NumPreserveRegisters = PreserveRegisters::size(); + static constexpr size_t NumClearRegisters = ClearRegisters::size(); + + static constexpr auto PreserveRegistersArray = ConvertToArray(PreserveRegisters{}); + static constexpr auto ClearRegistersArray = ConvertToArray(ClearRegisters{}); + public: + template<typename Operation, CodeGenerationKind CodeGenKind, size_t N> + static constexpr bool CanGenerateCode(RegisterAllocator<N> allocator) { + if constexpr (CodeGenKind == CodeGenerationKind::SvcInvocationToKernelProcedure) { + return Operation::ImplType::template CanGenerateCodeForSvcInvocationToKernelProcedure<Operation>(allocator); + } else { + static_assert(false, "Invalid CodeGenerationKind"); + } + } + + template<typename Operation, CodeGenerationKind CodeGenKind, size_t N> + static constexpr void GenerateCode(MetaCodeGenerator &mcg, RegisterAllocator<N> &allocator) { + if constexpr (CodeGenKind == CodeGenerationKind::SvcInvocationToKernelProcedure) { + Operation::ImplType::template GenerateCodeForSvcInvocationToKernelProcedure<Operation>(mcg, allocator); + } else if constexpr (CodeGenKind == CodeGenerationKind::PrepareForKernelProcedureToSvcInvocation) { + Operation::ImplType::template GenerateCodeForPrepareForKernelProcedureToSvcInvocation<Operation>(mcg); + } else if constexpr (CodeGenKind == CodeGenerationKind::KernelProcedureToSvcInvocation) { + Operation::ImplType::template GenerateCodeForKernelProcedureToSvcInvocation<Operation>(mcg); + } else { + static_assert(false, "Invalid CodeGenerationKind"); + } + } + }; + + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_meta_code.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_meta_code.hpp new file mode 100644 index 00000000..7dcb8e73 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_meta_code.hpp @@ -0,0 +1,242 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/codegen/impl/svc_codegen_impl_common.hpp> + +namespace ams::svc::codegen::impl { + + class MetaCode { + public: + static constexpr size_t MaxOperations = 0x40; + + enum class OperationKind { + SaveRegisters, + RestoreRegisters, + ClearRegisters, + AllocateStackSpace, + FreeStackSpace, + MoveRegister, + ConvertToBoolean, + LoadFromStack, + LoadPairFromStack, + StoreToStack, + StorePairToStack, + Pack, + Unpack, + LoadStackAddress, + }; + + static constexpr const char *GetOperationKindName(OperationKind k) { + #define META_CODE_OPERATION_KIND_ENUM_CASE(s) case OperationKind::s: return #s + switch (k) { + META_CODE_OPERATION_KIND_ENUM_CASE(SaveRegisters); + META_CODE_OPERATION_KIND_ENUM_CASE(RestoreRegisters); + META_CODE_OPERATION_KIND_ENUM_CASE(ClearRegisters); + META_CODE_OPERATION_KIND_ENUM_CASE(AllocateStackSpace); + META_CODE_OPERATION_KIND_ENUM_CASE(FreeStackSpace); + META_CODE_OPERATION_KIND_ENUM_CASE(MoveRegister); + META_CODE_OPERATION_KIND_ENUM_CASE(ConvertToBoolean); + META_CODE_OPERATION_KIND_ENUM_CASE(LoadFromStack); + META_CODE_OPERATION_KIND_ENUM_CASE(LoadPairFromStack); + META_CODE_OPERATION_KIND_ENUM_CASE(StoreToStack); + META_CODE_OPERATION_KIND_ENUM_CASE(StorePairToStack); + META_CODE_OPERATION_KIND_ENUM_CASE(Pack); + META_CODE_OPERATION_KIND_ENUM_CASE(Unpack); + META_CODE_OPERATION_KIND_ENUM_CASE(LoadStackAddress); + default: + std::abort(); + } + #undef META_CODE_OPERATION_KIND_ENUM_CASE + } + + struct Operation { + OperationKind kind; + size_t num_parameters; + size_t parameters[16]; + }; + + template<OperationKind Kind, size_t... Is> + static constexpr inline Operation MakeOperation() { + Operation op = {}; + static_assert(sizeof...(Is) <= sizeof(op.parameters) / sizeof(op.parameters[0])); + + op.kind = Kind; + op.num_parameters = sizeof...(Is); + + size_t i = 0; + ((op.parameters[i++] = Is), ...); + + return op; + } + public: + size_t m_num_operations; + std::array<Operation, MaxOperations> m_operations; + public: + constexpr explicit MetaCode() : m_num_operations(0), m_operations() { /* ... */ } + + constexpr size_t GetNumOperations() const { + return m_num_operations; + } + + constexpr Operation GetOperation(size_t i) const { + return m_operations[i]; + } + + constexpr void AddOperation(Operation op) { + m_operations[m_num_operations++] = op; + } + }; + + template<auto Operation> + static constexpr auto GetOperationParameterSequence() { + constexpr size_t NumParameters = Operation.num_parameters; + + return []<size_t... Is>(std::index_sequence<Is...>) { + return std::index_sequence<Operation.parameters[Is]...>{}; + }(std::make_index_sequence<NumParameters>()); + } + + template<typename CodeGenerator, MetaCode::OperationKind Kind, size_t... Parameters> + static ALWAYS_INLINE void GenerateCodeForOperationImpl(std::index_sequence<Parameters...>) { + #define META_CODE_OPERATION_KIND_GENERATE_CODE(KIND) else if constexpr (Kind == MetaCode::OperationKind::KIND) { CodeGenerator::template KIND<Parameters...>(); } + if constexpr (false) { /* ... */ } + META_CODE_OPERATION_KIND_GENERATE_CODE(SaveRegisters) + META_CODE_OPERATION_KIND_GENERATE_CODE(RestoreRegisters) + META_CODE_OPERATION_KIND_GENERATE_CODE(ClearRegisters) + META_CODE_OPERATION_KIND_GENERATE_CODE(AllocateStackSpace) + META_CODE_OPERATION_KIND_GENERATE_CODE(FreeStackSpace) + META_CODE_OPERATION_KIND_GENERATE_CODE(MoveRegister) + META_CODE_OPERATION_KIND_GENERATE_CODE(ConvertToBoolean) + META_CODE_OPERATION_KIND_GENERATE_CODE(LoadFromStack) + META_CODE_OPERATION_KIND_GENERATE_CODE(LoadPairFromStack) + META_CODE_OPERATION_KIND_GENERATE_CODE(StoreToStack) + META_CODE_OPERATION_KIND_GENERATE_CODE(StorePairToStack) + META_CODE_OPERATION_KIND_GENERATE_CODE(Pack) + META_CODE_OPERATION_KIND_GENERATE_CODE(Unpack) + META_CODE_OPERATION_KIND_GENERATE_CODE(LoadStackAddress) + else { static_assert(false, "Unknown MetaOperationKind"); } + #undef META_CODE_OPERATION_KIND_GENERATE_CODE + } + + template<typename CodeGenerator, auto Operation> + static ALWAYS_INLINE void GenerateCodeForOperation() { + GenerateCodeForOperationImpl<CodeGenerator, Operation.kind>(GetOperationParameterSequence<Operation>()); + } + + class MetaCodeGenerator { + private: + using OperationKind = typename MetaCode::OperationKind; + private: + MetaCode m_meta_code; + public: + constexpr explicit MetaCodeGenerator() : m_meta_code() { /* ... */ } + + constexpr MetaCode GetMetaCode() const { + return m_meta_code; + } + + constexpr void AddOperationDirectly(MetaCode::Operation op) { + m_meta_code.AddOperation(op); + } + + template<size_t... Registers> + constexpr void SaveRegisters() { + constexpr auto op = MetaCode::MakeOperation<OperationKind::SaveRegisters, Registers...>(); + m_meta_code.AddOperation(op); + } + + template<size_t... Registers> + constexpr void RestoreRegisters() { + constexpr auto op = MetaCode::MakeOperation<OperationKind::RestoreRegisters, Registers...>(); + m_meta_code.AddOperation(op); + } + + template<size_t... Registers> + constexpr void ClearRegisters() { + constexpr auto op = MetaCode::MakeOperation<OperationKind::ClearRegisters, Registers...>(); + m_meta_code.AddOperation(op); + } + + template<size_t Size> + constexpr void AllocateStackSpace() { + constexpr auto op = MetaCode::MakeOperation<OperationKind::AllocateStackSpace, Size>(); + m_meta_code.AddOperation(op); + } + + template<size_t Size> + constexpr void FreeStackSpace() { + constexpr auto op = MetaCode::MakeOperation<OperationKind::FreeStackSpace, Size>(); + m_meta_code.AddOperation(op); + } + + template<size_t Dst, size_t Src> + constexpr void MoveRegister() { + constexpr auto op = MetaCode::MakeOperation<OperationKind::MoveRegister, Dst, Src>(); + m_meta_code.AddOperation(op); + } + + template<size_t Reg> + constexpr void ConvertToBoolean() { + constexpr auto op = MetaCode::MakeOperation<OperationKind::ConvertToBoolean, Reg>(); + m_meta_code.AddOperation(op); + } + + template<size_t Reg, size_t Offset, size_t Size = 0> + constexpr void LoadFromStack() { + constexpr auto op = MetaCode::MakeOperation<OperationKind::LoadFromStack, Reg, Offset, Size>(); + m_meta_code.AddOperation(op); + } + + template<size_t Reg0, size_t Reg1, size_t Offset, size_t Size> + constexpr void LoadPairFromStack() { + static_assert(Offset % Size == 0); + constexpr auto op = MetaCode::MakeOperation<OperationKind::LoadPairFromStack, Reg0, Reg1, Offset, Size>(); + m_meta_code.AddOperation(op); + } + + template<size_t Reg, size_t Offset, size_t Size = 0> + constexpr void StoreToStack() { + constexpr auto op = MetaCode::MakeOperation<OperationKind::StoreToStack, Reg, Offset, Size>(); + m_meta_code.AddOperation(op); + } + + template<size_t Reg0, size_t Reg1, size_t Offset, size_t Size> + constexpr void StorePairToStack() { + static_assert(Offset % Size == 0); + constexpr auto op = MetaCode::MakeOperation<OperationKind::StorePairToStack, Reg0, Reg1, Offset, Size>(); + m_meta_code.AddOperation(op); + } + + template<size_t Dst, size_t Low, size_t High> + constexpr void Pack() { + constexpr auto op = MetaCode::MakeOperation<OperationKind::Pack, Dst, Low, High>(); + m_meta_code.AddOperation(op); + } + + template<size_t Low, size_t High, size_t Src> + constexpr void Unpack() { + constexpr auto op = MetaCode::MakeOperation<OperationKind::Unpack, Low, High, Src>(); + m_meta_code.AddOperation(op); + } + + template<size_t Dst, size_t Offset> + constexpr void LoadStackAddress() { + constexpr auto op = MetaCode::MakeOperation<OperationKind::LoadStackAddress, Dst, Offset>(); + m_meta_code.AddOperation(op); + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_parameter.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_parameter.hpp new file mode 100644 index 00000000..3017c2a3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/impl/svc_codegen_impl_parameter.hpp @@ -0,0 +1,198 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/codegen/impl/svc_codegen_impl_common.hpp> + +namespace ams::svc::codegen::impl { + + enum class Storage { + Register, + Stack, + Invalid, + }; + + class Location { + private: + static constexpr size_t InvalidIndex = std::numeric_limits<size_t>::max(); + public: + Storage m_storage; + size_t m_index; + public: + constexpr explicit Location() : m_storage(Storage::Invalid), m_index(InvalidIndex) { /* ... */ } + constexpr explicit Location(Storage s, size_t i) : m_storage(s), m_index(i) { /* ... */ } + + constexpr size_t GetIndex() const { return m_index; } + constexpr Storage GetStorage() const { return m_storage; } + + constexpr bool IsValid() const { + return m_index != InvalidIndex && m_storage != Storage::Invalid; + } + + constexpr bool operator==(const Location &rhs) const { + return m_index == rhs.m_index && m_storage == rhs.m_storage; + } + + constexpr bool operator<(const Location &rhs) const { + if (m_storage < rhs.m_storage) { + return true; + } else if (m_storage > rhs.m_storage) { + return false; + } else { + return m_index < rhs.m_index; + } + } + + constexpr bool operator>(const Location &rhs) const { + if (m_storage > rhs.m_storage) { + return true; + } else if (m_storage < rhs.m_storage) { + return false; + } else { + return m_index > rhs.m_index; + } + } + + constexpr bool operator!=(const Location &rhs) const { + return !(*this == rhs); + } + }; + + class Parameter { + public: + static constexpr size_t MaxLocations = 8; + static constexpr size_t IdentifierLengthMax = 0x40; + class Identifier { + public: + char m_name[IdentifierLengthMax]; + size_t m_index; + public: + constexpr explicit Identifier() : m_name(), m_index() { /* ... */ } + constexpr explicit Identifier(const char *nm, size_t idx = 0) : m_name(), m_index(idx) { + for (size_t i = 0; i < IdentifierLengthMax && nm[i]; i++) { + m_name[i] = nm[i]; + } + } + + constexpr bool operator==(const Identifier &rhs) const { + for (size_t i = 0; i < IdentifierLengthMax; i++) { + if (m_name[i] != rhs.m_name[i]) { + return false; + } + } + return m_index == rhs.m_index; + } + + constexpr bool operator!=(const Identifier &rhs) const { + return !(*this == rhs); + } + }; + public: + Identifier m_identifier; + ArgumentType m_type; + size_t m_type_size; + size_t m_passed_size; + bool m_passed_by_pointer; + bool m_is_boolean; + size_t m_num_locations; + Location m_locations[MaxLocations]; + public: + constexpr explicit Parameter() + : m_identifier(), m_type(ArgumentType::Invalid), m_type_size(0), m_passed_size(0), m_passed_by_pointer(0), m_is_boolean(0), m_num_locations(0), m_locations() + { /* ... */ } + + constexpr explicit Parameter(Identifier id, ArgumentType t, size_t ts, size_t ps, bool p, bool b, Location l) + : m_identifier(id), m_type(t), m_type_size(ts), m_passed_size(ps), m_passed_by_pointer(p), m_is_boolean(b), m_num_locations(1), m_locations() + { + m_locations[0] = l; + } + + constexpr Identifier GetIdentifier() const { + return m_identifier; + } + + constexpr bool Is(Identifier rhs) const { + return m_identifier == rhs; + } + + constexpr ArgumentType GetArgumentType() const { + return m_type; + } + + constexpr size_t GetTypeSize() const { + return m_type_size; + } + + constexpr size_t GetPassedSize() const { + return m_passed_size; + } + + constexpr bool IsPassedByPointer() const { + return m_passed_by_pointer; + } + + constexpr bool IsBoolean() const { + return m_is_boolean; + } + + constexpr size_t GetNumLocations() const { + return m_num_locations; + } + + constexpr Location GetLocation(size_t i) const { + return m_locations[i]; + } + + constexpr void AddLocation(Location l) { + m_locations[m_num_locations++] = l; + } + + constexpr bool UsesLocation(Location l) const { + for (size_t i = 0; i < m_num_locations; i++) { + if (m_locations[i] == l) { + return true; + } + } + return false; + } + + constexpr bool operator==(const Parameter &rhs) const { + if (!(m_identifier == rhs.m_identifier && + m_type == rhs.m_type && + m_type_size == rhs.m_type_size && + m_passed_size == rhs.m_passed_size && + m_passed_by_pointer == rhs.m_passed_by_pointer && + m_is_boolean == rhs.m_is_boolean && + m_num_locations == rhs.m_num_locations)) + { + return false; + } + + for (size_t i = 0; i < m_num_locations; i++) { + if (!(m_locations[i] == rhs.m_locations[i])) { + return false; + } + } + + return true; + } + + constexpr bool operator!=(const Parameter &rhs) const { + return !(*this == rhs); + } + }; + + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/svc_codegen_kernel_svc_wrapper.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/svc_codegen_kernel_svc_wrapper.hpp new file mode 100644 index 00000000..55420da7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/codegen/svc_codegen_kernel_svc_wrapper.hpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/codegen/impl/svc_codegen_impl_kernel_svc_wrapper.hpp> + +namespace ams::svc::codegen { + +#if defined(ATMOSPHERE_ARCH_ARM64) || defined(ATMOSPHERE_ARCH_ARM) + + template<auto &Function64, auto &Function64From32> + class KernelSvcWrapper { + private: + /* TODO: using Aarch32 = */ + using Aarch64 = impl::KernelSvcWrapperHelper<impl::Aarch64SvcInvokeAbi, impl::Aarch64Lp64Abi, impl::Aarch64Lp64Abi, Function64>; + using Aarch64From32 = impl::KernelSvcWrapperHelper<impl::Aarch32SvcInvokeAbi, impl::Aarch32Ilp32Abi, impl::Aarch64Lp64Abi, Function64From32>; + public: +/* Set omit-frame-pointer to prevent GCC from emitting MOV X29, SP instructions. */ +#pragma GCC push_options +#pragma GCC optimize ("-O2") +#pragma GCC optimize ("omit-frame-pointer") + + static ALWAYS_INLINE void Call64() { + if constexpr (std::is_same<typename Aarch64::ReturnType, void>::value) { + Aarch64::WrapSvcFunction(); + } else { + const auto &res = Aarch64::WrapSvcFunction(); + __asm__ __volatile__("" :: [res]"r"(res)); + } + + } + + static ALWAYS_INLINE void Call64From32() { + if constexpr (std::is_same<typename Aarch64::ReturnType, void>::value) { + Aarch64From32::WrapSvcFunction(); + } else { + const auto &res = Aarch64From32::WrapSvcFunction(); + __asm__ __volatile__("" :: [res]"r"(res)); + } + } + +#pragma GCC pop_options + }; + +#else + + #error "Unknown architecture for Kernel SVC Code Generation" + +#endif + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/ipc/svc_message_buffer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/ipc/svc_message_buffer.hpp new file mode 100644 index 00000000..88e7387e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/ipc/svc_message_buffer.hpp @@ -0,0 +1,573 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/svc_types_common.hpp> +#include <vapours/svc/svc_select_thread_local_region.hpp> + +namespace ams::svc::ipc { + + #pragma GCC push_options + #pragma GCC optimize ("-O3") + + ALWAYS_INLINE u32 *GetMessageBuffer() { + return GetThreadLocalRegion()->message_buffer; + } + + constexpr inline size_t MessageBufferSize = sizeof(::ams::svc::ThreadLocalRegion::message_buffer); + + class MessageBuffer { + public: + class MessageHeader { + private: + /* Define fields for the first header word. */ + using Tag = util::BitPack32::Field<0, BITSIZEOF(u16), u16>; + using PointerCount = util::BitPack32::Field<Tag::Next, 4, s32>; + using SendCount = util::BitPack32::Field<PointerCount::Next, 4, s32>; + using ReceiveCount = util::BitPack32::Field<SendCount::Next, 4, s32>; + using ExchangeCount = util::BitPack32::Field<ReceiveCount::Next, 4, s32>; + static_assert(ExchangeCount::Next == BITSIZEOF(u32)); + + /* Define fields for the second header word. */ + using RawCount = util::BitPack32::Field<0, 10, s32>; + using ReceiveListCount = util::BitPack32::Field<RawCount::Next, 4, s32>; + using Reserved0 = util::BitPack32::Field<ReceiveListCount::Next, 6, u32>; + using ReceiveListOffset = util::BitPack32::Field<Reserved0::Next, 11, s32>; + using HasSpecialHeader = util::BitPack32::Field<ReceiveListOffset::Next, 1, bool>; + + static constexpr inline u64 NullTag = 0; + static_assert(HasSpecialHeader::Next == BITSIZEOF(u32)); + public: + enum ReceiveListCountType { + ReceiveListCountType_None = 0, + ReceiveListCountType_ToMessageBuffer = 1, + ReceiveListCountType_ToSingleBuffer = 2, + + ReceiveListCountType_CountOffset = 2, + ReceiveListCountType_CountMax = 13, + }; + private: + util::BitPack32 m_header[2]; + public: + constexpr ALWAYS_INLINE MessageHeader() : m_header{util::BitPack32{0}, util::BitPack32{0}} { + m_header[0].Set<Tag>(NullTag); + } + + constexpr ALWAYS_INLINE MessageHeader(u16 tag, bool special, s32 ptr, s32 send, s32 recv, s32 exch, s32 raw, s32 recv_list) : m_header{util::BitPack32{0}, util::BitPack32{0}} { + m_header[0].Set<Tag>(tag); + m_header[0].Set<PointerCount>(ptr); + m_header[0].Set<SendCount>(send); + m_header[0].Set<ReceiveCount>(recv); + m_header[0].Set<ExchangeCount>(exch); + + m_header[1].Set<RawCount>(raw); + m_header[1].Set<ReceiveListCount>(recv_list); + m_header[1].Set<HasSpecialHeader>(special); + } + + ALWAYS_INLINE explicit MessageHeader(const MessageBuffer &buf) : m_header{util::BitPack32{0}, util::BitPack32{0}} { + buf.Get(0, m_header, util::size(m_header)); + } + + ALWAYS_INLINE explicit MessageHeader(const u32 *msg) : m_header{util::BitPack32{msg[0]}, util::BitPack32{msg[1]}} { /* ... */ } + + constexpr ALWAYS_INLINE u16 GetTag() const { + return m_header[0].Get<Tag>(); + } + + constexpr ALWAYS_INLINE s32 GetPointerCount() const { + return m_header[0].Get<PointerCount>(); + } + + constexpr ALWAYS_INLINE s32 GetSendCount() const { + return m_header[0].Get<SendCount>(); + } + + constexpr ALWAYS_INLINE s32 GetReceiveCount() const { + return m_header[0].Get<ReceiveCount>(); + } + + constexpr ALWAYS_INLINE s32 GetExchangeCount() const { + return m_header[0].Get<ExchangeCount>(); + } + + constexpr ALWAYS_INLINE s32 GetMapAliasCount() const { + return this->GetSendCount() + this->GetReceiveCount() + this->GetExchangeCount(); + } + + constexpr ALWAYS_INLINE s32 GetRawCount() const { + return m_header[1].Get<RawCount>(); + } + + constexpr ALWAYS_INLINE s32 GetReceiveListCount() const { + return m_header[1].Get<ReceiveListCount>(); + } + + constexpr ALWAYS_INLINE s32 GetReceiveListOffset() const { + return m_header[1].Get<ReceiveListOffset>(); + } + + constexpr ALWAYS_INLINE bool GetHasSpecialHeader() const { + return m_header[1].Get<HasSpecialHeader>(); + } + + constexpr ALWAYS_INLINE void SetReceiveListCount(s32 recv_list) { + m_header[1].Set<ReceiveListCount>(recv_list); + } + + constexpr ALWAYS_INLINE const util::BitPack32 *GetData() const { + return m_header; + } + + static constexpr ALWAYS_INLINE size_t GetDataSize() { + return sizeof(m_header); + } + }; + + class SpecialHeader { + private: + /* Define fields for the header word. */ + using HasProcessId = util::BitPack32::Field<0, 1, bool>; + using CopyHandleCount = util::BitPack32::Field<HasProcessId::Next, 4, s32>; + using MoveHandleCount = util::BitPack32::Field<CopyHandleCount::Next, 4, s32>; + private: + util::BitPack32 m_header; + bool m_has_header; + public: + constexpr ALWAYS_INLINE explicit SpecialHeader(bool pid, s32 copy, s32 move) : m_header{0}, m_has_header(true) { + m_header.Set<HasProcessId>(pid); + m_header.Set<CopyHandleCount>(copy); + m_header.Set<MoveHandleCount>(move); + } + + consteval explicit SpecialHeader(bool pid, s32 copy, s32 move, bool _has_header) : m_header{0}, m_has_header(_has_header) { + m_header.Set<HasProcessId>(pid); + m_header.Set<CopyHandleCount>(copy); + m_header.Set<MoveHandleCount>(move); + + AMS_ASSUME(m_has_header == (this->GetHasProcessId() || this->GetCopyHandleCount() > 0 || this->GetMoveHandleCount() > 0)); + } + + ALWAYS_INLINE explicit SpecialHeader(const MessageBuffer &buf, const MessageHeader &hdr) : m_header{0}, m_has_header(hdr.GetHasSpecialHeader()) { + if (m_has_header) { + buf.Get(MessageHeader::GetDataSize() / sizeof(util::BitPack32), std::addressof(m_header), sizeof(m_header) / sizeof(util::BitPack32)); + } + } + + constexpr ALWAYS_INLINE bool GetHasProcessId() const { + return m_header.Get<HasProcessId>(); + } + + constexpr ALWAYS_INLINE s32 GetCopyHandleCount() const { + return m_header.Get<CopyHandleCount>(); + } + + constexpr ALWAYS_INLINE s32 GetMoveHandleCount() const { + return m_header.Get<MoveHandleCount>(); + } + + constexpr ALWAYS_INLINE const util::BitPack32 *GetHeader() const { + return std::addressof(m_header); + } + + constexpr ALWAYS_INLINE size_t GetHeaderSize() const { + if (m_has_header) { + return sizeof(m_header); + } else { + return 0; + } + } + + constexpr ALWAYS_INLINE size_t GetDataSize() const { + if (m_has_header) { + return (this->GetHasProcessId() ? sizeof(u64) : 0) + + (this->GetCopyHandleCount() * sizeof(Handle)) + + (this->GetMoveHandleCount() * sizeof(Handle)); + } else { + return 0; + } + } + }; + + class MapAliasDescriptor { + public: + enum Attribute { + Attribute_Ipc = 0, + Attribute_NonSecureIpc = 1, + Attribute_NonDeviceIpc = 3, + }; + private: + /* Define fields for the first two words. */ + using SizeLow = util::BitPack32::Field<0, BITSIZEOF(u32), u32>; + using AddressLow = util::BitPack32::Field<0, BITSIZEOF(u32), u32>; + + /* Define fields for the packed descriptor word. */ + using Attributes = util::BitPack32::Field<0, 2, Attribute>; + using AddressHigh = util::BitPack32::Field<Attributes::Next, 3, u32>; + using Reserved = util::BitPack32::Field<AddressHigh::Next, 19, u32>; + using SizeHigh = util::BitPack32::Field<Reserved::Next, 4, u32>; + using AddressMid = util::BitPack32::Field<SizeHigh::Next, 4, u32>; + + constexpr ALWAYS_INLINE u32 GetAddressMid(u64 address) { + return static_cast<u32>(address >> AddressLow::Count) & ((1u << AddressMid::Count) - 1); + } + + constexpr ALWAYS_INLINE u32 GetAddressHigh(u64 address) { + return static_cast<u32>(address >> (AddressLow::Count + AddressMid::Count)); + } + private: + util::BitPack32 m_data[3]; + public: + constexpr ALWAYS_INLINE MapAliasDescriptor() : m_data{util::BitPack32{0}, util::BitPack32{0}, util::BitPack32{0}} { /* ... */ } + + ALWAYS_INLINE MapAliasDescriptor(const void *buffer, size_t _size, Attribute attr = Attribute_Ipc) : m_data{util::BitPack32{0}, util::BitPack32{0}, util::BitPack32{0}} { + const u64 address = reinterpret_cast<u64>(buffer); + const u64 size = static_cast<u64>(_size); + m_data[0] = { static_cast<u32>(size) }; + m_data[1] = { static_cast<u32>(address) }; + + m_data[2].Set<Attributes>(attr); + m_data[2].Set<AddressMid>(GetAddressMid(address)); + m_data[2].Set<SizeHigh>(static_cast<u32>(size >> SizeLow::Count)); + m_data[2].Set<AddressHigh>(GetAddressHigh(address)); + } + + ALWAYS_INLINE MapAliasDescriptor(const MessageBuffer &buf, s32 index) : m_data{util::BitPack32{0}, util::BitPack32{0}, util::BitPack32{0}} { + buf.Get(index, m_data, util::size(m_data)); + } + + constexpr ALWAYS_INLINE uintptr_t GetAddress() const { + const u64 address = (static_cast<u64>((m_data[2].Get<AddressHigh>() << AddressMid::Count) | m_data[2].Get<AddressMid>()) << AddressLow::Count) | m_data[1].Get<AddressLow>(); + return address; + } + + constexpr ALWAYS_INLINE uintptr_t GetSize() const { + const u64 size = (static_cast<u64>(m_data[2].Get<SizeHigh>()) << SizeLow::Count) | m_data[0].Get<SizeLow>(); + return size; + } + + constexpr ALWAYS_INLINE Attribute GetAttribute() const { + return m_data[2].Get<Attributes>(); + } + + constexpr ALWAYS_INLINE const util::BitPack32 *GetData() const { + return m_data; + } + + static constexpr ALWAYS_INLINE size_t GetDataSize() { + return sizeof(m_data); + } + }; + + class PointerDescriptor { + private: + /* Define fields for the packed descriptor word. */ + using Index = util::BitPack32::Field<0, 4, s32>; + using Reserved0 = util::BitPack32::Field<Index::Next, 2, u32>; + using AddressHigh = util::BitPack32::Field<Reserved0::Next, 3, u32>; + using Reserved1 = util::BitPack32::Field<AddressHigh::Next, 3, u32>; + using AddressMid = util::BitPack32::Field<Reserved1::Next, 4, u32>; + using Size = util::BitPack32::Field<AddressMid::Next, 16, u32>; + + /* Define fields for the second word. */ + using AddressLow = util::BitPack32::Field<0, BITSIZEOF(u32), u32>; + + constexpr ALWAYS_INLINE u32 GetAddressMid(u64 address) { + return static_cast<u32>(address >> AddressLow::Count) & ((1u << AddressMid::Count) - 1); + } + + constexpr ALWAYS_INLINE u32 GetAddressHigh(u64 address) { + return static_cast<u32>(address >> (AddressLow::Count + AddressMid::Count)); + } + private: + util::BitPack32 m_data[2]; + public: + constexpr ALWAYS_INLINE PointerDescriptor() : m_data{util::BitPack32{0}, util::BitPack32{0}} { /* ... */ } + + ALWAYS_INLINE PointerDescriptor(const void *buffer, size_t size, s32 index) : m_data{util::BitPack32{0}, util::BitPack32{0}} { + const u64 address = reinterpret_cast<u64>(buffer); + + m_data[0].Set<Index>(index); + m_data[0].Set<AddressHigh>(GetAddressHigh(address)); + m_data[0].Set<AddressMid>(GetAddressMid(address)); + m_data[0].Set<Size>(size); + + m_data[1] = { static_cast<u32>(address) }; + } + + ALWAYS_INLINE PointerDescriptor(const MessageBuffer &buf, s32 index) : m_data{util::BitPack32{0}, util::BitPack32{0}} { + buf.Get(index, m_data, util::size(m_data)); + } + + constexpr ALWAYS_INLINE s32 GetIndex() const { + return m_data[0].Get<Index>(); + } + + constexpr ALWAYS_INLINE uintptr_t GetAddress() const { + const u64 address = (static_cast<u64>((m_data[0].Get<AddressHigh>() << AddressMid::Count) | m_data[0].Get<AddressMid>()) << AddressLow::Count) | m_data[1].Get<AddressLow>(); + return address; + } + + constexpr ALWAYS_INLINE size_t GetSize() const { + return m_data[0].Get<Size>(); + } + + constexpr ALWAYS_INLINE const util::BitPack32 *GetData() const { + return m_data; + } + + static constexpr ALWAYS_INLINE size_t GetDataSize() { + return sizeof(m_data); + } + }; + + class ReceiveListEntry { + private: + /* Define fields for the first word. */ + using AddressLow = util::BitPack32::Field<0, BITSIZEOF(u32), u32>; + + /* Define fields for the packed descriptor word. */ + using AddressHigh = util::BitPack32::Field<0, 7, u32>; + using Reserved = util::BitPack32::Field<AddressHigh::Next, 9, u32>; + using Size = util::BitPack32::Field<Reserved::Next, 16, u32>; + + constexpr ALWAYS_INLINE u32 GetAddressHigh(u64 address) { + return static_cast<u32>(address >> (AddressLow::Count)); + } + private: + util::BitPack32 m_data[2]; + public: + constexpr ALWAYS_INLINE ReceiveListEntry() : m_data{util::BitPack32{0}, util::BitPack32{0}} { /* ... */ } + + ALWAYS_INLINE ReceiveListEntry(const void *buffer, size_t size) : m_data{util::BitPack32{0}, util::BitPack32{0}} { + const u64 address = reinterpret_cast<u64>(buffer); + + m_data[0] = { static_cast<u32>(address) }; + + m_data[1].Set<AddressHigh>(GetAddressHigh(address)); + m_data[1].Set<Size>(size); + } + + ALWAYS_INLINE ReceiveListEntry(u32 a, u32 b) : m_data{util::BitPack32{a}, util::BitPack32{b}} { /* ... */ } + + constexpr ALWAYS_INLINE uintptr_t GetAddress() const { + const u64 address = (static_cast<u64>(m_data[1].Get<AddressHigh>()) << AddressLow::Count) | m_data[0].Get<AddressLow>(); + return address; + } + + constexpr ALWAYS_INLINE size_t GetSize() const { + return m_data[1].Get<Size>(); + } + + constexpr ALWAYS_INLINE const util::BitPack32 *GetData() const { + return m_data; + } + + static constexpr ALWAYS_INLINE size_t GetDataSize() { + return sizeof(m_data); + } + }; + private: + u32 *m_buffer; + size_t m_size; + public: + constexpr ALWAYS_INLINE MessageBuffer(u32 *b, size_t sz) : m_buffer(b), m_size(sz) { /* ... */ } + constexpr explicit ALWAYS_INLINE MessageBuffer(u32 *b) : m_buffer(b), m_size(sizeof(::ams::svc::ThreadLocalRegion::message_buffer)) { /* ... */ } + + constexpr ALWAYS_INLINE void *GetBufferForDebug() const { + return m_buffer; + } + + constexpr ALWAYS_INLINE size_t GetBufferSize() const { + return m_size; + } + + ALWAYS_INLINE void Get(s32 index, util::BitPack32 *dst, size_t count) const { + /* Ensure that this doesn't get re-ordered. */ + __asm__ __volatile__("" ::: "memory"); + + /* Get the words. */ + static_assert(sizeof(*dst) == sizeof(*m_buffer)); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wclass-memaccess" + __builtin_memcpy(dst, m_buffer + index, count * sizeof(*dst)); +#pragma GCC diagnostic pop + } + + ALWAYS_INLINE s32 Set(s32 index, const util::BitPack32 *src, size_t count) const { + /* Ensure that this doesn't get re-ordered. */ + __asm__ __volatile__("" ::: "memory"); + + /* Set the words. */ + __builtin_memcpy(m_buffer + index, src, count * sizeof(*src)); + + /* Ensure that this doesn't get re-ordered. */ + __asm__ __volatile__("" ::: "memory"); + + return index + count; + } + + template<typename T> + ALWAYS_INLINE const T &GetRaw(s32 index) const { + return *reinterpret_cast<const T *>(m_buffer + index); + } + + template<typename T> + ALWAYS_INLINE s32 SetRaw(s32 index, const T &val) const { + *reinterpret_cast<const T *>(m_buffer + index) = val; + return index + (util::AlignUp(sizeof(val), sizeof(*m_buffer)) / sizeof(*m_buffer)); + } + + ALWAYS_INLINE void GetRawArray(s32 index, void *dst, size_t len) const { + __builtin_memcpy(dst, m_buffer + index, len); + } + + ALWAYS_INLINE void SetRawArray(s32 index, const void *src, size_t len) const { + __builtin_memcpy(m_buffer + index, src, len); + } + + ALWAYS_INLINE void SetNull() const { + this->Set(MessageHeader()); + } + + ALWAYS_INLINE s32 Set(const MessageHeader &hdr) const { + __builtin_memcpy(m_buffer, hdr.GetData(), hdr.GetDataSize()); + return hdr.GetDataSize() / sizeof(*m_buffer); + } + + ALWAYS_INLINE s32 Set(const SpecialHeader &spc) const { + const s32 index = MessageHeader::GetDataSize() / sizeof(*m_buffer); + __builtin_memcpy(m_buffer + index, spc.GetHeader(), spc.GetHeaderSize()); + return index + (spc.GetHeaderSize() / sizeof(*m_buffer)); + } + + ALWAYS_INLINE s32 SetHandle(s32 index, const ::ams::svc::Handle &hnd) const { + static_assert(util::IsAligned(sizeof(hnd), sizeof(*m_buffer))); + __builtin_memcpy(m_buffer + index, std::addressof(hnd), sizeof(hnd)); + return index + (sizeof(hnd) / sizeof(*m_buffer)); + } + + ALWAYS_INLINE s32 SetProcessId(s32 index, const u64 pid) const { + static_assert(util::IsAligned(sizeof(pid), sizeof(*m_buffer))); + __builtin_memcpy(m_buffer + index, std::addressof(pid), sizeof(pid)); + return index + (sizeof(pid) / sizeof(*m_buffer)); + } + + ALWAYS_INLINE s32 Set(s32 index, const MapAliasDescriptor &desc) const { + __builtin_memcpy(m_buffer + index, desc.GetData(), desc.GetDataSize()); + return index + (desc.GetDataSize() / sizeof(*m_buffer)); + } + + ALWAYS_INLINE s32 Set(s32 index, const PointerDescriptor &desc) const { + __builtin_memcpy(m_buffer + index, desc.GetData(), desc.GetDataSize()); + return index + (desc.GetDataSize() / sizeof(*m_buffer)); + } + + ALWAYS_INLINE s32 Set(s32 index, const ReceiveListEntry &desc) const { + __builtin_memcpy(m_buffer + index, desc.GetData(), desc.GetDataSize()); + return index + (desc.GetDataSize() / sizeof(*m_buffer)); + } + + ALWAYS_INLINE s32 Set(s32 index, const u32 val) const { + static_assert(util::IsAligned(sizeof(val), sizeof(*m_buffer))); + __builtin_memcpy(m_buffer + index, std::addressof(val), sizeof(val)); + return index + (sizeof(val) / sizeof(*m_buffer)); + } + + ALWAYS_INLINE Result GetAsyncResult() const { + MessageHeader hdr(m_buffer); + MessageHeader null{}; + R_SUCCEED_IF(AMS_UNLIKELY((__builtin_memcmp(hdr.GetData(), null.GetData(), MessageHeader::GetDataSize()) != 0))); + return m_buffer[MessageHeader::GetDataSize() / sizeof(*m_buffer)]; + } + + ALWAYS_INLINE void SetAsyncResult(Result res) const { + const s32 index = this->Set(MessageHeader()); + const auto value = res.GetValue(); + static_assert(util::IsAligned(sizeof(value), sizeof(*m_buffer))); + __builtin_memcpy(m_buffer + index, std::addressof(value), sizeof(value)); + } + + ALWAYS_INLINE u32 Get32(s32 index) const { + return m_buffer[index]; + } + + ALWAYS_INLINE u64 Get64(s32 index) const { + u64 value; + __builtin_memcpy(std::addressof(value), m_buffer + index, sizeof(value)); + return value; + } + + ALWAYS_INLINE u64 GetProcessId(s32 index) const { + return this->Get64(index); + } + + ALWAYS_INLINE ams::svc::Handle GetHandle(s32 index) const { + static_assert(sizeof(ams::svc::Handle) == sizeof(*m_buffer)); + return ::ams::svc::Handle(m_buffer[index]); + } + + static constexpr ALWAYS_INLINE s32 GetSpecialDataIndex(const MessageHeader &hdr, const SpecialHeader &spc) { + AMS_UNUSED(hdr); + return (MessageHeader::GetDataSize() / sizeof(util::BitPack32)) + (spc.GetHeaderSize() / sizeof(util::BitPack32)); + } + + static constexpr ALWAYS_INLINE s32 GetPointerDescriptorIndex(const MessageHeader &hdr, const SpecialHeader &spc) { + return GetSpecialDataIndex(hdr, spc) + (spc.GetDataSize() / sizeof(util::BitPack32)); + } + + static constexpr ALWAYS_INLINE s32 GetMapAliasDescriptorIndex(const MessageHeader &hdr, const SpecialHeader &spc) { + return GetPointerDescriptorIndex(hdr, spc) + (hdr.GetPointerCount() * PointerDescriptor::GetDataSize() / sizeof(util::BitPack32)); + } + + static constexpr ALWAYS_INLINE s32 GetRawDataIndex(const MessageHeader &hdr, const SpecialHeader &spc) { + return GetMapAliasDescriptorIndex(hdr, spc) + (hdr.GetMapAliasCount() * MapAliasDescriptor::GetDataSize() / sizeof(util::BitPack32)); + } + + static constexpr ALWAYS_INLINE s32 GetReceiveListIndex(const MessageHeader &hdr, const SpecialHeader &spc) { + if (const s32 recv_list_index = hdr.GetReceiveListOffset()) { + return recv_list_index; + } else { + return GetRawDataIndex(hdr, spc) + hdr.GetRawCount(); + } + } + + static constexpr ALWAYS_INLINE size_t GetMessageBufferSize(const MessageHeader &hdr, const SpecialHeader &spc) { + /* Get the size of the plain message. */ + size_t msg_size = GetReceiveListIndex(hdr, spc) * sizeof(util::BitPack32); + + /* Add the size of the receive list. */ + const auto count = hdr.GetReceiveListCount(); + switch (count) { + case MessageHeader::ReceiveListCountType_None: + break; + case MessageHeader::ReceiveListCountType_ToMessageBuffer: + break; + case MessageHeader::ReceiveListCountType_ToSingleBuffer: + msg_size += ReceiveListEntry::GetDataSize(); + break; + default: + msg_size += (count - MessageHeader::ReceiveListCountType_CountOffset) * ReceiveListEntry::GetDataSize(); + break; + } + + return msg_size; + } + }; + + #pragma GCC pop_options + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_codegen.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_codegen.hpp new file mode 100644 index 00000000..04ee2c19 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_codegen.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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +/* NOTE: This header must not be included by svc.hpp. */ +#include <vapours/svc/svc_common.hpp> +#include <vapours/svc/svc_types.hpp> +#include <vapours/svc/svc_definitions.hpp> + +#include <vapours/svc/codegen/svc_codegen_kernel_svc_wrapper.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_common.hpp new file mode 100644 index 00000000..0227e9bf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_common.hpp @@ -0,0 +1,95 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/results.hpp> + +namespace ams::svc { + + /* TODO: C++ style handle? */ + using Handle = u32; +#if defined(ATMOSPHERE_IS_STRATOSPHERE) + static_assert(std::same_as<::ams::svc::Handle, ::Handle>); +#endif + + enum { + HandleWaitMask = (1u << 30), + }; + + constexpr inline s32 ArgumentHandleCountMax = 0x40; + + constexpr inline s64 WaitInfinite = -1; + + enum PseudoHandle : Handle { + CurrentThread = 0xFFFF8000, + CurrentProcess = 0xFFFF8001, + }; + + constexpr inline Handle InvalidHandle = Handle(0); + + constexpr ALWAYS_INLINE bool operator==(const Handle &lhs, const PseudoHandle &rhs) { + return static_cast<Handle>(lhs) == static_cast<Handle>(rhs); + } + + constexpr ALWAYS_INLINE bool operator==(const PseudoHandle &lhs, const Handle &rhs) { + return static_cast<Handle>(lhs) == static_cast<Handle>(rhs); + } + + constexpr ALWAYS_INLINE bool operator!=(const Handle &lhs, const PseudoHandle &rhs) { + return !(lhs == rhs); + } + + constexpr ALWAYS_INLINE bool operator!=(const PseudoHandle &lhs, const Handle &rhs) { + return !(lhs == rhs); + } + + constexpr ALWAYS_INLINE bool IsPseudoHandle(const Handle &handle) { + return handle == PseudoHandle::CurrentProcess || handle == PseudoHandle::CurrentThread; + } + +#if defined(ATMOSPHERE_ARCH_ARM64) + + + namespace lp64 { /* ... */ } + namespace aarch64 { /* ... */ } + using namespace ::ams::svc::lp64; + using namespace ::ams::svc::aarch64; + + /* TODO: ifdef ATMOSPHERE_ABI_LP64 */ + #if 1 + namespace aarch64::lp64 { /* ... */ } + using namespace ::ams::svc::aarch64::lp64; + #else + namespace aarch64::ilp32 { /* ... */ } + using namespace ::ams::svc::aarch64::ilp32; + #endif + +#elif defined(ATMOSPHERE_ARCH_ARM) + + namespace ilp32 { /* ... */ } + namespace aarch32 { /* ... */ } + using namespace ::ams::svc::ilp32; + using namespace ::ams::svc::aarch32; + +#else + + #error "Unknown Architecture" + +#endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_definition_macro.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_definition_macro.hpp new file mode 100644 index 00000000..7e4121aa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_definition_macro.hpp @@ -0,0 +1,150 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#define AMS_SVC_FOREACH_DEFINITION_IMPL(HANDLER, NAMESPACE, INPUT, OUTPUT, INPTR, OUTPTR) \ + HANDLER(0x01, Result, SetHeapSize, OUTPUT(::ams::svc::Address, out_address), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x02, Result, SetMemoryPermission, INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Size, size), INPUT(::ams::svc::MemoryPermission, perm)) \ + HANDLER(0x03, Result, SetMemoryAttribute, INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Size, size), INPUT(uint32_t, mask), INPUT(uint32_t, attr)) \ + HANDLER(0x04, Result, MapMemory, INPUT(::ams::svc::Address, dst_address), INPUT(::ams::svc::Address, src_address), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x05, Result, UnmapMemory, INPUT(::ams::svc::Address, dst_address), INPUT(::ams::svc::Address, src_address), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x06, Result, QueryMemory, OUTPTR(::ams::svc::NAMESPACE::MemoryInfo, out_memory_info), OUTPUT(::ams::svc::PageInfo, out_page_info), INPUT(::ams::svc::Address, address)) \ + HANDLER(0x07, void, ExitProcess) \ + HANDLER(0x08, Result, CreateThread, OUTPUT(::ams::svc::Handle, out_handle), INPUT(::ams::svc::ThreadFunc, func), INPUT(::ams::svc::Address, arg), INPUT(::ams::svc::Address, stack_bottom), INPUT(int32_t, priority), INPUT(int32_t, core_id)) \ + HANDLER(0x09, Result, StartThread, INPUT(::ams::svc::Handle, thread_handle)) \ + HANDLER(0x0A, void, ExitThread) \ + HANDLER(0x0B, void, SleepThread, INPUT(int64_t, ns)) \ + HANDLER(0x0C, Result, GetThreadPriority, OUTPUT(int32_t, out_priority), INPUT(::ams::svc::Handle, thread_handle)) \ + HANDLER(0x0D, Result, SetThreadPriority, INPUT(::ams::svc::Handle, thread_handle), INPUT(int32_t, priority)) \ + HANDLER(0x0E, Result, GetThreadCoreMask, OUTPUT(int32_t, out_core_id), OUTPUT(uint64_t, out_affinity_mask), INPUT(::ams::svc::Handle, thread_handle)) \ + HANDLER(0x0F, Result, SetThreadCoreMask, INPUT(::ams::svc::Handle, thread_handle), INPUT(int32_t, core_id), INPUT(uint64_t, affinity_mask)) \ + HANDLER(0x10, int32_t, GetCurrentProcessorNumber) \ + HANDLER(0x11, Result, SignalEvent, INPUT(::ams::svc::Handle, event_handle)) \ + HANDLER(0x12, Result, ClearEvent, INPUT(::ams::svc::Handle, event_handle)) \ + HANDLER(0x13, Result, MapSharedMemory, INPUT(::ams::svc::Handle, shmem_handle), INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Size, size), INPUT(::ams::svc::MemoryPermission, map_perm)) \ + HANDLER(0x14, Result, UnmapSharedMemory, INPUT(::ams::svc::Handle, shmem_handle), INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x15, Result, CreateTransferMemory, OUTPUT(::ams::svc::Handle, out_handle), INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Size, size), INPUT(::ams::svc::MemoryPermission, map_perm)) \ + HANDLER(0x16, Result, CloseHandle, INPUT(::ams::svc::Handle, handle)) \ + HANDLER(0x17, Result, ResetSignal, INPUT(::ams::svc::Handle, handle)) \ + HANDLER(0x18, Result, WaitSynchronization, OUTPUT(int32_t, out_index), INPTR(::ams::svc::Handle, handles), INPUT(int32_t, num_handles), INPUT(int64_t, timeout_ns)) \ + HANDLER(0x19, Result, CancelSynchronization, INPUT(::ams::svc::Handle, handle)) \ + HANDLER(0x1A, Result, ArbitrateLock, INPUT(::ams::svc::Handle, thread_handle), INPUT(::ams::svc::Address, address), INPUT(uint32_t, tag)) \ + HANDLER(0x1B, Result, ArbitrateUnlock, INPUT(::ams::svc::Address, address)) \ + HANDLER(0x1C, Result, WaitProcessWideKeyAtomic, INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Address, cv_key), INPUT(uint32_t, tag), INPUT(int64_t, timeout_ns)) \ + HANDLER(0x1D, void, SignalProcessWideKey, INPUT(::ams::svc::Address, cv_key), INPUT(int32_t, count)) \ + HANDLER(0x1E, int64_t, GetSystemTick) \ + HANDLER(0x1F, Result, ConnectToNamedPort, OUTPUT(::ams::svc::Handle, out_handle), INPTR(char, name)) \ + HANDLER(0x20, Result, SendSyncRequestLight, INPUT(::ams::svc::Handle, session_handle)) \ + HANDLER(0x21, Result, SendSyncRequest, INPUT(::ams::svc::Handle, session_handle)) \ + HANDLER(0x22, Result, SendSyncRequestWithUserBuffer, INPUT(::ams::svc::Address, message_buffer), INPUT(::ams::svc::Size, message_buffer_size), INPUT(::ams::svc::Handle, session_handle)) \ + HANDLER(0x23, Result, SendAsyncRequestWithUserBuffer, OUTPUT(::ams::svc::Handle, out_event_handle), INPUT(::ams::svc::Address, message_buffer), INPUT(::ams::svc::Size, message_buffer_size), INPUT(::ams::svc::Handle, session_handle)) \ + HANDLER(0x24, Result, GetProcessId, OUTPUT(uint64_t, out_process_id), INPUT(::ams::svc::Handle, process_handle)) \ + HANDLER(0x25, Result, GetThreadId, OUTPUT(uint64_t, out_thread_id), INPUT(::ams::svc::Handle, thread_handle)) \ + HANDLER(0x26, void, Break, INPUT(::ams::svc::BreakReason, break_reason), INPUT(::ams::svc::Address, arg), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x27, Result, OutputDebugString, INPTR(char, debug_str), INPUT(::ams::svc::Size, len)) \ + HANDLER(0x28, void, ReturnFromException, INPUT(::ams::Result, result)) \ + HANDLER(0x29, Result, GetInfo, OUTPUT(uint64_t, out), INPUT(::ams::svc::InfoType, info_type), INPUT(::ams::svc::Handle, handle), INPUT(uint64_t, info_subtype)) \ + HANDLER(0x2A, void, FlushEntireDataCache) \ + HANDLER(0x2B, Result, FlushDataCache, INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x2C, Result, MapPhysicalMemory, INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x2D, Result, UnmapPhysicalMemory, INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x2E, Result, GetDebugFutureThreadInfo, OUTPUT(::ams::svc::NAMESPACE::LastThreadContext, out_context), OUTPUT(uint64_t, thread_id), INPUT(::ams::svc::Handle, debug_handle), INPUT(int64_t, ns)) \ + HANDLER(0x2F, Result, GetLastThreadInfo, OUTPUT(::ams::svc::NAMESPACE::LastThreadContext, out_context), OUTPUT(::ams::svc::Address, out_tls_address), OUTPUT(uint32_t, out_flags)) \ + HANDLER(0x30, Result, GetResourceLimitLimitValue, OUTPUT(int64_t, out_limit_value), INPUT(::ams::svc::Handle, resource_limit_handle), INPUT(::ams::svc::LimitableResource, which)) \ + HANDLER(0x31, Result, GetResourceLimitCurrentValue, OUTPUT(int64_t, out_current_value), INPUT(::ams::svc::Handle, resource_limit_handle), INPUT(::ams::svc::LimitableResource, which)) \ + HANDLER(0x32, Result, SetThreadActivity, INPUT(::ams::svc::Handle, thread_handle), INPUT(::ams::svc::ThreadActivity, thread_activity)) \ + HANDLER(0x33, Result, GetThreadContext3, OUTPTR(::ams::svc::ThreadContext, out_context), INPUT(::ams::svc::Handle, thread_handle)) \ + HANDLER(0x34, Result, WaitForAddress, INPUT(::ams::svc::Address, address), INPUT(::ams::svc::ArbitrationType, arb_type), INPUT(int64_t, value), INPUT(int64_t, timeout_ns)) \ + HANDLER(0x35, Result, SignalToAddress, INPUT(::ams::svc::Address, address), INPUT(::ams::svc::SignalType, signal_type), INPUT(int32_t, value), INPUT(int32_t, count)) \ + HANDLER(0x36, void, SynchronizePreemptionState) \ + HANDLER(0x37, Result, GetResourceLimitPeakValue, OUTPUT(int64_t, out_peak_value), INPUT(::ams::svc::Handle, resource_limit_handle), INPUT(::ams::svc::LimitableResource, which)) \ + \ + HANDLER(0x39, Result, CreateIoPool, OUTPUT(::ams::svc::Handle, out_handle), INPUT(::ams::svc::IoPoolType, which)) \ + HANDLER(0x3A, Result, CreateIoRegion, OUTPUT(::ams::svc::Handle, out_handle), INPUT(::ams::svc::Handle, io_pool), INPUT(::ams::svc::PhysicalAddress, physical_address), INPUT(::ams::svc::Size, size), INPUT(::ams::svc::MemoryMapping, mapping), INPUT(::ams::svc::MemoryPermission, perm)) \ + \ + HANDLER(0x3C, void, KernelDebug, INPUT(::ams::svc::KernelDebugType, kern_debug_type), INPUT(uint64_t, arg0), INPUT(uint64_t, arg1), INPUT(uint64_t, arg2)) \ + HANDLER(0x3D, void, ChangeKernelTraceState, INPUT(::ams::svc::KernelTraceState, kern_trace_state)) \ + \ + HANDLER(0x40, Result, CreateSession, OUTPUT(::ams::svc::Handle, out_server_session_handle), OUTPUT(::ams::svc::Handle, out_client_session_handle), INPUT(bool, is_light), INPUT(::ams::svc::Address, name)) \ + HANDLER(0x41, Result, AcceptSession, OUTPUT(::ams::svc::Handle, out_handle), INPUT(::ams::svc::Handle, port)) \ + HANDLER(0x42, Result, ReplyAndReceiveLight, INPUT(::ams::svc::Handle, handle)) \ + HANDLER(0x43, Result, ReplyAndReceive, OUTPUT(int32_t, out_index), INPTR(::ams::svc::Handle, handles), INPUT(int32_t, num_handles), INPUT(::ams::svc::Handle, reply_target), INPUT(int64_t, timeout_ns)) \ + HANDLER(0x44, Result, ReplyAndReceiveWithUserBuffer, OUTPUT(int32_t, out_index), INPUT(::ams::svc::Address, message_buffer), INPUT(::ams::svc::Size, message_buffer_size), INPTR(::ams::svc::Handle, handles), INPUT(int32_t, num_handles), INPUT(::ams::svc::Handle, reply_target), INPUT(int64_t, timeout_ns)) \ + HANDLER(0x45, Result, CreateEvent, OUTPUT(::ams::svc::Handle, out_write_handle), OUTPUT(::ams::svc::Handle, out_read_handle)) \ + HANDLER(0x46, Result, MapIoRegion, INPUT(::ams::svc::Handle, io_region), INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Size, size), INPUT(::ams::svc::MemoryPermission, perm)) \ + HANDLER(0x47, Result, UnmapIoRegion, INPUT(::ams::svc::Handle, io_region), INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x48, Result, MapPhysicalMemoryUnsafe, INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x49, Result, UnmapPhysicalMemoryUnsafe, INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x4A, Result, SetUnsafeLimit, INPUT(::ams::svc::Size, limit)) \ + HANDLER(0x4B, Result, CreateCodeMemory, OUTPUT(::ams::svc::Handle, out_handle), INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x4C, Result, ControlCodeMemory, INPUT(::ams::svc::Handle, code_memory_handle), INPUT(::ams::svc::CodeMemoryOperation, operation), INPUT(uint64_t, address), INPUT(uint64_t, size), INPUT(::ams::svc::MemoryPermission, perm)) \ + HANDLER(0x4D, void, SleepSystem) \ + HANDLER(0x4E, Result, ReadWriteRegister, OUTPUT(uint32_t, out_value), INPUT(::ams::svc::PhysicalAddress, address), INPUT(uint32_t, mask), INPUT(uint32_t, value)) \ + HANDLER(0x4F, Result, SetProcessActivity, INPUT(::ams::svc::Handle, process_handle), INPUT(::ams::svc::ProcessActivity, process_activity)) \ + HANDLER(0x50, Result, CreateSharedMemory, OUTPUT(::ams::svc::Handle, out_handle), INPUT(::ams::svc::Size, size), INPUT(::ams::svc::MemoryPermission, owner_perm), INPUT(::ams::svc::MemoryPermission, remote_perm)) \ + HANDLER(0x51, Result, MapTransferMemory, INPUT(::ams::svc::Handle, trmem_handle), INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Size, size), INPUT(::ams::svc::MemoryPermission, owner_perm)) \ + HANDLER(0x52, Result, UnmapTransferMemory, INPUT(::ams::svc::Handle, trmem_handle), INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x53, Result, CreateInterruptEvent, OUTPUT(::ams::svc::Handle, out_read_handle), INPUT(int32_t, interrupt_id), INPUT(::ams::svc::InterruptType, interrupt_type)) \ + HANDLER(0x54, Result, QueryPhysicalAddress, OUTPUT(::ams::svc::NAMESPACE::PhysicalMemoryInfo, out_info), INPUT(::ams::svc::Address, address)) \ + HANDLER(0x55, Result, QueryMemoryMapping, OUTPUT(::ams::svc::Address, out_address), OUTPUT(::ams::svc::Size, out_size), INPUT(::ams::svc::PhysicalAddress, physical_address), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x56, Result, CreateDeviceAddressSpace, OUTPUT(::ams::svc::Handle, out_handle), INPUT(uint64_t, das_address), INPUT(uint64_t, das_size)) \ + HANDLER(0x57, Result, AttachDeviceAddressSpace, INPUT(::ams::svc::DeviceName, device_name), INPUT(::ams::svc::Handle, das_handle)) \ + HANDLER(0x58, Result, DetachDeviceAddressSpace, INPUT(::ams::svc::DeviceName, device_name), INPUT(::ams::svc::Handle, das_handle)) \ + HANDLER(0x59, Result, MapDeviceAddressSpaceByForce, INPUT(::ams::svc::Handle, das_handle), INPUT(::ams::svc::Handle, process_handle), INPUT(uint64_t, process_address), INPUT(::ams::svc::Size, size), INPUT(uint64_t, device_address), INPUT(uint32_t, option)) \ + HANDLER(0x5A, Result, MapDeviceAddressSpaceAligned, INPUT(::ams::svc::Handle, das_handle), INPUT(::ams::svc::Handle, process_handle), INPUT(uint64_t, process_address), INPUT(::ams::svc::Size, size), INPUT(uint64_t, device_address), INPUT(uint32_t, option)) \ + HANDLER(0x5C, Result, UnmapDeviceAddressSpace, INPUT(::ams::svc::Handle, das_handle), INPUT(::ams::svc::Handle, process_handle), INPUT(uint64_t, process_address), INPUT(::ams::svc::Size, size), INPUT(uint64_t, device_address)) \ + HANDLER(0x5D, Result, InvalidateProcessDataCache, INPUT(::ams::svc::Handle, process_handle), INPUT(uint64_t, address), INPUT(uint64_t, size)) \ + HANDLER(0x5E, Result, StoreProcessDataCache, INPUT(::ams::svc::Handle, process_handle), INPUT(uint64_t, address), INPUT(uint64_t, size)) \ + HANDLER(0x5F, Result, FlushProcessDataCache, INPUT(::ams::svc::Handle, process_handle), INPUT(uint64_t, address), INPUT(uint64_t, size)) \ + HANDLER(0x60, Result, DebugActiveProcess, OUTPUT(::ams::svc::Handle, out_handle), INPUT(uint64_t, process_id)) \ + HANDLER(0x61, Result, BreakDebugProcess, INPUT(::ams::svc::Handle, debug_handle)) \ + HANDLER(0x62, Result, TerminateDebugProcess, INPUT(::ams::svc::Handle, debug_handle)) \ + HANDLER(0x63, Result, GetDebugEvent, OUTPTR(::ams::svc::NAMESPACE::DebugEventInfo, out_info), INPUT(::ams::svc::Handle, debug_handle)) \ + HANDLER(0x64, Result, ContinueDebugEvent, INPUT(::ams::svc::Handle, debug_handle), INPUT(uint32_t, flags), INPTR(uint64_t, thread_ids), INPUT(int32_t, num_thread_ids)) \ + HANDLER(0x65, Result, GetProcessList, OUTPUT(int32_t, out_num_processes), OUTPTR(uint64_t, out_process_ids), INPUT(int32_t, max_out_count)) \ + HANDLER(0x66, Result, GetThreadList, OUTPUT(int32_t, out_num_threads), OUTPTR(uint64_t, out_thread_ids), INPUT(int32_t, max_out_count), INPUT(::ams::svc::Handle, debug_handle)) \ + HANDLER(0x67, Result, GetDebugThreadContext, OUTPTR(::ams::svc::ThreadContext, out_context), INPUT(::ams::svc::Handle, debug_handle), INPUT(uint64_t, thread_id), INPUT(uint32_t, context_flags)) \ + HANDLER(0x68, Result, SetDebugThreadContext, INPUT(::ams::svc::Handle, debug_handle), INPUT(uint64_t, thread_id), INPTR(::ams::svc::ThreadContext, context), INPUT(uint32_t, context_flags)) \ + HANDLER(0x69, Result, QueryDebugProcessMemory, OUTPTR(::ams::svc::NAMESPACE::MemoryInfo, out_memory_info), OUTPUT(::ams::svc::PageInfo, out_page_info), INPUT(::ams::svc::Handle, process_handle), INPUT(::ams::svc::Address, address)) \ + HANDLER(0x6A, Result, ReadDebugProcessMemory, INPUT(::ams::svc::Address, buffer), INPUT(::ams::svc::Handle, debug_handle), INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x6B, Result, WriteDebugProcessMemory, INPUT(::ams::svc::Handle, debug_handle), INPUT(::ams::svc::Address, buffer), INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x6C, Result, SetHardwareBreakPoint, INPUT(::ams::svc::HardwareBreakPointRegisterName, name), INPUT(uint64_t, flags), INPUT(uint64_t, value)) \ + HANDLER(0x6D, Result, GetDebugThreadParam, OUTPUT(uint64_t, out_64), OUTPUT(uint32_t, out_32), INPUT(::ams::svc::Handle, debug_handle), INPUT(uint64_t, thread_id), INPUT(::ams::svc::DebugThreadParam, param)) \ + \ + HANDLER(0x6F, Result, GetSystemInfo, OUTPUT(uint64_t, out), INPUT(::ams::svc::SystemInfoType, info_type), INPUT(::ams::svc::Handle, handle), INPUT(uint64_t, info_subtype)) \ + HANDLER(0x70, Result, CreatePort, OUTPUT(::ams::svc::Handle, out_server_handle), OUTPUT(::ams::svc::Handle, out_client_handle), INPUT(int32_t, max_sessions), INPUT(bool, is_light), INPUT(::ams::svc::Address, name)) \ + HANDLER(0x71, Result, ManageNamedPort, OUTPUT(::ams::svc::Handle, out_server_handle), INPTR(char, name), INPUT(int32_t, max_sessions)) \ + HANDLER(0x72, Result, ConnectToPort, OUTPUT(::ams::svc::Handle, out_handle), INPUT(::ams::svc::Handle, port)) \ + HANDLER(0x73, Result, SetProcessMemoryPermission, INPUT(::ams::svc::Handle, process_handle), INPUT(uint64_t, address), INPUT(uint64_t, size), INPUT(::ams::svc::MemoryPermission, perm)) \ + HANDLER(0x74, Result, MapProcessMemory, INPUT(::ams::svc::Address, dst_address), INPUT(::ams::svc::Handle, process_handle), INPUT(uint64_t, src_address), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x75, Result, UnmapProcessMemory, INPUT(::ams::svc::Address, dst_address), INPUT(::ams::svc::Handle, process_handle), INPUT(uint64_t, src_address), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x76, Result, QueryProcessMemory, OUTPTR(::ams::svc::NAMESPACE::MemoryInfo, out_memory_info), OUTPUT(::ams::svc::PageInfo, out_page_info), INPUT(::ams::svc::Handle, process_handle), INPUT(uint64_t, address)) \ + HANDLER(0x77, Result, MapProcessCodeMemory, INPUT(::ams::svc::Handle, process_handle), INPUT(uint64_t, dst_address), INPUT(uint64_t, src_address), INPUT(uint64_t, size)) \ + HANDLER(0x78, Result, UnmapProcessCodeMemory, INPUT(::ams::svc::Handle, process_handle), INPUT(uint64_t, dst_address), INPUT(uint64_t, src_address), INPUT(uint64_t, size)) \ + HANDLER(0x79, Result, CreateProcess, OUTPUT(::ams::svc::Handle, out_handle), INPTR(::ams::svc::NAMESPACE::CreateProcessParameter, parameters), INPTR(uint32_t, caps), INPUT(int32_t, num_caps)) \ + HANDLER(0x7A, Result, StartProcess, INPUT(::ams::svc::Handle, process_handle), INPUT(int32_t, priority), INPUT(int32_t, core_id), INPUT(uint64_t, main_thread_stack_size)) \ + HANDLER(0x7B, Result, TerminateProcess, INPUT(::ams::svc::Handle, process_handle)) \ + HANDLER(0x7C, Result, GetProcessInfo, OUTPUT(int64_t, out_info), INPUT(::ams::svc::Handle, process_handle), INPUT(::ams::svc::ProcessInfoType, info_type)) \ + HANDLER(0x7D, Result, CreateResourceLimit, OUTPUT(::ams::svc::Handle, out_handle)) \ + HANDLER(0x7E, Result, SetResourceLimitLimitValue, INPUT(::ams::svc::Handle, resource_limit_handle), INPUT(::ams::svc::LimitableResource, which), INPUT(int64_t, limit_value)) \ + HANDLER(0x7F, void, CallSecureMonitor, OUTPUT(::ams::svc::NAMESPACE::SecureMonitorArguments, args)) \ + \ + HANDLER(0x90, Result, MapInsecurePhysicalMemory, INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x91, Result, UnmapInsecurePhysicalMemory, INPUT(::ams::svc::Address, address), INPUT(::ams::svc::Size, size)) \ + \ + HANDLER(0x2E, Result, LegacyGetFutureThreadInfo, OUTPUT(::ams::svc::NAMESPACE::LastThreadContext, out_context), OUTPUT(::ams::svc::Address, out_tls_address), OUTPUT(uint32_t, out_flags), INPUT(int64_t, ns)) \ + HANDLER(0x55, Result, LegacyQueryIoMapping, OUTPUT(::ams::svc::Address, out_address), INPUT(::ams::svc::PhysicalAddress, physical_address), INPUT(::ams::svc::Size, size)) \ + HANDLER(0x64, Result, LegacyContinueDebugEvent, INPUT(::ams::svc::Handle, debug_handle), INPUT(uint32_t, flags), INPUT(uint64_t, thread_id)) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_definitions.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_definitions.hpp new file mode 100644 index 00000000..ab7f1250 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_definitions.hpp @@ -0,0 +1,119 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/svc_common.hpp> +#include <vapours/svc/svc_types.hpp> +#include <vapours/svc/svc_definition_macro.hpp> + +#define AMS_SVC_KERN_INPUT_HANDLER(TYPE, NAME) TYPE NAME +#define AMS_SVC_KERN_OUTPUT_HANDLER(TYPE, NAME) TYPE *NAME +#define AMS_SVC_KERN_INPTR_HANDLER(TYPE, NAME) ::ams::kern::svc::KUserPointer<const TYPE *> NAME +#define AMS_SVC_KERN_OUTPTR_HANDLER(TYPE, NAME) ::ams::kern::svc::KUserPointer<TYPE *> NAME + +#define AMS_SVC_USER_INPUT_HANDLER(TYPE, NAME) TYPE NAME +#define AMS_SVC_USER_OUTPUT_HANDLER(TYPE, NAME) TYPE *NAME +#define AMS_SVC_USER_INPTR_HANDLER(TYPE, NAME) ::ams::svc::UserPointer<const TYPE *> NAME +#define AMS_SVC_USER_OUTPTR_HANDLER(TYPE, NAME) ::ams::svc::UserPointer<TYPE *> NAME + +#define AMS_SVC_FOREACH_USER_DEFINITION(HANDLER, NAMESPACE) AMS_SVC_FOREACH_DEFINITION_IMPL(HANDLER, NAMESPACE, AMS_SVC_USER_INPUT_HANDLER, AMS_SVC_USER_OUTPUT_HANDLER, AMS_SVC_USER_INPTR_HANDLER, AMS_SVC_USER_OUTPTR_HANDLER) +#define AMS_SVC_FOREACH_KERN_DEFINITION(HANDLER, NAMESPACE) AMS_SVC_FOREACH_DEFINITION_IMPL(HANDLER, NAMESPACE, AMS_SVC_KERN_INPUT_HANDLER, AMS_SVC_KERN_OUTPUT_HANDLER, AMS_SVC_KERN_INPTR_HANDLER, AMS_SVC_KERN_OUTPTR_HANDLER) + +#define AMS_SVC_DECLARE_FUNCTION_PROTOTYPE(ID, RETURN_TYPE, NAME, ...) \ + RETURN_TYPE NAME(__VA_ARGS__); + +namespace ams::svc { + + #define AMS_SVC_DEFINE_ID_ENUM_MEMBER(ID, RETURN_TYPE, NAME, ...) \ + SvcId_##NAME = ID, + + enum SvcId : u32 { + AMS_SVC_FOREACH_KERN_DEFINITION(AMS_SVC_DEFINE_ID_ENUM_MEMBER, _) + }; + + #undef AMS_SVC_DEFINE_ID_ENUM_MEMBER + +} + +#ifdef ATMOSPHERE_IS_STRATOSPHERE + +namespace ams::svc { + + namespace aarch64::lp64 { + + AMS_SVC_FOREACH_USER_DEFINITION(AMS_SVC_DECLARE_FUNCTION_PROTOTYPE, lp64) + + } + + namespace aarch64::ilp32 { + + AMS_SVC_FOREACH_USER_DEFINITION(AMS_SVC_DECLARE_FUNCTION_PROTOTYPE, ilp32) + + } + + namespace aarch32 { + + AMS_SVC_FOREACH_USER_DEFINITION(AMS_SVC_DECLARE_FUNCTION_PROTOTYPE, ilp32) + + } + +} + +/* NOTE: Change this to 1 to test the SVC definitions for user-pointer validity. */ +#if 0 +namespace ams::svc::test { + + namespace impl { + + template<typename... Ts> + struct Validator { + private: + std::array<bool, sizeof...(Ts)> m_valid; + public: + constexpr Validator(Ts... args) : m_valid{static_cast<bool>(args)...} { /* ... */ } + + constexpr bool IsValid() const { + for (size_t i = 0; i < sizeof...(Ts); i++) { + if (!m_valid[i]) { + return false; + } + } + return true; + } + }; + + } + + + #define AMS_SVC_TEST_EMPTY_HANDLER(TYPE, NAME) true + #define AMS_SVC_TEST_INPTR_HANDLER(TYPE, NAME) (sizeof(::ams::svc::UserPointer<const TYPE *>) == sizeof(uintptr_t) && std::is_trivially_destructible<::ams::svc::UserPointer<const TYPE *>>::value) + #define AMS_SVC_TEST_OUTPTR_HANDLER(TYPE, NAME) (sizeof(::ams::svc::UserPointer<TYPE *>) == sizeof(uintptr_t) && std::is_trivially_destructible<::ams::svc::UserPointer<TYPE *>>::value) + + #define AMS_SVC_TEST_VERIFY_USER_POINTERS(ID, RETURN_TYPE, NAME, ...) \ + static_assert(impl::Validator(__VA_ARGS__).IsValid(), "Invalid User Pointer in svc::" #NAME); + + AMS_SVC_FOREACH_DEFINITION_IMPL(AMS_SVC_TEST_VERIFY_USER_POINTERS, lp64, AMS_SVC_TEST_EMPTY_HANDLER, AMS_SVC_TEST_EMPTY_HANDLER, AMS_SVC_TEST_INPTR_HANDLER, AMS_SVC_TEST_OUTPTR_HANDLER); + AMS_SVC_FOREACH_DEFINITION_IMPL(AMS_SVC_TEST_VERIFY_USER_POINTERS, ilp32, AMS_SVC_TEST_EMPTY_HANDLER, AMS_SVC_TEST_EMPTY_HANDLER, AMS_SVC_TEST_INPTR_HANDLER, AMS_SVC_TEST_OUTPTR_HANDLER); + + #undef AMS_SVC_TEST_VERIFY_USER_POINTERS + #undef AMS_SVC_TEST_INPTR_HANDLER + #undef AMS_SVC_TEST_OUTPTR_HANDLER + #undef AMS_SVC_TEST_EMPTY_HANDLER + +} +#endif + +#endif /* ATMOSPHERE_IS_STRATOSPHERE */ + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_memory_map.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_memory_map.hpp new file mode 100644 index 00000000..08bf1773 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_memory_map.hpp @@ -0,0 +1,100 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> + +namespace ams::svc { + + #if defined(ATMOSPHERE_ARCH_ARM64) + + constexpr inline size_t AddressMemoryRegionSmall32Size = 1_GB; + constexpr inline size_t AddressMemoryRegionLarge32Size = 4_GB - AddressMemoryRegionSmall32Size; + constexpr inline size_t AddressMemoryRegionHeap32Size = 1_GB; + constexpr inline size_t AddressMemoryRegionAlias32Size = 1_GB; + + constexpr inline size_t AddressMemoryRegionSmall36Size = 2_GB; + constexpr inline size_t AddressMemoryRegionLarge36Size = 64_GB - AddressMemoryRegionSmall36Size; + constexpr inline size_t AddressMemoryRegionHeap36Size = 8_GB; + constexpr inline size_t AddressMemoryRegionAlias36Size = 6_GB; + + constexpr inline size_t AddressMemoryRegionSmall39Size = 64_GB; + constexpr inline size_t AddressMemoryRegionHeap39Size = 8_GB; + constexpr inline size_t AddressMemoryRegionAlias39Size = 64_GB; + constexpr inline size_t AddressMemoryRegionStack39Size = 2_GB; + + constexpr inline size_t AddressMemoryRegion39Size = 512_GB; + + #elif defined(ATMOSPHERE_ARCH_ARM) + + constexpr inline size_t AddressMemoryRegionSmall32Size = 512_MB; + constexpr inline size_t AddressMemoryRegionLarge32Size = 2_GB - AddressMemoryRegionSmall32Size; + constexpr inline size_t AddressMemoryRegionHeap32Size = 1_GB; + constexpr inline size_t AddressMemoryRegionAlias32Size = 512_MB; + + constexpr inline size_t AddressMemoryRegionSmall36Size = 0; + constexpr inline size_t AddressMemoryRegionLarge36Size = 0; + constexpr inline size_t AddressMemoryRegionHeap36Size = 0; + constexpr inline size_t AddressMemoryRegionAlias36Size = 0; + + constexpr inline size_t AddressMemoryRegionSmall39Size = 0; + constexpr inline size_t AddressMemoryRegionHeap39Size = 0; + constexpr inline size_t AddressMemoryRegionAlias39Size = 0; + constexpr inline size_t AddressMemoryRegionStack39Size = 0; + + constexpr inline size_t AddressMemoryRegion39Size = 0; + + #else + + #error "Unknown architecture for svc::AddressMemoryRegion*Size" + + #endif + + constexpr inline size_t AddressNullGuard32Size = 2_MB; + constexpr inline size_t AddressNullGuard64Size = 128_MB; + + constexpr inline uintptr_t AddressMemoryRegionSmall32Start = 0; + constexpr inline uintptr_t AddressMemoryRegionSmall32End = AddressMemoryRegionSmall32Start + AddressMemoryRegionSmall32Size; + + constexpr inline uintptr_t AddressMemoryRegionLarge32Start = AddressMemoryRegionSmall32End; + constexpr inline uintptr_t AddressMemoryRegionLarge32End = AddressMemoryRegionLarge32Start + AddressMemoryRegionLarge32Size; + + constexpr inline uintptr_t AddressSmallMap32Start = AddressMemoryRegionSmall32Start + AddressNullGuard32Size; + constexpr inline uintptr_t AddressSmallMap32End = AddressMemoryRegionSmall32End; + constexpr inline size_t AddressSmallMap32Size = AddressSmallMap32End - AddressSmallMap32Start; + + constexpr inline uintptr_t AddressLargeMap32Start = AddressMemoryRegionLarge32Start; + constexpr inline uintptr_t AddressLargeMap32End = AddressMemoryRegionLarge32End; + constexpr inline size_t AddressLargeMap32Size = AddressLargeMap32End - AddressLargeMap32Start; + + constexpr inline uintptr_t AddressMemoryRegionSmall36Start = 0; + constexpr inline uintptr_t AddressMemoryRegionSmall36End = AddressMemoryRegionSmall36Start + AddressMemoryRegionSmall36Size; + + constexpr inline uintptr_t AddressMemoryRegionLarge36Start = AddressMemoryRegionSmall36End; + constexpr inline uintptr_t AddressMemoryRegionLarge36End = AddressMemoryRegionLarge36Start + AddressMemoryRegionLarge36Size; + + constexpr inline uintptr_t AddressSmallMap36Start = AddressMemoryRegionSmall36Start + AddressNullGuard64Size; + constexpr inline uintptr_t AddressSmallMap36End = AddressMemoryRegionSmall36End; + constexpr inline size_t AddressSmallMap36Size = AddressSmallMap36End - AddressSmallMap36Start; + + constexpr inline uintptr_t AddressLargeMap36Start = AddressMemoryRegionLarge36Start; + constexpr inline uintptr_t AddressLargeMap36End = AddressMemoryRegionLarge36End; + constexpr inline size_t AddressLargeMap36Size = AddressLargeMap36End - AddressLargeMap36Start; + + constexpr inline uintptr_t AddressMap39Start = 0 + AddressNullGuard64Size; + constexpr inline uintptr_t AddressMap39End = AddressMemoryRegion39Size; + constexpr inline size_t AddressMap39Size = AddressMap39End - AddressMap39Start; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_select_device_name.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_select_device_name.hpp new file mode 100644 index 00000000..61f35245 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_select_device_name.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/svc/svc_common.hpp> + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + + #include <vapours/svc/board/nintendo/nx/svc_device_name.hpp> + namespace ams::svc { + using namespace ams::svc::board::nintendo::nx; + } + +#else + + #include <vapours/svc/board/generic/svc_device_name.hpp> + namespace ams::svc { + using namespace ams::svc::board::generic; + } + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_select_hardware_constants.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_select_hardware_constants.hpp new file mode 100644 index 00000000..ccd6db31 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_select_hardware_constants.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/svc/svc_common.hpp> + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + + #include <vapours/svc/board/nintendo/nx/svc_hardware_constants.hpp> + namespace ams::svc { + using namespace ams::svc::board::nintendo::nx; + } + +#elif defined(ATMOSPHERE_BOARD_QEMU_VIRT) + + #include <vapours/svc/board/qemu/virt/svc_hardware_constants.hpp> + namespace ams::svc { + using namespace ams::svc::board::qemu::virt; + } + +#else + + #error "Unknown board for svc Hardware Constants" + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_select_io_pool_type.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_select_io_pool_type.hpp new file mode 100644 index 00000000..7d21f97d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_select_io_pool_type.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/svc/svc_common.hpp> + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + + #include <vapours/svc/board/nintendo/nx/svc_io_pool_type.hpp> + namespace ams::svc { + + using IoPoolType = ::ams::svc::board::nintendo::nx::IoPoolType; + using enum ::ams::svc::board::nintendo::nx::IoPoolType; + + } + +#else + + #define AMS_SVC_IO_POOL_NOT_SUPPORTED + + namespace ams::svc { + + enum IoPoolType : u32 { + /* Not supported. */ + IoPoolType_Count = 0, + }; + + } + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_select_thread_local_region.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_select_thread_local_region.hpp new file mode 100644 index 00000000..21d1d199 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_select_thread_local_region.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/svc/svc_common.hpp> + +#if defined(ATMOSPHERE_ARCH_ARM64) + + #include <vapours/svc/arch/arm64/svc_thread_local_region.hpp> + namespace ams::svc { + using ams::svc::arch::arm64::ThreadLocalRegion; + using ams::svc::arch::arm64::GetThreadLocalRegion; + } + +#elif defined(ATMOSPHERE_ARCH_ARM) + + #include <vapours/svc/arch/arm/svc_thread_local_region.hpp> + namespace ams::svc { + using ams::svc::arch::arm::ThreadLocalRegion; + using ams::svc::arch::arm::GetThreadLocalRegion; + } + +#else + + #error "Unknown architecture for svc::ThreadLocalRegion" + +#endif + +namespace ams::svc { + + constexpr inline size_t ThreadLocalRegionSize = 0x200; + static_assert(sizeof(::ams::svc::ThreadLocalRegion) == ThreadLocalRegionSize); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_tick.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_tick.hpp new file mode 100644 index 00000000..fcf944fb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_tick.hpp @@ -0,0 +1,93 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/svc/svc_common.hpp> +#include <vapours/svc/svc_select_hardware_constants.hpp> + +namespace ams::svc { + + class Tick { + public: + static constexpr s64 TicksPerSecond = ::ams::svc::TicksPerSecond; + static consteval s64 GetTicksPerSecond() { return TicksPerSecond; } + private: + s64 m_tick; + private: + static constexpr s64 NanoSecondsPerSecond = TimeSpan::FromSeconds(1).GetNanoSeconds(); + + static constexpr ALWAYS_INLINE s64 ConvertTimeSpanToTickImpl(TimeSpan ts) { + /* Get nano-seconds. */ + const s64 ns = ts.GetNanoSeconds(); + + /* Special-case optimize arm64/nintendo-nx value. */ + if (!std::is_constant_evaluated()) { + if constexpr (TicksPerSecond == 19'200'000) { + #if defined(ATMOSPHERE_IS_MESOSPHERE) && defined(ATMOSPHERE_ARCH_ARM64) + s64 t0, t1, t2, t3; + __asm__ __volatile__("mov %[t1], #0x5A53\n" + "movk %[t1], #0xA09B, lsl #16\n" + "lsr %[t0], %[ns], #9\n" + "movk %[t1], #0xB82F, lsl #32\n" + "movk %[t1], #0x0044, lsl #48\n" + "umulh %[t0], %[t0], %[t1]\n" + "mov %[t1], #0xFFFFFFFFFFFF3600\n" + "movk %[t1], #0xC465, lsl #16\n" + "lsr %[t0], %[t0], #0xB\n" + "madd %[t1], %[t0], %[t1], %[ns]\n" + "mov %w[t2], #0xF800\n" + "movk %w[t2], #0x0124, lsl #16\n" + "mov %w[t3], #0xCA00\n" + "movk %w[t3], #0x3B9A, lsl #16\n" + "madd %[t1], %[t1], %[t2], %[t3]\n" + "mov %[t3], #0x94B3\n" + "movk %[t3], #0x26D6, lsl #16\n" + "movk %[t3], #0x0BE8, lsl #32\n" + "movk %[t3], #0x112E, lsl #48\n" + "sub %[t1], %[t1], #1\n" + "smulh %[t1], %[t1], %[t3]\n" + "asr %[t3], %[t1], #26\n" + "add %[t1], %[t3], %[t1], lsr #63\n" + "madd %[t0], %[t0], %[t2], %[t1]\n" + : [t0]"=&r"(t0), [t1]"=&r"(t1), [t2]"=&r"(t2), [t3]"=&r"(t3) + : [ns]"r"(ns) + : "cc"); + return t0; + #endif + } + } + + return util::ScaleByConstantFactorUp<s64, TicksPerSecond, NanoSecondsPerSecond>(ns); + } + public: + constexpr ALWAYS_INLINE explicit Tick(s64 t = 0) : m_tick(t) { /* ... */ } + constexpr ALWAYS_INLINE Tick(TimeSpan ts) : m_tick(ConvertTimeSpanToTickImpl(ts)) { /* ... */ } + + constexpr ALWAYS_INLINE operator s64() const { return m_tick; } + + /* Tick arithmetic. */ + constexpr ALWAYS_INLINE Tick &operator+=(Tick rhs) { m_tick += rhs.m_tick; return *this; } + constexpr ALWAYS_INLINE Tick &operator-=(Tick rhs) { m_tick -= rhs.m_tick; return *this; } + constexpr ALWAYS_INLINE Tick operator+(Tick rhs) const { Tick r(*this); return r += rhs; } + constexpr ALWAYS_INLINE Tick operator-(Tick rhs) const { Tick r(*this); return r -= rhs; } + + constexpr ALWAYS_INLINE Tick &operator+=(TimeSpan rhs) { m_tick += Tick(rhs).m_tick; return *this; } + constexpr ALWAYS_INLINE Tick &operator-=(TimeSpan rhs) { m_tick -= Tick(rhs).m_tick; return *this; } + constexpr ALWAYS_INLINE Tick operator+(TimeSpan rhs) const { Tick r(*this); return r += rhs; } + constexpr ALWAYS_INLINE Tick operator-(TimeSpan rhs) const { Tick r(*this); return r -= rhs; } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_types.hpp new file mode 100644 index 00000000..0d960eef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_types.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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <vapours/svc/svc_common.hpp> +#include <vapours/svc/svc_tick.hpp> +#include <vapours/svc/svc_select_thread_local_region.hpp> +#include <vapours/svc/svc_types_common.hpp> +#include <vapours/svc/svc_types_base.hpp> +#include <vapours/svc/svc_types_dd.hpp> +#include <vapours/svc/svc_types_dmnt.hpp> +#include <vapours/svc/svc_types_priv.hpp> +#include <vapours/svc/svc_select_io_pool_type.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_types_base.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_types_base.hpp new file mode 100644 index 00000000..0fee6810 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_types_base.hpp @@ -0,0 +1,65 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/svc_types_common.hpp> + +namespace ams::svc { + + namespace lp64 { + + struct MemoryInfo { + u64 base_address; + u64 size; + MemoryState state; + MemoryAttribute attribute; + MemoryPermission permission; + u32 ipc_count; + u32 device_count; + u32 padding; + }; + + struct LastThreadContext { + u64 fp; + u64 sp; + u64 lr; + u64 pc; + }; + + } + + namespace ilp32 { + + struct MemoryInfo { + u64 base_address; + u64 size; + MemoryState state; + MemoryAttribute attribute; + MemoryPermission permission; + u32 ipc_count; + u32 device_count; + u32 padding; + }; + + struct LastThreadContext { + u32 fp; + u32 sp; + u32 lr; + u32 pc; + }; + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_types_common.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_types_common.hpp new file mode 100644 index 00000000..21ab685d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_types_common.hpp @@ -0,0 +1,671 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/svc_common.hpp> + +namespace ams::kern::svc::impl { + + struct KUserPointerTag{}; + +} + +namespace ams::svc { + + /* Utility classes required to encode information into the type system for SVC veneers. */ + class Size { + private: + size_t m_size; + public: + constexpr ALWAYS_INLINE Size(size_t s) : m_size(s) { /* ... */ } + constexpr ALWAYS_INLINE operator size_t() { return m_size; } + }; + static_assert(sizeof(Size) == sizeof(size_t)); + static_assert(std::is_trivially_destructible<Size>::value); + + class Address { + private: + uintptr_t m_uintptr; + public: + constexpr ALWAYS_INLINE Address(uintptr_t u) : m_uintptr(u) { /* ... */ } + constexpr ALWAYS_INLINE operator uintptr_t() { return m_uintptr; } + }; + static_assert(sizeof(Address) == sizeof(uintptr_t)); + static_assert(std::is_trivially_destructible<Address>::value); + + namespace impl { + + struct UserPointerTag{}; + + } + + template<typename T> + struct UserPointer : impl::UserPointerTag { + public: + static_assert(std::is_pointer<T>::value); + static constexpr bool IsInput = std::is_const<typename std::remove_pointer<T>::type>::value; + private: + T m_pointer; + public: + constexpr ALWAYS_INLINE UserPointer(T p) : m_pointer(p) { /* ... */ } + + constexpr ALWAYS_INLINE T GetPointerUnsafe() { return m_pointer; } + }; + + template<typename T> + static constexpr inline bool IsUserPointer = std::is_base_of<impl::UserPointerTag, T>::value; + + using ProgramId = u64; + using PhysicalAddress = u64; + + /* Memory types. */ + enum MemoryState : u32 { + MemoryState_Free = 0x00, + MemoryState_Io = 0x01, + MemoryState_Static = 0x02, + MemoryState_Code = 0x03, + MemoryState_CodeData = 0x04, + MemoryState_Normal = 0x05, + MemoryState_Shared = 0x06, + MemoryState_Alias = 0x07, + MemoryState_AliasCode = 0x08, + MemoryState_AliasCodeData = 0x09, + MemoryState_Ipc = 0x0A, + MemoryState_Stack = 0x0B, + MemoryState_ThreadLocal = 0x0C, + MemoryState_Transfered = 0x0D, + MemoryState_SharedTransfered = 0x0E, + MemoryState_SharedCode = 0x0F, + MemoryState_Inaccessible = 0x10, + MemoryState_NonSecureIpc = 0x11, + MemoryState_NonDeviceIpc = 0x12, + MemoryState_Kernel = 0x13, + MemoryState_GeneratedCode = 0x14, + MemoryState_CodeOut = 0x15, + MemoryState_Coverage = 0x16, + MemoryState_Insecure = 0x17, + }; + + enum MemoryPermission : u32 { + MemoryPermission_None = (0 << 0), + + MemoryPermission_Read = (1 << 0), + MemoryPermission_Write = (1 << 1), + MemoryPermission_Execute = (1 << 2), + + MemoryPermission_ReadWrite = MemoryPermission_Read | MemoryPermission_Write, + MemoryPermission_ReadExecute = MemoryPermission_Read | MemoryPermission_Execute, + + MemoryPermission_DontCare = (1 << 28), /* For SharedMemory */ + }; + + enum MemoryAttribute : u32 { + MemoryAttribute_Locked = (1 << 0), + MemoryAttribute_IpcLocked = (1 << 1), + MemoryAttribute_DeviceShared = (1 << 2), + MemoryAttribute_Uncached = (1 << 3), + MemoryAttribute_PermissionLocked = (1 << 4), + }; + + enum MemoryMapping : u32 { + MemoryMapping_IoRegister = 0, + MemoryMapping_Uncached = 1, + MemoryMapping_Memory = 2, + }; + + constexpr inline size_t HeapSizeAlignment = 2_MB; + + struct PageInfo { + u32 flags; + }; + + enum MemoryRegionType { + MemoryRegionType_None = 0, + MemoryRegionType_KernelTraceBuffer = 1, + MemoryRegionType_OnMemoryBootImage = 2, + MemoryRegionType_DTB = 3, + MemoryRegionType_Count, + }; + + enum MapDeviceAddressSpaceFlag : u32 { + MapDeviceAddressSpaceFlag_None = (0 << 0), + MapDeviceAddressSpaceFlag_NotIoRegister = (1 << 0), + }; + + struct MapDeviceAddressSpaceOption { + using Permission = util::BitPack32::Field<0, 16, MemoryPermission>; + using Flags = util::BitPack32::Field<Permission::Next, 1, MapDeviceAddressSpaceFlag>; + using Reserved = util::BitPack32::Field<Flags::Next, 15, u32>; + + static constexpr ALWAYS_INLINE u32 Encode(MemoryPermission perm, u32 flags) { + util::BitPack32 pack{}; + + pack.Set<Permission>(perm); + pack.Set<Flags>(static_cast<svc::MapDeviceAddressSpaceFlag>(flags)); + pack.Set<Reserved>(0); + + return pack.value; + } + }; + + /* Info Types. */ + enum InfoType : u32 { + InfoType_CoreMask = 0, + InfoType_PriorityMask = 1, + InfoType_AliasRegionAddress = 2, + InfoType_AliasRegionSize = 3, + InfoType_HeapRegionAddress = 4, + InfoType_HeapRegionSize = 5, + InfoType_TotalMemorySize = 6, + InfoType_UsedMemorySize = 7, + InfoType_DebuggerAttached = 8, + InfoType_ResourceLimit = 9, + InfoType_IdleTickCount = 10, + InfoType_RandomEntropy = 11, + InfoType_AslrRegionAddress = 12, + InfoType_AslrRegionSize = 13, + InfoType_StackRegionAddress = 14, + InfoType_StackRegionSize = 15, + InfoType_SystemResourceSizeTotal = 16, + InfoType_SystemResourceSizeUsed = 17, + InfoType_ProgramId = 18, + InfoType_InitialProcessIdRange = 19, + InfoType_UserExceptionContextAddress = 20, + InfoType_TotalNonSystemMemorySize = 21, + InfoType_UsedNonSystemMemorySize = 22, + InfoType_IsApplication = 23, + InfoType_FreeThreadCount = 24, + InfoType_ThreadTickCount = 25, + InfoType_IsSvcPermitted = 26, + InfoType_IoRegionHint = 27, + InfoType_AliasRegionExtraSize = 28, + /* ... */ + InfoType_TransferMemoryHint = 34, + + InfoType_MesosphereMeta = 65000, + InfoType_MesosphereCurrentProcess = 65001, + }; + + enum TickCountInfo : u64 { + TickCountInfo_Core0 = 0, + TickCountInfo_Core1 = 1, + TickCountInfo_Core2 = 2, + TickCountInfo_Core3 = 3, + + TickCountInfo_Total = std::numeric_limits<s64>::max(), + }; + + enum MesosphereMetaInfo : u64 { + MesosphereMetaInfo_KernelVersion = 0, + MesosphereMetaInfo_IsKTraceEnabled = 1, + MesosphereMetaInfo_IsSingleStepEnabled = 2, + }; + + enum SystemInfoType : u32 { + SystemInfoType_TotalPhysicalMemorySize = 0, + SystemInfoType_UsedPhysicalMemorySize = 1, + SystemInfoType_InitialProcessIdRange = 2, + }; + + enum InitialProcessIdRangeInfo : u64 { + InitialProcessIdRangeInfo_Minimum = 0, + InitialProcessIdRangeInfo_Maximum = 1, + }; + + enum PhysicalMemorySystemInfo : u64 { + PhysicalMemorySystemInfo_Application = 0, + PhysicalMemorySystemInfo_Applet = 1, + PhysicalMemorySystemInfo_System = 2, + PhysicalMemorySystemInfo_SystemUnsafe = 3, + }; + + enum LastThreadInfoFlag : u32 { + LastThreadInfoFlag_ThreadInSystemCall = (1u << 0), + }; + + enum LimitableResource : u32 { + LimitableResource_PhysicalMemoryMax = 0, + LimitableResource_ThreadCountMax = 1, + LimitableResource_EventCountMax = 2, + LimitableResource_TransferMemoryCountMax = 3, + LimitableResource_SessionCountMax = 4, + + LimitableResource_Count, + }; + + enum CodeMemoryOperation : u32 { + CodeMemoryOperation_Map = 0, + CodeMemoryOperation_MapToOwner = 1, + CodeMemoryOperation_Unmap = 2, + CodeMemoryOperation_UnmapFromOwner = 3, + }; + + /* Synchronization types. */ + enum SignalType : u32 { + SignalType_Signal = 0, + SignalType_SignalAndIncrementIfEqual = 1, + SignalType_SignalAndModifyByWaitingCountIfEqual = 2, + }; + + enum ArbitrationType : u32 { + ArbitrationType_WaitIfLessThan = 0, + ArbitrationType_DecrementAndWaitIfLessThan = 1, + ArbitrationType_WaitIfEqual = 2, + ArbitrationType_WaitIfEqual64 = 3, + }; + + enum YieldType : s64 { + YieldType_WithoutCoreMigration = 0, + YieldType_WithCoreMigration = -1, + YieldType_ToAnyThread = -2, + }; + + enum InterruptType : u32 { + InterruptType_Edge = 0, + InterruptType_Level = 1, + }; + + /* Thread types. */ + using ThreadFunc = ams::svc::Address; + +#if defined(ATMOSPHERE_ARCH_ARM_V8A) + + struct ThreadContext { + u64 r[29]; + u64 fp; + u64 lr; + u64 sp; + u64 pc; + u32 pstate; + u32 padding; + u128 v[32]; + u32 fpcr; + u32 fpsr; + u64 tpidr; + }; + static_assert(sizeof(ThreadContext) == 0x320); + +#elif defined(ATMOSPHERE_ARCH_ARM_V7A) + + struct ThreadContext { + u32 r[13]; + u32 sp; + u32 lr; + u32 pc; + u32 cpsr; + u32 padding; + u64 fpu_registers[32]; + u32 fpscr; + u32 fpexc; + u32 tpidr; + }; + static_assert(sizeof(ThreadContext) == 0x158); + +#else + + #if !defined(ATMOSPHERE_IS_EXOSPHERE) + #error "Unknown Architecture for ams::svc::ThreadContext" + #endif + +#endif + + enum ThreadSuspend : u32 { + ThreadSuspend_Debug = (1 << 0), + ThreadSuspend_User = (1 << 1), + }; + + enum ThreadState : u32 { + ThreadState_Waiting = 0, + ThreadState_Running = 1, + ThreadState_Terminated = 4, + ThreadState_Initializing = 5, + }; + + enum ThreadContextFlag : u32 { + ThreadContextFlag_General = (1 << 0), + ThreadContextFlag_Control = (1 << 1), + ThreadContextFlag_Fpu = (1 << 2), + ThreadContextFlag_FpuControl = (1 << 3), + + ThreadContextFlag_All = (ThreadContextFlag_General | ThreadContextFlag_Control | ThreadContextFlag_Fpu | ThreadContextFlag_FpuControl), + + ThreadContextFlag_SetSingleStep = (1u << 30), + ThreadContextFlag_ClearSingleStep = (1u << 31), + }; + + enum ContinueFlag : u32 { + ContinueFlag_ExceptionHandled = (1u << 0), + ContinueFlag_EnableExceptionEvent = (1u << 1), + ContinueFlag_ContinueAll = (1u << 2), + ContinueFlag_ContinueOthers = (1u << 3), + + ContinueFlag_AllMask = (1u << 4) - 1, + }; + + enum ThreadExitReason : u32 { + ThreadExitReason_ExitThread = 0, + ThreadExitReason_TerminateThread = 1, + ThreadExitReason_ExitProcess = 2, + ThreadExitReason_TerminateProcess = 3, + }; + + enum ThreadActivity : u32 { + ThreadActivity_Runnable = 0, + ThreadActivity_Paused = 1, + }; + + constexpr inline s32 IdealCoreDontCare = -1; + constexpr inline s32 IdealCoreUseProcessValue = -2; + constexpr inline s32 IdealCoreNoUpdate = -3; + + constexpr inline s32 LowestThreadPriority = 63; + constexpr inline s32 HighestThreadPriority = 0; + + constexpr inline s32 SystemThreadPriorityHighest = 16; + + /* Process types. */ + enum ProcessInfoType : u32 { + ProcessInfoType_ProcessState = 0, + }; + + enum ProcessState : u32 { + ProcessState_Created = 0, + ProcessState_CreatedAttached = 1, + ProcessState_Running = 2, + ProcessState_Crashed = 3, + ProcessState_RunningAttached = 4, + ProcessState_Terminating = 5, + ProcessState_Terminated = 6, + ProcessState_DebugBreak = 7, + }; + + enum ProcessExitReason : u32 { + ProcessExitReason_ExitProcess = 0, + ProcessExitReason_TerminateProcess = 1, + ProcessExitReason_Exception = 2, + }; + + enum ProcessActivity : u32 { + ProcessActivity_Runnable = 0, + ProcessActivity_Paused = 1, + }; + + enum CreateProcessFlag : u32 { + /* Is 64 bit? */ + CreateProcessFlag_Is64Bit = (1 << 0), + + /* What kind of address space? */ + CreateProcessFlag_AddressSpaceShift = 1, + CreateProcessFlag_AddressSpaceMask = (7 << CreateProcessFlag_AddressSpaceShift), + CreateProcessFlag_AddressSpace32Bit = (0 << CreateProcessFlag_AddressSpaceShift), + CreateProcessFlag_AddressSpace64BitDeprecated = (1 << CreateProcessFlag_AddressSpaceShift), + CreateProcessFlag_AddressSpace32BitWithoutAlias = (2 << CreateProcessFlag_AddressSpaceShift), + CreateProcessFlag_AddressSpace64Bit = (3 << CreateProcessFlag_AddressSpaceShift), + + /* Should JIT debug be done on crash? */ + CreateProcessFlag_EnableDebug = (1 << 4), + + /* Should ASLR be enabled for the process? */ + CreateProcessFlag_EnableAslr = (1 << 5), + + /* Is the process an application? */ + CreateProcessFlag_IsApplication = (1 << 6), + + /* 4.x deprecated: Should use secure memory? */ + CreateProcessFlag_DeprecatedUseSecureMemory = (1 << 7), + + /* 5.x+ Pool partition type. */ + CreateProcessFlag_PoolPartitionShift = 7, + CreateProcessFlag_PoolPartitionMask = (0xF << CreateProcessFlag_PoolPartitionShift), + CreateProcessFlag_PoolPartitionApplication = (0 << CreateProcessFlag_PoolPartitionShift), + CreateProcessFlag_PoolPartitionApplet = (1 << CreateProcessFlag_PoolPartitionShift), + CreateProcessFlag_PoolPartitionSystem = (2 << CreateProcessFlag_PoolPartitionShift), + CreateProcessFlag_PoolPartitionSystemNonSecure = (3 << CreateProcessFlag_PoolPartitionShift), + + /* 7.x+ Should memory allocation be optimized? This requires IsApplication. */ + CreateProcessFlag_OptimizeMemoryAllocation = (1 << 11), + + /* 11.x+ DisableDeviceAddressSpaceMerge. */ + CreateProcessFlag_DisableDeviceAddressSpaceMerge = (1 << 12), + + /* 18.x EnableAliasRegionExtraSize. */ + CreateProcessFlag_EnableAliasRegionExtraSize = (1 << 13), + + /* Mask of all flags. */ + CreateProcessFlag_All = CreateProcessFlag_Is64Bit | + CreateProcessFlag_AddressSpaceMask | + CreateProcessFlag_EnableDebug | + CreateProcessFlag_EnableAslr | + CreateProcessFlag_IsApplication | + CreateProcessFlag_PoolPartitionMask | + CreateProcessFlag_OptimizeMemoryAllocation | + CreateProcessFlag_DisableDeviceAddressSpaceMerge | + CreateProcessFlag_EnableAliasRegionExtraSize, + }; + + /* Debug types. */ + enum DebugEvent : u32 { + DebugEvent_CreateProcess = 0, + DebugEvent_CreateThread = 1, + DebugEvent_ExitProcess = 2, + DebugEvent_ExitThread = 3, + DebugEvent_Exception = 4, + }; + + enum DebugThreadParam : u32 { + DebugThreadParam_Priority = 0, + DebugThreadParam_State = 1, + DebugThreadParam_IdealCore = 2, + DebugThreadParam_CurrentCore = 3, + DebugThreadParam_AffinityMask = 4, + }; + + enum DebugException : u32 { + DebugException_UndefinedInstruction = 0, + DebugException_InstructionAbort = 1, + DebugException_DataAbort = 2, + DebugException_AlignmentFault = 3, + DebugException_DebuggerAttached = 4, + DebugException_BreakPoint = 5, + DebugException_UserBreak = 6, + DebugException_DebuggerBreak = 7, + DebugException_UndefinedSystemCall = 8, + DebugException_MemorySystemError = 9, + }; + + enum DebugEventFlag : u32 { + DebugEventFlag_Stopped = (1u << 0), + }; + + enum ExceptionType : u32 { + ExceptionType_Init = 0x000, + ExceptionType_InstructionAbort = 0x100, + ExceptionType_DataAbort = 0x101, + ExceptionType_UnalignedInstruction = 0x102, + ExceptionType_UnalignedData = 0x103, + ExceptionType_UndefinedInstruction = 0x104, + ExceptionType_ExceptionInstruction = 0x105, + ExceptionType_MemorySystemError = 0x106, + ExceptionType_FpuException = 0x200, + ExceptionType_InvalidSystemCall = 0x301, + ExceptionType_SystemCallBreak = 0x302, + + ExceptionType_AtmosphereStdAbort = 0xFFE, + }; + + enum BreakReason : u32 { + BreakReason_Panic = 0, + BreakReason_Assert = 1, + BreakReason_User = 2, + BreakReason_PreLoadDll = 3, + BreakReason_PostLoadDll = 4, + BreakReason_PreUnloadDll = 5, + BreakReason_PostUnloadDll = 6, + BreakReason_CppException = 7, + + BreakReason_NotificationOnlyFlag = 0x80000000, + }; + + enum KernelDebugType : u32 { + KernelDebugType_Thread = 0, + KernelDebugType_ThreadCallStack = 1, + KernelDebugType_KernelObject = 2, + KernelDebugType_Handle = 3, + KernelDebugType_Memory = 4, + KernelDebugType_PageTable = 5, + KernelDebugType_CpuUtilization = 6, + KernelDebugType_Process = 7, + KernelDebugType_SuspendProcess = 8, + KernelDebugType_ResumeProcess = 9, + KernelDebugType_Port = 10, + }; + + enum KernelTraceState : u32 { + KernelTraceState_Disabled = 0, + KernelTraceState_Enabled = 1, + }; + + enum BreakPointType : u32 { + BreakPointType_HardwareInstruction = 0, + BreakPointType_HardwareData = 1, + }; + + enum HardwareBreakPointRegisterName : u32 { + HardwareBreakPointRegisterName_I0 = 0, + HardwareBreakPointRegisterName_I1 = 1, + HardwareBreakPointRegisterName_I2 = 2, + HardwareBreakPointRegisterName_I3 = 3, + HardwareBreakPointRegisterName_I4 = 4, + HardwareBreakPointRegisterName_I5 = 5, + HardwareBreakPointRegisterName_I6 = 6, + HardwareBreakPointRegisterName_I7 = 7, + HardwareBreakPointRegisterName_I8 = 8, + HardwareBreakPointRegisterName_I9 = 9, + HardwareBreakPointRegisterName_I10 = 10, + HardwareBreakPointRegisterName_I11 = 11, + HardwareBreakPointRegisterName_I12 = 12, + HardwareBreakPointRegisterName_I13 = 13, + HardwareBreakPointRegisterName_I14 = 14, + HardwareBreakPointRegisterName_I15 = 15, + HardwareBreakPointRegisterName_D0 = 16, + HardwareBreakPointRegisterName_D1 = 17, + HardwareBreakPointRegisterName_D2 = 18, + HardwareBreakPointRegisterName_D3 = 19, + HardwareBreakPointRegisterName_D4 = 20, + HardwareBreakPointRegisterName_D5 = 21, + HardwareBreakPointRegisterName_D6 = 22, + HardwareBreakPointRegisterName_D7 = 23, + HardwareBreakPointRegisterName_D8 = 24, + HardwareBreakPointRegisterName_D9 = 25, + HardwareBreakPointRegisterName_D10 = 26, + HardwareBreakPointRegisterName_D11 = 27, + HardwareBreakPointRegisterName_D12 = 28, + HardwareBreakPointRegisterName_D13 = 29, + HardwareBreakPointRegisterName_D14 = 30, + HardwareBreakPointRegisterName_D15 = 31, + }; + + /* Architecture specific types. */ + namespace aarch64 { + + struct ExceptionInfo { + u64 r[9]; + u64 lr; + u64 sp; + u64 pc; + u32 pstate; + u32 afsr0; + u32 afsr1; + u32 esr; + u64 far; + }; + static_assert(sizeof(ExceptionInfo) == 0x78); + + struct ProcessLocalRegion { + u64 data[(0x1C0 - sizeof(ExceptionInfo)) / sizeof(u64)]; + ExceptionInfo exception_info; + u64 dying_message_region_address; + u64 dying_message_region_size; + u64 padding[6]; + }; + static_assert(sizeof(ProcessLocalRegion) == 0x200); + static_assert(AMS_OFFSETOF(ProcessLocalRegion, dying_message_region_address) == 0x1C0); + + } + + namespace aarch32 { + + struct ExceptionInfoStatus32 { + u32 cpsr; + u32 fsr; + u32 far; + u32 fpexc; + u32 fpinst; + u32 fpinst2; + }; + + struct ExceptionInfoStatus64 { + u32 pstate; + u32 afsr0; + u32 afsr1; + u32 esr; + u32 far; + }; + + struct ExceptionInfo { + u32 r[8]; + u32 sp; + u32 lr; + u32 pc; + u32 flags; + union { + ExceptionInfoStatus32 status_32; + ExceptionInfoStatus64 status_64; + }; + }; + static_assert(sizeof(ExceptionInfo) == 0x48); + + struct ProcessLocalRegion { + u32 data[(0x1C0 - sizeof(ExceptionInfo)) / sizeof(u32)]; + ExceptionInfo exception_info; + u64 dying_message_region_address; + u64 dying_message_region_size; + u64 padding[6]; + }; + static_assert(sizeof(ProcessLocalRegion) == 0x200); + static_assert(AMS_OFFSETOF(ProcessLocalRegion, dying_message_region_address) == 0x1C0); + + } + + /* Secure monitor argument shims. */ + namespace lp64 { + + struct SecureMonitorArguments { + u64 r[8]; + }; + static_assert(sizeof(SecureMonitorArguments) == 0x40); + + } + + namespace ilp32 { + + struct SecureMonitorArguments { + u32 r[8]; + }; + static_assert(sizeof(SecureMonitorArguments) == 0x20); + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_types_dd.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_types_dd.hpp new file mode 100644 index 00000000..b4ab69b9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_types_dd.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/svc_types_common.hpp> +#include <vapours/svc/svc_select_hardware_constants.hpp> +#include <vapours/svc/svc_select_device_name.hpp> + +namespace ams::svc { + + namespace lp64 { + + struct PhysicalMemoryInfo { + PhysicalAddress physical_address; + u64 virtual_address; + u64 size; + }; + + } + + namespace ilp32 { + + struct PhysicalMemoryInfo { + PhysicalAddress physical_address; + u32 virtual_address; + u32 size; + }; + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_types_dmnt.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_types_dmnt.hpp new file mode 100644 index 00000000..c1160b8f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_types_dmnt.hpp @@ -0,0 +1,200 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/svc_types_common.hpp> + +namespace ams::svc { + + namespace lp64 { + + struct DebugInfoCreateProcess { + u64 program_id; + u64 process_id; + char name[0xC]; + u32 flags; + u64 user_exception_context_address; /* 5.0.0+ */ + }; + + struct DebugInfoCreateThread { + u64 thread_id; + u64 tls_address; + /* Removed in 11.0.0 u64 entrypoint; */ + }; + + struct DebugInfoExitProcess { + ProcessExitReason reason; + }; + + struct DebugInfoExitThread { + ThreadExitReason reason; + }; + + struct DebugInfoUndefinedInstructionException { + u32 insn; + }; + + struct DebugInfoDataAbortException { + u64 address; + }; + + struct DebugInfoAlignmentFaultException { + u64 address; + }; + + struct DebugInfoBreakPointException { + BreakPointType type; + u64 address; + }; + + struct DebugInfoUserBreakException { + BreakReason break_reason; + u64 address; + u64 size; + }; + + struct DebugInfoDebuggerBreakException { + u64 active_thread_ids[4]; + }; + + struct DebugInfoUndefinedSystemCallException { + u32 id; + }; + + union DebugInfoSpecificException { + DebugInfoUndefinedInstructionException undefined_instruction; + DebugInfoDataAbortException data_abort; + DebugInfoAlignmentFaultException alignment_fault; + DebugInfoBreakPointException break_point; + DebugInfoUserBreakException user_break; + DebugInfoDebuggerBreakException debugger_break; + DebugInfoUndefinedSystemCallException undefined_system_call; + u64 raw; + }; + + struct DebugInfoException { + DebugException type; + u64 address; + DebugInfoSpecificException specific; + }; + + union DebugInfo { + DebugInfoCreateProcess create_process; + DebugInfoCreateThread create_thread; + DebugInfoExitProcess exit_process; + DebugInfoExitThread exit_thread; + DebugInfoException exception; + }; + + struct DebugEventInfo { + DebugEvent type; + u32 flags; + u64 thread_id; + DebugInfo info; + }; + static_assert(sizeof(DebugEventInfo) >= 0x40); + + } + + namespace ilp32 { + + struct DebugInfoCreateProcess { + u64 program_id; + u64 process_id; + char name[0xC]; + u32 flags; + u32 user_exception_context_address; /* 5.0.0+ */ + }; + + struct DebugInfoCreateThread { + u64 thread_id; + u32 tls_address; + /* Removed in 11.0.0 u32 entrypoint; */ + }; + + struct DebugInfoExitProcess { + ProcessExitReason reason; + }; + + struct DebugInfoExitThread { + ThreadExitReason reason; + }; + + struct DebugInfoUndefinedInstructionException { + u32 insn; + }; + + struct DebugInfoDataAbortException { + u32 address; + }; + + struct DebugInfoAlignmentFaultException { + u32 address; + }; + + struct DebugInfoBreakPointException { + BreakPointType type; + u32 address; + }; + + struct DebugInfoUserBreakException { + BreakReason break_reason; + u32 address; + u32 size; + }; + + struct DebugInfoDebuggerBreakException { + u64 active_thread_ids[4]; + }; + + struct DebugInfoUndefinedSystemCallException { + u32 id; + }; + + union DebugInfoSpecificException { + DebugInfoUndefinedInstructionException undefined_instruction; + DebugInfoDataAbortException data_abort; + DebugInfoAlignmentFaultException alignment_fault; + DebugInfoBreakPointException break_point; + DebugInfoUserBreakException user_break; + DebugInfoDebuggerBreakException debugger_break; + DebugInfoUndefinedSystemCallException undefined_system_call; + u64 raw; + }; + + struct DebugInfoException { + DebugException type; + u32 address; + DebugInfoSpecificException specific; + }; + + union DebugInfo { + DebugInfoCreateProcess create_process; + DebugInfoCreateThread create_thread; + DebugInfoExitProcess exit_process; + DebugInfoExitThread exit_thread; + DebugInfoException exception; + }; + + struct DebugEventInfo { + DebugEvent type; + u32 flags; + u64 thread_id; + DebugInfo info; + }; + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_types_priv.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_types_priv.hpp new file mode 100644 index 00000000..8e8a45f7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_types_priv.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/svc/svc_types_common.hpp> + +namespace ams::svc { + + namespace lp64 { + + struct CreateProcessParameter { + char name[12]; + u32 version; + u64 program_id; + u64 code_address; + s32 code_num_pages; + u32 flags; + Handle reslimit; + s32 system_resource_num_pages; + }; + static_assert(sizeof(CreateProcessParameter) == 0x30); + + } + + namespace ilp32 { + + struct CreateProcessParameter { + char name[12]; + u32 version; + u64 program_id; + u64 code_address; + s32 code_num_pages; + u32 flags; + Handle reslimit; + s32 system_resource_num_pages; + }; + static_assert(sizeof(CreateProcessParameter) == 0x30); + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_version.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_version.hpp new file mode 100644 index 00000000..4ea900fb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/svc/svc_version.hpp @@ -0,0 +1,65 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/svc/svc_common.hpp> +#include <vapours/svc/svc_select_hardware_constants.hpp> +#include <vapours/util/util_bitpack.hpp> + +namespace ams::svc { + + constexpr inline u32 ConvertToSvcMajorVersion(u32 sdk) { return sdk + 4; } + constexpr inline u32 ConvertToSdkMajorVersion(u32 svc) { return svc - 4; } + + constexpr inline u32 ConvertToSvcMinorVersion(u32 sdk) { return sdk; } + constexpr inline u32 ConvertToSdkMinorVersion(u32 svc) { return svc; } + + struct KernelVersion { + using MinorVersion = util::BitPack32::Field<0, 4>; + using MajorVersion = util::BitPack32::Field<MinorVersion::Next, 13>; + }; + + constexpr inline u32 EncodeKernelVersion(u32 major, u32 minor) { + util::BitPack32 pack = {}; + pack.Set<KernelVersion::MinorVersion>(minor); + pack.Set<KernelVersion::MajorVersion>(major); + return pack.value; + } + + constexpr inline u32 GetKernelMajorVersion(u32 encoded) { + const util::BitPack32 pack = { encoded }; + return pack.Get<KernelVersion::MajorVersion>(); + } + + constexpr inline u32 GetKernelMinorVersion(u32 encoded) { + const util::BitPack32 pack = { encoded }; + return pack.Get<KernelVersion::MinorVersion>(); + } + + /* Nintendo doesn't support programs targeting SVC versions < 3.0. */ + constexpr inline u32 RequiredKernelMajorVersion = 3; + constexpr inline u32 RequiredKernelMinorVersion = 0; + + constexpr inline u32 RequiredKernelVersion = EncodeKernelVersion(RequiredKernelMajorVersion, RequiredKernelMinorVersion); + + /* This is the highest SVC version supported by Atmosphere, to be updated on new kernel releases. */ + /* NOTE: Official kernel versions have SVC major = SDK major + 4, SVC minor = SDK minor. */ + constexpr inline u32 SupportedKernelMajorVersion = ConvertToSvcMajorVersion(20); + constexpr inline u32 SupportedKernelMinorVersion = ConvertToSvcMinorVersion( 5); + + constexpr inline u32 SupportedKernelVersion = EncodeKernelVersion(SupportedKernelMajorVersion, SupportedKernelMinorVersion); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra.hpp new file mode 100644 index 00000000..08bf38cb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#include <vapours/tegra/tegra_ahb_arbc.hpp> +#include <vapours/tegra/tegra_apb_misc.hpp> +#include <vapours/tegra/tegra_avp_cache.hpp> +#include <vapours/tegra/tegra_clkrst.hpp> +#include <vapours/tegra/tegra_emc.hpp> +#include <vapours/tegra/tegra_evp.hpp> +#include <vapours/tegra/tegra_flow_ctlr.hpp> +#include <vapours/tegra/tegra_i2c.hpp> +#include <vapours/tegra/tegra_i2s.hpp> +#include <vapours/tegra/tegra_ictlr.hpp> +#include <vapours/tegra/tegra_mc.hpp> +#include <vapours/tegra/tegra_mipi_cal.hpp> +#include <vapours/tegra/tegra_mselect.hpp> +#include <vapours/tegra/tegra_pinmux.hpp> +#include <vapours/tegra/tegra_pg_up.hpp> +#include <vapours/tegra/tegra_pmc.hpp> +#include <vapours/tegra/tegra_pwm.hpp> +#include <vapours/tegra/tegra_sb.hpp> +#include <vapours/tegra/tegra_sysctr0.hpp> +#include <vapours/tegra/tegra_timer.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_ahb_arbc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_ahb_arbc.hpp new file mode 100644 index 00000000..4f8d6ac3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_ahb_arbc.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#define AHB_ARBC(x) (0x6000c000 + x) + +#define AHB_ARBITRATION_DISABLE (0x004) +#define AHB_ARBITRATION_PRIORITY_CTRL (0x008) +#define AHB_MASTER_SWID (0x018) +#define AHB_MASTER_SWID_1 (0x038) +#define AHB_GIZMO_TZRAM (0x054) +#define AHB_ARBITRATION_XBAR_CTRL (0x0E0) +#define AHB_AHB_SPARE_REG (0x110) + +#define AHB_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (AHB_, NAME) +#define AHB_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (AHB_, NAME, VALUE) +#define AHB_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (AHB_, NAME, ENUM) +#define AHB_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(AHB_, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + +#define DEFINE_AHB_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (AHB_, NAME, __OFFSET__, __WIDTH__) +#define DEFINE_AHB_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (AHB_, NAME, __OFFSET__, ZERO, ONE) +#define DEFINE_AHB_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (AHB_, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) +#define DEFINE_AHB_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(AHB_, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) +#define DEFINE_AHB_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (AHB_, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + +DEFINE_AHB_REG_BIT_ENUM(ARBITRATION_DISABLE_COP, 1, ENABLE, DISABLE); +DEFINE_AHB_REG_BIT_ENUM(ARBITRATION_DISABLE_AHBDMA, 5, ENABLE, DISABLE); +DEFINE_AHB_REG_BIT_ENUM(ARBITRATION_DISABLE_USB, 6, ENABLE, DISABLE); +DEFINE_AHB_REG_BIT_ENUM(ARBITRATION_DISABLE_USB2, 18, ENABLE, DISABLE); + +DEFINE_AHB_REG_BIT_ENUM(ARBITRATION_XBAR_CTRL_MEM_INIT_DONE, 16, NOT_DONE, DONE); + +DEFINE_AHB_REG(AHB_SPARE_REG_CSITE_PADMACRO3_TRIM_SEL, 0, 5); +DEFINE_AHB_REG_BIT_ENUM(AHB_SPARE_REG_OBS_OVERRIDE_EN, 5, DISABLE, ENABLE); +DEFINE_AHB_REG_BIT_ENUM(AHB_SPARE_REG_APB2JTAG_OVERRIDE_EN, 6, DISABLE, ENABLE); +DEFINE_AHB_REG(AHB_SPARE_REG_AHB_SPARE_REG, 12, 32-12); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_apb_misc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_apb_misc.hpp new file mode 100644 index 00000000..2dacfea2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_apb_misc.hpp @@ -0,0 +1,150 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#define APB_MISC_PP_CONFIG_CTL (0x024) + +#define APB_MISC_PP_PINMUX_GLOBAL_0 (0x040) + +#define APB_MISC_GP_ASDBGREG (0x810) + +#define APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL (0xA98) + +#define APB_MISC_GP_EMMC2_PAD_CFGPADCTRL (0xA9C) +#define APB_MISC_GP_SDMMC2_PAD_CFGPADCTRL (0xA9C) + +#define APB_MISC_GP_EMMC4_PAD_CFGPADCTRL (0xAB4) +#define APB_MISC_GP_EMMC4_PAD_PUPD_CFGPADCTRL (0xABC) + +/* Mariko only */ +#define APB_MISC_GP_DSI_PAD_CONTROL (0xAC0) + +#define APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG0_0 (0xc00) +#define APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG0_0 (0xc00) +#define APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG1_0 (0xc04) +#define APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG2_0 (0xc08) + +#define APB_MISC_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (APB_MISC, NAME) +#define APB_MISC_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (APB_MISC, NAME, VALUE) +#define APB_MISC_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (APB_MISC, NAME, ENUM) +#define APB_MISC_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(APB_MISC, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + +#define DEFINE_APB_MISC_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (APB_MISC, NAME, __OFFSET__, __WIDTH__) +#define DEFINE_APB_MISC_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (APB_MISC, NAME, __OFFSET__, ZERO, ONE) +#define DEFINE_APB_MISC_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (APB_MISC, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) +#define DEFINE_APB_MISC_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(APB_MISC, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) +#define DEFINE_APB_MISC_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (APB_MISC, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + +DEFINE_APB_MISC_REG_BIT_ENUM(PP_CONFIG_CTL_JTAG, 6, DISABLE, ENABLE); +DEFINE_APB_MISC_REG_BIT_ENUM(PP_CONFIG_CTL_TBE, 7, DISABLE, ENABLE); + +DEFINE_APB_MISC_REG(GP_ASDBGREG_CFG2TMC_RAM_SVOP_PDP, 24, 2); + +DEFINE_APB_MISC_REG (GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_PAD_CAL_DRVDN, 12, 7); +DEFINE_APB_MISC_REG (GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_PAD_CAL_DRVUP, 20, 7); +DEFINE_APB_MISC_REG (GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_CLK_CFG_CAL_DRVDN_SLWR, 28, 2); +DEFINE_APB_MISC_REG (GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_CLK_CFG_CAL_DRVDN_SLWF, 30, 2); + +DEFINE_APB_MISC_REG_BIT_ENUM(GP_EMMC2_PAD_CFGPADCTRL_CFG2TMC_EMMC2_PAD_E_SCH, 0, DISABLE, ENABLE); +DEFINE_APB_MISC_REG (GP_EMMC2_PAD_CFGPADCTRL_CFG2TMC_EMMC2_PAD_DRVDN_COMP, 2, 6); +DEFINE_APB_MISC_REG (GP_EMMC2_PAD_CFGPADCTRL_CFG2TMC_EMMC2_PAD_DRVUP_COMP, 8, 6); +DEFINE_APB_MISC_REG (GP_EMMC2_PAD_CFGPADCTRL_MISC2PMC_EMMC2_ALL_PARK, 14, 13); + +DEFINE_APB_MISC_REG (GP_SDMMC2_PAD_CFGPADCTRL_CFG2TMC_SDMMC2_PAD_CAL_DRVDN, 12, 7); +DEFINE_APB_MISC_REG (GP_SDMMC2_PAD_CFGPADCTRL_CFG2TMC_SDMMC2_PAD_CAL_DRVUP, 20, 7); + + +DEFINE_APB_MISC_REG_BIT_ENUM(GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_E_SCH, 0, DISABLE, ENABLE); +DEFINE_APB_MISC_REG (GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DRVDN_COMP, 2, 6); +DEFINE_APB_MISC_REG (GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DRVUP_COMP, 8, 6); +DEFINE_APB_MISC_REG (GP_EMMC4_PAD_CFGPADCTRL_MISC2PMC_EMMC4_ALL_PARK, 14, 13); + +DEFINE_APB_MISC_REG(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_CMD_PUPD_PULLU, 1, 1); +DEFINE_APB_MISC_REG(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_CLK_PUPD_PULLD, 2, 1); +DEFINE_APB_MISC_REG(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DQS_PUPD_PULLD, 22, 1); + +#define DEFINE_SLAVE_SECURITY_REG(RINDEX, INDEX, NAME) DEFINE_APB_MISC_REG_BIT_ENUM(SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG##RINDEX##_##NAME##_SECURITY_EN, INDEX, DISABLE, ENABLE) + +DEFINE_SLAVE_SECURITY_REG(0, 29, STM); +DEFINE_SLAVE_SECURITY_REG(0, 24, CEC); +DEFINE_SLAVE_SECURITY_REG(0, 23, ATOMICS); +DEFINE_SLAVE_SECURITY_REG(0, 22, LA); +DEFINE_SLAVE_SECURITY_REG(0, 21, HDA); +DEFINE_SLAVE_SECURITY_REG(0, 20, SATA); +DEFINE_SLAVE_SECURITY_REG(0, 16, KFUSE); +DEFINE_SLAVE_SECURITY_REG(0, 15, FUSE); +DEFINE_SLAVE_SECURITY_REG(0, 14, SE); +DEFINE_SLAVE_SECURITY_REG(0, 13, PMC); +DEFINE_SLAVE_SECURITY_REG(0, 11, RTC); +DEFINE_SLAVE_SECURITY_REG(0, 10, CSITE); +DEFINE_SLAVE_SECURITY_REG(0, 9, QSPI); +DEFINE_SLAVE_SECURITY_REG(0, 8, PWM); +DEFINE_SLAVE_SECURITY_REG(0, 6, DTV); +DEFINE_SLAVE_SECURITY_REG(0, 4, APE); +DEFINE_SLAVE_SECURITY_REG(0, 3, PINMUX_AUX); +DEFINE_SLAVE_SECURITY_REG(0, 2, SATA_AUX); +DEFINE_SLAVE_SECURITY_REG(0, 1, MISC_REGS); + +DEFINE_SLAVE_SECURITY_REG(1, 31, I2C6); +DEFINE_SLAVE_SECURITY_REG(1, 30, DVC); +DEFINE_SLAVE_SECURITY_REG(1, 29, I2C4); +DEFINE_SLAVE_SECURITY_REG(1, 28, I2C3); +DEFINE_SLAVE_SECURITY_REG(1, 27, I2C2); +DEFINE_SLAVE_SECURITY_REG(1, 26, I2C1); +DEFINE_SLAVE_SECURITY_REG(1, 25, SPI6); +DEFINE_SLAVE_SECURITY_REG(1, 24, SPI5); +DEFINE_SLAVE_SECURITY_REG(1, 23, SPI4); +DEFINE_SLAVE_SECURITY_REG(1, 22, SPI3); +DEFINE_SLAVE_SECURITY_REG(1, 21, SPI2); +DEFINE_SLAVE_SECURITY_REG(1, 20, SPI1); +DEFINE_SLAVE_SECURITY_REG(1, 15, UART_D); +DEFINE_SLAVE_SECURITY_REG(1, 14, UART_C); +DEFINE_SLAVE_SECURITY_REG(1, 13, UART_B); +DEFINE_SLAVE_SECURITY_REG(1, 12, UART_A); +DEFINE_SLAVE_SECURITY_REG(1, 11, EMCB); +DEFINE_SLAVE_SECURITY_REG(1, 10, MCB); +DEFINE_SLAVE_SECURITY_REG(1, 9, EMC1); +DEFINE_SLAVE_SECURITY_REG(1, 8, MC1); +DEFINE_SLAVE_SECURITY_REG(1, 5, EMC0); +DEFINE_SLAVE_SECURITY_REG(1, 4, MC0); + +DEFINE_SLAVE_SECURITY_REG(2, 21, FEK); +DEFINE_SLAVE_SECURITY_REG(2, 20, PKA1); +DEFINE_SLAVE_SECURITY_REG(2, 19, SE2); +DEFINE_SLAVE_SECURITY_REG(2, 16, DVFS); +DEFINE_SLAVE_SECURITY_REG(2, 15, MIPI_CAL); +DEFINE_SLAVE_SECURITY_REG(2, 14, XUSB_PADCTL); +DEFINE_SLAVE_SECURITY_REG(2, 13, XUSB_DEV); +DEFINE_SLAVE_SECURITY_REG(2, 12, XUSB_HOST); +DEFINE_SLAVE_SECURITY_REG(2, 11, APB2JTAG); +DEFINE_SLAVE_SECURITY_REG(2, 10, SOC_THERM); +DEFINE_SLAVE_SECURITY_REG(2, 9, DP2); +DEFINE_SLAVE_SECURITY_REG(2, 8, DDS); +DEFINE_SLAVE_SECURITY_REG(2, 7, MIPIBIF); +DEFINE_SLAVE_SECURITY_REG(2, 3, SDMMC4); +DEFINE_SLAVE_SECURITY_REG(2, 2, SDMMC3); +DEFINE_SLAVE_SECURITY_REG(2, 1, SDMMC2); +DEFINE_SLAVE_SECURITY_REG(2, 0, SDMMC1); + +#undef DEFINE_SLAVE_SECURITY_REG + +#define SLAVE_SECURITY_REG_BITS_ENUM(RINDEX, NAME, ENUM) APB_MISC_REG_BITS_ENUM(SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG##RINDEX##_##NAME##_SECURITY_EN, ENUM) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_avp_cache.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_avp_cache.hpp new file mode 100644 index 00000000..43eb3a88 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_avp_cache.hpp @@ -0,0 +1,117 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#define AVP_CACHE_ADDR(n) (0x50040000 + n) + +#define AVP_CACHE_CONFIG (0x000) +#define AVP_CACHE_LOCK (0x004) +#define AVP_CACHE_SIZE (0x00C) +#define AVP_CACHE_LFSR (0x010) +#define AVP_CACHE_TAG_STATUS (0x014) +#define AVP_CACHE_CLKEN_OVERRIDE (0x018) +#define AVP_CACHE_MAINT_0 (0x020) +#define AVP_CACHE_MAINT_1 (0x024) +#define AVP_CACHE_MAINT_2 (0x028) +#define AVP_CACHE_INT_MASK (0x040) +#define AVP_CACHE_INT_CLEAR (0x044) +#define AVP_CACHE_INT_RAW_EVENT (0x048) +#define AVP_CACHE_INT_STATUS (0x04C) +#define AVP_CACHE_RB_CFG (0x080) +#define AVP_CACHE_WB_CFG (0x084) +#define AVP_CACHE_MMU_FALLBACK_ENTRY (0x0A0) +#define AVP_CACHE_MMU_SHADOW_COPY_MASK_0 (0x0A4) +#define AVP_CACHE_MMU_CFG (0x0AC) +#define AVP_CACHE_MMU_CMD (0x0B0) +#define AVP_CACHE_MMU_ABORT_STAT (0x0B4) +#define AVP_CACHE_MMU_ABORT_ADDR (0x0B8) +#define AVP_CACHE_MMU_ACTIVE_ENTRIES (0x0BC) + +#define AVP_CACHE_MMU_SHADOW_ENTRY_0_MIN_ADDR (0x400) +#define AVP_CACHE_MMU_SHADOW_ENTRY_0_MAX_ADDR (0x404) +#define AVP_CACHE_MMU_SHADOW_ENTRY_0_CFG (0x408) + +#define AVP_CACHE_MMU_SHADOW_ENTRY_1_MIN_ADDR (0x410) +#define AVP_CACHE_MMU_SHADOW_ENTRY_1_MAX_ADDR (0x414) +#define AVP_CACHE_MMU_SHADOW_ENTRY_1_CFG (0x418) + +#define AVP_CACHE_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (AVP_CACHE, NAME) +#define AVP_CACHE_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (AVP_CACHE, NAME, VALUE) +#define AVP_CACHE_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (AVP_CACHE, NAME, ENUM) +#define AVP_CACHE_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(AVP_CACHE, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + +#define DEFINE_AVP_CACHE_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (AVP_CACHE, NAME, __OFFSET__, __WIDTH__) +#define DEFINE_AVP_CACHE_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (AVP_CACHE, NAME, __OFFSET__, ZERO, ONE) +#define DEFINE_AVP_CACHE_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (AVP_CACHE, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) +#define DEFINE_AVP_CACHE_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(AVP_CACHE, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) +#define DEFINE_AVP_CACHE_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (AVP_CACHE, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + +DEFINE_AVP_CACHE_REG_BIT_ENUM(CONFIG_ENABLE_CACHE, 0, FALSE, TRUE); +DEFINE_AVP_CACHE_REG_BIT_ENUM(CONFIG_FORCE_WRITE_THROUGH, 3, FALSE, TRUE); +DEFINE_AVP_CACHE_REG_TWO_BIT_ENUM(CONFIG_MMU_TAG_MODE, 8, PARALLEL, TAG_FIRST, MMU_FIRST, RSVD3); +DEFINE_AVP_CACHE_REG_BIT_ENUM(CONFIG_DISABLE_WB, 10, FALSE, TRUE); +DEFINE_AVP_CACHE_REG_BIT_ENUM(CONFIG_DISABLE_RB, 11, FALSE, TRUE); +DEFINE_AVP_CACHE_REG_BIT_ENUM(CONFIG_TAG_CHECK_ABORT_ON_ERROR, 14, FALSE, TRUE); + +DEFINE_AVP_CACHE_REG(MAINT_2_OPCODE, 0, 8); +DEFINE_AVP_CACHE_REG(MAINT_2_WAY_BITMAP, 8, 4); + +enum AVP_CACHE_MAINT_OPCODE : u32 { + AVP_CACHE_MAINT_OPCODE_NOP = 0, + + AVP_CACHE_MAINT_OPCODE_CLEAN_PHY = 1, + AVP_CACHE_MAINT_OPCODE_INVALID_PHY = 2, + AVP_CACHE_MAINT_OPCODE_CLEAN_INVALID_PHY = 3, + + AVP_CACHE_MAINT_OPCODE_CLEAN_LINE = 9, + AVP_CACHE_MAINT_OPCODE_INVALID_LINE = 10, + AVP_CACHE_MAINT_OPCODE_CLEAN_INVALID_LINE = 11, + + AVP_CACHE_MAINT_OPCODE_CLEAN_WAY = 17, + AVP_CACHE_MAINT_OPCODE_INVALID_WAY = 18, + AVP_CACHE_MAINT_OPCODE_CLEAN_INVALID_WAY = 19, +}; + +DEFINE_AVP_CACHE_REG_BIT_ENUM(INT_CLEAR_MAINTENANCE_DONE, 0, FALSE, TRUE); + +DEFINE_AVP_CACHE_REG_BIT_ENUM(INT_RAW_EVENT_MAINTENANCE_DONE, 0, FALSE, TRUE); + +DEFINE_AVP_CACHE_REG_BIT_ENUM(INT_STATUS_MAINTENANCE_DONE, 0, FALSE, TRUE); + +DEFINE_AVP_CACHE_REG_BIT_ENUM(MMU_FALLBACK_ENTRY_CACHED, 0, DISABLE, ENABLE); +DEFINE_AVP_CACHE_REG_BIT_ENUM(MMU_FALLBACK_ENTRY_EXE_ENA, 1, DISABLE, ENABLE); +DEFINE_AVP_CACHE_REG_BIT_ENUM(MMU_FALLBACK_ENTRY_RD_ENA, 2, DISABLE, ENABLE); +DEFINE_AVP_CACHE_REG_BIT_ENUM(MMU_FALLBACK_ENTRY_WR_ENA, 3, DISABLE, ENABLE); + +DEFINE_AVP_CACHE_REG_BIT_ENUM(MMU_CFG_BLOCK_MAIN_ENTRY_WR, 0, DISABLE, ENABLE); +DEFINE_AVP_CACHE_REG_BIT_ENUM(MMU_CFG_SEQ_ENA, 1, DISABLE, ENABLE); +DEFINE_AVP_CACHE_REG_BIT_ENUM(MMU_CFG_TLB_ENA, 2, DISABLE, ENABLE); +DEFINE_AVP_CACHE_REG_BIT_ENUM(MMU_CFG_SEQ_CHECK_ALL_ENTRIES, 3, DISABLE, ENABLE); +DEFINE_AVP_CACHE_REG_BIT_ENUM(MMU_CFG_ABORT_MODE, 4, STORE_FIRST, STORE_LAST); +DEFINE_AVP_CACHE_REG_BIT_ENUM(MMU_CFG_CLR_ABORT, 5, NOP, CLEAN); + +DEFINE_AVP_CACHE_REG_TWO_BIT_ENUM(MMU_CMD_CMD, 0, NOP, INIT, COPY_SHADOW, RSVD3); + +DEFINE_AVP_CACHE_REG_BIT_ENUM(SHADOW_ENTRY_CFG_CACHED, 0, DISABLE, ENABLE); +DEFINE_AVP_CACHE_REG_BIT_ENUM(SHADOW_ENTRY_CFG_EXE_ENA, 1, DISABLE, ENABLE); +DEFINE_AVP_CACHE_REG_BIT_ENUM(SHADOW_ENTRY_CFG_RD_ENA, 2, DISABLE, ENABLE); +DEFINE_AVP_CACHE_REG_BIT_ENUM(SHADOW_ENTRY_CFG_WR_ENA, 3, DISABLE, ENABLE); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_clkrst.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_clkrst.hpp new file mode 100644 index 00000000..c5e6719d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_clkrst.hpp @@ -0,0 +1,499 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +/* Clock source enums. */ +#define CLK_RST_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (CLK_RST_CONTROLLER, NAME) +#define CLK_RST_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (CLK_RST_CONTROLLER, NAME, VALUE) +#define CLK_RST_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (CLK_RST_CONTROLLER, NAME, ENUM) +#define CLK_RST_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(CLK_RST_CONTROLLER, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + +#define DEFINE_CLK_RST_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (CLK_RST_CONTROLLER, NAME, __OFFSET__, __WIDTH__) +#define DEFINE_CLK_RST_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (CLK_RST_CONTROLLER, NAME, __OFFSET__, ZERO, ONE) +#define DEFINE_CLK_RST_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (CLK_RST_CONTROLLER, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) +#define DEFINE_CLK_RST_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(CLK_RST_CONTROLLER, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) +#define DEFINE_CLK_RST_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (CLK_RST_CONTROLLER, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + + +#define CLK_RST_CONTROLLER_RST_SOURCE (0x000) + +#define CLK_RST_CONTROLLER_CCLK_BURST_POLICY (0x020) +#define CLK_RST_CONTROLLER_SUPER_CCLK_DIVIDER (0x024) +#define CLK_RST_CONTROLLER_SCLK_BURST_POLICY (0x028) +#define CLK_RST_CONTROLLER_SUPER_SCLK_DIVIDER (0x02C) +#define CLK_RST_CONTROLLER_CLK_SYSTEM_RATE (0x030) +#define CLK_RST_CONTROLLER_MISC_CLK_ENB (0x048) +#define CLK_RST_CONTROLLER_OSC_CTRL (0x050) +#define CLK_RST_CONTROLLER_PLLC_BASE (0x080) +#define CLK_RST_CONTROLLER_PLLC_OUT (0x084) +#define CLK_RST_CONTROLLER_PLLC_MISC (0x088) +#define CLK_RST_CONTROLLER_PLLC_MISC1 (0x08C) +#define CLK_RST_CONTROLLER_PLLM_BASE (0x090) +#define CLK_RST_CONTROLLER_PLLM_MISC1 (0x098) +#define CLK_RST_CONTROLLER_PLLM_MISC2 (0x09C) +#define CLK_RST_CONTROLLER_PLLD_BASE (0x0D0) +#define CLK_RST_CONTROLLER_PLLD_MISC1 (0x0D8) +#define CLK_RST_CONTROLLER_PLLD_MISC (0x0DC) +#define CLK_RST_CONTROLLER_PLLX_BASE (0x0E0) +#define CLK_RST_CONTROLLER_PLLX_MISC (0x0E4) +#define CLK_RST_CONTROLLER_CCLKG_BURST_POLICY (0x368) +#define CLK_RST_CONTROLLER_SUPER_CCLKG_DIVIDER (0x36C) +#define CLK_RST_CONTROLLER_CCLKLP_BURST_POLICY (0x370) +#define CLK_RST_CONTROLLER_SUPER_CCLKLP_DIVIDER (0x374) +#define CLK_RST_CONTROLLER_CPU_SOFTRST_CTRL2 (0x388) +#define CLK_RST_CONTROLLER_PLLX_MISC1 (0x510) +#define CLK_RST_CONTROLLER_PLLX_MISC2 (0x514) +#define CLK_RST_CONTROLLER_PLLX_MISC3 (0x518) +#define CLK_RST_CONTROLLER_SPARE_REG0 (0x55C) +#define CLK_RST_CONTROLLER_PLLC4_BASE (0x5A4) +#define CLK_RST_CONTROLLER_PLLC_MISC2 (0x5D0) +#define CLK_RST_CONTROLLER_PLLMB_BASE (0x5E8) +#define CLK_RST_CONTROLLER_PLLMB_MISC1 (0x5EC) + +/* Mariko. */ +#define CLK_RST_CONTROLLER_PLLM_SS_CFG (0x774) +#define CLK_RST_CONTROLLER_PLLM_SS_CTRL1 (0x778) +#define CLK_RST_CONTROLLER_PLLM_SS_CTRL2 (0x77C) +#define CLK_RST_CONTROLLER_PLLMB_SS_CFG (0x780) +#define CLK_RST_CONTROLLER_PLLMB_SS_CTRL1 (0x784) +#define CLK_RST_CONTROLLER_PLLMB_SS_CTRL2 (0x788) + +#define CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRA (0x0F8) +#define CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRB (0x0FC) +#define CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRC (0x3A0) +#define CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRD (0x3A4) +#define CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRE (0x554) + +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(SCLK_BURST_POLICY_SWAKEUP_IDLE_SOURCE, 0, CLKM, PLLC_OUT1, PLLC4_OUT3PLLP_OUT4, PLLP_OUT0, PLLP_OUT2, PLLC4_OUT1PLLC_OUT0, CLK_SCLKS, PLLC4_OUT2); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(SCLK_BURST_POLICY_SWAKEUP_RUN_SOURCE, 4, CLKM, PLLC_OUT1, PLLC4_OUT3PLLP_OUT4, PLLP_OUT0, PLLP_OUT2, PLLC4_OUT1PLLC_OUT0, CLK_SCLKS, PLLC4_OUT2); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(SCLK_BURST_POLICY_SWAKEUP_IRQ_SOURCE, 8, CLKM, PLLC_OUT1, PLLC4_OUT3PLLP_OUT4, PLLP_OUT0, PLLP_OUT2, PLLC4_OUT1PLLC_OUT0, CLK_SCLKS, PLLC4_OUT2); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(SCLK_BURST_POLICY_SWAKEUP_FIQ_SOURCE, 12, CLKM, PLLC_OUT1, PLLC4_OUT3PLLP_OUT4, PLLP_OUT0, PLLP_OUT2, PLLC4_OUT1PLLC_OUT0, CLK_SCLKS, PLLC4_OUT2); +DEFINE_CLK_RST_REG_BIT_ENUM(SCLK_BURST_POLICY_CPU_AUTO_SWAKEUP_FROM_IRQ, 24, NOP, BURST); +DEFINE_CLK_RST_REG_BIT_ENUM(SCLK_BURST_POLICY_COP_AUTO_SWAKEUP_FROM_IRQ, 25, NOP, BURST); +DEFINE_CLK_RST_REG_BIT_ENUM(SCLK_BURST_POLICY_CPU_AUTO_SWAKEUP_FROM_FIQ, 26, NOP, BURST); +DEFINE_CLK_RST_REG_BIT_ENUM(SCLK_BURST_POLICY_COP_AUTO_SWAKEUP_FROM_FIQ, 27, NOP, BURST); +DEFINE_CLK_RST_REG_FOUR_BIT_ENUM(SCLK_BURST_POLICY_SYS_STATE, 28, STDBY, IDLE, RUN, RSVD3, IRQ, RSVD5, RSVD6, RSVD7, FIQ, RSVD9, RSVD10, RSVD11, RSVD12, RSVD13, RSVD14, RSVD15); + +DEFINE_CLK_RST_REG(SUPER_SCLK_DIVIDER_SUPER_SDIV_DIVISOR, 0, 8); +DEFINE_CLK_RST_REG(SUPER_SCLK_DIVIDER_SUPER_SDIV_DIVIDEND, 8, 8); +DEFINE_CLK_RST_REG_BIT_ENUM(SUPER_SCLK_DIVIDER_SUPER_SDIV_DIS_FROM_CPU_IRQ, 24, NOP, DISABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(SUPER_SCLK_DIVIDER_SUPER_SDIV_DIS_FROM_COP_IRQ, 25, NOP, DISABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(SUPER_SCLK_DIVIDER_SUPER_SDIV_DIS_FROM_CPU_FIQ, 26, NOP, DISABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(SUPER_SCLK_DIVIDER_SUPER_SDIV_DIS_FROM_COP_FIQ, 27, NOP, DISABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(SUPER_SCLK_DIVIDER_SUPER_SDIV_ENB, 31, DISABLE, ENABLE); + +DEFINE_CLK_RST_REG(CLK_SYSTEM_RATE_APB_RATE, 0, 2); +DEFINE_CLK_RST_REG(CLK_SYSTEM_RATE_PCLK_DIS, 3, 1); +DEFINE_CLK_RST_REG(CLK_SYSTEM_RATE_AHB_RATE, 4, 2); +DEFINE_CLK_RST_REG(CLK_SYSTEM_RATE_HCLK_DIS, 7, 1); + +DEFINE_CLK_RST_REG(MISC_CLK_ENB_CFG_ALL_VISIBLE, 28, 1); + +DEFINE_CLK_RST_REG_BIT_ENUM(OSC_CTRL_XOE, 0, DISABLE, ENABLE); +DEFINE_CLK_RST_REG(OSC_CTRL_XOFS, 4, 6); +DEFINE_CLK_RST_REG_FOUR_BIT_ENUM(OSC_CTRL_OSC_FREQ, 28, OSC13, OSC16P8, RSVD2, RSVD3, OSC19P2, OSC38P4, RSVD6, RSVD7, OSC12, OSC48, RSVD10, RSVD11, OSC26, RSVD13, RSVD14, RSVD15); + +DEFINE_CLK_RST_REG(PLLC_BASE_PLLC_DIVM, 0, 8); +DEFINE_CLK_RST_REG(PLLC_BASE_PLLC_DIVN, 10, 8); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLC_BASE_PLLC_LOCK, 27, NOT_LOCK, LOCK); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLC_BASE_PLLC_REF_DIS, 29, REF_ENABLE, REF_DISABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLC_BASE_PLLC_ENABLE, 30, DISABLE, ENABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLC_BASE_PLLC_BYPASS, 31, DISABLE, ENABLE); + +DEFINE_CLK_RST_REG_BIT_ENUM(PLLC_OUT_PLLC_OUT1_RSTN, 0, RESET_ENABLE, RESET_DISABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLC_OUT_PLLC_OUT1_CLKEN, 1, DISABLE, ENABLE); +DEFINE_CLK_RST_REG(PLLC_OUT_PLLC_OUT1_RATIO, 8, 8); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLC_OUT_PLLC_OUT1_DIV_BYP, 16, DISABLE, ENABLE); + +DEFINE_CLK_RST_REG(PLLM_BASE_PLLM_DIVM, 0, 8); +DEFINE_CLK_RST_REG(PLLM_BASE_PLLM_DIVN, 8, 8); +DEFINE_CLK_RST_REG(PLLM_BASE_PLLM_DIVP, 20, 5); +DEFINE_CLK_RST_REG(PLLM_BASE_PLLM_DIVP_B01, 20, 1); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLM_BASE_PLLM_LOCK, 27, NOT_LOCK, LOCK); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLM_BASE_PLLM_REF_DIS, 29, REF_ENABLE, REF_DISABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLM_BASE_PLLM_ENABLE, 30, DISABLE, ENABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLM_BASE_PLLM_BYPASSPLL, 31, DISABLE, ENABLE); + +DEFINE_CLK_RST_REG_BIT_ENUM(PLLM_MISC2_PLLM_EN_LCKDET, 4, DISABLE, ENABLE); + +DEFINE_CLK_RST_REG_BIT_ENUM(PLLD_BASE_CSI_CLK_SRC, 23, BRICK, PLL_D); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLD_BASE_PLLD_REF_DIS, 29, REF_ENABLE, REF_DISABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLD_BASE_PLLD_ENABLE, 30, DISABLE, ENABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLD_BASE_PLLD_BYPASS, 31, DISABLE, ENABLE); + +DEFINE_CLK_RST_REG_BIT_ENUM(PLLX_BASE_PLLX_LOCK, 27, NOT_LOCK, LOCK); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLX_BASE_PLLX_REF_DIS, 29, REF_ENABLE, REF_DISABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLX_BASE_PLLX_ENABLE, 30, DISABLE, ENABLE); + +DEFINE_CLK_RST_REG_BIT_ENUM(PLLX_MISC_PLLX_LOCK_ENABLE, 18, DISABLE, ENABLE); + +DEFINE_CLK_RST_REG(SUPER_CCLK_DIVIDER_SUPER_CDIV_DIVISOR, 0, 8); +DEFINE_CLK_RST_REG(SUPER_CCLK_DIVIDER_SUPER_CDIV_DIVIDEND, 8, 8); +DEFINE_CLK_RST_REG_BIT_ENUM(SUPER_CCLK_DIVIDER_SUPER_CDIV_DIS_FROM_CPU_IRQ, 24, NO_IMPACT, DISABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(SUPER_CCLK_DIVIDER_SUPER_CDIV_DIS_FROM_COP_IRQ, 25, NO_IMPACT, DISABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(SUPER_CCLK_DIVIDER_SUPER_CDIV_DIS_FROM_CPU_FIQ, 26, NO_IMPACT, DISABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(SUPER_CCLK_DIVIDER_SUPER_CDIV_DIS_FROM_COP_FIQ, 27, NO_IMPACT, DISABLE); + +DEFINE_CLK_RST_REG_BIT_ENUM(SUPER_CCLK_DIVIDER_SUPER_CDIV_ENB, 31, DISABLE, ENABLE); + +DEFINE_CLK_RST_REG_BIT_ENUM(SUPER_CCLKG_DIVIDER_SUPER_CDIV_ENB, 31, DISABLE, ENABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(SUPER_CCLKLP_DIVIDER_SUPER_CDIV_ENB, 31, DISABLE, ENABLE); + +DEFINE_CLK_RST_REG_FOUR_BIT_ENUM(CCLK_BURST_POLICY_CWAKEUP_IDLE_SOURCE, 0, CLKM, RSVD1, CLKS, RSVD3, PLLP_OUT0, PLLP_OUT4, RSVD6, RSVD7, PLLX_OUT0_LJ, DVFS_CPU_CLK, RSVD10, RSVD11, RSVD12, RSVD13, PLLX_OUT0, DVFS_CPU_CLK_LJ); +DEFINE_CLK_RST_REG_FOUR_BIT_ENUM(CCLK_BURST_POLICY_CWAKEUP_RUN_SOURCE, 4, CLKM, RSVD1, CLKS, RSVD3, PLLP_OUT0, PLLP_OUT4, RSVD6, RSVD7, PLLX_OUT0_LJ, DVFS_CPU_CLK, RSVD10, RSVD11, RSVD12, RSVD13, PLLX_OUT0, DVFS_CPU_CLK_LJ); +DEFINE_CLK_RST_REG_FOUR_BIT_ENUM(CCLK_BURST_POLICY_CWAKEUP_IRQ_SOURCE, 8, CLKM, RSVD1, CLKS, RSVD3, PLLP_OUT0, PLLP_OUT4, RSVD6, RSVD7, PLLX_OUT0_LJ, DVFS_CPU_CLK, RSVD10, RSVD11, RSVD12, RSVD13, PLLX_OUT0, DVFS_CPU_CLK_LJ); +DEFINE_CLK_RST_REG_FOUR_BIT_ENUM(CCLK_BURST_POLICY_CWAKEUP_FIQ_SOURCE, 12, CLKM, RSVD1, CLKS, RSVD3, PLLP_OUT0, PLLP_OUT4, RSVD6, RSVD7, PLLX_OUT0_LJ, DVFS_CPU_CLK, RSVD10, RSVD11, RSVD12, RSVD13, PLLX_OUT0, DVFS_CPU_CLK_LJ); +DEFINE_CLK_RST_REG_FOUR_BIT_ENUM(CCLK_BURST_POLICY_CPU_STATE, 28, STDBY, IDLE, RUN, RSVD3, IRQ, RSVD5, RSVD6, RSVD7, FIQ, RSVD9, RSVD10, RSVD11, RSVD12, RSVD13, RSVD14, RSVD15); + +DEFINE_CLK_RST_REG(CPU_SOFTRST_CTRL2_CAR2PMC_CPU_ACK_WIDTH, 0, 12); + +DEFINE_CLK_RST_REG(PLLX_MISC3_PLLX_IDDQ, 3, 1); + +DEFINE_CLK_RST_REG_TWO_BIT_ENUM(SPARE_REG0_CLK_M_DIVISOR, 2, CLK_M_DIVISOR1, CLK_M_DIVISOR2, CLK_M_DIVISOR3, CLK_M_DIVISOR4); + +DEFINE_CLK_RST_REG_BIT_ENUM(PLLC4_BASE_PLLC4_IDDQ, 18, OFF, ON); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLC4_BASE_PLLC4_LOCK, 27, NOT_LOCK, LOCK_FEQ_AND_PHASE); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLC4_BASE_PLLC4_ENABLE, 30, DISABLE, ENABLE); + +DEFINE_CLK_RST_REG(PLLMB_BASE_PLLMB_DIVM, 0, 8); +DEFINE_CLK_RST_REG(PLLMB_BASE_PLLMB_DIVN, 8, 8); +DEFINE_CLK_RST_REG(PLLMB_BASE_PLLMB_DIVP, 20, 5); +DEFINE_CLK_RST_REG(PLLMB_BASE_PLLMB_DIVP_B01, 20, 1); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLMB_BASE_PLLMB_LOCK, 27, NOT_LOCK, LOCK); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLMB_BASE_PLLMB_ENABLE, 30, DISABLE, ENABLE); + +/* RST_DEVICES */ +#define CLK_RST_CONTROLLER_RST_DEVICES_L (0x004) +#define CLK_RST_CONTROLLER_RST_DEVICES_H (0x008) +#define CLK_RST_CONTROLLER_RST_DEVICES_U (0x00C) +#define CLK_RST_CONTROLLER_RST_DEVICES_X (0x28C) +#define CLK_RST_CONTROLLER_RST_DEVICES_Y (0x2A4) +#define CLK_RST_CONTROLLER_RST_DEVICES_V (0x358) +#define CLK_RST_CONTROLLER_RST_DEVICES_W (0x35C) + +/* CLK_OUT_ENB */ +#define CLK_RST_CONTROLLER_CLK_OUT_ENB_L (0x010) +#define CLK_RST_CONTROLLER_CLK_OUT_ENB_H (0x014) +#define CLK_RST_CONTROLLER_CLK_OUT_ENB_U (0x018) +#define CLK_RST_CONTROLLER_CLK_OUT_ENB_X (0x280) +#define CLK_RST_CONTROLLER_CLK_OUT_ENB_Y (0x298) +#define CLK_RST_CONTROLLER_CLK_OUT_ENB_V (0x360) +#define CLK_RST_CONTROLLER_CLK_OUT_ENB_W (0x364) + +/* CLK_SOURCE */ +#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_CSITE (0x1D4) +#define CLK_RST_CONTROLLER_CLK_SOURCE_TSEC (0x1F4) +#define CLK_RST_CONTROLLER_CLK_SOURCE_MSELECT (0x3B4) +#define CLK_RST_CONTROLLER_CLK_SOURCE_I2C4 (0x3C4) +#define CLK_RST_CONTROLLER_CLK_SOURCE_ACTMON (0x3E8) +#define CLK_RST_CONTROLLER_CLK_SOURCE_SOR1 (0x410) +#define CLK_RST_CONTROLLER_CLK_SOURCE_SE (0x42C) +#define CLK_RST_CONTROLLER_CLK_SOURCE_DSIA_LP (0x620) +#define CLK_RST_CONTROLLER_CLK_SOURCE_DVFS_REF (0x62C) +#define CLK_RST_CONTROLLER_CLK_SOURCE_DVFS_SOC (0x630) +#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_CLK_SOURCE_EMC_SAFE (0x724) + +/* RST_DEV_*_SET */ +#define CLK_RST_CONTROLLER_RST_DEV_L_SET (0x300) +#define CLK_RST_CONTROLLER_RST_DEV_H_SET (0x308) +#define CLK_RST_CONTROLLER_RST_DEV_U_SET (0x310) +#define CLK_RST_CONTROLLER_RST_DEV_V_SET (0x430) +#define CLK_RST_CONTROLLER_RST_DEV_W_SET (0x438) +#define CLK_RST_CONTROLLER_RST_DEV_X_SET (0x290) +#define CLK_RST_CONTROLLER_RST_DEV_Y_SET (0x2A8) + +/* RST_DEV_*_CLR */ +#define CLK_RST_CONTROLLER_RST_DEV_L_CLR (0x304) +#define CLK_RST_CONTROLLER_RST_DEV_H_CLR (0x30C) +#define CLK_RST_CONTROLLER_RST_DEV_U_CLR (0x314) +#define CLK_RST_CONTROLLER_RST_DEV_V_CLR (0x434) +#define CLK_RST_CONTROLLER_RST_DEV_W_CLR (0x43C) +#define CLK_RST_CONTROLLER_RST_DEV_X_CLR (0x294) +#define CLK_RST_CONTROLLER_RST_DEV_Y_CLR (0x2AC) + +/* CLK_ENB_*_SET */ +#define CLK_RST_CONTROLLER_CLK_ENB_L_SET (0x320) +#define CLK_RST_CONTROLLER_CLK_ENB_H_SET (0x328) +#define CLK_RST_CONTROLLER_CLK_ENB_U_SET (0x330) +#define CLK_RST_CONTROLLER_CLK_ENB_V_SET (0x440) +#define CLK_RST_CONTROLLER_CLK_ENB_W_SET (0x448) +#define CLK_RST_CONTROLLER_CLK_ENB_X_SET (0x284) +#define CLK_RST_CONTROLLER_CLK_ENB_Y_SET (0x29C) + +/* CLK_ENB_*_CLR */ +#define CLK_RST_CONTROLLER_CLK_ENB_L_CLR (0x324) +#define CLK_RST_CONTROLLER_CLK_ENB_H_CLR (0x32C) +#define CLK_RST_CONTROLLER_CLK_ENB_U_CLR (0x334) +#define CLK_RST_CONTROLLER_CLK_ENB_X_CLR (0x288) +#define CLK_RST_CONTROLLER_CLK_ENB_Y_CLR (0x2A0) +#define CLK_RST_CONTROLLER_CLK_ENB_V_CLR (0x444) +#define CLK_RST_CONTROLLER_CLK_ENB_W_CLR (0x44C) + +/* RST_*_INDEX */ +#define CLK_RST_CONTROLLER_RST_I2C1_INDEX (0x0C) +#define CLK_RST_CONTROLLER_RST_I2C2_INDEX (0x16) +#define CLK_RST_CONTROLLER_RST_I2C3_INDEX (0x03) +#define CLK_RST_CONTROLLER_RST_I2C4_INDEX (0x07) +#define CLK_RST_CONTROLLER_RST_I2C5_INDEX (0x0F) +#define CLK_RST_CONTROLLER_RST_I2C6_INDEX (0x06) + +#define CLK_RST_CONTROLLER_RST_PWM_INDEX (0x11) + +#define CLK_RST_CONTROLLER_RST_UARTA_INDEX (0x06) +#define CLK_RST_CONTROLLER_RST_UARTB_INDEX (0x07) +#define CLK_RST_CONTROLLER_RST_UARTC_INDEX (0x17) + +#define CLK_RST_CONTROLLER_RST_ACTMON_INDEX (0x17) + +/* CLK_ENB_*_INDEX */ +#define CLK_RST_CONTROLLER_CLK_ENB_I2C1_INDEX (0x0C) +#define CLK_RST_CONTROLLER_CLK_ENB_I2C2_INDEX (0x16) +#define CLK_RST_CONTROLLER_CLK_ENB_I2C3_INDEX (0x03) +#define CLK_RST_CONTROLLER_CLK_ENB_I2C4_INDEX (0x07) +#define CLK_RST_CONTROLLER_CLK_ENB_I2C5_INDEX (0x0F) +#define CLK_RST_CONTROLLER_CLK_ENB_I2C6_INDEX (0x06) + +#define CLK_RST_CONTROLLER_CLK_ENB_PWM_INDEX (0x11) + +#define CLK_RST_CONTROLLER_CLK_ENB_UARTA_INDEX (0x06) +#define CLK_RST_CONTROLLER_CLK_ENB_UARTB_INDEX (0x07) +#define CLK_RST_CONTROLLER_CLK_ENB_UARTC_INDEX (0x17) + +#define CLK_RST_CONTROLLER_CLK_ENB_ACTMON_INDEX (0x17) + +#define CLK_RST_CONTROLLER_CLK_ENB_DVFS_INDEX (0x1B) + +#define CLK_RST_CONTROLLER_CLK_ENB_TZRAM_INDEX (0x1E) + +#define CLK_RST_CONTROLLER_CLK_ENB_CACHE2_INDEX (0x1F) +#define CLK_RST_CONTROLLER_CLK_ENB_CRAM2_INDEX (0x18) + +#define CLK_RST_CONTROLLER_CLK_ENB_SE_INDEX (0x1F) + +#define CLK_RST_CONTROLLER_CLK_ENB_CSITE_INDEX (0x09) + +#define CLK_RST_CONTROLLER_CLK_ENB_HOST1X_INDEX (0x1C) +#define CLK_RST_CONTROLLER_CLK_ENB_TSEC_INDEX (0x13) +#define CLK_RST_CONTROLLER_CLK_ENB_SOR0_INDEX (0x16) +#define CLK_RST_CONTROLLER_CLK_ENB_SOR1_INDEX (0x17) +#define CLK_RST_CONTROLLER_CLK_ENB_SOR_SAFE_INDEX (0x1E) +#define CLK_RST_CONTROLLER_CLK_ENB_KFUSE_INDEX (0x08) + +/* RST_CPUG_CMPLX_* */ +#define CLK_RST_CONTROLLER_RST_CPUG_CMPLX_SET (0x450) +#define CLK_RST_CONTROLLER_RST_CPUG_CMPLX_CLR (0x454) + +DEFINE_CLK_RST_REG_BIT_ENUM(LVL2_CLK_GATE_OVRD_ARC_CLK_OVR_ON, 19, OFF, ON); +DEFINE_CLK_RST_REG_BIT_ENUM(LVL2_CLK_GATE_OVRD_TSEC_CLK_OVR_ON, 20, OFF, ON); +DEFINE_CLK_RST_REG_BIT_ENUM(LVL2_CLK_GATE_OVRD_TSECB_CLK_OVR_ON, 21, OFF, ON); +DEFINE_CLK_RST_REG_BIT_ENUM(LVL2_CLK_GATE_OVRD_ISPB_CLK_OVR_ON, 22, OFF, ON); +DEFINE_CLK_RST_REG_BIT_ENUM(LVL2_CLK_GATE_OVRD_TZRAM_CLK_OVR_ON, 23, OFF, ON); +DEFINE_CLK_RST_REG_BIT_ENUM(LVL2_CLK_GATE_OVRD_QSPI_CLK_OVR_ON, 24, OFF, ON); +DEFINE_CLK_RST_REG_BIT_ENUM(LVL2_CLK_GATE_OVRD_A9AVP_CLK_OVR_ON, 26, OFF, ON); +DEFINE_CLK_RST_REG_BIT_ENUM(LVL2_CLK_GATE_OVRD_MPCORE_MSELECT_CLK_OVR_ON, 27, OFF, ON); +DEFINE_CLK_RST_REG_BIT_ENUM(LVL2_CLK_GATE_OVRD_SDMMC1_LEGACY_TMCLK_OVR_ON, 28, OFF, ON); +DEFINE_CLK_RST_REG_BIT_ENUM(LVL2_CLK_GATE_OVRD_SDMMC2_LEGACY_TMCLK_OVR_ON, 29, OFF, ON); +DEFINE_CLK_RST_REG_BIT_ENUM(LVL2_CLK_GATE_OVRD_SDMMC3_LEGACY_TMCLK_OVR_ON, 30, OFF, ON); +DEFINE_CLK_RST_REG_BIT_ENUM(LVL2_CLK_GATE_OVRD_SDMMC4_LEGACY_TMCLK_OVR_ON, 31, OFF, ON); + +DEFINE_CLK_RST_REG(CLK_SOURCE_CLK_DIVISOR, 0, 8); +DEFINE_CLK_RST_REG(CLK_SOURCE_CLK_SOURCE, 29, 3); + +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_I2C1_I2C1_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT0, RESERVED4, PLLC4_OUT1, CLK_M, PLLC4_OUT2); +DEFINE_CLK_RST_REG(CLK_SOURCE_I2C1_I2C1_CLK_DIVISOR, 0, 8); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_I2C5_I2C5_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT0, RESERVED4, PLLC4_OUT1, CLK_M, PLLC4_OUT2); +DEFINE_CLK_RST_REG(CLK_SOURCE_I2C5_I2C5_CLK_DIVISOR, 0, 8); + +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SDMMC1_SDMMC1_CLK_SRC, 29, PLLP_OUT0, PLLA_OUT, PLLC_OUT0, PLLC4_OUT2, PLLM_OUT0, PLLE_OUT0, CLK_M, PLLC4_OUT0); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SDMMC2_SDMMC2_CLK_SRC, 29, PLLP_OUT0, PLLC4_OUT2_LJ, PLLC4_OUT0_LJ, PLLC4_OUT2, PLLC4_OUT1, PLLC4_OUT1_LJ, CLK_M, PLLC4_OUT0); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SDMMC4_SDMMC4_CLK_SRC, 29, PLLP_OUT0, PLLC4_OUT2_LJ, PLLC4_OUT0_LJ, PLLC4_OUT2, PLLC4_OUT1, PLLC4_OUT1_LJ, CLK_M, PLLC4_OUT0); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SDMMC3_SDMMC3_CLK_SRC, 29, PLLP_OUT0, PLLA_OUT, PLLC_OUT0, PLLC4_OUT2, PLLC4_OUT1, PLLE_OUT0, CLK_M, PLLC4_OUT0); + +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SDMMCX_SDMMCX_CLK_SRC, 29, PLLP_OUT0, _RSVD1_, _RSVD2_, PLLC4_OUT2, _RSVD4_, _RSVD5_, CLK_M, PLLC4_OUT0); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SDMMC24_SDMMC24_CLK_SRC, 29, PLLP_OUT0, PLLC4_OUT2_LJ, PLLC4_OUT0_LJ, PLLC4_OUT2, PLLC4_OUT1, PLLC4_OUT1_LJ, CLK_M, PLLC4_OUT0); + +DEFINE_CLK_RST_REG(CLK_SOURCE_SDMMC1_SDMMC1_CLK_DIVISOR, 0, 8); +DEFINE_CLK_RST_REG(CLK_SOURCE_SDMMC2_SDMMC2_CLK_DIVISOR, 0, 8); +DEFINE_CLK_RST_REG(CLK_SOURCE_SDMMC4_SDMMC4_CLK_DIVISOR, 0, 8); +DEFINE_CLK_RST_REG(CLK_SOURCE_SDMMC3_SDMMC3_CLK_DIVISOR, 0, 8); + +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_UARTA_UARTA_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT0, RESERVED4, PLLC4_OUT1, CLK_M, PLLC4_OUT2); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_UARTB_UARTB_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT0, RESERVED4, PLLC4_OUT1, CLK_M, PLLC4_OUT2); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_UARTC_UARTC_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT0, RESERVED4, PLLC4_OUT1, CLK_M, PLLC4_OUT2); + +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_VI_VI_CLK_SRC, 29, RESERVED0, PLLC2_OUT0, PLLC_OUT, PLLC3_OUT0, PLLP_OUT0, CLK_M, PLLA1_OUT0, PLLC4_OUT0); + +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_HOST1X_HOST1X_CLK_SRC, 29, PLLC4_OUT1, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT2, PLLP_OUT0, CLK_M, PLLA_OUT0, PLLC4_OUT0); + +DEFINE_CLK_RST_REG(CLK_SOURCE_EMC_EMC_2X_CLK_DIVISOR, 0, 8); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_EMC_EMC_2X_CLK_SRC, 29, PLLM_OUT0, PLLC_OUT0, PLLP_OUT0, CLK_M, PLLM_UD, PLLMB_UD, PLLMB_OUT0, PLLP_UD); + +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_NVENC_NVENC_CLK_SRC, 29, RESERVED0, PLLC2_OUT0, PLLC_OUT0, PLLC3_OUT0, PLLP_OUT0, RESERVED5, PLLA1_OUT0, CLK_M); + +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_CSITE_CSITE_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC3_OUT0, PLLREFE_OUT1, PLLA1_OUT0, CLK_M, PLLC4_OUT0); + +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_TSEC_TSEC_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC3_OUT0, RESERVED4, PLLA1_OUT0, CLK_M, PLLC4_OUT0); + +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SE_SE_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC3_OUT0, RSVD4, PLLA1_OUT0, CLK_M, PLLC4_OUT0); +DEFINE_CLK_RST_REG_BIT_ENUM(CLK_SOURCE_SE_CLK_LOCK, 8, DISABLE, ENABLE); + +DEFINE_CLK_RST_REG_BIT_ENUM(CLK_SOURCE_SOR1_SOR1_CLK_SEL0, 14, MUX, SOR1_BRICK_OUTPUT); +DEFINE_CLK_RST_REG_BIT_ENUM(CLK_SOURCE_SOR1_SOR1_CLK_SEL1, 15, SAFE_CLOCK, SOR1_CLOCK_SWITCH); + +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SOR1_SOR1_CLK_SRC, 29, PLLP_OUT0, RESERVED1, PLLD_OUT0, RESERVED3, RESERVED4, PLLD2_OUT0, CLK_M, RESERVED7); + +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_MSELECT_MSELECT_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT2, PLLC4_OUT1, CLK_S, CLK_M, PLLC4_OUT0); +DEFINE_CLK_RST_REG(CLK_SOURCE_MSELECT_MSELECT_CLK_DIVISOR, 0, 8); + +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_ACTMON_ACTMON_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT0, CLK_S, PLLC4_OUT1, CLK_M, PLLC4_OUT2); + +DEFINE_CLK_RST_REG(CLK_SOURCE_DSIA_LP_DSIA_LP_CLK_DIVISOR, 0, 8); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_DSIA_LP_DSIA_LP_CLK_SRC, 29, PLLP_OUT0, RSVD1, PLLC_OUT0, PLLC4_OUT0, PLLC4_OUT1, PLLC4_OUT2, CLK_M, RSVD7); + +DEFINE_CLK_RST_REG(CLK_SOURCE_DVFS_REF_DVFS_REF_DIVISOR, 0, 8); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_DVFS_REF_DVFS_REF_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT0, RESERVED4, PLLC4_OUT1, CLK_M, PLLC4_OUT2); + +DEFINE_CLK_RST_REG(CLK_SOURCE_DVFS_SOC_DVFS_SOC_DIVISOR, 0, 8); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_DVFS_SOC_DVFS_SOC_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT0, RESERVED4, PLLC4_OUT1, CLK_M, PLLC4_OUT2); + +DEFINE_CLK_RST_REG(CLK_SOURCE_UART_FST_MIPI_CAL_UART_FST_MIPI_CAL_CLK_DIVISOR, 0, 8); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_UART_FST_MIPI_CAL_UART_FST_MIPI_CAL_CLK_SRC, 29, PLLP_OUT3, PLLC_OUT0, PLLC2_OUT0_2, RSVD3, PLLC2_OUT0_4, RSVD5, CLK_M, RSVD7); + +DEFINE_CLK_RST_REG(CLK_SOURCE_LEGACY_TM_CLK_DIVISOR, 0, 8); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_LEGACY_TM_CLK_SRC, 29, PLLP_OUT3, PLLC_OUT0, PLLC2_OUT0, CLK_M, PLLP_OUT0, PLLC4_OUT0, PLLC4_OUT1, PLLC4_OUT2); + +DEFINE_CLK_RST_REG_BIT_ENUM(RST_DEV_L_SET_SET_COP_RST, 1, DISABLE, ENABLE); + +DEFINE_CLK_RST_REG_BIT_ENUM(RST_DEV_L_CLR_CLR_COP_RST, 1, DISABLE, ENABLE); + +DEFINE_CLK_RST_REG_BIT_ENUM(RST_CPUG_CMPLX_CLR_CLR_CPURESET0, 0, DISABLE, ENABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(RST_CPUG_CMPLX_CLR_CLR_CPURESET1, 1, DISABLE, ENABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(RST_CPUG_CMPLX_CLR_CLR_CPURESET2, 2, DISABLE, ENABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(RST_CPUG_CMPLX_CLR_CLR_CPURESET3, 3, DISABLE, ENABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(RST_CPUG_CMPLX_CLR_CLR_CORERESET0, 16, DISABLE, ENABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(RST_CPUG_CMPLX_CLR_CLR_CORERESET1, 17, DISABLE, ENABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(RST_CPUG_CMPLX_CLR_CLR_CORERESET2, 18, DISABLE, ENABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(RST_CPUG_CMPLX_CLR_CLR_CORERESET3, 19, DISABLE, ENABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(RST_CPUG_CMPLX_CLR_CLR_L2RESET, 24, DISABLE, ENABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(RST_CPUG_CMPLX_CLR_CLR_NONCPURESET, 29, DISABLE, ENABLE); +DEFINE_CLK_RST_REG_BIT_ENUM(RST_CPUG_CMPLX_CLR_CLR_PRESETDBG, 30, DISABLE, ENABLE); + +/* TODO: Actually include all devices. */ +#define CLK_RST_FOREACH_DEVICE(HANDLER) \ + HANDLER(L, CPU, 0, 0) \ + HANDLER(L, RTC, 0, 4) \ + HANDLER(L, TMR, 0, 5) \ + HANDLER(L, GPIO, 0, 8) \ + HANDLER(L, SDMMC2, 0, 9) \ + HANDLER(L, SDMMC1, 0, 14) \ + HANDLER(L, SDMMC4, 0, 15) \ + HANDLER(L, USBD, 0, 22) \ + HANDLER(L, DISP1, 0, 27) \ + HANDLER(L, HOST1X, 0, 28) \ + HANDLER(L, CACHE2, 0, 31) \ + HANDLER(H, MEM, 1, 0) \ + HANDLER(H, AHBDMA, 1, 1) \ + HANDLER(H, APBDMA, 1, 2) \ + HANDLER(H, PMC, 1, 6) \ + HANDLER(H, FUSE, 1, 7) \ + HANDLER(H, KFUSE, 1, 8) \ + HANDLER(H, I2C5, 1, 15) \ + HANDLER(H, DSI, 1, 16) \ + HANDLER(H, MIPI_CAL, 1, 24) \ + HANDLER(H, EMC, 1, 25) \ + HANDLER(H, USB2, 1, 26) \ + HANDLER(U, SDMMC3, 2, 5) \ + HANDLER(U, CSITE, 2, 9) \ + HANDLER(U, IRAMA, 2, 20) \ + HANDLER(U, IRAMB, 2, 21) \ + HANDLER(U, IRAMC, 2, 22) \ + HANDLER(U, IRAMD, 2, 23) \ + HANDLER(U, CRAM2, 2, 24) \ + HANDLER(V, CPUG, 3, 0) \ + HANDLER(V, MSELECT, 3, 3) \ + HANDLER(V, AHUB, 3, 10) \ + HANDLER(V, APB2APE, 3, 11) \ + HANDLER(V, SPDIF_DOUBLER, 3, 22) \ + HANDLER(V, ACTMON, 3, 23) \ + HANDLER(V, TZRAM, 3, 30) \ + HANDLER(V, SE, 3, 31) \ + HANDLER(W, PCIERX0, 4, 2) \ + HANDLER(W, PCIERX1, 4, 3) \ + HANDLER(W, PCIERX2, 4, 4) \ + HANDLER(W, PCIERX3, 4, 5) \ + HANDLER(W, PCIERX4, 4, 6) \ + HANDLER(W, PCIERX5, 4, 7) \ + HANDLER(W, DSIA_LP, 4, 19) \ + HANDLER(W, ENTROPY, 4, 21) \ + HANDLER(W, DVFS, 4, 27) \ + HANDLER(W, MC1, 4, 30) \ + HANDLER(X, MC_CAPA, 5, 7) \ + HANDLER(X, MC_CBPA, 5, 8) \ + HANDLER(X, MC_CPU, 5, 9) \ + HANDLER(X, MC_BBC, 5, 10) \ + HANDLER(X, EMC_DLL, 5, 14) \ + HANDLER(X, UART_FST_MIPI_CAL, 5, 17) \ + HANDLER(X, VIC, 5, 18) \ + HANDLER(X, GPU, 5, 24) \ + HANDLER(X, DBGAPB, 5, 25) \ + HANDLER(X, PLLG_REF, 5, 29) \ + HANDLER(Y, LEGACY_TM, 6, 1) \ + HANDLER(Y, APE, 6, 6) \ + HANDLER(Y, MC_CCPA, 6, 8) \ + HANDLER(Y, MC_CDPA, 6, 9) \ + HANDLER(Y, PLLP_OUT_CPU, 6, 31) + +#define CLK_RST_DEFINE_SET_CLR_REG(REGISTER, DEVICE, REGISTER_INDEX, DEVICE_INDEX) \ + DEFINE_CLK_RST_REG_BIT_ENUM(CLK_ENB_##REGISTER##_SET_SET_CLK_ENB_##DEVICE, DEVICE_INDEX, DISABLE, ENABLE); \ + DEFINE_CLK_RST_REG_BIT_ENUM(CLK_ENB_##REGISTER##_CLR_CLR_CLK_ENB_##DEVICE, DEVICE_INDEX, DISABLE, ENABLE); \ + DEFINE_CLK_RST_REG_BIT_ENUM(CLK_OUT_ENB_##REGISTER##_CLK_ENB_##DEVICE, DEVICE_INDEX, DISABLE, ENABLE); \ + DEFINE_CLK_RST_REG_BIT_ENUM(CLK_ENB_##REGISTER##_CLK_ENB_##DEVICE, DEVICE_INDEX, DISABLE, ENABLE); \ + DEFINE_CLK_RST_REG_BIT_ENUM(RST_DEV_##REGISTER##_SET_SET_##DEVICE##_RST, DEVICE_INDEX, DISABLE, ENABLE); \ + DEFINE_CLK_RST_REG_BIT_ENUM(RST_DEV_##REGISTER##_CLR_CLR_##DEVICE##_RST, DEVICE_INDEX, DISABLE, ENABLE); \ + DEFINE_CLK_RST_REG_BIT_ENUM(RST_DEV_##REGISTER##_##DEVICE##_RST, DEVICE_INDEX, DISABLE, ENABLE); \ + DEFINE_CLK_RST_REG_BIT_ENUM(RST_DEVICES_##REGISTER##_SWR_##DEVICE##_RST, DEVICE_INDEX, DISABLE, ENABLE); + +CLK_RST_FOREACH_DEVICE(CLK_RST_DEFINE_SET_CLR_REG) + +#undef CLK_RST_DEFINE_SET_CLR_REG + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_emc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_emc.hpp new file mode 100644 index 00000000..2121b62e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_emc.hpp @@ -0,0 +1,636 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#define EMC_ADDRESS(x) (0x7001B000 + x) +#define EMC0_ADDRESS(x) (0x7001E000 + x) +#define EMC1_ADDRESS(x) (0x7001F000 + x) + +#define EMC_INTSTATUS (0x000) +#define EMC_DBG (0x008) +#define EMC_CFG (0x00C) +#define EMC_ADR_CFG (0x010) +#define EMC_REFCTRL (0x020) +#define EMC_PIN (0x024) +#define EMC_TIMING_CONTROL (0x028) +#define EMC_RC (0x02C) +#define EMC_RFC (0x030) +#define EMC_RAS (0x034) +#define EMC_RP (0x038) +#define EMC_R2W (0x03C) +#define EMC_W2R (0x040) +#define EMC_R2P (0x044) +#define EMC_W2P (0x048) +#define EMC_RD_RCD (0x04C) +#define EMC_WR_RCD (0x050) +#define EMC_RRD (0x054) +#define EMC_REXT (0x058) +#define EMC_WDV (0x05C) +#define EMC_QUSE (0x060) +#define EMC_QRST (0x064) +#define EMC_QSAFE (0x068) +#define EMC_RDV (0x06C) +#define EMC_REFRESH (0x070) +#define EMC_BURST_REFRESH_NUM (0x074) +#define EMC_PDEX2WR (0x078) +#define EMC_PDEX2RD (0x07C) +#define EMC_PCHG2PDEN (0x080) +#define EMC_ACT2PDEN (0x084) +#define EMC_AR2PDEN (0x088) +#define EMC_RW2PDEN (0x08C) +#define EMC_TXSR (0x090) +#define EMC_TCKE (0x094) +#define EMC_TFAW (0x098) +#define EMC_TRPAB (0x09C) +#define EMC_TCLKSTABLE (0x0A0) +#define EMC_TCLKSTOP (0x0A4) +#define EMC_TREFBW (0x0A8) +#define EMC_TPPD (0x0AC) +#define EMC_ODT_WRITE (0x0B0) +#define EMC_PDEX2MRR (0x0B4) +#define EMC_WEXT (0x0B8) +#define EMC_TRTM (0x0BC) +#define EMC_RFC_SLR (0x0C0) +#define EMC_MRS_WAIT_CNT2 (0x0C4) +#define EMC_MRS_WAIT_CNT (0x0C8) +#define EMC_MRS (0x0CC) +#define EMC_EMRS (0x0D0) +#define EMC_REF (0x0D4) +#define EMC_NOP (0x0DC) +#define EMC_SELF_REF (0x0E0) +#define EMC_MRW (0x0E8) +#define EMC_MRR (0x0EC) +#define EMC_CMDQ (0x0F0) +#define EMC_MC2EMCQ (0x0F4) +#define EMC_TWTM (0x0F8) +#define EMC_TRATM (0x0FC) +#define EMC_FBIO_SPARE (0x100) +#define EMC_FBIO_CFG5 (0x104) +#define EMC_TWATM (0x108) +#define EMC_TR2REF (0x10C) +#define EMC_PMACRO_DATA_PI_CTRL (0x110) +#define EMC_PMACRO_CMD_PI_CTRL (0x114) +#define EMC_PDEX2CKE (0x118) +#define EMC_CKE2PDEN (0x11C) +#define EMC_CFG_RSV (0x120) +#define EMC_ACPD_CONTROL (0x124) +#define EMC_MPC (0x128) +#define EMC_EMRS2 (0x12C) +#define EMC_MRW2 (0x134) +#define EMC_MRW3 (0x138) +#define EMC_MRW3 (0x138) +#define EMC_MRW4 (0x13C) +#define EMC_CLKEN_OVERRIDE (0x140) +#define EMC_R2R (0x144) +#define EMC_W2W (0x148) +#define EMC_EINPUT (0x14C) +#define EMC_EINPUT_DURATION (0x150) +#define EMC_PUTERM_EXTRA (0x154) +#define EMC_TCKESR (0x158) +#define EMC_TPD (0x15C) +#define EMC_AUTO_CAL_CONFIG (0x2A4) +#define EMC_AUTO_CAL_INTERVAL (0x2A8) +#define EMC_REQ_CTRL (0x2B0) +#define EMC_EMC_STATUS (0x2B4) +#define EMC_CFG_2 (0x2B8) +#define EMC_CFG_DIG_DLL (0x2BC) +#define EMC_CFG_DIG_DLL_PERIOD (0x2C0) +#define EMC_DIG_DLL_STATUS (0x2C4) +#define EMC_CFG_DIG_DLL_1 (0x2C8) +#define EMC_RDV_MASK (0x2CC) +#define EMC_WDV_MASK (0x2D0) +#define EMC_RDV_EARLY_MASK (0x2D4) +#define EMC_RDV_EARLY (0x2D8) +#define EMC_AUTO_CAL_CONFIG8 (0x2DC) +#define EMC_ZCAL_INTERVAL (0x2E0) +#define EMC_ZCAL_WAIT_CNT (0x2E4) +#define EMC_ZCAL_MRW_CMD (0x2E8) +#define EMC_ZQ_CAL (0x2EC) +#define EMC_XM2COMPPADCTRL3 (0x2F4) +#define EMC_AUTO_CAL_VREF_SEL_0 (0x2F8) +#define EMC_AUTO_CAL_VREF_SEL_1 (0x300) +#define EMC_XM2COMPPADCTRL (0x30C) +#define EMC_FDPD_CTRL_DQ (0x310) +#define EMC_FDPD_CTRL_CMD (0x314) +#define EMC_PMACRO_CMD_BRICK_CTRL_FDPD (0x318) +#define EMC_PMACRO_DATA_BRICK_CTRL_FDPD (0x31C) +#define EMC_SCRATCH0 (0x324) +#define EMC_PMACRO_BRICK_CTRL_RFU1 (0x330) +#define EMC_PMACRO_BRICK_CTRL_RFU2 (0x334) +#define EMC_CMD_MAPPING_CMD0_0 (0x380) +#define EMC_CMD_MAPPING_CMD0_1 (0x384) +#define EMC_CMD_MAPPING_CMD0_2 (0x388) +#define EMC_CMD_MAPPING_CMD1_0 (0x38C) +#define EMC_CMD_MAPPING_CMD1_1 (0x390) +#define EMC_CMD_MAPPING_CMD1_2 (0x394) +#define EMC_CMD_MAPPING_CMD2_0 (0x398) +#define EMC_CMD_MAPPING_CMD2_1 (0x39C) +#define EMC_CMD_MAPPING_CMD2_2 (0x3A0) +#define EMC_CMD_MAPPING_CMD3_0 (0x3A4) +#define EMC_CMD_MAPPING_CMD3_1 (0x3A8) +#define EMC_CMD_MAPPING_CMD3_2 (0x3AC) +#define EMC_CMD_MAPPING_BYTE (0x3B0) +#define EMC_TR_TIMING_0 (0x3B4) +#define EMC_TR_CTRL_0 (0x3B8) +#define EMC_TR_CTRL_1 (0x3BC) +#define EMC_SWITCH_BACK_CTRL (0x3C0) +#define EMC_TR_RDV (0x3C4) +#define EMC_STALL_THEN_EXE_AFTER_CLKCHANGE (0x3CC) +#define EMC_SEL_DPD_CTRL (0x3D8) +#define EMC_PRE_REFRESH_REQ_CNT (0x3DC) +#define EMC_DYN_SELF_REF_CONTROL (0x3E0) +#define EMC_TXSRDLL (0x3E4) +#define EMC_CCFIFO_ADDR (0x3E8) +#define EMC_CCFIFO_DATA (0x3EC) +#define EMC_CCFIFO_STATUS (0x3F0) +#define EMC_TR_QPOP (0x3F4) +#define EMC_TR_RDV_MASK (0x3F8) +#define EMC_TR_QSAFE (0x3FC) +#define EMC_TR_QRST (0x400) +#define EMC_SWIZZLE_RANK0_BYTE0 (0x404) +#define EMC_SWIZZLE_RANK0_BYTE1 (0x408) +#define EMC_SWIZZLE_RANK0_BYTE2 (0x40C) +#define EMC_SWIZZLE_RANK0_BYTE3 (0x410) +#define EMC_SWIZZLE_RANK1_BYTE0 (0x418) +#define EMC_SWIZZLE_RANK1_BYTE1 (0x41C) +#define EMC_SWIZZLE_RANK1_BYTE2 (0x420) +#define EMC_SWIZZLE_RANK1_BYTE3 (0x424) +#define EMC_ISSUE_QRST (0x428) +#define EMC_AUTO_CAL_CONFIG9 (0x42C) +#define EMC_PMC_SCRATCH1 (0x440) +#define EMC_PMC_SCRATCH2 (0x444) +#define EMC_PMC_SCRATCH3 (0x448) +#define EMC_AUTO_CAL_CONFIG2 (0x458) +#define EMC_AUTO_CAL_CONFIG3 (0x45C) +#define EMC_TR_DVFS (0x460) +#define EMC_AUTO_CAL_CHANNEL (0x464) +#define EMC_IBDLY (0x468) +#define EMC_OBDLY (0x46C) +#define EMC_TXDSRVTTGEN (0x480) +#define EMC_WE_DURATION (0x48C) +#define EMC_WS_DURATION (0x490) +#define EMC_WEV (0x494) +#define EMC_WSV (0x498) +#define EMC_CFG_3 (0x49C) +#define EMC_MRW5 (0x4A0) +#define EMC_MRW6 (0x4A4) +#define EMC_MRW7 (0x4A8) +#define EMC_MRW8 (0x4AC) +#define EMC_MRW9 (0x4B0) +#define EMC_MRW10 (0x4B4) +#define EMC_MRW11 (0x4B8) +#define EMC_MRW12 (0x4BC) +#define EMC_MRW13 (0x4C0) +#define EMC_MRW14 (0x4C4) +#define EMC_MRW15 (0x4D0) +#define EMC_CFG_SYNC (0x4D4) +#define EMC_FDPD_CTRL_CMD_NO_RAMP (0x4D8) +#define EMC_WDV_CHK (0x4E0) +#define EMC_CFG_PIPE_2 (0x554) +#define EMC_CFG_PIPE_CLK (0x558) +#define EMC_CFG_PIPE_1 (0x55C) +#define EMC_CFG_PIPE (0x560) +#define EMC_QPOP (0x564) +#define EMC_QUSE_WIDTH (0x568) +#define EMC_PUTERM_WIDTH (0x56C) +#define EMC_AUTO_CAL_CONFIG7 (0x574) +#define EMC_XM2COMPPADCTRL2 (0x578) +#define EMC_REFCTRL2 (0x580) +#define EMC_FBIO_CFG7 (0x584) +#define EMC_DATA_BRLSHFT_0 (0x588) +#define EMC_DATA_BRLSHFT_1 (0x58C) +#define EMC_RFCPB (0x590) +#define EMC_DQS_BRLSHFT_0 (0x594) +#define EMC_DQS_BRLSHFT_1 (0x598) +#define EMC_CMD_BRLSHFT_0 (0x59C) +#define EMC_CMD_BRLSHFT_1 (0x5A0) +#define EMC_CMD_BRLSHFT_2 (0x5A4) +#define EMC_CMD_BRLSHFT_3 (0x5A8) +#define EMC_QUSE_BRLSHFT_0 (0x5AC) +#define EMC_AUTO_CAL_CONFIG4 (0x5B0) +#define EMC_AUTO_CAL_CONFIG5 (0x5B4) +#define EMC_QUSE_BRLSHFT_1 (0x5B8) +#define EMC_QUSE_BRLSHFT_2 (0x5BC) +#define EMC_CCDMW (0x5C0) +#define EMC_QUSE_BRLSHFT_3 (0x5C4) +#define EMC_FBIO_CFG8 (0x5C8) +#define EMC_AUTO_CAL_CONFIG6 (0x5CC) + +/* Erista */ +#define EMC_DLL_CFG_0 (0x5E4) +#define EMC_DLL_CFG_1 (0x5E8) + +/* Mariko */ +#define EMC_PMACRO_DLL_CFG_0 (0x5E4) +#define EMC_PMACRO_DLL_CFG_1 (0x5E8) +#define EMC_PMACRO_DLL_CFG_2 (0x5F8) + +#define EMC_CONFIG_SAMPLE_DELAY (0x5F0) +#define EMC_CFG_UPDATE (0x5F4) +#define EMC_PMACRO_QUSE_DDLL_RANK0_0 (0x600) +#define EMC_PMACRO_QUSE_DDLL_RANK0_1 (0x604) +#define EMC_PMACRO_QUSE_DDLL_RANK0_2 (0x608) +#define EMC_PMACRO_QUSE_DDLL_RANK0_3 (0x60C) +#define EMC_PMACRO_QUSE_DDLL_RANK0_4 (0x610) +#define EMC_PMACRO_QUSE_DDLL_RANK0_5 (0x614) +#define EMC_PMACRO_QUSE_DDLL_RANK1_4 (0x630) +#define EMC_PMACRO_QUSE_DDLL_RANK1_5 (0x634) +#define EMC_PMACRO_QUSE_DDLL_RANK1_0 (0x620) +#define EMC_PMACRO_QUSE_DDLL_RANK1_1 (0x624) +#define EMC_PMACRO_QUSE_DDLL_RANK1_2 (0x628) +#define EMC_PMACRO_QUSE_DDLL_RANK1_3 (0x62C) +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0 (0x640) +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1 (0x644) +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2 (0x648) +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3 (0x64C) +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_4 (0x650) +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_5 (0x654) +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0 (0x660) +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1 (0x664) +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2 (0x668) +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3 (0x66C) +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_4 (0x670) +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_5 (0x674) +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_0 (0x680) +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_1 (0x684) +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_2 (0x688) +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_3 (0x68C) +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_4 (0x690) +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_5 (0x694) +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_0 (0x6A0) +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_1 (0x6A4) +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_2 (0x6A8) +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_3 (0x6AC) +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_4 (0x6B0) +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_5 (0x6B4) +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_0 (0x6C0) +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_1 (0x6C4) +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_2 (0x6C8) +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_3 (0x6CC) +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_0 (0x6E0) +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_1 (0x6E4) +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_2 (0x6E8) +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_3 (0x6EC) +#define EMC_PMACRO_AUTOCAL_CFG_0 (0x700) +#define EMC_PMACRO_AUTOCAL_CFG_1 (0x704) +#define EMC_PMACRO_AUTOCAL_CFG_2 (0x708) +#define EMC_PMACRO_TX_PWRD_0 (0x720) +#define EMC_PMACRO_TX_PWRD_1 (0x724) +#define EMC_PMACRO_TX_PWRD_2 (0x728) +#define EMC_PMACRO_TX_PWRD_3 (0x72C) +#define EMC_PMACRO_TX_PWRD_4 (0x730) +#define EMC_PMACRO_TX_PWRD_5 (0x734) +#define EMC_PMACRO_TX_SEL_CLK_SRC_0 (0x740) +#define EMC_PMACRO_TX_SEL_CLK_SRC_1 (0x744) +#define EMC_PMACRO_TX_SEL_CLK_SRC_3 (0x74C) +#define EMC_PMACRO_TX_SEL_CLK_SRC_2 (0x748) +#define EMC_PMACRO_TX_SEL_CLK_SRC_4 (0x750) +#define EMC_PMACRO_TX_SEL_CLK_SRC_5 (0x754) +#define EMC_PMACRO_DDLL_BYPASS (0x760) +#define EMC_PMACRO_DDLL_PWRD_0 (0x770) +#define EMC_PMACRO_DDLL_PWRD_1 (0x774) +#define EMC_PMACRO_DDLL_PWRD_2 (0x778) +#define EMC_PMACRO_CMD_CTRL_0 (0x780) +#define EMC_PMACRO_CMD_CTRL_1 (0x784) +#define EMC_PMACRO_CMD_CTRL_2 (0x788) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_0 (0x800) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_1 (0x804) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_2 (0x808) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE0_3 (0x80C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_0 (0x810) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_1 (0x814) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_2 (0x818) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE1_3 (0x81C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_0 (0x820) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_1 (0x824) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_2 (0x828) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE2_3 (0x82C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_0 (0x830) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_1 (0x834) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_2 (0x838) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE3_3 (0x83C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_0 (0x840) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_1 (0x844) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_2 (0x848) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE4_3 (0x84C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_0 (0x850) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_1 (0x854) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_2 (0x858) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE5_3 (0x85C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_0 (0x860) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_1 (0x864) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_2 (0x868) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE6_3 (0x86C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_0 (0x870) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_1 (0x874) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_2 (0x878) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_BYTE7_3 (0x87C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_0 (0x880) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_1 (0x884) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_2 (0x888) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD0_3 (0x88C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_0 (0x890) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_1 (0x894) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_2 (0x898) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD1_3 (0x89C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_0 (0x8A0) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_1 (0x8A4) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_2 (0x8A8) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD2_3 (0x8AC) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_0 (0x8B0) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_1 (0x8B4) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_2 (0x8B8) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK0_CMD3_3 (0x8BC) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_0 (0x900) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_1 (0x904) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_2 (0x908) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE0_3 (0x90C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_0 (0x910) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_1 (0x914) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_2 (0x918) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE1_3 (0x91C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_0 (0x920) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_1 (0x924) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_2 (0x928) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE2_3 (0x92C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_0 (0x930) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_1 (0x934) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_2 (0x938) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE3_3 (0x93C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_0 (0x940) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_1 (0x944) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_2 (0x948) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE4_3 (0x94C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_0 (0x950) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_1 (0x954) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_2 (0x958) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE5_3 (0x95C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_0 (0x960) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_1 (0x964) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_2 (0x968) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE6_3 (0x96C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_0 (0x970) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_1 (0x974) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_2 (0x978) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_BYTE7_3 (0x97C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_0 (0x980) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_1 (0x984) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_2 (0x988) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD0_3 (0x98C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_0 (0x990) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_1 (0x994) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_2 (0x998) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD1_3 (0x99C) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_0 (0x9A0) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_1 (0x9A4) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_2 (0x9A8) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD2_3 (0x9AC) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_0 (0x9B0) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_1 (0x9B4) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_2 (0x9B8) +#define EMC_PMACRO_OB_DDLL_SHORT_DQ_RANK1_CMD3_3 (0x9BC) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_0 (0xA00) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_1 (0xA04) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE0_2 (0xA08) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_0 (0xA10) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_1 (0xA14) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE1_2 (0xA18) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_0 (0xA20) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_1 (0xA24) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE2_2 (0xA28) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_0 (0xA30) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_1 (0xA34) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE3_2 (0xA38) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_0 (0xA40) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_1 (0xA44) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE4_2 (0xA48) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_0 (0xA50) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_1 (0xA54) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE5_2 (0xA58) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_0 (0xA60) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_1 (0xA64) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE6_2 (0xA68) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_0 (0xA70) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_1 (0xA74) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK0_BYTE7_2 (0xA78) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_0 (0xB00) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_1 (0xB04) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE0_2 (0xB08) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_0 (0xB10) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_1 (0xB14) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE1_2 (0xB18) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_0 (0xB20) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_1 (0xB24) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE2_2 (0xB28) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_0 (0xB30) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_1 (0xB34) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE3_2 (0xB38) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_0 (0xB40) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_1 (0xB44) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE4_2 (0xB48) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_0 (0xB50) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_1 (0xB54) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE5_2 (0xB58) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_0 (0xB60) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_1 (0xB64) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE6_2 (0xB68) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_0 (0xB70) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_1 (0xB74) +#define EMC_PMACRO_IB_DDLL_SHORT_DQ_RANK1_BYTE7_2 (0xB78) +#define EMC_PMACRO_IB_VREF_DQ_0 (0xBE0) +#define EMC_PMACRO_IB_VREF_DQ_1 (0xBE4) +#define EMC_PMACRO_IB_VREF_DQS_0 (0xBF0) +#define EMC_PMACRO_IB_VREF_DQS_1 (0xBF4) +#define EMC_PMACRO_DDLL_LONG_CMD_0 (0xC00) +#define EMC_PMACRO_DDLL_LONG_CMD_1 (0xC04) +#define EMC_PMACRO_DDLL_LONG_CMD_2 (0xC08) +#define EMC_PMACRO_DDLL_LONG_CMD_3 (0xC0C) +#define EMC_PMACRO_DDLL_LONG_CMD_4 (0xC10) +#define EMC_PMACRO_DDLL_SHORT_CMD_0 (0xC20) +#define EMC_PMACRO_DDLL_SHORT_CMD_1 (0xC24) +#define EMC_PMACRO_DDLL_SHORT_CMD_2 (0xC28) +#define EMC_PMACRO_CFG_PM_GLOBAL_0 (0xC30) +#define EMC_PMACRO_VTTGEN_CTRL_0 (0xC34) +#define EMC_PMACRO_VTTGEN_CTRL_1 (0xC38) +#define EMC_PMACRO_BG_BIAS_CTRL_0 (0xC3C) +#define EMC_PMACRO_PAD_CFG_CTRL (0xC40) +#define EMC_PMACRO_ZCTRL (0xC44) +#define EMC_PMACRO_RX_TERM (0xC48) +#define EMC_PMACRO_CMD_TX_DRV (0xC4C) +#define EMC_PMACRO_CMD_PAD_RX_CTRL (0xC50) +#define EMC_PMACRO_DATA_PAD_RX_CTRL (0xC54) +#define EMC_PMACRO_CMD_RX_TERM_MODE (0xC58) +#define EMC_PMACRO_DATA_RX_TERM_MODE (0xC5C) +#define EMC_PMACRO_CMD_PAD_TX_CTRL (0xC60) +#define EMC_PMACRO_DATA_PAD_TX_CTRL (0xC64) +#define EMC_PMACRO_COMMON_PAD_TX_CTRL (0xC68) +#define EMC_PMACRO_DSR_VTTGEN_CTRL_0 (0xC6C) +#define EMC_PMACRO_DQ_TX_DRV (0xC70) +#define EMC_PMACRO_CA_TX_DRV (0xC74) +#define EMC_PMACRO_AUTOCAL_CFG_COMMON (0xC78) +#define EMC_PMACRO_BRICK_MAPPING_0 (0xC80) +#define EMC_PMACRO_BRICK_MAPPING_1 (0xC84) +#define EMC_PMACRO_BRICK_MAPPING_2 (0xC88) +#define EMC_PMACRO_DDLL_PERIODIC_OFFSET (0xCE8) +#define EMC_PMACRO_VTTGEN_CTRL_2 (0xCF0) +#define EMC_PMACRO_IB_RXRT (0xCF4) +#define EMC_PMACRO_TRAINING_CTRL_0 (0xCF8) +#define EMC_PMACRO_TRAINING_CTRL_1 (0xCFC) +#define EMC_PMACRO_DIG_DLL_STATUS_0 (0xD20) +#define EMC_PMACRO_PERBIT_FGCG_CTRL_0 (0xD40) +#define EMC_PMACRO_PERBIT_FGCG_CTRL_1 (0xD44) +#define EMC_PMACRO_PERBIT_FGCG_CTRL_2 (0xD48) +#define EMC_PMACRO_PERBIT_FGCG_CTRL_3 (0xD4C) +#define EMC_PMACRO_PERBIT_FGCG_CTRL_4 (0xD50) +#define EMC_PMACRO_PERBIT_FGCG_CTRL_5 (0xD54) +#define EMC_PMACRO_PERBIT_RFU_CTRL_0 (0xD60) +#define EMC_PMACRO_PERBIT_RFU_CTRL_1 (0xD64) +#define EMC_PMACRO_PERBIT_RFU_CTRL_2 (0xD68) +#define EMC_PMACRO_PERBIT_RFU_CTRL_3 (0xD6C) +#define EMC_PMACRO_PERBIT_RFU_CTRL_4 (0xD70) +#define EMC_PMACRO_PERBIT_RFU_CTRL_5 (0xD74) +#define EMC_PMACRO_PERBIT_RFU1_CTRL_0 (0xD80) +#define EMC_PMACRO_PERBIT_RFU1_CTRL_1 (0xD84) +#define EMC_PMACRO_PERBIT_RFU1_CTRL_2 (0xD88) +#define EMC_PMACRO_PERBIT_RFU1_CTRL_3 (0xD8C) +#define EMC_PMACRO_PERBIT_RFU1_CTRL_4 (0xD90) +#define EMC_PMACRO_PERBIT_RFU1_CTRL_5 (0xD94) +#define EMC_TRAINING_CMD (0xE00) +#define EMC_TRAINING_CTRL (0xE04) +#define EMC_TRAINING_STATUS (0xE08) +#define EMC_TRAINING_QUSE_CORS_CTRL (0xE0C) +#define EMC_TRAINING_QUSE_FINE_CTRL (0xE10) +#define EMC_TRAINING_QUSE_CTRL_MISC (0xE14) +#define EMC_TRAINING_WRITE_FINE_CTRL (0xE18) +#define EMC_TRAINING_WRITE_CTRL_MISC (0xE1C) +#define EMC_TRAINING_WRITE_VREF_CTRL (0xE20) +#define EMC_TRAINING_READ_FINE_CTRL (0xE24) +#define EMC_TRAINING_READ_CTRL_MISC (0xE28) +#define EMC_TRAINING_READ_VREF_CTRL (0xE2C) +#define EMC_TRAINING_CA_FINE_CTRL (0xE30) +#define EMC_TRAINING_CA_CTRL_MISC (0xE34) +#define EMC_TRAINING_CA_CTRL_MISC1 (0xE38) +#define EMC_TRAINING_CA_VREF_CTRL (0xE3C) +#define EMC_TRAINING_SETTLE (0xE44) +#define EMC_TRAINING_MPC (0xE5C) +#define EMC_TRAINING_PATRAM_CTRL (0xE60) +#define EMC_TRAINING_PATRAM_DQ (0xE64) +#define EMC_TRAINING_PATRAM_DMI (0xE68) +#define EMC_TRAINING_VREF_SETTLE (0xE6C) +#define EMC_TRAINING_RW_OFFSET_IB_BYTE0 (0xE98) +#define EMC_TRAINING_RW_OFFSET_IB_BYTE1 (0xE9C) +#define EMC_TRAINING_RW_OFFSET_IB_BYTE2 (0xEA0) +#define EMC_TRAINING_RW_OFFSET_IB_BYTE3 (0xEA4) +#define EMC_TRAINING_RW_OFFSET_IB_MISC (0xEA8) +#define EMC_TRAINING_RW_OFFSET_OB_BYTE0 (0xEAC) +#define EMC_TRAINING_RW_OFFSET_OB_BYTE1 (0xEB0) +#define EMC_TRAINING_RW_OFFSET_OB_BYTE2 (0xEB4) +#define EMC_TRAINING_RW_OFFSET_OB_BYTE3 (0xEB8) +#define EMC_TRAINING_RW_OFFSET_OB_MISC (0xEBC) +#define EMC_TRAINING_OPT_CA_VREF (0xEC0) +#define EMC_TRAINING_OPT_DQ_OB_VREF (0xEC4) +#define EMC_TRAINING_QUSE_VREF_CTRL (0xED0) +#define EMC_TRAINING_OPT_DQS_IB_VREF_RANK0 (0xED4) +#define EMC_TRAINING_OPT_DQS_IB_VREF_RANK1 (0xED8) + + +#define EMC_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (EMC, NAME) +#define EMC_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (EMC, NAME, VALUE) +#define EMC_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (EMC, NAME, ENUM) +#define EMC_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(EMC, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + +#define DEFINE_EMC_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (EMC, NAME, __OFFSET__, __WIDTH__) +#define DEFINE_EMC_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (EMC, NAME, __OFFSET__, ZERO, ONE) +#define DEFINE_EMC_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (EMC, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) +#define DEFINE_EMC_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(EMC, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) +#define DEFINE_EMC_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (EMC, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + +DEFINE_EMC_REG_BIT_ENUM(DBG_WRITE_MUX, 1, ASSEMBLY, ACTIVE); + +DEFINE_EMC_REG_BIT_ENUM(CFG_DYN_SELF_REF, 28, DISABLED, ENABLED); +DEFINE_EMC_REG_BIT_ENUM(CFG_DRAM_ACPD, 29, NO_POWERDOWN, ACTIVE_POWERDOWN); + +DEFINE_EMC_REG_BIT_ENUM(ADR_CFG_EMEM_NUMDEV, 0, N1, N2); + +DEFINE_EMC_REG_BIT_ENUM(TIMING_CONTROL_TIMING_UPDATE, 0, DISABLED, ENABLED); + +DEFINE_EMC_REG_BIT_ENUM(SELF_REF_SELF_REF_CMD, 0, DISABLED, ENABLED); +DEFINE_EMC_REG_BIT_ENUM(SELF_REF_ACTIVE_SELF_REF, 8, DISABLED, ENABLED); +DEFINE_EMC_REG_TWO_BIT_ENUM(SELF_REF_SREF_DEV_SELECTN, 30, BOTH, DEV1, DEV0, RESERVED); + +DEFINE_EMC_REG(MRW_OP, 0, 8); +DEFINE_EMC_REG(MRW_MA, 16, 8); +DEFINE_EMC_REG_TWO_BIT_ENUM(MRW_CNT, 26, SHORT, LONG, EXT1, EXT2); +DEFINE_EMC_REG_TWO_BIT_ENUM(MRW_DEV_SELECTN, 30, BOTH, DEV1, DEV0, RESERVED); + +DEFINE_EMC_REG_TWO_BIT_ENUM(FBIO_CFG5_DRAM_TYPE, 0, DDR4, LPDDR4, LPDDR2, DDR2); + +DEFINE_EMC_REG_BIT_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_MEASURE_STALL, 9, DISABLE, ENABLE); +DEFINE_EMC_REG_BIT_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_UPDATE_STALL, 10, DISABLE, ENABLE); +DEFINE_EMC_REG_BIT_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_START, 31, DISABLE, ENABLE); + +DEFINE_EMC_REG(REQ_CTRL_STALL_ALL_READS, 0, 1); +DEFINE_EMC_REG(REQ_CTRL_STALL_ALL_WRITES, 1, 1); + +DEFINE_EMC_REG_TWO_BIT_ENUM(EMC_STATUS_DRAM_IN_SELF_REFRESH, 8, DISABLED, DEV0_ENABLED, DEV1_ENABLED, BOTH_ENABLED); + +DEFINE_EMC_REG_BIT_ENUM(EMC_STATUS_DRAM_DEV0_IN_SELF_REFRESH, 8, DISABLED, ENABLED); + +DEFINE_EMC_REG_BIT_ENUM(EMC_STATUS_NO_OUTSTANDING_TRANSACTIONS, 2, WAITING, COMPLETED); +DEFINE_EMC_REG_BIT_ENUM(EMC_STATUS_TIMING_UPDATE_STALLED, 23, DONE, BUSY); + +DEFINE_EMC_REG_BIT_ENUM(CFG_DIG_DLL_CFG_DLL_EN, 0, DISABLED, ENABLED); + +DEFINE_EMC_REG(ZCAL_INTERVAL_LO, 0, 10); +DEFINE_EMC_REG(ZCAL_INTERVAL_HI, 10, 14); + +DEFINE_EMC_REG(PMC_SCRATCH3_DDR_CNTRL, 0, 19); +DEFINE_EMC_REG_BIT_ENUM(PMC_SCRATCH3_WEAK_BIAS, 30, DISABLED, ENABLED); + +DEFINE_EMC_REG_BIT_ENUM(FBIO_CFG7_CH0_ENABLE, 1, DISABLE, ENABLE); +DEFINE_EMC_REG_BIT_ENUM(FBIO_CFG7_CH1_ENABLE, 2, DISABLE, ENABLE); + +DEFINE_EMC_REG_BIT_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE0, 16, DISABLE, ENABLE); +DEFINE_EMC_REG_BIT_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE1, 17, DISABLE, ENABLE); +DEFINE_EMC_REG_BIT_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE2, 18, DISABLE, ENABLE); +DEFINE_EMC_REG_BIT_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE3, 19, DISABLE, ENABLE); +DEFINE_EMC_REG_BIT_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE4, 20, DISABLE, ENABLE); +DEFINE_EMC_REG_BIT_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE5, 21, DISABLE, ENABLE); +DEFINE_EMC_REG_BIT_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE6, 22, DISABLE, ENABLE); +DEFINE_EMC_REG_BIT_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_BYTE7, 23, DISABLE, ENABLE); +DEFINE_EMC_REG_BIT_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_CMD0, 24, DISABLE, ENABLE); +DEFINE_EMC_REG_BIT_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_CMD1, 25, DISABLE, ENABLE); +DEFINE_EMC_REG_BIT_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_CMD2, 26, DISABLE, ENABLE); +DEFINE_EMC_REG_BIT_ENUM(PMACRO_CFG_PM_GLOBAL_0_DISABLE_CFG_CMD3, 27, DISABLE, ENABLE); + +DEFINE_EMC_REG_BIT_ENUM(PMACRO_TRAINING_CTRL_0_CH0_TRAINING_ENABLED, 0, DISABLED, ENABLED); +DEFINE_EMC_REG_BIT_ENUM(PMACRO_TRAINING_CTRL_0_CH0_TRAINING_TRAIN_QPOP, 1, DISABLED, ENABLED); +DEFINE_EMC_REG_BIT_ENUM(PMACRO_TRAINING_CTRL_0_CH0_TRAINING_RX_E_DIRECT_ZI, 2, DISABLED, ENABLED); +DEFINE_EMC_REG_BIT_ENUM(PMACRO_TRAINING_CTRL_0_CH0_TRAINING_E_WRPTR, 3, DISABLED, ENABLED); +DEFINE_EMC_REG_BIT_ENUM(PMACRO_TRAINING_CTRL_0_CH0_TRAINING_DRV_DQS, 4, DISABLED, ENABLED); + +DEFINE_EMC_REG_BIT_ENUM(PMACRO_TRAINING_CTRL_1_CH1_TRAINING_ENABLED, 0, DISABLED, ENABLED); +DEFINE_EMC_REG_BIT_ENUM(PMACRO_TRAINING_CTRL_1_CH1_TRAINING_TRAIN_QPOP, 1, DISABLED, ENABLED); +DEFINE_EMC_REG_BIT_ENUM(PMACRO_TRAINING_CTRL_1_CH1_TRAINING_RX_E_DIRECT_ZI, 2, DISABLED, ENABLED); +DEFINE_EMC_REG_BIT_ENUM(PMACRO_TRAINING_CTRL_1_CH1_TRAINING_E_WRPTR, 3, DISABLED, ENABLED); +DEFINE_EMC_REG_BIT_ENUM(PMACRO_TRAINING_CTRL_1_CH1_TRAINING_DRV_DQS, 4, DISABLED, ENABLED); + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_evp.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_evp.hpp new file mode 100644 index 00000000..fecad6f8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_evp.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#define EVP_CPU_RESET_VECTOR (0x100) + +#define EVP_COP_RESET_VECTOR (0x200) +#define EVP_COP_UNDEF_VECTOR (0x204) +#define EVP_COP_SWI_VECTOR (0x208) +#define EVP_COP_PREFETCH_ABORT_VECTOR (0x20C) +#define EVP_COP_DATA_ABORT_VECTOR (0x210) +#define EVP_COP_RSVD_VECTOR (0x214) +#define EVP_COP_IRQ_VECTOR (0x218) +#define EVP_COP_FIQ_VECTOR (0x21C) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_flow_ctlr.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_flow_ctlr.hpp new file mode 100644 index 00000000..b5674afa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_flow_ctlr.hpp @@ -0,0 +1,83 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#define FLOW_CTLR_RAM_REPAIR (0x040) +#define FLOW_CTLR_FLOW_DBG_QUAL (0x050) +#define FLOW_CTLR_CC4_HVC_CONTROL (0x060) +#define FLOW_CTLR_CC4_RETENTION_CONTROL (0x064) +#define FLOW_CTLR_CC4_HVC_RETRY (0x08C) +#define FLOW_CTLR_L2FLUSH_CONTROL (0x094) +#define FLOW_CTLR_BPMP_CLUSTER_CONTROL (0x098) + + +#define FLOW_CTLR_CPU0_CSR (0x008) +#define FLOW_CTLR_CPU1_CSR (0x018) +#define FLOW_CTLR_CPU2_CSR (0x020) +#define FLOW_CTLR_CPU3_CSR (0x028) + +#define FLOW_CTLR_HALT_CPU0_EVENTS (0x000) +#define FLOW_CTLR_HALT_CPU1_EVENTS (0x014) +#define FLOW_CTLR_HALT_CPU2_EVENTS (0x01C) +#define FLOW_CTLR_HALT_CPU3_EVENTS (0x024) +#define FLOW_CTLR_HALT_COP_EVENTS (0x004) + +#define FLOW_CTLR_CC4_CORE0_CTRL (0x06C) +#define FLOW_CTLR_CC4_CORE1_CTRL (0x070) +#define FLOW_CTLR_CC4_CORE2_CTRL (0x074) +#define FLOW_CTLR_CC4_CORE3_CTRL (0x078) + +#define FLOW_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (FLOW_CTLR, NAME) +#define FLOW_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (FLOW_CTLR, NAME, VALUE) +#define FLOW_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (FLOW_CTLR, NAME, ENUM) +#define FLOW_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(FLOW_CTLR, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + +#define DEFINE_FLOW_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (FLOW_CTLR, NAME, __OFFSET__, __WIDTH__) +#define DEFINE_FLOW_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (FLOW_CTLR, NAME, __OFFSET__, ZERO, ONE) +#define DEFINE_FLOW_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (FLOW_CTLR, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) +#define DEFINE_FLOW_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(FLOW_CTLR, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) +#define DEFINE_FLOW_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (FLOW_CTLR, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + +DEFINE_FLOW_REG_BIT_ENUM(CPUN_CSR_ENABLE, 0, DISABLE, ENABLE); +DEFINE_FLOW_REG(CPUN_CSR_WAIT_WFI_BITMAP, 8, 4); +DEFINE_FLOW_REG_TWO_BIT_ENUM(CPUN_CSR_ENABLE_EXT, 12, POWERGATE_CPU_ONLY, POWERGATE_BOTH_CPU_NONCPU, POWERGATE_CPU_TURNOFF_CPURAIL, PG_EMULATION); +DEFINE_FLOW_REG_BIT_ENUM(CPUN_CSR_EVENT_FLAG, 14, FALSE, TRUE); +DEFINE_FLOW_REG_BIT_ENUM(CPUN_CSR_INTR_FLAG, 15, FALSE, TRUE); + +DEFINE_FLOW_REG_BIT_ENUM(HALT_CPUN_EVENTS_GIC_FIQN, 8, DISABLE, ENABLE); +DEFINE_FLOW_REG_BIT_ENUM(HALT_CPUN_EVENTS_GIC_IRQN, 9, DISABLE, ENABLE); +DEFINE_FLOW_REG_BIT_ENUM(HALT_CPUN_EVENTS_LIC_FIQN, 10, DISABLE, ENABLE); +DEFINE_FLOW_REG_BIT_ENUM(HALT_CPUN_EVENTS_LIC_IRQN, 11, DISABLE, ENABLE); + +DEFINE_FLOW_REG_THREE_BIT_ENUM(HALT_CPUN_EVENTS_FLOW_MODE, 29, NONE, RUN_AND_INT, WAITEVENT, WAITEVENT_AND_INT, STOP_UNTIL_IRQ, STOP_UNTIL_EVENT_AND_IRQ, RESERVED6, RESERVED7); + +DEFINE_FLOW_REG_BIT_ENUM(HALT_COP_EVENTS_JTAG, 28, DISABLED, ENABLED); +DEFINE_FLOW_REG_THREE_BIT_ENUM(HALT_COP_EVENTS_MODE, 29, FLOW_MODE_NONE, FLOW_MODE_RUN_AND_INT, FLOW_MODE_STOP, FLOW_MODE_STOP_AND_INT, FLOW_MODE_STOP_UNTIL_IRQ, FLOW_MODE_STOP_UNTIL_IRQ_AND_INT, FLOW_MODE_STOP_UNTIL_EVENT_AND_IRQ, RESERVED7); + +DEFINE_FLOW_REG_BIT_ENUM(FLOW_DBG_QUAL_FIQ2CCPLEX_ENABLE, 28, DISABLE, ENABLE); + +DEFINE_FLOW_REG_BIT_ENUM(RAM_REPAIR_REQ, 0, DISABLE, ENABLE); +DEFINE_FLOW_REG_BIT_ENUM(RAM_REPAIR_STS, 1, REQUESTED, DONE); + +DEFINE_FLOW_REG_BIT_ENUM(BPMP_CLUSTER_CONTROL_ACTIVE_CLUSTER, 0, FAST, SLOW); +DEFINE_FLOW_REG_BIT_ENUM(BPMP_CLUSTER_CONTROL_CLUSTER_SWITCH_ENABLE, 1, DISABLE, ENABLE); +DEFINE_FLOW_REG_BIT_ENUM(BPMP_CLUSTER_CONTROL_ACTIVE_CLUSTER_LOCK, 2, DISABLE, ENABLE); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_i2c.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_i2c.hpp new file mode 100644 index 00000000..68b076e9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_i2c.hpp @@ -0,0 +1,137 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#define I2C_I2C_CNFG (0x000) +#define I2C_I2C_CMD_ADDR0 (0x004) +#define I2C_I2C_CMD_DATA1 (0x00C) +#define I2C_I2C_STATUS (0x01C) +#define I2C_PACKET_TRANSFER_STATUS (0x058) +#define I2C_FIFO_CONTROL (0x05C) +#define I2C_FIFO_STATUS (0x060) +#define I2C_INTERRUPT_MASK_REGISTER (0x064) +#define I2C_INTERRUPT_STATUS_REGISTER (0x068) +#define I2C_CLK_DIVISOR_REGISTER (0x06C) +#define I2C_BUS_CLEAR_CONFIG (0x084) +#define I2C_BUS_CLEAR_STATUS (0x088) +#define I2C_CONFIG_LOAD (0x08C) +#define I2C_INTERFACE_TIMING_0 (0x094) +#define I2C_INTERFACE_TIMING_1 (0x098) +#define I2C_HS_INTERFACE_TIMING_0 (0x094) +#define I2C_HS_INTERFACE_TIMING_1 (0x098) + +#define I2C_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (I2C, NAME) +#define I2C_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (I2C, NAME, VALUE) +#define I2C_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (I2C, NAME, ENUM) +#define I2C_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(I2C, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + +#define DEFINE_I2C_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (I2C, NAME, __OFFSET__, __WIDTH__) +#define DEFINE_I2C_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (I2C, NAME, __OFFSET__, ZERO, ONE) +#define DEFINE_I2C_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (I2C, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) +#define DEFINE_I2C_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(I2C, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) +#define DEFINE_I2C_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (I2C, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + +/* I2C_CNFG */ +DEFINE_I2C_REG(I2C_CNFG_LENGTH, 1, 3); +DEFINE_I2C_REG_BIT_ENUM(I2C_CNFG_CMD1, 6, WRITE, READ); +DEFINE_I2C_REG_BIT_ENUM(I2C_CNFG_SEND, 9, NOP, GO); +DEFINE_I2C_REG_BIT_ENUM(I2C_CNFG_PACKET_MODE_EN, 10, NOP, GO); +DEFINE_I2C_REG_BIT_ENUM(I2C_CNFG_NEW_MASTER_FSM, 11, DISABLE, ENABLE); +DEFINE_I2C_REG_THREE_BIT_ENUM(I2C_CNFG_DEBOUNCE_CNT, 12, NO_DEBOUNCE, DEBOUNCE_2T, DEBOUNCE_4T, DEBOUNCE_6T, DEBOUNCE_8T, DEBOUNCE_10T, DEBOUNCE_12T, DEBOUNCE_14T); + +/* I2C_CMD_ADDR0 */ +DEFINE_I2C_REG_BIT_ENUM(I2C_CMD_ADDR0_7BIT_RW, 0, WRITE, READ); +DEFINE_I2C_REG(I2C_CMD_ADDR0_7BIT_ADDR, 1, 7); + +/* I2C_STATUS */ +DEFINE_I2C_REG_FOUR_BIT_ENUM(I2C_STATUS_CMD1_STAT, 0, SL1_XFER_SUCCESSFUL, SL1_NOACK_FOR_BYTE1, SL1_NOACK_FOR_BYTE2, SL1_NOACK_FOR_BYTE3, SL1_NOACK_FOR_BYTE4, SL1_NOACK_FOR_BYTE5, SL1_NOACK_FOR_BYTE6, SL1_NOACK_FOR_BYTE7, SL1_NOACK_FOR_BYTE8, SL1_NOACK_FOR_BYTE9, SL1_NOACK_FOR_BYTE10, RESERVED11, RESERVED12, RESERVED13, RESERVED14, RESERVED15); +DEFINE_I2C_REG_FOUR_BIT_ENUM(I2C_STATUS_CMD2_STAT, 4, SL2_XFER_SUCCESSFUL, SL2_NOACK_FOR_BYTE1, SL2_NOACK_FOR_BYTE2, SL2_NOACK_FOR_BYTE3, SL2_NOACK_FOR_BYTE4, SL2_NOACK_FOR_BYTE5, SL2_NOACK_FOR_BYTE6, SL2_NOACK_FOR_BYTE7, SL2_NOACK_FOR_BYTE8, SL2_NOACK_FOR_BYTE9, SL2_NOACK_FOR_BYTE10, RESERVED11, RESERVED12, RESERVED13, RESERVED14, RESERVED15); +DEFINE_I2C_REG_BIT_ENUM(I2C_STATUS_BUSY, 8, NOT_BUSY, BUSY); + +/* PACKET_TRANSFER_STATUS */ +DEFINE_I2C_REG_BIT_ENUM(PACKET_TRANSFER_STATUS_CONTROLLER_BUSY, 0, UNSET, SET); +DEFINE_I2C_REG_BIT_ENUM(PACKET_TRANSFER_STATUS_ARB_LOST, 1, UNSET, SET); +DEFINE_I2C_REG_BIT_ENUM(PACKET_TRANSFER_STATUS_NOACK_FOR_DATA, 2, UNSET, SET); +DEFINE_I2C_REG_BIT_ENUM(PACKET_TRANSFER_STATUS_NOACK_FOR_ADDR, 3, UNSET, SET); + +/* FIFO_CONTROL */ +DEFINE_I2C_REG_BIT_ENUM(FIFO_CONTROL_RX_FIFO_FLUSH, 0, UNSET, SET); +DEFINE_I2C_REG_BIT_ENUM(FIFO_CONTROL_TX_FIFO_FLUSH, 1, UNSET, SET); + +DEFINE_I2C_REG_TWO_BIT_ENUM(FIFO_CONTROL_FIFO_FLUSH, 0, RX_UNSET_TX_UNSET, RX_SET_TX_UNSET, RX_UNSET_TX_SET, RX_SET_TX_SET); + +DEFINE_I2C_REG(FIFO_CONTROL_RX_FIFO_TRIG, 2, 3); +DEFINE_I2C_REG(FIFO_CONTROL_TX_FIFO_TRIG, 5, 3); + +/* FIFO_STATUS */ +DEFINE_I2C_REG(FIFO_STATUS_RX_FIFO_FULL_CNT, 0, 4); +DEFINE_I2C_REG(FIFO_STATUS_TX_FIFO_EMPTY_CNT, 4, 4); + +/* INTERRUPT_MASK_REGISTER */ +DEFINE_I2C_REG_BIT_ENUM(INTERRUPT_MASK_REGISTER_RFIFO_DATA_REQ_INT_EN, 0, DISABLE, ENABLE); +DEFINE_I2C_REG_BIT_ENUM(INTERRUPT_MASK_REGISTER_TFIFO_DATA_REQ_INT_EN, 1, DISABLE, ENABLE); +DEFINE_I2C_REG_BIT_ENUM(INTERRUPT_MASK_REGISTER_ARB_LOST_INT_EN, 2, DISABLE, ENABLE); +DEFINE_I2C_REG_BIT_ENUM(INTERRUPT_MASK_REGISTER_NOACK_INT_EN, 3, DISABLE, ENABLE); +DEFINE_I2C_REG_BIT_ENUM(INTERRUPT_MASK_REGISTER_RFIFO_UNF_INT_EN, 4, DISABLE, ENABLE); +DEFINE_I2C_REG_BIT_ENUM(INTERRUPT_MASK_REGISTER_TFIFO_OVF_INT_EN, 5, DISABLE, ENABLE); +DEFINE_I2C_REG_BIT_ENUM(INTERRUPT_MASK_REGISTER_ALL_PACKETS_XFER_COMPLETE_INT_EN, 6, DISABLE, ENABLE); +DEFINE_I2C_REG_BIT_ENUM(INTERRUPT_MASK_REGISTER_PACKET_XFER_COMPLETE_INT_EN, 7, DISABLE, ENABLE); + +/* INTERRUPT_STATUS_REGISTER */ +DEFINE_I2C_REG_BIT_ENUM(INTERRUPT_STATUS_REGISTER_RFIFO_DATA_REQ, 0, UNSET, SET); +DEFINE_I2C_REG_BIT_ENUM(INTERRUPT_STATUS_REGISTER_TFIFO_DATA_REQ, 1, UNSET, SET); +DEFINE_I2C_REG_BIT_ENUM(INTERRUPT_STATUS_REGISTER_ARB_LOST, 2, UNSET, SET); +DEFINE_I2C_REG_BIT_ENUM(INTERRUPT_STATUS_REGISTER_NOACK, 3, UNSET, SET); +DEFINE_I2C_REG_BIT_ENUM(INTERRUPT_STATUS_REGISTER_RFIFO_UNF, 4, UNSET, SET); +DEFINE_I2C_REG_BIT_ENUM(INTERRUPT_STATUS_REGISTER_TFIFO_OVF, 5, UNSET, SET); +DEFINE_I2C_REG_BIT_ENUM(INTERRUPT_STATUS_REGISTER_ALL_PACKETS_XFER_COMPLETE, 6, UNSET, SET); +DEFINE_I2C_REG_BIT_ENUM(INTERRUPT_STATUS_REGISTER_PACKET_XFER_COMPLETE, 7, UNSET, SET); + +DEFINE_I2C_REG_BIT_ENUM(INTERRUPT_STATUS_REGISTER_BUS_CLEAR_DONE, 11, UNSET, SET); + +/* CLK_DIVISOR_REGISTER */ +DEFINE_I2C_REG(CLK_DIVISOR_REGISTER_HSMODE, 0, 16); +DEFINE_I2C_REG(CLK_DIVISOR_REGISTER_STD_FAST_MODE, 16, 16); + +/* BUS_CLEAR_CONFIG */ +DEFINE_I2C_REG_BIT_ENUM(BUS_CLEAR_CONFIG_BC_ENABLE, 0, DISABLE, ENABLE); +DEFINE_I2C_REG_BIT_ENUM(BUS_CLEAR_CONFIG_BC_TERMINATE, 1, THRESHOLD, IMMEDIATE); +DEFINE_I2C_REG_BIT_ENUM(BUS_CLEAR_CONFIG_BC_STOP_COND, 2, NO_STOP, STOP); +DEFINE_I2C_REG(BUS_CLEAR_CONFIG_BC_SCLK_THRESHOLD, 16, 8); + +/* BUS_CLEAR_STATUS */ +DEFINE_I2C_REG_BIT_ENUM(BUS_CLEAR_STATUS_BC_STATUS, 0, NOT_CLEARED, CLEARED); + +/* CONFIG_LOAD */ +DEFINE_I2C_REG_BIT_ENUM(CONFIG_LOAD_MSTR_CONFIG_LOAD, 0, DISABLE, ENABLE); +DEFINE_I2C_REG_BIT_ENUM(CONFIG_LOAD_SLV_CONFIG_LOAD, 1, DISABLE, ENABLE); +DEFINE_I2C_REG_BIT_ENUM(CONFIG_LOAD_TIMEOUT_CONFIG_LOAD, 2, DISABLE, ENABLE); +DEFINE_I2C_REG(CONFIG_LOAD_RESERVED_BIT_5, 5, 1); + +/* INTERFACE_TIMING_0 */ +DEFINE_I2C_REG(INTERFACE_TIMING_0_TLOW, 0, 6); +DEFINE_I2C_REG(INTERFACE_TIMING_0_THIGH, 8, 6); + +/* HS_INTERFACE_TIMING_0 */ +DEFINE_I2C_REG(HS_INTERFACE_TIMING_0_HS_TLOW, 0, 6); +DEFINE_I2C_REG(HS_INTERFACE_TIMING_0_HS_THIGH, 8, 6); + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_i2s.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_i2s.hpp new file mode 100644 index 00000000..7132a31b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_i2s.hpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#define I2S_REG(x) (0x702d1000 + x) + + +#define I2S0_I2S_CG (0x088) +#define I2S0_I2S_CTRL (0x0A0) +#define I2S1_I2S_CG (0x188) +#define I2S1_I2S_CTRL (0x1A0) +#define I2S2_I2S_CG (0x288) +#define I2S2_I2S_CTRL (0x2A0) +#define I2S3_I2S_CG (0x388) +#define I2S3_I2S_CTRL (0x3A0) +#define I2S4_I2S_CG (0x488) +#define I2S4_I2S_CTRL (0x4A0) + +#define I2S_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (I2S, NAME) +#define I2S_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (I2S, NAME, VALUE) +#define I2S_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (I2S, NAME, ENUM) +#define I2S_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(I2S, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + +#define DEFINE_I2S_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (I2S, NAME, __OFFSET__, __WIDTH__) +#define DEFINE_I2S_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (I2S, NAME, __OFFSET__, ZERO, ONE) +#define DEFINE_I2S_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (I2S, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) +#define DEFINE_I2S_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(I2S, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) +#define DEFINE_I2S_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (I2S, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + + +DEFINE_I2S_REG_BIT_ENUM(I2S_CG_SLCG_ENABLE, 0, FALSE, TRUE); + +DEFINE_I2S_REG_BIT_ENUM(I2S_CTRL_MASTER, 10, DISABLE, ENABLE); \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_ictlr.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_ictlr.hpp new file mode 100644 index 00000000..541573cc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_ictlr.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#define PRI_ICTLR(n) (0x60004000 + n) +#define SEC_ICTLR(n) (0x60004100 + n) +#define TRI_ICTLR(n) (0x60004200 + n) +#define QUAD_ICTLR(n) (0x60004300 + n) +#define PENTA_ICTLR(n) (0x60004400 + n) +#define HEXA_ICTLR(n) (0x60004500 + n) + +#define ICTLR_COP_IER_CLR (0x038) + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_mc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_mc.hpp new file mode 100644 index 00000000..d58e8b5b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_mc.hpp @@ -0,0 +1,579 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#define MC_INTSTATUS (0x000) +#define MC_INTMASK (0x004) +#define MC_ERR_STATUS (0x008) +#define MC_ERR_ADR (0x00C) +#define MC_SMMU_CONFIG (0x010) +#define MC_SMMU_PTB_ASID (0x01C) +#define MC_SMMU_PTB_DATA (0x020) +#define MC_SMMU_TLB_FLUSH (0x030) +#define MC_SMMU_PTC_FLUSH_0 (0x034) +#define MC_EMEM_CFG (0x050) +#define MC_EMEM_ADR_CFG (0x054) +#define MC_EMEM_ADR_CFG_DEV0 (0x058) +#define MC_EMEM_ADR_CFG_DEV1 (0x05C) +#define MC_EMEM_ADR_CFG_CHANNEL_MASK (0x060) +#define MC_EMEM_ADR_CFG_BANK_MASK_0 (0x064) +#define MC_EMEM_ADR_CFG_BANK_MASK_1 (0x068) +#define MC_EMEM_ADR_CFG_BANK_MASK_2 (0x06C) +#define MC_EMEM_ARB_CFG (0x090) +#define MC_EMEM_ARB_OUTSTANDING_REQ (0x094) +#define MC_EMEM_ARB_TIMING_RCD (0x098) +#define MC_EMEM_ARB_TIMING_RP (0x09C) +#define MC_EMEM_ARB_TIMING_RC (0x0A0) +#define MC_EMEM_ARB_TIMING_RAS (0x0A4) +#define MC_EMEM_ARB_TIMING_FAW (0x0A8) +#define MC_EMEM_ARB_TIMING_RRD (0x0AC) +#define MC_EMEM_ARB_TIMING_RAP2PRE (0x0B0) +#define MC_EMEM_ARB_TIMING_WAP2PRE (0x0B4) +#define MC_EMEM_ARB_TIMING_R2R (0x0B8) +#define MC_EMEM_ARB_TIMING_W2W (0x0BC) +#define MC_EMEM_ARB_TIMING_R2W (0x0C0) +#define MC_EMEM_ARB_TIMING_W2R (0x0C4) +#define MC_EMEM_ARB_MISC2 (0x0C8) +#define MC_EMEM_ARB_DA_TURNS (0x0D0) +#define MC_EMEM_ARB_DA_COVERS (0x0D4) +#define MC_EMEM_ARB_MISC0 (0x0D8) +#define MC_EMEM_ARB_MISC1 (0x0DC) +#define MC_EMEM_ARB_RING1_THROTTLE (0x0E0) +#define MC_EMEM_ARB_OVERRIDE (0x0E8) +#define MC_EMEM_ARB_RSV (0x0EC) +#define MC_CLKEN_OVERRIDE (0x0F4) +#define MC_TIMING_CONTROL_DBG (0x0F8) +#define MC_TIMING_CONTROL (0x0FC) +#define MC_CLIENT_HOTRESET_CTRL (0x200) +#define MC_CLIENT_HOTRESET_STATUS (0x204) +#define MC_SMMU_AFI_ASID (0x238) +#define MC_SMMU_DC_ASID (0x240) +#define MC_SMMU_DCB_ASID (0x244) +#define MC_SMMU_HC_ASID (0x250) +#define MC_SMMU_HDA_ASID (0x254) +#define MC_SMMU_ISP2_ASID (0x258) +#define MC_SMMU_MSENC_NVENC_ASID (0x264) +#define MC_SMMU_NV_ASID (0x268) +#define MC_SMMU_NV2_ASID (0x26C) +#define MC_SMMU_PPCS_ASID (0x270) +#define MC_SMMU_SATA_ASID (0x274) +#define MC_SMMU_VI_ASID (0x280) +#define MC_SMMU_VIC_ASID (0x284) +#define MC_SMMU_XUSB_HOST_ASID (0x288) +#define MC_SMMU_XUSB_DEV_ASID (0x28C) +#define MC_SMMU_TSEC_ASID (0x294) +#define MC_LATENCY_ALLOWANCE_AVPC_0 (0x2E4) +#define MC_LATENCY_ALLOWANCE_DC_0 (0x2E8) +#define MC_LATENCY_ALLOWANCE_DC_1 (0x2EC) +#define MC_LATENCY_ALLOWANCE_DCB_0 (0x2F4) +#define MC_LATENCY_ALLOWANCE_DCB_1 (0x2F8) +#define MC_LATENCY_ALLOWANCE_HC_0 (0x310) +#define MC_LATENCY_ALLOWANCE_HC_1 (0x314) +#define MC_LATENCY_ALLOWANCE_MPCORE_0 (0x320) +#define MC_LATENCY_ALLOWANCE_NVENC_0 (0x328) +#define MC_LATENCY_ALLOWANCE_PPCS_0 (0x344) +#define MC_LATENCY_ALLOWANCE_PPCS_1 (0x348) +#define MC_LATENCY_ALLOWANCE_ISP2_0 (0x370) +#define MC_LATENCY_ALLOWANCE_ISP2_1 (0x374) +#define MC_LATENCY_ALLOWANCE_XUSB_0 (0x37C) +#define MC_LATENCY_ALLOWANCE_XUSB_1 (0x380) +#define MC_LATENCY_ALLOWANCE_TSEC_0 (0x390) +#define MC_LATENCY_ALLOWANCE_VIC_0 (0x394) +#define MC_LATENCY_ALLOWANCE_VI2_0 (0x398) +#define MC_LATENCY_ALLOWANCE_GPU_0 (0x3AC) +#define MC_LATENCY_ALLOWANCE_SDMMCA_0 (0x3B8) +#define MC_LATENCY_ALLOWANCE_SDMMCAA_0 (0x3BC) +#define MC_LATENCY_ALLOWANCE_SDMMC_0 (0x3C0) +#define MC_LATENCY_ALLOWANCE_SDMMCAB_0 (0x3C4) +#define MC_LATENCY_ALLOWANCE_NVDEC_0 (0x3D8) +#define MC_LATENCY_ALLOWANCE_GPU2_0 (0x3E8) +#define MC_VIDEO_PROTECT_VPR_OVERRIDE (0x418) +#define MC_DIS_PTSA_RATE (0x41C) +#define MC_DIS_PTSA_MIN (0x420) +#define MC_DIS_PTSA_MAX (0x424) +#define MC_DISB_PTSA_RATE (0x428) +#define MC_DISB_PTSA_MIN (0x42C) +#define MC_DISB_PTSA_MAX (0x430) +#define MC_VE_PTSA_RATE (0x434) +#define MC_VE_PTSA_MIN (0x438) +#define MC_VE_PTSA_MAX (0x43C) +#define MC_MLL_MPCORER_PTSA_RATE (0x44C) +#define MC_RING1_PTSA_RATE (0x47C) +#define MC_RING1_PTSA_MIN (0x480) +#define MC_RING1_PTSA_MAX (0x484) +#define MC_PCX_PTSA_RATE (0x4AC) +#define MC_PCX_PTSA_MIN (0x4B0) +#define MC_PCX_PTSA_MAX (0x4B4) +#define MC_MSE_PTSA_RATE (0x4C4) +#define MC_MSE_PTSA_MIN (0x4C8) +#define MC_MSE_PTSA_MAX (0x4CC) +#define MC_AHB_PTSA_RATE (0x4DC) +#define MC_AHB_PTSA_MIN (0x4E0) +#define MC_AHB_PTSA_MAX (0x4E4) +#define MC_APB_PTSA_RATE (0x4E8) +#define MC_APB_PTSA_MIN (0x4EC) +#define MC_APB_PTSA_MAX (0x4F0) +#define MC_FTOP_PTSA_RATE (0x50C) +#define MC_HOST_PTSA_RATE (0x518) +#define MC_HOST_PTSA_MIN (0x51C) +#define MC_HOST_PTSA_MAX (0x520) +#define MC_USBX_PTSA_RATE (0x524) +#define MC_USBX_PTSA_MIN (0x528) +#define MC_USBX_PTSA_MAX (0x52C) +#define MC_USBD_PTSA_RATE (0x530) +#define MC_USBD_PTSA_MIN (0x534) +#define MC_USBD_PTSA_MAX (0x538) +#define MC_GK_PTSA_RATE (0x53C) +#define MC_GK_PTSA_MIN (0x540) +#define MC_GK_PTSA_MAX (0x544) +#define MC_AUD_PTSA_RATE (0x548) +#define MC_AUD_PTSA_MIN (0x54C) +#define MC_AUD_PTSA_MAX (0x550) +#define MC_VICPC_PTSA_RATE (0x554) +#define MC_VICPC_PTSA_MIN (0x558) +#define MC_VICPC_PTSA_MAX (0x55C) +#define MC_JPG_PTSA_RATE (0x584) +#define MC_JPG_PTSA_MIN (0x588) +#define MC_JPG_PTSA_MAX (0x58C) +#define MC_VIDEO_PROTECT_VPR_OVERRIDE1 (0x590) +#define MC_GK2_PTSA_RATE (0x610) +#define MC_GK2_PTSA_MIN (0x614) +#define MC_GK2_PTSA_MAX (0x618) +#define MC_SDM_PTSA_RATE (0x61C) +#define MC_SDM_PTSA_MIN (0x620) +#define MC_SDM_PTSA_MAX (0x624) +#define MC_HDAPC_PTSA_RATE (0x628) +#define MC_HDAPC_PTSA_MIN (0x62C) +#define MC_HDAPC_PTSA_MAX (0x630) +#define MC_VIDEO_PROTECT_BOM (0x648) +#define MC_EMEM_CFG_ACCESS_CTRL (0x664) +#define MC_SEC_CARVEOUT_BOM (0x670) +#define MC_SEC_CARVEOUT_SIZE_MB (0x674) +#define MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0A (0x690) +#define MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0AB (0x694) +#define MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0B (0x698) +#define MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0BB (0x69C) +#define MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0C (0x6A0) +#define MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0CB (0x6A4) +#define MC_EMEM_ARB_TIMING_RFCPB (0x6C0) +#define MC_EMEM_ARB_TIMING_CCDMW (0x6C4) +#define MC_EMEM_ARB_REFPB_HP_CTRL (0x6F0) +#define MC_EMEM_ARB_REFPB_BANK_CTRL (0x6F4) +#define MC_UNTRANSLATED_REGION_CHECK (0x948) +#define MC_PTSA_GRANT_DECREMENT (0x960) +#define MC_EMEM_ARB_OVERRIDE_1 (0x968) +#define MC_CLIENT_HOTRESET_CTRL_1 (0x970) +#define MC_CLIENT_HOTRESET_STATUS_1 (0x974) +#define MC_VIDEO_PROTECT_BOM_ADR_HI (0x978) +#define MC_SMMU_PTC_FLUSH_1 (0x9B8) +#define MC_SEC_CARVEOUT_ADR_HI (0x9D4) +#define MC_DA_CONFIG0 (0x9DC) +#define MC_SMMU_DC1_ASID (0xA88) +#define MC_SMMU_SDMMC1A_ASID (0xA94) +#define MC_SMMU_SDMMC2A_ASID (0xA98) +#define MC_SMMU_SDMMC3A_ASID (0xA9C) +#define MC_SMMU_SDMMC4A_ASID (0xAA0) +#define MC_SMMU_ISP2B_ASID (0xAA4) +#define MC_SMMU_GPU_ASID (0xAA8) +#define MC_SMMU_GPUB_ASID (0xAAC) +#define MC_SMMU_PPCS2_ASID (0xAB0) +#define MC_SMMU_NVDEC_ASID (0xAB4) +#define MC_SMMU_APE_ASID (0xAB8) +#define MC_SMMU_SE_ASID (0xABC) +#define MC_SMMU_NVJPG_ASID (0xAC0) +#define MC_SMMU_HC1_ASID (0xAC4) +#define MC_SMMU_SE1_ASID (0xAC8) +#define MC_SMMU_AXIAP_ASID (0xACC) +#define MC_SMMU_ETR_ASID (0xAD0) +#define MC_SMMU_TSECB_ASID (0xAD4) +#define MC_SMMU_TSEC1_ASID (0xAD8) +#define MC_SMMU_TSECB1_ASID (0xADC) +#define MC_SMMU_NVDEC1_ASID (0xAE0) +#define MC_EMEM_ARB_DHYST_CTRL (0xBCC) +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_0 (0xBD0) +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_1 (0xBD4) +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_2 (0xBD8) +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_3 (0xBDC) +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_4 (0xBE0) +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_5 (0xBE4) +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_6 (0xBE8) +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_7 (0xBEC) +#define MC_ERR_GENERALIZED_CARVEOUT_STATUS (0xC00) + + +#define MC_SMMU_TLB_CONFIG (0x014) +#define MC_SMMU_PTC_CONFIG (0x018) + +#define MC_SMMU_AVPC_ASID (0x23C) +#define MC_SMMU_PPCS1_ASID (0x298) + +#define MC_SECURITY_CFG0 (0x070) +#define MC_SECURITY_CFG1 (0x074) +#define MC_SECURITY_CFG3 (0x9BC) + +#define MC_SMMU_TRANSLATION_ENABLE_0 (0x228) +#define MC_SMMU_TRANSLATION_ENABLE_1 (0x22C) +#define MC_SMMU_TRANSLATION_ENABLE_2 (0x230) +#define MC_SMMU_TRANSLATION_ENABLE_3 (0x234) +#define MC_SMMU_TRANSLATION_ENABLE_4 (0xB98) + +#define MC_SMMU_ASID_SECURITY (0x038) +#define MC_SMMU_ASID_SECURITY_1 (0x03c) +#define MC_SMMU_ASID_SECURITY_2 (0x9e0) +#define MC_SMMU_ASID_SECURITY_3 (0x9e4) +#define MC_SMMU_ASID_SECURITY_4 (0x9e8) +#define MC_SMMU_ASID_SECURITY_5 (0x9ec) +#define MC_SMMU_ASID_SECURITY_6 (0x9f0) +#define MC_SMMU_ASID_SECURITY_7 (0x9f4) + +#define MC_IRAM_BOM (0x65c) +#define MC_IRAM_TOM (0x660) +#define MC_IRAM_REG_CTRL (0x964) + +#define MC_SEC_CARVEOUT_BOM (0x670) +#define MC_SEC_CARVEOUT_SIZE_MB (0x674) +#define MC_SEC_CARVEOUT_REG_CTRL (0x678) + +#define MC_VIDEO_PROTECT_BOM (0x648) +#define MC_VIDEO_PROTECT_SIZE_MB (0x64c) +#define MC_VIDEO_PROTECT_REG_CTRL (0x650) +#define MC_VIDEO_PROTECT_GPU_OVERRIDE_0 (0x984) +#define MC_VIDEO_PROTECT_GPU_OVERRIDE_1 (0x988) + +#define MC_MTS_CARVEOUT_BOM (0x9a0) +#define MC_MTS_CARVEOUT_SIZE_MB (0x9a4) +#define MC_MTS_CARVEOUT_ADR_HI (0x9a8) +#define MC_MTS_CARVEOUT_REG_CTRL (0x9ac) + +#define MC_SECURITY_CARVEOUT1_CFG0 (0xc08) +#define MC_SECURITY_CARVEOUT1_BOM (0xc0c) +#define MC_SECURITY_CARVEOUT1_BOM_HI (0xc10) +#define MC_SECURITY_CARVEOUT1_SIZE_128KB (0xc14) +#define MC_SECURITY_CARVEOUT1_CLIENT_ACCESS0 (0xc18) +#define MC_SECURITY_CARVEOUT1_CLIENT_ACCESS1 (0xc1c) +#define MC_SECURITY_CARVEOUT1_CLIENT_ACCESS2 (0xc20) +#define MC_SECURITY_CARVEOUT1_CLIENT_ACCESS3 (0xc24) +#define MC_SECURITY_CARVEOUT1_CLIENT_ACCESS4 (0xc28) +#define MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS0 (0xc2c) +#define MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS1 (0xc30) +#define MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS2 (0xc34) +#define MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS3 (0xc38) +#define MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS4 (0xc3c) + +#define MC_SECURITY_CARVEOUT2_CFG0 (0xc58) +#define MC_SECURITY_CARVEOUT2_BOM (0xc5c) +#define MC_SECURITY_CARVEOUT2_BOM_HI (0xc60) +#define MC_SECURITY_CARVEOUT2_SIZE_128KB (0xc64) +#define MC_SECURITY_CARVEOUT2_CLIENT_ACCESS0 (0xc68) +#define MC_SECURITY_CARVEOUT2_CLIENT_ACCESS1 (0xc6c) +#define MC_SECURITY_CARVEOUT2_CLIENT_ACCESS2 (0xc70) +#define MC_SECURITY_CARVEOUT2_CLIENT_ACCESS3 (0xc74) +#define MC_SECURITY_CARVEOUT2_CLIENT_ACCESS4 (0xc78) +#define MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS0 (0xc7c) +#define MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS1 (0xc80) +#define MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS2 (0xc84) +#define MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS3 (0xc88) +#define MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS4 (0xc8c) + +#define MC_SECURITY_CARVEOUT3_CFG0 (0xca8) +#define MC_SECURITY_CARVEOUT3_BOM (0xcac) +#define MC_SECURITY_CARVEOUT3_BOM_HI (0xcb0) +#define MC_SECURITY_CARVEOUT3_SIZE_128KB (0xcb4) +#define MC_SECURITY_CARVEOUT3_CLIENT_ACCESS0 (0xcb8) +#define MC_SECURITY_CARVEOUT3_CLIENT_ACCESS1 (0xcbc) +#define MC_SECURITY_CARVEOUT3_CLIENT_ACCESS2 (0xcc0) +#define MC_SECURITY_CARVEOUT3_CLIENT_ACCESS3 (0xcc4) +#define MC_SECURITY_CARVEOUT3_CLIENT_ACCESS4 (0xcc8) +#define MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS0 (0xccc) +#define MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS1 (0xcd0) +#define MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS2 (0xcd4) +#define MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS3 (0xcd8) +#define MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS4 (0xcdc) + +#define MC_SECURITY_CARVEOUT4_CFG0 (0xcf8) +#define MC_SECURITY_CARVEOUT4_BOM (0xcfc) +#define MC_SECURITY_CARVEOUT4_BOM_HI (0xd00) +#define MC_SECURITY_CARVEOUT4_SIZE_128KB (0xd04) +#define MC_SECURITY_CARVEOUT4_CLIENT_ACCESS0 (0xd08) +#define MC_SECURITY_CARVEOUT4_CLIENT_ACCESS1 (0xd0c) +#define MC_SECURITY_CARVEOUT4_CLIENT_ACCESS2 (0xd10) +#define MC_SECURITY_CARVEOUT4_CLIENT_ACCESS3 (0xd14) +#define MC_SECURITY_CARVEOUT4_CLIENT_ACCESS4 (0xd18) +#define MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS0 (0xd1c) +#define MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS1 (0xd20) +#define MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS2 (0xd24) +#define MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS3 (0xd28) +#define MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS4 (0xd2c) + +#define MC_SECURITY_CARVEOUT5_CFG0 (0xd48) +#define MC_SECURITY_CARVEOUT5_BOM (0xd4c) +#define MC_SECURITY_CARVEOUT5_BOM_HI (0xd50) +#define MC_SECURITY_CARVEOUT5_SIZE_128KB (0xd54) +#define MC_SECURITY_CARVEOUT5_CLIENT_ACCESS0 (0xd58) +#define MC_SECURITY_CARVEOUT5_CLIENT_ACCESS1 (0xd5c) +#define MC_SECURITY_CARVEOUT5_CLIENT_ACCESS2 (0xd60) +#define MC_SECURITY_CARVEOUT5_CLIENT_ACCESS3 (0xd64) +#define MC_SECURITY_CARVEOUT5_CLIENT_ACCESS4 (0xd68) +#define MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS0 (0xd6c) +#define MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS1 (0xd70) +#define MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS2 (0xd74) +#define MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS3 (0xd78) +#define MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS4 (0xd7c) + +#define MC_STAT_CONTROL (0x100) +#define MC_STAT_EMC_CLOCK_LIMIT (0x108) +#define MC_STAT_EMC_CLOCK_LIMIT_MSBS (0x10c) +#define MC_STAT_EMC_FILTER_SET0_ADR_LIMIT_LO (0x118) +#define MC_STAT_EMC_FILTER_SET0_ADR_LIMIT_HI (0x11c) +#define MC_STAT_EMC_FILTER_SET0_SPARE (0x124) +#define MC_STAT_EMC_FILTER_SET0_CLIENT_0 (0x128) +#define MC_STAT_EMC_FILTER_SET0_CLIENT_1 (0x12c) +#define MC_STAT_EMC_FILTER_SET0_CLIENT_2 (0x130) +#define MC_STAT_EMC_FILTER_SET0_CLIENT_3 (0x134) +#define MC_STAT_EMC_SET0_COUNT (0x138) +#define MC_STAT_EMC_SET0_COUNT_MSBS (0x13c) +#define MC_STAT_EMC_FILTER_SET1_ADR_LIMIT_LO (0x158) +#define MC_STAT_EMC_FILTER_SET1_ADR_LIMIT_HI (0x15c) +#define MC_STAT_EMC_FILTER_SET1_SPARE (0x164) +#define MC_STAT_EMC_FILTER_SET1_CLIENT_0 (0x168) +#define MC_STAT_EMC_FILTER_SET1_CLIENT_1 (0x16c) +#define MC_STAT_EMC_FILTER_SET1_CLIENT_2 (0x170) +#define MC_STAT_EMC_FILTER_SET1_CLIENT_3 (0x174) +#define MC_STAT_EMC_SET1_COUNT (0x178) +#define MC_STAT_EMC_SET1_COUNT_MSBS (0x17c) +#define MC_STAT_EMC_FILTER_SET0_ADR_LIMIT_UPPER (0xa20) +#define MC_STAT_EMC_FILTER_SET1_ADR_LIMIT_UPPER (0xa24) +#define MC_STAT_EMC_FILTER_SET0_CLIENT_4 (0xb88) +#define MC_STAT_EMC_FILTER_SET1_CLIENT_4 (0xb8c) +#define MC_STAT_EMC_FILTER_SET0_CLIENT_5 (0xbc4) +#define MC_STAT_EMC_FILTER_SET1_CLIENT_5 (0xbc8) + +#define MC_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (MC, NAME) +#define MC_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (MC, NAME, VALUE) +#define MC_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (MC, NAME, ENUM) +#define MC_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(MC, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + +#define DEFINE_MC_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (MC, NAME, __OFFSET__, __WIDTH__) +#define DEFINE_MC_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (MC, NAME, __OFFSET__, ZERO, ONE) +#define DEFINE_MC_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (MC, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) +#define DEFINE_MC_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(MC, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) +#define DEFINE_MC_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (MC, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + +DEFINE_MC_REG_BIT_ENUM(SMMU_CONFIG_SMMU_ENABLE, 0, DISABLE, ENABLE); + +DEFINE_MC_REG(SMMU_TLB_CONFIG_TLB_ACTIVE_LINES, 0, 6); +DEFINE_MC_REG_BIT_ENUM(SMMU_TLB_CONFIG_TLB_ROUND_ROBIN_ARBITRATION, 28, DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(SMMU_TLB_CONFIG_TLB_HIT_UNDER_MISS, 29, DISABLE, ENABLE); + +DEFINE_MC_REG(SMMU_PTC_CONFIG_PTC_INDEX_MAP, 0, 7); +DEFINE_MC_REG(SMMU_PTC_CONFIG_PTC_REQ_LIMIT, 24, 4); +DEFINE_MC_REG_BIT_ENUM(SMMU_PTC_CONFIG_PTC_CACHE_ENABLE, 29, DISABLE, ENABLE); + +DEFINE_MC_REG(SMMU_PTB_ASID_CURRENT_ASID, 0, 7); + +DEFINE_MC_REG(SMMU_PTB_DATA_ASID_PDE_BASE, 0, 22); +DEFINE_MC_REG_BIT_ENUM(SMMU_PTB_DATA_ASID_NONSECURE, 29, DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(SMMU_PTB_DATA_ASID_WRITABLE, 30, DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(SMMU_PTB_DATA_ASID_READABLE, 31, DISABLE, ENABLE); + +DEFINE_MC_REG(SMMU_AVPC_ASID_AVPC_ASID, 0, 7); +DEFINE_MC_REG_BIT_ENUM(SMMU_AVPC_ASID_AVPC_SMMU_ENABLE, 31, DISABLE, ENABLE); + +DEFINE_MC_REG(SMMU_PPCS1_ASID_PPCS1_ASID, 0, 7); +DEFINE_MC_REG_BIT_ENUM(SMMU_PPCS1_ASID_PPCS1_SMMU_ENABLE, 31, DISABLE, ENABLE); + +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_0, 0, NONSECURE, SECURE); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_1, 1, NONSECURE, SECURE); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_2, 2, NONSECURE, SECURE); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_3, 3, NONSECURE, SECURE); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_4, 4, NONSECURE, SECURE); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_5, 5, NONSECURE, SECURE); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_6, 6, NONSECURE, SECURE); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_7, 7, NONSECURE, SECURE); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_8, 8, NONSECURE, SECURE); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_9, 9, NONSECURE, SECURE); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_10, 10, NONSECURE, SECURE); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_11, 11, NONSECURE, SECURE); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_12, 12, NONSECURE, SECURE); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_13, 13, NONSECURE, SECURE); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_14, 14, NONSECURE, SECURE); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_SECURE_ASIDS_15, 15, NONSECURE, SECURE); + +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_PROMOTING_ASIDS_0, 16, NONPROMOTING, PROMOTING); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_PROMOTING_ASIDS_1, 17, NONPROMOTING, PROMOTING); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_PROMOTING_ASIDS_2, 18, NONPROMOTING, PROMOTING); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_PROMOTING_ASIDS_3, 19, NONPROMOTING, PROMOTING); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_PROMOTING_ASIDS_4, 20, NONPROMOTING, PROMOTING); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_PROMOTING_ASIDS_5, 21, NONPROMOTING, PROMOTING); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_PROMOTING_ASIDS_6, 22, NONPROMOTING, PROMOTING); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_PROMOTING_ASIDS_7, 23, NONPROMOTING, PROMOTING); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_PROMOTING_ASIDS_8, 24, NONPROMOTING, PROMOTING); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_PROMOTING_ASIDS_9, 25, NONPROMOTING, PROMOTING); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_PROMOTING_ASIDS_10, 26, NONPROMOTING, PROMOTING); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_PROMOTING_ASIDS_11, 27, NONPROMOTING, PROMOTING); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_PROMOTING_ASIDS_12, 28, NONPROMOTING, PROMOTING); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_PROMOTING_ASIDS_13, 29, NONPROMOTING, PROMOTING); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_PROMOTING_ASIDS_14, 30, NONPROMOTING, PROMOTING); +DEFINE_MC_REG_BIT_ENUM(SMMU_ASID_SECURITY_PROMOTING_ASIDS_15, 31, NONPROMOTING, PROMOTING); + +DEFINE_MC_REG(SECURITY_CFG0_SECURITY_BOM, 20, 12); +DEFINE_MC_REG(SECURITY_CFG1_SECURITY_SIZE, 0, 13); +DEFINE_MC_REG(SECURITY_CFG3_SECURITY_BOM_HI, 0, 2); + +DEFINE_MC_REG_BIT_ENUM(SEC_CARVEOUT_REG_CTRL_SEC_CARVEOUT_WRITE_ACCESS, 0, ENABLED, DISABLED); + +DEFINE_MC_REG_BIT_ENUM(VIDEO_PROTECT_REG_CTRL_VIDEO_PROTECT_WRITE_ACCESS, 0, ENABLED, DISABLED); +DEFINE_MC_REG_BIT_ENUM(VIDEO_PROTECT_REG_CTRL_VIDEO_PROTECT_ALLOW_TZ_WRITE, 1, DISABLED, ENABLED); + +DEFINE_MC_REG_BIT_ENUM(MTS_CARVEOUT_REG_CTRL_MTS_CARVEOUT_WRITE_ACCESS, 0, ENABLED, DISABLED); + +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_PROTECT_MODE, 0, LOCKBIT_SECURE, TZ_SECURE); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_LOCK_MODE, 1, UNLOCKED, LOCKED); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_ADDRESS_TYPE, 2, ANY_ADDRESS, UNTRANSLATED_ONLY); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL0, 3, DISABLED, ENABLED); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL1, 4, DISABLED, ENABLED); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL2, 5, DISABLED, ENABLED); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_READ_ACCESS_LEVEL3, 6, DISABLED, ENABLED); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL0, 7, DISABLED, ENABLED); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL1, 8, DISABLED, ENABLED); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL2, 9, DISABLED, ENABLED); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_WRITE_ACCESS_LEVEL3, 10, DISABLED, ENABLED); +DEFINE_MC_REG(SECURITY_CARVEOUT_CFG0_APERTURE_ID, 11, 3); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL0, 14, ENABLE_CHECKS, DISABLE_CHECKS); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL1, 15, ENABLE_CHECKS, DISABLE_CHECKS); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL2, 16, ENABLE_CHECKS, DISABLE_CHECKS); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_DISABLE_READ_CHECK_ACCESS_LEVEL3, 17, ENABLE_CHECKS, DISABLE_CHECKS); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL0, 18, ENABLE_CHECKS, DISABLE_CHECKS); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL1, 19, ENABLE_CHECKS, DISABLE_CHECKS); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL2, 20, ENABLE_CHECKS, DISABLE_CHECKS); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_DISABLE_WRITE_CHECK_ACCESS_LEVEL3, 21, ENABLE_CHECKS, DISABLE_CHECKS); + +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_SEND_CFG_TO_GPU, 22, DISABLED, ENABLED); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_WR_EN, 23, DISABLED, BYPASS_CHECK); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_TZ_GLOBAL_RD_EN, 24, DISABLED, BYPASS_CHECK); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_ALLOW_APERTURE_ID_MISMATCH, 25, DISABLED, ENABLED); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_FORCE_APERTURE_ID_MATCH, 26, DISABLED, ENABLED); +DEFINE_MC_REG_BIT_ENUM(SECURITY_CARVEOUT_CFG0_IS_WPR, 27, DISABLED, ENABLED); + +#define MC_CLIENT_ACCESS_NUM_CLIENTS 32 + +/* _ACCESS0 */ +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS0_PTCR, ( 0 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 0)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS0_DISPLAY0A, ( 1 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 0)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS0_DISPLAY0AB, ( 2 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 0)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS0_DISPLAY0B, ( 3 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 0)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS0_DISPLAY0BB, ( 4 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 0)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS0_DISPLAY0C, ( 5 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 0)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS0_DISPLAY0CB, ( 6 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 0)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS0_AFIR, ( 14 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 0)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS0_AVPCARM7R, ( 15 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 0)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS0_DISPLAYHC, ( 16 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 0)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS0_DISPLAYHCB, ( 17 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 0)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS0_HDAR, ( 21 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 0)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS0_HOST1XDMAR, ( 22 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 0)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS0_HOST1XR, ( 23 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 0)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS0_NVENCSRD, ( 28 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 0)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS0_PPCSAHBDMAR, ( 29 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 0)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS0_PPCSAHBSLVR, ( 30 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 0)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS0_SATAR, ( 31 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 0)), DISABLE, ENABLE); + +/* _ACCESS1 */ +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS1_VDEBSEVR, ( 34 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 1)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS1_VDEMBER, ( 35 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 1)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS1_VDEMCER, ( 36 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 1)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS1_VDETPER, ( 37 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 1)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS1_MPCORELPR, ( 38 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 1)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS1_MPCORER, ( 39 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 1)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS1_NVENCSWR, ( 43 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 1)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS1_AFIW, ( 49 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 1)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS1_AVPCARM7W, ( 50 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 1)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS1_HDAW, ( 53 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 1)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS1_HOST1XW, ( 54 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 1)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS1_MPCORELPW, ( 56 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 1)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS1_MPCOREW, ( 57 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 1)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS1_PPCSAHBDMAW, ( 59 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 1)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS1_PPCSAHBSLVW, ( 60 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 1)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS1_SATAW, ( 61 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 1)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS1_VDEBSEVW, ( 62 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 1)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS1_VDEDBGW, ( 63 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 1)), DISABLE, ENABLE); + +/* _ACCESS2 */ +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS2_VDEMBEW, ( 64 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 2)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS2_VDETPMW, ( 65 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 2)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS2_ISPRA, ( 68 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 2)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS2_ISPWA, ( 70 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 2)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS2_ISPWB, ( 71 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 2)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS2_XUSB_HOSTR, ( 74 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 2)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS2_XUSB_HOSTW, ( 75 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 2)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS2_XUSB_DEVR, ( 76 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 2)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS2_XUSB_DEVW, ( 77 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 2)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS2_ISPRAB, ( 78 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 2)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS2_ISPWAB, ( 80 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 2)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS2_ISPWBB, ( 81 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 2)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS2_TSECSRD, ( 84 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 2)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS2_TSECSWR, ( 85 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 2)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS2_A9AVPSCR, ( 86 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 2)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS2_A9AVPSCW, ( 87 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 2)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS2_GPUSRD, ( 88 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 2)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS2_GPUSWR, ( 89 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 2)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS2_DISPLAYT, ( 90 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 2)), DISABLE, ENABLE); + +/* _ACCESS3 */ +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS3_SDMMCRA, ( 96 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 3)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS3_SDMMCRAA, ( 97 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 3)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS3_SDMMCR, ( 98 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 3)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS3_SDMMCRAB, ( 99 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 3)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS3_SDMMCWA, (100 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 3)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS3_SDMMCWAA, (101 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 3)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS3_SDMMCW, (102 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 3)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS3_SDMMCWAB, (103 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 3)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS3_VICSRD, (108 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 3)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS3_VICSWR, (109 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 3)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS3_VIW, (114 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 3)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS3_DISPLAYD, (115 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 3)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS3_NVDECSRD, (120 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 3)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS3_NVDECSWR, (121 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 3)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS3_APER, (122 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 3)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS3_APEW, (123 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 3)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS3_NVJPGSRD, (126 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 3)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS3_NVJPGSWR, (127 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 3)), DISABLE, ENABLE); + +/* _ACCESS4 */ +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS4_SESRD, (128 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 4)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS4_SESWR, (129 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 4)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS4_AXIAPR, (130 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 4)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS4_AXIAPW, (131 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 4)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS4_ETRR, (132 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 4)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS4_ETRW, (133 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 4)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS4_TSECRDB, (134 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 4)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS4_TSECWRB, (135 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 4)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS4_GPUSRD2, (136 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 4)), DISABLE, ENABLE); +DEFINE_MC_REG_BIT_ENUM(CLIENT_ACCESS4_GPUSWR2, (137 - (MC_CLIENT_ACCESS_NUM_CLIENTS * 4)), DISABLE, ENABLE); + +DEFINE_MC_REG(IRAM_BOM_IRAM_BOM, 12, BITSIZEOF(u32) - 12); +DEFINE_MC_REG(IRAM_TOM_IRAM_TOM, 12, BITSIZEOF(u32) - 12); + +DEFINE_MC_REG_BIT_ENUM(IRAM_REG_CTRL_IRAM_CFG_WRITE_ACCESS, 0, ENABLED, DISABLED); + +DEFINE_MC_REG_BIT_ENUM(UNTRANSLATED_REGION_CHECK_UNTRANSLATED_REGION_CHECK_ACCESS, 0, ENABLED, DISABLED); +DEFINE_MC_REG_BIT_ENUM(UNTRANSLATED_REGION_CHECK_REQUIRE_UNTRANSLATED_CLIENTS_HIT_CARVEOUT, 8, DISABLED, ENABLED); +DEFINE_MC_REG_BIT_ENUM(UNTRANSLATED_REGION_CHECK_REQUIRE_UNTRANSLATED_GPU_HIT_CARVEOUT, 9, DISABLED, ENABLED); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_mipi_cal.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_mipi_cal.hpp new file mode 100644 index 00000000..52e91fca --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_mipi_cal.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#define MIPI_CAL_MIPI_CAL_CTRL (0x000) +#define MIPI_CAL_CIL_MIPI_CAL_STATUS (0x008) +#define MIPI_CAL_CILA_MIPI_CAL_CONFIG (0x014) +#define MIPI_CAL_CILB_MIPI_CAL_CONFIG (0x018) +#define MIPI_CAL_CILC_MIPI_CAL_CONFIG (0x01C) +#define MIPI_CAL_CILD_MIPI_CAL_CONFIG (0x020) +#define MIPI_CAL_CILE_MIPI_CAL_CONFIG (0x024) +#define MIPI_CAL_CILF_MIPI_CAL_CONFIG (0x028) +#define MIPI_CAL_DSIA_MIPI_CAL_CONFIG (0x038) +#define MIPI_CAL_DSIB_MIPI_CAL_CONFIG (0x03C) +#define MIPI_CAL_DSIC_MIPI_CAL_CONFIG (0x040) +#define MIPI_CAL_DSID_MIPI_CAL_CONFIG (0x044) +#define MIPI_CAL_MIPI_BIAS_PAD_CFG0 (0x058) +#define MIPI_CAL_MIPI_BIAS_PAD_CFG1 (0x05C) +#define MIPI_CAL_MIPI_BIAS_PAD_CFG2 (0x060) +#define MIPI_CAL_DSIA_MIPI_CAL_CONFIG_2 (0x064) +#define MIPI_CAL_DSIB_MIPI_CAL_CONFIG_2 (0x068) +#define MIPI_CAL_DSIC_MIPI_CAL_CONFIG_2 (0x070) +#define MIPI_CAL_DSID_MIPI_CAL_CONFIG_2 (0x074) + +#define MIPI_CAL_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (MIPI_CAL, NAME) +#define MIPI_CAL_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (MIPI_CAL, NAME, VALUE) +#define MIPI_CAL_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (MIPI_CAL, NAME, ENUM) +#define MIPI_CAL_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(MIPI_CAL, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + +#define DEFINE_MIPI_CAL_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (MIPI_CAL, NAME, __OFFSET__, __WIDTH__) +#define DEFINE_MIPI_CAL_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (MIPI_CAL, NAME, __OFFSET__, ZERO, ONE) +#define DEFINE_MIPI_CAL_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (MIPI_CAL, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) +#define DEFINE_MIPI_CAL_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(MIPI_CAL, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) +#define DEFINE_MIPI_CAL_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (MIPI_CAL, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_mselect.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_mselect.hpp new file mode 100644 index 00000000..3e8c9d98 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_mselect.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#define MSELECT(x) (0x50060000 + x) + +#define MSELECT_CONFIG (0x000) + +#define MSELECT_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (MSELECT, NAME) +#define MSELECT_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (MSELECT, NAME, VALUE) +#define MSELECT_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (MSELECT, NAME, ENUM) +#define MSELECT_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(MSELECT, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + +#define DEFINE_MSELECT_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (MSELECT, NAME, __OFFSET__, __WIDTH__) +#define DEFINE_MSELECT_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (MSELECT, NAME, __OFFSET__, ZERO, ONE) +#define DEFINE_MSELECT_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (MSELECT, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) +#define DEFINE_MSELECT_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(MSELECT, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) +#define DEFINE_MSELECT_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (MSELECT, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + +DEFINE_MSELECT_REG_BIT_ENUM(CONFIG_ERR_RESP_EN_SLAVE1, 24, DISABLE, ENABLE); +DEFINE_MSELECT_REG_BIT_ENUM(CONFIG_ERR_RESP_EN_SLAVE2, 25, DISABLE, ENABLE); +DEFINE_MSELECT_REG_BIT_ENUM(CONFIG_WRAP_TO_INCR_SLAVE0, 27, DISABLE, ENABLE); +DEFINE_MSELECT_REG_BIT_ENUM(CONFIG_WRAP_TO_INCR_SLAVE1, 28, DISABLE, ENABLE); +DEFINE_MSELECT_REG_BIT_ENUM(CONFIG_WRAP_TO_INCR_SLAVE2, 29, DISABLE, ENABLE); +DEFINE_MSELECT_REG_BIT_ENUM(CONFIG_WRAP_TO_INCR_SLAVE3, 30, DISABLE, ENABLE); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_pg_up.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_pg_up.hpp new file mode 100644 index 00000000..d401d621 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_pg_up.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#define PG_UP(x) (0x60000000 + x) + +#define PG_UP_TAG (0x000) + +#define PG_UP_TAG_PID_COP 0xAAAAAAAA diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_pinmux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_pinmux.hpp new file mode 100644 index 00000000..3bfd1d45 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_pinmux.hpp @@ -0,0 +1,105 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#define PINMUX_AUX_SDMMC1_CLK (0x3000) +#define PINMUX_AUX_SDMMC1_CMD (0x3004) +#define PINMUX_AUX_SDMMC1_DAT3 (0x3008) +#define PINMUX_AUX_SDMMC1_DAT2 (0x300C) +#define PINMUX_AUX_SDMMC1_DAT1 (0x3010) +#define PINMUX_AUX_SDMMC1_DAT0 (0x3014) + +#define PINMUX_AUX_DMIC3_CLK (0x30B4) + +#define PINMUX_AUX_GEN1_I2C_SCL (0x30BC) +#define PINMUX_AUX_GEN1_I2C_SDA (0x30C0) +#define PINMUX_AUX_PWR_I2C_SCL (0x30DC) +#define PINMUX_AUX_PWR_I2C_SDA (0x30E0) + +#define PINMUX_AUX_UART1_TX (0x30E4) +#define PINMUX_AUX_UART1_RX (0x30E8) +#define PINMUX_AUX_UART1_RTS (0x30EC) +#define PINMUX_AUX_UART1_CTS (0x30F0) +#define PINMUX_AUX_UART2_TX (0x30F4) +#define PINMUX_AUX_UART2_RX (0x30F8) +#define PINMUX_AUX_UART2_RTS (0x30FC) +#define PINMUX_AUX_UART2_CTS (0x3100) +#define PINMUX_AUX_UART3_TX (0x3104) +#define PINMUX_AUX_UART3_RX (0x3108) +#define PINMUX_AUX_UART3_RTS (0x310C) +#define PINMUX_AUX_UART3_CTS (0x3110) +#define PINMUX_AUX_DVFS_PWM (0x3184) +#define PINMUX_AUX_NFC_EN (0x31D0) +#define PINMUX_AUX_NFC_INT (0x31D4) +#define PINMUX_AUX_CAM_FLASH_EN (0x31E8) +#define PINMUX_AUX_LCD_BL_PWM (0x31FC) +#define PINMUX_AUX_LCD_BL_EN (0x3200) +#define PINMUX_AUX_LCD_RST (0x3204) +#define PINMUX_AUX_GPIO_PA6 (0x3244) +#define PINMUX_AUX_GPIO_PE6 (0x3248) +#define PINMUX_AUX_GPIO_PH6 (0x3250) + + +#define PINMUX_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (PINMUX, NAME) +#define PINMUX_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (PINMUX, NAME, VALUE) +#define PINMUX_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (PINMUX, NAME, ENUM) +#define PINMUX_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(PINMUX, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + +#define DEFINE_PINMUX_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (PINMUX, NAME, __OFFSET__, __WIDTH__) +#define DEFINE_PINMUX_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (PINMUX, NAME, __OFFSET__, ZERO, ONE) +#define DEFINE_PINMUX_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (PINMUX, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) +#define DEFINE_PINMUX_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(PINMUX, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) +#define DEFINE_PINMUX_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (PINMUX, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_PUPD, 2, NONE, PULL_DOWN, PULL_UP, RSVD); +DEFINE_PINMUX_REG_BIT_ENUM(AUX_TRISTATE, 4, PASSTHROUGH, TRISTATE); +DEFINE_PINMUX_REG_BIT_ENUM(AUX_PARK, 5, NORMAL, PARKED); +DEFINE_PINMUX_REG_BIT_ENUM(AUX_E_INPUT, 6, DISABLE, ENABLE); +DEFINE_PINMUX_REG_BIT_ENUM(AUX_LOCK, 7, DISABLE, ENABLE); +DEFINE_PINMUX_REG_BIT_ENUM(AUX_E_LPDR, 8, DISABLE, ENABLE); +DEFINE_PINMUX_REG_BIT_ENUM(AUX_E_OD, 11, DISABLE, ENABLE); +DEFINE_PINMUX_REG_BIT_ENUM(AUX_E_SCHMT, 12, DISABLE, ENABLE); + + +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_SDMMC1_CLK_PM, 0, SDMMC1, RSVD1, RSVD2, RSVD3); +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_SDMMC1_CMD_PM, 0, SDMMC1, RSVD1, RSVD2, RSVD3); +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_SDMMC1_DAT3_PM, 0, SDMMC1, RSVD1, RSVD2, RSVD3); +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_SDMMC1_DAT2_PM, 0, SDMMC1, RSVD1, RSVD2, RSVD3); +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_SDMMC1_DAT1_PM, 0, SDMMC1, RSVD1, RSVD2, RSVD3); +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_SDMMC1_DAT0_PM, 0, SDMMC1, RSVD1, RSVD2, RSVD3); + +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_DMIC3_CLK_PM, 0, DMIC3, I2S5A, RSVD2, RSVD3); + +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_GEN1_I2C_PM, 0, I2C1, RSVD1, RSVD2, RSVD3); +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_PWR_I2C_PM, 0, I2CPMU, RSVD1, RSVD2, RSVD3); + +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_UART1_PM, 0, UARTA, RSVD1, RSVD2, RSVD3); +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_UART2_PM, 0, UARTB, I2S4A, RSVD2, UART); +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_UART3_PM, 0, UARTC, SPI4, RSVD2, RSVD3); + +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_DVFS_PWM_PM, 0, RSVD0, CLDVFS, SPI3, RSVD3); + +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_LCD_BL_PWM_PM, 0, DISPLAYA, PWM0, SOR0, RSVD3); + +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_GPIO_PA6_PM, 0, SATA, RSVD1, RSVD2, RSVD3); +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_GPIO_PE6_PM, 0, RSVD0, I2S5A, PWM2, RSVD3); +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_GPIO_PH6_PM, 0, RSVD0, RSVD1, RSVD2, RSVD3); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_pmc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_pmc.hpp new file mode 100644 index 00000000..34536bf5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_pmc.hpp @@ -0,0 +1,665 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#define APBDEV_PMC_CNTRL (0x000) +#define APBDEV_PMC_WAKE_MASK (0x00C) +#define APBDEV_PMC_WAKE_LVL (0x010) +#define APBDEV_PMC_WAKE_STATUS (0x014) +#define APBDEV_PMC_DPD_PADS_ORIDE (0x01C) +#define APBDEV_PMC_DPD_SAMPLE (0x020) +#define APBDEV_PMC_DPD_ENABLE (0x024) +#define APBDEV_PMC_CLAMP_STATUS (0x02C) +#define APBDEV_PMC_PWRGATE_TOGGLE (0x030) +#define APBDEV_PMC_REMOVE_CLAMPING_CMD (0x034) +#define APBDEV_PMC_PWRGATE_STATUS (0x038) +#define APBDEV_PMC_PWRGOOD_TIMER (0x03C) +#define APBDEV_PMC_BLINK_TIMER (0x040) +#define APBDEV_PMC_NO_IOPOWER (0x044) +#define APBDEV_PMC_PWR_DET (0x048) +#define APBDEV_PMC_AUTO_WAKE_LVL_MASK (0x0DC) +#define APBDEV_PMC_WAKE_DELAY (0x0E0) +#define APBDEV_PMC_PWR_DET_VAL (0x0E4) +#define APBDEV_PMC_DDR_PWR (0x0E8) +#define APBDEV_PMC_CRYPTO_OP (0x0F4) +#define APBDEV_PMC_WAKE2_MASK (0x160) +#define APBDEV_PMC_WAKE2_LVL (0x164) +#define APBDEV_PMC_WAKE2_STATUS (0x168) +#define APBDEV_PMC_AUTO_WAKE2_LVL_MASK (0x170) +#define APBDEV_PMC_OSC_EDPD_OVER (0x1A4) +#define APBDEV_PMC_CLK_OUT_CNTRL (0x1A8) +#define APBDEV_PMC_RST_STATUS (0x1B4) +#define APBDEV_PMC_IO_DPD_REQ (0x1B8) +#define APBDEV_PMC_IO_DPD_STATUS (0x1BC) +#define APBDEV_PMC_IO_DPD2_REQ (0x1C0) +#define APBDEV_PMC_IO_DPD2_STATUS (0x1C4) +#define APBDEV_PMC_SEL_DPD_TIM (0x1C8) +#define APBDEV_PMC_VDDP_SEL (0x1CC) +#define APBDEV_PMC_DDR_CFG (0x1D0) +#define APBDEV_PMC_TSC_MULT (0x2B4) +#define APBDEV_PMC_STICKY_BITS (0x2C0) +#define APBDEV_PMC_WEAK_BIAS (0x2C8) +#define APBDEV_PMC_REG_SHORT (0x2CC) +#define APBDEV_PMC_GPU_RG_CNTRL (0x2D4) +#define APBDEV_PMC_CNTRL2 (0x440) +#define APBDEV_PMC_FUSE_CTRL (0x450) +#define APBDEV_PMC_IO_DPD3_REQ (0x45C) +#define APBDEV_PMC_IO_DPD3_STATUS (0x460) +#define APBDEV_PMC_IO_DPD4_REQ (0x464) +#define APBDEV_PMC_IO_DPD4_STATUS (0x468) +#define APBDEV_PMC_SET_SW_CLAMP (0x47C) +#define APBDEV_PMC_WAKE_DEBOUNCE_EN (0x4D8) +#define APBDEV_PMC_DDR_CNTRL (0x4E4) + +#define APBDEV_PMC_SEC_DISABLE (0x004) +#define APBDEV_PMC_SEC_DISABLE2 (0x2C4) +#define APBDEV_PMC_SEC_DISABLE3 (0x2D8) +#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) + +/* Mariko. */ +#define APBDEV_PMC_TZRAM_PWR_CNTRL (0xBE8) +#define APBDEV_PMC_TZRAM_SEC_DISABLE (0xBEC) +#define APBDEV_PMC_TZRAM_NON_SEC_DISABLE (0xBF0) + +#define APBDEV_PMC_SCRATCH0 (0x050) +#define APBDEV_PMC_SCRATCH1 (0x054) +#define APBDEV_PMC_SCRATCH2 (0x058) +#define APBDEV_PMC_SCRATCH3 (0x05C) +#define APBDEV_PMC_SCRATCH4 (0x060) +#define APBDEV_PMC_SCRATCH5 (0x064) +#define APBDEV_PMC_SCRATCH6 (0x068) +#define APBDEV_PMC_SCRATCH7 (0x06C) +#define APBDEV_PMC_SCRATCH8 (0x070) +#define APBDEV_PMC_SCRATCH9 (0x074) +#define APBDEV_PMC_SCRATCH10 (0x078) +#define APBDEV_PMC_SCRATCH11 (0x07C) +#define APBDEV_PMC_SCRATCH12 (0x080) +#define APBDEV_PMC_SCRATCH13 (0x084) +#define APBDEV_PMC_SCRATCH14 (0x088) +#define APBDEV_PMC_SCRATCH15 (0x08C) +#define APBDEV_PMC_SCRATCH16 (0x090) +#define APBDEV_PMC_SCRATCH17 (0x094) +#define APBDEV_PMC_SCRATCH18 (0x098) +#define APBDEV_PMC_SCRATCH19 (0x09C) +#define APBDEV_PMC_SCRATCH20 (0x0A0) +#define APBDEV_PMC_SCRATCH21 (0x0A4) +#define APBDEV_PMC_SCRATCH22 (0x0A8) +#define APBDEV_PMC_SCRATCH23 (0x0AC) +#define APBDEV_PMC_SCRATCH24 (0x0FC) +#define APBDEV_PMC_SCRATCH25 (0x100) +#define APBDEV_PMC_SCRATCH26 (0x104) +#define APBDEV_PMC_SCRATCH27 (0x108) +#define APBDEV_PMC_SCRATCH28 (0x10C) +#define APBDEV_PMC_SCRATCH29 (0x110) +#define APBDEV_PMC_SCRATCH30 (0x114) +#define APBDEV_PMC_SCRATCH31 (0x118) +#define APBDEV_PMC_SCRATCH32 (0x11C) +#define APBDEV_PMC_SCRATCH33 (0x120) +#define APBDEV_PMC_SCRATCH34 (0x124) +#define APBDEV_PMC_SCRATCH35 (0x128) +#define APBDEV_PMC_SCRATCH36 (0x12C) +#define APBDEV_PMC_SCRATCH37 (0x130) +#define APBDEV_PMC_SCRATCH38 (0x134) +#define APBDEV_PMC_SCRATCH39 (0x138) +#define APBDEV_PMC_SCRATCH40 (0x13C) +#define APBDEV_PMC_SCRATCH41 (0x140) +#define APBDEV_PMC_SCRATCH42 (0x144) +#define APBDEV_PMC_SCRATCH43 (0x22C) +#define APBDEV_PMC_SCRATCH44 (0x230) +#define APBDEV_PMC_SCRATCH45 (0x234) +#define APBDEV_PMC_SCRATCH46 (0x238) +#define APBDEV_PMC_SCRATCH47 (0x23C) +#define APBDEV_PMC_SCRATCH48 (0x240) +#define APBDEV_PMC_SCRATCH49 (0x244) +#define APBDEV_PMC_SCRATCH50 (0x248) +#define APBDEV_PMC_SCRATCH51 (0x24C) +#define APBDEV_PMC_SCRATCH52 (0x250) +#define APBDEV_PMC_SCRATCH53 (0x254) +#define APBDEV_PMC_SCRATCH54 (0x258) +#define APBDEV_PMC_SCRATCH55 (0x25C) +#define APBDEV_PMC_SCRATCH56 (0x600) +#define APBDEV_PMC_SCRATCH57 (0x604) +#define APBDEV_PMC_SCRATCH58 (0x608) +#define APBDEV_PMC_SCRATCH59 (0x60C) +#define APBDEV_PMC_SCRATCH60 (0x610) +#define APBDEV_PMC_SCRATCH61 (0x614) +#define APBDEV_PMC_SCRATCH62 (0x618) +#define APBDEV_PMC_SCRATCH63 (0x61C) +#define APBDEV_PMC_SCRATCH64 (0x620) +#define APBDEV_PMC_SCRATCH65 (0x624) +#define APBDEV_PMC_SCRATCH66 (0x628) +#define APBDEV_PMC_SCRATCH67 (0x62C) +#define APBDEV_PMC_SCRATCH68 (0x630) +#define APBDEV_PMC_SCRATCH69 (0x634) +#define APBDEV_PMC_SCRATCH70 (0x638) +#define APBDEV_PMC_SCRATCH71 (0x63C) +#define APBDEV_PMC_SCRATCH72 (0x640) +#define APBDEV_PMC_SCRATCH73 (0x644) +#define APBDEV_PMC_SCRATCH74 (0x648) +#define APBDEV_PMC_SCRATCH75 (0x64C) +#define APBDEV_PMC_SCRATCH76 (0x650) +#define APBDEV_PMC_SCRATCH77 (0x654) +#define APBDEV_PMC_SCRATCH78 (0x658) +#define APBDEV_PMC_SCRATCH79 (0x65C) +#define APBDEV_PMC_SCRATCH80 (0x660) +#define APBDEV_PMC_SCRATCH81 (0x664) +#define APBDEV_PMC_SCRATCH82 (0x668) +#define APBDEV_PMC_SCRATCH83 (0x66C) +#define APBDEV_PMC_SCRATCH84 (0x670) +#define APBDEV_PMC_SCRATCH85 (0x674) +#define APBDEV_PMC_SCRATCH86 (0x678) +#define APBDEV_PMC_SCRATCH87 (0x67C) +#define APBDEV_PMC_SCRATCH88 (0x680) +#define APBDEV_PMC_SCRATCH89 (0x684) +#define APBDEV_PMC_SCRATCH90 (0x688) +#define APBDEV_PMC_SCRATCH91 (0x68C) +#define APBDEV_PMC_SCRATCH92 (0x690) +#define APBDEV_PMC_SCRATCH93 (0x694) +#define APBDEV_PMC_SCRATCH94 (0x698) +#define APBDEV_PMC_SCRATCH95 (0x69C) +#define APBDEV_PMC_SCRATCH96 (0x6A0) +#define APBDEV_PMC_SCRATCH97 (0x6A4) +#define APBDEV_PMC_SCRATCH98 (0x6A8) +#define APBDEV_PMC_SCRATCH99 (0x6AC) +#define APBDEV_PMC_SCRATCH100 (0x6B0) +#define APBDEV_PMC_SCRATCH101 (0x6B4) +#define APBDEV_PMC_SCRATCH102 (0x6B8) +#define APBDEV_PMC_SCRATCH103 (0x6BC) +#define APBDEV_PMC_SCRATCH104 (0x6C0) +#define APBDEV_PMC_SCRATCH105 (0x6C4) +#define APBDEV_PMC_SCRATCH106 (0x6C8) +#define APBDEV_PMC_SCRATCH107 (0x6CC) +#define APBDEV_PMC_SCRATCH108 (0x6D0) +#define APBDEV_PMC_SCRATCH109 (0x6D4) +#define APBDEV_PMC_SCRATCH110 (0x6D8) +#define APBDEV_PMC_SCRATCH111 (0x6DC) +#define APBDEV_PMC_SCRATCH112 (0x6E0) +#define APBDEV_PMC_SCRATCH113 (0x6E4) +#define APBDEV_PMC_SCRATCH114 (0x6E8) +#define APBDEV_PMC_SCRATCH115 (0x6EC) +#define APBDEV_PMC_SCRATCH116 (0x6F0) +#define APBDEV_PMC_SCRATCH117 (0x6F4) +#define APBDEV_PMC_SCRATCH118 (0x6F8) +#define APBDEV_PMC_SCRATCH119 (0x6FC) +#define APBDEV_PMC_SCRATCH120 (0x700) +#define APBDEV_PMC_SCRATCH121 (0x704) +#define APBDEV_PMC_SCRATCH122 (0x708) +#define APBDEV_PMC_SCRATCH123 (0x70C) +#define APBDEV_PMC_SCRATCH124 (0x710) +#define APBDEV_PMC_SCRATCH125 (0x714) +#define APBDEV_PMC_SCRATCH126 (0x718) +#define APBDEV_PMC_SCRATCH127 (0x71C) +#define APBDEV_PMC_SCRATCH128 (0x720) +#define APBDEV_PMC_SCRATCH129 (0x724) +#define APBDEV_PMC_SCRATCH130 (0x728) +#define APBDEV_PMC_SCRATCH131 (0x72C) +#define APBDEV_PMC_SCRATCH132 (0x730) +#define APBDEV_PMC_SCRATCH133 (0x734) +#define APBDEV_PMC_SCRATCH134 (0x738) +#define APBDEV_PMC_SCRATCH135 (0x73C) +#define APBDEV_PMC_SCRATCH136 (0x740) +#define APBDEV_PMC_SCRATCH137 (0x744) +#define APBDEV_PMC_SCRATCH138 (0x748) +#define APBDEV_PMC_SCRATCH139 (0x74C) +#define APBDEV_PMC_SCRATCH140 (0x750) +#define APBDEV_PMC_SCRATCH141 (0x754) +#define APBDEV_PMC_SCRATCH142 (0x758) +#define APBDEV_PMC_SCRATCH143 (0x75C) +#define APBDEV_PMC_SCRATCH144 (0x760) +#define APBDEV_PMC_SCRATCH145 (0x764) +#define APBDEV_PMC_SCRATCH146 (0x768) +#define APBDEV_PMC_SCRATCH147 (0x76C) +#define APBDEV_PMC_SCRATCH148 (0x770) +#define APBDEV_PMC_SCRATCH149 (0x774) +#define APBDEV_PMC_SCRATCH150 (0x778) +#define APBDEV_PMC_SCRATCH151 (0x77C) +#define APBDEV_PMC_SCRATCH152 (0x780) +#define APBDEV_PMC_SCRATCH153 (0x784) +#define APBDEV_PMC_SCRATCH154 (0x788) +#define APBDEV_PMC_SCRATCH155 (0x78C) +#define APBDEV_PMC_SCRATCH156 (0x790) +#define APBDEV_PMC_SCRATCH157 (0x794) +#define APBDEV_PMC_SCRATCH158 (0x798) +#define APBDEV_PMC_SCRATCH159 (0x79C) +#define APBDEV_PMC_SCRATCH160 (0x7A0) +#define APBDEV_PMC_SCRATCH161 (0x7A4) +#define APBDEV_PMC_SCRATCH162 (0x7A8) +#define APBDEV_PMC_SCRATCH163 (0x7AC) +#define APBDEV_PMC_SCRATCH164 (0x7B0) +#define APBDEV_PMC_SCRATCH165 (0x7B4) +#define APBDEV_PMC_SCRATCH166 (0x7B8) +#define APBDEV_PMC_SCRATCH167 (0x7BC) +#define APBDEV_PMC_SCRATCH168 (0x7C0) +#define APBDEV_PMC_SCRATCH169 (0x7C4) +#define APBDEV_PMC_SCRATCH170 (0x7C8) +#define APBDEV_PMC_SCRATCH171 (0x7CC) +#define APBDEV_PMC_SCRATCH172 (0x7D0) +#define APBDEV_PMC_SCRATCH173 (0x7D4) +#define APBDEV_PMC_SCRATCH174 (0x7D8) +#define APBDEV_PMC_SCRATCH175 (0x7DC) +#define APBDEV_PMC_SCRATCH176 (0x7E0) +#define APBDEV_PMC_SCRATCH177 (0x7E4) +#define APBDEV_PMC_SCRATCH178 (0x7E8) +#define APBDEV_PMC_SCRATCH179 (0x7EC) +#define APBDEV_PMC_SCRATCH180 (0x7F0) +#define APBDEV_PMC_SCRATCH181 (0x7F4) +#define APBDEV_PMC_SCRATCH182 (0x7F8) +#define APBDEV_PMC_SCRATCH183 (0x7FC) +#define APBDEV_PMC_SCRATCH184 (0x800) +#define APBDEV_PMC_SCRATCH185 (0x804) +#define APBDEV_PMC_SCRATCH186 (0x808) +#define APBDEV_PMC_SCRATCH187 (0x80C) +#define APBDEV_PMC_SCRATCH188 (0x810) +#define APBDEV_PMC_SCRATCH189 (0x814) +#define APBDEV_PMC_SCRATCH190 (0x818) +#define APBDEV_PMC_SCRATCH191 (0x81C) +#define APBDEV_PMC_SCRATCH192 (0x820) +#define APBDEV_PMC_SCRATCH193 (0x824) +#define APBDEV_PMC_SCRATCH194 (0x828) +#define APBDEV_PMC_SCRATCH195 (0x82C) +#define APBDEV_PMC_SCRATCH196 (0x830) +#define APBDEV_PMC_SCRATCH197 (0x834) +#define APBDEV_PMC_SCRATCH198 (0x838) +#define APBDEV_PMC_SCRATCH199 (0x83C) +#define APBDEV_PMC_SCRATCH200 (0x840) +#define APBDEV_PMC_SCRATCH201 (0x844) +#define APBDEV_PMC_SCRATCH202 (0x848) +#define APBDEV_PMC_SCRATCH203 (0x84C) +#define APBDEV_PMC_SCRATCH204 (0x850) +#define APBDEV_PMC_SCRATCH205 (0x854) +#define APBDEV_PMC_SCRATCH206 (0x858) +#define APBDEV_PMC_SCRATCH207 (0x85C) +#define APBDEV_PMC_SCRATCH208 (0x860) +#define APBDEV_PMC_SCRATCH209 (0x864) +#define APBDEV_PMC_SCRATCH210 (0x868) +#define APBDEV_PMC_SCRATCH211 (0x86C) +#define APBDEV_PMC_SCRATCH212 (0x870) +#define APBDEV_PMC_SCRATCH213 (0x874) +#define APBDEV_PMC_SCRATCH214 (0x878) +#define APBDEV_PMC_SCRATCH215 (0x87C) +#define APBDEV_PMC_SCRATCH216 (0x880) +#define APBDEV_PMC_SCRATCH217 (0x884) +#define APBDEV_PMC_SCRATCH218 (0x888) +#define APBDEV_PMC_SCRATCH219 (0x88C) +#define APBDEV_PMC_SCRATCH220 (0x890) +#define APBDEV_PMC_SCRATCH221 (0x894) +#define APBDEV_PMC_SCRATCH222 (0x898) +#define APBDEV_PMC_SCRATCH223 (0x89C) +#define APBDEV_PMC_SCRATCH224 (0x8A0) +#define APBDEV_PMC_SCRATCH225 (0x8A4) +#define APBDEV_PMC_SCRATCH226 (0x8A8) +#define APBDEV_PMC_SCRATCH227 (0x8AC) +#define APBDEV_PMC_SCRATCH228 (0x8B0) +#define APBDEV_PMC_SCRATCH229 (0x8B4) +#define APBDEV_PMC_SCRATCH230 (0x8B8) +#define APBDEV_PMC_SCRATCH231 (0x8BC) +#define APBDEV_PMC_SCRATCH232 (0x8C0) +#define APBDEV_PMC_SCRATCH233 (0x8C4) +#define APBDEV_PMC_SCRATCH234 (0x8C8) +#define APBDEV_PMC_SCRATCH235 (0x8CC) +#define APBDEV_PMC_SCRATCH236 (0x8D0) +#define APBDEV_PMC_SCRATCH237 (0x8D4) +#define APBDEV_PMC_SCRATCH238 (0x8D8) +#define APBDEV_PMC_SCRATCH239 (0x8DC) +#define APBDEV_PMC_SCRATCH240 (0x8E0) +#define APBDEV_PMC_SCRATCH241 (0x8E4) +#define APBDEV_PMC_SCRATCH242 (0x8E8) +#define APBDEV_PMC_SCRATCH243 (0x8EC) +#define APBDEV_PMC_SCRATCH244 (0x8F0) +#define APBDEV_PMC_SCRATCH245 (0x8F4) +#define APBDEV_PMC_SCRATCH246 (0x8F8) +#define APBDEV_PMC_SCRATCH247 (0x8FC) +#define APBDEV_PMC_SCRATCH248 (0x900) +#define APBDEV_PMC_SCRATCH249 (0x904) +#define APBDEV_PMC_SCRATCH250 (0x908) +#define APBDEV_PMC_SCRATCH251 (0x90C) +#define APBDEV_PMC_SCRATCH252 (0x910) +#define APBDEV_PMC_SCRATCH253 (0x914) +#define APBDEV_PMC_SCRATCH254 (0x918) +#define APBDEV_PMC_SCRATCH255 (0x91C) +#define APBDEV_PMC_SCRATCH256 (0x920) +#define APBDEV_PMC_SCRATCH257 (0x924) +#define APBDEV_PMC_SCRATCH258 (0x928) +#define APBDEV_PMC_SCRATCH259 (0x92C) +#define APBDEV_PMC_SCRATCH260 (0x930) +#define APBDEV_PMC_SCRATCH261 (0x934) +#define APBDEV_PMC_SCRATCH262 (0x938) +#define APBDEV_PMC_SCRATCH263 (0x93C) +#define APBDEV_PMC_SCRATCH264 (0x940) +#define APBDEV_PMC_SCRATCH265 (0x944) +#define APBDEV_PMC_SCRATCH266 (0x948) +#define APBDEV_PMC_SCRATCH267 (0x94C) +#define APBDEV_PMC_SCRATCH268 (0x950) +#define APBDEV_PMC_SCRATCH269 (0x954) +#define APBDEV_PMC_SCRATCH270 (0x958) +#define APBDEV_PMC_SCRATCH271 (0x95C) +#define APBDEV_PMC_SCRATCH272 (0x960) +#define APBDEV_PMC_SCRATCH273 (0x964) +#define APBDEV_PMC_SCRATCH274 (0x968) +#define APBDEV_PMC_SCRATCH275 (0x96C) +#define APBDEV_PMC_SCRATCH276 (0x970) +#define APBDEV_PMC_SCRATCH277 (0x974) +#define APBDEV_PMC_SCRATCH278 (0x978) +#define APBDEV_PMC_SCRATCH279 (0x97C) +#define APBDEV_PMC_SCRATCH280 (0x980) +#define APBDEV_PMC_SCRATCH281 (0x984) +#define APBDEV_PMC_SCRATCH282 (0x988) +#define APBDEV_PMC_SCRATCH283 (0x98C) +#define APBDEV_PMC_SCRATCH284 (0x990) +#define APBDEV_PMC_SCRATCH285 (0x994) +#define APBDEV_PMC_SCRATCH286 (0x998) +#define APBDEV_PMC_SCRATCH287 (0x99C) +#define APBDEV_PMC_SCRATCH288 (0x9A0) +#define APBDEV_PMC_SCRATCH289 (0x9A4) +#define APBDEV_PMC_SCRATCH290 (0x9A8) +#define APBDEV_PMC_SCRATCH291 (0x9AC) +#define APBDEV_PMC_SCRATCH292 (0x9B0) +#define APBDEV_PMC_SCRATCH293 (0x9B4) +#define APBDEV_PMC_SCRATCH294 (0x9B8) +#define APBDEV_PMC_SCRATCH295 (0x9BC) +#define APBDEV_PMC_SCRATCH296 (0x9C0) +#define APBDEV_PMC_SCRATCH297 (0x9C4) +#define APBDEV_PMC_SCRATCH298 (0x9C8) +#define APBDEV_PMC_SCRATCH299 (0x9CC) + +#define APBDEV_PMC_SECURE_SCRATCH0 (0x0B0) +#define APBDEV_PMC_SECURE_SCRATCH1 (0x0B4) +#define APBDEV_PMC_SECURE_SCRATCH2 (0x0B8) +#define APBDEV_PMC_SECURE_SCRATCH3 (0x0BC) +#define APBDEV_PMC_SECURE_SCRATCH4 (0x0C0) +#define APBDEV_PMC_SECURE_SCRATCH5 (0x0C4) +#define APBDEV_PMC_SECURE_SCRATCH6 (0x224) +#define APBDEV_PMC_SECURE_SCRATCH7 (0x228) +#define APBDEV_PMC_SECURE_SCRATCH8 (0x300) +#define APBDEV_PMC_SECURE_SCRATCH9 (0x304) +#define APBDEV_PMC_SECURE_SCRATCH10 (0x308) +#define APBDEV_PMC_SECURE_SCRATCH11 (0x30C) +#define APBDEV_PMC_SECURE_SCRATCH12 (0x310) +#define APBDEV_PMC_SECURE_SCRATCH13 (0x314) +#define APBDEV_PMC_SECURE_SCRATCH14 (0x318) +#define APBDEV_PMC_SECURE_SCRATCH15 (0x31C) +#define APBDEV_PMC_SECURE_SCRATCH16 (0x320) +#define APBDEV_PMC_SECURE_SCRATCH17 (0x324) +#define APBDEV_PMC_SECURE_SCRATCH18 (0x328) +#define APBDEV_PMC_SECURE_SCRATCH19 (0x32C) +#define APBDEV_PMC_SECURE_SCRATCH20 (0x330) +#define APBDEV_PMC_SECURE_SCRATCH21 (0x334) +#define APBDEV_PMC_SECURE_SCRATCH22 (0x338) +#define APBDEV_PMC_SECURE_SCRATCH23 (0x33C) +#define APBDEV_PMC_SECURE_SCRATCH24 (0x340) +#define APBDEV_PMC_SECURE_SCRATCH25 (0x344) +#define APBDEV_PMC_SECURE_SCRATCH26 (0x348) +#define APBDEV_PMC_SECURE_SCRATCH27 (0x34C) +#define APBDEV_PMC_SECURE_SCRATCH28 (0x350) +#define APBDEV_PMC_SECURE_SCRATCH29 (0x354) +#define APBDEV_PMC_SECURE_SCRATCH30 (0x358) +#define APBDEV_PMC_SECURE_SCRATCH31 (0x35C) +#define APBDEV_PMC_SECURE_SCRATCH32 (0x360) +#define APBDEV_PMC_SECURE_SCRATCH33 (0x364) +#define APBDEV_PMC_SECURE_SCRATCH34 (0x368) +#define APBDEV_PMC_SECURE_SCRATCH35 (0x36C) +#define APBDEV_PMC_SECURE_SCRATCH36 (0x370) +#define APBDEV_PMC_SECURE_SCRATCH37 (0x374) +#define APBDEV_PMC_SECURE_SCRATCH38 (0x378) +#define APBDEV_PMC_SECURE_SCRATCH39 (0x37C) +#define APBDEV_PMC_SECURE_SCRATCH40 (0x380) +#define APBDEV_PMC_SECURE_SCRATCH41 (0x384) +#define APBDEV_PMC_SECURE_SCRATCH42 (0x388) +#define APBDEV_PMC_SECURE_SCRATCH43 (0x38C) +#define APBDEV_PMC_SECURE_SCRATCH44 (0x390) +#define APBDEV_PMC_SECURE_SCRATCH45 (0x394) +#define APBDEV_PMC_SECURE_SCRATCH46 (0x398) +#define APBDEV_PMC_SECURE_SCRATCH47 (0x39C) +#define APBDEV_PMC_SECURE_SCRATCH48 (0x3A0) +#define APBDEV_PMC_SECURE_SCRATCH49 (0x3A4) +#define APBDEV_PMC_SECURE_SCRATCH50 (0x3A8) +#define APBDEV_PMC_SECURE_SCRATCH51 (0x3AC) +#define APBDEV_PMC_SECURE_SCRATCH52 (0x3B0) +#define APBDEV_PMC_SECURE_SCRATCH53 (0x3B4) +#define APBDEV_PMC_SECURE_SCRATCH54 (0x3B8) +#define APBDEV_PMC_SECURE_SCRATCH55 (0x3BC) +#define APBDEV_PMC_SECURE_SCRATCH56 (0x3C0) +#define APBDEV_PMC_SECURE_SCRATCH57 (0x3C4) +#define APBDEV_PMC_SECURE_SCRATCH58 (0x3C8) +#define APBDEV_PMC_SECURE_SCRATCH59 (0x3CC) +#define APBDEV_PMC_SECURE_SCRATCH60 (0x3D0) +#define APBDEV_PMC_SECURE_SCRATCH61 (0x3D4) +#define APBDEV_PMC_SECURE_SCRATCH62 (0x3D8) +#define APBDEV_PMC_SECURE_SCRATCH63 (0x3DC) +#define APBDEV_PMC_SECURE_SCRATCH64 (0x3E0) +#define APBDEV_PMC_SECURE_SCRATCH65 (0x3E4) +#define APBDEV_PMC_SECURE_SCRATCH66 (0x3E8) +#define APBDEV_PMC_SECURE_SCRATCH67 (0x3EC) +#define APBDEV_PMC_SECURE_SCRATCH68 (0x3F0) +#define APBDEV_PMC_SECURE_SCRATCH69 (0x3F4) +#define APBDEV_PMC_SECURE_SCRATCH70 (0x3F8) +#define APBDEV_PMC_SECURE_SCRATCH71 (0x3FC) +#define APBDEV_PMC_SECURE_SCRATCH72 (0x400) +#define APBDEV_PMC_SECURE_SCRATCH73 (0x404) +#define APBDEV_PMC_SECURE_SCRATCH74 (0x408) +#define APBDEV_PMC_SECURE_SCRATCH75 (0x40C) +#define APBDEV_PMC_SECURE_SCRATCH76 (0x410) +#define APBDEV_PMC_SECURE_SCRATCH77 (0x414) +#define APBDEV_PMC_SECURE_SCRATCH78 (0x418) +#define APBDEV_PMC_SECURE_SCRATCH79 (0x41C) +#define APBDEV_PMC_SECURE_SCRATCH80 (0xA98) +#define APBDEV_PMC_SECURE_SCRATCH81 (0xA9C) +#define APBDEV_PMC_SECURE_SCRATCH82 (0xAA0) +#define APBDEV_PMC_SECURE_SCRATCH83 (0xAA4) +#define APBDEV_PMC_SECURE_SCRATCH84 (0xAA8) +#define APBDEV_PMC_SECURE_SCRATCH85 (0xAAC) +#define APBDEV_PMC_SECURE_SCRATCH86 (0xAB0) +#define APBDEV_PMC_SECURE_SCRATCH87 (0xAB4) +#define APBDEV_PMC_SECURE_SCRATCH88 (0xAB8) +#define APBDEV_PMC_SECURE_SCRATCH89 (0xABC) +#define APBDEV_PMC_SECURE_SCRATCH90 (0xAC0) +#define APBDEV_PMC_SECURE_SCRATCH91 (0xAC4) +#define APBDEV_PMC_SECURE_SCRATCH92 (0xAC8) +#define APBDEV_PMC_SECURE_SCRATCH93 (0xACC) +#define APBDEV_PMC_SECURE_SCRATCH94 (0xAD0) +#define APBDEV_PMC_SECURE_SCRATCH95 (0xAD4) +#define APBDEV_PMC_SECURE_SCRATCH96 (0xAD8) +#define APBDEV_PMC_SECURE_SCRATCH97 (0xADC) +#define APBDEV_PMC_SECURE_SCRATCH98 (0xAE0) +#define APBDEV_PMC_SECURE_SCRATCH99 (0xAE4) +#define APBDEV_PMC_SECURE_SCRATCH100 (0xAE8) +#define APBDEV_PMC_SECURE_SCRATCH101 (0xAEC) +#define APBDEV_PMC_SECURE_SCRATCH102 (0xAF0) +#define APBDEV_PMC_SECURE_SCRATCH103 (0xAF4) +#define APBDEV_PMC_SECURE_SCRATCH104 (0xAF8) +#define APBDEV_PMC_SECURE_SCRATCH105 (0xAFC) +#define APBDEV_PMC_SECURE_SCRATCH106 (0xB00) +#define APBDEV_PMC_SECURE_SCRATCH107 (0xB04) +#define APBDEV_PMC_SECURE_SCRATCH108 (0xB08) +#define APBDEV_PMC_SECURE_SCRATCH109 (0xB0C) +#define APBDEV_PMC_SECURE_SCRATCH110 (0xB10) +#define APBDEV_PMC_SECURE_SCRATCH111 (0xB14) +#define APBDEV_PMC_SECURE_SCRATCH112 (0xB18) +#define APBDEV_PMC_SECURE_SCRATCH113 (0xB1C) +#define APBDEV_PMC_SECURE_SCRATCH114 (0xB20) +#define APBDEV_PMC_SECURE_SCRATCH115 (0xB24) +#define APBDEV_PMC_SECURE_SCRATCH116 (0xB28) +#define APBDEV_PMC_SECURE_SCRATCH117 (0xB2C) +#define APBDEV_PMC_SECURE_SCRATCH118 (0xB30) +#define APBDEV_PMC_SECURE_SCRATCH119 (0xB34) + +/* Mariko. */ +#define APBDEV_PMC_SECURE_SCRATCH120 (0xB38) +#define APBDEV_PMC_SECURE_SCRATCH121 (0xB3C) +#define APBDEV_PMC_SECURE_SCRATCH122 (0xB40) +#define APBDEV_PMC_SECURE_SCRATCH123 (0xB44) +#define APBDEV_PMC_SECURE_SCRATCH124 (0xB68) +#define APBDEV_PMC_SECURE_SCRATCH125 (0xB6C) +#define APBDEV_PMC_SECURE_SCRATCH126 (0xB70) +#define APBDEV_PMC_SECURE_SCRATCH127 (0xB74) +#define APBDEV_PMC_SECURE_SCRATCH128 (0xB78) +#define APBDEV_PMC_SECURE_SCRATCH129 (0xB7C) +#define APBDEV_PMC_SECURE_SCRATCH130 (0xB80) +#define APBDEV_PMC_SECURE_SCRATCH131 (0xB84) +#define APBDEV_PMC_SECURE_SCRATCH132 (0xB88) +#define APBDEV_PMC_SECURE_SCRATCH133 (0xB8C) +#define APBDEV_PMC_SECURE_SCRATCH134 (0xB90) +#define APBDEV_PMC_SECURE_SCRATCH135 (0xB94) +#define APBDEV_PMC_SECURE_SCRATCH136 (0xB98) +#define APBDEV_PMC_SECURE_SCRATCH137 (0xB9C) +#define APBDEV_PMC_SECURE_SCRATCH138 (0xBA0) +#define APBDEV_PMC_SECURE_SCRATCH139 (0xBA4) + +#define PMC_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (APBDEV_PMC, NAME) +#define PMC_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (APBDEV_PMC, NAME, VALUE) +#define PMC_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (APBDEV_PMC, NAME, ENUM) +#define PMC_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(APBDEV_PMC, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + +#define DEFINE_PMC_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (APBDEV_PMC, NAME, __OFFSET__, __WIDTH__) +#define DEFINE_PMC_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (APBDEV_PMC, NAME, __OFFSET__, ZERO, ONE) +#define DEFINE_PMC_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (APBDEV_PMC, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) +#define DEFINE_PMC_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(APBDEV_PMC, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) +#define DEFINE_PMC_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (APBDEV_PMC, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + +DEFINE_PMC_REG_BIT_ENUM(CNTRL_MAIN_RESET, 4, DISABLE, ENABLE) + +DEFINE_PMC_REG_BIT_ENUM(DPD_SAMPLE_ON, 0, DISABLE, ENABLE); + +DEFINE_PMC_REG_BIT_ENUM(DPD_ENABLE_ON, 0, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(DPD_ENABLE_TSC_MULT_EN, 1, DISABLE, ENABLE); + +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_TOGGLE_START, 8, DISABLE, ENABLE); + +DEFINE_PMC_REG(PWRGATE_TOGGLE_PARTID, 0, 5); + +enum APBDEV_PMC_PWRGATE_TOGGLE_PARTID : u8 { + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CRAIL = 0, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_VE = 2, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_PCX = 3, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_MPE = 6, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_SAX = 8, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE1 = 9, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE2 = 10, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE3 = 11, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE0 = 14, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_C0NC = 15, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_SOR = 17, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_DIS = 18, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_DISB = 19, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_XUSBA = 20, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_XUSBB = 21, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_XUSBC = 22, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_VIC = 23, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_IRAM = 24, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_NVDEC = 25, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_NVJPG = 26, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_AUD = 27, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_DFD = 28, + APBDEV_PMC_PWRGATE_TOGGLE_PARTID_VE2 = 29, +}; + +DEFINE_PMC_REG_BIT_ENUM(REMOVE_CLAMPING_COMMAND_CRAIL, 0, DISABLE, ENABLE); + +enum APBDEV_PMC_PWRGATE_STATUS_STATUS { + APBDEV_PMC_PWRGATE_STATUS_STATUS_OFF = 0, + APBDEV_PMC_PWRGATE_STATUS_STATUS_ON = 1, +}; + +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_CRAIL, 0, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_VE, 2, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_PCX, 3, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_MPE, 6, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_SAX, 8, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_CE1, 9, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_CE2, 10, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_CE3, 11, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_CE0, 14, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_C0NC, 15, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_SOR, 17, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_DIS, 18, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_DISB, 19, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_XUSBA, 20, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_XUSBB, 21, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_XUSBC, 22, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_VIC, 23, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_IRAM, 24, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_NVDEC, 25, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_NVJPG, 26, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_AUD, 27, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_DFD, 28, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_VE2, 29, OFF, ON); + +DEFINE_PMC_REG(PWRGATE_STATUS_CE123, 9, 3); + +DEFINE_PMC_REG_BIT_ENUM(NO_IOPOWER_SDMMC1, 12, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(PWR_DET_SDMMC1, 12, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(PWR_DET_VAL_SDMMC1, 12, DISABLE, ENABLE); + +DEFINE_PMC_REG(SET_SW_CLAMP_CRAIL, 0, 1); + +DEFINE_PMC_REG_TWO_BIT_ENUM(IO_DPD_REQ_CODE, 30, IDLE, DPD_OFF, DPD_ON, RESERVED3); +DEFINE_PMC_REG_TWO_BIT_ENUM(IO_DPD2_REQ_CODE, 30, IDLE, DPD_OFF, DPD_ON, RESERVED3); + +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_CRAIL, 0, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_TE, 1, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_VE, 2, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_PCX, 3, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_VDE, 4, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_MPE, 6, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_HEG, 7, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_SAX, 8, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_CE1, 9, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_CE2, 10, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_CE3, 11, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_CELP, 12, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_CE0, 14, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_C0NC, 15, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_SOR, 17, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_C1NC, 16, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_DIS, 18, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_DISB, 19, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_XUSBA, 20, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_XUSBB, 21, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_XUSBC, 22, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_VIC, 23, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CLAMP_STATUS_IRAM, 24, DISABLE, ENABLE); + +DEFINE_PMC_REG(OSC_EDPD_OVER_XOFS, 1, 6); +DEFINE_PMC_REG_BIT_ENUM(OSC_EDPD_OVER_OSC_CTRL_SELECT, 22, CAR, PMC); + +DEFINE_PMC_REG(TSC_MULT_MULT_VAL, 0, 16); + +DEFINE_PMC_REG_BIT_ENUM(STICKY_BITS_HDA_LPBK_DIS, 0, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(STICKY_BITS_JTAG_STS, 6, ENABLE, DISABLE); + +DEFINE_PMC_REG_BIT_ENUM(CNTRL2_WAKE_DET_EN, 9, DISABLE, ENABLE); +DEFINE_PMC_REG_BIT_ENUM(CNTRL2_HOLD_CKE_LOW_EN, 12, DISABLE, ENABLE); + +DEFINE_PMC_REG_BIT_ENUM(SEC_DISABLE2_WRITE21, 26, OFF, ON); + +DEFINE_PMC_REG(TZRAM_PWR_CNTRL_TZRAM_SD, 0, 1); +DEFINE_PMC_REG(TZRAM_PWR_CNTRL_TZRAM_SLCG_OVR, 1, 1); + +DEFINE_PMC_REG_BIT_ENUM(TZRAM_SEC_DISABLE_SD_WRITE, 0, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(TZRAM_SEC_DISABLE_SD_READ, 1, OFF, ON); + +DEFINE_PMC_REG_BIT_ENUM(TZRAM_NON_SEC_DISABLE_SD_WRITE, 0, OFF, ON); +DEFINE_PMC_REG_BIT_ENUM(TZRAM_NON_SEC_DISABLE_SD_READ, 1, OFF, ON); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_pwm.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_pwm.hpp new file mode 100644 index 00000000..2a9479f9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_pwm.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#define PWM_CONTROLLER_PWM_CHANNEL_OFFSET(channel) (0x10 * channel) + +#define PWM_CONTROLLER_PWM_CSR (0x000) + + +#define PWM_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (PWM_CONTROLLER, NAME) +#define PWM_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (PWM_CONTROLLER, NAME, VALUE) +#define PWM_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (PWM_CONTROLLER, NAME, ENUM) +#define PWM_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(PWM_CONTROLLER, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + +#define DEFINE_PWM_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (PWM_CONTROLLER, NAME, __OFFSET__, __WIDTH__) +#define DEFINE_PWM_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (PWM_CONTROLLER, NAME, __OFFSET__, ZERO, ONE) +#define DEFINE_PWM_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (PWM_CONTROLLER, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) +#define DEFINE_PWM_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(PWM_CONTROLLER, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) +#define DEFINE_PWM_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (PWM_CONTROLLER, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + +DEFINE_PWM_REG(PWM_CSR_PFM, 0, 13); +DEFINE_PWM_REG(PWM_CSR_PWM, 16, 15); +DEFINE_PWM_REG_BIT_ENUM(PWM_CSR_ENB, 31, DISABLE, ENABLE); + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_sb.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_sb.hpp new file mode 100644 index 00000000..96562fc3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_sb.hpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#define SB_CSR (0x200) +#define SB_PFCFG (0x208) +#define SB_AA64_RESET_LOW (0x230) +#define SB_AA64_RESET_HIGH (0x234) + + +#define SB_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (SB, NAME) +#define SB_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (SB, NAME, VALUE) +#define SB_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (SB, NAME, ENUM) +#define SB_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(SB, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + +#define DEFINE_SB_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (SB, NAME, __OFFSET__, __WIDTH__) +#define DEFINE_SB_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (SB, NAME, __OFFSET__, ZERO, ONE) +#define DEFINE_SB_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (SB, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) +#define DEFINE_SB_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(SB, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) +#define DEFINE_SB_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (SB, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + +DEFINE_SB_REG_BIT_ENUM(CSR_SECURE_BOOT_FLAG, 0, DISABLE, ENABLE); +DEFINE_SB_REG_BIT_ENUM(CSR_NS_RST_VEC_WR_DIS, 1, ENABLE, DISABLE); +DEFINE_SB_REG_BIT_ENUM(CSR_PIROM_DISABLE, 4, ENABLE, DISABLE); +DEFINE_SB_REG_BIT_ENUM(CSR_HANG, 6, DISABLE, ENABLE); +DEFINE_SB_REG_BIT_ENUM(CSR_SWDM_ENABLE, 7, DISABLE, ENABLE); +DEFINE_SB_REG(CSR_SWDM_FAIL_COUNT, 8, 4); +DEFINE_SB_REG(CSR_COT_FAIL_COUNT, 12, 4); + +DEFINE_SB_REG_BIT_ENUM(PFCFG_SPNIDEN, 0, DISABLE, ENABLE); +DEFINE_SB_REG_BIT_ENUM(PFCFG_SPIDEN, 1, DISABLE, ENABLE); +DEFINE_SB_REG_BIT_ENUM(PFCFG_NIDEN, 2, DISABLE, ENABLE); +DEFINE_SB_REG_BIT_ENUM(PFCFG_DBGEN, 3, DISABLE, ENABLE); diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_sysctr0.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_sysctr0.hpp new file mode 100644 index 00000000..1854f4ca --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_sysctr0.hpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#define SYSCTR0_CNTCR (0x000) +#define SYSCTR0_CNTCV0 (0x008) +#define SYSCTR0_CNTCV1 (0x00C) +#define SYSCTR0_CNTFID0 (0x020) +#define SYSCTR0_CNTFID1 (0x024) + + +#define SYSCTR0_COUNTERID4 (0xFD0) +#define SYSCTR0_COUNTERID5 (0xFD4) +#define SYSCTR0_COUNTERID6 (0xFD8) +#define SYSCTR0_COUNTERID7 (0xFDC) +#define SYSCTR0_COUNTERID0 (0xFE0) +#define SYSCTR0_COUNTERID1 (0xFE4) +#define SYSCTR0_COUNTERID2 (0xFE8) +#define SYSCTR0_COUNTERID3 (0xFEC) +#define SYSCTR0_COUNTERID8 (0xFF0) +#define SYSCTR0_COUNTERID9 (0xFF4) +#define SYSCTR0_COUNTERID10 (0xFF8) +#define SYSCTR0_COUNTERID11 (0xFFC) + +#define SYSCTR0_COUNTERID(n) SYSCTR0_COUNTERID##n + +#define SYSCTR0_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (SYSCTR0, NAME) +#define SYSCTR0_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (SYSCTR0, NAME, VALUE) +#define SYSCTR0_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (SYSCTR0, NAME, ENUM) +#define SYSCTR0_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(SYSCTR0, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + +#define DEFINE_SYSCTR0_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (SYSCTR0, NAME, __OFFSET__, __WIDTH__) +#define DEFINE_SYSCTR0_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (SYSCTR0, NAME, __OFFSET__, ZERO, ONE) +#define DEFINE_SYSCTR0_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (SYSCTR0, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) +#define DEFINE_SYSCTR0_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(SYSCTR0, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) +#define DEFINE_SYSCTR0_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (SYSCTR0, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + +DEFINE_SYSCTR0_REG_BIT_ENUM(CNTCR_EN, 0, DISABLE, ENABLE); +DEFINE_SYSCTR0_REG_BIT_ENUM(CNTCR_HDBG, 1, DISABLE, ENABLE); \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_timer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_timer.hpp new file mode 100644 index 00000000..14045653 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/tegra/tegra_timer.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/literals.hpp> +#include <vapours/util.hpp> +#include <vapours/results.hpp> +#include <vapours/reg.hpp> + +#define TIMERUS_USEC_CFG (0x014) +#define TIMER_SHARED_TIMER_SECURE_CFG (0x1A4) + +#define TIMER_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (TIMER, NAME) +#define TIMER_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (TIMER, NAME, VALUE) +#define TIMER_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (TIMER, NAME, ENUM) +#define TIMER_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(TIMER, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + +#define DEFINE_TIMER_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (TIMER, NAME, __OFFSET__, __WIDTH__) +#define DEFINE_TIMER_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (TIMER, NAME, __OFFSET__, ZERO, ONE) +#define DEFINE_TIMER_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (TIMER, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) +#define DEFINE_TIMER_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(TIMER, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) +#define DEFINE_TIMER_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (TIMER, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + +DEFINE_TIMER_REG(USEC_CFG_USEC_DIVISOR, 0, 8); +DEFINE_TIMER_REG(USEC_CFG_USEC_DIVIDEND, 8, 8); + +DEFINE_TIMER_REG_BIT_ENUM(SHARED_TIMER_SECURE_CFG_TMR5, 5, DISABLE, ENABLE); +DEFINE_TIMER_REG_BIT_ENUM(SHARED_TIMER_SECURE_CFG_TMR6, 6, DISABLE, ENABLE); +DEFINE_TIMER_REG_BIT_ENUM(SHARED_TIMER_SECURE_CFG_TMR7, 7, DISABLE, ENABLE); +DEFINE_TIMER_REG_BIT_ENUM(SHARED_TIMER_SECURE_CFG_TMR8, 8, DISABLE, ENABLE); + +DEFINE_TIMER_REG_BIT_ENUM(SHARED_TIMER_SECURE_CFG_WDT0, 12, DISABLE, ENABLE); +DEFINE_TIMER_REG_BIT_ENUM(SHARED_TIMER_SECURE_CFG_WDT1, 13, DISABLE, ENABLE); +DEFINE_TIMER_REG_BIT_ENUM(SHARED_TIMER_SECURE_CFG_WDT2, 14, DISABLE, ENABLE); +DEFINE_TIMER_REG_BIT_ENUM(SHARED_TIMER_SECURE_CFG_WDT3, 15, DISABLE, ENABLE); \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/timespan.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/timespan.hpp new file mode 100644 index 00000000..435b787b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/timespan.hpp @@ -0,0 +1,106 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util/util_type_traits.hpp> +#include <chrono> + +namespace ams { + + struct TimeSpanType { + public: + s64 _ns; + public: + static constexpr ALWAYS_INLINE TimeSpanType FromNanoSeconds(s64 ns) { return {ns}; } + static constexpr ALWAYS_INLINE TimeSpanType FromMicroSeconds(s64 ms) { return FromNanoSeconds(ms * INT64_C(1000)); } + static constexpr ALWAYS_INLINE TimeSpanType FromMilliSeconds(s64 ms) { return FromMicroSeconds(ms * INT64_C(1000)); } + static constexpr ALWAYS_INLINE TimeSpanType FromSeconds(s64 s) { return FromMilliSeconds(s * INT64_C(1000)); } + static constexpr ALWAYS_INLINE TimeSpanType FromMinutes(s64 m) { return FromSeconds(m * INT64_C(60)); } + static constexpr ALWAYS_INLINE TimeSpanType FromHours(s64 h) { return FromMinutes(h * INT64_C(60)); } + static constexpr ALWAYS_INLINE TimeSpanType FromDays(s64 d) { return FromHours(d * INT64_C(24)); } + + constexpr ALWAYS_INLINE s64 GetNanoSeconds() const { return _ns; } + constexpr ALWAYS_INLINE s64 GetMicroSeconds() const { return this->GetNanoSeconds() / (INT64_C(1000)); } + constexpr ALWAYS_INLINE s64 GetMilliSeconds() const { return this->GetNanoSeconds() / (INT64_C(1000) * INT64_C(1000)); } + constexpr ALWAYS_INLINE s64 GetSeconds() const { return this->GetNanoSeconds() / (INT64_C(1000) * INT64_C(1000) * INT64_C(1000)); } + constexpr ALWAYS_INLINE s64 GetMinutes() const { return this->GetNanoSeconds() / (INT64_C(1000) * INT64_C(1000) * INT64_C(1000) * INT64_C( 60)); } + constexpr ALWAYS_INLINE s64 GetHours() const { return this->GetNanoSeconds() / (INT64_C(1000) * INT64_C(1000) * INT64_C(1000) * INT64_C( 60) * INT64_C( 60)); } + constexpr ALWAYS_INLINE s64 GetDays() const { return this->GetNanoSeconds() / (INT64_C(1000) * INT64_C(1000) * INT64_C(1000) * INT64_C( 60) * INT64_C( 60) * INT64_C( 24)); } + + constexpr ALWAYS_INLINE friend bool operator==(const TimeSpanType &lhs, const TimeSpanType &rhs) { return lhs._ns == rhs._ns; } + constexpr ALWAYS_INLINE friend bool operator!=(const TimeSpanType &lhs, const TimeSpanType &rhs) { return lhs._ns != rhs._ns; } + constexpr ALWAYS_INLINE friend bool operator<=(const TimeSpanType &lhs, const TimeSpanType &rhs) { return lhs._ns <= rhs._ns; } + constexpr ALWAYS_INLINE friend bool operator>=(const TimeSpanType &lhs, const TimeSpanType &rhs) { return lhs._ns >= rhs._ns; } + constexpr ALWAYS_INLINE friend bool operator< (const TimeSpanType &lhs, const TimeSpanType &rhs) { return lhs._ns < rhs._ns; } + constexpr ALWAYS_INLINE friend bool operator> (const TimeSpanType &lhs, const TimeSpanType &rhs) { return lhs._ns > rhs._ns; } + + constexpr ALWAYS_INLINE TimeSpanType &operator+=(const TimeSpanType &rhs) { _ns += rhs._ns; return *this; } + constexpr ALWAYS_INLINE TimeSpanType &operator-=(const TimeSpanType &rhs) { _ns -= rhs._ns; return *this; } + + constexpr ALWAYS_INLINE friend TimeSpanType operator+(const TimeSpanType &lhs, const TimeSpanType &rhs) { TimeSpanType r(lhs); return r += rhs; } + constexpr ALWAYS_INLINE friend TimeSpanType operator-(const TimeSpanType &lhs, const TimeSpanType &rhs) { TimeSpanType r(lhs); return r -= rhs; } + }; + static_assert(util::is_pod<TimeSpanType>::value); + + class TimeSpan { + private: + using ZeroTag = const class ZeroTagImpl{} *; + private: + TimeSpanType m_ts; + public: + constexpr ALWAYS_INLINE TimeSpan(ZeroTag z = nullptr) : m_ts(TimeSpanType::FromNanoSeconds(0)) { AMS_UNUSED(z); /* ... */ } + constexpr ALWAYS_INLINE TimeSpan(const TimeSpanType &t) : m_ts(t) { /* ... */ } + + template<typename R, typename P> + constexpr ALWAYS_INLINE TimeSpan(const std::chrono::duration<R, P>& c) : m_ts(TimeSpanType::FromNanoSeconds(static_cast<std::chrono::nanoseconds>(c).count())) { /* ... */ } + public: + static constexpr ALWAYS_INLINE TimeSpan FromNanoSeconds(s64 ns) { return TimeSpanType::FromNanoSeconds(ns); } + static constexpr ALWAYS_INLINE TimeSpan FromMicroSeconds(s64 ms) { return TimeSpanType::FromMicroSeconds(ms); } + static constexpr ALWAYS_INLINE TimeSpan FromMilliSeconds(s64 ms) { return TimeSpanType::FromMilliSeconds(ms); } + static constexpr ALWAYS_INLINE TimeSpan FromSeconds(s64 s) { return TimeSpanType::FromSeconds(s); } + static constexpr ALWAYS_INLINE TimeSpan FromMinutes(s64 m) { return TimeSpanType::FromMinutes(m); } + static constexpr ALWAYS_INLINE TimeSpan FromHours(s64 h) { return TimeSpanType::FromHours(h); } + static constexpr ALWAYS_INLINE TimeSpan FromDays(s64 d) { return TimeSpanType::FromDays(d); } + + constexpr ALWAYS_INLINE s64 GetNanoSeconds() const { return m_ts.GetNanoSeconds(); } + constexpr ALWAYS_INLINE s64 GetMicroSeconds() const { return m_ts.GetMicroSeconds(); } + constexpr ALWAYS_INLINE s64 GetMilliSeconds() const { return m_ts.GetMilliSeconds(); } + constexpr ALWAYS_INLINE s64 GetSeconds() const { return m_ts.GetSeconds(); } + constexpr ALWAYS_INLINE s64 GetMinutes() const { return m_ts.GetMinutes(); } + constexpr ALWAYS_INLINE s64 GetHours() const { return m_ts.GetHours(); } + constexpr ALWAYS_INLINE s64 GetDays() const { return m_ts.GetDays(); } + + constexpr ALWAYS_INLINE friend bool operator==(const TimeSpan &lhs, const TimeSpan &rhs) { return lhs.m_ts == rhs.m_ts; } + constexpr ALWAYS_INLINE friend bool operator!=(const TimeSpan &lhs, const TimeSpan &rhs) { return lhs.m_ts != rhs.m_ts; } + constexpr ALWAYS_INLINE friend bool operator<=(const TimeSpan &lhs, const TimeSpan &rhs) { return lhs.m_ts <= rhs.m_ts; } + constexpr ALWAYS_INLINE friend bool operator>=(const TimeSpan &lhs, const TimeSpan &rhs) { return lhs.m_ts >= rhs.m_ts; } + constexpr ALWAYS_INLINE friend bool operator< (const TimeSpan &lhs, const TimeSpan &rhs) { return lhs.m_ts < rhs.m_ts; } + constexpr ALWAYS_INLINE friend bool operator> (const TimeSpan &lhs, const TimeSpan &rhs) { return lhs.m_ts > rhs.m_ts; } + + constexpr ALWAYS_INLINE TimeSpan &operator+=(const TimeSpan &rhs) { m_ts += rhs.m_ts; return *this; } + constexpr ALWAYS_INLINE TimeSpan &operator-=(const TimeSpan &rhs) { m_ts -= rhs.m_ts; return *this; } + + constexpr ALWAYS_INLINE friend TimeSpan operator+(const TimeSpan &lhs, const TimeSpan &rhs) { TimeSpan r(lhs); return r += rhs; } + constexpr ALWAYS_INLINE friend TimeSpan operator-(const TimeSpan &lhs, const TimeSpan &rhs) { TimeSpan r(lhs); return r -= rhs; } + + constexpr ALWAYS_INLINE operator TimeSpanType() const { + return m_ts; + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/types.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/types.hpp new file mode 100644 index 00000000..d22f944a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/types.hpp @@ -0,0 +1,99 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <cstdint> +#include <cstddef> + +/* NOTE: This file serves as a substitute for libnx <switch/types.h>. */ + +typedef uint8_t u8; ///< 8-bit unsigned integer. +typedef uint16_t u16; ///< 16-bit unsigned integer. +typedef uint32_t u32; ///< 32-bit unsigned integer. +typedef uint64_t u64; ///< 64-bit unsigned integer. + +typedef int8_t s8; ///< 8-bit signed integer. +typedef int16_t s16; ///< 16-bit signed integer. +typedef int32_t s32; ///< 32-bit signed integer. +typedef int64_t s64; ///< 64-bit signed integer. + +typedef volatile u8 vu8; ///< 8-bit volatile unsigned integer. +typedef volatile u16 vu16; ///< 16-bit volatile unsigned integer. +typedef volatile u32 vu32; ///< 32-bit volatile unsigned integer. +typedef volatile u64 vu64; ///< 64-bit volatile unsigned integer. + +typedef volatile s8 vs8; ///< 8-bit volatile signed integer. +typedef volatile s16 vs16; ///< 16-bit volatile signed integer. +typedef volatile s32 vs32; ///< 32-bit volatile signed integer. +typedef volatile s64 vs64; ///< 64-bit volatile signed integer. + +#ifdef ATMOSPHERE_ARCH_ARM64 +typedef __uint128_t u128; ///< 128-bit unsigned integer. +typedef __int128_t s128; ///< 128-bit unsigned integer. +typedef volatile u128 vu128; ///< 128-bit volatile unsigned integer. +typedef volatile s128 vs128; ///< 128-bit volatile signed integer. +#endif + +typedef u32 Result; ///< Function error code result type. + +/// Creates a bitmask from a bit number. +#ifndef BIT +#define BIT(n) (1U<<(n)) +#endif + +/// Creates a bitmask from a bit number (long). +#ifndef BITL +#define BITL(n) (1UL<<(n)) +#endif + +/// Creates a bitmask representing the n least significant bits. +#ifndef MASK +#define MASK(n) (BIT(n) - 1U) +#endif + +/// Creates a bitmask representing the n least significant bits (long). +#ifndef MASKL +#define MASKL(n) (BITL(n) - 1UL) +#endif + +/// Creates a bitmask for bit range extraction. +#ifndef MASK2 +#define MASK2(a,b) (MASK((a) + 1) & ~MASK(b)) +#endif + +/// Creates a bitmask for bit range extraction (long). +#ifndef MASK2L +#define MASK2L(a,b) (MASKL((a) + 1) & ~MASKL(b)) +#endif + +/// Marks a function as not returning, for the purposes of compiler optimization. +#ifndef NORETURN +#define NORETURN __attribute__((noreturn)) +#endif + +/// This will get un-defined by <vapours/results/results_common.hpp> +#define R_SUCCEEDED(res) (res == 0) +#define R_FAILED(res) (res != 0) + + +/// Flags a function as (always) inline. +#define NX_INLINE __attribute__((always_inline)) static inline + +/// Flags a function as constexpr in C++14 and above; or as (always) inline otherwise. +#if __cplusplus >= 201402L +#define NX_CONSTEXPR NX_INLINE constexpr +#else +#define NX_CONSTEXPR NX_INLINE +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util.hpp new file mode 100644 index 00000000..0bc4a6bd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util.hpp @@ -0,0 +1,65 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +#include <vapours/util/util_type_traits.hpp> +#include <vapours/util/util_alignment.hpp> +#include <vapours/util/util_size.hpp> +#include <vapours/util/util_int_util.hpp> +#include <vapours/util/util_pointer_util.hpp> +#include <vapours/util/util_aligned_buffer.hpp> +#include <vapours/util/util_enum.hpp> +#include <vapours/util/util_endian.hpp> +#include <vapours/util/util_exchange.hpp> +#include <vapours/util/util_scope_guard.hpp> +#include <vapours/util/util_specialization_of.hpp> +#include <vapours/util/util_optional.hpp> +#include <vapours/util/util_bitpack.hpp> +#include <vapours/util/util_bitset.hpp> +#include <vapours/util/util_bitflagset.hpp> +#include <vapours/util/util_bitutil.hpp> +#include <vapours/util/util_typed_storage.hpp> +#include <vapours/util/util_fourcc.hpp> +#include <vapours/util/util_intrusive_list.hpp> +#include <vapours/util/util_intrusive_red_black_tree.hpp> +#include <vapours/util/util_tinymt.hpp> +#include <vapours/util/util_timer.hpp> +#include <vapours/util/util_uuid.hpp> +#include <vapours/util/util_bounded_map.hpp> +#include <vapours/util/util_overlap.hpp> +#include <vapours/util/util_string_util.hpp> +#include <vapours/util/util_string_view.hpp> +#include <vapours/util/util_variadic.hpp> +#include <vapours/util/util_character_encoding.hpp> +#include <vapours/util/util_format_string.hpp> +#include <vapours/util/util_range.hpp> +#include <vapours/util/util_utf8_string_util.hpp> + +#include <vapours/util/util_fixed_map.hpp> +#include <vapours/util/util_fixed_set.hpp> + +#include <vapours/util/util_atomic.hpp> + +#include <vapours/util/util_function_local_static.hpp> + +#include <vapours/util/util_i_function.hpp> + +#ifdef ATMOSPHERE_IS_STRATOSPHERE +#include <vapours/util/util_mutex_utils.hpp> +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/arch/arm64/util_atomic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/arch/arm64/util_atomic.hpp new file mode 100644 index 00000000..a06b54b5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/arch/arm64/util_atomic.hpp @@ -0,0 +1,432 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + namespace impl { + + template<typename T> + struct AtomicIntegerStorage; + + template<typename T> requires (sizeof(T) == sizeof(u8)) + struct AtomicIntegerStorage<T> { + using Type = u8; + }; + + template<typename T> requires (sizeof(T) == sizeof(u16)) + struct AtomicIntegerStorage<T> { + using Type = u16; + }; + + template<typename T> requires (sizeof(T) == sizeof(u32)) + struct AtomicIntegerStorage<T> { + using Type = u32; + }; + + template<typename T> requires (sizeof(T) == sizeof(u64)) + struct AtomicIntegerStorage<T> { + using Type = u64; + }; + + template<typename T> + concept UsableAtomicType = (sizeof(T) <= sizeof(u64)) && !std::is_const<T>::value && !std::is_volatile<T>::value && (std::is_pointer<T>::value || requires (const T &t) { + std::bit_cast<typename AtomicIntegerStorage<T>::Type, T>(t); + }); + + template<UsableAtomicType T> + using AtomicStorage = typename AtomicIntegerStorage<T>::Type; + + + static_assert(std::same_as<AtomicStorage<void *>, u64>); + + static_assert(std::same_as<AtomicStorage<s8>, u8>); + static_assert(std::same_as<AtomicStorage<u8>, u8>); + static_assert(std::same_as<AtomicStorage<s16>, u16>); + static_assert(std::same_as<AtomicStorage<u16>, u16>); + static_assert(std::same_as<AtomicStorage<s32>, u32>); + static_assert(std::same_as<AtomicStorage<u32>, u32>); + static_assert(std::same_as<AtomicStorage<s64>, u64>); + static_assert(std::same_as<AtomicStorage<u64>, u64>); + + ALWAYS_INLINE void ClearExclusiveForAtomic() { + __asm__ __volatile__("clrex" ::: "memory"); + } + + #define AMS_UTIL_IMPL_DEFINE_ATOMIC_LOAD_FUNCTION(_FNAME_, _MNEMONIC_) \ + template<std::unsigned_integral T> T _FNAME_ ##ForAtomic(const volatile T *); \ + \ + template<> ALWAYS_INLINE u8 _FNAME_ ##ForAtomic(const volatile u8 *p) { u8 v; __asm__ __volatile__(_MNEMONIC_ "b %w[v], %[p]" : [v]"=r"(v) : [p]"Q"(*p) : "memory"); return v; } \ + template<> ALWAYS_INLINE u16 _FNAME_ ##ForAtomic(const volatile u16 *p) { u16 v; __asm__ __volatile__(_MNEMONIC_ "h %w[v], %[p]" : [v]"=r"(v) : [p]"Q"(*p) : "memory"); return v; } \ + template<> ALWAYS_INLINE u32 _FNAME_ ##ForAtomic(const volatile u32 *p) { u32 v; __asm__ __volatile__(_MNEMONIC_ " %w[v], %[p]" : [v]"=r"(v) : [p]"Q"(*p) : "memory"); return v; } \ + template<> ALWAYS_INLINE u64 _FNAME_ ##ForAtomic(const volatile u64 *p) { u64 v; __asm__ __volatile__(_MNEMONIC_ " %[v], %[p]" : [v]"=r"(v) : [p]"Q"(*p) : "memory"); return v; } + + AMS_UTIL_IMPL_DEFINE_ATOMIC_LOAD_FUNCTION(LoadAcquire, "ldar") + AMS_UTIL_IMPL_DEFINE_ATOMIC_LOAD_FUNCTION(LoadExclusive, "ldxr") + AMS_UTIL_IMPL_DEFINE_ATOMIC_LOAD_FUNCTION(LoadAcquireExclusive, "ldaxr") + + #undef AMS_UTIL_IMPL_DEFINE_ATOMIC_LOAD_FUNCTION + + template<std::unsigned_integral T> void StoreReleaseForAtomic(volatile T *, T); + + template<> ALWAYS_INLINE void StoreReleaseForAtomic(volatile u8 *p, u8 v) { __asm__ __volatile__("stlrb %w[v], %[p]" : : [v]"r"(v), [p]"Q"(*p) : "memory"); } + template<> ALWAYS_INLINE void StoreReleaseForAtomic(volatile u16 *p, u16 v) { __asm__ __volatile__("stlrh %w[v], %[p]" : : [v]"r"(v), [p]"Q"(*p) : "memory"); } + template<> ALWAYS_INLINE void StoreReleaseForAtomic(volatile u32 *p, u32 v) { __asm__ __volatile__("stlr %w[v], %[p]" : : [v]"r"(v), [p]"Q"(*p) : "memory"); } + template<> ALWAYS_INLINE void StoreReleaseForAtomic(volatile u64 *p, u64 v) { __asm__ __volatile__("stlr %[v], %[p]" : : [v]"r"(v), [p]"Q"(*p) : "memory"); } + + #define AMS_UTIL_IMPL_DEFINE_ATOMIC_STORE_EXCLUSIVE_FUNCTION(_FNAME_, _MNEMONIC_) \ + template<std::unsigned_integral T> bool _FNAME_ ##ForAtomic(volatile T *, T); \ + \ + template<> ALWAYS_INLINE bool _FNAME_ ##ForAtomic(volatile u8 *p, u8 v) { int result; __asm__ __volatile__(_MNEMONIC_ "b %w[result], %w[v], %[p]" : [result]"=&r"(result) : [v]"r"(v), [p]"Q"(*p) : "memory"); return result == 0; } \ + template<> ALWAYS_INLINE bool _FNAME_ ##ForAtomic(volatile u16 *p, u16 v) { int result; __asm__ __volatile__(_MNEMONIC_ "h %w[result], %w[v], %[p]" : [result]"=&r"(result) : [v]"r"(v), [p]"Q"(*p) : "memory"); return result == 0; } \ + template<> ALWAYS_INLINE bool _FNAME_ ##ForAtomic(volatile u32 *p, u32 v) { int result; __asm__ __volatile__(_MNEMONIC_ " %w[result], %w[v], %[p]" : [result]"=&r"(result) : [v]"r"(v), [p]"Q"(*p) : "memory"); return result == 0; } \ + template<> ALWAYS_INLINE bool _FNAME_ ##ForAtomic(volatile u64 *p, u64 v) { int result; __asm__ __volatile__(_MNEMONIC_ " %w[result], %[v], %[p]" : [result]"=&r"(result) : [v]"r"(v), [p]"Q"(*p) : "memory"); return result == 0; } + + AMS_UTIL_IMPL_DEFINE_ATOMIC_STORE_EXCLUSIVE_FUNCTION(StoreExclusive, "stxr") + AMS_UTIL_IMPL_DEFINE_ATOMIC_STORE_EXCLUSIVE_FUNCTION(StoreReleaseExclusive, "stlxr") + + #undef AMS_UTIL_IMPL_DEFINE_ATOMIC_STORE_EXCLUSIVE_FUNCTION + + template<UsableAtomicType T> + constexpr ALWAYS_INLINE T ConvertToTypeForAtomic(AtomicStorage<T> s) { + if constexpr (std::integral<T>) { + return static_cast<T>(s); + } else if constexpr(std::is_pointer<T>::value) { + return reinterpret_cast<T>(s); + } else { + return std::bit_cast<T>(s); + } + } + + template<UsableAtomicType T> + constexpr ALWAYS_INLINE AtomicStorage<T> ConvertToStorageForAtomic(T arg) { + if constexpr (std::integral<T>) { + return static_cast<AtomicStorage<T>>(arg); + } else if constexpr(std::is_pointer<T>::value) { + if (std::is_constant_evaluated() && arg == nullptr) { + return 0; + } + + return reinterpret_cast<AtomicStorage<T>>(arg); + } else { + return std::bit_cast<AtomicStorage<T>>(arg); + } + } + + template<std::memory_order Order, std::unsigned_integral StorageType> + ALWAYS_INLINE StorageType AtomicLoadImpl(volatile StorageType * const p) { + if constexpr (Order != std::memory_order_relaxed) { + return ::ams::util::impl::LoadAcquireForAtomic(p); + } else { + return *p; + } + } + + template<std::memory_order Order, std::unsigned_integral StorageType> + ALWAYS_INLINE void AtomicStoreImpl(volatile StorageType * const p, const StorageType s) { + if constexpr (Order != std::memory_order_relaxed) { + ::ams::util::impl::StoreReleaseForAtomic(p, s); + } else { + *p = s; + } + } + + template<std::memory_order Order, std::unsigned_integral StorageType> + ALWAYS_INLINE StorageType LoadExclusiveForAtomicByMemoryOrder(volatile StorageType * const p) { + if constexpr (Order == std::memory_order_relaxed) { + return ::ams::util::impl::LoadExclusiveForAtomic(p); + } else if constexpr (Order == std::memory_order_consume || Order == std::memory_order_acquire) { + return ::ams::util::impl::LoadAcquireExclusiveForAtomic(p); + } else if constexpr (Order == std::memory_order_release) { + return ::ams::util::impl::LoadExclusiveForAtomic(p); + } else if constexpr (Order == std::memory_order_acq_rel || Order == std::memory_order_seq_cst) { + return ::ams::util::impl::LoadAcquireExclusiveForAtomic(p); + } else { + static_assert(false, "Invalid memory order"); + } + } + + template<std::memory_order Order, std::unsigned_integral StorageType> + ALWAYS_INLINE bool StoreExclusiveForAtomicByMemoryOrder(volatile StorageType * const p, const StorageType s) { + if constexpr (Order == std::memory_order_relaxed) { + return ::ams::util::impl::StoreExclusiveForAtomic(p, s); + } else if constexpr (Order == std::memory_order_consume || Order == std::memory_order_acquire) { + return ::ams::util::impl::StoreExclusiveForAtomic(p, s); + } else if constexpr (Order == std::memory_order_release) { + return ::ams::util::impl::StoreReleaseExclusiveForAtomic(p, s); + } else if constexpr (Order == std::memory_order_acq_rel || Order == std::memory_order_seq_cst) { + return ::ams::util::impl::StoreReleaseExclusiveForAtomic(p, s); + } else { + static_assert(false, "Invalid memory order"); + } + } + + template<std::memory_order Order, std::unsigned_integral StorageType> + ALWAYS_INLINE StorageType AtomicExchangeImpl(volatile StorageType * const p, const StorageType s) { + StorageType current; + do { + current = ::ams::util::impl::LoadExclusiveForAtomicByMemoryOrder<Order>(p); + } while(AMS_UNLIKELY(!impl::StoreExclusiveForAtomicByMemoryOrder<Order>(p, s))); + + return current; + } + + template<std::memory_order Order, UsableAtomicType T> + ALWAYS_INLINE bool AtomicCompareExchangeWeakImpl(volatile AtomicStorage<T> * const p, T &expected, T desired) { + const AtomicStorage<T> e = ::ams::util::impl::ConvertToStorageForAtomic(expected); + const AtomicStorage<T> d = ::ams::util::impl::ConvertToStorageForAtomic(desired); + + const AtomicStorage<T> current = ::ams::util::impl::LoadExclusiveForAtomicByMemoryOrder<Order>(p); + if (AMS_UNLIKELY(current != e)) { + impl::ClearExclusiveForAtomic(); + expected = ::ams::util::impl::ConvertToTypeForAtomic<T>(current); + return false; + } + + return AMS_LIKELY(impl::StoreExclusiveForAtomicByMemoryOrder<Order>(p, d)); + } + + template<std::memory_order Order, UsableAtomicType T> + ALWAYS_INLINE bool AtomicCompareExchangeStrongImpl(volatile AtomicStorage<T> * const p, T &expected, T desired) { + const AtomicStorage<T> e = ::ams::util::impl::ConvertToStorageForAtomic(expected); + const AtomicStorage<T> d = ::ams::util::impl::ConvertToStorageForAtomic(desired); + + do { + if (const AtomicStorage<T> current = ::ams::util::impl::LoadExclusiveForAtomicByMemoryOrder<Order>(p); AMS_UNLIKELY(current != e)) { + impl::ClearExclusiveForAtomic(); + expected = ::ams::util::impl::ConvertToTypeForAtomic<T>(current); + return false; + } + } while (AMS_UNLIKELY(!impl::StoreExclusiveForAtomicByMemoryOrder<Order>(p, d))); + + return true; + } + + } + + template<impl::UsableAtomicType T> + class Atomic { + NON_COPYABLE(Atomic); + NON_MOVEABLE(Atomic); + private: + using StorageType = impl::AtomicStorage<T>; + + static constexpr bool IsIntegral = std::integral<T>; + static constexpr bool IsPointer = std::is_pointer<T>::value; + + static constexpr bool HasArithmeticFunctions = IsIntegral || IsPointer; + + using DifferenceType = typename std::conditional<IsIntegral, T, typename std::conditional<IsPointer, std::ptrdiff_t, void>::type>::type; + + static constexpr ALWAYS_INLINE T ConvertToType(StorageType s) { + return impl::ConvertToTypeForAtomic<T>(s); + } + + static constexpr ALWAYS_INLINE StorageType ConvertToStorage(T arg) { + return impl::ConvertToStorageForAtomic<T>(arg); + } + private: + StorageType m_v; + private: + ALWAYS_INLINE volatile StorageType *GetStoragePointer() { return reinterpret_cast< volatile StorageType *>(std::addressof(m_v)); } + ALWAYS_INLINE const volatile StorageType *GetStoragePointer() const { return reinterpret_cast<const volatile StorageType *>(std::addressof(m_v)); } + public: + ALWAYS_INLINE Atomic() { /* ... */ } + constexpr ALWAYS_INLINE Atomic(T v) : m_v(ConvertToStorage(v)) { /* ... */ } + + constexpr ALWAYS_INLINE T operator=(T desired) { + if (std::is_constant_evaluated()) { + m_v = ConvertToStorage(desired); + } else { + this->Store(desired); + } + return desired; + } + + ALWAYS_INLINE operator T() const { return this->Load(); } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE T Load() const { + return ConvertToType(impl::AtomicLoadImpl<Order>(this->GetStoragePointer())); + } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE void Store(T arg) { + return impl::AtomicStoreImpl<Order>(this->GetStoragePointer(), ConvertToStorage(arg)); + } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE T Exchange(T arg) { + return ConvertToType(impl::AtomicExchangeImpl<Order>(this->GetStoragePointer(), ConvertToStorage(arg))); + } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE bool CompareExchangeWeak(T &expected, T desired) { + return impl::AtomicCompareExchangeWeakImpl<Order, T>(this->GetStoragePointer(), expected, desired); + } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE bool CompareExchangeStrong(T &expected, T desired) { + return impl::AtomicCompareExchangeStrongImpl<Order, T>(this->GetStoragePointer(), expected, desired); + } + + #define AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(_OPERATION_, _OPERATOR_, _POINTER_ALLOWED_) \ + template<bool Enable = (IsIntegral || (_POINTER_ALLOWED_ && IsPointer)), typename = typename std::enable_if<Enable, void>::type> \ + ALWAYS_INLINE T Fetch ## _OPERATION_(DifferenceType arg) { \ + static_assert(Enable == (IsIntegral || (_POINTER_ALLOWED_ && IsPointer))); \ + volatile StorageType * const p = this->GetStoragePointer(); \ + \ + StorageType current; \ + do { \ + current = impl::LoadAcquireExclusiveForAtomic<StorageType>(p); \ + } while (AMS_UNLIKELY(!impl::StoreReleaseExclusiveForAtomic<StorageType>(p, ConvertToStorage(ConvertToType(current) _OPERATOR_ arg)))); \ + return ConvertToType(current); \ + } \ + \ + template<bool Enable = (IsIntegral || (_POINTER_ALLOWED_ && IsPointer)), typename = typename std::enable_if<Enable, void>::type> \ + ALWAYS_INLINE T operator _OPERATOR_##=(DifferenceType arg) { \ + static_assert(Enable == (IsIntegral || (_POINTER_ALLOWED_ && IsPointer))); \ + return this->Fetch ## _OPERATION_(arg) _OPERATOR_ arg; \ + } + + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(Add, +, true) + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(Sub, -, true) + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(And, &, false) + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(Or, |, false) + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(Xor, ^, false) + + #undef AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION + + template<bool Enable = HasArithmeticFunctions, typename = typename std::enable_if<Enable, void>::type> + ALWAYS_INLINE T operator++() { static_assert(Enable == HasArithmeticFunctions); return this->FetchAdd(1) + 1; } + + template<bool Enable = HasArithmeticFunctions, typename = typename std::enable_if<Enable, void>::type> + ALWAYS_INLINE T operator++(int) { static_assert(Enable == HasArithmeticFunctions); return this->FetchAdd(1); } + + template<bool Enable = HasArithmeticFunctions, typename = typename std::enable_if<Enable, void>::type> + ALWAYS_INLINE T operator--() { static_assert(Enable == HasArithmeticFunctions); return this->FetchSub(1) - 1; } + + template<bool Enable = HasArithmeticFunctions, typename = typename std::enable_if<Enable, void>::type> + ALWAYS_INLINE T operator--(int) { static_assert(Enable == HasArithmeticFunctions); return this->FetchSub(1); } + }; + + template<impl::UsableAtomicType T> + class AtomicRef { + NON_MOVEABLE(AtomicRef); + public: + static constexpr size_t RequiredAlignment = std::max<size_t>(sizeof(T), alignof(T)); + private: + using StorageType = impl::AtomicStorage<T>; + static_assert(sizeof(StorageType) == sizeof(T)); + static_assert(alignof(StorageType) >= alignof(T)); + + static constexpr bool IsIntegral = std::integral<T>; + static constexpr bool IsPointer = std::is_pointer<T>::value; + + static constexpr bool HasArithmeticFunctions = IsIntegral || IsPointer; + + using DifferenceType = typename std::conditional<IsIntegral, T, typename std::conditional<IsPointer, std::ptrdiff_t, void>::type>::type; + + static constexpr ALWAYS_INLINE T ConvertToType(StorageType s) { + return impl::ConvertToTypeForAtomic<T>(s); + } + + static constexpr ALWAYS_INLINE StorageType ConvertToStorage(T arg) { + return impl::ConvertToStorageForAtomic<T>(arg); + } + private: + volatile StorageType * const m_p; + private: + ALWAYS_INLINE volatile StorageType *GetStoragePointer() const { return m_p; } + public: + explicit ALWAYS_INLINE AtomicRef(T &t) : m_p(reinterpret_cast<volatile StorageType *>(std::addressof(t))) { /* ... */ } + ALWAYS_INLINE AtomicRef(const AtomicRef &) noexcept = default; + + AtomicRef() = delete; + AtomicRef &operator=(const AtomicRef &) = delete; + + ALWAYS_INLINE T operator=(T desired) const { return const_cast<AtomicRef *>(this)->Store(desired); } + + ALWAYS_INLINE operator T() const { return this->Load(); } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE T Load() const { + return ConvertToType(impl::AtomicLoadImpl<Order>(this->GetStoragePointer())); + } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE void Store(T arg) const { + return impl::AtomicStoreImpl<Order>(this->GetStoragePointer(), ConvertToStorage(arg)); + } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE T Exchange(T arg) const { + return ConvertToType(impl::AtomicExchangeImpl<Order>(this->GetStoragePointer(), ConvertToStorage(arg))); + } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE bool CompareExchangeWeak(T &expected, T desired) const { + return impl::AtomicCompareExchangeWeakImpl<Order, T>(this->GetStoragePointer(), expected, desired); + } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE bool CompareExchangeStrong(T &expected, T desired) const { + return impl::AtomicCompareExchangeStrongImpl<Order, T>(this->GetStoragePointer(), expected, desired); + } + + #define AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(_OPERATION_, _OPERATOR_, _POINTER_ALLOWED_) \ + template<bool Enable = (IsIntegral || (_POINTER_ALLOWED_ && IsPointer)), typename = typename std::enable_if<Enable, void>::type> \ + ALWAYS_INLINE T Fetch ## _OPERATION_(DifferenceType arg) const { \ + static_assert(Enable == (IsIntegral || (_POINTER_ALLOWED_ && IsPointer))); \ + volatile StorageType * const p = this->GetStoragePointer(); \ + \ + StorageType current; \ + do { \ + current = impl::LoadAcquireExclusiveForAtomic<StorageType>(p); \ + } while (AMS_UNLIKELY(!impl::StoreReleaseExclusiveForAtomic<StorageType>(p, ConvertToStorage(ConvertToType(current) _OPERATOR_ arg)))); \ + return ConvertToType(current); \ + } \ + \ + template<bool Enable = (IsIntegral || (_POINTER_ALLOWED_ && IsPointer)), typename = typename std::enable_if<Enable, void>::type> \ + ALWAYS_INLINE T operator _OPERATOR_##=(DifferenceType arg) const { \ + static_assert(Enable == (IsIntegral || (_POINTER_ALLOWED_ && IsPointer))); \ + return this->Fetch ## _OPERATION_(arg) _OPERATOR_ arg; \ + } + + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(Add, +, true) + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(Sub, -, true) + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(And, &, false) + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(Or, |, false) + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(Xor, ^, false) + + #undef AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION + + template<bool Enable = HasArithmeticFunctions, typename = typename std::enable_if<Enable, void>::type> + ALWAYS_INLINE T operator++() const { static_assert(Enable == HasArithmeticFunctions); return this->FetchAdd(1) + 1; } + + template<bool Enable = HasArithmeticFunctions, typename = typename std::enable_if<Enable, void>::type> + ALWAYS_INLINE T operator++(int) const { static_assert(Enable == HasArithmeticFunctions); return this->FetchAdd(1); } + + template<bool Enable = HasArithmeticFunctions, typename = typename std::enable_if<Enable, void>::type> + ALWAYS_INLINE T operator--() const { static_assert(Enable == HasArithmeticFunctions); return this->FetchSub(1) - 1; } + + template<bool Enable = HasArithmeticFunctions, typename = typename std::enable_if<Enable, void>::type> + ALWAYS_INLINE T operator--(int) const { static_assert(Enable == HasArithmeticFunctions); return this->FetchSub(1); } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/arch/generic/util_atomic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/arch/generic/util_atomic.hpp new file mode 100644 index 00000000..a3af2226 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/arch/generic/util_atomic.hpp @@ -0,0 +1,226 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + namespace impl { + + template<typename T> + struct AtomicIntegerStorage; + + template<typename T> requires (sizeof(T) == sizeof(u8)) + struct AtomicIntegerStorage<T> { + using Type = u8; + }; + + template<typename T> requires (sizeof(T) == sizeof(u16)) + struct AtomicIntegerStorage<T> { + using Type = u16; + }; + + template<typename T> requires (sizeof(T) == sizeof(u32)) + struct AtomicIntegerStorage<T> { + using Type = u32; + }; + + template<typename T> requires (sizeof(T) == sizeof(u64)) + struct AtomicIntegerStorage<T> { + using Type = u64; + }; + + template<typename T> + concept UsableAtomicType = (sizeof(T) <= sizeof(u64)) && !std::is_const<T>::value && !std::is_volatile<T>::value && (std::is_pointer<T>::value || requires (const T &t) { + std::bit_cast<typename AtomicIntegerStorage<T>::Type, T>(t); + }); + + } + + template<impl::UsableAtomicType T> + class Atomic { + NON_COPYABLE(Atomic); + NON_MOVEABLE(Atomic); + private: + static constexpr bool IsIntegral = std::integral<T>; + static constexpr bool IsPointer = std::is_pointer<T>::value; + + static constexpr bool HasArithmeticFunctions = IsIntegral || IsPointer; + + using DifferenceType = typename std::conditional<IsIntegral, T, typename std::conditional<IsPointer, std::ptrdiff_t, void>::type>::type; + private: + static_assert(std::atomic<T>::is_always_lock_free); + private: + std::atomic<T> m_v; + public: + ALWAYS_INLINE Atomic() { /* ... */ } + constexpr ALWAYS_INLINE Atomic(T v) : m_v(v) { /* ... */ } + + ALWAYS_INLINE T operator=(T desired) { + return (m_v = desired); + } + + ALWAYS_INLINE operator T() const { return this->Load(); } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE T Load() const { + return m_v.load(Order); + } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE void Store(T arg) { + return m_v.store(arg, Order); + } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE T Exchange(T arg) { + return m_v.exchange(arg, Order); + } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE bool CompareExchangeWeak(T &expected, T desired) { + return m_v.compare_exchange_weak(expected, desired, Order); + } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE bool CompareExchangeStrong(T &expected, T desired) { + return m_v.compare_exchange_strong(expected, desired, Order); + } + + #define AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(_OPERATION_, _OPERATION_LOWER_, _OPERATOR_, _POINTER_ALLOWED_) \ + template<bool Enable = (IsIntegral || (_POINTER_ALLOWED_ && IsPointer)), typename = typename std::enable_if<Enable, void>::type> \ + ALWAYS_INLINE T Fetch ## _OPERATION_(DifferenceType arg) { \ + static_assert(Enable == (IsIntegral || (_POINTER_ALLOWED_ && IsPointer))); \ + return m_v.fetch_##_OPERATION_LOWER_(arg); \ + } \ + \ + template<bool Enable = (IsIntegral || (_POINTER_ALLOWED_ && IsPointer)), typename = typename std::enable_if<Enable, void>::type> \ + ALWAYS_INLINE T operator _OPERATOR_##=(DifferenceType arg) { \ + static_assert(Enable == (IsIntegral || (_POINTER_ALLOWED_ && IsPointer))); \ + return this->Fetch##_OPERATION_(arg) _OPERATOR_ arg; \ + } + + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(Add, add, +, true) + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(Sub, sub, -, true) + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(And, and, &, false) + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(Or, or, |, false) + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(Xor, xor, ^, false) + + #undef AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION + + template<bool Enable = HasArithmeticFunctions, typename = typename std::enable_if<Enable, void>::type> + ALWAYS_INLINE T operator++() { static_assert(Enable == HasArithmeticFunctions); return this->FetchAdd(1) + 1; } + + template<bool Enable = HasArithmeticFunctions, typename = typename std::enable_if<Enable, void>::type> + ALWAYS_INLINE T operator++(int) { static_assert(Enable == HasArithmeticFunctions); return this->FetchAdd(1); } + + template<bool Enable = HasArithmeticFunctions, typename = typename std::enable_if<Enable, void>::type> + ALWAYS_INLINE T operator--() { static_assert(Enable == HasArithmeticFunctions); return this->FetchSub(1) - 1; } + + template<bool Enable = HasArithmeticFunctions, typename = typename std::enable_if<Enable, void>::type> + ALWAYS_INLINE T operator--(int) { static_assert(Enable == HasArithmeticFunctions); return this->FetchSub(1); } + }; + + /* TODO: Clang does not yet define std::atomic_ref, so if we want this we will have to implement it ourselves. */ + #if !defined(ATMOSPHERE_COMPILER_CLANG) + template<impl::UsableAtomicType T> + class AtomicRef { + NON_MOVEABLE(AtomicRef); + public: + static constexpr size_t RequiredAlignment = std::atomic_ref<T>::required_alignment; + private: + static constexpr bool IsIntegral = std::integral<T>; + static constexpr bool IsPointer = std::is_pointer<T>::value; + + static constexpr bool HasArithmeticFunctions = IsIntegral || IsPointer; + + using DifferenceType = typename std::conditional<IsIntegral, T, typename std::conditional<IsPointer, std::ptrdiff_t, void>::type>::type; + private: + static_assert(std::atomic_ref<T>::is_always_lock_free); + private: + std::atomic_ref<T> m_ref; + public: + explicit ALWAYS_INLINE AtomicRef(T &t) : m_ref(t) { /* ... */ } + ALWAYS_INLINE AtomicRef(const AtomicRef &) noexcept = default; + + AtomicRef() = delete; + AtomicRef &operator=(const AtomicRef &) = delete; + + ALWAYS_INLINE T operator=(T desired) const { return (m_ref = desired); } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE T Load() const { + return m_ref.load(Order); + } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE void Store(T arg) const { + return m_ref.store(arg, Order); + } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE T Exchange(T arg) const { + return m_ref.exchange(arg, Order); + } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE bool CompareExchangeWeak(T &expected, T desired) const { + return m_ref.compare_exchange_weak(expected, desired, Order); + } + + template<std::memory_order Order = std::memory_order_seq_cst> + ALWAYS_INLINE bool CompareExchangeStrong(T &expected, T desired) const { + return m_ref.compare_exchange_strong(expected, desired, Order); + } + + #define AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(_OPERATION_, _OPERATION_LOWER_, _OPERATOR_, _POINTER_ALLOWED_) \ + template<bool Enable = (IsIntegral || (_POINTER_ALLOWED_ && IsPointer)), typename = typename std::enable_if<Enable, void>::type> \ + ALWAYS_INLINE T Fetch ## _OPERATION_(DifferenceType arg) const { \ + static_assert(Enable == (IsIntegral || (_POINTER_ALLOWED_ && IsPointer))); \ + return m_ref.fetch_##_OPERATION_LOWER_(arg); \ + } \ + \ + template<bool Enable = (IsIntegral || (_POINTER_ALLOWED_ && IsPointer)), typename = typename std::enable_if<Enable, void>::type> \ + ALWAYS_INLINE T operator _OPERATOR_##=(DifferenceType arg) const { \ + static_assert(Enable == (IsIntegral || (_POINTER_ALLOWED_ && IsPointer))); \ + return this->Fetch##_OPERATION_(arg) _OPERATOR_ arg; \ + } + + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(Add, add, +, true) + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(Sub, sub, -, true) + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(And, and, &, false) + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(Or, or, |, false) + AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION(Xor, xor, ^, false) + + #undef AMS_UTIL_IMPL_DEFINE_ATOMIC_FETCH_OPERATE_FUNCTION + + template<bool Enable = HasArithmeticFunctions, typename = typename std::enable_if<Enable, void>::type> + ALWAYS_INLINE T operator++() const { static_assert(Enable == HasArithmeticFunctions); return this->FetchAdd(1) + 1; } + + template<bool Enable = HasArithmeticFunctions, typename = typename std::enable_if<Enable, void>::type> + ALWAYS_INLINE T operator++(int) const { static_assert(Enable == HasArithmeticFunctions); return this->FetchAdd(1); } + + template<bool Enable = HasArithmeticFunctions, typename = typename std::enable_if<Enable, void>::type> + ALWAYS_INLINE T operator--() const { static_assert(Enable == HasArithmeticFunctions); return this->FetchSub(1) - 1; } + + template<bool Enable = HasArithmeticFunctions, typename = typename std::enable_if<Enable, void>::type> + ALWAYS_INLINE T operator--(int) const { static_assert(Enable == HasArithmeticFunctions); return this->FetchSub(1); } + }; + #endif + + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/impl/util_available_index_finder.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/impl/util_available_index_finder.hpp new file mode 100644 index 00000000..5bbfb2ad --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/impl/util_available_index_finder.hpp @@ -0,0 +1,220 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util/util_fourcc.hpp> + +namespace ams::util::impl { + + class AvailableIndexFinder { + private: + static constexpr int MaxDepthOfBox = 5; + + struct BitFlags64 { + private: + u64 m_data; + public: + constexpr ALWAYS_INLINE bool GetFlag(int index) const { + AMS_ASSERT(index < 64); + return ((m_data >> index) & UINT64_C(1)) != 0; + } + + constexpr ALWAYS_INLINE void SetFlag(int index) { + AMS_ASSERT(index < 64); + m_data |= (UINT64_C(1) << index); + } + + constexpr ALWAYS_INLINE void ClearFlag(int index) { + AMS_ASSERT(index < 64); + m_data &= ~(UINT64_C(1) << index); + } + + constexpr ALWAYS_INLINE bool IsFull() const { + return m_data == ~(UINT64_C(0)); + } + + constexpr ALWAYS_INLINE int FindIndexOfBitZero() const { + AMS_ASSERT(!this->IsFull()); + return __builtin_ctzll(~m_data); + } + }; + private: + int *m_p_current_index; + int *m_p_map_index; + void *m_buffer; + int m_depth; + BitFlags64 *m_flags; + private: + static constexpr int Pow64(int e) { + switch (e) { + case 0: return 0x1; + case 1: return 0x40; + case 2: return 0x1000; + case 3: return 0x40000; + case 4: return 0x1000000; + case 5: return 0x40000000; + default: return -1; + } + } + + static constexpr u64 Roundup64(u64 value) { + return (value + (64 - 1)) / 64; + } + + static constexpr size_t GetRequiredNumOfBox(int depth, size_t num_elements) { + if (depth == 1) { + return Roundup64(num_elements); + } else if (depth == 2) { + return Roundup64(num_elements) + Pow64(0); + } else if (depth == 3) { + return Roundup64(num_elements) + Pow64(0) + Pow64(1) + Pow64(2); + } else if (depth == 4) { + return Roundup64(num_elements) + Pow64(0) + Pow64(1) + Pow64(2) + Pow64(3); + } else if (depth == 5) { + return Roundup64(num_elements) + Pow64(0) + Pow64(1) + Pow64(2) + Pow64(3) + Pow64(4); + } else { + return 0; + } + } + + static constexpr int CalcOffset(int *arr, int depth) { + int offset = 0; + for (auto i = 0; i < depth; ++i) { + offset += Pow64(i); + } + for (auto i = 0; i < depth; ++i) { + offset += Pow64(i) - arr[depth - 1 - i]; + } + return offset; + } + public: + static consteval int GetSignature() { + return static_cast<int>(util::ReverseFourCC<'B', 'I', 'T', 'S'>::Code); + } + + static constexpr int GetNeedDepth(size_t num_elements) { + if (num_elements <= 0x40) { + return 1; + } else if (num_elements <= 0x1000) { + return 2; + } else if (num_elements <= 0x40000) { + return 3; + } else if (num_elements <= 0x1000000) { + return 4; + } else if (num_elements <= 0x40000000) { + return 5; + } else { + return -1; + } + } + + static constexpr size_t GetRequiredMemorySize(size_t num_elements) { + return sizeof(BitFlags64) * GetRequiredNumOfBox(GetNeedDepth(num_elements), num_elements); + } + public: + void Initialize(int *cur, int *map, u8 *buf) { + const size_t num_elements = static_cast<size_t>(*map); + AMS_ASSERT(num_elements <= static_cast<size_t>(Pow64(MaxDepthOfBox - 1))); + + /* Set fields. */ + m_p_current_index = cur; + m_p_map_index = map; + m_buffer = buf; + m_depth = GetNeedDepth(num_elements); + + /* Validate fields. */ + AMS_ASSERT(m_depth > 0); + + /* Setup memory. */ + std::memset(m_buffer, 0, GetRequiredMemorySize(num_elements)); + m_flags = reinterpret_cast<BitFlags64 *>(m_buffer); + } + + int AcquireIndex() { + /* Validate pre-conditions. */ + AMS_ASSERT(*m_p_current_index < *m_p_map_index); + + /* Build up arrays. */ + int table[MaxDepthOfBox]; + BitFlags64 *pos[MaxDepthOfBox]; + for (auto i = 0; i < m_depth; ++i) { + /* Determine the position. */ + pos[i] = std::addressof(m_flags[CalcOffset(table, i)]); + + /* Set table entry. */ + table[i] = pos[i]->FindIndexOfBitZero(); + AMS_ASSERT(table[i] != BITSIZEOF(u64)); + } + + /* Validate that the index is not acquired. */ + AMS_ASSERT(!pos[m_depth - 1]->GetFlag(table[m_depth - 1])); + + /* Acquire the index. */ + pos[m_depth - 1]->SetFlag(table[m_depth - 1]); + + /* Validate that the index was acquired. */ + AMS_ASSERT(pos[m_depth - 1]->GetFlag(table[m_depth - 1])); + + /* Update tracking flags. */ + for (auto i = m_depth - 1; i > 0; --i) { + if (pos[i]->IsFull()) { + pos[i - 1]->SetFlag(table[i - 1]); + } + } + + /* Calculate the index we acquired. */ + int index = 0, pow = 0; + for (auto i = m_depth; i > 0; --i, ++pow) { + index += Pow64(pow) * table[i - 1]; + } + + /* Increment current index. */ + ++(*m_p_current_index); + + return index; + } + + void ReleaseIndex(int index) { + /* Convert index to table. */ + int table[MaxDepthOfBox]; + for (auto i = 0; i < m_depth; ++i) { + table[m_depth - 1 - i] = index % BITSIZEOF(u64); + index /= BITSIZEOF(u64); + } + + /* Build up arrays. */ + BitFlags64 *pos[MaxDepthOfBox]; + for (auto i = 0; i < m_depth; ++i) { + /* Determine the position. */ + pos[i] = std::addressof(m_flags[CalcOffset(table, i)]); + } + + /* Validate that the flag is set. */ + AMS_ASSERT(pos[m_depth - 1]->GetFlag(table[m_depth - 1])); + + /* Clear the flags. */ + for (auto i = m_depth - 1; i >= 0; --i) { + pos[i]->ClearFlag(table[i]); + } + + /* Decrement current index. */ + --(*m_p_current_index); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/impl/util_enable_copy_move.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/impl/util_enable_copy_move.hpp new file mode 100644 index 00000000..88d1ab54 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/impl/util_enable_copy_move.hpp @@ -0,0 +1,147 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util::impl { + + template<bool Copy, bool CopyAssign, bool Move, bool MoveAssign, typename Tag = void> + struct EnableCopyMove{}; + + template<typename Tag> + struct EnableCopyMove<false, true, true, true, Tag> { + constexpr EnableCopyMove() noexcept = default; + + constexpr EnableCopyMove(const EnableCopyMove &) noexcept = delete; + constexpr EnableCopyMove(EnableCopyMove &&) noexcept = default; + constexpr EnableCopyMove &operator=(const EnableCopyMove &) noexcept = default; + constexpr EnableCopyMove &operator=(EnableCopyMove &&) noexcept = default; + }; + + template<typename Tag> + struct EnableCopyMove<true, false, true, true, Tag> { + constexpr EnableCopyMove() noexcept = default; + + constexpr EnableCopyMove(const EnableCopyMove &) noexcept = default; + constexpr EnableCopyMove(EnableCopyMove &&) noexcept = default; + constexpr EnableCopyMove &operator=(const EnableCopyMove &) noexcept = delete; + constexpr EnableCopyMove &operator=(EnableCopyMove &&) noexcept = default; + }; + + template<typename Tag> + struct EnableCopyMove<false, false, true, true, Tag> { + constexpr EnableCopyMove() noexcept = default; + + constexpr EnableCopyMove(const EnableCopyMove &) noexcept = delete; + constexpr EnableCopyMove(EnableCopyMove &&) noexcept = default; + constexpr EnableCopyMove &operator=(const EnableCopyMove &) noexcept = delete; + constexpr EnableCopyMove &operator=(EnableCopyMove &&) noexcept = default; + }; + + template<typename Tag> + struct EnableCopyMove<true, true, false, true, Tag> { + constexpr EnableCopyMove() noexcept = default; + + constexpr EnableCopyMove(const EnableCopyMove &) noexcept = default; + constexpr EnableCopyMove(EnableCopyMove &&) noexcept = delete; + constexpr EnableCopyMove &operator=(const EnableCopyMove &) noexcept = default; + constexpr EnableCopyMove &operator=(EnableCopyMove &&) noexcept = default; + }; + + template<typename Tag> + struct EnableCopyMove<false, true, false, true, Tag> { + constexpr EnableCopyMove() noexcept = default; + + constexpr EnableCopyMove(const EnableCopyMove &) noexcept = delete; + constexpr EnableCopyMove(EnableCopyMove &&) noexcept = delete; + constexpr EnableCopyMove &operator=(const EnableCopyMove &) noexcept = default; + constexpr EnableCopyMove &operator=(EnableCopyMove &&) noexcept = default; + }; + + template<typename Tag> + struct EnableCopyMove<true, false, false, true, Tag> { + constexpr EnableCopyMove() noexcept = default; + + constexpr EnableCopyMove(const EnableCopyMove &) noexcept = default; + constexpr EnableCopyMove(EnableCopyMove &&) noexcept = delete; + constexpr EnableCopyMove &operator=(const EnableCopyMove &) noexcept = delete; + constexpr EnableCopyMove &operator=(EnableCopyMove &&) noexcept = default; + }; + + template<typename Tag> + struct EnableCopyMove<false, false, false, true, Tag> { + constexpr EnableCopyMove() noexcept = default; + + constexpr EnableCopyMove(const EnableCopyMove &) noexcept = delete; + constexpr EnableCopyMove(EnableCopyMove &&) noexcept = delete; + constexpr EnableCopyMove &operator=(const EnableCopyMove &) noexcept = delete; + constexpr EnableCopyMove &operator=(EnableCopyMove &&) noexcept = default; + }; + + template<typename Tag> + struct EnableCopyMove<true, true, true, false, Tag> { + constexpr EnableCopyMove() noexcept = default; + + constexpr EnableCopyMove(const EnableCopyMove &) noexcept = default; + constexpr EnableCopyMove(EnableCopyMove &&) noexcept = default; + constexpr EnableCopyMove &operator=(const EnableCopyMove &) noexcept = default; + constexpr EnableCopyMove &operator=(EnableCopyMove &&) noexcept = delete; + }; + + template<typename Tag> + struct EnableCopyMove<true, true, false, false, Tag> { + constexpr EnableCopyMove() noexcept = default; + + constexpr EnableCopyMove(const EnableCopyMove &) noexcept = default; + constexpr EnableCopyMove(EnableCopyMove &&) noexcept = delete; + constexpr EnableCopyMove &operator=(const EnableCopyMove &) noexcept = default; + constexpr EnableCopyMove &operator=(EnableCopyMove &&) noexcept = delete; + }; + + template<typename Tag> + struct EnableCopyMove<false, true, false, false, Tag> { + constexpr EnableCopyMove() noexcept = default; + + constexpr EnableCopyMove(const EnableCopyMove &) noexcept = delete; + constexpr EnableCopyMove(EnableCopyMove &&) noexcept = delete; + constexpr EnableCopyMove &operator=(const EnableCopyMove &) noexcept = default; + constexpr EnableCopyMove &operator=(EnableCopyMove &&) noexcept = delete; + }; + + template<typename Tag> + struct EnableCopyMove<true, false, false, false, Tag> { + constexpr EnableCopyMove() noexcept = default; + + constexpr EnableCopyMove(const EnableCopyMove &) noexcept = default; + constexpr EnableCopyMove(EnableCopyMove &&) noexcept = delete; + constexpr EnableCopyMove &operator=(const EnableCopyMove &) noexcept = delete; + constexpr EnableCopyMove &operator=(EnableCopyMove &&) noexcept = delete; + }; + + template<typename Tag> + struct EnableCopyMove<false, false, false, false, Tag> { + constexpr EnableCopyMove() noexcept = default; + + constexpr EnableCopyMove(const EnableCopyMove &) noexcept = delete; + constexpr EnableCopyMove(EnableCopyMove &&) noexcept = delete; + constexpr EnableCopyMove &operator=(const EnableCopyMove &) noexcept = delete; + constexpr EnableCopyMove &operator=(EnableCopyMove &&) noexcept = delete; + }; + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_aligned_buffer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_aligned_buffer.hpp new file mode 100644 index 00000000..2fdb88ad --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_aligned_buffer.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util/util_alignment.hpp> + +namespace ams::util { + + template<size_t Alignment, size_t Size> + class AlignedBuffer { + private: + static constexpr size_t AlignedSize = ((Size + Alignment - 1) / Alignment) * Alignment; + static_assert(AlignedSize % Alignment == 0); + private: + u8 m_buffer[Alignment + AlignedSize]; + public: + ALWAYS_INLINE operator u8 *() { return reinterpret_cast<u8 *>(util::AlignUp(reinterpret_cast<uintptr_t>(m_buffer), Alignment)); } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_alignment.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_alignment.hpp new file mode 100644 index 00000000..017c82ce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_alignment.hpp @@ -0,0 +1,81 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util/util_bitutil.hpp> + +namespace ams::util { + + /* Utilities for alignment to power of two. */ + template<typename T> + constexpr ALWAYS_INLINE T AlignUp(T value, size_t alignment) { + using U = typename std::make_unsigned<T>::type; + const U invmask = static_cast<U>(alignment - 1); + return static_cast<T>((value + invmask) & ~invmask); + } + + template<typename T> + constexpr ALWAYS_INLINE T AlignDown(T value, size_t alignment) { + using U = typename std::make_unsigned<T>::type; + const U invmask = static_cast<U>(alignment - 1); + return static_cast<T>(value & ~invmask); + } + + template<typename T> + constexpr ALWAYS_INLINE bool IsAligned(T value, size_t alignment) { + using U = typename std::make_unsigned<T>::type; + const U invmask = static_cast<U>(alignment - 1); + return (value & invmask) == 0; + } + + template<typename T> requires std::unsigned_integral<T> + constexpr ALWAYS_INLINE T GetAlignment(T value) { + return value & -value; + } + + template<> + ALWAYS_INLINE void *AlignUp<void *>(void *value, size_t alignment) { + return reinterpret_cast<void *>(AlignUp(reinterpret_cast<uintptr_t>(value), alignment)); + } + + template<> + ALWAYS_INLINE const void *AlignUp<const void *>(const void *value, size_t alignment) { + return reinterpret_cast<const void *>(AlignUp(reinterpret_cast<uintptr_t>(value), alignment)); + } + + template<> + ALWAYS_INLINE void *AlignDown<void *>(void *value, size_t alignment) { + return reinterpret_cast<void *>(AlignDown(reinterpret_cast<uintptr_t>(value), alignment)); + } + + template<> + ALWAYS_INLINE const void *AlignDown<const void *>(const void *value, size_t alignment) { + return reinterpret_cast<void *>(AlignDown(reinterpret_cast<uintptr_t>(value), alignment)); + } + + template<> + ALWAYS_INLINE bool IsAligned<void *>(void *value, size_t alignment) { + return IsAligned(reinterpret_cast<uintptr_t>(value), alignment); + } + + template<> + ALWAYS_INLINE bool IsAligned<const void *>(const void *value, size_t alignment) { + return IsAligned(reinterpret_cast<uintptr_t>(value), alignment); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_atomic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_atomic.hpp new file mode 100644 index 00000000..8b1526b8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_atomic.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +#if defined(ATMOSPHERE_ARCH_ARM64) + + #include <vapours/util/arch/arm64/util_atomic.hpp> + +#else + + #include <vapours/util/arch/generic/util_atomic.hpp> + +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_bitflagset.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_bitflagset.hpp new file mode 100644 index 00000000..c3cb7865 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_bitflagset.hpp @@ -0,0 +1,197 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util/util_alignment.hpp> +#include <vapours/util/util_bitutil.hpp> + +namespace ams::util { + + namespace impl { + + template<size_t Count, typename Storage> + constexpr ALWAYS_INLINE void NegateImpl(Storage arr[]) { + for (size_t i = 0; i < Count; i++) { + arr[i] = ~arr[i]; + } + } + + template<size_t Count, typename Storage> + constexpr ALWAYS_INLINE void AndImpl(Storage dst[], const Storage src[]) { + for (size_t i = 0; i < Count; i++) { + dst[i] &= src[i]; + } + } + + template<size_t Count, typename Storage> + constexpr ALWAYS_INLINE void OrImpl(Storage dst[], const Storage src[]) { + for (size_t i = 0; i < Count; i++) { + dst[i] |= src[i]; + } + } + + template<size_t Count, typename Storage> + constexpr ALWAYS_INLINE void XorImpl(Storage dst[], const Storage src[]) { + for (size_t i = 0; i < Count; i++) { + dst[i] ^= src[i]; + } + } + + template<size_t Count, typename Storage> + constexpr ALWAYS_INLINE bool IsEqual(const Storage lhs[], const Storage rhs[]) { + for (size_t i = 0; i < Count; i++) { + if (lhs[i] != rhs[i]) { + return false; + } + } + return true; + } + + template<size_t Count, typename Storage> + constexpr ALWAYS_INLINE bool IsAnySet(const Storage arr[]) { + for (size_t i = 0; i < Count; i++) { + if (arr[i]) { + return true; + } + } + return false; + } + + template<size_t Count, typename Storage> + constexpr ALWAYS_INLINE int PopCount(const Storage arr[]) { + int count = 0; + for (size_t i = 0; i < Count; i++) { + count += ::ams::util::PopCount(arr[i]); + } + return count; + } + + } + + template<size_t N, typename T = void> + struct BitFlagSet { + static_assert(N > 0); + using Storage = typename std::conditional<N <= BITSIZEOF(u32), u32, u64>::type; + static constexpr size_t StorageBitCount = BITSIZEOF(Storage); + static constexpr size_t StorageCount = util::AlignUp(N, StorageBitCount) / StorageBitCount; + Storage _storage[StorageCount]; + private: + constexpr ALWAYS_INLINE BitFlagSet<N, T> &SetImpl(s32 idx, Storage mask, bool en) { + if (en) { + this->_storage[idx] |= mask; + } else { + this->_storage[idx] &= ~mask; + } + return *this; + } + + constexpr ALWAYS_INLINE bool TestImpl(s32 idx, Storage mask) const { return (this->_storage[idx] & mask) != 0; } + constexpr ALWAYS_INLINE void Truncate() { TruncateIf(std::integral_constant<bool, N % StorageBitCount != 0>{}); } + constexpr ALWAYS_INLINE void TruncateIf(std::true_type) { this->_storage[StorageCount - 1] &= MakeStorageMask(N) - 1; } + constexpr ALWAYS_INLINE void TruncateIf(std::false_type) { /* ... */ } + + static constexpr ALWAYS_INLINE s32 GetStorageIndex(s32 idx) { return idx / StorageBitCount; } + static constexpr ALWAYS_INLINE Storage MakeStorageMask(s32 idx) { return static_cast<Storage>(1) << (idx % StorageBitCount); } + public: + class Reference { + friend struct BitFlagSet<N, T>; + private: + BitFlagSet<N, T> *m_set; + s32 m_idx; + private: + constexpr ALWAYS_INLINE Reference() : m_set(nullptr), m_idx(0) { /* ... */ } + constexpr ALWAYS_INLINE Reference(BitFlagSet<N, T> &s, s32 i) : m_set(std::addressof(s)), m_idx(i) { /* ... */ } + public: + constexpr ALWAYS_INLINE Reference &operator=(bool en) { m_set->Set(m_idx, en); return *this; } + constexpr ALWAYS_INLINE Reference &operator=(const Reference &r) { m_set->Set(m_idx, r); return *this; } + constexpr ALWAYS_INLINE Reference &Negate() { m_set->Negate(m_idx); return *this; } + constexpr ALWAYS_INLINE operator bool() const { return m_set->Test(m_idx); } + constexpr ALWAYS_INLINE bool operator~() const { return !m_set->Test(m_idx); } + }; + + template<s32 _Index> + struct Flag { + static_assert(_Index < static_cast<s32>(N)); + friend struct BitFlagSet<N, T>; + static constexpr s32 Index = _Index; + static const BitFlagSet<N, T> Mask; + private: + static constexpr s32 StorageIndex = Index / StorageBitCount; + static constexpr Storage StorageMask = static_cast<Storage>(1) << (Index % StorageBitCount); + + template<size_t StorageCount> + struct SingleStorageTrait { + static_assert(StorageCount == 1); + using Type = Storage; + }; + }; + + template<typename FlagType> + constexpr ALWAYS_INLINE bool Test() const { return this->TestImpl(FlagType::StorageIndex, FlagType::StorageMask); } + constexpr ALWAYS_INLINE bool Test(s32 idx) const { return this->TestImpl(GetStorageIndex(idx), MakeStorageMask(idx)); } + + template<typename FlagType> + constexpr ALWAYS_INLINE BitFlagSet<N, T> &Set(bool en = true) { return this->SetImpl(FlagType::StorageIndex, FlagType::StorageMask, en); } + constexpr ALWAYS_INLINE BitFlagSet<N, T> &Set(s32 idx, bool en = true) { return this->SetImpl(GetStorageIndex(idx), MakeStorageMask(idx), en); } + constexpr ALWAYS_INLINE BitFlagSet<N, T> &Set() { std::memset(this->_storage, ~0, sizeof(this->_storage)); this->Truncate(); return *this; } + + template<typename FlagType> + constexpr ALWAYS_INLINE BitFlagSet<N, T> &Reset() { return this->Set<FlagType>(false); } + constexpr ALWAYS_INLINE BitFlagSet<N, T> &Reset(s32 idx) { return this->Set(idx, false); } + constexpr ALWAYS_INLINE BitFlagSet<N, T> &Reset() { std::memset(this->_storage, 0, sizeof(this->_storage)); this->Truncate(); return *this; } + + template<typename FlagType> + constexpr ALWAYS_INLINE BitFlagSet<N, T> &Negate() { return this->Set<FlagType>(!this->Test<FlagType>()); } + constexpr ALWAYS_INLINE BitFlagSet<N, T> &Negate(s32 idx) { return this->Set(idx, !this->Test(idx)); } + constexpr ALWAYS_INLINE BitFlagSet<N, T> &Negate() { ams::util::impl::NegateImpl<StorageCount>(this->_storage); this->Truncate(); return *this; } + + consteval static int GetCount() { return static_cast<int>(N); } + + constexpr ALWAYS_INLINE bool IsAnySet() const { return ams::util::impl::IsAnySet<StorageCount>(this->_storage); } + constexpr ALWAYS_INLINE int PopCount() const { return ams::util::impl::PopCount<StorageCount>(this->_storage); } + constexpr ALWAYS_INLINE bool IsAllSet() const { return this->PopCount() == this->GetCount(); } + constexpr ALWAYS_INLINE bool IsAllOff() const { return !this->IsAnySet(); } + + constexpr ALWAYS_INLINE bool operator[](s32 idx) const { return this->Test(idx); } + constexpr ALWAYS_INLINE Reference operator[](s32 idx) { return Reference(*this, idx); } + + constexpr ALWAYS_INLINE bool operator==(const BitFlagSet<N, T> &rhs) const { return ams::util::impl::IsEqual<StorageCount>(this->_storage, rhs._storage); } + constexpr ALWAYS_INLINE bool operator!=(const BitFlagSet<N, T> &rhs) const { return !(*this == rhs); } + + constexpr ALWAYS_INLINE BitFlagSet<N, T> operator~() const { BitFlagSet<N, T> tmp = *this; return tmp.Negate(); } + + constexpr ALWAYS_INLINE BitFlagSet<N, T> operator&(const BitFlagSet<N, T> &rhs) const { BitFlagSet<N, T> v = *this; v &= rhs; return v; } + constexpr ALWAYS_INLINE BitFlagSet<N, T> operator^(const BitFlagSet<N, T> &rhs) const { BitFlagSet<N, T> v = *this; v ^= rhs; return v; } + constexpr ALWAYS_INLINE BitFlagSet<N, T> operator|(const BitFlagSet<N, T> &rhs) const { BitFlagSet<N, T> v = *this; v |= rhs; return v; } + + constexpr ALWAYS_INLINE BitFlagSet<N, T> &operator&=(const BitFlagSet<N, T> &rhs) { ams::util::impl::AndImpl<StorageCount>(this->_storage, rhs._storage); return *this; } + constexpr ALWAYS_INLINE BitFlagSet<N, T> &operator^=(const BitFlagSet<N, T> &rhs) { ams::util::impl::XorImpl<StorageCount>(this->_storage, rhs._storage); return *this; } + constexpr ALWAYS_INLINE BitFlagSet<N, T> &operator|=(const BitFlagSet<N, T> &rhs) { ams::util::impl::OrImpl<StorageCount>(this->_storage, rhs._storage); return *this; } + }; + + template<size_t N, typename T> + template<s32 Index> + constexpr inline const BitFlagSet<N, T> BitFlagSet<N, T>::Flag<Index>::Mask = { { static_cast<typename SingleStorageTrait<BitFlagSet<N, T>::StorageCount>::Type>(1) << Index } }; + + template<size_t N, typename T> + constexpr BitFlagSet<N, T> MakeBitFlagSet() { return BitFlagSet<N, T>{}; } + + template<size_t N> + constexpr BitFlagSet<N, void> MakeBitFlagSet() { return MakeBitFlagSet<N, void>(); } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_bitpack.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_bitpack.hpp new file mode 100644 index 00000000..8a753305 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_bitpack.hpp @@ -0,0 +1,96 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + namespace impl { + + template<typename IntegralStorageType> + struct BitPack { + IntegralStorageType value; + private: + static_assert(std::is_integral<IntegralStorageType>::value); + static_assert(std::is_unsigned<IntegralStorageType>::value); + + template<size_t Index, size_t Count> + static constexpr inline IntegralStorageType Mask = [] { + static_assert(Index < BITSIZEOF(IntegralStorageType)); + static_assert(0 < Count && Count <= BITSIZEOF(IntegralStorageType)); + static_assert(Index + Count <= BITSIZEOF(IntegralStorageType)); + + if constexpr (Count == BITSIZEOF(IntegralStorageType)) { + return ~IntegralStorageType(0); + } else { + return ((IntegralStorageType(1) << Count) - 1) << Index; + } + }(); + public: + template<size_t _Index, size_t _Count, typename T = IntegralStorageType> + struct Field { + using Type = T; + static constexpr size_t Index = _Index; + static constexpr size_t Count = _Count; + static constexpr size_t Next = Index + Count; + + using BitPackType = BitPack<IntegralStorageType>; + static_assert(util::is_pod<BitPackType>::value); + + static_assert(Mask<Index, Count> != 0); + static_assert(std::is_integral<T>::value || std::is_enum<T>::value); + static_assert(!std::is_same<T, bool>::value || Count == 1); + }; + public: + constexpr ALWAYS_INLINE void Clear() { + constexpr IntegralStorageType Zero = IntegralStorageType(0); + this->value = Zero; + } + + template<typename FieldType> + constexpr ALWAYS_INLINE typename FieldType::Type Get() const { + static_assert(std::is_same<FieldType, Field<FieldType::Index, FieldType::Count, typename FieldType::Type>>::value); + return static_cast<typename FieldType::Type>((this->value & Mask<FieldType::Index, FieldType::Count>) >> FieldType::Index); + } + + template<typename FieldType> + constexpr ALWAYS_INLINE void Set(typename FieldType::Type field_value) { + static_assert(std::is_same<FieldType, Field<FieldType::Index, FieldType::Count, typename FieldType::Type>>::value); + constexpr IntegralStorageType FieldMask = Mask<FieldType::Index, FieldType::Count>; + this->value &= ~FieldMask; + this->value |= (static_cast<IntegralStorageType>(field_value) << FieldType::Index) & FieldMask; + } + }; + + } + + using BitPack8 = impl::BitPack<u8>; + using BitPack16 = impl::BitPack<u16>; + using BitPack32 = impl::BitPack<u32>; + using BitPack64 = impl::BitPack<u64>; + + static_assert(util::is_pod<BitPack8>::value); + static_assert(util::is_pod<BitPack16>::value); + static_assert(util::is_pod<BitPack32>::value); + static_assert(util::is_pod<BitPack64>::value); + static_assert(std::is_trivially_destructible<BitPack8 >::value); + static_assert(std::is_trivially_destructible<BitPack16>::value); + static_assert(std::is_trivially_destructible<BitPack32>::value); + static_assert(std::is_trivially_destructible<BitPack64>::value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_bitset.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_bitset.hpp new file mode 100644 index 00000000..21497774 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_bitset.hpp @@ -0,0 +1,89 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util/util_bitutil.hpp> + +namespace ams::util { + + namespace impl { + + template<typename Storage, size_t N> + class BitSet { + private: + static_assert(std::is_integral<Storage>::value); + static_assert(std::is_unsigned<Storage>::value); + static_assert(sizeof(Storage) <= sizeof(u64)); + + static constexpr size_t FlagsPerWord = BITSIZEOF(Storage); + static constexpr size_t NumWords = util::DivideUp(N, FlagsPerWord); + + static constexpr ALWAYS_INLINE Storage GetBitMask(size_t bit) { + return static_cast<Storage>(1) << (FlagsPerWord - 1 - bit); + } + private: + Storage m_words[NumWords]; + public: + constexpr ALWAYS_INLINE BitSet() : m_words() { /* ... */ } + + constexpr ALWAYS_INLINE void SetBit(size_t i) { + m_words[i / FlagsPerWord] |= GetBitMask(i % FlagsPerWord); + } + + constexpr ALWAYS_INLINE void ClearBit(size_t i) { + m_words[i / FlagsPerWord] &= ~GetBitMask(i % FlagsPerWord); + } + + constexpr ALWAYS_INLINE size_t CountLeadingZero() const { + for (size_t i = 0; i < NumWords; i++) { + if (m_words[i]) { + return FlagsPerWord * i + util::CountLeadingZeros<Storage>(m_words[i]); + } + } + return FlagsPerWord * NumWords; + } + + constexpr ALWAYS_INLINE size_t GetNextSet(size_t n) const { + for (size_t i = (n + 1) / FlagsPerWord; i < NumWords; i++) { + Storage word = m_words[i]; + if (!util::IsAligned(n + 1, FlagsPerWord)) { + word &= GetBitMask(n % FlagsPerWord) - 1; + } + if (word) { + return FlagsPerWord * i + util::CountLeadingZeros<Storage>(word); + } + } + return FlagsPerWord * NumWords; + } + }; + + } + + template<size_t N> + using BitSet8 = impl::BitSet<u8, N>; + + template<size_t N> + using BitSet16 = impl::BitSet<u16, N>; + + template<size_t N> + using BitSet32 = impl::BitSet<u32, N>; + + template<size_t N> + using BitSet64 = impl::BitSet<u64, N>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_bitutil.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_bitutil.hpp new file mode 100644 index 00000000..baf2af07 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_bitutil.hpp @@ -0,0 +1,287 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + namespace impl { + + template<size_t N> + constexpr inline size_t Log2 = Log2<N / 2> + 1; + + template<> + constexpr inline size_t Log2<1> = 0; + + } + + template<std::integral T> + constexpr inline T ReverseBits(T x, int sw_bits = 1, int swar_words = 1) { + /* Check pre-conditions. */ + AMS_ASSERT(0 <= swar_words && swar_words < (BITSIZEOF(T) + 1)); + AMS_ASSERT(BITSIZEOF(T) % swar_words == 0); + AMS_ASSERT(0 <= sw_bits && sw_bits < ((BITSIZEOF(T) / swar_words) + 1)); + AMS_ASSERT((BITSIZEOF(T) / swar_words) % sw_bits == 0); + + using U = typename std::make_unsigned<T>::type; + const int word_size = BITSIZEOF(T) / swar_words; + const int k = word_size - sw_bits; + + U u = std::bit_cast<U, T>(x); + for (int i = 1; i < BITSIZEOF(T); i <<= 1) { + const U mask = static_cast<U>(static_cast<U>(-1) / ((static_cast<U>(1) << i) + 1)); + + if (k & i) { + u = static_cast<U>(((u & mask) << i) | ((u & static_cast<U>(~mask)) >> i)); + } + } + + return std::bit_cast<T, U>(u); + } + + template<std::integral T> + constexpr ALWAYS_INLINE T ReverseBytes(T x, int sw_bytes = 1, int swar_words = 1) { + return ReverseBits(x, sw_bytes * BITSIZEOF(u8), swar_words); + } + + template<typename T = u64, typename ...Args> requires std::integral<T> + constexpr ALWAYS_INLINE T CombineBits(Args... args) { + return (... | (T(1u) << args)); + } + + template<std::integral T> + constexpr ALWAYS_INLINE T ResetLeastSignificantOneBit(T x) { + return x & (x - 1); + } + + template<std::integral T> + constexpr ALWAYS_INLINE T SetLeastSignificantZeroBit(T x) { + return x | (x + 1); + } + + template<std::integral T> + constexpr ALWAYS_INLINE T ResetTrailingOnes(T x) { + return x & (x + 1); + } + + template<std::integral T> + constexpr ALWAYS_INLINE T SetTrailingZeros(T x) { + return x | (x - 1); + } + + template<std::integral T> + constexpr ALWAYS_INLINE T LeastSignificantOneBit(T x) { + return x & ~(x - 1); + } + + template<std::integral T> + constexpr ALWAYS_INLINE T LeastSignificantZeroBit(T x) { + return ~x & (x + 1); + } + + template<std::integral T> + constexpr ALWAYS_INLINE T MaskTrailingZeros(T x) { + return (~x) & (x - 1); + } + + template<std::integral T> + constexpr ALWAYS_INLINE T MaskTrailingOnes(T x) { + return x & ~(x + 1); + } + + template<std::integral T> + constexpr ALWAYS_INLINE T MaskTrailingZerosAndLeastSignificantOneBit(T x) { + return x ^ (x - 1); + } + + template<std::integral T> + constexpr ALWAYS_INLINE T MaskTrailingOnesAndLeastSignificantZeroBit(T x) { + return x ^ (x + 1); + } + + template<std::integral T> + constexpr ALWAYS_INLINE int PopCount(T x) { + using U = typename std::make_unsigned<T>::type; + U u = static_cast<U>(x); + + if (std::is_constant_evaluated()) { + /* https://en.wikipedia.org/wiki/Hamming_weight */ + constexpr U m1 = U(-1) / 0x03; + constexpr U m2 = U(-1) / 0x05; + constexpr U m4 = U(-1) / 0x11; + + u = static_cast<U>(u - ((u >> 1) & m1)); + u = static_cast<U>((u & m2) + ((u >> 2) & m2)); + u = static_cast<U>((u + (u >> 4)) & m4); + + for (size_t i = 0; i < impl::Log2<sizeof(T)>; ++i) { + const size_t shift = (0x1 << i) * BITSIZEOF(u8); + u += u >> shift; + } + + return static_cast<int>(u & 0x7Fu); + } else { + if constexpr (std::is_same<U, unsigned long long>::value) { + return __builtin_popcountll(u); + } else if constexpr (std::is_same<U, unsigned long>::value) { + return __builtin_popcountl(u); + } else { + static_assert(sizeof(U) <= sizeof(unsigned int)); + return __builtin_popcount(static_cast<unsigned int>(u)); + } + } + } + + template<std::integral T> + constexpr ALWAYS_INLINE int CountLeadingZeros(T x) { + if (std::is_constant_evaluated()) { + for (size_t i = 0; i < impl::Log2<BITSIZEOF(T)>; ++i) { + const size_t shift = (0x1 << i); + x |= x >> shift; + } + return PopCount(static_cast<T>(~x)); + } else { + using U = typename std::make_unsigned<T>::type; + + if (const U u = static_cast<U>(x); u != 0) { + if constexpr (std::is_same<U, unsigned long long>::value) { + return __builtin_clzll(u); + } else if constexpr (std::is_same<U, unsigned long>::value) { + return __builtin_clzl(u); + } else if constexpr(std::is_same<U, unsigned int>::value) { + return __builtin_clz(u); + } else { + static_assert(sizeof(U) < sizeof(unsigned int)); + constexpr size_t BitDiff = BITSIZEOF(unsigned int) - BITSIZEOF(U); + return __builtin_clz(static_cast<unsigned int>(u)) - BitDiff; + } + } else { + return BITSIZEOF(U); + } + } + } + + static_assert(CountLeadingZeros(~static_cast<u64>(0)) == 0); + static_assert(CountLeadingZeros(static_cast<u64>(1) << 5) == BITSIZEOF(u64) - 1 - 5); + static_assert(CountLeadingZeros(static_cast<u64>(0)) == BITSIZEOF(u64)); + + template<std::integral T> + constexpr ALWAYS_INLINE int CountTrailingZeros(T x) { + if (std::is_constant_evaluated()) { + auto count = 0; + for (size_t i = 0; i < BITSIZEOF(T) && (x & 1) == 0; ++i) { + x >>= 1; + ++count; + } + return count; + } else { + using U = typename std::make_unsigned<T>::type; + if (const U u = static_cast<U>(x); u != 0) { + if constexpr (std::is_same<U, unsigned long long>::value) { + return __builtin_ctzll(u); + } else if constexpr (std::is_same<U, unsigned long>::value) { + return __builtin_ctzl(u); + } else if constexpr(std::is_same<U, unsigned int>::value) { + return __builtin_ctz(u); + } else { + static_assert(sizeof(U) < sizeof(unsigned int)); + return __builtin_ctz(static_cast<unsigned int>(u)); + } + } else { + return BITSIZEOF(U); + } + } + } + + static_assert(CountTrailingZeros(~static_cast<u64>(0)) == 0); + static_assert(CountTrailingZeros(static_cast<u64>(1) << 5) == 5); + static_assert(CountTrailingZeros(static_cast<u64>(0)) == BITSIZEOF(u64)); + + template<std::integral T> + constexpr ALWAYS_INLINE bool IsPowerOfTwo(T x) { + return x > 0 && ResetLeastSignificantOneBit(x) == 0; + } + + template<std::integral T> + constexpr ALWAYS_INLINE T CeilingPowerOfTwo(T x) { + AMS_ASSERT(x > 0); + return T(1) << (BITSIZEOF(T) - CountLeadingZeros(T(x - 1))); + } + + template<std::integral T> + constexpr ALWAYS_INLINE T FloorPowerOfTwo(T x) { + AMS_ASSERT(x > 0); + return T(1) << (BITSIZEOF(T) - CountLeadingZeros(x) - 1); + } + + template<std::integral T, std::integral U> + constexpr ALWAYS_INLINE T DivideUp(T v, U d) { + using Unsigned = typename std::make_unsigned<U>::type; + using Sum = decltype(T{0} + U{0}); + + #if defined(ATMOSPHERE_IS_STRATOSPHERE) + AMS_ASSERT(v >= 0); + AMS_ASSERT(d > 0); + AMS_ASSERT(static_cast<Sum>(v) <= (std::numeric_limits<Sum>::max() - static_cast<Sum>(d) + static_cast<Sum>(1))); + #endif + + const Unsigned add = static_cast<Unsigned>(d) - 1; + return static_cast<T>((static_cast<Sum>(v) + static_cast<Sum>(add)) / static_cast<Sum>(d)); + } + + template<std::integral T, T N, T D> + constexpr ALWAYS_INLINE T ScaleByConstantFactorUp(const T V) { + /* Multiplying and dividing by large numerator/denominator can cause error to be introduced. */ + /* This algorithm multiples/divides in stages, so as to mitigate this (particularly with large denominator). */ + + /* Justification for the algorithm. */ + /* Calculate: (V * N) / D */ + /* = (Quot_V * D + Rem_V) * (Quot_N * D + Rem_N) / D */ + /* = (D^2 * (Quot_V * Quot_N) + D * (Quot_V * Rem_N + Rem_V * Quot_N) + Rem_V * Rem_N) / D */ + /* = (D * Quot_V * Quot_N) + (Quot_V * Rem_N) + (Rem_V * Quot_N) + ((Rem_V * Rem_N) / D) */ + + /* Calculate quotients/remainders. */ + const T Quot_V = V / D; + const T Rem_V = V % D; + constexpr T Quot_N = N / D; + constexpr T Rem_N = N % D; + + /* Calculate the remainder multiplication, rounding up. */ + const T rem_mult = ((Rem_V * Rem_N) + (D - 1)) / D; + + /* Calculate results. */ + return (D * Quot_N * Quot_V) + (Quot_V * Rem_N) + (Rem_V * Quot_N) + rem_mult; + } + + template<std::integral T> + constexpr ALWAYS_INLINE T RotateLeft(T v, int n) { + using Unsigned = typename std::make_unsigned<T>::type; + static_assert(sizeof(Unsigned) == sizeof(T)); + + return static_cast<T>(std::rotl<Unsigned>(static_cast<Unsigned>(v), n)); + } + + template<std::integral T> + constexpr ALWAYS_INLINE T RotateRight(T v, int n) { + using Unsigned = typename std::make_unsigned<T>::type; + static_assert(sizeof(Unsigned) == sizeof(T)); + + return static_cast<T>(std::rotr<Unsigned>(static_cast<Unsigned>(v), n)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_bounded_map.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_bounded_map.hpp new file mode 100644 index 00000000..96d1a4f9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_bounded_map.hpp @@ -0,0 +1,148 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util/util_typed_storage.hpp> + +namespace ams::util { + + template<class Key, class Value, size_t N> + class BoundedMap { + private: + std::array<util::optional<Key>, N> m_keys; + std::array<TypedStorage<Value>, N> m_values; + private: + ALWAYS_INLINE void FreeEntry(size_t i) { + m_keys[i].reset(); + DestroyAt(m_values[i]); + } + public: + constexpr BoundedMap() : m_keys(), m_values() { /* ... */ } + + Value *Find(const Key &key) { + for (size_t i = 0; i < N; i++) { + if (m_keys[i] && m_keys[i].value() == key) { + return GetPointer(m_values[i]); + } + } + return nullptr; + } + + void Remove(const Key &key) { + for (size_t i = 0; i < N; i++) { + if (m_keys[i] && m_keys[i].value() == key) { + this->FreeEntry(i); + break; + } + } + } + + void RemoveAll() { + for (size_t i = 0; i < N; i++) { + this->FreeEntry(i); + } + } + + bool IsFull() { + for (size_t i = 0; i < N; i++) { + if (!m_keys[i]) { + return false; + } + } + + return true; + } + + bool Insert(const Key &key, Value &&value) { + /* We can't insert if the key is used. */ + if (this->Find(key) != nullptr) { + return false; + } + + /* Find a free value. */ + for (size_t i = 0; i < N; i++) { + if (!m_keys[i]) { + m_keys[i] = key; + ConstructAt(m_values[i], std::forward<Value>(value)); + return true; + } + } + + return false; + } + + bool InsertOrAssign(const Key &key, Value &&value) { + /* Try to find and assign an existing value. */ + for (size_t i = 0; i < N; i++) { + if (m_keys[i] && m_keys[i].value() == key) { + GetReference(m_values[i]) = std::forward<Value>(value); + return true; + } + } + + /* Find a free value. */ + for (size_t i = 0; i < N; i++) { + if (!m_keys[i]) { + m_keys[i] = key; + ConstructAt(m_values[i], std::move(value)); + return true; + } + } + + return false; + } + + template<class... Args> + bool Emplace(const Key &key, Args&&... args) { + /* We can't emplace if the key is used. */ + if (this->Find(key) != nullptr) { + return false; + } + + /* Find a free value. */ + for (size_t i = 0; i < N; i++) { + if (!m_keys[i]) { + m_keys[i] = key; + ConstructAt(m_values[i], std::forward<Args>(args)...); + return true; + } + } + + return false; + } + + template<typename F> + void ForEach(F f) { + for (size_t i = 0; i < N; ++i) { + if (m_keys[i]) { + f(m_keys[i].value(), GetReference(m_values[i])); + } + } + } + + template<typename F> + void RemoveIf(F f) { + for (size_t i = 0; i < N; ++i) { + if (m_keys[i] && f(m_keys[i].value(), GetReference(m_values[i]))) { + this->FreeEntry(i); + } + } + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_character_encoding.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_character_encoding.hpp new file mode 100644 index 00000000..60087b0e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_character_encoding.hpp @@ -0,0 +1,168 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + enum CharacterEncodingResult { + CharacterEncodingResult_Success = 0, + CharacterEncodingResult_InsufficientLength = 1, + CharacterEncodingResult_InvalidFormat = 2, + }; + + namespace impl { + + class CharacterEncodingHelper { + public: + static constexpr int8_t Utf8NBytesInnerTable[0x100 + 1] = { + -1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8, + }; + + static constexpr ALWAYS_INLINE char GetUtf8NBytes(size_t i) { + return static_cast<char>(Utf8NBytesInnerTable[1 + i]); + } + }; + + } + + constexpr inline CharacterEncodingResult ConvertCharacterUtf8ToUtf32(u32 *dst, const char *src) { + /* Check pre-conditions. */ + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(src != nullptr); + + /* Perform the conversion. */ + const auto *p = src; + switch (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[0]))) { + case 1: + *dst = static_cast<u32>(p[0]); + return CharacterEncodingResult_Success; + case 2: + if ((static_cast<u32>(p[0]) & 0x1E) != 0) { + if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0) { + *dst = (static_cast<u32>(p[0] & 0x1F) << 6) | (static_cast<u32>(p[1] & 0x3F) << 0); + return CharacterEncodingResult_Success; + } + } + break; + case 3: + if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 && impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0) { + const u32 c = (static_cast<u32>(p[0] & 0xF) << 12) | (static_cast<u32>(p[1] & 0x3F) << 6) | (static_cast<u32>(p[2] & 0x3F) << 0); + if ((c & 0xF800) != 0 && (c & 0xF800) != 0xD800) { + *dst = c; + return CharacterEncodingResult_Success; + } + } + return CharacterEncodingResult_InvalidFormat; + case 4: + if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 && impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0 && impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[3])) == 0) { + const u32 c = (static_cast<u32>(p[0] & 0x7) << 18) | (static_cast<u32>(p[1] & 0x3F) << 12) | (static_cast<u32>(p[2] & 0x3F) << 6) | (static_cast<u32>(p[3] & 0x3F) << 0); + if (c >= 0x10000 && c < 0x110000) { + *dst = c; + return CharacterEncodingResult_Success; + } + } + return CharacterEncodingResult_InvalidFormat; + default: + break; + } + + /* We failed to convert. */ + return CharacterEncodingResult_InvalidFormat; + } + + constexpr inline CharacterEncodingResult PickOutCharacterFromUtf8String(char *dst, const char **str) { + /* Check pre-conditions. */ + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(str != nullptr); + AMS_ASSERT(*str != nullptr); + + /* Clear the output. */ + dst[0] = 0; + dst[1] = 0; + dst[2] = 0; + dst[3] = 0; + + /* Perform the conversion. */ + const auto *p = *str; + u32 c = static_cast<u32>(*p); + switch (impl::CharacterEncodingHelper::GetUtf8NBytes(c)) { + case 1: + dst[0] = (*str)[0]; + ++(*str); + break; + case 2: + if ((p[0] & 0x1E) != 0) { + if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0) { + c = (static_cast<u32>(p[0] & 0x1F) << 6) | (static_cast<u32>(p[1] & 0x3F) << 0); + dst[0] = (*str)[0]; + dst[1] = (*str)[1]; + (*str) += 2; + break; + } + } + return CharacterEncodingResult_InvalidFormat; + case 3: + if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 && impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0) { + c = (static_cast<u32>(p[0] & 0xF) << 12) | (static_cast<u32>(p[1] & 0x3F) << 6) | (static_cast<u32>(p[2] & 0x3F) << 0); + if ((c & 0xF800) != 0 && (c & 0xF800) != 0xD800) { + dst[0] = (*str)[0]; + dst[1] = (*str)[1]; + dst[2] = (*str)[2]; + (*str) += 3; + break; + } + } + return CharacterEncodingResult_InvalidFormat; + case 4: + if (impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[1])) == 0 && impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[2])) == 0 && impl::CharacterEncodingHelper::GetUtf8NBytes(static_cast<unsigned char>(p[3])) == 0) { + c = (static_cast<u32>(p[0] & 0x7) << 18) | (static_cast<u32>(p[1] & 0x3F) << 12) | (static_cast<u32>(p[2] & 0x3F) << 6) | (static_cast<u32>(p[3] & 0x3F) << 0); + if (c >= 0x10000 && c < 0x110000) { + dst[0] = (*str)[0]; + dst[1] = (*str)[1]; + dst[2] = (*str)[2]; + dst[3] = (*str)[3]; + (*str) += 4; + break; + } + } + return CharacterEncodingResult_InvalidFormat; + default: + return CharacterEncodingResult_InvalidFormat; + } + + return CharacterEncodingResult_Success; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_endian.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_endian.hpp new file mode 100644 index 00000000..0bb84ebe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_endian.hpp @@ -0,0 +1,155 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + consteval bool IsLittleEndian() { + return std::endian::native == std::endian::little; + } + + consteval bool IsBigEndian() { + return std::endian::native == std::endian::big; + } + + static_assert(IsLittleEndian() ^ IsBigEndian()); + + template<std::unsigned_integral U> + constexpr ALWAYS_INLINE U SwapEndian(const U u) { + static_assert(BITSIZEOF(u8) == 8); + + if constexpr (sizeof(U) * BITSIZEOF(u8) == 64) { + static_assert(__builtin_bswap64(UINT64_C(0x0123456789ABCDEF)) == UINT64_C(0xEFCDAB8967452301)); + return __builtin_bswap64(u); + } else if constexpr (sizeof(U) * BITSIZEOF(u8) == 32) { + static_assert(__builtin_bswap32(0x01234567u) == 0x67452301u); + return __builtin_bswap32(u); + } else if constexpr (sizeof(U) * BITSIZEOF(u8) == 16) { + static_assert(__builtin_bswap16(0x0123u) == 0x2301u); + return __builtin_bswap16(u); + } else if constexpr (sizeof(U) * BITSIZEOF(u8) == 8) { + return u; + } else { + static_assert(!std::is_same<U, U>::value); + } + } + + constexpr ALWAYS_INLINE u64 SwapEndian48(const u64 u) { + using U = u64; + static_assert(BITSIZEOF(u8) == 8); + constexpr U ByteMask = 0xFFu; + AMS_ASSERT((u & UINT64_C(0xFFFF000000000000)) == 0); + return ((u & (ByteMask << 40)) >> 40) | + ((u & (ByteMask << 32)) >> 24) | + ((u & (ByteMask << 24)) >> 8) | + ((u & (ByteMask << 16)) << 8) | + ((u & (ByteMask << 8)) << 24) | + ((u & (ByteMask << 0)) << 40); + } + + template<std::integral T> + constexpr ALWAYS_INLINE void SwapEndian(T *ptr) { + using U = typename std::make_unsigned<T>::type; + + *ptr = static_cast<T>(SwapEndian<U>(static_cast<U>(*ptr))); + } + + template<std::integral T> + constexpr ALWAYS_INLINE T ConvertToBigEndian(const T val) { + using U = typename std::make_unsigned<T>::type; + + if constexpr (IsBigEndian()) { + return static_cast<T>(static_cast<U>(val)); + } else { + static_assert(IsLittleEndian()); + return static_cast<T>(SwapEndian<U>(static_cast<U>(val))); + } + } + + template<std::integral T> + constexpr ALWAYS_INLINE T ConvertToLittleEndian(const T val) { + using U = typename std::make_unsigned<T>::type; + + if constexpr (IsBigEndian()) { + return static_cast<T>(SwapEndian<U>(static_cast<U>(val))); + } else { + static_assert(IsLittleEndian()); + return static_cast<T>(static_cast<U>(val)); + } + } + + template<std::integral T> + constexpr ALWAYS_INLINE T ConvertFromBigEndian(const T val) { + return ConvertToBigEndian(val); + } + + template<std::integral T> + constexpr ALWAYS_INLINE T ConvertFromLittleEndian(const T val) { + return ConvertToLittleEndian(val); + } + + template<std::integral T> + constexpr ALWAYS_INLINE T ConvertToBigEndian48(const T val) { + using U = typename std::make_unsigned<T>::type; + static_assert(sizeof(T) == sizeof(u64)); + + if constexpr (IsBigEndian()) { + AMS_ASSERT((static_cast<U>(val) & UINT64_C(0xFFFF000000000000)) == 0); + return static_cast<T>(static_cast<U>(val)); + } else { + static_assert(IsLittleEndian()); + return static_cast<T>(SwapEndian48(static_cast<U>(val))); + } + } + + template<std::integral T> + constexpr ALWAYS_INLINE T ConvertToLittleEndian48(const T val) { + using U = typename std::make_unsigned<T>::type; + static_assert(sizeof(T) == sizeof(u64)); + + if constexpr (IsBigEndian()) { + return static_cast<T>(SwapEndian48(static_cast<U>(val))); + } else { + static_assert(IsLittleEndian()); + AMS_ASSERT((static_cast<U>(val) & UINT64_C(0xFFFF000000000000)) == 0); + return static_cast<T>(static_cast<U>(val)); + } + } + + template<std::integral T> + constexpr ALWAYS_INLINE T LoadBigEndian(const T *ptr) { + return ConvertToBigEndian<T>(*ptr); + } + + template<std::integral T> + constexpr ALWAYS_INLINE T LoadLittleEndian(const T *ptr) { + return ConvertToLittleEndian<T>(*ptr); + } + + template<std::integral T> + constexpr ALWAYS_INLINE void StoreBigEndian(T *ptr, T val) { + *static_cast<volatile T *>(ptr) = ConvertToBigEndian<T>(val); + } + + template<std::integral T> + constexpr ALWAYS_INLINE void StoreLittleEndian(T *ptr, T val) { + *static_cast<volatile T *>(ptr) = ConvertToLittleEndian<T>(val); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_enum.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_enum.hpp new file mode 100644 index 00000000..8e05ee45 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_enum.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + template<typename Enum> requires std::is_enum<Enum>::value + constexpr ALWAYS_INLINE typename std::underlying_type<Enum>::type ToUnderlying(Enum e) { + return static_cast<typename std::underlying_type<Enum>::type>(e); + } + + template<typename Enum> requires std::is_enum<Enum>::value + constexpr ALWAYS_INLINE Enum FromUnderlying(typename std::underlying_type<Enum>::type v) { + return static_cast<Enum>(v); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_exchange.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_exchange.hpp new file mode 100644 index 00000000..86d01ed7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_exchange.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + template<typename T, typename U> requires (std::is_assignable<T&, U&&>::value && std::is_copy_constructible<T>::value && std::is_move_constructible<T>::value) + constexpr inline T Exchange(T *ptr, U value) { + AMS_ASSERT(ptr != nullptr); + auto ret = std::move(*ptr); + *ptr = std::move(value); + return ret; + } + + template<typename T> + constexpr inline T *Exchange(T **ptr, std::nullptr_t) { + AMS_ASSERT(ptr != nullptr); + auto ret(*ptr); + *ptr = nullptr; + return ret; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_fixed_map.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_fixed_map.hpp new file mode 100644 index 00000000..f28cfa3d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_fixed_map.hpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util/util_fixed_tree.hpp> + +namespace ams::util { + + template<typename Key, typename Value, typename Compare = std::less<Key>, size_t BufferAlignment = 8> + class FixedMap { + private: + using KeyValuePair = std::pair<Key const, Value>; + + struct LessTypeForMap { + constexpr ALWAYS_INLINE bool operator()(const KeyValuePair &lhs, const KeyValuePair &rhs) const { + return Compare{}(lhs.first, rhs.first); + } + }; + + using TreeType = ::ams::util::FixedTree<KeyValuePair, LessTypeForMap, KeyValuePair, BufferAlignment>; + + using iterator = typename TreeType::iterator; + using const_iterator = typename TreeType::const_iterator; + public: + static constexpr size_t GetRequiredMemorySize(size_t num_elements) { + return TreeType::GetRequiredMemorySize(num_elements); + } + private: + TreeType m_tree; + public: + FixedMap() : m_tree() { /* ... */ } + + void Initialize(size_t num_elements, void *buffer, size_t buffer_size) { + return m_tree.Initialize(num_elements, buffer, buffer_size); + } + + ALWAYS_INLINE iterator begin() { return m_tree.begin(); } + ALWAYS_INLINE const_iterator begin() const { return m_tree.begin(); } + + ALWAYS_INLINE iterator end() { return m_tree.end(); } + ALWAYS_INLINE const_iterator end() const { return m_tree.end(); } + + ALWAYS_INLINE bool erase(const Key &key) { const KeyValuePair pair(key, Value{}); return m_tree.erase(pair); } + + ALWAYS_INLINE iterator find(const Key &key) { const KeyValuePair pair(key, Value{}); return m_tree.find(pair); } + ALWAYS_INLINE const_iterator find(const Key &key) const { const KeyValuePair pair(key, Value{}); return m_tree.find(pair); } + + ALWAYS_INLINE std::pair<iterator, bool> insert(const KeyValuePair &pair) { return m_tree.insert(pair); } + + ALWAYS_INLINE size_t size() const { return m_tree.size(); } + + ALWAYS_INLINE void clear() { return m_tree.clear(); } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_fixed_set.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_fixed_set.hpp new file mode 100644 index 00000000..5504f55e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_fixed_set.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util/util_fixed_tree.hpp> + +namespace ams::util { + + template<typename Element, typename Compare = std::less<Element>, size_t BufferAlignment = 8> + class FixedSet : public ::ams::util::FixedTree<Element, Compare, const Element, BufferAlignment> { + /* ... */ + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_fixed_tree.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_fixed_tree.hpp new file mode 100644 index 00000000..ead134c2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_fixed_tree.hpp @@ -0,0 +1,998 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util/impl/util_available_index_finder.hpp> +#include <vapours/util/util_alignment.hpp> + +namespace ams::util { + + template<typename Member, typename Compare, typename IteratorMember, size_t BufferAlignment = 8> requires std::convertible_to<Member &, IteratorMember &> + class FixedTree { + private: + class IteratorBase; + friend class IteratorBase; + private: + enum class Color : u8 { + Red = 0, + Black = 1, + }; + + static constexpr inline int Index_Nil = -1; + static constexpr inline int Index_Leaf = -2; + static constexpr inline int Index_BeforeBegin = -3; + static constexpr inline int Index_AfterEnd = -4; + + static constexpr inline size_t max_size = 0x40000000; + + struct Header { + /* "Nintendo Red-Black tree" */ + static constexpr u32 Signature = util::ReverseFourCC<'N','N','R','B'>::Code; + + u32 header_size; + u32 header_signature; + u32 _08; + s32 max_elements; + s32 cur_elements; + s32 root_index; + s32 left_most_index; + s32 right_most_index; + s32 index_signature; + u32 buffer_size; + u32 node_size; + u32 element_size; + u32 _30; + u32 _34; + u32 _38; + u32 _3C; + u32 _40; + u32 _44; + u32 _48; + u32 _4C; + + void InitializeHeader(u32 _08, s32 max_e, s32 cur_e, u32 ind_sig, u32 buf_sz, u32 node_sz, u32 e_sz, u32 _30, u32 _34, u32 _38, u32 _3C, u32 _40, u32 _44) { + this->header_size = sizeof(Header); + this->header_signature = Signature; + this->_08 = _08; + this->max_elements = max_e; + this->cur_elements = cur_e; + this->root_index = Index_Nil; + this->left_most_index = Index_Nil; + this->right_most_index = Index_Nil; + this->index_signature = ind_sig; + this->buffer_size = buf_sz; + this->node_size = node_sz; + this->element_size = e_sz; + this->_30 = _30; + this->_34 = _34; + this->_38 = _38; + this->_3C = _3C; + this->_40 = _40; + this->_44 = _44; + this->_48 = 0; + this->_4C = 0; + } + }; + static_assert(sizeof(Header) == 0x50); + + struct IndexPair { + int first; + int last; + }; + + struct Node { + Member m_data; + int m_parent; + int m_right; + int m_left; + Color m_color; + + void SetLeft(int l, Node *n, int p) { + m_left = l; + n->m_parent = p; + } + + void SetRight(int r, Node *n, int p) { + m_right = r; + n->m_parent = p; + } + }; + + class Iterator; + class ConstIterator; + + class IteratorBase { + private: + friend class ConstIterator; + private: + const FixedTree *m_tree; + int m_index; + protected: + constexpr ALWAYS_INLINE IteratorBase(const FixedTree *tree, int index) : m_tree(tree), m_index(index) { /* ... */ } + + constexpr bool IsEqualImpl(const IteratorBase &rhs) const { + /* Validate pre-conditions. */ + AMS_ASSERT(m_tree); + + /* Check for tree equality. */ + if (m_tree != rhs.m_tree) { + return false; + } + + /* Check for nil. */ + if (m_tree->IsNil(m_index) && m_tree->IsNil(rhs.m_index)) { + return true; + } + + /* Check for index equality. */ + return m_index == rhs.m_index; + } + + constexpr IteratorMember &DereferenceImpl() const { + /* Validate pre-conditions. */ + AMS_ASSERT(m_tree); + + if (!m_tree->IsNil(m_index)) { + return m_tree->m_nodes[m_index].m_data; + } else { + AMS_ASSERT(false); + return m_tree->GetNode(std::numeric_limits<int>::max())->m_data; + } + } + + constexpr ALWAYS_INLINE IteratorBase &IncrementImpl() { + /* Validate pre-conditions. */ + AMS_ASSERT(m_tree); + + this->OperateIndex(true); + return *this; + } + + constexpr ALWAYS_INLINE IteratorBase &DecrementImpl() { + /* Validate pre-conditions. */ + AMS_ASSERT(m_tree); + + this->OperateIndex(false); + return *this; + } + + constexpr void OperateIndex(bool increment) { + if (increment) { + /* We're incrementing. */ + if (m_index == Index_BeforeBegin) { + m_index = 0; + } else { + m_index = m_tree->UncheckedPP(m_index); + if (m_tree->IsNil(m_index)) { + m_index = Index_AfterEnd; + } + } + } else { + /* We're decrementing. */ + if (m_index == Index_AfterEnd) { + m_index = static_cast<int>(m_tree->size()) - 1; + } else { + m_index = m_tree->UncheckedMM(m_index); + if (m_tree->IsNil(m_index)) { + m_index = Index_BeforeBegin; + } + } + } + } + }; + + class Iterator : public IteratorBase { + public: + constexpr ALWAYS_INLINE Iterator(const FixedTree &tree) : IteratorBase(std::addressof(tree), tree.size() ? tree.GetLMost() : Index_Leaf) { /* ... */ } + constexpr ALWAYS_INLINE Iterator(const FixedTree &tree, int index) : IteratorBase(std::addressof(tree), index) { /* ... */ } + + constexpr ALWAYS_INLINE Iterator(const Iterator &rhs) = default; + + constexpr ALWAYS_INLINE bool operator==(const Iterator &rhs) const { + return this->IsEqualImpl(rhs); + } + + constexpr ALWAYS_INLINE bool operator!=(const Iterator &rhs) const { + return !(*this == rhs); + } + + constexpr ALWAYS_INLINE IteratorMember &operator*() const { + return static_cast<IteratorMember &>(this->DereferenceImpl()); + } + + constexpr ALWAYS_INLINE IteratorMember *operator->() const { + return std::addressof(this->operator *()); + } + + constexpr ALWAYS_INLINE Iterator &operator++() { + return static_cast<Iterator &>(this->IncrementImpl()); + } + + constexpr ALWAYS_INLINE Iterator &operator--() { + return static_cast<Iterator &>(this->DecrementImpl()); + } + }; + + class ConstIterator : public IteratorBase { + public: + constexpr ALWAYS_INLINE ConstIterator(const FixedTree &tree) : IteratorBase(std::addressof(tree), tree.size() ? tree.GetLMost() : Index_Leaf) { /* ... */ } + constexpr ALWAYS_INLINE ConstIterator(const FixedTree &tree, int index) : IteratorBase(std::addressof(tree), index) { /* ... */ } + + constexpr ALWAYS_INLINE ConstIterator(const ConstIterator &rhs) = default; + constexpr ALWAYS_INLINE ConstIterator(const Iterator &rhs) : IteratorBase(rhs.m_tree, rhs.m_index) { /* ... */ } + + constexpr ALWAYS_INLINE bool operator==(const ConstIterator &rhs) const { + return this->IsEqualImpl(rhs); + } + + constexpr ALWAYS_INLINE bool operator!=(const ConstIterator &rhs) const { + return !(*this == rhs); + } + + constexpr ALWAYS_INLINE const IteratorMember &operator*() const { + return static_cast<const IteratorMember &>(this->DereferenceImpl()); + } + + constexpr ALWAYS_INLINE const IteratorMember *operator->() const { + return std::addressof(this->operator *()); + } + + constexpr ALWAYS_INLINE ConstIterator &operator++() { + return static_cast<ConstIterator &>(this->IncrementImpl()); + } + + constexpr ALWAYS_INLINE ConstIterator &operator--() { + return static_cast<ConstIterator &>(this->DecrementImpl()); + } + }; + public: + using iterator = Iterator; + using const_iterator = ConstIterator; + private: + impl::AvailableIndexFinder m_index_finder; + Node m_dummy_leaf; + Node *m_p_dummy_leaf; + u8 *m_buffer; + Header *m_header; + Node *m_nodes; + iterator m_end_iterator; + public: + FixedTree() : m_end_iterator(*this, Index_Nil) { + this->SetDummyMemory(); + } + protected: + void InitializeImpl(int num_elements, void *buffer, size_t buffer_size) { + /* Check pre-conditions. */ + AMS_ASSERT(num_elements > 0); + AMS_ASSERT(static_cast<size_t>(num_elements) <= max_size); + AMS_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(buffer), BufferAlignment)); + AMS_ASSERT(buffer_size == GetRequiredMemorySize(num_elements)); + + /* Set buffer. */ + m_buffer = static_cast<u8 *>(buffer); + m_header = reinterpret_cast<Header *>(m_buffer); + + /* Setup memory. */ + this->InitializeMemory(num_elements, buffer_size, impl::AvailableIndexFinder::GetSignature()); + + /* Check that buffer was set up correctly. */ + AMS_ASSERT(static_cast<u32>(buffer_size) == m_header->buffer_size); + + /* Setup dummy leaf. */ + this->SetDummyMemory(); + } + public: + static constexpr size_t SizeOfNodes(size_t num_elements) { + return util::AlignUp(sizeof(Node) * num_elements, BufferAlignment); + } + + static constexpr size_t SizeOfIndex(size_t num_elements) { + return impl::AvailableIndexFinder::GetRequiredMemorySize(num_elements); + } + + static constexpr size_t GetRequiredMemorySize(size_t num_elements) { + return sizeof(Header) + SizeOfNodes(num_elements) + SizeOfIndex(num_elements); + } + private: + void SetDummyMemory() { + m_dummy_leaf.m_color = Color::Black; + m_dummy_leaf.m_parent = Index_Nil; + m_dummy_leaf.m_left = Index_Leaf; + m_dummy_leaf.m_right = Index_Leaf; + m_p_dummy_leaf = std::addressof(m_dummy_leaf); + } + + void InitializeMemory(int num_elements, u32 buffer_size, u32 signature) { + /* Initialize the header. */ + m_header->InitializeHeader(1, num_elements, 0, signature, buffer_size, sizeof(Node), sizeof(Member), 4, 4, 4, 4, 4, BufferAlignment); + + /* Setup index finder. */ + m_index_finder.Initialize(std::addressof(m_header->cur_elements), std::addressof(m_header->max_elements), m_buffer + sizeof(*m_header) + SizeOfNodes(num_elements)); + + /* Set nodes array. */ + m_nodes = reinterpret_cast<Node *>(m_buffer + sizeof(*m_header)); + } + + Node *GetNode(int index) const { + if (index >= 0) { + return m_nodes + index; + } else { + return m_p_dummy_leaf; + } + } + + constexpr ALWAYS_INLINE bool IsNil(int index) const { + return index < 0; + } + + constexpr ALWAYS_INLINE bool IsLeaf(int index) const { + return index == Index_Leaf; + } + + int GetRoot() const { return m_header->root_index; } + void SetRoot(int index) { + if (index == Index_Leaf) { + index = Index_Nil; + } + + m_header->root_index = index; + } + + int GetLMost() const { return m_header->left_most_index; } + void SetLMost(int index) { m_header->left_most_index = index; } + + int GetRMost() const { return m_header->right_most_index; } + void SetRMost(int index) { m_header->right_most_index = index; } + + int GetParent(int index) const { + return this->GetNode(index)->m_parent; + } + + int AcquireIndex() { return m_index_finder.AcquireIndex(); } + void ReleaseIndex(int index) { return m_index_finder.ReleaseIndex(index); } + + int EraseByIndex(int target_index) { + /* Setup tracking variables. */ + const auto next_index = this->UncheckedPP(target_index); + auto *target_node = this->GetNode(target_index); + + auto a_index = Index_Leaf; + auto *a_node = this->GetNode(a_index); + auto b_index = Index_Leaf; + auto *b_node = this->GetNode(b_index); + auto cur_index = target_index; + auto *cur_node = this->GetNode(cur_index); + + if (cur_node->m_left == Index_Leaf) { + a_index = cur_node->m_right; + a_node = this->GetNode(a_index); + + m_p_dummy_leaf->m_parent = cur_index; + } else { + if (cur_node->m_right == Index_Leaf) { + a_index = cur_node->m_left; + } else { + cur_index = next_index; + cur_node = this->GetNode(cur_index); + a_index = cur_node->m_right; + } + a_node = this->GetNode(a_index); + + m_p_dummy_leaf->m_parent = cur_index; + } + + /* Ensure the a node is updated (redundant) */ + a_node = this->GetNode(a_index); + + /* Update relevant metrics/links. */ + if (cur_index == target_index) { + /* No left, but has right. */ + b_index = target_node->m_parent; + b_node = this->GetNode(b_index); + + if (a_index != Index_Leaf) { + a_node->m_parent = b_index; + } + + if (this->GetRoot() == target_index) { + this->SetRoot(a_index); + } else if (b_node->m_left == target_index) { + b_node->m_left = a_index; + } else { + b_node->m_right = a_index; + } + + if (this->GetLMost() == target_index) { + this->SetLMost((a_index != Index_Leaf) ? this->FindMinInSubtree(a_index) : b_index); + } + + if (this->GetRMost() == target_index) { + this->SetRMost((a_index != Index_Leaf) ? this->FindMaxInSubtree(a_index) : b_index); + } + } else { + /* Has left or doesn't have right. */ + + /* Fix left links. */ + this->GetNode(target_node->m_left)->m_parent = cur_index; + cur_node->m_left = target_node->m_left; + + if (cur_index == target_node->m_right) { + b_index = cur_index; + b_node = this->GetNode(b_index); + } else { + b_index = cur_node->m_parent; + b_node = this->GetNode(b_index); + + if (!this->IsNil(a_index)) { + a_node->m_parent = b_index; + } + + b_node->m_left = a_index; + cur_node->m_right = target_node->m_right; + + this->GetNode(target_node->m_right)->m_parent = cur_index; + } + + if (this->GetRoot() == target_index) { + this->SetRoot(cur_index); + } else { + if (this->GetNode(target_node->m_parent)->m_left == target_index) { + this->GetNode(target_node->m_parent)->m_left = cur_index; + } else { + this->GetNode(target_node->m_parent)->m_right = cur_index; + } + } + + cur_node->m_parent = target_node->m_parent; + std::swap(cur_node->m_color, target_node->m_color); + } + + /* Ensure the tree remains balanced. */ + if (target_node->m_color == Color::Black) { + while (true) { + if (a_index == this->GetRoot() || a_node->m_color != Color::Black) { + break; + } + + if (a_index == b_node->m_left) { + cur_index = b_node->m_right; + cur_node = this->GetNode(cur_index); + + if (cur_node->m_color == Color::Red) { + cur_node->m_color = Color::Black; + b_node->m_color = Color::Red; + AMS_ASSERT(m_p_dummy_leaf->m_color == Color::Black); + + this->RotateLeft(b_index); + + cur_index = b_node->m_right; + cur_node = this->GetNode(cur_index); + } + + if (this->IsNil(cur_index)) { + a_index = b_index; + a_node = b_node; + } else { + if (this->GetNode(cur_node->m_left)->m_color != Color::Black || this->GetNode(cur_node->m_right)->m_color != Color::Black) { + if (this->GetNode(cur_node->m_right)->m_color == Color::Black) { + this->GetNode(cur_node->m_left)->m_color = Color::Black; + cur_node->m_color = Color::Red; + AMS_ASSERT(m_p_dummy_leaf->m_color == Color::Black); + + this->RotateRight(cur_index); + + cur_index = b_node->m_right; + cur_node = this->GetNode(cur_index); + } + + cur_node->m_color = b_node->m_color; + b_node->m_color = Color::Black; + + this->GetNode(cur_node->m_right)->m_color = Color::Black; + + this->RotateLeft(b_index); + + break; + } + + cur_node->m_color = Color::Red; + AMS_ASSERT(m_p_dummy_leaf->m_color == Color::Black); + + a_index = b_index; + a_node = b_node; + } + } else { + cur_index = b_node->m_left; + cur_node = this->GetNode(cur_index); + + if (cur_node->m_color == Color::Red) { + cur_node->m_color = Color::Black; + b_node->m_color = Color::Red; + AMS_ASSERT(m_p_dummy_leaf->m_color == Color::Black); + + this->RotateRight(b_index); + + cur_index = b_node->m_left; + cur_node = this->GetNode(cur_index); + } + + if (this->IsNil(cur_index)) { + a_index = b_index; + a_node = b_node; + } else { + if (this->GetNode(cur_node->m_right)->m_color != Color::Black || this->GetNode(cur_node->m_left)->m_color != Color::Black) { + if (this->GetNode(cur_node->m_left)->m_color == Color::Black) { + this->GetNode(cur_node->m_right)->m_color = Color::Black; + cur_node->m_color = Color::Red; + AMS_ASSERT(m_p_dummy_leaf->m_color == Color::Black); + + this->RotateLeft(cur_index); + + cur_index = b_node->m_left; + cur_node = this->GetNode(cur_index); + } + + cur_node->m_color = b_node->m_color; + b_node->m_color = Color::Black; + + this->GetNode(cur_node->m_left)->m_color = Color::Black; + + this->RotateRight(b_index); + + break; + } + + cur_node->m_color = Color::Red; + AMS_ASSERT(m_p_dummy_leaf->m_color == Color::Black); + + a_index = b_index; + a_node = b_node; + } + } + + b_index = a_node->m_parent; + b_node = this->GetNode(b_index); + } + + a_node->m_color = Color::Black; + } + + /* Release the index. */ + this->ReleaseIndex(target_index); + return target_index; + } + + int FindIndex(const Member &elem) const { + return this->FindIndexSub(this->GetRoot(), elem); + } + + int FindIndexSub(int index, const Member &elem) const { + if (index != Index_Nil) { + auto *node = this->GetNode(index); + if (Compare{}(elem, node->m_data)) { + if (!this->IsLeaf(node->m_left)) { + return this->FindIndexSub(node->m_left, elem); + } + } else { + if (!Compare{}(node->m_data, elem)) { + return index; + } + + if (!this->IsLeaf(node->m_right)) { + return this->FindIndexSub(node->m_right, elem); + } + } + } + + return Index_Nil; + } + + int FindMaxInSubtree(int index) const { + int max = index; + for (auto *node = this->GetNode(index); !this->IsNil(node->m_right); node = this->GetNode(node->m_right)) { + max = node->m_right; + } + return max; + } + + int FindMinInSubtree(int index) const { + int min = index; + for (auto *node = this->GetNode(index); !this->IsNil(node->m_left); node = this->GetNode(node->m_left)) { + min = node->m_left; + } + return min; + } + + int InsertAt(bool before, int parent, const Member &elem) { + /* Get an index for the new element. */ + const auto index = this->AcquireIndex(); + + /* Create the node. */ + auto *node = this->GetNode(index); + node->m_color = Color::Red; + node->m_parent = parent; + node->m_right = Index_Leaf; + node->m_left = Index_Leaf; + std::memcpy(reinterpret_cast<u8 *>(std::addressof(node->m_data)), reinterpret_cast<const u8 *>(std::addressof(elem)), sizeof(node->m_data)); + + /* Fix up the parent node. */ + auto *parent_node = this->GetNode(parent); + if (before) { + parent_node->m_left = index; + if (parent == this->GetLMost()) { + this->SetLMost(index); + } + } else { + parent_node->m_right = index; + if (parent == this->GetRMost()) { + this->SetRMost(index); + } + } + + /* Ensure the tree is balanced. */ + int cur_index = index; + while (true) { + auto *cur_node = this->GetNode(cur_index); + if (this->GetNode(cur_node->m_parent)->m_color != Color::Red) { + break; + } + + auto *p_node = this->GetNode(cur_node->m_parent); + auto *g_node = this->GetNode(p_node->m_parent); + if (cur_node->m_parent == g_node->m_left) { + if (auto *gr_node = this->GetNode(g_node->m_right); gr_node->m_color == Color::Red) { + p_node->m_color = Color::Black; + gr_node->m_color = Color::Black; + g_node->m_color = Color::Red; + AMS_ASSERT(m_p_dummy_leaf->m_color != Color::Red); + + cur_index = p_node->m_parent; + continue; + } + + if (cur_index == p_node->m_right) { + cur_index = cur_node->m_parent; + cur_node = this->GetNode(cur_index); + this->RotateLeft(cur_index); + } + + p_node = this->GetNode(cur_node->m_parent); + p_node->m_color = Color::Black; + + g_node = this->GetNode(p_node->m_parent); + g_node->m_color = Color::Red; + + AMS_ASSERT(m_p_dummy_leaf->m_color != Color::Red); + + this->RotateRight(p_node->m_parent); + } else { + if (auto *gl_node = this->GetNode(g_node->m_left); gl_node->m_color == Color::Red) { + p_node->m_color = Color::Black; + gl_node->m_color = Color::Black; + g_node->m_color = Color::Red; + AMS_ASSERT(m_p_dummy_leaf->m_color != Color::Red); + + cur_index = p_node->m_parent; + continue; + } + + if (cur_index == p_node->m_left) { + cur_index = cur_node->m_parent; + cur_node = this->GetNode(cur_index); + this->RotateRight(cur_index); + } + + p_node = this->GetNode(cur_node->m_parent); + p_node->m_color = Color::Black; + + g_node = this->GetNode(p_node->m_parent); + g_node->m_color = Color::Red; + + AMS_ASSERT(m_p_dummy_leaf->m_color != Color::Red); + + this->RotateLeft(p_node->m_parent); + } + } + + /* Set root color. */ + this->GetNode(this->GetRoot())->m_color = Color::Black; + + return index; + } + + int InsertNoHint(bool before, const Member &elem) { + int cur_index = this->GetRoot(); + int prev_index = Index_Nil; + bool less = true; + while (cur_index != Index_Nil && cur_index != Index_Leaf) { + auto *node = this->GetNode(cur_index); + prev_index = cur_index; + + if (before) { + less = Compare{}(node->m_data, elem); + } else { + less = Compare{}(elem, node->m_data); + } + + if (less) { + cur_index = node->m_left; + } else { + cur_index = node->m_right; + } + } + + if (cur_index == Index_Nil) { + /* Create a new node. */ + const auto index = this->AcquireIndex(); + auto *node = this->GetNode(index); + node->m_color = Color::Black; + node->m_parent = Index_Nil; + node->m_right = Index_Leaf; + node->m_left = Index_Leaf; + std::memcpy(reinterpret_cast<u8 *>(std::addressof(node->m_data)), reinterpret_cast<const u8 *>(std::addressof(elem)), sizeof(node->m_data)); + + this->SetRoot(index); + this->SetLMost(index); + this->SetRMost(index); + + return index; + } else { + auto *compare_node = this->GetNode(prev_index); + if (less) { + if (prev_index == this->GetLMost()) { + return this->InsertAt(less, prev_index, elem); + } else { + compare_node = this->GetNode(this->UncheckedMM(prev_index)); + } + } + + if (Compare{}(compare_node->m_data, elem)) { + return this->InsertAt(less, prev_index, elem); + } else { + return Index_Nil; + } + } + + } + + void RotateLeft(int index) { + /* Determine indices. */ + const auto p_index = this->GetParent(index); + const auto r_index = this->GetNode(index)->m_right; + const auto l_index = this->GetNode(index)->m_left; + const auto rl_index = this->GetNode(r_index)->m_left; + const auto rr_index = this->GetNode(r_index)->m_right; + + /* Get nodes. */ + auto *node = this->GetNode(index); + auto *p_node = this->GetNode(p_index); + auto *r_node = this->GetNode(r_index); + auto *l_node = this->GetNode(l_index); + auto *rl_node = this->GetNode(rl_index); + auto *rr_node = this->GetNode(rr_index); + + /* Perform the rotation. */ + if (p_index == Index_Nil) { + r_node->m_parent = Index_Nil; + m_header->root_index = r_index; + } else if (p_node->m_left == index) { + p_node->SetLeft(r_index, r_node, p_index); + } else { + p_node->SetRight(r_index, r_node, p_index); + } + r_node->SetLeft(index, node, r_index); + r_node->SetRight(rr_index, rr_node, r_index); + node->SetLeft(l_index, l_node, index); + node->SetRight(rl_index, rl_node, index); + } + + void RotateRight(int index) { + /* Determine indices. */ + const auto p_index = this->GetParent(index); + const auto l_index = this->GetNode(index)->m_left; + const auto ll_index = this->GetNode(l_index)->m_left; + const auto lr_index = this->GetNode(l_index)->m_right; + const auto r_index = this->GetNode(index)->m_right; + + /* Get nodes. */ + auto *node = this->GetNode(index); + auto *p_node = this->GetNode(p_index); + auto *l_node = this->GetNode(l_index); + auto *ll_node = this->GetNode(ll_index); + auto *lr_node = this->GetNode(lr_index); + auto *r_node = this->GetNode(r_index); + + /* Perform the rotation. */ + if (p_index == Index_Nil) { + l_node->m_parent = Index_Nil; + m_header->root_index = l_index; + } else if (p_node->m_left == index) { + p_node->SetLeft(l_index, l_node, p_index); + } else { + p_node->SetRight(l_index, l_node, p_index); + } + l_node->SetLeft(ll_index, ll_node, l_index); + l_node->SetRight(index, node, l_index); + node->SetLeft(lr_index, lr_node, index); + node->SetRight(r_index, r_node, index); + } + + int UncheckedMM(int index) const { + auto *node = this->GetNode(index); + if (this->IsNil(index)) { + index = this->GetRMost(); + node = this->GetNode(index); + } else if (this->IsNil(node->m_left)) { + int parent = node->m_parent; + Node *p; + + for (p = this->GetNode(parent); !this->IsNil(parent) && index == p->m_left; p = this->GetNode(parent)) { + index = parent; + node = p; + parent = p->m_parent; + } + + if (!this->IsNil(index)) { + index = parent; + node = p; + } + } else { + index = this->FindMaxInSubtree(node->m_left); + node = this->GetNode(index); + } + + if (this->IsNil(index)) { + return Index_Leaf; + } else { + return index; + } + } + + int UncheckedPP(int index) const { + auto *node = this->GetNode(index); + + if (!this->IsNil(index)) { + if (this->IsNil(node->m_right)) { + int parent = node->m_parent; + Node *p; + + for (p = this->GetNode(parent); !this->IsNil(parent) && index == p->m_right; p = this->GetNode(parent)) { + index = parent; + node = p; + parent = p->m_parent; + } + + index = parent; + node = p; + } else { + index = this->FindMinInSubtree(node->m_right); + node = this->GetNode(index); + } + } + + if (this->IsNil(index)) { + return Index_Leaf; + } else { + return index; + } + } + public: + void Initialize(size_t num_elements, void *buffer, size_t buffer_size) { + AMS_ASSERT(num_elements <= max_size); + + return this->InitializeImpl(static_cast<int>(num_elements), buffer, buffer_size); + } + + iterator begin() { return iterator(*this); } + const_iterator begin() const { return const_iterator(*this); } + + iterator end() { return m_end_iterator; } + const_iterator end() const { return m_end_iterator; } + + size_t size() const { return m_header->cur_elements; } + + void clear() { + const auto num_elements = m_header->max_elements; + const auto buffer_size = m_header->buffer_size; + AMS_ASSERT(buffer_size == static_cast<u32>(GetRequiredMemorySize(num_elements))); + + return this->InitializeMemory(num_elements, buffer_size, impl::AvailableIndexFinder::GetSignature()); + } + + bool erase(const Member &elem) { + const auto range = this->equal_range(elem); + if (range.first != range.last) { + this->EraseByIndex(range.first); + return true; + } else { + return false; + } + } + + iterator find(const Member &elem) { + if (const auto index = this->FindIndex(elem); index >= 0) { + return iterator(*this, index); + } else { + return this->end(); + } + } + + const_iterator find(const Member &elem) const { + if (const auto index = this->FindIndex(elem); index >= 0) { + return const_iterator(*this, index); + } else { + return this->end(); + } + } + + std::pair<iterator, bool> insert(const Member &elem) { + const auto index = this->InsertNoHint(false, elem); + const auto it = iterator(*this, index); + return std::make_pair(it, !this->IsNil(index)); + } + + IndexPair equal_range(const Member &elem) { + /* Get node to start iteration. */ + auto cur_index = this->GetRoot(); + auto cur_node = this->GetNode(cur_index); + + auto min_index = Index_Leaf; + auto min_node = this->GetNode(min_index); + + auto max_index = Index_Leaf; + auto max_node = this->GetNode(max_index); + + /* Iterate until current is leaf, to find min/max. */ + while (cur_index != Index_Leaf) { + if (Compare{}(cur_node->m_data, elem)) { + cur_index = cur_node->m_right; + cur_node = this->GetNode(cur_index); + } else { + if (max_index == Index_Leaf && Compare{}(elem, cur_node->m_data)) { + max_index = cur_index; + max_node = this->GetNode(max_index); + } + min_index = cur_index; + min_node = this->GetNode(min_index); + + cur_index = cur_node->m_left; + cur_node = this->GetNode(cur_index); + } + } + + /* Iterate again, to find correct range extent for max. */ + cur_index = (max_index == Index_Leaf) ? this->GetRoot() : max_node->m_left; + cur_node = this->GetNode(cur_index); + while (cur_index != Index_Leaf) { + if (Compare{}(elem, cur_node->m_data)) { + max_index = cur_index; + max_node = cur_node; + cur_index = cur_node->m_left; + } else { + cur_index = cur_node->m_right; + } + cur_node = this->GetNode(cur_index); + } + + AMS_UNUSED(min_node); + return IndexPair{min_index, max_index}; + } + }; + + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_format_string.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_format_string.hpp new file mode 100644 index 00000000..8b8b38f3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_format_string.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + int SNPrintf(char *dst, size_t dst_size, const char *fmt, ...) __attribute__((format(printf, 3, 4))); + int VSNPrintf(char *dst, size_t dst_size, const char *fmt, std::va_list vl); + + int TSNPrintf(char *dst, size_t dst_size, const char *fmt, ...) __attribute__((format(printf, 3, 4))); + int TVSNPrintf(char *dst, size_t dst_size, const char *fmt, std::va_list vl); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_fourcc.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_fourcc.hpp new file mode 100644 index 00000000..8e26fba8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_fourcc.hpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util/util_endian.hpp> + +namespace ams::util { + + template<char A, char B, char C, char D> + struct FourCC { + static constexpr u32 Code = IsLittleEndian() ? ((static_cast<u32>(A) << 0x00) | (static_cast<u32>(B) << 0x08) | (static_cast<u32>(C) << 0x10) | (static_cast<u32>(D) << 0x18)) + : ((static_cast<u32>(A) << 0x18) | (static_cast<u32>(B) << 0x10) | (static_cast<u32>(C) << 0x08) | (static_cast<u32>(D) << 0x00)); + + static constexpr const char String[] = {A, B, C, D}; + + static_assert(sizeof(Code) == 4); + static_assert(sizeof(String) == 4); + }; + + template<char A, char B, char C, char D> + struct ReverseFourCC { + static constexpr u32 Code = IsLittleEndian() ? ((static_cast<u32>(A) << 0x18) | (static_cast<u32>(B) << 0x10) | (static_cast<u32>(C) << 0x08) | (static_cast<u32>(D) << 0x00)) + : ((static_cast<u32>(A) << 0x00) | (static_cast<u32>(B) << 0x08) | (static_cast<u32>(C) << 0x10) | (static_cast<u32>(D) << 0x18)); + + static constexpr const char String[] = {D, C, B, A}; + + static_assert(sizeof(Code) == 4); + static_assert(sizeof(String) == 4); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_function_local_static.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_function_local_static.hpp new file mode 100644 index 00000000..399b4e75 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_function_local_static.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util/util_typed_storage.hpp> + +namespace ams::util { + + #define AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(_TYPE_, _NAME_, ...) static constinit _TYPE_ _NAME_ { __VA_ARGS__ } + + /* NOTE: This must use placement new, to support private constructors. */ + #define AMS_FUNCTION_LOCAL_STATIC_IMPL(_LOCKTYPE_, _SCOPELOCKTYPE_, _TYPE_, _NAME_, ...) \ + static constinit ::ams::util::TypedStorage<_TYPE_> s_fls_storage_for_##_NAME_ {}; \ + static constinit bool s_fls_initialized_##_NAME_ = false; \ + static constinit _LOCKTYPE_ s_fls_init_lock_##_NAME_ {}; \ + if (AMS_UNLIKELY(!(s_fls_initialized_##_NAME_))) { \ + _SCOPELOCKTYPE_ sl_fls_for_##_NAME_ { s_fls_init_lock_##_NAME_ }; \ + if (AMS_LIKELY(!(s_fls_initialized_##_NAME_))) { \ + new (::ams::util::impl::GetPointerForConstructAt(s_fls_storage_for_##_NAME_)) _TYPE_( __VA_ARGS__ ); \ + s_fls_initialized_##_NAME_ = true; \ + } \ + } \ + \ + _TYPE_ & _NAME_ = util::GetReference(s_fls_storage_for_##_NAME_) + + #if defined(ATMOSPHERE_IS_MESOSPHERE) + + #define AMS_FUNCTION_LOCAL_STATIC(_TYPE_, _NAME_, ...) AMS_FUNCTION_LOCAL_STATIC_IMPL(KSpinLock, KScopedSpinLock, _TYPE_, _NAME_, ##__VA_ARGS__) + + #elif defined(ATMOSPHERE_IS_STRATOSPHERE) + + #define AMS_FUNCTION_LOCAL_STATIC(_TYPE_, _NAME_, ...) AMS_FUNCTION_LOCAL_STATIC_IMPL(os::SdkMutex, std::scoped_lock, _TYPE_, _NAME_, ##__VA_ARGS__) + + #endif + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_i_function.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_i_function.hpp new file mode 100644 index 00000000..b2966baf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_i_function.hpp @@ -0,0 +1,112 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + template<typename T> + class IFunction; + + namespace impl { + + template<typename> + struct GetIFunctionTypeForObject; + + template<typename F, typename R, typename... Args> + struct GetIFunctionTypeForObject<R (F::*)(Args...)> { using Type = R(Args...); }; + + template<typename F, typename R, typename... Args> + struct GetIFunctionTypeForObject<R (F::*)(Args...) const> { using Type = R(Args...); }; + + template<typename> + struct GetIFunctionType; + + template<typename R, typename... Args> + struct GetIFunctionType<R(Args...)> { using Type = R(Args...); }; + + template<typename R, typename... Args> + struct GetIFunctionType<R(*)(Args...)> : public GetIFunctionType<R(Args...)>{}; + + template<typename F> + struct GetIFunctionType<std::reference_wrapper<F>> : public GetIFunctionType<F>{}; + + template<typename F> + struct GetIFunctionType : public GetIFunctionTypeForObject<decltype(&F::operator())>{}; + + template<typename T, typename F, typename Enabled = void> + class Function; + + template<typename R, typename... Args, typename F> + class Function<R(Args...), F, typename std::enable_if<!(std::is_class<F>::value && !std::is_final<F>::value)>::type> final : public IFunction<R(Args...)> { + private: + F m_f; + public: + constexpr explicit Function(F f) : m_f(std::move(f)) { /* ... */} + constexpr virtual ~Function() override { /* ... */ } + + constexpr virtual R operator()(Args... args) const override final { + return m_f(std::forward<Args>(args)...); + } + }; + + template<typename R, typename... Args, typename F> + class Function<R(Args...), F, typename std::enable_if<std::is_class<F>::value && !std::is_final<F>::value>::type> final : public IFunction<R(Args...)>, private F { + public: + constexpr explicit Function(F f) : F(std::move(f)) { /* ... */} + constexpr virtual ~Function() override { /* ... */ } + + constexpr virtual R operator()(Args... args) const override final { + return static_cast<const F &>(*this).operator()(std::forward<Args>(args)...); + } + }; + + template<typename I, typename F> + constexpr ALWAYS_INLINE auto MakeIFunctionExplicitly(F f) { + using FunctionType = ::ams::util::impl::Function<I, typename std::decay<F>::type>; + return FunctionType{ std::move(f) }; + } + + template<typename I, typename T, typename R> + constexpr ALWAYS_INLINE auto MakeIFunctionExplicitly(R T::*f) { + return MakeIFunctionExplicitly<I>(std::mem_fn(f)); + } + + } + + template<typename R, typename... Args> + class IFunction<R(Args...)> { + protected: + constexpr virtual ~IFunction() { /* ... */ }; + public: + constexpr virtual R operator()(Args... args) const = 0; + + template<typename F> + static constexpr ALWAYS_INLINE auto Make(F f) { + return ::ams::util::impl::MakeIFunctionExplicitly<R(Args...)>(std::move(f)); + } + }; + + template<typename F, typename = typename std::enable_if<!std::is_member_pointer<F>::value>::type> + constexpr ALWAYS_INLINE auto MakeIFunction(F f) { + static_assert(!std::is_member_pointer<F>::value); + + return IFunction<typename ::ams::util::impl::GetIFunctionType<typename std::decay<F>::type>::Type>::Make(std::move(f)); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_in_place.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_in_place.hpp new file mode 100644 index 00000000..7a6205de --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_in_place.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + struct in_place_t{}; + + constexpr inline in_place_t in_place = {}; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_int_util.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_int_util.hpp new file mode 100644 index 00000000..7cfed798 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_int_util.hpp @@ -0,0 +1,159 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + namespace impl { + + template<std::signed_integral To, std::signed_integral From> + constexpr ALWAYS_INLINE bool IsIntValueRepresentableImpl(From v) { + using ToLimit = std::numeric_limits<To>; + using FromLimit = std::numeric_limits<From>; + if constexpr (ToLimit::min() <= FromLimit::min() && FromLimit::max() <= ToLimit::max()) { + return true; + } else { + return ToLimit::min() <= v && v <= ToLimit::max(); + } + } + + template<std::unsigned_integral To, std::unsigned_integral From> + constexpr ALWAYS_INLINE bool IsIntValueRepresentableImpl(From v) { + using ToLimit = std::numeric_limits<To>; + using FromLimit = std::numeric_limits<From>; + if constexpr (ToLimit::min() <= FromLimit::min() && FromLimit::max() <= ToLimit::max()) { + return true; + } else { + return ToLimit::min() <= v && v <= ToLimit::max(); + } + } + + template<std::unsigned_integral To, std::signed_integral From> + constexpr ALWAYS_INLINE bool IsIntValueRepresentableImpl(From v) { + using UnsignedFrom = typename std::make_unsigned<From>::type; + + if (v < 0) { + return false; + } else { + return IsIntValueRepresentableImpl<To, UnsignedFrom>(static_cast<UnsignedFrom>(v)); + } + } + + template<std::signed_integral To, std::unsigned_integral From> + constexpr ALWAYS_INLINE bool IsIntValueRepresentableImpl(From v) { + using UnsignedTo = typename std::make_unsigned<To>::type; + + return v <= static_cast<UnsignedTo>(std::numeric_limits<To>::max()); + } + + } + + template<std::integral To, std::integral From> + constexpr ALWAYS_INLINE bool IsIntValueRepresentable(From v) { + return ::ams::util::impl::IsIntValueRepresentableImpl<To, From>(v); + } + + template<std::integral T> + constexpr ALWAYS_INLINE bool CanAddWithoutOverflow(T x, T y) { + if constexpr (std::unsigned_integral<T>) { + return x <= std::numeric_limits<T>::max() - y; + } else { + if (y >= 0) { + return x <= std::numeric_limits<T>::max() - y; + } else { + return x >= std::numeric_limits<T>::min() - y; + } + } + } + + template<std::integral T> + constexpr ALWAYS_INLINE bool CanSubtractWithoutOverflow(T x, T y) { + if constexpr (std::unsigned_integral<T>) { + return x >= std::numeric_limits<T>::min() + y; + } else { + if (y >= 0) { + return x >= std::numeric_limits<T>::min() + y; + } else { + return x <= std::numeric_limits<T>::max() + y; + } + } + } + + template<std::integral T> + constexpr ALWAYS_INLINE bool CanMultiplyWithoutOverflow(T x, T y) { + if (x == 0 || y == 0) { + return true; + } + + if constexpr (std::unsigned_integral<T>) { + return y <= std::numeric_limits<T>::max() / x; + } else { + if (x > 0) { + if (y > 0) { + return y <= std::numeric_limits<T>::max() / x; + } else /*if (y < 0) */ { + return y >= std::numeric_limits<T>::min() / x; + } + } else /* if (x < 0) */ { + if (y > 0) { + return x >= std::numeric_limits<T>::min() / y; + } else /*if (y < 0) */ { + return y >= std::numeric_limits<T>::max() / x; + } + } + } + } + + template<std::integral T> + constexpr inline bool TryAddWithoutOverflow(T *out, T x, T y) { + AMS_ASSERT(out != nullptr); + + if (CanAddWithoutOverflow(x, y)) { + *out = x + y; + return true; + } else { + return false; + } + } + + template<std::integral T> + constexpr inline bool TrySubtractWithoutOverflow(T *out, T x, T y) { + AMS_ASSERT(out != nullptr); + + if (CanSubtractWithoutOverflow(x, y)) { + *out = x - y; + return true; + } else { + return false; + } + } + + template<std::integral T> + constexpr inline bool TryMultiplyWithoutOverflow(T *out, T x, T y) { + AMS_ASSERT(out != nullptr); + + if (CanMultiplyWithoutOverflow(x, y)) { + *out = x * y; + return true; + } else { + return false; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_intrusive_list.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_intrusive_list.hpp new file mode 100644 index 00000000..0eceec9e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_intrusive_list.hpp @@ -0,0 +1,623 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util/util_parent_of_member.hpp> + +namespace ams::util { + + AMS_PRAGMA_BEGIN_OPTIMIZE("-O3") + + /* Forward declare implementation class for Node. */ + namespace impl { + + class IntrusiveListImpl; + + } + + class IntrusiveListNode { + NON_COPYABLE(IntrusiveListNode); + private: + friend class impl::IntrusiveListImpl; + + IntrusiveListNode *m_prev; + IntrusiveListNode *m_next; + public: + constexpr ALWAYS_INLINE IntrusiveListNode() : m_prev(this), m_next(this) { /* ... */ } + + constexpr ALWAYS_INLINE bool IsLinked() const { + return m_next != this; + } + private: + constexpr ALWAYS_INLINE void LinkPrev(IntrusiveListNode *node) { + /* We can't link an already linked node. */ + AMS_ASSERT(!node->IsLinked()); + this->SplicePrev(node, node); + } + + constexpr ALWAYS_INLINE void SplicePrev(IntrusiveListNode *first, IntrusiveListNode *last) { + /* Splice a range into the list. */ + auto last_prev = last->m_prev; + first->m_prev = m_prev; + last_prev->m_next = this; + m_prev->m_next = first; + m_prev = last_prev; + } + + constexpr ALWAYS_INLINE void LinkNext(IntrusiveListNode *node) { + /* We can't link an already linked node. */ + AMS_ASSERT(!node->IsLinked()); + return this->SpliceNext(node, node); + } + + constexpr ALWAYS_INLINE void SpliceNext(IntrusiveListNode *first, IntrusiveListNode *last) { + /* Splice a range into the list. */ + auto last_prev = last->m_prev; + first->m_prev = this; + last_prev->m_next = m_next; + m_next->m_prev = last_prev; + m_next = first; + } + + constexpr ALWAYS_INLINE void Unlink() { + this->Unlink(m_next); + } + + constexpr ALWAYS_INLINE void Unlink(IntrusiveListNode *last) { + /* Unlink a node from a next node. */ + auto last_prev = last->m_prev; + m_prev->m_next = last; + last->m_prev = m_prev; + last_prev->m_next = this; + m_prev = last_prev; + } + + constexpr ALWAYS_INLINE IntrusiveListNode *GetPrev() { + return m_prev; + } + + constexpr ALWAYS_INLINE const IntrusiveListNode *GetPrev() const { + return m_prev; + } + + constexpr ALWAYS_INLINE IntrusiveListNode *GetNext() { + return m_next; + } + + constexpr ALWAYS_INLINE const IntrusiveListNode *GetNext() const { + return m_next; + } + }; + /* DEPRECATED: static_assert(std::is_literal_type<IntrusiveListNode>::value); */ + + namespace impl { + + class IntrusiveListImpl { + NON_COPYABLE(IntrusiveListImpl); + private: + IntrusiveListNode m_root_node; + public: + template<bool Const> + class Iterator; + + using value_type = IntrusiveListNode; + using size_type = size_t; + using difference_type = ptrdiff_t; + using pointer = value_type *; + using const_pointer = const value_type *; + using reference = value_type &; + using const_reference = const value_type &; + using iterator = Iterator<false>; + using const_iterator = Iterator<true>; + using reverse_iterator = std::reverse_iterator<iterator>; + using const_reverse_iterator = std::reverse_iterator<const_iterator>; + + template<bool Const> + class Iterator { + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = typename IntrusiveListImpl::value_type; + using difference_type = typename IntrusiveListImpl::difference_type; + using pointer = typename std::conditional<Const, IntrusiveListImpl::const_pointer, IntrusiveListImpl::pointer>::type; + using reference = typename std::conditional<Const, IntrusiveListImpl::const_reference, IntrusiveListImpl::reference>::type; + private: + pointer m_node; + public: + constexpr ALWAYS_INLINE explicit Iterator(pointer n) : m_node(n) { /* ... */ } + + constexpr ALWAYS_INLINE bool operator==(const Iterator &rhs) const { + return m_node == rhs.m_node; + } + + constexpr ALWAYS_INLINE pointer operator->() const { + return m_node; + } + + constexpr ALWAYS_INLINE reference operator*() const { + return *m_node; + } + + constexpr ALWAYS_INLINE Iterator &operator++() { + m_node = m_node->m_next; + return *this; + } + + constexpr ALWAYS_INLINE Iterator &operator--() { + m_node = m_node->m_prev; + return *this; + } + + constexpr ALWAYS_INLINE Iterator operator++(int) { + const Iterator it{*this}; + ++(*this); + return it; + } + + constexpr ALWAYS_INLINE Iterator operator--(int) { + const Iterator it{*this}; + --(*this); + return it; + } + + constexpr ALWAYS_INLINE operator Iterator<true>() const { + return Iterator<true>(m_node); + } + + constexpr ALWAYS_INLINE Iterator<false> GetNonConstIterator() const { + return Iterator<false>(const_cast<IntrusiveListImpl::pointer>(m_node)); + } + }; + public: + constexpr ALWAYS_INLINE IntrusiveListImpl() : m_root_node() { /* ... */ } + + /* Iterator accessors. */ + constexpr ALWAYS_INLINE iterator begin() { + return iterator(m_root_node.GetNext()); + } + + constexpr ALWAYS_INLINE const_iterator begin() const { + return const_iterator(m_root_node.GetNext()); + } + + constexpr ALWAYS_INLINE iterator end() { + return iterator(std::addressof(m_root_node)); + } + + constexpr ALWAYS_INLINE const_iterator end() const { + return const_iterator(std::addressof(m_root_node)); + } + + constexpr ALWAYS_INLINE iterator iterator_to(reference v) { + /* Only allow iterator_to for values in lists. */ + AMS_ASSERT(v.IsLinked()); + return iterator(std::addressof(v)); + } + + constexpr ALWAYS_INLINE const_iterator iterator_to(const_reference v) const { + /* Only allow iterator_to for values in lists. */ + AMS_ASSERT(v.IsLinked()); + return const_iterator(std::addressof(v)); + } + + /* Content management. */ + constexpr ALWAYS_INLINE bool empty() const { + return !m_root_node.IsLinked(); + } + + constexpr ALWAYS_INLINE size_type size() const { + return static_cast<size_type>(std::distance(this->begin(), this->end())); + } + + constexpr ALWAYS_INLINE reference back() { + return *m_root_node.GetPrev(); + } + + constexpr ALWAYS_INLINE const_reference back() const { + return *m_root_node.GetPrev(); + } + + constexpr ALWAYS_INLINE reference front() { + return *m_root_node.GetNext(); + } + + constexpr ALWAYS_INLINE const_reference front() const { + return *m_root_node.GetNext(); + } + + constexpr ALWAYS_INLINE void push_back(reference node) { + m_root_node.LinkPrev(std::addressof(node)); + } + + constexpr ALWAYS_INLINE void push_front(reference node) { + m_root_node.LinkNext(std::addressof(node)); + } + + constexpr ALWAYS_INLINE void pop_back() { + m_root_node.GetPrev()->Unlink(); + } + + constexpr ALWAYS_INLINE void pop_front() { + m_root_node.GetNext()->Unlink(); + } + + constexpr ALWAYS_INLINE iterator insert(const_iterator pos, reference node) { + pos.GetNonConstIterator()->LinkPrev(std::addressof(node)); + return iterator(std::addressof(node)); + } + + constexpr ALWAYS_INLINE void splice(const_iterator pos, IntrusiveListImpl &o) { + splice_impl(pos, o.begin(), o.end()); + } + + constexpr ALWAYS_INLINE void splice(const_iterator pos, IntrusiveListImpl &o, const_iterator first) { + AMS_UNUSED(o); + const_iterator last(first); + std::advance(last, 1); + splice_impl(pos, first, last); + } + + constexpr ALWAYS_INLINE void splice(const_iterator pos, IntrusiveListImpl &o, const_iterator first, const_iterator last) { + AMS_UNUSED(o); + splice_impl(pos, first, last); + } + + constexpr ALWAYS_INLINE iterator erase(const_iterator pos) { + if (pos == this->end()) { + return this->end(); + } + iterator it(pos.GetNonConstIterator()); + (it++)->Unlink(); + return it; + } + + constexpr ALWAYS_INLINE void clear() { + while (!this->empty()) { + this->pop_front(); + } + } + private: + constexpr ALWAYS_INLINE void splice_impl(const_iterator _pos, const_iterator _first, const_iterator _last) { + if (_first == _last) { + return; + } + iterator pos(_pos.GetNonConstIterator()); + iterator first(_first.GetNonConstIterator()); + iterator last(_last.GetNonConstIterator()); + first->Unlink(std::addressof(*last)); + pos->SplicePrev(std::addressof(*first), std::addressof(*first)); + } + }; + + } + + template<class T, class Traits> + class IntrusiveList { + NON_COPYABLE(IntrusiveList); + private: + impl::IntrusiveListImpl m_impl; + public: + template<bool Const> + class Iterator; + + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + using pointer = value_type *; + using const_pointer = const value_type *; + using reference = value_type &; + using const_reference = const value_type &; + using iterator = Iterator<false>; + using const_iterator = Iterator<true>; + using reverse_iterator = std::reverse_iterator<iterator>; + using const_reverse_iterator = std::reverse_iterator<const_iterator>; + + template<bool Const> + class Iterator { + public: + friend class ams::util::IntrusiveList<T, Traits>; + + using ImplIterator = typename std::conditional<Const, ams::util::impl::IntrusiveListImpl::const_iterator, ams::util::impl::IntrusiveListImpl::iterator>::type; + + using iterator_category = std::bidirectional_iterator_tag; + using value_type = typename IntrusiveList::value_type; + using difference_type = typename IntrusiveList::difference_type; + using pointer = typename std::conditional<Const, IntrusiveList::const_pointer, IntrusiveList::pointer>::type; + using reference = typename std::conditional<Const, IntrusiveList::const_reference, IntrusiveList::reference>::type; + private: + ImplIterator m_iterator; + private: + constexpr explicit ALWAYS_INLINE Iterator(ImplIterator it) : m_iterator(it) { /* ... */ } + + constexpr ALWAYS_INLINE ImplIterator GetImplIterator() const { + return m_iterator; + } + public: + constexpr ALWAYS_INLINE bool operator==(const Iterator &rhs) const { + return m_iterator == rhs.m_iterator; + } + + constexpr ALWAYS_INLINE pointer operator->() const { + return std::addressof(Traits::GetParent(*m_iterator)); + } + + constexpr ALWAYS_INLINE reference operator*() const { + return Traits::GetParent(*m_iterator); + } + + constexpr ALWAYS_INLINE Iterator &operator++() { + ++m_iterator; + return *this; + } + + constexpr ALWAYS_INLINE Iterator &operator--() { + --m_iterator; + return *this; + } + + constexpr ALWAYS_INLINE Iterator operator++(int) { + const Iterator it{*this}; + ++m_iterator; + return it; + } + + constexpr ALWAYS_INLINE Iterator operator--(int) { + const Iterator it{*this}; + --m_iterator; + return it; + } + + constexpr ALWAYS_INLINE operator Iterator<true>() const { + return Iterator<true>(m_iterator); + } + }; + private: + static constexpr ALWAYS_INLINE IntrusiveListNode &GetNode(reference ref) { + return Traits::GetNode(ref); + } + + static constexpr ALWAYS_INLINE IntrusiveListNode const &GetNode(const_reference ref) { + return Traits::GetNode(ref); + } + + static constexpr ALWAYS_INLINE reference GetParent(IntrusiveListNode &node) { + return Traits::GetParent(node); + } + + static constexpr ALWAYS_INLINE const_reference GetParent(IntrusiveListNode const &node) { + return Traits::GetParent(node); + } + public: + constexpr ALWAYS_INLINE IntrusiveList() : m_impl() { /* ... */ } + + /* Iterator accessors. */ + constexpr ALWAYS_INLINE iterator begin() { + return iterator(m_impl.begin()); + } + + constexpr ALWAYS_INLINE const_iterator begin() const { + return const_iterator(m_impl.begin()); + } + + constexpr ALWAYS_INLINE iterator end() { + return iterator(m_impl.end()); + } + + constexpr ALWAYS_INLINE const_iterator end() const { + return const_iterator(m_impl.end()); + } + + constexpr ALWAYS_INLINE const_iterator cbegin() const { + return this->begin(); + } + + constexpr ALWAYS_INLINE const_iterator cend() const { + return this->end(); + } + + constexpr ALWAYS_INLINE reverse_iterator rbegin() { + return reverse_iterator(this->end()); + } + + constexpr ALWAYS_INLINE const_reverse_iterator rbegin() const { + return const_reverse_iterator(this->end()); + } + + constexpr ALWAYS_INLINE reverse_iterator rend() { + return reverse_iterator(this->begin()); + } + + constexpr ALWAYS_INLINE const_reverse_iterator rend() const { + return const_reverse_iterator(this->begin()); + } + + constexpr ALWAYS_INLINE const_reverse_iterator crbegin() const { + return this->rbegin(); + } + + constexpr ALWAYS_INLINE const_reverse_iterator crend() const { + return this->rend(); + } + + constexpr ALWAYS_INLINE iterator iterator_to(reference v) { + return iterator(m_impl.iterator_to(GetNode(v))); + } + + constexpr ALWAYS_INLINE const_iterator iterator_to(const_reference v) const { + return const_iterator(m_impl.iterator_to(GetNode(v))); + } + + /* Content management. */ + constexpr ALWAYS_INLINE bool empty() const { + return m_impl.empty(); + } + + constexpr ALWAYS_INLINE size_type size() const { + return m_impl.size(); + } + + constexpr ALWAYS_INLINE reference back() { + AMS_ASSERT(!m_impl.empty()); + return GetParent(m_impl.back()); + } + + constexpr ALWAYS_INLINE const_reference back() const { + AMS_ASSERT(!m_impl.empty()); + return GetParent(m_impl.back()); + } + + constexpr ALWAYS_INLINE reference front() { + AMS_ASSERT(!m_impl.empty()); + return GetParent(m_impl.front()); + } + + constexpr ALWAYS_INLINE const_reference front() const { + AMS_ASSERT(!m_impl.empty()); + return GetParent(m_impl.front()); + } + + constexpr ALWAYS_INLINE void push_back(reference ref) { + m_impl.push_back(GetNode(ref)); + } + + constexpr ALWAYS_INLINE void push_front(reference ref) { + m_impl.push_front(GetNode(ref)); + } + + constexpr ALWAYS_INLINE void pop_back() { + AMS_ASSERT(!m_impl.empty()); + m_impl.pop_back(); + } + + constexpr ALWAYS_INLINE void pop_front() { + AMS_ASSERT(!m_impl.empty()); + m_impl.pop_front(); + } + + constexpr ALWAYS_INLINE iterator insert(const_iterator pos, reference ref) { + return iterator(m_impl.insert(pos.GetImplIterator(), GetNode(ref))); + } + + constexpr ALWAYS_INLINE void splice(const_iterator pos, IntrusiveList &o) { + m_impl.splice(pos.GetImplIterator(), o.m_impl); + } + + constexpr ALWAYS_INLINE void splice(const_iterator pos, IntrusiveList &o, const_iterator first) { + m_impl.splice(pos.GetImplIterator(), o.m_impl, first.GetImplIterator()); + } + + constexpr ALWAYS_INLINE void splice(const_iterator pos, IntrusiveList &o, const_iterator first, const_iterator last) { + m_impl.splice(pos.GetImplIterator(), o.m_impl, first.GetImplIterator(), last.GetImplIterator()); + } + + constexpr ALWAYS_INLINE iterator erase(const_iterator pos) { + return iterator(m_impl.erase(pos.GetImplIterator())); + } + + constexpr ALWAYS_INLINE void clear() { + m_impl.clear(); + } + }; + + template<auto T, class Derived = util::impl::GetParentType<T>> + class IntrusiveListMemberTraits; + + template<class Parent, IntrusiveListNode Parent::*Member, class Derived> + class IntrusiveListMemberTraits<Member, Derived> { + public: + using ListType = IntrusiveList<Derived, IntrusiveListMemberTraits>; + private: + friend class IntrusiveList<Derived, IntrusiveListMemberTraits>; + + static constexpr ALWAYS_INLINE IntrusiveListNode &GetNode(Derived &parent) { + return parent.*Member; + } + + static constexpr ALWAYS_INLINE IntrusiveListNode const &GetNode(Derived const &parent) { + return parent.*Member; + } + + static ALWAYS_INLINE Derived &GetParent(IntrusiveListNode &node) { + return util::GetParentReference<Member, Derived>(std::addressof(node)); + } + + static ALWAYS_INLINE Derived const &GetParent(IntrusiveListNode const &node) { + return util::GetParentReference<Member, Derived>(std::addressof(node)); + } + }; + + template<auto T, class Derived = util::impl::GetParentType<T>> + class IntrusiveListMemberTraitsByNonConstexprOffsetOf; + + template<class Parent, IntrusiveListNode Parent::*Member, class Derived> + class IntrusiveListMemberTraitsByNonConstexprOffsetOf<Member, Derived> { + public: + using ListType = IntrusiveList<Derived, IntrusiveListMemberTraitsByNonConstexprOffsetOf>; + private: + friend class IntrusiveList<Derived, IntrusiveListMemberTraitsByNonConstexprOffsetOf>; + + static constexpr ALWAYS_INLINE IntrusiveListNode &GetNode(Derived &parent) { + return parent.*Member; + } + + static constexpr ALWAYS_INLINE IntrusiveListNode const &GetNode(Derived const &parent) { + return parent.*Member; + } + + static ALWAYS_INLINE Derived &GetParent(IntrusiveListNode &node) { + return *reinterpret_cast<Derived *>(reinterpret_cast<char *>(std::addressof(node)) - GetOffset()); + } + + static ALWAYS_INLINE Derived const &GetParent(IntrusiveListNode const &node) { + return *reinterpret_cast<const Derived *>(reinterpret_cast<const char *>(std::addressof(node)) - GetOffset()); + } + + static ALWAYS_INLINE uintptr_t GetOffset() { + return reinterpret_cast<uintptr_t>(std::addressof(reinterpret_cast<Derived *>(0)->*Member)); + } + }; + + template<class Derived> + class IntrusiveListBaseNode : public IntrusiveListNode{}; + + template<class Derived> + class IntrusiveListBaseTraits { + public: + using ListType = IntrusiveList<Derived, IntrusiveListBaseTraits>; + private: + friend class IntrusiveList<Derived, IntrusiveListBaseTraits>; + + static constexpr ALWAYS_INLINE IntrusiveListNode &GetNode(Derived &parent) { + return static_cast<IntrusiveListNode &>(static_cast<IntrusiveListBaseNode<Derived> &>(parent)); + } + + static constexpr ALWAYS_INLINE IntrusiveListNode const &GetNode(Derived const &parent) { + return static_cast<const IntrusiveListNode &>(static_cast<const IntrusiveListBaseNode<Derived> &>(parent)); + } + + static constexpr ALWAYS_INLINE Derived &GetParent(IntrusiveListNode &node) { + return static_cast<Derived &>(static_cast<IntrusiveListBaseNode<Derived> &>(node)); + } + + static constexpr ALWAYS_INLINE Derived const &GetParent(IntrusiveListNode const &node) { + return static_cast<const Derived &>(static_cast<const IntrusiveListBaseNode<Derived> &>(node)); + } + }; + + AMS_PRAGMA_END_OPTIMIZE() + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_intrusive_red_black_tree.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_intrusive_red_black_tree.hpp new file mode 100644 index 00000000..0476fc9c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_intrusive_red_black_tree.hpp @@ -0,0 +1,579 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util/util_parent_of_member.hpp> +#include <vapours/freebsd/tree.hpp> + +namespace ams::util { + + AMS_PRAGMA_BEGIN_OPTIMIZE("-O3") + + namespace impl { + + class IntrusiveRedBlackTreeImpl; + + } + + #pragma pack(push, 4) + struct IntrusiveRedBlackTreeNode { + NON_COPYABLE(IntrusiveRedBlackTreeNode); + public: + using RBEntry = freebsd::RBEntry<IntrusiveRedBlackTreeNode>; + private: + RBEntry m_entry; + public: + constexpr explicit ALWAYS_INLINE IntrusiveRedBlackTreeNode(util::ConstantInitializeTag) : m_entry(util::ConstantInitialize) { /* ... */ } + explicit ALWAYS_INLINE IntrusiveRedBlackTreeNode() { /* ... */ } + + [[nodiscard]] constexpr ALWAYS_INLINE RBEntry &GetRBEntry() { return m_entry; } + [[nodiscard]] constexpr ALWAYS_INLINE const RBEntry &GetRBEntry() const { return m_entry; } + + constexpr ALWAYS_INLINE void SetRBEntry(const RBEntry &entry) { m_entry = entry; } + }; + static_assert(sizeof(IntrusiveRedBlackTreeNode) == 3 * sizeof(void *) + std::max<size_t>(sizeof(freebsd::RBColor), 4)); + #pragma pack(pop) + + template<class T, class Traits, class Comparator> + class IntrusiveRedBlackTree; + + namespace impl { + + class IntrusiveRedBlackTreeImpl { + NON_COPYABLE(IntrusiveRedBlackTreeImpl); + private: + template<class, class, class> + friend class ::ams::util::IntrusiveRedBlackTree; + private: + using RootType = freebsd::RBHead<IntrusiveRedBlackTreeNode>; + private: + RootType m_root; + public: + template<bool Const> + class Iterator; + + using value_type = IntrusiveRedBlackTreeNode; + using size_type = size_t; + using difference_type = ptrdiff_t; + using pointer = value_type *; + using const_pointer = const value_type *; + using reference = value_type &; + using const_reference = const value_type &; + using iterator = Iterator<false>; + using const_iterator = Iterator<true>; + + template<bool Const> + class Iterator { + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = typename IntrusiveRedBlackTreeImpl::value_type; + using difference_type = typename IntrusiveRedBlackTreeImpl::difference_type; + using pointer = typename std::conditional<Const, IntrusiveRedBlackTreeImpl::const_pointer, IntrusiveRedBlackTreeImpl::pointer>::type; + using reference = typename std::conditional<Const, IntrusiveRedBlackTreeImpl::const_reference, IntrusiveRedBlackTreeImpl::reference>::type; + private: + pointer m_node; + public: + constexpr explicit ALWAYS_INLINE Iterator(pointer n) : m_node(n) { /* ... */ } + + constexpr ALWAYS_INLINE bool operator==(const Iterator &rhs) const { + return m_node == rhs.m_node; + } + + constexpr ALWAYS_INLINE pointer operator->() const { + return m_node; + } + + constexpr ALWAYS_INLINE reference operator*() const { + return *m_node; + } + + constexpr ALWAYS_INLINE Iterator &operator++() { + m_node = GetNext(m_node); + return *this; + } + + constexpr ALWAYS_INLINE Iterator &operator--() { + m_node = GetPrev(m_node); + return *this; + } + + constexpr ALWAYS_INLINE Iterator operator++(int) { + const Iterator it{*this}; + ++(*this); + return it; + } + + constexpr ALWAYS_INLINE Iterator operator--(int) { + const Iterator it{*this}; + --(*this); + return it; + } + + constexpr ALWAYS_INLINE operator Iterator<true>() const { + return Iterator<true>(m_node); + } + }; + private: + constexpr ALWAYS_INLINE bool EmptyImpl() const { + return m_root.IsEmpty(); + } + + constexpr ALWAYS_INLINE IntrusiveRedBlackTreeNode *GetMinImpl() const { + return freebsd::RB_MIN(const_cast<RootType &>(m_root)); + } + + constexpr ALWAYS_INLINE IntrusiveRedBlackTreeNode *GetMaxImpl() const { + return freebsd::RB_MAX(const_cast<RootType &>(m_root)); + } + + constexpr ALWAYS_INLINE IntrusiveRedBlackTreeNode *RemoveImpl(IntrusiveRedBlackTreeNode *node) { + return freebsd::RB_REMOVE(m_root, node); + } + public: + static constexpr IntrusiveRedBlackTreeNode *GetNext(IntrusiveRedBlackTreeNode *node) { + return freebsd::RB_NEXT(node); + } + + static constexpr IntrusiveRedBlackTreeNode *GetPrev(IntrusiveRedBlackTreeNode *node) { + return freebsd::RB_PREV(node); + } + + static constexpr ALWAYS_INLINE IntrusiveRedBlackTreeNode const *GetNext(IntrusiveRedBlackTreeNode const *node) { + return static_cast<const IntrusiveRedBlackTreeNode *>(GetNext(const_cast<IntrusiveRedBlackTreeNode *>(node))); + } + + static constexpr ALWAYS_INLINE IntrusiveRedBlackTreeNode const *GetPrev(IntrusiveRedBlackTreeNode const *node) { + return static_cast<const IntrusiveRedBlackTreeNode *>(GetPrev(const_cast<IntrusiveRedBlackTreeNode *>(node))); + } + public: + constexpr ALWAYS_INLINE IntrusiveRedBlackTreeImpl() = default; + + /* Iterator accessors. */ + constexpr ALWAYS_INLINE iterator begin() { + return iterator(this->GetMinImpl()); + } + + constexpr ALWAYS_INLINE const_iterator begin() const { + return const_iterator(this->GetMinImpl()); + } + + constexpr ALWAYS_INLINE iterator end() { + return iterator(static_cast<IntrusiveRedBlackTreeNode *>(nullptr)); + } + + constexpr ALWAYS_INLINE const_iterator end() const { + return const_iterator(static_cast<const IntrusiveRedBlackTreeNode *>(nullptr)); + } + + constexpr ALWAYS_INLINE const_iterator cbegin() const { + return this->begin(); + } + + constexpr ALWAYS_INLINE const_iterator cend() const { + return this->end(); + } + + constexpr ALWAYS_INLINE iterator iterator_to(reference ref) { + return iterator(std::addressof(ref)); + } + + constexpr ALWAYS_INLINE const_iterator iterator_to(const_reference ref) const { + return const_iterator(std::addressof(ref)); + } + + /* Content management. */ + constexpr ALWAYS_INLINE bool empty() const { + return this->EmptyImpl(); + } + + constexpr ALWAYS_INLINE reference back() { + return *this->GetMaxImpl(); + } + + constexpr ALWAYS_INLINE const_reference back() const { + return *this->GetMaxImpl(); + } + + constexpr ALWAYS_INLINE reference front() { + return *this->GetMinImpl(); + } + + constexpr ALWAYS_INLINE const_reference front() const { + return *this->GetMinImpl(); + } + + constexpr ALWAYS_INLINE iterator erase(iterator it) { + auto cur = std::addressof(*it); + auto next = GetNext(cur); + this->RemoveImpl(cur); + return iterator(next); + } + }; + + } + + template<typename T> + concept HasRedBlackKeyType = requires { + { std::is_same<typename T::RedBlackKeyType, void>::value } -> std::convertible_to<bool>; + }; + + namespace impl { + + template<typename T, typename Default> + consteval auto *GetRedBlackKeyType() { + if constexpr (HasRedBlackKeyType<T>) { + return static_cast<typename T::RedBlackKeyType *>(nullptr); + } else { + return static_cast<Default *>(nullptr); + } + } + + } + + template<typename T, typename Default> + using RedBlackKeyType = typename std::remove_pointer<decltype(impl::GetRedBlackKeyType<T, Default>())>::type; + + template<class T, class Traits, class Comparator> + class IntrusiveRedBlackTree { + NON_COPYABLE(IntrusiveRedBlackTree); + public: + using ImplType = impl::IntrusiveRedBlackTreeImpl; + private: + ImplType m_impl; + public: + template<bool Const> + class Iterator; + + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + using pointer = T *; + using const_pointer = const T *; + using reference = T &; + using const_reference = const T &; + using iterator = Iterator<false>; + using const_iterator = Iterator<true>; + + using key_type = RedBlackKeyType<Comparator, value_type>; + using const_key_pointer = const key_type *; + using const_key_reference = const key_type &; + + template<bool Const> + class Iterator { + public: + friend class IntrusiveRedBlackTree<T, Traits, Comparator>; + + using ImplIterator = typename std::conditional<Const, ImplType::const_iterator, ImplType::iterator>::type; + + using iterator_category = std::bidirectional_iterator_tag; + using value_type = typename IntrusiveRedBlackTree::value_type; + using difference_type = typename IntrusiveRedBlackTree::difference_type; + using pointer = typename std::conditional<Const, IntrusiveRedBlackTree::const_pointer, IntrusiveRedBlackTree::pointer>::type; + using reference = typename std::conditional<Const, IntrusiveRedBlackTree::const_reference, IntrusiveRedBlackTree::reference>::type; + private: + ImplIterator m_impl; + private: + constexpr explicit ALWAYS_INLINE Iterator(ImplIterator it) : m_impl(it) { /* ... */ } + + constexpr explicit ALWAYS_INLINE Iterator(typename ImplIterator::pointer p) : m_impl(p) { /* ... */ } + + constexpr ALWAYS_INLINE ImplIterator GetImplIterator() const { + return m_impl; + } + public: + constexpr ALWAYS_INLINE bool operator==(const Iterator &rhs) const { + return m_impl == rhs.m_impl; + } + + constexpr ALWAYS_INLINE pointer operator->() const { + return Traits::GetParent(std::addressof(*m_impl)); + } + + constexpr ALWAYS_INLINE reference operator*() const { + return *Traits::GetParent(std::addressof(*m_impl)); + } + + constexpr ALWAYS_INLINE Iterator &operator++() { + ++m_impl; + return *this; + } + + constexpr ALWAYS_INLINE Iterator &operator--() { + --m_impl; + return *this; + } + + constexpr ALWAYS_INLINE Iterator operator++(int) { + const Iterator it{*this}; + ++m_impl; + return it; + } + + constexpr ALWAYS_INLINE Iterator operator--(int) { + const Iterator it{*this}; + --m_impl; + return it; + } + + constexpr ALWAYS_INLINE operator Iterator<true>() const { + return Iterator<true>(m_impl); + } + }; + private: + static constexpr ALWAYS_INLINE int CompareImpl(const IntrusiveRedBlackTreeNode *lhs, const IntrusiveRedBlackTreeNode *rhs) { + return Comparator::Compare(*Traits::GetParent(lhs), *Traits::GetParent(rhs)); + } + + static constexpr ALWAYS_INLINE int CompareKeyImpl(const_key_reference key, const IntrusiveRedBlackTreeNode *rhs) { + return Comparator::Compare(key, *Traits::GetParent(rhs)); + } + + /* Define accessors using RB_* functions. */ + constexpr IntrusiveRedBlackTreeNode *InsertImpl(IntrusiveRedBlackTreeNode *node) { + return freebsd::RB_INSERT(m_impl.m_root, node, CompareImpl); + } + + constexpr ALWAYS_INLINE IntrusiveRedBlackTreeNode *FindImpl(IntrusiveRedBlackTreeNode const *node) const { + return freebsd::RB_FIND(const_cast<ImplType::RootType &>(m_impl.m_root), const_cast<IntrusiveRedBlackTreeNode *>(node), CompareImpl); + } + + constexpr ALWAYS_INLINE IntrusiveRedBlackTreeNode *NFindImpl(IntrusiveRedBlackTreeNode const *node) const { + return freebsd::RB_NFIND(const_cast<ImplType::RootType &>(m_impl.m_root), const_cast<IntrusiveRedBlackTreeNode *>(node), CompareImpl); + } + + constexpr ALWAYS_INLINE IntrusiveRedBlackTreeNode *FindKeyImpl(const_key_reference key) const { + return freebsd::RB_FIND_KEY(const_cast<ImplType::RootType &>(m_impl.m_root), key, CompareKeyImpl); + } + + constexpr ALWAYS_INLINE IntrusiveRedBlackTreeNode *NFindKeyImpl(const_key_reference key) const { + return freebsd::RB_NFIND_KEY(const_cast<ImplType::RootType &>(m_impl.m_root), key, CompareKeyImpl); + } + + constexpr ALWAYS_INLINE IntrusiveRedBlackTreeNode *FindExistingImpl(IntrusiveRedBlackTreeNode const *node) const { + return freebsd::RB_FIND_EXISTING(const_cast<ImplType::RootType &>(m_impl.m_root), const_cast<IntrusiveRedBlackTreeNode *>(node), CompareImpl); + } + + constexpr ALWAYS_INLINE IntrusiveRedBlackTreeNode *FindExistingKeyImpl(const_key_reference key) const { + return freebsd::RB_FIND_EXISTING_KEY(const_cast<ImplType::RootType &>(m_impl.m_root), key, CompareKeyImpl); + } + public: + constexpr ALWAYS_INLINE IntrusiveRedBlackTree() = default; + + /* Iterator accessors. */ + constexpr ALWAYS_INLINE iterator begin() { + return iterator(m_impl.begin()); + } + + constexpr ALWAYS_INLINE const_iterator begin() const { + return const_iterator(m_impl.begin()); + } + + constexpr ALWAYS_INLINE iterator end() { + return iterator(m_impl.end()); + } + + constexpr ALWAYS_INLINE const_iterator end() const { + return const_iterator(m_impl.end()); + } + + constexpr ALWAYS_INLINE const_iterator cbegin() const { + return this->begin(); + } + + constexpr ALWAYS_INLINE const_iterator cend() const { + return this->end(); + } + + constexpr ALWAYS_INLINE iterator iterator_to(reference ref) { + return iterator(m_impl.iterator_to(*Traits::GetNode(std::addressof(ref)))); + } + + constexpr ALWAYS_INLINE const_iterator iterator_to(const_reference ref) const { + return const_iterator(m_impl.iterator_to(*Traits::GetNode(std::addressof(ref)))); + } + + /* Content management. */ + constexpr ALWAYS_INLINE bool empty() const { + return m_impl.empty(); + } + + constexpr ALWAYS_INLINE reference back() { + return *Traits::GetParent(std::addressof(m_impl.back())); + } + + constexpr ALWAYS_INLINE const_reference back() const { + return *Traits::GetParent(std::addressof(m_impl.back())); + } + + constexpr ALWAYS_INLINE reference front() { + return *Traits::GetParent(std::addressof(m_impl.front())); + } + + constexpr ALWAYS_INLINE const_reference front() const { + return *Traits::GetParent(std::addressof(m_impl.front())); + } + + constexpr ALWAYS_INLINE iterator erase(iterator it) { + return iterator(m_impl.erase(it.GetImplIterator())); + } + + constexpr ALWAYS_INLINE iterator insert(reference ref) { + ImplType::pointer node = Traits::GetNode(std::addressof(ref)); + this->InsertImpl(node); + return iterator(node); + } + + constexpr ALWAYS_INLINE iterator find(const_reference ref) const { + return iterator(this->FindImpl(Traits::GetNode(std::addressof(ref)))); + } + + constexpr ALWAYS_INLINE iterator nfind(const_reference ref) const { + return iterator(this->NFindImpl(Traits::GetNode(std::addressof(ref)))); + } + + constexpr ALWAYS_INLINE iterator find_key(const_key_reference ref) const { + return iterator(this->FindKeyImpl(ref)); + } + + constexpr ALWAYS_INLINE iterator nfind_key(const_key_reference ref) const { + return iterator(this->NFindKeyImpl(ref)); + } + + constexpr ALWAYS_INLINE iterator find_existing(const_reference ref) const { + return iterator(this->FindExistingImpl(Traits::GetNode(std::addressof(ref)))); + } + + constexpr ALWAYS_INLINE iterator find_existing_key(const_key_reference ref) const { + return iterator(this->FindExistingKeyImpl(ref)); + } + }; + + template<auto T, class Derived = util::impl::GetParentType<T>> + class IntrusiveRedBlackTreeMemberTraits; + + template<class Parent, IntrusiveRedBlackTreeNode Parent::*Member, class Derived> + class IntrusiveRedBlackTreeMemberTraits<Member, Derived> { + public: + template<class Comparator> + using TreeType = IntrusiveRedBlackTree<Derived, IntrusiveRedBlackTreeMemberTraits, Comparator>; + using TreeTypeImpl = impl::IntrusiveRedBlackTreeImpl; + private: + template<class, class, class> + friend class IntrusiveRedBlackTree; + + friend class impl::IntrusiveRedBlackTreeImpl; + + static constexpr ALWAYS_INLINE IntrusiveRedBlackTreeNode *GetNode(Derived *parent) { + return std::addressof(parent->*Member); + } + + static constexpr ALWAYS_INLINE IntrusiveRedBlackTreeNode const *GetNode(Derived const *parent) { + return std::addressof(parent->*Member); + } + + static ALWAYS_INLINE Derived *GetParent(IntrusiveRedBlackTreeNode *node) { + return util::GetParentPointer<Member, Derived>(node); + } + + static ALWAYS_INLINE Derived const *GetParent(IntrusiveRedBlackTreeNode const *node) { + return util::GetParentPointer<Member, Derived>(node); + } + private: + static_assert(util::IsAligned(util::impl::OffsetOf<Member, Derived>::value, alignof(void *))); + }; + + template<auto T, class Derived = util::impl::GetParentType<T>> + class IntrusiveRedBlackTreeMemberTraitsDeferredAssert; + + template<class Parent, IntrusiveRedBlackTreeNode Parent::*Member, class Derived> + class IntrusiveRedBlackTreeMemberTraitsDeferredAssert<Member, Derived> { + public: + template<class Comparator> + using TreeType = IntrusiveRedBlackTree<Derived, IntrusiveRedBlackTreeMemberTraitsDeferredAssert, Comparator>; + using TreeTypeImpl = impl::IntrusiveRedBlackTreeImpl; + + static constexpr bool IsValid() { + return util::IsAligned(util::impl::OffsetOf<Member, Derived>::value, alignof(void *)); + } + private: + template<class, class, class> + friend class IntrusiveRedBlackTree; + + friend class impl::IntrusiveRedBlackTreeImpl; + + static constexpr ALWAYS_INLINE IntrusiveRedBlackTreeNode *GetNode(Derived *parent) { + return std::addressof(parent->*Member); + } + + static constexpr ALWAYS_INLINE IntrusiveRedBlackTreeNode const *GetNode(Derived const *parent) { + return std::addressof(parent->*Member); + } + + static ALWAYS_INLINE Derived *GetParent(IntrusiveRedBlackTreeNode *node) { + return util::GetParentPointer<Member, Derived>(node); + } + + static ALWAYS_INLINE Derived const *GetParent(IntrusiveRedBlackTreeNode const *node) { + return util::GetParentPointer<Member, Derived>(node); + } + }; + + template<class Derived> + class alignas(void *) IntrusiveRedBlackTreeBaseNode : public IntrusiveRedBlackTreeNode { + public: + using IntrusiveRedBlackTreeNode::IntrusiveRedBlackTreeNode; + + constexpr ALWAYS_INLINE Derived *GetPrev() { return static_cast< Derived *>(static_cast< IntrusiveRedBlackTreeBaseNode *>(impl::IntrusiveRedBlackTreeImpl::GetPrev(this))); } + constexpr ALWAYS_INLINE const Derived *GetPrev() const { return static_cast<const Derived *>(static_cast<const IntrusiveRedBlackTreeBaseNode *>(impl::IntrusiveRedBlackTreeImpl::GetPrev(this))); } + + constexpr ALWAYS_INLINE Derived *GetNext() { return static_cast< Derived *>(static_cast< IntrusiveRedBlackTreeBaseNode *>(impl::IntrusiveRedBlackTreeImpl::GetNext(this))); } + constexpr ALWAYS_INLINE const Derived *GetNext() const { return static_cast<const Derived *>(static_cast<const IntrusiveRedBlackTreeBaseNode *>(impl::IntrusiveRedBlackTreeImpl::GetNext(this))); } + }; + + template<class Derived> + class IntrusiveRedBlackTreeBaseTraits { + public: + template<class Comparator> + using TreeType = IntrusiveRedBlackTree<Derived, IntrusiveRedBlackTreeBaseTraits, Comparator>; + using TreeTypeImpl = impl::IntrusiveRedBlackTreeImpl; + private: + template<class, class, class> + friend class IntrusiveRedBlackTree; + + friend class impl::IntrusiveRedBlackTreeImpl; + + static constexpr ALWAYS_INLINE IntrusiveRedBlackTreeNode *GetNode(Derived *parent) { + return static_cast<IntrusiveRedBlackTreeNode *>(static_cast<IntrusiveRedBlackTreeBaseNode<Derived> *>(parent)); + } + + static constexpr ALWAYS_INLINE IntrusiveRedBlackTreeNode const *GetNode(Derived const *parent) { + return static_cast<const IntrusiveRedBlackTreeNode *>(static_cast<const IntrusiveRedBlackTreeBaseNode<Derived> *>(parent)); + } + + static constexpr ALWAYS_INLINE Derived *GetParent(IntrusiveRedBlackTreeNode *node) { + return static_cast<Derived *>(static_cast<IntrusiveRedBlackTreeBaseNode<Derived> *>(node)); + } + + static constexpr ALWAYS_INLINE Derived const *GetParent(IntrusiveRedBlackTreeNode const *node) { + return static_cast<const Derived *>(static_cast<const IntrusiveRedBlackTreeBaseNode<Derived> *>(node)); + } + }; + + AMS_PRAGMA_END_OPTIMIZE() + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_mutex_utils.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_mutex_utils.hpp new file mode 100644 index 00000000..3fea11bf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_mutex_utils.hpp @@ -0,0 +1,141 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + template<typename _Mutex> + class unique_lock { + NON_COPYABLE(unique_lock); + public: + using mutex_type = _Mutex; + private: + mutex_type *m_mutex; + bool m_owns; + public: + unique_lock() noexcept : m_mutex(nullptr), m_owns(false) { /* ... */ } + + explicit unique_lock(mutex_type &m) noexcept : m_mutex(std::addressof(m)), m_owns(false) { + this->lock(); + m_owns = true; + } + + unique_lock(mutex_type &m, std::defer_lock_t) noexcept : m_mutex(std::addressof(m)), m_owns(false) { /* ... */ } + unique_lock(mutex_type &m, std::try_to_lock_t) noexcept : m_mutex(std::addressof(m)), m_owns(m_mutex->try_lock()) { /* ... */ } + unique_lock(mutex_type &m, std::adopt_lock_t) noexcept : m_mutex(std::addressof(m)), m_owns(true) { /* ... */ } + + template<typename _Clock, typename _Duration> + unique_lock(mutex_type &m, const std::chrono::time_point<_Clock, _Duration> &time) noexcept : m_mutex(std::addressof(m)), m_owns(m_mutex->try_lock_until(time)) { /* ... */ } + + template<typename _Rep, typename _Period> + unique_lock(mutex_type &m, const std::chrono::duration<_Rep, _Period> &time) noexcept : m_mutex(std::addressof(m)), m_owns(m_mutex->try_lock_for(time)) { /* ... */ } + + ~unique_lock() noexcept { + if (m_owns) { + this->unlock(); + } + } + + unique_lock(unique_lock &&rhs) noexcept : m_mutex(rhs.m_mutex), m_owns(rhs.m_owns) { + rhs.m_mutex = nullptr; + rhs.m_owns = false; + } + + unique_lock &operator=(unique_lock &&rhs) noexcept { + if (m_owns) { + this->unlock(); + } + + unique_lock(std::move(rhs)).swap(*this); + + rhs.m_mutex = nullptr; + rhs.m_owns = false; + + return *this; + } + + void lock() noexcept { + AMS_ABORT_UNLESS(m_mutex); + AMS_ABORT_UNLESS(!m_owns); + + m_mutex->lock(); + m_owns = true; + } + + bool try_lock() noexcept { + AMS_ABORT_UNLESS(m_mutex); + AMS_ABORT_UNLESS(!m_owns); + + m_owns = m_mutex->try_lock(); + return m_owns; + } + + template<typename _Clock, typename _Duration> + bool try_lock_until(const std::chrono::time_point<_Clock, _Duration> &time) noexcept { + AMS_ABORT_UNLESS(m_mutex); + AMS_ABORT_UNLESS(!m_owns); + m_owns = m_mutex->try_lock_until(time); + return m_owns; + } + + template<typename _Rep, typename _Period> + bool try_lock_for(const std::chrono::duration<_Rep, _Period> &time) noexcept { + AMS_ABORT_UNLESS(m_mutex); + AMS_ABORT_UNLESS(!m_owns); + m_owns = m_mutex->try_lock_for(time); + return m_owns; + } + + + void unlock() noexcept { + AMS_ABORT_UNLESS(m_owns); + if (m_mutex) { + m_mutex->unlock(); + m_owns = false; + } + } + + void swap(unique_lock &rhs) noexcept { + std::swap(m_mutex, rhs.m_mutex); + std::swap(m_owns, rhs.m_owns); + } + + mutex_type *release() noexcept { + mutex_type *ret = m_mutex; + m_mutex = nullptr; + m_owns = false; + return ret; + } + + bool owns_lock() const noexcept { + return m_owns; + } + + explicit operator bool() const noexcept { + return this->owns_lock(); + } + + mutex_type *mutex() const noexcept { + return m_mutex; + } + }; + + template<typename _Mutex> + inline void swap(unique_lock<_Mutex> &lhs, unique_lock<_Mutex> &rhs) noexcept { return lhs.swap(rhs); } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_optional.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_optional.hpp new file mode 100644 index 00000000..01f8ad3a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_optional.hpp @@ -0,0 +1,719 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util/util_in_place.hpp> +#include <vapours/util/impl/util_enable_copy_move.hpp> + +namespace ams::util { + + namespace impl { + + class NulloptHelper { + public: + template<typename T> + static consteval T CreateInstance() { + return T(T::ConstructionArgument::Token); + } + }; + + template<typename F> + struct OptionalFunction { + F &m_f; + }; + + } + + struct nullopt_t { + private: + friend class impl::NulloptHelper; + + enum class ConstructionArgument { + Token, + }; + public: + consteval nullopt_t(ConstructionArgument) { /* ... */ } + }; + + constexpr inline nullopt_t nullopt = impl::NulloptHelper::CreateInstance<nullopt_t>(); + + namespace impl { + + template<typename T> + struct OptionalPayloadBase { + using StoredType = typename std::remove_const<T>::type; + + struct EmptyType{}; + + template<typename U, bool = std::is_trivially_destructible<U>::value> + union StorageType { + EmptyType m_empty; + U m_value; + + constexpr ALWAYS_INLINE StorageType() : m_empty() { /* ... */ } + + template<typename... Args> + constexpr ALWAYS_INLINE StorageType(in_place_t, Args &&... args) : m_value(std::forward<Args>(args)...) { /* ... */ } + + template<typename V, typename... Args> + constexpr ALWAYS_INLINE StorageType(std::initializer_list<V> il, Args &&... args) : m_value(il, std::forward<Args>(args)...) { /* ... */ } + + template<typename F, typename Arg> + constexpr ALWAYS_INLINE StorageType(OptionalFunction<F> f, Arg &&arg) : m_value(std::invoke(std::forward<F>(f.m_f), std::forward<Arg>(arg))) { /* ... */ } + }; + + template<typename U> + union StorageType<U, false> { + EmptyType m_empty; + U m_value; + + constexpr ALWAYS_INLINE StorageType() : m_empty() { /* ... */ } + + template<typename... Args> + constexpr ALWAYS_INLINE StorageType(in_place_t, Args &&... args) : m_value(std::forward<Args>(args)...) { /* ... */ } + + template<typename V, typename... Args> + constexpr ALWAYS_INLINE StorageType(std::initializer_list<V> il, Args &&... args) : m_value(il, std::forward<Args>(args)...) { /* ... */ } + + template<typename F, typename Arg> + constexpr ALWAYS_INLINE StorageType(OptionalFunction<F> f, Arg &&arg) : m_value(std::invoke(std::forward<F>(f.m_f), std::forward<Arg>(arg))) { /* ... */ } + + constexpr ALWAYS_INLINE ~StorageType() { /* ... */ } + }; + + StorageType<StoredType> m_payload; + bool m_engaged = false; + + constexpr OptionalPayloadBase() = default; + constexpr ~OptionalPayloadBase() = default; + + template<typename... Args> + constexpr OptionalPayloadBase(in_place_t tag, Args &&... args) : m_payload(tag, std::forward<Args>(args)...), m_engaged(true) { /* ... */ } + + template<typename U, typename... Args> + constexpr OptionalPayloadBase(std::initializer_list<U> il, Args &&... args) : m_payload(il, std::forward<Args>(args)...), m_engaged(true) { /* ... */ } + + constexpr OptionalPayloadBase(bool engaged, const OptionalPayloadBase &rhs) { AMS_UNUSED(engaged); if (rhs.m_engaged) { this->Construct(rhs.Get()); } } + constexpr OptionalPayloadBase(bool engaged, OptionalPayloadBase &&rhs) { AMS_UNUSED(engaged); if (rhs.m_engaged) { this->Construct(std::move(rhs.Get())); } } + + constexpr OptionalPayloadBase(const OptionalPayloadBase &) = default; + constexpr OptionalPayloadBase(OptionalPayloadBase &&) = default; + + constexpr OptionalPayloadBase &operator=(const OptionalPayloadBase &) = default; + constexpr OptionalPayloadBase &operator=(OptionalPayloadBase &&) = default; + + constexpr void CopyAssign(const OptionalPayloadBase &rhs) { + if (m_engaged && rhs.m_engaged) { + this->Get() = rhs.Get(); + } else if (rhs.m_engaged) { + this->Construct(rhs.Get()); + } else { + this->Reset(); + } + } + + constexpr void MoveAssign(OptionalPayloadBase &&rhs) { + if (m_engaged && rhs.m_engaged) { + this->Get() = std::move(rhs.Get()); + } else if (rhs.m_engaged) { + this->Construct(std::move(rhs.Get())); + } else { + this->Reset(); + } + } + + template<typename... Args> + constexpr void Construct(Args &&... args) { + std::construct_at(std::addressof(m_payload.m_value), std::forward<Args>(args)...); + m_engaged = true; + } + + constexpr void Destroy() { + m_engaged = false; + std::destroy_at(std::addressof(m_payload.m_value)); + } + + template<typename F, typename Arg> + constexpr void Apply(impl::OptionalFunction<F> f, Arg &&arg) { + std::construct_at(std::addressof(m_payload), f, std::forward<Arg>(arg)); + m_engaged = true; + } + + constexpr ALWAYS_INLINE T &Get() { return m_payload.m_value; } + constexpr ALWAYS_INLINE const T &Get() const { return m_payload.m_value; } + + constexpr void Reset() { + if (m_engaged) { + this->Destroy(); + } + } + }; + + template<typename T, bool = std::is_trivially_destructible<T>::value, bool = std::is_trivially_copy_assignable<T>::value && std::is_trivially_copy_constructible<T>::value, bool = std::is_trivially_move_assignable<T>::value && std::is_trivially_move_constructible<T>::value> + struct OptionalPayload; + + template<typename T> + struct OptionalPayload<T, true, true, true> : OptionalPayloadBase<T> { + using OptionalPayloadBase<T>::OptionalPayloadBase; + + constexpr OptionalPayload() = default; + }; + + template<typename T> + struct OptionalPayload<T, true, false, true> : OptionalPayloadBase<T> { + using OptionalPayloadBase<T>::OptionalPayloadBase; + + constexpr OptionalPayload() = default; + constexpr ~OptionalPayload() = default; + constexpr OptionalPayload(const OptionalPayload &) = default; + constexpr OptionalPayload(OptionalPayload &&) = default; + constexpr OptionalPayload& operator=(OptionalPayload &&) = default; + + constexpr OptionalPayload &operator=(const OptionalPayload &rhs) { + this->CopyAssign(rhs); + return *this; + } + }; + + template<typename T> + struct OptionalPayload<T, true, true, false> : OptionalPayloadBase<T> { + using OptionalPayloadBase<T>::OptionalPayloadBase; + + constexpr OptionalPayload() = default; + constexpr ~OptionalPayload() = default; + constexpr OptionalPayload(const OptionalPayload &) = default; + constexpr OptionalPayload(OptionalPayload &&) = default; + constexpr OptionalPayload& operator=(const OptionalPayload &) = default; + + constexpr OptionalPayload &operator=(OptionalPayload &&rhs) { + this->MoveAssign(std::move(rhs)); + return *this; + } + }; + + template<typename T> + struct OptionalPayload<T, true, false, false> : OptionalPayloadBase<T> { + using OptionalPayloadBase<T>::OptionalPayloadBase; + + constexpr OptionalPayload() = default; + constexpr ~OptionalPayload() = default; + constexpr OptionalPayload(const OptionalPayload &) = default; + constexpr OptionalPayload(OptionalPayload &&) = default; + + constexpr OptionalPayload &operator=(const OptionalPayload &rhs) { + this->CopyAssign(rhs); + return *this; + } + + constexpr OptionalPayload &operator=(OptionalPayload &&rhs) { + this->MoveAssign(std::move(rhs)); + return *this; + } + }; + + template<typename T, bool TrivialCopy, bool TrivialMove> + struct OptionalPayload<T, false, TrivialCopy, TrivialMove> : OptionalPayload<T, true, TrivialCopy, TrivialMove> { + using OptionalPayload<T, true, TrivialCopy, TrivialMove>::OptionalPayload; + + constexpr OptionalPayload() = default; + constexpr OptionalPayload(const OptionalPayload &) = default; + constexpr OptionalPayload(OptionalPayload &&) = default; + constexpr OptionalPayload& operator=(const OptionalPayload &) = default; + constexpr OptionalPayload& operator=(OptionalPayload &&) = default; + + constexpr ~OptionalPayload() { this->Reset(); } + }; + + template<typename T, typename Derived> + class OptionalBaseImpl { + protected: + using StoredType = std::remove_const_t<T>; + + template<typename... Args> + constexpr void ConstructImpl(Args &&... args) { static_cast<Derived *>(this)->m_payload.Construct(std::forward<Args>(args)...); } + + constexpr void DestructImpl() { static_cast<Derived *>(this)->m_payload.Destroy(); } + + constexpr void ResetImpl() { static_cast<Derived *>(this)->m_payload.Reset(); } + + template<typename F, typename Arg> + constexpr void ApplyImpl(OptionalFunction<F> f, Arg &&arg) { + static_cast<Derived *>(this)->m_payload.Apply(f, std::forward<Arg>(arg)); + } + + constexpr ALWAYS_INLINE bool IsEngagedImpl() const { return static_cast<const Derived *>(this)->m_payload.m_engaged; } + + constexpr ALWAYS_INLINE T &GetImpl() { return static_cast<Derived *>(this)->m_payload.Get(); } + constexpr ALWAYS_INLINE const T &GetImpl() const { return static_cast<const Derived *>(this)->m_payload.Get(); } + }; + + template<typename T, bool = std::is_trivially_copy_constructible<T>::value, bool = std::is_trivially_move_constructible<T>::value> + struct OptionalBase : OptionalBaseImpl<T, OptionalBase<T>> { + OptionalPayload<T> m_payload; + + constexpr OptionalBase() = default; + + template<typename... Args, std::enable_if_t<std::is_constructible<T, Args...>::value, bool> = false> + constexpr explicit OptionalBase(in_place_t, Args &&... args) : m_payload(in_place, std::forward<Args>(args)...) { /* ... */ } + + template<typename U, typename... Args, std::enable_if_t<std::is_constructible<T, std::initializer_list<U> &, Args...>::value, bool> = false> + constexpr explicit OptionalBase(in_place_t, std::initializer_list<U> il, Args &&... args) : m_payload(in_place, il, std::forward<Args>(args)...) { /* ... */ } + + constexpr OptionalBase(const OptionalBase &rhs) : m_payload(rhs.m_payload.m_engaged, rhs.m_payload) { /* ... */ } + constexpr OptionalBase(OptionalBase &&rhs) : m_payload(rhs.m_payload.m_engaged, std::move(rhs.m_payload)) { /* ... */ } + + constexpr OptionalBase &operator=(const OptionalBase &) = default; + constexpr OptionalBase &operator=(OptionalBase &&) = default; + }; + + template<typename T> + struct OptionalBase<T, false, true> : OptionalBaseImpl<T, OptionalBase<T>> { + OptionalPayload<T> m_payload; + + constexpr OptionalBase() = default; + + template<typename... Args, std::enable_if_t<std::is_constructible<T, Args...>::value, bool> = false> + constexpr explicit OptionalBase(in_place_t, Args &&... args) : m_payload(in_place, std::forward<Args>(args)...) { /* ... */ } + + template<typename U, typename... Args, std::enable_if_t<std::is_constructible<T, std::initializer_list<U> &, Args...>::value, bool> = false> + constexpr explicit OptionalBase(in_place_t, std::initializer_list<U> il, Args &&... args) : m_payload(in_place, il, std::forward<Args>(args)...) { /* ... */ } + + constexpr OptionalBase(const OptionalBase &rhs) : m_payload(rhs.m_payload.m_engaged, rhs.m_payload) { /* ... */ } + constexpr OptionalBase(OptionalBase &&rhs) = default; + + constexpr OptionalBase &operator=(const OptionalBase &) = default; + constexpr OptionalBase &operator=(OptionalBase &&) = default; + }; + + template<typename T> + struct OptionalBase<T, true, false> : OptionalBaseImpl<T, OptionalBase<T>> { + OptionalPayload<T> m_payload; + + constexpr OptionalBase() = default; + + template<typename... Args, std::enable_if_t<std::is_constructible<T, Args...>::value, bool> = false> + constexpr explicit OptionalBase(in_place_t, Args &&... args) : m_payload(in_place, std::forward<Args>(args)...) { /* ... */ } + + template<typename U, typename... Args, std::enable_if_t<std::is_constructible<T, std::initializer_list<U> &, Args...>::value, bool> = false> + constexpr explicit OptionalBase(in_place_t, std::initializer_list<U> il, Args &&... args) : m_payload(in_place, il, std::forward<Args>(args)...) { /* ... */ } + + constexpr OptionalBase(const OptionalBase &rhs) = default; + constexpr OptionalBase(OptionalBase &&rhs) : m_payload(rhs.m_payload.m_engaged, std::move(rhs.m_payload)) { /* ... */ } + + constexpr OptionalBase &operator=(const OptionalBase &) = default; + constexpr OptionalBase &operator=(OptionalBase &&) = default; + }; + + template<typename T> + struct OptionalBase<T, true, true> : OptionalBaseImpl<T, OptionalBase<T>> { + OptionalPayload<T> m_payload; + + constexpr OptionalBase() = default; + + template<typename... Args, std::enable_if_t<std::is_constructible<T, Args...>::value, bool> = false> + constexpr explicit OptionalBase(in_place_t, Args &&... args) : m_payload(in_place, std::forward<Args>(args)...) { /* ... */ } + + template<typename U, typename... Args, std::enable_if_t<std::is_constructible<T, std::initializer_list<U> &, Args...>::value, bool> = false> + constexpr explicit OptionalBase(in_place_t, std::initializer_list<U> il, Args &&... args) : m_payload(in_place, il, std::forward<Args>(args)...) { /* ... */ } + + constexpr OptionalBase(const OptionalBase &rhs) = default; + constexpr OptionalBase(OptionalBase &&rhs) = default; + + constexpr OptionalBase &operator=(const OptionalBase &) = default; + constexpr OptionalBase &operator=(OptionalBase &&) = default; + }; + + } + + template<typename T> + class optional; + + namespace impl { + + template<typename T, typename U> + constexpr inline bool ConvertsFromOptional = std::is_constructible<T, const optional<U> &>::value || + std::is_constructible<T, optional<U> &>::value || + std::is_constructible<T, const optional<U> &&>::value || + std::is_constructible<T, optional<U> &&>::value || + std::is_convertible<const optional<U> &, T>::value || + std::is_convertible<optional<U> &, T>::value || + std::is_convertible<const optional<U> &&, T>::value || + std::is_convertible<optional<U> &&, T>::value; + + template<typename T, typename U> + constexpr inline bool AssignsFromOptional = std::is_assignable<T &, const optional<U> &>::value || + std::is_assignable<T &, optional<U> &>::value || + std::is_assignable<T &, const optional<U> &&>::value || + std::is_assignable<T &, optional<U> &&>::value; + template<typename T> + constexpr inline bool IsOptional = false; + + template<typename T> + constexpr inline bool IsOptional<optional<T>> = true; + + } + + template<typename T> + class optional : private impl::OptionalBase<T>, private impl::EnableCopyMove<std::is_copy_constructible<T>::value, std::is_copy_constructible<T>::value && std::is_copy_assignable<T>::value, std::is_move_constructible<T>::value, std::is_move_constructible<T>::value && std::is_move_assignable<T>::value, optional<T>> { + static_assert(!std::is_same<std::remove_cv_t<T>, ::ams::util::nullopt_t>::value); + static_assert(!std::is_same<std::remove_cv_t<T>, ::ams::util::in_place_t>::value); + static_assert(!std::is_reference<T>::value); + private: + using Base = impl::OptionalBase<T>; + + template<typename U> static constexpr inline bool IsNotSelf = !std::is_same<optional, std::remove_cvref_t<U>>::value; + template<typename U> static constexpr inline bool IsNotTag = !std::is_same<::ams::util::in_place_t, std::remove_cvref_t<U>>::value && !std::is_same<::std::in_place_t, std::remove_cvref_t<U>>::value; + + template<bool... Cond> + using Requires = std::enable_if_t<(Cond && ...), bool>; + public: + using value_type = T; + public: + constexpr optional() { /* ... */ } + constexpr optional(nullopt_t) { /* ... */ } + + template<typename U = T, Requires<IsNotSelf<U>, IsNotTag<U>, std::is_constructible<T, U>::value, std::is_convertible<U, T>::value> = true> + constexpr optional(U &&u) : Base(::ams::util::in_place, std::forward<U>(u)) { /* ... */ } + + template<typename U = T, Requires<IsNotSelf<U>, IsNotTag<U>, std::is_constructible<T, U>::value, !std::is_convertible<U, T>::value> = false> + constexpr explicit optional(U &&u) : Base(::ams::util::in_place, std::forward<U>(u)) { /* ... */ } + + template<typename U, Requires<!std::is_same<T, U>::value, std::is_constructible<T, const U &>::value, std::is_convertible<const U &, T>::value, !impl::ConvertsFromOptional<T, U>> = true> + constexpr optional(const optional<U> &u) { + if (u) { + this->emplace(*u); + } + } + + template<typename U, Requires<!std::is_same<T, U>::value, std::is_constructible<T, const U &>::value, !std::is_convertible<const U &, T>::value, !impl::ConvertsFromOptional<T, U>> = false> + constexpr explicit optional(const optional<U> &u) { + if (u) { + this->emplace(*u); + } + } + + template<typename U, Requires<!std::is_same<T, U>::value, std::is_constructible<T, const U &>::value, std::is_convertible<const U &, T>::value, !impl::ConvertsFromOptional<T, U>> = true> + constexpr optional(optional<U> &&u) { + if (u) { + this->emplace(std::move(*u)); + } + } + + template<typename U, Requires<!std::is_same<T, U>::value, std::is_constructible<T, const U &>::value, !std::is_convertible<const U &, T>::value, !impl::ConvertsFromOptional<T, U>> = false> + constexpr explicit optional(optional<U> &&u) { + if (u) { + this->emplace(std::move(*u)); + } + } + + template<typename... Args, Requires<std::is_constructible<T, Args...>::value> = false> + constexpr explicit optional(in_place_t, Args &&... args) : Base(::ams::util::in_place, std::forward<Args>(args)...) { /* ... */ } + + template<typename U, typename... Args, Requires<std::is_constructible<T, std::initializer_list<U> &, Args...>::value> = false> + constexpr explicit optional(in_place_t, std::initializer_list<U> il, Args &&... args) : Base(::ams::util::in_place, il, std::forward<Args>(args)...) { /* ... */ } + + constexpr optional &operator=(nullopt_t) { this->ResetImpl(); return *this; } + + template<typename U = T> + constexpr std::enable_if_t<IsNotSelf<U> && !(std::is_scalar<T>::value && std::is_same<T, std::decay_t<U>>::value) && std::is_constructible<T, U>::value && std::is_assignable<T &, U>::value, + optional &> + operator =(U &&u) { + if (this->IsEngagedImpl()) { + this->GetImpl() = std::forward<U>(u); + } else { + this->ConstructImpl(std::forward<U>(u)); + } + + return *this; + } + + template<typename U> + constexpr std::enable_if_t<!std::is_same<T, U>::value && std::is_constructible<T, const U &>::value && std::is_assignable<T &, const U &>::value && !impl::ConvertsFromOptional<T, U> && !impl::AssignsFromOptional<T, U>, + optional &> + operator =(const optional<U> &u) { + if (u) { + if (this->IsEngagedImpl()) { + this->GetImpl() = *u; + } else { + this->ConstructImpl(*u); + } + } else { + this->ResetImpl(); + } + + return *this; + } + + template<typename U> + constexpr std::enable_if_t<!std::is_same<T, U>::value && std::is_constructible<T, U>::value && std::is_assignable<T &, U>::value && !impl::ConvertsFromOptional<T, U> && !impl::AssignsFromOptional<T, U>, + optional &> + operator =(optional<U> &&u) { + if (u) { + if (this->IsEngagedImpl()) { + this->GetImpl() = std::move(*u); + } else { + this->ConstructImpl(std::move(*u)); + } + } else { + this->ResetImpl(); + } + + return *this; + } + + template<typename... Args> + constexpr std::enable_if_t<std::is_constructible<T, Args...>::value, T &> emplace(Args &&... args) { + this->ResetImpl(); + this->ConstructImpl(std::forward<Args>(args)...); + return this->GetImpl(); + } + + template<typename U, typename... Args> + constexpr std::enable_if_t<std::is_constructible<T, std::initializer_list<U> &, Args...>::value, T &> emplace(std::initializer_list<U> il, Args &&... args) { + this->ResetImpl(); + this->ConstructImpl(il, std::forward<Args>(args)...); + return this->GetImpl(); + } + + constexpr void swap(optional &rhs) { + if (this->IsEngagedImpl() && rhs.IsEngagedImpl()) { + std::swap(this->GetImpl(), rhs.GetImpl()); + } else if (this->IsEngagedImpl()) { + rhs.ConstructImpl(std::move(this->GetImpl())); + this->DestructImpl(); + } else if (rhs.IsEngagedImpl()) { + this->ConstructImpl(std::move(rhs.GetImpl())); + rhs.DestructImpl(); + } + } + + constexpr ALWAYS_INLINE const T *operator ->() const { return std::addressof(this->GetImpl()); } + constexpr ALWAYS_INLINE T *operator ->() { return std::addressof(this->GetImpl()); } + + constexpr ALWAYS_INLINE const T &operator *() const & { return this->GetImpl(); } + constexpr ALWAYS_INLINE T &operator *() & { return this->GetImpl(); } + + constexpr ALWAYS_INLINE const T &&operator *() const && { return std::move(this->GetImpl()); } + constexpr ALWAYS_INLINE T &&operator *() && { return std::move(this->GetImpl()); } + + constexpr ALWAYS_INLINE explicit operator bool() const { return this->IsEngagedImpl(); } + constexpr ALWAYS_INLINE bool has_value() const { return this->IsEngagedImpl(); } + + constexpr ALWAYS_INLINE const T &value() const & { /* AMS_ASSERT(this->IsEngagedImpl()); */ return this->GetImpl(); } + constexpr ALWAYS_INLINE T &value() & { /* AMS_ASSERT(this->IsEngagedImpl()); */ return this->GetImpl(); } + + constexpr ALWAYS_INLINE const T &&value() const && { /* AMS_ASSERT(this->IsEngagedImpl()); */ return std::move(this->GetImpl()); } + constexpr ALWAYS_INLINE T &&value() && { /* AMS_ASSERT(this->IsEngagedImpl()); */ return std::move(this->GetImpl()); } + + template<typename U> + constexpr T value_or(U &&u) const & { + static_assert(std::is_copy_constructible<T>::value); + static_assert(std::is_convertible<U &&, T>::value); + + return this->IsEngagedImpl() ? this->GetImpl() : static_cast<T>(std::forward<U>(u)); + } + + template<typename U> + constexpr T value_or(U &&u) && { + static_assert(std::is_move_constructible<T>::value); + static_assert(std::is_convertible<U &&, T>::value); + + return this->IsEngagedImpl() ? std::move(this->GetImpl()) : static_cast<T>(std::forward<U>(u)); + } + + template<typename F> + constexpr auto and_then(F &&f) & { + using U = typename std::remove_cvref<typename std::invoke_result<F, T &>::type>::type; + static_assert(impl::IsOptional<typename std::remove_cvref<U>::type>); + return this->IsEngagedImpl() ? std::invoke(std::forward<F>(f), **this) : U{}; + } + + template<typename F> + constexpr auto and_then(F &&f) const & { + using U = typename std::remove_cvref<typename std::invoke_result<F, const T &>::type>::type; + static_assert(impl::IsOptional<typename std::remove_cvref<U>::type>); + return this->IsEngagedImpl() ? std::invoke(std::forward<F>(f), **this) : U{}; + } + + template<typename F> + constexpr auto and_then(F &&f) && { + using U = typename std::remove_cvref<typename std::invoke_result<F, T>::type>::type; + static_assert(impl::IsOptional<typename std::remove_cvref<U>::type>); + return this->IsEngagedImpl() ? std::invoke(std::forward<F>(f), std::move(**this)) : U{}; + } + + template<typename F> + constexpr auto and_then(F &&f) const && { + using U = typename std::remove_cvref<typename std::invoke_result<F, const T>::type>::type; + static_assert(impl::IsOptional<typename std::remove_cvref<U>::type>); + return this->IsEngagedImpl() ? std::invoke(std::forward<F>(f), std::move(**this)) : U{}; + } + + template<typename F> + constexpr auto transform(F &&f) & { + using U = typename std::remove_cvref<typename std::invoke_result<F, T &>::type>::type; + return this->IsEngagedImpl() ? optional<U>(impl::OptionalFunction<F>{f}, **this) : optional<U>{}; + } + + template<typename F> + constexpr auto transform(F &&f) const & { + using U = typename std::remove_cvref<typename std::invoke_result<F, const T &>::type>::type; + return this->IsEngagedImpl() ? optional<U>(impl::OptionalFunction<F>{f}, **this) : optional<U>{}; + } + + template<typename F> + constexpr auto transform(F &&f) && { + using U = typename std::remove_cvref<typename std::invoke_result<F, T>::type>::type; + return this->IsEngagedImpl() ? optional<U>(impl::OptionalFunction<F>{f}, std::move(**this)) : optional<U>{}; + } + + template<typename F> + constexpr auto transform(F &&f) const && { + using U = typename std::remove_cvref<typename std::invoke_result<F, const T>::type>::type; + return this->IsEngagedImpl() ? optional<U>(impl::OptionalFunction<F>{f}, std::move(**this)) : optional<U>{}; + } + + template<typename F> requires std::invocable<F> && std::copy_constructible<T> + constexpr optional or_else(F &&f) const & { + using U = typename std::invoke_result<F>::type; + static_assert(std::same_as<typename std::remove_cvref_t<U>, optional>); + return this->IsEngagedImpl() ? *this : std::forward<F>(f)(); + } + + template<typename F> requires std::invocable<F> && std::move_constructible<T> + constexpr optional or_else(F &&f) && { + using U = typename std::invoke_result<F>::type; + static_assert(std::same_as<typename std::remove_cvref_t<U>, optional>); + return this->IsEngagedImpl() ? std::move(*this) : std::forward<F>(f)(); + } + + constexpr void reset() { this->ResetImpl(); } + private: + template<typename U> friend class optional; + + template<typename F, typename Arg> + constexpr explicit optional(impl::OptionalFunction<F> f, Arg &&arg) { + this->ApplyImpl(f, std::forward<Arg>(arg)); + } + }; + + namespace impl { + + template<typename T> using optional_relop_t = std::enable_if_t<std::is_convertible<T, bool>::value, bool>; + + template<typename T, typename U> using optional_eq_t = optional_relop_t<decltype(std::declval<const T &>() == std::declval<const U &>())>; + template<typename T, typename U> using optional_ne_t = optional_relop_t<decltype(std::declval<const T &>() != std::declval<const U &>())>; + template<typename T, typename U> using optional_le_t = optional_relop_t<decltype(std::declval<const T &>() <= std::declval<const U &>())>; + template<typename T, typename U> using optional_ge_t = optional_relop_t<decltype(std::declval<const T &>() >= std::declval<const U &>())>; + template<typename T, typename U> using optional_lt_t = optional_relop_t<decltype(std::declval<const T &>() < std::declval<const U &>())>; + template<typename T, typename U> using optional_gt_t = optional_relop_t<decltype(std::declval<const T &>() > std::declval<const U &>())>; + + } + + template<typename T, typename U> + constexpr inline impl::optional_eq_t<T, U> operator==(const optional<T> &lhs, const optional<U> &rhs) { return static_cast<bool>(lhs) == static_cast<bool>(rhs) && (!lhs || *lhs == *rhs); } + + template<typename T, typename U> + constexpr inline impl::optional_ne_t<T, U> operator!=(const optional<T> &lhs, const optional<U> &rhs) { return static_cast<bool>(lhs) != static_cast<bool>(rhs) || (static_cast<bool>(lhs) && *lhs != *rhs); } + + template<typename T, typename U> + constexpr inline impl::optional_lt_t<T, U> operator< (const optional<T> &lhs, const optional<U> &rhs) { return static_cast<bool>(rhs) && (!lhs || *lhs < *rhs); } + + template<typename T, typename U> + constexpr inline impl::optional_gt_t<T, U> operator> (const optional<T> &lhs, const optional<U> &rhs) { return static_cast<bool>(lhs) && (!rhs || *lhs > *rhs); } + + template<typename T, typename U> + constexpr inline impl::optional_le_t<T, U> operator<=(const optional<T> &lhs, const optional<U> &rhs) { return !lhs || (static_cast<bool>(rhs) && *lhs <= *rhs); } + + template<typename T, typename U> + constexpr inline impl::optional_ge_t<T, U> operator>=(const optional<T> &lhs, const optional<U> &rhs) { return !rhs || (static_cast<bool>(lhs) && *lhs >= *rhs); } + + template<typename T, std::three_way_comparable_with<T> U> + constexpr inline std::compare_three_way_result_t<T, U> operator <=>(const optional<T> &lhs, const optional<U> &rhs) { + return (lhs && rhs) ? *lhs <=> *rhs : static_cast<bool>(lhs) <=> static_cast<bool>(rhs); + } + + template<typename T> constexpr inline bool operator==(const optional<T> &lhs, nullopt_t) { return !lhs; } + + template<typename T> constexpr inline std::strong_ordering operator<=>(const optional<T> &lhs, nullopt_t) { return static_cast<bool>(lhs) <=> false; } + + template<typename T, typename U> + constexpr inline impl::optional_eq_t<T, U> operator==(const optional<T> &lhs, const U &rhs) { return lhs && *lhs == rhs; } + + template<typename T, typename U> + constexpr inline impl::optional_eq_t<U, T> operator==(const U &lhs, const optional<T> &rhs) { return rhs && lhs == *rhs; } + + template<typename T, typename U> + constexpr inline impl::optional_ne_t<T, U> operator!=(const optional<T> &lhs, const U &rhs) { return !lhs || *lhs != rhs; } + + template<typename T, typename U> + constexpr inline impl::optional_ne_t<U, T> operator!=(const U &lhs, const optional<T> &rhs) { return !rhs || lhs != *rhs; } + + template<typename T, typename U> + constexpr inline impl::optional_lt_t<T, U> operator< (const optional<T> &lhs, const U &rhs) { return !lhs || *lhs < rhs; } + + template<typename T, typename U> + constexpr inline impl::optional_lt_t<U, T> operator< (const U &lhs, const optional<T> &rhs) { return rhs && lhs < *rhs; } + + template<typename T, typename U> + constexpr inline impl::optional_gt_t<T, U> operator> (const optional<T> &lhs, const U &rhs) { return lhs && *lhs > rhs; } + + template<typename T, typename U> + constexpr inline impl::optional_gt_t<U, T> operator> (const U &lhs, const optional<T> &rhs) { return !rhs || lhs > *rhs; } + + template<typename T, typename U> + constexpr inline impl::optional_le_t<T, U> operator<=(const optional<T> &lhs, const U &rhs) { return !lhs || *lhs <= rhs; } + + template<typename T, typename U> + constexpr inline impl::optional_le_t<U, T> operator<=(const U &lhs, const optional<T> &rhs) { return rhs && lhs <= *rhs; } + + template<typename T, typename U> + constexpr inline impl::optional_ge_t<T, U> operator>=(const optional<T> &lhs, const U &rhs) { return lhs && *lhs >= rhs; } + + template<typename T, typename U> + constexpr inline impl::optional_ge_t<U, T> operator>=(const U &lhs, const optional<T> &rhs) { return !rhs || lhs >= *rhs; } + + template<typename T, typename U> requires (!impl::IsOptional<U>) && std::three_way_comparable_with<T, U> + constexpr inline std::compare_three_way_result_t<T, U> operator<=>(const optional<T> &lhs, const U &rhs) { + return static_cast<bool>(lhs) ? *lhs <=> rhs : std::strong_ordering::less; + } + + template<typename T> + constexpr inline std::enable_if_t<std::is_constructible<std::decay_t<T>, T>::value, optional<std::decay_t<T>>> make_optional(T && t) { return optional<std::decay_t<T>>{ std::forward<T>(t) }; } + + template<typename T, typename... Args> + constexpr inline std::enable_if_t<std::is_constructible<T, Args...>::value, optional<T>> make_optional(Args &&... args) { return optional<T>{ ::ams::util::in_place, std::forward<Args>(args)... }; } + + template<typename T, typename U, typename... Args> + constexpr inline std::enable_if_t<std::is_constructible<T, std::initializer_list<U> &, Args...>::value, optional<T>> make_optional(std::initializer_list<U> il, Args &&... args) { return optional<T>{ ::ams::util::in_place, il, std::forward<Args>(args)... }; } + + template<typename T> optional(T) -> optional<T>; + +} + +namespace std { + + template<typename T> + constexpr inline enable_if_t<is_move_constructible_v<T> && is_swappable_v<T>> swap(::ams::util::optional<T> &lhs, ::ams::util::optional<T> &rhs) noexcept { + lhs.swap(rhs); + } + + template<typename T> + constexpr inline enable_if_t<!(is_move_constructible_v<T> && is_swappable_v<T>)> swap(::ams::util::optional<T> &lhs, ::ams::util::optional<T> &rhs) = delete; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_overlap.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_overlap.hpp new file mode 100644 index 00000000..2c2c4ef2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_overlap.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + constexpr inline bool HasOverlap(uintptr_t addr0, size_t size0, uintptr_t addr1, size_t size1) { + if (addr0 <= addr1) { + return addr1 < addr0 + size0; + } else { + return addr0 < addr1 + size1; + } + } + + constexpr inline bool Contains(uintptr_t addr, size_t size, uintptr_t ptr) { + return (addr <= ptr) && (ptr < addr + size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_parent_of_member.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_parent_of_member.hpp new file mode 100644 index 00000000..029e9746 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_parent_of_member.hpp @@ -0,0 +1,207 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util/util_typed_storage.hpp> + +namespace ams::util { + + namespace impl { + + #define AMS_UTIL_OFFSET_OF_STANDARD_COMPLIANT 1 + + #if AMS_UTIL_OFFSET_OF_STANDARD_COMPLIANT + + template<std::ptrdiff_t Offset, typename P, typename M, auto Ptr> + consteval std::strong_ordering TestOffsetForOffsetOfImpl() { + #pragma pack(push, 1) + const union Union { + char c; + struct { + char padding[Offset]; + M members[1 + (sizeof(P) / std::max<size_t>(sizeof(M), 1))]; + }; + P p; + + constexpr Union() : c() { /* ... */ } + constexpr ~Union() { /* ... */ } + } U; + #pragma pack(pop) + + const M *target = std::addressof(U.p.*Ptr); + const M *guess = std::addressof(U.members[0]); + + /* NOTE: target == guess is definitely legal, target < guess is probably legal, definitely legal if Offset <= true offsetof. */ + /* <=> may or may not be legal, but it definitely seems to work. Evaluate again, if it breaks. */ + return guess <=> target; + + //if (guess == target) { + // return std::strong_ordering::equal; + //} else if (guess < target) { + // return std::strong_ordering::less; + //} else { + // return std::strong_ordering::greater; + //} + } + + template<std::ptrdiff_t Low, std::ptrdiff_t High, typename P, typename M, auto Ptr> + consteval std::ptrdiff_t OffsetOfImpl() { + static_assert(Low <= High); + + constexpr std::ptrdiff_t Guess = (Low + High) / 2; + constexpr auto Order = TestOffsetForOffsetOfImpl<Guess, P, M, Ptr>(); + + if constexpr (Order == std::strong_ordering::equal) { + return Guess; + } else if constexpr (Order == std::strong_ordering::less) { + return OffsetOfImpl<Guess + 1, High, P, M, Ptr>(); + } else { + static_assert(Order == std::strong_ordering::greater); + return OffsetOfImpl<Low, Guess - 1, P, M, Ptr>(); + } + } + + template<typename P, typename M, auto Ptr> + struct OffsetOfCalculator { + static constexpr const std::ptrdiff_t Value = OffsetOfImpl<0, sizeof(P), P, M, Ptr>(); + }; + + #else + + template<typename ParentType, typename MemberType, auto Ptr> + struct OffsetOfCalculator { + private: + static consteval std::ptrdiff_t Calculate() { + const union Union { + ParentType p; + char c; + + constexpr Union() : c() { /* ... */ } + constexpr ~Union() { /* ... */ } + } U; + + const auto *parent = std::addressof(U.p); + const auto *target = std::addressof(parent->*Ptr); + + return static_cast<const uint8_t *>(static_cast<const void *>(target)) - static_cast<const uint8_t *>(static_cast<const void *>(parent)); + } + public: + static constexpr const std::ptrdiff_t Value = Calculate(); + }; + + #endif + + template<typename T> + struct GetMemberPointerTraits; + + template<typename P, typename M> + struct GetMemberPointerTraits<M P::*> { + using Parent = P; + using Member = M; + }; + + template<auto MemberPtr> + using GetParentType = typename GetMemberPointerTraits<decltype(MemberPtr)>::Parent; + + template<auto MemberPtr> + using GetMemberType = typename GetMemberPointerTraits<decltype(MemberPtr)>::Member; + + template<auto MemberPtr, typename RealParentType = GetParentType<MemberPtr>> requires (std::derived_from<RealParentType, GetParentType<MemberPtr>> || std::same_as<RealParentType, GetParentType<MemberPtr>>) + struct OffsetOf : public std::integral_constant<std::ptrdiff_t, OffsetOfCalculator<RealParentType, GetMemberType<MemberPtr>, MemberPtr>::Value> {}; + + #if defined(ATMOSPHERE_COMPILER_CLANG) + template<typename ParentType, typename MemberType, auto Ptr> + struct OffsetOfCalculatorTheSadWayForClangSupport { + static ALWAYS_INLINE std::ptrdiff_t Calculate() { + const union Union { + ParentType p; + char c; + + Union() : c() { /* ... */ } + ~Union() { /* ... */ } + } U; + + const auto *parent = std::addressof(U.p); + const auto *target = std::addressof(parent->*Ptr); + + return static_cast<const uint8_t *>(static_cast<const void *>(target)) - static_cast<const uint8_t *>(static_cast<const void *>(parent)); + } + }; + #endif + + } + + template<auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>> + ALWAYS_INLINE RealParentType &GetParentReference(impl::GetMemberType<MemberPtr> *member) { + /* TODO: If clang resolves compiler-bugs in consteval (or if my std proposal makes it into a future C++ standard), we should go back to constexpr. */ + #if defined(ATMOSPHERE_COMPILER_CLANG) + const std::ptrdiff_t Offset = impl::OffsetOfCalculatorTheSadWayForClangSupport<RealParentType, impl::GetMemberType<MemberPtr>, MemberPtr>::Calculate(); + #else + constexpr std::ptrdiff_t Offset = impl::OffsetOf<MemberPtr, RealParentType>::value; + #endif + return *static_cast<RealParentType *>(static_cast<void *>(static_cast<uint8_t *>(static_cast<void *>(member)) - Offset)); + } + + template<auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>> + ALWAYS_INLINE RealParentType const &GetParentReference(impl::GetMemberType<MemberPtr> const *member) { + /* TODO: If clang resolves compiler-bugs in consteval (or if my std proposal makes it into a future C++ standard), we should go back to constexpr. */ + #if defined(ATMOSPHERE_COMPILER_CLANG) + const std::ptrdiff_t Offset = impl::OffsetOfCalculatorTheSadWayForClangSupport<RealParentType, impl::GetMemberType<MemberPtr>, MemberPtr>::Calculate(); + #else + constexpr std::ptrdiff_t Offset = impl::OffsetOf<MemberPtr, RealParentType>::value; + #endif + return *static_cast<const RealParentType *>(static_cast<const void *>(static_cast<const uint8_t *>(static_cast<const void *>(member)) - Offset)); + } + + template<auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>> + ALWAYS_INLINE RealParentType *GetParentPointer(impl::GetMemberType<MemberPtr> *member) { + return std::addressof(GetParentReference<MemberPtr, RealParentType>(member)); + } + + template<auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>> + ALWAYS_INLINE RealParentType const *GetParentPointer(impl::GetMemberType<MemberPtr> const *member) { + return std::addressof(GetParentReference<MemberPtr, RealParentType>(member)); + } + + template<auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>> + ALWAYS_INLINE RealParentType &GetParentReference(impl::GetMemberType<MemberPtr> &member) { + return GetParentReference<MemberPtr, RealParentType>(std::addressof(member)); + } + + template<auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>> + ALWAYS_INLINE RealParentType const &GetParentReference(impl::GetMemberType<MemberPtr> const &member) { + return GetParentReference<MemberPtr, RealParentType>(std::addressof(member)); + } + + template<auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>> + ALWAYS_INLINE RealParentType *GetParentPointer(impl::GetMemberType<MemberPtr> &member) { + return std::addressof(GetParentReference<MemberPtr, RealParentType>(member)); + } + + template<auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>> + ALWAYS_INLINE RealParentType const *GetParentPointer(impl::GetMemberType<MemberPtr> const &member) { + return std::addressof(GetParentReference<MemberPtr, RealParentType>(member)); + } + + + /* Defines, for use by other code. */ + #define AMS_OFFSETOF(parent, member) (__builtin_offsetof(parent, member)) + #define AMS_GET_PARENT_PTR(parent, member, _arg) (::ams::util::GetParentPointer<&parent::member, parent>(_arg)) + #define AMS_GET_PARENT_REF(parent, member, _arg) (::ams::util::GetParentReference<&parent::member, parent>(_arg)) + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_pointer_util.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_pointer_util.hpp new file mode 100644 index 00000000..bbbbd1d1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_pointer_util.hpp @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +namespace ams::util { + + namespace impl { + + template<typename T> + struct IsSharedPointerImpl : public std::false_type{}; + + template<typename T> + struct IsSharedPointerImpl<std::shared_ptr<T>> : public std::true_type{}; + + template<typename T> + struct IsUniquePointerImpl : public std::false_type{}; + + template<typename T> + struct IsUniquePointerImpl<std::unique_ptr<T>> : public std::true_type{}; + + template<typename T, typename U> + concept PointerToImpl = std::same_as<typename std::pointer_traits<T>::element_type, U>; + + } + + template<typename T> + concept IsRawPointer = std::is_pointer<T>::value; + + template<typename T> + concept IsSharedPointer = impl::IsSharedPointerImpl<T>::value; + + template<typename T> + concept IsUniquePointer = impl::IsUniquePointerImpl<T>::value; + + template<typename T> + concept IsSmartPointer = IsSharedPointer<T> || IsUniquePointer<T>; + + template<typename T> + concept IsRawOrSmartPointer = IsRawPointer<T> || IsSmartPointer<T>; + + template<typename T, typename U> + concept SmartPointerTo = IsSmartPointer<T> && impl::PointerToImpl<T, U>; + + template<typename T, typename U> + concept RawOrSmartPointerTo = IsRawOrSmartPointer<T> && impl::PointerToImpl<T, U>; + +} +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_range.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_range.hpp new file mode 100644 index 00000000..85b7c8af --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_range.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util::range { + + template<typename T, typename F> + constexpr bool any_of(T &&t, F &&f) { + return std::any_of(std::begin(t), std::end(t), std::forward<F>(f)); + } + + template<typename T, typename F> + constexpr bool all_of(T &&t, F &&f) { + return std::all_of(std::begin(t), std::end(t), std::forward<F>(f)); + } + + template<typename T, typename F> + constexpr bool none_of(T &&t, F &&f) { + return std::none_of(std::begin(t), std::end(t), std::forward<F>(f)); + } + + template<typename T, typename F> + constexpr auto find_if(T &&t, F &&f) { + return std::find_if(std::begin(t), std::end(t), std::forward<F>(f)); + } + + template<typename T, typename F> + constexpr auto for_each(T &&t, F &&f) { + return std::for_each(std::begin(t), std::end(t), std::forward<F>(f)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_scope_guard.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_scope_guard.hpp new file mode 100644 index 00000000..4f6494d6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_scope_guard.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +/* Scope guard logic lovingly taken from Andrei Alexandrescu's "Systemic Error Handling in C++" */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + namespace impl { + + template<class F> + class ScopeGuard { + NON_COPYABLE(ScopeGuard); + private: + F f; + bool active; + public: + constexpr ALWAYS_INLINE ScopeGuard(F f) : f(std::move(f)), active(true) { } + constexpr ALWAYS_INLINE ~ScopeGuard() { if (active) { f(); } } + constexpr ALWAYS_INLINE void Cancel() { active = false; } + + constexpr ALWAYS_INLINE ScopeGuard(ScopeGuard&& rhs) : f(std::move(rhs.f)), active(rhs.active) { + rhs.Cancel(); + } + + ScopeGuard &operator=(ScopeGuard&& rhs) = delete; + }; + + template<class F> + constexpr ALWAYS_INLINE ScopeGuard<F> MakeScopeGuard(F f) { + return ScopeGuard<F>(std::move(f)); + } + + enum class ScopeGuardOnExit {}; + + template <typename F> + constexpr ALWAYS_INLINE ScopeGuard<F> operator+(ScopeGuardOnExit, F&& f) { + return ScopeGuard<F>(std::forward<F>(f)); + } + + } + +} + +#define SCOPE_GUARD ::ams::util::impl::ScopeGuardOnExit() + [&]() ALWAYS_INLINE_LAMBDA +#define ON_SCOPE_EXIT auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_) = SCOPE_GUARD diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_size.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_size.hpp new file mode 100644 index 00000000..702b6f6b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_size.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + /* std::size() does not support zero-size C arrays. We're fixing that. */ + template<class C> + constexpr auto size(const C& c) -> decltype(c.size()) { + return std::size(c); + } + + template<class C> + constexpr std::size_t size(const C& c) { + if constexpr (sizeof(C) == 0) { + return 0; + } else { + return std::size(c); + } + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_specialization_of.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_specialization_of.hpp new file mode 100644 index 00000000..3a56b3f6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_specialization_of.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + template<class T, template <class...> class Template> + struct is_specialization_of : std::false_type{}; + + template<template <class...> class Template, class... Args> + struct is_specialization_of<Template<Args...>, Template> : std::true_type{}; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_string_util.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_string_util.hpp new file mode 100644 index 00000000..67f799dd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_string_util.hpp @@ -0,0 +1,162 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + template<typename T> + constexpr T ToLower(T c) { + return ('A' <= c && c <= 'Z') ? (c - 'A' + 'a') : c; + } + + template<typename T> + constexpr T ToUpper(T c) { + return ('a' <= c && c <= 'z') ? (c - 'a' + 'A') : c; + } + + template<typename T> + constexpr int Strncmp(const T *lhs, const T *rhs, int count) { + AMS_ASSERT(lhs != nullptr); + AMS_ASSERT(rhs != nullptr); + AMS_ABORT_UNLESS(count >= 0); + + if (count == 0) { + return 0; + } + + T l, r; + do { + l = *(lhs++); + r = *(rhs++); + } while (l && (l == r) && (--count)); + + return l - r; + } + + template<typename T> + constexpr int Strnicmp(const T *lhs, const T *rhs, int count) { + AMS_ASSERT(lhs != nullptr); + AMS_ASSERT(rhs != nullptr); + AMS_ABORT_UNLESS(count >= 0); + + if (count == 0) { + return 0; + } + + T l, r; + do { + l = ::ams::util::ToLower(*(lhs++)); + r = ::ams::util::ToLower(*(rhs++)); + } while (l && (l == r) && (--count)); + + return l - r; + } + + template<typename T> + constexpr int Strlcpy(T *dst, const T *src, int count) { + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(src != nullptr); + + const T *cur = src; + if (count > 0) { + while ((--count) && *cur) { + *(dst++) = *(cur++); + } + *dst = 0; + } + + while (*cur) { + cur++; + } + + return static_cast<int>(cur - src); + } + + template<typename T> + constexpr int Strlen(const T *str) { + AMS_ASSERT(str != nullptr); + + int length = 0; + while (*str++) { + ++length; + } + + return length; + } + + template<typename T> + constexpr int Strnlen(const T *str, int count) { + AMS_ASSERT(str != nullptr); + AMS_ASSERT(count >= 0); + + int length = 0; + while (count-- && *str++) { + ++length; + } + + return length; + } + + #if defined(ATMOSPHERE_IS_STRATOSPHERE) + ALWAYS_INLINE void *Memchr(void *s, int c, size_t n) { + return const_cast<void*>(::memchr(s, c, n)); + } + + ALWAYS_INLINE const void *Memchr(const void *s, int c, size_t n) { + return ::memchr(s, c, n); + } + + inline void *Memrchr(void *s, int c, size_t n) { + #if !(defined(__MINGW32__) || defined(__MINGW64__) || defined(ATMOSPHERE_OS_MACOS)) + return const_cast<void *>(::memrchr(s, c, n)); + #else + /* TODO: Optimized implementation? */ + if (AMS_LIKELY(n > 0)) { + const u8 *p = static_cast<const u8 *>(s); + const u8 v = static_cast<u8>(c); + while ((n--) != 0) { + if (p[n] == v) { + return const_cast<void *>(static_cast<const void *>(p + n)); + } + } + } + return nullptr; + #endif + } + + inline const void *Memrchr(const void *s, int c, size_t n) { + #if !(defined(__MINGW32__) || defined(__MINGW64__) || defined(ATMOSPHERE_OS_MACOS)) + return ::memrchr(s, c, n); + #else + /* TODO: Optimized implementation? */ + if (AMS_LIKELY(n > 0)) { + const u8 *p = static_cast<const u8 *>(s); + const u8 v = static_cast<u8>(c); + while ((n--) != 0) { + if (p[n] == v) { + return static_cast<const void *>(p + n); + } + } + } + return nullptr; + #endif + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_string_view.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_string_view.hpp new file mode 100644 index 00000000..3e823fcf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_string_view.hpp @@ -0,0 +1,347 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + template<class _CharT, class _Traits = std::char_traits<_CharT>> + class basic_string_view { + static_assert(!std::is_array<_CharT>::value); + static_assert(std::is_trivial<_CharT>::value && std::is_standard_layout<_CharT>::value); + static_assert(std::same_as<_CharT, typename _Traits::char_type>); + public: + using traits_type = _Traits; + using value_type = _CharT; + using pointer = value_type *; + using const_pointer = const value_type *; + using reference = value_type &; + using const_reference = const value_type &; + using const_iterator = const value_type *; + using iterator = const_iterator; + using const_reverse_iterator = std::reverse_iterator<const_iterator>; + using reverse_iterator = const_reverse_iterator; + using size_type = size_t; + using difference_type = ptrdiff_t; + static constexpr size_type npos = size_type(-1); + private: + static constexpr int _s_compare(size_type lhs, size_type rhs) noexcept { + const difference_type diff = lhs - rhs; + if (diff > std::numeric_limits<int>::max()) { + return std::numeric_limits<int>::max(); + } + if (diff < std::numeric_limits<int>::min()) { + return std::numeric_limits<int>::min(); + } + return static_cast<int>(diff); + } + private: + const_pointer m_str; + size_type m_len; + public: + constexpr basic_string_view() noexcept : m_str(nullptr), m_len(0) { /* ... */ } + constexpr basic_string_view(const _CharT *str, size_type len) noexcept : m_str(str), m_len(len) { /* ... */ } + constexpr basic_string_view(const _CharT *str) noexcept : m_str(str), m_len(str ? traits_type::length(str) : 0) { /* ... */ } + + template<std::contiguous_iterator _It, std::sized_sentinel_for<_It> _End> requires std::same_as<std::iter_value_t<_It>, _CharT> && (!std::convertible_to<_End, size_type>) + constexpr basic_string_view(_It first, _End last) noexcept : m_str(std::to_address(first)), m_len(last - first) { /* ... */ } + + constexpr basic_string_view(const basic_string_view &) noexcept = default; + constexpr basic_string_view &operator=(const basic_string_view &) noexcept = default; + + constexpr const_iterator begin() const noexcept { return m_str; } + constexpr const_iterator end() const noexcept { return m_str + m_len; } + + constexpr const_iterator cbegin() const noexcept { return m_str; } + constexpr const_iterator cend() const noexcept { return m_str + m_len; } + + constexpr const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(this->end()); } + constexpr const_reverse_iterator rend() const noexcept { return const_reverse_iterator(this->begin()); } + + constexpr const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(this->cend()); } + constexpr const_reverse_iterator crend() const noexcept { return const_reverse_iterator(this->cbegin()); } + + constexpr size_type size() const noexcept { return m_len; } + constexpr size_type length() const noexcept { return m_len; } + + constexpr size_type max_size() const noexcept { return (npos - sizeof(size_type) - sizeof(void *)) / sizeof(value_type) / 4; } + + [[nodiscard]] constexpr bool empty() const noexcept { return m_len == 0; } + + constexpr const_reference operator[](size_type pos) const noexcept { + AMS_ASSERT(pos < m_len); + return *(m_str + pos); + } + + constexpr const_reference at(size_type pos) const noexcept { + AMS_ASSERT(pos < m_len); + AMS_ABORT_UNLESS(pos < m_len); + return *(m_str + pos); + } + + constexpr const_reference front() const noexcept { + AMS_ASSERT(m_len > 0); + return *m_str; + } + + constexpr const_reference back() const noexcept { + AMS_ASSERT(m_len > 0); + return *(m_str + m_len - 1); + } + + constexpr const_pointer data() const noexcept { return m_str; } + + constexpr void remove_prefix(size_type n) noexcept { + AMS_ASSERT(m_len >= n); + m_str += n; + m_len -= n; + } + + constexpr void remove_suffix(size_type n) noexcept { + AMS_ASSERT(m_len >= n); + m_len -= n; + } + + constexpr void swap(basic_string_view &rhs) noexcept { + auto tmp = *this; + *this = rhs; + rhs = tmp; + } + + constexpr size_type copy(_CharT *str, size_type n, size_type pos = 0) const noexcept { + AMS_ASSERT(pos <= this->size()); + const size_type rlen = std::min(n, m_len - pos); + traits_type::copy(str, this->data() + pos, rlen); + return rlen; + } + + constexpr basic_string_view substr(size_type pos = 0, size_type n = npos) const noexcept { + AMS_ASSERT(pos <= this->size()); + const size_type rlen = std::min(n, m_len - pos); + return basic_string_view{m_str + pos, rlen}; + } + + constexpr int compare(basic_string_view str) const noexcept { + const size_type rlen = std::min(m_len, str.m_len); + int ret = traits_type::compare(m_str, str.m_str, rlen); + if (ret == 0) { + ret = _s_compare(m_len, str.m_len); + } + return ret; + } + + constexpr int compare(size_type pos, size_type n, basic_string_view str) const noexcept { + return this->substr(pos, n).compare(str); + } + + constexpr int compare(size_type pos1, size_type n1, basic_string_view str, size_type pos2, size_type n2) const { + return this->substr(pos1, n1).compare(str.substr(pos2, n2)); + } + + constexpr int compare(const _CharT *str) const noexcept { + return this->compare(basic_string_view(str)); + } + + constexpr int compare(size_type pos, size_type n, const _CharT *str) const noexcept { + return this->substr(pos, n).compare(basic_string_view(str)); + } + + constexpr int compare(size_type pos, size_type n, const _CharT *str, size_type n2) const noexcept { + return this->substr(pos, n).compare(basic_string_view(str, n2)); + } + + constexpr bool starts_with(basic_string_view x) const noexcept { return this->substr(0, x.size()) == x; } + constexpr bool starts_with(_CharT x) const noexcept { return !this->empty() && traits_type::eq(this->front(), x); } + constexpr bool starts_with(const _CharT *x) const noexcept { return this->starts_with(basic_string_view(x)); } + + constexpr bool ends_with(basic_string_view x) const noexcept { return this->size() >= x.size() && this->compare(this->size() - x.size(), npos, x) == 0; } + constexpr bool ends_with(_CharT x) const noexcept { return !this->empty() && traits_type::eq(this->back(), x); } + constexpr bool ends_with(const _CharT *x) const noexcept { return this->ends_with(basic_string_view(x)); } + + constexpr size_type find(const _CharT *str, size_type pos, size_type n) const noexcept { + if (n == 0) { + return pos + m_len ? pos : npos; + } + + if (n <= m_len) { + for (/* ... */; pos <= m_len - n; ++pos) { + if (traits_type::eq(m_str[pos], str[0]) && traits_type::compare(m_str + pos + 1, str + 1, n - 1) == 0) { + return pos; + } + } + } + + return npos; + } + + constexpr size_type find(_CharT c, size_type pos = 0) const noexcept { + size_type ret = npos; + if (pos < m_len) { + const size_type n = m_len - pos; + if (const _CharT *p = traits_type::find(m_str + pos, n, c); p) { + ret = p - m_str; + } + } + return ret; + } + + constexpr size_type find(basic_string_view str, size_type pos = 0) const noexcept { return this->find(str.m_str, pos, str.m_len); } + + __attribute__((nonnull(2))) + constexpr size_type find(const _CharT *str, size_type pos = 0) const noexcept { return this->find(str, pos, traits_type::length(str)); } + + constexpr size_type rfind(const _CharT *str, size_type pos, size_type n) const noexcept { + if (n <= m_len) { + pos = std::min(size_type(m_len - n), pos); + do { + if (traits_type::compare(m_str + pos, str, n) == 0) { + return pos; + } + } while (pos-- > 0); + } + + return npos; + } + + constexpr size_type rfind(_CharT c, size_type pos = 0) const noexcept { + size_type size = m_len; + if (size > 0) { + if (--size > pos) { + size = pos; + } + for (++size; size-- > 0; /* ... */) { + if (traits_type::eq(m_str[size], c)) { + return size; + } + } + } + return npos; + } + + constexpr size_type rfind(basic_string_view str, size_type pos = 0) const noexcept { return this->rfind(str.m_str, pos, str.m_len); } + + __attribute__((nonnull(2))) + constexpr size_type rfind(const _CharT *str, size_type pos = 0) const noexcept { return this->rfind(str, pos, traits_type::length(str)); } + + constexpr size_type find_first_of(const _CharT *str, size_type pos, size_t n) const noexcept { + for (/* ... */; n && pos < m_len; ++pos) { + if (const _CharT *p = traits_type::find(str, n, m_str[pos]); p) { + return pos; + } + } + return npos; + } + + constexpr size_type find_first_of(basic_string_view str, size_type pos = 0) const noexcept { return this->find_first_of(str.m_str, pos, str.m_len); } + constexpr size_type find_first_of(_CharT c, size_type pos = 0) const noexcept { return this->find(c, pos); } + + __attribute__((nonnull(2))) + constexpr size_type find_first_of(const _CharT *str, size_type pos = 0) const noexcept { return this->find_first_of(str, pos, traits_type::length(str)); } + + constexpr size_type find_last_of(const _CharT *str, size_type pos, size_t n) const noexcept { + size_type size = this->size(); + if (size && n) { + if (--size > pos) { + size = pos; + } + do { + if (traits_type::find(str, n, m_str[size])) { + return size; + } + } while (size-- != 0); + } + return npos; + } + + constexpr size_type find_last_of(basic_string_view str, size_type pos = 0) const noexcept { return this->find_last_of(str.m_str, pos, str.m_len); } + constexpr size_type find_last_of(_CharT c, size_type pos = 0) const noexcept { return this->rfind(c, pos); } + + __attribute__((nonnull(2))) + constexpr size_type find_last_of(const _CharT *str, size_type pos = 0) const noexcept { return this->find_first_of(str, pos, traits_type::length(str)); } + + constexpr size_type find_first_not_of(const _CharT *str, size_type pos, size_t n) const noexcept { + for (/* ... */; pos < m_len; ++pos) { + if (!traits_type::find(str, n, m_str[pos])) { + return pos; + } + } + return npos; + } + + constexpr size_type find_first_not_of(_CharT c, size_type pos = 0) const noexcept { + for (/* ... */; pos < m_len; ++pos) { + if (!traits_type::eq(m_str[pos], c)) { + return pos; + } + } + return npos; + } + + constexpr size_type find_first_not_of(basic_string_view str, size_type pos = 0) const noexcept { return this->find_first_not_of(str.m_str, pos, str.m_len); } + + __attribute__((nonnull(2))) + constexpr size_type find_first_not_of(const _CharT *str, size_type pos = 0) const noexcept { return this->find_first_not_of(str, pos, traits_type::length(str)); } + + constexpr size_type find_last_not_of(const _CharT *str, size_type pos, size_t n) const noexcept { + size_type size = this->size(); + if (size) { + if (--size > pos) { + size = pos; + } + do { + if (!traits_type::find(str, n, m_str[size])) { + return size; + } + } while (size-- != 0); + } + return npos; + } + + constexpr size_type find_last_not_of(_CharT c, size_type pos = 0) const noexcept { + size_type size = this->size(); + if (size) { + if (--size > pos) { + size = pos; + } + do { + if (!traits_type::eq(m_str[size], c)) { + return size; + } + } while (size-- != 0); + } + return npos; + } + + constexpr size_type find_last_not_of(basic_string_view str, size_type pos = 0) const noexcept { return this->find_last_not_of(str.m_str, pos, str.m_len); } + + __attribute__((nonnull(2))) + constexpr size_type find_last_not_of(const _CharT *str, size_type pos = 0) const noexcept { return this->find_last_not_of(str, pos, traits_type::length(str)); } + + constexpr friend bool operator==(const basic_string_view &lhs, const basic_string_view &rhs) noexcept { return lhs.compare(rhs) == 0; } + constexpr friend bool operator!=(const basic_string_view &lhs, const basic_string_view &rhs) noexcept { return lhs.compare(rhs) != 0; } + constexpr friend bool operator<=(const basic_string_view &lhs, const basic_string_view &rhs) noexcept { return lhs.compare(rhs) <= 0; } + constexpr friend bool operator>=(const basic_string_view &lhs, const basic_string_view &rhs) noexcept { return lhs.compare(rhs) >= 0; } + constexpr friend bool operator< (const basic_string_view &lhs, const basic_string_view &rhs) noexcept { return lhs.compare(rhs) < 0; } + constexpr friend bool operator> (const basic_string_view &lhs, const basic_string_view &rhs) noexcept { return lhs.compare(rhs) > 0; } + }; + + template<std::contiguous_iterator _It, std::sized_sentinel_for<_It> _End> + basic_string_view(_It, _End) -> basic_string_view<std::iter_value_t<_It>>; + + using string_view = basic_string_view<char>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_timer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_timer.hpp new file mode 100644 index 00000000..1aab3d6b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_timer.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + u32 GetMicroSeconds(); + void WaitMicroSeconds(int us); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_tinymt.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_tinymt.hpp new file mode 100644 index 00000000..173eb471 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_tinymt.hpp @@ -0,0 +1,248 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + /* Implementation of TinyMT (mersenne twister RNG). */ + /* Like Nintendo, we will use the sample parameters. */ + class TinyMT { + public: + static constexpr size_t NumStateWords = 4; + + struct State { + u32 data[NumStateWords]; + }; + private: + static constexpr u32 ParamMat1 = 0x8F7011EE; + static constexpr u32 ParamMat2 = 0xFC78FF1F; + static constexpr u32 ParamTmat = 0x3793FDFF; + + static constexpr u32 ParamMult = 0x6C078965; + static constexpr u32 ParamPlus = 0x0019660D; + static constexpr u32 ParamXor = 0x5D588B65; + + static constexpr u32 TopBitmask = 0x7FFFFFFF; + + static constexpr int MinimumInitIterations = 8; + static constexpr int NumDiscardedInitOutputs = 8; + + static constexpr inline u32 XorByShifted27(u32 value) { + return value ^ (value >> 27); + } + + static constexpr inline u32 XorByShifted30(u32 value) { + return value ^ (value >> 30); + } + private: + State m_state; + private: + /* Internal API. */ + void FinalizeInitialization() { + const u32 state0 = m_state.data[0] & TopBitmask; + const u32 state1 = m_state.data[1]; + const u32 state2 = m_state.data[2]; + const u32 state3 = m_state.data[3]; + + if (state0 == 0 && state1 == 0 && state2 == 0 && state3 == 0) { + m_state.data[0] = 'T'; + m_state.data[1] = 'I'; + m_state.data[2] = 'N'; + m_state.data[3] = 'Y'; + } + + for (int i = 0; i < NumDiscardedInitOutputs; i++) { + this->GenerateRandomU32(); + } + } + + u32 GenerateRandomU24() { return (this->GenerateRandomU32() >> 8); } + + static void GenerateInitialValuePlus(TinyMT::State *state, int index, u32 value) { + u32 &state0 = state->data[(index + 0) % NumStateWords]; + u32 &state1 = state->data[(index + 1) % NumStateWords]; + u32 &state2 = state->data[(index + 2) % NumStateWords]; + u32 &state3 = state->data[(index + 3) % NumStateWords]; + + const u32 x = XorByShifted27(state0 ^ state1 ^ state3) * ParamPlus; + const u32 y = x + index + value; + + state0 = y; + state1 += x; + state2 += y; + } + + static void GenerateInitialValueXor(TinyMT::State *state, int index) { + u32 &state0 = state->data[(index + 0) % NumStateWords]; + u32 &state1 = state->data[(index + 1) % NumStateWords]; + u32 &state2 = state->data[(index + 2) % NumStateWords]; + u32 &state3 = state->data[(index + 3) % NumStateWords]; + + const u32 x = XorByShifted27(state0 + state1 + state3) * ParamXor; + const u32 y = x - index; + + state0 = y; + state1 ^= x; + state2 ^= y; + } + public: + constexpr explicit TinyMT(util::ConstantInitializeTag) : m_state() { /* ... */ } + explicit TinyMT() { /* ... */ } + + /* Initialization. */ + void Initialize(u32 seed) { + m_state.data[0] = seed; + m_state.data[1] = ParamMat1; + m_state.data[2] = ParamMat2; + m_state.data[3] = ParamTmat; + + for (int i = 1; i < MinimumInitIterations; i++) { + const u32 mixed = XorByShifted30(m_state.data[(i - 1) % NumStateWords]); + m_state.data[i % NumStateWords] ^= mixed * ParamMult + i; + } + + this->FinalizeInitialization(); + } + + void Initialize(const u32 *seed, int seed_count) { + m_state.data[0] = 0; + m_state.data[1] = ParamMat1; + m_state.data[2] = ParamMat2; + m_state.data[3] = ParamTmat; + + { + const int num_init_iterations = std::max(seed_count + 1, MinimumInitIterations) - 1; + + GenerateInitialValuePlus(std::addressof(m_state), 0, seed_count); + + for (int i = 0; i < num_init_iterations; i++) { + GenerateInitialValuePlus(std::addressof(m_state), (i + 1) % NumStateWords, (i < seed_count) ? seed[i] : 0); + } + + for (int i = 0; i < static_cast<int>(NumStateWords); i++) { + GenerateInitialValueXor(std::addressof(m_state), (i + 1 + num_init_iterations) % NumStateWords); + } + } + + this->FinalizeInitialization(); + } + + /* State management. */ + void GetState(TinyMT::State *out) const { + std::memcpy(out->data, m_state.data, sizeof(m_state)); + } + + void SetState(const TinyMT::State *state) { + std::memcpy(m_state.data, state->data, sizeof(m_state)); + } + + /* Random generation. */ + NOINLINE void GenerateRandomBytes(void *dst, size_t size) { + const uintptr_t start = reinterpret_cast<uintptr_t>(dst); + const uintptr_t end = start + size; + const uintptr_t aligned_start = util::AlignUp(start, 4); + const uintptr_t aligned_end = util::AlignDown(end, 4); + + /* Make sure we're aligned. */ + if (start < aligned_start) { + const u32 rnd = this->GenerateRandomU32(); + std::memcpy(dst, std::addressof(rnd), aligned_start - start); + } + + /* Write as many aligned u32s as we can. */ + { + u32 * cur_dst = reinterpret_cast<u32 *>(aligned_start); + u32 * const end_dst = reinterpret_cast<u32 *>(aligned_end); + + while (cur_dst < end_dst) { + *(cur_dst++) = this->GenerateRandomU32(); + } + } + + /* Handle any leftover unaligned data. */ + if (aligned_end < end) { + const u32 rnd = this->GenerateRandomU32(); + std::memcpy(reinterpret_cast<void *>(aligned_end), std::addressof(rnd), end - aligned_end); + } + } + + NOINLINE u32 GenerateRandomU32() { + /* Advance state. */ + const u32 x0 = (m_state.data[0] & TopBitmask) ^ m_state.data[1] ^ m_state.data[2]; + const u32 y0 = m_state.data[3]; + const u32 x1 = x0 ^ (x0 << 1); + const u32 y1 = y0 ^ (y0 >> 1) ^ x1; + + const u32 state0 = m_state.data[1]; + u32 state1 = m_state.data[2]; + u32 state2 = x1 ^ (y1 << 10); + const u32 state3 = y1; + + if ((y1 & 1) != 0) { + state1 ^= ParamMat1; + state2 ^= ParamMat2; + } + + m_state.data[0] = state0; + m_state.data[1] = state1; + m_state.data[2] = state2; + m_state.data[3] = state3; + + /* Temper. */ + const u32 t1 = state0 + (state2 >> 8); + u32 t0 = state3 ^ t1; + + if ((t1 & 1) != 0) { + t0 ^= ParamTmat; + } + + return t0; + } + + inline u64 GenerateRandomU64() { + const u32 lo = this->GenerateRandomU32(); + const u32 hi = this->GenerateRandomU32(); + return (static_cast<u64>(hi) << 32) | static_cast<u64>(lo); + } + + inline float GenerateRandomF32() { + /* Floats have 24 bits of mantissa. */ + constexpr int MantissaBits = 24; + return GenerateRandomU24() * (1.0f / (1ul << MantissaBits)); + } + + inline double GenerateRandomF64() { + /* Doubles have 53 bits of mantissa. */ + /* The smart way to generate 53 bits of random would be to use 32 bits */ + /* from the first rnd32() call, and then 21 from the second. */ + /* Nintendo does not. They use (32 - 5) = 27 bits from the first rnd32() */ + /* call, and (32 - 6) bits from the second. We'll do what they do, but */ + /* There's not a clear reason why. */ + constexpr int MantissaBits = 53; + constexpr int Shift1st = (64 - MantissaBits) / 2; + constexpr int Shift2nd = (64 - MantissaBits) - Shift1st; + + const u32 first = (this->GenerateRandomU32() >> Shift1st); + const u32 second = (this->GenerateRandomU32() >> Shift2nd); + + return (1.0 * first * (static_cast<u64>(1) << (32 - Shift2nd)) + second) * (1.0 / (static_cast<u64>(1) << MantissaBits)); + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_type_traits.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_type_traits.hpp new file mode 100644 index 00000000..448c18d6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_type_traits.hpp @@ -0,0 +1,289 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + template<typename T> + using is_pod = std::bool_constant<std::is_standard_layout<T>::value && std::is_trivial<T>::value>; + + struct ConstantInitializeTag final {}; + constexpr inline const ConstantInitializeTag ConstantInitialize{}; + + namespace impl { + + constexpr int ToIntegerForIsConstexprConstructible(...) { return {}; } + + template<typename T, auto...Lambdas> requires (std::is_constructible<T, decltype(Lambdas())...>::value) + using ToIntegralConstantForIsConstexprConstructible = std::integral_constant<int, ToIntegerForIsConstexprConstructible(T(Lambdas()...))>; + + template<typename T, auto...Lambdas, int = ToIntegralConstantForIsConstexprConstructible<T, Lambdas...>::value> + std::true_type IsConstexprConstructibleImpl(int); + + template<typename T, auto...Lambdas> + std::false_type IsConstexprConstructibleImpl(long); + + template<typename T> + consteval inline auto ConvertToLambdaForIsConstexprConstructible() { return [] { return T{}; }; } + + template<auto V> + consteval inline auto ConvertToLambdaForIsConstexprConstructible() { return [] { return V; }; } + + namespace ambiguous_parse { + + struct AmbiguousParseHelperForIsConstexprConstructible { + + constexpr inline AmbiguousParseHelperForIsConstexprConstructible operator-() { return *this; } + + template<typename T> + constexpr inline operator T() { + return T{}; + } + }; + + constexpr inline auto operator -(auto v, AmbiguousParseHelperForIsConstexprConstructible) { return v; } + + } + + #define AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(TYPE_OR_VALUE) [] { ::ams::util::impl::ambiguous_parse::AmbiguousParseHelperForIsConstexprConstructible p; auto v = (TYPE_OR_VALUE)-p; return v; } + + } + + template<typename T, typename...ArgTypes> + using is_constexpr_constructible = decltype(impl::IsConstexprConstructibleImpl<T, impl::ConvertToLambdaForIsConstexprConstructible<ArgTypes>()...>(0)); + + template<typename T, auto...Args> + using is_constexpr_constructible_by_values = decltype(impl::IsConstexprConstructibleImpl<T, impl::ConvertToLambdaForIsConstexprConstructible<Args>()...>(0)); + + #define AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE_1(_1) \ + (decltype(::ams::util::impl::IsConstexprConstructibleImpl<_1>(0))::value) + + #define AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE_2(_1, _2) \ + (decltype(::ams::util::impl::IsConstexprConstructibleImpl<_1, \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_2) \ + >(0))::value) + + #define AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE_3(_1, _2, _3) \ + (decltype(::ams::util::impl::IsConstexprConstructibleImpl<_1, \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_2), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_3) \ + >(0))::value) + + #define AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE_4(_1, _2, _3, _4) \ + (decltype(::ams::util::impl::IsConstexprConstructibleImpl<_1, \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_2), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_3), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_4) \ + >(0))::value) + + #define AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE_5(_1, _2, _3, _4, _5) \ + (decltype(::ams::util::impl::IsConstexprConstructibleImpl<_1, \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_2), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_3), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_4), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_5) \ + >(0))::value) + + #define AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE_6(_1, _2, _3, _4, _5, _6) \ + (decltype(::ams::util::impl::IsConstexprConstructibleImpl<_1, \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_2), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_3), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_4), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_5), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_6) \ + >(0))::value) + + #define AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE_7(_1, _2, _3, _4, _5, _6, _7) \ + (decltype(::ams::util::impl::IsConstexprConstructibleImpl<_1, \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_2), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_3), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_4), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_5), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_6), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_7) \ + >(0))::value) + + #define AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE_8(_1, _2, _3, _4, _5, _6, _7, _8) \ + (decltype(::ams::util::impl::IsConstexprConstructibleImpl<_1, \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_2), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_3), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_4), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_5), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_6), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_7), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_8) \ + >(0))::value) + + #define AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE_9(_1, _2, _3, _4, _5, _6, _7, _8, _9) \ + (decltype(::ams::util::impl::IsConstexprConstructibleImpl<_1, \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_2), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_3), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_4), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_5), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_6), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_7), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_8), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_9) \ + >(0))::value) + + #define AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE_10(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \ + (decltype(::ams::util::impl::IsConstexprConstructibleImpl<_1, \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_2), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_3), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_4), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_5), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_6), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_7), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_8), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_9), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_10) \ + >(0))::value) + + #define AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE_11(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \ + (decltype(::ams::util::impl::IsConstexprConstructibleImpl<_1, \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_2), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_3), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_4), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_5), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_6), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_7), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_8), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_9), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_10), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_11) \ + >(0))::value) + + #define AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE_12(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \ + (decltype(::ams::util::impl::IsConstexprConstructibleImpl<_1, \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_2), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_3), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_4), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_5), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_6), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_7), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_8), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_9), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_10), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_11), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_12) \ + >(0))::value) + + #define AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE_13(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \ + (decltype(::ams::util::impl::IsConstexprConstructibleImpl<_1, \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_2), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_3), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_4), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_5), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_6), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_7), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_8), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_9), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_10), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_11), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_12), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_13) \ + >(0))::value) + + #define AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE_14(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \ + (decltype(::ams::util::impl::IsConstexprConstructibleImpl<_1, \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_2), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_3), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_4), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_5), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_6), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_7), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_8), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_9), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_10), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_11), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_12), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_13), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_14) \ + >(0))::value) + + #define AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE_15(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \ + (decltype(::ams::util::impl::IsConstexprConstructibleImpl<_1, \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_2), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_3), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_4), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_5), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_6), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_7), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_8), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_9), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_10), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_11), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_12), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_13), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_14), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_15) \ + >(0))::value) + + #define AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE_16(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \ + (decltype(::ams::util::impl::IsConstexprConstructibleImpl<_1, \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_2), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_3), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_4), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_5), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_6), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_7), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_8), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_9), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_10), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_11), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_12), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_13), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_14), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_15), \ + AMS_UTIL_IMPL_CONVERT_TV_TO_LAMBDA(_16) \ + >(0))::value) + + #define AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE(...) AMS_VMACRO(AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE, __VA_ARGS__) + + #if 0 + namespace test { + + struct S { + private: + int m_v; + public: + S() { } + + constexpr S(int v) : m_v() { } + constexpr S(int v, double z) : m_v(v) { } + }; + + consteval inline int test_constexpr_int() { return 0; } + inline int test_not_constexpr_int() { return 0; } + + static_assert(!AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE(S)); + static_assert(AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE(S, int)); + static_assert(AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE(S, 0)); + static_assert(AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE(S, test_constexpr_int())); + static_assert(!AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE(S, test_not_constexpr_int())); + + static_assert(AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE(S, int, double)); + static_assert(AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE(S, int, 0.0)); + static_assert(AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE(S, 0, double)); + static_assert(AMS_UTIL_IS_CONSTEXPR_CONSTRUCTIBLE(S, 0, 0.0)); + + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_typed_storage.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_typed_storage.hpp new file mode 100644 index 00000000..50ebc372 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_typed_storage.hpp @@ -0,0 +1,99 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + template<typename T, size_t Size = sizeof(T), size_t Align = alignof(T)> + struct TypedStorage { + alignas(Align) std::byte _storage[Size]; + }; + + template<typename T> + static ALWAYS_INLINE T *GetPointer(TypedStorage<T> &ts) { + return std::launder(reinterpret_cast<T *>(std::addressof(ts._storage))); + } + + template<typename T> + static ALWAYS_INLINE const T *GetPointer(const TypedStorage<T> &ts) { + return std::launder(reinterpret_cast<const T *>(std::addressof(ts._storage))); + } + + template<typename T> + static ALWAYS_INLINE T &GetReference(TypedStorage<T> &ts) { + return *GetPointer(ts); + } + + template<typename T> + static ALWAYS_INLINE const T &GetReference(const TypedStorage<T> &ts) { + return *GetPointer(ts); + } + + namespace impl { + + template<typename T> + static ALWAYS_INLINE T *GetPointerForConstructAt(TypedStorage<T> &ts) { + return reinterpret_cast<T *>(std::addressof(ts._storage)); + } + + } + + template<typename T, typename... Args> + static ALWAYS_INLINE T *ConstructAt(TypedStorage<T> &ts, Args &&... args) { + return std::construct_at(impl::GetPointerForConstructAt(ts), std::forward<Args>(args)...); + } + + template<typename T> + static ALWAYS_INLINE void DestroyAt(TypedStorage<T> &ts) { + return std::destroy_at(GetPointer(ts)); + } + + namespace impl { + + template<typename T> + class TypedStorageGuard { + NON_COPYABLE(TypedStorageGuard); + private: + TypedStorage<T> &m_ts; + bool m_active; + public: + template<typename... Args> + ALWAYS_INLINE TypedStorageGuard(TypedStorage<T> &ts, Args &&... args) : m_ts(ts), m_active(true) { + ConstructAt(m_ts, std::forward<Args>(args)...); + } + + ALWAYS_INLINE ~TypedStorageGuard() { if (m_active) { DestroyAt(m_ts); } } + + ALWAYS_INLINE void Cancel() { m_active = false; } + + ALWAYS_INLINE TypedStorageGuard(TypedStorageGuard&& rhs) : m_ts(rhs.m_ts), m_active(rhs.m_active) { + rhs.Cancel(); + } + + TypedStorageGuard &operator=(TypedStorageGuard&& rhs) = delete; + }; + + } + + template<typename T, typename... Args> + static ALWAYS_INLINE impl::TypedStorageGuard<T> ConstructAtGuarded(TypedStorage<T> &ts, Args &&... args) { + return impl::TypedStorageGuard<T>(ts, std::forward<Args>(args)...); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_utf8_string_util.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_utf8_string_util.hpp new file mode 100644 index 00000000..99e12d08 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_utf8_string_util.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +namespace ams::util { + + bool VerifyUtf8String(const char *str, size_t size); + + int GetCodePointCountOfUtf8String(const char *str, size_t size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_uuid.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_uuid.hpp new file mode 100644 index 00000000..3670c0d7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_uuid.hpp @@ -0,0 +1,84 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> +#include <vapours/util/util_format_string.hpp> + +namespace ams::util { + + struct Uuid { + static constexpr size_t Size = 0x10; + static constexpr size_t StringSize = sizeof("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"); + + u8 data[Size]; + + friend bool operator==(const Uuid &lhs, const Uuid &rhs) { + return std::memcmp(lhs.data, rhs.data, Size) == 0; + } + + friend bool operator!=(const Uuid &lhs, const Uuid &rhs) { + return !(lhs == rhs); + } + + const char *ToString(char *dst, size_t dst_size) const { + util::SNPrintf(dst, dst_size, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + this->data[ 0], this->data[ 1], this->data[ 2], this->data[ 3], this->data[ 4], this->data[ 5], this->data[ 6], this->data[ 7], + this->data[ 8], this->data[ 9], this->data[10], this->data[11], this->data[12], this->data[13], this->data[14], this->data[15]); + + return dst; + } + + void FromString(const char *str) { + char buf[2 + 1] = {}; + char *end; + s32 i = 0; + + for (/* ... */; i < 4; ++i, str += 2) { + std::memcpy(buf, str, 2); + this->data[i] = static_cast<u8>(std::strtoul(buf, std::addressof(end), 16)); + } + ++str; + + for (/* ... */; i < 6; ++i, str += 2) { + std::memcpy(buf, str, 2); + this->data[i] = static_cast<u8>(std::strtoul(buf, std::addressof(end), 16)); + } + ++str; + + for (/* ... */; i < 8; ++i, str += 2) { + std::memcpy(buf, str, 2); + this->data[i] = static_cast<u8>(std::strtoul(buf, std::addressof(end), 16)); + } + ++str; + + for (/* ... */; i < 10; ++i, str += 2) { + std::memcpy(buf, str, 2); + this->data[i] = static_cast<u8>(std::strtoul(buf, std::addressof(end), 16)); + } + ++str; + + for (/* ... */; i < 16; ++i, str += 2) { + std::memcpy(buf, str, 2); + this->data[i] = static_cast<u8>(std::strtoul(buf, std::addressof(end), 16)); + } + } + }; + + constexpr inline Uuid InvalidUuid = {}; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_variadic.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_variadic.hpp new file mode 100644 index 00000000..8d523541 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/include/vapours/util/util_variadic.hpp @@ -0,0 +1,84 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <vapours/common.hpp> +#include <vapours/assert.hpp> + +#define AMS_UTIL_VARIADIC_INVOKE_MACRO(__HANDLER__) \ + __HANDLER__(_01_) \ + __HANDLER__(_02_) \ + __HANDLER__(_03_) \ + __HANDLER__(_04_) \ + __HANDLER__(_05_) \ + __HANDLER__(_06_) \ + __HANDLER__(_07_) \ + __HANDLER__(_08_) \ + __HANDLER__(_09_) \ + __HANDLER__(_0A_) \ + __HANDLER__(_0B_) \ + __HANDLER__(_0C_) \ + __HANDLER__(_0D_) \ + __HANDLER__(_0E_) \ + __HANDLER__(_0F_) + +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_01_(_T_) typename _T_##_01_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_02_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_01_(_T_), typename _T_##_02_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_03_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_02_(_T_), typename _T_##_03_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_04_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_03_(_T_), typename _T_##_04_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_05_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_04_(_T_), typename _T_##_05_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_06_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_05_(_T_), typename _T_##_06_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_07_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_06_(_T_), typename _T_##_07_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_08_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_07_(_T_), typename _T_##_08_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_09_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_08_(_T_), typename _T_##_09_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0A_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_09_(_T_), typename _T_##_0A_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0B_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0A_(_T_), typename _T_##_0B_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0C_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0B_(_T_), typename _T_##_0C_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0D_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0C_(_T_), typename _T_##_0D_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0E_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0D_(_T_), typename _T_##_0E_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0F_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0E_(_T_), typename _T_##_0F_ + +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_01_(_T_, _N_) _T_##_01_ &&_N_##_01_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_02_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_01_(_T_, _N_), _T_##_02_ &&_N_##_02_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_03_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_02_(_T_, _N_), _T_##_03_ &&_N_##_03_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_04_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_03_(_T_, _N_), _T_##_04_ &&_N_##_04_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_05_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_04_(_T_, _N_), _T_##_05_ &&_N_##_05_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_06_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_05_(_T_, _N_), _T_##_06_ &&_N_##_06_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_07_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_06_(_T_, _N_), _T_##_07_ &&_N_##_07_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_08_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_07_(_T_, _N_), _T_##_08_ &&_N_##_08_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_09_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_08_(_T_, _N_), _T_##_09_ &&_N_##_09_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0A_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_09_(_T_, _N_), _T_##_0A_ &&_N_##_0A_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0B_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0A_(_T_, _N_), _T_##_0B_ &&_N_##_0B_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0C_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0B_(_T_, _N_), _T_##_0C_ &&_N_##_0C_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0D_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0C_(_T_, _N_), _T_##_0D_ &&_N_##_0D_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0E_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0D_(_T_, _N_), _T_##_0E_ &&_N_##_0E_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0F_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0E_(_T_, _N_), _T_##_0F_ &&_N_##_0F_ + +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_01_(_T_, _N_) ::std::forward<_T_##_01_>(_N_##_01_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_02_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_01_(_T_, _N_), ::std::forward<_T_##_02_>(_N_##_02_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_03_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_02_(_T_, _N_), ::std::forward<_T_##_03_>(_N_##_03_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_04_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_03_(_T_, _N_), ::std::forward<_T_##_04_>(_N_##_04_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_05_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_04_(_T_, _N_), ::std::forward<_T_##_05_>(_N_##_05_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_06_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_05_(_T_, _N_), ::std::forward<_T_##_06_>(_N_##_06_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_07_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_06_(_T_, _N_), ::std::forward<_T_##_07_>(_N_##_07_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_08_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_07_(_T_, _N_), ::std::forward<_T_##_08_>(_N_##_08_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_09_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_08_(_T_, _N_), ::std::forward<_T_##_09_>(_N_##_09_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0A_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_09_(_T_, _N_), ::std::forward<_T_##_0A_>(_N_##_0A_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0B_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0A_(_T_, _N_), ::std::forward<_T_##_0B_>(_N_##_0B_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0C_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0B_(_T_, _N_), ::std::forward<_T_##_0C_>(_N_##_0C_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0D_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0C_(_T_, _N_), ::std::forward<_T_##_0D_>(_N_##_0D_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0E_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0D_(_T_, _N_), ::std::forward<_T_##_0E_>(_N_##_0E_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0F_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0E_(_T_, _N_), ::std::forward<_T_##_0F_>(_N_##_0F_) diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_aes_cbc_encryptor_decryptor.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_aes_cbc_encryptor_decryptor.cpp new file mode 100644 index 00000000..14e0113a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_aes_cbc_encryptor_decryptor.cpp @@ -0,0 +1,56 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::crypto { + + size_t EncryptAes128Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + Aes128CbcEncryptor aes; + aes.Initialize(key, key_size, iv, iv_size); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t EncryptAes192Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + Aes192CbcEncryptor aes; + aes.Initialize(key, key_size, iv, iv_size); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t EncryptAes256Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + Aes256CbcEncryptor aes; + aes.Initialize(key, key_size, iv, iv_size); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t DecryptAes128Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + Aes128CbcDecryptor aes; + aes.Initialize(key, key_size, iv, iv_size); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t DecryptAes192Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + Aes192CbcDecryptor aes; + aes.Initialize(key, key_size, iv, iv_size); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t DecryptAes256Cbc(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + Aes256CbcDecryptor aes; + aes.Initialize(key, key_size, iv, iv_size); + return aes.Update(dst, dst_size, src, src_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_aes_ctr_encryptor_decryptor.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_aes_ctr_encryptor_decryptor.cpp new file mode 100644 index 00000000..1f6f09be --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_aes_ctr_encryptor_decryptor.cpp @@ -0,0 +1,92 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::crypto { + + size_t EncryptAes128Ctr(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + Aes128CtrEncryptor aes; + aes.Initialize(key, key_size, iv, iv_size); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t EncryptAes192Ctr(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + Aes192CtrEncryptor aes; + aes.Initialize(key, key_size, iv, iv_size); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t EncryptAes256Ctr(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + Aes256CtrEncryptor aes; + aes.Initialize(key, key_size, iv, iv_size); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t DecryptAes128Ctr(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + Aes128CtrDecryptor aes; + aes.Initialize(key, key_size, iv, iv_size); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t DecryptAes192Ctr(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + Aes192CtrDecryptor aes; + aes.Initialize(key, key_size, iv, iv_size); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t DecryptAes256Ctr(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + Aes256CtrDecryptor aes; + aes.Initialize(key, key_size, iv, iv_size); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t EncryptAes128CtrPartial(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, s64 offset, const void *src, size_t src_size) { + Aes128CtrEncryptor aes; + aes.Initialize(key, key_size, iv, iv_size, offset); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t EncryptAes192CtrPartial(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, s64 offset, const void *src, size_t src_size) { + Aes192CtrEncryptor aes; + aes.Initialize(key, key_size, iv, iv_size, offset); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t EncryptAes256CtrPartial(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, s64 offset, const void *src, size_t src_size) { + Aes256CtrEncryptor aes; + aes.Initialize(key, key_size, iv, iv_size, offset); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t DecryptAes128CtrPartial(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, s64 offset, const void *src, size_t src_size) { + Aes128CtrDecryptor aes; + aes.Initialize(key, key_size, iv, iv_size, offset); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t DecryptAes192CtrPartial(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, s64 offset, const void *src, size_t src_size) { + Aes192CtrDecryptor aes; + aes.Initialize(key, key_size, iv, iv_size, offset); + return aes.Update(dst, dst_size, src, src_size); + } + + size_t DecryptAes256CtrPartial(void *dst, size_t dst_size, const void *key, size_t key_size, const void *iv, size_t iv_size, s64 offset, const void *src, size_t src_size) { + Aes256CtrDecryptor aes; + aes.Initialize(key, key_size, iv, iv_size, offset); + return aes.Update(dst, dst_size, src, src_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_hmac_sha1_generator.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_hmac_sha1_generator.cpp new file mode 100644 index 00000000..9e1ffec8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_hmac_sha1_generator.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::crypto { + + void GenerateHmacSha1(void *dst, size_t dst_size, const void *data, size_t data_size, const void *key, size_t key_size) { + HmacSha1Generator hmac; + + hmac.Initialize(key, key_size); + hmac.Update(data, data_size); + hmac.GetMac(dst, dst_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_hmac_sha256_generator.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_hmac_sha256_generator.cpp new file mode 100644 index 00000000..e430de28 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_hmac_sha256_generator.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::crypto { + + void GenerateHmacSha256(void *dst, size_t dst_size, const void *data, size_t data_size, const void *key, size_t key_size) { + HmacSha256Generator hmac; + + hmac.Initialize(key, key_size); + hmac.Update(data, data_size); + hmac.GetMac(dst, dst_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_md5_generator.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_md5_generator.cpp new file mode 100644 index 00000000..8c1b9b4d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_md5_generator.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::crypto { + + void GenerateMd5(void *dst, size_t dst_size, const void *src, size_t src_size) { + Md5Generator gen; + + gen.Initialize(); + gen.Update(src, src_size); + gen.GetHash(dst, dst_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_memory_clear.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_memory_clear.cpp new file mode 100644 index 00000000..17375d47 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_memory_clear.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::crypto { + + void ClearMemory(void *_mem, size_t size) { + volatile u8 *mem = reinterpret_cast<volatile u8 *>(_mem); + + for (size_t i = 0; i < size; i++) { + mem[i] = 0; + } + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_memory_compare.arch.arm.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_memory_compare.arch.arm.cpp new file mode 100644 index 00000000..1a2d5484 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_memory_compare.arch.arm.cpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::crypto { + + bool IsSameBytes(const void *lhs, const void *rhs, size_t size) { + bool result; + u8 xor_acc, ltmp, rtmp; + size_t index; + + __asm__ __volatile__( + /* Clear registers and prepare for comparison. */ + " movs %[xor_acc], #0\n" + " movs %[index], #0\n" + " b 1f\n" + + /* Compare one byte in constant time. */ + "0:\n" + " ldrb %[ltmp], [%[lhs]]\n" + " ldrb %[rtmp], [%[rhs]]\n" + " adds %[lhs], #1\n" + " adds %[rhs], #1\n" + " eors %[ltmp], %[ltmp], %[rtmp]\n" + " orrs %[xor_acc], %[xor_acc], %[ltmp]\n" + " adds %[index], #1\n" + + /* Check if there is still data to compare. */ + "1:\n" + " cmp %[index], %[size]\n" + " bcc 0b\n" + + /* We're done, set result. */ + " cmp %[xor_acc], #0\n" + " moveq %[result], #1\n" + " movne %[result], #0\n" + : [result]"=r"(result), [lhs]"+r"(lhs), [rhs]"+r"(rhs), [xor_acc]"=&r"(xor_acc), [index]"=&r"(index), [ltmp]"=&r"(ltmp), [rtmp]"=&r"(rtmp) + : "m"(*(const u8 (*)[size])lhs), "m"(*(const u8 (*)[size])rhs), [size]"r"(size) + : "cc" + ); + + return result; + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_memory_compare.arch.arm64.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_memory_compare.arch.arm64.cpp new file mode 100644 index 00000000..7fdd814d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_memory_compare.arch.arm64.cpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::crypto { + + bool IsSameBytes(const void *lhs, const void *rhs, size_t size) { + bool result; + u8 xor_acc, ltmp, rtmp; + size_t index; + + __asm__ __volatile__( + /* Clear registers and prepare for comparison. */ + " mov %w[xor_acc], #0\n" + " mov %w[index], #0\n" + " b 1f\n" + + /* Compare one byte in constant time. */ + "0:\n" + " ldrb %w[ltmp], [%[lhs]]\n" + " ldrb %w[rtmp], [%[rhs]]\n" + " adds %[lhs], %[lhs], #1\n" + " adds %[rhs], %[rhs], #1\n" + " eor %w[ltmp], %w[ltmp], %w[rtmp]\n" + " orr %w[xor_acc], %w[xor_acc], %w[ltmp]\n" + " adds %[index], %[index], #1\n" + + /* Check if there is still data to compare. */ + "1:\n" + " cmp %[index], %[size]\n" + " bcc 0b\n" + + /* We're done, set result. */ + " cmp %w[xor_acc], #0\n" + " cset %w[result], eq\n" + : [result]"=r"(result), [lhs]"+r"(lhs), [rhs]"+r"(rhs), [xor_acc]"=&r"(xor_acc), [index]"=&r"(index), [ltmp]"=&r"(ltmp), [rtmp]"=&r"(rtmp) + : "m"(*(const u8 (*)[size])lhs), "m"(*(const u8 (*)[size])rhs), [size]"r"(size) + : "cc" + ); + + return result; + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_memory_compare.arch.generic.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_memory_compare.arch.generic.cpp new file mode 100644 index 00000000..46f65b57 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_memory_compare.arch.generic.cpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::crypto { + + bool IsSameBytes(const void *lhs, const void *rhs, size_t size) { + /* TODO: Should the generic impl be constant time? */ + volatile u8 diff = 0; + + const volatile u8 *lhs8 = static_cast<const volatile u8 *>(lhs); + const volatile u8 *rhs8 = static_cast<const volatile u8 *>(rhs); + for (size_t i = 0; i < size; ++i) { + diff = diff | (lhs8[i] ^ rhs8[i]); + } + + return diff == 0; + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_sha1_generator.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_sha1_generator.cpp new file mode 100644 index 00000000..575dc384 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_sha1_generator.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::crypto { + + void GenerateSha1(void *dst, size_t dst_size, const void *src, size_t src_size) { + Sha1Generator gen; + + gen.Initialize(); + gen.Update(src, src_size); + gen.GetHash(dst, dst_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_sha256_generator.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_sha256_generator.cpp new file mode 100644 index 00000000..15c52047 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/crypto_sha256_generator.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::crypto { + + void GenerateSha256(void *dst, size_t dst_size, const void *src, size_t src_size) { + Sha256Generator gen{}; + + gen.Initialize(); + gen.Update(src, src_size); + gen.GetHash(dst, dst_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_aes_impl.arch.arm64.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_aes_impl.arch.arm64.cpp new file mode 100644 index 00000000..35fdf4fa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_aes_impl.arch.arm64.cpp @@ -0,0 +1,501 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <arm_neon.h> +#endif + +namespace ams::crypto::impl { + +#if defined(ATMOSPHERE_IS_STRATOSPHERE) + + + namespace { + + /* Helper macros to setup for inline AES asm */ + #define AES_ENC_DEC_SETUP_VARS() \ + const auto *ctx = reinterpret_cast<const RoundKeyHelper<KeySize> *>(m_round_keys); \ + static_assert(sizeof(*ctx) == sizeof(m_round_keys)); \ + \ + uint8x16_t tmp = vld1q_u8((const uint8_t *)src); \ + uint8x16_t tmp2 + + #define AES_ENC_DEC_OUTPUT_VARS() \ + [tmp]"+w"(tmp), [tmp2]"=w"(tmp2) + + #define AES_ENC_DEC_STORE_RESULT() \ + vst1q_u8((uint8_t *)dst, tmp) + + /* Helper macros to do AES encryption, via inline asm. */ + #define AES_ENC_ROUND(n) \ + "ldr %q[tmp2], %[round_key_" #n "]\n" \ + "aese %[tmp].16b, %[tmp2].16b\n" \ + "aesmc %[tmp].16b, %[tmp].16b\n" + + #define AES_ENC_FINAL_ROUND() \ + "ldr %q[tmp2], %[round_key_second_last]\n" \ + "aese %[tmp].16b, %[tmp2].16b\n" \ + "ldr %q[tmp2], %[round_key_last]\n" \ + "eor %[tmp].16b, %[tmp].16b, %[tmp2].16b" + + #define AES_ENC_INPUT_ROUND_KEY(num_rounds, n) \ + [round_key_##n]"m"(ctx->round_keys[(n-1)]) + + #define AES_ENC_INPUT_LAST_ROUND_KEYS(num_rounds) \ + [round_key_second_last]"m"(ctx->round_keys[(num_rounds - 1)]), \ + [round_key_last]"m"(ctx->round_keys[(num_rounds)]) + + /* Helper macros to do AES decryption, via inline asm. */ + #define AES_DEC_ROUND(n) \ + "ldr %q[tmp2], %[round_key_" #n "]\n" \ + "aesd %[tmp].16b, %[tmp2].16b\n" \ + "aesimc %[tmp].16b, %[tmp].16b\n" + + #define AES_DEC_FINAL_ROUND() \ + "ldr %q[tmp2], %[round_key_second_last]\n" \ + "aesd %[tmp].16b, %[tmp2].16b\n" \ + "ldr %q[tmp2], %[round_key_last]\n" \ + "eor %[tmp].16b, %[tmp].16b, %[tmp2].16b" + + #define AES_DEC_INPUT_ROUND_KEY(num_rounds, n) \ + [round_key_##n]"m"(ctx->round_keys[(num_rounds + 1 - n)]) + + #define AES_DEC_INPUT_LAST_ROUND_KEYS(num_rounds) \ + [round_key_second_last]"m"(ctx->round_keys[1]), \ + [round_key_last]"m"(ctx->round_keys[0]) + + + constexpr const u8 RoundKeyRcon0[] = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A, 0x2F, + 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A, 0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, + }; + + constexpr const u8 SubBytesTable[0x100] = { + 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, + 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, + 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, + 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, + 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, + 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, + 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, + 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, + 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, + 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, + 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, + 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, + 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, + 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, + 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, + 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16, + }; + + constexpr auto AesWordByte0Shift = 0 * BITSIZEOF(u8); + constexpr auto AesWordByte1Shift = 1 * BITSIZEOF(u8); + constexpr auto AesWordByte2Shift = 2 * BITSIZEOF(u8); + constexpr auto AesWordByte3Shift = 3 * BITSIZEOF(u8); + + constexpr u32 SubBytesAndRotate(u32 v) { + return (static_cast<u32>(SubBytesTable[(v >> AesWordByte0Shift) & 0xFFu]) << AesWordByte3Shift) ^ + (static_cast<u32>(SubBytesTable[(v >> AesWordByte1Shift) & 0xFFu]) << AesWordByte0Shift) ^ + (static_cast<u32>(SubBytesTable[(v >> AesWordByte2Shift) & 0xFFu]) << AesWordByte1Shift) ^ + (static_cast<u32>(SubBytesTable[(v >> AesWordByte3Shift) & 0xFFu]) << AesWordByte2Shift); + } + + constexpr u32 SubBytes(u32 v) { + return (static_cast<u32>(SubBytesTable[(v >> AesWordByte0Shift) & 0xFFu]) << AesWordByte0Shift) ^ + (static_cast<u32>(SubBytesTable[(v >> AesWordByte1Shift) & 0xFFu]) << AesWordByte1Shift) ^ + (static_cast<u32>(SubBytesTable[(v >> AesWordByte2Shift) & 0xFFu]) << AesWordByte2Shift) ^ + (static_cast<u32>(SubBytesTable[(v >> AesWordByte3Shift) & 0xFFu]) << AesWordByte3Shift); + } + + } + + template<size_t KeySize> + AesImpl<KeySize>::~AesImpl() { + ClearMemory(this, sizeof(*this)); + } + + template<size_t KeySize> + void AesImpl<KeySize>::Initialize(const void *key, size_t key_size, bool is_encrypt) { + /* Check pre-conditions. */ + AMS_ASSERT(key_size == KeySize); + AMS_UNUSED(key_size); + + /* Set up key. */ + u32 *dst = m_round_keys; + std::memcpy(dst, key, KeySize); + + /* Perform key scheduling. */ + constexpr auto InitialKeyWords = KeySize / sizeof(u32); + u32 tmp = dst[InitialKeyWords - 1]; + + for (auto i = InitialKeyWords; i < (RoundCount + 1) * 4; ++i) { + const auto idx_in_key = i % InitialKeyWords; + if (idx_in_key == 0) { + /* At start of key word, we need to handle sub/rotate/rcon. */ + tmp = SubBytesAndRotate(tmp); + tmp ^= (RoundKeyRcon0[i / InitialKeyWords - 1] << AesWordByte0Shift); + } else if ((InitialKeyWords > 6) && idx_in_key == 4) { + /* Halfway into a 256-bit key word, we need to do an additional subbytes. */ + tmp = SubBytes(tmp); + } + + /* Set the key word. */ + tmp ^= dst[i - InitialKeyWords]; + dst[i] = tmp; + } + + /* If decrypting, perform inverse mix columns on all round keys. */ + if (!is_encrypt) { + auto *key8 = reinterpret_cast<u8 *>(m_round_keys) + BlockSize; + + for (auto i = 1; i < RoundCount; ++i) { + vst1q_u8(key8, vaesimcq_u8(vld1q_u8(key8))); + key8 += BlockSize; + } + } + } + + template<size_t KeySize> + void AesImpl<KeySize>::EncryptBlock(void *dst, size_t dst_size, const void *src, size_t src_size) const { + AMS_UNUSED(src_size, dst_size); + + /* Get the key. */ + const u8 *key8 = reinterpret_cast<const u8 *>(m_round_keys); + + /* Read the block. */ + uint8x16_t block = vld1q_u8(static_cast<const u8 *>(src)); + + /* Encrypt block. */ + for (auto round = 1; round < RoundCount; ++round) { + /* Do aes round. */ + block = vaeseq_u8(block, vld1q_u8(key8)); + key8 += BlockSize; + + /* Do mix columns. */ + block = vaesmcq_u8(block); + } + + /* Do last aes round. */ + block = vaeseq_u8(block, vld1q_u8(key8)); + key8 += BlockSize; + + /* Add the final round key. */ + block = veorq_u8(block, vld1q_u8(key8)); + + /* Store the block. */ + vst1q_u8(static_cast<u8 *>(dst), block); + } + + template<size_t KeySize> + void AesImpl<KeySize>::DecryptBlock(void *dst, size_t dst_size, const void *src, size_t src_size) const { + AMS_UNUSED(src_size, dst_size); + + /* Get the key. */ + const u8 *key8 = reinterpret_cast<const u8 *>(m_round_keys) + (RoundCount * BlockSize); + + /* Read the block. */ + uint8x16_t block = vld1q_u8(static_cast<const u8 *>(src)); + + /* Encrypt block. */ + for (auto round = RoundCount; round > 1; --round) { + /* Do aes round. */ + block = vaesdq_u8(block, vld1q_u8(key8)); + key8 -= BlockSize; + + /* Do mix columns. */ + block = vaesimcq_u8(block); + } + + /* Do last aes round. */ + block = vaesdq_u8(block, vld1q_u8(key8)); + key8 -= BlockSize; + + /* Add the first round key. */ + block = veorq_u8(block, vld1q_u8(key8)); + + /* Store the block. */ + vst1q_u8(static_cast<u8 *>(dst), block); + } + + /* Specializations when building specifically for cortex-a57 (or for apple M* processors). */ + #if defined(ATMOSPHERE_CPU_ARM_CORTEX_A57) || defined(ATMOSPHERE_OS_MACOS) + + namespace { + + template<size_t KeySize> + struct RoundKeyHelper { + u8 round_keys[AesImpl<KeySize>::RoundCount + 1][AesImpl<KeySize>::BlockSize]; + }; + + } + + template<> + void AesImpl<16>::EncryptBlock(void *dst, size_t dst_size, const void *src, size_t src_size) const { + AMS_UNUSED(src_size, dst_size); + + /* Setup for asm */ + AES_ENC_DEC_SETUP_VARS(); + + /* Use optimized assembly to do all rounds. */ + __asm__ __volatile__ ( + AES_ENC_ROUND(1) + AES_ENC_ROUND(2) + AES_ENC_ROUND(3) + AES_ENC_ROUND(4) + AES_ENC_ROUND(5) + AES_ENC_ROUND(6) + AES_ENC_ROUND(7) + AES_ENC_ROUND(8) + AES_ENC_ROUND(9) + AES_ENC_FINAL_ROUND() + : AES_ENC_DEC_OUTPUT_VARS() + : AES_ENC_INPUT_ROUND_KEY(RoundCount, 1), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 2), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 3), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 4), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 5), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 6), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 7), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 8), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 9), + AES_ENC_INPUT_LAST_ROUND_KEYS(RoundCount) + ); + + /* Store result. */ + AES_ENC_DEC_STORE_RESULT(); + } + + template<> + void AesImpl<24>::EncryptBlock(void *dst, size_t dst_size, const void *src, size_t src_size) const { + AMS_UNUSED(src_size, dst_size); + + /* Setup for asm */ + AES_ENC_DEC_SETUP_VARS(); + + /* Use optimized assembly to do all rounds. */ + __asm__ __volatile__ ( + AES_ENC_ROUND(1) + AES_ENC_ROUND(2) + AES_ENC_ROUND(3) + AES_ENC_ROUND(4) + AES_ENC_ROUND(5) + AES_ENC_ROUND(6) + AES_ENC_ROUND(7) + AES_ENC_ROUND(8) + AES_ENC_ROUND(9) + AES_ENC_ROUND(10) + AES_ENC_ROUND(11) + AES_ENC_FINAL_ROUND() + : AES_ENC_DEC_OUTPUT_VARS() + : AES_ENC_INPUT_ROUND_KEY(RoundCount, 1), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 2), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 3), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 4), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 5), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 6), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 7), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 8), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 9), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 10), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 11), + AES_ENC_INPUT_LAST_ROUND_KEYS(RoundCount) + ); + + /* Store result. */ + AES_ENC_DEC_STORE_RESULT(); + } + + template<> + void AesImpl<32>::EncryptBlock(void *dst, size_t dst_size, const void *src, size_t src_size) const { + AMS_UNUSED(src_size, dst_size); + + /* Setup for asm */ + AES_ENC_DEC_SETUP_VARS(); + + /* Use optimized assembly to do all rounds. */ + __asm__ __volatile__ ( + AES_ENC_ROUND(1) + AES_ENC_ROUND(2) + AES_ENC_ROUND(3) + AES_ENC_ROUND(4) + AES_ENC_ROUND(5) + AES_ENC_ROUND(6) + AES_ENC_ROUND(7) + AES_ENC_ROUND(8) + AES_ENC_ROUND(9) + AES_ENC_ROUND(10) + AES_ENC_ROUND(11) + AES_ENC_ROUND(12) + AES_ENC_ROUND(13) + AES_ENC_FINAL_ROUND() + : AES_ENC_DEC_OUTPUT_VARS() + : AES_ENC_INPUT_ROUND_KEY(RoundCount, 1), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 2), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 3), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 4), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 5), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 6), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 7), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 8), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 9), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 10), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 11), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 12), + AES_ENC_INPUT_ROUND_KEY(RoundCount, 13), + AES_ENC_INPUT_LAST_ROUND_KEYS(RoundCount) + ); + + /* Store result. */ + AES_ENC_DEC_STORE_RESULT(); + } + + template<> + void AesImpl<16>::DecryptBlock(void *dst, size_t dst_size, const void *src, size_t src_size) const { + AMS_UNUSED(src_size, dst_size); + + /* Setup for asm */ + AES_ENC_DEC_SETUP_VARS(); + + /* Use optimized assembly to do all rounds. */ + __asm__ __volatile__ ( + AES_DEC_ROUND(1) + AES_DEC_ROUND(2) + AES_DEC_ROUND(3) + AES_DEC_ROUND(4) + AES_DEC_ROUND(5) + AES_DEC_ROUND(6) + AES_DEC_ROUND(7) + AES_DEC_ROUND(8) + AES_DEC_ROUND(9) + AES_DEC_FINAL_ROUND() + : AES_ENC_DEC_OUTPUT_VARS() + : AES_DEC_INPUT_ROUND_KEY(RoundCount, 1), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 2), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 3), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 4), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 5), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 6), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 7), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 8), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 9), + AES_DEC_INPUT_LAST_ROUND_KEYS(RoundCount) + ); + + /* Store result. */ + AES_ENC_DEC_STORE_RESULT(); + } + + template<> + void AesImpl<24>::DecryptBlock(void *dst, size_t dst_size, const void *src, size_t src_size) const { + AMS_UNUSED(src_size, dst_size); + + /* Setup for asm */ + AES_ENC_DEC_SETUP_VARS(); + + /* Use optimized assembly to do all rounds. */ + __asm__ __volatile__ ( + AES_DEC_ROUND(1) + AES_DEC_ROUND(2) + AES_DEC_ROUND(3) + AES_DEC_ROUND(4) + AES_DEC_ROUND(5) + AES_DEC_ROUND(6) + AES_DEC_ROUND(7) + AES_DEC_ROUND(8) + AES_DEC_ROUND(9) + AES_DEC_ROUND(10) + AES_DEC_ROUND(11) + AES_DEC_FINAL_ROUND() + : AES_ENC_DEC_OUTPUT_VARS() + : AES_DEC_INPUT_ROUND_KEY(RoundCount, 1), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 2), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 3), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 4), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 5), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 6), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 7), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 8), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 9), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 10), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 11), + AES_DEC_INPUT_LAST_ROUND_KEYS(RoundCount) + ); + + /* Store result. */ + AES_ENC_DEC_STORE_RESULT(); + } + + template<> + void AesImpl<32>::DecryptBlock(void *dst, size_t dst_size, const void *src, size_t src_size) const { + AMS_UNUSED(src_size, dst_size); + + /* Setup for asm */ + AES_ENC_DEC_SETUP_VARS(); + + /* Use optimized assembly to do all rounds. */ + __asm__ __volatile__ ( + AES_DEC_ROUND(1) + AES_DEC_ROUND(2) + AES_DEC_ROUND(3) + AES_DEC_ROUND(4) + AES_DEC_ROUND(5) + AES_DEC_ROUND(6) + AES_DEC_ROUND(7) + AES_DEC_ROUND(8) + AES_DEC_ROUND(9) + AES_DEC_ROUND(10) + AES_DEC_ROUND(11) + AES_DEC_ROUND(12) + AES_DEC_ROUND(13) + AES_DEC_FINAL_ROUND() + : AES_ENC_DEC_OUTPUT_VARS() + : AES_DEC_INPUT_ROUND_KEY(RoundCount, 1), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 2), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 3), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 4), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 5), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 6), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 7), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 8), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 9), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 10), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 11), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 12), + AES_DEC_INPUT_ROUND_KEY(RoundCount, 13), + AES_DEC_INPUT_LAST_ROUND_KEYS(RoundCount) + ); + + /* Store result. */ + AES_ENC_DEC_STORE_RESULT(); + } + + #endif + + /* Explicitly instantiate the three supported key sizes. */ + template class AesImpl<16>; + template class AesImpl<24>; + template class AesImpl<32>; + +#else + + /* NOTE: Exosphere defines this in libexosphere. */ + + /* TODO: Non-EL0 implementation. */ + +#endif + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_aes_impl.arch.x64.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_aes_impl.arch.x64.cpp new file mode 100644 index 00000000..be40a4fc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_aes_impl.arch.x64.cpp @@ -0,0 +1,435 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> +#include "crypto_aes_impl.arch.x64.hpp" + +namespace ams::crypto::impl { + + namespace { + + constexpr bool IsSupportedKeySize(size_t size) { + return size == 16 || size == 24 || size == 32; + } + + constexpr int WordsPerBlock = AesImpl<16>::BlockSize / sizeof(u32); + static_assert(AesImpl<16>::BlockSize == AesImpl<24>::BlockSize); + static_assert(AesImpl<16>::BlockSize == AesImpl<32>::BlockSize); + + bool GetAesNiAvailabilityImpl() { + /* Call cpu id. */ + int a = 0, b = 0, c = 0, d = 0; + __asm__ __volatile__("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "0"(1) : "memory"); + + /* Check for AES-NI and SSE2. */ + return (c & (1 << 25)) && (d & (1 << 26)); + } + + static_assert(util::IsLittleEndian()); + + constexpr const u8 RoundKeyRcon0[] = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A, 0x2F, + 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A, 0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, + }; + + constexpr const u8 SubBytesTable[0x100] = { + 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, + 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, + 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, + 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, + 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, + 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, + 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, + 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, + 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, + 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, + 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, + 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, + 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, + 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, + 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, + 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16, + }; + + constexpr const u8 InvSubBytesTable[0x100] = { + 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, + 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, + 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, + 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, + 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, + 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, + 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, + 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, + 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, + 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, + 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, + 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, + 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, + 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, + 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D, + }; + + constexpr bool IsSubBytesTableValid() { + for (size_t i = 0; i < 0x100; ++i) { + if (SubBytesTable[InvSubBytesTable[i]] != i) { + return false; + } + if (InvSubBytesTable[SubBytesTable[i]] != i) { + return false; + } + } + + return true; + } + + static_assert(IsSubBytesTableValid()); + + constexpr const u32 EncryptTable[0x100] = { + 0xA56363C6, 0x847C7CF8, 0x997777EE, 0x8D7B7BF6, 0x0DF2F2FF, 0xBD6B6BD6, 0xB16F6FDE, 0x54C5C591, + 0x50303060, 0x03010102, 0xA96767CE, 0x7D2B2B56, 0x19FEFEE7, 0x62D7D7B5, 0xE6ABAB4D, 0x9A7676EC, + 0x45CACA8F, 0x9D82821F, 0x40C9C989, 0x877D7DFA, 0x15FAFAEF, 0xEB5959B2, 0xC947478E, 0x0BF0F0FB, + 0xECADAD41, 0x67D4D4B3, 0xFDA2A25F, 0xEAAFAF45, 0xBF9C9C23, 0xF7A4A453, 0x967272E4, 0x5BC0C09B, + 0xC2B7B775, 0x1CFDFDE1, 0xAE93933D, 0x6A26264C, 0x5A36366C, 0x413F3F7E, 0x02F7F7F5, 0x4FCCCC83, + 0x5C343468, 0xF4A5A551, 0x34E5E5D1, 0x08F1F1F9, 0x937171E2, 0x73D8D8AB, 0x53313162, 0x3F15152A, + 0x0C040408, 0x52C7C795, 0x65232346, 0x5EC3C39D, 0x28181830, 0xA1969637, 0x0F05050A, 0xB59A9A2F, + 0x0907070E, 0x36121224, 0x9B80801B, 0x3DE2E2DF, 0x26EBEBCD, 0x6927274E, 0xCDB2B27F, 0x9F7575EA, + 0x1B090912, 0x9E83831D, 0x742C2C58, 0x2E1A1A34, 0x2D1B1B36, 0xB26E6EDC, 0xEE5A5AB4, 0xFBA0A05B, + 0xF65252A4, 0x4D3B3B76, 0x61D6D6B7, 0xCEB3B37D, 0x7B292952, 0x3EE3E3DD, 0x712F2F5E, 0x97848413, + 0xF55353A6, 0x68D1D1B9, 0x00000000, 0x2CEDEDC1, 0x60202040, 0x1FFCFCE3, 0xC8B1B179, 0xED5B5BB6, + 0xBE6A6AD4, 0x46CBCB8D, 0xD9BEBE67, 0x4B393972, 0xDE4A4A94, 0xD44C4C98, 0xE85858B0, 0x4ACFCF85, + 0x6BD0D0BB, 0x2AEFEFC5, 0xE5AAAA4F, 0x16FBFBED, 0xC5434386, 0xD74D4D9A, 0x55333366, 0x94858511, + 0xCF45458A, 0x10F9F9E9, 0x06020204, 0x817F7FFE, 0xF05050A0, 0x443C3C78, 0xBA9F9F25, 0xE3A8A84B, + 0xF35151A2, 0xFEA3A35D, 0xC0404080, 0x8A8F8F05, 0xAD92923F, 0xBC9D9D21, 0x48383870, 0x04F5F5F1, + 0xDFBCBC63, 0xC1B6B677, 0x75DADAAF, 0x63212142, 0x30101020, 0x1AFFFFE5, 0x0EF3F3FD, 0x6DD2D2BF, + 0x4CCDCD81, 0x140C0C18, 0x35131326, 0x2FECECC3, 0xE15F5FBE, 0xA2979735, 0xCC444488, 0x3917172E, + 0x57C4C493, 0xF2A7A755, 0x827E7EFC, 0x473D3D7A, 0xAC6464C8, 0xE75D5DBA, 0x2B191932, 0x957373E6, + 0xA06060C0, 0x98818119, 0xD14F4F9E, 0x7FDCDCA3, 0x66222244, 0x7E2A2A54, 0xAB90903B, 0x8388880B, + 0xCA46468C, 0x29EEEEC7, 0xD3B8B86B, 0x3C141428, 0x79DEDEA7, 0xE25E5EBC, 0x1D0B0B16, 0x76DBDBAD, + 0x3BE0E0DB, 0x56323264, 0x4E3A3A74, 0x1E0A0A14, 0xDB494992, 0x0A06060C, 0x6C242448, 0xE45C5CB8, + 0x5DC2C29F, 0x6ED3D3BD, 0xEFACAC43, 0xA66262C4, 0xA8919139, 0xA4959531, 0x37E4E4D3, 0x8B7979F2, + 0x32E7E7D5, 0x43C8C88B, 0x5937376E, 0xB76D6DDA, 0x8C8D8D01, 0x64D5D5B1, 0xD24E4E9C, 0xE0A9A949, + 0xB46C6CD8, 0xFA5656AC, 0x07F4F4F3, 0x25EAEACF, 0xAF6565CA, 0x8E7A7AF4, 0xE9AEAE47, 0x18080810, + 0xD5BABA6F, 0x887878F0, 0x6F25254A, 0x722E2E5C, 0x241C1C38, 0xF1A6A657, 0xC7B4B473, 0x51C6C697, + 0x23E8E8CB, 0x7CDDDDA1, 0x9C7474E8, 0x211F1F3E, 0xDD4B4B96, 0xDCBDBD61, 0x868B8B0D, 0x858A8A0F, + 0x907070E0, 0x423E3E7C, 0xC4B5B571, 0xAA6666CC, 0xD8484890, 0x05030306, 0x01F6F6F7, 0x120E0E1C, + 0xA36161C2, 0x5F35356A, 0xF95757AE, 0xD0B9B969, 0x91868617, 0x58C1C199, 0x271D1D3A, 0xB99E9E27, + 0x38E1E1D9, 0x13F8F8EB, 0xB398982B, 0x33111122, 0xBB6969D2, 0x70D9D9A9, 0x898E8E07, 0xA7949433, + 0xB69B9B2D, 0x221E1E3C, 0x92878715, 0x20E9E9C9, 0x49CECE87, 0xFF5555AA, 0x78282850, 0x7ADFDFA5, + 0x8F8C8C03, 0xF8A1A159, 0x80898909, 0x170D0D1A, 0xDABFBF65, 0x31E6E6D7, 0xC6424284, 0xB86868D0, + 0xC3414182, 0xB0999929, 0x772D2D5A, 0x110F0F1E, 0xCBB0B07B, 0xFC5454A8, 0xD6BBBB6D, 0x3A16162C, + }; + + constexpr const u32 DecryptTable[0x100] = { + 0x50A7F451, 0x5365417E, 0xC3A4171A, 0x965E273A, 0xCB6BAB3B, 0xF1459D1F, 0xAB58FAAC, 0x9303E34B, + 0x55FA3020, 0xF66D76AD, 0x9176CC88, 0x254C02F5, 0xFCD7E54F, 0xD7CB2AC5, 0x80443526, 0x8FA362B5, + 0x495AB1DE, 0x671BBA25, 0x980EEA45, 0xE1C0FE5D, 0x02752FC3, 0x12F04C81, 0xA397468D, 0xC6F9D36B, + 0xE75F8F03, 0x959C9215, 0xEB7A6DBF, 0xDA595295, 0x2D83BED4, 0xD3217458, 0x2969E049, 0x44C8C98E, + 0x6A89C275, 0x78798EF4, 0x6B3E5899, 0xDD71B927, 0xB64FE1BE, 0x17AD88F0, 0x66AC20C9, 0xB43ACE7D, + 0x184ADF63, 0x82311AE5, 0x60335197, 0x457F5362, 0xE07764B1, 0x84AE6BBB, 0x1CA081FE, 0x942B08F9, + 0x58684870, 0x19FD458F, 0x876CDE94, 0xB7F87B52, 0x23D373AB, 0xE2024B72, 0x578F1FE3, 0x2AAB5566, + 0x0728EBB2, 0x03C2B52F, 0x9A7BC586, 0xA50837D3, 0xF2872830, 0xB2A5BF23, 0xBA6A0302, 0x5C8216ED, + 0x2B1CCF8A, 0x92B479A7, 0xF0F207F3, 0xA1E2694E, 0xCDF4DA65, 0xD5BE0506, 0x1F6234D1, 0x8AFEA6C4, + 0x9D532E34, 0xA055F3A2, 0x32E18A05, 0x75EBF6A4, 0x39EC830B, 0xAAEF6040, 0x069F715E, 0x51106EBD, + 0xF98A213E, 0x3D06DD96, 0xAE053EDD, 0x46BDE64D, 0xB58D5491, 0x055DC471, 0x6FD40604, 0xFF155060, + 0x24FB9819, 0x97E9BDD6, 0xCC434089, 0x779ED967, 0xBD42E8B0, 0x888B8907, 0x385B19E7, 0xDBEEC879, + 0x470A7CA1, 0xE90F427C, 0xC91E84F8, 0x00000000, 0x83868009, 0x48ED2B32, 0xAC70111E, 0x4E725A6C, + 0xFBFF0EFD, 0x5638850F, 0x1ED5AE3D, 0x27392D36, 0x64D90F0A, 0x21A65C68, 0xD1545B9B, 0x3A2E3624, + 0xB1670A0C, 0x0FE75793, 0xD296EEB4, 0x9E919B1B, 0x4FC5C080, 0xA220DC61, 0x694B775A, 0x161A121C, + 0x0ABA93E2, 0xE52AA0C0, 0x43E0223C, 0x1D171B12, 0x0B0D090E, 0xADC78BF2, 0xB9A8B62D, 0xC8A91E14, + 0x8519F157, 0x4C0775AF, 0xBBDD99EE, 0xFD607FA3, 0x9F2601F7, 0xBCF5725C, 0xC53B6644, 0x347EFB5B, + 0x7629438B, 0xDCC623CB, 0x68FCEDB6, 0x63F1E4B8, 0xCADC31D7, 0x10856342, 0x40229713, 0x2011C684, + 0x7D244A85, 0xF83DBBD2, 0x1132F9AE, 0x6DA129C7, 0x4B2F9E1D, 0xF330B2DC, 0xEC52860D, 0xD0E3C177, + 0x6C16B32B, 0x99B970A9, 0xFA489411, 0x2264E947, 0xC48CFCA8, 0x1A3FF0A0, 0xD82C7D56, 0xEF903322, + 0xC74E4987, 0xC1D138D9, 0xFEA2CA8C, 0x360BD498, 0xCF81F5A6, 0x28DE7AA5, 0x268EB7DA, 0xA4BFAD3F, + 0xE49D3A2C, 0x0D927850, 0x9BCC5F6A, 0x62467E54, 0xC2138DF6, 0xE8B8D890, 0x5EF7392E, 0xF5AFC382, + 0xBE805D9F, 0x7C93D069, 0xA92DD56F, 0xB31225CF, 0x3B99ACC8, 0xA77D1810, 0x6E639CE8, 0x7BBB3BDB, + 0x097826CD, 0xF418596E, 0x01B79AEC, 0xA89A4F83, 0x656E95E6, 0x7EE6FFAA, 0x08CFBC21, 0xE6E815EF, + 0xD99BE7BA, 0xCE366F4A, 0xD4099FEA, 0xD67CB029, 0xAFB2A431, 0x31233F2A, 0x3094A5C6, 0xC066A235, + 0x37BC4E74, 0xA6CA82FC, 0xB0D090E0, 0x15D8A733, 0x4A9804F1, 0xF7DAEC41, 0x0E50CD7F, 0x2FF69117, + 0x8DD64D76, 0x4DB0EF43, 0x544DAACC, 0xDF0496E4, 0xE3B5D19E, 0x1B886A4C, 0xB81F2CC1, 0x7F516546, + 0x04EA5E9D, 0x5D358C01, 0x737487FA, 0x2E410BFB, 0x5A1D67B3, 0x52D2DB92, 0x335610E9, 0x1347D66D, + 0x8C61D79A, 0x7A0CA137, 0x8E14F859, 0x893C13EB, 0xEE27A9CE, 0x35C961B7, 0xEDE51CE1, 0x3CB1477A, + 0x59DFD29C, 0x3F73F255, 0x79CE1418, 0xBF37C773, 0xEACDF753, 0x5BAAFD5F, 0x146F3DDF, 0x86DB4478, + 0x81F3AFCA, 0x3EC468B9, 0x2C342438, 0x5F40A3C2, 0x72C31D16, 0x0C25E2BC, 0x8B493C28, 0x41950DFF, + 0x7101A839, 0xDEB30C08, 0x9CE4B4D8, 0x90C15664, 0x6184CB7B, 0x70B632D5, 0x745C6C48, 0x4257B8D0, + }; + + constexpr auto AesWordByte0Shift = 0 * BITSIZEOF(u8); + constexpr auto AesWordByte1Shift = 1 * BITSIZEOF(u8); + constexpr auto AesWordByte2Shift = 2 * BITSIZEOF(u8); + constexpr auto AesWordByte3Shift = 3 * BITSIZEOF(u8); + + constexpr auto AesMixShift = 3 * BITSIZEOF(u8); + + constexpr void InverseMixColumns(u32 *dst, const u32 *src) { + for (auto i = 0; i < WordsPerBlock; ++i) { + const u32 v0 = src[i]; + const u32 v1 = (((v0 & 0x7F7F7F7Fu) << 1) ^ (((v0 & 0x80808080) >> 7) * 0x1B)); + const u32 v2 = (((v1 & 0x7F7F7F7Fu) << 1) ^ (((v1 & 0x80808080) >> 7) * 0x1B)); + const u32 v3 = (((v2 & 0x7F7F7F7Fu) << 1) ^ (((v2 & 0x80808080) >> 7) * 0x1B)); + + u32 v = v0 ^ v3; + v ^= util::RotateLeft(v, AesMixShift) ^ v2; + v ^= util::RotateLeft(v, AesMixShift) ^ v1; + v ^= util::RotateLeft(v, AesMixShift) ^ v0; + + dst[i] = v; + } + } + + constexpr u32 SubBytesAndRotate(u32 v) { + return (static_cast<u32>(SubBytesTable[(v >> AesWordByte0Shift) & 0xFFu]) << AesWordByte3Shift) ^ + (static_cast<u32>(SubBytesTable[(v >> AesWordByte1Shift) & 0xFFu]) << AesWordByte0Shift) ^ + (static_cast<u32>(SubBytesTable[(v >> AesWordByte2Shift) & 0xFFu]) << AesWordByte1Shift) ^ + (static_cast<u32>(SubBytesTable[(v >> AesWordByte3Shift) & 0xFFu]) << AesWordByte2Shift); + } + + constexpr u32 SubBytes(u32 v) { + return (static_cast<u32>(SubBytesTable[(v >> AesWordByte0Shift) & 0xFFu]) << AesWordByte0Shift) ^ + (static_cast<u32>(SubBytesTable[(v >> AesWordByte1Shift) & 0xFFu]) << AesWordByte1Shift) ^ + (static_cast<u32>(SubBytesTable[(v >> AesWordByte2Shift) & 0xFFu]) << AesWordByte2Shift) ^ + (static_cast<u32>(SubBytesTable[(v >> AesWordByte3Shift) & 0xFFu]) << AesWordByte3Shift); + } + + constexpr u32 ShiftSubMix(u32 v0, u32 v1, u32 v2, u32 v3) { + return (util::RotateLeft(static_cast<u32>(EncryptTable[(v0 >> AesWordByte0Shift) & 0xFFu]), AesWordByte0Shift)) ^ + (util::RotateLeft(static_cast<u32>(EncryptTable[(v1 >> AesWordByte1Shift) & 0xFFu]), AesWordByte1Shift)) ^ + (util::RotateLeft(static_cast<u32>(EncryptTable[(v2 >> AesWordByte2Shift) & 0xFFu]), AesWordByte2Shift)) ^ + (util::RotateLeft(static_cast<u32>(EncryptTable[(v3 >> AesWordByte3Shift) & 0xFFu]), AesWordByte3Shift)); + } + + constexpr u32 ShiftSub(u32 v0, u32 v1, u32 v2, u32 v3) { + return (static_cast<u32>(SubBytesTable[(v0 >> AesWordByte0Shift) & 0xFFu]) << AesWordByte0Shift) ^ + (static_cast<u32>(SubBytesTable[(v1 >> AesWordByte1Shift) & 0xFFu]) << AesWordByte1Shift) ^ + (static_cast<u32>(SubBytesTable[(v2 >> AesWordByte2Shift) & 0xFFu]) << AesWordByte2Shift) ^ + (static_cast<u32>(SubBytesTable[(v3 >> AesWordByte3Shift) & 0xFFu]) << AesWordByte3Shift); + } + + constexpr u32 InvShiftSubMix(u32 v0, u32 v1, u32 v2, u32 v3) { + return (util::RotateLeft(static_cast<u32>(DecryptTable[(v0 >> AesWordByte0Shift) & 0xFFu]), AesWordByte0Shift)) ^ + (util::RotateLeft(static_cast<u32>(DecryptTable[(v1 >> AesWordByte1Shift) & 0xFFu]), AesWordByte1Shift)) ^ + (util::RotateLeft(static_cast<u32>(DecryptTable[(v2 >> AesWordByte2Shift) & 0xFFu]), AesWordByte2Shift)) ^ + (util::RotateLeft(static_cast<u32>(DecryptTable[(v3 >> AesWordByte3Shift) & 0xFFu]), AesWordByte3Shift)); + } + + constexpr u32 InvShiftSub(u32 v0, u32 v1, u32 v2, u32 v3) { + return (static_cast<u32>(InvSubBytesTable[(v0 >> AesWordByte0Shift) & 0xFFu]) << AesWordByte0Shift) ^ + (static_cast<u32>(InvSubBytesTable[(v1 >> AesWordByte1Shift) & 0xFFu]) << AesWordByte1Shift) ^ + (static_cast<u32>(InvSubBytesTable[(v2 >> AesWordByte2Shift) & 0xFFu]) << AesWordByte2Shift) ^ + (static_cast<u32>(InvSubBytesTable[(v3 >> AesWordByte3Shift) & 0xFFu]) << AesWordByte3Shift); + } + + } + + const bool g_is_aes_ni_available = GetAesNiAvailabilityImpl(); + + template<size_t KeySize> + AesImpl<KeySize>::~AesImpl() { + ClearMemory(this, sizeof(*this)); + } + + template<size_t KeySize> + void AesImpl<KeySize>::Initialize(const void *key, size_t key_size, bool is_encrypt) { + /* Check pre-conditions. */ + static_assert(IsSupportedKeySize(KeySize)); + AMS_ASSERT(key != nullptr); + AMS_ASSERT(key_size == KeySize); + AMS_UNUSED(key_size); + + /* Set up key. */ + u32 *dst = m_round_keys; + std::memcpy(dst, key, KeySize); + + /* Perform key scheduling. */ + constexpr auto InitialKeyWords = KeySize / sizeof(u32); + u32 tmp = dst[InitialKeyWords - 1]; + + for (auto i = InitialKeyWords; i < (RoundCount + 1) * 4; ++i) { + const auto idx_in_key = i % InitialKeyWords; + if (idx_in_key == 0) { + /* At start of key word, we need to handle sub/rotate/rcon. */ + tmp = SubBytesAndRotate(tmp); + tmp ^= (RoundKeyRcon0[i / InitialKeyWords - 1] << AesWordByte0Shift); + } else if ((InitialKeyWords > 6) && idx_in_key == 4) { + /* Halfway into a 256-bit key word, we need to do an additional subbytes. */ + tmp = SubBytes(tmp); + } + + /* Set the key word. */ + tmp ^= dst[i - InitialKeyWords]; + dst[i] = tmp; + } + + /* If decrypting, perform inverse mix columns on all round keys. */ + if (!is_encrypt) { + if (IsAesNiAvailable()) { + auto *key8 = reinterpret_cast<u8 *>(m_round_keys) + BlockSize; + + for (auto i = 1; i < RoundCount; ++i) { + auto * const key128 = reinterpret_cast<__m128i *>(key8); + _mm_storeu_si128(key128, _mm_aesimc_si128(_mm_loadu_si128(key128))); + key8 += BlockSize; + } + } else { + for (auto i = 1; i < RoundCount; ++i) { + InverseMixColumns(m_round_keys + WordsPerBlock * i, m_round_keys + WordsPerBlock * i); + } + } + } + } + + template<size_t KeySize> + void AesImpl<KeySize>::EncryptBlock(void *dst, size_t dst_size, const void *src, size_t src_size) const { + static_assert(IsSupportedKeySize(KeySize)); + AMS_ASSERT(dst_size == BlockSize && src_size == BlockSize); + AMS_UNUSED(dst_size, src_size); + + /* Perform block encryption. */ + if (IsAesNiAvailable()) { + const auto *key8 = reinterpret_cast<const u8 *>(m_round_keys); + + /* Load the block. */ + auto block = _mm_loadu_si128(reinterpret_cast<const __m128i *>(src)); + + /* Add the first round key. */ + block = _mm_xor_si128(block, _mm_loadu_si128(reinterpret_cast<const __m128i *>(key8))); + key8 += BlockSize; + + /* Perform aes round on remaining round keys. */ + for (auto i = 1; i < RoundCount; ++i) { + block = _mm_aesenc_si128(block, _mm_loadu_si128(reinterpret_cast<const __m128i *>(key8))); + key8 += BlockSize; + } + + /* Do final update. */ + block = _mm_aesenclast_si128(block, _mm_loadu_si128(reinterpret_cast<const __m128i *>(key8))); + + /* Store the output. */ + _mm_storeu_si128(reinterpret_cast<__m128i *>(dst), block); + } else { + static_assert(WordsPerBlock == 4); + + /* Without AES-NI, we'll operate on words. */ + const u32 *key32 = m_round_keys; + const u32 *src32 = static_cast<const u32 *>(src); + u32 *dst32 = static_cast< u32 *>(dst); + + /* Add the first round key. */ + u32 v0 = src32[0] ^ key32[0]; + u32 v1 = src32[1] ^ key32[1]; + u32 v2 = src32[2] ^ key32[2]; + u32 v3 = src32[3] ^ key32[3]; + key32 += 4; + + /* Perform each round. */ + auto round = RoundCount; + while (--round > 0) { + /* Perform aes round. */ + const u32 e0 = ShiftSubMix(v0, v1, v2, v3); + const u32 e1 = ShiftSubMix(v1, v2, v3, v0); + const u32 e2 = ShiftSubMix(v2, v3, v0, v1); + const u32 e3 = ShiftSubMix(v3, v0, v1, v2); + + /* Add the round key. */ + v0 = e0 ^ key32[0]; + v1 = e1 ^ key32[1]; + v2 = e2 ^ key32[2]; + v3 = e3 ^ key32[3]; + key32 += 4; + } + + /* Perform the final round. */ + dst32[0] = key32[0] ^ ShiftSub(v0, v1, v2, v3); + dst32[1] = key32[1] ^ ShiftSub(v1, v2, v3, v0); + dst32[2] = key32[2] ^ ShiftSub(v2, v3, v0, v1); + dst32[3] = key32[3] ^ ShiftSub(v3, v0, v1, v2); + } + } + + template<size_t KeySize> + void AesImpl<KeySize>::DecryptBlock(void *dst, size_t dst_size, const void *src, size_t src_size) const { + static_assert(IsSupportedKeySize(KeySize)); + AMS_ASSERT(dst_size == BlockSize && src_size == BlockSize); + AMS_UNUSED(dst_size, src_size); + + /* Perform block decryption. */ + if (IsAesNiAvailable()) { + const auto *key8 = reinterpret_cast<const u8 *>(m_round_keys) + (RoundCount * BlockSize); + + /* Load the block. */ + auto block = _mm_loadu_si128(reinterpret_cast<const __m128i *>(src)); + + /* Add the final round key. */ + block = _mm_xor_si128(block, _mm_loadu_si128(reinterpret_cast<const __m128i *>(key8))); + key8 -= BlockSize; + + /* Perform aes invround on remaining round keys. */ + for (auto i = RoundCount; i > 1; --i) { + block = _mm_aesdec_si128(block, _mm_loadu_si128(reinterpret_cast<const __m128i *>(key8))); + key8 -= BlockSize; + } + + /* Do final update. */ + block = _mm_aesdeclast_si128(block, _mm_loadu_si128(reinterpret_cast<const __m128i *>(key8))); + + /* Store the output. */ + _mm_storeu_si128(reinterpret_cast<__m128i *>(dst), block); + } else { + static_assert(WordsPerBlock == 4); + + /* Without AES-NI, we'll operate on words. */ + const u32 *key32 = m_round_keys + WordsPerBlock * RoundCount; + const u32 *src32 = static_cast<const u32 *>(src); + u32 *dst32 = static_cast< u32 *>(dst); + + /* Add the final round key. */ + u32 v0 = src32[0] ^ key32[0]; + u32 v1 = src32[1] ^ key32[1]; + u32 v2 = src32[2] ^ key32[2]; + u32 v3 = src32[3] ^ key32[3]; + key32 -= 4; + + /* Perform each round. */ + auto round = RoundCount; + while (--round > 0) { + /* Perform aes inv round. */ + const u32 e0 = InvShiftSubMix(v0, v3, v2, v1); + const u32 e1 = InvShiftSubMix(v1, v0, v3, v2); + const u32 e2 = InvShiftSubMix(v2, v1, v0, v3); + const u32 e3 = InvShiftSubMix(v3, v2, v1, v0); + + /* Add the round key. */ + v0 = e0 ^ key32[0]; + v1 = e1 ^ key32[1]; + v2 = e2 ^ key32[2]; + v3 = e3 ^ key32[3]; + key32 -= 4; + } + + /* Perform the final round. */ + dst32[0] = key32[0] ^ InvShiftSub(v0, v3, v2, v1); + dst32[1] = key32[1] ^ InvShiftSub(v1, v0, v3, v2); + dst32[2] = key32[2] ^ InvShiftSub(v2, v1, v0, v3); + dst32[3] = key32[3] ^ InvShiftSub(v3, v2, v1, v0); + } + } + + /* Explicitly instantiate the three supported key sizes. */ + template class AesImpl<16>; + template class AesImpl<24>; + template class AesImpl<32>; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_aes_impl.arch.x64.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_aes_impl.arch.x64.hpp new file mode 100644 index 00000000..a6563440 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_aes_impl.arch.x64.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <x86intrin.h> + +namespace ams::crypto::impl { + + extern const bool g_is_aes_ni_available; + + ALWAYS_INLINE bool IsAesNiAvailable() { + return g_is_aes_ni_available; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_bignum.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_bignum.cpp new file mode 100644 index 00000000..805c30c9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_bignum.cpp @@ -0,0 +1,148 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::crypto::impl { + + void BigNum::ImportImpl(Word *out, size_t out_size, const u8 *src, size_t src_size) { + size_t octet_ofs = src_size; + size_t word_ofs = 0; + + /* Parse octets into words. */ + while (word_ofs < out_size && octet_ofs > 0) { + Word w = 0; + for (size_t shift = 0; octet_ofs > 0 && shift < BITSIZEOF(Word); shift += BITSIZEOF(u8)) { + w |= static_cast<Word>(src[--octet_ofs]) << shift; + } + out[word_ofs++] = w; + } + + /* Zero-fill upper words. */ + while (word_ofs < out_size) { + out[word_ofs++] = 0; + } + } + + void BigNum::ExportImpl(u8 *out, size_t out_size, const Word *src, size_t src_size) { + size_t octet_ofs = out_size; + + /* Parse words into octets. */ + for (size_t word_ofs = 0; word_ofs < src_size && octet_ofs > 0; word_ofs++) { + const Word w = src[word_ofs]; + for (size_t shift = 0; octet_ofs > 0 && shift < BITSIZEOF(Word); shift += BITSIZEOF(u8)) { + out[--octet_ofs] = static_cast<u8>(w >> shift); + } + } + + /* Zero-clear remaining octets. */ + while (octet_ofs > 0) { + out[--octet_ofs] = 0; + } + } + + size_t BigNum::GetSize() const { + if (m_num_words == 0) { + return 0; + } + static_assert(sizeof(Word) == 4); + + size_t size = m_num_words * sizeof(Word); + const Word last = m_words[m_num_words - 1]; + AMS_ASSERT(last != 0); + if (last >= 0x01000000u) { + return size - 0; + } else if (last >= 0x00010000u) { + return size - 1; + } else if (last >= 0x00000100u) { + return size - 2; + } else { + return size - 3; + } + } + + bool BigNum::Import(const void *src, size_t src_size) { + AMS_ASSERT((src != nullptr) || (src_size != 0)); + + /* Ignore leading zeroes. */ + const u8 *data = static_cast<const u8 *>(src); + while (src_size > 0 && *data == 0) { + ++data; + --src_size; + } + + /* Ensure we have space for the number. */ + AMS_ASSERT(src_size <= m_max_words * sizeof(Word)); + if (AMS_UNLIKELY(!(src_size <= m_max_words * sizeof(Word)))) { + return false; + } + + /* Import. */ + m_num_words = util::AlignUp(src_size, sizeof(Word)) / sizeof(Word); + + ImportImpl(m_words, m_max_words, data, src_size); + return true; + } + + void BigNum::Export(void *dst, size_t dst_size) { + AMS_ASSERT(dst_size >= this->GetSize()); + ExportImpl(static_cast<u8 *>(dst), dst_size, m_words, m_num_words); + } + + bool BigNum::ExpMod(void *dst, const void *src, size_t size, const BigNum &exp, u32 *work_buf, size_t work_buf_size) const { + /* Can't exponentiate with or about zero. */ + if (this->IsZero() || exp.IsZero()) { + return false; + } + AMS_ASSERT(size == this->GetSize()); + + /* Create an allocator. */ + WordAllocator allocator(work_buf, work_buf_size / sizeof(Word)); + ON_SCOPE_EXIT { ClearMemory(work_buf, allocator.GetMaxUsedSize()); }; + + /* Create a BigNum for the signature. */ + BigNum signature; + auto signature_words = allocator.Allocate(size / sizeof(Word)); + if (!signature_words.IsValid()) { + return false; + } + + /* Import data for the signature. */ + signature.ReserveStatic(signature_words.GetBuffer(), signature_words.GetCount()); + if (!signature.Import(src, size)) { + return false; + } + + /* Perform the exponentiation. */ + if (!ExpMod(signature.m_words, signature.m_words, exp.m_words, exp.m_num_words, m_words, m_num_words, std::addressof(allocator))) { + return false; + } + + /* We succeeded, so export. */ + signature.UpdateCount(); + signature.Export(dst, size); + + return true; + } + + void BigNum::ClearToZero() { + std::memset(m_words, 0, m_num_words * sizeof(Word)); + } + + void BigNum::UpdateCount() { + m_num_words = CountWords(m_words, m_max_words); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_bignum_operations.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_bignum_operations.cpp new file mode 100644 index 00000000..75042c58 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_bignum_operations.cpp @@ -0,0 +1,490 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::crypto::impl { + + namespace { + + constexpr ALWAYS_INLINE BigNum::Word GetTop2Bits(BigNum::Word w) { + return (w >> (BigNum::BitsPerWord - 2)) & 0x3u; + } + + constexpr ALWAYS_INLINE void MultWord(BigNum::Word *dst, BigNum::Word lhs, BigNum::Word rhs) { + static_assert(sizeof(BigNum::DoubleWord) == sizeof(BigNum::Word) * 2); + BigNum::DoubleWord result = static_cast<BigNum::DoubleWord>(lhs) * static_cast<BigNum::DoubleWord>(rhs); + dst[0] = static_cast<BigNum::Word>(result & ~BigNum::Word()); + dst[1] = static_cast<BigNum::Word>(result >> BITSIZEOF(BigNum::Word)); + } + + constexpr ALWAYS_INLINE BigNum::HalfWord GetUpperHalf(BigNum::Word word) { + static_assert(sizeof(BigNum::Word) == sizeof(BigNum::HalfWord) * 2); + return static_cast<BigNum::HalfWord>((word >> BITSIZEOF(BigNum::HalfWord)) & ~BigNum::HalfWord()); + } + + constexpr ALWAYS_INLINE BigNum::HalfWord GetLowerHalf(BigNum::Word word) { + static_assert(sizeof(BigNum::Word) == sizeof(BigNum::HalfWord) * 2); + return static_cast<BigNum::HalfWord>(word & ~BigNum::HalfWord()); + } + + constexpr ALWAYS_INLINE BigNum::Word ToUpperHalf(BigNum::HalfWord half) { + static_assert(sizeof(BigNum::Word) == sizeof(BigNum::HalfWord) * 2); + return static_cast<BigNum::Word>(half) << BITSIZEOF(BigNum::HalfWord); + } + + [[maybe_unused]] constexpr ALWAYS_INLINE BigNum::Word ToLowerHalf(BigNum::HalfWord half) { + static_assert(sizeof(BigNum::Word) == sizeof(BigNum::HalfWord) * 2); + return static_cast<BigNum::Word>(half); + } + + constexpr ALWAYS_INLINE BigNum::Word DivWord(const BigNum::Word *w, BigNum::Word div) { + using Word = BigNum::Word; + using HalfWord = BigNum::HalfWord; + + Word work[2] = { w[0], w[1] }; + HalfWord r_hi = 0, r_lo = 0; + + HalfWord d_hi = GetUpperHalf(div); + HalfWord d_lo = GetLowerHalf(div); + + if (d_hi == BigNum::MaxHalfWord) { + r_hi = GetUpperHalf(work[1]); + } else { + r_hi = GetLowerHalf(work[1] / (d_hi + 1)); + } + + { + const Word hh = static_cast<Word>(r_hi) * static_cast<Word>(d_hi); + const Word hl = static_cast<Word>(r_hi) * static_cast<Word>(d_lo); + + const Word uhl = ToUpperHalf(static_cast<HalfWord>(hl)); + if ((work[0] -= uhl) > (BigNum::MaxWord - uhl)) { + work[1]--; + } + work[1] -= GetUpperHalf(hl); + work[1] -= hh; + + const Word udl = ToUpperHalf(d_lo); + while (work[1] > d_hi || (work[1] == d_hi && work[0] >= udl)) { + if ((work[0] -= udl) > (BigNum::MaxWord - udl)) { + work[1]--; + } + work[1] -= d_hi; + r_hi++; + } + } + + if (d_hi == BigNum::MaxHalfWord) { + r_lo = GetLowerHalf(work[1]); + } else { + r_lo = GetLowerHalf((ToUpperHalf(static_cast<HalfWord>(work[1])) + GetUpperHalf(work[0])) / (d_hi + 1)); + } + + { + const Word ll = static_cast<Word>(r_lo) * static_cast<Word>(d_lo); + const Word lh = static_cast<Word>(r_lo) * static_cast<Word>(d_hi); + + if ((work[0] -= ll) > (BigNum::MaxWord - ll)) { + work[1]--; + } + + const Word ulh = ToUpperHalf(static_cast<HalfWord>(lh)); + if ((work[0] -= ulh) > (BigNum::MaxWord - ulh)) { + work[1]--; + } + work[1] -= GetUpperHalf(lh); + + while ((work[1] > 0) || (work[1] == 0 && work[0] >= div)) { + if ((work[0] -= div) > (BigNum::MaxWord - div)) { + work[1]--; + } + r_lo++; + } + } + + return ToUpperHalf(r_hi) + r_lo; + } + + } + + bool BigNum::IsZero(const Word *w, size_t num_words) { + for (size_t i = 0; i < num_words; i++) { + if (w[i]) { + return false; + } + } + return true; + } + + int BigNum::Compare(const Word *lhs, const Word *rhs, size_t num_words) { + for (s32 i = static_cast<s32>(num_words) - 1; i >= 0; i--) { + if (lhs[i] > rhs[i]) { + return 1; + } else if (lhs[i] < rhs[i]) { + return -1; + } + } + return 0; + } + + size_t BigNum::CountWords(const Word *w, size_t num_words) { + s32 i = static_cast<s32>(num_words) - 1; + while (i >= 0 && !w[i]) { + i--; + } + return i + 1; + } + + size_t BigNum::CountSignificantBits(Word w) { + size_t i; + for (i = 0; i < BitsPerWord && w != 0; i++) { + w >>= 1; + } + return i; + } + + void BigNum::ClearToZero(Word *w, size_t num_words) { + for (size_t i = 0; i < num_words; i++) { + w[i] = 0; + } + } + + void BigNum::SetToWord(Word *w, size_t num_words, Word v) { + ClearToZero(w, num_words); + w[0] = v; + } + + void BigNum::Copy(Word *dst, const Word *src, size_t num_words) { + for (size_t i = 0; i < num_words; i++) { + dst[i] = src[i]; + } + } + + BigNum::Word BigNum::LeftShift(Word *dst, const Word *w, size_t num_words, const size_t shift) { + if (shift >= BitsPerWord) { + return 0; + } + + const size_t invshift = BitsPerWord - shift; + Word carry = 0; + for (size_t i = 0; i < num_words; i++) { + const Word cur = w[i]; + dst[i] = (cur << shift) | carry; + carry = shift ? (cur >> invshift) : 0; + } + + return carry; + } + + BigNum::Word BigNum::RightShift(Word *dst, const Word *w, size_t num_words, const size_t shift) { + if (shift >= BitsPerWord) { + return 0; + } + + const size_t invshift = BitsPerWord - shift; + Word carry = 0; + for (s32 i = static_cast<s32>(num_words) - 1; i >= 0; i--) { + const Word cur = w[i]; + dst[i] = (cur >> shift) | carry; + carry = shift ? (cur << invshift) : 0; + } + + return carry; + } + + BigNum::Word BigNum::MultSub(Word *dst, const Word *w, const Word *v, size_t num_words, Word mult) { + /* If multiplying by zero, nothing to do. */ + if (mult == 0) { + return 0; + } + + Word borrow = 0, work[2]; + for (size_t i = 0; i < num_words; i++) { + /* Multiply, calculate borrow for next. */ + MultWord(work, mult, v[i]); + if ((dst[i] = (w[i] - borrow)) > (MaxWord - borrow)) { + borrow = 1; + } else { + borrow = 0; + } + + if ((dst[i] -= work[0]) > (MaxWord - work[0])) { + borrow++; + } + borrow += work[1]; + } + + return borrow; + } + + bool BigNum::ExpMod(Word *dst, const Word *src, const Word *exp, size_t exp_words, const Word *mod, size_t mod_words, WordAllocator *allocator) { + /* Nintendo uses an algorithm that relies on powers of exp. */ + bool needs_exp[4] = {}; + if (exp_words > 1) { + needs_exp[2] = true; + needs_exp[3] = true; + } else { + Word exp_w = exp[0]; + + for (size_t i = 0; i < BitsPerWord / 2; i++) { + /* Nintendo at each step determines needed exponent from a pair of two bits. */ + needs_exp[exp_w & 0x3u] = true; + exp_w >>= 2; + } + + if (needs_exp[3]) { + needs_exp[2] = true; + } + } + + /* Allocate space for powers 1, 2, 3. */ + auto power_1 = allocator->Allocate(mod_words); + auto power_2 = allocator->Allocate(mod_words); + auto power_3 = allocator->Allocate(mod_words); + if (!(power_1.IsValid() && power_2.IsValid() && power_3.IsValid())) { + return false; + } + decltype(power_1)* powers[3] = { std::addressof(power_1), std::addressof(power_2), std::addressof(power_3) }; + + /* Set the powers of src. */ + Copy(power_1.GetBuffer(), src, mod_words); + if (needs_exp[2]) { + if (!MultMod(power_2.GetBuffer(), power_1.GetBuffer(), src, mod, mod_words, allocator)) { + return false; + } + } + if (needs_exp[3]) { + if (!MultMod(power_3.GetBuffer(), power_2.GetBuffer(), src, mod, mod_words, allocator)) { + return false; + } + } + + /* Allocate space to work. */ + auto work = allocator->Allocate(mod_words); + if (!work.IsValid()) { + return false; + } + SetToWord(work.GetBuffer(), work.GetCount(), 1); + + /* Ensure we're working with the correct exponent word count. */ + exp_words = CountWords(exp, exp_words); + + for (s32 i = static_cast<s32>(exp_words - 1); i >= 0; i--) { + Word cur_word = exp[i]; + size_t cur_bits = BitsPerWord; + + /* Remove leading zeroes in first word. */ + if (i == static_cast<s32>(exp_words - 1)) { + while (!GetTop2Bits(cur_word)) { + cur_word <<= 2; + cur_bits -= 2; + } + } + + /* Compute current modular multiplicative step. */ + for (size_t j = 0; j < cur_bits; j += 2, cur_word <<= 2) { + /* Exponentiate current work to the 4th power. */ + if (!MultMod(work.GetBuffer(), work.GetBuffer(), work.GetBuffer(), mod, mod_words, allocator)) { + return false; + } + + if (!MultMod(work.GetBuffer(), work.GetBuffer(), work.GetBuffer(), mod, mod_words, allocator)) { + return false; + } + + if (const Word top = GetTop2Bits(cur_word)) { + if (!MultMod(work.GetBuffer(), work.GetBuffer(), powers[top - 1]->GetBuffer(), mod, mod_words, allocator)) { + return false; + } + } + } + } + + /* Copy work to output. */ + Copy(dst, work.GetBuffer(), mod_words); + + return true; + } + + bool BigNum::MultMod(Word *dst, const Word *src, const Word *mult, const Word *mod, size_t num_words, WordAllocator *allocator) { + /* Allocate work. */ + auto work = allocator->Allocate(2 * num_words); + if (!work.IsValid()) { + return false; + } + + /* Multiply. */ + if (!Mult(work.GetBuffer(), src, mult, num_words, allocator)) { + return false; + } + + /* Mod. */ + if (!Mod(dst, work.GetBuffer(), 2 * num_words, mod, num_words, allocator)) { + return false; + } + + return true; + } + + bool BigNum::Mod(Word *dst, const Word *src, size_t src_words, const Word *mod, size_t mod_words, WordAllocator *allocator) { + /* Allocate work. */ + auto work = allocator->Allocate(src_words); + if (!work.IsValid()) { + return false; + } + + if (!DivMod(work.GetBuffer(), dst, src, src_words, mod, mod_words, allocator)) { + return false; + } + + return true; + } + + bool BigNum::DivMod(Word *quot, Word *rem, const Word *top, size_t top_words, const Word *bot, size_t bot_words, WordAllocator *allocator) { + /* Allocate work. */ + auto top_work = allocator->Allocate(top_words + 1); + auto bot_work = allocator->Allocate(bot_words); + if (!(top_work.IsValid() && bot_work.IsValid())) { + return false; + } + + /* Prevent division by zero. */ + size_t bot_work_words = CountWords(bot, bot_words); + if (bot_work_words == 0) { + return false; + } + + ClearToZero(quot, top_words); + ClearToZero(top_work.GetBuffer(), bot_work_words); + + /* Align to edges. */ + const size_t shift = BitsPerWord - CountSignificantBits(bot[bot_work_words - 1]); + top_work.GetBuffer()[top_words] = LeftShift(top_work.GetBuffer(), top, top_words, shift); + LeftShift(bot_work.GetBuffer(), bot, bot_work_words, shift); + const Word tb = bot_work.GetBuffer()[bot_work_words - 1]; + + /* Repeatedly div + sub. */ + for (s32 i = (top_words - bot_work_words); i >= 0; i--) { + Word cur_word; + if (tb == MaxWord) { + cur_word = top_work.GetBuffer()[i + bot_work_words]; + } else { + cur_word = DivWord(top_work.GetBuffer() + i + bot_work_words - 1, tb + 1); + } + top_work.GetBuffer()[i + bot_work_words] -= MultSub(top_work.GetBuffer() + i, top_work.GetBuffer() + i, bot_work.GetBuffer(), bot_work_words, cur_word); + + while (top_work.GetBuffer()[i + bot_work_words] || Compare(top_work.GetBuffer() + i, bot_work.GetBuffer(), bot_work_words) >= 0) { + cur_word++; + top_work.GetBuffer()[i + bot_work_words] -= Sub(top_work.GetBuffer() + i, top_work.GetBuffer() + i, bot_work.GetBuffer(), bot_work_words); + } + quot[i] = cur_word; + } + + /* Calculate remainder. */ + ClearToZero(rem, bot_words); + RightShift(rem, top_work.GetBuffer(), bot_work_words, shift); + + return true; + } + + bool BigNum::Mult(Word *dst, const Word *lhs, const Word *rhs, size_t num_words, WordAllocator *allocator) { + /* Allocate work. */ + auto work = allocator->Allocate(2 * num_words); + if (!work.IsValid()) { + return false; + } + ClearToZero(work.GetBuffer(), work.GetCount()); + + /* Repeatedly add and multiply. */ + const size_t lhs_words = CountWords(lhs, num_words); + const size_t rhs_words = CountWords(rhs, num_words); + + for (size_t i = 0; i < lhs_words; i++) { + work.GetBuffer()[i + rhs_words] += MultAdd(work.GetBuffer() + i, rhs, rhs_words, lhs[i]); + } + + /* Copy to output. */ + Copy(dst, work.GetBuffer(), work.GetCount()); + + return true; + } + + #if !defined(ATMOSPHERE_ARCH_ARM64) + BigNum::Word BigNum::Add(Word *dst, const Word *lhs, const Word *rhs, size_t num_words) { + Word carry = 0; + + for (size_t i = 0; i < num_words; ++i) { + Word v; + if ((v = lhs[i] + carry) < carry) { + v = rhs[i]; + } else if ((v += rhs[i]) < rhs[i]) { + carry = 1; + } else { + carry = 0; + } + + dst[i] = v; + } + + return carry; + } + + BigNum::Word BigNum::Sub(Word *dst, const Word *lhs, const Word *rhs, size_t num_words) { + Word borrow = 0; + + for (size_t i = 0; i < num_words; ++i) { + Word v; + if ((v = lhs[i] - borrow) > (BigNum::MaxWord - borrow)) { + v = BigNum::MaxWord - rhs[i]; + } else if ((v -= rhs[i]) > (BigNum::MaxWord - rhs[i])) { + borrow = 1; + } else { + borrow = 0; + } + + dst[i] = v; + } + + return borrow; + } + + BigNum::Word BigNum::MultAdd(Word *dst, const Word *w, size_t num_words, Word mult) { + /* If multiplying by zero, nothing to do. */ + if (mult == 0) { + return 0; + } + + Word carry = 0, work[2]; + for (size_t i = 0; i < num_words; i++) { + /* Multiply, calculate carry for next. */ + MultWord(work, mult, w[i]); + if ((dst[i] += carry) < carry) { + carry = 1; + } else { + carry = 0; + } + + if ((dst[i] += work[0]) < work[0]) { + carry++; + } + carry += work[1]; + } + + return carry; + } + #endif + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_bignum_operations_asm.arch.arm64.s b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_bignum_operations_asm.arch.arm64.s new file mode 100644 index 00000000..437434a7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_bignum_operations_asm.arch.arm64.s @@ -0,0 +1,294 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* ams::crypto::impl::BigNum::Add(Word *dst, const Word *lhs, const Word *rhs, size_t num_words) */ +#if !defined(ATMOSPHERE_OS_MACOS) +.section .text._ZN3ams6crypto4impl6BigNum3AddEPjPKjS5_m, "ax", %progbits +.global _ZN3ams6crypto4impl6BigNum3AddEPjPKjS5_m +.type _ZN3ams6crypto4impl6BigNum3AddEPjPKjS5_m, %function +#else +.text +.global _ZN3ams6crypto4impl6BigNum3AddEPjPKjS5_m +#endif +.balign 0x10 +_ZN3ams6crypto4impl6BigNum3AddEPjPKjPKjm: + /* Check if we have anything to do at all. */ + msr nzcv, xzr + cbz x3, 7f + + /* Save registers. */ + stp x16, x17, [sp, #-16]! + stp xzr, x19, [sp, #-16]! + stp x20, x21, [sp, #-16]! + + /* Check if we have less than 16 words to process. */ + lsr x20, x3, #4 + cbz x20, 2f + + sub x3, x3, x20, lsl #4 + +1: /* Process 16 words at a time. */ + /* NOTE: Nintendo uses X18 here, we will use X21 for EL1+ compat. */ + ldp x4, x5, [x1], #16 + ldp x12, x13, [x2], #16 + ldp x6, x7, [x1], #16 + ldp x14, x15, [x2], #16 + ldp x8, x9, [x1], #16 + ldp x16, x17, [x2], #16 + ldp x10, x11, [x1], #16 + ldp x21, x19, [x2], #16 + + adcs x4, x4, x12 + adcs x5, x5, x13 + stp x4, x5, [x0], #16 + + adcs x6, x6, x14 + adcs x7, x7, x15 + stp x6, x7, [x0], #16 + + adcs x8, x8, x16 + adcs x9, x9, x17 + stp x8, x9, [x0], #16 + + adcs x10, x10, x21 + adcs x11, x11, x19 + stp x10, x11, [x0], #16 + + sub x20, x20, #1 + cbnz x20, 1b + +2: /* We have less than 16 words to process. */ + lsr x15, x3, #2 + cbz x15, 4f + + sub x3, x3, x15, lsl #2 + +3: /* Process 4 words at a time. */ + ldp x4, x5, [x1], #16 + ldp x8, x9, [x2], #16 + + sub x15, x15, #1 + + adcs x4, x4, x8 + adcs x5, x5, x9 + + stp x4, x5, [x0], #16 + + cbnz x15, 3b + +4: /* We have less than 4 words to process. */ + cbz x3, 6f + +5: /* Process 1 word at a time. */ + ldr w4, [x1], #4 + ldr w8, [x2], #4 + adcs w4, w4, w8 + str w4, [x0], #4 + + sub x3, x3, #1 + cbnz x3, 5b + +6: /* Restore registers we used while adding. */ + ldp x20, x21, [sp], #16 + ldp xzr, x19, [sp], #16 + ldp x16, x17, [sp], #16 + +7: /* We're done. */ + adc x0, xzr, xzr + ret + +/* ams::crypto::impl::BigNum::Sub(Word *dst, const Word *lhs, const Word *rhs, size_t num_words) */ +#if !defined(ATMOSPHERE_OS_MACOS) +.section .text._ZN3ams6crypto4impl6BigNum3SubEPjPKjS5_m, "ax", %progbits +.global _ZN3ams6crypto4impl6BigNum3SubEPjPKjS5_m +.type _ZN3ams6crypto4impl6BigNum3SubEPjPKjS5_m, %function +#else +.text +.global _ZN3ams6crypto4impl6BigNum3SubEPjPKjS5_m +#endif +.balign 0x10 +_ZN3ams6crypto4impl6BigNum3SubEPjPKjS5_m: + /* Check if we have anything to do at all. */ + mov x4, #0x20000000 + msr nzcv, x4 + cbz x3, 7f + + /* Save registers. */ + stp x16, x17, [sp, #-16]! + stp xzr, x19, [sp, #-16]! + stp x20, x21, [sp, #-16]! + + /* Check if we have less than 16 words to process. */ + lsr x20, x3, #4 + cbz x20, 2f + + sub x3, x3, x20, lsl #4 + +1: /* Process 16 words at a time. */ + /* NOTE: Nintendo uses X18 here, we will use X21 for EL1+ compat. */ + ldp x4, x5, [x1], #16 + ldp x12, x13, [x2], #16 + ldp x6, x7, [x1], #16 + ldp x14, x15, [x2], #16 + ldp x8, x9, [x1], #16 + ldp x16, x17, [x2], #16 + ldp x10, x11, [x1], #16 + ldp x21, x19, [x2], #16 + + sbcs x4, x4, x12 + sbcs x5, x5, x13 + stp x4, x5, [x0], #16 + + sbcs x6, x6, x14 + sbcs x7, x7, x15 + stp x6, x7, [x0], #16 + + sbcs x8, x8, x16 + sbcs x9, x9, x17 + stp x8, x9, [x0], #16 + + sbcs x10, x10, x21 + sbcs x11, x11, x19 + stp x10, x11, [x0], #16 + + sub x20, x20, #1 + cbnz x20, 1b + +2: /* We have less than 16 words to process. */ + lsr x15, x3, #2 + cbz x15, 4f + + sub x3, x3, x15, lsl #2 + +3: /* Process 4 words at a time. */ + ldp x4, x5, [x1], #16 + ldp x8, x9, [x2], #16 + + sub x15, x15, #1 + + sbcs x4, x4, x8 + sbcs x5, x5, x9 + + stp x4, x5, [x0], #16 + + cbnz x15, 3b + +4: /* We have less than 4 words to process. */ + cbz x3, 6f + +5: /* Process 1 word at a time. */ + ldr w4, [x1], #4 + ldr w8, [x2], #4 + sbcs w4, w4, w8 + str w4, [x0], #4 + + sub x3, x3, #1 + cbnz x3, 5b + +6: /* Restore registers we used while adding. */ + ldp x20, x21, [sp], #16 + ldp xzr, x19, [sp], #16 + ldp x16, x17, [sp], #16 + +7: /* We're done. */ + cinc x0, xzr, cc + ret + +/* ams::crypto::impl::BigNum::MultAdd(Word *dst, const Word *w, size_t num_words, Word mult) */ +#if !defined(ATMOSPHERE_OS_MACOS) +.section .text._ZN3ams6crypto4impl6BigNum7MultAddEPjPKjmj, "ax", %progbits +.global _ZN3ams6crypto4impl6BigNum7MultAddEPjPKjmj +.type _ZN3ams6crypto4impl6BigNum7MultAddEPjPKjmj, %function +#else +.text +.global _ZN3ams6crypto4impl6BigNum7MultAddEPjPKjmj +#endif +.balign 0x10 +_ZN3ams6crypto4impl6BigNum7MultAddEPjPKjmj: + /* Check if we have anything to do at all. */ + mov x15, xzr + cbz x2, 5f + + /* Check if we have less than four words to process. */ + lsr x6, x2, #2 + cbz x6, 2f + + /* We have more than four words to process. */ + sub x2, x2, x6, lsl #2 + stp x16, x17, [sp, #-16]! + +1: /* Loop processing four words at a time. */ + ldp w4, w5, [x1], #8 + ldp w16, w7, [x1], #8 + ldp w8, w9, [x0] + ldp w10, w11, [x0, #8] + + umaddl x4, w3, w4, x8 + umaddl x5, w3, w5, x9 + umaddl x16, w3, w16, x10 + umaddl x7, w3, w7, x11 + + add x12, x4, x15, lsr #32 + add x13, x5, x12, lsr #32 + stp w12, w13, [x0], #8 + + add x14, x16, x13, lsr #32 + add x15, x7, x14, lsr #32 + stp w14, w15, [x0], #8 + + sub x6, x6, #1 + cbnz x6, 1b + + ldp x16, x17, [sp], #16 + +2: /* We have less than four words. Check if we have less than two. */ + lsr x6, x2, #1 + cbz x6, 4f + + /* We have more than two words to process. */ + sub x2, x2, x6, lsl #1 + +3: /* Loop processing two words at a time. */ + ldp w4, w5, [x1], #8 + ldp w8, w9, [x0] + + umaddl x4, w3, w4, x8 + umaddl x5, w3, w5, x9 + + sub x6, x6, #1 + + add x14, x4, x15, lsr #32 + add x15, x5, x14, lsr #32 + + stp w14, w15, [x0], #8 + + cbnz x6, 3b + +4: /* We have less than two words to process. */ + cbz x2, 5f + + /* We have one word to process. */ + ldr w4, [x1], #4 + ldr w8, [x0] + + umaddl x4, w3, w4, x8 + add x15, x4, x15, lsr #32 + + str w15, [x0], #4 + +5: /* We're done. */ + lsr x0, x15, #32 + ret diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_cbc_mac_impl.arch.generic.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_cbc_mac_impl.arch.generic.cpp new file mode 100644 index 00000000..d561ff08 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_cbc_mac_impl.arch.generic.cpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> +#include "crypto_update_impl.hpp" + +namespace ams::crypto::impl { + + void CbcMacImpl::UpdateGeneric(const void *data, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(m_state == State_Initialized); + + /* Update. */ + UpdateImpl<void>(this, data, size); + } + + void CbcMacImpl::ProcessBlocksGeneric(const void *data, size_t num_blocks) { + /* If we have a block remaining, process it. */ + if (m_buffered_bytes == BlockSize) { + this->ProcessBlock(m_buffer); + m_buffered_bytes = 0; + } + + /* Process blocks. */ + const u8 *data8 = static_cast<const u8 *>(data); + + u8 block[BlockSize]; + while ((--num_blocks) > 0) { + for (size_t i = 0; i < BlockSize; ++i) { + block[i] = data8[i] ^ m_mac[i]; + } + + m_cipher_function(m_mac, block, m_cipher_context); + + data8 += BlockSize; + } + + /* Process the last block. */ + std::memcpy(m_buffer, data8, BlockSize); + m_buffered_bytes = BlockSize; + } + + template<> + void CbcMacImpl::Update<AesEncryptor128>(const void *data, size_t size) { + this->UpdateGeneric(data, size); + } + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_cbc_mac_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_cbc_mac_impl.cpp new file mode 100644 index 00000000..c7d5e4f4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_cbc_mac_impl.cpp @@ -0,0 +1,81 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::crypto::impl { + + void CbcMacImpl::ProcessBlock(const void *data) { + /* Procses the block. */ + const u8 *data8 = static_cast<const u8 *>(data); + + u8 block[BlockSize]; + for (size_t i = 0; i < BlockSize; ++i) { + block[i] = data8[i] ^ m_mac[i]; + } + + m_cipher_function(m_mac, block, m_cipher_context); + } + + void CbcMacImpl::ProcessPartialData(const void *data, size_t size) { + /* Copy in the data. */ + std::memcpy(m_buffer + m_buffered_bytes, data, size); + m_buffered_bytes += size; + } + + void CbcMacImpl::ProcessRemainingData(const void *data, size_t size) { + /* If we have a block remaining, process it. */ + if (m_buffered_bytes == BlockSize) { + this->ProcessBlock(m_buffer); + m_buffered_bytes = 0; + } + + /* Copy the remaining data. */ + std::memcpy(m_buffer, data, size); + m_buffered_bytes = size; + } + + void CbcMacImpl::GetMac(void *mac, size_t mac_size) { + /* Check pre-conditions. */ + AMS_ASSERT(m_state == State_Initialized || m_state == State_Done); + AMS_ASSERT(mac_size >= BlockSize); + AMS_UNUSED(mac_size); + + /* Ensure we're done. */ + if (m_state == State_Initialized) { + if (m_buffered_bytes == BlockSize) { + this->ProcessBlock(m_buffer); + m_buffered_bytes = 0; + } + m_state = State_Done; + } + + /* Copy out the mac. */ + std::memcpy(mac, m_mac, sizeof(m_mac)); + } + + void CbcMacImpl::MaskBufferedData(const void *data, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(m_buffered_bytes == BlockSize); + AMS_ASSERT(size == BlockSize); + AMS_UNUSED(size); + + /* Mask the data. */ + for (size_t i = 0; i < BlockSize; ++i) { + m_buffer[i] ^= static_cast<const u8 *>(data)[i]; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_ctr_mode_impl.arch.arm64.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_ctr_mode_impl.arch.arm64.cpp new file mode 100644 index 00000000..10080f7e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_ctr_mode_impl.arch.arm64.cpp @@ -0,0 +1,591 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +#ifdef ATMOSPHERE_IS_STRATOSPHERE +#include <arm_neon.h> + +namespace ams::crypto::impl { + + /* Variable management macros. */ + #define DECLARE_ROUND_KEY_VAR(n) \ + const uint8x16_t round_key_##n = vld1q_u8(keys + (BlockSize * n)) + + #define AES_ENC_DEC_OUTPUT_THREE_BLOCKS() \ + [tmp0]"+w"(tmp0), [tmp1]"+w"(tmp1), [tmp2]"+w"(tmp2) + + #define AES_ENC_DEC_OUTPUT_THREE_CTRS() \ + [ctr0]"+w"(ctr0), [ctr1]"+w"(ctr1), [ctr2]"+w"(ctr2) + + #define AES_ENC_DEC_OUTPUT_ONE_BLOCK() \ + [tmp0]"+w"(tmp0) + + #define AES_ENC_DEC_OUTPUT_ONE_CTR() \ + [ctr0]"+w"(ctr0) + + #define CTR_INCREMENT_OUTPUT_HIGH_LOW() \ + [high]"=&r"(high), [low]"=&r"(low) + + #define CTR_INCREMENT_OUTPUT_HIGH_LOW_TMP() \ + [high_tmp]"=&r"(high_tmp), [low_tmp]"=&r"(low_tmp) + + #define CTR_INCREMENT_OUTPUT_HL_SINGLE_TMP() \ + [hl_tmp]"=&r"(hl_tmp) + + #define AES_ENC_DEC_INPUT_ROUND_KEY(n) \ + [round_key_##n]"w"(round_key_##n) + + /* AES Encryption macros. */ + #define AES_ENC_ROUND(n, i) \ + "aese %[tmp" #i "].16b, %[round_key_" #n "].16b\n" \ + "aesmc %[tmp" #i "].16b, %[tmp" #i "].16b\n" + + #define AES_ENC_SECOND_LAST_ROUND(n, i) \ + "aese %[tmp" #i "].16b, %[round_key_" #n "].16b\n" + + #define AES_ENC_LAST_ROUND(n, i) \ + "eor %[tmp" #i "].16b, %[tmp" #i "].16b, %[round_key_" #n "].16b\n" + + namespace { + + ALWAYS_INLINE uint8x16_t IncrementCounterOptimized(const uint8x16_t ctr) { + uint8x16_t inc; + uint64_t high, low; + /* Use ASM. TODO: Better than using intrinsics? */ + __asm__ __volatile__ ( + "mov %[high], %[ctr].d[0]\n" + "mov %[low], %[ctr].d[1]\n" + "rev %[high], %[high]\n" + "rev %[low], %[low]\n" + "adds %[low], %[low], 1\n" + "cinc %[high], %[high], cs\n" + "rev %[high], %[high]\n" + "rev %[low], %[low]\n" + "mov %[inc].d[0], %[high]\n" + "mov %[inc].d[1], %[low]\n" + : [inc]"=w"(inc), + CTR_INCREMENT_OUTPUT_HIGH_LOW() + : [ctr]"w"(ctr) + : "cc" + ); + return inc; + } + + } + + template<> + void CtrModeImpl<AesEncryptor128>::ProcessBlocks(u8 *dst, const u8 *src, size_t num_blocks) { + /* Preload all round keys + iv into neon registers. */ + const u8 *keys = m_block_cipher->GetRoundKey(); + DECLARE_ROUND_KEY_VAR(0); + DECLARE_ROUND_KEY_VAR(1); + DECLARE_ROUND_KEY_VAR(2); + DECLARE_ROUND_KEY_VAR(3); + DECLARE_ROUND_KEY_VAR(4); + DECLARE_ROUND_KEY_VAR(5); + DECLARE_ROUND_KEY_VAR(6); + DECLARE_ROUND_KEY_VAR(7); + DECLARE_ROUND_KEY_VAR(8); + DECLARE_ROUND_KEY_VAR(9); + DECLARE_ROUND_KEY_VAR(10); + uint8x16_t ctr0 = vld1q_u8(m_counter); + uint64_t high, low; + + /* Process three blocks at a time, when possible. */ + if (num_blocks >= 3) { + /* Increment CTR twice. */ + uint8x16_t ctr1 = IncrementCounterOptimized(ctr0); + uint8x16_t ctr2 = IncrementCounterOptimized(ctr1); + uint64_t high_tmp, low_tmp; + + while (num_blocks >= 3) { + /* Read blocks in. Keep them in registers for XOR later. */ + const uint8x16_t block0 = vld1q_u8(src); + src += AesEncryptor128::BlockSize; + const uint8x16_t block1 = vld1q_u8(src); + src += AesEncryptor128::BlockSize; + const uint8x16_t block2 = vld1q_u8(src); + src += AesEncryptor128::BlockSize; + + /* We'll be encrypting the three CTRs. */ + uint8x16_t tmp0 = ctr0, tmp1 = ctr1, tmp2 = ctr2; + + /* Actually do encryption, use optimized asm. */ + /* Interleave CTR calculations with AES ones, to mask latencies. */ + __asm__ __volatile__ ( + AES_ENC_ROUND(0, 0) "mov %[high], %[ctr2].d[0]\n" + AES_ENC_ROUND(0, 1) "mov %[low], %[ctr2].d[1]\n" + AES_ENC_ROUND(0, 2) "rev %[high], %[high]\n" + AES_ENC_ROUND(1, 0) "rev %[low], %[low]\n" + AES_ENC_ROUND(1, 1) "adds %[low], %[low], 1\n" + AES_ENC_ROUND(1, 2) "cinc %[high], %[high], cs\n" + AES_ENC_ROUND(2, 0) "rev %[high_tmp], %[high]\n" + AES_ENC_ROUND(2, 1) "rev %[low_tmp], %[low]\n" + AES_ENC_ROUND(2, 2) "mov %[ctr0].d[0], %[high_tmp]\n" + AES_ENC_ROUND(3, 0) "mov %[ctr0].d[1], %[low_tmp]\n" + AES_ENC_ROUND(3, 1) "adds %[low], %[low], 1\n" + AES_ENC_ROUND(3, 2) "cinc %[high], %[high], cs\n" + AES_ENC_ROUND(4, 0) "rev %[high_tmp], %[high]\n" + AES_ENC_ROUND(4, 1) "rev %[low_tmp], %[low]\n" + AES_ENC_ROUND(4, 2) "mov %[ctr1].d[0], %[high_tmp]\n" + AES_ENC_ROUND(5, 0) "mov %[ctr1].d[1], %[low_tmp]\n" + AES_ENC_ROUND(5, 1) "adds %[low], %[low], 1\n" + AES_ENC_ROUND(5, 2) "cinc %[high], %[high], cs\n" + AES_ENC_ROUND(6, 0) "rev %[high_tmp], %[high]\n" + AES_ENC_ROUND(6, 1) "rev %[low_tmp], %[low]\n" + AES_ENC_ROUND(6, 2) "mov %[ctr2].d[0], %[high_tmp]\n" + AES_ENC_ROUND(7, 0) "mov %[ctr2].d[1], %[low_tmp]\n" + AES_ENC_ROUND(7, 1) + AES_ENC_ROUND(7, 2) + AES_ENC_ROUND(8, 0) AES_ENC_ROUND(8, 1) AES_ENC_ROUND(8, 2) + AES_ENC_SECOND_LAST_ROUND(9, 0) AES_ENC_SECOND_LAST_ROUND(9, 1) AES_ENC_SECOND_LAST_ROUND(9, 2) + AES_ENC_LAST_ROUND(10, 0) AES_ENC_LAST_ROUND(10, 1) AES_ENC_LAST_ROUND(10, 2) + : AES_ENC_DEC_OUTPUT_THREE_BLOCKS(), + AES_ENC_DEC_OUTPUT_THREE_CTRS(), + CTR_INCREMENT_OUTPUT_HIGH_LOW(), + CTR_INCREMENT_OUTPUT_HIGH_LOW_TMP() + : AES_ENC_DEC_INPUT_ROUND_KEY(0), + AES_ENC_DEC_INPUT_ROUND_KEY(1), + AES_ENC_DEC_INPUT_ROUND_KEY(2), + AES_ENC_DEC_INPUT_ROUND_KEY(3), + AES_ENC_DEC_INPUT_ROUND_KEY(4), + AES_ENC_DEC_INPUT_ROUND_KEY(5), + AES_ENC_DEC_INPUT_ROUND_KEY(6), + AES_ENC_DEC_INPUT_ROUND_KEY(7), + AES_ENC_DEC_INPUT_ROUND_KEY(8), + AES_ENC_DEC_INPUT_ROUND_KEY(9), + AES_ENC_DEC_INPUT_ROUND_KEY(10) + : "cc" + ); + + /* XOR blocks. */ + tmp0 = veorq_u8(block0, tmp0); + tmp1 = veorq_u8(block1, tmp1); + tmp2 = veorq_u8(block2, tmp2); + + /* Store to output. */ + vst1q_u8(dst, tmp0); + dst += AesEncryptor128::BlockSize; + vst1q_u8(dst, tmp1); + dst += AesEncryptor128::BlockSize; + vst1q_u8(dst, tmp2); + dst += AesEncryptor128::BlockSize; + + num_blocks -= 3; + } + } + + while (num_blocks >= 1) { + /* Read block in, keep in register for XOR. */ + const uint8x16_t block0 = vld1q_u8(src); + src += AesEncryptor128::BlockSize; + + /* We'll be encrypting the CTR. */ + uint8x16_t tmp0 = ctr0; + + /* Actually do encryption, use optimized asm. */ + /* Interleave CTR calculations with AES ones, to mask latencies. */ + __asm__ __volatile__ ( + AES_ENC_ROUND(0, 0) "mov %[high], %[ctr0].d[0]\n" + AES_ENC_ROUND(1, 0) "mov %[low], %[ctr0].d[1]\n" + AES_ENC_ROUND(2, 0) "rev %[high], %[high]\n" + AES_ENC_ROUND(3, 0) "rev %[low], %[low]\n" + AES_ENC_ROUND(4, 0) "adds %[low], %[low], 1\n" + AES_ENC_ROUND(5, 0) "cinc %[high], %[high], cs\n" + AES_ENC_ROUND(6, 0) "rev %[high], %[high]\n" + AES_ENC_ROUND(7, 0) "rev %[low], %[low]\n" + AES_ENC_ROUND(8, 0) "mov %[ctr0].d[0], %[high]\n" + AES_ENC_SECOND_LAST_ROUND(9, 0) "mov %[ctr0].d[1], %[low]\n" + AES_ENC_LAST_ROUND(10, 0) + : AES_ENC_DEC_OUTPUT_ONE_BLOCK(), + AES_ENC_DEC_OUTPUT_ONE_CTR(), + CTR_INCREMENT_OUTPUT_HIGH_LOW() + : AES_ENC_DEC_INPUT_ROUND_KEY(0), + AES_ENC_DEC_INPUT_ROUND_KEY(1), + AES_ENC_DEC_INPUT_ROUND_KEY(2), + AES_ENC_DEC_INPUT_ROUND_KEY(3), + AES_ENC_DEC_INPUT_ROUND_KEY(4), + AES_ENC_DEC_INPUT_ROUND_KEY(5), + AES_ENC_DEC_INPUT_ROUND_KEY(6), + AES_ENC_DEC_INPUT_ROUND_KEY(7), + AES_ENC_DEC_INPUT_ROUND_KEY(8), + AES_ENC_DEC_INPUT_ROUND_KEY(9), + AES_ENC_DEC_INPUT_ROUND_KEY(10) + : "cc" + ); + + /* XOR blocks. */ + tmp0 = veorq_u8(block0, tmp0); + + /* Store to output. */ + vst1q_u8(dst, tmp0); + dst += AesEncryptor128::BlockSize; + + num_blocks--; + } + + vst1q_u8(m_counter, ctr0); + } + + template<> + void CtrModeImpl<AesEncryptor192>::ProcessBlocks(u8 *dst, const u8 *src, size_t num_blocks) { + /* Preload all round keys + iv into neon registers. */ + const u8 *keys = m_block_cipher->GetRoundKey(); + DECLARE_ROUND_KEY_VAR(0); + DECLARE_ROUND_KEY_VAR(1); + DECLARE_ROUND_KEY_VAR(2); + DECLARE_ROUND_KEY_VAR(3); + DECLARE_ROUND_KEY_VAR(4); + DECLARE_ROUND_KEY_VAR(5); + DECLARE_ROUND_KEY_VAR(6); + DECLARE_ROUND_KEY_VAR(7); + DECLARE_ROUND_KEY_VAR(8); + DECLARE_ROUND_KEY_VAR(9); + DECLARE_ROUND_KEY_VAR(10); + DECLARE_ROUND_KEY_VAR(11); + DECLARE_ROUND_KEY_VAR(12); + uint8x16_t ctr0 = vld1q_u8(m_counter); + uint64_t high, low; + + /* Process three blocks at a time, when possible. */ + if (num_blocks >= 3) { + /* Increment CTR twice. */ + uint8x16_t ctr1 = IncrementCounterOptimized(ctr0); + uint8x16_t ctr2 = IncrementCounterOptimized(ctr1); + uint64_t high_tmp, low_tmp; + + while (num_blocks >= 3) { + /* Read blocks in. Keep them in registers for XOR later. */ + const uint8x16_t block0 = vld1q_u8(src); + src += AesEncryptor192::BlockSize; + const uint8x16_t block1 = vld1q_u8(src); + src += AesEncryptor192::BlockSize; + const uint8x16_t block2 = vld1q_u8(src); + src += AesEncryptor192::BlockSize; + + /* We'll be encrypting the three CTRs. */ + uint8x16_t tmp0 = ctr0, tmp1 = ctr1, tmp2 = ctr2; + + /* Actually do encryption, use optimized asm. */ + /* Interleave CTR calculations with AES ones, to mask latencies. */ + __asm__ __volatile__ ( + AES_ENC_ROUND(0, 0) "mov %[high], %[ctr2].d[0]\n" + AES_ENC_ROUND(0, 1) "mov %[low], %[ctr2].d[1]\n" + AES_ENC_ROUND(0, 2) "rev %[high], %[high]\n" + AES_ENC_ROUND(1, 0) "rev %[low], %[low]\n" + AES_ENC_ROUND(1, 1) "adds %[low], %[low], 1\n" + AES_ENC_ROUND(1, 2) "cinc %[high], %[high], cs\n" + AES_ENC_ROUND(2, 0) "rev %[high_tmp], %[high]\n" + AES_ENC_ROUND(2, 1) "rev %[low_tmp], %[low]\n" + AES_ENC_ROUND(2, 2) "mov %[ctr0].d[0], %[high_tmp]\n" + AES_ENC_ROUND(3, 0) "mov %[ctr0].d[1], %[low_tmp]\n" + AES_ENC_ROUND(3, 1) "adds %[low], %[low], 1\n" + AES_ENC_ROUND(3, 2) "cinc %[high], %[high], cs\n" + AES_ENC_ROUND(4, 0) "rev %[high_tmp], %[high]\n" + AES_ENC_ROUND(4, 1) "rev %[low_tmp], %[low]\n" + AES_ENC_ROUND(4, 2) "mov %[ctr1].d[0], %[high_tmp]\n" + AES_ENC_ROUND(5, 0) "mov %[ctr1].d[1], %[low_tmp]\n" + AES_ENC_ROUND(5, 1) "adds %[low], %[low], 1\n" + AES_ENC_ROUND(5, 2) "cinc %[high], %[high], cs\n" + AES_ENC_ROUND(6, 0) "rev %[high_tmp], %[high]\n" + AES_ENC_ROUND(6, 1) "rev %[low_tmp], %[low]\n" + AES_ENC_ROUND(6, 2) "mov %[ctr2].d[0], %[high_tmp]\n" + AES_ENC_ROUND(7, 0) "mov %[ctr2].d[1], %[low_tmp]\n" + AES_ENC_ROUND(7, 1) + AES_ENC_ROUND(7, 2) + AES_ENC_ROUND(8, 0) AES_ENC_ROUND(8, 1) AES_ENC_ROUND(8, 2) + AES_ENC_ROUND(9, 0) AES_ENC_ROUND(9, 1) AES_ENC_ROUND(9, 2) + AES_ENC_ROUND(10, 0) AES_ENC_ROUND(10, 1) AES_ENC_ROUND(10, 2) + AES_ENC_SECOND_LAST_ROUND(11, 0) AES_ENC_SECOND_LAST_ROUND(11, 1) AES_ENC_SECOND_LAST_ROUND(11, 2) + AES_ENC_LAST_ROUND(12, 0) AES_ENC_LAST_ROUND(12, 1) AES_ENC_LAST_ROUND(12, 2) + : AES_ENC_DEC_OUTPUT_THREE_BLOCKS(), + AES_ENC_DEC_OUTPUT_THREE_CTRS(), + CTR_INCREMENT_OUTPUT_HIGH_LOW(), + CTR_INCREMENT_OUTPUT_HIGH_LOW_TMP() + : AES_ENC_DEC_INPUT_ROUND_KEY(0), + AES_ENC_DEC_INPUT_ROUND_KEY(1), + AES_ENC_DEC_INPUT_ROUND_KEY(2), + AES_ENC_DEC_INPUT_ROUND_KEY(3), + AES_ENC_DEC_INPUT_ROUND_KEY(4), + AES_ENC_DEC_INPUT_ROUND_KEY(5), + AES_ENC_DEC_INPUT_ROUND_KEY(6), + AES_ENC_DEC_INPUT_ROUND_KEY(7), + AES_ENC_DEC_INPUT_ROUND_KEY(8), + AES_ENC_DEC_INPUT_ROUND_KEY(9), + AES_ENC_DEC_INPUT_ROUND_KEY(10), + AES_ENC_DEC_INPUT_ROUND_KEY(11), + AES_ENC_DEC_INPUT_ROUND_KEY(12) + : "cc" + ); + + /* XOR blocks. */ + tmp0 = veorq_u8(block0, tmp0); + tmp1 = veorq_u8(block1, tmp1); + tmp2 = veorq_u8(block2, tmp2); + + /* Store to output. */ + vst1q_u8(dst, tmp0); + dst += AesEncryptor192::BlockSize; + vst1q_u8(dst, tmp1); + dst += AesEncryptor192::BlockSize; + vst1q_u8(dst, tmp2); + dst += AesEncryptor192::BlockSize; + + num_blocks -= 3; + } + } + + while (num_blocks >= 1) { + /* Read block in, keep in register for XOR. */ + const uint8x16_t block0 = vld1q_u8(src); + src += AesEncryptor192::BlockSize; + + /* We'll be encrypting the CTR. */ + uint8x16_t tmp0 = ctr0; + + /* Actually do encryption, use optimized asm. */ + /* Interleave CTR calculations with AES ones, to mask latencies. */ + __asm__ __volatile__ ( + AES_ENC_ROUND(0, 0) "mov %[high], %[ctr0].d[0]\n" + AES_ENC_ROUND(1, 0) "mov %[low], %[ctr0].d[1]\n" + AES_ENC_ROUND(2, 0) "rev %[high], %[high]\n" + AES_ENC_ROUND(3, 0) "rev %[low], %[low]\n" + AES_ENC_ROUND(4, 0) "adds %[low], %[low], 1\n" + AES_ENC_ROUND(5, 0) "cinc %[high], %[high], cs\n" + AES_ENC_ROUND(6, 0) "rev %[high], %[high]\n" + AES_ENC_ROUND(7, 0) "rev %[low], %[low]\n" + AES_ENC_ROUND(8, 0) "mov %[ctr0].d[0], %[high]\n" + AES_ENC_ROUND(9, 0) "mov %[ctr0].d[1], %[low]\n" + AES_ENC_ROUND(10, 0) + AES_ENC_SECOND_LAST_ROUND(11, 0) + AES_ENC_LAST_ROUND(12, 0) + : AES_ENC_DEC_OUTPUT_ONE_BLOCK(), + AES_ENC_DEC_OUTPUT_ONE_CTR(), + CTR_INCREMENT_OUTPUT_HIGH_LOW() + : AES_ENC_DEC_INPUT_ROUND_KEY(0), + AES_ENC_DEC_INPUT_ROUND_KEY(1), + AES_ENC_DEC_INPUT_ROUND_KEY(2), + AES_ENC_DEC_INPUT_ROUND_KEY(3), + AES_ENC_DEC_INPUT_ROUND_KEY(4), + AES_ENC_DEC_INPUT_ROUND_KEY(5), + AES_ENC_DEC_INPUT_ROUND_KEY(6), + AES_ENC_DEC_INPUT_ROUND_KEY(7), + AES_ENC_DEC_INPUT_ROUND_KEY(8), + AES_ENC_DEC_INPUT_ROUND_KEY(9), + AES_ENC_DEC_INPUT_ROUND_KEY(10), + AES_ENC_DEC_INPUT_ROUND_KEY(11), + AES_ENC_DEC_INPUT_ROUND_KEY(12) + : "cc" + ); + + /* XOR blocks. */ + tmp0 = veorq_u8(block0, tmp0); + + /* Store to output. */ + vst1q_u8(dst, tmp0); + dst += AesEncryptor192::BlockSize; + + num_blocks--; + } + + vst1q_u8(m_counter, ctr0); + } + + template<> + void CtrModeImpl<AesEncryptor256>::ProcessBlocks(u8 *dst, const u8 *src, size_t num_blocks) { + /* Preload all round keys + iv into neon registers. */ + const u8 *keys = m_block_cipher->GetRoundKey(); + DECLARE_ROUND_KEY_VAR(0); + DECLARE_ROUND_KEY_VAR(1); + DECLARE_ROUND_KEY_VAR(2); + DECLARE_ROUND_KEY_VAR(3); + DECLARE_ROUND_KEY_VAR(4); + DECLARE_ROUND_KEY_VAR(5); + DECLARE_ROUND_KEY_VAR(6); + DECLARE_ROUND_KEY_VAR(7); + DECLARE_ROUND_KEY_VAR(8); + DECLARE_ROUND_KEY_VAR(9); + DECLARE_ROUND_KEY_VAR(10); + DECLARE_ROUND_KEY_VAR(11); + DECLARE_ROUND_KEY_VAR(12); + DECLARE_ROUND_KEY_VAR(13); + DECLARE_ROUND_KEY_VAR(14); + uint8x16_t ctr0 = vld1q_u8(m_counter); + uint64_t high, low; + + /* Process three blocks at a time, when possible. */ + if (num_blocks >= 3) { + /* Increment CTR twice. */ + uint8x16_t ctr1 = IncrementCounterOptimized(ctr0); + uint8x16_t ctr2 = IncrementCounterOptimized(ctr1); + uint64_t hl_tmp; + + while (num_blocks >= 3) { + /* Read blocks in. Keep them in registers for XOR later. */ + const uint8x16_t block0 = vld1q_u8(src); + src += AesEncryptor256::BlockSize; + const uint8x16_t block1 = vld1q_u8(src); + src += AesEncryptor256::BlockSize; + const uint8x16_t block2 = vld1q_u8(src); + src += AesEncryptor256::BlockSize; + + /* We'll be encrypting the three CTRs. */ + uint8x16_t tmp0 = ctr0, tmp1 = ctr1, tmp2 = ctr2; + + /* Actually do encryption, use optimized asm. */ + /* Interleave CTR calculations with AES ones, to mask latencies. */ + /* Note: ASM here only uses one temporary u64 instead of two, due to 30 operand limit. */ + __asm__ __volatile__ ( + AES_ENC_ROUND(0, 0) "mov %[high], %[ctr2].d[0]\n" + AES_ENC_ROUND(0, 1) "mov %[low], %[ctr2].d[1]\n" + AES_ENC_ROUND(0, 2) "rev %[high], %[high]\n" + AES_ENC_ROUND(1, 0) "rev %[low], %[low]\n" + AES_ENC_ROUND(1, 1) "adds %[low], %[low], 1\n" + AES_ENC_ROUND(1, 2) "cinc %[high], %[high], cs\n" + AES_ENC_ROUND(2, 0) "rev %[hl_tmp], %[high]\n" + AES_ENC_ROUND(2, 1) "mov %[ctr0].d[0], %[hl_tmp]\n" + AES_ENC_ROUND(2, 2) "rev %[hl_tmp], %[low]\n" + AES_ENC_ROUND(3, 0) "mov %[ctr0].d[1], %[hl_tmp]\n" + AES_ENC_ROUND(3, 1) "adds %[low], %[low], 1\n" + AES_ENC_ROUND(3, 2) "cinc %[high], %[high], cs\n" + AES_ENC_ROUND(4, 0) "rev %[hl_tmp], %[high]\n" + AES_ENC_ROUND(4, 1) "mov %[ctr1].d[0], %[hl_tmp]\n" + AES_ENC_ROUND(4, 2) "rev %[hl_tmp], %[low]\n" + AES_ENC_ROUND(5, 0) "mov %[ctr1].d[1], %[hl_tmp]\n" + AES_ENC_ROUND(5, 1) "adds %[low], %[low], 1\n" + AES_ENC_ROUND(5, 2) "cinc %[high], %[high], cs\n" + AES_ENC_ROUND(6, 0) "rev %[hl_tmp], %[high]\n" + AES_ENC_ROUND(6, 1) "mov %[ctr2].d[0], %[hl_tmp]\n" + AES_ENC_ROUND(6, 2) "rev %[hl_tmp], %[low]\n" + AES_ENC_ROUND(7, 0) "mov %[ctr2].d[1], %[hl_tmp]\n" + AES_ENC_ROUND(7, 1) + AES_ENC_ROUND(7, 2) + AES_ENC_ROUND(8, 0) AES_ENC_ROUND(8, 1) AES_ENC_ROUND(8, 2) + AES_ENC_ROUND(9, 0) AES_ENC_ROUND(9, 1) AES_ENC_ROUND(9, 2) + AES_ENC_ROUND(10, 0) AES_ENC_ROUND(10, 1) AES_ENC_ROUND(10, 2) + AES_ENC_ROUND(11, 0) AES_ENC_ROUND(11, 1) AES_ENC_ROUND(11, 2) + AES_ENC_ROUND(12, 0) AES_ENC_ROUND(12, 1) AES_ENC_ROUND(12, 2) + AES_ENC_SECOND_LAST_ROUND(13, 0) AES_ENC_SECOND_LAST_ROUND(13, 1) AES_ENC_SECOND_LAST_ROUND(13, 2) + AES_ENC_LAST_ROUND(14, 0) AES_ENC_LAST_ROUND(14, 1) AES_ENC_LAST_ROUND(14, 2) + : AES_ENC_DEC_OUTPUT_THREE_BLOCKS(), + AES_ENC_DEC_OUTPUT_THREE_CTRS(), + CTR_INCREMENT_OUTPUT_HIGH_LOW(), + CTR_INCREMENT_OUTPUT_HL_SINGLE_TMP() + : AES_ENC_DEC_INPUT_ROUND_KEY(0), + AES_ENC_DEC_INPUT_ROUND_KEY(1), + AES_ENC_DEC_INPUT_ROUND_KEY(2), + AES_ENC_DEC_INPUT_ROUND_KEY(3), + AES_ENC_DEC_INPUT_ROUND_KEY(4), + AES_ENC_DEC_INPUT_ROUND_KEY(5), + AES_ENC_DEC_INPUT_ROUND_KEY(6), + AES_ENC_DEC_INPUT_ROUND_KEY(7), + AES_ENC_DEC_INPUT_ROUND_KEY(8), + AES_ENC_DEC_INPUT_ROUND_KEY(9), + AES_ENC_DEC_INPUT_ROUND_KEY(10), + AES_ENC_DEC_INPUT_ROUND_KEY(11), + AES_ENC_DEC_INPUT_ROUND_KEY(12), + AES_ENC_DEC_INPUT_ROUND_KEY(13), + AES_ENC_DEC_INPUT_ROUND_KEY(14) + : "cc" + ); + + /* XOR blocks. */ + tmp0 = veorq_u8(block0, tmp0); + tmp1 = veorq_u8(block1, tmp1); + tmp2 = veorq_u8(block2, tmp2); + + /* Store to output. */ + vst1q_u8(dst, tmp0); + dst += AesEncryptor256::BlockSize; + vst1q_u8(dst, tmp1); + dst += AesEncryptor256::BlockSize; + vst1q_u8(dst, tmp2); + dst += AesEncryptor256::BlockSize; + + num_blocks -= 3; + } + } + + while (num_blocks >= 1) { + /* Read block in, keep in register for XOR. */ + const uint8x16_t block0 = vld1q_u8(src); + src += AesEncryptor256::BlockSize; + + /* We'll be encrypting the CTR. */ + uint8x16_t tmp0 = ctr0; + + /* Actually do encryption, use optimized asm. */ + /* Interleave CTR calculations with AES ones, to mask latencies. */ + __asm__ __volatile__ ( + AES_ENC_ROUND(0, 0) "mov %[high], %[ctr0].d[0]\n" + AES_ENC_ROUND(1, 0) "mov %[low], %[ctr0].d[1]\n" + AES_ENC_ROUND(2, 0) "rev %[high], %[high]\n" + AES_ENC_ROUND(3, 0) "rev %[low], %[low]\n" + AES_ENC_ROUND(4, 0) "adds %[low], %[low], 1\n" + AES_ENC_ROUND(5, 0) "cinc %[high], %[high], cs\n" + AES_ENC_ROUND(6, 0) "rev %[high], %[high]\n" + AES_ENC_ROUND(7, 0) "rev %[low], %[low]\n" + AES_ENC_ROUND(8, 0) "mov %[ctr0].d[0], %[high]\n" + AES_ENC_ROUND(9, 0) "mov %[ctr0].d[1], %[low]\n" + AES_ENC_ROUND(10, 0) + AES_ENC_ROUND(11, 0) + AES_ENC_ROUND(12, 0) + AES_ENC_SECOND_LAST_ROUND(13, 0) + AES_ENC_LAST_ROUND(14, 0) + : AES_ENC_DEC_OUTPUT_ONE_BLOCK(), + AES_ENC_DEC_OUTPUT_ONE_CTR(), + CTR_INCREMENT_OUTPUT_HIGH_LOW() + : AES_ENC_DEC_INPUT_ROUND_KEY(0), + AES_ENC_DEC_INPUT_ROUND_KEY(1), + AES_ENC_DEC_INPUT_ROUND_KEY(2), + AES_ENC_DEC_INPUT_ROUND_KEY(3), + AES_ENC_DEC_INPUT_ROUND_KEY(4), + AES_ENC_DEC_INPUT_ROUND_KEY(5), + AES_ENC_DEC_INPUT_ROUND_KEY(6), + AES_ENC_DEC_INPUT_ROUND_KEY(7), + AES_ENC_DEC_INPUT_ROUND_KEY(8), + AES_ENC_DEC_INPUT_ROUND_KEY(9), + AES_ENC_DEC_INPUT_ROUND_KEY(10), + AES_ENC_DEC_INPUT_ROUND_KEY(11), + AES_ENC_DEC_INPUT_ROUND_KEY(12), + AES_ENC_DEC_INPUT_ROUND_KEY(13), + AES_ENC_DEC_INPUT_ROUND_KEY(14) + : "cc" + ); + + /* XOR blocks. */ + tmp0 = veorq_u8(block0, tmp0); + + /* Store to output. */ + vst1q_u8(dst, tmp0); + dst += AesEncryptor256::BlockSize; + + num_blocks--; + } + + vst1q_u8(m_counter, ctr0); + } + +} + +#else + +/* TODO: Non-EL0 implementation. */ +namespace ams::crypto::impl { + +} + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_ctr_mode_impl.arch.x64.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_ctr_mode_impl.arch.x64.cpp new file mode 100644 index 00000000..a2899d81 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_ctr_mode_impl.arch.x64.cpp @@ -0,0 +1,269 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> +#include "crypto_aes_impl.arch.x64.hpp" + +namespace ams::crypto::impl { + + template<> void CtrModeImpl<AesEncryptor128>::ProcessBlocks(u8 *dst, const u8 *src, size_t num_blocks) { + /* Check pre-conditions. */ + AMS_ASSERT(src != nullptr); + AMS_ASSERT(dst != nullptr); + + /* If we have aes-ni, use an optimized impl. */ + if (IsAesNiAvailable()) { + /* Load all keys into sse2 registers. */ + const u8 *raw_round_keys = m_block_cipher->GetRoundKey(); + const __m128i round_keys[AesEncryptor128::RoundKeySize / BlockSize] = { + _mm_loadu_si128(reinterpret_cast<const __m128i *>(raw_round_keys + BlockSize * 0)), + _mm_loadu_si128(reinterpret_cast<const __m128i *>(raw_round_keys + BlockSize * 1)), + _mm_loadu_si128(reinterpret_cast<const __m128i *>(raw_round_keys + BlockSize * 2)), + _mm_loadu_si128(reinterpret_cast<const __m128i *>(raw_round_keys + BlockSize * 3)), + _mm_loadu_si128(reinterpret_cast<const __m128i *>(raw_round_keys + BlockSize * 4)), + _mm_loadu_si128(reinterpret_cast<const __m128i *>(raw_round_keys + BlockSize * 5)), + _mm_loadu_si128(reinterpret_cast<const __m128i *>(raw_round_keys + BlockSize * 6)), + _mm_loadu_si128(reinterpret_cast<const __m128i *>(raw_round_keys + BlockSize * 7)), + _mm_loadu_si128(reinterpret_cast<const __m128i *>(raw_round_keys + BlockSize * 8)), + _mm_loadu_si128(reinterpret_cast<const __m128i *>(raw_round_keys + BlockSize * 9)), + _mm_loadu_si128(reinterpret_cast<const __m128i *>(raw_round_keys + BlockSize * 10)), + }; + static_assert(AesEncryptor128::RoundKeySize / BlockSize == 11); + + /* Declare constant for counter math. */ + const __m128i One = _mm_setr_epi8(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1); + + /* Process eight blocks at a time, while we can. */ + constexpr const auto UnrolledBlockCount = 8; + constexpr const auto CounterThreshold = static_cast<u8>(0x100 - UnrolledBlockCount); + + /* Load the counter. */ + auto counter = _mm_loadu_si128(reinterpret_cast<const __m128i *>(m_counter)); + + size_t cur_blocks; + for (cur_blocks = 0; cur_blocks + UnrolledBlockCount <= num_blocks; cur_blocks += UnrolledBlockCount) { + __m128i b0; + __m128i b1; + __m128i b2; + __m128i b3; + __m128i b4; + __m128i b5; + __m128i b6; + __m128i b7; + + __m128i key = round_keys[0]; + + /* Get the last byte of the block. */ + static_assert(util::IsLittleEndian()); + const u8 counter_val = _mm_extract_epi16(counter, 7) >> BITSIZEOF(u8); + + /* Do initial encryption of each block. */ + if (CounterThreshold <= counter_val) { + /* We'll overwrap, so take slow path for counter. */ + _mm_storeu_si128(reinterpret_cast<__m128i *>(m_counter), counter); + + b0 = _mm_xor_si128(_mm_loadu_si128(reinterpret_cast<const __m128i *>(m_counter)), key); + this->IncrementCounter(); + b1 = _mm_xor_si128(_mm_loadu_si128(reinterpret_cast<const __m128i *>(m_counter)), key); + this->IncrementCounter(); + b2 = _mm_xor_si128(_mm_loadu_si128(reinterpret_cast<const __m128i *>(m_counter)), key); + this->IncrementCounter(); + b3 = _mm_xor_si128(_mm_loadu_si128(reinterpret_cast<const __m128i *>(m_counter)), key); + this->IncrementCounter(); + b4 = _mm_xor_si128(_mm_loadu_si128(reinterpret_cast<const __m128i *>(m_counter)), key); + this->IncrementCounter(); + b5 = _mm_xor_si128(_mm_loadu_si128(reinterpret_cast<const __m128i *>(m_counter)), key); + this->IncrementCounter(); + b6 = _mm_xor_si128(_mm_loadu_si128(reinterpret_cast<const __m128i *>(m_counter)), key); + this->IncrementCounter(); + b7 = _mm_xor_si128(_mm_loadu_si128(reinterpret_cast<const __m128i *>(m_counter)), key); + this->IncrementCounter(); + + counter = _mm_loadu_si128(reinterpret_cast<const __m128i *>(m_counter)); + } else { + /* We can take the fast path for the counter. */ + b0 = _mm_xor_si128(counter, key); + counter = _mm_add_epi64(counter, One); + b1 = _mm_xor_si128(counter, key); + counter = _mm_add_epi64(counter, One); + b2 = _mm_xor_si128(counter, key); + counter = _mm_add_epi64(counter, One); + b3 = _mm_xor_si128(counter, key); + counter = _mm_add_epi64(counter, One); + b4 = _mm_xor_si128(counter, key); + counter = _mm_add_epi64(counter, One); + b5 = _mm_xor_si128(counter, key); + counter = _mm_add_epi64(counter, One); + b6 = _mm_xor_si128(counter, key); + counter = _mm_add_epi64(counter, One); + b7 = _mm_xor_si128(counter, key); + counter = _mm_add_epi64(counter, One); + } + + /* Do encryption for all rounds. */ + key = round_keys[1]; + b0 = _mm_aesenc_si128(b0, key); + b1 = _mm_aesenc_si128(b1, key); + b2 = _mm_aesenc_si128(b2, key); + b3 = _mm_aesenc_si128(b3, key); + b4 = _mm_aesenc_si128(b4, key); + b5 = _mm_aesenc_si128(b5, key); + b6 = _mm_aesenc_si128(b6, key); + b7 = _mm_aesenc_si128(b7, key); + + key = round_keys[2]; + b0 = _mm_aesenc_si128(b0, key); + b1 = _mm_aesenc_si128(b1, key); + b2 = _mm_aesenc_si128(b2, key); + b3 = _mm_aesenc_si128(b3, key); + b4 = _mm_aesenc_si128(b4, key); + b5 = _mm_aesenc_si128(b5, key); + b6 = _mm_aesenc_si128(b6, key); + b7 = _mm_aesenc_si128(b7, key); + + key = round_keys[3]; + b0 = _mm_aesenc_si128(b0, key); + b1 = _mm_aesenc_si128(b1, key); + b2 = _mm_aesenc_si128(b2, key); + b3 = _mm_aesenc_si128(b3, key); + b4 = _mm_aesenc_si128(b4, key); + b5 = _mm_aesenc_si128(b5, key); + b6 = _mm_aesenc_si128(b6, key); + b7 = _mm_aesenc_si128(b7, key); + + key = round_keys[4]; + b0 = _mm_aesenc_si128(b0, key); + b1 = _mm_aesenc_si128(b1, key); + b2 = _mm_aesenc_si128(b2, key); + b3 = _mm_aesenc_si128(b3, key); + b4 = _mm_aesenc_si128(b4, key); + b5 = _mm_aesenc_si128(b5, key); + b6 = _mm_aesenc_si128(b6, key); + b7 = _mm_aesenc_si128(b7, key); + + key = round_keys[5]; + b0 = _mm_aesenc_si128(b0, key); + b1 = _mm_aesenc_si128(b1, key); + b2 = _mm_aesenc_si128(b2, key); + b3 = _mm_aesenc_si128(b3, key); + b4 = _mm_aesenc_si128(b4, key); + b5 = _mm_aesenc_si128(b5, key); + b6 = _mm_aesenc_si128(b6, key); + b7 = _mm_aesenc_si128(b7, key); + + key = round_keys[6]; + b0 = _mm_aesenc_si128(b0, key); + b1 = _mm_aesenc_si128(b1, key); + b2 = _mm_aesenc_si128(b2, key); + b3 = _mm_aesenc_si128(b3, key); + b4 = _mm_aesenc_si128(b4, key); + b5 = _mm_aesenc_si128(b5, key); + b6 = _mm_aesenc_si128(b6, key); + b7 = _mm_aesenc_si128(b7, key); + + key = round_keys[7]; + b0 = _mm_aesenc_si128(b0, key); + b1 = _mm_aesenc_si128(b1, key); + b2 = _mm_aesenc_si128(b2, key); + b3 = _mm_aesenc_si128(b3, key); + b4 = _mm_aesenc_si128(b4, key); + b5 = _mm_aesenc_si128(b5, key); + b6 = _mm_aesenc_si128(b6, key); + b7 = _mm_aesenc_si128(b7, key); + + key = round_keys[8]; + b0 = _mm_aesenc_si128(b0, key); + b1 = _mm_aesenc_si128(b1, key); + b2 = _mm_aesenc_si128(b2, key); + b3 = _mm_aesenc_si128(b3, key); + b4 = _mm_aesenc_si128(b4, key); + b5 = _mm_aesenc_si128(b5, key); + b6 = _mm_aesenc_si128(b6, key); + b7 = _mm_aesenc_si128(b7, key); + + key = round_keys[9]; + b0 = _mm_aesenc_si128(b0, key); + b1 = _mm_aesenc_si128(b1, key); + b2 = _mm_aesenc_si128(b2, key); + b3 = _mm_aesenc_si128(b3, key); + b4 = _mm_aesenc_si128(b4, key); + b5 = _mm_aesenc_si128(b5, key); + b6 = _mm_aesenc_si128(b6, key); + b7 = _mm_aesenc_si128(b7, key); + + key = round_keys[10]; + b0 = _mm_aesenclast_si128(b0, key); + b1 = _mm_aesenclast_si128(b1, key); + b2 = _mm_aesenclast_si128(b2, key); + b3 = _mm_aesenclast_si128(b3, key); + b4 = _mm_aesenclast_si128(b4, key); + b5 = _mm_aesenclast_si128(b5, key); + b6 = _mm_aesenclast_si128(b6, key); + b7 = _mm_aesenclast_si128(b7, key); + + /* Write the blocks. */ + _mm_storeu_si128(reinterpret_cast<__m128i *>(dst + BlockSize * 0), _mm_xor_si128(_mm_loadu_si128(reinterpret_cast<const __m128i *>(src + BlockSize * 0)), b0)); + _mm_storeu_si128(reinterpret_cast<__m128i *>(dst + BlockSize * 1), _mm_xor_si128(_mm_loadu_si128(reinterpret_cast<const __m128i *>(src + BlockSize * 1)), b1)); + _mm_storeu_si128(reinterpret_cast<__m128i *>(dst + BlockSize * 2), _mm_xor_si128(_mm_loadu_si128(reinterpret_cast<const __m128i *>(src + BlockSize * 2)), b2)); + _mm_storeu_si128(reinterpret_cast<__m128i *>(dst + BlockSize * 3), _mm_xor_si128(_mm_loadu_si128(reinterpret_cast<const __m128i *>(src + BlockSize * 3)), b3)); + _mm_storeu_si128(reinterpret_cast<__m128i *>(dst + BlockSize * 4), _mm_xor_si128(_mm_loadu_si128(reinterpret_cast<const __m128i *>(src + BlockSize * 4)), b4)); + _mm_storeu_si128(reinterpret_cast<__m128i *>(dst + BlockSize * 5), _mm_xor_si128(_mm_loadu_si128(reinterpret_cast<const __m128i *>(src + BlockSize * 5)), b5)); + _mm_storeu_si128(reinterpret_cast<__m128i *>(dst + BlockSize * 6), _mm_xor_si128(_mm_loadu_si128(reinterpret_cast<const __m128i *>(src + BlockSize * 6)), b6)); + _mm_storeu_si128(reinterpret_cast<__m128i *>(dst + BlockSize * 7), _mm_xor_si128(_mm_loadu_si128(reinterpret_cast<const __m128i *>(src + BlockSize * 7)), b7)); + + src += BlockSize * UnrolledBlockCount; + dst += BlockSize * UnrolledBlockCount; + } + + /* Store the updated counter. */ + _mm_storeu_si128(reinterpret_cast<__m128i *>(m_counter), counter); + + /* Process blocks one at a time. */ + for (/* ... */; cur_blocks < num_blocks; ++cur_blocks) { + /* Load current counter. */ + __m128i b = _mm_loadu_si128(reinterpret_cast<const __m128i *>(m_counter)); + + /* Do aes rounds. */ + b = _mm_xor_si128(b, round_keys[0]); + b = _mm_aesenc_si128(b, round_keys[1]); + b = _mm_aesenc_si128(b, round_keys[2]); + b = _mm_aesenc_si128(b, round_keys[3]); + b = _mm_aesenc_si128(b, round_keys[4]); + b = _mm_aesenc_si128(b, round_keys[5]); + b = _mm_aesenc_si128(b, round_keys[6]); + b = _mm_aesenc_si128(b, round_keys[7]); + b = _mm_aesenc_si128(b, round_keys[8]); + b = _mm_aesenc_si128(b, round_keys[9]); + b = _mm_aesenclast_si128(b, round_keys[10]); + + /* Write the block. */ + _mm_storeu_si128(reinterpret_cast<__m128i *>(dst), _mm_xor_si128(_mm_loadu_si128(reinterpret_cast<const __m128i *>(src)), b)); + + /* Advance. */ + src += BlockSize; + dst += BlockSize; + this->IncrementCounter(); + } + } else { + /* Fall back to the default implementation. */ + while (num_blocks--) { + this->ProcessBlock(dst, src, BlockSize); + dst += BlockSize; + src += BlockSize; + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_gcm_mode_impl.arch.arm64.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_gcm_mode_impl.arch.arm64.cpp new file mode 100644 index 00000000..b63720e2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_gcm_mode_impl.arch.arm64.cpp @@ -0,0 +1,306 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +#if defined(ATMOSPHERE_IS_STRATOSPHERE) + +/* TODO: EL0 implementation. */ +namespace ams::crypto::impl { + +} + +#else + +/* EL1+ implementation. */ +namespace ams::crypto::impl { + + namespace { + + constexpr u64 GetMultiplyFactor(u8 value) { + constexpr size_t Shift = BITSIZEOF(u8) - 1; + constexpr u8 Mask = (1u << Shift); + return (value & Mask) >> Shift; + } + + /* TODO: Big endian support, eventually? */ + constexpr void GaloisShiftLeft(u64 *block) { + /* Shift the block left by one. */ + block[1] <<= 1; + block[1] |= (block[0] & (static_cast<u64>(1) << (BITSIZEOF(u64) - 1))) >> (BITSIZEOF(u64) - 1); + block[0] <<= 1; + } + + constexpr u8 GaloisShiftRight(u64 *block) { + /* Determine the mask to return. */ + constexpr u8 GaloisFieldMask = 0xE1; + const u8 mask = (block[0] & 1) * GaloisFieldMask; + + /* Shift the block right by one. */ + block[0] >>= 1; + block[0] |= (block[1] & 1) << (BITSIZEOF(u64) - 1); + block[1] >>= 1; + + /* Return the mask. */ + return mask; + } + + /* Multiply two 128-bit numbers X, Y in the GF(128) Galois Field. */ + void GaloisFieldMult(void *dst, const void *x, const void *y) { + /* Our block size is 16 bytes (for a 128-bit integer). */ + constexpr size_t BlockSize = 16; + constexpr size_t FieldSize = 128; + + /* Declare work blocks for us to store temporary values. */ + u8 x_block[BlockSize]; + u8 y_block[BlockSize]; + u8 out[BlockSize]; + + /* Declare 64-bit pointers for our convenience. */ + u64 *x_64 = static_cast<u64 *>(static_cast<void *>(x_block)); + u64 *y_64 = static_cast<u64 *>(static_cast<void *>(y_block)); + u64 *out_64 = static_cast<u64 *>(static_cast<void *>(out)); + + /* Initialize our work blocks. */ + for (size_t i = 0; i < BlockSize; ++i) { + x_block[i] = static_cast<const u8 *>(x)[BlockSize - 1 - i]; + y_block[i] = static_cast<const u8 *>(y)[BlockSize - 1 - i]; + out[i] = 0; + } + + /* Perform multiplication on each bit in y. */ + for (size_t i = 0; i < FieldSize; ++i) { + /* Get the multiply factor for this bit. */ + const auto y_mult = GetMultiplyFactor(y_block[BlockSize - 1]); + + /* Multiply x by the factor. */ + out_64[0] ^= x_64[0] * y_mult; + out_64[1] ^= x_64[1] * y_mult; + + /* Shift left y by one. */ + GaloisShiftLeft(y_64); + + /* Shift right x by one, and mask appropriately. */ + const u8 x_mask = GaloisShiftRight(x_64); + x_block[BlockSize - 1] ^= x_mask; + } + + /* Copy out our result. */ + for (size_t i = 0; i < BlockSize; ++i) { + static_cast<u8 *>(dst)[i] = out[BlockSize - 1 - i]; + } + } + + } + + template<class BlockCipher> + void GcmModeImpl<BlockCipher>::Initialize(const BlockCipher *block_cipher) { + /* Set member variables. */ + m_block_cipher = block_cipher; + m_cipher_func = std::addressof(GcmModeImpl<BlockCipher>::ProcessBlock); + + /* Pre-calculate values to speed up galois field multiplications later. */ + this->InitializeHashKey(); + + /* Note that we're initialized. */ + m_state = State_Initialized; + } + + template<class BlockCipher> + void GcmModeImpl<BlockCipher>::Reset(const void *iv, size_t iv_size) { + /* Validate pre-conditions. */ + AMS_ASSERT(m_state >= State_Initialized); + + /* Reset blocks. */ + m_block_x.block_128.Clear(); + m_block_tmp.block_128.Clear(); + + /* Clear sizes. */ + m_aad_size = 0; + m_msg_size = 0; + m_aad_remaining = 0; + m_msg_remaining = 0; + + /* Update our state. */ + m_state = State_ProcessingAad; + + /* Set our iv. */ + if (iv_size == 12) { + /* If our iv is the correct size, simply copy in the iv, and set the magic bit. */ + std::memcpy(std::addressof(m_block_ek0), iv, iv_size); + util::StoreBigEndian(m_block_ek0.block_32 + 3, static_cast<u32>(1)); + } else { + /* Clear our ek0 block. */ + m_block_ek0.block_128.Clear(); + + /* Update using the iv as aad. */ + this->UpdateAad(iv, iv_size); + + /* Treat the iv as fake msg for the mac that will become our iv. */ + m_msg_size = m_aad_size; + m_aad_size = 0; + + /* Compute a non-final mac. */ + this->ComputeMac(false); + + /* Set our ek0 block to our calculated mac block. */ + m_block_ek0 = m_block_x; + + /* Clear our calculated mac block. */ + m_block_x.block_128.Clear(); + + /* Reset our state. */ + m_msg_size = 0; + m_aad_size = 0; + m_msg_remaining = 0; + m_aad_remaining = 0; + } + + /* Set the working block to the iv. */ + m_block_ek = m_block_ek0; + } + + template<class BlockCipher> + void GcmModeImpl<BlockCipher>::UpdateAad(const void *aad, size_t aad_size) { + /* Validate pre-conditions. */ + AMS_ASSERT(m_state == State_ProcessingAad); + AMS_ASSERT(m_msg_size == 0); + + /* Update our aad size. */ + m_aad_size += aad_size; + + /* Define a working tracker variable. */ + const u8 *cur_aad = static_cast<const u8 *>(aad); + + /* Process any leftover aad data from a previous invocation. */ + if (m_aad_remaining > 0) { + while (aad_size > 0) { + /* Copy in a byte of the aad to our partial block. */ + m_block_x.block_8[m_aad_remaining] ^= *(cur_aad++); + + /* Note that we consumed a byte. */ + --aad_size; + + /* Increment our partial block size. */ + m_aad_remaining = (m_aad_remaining + 1) % BlockSize; + + /* If we have a complete block, process it and move onward. */ + GaloisFieldMult(std::addressof(m_block_x), std::addressof(m_block_x), std::addressof(m_h_mult_blocks[0])); + } + } + + /* Process as many blocks as we can. */ + while (aad_size >= BlockSize) { + /* Xor the current aad into our work block. */ + for (size_t i = 0; i < BlockSize; ++i) { + m_block_x.block_8[i] ^= *(cur_aad++); + } + + /* Multiply the blocks in our galois field. */ + GaloisFieldMult(std::addressof(m_block_x), std::addressof(m_block_x), std::addressof(m_h_mult_blocks[0])); + + /* Note that we've processed a block. */ + aad_size -= BlockSize; + } + + /* Update our state with whatever aad is left over. */ + if (aad_size > 0) { + /* Note how much left over data we have. */ + m_aad_remaining = static_cast<u32>(aad_size); + + /* Xor the data in. */ + for (size_t i = 0; i < aad_size; ++i) { + m_block_x.block_8[i] ^= *(cur_aad++); + } + } + } + + /* TODO: template<class BlockCipher> size_t GcmModeImpl<BlockCipher>::UpdateEncrypt(void *dst, size_t dst_size, const void *src, size_t src_size); */ + + /* TODO: template<class BlockCipher> size_t GcmModeImpl<BlockCipher>::UpdateDecrypt(void *dst, size_t dst_size, const void *src, size_t src_size); */ + + template<class BlockCipher> + void GcmModeImpl<BlockCipher>::GetMac(void *dst, size_t dst_size) { + /* Validate pre-conditions. */ + AMS_ASSERT(State_ProcessingAad <= m_state && m_state <= State_Done); + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(dst_size >= MacSize); + AMS_ASSERT(m_aad_remaining == 0); + AMS_ASSERT(m_msg_remaining == 0); + AMS_UNUSED(dst_size); + + /* If we haven't already done so, compute the final mac. */ + if (m_state != State_Done) { + this->ComputeMac(true); + m_state = State_Done; + } + + static_assert(sizeof(m_block_x) == MacSize); + std::memcpy(dst, std::addressof(m_block_x), MacSize); + } + + template<class BlockCipher> + void GcmModeImpl<BlockCipher>::InitializeHashKey() { + /* We want to encrypt an empty block to use for intermediate calculations. */ + /* NOTE: Non-EL1 implementations will do multiple encryptions ahead of time, */ + /* to speed up galois field arithmetic. */ + constexpr const Block EmptyBlock = {}; + + this->ProcessBlock(std::addressof(m_h_mult_blocks[0]), std::addressof(EmptyBlock), m_block_cipher); + } + + template<class BlockCipher> + void GcmModeImpl<BlockCipher>::ComputeMac(bool encrypt) { + /* If we have leftover data, process it. */ + if (m_aad_remaining > 0 || m_msg_remaining > 0) { + GaloisFieldMult(std::addressof(m_block_x), std::addressof(m_block_x), std::addressof(m_h_mult_blocks[0])); + } + + /* Setup the last block. */ + Block last_block = Block{ .block_128 = { m_msg_size, m_aad_size } }; + + /* Multiply the last block by 8 to account for bit vs byte sizes. */ + static_assert(AMS_OFFSETOF(Block128, hi) == 0); + GaloisShiftLeft(std::addressof(last_block.block_128.hi)); + GaloisShiftLeft(std::addressof(last_block.block_128.hi)); + GaloisShiftLeft(std::addressof(last_block.block_128.hi)); + + /* Xor the data in. */ + for (size_t i = 0; i < BlockSize; ++i) { + m_block_x.block_8[BlockSize - 1 - i] ^= last_block.block_8[i]; + } + + /* Perform the final multiplication. */ + GaloisFieldMult(std::addressof(m_block_x), std::addressof(m_block_x), std::addressof(m_h_mult_blocks[0])); + + /* If we need to do an encryption, do so. */ + if (encrypt) { + /* Encrypt the iv. */ + u8 enc_result[BlockSize]; + this->ProcessBlock(enc_result, std::addressof(m_block_ek0), m_block_cipher); + + /* Xor the iv in. */ + for (size_t i = 0; i < BlockSize; ++i) { + m_block_x.block_8[i] ^= enc_result[i]; + } + } + } + + /* Explicitly instantiate the valid template classes. */ + template class GcmModeImpl<AesEncryptor128>; + +} + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_md5_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_md5_impl.cpp new file mode 100644 index 00000000..6b98c384 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_md5_impl.cpp @@ -0,0 +1,255 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::crypto::impl { + + namespace { + + struct Md5Constants { + static constexpr const u32 A = 0x67452301; + static constexpr const u32 B = 0xEFCDAB89; + static constexpr const u32 C = 0x98BADCFE; + static constexpr const u32 D = 0x10325476; + + static constexpr const u32 T[] = { + 0xD76AA478, 0xE8C7B756, 0x242070DB, 0xC1BDCEEE, + 0xF57C0FAF, 0x4787C62A, 0xA8304613, 0xFD469501, + 0x698098D8, 0x8B44F7AF, 0xFFFF5BB1, 0x895CD7BE, + 0x6B901122, 0xFD987193, 0xA679438E, 0x49B40821, + 0xF61E2562, 0xC040B340, 0x265E5A51, 0xE9B6C7AA, + 0xD62F105D, 0x02441453, 0xD8A1E681, 0xE7D3FBC8, + 0x21E1CDE6, 0xC33707D6, 0xF4D50D87, 0x455A14ED, + 0xA9E3E905, 0xFCEFA3F8, 0x676F02D9, 0x8D2A4C8A, + 0xFFFA3942, 0x8771F681, 0x6D9D6122, 0xFDE5380C, + 0xA4BEEA44, 0x4BDECFA9, 0xF6BB4B60, 0xBEBFBC70, + 0x289B7EC6, 0xEAA127FA, 0xD4EF3085, 0x04881D05, + 0xD9D4D039, 0xE6DB99E5, 0x1FA27CF8, 0xC4AC5665, + 0xF4292244, 0x432AFF97, 0xAB9423A7, 0xFC93A039, + 0x655B59C3, 0x8F0CCC92, 0xFFEFF47D, 0x85845DD1, + 0x6FA87E4F, 0xFE2CE6E0, 0xA3014314, 0x4E0811A1, + 0xF7537E82, 0xBD3AF235, 0x2AD7D2BB, 0xEB86D391, + }; + + static constexpr u32 K[] = { + 0x1, 0x6, 0xB, 0x0, + 0x5, 0xA, 0xF, 0x4, + 0x9, 0xE, 0x3, 0x8, + 0xD, 0x2, 0x7, 0xC, + 0x5, 0x8, 0xB, 0xE, + 0x1, 0x4, 0x7, 0xA, + 0xD, 0x0, 0x3, 0x6, + 0x9, 0xC, 0xF, 0x2, + 0x0, 0x7, 0xE, 0x5, + 0xC, 0x3, 0xA, 0x1, + 0x8, 0xF, 0x6, 0xD, + 0x4, 0xB, 0x2, 0x9, + }; + + static constexpr u8 Padding[] = { + 0x80 + }; + }; + + constexpr ALWAYS_INLINE u32 F(u32 x, u32 y, u32 z) { return (x & y) | ((~x) & z); } + constexpr ALWAYS_INLINE u32 G(u32 x, u32 y, u32 z) { return (x & z) | (y & (~z)); } + constexpr ALWAYS_INLINE u32 H(u32 x, u32 y, u32 z) { return x ^ y ^ z; } + constexpr ALWAYS_INLINE u32 I(u32 x, u32 y, u32 z) { return y ^ (x | (~z)); } + + constexpr ALWAYS_INLINE u32 CalculateRound1(u32 a, u32 b, u32 c, u32 d, u32 x, u32 s, u32 t) { return b + util::RotateLeft<u32>(a + F(b, c, d) + x + t, s); } + constexpr ALWAYS_INLINE u32 CalculateRound2(u32 a, u32 b, u32 c, u32 d, u32 x, u32 s, u32 t) { return b + util::RotateLeft<u32>(a + G(b, c, d) + x + t, s); } + constexpr ALWAYS_INLINE u32 CalculateRound3(u32 a, u32 b, u32 c, u32 d, u32 x, u32 s, u32 t) { return b + util::RotateLeft<u32>(a + H(b, c, d) + x + t, s); } + constexpr ALWAYS_INLINE u32 CalculateRound4(u32 a, u32 b, u32 c, u32 d, u32 x, u32 s, u32 t) { return b + util::RotateLeft<u32>(a + I(b, c, d) + x + t, s); } + + void Encode(u32 *dst, const u32 *src, size_t size) { + if constexpr (util::IsBigEndian()) { + for (size_t i = 0; i < size; i += sizeof(u32)) { + util::StoreLittleEndian(dst + i, src[i]); + } + } else { + std::memcpy(dst, src, size); + } + } + + void Decode(u32 *dst, const u32 *src, size_t size) { + if constexpr (util::IsBigEndian()) { + for (size_t i = 0; i < size; i += sizeof(u32)) { + dst[i] = util::LoadLittleEndian(src + i); + } + } else { + std::memcpy(dst, src, size); + } + } + + } + + void Md5Impl::Initialize() { + /* Set constants. */ + m_x.p.a = Md5Constants::A; + m_x.p.b = Md5Constants::B; + m_x.p.c = Md5Constants::C; + m_x.p.d = Md5Constants::D; + + /* Set size. */ + m_size = 0; + + /* Set initialized. */ + m_state = State_Initialized; + } + + void Md5Impl::Update(const void *data, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(m_state == State_Initialized); + + /* Determine how much we can process. */ + const size_t work_idx = m_size % BlockSize; + const size_t work_remaining = BlockSize - work_idx; + + /* Increment our size. */ + m_size += size; + + /* Copy in the data to our buffer, if we don't have a full block. */ + if (work_remaining > size) { + if (size > 0) { + std::memcpy(m_y + work_idx, data, size); + } + return; + } + + /* Copy what we can to complete our block. */ + std::memcpy(m_y + work_idx, data, work_remaining); + + /* Process the block. */ + this->ProcessBlock(); + + /* Adjust size to account for what we've processed. */ + size -= work_remaining; + + /* Process as many full blocks as we can. */ + const u8 *cur_block = static_cast<const u8 *>(data) + work_remaining; + for (size_t i = 0; i < size / BlockSize; ++i) { + std::memcpy(m_y, cur_block, BlockSize); + cur_block += BlockSize; + + this->ProcessBlock(); + } + + /* Copy in any leftover data. */ + if (const auto left = size % BlockSize; left > 0) { + std::memcpy(m_y, cur_block, left); + } + + } + + void Md5Impl::GetHash(void *dst, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(m_state == State_Initialized || m_state == State_Done); + AMS_ASSERT(size >= HashSize); + AMS_UNUSED(size); + + /* If we need to, finish processing. */ + if (m_state == State_Initialized) { + this->ProcessLastBlock(); + m_state = State_Done; + } + + /* Encode the result. */ + Encode(static_cast<u32 *>(dst), m_x.state, HashSize); + } + + void Md5Impl::ProcessBlock() { + /* Declare tracking pointers for rounds. */ + u32 x[BlockSize / sizeof(u32)]; + const u32 *p_t = Md5Constants::T; + const u32 *p_k = Md5Constants::K; + const u32 *p_x = x; + + /* Extract current state. */ + u32 a = m_x.p.a; + u32 b = m_x.p.b; + u32 c = m_x.p.c; + u32 d = m_x.p.d; + + /* Decode the block into native endian. */ + Decode(x, reinterpret_cast<const u32 *>(m_y), BlockSize); + + /* Perform round 1. */ + for (size_t i = 0; i < 4; ++i) { + a = CalculateRound1(a, b, c, d, *p_x++, 7, *p_t++); + d = CalculateRound1(d, a, b, c, *p_x++, 12, *p_t++); + c = CalculateRound1(c, d, a, b, *p_x++, 17, *p_t++); + b = CalculateRound1(b, c, d, a, *p_x++, 22, *p_t++); + } + + /* Perform round 2. */ + for (size_t i = 0; i < 4; ++i) { + a = CalculateRound2(a, b, c, d, x[*p_k++], 5, *p_t++); + d = CalculateRound2(d, a, b, c, x[*p_k++], 9, *p_t++); + c = CalculateRound2(c, d, a, b, x[*p_k++], 14, *p_t++); + b = CalculateRound2(b, c, d, a, x[*p_k++], 20, *p_t++); + } + + /* Perform round 3. */ + for (size_t i = 0; i < 4; ++i) { + a = CalculateRound3(a, b, c, d, x[*p_k++], 4, *p_t++); + d = CalculateRound3(d, a, b, c, x[*p_k++], 11, *p_t++); + c = CalculateRound3(c, d, a, b, x[*p_k++], 16, *p_t++); + b = CalculateRound3(b, c, d, a, x[*p_k++], 23, *p_t++); + } + + /* Perform round 4. */ + for (size_t i = 0; i < 4; ++i) { + a = CalculateRound4(a, b, c, d, x[*p_k++], 6, *p_t++); + d = CalculateRound4(d, a, b, c, x[*p_k++], 10, *p_t++); + c = CalculateRound4(c, d, a, b, x[*p_k++], 15, *p_t++); + b = CalculateRound4(b, c, d, a, x[*p_k++], 21, *p_t++); + } + + /* Mix the result back into our state. */ + m_x.p.a += a; + m_x.p.b += b; + m_x.p.c += c; + m_x.p.d += d; + } + + void Md5Impl::ProcessLastBlock() { + /* Get bit count. */ + const u64 bit_count = m_size * BITSIZEOF(u8); + + /* Add padding byte unconditionally. */ + this->Update(Md5Constants::Padding, sizeof(Md5Constants::Padding)); + + /* Determine remaining. */ + size_t work_idx = m_size % BlockSize; + size_t work_remaining = BlockSize - work_idx; + + /* We want to process 8000.....{bit count}. */ + if (work_remaining < sizeof(u64)) { + std::memset(m_y + work_idx, 0, work_remaining); + this->ProcessBlock(); + work_idx = 0; + work_remaining = BlockSize; + } + if (work_remaining > sizeof(u64)) { + std::memset(m_y + work_idx, 0, work_remaining - sizeof(u64)); + } + + util::StoreLittleEndian<u64>(reinterpret_cast<u64 *>(m_y + BlockSize - sizeof(u64)), bit_count); + + this->ProcessBlock(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_sha1_impl.arch.arm64.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_sha1_impl.arch.arm64.cpp new file mode 100644 index 00000000..b78ed33e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_sha1_impl.arch.arm64.cpp @@ -0,0 +1,247 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <arm_neon.h> + +namespace ams::crypto::impl { + + namespace { + + constexpr const u32 RoundConstants[4] = { + 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6 + }; + + /* Define for loading work var from message. */ + #define SHA1_LOAD_W_FROM_MESSAGE(which) \ + w[which] = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(data))); \ + data += 0x10 + + #define SHA1_CALCULATE_W_FROM_PREVIOUS(i) \ + w[i] = vsha1su1q_u32(vsha1su0q_u32(w[i-4], w[i-3], w[i-2]), w[i-1]) + + /* Define for doing four rounds of SHA1. */ + #define SHA1_DO_ROUND(r, insn, constant) \ + do { \ + const u32 a = vgetq_lane_u32(cur_abcd, 0); \ + cur_abcd = v##insn##q_u32(cur_abcd, cur_e, vaddq_u32(w[r], constant)); \ + cur_e = vsha1h_u32(a); \ + } while (0) + + + } + + void Sha1Impl::Initialize() { + /* Reset buffered bytes/bits. */ + m_buffered_bytes = 0; + m_bits_consumed = 0; + + /* Set intermediate hash. */ + m_intermediate_hash[0] = 0x67452301; + m_intermediate_hash[1] = 0xEFCDAB89; + m_intermediate_hash[2] = 0x98BADCFE; + m_intermediate_hash[3] = 0x10325476; + m_intermediate_hash[4] = 0xC3D2E1F0; + + /* Set state. */ + m_state = State_Initialized; + } + + void Sha1Impl::Update(const void *data, size_t size) { + /* Verify we're in a state to update. */ + AMS_ASSERT(m_state == State_Initialized); + + /* Advance our input bit count. */ + m_bits_consumed += BITSIZEOF(u8) * (((m_buffered_bytes + size) / BlockSize) * BlockSize); + + /* Process anything we have buffered. */ + const u8 *data8 = static_cast<const u8 *>(data); + size_t remaining = size; + + if (m_buffered_bytes > 0) { + const size_t copy_size = std::min(BlockSize - m_buffered_bytes, remaining); + std::memcpy(m_buffer + m_buffered_bytes, data8, copy_size); + + data8 += copy_size; + remaining -= copy_size; + m_buffered_bytes += copy_size; + + /* Process a block, if we filled one. */ + if (m_buffered_bytes == BlockSize) { + this->ProcessBlock(m_buffer); + m_buffered_bytes = 0; + } + } + + /* Process blocks, if we have any. */ + if (remaining >= BlockSize) { + const size_t blocks = remaining / BlockSize; + + this->ProcessBlocks(data8, blocks); + data8 += BlockSize * blocks; + remaining -= BlockSize * blocks; + } + + /* Copy any leftover data to our buffer. */ + if (remaining > 0) { + m_buffered_bytes = remaining; + std::memcpy(m_buffer, data8, remaining); + } + } + + void Sha1Impl::GetHash(void *dst, size_t size) { + /* Verify we're in a state to get hash. */ + AMS_ASSERT(m_state == State_Initialized || m_state == State_Done); + AMS_ASSERT(size >= HashSize); + AMS_UNUSED(size); + + /* If we need to, process the last block. */ + if (m_state == State_Initialized) { + this->ProcessLastBlock(); + m_state = State_Done; + } + + /* Copy the output hash. */ + if constexpr (util::IsLittleEndian()) { + static_assert(HashSize % sizeof(u32) == 0); + + u32 *dst_32 = static_cast<u32 *>(dst); + for (size_t i = 0; i < HashSize / sizeof(u32); ++i) { + dst_32[i] = util::LoadBigEndian<u32>(m_intermediate_hash + i); + } + } else { + std::memcpy(dst, m_intermediate_hash, HashSize); + } + } + + ALWAYS_INLINE void Sha1Impl::ProcessBlock(const void *data) { + return this->ProcessBlocks(static_cast<const u8 *>(data), 1); + } + + void Sha1Impl::ProcessBlocks(const u8 *data, size_t block_count) { + /* Setup round constants. */ + const uint32x4_t k0 = vdupq_n_u32(RoundConstants[0]); + const uint32x4_t k1 = vdupq_n_u32(RoundConstants[1]); + const uint32x4_t k2 = vdupq_n_u32(RoundConstants[2]); + const uint32x4_t k3 = vdupq_n_u32(RoundConstants[3]); + + /* Load hash variables with intermediate state. */ + uint32x4_t cur_abcd = vld1q_u32(m_intermediate_hash + 0); + u32 cur_e = m_intermediate_hash[4]; + + /* Actually do hash processing blocks. */ + do { + /* Save current state. */ + const uint32x4_t prev_abcd = cur_abcd; + const u32 prev_e = cur_e; + + uint32x4_t w[20]; + + /* Setup w[0-3] with message. */ + SHA1_LOAD_W_FROM_MESSAGE(0); + SHA1_LOAD_W_FROM_MESSAGE(1); + SHA1_LOAD_W_FROM_MESSAGE(2); + SHA1_LOAD_W_FROM_MESSAGE(3); + + /* Calculate w[4-19], w[i] = sha1su1(sha1su0(w[i-4], w[i-3], w[i-2]), w[i-1]); */ + SHA1_CALCULATE_W_FROM_PREVIOUS(4); + SHA1_CALCULATE_W_FROM_PREVIOUS(5); + SHA1_CALCULATE_W_FROM_PREVIOUS(6); + SHA1_CALCULATE_W_FROM_PREVIOUS(7); + SHA1_CALCULATE_W_FROM_PREVIOUS(8); + SHA1_CALCULATE_W_FROM_PREVIOUS(9); + SHA1_CALCULATE_W_FROM_PREVIOUS(10); + SHA1_CALCULATE_W_FROM_PREVIOUS(11); + SHA1_CALCULATE_W_FROM_PREVIOUS(12); + SHA1_CALCULATE_W_FROM_PREVIOUS(13); + SHA1_CALCULATE_W_FROM_PREVIOUS(14); + SHA1_CALCULATE_W_FROM_PREVIOUS(15); + SHA1_CALCULATE_W_FROM_PREVIOUS(16); + SHA1_CALCULATE_W_FROM_PREVIOUS(17); + SHA1_CALCULATE_W_FROM_PREVIOUS(18); + SHA1_CALCULATE_W_FROM_PREVIOUS(19); + + /* Do round calculations 0-20. Uses sha1c, k0. */ + SHA1_DO_ROUND(0, sha1c, k0); + SHA1_DO_ROUND(1, sha1c, k0); + SHA1_DO_ROUND(2, sha1c, k0); + SHA1_DO_ROUND(3, sha1c, k0); + SHA1_DO_ROUND(4, sha1c, k0); + + /* Do round calculations 20-40. Uses sha1p, k1. */ + SHA1_DO_ROUND(5, sha1p, k1); + SHA1_DO_ROUND(6, sha1p, k1); + SHA1_DO_ROUND(7, sha1p, k1); + SHA1_DO_ROUND(8, sha1p, k1); + SHA1_DO_ROUND(9, sha1p, k1); + + /* Do round calculations 40-60. Uses sha1m, k2. */ + SHA1_DO_ROUND(10, sha1m, k2); + SHA1_DO_ROUND(11, sha1m, k2); + SHA1_DO_ROUND(12, sha1m, k2); + SHA1_DO_ROUND(13, sha1m, k2); + SHA1_DO_ROUND(14, sha1m, k2); + + /* Do round calculations 60-80. Uses sha1p, k3. */ + SHA1_DO_ROUND(15, sha1p, k3); + SHA1_DO_ROUND(16, sha1p, k3); + SHA1_DO_ROUND(17, sha1p, k3); + SHA1_DO_ROUND(18, sha1p, k3); + SHA1_DO_ROUND(19, sha1p, k3); + + /* Add to previous. */ + cur_abcd = vaddq_u32(cur_abcd, prev_abcd); + cur_e = cur_e + prev_e; + } while (--block_count != 0); + + /* Save result to intermediate hash. */ + vst1q_u32(m_intermediate_hash, cur_abcd); + m_intermediate_hash[4] = cur_e; + } + + void Sha1Impl::ProcessLastBlock() { + /* Setup the final block. */ + constexpr const auto BlockSizeWithoutSizeField = BlockSize - sizeof(u64); + + /* Increment our bits consumed. */ + m_bits_consumed += BITSIZEOF(u8) * m_buffered_bytes; + + /* Add 0x80 terminator. */ + m_buffer[m_buffered_bytes++] = 0x80; + + /* If we can process the size field directly, do so, otherwise set up to process it. */ + if (m_buffered_bytes <= BlockSizeWithoutSizeField) { + /* Clear up to size field. */ + std::memset(m_buffer + m_buffered_bytes, 0, BlockSizeWithoutSizeField - m_buffered_bytes); + } else { + /* Consume full block */ + std::memset(m_buffer + m_buffered_bytes, 0, BlockSize - m_buffered_bytes); + this->ProcessBlock(m_buffer); + + /* Clear up to size field. */ + std::memset(m_buffer, 0, BlockSizeWithoutSizeField); + } + + /* Store the size field. */ + util::StoreBigEndian<u64>(reinterpret_cast<u64 *>(m_buffer + BlockSizeWithoutSizeField), m_bits_consumed); + + /* Process the final block. */ + this->ProcessBlock(m_buffer); + } + +} +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_sha1_impl.arch.generic.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_sha1_impl.arch.generic.cpp new file mode 100644 index 00000000..2eda14d7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_sha1_impl.arch.generic.cpp @@ -0,0 +1,225 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::crypto::impl { + + namespace { + + constexpr const u32 RoundConstants[4] = { + 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6 + }; + + constexpr ALWAYS_INLINE u32 Choose(u32 x, u32 y, u32 z) { + return (x & y) ^ ((~x) & z); + } + + constexpr ALWAYS_INLINE u32 Majority(u32 x, u32 y, u32 z) { + return (x & y) ^ (x & z) ^ (y & z); + } + + constexpr ALWAYS_INLINE u32 Parity(u32 x, u32 y, u32 z) { + return x ^ y ^ z; + } + + } + + void Sha1Impl::Initialize() { + /* Reset buffered bytes/bits. */ + m_buffered_bytes = 0; + m_bits_consumed = 0; + + /* Set intermediate hash. */ + m_intermediate_hash[0] = 0x67452301; + m_intermediate_hash[1] = 0xEFCDAB89; + m_intermediate_hash[2] = 0x98BADCFE; + m_intermediate_hash[3] = 0x10325476; + m_intermediate_hash[4] = 0xC3D2E1F0; + + /* Set state. */ + m_state = State_Initialized; + } + + void Sha1Impl::Update(const void *data, size_t size) { + /* Verify we're in a state to update. */ + AMS_ASSERT(m_state == State_Initialized); + + /* Advance our input bit count. */ + m_bits_consumed += BITSIZEOF(u8) * (((m_buffered_bytes + size) / BlockSize) * BlockSize); + + /* Process anything we have buffered. */ + const u8 *data8 = static_cast<const u8 *>(data); + size_t remaining = size; + + if (m_buffered_bytes > 0) { + const size_t copy_size = std::min(BlockSize - m_buffered_bytes, remaining); + std::memcpy(m_buffer + m_buffered_bytes, data8, copy_size); + + data8 += copy_size; + remaining -= copy_size; + m_buffered_bytes += copy_size; + + /* Process a block, if we filled one. */ + if (m_buffered_bytes == BlockSize) { + this->ProcessBlock(m_buffer); + m_buffered_bytes = 0; + } + } + + /* Process blocks, while we have any. */ + while (remaining >= BlockSize) { + this->ProcessBlock(data8); + data8 += BlockSize; + remaining -= BlockSize; + } + + /* Copy any leftover data to our buffer. */ + if (remaining > 0) { + m_buffered_bytes = remaining; + std::memcpy(m_buffer, data8, remaining); + } + } + + void Sha1Impl::GetHash(void *dst, size_t size) { + /* Verify we're in a state to get hash. */ + AMS_ASSERT(m_state == State_Initialized || m_state == State_Done); + AMS_ASSERT(size >= HashSize); + AMS_UNUSED(size); + + /* If we need to, process the last block. */ + if (m_state == State_Initialized) { + this->ProcessLastBlock(); + m_state = State_Done; + } + + /* Copy the output hash. */ + if constexpr (util::IsLittleEndian()) { + static_assert(HashSize % sizeof(u32) == 0); + + u32 *dst_32 = static_cast<u32 *>(dst); + for (size_t i = 0; i < HashSize / sizeof(u32); ++i) { + dst_32[i] = util::LoadBigEndian<u32>(m_intermediate_hash + i); + } + } else { + std::memcpy(dst, m_intermediate_hash, HashSize); + } + } + + void Sha1Impl::ProcessBlock(const void *data) { + /* Load work variables. */ + u32 a = m_intermediate_hash[0]; + u32 b = m_intermediate_hash[1]; + u32 c = m_intermediate_hash[2]; + u32 d = m_intermediate_hash[3]; + u32 e = m_intermediate_hash[4]; + u32 tmp; + size_t i; + + /* Copy the input. */ + u32 w[80]; + if constexpr (util::IsLittleEndian()) { + static_assert(BlockSize % sizeof(u32) == 0); + + const u32 *src_32 = static_cast<const u32 *>(data); + for (size_t i = 0; i < BlockSize / sizeof(u32); ++i) { + w[i] = util::LoadBigEndian<u32>(src_32 + i); + } + } else { + std::memcpy(w, data, BlockSize); + } + + /* Initialize the rest of w. */ + for (i = BlockSize / sizeof(u32); i < util::size(w); ++i) { + const u32 *prev = w + (i - BlockSize / sizeof(u32)); + w[i] = util::RotateLeft<u32>(prev[0] ^ prev[2] ^ prev[8] ^ prev[13], 1); + } + + /* Perform rounds. */ + for (i = 0; i < 20; ++i) { + tmp = util::RotateLeft<u32>(a, 5) + Choose(b, c, d) + e + w[i] + RoundConstants[0]; + e = d; + d = c; + c = util::RotateLeft<u32>(b, 30); + b = a; + a = tmp; + } + + for (/* ... */; i < 40; ++i) { + tmp = util::RotateLeft<u32>(a, 5) + Parity(b, c, d) + e + w[i] + RoundConstants[1]; + e = d; + d = c; + c = util::RotateLeft<u32>(b, 30); + b = a; + a = tmp; + } + + for (/* ... */; i < 60; ++i) { + tmp = util::RotateLeft<u32>(a, 5) + Majority(b, c, d) + e + w[i] + RoundConstants[2]; + e = d; + d = c; + c = util::RotateLeft<u32>(b, 30); + b = a; + a = tmp; + } + + for (/* ... */; i < 80; ++i) { + tmp = util::RotateLeft<u32>(a, 5) + Parity(b, c, d) + e + w[i] + RoundConstants[3]; + e = d; + d = c; + c = util::RotateLeft<u32>(b, 30); + b = a; + a = tmp; + } + + /* Update intermediate hash. */ + m_intermediate_hash[0] += a; + m_intermediate_hash[1] += b; + m_intermediate_hash[2] += c; + m_intermediate_hash[3] += d; + m_intermediate_hash[4] += e; + } + + void Sha1Impl::ProcessLastBlock() { + /* Setup the final block. */ + constexpr const auto BlockSizeWithoutSizeField = BlockSize - sizeof(u64); + + /* Increment our bits consumed. */ + m_bits_consumed += BITSIZEOF(u8) * m_buffered_bytes; + + /* Add 0x80 terminator. */ + m_buffer[m_buffered_bytes++] = 0x80; + + /* If we can process the size field directly, do so, otherwise set up to process it. */ + if (m_buffered_bytes <= BlockSizeWithoutSizeField) { + /* Clear up to size field. */ + std::memset(m_buffer + m_buffered_bytes, 0, BlockSizeWithoutSizeField - m_buffered_bytes); + } else { + /* Consume full block */ + std::memset(m_buffer + m_buffered_bytes, 0, BlockSize - m_buffered_bytes); + this->ProcessBlock(m_buffer); + + /* Clear up to size field. */ + std::memset(m_buffer, 0, BlockSizeWithoutSizeField); + } + + /* Store the size field. */ + util::StoreBigEndian<u64>(reinterpret_cast<u64 *>(m_buffer + BlockSizeWithoutSizeField), m_bits_consumed); + + /* Process the final block. */ + this->ProcessBlock(m_buffer); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_sha256_impl.arch.arm64.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_sha256_impl.arch.arm64.cpp new file mode 100644 index 00000000..74ff84b4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_sha256_impl.arch.arm64.cpp @@ -0,0 +1,327 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <arm_neon.h> + +namespace ams::crypto::impl { + + namespace { + + alignas(Sha256Impl::BlockSize) constexpr const u32 RoundConstants[0x40] = { + 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, + 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, + 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, + 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, + 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, + 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, + 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, + 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, + 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, + 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, + 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, + 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, + 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, + 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, + 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, + 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2, + }; + + } + + void Sha256Impl::Initialize() { + /* Reset buffered bytes/bits. */ + m_buffered_bytes = 0; + m_bits_consumed = 0; + + /* Set intermediate hash. */ + m_intermediate_hash[0] = 0x6A09E667; + m_intermediate_hash[1] = 0xBB67AE85; + m_intermediate_hash[2] = 0x3C6EF372; + m_intermediate_hash[3] = 0xA54FF53A; + m_intermediate_hash[4] = 0x510E527F; + m_intermediate_hash[5] = 0x9B05688C; + m_intermediate_hash[6] = 0x1F83D9AB; + m_intermediate_hash[7] = 0x5BE0CD19; + + /* Set state. */ + m_state = State_Initialized; + } + + void Sha256Impl::Update(const void *data, size_t size) { + /* Verify we're in a state to update. */ + AMS_ASSERT(m_state == State_Initialized); + + /* Advance our input bit count. */ + m_bits_consumed += BITSIZEOF(u8) * (((m_buffered_bytes + size) / BlockSize) * BlockSize); + + /* Process anything we have buffered. */ + const u8 *data8 = static_cast<const u8 *>(data); + size_t remaining = size; + + if (m_buffered_bytes > 0) { + const size_t copy_size = std::min(BlockSize - m_buffered_bytes, remaining); + std::memcpy(m_buffer + m_buffered_bytes, data8, copy_size); + + data8 += copy_size; + remaining -= copy_size; + m_buffered_bytes += copy_size; + + /* Process a block, if we filled one. */ + if (m_buffered_bytes == BlockSize) { + this->ProcessBlock(m_buffer); + m_buffered_bytes = 0; + } + } + + /* Process blocks, if we have any. */ + if (remaining >= BlockSize) { + const size_t blocks = remaining / BlockSize; + + this->ProcessBlocks(data8, blocks); + data8 += BlockSize * blocks; + remaining -= BlockSize * blocks; + } + + /* Copy any leftover data to our buffer. */ + if (remaining > 0) { + m_buffered_bytes = remaining; + std::memcpy(m_buffer, data8, remaining); + } + } + + void Sha256Impl::GetHash(void *dst, size_t size) { + /* Verify we're in a state to get hash. */ + AMS_ASSERT(m_state == State_Initialized || m_state == State_Done); + AMS_ASSERT(size >= HashSize); + AMS_UNUSED(size); + + /* If we need to, process the last block. */ + if (m_state == State_Initialized) { + this->ProcessLastBlock(); + m_state = State_Done; + } + + /* Copy the output hash. */ + if constexpr (util::IsLittleEndian()) { + static_assert(HashSize % sizeof(u32) == 0); + + u32 *dst_32 = static_cast<u32 *>(dst); + for (size_t i = 0; i < HashSize / sizeof(u32); ++i) { + dst_32[i] = util::LoadBigEndian<u32>(m_intermediate_hash + i); + } + } else { + std::memcpy(dst, m_intermediate_hash, HashSize); + } + } + + void Sha256Impl::InitializeWithContext(const Sha256Context *context) { + /* Copy state in from the context. */ + std::memcpy(m_intermediate_hash, context->intermediate_hash, sizeof(m_intermediate_hash)); + m_bits_consumed = context->bits_consumed; + + /* Reset other fields. */ + m_buffered_bytes = 0; + m_state = State_Initialized; + } + + size_t Sha256Impl::GetContext(Sha256Context *context) const { + /* Check our state. */ + AMS_ASSERT(m_state == State_Initialized); + + /* Copy out the context. */ + std::memcpy(context->intermediate_hash, m_intermediate_hash, sizeof(context->intermediate_hash)); + context->bits_consumed = m_bits_consumed; + + return m_buffered_bytes; + } + + ALWAYS_INLINE void Sha256Impl::ProcessBlock(const void *data) { + return this->ProcessBlocks(static_cast<const u8 *>(data), 1); + } + + void Sha256Impl::ProcessBlocks(const u8 *data, size_t block_count) { + /* Load previous hash with intermediate state, current hash with zeroes. */ + uint32x4_t prev_hash0 = vld1q_u32(m_intermediate_hash + 0); + uint32x4_t prev_hash1 = vld1q_u32(m_intermediate_hash + 4); + uint32x4_t cur_hash0 = vdupq_n_u32(0); + uint32x4_t cur_hash1 = vdupq_n_u32(0); + + /* Process blocks. */ + do { + uint32x4_t round_constant0, round_constant1; + uint32x4_t data0, data1, data2, data3; + uint32x4_t tmp0, tmp1, tmp2, tmp3; + uint32x4_t tmp_hash; + + /* Use optimized ASM implementation to process the block. */ + __asm__ __volatile__ ( + "ldp %q[data0], %q[data1], [%[data]], #0x20\n" + "ldp %q[data2], %q[data3], [%[data]], #0x20\n" + "add %[cur_hash0].4s, %[cur_hash0].4s, %[prev_hash0].4s\n" + "ldp %q[round_constant0], %q[round_constant1], [%[round_constants], 0x00]\n" + "add %[cur_hash1].4s, %[cur_hash1].4s, %[prev_hash1].4s\n" + "rev32 %[data0].16b, %[data0].16b\n" + "rev32 %[data1].16b, %[data1].16b\n" + "rev32 %[data2].16b, %[data2].16b\n" + "rev32 %[data3].16b, %[data3].16b\n" + "add %[tmp0].4s, %[data0].4s, %[round_constant0].4s\n" + "add %[tmp1].4s, %[data1].4s, %[round_constant1].4s\n" + "ldp %q[round_constant0], %q[round_constant1], [%[round_constants], 0x20]\n" + "sha256su0 %[data0].4s, %[data1].4s\n" + "mov %[prev_hash0].16b, %[cur_hash0].16b\n" + "sha256h %q[cur_hash0], %q[cur_hash1], %[tmp0].4s\n" + "mov %[prev_hash1].16b, %[cur_hash1].16b\n" + "sha256h2 %q[cur_hash1], %q[prev_hash0], %[tmp0].4s\n" + "sha256su0 %[data1].4s, %[data2].4s\n" + "sha256su1 %[data0].4s, %[data2].4s, %[data3].4s\n" + "add %[tmp2].4s, %[data2].4s, %[round_constant0].4s\n" + "mov %[tmp_hash].16b, %[cur_hash0].16b\n" + "sha256h %q[cur_hash0], %q[cur_hash1], %[tmp1].4s\n" + "sha256h2 %q[cur_hash1], %q[tmp_hash], %[tmp1].4s\n" + "sha256su0 %[data2].4s, %[data3].4s\n" + "sha256su1 %[data1].4s, %[data3].4s, %[data0].4s\n" + "add %[tmp3].4s, %[data3].4s, %[round_constant1].4s\n" + "mov %[tmp_hash].16b, %[cur_hash0].16b\n" + "ldp %q[round_constant0], %q[round_constant1], [%[round_constants], 0x40]\n" + "sha256h %q[cur_hash0], %q[cur_hash1], %[tmp2].4s\n" + "sha256h2 %q[cur_hash1], %q[tmp_hash], %[tmp2].4s\n" + "sha256su0 %[data3].4s, %[data0].4s\n" + "sha256su1 %[data2].4s, %[data0].4s, %[data1].4s\n" + "add %[tmp0].4s, %[data0].4s, %[round_constant0].4s\n" + "mov %[tmp_hash].16b, %[cur_hash0].16b\n" + "sha256h %q[cur_hash0], %q[cur_hash1], %[tmp3].4s\n" + "sha256h2 %q[cur_hash1], %q[tmp_hash], %[tmp3].4s\n" + "sha256su0 %[data0].4s, %[data1].4s\n" + "sha256su1 %[data3].4s, %[data1].4s, %[data2].4s\n" + "add %[tmp1].4s, %[data1].4s, %[round_constant1].4s\n" + "mov %[tmp_hash].16b, %[cur_hash0].16b\n" + "ldp %q[round_constant0], %q[round_constant1], [%[round_constants], 0x60]\n" + "sha256h %q[cur_hash0], %q[cur_hash1], %[tmp0].4s\n" + "sha256h2 %q[cur_hash1], %q[tmp_hash], %[tmp0].4s\n" + "sha256su0 %[data1].4s, %[data2].4s\n" + "sha256su1 %[data0].4s, %[data2].4s, %[data3].4s\n" + "add %[tmp2].4s, %[data2].4s, %[round_constant0].4s\n" + "mov %[tmp_hash].16b, %[cur_hash0].16b\n" + "sha256h %q[cur_hash0], %q[cur_hash1], %[tmp1].4s\n" + "sha256h2 %q[cur_hash1], %q[tmp_hash], %[tmp1].4s\n" + "sha256su0 %[data2].4s, %[data3].4s\n" + "sha256su1 %[data1].4s, %[data3].4s, %[data0].4s\n" + "add %[tmp3].4s, %[data3].4s, %[round_constant1].4s\n" + "mov %[tmp_hash].16b, %[cur_hash0].16b\n" + "ldp %q[round_constant0], %q[round_constant1], [%[round_constants], 0x80]\n" + "sha256h %q[cur_hash0], %q[cur_hash1], %[tmp2].4s\n" + "sha256h2 %q[cur_hash1], %q[tmp_hash], %[tmp2].4s\n" + "sha256su0 %[data3].4s, %[data0].4s\n" + "sha256su1 %[data2].4s, %[data0].4s, %[data1].4s\n" + "add %[tmp0].4s, %[data0].4s, %[round_constant0].4s\n" + "mov %[tmp_hash].16b, %[cur_hash0].16b\n" + "sha256h %q[cur_hash0], %q[cur_hash1], %[tmp3].4s\n" + "sha256h2 %q[cur_hash1], %q[tmp_hash], %[tmp3].4s\n" + "sha256su0 %[data0].4s, %[data1].4s\n" + "sha256su1 %[data3].4s, %[data1].4s, %[data2].4s\n" + "add %[tmp1].4s, %[data1].4s, %[round_constant1].4s\n" + "mov %[tmp_hash].16b, %[cur_hash0].16b\n" + "ldp %q[round_constant0], %q[round_constant1], [%[round_constants], 0xA0]\n" + "sha256h %q[cur_hash0], %q[cur_hash1], %[tmp0].4s\n" + "sha256h2 %q[cur_hash1], %q[tmp_hash], %[tmp0].4s\n" + "sha256su0 %[data1].4s, %[data2].4s\n" + "sha256su1 %[data0].4s, %[data2].4s, %[data3].4s\n" + "add %[tmp2].4s, %[data2].4s, %[round_constant0].4s\n" + "mov %[tmp_hash].16b, %[cur_hash0].16b\n" + "sha256h %q[cur_hash0], %q[cur_hash1], %[tmp1].4s\n" + "sha256h2 %q[cur_hash1], %q[tmp_hash], %[tmp1].4s\n" + "sha256su0 %[data2].4s, %[data3].4s\n" + "sha256su1 %[data1].4s, %[data3].4s, %[data0].4s\n" + "add %[tmp3].4s, %[data3].4s, %[round_constant1].4s\n" + "mov %[tmp_hash].16b, %[cur_hash0].16b\n" + "ldp %q[round_constant0], %q[round_constant1], [%[round_constants], 0xC0]\n" + "sha256h %q[cur_hash0], %q[cur_hash1], %[tmp2].4s\n" + "sha256h2 %q[cur_hash1], %q[tmp_hash], %[tmp2].4s\n" + "sha256su0 %[data3].4s, %[data0].4s\n" + "sha256su1 %[data2].4s, %[data0].4s, %[data1].4s\n" + "add %[tmp0].4s, %[data0].4s, %[round_constant0].4s\n" + "mov %[tmp_hash].16b, %[cur_hash0].16b\n" + "sha256h %q[cur_hash0], %q[cur_hash1], %[tmp3].4s\n" + "sha256h2 %q[cur_hash1], %q[tmp_hash], %[tmp3].4s\n" + "sha256su1 %[data3].4s, %[data1].4s, %[data2].4s\n" + "add %[tmp1].4s, %[data1].4s, %[round_constant1].4s\n" + "mov %[tmp_hash].16b, %[cur_hash0].16b\n" + "ldp %q[round_constant0], %q[round_constant1], [%[round_constants], 0xE0]\n" + "sha256h %q[cur_hash0], %q[cur_hash1], %[tmp0].4s\n" + "sha256h2 %q[cur_hash1], %q[tmp_hash], %[tmp0].4s\n" + "add %[tmp2].4s, %[data2].4s, %[round_constant0].4s\n" + "mov %[tmp_hash].16b, %[cur_hash0].16b\n" + "sha256h %q[cur_hash0], %q[cur_hash1], %[tmp1].4s\n" + "sha256h2 %q[cur_hash1], %q[tmp_hash], %[tmp1].4s\n" + "add %[tmp3].4s, %[data3].4s, %[round_constant1].4s\n" + "mov %[tmp_hash].16b, %[cur_hash0].16b\n" + "sha256h %q[cur_hash0], %q[cur_hash1], %[tmp2].4s\n" + "sha256h2 %q[cur_hash1], %q[tmp_hash], %[tmp2].4s\n" + "mov %[tmp_hash].16b, %[cur_hash0].16b\n" + "sha256h %q[cur_hash0], %q[cur_hash1], %[tmp3].4s\n" + "sha256h2 %q[cur_hash1], %q[tmp_hash], %[tmp3].4s\n" + : [data0]"=w"(data0), [data1]"=w"(data1), [data2]"=w"(data2), [data3]"=w"(data3), + [tmp0]"=w"(tmp0), [tmp1]"=w"(tmp1), [tmp2]"=w"(tmp2), [tmp3]"=w"(tmp3), + [round_constant0]"=w"(round_constant0), [round_constant1]"=w"(round_constant1), + [cur_hash0]"+w"(cur_hash0), [cur_hash1]"+w"(cur_hash1), + [prev_hash0]"+w"(prev_hash0), [prev_hash1]"+w"(prev_hash1), + [tmp_hash]"=w"(tmp_hash), [data]"+r"(data) + : "m"(*(const u8 (*)[block_count*BlockSize])data), [round_constants]"r"(RoundConstants) + : + ); + } while (--block_count != 0); + + /* Add hashes together, and store. */ + cur_hash0 = vaddq_u32(prev_hash0, cur_hash0); + cur_hash1 = vaddq_u32(prev_hash1, cur_hash1); + vst1q_u32(m_intermediate_hash + 0, cur_hash0); + vst1q_u32(m_intermediate_hash + 4, cur_hash1); + } + + void Sha256Impl::ProcessLastBlock() { + /* Setup the final block. */ + constexpr const auto BlockSizeWithoutSizeField = BlockSize - sizeof(u64); + + /* Increment our bits consumed. */ + m_bits_consumed += BITSIZEOF(u8) * m_buffered_bytes; + + /* Add 0x80 terminator. */ + m_buffer[m_buffered_bytes++] = 0x80; + + /* If we can process the size field directly, do so, otherwise set up to process it. */ + if (m_buffered_bytes <= BlockSizeWithoutSizeField) { + /* Clear up to size field. */ + std::memset(m_buffer + m_buffered_bytes, 0, BlockSizeWithoutSizeField - m_buffered_bytes); + } else { + /* Consume full block */ + std::memset(m_buffer + m_buffered_bytes, 0, BlockSize - m_buffered_bytes); + this->ProcessBlock(m_buffer); + + /* Clear up to size field. */ + std::memset(m_buffer, 0, BlockSizeWithoutSizeField); + } + + /* Store the size field. */ + util::StoreBigEndian<u64>(reinterpret_cast<u64 *>(m_buffer + BlockSizeWithoutSizeField), m_bits_consumed); + + /* Process the final block. */ + this->ProcessBlock(m_buffer); + } + +} +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_sha256_impl.arch.generic.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_sha256_impl.arch.generic.cpp new file mode 100644 index 00000000..ba7076c7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_sha256_impl.arch.generic.cpp @@ -0,0 +1,260 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::crypto::impl { + + namespace { + + alignas(Sha256Impl::BlockSize) constexpr const u32 RoundConstants[0x40] = { + 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, + 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, + 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, + 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, + 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, + 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, + 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, + 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, + 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, + 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, + 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, + 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, + 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, + 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, + 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, + 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2, + }; + + constexpr ALWAYS_INLINE u32 Choose(u32 x, u32 y, u32 z) { + return (x & y) ^ ((~x) & z); + } + + constexpr ALWAYS_INLINE u32 Majority(u32 x, u32 y, u32 z) { + return (x & y) ^ (x & z) ^ (y & z); + } + + constexpr ALWAYS_INLINE u32 LargeSigma0(u32 x) { + return util::RotateRight<u32>(x, 2) ^ util::RotateRight<u32>(x, 13) ^ util::RotateRight<u32>(x, 22); + } + + constexpr ALWAYS_INLINE u32 LargeSigma1(u32 x) { + return util::RotateRight<u32>(x, 6) ^ util::RotateRight<u32>(x, 11) ^ util::RotateRight<u32>(x, 25); + } + + constexpr ALWAYS_INLINE u32 SmallSigma0(u32 x) { + return util::RotateRight<u32>(x, 7) ^ util::RotateRight<u32>(x, 18) ^ (x >> 3); + } + + constexpr ALWAYS_INLINE u32 SmallSigma1(u32 x) { + return util::RotateRight<u32>(x, 17) ^ util::RotateRight<u32>(x, 19) ^ (x >> 10); + } + + } + + void Sha256Impl::Initialize() { + /* Reset buffered bytes/bits. */ + m_buffered_bytes = 0; + m_bits_consumed = 0; + + /* Set intermediate hash. */ + m_intermediate_hash[0] = 0x6A09E667; + m_intermediate_hash[1] = 0xBB67AE85; + m_intermediate_hash[2] = 0x3C6EF372; + m_intermediate_hash[3] = 0xA54FF53A; + m_intermediate_hash[4] = 0x510E527F; + m_intermediate_hash[5] = 0x9B05688C; + m_intermediate_hash[6] = 0x1F83D9AB; + m_intermediate_hash[7] = 0x5BE0CD19; + + /* Set state. */ + m_state = State_Initialized; + } + + void Sha256Impl::Update(const void *data, size_t size) { + /* Verify we're in a state to update. */ + AMS_ASSERT(m_state == State_Initialized); + + /* Advance our input bit count. */ + m_bits_consumed += BITSIZEOF(u8) * (((m_buffered_bytes + size) / BlockSize) * BlockSize); + + /* Process anything we have buffered. */ + const u8 *data8 = static_cast<const u8 *>(data); + size_t remaining = size; + + if (m_buffered_bytes > 0) { + const size_t copy_size = std::min(BlockSize - m_buffered_bytes, remaining); + std::memcpy(m_buffer + m_buffered_bytes, data8, copy_size); + + data8 += copy_size; + remaining -= copy_size; + m_buffered_bytes += copy_size; + + /* Process a block, if we filled one. */ + if (m_buffered_bytes == BlockSize) { + this->ProcessBlock(m_buffer); + m_buffered_bytes = 0; + } + } + + /* Process blocks, if we have any. */ + while (remaining >= BlockSize) { + this->ProcessBlock(data8); + data8 += BlockSize; + remaining -= BlockSize; + } + + /* Copy any leftover data to our buffer. */ + if (remaining > 0) { + m_buffered_bytes = remaining; + std::memcpy(m_buffer, data8, remaining); + } + } + + void Sha256Impl::GetHash(void *dst, size_t size) { + /* Verify we're in a state to get hash. */ + AMS_ASSERT(m_state == State_Initialized || m_state == State_Done); + AMS_ASSERT(size >= HashSize); + AMS_UNUSED(size); + + /* If we need to, process the last block. */ + if (m_state == State_Initialized) { + this->ProcessLastBlock(); + m_state = State_Done; + } + + /* Copy the output hash. */ + if constexpr (util::IsLittleEndian()) { + static_assert(HashSize % sizeof(u32) == 0); + + u32 *dst_32 = static_cast<u32 *>(dst); + for (size_t i = 0; i < HashSize / sizeof(u32); ++i) { + dst_32[i] = util::LoadBigEndian<u32>(m_intermediate_hash + i); + } + } else { + std::memcpy(dst, m_intermediate_hash, HashSize); + } + } + + void Sha256Impl::InitializeWithContext(const Sha256Context *context) { + /* Copy state in from the context. */ + std::memcpy(m_intermediate_hash, context->intermediate_hash, sizeof(m_intermediate_hash)); + m_bits_consumed = context->bits_consumed; + + /* Reset other fields. */ + m_buffered_bytes = 0; + m_state = State_Initialized; + } + + size_t Sha256Impl::GetContext(Sha256Context *context) const { + /* Check our state. */ + AMS_ASSERT(m_state == State_Initialized); + + /* Copy out the context. */ + std::memcpy(context->intermediate_hash, m_intermediate_hash, sizeof(context->intermediate_hash)); + context->bits_consumed = m_bits_consumed; + + return m_buffered_bytes; + } + + void Sha256Impl::ProcessBlock(const void *data) { + /* Load work variables. */ + u32 a = m_intermediate_hash[0]; + u32 b = m_intermediate_hash[1]; + u32 c = m_intermediate_hash[2]; + u32 d = m_intermediate_hash[3]; + u32 e = m_intermediate_hash[4]; + u32 f = m_intermediate_hash[5]; + u32 g = m_intermediate_hash[6]; + u32 h = m_intermediate_hash[7]; + u32 tmp[2]; + size_t i; + + /* Copy the input. */ + u32 w[64]; + if constexpr (util::IsLittleEndian()) { + static_assert(BlockSize % sizeof(u32) == 0); + + const u32 *src_32 = static_cast<const u32 *>(data); + for (size_t i = 0; i < BlockSize / sizeof(u32); ++i) { + w[i] = util::LoadBigEndian<u32>(src_32 + i); + } + } else { + std::memcpy(w, data, BlockSize); + } + + /* Initialize the rest of w. */ + for (i = BlockSize / sizeof(u32); i < util::size(w); ++i) { + const u32 *prev = w + (i - BlockSize / sizeof(u32)); + w[i] = prev[0] + SmallSigma0(prev[1]) + prev[9] + SmallSigma1(prev[14]); + } + + /* Perform rounds. */ + for (i = 0; i < 64; ++i) { + tmp[0] = h + LargeSigma1(e) + Choose(e, f, g) + RoundConstants[i] + w[i]; + tmp[1] = LargeSigma0(a) + Majority(a, b, c); + + h = g; + g = f; + f = e; + e = d + tmp[0]; + d = c; + c = b; + b = a; + a = tmp[0] + tmp[1]; + } + + /* Update intermediate hash. */ + m_intermediate_hash[0] += a; + m_intermediate_hash[1] += b; + m_intermediate_hash[2] += c; + m_intermediate_hash[3] += d; + m_intermediate_hash[4] += e; + m_intermediate_hash[5] += f; + m_intermediate_hash[6] += g; + m_intermediate_hash[7] += h; + } + + void Sha256Impl::ProcessLastBlock() { + /* Setup the final block. */ + constexpr const auto BlockSizeWithoutSizeField = BlockSize - sizeof(u64); + + /* Increment our bits consumed. */ + m_bits_consumed += BITSIZEOF(u8) * m_buffered_bytes; + + /* Add 0x80 terminator. */ + m_buffer[m_buffered_bytes++] = 0x80; + + /* If we can process the size field directly, do so, otherwise set up to process it. */ + if (m_buffered_bytes <= BlockSizeWithoutSizeField) { + /* Clear up to size field. */ + std::memset(m_buffer + m_buffered_bytes, 0, BlockSizeWithoutSizeField - m_buffered_bytes); + } else { + /* Consume full block */ + std::memset(m_buffer + m_buffered_bytes, 0, BlockSize - m_buffered_bytes); + this->ProcessBlock(m_buffer); + + /* Clear up to size field. */ + std::memset(m_buffer, 0, BlockSizeWithoutSizeField); + } + + /* Store the size field. */ + util::StoreBigEndian<u64>(reinterpret_cast<u64 *>(m_buffer + BlockSizeWithoutSizeField), m_bits_consumed); + + /* Process the final block. */ + this->ProcessBlock(m_buffer); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_sha3_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_sha3_impl.cpp new file mode 100644 index 00000000..5da8b159 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_sha3_impl.cpp @@ -0,0 +1,240 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::crypto::impl { + + namespace { + + constexpr auto NumRounds = 24; + + constexpr const u64 IotaRoundConstant[NumRounds] = { + UINT64_C(0x0000000000000001), UINT64_C(0x0000000000008082), + UINT64_C(0x800000000000808A), UINT64_C(0x8000000080008000), + UINT64_C(0x000000000000808B), UINT64_C(0x0000000080000001), + UINT64_C(0x8000000080008081), UINT64_C(0x8000000000008009), + UINT64_C(0x000000000000008A), UINT64_C(0x0000000000000088), + UINT64_C(0x0000000080008009), UINT64_C(0x000000008000000A), + UINT64_C(0x000000008000808B), UINT64_C(0x800000000000008B), + UINT64_C(0x8000000000008089), UINT64_C(0x8000000000008003), + UINT64_C(0x8000000000008002), UINT64_C(0x8000000000000080), + UINT64_C(0x000000000000800A), UINT64_C(0x800000008000000A), + UINT64_C(0x8000000080008081), UINT64_C(0x8000000000008080), + UINT64_C(0x0000000080000001), UINT64_C(0x8000000080008008) + }; + + constexpr const int RhoShiftBit[NumRounds] = { + 1, 3, 6, 10, 15, 21, 28, 36, + 45, 55, 2, 14, 27, 41, 56, 8, + 25, 43, 62, 18, 39, 61, 20, 44 + }; + + constexpr const int RhoNextIndex[NumRounds] = { + 10, 7, 11, 17, 18, 3, 5, 16, + 8, 21, 24, 4, 15, 23, 19, 13, + 12, 2, 20, 14, 22, 9, 6, 1 + }; + + } + + template<size_t HashSize> + void Sha3Impl<HashSize>::Initialize() { + /* Clear internal state. */ + std::memset(m_internal_state, 0, sizeof(m_internal_state)); + + /* Reset buffered bytes. */ + m_buffered_bytes = 0; + + /* Set state. */ + m_state = State_Initialized; + } + + template<size_t HashSize> + void Sha3Impl<HashSize>::Update(const void *data, size_t size) { + /* Verify we're in a state to update. */ + AMS_ASSERT(m_state == State_Initialized); + + /* Process we have anything buffered. */ + const u8 *data8 = static_cast<const u8 *>(data); + size_t remaining = size; + if (m_buffered_bytes > 0) { + /* Determine how much we can copy. */ + const size_t copy_size = std::min(BlockSize - m_buffered_bytes, remaining); + + /* Mix the bytes into our state. */ + u8 *dst8 = reinterpret_cast<u8 *>(m_internal_state) + m_buffered_bytes; + for (size_t i = 0; i < copy_size; ++i) { + dst8[i] ^= data8[i]; + } + + /* Advance. */ + data8 += copy_size; + remaining -= copy_size; + m_buffered_bytes += copy_size; + + /* Process a block, if we filled one. */ + if (m_buffered_bytes == BlockSize) { + this->ProcessBlock(); + m_buffered_bytes = 0; + } + } + + /* Process blocks, if we have any. */ + while (remaining >= BlockSize) { + /* Mix the bytes into our state. */ + u8 *dst8 = reinterpret_cast<u8 *>(m_internal_state); + for (size_t i = 0; i < BlockSize; ++i) { + dst8[i] ^= data8[i]; + } + + this->ProcessBlock(); + + data8 += BlockSize; + remaining -= BlockSize; + } + + /* Copy any leftover data to our buffer. */ + if (remaining > 0) { + u8 *dst8 = reinterpret_cast<u8 *>(m_internal_state); + for (size_t i = 0; i < remaining; ++i) { + dst8[i] ^= data8[i]; + } + + m_buffered_bytes = remaining; + } + } + + template<size_t HashSize> + void Sha3Impl<HashSize>::GetHash(void *dst, size_t size) { + /* Verify we're in a state to get hash. */ + AMS_ASSERT(m_state == State_Initialized || m_state == State_Done); + AMS_ASSERT(size >= HashSize); + AMS_UNUSED(size); + + /* If we need to, process the last block. */ + if (m_state == State_Initialized) { + this->ProcessLastBlock(); + m_state = State_Done; + } + + /* Copy the output hash. */ + std::memcpy(dst, m_internal_state, HashSize); + } + + template<size_t HashSize> + void Sha3Impl<HashSize>::InitializeWithContext(const Sha3Context *context) { + /* Check the context is for the right hash size. */ + AMS_ASSERT(context->hash_size == HashSize); + + /* Set buffered bytes. */ + m_buffered_bytes = context->buffered_bytes; + + /* Copy state in from the context. */ + std::memcpy(m_internal_state, context->internal_state, sizeof(m_internal_state)); + + /* Reset other fields. */ + m_state = State_Initialized; + } + + template<size_t HashSize> + void Sha3Impl<HashSize>::GetContext(Sha3Context *context) const { + /* Check our state. */ + AMS_ASSERT(m_state == State_Initialized); + + /* Set the output hash size. */ + context->hash_size = HashSize; + + /* Set buffered bytes. */ + context->buffered_bytes = m_buffered_bytes; + + /* Copy out the context. */ + std::memcpy(context->internal_state, m_internal_state, sizeof(context->internal_state)); + } + + template<size_t HashSize> + void Sha3Impl<HashSize>::ProcessBlock() { + /* Ensure correct endianness. */ + if constexpr (util::IsBigEndian()) { + for (size_t i = 0; i < util::size(m_internal_state); ++i) { + m_internal_state[i] = util::LoadLittleEndian<u64>(m_internal_state + i); + } + } + + /* Perform all rounds. */ + uint64_t tmp, C[5]; + for (auto round = 0; round < NumRounds; ++round) { + /* Handle theta. */ + for (size_t i = 0; i < 5; ++i) { + C[i] = m_internal_state[i] ^ m_internal_state[i + 5] ^ m_internal_state[i + 10] ^ m_internal_state[i + 15] ^ m_internal_state[i + 20]; + } + + for (size_t i = 0; i < 5; ++i) { + tmp = C[(i + 4) % 5] ^ util::RotateLeft<u64>(C[(i + 1) % 5], 1); + for (size_t j = 0; j < 5; ++j) { + m_internal_state[5 * j + i] ^= tmp; + } + } + + /* Handle rho/pi. */ + tmp = m_internal_state[1]; + for (size_t i = 0; i < NumRounds; ++i) { + const auto rho_next_idx = RhoNextIndex[i]; + C[0] = m_internal_state[rho_next_idx]; + m_internal_state[rho_next_idx] = util::RotateLeft<u64>(tmp, RhoShiftBit[i]); + tmp = C[0]; + } + + /* Handle chi. */ + for (size_t i = 0; i < 5; ++i) { + for (size_t j = 0; j < 5; ++j) { + C[j] = m_internal_state[5 * i + j]; + } + for (size_t j = 0; j < 5; ++j) { + m_internal_state[5 * i + j] ^= (~C[(j + 1) % 5]) & C[(j + 2) % 5]; + } + } + + /* Handle iota. */ + m_internal_state[0] ^= IotaRoundConstant[round]; + } + /* Ensure correct endianness. */ + if constexpr (util::IsBigEndian()) { + for (size_t i = 0; i < util::size(m_internal_state); ++i) { + util::StoreLittleEndian<u64>(m_internal_state + i, m_internal_state[i]); + } + } + } + + template<size_t HashSize> + void Sha3Impl<HashSize>::ProcessLastBlock() { + /* Mix final bits (011) into our state. */ + reinterpret_cast<u8 *>(m_internal_state)[m_buffered_bytes] ^= 0b110; + + /* Mix in the high bit of the last word in our block. */ + constexpr u64 FinalMask = UINT64_C(0x8000000000000000); + m_internal_state[(BlockSize / sizeof(u64)) - 1] ^= FinalMask; + + /* Process the last block. */ + this->ProcessBlock(); + } + + /* Explicitly instantiate the supported hash sizes. */ + template class Sha3Impl<224 / BITSIZEOF(u8)>; + template class Sha3Impl<256 / BITSIZEOF(u8)>; + template class Sha3Impl<384 / BITSIZEOF(u8)>; + template class Sha3Impl<512 / BITSIZEOF(u8)>; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_update_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_update_impl.hpp new file mode 100644 index 00000000..f6c7689e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_update_impl.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::crypto::impl { + + template<typename Cipher, typename Self> + void UpdateImpl(Self *self, const void *src, size_t src_size) { + const size_t BlockSize = self->GetBlockSize(); + + const u8 *src_u8 = static_cast<const u8 *>(src); + size_t remaining = src_size; + + if (const size_t buffered = self->GetBufferedDataSize(); buffered > 0) { + const size_t partial = std::min(BlockSize - buffered, remaining); + + self->ProcessPartialData(src_u8, partial); + src_u8 += partial; + remaining -= partial; + } + + if (remaining >= BlockSize) { + const size_t num_blocks = remaining / BlockSize; + + self->template ProcessBlocks<Cipher>(src_u8, num_blocks); + + const size_t processed = num_blocks * BlockSize; + src_u8 += processed; + remaining -= processed; + } + + if (remaining > 0) { + self->ProcessRemainingData(src_u8, remaining); + } + } + + template<typename Cipher, typename Self> + size_t UpdateImpl(Self *self, void *dst, size_t dst_size, const void *src, size_t src_size) { + AMS_UNUSED(dst_size); + + const size_t BlockSize = self->GetBlockSize(); + + const u8 *src_u8 = static_cast<const u8 *>(src); + u8 *dst_u8 = static_cast<u8 *>(dst); + size_t remaining = src_size; + size_t total_processed = 0; + + if (const size_t buffered = self->GetBufferedDataSize(); buffered > 0) { + const size_t partial = std::min(BlockSize - buffered, remaining); + + const size_t processed = self->ProcessPartialData(dst_u8, src_u8, partial); + + dst_u8 += processed; + total_processed += processed; + + src_u8 += partial; + remaining -= partial; + } + + if (remaining >= BlockSize) { + const size_t num_blocks = remaining / BlockSize; + const size_t input_size = num_blocks * BlockSize; + + const size_t processed = self->template ProcessBlocks<Cipher>(dst_u8, src_u8, num_blocks); + + dst_u8 += processed; + total_processed += processed; + + src_u8 += input_size; + remaining -= input_size; + } + + if (remaining > 0) { + const size_t processed = self->ProcessRemainingData(dst_u8, src_u8, remaining); + total_processed += processed; + } + + return total_processed; + } + + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_xts_mode_impl.arch.arm64.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_xts_mode_impl.arch.arm64.cpp new file mode 100644 index 00000000..165c522e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_xts_mode_impl.arch.arm64.cpp @@ -0,0 +1,1191 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> +#include "crypto_update_impl.hpp" + +#ifdef ATMOSPHERE_IS_STRATOSPHERE +#include <arm_neon.h> + +namespace ams::crypto::impl { + + /* Variable management macros. */ + #define DECLARE_ROUND_KEY_VAR(n) \ + const uint8x16_t round_key_##n = vld1q_u8(keys + (BlockSize * n)) + + #define AES_ENC_DEC_OUTPUT_THREE_BLOCKS() \ + [tmp0]"+w"(tmp0), [tmp1]"+w"(tmp1), [tmp2]"+w"(tmp2) + + #define AES_ENC_DEC_OUTPUT_THREE_TWEAKS() \ + [tweak0]"+w"(tweak0), [tweak1]"+w"(tweak1), [tweak2]"+w"(tweak2) + + #define AES_ENC_DEC_OUTPUT_ONE_BLOCK() \ + [tmp0]"+w"(tmp0) + + #define AES_ENC_DEC_OUTPUT_ONE_TWEAK() \ + [tweak0]"+w"(tweak0) + + #define XTS_INCREMENT_OUTPUT_HIGH_LOW_MASK() \ + [high]"=&r"(high), [low]"=&r"(low), [mask]"=&r"(mask) + + #define XTS_INCREMENT_INPUT_XOR() \ + [xorv]"r"(xorv) + + #define AES_ENC_DEC_INPUT_ROUND_KEY(n) \ + [round_key_##n]"w"(round_key_##n) + + /* AES Encryption macros. */ + #define AES_ENC_ROUND(n, i) \ + "aese %[tmp" #i "].16b, %[round_key_" #n "].16b\n" \ + "aesmc %[tmp" #i "].16b, %[tmp" #i "].16b\n" + + #define AES_ENC_SECOND_LAST_ROUND(n, i) \ + "aese %[tmp" #i "].16b, %[round_key_" #n "].16b\n" + + #define AES_ENC_LAST_ROUND(n, i) \ + "eor %[tmp" #i "].16b, %[tmp" #i "].16b, %[round_key_" #n "].16b\n" + + /* AES Decryption macros. */ + #define AES_DEC_ROUND(n, i) \ + "aesd %[tmp" #i "].16b, %[round_key_" #n "].16b\n" \ + "aesimc %[tmp" #i "].16b, %[tmp" #i "].16b\n" + + #define AES_DEC_SECOND_LAST_ROUND(n, i) \ + "aesd %[tmp" #i "].16b, %[round_key_" #n "].16b\n" + + #define AES_DEC_LAST_ROUND(n, i) \ + "eor %[tmp" #i "].16b, %[tmp" #i "].16b, %[round_key_" #n "].16b\n" + + namespace { + + /* TODO: Support non-Nintendo Endianness */ + + ALWAYS_INLINE uint8x16_t MultiplyTweak(const uint8x16_t tweak) { + /* TODO: Is the inline asm better than using intrinsics? */ + #if 1 + uint8x16_t mult; + uint64_t high, low, mask; + constexpr uint64_t xorv = 0x87ul; + /* Use ASM. TODO: Better than using intrinsics? */ + __asm__ __volatile__ ( + "mov %[high], %[tweak].d[1]\n" + "mov %[low], %[tweak].d[0]\n" + "and %[mask], %[xorv], %[high], asr 63\n" + "extr %[high], %[high], %[low], 63\n" + "eor %[low], %[mask], %[low], lsl 1\n" + "mov %[mult].d[1], %[high]\n" + "mov %[mult].d[0], %[low]\n" + : [mult]"=w"(mult), + XTS_INCREMENT_OUTPUT_HIGH_LOW_MASK() + : [tweak]"w"(tweak), + XTS_INCREMENT_INPUT_XOR() + : "cc" + ); + return mult; + #else + constexpr uint64_t XorMask = 0x87ul; + + const uint64x2_t tweak64 = vreinterpretq_u64_u8(tweak); + const uint64_t high = vgetq_lane_u64(tweak64, 1); + const uint64_t low = vgetq_lane_u64(tweak64, 0); + const uint64_t mask = static_cast<int64_t>(high) >> (BITSIZEOF(uint64_t) - 1); + + return vreinterpretq_u8_u64(vcombine_u64(vmov_n_u64((low << 1) ^ (mask & XorMask)), vmov_n_u64((high << 1) | (low >> (BITSIZEOF(uint64_t) - 1))))); + #endif + } + + } + + size_t XtsModeImpl::UpdateGeneric(void *dst, size_t dst_size, const void *src, size_t src_size) { + AMS_ASSERT(m_state == State_Initialized || m_state == State_Processing); + + return UpdateImpl<void>(this, dst, dst_size, src, src_size); + } + + size_t XtsModeImpl::ProcessBlocksGeneric(u8 *dst, const u8 *src, size_t num_blocks) { + size_t processed = BlockSize * (num_blocks - 1); + + if (m_state == State_Processing) { + this->ProcessBlock(dst, m_last_block); + dst += BlockSize; + processed += BlockSize; + } + + uint8x16_t tweak = vld1q_u8(m_tweak); + + while ((--num_blocks) > 0) { + /* Xor */ + uint8x16_t block = vld1q_u8(src); + src += BlockSize; + block = veorq_u8(block, tweak); + + /* Encrypt */ + vst1q_u8(dst, block); + m_cipher_func(dst, dst, m_cipher_ctx); + block = vld1q_u8(dst); + + /* Xor */ + veorq_u8(block, tweak); + vst1q_u8(dst, block); + dst += BlockSize; + + /* Increment tweak. */ + tweak = MultiplyTweak(tweak); + } + + vst1q_u8(m_tweak, tweak); + + std::memcpy(m_last_block, src, BlockSize); + + m_state = State_Processing; + + return processed; + } + + template<> size_t XtsModeImpl::Update<AesEncryptor128>(void *dst, size_t dst_size, const void *src, size_t src_size) { return UpdateImpl<AesEncryptor128>(this, dst, dst_size, src, src_size); } + template<> size_t XtsModeImpl::Update<AesEncryptor192>(void *dst, size_t dst_size, const void *src, size_t src_size) { return UpdateImpl<AesEncryptor192>(this, dst, dst_size, src, src_size); } + template<> size_t XtsModeImpl::Update<AesEncryptor256>(void *dst, size_t dst_size, const void *src, size_t src_size) { return UpdateImpl<AesEncryptor256>(this, dst, dst_size, src, src_size); } + + template<> size_t XtsModeImpl::Update<AesDecryptor128>(void *dst, size_t dst_size, const void *src, size_t src_size) { return UpdateImpl<AesDecryptor128>(this, dst, dst_size, src, src_size); } + template<> size_t XtsModeImpl::Update<AesDecryptor192>(void *dst, size_t dst_size, const void *src, size_t src_size) { return UpdateImpl<AesDecryptor192>(this, dst, dst_size, src, src_size); } + template<> size_t XtsModeImpl::Update<AesDecryptor256>(void *dst, size_t dst_size, const void *src, size_t src_size) { return UpdateImpl<AesDecryptor256>(this, dst, dst_size, src, src_size); } + + template<> + size_t XtsModeImpl::ProcessBlocks<AesEncryptor128>(u8 *dst, const u8 *src, size_t num_blocks) { + /* Handle last buffered block. */ + size_t processed = (num_blocks - 1) * BlockSize; + + if (m_state == State_Processing) { + this->ProcessBlock(dst, m_last_block); + dst += BlockSize; + processed += BlockSize; + } + + /* Preload all round keys + iv into neon registers. */ + const u8 *keys = static_cast<const AesEncryptor128 *>(m_cipher_ctx)->GetRoundKey(); + DECLARE_ROUND_KEY_VAR(0); + DECLARE_ROUND_KEY_VAR(1); + DECLARE_ROUND_KEY_VAR(2); + DECLARE_ROUND_KEY_VAR(3); + DECLARE_ROUND_KEY_VAR(4); + DECLARE_ROUND_KEY_VAR(5); + DECLARE_ROUND_KEY_VAR(6); + DECLARE_ROUND_KEY_VAR(7); + DECLARE_ROUND_KEY_VAR(8); + DECLARE_ROUND_KEY_VAR(9); + DECLARE_ROUND_KEY_VAR(10); + uint8x16_t tweak0 = vld1q_u8(m_tweak); + constexpr uint64_t xorv = 0x87ul; + uint64_t high, low, mask; + + /* Process three blocks at a time, when possible. */ + if (num_blocks > 3) { + /* Multiply tweak twice. */ + uint8x16_t tweak1 = MultiplyTweak(tweak0); + uint8x16_t tweak2 = MultiplyTweak(tweak1); + + do { + /* Save tweaks for xor usage. */ + const uint8x16_t mask0 = tweak0; + const uint8x16_t mask1 = tweak1; + const uint8x16_t mask2 = tweak2; + + /* Read blocks in, XOR with tweaks. */ + uint8x16_t tmp0 = veorq_u8(mask0, vld1q_u8(src)); src += BlockSize; + uint8x16_t tmp1 = veorq_u8(mask1, vld1q_u8(src)); src += BlockSize; + uint8x16_t tmp2 = veorq_u8(mask2, vld1q_u8(src)); src += BlockSize; + + /* Actually do encryption, use optimized asm. */ + /* Interleave GF mult calculations with AES ones, to mask latencies. */ + __asm__ __volatile__ ( + AES_ENC_ROUND(0, 0) "mov %[high], %[tweak2].d[1]\n" + AES_ENC_ROUND(0, 1) "mov %[low], %[tweak2].d[0]\n" + AES_ENC_ROUND(0, 2) "and %[mask], %[xorv], %[high], asr 63\n" + AES_ENC_ROUND(1, 0) "extr %[high], %[high], %[low], 63\n" + AES_ENC_ROUND(1, 1) "eor %[low], %[mask], %[low], lsl 1\n" + AES_ENC_ROUND(1, 2) "mov %[tweak0].d[1], %[high]\n" + AES_ENC_ROUND(2, 0) "mov %[tweak0].d[0], %[low]\n" + AES_ENC_ROUND(2, 1) "and %[mask], %[xorv], %[high], asr 63\n" + AES_ENC_ROUND(2, 2) "extr %[high], %[high], %[low], 63\n" + AES_ENC_ROUND(3, 0) "eor %[low], %[mask], %[low], lsl 1\n" + AES_ENC_ROUND(3, 1) "mov %[tweak1].d[1], %[high]\n" + AES_ENC_ROUND(3, 2) "mov %[tweak1].d[0], %[low]\n" + AES_ENC_ROUND(4, 0) "and %[mask], %[xorv], %[high], asr 63\n" + AES_ENC_ROUND(4, 1) "extr %[high], %[high], %[low], 63\n" + AES_ENC_ROUND(4, 2) "eor %[low], %[mask], %[low], lsl 1\n" + AES_ENC_ROUND(5, 0) "mov %[tweak2].d[1], %[high]\n" + AES_ENC_ROUND(5, 1) "mov %[tweak2].d[0], %[low]\n" + AES_ENC_ROUND(5, 2) + AES_ENC_ROUND(6, 0) AES_ENC_ROUND(6, 1) AES_ENC_ROUND(6, 2) + AES_ENC_ROUND(7, 0) AES_ENC_ROUND(7, 1) AES_ENC_ROUND(7, 2) + AES_ENC_ROUND(8, 0) AES_ENC_ROUND(8, 1) AES_ENC_ROUND(8, 2) + AES_ENC_SECOND_LAST_ROUND(9, 0) AES_ENC_SECOND_LAST_ROUND(9, 1) AES_ENC_SECOND_LAST_ROUND(9, 2) + AES_ENC_LAST_ROUND(10, 0) AES_ENC_LAST_ROUND(10, 1) AES_ENC_LAST_ROUND(10, 2) + : AES_ENC_DEC_OUTPUT_THREE_BLOCKS(), + AES_ENC_DEC_OUTPUT_THREE_TWEAKS(), + XTS_INCREMENT_OUTPUT_HIGH_LOW_MASK() + : XTS_INCREMENT_INPUT_XOR(), + AES_ENC_DEC_INPUT_ROUND_KEY(0), + AES_ENC_DEC_INPUT_ROUND_KEY(1), + AES_ENC_DEC_INPUT_ROUND_KEY(2), + AES_ENC_DEC_INPUT_ROUND_KEY(3), + AES_ENC_DEC_INPUT_ROUND_KEY(4), + AES_ENC_DEC_INPUT_ROUND_KEY(5), + AES_ENC_DEC_INPUT_ROUND_KEY(6), + AES_ENC_DEC_INPUT_ROUND_KEY(7), + AES_ENC_DEC_INPUT_ROUND_KEY(8), + AES_ENC_DEC_INPUT_ROUND_KEY(9), + AES_ENC_DEC_INPUT_ROUND_KEY(10) + : "cc" + ); + + /* XOR blocks. */ + tmp0 = veorq_u8(mask0, tmp0); + tmp1 = veorq_u8(mask1, tmp1); + tmp2 = veorq_u8(mask2, tmp2); + + /* Store to output. */ + vst1q_u8(dst, tmp0); dst += BlockSize; + vst1q_u8(dst, tmp1); dst += BlockSize; + vst1q_u8(dst, tmp2); dst += BlockSize; + + num_blocks -= 3; + } while (num_blocks > 3); + } + + while ((--num_blocks) > 0) { + /* Save tweak for xor usage. */ + const uint8x16_t mask0 = tweak0; + + /* Read block in, XOR with tweak. */ + uint8x16_t tmp0 = veorq_u8(mask0, vld1q_u8(src)); + src += BlockSize; + + /* Actually do encryption, use optimized asm. */ + /* Interleave CTR calculations with AES ones, to mask latencies. */ + __asm__ __volatile__ ( + AES_ENC_ROUND(0, 0) "mov %[high], %[tweak0].d[1]\n" + AES_ENC_ROUND(1, 0) "mov %[low], %[tweak0].d[0]\n" + AES_ENC_ROUND(2, 0) "and %[mask], %[xorv], %[high], asr 63\n" + AES_ENC_ROUND(3, 0) "extr %[high], %[high], %[low], 63\n" + AES_ENC_ROUND(4, 0) "eor %[low], %[mask], %[low], lsl 1\n" + AES_ENC_ROUND(5, 0) "mov %[tweak0].d[1], %[high]\n" + AES_ENC_ROUND(6, 0) "mov %[tweak0].d[0], %[low]\n" + AES_ENC_ROUND(7, 0) + AES_ENC_ROUND(8, 0) + AES_ENC_SECOND_LAST_ROUND(9, 0) + AES_ENC_LAST_ROUND(10, 0) + : AES_ENC_DEC_OUTPUT_ONE_BLOCK(), + AES_ENC_DEC_OUTPUT_ONE_TWEAK(), + XTS_INCREMENT_OUTPUT_HIGH_LOW_MASK() + : XTS_INCREMENT_INPUT_XOR(), + AES_ENC_DEC_INPUT_ROUND_KEY(0), + AES_ENC_DEC_INPUT_ROUND_KEY(1), + AES_ENC_DEC_INPUT_ROUND_KEY(2), + AES_ENC_DEC_INPUT_ROUND_KEY(3), + AES_ENC_DEC_INPUT_ROUND_KEY(4), + AES_ENC_DEC_INPUT_ROUND_KEY(5), + AES_ENC_DEC_INPUT_ROUND_KEY(6), + AES_ENC_DEC_INPUT_ROUND_KEY(7), + AES_ENC_DEC_INPUT_ROUND_KEY(8), + AES_ENC_DEC_INPUT_ROUND_KEY(9), + AES_ENC_DEC_INPUT_ROUND_KEY(10) + : "cc" + ); + + /* XOR blocks. */ + tmp0 = veorq_u8(mask0, tmp0); + + /* Store to output. */ + vst1q_u8(dst, tmp0); + dst += BlockSize; + } + + vst1q_u8(m_tweak, tweak0); + + std::memcpy(m_last_block, src, BlockSize); + m_state = State_Processing; + + return processed; + } + + template<> + size_t XtsModeImpl::ProcessBlocks<AesEncryptor192>(u8 *dst, const u8 *src, size_t num_blocks) { + /* Handle last buffered block. */ + size_t processed = (num_blocks - 1) * BlockSize; + + if (m_state == State_Processing) { + this->ProcessBlock(dst, m_last_block); + dst += BlockSize; + processed += BlockSize; + } + + /* Preload all round keys + iv into neon registers. */ + const u8 *keys = static_cast<const AesEncryptor192 *>(m_cipher_ctx)->GetRoundKey(); + DECLARE_ROUND_KEY_VAR(0); + DECLARE_ROUND_KEY_VAR(1); + DECLARE_ROUND_KEY_VAR(2); + DECLARE_ROUND_KEY_VAR(3); + DECLARE_ROUND_KEY_VAR(4); + DECLARE_ROUND_KEY_VAR(5); + DECLARE_ROUND_KEY_VAR(6); + DECLARE_ROUND_KEY_VAR(7); + DECLARE_ROUND_KEY_VAR(8); + DECLARE_ROUND_KEY_VAR(9); + DECLARE_ROUND_KEY_VAR(10); + DECLARE_ROUND_KEY_VAR(11); + DECLARE_ROUND_KEY_VAR(12); + uint8x16_t tweak0 = vld1q_u8(m_tweak); + constexpr uint64_t xorv = 0x87ul; + uint64_t high, low, mask; + + /* Process three blocks at a time, when possible. */ + if (num_blocks > 3) { + /* Multiply tweak twice. */ + uint8x16_t tweak1 = MultiplyTweak(tweak0); + uint8x16_t tweak2 = MultiplyTweak(tweak1); + + do { + /* Save tweaks for xor usage. */ + const uint8x16_t mask0 = tweak0; + const uint8x16_t mask1 = tweak1; + const uint8x16_t mask2 = tweak2; + + /* Read blocks in, XOR with tweaks. */ + uint8x16_t tmp0 = veorq_u8(mask0, vld1q_u8(src)); src += BlockSize; + uint8x16_t tmp1 = veorq_u8(mask1, vld1q_u8(src)); src += BlockSize; + uint8x16_t tmp2 = veorq_u8(mask2, vld1q_u8(src)); src += BlockSize; + + /* Actually do encryption, use optimized asm. */ + /* Interleave GF mult calculations with AES ones, to mask latencies. */ + __asm__ __volatile__ ( + AES_ENC_ROUND(0, 0) "mov %[high], %[tweak2].d[1]\n" + AES_ENC_ROUND(0, 1) "mov %[low], %[tweak2].d[0]\n" + AES_ENC_ROUND(0, 2) "and %[mask], %[xorv], %[high], asr 63\n" + AES_ENC_ROUND(1, 0) "extr %[high], %[high], %[low], 63\n" + AES_ENC_ROUND(1, 1) "eor %[low], %[mask], %[low], lsl 1\n" + AES_ENC_ROUND(1, 2) "mov %[tweak0].d[1], %[high]\n" + AES_ENC_ROUND(2, 0) "mov %[tweak0].d[0], %[low]\n" + AES_ENC_ROUND(2, 1) "and %[mask], %[xorv], %[high], asr 63\n" + AES_ENC_ROUND(2, 2) "extr %[high], %[high], %[low], 63\n" + AES_ENC_ROUND(3, 0) "eor %[low], %[mask], %[low], lsl 1\n" + AES_ENC_ROUND(3, 1) "mov %[tweak1].d[1], %[high]\n" + AES_ENC_ROUND(3, 2) "mov %[tweak1].d[0], %[low]\n" + AES_ENC_ROUND(4, 0) "and %[mask], %[xorv], %[high], asr 63\n" + AES_ENC_ROUND(4, 1) "extr %[high], %[high], %[low], 63\n" + AES_ENC_ROUND(4, 2) "eor %[low], %[mask], %[low], lsl 1\n" + AES_ENC_ROUND(5, 0) "mov %[tweak2].d[1], %[high]\n" + AES_ENC_ROUND(5, 1) "mov %[tweak2].d[0], %[low]\n" + AES_ENC_ROUND(5, 2) + AES_ENC_ROUND(6, 0) AES_ENC_ROUND(6, 1) AES_ENC_ROUND(6, 2) + AES_ENC_ROUND(7, 0) AES_ENC_ROUND(7, 1) AES_ENC_ROUND(7, 2) + AES_ENC_ROUND(8, 0) AES_ENC_ROUND(8, 1) AES_ENC_ROUND(8, 2) + AES_ENC_ROUND(9, 0) AES_ENC_ROUND(9, 1) AES_ENC_ROUND(9, 2) + AES_ENC_ROUND(10, 0) AES_ENC_ROUND(10, 1) AES_ENC_ROUND(10, 2) + AES_ENC_SECOND_LAST_ROUND(11, 0) AES_ENC_SECOND_LAST_ROUND(11, 1) AES_ENC_SECOND_LAST_ROUND(11, 2) + AES_ENC_LAST_ROUND(12, 0) AES_ENC_LAST_ROUND(12, 1) AES_ENC_LAST_ROUND(12, 2) + : AES_ENC_DEC_OUTPUT_THREE_BLOCKS(), + AES_ENC_DEC_OUTPUT_THREE_TWEAKS(), + XTS_INCREMENT_OUTPUT_HIGH_LOW_MASK() + : XTS_INCREMENT_INPUT_XOR(), + AES_ENC_DEC_INPUT_ROUND_KEY(0), + AES_ENC_DEC_INPUT_ROUND_KEY(1), + AES_ENC_DEC_INPUT_ROUND_KEY(2), + AES_ENC_DEC_INPUT_ROUND_KEY(3), + AES_ENC_DEC_INPUT_ROUND_KEY(4), + AES_ENC_DEC_INPUT_ROUND_KEY(5), + AES_ENC_DEC_INPUT_ROUND_KEY(6), + AES_ENC_DEC_INPUT_ROUND_KEY(7), + AES_ENC_DEC_INPUT_ROUND_KEY(8), + AES_ENC_DEC_INPUT_ROUND_KEY(9), + AES_ENC_DEC_INPUT_ROUND_KEY(10), + AES_ENC_DEC_INPUT_ROUND_KEY(11), + AES_ENC_DEC_INPUT_ROUND_KEY(12) + : "cc" + ); + + /* XOR blocks. */ + tmp0 = veorq_u8(mask0, tmp0); + tmp1 = veorq_u8(mask1, tmp1); + tmp2 = veorq_u8(mask2, tmp2); + + /* Store to output. */ + vst1q_u8(dst, tmp0); dst += BlockSize; + vst1q_u8(dst, tmp1); dst += BlockSize; + vst1q_u8(dst, tmp2); dst += BlockSize; + + num_blocks -= 3; + } while (num_blocks > 3); + } + + while ((--num_blocks) > 0) { + /* Save tweak for xor usage. */ + const uint8x16_t mask0 = tweak0; + + /* Read block in, XOR with tweak. */ + uint8x16_t tmp0 = veorq_u8(mask0, vld1q_u8(src)); + src += BlockSize; + + /* Actually do encryption, use optimized asm. */ + /* Interleave CTR calculations with AES ones, to mask latencies. */ + __asm__ __volatile__ ( + AES_ENC_ROUND(0, 0) "mov %[high], %[tweak0].d[1]\n" + AES_ENC_ROUND(1, 0) "mov %[low], %[tweak0].d[0]\n" + AES_ENC_ROUND(2, 0) "and %[mask], %[xorv], %[high], asr 63\n" + AES_ENC_ROUND(3, 0) "extr %[high], %[high], %[low], 63\n" + AES_ENC_ROUND(4, 0) "eor %[low], %[mask], %[low], lsl 1\n" + AES_ENC_ROUND(5, 0) "mov %[tweak0].d[1], %[high]\n" + AES_ENC_ROUND(6, 0) "mov %[tweak0].d[0], %[low]\n" + AES_ENC_ROUND(7, 0) + AES_ENC_ROUND(8, 0) + AES_ENC_ROUND(9, 0) + AES_ENC_ROUND(10, 0) + AES_ENC_SECOND_LAST_ROUND(11, 0) + AES_ENC_LAST_ROUND(12, 0) + : AES_ENC_DEC_OUTPUT_ONE_BLOCK(), + AES_ENC_DEC_OUTPUT_ONE_TWEAK(), + XTS_INCREMENT_OUTPUT_HIGH_LOW_MASK() + : XTS_INCREMENT_INPUT_XOR(), + AES_ENC_DEC_INPUT_ROUND_KEY(0), + AES_ENC_DEC_INPUT_ROUND_KEY(1), + AES_ENC_DEC_INPUT_ROUND_KEY(2), + AES_ENC_DEC_INPUT_ROUND_KEY(3), + AES_ENC_DEC_INPUT_ROUND_KEY(4), + AES_ENC_DEC_INPUT_ROUND_KEY(5), + AES_ENC_DEC_INPUT_ROUND_KEY(6), + AES_ENC_DEC_INPUT_ROUND_KEY(7), + AES_ENC_DEC_INPUT_ROUND_KEY(8), + AES_ENC_DEC_INPUT_ROUND_KEY(9), + AES_ENC_DEC_INPUT_ROUND_KEY(10), + AES_ENC_DEC_INPUT_ROUND_KEY(11), + AES_ENC_DEC_INPUT_ROUND_KEY(12) + : "cc" + ); + + /* XOR blocks. */ + tmp0 = veorq_u8(mask0, tmp0); + + /* Store to output. */ + vst1q_u8(dst, tmp0); + dst += BlockSize; + } + + vst1q_u8(m_tweak, tweak0); + + std::memcpy(m_last_block, src, BlockSize); + m_state = State_Processing; + + return processed; + } + + template<> + size_t XtsModeImpl::ProcessBlocks<AesEncryptor256>(u8 *dst, const u8 *src, size_t num_blocks) { + /* Handle last buffered block. */ + size_t processed = (num_blocks - 1) * BlockSize; + + if (m_state == State_Processing) { + this->ProcessBlock(dst, m_last_block); + dst += BlockSize; + processed += BlockSize; + } + + /* Preload all round keys + iv into neon registers. */ + const u8 *keys = static_cast<const AesEncryptor256 *>(m_cipher_ctx)->GetRoundKey(); + DECLARE_ROUND_KEY_VAR(0); + DECLARE_ROUND_KEY_VAR(1); + DECLARE_ROUND_KEY_VAR(2); + DECLARE_ROUND_KEY_VAR(3); + DECLARE_ROUND_KEY_VAR(4); + DECLARE_ROUND_KEY_VAR(5); + DECLARE_ROUND_KEY_VAR(6); + DECLARE_ROUND_KEY_VAR(7); + DECLARE_ROUND_KEY_VAR(8); + DECLARE_ROUND_KEY_VAR(9); + DECLARE_ROUND_KEY_VAR(10); + DECLARE_ROUND_KEY_VAR(11); + DECLARE_ROUND_KEY_VAR(12); + DECLARE_ROUND_KEY_VAR(13); + DECLARE_ROUND_KEY_VAR(14); + uint8x16_t tweak0 = vld1q_u8(m_tweak); + constexpr uint64_t xorv = 0x87ul; + uint64_t high, low, mask; + + /* Process three blocks at a time, when possible. */ + if (num_blocks > 3) { + /* Multiply tweak twice. */ + uint8x16_t tweak1 = MultiplyTweak(tweak0); + uint8x16_t tweak2 = MultiplyTweak(tweak1); + + do { + /* Save tweaks for xor usage. */ + const uint8x16_t mask0 = tweak0; + const uint8x16_t mask1 = tweak1; + const uint8x16_t mask2 = tweak2; + + /* Read blocks in, XOR with tweaks. */ + uint8x16_t tmp0 = veorq_u8(mask0, vld1q_u8(src)); src += BlockSize; + uint8x16_t tmp1 = veorq_u8(mask1, vld1q_u8(src)); src += BlockSize; + uint8x16_t tmp2 = veorq_u8(mask2, vld1q_u8(src)); src += BlockSize; + + /* Actually do encryption, use optimized asm. */ + /* Interleave GF mult calculations with AES ones, to mask latencies. */ + __asm__ __volatile__ ( + AES_ENC_ROUND(0, 0) "mov %[high], %[tweak2].d[1]\n" + AES_ENC_ROUND(0, 1) "mov %[low], %[tweak2].d[0]\n" + AES_ENC_ROUND(0, 2) "mov %[mask], #0x87\n" + AES_ENC_ROUND(1, 0) "and %[mask], %[mask], %[high], asr 63\n" + AES_ENC_ROUND(1, 1) "extr %[high], %[high], %[low], 63\n" + AES_ENC_ROUND(1, 2) "eor %[low], %[mask], %[low], lsl 1\n" + AES_ENC_ROUND(2, 0) "mov %[tweak0].d[1], %[high]\n" + AES_ENC_ROUND(2, 1) "mov %[tweak0].d[0], %[low]\n" + AES_ENC_ROUND(2, 2) "mov %[mask], #0x87\n" + AES_ENC_ROUND(3, 0) "and %[mask], %[mask], %[high], asr 63\n" + AES_ENC_ROUND(3, 1) "extr %[high], %[high], %[low], 63\n" + AES_ENC_ROUND(3, 2) "eor %[low], %[mask], %[low], lsl 1\n" + AES_ENC_ROUND(4, 0) "mov %[tweak1].d[1], %[high]\n" + AES_ENC_ROUND(4, 1) "mov %[tweak1].d[0], %[low]\n" + AES_ENC_ROUND(4, 2) "mov %[mask], #0x87\n" + AES_ENC_ROUND(5, 0) "and %[mask], %[mask], %[high], asr 63\n" + AES_ENC_ROUND(5, 1) "extr %[high], %[high], %[low], 63\n" + AES_ENC_ROUND(5, 2) "eor %[low], %[mask], %[low], lsl 1\n" + AES_ENC_ROUND(6, 0) "mov %[tweak2].d[1], %[high]\n" + AES_ENC_ROUND(6, 1) "mov %[tweak2].d[0], %[low]\n" + AES_ENC_ROUND(6, 2) + AES_ENC_ROUND(7, 0) AES_ENC_ROUND(7, 1) AES_ENC_ROUND(7, 2) + AES_ENC_ROUND(8, 0) AES_ENC_ROUND(8, 1) AES_ENC_ROUND(8, 2) + AES_ENC_ROUND(9, 0) AES_ENC_ROUND(9, 1) AES_ENC_ROUND(9, 2) + AES_ENC_ROUND(10, 0) AES_ENC_ROUND(10, 1) AES_ENC_ROUND(10, 2) + AES_ENC_ROUND(11, 0) AES_ENC_ROUND(11, 1) AES_ENC_ROUND(11, 2) + AES_ENC_ROUND(12, 0) AES_ENC_ROUND(12, 1) AES_ENC_ROUND(12, 2) + AES_ENC_SECOND_LAST_ROUND(13, 0) AES_ENC_SECOND_LAST_ROUND(13, 1) AES_ENC_SECOND_LAST_ROUND(13, 2) + AES_ENC_LAST_ROUND(14, 0) AES_ENC_LAST_ROUND(14, 1) AES_ENC_LAST_ROUND(14, 2) + : AES_ENC_DEC_OUTPUT_THREE_BLOCKS(), + AES_ENC_DEC_OUTPUT_THREE_TWEAKS(), + XTS_INCREMENT_OUTPUT_HIGH_LOW_MASK() + : AES_ENC_DEC_INPUT_ROUND_KEY(0), + AES_ENC_DEC_INPUT_ROUND_KEY(1), + AES_ENC_DEC_INPUT_ROUND_KEY(2), + AES_ENC_DEC_INPUT_ROUND_KEY(3), + AES_ENC_DEC_INPUT_ROUND_KEY(4), + AES_ENC_DEC_INPUT_ROUND_KEY(5), + AES_ENC_DEC_INPUT_ROUND_KEY(6), + AES_ENC_DEC_INPUT_ROUND_KEY(7), + AES_ENC_DEC_INPUT_ROUND_KEY(8), + AES_ENC_DEC_INPUT_ROUND_KEY(9), + AES_ENC_DEC_INPUT_ROUND_KEY(10), + AES_ENC_DEC_INPUT_ROUND_KEY(11), + AES_ENC_DEC_INPUT_ROUND_KEY(12), + AES_ENC_DEC_INPUT_ROUND_KEY(13), + AES_ENC_DEC_INPUT_ROUND_KEY(14) + : "cc" + ); + + /* XOR blocks. */ + tmp0 = veorq_u8(mask0, tmp0); + tmp1 = veorq_u8(mask1, tmp1); + tmp2 = veorq_u8(mask2, tmp2); + + /* Store to output. */ + vst1q_u8(dst, tmp0); dst += BlockSize; + vst1q_u8(dst, tmp1); dst += BlockSize; + vst1q_u8(dst, tmp2); dst += BlockSize; + + num_blocks -= 3; + } while (num_blocks > 3); + } + + while ((--num_blocks) > 0) { + /* Save tweak for xor usage. */ + const uint8x16_t mask0 = tweak0; + + /* Read block in, XOR with tweak. */ + uint8x16_t tmp0 = veorq_u8(mask0, vld1q_u8(src)); + src += BlockSize; + + /* Actually do encryption, use optimized asm. */ + /* Interleave CTR calculations with AES ones, to mask latencies. */ + __asm__ __volatile__ ( + AES_ENC_ROUND(0, 0) "mov %[high], %[tweak0].d[1]\n" + AES_ENC_ROUND(1, 0) "mov %[low], %[tweak0].d[0]\n" + AES_ENC_ROUND(2, 0) "and %[mask], %[xorv], %[high], asr 63\n" + AES_ENC_ROUND(3, 0) "extr %[high], %[high], %[low], 63\n" + AES_ENC_ROUND(4, 0) "eor %[low], %[mask], %[low], lsl 1\n" + AES_ENC_ROUND(5, 0) "mov %[tweak0].d[1], %[high]\n" + AES_ENC_ROUND(6, 0) "mov %[tweak0].d[0], %[low]\n" + AES_ENC_ROUND(7, 0) + AES_ENC_ROUND(8, 0) + AES_ENC_ROUND(9, 0) + AES_ENC_ROUND(10, 0) + AES_ENC_ROUND(11, 0) + AES_ENC_ROUND(12, 0) + AES_ENC_SECOND_LAST_ROUND(13, 0) + AES_ENC_LAST_ROUND(14, 0) + : AES_ENC_DEC_OUTPUT_ONE_BLOCK(), + AES_ENC_DEC_OUTPUT_ONE_TWEAK(), + XTS_INCREMENT_OUTPUT_HIGH_LOW_MASK() + : XTS_INCREMENT_INPUT_XOR(), + AES_ENC_DEC_INPUT_ROUND_KEY(0), + AES_ENC_DEC_INPUT_ROUND_KEY(1), + AES_ENC_DEC_INPUT_ROUND_KEY(2), + AES_ENC_DEC_INPUT_ROUND_KEY(3), + AES_ENC_DEC_INPUT_ROUND_KEY(4), + AES_ENC_DEC_INPUT_ROUND_KEY(5), + AES_ENC_DEC_INPUT_ROUND_KEY(6), + AES_ENC_DEC_INPUT_ROUND_KEY(7), + AES_ENC_DEC_INPUT_ROUND_KEY(8), + AES_ENC_DEC_INPUT_ROUND_KEY(9), + AES_ENC_DEC_INPUT_ROUND_KEY(10), + AES_ENC_DEC_INPUT_ROUND_KEY(11), + AES_ENC_DEC_INPUT_ROUND_KEY(12), + AES_ENC_DEC_INPUT_ROUND_KEY(13), + AES_ENC_DEC_INPUT_ROUND_KEY(14) + : "cc" + ); + + /* XOR blocks. */ + tmp0 = veorq_u8(mask0, tmp0); + + /* Store to output. */ + vst1q_u8(dst, tmp0); + dst += BlockSize; + } + + vst1q_u8(m_tweak, tweak0); + + std::memcpy(m_last_block, src, BlockSize); + m_state = State_Processing; + + return processed; + } + + template<> + size_t XtsModeImpl::ProcessBlocks<AesDecryptor128>(u8 *dst, const u8 *src, size_t num_blocks) { + /* Handle last buffered block. */ + size_t processed = (num_blocks - 1) * BlockSize; + + if (m_state == State_Processing) { + this->ProcessBlock(dst, m_last_block); + dst += BlockSize; + processed += BlockSize; + } + + /* Preload all round keys + iv into neon registers. */ + const u8 *keys = static_cast<const AesDecryptor128 *>(m_cipher_ctx)->GetRoundKey(); + DECLARE_ROUND_KEY_VAR(0); + DECLARE_ROUND_KEY_VAR(1); + DECLARE_ROUND_KEY_VAR(2); + DECLARE_ROUND_KEY_VAR(3); + DECLARE_ROUND_KEY_VAR(4); + DECLARE_ROUND_KEY_VAR(5); + DECLARE_ROUND_KEY_VAR(6); + DECLARE_ROUND_KEY_VAR(7); + DECLARE_ROUND_KEY_VAR(8); + DECLARE_ROUND_KEY_VAR(9); + DECLARE_ROUND_KEY_VAR(10); + uint8x16_t tweak0 = vld1q_u8(m_tweak); + constexpr uint64_t xorv = 0x87ul; + uint64_t high, low, mask; + + /* Process three blocks at a time, when possible. */ + if (num_blocks > 3) { + /* Multiply tweak twice. */ + uint8x16_t tweak1 = MultiplyTweak(tweak0); + uint8x16_t tweak2 = MultiplyTweak(tweak1); + + do { + /* Save tweaks for xor usage. */ + const uint8x16_t mask0 = tweak0; + const uint8x16_t mask1 = tweak1; + const uint8x16_t mask2 = tweak2; + + /* Read blocks in, XOR with tweaks. */ + uint8x16_t tmp0 = veorq_u8(mask0, vld1q_u8(src)); src += BlockSize; + uint8x16_t tmp1 = veorq_u8(mask1, vld1q_u8(src)); src += BlockSize; + uint8x16_t tmp2 = veorq_u8(mask2, vld1q_u8(src)); src += BlockSize; + + /* Actually do encryption, use optimized asm. */ + /* Interleave GF mult calculations with AES ones, to mask latencies. */ + __asm__ __volatile__ ( + AES_DEC_ROUND(10, 0) "mov %[high], %[tweak2].d[1]\n" + AES_DEC_ROUND(10, 1) "mov %[low], %[tweak2].d[0]\n" + AES_DEC_ROUND(10, 2) "and %[mask], %[xorv], %[high], asr 63\n" + AES_DEC_ROUND(9, 0) "extr %[high], %[high], %[low], 63\n" + AES_DEC_ROUND(9, 1) "eor %[low], %[mask], %[low], lsl 1\n" + AES_DEC_ROUND(9, 2) "mov %[tweak0].d[1], %[high]\n" + AES_DEC_ROUND(8, 0) "mov %[tweak0].d[0], %[low]\n" + AES_DEC_ROUND(8, 1) "and %[mask], %[xorv], %[high], asr 63\n" + AES_DEC_ROUND(8, 2) "extr %[high], %[high], %[low], 63\n" + AES_DEC_ROUND(7, 0) "eor %[low], %[mask], %[low], lsl 1\n" + AES_DEC_ROUND(7, 1) "mov %[tweak1].d[1], %[high]\n" + AES_DEC_ROUND(7, 2) "mov %[tweak1].d[0], %[low]\n" + AES_DEC_ROUND(6, 0) "and %[mask], %[xorv], %[high], asr 63\n" + AES_DEC_ROUND(6, 1) "extr %[high], %[high], %[low], 63\n" + AES_DEC_ROUND(6, 2) "eor %[low], %[mask], %[low], lsl 1\n" + AES_DEC_ROUND(5, 0) "mov %[tweak2].d[1], %[high]\n" + AES_DEC_ROUND(5, 1) "mov %[tweak2].d[0], %[low]\n" + AES_DEC_ROUND(5, 2) + AES_DEC_ROUND(4, 0) AES_DEC_ROUND(4, 1) AES_DEC_ROUND(4, 2) + AES_DEC_ROUND(3, 0) AES_DEC_ROUND(3, 1) AES_DEC_ROUND(3, 2) + AES_DEC_ROUND(2, 0) AES_DEC_ROUND(2, 1) AES_DEC_ROUND(2, 2) + AES_DEC_SECOND_LAST_ROUND(1, 0) AES_DEC_SECOND_LAST_ROUND(1, 1) AES_DEC_SECOND_LAST_ROUND(1, 2) + AES_DEC_LAST_ROUND(0, 0) AES_DEC_LAST_ROUND(0, 1) AES_DEC_LAST_ROUND(0, 2) + : AES_ENC_DEC_OUTPUT_THREE_BLOCKS(), + AES_ENC_DEC_OUTPUT_THREE_TWEAKS(), + XTS_INCREMENT_OUTPUT_HIGH_LOW_MASK() + : XTS_INCREMENT_INPUT_XOR(), + AES_ENC_DEC_INPUT_ROUND_KEY(0), + AES_ENC_DEC_INPUT_ROUND_KEY(1), + AES_ENC_DEC_INPUT_ROUND_KEY(2), + AES_ENC_DEC_INPUT_ROUND_KEY(3), + AES_ENC_DEC_INPUT_ROUND_KEY(4), + AES_ENC_DEC_INPUT_ROUND_KEY(5), + AES_ENC_DEC_INPUT_ROUND_KEY(6), + AES_ENC_DEC_INPUT_ROUND_KEY(7), + AES_ENC_DEC_INPUT_ROUND_KEY(8), + AES_ENC_DEC_INPUT_ROUND_KEY(9), + AES_ENC_DEC_INPUT_ROUND_KEY(10) + : "cc" + ); + + /* XOR blocks. */ + tmp0 = veorq_u8(mask0, tmp0); + tmp1 = veorq_u8(mask1, tmp1); + tmp2 = veorq_u8(mask2, tmp2); + + /* Store to output. */ + vst1q_u8(dst, tmp0); dst += BlockSize; + vst1q_u8(dst, tmp1); dst += BlockSize; + vst1q_u8(dst, tmp2); dst += BlockSize; + + num_blocks -= 3; + } while (num_blocks > 3); + } + + while ((--num_blocks) > 0) { + /* Save tweak for xor usage. */ + const uint8x16_t mask0 = tweak0; + + /* Read block in, XOR with tweak. */ + uint8x16_t tmp0 = veorq_u8(mask0, vld1q_u8(src)); + src += BlockSize; + + /* Actually do encryption, use optimized asm. */ + /* Interleave CTR calculations with AES ones, to mask latencies. */ + __asm__ __volatile__ ( + AES_DEC_ROUND(10, 0) "mov %[high], %[tweak0].d[1]\n" + AES_DEC_ROUND(9, 0) "mov %[low], %[tweak0].d[0]\n" + AES_DEC_ROUND(8, 0) "and %[mask], %[xorv], %[high], asr 63\n" + AES_DEC_ROUND(7, 0) "extr %[high], %[high], %[low], 63\n" + AES_DEC_ROUND(6, 0) "eor %[low], %[mask], %[low], lsl 1\n" + AES_DEC_ROUND(5, 0) "mov %[tweak0].d[1], %[high]\n" + AES_DEC_ROUND(4, 0) "mov %[tweak0].d[0], %[low]\n" + AES_DEC_ROUND(3, 0) + AES_DEC_ROUND(2, 0) + AES_DEC_SECOND_LAST_ROUND(1, 0) + AES_DEC_LAST_ROUND(0, 0) + : AES_ENC_DEC_OUTPUT_ONE_BLOCK(), + AES_ENC_DEC_OUTPUT_ONE_TWEAK(), + XTS_INCREMENT_OUTPUT_HIGH_LOW_MASK() + : XTS_INCREMENT_INPUT_XOR(), + AES_ENC_DEC_INPUT_ROUND_KEY(0), + AES_ENC_DEC_INPUT_ROUND_KEY(1), + AES_ENC_DEC_INPUT_ROUND_KEY(2), + AES_ENC_DEC_INPUT_ROUND_KEY(3), + AES_ENC_DEC_INPUT_ROUND_KEY(4), + AES_ENC_DEC_INPUT_ROUND_KEY(5), + AES_ENC_DEC_INPUT_ROUND_KEY(6), + AES_ENC_DEC_INPUT_ROUND_KEY(7), + AES_ENC_DEC_INPUT_ROUND_KEY(8), + AES_ENC_DEC_INPUT_ROUND_KEY(9), + AES_ENC_DEC_INPUT_ROUND_KEY(10) + : "cc" + ); + + /* XOR blocks. */ + tmp0 = veorq_u8(mask0, tmp0); + + /* Store to output. */ + vst1q_u8(dst, tmp0); + dst += BlockSize; + } + + vst1q_u8(m_tweak, tweak0); + + std::memcpy(m_last_block, src, BlockSize); + m_state = State_Processing; + + return processed; + } + + template<> + size_t XtsModeImpl::ProcessBlocks<AesDecryptor192>(u8 *dst, const u8 *src, size_t num_blocks) { + /* Handle last buffered block. */ + size_t processed = (num_blocks - 1) * BlockSize; + + if (m_state == State_Processing) { + this->ProcessBlock(dst, m_last_block); + dst += BlockSize; + processed += BlockSize; + } + + /* Preload all round keys + iv into neon registers. */ + const u8 *keys = static_cast<const AesDecryptor192 *>(m_cipher_ctx)->GetRoundKey(); + DECLARE_ROUND_KEY_VAR(0); + DECLARE_ROUND_KEY_VAR(1); + DECLARE_ROUND_KEY_VAR(2); + DECLARE_ROUND_KEY_VAR(3); + DECLARE_ROUND_KEY_VAR(4); + DECLARE_ROUND_KEY_VAR(5); + DECLARE_ROUND_KEY_VAR(6); + DECLARE_ROUND_KEY_VAR(7); + DECLARE_ROUND_KEY_VAR(8); + DECLARE_ROUND_KEY_VAR(9); + DECLARE_ROUND_KEY_VAR(10); + DECLARE_ROUND_KEY_VAR(11); + DECLARE_ROUND_KEY_VAR(12); + uint8x16_t tweak0 = vld1q_u8(m_tweak); + constexpr uint64_t xorv = 0x87ul; + uint64_t high, low, mask; + + /* Process three blocks at a time, when possible. */ + if (num_blocks > 3) { + /* Multiply tweak twice. */ + uint8x16_t tweak1 = MultiplyTweak(tweak0); + uint8x16_t tweak2 = MultiplyTweak(tweak1); + + do { + /* Save tweaks for xor usage. */ + const uint8x16_t mask0 = tweak0; + const uint8x16_t mask1 = tweak1; + const uint8x16_t mask2 = tweak2; + + /* Read blocks in, XOR with tweaks. */ + uint8x16_t tmp0 = veorq_u8(mask0, vld1q_u8(src)); src += BlockSize; + uint8x16_t tmp1 = veorq_u8(mask1, vld1q_u8(src)); src += BlockSize; + uint8x16_t tmp2 = veorq_u8(mask2, vld1q_u8(src)); src += BlockSize; + + /* Actually do encryption, use optimized asm. */ + /* Interleave GF mult calculations with AES ones, to mask latencies. */ + __asm__ __volatile__ ( + AES_DEC_ROUND(12, 0) "mov %[high], %[tweak2].d[1]\n" + AES_DEC_ROUND(12, 1) "mov %[low], %[tweak2].d[0]\n" + AES_DEC_ROUND(12, 2) "and %[mask], %[xorv], %[high], asr 63\n" + AES_DEC_ROUND(11, 0) "extr %[high], %[high], %[low], 63\n" + AES_DEC_ROUND(11, 1) "eor %[low], %[mask], %[low], lsl 1\n" + AES_DEC_ROUND(11, 2) "mov %[tweak0].d[1], %[high]\n" + AES_DEC_ROUND(10, 0) "mov %[tweak0].d[0], %[low]\n" + AES_DEC_ROUND(10, 1) "and %[mask], %[xorv], %[high], asr 63\n" + AES_DEC_ROUND(10, 2) "extr %[high], %[high], %[low], 63\n" + AES_DEC_ROUND(9, 0) "eor %[low], %[mask], %[low], lsl 1\n" + AES_DEC_ROUND(9, 1) "mov %[tweak1].d[1], %[high]\n" + AES_DEC_ROUND(9, 2) "mov %[tweak1].d[0], %[low]\n" + AES_DEC_ROUND(8, 0) "and %[mask], %[xorv], %[high], asr 63\n" + AES_DEC_ROUND(8, 1) "extr %[high], %[high], %[low], 63\n" + AES_DEC_ROUND(8, 2) "eor %[low], %[mask], %[low], lsl 1\n" + AES_DEC_ROUND(7, 0) "mov %[tweak2].d[1], %[high]\n" + AES_DEC_ROUND(7, 1) "mov %[tweak2].d[0], %[low]\n" + AES_DEC_ROUND(7, 2) + AES_DEC_ROUND(6, 0) AES_DEC_ROUND(6, 1) AES_DEC_ROUND(6, 2) + AES_DEC_ROUND(5, 0) AES_DEC_ROUND(5, 1) AES_DEC_ROUND(5, 2) + AES_DEC_ROUND(4, 0) AES_DEC_ROUND(4, 1) AES_DEC_ROUND(4, 2) + AES_DEC_ROUND(3, 0) AES_DEC_ROUND(3, 1) AES_DEC_ROUND(3, 2) + AES_DEC_ROUND(2, 0) AES_DEC_ROUND(2, 1) AES_DEC_ROUND(2, 2) + AES_DEC_SECOND_LAST_ROUND(1, 0) AES_DEC_SECOND_LAST_ROUND(1, 1) AES_DEC_SECOND_LAST_ROUND(1, 2) + AES_DEC_LAST_ROUND(0, 0) AES_DEC_LAST_ROUND(0, 1) AES_DEC_LAST_ROUND(0, 2) + : AES_ENC_DEC_OUTPUT_THREE_BLOCKS(), + AES_ENC_DEC_OUTPUT_THREE_TWEAKS(), + XTS_INCREMENT_OUTPUT_HIGH_LOW_MASK() + : XTS_INCREMENT_INPUT_XOR(), + AES_ENC_DEC_INPUT_ROUND_KEY(0), + AES_ENC_DEC_INPUT_ROUND_KEY(1), + AES_ENC_DEC_INPUT_ROUND_KEY(2), + AES_ENC_DEC_INPUT_ROUND_KEY(3), + AES_ENC_DEC_INPUT_ROUND_KEY(4), + AES_ENC_DEC_INPUT_ROUND_KEY(5), + AES_ENC_DEC_INPUT_ROUND_KEY(6), + AES_ENC_DEC_INPUT_ROUND_KEY(7), + AES_ENC_DEC_INPUT_ROUND_KEY(8), + AES_ENC_DEC_INPUT_ROUND_KEY(9), + AES_ENC_DEC_INPUT_ROUND_KEY(10), + AES_ENC_DEC_INPUT_ROUND_KEY(11), + AES_ENC_DEC_INPUT_ROUND_KEY(12) + : "cc" + ); + + /* XOR blocks. */ + tmp0 = veorq_u8(mask0, tmp0); + tmp1 = veorq_u8(mask1, tmp1); + tmp2 = veorq_u8(mask2, tmp2); + + /* Store to output. */ + vst1q_u8(dst, tmp0); dst += BlockSize; + vst1q_u8(dst, tmp1); dst += BlockSize; + vst1q_u8(dst, tmp2); dst += BlockSize; + + num_blocks -= 3; + } while (num_blocks > 3); + } + + while ((--num_blocks) > 0) { + /* Save tweak for xor usage. */ + const uint8x16_t mask0 = tweak0; + + /* Read block in, XOR with tweak. */ + uint8x16_t tmp0 = veorq_u8(mask0, vld1q_u8(src)); + src += BlockSize; + + /* Actually do encryption, use optimized asm. */ + /* Interleave CTR calculations with AES ones, to mask latencies. */ + __asm__ __volatile__ ( + AES_DEC_ROUND(12, 0) "mov %[high], %[tweak0].d[1]\n" + AES_DEC_ROUND(11, 0) "mov %[low], %[tweak0].d[0]\n" + AES_DEC_ROUND(10, 0) "and %[mask], %[xorv], %[high], asr 63\n" + AES_DEC_ROUND(9, 0) "extr %[high], %[high], %[low], 63\n" + AES_DEC_ROUND(8, 0) "eor %[low], %[mask], %[low], lsl 1\n" + AES_DEC_ROUND(7, 0) "mov %[tweak0].d[1], %[high]\n" + AES_DEC_ROUND(6, 0) "mov %[tweak0].d[0], %[low]\n" + AES_DEC_ROUND(5, 0) + AES_DEC_ROUND(4, 0) + AES_DEC_ROUND(3, 0) + AES_DEC_ROUND(2, 0) + AES_DEC_SECOND_LAST_ROUND(1, 0) + AES_DEC_LAST_ROUND(0, 0) + : AES_ENC_DEC_OUTPUT_ONE_BLOCK(), + AES_ENC_DEC_OUTPUT_ONE_TWEAK(), + XTS_INCREMENT_OUTPUT_HIGH_LOW_MASK() + : XTS_INCREMENT_INPUT_XOR(), + AES_ENC_DEC_INPUT_ROUND_KEY(0), + AES_ENC_DEC_INPUT_ROUND_KEY(1), + AES_ENC_DEC_INPUT_ROUND_KEY(2), + AES_ENC_DEC_INPUT_ROUND_KEY(3), + AES_ENC_DEC_INPUT_ROUND_KEY(4), + AES_ENC_DEC_INPUT_ROUND_KEY(5), + AES_ENC_DEC_INPUT_ROUND_KEY(6), + AES_ENC_DEC_INPUT_ROUND_KEY(7), + AES_ENC_DEC_INPUT_ROUND_KEY(8), + AES_ENC_DEC_INPUT_ROUND_KEY(9), + AES_ENC_DEC_INPUT_ROUND_KEY(10), + AES_ENC_DEC_INPUT_ROUND_KEY(11), + AES_ENC_DEC_INPUT_ROUND_KEY(12) + : "cc" + ); + + /* XOR blocks. */ + tmp0 = veorq_u8(mask0, tmp0); + + /* Store to output. */ + vst1q_u8(dst, tmp0); + dst += BlockSize; + } + + vst1q_u8(m_tweak, tweak0); + + std::memcpy(m_last_block, src, BlockSize); + m_state = State_Processing; + + return processed; + } + + template<> + size_t XtsModeImpl::ProcessBlocks<AesDecryptor256>(u8 *dst, const u8 *src, size_t num_blocks) { + /* Handle last buffered block. */ + size_t processed = (num_blocks - 1) * BlockSize; + + if (m_state == State_Processing) { + this->ProcessBlock(dst, m_last_block); + dst += BlockSize; + processed += BlockSize; + } + + /* Preload all round keys + iv into neon registers. */ + const u8 *keys = static_cast<const AesDecryptor256 *>(m_cipher_ctx)->GetRoundKey(); + DECLARE_ROUND_KEY_VAR(0); + DECLARE_ROUND_KEY_VAR(1); + DECLARE_ROUND_KEY_VAR(2); + DECLARE_ROUND_KEY_VAR(3); + DECLARE_ROUND_KEY_VAR(4); + DECLARE_ROUND_KEY_VAR(5); + DECLARE_ROUND_KEY_VAR(6); + DECLARE_ROUND_KEY_VAR(7); + DECLARE_ROUND_KEY_VAR(8); + DECLARE_ROUND_KEY_VAR(9); + DECLARE_ROUND_KEY_VAR(10); + DECLARE_ROUND_KEY_VAR(11); + DECLARE_ROUND_KEY_VAR(12); + DECLARE_ROUND_KEY_VAR(13); + DECLARE_ROUND_KEY_VAR(14); + uint8x16_t tweak0 = vld1q_u8(m_tweak); + constexpr uint64_t xorv = 0x87ul; + uint64_t high, low, mask; + + /* Process three blocks at a time, when possible. */ + if (num_blocks > 3) { + /* Multiply tweak twice. */ + uint8x16_t tweak1 = MultiplyTweak(tweak0); + uint8x16_t tweak2 = MultiplyTweak(tweak1); + + do { + /* Save tweaks for xor usage. */ + const uint8x16_t mask0 = tweak0; + const uint8x16_t mask1 = tweak1; + const uint8x16_t mask2 = tweak2; + + /* Read blocks in, XOR with tweaks. */ + uint8x16_t tmp0 = veorq_u8(mask0, vld1q_u8(src)); src += BlockSize; + uint8x16_t tmp1 = veorq_u8(mask1, vld1q_u8(src)); src += BlockSize; + uint8x16_t tmp2 = veorq_u8(mask2, vld1q_u8(src)); src += BlockSize; + + /* Actually do encryption, use optimized asm. */ + /* Interleave GF mult calculations with AES ones, to mask latencies. */ + __asm__ __volatile__ ( + AES_DEC_ROUND(14, 0) "mov %[high], %[tweak2].d[1]\n" + AES_DEC_ROUND(14, 1) "mov %[low], %[tweak2].d[0]\n" + AES_DEC_ROUND(14, 2) "mov %[mask], 0x87\n" + AES_DEC_ROUND(13, 0) "and %[mask], %[mask], %[high], asr 63\n" + AES_DEC_ROUND(13, 1) "extr %[high], %[high], %[low], 63\n" + AES_DEC_ROUND(13, 2) "eor %[low], %[mask], %[low], lsl 1\n" + AES_DEC_ROUND(12, 0) "mov %[tweak0].d[1], %[high]\n" + AES_DEC_ROUND(12, 1) "mov %[tweak0].d[0], %[low]\n" + AES_DEC_ROUND(12, 2) "mov %[mask], 0x87\n" + AES_DEC_ROUND(11, 0) "and %[mask], %[mask], %[high], asr 63\n" + AES_DEC_ROUND(11, 1) "extr %[high], %[high], %[low], 63\n" + AES_DEC_ROUND(11, 2) "eor %[low], %[mask], %[low], lsl 1\n" + AES_DEC_ROUND(10, 0) "mov %[tweak1].d[1], %[high]\n" + AES_DEC_ROUND(10, 1) "mov %[tweak1].d[0], %[low]\n" + AES_DEC_ROUND(10, 2) "mov %[mask], 0x87\n" + AES_DEC_ROUND(9, 0) "and %[mask], %[mask], %[high], asr 63\n" + AES_DEC_ROUND(9, 1) "extr %[high], %[high], %[low], 63\n" + AES_DEC_ROUND(9, 2) "eor %[low], %[mask], %[low], lsl 1\n" + AES_DEC_ROUND(8, 0) "mov %[tweak2].d[1], %[high]\n" + AES_DEC_ROUND(8, 1) "mov %[tweak2].d[0], %[low]\n" + AES_DEC_ROUND(8, 2) + AES_DEC_ROUND(7, 0) AES_DEC_ROUND(7, 1) AES_DEC_ROUND(7, 2) + AES_DEC_ROUND(6, 0) AES_DEC_ROUND(6, 1) AES_DEC_ROUND(6, 2) + AES_DEC_ROUND(5, 0) AES_DEC_ROUND(5, 1) AES_DEC_ROUND(5, 2) + AES_DEC_ROUND(4, 0) AES_DEC_ROUND(4, 1) AES_DEC_ROUND(4, 2) + AES_DEC_ROUND(3, 0) AES_DEC_ROUND(3, 1) AES_DEC_ROUND(3, 2) + AES_DEC_ROUND(2, 0) AES_DEC_ROUND(2, 1) AES_DEC_ROUND(2, 2) + AES_DEC_SECOND_LAST_ROUND(1, 0) AES_DEC_SECOND_LAST_ROUND(1, 1) AES_DEC_SECOND_LAST_ROUND(1, 2) + AES_DEC_LAST_ROUND(0, 0) AES_DEC_LAST_ROUND(0, 1) AES_DEC_LAST_ROUND(0, 2) + : AES_ENC_DEC_OUTPUT_THREE_BLOCKS(), + AES_ENC_DEC_OUTPUT_THREE_TWEAKS(), + XTS_INCREMENT_OUTPUT_HIGH_LOW_MASK() + : AES_ENC_DEC_INPUT_ROUND_KEY(0), + AES_ENC_DEC_INPUT_ROUND_KEY(1), + AES_ENC_DEC_INPUT_ROUND_KEY(2), + AES_ENC_DEC_INPUT_ROUND_KEY(3), + AES_ENC_DEC_INPUT_ROUND_KEY(4), + AES_ENC_DEC_INPUT_ROUND_KEY(5), + AES_ENC_DEC_INPUT_ROUND_KEY(6), + AES_ENC_DEC_INPUT_ROUND_KEY(7), + AES_ENC_DEC_INPUT_ROUND_KEY(8), + AES_ENC_DEC_INPUT_ROUND_KEY(9), + AES_ENC_DEC_INPUT_ROUND_KEY(10), + AES_ENC_DEC_INPUT_ROUND_KEY(11), + AES_ENC_DEC_INPUT_ROUND_KEY(12), + AES_ENC_DEC_INPUT_ROUND_KEY(13), + AES_ENC_DEC_INPUT_ROUND_KEY(14) + : "cc" + ); + + /* XOR blocks. */ + tmp0 = veorq_u8(mask0, tmp0); + tmp1 = veorq_u8(mask1, tmp1); + tmp2 = veorq_u8(mask2, tmp2); + + /* Store to output. */ + vst1q_u8(dst, tmp0); dst += BlockSize; + vst1q_u8(dst, tmp1); dst += BlockSize; + vst1q_u8(dst, tmp2); dst += BlockSize; + + num_blocks -= 3; + } while (num_blocks > 3); + } + + while ((--num_blocks) > 0) { + /* Save tweak for xor usage. */ + const uint8x16_t mask0 = tweak0; + + /* Read block in, XOR with tweak. */ + uint8x16_t tmp0 = veorq_u8(mask0, vld1q_u8(src)); + src += BlockSize; + + /* Actually do encryption, use optimized asm. */ + /* Interleave CTR calculations with AES ones, to mask latencies. */ + __asm__ __volatile__ ( + AES_DEC_ROUND(14, 0) "mov %[high], %[tweak0].d[1]\n" + AES_DEC_ROUND(13, 0) "mov %[low], %[tweak0].d[0]\n" + AES_DEC_ROUND(12, 0) "and %[mask], %[xorv], %[high], asr 63\n" + AES_DEC_ROUND(11, 0) "extr %[high], %[high], %[low], 63\n" + AES_DEC_ROUND(10, 0) "eor %[low], %[mask], %[low], lsl 1\n" + AES_DEC_ROUND(9, 0) "mov %[tweak0].d[1], %[high]\n" + AES_DEC_ROUND(8, 0) "mov %[tweak0].d[0], %[low]\n" + AES_DEC_ROUND(7, 0) + AES_DEC_ROUND(6, 0) + AES_DEC_ROUND(5, 0) + AES_DEC_ROUND(4, 0) + AES_DEC_ROUND(3, 0) + AES_DEC_ROUND(2, 0) + AES_DEC_SECOND_LAST_ROUND(1, 0) + AES_DEC_LAST_ROUND(0, 0) + : AES_ENC_DEC_OUTPUT_ONE_BLOCK(), + AES_ENC_DEC_OUTPUT_ONE_TWEAK(), + XTS_INCREMENT_OUTPUT_HIGH_LOW_MASK() + : XTS_INCREMENT_INPUT_XOR(), + AES_ENC_DEC_INPUT_ROUND_KEY(0), + AES_ENC_DEC_INPUT_ROUND_KEY(1), + AES_ENC_DEC_INPUT_ROUND_KEY(2), + AES_ENC_DEC_INPUT_ROUND_KEY(3), + AES_ENC_DEC_INPUT_ROUND_KEY(4), + AES_ENC_DEC_INPUT_ROUND_KEY(5), + AES_ENC_DEC_INPUT_ROUND_KEY(6), + AES_ENC_DEC_INPUT_ROUND_KEY(7), + AES_ENC_DEC_INPUT_ROUND_KEY(8), + AES_ENC_DEC_INPUT_ROUND_KEY(9), + AES_ENC_DEC_INPUT_ROUND_KEY(10), + AES_ENC_DEC_INPUT_ROUND_KEY(11), + AES_ENC_DEC_INPUT_ROUND_KEY(12), + AES_ENC_DEC_INPUT_ROUND_KEY(13), + AES_ENC_DEC_INPUT_ROUND_KEY(14) + : "cc" + ); + + /* XOR blocks. */ + tmp0 = veorq_u8(mask0, tmp0); + + /* Store to output. */ + vst1q_u8(dst, tmp0); + dst += BlockSize; + } + + vst1q_u8(m_tweak, tweak0); + + std::memcpy(m_last_block, src, BlockSize); + m_state = State_Processing; + + return processed; + } + +} + +#else + +/* TODO: Non-EL0 implementation. */ +namespace ams::crypto::impl { + +} + +#endif + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_xts_mode_impl.arch.generic.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_xts_mode_impl.arch.generic.cpp new file mode 100644 index 00000000..510d107c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_xts_mode_impl.arch.generic.cpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> +#include "crypto_update_impl.hpp" + +namespace ams::crypto::impl { + + size_t XtsModeImpl::UpdateGeneric(void *dst, size_t dst_size, const void *src, size_t src_size) { + AMS_ASSERT(m_state == State_Initialized || m_state == State_Processing); + + return UpdateImpl<void>(this, dst, dst_size, src, src_size); + } + + size_t XtsModeImpl::ProcessBlocksGeneric(u8 *dst, const u8 *src, size_t num_blocks) { + size_t processed = BlockSize * (num_blocks - 1); + + if (m_state == State_Processing) { + this->ProcessBlock(dst, m_last_block); + dst += BlockSize; + processed += BlockSize; + } + + while ((--num_blocks) > 0) { + this->ProcessBlock(dst, src); + dst += BlockSize; + src += BlockSize; + } + + std::memcpy(m_last_block, src, BlockSize); + + m_state = State_Processing; + + return processed; + } + + template<> size_t XtsModeImpl::Update<AesEncryptor128>(void *dst, size_t dst_size, const void *src, size_t src_size) { return this->UpdateGeneric(dst, dst_size, src, src_size); } + template<> size_t XtsModeImpl::Update<AesEncryptor192>(void *dst, size_t dst_size, const void *src, size_t src_size) { return this->UpdateGeneric(dst, dst_size, src, src_size); } + template<> size_t XtsModeImpl::Update<AesEncryptor256>(void *dst, size_t dst_size, const void *src, size_t src_size) { return this->UpdateGeneric(dst, dst_size, src, src_size); } + + template<> size_t XtsModeImpl::Update<AesDecryptor128>(void *dst, size_t dst_size, const void *src, size_t src_size) { return this->UpdateGeneric(dst, dst_size, src, src_size); } + template<> size_t XtsModeImpl::Update<AesDecryptor192>(void *dst, size_t dst_size, const void *src, size_t src_size) { return this->UpdateGeneric(dst, dst_size, src, src_size); } + template<> size_t XtsModeImpl::Update<AesDecryptor256>(void *dst, size_t dst_size, const void *src, size_t src_size) { return this->UpdateGeneric(dst, dst_size, src, src_size); } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_xts_mode_impl.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_xts_mode_impl.cpp new file mode 100644 index 00000000..e29f4708 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/crypto/impl/crypto_xts_mode_impl.cpp @@ -0,0 +1,144 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::crypto::impl { + + namespace { + + /* TODO: Support non-Nintendo Endianness */ + + void MultiplyTweakGeneric(u64 *tweak) { + const u64 carry = tweak[1] & (static_cast<u64>(1) << (BITSIZEOF(u64) - 1)); + + tweak[1] = ((tweak[1] << 1) | (tweak[0] >> (BITSIZEOF(u64) - 1))); + tweak[0] = (tweak[0] << 1); + + if (carry) { + tweak[0] ^= static_cast<u64>(0x87); + } + } + + } + + void XtsModeImpl::ProcessBlock(u8 *dst, const u8 *src) { + u8 tmp[BlockSize]; + + /* Xor. */ + for (size_t i = 0; i < BlockSize; i++) { + tmp[i] = m_tweak[i] ^ src[i]; + } + + /* Crypt */ + m_cipher_func(tmp, tmp, m_cipher_ctx); + + /* Xor. */ + for (size_t i = 0; i < BlockSize; i++) { + dst[i] = m_tweak[i] ^ tmp[i]; + } + + MultiplyTweakGeneric(reinterpret_cast<u64 *>(m_tweak)); + } + + size_t XtsModeImpl::FinalizeEncryption(void *dst, size_t dst_size) { + AMS_ASSERT(m_state == State_Processing); + AMS_UNUSED(dst_size); + + u8 *dst_u8 = static_cast<u8 *>(dst); + size_t processed = 0; + + if (m_num_buffered == 0) { + this->ProcessBlock(dst_u8, m_last_block); + processed = BlockSize; + } else { + this->ProcessBlock(m_last_block, m_last_block); + + std::memcpy(m_buffer + m_num_buffered, m_last_block + m_num_buffered, BlockSize - m_num_buffered); + + this->ProcessBlock(dst_u8, m_buffer); + + std::memcpy(dst_u8 + BlockSize, m_last_block, m_num_buffered); + + processed = BlockSize + m_num_buffered; + } + + m_state = State_Done; + return processed; + } + + size_t XtsModeImpl::FinalizeDecryption(void *dst, size_t dst_size) { + AMS_ASSERT(m_state == State_Processing); + AMS_UNUSED(dst_size); + + u8 *dst_u8 = static_cast<u8 *>(dst); + size_t processed = 0; + + if (m_num_buffered == 0) { + this->ProcessBlock(dst_u8, m_last_block); + processed = BlockSize; + } else { + u8 tmp_tweak[BlockSize]; + std::memcpy(tmp_tweak, m_tweak, BlockSize); + MultiplyTweakGeneric(reinterpret_cast<u64 *>(m_tweak)); + + this->ProcessBlock(m_last_block, m_last_block); + + std::memcpy(m_buffer + m_num_buffered, m_last_block + m_num_buffered, BlockSize - m_num_buffered); + + std::memcpy(m_tweak, tmp_tweak, BlockSize); + + this->ProcessBlock(dst_u8, m_buffer); + + std::memcpy(dst_u8 + BlockSize, m_last_block, m_num_buffered); + + processed = BlockSize + m_num_buffered; + } + + m_state = State_Done; + return processed; + } + + size_t XtsModeImpl::ProcessPartialData(u8 *dst, const u8 *src, size_t size) { + size_t processed = 0; + + std::memcpy(m_buffer + m_num_buffered, src, size); + m_num_buffered += size; + + if (m_num_buffered == BlockSize) { + if (m_state == State_Processing) { + this->ProcessBlock(dst, m_last_block); + processed += BlockSize; + } + + std::memcpy(m_last_block, m_buffer, BlockSize); + m_num_buffered = 0; + + m_state = State_Processing; + } + + return processed; + } + + size_t XtsModeImpl::ProcessRemainingData(u8 *dst, const u8 *src, size_t size) { + AMS_UNUSED(dst); + + std::memcpy(m_buffer, src, size); + m_num_buffered = size; + + return 0; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/dd_cache.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/dd_cache.cpp new file mode 100644 index 00000000..38bb9469 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/dd_cache.cpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#include "impl/dd_select_cache_impl.hpp" + +namespace ams::dd { + + void InvalidateDataCache(void *addr, size_t size) { + return impl::InvalidateDataCacheImpl(addr, size); + } + + void StoreDataCache(void *addr, size_t size) { + return impl::StoreDataCacheImpl(addr, size); + } + + void FlushDataCache(void *addr, size_t size) { + return impl::FlushDataCacheImpl(addr, size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/dd_io_mapping.os.horizon.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/dd_io_mapping.os.horizon.cpp new file mode 100644 index 00000000..11d7c497 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/dd_io_mapping.os.horizon.cpp @@ -0,0 +1,179 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif + +namespace ams::dd { + + uintptr_t QueryIoMapping(dd::PhysicalAddress phys_addr, size_t size) { + #if defined(ATMOSPHERE_IS_EXOSPHERE) + #if defined(ATMOSPHERE_ARCH_ARM64) + /* TODO: Do this in a less shitty way. */ + if (secmon::MemoryRegionPhysicalDeviceClkRst.Contains(phys_addr, size)) { + return secmon::MemoryRegionVirtualDeviceClkRst.GetAddress() + phys_addr - secmon::MemoryRegionPhysicalDeviceClkRst.GetAddress(); + } else if (secmon::MemoryRegionPhysicalDeviceGpio.Contains(phys_addr, size)) { + return secmon::MemoryRegionVirtualDeviceGpio.GetAddress() + phys_addr - secmon::MemoryRegionPhysicalDeviceGpio.GetAddress(); + } else if (secmon::MemoryRegionPhysicalDeviceApbMisc.Contains(phys_addr, size)) { + return secmon::MemoryRegionVirtualDeviceApbMisc.GetAddress() + phys_addr - secmon::MemoryRegionPhysicalDeviceApbMisc.GetAddress(); + } else if (secmon::MemoryRegionPhysicalDeviceSdmmc.Contains(phys_addr, size)) { + return secmon::MemoryRegionVirtualDeviceSdmmc.GetAddress() + phys_addr - secmon::MemoryRegionPhysicalDeviceSdmmc.GetAddress(); + } else if (secmon::MemoryRegionPhysicalDevicePmc.Contains(phys_addr, size)) { + return secmon::MemoryRegionVirtualDevicePmc.GetAddress() + phys_addr - secmon::MemoryRegionPhysicalDevicePmc.GetAddress(); + } else if (secmon::MemoryRegionPhysicalDeviceI2c5.Contains(phys_addr, size)) { + return secmon::MemoryRegionVirtualDeviceI2c5.GetAddress() + phys_addr - secmon::MemoryRegionPhysicalDeviceI2c5.GetAddress(); + } else if (secmon::MemoryRegionPhysicalDeviceI2c1.Contains(phys_addr, size)) { + return secmon::MemoryRegionVirtualDeviceI2c1.GetAddress() + phys_addr - secmon::MemoryRegionPhysicalDeviceI2c1.GetAddress(); + } else { + AMS_UNUSED(size); + return static_cast<uintptr_t>(phys_addr); + } + + #elif defined(ATMOSPHERE_ARCH_ARM) + /* TODO: BPMP translation? */ + AMS_UNUSED(size); + return static_cast<uintptr_t>(phys_addr); + #else + #error "Unknown architecture for ams::dd::QueryIoMapping (EXOSPHERE)!" + #endif + #elif defined(ATMOSPHERE_IS_MESOSPHERE) + /* TODO: Kernel address translation? */ + AMS_UNUSED(size); + return static_cast<uintptr_t>(phys_addr); + #elif defined(ATMOSPHERE_IS_STRATOSPHERE) + + svc::Address virt_addr = 0; + const dd::PhysicalAddress aligned_addr = util::AlignDown(phys_addr, os::MemoryPageSize); + const size_t offset = phys_addr - aligned_addr; + const size_t aligned_size = size + offset; + + if (hos::GetVersion() >= hos::Version_10_0_0) { + svc::Size region_size = 0; + R_TRY_CATCH(svc::QueryMemoryMapping(std::addressof(virt_addr), std::addressof(region_size), aligned_addr, aligned_size)) { + /* Official software handles this by returning 0. */ + R_CATCH(svc::ResultNotFound) { return 0; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + AMS_ASSERT(region_size >= aligned_size); + } else { + R_TRY_CATCH(svc::LegacyQueryIoMapping(std::addressof(virt_addr), aligned_addr, aligned_size)) { + /* Official software handles this by returning 0. */ + R_CATCH(svc::ResultNotFound) { return 0; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + } + + return static_cast<uintptr_t>(virt_addr) + offset; + + #else + #error "Unknown execution context for ams::dd::QueryIoMapping!" + #endif + } + + namespace { + + #if defined(ATMOSPHERE_IS_STRATOSPHERE) + + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + + constexpr dd::PhysicalAddress PmcPhysStart = 0x7000E400; + constexpr dd::PhysicalAddress PmcPhysLast = 0x7000EFFF; + + constexpr bool IsValidPmcPhysicalAddress(dd::PhysicalAddress phys_addr) { + return util::IsAligned(phys_addr, alignof(u32)) && PmcPhysStart <= phys_addr && phys_addr <= PmcPhysLast; + } + + u32 ReadWritePmcRegisterImpl(dd::PhysicalAddress phys_addr, u32 value, u32 mask) { + u32 out_value; + R_ABORT_UNLESS(spl::smc::ConvertResult(spl::smc::AtmosphereReadWriteRegister(phys_addr, mask, value, std::addressof(out_value)))); + return out_value; + } + + bool TryReadModifyWritePmcRegister(u32 *out, dd::PhysicalAddress phys_addr, u32 value, u32 mask) { + if (IsValidPmcPhysicalAddress(phys_addr)) { + *out = ReadWritePmcRegisterImpl(phys_addr, value, mask); + return true; + } else { + return false; + } + } + + #else + + bool TryReadModifyWritePmcRegister(u32 *out, dd::PhysicalAddress phys_addr, u32 value, u32 mask) { + AMS_UNUSED(out, phys_addr, value, mask); + return false; + } + + #endif + + #endif + } + + u32 ReadIoRegister(dd::PhysicalAddress phys_addr) { + #if defined(ATMOSPHERE_IS_EXOSPHERE) || defined(ATMOSPHERE_IS_MESOSPHERE) + return reg::Read(dd::QueryIoMapping(phys_addr, sizeof(u32))); + #elif defined(ATMOSPHERE_IS_STRATOSPHERE) + + u32 val; + if (!TryReadModifyWritePmcRegister(std::addressof(val), phys_addr, 0, 0)) { + R_ABORT_UNLESS(svc::ReadWriteRegister(std::addressof(val), phys_addr, 0, 0)); + } + return val; + + #else + #error "Unknown execution context for ams::dd::ReadIoRegister!" + #endif + } + + void WriteIoRegister(dd::PhysicalAddress phys_addr, u32 value) { + #if defined(ATMOSPHERE_IS_EXOSPHERE) || defined(ATMOSPHERE_IS_MESOSPHERE) + reg::Write(dd::QueryIoMapping(phys_addr, sizeof(u32)), value); + #elif defined(ATMOSPHERE_IS_STRATOSPHERE) + + u32 out_val; + if (!TryReadModifyWritePmcRegister(std::addressof(out_val), phys_addr, value, 0xFFFFFFFF)) { + R_ABORT_UNLESS(svc::ReadWriteRegister(std::addressof(out_val), phys_addr, 0xFFFFFFFF, value)); + } + AMS_UNUSED(out_val); + + #else + #error "Unknown execution context for ams::dd::WriteIoRegister!" + #endif + } + + u32 ReadModifyWriteIoRegister(PhysicalAddress phys_addr, u32 value, u32 mask) { + #if defined(ATMOSPHERE_IS_EXOSPHERE) || defined(ATMOSPHERE_IS_MESOSPHERE) + AMS_UNUSED(phys_addr, value, mask); + AMS_ABORT("ReadModifyWriteIoRegister TODO under non-stratosphere"); + #elif defined(ATMOSPHERE_IS_STRATOSPHERE) + + u32 out_val; + if (!TryReadModifyWritePmcRegister(std::addressof(out_val), phys_addr, value, mask)) { + R_ABORT_UNLESS(svc::ReadWriteRegister(std::addressof(out_val), phys_addr, mask, value)); + } + return out_val; + + #else + #error "Unknown execution context for ams::dd::ReadModifyWriteIoRegister!" + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/impl/dd_cache_impl.os.horizon.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/impl/dd_cache_impl.os.horizon.hpp new file mode 100644 index 00000000..8930d215 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/impl/dd_cache_impl.os.horizon.hpp @@ -0,0 +1,114 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#else +#include <vapours.hpp> +#endif + +namespace ams::dd::impl { + + void StoreDataCacheImpl(void *addr, size_t size) { + #if defined(ATMOSPHERE_ARCH_ARM64) + /* On aarch64, we can use cache maintenance instructions. */ + + /* Get cache line size. */ + uintptr_t ctr_el0 = 0; + __asm__ __volatile__("mrs %[ctr_el0], ctr_el0" : [ctr_el0]"=r"(ctr_el0)); + const uintptr_t cache_line_size = 4 << ((ctr_el0 >> 16) & 0xF); + + #if defined(ATMOSPHERE_IS_STRATOSPHERE) + /* Get the thread local region. */ + auto * const tlr = svc::GetThreadLocalRegion(); + + /* Note to the kernel that we're performing cache maintenance, in case we get interrupted while touching cache lines. */ + tlr->cache_maintenance_flag = 1; + ON_SCOPE_EXIT { tlr->cache_maintenance_flag = 0; }; + #endif + + /* Invalidate the cache. */ + const uintptr_t start_addr = reinterpret_cast<uintptr_t>(addr) & ~(cache_line_size - 1); + const uintptr_t end_addr = reinterpret_cast<uintptr_t>(addr) + size; + for (uintptr_t cur = start_addr; cur < end_addr; cur += cache_line_size) { + __asm__ __volatile__("dc cvac, %[cur]" : : [cur]"r"(cur)); + } + + /* Add a memory barrier. */ + __asm__ __volatile__("dsb sy" ::: "memory"); + #else + #if defined(ATMOSPHERE_IS_STRATOSPHERE) + /* Invoke the relevant svc. */ + const auto result = svc::StoreProcessDataCache(svc::PseudoHandle::CurrentProcess, reinterpret_cast<uintptr_t>(addr), size); + R_ASSERT(result); + #elif defined(ATMOSPHERE_IS_EXOSPHERE) && defined(__BPMP__) + return hw::StoreDataCache(addr, size); + #else + #error "Unknown execution context for ams::dd::impl::StoreDataCacheImpl" + #endif + #endif + } + + void FlushDataCacheImpl(void *addr, size_t size) { + #if defined(ATMOSPHERE_ARCH_ARM64) + /* On aarch64, we can use cache maintenance instructions. */ + + /* Get cache line size. */ + uintptr_t ctr_el0 = 0; + __asm__ __volatile__("mrs %[ctr_el0], ctr_el0" : [ctr_el0]"=r"(ctr_el0)); + const uintptr_t cache_line_size = 4 << ((ctr_el0 >> 16) & 0xF); + + #if defined(ATMOSPHERE_IS_STRATOSPHERE) + /* Get the thread local region. */ + auto * const tlr = svc::GetThreadLocalRegion(); + + /* Note to the kernel that we're performing cache maintenance, in case we get interrupted while touching cache lines. */ + tlr->cache_maintenance_flag = 1; + ON_SCOPE_EXIT { tlr->cache_maintenance_flag = 0; }; + #endif + + /* Invalidate the cache. */ + const uintptr_t start_addr = reinterpret_cast<uintptr_t>(addr) & ~(cache_line_size - 1); + const uintptr_t end_addr = reinterpret_cast<uintptr_t>(addr) + size; + for (uintptr_t cur = start_addr; cur < end_addr; cur += cache_line_size) { + __asm__ __volatile__("dc civac, %[cur]" : : [cur]"r"(cur)); + } + + /* Add a memory barrier. */ + __asm__ __volatile__("dsb sy" ::: "memory"); + #else + #if defined(ATMOSPHERE_IS_STRATOSPHERE) + /* Invoke the relevant svc. */ + const auto result = svc::FlushProcessDataCache(svc::PseudoHandle::CurrentProcess, reinterpret_cast<uintptr_t>(addr), size); + R_ASSERT(result); + #elif defined(ATMOSPHERE_IS_EXOSPHERE) && defined(__BPMP__) + return hw::FlushDataCache(addr, size); + #else + #error "Unknown execution context for ams::dd::impl::FlushDataCacheImpl" + #endif + #endif + } + + void InvalidateDataCacheImpl(void *addr, size_t size) { + #if defined(ATMOSPHERE_IS_EXOSPHERE) && defined(__BPMP__) + return hw::InvalidateDataCache(addr, size); + #else + /* Just perform a flush, which is clean + invalidate. */ + return FlushDataCacheImpl(addr, size); + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/impl/dd_cache_impl.os.linux.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/impl/dd_cache_impl.os.linux.hpp new file mode 100644 index 00000000..4fe82cd0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/impl/dd_cache_impl.os.linux.hpp @@ -0,0 +1,89 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#else +#include <vapours.hpp> +#endif + +namespace ams::dd::impl { + + void StoreDataCacheImpl(void *addr, size_t size) { + #if defined(ATMOSPHERE_ARCH_ARM64) + /* On aarch64, we can use cache maintenance instructions. */ + + /* Get cache line size. */ + uintptr_t ctr_el0 = 0; + __asm__ __volatile__("mrs %[ctr_el0], ctr_el0" : [ctr_el0]"=r"(ctr_el0)); + const uintptr_t cache_line_size = 4 << ((ctr_el0 >> 16) & 0xF); + + /* Invalidate the cache. */ + const uintptr_t start_addr = reinterpret_cast<uintptr_t>(addr) & ~(cache_line_size - 1); + const uintptr_t end_addr = reinterpret_cast<uintptr_t>(addr) + size; + for (uintptr_t cur = start_addr; cur < end_addr; cur += cache_line_size) { + __asm__ __volatile__("dc cvac, %[cur]" : : [cur]"r"(cur)); + } + + /* Add a memory barrier. */ + __asm__ __volatile__("dsb sy" ::: "memory"); + #elif defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + /* Don't do anything, cache maintenance isn't available/relevant to userland. */ + AMS_UNUSED(addr, size); + #else + #error "Unknown architecture for linux dd::StoreDataCacheImpl" + #endif + } + + void FlushDataCacheImpl(void *addr, size_t size) { + #if defined(ATMOSPHERE_ARCH_ARM64) + /* On aarch64, we can use cache maintenance instructions. */ + + /* Get cache line size. */ + uintptr_t ctr_el0 = 0; + __asm__ __volatile__("mrs %[ctr_el0], ctr_el0" : [ctr_el0]"=r"(ctr_el0)); + const uintptr_t cache_line_size = 4 << ((ctr_el0 >> 16) & 0xF); + + /* Invalidate the cache. */ + const uintptr_t start_addr = reinterpret_cast<uintptr_t>(addr) & ~(cache_line_size - 1); + const uintptr_t end_addr = reinterpret_cast<uintptr_t>(addr) + size; + for (uintptr_t cur = start_addr; cur < end_addr; cur += cache_line_size) { + __asm__ __volatile__("dc civac, %[cur]" : : [cur]"r"(cur)); + } + + /* Add a memory barrier. */ + __asm__ __volatile__("dsb sy" ::: "memory"); + #elif defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + /* Don't do anything, cache maintenance isn't available/relevant to userland. */ + AMS_UNUSED(addr, size); + #else + #error "Unknown architecture for linux dd::FlushDataCacheImpl" + #endif + } + + void InvalidateDataCacheImpl(void *addr, size_t size) { + #if defined(ATMOSPHERE_ARCH_ARM64) + /* Just perform a flush, which is clean + invalidate. */ + return FlushDataCacheImpl(addr, size); + #elif defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + /* Don't do anything, cache maintenance isn't available/relevant to userland. */ + AMS_UNUSED(addr, size); + #else + #error "Unknown architecture for linux dd::InvalidateDataCacheImpl" + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/impl/dd_cache_impl.os.macos.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/impl/dd_cache_impl.os.macos.hpp new file mode 100644 index 00000000..444f8e68 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/impl/dd_cache_impl.os.macos.hpp @@ -0,0 +1,89 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#else +#include <vapours.hpp> +#endif + +namespace ams::dd::impl { + + void StoreDataCacheImpl(void *addr, size_t size) { + #if defined(ATMOSPHERE_ARCH_ARM64) + /* On aarch64, we can use cache maintenance instructions. */ + + /* Get cache line size. */ + uintptr_t ctr_el0 = 0; + __asm__ __volatile__("mrs %[ctr_el0], ctr_el0" : [ctr_el0]"=r"(ctr_el0)); + const uintptr_t cache_line_size = 4 << ((ctr_el0 >> 16) & 0xF); + + /* Invalidate the cache. */ + const uintptr_t start_addr = reinterpret_cast<uintptr_t>(addr) & ~(cache_line_size - 1); + const uintptr_t end_addr = reinterpret_cast<uintptr_t>(addr) + size; + for (uintptr_t cur = start_addr; cur < end_addr; cur += cache_line_size) { + __asm__ __volatile__("dc cvac, %[cur]" : : [cur]"r"(cur)); + } + + /* Add a memory barrier. */ + __asm__ __volatile__("dsb sy" ::: "memory"); + #elif defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + /* Don't do anything, cache maintenance isn't available/relevant to userland. */ + AMS_UNUSED(addr, size); + #else + #error "Unknown architecture for macOS dd::StoreDataCacheImpl" + #endif + } + + void FlushDataCacheImpl(void *addr, size_t size) { + #if defined(ATMOSPHERE_ARCH_ARM64) + /* On aarch64, we can use cache maintenance instructions. */ + + /* Get cache line size. */ + uintptr_t ctr_el0 = 0; + __asm__ __volatile__("mrs %[ctr_el0], ctr_el0" : [ctr_el0]"=r"(ctr_el0)); + const uintptr_t cache_line_size = 4 << ((ctr_el0 >> 16) & 0xF); + + /* Invalidate the cache. */ + const uintptr_t start_addr = reinterpret_cast<uintptr_t>(addr) & ~(cache_line_size - 1); + const uintptr_t end_addr = reinterpret_cast<uintptr_t>(addr) + size; + for (uintptr_t cur = start_addr; cur < end_addr; cur += cache_line_size) { + __asm__ __volatile__("dc civac, %[cur]" : : [cur]"r"(cur)); + } + + /* Add a memory barrier. */ + __asm__ __volatile__("dsb sy" ::: "memory"); + #elif defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + /* Don't do anything, cache maintenance isn't available/relevant to userland. */ + AMS_UNUSED(addr, size); + #else + #error "Unknown architecture for macOS dd::FlushDataCacheImpl" + #endif + } + + void InvalidateDataCacheImpl(void *addr, size_t size) { + #if defined(ATMOSPHERE_ARCH_ARM64) + /* Just perform a flush, which is clean + invalidate. */ + return FlushDataCacheImpl(addr, size); + #elif defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + /* Don't do anything, cache maintenance isn't available/relevant to userland. */ + AMS_UNUSED(addr, size); + #else + #error "Unknown architecture for macOS dd::InvalidateDataCacheImpl" + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/impl/dd_cache_impl.os.windows.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/impl/dd_cache_impl.os.windows.hpp new file mode 100644 index 00000000..7dba8676 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/impl/dd_cache_impl.os.windows.hpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#else +#include <vapours.hpp> +#endif + +namespace ams::dd::impl { + + void StoreDataCacheImpl(void *addr, size_t size) { + #if defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + /* Don't do anything, cache maintenance isn't available/relevant to userland. */ + AMS_UNUSED(addr, size); + #else + #error "Unknown architecture for windows dd::StoreDataCacheImpl" + #endif + } + + void FlushDataCacheImpl(void *addr, size_t size) { + #if defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + /* Don't do anything, cache maintenance isn't available/relevant to userland. */ + AMS_UNUSED(addr, size); + #else + #error "Unknown architecture for windows dd::FlushDataCacheImpl" + #endif + } + + void InvalidateDataCacheImpl(void *addr, size_t size) { + #if defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + /* Don't do anything, cache maintenance isn't available/relevant to userland. */ + AMS_UNUSED(addr, size); + #else + #error "Unknown architecture for windows dd::InvalidateDataCacheImpl" + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/impl/dd_select_cache_impl.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/impl/dd_select_cache_impl.hpp new file mode 100644 index 00000000..5292da7e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/dd/impl/dd_select_cache_impl.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "dd_cache_impl.os.horizon.hpp" +#elif defined(ATMOSPHERE_OS_WINDOWS) + #include "dd_cache_impl.os.windows.hpp" +#elif defined(ATMOSPHERE_OS_LINUX) + #include "dd_cache_impl.os.linux.hpp" +#elif defined(ATMOSPHERE_OS_MACOS) + #include "dd_cache_impl.os.macos.hpp" +#else + #error "Unknown OS for ams::dd::CacheImpl" +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/result/result_get_name.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/result/result_get_name.cpp new file mode 100644 index 00000000..4595d0b1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/result/result_get_name.cpp @@ -0,0 +1,73 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#if !defined(ATMOSPHERE_OS_HORIZON) && (defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING)) + #define AMS_AUTO_GENERATE_RESULT_NAMES +#endif + +#include <vapours.hpp> + +namespace ams { + + #if defined(AMS_AUTO_GENERATE_RESULT_NAMES) + + #define AMS_INVOKE_MACRO_01(EXPR, n) EXPR(n); EXPR(n + (1 << 0)); + + #define AMS_INVOKE_MACRO_02(EXPR, n) AMS_INVOKE_MACRO_01(EXPR, n); AMS_INVOKE_MACRO_01(EXPR, n + (1 << 1)); + #define AMS_INVOKE_MACRO_03(EXPR, n) AMS_INVOKE_MACRO_02(EXPR, n); AMS_INVOKE_MACRO_02(EXPR, n + (1 << 2)); + #define AMS_INVOKE_MACRO_04(EXPR, n) AMS_INVOKE_MACRO_03(EXPR, n); AMS_INVOKE_MACRO_03(EXPR, n + (1 << 3)); + #define AMS_INVOKE_MACRO_05(EXPR, n) AMS_INVOKE_MACRO_04(EXPR, n); AMS_INVOKE_MACRO_04(EXPR, n + (1 << 4)); + #define AMS_INVOKE_MACRO_06(EXPR, n) AMS_INVOKE_MACRO_05(EXPR, n); AMS_INVOKE_MACRO_05(EXPR, n + (1 << 5)); + #define AMS_INVOKE_MACRO_07(EXPR, n) AMS_INVOKE_MACRO_06(EXPR, n); AMS_INVOKE_MACRO_06(EXPR, n + (1 << 6)); + #define AMS_INVOKE_MACRO_08(EXPR, n) AMS_INVOKE_MACRO_07(EXPR, n); AMS_INVOKE_MACRO_07(EXPR, n + (1 << 7)); + #define AMS_INVOKE_MACRO_09(EXPR, n) AMS_INVOKE_MACRO_08(EXPR, n); AMS_INVOKE_MACRO_08(EXPR, n + (1 << 8)); + #define AMS_INVOKE_MACRO_10(EXPR, n) AMS_INVOKE_MACRO_09(EXPR, n); AMS_INVOKE_MACRO_09(EXPR, n + (1 << 9)); + #define AMS_INVOKE_MACRO_11(EXPR, n) AMS_INVOKE_MACRO_10(EXPR, n); AMS_INVOKE_MACRO_10(EXPR, n + (1 << 10)); + #define AMS_INVOKE_MACRO_12(EXPR, n) AMS_INVOKE_MACRO_11(EXPR, n); AMS_INVOKE_MACRO_11(EXPR, n + (1 << 11)); + #define AMS_INVOKE_MACRO_13(EXPR, n) AMS_INVOKE_MACRO_12(EXPR, n); AMS_INVOKE_MACRO_12(EXPR, n + (1 << 12)); + + namespace { + + template<int Module, int Description> + constexpr const char *GetResultNameByModuleAndDescription() { + return ::ams::result::impl::ResultNameSpaceExistsImpl<Module>::template NameHolder<Description>::Name; + } + + template<int Module> + constexpr const char *GetResultNameByModule(int description) { + #define AMS_TEST_RESULT_DESCRIPTION_DEFINED(n) if constexpr (::ams::result::impl::ResultNameSpaceExistsImpl<Module>::template NameHolder<n>::Exists) { if (description == n) { return GetResultNameByModuleAndDescription<Module, n>(); } } + + AMS_INVOKE_MACRO_13(AMS_TEST_RESULT_DESCRIPTION_DEFINED, 0) + + return "Unknown"; + } + + } + + const char *GetResultName(int module, int description) { + #define AMS_TEST_RESULT_MODULE_DEFINED(n) if constexpr (::ams::result::impl::ResultNameSpaceExistsImpl<n>::Exists) { if (module == n) { return GetResultNameByModule<n>(description); } } + + AMS_INVOKE_MACRO_08(AMS_TEST_RESULT_MODULE_DEFINED, 0) + + return "Unknown"; + } + + #else + const char *GetResultName(int, int) { + return "Unknown"; + } + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_base_device_accessor.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_base_device_accessor.cpp new file mode 100644 index 00000000..75e8fe36 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_base_device_accessor.cpp @@ -0,0 +1,581 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#include "sdmmc_base_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + #if defined(AMS_SDMMC_THREAD_SAFE) + + #define AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX() std::scoped_lock lk(m_base_device->m_device_mutex) + + #else + + #define AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX() + + #endif + + void BaseDevice::GetLegacyCapacityParameters(u8 *out_c_size_mult, u8 *out_read_bl_len) const { + AMS_ABORT_UNLESS(out_c_size_mult != nullptr); + AMS_ABORT_UNLESS(out_read_bl_len != nullptr); + + /* Extract C_SIZE_MULT and READ_BL_LEN from the CSD. */ + *out_c_size_mult = static_cast<u8>((m_csd[2] >> 7) & 0x7); + *out_read_bl_len = static_cast<u8>((m_csd[4] >> 8) & 0xF); + } + + Result BaseDevice::SetLegacyMemoryCapacity() { + /* Get csize from the csd. */ + const u32 c_size = ((m_csd[3] >> 6) & 0x3FF) | ((m_csd[4] & 0x3) << 10); + + /* Get c_size_mult and read_bl_len. */ + u8 c_size_mult, read_bl_len; + this->GetLegacyCapacityParameters(std::addressof(c_size_mult), std::addressof(read_bl_len)); + + /* Validate the parameters. */ + R_UNLESS((read_bl_len + c_size_mult + 2) >= 9, sdmmc::ResultUnexpectedDeviceCsdValue()); + + /* Set memory capacity. */ + m_memory_capacity = (c_size + 1) << ((read_bl_len + c_size_mult + 2) - 9); + m_is_valid_memory_capacity = true; + + R_SUCCEED(); + } + + Result BaseDevice::CheckDeviceStatus(u32 r1_resp) const { + /* Check if there are any errors at all. */ + R_SUCCEED_IF((r1_resp & DeviceStatus_ErrorMask) == 0); + + /* Check errors individually. */ + #define AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(__ERROR__) R_UNLESS((r1_resp & DeviceStatus_##__ERROR__) == 0, sdmmc::ResultDeviceStatus##__ERROR__()) + + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(ComCrcError); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(DeviceEccFailed); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(CcError); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(Error); + + if (this->GetDeviceType() == DeviceType_Mmc) { + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(SwitchError); + } + + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(AddressMisaligned); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(BlockLenError); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(EraseSeqError); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(EraseParam); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(WpViolation); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(LockUnlockFailed); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(CidCsdOverwrite); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(WpEraseSkip); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(WpEraseSkip); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(EraseReset); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(IllegalCommand); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(AddressOutOfRange); + + #undef AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR + + R_SUCCEED(); + } + + DeviceState BaseDevice::GetDeviceState(u32 r1_resp) const { + return static_cast<DeviceState>((r1_resp & DeviceStatus_CurrentStateMask) >> DeviceStatus_CurrentStateShift); + } + + Result BaseDeviceAccessor::IssueCommandAndCheckR1(u32 *out_response, u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state, u32 status_ignore_mask) const { + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(command_index, command_arg, CommandResponseType, is_busy); + R_TRY(m_host_controller->IssueCommand(std::addressof(command))); + + /* Get the response. */ + AMS_ABORT_UNLESS(out_response != nullptr); + m_host_controller->GetLastResponse(out_response, sizeof(u32), CommandResponseType); + + /* Mask out the ignored status bits. */ + if (status_ignore_mask != 0) { + *out_response &= ~status_ignore_mask; + } + + /* Check the r1 response for errors. */ + AMS_ABORT_UNLESS(m_base_device != nullptr); + R_TRY(m_base_device->CheckDeviceStatus(*out_response)); + + /* Check the device state. */ + if (expected_state != DeviceState_Unknown) { + R_UNLESS(m_base_device->GetDeviceState(*out_response) == expected_state, sdmmc::ResultUnexpectedDeviceState()); + } + + R_SUCCEED(); + } + + Result BaseDeviceAccessor::IssueCommandGoIdleState() const { + /* Issue the command. */ + Command command(CommandIndex_GoIdleState, 0, ResponseType_R0, false); + R_RETURN(m_host_controller->IssueCommand(std::addressof(command))); + } + + Result BaseDeviceAccessor::IssueCommandAllSendCid(void *dst, size_t dst_size) const { + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R2; + Command command(CommandIndex_AllSendCid, 0, CommandResponseType, false); + R_TRY(m_host_controller->IssueCommand(std::addressof(command))); + + /* Copy the data out. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast<uintptr_t>(dst), alignof(u32))); + AMS_ABORT_UNLESS(dst_size >= DeviceCidSize); + m_host_controller->GetLastResponse(static_cast<u32 *>(dst), DeviceCidSize, CommandResponseType); + + R_SUCCEED(); + } + + Result BaseDeviceAccessor::IssueCommandSelectCard() const { + /* Get the command argument. */ + AMS_ABORT_UNLESS(m_base_device != nullptr); + const u32 arg = static_cast<u32>(m_base_device->GetRca()) << 16; + + /* Issue the command. */ + R_RETURN(this->IssueCommandAndCheckR1(CommandIndex_SelectCard, arg, true, DeviceState_Unknown)); + } + + Result BaseDeviceAccessor::IssueCommandSendCsd(void *dst, size_t dst_size) const { + /* Get the command argument. */ + AMS_ABORT_UNLESS(m_base_device != nullptr); + const u32 arg = static_cast<u32>(m_base_device->GetRca()) << 16; + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R2; + Command command(CommandIndex_SendCsd, arg, CommandResponseType, false); + R_TRY(m_host_controller->IssueCommand(std::addressof(command))); + + /* Copy the data out. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast<uintptr_t>(dst), alignof(u32))); + AMS_ABORT_UNLESS(dst_size >= DeviceCsdSize); + m_host_controller->GetLastResponse(static_cast<u32 *>(dst), DeviceCsdSize, CommandResponseType); + + R_SUCCEED(); + } + + Result BaseDeviceAccessor::IssueCommandSendStatus(u32 *out_device_status, u32 status_ignore_mask) const { + /* Get the command argument. */ + AMS_ABORT_UNLESS(m_base_device != nullptr); + const u32 arg = static_cast<u32>(m_base_device->GetRca()) << 16; + + /* Issue the command. */ + R_RETURN(this->IssueCommandAndCheckR1(out_device_status, CommandIndex_SendStatus, arg, false, DeviceState_Tran, status_ignore_mask)); + } + + Result BaseDeviceAccessor::IssueCommandSetBlockLenToSectorSize() const { + /* Issue the command. */ + R_RETURN(this->IssueCommandAndCheckR1(CommandIndex_SetBlockLen, SectorSize, false, DeviceState_Tran)); + } + + Result BaseDeviceAccessor::IssueCommandMultipleBlock(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const { + /* Get the argument. */ + AMS_ABORT_UNLESS(m_base_device != nullptr); + const u32 arg = m_base_device->IsHighCapacity() ? sector_index : sector_index * SectorSize; + + /* Get the command index and transfer direction. */ + const u32 command_index = is_read ? CommandIndex_ReadMultipleBlock : CommandIndex_WriteMultipleBlock; + const auto xfer_direction = is_read ? TransferDirection_ReadFromDevice : TransferDirection_WriteToDevice; + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(command_index, arg, CommandResponseType, false); + TransferData xfer_data(buf, SectorSize, num_sectors, xfer_direction, true, true); + Result result = m_host_controller->IssueCommand(std::addressof(command), std::addressof(xfer_data), out_num_transferred_blocks); + + /* Handle the failure case. */ + if (R_FAILED(result)) { + /* Check if we were removed. */ + R_TRY(this->CheckRemoved()); + + /* By default, we'll want to return the result we just got. */ + Result result_to_return = result; + + /* Stop transmission. */ + u32 resp = 0; + result = m_host_controller->IssueStopTransmissionCommand(std::addressof(resp)); + if (R_SUCCEEDED(result)) { + result = m_base_device->CheckDeviceStatus(resp & (~DeviceStatus_IllegalCommand)); + if (R_FAILED(result)) { + result_to_return = result; + } + } + + /* Check if we were removed. */ + R_TRY(this->CheckRemoved()); + + /* Get the device status. */ + u32 device_status = 0; + result = this->IssueCommandSendStatus(std::addressof(device_status), DeviceStatus_IllegalCommand); + + /* If there's a device status error we don't already have, we prefer to return it. */ + if (!sdmmc::ResultDeviceStatusHasError::Includes(result_to_return) && sdmmc::ResultDeviceStatusHasError::Includes(result)) { + result_to_return = result; + } + + /* Return the result we chose. */ + R_RETURN(result_to_return); + } + + /* Get the responses. */ + u32 resp, st_resp; + m_host_controller->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + m_host_controller->GetLastStopTransmissionResponse(std::addressof(st_resp), sizeof(st_resp)); + + /* Check the device status. */ + R_TRY(m_base_device->CheckDeviceStatus(resp)); + + /* Decide on what errors to ignore. */ + u32 status_ignore_mask = 0; + if (is_read) { + AMS_ABORT_UNLESS(out_num_transferred_blocks != nullptr); + if ((*out_num_transferred_blocks + sector_index) == m_base_device->GetMemoryCapacity()) { + status_ignore_mask = DeviceStatus_AddressOutOfRange; + } + } + + /* Check the device status. */ + R_TRY(m_base_device->CheckDeviceStatus(st_resp & ~status_ignore_mask)); + + R_SUCCEED(); + } + + Result BaseDeviceAccessor::ReadWriteSingle(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const { + /* Issue the read/write command. */ + AMS_ABORT_UNLESS(out_num_transferred_blocks != nullptr); + R_TRY(this->IssueCommandMultipleBlock(out_num_transferred_blocks, sector_index, num_sectors, buf, is_read)); + + /* Decide on what errors to ignore. */ + u32 status_ignore_mask = 0; + if (is_read) { + AMS_ABORT_UNLESS(m_base_device != nullptr); + if ((*out_num_transferred_blocks + sector_index) == m_base_device->GetMemoryCapacity()) { + status_ignore_mask = DeviceStatus_AddressOutOfRange; + } + } + + /* Get and check the status. */ + u32 device_status; + R_TRY(this->IssueCommandSendStatus(std::addressof(device_status), status_ignore_mask)); + + R_SUCCEED(); + } + + Result BaseDeviceAccessor::ReadWriteMultiple(u32 sector_index, u32 num_sectors, u32 sector_index_alignment, void *buf, size_t buf_size, bool is_read) { + /* Verify that we can send the command. */ + AMS_ABORT_UNLESS(m_base_device != nullptr); + + /* If we want to read zero sectors, there's no work for us to do. */ + R_SUCCEED_IF(num_sectors == 0); + + /* Check that the buffer is big enough for the sectors we're reading. */ + AMS_ABORT_UNLESS((buf_size / SectorSize) >= num_sectors); + + /* Read sectors repeatedly until we've read all the ones we want. */ + u32 cur_sector_index = sector_index; + u32 remaining_sectors = num_sectors; + u8 *cur_buf = static_cast<u8 *>(buf); + while (remaining_sectors > 0) { + /* Determine how many sectors we can read in this iteration. */ + u32 cur_sectors = remaining_sectors; + if (sector_index_alignment > 0) { + AMS_ABORT_UNLESS((cur_sector_index % sector_index_alignment) == 0); + + const u32 max_sectors = m_host_controller->GetMaxTransferNumBlocks(); + if (remaining_sectors > max_sectors) { + cur_sectors = max_sectors - (max_sectors % sector_index_alignment); + } + } + + /* Try to perform the read/write. */ + u32 num_transferred_blocks = 0; + Result result = this->ReadWriteSingle(std::addressof(num_transferred_blocks), cur_sector_index, cur_sectors, cur_buf, is_read); + if (R_FAILED(result)) { + /* Check if we were removed. */ + R_TRY(this->CheckRemoved()); + + /* Log that we failed to read/write. */ + this->PushErrorLog(false, "%s %X %X:%X", is_read ? "R" : "W", cur_sector_index, cur_sectors, result.GetValue()); + + /* Retry the read/write. */ + num_transferred_blocks = 0; + result = this->ReadWriteSingle(std::addressof(num_transferred_blocks), cur_sector_index, cur_sectors, cur_buf, is_read); + if (R_FAILED(result)) { + /* Check if we were removed. */ + R_TRY(this->CheckRemoved()); + + /* Log that we failed to read/write. */ + this->PushErrorLog(false, "%s %X %X:%X", is_read ? "R" : "W", cur_sector_index, cur_sectors, result.GetValue()); + + /* Re-startup the connection, to see if that helps. */ + R_TRY(this->ReStartup()); + + /* Retry the read/write a third time. */ + num_transferred_blocks = 0; + result = this->ReadWriteSingle(std::addressof(num_transferred_blocks), cur_sector_index, cur_sectors, cur_buf, is_read); + if (R_FAILED(result)) { + /* Log that we failed after a re-startup. */ + this->PushErrorLog(true, "%s %X %X:%X", is_read ? "R" : "W", cur_sector_index, cur_sectors, result.GetValue()); + R_RETURN(result); + } + + /* Log that we succeeded after a retry. */ + this->PushErrorLog(true, "%s %X %X:0", is_read ? "R" : "W", cur_sector_index, cur_sectors); + + /* Increment the number of error corrections we've done. */ + ++m_num_read_write_error_corrections; + } + } + + /* Update our tracking variables. */ + AMS_ABORT_UNLESS(remaining_sectors >= num_transferred_blocks); + remaining_sectors -= num_transferred_blocks; + cur_sector_index += num_transferred_blocks; + cur_buf += num_transferred_blocks * SectorSize; + } + + R_SUCCEED(); + } + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void BaseDeviceAccessor::RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(m_base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Register the address. */ + return m_host_controller->RegisterDeviceVirtualAddress(buffer, buffer_size, buffer_device_virtual_address); + } + + void BaseDeviceAccessor::UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(m_base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Register the address. */ + return m_host_controller->UnregisterDeviceVirtualAddress(buffer, buffer_size, buffer_device_virtual_address); + } + #endif + + Result BaseDeviceAccessor::Activate() { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(m_base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is awake. */ + R_UNLESS(m_base_device->IsAwake(), sdmmc::ResultNotAwakened()); + + /* If the device is already active, we don't need to do anything. */ + R_SUCCEED_IF(m_base_device->IsActive()); + + /* Activate the base device. */ + auto activate_guard = SCOPE_GUARD { ++m_num_activation_failures; }; + R_TRY(this->OnActivate()); + + /* We successfully activated the device. */ + activate_guard.Cancel(); + m_base_device->SetActive(); + + R_SUCCEED(); + } + + void BaseDeviceAccessor::Deactivate() { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(m_base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Deactivate the base device. */ + if (m_base_device->IsActive()) { + m_host_controller->Shutdown(); + m_base_device->Deactivate(); + } + } + + Result BaseDeviceAccessor::ReadWrite(u32 sector_index, u32 num_sectors, void *buffer, size_t buffer_size, bool is_read) { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(m_base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(m_base_device->CheckAccessible()); + + /* Perform the read/write. */ + auto rw_guard = SCOPE_GUARD { ++m_num_read_write_failures; }; + R_TRY(this->OnReadWrite(sector_index, num_sectors, buffer, buffer_size, is_read)); + + /* We successfully performed the read/write. */ + rw_guard.Cancel(); + R_SUCCEED(); + } + + Result BaseDeviceAccessor::CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width) { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(m_base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(m_base_device->CheckAccessible()); + + /* Get the current speed mode/bus width. */ + *out_speed_mode = m_host_controller->GetSpeedMode(); + *out_bus_width = m_host_controller->GetBusWidth(); + + /* Verify that we can get the status. */ + R_TRY(m_host_controller->GetInternalStatus()); + + R_SUCCEED(); + } + + Result BaseDeviceAccessor::GetMemoryCapacity(u32 *out_sectors) const { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(m_base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(m_base_device->CheckAccessible()); + + /* Get the capacity. */ + AMS_ABORT_UNLESS(out_sectors != nullptr); + *out_sectors = m_base_device->GetMemoryCapacity(); + + R_SUCCEED(); + } + + Result BaseDeviceAccessor::GetDeviceStatus(u32 *out) const { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(m_base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(m_base_device->CheckAccessible()); + + /* Get the status. */ + R_TRY(this->IssueCommandSendStatus(out, 0)); + + R_SUCCEED(); + } + + Result BaseDeviceAccessor::GetOcr(u32 *out) const { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(m_base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(m_base_device->CheckAccessible()); + + /* Get the ocr. */ + AMS_ABORT_UNLESS(out != nullptr); + *out = m_base_device->GetOcr(); + + R_SUCCEED(); + } + + Result BaseDeviceAccessor::GetRca(u16 *out) const { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(m_base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(m_base_device->CheckAccessible()); + + /* Get the rca. */ + AMS_ABORT_UNLESS(out != nullptr); + *out = m_base_device->GetRca(); + + R_SUCCEED(); + } + + Result BaseDeviceAccessor::GetCid(void *out, size_t size) const { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(m_base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(m_base_device->CheckAccessible()); + + /* Get the cid. */ + m_base_device->GetCid(out, size); + + R_SUCCEED(); + } + + Result BaseDeviceAccessor::GetCsd(void *out, size_t size) const { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(m_base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(m_base_device->CheckAccessible()); + + /* Get the csd. */ + m_base_device->GetCsd(out, size); + + R_SUCCEED(); + } + + void BaseDeviceAccessor::GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size) { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(m_base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Set the output error info. */ + AMS_ABORT_UNLESS(out_error_info != nullptr); + out_error_info->num_activation_failures = m_num_activation_failures; + out_error_info->num_activation_error_corrections = m_num_activation_error_corrections; + out_error_info->num_read_write_failures = m_num_read_write_failures; + out_error_info->num_read_write_error_corrections = m_num_read_write_error_corrections; + this->ClearErrorInfo(); + + /* Check if we should write logs. */ + if (out_log_size == nullptr) { + return; + } + + /* Check if we can write logs. */ + if (out_log_buffer == nullptr || log_buffer_size == 0) { + *out_log_size = 0; + return; + } + + /* Get and clear our logs. */ + #if defined(AMS_SDMMC_USE_LOGGER) + { + if (m_error_logger.HasLog()) { + this->PushErrorTimeStamp(); + + *out_log_size = m_error_logger.GetAndClearLogs(out_log_buffer, log_buffer_size); + } else { + *out_log_size = 0; + } + } + #else + { + *out_log_size = 0; + } + #endif + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_base_device_accessor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_base_device_accessor.hpp new file mode 100644 index 00000000..be263739 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_base_device_accessor.hpp @@ -0,0 +1,512 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include "sdmmc_i_host_controller.hpp" +#include "sdmmc_i_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + #if defined(AMS_SDMMC_USE_LOGGER) + class Logger { + private: + static constexpr size_t LogLengthMax = 0x20; + static constexpr size_t LogCountMax = 0x10; + private: + char m_logs[LogCountMax][LogLengthMax]; + int m_log_index; + private: + void Clear() { + for (size_t i = 0; i < LogCountMax; ++i) { + m_logs[i][0] = '\0'; + } + m_log_index = 0; + } + + size_t Pop(char *dst, size_t dst_size) { + /* Decrease log index. */ + if ((--m_log_index) < 0) { + m_log_index = LogCountMax - 1; + } + + /* Check if we have a log. */ + if (m_logs[m_log_index][0] == '\0') { + return 0; + } + + /* Copy log to output. */ + const int len = ::ams::util::Strlcpy(dst, m_logs[m_log_index], dst_size); + + /* Clear the log we copied. */ + m_logs[m_log_index][0] = '\0'; + + return static_cast<size_t>(len); + } + + public: + Logger() { + this->Clear(); + } + + void Push(const char *fmt, std::va_list vl) { + /* Format the log into the current buffer. */ + ::ams::util::TVSNPrintf(m_logs[m_log_index], LogLengthMax, fmt, vl); + + /* Update our log index. */ + if ((++m_log_index) >= static_cast<int>(LogCountMax)) { + m_log_index = 0; + } + } + + void Push(const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + this->Push(fmt, vl); + va_end(vl); + } + + bool HasLog() const { + const int index = m_log_index > 0 ? m_log_index - 1 : static_cast<int>(LogCountMax - 1); + return m_logs[index][0] != '\0'; + } + + size_t GetAndClearLogs(char *dst, size_t dst_size) { + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size > 0); + + /* Pop logs until we run out of them. */ + size_t total_len = 0; + while (true) { + /* Pop the current log. */ + const size_t cur_len = this->Pop(dst + total_len, dst_size - total_len); + if (cur_len == 0) { + break; + } + + /* Check if the log exceeded the buffer size. */ + if (total_len + cur_len + 1 >= dst_size) { + break; + } + + /* Advance the total length. */ + total_len += cur_len; + + /* Check if there's space for our separator. */ + if (total_len + 3 >= dst_size) { + break; + } + + dst[total_len + 0] = ','; + dst[total_len + 1] = ' '; + total_len += 2; + } + + /* Ensure that the complete log fits in the buffer. */ + if (total_len >= dst_size) { + total_len = dst_size - 1; + } + + /* Ensure null termination. */ + dst[total_len] = '\0'; + + /* Clear any remaining logs. */ + this->Clear(); + + /* Return the length of the logs, including null terminator. */ + return total_len + 1; + } + }; + #endif + + enum DeviceType { + DeviceType_Mmc = 0, + DeviceType_SdCard = 1, + DeviceType_GcAsic = 2, + }; + + enum DeviceState { + DeviceState_Idle = 0, + DeviceState_Ready = 1, + DeviceState_Ident = 2, + DeviceState_Stby = 3, + DeviceState_Tran = 4, + DeviceState_Data = 5, + DeviceState_Rcv = 6, + DeviceState_Prg = 7, + DeviceState_Dis = 8, + DeviceState_Rsvd0 = 9, + DeviceState_Rsvd1 = 10, + DeviceState_Rsvd2 = 11, + DeviceState_Rsvd3 = 12, + DeviceState_Rsvd4 = 13, + DeviceState_Rsvd5 = 14, + DeviceState_RsvdIoMode = 15, + DeviceState_Unknown = 16, + }; + + enum DeviceStatus : u32 { + DeviceStatus_AkeSeqError = (1u << 3), + DeviceStatus_AppCmd = (1u << 5), + DeviceStatus_SwitchError = (1u << 7), + DeviceStatus_EraseReset = (1u << 13), + DeviceStatus_WpEraseSkip = (1u << 15), + DeviceStatus_CidCsdOverwrite = (1u << 16), + DeviceStatus_Error = (1u << 19), + DeviceStatus_CcError = (1u << 20), + DeviceStatus_DeviceEccFailed = (1u << 21), + DeviceStatus_IllegalCommand = (1u << 22), + DeviceStatus_ComCrcError = (1u << 23), + DeviceStatus_LockUnlockFailed = (1u << 24), + DeviceStatus_WpViolation = (1u << 26), + DeviceStatus_EraseParam = (1u << 27), + DeviceStatus_EraseSeqError = (1u << 28), + DeviceStatus_BlockLenError = (1u << 29), + DeviceStatus_AddressMisaligned = (1u << 30), + DeviceStatus_AddressOutOfRange = (1u << 31), + + DeviceStatus_CurrentStateShift = 9, + DeviceStatus_CurrentStateMask = (0b1111u << DeviceStatus_CurrentStateShift), + + DeviceStatus_ErrorMask = (DeviceStatus_SwitchError | + DeviceStatus_EraseReset | + DeviceStatus_WpEraseSkip | + DeviceStatus_CidCsdOverwrite | + DeviceStatus_Error | + DeviceStatus_CcError | + DeviceStatus_DeviceEccFailed | + DeviceStatus_IllegalCommand | + DeviceStatus_ComCrcError | + DeviceStatus_LockUnlockFailed | + DeviceStatus_WpViolation | + DeviceStatus_EraseParam | + DeviceStatus_EraseSeqError | + DeviceStatus_BlockLenError | + DeviceStatus_AddressMisaligned | + DeviceStatus_AddressOutOfRange), + }; + + class BaseDevice { + private: + u32 m_ocr; + u8 m_cid[DeviceCidSize]; + u16 m_csd[DeviceCsdSize / sizeof(u16)]; + u32 m_memory_capacity; + bool m_is_high_capacity; + bool m_is_valid_ocr; + bool m_is_valid_cid; + bool m_is_valid_csd; + bool m_is_valid_high_capacity; + bool m_is_valid_memory_capacity; + bool m_is_active; + bool m_is_awake; + public: + #if defined(AMS_SDMMC_THREAD_SAFE) + mutable os::SdkRecursiveMutex m_device_mutex; + public: + BaseDevice() : m_device_mutex() + #else + BaseDevice() + #endif + { + m_is_awake = true; + m_ocr = 0; + m_memory_capacity = 0; + m_is_high_capacity = false; + this->OnDeactivate(); + } + + void OnDeactivate() { + m_is_active = false; + m_is_valid_ocr = false; + m_is_valid_cid = false; + m_is_valid_csd = false; + m_is_valid_high_capacity = false; + m_is_valid_memory_capacity = false; + } + + bool IsAwake() const { + return m_is_awake; + } + + void Awaken() { + m_is_awake = true; + } + + void PutToSleep() { + m_is_awake = false; + } + + bool IsActive() const { + return m_is_active; + } + + void SetActive() { + m_is_active = true; + } + + virtual void Deactivate() { + this->OnDeactivate(); + } + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual os::EventType *GetRemovedEvent() const = 0; + #endif + + virtual DeviceType GetDeviceType() const = 0; + virtual u16 GetRca() const = 0; + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + void InitializeRemovedEvent() { + if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { + os::InitializeEvent(removed_event, false, os::EventClearMode_ManualClear); + } + } + + void FinalizeRemovedEvent() { + if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { + os::FinalizeEvent(removed_event); + } + } + + void SignalRemovedEvent() { + if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { + os::SignalEvent(removed_event); + } + } + + void ClearRemovedEvent() { + if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { + os::ClearEvent(removed_event); + } + } + + bool IsRemoved() const { + if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { + return os::TryWaitEvent(removed_event); + } + return false; + } + #endif + + Result CheckRemoved() const { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + R_UNLESS(!this->IsRemoved(), sdmmc::ResultDeviceRemoved()); + #endif + + R_SUCCEED(); + } + + Result CheckAccessible() const { + /* Check awake. */ + R_UNLESS(this->IsAwake(), sdmmc::ResultNotAwakened()); + + /* Check active. */ + R_UNLESS(this->IsActive(), sdmmc::ResultNotActivated()); + + /* Check removed. */ + R_TRY(this->CheckRemoved()); + + R_SUCCEED(); + } + + void SetHighCapacity(bool en) { + m_is_high_capacity = en; + m_is_valid_high_capacity = true; + } + + bool IsHighCapacity() const { + AMS_ABORT_UNLESS(m_is_valid_high_capacity); + return m_is_high_capacity; + } + + void SetOcr(u32 o) { + m_ocr = o; + m_is_valid_ocr = true; + } + + u32 GetOcr() const { + AMS_ABORT_UNLESS(m_is_valid_ocr); + return m_ocr; + } + + void SetCid(const void *src, size_t src_size) { + AMS_ABORT_UNLESS(src != nullptr); + AMS_ABORT_UNLESS(src_size >= DeviceCidSize); + std::memcpy(m_cid, src, DeviceCidSize); + m_is_valid_cid = true; + } + + void GetCid(void *dst, size_t dst_size) const { + AMS_ABORT_UNLESS(m_is_valid_cid); + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= DeviceCidSize); + std::memcpy(dst, m_cid, DeviceCidSize); + } + + void SetCsd(const void *src, size_t src_size) { + AMS_ABORT_UNLESS(src != nullptr); + AMS_ABORT_UNLESS(src_size >= DeviceCsdSize); + std::memcpy(m_csd, src, DeviceCsdSize); + m_is_valid_csd = true; + } + + void GetCsd(void *dst, size_t dst_size) const { + AMS_ABORT_UNLESS(m_is_valid_csd); + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= DeviceCsdSize); + std::memcpy(dst, m_csd, DeviceCsdSize); + } + + void SetMemoryCapacity(u32 num_sectors) { + m_memory_capacity = num_sectors; + m_is_valid_memory_capacity = true; + } + + u32 GetMemoryCapacity() const { + AMS_ABORT_UNLESS(m_is_valid_memory_capacity); + return m_memory_capacity; + } + + void GetLegacyCapacityParameters(u8 *out_c_size_mult, u8 *out_read_bl_len) const; + Result SetLegacyMemoryCapacity(); + + Result CheckDeviceStatus(u32 r1_resp) const; + DeviceState GetDeviceState(u32 r1_resp) const; + }; + + class BaseDeviceAccessor : public IDeviceAccessor { + private: + IHostController *m_host_controller; + BaseDevice *m_base_device; + u32 m_num_activation_failures; + u32 m_num_activation_error_corrections; + u32 m_num_read_write_failures; + u32 m_num_read_write_error_corrections; + #if defined(AMS_SDMMC_USE_LOGGER) + Logger m_error_logger; + #endif + private: + void ClearErrorInfo() { + m_num_activation_failures = 0; + m_num_activation_error_corrections = 0; + m_num_read_write_failures = 0; + m_num_read_write_error_corrections = 0; + } + protected: + explicit BaseDeviceAccessor(IHostController *hc) : m_host_controller(hc), m_base_device(nullptr) { + this->ClearErrorInfo(); + } + + IHostController *GetHostController() const { + return m_host_controller; + } + + void SetDevice(BaseDevice *bd) { + m_base_device = bd; + } + + Result CheckRemoved() const { + R_RETURN(m_base_device->CheckRemoved()); + } + + Result IssueCommandAndCheckR1(u32 *out_response, u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state, u32 status_ignore_mask) const; + + Result IssueCommandAndCheckR1(u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state, u32 status_ignore_mask) const { + u32 dummy; + R_RETURN(this->IssueCommandAndCheckR1(std::addressof(dummy), command_index, command_arg, is_busy, expected_state, status_ignore_mask)); + } + + Result IssueCommandAndCheckR1(u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state) const { + R_RETURN(this->IssueCommandAndCheckR1(command_index, command_arg, is_busy, expected_state, 0)); + } + + Result IssueCommandGoIdleState() const; + Result IssueCommandAllSendCid(void *dst, size_t dst_size) const; + Result IssueCommandSelectCard() const; + Result IssueCommandSendCsd(void *dst, size_t dst_size) const; + Result IssueCommandSendStatus(u32 *out_device_status, u32 status_ignore_mask) const; + + Result IssueCommandSendStatus(u32 status_ignore_mask) const { + u32 dummy; + R_RETURN(this->IssueCommandSendStatus(std::addressof(dummy), status_ignore_mask)); + } + + Result IssueCommandSendStatus() const { + R_RETURN(this->IssueCommandSendStatus(0)); + } + + Result IssueCommandSetBlockLenToSectorSize() const; + Result IssueCommandMultipleBlock(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const; + Result ReadWriteSingle(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const; + Result ReadWriteMultiple(u32 sector_index, u32 num_sectors, u32 sector_index_alignment, void *buf, size_t buf_size, bool is_read); + + void IncrementNumActivationErrorCorrections() { + ++m_num_activation_error_corrections; + } + + void PushErrorTimeStamp() { + #if defined(AMS_SDMMC_USE_LOGGER) + { + m_error_logger.Push("%u", static_cast<u32>(os::ConvertToTimeSpan(os::GetSystemTick()).GetSeconds())); + } + #endif + } + + void PushErrorLog(bool with_timestamp, const char *fmt, ...) { + #if defined(AMS_SDMMC_USE_LOGGER) + { + std::va_list vl; + va_start(vl, fmt); + m_error_logger.Push(fmt, vl); + va_end(vl); + + if (with_timestamp) { + this->PushErrorTimeStamp(); + } + } + #else + { + AMS_UNUSED(with_timestamp, fmt); + } + #endif + } + + virtual Result OnActivate() = 0; + virtual Result OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) = 0; + virtual Result ReStartup() = 0; + public: + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + virtual void RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override; + virtual void UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override; + #endif + + virtual Result Activate() override; + virtual void Deactivate() override; + + virtual Result ReadWrite(u32 sector_index, u32 num_sectors, void *buffer, size_t buffer_size, bool is_read) override; + virtual Result CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width) override; + + virtual Result GetMemoryCapacity(u32 *out_sectors) const override; + virtual Result GetDeviceStatus(u32 *out) const override; + virtual Result GetOcr(u32 *out) const override; + virtual Result GetRca(u16 *out) const override; + virtual Result GetCid(void *out, size_t size) const override; + virtual Result GetCsd(void *out, size_t size) const override; + + virtual void GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size) override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.board.nintendo_nx.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.board.nintendo_nx.cpp new file mode 100644 index 00000000..979799d8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.board.nintendo_nx.cpp @@ -0,0 +1,151 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#include "sdmmc_clock_reset_controller.hpp" +#include "sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp" +#include "sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp" + +namespace ams::sdmmc::impl::ClockResetController { + + namespace { + + constinit bool g_is_module_initialized[Module_Count] = {}; + + #if defined(AMS_SDMMC_THREAD_SAFE) + + constinit os::SdkMutex g_module_mutex; + + #define AMS_SDMMC_LOCK_MODULE_MUTEX() std::scoped_lock lk(g_module_mutex) + + #else + + #define AMS_SDMMC_LOCK_MODULE_MUTEX() + + #endif + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + constinit bool g_is_pcv_control = false; + + #define AMS_SDMMC_IF_IS_PCV_CONTROL() if (g_is_pcv_control) + + #else + + #define AMS_SDMMC_IF_IS_PCV_CONTROL() if constexpr (false) + + #endif + + } + + void Initialize(Module module) { + /* Acquire exclusive access to module state. */ + AMS_SDMMC_LOCK_MODULE_MUTEX(); + + /* Mark the module as initialized. */ + g_is_module_initialized[module] = true; + + /* Initialize the module. */ + AMS_SDMMC_IF_IS_PCV_CONTROL() { + ClockResetController::pcv::Initialize(module); + } else { + ClockResetController::reg::Initialize(module); + } + } + + void Finalize(Module module) { + /* Acquire exclusive access to module state. */ + AMS_SDMMC_LOCK_MODULE_MUTEX(); + + /* Finalize the module. */ + AMS_SDMMC_IF_IS_PCV_CONTROL() { + ClockResetController::pcv::Finalize(module); + } else { + ClockResetController::reg::Finalize(module); + } + + /* Mark the module as finalized. */ + g_is_module_initialized[module] = false; + } + + bool IsAvailable(Module module) { + AMS_SDMMC_IF_IS_PCV_CONTROL() { + return ClockResetController::pcv::IsAvailable(module); + } else { + return ClockResetController::reg::IsAvailable(module); + } + } + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + void SwitchToPcvControl() { + /* Acquire exclusive access to module state. */ + AMS_SDMMC_LOCK_MODULE_MUTEX(); + + /* If we're already using pcv control, we don't need to do anything. */ + AMS_SDMMC_IF_IS_PCV_CONTROL() { + return; + } + + /* Finalize all modules. */ + for (int i = 0; i < Module_Count; ++i) { + if (g_is_module_initialized[i]) { + ClockResetController::reg::Finalize(static_cast<Module>(i)); + } + } + + /* Mark that we've switched to pcv control. */ + + /* Initialize modules using pcv control. */ + for (int i = 0; i < Module_Count; ++i) { + if (g_is_module_initialized[i]) { + ClockResetController::pcv::Initialize(static_cast<Module>(i)); + } + } + } + #endif + + void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency) { + AMS_SDMMC_IF_IS_PCV_CONTROL() { + return ClockResetController::pcv::SetClockFrequencyKHz(out_actual_frequency, module, target_frequency); + } else { + return ClockResetController::reg::SetClockFrequencyKHz(out_actual_frequency, module, target_frequency); + } + } + + void AssertReset(Module module) { + AMS_SDMMC_IF_IS_PCV_CONTROL() { + return ClockResetController::pcv::AssertReset(module); + } else { + return ClockResetController::reg::AssertReset(module); + } + } + + void ReleaseReset(Module module, u32 target_frequency_khz) { + AMS_SDMMC_IF_IS_PCV_CONTROL() { + return ClockResetController::pcv::ReleaseReset(module, target_frequency_khz); + } else { + return ClockResetController::reg::ReleaseReset(module, target_frequency_khz); + } + } + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.hpp new file mode 100644 index 00000000..a2a78869 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::sdmmc::impl::ClockResetController { + + enum Module { + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + Module_Sdmmc1 = 0, + Module_Sdmmc2 = 1, + Module_Sdmmc3 = 2, + Module_Sdmmc4 = 3, + #endif + + Module_Count, + }; + + void Initialize(Module module); + void Finalize(Module module); + bool IsAvailable(Module module); + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + void SwitchToPcvControl(); + #endif + + void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency); + void AssertReset(Module module); + void ReleaseReset(Module module, u32 target_frequency_khz); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.cpp new file mode 100644 index 00000000..6eeaf006 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.cpp @@ -0,0 +1,95 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#include "sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp" + +namespace ams::sdmmc::impl::ClockResetController::pcv { + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + + void Initialize(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not implemented"); + } + + void Finalize(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not implemented"); + } + + bool IsAvailable(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not implemented"); + } + + void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency) { + AMS_UNUSED(out_actual_frequency, module, target_frequency); + AMS_ABORT("PCV Control not implemented"); + } + + void AssertReset(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not implemented"); + } + + void ReleaseReset(Module module, u32 target_frequency_khz) { + AMS_UNUSED(module, target_frequency_khz); + AMS_ABORT("PCV Control not implemented"); + } + + #else + + void Initialize(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not supported"); + } + + void Finalize(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not supported"); + } + + bool IsAvailable(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not supported"); + } + + void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency) { + AMS_UNUSED(out_actual_frequency, module, target_frequency); + AMS_ABORT("PCV Control not supported"); + } + + void AssertReset(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not supported"); + } + + void ReleaseReset(Module module, u32 target_frequency_khz) { + AMS_UNUSED(module, target_frequency_khz); + AMS_ABORT("PCV Control not supported"); + } + + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp new file mode 100644 index 00000000..bd383872 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include "sdmmc_clock_reset_controller.hpp" + +namespace ams::sdmmc::impl::ClockResetController::pcv { + + void Initialize(Module module); + void Finalize(Module module); + bool IsAvailable(Module module); + + void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency); + void AssertReset(Module module); + void ReleaseReset(Module module, u32 target_frequency_khz); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.cpp new file mode 100644 index 00000000..3bf17eab --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.cpp @@ -0,0 +1,391 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#include "sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp" +#include "sdmmc_timer.hpp" + +namespace ams::sdmmc::impl::ClockResetController::reg { + + namespace { + + #if defined(AMS_SDMMC_THREAD_SAFE) + + constinit os::SdkMutex g_init_mutex; + + #define AMS_SDMMC_LOCK_INIT_MUTEX() std::scoped_lock lk(g_init_mutex) + + #else + + #define AMS_SDMMC_LOCK_INIT_MUTEX() + + #endif + + constinit bool g_is_initialized = false; + + struct ModuleInfo { + u32 target_frequency_khz; + u32 actual_frequency_khz; + }; + + constinit ModuleInfo g_module_infos[Module_Count] = {}; + + constexpr inline dd::PhysicalAddress ClockResetControllerRegistersPhysicalAddress = UINT64_C(0x60006000); + constexpr inline size_t ClockResetControllerRegistersSize = 4_KB; + + constinit uintptr_t g_clkrst_registers_address = 0; + + [[maybe_unused]] void InitializePllc4() { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Check if PLLC4_BASE has the expected value; if it does, we have nothing to do. */ + constexpr u32 ExpectedPllc4Base = 0x58006804; + if (ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE) == ExpectedPllc4Base) { + return; + } + + /* Disable PLLC4_ENABLE, if it's currently set. */ + if (ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_ENABLE, ENABLE))) { + ams::reg::ReadWrite(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_ENABLE, DISABLE)); + } + + /* Operate on the register with PLLC4_ENABLE cleared. */ + { + /* Clear IDDQ, read to be sure it takes, wait 5 us. */ + ams::reg::ReadWrite(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_IDDQ, OFF)); + ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE); + WaitMicroSeconds(5); + + /* Write the expected value sans IDDQ/PLLC4_ENABLE. */ + constexpr u32 ExpectedPllc4BaseMask = ~ams::reg::EncodeMask(CLK_RST_REG_BITS_MASK(PLLC4_BASE_PLLC4_ENABLE), + CLK_RST_REG_BITS_MASK(PLLC4_BASE_PLLC4_IDDQ)); + ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, ExpectedPllc4Base & ExpectedPllc4BaseMask); + } + + /* Write PLLC4_ENABLE, and read to be sure our configuration takes. */ + ams::reg::ReadWrite(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_ENABLE, ENABLE)); + ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE); + + /* Wait up to 1s for changes to take. */ + { + ManualTimer timer(1000); + while (true) { + /* Check if we're done. */ + if (!ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_LOCK, NOT_LOCK))) { + break; + } + + /* Check that we haven't timed out. */ + AMS_ABORT_UNLESS(timer.Update()); + } + } + } + + void InitializeLegacyTmClk() { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Configure the legacy tm clock as 12MHz. */ + ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC_LEGACY_TM, CLK_RST_REG_BITS_ENUM (CLK_SOURCE_LEGACY_TM_CLK_SRC, PLLP_OUT0), + CLK_RST_REG_BITS_VALUE(CLK_SOURCE_LEGACY_TM_CLK_DIVISOR, 66)); + + /* Enable clock to the legacy tm. */ + ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_Y_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_Y_CLK_ENB_LEGACY_TM, ENABLE)); + } + + bool IsResetReleased(Module module) { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Get the reset bit from RST_DEVICES_* */ + switch (module) { + case Module_Sdmmc1: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_REG_BITS_ENUM(RST_DEVICES_L_SWR_SDMMC1_RST, DISABLE)); + case Module_Sdmmc2: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_REG_BITS_ENUM(RST_DEVICES_L_SWR_SDMMC2_RST, DISABLE)); + case Module_Sdmmc3: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_U, CLK_RST_REG_BITS_ENUM(RST_DEVICES_U_SWR_SDMMC3_RST, DISABLE)); + case Module_Sdmmc4: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_REG_BITS_ENUM(RST_DEVICES_L_SWR_SDMMC4_RST, DISABLE)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void SetReset(Module module) { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Set reset in RST_DEV_*_SET */ + switch (module) { + case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC1_RST, ENABLE)); + case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC2_RST, ENABLE)); + case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_U_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_U_SDMMC3_RST, ENABLE)); + case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC4_RST, ENABLE)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void ClearReset(Module module) { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Set reset in RST_DEV_*_CLR */ + switch (module) { + case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC1_RST, ENABLE)); + case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC2_RST, ENABLE)); + case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_U_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_U_SDMMC3_RST, ENABLE)); + case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC4_RST, ENABLE)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + bool IsClockEnabled(Module module) { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Get the enable bit from CLK_OUT_ENB_* */ + switch (module) { + case Module_Sdmmc1: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_L_CLK_ENB_SDMMC1, ENABLE)); + case Module_Sdmmc2: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_L_CLK_ENB_SDMMC2, ENABLE)); + case Module_Sdmmc3: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_U, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_U_CLK_ENB_SDMMC3, ENABLE)); + case Module_Sdmmc4: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_L_CLK_ENB_SDMMC4, ENABLE)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void SetClockEnable(Module module) { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Set clock enable bit in CLK_ENB_*_SET */ + switch (module) { + case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC1, ENABLE)); + case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC2, ENABLE)); + case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_U_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_U_CLK_ENB_SDMMC3, ENABLE)); + case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC4, ENABLE)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void ClearClockEnable(Module module) { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Set clock enable bit in CLK_ENB_*_CLR */ + switch (module) { + case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC1, ENABLE)); + case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC2, ENABLE)); + case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_U_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_U_CLK_ENB_SDMMC3, ENABLE)); + case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC4, ENABLE)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void SetClockSourceSdmmc(u32 *out_actual_frequency_khz, Module module, u32 target_frequency_khz) { + /* Check that we can write to output. */ + AMS_ABORT_UNLESS(out_actual_frequency_khz != nullptr); + + /* Determine frequency/divisor. */ + u32 clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMCX_SDMMCX_CLK_SRC, PLLP_OUT0)); + u8 n; + switch (target_frequency_khz) { + case 25'000: + *out_actual_frequency_khz = 24'728; + n = 31; + break; + case 26'000: + *out_actual_frequency_khz = 25'500; + n = 30; + break; + case 40'800: + *out_actual_frequency_khz = 40'800; + n = 18; + break; + case 50'000: + *out_actual_frequency_khz = 48'000; + n = 15; + break; + case 52'000: + *out_actual_frequency_khz = 51'000; + n = 14; + break; + case 100'000: + #if defined(AMS_SDMMC_SET_PLLC4_BASE) + *out_actual_frequency_khz = 99'840; + n = 2; + clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMCX_SDMMCX_CLK_SRC, PLLC4_OUT2)); + #else + *out_actual_frequency_khz = 90'667; + n = 7; + #endif + break; + case 200'000: + #if defined(AMS_SDMMC_SET_PLLC4_BASE) + *out_actual_frequency_khz = 199'680; + n = 0; + if (module == Module_Sdmmc2 || module == Module_Sdmmc4) { + clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMC24_SDMMC24_CLK_SRC, PLLC4_OUT2_LJ)); + } else { + clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMCX_SDMMCX_CLK_SRC, PLLC4_OUT2)); + } + #else + *out_actual_frequency_khz = 163'200; + n = 3; + #endif + break; + case 208'000: + *out_actual_frequency_khz = 204'000; + n = 2; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Set frequencies in module info. */ + g_module_infos[module].target_frequency_khz = target_frequency_khz; + g_module_infos[module].actual_frequency_khz = *out_actual_frequency_khz; + + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Update the clock source. */ + switch (module) { + case Module_Sdmmc1: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC1, clk_m | static_cast<u32>(n)); break; + case Module_Sdmmc2: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC2, clk_m | static_cast<u32>(n)); break; + case Module_Sdmmc3: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC3, clk_m | static_cast<u32>(n)); break; + case Module_Sdmmc4: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC4, clk_m | static_cast<u32>(n)); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void EnsureControl(Module module) { + /* Read from RST_DEVICES_* to be sure previous configuration takes. */ + switch (module) { + case Module_Sdmmc1: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L); break; + case Module_Sdmmc2: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L); break; + case Module_Sdmmc3: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_U); break; + case Module_Sdmmc4: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + } + + void Initialize(Module module) { + /* Initialization isn't module specific. */ + AMS_UNUSED(module); + + /* Acquire exclusive access to the initialization lock. */ + AMS_SDMMC_LOCK_INIT_MUTEX(); + + /* If we've already initialized, we don't need to do anything. */ + if (g_is_initialized) { + return; + } + + /* Clear module infos. */ + std::memset(g_module_infos, 0, sizeof(g_module_infos)); + + /* Get the registers address. */ + g_clkrst_registers_address = dd::QueryIoMapping(ClockResetControllerRegistersPhysicalAddress, ClockResetControllerRegistersSize); + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Perform register initialization. */ + #if defined(AMS_SDMMC_SET_PLLC4_BASE) + InitializePllc4(); + #endif + InitializeLegacyTmClk(); + + /* Mark that we've initialized. */ + g_is_initialized = true; + } + + void Finalize(Module module) { + /* Nothing is needed for finalization. */ + AMS_UNUSED(module); + } + + bool IsAvailable(Module module) { + return IsResetReleased(module) && IsClockEnabled(module); + } + + void SetClockFrequencyKHz(u32 *out_actual_frequency_khz, Module module, u32 target_frequency_khz) { + /* If we're not changing the clock frequency, we don't need to do anything. */ + if (target_frequency_khz == g_module_infos[module].target_frequency_khz) { + *out_actual_frequency_khz = g_module_infos[module].actual_frequency_khz; + return; + } + + /* Temporarily disable clock. */ + const bool clock_enabled = IsClockEnabled(module); + if (clock_enabled) { + ClearClockEnable(module); + } + + /* Set the clock source. */ + SetClockSourceSdmmc(out_actual_frequency_khz, module, target_frequency_khz); + + /* Re-enable clock, if we should. */ + if (clock_enabled) { + SetClockEnable(module); + } + + /* Ensure that our configuration takes. */ + EnsureControl(module); + } + + void AssertReset(Module module) { + /* Set reset and disable clock. */ + SetReset(module); + ClearClockEnable(module); + + /* Ensure that our configuration takes. */ + EnsureControl(module); + } + + void ReleaseReset(Module module, u32 target_frequency_khz) { + /* Disable clock if it's enabled. */ + if (IsClockEnabled(module)) { + ClearClockEnable(module); + } + + /* Set reset. */ + SetReset(module); + + /* Set the clock source. */ + u32 actual_source_frequency_khz; + SetClockSourceSdmmc(std::addressof(actual_source_frequency_khz), module, target_frequency_khz); + + /* Enable clock. */ + SetClockEnable(module); + + /* Ensure that our configuration takes. */ + EnsureControl(module); + + /* Wait 100 clocks. */ + WaitClocks(100, actual_source_frequency_khz); + + /* Clear reset. */ + ClearReset(module); + + /* Ensure that our configuration takes. */ + EnsureControl(module); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp new file mode 100644 index 00000000..a18c62a9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include "sdmmc_clock_reset_controller.hpp" + +namespace ams::sdmmc::impl::ClockResetController::reg { + + void Initialize(Module module); + void Finalize(Module module); + bool IsAvailable(Module module); + + void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency); + void AssertReset(Module module); + void ReleaseReset(Module module, u32 target_frequency_khz); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.cpp new file mode 100644 index 00000000..48b9a25f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.cpp @@ -0,0 +1,272 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) +#include "sdmmc_device_detector.hpp" + +namespace ams::sdmmc::impl { + + bool DeviceDetector::IsCurrentInserted() { + return gpio::GetValue(std::addressof(m_gpio_pad_session)) == m_inserted_gpio_value; + } + + void DeviceDetector::HandleDeviceStatus(bool prev_inserted, bool cur_inserted) { + if (!prev_inserted && !cur_inserted) { + /* Not inserted -> Not inserted, nothing to do. */ + } else if (!prev_inserted && cur_inserted) { + /* Card was inserted. */ + if (m_callback_info.inserted_callback != nullptr) { + m_callback_info.inserted_callback(m_callback_info.inserted_callback_arg); + } + } else if (prev_inserted && !cur_inserted) { + /* Card was removed. */ + if (m_callback_info.removed_callback != nullptr) { + m_callback_info.removed_callback(m_callback_info.removed_callback_arg); + } + } else /* if (prev_inserted && cur_inserted) */ { + /* Card was removed, and then inserted. */ + if (m_callback_info.removed_callback != nullptr) { + m_callback_info.removed_callback(m_callback_info.removed_callback_arg); + } + + if (m_callback_info.inserted_callback != nullptr) { + m_callback_info.inserted_callback(m_callback_info.inserted_callback_arg); + } + } + } + + void DeviceDetector::DetectorThread() { + /* Initialize the gpio session. */ + gpio::Initialize(); + + /* Open and configure the pad session. */ + gpio::OpenSession(std::addressof(m_gpio_pad_session), m_gpio_device_code); + gpio::SetDirection(std::addressof(m_gpio_pad_session), gpio::Direction_Input); + gpio::SetDebounceTime(std::addressof(m_gpio_pad_session), m_gpio_debounce_ms); + gpio::SetDebounceEnabled(std::addressof(m_gpio_pad_session), true); + gpio::SetInterruptMode(std::addressof(m_gpio_pad_session), gpio::InterruptMode_AnyEdge); + + /* Get the gpio session's interrupt event. */ + os::SystemEventType gpio_event; + R_ABORT_UNLESS(gpio::BindInterrupt(std::addressof(gpio_event), std::addressof(m_gpio_pad_session))); + + /* Initialize and link multi wait/holders. */ + os::MultiWaitType multi_wait; + os::MultiWaitHolderType detector_thread_end_holder; + os::MultiWaitHolderType request_sleep_wake_event_holder; + os::MultiWaitHolderType gpio_event_holder; + os::InitializeMultiWait(std::addressof(multi_wait)); + os::InitializeMultiWaitHolder(std::addressof(detector_thread_end_holder), std::addressof(m_detector_thread_end_event)); + os::LinkMultiWaitHolder(std::addressof(multi_wait), std::addressof(detector_thread_end_holder)); + os::InitializeMultiWaitHolder(std::addressof(request_sleep_wake_event_holder), std::addressof(m_request_sleep_wake_event)); + os::LinkMultiWaitHolder(std::addressof(multi_wait), std::addressof(request_sleep_wake_event_holder)); + os::InitializeMultiWaitHolder(std::addressof(gpio_event_holder), std::addressof(gpio_event)); + os::LinkMultiWaitHolder(std::addressof(multi_wait), std::addressof(gpio_event_holder)); + + /* Wait before detecting the initial state of the card. */ + os::SleepThread(TimeSpan::FromMilliSeconds(m_gpio_debounce_ms)); + bool cur_inserted = this->IsCurrentInserted(); + m_is_prev_inserted = cur_inserted; + + /* Set state as awake. */ + m_state = State_Awake; + os::SignalEvent(std::addressof(m_ready_device_status_event)); + + /* Enable interrupts to be informed of device status. */ + gpio::SetInterruptEnable(std::addressof(m_gpio_pad_session), true); + + /* Wait, servicing our events. */ + while (true) { + /* Get the signaled holder. */ + os::MultiWaitHolderType *signaled_holder = os::WaitAny(std::addressof(multi_wait)); + + /* Process the holder. */ + bool insert_change = false; + if (signaled_holder == std::addressof(detector_thread_end_holder)) { + /* We should kill ourselves. */ + os::ClearEvent(std::addressof(m_detector_thread_end_event)); + m_state = State_Finalized; + break; + } else if (signaled_holder == std::addressof(request_sleep_wake_event_holder)) { + /* A request for us to sleep/wake has come in, so we'll acknowledge it. */ + os::ClearEvent(std::addressof(m_request_sleep_wake_event)); + m_state = State_Sleep; + os::SignalEvent(std::addressof(m_acknowledge_sleep_awake_event)); + + /* Temporarily unlink our interrupt event. */ + os::UnlinkMultiWaitHolder(std::addressof(gpio_event_holder)); + + /* Wait to be signaled. */ + signaled_holder = os::WaitAny(std::addressof(multi_wait)); + + /* Link our interrupt event back in. */ + os::LinkMultiWaitHolder(std::addressof(multi_wait), std::addressof(gpio_event_holder)); + + /* We're awake again. Either because we should exit, or because we were asked to wake up. */ + os::ClearEvent(std::addressof(m_request_sleep_wake_event)); + m_state = State_Awake; + os::SignalEvent(std::addressof(m_acknowledge_sleep_awake_event)); + + /* If we were asked to exit, do so. */ + if (signaled_holder == std::addressof(detector_thread_end_holder)) { + /* We should kill ourselves. */ + os::ClearEvent(std::addressof(m_detector_thread_end_event)); + m_state = State_Finalized; + break; + } else /* if (signaled_holder == std::addressof(request_sleep_wake_event_holder)) */ { + if ((m_force_detection) || + (({ bool active; R_SUCCEEDED(gpio::IsWakeEventActive(std::addressof(active), m_gpio_device_code)) && active; })) || + (os::TryWaitSystemEvent(std::addressof(gpio_event))) || + (m_is_prev_inserted != this->IsCurrentInserted())) + { + insert_change = true; + } + } + } else /* if (signaled_holder == std::addressof(gpio_event_holder)) */ { + /* An event was detected. */ + insert_change = true; + } + + /* Handle an insert change, if one occurred. */ + if (insert_change) { + /* Call the relevant callback, if we have one. */ + if (m_device_detection_event_callback != nullptr) { + m_device_detection_event_callback(m_device_detection_event_callback_arg); + } + + /* Clear the interrupt event. */ + os::ClearSystemEvent(std::addressof(gpio_event)); + gpio::ClearInterruptStatus(std::addressof(m_gpio_pad_session)); + gpio::SetInterruptEnable(std::addressof(m_gpio_pad_session), true); + + /* Update insertion status. */ + cur_inserted = this->IsCurrentInserted(); + this->HandleDeviceStatus(m_is_prev_inserted, cur_inserted); + m_is_prev_inserted = cur_inserted; + } + } + + /* Disable interrupts to our gpio event. */ + gpio::SetInterruptEnable(std::addressof(m_gpio_pad_session), false); + + /* Finalize and unlink multi wait/holders. */ + os::UnlinkMultiWaitHolder(std::addressof(gpio_event_holder)); + os::FinalizeMultiWaitHolder(std::addressof(gpio_event_holder)); + os::UnlinkMultiWaitHolder(std::addressof(request_sleep_wake_event_holder)); + os::FinalizeMultiWaitHolder(std::addressof(request_sleep_wake_event_holder)); + os::UnlinkMultiWaitHolder(std::addressof(detector_thread_end_holder)); + os::FinalizeMultiWaitHolder(std::addressof(detector_thread_end_holder)); + os::FinalizeMultiWait(std::addressof(multi_wait)); + + /* Finalize the gpio session. */ + gpio::UnbindInterrupt(std::addressof(m_gpio_pad_session)); + gpio::CloseSession(std::addressof(m_gpio_pad_session)); + gpio::Finalize(); + } + + void DeviceDetector::Initialize(CallbackInfo *ci) { + /* Transition our state from finalized to initializing. */ + AMS_ABORT_UNLESS(m_state == State_Finalized); + m_state = State_Initializing; + + /* Set our callback infos. */ + m_callback_info = *ci; + + /* Initialize our events. */ + os::InitializeEvent(std::addressof(m_ready_device_status_event), false, os::EventClearMode_ManualClear); + os::InitializeEvent(std::addressof(m_request_sleep_wake_event), false, os::EventClearMode_ManualClear); + os::InitializeEvent(std::addressof(m_acknowledge_sleep_awake_event), false, os::EventClearMode_ManualClear); + os::InitializeEvent(std::addressof(m_detector_thread_end_event), false, os::EventClearMode_ManualClear); + + /* Create and start the detector thread. */ + os::CreateThread(std::addressof(m_detector_thread), DetectorThreadEntry, this, m_detector_thread_stack, sizeof(m_detector_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(sdmmc, DeviceDetector)); + os::SetThreadNamePointer(std::addressof(m_detector_thread), AMS_GET_SYSTEM_THREAD_NAME(sdmmc, DeviceDetector)); + os::StartThread(std::addressof(m_detector_thread)); + } + + void DeviceDetector::Finalize() { + /* Ensure we're not already finalized. */ + AMS_ABORT_UNLESS(m_state != State_Finalized); + + /* Signal event to end the detector thread. */ + os::SignalEvent(std::addressof(m_detector_thread_end_event)); + os::WaitThread(std::addressof(m_detector_thread)); + + /* Finalize thread and events. */ + os::DestroyThread(std::addressof(m_detector_thread)); + os::FinalizeEvent(std::addressof(m_ready_device_status_event)); + os::FinalizeEvent(std::addressof(m_request_sleep_wake_event)); + os::FinalizeEvent(std::addressof(m_acknowledge_sleep_awake_event)); + os::FinalizeEvent(std::addressof(m_detector_thread_end_event)); + } + + void DeviceDetector::PutToSleep() { + /* Signal request, wait for acknowledgement. */ + os::SignalEvent(std::addressof(m_request_sleep_wake_event)); + os::WaitEvent(std::addressof(m_acknowledge_sleep_awake_event)); + os::ClearEvent(std::addressof(m_acknowledge_sleep_awake_event)); + } + + void DeviceDetector::Awaken(bool force_det) { + /* Signal request, wait for acknowledgement. */ + m_force_detection = force_det; + os::SignalEvent(std::addressof(m_request_sleep_wake_event)); + os::WaitEvent(std::addressof(m_acknowledge_sleep_awake_event)); + os::ClearEvent(std::addressof(m_acknowledge_sleep_awake_event)); + } + + bool DeviceDetector::IsInserted() { + bool inserted = false; + + switch (m_state) { + case State_Initializing: + /* Wait for us to know whether the device is inserted. */ + os::WaitEvent(std::addressof(m_ready_device_status_event)); + [[fallthrough]]; + case State_Awake: + /* Get whether the device is currently inserted. */ + inserted = this->IsCurrentInserted(); + break; + case State_Sleep: + case State_Finalized: + /* Get whether the device was inserted when we last knew. */ + inserted = m_is_prev_inserted; + break; + } + + return inserted; + } + + void DeviceDetector::RegisterDetectionEventCallback(DeviceDetectionEventCallback cb, void *arg) { + m_device_detection_event_callback_arg = arg; + m_device_detection_event_callback = cb; + } + + void DeviceDetector::UnregisterDetectionEventCallback() { + m_device_detection_event_callback = nullptr; + } + +} + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.hpp new file mode 100644 index 00000000..82c15110 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.hpp @@ -0,0 +1,99 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) +#include <stratosphere.hpp> + +namespace ams::sdmmc::impl { + + using InsertedCallback = void (*)(void *); + using RemovedCallback = void (*)(void *); + + struct CallbackInfo { + InsertedCallback inserted_callback; + void *inserted_callback_arg; + RemovedCallback removed_callback; + void *removed_callback_arg; + }; + + class DeviceDetector { + private: + enum State { + State_Initializing = 0, + State_Awake = 1, + State_Sleep = 2, + State_Finalized = 3, + }; + private: + alignas(os::ThreadStackAlignment) u8 m_detector_thread_stack[8_KB]; + State m_state; + bool m_is_prev_inserted; + bool m_force_detection; + os::ThreadType m_detector_thread; + os::EventType m_detector_thread_end_event; + os::EventType m_request_sleep_wake_event; + os::EventType m_acknowledge_sleep_awake_event; + os::EventType m_ready_device_status_event; + + DeviceCode m_gpio_device_code; + gpio::GpioValue m_inserted_gpio_value; + u32 m_gpio_debounce_ms; + gpio::GpioPadSession m_gpio_pad_session; + + CallbackInfo m_callback_info; + + DeviceDetectionEventCallback m_device_detection_event_callback; + void *m_device_detection_event_callback_arg; + private: + static void DetectorThreadEntry(void *arg) { + reinterpret_cast<DeviceDetector *>(arg)->DetectorThread(); + } + + void DetectorThread(); + bool IsCurrentInserted(); + void HandleDeviceStatus(bool prev_inserted, bool cur_inserted); + public: + explicit DeviceDetector(DeviceCode dc, gpio::GpioValue igv, u32 gd) + : m_gpio_device_code(dc), m_inserted_gpio_value(igv), m_gpio_debounce_ms(gd) + { + m_state = State_Finalized; + m_is_prev_inserted = false; + m_force_detection = false; + m_callback_info = {}; + m_device_detection_event_callback = nullptr; + m_device_detection_event_callback_arg = nullptr; + } + + void Initialize(CallbackInfo *ci); + void Finalize(); + + void PutToSleep(); + void Awaken(bool force_det); + + u32 GetDebounceMilliSeconds() const { + return m_gpio_debounce_ms; + } + + bool IsInserted(); + + void RegisterDetectionEventCallback(DeviceDetectionEventCallback cb, void *arg); + void UnregisterDetectionEventCallback(); + }; + +} + +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_gc_asic_device_accessor.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_gc_asic_device_accessor.cpp new file mode 100644 index 00000000..919461d0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_gc_asic_device_accessor.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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#include "sdmmc_gc_asic_device_accessor.hpp" +#include "sdmmc_timer.hpp" + +namespace ams::sdmmc::impl { + + #if defined(AMS_SDMMC_THREAD_SAFE) + + #define AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX() std::scoped_lock lk(m_gc_asic_device.m_device_mutex) + + #else + + #define AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX() + + #endif + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + + #define AMS_SDMMC_CHECK_GC_ASIC_REMOVED() R_UNLESS(!m_gc_asic_device.IsRemoved(), sdmmc::ResultDeviceRemoved()) + + #else + + #define AMS_SDMMC_CHECK_GC_ASIC_REMOVED() + + #endif + + Result GcAsicDeviceAccessor::IssueCommandWriteOperation(const void *op_buf, size_t op_buf_size) const { + /* Validate the operation buffer. */ + AMS_ABORT_UNLESS(op_buf != nullptr); + AMS_ABORT_UNLESS(op_buf_size >= GcAsicOperationSize); + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(CommandIndex_GcAsicWriteOperation, 0, CommandResponseType, false); + TransferData xfer_data(const_cast<void *>(op_buf), GcAsicOperationSize, 1, TransferDirection_WriteToDevice); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + Result result = hc->IssueCommand(std::addressof(command), std::addressof(xfer_data)); + if (R_FAILED(result)) { + /* We failed to write operation. Check if we were removed. */ + AMS_SDMMC_CHECK_GC_ASIC_REMOVED(); + + /* Determine what result we should return. */ + Result return_result = result; + { + /* Issue a stop transmission command. */ + u32 resp = 0; + result = hc->IssueStopTransmissionCommand(std::addressof(resp)); + if (R_SUCCEEDED(result)) { + /* If we successfully stopped transmission but have an error status, we prefer to return that. */ + result = m_gc_asic_device.CheckDeviceStatus(resp); + if (R_FAILED(result)) { + return_result = result; + } + } + + /* Check again if we were removed. */ + AMS_SDMMC_CHECK_GC_ASIC_REMOVED(); + + /* Request device status. */ + u32 device_status; + result = BaseDeviceAccessor::IssueCommandSendStatus(std::addressof(device_status), 0); + + /* If we got a device status error here and we didn't previously, we prefer to return that. */ + if (!sdmmc::ResultDeviceStatusHasError::Includes(return_result) && sdmmc::ResultDeviceStatusHasError::Includes(result)) { + return_result = result; + } + } + R_RETURN(return_result); + } + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + R_TRY(m_gc_asic_device.CheckDeviceStatus(resp)); + + R_SUCCEED(); + } + + Result GcAsicDeviceAccessor::IssueCommandFinishOperation() const { + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_GcAsicFinishOperation, 0, true, DeviceState_Tran)); + R_SUCCEED(); + } + + Result GcAsicDeviceAccessor::IssueCommandSleep() { + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_GcAsicSleep, 0, true, DeviceState_Tran)); + R_SUCCEED(); + } + + Result GcAsicDeviceAccessor::IssueCommandUpdateKey() const { + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_GcAsicUpdateKey, 0, true, DeviceState_Tran)); + R_SUCCEED(); + } + + Result GcAsicDeviceAccessor::StartupGcAsicDevice() { + /* Start up the host controller. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->Startup(BusPower_1_8V, BusWidth_8Bit, SpeedMode_GcAsicSpeed, false)); + + /* Wait 10 clocks for configuration to take. */ + WaitClocks(10, hc->GetDeviceClockFrequencyKHz()); + + /* Perform tuning with command index 21. */ + AMS_ABORT_UNLESS(hc->IsSupportedTuning()); + R_TRY(hc->Tuning(SpeedMode_GcAsicSpeed, 21)); + + /* Set the device as low capacity/no memory. */ + m_gc_asic_device.SetHighCapacity(false); + m_gc_asic_device.SetMemoryCapacity(0); + + /* Enable power saving. */ + hc->SetPowerSaving(true); + + R_SUCCEED(); + } + + Result GcAsicDeviceAccessor::OnActivate() { + /* If we fail to start up the device, ensure the host controller is shut down. */ + auto power_guard = SCOPE_GUARD { BaseDeviceAccessor::GetHostController()->Shutdown(); }; + + /* Try to start up the device. */ + R_TRY(this->StartupGcAsicDevice()); + + /* We started up, so we don't need to power down. */ + power_guard.Cancel(); + R_SUCCEED(); + } + + Result GcAsicDeviceAccessor::OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) { + /* Check that we're not performing zero-byte rw. */ + AMS_ABORT_UNLESS(num_sectors > 0); + + /* Check that the buffer is big enough for the rw. */ + AMS_ABORT_UNLESS((buf_size / SectorSize) >= num_sectors); + + /* Perform the read/write. */ + u32 num_transferred_blocks; + R_TRY(BaseDeviceAccessor::ReadWriteSingle(std::addressof(num_transferred_blocks), sector_index, num_sectors, buf, is_read)); + + /* Require that we read/wrote as many sectors as we expected. */ + AMS_ABORT_UNLESS(num_transferred_blocks == num_sectors); + + R_SUCCEED(); + } + + void GcAsicDeviceAccessor::Initialize() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* If we've already initialized, we don't need to do anything. */ + if (m_is_initialized) { + return; + } + + /* Set the base device to our gc asic device. */ + BaseDeviceAccessor::SetDevice(std::addressof(m_gc_asic_device)); + + /* Initialize. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + m_gc_asic_device.InitializeRemovedEvent(); + hc->PreSetRemovedEvent(m_gc_asic_device.GetRemovedEvent()); + } + #endif + hc->Initialize(); + + /* Mark ourselves as initialized. */ + m_is_initialized = true; + } + + void GcAsicDeviceAccessor::Finalize() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* If we've already finalized, we don't need to do anything. */ + if (!m_is_initialized) { + return; + } + m_is_initialized = false; + + /* Deactivate the device. */ + BaseDeviceAccessor::Deactivate(); + + /* Finalize the host controller. */ + BaseDeviceAccessor::GetHostController()->Finalize(); + + /* Finalize the removed event. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + m_gc_asic_device.FinalizeRemovedEvent(); + } + #endif + } + + Result GcAsicDeviceAccessor::GetSpeedMode(SpeedMode *out_speed_mode) const { + /* Check that we can write to output. */ + AMS_ABORT_UNLESS(out_speed_mode != nullptr); + + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(m_gc_asic_device.CheckAccessible()); + + *out_speed_mode = SpeedMode_GcAsicSpeed; + R_SUCCEED(); + } + + void GcAsicDeviceAccessor::PutGcAsicToSleep() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* If the device isn't awake, we don't need to do anything. */ + if (!m_gc_asic_device.IsAwake()) { + return; + } + + /* If necessary, put the host controller to sleep. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + if (m_gc_asic_device.IsActive() && !m_gc_asic_device.IsRemoved()) + #else + if (m_gc_asic_device.IsActive()) + #endif + { + BaseDeviceAccessor::GetHostController()->PutToSleep(); + } + + /* Put the gc asic device to sleep. */ + m_gc_asic_device.PutToSleep(); + } + + Result GcAsicDeviceAccessor::AwakenGcAsic() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* If the device is awake, we don't need to do anything. */ + R_SUCCEED_IF(m_gc_asic_device.IsAwake()); + + /* Wake the device. */ + m_gc_asic_device.Awaken(); + + /* Wake the host controller, if we need to.*/ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + if (m_gc_asic_device.IsActive() && !m_gc_asic_device.IsRemoved()) + #else + if (m_gc_asic_device.IsActive()) + #endif + { + R_TRY(BaseDeviceAccessor::GetHostController()->Awaken()); + } + + R_SUCCEED(); + } + + Result GcAsicDeviceAccessor::WriteGcAsicOperation(const void *op_buf, size_t op_buf_size) { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(m_gc_asic_device.CheckAccessible()); + + /* Issue the command. */ + R_TRY(this->IssueCommandWriteOperation(op_buf, op_buf_size)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + R_SUCCEED(); + } + + Result GcAsicDeviceAccessor::FinishGcAsicOperation() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(m_gc_asic_device.CheckAccessible()); + + /* Issue the command. */ + R_TRY(this->IssueCommandFinishOperation()); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + R_SUCCEED(); + } + + Result GcAsicDeviceAccessor::AbortGcAsicOperation() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(m_gc_asic_device.CheckAccessible()); + + /* Issue stop transmission command. */ + u32 resp = 0; + R_TRY(BaseDeviceAccessor::GetHostController()->IssueStopTransmissionCommand(std::addressof(resp))); + R_TRY(m_gc_asic_device.CheckDeviceStatus(resp)); + + R_SUCCEED(); + } + + Result GcAsicDeviceAccessor::SleepGcAsic() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(m_gc_asic_device.CheckAccessible()); + + /* Issue the command. */ + R_TRY(this->IssueCommandSleep()); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + R_SUCCEED(); + } + + Result GcAsicDeviceAccessor::UpdateGcAsicKey() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(m_gc_asic_device.CheckAccessible()); + + /* Issue the command. */ + R_TRY(this->IssueCommandUpdateKey()); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_gc_asic_device_accessor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_gc_asic_device_accessor.hpp new file mode 100644 index 00000000..1f2a7251 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_gc_asic_device_accessor.hpp @@ -0,0 +1,96 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include "sdmmc_base_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + class GcAsicDevice : public BaseDevice { + private: + static constexpr u16 Rca = 0; + private: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + mutable os::EventType m_removed_event; + #endif + public: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual os::EventType *GetRemovedEvent() const override { + return std::addressof(m_removed_event); + } + #endif + + virtual DeviceType GetDeviceType() const override { + return DeviceType_GcAsic; + } + + virtual u16 GetRca() const override { + return Rca; + } + }; + + class GcAsicDeviceAccessor : public BaseDeviceAccessor { + private: + GcAsicDevice m_gc_asic_device; + bool m_is_initialized; + private: + Result IssueCommandWriteOperation(const void *op_buf, size_t op_buf_size) const; + Result IssueCommandFinishOperation() const; + Result IssueCommandSleep(); + Result IssueCommandUpdateKey() const; + Result StartupGcAsicDevice(); + protected: + virtual Result OnActivate() override; + virtual Result OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) override; + + virtual Result ReStartup() override { + AMS_ABORT("Can't ReStartup GcAsic\n"); + } + public: + virtual void Initialize() override; + virtual void Finalize() override; + virtual Result GetSpeedMode(SpeedMode *out_speed_mode) const override; + public: + explicit GcAsicDeviceAccessor(IHostController *hc) : BaseDeviceAccessor(hc), m_is_initialized(false) { + /* ... */ + } + + void PutGcAsicToSleep(); + Result AwakenGcAsic(); + Result WriteGcAsicOperation(const void *op_buf, size_t op_buf_size); + Result FinishGcAsicOperation(); + Result AbortGcAsicOperation(); + Result SleepGcAsic(); + Result UpdateGcAsicKey(); + + void SignalGcRemovedEvent() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + m_gc_asic_device.SignalRemovedEvent(); + #else + AMS_ABORT("SignalGcRemovedEvent called without event support\n"); + #endif + } + + void ClearGcRemovedEvent() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + m_gc_asic_device.ClearRemovedEvent(); + #else + AMS_ABORT("ClearGcRemovedEvent called without event support\n"); + #endif + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_i_device_accessor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_i_device_accessor.hpp new file mode 100644 index 00000000..3769c771 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_i_device_accessor.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include "sdmmc_i_host_controller.hpp" + +namespace ams::sdmmc::impl { + + class IDeviceAccessor { + public: + virtual void Initialize() = 0; + virtual void Finalize() = 0; + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + virtual void RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) = 0; + virtual void UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) = 0; + #endif + + virtual Result Activate() = 0; + virtual void Deactivate() = 0; + + virtual Result ReadWrite(u32 sector_index, u32 num_sectors, void *buffer, size_t buffer_size, bool is_read) = 0; + virtual Result CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width) = 0; + + virtual Result GetSpeedMode(SpeedMode *out) const = 0; + virtual Result GetMemoryCapacity(u32 *out_sectors) const = 0; + virtual Result GetDeviceStatus(u32 *out) const = 0; + virtual Result GetOcr(u32 *out) const = 0; + virtual Result GetRca(u16 *out) const = 0; + virtual Result GetCid(void *out, size_t size) const = 0; + virtual Result GetCsd(void *out, size_t size) const = 0; + + virtual void GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size) = 0; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_i_host_controller.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_i_host_controller.hpp new file mode 100644 index 00000000..5df6ede7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_i_host_controller.hpp @@ -0,0 +1,196 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +#if defined(AMS_SDMMC_USE_OS_EVENTS) +#include <stratosphere/os.hpp> +#endif + +namespace ams::sdmmc::impl { + + enum ResponseType { + ResponseType_R0 = 0, + ResponseType_R1 = 1, + ResponseType_R2 = 2, + ResponseType_R3 = 3, + ResponseType_R6 = 4, + ResponseType_R7 = 5, + }; + + enum TransferDirection { + TransferDirection_ReadFromDevice = 0, + TransferDirection_WriteToDevice = 1, + }; + + enum CommandIndex { + /* Generic commands. */ + CommandIndex_GoIdleState = 0, + CommandIndex_SendOpCond = 1, + CommandIndex_AllSendCid = 2, + CommandIndex_SendRelativeAddr = 3, + CommandIndex_SetRelativeAddr = 3, + CommandIndex_SetDsr = 4, + + CommandIndex_Switch = 6, + CommandIndex_SelectCard = 7, + CommandIndex_DeselectCard = 7, + CommandIndex_SendIfCond = 8, + CommandIndex_SendExtCsd = 8, + CommandIndex_SendCsd = 9, + CommandIndex_SendCid = 10, + CommandIndex_VoltageSwitch = 11, + CommandIndex_StopTransmission = 12, + CommandIndex_SendStatus = 13, + CommandIndex_SendTaskStatus = 13, + + CommandIndex_GoInactiveState = 15, + CommandIndex_SetBlockLen = 16, + CommandIndex_ReadSingleBlock = 17, + CommandIndex_ReadMultipleBlock = 18, + CommandIndex_SendTuningBlock = 19, + CommandIndex_SpeedClassControl = 20, + + CommandIndex_AddressExtension = 22, + CommandIndex_SetBlockCount = 23, + CommandIndex_WriteBlock = 24, + CommandIndex_WriteMultipleBlock = 25, + + CommandIndex_ProgramCsd = 27, + CommandIndex_SetWriteProt = 28, + CommandIndex_ClearWriteProt = 29, + CommandIndex_SendWriteProt = 30, + + CommandIndex_EraseWriteBlockStart = 32, + CommandIndex_EraseWriteBlockEnd = 33, + + CommandIndex_EraseGroupStart = 35, + CommandIndex_EraseGroupEnd = 36, + + CommandIndex_Erase = 38, + + CommandIndex_LockUnlock = 42, + + CommandIndex_AppCmd = 55, + CommandIndex_GenCmd = 56, + + /* Nintendo specific vendor commands for lotus3. */ + CommandIndex_GcAsicWriteOperation = 60, + CommandIndex_GcAsicFinishOperation = 61, + CommandIndex_GcAsicSleep = 62, + CommandIndex_GcAsicUpdateKey = 63, + }; + + struct Command { + u32 command_index; + u32 command_argument; + ResponseType response_type; + bool is_busy; + + constexpr Command(u32 ci, u32 ca, ResponseType r, bool b) : command_index(ci), command_argument(ca), response_type(r), is_busy(b) { /* ... */ } + }; + + struct TransferData { + void *buffer; + size_t block_size; + u32 num_blocks; + TransferDirection transfer_direction; + bool is_multi_block_transfer; + bool is_stop_transmission_command_enabled; + + constexpr TransferData(void *b, size_t bs, u32 nb, TransferDirection xd, bool mb, bool st) + : buffer(b), block_size(bs), num_blocks(nb), transfer_direction(xd), is_multi_block_transfer(mb), is_stop_transmission_command_enabled(st) + { + if (this->num_blocks > 1) { + AMS_ABORT_UNLESS(this->is_multi_block_transfer); + } + } + + constexpr TransferData(void *b, size_t bs, u32 nb, TransferDirection xd) + : buffer(b), block_size(bs), num_blocks(nb), transfer_direction(xd), is_multi_block_transfer(false), is_stop_transmission_command_enabled(false) + { + AMS_ABORT_UNLESS(this->num_blocks == 1); + } + }; + + class IHostController { + public: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual void PreSetRemovedEvent(ams::os::EventType *event) = 0; + #endif + + virtual void Initialize() = 0; + virtual void Finalize() = 0; + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + virtual void RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) = 0; + virtual void UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) = 0; + #endif + + virtual void SetWorkBuffer(void *wb, size_t wb_size) = 0; + + virtual Result Startup(BusPower bus_power, BusWidth bus_width, SpeedMode speed_mode, bool power_saving_enable) = 0; + virtual void Shutdown() = 0; + virtual void PutToSleep() = 0; + virtual Result Awaken() = 0; + + virtual Result SwitchToSdr12(); + + virtual bool IsSupportedBusPower(BusPower bus_power) const = 0; + virtual BusPower GetBusPower() const = 0; + + virtual bool IsSupportedBusWidth(BusWidth bus_width) const = 0; + virtual void SetBusWidth(BusWidth bus_width) = 0; + virtual BusWidth GetBusWidth() const = 0; + + virtual Result SetSpeedMode(SpeedMode speed_mode) = 0; + virtual SpeedMode GetSpeedMode() const = 0; + + virtual u32 GetDeviceClockFrequencyKHz() const = 0; + + virtual void SetPowerSaving(bool en) = 0; + virtual bool IsPowerSavingEnable() const = 0; + + virtual void EnableDeviceClock() = 0; + virtual void DisableDeviceClock() = 0; + virtual bool IsDeviceClockEnable() const = 0; + + virtual u32 GetMaxTransferNumBlocks() const = 0; + + virtual void ChangeCheckTransferInterval(u32 ms) = 0; + virtual void SetDefaultCheckTransferInterval() = 0; + + virtual Result IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) = 0; + virtual Result IssueStopTransmissionCommand(u32 *out_response) = 0; + + ALWAYS_INLINE Result IssueCommand(const Command *command, TransferData *xfer_data) { + R_RETURN(this->IssueCommand(command, xfer_data, nullptr)); + } + + ALWAYS_INLINE Result IssueCommand(const Command *command) { + R_RETURN(this->IssueCommand(command, nullptr, nullptr)); + } + + virtual void GetLastResponse(u32 *out_response, size_t response_size, ResponseType response_type) const = 0; + virtual void GetLastStopTransmissionResponse(u32 *out_response, size_t response_size) const = 0; + + virtual bool IsSupportedTuning() const = 0; + virtual Result Tuning(SpeedMode speed_mode, u32 command_index) = 0; + virtual void SaveTuningStatusForHs400() = 0; + virtual Result GetInternalStatus() const = 0; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.cpp new file mode 100644 index 00000000..7e4ca044 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.cpp @@ -0,0 +1,998 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#include "sdmmc_timer.hpp" +#include "sdmmc_sdmmc_controller.board.nintendo_nx.hpp" +#include "sdmmc_io_impl.board.nintendo_nx.hpp" + +namespace ams::sdmmc::impl { + + /* Lovingly taken from libexosphere. */ + namespace i2c_impl { + + enum Port { + Port_1 = 0, + /* TODO: Support other ports? */ + Port_5 = 4, + + Port_Count = 5, + }; + + constexpr inline const dd::PhysicalAddress I2c5RegistersAddress = UINT64_C(0x7000D000); + constexpr inline const size_t I2c5RegistersSize = 4_KB; + + namespace { + + constexpr inline size_t MaxTransferSize = sizeof(u32); + + constinit std::array<uintptr_t, Port_Count> g_register_addresses = [] { + std::array<uintptr_t, Port_Count> arr = {}; + return arr; + }(); + + void LoadConfig(uintptr_t address) { + /* Configure for TIMEOUT and MSTR config load. */ + /* NOTE: Nintendo writes value 1 to reserved bit 5 here. This bit is documented as having no meaning. */ + /* We will reproduce the write just in case it is undocumented. */ + reg::Write(address + I2C_CONFIG_LOAD, I2C_REG_BITS_VALUE(CONFIG_LOAD_RESERVED_BIT_5, 1), + I2C_REG_BITS_ENUM (CONFIG_LOAD_TIMEOUT_CONFIG_LOAD, ENABLE), + I2C_REG_BITS_ENUM (CONFIG_LOAD_SLV_CONFIG_LOAD, DISABLE), + I2C_REG_BITS_ENUM (CONFIG_LOAD_MSTR_CONFIG_LOAD, ENABLE)); + + /* Wait up to 20 microseconds for the master config to be loaded. */ + for (int i = 0; i < 20; ++i) { + if (reg::HasValue(address + I2C_CONFIG_LOAD, I2C_REG_BITS_ENUM(CONFIG_LOAD_MSTR_CONFIG_LOAD, DISABLE))) { + return; + } + util::WaitMicroSeconds(1); + } + } + + void ClearBus(uintptr_t address) { + /* Configure the bus clear register. */ + reg::Write(address + I2C_BUS_CLEAR_CONFIG, I2C_REG_BITS_VALUE(BUS_CLEAR_CONFIG_BC_SCLK_THRESHOLD, 9), + I2C_REG_BITS_ENUM (BUS_CLEAR_CONFIG_BC_STOP_COND, NO_STOP), + I2C_REG_BITS_ENUM (BUS_CLEAR_CONFIG_BC_TERMINATE, IMMEDIATE), + I2C_REG_BITS_ENUM (BUS_CLEAR_CONFIG_BC_ENABLE, ENABLE)); + + /* Load the config. */ + LoadConfig(address); + + /* Wait up to 250us (in 25 us increments) until the bus clear is done. */ + for (int i = 0; i < 10; ++i) { + if (reg::HasValue(address + I2C_INTERRUPT_STATUS_REGISTER, I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_BUS_CLEAR_DONE, SET))) { + break; + } + + util::WaitMicroSeconds(25); + } + + /* Read the bus clear status. */ + reg::Read(address + I2C_BUS_CLEAR_STATUS); + } + + void InitializePort(uintptr_t address) { + /* Calculate the divisor. */ + constexpr int Divisor = util::DivideUp(19200, 8 * 400); + + /* Set the divisor. */ + reg::Write(address + I2C_CLK_DIVISOR_REGISTER, I2C_REG_BITS_VALUE(CLK_DIVISOR_REGISTER_STD_FAST_MODE, Divisor - 1), + I2C_REG_BITS_VALUE(CLK_DIVISOR_REGISTER_HSMODE, 1)); + + /* Clear the bus. */ + ClearBus(address); + + /* Clear the status. */ + reg::Write(address + I2C_INTERRUPT_STATUS_REGISTER, reg::Read(address + I2C_INTERRUPT_STATUS_REGISTER)); + } + + bool Write(uintptr_t base_address, Port port, int address, const void *src, size_t src_size, bool unused) { + AMS_UNUSED(port, unused); + + /* Ensure we don't write too much. */ + u32 data = 0; + if (src_size > MaxTransferSize) { + return false; + } + + /* Copy the data to a transfer word. */ + std::memcpy(std::addressof(data), src, src_size); + + + /* Configure the to write the 7-bit address. */ + reg::Write(base_address + I2C_I2C_CMD_ADDR0, I2C_REG_BITS_VALUE(I2C_CMD_ADDR0_7BIT_ADDR, address), + I2C_REG_BITS_ENUM (I2C_CMD_ADDR0_7BIT_RW, WRITE)); + + /* Configure to write the data. */ + reg::Write(base_address + I2C_I2C_CMD_DATA1, data); + + /* Configure to write the correct amount of data. */ + reg::Write(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM (I2C_CNFG_DEBOUNCE_CNT, DEBOUNCE_4T), + I2C_REG_BITS_ENUM (I2C_CNFG_NEW_MASTER_FSM, ENABLE), + I2C_REG_BITS_ENUM (I2C_CNFG_CMD1, WRITE), + I2C_REG_BITS_VALUE(I2C_CNFG_LENGTH, src_size - 1)); + + /* Load the configuration. */ + LoadConfig(base_address); + + /* Start the command. */ + reg::ReadWrite(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM(I2C_CNFG_SEND, GO)); + + /* Wait for the command to be done. */ + while (!reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_BUSY, NOT_BUSY))) { /* ... */ } + + /* Check if the transfer was successful. */ + return reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_CMD1_STAT, SL1_XFER_SUCCESSFUL)); + } + + bool Read(uintptr_t base_address, Port port, void *dst, size_t dst_size, int address, bool unused) { + AMS_UNUSED(port, unused); + + /* Ensure we don't read too much. */ + if (dst_size > MaxTransferSize) { + return false; + } + + /* Configure the to read the 7-bit address. */ + reg::Write(base_address + I2C_I2C_CMD_ADDR0, I2C_REG_BITS_VALUE(I2C_CMD_ADDR0_7BIT_ADDR, address), + I2C_REG_BITS_ENUM (I2C_CMD_ADDR0_7BIT_RW, READ)); + + /* Configure to read the correct amount of data. */ + reg::Write(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM (I2C_CNFG_DEBOUNCE_CNT, DEBOUNCE_4T), + I2C_REG_BITS_ENUM (I2C_CNFG_NEW_MASTER_FSM, ENABLE), + I2C_REG_BITS_ENUM (I2C_CNFG_CMD1, READ), + I2C_REG_BITS_VALUE(I2C_CNFG_LENGTH, dst_size - 1)); + + /* Load the configuration. */ + LoadConfig(base_address); + + /* Start the command. */ + reg::ReadWrite(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM(I2C_CNFG_SEND, GO)); + + /* Wait for the command to be done. */ + while (!reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_BUSY, NOT_BUSY))) { /* ... */ } + + /* Check that the transfer was successful. */ + if (!reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_CMD1_STAT, SL1_XFER_SUCCESSFUL))) { + return false; + } + + /* Read and copy out the data. */ + u32 data = reg::Read(base_address + I2C_I2C_CMD_DATA1); + std::memcpy(dst, std::addressof(data), dst_size); + return true; + } + + } + + void SetRegisterAddress(Port port, uintptr_t address) { + g_register_addresses[port] = address; + } + + void Initialize(Port port) { + InitializePort(g_register_addresses[port]); + } + + bool Query(void *dst, size_t dst_size, Port port, int address, int r) { + const uintptr_t base_address = g_register_addresses[port]; + + /* Select the register we want to read. */ + bool success = Write(base_address, port, address, std::addressof(r), 1, false); + if (success) { + /* If we successfully selected, read data from the register. */ + success = Read(base_address, port, dst, dst_size, address, true); + } + + return success; + } + + bool Send(Port port, int address, int r, const void *src, size_t src_size) { + const uintptr_t base_address = g_register_addresses[port]; + + /* Create a transfer buffer, make sure we can use it. */ + u8 buffer[MaxTransferSize]; + if (src_size > sizeof(buffer) - 1) { + return false; + } + + /* Copy data into the buffer. */ + buffer[0] = static_cast<u8>(r); + std::memcpy(buffer + 1, src, src_size); + + return Write(base_address, port, address, buffer, src_size + 1, false); + } + + } + + namespace max7762x { + + /* The only regulator we care about for SD card power is ldo2. */ + constexpr inline const u8 Max77620PwrI2cAddr = 0x3C; + + constexpr inline const u32 Max77620Ldo2MvStep = 50'000; /* 50 mV (50K uV) steps. */ + constexpr inline const u32 Max77620Ldo2MvMin = 800'000; /* 0.8V min voltage. */ + constexpr inline const u32 Max77620Ldo2MvDefault = 1'800'000; /* 1.8V default voltage. */ + constexpr inline const u32 Max77620Ldo2MvMax = 3'300'000; /* 3.3V max voltage. */ + constexpr inline const u8 Max77620Ldo2VoltAddr = 0x27; + constexpr inline const u8 Max77620Ldo2CfgAddr = 0x28; + constexpr inline const u8 Max77620Ldo2VoltMask = 0x3F; + constexpr inline const u8 Max77620Ldo2EnableMask = 0xC0; + constexpr inline const u8 Max77620Ldo2EnableShift = 0x6; + constexpr inline const u8 Max77620Ldo2StatusMask = 0x00; + + namespace { + + #if defined(AMS_SDMMC_THREAD_SAFE) + constinit os::SdkMutex g_i2c_init_mutex; + + #define AMS_SDMMC_LOCK_I2C_INIT_MUTEX() std::scoped_lock lk(g_i2c_init_mutex) + + #else + + #define AMS_SDMMC_LOCK_I2C_INIT_MUTEX() + + #endif + + constinit bool g_initialized_i2c = false; + + void EnsureI2cInitialized() { + if (AMS_UNLIKELY(!g_initialized_i2c)) { + /* Ensure we have exclusive access to the i2c init status. */ + AMS_SDMMC_LOCK_I2C_INIT_MUTEX(); + + if (AMS_LIKELY(!g_initialized_i2c)) { + i2c_impl::SetRegisterAddress(i2c_impl::Port_5, dd::QueryIoMapping(i2c_impl::I2c5RegistersAddress, i2c_impl::I2c5RegistersSize)); + i2c_impl::Initialize(i2c_impl::Port_5); + g_initialized_i2c = true; + } + } + } + + } + + bool SetVoltageEnabled(bool en) { + /* Ensure that we can use i2c to communicate with the max7762x regulator. */ + EnsureI2cInitialized(); + + /* Read the current value. */ + u8 val; + if (!i2c_impl::Query(std::addressof(val), sizeof(val), i2c_impl::Port_5, Max77620PwrI2cAddr, Max77620Ldo2VoltAddr)) { + return false; + } + + /* Set or clear the enable mask. */ + val &= ~Max77620Ldo2EnableMask; + if (en) { + val |= ((3 << Max77620Ldo2EnableShift) & Max77620Ldo2EnableMask); + } + + /* Write the updated value. */ + if (!i2c_impl::Send(i2c_impl::Port_5, Max77620PwrI2cAddr, Max77620Ldo2VoltAddr, std::addressof(val), sizeof(val))) { + return false; + } + + /* Wait 1ms for change to take. */ + WaitMicroSeconds(1); + + /* Voltage is now enabled/disabled. */ + return true; + } + + bool SetVoltageValue(u32 micro_volts) { + /* Ensure that we can use i2c to communicate with the max7762x regulator. */ + EnsureI2cInitialized(); + + /* Check that the value is within range. */ + if (micro_volts < Max77620Ldo2MvMin || Max77620Ldo2MvMax < micro_volts) { + return false; + } + + /* Determine the mult. */ + const u32 mult = util::DivideUp(micro_volts - Max77620Ldo2MvMin, Max77620Ldo2MvStep); + + /* Read the current value. */ + u8 val; + if (!i2c_impl::Query(std::addressof(val), sizeof(val), i2c_impl::Port_5, Max77620PwrI2cAddr, Max77620Ldo2VoltAddr)) { + return false; + } + + /* Set the new voltage. */ + val &= ~Max77620Ldo2VoltMask; + val |= (mult & Max77620Ldo2VoltMask); + + /* Write the updated value. */ + if (!i2c_impl::Send(i2c_impl::Port_5, Max77620PwrI2cAddr, Max77620Ldo2VoltAddr, std::addressof(val), sizeof(val))) { + return false; + } + + /* Wait 1ms for change to take. */ + WaitMicroSeconds(1); + + /* Voltage is now set. */ + return true; + } + + } + + Result SetSdCardVoltageEnabled(bool en) { + /* TODO: A way for this to be non-fatal? */ + AMS_ABORT_UNLESS(max7762x::SetVoltageEnabled(en)); + + R_SUCCEED(); + } + + Result SetSdCardVoltageValue(u32 micro_volts) { + /* TODO: A way for this to be non-fatal? */ + AMS_ABORT_UNLESS(max7762x::SetVoltageValue(micro_volts)); + + R_SUCCEED(); + } + + namespace gpio_impl { + + namespace { + + constexpr inline dd::PhysicalAddress GpioRegistersPhysicalAddress = 0x6000d000; + constexpr inline size_t GpioRegistersSize = 4_KB; + + enum GpioPadPort { + GpioPadPort_A = 0, + GpioPadPort_B = 1, + GpioPadPort_C = 2, + GpioPadPort_D = 3, + GpioPadPort_E = 4, + GpioPadPort_F = 5, + GpioPadPort_G = 6, + GpioPadPort_H = 7, + GpioPadPort_I = 8, + GpioPadPort_J = 9, + GpioPadPort_K = 10, + GpioPadPort_L = 11, + GpioPadPort_M = 12, + GpioPadPort_N = 13, + GpioPadPort_O = 14, + GpioPadPort_P = 15, + GpioPadPort_Q = 16, + GpioPadPort_R = 17, + GpioPadPort_S = 18, + GpioPadPort_T = 19, + GpioPadPort_U = 20, + GpioPadPort_V = 21, + GpioPadPort_W = 22, + GpioPadPort_X = 23, + GpioPadPort_Y = 24, + GpioPadPort_Z = 25, + GpioPadPort_AA = 26, + GpioPadPort_BB = 27, + GpioPadPort_CC = 28, + GpioPadPort_DD = 29, + GpioPadPort_EE = 30, + GpioPadPort_FF = 31, + }; + + consteval unsigned int GetInternalGpioPadNumber(GpioPadPort port, unsigned int which) { + AMS_ASSUME(which < 8); + + return (static_cast<unsigned int>(port) * 8) + which; + } + + enum InternalGpioPadNumber { + InternalGpioPadNumber_E4 = GetInternalGpioPadNumber(GpioPadPort_E, 4), + InternalGpioPadNumber_M0 = GetInternalGpioPadNumber(GpioPadPort_M, 0), + }; + + constexpr int ConvertInternalGpioPadNumberToController(InternalGpioPadNumber number) { + return (number >> 5); + } + + constexpr int ConvertInternalGpioPadNumberToPort(InternalGpioPadNumber number) { + return (number >> 3); + } + + constexpr int ConvertInternalGpioPadNumberToBitIndex(InternalGpioPadNumber number) { + return (number & 7); + } + + constexpr int ConvertPortNumberToOffset(int port_number) { + return (port_number & 3); + } + + struct PadNameToInternalPadNumberEntry { + GpioPadName pad_name; + InternalGpioPadNumber internal_number; + }; + + constexpr inline const PadNameToInternalPadNumberEntry PadNameToInternalPadNumberTable[] = { + { GpioPadName_PowSdEn, InternalGpioPadNumber_E4 }, + }; + + constexpr InternalGpioPadNumber ConvertPadNameToInternalPadNumber(GpioPadName pad) { + const PadNameToInternalPadNumberEntry *target = nullptr; + for (const auto &entry : PadNameToInternalPadNumberTable) { + if (entry.pad_name == pad) { + target = std::addressof(entry); + break; + } + } + + AMS_ABORT_UNLESS(target != nullptr); + return target->internal_number; + } + + enum GpioRegisterType { + GpioRegisterType_GPIO_CNF = 0, + GpioRegisterType_GPIO_OE = 1, + GpioRegisterType_GPIO_OUT = 2, + GpioRegisterType_GPIO_IN = 3, + GpioRegisterType_GPIO_INT_STA = 4, + GpioRegisterType_GPIO_INT_ENB = 5, + GpioRegisterType_GPIO_INT_LVL = 6, + GpioRegisterType_GPIO_INT_CLR = 7, + GpioRegisterType_GPIO_DB_CTRL = 8, + GpioRegisterType_GPIO_DB_CNT = 9, + }; + + constexpr inline uintptr_t MaskedWriteAddressOffset = 0x80; + constexpr inline int MaskedWriteBitOffset = 8; + + constexpr uintptr_t GetGpioRegisterAddress(uintptr_t gpio_address, GpioRegisterType reg_type, InternalGpioPadNumber pad_number) { + const auto controller = ConvertInternalGpioPadNumberToController(pad_number); + const auto port = ConvertInternalGpioPadNumberToPort(pad_number); + const auto offset = ConvertPortNumberToOffset(port); + + switch (reg_type) { + default: + return gpio_address + (0x100 * controller) + (0x10 * reg_type) + (0x4 * offset); + case GpioRegisterType_GPIO_DB_CTRL: + return gpio_address + (0x100 * controller) + (0x10 * GpioRegisterType_GPIO_IN) + (0x4 * offset); + case GpioRegisterType_GPIO_DB_CNT: + return gpio_address + (0x100 * controller) + MaskedWriteAddressOffset + (0x10 * GpioRegisterType_GPIO_INT_CLR) + (0x4 * offset); + } + } + + void SetMaskedBit(uintptr_t pad_address, int index, int value) { + const uintptr_t mask_address = pad_address + MaskedWriteAddressOffset; + + reg::Write(mask_address, (1u << (MaskedWriteBitOffset + index)) | (static_cast<unsigned int>(value) << index)); + } + + void SetMaskedBits(uintptr_t pad_address, unsigned int mask, unsigned int value) { + const uintptr_t mask_address = pad_address + MaskedWriteAddressOffset; + + reg::Write(mask_address, (mask << MaskedWriteBitOffset) | (value)); + } + + } + + void OpenSession(GpioPadName pad) { + /* Convert the pad to an internal number. */ + const auto pad_number = ConvertPadNameToInternalPadNumber(pad); + + /* Get the gpio registers address. */ + const uintptr_t gpio_address = dd::QueryIoMapping(GpioRegistersPhysicalAddress, GpioRegistersSize); + + /* Configure the pad as GPIO by setting the appropriate bit in CNF. */ + const uintptr_t pad_address = GetGpioRegisterAddress(gpio_address, GpioRegisterType_GPIO_CNF, pad_number); + const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); + SetMaskedBit(pad_address, pad_index, 1); + + /* Read the pad address to make sure our configuration takes. */ + reg::Read(pad_address); + } + + void CloseSession(GpioPadName pad) { + /* Nothing needs to be done here, as the only thing official code does is unbind the interrupt event. */ + AMS_UNUSED(pad); + } + + void SetDirection(GpioPadName pad, Direction direction) { + /* Convert the pad to an internal number. */ + const auto pad_number = ConvertPadNameToInternalPadNumber(pad); + + /* Get the gpio registers address. */ + const uintptr_t gpio_address = dd::QueryIoMapping(GpioRegistersPhysicalAddress, GpioRegistersSize); + + /* Configure the pad direction modifying the appropriate bit in OE. */ + const uintptr_t pad_address = GetGpioRegisterAddress(gpio_address, GpioRegisterType_GPIO_OE, pad_number); + const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); + SetMaskedBit(pad_address, pad_index, direction); + + /* Read the pad address to make sure our configuration takes. */ + reg::Read(pad_address); + } + + void SetValue(GpioPadName pad, GpioValue value) { + /* Convert the pad to an internal number. */ + const auto pad_number = ConvertPadNameToInternalPadNumber(pad); + + /* Get the gpio registers address. */ + const uintptr_t gpio_address = dd::QueryIoMapping(GpioRegistersPhysicalAddress, GpioRegistersSize); + + /* Configure the pad value modifying the appropriate bit in OUT. */ + const uintptr_t pad_address = GetGpioRegisterAddress(gpio_address, GpioRegisterType_GPIO_OUT, pad_number); + const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); + SetMaskedBit(pad_address, pad_index, value); + + /* Read the pad address to make sure our configuration takes. */ + reg::Read(pad_address); + } + + } + + namespace pinmux_impl { + + namespace { + + constexpr auto Sdmmc1ClkCmdDat03PadNumber = gpio_impl::InternalGpioPadNumber_M0; + + constexpr unsigned int Sdmmc1ClkCmdDat03PadMask = 0x3F; + + constexpr unsigned int Sdmmc1ClkCmdDat03PadCnfGpio = 0x3F; + constexpr unsigned int Sdmmc1ClkCmdDat03PadCnfSfio = 0x00; + + constexpr unsigned int Sdmmc1ClkCmdDat03PadOutHigh = 0x3F; + constexpr unsigned int Sdmmc1ClkCmdDat03PadOutLow = 0x00; + + constexpr unsigned int Sdmmc1ClkCmdDat03PadOeOutput = 0x3F; + constexpr unsigned int Sdmmc1ClkCmdDat03PadOeInput = 0x00; + + struct PinmuxDefinition { + u32 reg_offset; + u32 mask_val; + u32 pm_val; + }; + + /* NOTE: We only use the SDMMC1 pins, which are conveniently the first few... */ + constexpr const PinmuxDefinition PinmuxDefinitionMap[] = { + {0x00003000, 0x72FF, 0x01}, /* Sdmmc1Clk */ + {0x00003004, 0x72FF, 0x02}, /* Sdmmc1Cmd */ + {0x00003008, 0x72FF, 0x02}, /* Sdmmc1Dat3 */ + {0x0000300C, 0x72FF, 0x02}, /* Sdmmc1Dat2 */ + {0x00003010, 0x72FF, 0x02}, /* Sdmmc1Dat1 */ + {0x00003014, 0x72FF, 0x01}, /* Sdmmc1Dat0 */ + }; + + enum PinmuxPadIndex { + PinmuxPadIndex_Sdmmc1Clk = 0, + PinmuxPadIndex_Sdmmc1Cmd = 1, + PinmuxPadIndex_Sdmmc1Dat3 = 2, + PinmuxPadIndex_Sdmmc1Dat2 = 3, + PinmuxPadIndex_Sdmmc1Dat1 = 4, + PinmuxPadIndex_Sdmmc1Dat0 = 5, + + PinmuxPadIndex_Count, + }; + + static_assert(util::size(PinmuxDefinitionMap) == PinmuxPadIndex_Count); + + consteval const PinmuxDefinition GetDefinition(PinmuxPadIndex pad_index) { + AMS_ABORT_UNLESS(pad_index < PinmuxPadIndex_Count); + + return PinmuxDefinitionMap[pad_index]; + } + + template<PinmuxPadIndex PadIndex, u32 PinmuxConfigVal, u32 PinmuxConfigMaskVal> + ALWAYS_INLINE u32 UpdatePinmuxPad(uintptr_t pinmux_base_vaddr) { + constexpr const PinmuxDefinition Definition = GetDefinition(PadIndex); + + /* Fetch this PINMUX's register offset */ + constexpr u32 PinmuxRegOffset = Definition.reg_offset; + + /* Fetch this PINMUX's mask value */ + constexpr u32 PinmuxMaskVal = Definition.mask_val; + + /* Get current register ptr. */ + const uintptr_t pinmux_reg = pinmux_base_vaddr + PinmuxRegOffset; + + /* Read from the PINMUX register */ + u32 pinmux_val = reg::Read(pinmux_reg); + + /* This PINMUX register is locked */ + AMS_ABORT_UNLESS((pinmux_val & 0x80) == 0); + + constexpr u32 PmVal = (PinmuxConfigVal & 0x07); + + /* Adjust PM */ + if constexpr (PinmuxConfigMaskVal & 0x07) { + /* Apply additional changes first */ + if constexpr (PmVal == 0x05) { + /* This pin supports PUPD change */ + if constexpr (PinmuxMaskVal & 0x0C) { + /* Change PUPD */ + if ((pinmux_val & 0x0C) != 0x04) { + pinmux_val &= 0xFFFFFFF3; + pinmux_val |= 0x04; + } + } + + /* This pin supports Tristate change */ + if constexpr (PinmuxMaskVal & 0x10) { + /* Change Tristate */ + if (!(pinmux_val & 0x10)) { + pinmux_val |= 0x10; + } + } + + /* This pin supports EInput change */ + if constexpr (PinmuxMaskVal & 0x40) { + /* Change EInput */ + if (pinmux_val & 0x40) { + pinmux_val &= 0xFFFFFFBF; + } + } + } + + /* Translate PM value if necessary */ + constexpr u32 TranslatedPmVal = (PmVal == 0x04 || PmVal == 0x05 || PmVal >= 0x06) ? Definition.pm_val : PmVal; + + /* This pin supports PM change */ + if constexpr (PinmuxMaskVal & 0x03) { + /* Change PM */ + if ((pinmux_val & 0x03) != (TranslatedPmVal & 0x03)) { + pinmux_val &= 0xFFFFFFFC; + pinmux_val |= (TranslatedPmVal & 0x03); + } + } + } + + constexpr u32 PupdConfigVal = (PinmuxConfigVal & 0x18); + + /* Adjust PUPD */ + if constexpr (PinmuxConfigMaskVal & 0x18) { + if constexpr (PupdConfigVal < 0x11) { + /* This pin supports PUPD change */ + if constexpr (PinmuxMaskVal & 0x0C) { + /* Change PUPD */ + if (((pinmux_val >> 0x02) & 0x03) != (PupdConfigVal >> 0x03)) { + pinmux_val &= 0xFFFFFFF3; + pinmux_val |= (PupdConfigVal >> 0x01); + } + } + } + } + + constexpr u32 EodConfigVal = (PinmuxConfigVal & 0x60); + + /* Adjust EOd field */ + if constexpr (PinmuxConfigMaskVal & 0x60) { + if constexpr (EodConfigVal == 0x20) { + /* This pin supports Tristate change */ + if constexpr (PinmuxMaskVal & 0x10) { + /* Change Tristate */ + if (!(pinmux_val & 0x10)) { + pinmux_val |= 0x10; + } + } + + /* This pin supports EInput change */ + if constexpr (PinmuxMaskVal & 0x40) { + /* Change EInput */ + if (!(pinmux_val & 0x40)) { + pinmux_val |= 0x40; + } + } + + /* This pin supports EOd change */ + if constexpr (PinmuxMaskVal & 0x800) { + /* Change EOd */ + if (pinmux_val & 0x800) { + pinmux_val &= 0xFFFFF7FF; + } + } + } else if constexpr (EodConfigVal == 0x40) { + /* This pin supports Tristate change */ + if constexpr (PinmuxMaskVal & 0x10) { + /* Change Tristate */ + if (pinmux_val & 0x10) { + pinmux_val &= 0xFFFFFFEF; + } + } + + /* This pin supports EInput change */ + if constexpr (PinmuxMaskVal & 0x40) { + /* Change EInput */ + if (!(pinmux_val & 0x40)) { + pinmux_val |= 0x40; + } + } + + /* This pin supports EOd change */ + if constexpr (PinmuxMaskVal & 0x800) { + /* Change EOd */ + if (pinmux_val & 0x800) { + pinmux_val &= 0xFFFFF7FF; + } + } + } else if constexpr (EodConfigVal == 0x60) { + /* This pin supports Tristate change */ + if constexpr (PinmuxMaskVal & 0x10) { + /* Change Tristate */ + if (pinmux_val & 0x10) { + pinmux_val &= 0xFFFFFFEF; + } + } + + /* This pin supports EInput change */ + if constexpr (PinmuxMaskVal & 0x40) { + /* Change EInput */ + if (!(pinmux_val & 0x40)) { + pinmux_val |= 0x40; + } + } + + /* This pin supports EOd change */ + if constexpr (PinmuxMaskVal & 0x800) { + /* Change EOd */ + if (!(pinmux_val & 0x800)) { + pinmux_val |= 0x800; + } + } + } else { + /* This pin supports Tristate change */ + if constexpr (PinmuxMaskVal & 0x10) { + /* Change Tristate */ + if (pinmux_val & 0x10) { + pinmux_val &= 0xFFFFFFEF; + } + } + + /* This pin supports EInput change */ + if constexpr (PinmuxMaskVal & 0x40) { + /* Change EInput */ + if (pinmux_val & 0x40) { + pinmux_val &= 0xFFFFFFBF; + } + } + + /* This pin supports EOd change */ + if constexpr (PinmuxMaskVal & 0x800) { + /* Change EOd */ + if (pinmux_val & 0x800) { + pinmux_val &= 0xFFFFF7FF; + } + } + } + } + + constexpr u32 LockConfigVal = (PinmuxConfigVal & 0x80); + + /* Adjust Lock */ + if constexpr (PinmuxConfigMaskVal & 0x80) { + /* This pin supports Lock change */ + if constexpr (PinmuxMaskVal & 0x80) { + /* Change Lock */ + if ((pinmux_val ^ PinmuxConfigVal) & 0x80) { + pinmux_val &= 0xFFFFFF7F; + pinmux_val |= LockConfigVal; + } + } + } + + constexpr u32 IoResetConfigVal = (((PinmuxConfigVal >> 0x08) & 0x1) << 0x10); + + /* Adjust IoReset */ + if constexpr (PinmuxConfigMaskVal & 0x100) { + /* This pin supports IoReset change */ + if constexpr (PinmuxMaskVal & 0x10000) { + /* Change IoReset */ + if (((pinmux_val >> 0x10) ^ (PinmuxConfigVal >> 0x08)) & 0x01) { + pinmux_val &= 0xFFFEFFFF; + pinmux_val |= IoResetConfigVal; + } + } + } + + constexpr u32 ParkConfigVal = (((PinmuxConfigVal >> 0x0A) & 0x1) << 0x5); + + /* Adjust Park */ + if constexpr (PinmuxConfigMaskVal & 0x400) { + /* This pin supports Park change */ + if constexpr (PinmuxMaskVal & 0x20) { + /* Change Park */ + if (((pinmux_val >> 0x05) ^ (PinmuxConfigVal >> 0x0A)) & 0x01) { + pinmux_val &= 0xFFFFFFDF; + pinmux_val |= ParkConfigVal; + } + } + } + + constexpr u32 ElpdrConfigVal = (((PinmuxConfigVal >> 0x0B) & 0x1) << 0x08); + + /* Adjust ELpdr */ + if constexpr (PinmuxConfigMaskVal & 0x800) { + /* This pin supports ELpdr change */ + if constexpr (PinmuxMaskVal & 0x100) { + /* Change ELpdr */ + if (((pinmux_val >> 0x08) ^ (PinmuxConfigVal >> 0x0B)) & 0x01) { + pinmux_val &= 0xFFFFFEFF; + pinmux_val |= ElpdrConfigVal; + } + } + } + + constexpr u32 EhsmConfigVal = (((PinmuxConfigVal >> 0x0C) & 0x1) << 0x09); + + /* Adjust EHsm */ + if constexpr (PinmuxConfigMaskVal & 0x1000) { + /* This pin supports EHsm change */ + if constexpr (PinmuxMaskVal & 0x200) { + /* Change EHsm */ + if (((pinmux_val >> 0x09) ^ (PinmuxConfigVal >> 0x0C)) & 0x01) { + pinmux_val &= 0xFFFFFDFF; + pinmux_val |= EhsmConfigVal; + } + } + } + + constexpr u32 EIoHvConfigVal = (((PinmuxConfigVal >> 0x09) & 0x1) << 0x0A); + + /* Adjust EIoHv */ + if constexpr (PinmuxConfigMaskVal & 0x200) { + /* This pin supports EIoHv change */ + if constexpr (PinmuxMaskVal & 0x400) { + /* Change EIoHv */ + if (((pinmux_val >> 0x0A) ^ (PinmuxConfigVal >> 0x09)) & 0x01) { + pinmux_val &= 0xFFFFFBFF; + pinmux_val |= EIoHvConfigVal; + } + } + } + + constexpr u32 EschmtConfigVal = (((PinmuxConfigVal >> 0x0D) & 0x1) << 0x0C); + + /* Adjust ESchmt */ + if constexpr (PinmuxConfigMaskVal & 0x2000) { + /* This pin supports ESchmt change */ + if constexpr (PinmuxMaskVal & 0x1000) { + /* Change ESchmt */ + if (((pinmux_val >> 0x0C) ^ (PinmuxConfigVal >> 0x0D)) & 0x01) { + pinmux_val &= 0xFFFFEFFF; + pinmux_val |= EschmtConfigVal; + } + } + } + + constexpr u32 PreempConfigVal = (((PinmuxConfigVal >> 0x10) & 0x1) << 0xF); + + /* Adjust Preemp */ + if constexpr (PinmuxConfigMaskVal & 0x10000) { + /* This pin supports Preemp change */ + if constexpr (PinmuxMaskVal & 0x8000) { + /* Change Preemp */ + if (((pinmux_val >> 0x0F) ^ (PinmuxConfigVal >> 0x10)) & 0x01) { + pinmux_val &= 0xFFFF7FFF; + pinmux_val |= PreempConfigVal; + } + } + } + + constexpr u32 DrvTypeConfigVal = (((PinmuxConfigVal >> 0x0E) & 0x3) << 0xD); + + /* Adjust DrvType */ + if constexpr (PinmuxConfigMaskVal & 0xC000) { + /* This pin supports DrvType change */ + if constexpr (PinmuxMaskVal & 0x6000) { + /* Change DrvType */ + if (((pinmux_val >> 0x0D) ^ (PinmuxConfigVal >> 0x0E)) & 0x03) { + pinmux_val &= 0xFFFF9FFF; + pinmux_val |= DrvTypeConfigVal; + } + } + } + + /* Write to the appropriate PINMUX register */ + reg::Write(pinmux_reg, pinmux_val); + + /* Do a dummy read from the PINMUX register */ + pinmux_val = reg::Read(pinmux_reg); + + return pinmux_val; + } + + } + + void SetPinAssignment(PinAssignment assignment) { + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + AMS_UNUSED(apb_address); + + /* Set the pin assignment. */ + switch (assignment) { + case PinAssignment_Sdmmc1OutputHigh: + { + /* Clear Sdmmc1Clk pulldown. */ + UpdatePinmuxPad<PinmuxPadIndex_Sdmmc1Clk, 0, 0x18>(apb_address); + + /* Get the gpio registers address. */ + const uintptr_t gpio_address = dd::QueryIoMapping(gpio_impl::GpioRegistersPhysicalAddress, gpio_impl::GpioRegistersSize); + + /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as gpio. */ + const uintptr_t cnf_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_CNF, Sdmmc1ClkCmdDat03PadNumber); + gpio_impl::SetMaskedBits(cnf_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadCnfGpio); + + /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as high. */ + const uintptr_t out_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_OUT, Sdmmc1ClkCmdDat03PadNumber); + gpio_impl::SetMaskedBits(out_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadOutHigh); + + /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as output. */ + const uintptr_t oe_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_OE, Sdmmc1ClkCmdDat03PadNumber); + gpio_impl::SetMaskedBits(oe_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadOeOutput); + + /* Read to be sure that our configuration takes. */ + reg::Read(oe_address); + } + break; + case PinAssignment_Sdmmc1ResetState: + { + /* Get the gpio registers address. */ + const uintptr_t gpio_address = dd::QueryIoMapping(gpio_impl::GpioRegistersPhysicalAddress, gpio_impl::GpioRegistersSize); + + /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as sfio. */ + const uintptr_t cnf_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_CNF, Sdmmc1ClkCmdDat03PadNumber); + gpio_impl::SetMaskedBits(cnf_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadCnfSfio); + + /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as low. */ + const uintptr_t out_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_OUT, Sdmmc1ClkCmdDat03PadNumber); + gpio_impl::SetMaskedBits(out_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadOutLow); + + /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as input. */ + const uintptr_t oe_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_OE, Sdmmc1ClkCmdDat03PadNumber); + gpio_impl::SetMaskedBits(oe_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadOeInput); + + /* Read to be sure that our configuration takes. */ + reg::Read(oe_address); + + /* Set Sdmmc1Clk pulldown. */ + UpdatePinmuxPad<PinmuxPadIndex_Sdmmc1Clk, 0x8, 0x18>(apb_address); + } + break; + case PinAssignment_Sdmmc1SchmtEnable: + { + /* Set Schmitt enable for all pins in the group. */ + UpdatePinmuxPad<PinmuxPadIndex_Sdmmc1Clk, 0x2000, 0x2000>(apb_address); + UpdatePinmuxPad<PinmuxPadIndex_Sdmmc1Cmd, 0x2000, 0x2000>(apb_address); + UpdatePinmuxPad<PinmuxPadIndex_Sdmmc1Dat3, 0x2000, 0x2000>(apb_address); + UpdatePinmuxPad<PinmuxPadIndex_Sdmmc1Dat2, 0x2000, 0x2000>(apb_address); + UpdatePinmuxPad<PinmuxPadIndex_Sdmmc1Dat1, 0x2000, 0x2000>(apb_address); + UpdatePinmuxPad<PinmuxPadIndex_Sdmmc1Dat0, 0x2000, 0x2000>(apb_address); + } + break; + case PinAssignment_Sdmmc1SchmtDisable: + { + /* Set Schmitt disable for all pins in the group. */ + UpdatePinmuxPad<PinmuxPadIndex_Sdmmc1Clk, 0x0000, 0x2000>(apb_address); + UpdatePinmuxPad<PinmuxPadIndex_Sdmmc1Cmd, 0x0000, 0x2000>(apb_address); + UpdatePinmuxPad<PinmuxPadIndex_Sdmmc1Dat3, 0x0000, 0x2000>(apb_address); + UpdatePinmuxPad<PinmuxPadIndex_Sdmmc1Dat2, 0x0000, 0x2000>(apb_address); + UpdatePinmuxPad<PinmuxPadIndex_Sdmmc1Dat1, 0x0000, 0x2000>(apb_address); + UpdatePinmuxPad<PinmuxPadIndex_Sdmmc1Dat0, 0x0000, 0x2000>(apb_address); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.hpp new file mode 100644 index 00000000..3ac4d5d1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::sdmmc::impl { + + Result SetSdCardVoltageEnabled(bool en); + Result SetSdCardVoltageValue(u32 micro_volts); + + namespace pinmux_impl { + + enum PinAssignment { + PinAssignment_Sdmmc1OutputHigh = 2, + PinAssignment_Sdmmc1ResetState = 3, + PinAssignment_Sdmmc1SchmtEnable = 4, + PinAssignment_Sdmmc1SchmtDisable = 5, + }; + + void SetPinAssignment(PinAssignment assignment); + + } + + namespace gpio_impl { + + enum GpioValue { + GpioValue_Low = 0, + GpioValue_High = 1 + }; + + enum Direction { + Direction_Input = 0, + Direction_Output = 1, + }; + + enum GpioPadName { + GpioPadName_PowSdEn = 2, + }; + + void OpenSession(GpioPadName pad); + void CloseSession(GpioPadName pad); + + void SetDirection(GpioPadName pad, Direction direction); + void SetValue(GpioPadName pad, GpioValue value); + + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_mmc_device_accessor.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_mmc_device_accessor.cpp new file mode 100644 index 00000000..2e2b8429 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_mmc_device_accessor.cpp @@ -0,0 +1,728 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#include "sdmmc_mmc_device_accessor.hpp" +#include "sdmmc_timer.hpp" + +namespace ams::sdmmc::impl { + + #if defined(AMS_SDMMC_THREAD_SAFE) + + #define AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX() std::scoped_lock lk(m_mmc_device.m_device_mutex) + + #else + + #define AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX() + + #endif + + namespace { + + constexpr inline u32 OcrCardPowerUpStatus = (1 << 31); + + constexpr inline u32 OcrAccessMode_Mask = (3 << 29); + constexpr inline u32 OcrAccessMode_SectorMode = (2 << 29); + + constexpr inline u8 ManufacturerId_Toshiba = 0x11; + + enum DeviceType : u8 { + DeviceType_HighSpeed26MHz = (1u << 0), + DeviceType_HighSpeed52MHz = (1u << 1), + DeviceType_HighSpeedDdr52MHz1_8VOr3_0V = (1u << 2), + DeviceType_HighSpeedDdr52MHz1_2V = (1u << 3), + DeviceType_Hs200Sdr200MHz1_8V = (1u << 4), + DeviceType_Hs200Sdr200MHz1_2V = (1u << 5), + DeviceType_Hs400Sdr200MHz1_8V = (1u << 6), + DeviceType_Hs400Sdr200MHz1_2V = (1u << 7), + }; + + constexpr bool IsToshibaMmc(const u8 *cid) { + /* Check whether the CID's manufacturer id field is Toshiba. */ + AMS_ABORT_UNLESS(cid != nullptr); + return cid[14] == ManufacturerId_Toshiba; + } + + constexpr bool IsLessThanSpecification4(const u8 *csd) { + const u8 spec_vers = ((csd[14] >> 2) & 0xF); + return spec_vers < 4; + } + + constexpr bool IsBkopAutoEnable(const u8 *ext_csd) { + /* Check the AUTO_EN bit of BKOPS_EN. */ + return (ext_csd[163] & (1u << 1)) != 0; + } + + constexpr u8 GetDeviceType(const u8 *ext_csd) { + /* Get the DEVICE_TYPE register. */ + AMS_ABORT_UNLESS(ext_csd != nullptr); + return ext_csd[196]; + } + + constexpr bool IsSupportedHs400(u8 device_type) { + return (device_type & DeviceType_Hs400Sdr200MHz1_8V) != 0; + } + + constexpr bool IsSupportedHs200(u8 device_type) { + return (device_type & DeviceType_Hs200Sdr200MHz1_8V) != 0; + } + + constexpr bool IsSupportedHighSpeed(u8 device_type) { + return (device_type & DeviceType_HighSpeed52MHz) != 0; + } + + constexpr u32 GetMemoryCapacityFromExtCsd(const u32 *ext_csd) { + /* Get the SEC_COUNT register. */ + AMS_ABORT_UNLESS(ext_csd != nullptr); + return ext_csd[212 / sizeof(u32)]; + } + + constexpr u32 GetBootPartitionMemoryCapacityFromExtCsd(const u8 *ext_csd) { + /* Get the BOOT_SIZE_MULT register. */ + AMS_ABORT_UNLESS(ext_csd != nullptr); + return ext_csd[226] * (128_KB / SectorSize); + } + + constexpr Result GetCurrentSpeedModeFromExtCsd(SpeedMode *out, const u8 *ext_csd) { + /* Get the HS_TIMING register. */ + AMS_ABORT_UNLESS(out != nullptr); + AMS_ABORT_UNLESS(ext_csd != nullptr); + + switch (ext_csd[185] & 0xF) { + case 0: *out = SpeedMode_MmcLegacySpeed; break; + case 1: *out = SpeedMode_MmcHighSpeed; break; + case 2: *out = SpeedMode_MmcHs200; break; + case 3: *out = SpeedMode_MmcHs400; break; + default: R_THROW(sdmmc::ResultUnexpectedMmcExtendedCsdValue()); + } + + R_SUCCEED(); + } + + } + + void MmcDevice::SetOcrAndHighCapacity(u32 ocr) { + /* Set ocr. */ + BaseDevice::SetOcr(ocr); + + /* Set high capacity. */ + BaseDevice::SetHighCapacity((ocr & OcrAccessMode_Mask) == OcrAccessMode_SectorMode); + } + + Result MmcDeviceAccessor::IssueCommandSendOpCond(u32 *out_ocr, BusPower bus_power) const { + /* Get the command argument. */ + u32 arg = OcrAccessMode_SectorMode; + switch (bus_power) { + case BusPower_1_8V: arg |= 0x000080; break; + case BusPower_3_3V: arg |= 0x03F800; break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R3; + Command command(CommandIndex_SendOpCond, arg, CommandResponseType, false); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command))); + + /* Get the response. */ + hc->GetLastResponse(out_ocr, sizeof(*out_ocr), CommandResponseType); + + R_SUCCEED(); + } + + Result MmcDeviceAccessor::IssueCommandSetRelativeAddr() const { + /* Get rca. */ + const u32 rca = m_mmc_device.GetRca(); + AMS_ABORT_UNLESS(rca > 0); + + /* Issue comamnd. */ + const u32 arg = rca << 16; + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_SetRelativeAddr, arg, false, DeviceState_Unknown)); + + R_SUCCEED(); + } + + Result MmcDeviceAccessor::IssueCommandSwitch(CommandSwitch cs) const { + /* Get the command argument. */ + const u32 arg = GetCommandSwitchArgument(cs); + + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_Switch, arg, true, DeviceState_Unknown)); + + R_SUCCEED(); + } + + Result MmcDeviceAccessor::IssueCommandSendExtCsd(void *dst, size_t dst_size) const { + /* Validate the output buffer. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= MmcExtendedCsdSize); + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(CommandIndex_SendExtCsd, 0, CommandResponseType, false); + TransferData xfer_data(dst, MmcExtendedCsdSize, 1, TransferDirection_ReadFromDevice); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + R_TRY(m_mmc_device.CheckDeviceStatus(resp)); + + R_SUCCEED(); + } + + Result MmcDeviceAccessor::IssueCommandEraseGroupStart(u32 sector_index) const { + /* Get the command argument. */ + const u32 arg = m_mmc_device.IsHighCapacity() ? sector_index : sector_index * SectorSize; + + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_EraseGroupStart, arg, false, DeviceState_Unknown)); + + R_SUCCEED(); + } + + Result MmcDeviceAccessor::IssueCommandEraseGroupEnd(u32 sector_index) const { + /* Get the command argument. */ + const u32 arg = m_mmc_device.IsHighCapacity() ? sector_index : sector_index * SectorSize; + + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_EraseGroupEnd, arg, false, DeviceState_Tran)); + + R_SUCCEED(); + } + + Result MmcDeviceAccessor::IssueCommandErase() const { + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_Erase, 0, false, DeviceState_Tran)); + + R_SUCCEED(); + } + + Result MmcDeviceAccessor::CancelToshibaMmcModel() { + /* Special erase sequence done by Nintendo on Toshiba MMCs. */ + R_TRY(this->IssueCommandSwitch(CommandSwitch_SetBitsProductionStateAwarenessEnable)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + R_TRY(this->IssueCommandSwitch(CommandSwitch_ClearBitsAutoModeEnable)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteProductionStateAwarenessPreSolderingWrites)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteProductionStateAwarenessPreSolderingPostWrites)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteProductionStateAwarenessNormal)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + R_SUCCEED(); + } + + Result MmcDeviceAccessor::ChangeToReadyState(BusPower bus_power) { + /* Be prepared to wait up to 1.5 seconds to change state. */ + ManualTimer timer(1500); + while (true) { + /* Get the ocr, and check if we're done. */ + u32 ocr; + R_TRY(this->IssueCommandSendOpCond(std::addressof(ocr), bus_power)); + if ((ocr & OcrCardPowerUpStatus) != 0) { + m_mmc_device.SetOcrAndHighCapacity(ocr); + R_SUCCEED(); + } + + /* Check if we've timed out. */ + R_UNLESS(timer.Update(), sdmmc::ResultMmcInitializationSoftwareTimeout()); + + /* Try again in 1ms. */ + WaitMicroSeconds(1000); + } + } + + Result MmcDeviceAccessor::ExtendBusWidth(BusWidth max_bw) { + /* If the maximum bus width is 1bit, we can't extend. */ + R_SUCCEED_IF(max_bw == BusWidth_1Bit); + + /* Determine what bus width to switch to. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + BusWidth target_bw = BusWidth_1Bit; + CommandSwitch cs; + if (max_bw == BusWidth_8Bit && hc->IsSupportedBusWidth(BusWidth_8Bit)) { + target_bw = BusWidth_8Bit; + cs = CommandSwitch_WriteBusWidth8Bit; + } else if ((max_bw == BusWidth_8Bit || max_bw == BusWidth_4Bit) && hc->IsSupportedBusWidth(BusWidth_4Bit)) { + target_bw = BusWidth_4Bit; + cs = CommandSwitch_WriteBusWidth4Bit; + } else { + /* Target bus width is 1bit. */ + R_SUCCEED(); + } + + /* Set the bus width. */ + R_TRY(this->IssueCommandSwitch(cs)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + hc->SetBusWidth(target_bw); + + R_SUCCEED(); + } + + Result MmcDeviceAccessor::EnableBkopsAuto() { + /* Issue the command. */ + R_TRY(this->IssueCommandSwitch(CommandSwitch_SetBitsBkopsEnAutoEn)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + R_SUCCEED(); + } + + Result MmcDeviceAccessor::ChangeToHighSpeed(bool check_before) { + /* Issue high speed command. */ + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteHsTimingHighSpeed)); + + /* If we should check status before setting mode, do so. */ + if (check_before) { + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + } + + /* Set the host controller to high speed. */ + R_TRY(BaseDeviceAccessor::GetHostController()->SetSpeedMode(SpeedMode_MmcHighSpeed)); + + /* If we should check status after setting mode, do so. */ + if (!check_before) { + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + } + + R_SUCCEED(); + } + + Result MmcDeviceAccessor::ChangeToHs200() { + /* Issue Hs200 command. */ + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteHsTimingHs200)); + + /* Set the host controller to Hs200. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->SetSpeedMode(SpeedMode_MmcHs200)); + + /* Perform tuning using command index 21. */ + R_TRY(hc->Tuning(SpeedMode_MmcHs200, 21)); + + /* Check status. */ + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + R_SUCCEED(); + } + + Result MmcDeviceAccessor::ChangeToHs400() { + /* Change first to Hs200. */ + R_TRY(this->ChangeToHs200()); + + /* Save tuning status. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + hc->SaveTuningStatusForHs400(); + + /* Change to high speed. */ + R_TRY(this->ChangeToHighSpeed(false)); + + /* Issue Hs400 command. */ + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteBusWidth8BitDdr)); + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteHsTimingHs400)); + + /* Set the host controller to Hs400. */ + R_TRY(hc->SetSpeedMode(SpeedMode_MmcHs400)); + + /* Check status. */ + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + R_SUCCEED(); + } + + Result MmcDeviceAccessor::ExtendBusSpeed(u8 device_type, SpeedMode max_sm) { + /* We want to switch to the highest speed we can. */ + /* Check Hs400/Hs200 first. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + if (hc->IsSupportedTuning() && hc->GetBusPower() == BusPower_1_8V) { + if (hc->GetBusWidth() == BusWidth_8Bit && IsSupportedHs400(device_type) && max_sm == SpeedMode_MmcHs400) { + R_RETURN(this->ChangeToHs400()); + } else if ((hc->GetBusWidth() == BusWidth_8Bit || hc->GetBusWidth() == BusWidth_4Bit) && IsSupportedHs200(device_type) && (max_sm == SpeedMode_MmcHs400 || max_sm == SpeedMode_MmcHs200)) { + R_RETURN(this->ChangeToHs200()); + } + } + + /* Check if we can switch to high speed. */ + if (IsSupportedHighSpeed(device_type)) { + R_RETURN(this->ChangeToHighSpeed(true)); + } + + /* We can't, so stay at normal speeds. */ + R_SUCCEED(); + } + + Result MmcDeviceAccessor::StartupMmcDevice(BusWidth max_bw, SpeedMode max_sm, void *wb, size_t wb_size) { + /* Start up at an appropriate bus power. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + const BusPower bp = hc->IsSupportedBusPower(BusPower_1_8V) ? BusPower_1_8V : BusPower_3_3V; + R_TRY(hc->Startup(bp, BusWidth_1Bit, SpeedMode_MmcIdentification, false)); + + /* Wait 1ms for configuration to take. */ + WaitMicroSeconds(1000); + + /* Wait an additional 74 clocks for configuration to take. */ + WaitClocks(74, hc->GetDeviceClockFrequencyKHz()); + + /* Go to idle state. */ + R_TRY(BaseDeviceAccessor::IssueCommandGoIdleState()); + m_current_partition = MmcPartition_UserData; + + /* Go to ready state. */ + R_TRY(this->ChangeToReadyState(bp)); + + /* Get the CID. */ + R_TRY(BaseDeviceAccessor::IssueCommandAllSendCid(wb, wb_size)); + m_mmc_device.SetCid(wb, wb_size); + const bool is_toshiba = IsToshibaMmc(static_cast<const u8 *>(wb)); + + /* Issue set relative addr. */ + R_TRY(this->IssueCommandSetRelativeAddr()); + + /* Get the CSD. */ + R_TRY(BaseDeviceAccessor::IssueCommandSendCsd(wb, wb_size)); + m_mmc_device.SetCsd(wb, wb_size); + const bool spec_under_4 = IsLessThanSpecification4(static_cast<const u8 *>(wb)); + + /* Set the speed mode to legacy. */ + R_TRY(hc->SetSpeedMode(SpeedMode_MmcLegacySpeed)); + + /* Issue select card command. */ + R_TRY(BaseDeviceAccessor::IssueCommandSelectCard()); + + /* Set block length to sector size. */ + R_TRY(BaseDeviceAccessor::IssueCommandSetBlockLenToSectorSize()); + + /* If the device SPEC_VERS is less than 4, extended csd/switch aren't supported. */ + if (spec_under_4) { + R_TRY(m_mmc_device.SetLegacyMemoryCapacity()); + + m_mmc_device.SetActive(); + R_SUCCEED(); + } + + /* Extend the bus width to the largest that we can. */ + R_TRY(this->ExtendBusWidth(max_bw)); + + /* Get the extended csd. */ + R_TRY(this->IssueCommandSendExtCsd(wb, wb_size)); + AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast<uintptr_t>(wb), alignof(u32))); + m_mmc_device.SetMemoryCapacity(GetMemoryCapacityFromExtCsd(static_cast<const u32 *>(wb))); + + /* If the mmc is manufactured by toshiba, try to enable bkops auto. */ + if (is_toshiba && !IsBkopAutoEnable(static_cast<const u8 *>(wb))) { + /* NOTE: Nintendo does not check the result of this. */ + this->EnableBkopsAuto(); + } + + /* Extend the bus speed to as fast as we can. */ + const u8 device_type = GetDeviceType(static_cast<const u8 *>(wb)); + R_TRY(this->ExtendBusSpeed(device_type, max_sm)); + + /* Enable power saving. */ + hc->SetPowerSaving(true); + + R_SUCCEED(); + } + + Result MmcDeviceAccessor::OnActivate() { + /* Define the possible startup parameters. */ + constexpr const struct { + BusWidth bus_width; + SpeedMode speed_mode; + } StartupParameters[] = { + #if defined(AMS_SDMMC_ENABLE_MMC_HS400) + { BusWidth_8Bit, SpeedMode_MmcHs400 }, + #else + { BusWidth_8Bit, SpeedMode_MmcHighSpeed }, + #endif + { BusWidth_8Bit, SpeedMode_MmcHighSpeed }, + { BusWidth_1Bit, SpeedMode_MmcHighSpeed }, + }; + + /* Try to start up with each set of parameters. */ + Result result; + for (int i = 0; i < static_cast<int>(util::size(StartupParameters)); ++i) { + /* Alias the parameters. */ + const auto ¶ms = StartupParameters[i]; + + /* Set our max bus width/speed mode. */ + m_max_bus_width = params.bus_width; + m_max_speed_mode = params.speed_mode; + + /* Try to start up the device. */ + result = this->StartupMmcDevice(m_max_bus_width, m_max_speed_mode, m_work_buffer, m_work_buffer_size); + if (R_SUCCEEDED(result)) { + /* If we previously failed to start up the device, log the error correction. */ + if (i != 0) { + BaseDeviceAccessor::PushErrorLog(true, "S %d %d:0", m_max_bus_width, m_max_speed_mode); + BaseDeviceAccessor::IncrementNumActivationErrorCorrections(); + } + + R_SUCCEED(); + } + + /* Log that our startup failed. */ + BaseDeviceAccessor::PushErrorLog(false, "S %d %d:%X", m_max_bus_width, m_max_speed_mode, result.GetValue()); + + /* Shut down the host controller before we try to start up again. */ + BaseDeviceAccessor::GetHostController()->Shutdown(); + } + + /* We failed to start up with all sets of parameters. */ + BaseDeviceAccessor::PushErrorTimeStamp(); + + R_RETURN(result); + } + + Result MmcDeviceAccessor::OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) { + /* Get the sector index alignment. */ + u32 sector_index_alignment = 0; + if (!is_read) { + constexpr u32 MmcWriteSectorAlignment = 16_KB / SectorSize; + sector_index_alignment = MmcWriteSectorAlignment; + AMS_ABORT_UNLESS(util::IsAligned(sector_index, MmcWriteSectorAlignment)); + } + + /* Do the read/write. */ + R_RETURN(BaseDeviceAccessor::ReadWriteMultiple(sector_index, num_sectors, sector_index_alignment, buf, buf_size, is_read)); + } + + Result MmcDeviceAccessor::ReStartup() { + /* Shut down the host controller. */ + BaseDeviceAccessor::GetHostController()->Shutdown(); + + /* Perform start up. */ + Result result = this->StartupMmcDevice(m_max_bus_width, m_max_speed_mode, m_work_buffer, m_work_buffer_size); + if (R_FAILED(result)) { + BaseDeviceAccessor::PushErrorLog(false, "S %d %d:%X", m_max_bus_width, m_max_speed_mode, result.GetValue()); + R_RETURN(result); + } + + R_SUCCEED(); + } + + void MmcDeviceAccessor::Initialize() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* If we've already initialized, we don't need to do anything. */ + if (m_is_initialized) { + return; + } + + /* Set the base device to our mmc device. */ + BaseDeviceAccessor::SetDevice(std::addressof(m_mmc_device)); + + /* Initialize. */ + BaseDeviceAccessor::GetHostController()->Initialize(); + m_is_initialized = true; + } + + void MmcDeviceAccessor::Finalize() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* If we've already finalized, we don't need to do anything. */ + if (!m_is_initialized) { + return; + } + m_is_initialized = false; + + /* Deactivate the device. */ + BaseDeviceAccessor::Deactivate(); + + /* Finalize the host controller. */ + BaseDeviceAccessor::GetHostController()->Finalize(); + } + + Result MmcDeviceAccessor::GetSpeedMode(SpeedMode *out_speed_mode) const { + /* Check that we can write to output. */ + AMS_ABORT_UNLESS(out_speed_mode != nullptr); + + /* Get the current speed mode from the ext csd. */ + R_TRY(GetMmcExtendedCsd(m_work_buffer, m_work_buffer_size)); + R_TRY(GetCurrentSpeedModeFromExtCsd(out_speed_mode, static_cast<const u8 *>(m_work_buffer))); + + R_SUCCEED(); + } + + void MmcDeviceAccessor::PutMmcToSleep() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* If the device isn't awake, we don't need to do anything. */ + if (!m_mmc_device.IsAwake()) { + return; + } + + /* Put the device to sleep. */ + m_mmc_device.PutToSleep(); + + /* If necessary, put the host controller to sleep. */ + if (m_mmc_device.IsActive()) { + BaseDeviceAccessor::GetHostController()->PutToSleep(); + } + } + + void MmcDeviceAccessor::AwakenMmc() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* If the device is awake, we don't need to do anything. */ + if (m_mmc_device.IsAwake()) { + return; + } + + /* Wake the host controller, if we need to.*/ + if (m_mmc_device.IsActive()) { + const Result result = BaseDeviceAccessor::GetHostController()->Awaken(); + if (R_FAILED(result)) { + BaseDeviceAccessor::PushErrorLog(true, "A:%X", result.GetValue()); + } + } + + /* Wake the device. */ + m_mmc_device.Awaken(); + } + + Result MmcDeviceAccessor::SelectMmcPartition(MmcPartition part) { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* Check that we can access the device. */ + R_TRY(m_mmc_device.CheckAccessible()); + + /* Determine the appropriate SWITCH subcommand. */ + CommandSwitch cs; + switch (part) { + case MmcPartition_UserData: cs = CommandSwitch_WritePartitionAccessDefault; break; + case MmcPartition_BootPartition1: cs = CommandSwitch_WritePartitionAccessRwBootPartition1; break; + case MmcPartition_BootPartition2: cs = CommandSwitch_WritePartitionAccessRwBootPartition2; break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Change partition. */ + m_current_partition = MmcPartition_Unknown; + { + R_TRY(this->IssueCommandSwitch(cs)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + } + m_current_partition = part; + + R_SUCCEED(); + } + + Result MmcDeviceAccessor::EraseMmc() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* Check that we can access the device. */ + R_TRY(m_mmc_device.CheckAccessible()); + + /* Get the partition capacity. */ + u32 part_capacity; + switch (m_current_partition) { + case MmcPartition_UserData: + part_capacity = m_mmc_device.GetMemoryCapacity(); + break; + case MmcPartition_BootPartition1: + case MmcPartition_BootPartition2: + R_TRY(this->GetMmcBootPartitionCapacity(std::addressof(part_capacity))); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Begin the erase. */ + R_TRY(this->IssueCommandEraseGroupStart(0)); + R_TRY(this->IssueCommandEraseGroupEnd(part_capacity - 1)); + + /* Issue the erase command, allowing 30 seconds for it to complete. */ + ManualTimer timer(30000); + Result result = this->IssueCommandErase(); + R_TRY_CATCH(result) { + R_CATCH(sdmmc::ResultDataTimeoutError) { /* Data timeout error is acceptable. */ } + R_CATCH(sdmmc::ResultCommandCompleteSoftwareTimeout) { /* Command complete software timeout error is acceptable. */ } + R_CATCH(sdmmc::ResultBusySoftwareTimeout) { /* Busy software timeout error is acceptable. */ } + } R_END_TRY_CATCH; + + /* Wait for the erase to finish. */ + while (true) { + /* Check if we're done. */ + result = BaseDeviceAccessor::IssueCommandSendStatus(); + if (R_SUCCEEDED(result)) { + break; + } + + /* Otherwise, check if we should reject the error. */ + if (!sdmmc::ResultUnexpectedDeviceState::Includes(result)) { + R_RETURN(result); + } + + /* Check if timeout has been exceeded. */ + R_UNLESS(timer.Update(), sdmmc::ResultMmcEraseSoftwareTimeout()); + } + + /* If the partition is user data, check if we need to perform toshiba-specific erase. */ + if (m_current_partition == MmcPartition_UserData) { + u8 cid[DeviceCidSize]; + m_mmc_device.GetCid(cid, sizeof(cid)); + if (IsToshibaMmc(cid)) { + /* NOTE: Nintendo does not check the result of this operation. */ + this->CancelToshibaMmcModel(); + } + } + + R_SUCCEED(); + } + + Result MmcDeviceAccessor::GetMmcBootPartitionCapacity(u32 *out_num_sectors) const { + /* Get the capacity from the extended csd. */ + AMS_ABORT_UNLESS(out_num_sectors != nullptr); + R_TRY(this->GetMmcExtendedCsd(m_work_buffer, m_work_buffer_size)); + + *out_num_sectors = GetBootPartitionMemoryCapacityFromExtCsd(static_cast<const u8 *>(m_work_buffer)); + R_SUCCEED(); + } + + Result MmcDeviceAccessor::GetMmcExtendedCsd(void *dst, size_t dst_size) const { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* Check that we can access the device. */ + R_TRY(m_mmc_device.CheckAccessible()); + + /* Get the csd. */ + u8 csd[DeviceCsdSize]; + m_mmc_device.GetCsd(csd, sizeof(csd)); + + /* Check that the card supports ext csd. */ + R_UNLESS(!IsLessThanSpecification4(csd), sdmmc::ResultMmcNotSupportExtendedCsd()); + + /* Get the ext csd. */ + R_TRY(this->IssueCommandSendExtCsd(dst, dst_size)); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_mmc_device_accessor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_mmc_device_accessor.hpp new file mode 100644 index 00000000..58aade78 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_mmc_device_accessor.hpp @@ -0,0 +1,143 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include "sdmmc_base_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + class MmcDevice : public BaseDevice { + private: + static constexpr u16 Rca = 2; + public: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual os::EventType *GetRemovedEvent() const override { + /* Mmc can't be removed. */ + return nullptr; + } + #endif + + virtual DeviceType GetDeviceType() const override { + return DeviceType_Mmc; + } + + virtual u16 GetRca() const override { + return Rca; + } + + void SetOcrAndHighCapacity(u32 ocr); + }; + + class MmcDeviceAccessor : public BaseDeviceAccessor { + private: + MmcDevice m_mmc_device; + void *m_work_buffer; + size_t m_work_buffer_size; + BusWidth m_max_bus_width; + SpeedMode m_max_speed_mode; + MmcPartition m_current_partition; + bool m_is_initialized; + private: + enum CommandSwitch { + CommandSwitch_SetBitsProductionStateAwarenessEnable = 0, + CommandSwitch_ClearBitsAutoModeEnable = 1, + CommandSwitch_WriteProductionStateAwarenessNormal = 2, + CommandSwitch_WriteProductionStateAwarenessPreSolderingWrites = 3, + CommandSwitch_WriteProductionStateAwarenessPreSolderingPostWrites = 4, + CommandSwitch_SetBitsBkopsEnAutoEn = 5, + CommandSwitch_WriteBusWidth1Bit = 6, + CommandSwitch_WriteBusWidth4Bit = 7, + CommandSwitch_WriteBusWidth8Bit = 8, + CommandSwitch_WriteBusWidth8BitDdr = 9, + CommandSwitch_WriteHsTimingLegacySpeed = 10, + CommandSwitch_WriteHsTimingHighSpeed = 11, + CommandSwitch_WriteHsTimingHs200 = 12, + CommandSwitch_WriteHsTimingHs400 = 13, + CommandSwitch_WritePartitionAccessDefault = 14, + CommandSwitch_WritePartitionAccessRwBootPartition1 = 15, + CommandSwitch_WritePartitionAccessRwBootPartition2 = 16, + }; + + static constexpr ALWAYS_INLINE u32 GetCommandSwitchArgument(CommandSwitch cs) { + switch (cs) { + case CommandSwitch_SetBitsProductionStateAwarenessEnable: return 0x01111000; + case CommandSwitch_ClearBitsAutoModeEnable: return 0x02112000; + case CommandSwitch_WriteProductionStateAwarenessNormal: return 0x03850000; + case CommandSwitch_WriteProductionStateAwarenessPreSolderingWrites: return 0x03850100; + case CommandSwitch_WriteProductionStateAwarenessPreSolderingPostWrites: return 0x03850200; + case CommandSwitch_SetBitsBkopsEnAutoEn: return 0x01A30200; + case CommandSwitch_WriteBusWidth1Bit: return 0x03B70000; + case CommandSwitch_WriteBusWidth4Bit: return 0x03B70100; + case CommandSwitch_WriteBusWidth8Bit: return 0x03B70200; + case CommandSwitch_WriteBusWidth8BitDdr: return 0x03B70600; + case CommandSwitch_WriteHsTimingLegacySpeed: return 0x03B90000; + case CommandSwitch_WriteHsTimingHighSpeed: return 0x03B90100; + case CommandSwitch_WriteHsTimingHs200: return 0x03B90200; + case CommandSwitch_WriteHsTimingHs400: return 0x03B90300; + case CommandSwitch_WritePartitionAccessDefault: return 0x03B30000; + case CommandSwitch_WritePartitionAccessRwBootPartition1: return 0x03B30100; + case CommandSwitch_WritePartitionAccessRwBootPartition2: return 0x03B30200; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + private: + Result IssueCommandSendOpCond(u32 *out_ocr, BusPower bus_power) const; + Result IssueCommandSetRelativeAddr() const; + Result IssueCommandSwitch(CommandSwitch cs) const; + Result IssueCommandSendExtCsd(void *dst, size_t dst_size) const; + Result IssueCommandEraseGroupStart(u32 sector_index) const; + Result IssueCommandEraseGroupEnd(u32 sector_index) const; + Result IssueCommandErase() const; + Result CancelToshibaMmcModel(); + Result ChangeToReadyState(BusPower bus_power); + Result ExtendBusWidth(BusWidth max_bus_width); + Result EnableBkopsAuto(); + Result ChangeToHighSpeed(bool check_before); + Result ChangeToHs200(); + Result ChangeToHs400(); + Result ExtendBusSpeed(u8 device_type, SpeedMode max_sm); + Result StartupMmcDevice(BusWidth max_bw, SpeedMode max_sm, void *wb, size_t wb_size); + protected: + virtual Result OnActivate() override; + virtual Result OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) override; + virtual Result ReStartup() override; + public: + virtual void Initialize() override; + virtual void Finalize() override; + virtual Result GetSpeedMode(SpeedMode *out_speed_mode) const override; + public: + explicit MmcDeviceAccessor(IHostController *hc) + : BaseDeviceAccessor(hc), m_work_buffer(nullptr), m_work_buffer_size(0), + m_max_bus_width(BusWidth_8Bit), m_max_speed_mode(SpeedMode_MmcHs400), m_current_partition(MmcPartition_Unknown), + m_is_initialized(false) + { + /* ... */ + } + + void SetMmcWorkBuffer(void *wb, size_t wb_size) { + m_work_buffer = wb; + m_work_buffer_size = wb_size; + } + + void PutMmcToSleep(); + void AwakenMmc(); + Result SelectMmcPartition(MmcPartition part); + Result EraseMmc(); + Result GetMmcBootPartitionCapacity(u32 *out_num_sectors) const; + Result GetMmcExtendedCsd(void *dst, size_t dst_size) const; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_port_gc_asic0.board.nintendo_nx.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_port_gc_asic0.board.nintendo_nx.cpp new file mode 100644 index 00000000..ef4d5d79 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_port_gc_asic0.board.nintendo_nx.cpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#include "sdmmc_port_gc_asic0.hpp" +#include "sdmmc_select_sdmmc_controller.hpp" + + +namespace ams::sdmmc::impl { + + namespace { + + SdmmcControllerForPortGcAsic0 g_gc_asic0_host_controller; + GcAsicDeviceAccessor g_gc_asic0_device_accessor(std::addressof(g_gc_asic0_host_controller)); + + } + + IHostController *GetHostControllerOfPortGcAsic0() { + return std::addressof(g_gc_asic0_host_controller); + } + + IDeviceAccessor *GetDeviceAccessorOfPortGcAsic0() { + return std::addressof(g_gc_asic0_device_accessor); + } + + GcAsicDeviceAccessor *GetGcAsicDeviceAccessorOfPortGcAsic0() { + return std::addressof(g_gc_asic0_device_accessor); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_port_gc_asic0.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_port_gc_asic0.hpp new file mode 100644 index 00000000..3a79262b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_port_gc_asic0.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include "sdmmc_i_host_controller.hpp" +#include "sdmmc_i_device_accessor.hpp" +#include "sdmmc_gc_asic_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + IHostController *GetHostControllerOfPortGcAsic0(); + IDeviceAccessor *GetDeviceAccessorOfPortGcAsic0(); + GcAsicDeviceAccessor *GetGcAsicDeviceAccessorOfPortGcAsic0(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.board.nintendo_nx.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.board.nintendo_nx.cpp new file mode 100644 index 00000000..0f6003ce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.board.nintendo_nx.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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#include "sdmmc_port_mmc0.hpp" +#include "sdmmc_select_sdmmc_controller.hpp" +#include "sdmmc_base_device_accessor.hpp" + + +namespace ams::sdmmc::impl { + + namespace { + + SdmmcControllerForPortMmc0 g_mmc0_host_controller; + MmcDeviceAccessor g_mmc0_device_accessor(std::addressof(g_mmc0_host_controller)); + + } + + IHostController *GetHostControllerOfPortMmc0() { + return std::addressof(g_mmc0_host_controller); + } + + IDeviceAccessor *GetDeviceAccessorOfPortMmc0() { + return std::addressof(g_mmc0_device_accessor); + } + + MmcDeviceAccessor *GetMmcDeviceAccessorOfPortMmc0() { + return std::addressof(g_mmc0_device_accessor); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.hpp new file mode 100644 index 00000000..5add8d06 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include "sdmmc_i_host_controller.hpp" +#include "sdmmc_i_device_accessor.hpp" +#include "sdmmc_mmc_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + IHostController *GetHostControllerOfPortMmc0(); + IDeviceAccessor *GetDeviceAccessorOfPortMmc0(); + MmcDeviceAccessor *GetMmcDeviceAccessorOfPortMmc0(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_port_sd_card0.board.nintendo_nx.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_port_sd_card0.board.nintendo_nx.cpp new file mode 100644 index 00000000..ce0a6d2a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_port_sd_card0.board.nintendo_nx.cpp @@ -0,0 +1,63 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#include "sdmmc_port_sd_card0.hpp" +#include "sdmmc_select_sdmmc_controller.hpp" + + +namespace ams::sdmmc::impl { + + namespace { + + SdmmcControllerForPortSdCard0 g_sd_card0_host_controller; + + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + + constexpr inline u32 SdCard0DebounceMilliSeconds = 128; + DeviceDetector g_sd_card0_detector(gpio::DeviceCode_SdCd, gpio::GpioValue_Low, SdCard0DebounceMilliSeconds); + + SdCardDeviceAccessor g_sd_card0_device_accessor(std::addressof(g_sd_card0_host_controller), std::addressof(g_sd_card0_detector)); + + #else + + SdCardDeviceAccessor g_sd_card0_device_accessor(std::addressof(g_sd_card0_host_controller)); + + #endif + + + } + + IHostController *GetHostControllerOfPortSdCard0() { + return std::addressof(g_sd_card0_host_controller); + } + + IDeviceAccessor *GetDeviceAccessorOfPortSdCard0() { + return std::addressof(g_sd_card0_device_accessor); + } + + SdCardDeviceAccessor *GetSdCardDeviceAccessorOfPortSdCard0() { + return std::addressof(g_sd_card0_device_accessor); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_port_sd_card0.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_port_sd_card0.hpp new file mode 100644 index 00000000..902529c6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_port_sd_card0.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include "sdmmc_i_host_controller.hpp" +#include "sdmmc_i_device_accessor.hpp" +#include "sdmmc_sd_card_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + IHostController *GetHostControllerOfPortSdCard0(); + IDeviceAccessor *GetDeviceAccessorOfPortSdCard0(); + SdCardDeviceAccessor *GetSdCardDeviceAccessorOfPortSdCard0(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_card_device_accessor.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_card_device_accessor.cpp new file mode 100644 index 00000000..69467e54 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_card_device_accessor.cpp @@ -0,0 +1,1056 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#include "sdmmc_sd_card_device_accessor.hpp" +#include "sdmmc_timer.hpp" + +namespace ams::sdmmc::impl { + + #if defined(AMS_SDMMC_THREAD_SAFE) + + #define AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX() std::scoped_lock lk(m_sd_card_device.m_device_mutex) + + #else + + #define AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX() + + #endif + + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + + #define AMS_SDMMC_CHECK_SD_CARD_REMOVED() R_UNLESS(!m_sd_card_device.IsRemoved(), sdmmc::ResultDeviceRemoved()) + + #else + + #define AMS_SDMMC_CHECK_SD_CARD_REMOVED() + + #endif + + namespace { + + constexpr inline u32 OcrCardPowerUpStatus = (1 << 31); + constexpr inline u32 OcrCardCapacityStatus = (1 << 30); + constexpr inline u32 OcrSwitchingTo1_8VAccepted = (1 << 24); + + constexpr bool IsLessThanSpecification1_1(const u8 *scr) { + AMS_ABORT_UNLESS(scr != nullptr); + + const u8 sd_spec = scr[0] & 0xF; + return sd_spec < 1; + } + + constexpr u32 GetSendOpCmdArgument(bool spec_under_2, bool uhs_i_supported) { + const u32 hcs = !spec_under_2 ? (1u << 30) : (0u << 30); + const u32 xpc = !spec_under_2 ? (1u << 28) : (0u << 28); + const u32 s18r = (!spec_under_2 && uhs_i_supported) ? (1u << 24) : (0u << 24); + return hcs | xpc | s18r | 0x00100000u; + } + + constexpr bool IsLessThanCsdVersion2(const u8 *csd) { + AMS_ABORT_UNLESS(csd != nullptr); + + /* Check whether CSD_STRUCTURE is 0. */ + return ((csd[14] & 0xC0) >> 6) == 0; + } + + constexpr u32 GetMemoryCapacityFromCsd(const u16 *csd) { + AMS_ABORT_UNLESS(csd != nullptr); + + /* Get CSIZE, convert appropriately. */ + const u32 csize = (static_cast<u32>(csd[3] & 0x3FFF) << 8) | (static_cast<u32>(csd[2] & 0xFF00) >> 8); + return (1 + csize) << 10; + } + + constexpr u8 GetSdBusWidths(const u8 *scr) { + AMS_ABORT_UNLESS(scr != nullptr); + return scr[1] & 0xF; + } + + constexpr bool IsSupportedBusWidth4Bit(u8 sd_bw) { + return (sd_bw & 0x4) != 0; + } + + constexpr bool IsSupportedAccessMode(const u8 *status, SwitchFunctionAccessMode access_mode) { + AMS_ABORT_UNLESS(status != nullptr); + + return (status[13] & (1u << access_mode)) != 0; + } + + constexpr u8 GetAccessModeFromFunctionSelection(const u8 *status) { + AMS_ABORT_UNLESS(status != nullptr); + return (status[16] & 0xF); + } + + constexpr bool IsAccessModeInFunctionSelection(const u8 *status, SwitchFunctionAccessMode mode) { + return GetAccessModeFromFunctionSelection(status) == static_cast<u8>(mode); + } + + constexpr u16 GetMaximumCurrentConsumption(const u8 *status) { + AMS_ABORT_UNLESS(status != nullptr); + + return (static_cast<u16>(status[0]) << 8) | + (static_cast<u16>(status[1]) << 0); + } + + constexpr u32 GetSizeOfProtectedArea(const u8 *sd_status) { + return (static_cast<u32>(sd_status[4]) << 24) | + (static_cast<u32>(sd_status[5]) << 16) | + (static_cast<u32>(sd_status[6]) << 8) | + (static_cast<u32>(sd_status[7]) << 0); + } + + Result GetCurrentSpeedMode(SpeedMode *out_sm, const u8 *status, bool is_uhs_i) { + AMS_ABORT_UNLESS(out_sm != nullptr); + + /* Get the access mode. */ + switch (static_cast<SwitchFunctionAccessMode>(GetAccessModeFromFunctionSelection(status))) { + case SwitchFunctionAccessMode_Default: + if (is_uhs_i) { + *out_sm = SpeedMode_SdCardSdr12; + } else { + *out_sm = SpeedMode_SdCardDefaultSpeed; + } + break; + case SwitchFunctionAccessMode_HighSpeed: + if (is_uhs_i) { + *out_sm = SpeedMode_SdCardSdr25; + } else { + *out_sm = SpeedMode_SdCardHighSpeed; + } + break; + case SwitchFunctionAccessMode_Sdr50: + *out_sm = SpeedMode_SdCardSdr50; + break; + case SwitchFunctionAccessMode_Sdr104: + *out_sm = SpeedMode_SdCardSdr104; + break; + case SwitchFunctionAccessMode_Ddr50: + *out_sm = SpeedMode_SdCardDdr50; + break; + default: + R_THROW(sdmmc::ResultUnexpectedSdCardSwitchFunctionStatus()); + } + + R_SUCCEED(); + } + + } + + void SdCardDevice::SetOcrAndHighCapacity(u32 ocr) { + /* Set ocr. */ + BaseDevice::SetOcr(ocr); + + /* Set high capacity. */ + BaseDevice::SetHighCapacity((ocr & OcrCardCapacityStatus) != 0); + } + + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + void SdCardDeviceAccessor::RemovedCallback() { + /* Signal that the device was removed. */ + m_sd_card_device.SignalRemovedEvent(); + + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Shut down. */ + BaseDeviceAccessor::GetHostController()->Shutdown(); + } + #endif + + Result SdCardDeviceAccessor::IssueCommandSendRelativeAddr(u16 *out_rca) const { + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R6; + Command command(CommandIndex_SendRelativeAddr, 0, CommandResponseType, false); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + + /* Set the output rca. */ + AMS_ABORT_UNLESS(out_rca != nullptr); + *out_rca = static_cast<u16>(resp >> 16); + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::IssueCommandSendIfCond() const { + /* Get the argument. */ + constexpr u32 SendIfCommandArgument = 0x01AAu; + constexpr u32 SendIfCommandArgumentMask = 0x0FFFu; + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R7; + Command command(CommandIndex_SendIfCond, SendIfCommandArgument, CommandResponseType, false); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + + /* Verify that our argument was returned to us. */ + R_UNLESS((resp & SendIfCommandArgumentMask) == (SendIfCommandArgument & SendIfCommandArgumentMask), sdmmc::ResultSdCardValidationError()); + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::IssueCommandCheckSupportedFunction(void *dst, size_t dst_size) const { + /* Validate the output buffer. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= SdCardSwitchFunctionStatusSize); + + /* Get the argument. */ + constexpr u32 CheckSupportedFunctionArgument = 0x00FFFFFF; + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(CommandIndex_Switch, CheckSupportedFunctionArgument, CommandResponseType, false); + TransferData xfer_data(dst, SdCardSwitchFunctionStatusSize, 1, TransferDirection_ReadFromDevice); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + R_TRY(m_sd_card_device.CheckDeviceStatus(resp)); + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::IssueCommandSwitchAccessMode(void *dst, size_t dst_size, bool set_function, SwitchFunctionAccessMode access_mode) const { + /* Validate the output buffer. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= SdCardSwitchFunctionStatusSize); + + /* Get the argument. */ + const u32 arg = (set_function ? (1u << 31) : (0u << 31)) | 0x00FFFFF0 | static_cast<u32>(access_mode); + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(CommandIndex_Switch, arg, CommandResponseType, false); + TransferData xfer_data(dst, SdCardSwitchFunctionStatusSize, 1, TransferDirection_ReadFromDevice); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + R_TRY(m_sd_card_device.CheckDeviceStatus(resp)); + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::IssueCommandVoltageSwitch() const { + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_VoltageSwitch, 0, false, DeviceState_Ready)); + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::IssueCommandAppCmd(DeviceState expected_state, u32 ignore_mask) const { + /* Get arg. */ + const u32 arg = static_cast<u32>(m_sd_card_device.GetRca()) << 16; + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(CommandIndex_AppCmd, arg, CommandResponseType, false); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + + /* Mask out the ignored status bits. */ + if (ignore_mask != 0) { + resp &= ~ignore_mask; + } + + /* Check the device status. */ + R_TRY(m_sd_card_device.CheckDeviceStatus(resp)); + + /* Check the app command bit. */ + R_UNLESS((resp & DeviceStatus_AppCmd) != 0, sdmmc::ResultUnexpectedSdCardAcmdDisabled()); + + /* Check the device state. */ + if (expected_state != DeviceState_Unknown) { + R_UNLESS(m_sd_card_device.GetDeviceState(resp) == expected_state, sdmmc::ResultUnexpectedDeviceState()); + } + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::IssueCommandSetBusWidth4Bit() const { + /* Issue the application command. */ + constexpr u32 Arg = 0x2; + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(SdApplicationCommandIndex_SetBusWidth, Arg, false, DeviceState_Tran)); + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::IssueCommandSdStatus(void *dst, size_t dst_size) const { + /* Validate the output buffer. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= SdCardSdStatusSize); + + /* Issue the application command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(SdApplicationCommandIndex_SdStatus, 0, CommandResponseType, false); + TransferData xfer_data(dst, SdCardSdStatusSize, 1, TransferDirection_ReadFromDevice); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + R_TRY(m_sd_card_device.CheckDeviceStatus(resp)); + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::IssueCommandSendOpCond(u32 *out_ocr, bool spec_under_2, bool uhs_i_supported) const { + /* Get the argument. */ + const u32 arg = GetSendOpCmdArgument(spec_under_2, uhs_i_supported); + + /* Issue the application command. */ + constexpr ResponseType CommandResponseType = ResponseType_R3; + Command command(SdApplicationCommandIndex_SdSendOpCond, arg, CommandResponseType, false); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command))); + + /* Get the response. */ + hc->GetLastResponse(out_ocr, sizeof(u32), CommandResponseType); + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::IssueCommandClearCardDetect() const { + /* Issue the application command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(SdApplicationCommandIndex_SetClearCardDetect, 0, false, DeviceState_Tran)); + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::IssueCommandSendScr(void *dst, size_t dst_size) const { + /* Validate the output buffer. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= SdCardScrSize); + + /* Issue the application command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(SdApplicationCommandIndex_SendScr, 0, CommandResponseType, false); + TransferData xfer_data(dst, SdCardScrSize, 1, TransferDirection_ReadFromDevice); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + R_TRY(m_sd_card_device.CheckDeviceStatus(resp)); + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::EnterUhsIMode() { + /* Send voltage switch command. */ + R_TRY(this->IssueCommandVoltageSwitch()); + + /* Switch to sdr12. */ + R_TRY(BaseDeviceAccessor::GetHostController()->SwitchToSdr12()); + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::ChangeToReadyState(bool spec_under_2, bool uhs_i_supported) { + /* Decide on an ignore mask. */ + u32 ignore_mask = spec_under_2 ? static_cast<u32>(DeviceStatus_IllegalCommand) : 0u; + + /* Be prepared to wait up to 3.0 seconds to change state. */ + ManualTimer timer(3000); + while (true) { + /* We want to get ocr, which requires our sending an application command. */ + R_TRY(this->IssueCommandAppCmd(DeviceState_Unknown, ignore_mask)); + ignore_mask = 0; + + /* Get the ocr, and check if we're done. */ + u32 ocr; + R_TRY(this->IssueCommandSendOpCond(std::addressof(ocr), spec_under_2, uhs_i_supported)); + + if ((ocr & OcrCardPowerUpStatus) != 0) { + m_sd_card_device.SetOcrAndHighCapacity(ocr); + + /* Handle uhs i mode. */ + m_sd_card_device.SetUhsIMode(false); + if (uhs_i_supported && ((ocr & OcrSwitchingTo1_8VAccepted) != 0)) { + R_TRY(this->EnterUhsIMode()); + m_sd_card_device.SetUhsIMode(true); + } + + R_SUCCEED(); + } + + /* Check if we've timed out. */ + R_UNLESS(timer.Update(), sdmmc::ResultSdCardInitializationSoftwareTimeout()); + + /* Try again in 1ms. */ + WaitMicroSeconds(1000); + } + } + + Result SdCardDeviceAccessor::ChangeToStbyStateAndGetRca() { + /* Be prepared to wait up to 1.0 seconds to change state. */ + ManualTimer timer(1000); + while (true) { + /* Get rca. */ + u16 rca; + R_TRY(this->IssueCommandSendRelativeAddr(std::addressof(rca))); + if (rca != 0) { + m_sd_card_device.SetRca(rca); + R_SUCCEED(); + } + + /* Check if we've timed out. */ + R_UNLESS(timer.Update(), sdmmc::ResultSdCardGetValidRcaSoftwareTimeout()); + } + } + + Result SdCardDeviceAccessor::SetMemoryCapacity(const void *csd) { + if (IsLessThanCsdVersion2(static_cast<const u8 *>(csd))) { + R_TRY(m_sd_card_device.SetLegacyMemoryCapacity()); + } else { + AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast<uintptr_t>(csd), alignof(u16))); + m_sd_card_device.SetMemoryCapacity(GetMemoryCapacityFromCsd(static_cast<const u16 *>(csd))); + } + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::GetScr(void *dst, size_t dst_size) const { + /* Issue the application command. */ + R_TRY(this->IssueCommandAppCmd(DeviceState_Tran)); + R_TRY(this->IssueCommandSendScr(dst, dst_size)); + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::ExtendBusWidth(BusWidth max_bw, u8 sd_bw) { + /* If the maximum bus width is 1bit, we can't extend. */ + R_SUCCEED_IF(max_bw == BusWidth_1Bit); + + /* If 4bit mode isn't supported, we can't extend. */ + R_SUCCEED_IF(!IsSupportedBusWidth4Bit(sd_bw)); + + /* If the host controller doesn't support 4bit mode, we can't extend. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_SUCCEED_IF(!hc->IsSupportedBusWidth(BusWidth_4Bit)); + + /* Issue the application command to change to 4bit mode. */ + R_TRY(this->IssueCommandAppCmd(DeviceState_Tran)); + R_TRY(this->IssueCommandSetBusWidth4Bit()); + + /* Set the host controller's bus width. */ + hc->SetBusWidth(BusWidth_4Bit); + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::SwitchAccessMode(SwitchFunctionAccessMode access_mode, void *wb, size_t wb_size) { + /* Issue command to check if we can switch access mode. */ + R_TRY(this->IssueCommandSwitchAccessMode(wb, wb_size, false, access_mode)); + R_UNLESS(IsAccessModeInFunctionSelection(static_cast<const u8 *>(wb), access_mode), sdmmc::ResultSdCardCannotSwitchAccessMode()); + + /* Check if we can accept the resulting current consumption. */ + constexpr u16 AcceptableCurrentLimit = 800; /* mA */ + R_UNLESS(GetMaximumCurrentConsumption(static_cast<const u8 *>(wb)) < AcceptableCurrentLimit, sdmmc::ResultSdCardUnacceptableCurrentConsumption()); + + /* Switch the access mode. */ + R_TRY(this->IssueCommandSwitchAccessMode(wb, wb_size, true, access_mode)); + R_UNLESS(IsAccessModeInFunctionSelection(static_cast<const u8 *>(wb), access_mode), sdmmc::ResultSdCardFailedSwitchAccessMode()); + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::ExtendBusSpeedAtUhsIMode(SpeedMode max_sm, void *wb, size_t wb_size) { + /* Check that we're in 4bit bus mode. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_UNLESS(hc->GetBusWidth() == BusWidth_4Bit, sdmmc::ResultSdCardNot4BitBusWidthAtUhsIMode()); + + /* Determine what speed mode/access mode we should switch to. */ + R_TRY(this->IssueCommandCheckSupportedFunction(wb, wb_size)); + SwitchFunctionAccessMode target_am; + SpeedMode target_sm; + if (max_sm == SpeedMode_SdCardSdr104 && IsSupportedAccessMode(static_cast<const u8 *>(wb), SwitchFunctionAccessMode_Sdr104)) { + target_am = SwitchFunctionAccessMode_Sdr104; + target_sm = SpeedMode_SdCardSdr104; + } else if ((max_sm == SpeedMode_SdCardSdr104 || max_sm == SpeedMode_SdCardSdr50) && IsSupportedAccessMode(static_cast<const u8 *>(wb), SwitchFunctionAccessMode_Sdr50)) { + target_am = SwitchFunctionAccessMode_Sdr50; + target_sm = SpeedMode_SdCardSdr50; + } else { + R_THROW(sdmmc::ResultSdCardNotSupportSdr104AndSdr50()); + } + + /* Switch the access mode. */ + R_TRY(this->SwitchAccessMode(target_am, wb, wb_size)); + + /* Set the host controller speed mode and perform tuning using command index 19. */ + R_TRY(hc->SetSpeedMode(target_sm)); + R_TRY(hc->Tuning(target_sm, 19)); + + /* Check status. */ + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::ExtendBusSpeedAtNonUhsIMode(SpeedMode max_sm, bool spec_under_1_1, void *wb, size_t wb_size) { + /* If the maximum speed is default speed, we have nothing to do. */ + R_SUCCEED_IF(max_sm == SpeedMode_SdCardDefaultSpeed); + + /* Otherwise, if the spec is under 1.1, we have nothing to do. */ + R_SUCCEED_IF(spec_under_1_1); + + /* Otherwise, Check if high speed is supported. */ + R_TRY(this->IssueCommandCheckSupportedFunction(wb, wb_size)); + R_SUCCEED_IF(!IsSupportedAccessMode(static_cast<const u8 *>(wb), SwitchFunctionAccessMode_HighSpeed)); + + /* Switch the access mode. */ + R_TRY(this->SwitchAccessMode(SwitchFunctionAccessMode_HighSpeed, wb, wb_size)); + + /* Check status. */ + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + /* Set the host controller speed mode. */ + R_TRY(BaseDeviceAccessor::GetHostController()->SetSpeedMode(SpeedMode_SdCardHighSpeed)); + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::GetSdStatus(void *dst, size_t dst_size) const { + /* Issue the application command. */ + R_TRY(this->IssueCommandAppCmd(DeviceState_Tran)); + R_TRY(this->IssueCommandSdStatus(dst, dst_size)); + + R_SUCCEED(); + } + + void SdCardDeviceAccessor::TryDisconnectDat3PullUpResistor() const { + /* Issue the application command to clear card detect. */ + /* NOTE: Nintendo accepts a failure. */ + if (R_SUCCEEDED(this->IssueCommandAppCmd(DeviceState_Tran))) { + /* NOTE: Nintendo does not check the result of this. */ + this->IssueCommandClearCardDetect(); + } + + /* NOTE: Nintendo does not check the result of this. */ + BaseDeviceAccessor::IssueCommandSendStatus(); + } + + Result SdCardDeviceAccessor::StartupSdCardDevice(BusWidth max_bw, SpeedMode max_sm, void *wb, size_t wb_size) { + /* Start up the host controller. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->Startup(BusPower_3_3V, BusWidth_1Bit, SpeedMode_SdCardIdentification, false)); + + /* Wait 1ms for configuration to take. */ + WaitMicroSeconds(1000); + + /* Wait an additional 74 clocks for configuration to take. */ + WaitClocks(74, hc->GetDeviceClockFrequencyKHz()); + + /* Go to idle state. */ + R_TRY(BaseDeviceAccessor::IssueCommandGoIdleState()); + + /* Check whether the spec is under 2.0. */ + bool spec_under_2 = false; + R_TRY_CATCH(this->IssueCommandSendIfCond()) { + R_CATCH(sdmmc::ResultResponseTimeoutError) { spec_under_2 = true; } + } R_END_TRY_CATCH; + + /* Set the rca to 0. */ + m_sd_card_device.SetRca(0); + + /* Go to ready state. */ + const bool can_use_uhs_i_mode = (max_bw != BusWidth_1Bit) && (max_sm == SpeedMode_SdCardSdr104 || max_sm == SpeedMode_SdCardSdr50); + const bool uhs_i_supported = hc->IsSupportedTuning() && hc->IsSupportedBusPower(BusPower_1_8V); + R_TRY(this->ChangeToReadyState(spec_under_2, can_use_uhs_i_mode && uhs_i_supported)); + + /* Get the CID. */ + R_TRY(BaseDeviceAccessor::IssueCommandAllSendCid(wb, wb_size)); + m_sd_card_device.SetCid(wb, wb_size); + + /* Go to stby state and get the RCA. */ + R_TRY(this->ChangeToStbyStateAndGetRca()); + + /* Get the CSD. */ + R_TRY(BaseDeviceAccessor::IssueCommandSendCsd(wb, wb_size)); + m_sd_card_device.SetCsd(wb, wb_size); + R_TRY(this->SetMemoryCapacity(wb)); + + /* Set the host controller speed mode to default if we're not in uhs i mode. */ + if (!m_sd_card_device.IsUhsIMode()) { + R_TRY(hc->SetSpeedMode(SpeedMode_SdCardDefaultSpeed)); + } + + /* Issue select card command. */ + R_TRY(BaseDeviceAccessor::IssueCommandSelectCard()); + + /* Set block length to sector size. */ + R_TRY(BaseDeviceAccessor::IssueCommandSetBlockLenToSectorSize()); + + /* Try to disconnect dat3 pullup resistor. */ + TryDisconnectDat3PullUpResistor(); + + /* Get the SCR. */ + R_TRY(this->GetScr(wb, wb_size)); + const u8 sd_bw = GetSdBusWidths(static_cast<const u8 *>(wb)); + const bool spec_under_1_1 = IsLessThanSpecification1_1(static_cast<const u8 *>(wb)); + + /* Extend the bus width to the largest that we can. */ + R_TRY(this->ExtendBusWidth(max_bw, sd_bw)); + + /* Extend the bus speed to as fast as we can. */ + if (m_sd_card_device.IsUhsIMode()) { + R_TRY(this->ExtendBusSpeedAtUhsIMode(max_sm, wb, wb_size)); + } else { + R_TRY(this->ExtendBusSpeedAtNonUhsIMode(max_sm, spec_under_1_1, wb, wb_size)); + + R_TRY(this->GetSdStatus(wb, wb_size)); + } + + /* Enable power saving. */ + hc->SetPowerSaving(true); + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::OnActivate() { + /* Define the possible startup parameters. */ + constexpr const struct { + BusWidth bus_width; + SpeedMode speed_mode; + } StartupParameters[] = { + #if defined(AMS_SDMMC_ENABLE_SD_UHS_I) + { BusWidth_4Bit, SpeedMode_SdCardSdr104 }, + { BusWidth_4Bit, SpeedMode_SdCardSdr104 }, + { BusWidth_4Bit, SpeedMode_SdCardHighSpeed }, + { BusWidth_4Bit, SpeedMode_SdCardDefaultSpeed }, + { BusWidth_1Bit, SpeedMode_SdCardHighSpeed }, + #else + { BusWidth_4Bit, SpeedMode_SdCardHighSpeed }, + { BusWidth_4Bit, SpeedMode_SdCardHighSpeed }, + { BusWidth_4Bit, SpeedMode_SdCardDefaultSpeed }, + { BusWidth_1Bit, SpeedMode_SdCardHighSpeed }, + #endif + }; + + /* Try to start up with each set of parameters. */ + Result result; + for (int i = 0; i < static_cast<int>(util::size(StartupParameters)); ++i) { + /* Alias the parameters. */ + const auto ¶ms = StartupParameters[i]; + + /* Set our max bus width/speed mode. */ + m_max_bus_width = params.bus_width; + m_max_speed_mode = params.speed_mode; + + /* Try to start up the device. */ + result = this->StartupSdCardDevice(m_max_bus_width, m_max_speed_mode, m_work_buffer, m_work_buffer_size); + if (R_SUCCEEDED(result)) { + /* If we previously failed to start up the device, log the error correction. */ + if (i != 0) { + BaseDeviceAccessor::PushErrorLog(true, "S %d %d:0", m_max_bus_width, m_max_speed_mode); + BaseDeviceAccessor::IncrementNumActivationErrorCorrections(); + } + + R_SUCCEED(); + } + + /* Check if we were removed. */ + AMS_SDMMC_CHECK_SD_CARD_REMOVED(); + + /* Log that our startup failed. */ + BaseDeviceAccessor::PushErrorLog(false, "S %d %d:%X", m_max_bus_width, m_max_speed_mode, result.GetValue()); + + /* Shut down the host controller before we try to start up again. */ + BaseDeviceAccessor::GetHostController()->Shutdown(); + } + + /* We failed to start up with all sets of parameters. */ + /* Check the csd for errors. */ + if (sdmmc::ResultUnexpectedDeviceCsdValue::Includes(result)) { + u32 csd[DeviceCsdSize / sizeof(u32)]; + m_sd_card_device.GetCsd(csd, sizeof(csd)); + BaseDeviceAccessor::PushErrorLog(false, "%06X%08X%08X%08X", csd[3] & 0x00FFFFFF, csd[2], csd[1], csd[0]); + } + BaseDeviceAccessor::PushErrorTimeStamp(); + + /* Check if we failed because the sd card is removed. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + if (sdmmc::ResultCommunicationNotAttained::Includes(result)) { + WaitMicroSeconds(m_sd_card_detector->GetDebounceMilliSeconds() * 1000); + AMS_SDMMC_CHECK_SD_CARD_REMOVED(); + } + #endif + + R_RETURN(result); + } + + Result SdCardDeviceAccessor::OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) { + /* Do the read/write. */ + const Result result = BaseDeviceAccessor::ReadWriteMultiple(sector_index, num_sectors, 0, buf, buf_size, is_read); + + /* Check if we failed because the sd card is removed. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + if (sdmmc::ResultCommunicationNotAttained::Includes(result)) { + WaitMicroSeconds(m_sd_card_detector->GetDebounceMilliSeconds() * 1000); + AMS_SDMMC_CHECK_SD_CARD_REMOVED(); + } + #endif + + R_RETURN(result); + } + + Result SdCardDeviceAccessor::ReStartup() { + /* Shut down the host controller. */ + BaseDeviceAccessor::GetHostController()->Shutdown(); + + /* Perform start up. */ + Result result = this->StartupSdCardDevice(m_max_bus_width, m_max_speed_mode, m_work_buffer, m_work_buffer_size); + if (R_FAILED(result)) { + AMS_SDMMC_CHECK_SD_CARD_REMOVED(); + + BaseDeviceAccessor::PushErrorLog(false, "S %d %d:%X", m_max_bus_width, m_max_speed_mode, result.GetValue()); + R_RETURN(result); + } + + R_SUCCEED(); + } + + void SdCardDeviceAccessor::Initialize() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* If we've already initialized, we don't need to do anything. */ + if (m_is_initialized) { + return; + } + + /* Set the base device to our sd card device. */ + BaseDeviceAccessor::SetDevice(std::addressof(m_sd_card_device)); + + /* Initialize. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + { + /* TODO: We probably want this (and other sd card detection stuff) to be conditional pcv control active. */ + /* This will be a requirement to support sd card access with detector in stratosphere before PCV is alive. */ + m_sd_card_device.InitializeRemovedEvent(); + hc->PreSetRemovedEvent(m_sd_card_device.GetRemovedEvent()); + CallbackInfo ci = { + .inserted_callback = nullptr, + .inserted_callback_arg = this, + .removed_callback = RemovedCallbackEntry, + .removed_callback_arg = this, + }; + m_sd_card_detector->Initialize(std::addressof(ci)); + } + #endif + hc->Initialize(); + + /* Mark ourselves as initialized. */ + m_is_initialized = true; + } + + void SdCardDeviceAccessor::Finalize() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* If we've already finalized, we don't need to do anything. */ + if (!m_is_initialized) { + return; + } + m_is_initialized = false; + + /* Deactivate the device. */ + BaseDeviceAccessor::Deactivate(); + + /* Finalize the host controller. */ + BaseDeviceAccessor::GetHostController()->Finalize(); + + /* Finalize the detector. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + { + m_sd_card_detector->Finalize(); + m_sd_card_device.FinalizeRemovedEvent(); + } + #endif + } + + Result SdCardDeviceAccessor::Activate() { + /* Activate the detector. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Check that we're awake. */ + R_UNLESS(m_sd_card_device.IsAwake(), sdmmc::ResultNotAwakened()); + + /* Check that we're not already active. */ + R_SUCCEED_IF(m_sd_card_device.IsActive()); + + /* Clear the removed event. */ + m_sd_card_device.ClearRemovedEvent(); + + /* Check that the SD card is inserted. */ + R_UNLESS(m_sd_card_detector->IsInserted(), sdmmc::ResultNoDevice()); + } + #endif + + /* Activate the base device. */ + R_RETURN(BaseDeviceAccessor::Activate()); + } + + Result SdCardDeviceAccessor::GetSpeedMode(SpeedMode *out_speed_mode) const { + /* Check that we can write to output. */ + AMS_ABORT_UNLESS(out_speed_mode != nullptr); + + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(m_sd_card_device.CheckAccessible()); + + /* Check whether we're specification 1 (and thus default speed). */ + R_TRY(this->GetScr(m_work_buffer, m_work_buffer_size)); + if (IsLessThanSpecification1_1(static_cast<const u8 *>(m_work_buffer))) { + *out_speed_mode = SpeedMode_SdCardDefaultSpeed; + R_SUCCEED(); + } + + /* Get the current speed mode. */ + R_TRY(this->IssueCommandCheckSupportedFunction(m_work_buffer, m_work_buffer_size)); + R_TRY(GetCurrentSpeedMode(out_speed_mode, static_cast<const u8 *>(m_work_buffer), m_sd_card_device.IsUhsIMode())); + + R_SUCCEED(); + } + + void SdCardDeviceAccessor::PutSdCardToSleep() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* If the device isn't awake, we don't need to do anything. */ + if (!m_sd_card_device.IsAwake()) { + return; + } + + /* Put the device to sleep. */ + m_sd_card_device.PutToSleep(); + + /* Put the detector to sleep. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + m_sd_card_detector->PutToSleep(); + #endif + + /* If necessary, put the host controller to sleep. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + if (m_sd_card_device.IsActive() && !m_sd_card_device.IsRemoved()) + #else + if (m_sd_card_device.IsActive()) + #endif + { + BaseDeviceAccessor::GetHostController()->PutToSleep(); + } + } + + void SdCardDeviceAccessor::AwakenSdCard() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* If the device is awake, we don't need to do anything. */ + if (m_sd_card_device.IsAwake()) { + return; + } + + /* Wake the host controller, if we need to.*/ + bool force_det = false; + + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + if (m_sd_card_device.IsActive() && !m_sd_card_device.IsRemoved()) + #else + if (m_sd_card_device.IsActive()) + #endif + { + const Result result = BaseDeviceAccessor::GetHostController()->Awaken(); + if (R_SUCCEEDED(result)) { + force_det = R_FAILED(BaseDeviceAccessor::IssueCommandSendStatus()); + } else { + BaseDeviceAccessor::PushErrorLog(true, "A:%X", result.GetValue()); + force_det = true; + } + } + + /* Wake the detector. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + m_sd_card_detector->Awaken(force_det); + #else + AMS_UNUSED(force_det); + #endif + + /* Wake the device. */ + m_sd_card_device.Awaken(); + } + + Result SdCardDeviceAccessor::GetSdCardScr(void *dst, size_t dst_size) const { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(m_sd_card_device.CheckAccessible()); + + /* Get the SCR. */ + R_TRY(this->GetScr(dst, dst_size)); + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::GetSdCardSwitchFunctionStatus(void *dst, size_t dst_size, SdCardSwitchFunction switch_function) const { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(m_sd_card_device.CheckAccessible()); + + /* Check whether we're specification 1 (and thus can't switch). */ + R_TRY(this->GetScr(dst, dst_size)); + R_UNLESS(!IsLessThanSpecification1_1(static_cast<const u8 *>(dst)), sdmmc::ResultSdCardNotSupportSwitchFunctionStatus()); + + /* Get the status. */ + if (switch_function == SdCardSwitchFunction_CheckSupportedFunction) { + R_TRY(this->IssueCommandCheckSupportedFunction(dst, dst_size)); + } else { + SwitchFunctionAccessMode am; + switch (switch_function) { + case SdCardSwitchFunction_CheckDefault: am = SwitchFunctionAccessMode_Default; break; + case SdCardSwitchFunction_CheckHighSpeed: am = SwitchFunctionAccessMode_HighSpeed; break; + case SdCardSwitchFunction_CheckSdr50: am = SwitchFunctionAccessMode_Sdr50; break; + case SdCardSwitchFunction_CheckSdr104: am = SwitchFunctionAccessMode_Sdr104; break; + case SdCardSwitchFunction_CheckDdr50: am = SwitchFunctionAccessMode_Ddr50; break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + R_TRY(this->IssueCommandSwitchAccessMode(dst, dst_size, false, am)); + } + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::GetSdCardCurrentConsumption(u16 *out_current_consumption, SpeedMode speed_mode) const { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(m_sd_card_device.CheckAccessible()); + + /* Check whether we're specification 1 (and thus can't switch). */ + R_TRY(this->GetScr(m_work_buffer, m_work_buffer_size)); + R_UNLESS(!IsLessThanSpecification1_1(static_cast<const u8 *>(m_work_buffer)), sdmmc::ResultSdCardNotSupportSwitchFunctionStatus()); + + /* Determine the access mode. */ + SwitchFunctionAccessMode am; + switch (speed_mode) { + case SpeedMode_SdCardSdr12: + case SpeedMode_SdCardDefaultSpeed: + am = SwitchFunctionAccessMode_Default; + break; + case SpeedMode_SdCardSdr25: + case SpeedMode_SdCardHighSpeed: + am = SwitchFunctionAccessMode_HighSpeed; + break; + case SpeedMode_SdCardSdr50: + am = SwitchFunctionAccessMode_Sdr50; + break; + case SpeedMode_SdCardSdr104: + am = SwitchFunctionAccessMode_Sdr104; + break; + case SpeedMode_SdCardDdr50: + am = SwitchFunctionAccessMode_Ddr50; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Check that the mode is supported. */ + R_TRY(this->IssueCommandSwitchAccessMode(m_work_buffer, m_work_buffer_size, false, am)); + R_UNLESS(IsSupportedAccessMode(static_cast<const u8 *>(m_work_buffer), am), sdmmc::ResultSdCardNotSupportAccessMode()); + + /* Get the current consumption. */ + AMS_ABORT_UNLESS(out_current_consumption != nullptr); + *out_current_consumption = GetMaximumCurrentConsumption(static_cast<const u8 *>(m_work_buffer)); + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::GetSdCardSdStatus(void *dst, size_t dst_size) const { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(m_sd_card_device.CheckAccessible()); + + /* Get the status. */ + R_TRY(this->GetSdStatus(dst, dst_size)); + + R_SUCCEED(); + } + + Result SdCardDeviceAccessor::GetSdCardProtectedAreaCapacity(u32 *out_num_sectors) const { + AMS_ABORT_UNLESS(out_num_sectors != nullptr); + + /* Get the sd status. */ + R_TRY(this->GetSdCardSdStatus(m_work_buffer, m_work_buffer_size)); + const u32 size_of_protected_area = GetSizeOfProtectedArea(static_cast<const u8 *>(m_work_buffer)); + + /* Get the csd. */ + u8 csd[DeviceCsdSize]; + m_sd_card_device.GetCsd(csd, sizeof(csd)); + + /* Handle based on csd version. */ + if (IsLessThanCsdVersion2(csd)) { + /* Get c_size_mult and read_bl_len. */ + u8 c_size_mult, read_bl_len; + m_sd_card_device.GetLegacyCapacityParameters(std::addressof(c_size_mult), std::addressof(read_bl_len)); + + /* Validate the parameters. */ + R_UNLESS((read_bl_len + c_size_mult + 2) >= 9, sdmmc::ResultUnexpectedDeviceCsdValue()); + + /* Calculate capacity. */ + *out_num_sectors = size_of_protected_area << ((read_bl_len + c_size_mult + 2) - 9); + } else { + /* SIZE_OF_PROTECTED_AREA is in bytes. */ + *out_num_sectors = size_of_protected_area / SectorSize; + } + + R_SUCCEED(); + } + + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_card_device_accessor.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_card_device_accessor.hpp new file mode 100644 index 00000000..5460622d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_card_device_accessor.hpp @@ -0,0 +1,222 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include "sdmmc_base_device_accessor.hpp" + +#if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) +#include "sdmmc_device_detector.hpp" +#endif + +namespace ams::sdmmc::impl { + + class SdCardDevice : public BaseDevice { + private: + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + mutable os::EventType m_removed_event; + #endif + u16 m_rca; + bool m_is_valid_rca; + bool m_is_uhs_i_mode; + public: + SdCardDevice() : m_rca(0) { + this->OnDeactivate(); + } + + virtual void Deactivate() override { + this->OnDeactivate(); + BaseDevice::Deactivate(); + } + + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + virtual os::EventType *GetRemovedEvent() const override { + return std::addressof(m_removed_event); + } + #elif defined(AMS_SDMMC_USE_OS_EVENTS) + virtual os::EventType *GetRemovedEvent() const override { + /* Mmc can't be removed. */ + return nullptr; + } + #endif + + virtual DeviceType GetDeviceType() const override { + return DeviceType_SdCard; + } + + virtual u16 GetRca() const override { + AMS_ABORT_UNLESS(m_is_valid_rca); + return m_rca; + } + + void OnDeactivate() { + m_is_valid_rca = false; + m_is_uhs_i_mode = false; + } + + void SetRca(u16 v) { + m_rca = v; + m_is_valid_rca = true; + } + + void SetOcrAndHighCapacity(u32 ocr); + + void SetUhsIMode(bool en) { + m_is_uhs_i_mode = en; + } + + bool IsUhsIMode() const { + return m_is_uhs_i_mode; + } + }; + + enum SdCardApplicationCommandIndex : std::underlying_type<CommandIndex>::type { + SdApplicationCommandIndex_SetBusWidth = 6, + + SdApplicationCommandIndex_SdStatus = 13, + + SdApplicationCommandIndex_SendNumWriteBlocks = 22, + SdApplicationCommandIndex_SetWriteBlockEraseCount = 23, + + SdApplicationCommandIndex_SdSendOpCond = 41, + SdApplicationCommandIndex_SetClearCardDetect = 42, + + SdApplicationCommandIndex_SendScr = 51, + }; + + enum SwitchFunctionAccessMode { + SwitchFunctionAccessMode_Default = 0, + SwitchFunctionAccessMode_HighSpeed = 1, + SwitchFunctionAccessMode_Sdr50 = 2, + SwitchFunctionAccessMode_Sdr104 = 3, + SwitchFunctionAccessMode_Ddr50 = 4, + }; + + class SdCardDeviceAccessor : public BaseDeviceAccessor { + private: + SdCardDevice m_sd_card_device; + void *m_work_buffer; + size_t m_work_buffer_size; + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + DeviceDetector *m_sd_card_detector; + #endif + BusWidth m_max_bus_width; + SpeedMode m_max_speed_mode; + bool m_is_initialized; + private: + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + void RemovedCallback(); + + static void RemovedCallbackEntry(void *arg) { + static_cast<SdCardDeviceAccessor *>(arg)->RemovedCallback(); + } + #endif + + Result IssueCommandSendRelativeAddr(u16 *out_rca) const; + Result IssueCommandSendIfCond() const; + Result IssueCommandCheckSupportedFunction(void *dst, size_t dst_size) const; + Result IssueCommandSwitchAccessMode(void *dst, size_t dst_size, bool set_function, SwitchFunctionAccessMode access_mode) const; + Result IssueCommandVoltageSwitch() const; + Result IssueCommandAppCmd(DeviceState expected_state, u32 ignore_mask = 0) const; + Result IssueCommandSetBusWidth4Bit() const; + Result IssueCommandSdStatus(void *dst, size_t dst_size) const; + Result IssueCommandSendOpCond(u32 *out_ocr, bool spec_under_2, bool uhs_i_supported) const; + Result IssueCommandClearCardDetect() const; + Result IssueCommandSendScr(void *dst, size_t dst_size) const; + + Result EnterUhsIMode(); + Result ChangeToReadyState(bool spec_under_2, bool uhs_i_supported); + Result ChangeToStbyStateAndGetRca(); + Result SetMemoryCapacity(const void *csd); + Result GetScr(void *dst, size_t dst_size) const; + Result ExtendBusWidth(BusWidth max_bw, u8 sd_bw); + Result SwitchAccessMode(SwitchFunctionAccessMode access_mode, void *wb, size_t wb_size); + Result ExtendBusSpeedAtUhsIMode(SpeedMode max_sm, void *wb, size_t wb_size); + Result ExtendBusSpeedAtNonUhsIMode(SpeedMode max_sm, bool spec_under_1_1, void *wb, size_t wb_size); + Result GetSdStatus(void *dst, size_t dst_size) const; + Result StartupSdCardDevice(BusWidth max_bw, SpeedMode max_sm, void *wb, size_t wb_size); + + void TryDisconnectDat3PullUpResistor() const; + protected: + virtual Result OnActivate() override; + virtual Result OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) override; + virtual Result ReStartup() override; + public: + virtual void Initialize() override; + virtual void Finalize() override; + virtual Result Activate() override; + virtual Result GetSpeedMode(SpeedMode *out_speed_mode) const override; + public: + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + explicit SdCardDeviceAccessor(IHostController *hc, DeviceDetector *dd) : BaseDeviceAccessor(hc), m_sd_card_detector(dd) + #else + explicit SdCardDeviceAccessor(IHostController *hc) : BaseDeviceAccessor(hc) + #endif + { + m_work_buffer = nullptr; + m_work_buffer_size = 0; + m_max_bus_width = BusWidth_4Bit; + m_max_speed_mode = SpeedMode_SdCardSdr104; + m_is_initialized = false; + } + + void SetSdCardWorkBuffer(void *wb, size_t wb_size) { + m_work_buffer = wb; + m_work_buffer_size = wb_size; + } + + void PutSdCardToSleep(); + void AwakenSdCard(); + Result GetSdCardProtectedAreaCapacity(u32 *out_num_sectors) const; + Result GetSdCardScr(void *dst, size_t dst_size) const; + Result GetSdCardSwitchFunctionStatus(void *dst, size_t dst_size, SdCardSwitchFunction switch_function) const; + Result GetSdCardCurrentConsumption(u16 *out_current_consumption, SpeedMode speed_mode) const; + Result GetSdCardSdStatus(void *dst, size_t dst_size) const; + + bool IsSdCardInserted() { + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + return m_sd_card_detector->IsInserted(); + #else + AMS_ABORT("IsSdCardInserted without SdCardDetector"); + #endif + } + + bool IsSdCardRemoved() { + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + return m_sd_card_device.IsRemoved(); + #else + AMS_ABORT("IsSdCardRemoved without SdCardDetector"); + #endif + } + + void RegisterSdCardDetectionEventCallback(DeviceDetectionEventCallback cb, void *arg) { + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + return m_sd_card_detector->RegisterDetectionEventCallback(cb, arg); + #else + AMS_UNUSED(cb, arg); + AMS_ABORT("RegisterSdCardDetectionEventCallback without SdCardDetector"); + #endif + } + + void UnregisterSdCardDetectionEventCallback() { + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + return m_sd_card_detector->UnregisterDetectionEventCallback(); + #else + AMS_ABORT("UnregisterSdCardDetectionEventCallback without SdCardDetector"); + #endif + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.cpp new file mode 100644 index 00000000..35b3a390 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.cpp @@ -0,0 +1,1039 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#include "sdmmc_sd_host_standard_controller.hpp" +#include "sdmmc_timer.hpp" + +#if defined(ATMOSPHERE_IS_STRATOSPHERE) + #include <stratosphere/dd.hpp> +#endif + + +namespace ams::sdmmc::impl { + + namespace { + + constexpr inline u32 ControllerReactionTimeoutMilliSeconds = 2000; + constexpr inline u32 CommandTimeoutMilliSeconds = 2000; + constexpr inline u32 DefaultCheckTransferIntervalMilliSeconds = 1500; + constexpr inline u32 BusyTimeoutMilliSeconds = 2000; + + } + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void SdHostStandardController::ResetBufferInfos() { + for (auto &info : m_buffer_infos) { + info.buffer_address = 0; + info.buffer_size = 0; + } + } + + dd::DeviceVirtualAddress SdHostStandardController::GetDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size) { + /* Try to find the buffer in our registered regions. */ + dd::DeviceVirtualAddress device_addr = 0; + for (const auto &info : m_buffer_infos) { + if (info.buffer_address <= buffer && (buffer + buffer_size) <= (info.buffer_address + info.buffer_size)) { + device_addr = info.buffer_device_virtual_address + (buffer - info.buffer_address); + break; + } + } + + /* Ensure that we found the buffer. */ + AMS_ABORT_UNLESS(device_addr != 0); + return device_addr; + } + #endif + + void SdHostStandardController::EnsureControl() { + /* Perform a read of clock control to be sure previous configuration takes. */ + reg::Read(m_registers->clock_control); + } + + Result SdHostStandardController::EnableInternalClock() { + /* Enable internal clock. */ + reg::ReadWrite(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_INTERNAL_CLOCK_ENABLE, OSCILLATE)); + this->EnsureControl(); + + /* Wait for the internal clock to become stable. */ + { + ManualTimer timer(ControllerReactionTimeoutMilliSeconds); + while (true) { + /* Check if the clock is steady. */ + if (reg::HasValue(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_INTERNAL_CLOCK_STABLE, READY))) { + break; + } + + /* If not, check for timeout. */ + R_UNLESS(timer.Update(), sdmmc::ResultInternalClockStableSoftwareTimeout()); + } + } + + /* Configure to use host controlled divided clock. */ + reg::ReadWrite(m_registers->host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_PRESET_VALUE_ENABLE, HOST_DRIVER)); + reg::ReadWrite(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_CLOCK_GENERATOR_SELECT, DIVIDED_CLOCK)); + + /* Set host version 4.0.0 enable. */ + reg::ReadWrite(m_registers->host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_HOST_VERSION_4_ENABLE, VERSION_4)); + + /* Set host 64 bit addressing enable. */ + AMS_ABORT_UNLESS(reg::HasValue(m_registers->capabilities, SD_REG_BITS_ENUM(CAPABILITIES_64_BIT_SYSTEM_ADDRESS_SUPPORT_FOR_V3, SUPPORTED))); + reg::ReadWrite(m_registers->host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_64_BIT_ADDRESSING, 64_BIT_ADDRESSING)); + + /* Select SDMA mode. */ + reg::ReadWrite(m_registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DMA_SELECT, SDMA)); + + /* Configure timeout control to use the maximum timeout value (TMCLK * 2^27) */ + reg::ReadWrite(m_registers->timeout_control, SD_REG_BITS_VALUE(TIMEOUT_CONTROL_DATA_TIMEOUT_COUNTER, 0b1110)); + + R_SUCCEED(); + } + + void SdHostStandardController::SetBusPower(BusPower bus_power) { + /* Check that we support the bus power. */ + AMS_ABORT_UNLESS(this->IsSupportedBusPower(bus_power)); + + /* Set the appropriate power. */ + switch (bus_power) { + case BusPower_Off: + reg::ReadWrite(m_registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, OFF)); + break; + case BusPower_1_8V: + reg::ReadWrite(m_registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1, 1_8V)); + reg::ReadWrite(m_registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, ON)); + break; + case BusPower_3_3V: + reg::ReadWrite(m_registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1, 3_3V)); + reg::ReadWrite(m_registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, ON)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void SdHostStandardController::EnableInterruptStatus() { + /* Set the status register interrupt enables. */ + reg::ReadWrite(m_registers->normal_int_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(ENABLED)); + reg::ReadWrite(m_registers->error_int_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (ENABLED)); + + /* Read/write the interrupt enables to be sure they take. */ + reg::Write(m_registers->normal_int_enable, reg::Read(m_registers->normal_int_enable)); + reg::Write(m_registers->error_int_enable, reg::Read(m_registers->error_int_enable)); + + /* If we're using interrupt events, configure appropriately. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + /* Clear interrupts. */ + this->ClearInterrupt(); + + /* Enable the interrupt signals. */ + reg::ReadWrite(m_registers->normal_signal_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(ENABLED)); + reg::ReadWrite(m_registers->error_signal_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (ENABLED)); + } + #endif + } + + void SdHostStandardController::DisableInterruptStatus() { + /* If we're using interrupt events, configure appropriately. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + /* Disable the interrupt signals. */ + reg::ReadWrite(m_registers->normal_signal_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(MASKED)); + reg::ReadWrite(m_registers->error_signal_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (MASKED)); + } + #endif + + /* Mask the status register interrupt enables. */ + reg::ReadWrite(m_registers->normal_int_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(MASKED)); + reg::ReadWrite(m_registers->error_int_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (MASKED)); + } + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + Result SdHostStandardController::WaitInterrupt(u32 timeout_ms) { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait for the interrupt to be signaled. */ + os::MultiWaitHolderType *signaled_holder = os::TimedWaitAny(std::addressof(m_multi_wait), TimeSpan::FromMilliSeconds(timeout_ms)); + if (signaled_holder == std::addressof(m_interrupt_event_holder)) { + /* We received the interrupt. */ + R_SUCCEED(); + } else if (signaled_holder == std::addressof(m_removed_event_holder)) { + /* The device was removed. */ + R_THROW(sdmmc::ResultDeviceRemoved()); + } else { + /* Timeout occurred. */ + R_THROW(sdmmc::ResultWaitInterruptSoftwareTimeout()); + } + } + + void SdHostStandardController::ClearInterrupt() { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Clear the interrupt event. */ + os::ClearInterruptEvent(m_interrupt_event); + } + #endif + + void SdHostStandardController::SetTransfer(u32 *out_num_transferred_blocks, const TransferData *xfer_data) { + /* Ensure the transfer data is valid. */ + AMS_ABORT_UNLESS(xfer_data->block_size != 0); + AMS_ABORT_UNLESS(xfer_data->num_blocks != 0); + + /* Determine the number of blocks. */ + const u16 num_blocks = std::min<u16>(xfer_data->num_blocks, SdHostStandardRegisters::BlockCountMax); + + /* Determine the address/how many blocks to transfer. */ + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + const u64 address = this->GetDeviceVirtualAddress(reinterpret_cast<uintptr_t>(xfer_data->buffer), xfer_data->block_size * num_blocks); + const u16 num_xfer_blocks = num_blocks; + #else + const u64 address = reinterpret_cast<uintptr_t>(xfer_data->buffer); + const u16 num_xfer_blocks = num_blocks; + #endif + + /* Verify the address is usable. */ + AMS_ABORT_UNLESS(util::IsAligned(address, BufferDeviceVirtualAddressAlignment)); + + /* Configure for sdma. */ + reg::Write(m_registers->adma_address, static_cast<u32>(address >> 0)); + reg::Write(m_registers->upper_adma_address, static_cast<u32>(address >> BITSIZEOF(u32))); + + /* Set our next sdma address. */ + m_next_sdma_address = util::AlignDown<u64>(address + SdmaBufferBoundary, SdmaBufferBoundary); + + /* Configure block size. */ + AMS_ABORT_UNLESS(xfer_data->block_size <= SdHostStandardBlockSizeTransferBlockSizeMax); + reg::Write(m_registers->block_size, SD_REG_BITS_ENUM (BLOCK_SIZE_SDMA_BUFFER_BOUNDARY, 512_KB), + SD_REG_BITS_VALUE(BLOCK_SIZE_TRANSFER_BLOCK_SIZE, static_cast<u16>(xfer_data->block_size))); + + /* Configure transfer blocks. */ + reg::Write(m_registers->block_count, num_xfer_blocks); + if (out_num_transferred_blocks != nullptr) { + *out_num_transferred_blocks = num_xfer_blocks; + } + + /* Configure transfer mode. */ + reg::Write(m_registers->transfer_mode, SD_REG_BITS_ENUM (TRANSFER_MODE_DMA_ENABLE, ENABLE), + SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_BLOCK_COUNT_ENABLE, (xfer_data->is_multi_block_transfer), ENABLE, DISABLE), + SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_MULTI_BLOCK_SELECT, (xfer_data->is_multi_block_transfer), MULTI_BLOCK, SINGLE_BLOCK), + SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_DATA_TRANSFER_DIRECTION, (xfer_data->transfer_direction == TransferDirection_ReadFromDevice), READ, WRITE), + SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_AUTO_CMD_ENABLE, (xfer_data->is_stop_transmission_command_enabled), CMD12_ENABLE, DISABLE)); + } + + void SdHostStandardController::SetTransferForTuning() { + /* Get the tuning block size. */ + u16 tuning_block_size; + switch (this->GetBusWidth()) { + case BusWidth_4Bit: + tuning_block_size = 64; + break; + case BusWidth_8Bit: + tuning_block_size = 128; + break; + case BusWidth_1Bit: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Configure block size. */ + AMS_ABORT_UNLESS(tuning_block_size <= SdHostStandardBlockSizeTransferBlockSizeMax); + reg::Write(m_registers->block_size, SD_REG_BITS_VALUE(BLOCK_SIZE_TRANSFER_BLOCK_SIZE, tuning_block_size)); + + /* Configure transfer blocks. */ + reg::Write(m_registers->block_count, 1); + + /* Configure transfer mode. */ + reg::Write(m_registers->transfer_mode, SD_REG_BITS_ENUM(TRANSFER_MODE_DATA_TRANSFER_DIRECTION, READ)); + } + + void SdHostStandardController::SetCommand(const Command *command, bool has_xfer_data) { + /* Encode the command value. */ + u16 command_val = 0; + + /* Encode the response type. */ + switch (command->response_type) { + case ResponseType_R0: + command_val |= reg::Encode(SD_REG_BITS_ENUM(COMMAND_RESPONSE_TYPE, NO_RESPONSE), + SD_REG_BITS_ENUM(COMMAND_CRC_CHECK, DISABLE), + SD_REG_BITS_ENUM(COMMAND_INDEX_CHECK, DISABLE)); + break; + case ResponseType_R1: + case ResponseType_R6: + case ResponseType_R7: + command_val |= reg::Encode(SD_REG_BITS_ENUM_SEL(COMMAND_RESPONSE_TYPE, command->is_busy, RESPONSE_LENGTH_48_CHECK_BUSY_AFTER_RESPONSE, RESPONSE_LENGTH_48), + SD_REG_BITS_ENUM (COMMAND_CRC_CHECK, ENABLE), + SD_REG_BITS_ENUM (COMMAND_INDEX_CHECK, ENABLE)); + break; + case ResponseType_R2: + command_val |= reg::Encode(SD_REG_BITS_ENUM(COMMAND_RESPONSE_TYPE, RESPONSE_LENGTH_136), + SD_REG_BITS_ENUM(COMMAND_CRC_CHECK, ENABLE), + SD_REG_BITS_ENUM(COMMAND_INDEX_CHECK, DISABLE)); + break; + case ResponseType_R3: + command_val |= reg::Encode(SD_REG_BITS_ENUM(COMMAND_RESPONSE_TYPE, RESPONSE_LENGTH_48), + SD_REG_BITS_ENUM(COMMAND_CRC_CHECK, DISABLE), + SD_REG_BITS_ENUM(COMMAND_INDEX_CHECK, DISABLE)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Encode the data present select. */ + command_val |= reg::Encode(SD_REG_BITS_ENUM_SEL(COMMAND_DATA_PRESENT, has_xfer_data, DATA_PRESENT, NO_DATA_PRESENT)); + + /* Encode the command index. */ + AMS_ABORT_UNLESS(command->command_index <= SdHostStandardCommandIndexMax); + command_val |= reg::Encode(SD_REG_BITS_VALUE(COMMAND_COMMAND_INDEX, command->command_index)); + + /* Write the command and argument. */ + reg::Write(m_registers->argument, command->command_argument); + reg::Write(m_registers->command, command_val); + } + + void SdHostStandardController::SetCommandForTuning(u32 command_index) { + Command command(command_index, 0, ResponseType_R1, false); + return this->SetCommand(std::addressof(command), true); + } + + Result SdHostStandardController::ResetCmdDatLine() { + /* Set the software reset cmd/dat bits. */ + reg::ReadWrite(m_registers->software_reset, SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_CMD, RESET), + SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_DAT, RESET)); + + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait until reset is done. */ + { + ManualTimer timer(ControllerReactionTimeoutMilliSeconds); + while (true) { + /* Check if the target has been removed. */ + R_TRY(this->CheckRemoved()); + + /* Check if command inhibit is no longer present. */ + if (reg::HasValue(m_registers->software_reset, SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_CMD, WORK), + SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_DAT, WORK))) + { + break; + } + + /* Otherwise, check if we've timed out. */ + if (!timer.Update()) { + R_THROW(sdmmc::ResultAbortTransactionSoftwareTimeout()); + } + } + } + + R_SUCCEED(); + } + + Result SdHostStandardController::AbortTransaction() { + R_TRY(this->ResetCmdDatLine()); + R_SUCCEED(); + } + + Result SdHostStandardController::WaitWhileCommandInhibit(bool has_dat) { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait while command inhibit cmd is set. */ + { + ManualTimer timer(ControllerReactionTimeoutMilliSeconds); + while (true) { + /* Check if the target has been removed. */ + R_TRY(this->CheckRemoved()); + + /* Check if command inhibit is no longer present. */ + if (reg::HasValue(m_registers->present_state, SD_REG_BITS_ENUM(PRESENT_STATE_COMMAND_INHIBIT_CMD, READY))) { + break; + } + + /* Otherwise, check if we've timed out. */ + if (!timer.Update()) { + this->AbortTransaction(); + R_THROW(sdmmc::ResultCommandInhibitCmdSoftwareTimeout()); + } + } + } + + /* Wait while command inhibit dat is set. */ + if (has_dat) { + ManualTimer timer(ControllerReactionTimeoutMilliSeconds); + while (true) { + /* Check if the target has been removed. */ + R_TRY(this->CheckRemoved()); + + /* Check if command inhibit is no longer present. */ + if (reg::HasValue(m_registers->present_state, SD_REG_BITS_ENUM(PRESENT_STATE_COMMAND_INHIBIT_DAT, READY))) { + break; + } + + /* Otherwise, check if we've timed out. */ + if (!timer.Update()) { + this->AbortTransaction(); + R_THROW(sdmmc::ResultCommandInhibitDatSoftwareTimeout()); + } + } + } + + R_SUCCEED(); + } + + Result SdHostStandardController::CheckAndClearInterruptStatus(volatile u16 *out_normal_int_status, u16 wait_mask) { + /* Read the statuses. */ + volatile u16 normal_int_status = reg::Read(m_registers->normal_int_status); + volatile u16 error_int_status = reg::Read(m_registers->error_int_status); + volatile u16 auto_cmd_err_status = reg::Read(m_registers->acmd12_err); + + /* Set the output status, if necessary. */ + if (out_normal_int_status != nullptr) { + *out_normal_int_status = normal_int_status; + } + + /* If we don't have an error interrupt, just use the normal status. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_ERROR_INTERRUPT, NO_ERROR))) { + /* If the wait mask has any bits set, we're done waiting for the interrupt. */ + const u16 masked_status = (normal_int_status & wait_mask); + R_UNLESS(masked_status != 0, sdmmc::ResultNoWaitedInterrupt()); + + /* Write the masked value to the status register to ensure consistent state. */ + reg::Write(m_registers->normal_int_status, masked_status); + R_SUCCEED(); + } + + /* We have an error interrupt. Write the status to the register to ensure consistent state. */ + reg::Write(m_registers->error_int_status, error_int_status); + + /* Check the error interrupt status bits, and return appropriate errors. */ + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_INDEX, NO_ERROR)), sdmmc::ResultResponseIndexError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_END_BIT, NO_ERROR)), sdmmc::ResultResponseEndBitError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_CRC, NO_ERROR)), sdmmc::ResultResponseCrcError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_TIMEOUT, NO_ERROR)), sdmmc::ResultResponseTimeoutError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_DATA_END_BIT, NO_ERROR)), sdmmc::ResultDataEndBitError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_DATA_CRC, NO_ERROR)), sdmmc::ResultDataCrcError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_DATA_TIMEOUT, NO_ERROR)), sdmmc::ResultDataTimeoutError()); + + /* Check for auto cmd errors. */ + if (reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_AUTO_CMD, ERROR))) { + R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_INDEX, NO_ERROR)), sdmmc::ResultAutoCommandResponseIndexError()); + R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_END_BIT, NO_ERROR)), sdmmc::ResultAutoCommandResponseEndBitError()); + R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_CRC, NO_ERROR)), sdmmc::ResultAutoCommandResponseCrcError()); + R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_TIMEOUT, NO_ERROR)), sdmmc::ResultAutoCommandResponseTimeoutError()); + + /* An known auto cmd error occurred. */ + R_THROW(sdmmc::ResultSdHostStandardUnknownAutoCmdError()); + } else { + /* Unknown error occurred. */ + R_THROW(sdmmc::ResultSdHostStandardUnknownError()); + } + } + + Result SdHostStandardController::WaitCommandComplete() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + /* Wait for interrupt. */ + Result result = this->WaitInterrupt(CommandTimeoutMilliSeconds); + if (R_SUCCEEDED(result)) { + /* If we succeeded, check/clear our interrupt status. */ + result = this->CheckAndClearInterruptStatus(nullptr, reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_COMMAND_COMPLETE, COMPLETE))); + this->ClearInterrupt(); + + if (R_FAILED(result)) { + this->AbortTransaction(); + } + + R_RETURN(result); + } else if (sdmmc::ResultDeviceRemoved::Includes(result)) { + /* Otherwise, check if the device was removed. */ + R_RETURN(result); + } else { + /* If the device wasn't removed, cancel our transaction. */ + this->AbortTransaction(); + R_THROW(sdmmc::ResultCommandCompleteSoftwareTimeout()); + } + } + #else + { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait while command is not complete. */ + { + ManualTimer timer(CommandTimeoutMilliSeconds); + while (true) { + /* Check and clear the interrupt status. */ + const auto result = this->CheckAndClearInterruptStatus(nullptr, reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_COMMAND_COMPLETE, COMPLETE))); + + /* If we succeeded, we're done. */ + if (R_SUCCEEDED(result)) { + R_SUCCEED(); + } else if (sdmmc::ResultNoWaitedInterrupt::Includes(result)) { + /* Otherwise, if the wait for the interrupt isn't done, update the timer and check for timeout. */ + if (!timer.Update()) { + this->AbortTransaction(); + R_THROW(sdmmc::ResultCommandCompleteSoftwareTimeout()); + } + } else { + /* Otherwise, we have a generic failure. */ + this->AbortTransaction(); + R_RETURN(result); + } + } + } + } + #endif + } + + Result SdHostStandardController::WaitTransferComplete() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + /* Wait while transfer is not complete. */ + while (true) { + /* Get the last block count. */ + const u16 last_block_count = reg::Read(m_registers->block_count); + + /* Wait for interrupt. */ + Result result = this->WaitInterrupt(m_check_transfer_interval_ms); + if (R_SUCCEEDED(result)) { + /* If we succeeded, check/clear our interrupt status. */ + volatile u16 normal_int_status; + result = this->CheckAndClearInterruptStatus(std::addressof(normal_int_status), reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE), + SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))); + + this->ClearInterrupt(); + + /* If the interrupt succeeded, check status. */ + if (R_SUCCEEDED(result)) { + /* If the transfer is complete, we're done. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE))) { + R_SUCCEED(); + } + + /* Otherwise, if a DMA interrupt was generated, advance to the next address. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))) { + reg::Write(m_registers->adma_address, static_cast<u32>(m_next_sdma_address >> 0)); + reg::Write(m_registers->upper_adma_address, static_cast<u32>(m_next_sdma_address >> BITSIZEOF(u32))); + + m_next_sdma_address += SdmaBufferBoundary; + } + } else { + /* Abort the transaction. */ + this->AbortTransaction(); + R_RETURN(result); + } + + R_RETURN(result); + } else if (sdmmc::ResultDeviceRemoved::Includes(result)) { + /* Otherwise, check if the device was removed. */ + R_RETURN(result); + } else { + /* Otherwise, timeout if the transfer hasn't advanced. */ + if (last_block_count != reg::Read(m_registers->block_count)) { + this->AbortTransaction(); + R_THROW(sdmmc::ResultTransferCompleteSoftwareTimeout()); + } + } + } + } + #else + { + /* Wait while transfer is not complete. */ + while (true) { + /* Get the last block count. */ + const u16 last_block_count = reg::Read(m_registers->block_count); + + /* Wait until transfer times out. */ + { + ManualTimer timer(m_check_transfer_interval_ms); + while (true) { + /* Check/clear our interrupt status. */ + volatile u16 normal_int_status; + const auto result = this->CheckAndClearInterruptStatus(std::addressof(normal_int_status), reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE), + SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))); + + /* If the check succeeded, check status. */ + if (R_SUCCEEDED(result)) { + /* If the transfer is complete, we're done. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE))) { + R_SUCCEED(); + } + + /* Otherwise, if a DMA interrupt was generated, advance to the next address. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))) { + reg::Write(m_registers->adma_address, static_cast<u32>(m_next_sdma_address >> 0)); + reg::Write(m_registers->upper_adma_address, static_cast<u32>(m_next_sdma_address >> BITSIZEOF(u32))); + + m_next_sdma_address += SdmaBufferBoundary; + } + } else if (sdmmc::ResultNoWaitedInterrupt::Includes(result)) { + /* Otherwise, if the wait for the interrupt isn't done, update the timer and check for timeout. */ + if (!timer.Update()) { + /* Only timeout if the transfer hasn't advanced. */ + if (last_block_count != reg::Read(m_registers->block_count)) { + this->AbortTransaction(); + R_THROW(sdmmc::ResultTransferCompleteSoftwareTimeout()); + } + break; + } + } else { + /* Otherwise, we have a generic failure. */ + this->AbortTransaction(); + R_RETURN(result); + } + } + } + } + } + #endif + } + + Result SdHostStandardController::WaitWhileBusy() { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait while busy. */ + { + ManualTimer timer(BusyTimeoutMilliSeconds); + while (true) { + /* Check if the target has been removed. */ + R_TRY(this->CheckRemoved()); + + /* If the DAT0 line signal is level high, we're done. */ + if (reg::HasValue(m_registers->present_state, SD_REG_BITS_ENUM(PRESENT_STATE_DAT0_LINE_SIGNAL_LEVEL, HIGH))) { + R_SUCCEED(); + } + + /* Otherwise, check if we're timed out. */ + if (!timer.Update()) { + this->AbortTransaction(); + R_THROW(sdmmc::ResultBusySoftwareTimeout()); + } + } + } + } + + void SdHostStandardController::GetResponse(u32 *out_response, size_t response_size, ResponseType response_type) const { + /* Check that we can write the response. */ + AMS_ABORT_UNLESS(out_response != nullptr); + + /* Get the response appropriately. */ + switch (response_type) { + case ResponseType_R1: + case ResponseType_R3: + case ResponseType_R6: + case ResponseType_R7: + /* 32-bit response. */ + AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 1); + out_response[0] = reg::Read(m_registers->response[0]); + break; + case ResponseType_R2: + /* 128-bit response. */ + AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 4); + out_response[0] = reg::Read(m_registers->response[0]); + out_response[1] = reg::Read(m_registers->response[1]); + out_response[2] = reg::Read(m_registers->response[2]); + out_response[3] = reg::Read(m_registers->response[3]); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + Result SdHostStandardController::IssueCommandWithDeviceClock(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) { + /* Wait until we can issue the command. */ + R_TRY(this->WaitWhileCommandInhibit((xfer_data != nullptr) || command->is_busy)); + + /* Configure for the transfer. */ + u32 num_transferred_blocks = 0; + if (xfer_data != nullptr) { + /* Setup the transfer, and get the number of blocks. */ + this->SetTransfer(std::addressof(num_transferred_blocks), xfer_data); + + /* Ensure the device sees consistent data with the cpu. */ + dd::FlushDataCache(xfer_data->buffer, xfer_data->block_size * num_transferred_blocks); + } + + /* Issue the command with interrupt status enabled. */ + { + this->EnableInterruptStatus(); + ON_SCOPE_EXIT { this->DisableInterruptStatus(); }; + + /* Set the command. */ + this->SetCommand(command, xfer_data != nullptr); + + /* Wait for the command to complete. */ + R_TRY(this->WaitCommandComplete()); + + /* Process any response. */ + if (command->response_type != ResponseType_R0) { + m_last_response_type = command->response_type; + this->GetResponse(m_last_response, sizeof(m_last_response), m_last_response_type); + } + + /* Wait for data to be transferred. */ + if (xfer_data != nullptr) { + R_TRY(this->WaitTransferComplete()); + } + } + + /* If data was transferred, ensure we're in a consistent state. */ + if (xfer_data != nullptr) { + /* Ensure the cpu sees consistent data with the device. */ + if (xfer_data->transfer_direction == TransferDirection_ReadFromDevice) { + dd::InvalidateDataCache(xfer_data->buffer, xfer_data->block_size * num_transferred_blocks); + } + + /* Set the number of transferred blocks. */ + if (out_num_transferred_blocks != nullptr) { + *out_num_transferred_blocks = num_transferred_blocks; + } + + /* Process stop transition command. */ + m_last_stop_transmission_response = m_registers->response[3]; + } + + /* Wait until we're no longer busy. */ + if (command->is_busy || (xfer_data != nullptr)) { + R_TRY(this->WaitWhileBusy()); + } + + R_SUCCEED(); + } + + Result SdHostStandardController::IssueStopTransmissionCommandWithDeviceClock(u32 *out_response) { + /* Wait until we can issue the command. */ + R_TRY(this->WaitWhileCommandInhibit(false)); + + /* Issue the command with interrupt status enabled. */ + constexpr ResponseType StopTransmissionCommandResponseType = ResponseType_R1; + { + this->EnableInterruptStatus(); + ON_SCOPE_EXIT { this->DisableInterruptStatus(); }; + + /* Set the command. */ + Command command(CommandIndex_StopTransmission, 0, StopTransmissionCommandResponseType, true); + this->SetCommand(std::addressof(command), false); + + /* Wait for the command to complete. */ + R_TRY(this->WaitCommandComplete()); + } + + /* Process response. */ + this->GetResponse(out_response, sizeof(u32), StopTransmissionCommandResponseType); + + /* Wait until we're done. */ + R_TRY(this->WaitWhileBusy()); + + R_SUCCEED(); + } + + SdHostStandardController::SdHostStandardController(dd::PhysicalAddress registers_phys_addr, size_t registers_size) { + /* Translate the physical address to a address. */ + const uintptr_t registers_addr = dd::QueryIoMapping(registers_phys_addr, registers_size); + + /* Set registers. */ + AMS_ABORT_UNLESS(registers_addr != 0); + m_registers = reinterpret_cast<SdHostStandardRegisters *>(registers_addr); + + /* Reset DMA buffers, if we have any. */ + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + this->ResetBufferInfos(); + #endif + + /* Clear removed event, if we have one. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + m_removed_event = nullptr; + #endif + + /* Clear dma address. */ + m_next_sdma_address = 0; + m_check_transfer_interval_ms = DefaultCheckTransferIntervalMilliSeconds; + + /* Clear clock/power trackers. */ + m_device_clock_frequency_khz = 0; + m_is_power_saving_enable = false; + m_is_device_clock_enable = false; + + /* Clear last response. */ + m_last_response_type = ResponseType_R0; + std::memset(m_last_response, 0, sizeof(m_last_response)); + m_last_stop_transmission_response = 0; + } + + void SdHostStandardController::Initialize() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + os::InitializeMultiWait(std::addressof(m_multi_wait)); + + AMS_ABORT_UNLESS(m_interrupt_event != nullptr); + os::InitializeMultiWaitHolder(std::addressof(m_interrupt_event_holder), m_interrupt_event); + os::LinkMultiWaitHolder(std::addressof(m_multi_wait), std::addressof(m_interrupt_event_holder)); + + if (m_removed_event != nullptr) { + os::InitializeMultiWaitHolder(std::addressof(m_removed_event_holder), m_removed_event); + os::LinkMultiWaitHolder(std::addressof(m_multi_wait), std::addressof(m_removed_event_holder)); + } + } + #endif + } + + void SdHostStandardController::Finalize() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + if (m_removed_event != nullptr) { + os::UnlinkMultiWaitHolder(std::addressof(m_removed_event_holder)); + os::FinalizeMultiWaitHolder(std::addressof(m_removed_event_holder)); + } + + os::UnlinkMultiWaitHolder(std::addressof(m_interrupt_event_holder)); + os::FinalizeMultiWaitHolder(std::addressof(m_interrupt_event_holder)); + + os::FinalizeMultiWait(std::addressof(m_multi_wait)); + } + #endif + } + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void SdHostStandardController::RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + /* Find and set a free info. */ + for (auto &info : m_buffer_infos) { + if (info.buffer_address == 0) { + info = { + .buffer_address = buffer, + .buffer_size = buffer_size, + .buffer_device_virtual_address = buffer_device_virtual_address, + }; + return; + } + } + + AMS_ABORT("Out of BufferInfos\n"); + } + + void SdHostStandardController::UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + /* Find and clear the buffer info. */ + for (auto &info : m_buffer_infos) { + if (info.buffer_address == buffer) { + AMS_ABORT_UNLESS(info.buffer_size == buffer_size); + AMS_ABORT_UNLESS(info.buffer_device_virtual_address == buffer_device_virtual_address); + + info.buffer_address = 0; + info.buffer_size = 0; + return; + } + } + + AMS_ABORT("BufferInfo not found\n"); + } + #endif + + void SdHostStandardController::SetWorkBuffer(void *wb, size_t wb_size) { + AMS_UNUSED(wb, wb_size); + AMS_ABORT("WorkBuffer is not needed\n"); + } + + BusPower SdHostStandardController::GetBusPower() const { + /* Check if the bus has power. */ + if (reg::HasValue(m_registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, ON))) { + /* If it does, return the corresponding power. */ + switch (reg::GetValue(m_registers->power_control, SD_REG_BITS_MASK(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1))) { + case SD_HOST_STANDARD_POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1_1_8V: + return BusPower_1_8V; + case SD_HOST_STANDARD_POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1_3_3V: + return BusPower_3_3V; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } else { + /* It doesn't, so it's off. */ + return BusPower_Off; + } + } + + void SdHostStandardController::SetBusWidth(BusWidth bus_width) { + /* Check that we support the bus width. */ + AMS_ABORT_UNLESS(this->IsSupportedBusWidth(bus_width)); + + /* Set the appropriate data transfer width. */ + switch (bus_width) { + case BusWidth_1Bit: + reg::ReadWrite(m_registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, ONE_BIT)); + reg::ReadWrite(m_registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, USE_DATA_TRANSFER_WIDTH)); + break; + case BusWidth_4Bit: + reg::ReadWrite(m_registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, FOUR_BIT)); + reg::ReadWrite(m_registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, USE_DATA_TRANSFER_WIDTH)); + break; + case BusWidth_8Bit: + reg::ReadWrite(m_registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, EIGHT_BIT)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + BusWidth SdHostStandardController::GetBusWidth() const { + /* Check if the bus is using eight-bit extended data transfer. */ + if (reg::HasValue(m_registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, EIGHT_BIT))) { + return BusWidth_8Bit; + } else { + /* Bus is configured as USE_DATA_TRANSFER_WIDTH, so check if it's four bit. */ + if (reg::HasValue(m_registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, FOUR_BIT))) { + return BusWidth_4Bit; + } else { + return BusWidth_1Bit; + } + } + } + + void SdHostStandardController::SetPowerSaving(bool en) { + /* Set whether we're power saving enable. */ + m_is_power_saving_enable = en; + + /* Configure accordingly. */ + if (m_is_power_saving_enable) { + /* We want to disable SD clock if it's enabled. */ + if (reg::HasValue(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE))) { + reg::ReadWrite(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + } + } else { + /* We want to enable SD clock if it's disabled and we're supposed to enable device clock. */ + if (m_is_device_clock_enable && reg::HasValue(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE))) { + reg::ReadWrite(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + } + } + } + + void SdHostStandardController::EnableDeviceClock() { + /* If we're not in power-saving mode and the device clock is disabled, enable it. */ + if (!m_is_power_saving_enable && reg::HasValue(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE))) { + reg::ReadWrite(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + } + m_is_device_clock_enable = true; + } + + void SdHostStandardController::DisableDeviceClock() { + /* Unconditionally disable the device clock. */ + m_is_device_clock_enable = false; + reg::ReadWrite(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + } + + void SdHostStandardController::ChangeCheckTransferInterval(u32 ms) { + m_check_transfer_interval_ms = ms; + } + + void SdHostStandardController::SetDefaultCheckTransferInterval() { + m_check_transfer_interval_ms = DefaultCheckTransferIntervalMilliSeconds; + } + + Result SdHostStandardController::IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) { + /* We need to have device clock enabled to issue commands. */ + AMS_ABORT_UNLESS(m_is_device_clock_enable); + + /* Check if we need to temporarily re-enable the device clock. */ + const bool clock_disabled = reg::HasValue(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + + /* Ensure that the clock is enabled and the device is usable for the period we're using it. */ + if (clock_disabled) { + /* Turn on the clock. */ + reg::ReadWrite(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + + /* Ensure that our configuration takes. */ + this->EnsureControl(); + + /* Wait 8 device clocks to be sure that it's usable. */ + WaitClocks(8, m_device_clock_frequency_khz); + } + ON_SCOPE_EXIT { if (clock_disabled) { reg::ReadWrite(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); } }; + + /* Issue the command. */ + { + /* After we issue the command, we need to wait 8 device clocks. */ + ON_SCOPE_EXIT { WaitClocks(8, m_device_clock_frequency_khz); }; + + R_RETURN(this->IssueCommandWithDeviceClock(command, xfer_data, out_num_transferred_blocks)); + } + } + + Result SdHostStandardController::IssueStopTransmissionCommand(u32 *out_response) { + /* We need to have device clock enabled to issue commands. */ + AMS_ABORT_UNLESS(m_is_device_clock_enable); + + /* Check if we need to temporarily re-enable the device clock. */ + const bool clock_disabled = reg::HasValue(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + + /* Ensure that the clock is enabled and the device is usable for the period we're using it. */ + if (clock_disabled) { + /* Turn on the clock. */ + reg::ReadWrite(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + + /* Ensure that our configuration takes. */ + this->EnsureControl(); + + /* Wait 8 device clocks to be sure that it's usable. */ + WaitClocks(8, m_device_clock_frequency_khz); + } + ON_SCOPE_EXIT { if (clock_disabled) { reg::ReadWrite(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); } }; + + /* Issue the command. */ + { + /* After we issue the command, we need to wait 8 device clocks. */ + ON_SCOPE_EXIT { WaitClocks(8, m_device_clock_frequency_khz); }; + + R_RETURN(this->IssueStopTransmissionCommandWithDeviceClock(out_response)); + } + } + + void SdHostStandardController::GetLastResponse(u32 *out_response, size_t response_size, ResponseType response_type) const { + /* Check that we can get the response. */ + AMS_ABORT_UNLESS(out_response != nullptr); + AMS_ABORT_UNLESS(response_type == m_last_response_type); + + /* Get the response appropriately. */ + switch (response_type) { + case ResponseType_R1: + case ResponseType_R3: + case ResponseType_R6: + case ResponseType_R7: + /* 32-bit response. */ + AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 1); + out_response[0] = m_last_response[0]; + break; + case ResponseType_R2: + /* 128-bit response. */ + AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 4); + out_response[0] = m_last_response[0]; + out_response[1] = m_last_response[1]; + out_response[2] = m_last_response[2]; + out_response[3] = m_last_response[3]; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void SdHostStandardController::GetLastStopTransmissionResponse(u32 *out_response, size_t response_size) const { + /* Check that we can get the response. */ + AMS_ABORT_UNLESS(out_response != nullptr); + AMS_ABORT_UNLESS(response_size >= sizeof(u32)); + + /* Get the response. */ + out_response[0] = m_last_stop_transmission_response; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.hpp new file mode 100644 index 00000000..a97c4919 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.hpp @@ -0,0 +1,188 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include "sdmmc_i_host_controller.hpp" +#include "sdmmc_sd_host_standard_registers.hpp" + +namespace ams::sdmmc::impl { + + class SdHostStandardController : public IHostController { + protected: + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + struct BufferInfo { + uintptr_t buffer_address; + size_t buffer_size; + dd::DeviceVirtualAddress buffer_device_virtual_address; + }; + + static constexpr inline auto NumBufferInfos = 3; + #endif + protected: + SdHostStandardRegisters *m_registers; + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + BufferInfo m_buffer_infos[NumBufferInfos]; + #endif + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + os::MultiWaitType m_multi_wait; + os::InterruptEventType *m_interrupt_event; + os::MultiWaitHolderType m_interrupt_event_holder; + os::EventType *m_removed_event; + os::MultiWaitHolderType m_removed_event_holder; + #endif + + u64 m_next_sdma_address; + u32 m_check_transfer_interval_ms; + + u32 m_device_clock_frequency_khz; + bool m_is_power_saving_enable; + bool m_is_device_clock_enable; + + ResponseType m_last_response_type; + u32 m_last_response[4]; + u32 m_last_stop_transmission_response; + protected: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + void PreSetInterruptEvent(os::InterruptEventType *ie) { + m_interrupt_event = ie; + } + + bool IsRemoved() const { + return m_removed_event != nullptr && os::TryWaitEvent(m_removed_event); + } + #endif + + void SetDeviceClockFrequencyKHz(u32 khz) { + m_device_clock_frequency_khz = khz; + } + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void ResetBufferInfos(); + dd::DeviceVirtualAddress GetDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size); + #endif + + void EnsureControl(); + Result EnableInternalClock(); + void SetBusPower(BusPower bus_power); + + void EnableInterruptStatus(); + void DisableInterruptStatus(); + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + Result WaitInterrupt(u32 timeout_ms); + void ClearInterrupt(); + #endif + + void SetTransfer(u32 *out_num_transferred_blocks, const TransferData *xfer_data); + void SetTransferForTuning(); + + void SetCommand(const Command *command, bool has_xfer_data); + void SetCommandForTuning(u32 command_index); + + Result ResetCmdDatLine(); + Result AbortTransaction(); + Result WaitWhileCommandInhibit(bool has_dat); + Result CheckAndClearInterruptStatus(volatile u16 *out_normal_int_status, u16 wait_mask); + Result WaitCommandComplete(); + Result WaitTransferComplete(); + Result WaitWhileBusy(); + + void GetResponse(u32 *out_response, size_t response_size, ResponseType response_type) const; + + Result IssueCommandWithDeviceClock(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks); + Result IssueStopTransmissionCommandWithDeviceClock(u32 *out_response); + + ALWAYS_INLINE Result CheckRemoved() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + R_UNLESS(!this->IsRemoved(), sdmmc::ResultDeviceRemoved()); + #endif + + R_SUCCEED(); + } + public: + SdHostStandardController(dd::PhysicalAddress registers_phys_addr, size_t registers_size); + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual void PreSetRemovedEvent(os::EventType *e) override { + m_removed_event = e; + } + #endif + + virtual void Initialize() override; + virtual void Finalize() override; + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + virtual void RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override; + virtual void UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override; + #endif + + virtual void SetWorkBuffer(void *wb, size_t wb_size) override; + + virtual Result SwitchToSdr12() override { + AMS_ABORT("SwitchToSdr12 not supported\n"); + } + + virtual BusPower GetBusPower() const override; + + virtual void SetBusWidth(BusWidth bus_width) override; + virtual BusWidth GetBusWidth() const override; + + virtual u32 GetDeviceClockFrequencyKHz() const override { + return m_device_clock_frequency_khz; + } + + virtual void SetPowerSaving(bool en) override; + virtual bool IsPowerSavingEnable() const override { + return m_is_power_saving_enable; + } + + virtual void EnableDeviceClock() override; + virtual void DisableDeviceClock() override; + virtual bool IsDeviceClockEnable() const override { + return m_is_device_clock_enable; + } + + virtual u32 GetMaxTransferNumBlocks() const override { + return SdHostStandardRegisters::BlockCountMax; + } + + virtual void ChangeCheckTransferInterval(u32 ms) override; + virtual void SetDefaultCheckTransferInterval() override; + + virtual Result IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) override; + virtual Result IssueStopTransmissionCommand(u32 *out_response) override; + + virtual void GetLastResponse(u32 *out_response, size_t response_size, ResponseType response_type) const override; + virtual void GetLastStopTransmissionResponse(u32 *out_response, size_t response_size) const override; + + virtual bool IsSupportedTuning() const override { + return false; + } + + virtual Result Tuning(SpeedMode speed_mode, u32 command_index) override { + AMS_UNUSED(speed_mode, command_index); + AMS_ABORT("Tuning not supported\n"); + } + + virtual void SaveTuningStatusForHs400() override { + AMS_ABORT("SaveTuningStatusForHs400 not supported\n"); + } + + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_registers.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_registers.hpp new file mode 100644 index 00000000..d38781ea --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_registers.hpp @@ -0,0 +1,193 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::sdmmc::impl { + + struct SdHostStandardRegisters { + volatile uint32_t dma_address; + volatile uint16_t block_size; + volatile uint16_t block_count; + volatile uint32_t argument; + volatile uint16_t transfer_mode; + volatile uint16_t command; + volatile uint32_t response[0x4]; + volatile uint32_t buffer; + volatile uint32_t present_state; + volatile uint8_t host_control; + volatile uint8_t power_control; + volatile uint8_t block_gap_control; + volatile uint8_t wake_up_control; + volatile uint16_t clock_control; + volatile uint8_t timeout_control; + volatile uint8_t software_reset; + volatile uint16_t normal_int_status; + volatile uint16_t error_int_status; + volatile uint16_t normal_int_enable; + volatile uint16_t error_int_enable; + volatile uint16_t normal_signal_enable; + volatile uint16_t error_signal_enable; + volatile uint16_t acmd12_err; + volatile uint16_t host_control2; + volatile uint32_t capabilities; + volatile uint32_t capabilities_1; + volatile uint32_t max_current; + volatile uint32_t _0x4c; + volatile uint16_t set_acmd12_error; + volatile uint16_t set_int_error; + volatile uint8_t adma_error; + volatile uint8_t _0x56[0x3]; + volatile uint32_t adma_address; + volatile uint32_t upper_adma_address; + volatile uint16_t preset_for_init; + volatile uint16_t preset_for_default; + volatile uint16_t preset_for_high; + volatile uint16_t preset_for_sdr12; + volatile uint16_t preset_for_sdr25; + volatile uint16_t preset_for_sdr50; + volatile uint16_t preset_for_sdr104; + volatile uint16_t preset_for_ddr50; + volatile uint32_t _0x70[0x23]; + volatile uint16_t slot_int_status; + volatile uint16_t host_version; + + static constexpr inline u16 BlockCountMax = 0xFFFF; + }; + static_assert(sizeof(SdHostStandardRegisters) == 0x100); + + constexpr inline size_t SdmaBufferBoundary = 512_KB; + + #define SD_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (SD_HOST_STANDARD, NAME) + #define SD_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (SD_HOST_STANDARD, NAME, VALUE) + #define SD_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (SD_HOST_STANDARD, NAME, ENUM) + #define SD_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(SD_HOST_STANDARD, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + + #define DEFINE_SD_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (SD_HOST_STANDARD, NAME, __OFFSET__, __WIDTH__) + #define DEFINE_SD_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE) + #define DEFINE_SD_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) + #define DEFINE_SD_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) + #define DEFINE_SD_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + + DEFINE_SD_REG(BLOCK_SIZE_TRANSFER_BLOCK_SIZE, 0, 12); + DEFINE_SD_REG_THREE_BIT_ENUM(BLOCK_SIZE_SDMA_BUFFER_BOUNDARY, 12, 4_KB, 8_KB, 16_KB, 32_KB, 64_KB, 128_KB, 256_KB, 512_KB); + constexpr inline size_t SdHostStandardBlockSizeTransferBlockSizeMax = 0xFFF; + + DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_DMA_ENABLE, 0, DISABLE, ENABLE); + DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_BLOCK_COUNT_ENABLE, 1, DISABLE, ENABLE); + DEFINE_SD_REG_TWO_BIT_ENUM(TRANSFER_MODE_AUTO_CMD_ENABLE, 2, DISABLE, CMD12_ENABLE, CMD23_ENABLE, AUTO_SELECT); + DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_DATA_TRANSFER_DIRECTION, 4, WRITE, READ); + DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_MULTI_BLOCK_SELECT, 5, SINGLE_BLOCK, MULTI_BLOCK); + + DEFINE_SD_REG_TWO_BIT_ENUM(COMMAND_RESPONSE_TYPE, 0, NO_RESPONSE, RESPONSE_LENGTH_136, RESPONSE_LENGTH_48, RESPONSE_LENGTH_48_CHECK_BUSY_AFTER_RESPONSE); + DEFINE_SD_REG_BIT_ENUM(COMMAND_CRC_CHECK, 3, DISABLE, ENABLE); + DEFINE_SD_REG_BIT_ENUM(COMMAND_INDEX_CHECK, 4, DISABLE, ENABLE); + DEFINE_SD_REG_BIT_ENUM(COMMAND_DATA_PRESENT, 5, NO_DATA_PRESENT, DATA_PRESENT); + DEFINE_SD_REG(COMMAND_COMMAND_INDEX, 8, 6); + constexpr inline size_t SdHostStandardCommandIndexMax = 0x3F; + + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_COMMAND_INHIBIT_CMD, 0, READY, NOT_READY); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_COMMAND_INHIBIT_DAT, 1, READY, NOT_READY); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT0_LINE_SIGNAL_LEVEL, 20, LOW, HIGH); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT1_LINE_SIGNAL_LEVEL, 21, LOW, HIGH); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT2_LINE_SIGNAL_LEVEL, 22, LOW, HIGH); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT3_LINE_SIGNAL_LEVEL, 23, LOW, HIGH); + DEFINE_SD_REG(PRESENT_STATE_DAT0_3_LINE_SIGNAL_LEVEL, 20, 4); + + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, 1, ONE_BIT, FOUR_BIT); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL_HIGH_SPEED_ENABLE, 2, NORMAL_SPEED, HIGH_SPEED); + DEFINE_SD_REG_TWO_BIT_ENUM(HOST_CONTROL_DMA_SELECT, 3, SDMA, RESERVED1, ADMA2, ADMA2_OR_ADMA3); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, 5, USE_DATA_TRANSFER_WIDTH, EIGHT_BIT); + + DEFINE_SD_REG_BIT_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, 0, OFF, ON); + DEFINE_SD_REG_THREE_BIT_ENUM(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1, 1, RESERVED0, RESERVED1, RESERVED2, RESERVED3, RESERVED4, 1_8V, 3_0V, 3_3V); + + DEFINE_SD_REG_BIT_ENUM(CLOCK_CONTROL_INTERNAL_CLOCK_ENABLE, 0, STOP, OSCILLATE); + DEFINE_SD_REG_BIT_ENUM(CLOCK_CONTROL_INTERNAL_CLOCK_STABLE, 1, NOT_READY, READY); + DEFINE_SD_REG_BIT_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, 2, DISABLE, ENABLE); + DEFINE_SD_REG_BIT_ENUM(CLOCK_CONTROL_CLOCK_GENERATOR_SELECT, 5, DIVIDED_CLOCK, PROGRAMMABLE_CLOCK); + DEFINE_SD_REG(CLOCK_CONTROL_UPPER_BITS_OF_SDCLK_FREQUENCY_SELECT, 6, 2); + DEFINE_SD_REG(CLOCK_CONTROL_SDCLK_FREQUENCY_SELECT, 8, 8); + + DEFINE_SD_REG(TIMEOUT_CONTROL_DATA_TIMEOUT_COUNTER, 0, 4); + + DEFINE_SD_REG_BIT_ENUM(SOFTWARE_RESET_FOR_ALL, 0, WORK, RESET); + DEFINE_SD_REG_BIT_ENUM(SOFTWARE_RESET_FOR_CMD, 1, WORK, RESET); + DEFINE_SD_REG_BIT_ENUM(SOFTWARE_RESET_FOR_DAT, 2, WORK, RESET); + + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_COMMAND_COMPLETE, 0, NOT_COMPLETE, COMPLETE); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, 1, NOT_COMPLETE, COMPLETE); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, 3, NOT_GENERATED, GENERATED); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_ERROR_INTERRUPT, 15, NO_ERROR, ERROR); + + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_TIMEOUT, 0, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_CRC, 1, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_END_BIT, 2, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_INDEX, 3, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_DATA_TIMEOUT, 4, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_DATA_CRC, 5, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_DATA_END_BIT, 6, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_AUTO_CMD, 8, NO_ERROR, ERROR); + + DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_TIMEOUT, 1, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_CRC, 2, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_END_BIT, 3, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_INDEX, 4, NO_ERROR, ERROR); + + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_COMMAND_COMPLETE, 0, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_TRANSFER_COMPLETE, 1, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_DMA_INTERRUPT, 3, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, 5, MASKED, ENABLED); + + #define SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(__ENUM__) \ + SD_REG_BITS_ENUM(NORMAL_INTERRUPT_COMMAND_COMPLETE, __ENUM__), \ + SD_REG_BITS_ENUM(NORMAL_INTERRUPT_TRANSFER_COMPLETE, __ENUM__), \ + SD_REG_BITS_ENUM(NORMAL_INTERRUPT_DMA_INTERRUPT, __ENUM__) + + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_COMMAND_TIMEOUT_ERROR, 0, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_COMMAND_CRC_ERROR, 1, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_COMMAND_END_BIT_ERROR, 2, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_COMMAND_INDEX_ERROR, 3, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_DATA_TIMEOUT_ERROR, 4, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_DATA_CRC_ERROR, 5, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_DATA_END_BIT_ERROR, 6, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_AUTO_CMD_ERROR, 8, MASKED, ENABLED); + + #define SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND(__ENUM__) \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_COMMAND_TIMEOUT_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_COMMAND_CRC_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_COMMAND_END_BIT_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_COMMAND_INDEX_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_DATA_TIMEOUT_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_DATA_CRC_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_DATA_END_BIT_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_AUTO_CMD_ERROR, __ENUM__) + + + DEFINE_SD_REG_THREE_BIT_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, 0, SDR12, SDR25, SDR50, SDR104, DDR50, HS400, RSVD6, UHS_II); + + constexpr inline auto SD_HOST_STANDARD_HOST_CONTROL2_UHS_MODE_SELECT_HS200 = SD_HOST_STANDARD_HOST_CONTROL2_UHS_MODE_SELECT_SDR104; + + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 3, 3_3V_SIGNALING, 1_8V_SIGNALING); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_EXECUTE_TUNING, 6, TUNING_COMPLETED, EXECUTE_TUNING); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_SAMPLING_CLOCK, 7, USING_FIXED_CLOCK, USING_TUNED_CLOCK); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_HOST_VERSION_4_ENABLE, 12, VERSION_300_COMPATIBLE, VERSION_4); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_64_BIT_ADDRESSING, 13, 32_BIT_ADDRESSING, 64_BIT_ADDRESSING); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_PRESET_VALUE_ENABLE, 15, HOST_DRIVER, AUTOMATIC_SELECTION); + + DEFINE_SD_REG_BIT_ENUM(CAPABILITIES_64_BIT_SYSTEM_ADDRESS_SUPPORT_FOR_V3, 28, NOT_SUPPORTED, SUPPORTED); + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.cpp new file mode 100644 index 00000000..a3e6e149 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.cpp @@ -0,0 +1,1315 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#include "sdmmc_sdmmc_controller.board.nintendo_nx.hpp" +#include "sdmmc_io_impl.board.nintendo_nx.hpp" +#include "sdmmc_timer.hpp" + +namespace ams::sdmmc::impl { + + /* FOR REFERENCE: board-specific sdmmc registers. */ + //struct SdmmcRegisters { + // /* Standard registers. */ + // volatile SdHostStandardRegisters sd_host_standard_registers; + // + // /* Vendor specific registers */ + // volatile uint32_t vendor_clock_cntrl; + // volatile uint32_t vendor_sys_sw_cntrl; + // volatile uint32_t vendor_err_intr_status; + // volatile uint32_t vendor_cap_overrides; + // volatile uint32_t vendor_boot_cntrl; + // volatile uint32_t vendor_boot_ack_timeout; + // volatile uint32_t vendor_boot_dat_timeout; + // volatile uint32_t vendor_debounce_count; + // volatile uint32_t vendor_misc_cntrl; + // volatile uint32_t max_current_override; + // volatile uint32_t max_current_override_hi; + // volatile uint32_t _0x12c[0x20]; + // volatile uint32_t vendor_io_trim_cntrl; + // + // /* Start of sdmmc2/sdmmc4 only */ + // volatile uint32_t vendor_dllcal_cfg; + // volatile uint32_t vendor_dll_ctrl0; + // volatile uint32_t vendor_dll_ctrl1; + // volatile uint32_t vendor_dllcal_cfg_sta; + // /* End of sdmmc2/sdmmc4 only */ + // + // volatile uint32_t vendor_tuning_cntrl0; + // volatile uint32_t vendor_tuning_cntrl1; + // volatile uint32_t vendor_tuning_status0; + // volatile uint32_t vendor_tuning_status1; + // volatile uint32_t vendor_clk_gate_hysteresis_count; + // volatile uint32_t vendor_preset_val0; + // volatile uint32_t vendor_preset_val1; + // volatile uint32_t vendor_preset_val2; + // volatile uint32_t sdmemcomppadctrl; + // volatile uint32_t auto_cal_config; + // volatile uint32_t auto_cal_interval; + // volatile uint32_t auto_cal_status; + // volatile uint32_t io_spare; + // volatile uint32_t sdmmca_mccif_fifoctrl; + // volatile uint32_t timeout_wcoal_sdmmca; + // volatile uint32_t _0x1fc; + //}; + + DEFINE_SD_REG_BIT_ENUM(VENDOR_CLOCK_CNTRL_SPI_MODE_CLKEN_OVERRIDE, 2, NORMAL, OVERRIDE); + DEFINE_SD_REG(VENDOR_CLOCK_CNTRL_TAP_VAL, 16, 8); + DEFINE_SD_REG(VENDOR_CLOCK_CNTRL_TRIM_VAL, 24, 5); + + DEFINE_SD_REG(VENDOR_CAP_OVERRIDES_DQS_TRIM_VAL, 8, 6); + + DEFINE_SD_REG(VENDOR_IO_TRIM_CNTRL_SEL_VREG, 2, 1); + + DEFINE_SD_REG_BIT_ENUM(VENDOR_DLLCAL_CFG_CALIBRATE, 31, DISABLE, ENABLE); + + DEFINE_SD_REG_BIT_ENUM(VENDOR_DLLCAL_CFG_STA_DLL_CAL_ACTIVE, 31, DONE, RUNNING); + + DEFINE_SD_REG(VENDOR_TUNING_CNTRL0_MUL_M, 6, 7); + DEFINE_SD_REG_THREE_BIT_ENUM(VENDOR_TUNING_CNTRL0_NUM_TUNING_ITERATIONS, 13, TRIES_40, TRIES_64, TRIES_128, TRIES_192, TRIES_256, RESERVED5, RESERVED6, RESERVED7); + DEFINE_SD_REG_BIT_ENUM(VENDOR_TUNING_CNTRL0_TAP_VALUE_UPDATED_BY_HW, 17, NOT_UPDATED_BY_HW, UPDATED_BY_HW); + + DEFINE_SD_REG(SDMEMCOMPPADCTRL_SDMMC2TMC_CFG_SDMEMCOMP_VREF_SEL, 0, 4); + DEFINE_SD_REG(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 31, 1); + + DEFINE_SD_REG(AUTO_CAL_CONFIG_AUTO_CAL_PU_OFFSET, 0, 7); + DEFINE_SD_REG(AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET, 8, 7); + DEFINE_SD_REG_BIT_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_ENABLE, 29, DISABLED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_START, 31, DISABLED, ENABLED); + + DEFINE_SD_REG(AUTO_CAL_STATUS_AUTO_CAL_PULLUP, 0, 7); + DEFINE_SD_REG_BIT_ENUM(AUTO_CAL_STATUS_AUTO_CAL_ACTIVE, 31, INACTIVE, ACTIVE); + + DEFINE_SD_REG_BIT_ENUM(IO_SPARE_SPARE_OUT_3, 19, TWO_CYCLE_DELAY, ONE_CYCLE_DELAY); + + namespace { + + constexpr inline u32 TuningCommandTimeoutMilliSeconds = 5; + + constexpr void GetDividerSetting(u32 *out_target_clock_frequency_khz, u16 *out_x, SpeedMode speed_mode) { + switch (speed_mode) { + case SpeedMode_MmcIdentification: + *out_target_clock_frequency_khz = 26000; + *out_x = 66; + break; + case SpeedMode_MmcLegacySpeed: + *out_target_clock_frequency_khz = 26000; + *out_x = 1; + break; + case SpeedMode_MmcHighSpeed: + *out_target_clock_frequency_khz = 52000; + *out_x = 1; + break; + case SpeedMode_MmcHs200: + *out_target_clock_frequency_khz = 200000; + *out_x = 1; + break; + case SpeedMode_MmcHs400: + *out_target_clock_frequency_khz = 200000; + *out_x = 1; + break; + case SpeedMode_SdCardIdentification: + *out_target_clock_frequency_khz = 25000; + *out_x = 64; + break; + case SpeedMode_SdCardDefaultSpeed: + *out_target_clock_frequency_khz = 25000; + *out_x = 1; + break; + case SpeedMode_SdCardHighSpeed: + *out_target_clock_frequency_khz = 50000; + *out_x = 1; + break; + case SpeedMode_SdCardSdr12: + *out_target_clock_frequency_khz = 25000; + *out_x = 1; + break; + case SpeedMode_SdCardSdr50: + *out_target_clock_frequency_khz = 100000; + *out_x = 1; + break; + case SpeedMode_SdCardSdr104: + *out_target_clock_frequency_khz = 200000; + *out_x = 1; + break; + case SpeedMode_GcAsicFpgaSpeed: + *out_target_clock_frequency_khz = 40800; + *out_x = 1; + break; + case SpeedMode_GcAsicSpeed: + *out_target_clock_frequency_khz = 200000; + *out_x = 2; + break; + case SpeedMode_SdCardSdr25: + case SpeedMode_SdCardDdr50: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + } + + namespace { + + #if defined(AMS_SDMMC_THREAD_SAFE) + constinit os::SdkMutex g_soc_mutex; + + #define AMS_SDMMC_LOCK_SOC_MUTEX() std::scoped_lock lk(g_soc_mutex) + + #else + + #define AMS_SDMMC_LOCK_SOC_MUTEX() + + #endif + + constinit bool g_determined_soc = false; + constinit bool g_is_soc_mariko = false; + + } + + bool IsSocMariko() { + if (!g_determined_soc) { + /* Ensure we have exclusive access to the soc variables. */ + AMS_SDMMC_LOCK_SOC_MUTEX(); + + /* Check the SocType. */ + #if defined(ATMOSPHERE_IS_EXOSPHERE) + { + g_is_soc_mariko = fuse::GetSocType() == fuse::SocType_Mariko; + } + #elif defined(ATMOSPHERE_IS_MESOSPHERE) + { + MESOSPHERE_TODO("Detect mariko via KSystemControl call?"); + } + #elif defined(ATMOSPHERE_IS_STRATOSPHERE) + { + /* Connect to spl for the duration of our check. */ + spl::Initialize(); + ON_SCOPE_EXIT { spl::Finalize(); }; + + g_is_soc_mariko = spl::GetSocType() == spl::SocType_Mariko; + } + #else + #error "Unknown execution context for ams::sdmmc::impl::IsSocMariko" + #endif + + /* Note that we determined the soc. */ + g_determined_soc = true; + } + + return g_is_soc_mariko; + } + + void SdmmcController::ReleaseReset(SpeedMode speed_mode) { + /* Get the clock reset module. */ + const auto module = this->GetClockResetModule(); + + /* If the module is available, disable clock. */ + if (ClockResetController::IsAvailable(module)) { + SdHostStandardController::DisableDeviceClock(); + SdHostStandardController::EnsureControl(); + } + + /* Get the correct divider setting for the speed mode. */ + u32 target_clock_frequency_khz; + u16 x; + GetDividerSetting(std::addressof(target_clock_frequency_khz), std::addressof(x), speed_mode); + + /* Release reset. */ + ClockResetController::ReleaseReset(module, target_clock_frequency_khz); + } + + void SdmmcController::AssertReset() { + return ClockResetController::AssertReset(this->GetClockResetModule()); + } + + Result SdmmcController::StartupCore(BusPower bus_power) { + /* Set schmitt trigger. */ + this->SetSchmittTrigger(bus_power); + + /* Select one-cycle delay version of cmd_oen. */ + reg::ReadWrite(m_sdmmc_registers->io_spare, SD_REG_BITS_ENUM(IO_SPARE_SPARE_OUT_3, ONE_CYCLE_DELAY)); + + /* Select regulated reference voltage for trimmer and DLL supply. */ + reg::ReadWrite(m_sdmmc_registers->vendor_io_trim_cntrl, SD_REG_BITS_VALUE(VENDOR_IO_TRIM_CNTRL_SEL_VREG, 0)); + + /* Configure outbound tap value. */ + reg::ReadWrite(m_sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_VALUE(VENDOR_CLOCK_CNTRL_TRIM_VAL, this->GetOutboundTapValue())); + + /* Configure SPI_MODE_CLKEN_OVERRIDE. */ + reg::ReadWrite(m_sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_ENUM(VENDOR_CLOCK_CNTRL_SPI_MODE_CLKEN_OVERRIDE, NORMAL)); + + /* Set slew codes. */ + this->SetSlewCodes(); + + /* Set vref sel. */ + reg::ReadWrite(m_sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_SDMMC2TMC_CFG_SDMEMCOMP_VREF_SEL, this->GetVrefSelValue())); + + /* Perform drive strength calibration at the new power. */ + this->SetDriveCodeOffsets(bus_power); + this->CalibrateDriveStrength(bus_power); + + /* Enable internal clock. */ + R_TRY(SdHostStandardController::EnableInternalClock()); + + R_SUCCEED(); + } + + Result SdmmcController::SetClockTrimmer(SpeedMode speed_mode, u8 tap_value) { + /* If speed mode is Hs400, set the dqs trim value. */ + if (speed_mode == SpeedMode_MmcHs400) { + reg::ReadWrite(m_sdmmc_registers->vendor_cap_overrides, SD_REG_BITS_VALUE(VENDOR_CAP_OVERRIDES_DQS_TRIM_VAL, 40)); + } + + /* Configure tap value as updated by software. */ + reg::ReadWrite(m_sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_TAP_VALUE_UPDATED_BY_HW, NOT_UPDATED_BY_HW)); + + /* Set the inbound tap value. */ + reg::ReadWrite(m_sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_VALUE(VENDOR_CLOCK_CNTRL_TAP_VAL, tap_value)); + + /* Reset the cmd/dat line. */ + R_TRY(SdHostStandardController::ResetCmdDatLine()); + + R_SUCCEED(); + } + + u8 SdmmcController::GetCurrentTapValue() { + return static_cast<u8>(reg::GetValue(m_sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_MASK(VENDOR_CLOCK_CNTRL_TAP_VAL))); + } + + Result SdmmcController::CalibrateDll() { + /* Check if we need to temporarily re-enable the device clock. */ + const bool clock_disabled = reg::HasValue(m_sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + + /* Ensure that the clock is enabled for the period we're using it. */ + if (clock_disabled) { + /* Turn on the clock. */ + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + } + ON_SCOPE_EXIT { if (clock_disabled) { reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); } }; + + /* Begin calibration. */ + reg::ReadWrite(m_sdmmc_registers->vendor_dllcal_cfg, SD_REG_BITS_ENUM(VENDOR_DLLCAL_CFG_CALIBRATE, ENABLE)); + + /* Wait up to 5ms for calibration to begin. */ + { + ManualTimer timer(5); + while (true) { + /* If calibration is done, we're done. */ + if (!reg::HasValue(m_sdmmc_registers->vendor_dllcal_cfg, SD_REG_BITS_ENUM(VENDOR_DLLCAL_CFG_CALIBRATE, ENABLE))) { + break; + } + + /* Otherwise, check if we've timed out. */ + R_UNLESS((timer.Update()), sdmmc::ResultSdmmcDllCalibrationSoftwareTimeout()); + } + } + + /* Wait up to 10ms for calibration to complete. */ + { + ManualTimer timer(10); + while (true) { + /* If calibration is done, we're done. */ + if (reg::HasValue(m_sdmmc_registers->vendor_dllcal_cfg_sta, SD_REG_BITS_ENUM(VENDOR_DLLCAL_CFG_STA_DLL_CAL_ACTIVE, DONE))) { + break; + } + + /* Otherwise, check if we've timed out. */ + R_UNLESS((timer.Update()), sdmmc::ResultSdmmcDllApplicationSoftwareTimeout()); + } + } + + R_SUCCEED(); + } + + Result SdmmcController::SetSpeedModeWithTapValue(SpeedMode speed_mode, u8 tap_value) { + /* Check if we need to temporarily disable the device clock. */ + const bool clock_enabled = reg::HasValue(m_sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + + /* Ensure that the clock is disabled for the period we're using it. */ + if (clock_enabled) { + /* Turn off the clock. */ + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + } + + /* Set clock trimmer. */ + /* NOTE: Nintendo does not re-enable the clock if this fails... */ + R_TRY(this->SetClockTrimmer(speed_mode, tap_value)); + + /* Configure for the desired speed mode. */ + switch (speed_mode) { + case SpeedMode_MmcIdentification: + case SpeedMode_SdCardIdentification: + case SpeedMode_MmcLegacySpeed: + case SpeedMode_SdCardDefaultSpeed: + /* Set as normal speed, 3.3V. */ + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.host_control, SD_REG_BITS_ENUM(HOST_CONTROL_HIGH_SPEED_ENABLE, NORMAL_SPEED)); + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 3_3V_SIGNALING)); + break; + case SpeedMode_MmcHighSpeed: + case SpeedMode_SdCardHighSpeed: + /* Set as high speed, 3.3V. */ + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.host_control, SD_REG_BITS_ENUM(HOST_CONTROL_HIGH_SPEED_ENABLE, HIGH_SPEED)); + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 3_3V_SIGNALING)); + break; + case SpeedMode_MmcHs200: + /* Set as HS200, 1.8V. */ + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, HS200)); + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)); + break; + case SpeedMode_MmcHs400: + /* Set as HS400, 1.8V. */ + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, HS400)); + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)); + break; + case SpeedMode_SdCardSdr12: + /* Set as SDR12, 1.8V. */ + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, SDR12)); + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)); + break; + case SpeedMode_SdCardSdr50: + case SpeedMode_SdCardSdr104: + /* Set as SDR104, 1.8V. */ + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, SDR104)); + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)); + break; + case SpeedMode_GcAsicFpgaSpeed: + case SpeedMode_GcAsicSpeed: + /* Set as HS200, 1.8V. */ + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, HS200)); + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + SdHostStandardController::EnsureControl(); + + /* Get the divider setting. */ + u32 target_source_clock_frequency_khz; + u16 x; + GetDividerSetting(std::addressof(target_source_clock_frequency_khz), std::addressof(x), speed_mode); + + /* Set the clock frequency. */ + u32 actual_source_clock_frequency_khz; + ClockResetController::SetClockFrequencyKHz(std::addressof(actual_source_clock_frequency_khz), this->GetClockResetModule(), target_source_clock_frequency_khz); + + /* Set the device clock frequency. */ + const u32 actual_device_clock_frequency_khz = util::DivideUp(actual_source_clock_frequency_khz, x); + SdHostStandardController::SetDeviceClockFrequencyKHz(actual_device_clock_frequency_khz); + + /* Check that the divider is correct. */ + AMS_ABORT_UNLESS((x == 1) || util::IsAligned(x, 2)); + + /* Write the divider val to clock control. */ + const u16 n = x / 2; + const u16 upper_n = n >> 8; + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_VALUE(CLOCK_CONTROL_SDCLK_FREQUENCY_SELECT, n), + SD_REG_BITS_VALUE(CLOCK_CONTROL_UPPER_BITS_OF_SDCLK_FREQUENCY_SELECT, upper_n)); + + /* Re-enable the clock, if we should. */ + if (clock_enabled) { + /* Turn on the clock. */ + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + } + + /* If speed mode is Hs400, calibrate dll. */ + if (speed_mode == SpeedMode_MmcHs400) { + R_TRY(this->CalibrateDll()); + } + + /* Set the current speed mode. */ + m_current_speed_mode = speed_mode; + + R_SUCCEED(); + } + + Result SdmmcController::IssueTuningCommand(u32 command_index) { + /* Check that we're not power saving enable. */ + AMS_ABORT_UNLESS(!SdHostStandardController::IsPowerSavingEnable()); + + /* Wait until command inhibit is done. */ + R_TRY(SdHostStandardController::WaitWhileCommandInhibit(true)); + + /* Set transfer for tuning. */ + SdHostStandardController::SetTransferForTuning(); + + /* If necessary, clear interrupt and enable buffer read ready signal. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + this->ClearInterrupt(); + + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.normal_signal_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED)); + } + #endif + + /* Set the buffer read ready enable, and read status to ensure it takes. */ + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.normal_int_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED)); + reg::Write(m_sdmmc_registers->sd_host_standard_registers.normal_int_status, reg::Read(m_sdmmc_registers->sd_host_standard_registers.normal_int_status)); + + /* Issue command with clock disabled. */ + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + { + SdHostStandardController::SetCommandForTuning(command_index); + + SdHostStandardController::EnsureControl(); + WaitMicroSeconds(1); + SdHostStandardController::AbortTransaction(); + } + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + + /* When we're done waiting, ensure that we clean up appropriately. */ + ON_SCOPE_EXIT { + /* Clear the buffer read ready signal, if we should. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.normal_signal_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, MASKED)); + #endif + + /* Clear the buffer read ready enable. */ + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.normal_int_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, MASKED)); + + /* Wait 8 clocks to ensure configuration takes. */ + SdHostStandardController::EnsureControl(); + WaitClocks(8, SdHostStandardController::GetDeviceClockFrequencyKHz()); + }; + + /* Wait for the command to finish. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + const auto result = SdHostStandardController::WaitInterrupt(TuningCommandTimeoutMilliSeconds); + if (R_SUCCEEDED(result)) { + /* If we succeeded, clear the interrupt. */ + reg::Write(m_sdmmc_registers->sd_host_standard_registers.normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED)); + this->ClearInterrupt(); + R_SUCCEED(); + } else if (sdmmc::ResultWaitInterruptSoftwareTimeout::Includes(result)) { + SdHostStandardController::AbortTransaction(); + R_THROW(sdmmc::ResultIssueTuningCommandSoftwareTimeout()); + } else { + R_RETURN(result); + } + } + #else + { + SdHostStandardController::EnsureControl(); + ManualTimer timer(TuningCommandTimeoutMilliSeconds); + while (true) { + /* Check if we received the interrupt. */ + if (reg::HasValue(m_sdmmc_registers->sd_host_standard_registers.normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED))) { + /* If we did, acknowledge it. */ + reg::Write(m_sdmmc_registers->sd_host_standard_registers.normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED)); + R_SUCCEED(); + } + + /* Otherwise, check if we timed out. */ + if (!timer.Update()) { + SdHostStandardController::AbortTransaction(); + R_THROW(sdmmc::ResultIssueTuningCommandSoftwareTimeout()); + } + } + } + #endif + } + + void SdmmcController::SetDriveCodeOffsets(BusPower bus_power) { + /* Get the offsets. */ + u8 pd, pu; + this->GetAutoCalOffsets(std::addressof(pd), std::addressof(pu), bus_power); + + /* Set the offsets. */ + reg::ReadWrite(m_sdmmc_registers->auto_cal_config, SD_REG_BITS_VALUE(AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET, pd), + SD_REG_BITS_VALUE(AUTO_CAL_CONFIG_AUTO_CAL_PU_OFFSET, pu)); + + /* Wait for 1ms to ensure that our configuration takes. */ + /* NOTE/HACK: Nintendo does not (need) to do this, but they use SvcSleepThread for waits. */ + /* It's unclear why this wait is necessary, but not doing it causes drive strength calibration to fail if done immediately afterwards. */ + util::WaitMicroSeconds(1000); + } + + void SdmmcController::CalibrateDriveStrength(BusPower bus_power) { + /* Reset drive strength calibration status. */ + m_drive_strength_calibration_status = sdmmc::ResultDriveStrengthCalibrationNotCompleted(); + + /* Check if we need to temporarily disable the device clock. */ + const bool clock_enabled = reg::HasValue(m_sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + + /* Ensure that the clock is disabled for the period we're using it. */ + if (clock_enabled) { + /* Turn off the clock. */ + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + } + + /* Calibrate with the clock disabled. */ + { + /* Set SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD. */ + if (reg::HasValue(m_sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 0))) { + reg::ReadWrite(m_sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 1)); + SdHostStandardController::EnsureControl(); + WaitMicroSeconds(1); + } + + /* Calibrate with SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD set. */ + { + /* Begin autocal. */ + reg::ReadWrite(m_sdmmc_registers->auto_cal_config, SD_REG_BITS_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_START, ENABLED), + SD_REG_BITS_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_ENABLE, ENABLED)); + SdHostStandardController::EnsureControl(); + WaitMicroSeconds(2); + + /* Wait up to 10ms for auto cal to complete. */ + ManualTimer timer(10); + while (true) { + /* Check if auto cal is inactive. */ + if (reg::HasValue(m_sdmmc_registers->auto_cal_status, SD_REG_BITS_ENUM(AUTO_CAL_STATUS_AUTO_CAL_ACTIVE, INACTIVE))) { + /* Check the pullup status. */ + const u32 pullup = (reg::GetValue(m_sdmmc_registers->auto_cal_status, SD_REG_BITS_MASK(AUTO_CAL_STATUS_AUTO_CAL_PULLUP))) & 0x1F; + if (pullup == 0x1F) { + m_drive_strength_calibration_status = sdmmc::ResultSdmmcCompShortToGnd(); + } + if (pullup == 0) { + m_drive_strength_calibration_status = sdmmc::ResultSdmmcCompOpen(); + } + + break; + } + + /* Otherwise, check if we've timed out. */ + if (!timer.Update()) { + m_drive_strength_calibration_status = sdmmc::ResultDriveStrengthCalibrationSoftwareTimeout(); + + this->SetDriveStrengthToDefaultValues(bus_power); + reg::ReadWrite(m_sdmmc_registers->auto_cal_config, SD_REG_BITS_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_ENABLE, DISABLED)); + break; + } + } + } + + /* Clear SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD. */ + reg::ReadWrite(m_sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 0)); + } + + /* Re-enable the clock, if we should. */ + if (clock_enabled) { + /* Turn on the clock. */ + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + } + + /* If calibration didn't receive a replacement error, set internal state to success. */ + if (sdmmc::ResultDriveStrengthCalibrationNotCompleted::Includes(m_drive_strength_calibration_status)) { + m_drive_strength_calibration_status = ResultSuccess(); + } + } + + Result SdmmcController::Startup(BusPower bus_power, BusWidth bus_width, SpeedMode speed_mode, bool power_saving_enable) { + /* Verify that we're awake. */ + AMS_ABORT_UNLESS(m_is_awake); + + /* Release the controller from reset. */ + this->ReleaseReset(speed_mode); + + /* Mark that we're not shutdown. */ + m_is_shutdown = false; + + /* Power on the controller. */ + R_TRY(this->PowerOn(bus_power)); + + /* Start up for the specific power. */ + R_TRY(this->StartupCore(bus_power)); + + /* Set our current power/width/speed. */ + SdHostStandardController::SetBusWidth(bus_width); + SdHostStandardController::SetBusPower(bus_power); + R_TRY(this->SetSpeedMode(speed_mode)); + this->SetPowerSaving(power_saving_enable); + + /* Enable clock to the device. */ + SdHostStandardController::EnableDeviceClock(); + + /* Ensure that we can control the device. */ + SdHostStandardController::EnsureControl(); + + R_SUCCEED(); + } + + void SdmmcController::Shutdown() { + /* If we're already shut down, there's nothing to do. */ + if (m_is_shutdown) { + return; + } + + /* If we're currently awake, we need to disable clock/power. */ + if (m_is_awake) { + SdHostStandardController::DisableDeviceClock(); + SdHostStandardController::SetBusPower(BusPower_Off); + SdHostStandardController::EnsureControl(); + } + + /* Power off. */ + this->PowerOff(); + + /* If awake, assert reset. */ + if (m_is_awake) { + this->AssertReset(); + } + + /* Mark that we're shutdown. */ + m_is_shutdown = true; + } + + void SdmmcController::PutToSleep() { + /* If we're already shut down or asleep, there's nothing to do. */ + if (m_is_shutdown || !m_is_awake) { + return; + } + + /* Save values before sleep. */ + m_bus_power_before_sleep = SdHostStandardController::GetBusPower(); + m_bus_width_before_sleep = SdHostStandardController::GetBusWidth(); + m_speed_mode_before_sleep = m_current_speed_mode; + m_tap_value_before_sleep = this->GetCurrentTapValue(); + m_is_powersaving_enable_before_sleep = SdHostStandardController::IsPowerSavingEnable(); + + /* Disable clock/power to the device. */ + SdHostStandardController::DisableDeviceClock(); + SdHostStandardController::SetBusPower(BusPower_Off); + SdHostStandardController::EnsureControl(); + + /* Assert reset. */ + this->AssertReset(); + + /* Mark that we're asleep. */ + m_is_awake = false; + } + + Result SdmmcController::Awaken() { + /* If we're shut down, or if we're awake already, there's nothing to do. */ + R_SUCCEED_IF(m_is_shutdown); + R_SUCCEED_IF(m_is_awake); + + /* Mark that we're awake. */ + m_is_awake = true; + + /* Clear pad parked status. */ + this->ClearPadParked(); + + /* Release reset. */ + this->ReleaseReset(m_speed_mode_before_sleep); + + /* Start up for the correct power. */ + R_TRY(this->StartupCore(m_bus_power_before_sleep)); + + /* Configure values to what they were before sleep. */ + SdHostStandardController::SetBusWidth(m_bus_width_before_sleep); + SdHostStandardController::SetBusPower(m_bus_power_before_sleep); + R_TRY(this->SetSpeedModeWithTapValue(m_speed_mode_before_sleep, m_tap_value_before_sleep)); + this->SetPowerSaving(m_is_powersaving_enable_before_sleep); + + /* Enable clock to the device. */ + SdHostStandardController::EnableDeviceClock(); + SdHostStandardController::EnsureControl(); + + R_SUCCEED(); + } + + Result SdmmcController::SwitchToSdr12() { + /* Disable clock. */ + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + + /* Check that the dat lines are all low. */ + R_UNLESS(reg::HasValue(m_sdmmc_registers->sd_host_standard_registers.present_state, SD_REG_BITS_VALUE(PRESENT_STATE_DAT0_3_LINE_SIGNAL_LEVEL, 0b0000)), sdmmc::ResultSdCardNotReadyToVoltageSwitch()); + + /* Set Speed Mode. */ + R_TRY(this->SetSpeedMode(SpeedMode_SdCardSdr12)); + + /* Set voltage to 1.8V. */ + SdHostStandardController::EnsureControl(); + R_TRY(this->LowerBusPower()); + this->SetSchmittTrigger(BusPower_1_8V); + + /* Perform drive strength calibration at the new power. */ + this->SetDriveCodeOffsets(BusPower_1_8V); + this->CalibrateDriveStrength(BusPower_1_8V); + + /* Set the bus power in standard controller. */ + SdHostStandardController::SetBusPower(BusPower_1_8V); + + /* Wait up to 5ms for the switch to take. */ + SdHostStandardController::EnsureControl(); + WaitMicroSeconds(5000); + + /* Check that we switched to 1.8V. */ + R_UNLESS(reg::HasValue(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)), sdmmc::ResultSdHostStandardFailSwitchTo1_8V()); + + /* Enable clock, and wait 1ms. */ + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + SdHostStandardController::EnsureControl(); + WaitMicroSeconds(1000); + + /* Check that the dat lines are all high. */ + R_UNLESS(reg::HasValue(m_sdmmc_registers->sd_host_standard_registers.present_state, SD_REG_BITS_VALUE(PRESENT_STATE_DAT0_3_LINE_SIGNAL_LEVEL, 0b1111)), sdmmc::ResultSdCardNotCompleteVoltageSwitch()); + + R_SUCCEED(); + } + + Result SdmmcController::SetSpeedMode(SpeedMode speed_mode) { + /* Get the tap value. */ + u8 tap_value; + if (speed_mode == SpeedMode_MmcHs400) { + AMS_ABORT_UNLESS(m_is_valid_tap_value_for_hs_400); + tap_value = m_tap_value_for_hs_400; + } else { + tap_value = this->GetDefaultInboundTapValue(); + } + + /* Set the speed mode. */ + R_TRY(this->SetSpeedModeWithTapValue(speed_mode, tap_value)); + + R_SUCCEED(); + } + + void SdmmcController::SetPowerSaving(bool en) { + /* If necessary, calibrate the drive strength. */ + if (this->IsNeedPeriodicDriveStrengthCalibration() && !en && SdHostStandardController::IsDeviceClockEnable()) { + this->CalibrateDriveStrength(SdHostStandardController::GetBusPower()); + } + + return SdHostStandardController::SetPowerSaving(en); + } + + void SdmmcController::EnableDeviceClock() { + /* If necessary, calibrate the drive strength. */ + if (this->IsNeedPeriodicDriveStrengthCalibration() && !SdHostStandardController::IsPowerSavingEnable()) { + this->CalibrateDriveStrength(SdHostStandardController::GetBusPower()); + } + + return SdHostStandardController::EnableDeviceClock(); + } + + Result SdmmcController::IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) { + /* If necessary, calibrate the drive strength. */ + if (this->IsNeedPeriodicDriveStrengthCalibration() && SdHostStandardController::IsPowerSavingEnable()) { + this->CalibrateDriveStrength(SdHostStandardController::GetBusPower()); + } + + R_RETURN(SdHostStandardController::IssueCommand(command, xfer_data, out_num_transferred_blocks)); + } + + Result SdmmcController::IssueStopTransmissionCommand(u32 *out_response) { + /* If necessary, calibrate the drive strength. */ + if (this->IsNeedPeriodicDriveStrengthCalibration() && SdHostStandardController::IsPowerSavingEnable()) { + this->CalibrateDriveStrength(SdHostStandardController::GetBusPower()); + } + + R_RETURN(SdHostStandardController::IssueStopTransmissionCommand(out_response)); + } + + Result SdmmcController::Tuning(SpeedMode speed_mode, u32 command_index) { + /* Clear vendor tuning control 1. */ + reg::Write(m_sdmmc_registers->vendor_tuning_cntrl1, 0); + + /* Determine/configure the number of tries. */ + int num_tries; + switch (speed_mode) { + case SpeedMode_MmcHs200: + case SpeedMode_MmcHs400: + case SpeedMode_SdCardSdr104: + num_tries = 128; + reg::ReadWrite(m_sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_NUM_TUNING_ITERATIONS, TRIES_128)); + break; + case SpeedMode_SdCardSdr50: + case SpeedMode_GcAsicFpgaSpeed: + case SpeedMode_GcAsicSpeed: + num_tries = 256; + reg::ReadWrite(m_sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_NUM_TUNING_ITERATIONS, TRIES_256)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Configure the multiplier. */ + reg::ReadWrite(m_sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_VALUE(VENDOR_TUNING_CNTRL0_MUL_M, 1)); + + /* Configure tap value to be updated by hardware. */ + reg::ReadWrite(m_sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_TAP_VALUE_UPDATED_BY_HW, UPDATED_BY_HW)); + + /* Configure to execute tuning. */ + reg::ReadWrite(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_EXECUTE_TUNING, EXECUTE_TUNING)); + + /* Perform tuning num_tries times. */ + for (int i = 0; /* ... */; ++i) { + /* Check if we've been removed. */ + R_TRY(this->CheckRemoved()); + + /* Issue the command. */ + this->IssueTuningCommand(command_index); + + /* Check if tuning is done. */ + if (i >= num_tries) { + break; + } + + if (reg::HasValue(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_EXECUTE_TUNING, TUNING_COMPLETED))) { + break; + } + } + + /* Check if we're using the tuned clock. */ + R_UNLESS(reg::HasValue(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_SAMPLING_CLOCK, USING_TUNED_CLOCK)), sdmmc::ResultTuningFailed()); + + R_SUCCEED(); + } + + void SdmmcController::SaveTuningStatusForHs400() { + /* Save the current tap value. */ + m_tap_value_for_hs_400 = GetCurrentTapValue(); + m_is_valid_tap_value_for_hs_400 = true; + } + + Result Sdmmc1Controller::PowerOnForRegisterControl(BusPower bus_power) { + AMS_ABORT_UNLESS(bus_power == BusPower_3_3V); + + /* Nintendo sets the current bus power regardless of whether the call succeeds. */ + ON_SCOPE_EXIT { m_current_bus_power = BusPower_3_3V; }; + + /* pcv::PowerOn(pcv::PowerControlTarget_SdCard, 3300000); */ + R_TRY(m_power_controller->PowerOn(BusPower_3_3V)); + + R_SUCCEED(); + } + + void Sdmmc1Controller::PowerOffForRegisterControl() { + /* If we're already off, there's nothing to do. */ + if (m_current_bus_power == BusPower_Off) { + return; + } + + /* If we're at 3.3V, lower to 1.8V. */ + if (m_current_bus_power == BusPower_3_3V) { + /* pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */ + m_power_controller->LowerBusPower(); + + /* Set our bus power. */ + m_current_bus_power = BusPower_1_8V; + } + + /* pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1OutputHigh); */ + pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1OutputHigh); + + + /* pcv::PowerOff(pcv::PowerControlTarget_SdCard); */ + m_power_controller->PowerOff(); + + /* Set our bus power. */ + m_current_bus_power = BusPower_Off; + + /* pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1ResetState); */ + pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1ResetState); + } + + Result Sdmmc1Controller::LowerBusPowerForRegisterControl() { + /* Nintendo sets the current bus power regardless of whether the call succeeds. */ + ON_SCOPE_EXIT { m_current_bus_power = BusPower_1_8V; }; + + /* pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */ + R_TRY(m_power_controller->LowerBusPower()); + + R_SUCCEED(); + } + + void Sdmmc1Controller::SetSchmittTriggerForRegisterControl(BusPower bus_power) { + SdHostStandardController::EnsureControl(); + + if (IsSocMariko()) { + /* pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */ + pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1SchmtEnable); + } else { + switch (bus_power) { + case BusPower_1_8V: + /* pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */ + pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1SchmtEnable); + break; + case BusPower_3_3V: + /* pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtDisable); */ + pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1SchmtDisable); + break; + case BusPower_Off: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + Result Sdmmc1Controller::PowerOnForPcvControl(BusPower bus_power) { + AMS_ABORT_UNLESS(bus_power == BusPower_3_3V); + + /* Nintendo sets the current bus power regardless of whether the call succeeds. */ + ON_SCOPE_EXIT { m_current_bus_power = BusPower_3_3V; }; + + /* TODO: R_RETURN(pcv::PowerOn(pcv::PowerControlTarget_SdCard, 3300000)); */ + R_SUCCEED(); + } + + void Sdmmc1Controller::PowerOffForPcvControl() { + /* If we're already off, there's nothing to do. */ + if (m_current_bus_power == BusPower_Off) { + return; + } + + /* If we're at 3.3V, lower to 1.8V. */ + { + /* TODO: pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */ + m_current_bus_power = BusPower_1_8V; + } + + /* TODO: pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1OutputHigh); */ + + /* TODO: pcv::PowerOff(pcv::PowerControlTarget_SdCard); */ + m_current_bus_power = BusPower_Off; + + /* TODO: pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1ResetState); */ + + } + + Result Sdmmc1Controller::LowerBusPowerForPcvControl() { + /* Nintendo sets the current bus power regardless of whether the call succeeds. */ + ON_SCOPE_EXIT { m_current_bus_power = BusPower_1_8V; }; + + /* TODO: R_RETURN(pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000)); */ + R_SUCCEED(); + } + + void Sdmmc1Controller::SetSchmittTriggerForPcvControl(BusPower bus_power) { + SdHostStandardController::EnsureControl(); + + if (IsSocMariko()) { + /* TODO: pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */ + } else { + switch (bus_power) { + case BusPower_1_8V: + /* TODO: pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */ + break; + case BusPower_3_3V: + /* TODO: pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtDisable); */ + break; + case BusPower_Off: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + #endif + + Result Sdmmc1Controller::PowerOn(BusPower bus_power) { + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + if (m_is_pcv_control) { + R_RETURN(this->PowerOnForPcvControl(bus_power)); + } else + #endif + { + R_RETURN(this->PowerOnForRegisterControl(bus_power)); + } + } + + void Sdmmc1Controller::PowerOff() { + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + if (m_is_pcv_control) { + return this->PowerOffForPcvControl(); + } else + #endif + { + return this->PowerOffForRegisterControl(); + } + } + + Result Sdmmc1Controller::LowerBusPower() { + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + if (m_is_pcv_control) { + R_RETURN(this->LowerBusPowerForPcvControl()); + } else + #endif + { + R_RETURN(this->LowerBusPowerForRegisterControl()); + } + } + + void Sdmmc1Controller::SetSchmittTrigger(BusPower bus_power) { + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + if (m_is_pcv_control) { + return this->SetSchmittTriggerForPcvControl(bus_power); + } else + #endif + { + return this->SetSchmittTriggerForRegisterControl(bus_power); + } + } + + void Sdmmc1Controller::Initialize() { + return this->InitializeForRegisterControl(); + } + + void Sdmmc1Controller::Finalize() { + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + if (m_is_pcv_control) { + return this->FinalizeForPcvControl(); + } else + #endif + { + return this->FinalizeForRegisterControl(); + } + } + + void Sdmmc1Controller::InitializeForRegisterControl() { + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + /* Mark ourselves as initialized by register control. */ + m_is_pcv_control = false; + #endif + + /* pinmux::Initialize(); */ + /* This just opens a session handle to pinmux service, no work to do. */ + + /* pinmux::OpenSession(std::addressof(m_pinmux_session), pinmux::AssignablePinGroupName_Sdmmc1); */ + /* This just sets the session's internal value to the pin group name, so nothing to do here either. */ + + /* pcv::Initialize(); */ + /* This initializes a lot of globals in pcv, most of which we don't care about. */ + /* However, we do care about the Sdmmc1PowerController. */ + AMS_ABORT_UNLESS(m_power_controller == nullptr); + m_power_controller = util::ConstructAt(m_power_controller_storage); + + /* Perform base initialization. */ + SdmmcController::Initialize(); + } + + void Sdmmc1Controller::FinalizeForRegisterControl() { + /* Perform base finalization. */ + SdmmcController::Finalize(); + + /* pcv::Finalize(); */ + /* As with initialize, we mostly don't care about the globals this touches. */ + /* However, we do want to finalize the Sdmmc1PowerController. */ + AMS_ABORT_UNLESS(m_power_controller != nullptr); + m_power_controller = nullptr; + util::DestroyAt(m_power_controller_storage); + + /* pinmux::CloseSession(std::addressof(m_pinmux_session)); */ + /* This does nothing. */ + + /* pinmux::Finalize(); */ + /* This does nothing. */ + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + /* Mark ourselves as initialized by register control. */ + m_is_pcv_control = false; + #endif + } + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + void Sdmmc1Controller::InitializeForPcvControl() { + /* Mark ourselves as initialized by pcv control. */ + m_is_pcv_control = true; + + /* TODO: pinmux::Initialize(); */ + /* TODO: pinmux::OpenSession(std::addressof(m_pinmux_session), pinmux::AssignablePinGroupName_Sdmmc1); */ + /* TODO: pcv::Initialize(); */ + + /* Perform base initialization. */ + SdmmcController::Initialize(); + } + + void Sdmmc1Controller::FinalizeForPcvControl() { + /* Perform base finalization. */ + SdmmcController::Finalize(); + + /* TODO: pcv::Finalize(); */ + /* TODO: pinmux::CloseSession(std::addressof(m_pinmux_session)); */ + /* TODO: pinmux::Finalize(); */ + + /* Mark ourselves as initialized by register control. */ + m_is_pcv_control = false; + } + #endif + + namespace { + + constexpr inline dd::PhysicalAddress PmcRegistersPhysicalAddress = 0x7000E400; + + } + + Result Sdmmc1Controller::PowerController::ControlVddioSdmmc1(BusPower bus_power) { + /* Configure appropriate voltage. */ + switch (bus_power) { + case BusPower_Off: + R_TRY(SetSdCardVoltageEnabled(false)); + break; + case BusPower_1_8V: + R_TRY(SetSdCardVoltageValue(1'800'000)); + R_TRY(SetSdCardVoltageEnabled(true)); + break; + case BusPower_3_3V: + R_TRY(SetSdCardVoltageValue(3'300'000)); + R_TRY(SetSdCardVoltageEnabled(true)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + R_SUCCEED(); + } + + void Sdmmc1Controller::PowerController::SetSdmmcIoMode(bool is_3_3V) { + /* Determine the address we're updating. */ + constexpr dd::PhysicalAddress ApbdevPmcPwrDetValAddress = PmcRegistersPhysicalAddress + APBDEV_PMC_PWR_DET_VAL; + + /* Read the current value. */ + u32 value = dd::ReadIoRegister(ApbdevPmcPwrDetValAddress); + + /* Mask out the existing bits. */ + value &= ~(reg::EncodeMask(PMC_REG_BITS_MASK(PWR_DET_VAL_SDMMC1))); + + /* ORR in the new bits. */ + value |= reg::Encode(PMC_REG_BITS_ENUM_SEL(PWR_DET_VAL_SDMMC1, is_3_3V, ENABLE, DISABLE)); + + /* Write the new value. */ + dd::WriteIoRegister(ApbdevPmcPwrDetValAddress, value); + + /* Read the value back to be sure our write takes. */ + dd::ReadIoRegister(ApbdevPmcPwrDetValAddress); + } + + void Sdmmc1Controller::PowerController::ControlRailSdmmc1Io(bool is_power_on) { + /* Determine the address we're updating. */ + constexpr dd::PhysicalAddress ApbdevPmcNoIoPowerAddress = PmcRegistersPhysicalAddress + APBDEV_PMC_NO_IOPOWER; + + /* Read the current value. */ + u32 value = dd::ReadIoRegister(ApbdevPmcNoIoPowerAddress); + + /* Mask out the existing bits. */ + value &= ~(reg::EncodeMask(PMC_REG_BITS_MASK(NO_IOPOWER_SDMMC1))); + + /* ORR in the new bits. */ + value |= reg::Encode(PMC_REG_BITS_ENUM_SEL(NO_IOPOWER_SDMMC1, is_power_on, DISABLE, ENABLE)); + + /* Write the new value. */ + dd::WriteIoRegister(ApbdevPmcNoIoPowerAddress, value); + + /* Read the value back to be sure our write takes. */ + dd::ReadIoRegister(ApbdevPmcNoIoPowerAddress); + } + + Sdmmc1Controller::PowerController::PowerController() : m_current_bus_power(BusPower_Off) { + /* gpio::Initialize(); */ + /* ... */ + + /* Open gpio session. */ + /* gpio::OpenSession(std::addressof(m_gpio_pad_session), gpio::GpioPadName_PowSdEn); */ + gpio_impl::OpenSession(gpio_impl::GpioPadName_PowSdEn); + + /* Configure the gpio as low/output. */ + /* gpio::SetValue(std::addressof(m_gpio_pad_session), gpio::GpioValue_Low); */ + gpio_impl::SetValue(gpio_impl::GpioPadName_PowSdEn, gpio_impl::GpioValue_Low); + + /* gpio::SetDirection(std::addressof(m_gpio_pad_session), gpio::Direction_Output); */ + gpio_impl::SetDirection(gpio_impl::GpioPadName_PowSdEn, gpio_impl::Direction_Output); + } + + Sdmmc1Controller::PowerController::~PowerController() { + /* gpio::CloseSession(std::addressof(m_gpio_pad_session)); */ + gpio_impl::CloseSession(gpio_impl::GpioPadName_PowSdEn); + + /* gpio::Finalize(); */ + /* ... */ + } + + Result Sdmmc1Controller::PowerController::PowerOn(BusPower bus_power) { + /* Bus power should be off, and if it's not we don't need to do anything. */ + AMS_ASSERT(m_current_bus_power == BusPower_Off); + R_SUCCEED_IF(m_current_bus_power != BusPower_Off); + + /* Power on requires the target bus power be 3.3V. */ + AMS_ABORT_UNLESS(bus_power == BusPower_3_3V); + + /* Enable the rail. */ + this->ControlRailSdmmc1Io(true); + + /* Set the SD power GPIO to high. */ + /* gpio::SetValue(std::addressof(m_gpio_pad_session), gpio::GpioValue_High); */ + gpio_impl::SetValue(gpio_impl::GpioPadName_PowSdEn, gpio_impl::GpioValue_High); + + /* Wait 10ms for power change to take. */ + WaitMicroSeconds(10000); + + /* Configure Sdmmc1 IO as 3.3V. */ + this->SetSdmmcIoMode(true); + R_TRY(this->ControlVddioSdmmc1(BusPower_3_3V)); + + /* Wait 130 us for changes to take. */ + WaitMicroSeconds(130); + + /* Update our current bus power. */ + m_current_bus_power = bus_power; + + R_SUCCEED(); + } + + Result Sdmmc1Controller::PowerController::PowerOff() { + /* Bus power should be on, and if it's not we don't need to do anything. */ + AMS_ASSERT(m_current_bus_power != BusPower_Off); + R_SUCCEED_IF(m_current_bus_power == BusPower_Off); + + /* Bus power should be 1.8V. */ + /* NOTE: the result returned here is 0x8C0 (regulator::ResultIllegalRequest()) on newer firmwares. */ + AMS_ASSERT(m_current_bus_power == BusPower_1_8V); + R_UNLESS(m_current_bus_power == BusPower_1_8V, pcv::ResultIllegalRequest()); + + /* Disable vddio, and wait 4 ms. */ + this->ControlVddioSdmmc1(BusPower_Off); + WaitMicroSeconds(4000); + + /* Set the SD power GPIO to low. */ + /* gpio::SetValue(std::addressof(m_gpio_pad_session), gpio::GpioValue_Low); */ + gpio_impl::SetValue(gpio_impl::GpioPadName_PowSdEn, gpio_impl::GpioValue_Low); + + /* Wait 239ms for the gpio config to take. */ + WaitMicroSeconds(239000); + + /* Disable the rail. */ + this->ControlRailSdmmc1Io(false); + this->SetSdmmcIoMode(true); + + /* Update our current bus power. */ + m_current_bus_power = BusPower_Off; + + R_SUCCEED(); + } + + Result Sdmmc1Controller::PowerController::LowerBusPower() { + /* Bus power should be 3.3V, and if it's not we don't need to do anything. */ + AMS_ASSERT(m_current_bus_power == BusPower_3_3V); + R_SUCCEED_IF(m_current_bus_power != BusPower_3_3V); + + /* Configure as 1.8V, then wait 150us for it to take. */ + R_TRY(this->ControlVddioSdmmc1(BusPower_1_8V)); + WaitMicroSeconds(150); + this->SetSdmmcIoMode(false); + + /* Update our current bus power. */ + m_current_bus_power = BusPower_1_8V; + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.hpp new file mode 100644 index 00000000..0e04c489 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include "sdmmc_sd_host_standard_controller.hpp" +#include "sdmmc_clock_reset_controller.hpp" + +namespace ams::sdmmc::impl { + + bool IsSocMariko(); + + constexpr inline size_t SdmmcRegistersSize = 0x200; + + constexpr inline dd::PhysicalAddress ApbMiscRegistersPhysicalAddress = UINT64_C(0x70000000); + constexpr inline size_t ApbMiscRegistersSize = 16_KB; + + class SdmmcController : public SdHostStandardController { + private: + struct SdmmcRegisters { + /* Standard registers. */ + volatile SdHostStandardRegisters sd_host_standard_registers; + + /* Vendor specific registers */ + volatile uint32_t vendor_clock_cntrl; + volatile uint32_t vendor_sys_sw_cntrl; + volatile uint32_t vendor_err_intr_status; + volatile uint32_t vendor_cap_overrides; + volatile uint32_t vendor_boot_cntrl; + volatile uint32_t vendor_boot_ack_timeout; + volatile uint32_t vendor_boot_dat_timeout; + volatile uint32_t vendor_debounce_count; + volatile uint32_t vendor_misc_cntrl; + volatile uint32_t max_current_override; + volatile uint32_t max_current_override_hi; + volatile uint32_t _0x12c[0x20]; + volatile uint32_t vendor_io_trim_cntrl; + + /* Start of sdmmc2/sdmmc4 only */ + volatile uint32_t vendor_dllcal_cfg; + volatile uint32_t vendor_dll_ctrl0; + volatile uint32_t vendor_dll_ctrl1; + volatile uint32_t vendor_dllcal_cfg_sta; + /* End of sdmmc2/sdmmc4 only */ + + volatile uint32_t vendor_tuning_cntrl0; + volatile uint32_t vendor_tuning_cntrl1; + volatile uint32_t vendor_tuning_status0; + volatile uint32_t vendor_tuning_status1; + volatile uint32_t vendor_clk_gate_hysteresis_count; + volatile uint32_t vendor_preset_val0; + volatile uint32_t vendor_preset_val1; + volatile uint32_t vendor_preset_val2; + volatile uint32_t sdmemcomppadctrl; + volatile uint32_t auto_cal_config; + volatile uint32_t auto_cal_interval; + volatile uint32_t auto_cal_status; + volatile uint32_t io_spare; + volatile uint32_t sdmmca_mccif_fifoctrl; + volatile uint32_t timeout_wcoal_sdmmca; + volatile uint32_t _0x1fc; + }; + static_assert(sizeof(SdmmcRegisters) == SdmmcRegistersSize); + private: + SdmmcRegisters *m_sdmmc_registers; + bool m_is_shutdown; + bool m_is_awake; + SpeedMode m_current_speed_mode; + BusPower m_bus_power_before_sleep; + BusWidth m_bus_width_before_sleep; + SpeedMode m_speed_mode_before_sleep; + u8 m_tap_value_before_sleep; + bool m_is_powersaving_enable_before_sleep; + u8 m_tap_value_for_hs_400; + bool m_is_valid_tap_value_for_hs_400; + Result m_drive_strength_calibration_status; + private: + void ReleaseReset(SpeedMode speed_mode); + void AssertReset(); + Result StartupCore(BusPower bus_power); + Result SetClockTrimmer(SpeedMode speed_mode, u8 tap_value); + u8 GetCurrentTapValue(); + Result CalibrateDll(); + Result SetSpeedModeWithTapValue(SpeedMode speed_mode, u8 tap_value); + Result IssueTuningCommand(u32 command_index); + protected: + void SetDriveCodeOffsets(BusPower bus_power); + void CalibrateDriveStrength(BusPower bus_power); + + virtual void SetPad() = 0; + + virtual ClockResetController::Module GetClockResetModule() const = 0; + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual int GetInterruptNumber() const = 0; + virtual os::InterruptEventType *GetInterruptEvent() const = 0; + #endif + + virtual bool IsNeedPeriodicDriveStrengthCalibration() = 0; + virtual void ClearPadParked() = 0; + virtual Result PowerOn(BusPower bus_power) = 0; + virtual void PowerOff() = 0; + virtual Result LowerBusPower() = 0; + virtual void SetSchmittTrigger(BusPower bus_power) = 0; + virtual u8 GetOutboundTapValue() const = 0; + virtual u8 GetDefaultInboundTapValue() const = 0; + virtual u8 GetVrefSelValue() const = 0; + virtual void SetSlewCodes() = 0; + virtual void GetAutoCalOffsets(u8 *out_auto_cal_pd_offset, u8 *out_auto_cal_pu_offset, BusPower bus_power) const = 0; + virtual void SetDriveStrengthToDefaultValues(BusPower bus_power) = 0; + public: + explicit SdmmcController(dd::PhysicalAddress registers_phys_addr) : SdHostStandardController(registers_phys_addr, SdmmcRegistersSize) { + /* Set sdmmc registers. */ + static_assert(AMS_OFFSETOF(SdmmcRegisters, sd_host_standard_registers) == 0); + m_sdmmc_registers = reinterpret_cast<SdmmcRegisters *>(m_registers); + + m_is_shutdown = true; + m_is_awake = true; + m_is_valid_tap_value_for_hs_400 = false; + m_drive_strength_calibration_status = sdmmc::ResultDriveStrengthCalibrationNotCompleted(); + m_tap_value_for_hs_400 = 0; + m_current_speed_mode = SpeedMode_MmcIdentification; + m_bus_power_before_sleep = BusPower_Off; + m_bus_width_before_sleep = BusWidth_1Bit; + m_speed_mode_before_sleep = SpeedMode_MmcIdentification; + m_tap_value_before_sleep = 0; + m_is_powersaving_enable_before_sleep = false; + } + + virtual void Initialize() override { + /* Set pad. */ + this->SetPad(); + + /* Initialize our clock/reset module. */ + ClockResetController::Initialize(this->GetClockResetModule()); + + /* If necessary, initialize our interrupt event. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + os::InterruptEventType *interrupt_event = this->GetInterruptEvent(); + os::InitializeInterruptEvent(interrupt_event, this->GetInterruptNumber(), os::EventClearMode_ManualClear); + SdHostStandardController::PreSetInterruptEvent(interrupt_event); + } + #endif + + /* Perform base initialization. */ + SdHostStandardController::Initialize(); + } + + virtual void Finalize() override { + /* Perform base finalization. */ + SdHostStandardController::Finalize(); + + /* If necessary, finalize our interrupt event. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + os::FinalizeInterruptEvent(this->GetInterruptEvent()); + } + #endif + + /* Finalize our clock/reset module. */ + ClockResetController::Finalize(this->GetClockResetModule()); + } + + virtual Result Startup(BusPower bus_power, BusWidth bus_width, SpeedMode speed_mode, bool power_saving_enable) override; + virtual void Shutdown() override; + virtual void PutToSleep() override; + virtual Result Awaken() override; + virtual Result SwitchToSdr12() override; + virtual Result SetSpeedMode(SpeedMode speed_mode) override; + + virtual SpeedMode GetSpeedMode() const override { + return m_current_speed_mode; + } + + virtual void SetPowerSaving(bool en) override; + virtual void EnableDeviceClock() override; + + virtual Result IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) override; + virtual Result IssueStopTransmissionCommand(u32 *out_response) override; + + virtual bool IsSupportedTuning() const override { + return true; + } + + virtual Result Tuning(SpeedMode speed_mode, u32 command_index) override; + virtual void SaveTuningStatusForHs400() override; + + virtual Result GetInternalStatus() const override { + return m_drive_strength_calibration_status; + } + }; + + constexpr inline dd::PhysicalAddress Sdmmc1RegistersPhysicalAddress = UINT64_C(0x700B0000); + + class Sdmmc1Controller : public SdmmcController { + private: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + static constinit inline os::InterruptEventType s_interrupt_event{}; + #endif + + /* NOTE: This is a fascimile of pcv's Sdmmc1PowerController. */ + class PowerController { + NON_COPYABLE(PowerController); + NON_MOVEABLE(PowerController); + private: + BusPower m_current_bus_power; + private: + Result ControlVddioSdmmc1(BusPower bus_power); + void SetSdmmcIoMode(bool is_3_3V); + void ControlRailSdmmc1Io(bool is_power_on); + public: + PowerController(); + ~PowerController(); + + Result PowerOn(BusPower bus_power); + Result PowerOff(); + Result LowerBusPower(); + }; + private: + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + /* TODO: pinmux::PinmuxSession m_pinmux_session; */ + #endif + BusPower m_current_bus_power; + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + bool m_is_pcv_control; + #endif + util::TypedStorage<PowerController> m_power_controller_storage; + PowerController *m_power_controller; + private: + Result PowerOnForRegisterControl(BusPower bus_power); + void PowerOffForRegisterControl(); + Result LowerBusPowerForRegisterControl(); + void SetSchmittTriggerForRegisterControl(BusPower bus_power); + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + Result PowerOnForPcvControl(BusPower bus_power); + void PowerOffForPcvControl(); + Result LowerBusPowerForPcvControl(); + void SetSchmittTriggerForPcvControl(BusPower bus_power); + #endif + protected: + virtual void SetPad() override { + /* Nothing is needed here. */ + } + + virtual ClockResetController::Module GetClockResetModule() const override { + return ClockResetController::Module_Sdmmc1; + } + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual int GetInterruptNumber() const override { + return 46; + } + + virtual os::InterruptEventType *GetInterruptEvent() const override { + return std::addressof(s_interrupt_event); + } + #endif + + virtual bool IsNeedPeriodicDriveStrengthCalibration() override { + return !IsSocMariko(); + } + + virtual void ClearPadParked() override { + /* Nothing is needed here. */ + } + + virtual Result PowerOn(BusPower bus_power) override; + virtual void PowerOff() override; + virtual Result LowerBusPower() override; + + virtual void SetSchmittTrigger(BusPower bus_power) override; + + virtual u8 GetOutboundTapValue() const override { + if (IsSocMariko()) { + return 0xE; + } else { + return 0x2; + } + } + + virtual u8 GetDefaultInboundTapValue() const override { + if (IsSocMariko()) { + return 0xB; + } else { + return 0x4; + } + } + + virtual u8 GetVrefSelValue() const override { + if (IsSocMariko()) { + return 0x0; + } else { + return 0x7; + } + } + + virtual void SetSlewCodes() override { + if (IsSocMariko()) { + /* Do nothing. */ + } else { + /* Ensure that we can control registers. */ + SdHostStandardController::EnsureControl(); + + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + /* Write the slew values to APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL. */ + reg::ReadWrite(apb_address + APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_CLK_CFG_CAL_DRVDN_SLWR, 0x1), + APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_CLK_CFG_CAL_DRVDN_SLWF, 0x1)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL); + } + } + + virtual void GetAutoCalOffsets(u8 *out_auto_cal_pd_offset, u8 *out_auto_cal_pu_offset, BusPower bus_power) const override { + /* Ensure that we can write the offsets. */ + AMS_ABORT_UNLESS(out_auto_cal_pd_offset != nullptr); + AMS_ABORT_UNLESS(out_auto_cal_pu_offset != nullptr); + + /* Set the offsets. */ + if (IsSocMariko()) { + switch (bus_power) { + case BusPower_1_8V: + *out_auto_cal_pd_offset = 6; + *out_auto_cal_pu_offset = 6; + break; + case BusPower_3_3V: + *out_auto_cal_pd_offset = 0; + *out_auto_cal_pu_offset = 0; + break; + case BusPower_Off: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } else { + switch (bus_power) { + case BusPower_1_8V: + *out_auto_cal_pd_offset = 0x7B; + *out_auto_cal_pu_offset = 0x7B; + break; + case BusPower_3_3V: + *out_auto_cal_pd_offset = 0x7D; + *out_auto_cal_pu_offset = 0; + break; + case BusPower_Off: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + virtual void SetDriveStrengthToDefaultValues(BusPower bus_power) override { + /* Ensure that we can control registers. */ + SdHostStandardController::EnsureControl(); + + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + /* Determine the drive code values. */ + u8 drvdn, drvup; + if (IsSocMariko()) { + drvdn = 0x8; + drvup = 0x8; + } else { + switch (bus_power) { + case BusPower_1_8V: + drvdn = 0xF; + drvup = 0xB; + break; + case BusPower_3_3V: + drvdn = 0xC; + drvup = 0xC; + break; + case BusPower_Off: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + /* Write the drv up/down values to APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL. */ + reg::ReadWrite(apb_address + APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_PAD_CAL_DRVDN, drvdn), + APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_PAD_CAL_DRVUP, drvup)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL); + } + public: + Sdmmc1Controller() : SdmmcController(Sdmmc1RegistersPhysicalAddress) { + m_current_bus_power = BusPower_Off; + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + m_is_pcv_control = false; + #endif + m_power_controller = nullptr; + } + + virtual void Initialize() override; + virtual void Finalize() override; + + void InitializeForRegisterControl(); + void FinalizeForRegisterControl(); + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + void InitializeForPcvControl(); + void FinalizeForPcvControl(); + #endif + + virtual bool IsSupportedBusPower(BusPower bus_power) const override { + switch (bus_power) { + case BusPower_Off: return true; + case BusPower_1_8V: return true; + case BusPower_3_3V: return true; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + virtual bool IsSupportedBusWidth(BusWidth bus_width) const override { + switch (bus_width) { + case BusWidth_1Bit: return true; + case BusWidth_4Bit: return true; + case BusWidth_8Bit: return false; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + }; + + class Sdmmc2And4Controller : public SdmmcController { + protected: + virtual bool IsNeedPeriodicDriveStrengthCalibration() override { + return false; + } + + virtual Result PowerOn(BusPower bus_power) override { + /* Power for SDMMC2/4 is assumed on, so we don't need to do anything. */ + AMS_UNUSED(bus_power); + R_SUCCEED(); + } + + virtual void PowerOff() override { + /* Power for SDMMC2/4 is assumed on, so we don't need to do anything. */ + } + + virtual Result LowerBusPower() override { + AMS_ABORT("Sdmmc2And4Controller cannot lower bus power\n"); + } + + virtual void SetSchmittTrigger(BusPower bus_power) override { + /* Do nothing. */ + AMS_UNUSED(bus_power); + } + + virtual u8 GetOutboundTapValue() const override { + if (IsSocMariko()) { + return 0xD; + } else { + return 0x8; + } + } + + virtual u8 GetDefaultInboundTapValue() const override { + if (IsSocMariko()) { + return 0xB; + } else { + return 0x0; + } + } + + virtual u8 GetVrefSelValue() const override { + return 0x7; + } + + virtual void SetSlewCodes() override { + /* Do nothing. */ + } + + virtual void GetAutoCalOffsets(u8 *out_auto_cal_pd_offset, u8 *out_auto_cal_pu_offset, BusPower bus_power) const override { + /* Ensure that we can write the offsets. */ + AMS_ABORT_UNLESS(out_auto_cal_pd_offset != nullptr); + AMS_ABORT_UNLESS(out_auto_cal_pu_offset != nullptr); + + /* Sdmmc2And4Controller only supports 1.8v. */ + AMS_ABORT_UNLESS(bus_power == BusPower_1_8V); + + /* Set the offsets. */ + *out_auto_cal_pd_offset = 5; + *out_auto_cal_pu_offset = 5; + } + public: + explicit Sdmmc2And4Controller(dd::PhysicalAddress registers_phys_addr) : SdmmcController(registers_phys_addr) { + /* ... */ + } + + virtual bool IsSupportedBusPower(BusPower bus_power) const override { + switch (bus_power) { + case BusPower_Off: return true; + case BusPower_1_8V: return true; + case BusPower_3_3V: return false; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + virtual bool IsSupportedBusWidth(BusWidth bus_width) const override { + switch (bus_width) { + case BusWidth_1Bit: return true; + case BusWidth_4Bit: return true; + case BusWidth_8Bit: return true; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + }; + + constexpr inline dd::PhysicalAddress Sdmmc2RegistersPhysicalAddress = UINT64_C(0x700B0200); + + class Sdmmc2Controller : public Sdmmc2And4Controller { + private: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + static constinit inline os::InterruptEventType s_interrupt_event{}; + #endif + protected: + virtual void SetPad() override { + /* Nothing is needed here. */ + } + + virtual ClockResetController::Module GetClockResetModule() const override { + return ClockResetController::Module_Sdmmc2; + } + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual int GetInterruptNumber() const override { + return 47; + } + + virtual os::InterruptEventType *GetInterruptEvent() const override { + return std::addressof(s_interrupt_event); + } + #endif + + virtual void ClearPadParked() override { + if (IsSocMariko()) { + /* Nothing is needed here. */ + } else { + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + /* Clear all MISC2PMC_EMMC2_*_PARK bits. */ + reg::ReadWrite(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC2_PAD_CFGPADCTRL_MISC2PMC_EMMC2_ALL_PARK, 0)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL); + } + } + + virtual void SetDriveStrengthToDefaultValues(BusPower bus_power) override { + /* SDMMC2 only supports 1.8v. */ + AMS_ABORT_UNLESS(bus_power == BusPower_1_8V); + + /* Ensure that we can control registers. */ + SdHostStandardController::EnsureControl(); + + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + if (IsSocMariko()) { + /* Write the drv up/down values to APB_MISC_GP_SDMMC2_PAD_CFGPADCTRL. */ + reg::ReadWrite(apb_address + APB_MISC_GP_SDMMC2_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_SDMMC2_PAD_CFGPADCTRL_CFG2TMC_SDMMC2_PAD_CAL_DRVDN, 0xA), + APB_MISC_REG_BITS_VALUE(GP_SDMMC2_PAD_CFGPADCTRL_CFG2TMC_SDMMC2_PAD_CAL_DRVUP, 0xA)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_SDMMC2_PAD_CFGPADCTRL); + } else { + /* Write the drv up/down values to APB_MISC_GP_EMMC4_PAD_CFGPADCTRL. */ + reg::ReadWrite(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC2_PAD_CFGPADCTRL_CFG2TMC_EMMC2_PAD_DRVDN_COMP, 0x10), + APB_MISC_REG_BITS_VALUE(GP_EMMC2_PAD_CFGPADCTRL_CFG2TMC_EMMC2_PAD_DRVUP_COMP, 0x10)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL); + } + } + public: + Sdmmc2Controller() : Sdmmc2And4Controller(Sdmmc2RegistersPhysicalAddress) { + /* ... */ + } + }; + + constexpr inline dd::PhysicalAddress Sdmmc4RegistersPhysicalAddress = UINT64_C(0x700B0600); + + class Sdmmc4Controller : public Sdmmc2And4Controller { + private: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + static constinit inline os::InterruptEventType s_interrupt_event{}; + #endif + protected: + virtual void SetPad() override { + if (IsSocMariko()) { + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + /* Enable Schmitt Trigger in emmc4 iobrick. */ + reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL, APB_MISC_REG_BITS_ENUM(GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_E_SCH, ENABLE)); + + /* Clear CMD_PULLU, CLK_PULLD, DQS_PULLD. */ + reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_PUPD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_CMD_PUPD_PULLU, 0), + APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_CLK_PUPD_PULLD, 0), + APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DQS_PUPD_PULLD, 0)); + + /* Read again to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_EMMC4_PAD_PUPD_CFGPADCTRL); + } else { + /* On Erista, we can just leave the reset value intact. */ + } + } + + virtual ClockResetController::Module GetClockResetModule() const override { + return ClockResetController::Module_Sdmmc4; + } + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual int GetInterruptNumber() const override { + return 63; + } + + virtual os::InterruptEventType *GetInterruptEvent() const override { + return std::addressof(s_interrupt_event); + } + #endif + + virtual void ClearPadParked() override { + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + /* Clear all MISC2PMC_EMMC4_*_PARK bits. */ + reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_CFGPADCTRL_MISC2PMC_EMMC4_ALL_PARK, 0)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL); + } + + virtual void SetDriveStrengthToDefaultValues(BusPower bus_power) override { + /* SDMMC4 only supports 1.8v. */ + AMS_ABORT_UNLESS(bus_power == BusPower_1_8V); + + /* Ensure that we can control registers. */ + SdHostStandardController::EnsureControl(); + + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + /* Determine the drv up/down values. */ + u8 drvdn, drvup; + if (IsSocMariko()) { + drvdn = 0xA; + drvup = 0xA; + } else { + drvdn = 0x10; + drvup = 0x10; + } + + /* Write the drv up/down values to APB_MISC_GP_EMMC4_PAD_CFGPADCTRL. */ + reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DRVDN_COMP, drvdn), + APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DRVUP_COMP, drvup)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL); + } + public: + Sdmmc4Controller() : Sdmmc2And4Controller(Sdmmc4RegistersPhysicalAddress) { + /* ... */ + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_select_sdmmc_controller.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_select_sdmmc_controller.hpp new file mode 100644 index 00000000..8d3b9ff6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_select_sdmmc_controller.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + + #include "sdmmc_sdmmc_controller.board.nintendo_nx.hpp" + + namespace ams::sdmmc::impl { + + using SdmmcControllerForPortSdCard0 = Sdmmc1Controller; + using SdmmcControllerForPortGcAsic0 = Sdmmc2Controller; + using SdmmcControllerForPortMmc0 = Sdmmc4Controller; + + } + +#else + #error "Unknown board for ams::sdmmc::SdmmcController" +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.cpp new file mode 100644 index 00000000..b3d82558 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.cpp @@ -0,0 +1,96 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#include "sdmmc_timer.hpp" + +namespace ams::sdmmc::impl { + + namespace { + + #if defined(AMS_SDMMC_USE_OS_TIMER) + void SpinWaitMicroSeconds(u32 us) { + const os::Tick timeout_tick = os::GetSystemTick() + os::ConvertToTick(TimeSpan::FromMicroSeconds(us)) + os::Tick(1); + while (true) { + if (os::GetSystemTick() > timeout_tick) { + break; + } + } + } + + ALWAYS_INLINE void DataSynchronizationBarrier() { + #if defined(ATMOSPHERE_ARCH_ARM64) + __asm__ __volatile__("dsb sy" ::: "memory"); + #elif defined(ATMOSPHERE_ARCH_ARM) + __asm__ __volatile__("dsb" ::: "memory"); + #elif defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + /* Nothing needed? */ + __asm__ __volatile__("" ::: "memory"); + #else + #error "Unknown architecture for DataSynchronizationBarrier" + #endif + } + + ALWAYS_INLINE void InstructionSynchronizationBarrier() { + #if defined(ATMOSPHERE_ARCH_ARM64) || defined(ATMOSPHERE_ARCH_ARM) + __asm__ __volatile__("isb" ::: "memory"); + #elif defined(ATMOSPHERE_ARCH_X64) || defined(ATMOSPHERE_ARCH_X86) + /* Nothing needed? */ + __asm__ __volatile__("" ::: "memory"); + #else + #error "Unknown architecture for InstructionSynchronizationBarrier" + #endif + } + #endif + + } + + void WaitMicroSeconds(u32 us) { + #if defined(AMS_SDMMC_USE_OS_TIMER) + /* Ensure that nothing is reordered before we wait. */ + DataSynchronizationBarrier(); + InstructionSynchronizationBarrier(); + + /* If the time is small, spinloop, otherwise pend ourselves. */ + if (us < 100) { + SpinWaitMicroSeconds(us); + } else { + os::SleepThread(TimeSpan::FromMicroSeconds(us)); + } + + /* Ensure that nothing is reordered after we wait. */ + DataSynchronizationBarrier(); + InstructionSynchronizationBarrier(); + #elif defined(AMS_SDMMC_USE_UTIL_TIMER) + util::WaitMicroSeconds(us); + #else + #error "Unknown context for ams::sdmmc::impl::WaitMicroSeconds" + #endif + } + + void WaitClocks(u32 num_clocks, u32 clock_frequency_khz) { + AMS_ABORT_UNLESS(clock_frequency_khz > 0); + WaitMicroSeconds(util::DivideUp(1000 * num_clocks, clock_frequency_khz)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.hpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.hpp new file mode 100644 index 00000000..4c05ca82 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +namespace ams::sdmmc::impl { + + void WaitMicroSeconds(u32 us); + void WaitClocks(u32 num_clocks, u32 clock_frequency_khz); + + #if defined(AMS_SDMMC_USE_OS_TIMER) + class ManualTimer { + private: + os::Tick m_timeout_tick; + bool m_is_timed_out; + public: + explicit ManualTimer(u32 ms) { + m_timeout_tick = os::GetSystemTick() + os::ConvertToTick(TimeSpan::FromMilliSeconds(ms)); + m_is_timed_out = false; + } + + bool Update() { + if (m_is_timed_out) { + return false; + } + + m_is_timed_out = os::GetSystemTick() > m_timeout_tick; + return true; + } + }; + #elif defined(AMS_SDMMC_USE_UTIL_TIMER) + class ManualTimer { + private: + u32 m_timeout_us; + bool m_is_timed_out; + public: + explicit ManualTimer(u32 ms) { + m_timeout_us = util::GetMicroSeconds() + (ms * 1000); + m_is_timed_out = false; + } + + bool Update() { + if (m_is_timed_out) { + return false; + } + + m_is_timed_out = util::GetMicroSeconds() > m_timeout_us; + return true; + } + }; + #else + #error "Unknown context for ams::sdmmc::ManualTimer" + #endif + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/sdmmc_common.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/sdmmc_common.cpp new file mode 100644 index 00000000..3a0fde18 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/sdmmc_common.cpp @@ -0,0 +1,145 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#include "impl/sdmmc_i_host_controller.hpp" +#include "impl/sdmmc_i_device_accessor.hpp" +#include "impl/sdmmc_clock_reset_controller.hpp" +#include "impl/sdmmc_port_mmc0.hpp" +#include "impl/sdmmc_port_sd_card0.hpp" +#include "impl/sdmmc_port_gc_asic0.hpp" + +namespace ams::sdmmc { + + namespace { + + impl::IHostController *GetHostController(Port port) { + /* Get the controller. */ + impl::IHostController *host_controller = nullptr; + switch (port) { + case Port_Mmc0: host_controller = impl::GetHostControllerOfPortMmc0(); break; + case Port_SdCard0: host_controller = impl::GetHostControllerOfPortSdCard0(); break; + case Port_GcAsic0: host_controller = impl::GetHostControllerOfPortGcAsic0(); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Ensure it's valid */ + AMS_ABORT_UNLESS(host_controller != nullptr); + return host_controller; + } + + impl::IDeviceAccessor *GetDeviceAccessor(Port port) { + /* Get the accessor. */ + impl::IDeviceAccessor *device_accessor = nullptr; + switch (port) { + case Port_Mmc0: device_accessor = impl::GetDeviceAccessorOfPortMmc0(); break; + case Port_SdCard0: device_accessor = impl::GetDeviceAccessorOfPortSdCard0(); break; + case Port_GcAsic0: device_accessor = impl::GetDeviceAccessorOfPortGcAsic0(); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Ensure it's valid */ + AMS_ABORT_UNLESS(device_accessor != nullptr); + return device_accessor; + } + + } + + + + void Initialize(Port port) { + return GetDeviceAccessor(port)->Initialize(); + } + + void Finalize(Port port) { + return GetDeviceAccessor(port)->Finalize(); + } + +#if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + void SwitchToPcvClockResetControl() { + return impl::ClockResetController::SwitchToPcvControl(); + } +#endif + +#if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void RegisterDeviceVirtualAddress(Port port, uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + return GetDeviceAccessor(port)->RegisterDeviceVirtualAddress(buffer, buffer_size, buffer_device_virtual_address); + } + + void UnregisterDeviceVirtualAddress(Port port, uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + return GetDeviceAccessor(port)->UnregisterDeviceVirtualAddress(buffer, buffer_size, buffer_device_virtual_address); + } +#endif + + void ChangeCheckTransferInterval(Port port, u32 ms) { + return GetHostController(port)->ChangeCheckTransferInterval(ms); + } + void SetDefaultCheckTransferInterval(Port port) { + return GetHostController(port)->SetDefaultCheckTransferInterval(); + } + + Result Activate(Port port) { + R_RETURN(GetDeviceAccessor(port)->Activate()); + } + + void Deactivate(Port port) { + return GetDeviceAccessor(port)->Deactivate(); + } + + Result Read(void *dst, size_t dst_size, Port port, u32 sector_index, u32 num_sectors) { + R_RETURN(GetDeviceAccessor(port)->ReadWrite(sector_index, num_sectors, dst, dst_size, true)); + } + + Result Write(Port port, u32 sector_index, u32 num_sectors, const void *src, size_t src_size) { + R_RETURN(GetDeviceAccessor(port)->ReadWrite(sector_index, num_sectors, const_cast<void *>(src), src_size, false)); + } + + Result CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width, Port port) { + R_RETURN(GetDeviceAccessor(port)->CheckConnection(out_speed_mode, out_bus_width)); + } + + Result GetDeviceSpeedMode(SpeedMode *out, Port port) { + R_RETURN(GetDeviceAccessor(port)->GetSpeedMode(out)); + } + + Result GetDeviceMemoryCapacity(u32 *out_num_sectors, Port port) { + R_RETURN(GetDeviceAccessor(port)->GetMemoryCapacity(out_num_sectors)); + } + + Result GetDeviceStatus(u32 *out_device_status, Port port) { + R_RETURN(GetDeviceAccessor(port)->GetDeviceStatus(out_device_status)); + } + + Result GetDeviceCid(void *out, size_t out_size, Port port) { + R_RETURN(GetDeviceAccessor(port)->GetCid(out, out_size)); + } + + Result GetDeviceCsd(void *out, size_t out_size, Port port) { + R_RETURN(GetDeviceAccessor(port)->GetCsd(out, out_size)); + } + + void GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size, Port port) { + return GetDeviceAccessor(port)->GetAndClearErrorInfo(out_error_info, out_log_size, out_log_buffer, log_buffer_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/sdmmc_gc_asic.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/sdmmc_gc_asic.cpp new file mode 100644 index 00000000..ac84ef38 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/sdmmc_gc_asic.cpp @@ -0,0 +1,85 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#include "impl/sdmmc_gc_asic_device_accessor.hpp" +#include "impl/sdmmc_port_mmc0.hpp" +#include "impl/sdmmc_port_sd_card0.hpp" +#include "impl/sdmmc_port_gc_asic0.hpp" + +namespace ams::sdmmc { + + namespace { + + impl::GcAsicDeviceAccessor *GetGcAsicDeviceAccessor(Port port) { + /* Get the accessor. */ + impl::GcAsicDeviceAccessor *gc_asic_device_accessor = nullptr; + switch (port) { + case Port_GcAsic0: gc_asic_device_accessor = impl::GetGcAsicDeviceAccessorOfPortGcAsic0(); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Ensure it's valid */ + AMS_ABORT_UNLESS(gc_asic_device_accessor != nullptr); + return gc_asic_device_accessor; + } + + } + + void PutGcAsicToSleep(Port port) { + return GetGcAsicDeviceAccessor(port)->PutGcAsicToSleep(); + } + + Result AwakenGcAsic(Port port) { + R_RETURN(GetGcAsicDeviceAccessor(port)->AwakenGcAsic()); + } + + Result WriteGcAsicOperation(Port port, const void *op_buf, size_t op_buf_size) { + R_RETURN(GetGcAsicDeviceAccessor(port)->WriteGcAsicOperation(op_buf, op_buf_size)); + } + + Result FinishGcAsicOperation(Port port) { + R_RETURN(GetGcAsicDeviceAccessor(port)->FinishGcAsicOperation()); + } + + Result AbortGcAsicOperation(Port port) { + R_RETURN(GetGcAsicDeviceAccessor(port)->AbortGcAsicOperation()); + } + + Result SleepGcAsic(Port port) { + R_RETURN(GetGcAsicDeviceAccessor(port)->SleepGcAsic()); + } + + Result UpdateGcAsicKey(Port port) { + R_RETURN(GetGcAsicDeviceAccessor(port)->UpdateGcAsicKey()); + } + + void SignalGcRemovedEvent(Port port) { + return GetGcAsicDeviceAccessor(port)->SignalGcRemovedEvent(); + } + + void ClearGcRemovedEvent(Port port) { + return GetGcAsicDeviceAccessor(port)->ClearGcRemovedEvent(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/sdmmc_mmc.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/sdmmc_mmc.cpp new file mode 100644 index 00000000..fc657d3b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/sdmmc_mmc.cpp @@ -0,0 +1,81 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#include "impl/sdmmc_mmc_device_accessor.hpp" +#include "impl/sdmmc_port_mmc0.hpp" +#include "impl/sdmmc_port_sd_card0.hpp" +#include "impl/sdmmc_port_gc_asic0.hpp" + +namespace ams::sdmmc { + + namespace { + + impl::MmcDeviceAccessor *GetMmcDeviceAccessor(Port port) { + /* Get the accessor. */ + impl::MmcDeviceAccessor *mmc_device_accessor = nullptr; + switch (port) { + case Port_Mmc0: mmc_device_accessor = impl::GetMmcDeviceAccessorOfPortMmc0(); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Ensure it's valid */ + AMS_ABORT_UNLESS(mmc_device_accessor != nullptr); + return mmc_device_accessor; + } + + } + + void SetMmcWorkBuffer(Port port, void *buffer, size_t buffer_size) { + return GetMmcDeviceAccessor(port)->SetMmcWorkBuffer(buffer, buffer_size); + } + + void PutMmcToSleep(Port port) { + return GetMmcDeviceAccessor(port)->PutMmcToSleep(); + } + + void AwakenMmc(Port port) { + return GetMmcDeviceAccessor(port)->AwakenMmc(); + } + + Result SelectMmcPartition(Port port, MmcPartition mmc_partition) { + R_RETURN(GetMmcDeviceAccessor(port)->SelectMmcPartition(mmc_partition)); + } + + Result EraseMmc(Port port) { + R_RETURN(GetMmcDeviceAccessor(port)->EraseMmc()); + } + + Result GetMmcBootPartitionCapacity(u32 *out_num_sectors, Port port) { + R_RETURN(GetMmcDeviceAccessor(port)->GetMmcBootPartitionCapacity(out_num_sectors)); + } + + Result GetMmcExtendedCsd(void *out_buffer, size_t buffer_size, Port port) { + R_RETURN(GetMmcDeviceAccessor(port)->GetMmcExtendedCsd(out_buffer, buffer_size)); + } + + Result CheckMmcConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width, Port port) { + R_RETURN(GetMmcDeviceAccessor(port)->CheckConnection(out_speed_mode, out_bus_width)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/sdmmc_sd_card.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/sdmmc_sd_card.cpp new file mode 100644 index 00000000..faf83831 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/sdmmc/sdmmc_sd_card.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 <http://www.gnu.org/licenses/>. + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include <stratosphere.hpp> +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include <mesosphere.hpp> +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include <exosphere.hpp> +#else +#include <vapours.hpp> +#endif +#include "impl/sdmmc_sd_card_device_accessor.hpp" +#include "impl/sdmmc_port_mmc0.hpp" +#include "impl/sdmmc_port_sd_card0.hpp" +#include "impl/sdmmc_port_gc_asic0.hpp" + +namespace ams::sdmmc { + + namespace { + + impl::SdCardDeviceAccessor *GetSdCardDeviceAccessor(Port port) { + /* Get the accessor. */ + impl::SdCardDeviceAccessor *sd_card_device_accessor = nullptr; + switch (port) { + case Port_SdCard0: sd_card_device_accessor = impl::GetSdCardDeviceAccessorOfPortSdCard0(); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Ensure it's valid */ + AMS_ABORT_UNLESS(sd_card_device_accessor != nullptr); + return sd_card_device_accessor; + } + + } + + void SetSdCardWorkBuffer(Port port, void *buffer, size_t buffer_size) { + return GetSdCardDeviceAccessor(port)->SetSdCardWorkBuffer(buffer, buffer_size); + } + + void PutSdCardToSleep(Port port) { + return GetSdCardDeviceAccessor(port)->PutSdCardToSleep(); + } + + void AwakenSdCard(Port port) { + return GetSdCardDeviceAccessor(port)->AwakenSdCard(); + } + + Result GetSdCardProtectedAreaCapacity(u32 *out_num_sectors, Port port) { + R_RETURN(GetSdCardDeviceAccessor(port)->GetSdCardProtectedAreaCapacity(out_num_sectors)); + } + + Result GetSdCardScr(void *dst, size_t dst_size, Port port) { + R_RETURN(GetSdCardDeviceAccessor(port)->GetSdCardScr(dst, dst_size)); + } + + Result GetSdCardSwitchFunctionStatus(void *dst, size_t dst_size, Port port, SdCardSwitchFunction switch_function) { + R_RETURN(GetSdCardDeviceAccessor(port)->GetSdCardSwitchFunctionStatus(dst, dst_size, switch_function)); + } + + Result GetSdCardCurrentConsumption(u16 *out_current_consumption, Port port, SpeedMode speed_mode) { + R_RETURN(GetSdCardDeviceAccessor(port)->GetSdCardCurrentConsumption(out_current_consumption, speed_mode)); + } + + Result GetSdCardSdStatus(void *dst, size_t dst_size, Port port) { + R_RETURN(GetSdCardDeviceAccessor(port)->GetSdCardSdStatus(dst, dst_size)); + } + + Result CheckSdCardConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width, Port port) { + R_RETURN(GetSdCardDeviceAccessor(port)->CheckConnection(out_speed_mode, out_bus_width)); + } + + bool IsSdCardInserted(Port port) { + return GetSdCardDeviceAccessor(port)->IsSdCardInserted(); + } + + bool IsSdCardRemoved(Port port) { + return GetSdCardDeviceAccessor(port)->IsSdCardRemoved(); + } + + + void RegisterSdCardDetectionEventCallback(Port port, DeviceDetectionEventCallback callback, void *arg) { + return GetSdCardDeviceAccessor(port)->RegisterSdCardDetectionEventCallback(callback, arg); + } + + void UnregisterSdCardDetectionEventCallback(Port port) { + return GetSdCardDeviceAccessor(port)->UnregisterSdCardDetectionEventCallback(); + } + +} + diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/test/test_intrusive_red_black_tree.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/test/test_intrusive_red_black_tree.cpp new file mode 100644 index 00000000..ced217ad --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/test/test_intrusive_red_black_tree.cpp @@ -0,0 +1,232 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +/* TODO: Define to enable tests? */ +#if 0 + +namespace ams::test { + + template<typename T> + concept IsRedBlackTreeTestNode = std::constructible_from<T, int> && requires (T &t, const T &ct) { + { ct.GetValue() } -> std::same_as<int>; + { t.GetNode() } -> std::same_as< util::IntrusiveRedBlackTreeNode &>; + { ct.GetNode() } -> std::same_as<const util::IntrusiveRedBlackTreeNode &>; + }; + + template<typename T> requires IsRedBlackTreeTestNode<T> + struct TestComparator { + using RedBlackKeyType = int; + + static constexpr int Compare(const T &lhs, const T &rhs) { + if (lhs.GetValue() < rhs.GetValue()) { + return -1; + } else if (lhs.GetValue() > rhs.GetValue()) { + return 1; + } else { + return 0; + } + } + + static constexpr int Compare(const int &lhs, const T &rhs) { + if (lhs < rhs.GetValue()) { + return -1; + } else if (lhs > rhs.GetValue()) { + return 1; + } else { + return 0; + } + } + }; + + class TestBaseNode : public util::IntrusiveRedBlackTreeBaseNode<TestBaseNode> { + private: + const int m_value; + public: + constexpr TestBaseNode(int value) : m_value(value) { /* ... */ } + + constexpr int GetValue() const { return m_value; } + + constexpr util::IntrusiveRedBlackTreeNode &GetNode() { return static_cast< util::IntrusiveRedBlackTreeNode &>(*this); } + constexpr const util::IntrusiveRedBlackTreeNode &GetNode() const { return static_cast<const util::IntrusiveRedBlackTreeNode &>(*this); } + }; + static_assert(IsRedBlackTreeTestNode<TestBaseNode>); + + class TestTreeTypes; + + class TestMemberNode { + private: + friend class TestTreeTypes; + private: + const int m_value; + util::IntrusiveRedBlackTreeNode m_node; + public: + constexpr TestMemberNode(int value) : m_value(value), m_node() { /* ... */ } + + constexpr int GetValue() const { return m_value; } + + constexpr util::IntrusiveRedBlackTreeNode &GetNode() { return m_node; } + constexpr const util::IntrusiveRedBlackTreeNode &GetNode() const { return m_node; } + }; + static_assert(IsRedBlackTreeTestNode<TestMemberNode>); + + class TestTreeTypes { + public: + using BaseTree = util::IntrusiveRedBlackTreeBaseTraits<TestBaseNode>::TreeType<TestComparator<TestBaseNode>>; + using MemberTree = util::IntrusiveRedBlackTreeMemberTraits<&TestMemberNode::m_node>::TreeType<TestComparator<TestMemberNode>>; + }; + + using TestBaseTree = TestTreeTypes::BaseTree; + using TestMemberTree = TestTreeTypes::MemberTree; + + template<typename Tree, typename Node> + consteval bool TestUsage() { + constexpr int Values[] = { -3, 0, 5, 7, 11111111, 924, -100, 68, 70, 69, }; + + /* Get sorted array. */ + std::array<int, util::size(Values)> sorted_values{}; + std::copy(std::begin(Values), std::end(Values), std::begin(sorted_values)); + std::sort(std::begin(sorted_values), std::end(sorted_values)); + + /* Create the tree. */ + Tree tree{}; + AMS_ASSUME(tree.begin() == tree.end()); + + /* Create a node for each value. */ + /* TODO: GCC bug in constant evaluation fails if we use constexpr new/dynamically allocated nodes. */ + /* Check if this works in gcc 11. */ + std::array<Node, util::size(Values)> nodes = [&]<size_t... Ix>(std::index_sequence<Ix...>) { + return std::array<Node, util::size(Values)> { Node(Values[Ix])... }; + }(std::make_index_sequence<util::size(Values)>()); + + /* Insert each node into the tree. */ + for (size_t i = 0; i < util::size(Values); ++i) { + tree.insert(nodes[i]); + if (std::distance(tree.begin(), tree.end()) != static_cast<int>(i + 1)) { + return false; + } + } + + /* Verify that the nodes are in sorted order. */ + { + size_t i = 0; + for (const auto &node : tree) { + if (node.GetValue() != sorted_values[i++]) { + return false; + } + } + } + + /* Verify correctness with begin() */ + { + size_t i = 0; + for (auto it = tree.begin(); it != tree.end(); ++it) { + if (it->GetValue() != sorted_values[i++]) { + return false; + } + } + } + + /* Verify correctness with cbegin() */ + { + size_t i = 0; + for (auto it = tree.cbegin(); it != tree.cend(); ++it) { + if (it->GetValue() != sorted_values[i++]) { + return false; + } + } + } + + /* Verify min/max. */ + if (tree.front().GetValue() != sorted_values[0]) { + return false; + } + if (tree.back().GetValue() != sorted_values[sorted_values.size() - 1]) { + return false; + } + + /* Remove a value. */ + tree.erase(tree.iterator_to(nodes[3])); + + /* Verify nodes are in sorted order. */ + { + size_t i = 0; + for (const auto &node : tree) { + if (node.GetValue() == nodes[3].GetValue()) { + return false; + } + + if (node.GetValue() != sorted_values[i++]) { + if (node.GetValue() != sorted_values[i++]) { + return false; + } + } + } + } + + /* Add the node back. */ + tree.insert(nodes[3]); + + /* Verify nodes are in sorted order. */ + { + size_t i = 0; + for (const auto &node : tree) { + if (node.GetValue() != sorted_values[i++]) { + return false; + } + } + } + + /* Verify that find works. */ + for (size_t i = 0; i < util::size(Values); ++i) { + if (tree.find(Node(Values[i])) != tree.iterator_to(nodes[i])) { + return false; + } + if (tree.nfind(Node(sorted_values[i]))->GetValue() != sorted_values[i]) { + return false; + } + if (tree.find_key(Values[i]) != tree.iterator_to(nodes[i])) { + return false; + } + if (tree.nfind_key(sorted_values[i])->GetValue() != sorted_values[i]) { + return false; + } + } + + if (tree.find(Node(std::numeric_limits<int>::min())) != tree.end()) { + return false; + } + + /* Verify that nfind works. */ + for (size_t i = 0; i < util::size(Values) - 1; ++i) { + if (tree.nfind(Node(sorted_values[i] + 1))->GetValue() != sorted_values[i + 1]) { + return false; + } + if (tree.nfind_key(sorted_values[i] + 1)->GetValue() != sorted_values[i + 1]) { + return false; + } + } + + return true; + } + + static_assert(TestUsage<TestBaseTree, TestBaseNode>()); + static_assert(TestUsage<TestMemberTree, TestMemberNode>()); + + +} + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/util/util_format_string.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/util/util_format_string.cpp new file mode 100644 index 00000000..9ef0bb7a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/util/util_format_string.cpp @@ -0,0 +1,436 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::util { + + AMS_PRAGMA_BEGIN_OPTIMIZE("-Os") + + namespace { + + /* Useful definitions for our VSNPrintf implementation. */ + enum FormatSpecifierFlag : u32 { + FormatSpecifierFlag_None = 0, + FormatSpecifierFlag_EmptySign = (1 << 0), + FormatSpecifierFlag_ForceSign = (1 << 1), + FormatSpecifierFlag_Hash = (1 << 2), + FormatSpecifierFlag_LeftJustify = (1 << 3), + FormatSpecifierFlag_ZeroPad = (1 << 4), + FormatSpecifierFlag_Char = (1 << 5), + FormatSpecifierFlag_Short = (1 << 6), + FormatSpecifierFlag_Long = (1 << 7), + FormatSpecifierFlag_LongLong = (1 << 8), + FormatSpecifierFlag_Uppercase = (1 << 9), + FormatSpecifierFlag_HasPrecision = (1 << 10), + }; + + using FormatSpecifierFlagStorage = std::underlying_type<FormatSpecifierFlag>::type; + + constexpr ALWAYS_INLINE bool IsDigit(char c) { + return '0' <= c && c <= '9'; + } + + constexpr ALWAYS_INLINE u32 ParseU32(const char *&str) { + u32 value = 0; + do { + value = (value * 10) + static_cast<u32>(*(str++) - '0'); + } while (IsDigit(*str)); + return value; + } + + constexpr ALWAYS_INLINE size_t Strnlen(const char *str, size_t max) { + const char *cur = str; + while (*cur && max--) { + cur++; + } + return static_cast<size_t>(cur - str); + } + + int TVSNPrintfImpl(char * const dst, const size_t dst_size, const char *format, ::std::va_list vl) { + size_t dst_index = 0; + + auto WriteCharacter = [dst, dst_size, &dst_index](char c) ALWAYS_INLINE_LAMBDA { + if (const size_t i = (dst_index++); i < dst_size) { + dst[i] = c; + } + }; + + /* Loop over every character in the string, looking for format specifiers. */ + while (*format) { + if (const char c = *(format++); c != '%') { + WriteCharacter(c); + continue; + } + + /* We have to parse a format specifier. */ + /* Start by parsing flags. */ + FormatSpecifierFlagStorage flags = FormatSpecifierFlag_None; + auto SetFlag = [&flags](FormatSpecifierFlag f) ALWAYS_INLINE_LAMBDA { flags |= f; }; + auto ClearFlag = [&flags](FormatSpecifierFlag f) ALWAYS_INLINE_LAMBDA { flags &= ~f; }; + auto HasFlag = [&flags](FormatSpecifierFlag f) ALWAYS_INLINE_LAMBDA { return (flags & f) != 0; }; + { + bool parsed_flags = false; + while (!parsed_flags) { + switch (*format) { + case ' ': SetFlag(FormatSpecifierFlag_EmptySign); format++; break; + case '+': SetFlag(FormatSpecifierFlag_ForceSign); format++; break; + case '#': SetFlag(FormatSpecifierFlag_Hash); format++; break; + case '-': SetFlag(FormatSpecifierFlag_LeftJustify); format++; break; + case '0': SetFlag(FormatSpecifierFlag_ZeroPad); format++; break; + default: + parsed_flags = true; + break; + } + } + } + + /* Next, parse width. */ + u32 width = 0; + if (IsDigit(*format)) { + /* Integer width. */ + width = ParseU32(format); + } else if (*format == '*') { + /* Dynamic width. */ + const int _width = va_arg(vl, int); + if (_width >= 0) { + width = static_cast<u32>(_width); + } else { + SetFlag(FormatSpecifierFlag_LeftJustify); + width = static_cast<u32>(-_width); + } + format++; + } + + /* Next, parse precision if present. */ + u32 precision = 0; + if (*format == '.') { + SetFlag(FormatSpecifierFlag_HasPrecision); + format++; + + if (IsDigit(*format)) { + /* Integer precision. */ + precision = ParseU32(format); + } else if (*format == '*') { + /* Dynamic precision. */ + const int _precision = va_arg(vl, int); + if (_precision > 0) { + precision = static_cast<u32>(_precision); + } + format++; + } + } + + /* Parse length. */ + constexpr bool SizeIsLong = sizeof(size_t) == sizeof(long); + constexpr bool PointerIsLong = sizeof(uintptr_t) == sizeof(long); + constexpr bool IntMaxIsLong = sizeof(intmax_t) == sizeof(long); + constexpr bool PtrDiffIsLong = sizeof(ptrdiff_t) == sizeof(long); + switch (*format) { + case 'z': + SetFlag(SizeIsLong ? FormatSpecifierFlag_Long : FormatSpecifierFlag_LongLong); + format++; + break; + case 'j': + SetFlag(IntMaxIsLong ? FormatSpecifierFlag_Long : FormatSpecifierFlag_LongLong); + format++; + break; + case 't': + SetFlag(PtrDiffIsLong ? FormatSpecifierFlag_Long : FormatSpecifierFlag_LongLong); + format++; + break; + case 'h': + SetFlag(FormatSpecifierFlag_Short); + format++; + if (*format == 'h') { + SetFlag(FormatSpecifierFlag_Char); + format++; + } + break; + case 'l': + SetFlag(FormatSpecifierFlag_Long); + format++; + if (*format == 'l') { + SetFlag(FormatSpecifierFlag_LongLong); + format++; + } + break; + default: + break; + } + + const char specifier = *(format++); + switch (specifier) { + case 'p': + if constexpr (PointerIsLong) { + SetFlag(FormatSpecifierFlag_Long); + } else { + SetFlag(FormatSpecifierFlag_LongLong); + } + SetFlag(FormatSpecifierFlag_Hash); + [[fallthrough]]; + case 'd': + case 'i': + case 'u': + case 'b': + case 'o': + case 'x': + case 'X': + { + /* Determine the base to print with. */ + u32 base; + switch (specifier) { + case 'b': + base = 2; + break; + case 'o': + base = 8; + break; + case 'X': + SetFlag(FormatSpecifierFlag_Uppercase); + [[fallthrough]]; + case 'p': + case 'x': + base = 16; + break; + default: + base = 10; + ClearFlag(FormatSpecifierFlag_Hash); + break; + } + + /* Precision implies no zero-padding. */ + if (HasFlag(FormatSpecifierFlag_HasPrecision)) { + ClearFlag(FormatSpecifierFlag_ZeroPad); + } + + /* Unsigned types don't get signs. */ + const bool is_unsigned = base != 10 || specifier == 'u'; + if (is_unsigned) { + ClearFlag(FormatSpecifierFlag_EmptySign); + ClearFlag(FormatSpecifierFlag_ForceSign); + } + + auto PrintInteger = [&](bool negative, uintmax_t value) { + constexpr size_t BufferSize = 64; /* Binary digits for 64-bit numbers may use 64 digits. */ + char buf[BufferSize]; + size_t len = 0; + + /* No hash flag for zero. */ + if (value == 0) { + ClearFlag(FormatSpecifierFlag_Hash); + } + + if (!HasFlag(FormatSpecifierFlag_HasPrecision) || value != 0) { + do { + const char digit = static_cast<char>(value % base); + buf[len++] = (digit < 10) ? ('0' + digit) : ((HasFlag(FormatSpecifierFlag_Uppercase) ? 'A' : 'a') + digit - 10); + value /= base; + } while (value); + } + + /* Determine our prefix length. */ + size_t prefix_len = 0; + const bool has_sign = negative || HasFlag(FormatSpecifierFlag_ForceSign) || HasFlag(FormatSpecifierFlag_EmptySign); + if (has_sign) { + prefix_len++; + } + if (HasFlag(FormatSpecifierFlag_Hash)) { + prefix_len += (base != 8) ? 2 : 1; + } + + /* Determine zero-padding count. */ + size_t num_zeroes = (len < precision) ? precision - len : 0; + if (!HasFlag(FormatSpecifierFlag_LeftJustify) && HasFlag(FormatSpecifierFlag_ZeroPad)) { + num_zeroes = (len + prefix_len < width) ? width - len - prefix_len : 0; + } + + /* Print out left padding. */ + if (!HasFlag(FormatSpecifierFlag_LeftJustify)) { + for (size_t i = len + prefix_len + num_zeroes; i < static_cast<size_t>(width); i++) { + WriteCharacter(' '); + } + } + + /* Print out sign. */ + if (negative) { + WriteCharacter('-'); + } else if (HasFlag(FormatSpecifierFlag_ForceSign)) { + WriteCharacter('+'); + } else if (HasFlag(FormatSpecifierFlag_EmptySign)) { + WriteCharacter(' '); + } + + /* Print out base prefix. */ + if (HasFlag(FormatSpecifierFlag_Hash)) { + WriteCharacter('0'); + if (base == 2) { + WriteCharacter('b'); + } else if (base == 16) { + WriteCharacter('x'); + } + } + + /* Print out zeroes. */ + for (size_t i = 0; i < num_zeroes; i++) { + WriteCharacter('0'); + } + + /* Print out digits. */ + for (size_t i = 0; i < len; i++) { + WriteCharacter(buf[len - 1 - i]); + } + + /* Print out right padding. */ + if (HasFlag(FormatSpecifierFlag_LeftJustify)) { + for (size_t i = len + prefix_len + num_zeroes; i < static_cast<size_t>(width); i++) { + WriteCharacter(' '); + } + } + }; + + /* Output the integer. */ + if (is_unsigned) { + uintmax_t n = 0; + if (HasFlag(FormatSpecifierFlag_LongLong)) { + n = static_cast<unsigned long long>(va_arg(vl, unsigned long long)); + } else if (HasFlag(FormatSpecifierFlag_Long)) { + n = static_cast<unsigned long>(va_arg(vl, unsigned long)); + } else if (HasFlag(FormatSpecifierFlag_Char)) { + n = static_cast<unsigned char>(va_arg(vl, unsigned int)); + } else if (HasFlag(FormatSpecifierFlag_Short)) { + n = static_cast<unsigned short>(va_arg(vl, unsigned int)); + } else { + n = static_cast<unsigned int>(va_arg(vl, unsigned int)); + } + if (specifier == 'p' && n == 0) { + WriteCharacter('('); + WriteCharacter('n'); + WriteCharacter('i'); + WriteCharacter('l'); + WriteCharacter(')'); + } else { + PrintInteger(false, n); + } + } else { + intmax_t n = 0; + if (HasFlag(FormatSpecifierFlag_LongLong)) { + n = static_cast<signed long long>(va_arg(vl, signed long long)); + } else if (HasFlag(FormatSpecifierFlag_Long)) { + n = static_cast<signed long>(va_arg(vl, signed long)); + } else if (HasFlag(FormatSpecifierFlag_Char)) { + n = static_cast<signed char>(va_arg(vl, signed int)); + } else if (HasFlag(FormatSpecifierFlag_Short)) { + n = static_cast<signed short>(va_arg(vl, signed int)); + } else { + n = static_cast<signed int>(va_arg(vl, signed int)); + } + const bool negative = n < 0; + const uintmax_t u = (negative) ? static_cast<uintmax_t>(-n) : static_cast<uintmax_t>(n); + PrintInteger(negative, u); + } + } + break; + case 'c': + { + size_t len = 1; + if (!HasFlag(FormatSpecifierFlag_LeftJustify)) { + while (len++ < width) { + WriteCharacter(' '); + } + } + WriteCharacter(static_cast<char>(va_arg(vl, int))); + if (HasFlag(FormatSpecifierFlag_LeftJustify)) { + while (len++ < width) { + WriteCharacter(' '); + } + } + } + break; + case 's': + { + const char *str = va_arg(vl, char *); + if (str == nullptr) { + str = "(null)"; + } + + size_t len = Strnlen(str, precision > 0 ? precision : std::numeric_limits<size_t>::max()); + if (HasFlag(FormatSpecifierFlag_HasPrecision)) { + len = (len < precision) ? len : precision; + } + if (!HasFlag(FormatSpecifierFlag_LeftJustify)) { + while (len++ < width) { + WriteCharacter(' '); + } + } + while (*str && (!HasFlag(FormatSpecifierFlag_HasPrecision) || (precision--) != 0)) { + WriteCharacter(*(str++)); + } + if (HasFlag(FormatSpecifierFlag_LeftJustify)) { + while (len++ < width) { + WriteCharacter(' '); + } + } + } + break; + case '%': + default: + WriteCharacter(specifier); + break; + } + } + + /* Ensure null termination. */ + WriteCharacter('\0'); + if (dst_size > 0) { + dst[dst_size - 1] = '\0'; + } + + /* Return number of characters that would have been printed sans the null terminator. */ + return static_cast<int>(dst_index) - 1; + } + + } + + AMS_PRAGMA_END_OPTIMIZE() + + int TVSNPrintf(char *dst, size_t dst_size, const char *fmt, std::va_list vl) { + return TVSNPrintfImpl(dst, dst_size, fmt, vl); + } + + int TSNPrintf(char *dst, size_t dst_size, const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + const int len = TVSNPrintf(dst, dst_size, fmt, vl); + va_end(vl); + + return len; + } + + int VSNPrintf(char *dst, size_t dst_size, const char *fmt, std::va_list vl) { + /* TODO: floating point support? */ + return TVSNPrintfImpl(dst, dst_size, fmt, vl); + } + + int SNPrintf(char *dst, size_t dst_size, const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + const int len = VSNPrintf(dst, dst_size, fmt, vl); + va_end(vl); + + return len; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/util/util_utf8_string_util.cpp b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/util/util_utf8_string_util.cpp new file mode 100644 index 00000000..d6430850 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/libraries/libvapours/source/util/util_utf8_string_util.cpp @@ -0,0 +1,121 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <vapours.hpp> + +namespace ams::util { + + namespace { + + constexpr inline const u8 CodePointByteLengthTable[0x100] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + + constexpr ALWAYS_INLINE size_t GetCodePointByteLength(u8 c) { + return CodePointByteLengthTable[c]; + } + + constexpr ALWAYS_INLINE bool IsValidTail(u8 c) { + return (c & 0xC0) == 0x80; + } + + constexpr inline bool VerifyCode(const u8 *code, size_t size) { + switch (size) { + case 1: + break; + case 2: + if (!IsValidTail(code[1])) { + return false; + } + break; + case 3: + if (code[0] == 0xE0 && (code[1] & 0x20) == 0x00) { + return false; + } + if (code[0] == 0xED && (code[1] & 0x20) != 0x00) { + return false; + } + if (!IsValidTail(code[1]) || !IsValidTail(code[2])) { + return false; + } + break; + case 4: + if (code[0] == 0xF0 && (code[1] & 0x30) == 0x00) { + return false; + } + if (code[0] == 0xF4 && (code[1] & 0x30) != 0x00) { + return false; + } + if (!IsValidTail(code[1]) || !IsValidTail(code[2]) || !IsValidTail(code[3])) { + return false; + } + break; + default: + return false; + } + + return true; + } + + } + + bool VerifyUtf8String(const char *str, size_t size) { + return GetCodePointCountOfUtf8String(str, size) != -1; + } + + int GetCodePointCountOfUtf8String(const char *str, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(str != nullptr); + AMS_ASSERT(size > 0); + + /* Parse codepoints. */ + int count = 0; + + while (size > 0) { + /* Get and check the current codepoint. */ + const u8 *code = reinterpret_cast<const u8 *>(str); + const size_t code_size = GetCodePointByteLength(code[0]); + + if (code_size > size || !VerifyCode(code, code_size)) { + return -1; + } + + /* Advance. */ + str += code_size; + size -= code_size; + + /* Increment count. */ + ++count; + } + + return count; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/Makefile b/Source/Atmosphere-MTC-Unlock/mesosphere/Makefile new file mode 100644 index 00000000..05c8b167 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/Makefile @@ -0,0 +1,42 @@ +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)/mesosphere.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)/mesosphere.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,)) +$(eval $(call ATMOSPHERE_ADD_TARGETS, qemu_virt_a57, qemu-virt, 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/mesosphere/build_mesosphere.py b/Source/Atmosphere-MTC-Unlock/mesosphere/build_mesosphere.py new file mode 100644 index 00000000..db959976 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/build_mesosphere.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +import sys, os +from struct import pack as pk, unpack as up + +def atmosphere_target_firmware(major, minor, micro, rev = 0): + return (major << 24) | (minor << 16) | (micro << 8) | rev + +def align_up(val, algn): + val += algn - 1 + return val - (val % algn) + +def main(argc, argv): + if argc < 4: + print('Usage: %s kernel_ldr.bin kernel.bin output.bin [initial_process.kip ...]' % argv[0]) + return 1 + with open(argv[1], 'rb') as f: + kernel_ldr = f.read() + with open(argv[2], 'rb') as f: + kernel = f.read() + kernel_metaptr_offset = 4 + assert (kernel_metaptr_offset <= len(kernel) - 0x40) + assert (kernel[kernel_metaptr_offset:kernel_metaptr_offset + 4] == b'MSS1') + kernel_metadata_offset = up('<I', kernel[kernel_metaptr_offset+4:kernel_metaptr_offset+8])[0] + assert (kernel_metadata_offset <= len(kernel) - 0x40) + + bss_start, bss_end, kernel_end = up('<III', kernel[kernel_metadata_offset + 0x2C:kernel_metadata_offset + 0x38]) + bss_start += kernel_metadata_offset + 0x14 + bss_end += kernel_metadata_offset + 0x14 + kernel_end += kernel_metadata_offset + 0x14 + assert (bss_end >= bss_start) + assert (bss_end == kernel_end) + + assert (len(kernel) <= kernel_end) + if len(kernel) < kernel_end: + kernel += b'\x00' * (kernel_end - len(kernel)) + assert (kernel_end == len(kernel)) + + embedded_kips = b'' + num_kips = 0 + for kip_file in argv[4:]: + try: + with open(kip_file, 'rb') as f: + data = f.read() + if data.startswith(b'KIP1'): + embedded_kips += data + num_kips += 1 + except: + pass + if num_kips > 0: + embedded_ini_header = pk('<4sIII', b'INI1', len(embedded_kips) + 0x10, num_kips, 0) + else: + embedded_ini_header = b'' + embedded_ini_offset = align_up(kernel_end, 0x1000) + embedded_ini_end = embedded_ini_offset + len(embedded_ini_header) + len(embedded_kips) + + kernel_ldr_offset = align_up(embedded_ini_end, 0x1000) + (0x1000 if len(embedded_ini_header) == 0 else 0) + kernel_ldr_end = kernel_ldr_offset + len(kernel_ldr) + mesosphere_end = align_up(kernel_ldr_end, 0x1000) + + with open(argv[3], 'wb') as f: + f.write(kernel[:kernel_metadata_offset]) + f.write(pk('<QQI', embedded_ini_offset - (kernel_metadata_offset), kernel_ldr_offset - (kernel_metadata_offset + 8), atmosphere_target_firmware(17, 0, 0))) + f.write(kernel[kernel_metadata_offset + 0x14:]) + f.seek(embedded_ini_offset) + f.write(embedded_ini_header) + f.write(embedded_kips) + f.seek(embedded_ini_end) + f.seek(kernel_ldr_offset) + f.write(kernel_ldr) + f.seek(mesosphere_end) + f.write(b'\x00'*0x1000) + return 0 + + +if __name__ == '__main__': + sys.exit(main(len(sys.argv), sys.argv)) diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/Makefile b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/Makefile new file mode 100644 index 00000000..f52b77a9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/Makefile @@ -0,0 +1,42 @@ +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)/kernel.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)/kernel.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,)) +$(eval $(call ATMOSPHERE_ADD_TARGETS, qemu_virt_a57, qemu-virt, 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/mesosphere/kernel/kernel.ld b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/kernel.ld new file mode 100644 index 00000000..bcd65857 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/kernel.ld @@ -0,0 +1,218 @@ +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__; + __bin_start__ = .; + __code_start = . ; + + .start : + { + KEEP (*(.start .start.*)) + . = ALIGN(8); + } :code + + /* .sleep. */ + .sleep : + { + KEEP( *(.sleep .sleep.*) ) + . = 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 + + /* .vectors. */ + . = ALIGN(2K); + __vectors_start__ = . ; + .vectors : + { + KEEP( *(.vectors) ) + . = ALIGN(8); + } :code + + /* =========== RODATA section =========== */ + . = ALIGN(0x1000); + __rodata_start = . ; + + .rodata.text.crt0 : + { + KEEP (*(.crt0 .crt0.*)) + . = ALIGN(8); + } :code + + .rodata : + { + *(.rodata .rodata.* .gnu.linkonce.r.*) + . = ALIGN(8); + } :rodata + + .data.rel.ro : + { + *(.data.rel.ro .data.rel.ro.*) + . = ALIGN(8); + } :rodata + + .preinit_array ALIGN(8) : + { + PROVIDE (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE (__preinit_array_end = .); + } :rodata + + .init_array ALIGN(8) : + { + PROVIDE (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + PROVIDE (__init_array_end = .); + } :rodata + + __got_start__ = .; + + .got : { *(.got) *(.igot) } :rodata + .got.plt : { *(.got.plt) *(.igot.plt) } :rodata + + __got_end__ = .; + + .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 + .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 + + __rodata_end = .; + + /* =========== 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 + + .ctors ALIGN(8) : + { + KEEP (*crtbegin.o(.ctors)) /* MUST be first -- GCC requires it */ + KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + } :data + + .dtors ALIGN(8) : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + } :data + + .data ALIGN(8) : + { + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + . = ALIGN(8); + } :data + + __bss_start__ = .; + + .rela.dyn : { *(.rela.*) } :data + .relr.dyn : { *(.relr.*) } :data + + .bss ADDR(.rela.dyn) (NOLOAD) : { + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + . = ALIGN(0x1000); + } + + __bss_end__ = .; + + __bin_end__ = .; + __end__ = ABSOLUTE(.); + + /* ================== + ==== Metadata ==== + ================== */ + + /* Discard sections that difficult post-processing */ + /DISCARD/ : { *(.group .comment .note .interp .fini_array .fini_array.* .dynsym .dynstr) } + + /* 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/mesosphere/kernel/kernel.mk b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/kernel.mk new file mode 100644 index 00000000..9d9896fa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/kernel.mk @@ -0,0 +1,112 @@ +#--------------------------------------------------------------------------------- +# pull in common atmosphere configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(CURRENT_DIRECTORY)/../../libraries/config/templates/mesosphere.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) + +#--------------------------------------------------------------------------------- +# 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)/libmesosphere/$(ATMOSPHERE_LIBRARY_DIR)/libmesosphere.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)/libmesosphere/$(ATMOSPHERE_LIBRARY_DIR)/libmesosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBMESOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libmesosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libmesosphere/libmesosphere.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 $< $@ + @echo built ... $(notdir $@) + +$(OUTPUT).elf : $(OFILES) $(ATMOSPHERE_LIBRARIES_DIR)/libmesosphere/$(ATMOSPHERE_LIBRARY_DIR)/libmesosphere.a + +%.elf: + @echo linking $(notdir $@) + $(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + @$(NM) -CSn $@ > $(notdir $*.lst) + +$(OFILES_SRC) : $(HFILES_BIN) + +kern_libc_generic.o: CFLAGS += -fno-builtin +kern_kernel_instantiations.o: CXXFLAGS += -flto + +#--------------------------------------------------------------------------------- +# 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 +#--------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/kernel.specs b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/kernel.specs new file mode 100644 index 00000000..a6b848cd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/kernel.specs @@ -0,0 +1,7 @@ +%rename link old_link + +*link: +%(old_link) -T %:getenv(ATMOSPHERE_TOPDIR /kernel.ld) -pie --gc-sections -z text -z nodynamic-undefined-weak -z pack-relative-relocs -nostdlib + +*startfile: +crti%O%s crtbegin%O%s diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/exception_vectors.s b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/exception_vectors.s new file mode 100644 index 00000000..88a93d51 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/exception_vectors.s @@ -0,0 +1,164 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* Some macros taken from https://github.com/ARM-software/arm-trusted-firmware/blob/master/include/common/aarch64/asm_macros.S */ +/* + * Copyright (c) 2013-2017, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/* + * Declare the exception vector table, enforcing it is aligned on a + * 2KB boundary, as required by the ARMv8 architecture. + * Use zero bytes as the fill value to be stored in the padding bytes + * so that it inserts illegal AArch64 instructions. This increases + * security, robustness and potentially facilitates debugging. + */ +.macro vector_base label, section_name=.vectors +.section \section_name, "ax" +.align 11, 0 +\label: +.endm + +/* + * Create an entry in the exception vector table, enforcing it is + * aligned on a 128-byte boundary, as required by the ARMv8 architecture. + * Use zero bytes as the fill value to be stored in the padding bytes + * so that it inserts illegal AArch64 instructions. This increases + * security, robustness and potentially facilitates debugging. + */ +.macro vector_entry label, section_name=.vectors +.cfi_sections .debug_frame +.section \section_name, "ax" +.align 7, 0 +.type \label, %function +.func \label +.cfi_startproc +\label: +.endm + +/* + * This macro verifies that the given vector doesnt exceed the + * architectural limit of 32 instructions. This is meant to be placed + * immediately after the last instruction in the vector. It takes the + * vector entry as the parameter + */ +.macro check_vector_size since + .endfunc + .cfi_endproc + .if (. - \since) > (32 * 4) + .error "Vector exceeds 32 instructions" + .endif +.endm + +/* Actual Vectors for Kernel. */ +.global _ZN3ams4kern16ExceptionVectorsEv +vector_base _ZN3ams4kern16ExceptionVectorsEv + +/* Current EL, SP0 */ +vector_entry synch_sp0 + /* Just infinite loop. */ + clrex + nop + b synch_sp0 + check_vector_size synch_sp0 + +vector_entry irq_sp0 + clrex + nop + b irq_sp0 + check_vector_size irq_sp0 + +vector_entry fiq_sp0 + clrex + nop + b fiq_sp0 + check_vector_size fiq_sp0 + +vector_entry serror_sp0 + clrex + nop + b serror_sp0 + check_vector_size serror_sp0 + +/* Current EL, SPx */ +vector_entry synch_spx + clrex + b _ZN3ams4kern4arch5arm6430EL1SynchronousExceptionHandlerEv + check_vector_size synch_spx + +vector_entry irq_spx + b _ZN3ams4kern4arch5arm6422EL1IrqExceptionHandlerEv + check_vector_size irq_spx + +vector_entry fiq_spx + clrex + nop + b fiq_spx + check_vector_size fiq_spx + +vector_entry serror_spx + clrex + nop + b _ZN3ams4kern4arch5arm6421EL1SystemErrorHandlerEv + check_vector_size serror_spx + +/* Lower EL, A64 */ +vector_entry synch_a64 + clrex + b _ZN3ams4kern4arch5arm6430EL0SynchronousExceptionHandlerEv + check_vector_size synch_a64 + +vector_entry irq_a64 + clrex + b _ZN3ams4kern4arch5arm6425EL0A64IrqExceptionHandlerEv + check_vector_size irq_a64 + +vector_entry fiq_a64 + clrex + nop + b fiq_a64 + check_vector_size fiq_a64 + +vector_entry serror_a64 + clrex + nop + b _ZN3ams4kern4arch5arm6421EL0SystemErrorHandlerEv + check_vector_size serror_a64 + +/* Lower EL, A32 */ +vector_entry synch_a32 + clrex + b _ZN3ams4kern4arch5arm6430EL0SynchronousExceptionHandlerEv + check_vector_size synch_a32 + +vector_entry irq_a32 + clrex + b _ZN3ams4kern4arch5arm6425EL0A32IrqExceptionHandlerEv + check_vector_size irq_a32 + +vector_entry fiq_a32 + clrex + nop + b fiq_a32 + check_vector_size fiq_a32 + +vector_entry serror_a32 + clrex + nop + b _ZN3ams4kern4arch5arm6421EL0SystemErrorHandlerEv + check_vector_size serror_a32 diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/init/kern_init_core.cpp b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/init/kern_init_core.cpp new file mode 100644 index 00000000..10a000c6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/init/kern_init_core.cpp @@ -0,0 +1,772 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +extern "C" void __rodata_start(); +extern "C" void __rodata_end(); + +extern "C" void __bin_start__(); +extern "C" void __bin_end__(); + +namespace ams::kern { + + void ExceptionVectors(); + +} + +namespace ams::kern::init { + + /* Prototypes for functions declared in ASM that we need to reference. */ + void StartOtherCore(const ams::kern::init::KInitArguments *init_args); + + void IdentityMappedFunctionAreaBegin(); + void IdentityMappedFunctionAreaEnd(); + + size_t GetMiscUnknownDebugRegionSize(); + size_t GetSecureUnknownRegionSize(); + + void InitializeDebugRegisters(); + void InitializeExceptionVectors(); + + namespace { + + /* Global Allocator. */ + constinit KInitialPageAllocator g_initial_page_allocator; + + constinit KInitArguments g_init_arguments[cpu::NumCores]; + + /* Globals for passing data between InitializeCorePhase1 and InitializeCorePhase2. */ + constinit InitialProcessBinaryLayoutWithSize g_phase2_initial_process_binary_meta{}; + constinit KPhysicalAddress g_phase2_resource_end_phys_addr = Null<KPhysicalAddress>; + constinit u64 g_phase2_linear_region_phys_to_virt_diff = 0; + + /* Page table attributes. */ + constexpr PageTableEntry KernelTextAttribute(PageTableEntry::Permission_KernelRX, PageTableEntry::PageAttribute_NormalMemory, PageTableEntry::Shareable_InnerShareable, PageTableEntry::MappingFlag_Mapped); + constexpr PageTableEntry KernelRoDataAttribute(PageTableEntry::Permission_KernelR, PageTableEntry::PageAttribute_NormalMemory, PageTableEntry::Shareable_InnerShareable, PageTableEntry::MappingFlag_Mapped); + constexpr PageTableEntry KernelRwDataAttribute(PageTableEntry::Permission_KernelRW, PageTableEntry::PageAttribute_NormalMemory, PageTableEntry::Shareable_InnerShareable, PageTableEntry::MappingFlag_Mapped); + constexpr PageTableEntry KernelMmioAttribute(PageTableEntry::Permission_KernelRW, PageTableEntry::PageAttribute_Device_nGnRE, PageTableEntry::Shareable_OuterShareable, PageTableEntry::MappingFlag_Mapped); + + constexpr PageTableEntry KernelRwDataUncachedAttribute(PageTableEntry::Permission_KernelRW, PageTableEntry::PageAttribute_NormalMemoryNotCacheable, PageTableEntry::Shareable_InnerShareable, PageTableEntry::MappingFlag_Mapped); + + void TurnOnAllCores() { + cpu::MultiprocessorAffinityRegisterAccessor mpidr; + const auto arg = mpidr.GetCpuOnArgument(); + const auto current_core = mpidr.GetAff0(); + + for (s32 i = 0; i < static_cast<s32>(cpu::NumCores); i++) { + if (static_cast<s32>(current_core) != i) { + KSystemControl::Init::TurnOnCpu(arg | i, g_init_arguments + i); + } + } + } + + void InvokeMain(u64 core_id) { + /* Clear cpacr_el1. */ + cpu::SetCpacrEl1(0); + cpu::InstructionMemoryBarrier(); + + /* Initialize registers. */ + InitializeDebugRegisters(); + InitializeExceptionVectors(); + + /* Set exception stack. */ + cpu::SetCntvCvalEl0(GetInteger(KMemoryLayout::GetExceptionStackTopAddress(static_cast<s32>(core_id))) - sizeof(KThread::StackParameters)); + + /* Call main. */ + HorizonKernelMain(static_cast<s32>(core_id)); + } + + void SetupInitialArguments() { + /* Determine whether we're running on a cortex-a53 or a-57. */ + cpu::MainIdRegisterAccessor midr_el1; + const auto implementer = midr_el1.GetImplementer(); + const auto primary_part = midr_el1.GetPrimaryPartNumber(); + const bool needs_cpu_ctlr = (implementer == cpu::MainIdRegisterAccessor::Implementer::ArmLimited) && (primary_part == cpu::MainIdRegisterAccessor::PrimaryPartNumber::CortexA57 || primary_part == cpu::MainIdRegisterAccessor::PrimaryPartNumber::CortexA53); + + /* Get parameters for initial arguments. */ + const u64 cpuactlr = needs_cpu_ctlr ? cpu::GetCpuActlrEl1() : 0; + const u64 cpuectlr = needs_cpu_ctlr ? cpu::GetCpuEctlrEl1() : 0; + + for (s32 i = 0; i < static_cast<s32>(cpu::NumCores); ++i) { + /* Get the arguments. */ + KInitArguments *init_args = g_init_arguments + i; + + /* Set the arguments. */ + init_args->cpuactlr = cpuactlr; + init_args->cpuectlr = cpuectlr; + init_args->sp = GetInteger(KMemoryLayout::GetMainStackTopAddress(i)) - sizeof(KThread::StackParameters); + init_args->entrypoint = reinterpret_cast<uintptr_t>(::ams::kern::init::InvokeMain); + init_args->argument = static_cast<u64>(i); + } + } + + KVirtualAddress GetRandomAlignedRegionWithGuard(size_t size, size_t alignment, KInitialPageTable &pt, KMemoryRegionTree &tree, u32 type_id, size_t guard_size) { + /* Check that the size is valid. */ + MESOSPHERE_INIT_ABORT_UNLESS(size > 0); + + /* We want to find the total extents of the type id. */ + const auto extents = tree.GetDerivedRegionExtents(type_id); + + /* Ensure that our alignment is correct. */ + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(extents.GetAddress(), alignment)); + + const uintptr_t first_address = extents.GetAddress(); + const uintptr_t last_address = extents.GetLastAddress(); + + const uintptr_t first_index = first_address / alignment; + const uintptr_t last_index = last_address / alignment; + + while (true) { + const uintptr_t candidate_start = KSystemControl::Init::GenerateRandomRange(first_index, last_index) * alignment; + const uintptr_t candidate_end = candidate_start + size + guard_size; + + /* Ensure that the candidate doesn't overflow with the size/guard. */ + if (!(candidate_start < candidate_end) || !(candidate_start >= guard_size)) { + continue; + } + + const uintptr_t candidate_last = candidate_end - 1; + + /* Ensure that the candidate fits within the region. */ + if (candidate_last > last_address) { + continue; + } + + /* Ensure that the candidate range is free. */ + if (!pt.IsFree(candidate_start, size)) { + continue; + } + + /* Locate the candidate's guard start, and ensure the whole range fits/has the correct type id. */ + if (const auto &candidate_region = *tree.Find(candidate_start - guard_size); !(candidate_last <= candidate_region.GetLastAddress() && candidate_region.GetType() == type_id)) { + continue; + } + + return candidate_start; + } + } + + KVirtualAddress GetRandomAlignedRegion(size_t size, size_t alignment, KInitialPageTable &pt, KMemoryRegionTree &tree, u32 type_id) { + return GetRandomAlignedRegionWithGuard(size, alignment, pt, tree, type_id, 0); + } + + void MapStackForCore(KInitialPageTable &page_table, KMemoryRegionType type, u32 core_id) { + constexpr size_t StackSize = PageSize; + constexpr size_t StackAlign = PageSize; + const KVirtualAddress stack_start_virt = GetRandomAlignedRegionWithGuard(StackSize, StackAlign, page_table, KMemoryLayout::GetVirtualMemoryRegionTree(), KMemoryRegionType_KernelMisc, PageSize); + const KPhysicalAddress stack_start_phys = g_initial_page_allocator.Allocate(PageSize); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetVirtualMemoryRegionTree().Insert(GetInteger(stack_start_virt), StackSize, type, core_id)); + + page_table.Map(stack_start_virt, StackSize, stack_start_phys, KernelRwDataAttribute, g_initial_page_allocator, 0); + } + + class KInitialPageAllocatorForFinalizeIdentityMapping final { + private: + struct FreeListEntry { + FreeListEntry *next; + }; + private: + FreeListEntry *m_free_list_head; + u64 m_phys_to_virt_offset; + public: + template<kern::arch::arm64::init::IsInitialPageAllocator PageAllocator> + KInitialPageAllocatorForFinalizeIdentityMapping(PageAllocator &allocator, u64 phys_to_virt) : m_free_list_head(nullptr), m_phys_to_virt_offset(phys_to_virt) { + /* Allocate and free two pages. */ + for (size_t i = 0; i < 2; ++i) { + KPhysicalAddress page = allocator.Allocate(PageSize); + MESOSPHERE_INIT_ABORT_UNLESS(page != Null<KPhysicalAddress>); + + /* Free the pages. */ + this->Free(page, PageSize); + } + } + public: + KPhysicalAddress Allocate(size_t size) { + /* Check that the size is correct. */ + MESOSPHERE_INIT_ABORT_UNLESS(size == PageSize); + + /* Check that we have a free page. */ + FreeListEntry *head = m_free_list_head; + MESOSPHERE_INIT_ABORT_UNLESS(head != nullptr); + + /* Update the free list. */ + m_free_list_head = head->next; + + /* Return the page. */ + return KPhysicalAddress(reinterpret_cast<uintptr_t>(head) - m_phys_to_virt_offset); + } + + void Free(KPhysicalAddress phys_addr, size_t size) { + /* Check that the size is correct. */ + MESOSPHERE_INIT_ABORT_UNLESS(size == PageSize); + + /* Convert to a free list entry. */ + FreeListEntry *fl = reinterpret_cast<FreeListEntry *>(GetInteger(phys_addr) + m_phys_to_virt_offset); + + /* Insert into free list. */ + fl->next = m_free_list_head; + m_free_list_head = fl; + } + }; + static_assert(kern::arch::arm64::init::IsInitialPageAllocator<KInitialPageAllocatorForFinalizeIdentityMapping>); + + void SetupAllTtbr0Entries(KInitialPageTable &init_pt, KInitialPageAllocator &allocator) { + /* Validate that the ttbr0 array is in rodata. */ + const uintptr_t rodata_start = reinterpret_cast<uintptr_t>(__rodata_start); + const uintptr_t rodata_end = reinterpret_cast<uintptr_t>(__rodata_end); + MESOSPHERE_INIT_ABORT_UNLESS(rodata_start < rodata_end); + MESOSPHERE_INIT_ABORT_UNLESS(rodata_start <= reinterpret_cast<uintptr_t>(std::addressof(KPageTable::GetTtbr0Entry(0)))); + MESOSPHERE_INIT_ABORT_UNLESS(reinterpret_cast<uintptr_t>(std::addressof(KPageTable::GetTtbr0Entry(KPageTable::NumTtbr0Entries))) < rodata_end); + + /* Allocate pages for all ttbr0 entries. */ + for (size_t i = 0; i < KPageTable::NumTtbr0Entries; ++i) { + /* Allocate a page. */ + KPhysicalAddress page = allocator.Allocate(PageSize); + MESOSPHERE_INIT_ABORT_UNLESS(page != Null<KPhysicalAddress>); + + /* Check that the page is allowed to be a ttbr0 entry. */ + MESOSPHERE_INIT_ABORT_UNLESS((GetInteger(page) & UINT64_C(0xFFFF000000000001)) == 0); + + /* Get the physical address of the ttbr0 entry. */ + const auto ttbr0_phys_ptr = init_pt.GetPhysicalAddress(KVirtualAddress(std::addressof(KPageTable::GetTtbr0Entry(i)))); + + /* Set the entry to the newly allocated page. */ + *reinterpret_cast<volatile u64 *>(GetInteger(ttbr0_phys_ptr)) = (static_cast<u64>(i) << 48) | GetInteger(page); + } + } + + void FinalizeIdentityMapping(KInitialPageTable &init_pt, KInitialPageAllocator &allocator, u64 phys_to_virt_offset) { + /* Create an allocator for identity mapping finalization. */ + KInitialPageAllocatorForFinalizeIdentityMapping finalize_allocator(allocator, phys_to_virt_offset); + + /* Get the physical address of crt0. */ + const KPhysicalAddress start_phys_addr = init_pt.GetPhysicalAddress(reinterpret_cast<uintptr_t>(::ams::kern::init::IdentityMappedFunctionAreaBegin)); + + /* Unmap the entire identity mapping. */ + init_pt.UnmapTtbr0Entries(phys_to_virt_offset); + + /* Re-map only the first page of code. */ + const size_t size = util::AlignUp<size_t>(reinterpret_cast<uintptr_t>(::ams::kern::init::IdentityMappedFunctionAreaEnd) - reinterpret_cast<uintptr_t>(::ams::kern::init::IdentityMappedFunctionAreaBegin), PageSize); + init_pt.Map(KVirtualAddress(GetInteger(start_phys_addr)), size, start_phys_addr, KernelTextAttribute, finalize_allocator, phys_to_virt_offset); + } + + } + + void InitializeCorePhase1(uintptr_t misc_unk_debug_phys_addr, void **initial_state) { + /* Ensure our first argument is page aligned. */ + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(misc_unk_debug_phys_addr, PageSize)); + + /* Decode the initial state. */ + const auto initial_page_allocator_state = *static_cast<KInitialPageAllocator::State *>(initial_state[0]); + g_phase2_initial_process_binary_meta = *static_cast<InitialProcessBinaryLayoutWithSize *>(initial_state[1]); + + /* Restore the page allocator state setup by kernel loader. */ + g_initial_page_allocator.InitializeFromState(std::addressof(initial_page_allocator_state)); + + /* Ensure that the T1SZ is correct (and what we expect). */ + MESOSPHERE_INIT_ABORT_UNLESS((cpu::TranslationControlRegisterAccessor().GetT1Size() / arch::arm64::L1BlockSize) == arch::arm64::MaxPageTableEntries); + + /* Create page table object for use during initialization. */ + KInitialPageTable init_pt; + + /* Initialize the slab allocator counts. */ + InitializeSlabResourceCounts(); + + /* Insert the root region for the virtual memory tree, from which all other regions will derive. */ + KMemoryLayout::GetVirtualMemoryRegionTree().InsertDirectly(KernelVirtualAddressSpaceBase, KernelVirtualAddressSpaceBase + KernelVirtualAddressSpaceSize - 1); + + /* Insert the root region for the physical memory tree, from which all other regions will derive. */ + KMemoryLayout::GetPhysicalMemoryRegionTree().InsertDirectly(KernelPhysicalAddressSpaceBase, KernelPhysicalAddressSpaceBase + KernelPhysicalAddressSpaceSize - 1); + + /* Save start and end for ease of use. */ + const uintptr_t code_start_virt_addr = reinterpret_cast<uintptr_t>(__bin_start__); + const uintptr_t code_end_virt_addr = reinterpret_cast<uintptr_t>(__bin_end__); + + /* Setup the containing kernel region. */ + constexpr size_t KernelRegionSize = 1_GB; + constexpr size_t KernelRegionAlign = 1_GB; + const KVirtualAddress kernel_region_start = util::AlignDown(code_start_virt_addr, KernelRegionAlign); + size_t kernel_region_size = KernelRegionSize; + if (!(kernel_region_start + KernelRegionSize - 1 <= KernelVirtualAddressSpaceLast)) { + kernel_region_size = KernelVirtualAddressSpaceEnd - GetInteger(kernel_region_start); + } + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetVirtualMemoryRegionTree().Insert(GetInteger(kernel_region_start), kernel_region_size, KMemoryRegionType_Kernel)); + + /* Setup the code region. */ + constexpr size_t CodeRegionAlign = PageSize; + const KVirtualAddress code_region_start = util::AlignDown(code_start_virt_addr, CodeRegionAlign); + const KVirtualAddress code_region_end = util::AlignUp(code_end_virt_addr, CodeRegionAlign); + const size_t code_region_size = GetInteger(code_region_end) - GetInteger(code_region_start); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetVirtualMemoryRegionTree().Insert(GetInteger(code_region_start), code_region_size, KMemoryRegionType_KernelCode)); + + /* Setup board-specific device physical regions. */ + SetupDevicePhysicalMemoryRegions(); + + /* Determine the amount of space needed for the misc region. */ + size_t misc_region_needed_size; + { + /* Each core has a one page stack for all three stack types (Main, Idle, Exception). */ + misc_region_needed_size = cpu::NumCores * (3 * (PageSize + PageSize)); + + /* Account for each auto-map device. */ + for (const auto ®ion : KMemoryLayout::GetPhysicalMemoryRegionTree()) { + if (region.HasTypeAttribute(KMemoryRegionAttr_ShouldKernelMap)) { + /* Check that the region is valid. */ + MESOSPHERE_INIT_ABORT_UNLESS(region.GetEndAddress() != 0); + + /* Account for the region. */ + const auto aligned_start = util::AlignDown(region.GetAddress(), PageSize); + const auto aligned_end = util::AlignUp(region.GetLastAddress(), PageSize); + const size_t cur_region_size = aligned_end - aligned_start; + misc_region_needed_size += cur_region_size; + + /* Account for alignment requirements. */ + const size_t min_align = std::min<size_t>(util::GetAlignment(cur_region_size), util::GetAlignment(aligned_start)); + misc_region_needed_size += min_align >= KernelAslrAlignment ? KernelAslrAlignment : PageSize; + } + } + + /* Account for the unknown debug region. */ + misc_region_needed_size += GetMiscUnknownDebugRegionSize(); + + /* Multiply the needed size by three, to account for the need for guard space. */ + misc_region_needed_size *= 3; + } + + /* Decide on the actual size for the misc region. */ + constexpr size_t MiscRegionAlign = KernelAslrAlignment; + constexpr size_t MiscRegionMinimumSize = 32_MB; + const size_t misc_region_size = util::AlignUp(std::max(misc_region_needed_size, MiscRegionMinimumSize), MiscRegionAlign); + MESOSPHERE_INIT_ABORT_UNLESS(misc_region_size > 0); + + /* Setup the misc region. */ + const KVirtualAddress misc_region_start = GetRandomAlignedRegion(misc_region_size, MiscRegionAlign, init_pt, KMemoryLayout::GetVirtualMemoryRegionTree(), KMemoryRegionType_Kernel); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetVirtualMemoryRegionTree().Insert(GetInteger(misc_region_start), misc_region_size, KMemoryRegionType_KernelMisc)); + + /* Determine if we'll use extra thread resources. */ + const bool use_extra_resources = KSystemControl::Init::ShouldIncreaseThreadResourceLimit(); + + /* Setup the stack region. */ + const size_t stack_region_size = use_extra_resources ? 24_MB : 14_MB; + constexpr size_t StackRegionAlign = KernelAslrAlignment; + const KVirtualAddress stack_region_start = GetRandomAlignedRegion(stack_region_size, StackRegionAlign, init_pt, KMemoryLayout::GetVirtualMemoryRegionTree(), KMemoryRegionType_Kernel); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetVirtualMemoryRegionTree().Insert(GetInteger(stack_region_start), stack_region_size, KMemoryRegionType_KernelStack)); + + /* Determine the size of the resource region. */ + const size_t resource_region_size = KMemoryLayout::GetResourceRegionSizeForInit(use_extra_resources); + + /* Determine the size of the slab region. */ + const size_t slab_region_size = util::AlignUp(CalculateTotalSlabHeapSize(), PageSize); + MESOSPHERE_INIT_ABORT_UNLESS(slab_region_size <= resource_region_size); + + /* Setup the slab region. */ + const KPhysicalAddress code_start_phys_addr = g_phase2_initial_process_binary_meta.layout.kern_address; + const KPhysicalAddress code_end_phys_addr = code_start_phys_addr + code_region_size; + const KPhysicalAddress slab_start_phys_addr = code_end_phys_addr; + const KPhysicalAddress slab_end_phys_addr = slab_start_phys_addr + slab_region_size; + constexpr size_t SlabRegionAlign = KernelAslrAlignment; + const size_t slab_region_needed_size = util::AlignUp(GetInteger(code_end_phys_addr) + slab_region_size, SlabRegionAlign) - util::AlignDown(GetInteger(code_end_phys_addr), SlabRegionAlign); + const KVirtualAddress slab_region_start = GetRandomAlignedRegion(slab_region_needed_size, SlabRegionAlign, init_pt, KMemoryLayout::GetVirtualMemoryRegionTree(), KMemoryRegionType_Kernel) + (GetInteger(code_end_phys_addr) % SlabRegionAlign); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetVirtualMemoryRegionTree().Insert(GetInteger(slab_region_start), slab_region_size, KMemoryRegionType_KernelSlab)); + + /* Setup the temp region. */ + constexpr size_t TempRegionSize = 128_MB; + constexpr size_t TempRegionAlign = KernelAslrAlignment; + const KVirtualAddress temp_region_start = GetRandomAlignedRegion(TempRegionSize, TempRegionAlign, init_pt, KMemoryLayout::GetVirtualMemoryRegionTree(), KMemoryRegionType_Kernel); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetVirtualMemoryRegionTree().Insert(GetInteger(temp_region_start), TempRegionSize, KMemoryRegionType_KernelTemp)); + + /* Automatically map in devices that have auto-map attributes, from largest region to smallest region. */ + { + /* We want to map the regions from largest to smallest. */ + KMemoryRegion *largest; + do { + /* Begin with no knowledge of the largest region. */ + largest = nullptr; + + for (auto ®ion : KMemoryLayout::GetPhysicalMemoryRegionTree()) { + /* We only care about kernel regions. */ + if (!region.IsDerivedFrom(KMemoryRegionType_Kernel)) { + continue; + } + + /* Check whether we should map the region. */ + if (!region.HasTypeAttribute(KMemoryRegionAttr_ShouldKernelMap)) { + continue; + } + + /* If this region has already been mapped, no need to consider it. */ + if (region.HasTypeAttribute(KMemoryRegionAttr_DidKernelMap)) { + continue; + } + + /* Check that the region is valid. */ + MESOSPHERE_INIT_ABORT_UNLESS(region.GetEndAddress() != 0); + + /* Update the largest region. */ + if (largest == nullptr || largest->GetSize() < region.GetSize()) { + largest = std::addressof(region); + } + } + + /* If we found a region, map it. */ + if (largest != nullptr) { + /* Set the attribute to note we've mapped this region. */ + largest->SetTypeAttribute(KMemoryRegionAttr_DidKernelMap); + + /* Create a virtual pair region and insert it into the tree. */ + const KPhysicalAddress map_phys_addr = util::AlignDown(largest->GetAddress(), PageSize); + const size_t map_size = util::AlignUp(largest->GetEndAddress(), PageSize) - GetInteger(map_phys_addr); + const size_t min_align = std::min<size_t>(util::GetAlignment(map_size), util::GetAlignment(GetInteger(map_phys_addr))); + const size_t map_align = min_align >= KernelAslrAlignment ? KernelAslrAlignment : PageSize; + const KVirtualAddress map_virt_addr = GetRandomAlignedRegionWithGuard(map_size, map_align, init_pt, KMemoryLayout::GetVirtualMemoryRegionTree(), KMemoryRegionType_KernelMisc, PageSize); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetVirtualMemoryRegionTree().Insert(GetInteger(map_virt_addr), map_size, KMemoryRegionType_KernelMiscMappedDevice)); + largest->SetPairAddress(GetInteger(map_virt_addr) + largest->GetAddress() - GetInteger(map_phys_addr)); + + /* Map the page in to our page table. */ + init_pt.Map(map_virt_addr, map_size, map_phys_addr, KernelMmioAttribute, g_initial_page_allocator, 0); + } + } while (largest != nullptr); + } + + /* Setup the basic DRAM regions. */ + SetupDramPhysicalMemoryRegions(); + + /* Automatically map in reserved physical memory that has auto-map attributes. */ + { + /* We want to map the regions from largest to smallest. */ + KMemoryRegion *largest; + do { + /* Begin with no knowledge of the largest region. */ + largest = nullptr; + + for (auto ®ion : KMemoryLayout::GetPhysicalMemoryRegionTree()) { + /* We only care about reserved memory. */ + if (!region.IsDerivedFrom(KMemoryRegionType_DramReservedBase)) { + continue; + } + + /* Check whether we should map the region. */ + if (!region.HasTypeAttribute(KMemoryRegionAttr_ShouldKernelMap)) { + continue; + } + + /* If this region has already been mapped, no need to consider it. */ + if (region.HasTypeAttribute(KMemoryRegionAttr_DidKernelMap)) { + continue; + } + + /* Check that the region is valid. */ + MESOSPHERE_INIT_ABORT_UNLESS(region.GetEndAddress() != 0); + + /* Update the largest region. */ + if (largest == nullptr || largest->GetSize() < region.GetSize()) { + largest = std::addressof(region); + } + } + + /* If we found a region, map it. */ + if (largest != nullptr) { + /* Set the attribute to note we've mapped this region. */ + largest->SetTypeAttribute(KMemoryRegionAttr_DidKernelMap); + + /* Create a virtual pair region and insert it into the tree. */ + const KPhysicalAddress map_phys_addr = util::AlignDown(largest->GetAddress(), PageSize); + const size_t map_size = util::AlignUp(largest->GetEndAddress(), PageSize) - GetInteger(map_phys_addr); + const size_t min_align = std::min<size_t>(util::GetAlignment(map_size), util::GetAlignment(GetInteger(map_phys_addr))); + const size_t map_align = min_align >= KernelAslrAlignment ? KernelAslrAlignment : PageSize; + const KVirtualAddress map_virt_addr = GetRandomAlignedRegionWithGuard(map_size, map_align, init_pt, KMemoryLayout::GetVirtualMemoryRegionTree(), KMemoryRegionType_KernelMisc, PageSize); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetVirtualMemoryRegionTree().Insert(GetInteger(map_virt_addr), map_size, KMemoryRegionType_KernelMiscUnknownDebug)); + largest->SetPairAddress(GetInteger(map_virt_addr) + largest->GetAddress() - GetInteger(map_phys_addr)); + + /* Map the page in to our page table. */ + const auto attribute = largest->HasTypeAttribute(KMemoryRegionAttr_Uncached) ? KernelRwDataUncachedAttribute : KernelRwDataAttribute; + init_pt.Map(map_virt_addr, map_size, map_phys_addr, attribute, g_initial_page_allocator, 0); + } + } while (largest != nullptr); + } + + /* Insert a physical region for the kernel code region. */ + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(GetInteger(code_start_phys_addr), code_region_size, KMemoryRegionType_DramKernelCode)); + + /* Insert a physical region for the kernel slab region. */ + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(GetInteger(slab_start_phys_addr), slab_region_size, KMemoryRegionType_DramKernelSlab)); + + /* Map the slab region. */ + init_pt.Map(slab_region_start, slab_region_size, slab_start_phys_addr, KernelRwDataAttribute, g_initial_page_allocator, 0); + + /* Physically randomize the slab region. */ + /* NOTE: Nintendo does this only on 10.0.0+ */ + init_pt.PhysicallyRandomize(slab_region_start, slab_region_size, false); + + /* Insert a physical region for the secure applet memory. */ + const auto secure_applet_end_phys_addr = slab_end_phys_addr + KSystemControl::SecureAppletMemorySize; + if constexpr (KSystemControl::SecureAppletMemorySize > 0) { + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(GetInteger(slab_end_phys_addr), KSystemControl::SecureAppletMemorySize, KMemoryRegionType_DramKernelSecureAppletMemory)); + } + + /* Insert a physical region for the unknown debug2 region. */ + const size_t secure_unknown_size = GetSecureUnknownRegionSize(); + const auto secure_unknown_end_phys_addr = secure_applet_end_phys_addr + secure_unknown_size; + if (secure_unknown_size > 0) { + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(GetInteger(secure_applet_end_phys_addr), secure_unknown_size, KMemoryRegionType_DramKernelSecureUnknown)); + } + + /* Determine size available for kernel page table heaps. */ + const KPhysicalAddress resource_end_phys_addr = slab_start_phys_addr + resource_region_size; + g_phase2_resource_end_phys_addr = resource_end_phys_addr; + + const size_t page_table_heap_size = GetInteger(resource_end_phys_addr) - GetInteger(secure_unknown_end_phys_addr); + + /* Insert a physical region for the kernel page table heap region */ + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(GetInteger(secure_unknown_end_phys_addr), page_table_heap_size, KMemoryRegionType_DramKernelPtHeap)); + + /* All DRAM regions that we haven't tagged by this point will be mapped under the linear mapping. Tag them. */ + for (auto ®ion : KMemoryLayout::GetPhysicalMemoryRegionTree()) { + if (region.GetType() == KMemoryRegionType_Dram) { + /* Check that the region is valid. */ + MESOSPHERE_INIT_ABORT_UNLESS(region.GetEndAddress() != 0); + + /* Set the linear map attribute. */ + region.SetTypeAttribute(KMemoryRegionAttr_LinearMapped); + } + } + + /* Get the linear region extents. */ + const auto linear_extents = KMemoryLayout::GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionAttr_LinearMapped); + MESOSPHERE_INIT_ABORT_UNLESS(linear_extents.GetEndAddress() != 0); + + /* Setup the linear mapping region. */ + constexpr size_t LinearRegionAlign = 1_GB; + const KPhysicalAddress aligned_linear_phys_start = util::AlignDown(linear_extents.GetAddress(), LinearRegionAlign); + const size_t linear_region_size = util::AlignUp(linear_extents.GetEndAddress(), LinearRegionAlign) - GetInteger(aligned_linear_phys_start); + const KVirtualAddress linear_region_start = GetRandomAlignedRegionWithGuard(linear_region_size, LinearRegionAlign, init_pt, KMemoryLayout::GetVirtualMemoryRegionTree(), KMemoryRegionType_None, LinearRegionAlign); + + const uintptr_t linear_region_phys_to_virt_diff = GetInteger(linear_region_start) - GetInteger(aligned_linear_phys_start); + + /* Map and create regions for all the linearly-mapped data. */ + { + uintptr_t cur_phys_addr = 0; + uintptr_t cur_size = 0; + for (auto ®ion : KMemoryLayout::GetPhysicalMemoryRegionTree()) { + if (!region.HasTypeAttribute(KMemoryRegionAttr_LinearMapped)) { + continue; + } + + MESOSPHERE_INIT_ABORT_UNLESS(region.GetEndAddress() != 0); + + if (cur_size == 0) { + cur_phys_addr = region.GetAddress(); + cur_size = region.GetSize(); + } else if (cur_phys_addr + cur_size == region.GetAddress()) { + cur_size += region.GetSize(); + } else { + const uintptr_t cur_virt_addr = cur_phys_addr + linear_region_phys_to_virt_diff; + init_pt.Map(cur_virt_addr, cur_size, cur_phys_addr, KernelRwDataAttribute, g_initial_page_allocator, 0); + cur_phys_addr = region.GetAddress(); + cur_size = region.GetSize(); + } + + const uintptr_t region_virt_addr = region.GetAddress() + linear_region_phys_to_virt_diff; + if (!region.HasTypeAttribute(KMemoryRegionAttr_ShouldKernelMap)) { + region.SetPairAddress(region_virt_addr); + } + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetVirtualMemoryRegionTree().Insert(region_virt_addr, region.GetSize(), GetTypeForVirtualLinearMapping(region.GetType()))); + + KMemoryRegion *virt_region = KMemoryLayout::GetVirtualMemoryRegionTree().FindModifiable(region_virt_addr); + MESOSPHERE_INIT_ABORT_UNLESS(virt_region != nullptr); + virt_region->SetPairAddress(region.GetAddress()); + } + + /* Map the last block, which we may have skipped. */ + if (cur_size != 0) { + const uintptr_t cur_virt_addr = cur_phys_addr + linear_region_phys_to_virt_diff; + init_pt.Map(cur_virt_addr, cur_size, cur_phys_addr, KernelRwDataAttribute, g_initial_page_allocator, 0); + } + } + + /* NOTE: Unknown function is called here which is ifdef'd out on retail kernel. */ + /* The unknown function is immediately before the function which gets an unknown debug region size, inside this translation unit. */ + /* It's likely that this is some kind of initializer for this unknown debug region. */ + + /* Create regions for and map all core-specific stacks. */ + for (size_t i = 0; i < cpu::NumCores; i++) { + MapStackForCore(init_pt, KMemoryRegionType_KernelMiscMainStack, i); + MapStackForCore(init_pt, KMemoryRegionType_KernelMiscIdleStack, i); + MapStackForCore(init_pt, KMemoryRegionType_KernelMiscExceptionStack, i); + } + + /* Setup the initial arguments. */ + SetupInitialArguments(); + + /* Set linear difference for Phase2. */ + g_phase2_linear_region_phys_to_virt_diff = linear_region_phys_to_virt_diff; + } + + void InitializeCorePhase2() { + /* Create page table object for use during remaining initialization. */ + KInitialPageTable init_pt; + + /* Setup all ttbr0 pages. */ + SetupAllTtbr0Entries(init_pt, g_initial_page_allocator); + + /* Unmap the identity mapping. */ + FinalizeIdentityMapping(init_pt, g_initial_page_allocator, g_phase2_linear_region_phys_to_virt_diff); + + /* Finalize the page allocator, we're done allocating at this point. */ + KInitialPageAllocator::State final_init_page_table_state; + g_initial_page_allocator.GetFinalState(std::addressof(final_init_page_table_state)); + const KPhysicalAddress final_init_page_table_end_address = final_init_page_table_state.end_address; + const size_t init_page_table_region_size = GetInteger(final_init_page_table_end_address) - GetInteger(g_phase2_resource_end_phys_addr); + + /* Insert regions for the initial page table region. */ + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(GetInteger(g_phase2_resource_end_phys_addr), init_page_table_region_size, KMemoryRegionType_DramKernelInitPt)); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetVirtualMemoryRegionTree().Insert(GetInteger(g_phase2_resource_end_phys_addr) + g_phase2_linear_region_phys_to_virt_diff, init_page_table_region_size, KMemoryRegionType_VirtualDramKernelInitPt)); + + /* Insert a physical region for the kernel trace buffer */ + if constexpr (IsKTraceEnabled) { + const KPhysicalAddress ktrace_buffer_phys_addr = GetInteger(g_phase2_resource_end_phys_addr) + init_page_table_region_size; + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(GetInteger(ktrace_buffer_phys_addr), KTraceBufferSize, KMemoryRegionType_KernelTraceBuffer)); + MESOSPHERE_INIT_ABORT_UNLESS(KMemoryLayout::GetPhysicalMemoryRegionTree().Insert(GetInteger(ktrace_buffer_phys_addr) + g_phase2_linear_region_phys_to_virt_diff, KTraceBufferSize, GetTypeForVirtualLinearMapping(KMemoryRegionType_KernelTraceBuffer))); + } + + /* All linear-mapped DRAM regions that we haven't tagged by this point will be allocated to some pool partition. Tag them. */ + for (auto ®ion : KMemoryLayout::GetPhysicalMemoryRegionTree()) { + constexpr auto UntaggedLinearDram = util::FromUnderlying<KMemoryRegionType>(util::ToUnderlying<KMemoryRegionType>(KMemoryRegionType_Dram) | util::ToUnderlying(KMemoryRegionAttr_LinearMapped)); + if (region.GetType() == UntaggedLinearDram) { + region.SetType(KMemoryRegionType_DramPoolPartition); + } + } + + /* Set the linear memory offsets, to enable conversion between physical and virtual addresses. */ + KMemoryLayout::InitializeLinearMemoryAddresses(g_phase2_linear_region_phys_to_virt_diff); + + /* Set the initial process binary physical address. */ + /* NOTE: Nintendo does this after pool partition setup, but it's a requirement that we do it before */ + /* to retain compatibility with < 5.0.0. */ + const KPhysicalAddress ini_address = g_phase2_initial_process_binary_meta.layout.address; + const size_t ini_size = g_phase2_initial_process_binary_meta.size; + MESOSPHERE_INIT_ABORT_UNLESS(ini_address != Null<KPhysicalAddress>); + SetInitialProcessBinaryPhysicalAddress(ini_address, ini_size); + + /* Setup all other memory regions needed to arrange the pool partitions. */ + SetupPoolPartitionMemoryRegions(); + + /* Validate the initial process binary address. */ + { + const KMemoryRegion *ini_region = KMemoryLayout::Find(ini_address); + + /* Check that the region is non-kernel dram. */ + MESOSPHERE_INIT_ABORT_UNLESS(ini_region->IsDerivedFrom(KMemoryRegionType_DramUserPool)); + + /* Check that the region contains the ini. */ + MESOSPHERE_INIT_ABORT_UNLESS(ini_region->GetAddress() <= GetInteger(ini_address)); + MESOSPHERE_INIT_ABORT_UNLESS(GetInteger(ini_address) + ini_size <= ini_region->GetEndAddress()); + MESOSPHERE_INIT_ABORT_UNLESS(ini_region->GetEndAddress() != 0); + } + + /* Cache all linear regions in their own trees for faster access, later. */ + KMemoryLayout::InitializeLinearMemoryRegionTrees(); + + /* Turn on all other cores. */ + TurnOnAllCores(); + } + + KInitArguments *GetInitArguments(s32 core_id) { + return g_init_arguments + core_id; + } + + void InitializeDebugRegisters() { + /* Determine how many watchpoints and breakpoints we have */ + cpu::DebugFeatureRegisterAccessor aa64dfr0; + const auto num_watchpoints = aa64dfr0.GetNumWatchpoints(); + const auto num_breakpoints = aa64dfr0.GetNumBreakpoints(); + cpu::EnsureInstructionConsistencyFullSystem(); + + /* Clear the debug monitor register and the os lock access register. */ + cpu::MonitorDebugSystemControlRegisterAccessor(0).Store(); + cpu::EnsureInstructionConsistencyFullSystem(); + cpu::OsLockAccessRegisterAccessor(0).Store(); + cpu::EnsureInstructionConsistencyFullSystem(); + + /* Clear all debug watchpoints/breakpoints. */ + #define FOR_I_IN_15_TO_1(HANDLER, ...) \ + HANDLER(15, ## __VA_ARGS__) HANDLER(14, ## __VA_ARGS__) HANDLER(13, ## __VA_ARGS__) HANDLER(12, ## __VA_ARGS__) \ + HANDLER(11, ## __VA_ARGS__) HANDLER(10, ## __VA_ARGS__) HANDLER(9, ## __VA_ARGS__) HANDLER(8, ## __VA_ARGS__) \ + HANDLER(7, ## __VA_ARGS__) HANDLER(6, ## __VA_ARGS__) HANDLER(5, ## __VA_ARGS__) HANDLER(4, ## __VA_ARGS__) \ + HANDLER(3, ## __VA_ARGS__) HANDLER(2, ## __VA_ARGS__) HANDLER(1, ## __VA_ARGS__) + + #define MESOSPHERE_INITIALIZE_WATCHPOINT_CASE(ID, ...) \ + case ID: \ + cpu::SetDbgWcr##ID##El1(__VA_ARGS__); \ + cpu::SetDbgWvr##ID##El1(__VA_ARGS__); \ + [[fallthrough]]; + + #define MESOSPHERE_INITIALIZE_BREAKPOINT_CASE(ID, ...) \ + case ID: \ + cpu::SetDbgBcr##ID##El1(__VA_ARGS__); \ + cpu::SetDbgBvr##ID##El1(__VA_ARGS__); \ + [[fallthrough]]; + + + switch (num_watchpoints) { + FOR_I_IN_15_TO_1(MESOSPHERE_INITIALIZE_WATCHPOINT_CASE, 0) + case 0: + cpu::SetDbgWcr0El1(0); + cpu::SetDbgWvr0El1(0); + [[fallthrough]]; + default: + break; + } + + switch (num_breakpoints) { + FOR_I_IN_15_TO_1(MESOSPHERE_INITIALIZE_BREAKPOINT_CASE, 0) + default: + break; + } + cpu::SetDbgBcr0El1(0); + cpu::SetDbgBvr0El1(0); + + #undef MESOSPHERE_INITIALIZE_WATCHPOINT_CASE + #undef MESOSPHERE_INITIALIZE_BREAKPOINT_CASE + #undef FOR_I_IN_15_TO_1 + + cpu::EnsureInstructionConsistencyFullSystem(); + + /* Initialize the context id register to all 1s. */ + cpu::ContextIdRegisterAccessor(0).SetProcId(std::numeric_limits<u32>::max()).Store(); + cpu::EnsureInstructionConsistencyFullSystem(); + + /* Configure the debug monitor register. */ + cpu::MonitorDebugSystemControlRegisterAccessor(0).SetMde(true).SetTdcc(true).Store(); + cpu::EnsureInstructionConsistencyFullSystem(); + } + + void InitializeExceptionVectors() { + cpu::SetVbarEl1(reinterpret_cast<uintptr_t>(::ams::kern::ExceptionVectors)); + cpu::SetTpidrEl1(0); + cpu::SetExceptionThreadStackTop(0); + cpu::EnsureInstructionConsistencyFullSystem(); + } + + size_t GetMiscUnknownDebugRegionSize() { + return 0; + } + + size_t GetSecureUnknownRegionSize() { + return 0; + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/init/start.s b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/init/start.s new file mode 100644 index 00000000..ad483ffc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/init/start.s @@ -0,0 +1,637 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere/kern_select_assembly_offsets.h> + +/* 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 + +#define LOAD_IMMEDIATE_32(reg, val) \ + mov reg, #(((val) >> 0x00) & 0xFFFF); \ + movk reg, #(((val) >> 0x10) & 0xFFFF), lsl#16 + +#define LOAD_IMMEDIATE_64(reg, val) \ + mov reg, #(((val) >> 0x00) & 0xFFFF); \ + movk reg, #(((val) >> 0x10) & 0xFFFF), lsl#16; \ + movk reg, #(((val) >> 0x20) & 0xFFFF), lsl#32; \ + movk reg, #(((val) >> 0x30) & 0xFFFF), lsl#48 + +#define LOAD_FROM_LABEL(reg, label) \ + adr reg, label; \ + ldr reg, [reg] + +#define LOAD_RELATIVE_FROM_LABEL(reg, reg2, label) \ + adr reg2, label; \ + ldr reg, [reg2]; \ + add reg, reg, reg2 + +#define INDIRECT_RELATIVE_CALL(reg, reg2, label) \ + adr reg, label; \ + add reg, reg, reg2; \ + blr reg + +#define SETUP_SYSTEM_REGISTER(_reg, _sr) \ + LOAD_FROM_LABEL(_reg, __sysreg_constant_ ## _sr); \ + msr _sr, _reg + +.section .start, "ax", %progbits +.global _start +_start: + b _ZN3ams4kern4init10StartCore0Emm +__metadata_magic_number: + .ascii "MSS1" /* Magic, if executed as gadget "adds w13, w26, #0x4d4, lsl #12" */ +__metadata_offset: + .word __metadata_begin - _start + +#ifdef ATMOSPHERE_BOARD_NINTENDO_NX +.global _ZN3ams4kern17GetTargetFirmwareEv +.type _ZN3ams4kern17GetTargetFirmwareEv, %function +_ZN3ams4kern17GetTargetFirmwareEv: + adr x0, __metadata_target_firmware + ldr w0, [x0] + ret +#endif + +.section .crt0.text.start, "ax", %progbits + +/* ams::kern::init::IdentityMappedFunctionAreaBegin() */ +.global _ZN3ams4kern4init31IdentityMappedFunctionAreaBeginEv +.type _ZN3ams4kern4init31IdentityMappedFunctionAreaBeginEv, %function +_ZN3ams4kern4init31IdentityMappedFunctionAreaBeginEv: +/* NOTE: This is not a real function, and only exists as a label for safety. */ + +/* ================ Functions after this line remain identity-mapped after initialization finishes. ================ */ + +/* ams::kern::init::StartCore0(uintptr_t, uintptr_t) */ +.section .crt0.text._ZN3ams4kern4init10StartCore0Emm, "ax", %progbits +.global _ZN3ams4kern4init10StartCore0Emm +.type _ZN3ams4kern4init10StartCore0Emm, %function +_ZN3ams4kern4init10StartCore0Emm: + /* Mask all interrupts. */ + msr daifset, #0xF + + /* Save arguments for later use. */ + mov x19, x0 + mov x20, x1 + + /* Check our current EL. We want to be executing out of EL1. */ + mrs x1, currentel + + /* Check if we're EL1. */ + cmp x1, #0x4 + b.eq 2f + + /* Check if we're EL2. */ + cmp x1, #0x8 + b.eq 1f + +0: /* We're EL3. This is a panic condition. */ + b 0b + +1: /* We're EL2. */ + #ifdef ATMOSPHERE_BOARD_NINTENDO_NX + /* On NX board, this is a panic condition. */ + b 1b + #else + /* Otherwise, deprivilege to EL2. */ + /* TODO: Does N still have this? We need it for qemu emulation/unit testing, we should come up with a better solution maybe. */ + bl _ZN3ams4kern4init16JumpFromEL2ToEL1Ev + #endif + +2: /* We're EL1. */ + /* Flush the entire data cache and invalidate the entire TLB. */ + bl _ZN3ams4kern4arch5arm643cpu32FlushEntireDataCacheWithoutStackEv + + /* Invalidate the instruction cache, and ensure instruction consistency. */ + ic ialluis + dsb sy + isb + + /* Disable the MMU/Caches. */ + bl _ZN3ams4kern4init19DisableMmuAndCachesEv + +#ifdef ATMOSPHERE_BOARD_NINTENDO_NX + /* Get the target firmware from exosphere. */ + LOAD_IMMEDIATE_32(w0, 0xC3000004) + mov w1, #65000 + smc #1 + cmp x0, #0 +3: + b.ne 3b + + /* Store the target firmware. */ + adr x0, __metadata_target_firmware + str w1, [x0] +#endif + + /* Get the unknown debug region. */ + /* TODO: This is always zero in release kernels -- what is this? Is it the device tree buffer? */ + mov x21, #0 + nop + + /* We want to invoke kernel loader. */ + adr x0, _start + adr x1, __metadata_kernel_layout + LOAD_RELATIVE_FROM_LABEL(x2, x4, __metadata_ini_offset) + LOAD_RELATIVE_FROM_LABEL(x3, x4, __metadata_kernelldr_offset) + + /* Invoke kernel loader. */ + blr x3 + + /* Save the offset to virtual address from this page's physical address for our use. */ + mov x24, x1 + + /* Clear the platform register (used for Kernel::GetCurrentThreadPointer()) */ + mov x18, #0 + + /* At this point kernelldr has been invoked, and we are relocated at a random virtual address. */ + /* Next thing to do is to set up our memory management and slabheaps -- all the other core initialization. */ + /* Call ams::kern::init::InitializeCore(uintptr_t, void **) */ + mov x1, x0 /* Kernelldr returns a state object for the kernel to re-use. */ + mov x0, x21 /* Use the address we determined earlier. */ + nop + INDIRECT_RELATIVE_CALL(x16, x24, _ZN3ams4kern4init20InitializeCorePhase1EmPPv) + + /* Get the init arguments for core 0. */ + mov x0, xzr + nop + INDIRECT_RELATIVE_CALL(x16, x24, _ZN3ams4kern4init16GetInitArgumentsEi) + + /* Setup the stack pointer. */ + ldr x2, [x0, #(INIT_ARGUMENTS_SP)] + mov sp, x2 + + /* Perform further initialization with the stack pointer set up, as required. */ + /* This will include e.g. unmapping the identity mapping. */ + nop + INDIRECT_RELATIVE_CALL(x16, x24, _ZN3ams4kern4init20InitializeCorePhase2Ev) + + /* Get the init arguments for core 0. */ + mov x0, xzr + nop + INDIRECT_RELATIVE_CALL(x16, x24, _ZN3ams4kern4init16GetInitArgumentsEi) + + /* Retrieve entrypoint and argument. */ + ldr x1, [x0, #(INIT_ARGUMENTS_ENTRYPOINT)] + ldr x0, [x0, #(INIT_ARGUMENTS_ARGUMENT)] + + /* Set sctlr_el1 and ensure instruction consistency. */ + SETUP_SYSTEM_REGISTER(x3, sctlr_el1) + + dsb sy + isb + + /* Invoke the entrypoint. */ + blr x1 + +0: /* If we return here, something has gone wrong, so wait forever. */ + b 0b + +/* ams::kern::init::StartOtherCore(const ams::kern::init::KInitArguments *) */ +.section .crt0.text._ZN3ams4kern4init14StartOtherCoreEPKNS1_14KInitArgumentsE, "ax", %progbits +.global _ZN3ams4kern4init14StartOtherCoreEPKNS1_14KInitArgumentsE +.type _ZN3ams4kern4init14StartOtherCoreEPKNS1_14KInitArgumentsE, %function +_ZN3ams4kern4init14StartOtherCoreEPKNS1_14KInitArgumentsE: + /* Preserve the KInitArguments pointer in a register. */ + mov x20, x0 + + /* Check our current EL. We want to be executing out of EL1. */ + mrs x1, currentel + + /* Check if we're EL1. */ + cmp x1, #0x4 + b.eq 2f + + /* Check if we're EL2. */ + cmp x1, #0x8 + b.eq 1f + +0: /* We're EL3. This is a panic condition. */ + b 0b + +1: /* We're EL2. */ + #ifdef ATMOSPHERE_BOARD_NINTENDO_NX + /* On NX board, this is a panic condition. */ + b 1b + #else + /* Otherwise, deprivilege to EL2. */ + /* TODO: Does N still have this? We need it for qemu emulation/unit testing, we should come up with a better solution maybe. */ + bl _ZN3ams4kern4init16JumpFromEL2ToEL1Ev + #endif + +2: /* We're EL1. */ + /* Flush the entire data cache and invalidate the entire TLB. */ + bl _ZN3ams4kern4arch5arm643cpu32FlushEntireDataCacheWithoutStackEv + + /* Invalidate the instruction cache, and ensure instruction consistency. */ + ic ialluis + dsb sy + isb + + /* Disable the MMU/Caches. */ + bl _ZN3ams4kern4init19DisableMmuAndCachesEv + + /* Setup system registers using values from constants table. */ + SETUP_SYSTEM_REGISTER(x1, ttbr0_el1) + SETUP_SYSTEM_REGISTER(x1, ttbr1_el1) + SETUP_SYSTEM_REGISTER(x1, tcr_el1) + SETUP_SYSTEM_REGISTER(x1, mair_el1) + + /* Perform cpu-specific setup. */ + mrs x1, midr_el1 + ubfx x2, x1, #0x18, #0x8 /* Extract implementer bits. */ + cmp x2, #0x41 /* Implementer::ArmLimited */ + b.ne 4f + ubfx x2, x1, #0x4, #0xC /* Extract primary part number. */ + cmp x2, #0xD07 /* PrimaryPartNumber::CortexA57 */ + b.eq 3f + cmp x2, #0xD03 /* PrimaryPartNumber::CortexA53 */ + b.eq 3f + b 4f +3: /* We're running on a Cortex-A53/Cortex-A57. */ + /* NOTE: Nintendo compares these values instead of setting them, infinite looping on incorrect value. */ + ldr x1, [x20, #(INIT_ARGUMENTS_CPUACTLR)] + msr cpuactlr_el1, x1 + ldr x1, [x20, #(INIT_ARGUMENTS_CPUECTLR)] + msr cpuectlr_el1, x1 + +4: + /* Ensure instruction consistency. */ + dsb sy + isb + + /* Load remaining needed fields from the init args. */ + ldr x2, [x20, #(INIT_ARGUMENTS_SP)] + ldr x1, [x20, #(INIT_ARGUMENTS_ENTRYPOINT)] + ldr x0, [x20, #(INIT_ARGUMENTS_ARGUMENT)] + + /* Set sctlr_el1 and ensure instruction consistency. */ + SETUP_SYSTEM_REGISTER(x3, sctlr_el1) + + dsb sy + isb + + /* Set the stack pointer. */ + mov sp, x2 + + /* Clear the platform register (used for Kernel::GetCurrentThreadPointer()) */ + mov x18, #0 + + /* Invoke the entrypoint. */ + blr x1 + +0: /* If we return here, something has gone wrong, so wait forever. */ + b 0b + +/* Nintendo places the metadata after StartOthercore. */ +.align 8 + +__metadata_begin: +__metadata_ini_offset: + .quad 0 /* INI1 base address. */ +__metadata_kernelldr_offset: + .quad 0 /* Kernel Loader base address. */ +__metadata_target_firmware: + .word 0xCCCCCCCC /* Target firmware. */ +__metadata_kernel_layout: + .word _start - __metadata_kernel_layout /* rx_offset */ + .word __rodata_start - __metadata_kernel_layout /* rx_end_offset */ + .word __rodata_start - __metadata_kernel_layout /* ro_offset */ + .word __data_start - __metadata_kernel_layout /* ro_end_offset */ + .word __data_start - __metadata_kernel_layout /* rw_offset */ + .word __bss_start__ - __metadata_kernel_layout /* rw_end_offset */ + .word __bss_start__ - __metadata_kernel_layout /* bss_offset */ + .word __bss_end__ - __metadata_kernel_layout /* bss_end_offset */ + .word __end__ - __metadata_kernel_layout /* resource_offset */ + .word _DYNAMIC - __metadata_kernel_layout /* dynamic_offset */ + .word __init_array_start - __metadata_kernel_layout /* init_array_offset */ + .word __init_array_end - __metadata_kernel_layout /* init_array_end_offset */ + .word __sysreg_constant_begin - __metadata_kernel_layout /* sysreg_offset */ +.if (. - __metadata_begin) != 0x48 + .error "Incorrect Mesosphere Metadata" +.endif + + +/* TODO: Can we remove this while retaining QEMU support? */ +#ifndef ATMOSPHERE_BOARD_NINTENDO_NX +/* ams::kern::init::JumpFromEL2ToEL1() */ +.section .crt0.text._ZN3ams4kern4init16JumpFromEL2ToEL1Ev, "ax", %progbits +.global _ZN3ams4kern4init16JumpFromEL2ToEL1Ev +.type _ZN3ams4kern4init16JumpFromEL2ToEL1Ev, %function +_ZN3ams4kern4init16JumpFromEL2ToEL1Ev: + /* We're going to want to ERET to our caller. */ + msr elr_el2, x30 + + /* Flush the entire data cache and invalidate the entire TLB. */ + bl _ZN3ams4kern4arch5arm643cpu32FlushEntireDataCacheWithoutStackEv + + /* Setup system registers for deprivileging. */ + + /* Check if we're on cortex A57 or A53. If we are, set ACTLR_EL2. */ + mrs x1, midr_el1 + + /* Is the manufacturer ID 'A' (ARM)? */ + ubfx x2, x1, #0x18, #8 + cmp x2, #0x41 + b.ne 2f + + /* Is the board ID Cortex-A57? */ + ubfx x2, x1, #4, #0xC + mov x3, #0xD07 + cmp x2, x3 + b.eq 1f + + /* Is the board ID Cortex-A53? */ + mov x3, #0xD03 + cmp x2, x3 + b.ne 2f + +1: + /* ACTLR_EL2: */ + /* - CPUACTLR access control = 1 */ + /* - CPUECTLR access control = 1 */ + /* - L2CTLR access control = 1 */ + /* - L2ECTLR access control = 1 */ + /* - L2ACTLR access control = 1 */ + mov x0, #0x73 + msr actlr_el2, x0 + +2: + /* HCR_EL2: */ + /* - RW = 1 (el1 is aarch64) */ + mov x0, #0x80000000 + msr hcr_el2, x0 + + /* SCTLR_EL1: */ + /* - EOS = 1 */ + /* - EIS = 1 */ + /* - SPAN = 1 */ + LOAD_IMMEDIATE_32(x0, 0x00C00800) + msr sctlr_el1, x0 + + /* DACR32_EL2: */ + /* - Manager access for all D<n> */ + mov x0, #0xFFFFFFFF + msr dacr32_el2, x0 + + /* Set VPIDR_EL2 = MIDR_EL1 */ + mrs x0, midr_el1 + msr vpidr_el2, x0 + + /* SET VMPIDR_EL2 = MPIDR_EL1 */ + mrs x0, mpidr_el1 + msr vmpidr_el2, x0 + + /* SPSR_EL2: */ + /* - EL1h */ + /* - IRQ masked */ + /* - FIQ masked */ + mov x0, #0xC5 + msr spsr_el2, x0 + + ERET_WITH_SPECULATION_BARRIER +#endif + +/* ams::kern::init::DisableMmuAndCaches() */ +.section .crt0.text._ZN3ams4kern4init19DisableMmuAndCachesEv, "ax", %progbits +.global _ZN3ams4kern4init19DisableMmuAndCachesEv +.type _ZN3ams4kern4init19DisableMmuAndCachesEv, %function +_ZN3ams4kern4init19DisableMmuAndCachesEv: + /* The stack isn't set up, so we'll need to trash a register. */ + mov x22, x30 + + /* Set SCTLR_EL1 to disable the caches and mmu. */ + /* SCTLR_EL1: */ + /* - M = 0 */ + /* - C = 0 */ + /* - I = 0 */ + mrs x0, sctlr_el1 + LOAD_IMMEDIATE_64(x1, ~0x1005) + and x0, x0, x1 + msr sctlr_el1, x0 + + /* Ensure instruction consistency. */ + dsb sy + isb + + mov x30, x22 + ret + +/* ams::kern::arch::arm64::cpu::FlushEntireDataCacheSharedWithoutStack() */ +.section .crt0.text._ZN3ams4kern4arch5arm643cpu38FlushEntireDataCacheSharedWithoutStackEv, "ax", %progbits +.global _ZN3ams4kern4arch5arm643cpu38FlushEntireDataCacheSharedWithoutStackEv +.type _ZN3ams4kern4arch5arm643cpu38FlushEntireDataCacheSharedWithoutStackEv, %function +_ZN3ams4kern4arch5arm643cpu38FlushEntireDataCacheSharedWithoutStackEv: + /* The stack isn't set up, so we'll need to trash a register. */ + mov x24, x30 + + /* CacheLineIdAccessor clidr_el1; */ + mrs x10, clidr_el1 + /* const int levels_of_coherency = clidr_el1.GetLevelsOfCoherency(); */ + ubfx x9, x10, #0x15, 3 + /* const int levels_of_unification = clidr_el1.GetLevelsOfUnification(); */ + ubfx x10, x10, #0x18, 3 + + /* int level = levels_of_unification */ + + /* while (level <= levels_of_coherency) { */ + cmp w9, w10 + b.hi 1f + +0: + /* FlushEntireDataCacheImplWithoutStack(level); */ + mov w0, w9 + bl _ZN3ams4kern4arch5arm643cpu36FlushEntireDataCacheImplWithoutStackEv + + /* level++; */ + cmp w9, w10 + add w9, w9, #1 + + /* } */ + b.cc 0b + + /* cpu::DataSynchronizationBarrier(); */ + dsb sy + +1: + mov x30, x24 + ret + +/* ams::kern::arch::arm64::cpu::FlushEntireDataCacheLocalWithoutStack() */ +.section .crt0.text._ZN3ams4kern4arch5arm643cpu37FlushEntireDataCacheLocalWithoutStackEv, "ax", %progbits +.global _ZN3ams4kern4arch5arm643cpu37FlushEntireDataCacheLocalWithoutStackEv +.type _ZN3ams4kern4arch5arm643cpu37FlushEntireDataCacheLocalWithoutStackEv, %function +_ZN3ams4kern4arch5arm643cpu37FlushEntireDataCacheLocalWithoutStackEv: + /* The stack isn't set up, so we'll need to trash a register. */ + mov x24, x30 + + /* CacheLineIdAccessor clidr_el1; */ + mrs x10, clidr_el1 + /* const int levels_of_unification = clidr_el1.GetLevelsOfUnification(); */ + ubfx x10, x10, #0x15, 3 + + /* int level = 0 */ + mov x9, xzr + + /* while (level <= levels_of_unification) { */ + cmp x9, x10 + b.eq 1f + +0: + /* FlushEntireDataCacheImplWithoutStack(level); */ + mov w0, w9 + bl _ZN3ams4kern4arch5arm643cpu36FlushEntireDataCacheImplWithoutStackEv + + /* level++; */ + add w9, w9, #1 + + /* } */ + cmp x9, x10 + b.ne 0b + + /* cpu::DataSynchronizationBarrier(); */ + dsb sy + +1: + mov x30, x24 + ret + +/* ams::kern::arch::arm64::cpu::FlushEntireDataCacheWithoutStack() */ +.section .crt0.text._ZN3ams4kern4arch5arm643cpu32FlushEntireDataCacheWithoutStackEv, "ax", %progbits +.global _ZN3ams4kern4arch5arm643cpu32FlushEntireDataCacheWithoutStackEv +.type _ZN3ams4kern4arch5arm643cpu32FlushEntireDataCacheWithoutStackEv, %function +_ZN3ams4kern4arch5arm643cpu32FlushEntireDataCacheWithoutStackEv: + /* The stack isn't set up, so we'll need to trash a register. */ + mov x23, x30 + + /* Ensure that the cache is coherent. */ + bl _ZN3ams4kern4arch5arm643cpu37FlushEntireDataCacheLocalWithoutStackEv + + bl _ZN3ams4kern4arch5arm643cpu38FlushEntireDataCacheSharedWithoutStackEv + + bl _ZN3ams4kern4arch5arm643cpu37FlushEntireDataCacheLocalWithoutStackEv + + /* Invalidate the entire TLB, and ensure instruction consistency. */ + tlbi vmalle1is + dsb sy + isb + + mov x30, x23 + ret + +/* ams::kern::arch::arm64::cpu::FlushEntireDataCacheImplWithoutStack() */ +.section .crt0.text._ZN3ams4kern4arch5arm643cpu36FlushEntireDataCacheImplWithoutStackEv, "ax", %progbits +.global _ZN3ams4kern4arch5arm643cpu36FlushEntireDataCacheImplWithoutStackEv +.type _ZN3ams4kern4arch5arm643cpu36FlushEntireDataCacheImplWithoutStackEv, %function +_ZN3ams4kern4arch5arm643cpu36FlushEntireDataCacheImplWithoutStackEv: + /* const u64 level_sel_value = static_cast<u64>(level << 1); */ + lsl w6, w0, #1 + sxtw x6, w6 + + /* cpu::DataSynchronizationBarrier(); */ + dsb sy + + /* cpu::SetCsselrEl1(level_sel_value); */ + msr csselr_el1, x6 + + /* cpu::InstructionMemoryBarrier(); */ + isb + + /* CacheSizeIdAccessor ccsidr_el1; */ + mrs x3, ccsidr_el1 + + /* const int num_ways = ccsidr_el1.GetAssociativity(); */ + ubfx x7, x3, #3, #0xA + mov w8, w7 + + /* const int line_size = ccsidr_el1.GetLineSize(); */ + and x4, x3, #7 + + /* const u64 way_shift = static_cast<u64>(__builtin_clz(num_ways)); */ + clz w7, w7 + + /* const u64 set_shift = static_cast<u64>(line_size + 4); */ + add w4, w4, #4 + + /* const int num_sets = ccsidr_el1.GetNumberOfSets(); */ + ubfx w3, w3, #0xD, #0xF + + /* int way = 0; */ + mov x5, #0 + + /* while (way <= num_ways) { */ +0: + cmp w8, w5 + b.lt 3f + + /* int set = 0; */ + mov x0, #0 + + /* while (set <= num_sets) { */ +1: + cmp w3, w0 + b.lt 2f + + /* const u64 cisw_value = (static_cast<u64>(way) << way_shift) | (static_cast<u64>(set) << set_shift) | level_sel_value; */ + lsl x2, x5, x7 + lsl x1, x0, x4 + orr x1, x1, x2 + orr x1, x1, x6 + + /* __asm__ __volatile__("dc cisw, %0" :: "r"(cisw_value) : "memory"); */ + dc cisw, x1 + + /* set++; */ + add x0, x0, #1 + + /* } */ + b 1b +2: + + /* way++; */ + add x5, x5, 1 + + /* } */ + b 0b +3: + ret + + +/* System register values. */ +.align 8 + +__sysreg_constant_begin: +__sysreg_constant_ttbr0_el1: + .quad 0 /* ttbr0_e11. */ +__sysreg_constant_ttbr1_el1: + .quad 0 /* ttbr1_e11. */ +__sysreg_constant_tcr_el1: + .quad 0 /* tcr_e11. */ +__sysreg_constant_mair_el1: + .quad 0 /* mair_e11. */ +__sysreg_constant_sctlr_el1: + .quad 0 /* sctlr_e11. */ +.if (. - __sysreg_constant_begin) != 0x28 + .error "Incorrect System Registers" +.endif + +/* ================ Functions before this line remain identity-mapped after initialization finishes. ================ */ + +/* ams::kern::init::IdentityMappedFunctionAreaEnd() */ +.global _ZN3ams4kern4init29IdentityMappedFunctionAreaEndEv +.type _ZN3ams4kern4init31IdentityMappedFunctionAreaEndEv, %function +_ZN3ams4kern4init29IdentityMappedFunctionAreaEndEv: +/* NOTE: This is not a real function, and only exists as a label for safety. */ \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/kern_exception_handlers_asm.s b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/kern_exception_handlers_asm.s new file mode 100644 index 00000000..5a6b3e7c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/kern_exception_handlers_asm.s @@ -0,0 +1,659 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere/kern_select_assembly_macros.h> + +/* ams::kern::arch::arm64::EL1IrqExceptionHandler() */ +.section .text._ZN3ams4kern4arch5arm6422EL1IrqExceptionHandlerEv, "ax", %progbits +.global _ZN3ams4kern4arch5arm6422EL1IrqExceptionHandlerEv +.type _ZN3ams4kern4arch5arm6422EL1IrqExceptionHandlerEv, %function +_ZN3ams4kern4arch5arm6422EL1IrqExceptionHandlerEv: + /* Save registers that need saving. */ + sub sp, sp, #(8 * 24) + + stp x0, x1, [sp, #(8 * 0)] + stp x2, x3, [sp, #(8 * 2)] + stp x4, x5, [sp, #(8 * 4)] + stp x6, x7, [sp, #(8 * 6)] + stp x8, x9, [sp, #(8 * 8)] + stp x10, x11, [sp, #(8 * 10)] + stp x12, x13, [sp, #(8 * 12)] + stp x14, x15, [sp, #(8 * 14)] + stp x16, x17, [sp, #(8 * 16)] + stp x19, x20, [sp, #(8 * 18)] + stp x21, x30, [sp, #(8 * 20)] + + mrs x19, sp_el0 + mrs x20, elr_el1 + mrs x21, spsr_el1 + mov w21, w21 + + /* Invoke KInterruptManager::HandleInterrupt(bool user_mode). */ + mov x0, #0 + bl _ZN3ams4kern4arch5arm6417KInterruptManager15HandleInterruptEb + + /* Restore registers that we saved. */ + msr sp_el0, x19 + msr elr_el1, x20 + msr spsr_el1, x21 + + ldp x0, x1, [sp, #(8 * 0)] + ldp x2, x3, [sp, #(8 * 2)] + ldp x4, x5, [sp, #(8 * 4)] + ldp x6, x7, [sp, #(8 * 6)] + ldp x8, x9, [sp, #(8 * 8)] + ldp x10, x11, [sp, #(8 * 10)] + ldp x12, x13, [sp, #(8 * 12)] + ldp x14, x15, [sp, #(8 * 14)] + ldp x16, x17, [sp, #(8 * 16)] + ldp x19, x20, [sp, #(8 * 18)] + ldp x21, x30, [sp, #(8 * 20)] + + add sp, sp, #(8 * 24) + + /* Return from the exception. */ + ERET_WITH_SPECULATION_BARRIER + +/* ams::kern::arch::arm64::EL0A64IrqExceptionHandler() */ +.section .text._ZN3ams4kern4arch5arm6425EL0A64IrqExceptionHandlerEv, "ax", %progbits +.global _ZN3ams4kern4arch5arm6425EL0A64IrqExceptionHandlerEv +.type _ZN3ams4kern4arch5arm6425EL0A64IrqExceptionHandlerEv, %function +_ZN3ams4kern4arch5arm6425EL0A64IrqExceptionHandlerEv: + /* Save registers that need saving. */ + sub sp, sp, #(EXCEPTION_CONTEXT_SIZE) + + stp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + stp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + stp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)] + stp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)] + stp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)] + stp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)] + stp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)] + stp x14, x15, [sp, #(EXCEPTION_CONTEXT_X14_X15)] + stp x16, x17, [sp, #(EXCEPTION_CONTEXT_X16_X17)] + stp x18, x19, [sp, #(EXCEPTION_CONTEXT_X18_X19)] + stp x20, x21, [sp, #(EXCEPTION_CONTEXT_X20_X21)] + stp x22, x23, [sp, #(EXCEPTION_CONTEXT_X22_X23)] + stp x24, x25, [sp, #(EXCEPTION_CONTEXT_X24_X25)] + stp x26, x27, [sp, #(EXCEPTION_CONTEXT_X26_X27)] + stp x28, x29, [sp, #(EXCEPTION_CONTEXT_X28_X29)] + + mrs x20, sp_el0 + mrs x21, elr_el1 + mrs x22, spsr_el1 + mrs x23, tpidr_el0 + mov w22, w22 + + stp x30, x20, [sp, #(EXCEPTION_CONTEXT_X30_SP)] + stp x21, x22, [sp, #(EXCEPTION_CONTEXT_PC_PSR)] + str x23, [sp, #(EXCEPTION_CONTEXT_TPIDR)] + + /* Invoke KInterruptManager::HandleInterrupt(bool user_mode). */ + ldr x18, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_CUR_THREAD)] + mov x0, #1 + bl _ZN3ams4kern4arch5arm6417KInterruptManager15HandleInterruptEb + + /* If we don't need to restore the fpu, skip restoring it. */ + ldrb w1, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + tbz w1, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_CONTEXT_RESTORE_NEEDED), 1f + + /* Clear the needs-fpu-restore flag. */ + and w1, w1, #(~THREAD_EXCEPTION_FLAG_IS_FPU_CONTEXT_RESTORE_NEEDED) + strb w1, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + + /* Perform a full fpu restore. */ + ENABLE_AND_RESTORE_FPU64(x2, x0, x1, w0, w1) + +1: /* Restore state from the context. */ + ldp x30, x20, [sp, #(EXCEPTION_CONTEXT_X30_SP)] + ldp x21, x22, [sp, #(EXCEPTION_CONTEXT_PC_PSR)] + ldr x23, [sp, #(EXCEPTION_CONTEXT_TPIDR)] + + #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) + /* Since we're returning from an exception, set SPSR.SS so that we advance an instruction if single-stepping. */ + orr x22, x22, #(1 << 21) + #endif + + msr sp_el0, x20 + msr elr_el1, x21 + msr spsr_el1, x22 + msr tpidr_el0, x23 + + ldp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + ldp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + ldp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)] + ldp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)] + ldp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)] + ldp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)] + ldp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)] + ldp x14, x15, [sp, #(EXCEPTION_CONTEXT_X14_X15)] + ldp x16, x17, [sp, #(EXCEPTION_CONTEXT_X16_X17)] + ldp x18, x19, [sp, #(EXCEPTION_CONTEXT_X18_X19)] + ldp x20, x21, [sp, #(EXCEPTION_CONTEXT_X20_X21)] + ldp x22, x23, [sp, #(EXCEPTION_CONTEXT_X22_X23)] + ldp x24, x25, [sp, #(EXCEPTION_CONTEXT_X24_X25)] + ldp x26, x27, [sp, #(EXCEPTION_CONTEXT_X26_X27)] + ldp x28, x29, [sp, #(EXCEPTION_CONTEXT_X28_X29)] + + add sp, sp, #(EXCEPTION_CONTEXT_SIZE) + + /* Return from the exception. */ + ERET_WITH_SPECULATION_BARRIER + +/* ams::kern::arch::arm64::EL0A32IrqExceptionHandler() */ +.section .text._ZN3ams4kern4arch5arm6425EL0A32IrqExceptionHandlerEv, "ax", %progbits +.global _ZN3ams4kern4arch5arm6425EL0A32IrqExceptionHandlerEv +.type _ZN3ams4kern4arch5arm6425EL0A32IrqExceptionHandlerEv, %function +_ZN3ams4kern4arch5arm6425EL0A32IrqExceptionHandlerEv: + /* Save registers that need saving. */ + sub sp, sp, #(EXCEPTION_CONTEXT_SIZE) + + stp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + stp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + stp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)] + stp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)] + stp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)] + stp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)] + stp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)] + stp x14, x15, [sp, #(EXCEPTION_CONTEXT_X14_X15)] + + mrs x21, elr_el1 + mrs x22, spsr_el1 + mrs x23, tpidr_el0 + mov w22, w22 + + stp x21, x22, [sp, #(EXCEPTION_CONTEXT_PC_PSR)] + str x23, [sp, #(EXCEPTION_CONTEXT_TPIDR)] + + /* Invoke KInterruptManager::HandleInterrupt(bool user_mode). */ + ldr x18, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_CUR_THREAD)] + mov x0, #1 + bl _ZN3ams4kern4arch5arm6417KInterruptManager15HandleInterruptEb + + /* If we don't need to restore the fpu, skip restoring it. */ + ldrb w1, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + tbz w1, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_CONTEXT_RESTORE_NEEDED), 1f + + /* Clear the needs-fpu-restore flag. */ + and w1, w1, #(~THREAD_EXCEPTION_FLAG_IS_FPU_CONTEXT_RESTORE_NEEDED) + strb w1, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + + /* Perform a full fpu restore. */ + ENABLE_AND_RESTORE_FPU32(x2, x0, x1, w0, w1) + +1: /* Restore state from the context. */ + ldp x21, x22, [sp, #(EXCEPTION_CONTEXT_PC_PSR)] + ldr x23, [sp, #(EXCEPTION_CONTEXT_TPIDR)] + + #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) + /* Since we're returning from an exception, set SPSR.SS so that we advance an instruction if single-stepping. */ + orr x22, x22, #(1 << 21) + #endif + + msr elr_el1, x21 + msr spsr_el1, x22 + msr tpidr_el0, x23 + + ldp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + ldp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + ldp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)] + ldp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)] + ldp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)] + ldp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)] + ldp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)] + ldp x14, x15, [sp, #(EXCEPTION_CONTEXT_X14_X15)] + + add sp, sp, #(EXCEPTION_CONTEXT_SIZE) + + /* Return from the exception. */ + ERET_WITH_SPECULATION_BARRIER + +/* ams::kern::arch::arm64::EL0SynchronousExceptionHandler() */ +.section .text._ZN3ams4kern4arch5arm6430EL0SynchronousExceptionHandlerEv, "ax", %progbits +.global _ZN3ams4kern4arch5arm6430EL0SynchronousExceptionHandlerEv +.type _ZN3ams4kern4arch5arm6430EL0SynchronousExceptionHandlerEv, %function +_ZN3ams4kern4arch5arm6430EL0SynchronousExceptionHandlerEv: + /* Save x16 and x17, so that we can use them as scratch. */ + stp x16, x17, [sp, #-16]! + + /* Get and parse the exception syndrome register. */ + mrs x16, esr_el1 + lsr x17, x16, #0x1a + + /* Is this an aarch32 SVC? */ + cmp x17, #0x11 + b.eq 4f + + /* Is this an aarch64 SVC? */ + cmp x17, #0x15 + b.eq 5f + + /* Is this an FPU error? */ + cmp x17, #0x7 + b.eq 6f + + /* Is this a data abort? */ + cmp x17, #0x24 + b.eq 7f + + /* Is this an instruction abort? */ + cmp x17, #0x20 + b.eq 7f + +1: /* The exception is not a data abort or instruction abort caused by a TLB conflict. */ + /* It is also not an SVC or an FPU exception. Handle it generically! */ + + /* Restore x16 and x17. */ + ldp x16, x17, [sp], 16 + + /* Create a KExceptionContext to pass to HandleException. */ + sub sp, sp, #(EXCEPTION_CONTEXT_SIZE) + stp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + stp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + stp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)] + stp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)] + stp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)] + stp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)] + stp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)] + stp x14, x15, [sp, #(EXCEPTION_CONTEXT_X14_X15)] + stp x16, x17, [sp, #(EXCEPTION_CONTEXT_X16_X17)] + stp x18, x19, [sp, #(EXCEPTION_CONTEXT_X18_X19)] + stp x20, x21, [sp, #(EXCEPTION_CONTEXT_X20_X21)] + stp x22, x23, [sp, #(EXCEPTION_CONTEXT_X22_X23)] + stp x24, x25, [sp, #(EXCEPTION_CONTEXT_X24_X25)] + stp x26, x27, [sp, #(EXCEPTION_CONTEXT_X26_X27)] + stp x28, x29, [sp, #(EXCEPTION_CONTEXT_X28_X29)] + + mrs x20, sp_el0 + mrs x21, elr_el1 + mrs x22, spsr_el1 + mrs x23, tpidr_el0 + mov w22, w22 + + stp x30, x20, [sp, #(EXCEPTION_CONTEXT_X30_SP)] + stp x21, x22, [sp, #(EXCEPTION_CONTEXT_PC_PSR)] + str x23, [sp, #(EXCEPTION_CONTEXT_TPIDR)] + + /* Call ams::kern::arch::arm64::HandleException(ams::kern::arch::arm64::KExceptionContext *) */ + ldr x18, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_CUR_THREAD)] + mov x0, sp + bl _ZN3ams4kern4arch5arm6415HandleExceptionEPNS2_17KExceptionContextE + + /* If we don't need to restore the fpu, skip restoring it. */ + ldrb w1, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + tbz w1, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_CONTEXT_RESTORE_NEEDED), 3f + + /* Clear the needs-fpu-restore flag. */ + and w1, w1, #(~THREAD_EXCEPTION_FLAG_IS_FPU_CONTEXT_RESTORE_NEEDED) + strb w1, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + + /* Enable and restore the fpu. */ + ENABLE_AND_RESTORE_FPU(x2, x0, x1, w0, w1, 2, 3) + + /* Restore state from the context. */ + ldp x30, x20, [sp, #(EXCEPTION_CONTEXT_X30_SP)] + ldp x21, x22, [sp, #(EXCEPTION_CONTEXT_PC_PSR)] + ldr x23, [sp, #(EXCEPTION_CONTEXT_TPIDR)] + + msr sp_el0, x20 + msr elr_el1, x21 + msr spsr_el1, x22 + msr tpidr_el0, x23 + + ldp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + ldp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + ldp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)] + ldp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)] + ldp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)] + ldp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)] + ldp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)] + ldp x14, x15, [sp, #(EXCEPTION_CONTEXT_X14_X15)] + ldp x16, x17, [sp, #(EXCEPTION_CONTEXT_X16_X17)] + ldp x18, x19, [sp, #(EXCEPTION_CONTEXT_X18_X19)] + ldp x20, x21, [sp, #(EXCEPTION_CONTEXT_X20_X21)] + ldp x22, x23, [sp, #(EXCEPTION_CONTEXT_X22_X23)] + ldp x24, x25, [sp, #(EXCEPTION_CONTEXT_X24_X25)] + ldp x26, x27, [sp, #(EXCEPTION_CONTEXT_X26_X27)] + ldp x28, x29, [sp, #(EXCEPTION_CONTEXT_X28_X29)] + + add sp, sp, #(EXCEPTION_CONTEXT_SIZE) + + /* Return from the exception. */ + ERET_WITH_SPECULATION_BARRIER + +4: /* SVC from aarch32. */ + ldp x16, x17, [sp], 16 + b _ZN3ams4kern4arch5arm6412SvcHandler32Ev + +5: /* SVC from aarch64. */ + ldp x16, x17, [sp], 16 + b _ZN3ams4kern4arch5arm6412SvcHandler64Ev + +6: /* FPU exception. */ + ldp x16, x17, [sp], 16 + b _ZN3ams4kern4arch5arm6425FpuAccessExceptionHandlerEv + +7: /* Check if there's a TLB conflict that caused the abort. */ + and x17, x16, #0x3F + cmp x17, #0x30 + b.ne 1b + + /* Get the ASID in x17. */ + mrs x17, ttbr0_el1 + and x17, x17, #(0xFFFF << 48) + + /* Invalidate the address held by FAR (and assume it is valid). */ + mrs x16, far_el1 + lsr x16, x16, #12 + orr x17, x16, x17 + tlbi vae1, x17 + + /* Ensure instruction consistency. */ + dsb ish + isb + + /* Restore x16 and x17. */ + ldp x16, x17, [sp], 16 + + /* Return from the exception. */ + ERET_WITH_SPECULATION_BARRIER + + +/* ams::kern::arch::arm64::EL1SynchronousExceptionHandler() */ +.section .text._ZN3ams4kern4arch5arm6430EL1SynchronousExceptionHandlerEv, "ax", %progbits +.global _ZN3ams4kern4arch5arm6430EL1SynchronousExceptionHandlerEv +.type _ZN3ams4kern4arch5arm6430EL1SynchronousExceptionHandlerEv, %function +_ZN3ams4kern4arch5arm6430EL1SynchronousExceptionHandlerEv: + /* Nintendo uses tpidr_el1 as a scratch register. */ + msr tpidr_el1, x0 + + /* Get and parse the exception syndrome register. */ + mrs x0, esr_el1 + lsr x0, x0, #0x1a + + /* Is this an instruction abort? */ + cmp x0, #0x21 + b.eq 4f + + /* Is this a data abort? */ + cmp x0, #0x25 + b.eq 4f + +1: /* The exception is not a data abort or instruction abort caused by a TLB conflict. */ + /* Load the exception stack top from otherwise "unused" virtual timer compare value. */ + mrs x0, cntv_cval_el0 + + /* Setup the stack for a generic exception handle */ + lsl x0, x0, #8 + asr x0, x0, #8 + sub x0, x0, #0x20 + str x1, [x0, #8] + mov x1, sp + str x1, [x0] + mov sp, x0 + ldr x1, [x0, #8] + mrs x0, tpidr_el1 + str x0, [sp, #8] + str x1, [sp, #16] + + /* Check again if this is a data abort from EL1. */ + mrs x0, esr_el1 + lsr x1, x0, #0x1a + cmp x1, #0x25 + b.ne 2f + + /* Data abort. Check if it was from trying to access userspace memory. */ + mrs x1, elr_el1 + adr x0, _ZN3ams4kern4arch5arm6432UserspaceAccessFunctionAreaBeginEv + cmp x1, x0 + b.lo 2f + adr x0, _ZN3ams4kern4arch5arm6430UserspaceAccessFunctionAreaEndEv + cmp x1, x0 + b.hs 2f + + /* We aborted trying to access userspace memory. */ + /* All functions that access user memory return a boolean for whether they succeeded. */ + /* With that in mind, we can simply restore the stack pointer and return false directly. */ + ldr x0, [sp] + mov sp, x0 + + /* Return false. */ + mov x0, #0x0 + msr elr_el1, x30 + ERET_WITH_SPECULATION_BARRIER + +2: /* The exception wasn't an triggered by copying memory from userspace. */ + /* NOTE: The following is, as of 19.0.0, now ifdef'd out on NX non-debug kernel. */ + ldr x0, [sp, #8] + ldr x1, [sp, #16] + + /* Create a KExceptionContext to pass to HandleException. */ + sub sp, sp, #(EXCEPTION_CONTEXT_SIZE) + + stp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + stp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + stp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)] + stp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)] + stp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)] + stp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)] + stp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)] + stp x14, x15, [sp, #(EXCEPTION_CONTEXT_X14_X15)] + stp x16, x17, [sp, #(EXCEPTION_CONTEXT_X16_X17)] + stp x18, x19, [sp, #(EXCEPTION_CONTEXT_X18_X19)] + stp x20, x21, [sp, #(EXCEPTION_CONTEXT_X20_X21)] + stp x22, x23, [sp, #(EXCEPTION_CONTEXT_X22_X23)] + stp x24, x25, [sp, #(EXCEPTION_CONTEXT_X24_X25)] + stp x26, x27, [sp, #(EXCEPTION_CONTEXT_X26_X27)] + stp x28, x29, [sp, #(EXCEPTION_CONTEXT_X28_X29)] + + ldr x20, [sp] + mrs x21, elr_el1 + mrs x22, spsr_el1 + mrs x23, tpidr_el0 + mov w22, w22 + + stp x30, x20, [sp, #(EXCEPTION_CONTEXT_X30_SP)] + stp x21, x22, [sp, #(EXCEPTION_CONTEXT_PC_PSR)] + str x23, [sp, #(EXCEPTION_CONTEXT_TPIDR)] + + /* Call ams::kern::arch::arm64::HandleException(ams::kern::arch::arm64::KExceptionContext *) */ + mov x0, sp + bl _ZN3ams4kern4arch5arm6415HandleExceptionEPNS2_17KExceptionContextE + +3: /* HandleException should never return. The best we can do is infinite loop. */ + b 3b + +4: /* Check if there's a TLB conflict that caused the abort. */ + mrs x0, esr_el1 + and x0, x0, #0x3F + cmp x0, #0x30 + b.ne 1b + + /* Invalidate the address held by FAR (and assume it is valid). */ + mrs x0, far_el1 + lsr x0, x0, #12 + tlbi vaae1, x0 + + /* Ensure instruction consistency. */ + dsb ish + isb + + /* Restore x0 from scratch. */ + mrs x0, tpidr_el1 + + /* Return from the exception. */ + ERET_WITH_SPECULATION_BARRIER + + +/* ams::kern::arch::arm64::FpuAccessExceptionHandler() */ +.section .text._ZN3ams4kern4arch5arm6425FpuAccessExceptionHandlerEv, "ax", %progbits +.global _ZN3ams4kern4arch5arm6425FpuAccessExceptionHandlerEv +.type _ZN3ams4kern4arch5arm6425FpuAccessExceptionHandlerEv, %function +_ZN3ams4kern4arch5arm6425FpuAccessExceptionHandlerEv: + /* Save registers. */ + sub sp, sp, #(EXCEPTION_CONTEXT_SIZE) + + stp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + stp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + + ENABLE_AND_RESTORE_FPU(x2, x0, x1, w0, w1, 1, 2) + + /* Restore registers. */ + ldp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + ldp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + + add sp, sp, #(EXCEPTION_CONTEXT_SIZE) + + /* Return from the exception. */ + ERET_WITH_SPECULATION_BARRIER + +/* ams::kern::arch::arm64::EL1SystemErrorHandler() */ +.section .text._ZN3ams4kern4arch5arm6421EL1SystemErrorHandlerEv, "ax", %progbits +.global _ZN3ams4kern4arch5arm6421EL1SystemErrorHandlerEv +.type _ZN3ams4kern4arch5arm6421EL1SystemErrorHandlerEv, %function +_ZN3ams4kern4arch5arm6421EL1SystemErrorHandlerEv: + /* Nintendo uses tpidr_el1 as a scratch register. */ + msr tpidr_el1, x0 + + /* Load the exception stack top from otherwise "unused" virtual timer compare value. */ + mrs x0, cntv_cval_el0 + + /* Setup the stack for a generic exception handle */ + lsl x0, x0, #8 + asr x0, x0, #8 + sub x0, x0, #0x20 + str x1, [x0, #8] + mov x1, sp + str x1, [x0] + mov sp, x0 + ldr x1, [x0, #8] + mrs x0, tpidr_el1 + + /* Create a KExceptionContext to pass to HandleException. */ + sub sp, sp, #(EXCEPTION_CONTEXT_SIZE) + stp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + stp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + stp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)] + stp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)] + stp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)] + stp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)] + stp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)] + stp x14, x15, [sp, #(EXCEPTION_CONTEXT_X14_X15)] + stp x16, x17, [sp, #(EXCEPTION_CONTEXT_X16_X17)] + stp x18, x19, [sp, #(EXCEPTION_CONTEXT_X18_X19)] + stp x20, x21, [sp, #(EXCEPTION_CONTEXT_X20_X21)] + stp x22, x23, [sp, #(EXCEPTION_CONTEXT_X22_X23)] + stp x24, x25, [sp, #(EXCEPTION_CONTEXT_X24_X25)] + stp x26, x27, [sp, #(EXCEPTION_CONTEXT_X26_X27)] + stp x28, x29, [sp, #(EXCEPTION_CONTEXT_X28_X29)] + + ldr x20, [sp] + mrs x21, elr_el1 + mrs x22, spsr_el1 + mrs x23, tpidr_el0 + mov w22, w22 + + stp x30, x20, [sp, #(EXCEPTION_CONTEXT_X30_SP)] + stp x21, x22, [sp, #(EXCEPTION_CONTEXT_PC_PSR)] + str x23, [sp, #(EXCEPTION_CONTEXT_TPIDR)] + + /* Invoke ams::kern::arch::arm64::HandleException(ams::kern::arch::arm64::KExceptionContext *). */ + mov x0, sp + bl _ZN3ams4kern4arch5arm6415HandleExceptionEPNS2_17KExceptionContextE + +1: /* HandleException should never return. The best we can do is infinite loop. */ + b 1b + +/* ams::kern::arch::arm64::EL0SystemErrorHandler() */ +.section .text._ZN3ams4kern4arch5arm6421EL0SystemErrorHandlerEv, "ax", %progbits +.global _ZN3ams4kern4arch5arm6421EL0SystemErrorHandlerEv +.type _ZN3ams4kern4arch5arm6421EL0SystemErrorHandlerEv, %function +_ZN3ams4kern4arch5arm6421EL0SystemErrorHandlerEv: + /* Create a KExceptionContext to pass to HandleException. */ + sub sp, sp, #(EXCEPTION_CONTEXT_SIZE) + + stp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + stp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + stp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)] + stp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)] + stp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)] + stp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)] + stp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)] + stp x14, x15, [sp, #(EXCEPTION_CONTEXT_X14_X15)] + stp x16, x17, [sp, #(EXCEPTION_CONTEXT_X16_X17)] + stp x18, x19, [sp, #(EXCEPTION_CONTEXT_X18_X19)] + stp x20, x21, [sp, #(EXCEPTION_CONTEXT_X20_X21)] + stp x22, x23, [sp, #(EXCEPTION_CONTEXT_X22_X23)] + stp x24, x25, [sp, #(EXCEPTION_CONTEXT_X24_X25)] + stp x26, x27, [sp, #(EXCEPTION_CONTEXT_X26_X27)] + stp x28, x29, [sp, #(EXCEPTION_CONTEXT_X28_X29)] + + mrs x20, sp_el0 + mrs x21, elr_el1 + mrs x22, spsr_el1 + mrs x23, tpidr_el0 + mov w22, w22 + + stp x30, x20, [sp, #(EXCEPTION_CONTEXT_X30_SP)] + stp x21, x22, [sp, #(EXCEPTION_CONTEXT_PC_PSR)] + str x23, [sp, #(EXCEPTION_CONTEXT_TPIDR)] + + /* Invoke ams::kern::arch::arm64::HandleException(ams::kern::arch::arm64::KExceptionContext *). */ + ldr x18, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_CUR_THREAD)] + mov x0, sp + bl _ZN3ams4kern4arch5arm6415HandleExceptionEPNS2_17KExceptionContextE + + /* If we don't need to restore the fpu, skip restoring it. */ + ldrb w1, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + tbz w1, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_CONTEXT_RESTORE_NEEDED), 2f + + /* Clear the needs-fpu-restore flag. */ + and w1, w1, #(~THREAD_EXCEPTION_FLAG_IS_FPU_CONTEXT_RESTORE_NEEDED) + strb w1, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + + /* Enable and restore the fpu. */ + ENABLE_AND_RESTORE_FPU(x2, x0, x1, w0, w1, 1, 2) + + /* Restore state from the context. */ + ldp x30, x20, [sp, #(EXCEPTION_CONTEXT_X30_SP)] + ldp x21, x22, [sp, #(EXCEPTION_CONTEXT_PC_PSR)] + ldr x23, [sp, #(EXCEPTION_CONTEXT_TPIDR)] + + msr sp_el0, x20 + msr elr_el1, x21 + msr spsr_el1, x22 + msr tpidr_el0, x23 + + ldp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + ldp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + ldp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)] + ldp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)] + ldp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)] + ldp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)] + ldp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)] + ldp x14, x15, [sp, #(EXCEPTION_CONTEXT_X14_X15)] + ldp x16, x17, [sp, #(EXCEPTION_CONTEXT_X16_X17)] + ldp x18, x19, [sp, #(EXCEPTION_CONTEXT_X18_X19)] + ldp x20, x21, [sp, #(EXCEPTION_CONTEXT_X20_X21)] + ldp x22, x23, [sp, #(EXCEPTION_CONTEXT_X22_X23)] + ldp x24, x25, [sp, #(EXCEPTION_CONTEXT_X24_X25)] + ldp x26, x27, [sp, #(EXCEPTION_CONTEXT_X26_X27)] + ldp x28, x29, [sp, #(EXCEPTION_CONTEXT_X28_X29)] + + add sp, sp, #(EXCEPTION_CONTEXT_SIZE) + + /* Return from the exception. */ + ERET_WITH_SPECULATION_BARRIER + diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/kern_k_scheduler_asm.s b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/kern_k_scheduler_asm.s new file mode 100644 index 00000000..8fe2beb7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/kern_k_scheduler_asm.s @@ -0,0 +1,283 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere/kern_select_assembly_macros.h> + +/* ams::kern::KScheduler::ScheduleImpl() */ +.section .text._ZN3ams4kern10KScheduler12ScheduleImplEv, "ax", %progbits +.global _ZN3ams4kern10KScheduler12ScheduleImplEv +.type _ZN3ams4kern10KScheduler12ScheduleImplEv, %function + +/* Ensure ScheduleImpl is aligned to 0x40 bytes. */ +.balign 0x40 + +_ZN3ams4kern10KScheduler12ScheduleImplEv: + /* Right now, x0 contains (this). We want x1 to point to the scheduling state, */ + /* KScheduler layout has state at +0x0, this is guaranteed statically by assembly offsets. */ + mov x1, x0 + + /* First, clear the need's scheduling bool (and dmb ish after, as it's an atomic). */ + strb wzr, [x1] + dmb ish + + /* Check whether there are runnable interrupt tasks. */ + ldrb w8, [x1, #(KSCHEDULER_INTERRUPT_TASK_RUNNABLE)] + cbnz w8, 0f + + /* If it isn't, we want to check if the highest priority thread is the same as the current thread. */ + ldr x7, [x1, #(KSCHEDULER_HIGHEST_PRIORITY_THREAD)] + cmp x7, x18 + b.ne 1f + + /* If they're the same, then we can just issue a memory barrier and return. */ + dmb ish + ret + +0: /* The interrupt task thread is runnable. */ + /* We want to switch to the interrupt task/idle thread. */ + mov x7, #0 + +1: /* The highest priority thread is not the same as the current thread. */ + /* Get a reference to the current thread's stack parameters. */ + add x2, sp, #0x1000 + and x2, x2, #~(0x1000-1) + sub x2, x2, #(THREAD_STACK_PARAMETERS_SIZE) + + /* Get a reference to the current thread's context. */ + add x3, x2, #(THREAD_STACK_PARAMETERS_THREAD_CONTEXT) + + /* Save the callee-save registers + SP. */ + stp x19, x20, [x3, #(THREAD_CONTEXT_X19_X20)] + stp x21, x22, [x3, #(THREAD_CONTEXT_X21_X22)] + stp x23, x24, [x3, #(THREAD_CONTEXT_X23_X24)] + stp x25, x26, [x3, #(THREAD_CONTEXT_X25_X26)] + stp x27, x28, [x3, #(THREAD_CONTEXT_X27_X28)] + stp x29, x30, [x3, #(THREAD_CONTEXT_X29_X30)] + + mov x4, sp + str x4, [x3, #(THREAD_CONTEXT_SP)] + + /* Check if the fpu is enabled; if it is, we need to save it. */ + mrs x5, cpacr_el1 + and x4, x5, #0x300000 + cbz x4, 8f + + /* We need to save the fpu state; save fpsr/fpcr. */ + mrs x4, fpcr + mrs x6, fpsr + stp w4, w6, [x3, #(THREAD_CONTEXT_FPCR_FPSR)] + + /* Set fpu-restore-needed in our exception flags. */ + ldrb w4, [x2, #(THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + orr w4, w4, #(THREAD_EXCEPTION_FLAG_IS_FPU_CONTEXT_RESTORE_NEEDED) + strb w4, [x2, #(THREAD_STACK_PARAMETERS_EXCEPTION_FLAGS)] + + /* We need to save fpu state based on whether we're a 64-bit or 32-bit thread. */ + tbz w4, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_FPU_64_BIT), 4f + + /* We have a 64-bit fpu. */ + + /* If we're in a usermode exception, we need to save the caller-save fpu registers. */ + tbz w4, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_IN_USERMODE_EXCEPTION_HANDLER), 2f + + /* If we're in an SVC (and not a usermode exception), we only need to save the callee-save fpu registers. */ + tbz w4, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_CALLING_SVC), 3f + +2: /* Save the 64-bit caller-save fpu registers. */ + ldr x6, [x2, #(THREAD_STACK_PARAMETERS_CALLER_SAVE_FPU_REGISTERS)] + stp q0, q1, [x6, #(THREAD_FPU64_CONTEXT_Q0_Q1)] + stp q2, q3, [x6, #(THREAD_FPU64_CONTEXT_Q2_Q3)] + stp q4, q5, [x6, #(THREAD_FPU64_CONTEXT_Q4_Q5)] + stp q6, q7, [x6, #(THREAD_FPU64_CONTEXT_Q6_Q7)] + stp q16, q17, [x6, #(THREAD_FPU64_CONTEXT_Q16_Q17)] + stp q18, q19, [x6, #(THREAD_FPU64_CONTEXT_Q18_Q19)] + stp q20, q21, [x6, #(THREAD_FPU64_CONTEXT_Q20_Q21)] + stp q22, q23, [x6, #(THREAD_FPU64_CONTEXT_Q22_Q23)] + stp q24, q25, [x6, #(THREAD_FPU64_CONTEXT_Q24_Q25)] + stp q26, q27, [x6, #(THREAD_FPU64_CONTEXT_Q26_Q27)] + stp q28, q29, [x6, #(THREAD_FPU64_CONTEXT_Q28_Q29)] + stp q30, q31, [x6, #(THREAD_FPU64_CONTEXT_Q30_Q31)] + +3: /* Save the 64-bit callee-save fpu registers. */ + stp q8, q9, [x3, #(THREAD_CONTEXT_FPU64_Q8_Q9)] + stp q10, q11, [x3, #(THREAD_CONTEXT_FPU64_Q10_Q11)] + stp q12, q13, [x3, #(THREAD_CONTEXT_FPU64_Q12_Q13)] + stp q14, q15, [x3, #(THREAD_CONTEXT_FPU64_Q14_Q15)] + b 7f + +4: /* We have a 32-bit fpu. */ + + /* If we're in a usermode exception, we need to save the caller-save fpu registers. */ + tbz w4, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_IN_USERMODE_EXCEPTION_HANDLER), 5f + + /* If we're in an SVC (and not a usermode exception), we only need to save the callee-save fpu registers. */ + tbz w4, #(THREAD_EXCEPTION_FLAG_BIT_INDEX_IS_CALLING_SVC), 6f + +5: /* Save the 32-bit caller-save fpu registers. */ + ldr x6, [x2, #(THREAD_STACK_PARAMETERS_CALLER_SAVE_FPU_REGISTERS)] + stp q0, q1, [x6, #(THREAD_FPU32_CONTEXT_Q0_Q1)] + stp q2, q3, [x6, #(THREAD_FPU32_CONTEXT_Q2_Q3)] + stp q8, q9, [x6, #(THREAD_FPU32_CONTEXT_Q8_Q9)] + stp q10, q11, [x6, #(THREAD_FPU32_CONTEXT_Q10_Q11)] + stp q12, q13, [x6, #(THREAD_FPU32_CONTEXT_Q12_Q13)] + stp q14, q15, [x6, #(THREAD_FPU32_CONTEXT_Q14_Q15)] + +6: /* Save the 32-bit callee-save fpu registers. */ + stp q4, q5, [x3, #(THREAD_CONTEXT_FPU32_Q4_Q5)] + stp q6, q7, [x3, #(THREAD_CONTEXT_FPU32_Q6_Q7)] + +7: /* With the fpu state saved, disable the fpu. */ + and x5, x5, #(~0x300000) + msr cpacr_el1, x5 + +8: /* We're done saving this thread's context. */ + + /* Check if the thread is terminated by checking the DPC flags for DpcFlag_Terminated. */ + ldrb w4, [x2, #(THREAD_STACK_PARAMETERS_DPC_FLAGS)] + tbnz w4, #1, 9f + + /* The thread isn't terminated, so we want to unlock it. */ + /* Write atomically to the context's locked member. */ + add x3, x3, #(THREAD_CONTEXT_LOCKED) + stlrb wzr, [x3] + +9: /* The current thread's context has been entirely taken care of. */ + /* Now we want to loop until we successfully switch the thread context. */ + /* Start by saving all the values we care about in callee-save registers. */ + mov x19, x0 /* this */ + mov x20, x1 /* this->state */ + mov x21, x7 /* highest priority thread */ + + /* Set our stack to the idle thread stack. */ + ldr x3, [x20, #(KSCHEDULER_IDLE_THREAD_STACK)] + mov sp, x3 + b 11f + +10: /* We failed to successfully do the context switch, and need to retry. */ + /* Clear the exclusive monitor. */ + clrex + + /* Clear the need's scheduling bool (and dmb ish after, as it's an atomic). */ + strb wzr, [x20] + dmb ish + + /* Refresh the highest priority thread. */ + ldr x21, [x20, #(KSCHEDULER_HIGHEST_PRIORITY_THREAD)] + +11: /* We're starting to try to do the context switch. */ + /* Check if the highest priority thread if null. */ + /* If it is, we want to branch to a special idle thread loop. */ + cbz x21, 16f + + /* Get the highest priority thread's context, and save it. */ + /* ams::kern::KThread::GetContextForSchedulerLoop() */ + ldr x22, [x21, #(THREAD_KERNEL_STACK_TOP)] + sub x22, x22, #(THREAD_STACK_PARAMETERS_SIZE - THREAD_STACK_PARAMETERS_THREAD_CONTEXT) + + /* Prepare to try to acquire the context lock. */ + add x1, x22, #(THREAD_CONTEXT_LOCKED) + mov w2, #1 + +12: /* We want to try to lock the highest priority thread's context. */ + /* Check if the lock is already held. */ + ldaxrb w3, [x1] + cbnz w3, 13f + + /* If it's not, try to take it. */ + stxrb w3, w2, [x1] + cbnz w3, 12b + + /* We hold the lock, so we can now switch the thread. */ + b 14f + +13: /* The highest priority thread's context is already locked. */ + /* Check if we need scheduling. If we don't, we can retry directly. */ + ldarb w3, [x20] // ldarb w3, [x20, #(KSCHEDULER_NEEDS_SCHEDULING)] + cbz w3, 12b + + /* If we do, another core is interfering, and we must start from the top. */ + b 10b + +14: /* It's time to switch the thread. */ + /* Switch to the highest priority thread. */ + mov x0, x19 + mov x1, x21 + + /* Call ams::kern::KScheduler::SwitchThread(ams::kern::KThread *) */ + bl _ZN3ams4kern10KScheduler12SwitchThreadEPNS0_7KThreadE + + /* Check if we need scheduling. If we do, then we can't complete the switch and should retry. */ + ldarb w1, [x20] // ldarb w1, [x20, #(KSCHEDULER_NEEDS_SCHEDULING)] + cbnz w1, 15f + + /* Restore the thread context. */ + mov x0, x22 + ldp x19, x20, [x0, #(THREAD_CONTEXT_X19_X20)] + ldp x21, x22, [x0, #(THREAD_CONTEXT_X21_X22)] + ldp x23, x24, [x0, #(THREAD_CONTEXT_X23_X24)] + ldp x25, x26, [x0, #(THREAD_CONTEXT_X25_X26)] + ldp x27, x28, [x0, #(THREAD_CONTEXT_X27_X28)] + ldp x29, x30, [x0, #(THREAD_CONTEXT_X29_X30)] + + ldr x1, [x0, #(THREAD_CONTEXT_SP)] + mov sp, x1 + + /* Return. */ + ret + +15: /* Our switch failed. */ + /* We should unlock the thread context, and then retry. */ + add x1, x22, #(THREAD_CONTEXT_LOCKED) + stlrb wzr, [x1] + b 10b + +16: /* The next thread is nullptr! */ + /* Switch to nullptr. This will actually switch to the idle thread. */ + mov x0, x19 + mov x1, #0 + + /* Call ams::kern::KScheduler::SwitchThread(ams::kern::KThread *) */ + bl _ZN3ams4kern10KScheduler12SwitchThreadEPNS0_7KThreadE + +17: /* We've switched to the idle thread, so we want to process interrupt tasks until we schedule a non-idle thread. */ + /* Check whether there are runnable interrupt tasks. */ + ldrb w3, [x20, #(KSCHEDULER_INTERRUPT_TASK_RUNNABLE)] + cbnz w3, 18f + + /* Check if we need scheduling. */ + ldarb w3, [x20] // ldarb w3, [x20, #(KSCHEDULER_NEEDS_SCHEDULING)] + cbnz w3, 10b + + /* Clear the previous thread. */ + str xzr, [x20, #(KSCHEDULER_PREVIOUS_THREAD)] + + /* Wait for an interrupt and check again. */ + wfi + + msr daifclr, #2 + msr daifset, #2 + + b 17b + +18: /* We have interrupt tasks to execute! */ + /* Execute any pending interrupt tasks. */ + ldr x0, [x20, #(KSCHEDULER_INTERRUPT_TASK_MANAGER)] + bl _ZN3ams4kern21KInterruptTaskManager7DoTasksEv + + /* Clear the interrupt task thread as runnable. */ + strb wzr, [x20, #(KSCHEDULER_INTERRUPT_TASK_RUNNABLE)] + + /* Retry the scheduling loop. */ + b 10b diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/kern_k_thread_context_asm.s b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/kern_k_thread_context_asm.s new file mode 100644 index 00000000..4b4b1bd3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/kern_k_thread_context_asm.s @@ -0,0 +1,92 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere/kern_select_assembly_offsets.h> +#include <mesosphere/kern_select_assembly_macros.h> + +/* ams::kern::arch::arm64::UserModeThreadStarter() */ +.section .text._ZN3ams4kern4arch5arm6421UserModeThreadStarterEv, "ax", %progbits +.global _ZN3ams4kern4arch5arm6421UserModeThreadStarterEv +.type _ZN3ams4kern4arch5arm6421UserModeThreadStarterEv, %function +_ZN3ams4kern4arch5arm6421UserModeThreadStarterEv: + /* NOTE: Stack layout on entry looks like following: */ + /* SP */ + /* | */ + /* v */ + /* | KExceptionContext (size 0x120) | KThread::StackParameters (size 0x30) | */ + + /* Clear the disable count for this thread's stack parameters. */ + strh wzr, [sp, #(EXCEPTION_CONTEXT_SIZE + THREAD_STACK_PARAMETERS_DISABLE_COUNT)] + + /* Call ams::kern::arch::arm64::OnThreadStart() */ + bl _ZN3ams4kern4arch5arm6413OnThreadStartEv + + /* Restore thread state from the KExceptionContext on stack */ + ldp x30, x19, [sp, #(EXCEPTION_CONTEXT_X30_SP)] /* x30 = lr, x19 = sp */ + ldp x20, x21, [sp, #(EXCEPTION_CONTEXT_PC_PSR)] /* x20 = pc, x21 = psr */ + ldr x22, [sp, #(EXCEPTION_CONTEXT_TPIDR)] /* x22 = tpidr */ + + msr sp_el0, x19 + msr elr_el1, x20 + msr spsr_el1, x21 + msr tpidr_el0, x22 + + ldp x0, x1, [sp, #(EXCEPTION_CONTEXT_X0_X1)] + ldp x2, x3, [sp, #(EXCEPTION_CONTEXT_X2_X3)] + ldp x4, x5, [sp, #(EXCEPTION_CONTEXT_X4_X5)] + ldp x6, x7, [sp, #(EXCEPTION_CONTEXT_X6_X7)] + ldp x8, x9, [sp, #(EXCEPTION_CONTEXT_X8_X9)] + ldp x10, x11, [sp, #(EXCEPTION_CONTEXT_X10_X11)] + ldp x12, x13, [sp, #(EXCEPTION_CONTEXT_X12_X13)] + ldp x14, x15, [sp, #(EXCEPTION_CONTEXT_X14_X15)] + ldp x16, x17, [sp, #(EXCEPTION_CONTEXT_X16_X17)] + ldp x18, x19, [sp, #(EXCEPTION_CONTEXT_X18_X19)] + ldp x20, x21, [sp, #(EXCEPTION_CONTEXT_X20_X21)] + ldp x22, x23, [sp, #(EXCEPTION_CONTEXT_X22_X23)] + ldp x24, x25, [sp, #(EXCEPTION_CONTEXT_X24_X25)] + ldp x26, x27, [sp, #(EXCEPTION_CONTEXT_X26_X27)] + ldp x28, x29, [sp, #(EXCEPTION_CONTEXT_X28_X29)] + + /* Increment stack pointer above the KExceptionContext */ + add sp, sp, #(EXCEPTION_CONTEXT_SIZE) + + /* Return to EL0 */ + ERET_WITH_SPECULATION_BARRIER + +/* ams::kern::arch::arm64::SupervisorModeThreadStarter() */ +.section .text._ZN3ams4kern4arch5arm6427SupervisorModeThreadStarterEv, "ax", %progbits +.global _ZN3ams4kern4arch5arm6427SupervisorModeThreadStarterEv +.type _ZN3ams4kern4arch5arm6427SupervisorModeThreadStarterEv, %function +_ZN3ams4kern4arch5arm6427SupervisorModeThreadStarterEv: + /* NOTE: Stack layout on entry looks like following: */ + /* SP */ + /* | */ + /* v */ + /* | u64 argument | u64 entrypoint | KThread::StackParameters (size 0x30) | */ + + /* Clear the link register. */ + mov x30, #0 + + /* Load the argument and entrypoint. */ + ldp x0, x1, [sp], #0x10 + + /* Clear the disable count for this thread's stack parameters. */ + strh wzr, [sp, #(THREAD_STACK_PARAMETERS_DISABLE_COUNT)] + + /* Mask I bit in DAIF */ + msr daifclr, #2 + + /* Invoke the function (by calling ams::kern::arch::arm64::InvokeSupervisorModeThread(argument, entrypoint)). */ + b _ZN3ams4kern4arch5arm6426InvokeSupervisorModeThreadEmm diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/svc/kern_svc_tables_asm.s b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/svc/kern_svc_tables_asm.s new file mode 100644 index 00000000..f0483301 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/arch/arm64/svc/kern_svc_tables_asm.s @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* ams::kern::svc::PatchSvcTableEntry(void (* const*)(), unsigned int, void (*)()) */ +.section .text._ZN3ams4kern3svc18PatchSvcTableEntryEPKPFvvEjS3_, "ax", %progbits +.global _ZN3ams4kern3svc18PatchSvcTableEntryEPKPFvvEjS3_ +.type _ZN3ams4kern3svc18PatchSvcTableEntryEPKPFvvEjS3_, %function +_ZN3ams4kern3svc18PatchSvcTableEntryEPKPFvvEjS3_: + /* This function violates const correctness by design, to patch the svc tables. */ + /* The svc tables live in .rodata (.rel.ro), but must be patched by initial constructors */ + /* to support firmware-specific table entries. */ + mov w1, w1 + str x2, [x0, x1, lsl #3] + ret \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/kern_kernel_instantiations.cpp b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/kern_kernel_instantiations.cpp new file mode 100644 index 00000000..d01ddd15 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/kern_kernel_instantiations.cpp @@ -0,0 +1,118 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + /* Declare kernel data members in kernel TU. */ + constinit Kernel::State Kernel::s_state = Kernel::State::Invalid; + constinit KResourceLimit Kernel::s_system_resource_limit{util::ConstantInitialize}; + KMemoryManager Kernel::s_memory_manager; + constinit KSupervisorPageTable Kernel::s_supervisor_page_table; + constinit KUnsafeMemory Kernel::s_unsafe_memory; + constinit KWorkerTaskManager Kernel::s_worker_task_managers[KWorkerTaskManager::WorkerType_Count]; + constinit KInterruptManager Kernel::s_interrupt_manager; + constinit KScheduler Kernel::s_schedulers[cpu::NumCores]; + constinit KInterruptTaskManager Kernel::s_interrupt_task_managers[cpu::NumCores]; + constinit KHardwareTimer Kernel::s_hardware_timers[cpu::NumCores]; + + constinit KPageTableSlabHeap Kernel::s_page_table_heap; + constinit KMemoryBlockSlabHeap Kernel::s_app_memory_block_heap; + constinit KMemoryBlockSlabHeap Kernel::s_sys_memory_block_heap; + constinit KBlockInfoSlabHeap Kernel::s_block_info_heap; + constinit KPageTableManager Kernel::s_app_page_table_manager{util::ConstantInitialize}; + constinit KPageTableManager Kernel::s_sys_page_table_manager{util::ConstantInitialize}; + constinit KMemoryBlockSlabManager Kernel::s_app_memory_block_manager; + constinit KMemoryBlockSlabManager Kernel::s_sys_memory_block_manager; + constinit KBlockInfoManager Kernel::s_app_block_info_manager; + constinit KBlockInfoManager Kernel::s_sys_block_info_manager; + + constinit KSystemResource Kernel::s_app_system_resource{util::ConstantInitialize}; + constinit KSystemResource Kernel::s_sys_system_resource{util::ConstantInitialize}; + + namespace { + + template<size_t N> requires (N > 0) + union KThreadArray { + struct RecursiveHolder { + KThread m_thread; + KThreadArray<N - 1> m_next; + + consteval RecursiveHolder() : m_thread{util::ConstantInitialize}, m_next() { /* ... */ } + } m_holder; + KThread m_arr[N]; + + consteval KThreadArray() : m_holder() { /* ... */ } + }; + + template<> + union KThreadArray<1>{ + struct RecursiveHolder { + KThread m_thread; + + consteval RecursiveHolder() : m_thread{util::ConstantInitialize} { /* ... */ } + } m_holder; + KThread m_arr[1]; + + consteval KThreadArray() : m_holder() { /* ... */ } + }; + + template<size_t Ix> + consteval bool IsKThreadArrayValid(const KThreadArray<Ix> &v, const KThread *thread) { + if (std::addressof(v.m_holder.m_thread) != thread) { + return false; + } + + if constexpr (Ix == 1) { + return true; + } else { + return IsKThreadArrayValid(v.m_holder.m_next, thread + 1); + } + } + + template<size_t N> + consteval bool IsKThreadArrayValid() { + const KThreadArray<N> v{}; + + if (!IsKThreadArrayValid(v, v.m_arr)) { + return false; + } + + if constexpr (N == 1) { + return true; + } else { + return IsKThreadArrayValid<N - 1>(); + } + } + + static_assert(IsKThreadArrayValid<cpu::NumCores>()); + + constinit KThreadArray<cpu::NumCores> g_main_threads; + constinit KThreadArray<cpu::NumCores> g_idle_threads; + + static_assert(sizeof(g_main_threads) == cpu::NumCores * sizeof(KThread)); + static_assert(sizeof(g_main_threads.m_holder) == sizeof(g_main_threads.m_arr)); + + } + + KThread &Kernel::GetMainThread(s32 core_id) { return g_main_threads.m_arr[core_id]; } + KThread &Kernel::GetIdleThread(s32 core_id) { return g_idle_threads.m_arr[core_id]; } + + __attribute__((constructor)) void ConfigureKTargetSystem() { + KSystemControl::ConfigureKTargetSystem(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/arch/arm64/asmdefs.h b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/arch/arm64/asmdefs.h new file mode 100644 index 00000000..edc4e66c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/arch/arm64/asmdefs.h @@ -0,0 +1,31 @@ +/* + * Macros for asm code. + * + * Copyright (c) 2019, Arm Limited. + * SPDX-License-Identifier: MIT + */ + +#ifndef _ASMDEFS_H +#define _ASMDEFS_H + +#define ENTRY_ALIGN(name, alignment) \ + .global name; \ + .type name,%function; \ + .align alignment; \ + name: \ + .cfi_startproc; + +#define ENTRY(name) ENTRY_ALIGN(name, 6) + +#define ENTRY_ALIAS(name) \ + .global name; \ + .type name,%function; \ + name: + +#define END(name) \ + .cfi_endproc; \ + .size name, .-name; + +#define L(l) .L ## l + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/arch/arm64/memcmp.arch.arm64.s b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/arch/arm64/memcmp.arch.arm64.s new file mode 100644 index 00000000..609f7e20 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/arch/arm64/memcmp.arch.arm64.s @@ -0,0 +1,133 @@ +/* memcmp - compare memory + * + * Copyright (c) 2013, Arm Limited. + * SPDX-License-Identifier: MIT + */ + +/* Assumptions: + * + * ARMv8-a, AArch64, unaligned accesses. + */ + +#include "asmdefs.h" + +/* Parameters and result. */ +#define src1 x0 +#define src2 x1 +#define limit x2 +#define result w0 + +/* Internal variables. */ +#define data1 x3 +#define data1w w3 +#define data1h x4 +#define data2 x5 +#define data2w w5 +#define data2h x6 +#define tmp1 x7 +#define tmp2 x8 + +ENTRY (memcmp) + subs limit, limit, 8 + b.lo L(less8) + + ldr data1, [src1], 8 + ldr data2, [src2], 8 + cmp data1, data2 + b.ne L(return) + + subs limit, limit, 8 + b.gt L(more16) + + ldr data1, [src1, limit] + ldr data2, [src2, limit] + b L(return) + +L(more16): + ldr data1, [src1], 8 + ldr data2, [src2], 8 + cmp data1, data2 + bne L(return) + + /* Jump directly to comparing the last 16 bytes for 32 byte (or less) + strings. */ + subs limit, limit, 16 + b.ls L(last_bytes) + + /* We overlap loads between 0-32 bytes at either side of SRC1 when we + try to align, so limit it only to strings larger than 128 bytes. */ + cmp limit, 96 + b.ls L(loop16) + + /* Align src1 and adjust src2 with bytes not yet done. */ + and tmp1, src1, 15 + add limit, limit, tmp1 + sub src1, src1, tmp1 + sub src2, src2, tmp1 + + /* Loop performing 16 bytes per iteration using aligned src1. + Limit is pre-decremented by 16 and must be larger than zero. + Exit if <= 16 bytes left to do or if the data is not equal. */ + .p2align 4 +L(loop16): + ldp data1, data1h, [src1], 16 + ldp data2, data2h, [src2], 16 + subs limit, limit, 16 + ccmp data1, data2, 0, hi + ccmp data1h, data2h, 0, eq + b.eq L(loop16) + + cmp data1, data2 + bne L(return) + mov data1, data1h + mov data2, data2h + cmp data1, data2 + bne L(return) + + /* Compare last 1-16 bytes using unaligned access. */ +L(last_bytes): + add src1, src1, limit + add src2, src2, limit + ldp data1, data1h, [src1] + ldp data2, data2h, [src2] + cmp data1, data2 + bne L(return) + mov data1, data1h + mov data2, data2h + cmp data1, data2 + + /* Compare data bytes and set return value to 0, -1 or 1. */ +L(return): +#ifndef __AARCH64EB__ + rev data1, data1 + rev data2, data2 +#endif + cmp data1, data2 +L(ret_eq): + cset result, ne + cneg result, result, lo + ret + + .p2align 4 + /* Compare up to 8 bytes. Limit is [-8..-1]. */ +L(less8): + adds limit, limit, 4 + b.lo L(less4) + ldr data1w, [src1], 4 + ldr data2w, [src2], 4 + cmp data1w, data2w + b.ne L(return) + sub limit, limit, 4 +L(less4): + adds limit, limit, 4 + beq L(ret_eq) +L(byte_loop): + ldrb data1w, [src1], 1 + ldrb data2w, [src2], 1 + subs limit, limit, 1 + ccmp data1w, data2w, 0, ne /* NZCV = 0b0000. */ + b.eq L(byte_loop) + sub result, data1w, data2w + ret + +END (memcmp) diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/arch/arm64/memcpy.arch.arm64.s b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/arch/arm64/memcpy.arch.arm64.s new file mode 100644 index 00000000..02ed1dd8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/arch/arm64/memcpy.arch.arm64.s @@ -0,0 +1,239 @@ +/* + * memcpy - copy memory area + * + * Copyright (c) 2012-2020, Arm Limited. + * SPDX-License-Identifier: MIT + */ + +/* Assumptions: + * + * ARMv8-a, AArch64, unaligned accesses. + * + */ + +#include "asmdefs.h" + +#define dstin x0 +#define src x1 +#define count x2 +#define dst x3 +#define srcend x4 +#define dstend x5 +#define A_l x6 +#define A_lw w6 +#define A_h x7 +#define B_l x8 +#define B_lw w8 +#define B_h x9 +#define C_l x10 +#define C_lw w10 +#define C_h x11 +#define D_l x12 +#define D_h x13 +#define E_l x14 +#define E_h x15 +#define F_l x16 +#define F_h x17 +#define G_l count +#define G_h dst +#define H_l src +#define H_h srcend +#define tmp1 x14 + +/* This implementation handles overlaps and supports both memcpy and memmove + from a single entry point. It uses unaligned accesses and branchless + sequences to keep the code small, simple and improve performance. + + Copies are split into 3 main cases: small copies of up to 32 bytes, medium + copies of up to 128 bytes, and large copies. The overhead of the overlap + check is negligible since it is only required for large copies. + + Large copies use a software pipelined loop processing 64 bytes per iteration. + The destination pointer is 16-byte aligned to minimize unaligned accesses. + The loop tail is handled by always copying 64 bytes from the end. +*/ + +ENTRY (memcpy) +ENTRY_ALIAS (memmove) + add srcend, src, count + add dstend, dstin, count + cmp count, 128 + b.hi L(copy_long) + cmp count, 32 + b.hi L(copy32_128) + + /* Small copies: 0..32 bytes. */ + cmp count, 16 + b.lo L(copy16) + ldp A_l, A_h, [src] + ldp D_l, D_h, [srcend, -16] + stp A_l, A_h, [dstin] + stp D_l, D_h, [dstend, -16] + ret + + /* Copy 8-15 bytes. */ +L(copy16): + tbz count, 3, L(copy8) + ldr A_l, [src] + ldr A_h, [srcend, -8] + str A_l, [dstin] + str A_h, [dstend, -8] + ret + + .p2align 3 + /* Copy 4-7 bytes. */ +L(copy8): + tbz count, 2, L(copy4) + ldr A_lw, [src] + ldr B_lw, [srcend, -4] + str A_lw, [dstin] + str B_lw, [dstend, -4] + ret + + /* Copy 0..3 bytes using a branchless sequence. */ +L(copy4): + cbz count, L(copy0) + lsr tmp1, count, 1 + ldrb A_lw, [src] + ldrb C_lw, [srcend, -1] + ldrb B_lw, [src, tmp1] + strb A_lw, [dstin] + strb B_lw, [dstin, tmp1] + strb C_lw, [dstend, -1] +L(copy0): + ret + + .p2align 4 + /* Medium copies: 33..128 bytes. */ +L(copy32_128): + ldp A_l, A_h, [src] + ldp B_l, B_h, [src, 16] + ldp C_l, C_h, [srcend, -32] + ldp D_l, D_h, [srcend, -16] + cmp count, 64 + b.hi L(copy128) + stp A_l, A_h, [dstin] + stp B_l, B_h, [dstin, 16] + stp C_l, C_h, [dstend, -32] + stp D_l, D_h, [dstend, -16] + ret + + .p2align 4 + /* Copy 65..128 bytes. */ +L(copy128): + ldp E_l, E_h, [src, 32] + ldp F_l, F_h, [src, 48] + cmp count, 96 + b.ls L(copy96) + ldp G_l, G_h, [srcend, -64] + ldp H_l, H_h, [srcend, -48] + stp G_l, G_h, [dstend, -64] + stp H_l, H_h, [dstend, -48] +L(copy96): + stp A_l, A_h, [dstin] + stp B_l, B_h, [dstin, 16] + stp E_l, E_h, [dstin, 32] + stp F_l, F_h, [dstin, 48] + stp C_l, C_h, [dstend, -32] + stp D_l, D_h, [dstend, -16] + ret + + .p2align 4 + /* Copy more than 128 bytes. */ +L(copy_long): + /* Use backwards copy if there is an overlap. */ + sub tmp1, dstin, src + cbz tmp1, L(copy0) + cmp tmp1, count + b.lo L(copy_long_backwards) + + /* Copy 16 bytes and then align dst to 16-byte alignment. */ + + ldp D_l, D_h, [src] + and tmp1, dstin, 15 + bic dst, dstin, 15 + sub src, src, tmp1 + add count, count, tmp1 /* Count is now 16 too large. */ + ldp A_l, A_h, [src, 16] + stp D_l, D_h, [dstin] + ldp B_l, B_h, [src, 32] + ldp C_l, C_h, [src, 48] + ldp D_l, D_h, [src, 64]! + subs count, count, 128 + 16 /* Test and readjust count. */ + b.ls L(copy64_from_end) + +L(loop64): + stp A_l, A_h, [dst, 16] + ldp A_l, A_h, [src, 16] + stp B_l, B_h, [dst, 32] + ldp B_l, B_h, [src, 32] + stp C_l, C_h, [dst, 48] + ldp C_l, C_h, [src, 48] + stp D_l, D_h, [dst, 64]! + ldp D_l, D_h, [src, 64]! + subs count, count, 64 + b.hi L(loop64) + + /* Write the last iteration and copy 64 bytes from the end. */ +L(copy64_from_end): + ldp E_l, E_h, [srcend, -64] + stp A_l, A_h, [dst, 16] + ldp A_l, A_h, [srcend, -48] + stp B_l, B_h, [dst, 32] + ldp B_l, B_h, [srcend, -32] + stp C_l, C_h, [dst, 48] + ldp C_l, C_h, [srcend, -16] + stp D_l, D_h, [dst, 64] + stp E_l, E_h, [dstend, -64] + stp A_l, A_h, [dstend, -48] + stp B_l, B_h, [dstend, -32] + stp C_l, C_h, [dstend, -16] + ret + + .p2align 4 + + /* Large backwards copy for overlapping copies. + Copy 16 bytes and then align dst to 16-byte alignment. */ +L(copy_long_backwards): + ldp D_l, D_h, [srcend, -16] + and tmp1, dstend, 15 + sub srcend, srcend, tmp1 + sub count, count, tmp1 + ldp A_l, A_h, [srcend, -16] + stp D_l, D_h, [dstend, -16] + ldp B_l, B_h, [srcend, -32] + ldp C_l, C_h, [srcend, -48] + ldp D_l, D_h, [srcend, -64]! + sub dstend, dstend, tmp1 + subs count, count, 128 + b.ls L(copy64_from_start) + +L(loop64_backwards): + stp A_l, A_h, [dstend, -16] + ldp A_l, A_h, [srcend, -16] + stp B_l, B_h, [dstend, -32] + ldp B_l, B_h, [srcend, -32] + stp C_l, C_h, [dstend, -48] + ldp C_l, C_h, [srcend, -48] + stp D_l, D_h, [dstend, -64]! + ldp D_l, D_h, [srcend, -64]! + subs count, count, 64 + b.hi L(loop64_backwards) + + /* Write the last iteration and copy 64 bytes from the start. */ +L(copy64_from_start): + ldp G_l, G_h, [src, 48] + stp A_l, A_h, [dstend, -16] + ldp A_l, A_h, [src, 32] + stp B_l, B_h, [dstend, -32] + ldp B_l, B_h, [src, 16] + stp C_l, C_h, [dstend, -48] + ldp C_l, C_h, [src] + stp D_l, D_h, [dstend, -64] + stp G_l, G_h, [dstin, 48] + stp A_l, A_h, [dstin, 32] + stp B_l, B_h, [dstin, 16] + stp C_l, C_h, [dstin] + ret + +END (memcpy) diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/arch/arm64/memset.arch.arm64.s b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/arch/arm64/memset.arch.arm64.s new file mode 100644 index 00000000..700f0e84 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/arch/arm64/memset.arch.arm64.s @@ -0,0 +1,172 @@ +/* + * memset - fill memory with a constant byte + * + * Copyright (c) 2012-2020, Arm Limited. + * SPDX-License-Identifier: MIT + */ + +/* Assumptions: + * + * ARMv8-a, AArch64, Advanced SIMD, unaligned accesses. + * + */ + +#include "asmdefs.h" + +#define DC_ZVA_THRESHOLD 512 + +#define dstin x0 +#define val x1 +#define valw w1 +#define count x2 +#define dst x3 +#define dstend x4 +#define zva_val x5 + +ENTRY (memset) + + bfi valw, valw, 8, 8 + bfi valw, valw, 16, 16 + bfi val, val, 32, 32 + + add dstend, dstin, count + + cmp count, 96 + b.hi L(set_long) + cmp count, 16 + b.hs L(set_medium) + + /* Set 0..15 bytes. */ + tbz count, 3, 1f + str val, [dstin] + str val, [dstend, -8] + ret +1: tbz count, 2, 2f + str valw, [dstin] + str valw, [dstend, -4] + ret +2: cbz count, 3f + strb valw, [dstin] + tbz count, 1, 3f + strh valw, [dstend, -2] +3: ret + + /* Set 16..96 bytes. */ + .p2align 4 +L(set_medium): + stp val, val, [dstin] + tbnz count, 6, L(set96) + stp val, val, [dstend, -16] + tbz count, 5, 1f + stp val, val, [dstin, 16] + stp val, val, [dstend, -32] +1: ret + + .p2align 4 + /* Set 64..96 bytes. Write 64 bytes from the start and + 32 bytes from the end. */ +L(set96): + stp val, val, [dstin, 16] + stp val, val, [dstin, 32] + stp val, val, [dstin, 48] + stp val, val, [dstend, -32] + stp val, val, [dstend, -16] + ret + + .p2align 4 +L(set_long): + stp val, val, [dstin] +#if DC_ZVA_THRESHOLD + cmp count, DC_ZVA_THRESHOLD + ccmp val, 0, 0, cs + bic dst, dstin, 15 + b.eq L(zva_64) +#else + bic dst, dstin, 15 +#endif + /* Small-size or non-zero memset does not use DC ZVA. */ + sub count, dstend, dst + + /* + * Adjust count and bias for loop. By substracting extra 1 from count, + * it is easy to use tbz instruction to check whether loop tailing + * count is less than 33 bytes, so as to bypass 2 unneccesary stps. + */ + sub count, count, 64+16+1 + +#if DC_ZVA_THRESHOLD + /* Align loop on 16-byte boundary, this might be friendly to i-cache. */ + nop +#endif + +1: stp val, val, [dst, 16] + stp val, val, [dst, 32] + stp val, val, [dst, 48] + stp val, val, [dst, 64]! + subs count, count, 64 + b.hs 1b + + tbz count, 5, 1f /* Remaining count is less than 33 bytes? */ + stp val, val, [dst, 16] + stp val, val, [dst, 32] +1: stp val, val, [dstend, -32] + stp val, val, [dstend, -16] + ret + +#if DC_ZVA_THRESHOLD + .p2align 4 +L(zva_64): + stp val, val, [dst, 16] + stp val, val, [dst, 32] + stp val, val, [dst, 48] + bic dst, dst, 63 + + /* + * Previous memory writes might cross cache line boundary, and cause + * cache line partially dirty. Zeroing this kind of cache line using + * DC ZVA will incur extra cost, for it requires loading untouched + * part of the line from memory before zeoring. + * + * So, write the first 64 byte aligned block using stp to force + * fully dirty cache line. + */ + stp val, val, [dst, 64] + stp val, val, [dst, 80] + stp val, val, [dst, 96] + stp val, val, [dst, 112] + + sub count, dstend, dst + /* + * Adjust count and bias for loop. By substracting extra 1 from count, + * it is easy to use tbz instruction to check whether loop tailing + * count is less than 33 bytes, so as to bypass 2 unneccesary stps. + */ + sub count, count, 128+64+64+1 + add dst, dst, 128 + nop + + /* DC ZVA sets 64 bytes each time. */ +1: dc zva, dst + add dst, dst, 64 + subs count, count, 64 + b.hs 1b + + /* + * Write the last 64 byte aligned block using stp to force fully + * dirty cache line. + */ + stp val, val, [dst, 0] + stp val, val, [dst, 16] + stp val, val, [dst, 32] + stp val, val, [dst, 48] + + tbz count, 5, 1f /* Remaining count is less than 33 bytes? */ + stp val, val, [dst, 64] + stp val, val, [dst, 80] +1: stp val, val, [dstend, -32] + stp val, val, [dstend, -16] + ret +#endif + + +END (memset) diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/kern_env.cpp b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/kern_env.cpp new file mode 100644 index 00000000..3e688794 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/kern_env.cpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +void operator delete (void *deleted) throw() { + MESOSPHERE_PANIC("operator delete(void *) was called: %p", deleted); +} + +void operator delete (void *deleted, size_t size) throw() { + MESOSPHERE_PANIC("operator delete(void *, size_t) was called: %p %zu", deleted, size); +} + +void operator delete (void *deleted, size_t size, std::align_val_t align) throw() { + MESOSPHERE_PANIC("operator delete(void *, size_t, std::align_val_t) was called: %p %zu, %zu", deleted, size, static_cast<size_t>(align)); +} + +extern "C" void abort() { + MESOSPHERE_PANIC("abort() was called"); +} diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/kern_libc_config.arch.arm64.h b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/kern_libc_config.arch.arm64.h new file mode 100644 index 00000000..b7a8f72b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/kern_libc_config.arch.arm64.h @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +/* Definitions for libc genericity. */ +#define MESOSPHERE_LIBC_MEMCPY_GENERIC 0 +#define MESOSPHERE_LIBC_MEMCMP_GENERIC 0 +#define MESOSPHERE_LIBC_MEMMOVE_GENERIC 0 +#define MESOSPHERE_LIBC_MEMSET_GENERIC 0 +#define MESOSPHERE_LIBC_STRNCPY_GENERIC 1 +#define MESOSPHERE_LIBC_STRNCMP_GENERIC 1 diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/kern_libc_config.h b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/kern_libc_config.h new file mode 100644 index 00000000..ca7bb2e0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/kern_libc_config.h @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#if defined(ATMOSPHERE_ARCH_ARM64) + + #include "kern_libc_config.arch.arm64.h" + +#else + + #error "Unknown architecture for libc" + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/kern_libc_generic.c b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/kern_libc_generic.c new file mode 100644 index 00000000..891dd6fb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel/source/libc/kern_libc_generic.c @@ -0,0 +1,673 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <string.h> +#include <stddef.h> +#include <limits.h> +#include "kern_libc_config.h" + +/* Note: copied from newlib */ +#ifdef __cplusplus +extern "C" { +#endif + +/* +FUNCTION + <<memmove>>---move possibly overlapping memory +INDEX + memmove +SYNOPSIS + #include <string.h> + void *memmove(void *<[dst]>, const void *<[src]>, size_t <[length]>); +DESCRIPTION + This function moves <[length]> characters from the block of + memory starting at <<*<[src]>>> to the memory starting at + <<*<[dst]>>>. <<memmove>> reproduces the characters correctly + at <<*<[dst]>>> even if the two areas overlap. +RETURNS + The function returns <[dst]> as passed. +PORTABILITY +<<memmove>> is ANSI C. +<<memmove>> requires no supporting OS subroutines. +QUICKREF + memmove ansi pure +*/ + +/* Nonzero if either X or Y is not aligned on a "long" boundary. */ +#define UNALIGNED(X, Y) \ + (((long)X & (sizeof (long) - 1)) | ((long)Y & (sizeof (long) - 1))) + +/* How many bytes are copied each iteration of the 4X unrolled loop. */ +#define BIGBLOCKSIZE (sizeof (long) << 2) + +/* How many bytes are copied each iteration of the word copy loop. */ +#define LITTLEBLOCKSIZE (sizeof (long)) + +/* Threshhold for punting to the byte copier. */ +#undef TOO_SMALL +#define TOO_SMALL(LEN) ((LEN) < BIGBLOCKSIZE) + +#if MESOSPHERE_LIBC_MEMMOVE_GENERIC + +/*SUPPRESS 20*/ +void * +//__inhibit_loop_to_libcall +__attribute__((weak)) +memmove (void *dst_void, + const void *src_void, + size_t length) +{ +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) + char *dst = dst_void; + const char *src = src_void; + + if (src < dst && dst < src + length) + { + /* Have to copy backwards */ + src += length; + dst += length; + while (length--) + { + *--dst = *--src; + } + } + else + { + while (length--) + { + *dst++ = *src++; + } + } + + return dst_void; +#else + char *dst = dst_void; + const char *src = src_void; + long *aligned_dst; + const long *aligned_src; + + if (src < dst && dst < src + length) + { + /* Destructive overlap...have to copy backwards */ + src += length; + dst += length; + while (length--) + { + *--dst = *--src; + } + } + else + { + /* Use optimizing algorithm for a non-destructive copy to closely + match memcpy. If the size is small or either SRC or DST is unaligned, + then punt into the byte copy loop. This should be rare. */ + if (!TOO_SMALL(length) && !UNALIGNED (src, dst)) + { + aligned_dst = (long*)dst; + aligned_src = (long*)src; + + /* Copy 4X long words at a time if possible. */ + while (length >= BIGBLOCKSIZE) + { + *aligned_dst++ = *aligned_src++; + *aligned_dst++ = *aligned_src++; + *aligned_dst++ = *aligned_src++; + *aligned_dst++ = *aligned_src++; + length -= BIGBLOCKSIZE; + } + + /* Copy one long word at a time if possible. */ + while (length >= LITTLEBLOCKSIZE) + { + *aligned_dst++ = *aligned_src++; + length -= LITTLEBLOCKSIZE; + } + + /* Pick up any residual with a byte copier. */ + dst = (char*)aligned_dst; + src = (char*)aligned_src; + } + + while (length--) + { + *dst++ = *src++; + } + } + + return dst_void; +#endif /* not PREFER_SIZE_OVER_SPEED */ +} + +#endif /* MESOSPHERE_LIBC_MEMMOVE_GENERIC */ + +/* +FUNCTION + <<memcpy>>---copy memory regions +SYNOPSIS + #include <string.h> + void* memcpy(void *restrict <[out]>, const void *restrict <[in]>, + size_t <[n]>); +DESCRIPTION + This function copies <[n]> bytes from the memory region + pointed to by <[in]> to the memory region pointed to by + <[out]>. + If the regions overlap, the behavior is undefined. +RETURNS + <<memcpy>> returns a pointer to the first byte of the <[out]> + region. +PORTABILITY +<<memcpy>> is ANSI C. +<<memcpy>> requires no supporting OS subroutines. +QUICKREF + memcpy ansi pure + */ + +#if MESOSPHERE_LIBC_MEMCPY_GENERIC + +void * +__attribute__((weak)) +memcpy (void * dst0, + const void * __restrict src0, + size_t len0) +{ +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) + char *dst = (char *) dst0; + char *src = (char *) src0; + + void *save = dst0; + + while (len0--) + { + *dst++ = *src++; + } + + return save; +#else + char *dst = dst0; + const char *src = src0; + long *aligned_dst; + const long *aligned_src; + + /* If the size is small, or either SRC or DST is unaligned, + then punt into the byte copy loop. This should be rare. */ + if (!TOO_SMALL(len0) && !UNALIGNED (src, dst)) + { + aligned_dst = (long*)dst; + aligned_src = (long*)src; + + /* Copy 4X long words at a time if possible. */ + while (len0 >= BIGBLOCKSIZE) + { + *aligned_dst++ = *aligned_src++; + *aligned_dst++ = *aligned_src++; + *aligned_dst++ = *aligned_src++; + *aligned_dst++ = *aligned_src++; + len0 -= BIGBLOCKSIZE; + } + + /* Copy one long word at a time if possible. */ + while (len0 >= LITTLEBLOCKSIZE) + { + *aligned_dst++ = *aligned_src++; + len0 -= LITTLEBLOCKSIZE; + } + + /* Pick up any residual with a byte copier. */ + dst = (char*)aligned_dst; + src = (char*)aligned_src; + } + + while (len0--) + *dst++ = *src++; + + return dst0; +#endif /* not PREFER_SIZE_OVER_SPEED */ +} + +#endif /* MESOSPHERE_LIBC_MEMCPY_GENERIC */ + +/* +FUNCTION + <<memset>>---set an area of memory +INDEX + memset +SYNOPSIS + #include <string.h> + void *memset(void *<[dst]>, int <[c]>, size_t <[length]>); +DESCRIPTION + This function converts the argument <[c]> into an unsigned + char and fills the first <[length]> characters of the array + pointed to by <[dst]> to the value. +RETURNS + <<memset>> returns the value of <[dst]>. +PORTABILITY +<<memset>> is ANSI C. + <<memset>> requires no supporting OS subroutines. +QUICKREF + memset ansi pure +*/ + +#include <string.h> + +#undef LBLOCKSIZE +#undef UNALIGNED +#undef TOO_SMALL + +#define LBLOCKSIZE (sizeof(long)) +#define UNALIGNED(X) ((long)X & (LBLOCKSIZE - 1)) +#define TOO_SMALL(LEN) ((LEN) < LBLOCKSIZE) + +#if MESOSPHERE_LIBC_MEMSET_GENERIC + +void * +__attribute__((weak)) +memset (void *m, + int c, + size_t n) +{ + char *s = (char *) m; + +#if !defined(PREFER_SIZE_OVER_SPEED) && !defined(__OPTIMIZE_SIZE__) + unsigned int i; + unsigned long buffer; + unsigned long *aligned_addr; + unsigned int d = c & 0xff; /* To avoid sign extension, copy C to an + unsigned variable. */ + + while (UNALIGNED (s)) + { + if (n--) + *s++ = (char) c; + else + return m; + } + + if (!TOO_SMALL (n)) + { + /* If we get this far, we know that n is large and s is word-aligned. */ + aligned_addr = (unsigned long *) s; + + /* Store D into each char sized location in BUFFER so that + we can set large blocks quickly. */ + buffer = (d << 8) | d; + buffer |= (buffer << 16); + for (i = 32; i < LBLOCKSIZE * 8; i <<= 1) + buffer = (buffer << i) | buffer; + + /* Unroll the loop. */ + while (n >= LBLOCKSIZE*4) + { + *aligned_addr++ = buffer; + *aligned_addr++ = buffer; + *aligned_addr++ = buffer; + *aligned_addr++ = buffer; + n -= 4*LBLOCKSIZE; + } + + while (n >= LBLOCKSIZE) + { + *aligned_addr++ = buffer; + n -= LBLOCKSIZE; + } + /* Pick up the remainder with a bytewise loop. */ + s = (char*)aligned_addr; + } + +#endif /* not PREFER_SIZE_OVER_SPEED */ + + while (n--) + *s++ = (char) c; + + return m; +} + +#endif /* MESOSPHERE_LIBC_MEMSET_GENERIC */ + +/* +FUNCTION + <<memcmp>>---compare two memory areas +INDEX + memcmp +SYNOPSIS + #include <string.h> + int memcmp(const void *<[s1]>, const void *<[s2]>, size_t <[n]>); +DESCRIPTION + This function compares not more than <[n]> characters of the + object pointed to by <[s1]> with the object pointed to by <[s2]>. +RETURNS + The function returns an integer greater than, equal to or + less than zero according to whether the object pointed to by + <[s1]> is greater than, equal to or less than the object + pointed to by <[s2]>. +PORTABILITY +<<memcmp>> is ANSI C. +<<memcmp>> requires no supporting OS subroutines. +QUICKREF + memcmp ansi pure +*/ + +#undef LBLOCKSIZE +#undef UNALIGNED +#undef TOO_SMALL + +/* Nonzero if either X or Y is not aligned on a "long" boundary. */ +#define UNALIGNED(X, Y) \ + (((long)X & (sizeof (long) - 1)) | ((long)Y & (sizeof (long) - 1))) + +/* How many bytes are copied each iteration of the word copy loop. */ +#define LBLOCKSIZE (sizeof (long)) + +/* Threshhold for punting to the byte copier. */ +#define TOO_SMALL(LEN) ((LEN) < LBLOCKSIZE) + +#if MESOSPHERE_LIBC_MEMCMP_GENERIC + +int +__attribute__((weak)) +memcmp (const void *m1, + const void *m2, + size_t n) +{ +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) + unsigned char *s1 = (unsigned char *) m1; + unsigned char *s2 = (unsigned char *) m2; + + while (n--) + { + if (*s1 != *s2) + { + return *s1 - *s2; + } + s1++; + s2++; + } + return 0; +#else + unsigned char *s1 = (unsigned char *) m1; + unsigned char *s2 = (unsigned char *) m2; + unsigned long *a1; + unsigned long *a2; + + /* If the size is too small, or either pointer is unaligned, + then we punt to the byte compare loop. Hopefully this will + not turn up in inner loops. */ + if (!TOO_SMALL(n) && !UNALIGNED(s1,s2)) + { + /* Otherwise, load and compare the blocks of memory one + word at a time. */ + a1 = (unsigned long*) s1; + a2 = (unsigned long*) s2; + while (n >= LBLOCKSIZE) + { + if (*a1 != *a2) + break; + a1++; + a2++; + n -= LBLOCKSIZE; + } + + /* check m mod LBLOCKSIZE remaining characters */ + + s1 = (unsigned char*)a1; + s2 = (unsigned char*)a2; + } + + while (n--) + { + if (*s1 != *s2) + return *s1 - *s2; + s1++; + s2++; + } + + return 0; +#endif /* not PREFER_SIZE_OVER_SPEED */ +} + +#endif /* MESOSPHERE_LIBC_MEMCMP_GENERIC */ + +/* +FUNCTION + <<strncpy>>---counted copy string +INDEX + strncpy +SYNOPSIS + #include <string.h> + char *strncpy(char *restrict <[dst]>, const char *restrict <[src]>, + size_t <[length]>); +DESCRIPTION + <<strncpy>> copies not more than <[length]> characters from the + the string pointed to by <[src]> (including the terminating + null character) to the array pointed to by <[dst]>. If the + string pointed to by <[src]> is shorter than <[length]> + characters, null characters are appended to the destination + array until a total of <[length]> characters have been + written. +RETURNS + This function returns the initial value of <[dst]>. +PORTABILITY +<<strncpy>> is ANSI C. +<<strncpy>> requires no supporting OS subroutines. +QUICKREF + strncpy ansi pure +*/ + +#include <string.h> +#include <limits.h> + +/*SUPPRESS 560*/ +/*SUPPRESS 530*/ + +/* Nonzero if either X or Y is not aligned on a "long" boundary. */ +#define UNALIGNED(X, Y) \ + (((long)X & (sizeof (long) - 1)) | ((long)Y & (sizeof (long) - 1))) + +#if LONG_MAX == 2147483647L +#define DETECTNULL(X) (((X) - 0x01010101) & ~(X) & 0x80808080) +#else +#if LONG_MAX == 9223372036854775807L +/* Nonzero if X (a long int) contains a NULL byte. */ +#define DETECTNULL(X) (((X) - 0x0101010101010101) & ~(X) & 0x8080808080808080) +#else +#error long int is not a 32bit or 64bit type. +#endif +#endif + +#ifndef DETECTNULL +#error long int is not a 32bit or 64bit byte +#endif + +#undef TOO_SMALL +#define TOO_SMALL(LEN) ((LEN) < sizeof (long)) + +#if MESOSPHERE_LIBC_STRNCMP_GENERIC + +char * +strncpy (char *__restrict dst0, + const char *__restrict src0, + size_t count) +{ +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) + char *dscan; + const char *sscan; + + dscan = dst0; + sscan = src0; + while (count > 0) + { + --count; + if ((*dscan++ = *sscan++) == '\0') + break; + } + while (count-- > 0) + *dscan++ = '\0'; + + return dst0; +#else + char *dst = dst0; + const char *src = src0; + long *aligned_dst; + const long *aligned_src; + + /* If SRC and DEST is aligned and count large enough, then copy words. */ + if (!UNALIGNED (src, dst) && !TOO_SMALL (count)) + { + aligned_dst = (long*)dst; + aligned_src = (long*)src; + + /* SRC and DEST are both "long int" aligned, try to do "long int" + sized copies. */ + while (count >= sizeof (long int) && !DETECTNULL(*aligned_src)) + { + count -= sizeof (long int); + *aligned_dst++ = *aligned_src++; + } + + dst = (char*)aligned_dst; + src = (char*)aligned_src; + } + + while (count > 0) + { + --count; + if ((*dst++ = *src++) == '\0') + break; + } + + while (count-- > 0) + *dst++ = '\0'; + + return dst0; +#endif /* not PREFER_SIZE_OVER_SPEED */ +} + +#endif /* MESOSPHERE_LIBC_STRNCPY_GENERIC */ + +/* +FUNCTION + <<strncmp>>---character string compare + +INDEX + strncmp +SYNOPSIS + #include <string.h> + int strncmp(const char *<[a]>, const char * <[b]>, size_t <[length]>); +DESCRIPTION + <<strncmp>> compares up to <[length]> characters + from the string at <[a]> to the string at <[b]>. +RETURNS + If <<*<[a]>>> sorts lexicographically after <<*<[b]>>>, + <<strncmp>> returns a number greater than zero. If the two + strings are equivalent, <<strncmp>> returns zero. If <<*<[a]>>> + sorts lexicographically before <<*<[b]>>>, <<strncmp>> returns a + number less than zero. +PORTABILITY +<<strncmp>> is ANSI C. +<<strncmp>> requires no supporting OS subroutines. +QUICKREF + strncmp ansi pure +*/ + +#include <string.h> +#include <limits.h> + +/* Nonzero if either X or Y is not aligned on a "long" boundary. */ +#define UNALIGNED(X, Y) \ + (((long)X & (sizeof (long) - 1)) | ((long)Y & (sizeof (long) - 1))) + +/* DETECTNULL returns nonzero if (long)X contains a NULL byte. */ +#if LONG_MAX == 2147483647L +#define DETECTNULL(X) (((X) - 0x01010101) & ~(X) & 0x80808080) +#else +#if LONG_MAX == 9223372036854775807L +#define DETECTNULL(X) (((X) - 0x0101010101010101) & ~(X) & 0x8080808080808080) +#else +#error long int is not a 32bit or 64bit type. +#endif +#endif + +#ifndef DETECTNULL +#error long int is not a 32bit or 64bit byte +#endif + +#if MESOSPHERE_LIBC_STRNCMP_GENERIC + +int +strncmp (const char *s1, + const char *s2, + size_t n) +{ +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) + if (n == 0) + return 0; + + while (n-- != 0 && *s1 == *s2) + { + if (n == 0 || *s1 == '\0') + break; + s1++; + s2++; + } + + return (*(unsigned char *) s1) - (*(unsigned char *) s2); +#else + unsigned long *a1; + unsigned long *a2; + + if (n == 0) + return 0; + + /* If s1 or s2 are unaligned, then compare bytes. */ + if (!UNALIGNED (s1, s2)) + { + /* If s1 and s2 are word-aligned, compare them a word at a time. */ + a1 = (unsigned long*)s1; + a2 = (unsigned long*)s2; + while (n >= sizeof (long) && *a1 == *a2) + { + n -= sizeof (long); + + /* If we've run out of bytes or hit a null, return zero + since we already know *a1 == *a2. */ + if (n == 0 || DETECTNULL (*a1)) + return 0; + + a1++; + a2++; + } + + /* A difference was detected in last few bytes of s1, so search bytewise */ + s1 = (char*)a1; + s2 = (char*)a2; + } + + while (n-- > 0 && *s1 == *s2) + { + /* If we've run out of bytes or hit a null, return zero + since we already know *s1 == *s2. */ + if (n == 0 || *s1 == '\0') + return 0; + s1++; + s2++; + } + return (*(unsigned char *) s1) - (*(unsigned char *) s2); +#endif /* not PREFER_SIZE_OVER_SPEED */ +} + +#endif /* MESOSPHERE_LIBC_STRNCMP_GENERIC */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/Makefile b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/Makefile new file mode 100644 index 00000000..15a6367b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/Makefile @@ -0,0 +1,42 @@ +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)/kernel_ldr.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)/kernel_ldr.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,)) +$(eval $(call ATMOSPHERE_ADD_TARGETS, qemu_virt_a57, qemu-virt, 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/mesosphere/kernel_ldr/kernel_ldr.ld b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/kernel_ldr.ld new file mode 100644 index 00000000..26802a6f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/kernel_ldr.ld @@ -0,0 +1,204 @@ +OUTPUT_ARCH(aarch64) +ENTRY(_start) + +PHDRS +{ + krnlldr PT_LOAD FLAGS(7) /* Read | Write | Execute */; + dyn PT_DYNAMIC; +} + +SECTIONS +{ + /* =========== CODE section =========== */ + PROVIDE(__start__ = 0x0); + . = __start__; + __bin_start__ = .; + __code_start = . ; + + .crt0 : + { + KEEP (*(.crt0 .crt0.*)) + . = ALIGN(8); + } :krnlldr + + .init : + { + KEEP( *(.init) ) + . = ALIGN(8); + } :krnlldr + + .plt : + { + *(.plt) + *(.iplt) + . = ALIGN(8); + } :krnlldr + + .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); + } :krnlldr + + .fini : + { + KEEP( *(.fini) ) + . = ALIGN(8); + } :krnlldr + + /* .vectors. */ + . = ALIGN(2K); + __vectors_start__ = . ; + .vectors : + { + KEEP( *(.vectors) ) + . = ALIGN(8); + } :krnlldr + + /* =========== RODATA section =========== */ + . = ALIGN(8); + __rodata_start = . ; + + .rodata : + { + *(.rodata .rodata.* .gnu.linkonce.r.*) + . = ALIGN(8); + } :krnlldr + + .eh_frame_hdr : { __eh_frame_hdr_start = .; *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) __eh_frame_hdr_end = .; } :krnlldr + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } :krnlldr + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } :krnlldr + .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } : rodata + + .dynamic : { *(.dynamic) } :krnlldr :dyn + .rela.dyn : { *(.rela.*) } :krnlldr + .relr.dyn : { *(.relr.*) } :krnlldr + .hash : { *(.hash) } :krnlldr + .gnu.hash : { *(.gnu.hash) } :krnlldr + .gnu.version : { *(.gnu.version) } :krnlldr + .gnu.version_d : { *(.gnu.version_d) } :krnlldr + .gnu.version_r : { *(.gnu.version_r) } :krnlldr + + /* =========== DATA section =========== */ + . = ALIGN(8); + __data_start = . ; + + .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } :krnlldr + .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } :krnlldr + .gnu_extab : ONLY_IF_RW { *(.gnu_extab*) } : data + .exception_ranges : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) } :krnlldr + + .preinit_array ALIGN(8) : + { + PROVIDE (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE (__preinit_array_end = .); + } :krnlldr + + .init_array ALIGN(8) : + { + PROVIDE (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + PROVIDE (__init_array_end = .); + } :krnlldr + + .fini_array ALIGN(8) : + { + PROVIDE (__fini_array_start = .); + KEEP (*(.fini_array)) + KEEP (*(SORT(.fini_array.*))) + PROVIDE (__fini_array_end = .); + } :krnlldr + + .ctors ALIGN(8) : + { + KEEP (*crtbegin.o(.ctors)) /* MUST be first -- GCC requires it */ + KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + } :krnlldr + + .dtors ALIGN(8) : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + } :krnlldr + + __got_start__ = .; + + .got : { *(.got) *(.igot) } :krnlldr + .got.plt : { *(.got.plt) *(.igot.plt) } :krnlldr + + __got_end__ = .; + + .data ALIGN(8) : + { + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + } :krnlldr + + __bss_start__ = .; + .bss ALIGN(8) : + { + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + . = ALIGN(16); + + /* Reserve space for the stack */ + __stack_start = .; + . += 0x1000; + __stack_end = .; + } :krnlldr + __bss_end__ = .; + + __bin_end__ = .; + __end__ = ABSOLUTE(.) ; + + /* ================== + ==== Metadata ==== + ================== */ + + /* Discard sections that difficult post-processing */ + /DISCARD/ : { *(.group .comment .note .interp .dynsym .dynstr) } + + /* 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/mesosphere/kernel_ldr/kernel_ldr.mk b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/kernel_ldr.mk new file mode 100644 index 00000000..9d9896fa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/kernel_ldr.mk @@ -0,0 +1,112 @@ +#--------------------------------------------------------------------------------- +# pull in common atmosphere configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(CURRENT_DIRECTORY)/../../libraries/config/templates/mesosphere.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) + +#--------------------------------------------------------------------------------- +# 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)/libmesosphere/$(ATMOSPHERE_LIBRARY_DIR)/libmesosphere.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)/libmesosphere/$(ATMOSPHERE_LIBRARY_DIR)/libmesosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBMESOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libmesosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libmesosphere/libmesosphere.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 $< $@ + @echo built ... $(notdir $@) + +$(OUTPUT).elf : $(OFILES) $(ATMOSPHERE_LIBRARIES_DIR)/libmesosphere/$(ATMOSPHERE_LIBRARY_DIR)/libmesosphere.a + +%.elf: + @echo linking $(notdir $@) + $(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + @$(NM) -CSn $@ > $(notdir $*.lst) + +$(OFILES_SRC) : $(HFILES_BIN) + +kern_libc_generic.o: CFLAGS += -fno-builtin +kern_kernel_instantiations.o: CXXFLAGS += -flto + +#--------------------------------------------------------------------------------- +# 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 +#--------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/kernel_ldr.specs b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/kernel_ldr.specs new file mode 100644 index 00000000..42d7e341 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/kernel_ldr.specs @@ -0,0 +1,7 @@ +%rename link old_link + +*link: +%(old_link) -T %:getenv(ATMOSPHERE_TOPDIR /kernel_ldr.ld) -pie --gc-sections -z text -z nodynamic-undefined-weak -z pack-relative-relocs -nostdlib + +*startfile: +crti%O%s crtbegin%O%s diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/arch/arm64/exceptions.s b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/arch/arm64/exceptions.s new file mode 100644 index 00000000..d8b20625 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/arch/arm64/exceptions.s @@ -0,0 +1,161 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* Some macros taken from https://github.com/ARM-software/arm-trusted-firmware/blob/master/include/common/aarch64/asm_macros.S */ +/* + * Copyright (c) 2013-2017, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/* + * Declare the exception vector table, enforcing it is aligned on a + * 2KB boundary, as required by the ARMv8 architecture. + * Use zero bytes as the fill value to be stored in the padding bytes + * so that it inserts illegal AArch64 instructions. This increases + * security, robustness and potentially facilitates debugging. + */ +.macro vector_base label, section_name=.vectors +.section \section_name, "ax" +.align 11, 0 +\label: +.endm + +/* + * Create an entry in the exception vector table, enforcing it is + * aligned on a 128-byte boundary, as required by the ARMv8 architecture. + * Use zero bytes as the fill value to be stored in the padding bytes + * so that it inserts illegal AArch64 instructions. This increases + * security, robustness and potentially facilitates debugging. + */ +.macro vector_entry label, section_name=.vectors +.cfi_sections .debug_frame +.section \section_name, "ax" +.align 7, 0 +.type \label, %function +.func \label +.cfi_startproc +\label: +.endm + +/* + * This macro verifies that the given vector doesnt exceed the + * architectural limit of 32 instructions. This is meant to be placed + * immediately after the last instruction in the vector. It takes the + * vector entry as the parameter + */ +.macro check_vector_size since + .endfunc + .cfi_endproc + .if (. - \since) > (32 * 4) + .error "Vector exceeds 32 instructions" + .endif +.endm + +/* Actual Vectors for KernelLdr. */ +.global kernelldr_vectors +vector_base kernelldr_vectors + +/* Current EL, SP0 */ +.global unknown_exception +unknown_exception: +vector_entry synch_sp0 + /* Just infinite loop. */ + b unknown_exception + check_vector_size synch_sp0 + +vector_entry irq_sp0 + b unknown_exception + check_vector_size irq_sp0 + +vector_entry fiq_sp0 + b unknown_exception + check_vector_size fiq_sp0 + +vector_entry serror_sp0 + b unknown_exception + check_vector_size serror_sp0 + +/* Current EL, SPx */ +vector_entry synch_spx + b restore_tpidr_el1 + check_vector_size synch_spx + +vector_entry irq_spx + b unknown_exception + check_vector_size irq_spx + +vector_entry fiq_spx + b unknown_exception + check_vector_size fiq_spx + +vector_entry serror_spx + b unknown_exception + check_vector_size serror_spx + +/* Lower EL, A64 */ +vector_entry synch_a64 + b unknown_exception + check_vector_size synch_a64 + +vector_entry irq_a64 + b unknown_exception + check_vector_size irq_a64 + +vector_entry fiq_a64 + b unknown_exception + check_vector_size fiq_a64 + +vector_entry serror_a64 + b unknown_exception + check_vector_size serror_a64 + +/* Lower EL, A32 */ +vector_entry synch_a32 + b unknown_exception + check_vector_size synch_a32 + +vector_entry irq_a32 + b unknown_exception + check_vector_size irq_a32 + +vector_entry fiq_a32 + b unknown_exception + check_vector_size fiq_a32 + +vector_entry serror_a32 + b unknown_exception + .endfunc + .cfi_endproc +/* To save space, insert in an unused vector segment. */ +.global restore_tpidr_el1 +.type restore_tpidr_el1, %function +restore_tpidr_el1: + mrs x0, tpidr_el1 + /* Make sure that TPIDR_EL1 can be dereferenced. */ + invalid_tpidr: + cbz x0, invalid_tpidr + /* Restore saved registers. */ + ldp x19, x20, [x0], #0x10 + ldp x21, x22, [x0], #0x10 + ldp x23, x24, [x0], #0x10 + ldp x25, x26, [x0], #0x10 + ldp x27, x28, [x0], #0x10 + ldp x29, x30, [x0], #0x10 + ldp x1, xzr, [x0], #0x10 + mov sp, x1 + mov x0, #0x1 + ret \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/arch/arm64/kern_init_loader_asm.s b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/arch/arm64/kern_init_loader_asm.s new file mode 100644 index 00000000..533f74dc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/arch/arm64/kern_init_loader_asm.s @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +.section .text._ZN3ams4kern4init6loader23SaveRegistersToTpidrEl1EPv, "ax", %progbits +.global _ZN3ams4kern4init6loader23SaveRegistersToTpidrEl1EPv +_ZN3ams4kern4init6loader23SaveRegistersToTpidrEl1EPv: + /* Set TPIDR_EL1 to the input register. */ + msr tpidr_el1, x0 + + /* Save registers to the region specified. */ + mov x1, sp + stp x19, x20, [x0], #0x10 + stp x21, x22, [x0], #0x10 + stp x23, x24, [x0], #0x10 + stp x25, x26, [x0], #0x10 + stp x27, x28, [x0], #0x10 + stp x29, x30, [x0], #0x10 + stp x1, xzr, [x0], #0x10 + mov x0, #0x0 + ret + +.section .text._ZN3ams4kern4init6loader22VerifyAndClearTpidrEl1EPv, "ax", %progbits +.global _ZN3ams4kern4init6loader22VerifyAndClearTpidrEl1EPv +_ZN3ams4kern4init6loader22VerifyAndClearTpidrEl1EPv: + /* Get system register area from thread-specific processor id */ + mrs x1, tpidr_el1 + + /* We require here that the region registers are saved is same as input. */ + cmp x0, x1 + invalid_tpidr: + b.ne invalid_tpidr + + /* Clear TPIDR_EL1. */ + msr tpidr_el1, xzr + ret diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/arch/arm64/start.s b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/arch/arm64/start.s new file mode 100644 index 00000000..27a31472 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/arch/arm64/start.s @@ -0,0 +1,160 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* 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 + +#define LOAD_IMMEDIATE_32(reg, val) \ + mov reg, #(((val) >> 0x00) & 0xFFFF); \ + movk reg, #(((val) >> 0x10) & 0xFFFF), lsl#16 + +.section .crt0.text.start, "ax", %progbits +.global _start +_start: + b _main +__metadata_begin: + .ascii "MLD0" /* Magic */ +__metadata_target_firmware: + .word 0xCCCCCCCC /* Target Firmware. */ +__metadata_reserved: + .word 0xCCCCCCCC /* Reserved. */ +_main: + /* KernelLdr_Main(uintptr_t kernel_base_address, KernelMap *kernel_map, uintptr_t ini1_base_address); */ + adr x18, _start + adr x16, __external_references + ldr x17, [x16, #0x8] /* bss end */ + ldr x16, [x16, #0x0] /* bss start */ + add x16, x16, x18 + add x17, x17, x18 + clear_bss: + cmp x16, x17 + b.cs clear_bss_done + str xzr, [x16],#0x8 + b clear_bss + clear_bss_done: + adr x17, __external_references + ldr x17, [x17, #0x10] /* stack top */ + add sp, x17, x18 + + /* Stack is now set up, so save important state. */ + sub sp, sp, #0x30 + stp x0, x1, [sp, #0x00] + stp x2, x30, [sp, #0x10] + stp xzr, xzr, [sp, #0x20] + +#ifdef ATMOSPHERE_BOARD_NINTENDO_NX + /* Get the target firmware from exosphere. */ + LOAD_IMMEDIATE_32(w0, 0xC3000004) + mov w1, #65000 + smc #1 + cmp x0, #0 +0: + b.ne 0b + + /* Store the target firmware. */ + adr x0, __metadata_target_firmware + str w1, [x0] +#endif + + /* Apply relocations and call init array for KernelLdr. */ + adr x0, _start + adr x1, __external_references + ldr x1, [x1, #0x18] /* .dynamic. */ + add x1, x0, x1 + + /* branch to ams::kern::init::Elf::ApplyRelocations(uintptr_t, const ams::kern::init::Elf::Elf64::Dyn *); */ + bl _ZN3ams4kern4init3Elf16ApplyRelocationsEmPKNS2_5Elf643DynE + + /* branch to ams::kern::init::Elf::CallInitArrayFuncs(uintptr_t, uintptr_t) */ + adr x2, _start + adr x1, __external_references + ldr x0, [x1, #0x20] /* init_array_start */ + ldr x1, [x1, #0x28] /* init_array_end */ + add x0, x0, x2 + add x1, x1, x2 + bl _ZN3ams4kern4init3Elf18CallInitArrayFuncsEmm + + /* Setup system registers, for detection of errors during init later. */ + msr tpidr_el1, xzr + msr cntv_cval_el0, xzr + adr x0, __external_references + adr x1, _start + ldr x0, [x0, #0x30] + add x0, x1, x0 + msr vbar_el1, x0 + isb + + /* Call ams::kern::init::loader::Main(uintptr_t, ams::kern::init::KernelLayout *, uintptr_t) */ + ldp x0, x1, [sp, #0x00] + ldr x2, [sp, #0x10] + + bl _ZN3ams4kern4init6loader4MainEmPNS1_12KernelLayoutEm + str x0, [sp, #0x00] + + /* Get ams::kern::init::loader::AllocateKernelInitStack(). */ + bl _ZN3ams4kern4init6loader23AllocateKernelInitStackEv + str x0, [sp, #0x20] + + + /* Call ams::kern::init::loader::GetFinalState() */ + bl _ZN3ams4kern4init6loader13GetFinalStateEv + + /* X0 is now the saved state. */ + /* We will return this to the kernel. */ + + /* Adjust return address to point to the relocated kernel. */ + ldr x1, [sp, #0x18] /* Return address to Kernel */ + ldr x2, [sp, #0x00] /* Relocated kernel base address diff. */ + add x1, x2, x1 + + /* Translate the relocated address back to a physical address. */ + and x4, x1, #0xFFF + sub x3, x1, x4 + at s1e1r, x3 + isb + mrs x3, par_el1 +1: + tbnz w3, #0, 1b + and x3, x3, #0xFFFFFFFFF000 + add x3, x3, x4 + + /* Return the difference between relocated and physical in x1. */ + sub x1, x1, x3 + + /* Setup stack, and return to the kernel. */ + ldr x2, [sp, #0x20] + mov sp, x2 + br x3 + +#ifdef ATMOSPHERE_BOARD_NINTENDO_NX +.global _ZN3ams4kern17GetTargetFirmwareEv +.type _ZN3ams4kern17GetTargetFirmwareEv, %function +_ZN3ams4kern17GetTargetFirmwareEv: + adr x0, __metadata_target_firmware + ldr w0, [x0] + ret +#endif + +.balign 8 +__external_references: + .quad __bss_start__ - _start + .quad __bss_end__ - _start + .quad __stack_end - _start + .quad _DYNAMIC - _start + .quad __init_array_start - _start + .quad __init_array_end - _start + .quad __vectors_start__ - _start \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/board/nintendo/nx/kern_init_loader_board_setup.cpp b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/board/nintendo/nx/kern_init_loader_board_setup.cpp new file mode 100644 index 00000000..e88c42e8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/board/nintendo/nx/kern_init_loader_board_setup.cpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> +#include "../../../kern_init_loader_board_setup.hpp" + +namespace ams::kern::init::loader { + + void PerformBoardSpecificSetup() { + /* ... */ + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/kern_init_loader.cpp b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/kern_init_loader.cpp new file mode 100644 index 00000000..43979905 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/kern_init_loader.cpp @@ -0,0 +1,295 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> +#include "kern_init_loader_board_setup.hpp" + +/* Necessary for calculating kernelldr size/base for initial identity mapping */ +extern "C" { + + extern const u8 __bin_start__[]; + extern const u8 __bin_end__[]; + +} + +namespace ams::kern::init::loader { + + namespace { + + constexpr uintptr_t KernelBaseAlignment = 0x200000; + constexpr uintptr_t KernelBaseRangeStart = 0xFFFFFF8000000000; + constexpr uintptr_t KernelBaseRangeEnd = 0xFFFFFFFFFFE00000; + constexpr uintptr_t KernelBaseRangeLast = KernelBaseRangeEnd - 1; + static_assert(util::IsAligned(KernelBaseRangeStart, KernelBaseAlignment)); + static_assert(util::IsAligned(KernelBaseRangeEnd, KernelBaseAlignment)); + static_assert(KernelBaseRangeStart <= KernelBaseRangeLast); + + static_assert(InitialProcessBinarySizeMax <= KernelResourceSize); + + constexpr size_t InitialPageTableRegionSizeMax = 2_MB; + static_assert(InitialPageTableRegionSizeMax < KernelPageTableHeapSize + KernelInitialPageHeapSize); + + /* Global Allocator. */ + constinit KInitialPageAllocator g_initial_page_allocator; + + constinit KInitialPageAllocator::State g_final_page_allocator_state; + constinit InitialProcessBinaryLayoutWithSize g_initial_process_binary_meta; + + constinit void *g_final_state[2]; + + void RelocateKernelPhysically(uintptr_t &base_address, KernelLayout *&layout, const uintptr_t &ini_base_address) { + /* Adjust layout to be correct. */ + { + const ptrdiff_t layout_offset = reinterpret_cast<uintptr_t>(layout) - base_address; + layout->rx_offset += layout_offset; + layout->rx_end_offset += layout_offset; + layout->ro_offset += layout_offset; + layout->ro_end_offset += layout_offset; + layout->rw_offset += layout_offset; + layout->rw_end_offset += layout_offset; + layout->bss_offset += layout_offset; + layout->bss_end_offset += layout_offset; + layout->resource_offset += layout_offset; + layout->dynamic_offset += layout_offset; + layout->init_array_offset += layout_offset; + layout->init_array_end_offset += layout_offset; + layout->sysreg_offset += layout_offset; + } + + /* Relocate the kernel if necessary. */ + KPhysicalAddress correct_base = KSystemControl::Init::GetKernelPhysicalBaseAddress(base_address); + if (correct_base != base_address) { + const uintptr_t diff = GetInteger(correct_base) - base_address; + const size_t size = layout->rw_end_offset; + + /* Check that the new kernel doesn't overlap with us. */ + MESOSPHERE_INIT_ABORT_UNLESS((GetInteger(correct_base) >= reinterpret_cast<uintptr_t>(__bin_end__)) || (GetInteger(correct_base) + size <= reinterpret_cast<uintptr_t>(__bin_start__))); + + /* Check that the new kernel doesn't overlap with the initial process binary. */ + MESOSPHERE_INIT_ABORT_UNLESS((ini_base_address + InitialProcessBinarySizeMax <= GetInteger(correct_base)) || (GetInteger(correct_base) + size <= ini_base_address)); + + /* Conversion from KPhysicalAddress to void * is safe here, because MMU is not set up yet. */ + std::memmove(reinterpret_cast<void *>(GetInteger(correct_base)), reinterpret_cast<void *>(base_address), size); + base_address += diff; + layout = reinterpret_cast<KernelLayout *>(reinterpret_cast<uintptr_t>(layout) + diff); + } + } + + void SetupInitialIdentityMapping(KInitialPageTable &init_pt, uintptr_t base_address, uintptr_t kernel_size, uintptr_t page_table_region, size_t page_table_region_size, KInitialPageAllocator &allocator, KernelSystemRegisters *sysregs) { + /* Map in an RWX identity mapping for the kernel. */ + constexpr PageTableEntry KernelRWXIdentityAttribute(PageTableEntry::Permission_KernelRWX, PageTableEntry::PageAttribute_NormalMemory, PageTableEntry::Shareable_InnerShareable, PageTableEntry::MappingFlag_Mapped); + init_pt.Map(base_address, kernel_size, base_address, KernelRWXIdentityAttribute, allocator, 0); + + /* Map in an RWX identity mapping for ourselves. */ + constexpr PageTableEntry KernelLdrRWXIdentityAttribute(PageTableEntry::Permission_KernelRWX, PageTableEntry::PageAttribute_NormalMemory, PageTableEntry::Shareable_InnerShareable, PageTableEntry::MappingFlag_Mapped); + const uintptr_t kernel_ldr_base = util::AlignDown(reinterpret_cast<uintptr_t>(__bin_start__), PageSize); + const uintptr_t kernel_ldr_size = util::AlignUp(reinterpret_cast<uintptr_t>(__bin_end__), PageSize) - kernel_ldr_base; + init_pt.Map(kernel_ldr_base, kernel_ldr_size, kernel_ldr_base, KernelLdrRWXIdentityAttribute, allocator, 0); + + /* Map in the page table region as RW- for ourselves. */ + constexpr PageTableEntry PageTableRegionRWAttribute(PageTableEntry::Permission_KernelRW, PageTableEntry::PageAttribute_NormalMemory, PageTableEntry::Shareable_InnerShareable, PageTableEntry::MappingFlag_Mapped); + init_pt.Map(page_table_region, page_table_region_size, page_table_region, PageTableRegionRWAttribute, allocator, 0); + + /* Place the L1 table addresses in the relevant system registers. */ + cpu::SetTtbr0El1(init_pt.GetTtbr0L1TableAddress()); + cpu::SetTtbr1El1(init_pt.GetTtbr1L1TableAddress()); + + /* Setup MAIR_EL1, TCR_EL1. */ + /* TODO: Define these bits properly elsewhere, document exactly what each bit set is doing .*/ + constexpr u64 MairValue = 0x0000000044FF0400ul; + constexpr u64 TcrValue = 0x00000011B5193519ul; + cpu::MemoryAccessIndirectionRegisterAccessor(MairValue).Store(); + cpu::TranslationControlRegisterAccessor(TcrValue).Store(); + + /* Ensure that our configuration takes before proceeding. */ + cpu::EnsureInstructionConsistency(); + + /* Perform board-specific setup. */ + PerformBoardSpecificSetup(); + + /* Setup SCTLR_EL1. */ + /* TODO: Define these bits properly elsewhere, document exactly what each bit set is doing .*/ + constexpr u64 SctlrValue = 0x0000000034D5D92Dul; + cpu::SetSctlrEl1(SctlrValue); + cpu::InstructionMemoryBarrier(); + + /* Setup the system registers for other cores. */ + /* NOTE: sctlr_el1 on other cores has the WXN bit set (0x80000); this will be set before KernelMain() on this core. */ + sysregs->ttbr0_el1 = init_pt.GetTtbr0L1TableAddress(); + sysregs->ttbr1_el1 = init_pt.GetTtbr1L1TableAddress(); + sysregs->tcr_el1 = TcrValue; + sysregs->mair_el1 = MairValue; + sysregs->sctlr_el1 = SctlrValue | 0x80000; + } + + KVirtualAddress GetRandomKernelBaseAddress(KInitialPageTable &page_table, KPhysicalAddress phys_base_address, size_t kernel_size) { + /* Define useful values for random generation. */ + + const uintptr_t kernel_offset = GetInteger(phys_base_address) % KernelBaseAlignment; + + /* Repeatedly generate a random virtual address until we get one that's unmapped in the destination page table. */ + while (true) { + const uintptr_t random_kaslr_slide = KSystemControl::Init::GenerateRandomRange(KernelBaseRangeStart / KernelBaseAlignment, KernelBaseRangeLast / KernelBaseAlignment); + const KVirtualAddress kernel_region_start = random_kaslr_slide * KernelBaseAlignment; + const KVirtualAddress kernel_region_end = kernel_region_start + util::AlignUp(kernel_offset + kernel_size, KernelBaseAlignment); + const size_t kernel_region_size = GetInteger(kernel_region_end) - GetInteger(kernel_region_start); + + /* Make sure the region has not overflowed */ + if (kernel_region_start >= kernel_region_end) { + continue; + } + + /* Make sure that the region stays within our intended bounds. */ + if (kernel_region_end > KernelBaseRangeEnd) { + continue; + } + + /* Validate we can map the range we've selected. */ + if (!page_table.IsFree(kernel_region_start, kernel_region_size)) { + continue; + } + + /* Our range is valid! */ + return kernel_region_start + kernel_offset; + } + } + + } + + uintptr_t Main(uintptr_t base_address, KernelLayout *layout, uintptr_t ini_base_address) { + /* Relocate the kernel to the correct physical base address. */ + /* Base address and layout are passed by reference and modified. */ + RelocateKernelPhysically(base_address, layout, ini_base_address); + + /* Validate kernel layout. */ + const uintptr_t rx_offset = layout->rx_offset; + const uintptr_t rx_end_offset = layout->rx_end_offset; + const uintptr_t ro_offset = layout->ro_offset; + const uintptr_t ro_end_offset = layout->ro_end_offset; + const uintptr_t rw_offset = layout->rw_offset; + /* UNUSED: const uintptr_t rw_end_offset = layout->rw_end_offset; */ + const uintptr_t bss_end_offset = layout->bss_end_offset; + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(rx_offset, PageSize)); + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(rx_end_offset, PageSize)); + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(ro_offset, PageSize)); + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(ro_end_offset, PageSize)); + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(rw_offset, PageSize)); + MESOSPHERE_INIT_ABORT_UNLESS(util::IsAligned(bss_end_offset, PageSize)); + const uintptr_t bss_offset = layout->bss_offset; + const uintptr_t resource_offset = layout->resource_offset; + const uintptr_t dynamic_offset = layout->dynamic_offset; + const uintptr_t init_array_offset = layout->init_array_offset; + const uintptr_t init_array_end_offset = layout->init_array_end_offset; + const uintptr_t sysreg_offset = layout->sysreg_offset; + + /* Determine the size of the resource region. */ + const size_t resource_region_size = KMemoryLayout::GetResourceRegionSizeForInit(KSystemControl::Init::ShouldIncreaseThreadResourceLimit()); + const uintptr_t resource_end_address = base_address + resource_offset + resource_region_size; + + /* Setup the INI1 header in memory for the kernel. */ + { + /* Get the kernel layout. */ + KSystemControl::Init::GetInitialProcessBinaryLayout(std::addressof(g_initial_process_binary_meta.layout), base_address); + + /* If there's no desired base address, use the ini in place. */ + if (g_initial_process_binary_meta.layout.address == 0) { + g_initial_process_binary_meta.layout.address = ini_base_address; + } + + + /* Validate and potentially relocate the INI. */ + const InitialProcessBinaryHeader *ini_header = reinterpret_cast<const InitialProcessBinaryHeader *>(ini_base_address); + size_t ini_size = 0; + if (ini_header->magic == InitialProcessBinaryMagic && (ini_size = ini_header->size) <= InitialProcessBinarySizeMax) { + /* INI is valid, relocate it if necessary. */ + if (ini_base_address != g_initial_process_binary_meta.layout.address) { + std::memmove(reinterpret_cast<void *>(g_initial_process_binary_meta.layout.address), ini_header, ini_size); + } + } else { + /* INI is invalid. Make the destination header invalid. */ + std::memset(reinterpret_cast<void *>(g_initial_process_binary_meta.layout.address), 0, sizeof(InitialProcessBinaryHeader)); + } + + /* Set the INI size in layout. */ + g_initial_process_binary_meta.size = util::AlignUp(ini_size, PageSize); + } + + /* We want to start allocating page tables at the end of the resource region. */ + g_initial_page_allocator.Initialize(resource_end_address); + + /* Make a new page table for TTBR1_EL1. */ + KInitialPageTable init_pt(KernelBaseRangeStart, KernelBaseRangeLast, g_initial_page_allocator); + + /* Setup initial identity mapping. TTBR1 table passed by reference. */ + SetupInitialIdentityMapping(init_pt, base_address, bss_end_offset, resource_end_address, InitialPageTableRegionSizeMax, g_initial_page_allocator, reinterpret_cast<KernelSystemRegisters *>(base_address + sysreg_offset)); + + /* NOTE: On 19.0.0+, Nintendo calls an unknown function here on init_pt and g_initial_page_allocator. */ + /* This is stubbed in prod KernelLdr. */ + + /* Generate a random slide for the kernel's base address. */ + const KVirtualAddress virtual_base_address = GetRandomKernelBaseAddress(init_pt, base_address, bss_end_offset); + + /* Map kernel .text as R-X. */ + constexpr PageTableEntry KernelTextAttribute(PageTableEntry::Permission_KernelRX, PageTableEntry::PageAttribute_NormalMemory, PageTableEntry::Shareable_InnerShareable, PageTableEntry::MappingFlag_Mapped); + init_pt.Map(virtual_base_address + rx_offset, rx_end_offset - rx_offset, base_address + rx_offset, KernelTextAttribute, g_initial_page_allocator, 0); + + /* Map kernel .rodata and .rwdata as RW-. */ + /* Note that we will later reprotect .rodata as R-- */ + constexpr PageTableEntry KernelRoDataAttribute(PageTableEntry::Permission_KernelR, PageTableEntry::PageAttribute_NormalMemory, PageTableEntry::Shareable_InnerShareable, PageTableEntry::MappingFlag_Mapped); + constexpr PageTableEntry KernelRwDataAttribute(PageTableEntry::Permission_KernelRW, PageTableEntry::PageAttribute_NormalMemory, PageTableEntry::Shareable_InnerShareable, PageTableEntry::MappingFlag_Mapped); + init_pt.Map(virtual_base_address + ro_offset, ro_end_offset - ro_offset, base_address + ro_offset, KernelRwDataAttribute, g_initial_page_allocator, 0); + init_pt.Map(virtual_base_address + rw_offset, bss_end_offset - rw_offset, base_address + rw_offset, KernelRwDataAttribute, g_initial_page_allocator, 0); + + /* Physically randomize the kernel region. */ + /* NOTE: Nintendo does this only on 10.0.0+ */ + init_pt.PhysicallyRandomize(virtual_base_address + rx_offset, bss_end_offset - rx_offset, true); + + /* Apply relocations to the kernel. */ + const Elf::Dyn *kernel_dynamic = reinterpret_cast<const Elf::Dyn *>(GetInteger(virtual_base_address) + dynamic_offset); + Elf::ApplyRelocations(GetInteger(virtual_base_address), kernel_dynamic); + + /* Clear kernel .bss. */ + /* NOTE: The kernel does this before applying relocations, but we do it after. */ + /* This allows us to place our relocations in space overlapping with .bss...and thereby reclaim the memory that would otherwise be wasted. */ + std::memset(GetVoidPointer(virtual_base_address + bss_offset), 0, bss_end_offset - bss_offset); + + /* Call the kernel's init array functions. */ + /* NOTE: The kernel does this after reprotecting .rodata, but we do it before. */ + /* This allows our global constructors to edit .rodata, which is valuable for editing the SVC tables to support older firmwares' ABIs. */ + Elf::CallInitArrayFuncs(GetInteger(virtual_base_address) + init_array_offset, GetInteger(virtual_base_address) + init_array_end_offset); + + /* Reprotect .rodata as R-- */ + init_pt.Reprotect(virtual_base_address + ro_offset, ro_end_offset - ro_offset, KernelRwDataAttribute, KernelRoDataAttribute); + + /* Return the difference between the random virtual base and the physical base. */ + return GetInteger(virtual_base_address) - base_address; + } + + KPhysicalAddress AllocateKernelInitStack() { + return g_initial_page_allocator.Allocate(PageSize) + PageSize; + } + + void **GetFinalState() { + /* Get final page allocator state. */ + g_initial_page_allocator.GetFinalState(std::addressof(g_final_page_allocator_state)); + + /* Setup final kernel loader state. */ + g_final_state[0] = std::addressof(g_final_page_allocator_state); + g_final_state[1] = std::addressof(g_initial_process_binary_meta); + + return g_final_state; + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/kern_init_loader_asm.hpp b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/kern_init_loader_asm.hpp new file mode 100644 index 00000000..e6320168 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/kern_init_loader_asm.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere.hpp> + +namespace ams::kern::init::loader { + + struct SavedRegisterState { + u64 x[(30 - 19) + 1]; + u64 sp; + u64 xzr; + }; + static_assert(sizeof(SavedRegisterState) == 0x70); + + int SaveRegistersToTpidrEl1(void *tpidr_el1); + void VerifyAndClearTpidrEl1(void *tpidr_el1); + +} diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/kern_init_loader_board_default_setup.arch.arm64.cpp b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/kern_init_loader_board_default_setup.arch.arm64.cpp new file mode 100644 index 00000000..6c796c6f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/kern_init_loader_board_default_setup.arch.arm64.cpp @@ -0,0 +1,93 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> +#include "kern_init_loader_asm.hpp" +#include "kern_init_loader_board_setup.hpp" + +namespace ams::kern::init::loader { + + void PerformDefaultAarch64SpecificSetup() { + SavedRegisterState saved_registers; + SaveRegistersToTpidrEl1(std::addressof(saved_registers)); + ON_SCOPE_EXIT { VerifyAndClearTpidrEl1(std::addressof(saved_registers)); }; + + /* Main ID specific setup. */ + cpu::MainIdRegisterAccessor midr_el1; + if (midr_el1.GetImplementer() == cpu::MainIdRegisterAccessor::Implementer::ArmLimited) { + /* ARM limited specific setup. */ + const auto cpu_primary_part = midr_el1.GetPrimaryPartNumber(); + const auto cpu_variant = midr_el1.GetVariant(); + const auto cpu_revision = midr_el1.GetRevision(); + if (cpu_primary_part == cpu::MainIdRegisterAccessor::PrimaryPartNumber::CortexA57) { + /* Cortex-A57 specific setup. */ + + /* Non-cacheable load forwarding enabled. */ + u64 cpuactlr_value = 0x1000000; + + /* Enable the processor to receive instruction cache and TLB maintenance */ + /* operations broadcast from other processors in the cluster; */ + /* set the L2 load/store data prefetch distance to 8 requests; */ + /* set the L2 instruction fetch prefetch distance to 3 requests. */ + u64 cpuectlr_value = 0x1B00000040; + + /* Disable load-pass DMB on certain hardware variants. */ + if (cpu_variant == 0 || (cpu_variant == 1 && cpu_revision <= 1)) { + cpuactlr_value |= 0x800000000000000; + } + + /* Set actlr and ectlr. */ + if (cpu::GetCpuActlrEl1() != cpuactlr_value) { + cpu::SetCpuActlrEl1(cpuactlr_value); + } + if (cpu::GetCpuEctlrEl1() != cpuectlr_value) { + cpu::SetCpuEctlrEl1(cpuectlr_value); + } + } else if (cpu_primary_part == cpu::MainIdRegisterAccessor::PrimaryPartNumber::CortexA53) { + /* Cortex-A53 specific setup. */ + + /* Set L1 data prefetch control to allow 5 outstanding prefetches; */ + /* enable device split throttle; */ + /* set the number of independent data prefetch streams to 2; */ + /* disable transient and no-read-allocate hints for loads; */ + /* set write streaming no-allocate threshold so the 128th consecutive streaming */ + /* cache line does not allocate in the L1 or L2 cache. */ + u64 cpuactlr_value = 0x90CA000; + + /* Enable hardware management of data coherency with other cores in the cluster. */ + u64 cpuectlr_value = 0x40; + + /* If supported, enable data cache clean as data cache clean/invalidate. */ + if (cpu_variant != 0 || (cpu_variant == 0 && cpu_revision > 2)) { + cpuactlr_value |= 0x100000000000; + } + + /* Set actlr and ectlr. */ + if (cpu::GetCpuActlrEl1() != cpuactlr_value) { + cpu::SetCpuActlrEl1(cpuactlr_value); + } + if (cpu::GetCpuEctlrEl1() != cpuectlr_value) { + cpu::SetCpuEctlrEl1(cpuectlr_value); + } + } + } + } + + /* This is a default implementation, which should be overridden in a source file in board/ */ + WEAK_SYMBOL void PerformBoardSpecificSetup() { + return PerformDefaultAarch64SpecificSetup(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/kern_init_loader_board_setup.hpp b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/kern_init_loader_board_setup.hpp new file mode 100644 index 00000000..8749397f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/kern_init_loader_board_setup.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <mesosphere.hpp> + +namespace ams::kern::init::loader { + +#if defined(ATMOSPHERE_ARCH_ARM64) + void PerformDefaultAarch64SpecificSetup(); +#endif + + void PerformBoardSpecificSetup(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/kern_loader_panic.cpp b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/kern_loader_panic.cpp new file mode 100644 index 00000000..0b9277cb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/kern_loader_panic.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 <http://www.gnu.org/licenses/>. + */ +#include <mesosphere.hpp> + +namespace ams::kern { + + /* This overrides the panic implementation from the kernel, to prevent linking debug print into kldr. */ + + NORETURN void PanicImpl(const char *file, int line, const char *format, ...) { + MESOSPHERE_UNUSED(file, line, format); + MESOSPHERE_INIT_ABORT(); + } + + NORETURN void PanicImpl() { + MESOSPHERE_INIT_ABORT(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/libc/kern_libc_config.arch.arm64.h b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/libc/kern_libc_config.arch.arm64.h new file mode 100644 index 00000000..440eae0b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/libc/kern_libc_config.arch.arm64.h @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +/* Definitions for libc genericity. */ +#define MESOSPHERE_LIBC_MEMCPY_GENERIC 1 +#define MESOSPHERE_LIBC_MEMCMP_GENERIC 1 +#define MESOSPHERE_LIBC_MEMMOVE_GENERIC 1 +#define MESOSPHERE_LIBC_MEMSET_GENERIC 1 +#define MESOSPHERE_LIBC_STRNCPY_GENERIC 1 +#define MESOSPHERE_LIBC_STRNCMP_GENERIC 1 diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/libc/kern_libc_config.h b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/libc/kern_libc_config.h new file mode 100644 index 00000000..ca7bb2e0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/libc/kern_libc_config.h @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#if defined(ATMOSPHERE_ARCH_ARM64) + + #include "kern_libc_config.arch.arm64.h" + +#else + + #error "Unknown architecture for libc" + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/libc/kern_libc_generic.c b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/libc/kern_libc_generic.c new file mode 100644 index 00000000..891dd6fb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/kernel_ldr/source/libc/kern_libc_generic.c @@ -0,0 +1,673 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <string.h> +#include <stddef.h> +#include <limits.h> +#include "kern_libc_config.h" + +/* Note: copied from newlib */ +#ifdef __cplusplus +extern "C" { +#endif + +/* +FUNCTION + <<memmove>>---move possibly overlapping memory +INDEX + memmove +SYNOPSIS + #include <string.h> + void *memmove(void *<[dst]>, const void *<[src]>, size_t <[length]>); +DESCRIPTION + This function moves <[length]> characters from the block of + memory starting at <<*<[src]>>> to the memory starting at + <<*<[dst]>>>. <<memmove>> reproduces the characters correctly + at <<*<[dst]>>> even if the two areas overlap. +RETURNS + The function returns <[dst]> as passed. +PORTABILITY +<<memmove>> is ANSI C. +<<memmove>> requires no supporting OS subroutines. +QUICKREF + memmove ansi pure +*/ + +/* Nonzero if either X or Y is not aligned on a "long" boundary. */ +#define UNALIGNED(X, Y) \ + (((long)X & (sizeof (long) - 1)) | ((long)Y & (sizeof (long) - 1))) + +/* How many bytes are copied each iteration of the 4X unrolled loop. */ +#define BIGBLOCKSIZE (sizeof (long) << 2) + +/* How many bytes are copied each iteration of the word copy loop. */ +#define LITTLEBLOCKSIZE (sizeof (long)) + +/* Threshhold for punting to the byte copier. */ +#undef TOO_SMALL +#define TOO_SMALL(LEN) ((LEN) < BIGBLOCKSIZE) + +#if MESOSPHERE_LIBC_MEMMOVE_GENERIC + +/*SUPPRESS 20*/ +void * +//__inhibit_loop_to_libcall +__attribute__((weak)) +memmove (void *dst_void, + const void *src_void, + size_t length) +{ +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) + char *dst = dst_void; + const char *src = src_void; + + if (src < dst && dst < src + length) + { + /* Have to copy backwards */ + src += length; + dst += length; + while (length--) + { + *--dst = *--src; + } + } + else + { + while (length--) + { + *dst++ = *src++; + } + } + + return dst_void; +#else + char *dst = dst_void; + const char *src = src_void; + long *aligned_dst; + const long *aligned_src; + + if (src < dst && dst < src + length) + { + /* Destructive overlap...have to copy backwards */ + src += length; + dst += length; + while (length--) + { + *--dst = *--src; + } + } + else + { + /* Use optimizing algorithm for a non-destructive copy to closely + match memcpy. If the size is small or either SRC or DST is unaligned, + then punt into the byte copy loop. This should be rare. */ + if (!TOO_SMALL(length) && !UNALIGNED (src, dst)) + { + aligned_dst = (long*)dst; + aligned_src = (long*)src; + + /* Copy 4X long words at a time if possible. */ + while (length >= BIGBLOCKSIZE) + { + *aligned_dst++ = *aligned_src++; + *aligned_dst++ = *aligned_src++; + *aligned_dst++ = *aligned_src++; + *aligned_dst++ = *aligned_src++; + length -= BIGBLOCKSIZE; + } + + /* Copy one long word at a time if possible. */ + while (length >= LITTLEBLOCKSIZE) + { + *aligned_dst++ = *aligned_src++; + length -= LITTLEBLOCKSIZE; + } + + /* Pick up any residual with a byte copier. */ + dst = (char*)aligned_dst; + src = (char*)aligned_src; + } + + while (length--) + { + *dst++ = *src++; + } + } + + return dst_void; +#endif /* not PREFER_SIZE_OVER_SPEED */ +} + +#endif /* MESOSPHERE_LIBC_MEMMOVE_GENERIC */ + +/* +FUNCTION + <<memcpy>>---copy memory regions +SYNOPSIS + #include <string.h> + void* memcpy(void *restrict <[out]>, const void *restrict <[in]>, + size_t <[n]>); +DESCRIPTION + This function copies <[n]> bytes from the memory region + pointed to by <[in]> to the memory region pointed to by + <[out]>. + If the regions overlap, the behavior is undefined. +RETURNS + <<memcpy>> returns a pointer to the first byte of the <[out]> + region. +PORTABILITY +<<memcpy>> is ANSI C. +<<memcpy>> requires no supporting OS subroutines. +QUICKREF + memcpy ansi pure + */ + +#if MESOSPHERE_LIBC_MEMCPY_GENERIC + +void * +__attribute__((weak)) +memcpy (void * dst0, + const void * __restrict src0, + size_t len0) +{ +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) + char *dst = (char *) dst0; + char *src = (char *) src0; + + void *save = dst0; + + while (len0--) + { + *dst++ = *src++; + } + + return save; +#else + char *dst = dst0; + const char *src = src0; + long *aligned_dst; + const long *aligned_src; + + /* If the size is small, or either SRC or DST is unaligned, + then punt into the byte copy loop. This should be rare. */ + if (!TOO_SMALL(len0) && !UNALIGNED (src, dst)) + { + aligned_dst = (long*)dst; + aligned_src = (long*)src; + + /* Copy 4X long words at a time if possible. */ + while (len0 >= BIGBLOCKSIZE) + { + *aligned_dst++ = *aligned_src++; + *aligned_dst++ = *aligned_src++; + *aligned_dst++ = *aligned_src++; + *aligned_dst++ = *aligned_src++; + len0 -= BIGBLOCKSIZE; + } + + /* Copy one long word at a time if possible. */ + while (len0 >= LITTLEBLOCKSIZE) + { + *aligned_dst++ = *aligned_src++; + len0 -= LITTLEBLOCKSIZE; + } + + /* Pick up any residual with a byte copier. */ + dst = (char*)aligned_dst; + src = (char*)aligned_src; + } + + while (len0--) + *dst++ = *src++; + + return dst0; +#endif /* not PREFER_SIZE_OVER_SPEED */ +} + +#endif /* MESOSPHERE_LIBC_MEMCPY_GENERIC */ + +/* +FUNCTION + <<memset>>---set an area of memory +INDEX + memset +SYNOPSIS + #include <string.h> + void *memset(void *<[dst]>, int <[c]>, size_t <[length]>); +DESCRIPTION + This function converts the argument <[c]> into an unsigned + char and fills the first <[length]> characters of the array + pointed to by <[dst]> to the value. +RETURNS + <<memset>> returns the value of <[dst]>. +PORTABILITY +<<memset>> is ANSI C. + <<memset>> requires no supporting OS subroutines. +QUICKREF + memset ansi pure +*/ + +#include <string.h> + +#undef LBLOCKSIZE +#undef UNALIGNED +#undef TOO_SMALL + +#define LBLOCKSIZE (sizeof(long)) +#define UNALIGNED(X) ((long)X & (LBLOCKSIZE - 1)) +#define TOO_SMALL(LEN) ((LEN) < LBLOCKSIZE) + +#if MESOSPHERE_LIBC_MEMSET_GENERIC + +void * +__attribute__((weak)) +memset (void *m, + int c, + size_t n) +{ + char *s = (char *) m; + +#if !defined(PREFER_SIZE_OVER_SPEED) && !defined(__OPTIMIZE_SIZE__) + unsigned int i; + unsigned long buffer; + unsigned long *aligned_addr; + unsigned int d = c & 0xff; /* To avoid sign extension, copy C to an + unsigned variable. */ + + while (UNALIGNED (s)) + { + if (n--) + *s++ = (char) c; + else + return m; + } + + if (!TOO_SMALL (n)) + { + /* If we get this far, we know that n is large and s is word-aligned. */ + aligned_addr = (unsigned long *) s; + + /* Store D into each char sized location in BUFFER so that + we can set large blocks quickly. */ + buffer = (d << 8) | d; + buffer |= (buffer << 16); + for (i = 32; i < LBLOCKSIZE * 8; i <<= 1) + buffer = (buffer << i) | buffer; + + /* Unroll the loop. */ + while (n >= LBLOCKSIZE*4) + { + *aligned_addr++ = buffer; + *aligned_addr++ = buffer; + *aligned_addr++ = buffer; + *aligned_addr++ = buffer; + n -= 4*LBLOCKSIZE; + } + + while (n >= LBLOCKSIZE) + { + *aligned_addr++ = buffer; + n -= LBLOCKSIZE; + } + /* Pick up the remainder with a bytewise loop. */ + s = (char*)aligned_addr; + } + +#endif /* not PREFER_SIZE_OVER_SPEED */ + + while (n--) + *s++ = (char) c; + + return m; +} + +#endif /* MESOSPHERE_LIBC_MEMSET_GENERIC */ + +/* +FUNCTION + <<memcmp>>---compare two memory areas +INDEX + memcmp +SYNOPSIS + #include <string.h> + int memcmp(const void *<[s1]>, const void *<[s2]>, size_t <[n]>); +DESCRIPTION + This function compares not more than <[n]> characters of the + object pointed to by <[s1]> with the object pointed to by <[s2]>. +RETURNS + The function returns an integer greater than, equal to or + less than zero according to whether the object pointed to by + <[s1]> is greater than, equal to or less than the object + pointed to by <[s2]>. +PORTABILITY +<<memcmp>> is ANSI C. +<<memcmp>> requires no supporting OS subroutines. +QUICKREF + memcmp ansi pure +*/ + +#undef LBLOCKSIZE +#undef UNALIGNED +#undef TOO_SMALL + +/* Nonzero if either X or Y is not aligned on a "long" boundary. */ +#define UNALIGNED(X, Y) \ + (((long)X & (sizeof (long) - 1)) | ((long)Y & (sizeof (long) - 1))) + +/* How many bytes are copied each iteration of the word copy loop. */ +#define LBLOCKSIZE (sizeof (long)) + +/* Threshhold for punting to the byte copier. */ +#define TOO_SMALL(LEN) ((LEN) < LBLOCKSIZE) + +#if MESOSPHERE_LIBC_MEMCMP_GENERIC + +int +__attribute__((weak)) +memcmp (const void *m1, + const void *m2, + size_t n) +{ +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) + unsigned char *s1 = (unsigned char *) m1; + unsigned char *s2 = (unsigned char *) m2; + + while (n--) + { + if (*s1 != *s2) + { + return *s1 - *s2; + } + s1++; + s2++; + } + return 0; +#else + unsigned char *s1 = (unsigned char *) m1; + unsigned char *s2 = (unsigned char *) m2; + unsigned long *a1; + unsigned long *a2; + + /* If the size is too small, or either pointer is unaligned, + then we punt to the byte compare loop. Hopefully this will + not turn up in inner loops. */ + if (!TOO_SMALL(n) && !UNALIGNED(s1,s2)) + { + /* Otherwise, load and compare the blocks of memory one + word at a time. */ + a1 = (unsigned long*) s1; + a2 = (unsigned long*) s2; + while (n >= LBLOCKSIZE) + { + if (*a1 != *a2) + break; + a1++; + a2++; + n -= LBLOCKSIZE; + } + + /* check m mod LBLOCKSIZE remaining characters */ + + s1 = (unsigned char*)a1; + s2 = (unsigned char*)a2; + } + + while (n--) + { + if (*s1 != *s2) + return *s1 - *s2; + s1++; + s2++; + } + + return 0; +#endif /* not PREFER_SIZE_OVER_SPEED */ +} + +#endif /* MESOSPHERE_LIBC_MEMCMP_GENERIC */ + +/* +FUNCTION + <<strncpy>>---counted copy string +INDEX + strncpy +SYNOPSIS + #include <string.h> + char *strncpy(char *restrict <[dst]>, const char *restrict <[src]>, + size_t <[length]>); +DESCRIPTION + <<strncpy>> copies not more than <[length]> characters from the + the string pointed to by <[src]> (including the terminating + null character) to the array pointed to by <[dst]>. If the + string pointed to by <[src]> is shorter than <[length]> + characters, null characters are appended to the destination + array until a total of <[length]> characters have been + written. +RETURNS + This function returns the initial value of <[dst]>. +PORTABILITY +<<strncpy>> is ANSI C. +<<strncpy>> requires no supporting OS subroutines. +QUICKREF + strncpy ansi pure +*/ + +#include <string.h> +#include <limits.h> + +/*SUPPRESS 560*/ +/*SUPPRESS 530*/ + +/* Nonzero if either X or Y is not aligned on a "long" boundary. */ +#define UNALIGNED(X, Y) \ + (((long)X & (sizeof (long) - 1)) | ((long)Y & (sizeof (long) - 1))) + +#if LONG_MAX == 2147483647L +#define DETECTNULL(X) (((X) - 0x01010101) & ~(X) & 0x80808080) +#else +#if LONG_MAX == 9223372036854775807L +/* Nonzero if X (a long int) contains a NULL byte. */ +#define DETECTNULL(X) (((X) - 0x0101010101010101) & ~(X) & 0x8080808080808080) +#else +#error long int is not a 32bit or 64bit type. +#endif +#endif + +#ifndef DETECTNULL +#error long int is not a 32bit or 64bit byte +#endif + +#undef TOO_SMALL +#define TOO_SMALL(LEN) ((LEN) < sizeof (long)) + +#if MESOSPHERE_LIBC_STRNCMP_GENERIC + +char * +strncpy (char *__restrict dst0, + const char *__restrict src0, + size_t count) +{ +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) + char *dscan; + const char *sscan; + + dscan = dst0; + sscan = src0; + while (count > 0) + { + --count; + if ((*dscan++ = *sscan++) == '\0') + break; + } + while (count-- > 0) + *dscan++ = '\0'; + + return dst0; +#else + char *dst = dst0; + const char *src = src0; + long *aligned_dst; + const long *aligned_src; + + /* If SRC and DEST is aligned and count large enough, then copy words. */ + if (!UNALIGNED (src, dst) && !TOO_SMALL (count)) + { + aligned_dst = (long*)dst; + aligned_src = (long*)src; + + /* SRC and DEST are both "long int" aligned, try to do "long int" + sized copies. */ + while (count >= sizeof (long int) && !DETECTNULL(*aligned_src)) + { + count -= sizeof (long int); + *aligned_dst++ = *aligned_src++; + } + + dst = (char*)aligned_dst; + src = (char*)aligned_src; + } + + while (count > 0) + { + --count; + if ((*dst++ = *src++) == '\0') + break; + } + + while (count-- > 0) + *dst++ = '\0'; + + return dst0; +#endif /* not PREFER_SIZE_OVER_SPEED */ +} + +#endif /* MESOSPHERE_LIBC_STRNCPY_GENERIC */ + +/* +FUNCTION + <<strncmp>>---character string compare + +INDEX + strncmp +SYNOPSIS + #include <string.h> + int strncmp(const char *<[a]>, const char * <[b]>, size_t <[length]>); +DESCRIPTION + <<strncmp>> compares up to <[length]> characters + from the string at <[a]> to the string at <[b]>. +RETURNS + If <<*<[a]>>> sorts lexicographically after <<*<[b]>>>, + <<strncmp>> returns a number greater than zero. If the two + strings are equivalent, <<strncmp>> returns zero. If <<*<[a]>>> + sorts lexicographically before <<*<[b]>>>, <<strncmp>> returns a + number less than zero. +PORTABILITY +<<strncmp>> is ANSI C. +<<strncmp>> requires no supporting OS subroutines. +QUICKREF + strncmp ansi pure +*/ + +#include <string.h> +#include <limits.h> + +/* Nonzero if either X or Y is not aligned on a "long" boundary. */ +#define UNALIGNED(X, Y) \ + (((long)X & (sizeof (long) - 1)) | ((long)Y & (sizeof (long) - 1))) + +/* DETECTNULL returns nonzero if (long)X contains a NULL byte. */ +#if LONG_MAX == 2147483647L +#define DETECTNULL(X) (((X) - 0x01010101) & ~(X) & 0x80808080) +#else +#if LONG_MAX == 9223372036854775807L +#define DETECTNULL(X) (((X) - 0x0101010101010101) & ~(X) & 0x8080808080808080) +#else +#error long int is not a 32bit or 64bit type. +#endif +#endif + +#ifndef DETECTNULL +#error long int is not a 32bit or 64bit byte +#endif + +#if MESOSPHERE_LIBC_STRNCMP_GENERIC + +int +strncmp (const char *s1, + const char *s2, + size_t n) +{ +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) + if (n == 0) + return 0; + + while (n-- != 0 && *s1 == *s2) + { + if (n == 0 || *s1 == '\0') + break; + s1++; + s2++; + } + + return (*(unsigned char *) s1) - (*(unsigned char *) s2); +#else + unsigned long *a1; + unsigned long *a2; + + if (n == 0) + return 0; + + /* If s1 or s2 are unaligned, then compare bytes. */ + if (!UNALIGNED (s1, s2)) + { + /* If s1 and s2 are word-aligned, compare them a word at a time. */ + a1 = (unsigned long*)s1; + a2 = (unsigned long*)s2; + while (n >= sizeof (long) && *a1 == *a2) + { + n -= sizeof (long); + + /* If we've run out of bytes or hit a null, return zero + since we already know *a1 == *a2. */ + if (n == 0 || DETECTNULL (*a1)) + return 0; + + a1++; + a2++; + } + + /* A difference was detected in last few bytes of s1, so search bytewise */ + s1 = (char*)a1; + s2 = (char*)a2; + } + + while (n-- > 0 && *s1 == *s2) + { + /* If we've run out of bytes or hit a null, return zero + since we already know *s1 == *s2. */ + if (n == 0 || *s1 == '\0') + return 0; + s1++; + s2++; + } + return (*(unsigned char *) s1) - (*(unsigned char *) s2); +#endif /* not PREFER_SIZE_OVER_SPEED */ +} + +#endif /* MESOSPHERE_LIBC_STRNCMP_GENERIC */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/Source/Atmosphere-MTC-Unlock/mesosphere/mesosphere.mk b/Source/Atmosphere-MTC-Unlock/mesosphere/mesosphere.mk new file mode 100644 index 00000000..439079e1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/mesosphere/mesosphere.mk @@ -0,0 +1,49 @@ +#--------------------------------------------------------------------------------- +# 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)/mesosphere.bin + +$(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)/mesosphere.bin: $(CURRENT_DIRECTORY)/kernel/$(ATMOSPHERE_OUT_DIR)/kernel.bin $(CURRENT_DIRECTORY)/kernel_ldr/$(ATMOSPHERE_OUT_DIR)/kernel_ldr.bin $(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR) + $(SILENTCMD)$(PYTHON) build_mesosphere.py $(CURRENT_DIRECTORY)/kernel_ldr/$(ATMOSPHERE_OUT_DIR)/kernel_ldr.bin $(CURRENT_DIRECTORY)/kernel/$(ATMOSPHERE_OUT_DIR)/kernel.bin $(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)/mesosphere.bin + @echo "Built mesosphere.bin..." + +$(CURRENT_DIRECTORY)/kernel/$(ATMOSPHERE_OUT_DIR)/kernel.bin: check_kernel + @$(SILENTCMD)echo "Checked kernel." + +$(CURRENT_DIRECTORY)/kernel_ldr/$(ATMOSPHERE_OUT_DIR)/kernel_ldr.bin: check_kernel_ldr + @$(SILENTCMD)echo "Checked kernel ldr." + +$(ATMOSPHERE_LIBRARIES_DIR)/libmesosphere/$(ATMOSPHERE_LIBRARY_DIR)/libmesosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +check_kernel: $(ATMOSPHERE_LIBRARIES_DIR)/libmesosphere/$(ATMOSPHERE_LIBRARY_DIR)/libmesosphere.a + @$(SILENTCMD)echo "Checking kernel..." + @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/kernel -f $(CURRENT_DIRECTORY)/kernel/kernel.mk ATMOSPHERE_CHECKED_LIBMESOSPHERE=1 + +check_kernel_ldr: $(ATMOSPHERE_LIBRARIES_DIR)/libmesosphere/$(ATMOSPHERE_LIBRARY_DIR)/libmesosphere.a + @$(SILENTCMD)echo "Checking kernel ldr..." + @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/kernel_ldr -f $(CURRENT_DIRECTORY)/kernel_ldr/kernel_ldr.mk ATMOSPHERE_CHECKED_LIBMESOSPHERE=1 + +ifeq ($(ATMOSPHERE_CHECKED_LIBMESOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libmesosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libmesosphere/libmesosphere.mk +endif + + +$(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR): + @[ -d $@ ] || mkdir -p $@ + +clean: + @echo clean ... + @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/kernel -f $(CURRENT_DIRECTORY)/kernel/kernel.mk clean + @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/kernel_ldr -f $(CURRENT_DIRECTORY)/kernel_ldr/kernel_ldr.mk clean + @rm -fr $(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)/mesosphere.bin + @for i in $(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR); do [ -d $$i ] && rmdir $$i 2>/dev/null || true; done + +.PHONY: all clean check_lib check_kernel check_kernel_ldr \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/LogManager/LogManager.json b/Source/Atmosphere-MTC-Unlock/stratosphere/LogManager/LogManager.json new file mode 100644 index 00000000..66466632 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/LogManager/LogManager.json @@ -0,0 +1,89 @@ +{ + "name": "LogManager", + "title_id": "0x0100000000000420", + "title_id_range_min": "0x0100000000000420", + "title_id_range_max": "0x0100000000000420", + "main_thread_stack_size": "0x00003000", + "main_thread_priority": 38, + "default_cpu_id": 3, + "process_category": 0, + "is_retail": true, + "pool_partition": 2, + "is_64_bit": true, + "address_space_type": 1, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_access": ["fsp-srv", "htc", "htcs", "psc:m", "set", "set:sys", "srepo:u", "time:su", "time:u", "tma_log"], + "service_host": ["lm", "lm:get"], + "kernel_capabilities": [{ + "type": "kernel_flags", + "value": { + "highest_thread_priority": 63, + "lowest_thread_priority": 24, + "lowest_cpu_id": 3, + "highest_cpu_id": 3 + } + }, { + "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", + "svcSynchronizePreemptionState": "0x36", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcCallSecureMonitor": "0x7f" + } + }, { + "type": "min_kernel_version", + "value": "0x0030" + }, { + "type": "handle_table_size", + "value": 64 + }] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/LogManager/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/LogManager/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/LogManager/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)/system_module.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)/system_module.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/stratosphere/LogManager/source/lm_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/LogManager/source/lm_main.cpp new file mode 100644 index 00000000..243645c6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/LogManager/source/lm_main.cpp @@ -0,0 +1,80 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams { + + namespace lm::srv { + + void StartLogServerProxy(); + void StopLogServerProxy(); + + void InitializeFlushThread(); + void FinalizeFlushThread(); + + void InitializeIpcServer(); + void LoopIpcServer(); + void FinalizeIpcServer(); + + } + + namespace init { + + void InitializeSystemModule() { + /* Initialize our connection to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + + /* Initialize services we need. */ + R_ABORT_UNLESS(::setsysInitialize()); + R_ABORT_UNLESS(::pscmInitialize()); + + /* Verify that we can sanely execute. */ + ams::CheckApiVersion(); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void Main() { + /* Check thread priority. */ + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(LogManager, MainThread)); + + /* Set thread name. */ + os::ChangeThreadPriority(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_PRIORITY(lm, IpcServer)); + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(lm, IpcServer)); + + /* Start log server proxy. */ + lm::srv::StartLogServerProxy(); + + /* Initialize flush thread. */ + lm::srv::InitializeFlushThread(); + + /* Process IPC server. */ + lm::srv::InitializeIpcServer(); + lm::srv::LoopIpcServer(); + lm::srv::FinalizeIpcServer(); + + /* Finalize flush thread. */ + lm::srv::FinalizeFlushThread(); + + /* Stop log server proxy. */ + lm::srv::StopLogServerProxy(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/LogManager/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/LogManager/system_module.mk new file mode 100644 index 00000000..d6de42ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/LogManager/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := nsp + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/Makefile new file mode 100644 index 00000000..9423a8f3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/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)/stratosphere.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)/stratosphere.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/stratosphere/TioServer/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/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)/system_module.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)/system_module.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/stratosphere/TioServer/TioServer.json b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/TioServer.json new file mode 100644 index 00000000..c0feef16 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/TioServer.json @@ -0,0 +1,84 @@ +{ + "name": "TioServer", + "title_id": "0x010000000000D623", + "title_id_range_min": "0x010000000000D623", + "title_id_range_max": "0x010000000000D623", + "main_thread_stack_size": "0x00004000", + "main_thread_priority": 49, + "default_cpu_id": 3, + "process_category": 0, + "is_retail": true, + "pool_partition": 2, + "is_64_bit": true, + "address_space_type": 3, + "disable_device_address_space_merge": true, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_access": ["fatal:u", "fsp-srv", "htcs"], + "service_host": [], + "kernel_capabilities": [{ + "type": "kernel_flags", + "value": { + "highest_thread_priority": 63, + "lowest_thread_priority": 24, + "lowest_cpu_id": 3, + "highest_cpu_id": 3 + } + }, { + "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", + "svcSynchronizePreemptionState": "0x36", + "svcCallSecureMonitor": "0x7f" + } + }, { + "type": "min_kernel_version", + "value": "0x0030" + }, { + "type": "handle_table_size", + "value": 0 + }] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server.cpp new file mode 100644 index 00000000..32ad5e6f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server.cpp @@ -0,0 +1,159 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "tio_file_server.hpp" +#include "tio_file_server_packet.hpp" +#include "tio_file_server_htcs_server.hpp" +#include "tio_file_server_processor.hpp" +#include "tio_sd_card_observer.hpp" + +namespace ams::tio { + + namespace { + + constexpr inline auto NumDispatchThreads = 2; + constexpr inline auto DispatchThreadPriority = 21; + constexpr inline size_t RequestBufferSize = 1_MB + util::AlignUp(0x40 + fs::EntryNameLengthMax, 1_KB); + + struct FileServerRequest { + int socket; + FileServerRequestHeader header; + u8 body[RequestBufferSize]; + }; + + constexpr const char HtcsPortName[] = "iywys@$TioServer_FileServer"; + + alignas(os::ThreadStackAlignment) u8 g_server_stack[os::MemoryPageSize]; + alignas(os::ThreadStackAlignment) u8 g_observer_stack[os::MemoryPageSize]; + alignas(os::ThreadStackAlignment) u8 g_dispatch_stacks[NumDispatchThreads][os::MemoryPageSize]; + + constinit FileServerHtcsServer g_file_server_htcs_server; + constinit FileServerProcessor g_file_server_processor(g_file_server_htcs_server); + constinit SdCardObserver g_sd_card_observer; + + constinit os::ThreadType g_file_server_dispatch_threads[NumDispatchThreads]; + + constinit FileServerRequest g_requests[NumDispatchThreads]; + + constinit os::MessageQueueType g_free_mq; + constinit os::MessageQueueType g_dispatch_mq; + + constinit uintptr_t g_free_mq_storage[NumDispatchThreads]; + constinit uintptr_t g_dispatch_mq_storage[NumDispatchThreads]; + + void OnSdCardInsertionChanged(bool inserted) { + g_file_server_processor.SetInserted(inserted); + } + + void OnFileServerHtcsSocketAccepted(int fd) { + /* Service requests, while we can. */ + while (true) { + /* Receive a free request. */ + uintptr_t request_address; + os::ReceiveMessageQueue(std::addressof(request_address), std::addressof(g_free_mq)); + + /* Ensure we manage our request properly. */ + auto req_guard = SCOPE_GUARD { os::SendMessageQueue(std::addressof(g_free_mq), request_address); }; + + /* Receive the request header. */ + FileServerRequest *request = reinterpret_cast<FileServerRequest *>(request_address); + if (htcs::Recv(fd, std::addressof(request->header), sizeof(request->header), htcs::HTCS_MSG_WAITALL) != sizeof(request->header)) { + break; + } + + /* Receive the request body, if necessary. */ + if (request->header.body_size > 0) { + if (htcs::Recv(fd, request->body, request->header.body_size, htcs::HTCS_MSG_WAITALL) != request->header.body_size) { + break; + } + } + + /* Dispatch the request. */ + req_guard.Cancel(); + request->socket = fd; + os::SendMessageQueue(std::addressof(g_dispatch_mq), request_address); + } + + /* Our socket is no longer making requests, so close it. */ + htcs::Close(fd); + + /* Clean up any server resources. */ + g_file_server_processor.Unmount(); + } + + void FileServerDispatchThreadFunction(void *) { + while (true) { + /* Receive a request. */ + uintptr_t request_address; + os::ReceiveMessageQueue(std::addressof(request_address), std::addressof(g_dispatch_mq)); + + /* Process the request. */ + FileServerRequest *request = reinterpret_cast<FileServerRequest *>(request_address); + if (!g_file_server_processor.ProcessRequest(std::addressof(request->header), request->body, request->socket)) { + htcs::Close(request->socket); + } + + /* Free the request. */ + os::SendMessageQueue(std::addressof(g_free_mq), request_address); + } + } + + } + + void InitializeFileServer() { + /* Initialize the htcs server. */ + g_file_server_htcs_server.Initialize(HtcsPortName, g_server_stack, sizeof(g_server_stack), OnFileServerHtcsSocketAccepted); + + /* Initialize SD card observer. */ + g_sd_card_observer.Initialize(g_observer_stack, sizeof(g_observer_stack)); + g_sd_card_observer.SetCallback(OnSdCardInsertionChanged); + + /* Initialize the command processor. */ + g_file_server_processor.SetInserted(g_sd_card_observer.IsSdCardInserted()); + g_file_server_processor.SetRequestBufferSize(RequestBufferSize); + + /* Initialize the dispatch message queues. */ + os::InitializeMessageQueue(std::addressof(g_free_mq), g_free_mq_storage, util::size(g_free_mq_storage)); + os::InitializeMessageQueue(std::addressof(g_dispatch_mq), g_dispatch_mq_storage, util::size(g_dispatch_mq_storage)); + + /* Begin with all requests free. */ + for (auto i = 0; i < NumDispatchThreads; ++i) { + os::SendMessageQueue(std::addressof(g_free_mq), reinterpret_cast<uintptr_t>(g_requests + i)); + } + + /* Initialize the dispatch threads. */ + /* NOTE: Nintendo does not name these threads. */ + for (auto i = 0; i < NumDispatchThreads; ++i) { + R_ABORT_UNLESS(os::CreateThread(g_file_server_dispatch_threads + i, FileServerDispatchThreadFunction, nullptr, g_dispatch_stacks + i, sizeof(g_dispatch_stacks[i]), DispatchThreadPriority)); + } + } + + void StartFileServer() { + /* Start the htcs server. */ + g_file_server_htcs_server.Start(); + + /* Start the dispatch threads. */ + for (auto i = 0; i < NumDispatchThreads; ++i) { + os::StartThread(g_file_server_dispatch_threads + i); + } + } + + void WaitFileServer() { + /* Wait for the htcs server to finish. */ + g_file_server_htcs_server.Wait(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server.hpp new file mode 100644 index 00000000..9e166c3b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::tio { + + void InitializeFileServer(); + void StartFileServer(); + void WaitFileServer(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server_htcs_server.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server_htcs_server.cpp new file mode 100644 index 00000000..92f54c87 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server_htcs_server.cpp @@ -0,0 +1,94 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "tio_file_server_htcs_server.hpp" + +namespace ams::tio { + + void FileServerHtcsServer::Initialize(const char *port_name, void *thread_stack, size_t thread_stack_size, SocketAcceptedCallback on_socket_accepted) { + /* Set our port name. */ + std::strcpy(m_port_name.name, port_name); + + /* Set our callback. */ + m_on_socket_accepted = on_socket_accepted; + + /* Setup our thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_thread), ThreadEntry, this, thread_stack, thread_stack_size, AMS_GET_SYSTEM_THREAD_PRIORITY(TioServer, FileServerHtcsServer))); + + /* Set our thread name pointer. */ + os::SetThreadNamePointer(std::addressof(m_thread), AMS_GET_SYSTEM_THREAD_NAME(TioServer, FileServerHtcsServer)); + } + + void FileServerHtcsServer::Start() { + os::StartThread(std::addressof(m_thread)); + } + void FileServerHtcsServer::Wait() { + os::WaitThread(std::addressof(m_thread)); + } + + void FileServerHtcsServer::ThreadFunc() { + /* Loop forever, servicing sockets. */ + while (true) { + /* Get a socket. */ + int fd; + while ((fd = htcs::Socket()) == -1) { + os::SleepThread(TimeSpan::FromSeconds(1)); + } + + /* Ensure we cleanup the socket when we're done with it. */ + ON_SCOPE_EXIT { + htcs::Close(fd); + os::SleepThread(TimeSpan::FromSeconds(1)); + }; + + /* Create a sock addr for our server. */ + htcs::SockAddrHtcs addr; + addr.family = htcs::HTCS_AF_HTCS; + addr.peer_name = htcs::GetPeerNameAny(); + addr.port_name = m_port_name; + + /* Bind. */ + if (htcs::Bind(fd, std::addressof(addr)) == -1) { + continue; + } + + /* Listen on our port. */ + while (htcs::Listen(fd, 0) == 0) { + /* Continue accepting clients, so long as we can. */ + int client_fd; + while (true) { + /* Try to accept a client. */ + if (client_fd = htcs::Accept(fd, std::addressof(addr)); client_fd < 0) { + break; + } + + /* Handle the client. */ + m_on_socket_accepted(client_fd); + } + + /* NOTE: This seems unnecessary (client_fd guaranteed < 0 here), but Nintendo does it. */ + htcs::Close(client_fd); + } + } + } + + ssize_t FileServerHtcsServer::Send(s32 desc, const void *buffer, size_t buffer_size, s32 flags) { + AMS_ASSERT(m_mutex.IsLockedByCurrentThread()); + + return htcs::Send(desc, buffer, buffer_size, flags); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server_htcs_server.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server_htcs_server.hpp new file mode 100644 index 00000000..da6b7182 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server_htcs_server.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::tio { + + using SocketAcceptedCallback = void(*)(s32 desc); + + class FileServerHtcsServer { + private: + SocketAcceptedCallback m_on_socket_accepted; + htcs::HtcsPortName m_port_name; + os::ThreadType m_thread; + os::SdkMutex m_mutex; + public: + constexpr FileServerHtcsServer() : m_on_socket_accepted(nullptr), m_port_name{}, m_thread{}, m_mutex{} { /* ... */ } + private: + static void ThreadEntry(void *arg) { + static_cast<FileServerHtcsServer *>(arg)->ThreadFunc(); + } + + void ThreadFunc(); + public: + os::SdkMutex &GetMutex() { return m_mutex; } + public: + void Initialize(const char *port_name, void *thread_stack, size_t thread_stack_size, SocketAcceptedCallback on_socket_accepted); + void Start(); + void Wait(); + + ssize_t Send(s32 desc, const void *buffer, size_t buffer_size, s32 flags); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server_packet.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server_packet.hpp new file mode 100644 index 00000000..4583add0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server_packet.hpp @@ -0,0 +1,187 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::tio { + + enum class PacketType : u32 { + /* Control commands. */ + Connect = 0, + Disconnect = 1, + + /* Direct filesystem access. */ + CreateDirectory = 500, + DeleteDirectory = 501, + DeleteDirectoryRecursively = 502, + OpenDirectory = 503, + CloseDirectory = 504, + RenameDirectory = 505, + CreateFile = 506, + DeleteFile = 507, + OpenFile = 508, + FlushFile = 509, + CloseFile = 510, + RenameFile = 511, + ReadFile = 512, + WriteFile = 513, + GetEntryType = 514, + ReadDirectory = 515, + GetFileSize = 516, + SetFileSize = 517, + GetTotalSpaceSize = 518, + GetFreeSpaceSize = 519, + + /* Utilities. */ + Stat = 1000, + ListDirectory = 1001, + }; + + struct FileServerRequestHeader { + u64 request_id; + PacketType packet_type; + u32 body_size; + }; + + struct FileServerResponseHeader { + u64 request_id; + Result result; + u32 body_size; + }; + static_assert(sizeof(FileServerRequestHeader) == sizeof(FileServerResponseHeader)); + + struct CreateDirectoryParam { + u32 path_len; + char path[]; + }; + + struct DeleteDirectoryParam { + u32 path_len; + char path[]; + }; + + struct DeleteDirectoryRecursivelyParam { + u32 path_len; + char path[]; + }; + + struct OpenDirectoryParam { + u32 path_len; + fs::OpenDirectoryMode open_mode; + char path[]; + }; + static_assert(sizeof(OpenDirectoryParam) == 0x8); + + struct CloseDirectoryParam { + u64 handle; + }; + + struct RenameDirectoryParam { + u32 old_len; + u32 new_len; + char data[]; + }; + + struct CreateFileParam { + s64 size; + u32 path_len; + fs::CreateOption option; + char path[]; + }; + static_assert(sizeof(CreateFileParam) == 0x10); + + struct DeleteFileParam { + u32 path_len; + char path[]; + }; + + struct OpenFileParam { + u32 path_len; + fs::OpenMode mode; + char path[]; + }; + static_assert(sizeof(OpenFileParam) == 0x8); + + struct FlushFileParam { + u64 handle; + }; + + struct CloseFileParam { + u64 handle; + }; + + struct RenameFileParam { + u32 old_len; + u32 new_len; + char data[]; + }; + + struct ReadFileParam { + u64 handle; + s64 offset; + u64 size; + fs::ReadOption option; + }; + static_assert(sizeof(ReadFileParam) == 0x20); + + struct WriteFileParam { + u64 handle; + s64 offset; + u64 size; + fs::WriteOption option; + }; + static_assert(sizeof(WriteFileParam) == 0x20); + + struct GetEntryTypeParam { + u32 path_len; + char path[]; + }; + + struct ReadDirectoryParam { + u64 handle; + s64 count; + }; + + struct GetFileSizeParam { + u64 handle; + }; + + struct SetFileSizeParam { + u64 handle; + s64 size; + }; + + struct GetTotalSpaceSizeParam { + u32 path_len; + char path[]; + }; + + struct GetFreeSpaceSizeParam { + u32 path_len; + char path[]; + }; + + struct StatParam { + u32 path_len; + char path[]; + }; + + struct ListDirectoryParam { + u32 path_len; + char path[]; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server_processor.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server_processor.cpp new file mode 100644 index 00000000..7a1f6e3b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server_processor.cpp @@ -0,0 +1,735 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "tio_file_server_processor.hpp" + +namespace ams::tio { + + namespace { + + constexpr inline int ProtocolVersion = 1; + + } + + void FileServerProcessor::Unmount() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Close all our directories. */ + if (m_open_directory_count > 0) { + for (size_t i = 0; i < util::size(m_directories); ++i) { + if (m_directories[i].handle != nullptr) { + fs::CloseDirectory(m_directories[i]); + m_directories[i] = {}; + --m_open_directory_count; + } + } + } + AMS_ABORT_UNLESS(m_open_directory_count == 0); + + /* Close all our files. */ + if (m_open_file_count > 0) { + for (size_t i = 0; i < util::size(m_files); ++i) { + if (m_files[i].handle != nullptr) { + fs::CloseFile(m_files[i]); + m_files[i] = {}; + --m_open_file_count; + } + } + } + AMS_ABORT_UNLESS(m_open_file_count == 0); + + /* If we're mounted, unmount the sd card. */ + if (m_is_mounted) { + m_is_mounted = false; + fs::Unmount("sd"); + } + } + + bool FileServerProcessor::ProcessRequest(FileServerRequestHeader *header, u8 *body, int socket) { + /* Declare a response header for us to use. */ + FileServerResponseHeader response_header = { + .request_id = header->request_id, + .result = ResultSuccess(), + .body_size = 0, + }; + + /* Handle the special control commands. */ + if (header->packet_type == PacketType::Connect) { + /* If the SD card isn't already mounted, try to mount it. */ + if (!m_is_mounted) { + /* Mount the sd card. */ + m_is_mounted = !fs::ResultSdCardAccessFailed::Includes(fs::MountSdCard("sd")); + + /* Prepare the response. */ + char *response_body = reinterpret_cast<char *>(body); + util::SNPrintf(response_body, 0x100, "{\"bufferSize\":%zu, \"sdcardMounted\":%s, \"sdcardInserted\":%s, \"version\":%d}", + m_request_buffer_size, + m_is_mounted ? "true" : "false", + m_is_inserted ? "true" : "false", + ProtocolVersion); + + /* Get the response length. */ + response_header.body_size = std::strlen(response_body); + } + + return this->SendResponse(response_header, body, socket); + } else if (header->packet_type == PacketType::Disconnect) { + /* If we need to, unmount the sd card. */ + if (m_is_mounted) { + this->Unmount(); + } + + /* Send the response. */ + return this->SendResponse(response_header, body, socket); + } + + /* The SD card must be inserted and mounted for us to process requests. */ + if (m_is_inserted && m_is_mounted) { + switch (header->packet_type) { + case PacketType::CreateDirectory: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const CreateDirectoryParam *>(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Create the directory. */ + response_header.result = fs::CreateDirectory(param->path); + } + break; + case PacketType::DeleteDirectory: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const DeleteDirectoryParam *>(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Delete the directory. */ + response_header.result = fs::DeleteDirectory(param->path); + } + break; + case PacketType::DeleteDirectoryRecursively: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const DeleteDirectoryRecursivelyParam *>(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Delete the directory. */ + response_header.result = fs::DeleteDirectoryRecursively(param->path); + } + break; + case PacketType::OpenDirectory: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const OpenDirectoryParam *>(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Open the directory. */ + fs::DirectoryHandle handle; + response_header.result = fs::OpenDirectory(std::addressof(handle), param->path, param->open_mode); + if (R_SUCCEEDED(response_header.result)) { + std::scoped_lock lk(m_mutex); + + if (m_open_directory_count < util::size(m_directories)) { + /* Insert the directory into our table. */ + u64 index = std::numeric_limits<u64>::max(); + for (size_t i = 0; i < util::size(m_directories); ++i) { + if (m_directories[i].handle == nullptr) { + m_directories[i] = handle; + index = i; + ++m_open_directory_count; + break; + } + } + AMS_ABORT_UNLESS(index < util::size(m_directories)); + + /* Return the index. */ + response_header.body_size = sizeof(index); + std::memcpy(body, std::addressof(index), sizeof(index)); + } else { + /* We can't actually open the directory. */ + fs::CloseDirectory(handle); + + response_header.result = fs::ResultOpenCountLimit(); + } + } + } + break; + case PacketType::CloseDirectory: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const CloseDirectoryParam *>(body); + if (header->body_size != sizeof(*param)) { + return false; + } + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that the directory handle is valid. */ + if (param->handle >= util::size(m_directories) || m_directories[param->handle].handle == nullptr) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Lock the filesystem. */ + std::scoped_lock lk2(m_fs_mutex); + + /* Close the directory. */ + fs::CloseDirectory(m_directories[param->handle]); + m_directories[param->handle].handle = {}; + --m_open_directory_count; + } + break; + case PacketType::RenameDirectory: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const RenameDirectoryParam *>(body); + if (header->body_size != sizeof(*param) + param->old_len + param->new_len) { + return false; + } + + /* Delete the directory. */ + const char *old_path = param->data + 0; + const char *new_path = param->data + param->old_len; + response_header.result = fs::RenameDirectory(old_path, new_path); + } + break; + case PacketType::CreateFile: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const CreateFileParam *>(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Create the file. */ + response_header.result = fs::CreateFile(param->path, param->size, static_cast<int>(param->option)); + } + break; + case PacketType::DeleteFile: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const DeleteFileParam *>(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Delete the file. */ + response_header.result = fs::DeleteFile(param->path); + } + break; + case PacketType::OpenFile: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const OpenFileParam *>(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Open the file. */ + fs::FileHandle handle; + response_header.result = fs::OpenFile(std::addressof(handle), param->path, param->mode); + if (R_SUCCEEDED(response_header.result)) { + std::scoped_lock lk(m_mutex); + + if (m_open_file_count < util::size(m_files)) { + /* Insert the file into our table. */ + u64 index = std::numeric_limits<u64>::max(); + for (size_t i = 0; i < util::size(m_files); ++i) { + if (m_files[i].handle == nullptr) { + m_files[i] = handle; + index = i; + ++m_open_file_count; + break; + } + } + AMS_ABORT_UNLESS(index < util::size(m_files)); + + /* Return the index. */ + response_header.body_size = sizeof(index); + std::memcpy(body, std::addressof(index), sizeof(index)); + } else { + /* We can't actually open the file. */ + fs::CloseFile(handle); + + response_header.result = fs::ResultOpenCountLimit(); + } + } + } + break; + case PacketType::FlushFile: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const FlushFileParam *>(body); + if (header->body_size != sizeof(*param)) { + return false; + } + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that the file handle is valid. */ + if (param->handle >= util::size(m_files) || m_files[param->handle].handle == nullptr) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Lock the filesystem. */ + std::scoped_lock lk2(m_fs_mutex); + + /* Flush the file. */ + response_header.result = fs::FlushFile(m_files[param->handle]); + } + break; + case PacketType::CloseFile: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const CloseFileParam *>(body); + if (header->body_size != sizeof(*param)) { + return false; + } + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that the file handle is valid. */ + if (param->handle >= util::size(m_files) || m_files[param->handle].handle == nullptr) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Lock the filesystem. */ + std::scoped_lock lk2(m_fs_mutex); + + /* Close the directory. */ + fs::CloseFile(m_files[param->handle]); + m_files[param->handle].handle = {}; + --m_open_file_count; + } + break; + case PacketType::RenameFile: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const RenameFileParam *>(body); + if (header->body_size != sizeof(*param) + param->old_len + param->new_len) { + return false; + } + + /* Delete the directory. */ + const char *old_path = param->data + 0; + const char *new_path = param->data + param->old_len; + response_header.result = fs::RenameFile(old_path, new_path); + } + break; + case PacketType::ReadFile: + { + /* Get the parameters. */ + const auto param = *reinterpret_cast<const ReadFileParam *>(body); + if (header->body_size != sizeof(param)) { + return false; + } + + /* Check that the read is valid. */ + if (param.size + sizeof(u64) > m_request_buffer_size) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Prepare response variables. */ + u64 *out_size = reinterpret_cast<u64 *>(body); + void *dst = out_size + 1; + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that the file handle is valid. */ + if (param.handle >= util::size(m_files) || m_files[param.handle].handle == nullptr) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Read the file. */ + size_t read_size; + response_header.result = fs::ReadFile(std::addressof(read_size), m_files[param.handle], param.offset, dst, param.size); + + if (R_SUCCEEDED(response_header.result)) { + *out_size = read_size; + response_header.body_size = sizeof(u64) + read_size; + } + } + break; + case PacketType::WriteFile: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const WriteFileParam *>(body); + if (header->body_size != sizeof(*param) + param->size) { + return false; + } + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that the file handle is valid. */ + if (param->handle >= util::size(m_files) || m_files[param->handle].handle == nullptr) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Lock the filesystem. */ + std::scoped_lock lk2(m_fs_mutex); + + /* Write the file. */ + response_header.result = fs::WriteFile(m_files[param->handle], param->offset, body + sizeof(*param), param->size, param->option); + } + break; + case PacketType::GetEntryType: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const GetEntryTypeParam *>(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Get the entry type. */ + fs::DirectoryEntryType type; + response_header.result = fs::GetEntryType(std::addressof(type), param->path); + + if (R_SUCCEEDED(response_header.result)) { + /* Return the type. */ + response_header.body_size = sizeof(type); + std::memcpy(body, std::addressof(type), sizeof(type)); + + static_assert(sizeof(type) == sizeof(u32)); + } + } + break; + case PacketType::ReadDirectory: + { + /* Get the parameters. */ + const auto param = *reinterpret_cast<const ReadDirectoryParam *>(body); + if (header->body_size != sizeof(param)) { + return false; + } + + /* Check that the read is valid. */ + if (sizeof(s64) + param.count * sizeof(fs::DirectoryEntry) > m_request_buffer_size) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Prepare response variables. */ + s64 *out_count = reinterpret_cast<s64 *>(body); + fs::DirectoryEntry *dst = reinterpret_cast<fs::DirectoryEntry *>(out_count + 1); + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that the directory handle is valid. */ + if (param.handle >= util::size(m_directories) || m_directories[param.handle].handle == nullptr) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Read the directory. */ + response_header.result = fs::ReadDirectory(out_count, dst, m_directories[param.handle], param.count); + + if (R_SUCCEEDED(response_header.result)) { + response_header.body_size = sizeof(s64) + *out_count * sizeof(fs::DirectoryEntry); + } + } + break; + case PacketType::GetFileSize: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const GetFileSizeParam *>(body); + if (header->body_size != sizeof(*param)) { + return false; + } + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that the file handle is valid. */ + if (param->handle >= util::size(m_files) || m_files[param->handle].handle == nullptr) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Get the file size. */ + response_header.result = fs::GetFileSize(reinterpret_cast<s64 *>(body), m_files[param->handle]); + + if (R_SUCCEEDED(response_header.result)) { + response_header.body_size = sizeof(s64); + } + } + break; + case PacketType::SetFileSize: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const SetFileSizeParam *>(body); + if (header->body_size != sizeof(*param)) { + return false; + } + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that the file handle is valid. */ + if (param->handle >= util::size(m_files) || m_files[param->handle].handle == nullptr) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Lock the filesystem. */ + std::scoped_lock lk2(m_fs_mutex); + + /* Get the file size. */ + response_header.result = fs::SetFileSize(m_files[param->handle], param->size); + } + break; + case PacketType::GetTotalSpaceSize: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const GetTotalSpaceSizeParam *>(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Get the total space size. */ + s64 size; + response_header.result = fs::GetTotalSpaceSize(std::addressof(size), param->path); + + if (R_SUCCEEDED(response_header.result)) { + /* Return the size. */ + response_header.body_size = sizeof(size); + std::memcpy(body, std::addressof(size), sizeof(size)); + } + } + break; + case PacketType::GetFreeSpaceSize: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const GetFreeSpaceSizeParam *>(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Get the free space size. */ + s64 size; + response_header.result = fs::GetFreeSpaceSize(std::addressof(size), param->path); + + if (R_SUCCEEDED(response_header.result)) { + /* Return the size. */ + response_header.body_size = sizeof(size); + std::memcpy(body, std::addressof(size), sizeof(size)); + } + } + break; + case PacketType::Stat: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const StatParam *>(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Prepare a response stat structure. */ + struct { + fs::DirectoryEntryType type; + s64 file_size; + fs::FileTimeStampRaw file_timestamp; + } out = {}; + static_assert(sizeof(out) == 0x30); + + /* Get the entry type. */ + response_header.result = fs::GetEntryType(std::addressof(out.type), param->path); + if (R_FAILED(response_header.result)) { + break; + } + + /* If the path is a file, get further information. */ + if (out.type == fs::DirectoryEntryType_File) { + /* Try to get the file size. */ + { + fs::FileHandle handle; + const auto open_result = fs::OpenFile(std::addressof(handle), param->path, fs::OpenMode_Read); + if (R_SUCCEEDED(open_result)) { + ON_SCOPE_EXIT { fs::CloseFile(handle); }; + + response_header.result = fs::GetFileSize(std::addressof(out.file_size), handle); + if (R_FAILED(response_header.result)) { + break; + } + } else { + if (fs::ResultTargetLocked::Includes(open_result)) { + out.file_size = 0; + } else { + response_header.result = open_result; + break; + } + } + } + + /* Get the file timestamp. */ + response_header.result = fs::impl::GetFileTimeStampRawForDebug(std::addressof(out.file_timestamp), param->path); + if (R_FAILED(response_header.result)) { + break; + } + } + + /* If we successfully got the stat information, send it as response. */ + if (R_SUCCEEDED(response_header.result)) { + response_header.body_size = sizeof(out); + std::memcpy(body, std::addressof(out), sizeof(out)); + } + } + break; + case PacketType::ListDirectory: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast<const ListDirectoryParam *>(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Open the directory. */ + fs::DirectoryHandle handle; + response_header.result = fs::OpenDirectory(std::addressof(handle), param->path, fs::OpenDirectoryMode_All); + if (R_FAILED(response_header.result)) { + break; + } + + /* When we're done, close the handle. */ + ON_SCOPE_EXIT { fs::CloseDirectory(handle); }; + + /* Get the directory entry count. */ + s64 count; + response_header.result = fs::GetDirectoryEntryCount(std::addressof(count), handle); + if (R_FAILED(response_header.result)) { + break; + } + /* Determine whether we can send the response in one go. */ + const size_t needed_size = sizeof(s64) + sizeof(u64) + sizeof(fs::DirectoryEntry) * count; + if (needed_size <= m_request_buffer_size) { + /* We can perform the entire read in one send. */ + struct { + s64 count; + u64 size; + fs::DirectoryEntry entries[]; + } *out = reinterpret_cast<decltype(out)>(body); + + s64 read_count; + response_header.result = fs::ReadDirectory(std::addressof(read_count), out->entries, handle, count); + if (R_FAILED(response_header.result)) { + break; + } + + /* Set the output. */ + out->count = read_count; + out->size = read_count * sizeof(fs::DirectoryEntry); + + /* Set the response body size. */ + response_header.body_size = sizeof(*out) + out->size; + } else { + /* We have to use multiple sends. */ + /* Lock our server. */ + std::scoped_lock lk(m_htcs_server.GetMutex()); + + /* Send the response header. */ + response_header.body_size = needed_size; + if (m_htcs_server.Send(socket, std::addressof(header), sizeof(header), 0) != sizeof(header)) { + return false; + } + + /* Send the body header. */ + struct { + s64 count; + u64 size; + } out = { count, count * sizeof(fs::DirectoryEntry) }; + if (m_htcs_server.Send(socket, std::addressof(out), sizeof(out), 0) != sizeof(out)) { + return false; + } + + /* Loop sending entries. */ + s64 remaining = count; + do { + /* Determine how many entries we can read. */ + const s64 cur = std::min<s64>(remaining, static_cast<s64>(m_request_buffer_size / sizeof(fs::DirectoryEntry))); + + /* NOTE: Nintendo does not check the output of this call. */ + s64 read_count = 0; + fs::ReadDirectory(std::addressof(read_count), reinterpret_cast<fs::DirectoryEntry *>(body), handle, cur); + + /* Send the current entries. */ + const ssize_t cur_size = read_count * sizeof(fs::DirectoryEntry); + if (m_htcs_server.Send(socket, body, cur_size, 0) != cur_size) { + return false; + } + + /* Advance. */ + remaining -= read_count; + } while (remaining > 0); + + /* We've sent the entirety of our response, so early return. */ + return true; + } + } + break; + default: + /* Unsupported packet. */ + return false; + } + + /* Send the response. */ + return this->SendResponse(response_header, body, socket); + } else if (m_is_mounted) { + /* The SD card is mounted but not inserted, so we should unmount it. */ + this->Unmount(); + } + + /* We failed to process the request due to SD card not being inserted or mounted. */ + response_header.result = fs::ResultSdCardAccessFailed(); + + return this->SendResponse(response_header, body, socket); + } + + bool FileServerProcessor::SendResponse(const FileServerResponseHeader &header, const void *body, int socket) { + /* Lock our server. */ + std::scoped_lock lk(m_htcs_server.GetMutex()); + + /* Send the response header. */ + if (m_htcs_server.Send(socket, std::addressof(header), sizeof(header), 0) != sizeof(header)) { + return false; + } + + /* If we don't have a body, we're done. */ + if (header.body_size == 0) { + return true; + } + + /* Send the body. */ + return m_htcs_server.Send(socket, body, header.body_size, 0) == header.body_size; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server_processor.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server_processor.hpp new file mode 100644 index 00000000..ddc59a88 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_file_server_processor.hpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "tio_file_server_htcs_server.hpp" +#include "tio_file_server_packet.hpp" + +namespace ams::tio { + + class FileServerProcessor { + private: + bool m_is_inserted{}; + bool m_is_mounted{}; + size_t m_request_buffer_size{}; + FileServerHtcsServer &m_htcs_server; + size_t m_open_file_count{}; + size_t m_open_directory_count{}; + fs::FileHandle m_files[0x80]{}; + fs::DirectoryHandle m_directories[0x80]{}; + os::SdkMutex m_fs_mutex{}; + os::SdkMutex m_mutex{}; + public: + constexpr FileServerProcessor(FileServerHtcsServer &htcs_server) : m_htcs_server(htcs_server) { /* ... */ } + + void SetInserted(bool ins) { m_is_inserted = ins; } + void SetRequestBufferSize(size_t size) { m_request_buffer_size = size; } + public: + bool ProcessRequest(FileServerRequestHeader *header, u8 *body, int socket); + + void Unmount(); + private: + bool SendResponse(const FileServerResponseHeader &header, const void *body, int socket); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_main.cpp new file mode 100644 index 00000000..0c33d763 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_main.cpp @@ -0,0 +1,93 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "tio_file_server.hpp" + +namespace ams { + + namespace tio { + + namespace { + + alignas(0x40) constinit u8 g_fs_heap_buffer[64_KB]; + alignas(0x40) constinit u8 g_htcs_buffer[1_KB]; + lmem::HeapHandle g_fs_heap_handle; + + void *AllocateForFs(size_t size) { + return lmem::AllocateFromExpHeap(g_fs_heap_handle, size); + } + + void DeallocateForFs(void *p, size_t size) { + AMS_UNUSED(size); + + return lmem::FreeToExpHeap(g_fs_heap_handle, p); + } + + void InitializeFsHeap() { + /* Setup fs allocator. */ + g_fs_heap_handle = lmem::CreateExpHeap(g_fs_heap_buffer, sizeof(g_fs_heap_buffer), lmem::CreateOption_ThreadSafe); + } + + } + + } + + namespace init { + + void InitializeSystemModule() { + /* Initialize heap. */ + tio::InitializeFsHeap(); + + /* Initialize our connection to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + + /* Initialize fs. */ + fs::InitializeForSystem(); + fs::SetAllocator(tio::AllocateForFs, tio::DeallocateForFs); + fs::SetEnabledAutoAbort(false); + + /* Verify that we can sanely execute. */ + ams::CheckApiVersion(); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void Main() { + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(TioServer, Main)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(TioServer, Main)); + + /* Initialize htcs. */ + constexpr auto HtcsSocketCountMax = 2; + const size_t buffer_size = htcs::GetWorkingMemorySize(HtcsSocketCountMax); + AMS_ABORT_UNLESS(sizeof(tio::g_htcs_buffer) >= buffer_size); + htcs::InitializeForSystem(tio::g_htcs_buffer, buffer_size, HtcsSocketCountMax); + + /* Initialize the file server. */ + tio::InitializeFileServer(); + + /* Start the file server. */ + tio::StartFileServer(); + + /* Wait for the file server to finish. */ + tio::WaitFileServer(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_sd_card_observer.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_sd_card_observer.cpp new file mode 100644 index 00000000..7ac79437 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_sd_card_observer.cpp @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "tio_sd_card_observer.hpp" + +namespace ams::tio { + + void SdCardObserver::Initialize(void *thread_stack, size_t thread_stack_size) { + /* Setup our thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_thread), ThreadEntry, this, thread_stack, thread_stack_size, AMS_GET_SYSTEM_THREAD_PRIORITY(TioServer, SdCardObserver))); + + /* Set our thread name pointer. */ + os::SetThreadNamePointer(std::addressof(m_thread), AMS_GET_SYSTEM_THREAD_NAME(TioServer, SdCardObserver)); + + /* Set our initial insertion state. */ + m_inserted = fs::IsSdCardInserted(); + } + + void SdCardObserver::SetCallback(SdCardInsertionCallback callback) { + /* Check that we don't already have a callback. */ + AMS_ABORT_UNLESS(m_callback == nullptr); + + /* Set our callback. */ + m_callback = callback; + } + + void SdCardObserver::ThreadFunc() { + /* Open detection event notifier. */ + std::unique_ptr<fs::IEventNotifier> notifier; + R_ABORT_UNLESS(fs::OpenSdCardDetectionEventNotifier(std::addressof(notifier))); + + /* Bind the detection event. */ + os::SystemEventType event; + R_ABORT_UNLESS(notifier->BindEvent(std::addressof(event), os::EventClearMode_AutoClear)); + + /* Loop, waiting for insertion events. */ + while (true) { + /* Wait for an event. */ + os::WaitSystemEvent(std::addressof(event)); + + /* Update our insertion state. */ + m_inserted = fs::IsSdCardInserted(); + + /* Invoke our callback. */ + if (m_callback) { + m_callback(m_inserted); + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_sd_card_observer.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_sd_card_observer.hpp new file mode 100644 index 00000000..506f27ba --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/source/tio_sd_card_observer.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::tio { + + using SdCardInsertionCallback = void(*)(bool inserted); + + class SdCardObserver { + private: + bool m_inserted; + SdCardInsertionCallback m_callback; + os::ThreadType m_thread; + public: + constexpr SdCardObserver() : m_inserted(false), m_callback(nullptr), m_thread{} { /* ... */ } + + bool IsSdCardInserted() const { return m_inserted; } + private: + static void ThreadEntry(void *arg) { + static_cast<SdCardObserver *>(arg)->ThreadFunc(); + } + + void ThreadFunc(); + public: + void Initialize(void *thread_stack, size_t thread_stack_size); + void SetCallback(SdCardInsertionCallback callback); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/system_module.mk new file mode 100644 index 00000000..d6de42ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/TioServer/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := nsp + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/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)/system_module.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)/system_module.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/stratosphere/ams_mitm/ams_mitm.json b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/ams_mitm.json new file mode 100644 index 00000000..0b98a258 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/ams_mitm.json @@ -0,0 +1,86 @@ +{ + "name": "ams.mitm", + "title_id": "0x010041544D530000", + "main_thread_stack_size": "0x20000", + "main_thread_priority": 43, + "default_cpu_id": 3, + "process_category": 1, + "use_secure_memory": true, + "immortal": true, + "kernel_capabilities": [ + { + "type": "handle_table_size", + "value": 512 + }, + { + "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", + "svcSynchronizePreemptionState": "0x36", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcMapPhysicalMemoryUnsafe": "0x48", + "svcUnmapPhysicalMemoryUnsafe": "0x49", + "svcMapTransferMemory": "0x51", + "svcUnmapTransferMemory": "0x52", + "svcCreateInterruptEvent": "0x53", + "svcReadWriteRegister": "0x4E", + "svcQueryIoMapping": "0x55", + "svcCreateDeviceAddressSpace": "0x56", + "svcAttachDeviceAddressSpace": "0x57", + "svcDetachDeviceAddressSpace": "0x58", + "svcMapDeviceAddressSpaceAligned": "0x5a", + "svcUnmapDeviceAddressSpace": "0x5c", + "svcGetSystemInfo": "0x6f", + "svcManageNamedPort": "0x71", + "svcCallSecureMonitor": "0x7F" + } + } + ] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_fs_utils.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_fs_utils.cpp new file mode 100644 index 00000000..72e00bf7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_fs_utils.cpp @@ -0,0 +1,261 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "amsmitm_initialization.hpp" +#include "amsmitm_fs_utils.hpp" + +namespace ams::mitm::fs { + + using namespace ams::fs; + + namespace { + + /* Globals. */ + FsFileSystem g_sd_filesystem; + + /* Helpers. */ + Result EnsureSdInitialized() { + R_UNLESS(serviceIsActive(std::addressof(g_sd_filesystem.s)), ams::fs::ResultSdCardNotPresent()); + R_SUCCEED(); + } + + void FormatAtmosphereRomfsPath(char *dst_path, size_t dst_path_size, ncm::ProgramId program_id, const char *src_path) { + return FormatAtmosphereSdPath(dst_path, dst_path_size, program_id, "romfs", src_path); + } + + } + + void OpenGlobalSdCardFileSystem() { + R_ABORT_UNLESS(fsOpenSdCardFileSystem(std::addressof(g_sd_filesystem))); + } + + Result CreateSdFile(const char *path, s64 size, s32 option) { + R_TRY(EnsureSdInitialized()); + R_RETURN(fsFsCreateFile(std::addressof(g_sd_filesystem), path, size, option)); + } + + Result DeleteSdFile(const char *path) { + R_TRY(EnsureSdInitialized()); + R_RETURN(fsFsDeleteFile(std::addressof(g_sd_filesystem), path)); + } + + bool HasSdFile(const char *path) { + if (R_FAILED(EnsureSdInitialized())) { + return false; + } + + FsDirEntryType type; + if (R_FAILED(fsFsGetEntryType(std::addressof(g_sd_filesystem), path, std::addressof(type)))) { + return false; + } + + return type == FsDirEntryType_File; + } + + bool HasAtmosphereSdFile(const char *path) { + char fixed_path[ams::fs::EntryNameLengthMax + 1]; + FormatAtmosphereSdPath(fixed_path, sizeof(fixed_path), path); + return HasSdFile(fixed_path); + } + + Result DeleteAtmosphereSdFile(const char *path) { + char fixed_path[ams::fs::EntryNameLengthMax + 1]; + FormatAtmosphereSdPath(fixed_path, sizeof(fixed_path), path); + R_RETURN(DeleteSdFile(fixed_path)); + } + + Result CreateAtmosphereSdFile(const char *path, s64 size, s32 option) { + char fixed_path[ams::fs::EntryNameLengthMax + 1]; + FormatAtmosphereSdPath(fixed_path, sizeof(fixed_path), path); + R_RETURN(CreateSdFile(fixed_path, size, option)); + } + + Result OpenSdFile(FsFile *out, const char *path, u32 mode) { + R_TRY(EnsureSdInitialized()); + R_RETURN(fsFsOpenFile(std::addressof(g_sd_filesystem), path, mode, out)); + } + + Result OpenAtmosphereSdFile(FsFile *out, const char *path, u32 mode) { + char fixed_path[ams::fs::EntryNameLengthMax + 1]; + FormatAtmosphereSdPath(fixed_path, sizeof(fixed_path), path); + R_RETURN(OpenSdFile(out, fixed_path, mode)); + } + + Result OpenAtmosphereSdFile(FsFile *out, ncm::ProgramId program_id, const char *path, u32 mode) { + char fixed_path[ams::fs::EntryNameLengthMax + 1]; + FormatAtmosphereSdPath(fixed_path, sizeof(fixed_path), program_id, path); + R_RETURN(OpenSdFile(out, fixed_path, mode)); + } + + Result OpenAtmosphereSdRomfsFile(FsFile *out, ncm::ProgramId program_id, const char *path, u32 mode) { + char fixed_path[ams::fs::EntryNameLengthMax + 1]; + FormatAtmosphereRomfsPath(fixed_path, sizeof(fixed_path), program_id, path); + R_RETURN(OpenSdFile(out, fixed_path, mode)); + } + + Result OpenAtmosphereRomfsFile(FsFile *out, ncm::ProgramId program_id, const char *path, u32 mode, FsFileSystem *fs) { + char fixed_path[ams::fs::EntryNameLengthMax + 1]; + FormatAtmosphereRomfsPath(fixed_path, sizeof(fixed_path), program_id, path); + R_RETURN(fsFsOpenFile(fs, fixed_path, mode, out)); + } + + Result CreateSdDirectory(const char *path) { + R_TRY(EnsureSdInitialized()); + R_RETURN(fsFsCreateDirectory(std::addressof(g_sd_filesystem), path)); + } + + Result CreateAtmosphereSdDirectory(const char *path) { + char fixed_path[ams::fs::EntryNameLengthMax + 1]; + FormatAtmosphereSdPath(fixed_path, sizeof(fixed_path), path); + R_RETURN(CreateSdDirectory(fixed_path)); + } + + Result OpenSdDirectory(FsDir *out, const char *path, u32 mode) { + R_TRY(EnsureSdInitialized()); + R_RETURN(fsFsOpenDirectory(std::addressof(g_sd_filesystem), path, mode, out)); + } + + Result OpenAtmosphereSdDirectory(FsDir *out, const char *path, u32 mode) { + char fixed_path[ams::fs::EntryNameLengthMax + 1]; + FormatAtmosphereSdPath(fixed_path, sizeof(fixed_path), path); + R_RETURN(OpenSdDirectory(out, fixed_path, mode)); + } + + Result OpenAtmosphereSdDirectory(FsDir *out, ncm::ProgramId program_id, const char *path, u32 mode) { + char fixed_path[ams::fs::EntryNameLengthMax + 1]; + FormatAtmosphereSdPath(fixed_path, sizeof(fixed_path), program_id, path); + R_RETURN(OpenSdDirectory(out, fixed_path, mode)); + } + + Result OpenAtmosphereSdRomfsDirectory(FsDir *out, ncm::ProgramId program_id, const char *path, u32 mode) { + char fixed_path[ams::fs::EntryNameLengthMax + 1]; + FormatAtmosphereRomfsPath(fixed_path, sizeof(fixed_path), program_id, path); + R_RETURN(OpenSdDirectory(out, fixed_path, mode)); + } + + Result OpenAtmosphereRomfsDirectory(FsDir *out, ncm::ProgramId program_id, const char *path, u32 mode, FsFileSystem *fs) { + char fixed_path[ams::fs::EntryNameLengthMax + 1]; + FormatAtmosphereRomfsPath(fixed_path, sizeof(fixed_path), program_id, path); + R_RETURN(fsFsOpenDirectory(fs, fixed_path, mode, out)); + } + + void FormatAtmosphereSdPath(char *dst_path, size_t dst_path_size, const char *src_path) { + if (src_path[0] == '/') { + util::SNPrintf(dst_path, dst_path_size, "/atmosphere%s", src_path); + } else { + util::SNPrintf(dst_path, dst_path_size, "/atmosphere/%s", src_path); + } + } + + void FormatAtmosphereSdPath(char *dst_path, size_t dst_path_size, const char *subdir, const char *src_path) { + if (src_path[0] == '/') { + util::SNPrintf(dst_path, dst_path_size, "/atmosphere/%s%s", subdir, src_path); + } else { + util::SNPrintf(dst_path, dst_path_size, "/atmosphere/%s/%s", subdir, src_path); + } + } + + void FormatAtmosphereSdPath(char *dst_path, size_t dst_path_size, ncm::ProgramId program_id, const char *src_path) { + if (src_path[0] == '/') { + util::SNPrintf(dst_path, dst_path_size, "/atmosphere/contents/%016lx%s", static_cast<u64>(program_id), src_path); + } else { + util::SNPrintf(dst_path, dst_path_size, "/atmosphere/contents/%016lx/%s", static_cast<u64>(program_id), src_path); + } + } + + void FormatAtmosphereSdPath(char *dst_path, size_t dst_path_size, ncm::ProgramId program_id, const char *subdir, const char *src_path) { + if (src_path[0] == '/') { + util::SNPrintf(dst_path, dst_path_size, "/atmosphere/contents/%016lx/%s%s", static_cast<u64>(program_id), subdir, src_path); + } else { + util::SNPrintf(dst_path, dst_path_size, "/atmosphere/contents/%016lx/%s/%s", static_cast<u64>(program_id), subdir, src_path); + } + } + + bool HasSdRomfsContent(ncm::ProgramId program_id) { + /* Check if romfs.bin is present. */ + { + FsFile romfs_file; + if (R_SUCCEEDED(OpenAtmosphereSdFile(std::addressof(romfs_file), program_id, "romfs.bin", OpenMode_Read))) { + fsFileClose(std::addressof(romfs_file)); + return true; + } + } + + /* Check for romfs folder with content. */ + FsDir romfs_dir; + if (R_FAILED(OpenAtmosphereSdRomfsDirectory(std::addressof(romfs_dir), program_id, "", fs::OpenDirectoryMode_All))) { + return false; + } + ON_SCOPE_EXIT { fsDirClose(std::addressof(romfs_dir)); }; + + /* Verify the folder has at least one entry. */ + s64 num_entries = 0; + return R_SUCCEEDED(fsDirGetEntryCount(std::addressof(romfs_dir), std::addressof(num_entries))) && num_entries > 0; + } + + Result SaveAtmosphereSdFile(FsFile *out, ncm::ProgramId program_id, const char *path, void *data, size_t size) { + R_TRY(EnsureSdInitialized()); + + char fixed_path[ams::fs::EntryNameLengthMax + 1]; + FormatAtmosphereSdPath(fixed_path, sizeof(fixed_path), program_id, path); + + /* Unconditionally create. */ + /* Don't check error, as a failure here should be okay. */ + FsFile f; + fsFsCreateFile(std::addressof(g_sd_filesystem), fixed_path, size, 0); + + /* Try to open. */ + R_TRY(fsFsOpenFile(std::addressof(g_sd_filesystem), fixed_path, OpenMode_ReadWrite, std::addressof(f))); + auto file_guard = SCOPE_GUARD { fsFileClose(std::addressof(f)); }; + + /* Try to set the size. */ + R_TRY(fsFileSetSize(std::addressof(f), static_cast<s64>(size))); + + /* Try to write data. */ + R_TRY(fsFileWrite(std::addressof(f), 0, data, size, FsWriteOption_Flush)); + + /* Set output. */ + file_guard.Cancel(); + *out = f; + R_SUCCEED(); + } + + Result CreateAndOpenAtmosphereSdFile(FsFile *out, ncm::ProgramId program_id, const char *path, size_t size) { + R_TRY(EnsureSdInitialized()); + + char fixed_path[ams::fs::EntryNameLengthMax + 1]; + FormatAtmosphereSdPath(fixed_path, sizeof(fixed_path), program_id, path); + + /* Unconditionally create. */ + /* Don't check error, as a failure here should be okay. */ + FsFile f; + fsFsCreateFile(std::addressof(g_sd_filesystem), fixed_path, size, 0); + + /* Try to open. */ + R_TRY(fsFsOpenFile(std::addressof(g_sd_filesystem), fixed_path, OpenMode_ReadWrite, std::addressof(f))); + auto file_guard = SCOPE_GUARD { fsFileClose(std::addressof(f)); }; + + /* Try to set the size. */ + R_TRY(fsFileSetSize(std::addressof(f), static_cast<s64>(size))); + + /* Set output. */ + file_guard.Cancel(); + *out = f; + R_SUCCEED(); + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_fs_utils.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_fs_utils.hpp new file mode 100644 index 00000000..2e22dd90 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_fs_utils.hpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::mitm::fs { + + /* Initialization. */ + void OpenGlobalSdCardFileSystem(); + + /* Utilities. */ + Result DeleteAtmosphereSdFile(const char *path); + Result CreateSdFile(const char *path, s64 size, s32 option); + Result CreateAtmosphereSdFile(const char *path, s64 size, s32 option); + Result OpenSdFile(FsFile *out, const char *path, u32 mode); + Result OpenAtmosphereSdFile(FsFile *out, const char *path, u32 mode); + Result OpenAtmosphereSdFile(FsFile *out, ncm::ProgramId program_id, const char *path, u32 mode); + Result OpenAtmosphereSdRomfsFile(FsFile *out, ncm::ProgramId program_id, const char *path, u32 mode); + Result OpenAtmosphereRomfsFile(FsFile *out, ncm::ProgramId program_id, const char *path, u32 mode, FsFileSystem *fs); + + bool HasSdFile(const char *path); + bool HasAtmosphereSdFile(const char *path); + + Result CreateSdDirectory(const char *path); + Result CreateAtmosphereSdDirectory(const char *path); + Result OpenSdDirectory(FsDir *out, const char *path, u32 mode); + Result OpenAtmosphereSdDirectory(FsDir *out, const char *path, u32 mode); + Result OpenAtmosphereSdDirectory(FsDir *out, ncm::ProgramId program_id, const char *path, u32 mode); + Result OpenAtmosphereSdRomfsDirectory(FsDir *out, ncm::ProgramId program_id, const char *path, u32 mode); + Result OpenAtmosphereRomfsDirectory(FsDir *out, ncm::ProgramId program_id, const char *path, u32 mode, FsFileSystem *fs); + + void FormatAtmosphereSdPath(char *dst_path, size_t dst_path_size, const char *src_path); + void FormatAtmosphereSdPath(char *dst_path, size_t dst_path_size, const char *subdir, const char *src_path); + void FormatAtmosphereSdPath(char *dst_path, size_t dst_path_size, ncm::ProgramId program_id, const char *src_path); + void FormatAtmosphereSdPath(char *dst_path, size_t dst_path_size, ncm::ProgramId program_id, const char *subdir, const char *src_path); + + bool HasSdRomfsContent(ncm::ProgramId program_id); + + Result SaveAtmosphereSdFile(FsFile *out, ncm::ProgramId program_id, const char *path, void *data, size_t size); + Result CreateAndOpenAtmosphereSdFile(FsFile *out, ncm::ProgramId program_id, const char *path, size_t size); + + /* NOTE: Implemented in fs.mitm logic. */ + bool HasSdManualHtmlContent(ncm::ProgramId program_id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_initialization.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_initialization.cpp new file mode 100644 index 00000000..e9979ce5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_initialization.cpp @@ -0,0 +1,245 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "amsmitm_initialization.hpp" +#include "amsmitm_fs_utils.hpp" +#include "amsmitm_prodinfo_utils.hpp" +#include "bpc_mitm/bpc_ams_power_utils.hpp" +#include "set_mitm/settings_sd_kvs.hpp" + +namespace ams::mitm { + + namespace { + + /* BIS key sources. */ + constexpr u8 BisKeySources[4][2][0x10] = { + { + {0xF8, 0x3F, 0x38, 0x6E, 0x2C, 0xD2, 0xCA, 0x32, 0xA8, 0x9A, 0xB9, 0xAA, 0x29, 0xBF, 0xC7, 0x48}, + {0x7D, 0x92, 0xB0, 0x3A, 0xA8, 0xBF, 0xDE, 0xE1, 0xA7, 0x4C, 0x3B, 0x6E, 0x35, 0xCB, 0x71, 0x06} + }, + { + {0x41, 0x00, 0x30, 0x49, 0xDD, 0xCC, 0xC0, 0x65, 0x64, 0x7A, 0x7E, 0xB4, 0x1E, 0xED, 0x9C, 0x5F}, + {0x44, 0x42, 0x4E, 0xDA, 0xB4, 0x9D, 0xFC, 0xD9, 0x87, 0x77, 0x24, 0x9A, 0xDC, 0x9F, 0x7C, 0xA4} + }, + { + {0x52, 0xC2, 0xE9, 0xEB, 0x09, 0xE3, 0xEE, 0x29, 0x32, 0xA1, 0x0C, 0x1F, 0xB6, 0xA0, 0x92, 0x6C}, + {0x4D, 0x12, 0xE1, 0x4B, 0x2A, 0x47, 0x4C, 0x1C, 0x09, 0xCB, 0x03, 0x59, 0xF0, 0x15, 0xF4, 0xE4} + }, + { + {0x52, 0xC2, 0xE9, 0xEB, 0x09, 0xE3, 0xEE, 0x29, 0x32, 0xA1, 0x0C, 0x1F, 0xB6, 0xA0, 0x92, 0x6C}, + {0x4D, 0x12, 0xE1, 0x4B, 0x2A, 0x47, 0x4C, 0x1C, 0x09, 0xCB, 0x03, 0x59, 0xF0, 0x15, 0xF4, 0xE4} + } + }; + + constexpr u8 BisKekSource[0x10] = { + 0x34, 0xC1, 0xA0, 0xC4, 0x82, 0x58, 0xF8, 0xB4, 0xFA, 0x9E, 0x5E, 0x6A, 0xDA, 0xFC, 0x7E, 0x4F, + }; + + void InitializeThreadFunc(void *arg); + + constexpr size_t InitializeThreadStackSize = 0x4000; + + /* Globals. */ + os::Event g_init_event(os::EventClearMode_ManualClear); + + os::ThreadType g_initialize_thread; + alignas(os::ThreadStackAlignment) u8 g_initialize_thread_stack[InitializeThreadStackSize]; + + /* Console-unique data backup and protection. */ + FsFile g_bis_key_file; + + /* Emummc file protection. */ + FsFile g_emummc_file; + + /* Maintain exclusive access to the fusee external package. */ + FsFile g_stratosphere_file; + FsFile g_package3_file; + + constexpr inline bool IsHexadecimal(const char *str) { + while (*str) { + if (std::isxdigit(static_cast<unsigned char>(*str))) { + str++; + } else { + return false; + } + } + return true; + } + + void GetBackupFileName(char *dst, size_t dst_size, const char *serial_number, const char *fn) { + if (strlen(serial_number) > 0) { + util::SNPrintf(dst, dst_size, "automatic_backups/%s_%s", serial_number, fn); + } else { + util::SNPrintf(dst, dst_size, "automatic_backups/%s", fn); + } + } + + void CreateAutomaticBackups() { + /* Create a backup directory, if one doesn't exist. */ + mitm::fs::CreateAtmosphereSdDirectory("/automatic_backups"); + + /* Initialize PRODINFO and get a reference for the device. */ + char device_reference[0x40] = {}; + ON_SCOPE_EXIT { std::memset(device_reference, 0, sizeof(device_reference)); }; + mitm::SaveProdInfoBackupsAndWipeMemory(device_reference, sizeof(device_reference)); + + /* Backup BIS keys. */ + { + u64 key_generation = 0; + if (hos::GetVersion() >= hos::Version_5_0_0) { + R_ABORT_UNLESS(spl::GetConfig(std::addressof(key_generation), spl::ConfigItem::DeviceUniqueKeyGeneration)); + } + + u8 bis_keys[4][2][0x10]; + std::memset(bis_keys, 0xCC, sizeof(bis_keys)); + ON_SCOPE_EXIT { std::memset(bis_keys, 0xCC, sizeof(bis_keys)); }; + + /* TODO: Clean this up. */ + for (size_t partition = 0; partition < 4; partition++) { + if (partition == 0) { + for (size_t i = 0; i < 2; i++) { + R_ABORT_UNLESS(spl::GenerateSpecificAesKey(bis_keys[partition][i], 0x10, BisKeySources[partition][i], 0x10, key_generation, i)); + } + } else { + const u32 option = (partition == 3 && spl::IsRecoveryBoot()) ? 0x4 : 0x1; + + spl::AccessKey access_key; + R_ABORT_UNLESS(spl::GenerateAesKek(std::addressof(access_key), BisKekSource, 0x10, key_generation, option)); + for (size_t i = 0; i < 2; i++) { + R_ABORT_UNLESS(spl::GenerateAesKey(bis_keys[partition][i], 0x10, access_key, BisKeySources[partition][i], 0x10)); + } + } + } + + char bis_keys_backup_name[ams::fs::EntryNameLengthMax + 1]; + GetBackupFileName(bis_keys_backup_name, sizeof(bis_keys_backup_name), device_reference, "BISKEYS.bin"); + + mitm::fs::CreateAtmosphereSdFile(bis_keys_backup_name, sizeof(bis_keys), ams::fs::CreateOption_None); + R_ABORT_UNLESS(mitm::fs::OpenAtmosphereSdFile(std::addressof(g_bis_key_file), bis_keys_backup_name, ams::fs::OpenMode_ReadWrite)); + R_ABORT_UNLESS(fsFileSetSize(std::addressof(g_bis_key_file), sizeof(bis_keys))); + R_ABORT_UNLESS(fsFileWrite(std::addressof(g_bis_key_file), 0, bis_keys, sizeof(bis_keys), FsWriteOption_Flush)); + /* NOTE: g_bis_key_file is intentionally not closed here. This prevents any other process from opening it. */ + } + + /* Open a reference to the fusee external package. */ + /* As upcoming/current atmosphere releases may contain more than one zip which users much choose between, */ + /* maintaining an open reference prevents cleanly the issue of "automatic" updaters selecting the incorrect */ + /* zip, and encourages good updating hygiene -- atmosphere should not be updated on SD while HOS is alive. */ + { + R_ABORT_UNLESS(mitm::fs::OpenSdFile(std::addressof(g_package3_file), "/atmosphere/package3", ams::fs::OpenMode_Read)); + R_ABORT_UNLESS(mitm::fs::OpenSdFile(std::addressof(g_stratosphere_file), "/atmosphere/stratosphere.romfs", ams::fs::OpenMode_Read)); + } + } + + /* Initialization implementation */ + void InitializeThreadFunc(void *) { + /* Wait for the SD card to be ready. */ + cfg::WaitSdCardInitialized(); + + /* Open global SD card file system, so that other threads can begin using the SD. */ + mitm::fs::OpenGlobalSdCardFileSystem(); + + /* Mount the sd card at a convenient mountpoint. */ + ams::fs::MountSdCard(ams::fs::impl::SdCardFileSystemMountName); + + /* Initialize the reboot manager (load a payload off the SD). */ + /* Discard result, since it doesn't need to succeed. */ + mitm::bpc::LoadRebootPayload(); + + /* Backup Calibration Binary and BIS keys. */ + CreateAutomaticBackups(); + + /* If we're emummc, persist a write-handle to prevent other processes from touching the image. */ + if (emummc::IsActive()) { + if (const char *emummc_file_path = emummc::GetFilePath(); emummc_file_path != nullptr) { + char emummc_path[ams::fs::EntryNameLengthMax + 1]; + util::SNPrintf(emummc_path, sizeof(emummc_path), "%s/eMMC", emummc_file_path); + mitm::fs::OpenSdFile(std::addressof(g_emummc_file), emummc_path, ams::fs::OpenMode_Read); + } + + /* NOTE: due to an Atmosphere bug, NS accesses to the Nintendo dir accessed /Nintendo/Nintendo */ + /* instead of /Nintendo. This logic is potentially temporary, and fixes the case where this would have happened. */ + { + auto HasDir = [](const char *p) -> bool { bool res{}; R_ABORT_UNLESS(ams::fs::HasDirectory(std::addressof(res), p)); return res; }; + auto HasFile = [](const char *p) -> bool { bool res{}; R_ABORT_UNLESS(ams::fs::HasFile(std::addressof(res), p)); return res; }; + + char emummc_path[ams::fs::EntryNameLengthMax + 1]; + char emummc_bug_path[ams::fs::EntryNameLengthMax + 1]; + util::SNPrintf(emummc_path, sizeof(emummc_path), "%s:/%s", ams::fs::impl::SdCardFileSystemMountName, emummc::GetNintendoDirPath()); + util::SNPrintf(emummc_bug_path, sizeof(emummc_bug_path), "%s:/%s/Nintendo", ams::fs::impl::SdCardFileSystemMountName, emummc::GetNintendoDirPath()); + + if (HasDir(emummc_bug_path)) { + /* Ensure Contents directory exists for normal emummc. */ + /* NOTE: Allowed to fail on already-exists. */ + util::SNPrintf(emummc_path, sizeof(emummc_path), "%s:/%s/Contents", ams::fs::impl::SdCardFileSystemMountName, emummc::GetNintendoDirPath()); + ams::fs::CreateDirectory(emummc_path); + + /* Fix Contents/private */ + util::SNPrintf(emummc_path, sizeof(emummc_path), "%s:/%s/Contents/private", ams::fs::impl::SdCardFileSystemMountName, emummc::GetNintendoDirPath()); + util::SNPrintf(emummc_bug_path, sizeof(emummc_bug_path), "%s:/%s/Nintendo/Contents/private", ams::fs::impl::SdCardFileSystemMountName, emummc::GetNintendoDirPath()); + if (HasFile(emummc_bug_path) && !HasFile(emummc_path)) { + R_ABORT_UNLESS(ams::fs::RenameFile(emummc_bug_path, emummc_path)); + } + + /* Fix Contents/private1 */ + util::SNPrintf(emummc_path, sizeof(emummc_path), "%s:/%s/Contents/private1", ams::fs::impl::SdCardFileSystemMountName, emummc::GetNintendoDirPath()); + util::SNPrintf(emummc_bug_path, sizeof(emummc_bug_path), "%s:/%s/Nintendo/Contents/private1", ams::fs::impl::SdCardFileSystemMountName, emummc::GetNintendoDirPath()); + if (HasFile(emummc_bug_path) && !HasFile(emummc_path)) { + R_ABORT_UNLESS(ams::fs::RenameFile(emummc_bug_path, emummc_path)); + } + + /* Delete bug directory. */ + util::SNPrintf(emummc_bug_path, sizeof(emummc_bug_path), "%s:/%s/Nintendo", ams::fs::impl::SdCardFileSystemMountName, emummc::GetNintendoDirPath()); + R_ABORT_UNLESS(ams::fs::DeleteDirectoryRecursively(emummc_bug_path)); + } + } + } + + /* Connect to set:sys. */ + R_ABORT_UNLESS(setInitialize()); + R_ABORT_UNLESS(setsysInitialize()); + + /* Load settings off the SD card. */ + settings::fwdbg::InitializeSdCardKeyValueStore(); + + /* Ensure that we reboot using the user's preferred method. */ + R_ABORT_UNLESS(mitm::bpc::DetectPreferredRebootFunctionality()); + + /* Signal to waiters that we are ready. */ + g_init_event.Signal(); + } + + } + + void StartInitialize() { + /* Initialize prodinfo. */ + mitm::InitializeProdInfoManagement(); + + /* Launch initialize thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(g_initialize_thread), InitializeThreadFunc, nullptr, g_initialize_thread_stack, sizeof(g_initialize_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(mitm, InitializeThread))); + os::SetThreadNamePointer(std::addressof(g_initialize_thread), AMS_GET_SYSTEM_THREAD_NAME(mitm, InitializeThread)); + os::StartThread(std::addressof(g_initialize_thread)); + } + + bool IsInitialized() { + return g_init_event.TryWait(); + } + + void WaitInitialized() { + g_init_event.Wait(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_initialization.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_initialization.hpp new file mode 100644 index 00000000..ab1c90f4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_initialization.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::mitm { + + void StartInitialize(); + + bool IsInitialized(); + void WaitInitialized(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_main.cpp new file mode 100644 index 00000000..9e053813 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_main.cpp @@ -0,0 +1,93 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "amsmitm_initialization.hpp" +#include "amsmitm_module_management.hpp" +#include "bpc_mitm/bpc_ams_power_utils.hpp" +#include "sysupdater/sysupdater_fs_utils.hpp" + +namespace ams { + + namespace { + + /* TODO: we really shouldn't be using malloc just to avoid dealing with real allocator separation. */ + constexpr size_t MallocBufferSize = 12_MB; + alignas(os::MemoryPageSize) constinit u8 g_malloc_buffer[MallocBufferSize]; + + } + + namespace init { + + void InitializeSystemModule() { + /* Initialize our connection to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + + /* Initialize fs. */ + fs::InitializeForSystem(); + fs::SetEnabledAutoAbort(false); + + /* Initialize other services. */ + R_ABORT_UNLESS(pmdmntInitialize()); + R_ABORT_UNLESS(pminfoInitialize()); + ncm::Initialize(); + + /* Verify that we can sanely execute. */ + ams::CheckApiVersion(); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { + /* Initialize the global malloc allocator. */ + init::InitializeAllocator(g_malloc_buffer, sizeof(g_malloc_buffer)); + } + + } + + void ExceptionHandler(FatalErrorContext *ctx) { + /* We're bpc-mitm (or ams_mitm, anyway), so manually reboot to fatal error. */ + mitm::bpc::RebootForFatalError(ctx); + } + + void NORETURN Exit(int rc) { + AMS_UNUSED(rc); + AMS_ABORT("Exit called by immortal process"); + } + + void Main() { + /* Register "ams" port, use up its session. */ + { + svc::Handle ams_port; + R_ABORT_UNLESS(svc::ManageNamedPort(std::addressof(ams_port), "ams", 1)); + + svc::Handle ams_session; + R_ABORT_UNLESS(svc::ConnectToNamedPort(std::addressof(ams_session), "ams")); + } + + /* Initialize fssystem library. */ + fssystem::InitializeForAtmosphereMitm(); + + /* Configure ncm to use fssystem library to mount content from the sd card. */ + ncm::SetMountContentMetaFunction(mitm::sysupdater::MountSdCardContentMeta); + + /* Launch all mitm modules in sequence. */ + mitm::LaunchAllModules(); + + /* Wait for all mitm modules to end. */ + mitm::WaitAllModules(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_module.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_module.hpp new file mode 100644 index 00000000..dd0568ca --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_module.hpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::mitm { + + template<typename T> + concept IsModule = requires(T, void *arg) { + { T::ThreadPriority } -> std::convertible_to<s32>; + { T::StackSize } -> std::convertible_to<size_t>; + { T::Stack } -> std::convertible_to<void *>; + { T::ThreadFunction(arg) } -> std::same_as<void>; + }; + + #define DEFINE_MITM_MODULE_CLASS(ss, prio) class MitmModule { \ + public: \ + static constexpr s32 ThreadPriority = prio; \ + static constexpr size_t StackSize = ss; \ + alignas(os::ThreadStackAlignment) static inline u8 Stack[StackSize]; \ + public: \ + static void ThreadFunction(void *); \ + } + + template<class M> requires IsModule<M> + struct ModuleTraits { + static constexpr void *Stack = &M::Stack[0]; + static constexpr size_t StackSize = M::StackSize; + + static constexpr s32 ThreadPriority = M::ThreadPriority; + + static constexpr ::ThreadFunc ThreadFunction = &M::ThreadFunction; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_module_management.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_module_management.cpp new file mode 100644 index 00000000..c8087a07 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_module_management.cpp @@ -0,0 +1,100 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "amsmitm_module_management.hpp" +#include "amsmitm_module.hpp" + +#include "fs_mitm/fsmitm_module.hpp" +#include "set_mitm/setmitm_module.hpp" +#include "bpc_mitm/bpcmitm_module.hpp" +#include "bpc_mitm/bpc_ams_module.hpp" +#include "ns_mitm/nsmitm_module.hpp" +#include "dns_mitm/dnsmitm_module.hpp" +#include "sysupdater/sysupdater_module.hpp" +#include "mitm_pm/mitm_pm_module.hpp" + +namespace ams::mitm { + + namespace { + + enum ModuleId : u32 { + ModuleId_FsMitm, + ModuleId_SetMitm, + ModuleId_BpcMitm, + ModuleId_BpcAms, + ModuleId_NsMitm, + ModuleId_DnsMitm, + ModuleId_Sysupdater, + ModuleId_PmService, + + ModuleId_Count, + }; + + struct ModuleDefinition { + ThreadFunc main; + void *stack_mem; + s32 priority; + u32 stack_size; + }; + + template<class M> + constexpr ModuleDefinition GetModuleDefinition() { + using Traits = ModuleTraits<M>; + + return ModuleDefinition { + .main = Traits::ThreadFunction, + .stack_mem = Traits::Stack, + .priority = Traits::ThreadPriority, + .stack_size = static_cast<u32>(Traits::StackSize), + }; + } + + ams::os::ThreadType g_module_threads[ModuleId_Count]; + + constexpr ModuleDefinition g_module_definitions[ModuleId_Count] = { + GetModuleDefinition<fs::MitmModule>(), + GetModuleDefinition<settings::MitmModule>(), + GetModuleDefinition<bpc::MitmModule>(), + GetModuleDefinition<bpc_ams::MitmModule>(), + GetModuleDefinition<ns::MitmModule>(), + GetModuleDefinition<socket::resolver::MitmModule>(), + GetModuleDefinition<sysupdater::MitmModule>(), + GetModuleDefinition<pm::MitmModule>(), + }; + + } + + void LaunchAllModules() { + /* Create thread for each module. */ + for (u32 i = 0; i < static_cast<u32>(ModuleId_Count); i++) { + const ModuleDefinition &cur_module = g_module_definitions[i]; + R_ABORT_UNLESS(os::CreateThread(g_module_threads + i, cur_module.main, nullptr, cur_module.stack_mem, cur_module.stack_size, cur_module.priority)); + } + + /* Start thread for each module. */ + for (u32 i = 0; i < static_cast<u32>(ModuleId_Count); i++) { + os::StartThread(g_module_threads + i); + } + } + + void WaitAllModules() { + /* Wait on thread for each module. */ + for (u32 i = 0; i < static_cast<u32>(ModuleId_Count); i++) { + os::WaitThread(g_module_threads + i); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_module_management.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_module_management.hpp new file mode 100644 index 00000000..b9bcacaf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_module_management.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::mitm { + + void LaunchAllModules(); + void WaitAllModules(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_prodinfo_utils.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_prodinfo_utils.cpp new file mode 100644 index 00000000..8722877f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_prodinfo_utils.cpp @@ -0,0 +1,637 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "amsmitm_fs_utils.hpp" +#include "amsmitm_prodinfo_utils.hpp" + +namespace ams::mitm { + + namespace { + + constexpr inline u16 Crc16InitialValue = 0x55AA; + + constexpr inline u16 Crc16Table[] = { + 0x0000, 0xCC01, 0xD801, 0x1400, + 0xF001, 0x3C00, 0x2800, 0xE401, + 0xA001, 0x6C00, 0x7800, 0xB401, + 0x5000, 0x9C01, 0x8801, 0x4400, + }; + + u16 GetCrc16(const void *data, size_t size) { + AMS_ASSERT(data != nullptr); + AMS_ASSERT(size > 0); + + const u8 *src = static_cast<const u8 *>(data); + + u16 crc = Crc16InitialValue; + + u16 tmp = 0; + while ((size--) > 0) { + tmp = Crc16Table[crc & 0xF]; + crc = ((crc >> 4) & 0x0FFF) ^ tmp ^ Crc16Table[*src & 0xF]; + tmp = Crc16Table[crc & 0xF]; + crc = ((crc >> 4) & 0x0FFF) ^ tmp ^ Crc16Table[(*(src++) >> 4) & 0xF]; + } + return crc; + } + + bool IsBlank(const void *data, size_t size) { + AMS_ASSERT(data != nullptr); + AMS_ASSERT(size > 0); + + const u8 *src = static_cast<const u8 *>(data); + while ((size--) > 0) { + if (*(src++) != 0) { + return false; + } + } + return true; + } + + constexpr inline u32 CalibrationMagic = util::FourCC<'C','A','L','0'>::Code; + + struct Sha256Hash { + u8 data[crypto::Sha256Generator::HashSize]; + }; + + struct CalibrationInfoHeader { + u32 magic; + u32 version; + u32 body_size; + u16 model; + u16 update_count; + u8 pad[0xE]; + u16 crc; + Sha256Hash body_hash; + }; + static_assert(sizeof(CalibrationInfoHeader) == 0x40); + + constexpr inline size_t CalibrationInfoBodySizeMax = CalibrationBinarySize - sizeof(CalibrationInfoHeader); + + struct CalibrationInfo { + CalibrationInfoHeader header; + u8 body[CalibrationInfoBodySizeMax]; /* TODO: CalibrationInfoBody body; */ + + template<typename Block> + Block &GetBlock() { + static_assert(Block::Offset >= sizeof(CalibrationInfoHeader)); + static_assert(Block::Offset < sizeof(CalibrationInfo)); + static_assert(Block::Offset + Block::Size <= sizeof(CalibrationInfo)); + return *static_cast<Block *>(static_cast<void *>(std::addressof(this->body[Block::Offset - sizeof(this->header)]))); + } + + template<typename Block> + const Block &GetBlock() const { + static_assert(Block::Offset >= sizeof(CalibrationInfoHeader)); + static_assert(Block::Offset < sizeof(CalibrationInfo)); + static_assert(Block::Offset + Block::Size <= sizeof(CalibrationInfo)); + return *static_cast<const Block *>(static_cast<const void *>(std::addressof(this->body[Block::Offset - sizeof(this->header)]))); + } + }; + static_assert(sizeof(CalibrationInfo) == CalibrationBinarySize); + + struct SecureCalibrationInfoBackup { + CalibrationInfo info; + Sha256Hash hash; + u8 pad[SecureCalibrationBinaryBackupSize - sizeof(info) - sizeof(hash)]; + }; + static_assert(sizeof(SecureCalibrationInfoBackup) == SecureCalibrationBinaryBackupSize); + + bool IsValidSha256Hash(const Sha256Hash &hash, const void *data, size_t data_size) { + Sha256Hash calc_hash; + ON_SCOPE_EXIT { ::ams::crypto::ClearMemory(std::addressof(calc_hash), sizeof(calc_hash)); }; + + ::ams::crypto::GenerateSha256(std::addressof(calc_hash), sizeof(calc_hash), data, data_size); + return ::ams::crypto::IsSameBytes(std::addressof(calc_hash), std::addressof(hash), sizeof(Sha256Hash)); + } + + bool IsValid(const CalibrationInfoHeader &header) { + return header.magic == CalibrationMagic && GetCrc16(std::addressof(header), AMS_OFFSETOF(CalibrationInfoHeader, crc)) == header.crc; + } + + bool IsValid(const CalibrationInfoHeader &header, const void *body) { + return IsValid(header) && IsValidSha256Hash(header.body_hash, body, header.body_size); + } + + #define DEFINE_CALIBRATION_CRC_BLOCK(_TypeName, _Offset, _Size, _Decl, _MemberName) \ + struct _TypeName { \ + static constexpr size_t Offset = _Offset; \ + static constexpr size_t Size = _Size; \ + static constexpr bool IsCrcBlock = true; \ + static constexpr bool IsShaBlock = false; \ + _Decl; \ + static_assert(Size >= sizeof(_MemberName) + sizeof(u16)); \ + u8 pad[Size - sizeof(_MemberName) - sizeof(u16)]; \ + u16 crc; \ + }; \ + static_assert(sizeof(_TypeName) == _TypeName::Size) + + #define DEFINE_CALIBRATION_SHA_BLOCK(_TypeName, _Offset, _Size, _Decl, _MemberName) \ + struct _TypeName { \ + static constexpr size_t Offset = _Offset; \ + static constexpr size_t Size = _Size; \ + static constexpr bool IsCrcBlock = false; \ + static constexpr bool IsShaBlock = true; \ + _Decl; \ + static_assert(Size == sizeof(_MemberName) + sizeof(Sha256Hash)); \ + Sha256Hash sha256_hash; \ + }; \ + static_assert(sizeof(_TypeName) == _TypeName::Size) + + DEFINE_CALIBRATION_CRC_BLOCK(SerialNumberBlock, 0x0250, 0x020, ::ams::settings::factory::SerialNumber serial_number, serial_number); + DEFINE_CALIBRATION_CRC_BLOCK(EccB233DeviceCertificateBlock, 0x0480, 0x190, ::ams::settings::factory::EccB233DeviceCertificate device_certificate, device_certificate); + DEFINE_CALIBRATION_CRC_BLOCK(SslKeyBlock, 0x09B0, 0x120, u8 ssl_key[0x110], ssl_key); + DEFINE_CALIBRATION_CRC_BLOCK(SslCertificateSizeBlock, 0x0AD0, 0x010, u64 ssl_certificate_size, ssl_certificate_size); + DEFINE_CALIBRATION_SHA_BLOCK(SslCertificateBlock, 0x0AE0, 0x820, u8 ssl_certificate[0x800], ssl_certificate); + DEFINE_CALIBRATION_CRC_BLOCK(EcqvEcdsaAmiiboRootCertificateBlock, 0x35A0, 0x080, u8 data[0x70], data); + DEFINE_CALIBRATION_CRC_BLOCK(EcqvBlsAmiiboRootCertificateBlock, 0x36A0, 0x0A0, u8 data[0x90], data); + DEFINE_CALIBRATION_CRC_BLOCK(ExtendedSslKeyBlock, 0x3AE0, 0x140, u8 ssl_key[0x134], ssl_key); + DEFINE_CALIBRATION_CRC_BLOCK(Rsa2048DeviceKeyBlock, 0x3D70, 0x250, u8 device_key[0x240], device_key); + DEFINE_CALIBRATION_CRC_BLOCK(Rsa2048DeviceCertificateBlock, 0x3FC0, 0x250, ::ams::settings::factory::Rsa2048DeviceCertificate device_certificate, device_certificate); + + #undef DEFINE_CALIBRATION_CRC_BLOCK + #undef DEFINE_CALIBRATION_SHA_BLOCK + + constexpr inline const char BlankSerialNumberString[] = "XAW00000000000"; + + template<typename Block> + void Blank(Block &block) { + if constexpr (std::is_same<Block, SerialNumberBlock>::value) { + static_assert(sizeof(BlankSerialNumberString) <= sizeof(SerialNumberBlock::serial_number)); + std::memset(std::addressof(block), 0, Block::Size - sizeof(block.crc)); + std::memcpy(block.serial_number.str, BlankSerialNumberString, sizeof(BlankSerialNumberString)); + block.crc = GetCrc16(std::addressof(block), Block::Size - sizeof(block.crc)); + } else if constexpr (std::is_same<Block, SslCertificateBlock>::value) { + std::memset(std::addressof(block), 0, sizeof(block.ssl_certificate)); + } else if constexpr (Block::IsCrcBlock) { + std::memset(std::addressof(block), 0, Block::Size - sizeof(block.crc)); + block.crc = GetCrc16(std::addressof(block), Block::Size - sizeof(block.crc)); + } else { + static_assert(Block::IsShaBlock); + std::memset(std::addressof(block), 0, Block::Size); + ::ams::crypto::GenerateSha256(std::addressof(block.sha256_hash), sizeof(block.sha256_hash), std::addressof(block), Block::Size - sizeof(block.sha256_hash)); + } + } + + template<typename Block> + bool IsBlank(const Block &block) { + if constexpr (std::is_same<Block, SerialNumberBlock>::value) { + static_assert(sizeof(BlankSerialNumberString) <= sizeof(SerialNumberBlock::serial_number)); + return std::memcmp(block.serial_number.str, BlankSerialNumberString, sizeof(BlankSerialNumberString) - 1) == 0 || IsBlank(std::addressof(block), Block::Size - sizeof(block.crc)); + } else if constexpr (Block::IsCrcBlock) { + return IsBlank(std::addressof(block), Block::Size - sizeof(block.crc)); + } else { + return IsBlank(std::addressof(block), Block::Size - sizeof(block.sha256_hash)); + } + } + + template<typename Block> + bool IsValid(const Block &block, size_t size = 0) { + if constexpr (Block::IsCrcBlock) { + return GetCrc16(std::addressof(block), Block::Size - sizeof(block.crc)) == block.crc; + } else { + static_assert(Block::IsShaBlock); + return IsValidSha256Hash(block.sha256_hash, std::addressof(block), size != 0 ? size : Block::Size - sizeof(block.sha256_hash)); + } + } + + + void Blank(CalibrationInfo &info) { + /* Set header. */ + info.header.magic = CalibrationMagic; + info.header.body_size = sizeof(info.body); + info.header.crc = GetCrc16(std::addressof(info.header), AMS_OFFSETOF(CalibrationInfoHeader, crc)); + + /* Set blocks. */ + Blank(info.GetBlock<SerialNumberBlock>()); + Blank(info.GetBlock<SslCertificateSizeBlock>()); + Blank(info.GetBlock<SslCertificateBlock>()); + Blank(info.GetBlock<EcqvEcdsaAmiiboRootCertificateBlock>()); + Blank(info.GetBlock<EcqvBlsAmiiboRootCertificateBlock>()); + Blank(info.GetBlock<ExtendedSslKeyBlock>()); + + /* Set header hash. */ + crypto::GenerateSha256(std::addressof(info.header.body_hash), sizeof(info.header.body_hash), std::addressof(info.body), sizeof(info.body)); + } + + bool IsValidHeader(const CalibrationInfo &cal) { + return IsValid(cal.header) && cal.header.body_size <= CalibrationInfoBodySizeMax && IsValid(cal.header, cal.body); + } + + bool IsValidSerialNumber(const char *sn) { + for (size_t i = 0; i < std::strlen(sn); i++) { + if (!std::isalnum(static_cast<unsigned char>(sn[i]))) { + return false; + } + } + return true; + } + + void GetSerialNumber(char *dst, const CalibrationInfo &info) { + std::memcpy(dst, std::addressof(info.GetBlock<SerialNumberBlock>()), sizeof(info.GetBlock<SerialNumberBlock>().serial_number)); + dst[sizeof(info.GetBlock<SerialNumberBlock>().serial_number) + 1] = '\x00'; + } + + bool IsValidSerialNumber(const CalibrationInfo &cal) { + char sn[0x20] = {}; + ON_SCOPE_EXIT { std::memset(sn, 0, sizeof(sn)); }; + + GetSerialNumber(sn, cal); + return IsValidSerialNumber(sn); + } + + bool IsValid(const CalibrationInfo &cal) { + return IsValidHeader(cal) && + IsValid(cal.GetBlock<SerialNumberBlock>()) && + IsValid(cal.GetBlock<EccB233DeviceCertificateBlock>()) && + IsValid(cal.GetBlock<SslKeyBlock>()) && + IsValid(cal.GetBlock<SslCertificateSizeBlock>()) && + cal.GetBlock<SslCertificateSizeBlock>().ssl_certificate_size <= sizeof(cal.GetBlock<SslCertificateBlock>().ssl_certificate) && + IsValid(cal.GetBlock<SslCertificateBlock>(), cal.GetBlock<SslCertificateSizeBlock>().ssl_certificate_size) && + IsValid(cal.GetBlock<EcqvEcdsaAmiiboRootCertificateBlock>()) && + IsValid(cal.GetBlock<EcqvBlsAmiiboRootCertificateBlock>()) && + IsValid(cal.GetBlock<ExtendedSslKeyBlock>()) && + IsValidSerialNumber(cal); + } + + bool ContainsCorrectDeviceId(const EccB233DeviceCertificateBlock &block, u64 device_id) { + static constexpr size_t DeviceIdOffset = 0xC6; + char found_device_id_str[sizeof("0011223344556677")] = {}; + ON_SCOPE_EXIT { std::memset(found_device_id_str, 0, sizeof(found_device_id_str)); }; + std::memcpy(found_device_id_str, std::addressof(block.device_certificate.data[DeviceIdOffset]), sizeof(found_device_id_str) - 1); + + static constexpr u64 DeviceIdLowMask = 0x00FFFFFFFFFFFFFFul; + + return (std::strtoul(found_device_id_str, nullptr, 16) & DeviceIdLowMask) == (device_id & DeviceIdLowMask); + } + + bool ContainsCorrectDeviceId(const CalibrationInfo &cal) { + return ContainsCorrectDeviceId(cal.GetBlock<EccB233DeviceCertificateBlock>(), exosphere::GetDeviceId()); + } + + bool IsValidForSecureBackup(const CalibrationInfo &cal) { + return IsValid(cal) && ContainsCorrectDeviceId(cal); + } + + bool IsBlank(const CalibrationInfo &cal) { + return IsBlank(cal.GetBlock<SerialNumberBlock>()) || + IsBlank(cal.GetBlock<SslCertificateSizeBlock>()) || + IsBlank(cal.GetBlock<SslCertificateBlock>()) || + IsBlank(cal.GetBlock<EcqvEcdsaAmiiboRootCertificateBlock>()) || + IsBlank(cal.GetBlock<EcqvBlsAmiiboRootCertificateBlock>()) || + IsBlank(cal.GetBlock<ExtendedSslKeyBlock>()); + } + + void ReadStorageCalibrationBinary(CalibrationInfo *out) { + FsStorage calibration_binary_storage; + R_ABORT_UNLESS(fsOpenBisStorage(std::addressof(calibration_binary_storage), FsBisPartitionId_CalibrationBinary)); + ON_SCOPE_EXIT { fsStorageClose(std::addressof(calibration_binary_storage)); }; + + R_ABORT_UNLESS(fsStorageRead(std::addressof(calibration_binary_storage), 0, out, sizeof(*out))); + } + + constexpr inline const u8 SecureCalibrationBinaryBackupIv[crypto::Aes128CtrDecryptor::IvSize] = {}; + + void ReadStorageEncryptedSecureCalibrationBinaryBackupUnsafe(SecureCalibrationInfoBackup *out) { + FsStorage calibration_binary_storage; + R_ABORT_UNLESS(fsOpenBisStorage(std::addressof(calibration_binary_storage), FsBisPartitionId_CalibrationBinary)); + ON_SCOPE_EXIT { fsStorageClose(std::addressof(calibration_binary_storage)); }; + + R_ABORT_UNLESS(fsStorageRead(std::addressof(calibration_binary_storage), SecureCalibrationInfoBackupOffset, out, sizeof(*out))); + } + + void WriteStorageEncryptedSecureCalibrationBinaryBackupUnsafe(const SecureCalibrationInfoBackup *src) { + FsStorage calibration_binary_storage; + R_ABORT_UNLESS(fsOpenBisStorage(std::addressof(calibration_binary_storage), FsBisPartitionId_CalibrationBinary)); + ON_SCOPE_EXIT { fsStorageClose(std::addressof(calibration_binary_storage)); }; + + R_ABORT_UNLESS(fsStorageWrite(std::addressof(calibration_binary_storage), SecureCalibrationInfoBackupOffset, src, sizeof(*src))); + } + + void GenerateSecureCalibrationBinaryBackupKey(void *dst, size_t dst_size) { + static constexpr const u8 SecureCalibrationBinaryBackupKeySource[crypto::Aes128CtrDecryptor::KeySize] = { '|', '-', 'A', 'M', 'S', '-', 'C', 'A', 'L', '0', '-', 'K', 'E', 'Y', '-', '|' }; + spl::AccessKey access_key; + ON_SCOPE_EXIT { crypto::ClearMemory(std::addressof(access_key), sizeof(access_key)); }; + + /* Generate a personalized kek. */ + R_ABORT_UNLESS(spl::GenerateAesKek(std::addressof(access_key), SecureCalibrationBinaryBackupKeySource, sizeof(SecureCalibrationBinaryBackupKeySource), 0, 1)); + + /* Generate a personalized key. */ + R_ABORT_UNLESS(spl::GenerateAesKey(dst, dst_size, access_key, SecureCalibrationBinaryBackupKeySource, sizeof(SecureCalibrationBinaryBackupKeySource))); + } + + bool ReadStorageSecureCalibrationBinaryBackup(SecureCalibrationInfoBackup *out) { + /* Read the data. */ + ReadStorageEncryptedSecureCalibrationBinaryBackupUnsafe(out); + + /* Don't leak any data unless we validate. */ + auto clear_guard = SCOPE_GUARD { std::memset(out, 0, sizeof(*out)); }; + + { + /* Create a buffer to hold our key. */ + u8 key[crypto::Aes128CtrDecryptor::KeySize]; + ON_SCOPE_EXIT { crypto::ClearMemory(key, sizeof(key)); }; + + /* Generate the key. */ + GenerateSecureCalibrationBinaryBackupKey(key, sizeof(key)); + + /* Decrypt the data in place. */ + crypto::DecryptAes128Ctr(out, sizeof(*out), key, sizeof(key), SecureCalibrationBinaryBackupIv, sizeof(SecureCalibrationBinaryBackupIv), out, sizeof(*out)); + } + + /* Generate a hash for the data. */ + if (!IsValidSha256Hash(out->hash, std::addressof(out->info), sizeof(out->info))) { + return false; + } + + /* Validate the backup. */ + if (!IsValidForSecureBackup(out->info)) { + return false; + } + + /* Our backup is valid. */ + clear_guard.Cancel(); + return true; + } + + void WriteStorageSecureCalibrationBinaryBackup(SecureCalibrationInfoBackup *src) { + /* Clear the input once we've written it. */ + ON_SCOPE_EXIT { std::memset(src, 0, sizeof(*src)); }; + + /* Ensure that the input is valid. */ + AMS_ABORT_UNLESS(IsValidForSecureBackup(src->info)); + + /* Set the Sha256 hash. */ + crypto::GenerateSha256(std::addressof(src->hash), sizeof(src->hash), std::addressof(src->info), sizeof(src->info)); + + /* Validate the hash. */ + AMS_ABORT_UNLESS(IsValidSha256Hash(src->hash, std::addressof(src->info), sizeof(src->info))); + + /* Encrypt the data. */ + { + /* Create a buffer to hold our key. */ + u8 key[crypto::Aes128CtrDecryptor::KeySize]; + ON_SCOPE_EXIT { crypto::ClearMemory(key, sizeof(key)); }; + + /* Generate the key. */ + GenerateSecureCalibrationBinaryBackupKey(key, sizeof(key)); + + /* Encrypt the data in place. */ + crypto::EncryptAes128Ctr(src, sizeof(*src), key, sizeof(key), SecureCalibrationBinaryBackupIv, sizeof(SecureCalibrationBinaryBackupIv), src, sizeof(*src)); + } + + /* Write the encrypted data. */ + WriteStorageEncryptedSecureCalibrationBinaryBackupUnsafe(src); + } + + void GetBackupFileName(char *dst, size_t dst_size, const CalibrationInfo &info) { + char sn[0x20] = {}; + ON_SCOPE_EXIT { std::memset(sn, 0, sizeof(sn)); }; + + + if (IsValidForSecureBackup(info)) { + GetSerialNumber(sn, info); + util::SNPrintf(dst, dst_size, "automatic_backups/%s_PRODINFO.bin", sn); + } else { + Sha256Hash hash; + crypto::GenerateSha256(std::addressof(hash), sizeof(hash), std::addressof(info), sizeof(info)); + ON_SCOPE_EXIT { crypto::ClearMemory(std::addressof(hash), sizeof(hash)); }; + + if (IsValid(info)) { + if (IsBlank(info)) { + util::SNPrintf(dst, dst_size, "automatic_backups/BLANK_PRODINFO_%02X%02X%02X%02X.bin", hash.data[0], hash.data[1], hash.data[2], hash.data[3]); + } else { + GetSerialNumber(sn, info); + util::SNPrintf(dst, dst_size, "automatic_backups/%s_PRODINFO_%02X%02X%02X%02X.bin", sn, hash.data[0], hash.data[1], hash.data[2], hash.data[3]); + } + } else { + util::SNPrintf(dst, dst_size, "automatic_backups/INVALID_PRODINFO_%02X%02X%02X%02X.bin", hash.data[0], hash.data[1], hash.data[2], hash.data[3]); + } + } + } + + void SafeRead(ams::fs::fsa::IFile *file, s64 offset, void *dst, size_t size) { + size_t read_size = 0; + R_ABORT_UNLESS(file->Read(std::addressof(read_size), offset, dst, size)); + AMS_ABORT_UNLESS(read_size == size); + } + + alignas(os::MemoryPageSize) CalibrationInfo g_temp_calibration_info = {}; + + void SaveProdInfoBackup(util::optional<ams::fs::FileStorage> *dst, const CalibrationInfo &info) { + char backup_fn[0x100]; + GetBackupFileName(backup_fn, sizeof(backup_fn), info); + + /* Create the file, in case it does not exist. */ + mitm::fs::CreateAtmosphereSdFile(backup_fn, sizeof(CalibrationInfo), ams::fs::CreateOption_None); + + /* Open the file. */ + FsFile libnx_file; + R_ABORT_UNLESS(mitm::fs::OpenAtmosphereSdFile(std::addressof(libnx_file), backup_fn, ams::fs::OpenMode_ReadWrite)); + + /* Create our accessor. */ + std::unique_ptr<ams::fs::fsa::IFile> file = std::make_unique<ams::fs::RemoteFile>(libnx_file); + AMS_ABORT_UNLESS(file != nullptr); + + /* Check if we're valid already. */ + bool valid = false; + s64 size; + R_ABORT_UNLESS(file->GetSize(std::addressof(size))); + if (size == sizeof(CalibrationInfo)) { + SafeRead(file.get(), 0, std::addressof(g_temp_calibration_info), sizeof(g_temp_calibration_info)); + ON_SCOPE_EXIT { std::memset(std::addressof(g_temp_calibration_info), 0, sizeof(g_temp_calibration_info)); }; + + if (std::memcmp(std::addressof(info), std::addressof(g_temp_calibration_info), sizeof(CalibrationInfo)) == 0) { + valid = true; + } + } + + /* If we're not valid, we need to save. */ + if (!valid) { + R_ABORT_UNLESS(file->Write(0, std::addressof(info), sizeof(info), ams::fs::WriteOption::Flush)); + } + + /* Save our storage to output. */ + if (dst != nullptr) { + dst->emplace(std::move(file)); + } + } + + void GetRandomEntropy(Sha256Hash *dst) { + AMS_ASSERT(dst != nullptr); + + u64 data_buffer[3] = {}; + ON_SCOPE_EXIT { crypto::ClearMemory(data_buffer, sizeof(data_buffer)); }; + + data_buffer[0] = os::GetSystemTick().GetInt64Value(); + R_ABORT_UNLESS(svc::GetInfo(data_buffer + 1, svc::InfoType_AliasRegionAddress, svc::PseudoHandle::CurrentProcess, 0)); + if (hos::GetVersion() >= hos::Version_2_0_0) { + R_ABORT_UNLESS(svc::GetInfo(data_buffer + 2, svc::InfoType_RandomEntropy, svc::InvalidHandle, (data_buffer[0] ^ (data_buffer[1] >> 24)) & 3)); + } else { + data_buffer[2] = os::GetSystemTick().GetInt64Value(); + } + + return crypto::GenerateSha256(dst, sizeof(*dst), data_buffer, sizeof(data_buffer)); + } + + void FillWithGarbage(void *dst, size_t dst_size) { + /* Get random entropy. */ + Sha256Hash entropy; + ON_SCOPE_EXIT { crypto::ClearMemory(std::addressof(entropy), sizeof(entropy)); }; + GetRandomEntropy(std::addressof(entropy)); + + /* Clear dst. */ + std::memset(dst, 0xCC, dst_size); + + /* Encrypt dst. */ + static_assert(sizeof(entropy) == crypto::Aes128CtrEncryptor::KeySize + crypto::Aes128CtrEncryptor::IvSize); + crypto::EncryptAes128Ctr(dst, dst_size, entropy.data, crypto::Aes128CtrEncryptor::KeySize, entropy.data + crypto::Aes128CtrEncryptor::KeySize, crypto::Aes128CtrEncryptor::IvSize, dst, dst_size); + } + + alignas(os::MemoryPageSize) constinit CalibrationInfo g_calibration_info = {}; + alignas(os::MemoryPageSize) constinit CalibrationInfo g_blank_calibration_info = {}; + alignas(os::MemoryPageSize) constinit SecureCalibrationInfoBackup g_secure_calibration_info_backup = {}; + + constinit util::optional<ams::fs::FileStorage> g_prodinfo_backup_file; + constinit util::optional<ams::fs::MemoryStorage> g_blank_prodinfo_storage; + constinit util::optional<ams::fs::MemoryStorage> g_fake_secure_backup_storage; + + constinit bool g_allow_writes = false; + constinit bool g_has_secure_backup = false; + + constinit os::SdkMutex g_prodinfo_management_lock; + + } + + void InitializeProdInfoManagement() { + std::scoped_lock lk(g_prodinfo_management_lock); + + /* First, get our options. */ + const bool should_blank = exosphere::ShouldBlankProdInfo(); + bool allow_writes = exosphere::ShouldAllowWritesToProdInfo(); + + /* Next, read our prodinfo. */ + ReadStorageCalibrationBinary(std::addressof(g_calibration_info)); + + /* Next, check if we have a secure backup. */ + bool has_secure_backup = ReadStorageSecureCalibrationBinaryBackup(std::addressof(g_secure_calibration_info_backup)); + + /* Only allow writes if we have a secure backup. */ + if (allow_writes && !has_secure_backup) { + /* If we can make a secure backup, great. */ + if (IsValidForSecureBackup(g_calibration_info)) { + g_secure_calibration_info_backup.info = g_calibration_info; + WriteStorageSecureCalibrationBinaryBackup(std::addressof(g_secure_calibration_info_backup)); + g_secure_calibration_info_backup.info = g_calibration_info; + has_secure_backup = true; + } else { + /* Don't allow writes if we can't make a secure backup. */ + allow_writes = false; + } + } + + /* Ensure our preconditions are met. */ + AMS_ABORT_UNLESS(!allow_writes || has_secure_backup); + + /* Set globals. */ + g_allow_writes = allow_writes; + g_has_secure_backup = has_secure_backup; + + /* If we should blank, do so. */ + if (should_blank) { + g_blank_calibration_info = g_calibration_info; + Blank(g_blank_calibration_info); + g_blank_prodinfo_storage.emplace(std::addressof(g_blank_calibration_info), sizeof(g_blank_calibration_info)); + } + + /* Ensure that we have a blank file only if we need one. */ + AMS_ABORT_UNLESS(should_blank == static_cast<bool>(g_blank_prodinfo_storage)); + } + + void SaveProdInfoBackupsAndWipeMemory(char *out_name, size_t out_name_size) { + std::scoped_lock lk(g_prodinfo_management_lock); + + ON_SCOPE_EXIT { + FillWithGarbage(std::addressof(g_calibration_info), sizeof(g_calibration_info)); + FillWithGarbage(std::addressof(g_secure_calibration_info_backup), sizeof(g_secure_calibration_info_backup)); + }; + + /* Save our backup. We always prefer to save a secure copy of data over a non-secure one. */ + if (g_has_secure_backup) { + GetSerialNumber(out_name, g_secure_calibration_info_backup.info); + SaveProdInfoBackup(std::addressof(g_prodinfo_backup_file), g_secure_calibration_info_backup.info); + } else { + if (IsValid(g_calibration_info) && !IsBlank(g_calibration_info)) { + GetSerialNumber(out_name, g_calibration_info); + } else { + Sha256Hash hash; + ON_SCOPE_EXIT { crypto::ClearMemory(std::addressof(hash), sizeof(hash)); }; + crypto::GenerateSha256(std::addressof(hash), sizeof(hash), std::addressof(g_calibration_info), sizeof(g_calibration_info)); + + util::SNPrintf(out_name, out_name_size, "%02X%02X%02X%02X", hash.data[0], hash.data[1], hash.data[2], hash.data[3]); + } + SaveProdInfoBackup(std::addressof(g_prodinfo_backup_file), g_calibration_info); + } + + /* Ensure we made our backup. */ + AMS_ABORT_UNLESS(g_prodinfo_backup_file); + + /* Setup our memory storage. */ + g_fake_secure_backup_storage.emplace(std::addressof(g_secure_calibration_info_backup), sizeof(g_secure_calibration_info_backup)); + + /* Ensure that we have a fake storage. */ + AMS_ABORT_UNLESS(static_cast<bool>(g_fake_secure_backup_storage)); + } + + bool ShouldReadBlankCalibrationBinary() { + std::scoped_lock lk(g_prodinfo_management_lock); + return static_cast<bool>(g_blank_prodinfo_storage); + } + + bool IsWriteToCalibrationBinaryAllowed() { + std::scoped_lock lk(g_prodinfo_management_lock); + return g_allow_writes; + } + + void ReadFromBlankCalibrationBinary(s64 offset, void *dst, size_t size) { + AMS_ABORT_UNLESS(ShouldReadBlankCalibrationBinary()); + + std::scoped_lock lk(g_prodinfo_management_lock); + R_ABORT_UNLESS(g_blank_prodinfo_storage->Read(offset, dst, size)); + } + + void WriteToBlankCalibrationBinary(s64 offset, const void *src, size_t size) { + AMS_ABORT_UNLESS(ShouldReadBlankCalibrationBinary()); + + std::scoped_lock lk(g_prodinfo_management_lock); + R_ABORT_UNLESS(g_blank_prodinfo_storage->Write(offset, src, size)); + } + + void ReadFromFakeSecureBackupStorage(s64 offset, void *dst, size_t size) { + AMS_ABORT_UNLESS(IsWriteToCalibrationBinaryAllowed()); + + std::scoped_lock lk(g_prodinfo_management_lock); + R_ABORT_UNLESS(g_fake_secure_backup_storage->Read(offset, dst, size)); + } + + void WriteToFakeSecureBackupStorage(s64 offset, const void *src, size_t size) { + AMS_ABORT_UNLESS(IsWriteToCalibrationBinaryAllowed()); + + std::scoped_lock lk(g_prodinfo_management_lock); + R_ABORT_UNLESS(g_fake_secure_backup_storage->Write(offset, src, size)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_prodinfo_utils.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_prodinfo_utils.hpp new file mode 100644 index 00000000..e7db8c8b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/amsmitm_prodinfo_utils.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::mitm { + + constexpr inline size_t CalibrationBinarySize = 0x8000; + + constexpr inline s64 SecureCalibrationInfoBackupOffset = 3_MB; + constexpr inline size_t SecureCalibrationBinaryBackupSize = 0xC000; + + void InitializeProdInfoManagement(); + + void SaveProdInfoBackupsAndWipeMemory(char *out_name, size_t out_name_size); + + bool ShouldReadBlankCalibrationBinary(); + bool IsWriteToCalibrationBinaryAllowed(); + + void ReadFromBlankCalibrationBinary(s64 offset, void *dst, size_t size); + void WriteToBlankCalibrationBinary(s64 offset, const void *src, size_t size); + + void ReadFromFakeSecureBackupStorage(s64 offset, void *dst, size_t size); + void WriteToFakeSecureBackupStorage(s64 offset, const void *src, size_t size); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_module.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_module.cpp new file mode 100644 index 00000000..11c96894 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_module.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../amsmitm_initialization.hpp" +#include "bpc_ams_module.hpp" +#include "bpc_ams_service.hpp" + +namespace ams::mitm::bpc_ams { + + namespace { + + constexpr sm::ServiceName AtmosphereServiceName = sm::ServiceName::Encode("bpc:ams"); + constexpr size_t AtmosphereMaxSessions = 4; + + constexpr size_t MaxServers = 1; + constexpr size_t MaxSessions = AtmosphereMaxSessions; + using ServerOptions = sf::hipc::DefaultServerManagerOptions; + sf::hipc::ServerManager<MaxServers, ServerOptions, MaxSessions> g_server_manager; + + constinit sf::UnmanagedServiceObject<bpc::impl::IAtmosphereInterface, bpc::AtmosphereService> g_ams_service_object; + + } + + void MitmModule::ThreadFunction(void *) { + /* Create bpc:ams. */ + { + os::NativeHandle bpcams_h; + R_ABORT_UNLESS(svc::ManageNamedPort(&bpcams_h, AtmosphereServiceName.name, AtmosphereMaxSessions)); + g_server_manager.RegisterObjectForServer(g_ams_service_object.GetShared(), bpcams_h); + } + + /* Loop forever, servicing our services. */ + g_server_manager.LoopProcess(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_module.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_module.hpp new file mode 100644 index 00000000..ccc93640 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_module.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../amsmitm_module.hpp" + +namespace ams::mitm::bpc_ams { + + DEFINE_MITM_MODULE_CLASS(0x8000, AMS_GET_SYSTEM_THREAD_PRIORITY(bpc, IpcServer)); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_power_utils.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_power_utils.cpp new file mode 100644 index 00000000..12f8a0e7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_power_utils.cpp @@ -0,0 +1,186 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "bpc_ams_power_utils.hpp" +#include "../amsmitm_fs_utils.hpp" + +namespace ams::mitm::bpc { + + namespace { + + /* Convenience definitions. */ + constexpr uintptr_t IramBase = 0x40000000ull; + constexpr uintptr_t IramPayloadBase = 0x40010000ull; + constexpr size_t IramSize = 0x40000; + constexpr size_t IramPayloadMaxSize = 0x24000; + constexpr size_t IramFatalErrorContextOffset = 0x2E000; + + /* Helper enum. */ + enum class RebootType : u32 { + Standard, + ToRcm, + ToPayload, + ByPmic, + }; + + /* Globals. */ + alignas(os::MemoryPageSize) u8 g_work_page[os::MemoryPageSize]; + alignas(os::MemoryPageSize) u8 g_reboot_payload[IramPayloadMaxSize]; + RebootType g_reboot_type = RebootType::ToRcm; + + /* Helpers. */ + void ClearIram() { + /* Make page CCs. */ + std::memset(g_work_page, 0xCC, sizeof(g_work_page)); + + /* Overwrite all of IRAM with CCs. */ + for (size_t ofs = 0; ofs < IramSize; ofs += sizeof(g_work_page)) { + exosphere::CopyToIram(IramBase + ofs, g_work_page, sizeof(g_work_page)); + } + } + + void DoRebootToPayload() { + /* Ensure clean IRAM state. */ + ClearIram(); + + /* Copy in payload. */ + for (size_t ofs = 0; ofs < sizeof(g_reboot_payload); ofs += sizeof(g_work_page)) { + std::memcpy(g_work_page, g_reboot_payload + ofs, std::min(sizeof(g_reboot_payload) - ofs, sizeof(g_work_page))); + exosphere::CopyToIram(IramPayloadBase + ofs, g_work_page, sizeof(g_work_page)); + } + + exosphere::ForceRebootToIramPayload(); + } + + void DoRebootToFatalError(const ams::FatalErrorContext *ctx) { + /* Ensure clean IRAM state. */ + ClearIram(); + + /* Copy in payload. */ + for (size_t ofs = 0; ofs < sizeof(g_reboot_payload); ofs += sizeof(g_work_page)) { + std::memcpy(g_work_page, g_reboot_payload + ofs, std::min(sizeof(g_reboot_payload) - ofs, sizeof(g_work_page))); + exosphere::CopyToIram(IramPayloadBase + ofs, g_work_page, sizeof(g_work_page)); + } + + /* Copy in fatal error context, if relevant. */ + if (ctx != nullptr) { + std::memset(g_work_page, 0xCC, sizeof(g_work_page)); + std::memcpy(g_work_page, ctx, sizeof(*ctx)); + exosphere::CopyToIram(IramPayloadBase + IramFatalErrorContextOffset, g_work_page, sizeof(g_work_page)); + } + + exosphere::ForceRebootToFatalError(); + } + + } + + /* Power utilities. */ + bool IsRebootManaged() { + return g_reboot_type != RebootType::Standard; + } + + void RebootSystem() { + switch (g_reboot_type) { + case RebootType::ByPmic: + exosphere::ForceRebootByPmic(); + break; + case RebootType::ToRcm: + exosphere::ForceRebootToRcm(); + break; + case RebootType::ToPayload: + default: /* This should never be called with ::Standard */ + DoRebootToPayload(); + break; + } + } + + void ShutdownSystem() { + exosphere::ForceShutdown(); + } + + /* Atmosphere power utilities. */ + void RebootForFatalError(const ams::FatalErrorContext *ctx) { + DoRebootToFatalError(ctx); + } + + void SetRebootPayload(const void *payload, size_t payload_size) { + /* Mariko does not support reboot-to-payload. */ + if (spl::GetSocType() == spl::SocType_Mariko) { + return; + } + + /* Clear payload buffer */ + std::memset(g_reboot_payload, 0xCC, sizeof(g_reboot_payload)); + + /* Ensure valid. */ + AMS_ABORT_UNLESS(payload != nullptr && payload_size <= sizeof(g_reboot_payload)); + + /* Copy in payload. */ + std::memcpy(g_reboot_payload, payload, payload_size); + + /* Note to the secure monitor that we have a payload. */ + spl::smc::AsyncOperationKey dummy; + spl::smc::SetConfig(std::addressof(dummy), spl::ConfigItem::ExospherePayloadAddress, nullptr, 0, g_reboot_payload); + + /* NOTE: Preferred reboot type may be overrwritten when parsed from settings during boot. */ + g_reboot_type = RebootType::ToPayload; + } + + Result LoadRebootPayload() { + /* Mariko does not support reboot-to-payload. */ + R_SUCCEED_IF(spl::GetSocType() == spl::SocType_Mariko) + + /* Clear payload buffer */ + std::memset(g_reboot_payload, 0xCC, sizeof(g_reboot_payload)); + + /* Open payload file. */ + FsFile payload_file; + R_TRY(fs::OpenAtmosphereSdFile(std::addressof(payload_file), "/reboot_payload.bin", ams::fs::OpenMode_Read)); + ON_SCOPE_EXIT { fsFileClose(std::addressof(payload_file)); }; + + /* Read payload file. Discard result. */ + { + size_t actual_size; + fsFileRead(std::addressof(payload_file), 0, g_reboot_payload, sizeof(g_reboot_payload), FsReadOption_None, std::addressof(actual_size)); + } + + /* NOTE: Preferred reboot type will be parsed from settings later on. */ + g_reboot_type = RebootType::ToPayload; + + R_SUCCEED(); + } + + Result DetectPreferredRebootFunctionality() { + char reboot_type[0x40] = {}; + settings::fwdbg::GetSettingsItemValue(reboot_type, sizeof(reboot_type) - 1, "atmosphere", "power_menu_reboot_function"); + + if (strcasecmp(reboot_type, "stock") == 0 || strcasecmp(reboot_type, "normal") == 0 || strcasecmp(reboot_type, "standard") == 0) { + g_reboot_type = RebootType::Standard; + } else if (strcasecmp(reboot_type, "rcm") == 0) { + g_reboot_type = RebootType::ToRcm; + } else if (strcasecmp(reboot_type, "payload") == 0) { + g_reboot_type = RebootType::ToPayload; + } + + /* TODO: Should we actually allow control over this on mariko? */ + if (spl::GetSocType() == spl::SocType_Mariko) { + g_reboot_type = RebootType::ByPmic; + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_power_utils.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_power_utils.hpp new file mode 100644 index 00000000..ae078c34 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_power_utils.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::mitm::bpc { + + /* Power utilities. */ + bool IsRebootManaged(); + void RebootSystem(); + void ShutdownSystem(); + + /* Atmosphere power utilities. */ + void SetRebootPayload(const void *payload, size_t payload_size); + Result LoadRebootPayload(); + Result DetectPreferredRebootFunctionality(); + void RebootForFatalError(const ams::FatalErrorContext *ctx); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_service.cpp new file mode 100644 index 00000000..3d7330d5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_service.cpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../amsmitm_initialization.hpp" +#include "bpc_ams_service.hpp" +#include "bpc_ams_power_utils.hpp" + +namespace ams::mitm::bpc { + + namespace { + + bool g_set_initial_payload = false; + + } + + void AtmosphereService::RebootToFatalError(const ams::FatalErrorContext &ctx) { + bpc::RebootForFatalError(std::addressof(ctx)); + } + + void AtmosphereService::SetRebootPayload(const ams::sf::InBuffer &payload) { + /* Set the reboot payload. */ + bpc::SetRebootPayload(payload.GetPointer(), payload.GetSize()); + + /* If this is being called for the first time (by boot sysmodule), */ + /* Then we should kick off the rest of init. */ + if (!g_set_initial_payload) { + g_set_initial_payload = true; + + /* Start the initialization process. */ + ::ams::mitm::StartInitialize(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_service.hpp new file mode 100644 index 00000000..51c0001f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_service.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#define AMS_BPC_MITM_ATMOSPHERE_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 65000, void, RebootToFatalError, (const ams::FatalErrorContext &ctx), (ctx)) \ + AMS_SF_METHOD_INFO(C, H, 65001, void, SetRebootPayload, (const ams::sf::InBuffer &payload), (payload)) + +AMS_SF_DEFINE_INTERFACE(ams::mitm::bpc::impl, IAtmosphereInterface, AMS_BPC_MITM_ATMOSPHERE_INTERFACE_INTERFACE_INFO, 0x00000000) + +namespace ams::mitm::bpc { + + class AtmosphereService { + public: + void RebootToFatalError(const ams::FatalErrorContext &ctx); + void SetRebootPayload(const ams::sf::InBuffer &payload); + }; + static_assert(impl::IsIAtmosphereInterface<AtmosphereService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_mitm_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_mitm_service.cpp new file mode 100644 index 00000000..2c76f002 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_mitm_service.cpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "bpc_mitm_service.hpp" +#include "bpc_ams_power_utils.hpp" + +namespace ams::mitm::bpc { + + Result BpcMitmService::RebootSystem() { + R_UNLESS(bpc::IsRebootManaged(), sm::mitm::ResultShouldForwardToSession()); + bpc::RebootSystem(); + R_SUCCEED(); + } + + Result BpcMitmService::ShutdownSystem() { + bpc::ShutdownSystem(); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_mitm_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_mitm_service.hpp new file mode 100644 index 00000000..01a7c49e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpc_mitm_service.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#define AMS_BPC_MITM_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, ShutdownSystem, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, RebootSystem, (), ()) + +AMS_SF_DEFINE_MITM_INTERFACE(ams::mitm::bpc::impl, IBpcMitmInterface, AMS_BPC_MITM_INTERFACE_INFO, 0xF6C277FD) + +namespace ams::mitm::bpc { + + class BpcMitmService : public sf::MitmServiceImplBase { + public: + using MitmServiceImplBase::MitmServiceImplBase; + public: + static bool ShouldMitm(const sm::MitmProcessInfo &client_info) { + /* We will mitm: + * - am (omm on 14.0.0+), to intercept the Reboot/Power buttons in the overlay menu. + * - fatal, to simplify payload reboot logic significantly + * - hbl, to allow homebrew to take advantage of the feature. + */ + return client_info.program_id == ncm::SystemProgramId::Am || + client_info.program_id == ncm::SystemProgramId::Omm || + client_info.program_id == ncm::SystemProgramId::Fatal || + client_info.override_status.IsHbl(); + } + public: + /* Overridden commands. */ + Result ShutdownSystem(); + Result RebootSystem(); + }; + static_assert(impl::IsIBpcMitmInterface<BpcMitmService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpcmitm_module.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpcmitm_module.cpp new file mode 100644 index 00000000..da01abc4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpcmitm_module.cpp @@ -0,0 +1,79 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../amsmitm_initialization.hpp" +#include "bpcmitm_module.hpp" +#include "bpc_mitm_service.hpp" + +namespace ams::mitm::bpc { + + namespace { + + enum PortIndex { + PortIndex_Mitm, + PortIndex_Count, + }; + + constexpr sm::ServiceName MitmServiceName = sm::ServiceName::Encode("bpc"); + constexpr sm::ServiceName DeprecatedMitmServiceName = sm::ServiceName::Encode("bpc:c"); + constexpr size_t MitmServiceMaxSessions = 13; + + constexpr size_t MaxSessions = MitmServiceMaxSessions; + + struct ServerOptions { + static constexpr size_t PointerBufferSize = sf::hipc::DefaultServerManagerOptions::PointerBufferSize; + static constexpr size_t MaxDomains = sf::hipc::DefaultServerManagerOptions::MaxDomains; + static constexpr size_t MaxDomainObjects = sf::hipc::DefaultServerManagerOptions::MaxDomainObjects; + static constexpr bool CanDeferInvokeRequest = sf::hipc::DefaultServerManagerOptions::CanDeferInvokeRequest; + static constexpr bool CanManageMitmServers = true; + }; + + class ServerManager final : public sf::hipc::ServerManager<PortIndex_Count, ServerOptions, MaxSessions> { + private: + virtual Result OnNeedsToAccept(int port_index, Server *server) override; + }; + + ServerManager g_server_manager; + + Result ServerManager::OnNeedsToAccept(int port_index, Server *server) { + /* Acknowledge the mitm session. */ + std::shared_ptr<::Service> fsrv; + sm::MitmProcessInfo client_info; + server->AcknowledgeMitmSession(std::addressof(fsrv), std::addressof(client_info)); + + switch (port_index) { + case PortIndex_Mitm: + R_RETURN(this->AcceptMitmImpl(server, sf::CreateSharedObjectEmplaced<impl::IBpcMitmInterface, BpcMitmService>(decltype(fsrv)(fsrv), client_info), fsrv)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + } + + void MitmModule::ThreadFunction(void *) { + /* Wait until initialization is complete. */ + mitm::WaitInitialized(); + + /* Create bpc mitm. */ + const sm::ServiceName service_name = (hos::GetVersion() >= hos::Version_2_0_0) ? MitmServiceName : DeprecatedMitmServiceName; + + R_ABORT_UNLESS((g_server_manager.RegisterMitmServer<BpcMitmService>(PortIndex_Mitm, service_name))); + + /* Loop forever, servicing our services. */ + g_server_manager.LoopProcess(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpcmitm_module.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpcmitm_module.hpp new file mode 100644 index 00000000..61c59d9a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/bpc_mitm/bpcmitm_module.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../amsmitm_module.hpp" + +namespace ams::mitm::bpc { + + DEFINE_MITM_MODULE_CLASS(0x8000, AMS_GET_SYSTEM_THREAD_PRIORITY(bpc, IpcServer)); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_debug.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_debug.cpp new file mode 100644 index 00000000..477ac2f9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_debug.cpp @@ -0,0 +1,77 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../amsmitm_fs_utils.hpp" +#include "dnsmitm_debug.hpp" + +namespace ams::mitm::socket::resolver { + + namespace { + + constinit os::SdkMutex g_log_mutex; + constinit bool g_log_enabled; + + constinit ::FsFile g_log_file; + constinit s64 g_log_ofs; + + + constinit char g_log_buf[0x400]; + + } + + void InitializeDebug(bool enable_log) { + { + std::scoped_lock lk(g_log_mutex); + + g_log_enabled = enable_log; + + if (g_log_enabled) { + /* Create the logs directory. */ + mitm::fs::CreateAtmosphereSdDirectory("/logs"); + + /* Create the log file. */ + mitm::fs::CreateAtmosphereSdFile("/logs/dns_mitm_debug.log", 0, ams::fs::CreateOption_None); + + /* Open the log file. */ + R_ABORT_UNLESS(mitm::fs::OpenAtmosphereSdFile(std::addressof(g_log_file), "/logs/dns_mitm_debug.log", ams::fs::OpenMode_ReadWrite | ams::fs::OpenMode_AllowAppend)); + + /* Get the current log offset. */ + R_ABORT_UNLESS(::fsFileGetSize(std::addressof(g_log_file), std::addressof(g_log_ofs))); + } + } + + /* Start a new log. */ + LogDebug("\n---\n"); + } + + void LogDebug(const char *fmt, ...) { + std::scoped_lock lk(g_log_mutex); + + if (g_log_enabled) { + int len = 0; + { + std::va_list vl; + va_start(vl, fmt); + len = util::VSNPrintf(g_log_buf, sizeof(g_log_buf), fmt, vl); + va_end(vl); + } + + R_ABORT_UNLESS(::fsFileWrite(std::addressof(g_log_file), g_log_ofs, g_log_buf, len, FsWriteOption_Flush)); + g_log_ofs += len; + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_debug.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_debug.hpp new file mode 100644 index 00000000..623b6ab1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_debug.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::mitm::socket::resolver { + + void InitializeDebug(bool enable_log); + + void LogDebug(const char *fmt, ...) __attribute__((format(printf, 1, 2))); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_host_redirection.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_host_redirection.cpp new file mode 100644 index 00000000..ea6f1eb0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_host_redirection.cpp @@ -0,0 +1,399 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../amsmitm_fs_utils.hpp" +#include "dnsmitm_debug.hpp" +#include "dnsmitm_host_redirection.hpp" +#include "socket_allocator.hpp" + +namespace ams::mitm::socket::resolver { + + namespace { + + /* https://github.com/clibs/wildcardcmp */ + constexpr int wildcardcmp(const char *pattern, const char *string) { + const char *w = nullptr; /* last `*` */ + const char *s = nullptr; /* last checked char */ + + /* malformed */ + if (!pattern || !string) return 0; + + /* loop 1 char at a time */ + while (1) { + if (!*string) { + if (!*pattern) return 1; + if ('*' == *pattern) return 1; + if (!s || !*s) return 0; + string = s++; + pattern = w; + continue; + } else { + if (*pattern != *string) { + if ('*' == *pattern) { + w = ++pattern; + s = string; + /* "*" -> "foobar" */ + if (*pattern) continue; + return 1; + } else if (w) { + string++; + /* "*ooba*" -> "foobar" */ + continue; + } + return 0; + } + } + + string++; + pattern++; + } + + return 1; + } + + constexpr const char DefaultHostsFile[] = + "# Nintendo telemetry servers\n" + "127.0.0.1 receive-%.dg.srv.nintendo.net receive-%.er.srv.nintendo.net\n"; + + constinit os::SdkMutex g_redirection_lock; + std::vector<std::pair<std::string, ams::socket::InAddrT>> g_redirection_list; + + void RemoveRedirection(const char *hostname) { + for (auto it = g_redirection_list.begin(); it != g_redirection_list.end(); ++it) { + if (std::strcmp(it->first.c_str(), hostname) == 0) { + g_redirection_list.erase(it); + break; + } + } + } + + void AddRedirection(const char *hostname, ams::socket::InAddrT addr) { + RemoveRedirection(hostname); + g_redirection_list.emplace(g_redirection_list.begin(), std::string(hostname), addr); + } + + constinit char g_specific_emummc_hosts_path[0x40] = {}; + + void ParseHostsFile(const char *file_data) { + /* Get the environment identifier from settings. */ + const auto env = ams::nsd::impl::device::GetEnvironmentIdentifierFromSettings(); + const auto env_len = std::strlen(env.value); + + /* Parse the file. */ + enum class State { + IgnoredLine, + BeginLine, + Ip1, + IpDot1, + Ip2, + IpDot2, + Ip3, + IpDot3, + Ip4, + WhiteSpace, + HostName, + }; + + ams::socket::InAddrT current_address{}; + char current_hostname[0x200]; + u32 work{}; + + State state = State::BeginLine; + for (const char *cur = file_data; *cur != '\x00'; ++cur) { + const char c = *cur; + switch (state) { + case State::IgnoredLine: + if (c == '\n') { + state = State::BeginLine; + } + break; + case State::BeginLine: + if (std::isdigit(static_cast<unsigned char>(c))) { + current_address = 0; + work = static_cast<u32>(c - '0'); + state = State::Ip1; + } else if (c == '\n') { + state = State::BeginLine; + } else { + state = State::IgnoredLine; + } + break; + case State::Ip1: + if (std::isdigit(static_cast<unsigned char>(c))) { + work *= 10; + work += static_cast<u32>(c - '0'); + } else if (c == '.') { + current_address |= (work & 0xFF) << 0; + work = 0; + state = State::IpDot1; + } else { + state = State::IgnoredLine; + } + break; + case State::IpDot1: + if (std::isdigit(static_cast<unsigned char>(c))) { + work = static_cast<u32>(c - '0'); + state = State::Ip2; + } else { + state = State::IgnoredLine; + } + break; + case State::Ip2: + if (std::isdigit(static_cast<unsigned char>(c))) { + work *= 10; + work += static_cast<u32>(c - '0'); + } else if (c == '.') { + current_address |= (work & 0xFF) << 8; + work = 0; + state = State::IpDot2; + } else { + state = State::IgnoredLine; + } + break; + case State::IpDot2: + if (std::isdigit(static_cast<unsigned char>(c))) { + work = static_cast<u32>(c - '0'); + state = State::Ip3; + } else { + state = State::IgnoredLine; + } + break; + case State::Ip3: + if (std::isdigit(static_cast<unsigned char>(c))) { + work *= 10; + work += static_cast<u32>(c - '0'); + } else if (c == '.') { + current_address |= (work & 0xFF) << 16; + work = 0; + state = State::IpDot3; + } else { + state = State::IgnoredLine; + } + break; + case State::IpDot3: + if (std::isdigit(static_cast<unsigned char>(c))) { + work = static_cast<u32>(c - '0'); + state = State::Ip4; + } else { + state = State::IgnoredLine; + } + break; + case State::Ip4: + if (std::isdigit(static_cast<unsigned char>(c))) { + work *= 10; + work += static_cast<u32>(c - '0'); + } else if (c == ' ' || c == '\t') { + current_address |= (work & 0xFF) << 24; + work = 0; + state = State::WhiteSpace; + } else { + state = State::IgnoredLine; + } + break; + case State::WhiteSpace: + if (c == '\n') { + state = State::BeginLine; + } else if (c != ' ' && c != '\r' && c != '\t') { + if (c == '%') { + std::memcpy(current_hostname, env.value, env_len); + work = env_len; + } else { + current_hostname[0] = c; + work = 1; + } + state = State::HostName; + } + break; + case State::HostName: + if (c == ' ' || c == '\r' || c == '\n' || c == '\t') { + AMS_ABORT_UNLESS(work < sizeof(current_hostname)); + current_hostname[work] = '\x00'; + + AddRedirection(current_hostname, current_address); + work = 0; + + if (c == '\n') { + state = State::BeginLine; + } else { + state = State::WhiteSpace; + } + } else if (c == '%') { + AMS_ABORT_UNLESS(work < sizeof(current_hostname) - env_len); + std::memcpy(current_hostname + work, env.value, env_len); + work += env_len; + } else { + AMS_ABORT_UNLESS(work < sizeof(current_hostname) - 1); + current_hostname[work++] = c; + } + } + } + + if (state == State::HostName) { + AMS_ABORT_UNLESS(work < sizeof(current_hostname)); + current_hostname[work] = '\x00'; + + AddRedirection(current_hostname, current_address); + } + } + + void Log(::FsFile &f, const char *fmt, ...) __attribute__((format(printf, 2, 3))); + void Log(::FsFile &f, const char *fmt, ...) { + char log_buf[0x100]; + int len = 0; + { + std::va_list vl; + va_start(vl, fmt); + len = util::VSNPrintf(log_buf, sizeof(log_buf), fmt, vl); + va_end(vl); + } + + s64 ofs; + R_ABORT_UNLESS(::fsFileGetSize(std::addressof(f), std::addressof(ofs))); + R_ABORT_UNLESS(::fsFileWrite(std::addressof(f), ofs, log_buf, len, FsWriteOption_Flush)); + } + + const char *SelectHostsFile(::FsFile &log_file) { + Log(log_file, "Selecting hosts file...\n"); + const bool is_emummc = emummc::IsActive(); + const u32 emummc_id = emummc::GetActiveId(); + util::SNPrintf(g_specific_emummc_hosts_path, sizeof(g_specific_emummc_hosts_path), "/hosts/emummc_%04x.txt", emummc_id); + + if (is_emummc) { + if (mitm::fs::HasAtmosphereSdFile(g_specific_emummc_hosts_path)) { + return g_specific_emummc_hosts_path; + } + Log(log_file, "Skipping %s because it does not exist...\n", g_specific_emummc_hosts_path); + + if (mitm::fs::HasAtmosphereSdFile("/hosts/emummc.txt")) { + return "/hosts/emummc.txt"; + } + Log(log_file, "Skipping %s because it does not exist...\n", "/hosts/emummc.txt"); + } else { + if (mitm::fs::HasAtmosphereSdFile("/hosts/sysmmc.txt")) { + return "/hosts/sysmmc.txt"; + } + Log(log_file, "Skipping %s because it does not exist...\n", "/hosts/sysmmc.txt"); + } + + return "/hosts/default.txt"; + } + + bool ShouldAddDefaultResolverRedirections() { + u8 en = 0; + if (settings::fwdbg::GetSettingsItemValue(std::addressof(en), sizeof(en), "atmosphere", "add_defaults_to_dns_hosts") == sizeof(en)) { + return (en != 0); + } + return false; + } + + } + + void InitializeResolverRedirections() { + /* Get whether we should add defaults. */ + const bool add_defaults = ShouldAddDefaultResolverRedirections(); + + /* Acquire exclusive access to the map. */ + std::scoped_lock lk(g_redirection_lock); + + /* Clear the redirections map. */ + g_redirection_list.clear(); + + /* Open log file. */ + ::FsFile log_file; + mitm::fs::DeleteAtmosphereSdFile("/logs/dns_mitm_startup.log"); + mitm::fs::CreateAtmosphereSdDirectory("/logs"); + R_ABORT_UNLESS(mitm::fs::CreateAtmosphereSdFile("/logs/dns_mitm_startup.log", 0, ams::fs::CreateOption_None)); + R_ABORT_UNLESS(mitm::fs::OpenAtmosphereSdFile(std::addressof(log_file), "/logs/dns_mitm_startup.log", ams::fs::OpenMode_ReadWrite | ams::fs::OpenMode_AllowAppend)); + ON_SCOPE_EXIT { ::fsFileClose(std::addressof(log_file)); }; + + Log(log_file, "DNS Mitm:\n"); + + /* If a default hosts file doesn't exist on the sd card, create one. */ + if (!mitm::fs::HasAtmosphereSdFile("/hosts/default.txt")) { + Log(log_file, "Creating /hosts/default.txt because it does not exist.\n"); + + mitm::fs::CreateAtmosphereSdDirectory("/hosts"); + R_ABORT_UNLESS(mitm::fs::CreateAtmosphereSdFile("/hosts/default.txt", sizeof(DefaultHostsFile) - 1, ams::fs::CreateOption_None)); + + ::FsFile default_file; + R_ABORT_UNLESS(mitm::fs::OpenAtmosphereSdFile(std::addressof(default_file), "/hosts/default.txt", ams::fs::OpenMode_ReadWrite)); + R_ABORT_UNLESS(::fsFileWrite(std::addressof(default_file), 0, DefaultHostsFile, sizeof(DefaultHostsFile) - 1, ::FsWriteOption_Flush)); + ::fsFileClose(std::addressof(default_file)); + } + + /* If we should, add the defaults. */ + if (add_defaults) { + Log(log_file, "Adding defaults to redirection list.\n"); + ParseHostsFile(DefaultHostsFile); + } + + /* Select the hosts file. */ + const char *hosts_path = SelectHostsFile(log_file); + Log(log_file, "Selected %s\n", hosts_path); + + /* Load the hosts file. */ + { + char *hosts_file_data = nullptr; + ON_SCOPE_EXIT { if (hosts_file_data != nullptr) { ams::Free(hosts_file_data); } }; + { + ::FsFile hosts_file; + R_ABORT_UNLESS(mitm::fs::OpenAtmosphereSdFile(std::addressof(hosts_file), hosts_path, ams::fs::OpenMode_Read)); + ON_SCOPE_EXIT { ::fsFileClose(std::addressof(hosts_file)); }; + + /* Get the hosts file size. */ + s64 hosts_size; + R_ABORT_UNLESS(::fsFileGetSize(std::addressof(hosts_file), std::addressof(hosts_size))); + + /* Validate we can read the file. */ + AMS_ABORT_UNLESS(0 <= hosts_size && hosts_size < 0x8000); + + /* Read the data. */ + hosts_file_data = static_cast<char *>(ams::Malloc(0x8000)); + AMS_ABORT_UNLESS(hosts_file_data != nullptr); + + u64 br; + R_ABORT_UNLESS(::fsFileRead(std::addressof(hosts_file), 0, hosts_file_data, hosts_size, ::FsReadOption_None, std::addressof(br))); + AMS_ABORT_UNLESS(br == static_cast<u64>(hosts_size)); + + /* Null-terminate. */ + hosts_file_data[hosts_size] = '\x00'; + } + + /* Parse the hosts file. */ + ParseHostsFile(hosts_file_data); + } + + /* Print the redirections. */ + Log(log_file, "Redirections:\n"); + for (const auto &[host, address] : g_redirection_list) { + Log(log_file, " `%s` -> %u.%u.%u.%u\n", host.c_str(), (address >> 0) & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF, (address >> 24) & 0xFF); + } + } + + bool GetRedirectedHostByName(ams::socket::InAddrT *out, const char *hostname) { + std::scoped_lock lk(g_redirection_lock); + + for (const auto &[host, address] : g_redirection_list) { + if (wildcardcmp(host.c_str(), hostname)) { + *out = address; + return true; + } + } + + return false; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_host_redirection.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_host_redirection.hpp new file mode 100644 index 00000000..51fb6325 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_host_redirection.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::mitm::socket::resolver { + + void InitializeResolverRedirections(); + + bool GetRedirectedHostByName(ams::socket::InAddrT *out, const char *hostname); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.cpp new file mode 100644 index 00000000..004c3a1c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.cpp @@ -0,0 +1,149 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../amsmitm_initialization.hpp" +#include "dnsmitm_module.hpp" +#include "dnsmitm_debug.hpp" +#include "dnsmitm_resolver_impl.hpp" +#include "dnsmitm_host_redirection.hpp" + +namespace ams::mitm::socket::resolver { + + namespace { + + enum PortIndex { + PortIndex_Mitm, + PortIndex_Count, + }; + + constexpr sm::ServiceName DnsMitmServiceName = sm::ServiceName::Encode("sfdnsres"); + + constexpr size_t MaxSessions = 30; + + struct ServerOptions { + static constexpr size_t PointerBufferSize = sf::hipc::DefaultServerManagerOptions::PointerBufferSize; + static constexpr size_t MaxDomains = sf::hipc::DefaultServerManagerOptions::MaxDomains; + static constexpr size_t MaxDomainObjects = sf::hipc::DefaultServerManagerOptions::MaxDomainObjects; + static constexpr bool CanDeferInvokeRequest = sf::hipc::DefaultServerManagerOptions::CanDeferInvokeRequest; + static constexpr bool CanManageMitmServers = true; + }; + + class ServerManager final : public sf::hipc::ServerManager<PortIndex_Count, ServerOptions, MaxSessions> { + private: + virtual Result OnNeedsToAccept(int port_index, Server *server) override; + }; + + alignas(os::MemoryPageSize) constinit u8 g_resolver_allocator_buffer[16_KB]; + + ServerManager g_server_manager; + + Result ServerManager::OnNeedsToAccept(int port_index, Server *server) { + /* Acknowledge the mitm session. */ + std::shared_ptr<::Service> fsrv; + sm::MitmProcessInfo client_info; + server->AcknowledgeMitmSession(std::addressof(fsrv), std::addressof(client_info)); + + switch (port_index) { + case PortIndex_Mitm: + R_RETURN(this->AcceptMitmImpl(server, sf::CreateSharedObjectEmplaced<IResolver, ResolverImpl>(decltype(fsrv)(fsrv), client_info), fsrv)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + constexpr size_t TotalThreads = 8; + static_assert(TotalThreads >= 1, "TotalThreads"); + constexpr size_t NumExtraThreads = TotalThreads - 1; + constexpr size_t ThreadStackSize = mitm::ModuleTraits<socket::resolver::MitmModule>::StackSize; + alignas(os::MemoryPageSize) u8 g_extra_thread_stacks[NumExtraThreads][ThreadStackSize]; + + os::ThreadType g_extra_threads[NumExtraThreads]; + + void LoopServerThread(void *) { + /* Loop forever, servicing our services. */ + g_server_manager.LoopProcess(); + } + + void ProcessForServerOnAllThreads() { + /* Initialize threads. */ + if constexpr (NumExtraThreads > 0) { + const s32 priority = os::GetThreadCurrentPriority(os::GetCurrentThread()); + for (size_t i = 0; i < NumExtraThreads; i++) { + R_ABORT_UNLESS(os::CreateThread(g_extra_threads + i, LoopServerThread, nullptr, g_extra_thread_stacks[i], ThreadStackSize, priority)); + } + } + + /* Start extra threads. */ + if constexpr (NumExtraThreads > 0) { + for (size_t i = 0; i < NumExtraThreads; i++) { + os::StartThread(g_extra_threads + i); + } + } + + /* Loop this thread. */ + LoopServerThread(nullptr); + + /* Wait for extra threads to finish. */ + if constexpr (NumExtraThreads > 0) { + for (size_t i = 0; i < NumExtraThreads; i++) { + os::WaitThread(g_extra_threads + i); + } + } + } + + bool ShouldMitmDns() { + u8 en = 0; + if (settings::fwdbg::GetSettingsItemValue(std::addressof(en), sizeof(en), "atmosphere", "enable_dns_mitm") == sizeof(en)) { + return (en != 0); + } + return false; + } + + bool ShouldEnableDebugLog() { + u8 en = 0; + if (settings::fwdbg::GetSettingsItemValue(std::addressof(en), sizeof(en), "atmosphere", "enable_dns_mitm_debug_log") == sizeof(en)) { + return (en != 0); + } + return false; + } + + } + + void MitmModule::ThreadFunction(void *) { + /* Wait until initialization is complete. */ + mitm::WaitInitialized(); + + /* If we shouldn't mitm dns, don't do anything at all. */ + if (!ShouldMitmDns()) { + return; + } + + /* Initialize the socket allocator. */ + ams::socket::InitializeAllocatorForInternal(g_resolver_allocator_buffer, sizeof(g_resolver_allocator_buffer)); + + /* Initialize debug. */ + resolver::InitializeDebug(ShouldEnableDebugLog()); + + /* Initialize redirection map. */ + resolver::InitializeResolverRedirections(); + + /* Create mitm servers. */ + R_ABORT_UNLESS((g_server_manager.RegisterMitmServer<ResolverImpl>(PortIndex_Mitm, DnsMitmServiceName))); + + /* Loop forever, servicing our services. */ + ProcessForServerOnAllThreads(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.hpp new file mode 100644 index 00000000..af657b74 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_module.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../amsmitm_module.hpp" + +namespace ams::mitm::socket::resolver { + + DEFINE_MITM_MODULE_CLASS(0x2000, AMS_GET_SYSTEM_THREAD_PRIORITY(socket, ResolverIpcServer) - 1); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_resolver_impl.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_resolver_impl.cpp new file mode 100644 index 00000000..5daf6421 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_resolver_impl.cpp @@ -0,0 +1,218 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dnsmitm_resolver_impl.hpp" +#include "dnsmitm_debug.hpp" +#include "dnsmitm_host_redirection.hpp" +#include "serializer/serializer.hpp" +#include "sfdnsres_shim.h" + +namespace ams::mitm::socket::resolver { + + ssize_t SerializeRedirectedHostEnt(u8 * const dst, size_t dst_size, const char *hostname, ams::socket::InAddrT redirect_addr) { + struct in_addr addr = { .s_addr = redirect_addr }; + struct in_addr *addr_list[2] = { std::addressof(addr), nullptr }; + + struct hostent ent = { + .h_name = const_cast<char *>(hostname), + .h_aliases = nullptr, + .h_addrtype = AF_INET, + .h_length = sizeof(u32), + .h_addr_list = (char **)addr_list, + }; + + const auto result = serializer::DNSSerializer::ToBuffer(dst, dst_size, ent); + AMS_ABORT_UNLESS(result >= 0); + return result; + } + + ssize_t SerializeRedirectedAddrInfo(u8 * const dst, size_t dst_size, const char *hostname, ams::socket::InAddrT redirect_addr, u16 redirect_port, const struct addrinfo *hint) { + AMS_UNUSED(hostname); + + struct addrinfo ai = { + .ai_flags = 0, + .ai_family = AF_UNSPEC, + .ai_socktype = 0, + .ai_protocol = 0, + .ai_addrlen = 0, + .ai_canonname = nullptr, + .ai_next = nullptr, + }; + + if (hint != nullptr) { + ai = *hint; + } + + switch (ai.ai_family) { + case AF_UNSPEC: ai.ai_family = AF_INET; break; + case AF_INET: ai.ai_family = AF_INET; break; + case AF_INET6: AMS_ABORT("Redirected INET6 not supported"); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + if (ai.ai_socktype == 0) { + ai.ai_socktype = SOCK_STREAM; + } + + if (ai.ai_protocol == 0) { + ai.ai_protocol = IPPROTO_TCP; + } + + const struct sockaddr_in sin = { + .sin_family = AF_INET, + .sin_port = ams::socket::InetHtons(redirect_port), + .sin_addr = { .s_addr = redirect_addr }, + .sin_zero = {}, + }; + + ai.ai_addrlen = sizeof(sin); + ai.ai_addr = (struct sockaddr *)(std::addressof(sin)); + + const auto result = serializer::DNSSerializer::ToBuffer(dst, dst_size, ai); + AMS_ABORT_UNLESS(result >= 0); + return result; + } + + Result ResolverImpl::GetHostByNameRequest(u32 cancel_handle, const sf::ClientProcessId &client_pid, bool use_nsd_resolve, const sf::InBuffer &name, sf::Out<u32> out_host_error, sf::Out<u32> out_errno, const sf::OutBuffer &out_hostent, sf::Out<u32> out_size) { + AMS_UNUSED(cancel_handle, client_pid, use_nsd_resolve); + + const char *hostname = reinterpret_cast<const char *>(name.GetPointer()); + + LogDebug("[%016lx]: GetHostByNameRequest(%s)\n", m_client_info.program_id.value, hostname); + + R_UNLESS(hostname != nullptr, sm::mitm::ResultShouldForwardToSession()); + + ams::socket::InAddrT redirect_addr = {}; + R_UNLESS(GetRedirectedHostByName(std::addressof(redirect_addr), hostname), sm::mitm::ResultShouldForwardToSession()); + + LogDebug("[%016lx]: Redirecting %s to %u.%u.%u.%u\n", m_client_info.program_id.value, hostname, (redirect_addr >> 0) & 0xFF, (redirect_addr >> 8) & 0xFF, (redirect_addr >> 16) & 0xFF, (redirect_addr >> 24) & 0xFF); + const auto size = SerializeRedirectedHostEnt(out_hostent.GetPointer(), out_hostent.GetSize(), hostname, redirect_addr); + + *out_host_error = 0; + *out_errno = 0; + *out_size = size; + + R_SUCCEED(); + } + + Result ResolverImpl::GetAddrInfoRequest(u32 cancel_handle, const sf::ClientProcessId &client_pid, bool use_nsd_resolve, const sf::InBuffer &node, const sf::InBuffer &srv, const sf::InBuffer &serialized_hint, const sf::OutBuffer &out_addrinfo, sf::Out<u32> out_errno, sf::Out<s32> out_retval, sf::Out<u32> out_size) { + AMS_UNUSED(cancel_handle, client_pid, use_nsd_resolve); + + const char *hostname = reinterpret_cast<const char *>(node.GetPointer()); + + LogDebug("[%016lx]: GetAddrInfoRequest(%s, %s)\n", m_client_info.program_id.value, reinterpret_cast<const char *>(node.GetPointer()), reinterpret_cast<const char *>(srv.GetPointer())); + + R_UNLESS(hostname != nullptr, sm::mitm::ResultShouldForwardToSession()); + + ams::socket::InAddrT redirect_addr = {}; + R_UNLESS(GetRedirectedHostByName(std::addressof(redirect_addr), hostname), sm::mitm::ResultShouldForwardToSession()); + + u16 port = 0; + if (srv.GetPointer() != nullptr) { + for (const char *cur = reinterpret_cast<const char *>(srv.GetPointer()); *cur != 0; ++cur) { + AMS_ABORT_UNLESS(std::isdigit(static_cast<unsigned char>(*cur))); + port *= 10; + port += *cur - '0'; + } + } + + LogDebug("[%016lx]: Redirecting %s:%u to %u.%u.%u.%u\n", m_client_info.program_id.value, hostname, port, (redirect_addr >> 0) & 0xFF, (redirect_addr >> 8) & 0xFF, (redirect_addr >> 16) & 0xFF, (redirect_addr >> 24) & 0xFF); + + const bool use_hint = serialized_hint.GetPointer() != nullptr; + struct addrinfo hint = {}; + if (use_hint) { + AMS_ABORT_UNLESS(serializer::DNSSerializer::FromBuffer(hint, serialized_hint.GetPointer(), serialized_hint.GetSize()) >= 0); + } + ON_SCOPE_EXIT { if (use_hint) { serializer::FreeAddrInfo(hint); } }; + + const auto size = SerializeRedirectedAddrInfo(out_addrinfo.GetPointer(), out_addrinfo.GetSize(), hostname, redirect_addr, port, use_hint ? std::addressof(hint) : nullptr); + + *out_retval = 0; + *out_errno = 0; + *out_size = size; + + R_SUCCEED(); + } + + Result ResolverImpl::GetHostByNameRequestWithOptions(const sf::ClientProcessId &client_pid, const sf::InAutoSelectBuffer &name, const sf::OutAutoSelectBuffer &out_hostent, sf::Out<u32> out_size, u32 options_version, const sf::InAutoSelectBuffer &options, u32 num_options, sf::Out<s32> out_host_error, sf::Out<s32> out_errno) { + AMS_UNUSED(client_pid, options_version, options, num_options); + + const char *hostname = reinterpret_cast<const char *>(name.GetPointer()); + + LogDebug("[%016lx]: GetHostByNameRequestWithOptions(%s)\n", m_client_info.program_id.value, hostname); + + R_UNLESS(hostname != nullptr, sm::mitm::ResultShouldForwardToSession()); + + ams::socket::InAddrT redirect_addr = {}; + R_UNLESS(GetRedirectedHostByName(std::addressof(redirect_addr), hostname), sm::mitm::ResultShouldForwardToSession()); + + LogDebug("[%016lx]: Redirecting %s to %u.%u.%u.%u\n", m_client_info.program_id.value, hostname, (redirect_addr >> 0) & 0xFF, (redirect_addr >> 8) & 0xFF, (redirect_addr >> 16) & 0xFF, (redirect_addr >> 24) & 0xFF); + const auto size = SerializeRedirectedHostEnt(out_hostent.GetPointer(), out_hostent.GetSize(), hostname, redirect_addr); + + *out_host_error = 0; + *out_errno = 0; + *out_size = size; + + R_SUCCEED(); + } + + Result ResolverImpl::GetAddrInfoRequestWithOptions(const sf::ClientProcessId &client_pid, const sf::InBuffer &node, const sf::InBuffer &srv, const sf::InBuffer &serialized_hint, const sf::OutAutoSelectBuffer &out_addrinfo, sf::Out<u32> out_size, sf::Out<s32> out_retval, u32 options_version, const sf::InAutoSelectBuffer &options, u32 num_options, sf::Out<s32> out_host_error, sf::Out<s32> out_errno) { + AMS_UNUSED(client_pid, options_version, options, num_options); + + const char *hostname = reinterpret_cast<const char *>(node.GetPointer()); + + LogDebug("[%016lx]: GetAddrInfoRequestWithOptions(%s, %s)\n", m_client_info.program_id.value, hostname, reinterpret_cast<const char *>(srv.GetPointer())); + + R_UNLESS(hostname != nullptr, sm::mitm::ResultShouldForwardToSession()); + + ams::socket::InAddrT redirect_addr = {}; + R_UNLESS(GetRedirectedHostByName(std::addressof(redirect_addr), hostname), sm::mitm::ResultShouldForwardToSession()); + + u16 port = 0; + if (srv.GetPointer() != nullptr) { + for (const char *cur = reinterpret_cast<const char *>(srv.GetPointer()); *cur != 0; ++cur) { + AMS_ABORT_UNLESS(std::isdigit(static_cast<unsigned char>(*cur))); + port *= 10; + port += *cur - '0'; + } + } + + LogDebug("[%016lx]: Redirecting %s:%u to %u.%u.%u.%u\n", m_client_info.program_id.value, hostname, port, (redirect_addr >> 0) & 0xFF, (redirect_addr >> 8) & 0xFF, (redirect_addr >> 16) & 0xFF, (redirect_addr >> 24) & 0xFF); + + const bool use_hint = serialized_hint.GetPointer() != nullptr; + struct addrinfo hint = {}; + if (use_hint) { + AMS_ABORT_UNLESS(serializer::DNSSerializer::FromBuffer(hint, serialized_hint.GetPointer(), serialized_hint.GetSize()) >= 0); + } + ON_SCOPE_EXIT { if (use_hint) { serializer::FreeAddrInfo(hint); } }; + + const auto size = SerializeRedirectedAddrInfo(out_addrinfo.GetPointer(), out_addrinfo.GetSize(), hostname, redirect_addr, port, use_hint ? std::addressof(hint) : nullptr); + + *out_retval = 0; + *out_host_error = 0; + *out_errno = 0; + *out_size = size; + + R_SUCCEED(); + } + + Result ResolverImpl::AtmosphereReloadHostsFile() { + /* Perform a hosts file reload. */ + InitializeResolverRedirections(); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_resolver_impl.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_resolver_impl.hpp new file mode 100644 index 00000000..0b403696 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/dnsmitm_resolver_impl.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#define AMS_DNS_MITM_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, GetHostByNameRequest, (u32 cancel_handle, const sf::ClientProcessId &client_pid, bool use_nsd_resolve, const sf::InBuffer &name, sf::Out<u32> out_host_error, sf::Out<u32> out_errno, const sf::OutBuffer &out_hostent, sf::Out<u32> out_size), (cancel_handle, client_pid, use_nsd_resolve, name, out_host_error, out_errno, out_hostent, out_size)) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, GetAddrInfoRequest, (u32 cancel_handle, const sf::ClientProcessId &client_pid, bool use_nsd_resolve, const sf::InBuffer &node, const sf::InBuffer &srv, const sf::InBuffer &serialized_hint, const sf::OutBuffer &out_addrinfo, sf::Out<u32> out_errno, sf::Out<s32> out_retval, sf::Out<u32> out_size), (cancel_handle, client_pid, use_nsd_resolve, node, srv, serialized_hint, out_addrinfo, out_errno, out_retval, out_size)) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, GetHostByNameRequestWithOptions, (const sf::ClientProcessId &client_pid, const sf::InAutoSelectBuffer &name, const sf::OutAutoSelectBuffer &out_hostent, sf::Out<u32> out_size, u32 options_version, const sf::InAutoSelectBuffer &options, u32 num_options, sf::Out<s32> out_host_error, sf::Out<s32> out_errno), (client_pid, name, out_hostent, out_size, options_version, options, num_options, out_host_error, out_errno), hos::Version_5_0_0) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, GetAddrInfoRequestWithOptions, (const sf::ClientProcessId &client_pid, const sf::InBuffer &node, const sf::InBuffer &srv, const sf::InBuffer &serialized_hint, const sf::OutAutoSelectBuffer &out_addrinfo, sf::Out<u32> out_size, sf::Out<s32> out_retval, u32 options_version, const sf::InAutoSelectBuffer &options, u32 num_options, sf::Out<s32> out_host_error, sf::Out<s32> out_errno), (client_pid, node, srv, serialized_hint, out_addrinfo, out_size, out_retval, options_version, options, num_options, out_host_error, out_errno), hos::Version_5_0_0) \ + AMS_SF_METHOD_INFO(C, H, 65000, Result, AtmosphereReloadHostsFile, (), ()) + +AMS_SF_DEFINE_MITM_INTERFACE(ams::mitm::socket::resolver, IResolver, AMS_DNS_MITM_INTERFACE_INFO, 0x5935F91A) + +namespace ams::mitm::socket::resolver { + + class ResolverImpl : public sf::MitmServiceImplBase { + public: + using MitmServiceImplBase::MitmServiceImplBase; + public: + static bool ShouldMitm(const sm::MitmProcessInfo &client_info) { + /* We will mitm: + * - everything. + */ + AMS_UNUSED(client_info); + return true; + } + public: + /* Overridden commands. */ + Result GetHostByNameRequest(u32 cancel_handle, const sf::ClientProcessId &client_pid, bool use_nsd_resolve, const sf::InBuffer &name, sf::Out<u32> out_host_error, sf::Out<u32> out_errno, const sf::OutBuffer &out_hostent, sf::Out<u32> out_size); + Result GetAddrInfoRequest(u32 cancel_handle, const sf::ClientProcessId &client_pid, bool use_nsd_resolve, const sf::InBuffer &node, const sf::InBuffer &srv, const sf::InBuffer &serialized_hint, const sf::OutBuffer &out_addrinfo, sf::Out<u32> out_errno, sf::Out<s32> out_retval, sf::Out<u32> out_size); + Result GetHostByNameRequestWithOptions(const sf::ClientProcessId &client_pid, const sf::InAutoSelectBuffer &name, const sf::OutAutoSelectBuffer &out_hostent, sf::Out<u32> out_size, u32 options_version, const sf::InAutoSelectBuffer &options, u32 num_options, sf::Out<s32> out_host_error, sf::Out<s32> out_errno); + Result GetAddrInfoRequestWithOptions(const sf::ClientProcessId &client_pid, const sf::InBuffer &node, const sf::InBuffer &srv, const sf::InBuffer &serialized_hint, const sf::OutAutoSelectBuffer &out_addrinfo, sf::Out<u32> out_size, sf::Out<s32> out_retval, u32 options_version, const sf::InAutoSelectBuffer &options, u32 num_options, sf::Out<s32> out_host_error, sf::Out<s32> out_errno); + + /* Extension commands. */ + Result AtmosphereReloadHostsFile(); + }; + static_assert(IsIResolver<ResolverImpl>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer.cpp new file mode 100644 index 00000000..bb7d9845 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer.cpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../dnsmitm_debug.hpp" +#include "serializer.hpp" + +namespace ams::mitm::socket::resolver::serializer { + + ssize_t DNSSerializer::CheckToBufferArguments(const u8 *dst, size_t dst_size, size_t required, int error_id) { + /* TODO: Logging, using error_id */ + AMS_UNUSED(error_id); + + if (dst == nullptr) { + return -1; + } else if (dst_size < required) { + return -1; + } + + return 0; + } + + u32 DNSSerializer::InternalHton(const u32 &v) { + return ams::socket::InetHtonl(v); + } + + u16 DNSSerializer::InternalHton(const u16 &v) { + return ams::socket::InetHtons(v); + } + + u32 DNSSerializer::InternalNtoh(const u32 &v) { + return ams::socket::InetNtohl(v); + } + + u16 DNSSerializer::InternalNtoh(const u16 &v) { + return ams::socket::InetNtohs(v); + } + + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer.hpp new file mode 100644 index 00000000..3ab893b2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer.hpp @@ -0,0 +1,73 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::mitm::socket::resolver::serializer { + + class DNSSerializer { + public: + static ssize_t CheckToBufferArguments(const u8 *dst, size_t dst_size, size_t required, int error_id); + + static u32 InternalHton(const u32 &v); + static u16 InternalHton(const u16 &v); + static u32 InternalNtoh(const u32 &v); + static u16 InternalNtoh(const u16 &v); + public: + template<typename T> + static size_t SizeOf(const T &in); + + template<typename T> + static size_t SizeOf(const T *in); + + template<typename T> + static size_t SizeOf(const T *in, size_t count); + + template<typename T> + static size_t SizeOf(const T **arr, u32 &out_count); + + template<typename T> + static ssize_t ToBuffer(u8 * const dst, size_t dst_size, const T &in); + + template<typename T> + static ssize_t FromBuffer(T &out, const u8 *src, size_t src_size); + + template<typename T> + static ssize_t ToBuffer(u8 * const dst, size_t dst_Size, T *in); + + template<typename T> + static ssize_t ToBuffer(u8 * const dst, size_t dst_size, T **arr); + + template<typename T> + static ssize_t FromBuffer(T *&out, const u8 *src, size_t src_size); + + template<typename T> + static ssize_t FromBuffer(T **&out_arr, const u8 *src, size_t src_size); + + template<typename T> + static ssize_t ToBuffer(u8 * const dst, size_t dst_size, const T * const arr, size_t count); + + template<typename T> + static ssize_t FromBuffer(T * const arr, size_t arr_size, const u8 *src, size_t src_size, size_t count); + }; + + void FreeHostent(ams::socket::HostEnt &ent); + void FreeHostent(struct hostent &ent); + + void FreeAddrInfo(ams::socket::AddrInfo &addr_info); + void FreeAddrInfo(struct addrinfo &addr_info); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_addrinfo.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_addrinfo.cpp new file mode 100644 index 00000000..efbc79a1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_addrinfo.cpp @@ -0,0 +1,444 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../dnsmitm_debug.hpp" +#include "../socket_allocator.hpp" +#include "serializer.hpp" + +namespace ams::mitm::socket::resolver::serializer { + + namespace { + + constexpr inline u32 AddrInfoMagic = 0xBEEFCAFE; + + template<typename T> + concept IsAddrInfo = std::same_as<T, ams::socket::AddrInfo> || std::same_as<T, struct addrinfo>; + + template<typename T> requires IsAddrInfo<T> + using SockAddrType = typename std::conditional<std::same_as<T, ams::socket::AddrInfo>, ams::socket::SockAddr, struct sockaddr>::type; + + template<typename T> requires IsAddrInfo<T> + using SockAddrInType = typename std::conditional<std::same_as<T, ams::socket::AddrInfo>, ams::socket::SockAddrIn, struct sockaddr_in>::type; + + template<typename T> requires IsAddrInfo<T> + using SockAddrIn6Type = typename std::conditional<std::same_as<T, ams::socket::AddrInfo>, struct sockaddr_in6, struct sockaddr_in6>::type; + + template<typename T> requires IsAddrInfo<T> + constexpr bool IsAfInet(const auto ai_family) { + if constexpr (std::same_as<T, ams::socket::AddrInfo>) { + return ai_family == ams::socket::Family::Af_Inet; + } else { + return ai_family == AF_INET; + } + } + + template<typename T> requires IsAddrInfo<T> + constexpr bool IsAfInet6(const auto ai_family) { + if constexpr (std::same_as<T, ams::socket::AddrInfo>) { + return ai_family == ams::socket::Family::Af_Inet6; + } else { + return ai_family == AF_INET; + } + } + + template<typename T> requires IsAddrInfo<T> + void FreeAddrInfoImpl(T &addr_info) { + T *next = nullptr; + for (T *cur = std::addressof(addr_info); cur != nullptr; cur = next) { + next = cur->ai_next; + + if (cur->ai_addr != nullptr) { + if (IsAfInet<T>(cur->ai_family)) { + ams::socket::impl::Free(reinterpret_cast<SockAddrInType<T> *>(cur->ai_addr)); + } else if (IsAfInet6<T>(cur->ai_family)) { + ams::socket::impl::Free(reinterpret_cast<SockAddrIn6Type<T> *>(cur->ai_addr)); + } else { + ams::socket::impl::Free(cur->ai_addr); + } + cur->ai_addr = nullptr; + } + + if (cur->ai_canonname != nullptr) { + ams::socket::impl::Free(cur->ai_canonname); + cur->ai_canonname = nullptr; + } + + if (cur != std::addressof(addr_info)) { + ams::socket::impl::Free(cur); + } + } + } + + template<typename T> requires IsAddrInfo<T> + size_t AddrInfoSingleSizeOf(const T *addr_info) { + size_t rc = 6 * sizeof(u32); + + if (addr_info->ai_addr == nullptr) { + rc += sizeof(u32); + } else if (IsAfInet<T>(addr_info->ai_family)) { + rc += DNSSerializer::SizeOf(*reinterpret_cast<SockAddrInType<T> *>(addr_info->ai_addr)); + } else if (IsAfInet6<T>(addr_info->ai_family)) { + rc += DNSSerializer::SizeOf(*reinterpret_cast<SockAddrIn6Type<T> *>(addr_info->ai_addr)); + } else if (addr_info->ai_addrlen == 0) { + rc += sizeof(u32); + } else { + rc += addr_info->ai_addrlen; + } + + if (addr_info->ai_canonname != nullptr) { + rc += DNSSerializer::SizeOf(static_cast<const char *>(addr_info->ai_canonname)); + } else { + rc += sizeof(u8); + } + + if (addr_info->ai_next == nullptr) { + rc += sizeof(u32); + } + + return rc; + } + + template<typename T> requires IsAddrInfo<T> + size_t SizeOfImpl(const T &in) { + size_t rc = 0; + + for (const T *addr_info = std::addressof(in); addr_info != nullptr; addr_info = addr_info->ai_next) { + rc += AddrInfoSingleSizeOf(addr_info); + } + + return rc; + } + + template<typename T> requires IsAddrInfo<T> + ssize_t ToBufferInternalImpl(u8 * const dst, size_t dst_size, const T &addr_info) { + ssize_t rc = -1; + u8 *cur = dst; + + { + const u32 value = AddrInfoMagic; + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), value)) == -1) { + return rc; + } + cur += rc; + } + + { + const u32 value = static_cast<u32>(addr_info.ai_flags); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), value)) == -1) { + return rc; + } + cur += rc; + } + + { + const u32 value = static_cast<u32>(addr_info.ai_family); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), value)) == -1) { + return rc; + } + cur += rc; + } + + { + const u32 value = static_cast<u32>(addr_info.ai_socktype); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), value)) == -1) { + return rc; + } + cur += rc; + } + + { + const u32 value = static_cast<u32>(addr_info.ai_protocol); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), value)) == -1) { + return rc; + } + cur += rc; + } + + { + const u32 value = static_cast<u32>(addr_info.ai_addrlen); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), value)) == -1) { + return rc; + } + cur += rc; + } + + { + if (addr_info.ai_addr == nullptr || addr_info.ai_addrlen == 0) { + const u32 value = 0; + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), value)) == -1) { + return rc; + } + } else if (IsAfInet<T>(addr_info.ai_family)) { + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), *reinterpret_cast<SockAddrInType<T> *>(addr_info.ai_addr))) == -1) { + return rc; + } + } else if (IsAfInet6<T>(addr_info.ai_family)) { + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), *reinterpret_cast<SockAddrIn6Type<T> *>(addr_info.ai_addr))) == -1) { + return rc; + } + } else { + if (dst_size - (cur - dst) < addr_info.ai_addrlen) { + rc = -1; + return rc; + } + + /* NOTE: This is clearly a nintendo bug, see the accompanying note in FromBufferInternalImpl */ + std::memmove(cur, std::addressof(addr_info.ai_addr), addr_info.ai_addrlen); + rc = addr_info.ai_addrlen; + } + cur += rc; + } + + { + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), addr_info.ai_canonname)) == -1) { + return rc; + } + cur += rc; + } + + if (addr_info.ai_next == nullptr) { + const u32 value = 0; + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), value)) == -1) { + return rc; + } + cur += rc; + } + + rc = cur - dst; + return rc; + } + + template<typename T> requires IsAddrInfo<T> + ssize_t ToBufferImpl(u8 * const dst, size_t dst_size, const T &in) { + ssize_t rc = -1; + u8 *cur = dst; + std::memset(dst, 0, dst_size); + + const size_t required = DNSSerializer::SizeOf(in); + if ((rc = DNSSerializer::CheckToBufferArguments(cur, dst_size, required, __LINE__)) == -1) { + return rc; + } + + for (const T *addr_info = std::addressof(in); addr_info != nullptr; addr_info = addr_info->ai_next) { + if ((rc = ToBufferInternalImpl(cur, dst_size, *addr_info)) == -1) { + return rc; + } + cur += rc; + } + + rc = cur - dst; + return rc; + } + + template<typename T> requires IsAddrInfo<T> + ssize_t FromBufferInternalImpl(T &out, const u8 *src, size_t src_size) { + ssize_t rc = -1; + const u8 *cur = src; + + std::memset(std::addressof(out), 0, sizeof(out)); + + ON_SCOPE_EXIT { if (rc < 0) { FreeAddrInfo(out); } }; + + u32 tmp_value; + + { + if ((rc = DNSSerializer::FromBuffer(tmp_value, cur, src_size - (cur - src))) == -1) { + return rc; + } else if (tmp_value != AddrInfoMagic) { + return rc; + } + cur += rc; + } + + { + if ((rc = DNSSerializer::FromBuffer(tmp_value, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.ai_flags = static_cast<decltype(out.ai_flags)>(tmp_value); + cur += rc; + } + + { + if ((rc = DNSSerializer::FromBuffer(tmp_value, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.ai_family = static_cast<decltype(out.ai_family)>(tmp_value); + cur += rc; + } + + { + if ((rc = DNSSerializer::FromBuffer(tmp_value, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.ai_socktype = static_cast<decltype(out.ai_socktype)>(tmp_value); + cur += rc; + } + + { + if ((rc = DNSSerializer::FromBuffer(tmp_value, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.ai_protocol = static_cast<decltype(out.ai_protocol)>(tmp_value); + cur += rc; + } + + { + if ((rc = DNSSerializer::FromBuffer(tmp_value, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.ai_addrlen = static_cast<decltype(out.ai_addrlen)>(tmp_value); + cur += rc; + } + + { + if (out.ai_addrlen == 0) { + if ((rc = DNSSerializer::FromBuffer(tmp_value, cur, src_size - (cur - src))) == -1) { + return rc; + } + + if (tmp_value != 0) { + rc = -1; + return rc; + } + out.ai_addr = nullptr; + } else if (IsAfInet<T>(out.ai_family)) { + out.ai_addr = static_cast<SockAddrType<T> *>(ams::socket::impl::Alloc(sizeof(SockAddrInType<T>))); + if (out.ai_addr == nullptr) { + rc = -1; + return rc; + } + std::memset(out.ai_addr, 0, sizeof(SockAddrInType<T>)); + + if ((rc = DNSSerializer::FromBuffer(*reinterpret_cast<SockAddrInType<T> *>(out.ai_addr), cur, src_size - (cur - src))) == -1) { + return rc; + } + } else if (IsAfInet6<T>(out.ai_family)) { + out.ai_addr = static_cast<SockAddrType<T> *>(ams::socket::impl::Alloc(sizeof(SockAddrIn6Type<T>))); + if (out.ai_addr == nullptr) { + rc = -1; + return rc; + } + std::memset(out.ai_addr, 0, sizeof(SockAddrIn6Type<T>)); + + if ((rc = DNSSerializer::FromBuffer(*reinterpret_cast<SockAddrIn6Type<T> *>(out.ai_addr), cur, src_size - (cur - src))) == -1) { + return rc; + } + } else { + out.ai_addr = static_cast<decltype(out.ai_addr)>(ams::socket::impl::Alloc(out.ai_addrlen)); + if (out.ai_addr == nullptr) { + rc = -1; + return rc; + } + + /* NOTE: This is *clearly* a nintendo bug. */ + /* They obviously intend to copy to the buffer they just allocated, but instead they copy to the addrinfo structure itself. */ + /* Probably &out.ai_addr instead of &out.ai_addr[0]? Either way, we'll implement what they do, but... */ + std::memcpy(std::addressof(out.ai_addr), cur, out.ai_addrlen); + rc = out.ai_addrlen; + } + cur += rc; + } + + { + if ((rc = DNSSerializer::FromBuffer(out.ai_canonname, cur, src_size - (cur - src))) == -1) { + return rc; + } + cur += rc; + } + + { + if ((rc = DNSSerializer::FromBuffer(tmp_value, cur, src_size - (cur - src))) == -1) { + return rc; + } else if (tmp_value == 0) { + out.ai_next = nullptr; + cur += rc; + } else if (tmp_value == AddrInfoMagic) { + out.ai_next = static_cast<T *>(ams::socket::impl::Alloc(sizeof(T))); + if (out.ai_next == nullptr) { + rc = -1; + return rc; + } + + std::memset(out.ai_next, 0, sizeof(T)); + } else { + rc = -1; + return rc; + } + } + + rc = cur - src; + return rc; + } + + template<typename T> requires IsAddrInfo<T> + ssize_t FromBufferImpl(T &out, const u8 *src, size_t src_size) { + ssize_t rc = 0; + const u8 *cur = src; + + const size_t required = DNSSerializer::SizeOf(out); + if (src_size < required) { + ams::socket::SetLastError(ams::socket::Errno::ENoSpc); + rc = -1; + return rc; + } + + for (T *addr_info = std::addressof(out); addr_info != nullptr; addr_info = addr_info->ai_next) { + if ((rc = FromBufferInternalImpl(*addr_info, cur, src_size - (cur - src))) == -1) { + rc = -1; + return rc; + } + cur += rc; + } + + rc = cur - src; + return rc; + } + + } + + template<> size_t DNSSerializer::SizeOf(const struct addrinfo &in) { + return SizeOfImpl(in); + } + + template<> size_t DNSSerializer::SizeOf(const ams::socket::AddrInfo &in) { + return SizeOfImpl(in); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const ams::socket::AddrInfo &in) { + return ToBufferImpl(dst, dst_size, in); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const struct addrinfo &in) { + return ToBufferImpl(dst, dst_size, in); + } + + template<> ssize_t DNSSerializer::FromBuffer(ams::socket::AddrInfo &out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + + template<> ssize_t DNSSerializer::FromBuffer(struct addrinfo &out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + + void FreeAddrInfo(ams::socket::AddrInfo &addr_info) { + return FreeAddrInfoImpl(addr_info); + } + + void FreeAddrInfo(struct addrinfo &addr_info) { + return FreeAddrInfoImpl(addr_info); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_hostent.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_hostent.cpp new file mode 100644 index 00000000..e62d87d1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_hostent.cpp @@ -0,0 +1,234 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../dnsmitm_debug.hpp" +#include "../socket_allocator.hpp" +#include "serializer.hpp" + +namespace ams::mitm::socket::resolver::serializer { + + namespace { + + template<typename T> + concept IsHostEnt = std::same_as<T, ams::socket::HostEnt> || std::same_as<T, struct hostent>; + + template<typename T> requires IsHostEnt<T> + using InAddrType = typename std::conditional<std::same_as<T, ams::socket::HostEnt>, ams::socket::InAddr, struct in_addr>::type; + + template<typename T> requires IsHostEnt<T> + constexpr bool IsAfInet(const auto h_addrtype) { + if constexpr (std::same_as<T, ams::socket::HostEnt>) { + return h_addrtype == ams::socket::Family::Af_Inet; + } else { + return h_addrtype == AF_INET; + } + } + + template<typename T> requires IsHostEnt<T> + constexpr bool IsAfInet6(const auto h_addrtype) { + if constexpr (std::same_as<T, ams::socket::HostEnt>) { + return h_addrtype == ams::socket::Family::Af_Inet6; + } else { + return h_addrtype == AF_INET; + } + } + + template<typename T> requires IsHostEnt<T> + size_t SizeOfImpl(const T &in) { + size_t rc = 0; + u32 dummy = 0; + + rc += DNSSerializer::SizeOf((const char *)(in.h_name)); + rc += DNSSerializer::SizeOf((const char **)(in.h_aliases), dummy); + rc += sizeof(u32); + rc += sizeof(u32); + rc += DNSSerializer::SizeOf((const InAddrType<T> **)(in.h_addr_list), dummy); + + return rc; + } + + template<typename T> requires IsHostEnt<T> + ssize_t ToBufferImpl(u8 * const dst, size_t dst_size, const T &in) { + ssize_t rc = -1; + u8 *cur = dst; + + const size_t required = DNSSerializer::SizeOf(in); + if ((rc = DNSSerializer::CheckToBufferArguments(cur, dst_size, required, __LINE__)) == -1) { + return rc; + } + + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), in.h_name)) == -1) { + return rc; + } + cur += rc; + + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), in.h_aliases)) == -1) { + return rc; + } + cur += rc; + + const u16 h_addrtype = static_cast<u16>(in.h_addrtype); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), h_addrtype)) == -1) { + return rc; + } + cur += rc; + + const u16 h_length = in.h_length; + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), h_length)) == -1) { + return rc; + } + cur += rc; + + if (IsAfInet<T>(in.h_addrtype)) { + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), (InAddrType<T> **)(in.h_addr_list))) == -1) { + return rc; + } + } else if (IsAfInet6<T>(in.h_addrtype)) { + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), (InAddrType<T> **)(in.h_addr_list))) == -1) { + return rc; + } + } else { + const u32 null = 0; + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), null)) == -1) { + return rc; + } + } + cur += rc; + + rc = cur - dst; + return rc; + } + + template<typename T> requires IsHostEnt<T> + ssize_t FromBufferImpl(T &out, const u8 *src, size_t src_size) { + ssize_t rc = -1; + const u8 *cur = src; + + std::memset(std::addressof(out), 0, sizeof(out)); + + ON_SCOPE_EXIT { + if (rc < 0) { + FreeHostent(out); + } + }; + + if ((rc = DNSSerializer::FromBuffer(out.h_name, cur, src_size - (cur - src))) == -1) { + return rc; + } + cur += rc; + + if ((rc = DNSSerializer::FromBuffer(out.h_aliases, cur, src_size - (cur - src))) == -1) { + return rc; + } + cur += rc; + + u16 h_addrtype = 0; + if ((rc = DNSSerializer::FromBuffer(h_addrtype, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.h_addrtype = static_cast<decltype(out.h_addrtype)>(h_addrtype); + cur += rc; + + u16 h_length = 0; + if ((rc = DNSSerializer::FromBuffer(h_length, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.h_length = h_length; + cur += rc; + + InAddrType<T> **addrs = nullptr; + if ((rc = DNSSerializer::FromBuffer(addrs, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.h_addr_list = (char **)addrs; + cur += rc; + + rc = cur - src; + return rc; + } + + template<typename T> requires IsHostEnt<T> + void FreeHostentImpl(T &ent) { + if (ent.h_name != nullptr) { + ams::socket::impl::Free(ent.h_name); + ent.h_name = nullptr; + } + + if (ent.h_aliases != nullptr) { + u32 i = 0; + for (char *str = ent.h_aliases[i]; str != nullptr; str = ent.h_aliases[++i]) { + ams::socket::impl::Free(str); + ent.h_aliases[i] = nullptr; + } + + ams::socket::impl::Free(ent.h_aliases); + ent.h_aliases = nullptr; + } + + if (ent.h_addr_list != nullptr) { + AMS_ASSERT(ent.h_length == sizeof(u32)); + if (ent.h_length == sizeof(u32)) { + auto **addr_list = reinterpret_cast<InAddrType<T> **>(ent.h_addr_list); + + u32 i = 0; + for (auto *addr = addr_list[i]; addr != nullptr; addr = addr_list[++i]) { + ams::socket::impl::Free(addr); + addr_list[i] = nullptr; + } + + ams::socket::impl::Free(ent.h_addr_list); + ent.h_addr_list = nullptr; + } + } + + std::memset(std::addressof(ent), 0, sizeof(ent)); + } + + } + + template<> size_t DNSSerializer::SizeOf(const struct hostent &in) { + return SizeOfImpl(in); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const struct hostent &in) { + return ToBufferImpl(dst, dst_size, in); + } + + template<> ssize_t DNSSerializer::FromBuffer(struct hostent &out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + + template<> size_t DNSSerializer::SizeOf(const ams::socket::HostEnt &in) { + return SizeOfImpl(in); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const ams::socket::HostEnt &in) { + return ToBufferImpl(dst, dst_size, in); + } + + template<> ssize_t DNSSerializer::FromBuffer(ams::socket::HostEnt &out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + + void FreeHostent(ams::socket::HostEnt &ent) { + return FreeHostentImpl(ent); + } + + void FreeHostent(struct hostent &ent) { + return FreeHostentImpl(ent); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_in_addr.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_in_addr.cpp new file mode 100644 index 00000000..564b67f3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_in_addr.cpp @@ -0,0 +1,237 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../dnsmitm_debug.hpp" +#include "../socket_allocator.hpp" +#include "serializer.hpp" + +namespace ams::mitm::socket::resolver::serializer { + + namespace { + + template<typename T> + concept IsInAddr = std::same_as<T, ams::socket::InAddr> || std::same_as<T, struct in_addr>; + + template<typename T> requires IsInAddr<T> + size_t SizeOfImpl(const T &) { + return sizeof(u32); + } + + template<typename T> requires IsInAddr<T> + size_t SizeOfImpl(const T **in, u32 &out_count) { + size_t rc = sizeof(u32); + out_count = 0; + + if (in != nullptr) { + for (const T ** cur_addr = in; *cur_addr != nullptr; ++cur_addr) { + ++out_count; + rc += sizeof(u32); + } + } + + return rc; + } + + template<typename T> requires IsInAddr<T> + ssize_t ToBufferImpl(u8 * const dst, size_t dst_size, const T &in) { + ssize_t rc = -1; + u8 *cur = dst; + + const u32 val = DNSSerializer::InternalHton(in.s_addr); + + if ((rc = DNSSerializer::CheckToBufferArguments(cur, dst_size, sizeof(in), __LINE__)) == -1) { + return rc; + } + + std::memcpy(cur, std::addressof(val), sizeof(val)); + rc += sizeof(val); + + return rc; + } + + template<typename T> requires IsInAddr<T> + ssize_t FromBufferImpl(T &out, const u8 *src, size_t src_size) { + ssize_t rc = -1; + if (src_size < sizeof(out)) { + return rc; + } + + std::memset(std::addressof(out), 0, sizeof(out)); + out.s_addr = DNSSerializer::InternalNtoh(*reinterpret_cast<const u32 *>(src)); + + rc = sizeof(u32); + return rc; + } + + template<typename T> requires IsInAddr<T> + ssize_t ToBufferImpl(u8 * const dst, size_t dst_size, T **arr) { + ssize_t rc = -1; + u8 *cur = dst; + + if (arr == nullptr && dst_size < sizeof(u32)) { + return rc; + } else if (arr == nullptr) { + const u32 null = 0; + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), null)) == -1) { + return rc; + } + cur += rc; + } else { + u32 count = 0; + for (auto *tmp = arr; *tmp != nullptr; ++tmp) { + ++count; + } + + if ((rc = DNSSerializer::CheckToBufferArguments(cur, dst_size, (count + 1) * sizeof(**arr), __LINE__)) == -1) { + return rc; + } + + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), count)) == -1) { + return rc; + } + cur += rc; + + rc = 0; + for (auto i = 0; arr[i] != nullptr; ++i) { + const T addr = *arr[i]; + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), addr)) == -1) { + return rc; + } + + cur += rc; + } + + rc = cur - dst; + } + + return rc; + } + + template<typename T> requires IsInAddr<T> + ssize_t FromBufferImpl(T **&out, const u8 *src, size_t src_size) { + ssize_t rc = -1; + const u8 *cur = src; + + out = nullptr; + + ON_SCOPE_EXIT { + if (rc == -1 && out != nullptr) { + for (auto i = 0; out[i] != nullptr; ++i) { + ams::socket::impl::Free(out[i]); + out[i] = nullptr; + } + + ams::socket::impl::Free(out); + out = nullptr; + } + }; + + if (src == nullptr) { + rc = 0; + return rc; + } else if (src_size == 0) { + rc = 0; + return rc; + } + + u32 count = 0; + if ((rc = DNSSerializer::FromBuffer(count, cur, src_size)) == -1) { + return rc; + } + cur += rc; + + if (count == 0) { + return rc; + } + + out = static_cast<T **>(ams::socket::impl::Alloc((count + 1) * sizeof(T *))); + if (out == nullptr) { + rc = -1; + return rc; + } + std::memset(out, 0, (count + 1) * sizeof(T *)); + + for (u32 i = 0; i < count; ++i) { + out[i] = static_cast<T *>(ams::socket::impl::Alloc(sizeof(T))); + if (out[i] == nullptr) { + rc = -1; + return rc; + } + + u32 s_addr = 0; + if ((rc = DNSSerializer::FromBuffer(s_addr, cur, src_size - (cur - src))) == -1) { + return rc; + } + out[i]->s_addr = s_addr; + cur += rc; + } + out[count] = nullptr; + + rc = cur - src; + return rc; + } + + } + + template<> size_t DNSSerializer::SizeOf(const struct in_addr &in) { + return SizeOfImpl(in); + } + + template<> size_t DNSSerializer::SizeOf(const ams::socket::InAddr &in) { + return SizeOfImpl(in); + } + + template<> size_t DNSSerializer::SizeOf(const struct in_addr **in, u32 &out_count) { + return SizeOfImpl(in, out_count); + } + + template<> size_t DNSSerializer::SizeOf(const ams::socket::InAddr **in, u32 &out_count) { + return SizeOfImpl(in, out_count); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const struct in_addr &in) { + return ToBufferImpl(dst, dst_size, in); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const ams::socket::InAddr &in) { + return ToBufferImpl(dst, dst_size, in); + } + + template<> ssize_t DNSSerializer::FromBuffer(struct in_addr &out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + + template<> ssize_t DNSSerializer::FromBuffer(struct ams::socket::InAddr &out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, struct in_addr **arr) { + return ToBufferImpl(dst, dst_size, arr); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, ams::socket::InAddr **arr) { + return ToBufferImpl(dst, dst_size, arr); + } + + template<> ssize_t DNSSerializer::FromBuffer(struct in_addr **&out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + + template<> ssize_t DNSSerializer::FromBuffer(struct ams::socket::InAddr **&out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_ints.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_ints.cpp new file mode 100644 index 00000000..9dcfa411 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_ints.cpp @@ -0,0 +1,74 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../dnsmitm_debug.hpp" +#include "serializer.hpp" + +namespace ams::mitm::socket::resolver::serializer { + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const u16 &in) { + /* Convert the value. */ + u8 *cur = dst; + const u16 val = InternalHton(in); + + /* Check arguments. */ + ssize_t rc = -1; + if ((rc = CheckToBufferArguments(cur, dst_size, sizeof(u16), __LINE__)) == -1) { + return rc; + } + + std::memcpy(cur, std::addressof(val), sizeof(u16)); + rc += sizeof(u16); + + return rc; + } + + template<> ssize_t DNSSerializer::FromBuffer(u16 &out, const u8 *src, size_t src_size) { + if (src_size < sizeof(u16)) { + return -1; + } + + out = InternalNtoh(*reinterpret_cast<const u16 *>(src)); + return sizeof(u16); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const u32 &in) { + /* Convert the value. */ + u8 *cur = dst; + const u32 val = InternalHton(in); + + /* Check arguments. */ + ssize_t rc = -1; + if ((rc = CheckToBufferArguments(cur, dst_size, sizeof(u32), __LINE__)) == -1) { + return rc; + } + + std::memcpy(cur, std::addressof(val), sizeof(u32)); + rc += sizeof(u32); + + return rc; + } + + template<> ssize_t DNSSerializer::FromBuffer(u32 &out, const u8 *src, size_t src_size) { + if (src_size < sizeof(u32)) { + return -1; + } + + out = InternalNtoh(*reinterpret_cast<const u32 *>(src)); + return sizeof(u32); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_sockaddrin_4.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_sockaddrin_4.cpp new file mode 100644 index 00000000..ee418925 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_sockaddrin_4.cpp @@ -0,0 +1,141 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../dnsmitm_debug.hpp" +#include "../socket_allocator.hpp" +#include "serializer.hpp" + +namespace ams::mitm::socket::resolver::serializer { + + namespace { + + template<typename T> + concept IsSockAddrIn = std::same_as<T, ams::socket::SockAddrIn> || std::same_as<T, struct sockaddr_in>; + + template<typename T> requires IsSockAddrIn<T> + size_t SizeOfImpl(const T &in) { + size_t rc = 0; + rc += sizeof(u16); + rc += sizeof(u16); + rc += DNSSerializer::SizeOf(in.sin_addr); + rc += sizeof(in.sin_zero); + return rc; + } + + template<typename T> requires IsSockAddrIn<T> + ssize_t ToBufferImpl(u8 * const dst, size_t dst_size, const T &in) { + ssize_t rc = -1; + u8 *cur = dst; + + if ((rc = DNSSerializer::CheckToBufferArguments(cur, dst_size, sizeof(in), __LINE__)) == -1) { + return rc; + } + + const u16 sin_family = static_cast<u16>(in.sin_family); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), sin_family)) == -1) { + return rc; + } + cur += rc; + + const u16 sin_port = static_cast<u16>(in.sin_port); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), sin_port)) == -1) { + return rc; + } + cur += rc; + + const u32 s_addr = static_cast<u32>(in.sin_addr.s_addr); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), s_addr)) == -1) { + return rc; + } + cur += rc; + + if (dst_size - (cur - dst) < sizeof(in.sin_zero)) { + rc = -1; + return rc; + } + + std::memcpy(cur, in.sin_zero, sizeof(in.sin_zero)); + cur += sizeof(in.sin_zero); + + rc = cur - dst; + return rc; + } + + template<typename T> requires IsSockAddrIn<T> + ssize_t FromBufferImpl(T &out, const u8 *src, size_t src_size) { + ssize_t rc = -1; + const u8 *cur = src; + + u16 sin_family; + if ((rc = DNSSerializer::FromBuffer(sin_family, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.sin_family = static_cast<decltype(out.sin_family)>(sin_family); + cur += rc; + + u16 sin_port; + if ((rc = DNSSerializer::FromBuffer(sin_port, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.sin_port = static_cast<decltype(out.sin_port)>(sin_port); + cur += rc; + + u32 s_addr; + if ((rc = DNSSerializer::FromBuffer(s_addr, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.sin_addr.s_addr = static_cast<decltype(out.sin_addr.s_addr)>(s_addr); + cur += rc; + + if (src_size - (cur - src) < sizeof(out.sin_zero)) { + rc = -1; + return rc; + } + + std::memcpy(out.sin_zero, cur, sizeof(out.sin_zero)); + cur += sizeof(out.sin_zero); + + rc = cur - src; + return rc; + } + + } + + template<> size_t DNSSerializer::SizeOf(const struct sockaddr_in &in) { + return SizeOfImpl(in); + } + + template<> size_t DNSSerializer::SizeOf(const ams::socket::SockAddrIn &in) { + return SizeOfImpl(in); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const struct sockaddr_in &in) { + return ToBufferImpl(dst, dst_size, in); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const ams::socket::SockAddrIn &in) { + return ToBufferImpl(dst, dst_size, in); + } + + template<> ssize_t DNSSerializer::FromBuffer(struct sockaddr_in &out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + + template<> ssize_t DNSSerializer::FromBuffer(struct ams::socket::SockAddrIn &out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_sockaddrin_6.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_sockaddrin_6.cpp new file mode 100644 index 00000000..d5271037 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_sockaddrin_6.cpp @@ -0,0 +1,133 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../dnsmitm_debug.hpp" +#include "../socket_allocator.hpp" +#include "serializer.hpp" + +namespace ams::mitm::socket::resolver::serializer { + + namespace { + + template<typename T> + concept IsSockAddrIn6 = std::same_as<T, struct sockaddr_in6>; + + template<typename T> requires IsSockAddrIn6<T> + size_t SizeOfImpl(const T &in) { + size_t rc = 0; + rc += sizeof(u16); + rc += sizeof(u16); + rc += sizeof(u32); + rc += DNSSerializer::SizeOf(in.sin6_addr); + rc += sizeof(u32); + return rc; + } + + template<typename T> requires IsSockAddrIn6<T> + ssize_t ToBufferImpl(u8 * const dst, size_t dst_size, const T &in) { + ssize_t rc = -1; + u8 *cur = dst; + + if ((rc = DNSSerializer::CheckToBufferArguments(cur, dst_size, sizeof(in), __LINE__)) == -1) { + return rc; + } + + const u16 sin6_family = static_cast<u16>(in.sin6_family); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), sin6_family)) == -1) { + return rc; + } + cur += rc; + + const u16 sin6_port = static_cast<u16>(in.sin6_port); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), sin6_port)) == -1) { + return rc; + } + cur += rc; + + const u32 sin6_flowinfo = static_cast<u32>(in.sin6_flowinfo); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), sin6_flowinfo)) == -1) { + return rc; + } + cur += rc; + + std::memcpy(cur, std::addressof(in.sin6_addr), sizeof(in.sin6_addr)); + cur += sizeof(in.sin6_addr); + + const u32 sin6_scope_id = static_cast<u32>(in.sin6_scope_id); + if ((rc = DNSSerializer::ToBuffer(cur, dst_size - (cur - dst), sin6_scope_id)) == -1) { + return rc; + } + cur += rc; + + rc = cur - dst; + return rc; + } + + template<typename T> requires IsSockAddrIn6<T> + ssize_t FromBufferImpl(T &out, const u8 *src, size_t src_size) { + ssize_t rc = -1; + const u8 *cur = src; + + u16 sin6_family; + if ((rc = DNSSerializer::FromBuffer(sin6_family, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.sin6_family = static_cast<decltype(out.sin6_family)>(sin6_family); + cur += rc; + + u16 sin6_port; + if ((rc = DNSSerializer::FromBuffer(sin6_port, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.sin6_port = static_cast<decltype(out.sin6_port)>(sin6_port); + cur += rc; + + u32 sin6_flowinfo; + if ((rc = DNSSerializer::FromBuffer(sin6_flowinfo, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.sin6_flowinfo = static_cast<decltype(out.sin6_flowinfo)>(sin6_flowinfo); + cur += rc; + + std::memcpy(std::addressof(out.sin6_addr), cur, sizeof(out.sin6_addr)); + cur += sizeof(out.sin6_addr); + + u32 sin6_scope_id; + if ((rc = DNSSerializer::FromBuffer(sin6_scope_id, cur, src_size - (cur - src))) == -1) { + return rc; + } + out.sin6_scope_id = static_cast<decltype(out.sin6_scope_id)>(sin6_scope_id); + cur += rc; + + rc = cur - src; + return rc; + } + + } + + template<> size_t DNSSerializer::SizeOf(const struct sockaddr_in6 &in) { + return SizeOfImpl(in); + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, const struct sockaddr_in6 &in) { + return ToBufferImpl(dst, dst_size, in); + } + + template<> ssize_t DNSSerializer::FromBuffer(struct sockaddr_in6 &out, const u8 *src, size_t src_size) { + return FromBufferImpl(out, src, src_size); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_string.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_string.cpp new file mode 100644 index 00000000..33ff9899 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/serializer/serializer_impls_string.cpp @@ -0,0 +1,180 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../dnsmitm_debug.hpp" +#include "../socket_allocator.hpp" +#include "serializer.hpp" + +namespace ams::mitm::socket::resolver::serializer { + + template<> size_t DNSSerializer::SizeOf(const char *str) { + if (str == nullptr) { + return sizeof(char); + } + return std::strlen(str) + 1; + } + + template<> size_t DNSSerializer::SizeOf(const char **str, u32 &out_count) { + size_t rc = sizeof(u32); + out_count = 0; + + if (str != nullptr) { + for (const char **cur = str; *cur != nullptr; ++cur) { + ++out_count; + rc += SizeOf(*cur); + } + } + + return rc; + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, char *str) { + ssize_t rc = -1; + u8 *cur = dst; + + if (str == nullptr && dst_size == 0) { + return -1; + } else if (str == nullptr) { + *cur = '\x00'; + return 1; + } else if ((rc = SizeOf(static_cast<const char *>(str))) == 0) { + *cur = '\x00'; + return 1; + } else if (CheckToBufferArguments(cur, dst_size, rc + 1, __LINE__) == -1) { + return -1; + } + + std::memmove(cur, str, rc); + return rc; + } + + template<> ssize_t DNSSerializer::FromBuffer(char *&out, const u8 *src, size_t src_size) { + size_t len = 0; + + if (src == nullptr) { + return 0; + } else if (src_size == 0) { + return 0; + } else if (src_size < (len = SizeOf(reinterpret_cast<const char *>(src)))) { + return 1; + } else if (src[0] == '\x00') { + return 1; + } + + out = static_cast<char *>(ams::socket::impl::Alloc(len)); + if (out == nullptr) { + return -1; + } + + std::memmove(out, src, len); + return len; + } + + template<> ssize_t DNSSerializer::ToBuffer(u8 * const dst, size_t dst_size, char **str) { + ssize_t rc = -1; + u8 *cur = dst; + u32 count = 0; + + if (dst_size == 0) { + return -1; + } + + const size_t total_size = SizeOf(const_cast<const char **>(str), count); + AMS_UNUSED(total_size); + + if ((rc = CheckToBufferArguments(cur, dst_size, sizeof(u32), __LINE__)) == -1) { + return rc; + } else if ((rc = ToBuffer(cur, dst_size, count)) == -1) { + return rc; + } + cur += rc; + dst_size -= rc; + + if (str != nullptr) { + for (char **cur_str = str; *cur_str != nullptr; ++cur_str) { + const auto tmp = ToBuffer(cur, dst_size, *cur_str); + if (tmp == -1) { + return rc; + } + + cur += tmp; + dst_size -= tmp; + rc += tmp; + } + } + + rc = cur - dst; + return rc; + } + + template<> ssize_t DNSSerializer::FromBuffer(char **&out, const u8 *src, size_t src_size) { + ssize_t rc = -1; + const u8 *cur = src; + u32 count = 0; + + out = nullptr; + + ON_SCOPE_EXIT { + if (rc < 0 && out != nullptr) { + u32 i = 0; + for (char *str = *out; str != nullptr; str = out[++i]) { + ams::socket::impl::Free(str); + } + ams::socket::impl::Free(out); + out = nullptr; + } + }; + + if (src == nullptr) { + rc = 0; + return rc; + } else if (src_size == 0) { + rc = 0; + return rc; + } else if ((rc = FromBuffer(count, cur, src_size)) == -1) { + rc = -1; + return rc; + } + cur += rc; + + out = static_cast<char **>(ams::socket::impl::Alloc((count + 1) * sizeof(char *))); + if (out == nullptr) { + rc = -1; + return rc; + } + std::memset(out, 0, (count + 1) * sizeof(char *)); + + u32 i; + for (i = 0; i < count; ++i) { + const size_t len = std::strlen(reinterpret_cast<const char *>(cur)); + out[i] = static_cast<char *>(ams::socket::impl::Alloc(len + 1)); + if (out[i] == nullptr) { + rc = -1; + return rc; + } + + std::memmove(out[i], cur, len + 1); + cur += len + 1; + } + + out[i] = nullptr; + rc = cur - src; + + return rc; + } + + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/sfdnsres_shim.c b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/sfdnsres_shim.c new file mode 100644 index 00000000..cfd3d610 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/sfdnsres_shim.c @@ -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 <http://www.gnu.org/licenses/>. + */ +#include "sfdnsres_shim.h" +#include <stratosphere/sf/sf_mitm_dispatch.h> + +/* Command forwarders. */ +Result sfdnsresGetHostByNameRequestWithOptionsFwd(Service *s, u64 process_id, const void *name, size_t name_size, void *out_hostent, size_t out_hostent_size, u32 *out_size, u32 options_version, const void *option, size_t option_size, u32 num_options, s32 *out_host_error, s32 *out_errno) { + const struct { + u32 options_version; + u32 num_options; + u64 process_id; + } in = { options_version, num_options, process_id }; + struct { + u32 size; + s32 host_error; + s32 errno; + } out; + + Result rc = serviceMitmDispatchInOut(s, 10, in, out, + .buffer_attrs = { + SfBufferAttr_HipcAutoSelect | SfBufferAttr_In, + SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out, + SfBufferAttr_HipcAutoSelect | SfBufferAttr_In + }, + .buffers = { + { name, name_size }, + { out_hostent, out_hostent_size }, + { option, option_size } + }, + .in_send_pid = true, + .override_pid = process_id, + ); + + if (R_SUCCEEDED(rc)) { + if (out_size) *out_size = out.size; + if (out_host_error) *out_host_error = out.host_error; + if (out_errno) *out_errno = out.errno; + } + + return rc; +} + +Result sfdnsresGetAddrInfoRequestWithOptionsFwd(Service *s, u64 process_id, const void *node, size_t node_size, const void *srv, size_t srv_size, const void *hint, size_t hint_size, void *out_ai, size_t out_ai_size, u32 *out_size, s32 *out_rv, u32 options_version, const void *option, size_t option_size, u32 num_options, s32 *out_host_error, s32 *out_errno) { + const struct { + u32 options_version; + u32 num_options; + u64 process_id; + } in = { options_version, num_options, process_id }; + struct { + u32 size; + s32 rv; + s32 host_error; + s32 errno; + } out; + + Result rc = serviceMitmDispatchInOut(s, 12, in, out, + .buffer_attrs = { + SfBufferAttr_HipcMapAlias | SfBufferAttr_In, + SfBufferAttr_HipcMapAlias | SfBufferAttr_In, + SfBufferAttr_HipcMapAlias | SfBufferAttr_In, + SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out, + SfBufferAttr_HipcAutoSelect | SfBufferAttr_In + }, + .buffers = { + { node, node_size }, + { srv, srv_size }, + { hint, hint_size }, + { out_ai, out_ai_size }, + { option, option_size } + }, + .in_send_pid = true, + .override_pid = process_id, + ); + + if (R_SUCCEEDED(rc)) { + if (out_size) *out_size = out.size; + if (out_rv) *out_rv = out.rv; + if (out_host_error) *out_host_error = out.host_error; + if (out_errno) *out_errno = out.errno; + } + + return rc; +} + diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/sfdnsres_shim.h b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/sfdnsres_shim.h new file mode 100644 index 00000000..07f3fa94 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/sfdnsres_shim.h @@ -0,0 +1,21 @@ +/** + * @file sfdnsres_shim.h + * @brief IPC wrapper for dns.mitm. + * @author SciresM + * @copyright libnx Authors + */ +#pragma once +#include <switch.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Command forwarders. */ +Result sfdnsresGetHostByNameRequestWithOptionsFwd(Service *s, u64 process_id, const void *name, size_t name_size, void *out_hostent, size_t out_hostent_size, u32 *out_size, u32 options_version, const void *option, size_t option_size, u32 num_options, s32 *out_host_error, s32 *out_errno); + +Result sfdnsresGetAddrInfoRequestWithOptionsFwd(Service *s, u64 process_id, const void *node, size_t node_size, const void *srv, size_t srv_size, const void *hint, size_t hint_size, void *out_ai, size_t out_ai_size, u32 *out_size, s32 *out_rv, u32 options_version, const void *option, size_t option_size, u32 num_options, s32 *out_host_error, s32 *out_errno); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/socket_allocator.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/socket_allocator.hpp new file mode 100644 index 00000000..1bfd27ea --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/dns_mitm/socket_allocator.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::socket::impl { + + void *Alloc(size_t size); + void *Calloc(size_t num, size_t size); + void Free(void *ptr); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.cpp new file mode 100644 index 00000000..f82a6e80 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.cpp @@ -0,0 +1,383 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../amsmitm_fs_utils.hpp" +#include "../amsmitm_initialization.hpp" +#include "fs_shim.h" +#include "fs_mitm_service.hpp" +#include "fsmitm_boot0storage.hpp" +#include "fsmitm_calibration_binary_storage.hpp" +#include "fsmitm_layered_romfs_storage.hpp" +#include "fsmitm_save_utils.hpp" +#include "fsmitm_readonly_layered_filesystem.hpp" + +namespace ams::mitm::fs { + + using namespace ams::fs; + + namespace { + + constexpr const ams::fs::Path AtmosphereHblWebContentDirPath = fs::MakeConstantPath("/atmosphere/hbl_html/"); + constexpr const char ProgramWebContentDir[] = "/manual_html/"; + + constinit os::SdkMutex g_boot0_detect_lock; + constinit bool g_detected_boot0_kind = false; + constinit bool g_is_boot0_custom_public_key = false; + + constinit fssrv::impl::ProgramIndexMapInfoManager g_program_index_map_info_manager; + + bool IsBoot0CustomPublicKey(::FsStorage &storage) { + if (AMS_UNLIKELY(!g_detected_boot0_kind)) { + std::scoped_lock lk(g_boot0_detect_lock); + + if (AMS_LIKELY(!g_detected_boot0_kind)) { + g_is_boot0_custom_public_key = DetectBoot0CustomPublicKey(storage); + g_detected_boot0_kind = true; + } + } + + return g_is_boot0_custom_public_key; + } + + bool GetSettingsItemBooleanValue(const char *name, const char *key) { + u8 tmp = 0; + AMS_ABORT_UNLESS(settings::fwdbg::GetSettingsItemValue(std::addressof(tmp), sizeof(tmp), name, key) == sizeof(tmp)); + return (tmp != 0); + } + + template<typename... Arguments> + constexpr ALWAYS_INLINE auto MakeSharedFileSystem(Arguments &&... args) { + return sf::CreateSharedObjectEmplaced<ams::fssrv::sf::IFileSystem, ams::fssrv::impl::FileSystemInterfaceAdapter>(std::forward<Arguments>(args)...); + } + + template<typename... Arguments> + constexpr ALWAYS_INLINE auto MakeSharedStorage(Arguments &&... args) { + return sf::CreateSharedObjectEmplaced<ams::fssrv::sf::IStorage, ams::fssrv::impl::StorageInterfaceAdapter>(std::forward<Arguments>(args)...); + } + + Result OpenHblWebContentFileSystem(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> &out, ncm::ProgramId program_id) { + /* Verify eligibility. */ + bool is_hbl; + R_UNLESS(R_SUCCEEDED(ams::pm::info::IsHblProgramId(std::addressof(is_hbl), program_id)), sm::mitm::ResultShouldForwardToSession()); + R_UNLESS(is_hbl, sm::mitm::ResultShouldForwardToSession()); + + /* Hbl html directory must exist. */ + { + FsDir d; + R_UNLESS(R_SUCCEEDED(mitm::fs::OpenSdDirectory(std::addressof(d), AtmosphereHblWebContentDirPath.GetString(), fs::OpenDirectoryMode_Directory)), sm::mitm::ResultShouldForwardToSession()); + fsDirClose(std::addressof(d)); + } + + /* Open the SD card using fs.mitm's session. */ + FsFileSystem sd_fs; + R_TRY(fsOpenSdCardFileSystem(std::addressof(sd_fs))); + const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(std::addressof(sd_fs.s))}; + std::unique_ptr<fs::fsa::IFileSystem> sd_ifs = std::make_unique<fs::RemoteFileSystem>(sd_fs); + + auto subdir_fs = std::make_unique<fssystem::SubDirectoryFileSystem>(std::move(sd_ifs)); + R_TRY(subdir_fs->Initialize(AtmosphereHblWebContentDirPath)); + + out.SetValue(MakeSharedFileSystem(std::make_shared<fs::ReadOnlyFileSystem>(std::move(subdir_fs)), false), target_object_id); + R_SUCCEED(); + } + + Result OpenProgramSpecificWebContentFileSystem(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> &out, ncm::ProgramId program_id, FsFileSystemType filesystem_type, Service *fwd, const fssrv::sf::Path *path, bool with_id) { + /* Directory must exist. */ + R_UNLESS(HasSdManualHtmlContent(program_id), sm::mitm::ResultShouldForwardToSession()); + + /* Open the SD card using fs.mitm's session. */ + FsFileSystem sd_fs; + R_TRY(fsOpenSdCardFileSystem(std::addressof(sd_fs))); + const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(std::addressof(sd_fs.s))}; + std::unique_ptr<fs::fsa::IFileSystem> sd_ifs = std::make_unique<fs::RemoteFileSystem>(sd_fs); + + /* Format the subdirectory path. */ + char program_web_content_raw_path[0x100]; + FormatAtmosphereSdPath(program_web_content_raw_path, sizeof(program_web_content_raw_path), program_id, ProgramWebContentDir); + + ams::fs::Path program_web_content_path; + R_TRY(program_web_content_path.SetShallowBuffer(program_web_content_raw_path)); + + /* Make a new filesystem. */ + { + auto subdir_fs = std::make_unique<fssystem::SubDirectoryFileSystem>(std::move(sd_ifs)); + R_TRY(subdir_fs->Initialize(program_web_content_path)); + + std::shared_ptr<fs::fsa::IFileSystem> new_fs = nullptr; + + /* Try to open the existing fs. */ + FsFileSystem base_fs; + bool opened_base_fs = false; + if (with_id) { + opened_base_fs = R_SUCCEEDED(fsOpenFileSystemWithIdFwd(fwd, std::addressof(base_fs), static_cast<u64>(program_id), filesystem_type, path->str)); + } else { + opened_base_fs = R_SUCCEEDED(fsOpenFileSystemWithPatchFwd(fwd, std::addressof(base_fs), static_cast<u64>(program_id), filesystem_type)); + } + + if (opened_base_fs) { + /* Create a layered adapter. */ + new_fs = std::make_shared<ReadOnlyLayeredFileSystem>(std::move(subdir_fs), std::make_unique<fs::RemoteFileSystem>(base_fs)); + } else { + /* Without an existing FS, just make a read only adapter to the subdirectory. */ + new_fs = std::make_shared<fs::ReadOnlyFileSystem>(std::move(subdir_fs)); + } + + out.SetValue(MakeSharedFileSystem(std::move(new_fs), false), target_object_id); + } + + R_SUCCEED(); + } + + Result OpenWebContentFileSystem(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> &out, ncm::ProgramId client_program_id, ncm::ProgramId program_id, FsFileSystemType filesystem_type, Service *fwd, const fssrv::sf::Path *path, bool with_id, bool try_program_specific) { + /* Check first that we're a web applet opening web content. */ + R_UNLESS(ncm::IsWebAppletId(client_program_id), sm::mitm::ResultShouldForwardToSession()); + R_UNLESS(filesystem_type == FsFileSystemType_ContentManual, sm::mitm::ResultShouldForwardToSession()); + + /* Try to mount the HBL web filesystem. If this succeeds then we're done. */ + R_SUCCEED_IF(R_SUCCEEDED(OpenHblWebContentFileSystem(out, program_id))); + + /* If program specific override shouldn't be attempted, fall back. */ + R_UNLESS(try_program_specific, sm::mitm::ResultShouldForwardToSession()); + + /* If we're not opening a HBL filesystem, just try to open a generic one. */ + R_RETURN(OpenProgramSpecificWebContentFileSystem(out, program_id, filesystem_type, fwd, path, with_id)); + } + + } + + bool HasSdManualHtmlContent(ncm::ProgramId program_id) { + /* Directory must exist. */ + FsDir d; + if (R_SUCCEEDED(OpenAtmosphereSdDirectory(std::addressof(d), program_id, ProgramWebContentDir, fs::OpenDirectoryMode_Directory))) { + ::fsDirClose(std::addressof(d)); + return true; + } else { + return false; + } + } + + Result FsMitmService::OpenFileSystemWithPatch(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out, ncm::ProgramId program_id, u32 _filesystem_type) { + R_RETURN(OpenWebContentFileSystem(out, m_client_info.program_id, program_id, static_cast<FsFileSystemType>(_filesystem_type), m_forward_service.get(), nullptr, false, m_client_info.override_status.IsProgramSpecific())); + } + + Result FsMitmService::OpenFileSystemWithId(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out, const fssrv::sf::Path &path, ncm::ProgramId program_id, u32 _filesystem_type) { + R_RETURN(OpenWebContentFileSystem(out, m_client_info.program_id, program_id, static_cast<FsFileSystemType>(_filesystem_type), m_forward_service.get(), std::addressof(path), true, m_client_info.override_status.IsProgramSpecific())); + } + + Result FsMitmService::OpenSdCardFileSystem(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out) { + /* We only care about redirecting this for NS/emummc. */ + R_UNLESS(m_client_info.program_id == ncm::SystemProgramId::Ns, sm::mitm::ResultShouldForwardToSession()); + R_UNLESS(emummc::IsActive(), sm::mitm::ResultShouldForwardToSession()); + + /* Create a new SD card filesystem. */ + FsFileSystem sd_fs; + R_TRY(fsOpenSdCardFileSystemFwd(m_forward_service.get(), std::addressof(sd_fs))); + const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(std::addressof(sd_fs.s))}; + + /* Return output filesystem. */ + auto redir_fs = std::make_shared<fssystem::DirectoryRedirectionFileSystem>(std::make_unique<RemoteFileSystem>(sd_fs)); + R_TRY(redir_fs->InitializeWithFixedPath("/Nintendo", emummc::GetNintendoDirPath())); + + out.SetValue(MakeSharedFileSystem(std::move(redir_fs), false), target_object_id); + R_SUCCEED(); + } + + Result FsMitmService::OpenSaveDataFileSystem(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out, u8 _space_id, const fs::SaveDataAttribute &attribute) { + /* We only want to intercept saves for games, right now. */ + const bool is_game_or_hbl = m_client_info.override_status.IsHbl() || ncm::IsApplicationId(m_client_info.program_id); + R_UNLESS(is_game_or_hbl, sm::mitm::ResultShouldForwardToSession()); + + /* Only redirect if the appropriate system setting is set. */ + R_UNLESS(GetSettingsItemBooleanValue("atmosphere", "fsmitm_redirect_saves_to_sd"), sm::mitm::ResultShouldForwardToSession()); + + /* Only redirect if the specific title being accessed has a redirect save flag. */ + R_UNLESS(cfg::HasContentSpecificFlag(m_client_info.program_id, "redirect_save"), sm::mitm::ResultShouldForwardToSession()); + + /* Only redirect account savedata. */ + R_UNLESS(attribute.type == fs::SaveDataType::Account, sm::mitm::ResultShouldForwardToSession()); + + /* Get enum type for space id. */ + auto space_id = static_cast<FsSaveDataSpaceId>(_space_id); + + /* Verify we can open the save. */ + static_assert(sizeof(fs::SaveDataAttribute) == sizeof(::FsSaveDataAttribute)); + FsFileSystem save_fs; + R_UNLESS(R_SUCCEEDED(fsOpenSaveDataFileSystemFwd(m_forward_service.get(), std::addressof(save_fs), space_id, reinterpret_cast<const FsSaveDataAttribute *>(std::addressof(attribute)))), sm::mitm::ResultShouldForwardToSession()); + std::unique_ptr<fs::fsa::IFileSystem> save_ifs = std::make_unique<fs::RemoteFileSystem>(save_fs); + + /* Mount the SD card using fs.mitm's session. */ + FsFileSystem sd_fs; + R_TRY(fsOpenSdCardFileSystem(std::addressof(sd_fs))); + const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(std::addressof(sd_fs.s))}; + std::shared_ptr<fs::fsa::IFileSystem> sd_ifs = std::make_shared<fs::RemoteFileSystem>(sd_fs); + + /* Verify that we can open the save directory, and that it exists. */ + const ncm::ProgramId application_id = attribute.program_id == ncm::InvalidProgramId ? m_client_info.program_id : attribute.program_id; + + char save_dir_raw_path[0x100]; + R_TRY(mitm::fs::SaveUtil::GetDirectorySaveDataPath(save_dir_raw_path, sizeof(save_dir_raw_path), application_id, space_id, attribute)); + + ams::fs::Path save_dir_path; + R_TRY(save_dir_path.SetShallowBuffer(save_dir_raw_path)); + + /* Check if this is the first time we're making the save. */ + bool is_new_save = false; + { + fs::DirectoryEntryType ent; + R_TRY_CATCH(sd_ifs->GetEntryType(std::addressof(ent), save_dir_path)) { + R_CATCH(fs::ResultPathNotFound) { is_new_save = true; } + R_CATCH_ALL() { /* ... */ } + } R_END_TRY_CATCH; + } + + /* Ensure the directory exists. */ + R_TRY(fssystem::EnsureDirectory(sd_ifs.get(), save_dir_path)); + + /* Create directory savedata filesystem. */ + auto subdir_fs = std::make_unique<fssystem::SubDirectoryFileSystem>(sd_ifs); + R_TRY(subdir_fs->Initialize(save_dir_path)); + + std::shared_ptr<fssystem::DirectorySaveDataFileSystem> dirsave_ifs = std::make_shared<fssystem::DirectorySaveDataFileSystem>(std::move(subdir_fs)); + + /* Ensure correct directory savedata filesystem state. */ + R_TRY(dirsave_ifs->Initialize(true, true, true)); + + /* If it's the first time we're making the save, copy existing savedata over. */ + if (is_new_save) { + /* TODO: Check error? */ + fs::DirectoryEntry work_entry; + constexpr const fs::Path root_path = fs::MakeConstantPath("/"); + + u8 savedata_copy_buffer[2_KB]; + fssystem::CopyDirectoryRecursively(dirsave_ifs.get(), save_ifs.get(), root_path, root_path, std::addressof(work_entry), savedata_copy_buffer, sizeof(savedata_copy_buffer)); + } + + /* Set output. */ + out.SetValue(MakeSharedFileSystem(std::move(dirsave_ifs), false), target_object_id); + R_SUCCEED(); + } + + Result FsMitmService::OpenBisStorage(sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out, u32 _bis_partition_id) { + const ::FsBisPartitionId bis_partition_id = static_cast<::FsBisPartitionId>(_bis_partition_id); + + /* Try to open a storage for the partition. */ + FsStorage bis_storage; + R_TRY(fsOpenBisStorageFwd(m_forward_service.get(), std::addressof(bis_storage), bis_partition_id)); + const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(std::addressof(bis_storage.s))}; + + const bool is_sysmodule = ncm::IsSystemProgramId(m_client_info.program_id); + const bool is_hbl = m_client_info.override_status.IsHbl(); + const bool can_write_bis = is_sysmodule || (is_hbl && GetSettingsItemBooleanValue("atmosphere", "enable_hbl_bis_write")); + + /* Allow HBL to write to boot1 (safe firm) + package2. */ + /* This is needed to not break compatibility with ChoiDujourNX, which does not check for write access before beginning an update. */ + /* TODO: get fixed so that this can be turned off without causing bricks :/ */ + const bool is_package2 = (FsBisPartitionId_BootConfigAndPackage2Part1 <= bis_partition_id && bis_partition_id <= FsBisPartitionId_BootConfigAndPackage2Part6); + const bool is_boot1 = bis_partition_id == FsBisPartitionId_BootPartition2Root; + const bool can_write_bis_for_choi_support = is_hbl && (is_package2 || is_boot1); + + /* Set output storage. */ + if (bis_partition_id == FsBisPartitionId_BootPartition1Root) { + if (IsBoot0CustomPublicKey(bis_storage)) { + out.SetValue(MakeSharedStorage(std::make_shared<CustomPublicKeyBoot0Storage>(bis_storage, m_client_info, spl::GetSocType())), target_object_id); + } else { + out.SetValue(MakeSharedStorage(std::make_shared<Boot0Storage>(bis_storage, m_client_info)), target_object_id); + } + } else if (bis_partition_id == FsBisPartitionId_CalibrationBinary) { + out.SetValue(MakeSharedStorage(std::make_shared<CalibrationBinaryStorage>(bis_storage, m_client_info)), target_object_id); + } else { + if (can_write_bis || can_write_bis_for_choi_support) { + /* We can write, so create a writable storage. */ + out.SetValue(MakeSharedStorage(std::make_shared<RemoteStorage>(bis_storage)), target_object_id); + } else { + /* We can only read, so create a readable storage. */ + std::unique_ptr<ams::fs::IStorage> unique_bis = std::make_unique<RemoteStorage>(bis_storage); + out.SetValue(MakeSharedStorage(std::make_shared<ReadOnlyStorageAdapter>(std::move(unique_bis))), target_object_id); + } + } + + R_SUCCEED(); + } + + Result FsMitmService::OpenDataStorageByCurrentProcess(sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out) { + /* Only mitm if we should override contents for the current process. */ + R_UNLESS(m_client_info.override_status.IsProgramSpecific(), sm::mitm::ResultShouldForwardToSession()); + + /* Only mitm if there is actually an override romfs. */ + R_UNLESS(mitm::fs::HasSdRomfsContent(m_client_info.program_id), sm::mitm::ResultShouldForwardToSession()); + + /* Try to open the process romfs. */ + FsStorage data_storage; + R_TRY(fsOpenDataStorageByCurrentProcessFwd(m_forward_service.get(), std::addressof(data_storage))); + const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(std::addressof(data_storage.s))}; + + /* Get a layered storage for the process romfs. */ + out.SetValue(MakeSharedStorage(GetLayeredRomfsStorage(m_client_info.program_id, data_storage, true)), target_object_id); + R_SUCCEED(); + } + + Result FsMitmService::OpenDataStorageByDataId(sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out, ncm::DataId _data_id, u8 storage_id) { + /* Only mitm if we should override contents for the current process. */ + R_UNLESS(m_client_info.override_status.IsProgramSpecific(), sm::mitm::ResultShouldForwardToSession()); + + /* TODO: Decide how to handle DataId vs ProgramId for this API. */ + const ncm::ProgramId data_id = {_data_id.value}; + + /* Only mitm if there is actually an override romfs. */ + R_UNLESS(mitm::fs::HasSdRomfsContent(data_id), sm::mitm::ResultShouldForwardToSession()); + + /* Try to open the data id. */ + FsStorage data_storage; + R_TRY(fsOpenDataStorageByDataIdFwd(m_forward_service.get(), std::addressof(data_storage), static_cast<u64>(data_id), static_cast<NcmStorageId>(storage_id))); + const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(std::addressof(data_storage.s))}; + + /* Get a layered storage for the data id. */ + out.SetValue(MakeSharedStorage(GetLayeredRomfsStorage(data_id, data_storage, false)), target_object_id); + R_SUCCEED(); + } + + Result FsMitmService::OpenDataStorageWithProgramIndex(sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out, u8 program_index) { + /* Only mitm if we should override contents for the current process. */ + R_UNLESS(m_client_info.override_status.IsProgramSpecific(), sm::mitm::ResultShouldForwardToSession()); + + /* Get the relevant program id. */ + const ncm::ProgramId program_id = g_program_index_map_info_manager.GetProgramId(m_client_info.program_id, program_index); + + /* If we don't know about the program or don't have content, forward. */ + R_UNLESS(program_id != ncm::InvalidProgramId, sm::mitm::ResultShouldForwardToSession()); + R_UNLESS(mitm::fs::HasSdRomfsContent(program_id), sm::mitm::ResultShouldForwardToSession()); + + /* Try to open the process romfs. */ + FsStorage data_storage; + R_TRY(fsOpenDataStorageWithProgramIndexFwd(m_forward_service.get(), std::addressof(data_storage), program_index)); + const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(std::addressof(data_storage.s))}; + + /* Get a layered storage for the process romfs. */ + out.SetValue(MakeSharedStorage(GetLayeredRomfsStorage(program_id, data_storage, true)), target_object_id); + R_SUCCEED(); + } + + Result FsMitmService::RegisterProgramIndexMapInfo(const sf::InBuffer &info_buffer, s32 info_count) { + /* Try to register with FS. */ + R_TRY(fsRegisterProgramIndexMapInfoFwd(m_forward_service.get(), info_buffer.GetPointer(), info_buffer.GetSize(), info_count)); + + /* Register with ourselves. */ + R_ABORT_UNLESS(g_program_index_map_info_manager.Reset(reinterpret_cast<const fs::ProgramIndexMapInfo *>(info_buffer.GetPointer()), info_count)); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.hpp new file mode 100644 index 00000000..71e87c10 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fs_mitm_service.hpp @@ -0,0 +1,92 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include <stratosphere/fssrv/fssrv_interface_adapters.hpp> + +#define AMS_FS_MITM_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, OpenFileSystemWithPatch, (sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out, ncm::ProgramId program_id, u32 _filesystem_type), (out, program_id, _filesystem_type), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, OpenFileSystemWithId, (sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out, const fssrv::sf::Path &path, ncm::ProgramId program_id, u32 _filesystem_type), (out, path, program_id, _filesystem_type), hos::Version_2_0_0) \ + AMS_SF_METHOD_INFO(C, H, 18, Result, OpenSdCardFileSystem, (sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 51, Result, OpenSaveDataFileSystem, (sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out, u8 space_id, const ams::fs::SaveDataAttribute &attribute), (out, space_id, attribute)) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, OpenBisStorage, (sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out, u32 bis_partition_id), (out, bis_partition_id)) \ + AMS_SF_METHOD_INFO(C, H, 200, Result, OpenDataStorageByCurrentProcess, (sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 202, Result, OpenDataStorageByDataId, (sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out, ncm::DataId data_id, u8 storage_id), (out, data_id, storage_id)) \ + AMS_SF_METHOD_INFO(C, H, 205, Result, OpenDataStorageWithProgramIndex, (sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out, u8 program_index), (out, program_index), hos::Version_7_0_0) \ + AMS_SF_METHOD_INFO(C, H, 810, Result, RegisterProgramIndexMapInfo, (const sf::InBuffer &info_buffer, s32 info_count), (info_buffer, info_count), hos::Version_7_0_0) + + +AMS_SF_DEFINE_MITM_INTERFACE(ams::mitm::fs, IFsMitmInterface, AMS_FS_MITM_INTERFACE_INFO, 0x7DF34ED2) + +namespace ams::mitm::fs { + + class FsMitmService : public sf::MitmServiceImplBase { + public: + using MitmServiceImplBase::MitmServiceImplBase; + public: + static constexpr ALWAYS_INLINE bool ShouldMitmProgramId(const ncm::ProgramId program_id) { + /* We want to mitm everything that isn't a system-module. */ + if (!ncm::IsSystemProgramId(program_id)) { + return true; + } + + /* We want to mitm ns, to intercept SD card requests and program index map info registration. */ + if (program_id == ncm::SystemProgramId::Ns) { + return true; + } + + /* We want to mitm settings, to intercept CAL0. */ + if (program_id == ncm::SystemProgramId::Settings) { + return true; + } + + /* We want to mitm sdb, to support sd-romfs redirection of common system archives (like system font, etc). */ + /* NOTE: In 16.0.0+, this was moved to glue. */ + if (program_id == ncm::SystemProgramId::Sdb || program_id == ncm::SystemProgramId::Glue) { + return true; + } + + return false; + } + + static bool ShouldMitm(const sm::MitmProcessInfo &client_info) { + static std::atomic_bool has_launched_qlaunch = false; + + /* TODO: intercepting everything seems to cause issues with sleep mode, for some reason. */ + /* Figure out why, and address it. */ + /* TODO: This may be because pre-rewrite code really mismanaged domain objects in a way that would cause bad things. */ + /* Need to verify if this is fixed now. */ + if (client_info.program_id == ncm::SystemAppletId::Qlaunch || client_info.program_id == ncm::SystemAppletId::MaintenanceMenu) { + has_launched_qlaunch = true; + } + + return has_launched_qlaunch || ShouldMitmProgramId(client_info.program_id); + } + public: + /* Overridden commands. */ + Result OpenFileSystemWithPatch(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out, ncm::ProgramId program_id, u32 _filesystem_type); + Result OpenFileSystemWithId(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out, const fssrv::sf::Path &path, ncm::ProgramId program_id, u32 _filesystem_type); + Result OpenSdCardFileSystem(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out); + Result OpenSaveDataFileSystem(sf::Out<sf::SharedPointer<ams::fssrv::sf::IFileSystem>> out, u8 space_id, const ams::fs::SaveDataAttribute &attribute); + Result OpenBisStorage(sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out, u32 bis_partition_id); + Result OpenDataStorageByCurrentProcess(sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out); + Result OpenDataStorageByDataId(sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out, ncm::DataId data_id, u8 storage_id); + Result OpenDataStorageWithProgramIndex(sf::Out<sf::SharedPointer<ams::fssrv::sf::IStorage>> out, u8 program_index); + Result RegisterProgramIndexMapInfo(const sf::InBuffer &info_buffer, s32 info_count); + }; + static_assert(IsIFsMitmInterface<FsMitmService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fs_shim.c b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fs_shim.c new file mode 100644 index 00000000..7f44cb5d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fs_shim.c @@ -0,0 +1,105 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include "fs_shim.h" + +/* Missing fsp-srv commands. */ +static Result _fsOpenSession(Service *s, Service* out, u32 cmd_id) { + return serviceDispatch(s, cmd_id, + .out_num_objects = 1, + .out_objects = out, + ); +} + +Result fsOpenSdCardFileSystemFwd(Service* s, FsFileSystem* out) { + return _fsOpenSession(s, &out->s, 18); +} + +Result fsOpenBisStorageFwd(Service* s, FsStorage* out, FsBisPartitionId partition_id) { + const u32 tmp = partition_id; + return serviceDispatchIn(s, 12, tmp, + .out_num_objects = 1, + .out_objects = &out->s, + ); +} + +Result fsOpenDataStorageByCurrentProcessFwd(Service* s, FsStorage* out) { + return _fsOpenSession(s, &out->s, 200); +} + +Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorage* out, u64 data_id, NcmStorageId storage_id) { + const struct { + u8 storage_id; + u64 data_id; + } in = { storage_id, data_id }; + + return serviceDispatchIn(s, 202, in, + .out_num_objects = 1, + .out_objects = &out->s, + ); +} + +Result fsOpenDataStorageWithProgramIndexFwd(Service* s, FsStorage* out, u8 program_index) { + return serviceDispatchIn(s, 205, program_index, + .out_num_objects = 1, + .out_objects = &out->s, + ); +} + +Result fsRegisterProgramIndexMapInfoFwd(Service* s, const void *buf, size_t buf_size, s32 count) { + return serviceDispatchIn(s, 810, count, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, + .buffers = { { buf, buf_size } }, + ); +} + +Result fsOpenSaveDataFileSystemFwd(Service* s, FsFileSystem* out, FsSaveDataSpaceId save_data_space_id, const FsSaveDataAttribute *attr) { + const struct { + u8 save_data_space_id; + u8 pad[7]; + FsSaveDataAttribute attr; + } in = { (u8)save_data_space_id, {0}, *attr }; + + return serviceDispatchIn(s, 51, in, + .out_num_objects = 1, + .out_objects = &out->s, + ); +} + +Result fsOpenFileSystemWithPatchFwd(Service* s, FsFileSystem* out, u64 id, FsFileSystemType fsType) { + const struct { + u32 fsType; + u64 id; + } in = { fsType, id }; + + return serviceDispatchIn(s, 7, in, + .out_num_objects = 1, + .out_objects = &out->s + ); +} + +Result fsOpenFileSystemWithIdFwd(Service* s, FsFileSystem* out, u64 id, FsFileSystemType fsType, const char* contentPath) { + const struct { + u32 fsType; + u64 id; + } in = { fsType, id }; + + return serviceDispatchIn(s, 8, in, + .buffer_attrs = { SfBufferAttr_HipcPointer | SfBufferAttr_In }, + .buffers = { { contentPath, FS_MAX_PATH } }, + .out_num_objects = 1, + .out_objects = &out->s + ); +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fs_shim.h b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fs_shim.h new file mode 100644 index 00000000..60743b80 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fs_shim.h @@ -0,0 +1,31 @@ +/** + * @file fs_shim.h + * @brief Filesystem Services (fs) IPC wrapper for fs.mitm. + * @author SciresM + * @copyright libnx Authors + */ +#pragma once +#include <switch.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Missing fsp-srv commands. */ +Result fsOpenSdCardFileSystemFwd(Service* s, FsFileSystem* out); +Result fsOpenBisStorageFwd(Service* s, FsStorage* out, FsBisPartitionId partition_id); +Result fsOpenDataStorageByCurrentProcessFwd(Service* s, FsStorage* out); +Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorage* out, u64 data_id, NcmStorageId storage_id); +Result fsOpenDataStorageWithProgramIndexFwd(Service* s, FsStorage* out, u8 program_index); + +Result fsRegisterProgramIndexMapInfoFwd(Service* s, const void *buf, size_t buf_size, s32 count); + +Result fsOpenSaveDataFileSystemFwd(Service* s, FsFileSystem* out, FsSaveDataSpaceId save_data_space_id, const FsSaveDataAttribute *attr); + +Result fsOpenFileSystemWithPatchFwd(Service* s, FsFileSystem* out, u64 id, FsFileSystemType fsType); +Result fsOpenFileSystemWithIdFwd(Service* s, FsFileSystem* out, u64 id, FsFileSystemType fsType, const char* contentPath); + + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_boot0storage.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_boot0storage.cpp new file mode 100644 index 00000000..06d91ea5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_boot0storage.cpp @@ -0,0 +1,267 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsmitm_boot0storage.hpp" + +namespace ams::mitm::fs { + + using namespace ams::fs; + + namespace { + + constinit os::SdkMutex g_boot0_access_mutex; + constinit bool g_custom_public_key = false; + constinit u8 g_boot0_bct_buffer[Boot0Storage::BctEndOffset]; + + /* Recognize special public key (https://gist.github.com/SciresM/16b63ac1d80494522bdba2c57995257c). */ + /* P = 19 */ + /* Q = 1696986749729493925354392349339746171297507422986462747526968361144447230710192316397327889522451749459854070558277878297255552508603806832852079596337539247651161831569525505882103311631577368514276343192042634740927726070847704397913856975832811679847928433261678072951551065705680482548543833651752439700272736498378724153330763357721354498194000536297732323628263256733931353143625854828275237159155585342783077681713929284136658773985266864804093157854331138230313706015557050002740810464618031715670281442110238274404626065924786185264268216336867948322976979393032640085259926883014490947373494538254895109731 */ + /* N = 0xFF696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696959 */ + /* E = 0x10001 */ + /* D = 6512128715229088976470211610075969347035078304643231077895577077900787352712063823560162578441773733649014439616165727455431015055675770987914713980812453585413988983206576233689754710500864883529402371292948326392791238474661859182717295176679567362482790015587820446999760239570255254879359445627372805817473978644067558931078225451477635089763009580492462097185005355990612929951162366081631888011031830742459571000203341001926135389508196521518687349554188686396554248868403128728646457247197407637887195043486221295751496667162366700477934591694110831002874992896076061627516220934290742867397720103040314639313 */ + + constexpr const u8 CustomPublicKey[0x100] = { + 0x59, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0xFF, + }; + + } + + bool Boot0Storage::CanModifyBctPublicKey() { + if (exosphere::IsRcmBugPatched()) { + /* RCM bug patched. */ + /* Only allow NS to update the BCT pubks. */ + /* AutoRCM on a patched unit will cause a brick, so homebrew should NOT be allowed to write. */ + return m_client_info.program_id == ncm::SystemProgramId::Ns; + } else { + /* RCM bug unpatched. */ + /* Allow homebrew but not NS to update the BCT pubks. */ + return m_client_info.override_status.IsHbl(); + } + } + + Result Boot0Storage::Read(s64 offset, void *_buffer, size_t size) { + std::scoped_lock lk{g_boot0_access_mutex}; + AMS_ABORT_UNLESS(!g_custom_public_key); + + /* Check if we have nothing to do. */ + R_SUCCEED_IF(size == 0); + + R_RETURN(Base::Read(offset, _buffer, size)); + } + + Result Boot0Storage::Write(s64 offset, const void *_buffer, size_t size) { + std::scoped_lock lk{g_boot0_access_mutex}; + AMS_ABORT_UNLESS(!g_custom_public_key); + + const u8 *buffer = static_cast<const u8 *>(_buffer); + + /* Check if we have nothing to do. */ + R_SUCCEED_IF(size == 0); + + /* Protect the EKS region from writes. */ + if (offset <= EksStart) { + if (offset + size < EksStart) { + /* Fall through, no need to do anything here. */ + } else { + if (offset + size > EksEnd) { + /* Perform portion of write falling past end of keyblobs. */ + const s64 diff = EksEnd - offset; + R_TRY(Base::Write(EksEnd, buffer + diff, size - diff)); + } + /* Adjust size to avoid writing end of data. */ + size = EksStart - offset; + } + } else if (offset < EksEnd) { + /* Ignore writes falling strictly within the region. */ + R_SUCCEED_IF(offset + size <= EksEnd); + + /* Only write past the end of the region. */ + const s64 diff = EksEnd - offset; + buffer += diff; + size -= diff; + offset = EksEnd; + } + + /* If we have nothing to write, succeed immediately. */ + R_SUCCEED_IF(size == 0); + + /* We want to protect AutoRCM from NS on ipatched units. If we can modify bct pubks or we're not touching any of them, proceed. */ + if (this->CanModifyBctPublicKey() || offset >= BctEndOffset || (util::AlignUp(offset, BctSize) >= BctEndOffset && (offset % BctSize) >= BctPubkEnd)) { + R_RETURN(Base::Write(offset, buffer, size)); + } + + /* Handle any data written past the end of the pubk region. */ + if (offset + size > BctEndOffset) { + const u64 diff = BctEndOffset - offset; + R_TRY(Base::Write(BctEndOffset, buffer + diff, size - diff)); + size = diff; + } + + /* Read in the current BCT region. */ + R_TRY(Base::Read(0, g_boot0_bct_buffer, BctEndOffset)); + + /* Update the bct buffer. */ + for (u64 cur_offset = offset; cur_offset < BctEndOffset && cur_offset < offset + size; cur_offset++) { + const u64 cur_bct_relative_ofs = cur_offset % BctSize; + if (cur_bct_relative_ofs < BctPubkStart || BctPubkEnd <= cur_bct_relative_ofs) { + g_boot0_bct_buffer[cur_offset] = buffer[cur_offset - offset]; + } + } + + R_RETURN(Base::Write(0, g_boot0_bct_buffer, BctEndOffset)); + } + + CustomPublicKeyBoot0Storage::CustomPublicKeyBoot0Storage(FsStorage &s, const sm::MitmProcessInfo &c, spl::SocType soc) : Base(s), m_client_info(c), m_soc_type(soc) { + std::scoped_lock lk{g_boot0_access_mutex}; + + /* We're custom public key. */ + g_custom_public_key = true; + } + + Result CustomPublicKeyBoot0Storage::Read(s64 offset, void *_buffer, size_t size) { + std::scoped_lock lk{g_boot0_access_mutex}; + AMS_ABORT_UNLESS(g_custom_public_key); + + /* Check if we have nothing to do. */ + R_SUCCEED_IF(size == 0); + + u8 *buffer = static_cast<u8 *>(_buffer); + + /* Check if we're reading the first BCTs for NS. */ + /* If we are, we want to lie about the contents of BCT0/1 so that they validate. */ + if (offset < 0x8000 && m_client_info.program_id == ncm::SystemProgramId::Ns) { + R_TRY(Base::Read(0, g_boot0_bct_buffer, Boot0Storage::BctEndOffset)); + + /* Determine the readable size. */ + const size_t readable_bct01_size = std::min<size_t>(0x8000, offset + size) - offset; + std::memcpy(buffer, g_boot0_bct_buffer + 0x8000 + offset, readable_bct01_size); + + /* Advance. */ + buffer += readable_bct01_size; + offset += readable_bct01_size; + size -= readable_bct01_size; + } + + /* Check if we have nothing to do. */ + R_SUCCEED_IF(size == 0); + + /* Perform whatever remains of the read. */ + R_RETURN(Base::Read(offset, buffer, size)); + } + + Result CustomPublicKeyBoot0Storage::Write(s64 offset, const void *_buffer, size_t size) { + std::scoped_lock lk{g_boot0_access_mutex}; + AMS_ABORT_UNLESS(g_custom_public_key); + + const u8 *buffer = static_cast<const u8 *>(_buffer); + + /* Check if we have nothing to do. */ + R_SUCCEED_IF(size == 0); + + /* Drop writes to the first BCTs. */ + if (offset < 0x8000) { + /* Determine the writable size. */ + const size_t writable_bct01_size = std::min<size_t>(0x8000, offset + size) - offset; + + /* Advance. */ + buffer += writable_bct01_size; + offset += writable_bct01_size; + size -= writable_bct01_size; + } + + /* Check if we have nothing to do. */ + R_SUCCEED_IF(size == 0); + + /* Similarly, we want to drop writes to the end of boot0, where custom bootloader lives. */ + R_SUCCEED_IF(offset >= 0x380000); + if (offset + size >= 0x380000) { + size = 0x380000 - offset; + } + + /* On erista, we want to protect the EKS region. */ + if (m_soc_type == spl::SocType_Erista) { + if (offset <= Boot0Storage::EksStart) { + if (offset + size < Boot0Storage::EksStart) { + /* Fall through, no need to do anything here. */ + } else { + if (offset + size > Boot0Storage::EksEnd) { + /* Perform portion of write falling past end of keyblobs. */ + const s64 diff = Boot0Storage::EksEnd - offset; + R_TRY(Base::Write(Boot0Storage::EksEnd, buffer + diff, size - diff)); + } + /* Adjust size to avoid writing end of data. */ + size = Boot0Storage::EksStart - offset; + } + } else if (offset < Boot0Storage::EksEnd) { + /* Ignore writes falling strictly within the region. */ + R_SUCCEED_IF(offset + size <= Boot0Storage::EksEnd); + + /* Only write past the end of the region. */ + const s64 diff = Boot0Storage::EksEnd - offset; + buffer += diff; + size -= diff; + offset = Boot0Storage::EksEnd; + } + } + + /* If we have nothing to write, succeed immediately. */ + R_SUCCEED_IF(size == 0); + + /* Perform whatever remains of the write. */ + R_RETURN(Base::Write(offset, buffer, size)); + } + + bool DetectBoot0CustomPublicKey(::FsStorage &storage) { + /* Determine public key offset. */ + const size_t bct_pubk_offset = spl::GetSocType() == spl::SocType_Mariko ? 0x10 : 0x210; + + u8 work_buffer[0x400]; + + /* Read BCT-Normal-Main. */ + R_ABORT_UNLESS(::fsStorageRead(std::addressof(storage), 0, work_buffer, sizeof(work_buffer))); + + /* Check for custom public key. */ + if (std::memcmp(work_buffer + bct_pubk_offset, CustomPublicKey, sizeof(CustomPublicKey)) != 0) { + return false; + } + + /* Read BCT-Safe-Main. */ + R_ABORT_UNLESS(::fsStorageRead(std::addressof(storage), 0x4000, work_buffer, sizeof(work_buffer))); + + /* Check for custom public key. */ + if (std::memcmp(work_buffer + bct_pubk_offset, CustomPublicKey, sizeof(CustomPublicKey)) != 0) { + return false; + } + + return true; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_boot0storage.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_boot0storage.hpp new file mode 100644 index 00000000..dc85d527 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_boot0storage.hpp @@ -0,0 +1,164 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> + +namespace ams::mitm::fs { + + template<class Base, size_t SectorSize> + class SectoredStorageAdapter : public Base { + static_assert(std::is_base_of<ams::fs::IStorage, Base>::value); + private: + u8 m_sector_buf[SectorSize]; + public: + /* Inherit constructors. */ + using Base::Base; + public: + virtual Result Read(s64 offset, void *_buffer, size_t size) override { + u8 *buffer = static_cast<u8 *>(_buffer); + + const s64 seek = util::AlignDown(offset, SectorSize); + const s64 sector_ofs = offset - seek; + + /* Check if we have nothing to do. */ + if (size == 0) { + R_SUCCEED(); + } + + /* Fast case. */ + if (sector_ofs == 0 && util::IsAligned(size, SectorSize)) { + R_RETURN(Base::Read(offset, buffer, size)); + } + + R_TRY(Base::Read(seek, m_sector_buf, SectorSize)); + + if (size + sector_ofs <= SectorSize) { + /* Staying within the sector. */ + std::memcpy(buffer, m_sector_buf + sector_ofs, size); + } else { + /* Leaving the sector. */ + const size_t size_in_sector = SectorSize - sector_ofs; + std::memcpy(buffer, m_sector_buf + sector_ofs, size_in_sector); + size -= size_in_sector; + + /* Read as many guaranteed aligned sectors as we can. */ + const size_t aligned_remaining_size = util::AlignDown(size, SectorSize); + if (aligned_remaining_size) { + R_TRY(Base::Read(offset + size_in_sector, buffer + size_in_sector, aligned_remaining_size)); + size -= aligned_remaining_size; + } + + /* Read any leftover data. */ + if (size) { + R_TRY(Base::Read(offset + size_in_sector + aligned_remaining_size, m_sector_buf, SectorSize)); + std::memcpy(buffer + size_in_sector + aligned_remaining_size, m_sector_buf, size); + } + } + + R_SUCCEED(); + } + + virtual Result Write(s64 offset, const void *_buffer, size_t size) override { + const u8 *buffer = static_cast<const u8 *>(_buffer); + + const s64 seek = util::AlignDown(offset, SectorSize); + const s64 sector_ofs = offset - seek; + + /* Check if we have nothing to do. */ + if (size == 0) { + R_SUCCEED(); + } + + /* Fast case. */ + if (sector_ofs == 0 && util::IsAligned(size, SectorSize)) { + R_RETURN(Base::Write(offset, buffer, size)); + } + + /* Load existing sector data. */ + R_TRY(Base::Read(seek, m_sector_buf, SectorSize)); + + if (size + sector_ofs <= SectorSize) { + /* Staying within the sector. */ + std::memcpy(m_sector_buf + sector_ofs, buffer, size); + R_TRY(Base::Write(seek, m_sector_buf, SectorSize)); + } else { + /* Leaving the sector. */ + const size_t size_in_sector = SectorSize - sector_ofs; + std::memcpy(m_sector_buf + sector_ofs, buffer, size_in_sector); + R_TRY(Base::Write(seek, m_sector_buf, SectorSize)); + size -= size_in_sector; + + /* Write as many guaranteed aligned sectors as we can. */ + const size_t aligned_remaining_size = util::AlignDown(size, SectorSize); + if (aligned_remaining_size) { + R_TRY(Base::Write(offset + size_in_sector, buffer + size_in_sector, aligned_remaining_size)); + size -= aligned_remaining_size; + } + + /* Write any leftover data. */ + if (size) { + R_TRY(Base::Read(offset + size_in_sector + aligned_remaining_size, m_sector_buf, SectorSize)); + std::memcpy(m_sector_buf, buffer + size_in_sector + aligned_remaining_size, size); + R_TRY(Base::Write(offset + size_in_sector + aligned_remaining_size, m_sector_buf, SectorSize)); + } + } + + R_SUCCEED(); + } + }; + + /* Represents an RCM-preserving BOOT0 partition. */ + class Boot0Storage : public SectoredStorageAdapter<ams::fs::RemoteStorage, 0x200> { + public: + using Base = SectoredStorageAdapter<ams::fs::RemoteStorage, 0x200>; + + static constexpr s64 BctEndOffset = 0xFC000; + static constexpr s64 BctSize = static_cast<s64>(ams::updater::BctSize); + static constexpr s64 BctPubkStart = 0x210; + static constexpr s64 BctPubkSize = 0x100; + static constexpr s64 BctPubkEnd = BctPubkStart + BctPubkSize; + + static constexpr s64 EksStart = 0x180000; + static constexpr s64 EksSize = static_cast<s64>(ams::updater::EksSize); + static constexpr s64 EksEnd = EksStart + EksSize; + private: + sm::MitmProcessInfo m_client_info; + private: + bool CanModifyBctPublicKey(); + public: + Boot0Storage(FsStorage &s, const sm::MitmProcessInfo &c) : Base(s), m_client_info(c) { /* ... */ } + public: + virtual Result Read(s64 offset, void *_buffer, size_t size) override; + virtual Result Write(s64 offset, const void *_buffer, size_t size) override; + }; + + class CustomPublicKeyBoot0Storage : public SectoredStorageAdapter<ams::fs::RemoteStorage, 0x200> { + public: + using Base = SectoredStorageAdapter<ams::fs::RemoteStorage, 0x200>; + private: + sm::MitmProcessInfo m_client_info; + spl::SocType m_soc_type; + public: + CustomPublicKeyBoot0Storage(FsStorage &s, const sm::MitmProcessInfo &c, spl::SocType soc); + public: + virtual Result Read(s64 offset, void *_buffer, size_t size) override; + virtual Result Write(s64 offset, const void *_buffer, size_t size) override; + }; + + bool DetectBoot0CustomPublicKey(::FsStorage &storage); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_calibration_binary_storage.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_calibration_binary_storage.cpp new file mode 100644 index 00000000..ea3294fc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_calibration_binary_storage.cpp @@ -0,0 +1,136 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsmitm_calibration_binary_storage.hpp" + +namespace ams::mitm::fs { + + using namespace ams::fs; + + namespace { + + constinit os::SdkMutex g_cal0_access_mutex; + + } + + Result CalibrationBinaryStorage::Read(s64 offset, void *_buffer, size_t size) { + /* Acquire exclusive calibration binary access. */ + std::scoped_lock lk(g_cal0_access_mutex); + + /* Get u8 buffer. */ + u8 *buffer = static_cast<u8 *>(_buffer); + + /* Succeed on zero-size. */ + R_SUCCEED_IF(size == 0); + + /* Handle the blank region. */ + if (m_read_blank) { + if (BlankStartOffset <= offset && offset < BlankEndOffset) { + const size_t blank_size = std::min(size, static_cast<size_t>(BlankEndOffset - offset)); + mitm::ReadFromBlankCalibrationBinary(offset, buffer, blank_size); + size -= blank_size; + buffer += blank_size; + offset += blank_size; + } + } + + /* Succeed if we're done. */ + R_SUCCEED_IF(size == 0); + + /* Handle any in-between data. */ + if (BlankEndOffset <= offset && offset < FakeSecureStartOffset) { + const size_t mid_size = std::min(size, static_cast<size_t>(FakeSecureStartOffset - offset)); + R_TRY(Base::Read(offset, buffer, mid_size)); + size -= mid_size; + buffer += mid_size; + offset += mid_size; + } + + /* Succeed if we're done. */ + R_SUCCEED_IF(size == 0); + + /* Handle the secure region. */ + if (FakeSecureStartOffset <= offset && offset < FakeSecureEndOffset) { + const size_t fake_size = std::min(size, static_cast<size_t>(FakeSecureEndOffset - offset)); + mitm::ReadFromFakeSecureBackupStorage(offset, buffer, fake_size); + size -= fake_size; + buffer += fake_size; + offset += fake_size; + } + + /* Succeed if we're done. */ + R_SUCCEED_IF(size == 0); + + /* Handle any remaining data. */ + R_RETURN(Base::Read(offset, buffer, size)); + } + + Result CalibrationBinaryStorage::Write(s64 offset, const void *_buffer, size_t size) { + /* Acquire exclusive calibration binary access. */ + std::scoped_lock lk(g_cal0_access_mutex); + + /* Get const u8 buffer. */ + const u8 *buffer = static_cast<const u8 *>(_buffer); + + /* Succeed on zero-size. */ + R_SUCCEED_IF(size == 0); + + /* Only allow writes if we should. */ + R_UNLESS(m_allow_writes, fs::ResultUnsupportedOperation()); + + /* Handle the blank region. */ + if (m_read_blank) { + if (BlankStartOffset <= offset && offset < BlankEndOffset) { + const size_t blank_size = std::min(size, static_cast<size_t>(BlankEndOffset - offset)); + mitm::WriteToBlankCalibrationBinary(offset, buffer, blank_size); + size -= blank_size; + buffer += blank_size; + offset += blank_size; + } + } + + /* Succeed if we're done. */ + R_SUCCEED_IF(size == 0); + + /* Handle any in-between data. */ + if (BlankEndOffset <= offset && offset < FakeSecureStartOffset) { + const size_t mid_size = std::min(size, static_cast<size_t>(FakeSecureStartOffset - offset)); + R_TRY(Base::Write(offset, buffer, mid_size)); + size -= mid_size; + buffer += mid_size; + offset += mid_size; + } + + /* Succeed if we're done. */ + R_SUCCEED_IF(size == 0); + + /* Handle the secure region. */ + if (FakeSecureStartOffset <= offset && offset < FakeSecureEndOffset) { + const size_t fake_size = std::min(size, static_cast<size_t>(FakeSecureEndOffset - offset)); + mitm::WriteToFakeSecureBackupStorage(offset, buffer, fake_size); + size -= fake_size; + buffer += fake_size; + offset += fake_size; + } + + /* Succeed if we're done. */ + R_SUCCEED_IF(size == 0); + + /* Handle any remaining data. */ + R_RETURN(Base::Write(offset, buffer, size)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_calibration_binary_storage.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_calibration_binary_storage.hpp new file mode 100644 index 00000000..9d666547 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_calibration_binary_storage.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> +#include "fsmitm_boot0storage.hpp" +#include "../amsmitm_prodinfo_utils.hpp" + +namespace ams::mitm::fs { + + /* Represents a protected calibration binary partition. */ + class CalibrationBinaryStorage : public SectoredStorageAdapter<ams::fs::RemoteStorage, 0x200> { + public: + using Base = SectoredStorageAdapter<ams::fs::RemoteStorage, 0x200>; + + static constexpr s64 BlankStartOffset = 0x0; + static constexpr s64 BlankSize = static_cast<s64>(CalibrationBinarySize); + static constexpr s64 BlankEndOffset = BlankStartOffset + BlankSize; + + static constexpr s64 FakeSecureStartOffset = SecureCalibrationInfoBackupOffset; + static constexpr s64 FakeSecureSize = static_cast<s64>(SecureCalibrationBinaryBackupSize); + static constexpr s64 FakeSecureEndOffset = FakeSecureStartOffset + FakeSecureSize; + private: + sm::MitmProcessInfo m_client_info; + bool m_read_blank; + bool m_allow_writes; + public: + CalibrationBinaryStorage(FsStorage &s, const sm::MitmProcessInfo &c) + : Base(s), m_client_info(c), + m_read_blank(mitm::ShouldReadBlankCalibrationBinary()), + m_allow_writes(mitm::IsWriteToCalibrationBinaryAllowed()) + { + /* ... */ + } + public: + virtual Result Read(s64 offset, void *_buffer, size_t size) override; + virtual Result Write(s64 offset, const void *_buffer, size_t size) override; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_layered_romfs_storage.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_layered_romfs_storage.cpp new file mode 100644 index 00000000..f5bd6079 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_layered_romfs_storage.cpp @@ -0,0 +1,432 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../amsmitm_initialization.hpp" +#include "../amsmitm_fs_utils.hpp" +#include "fsmitm_layered_romfs_storage.hpp" + +namespace ams::mitm::fs { + + namespace { + + constinit os::SdkMutex g_mq_lock; + constinit bool g_started_req_thread; + constinit uintptr_t g_mq_storage[2]; + os::MessageQueue g_req_mq(g_mq_storage + 0, 1); + os::MessageQueue g_ack_mq(g_mq_storage + 1, 1); + + class LayeredRomfsStorageHolder : public util::IntrusiveRedBlackTreeBaseNode<LayeredRomfsStorageHolder> { + public: + using RedBlackKeyType = u64; + private: + LayeredRomfsStorageImpl *m_impl; + u32 m_reference_count; + bool m_second_chance; + bool m_process_romfs; + public: + LayeredRomfsStorageHolder(LayeredRomfsStorageImpl *impl, bool process_rom) : m_impl(impl), m_reference_count(1), m_second_chance(true), m_process_romfs(process_rom) { + /* ... */ + } + + ~LayeredRomfsStorageHolder() { + delete m_impl; + } + + constexpr LayeredRomfsStorageImpl *GetImpl() const { return m_impl; } + constexpr ncm::ProgramId GetProgramId() const { return m_impl->GetProgramId(); } + constexpr u32 GetReferenceCount() const { return m_reference_count; } + + void OpenReferenceImpl() { ++m_reference_count; } + void CloseReferenceImpl() { --m_reference_count; } + + bool GetSecondChanceImpl() const { return m_second_chance; } + void SetSecondChanceImpl(bool sc) { m_second_chance = sc; } + + bool IsProcessRomfs() const { return m_process_romfs; } + + static constexpr ALWAYS_INLINE int Compare(const RedBlackKeyType &lval, const LayeredRomfsStorageHolder &rhs) { + const auto rval = rhs.GetProgramId().value; + + if (lval < rval) { + return -1; + } else if (lval == rval) { + return 0; + } else { + return 1; + } + } + + static constexpr ALWAYS_INLINE int Compare(const LayeredRomfsStorageHolder &lhs, const LayeredRomfsStorageHolder &rhs) { + return Compare(lhs.GetProgramId().value, rhs); + } + }; + + using LayeredRomfsStorageSet = typename util::IntrusiveRedBlackTreeBaseTraits<LayeredRomfsStorageHolder>::TreeType<LayeredRomfsStorageHolder>; + + constinit os::SdkRecursiveMutex g_storage_set_mutex; + constinit LayeredRomfsStorageSet g_storage_set; + constinit os::SdkMutex g_initialization_mutex; + + void OpenReference(LayeredRomfsStorageImpl *impl) { + std::scoped_lock lk(g_storage_set_mutex); + + auto it = g_storage_set.find_key(impl->GetProgramId().value); + AMS_ABORT_UNLESS(it != g_storage_set.end()); + + it->OpenReferenceImpl(); + } + + void CloseReference(LayeredRomfsStorageImpl *impl) { + std::scoped_lock lk(g_storage_set_mutex); + + auto it = g_storage_set.find_key(impl->GetProgramId().value); + AMS_ABORT_UNLESS(it != g_storage_set.end()); + + AMS_ABORT_UNLESS(it->GetReferenceCount() > 0); + it->CloseReferenceImpl(); + } + + void RomfsInitializerThreadFunction(void *) { + while (true) { + uintptr_t storage_uptr = 0; + + g_req_mq.Receive(std::addressof(storage_uptr)); + auto *impl = reinterpret_cast<LayeredRomfsStorageImpl *>(storage_uptr); + g_ack_mq.Send(storage_uptr); + + std::scoped_lock lk(g_initialization_mutex); + + impl->InitializeImpl(); + + /* Close the initial reference. */ + CloseReference(impl); + } + } + + void RomfsFinalizerThreadFunction(void *) { + while (true) { + { + std::scoped_lock lk(g_storage_set_mutex); + + auto it = g_storage_set.begin(); + while (it != g_storage_set.end()) { + if (it->GetReferenceCount() > 0) { + it->SetSecondChanceImpl(true); + ++it; + } else if (it->GetSecondChanceImpl()) { + it->SetSecondChanceImpl(false); + ++it; + } else { + auto *holder = std::addressof(*it); + it = g_storage_set.erase(it); + delete holder; + } + } + } + + os::SleepThread(TimeSpan::FromMilliSeconds(500)); + } + } + + constexpr size_t RomfsInitializerThreadStackSize = 0x8000; + os::ThreadType g_romfs_initializer_thread; + os::ThreadType g_romfs_finalizer_thread; + alignas(os::ThreadStackAlignment) u8 g_romfs_initializer_thread_stack[RomfsInitializerThreadStackSize]; + alignas(os::ThreadStackAlignment) u8 g_romfs_finalizer_thread_stack[os::MemoryPageSize]; + + void RequestInitializeStorage(uintptr_t storage_uptr) { + std::scoped_lock lk(g_mq_lock); + + if (AMS_UNLIKELY(!g_started_req_thread)) { + R_ABORT_UNLESS(os::CreateThread(std::addressof(g_romfs_initializer_thread), RomfsInitializerThreadFunction, nullptr, g_romfs_initializer_thread_stack, sizeof(g_romfs_initializer_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(mitm_fs, RomFileSystemInitializeThread))); + os::SetThreadNamePointer(std::addressof(g_romfs_initializer_thread), AMS_GET_SYSTEM_THREAD_NAME(mitm_fs, RomFileSystemInitializeThread)); + os::StartThread(std::addressof(g_romfs_initializer_thread)); + + R_ABORT_UNLESS(os::CreateThread(std::addressof(g_romfs_finalizer_thread), RomfsFinalizerThreadFunction, nullptr, g_romfs_finalizer_thread_stack, sizeof(g_romfs_finalizer_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(mitm_fs, RomFileSystemInitializeThread))); + os::SetThreadNamePointer(std::addressof(g_romfs_finalizer_thread), AMS_GET_SYSTEM_THREAD_NAME(mitm_fs, RomFileSystemFinalizeThread)); + os::StartThread(std::addressof(g_romfs_finalizer_thread)); + + g_started_req_thread = true; + } + + g_req_mq.Send(storage_uptr); + uintptr_t ack = 0; + g_ack_mq.Receive(std::addressof(ack)); + AMS_ABORT_UNLESS(ack == storage_uptr); + } + + class LayeredRomfsStorage : public ams::fs::IStorage { + private: + LayeredRomfsStorageImpl *m_impl; + public: + LayeredRomfsStorage(LayeredRomfsStorageImpl *impl) : m_impl(impl) { + OpenReference(m_impl); + } + + virtual ~LayeredRomfsStorage() { + CloseReference(m_impl); + } + + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + R_RETURN(m_impl->Read(offset, buffer, size)); + } + + virtual Result GetSize(s64 *out_size) override { + R_RETURN(m_impl->GetSize(out_size)); + } + + virtual Result Flush() override { + R_RETURN(m_impl->Flush()); + } + + virtual Result OperateRange(void *dst, size_t dst_size, ams::fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + R_RETURN(m_impl->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + /* TODO: Better result code? */ + AMS_UNUSED(offset, buffer, size); + R_THROW(ams::fs::ResultUnsupportedOperation()) + } + + virtual Result SetSize(s64 size) override { + /* TODO: Better result code? */ + AMS_UNUSED(size); + R_THROW(ams::fs::ResultUnsupportedOperation()) + } + }; + + } + + using namespace ams::fs; + + std::shared_ptr<ams::fs::IStorage> GetLayeredRomfsStorage(ncm::ProgramId program_id, ::FsStorage &data_storage, bool is_process_romfs) { + /*Prepare to find or create a new storage. */ + LayeredRomfsStorageImpl *impl = nullptr; + { + std::scoped_lock lk(g_storage_set_mutex); + + /* Find an existing storage. */ + if (auto it = g_storage_set.find_key(program_id.value); it != g_storage_set.end()) { + return std::make_shared<LayeredRomfsStorage>(it->GetImpl()); + } + + /* We don't have an existing storage. If we're creating process romfs, free any unreferenced process romfs. */ + /* This should help prevent too much memory in use at any time. */ + if (is_process_romfs) { + auto it = g_storage_set.begin(); + while (it != g_storage_set.end()) { + if (it->GetReferenceCount() > 0 || !it->IsProcessRomfs()) { + ++it; + } else { + auto *holder = std::addressof(*it); + it = g_storage_set.erase(it); + delete holder; + } + } + } + + /* Create a new storage. */ + { + ::FsFile data_file; + if (R_SUCCEEDED(OpenAtmosphereSdFile(std::addressof(data_file), program_id, "romfs.bin", OpenMode_Read))) { + impl = new LayeredRomfsStorageImpl(std::make_unique<ReadOnlyStorageAdapter>(new RemoteStorage(data_storage)), std::make_unique<ReadOnlyStorageAdapter>(new FileStorage(new RemoteFile(data_file))), program_id); + } else { + impl = new LayeredRomfsStorageImpl(std::make_unique<ReadOnlyStorageAdapter>(new RemoteStorage(data_storage)), nullptr, program_id); + } + } + + /* Insert holder. Reference count will now be one. */ + g_storage_set.insert(*(new LayeredRomfsStorageHolder(impl, is_process_romfs))); + } + + /* Begin initialization. When this finishes, a decref will occur. */ + AMS_ABORT_UNLESS(impl != nullptr); + impl->BeginInitialize(); + + /* Return a new shared storage for the impl. */ + return std::make_shared<LayeredRomfsStorage>(impl); + } + + void FinalizeLayeredRomfsStorage(ncm::ProgramId program_id) { + std::scoped_lock lk(g_initialization_mutex); + std::scoped_lock lk2(g_storage_set_mutex); + + /* Find an existing storage. */ + if (auto it = g_storage_set.find_key(program_id.value); it != g_storage_set.end()) { + /* We need to delete the process romfs. Require invariant that it is unreferenced, by this point. */ + AMS_ABORT_UNLESS(it->GetReferenceCount() == 0); + + auto *holder = std::addressof(*it); + it = g_storage_set.erase(it); + delete holder; + } + } + + LayeredRomfsStorageImpl::LayeredRomfsStorageImpl(std::unique_ptr<IStorage> s_r, std::unique_ptr<IStorage> f_r, ncm::ProgramId pr_id) : m_storage_romfs(std::move(s_r)), m_file_romfs(std::move(f_r)), m_initialize_event(os::EventClearMode_ManualClear), m_program_id(std::move(pr_id)), m_is_initialized(false), m_started_initialize(false) { + /* ... */ + } + + LayeredRomfsStorageImpl::~LayeredRomfsStorageImpl() { + for (size_t i = 0; i < m_source_infos.size(); i++) { + m_source_infos[i].Cleanup(); + } + } + + void LayeredRomfsStorageImpl::BeginInitialize() { + AMS_ABORT_UNLESS(!m_started_initialize); + RequestInitializeStorage(reinterpret_cast<uintptr_t>(this)); + m_started_initialize = true; + } + + void LayeredRomfsStorageImpl::InitializeImpl() { + /* Build new virtual romfs. */ + romfs::Builder builder(m_program_id); + + if (mitm::IsInitialized()) { + builder.AddSdFiles(); + } + if (m_file_romfs) { + builder.AddStorageFiles(m_file_romfs.get(), romfs::DataSourceType::File); + } + if (m_storage_romfs) { + builder.AddStorageFiles(m_storage_romfs.get(), romfs::DataSourceType::Storage); + } + + builder.Build(std::addressof(m_source_infos)); + + m_is_initialized = true; + m_initialize_event.Signal(); + } + + Result LayeredRomfsStorageImpl::Read(s64 offset, void *buffer, size_t size) { + /* Check if we can succeed immediately. */ + R_SUCCEED_IF(size == 0); + + /* Ensure we're initialized. */ + if (!m_is_initialized) { + m_initialize_event.Wait(); + } + + /* Validate offset/size. */ + const s64 virt_size = this->GetSize(); + R_UNLESS(offset >= 0, fs::ResultInvalidOffset()); + R_UNLESS(offset < virt_size, fs::ResultInvalidOffset()); + if (static_cast<size_t>(virt_size - offset) < size) { + size = static_cast<size_t>(virt_size - offset); + } + + /* Find first source info via binary search. */ + auto it = std::lower_bound(m_source_infos.begin(), m_source_infos.end(), offset); + u8 *cur_dst = static_cast<u8 *>(buffer); + + /* Our operator < compares against start of info instead of end, so we need to subtract one from lower bound. */ + it--; + + size_t read_so_far = 0; + while (read_so_far < size) { + const auto &cur_source = *it; + AMS_ABORT_UNLESS(offset >= cur_source.virtual_offset); + + if (offset < cur_source.virtual_offset + cur_source.size) { + const s64 offset_within_source = offset - cur_source.virtual_offset; + const size_t cur_read_size = std::min(size - read_so_far, static_cast<size_t>(cur_source.size - offset_within_source)); + switch (cur_source.source_type) { + case romfs::DataSourceType::Storage: + R_ABORT_UNLESS(m_storage_romfs->Read(cur_source.storage_source_info.offset + offset_within_source, cur_dst, cur_read_size)); + break; + case romfs::DataSourceType::File: + R_ABORT_UNLESS(m_file_romfs->Read(cur_source.file_source_info.offset + offset_within_source, cur_dst, cur_read_size)); + break; + case romfs::DataSourceType::LooseSdFile: + { + FsFile file; + R_ABORT_UNLESS(mitm::fs::OpenAtmosphereSdRomfsFile(std::addressof(file), m_program_id, cur_source.loose_source_info.path, OpenMode_Read)); + ON_SCOPE_EXIT { fsFileClose(std::addressof(file)); }; + + u64 out_read = 0; + R_ABORT_UNLESS(fsFileRead(std::addressof(file), offset_within_source, cur_dst, cur_read_size, FsReadOption_None, std::addressof(out_read))); + AMS_ABORT_UNLESS(out_read == cur_read_size); + } + break; + case romfs::DataSourceType::Memory: + std::memcpy(cur_dst, cur_source.memory_source_info.data + offset_within_source, cur_read_size); + break; + case romfs::DataSourceType::Metadata: + { + size_t out_read = 0; + R_ABORT_UNLESS(cur_source.metadata_source_info.file->Read(std::addressof(out_read), offset_within_source, cur_dst, cur_read_size)); + AMS_ABORT_UNLESS(out_read == cur_read_size); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + read_so_far += cur_read_size; + cur_dst += cur_read_size; + offset += cur_read_size; + } else { + /* Explicitly handle padding. */ + const auto &next_source = *(++it); + const size_t padding_size = static_cast<size_t>(next_source.virtual_offset - offset); + + std::memset(cur_dst, 0, padding_size); + read_so_far += padding_size; + cur_dst += padding_size; + offset += padding_size; + } + } + + R_SUCCEED(); + } + + Result LayeredRomfsStorageImpl::GetSize(s64 *out_size) { + /* Ensure we're initialized. */ + if (!m_is_initialized) { + m_initialize_event.Wait(); + } + + *out_size = this->GetSize(); + R_SUCCEED(); + } + + Result LayeredRomfsStorageImpl::Flush() { + R_SUCCEED(); + } + + Result LayeredRomfsStorageImpl::OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + AMS_UNUSED(offset, src, src_size); + + switch (op_id) { + case OperationId::Invalidate: + case OperationId::QueryRange: + if (size == 0) { + if (op_id == OperationId::QueryRange) { + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(QueryRangeInfo), fs::ResultInvalidSize()); + reinterpret_cast<QueryRangeInfo *>(dst)->Clear(); + } + R_SUCCEED(); + } + /* TODO: How to deal with this? */ + R_THROW(fs::ResultUnsupportedOperation()); + default: + R_THROW(fs::ResultUnsupportedOperation()); + } + } + + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_layered_romfs_storage.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_layered_romfs_storage.hpp new file mode 100644 index 00000000..337e47c4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_layered_romfs_storage.hpp @@ -0,0 +1,56 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> +#include "fsmitm_romfs.hpp" + +namespace ams::mitm::fs { + + class LayeredRomfsStorageImpl { + private: + romfs::Builder::SourceInfoVector m_source_infos; + std::unique_ptr<ams::fs::IStorage> m_storage_romfs; + std::unique_ptr<ams::fs::IStorage> m_file_romfs; + os::Event m_initialize_event; + ncm::ProgramId m_program_id; + bool m_is_initialized; + bool m_started_initialize; + protected: + inline s64 GetSize() const { + const auto &back = m_source_infos.back(); + return back.virtual_offset + back.size; + } + public: + LayeredRomfsStorageImpl(std::unique_ptr<ams::fs::IStorage> s_r, std::unique_ptr<ams::fs::IStorage> f_r, ncm::ProgramId pr_id); + ~LayeredRomfsStorageImpl(); + + void BeginInitialize(); + void InitializeImpl(); + + constexpr ncm::ProgramId GetProgramId() const { return m_program_id; } + + Result Read(s64 offset, void *buffer, size_t size); + Result GetSize(s64 *out_size); + Result Flush(); + Result OperateRange(void *dst, size_t dst_size, ams::fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size); + }; + + std::shared_ptr<ams::fs::IStorage> GetLayeredRomfsStorage(ncm::ProgramId program_id, ::FsStorage &data_storage, bool is_process_romfs); + + void FinalizeLayeredRomfsStorage(ncm::ProgramId program_id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_module.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_module.cpp new file mode 100644 index 00000000..7fca526a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_module.cpp @@ -0,0 +1,111 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsmitm_module.hpp" +#include "fs_mitm_service.hpp" + +namespace ams::mitm::fs { + + namespace { + + enum PortIndex { + PortIndex_Mitm, + PortIndex_Count, + }; + + constexpr sm::ServiceName MitmServiceName = sm::ServiceName::Encode("fsp-srv"); + + struct ServerOptions { + static constexpr size_t PointerBufferSize = 0x800; + static constexpr size_t MaxDomains = 0x40; + static constexpr size_t MaxDomainObjects = 0x4000; + static constexpr bool CanDeferInvokeRequest = false; + static constexpr bool CanManageMitmServers = true; + }; + + constexpr size_t MaxSessions = 61; + + class ServerManager final : public sf::hipc::ServerManager<PortIndex_Count, ServerOptions, MaxSessions> { + private: + virtual Result OnNeedsToAccept(int port_index, Server *server) override; + }; + + ServerManager g_server_manager; + + Result ServerManager::OnNeedsToAccept(int port_index, Server *server) { + /* Acknowledge the mitm session. */ + std::shared_ptr<::Service> fsrv; + sm::MitmProcessInfo client_info; + server->AcknowledgeMitmSession(std::addressof(fsrv), std::addressof(client_info)); + + switch (port_index) { + case PortIndex_Mitm: + R_RETURN(this->AcceptMitmImpl(server, sf::CreateSharedObjectEmplaced<IFsMitmInterface, FsMitmService>(decltype(fsrv)(fsrv), client_info), fsrv)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + constexpr size_t TotalThreads = 5; + static_assert(TotalThreads >= 1, "TotalThreads"); + constexpr size_t NumExtraThreads = TotalThreads - 1; + constexpr size_t ThreadStackSize = mitm::ModuleTraits<fs::MitmModule>::StackSize; + alignas(os::MemoryPageSize) u8 g_extra_thread_stacks[NumExtraThreads][ThreadStackSize]; + + os::ThreadType g_extra_threads[NumExtraThreads]; + + void LoopServerThread(void *) { + /* Loop forever, servicing our services. */ + g_server_manager.LoopProcess(); + } + + void ProcessForServerOnAllThreads() { + /* Initialize threads. */ + if constexpr (NumExtraThreads > 0) { + const s32 priority = os::GetThreadCurrentPriority(os::GetCurrentThread()); + for (size_t i = 0; i < NumExtraThreads; i++) { + R_ABORT_UNLESS(os::CreateThread(g_extra_threads + i, LoopServerThread, nullptr, g_extra_thread_stacks[i], ThreadStackSize, priority)); + } + } + + /* Start extra threads. */ + if constexpr (NumExtraThreads > 0) { + for (size_t i = 0; i < NumExtraThreads; i++) { + os::StartThread(g_extra_threads + i); + } + } + + /* Loop this thread. */ + LoopServerThread(nullptr); + + /* Wait for extra threads to finish. */ + if constexpr (NumExtraThreads > 0) { + for (size_t i = 0; i < NumExtraThreads; i++) { + os::WaitThread(g_extra_threads + i); + } + } + } + + } + + void MitmModule::ThreadFunction(void *) { + /* Create fs mitm. */ + R_ABORT_UNLESS((g_server_manager.RegisterMitmServer<FsMitmService>(PortIndex_Mitm, MitmServiceName))); + + /* Process for the server. */ + ProcessForServerOnAllThreads(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_module.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_module.hpp new file mode 100644 index 00000000..5ae370bd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_module.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../amsmitm_module.hpp" + +namespace ams::mitm::fs { + + DEFINE_MITM_MODULE_CLASS(0x8000, AMS_GET_SYSTEM_THREAD_PRIORITY(fs, WorkerThreadPool) - 1); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_readonly_layered_filesystem.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_readonly_layered_filesystem.hpp new file mode 100644 index 00000000..f9030c9e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_readonly_layered_filesystem.hpp @@ -0,0 +1,105 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::mitm::fs { + + class ReadOnlyLayeredFileSystem : public ams::fs::fsa::IFileSystem { + private: + ams::fs::ReadOnlyFileSystem m_fs_1; + ams::fs::ReadOnlyFileSystem m_fs_2; + public: + explicit ReadOnlyLayeredFileSystem(std::unique_ptr<ams::fs::fsa::IFileSystem> a, std::unique_ptr<ams::fs::fsa::IFileSystem> b) : m_fs_1(std::move(a)), m_fs_2(std::move(b)) { /* ... */ } + + virtual ~ReadOnlyLayeredFileSystem() { /* ... */ } + private: + virtual Result DoCreateFile(const ams::fs::Path &path, s64 size, int flags) override final { + AMS_UNUSED(path, size, flags); + R_THROW(ams::fs::ResultUnsupportedOperation()) + } + + virtual Result DoDeleteFile(const ams::fs::Path &path) override final { + AMS_UNUSED(path); + R_THROW(ams::fs::ResultUnsupportedOperation()) + } + + virtual Result DoCreateDirectory(const ams::fs::Path &path) override final { + AMS_UNUSED(path); + R_THROW(ams::fs::ResultUnsupportedOperation()) + } + + virtual Result DoDeleteDirectory(const ams::fs::Path &path) override final { + AMS_UNUSED(path); + R_THROW(ams::fs::ResultUnsupportedOperation()) + } + + virtual Result DoDeleteDirectoryRecursively(const ams::fs::Path &path) override final { + AMS_UNUSED(path); + R_THROW(ams::fs::ResultUnsupportedOperation()) + } + + virtual Result DoRenameFile(const ams::fs::Path &old_path, const ams::fs::Path &new_path) override final { + AMS_UNUSED(old_path, new_path); + R_THROW(ams::fs::ResultUnsupportedOperation()) + } + + virtual Result DoRenameDirectory(const ams::fs::Path &old_path, const ams::fs::Path &new_path) override final { + AMS_UNUSED(old_path, new_path); + R_THROW(ams::fs::ResultUnsupportedOperation()) + } + + virtual Result DoGetEntryType(ams::fs::DirectoryEntryType *out, const ams::fs::Path &path) override final { + R_SUCCEED_IF(R_SUCCEEDED(m_fs_1.GetEntryType(out, path))); + R_RETURN(m_fs_2.GetEntryType(out, path)); + } + + virtual Result DoOpenFile(std::unique_ptr<ams::fs::fsa::IFile> *out_file, const ams::fs::Path &path, ams::fs::OpenMode mode) override final { + R_SUCCEED_IF(R_SUCCEEDED(m_fs_1.OpenFile(out_file, path, mode))); + R_RETURN(m_fs_2.OpenFile(out_file, path, mode)); + } + + virtual Result DoOpenDirectory(std::unique_ptr<ams::fs::fsa::IDirectory> *out_dir, const ams::fs::Path &path, ams::fs::OpenDirectoryMode mode) override final { + R_SUCCEED_IF(R_SUCCEEDED(m_fs_1.OpenDirectory(out_dir, path, mode))); + R_RETURN(m_fs_2.OpenDirectory(out_dir, path, mode)); + } + + virtual Result DoCommit() override final { + R_SUCCEED(); + } + + virtual Result DoGetFreeSpaceSize(s64 *out, const ams::fs::Path &path) { + AMS_UNUSED(out, path); + R_THROW(ams::fs::ResultUnsupportedOperation()) + } + + virtual Result DoGetTotalSpaceSize(s64 *out, const ams::fs::Path &path) { + AMS_UNUSED(out, path); + R_THROW(ams::fs::ResultUnsupportedOperation()) + } + + virtual Result DoCleanDirectoryRecursively(const ams::fs::Path &path) { + AMS_UNUSED(path); + R_THROW(ams::fs::ResultUnsupportedOperation()) + } + + virtual Result DoGetFileTimeStampRaw(ams::fs::FileTimeStampRaw *out, const ams::fs::Path &path) { + R_SUCCEED_IF(R_SUCCEEDED(m_fs_1.GetFileTimeStampRaw(out, path))); + R_RETURN(m_fs_2.GetFileTimeStampRaw(out, path)); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.cpp new file mode 100644 index 00000000..cba29e38 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.cpp @@ -0,0 +1,1117 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../amsmitm_fs_utils.hpp" +#include "fsmitm_romfs.hpp" +#include "fsmitm_layered_romfs_storage.hpp" +#include "memlet/memlet.h" + +namespace ams::mitm::fs { + + using namespace ams::fs; + + namespace romfs { + + namespace { + + constexpr size_t MaximumRomfsBuildAppletMemorySize = 32_MB; + + struct ApplicationWithDynamicHeapInfo { + ncm::ProgramId program_id; + size_t dynamic_app_heap_size; + size_t dynamic_system_heap_size; + }; + + constexpr const ApplicationWithDynamicHeapInfo ApplicationsWithDynamicHeap[] = { + /* STAR WARS: Knights of the Old Republic II: The Sith Lords. */ + /* Requirement ? MB. 16 MB stolen heap fixes a crash, though. */ + /* Unknown heap sensitivity. */ + { 0x0100B2C016252000, 16_MB, 0_MB }, + + /* Animal Crossing: New Horizons. */ + /* Requirement ~24 MB. */ + /* No particular heap sensitivity. */ + { 0x01006F8002326000, 16_MB, 0_MB }, + + /* Fire Emblem: Engage. */ + /* Requirement ~32+ MB. */ + /* No particular heap sensitivity. */ + { 0x0100A6301214E000, 20_MB, 0_MB }, + + /* The Legend of Zelda: Tears of the Kingdom. */ + /* Requirement ~48 MB. */ + /* Game is highly sensitive to memory stolen from application heap. */ + /* 1.0.0 tolerates no more than 16 MB stolen. 1.1.0 no more than 12 MB. */ + { 0x0100F2C0115B6000, 10_MB, 8_MB }, + }; + + constexpr size_t GetDynamicAppHeapSize(ncm::ProgramId program_id) { + for (const auto &info : ApplicationsWithDynamicHeap) { + if (info.program_id == program_id) { + return info.dynamic_app_heap_size; + } + } + + return 0; + } + + constexpr size_t GetDynamicSysHeapSize(ncm::ProgramId program_id) { + for (const auto &info : ApplicationsWithDynamicHeap) { + if (info.program_id == program_id) { + return info.dynamic_system_heap_size; + } + } + + return 0; + } + + template<auto MapImpl, auto UnmapImpl> + struct DynamicHeap { + uintptr_t heap_address{}; + size_t heap_size{}; + size_t outstanding_allocations{}; + util::TypedStorage<mem::StandardAllocator> heap{}; + os::SdkMutex release_heap_lock{}; + + constexpr DynamicHeap() = default; + + void Map() { + if (this->heap_address == 0) { + /* NOTE: Lock not necessary, because this is the only location which do 0 -> non-zero. */ + + R_ABORT_UNLESS(MapImpl(std::addressof(this->heap_address), this->heap_size)); + AMS_ABORT_UNLESS(this->heap_address != 0 || this->heap_size == 0); + + /* Create heap. */ + if (this->heap_size > 0) { + util::ConstructAt(this->heap, reinterpret_cast<void *>(this->heap_address), this->heap_size); + } + } + } + + void TryRelease() { + if (this->outstanding_allocations == 0) { + std::scoped_lock lk(this->release_heap_lock); + + if (this->heap_address != 0) { + util::DestroyAt(this->heap); + this->heap = {}; + + R_ABORT_UNLESS(UnmapImpl(this->heap_address, this->heap_size)); + + this->heap_address = 0; + } + } + } + + void *Allocate(size_t size) { + void * const ret = util::GetReference(this->heap).Allocate(size); + if (AMS_LIKELY(ret != nullptr)) { + ++this->outstanding_allocations; + } + return ret; + } + + bool TryFree(void *p) { + if (this->IsAllocated(p)) { + --this->outstanding_allocations; + + util::GetReference(this->heap).Free(p); + + return true; + } else { + return false; + } + } + + bool IsAllocated(void *p) const { + const uintptr_t address = reinterpret_cast<uintptr_t>(p); + + return this->heap_address != 0 && (this->heap_address <= address && address < this->heap_address + this->heap_size); + } + + void Reset() { + /* This should require no remaining allocations. */ + AMS_ABORT_UNLESS(this->outstanding_allocations == 0); + + /* Free the heap. */ + this->TryRelease(); + AMS_ABORT_UNLESS(this->heap_address == 0); + + /* Clear the heap size. */ + this->heap_size = 0; + } + }; + + Result MapByHeap(uintptr_t *out, size_t size) { + R_TRY(os::SetMemoryHeapSize(size)); + R_RETURN(os::AllocateMemoryBlock(out, size)); + } + + Result UnmapByHeap(uintptr_t address, size_t size) { + os::FreeMemoryBlock(address, size); + R_RETURN(os::SetMemoryHeapSize(0)); + } + + constinit os::SharedMemoryType g_applet_shared_memory; + + Result MapAppletMemory(uintptr_t *out, size_t &size) { + /* Ensure that we can try to get a native handle for the shared memory. */ + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(bool, s_initialized_memlet, false); + if (AMS_UNLIKELY(!s_initialized_memlet)) { + R_ABORT_UNLESS(::memletInitialize()); + s_initialized_memlet = true; + } + + /* Try to get a shared handle for the memory. */ + ::Handle shmem_handle = INVALID_HANDLE; + u64 shmem_size = 0; + if (R_FAILED(::memletCreateAppletSharedMemory(std::addressof(shmem_handle), std::addressof(shmem_size), size))) { + /* If we fail, set the heap size to 0. */ + size = 0; + R_SUCCEED(); + } + + /* Set the output size. */ + size = shmem_size; + + /* Setup the shared memory. */ + os::AttachSharedMemory(std::addressof(g_applet_shared_memory), shmem_size, shmem_handle, true); + + /* Map the shared memory. */ + void *mem = os::MapSharedMemory(std::addressof(g_applet_shared_memory), os::MemoryPermission_ReadWrite); + AMS_ABORT_UNLESS(mem != nullptr); + + /* Set the output. */ + *out = reinterpret_cast<uintptr_t>(mem); + R_SUCCEED(); + } + + Result UnmapAppletMemory(uintptr_t, size_t) { + /* Check that it's possible for us to unmap. */ + AMS_ABORT_UNLESS(os::GetSharedMemoryHandle(std::addressof(g_applet_shared_memory)) != os::InvalidNativeHandle); + + /* Unmap. */ + os::DestroySharedMemory(std::addressof(g_applet_shared_memory)); + + /* Check that we unmapped successfully. */ + AMS_ABORT_UNLESS(os::GetSharedMemoryHandle(std::addressof(g_applet_shared_memory)) == os::InvalidNativeHandle); + R_SUCCEED(); + } + + /* Dynamic allocation globals. */ + constinit os::SdkMutex g_romfs_build_lock; + constinit ncm::ProgramId g_dynamic_heap_program_id{}; + + constinit bool g_building_from_dynamic_heap = false; + + constinit DynamicHeap<os::AllocateUnsafeMemory, os::FreeUnsafeMemory> g_dynamic_app_heap; + constinit DynamicHeap<MapByHeap, UnmapByHeap> g_dynamic_sys_heap; + constinit DynamicHeap<MapAppletMemory, UnmapAppletMemory> g_dynamic_let_heap; + + void InitializeDynamicHeapForBuildRomfs(ncm::ProgramId program_id) { + if (program_id == g_dynamic_heap_program_id && g_dynamic_app_heap.heap_size > 0) { + /* This romfs will build out of dynamic heap. */ + g_building_from_dynamic_heap = true; + + g_dynamic_app_heap.Map(); + + if (g_dynamic_sys_heap.heap_size > 0) { + g_dynamic_sys_heap.Map(); + } + + if (g_dynamic_let_heap.heap_size > 0) { + g_dynamic_let_heap.Map(); + } + } + } + + void FinalizeDynamicHeapForBuildRomfs() { + /* We are definitely no longer building out of dynamic heap. */ + g_building_from_dynamic_heap = false; + + g_dynamic_app_heap.TryRelease(); + g_dynamic_let_heap.Reset(); + } + + constexpr bool CanAllocateFromDynamicAppletHeap(AllocationType type) { + switch (type) { + case AllocationType_FullPath: + case AllocationType_SourceInfo: + case AllocationType_Memory: + case AllocationType_TableCache: + return false; + default: + return true; + } + } + + } + + void *AllocateTracked(AllocationType type, size_t size) { + if (g_building_from_dynamic_heap) { + void *ret = nullptr; + if (CanAllocateFromDynamicAppletHeap(type) && g_dynamic_let_heap.heap_address != 0) { + ret = g_dynamic_let_heap.Allocate(size); + } + + if (ret == nullptr && g_dynamic_app_heap.heap_address != 0) { + ret = g_dynamic_app_heap.Allocate(size); + } + + if (ret == nullptr && g_dynamic_sys_heap.heap_address != 0) { + ret = g_dynamic_sys_heap.Allocate(size); + } + + if (ret == nullptr) { + ret = std::malloc(size); + } + + return ret; + } else { + return std::malloc(size); + } + } + + void FreeTracked(AllocationType type, void *p, size_t size) { + AMS_UNUSED(type); + AMS_UNUSED(size); + + if (g_dynamic_let_heap.TryFree(p)) { + if (!g_building_from_dynamic_heap) { + g_dynamic_let_heap.TryRelease(); + } + } else if (g_dynamic_app_heap.TryFree(p)) { + if (!g_building_from_dynamic_heap) { + g_dynamic_app_heap.TryRelease(); + } + } else if (g_dynamic_sys_heap.TryFree(p)) { + if (!g_building_from_dynamic_heap) { + g_dynamic_sys_heap.TryRelease(); + } + } else { + std::free(p); + } + } + + namespace { + + constexpr u32 EmptyEntry = 0xFFFFFFFF; + constexpr size_t FilePartitionOffset = 0x200; + + struct Header { + s64 header_size; + s64 dir_hash_table_ofs; + s64 dir_hash_table_size; + s64 dir_table_ofs; + s64 dir_table_size; + s64 file_hash_table_ofs; + s64 file_hash_table_size; + s64 file_table_ofs; + s64 file_table_size; + s64 file_partition_ofs; + }; + static_assert(util::is_pod<Header>::value && sizeof(Header) == 0x50); + + struct DirectoryEntry { + u32 parent; + u32 sibling; + u32 child; + u32 file; + u32 hash; + u32 name_size; + char name[]; + }; + static_assert(util::is_pod<DirectoryEntry>::value && sizeof(DirectoryEntry) == 0x18); + + struct FileEntry { + u32 parent; + u32 sibling; + s64 offset; + s64 size; + u32 hash; + u32 name_size; + char name[]; + }; + static_assert(util::is_pod<FileEntry>::value && sizeof(FileEntry) == 0x20); + + class DynamicTableCache { + NON_COPYABLE(DynamicTableCache); + NON_MOVEABLE(DynamicTableCache); + private: + static constexpr size_t MaxCachedSize = (1_MB / 4); + private: + size_t m_cache_bitsize; + size_t m_cache_size; + protected: + void *m_cache; + protected: + DynamicTableCache(size_t sz) { + m_cache_size = util::CeilingPowerOfTwo(std::min(sz, MaxCachedSize)); + m_cache = AllocateTracked(AllocationType_TableCache, m_cache_size); + while (m_cache == nullptr) { + m_cache_size >>= 1; + AMS_ABORT_UNLESS(m_cache_size >= 16_KB); + m_cache = AllocateTracked(AllocationType_TableCache, m_cache_size); + } + m_cache_bitsize = util::CountTrailingZeros(m_cache_size); + } + + ~DynamicTableCache() { + FreeTracked(AllocationType_TableCache, m_cache, m_cache_size); + } + + ALWAYS_INLINE size_t GetCacheSize() const { return static_cast<size_t>(1) << m_cache_bitsize; } + }; + + class HashTableStorage : public DynamicTableCache { + public: + HashTableStorage(size_t sz) : DynamicTableCache(sz) { /* ... */ } + + ALWAYS_INLINE u32 *GetBuffer() { return reinterpret_cast<u32 *>(m_cache); } + ALWAYS_INLINE size_t GetBufferSize() const { return DynamicTableCache::GetCacheSize(); } + }; + + template<typename Entry> + class TableReader : public DynamicTableCache { + NON_COPYABLE(TableReader); + NON_MOVEABLE(TableReader); + private: + static constexpr size_t FallbackCacheSize = 1_KB; + private: + ams::fs::IStorage *m_storage; + size_t m_offset; + size_t m_size; + size_t m_cache_idx; + u8 m_fallback_cache[FallbackCacheSize]; + private: + ALWAYS_INLINE bool Read(size_t ofs, void *dst, size_t size) { + R_TRY_CATCH(m_storage->Read(m_offset + ofs, dst, size)) { + R_CATCH(fs::ResultNcaExternalKeyNotFound) { return false; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + return true; + } + ALWAYS_INLINE bool ReloadCacheImpl(size_t idx) { + const size_t rel_ofs = idx * this->GetCacheSize(); + AMS_ABORT_UNLESS(rel_ofs < m_size); + const size_t new_cache_size = std::min(m_size - rel_ofs, this->GetCacheSize()); + if (!this->Read(rel_ofs, m_cache, new_cache_size)) { + return false; + } + + m_cache_idx = idx; + return true; + } + + ALWAYS_INLINE bool ReloadCache(size_t idx) { + if (m_cache_idx != idx) { + if (!this->ReloadCacheImpl(idx)) { + return false; + } + } + + return true; + } + + ALWAYS_INLINE size_t GetCacheIndex(u32 ofs) { + return ofs / this->GetCacheSize(); + } + public: + TableReader(ams::fs::IStorage *s, size_t ofs, size_t sz) : DynamicTableCache(sz), m_storage(s), m_offset(ofs), m_size(sz), m_cache_idx(0) { + AMS_ABORT_UNLESS(m_cache != nullptr); + this->ReloadCacheImpl(0); + } + + const Entry *GetEntry(u32 entry_offset) { + if (!this->ReloadCache(this->GetCacheIndex(entry_offset))) { + return nullptr; + } + + const size_t ofs = entry_offset % this->GetCacheSize(); + + const Entry *entry = reinterpret_cast<const Entry *>(reinterpret_cast<uintptr_t>(m_cache) + ofs); + if (AMS_UNLIKELY(this->GetCacheIndex(entry_offset) != this->GetCacheIndex(entry_offset + sizeof(Entry) + entry->name_size + sizeof(u32)))) { + if (!this->Read(entry_offset, m_fallback_cache, std::min(m_size - entry_offset, FallbackCacheSize))) { + return nullptr; + } + + entry = reinterpret_cast<const Entry *>(m_fallback_cache); + } + return entry; + } + }; + + template<typename Entry> + class TableWriter : public DynamicTableCache { + NON_COPYABLE(TableWriter); + NON_MOVEABLE(TableWriter); + private: + static constexpr size_t FallbackCacheSize = 1_KB; + private: + ::FsFile *m_file; + size_t m_offset; + size_t m_size; + size_t m_cache_idx; + u8 m_fallback_cache[FallbackCacheSize]; + size_t m_fallback_cache_entry_offset; + size_t m_fallback_cache_entry_size; + bool m_cache_dirty; + bool m_fallback_cache_dirty; + private: + ALWAYS_INLINE void Read(size_t ofs, void *dst, size_t sz) { + u64 read_size; + R_ABORT_UNLESS(fsFileRead(m_file, m_offset + ofs, dst, sz, 0, std::addressof(read_size))); + AMS_ABORT_UNLESS(read_size == sz); + } + + ALWAYS_INLINE void Write(size_t ofs, const void *src, size_t sz) { + R_ABORT_UNLESS(fsFileWrite(m_file, m_offset + ofs, src, sz, FsWriteOption_None)); + } + + ALWAYS_INLINE void Flush() { + AMS_ABORT_UNLESS(!(m_cache_dirty && m_fallback_cache_dirty)); + + if (m_cache_dirty) { + const size_t ofs = m_cache_idx * this->GetCacheSize(); + this->Write(ofs, m_cache, std::min(m_size - ofs, this->GetCacheSize())); + m_cache_dirty = false; + } + if (m_fallback_cache_dirty) { + this->Write(m_fallback_cache_entry_offset, m_fallback_cache, m_fallback_cache_entry_size); + m_fallback_cache_dirty = false; + } + } + + ALWAYS_INLINE size_t GetCacheIndex(u32 ofs) { + return ofs / this->GetCacheSize(); + } + + ALWAYS_INLINE void RefreshCacheImpl() { + const size_t cur_cache = m_cache_idx * this->GetCacheSize(); + this->Read(cur_cache, m_cache, std::min(m_size - cur_cache, this->GetCacheSize())); + } + + ALWAYS_INLINE void RefreshCache(u32 entry_offset) { + if (size_t idx = this->GetCacheIndex(entry_offset); idx != m_cache_idx || m_fallback_cache_dirty) { + this->Flush(); + m_cache_idx = idx; + this->RefreshCacheImpl(); + } + } + public: + TableWriter(::FsFile *f, size_t ofs, size_t sz) : DynamicTableCache(sz), m_file(f), m_offset(ofs), m_size(sz), m_cache_idx(0), m_fallback_cache_entry_offset(), m_fallback_cache_entry_size(), m_cache_dirty(), m_fallback_cache_dirty() { + AMS_ABORT_UNLESS(m_cache != nullptr); + + std::memset(m_cache, 0, this->GetCacheSize()); + std::memset(m_fallback_cache, 0, sizeof(m_fallback_cache)); + for (size_t cur = 0; cur < m_size; cur += this->GetCacheSize()) { + this->Write(cur, m_cache, std::min(m_size - cur, this->GetCacheSize())); + } + } + + ~TableWriter() { + this->Flush(); + } + + Entry *GetEntry(u32 entry_offset, u32 name_len) { + this->RefreshCache(entry_offset); + + const size_t ofs = entry_offset % this->GetCacheSize(); + + Entry *entry = reinterpret_cast<Entry *>(reinterpret_cast<uintptr_t>(m_cache) + ofs); + if (ofs + sizeof(Entry) + util::AlignUp(name_len, sizeof(u32)) > this->GetCacheSize()) { + this->Flush(); + + m_fallback_cache_entry_offset = entry_offset; + m_fallback_cache_entry_size = sizeof(Entry) + util::AlignUp(name_len, sizeof(u32)); + this->Read(m_fallback_cache_entry_offset, m_fallback_cache, m_fallback_cache_entry_size); + + entry = reinterpret_cast<Entry *>(m_fallback_cache); + m_fallback_cache_dirty = true; + } else { + m_cache_dirty = true; + } + + return entry; + } + }; + + using DirectoryTableWriter = TableWriter<DirectoryEntry>; + using FileTableWriter = TableWriter<FileEntry>; + + constexpr inline u32 CalculatePathHash(u32 parent, const char *path, u32 start, size_t path_len) { + u32 hash = parent ^ 123456789; + for (size_t i = 0; i < path_len; i++) { + hash = (hash >> 5) | (hash << 27); + hash ^= static_cast<unsigned char>(path[start + i]); + } + return hash; + } + + constexpr inline size_t GetHashTableSize(size_t num_entries) { + if (num_entries < 3) { + return 3; + } else if (num_entries < 19) { + return num_entries | 1; + } else { + size_t count = num_entries; + while ((count % 2 == 0) || + (count % 3 == 0) || + (count % 5 == 0) || + (count % 7 == 0) || + (count % 11 == 0) || + (count % 13 == 0) || + (count % 17 == 0)) + { + count++; + } + return count; + } + } + + constinit os::SdkMutex g_fs_romfs_path_lock; + constinit char g_fs_romfs_path_buffer[fs::EntryNameLengthMax + 1]; + + NOINLINE void OpenFileSystemRomfsDirectory(FsDir *out, ncm::ProgramId program_id, BuildDirectoryContext *parent, fs::OpenDirectoryMode mode, FsFileSystem *fs) { + std::scoped_lock lk(g_fs_romfs_path_lock); + parent->GetPath(g_fs_romfs_path_buffer); + R_ABORT_UNLESS(mitm::fs::OpenAtmosphereRomfsDirectory(out, program_id, g_fs_romfs_path_buffer, mode, fs)); + } + + } + + Builder::Builder(ncm::ProgramId pr_id) : m_program_id(pr_id), m_num_dirs(0), m_num_files(0), m_dir_table_size(0), m_file_table_size(0), m_dir_hash_table_size(0), m_file_hash_table_size(0), m_file_partition_size(0) { + /* Ensure only one romfs is built at any time. */ + g_romfs_build_lock.Lock(); + + /* If we should be using dynamic heap, turn it on. */ + InitializeDynamicHeapForBuildRomfs(m_program_id); + + auto res = m_directories.emplace(std::unique_ptr<BuildDirectoryContext>(AllocateTyped<BuildDirectoryContext>(AllocationType_BuildDirContext, BuildDirectoryContext::RootTag{}))); + AMS_ABORT_UNLESS(res.second); + m_root = res.first->get(); + m_num_dirs = 1; + m_dir_table_size = 0x18; + } + + Builder::~Builder() { + /* If we have nothing remaining in dynamic heap, release it. */ + FinalizeDynamicHeapForBuildRomfs(); + + /* Release the romfs build lock. */ + g_romfs_build_lock.Unlock(); + } + + + void Builder::AddDirectory(BuildDirectoryContext **out, BuildDirectoryContext *parent_ctx, std::unique_ptr<BuildDirectoryContext> child_ctx) { + /* Set parent context member. */ + child_ctx->parent = parent_ctx; + + /* Check if the directory already exists. */ + auto existing = m_directories.find(child_ctx); + if (existing != m_directories.end()) { + *out = existing->get(); + return; + } + + /* Add a new directory. */ + m_num_dirs++; + m_dir_table_size += sizeof(DirectoryEntry) + util::AlignUp(child_ctx->path_len, 4); + + *out = child_ctx.get(); + m_directories.emplace(std::move(child_ctx)); + } + + void Builder::AddFile(BuildDirectoryContext *parent_ctx, std::unique_ptr<BuildFileContext> file_ctx) { + /* Set parent context member. */ + file_ctx->parent = parent_ctx; + + /* Check if the file already exists. */ + if (m_files.find(file_ctx) != m_files.end()) { + return; + } + + /* Add a new file. */ + m_num_files++; + m_file_table_size += sizeof(FileEntry) + util::AlignUp(file_ctx->path_len, 4); + m_files.emplace(std::move(file_ctx)); + } + + void Builder::VisitDirectory(FsFileSystem *fs, BuildDirectoryContext *parent) { + FsDir dir; + + /* Get number of child directories. */ + s64 num_child_dirs = 0; + { + OpenFileSystemRomfsDirectory(std::addressof(dir), m_program_id, parent, OpenDirectoryMode_Directory, fs); + ON_SCOPE_EXIT { fsDirClose(std::addressof(dir)); }; + R_ABORT_UNLESS(fsDirGetEntryCount(std::addressof(dir), std::addressof(num_child_dirs))); + } + AMS_ABORT_UNLESS(num_child_dirs >= 0); + + { + BuildDirectoryContext **child_dirs = num_child_dirs != 0 ? reinterpret_cast<BuildDirectoryContext **>(AllocateTracked(AllocationType_DirPointerArray, sizeof(BuildDirectoryContext *) * num_child_dirs)) : nullptr; + AMS_ABORT_UNLESS(num_child_dirs == 0 || child_dirs != nullptr); + ON_SCOPE_EXIT { if (child_dirs != nullptr) { FreeTracked(AllocationType_DirPointerArray, child_dirs, sizeof(BuildDirectoryContext *) * num_child_dirs); } }; + + s64 cur_child_dir_ind = 0; + { + OpenFileSystemRomfsDirectory(std::addressof(dir), m_program_id, parent, OpenDirectoryMode_All, fs); + ON_SCOPE_EXIT { fsDirClose(std::addressof(dir)); }; + + s64 read_entries = 0; + while (true) { + R_ABORT_UNLESS(fsDirRead(std::addressof(dir), std::addressof(read_entries), 1, std::addressof(m_dir_entry))); + if (read_entries != 1) { + break; + } + + AMS_ABORT_UNLESS(m_dir_entry.type == FsDirEntryType_Dir || m_dir_entry.type == FsDirEntryType_File); + if (m_dir_entry.type == FsDirEntryType_Dir) { + AMS_ABORT_UNLESS(child_dirs != nullptr); + + BuildDirectoryContext *real_child = nullptr; + this->AddDirectory(std::addressof(real_child), parent, std::unique_ptr<BuildDirectoryContext>(AllocateTyped<BuildDirectoryContext>(AllocationType_BuildDirContext, m_dir_entry.name, strlen(m_dir_entry.name)))); + AMS_ABORT_UNLESS(real_child != nullptr); + child_dirs[cur_child_dir_ind++] = real_child; + AMS_ABORT_UNLESS(cur_child_dir_ind <= num_child_dirs); + } else /* if (m_dir_entry.type == FsDirEntryType_File) */ { + this->AddFile(parent, std::unique_ptr<BuildFileContext>(AllocateTyped<BuildFileContext>(AllocationType_BuildFileContext, m_dir_entry.name, strlen(m_dir_entry.name), m_dir_entry.file_size, 0, m_cur_source_type))); + } + } + } + + AMS_ABORT_UNLESS(num_child_dirs == cur_child_dir_ind); + for (s64 i = 0; i < num_child_dirs; i++) { + this->VisitDirectory(fs, child_dirs[i]); + } + } + + } + + class DirectoryTableReader : public TableReader<DirectoryEntry> { + public: + DirectoryTableReader(ams::fs::IStorage *s, size_t ofs, size_t sz) : TableReader(s, ofs, sz) { /* ... */ } + }; + + class FileTableReader : public TableReader<FileEntry> { + public: + FileTableReader(ams::fs::IStorage *s, size_t ofs, size_t sz) : TableReader(s, ofs, sz) { /* ... */ } + }; + + void Builder::VisitDirectory(BuildDirectoryContext *parent, u32 parent_offset, DirectoryTableReader &dir_table, FileTableReader &file_table) { + const DirectoryEntry *parent_entry = dir_table.GetEntry(parent_offset); + if (AMS_UNLIKELY(parent_entry == nullptr)) { + return; + } + + u32 cur_file_offset = parent_entry->file; + while (cur_file_offset != EmptyEntry) { + const FileEntry *cur_file = file_table.GetEntry(cur_file_offset); + if (AMS_UNLIKELY(cur_file == nullptr)) { + return; + } + + this->AddFile(parent, std::unique_ptr<BuildFileContext>(AllocateTyped<BuildFileContext>(AllocationType_BuildFileContext, cur_file->name, cur_file->name_size, cur_file->size, cur_file->offset, m_cur_source_type))); + + cur_file_offset = cur_file->sibling; + } + + u32 cur_child_offset = parent_entry->child; + while (cur_child_offset != EmptyEntry) { + BuildDirectoryContext *real_child = nullptr; + u32 next_child_offset = 0; + { + const DirectoryEntry *cur_child = dir_table.GetEntry(cur_child_offset); + if (AMS_UNLIKELY(cur_child == nullptr)) { + return; + } + + this->AddDirectory(std::addressof(real_child), parent, std::unique_ptr<BuildDirectoryContext>(AllocateTyped<BuildDirectoryContext>(AllocationType_BuildDirContext, cur_child->name, cur_child->name_size))); + AMS_ABORT_UNLESS(real_child != nullptr); + + next_child_offset = cur_child->sibling; + __asm__ __volatile__("" ::: "memory"); + } + + this->VisitDirectory(real_child, cur_child_offset, dir_table, file_table); + + cur_child_offset = next_child_offset; + } + } + + + void Builder::AddSdFiles() { + /* Open Sd Card filesystem. */ + FsFileSystem sd_filesystem; + R_ABORT_UNLESS(fsOpenSdCardFileSystem(std::addressof(sd_filesystem))); + ON_SCOPE_EXIT { fsFsClose(std::addressof(sd_filesystem)); }; + + /* If there is no romfs folder on the SD, don't bother continuing. */ + { + FsDir dir; + if (R_FAILED(mitm::fs::OpenAtmosphereRomfsDirectory(std::addressof(dir), m_program_id, m_root->path, OpenDirectoryMode_Directory, std::addressof(sd_filesystem)))) { + return; + } + fsDirClose(std::addressof(dir)); + } + + m_cur_source_type = DataSourceType::LooseSdFile; + this->VisitDirectory(std::addressof(sd_filesystem), m_root); + } + + void Builder::AddStorageFiles(ams::fs::IStorage *storage, DataSourceType source_type) { + Header header; + R_ABORT_UNLESS(storage->Read(0, std::addressof(header), sizeof(Header))); + AMS_ABORT_UNLESS(header.header_size == sizeof(Header)); + + /* Read tables. */ + DirectoryTableReader dir_table(storage, header.dir_table_ofs, header.dir_table_size); + FileTableReader file_table(storage, header.file_table_ofs, header.file_table_size); + + m_cur_source_type = source_type; + this->VisitDirectory(m_root, 0x0, dir_table, file_table); + } + + void Builder::Build(SourceInfoVector *out_infos) { + /* Clear output. */ + out_infos->clear(); + + /* Open an SD card filesystem. */ + FsFileSystem sd_filesystem; + R_ABORT_UNLESS(fsOpenSdCardFileSystem(std::addressof(sd_filesystem))); + ON_SCOPE_EXIT { fsFsClose(std::addressof(sd_filesystem)); }; + + /* Calculate hash table sizes. */ + const size_t num_dir_hash_table_entries = GetHashTableSize(m_num_dirs); + const size_t num_file_hash_table_entries = GetHashTableSize(m_num_files); + m_dir_hash_table_size = sizeof(u32) * num_dir_hash_table_entries; + m_file_hash_table_size = sizeof(u32) * num_file_hash_table_entries; + + /* Allocate metadata, make pointers. */ + Header *header = reinterpret_cast<Header *>(AllocateTracked(AllocationType_Memory, sizeof(Header))); + std::memset(header, 0x00, sizeof(*header)); + + /* Open metadata file. */ + const size_t metadata_size = m_dir_hash_table_size + m_dir_table_size + m_file_hash_table_size + m_file_table_size; + FsFile metadata_file; + R_ABORT_UNLESS(mitm::fs::CreateAndOpenAtmosphereSdFile(std::addressof(metadata_file), m_program_id, "romfs_metadata.bin", metadata_size)); + + /* Ensure later hash tables will have correct defaults. */ + static_assert(EmptyEntry == 0xFFFFFFFF); + + /* Emplace metadata source info. */ + out_infos->emplace_back(0, sizeof(*header), DataSourceType::Memory, header); + + /* Process Files. */ + { + u32 entry_offset = 0; + BuildFileContext *cur_file = nullptr; + BuildFileContext *prev_file = nullptr; + for (const auto &it : m_files) { + cur_file = it.get(); + + /* By default, pad to 0x10 alignment. */ + m_file_partition_size = util::AlignUp(m_file_partition_size, 0x10); + + /* Check if extra padding is present in original source, preserve it to make our life easier. */ + const bool is_storage_or_file = cur_file->source_type == DataSourceType::Storage || cur_file->source_type == DataSourceType::File; + if (prev_file != nullptr && prev_file->source_type == cur_file->source_type && is_storage_or_file) { + const s64 expected = m_file_partition_size - prev_file->offset + prev_file->orig_offset; + if (expected != cur_file->orig_offset) { + AMS_ABORT_UNLESS(expected <= cur_file->orig_offset); + m_file_partition_size += cur_file->orig_offset - expected; + } + } + + /* Calculate offsets. */ + cur_file->offset = m_file_partition_size; + m_file_partition_size += cur_file->size; + cur_file->entry_offset = entry_offset; + entry_offset += sizeof(FileEntry) + util::AlignUp(cur_file->path_len, 4); + + /* Save current file as prev for next iteration. */ + prev_file = cur_file; + } + /* Assign deferred parent/sibling ownership. */ + for (auto it = m_files.rbegin(); it != m_files.rend(); it++) { + cur_file = it->get(); + cur_file->sibling = cur_file->parent->file; + cur_file->parent->file = cur_file; + } + } + + /* Process Directories. */ + { + u32 entry_offset = 0; + BuildDirectoryContext *cur_dir = nullptr; + for (const auto &it : m_directories) { + cur_dir = it.get(); + cur_dir->entry_offset = entry_offset; + entry_offset += sizeof(DirectoryEntry) + util::AlignUp(cur_dir->path_len, 4); + } + /* Assign deferred parent/sibling ownership. */ + for (auto it = m_directories.rbegin(); it != m_directories.rend(); it++) { + cur_dir = it->get(); + if (cur_dir == m_root) { + continue; + } + cur_dir->sibling = cur_dir->parent->child; + cur_dir->parent->child = cur_dir; + } + } + + /* Set all files' hash value = hash index. */ + for (const auto &it : m_files) { + BuildFileContext *cur_file = it.get(); + cur_file->hash_value = CalculatePathHash(cur_file->parent->entry_offset, cur_file->path, 0, cur_file->path_len) % num_file_hash_table_entries; + } + + /* Set all directories' hash value = hash index. */ + for (const auto &it : m_directories) { + BuildDirectoryContext *cur_dir = it.get(); + cur_dir->hash_value = CalculatePathHash(cur_dir == m_root ? 0 : cur_dir->parent->entry_offset, cur_dir->path, 0, cur_dir->path_len) % num_dir_hash_table_entries; + } + + /* Write hash tables. */ + { + HashTableStorage hash_table_storage(std::max(m_dir_hash_table_size, m_file_hash_table_size)); + + u32 *hash_table = hash_table_storage.GetBuffer(); + size_t hash_table_size = hash_table_storage.GetBufferSize(); + + /* Write the file hash table. */ + for (size_t ofs = 0; ofs < m_file_hash_table_size; ofs += hash_table_size) { + std::memset(hash_table, 0xFF, hash_table_size); + + const u32 ofs_ind = ofs / sizeof(u32); + const u32 end_ind = (ofs + hash_table_size) / sizeof(u32); + + for (const auto &it : m_files) { + BuildFileContext *cur_file = it.get(); + if (cur_file->HasHashMark()) { + continue; + } + + if (const auto hash_ind = cur_file->hash_value; ofs_ind <= hash_ind && hash_ind < end_ind) { + cur_file->hash_value = hash_table[hash_ind - ofs_ind]; + hash_table[hash_ind - ofs_ind] = cur_file->entry_offset; + + cur_file->SetHashMark(); + } + } + + R_ABORT_UNLESS(fsFileWrite(std::addressof(metadata_file), m_dir_hash_table_size + m_dir_table_size + ofs, hash_table, std::min(m_file_hash_table_size - ofs, hash_table_size), FsWriteOption_None)); + } + + /* Write the directory hash table. */ + for (size_t ofs = 0; ofs < m_dir_hash_table_size; ofs += hash_table_size) { + std::memset(hash_table, 0xFF, hash_table_size); + + const u32 ofs_ind = ofs / sizeof(u32); + const u32 end_ind = (ofs + hash_table_size) / sizeof(u32); + + for (const auto &it : m_directories) { + BuildDirectoryContext *cur_dir = it.get(); + if (cur_dir->HasHashMark()) { + continue; + } + + if (const auto hash_ind = cur_dir->hash_value; ofs_ind <= hash_ind && hash_ind < end_ind) { + cur_dir->hash_value = hash_table[hash_ind - ofs_ind]; + hash_table[hash_ind - ofs_ind] = cur_dir->entry_offset; + + cur_dir->SetHashMark(); + } + } + + R_ABORT_UNLESS(fsFileWrite(std::addressof(metadata_file), ofs, hash_table, std::min(m_dir_hash_table_size - ofs, hash_table_size), FsWriteOption_None)); + } + } + + /* Replace sibling pointers with sibling entry_offsets, so that we can de-allocate as we go. */ + { + /* Set all directories sibling and file pointers. */ + for (const auto &it : m_directories) { + BuildDirectoryContext *cur_dir = it.get(); + + cur_dir->ClearHashMark(); + + cur_dir->sibling_offset = (cur_dir->sibling == nullptr) ? EmptyEntry : cur_dir->sibling->entry_offset; + cur_dir->child_offset = (cur_dir->child == nullptr) ? EmptyEntry : cur_dir->child->entry_offset; + cur_dir->file_offset = (cur_dir->file == nullptr) ? EmptyEntry : cur_dir->file->entry_offset; + + cur_dir->parent_offset = cur_dir == m_root ? 0 : cur_dir->parent->entry_offset; + } + + /* Replace all files' sibling pointers. */ + for (const auto &it : m_files) { + BuildFileContext *cur_file = it.get(); + + cur_file->ClearHashMark(); + + cur_file->sibling_offset = (cur_file->sibling == nullptr) ? EmptyEntry : cur_file->sibling->entry_offset; + } + } + + /* Write the file table. */ + { + FileTableWriter file_table(std::addressof(metadata_file), m_dir_hash_table_size + m_dir_table_size + m_file_hash_table_size, m_file_table_size); + + for (auto it = m_files.begin(); it != m_files.end(); it = m_files.erase(it)) { + BuildFileContext *cur_file = it->get(); + FileEntry *cur_entry = file_table.GetEntry(cur_file->entry_offset, cur_file->path_len); + + /* Set entry fields. */ + cur_entry->parent = cur_file->parent->entry_offset; + cur_entry->sibling = cur_file->sibling_offset; + cur_entry->offset = cur_file->offset; + cur_entry->size = cur_file->size; + cur_entry->hash = cur_file->hash_value; + + /* Set name. */ + const u32 name_size = cur_file->path_len; + cur_entry->name_size = name_size; + if (name_size) { + std::memcpy(cur_entry->name, cur_file->path, name_size); + for (size_t i = name_size; i < util::AlignUp(name_size, 4); i++) { + cur_entry->name[i] = 0; + } + } + + /* Emplace a source. */ + switch (cur_file->source_type) { + case DataSourceType::Storage: + case DataSourceType::File: + { + /* Try to compact if possible. */ + auto &back = out_infos->back(); + if (back.source_type == cur_file->source_type) { + back.size = cur_file->offset + FilePartitionOffset + cur_file->size - back.virtual_offset; + } else { + out_infos->emplace_back(cur_file->offset + FilePartitionOffset, cur_file->size, cur_file->source_type, cur_file->orig_offset + FilePartitionOffset); + } + } + break; + case DataSourceType::LooseSdFile: + { + char full_path[fs::EntryNameLengthMax + 1]; + const size_t path_needed_size = cur_file->GetPathLength() + 1; + AMS_ABORT_UNLESS(path_needed_size <= sizeof(full_path)); + cur_file->GetPath(full_path); + + FreeTracked(AllocationType_FileName, cur_file->path, cur_file->path_len + 1); + cur_file->path = nullptr; + + char *new_path = static_cast<char *>(AllocateTracked(AllocationType_FullPath, path_needed_size)); + std::memcpy(new_path, full_path, path_needed_size); + out_infos->emplace_back(cur_file->offset + FilePartitionOffset, cur_file->size, cur_file->source_type, new_path); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + /* Write the directory table. */ + { + DirectoryTableWriter dir_table(std::addressof(metadata_file), m_dir_hash_table_size, m_dir_table_size); + + for (auto it = m_directories.begin(); it != m_directories.end(); it = m_directories.erase(it)) { + BuildDirectoryContext *cur_dir = it->get(); + DirectoryEntry *cur_entry = dir_table.GetEntry(cur_dir->entry_offset, cur_dir->path_len); + + /* Set entry fields. */ + cur_entry->parent = cur_dir->parent_offset; + cur_entry->sibling = cur_dir->sibling_offset; + cur_entry->child = cur_dir->child_offset; + cur_entry->file = cur_dir->file_offset; + cur_entry->hash = cur_dir->hash_value; + + /* Set name. */ + const u32 name_size = cur_dir->path_len; + cur_entry->name_size = name_size; + if (name_size) { + std::memcpy(cur_entry->name, cur_dir->path, name_size); + for (size_t i = name_size; i < util::AlignUp(name_size, 4); i++) { + cur_entry->name[i] = 0; + } + } + } + } + + /* Delete maps. */ + m_root = nullptr; + m_directories.clear(); + m_files.clear(); + + /* Set header fields. */ + header->header_size = sizeof(*header); + header->file_hash_table_size = m_file_hash_table_size; + header->file_table_size = m_file_table_size; + header->dir_hash_table_size = m_dir_hash_table_size; + header->dir_table_size = m_dir_table_size; + header->file_partition_ofs = FilePartitionOffset; + header->dir_hash_table_ofs = util::AlignUp(FilePartitionOffset + m_file_partition_size, 4); + header->dir_table_ofs = header->dir_hash_table_ofs + header->dir_hash_table_size; + header->file_hash_table_ofs = header->dir_table_ofs + header->dir_table_size; + header->file_table_ofs = header->file_hash_table_ofs + header->file_hash_table_size; + + /* Save metadata to the SD card, to save on memory space. */ + { + R_ABORT_UNLESS(fsFileFlush(std::addressof(metadata_file))); + out_infos->emplace_back(header->dir_hash_table_ofs, metadata_size, DataSourceType::Metadata, new RemoteFile(metadata_file)); + } + } + + Result ConfigureDynamicHeap(u64 *out_size, ncm::ProgramId program_id, const cfg::OverrideStatus &status, bool is_application) { + /* Baseline: use no dynamic heap. */ + *out_size = 0; + + /* If the process is not an application, we do not care about dynamic heap. */ + R_SUCCEED_IF(!is_application); + + /* First, we need to ensure that, if the game used dynamic heap, we clear it. */ + if (g_dynamic_app_heap.heap_size > 0) { + mitm::fs::FinalizeLayeredRomfsStorage(g_dynamic_heap_program_id); + + /* Free the heap. */ + g_dynamic_app_heap.Reset(); + g_dynamic_sys_heap.Reset(); + } + + /* Next, if we aren't going to end up building a romfs, we can ignore dynamic heap. */ + R_SUCCEED_IF(!status.IsProgramSpecific()); + + /* Only mitm if there is actually an override romfs. */ + R_SUCCEED_IF(!mitm::fs::HasSdRomfsContent(program_id)); + + /* Next, set the new program id for dynamic heap. */ + g_dynamic_heap_program_id = program_id; + g_dynamic_app_heap.heap_size = GetDynamicAppHeapSize(g_dynamic_heap_program_id); + g_dynamic_sys_heap.heap_size = GetDynamicSysHeapSize(g_dynamic_heap_program_id); + g_dynamic_let_heap.heap_size = MaximumRomfsBuildAppletMemorySize; + + /* Set output. */ + *out_size = g_dynamic_app_heap.heap_size; + + R_SUCCEED(); + } + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.hpp new file mode 100644 index 00000000..5eeb7dc0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_romfs.hpp @@ -0,0 +1,379 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> + +namespace ams::mitm::fs::romfs { + + enum class DataSourceType : u8 { + Storage, + File, + LooseSdFile, + Metadata, + Memory, + }; + + enum AllocationType { + AllocationType_FileName, + AllocationType_DirName, + AllocationType_FullPath, + AllocationType_SourceInfo, + AllocationType_BuildFileContext, + AllocationType_BuildDirContext, + AllocationType_TableCache, + AllocationType_DirPointerArray, + AllocationType_DirContextSet, + AllocationType_FileContextSet, + AllocationType_Memory, + + AllocationType_Count, + }; + + void *AllocateTracked(AllocationType type, size_t size); + void FreeTracked(AllocationType type, void *p, size_t size); + + template<typename T, typename... Args> + T *AllocateTyped(AllocationType type, Args &&... args) { + void *mem = AllocateTracked(type, sizeof(T)); + return std::construct_at(static_cast<T *>(mem), std::forward<Args>(args)...); + } + + template<AllocationType AllocType, typename T> + class TrackedAllocator { + public: + using value_type = T; + + template<typename U> + struct rebind { + using other = TrackedAllocator<AllocType, U>; + }; + public: + TrackedAllocator() = default; + + T *allocate(size_t n) { + return static_cast<T *>(AllocateTracked(AllocType, sizeof(T) * n)); + } + + void deallocate(T *p, size_t n) { + FreeTracked(AllocType, p, sizeof(T) * n); + } + }; + + struct SourceInfo { + s64 virtual_offset; + s64 size; + union { + struct { + s64 offset; + } storage_source_info; + struct { + s64 offset; + } file_source_info; + struct { + char *path; + } loose_source_info; + struct { + ams::fs::fsa::IFile *file; + } metadata_source_info; + struct { + u8 *data; + } memory_source_info; + }; + DataSourceType source_type; + bool cleaned_up; + + SourceInfo(s64 v_o, s64 sz, DataSourceType type, s64 p_o) : virtual_offset(v_o), size(sz), source_type(type), cleaned_up(false) { + switch (this->source_type) { + case DataSourceType::Storage: + this->storage_source_info.offset = p_o; + break; + case DataSourceType::File: + this->file_source_info.offset = p_o; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + SourceInfo(s64 v_o, s64 sz, DataSourceType type, void *arg) : virtual_offset(v_o), size(sz), source_type(type), cleaned_up(false) { + switch (this->source_type) { + case DataSourceType::LooseSdFile: + this->loose_source_info.path = static_cast<char *>(arg); + break; + case DataSourceType::Metadata: + this->metadata_source_info.file = static_cast<ams::fs::fsa::IFile *>(arg); + break; + case DataSourceType::Memory: + this->memory_source_info.data = static_cast<u8 *>(arg); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void Cleanup() { + AMS_ABORT_UNLESS(!this->cleaned_up); + this->cleaned_up = true; + + switch (this->source_type) { + case DataSourceType::Storage: + case DataSourceType::File: + break; + case DataSourceType::Metadata: + delete this->metadata_source_info.file; + break; + case DataSourceType::LooseSdFile: + FreeTracked(AllocationType_FullPath, this->loose_source_info.path, std::strlen(this->loose_source_info.path) + 1); + break; + case DataSourceType::Memory: + FreeTracked(AllocationType_Memory, this->memory_source_info.data, this->size); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + }; + + constexpr inline bool operator<(const SourceInfo &lhs, const SourceInfo &rhs) { + return lhs.virtual_offset < rhs.virtual_offset; + } + + constexpr inline bool operator<(const SourceInfo &lhs, const s64 rhs) { + return lhs.virtual_offset <= rhs; + } + + struct BuildFileContext; + + struct BuildDirectoryContext { + NON_COPYABLE(BuildDirectoryContext); + NON_MOVEABLE(BuildDirectoryContext); + + char *path; + union { + BuildDirectoryContext *parent; + }; + union { + BuildDirectoryContext *child; + struct { + u32 parent_offset; + u32 child_offset; + }; + }; + union { + BuildDirectoryContext *sibling; + u32 sibling_offset; + }; + union { + BuildFileContext *file; + u32 file_offset; + }; + u32 path_len; + u32 entry_offset; + u32 hash_value; + + struct RootTag{}; + + BuildDirectoryContext(RootTag) : parent(nullptr), child(nullptr), sibling(nullptr), file(nullptr), path_len(0), entry_offset(0), hash_value(0xFFFFFFFF) { + this->path = static_cast<char *>(AllocateTracked(AllocationType_DirName, 1)); + this->path[0] = '\x00'; + } + + BuildDirectoryContext(const char *entry_name, size_t entry_name_len) : parent(nullptr), child(nullptr), sibling(nullptr), file(nullptr), entry_offset(0) { + this->path_len = entry_name_len; + this->path = static_cast<char *>(AllocateTracked(AllocationType_DirName, this->path_len + 1)); + std::memcpy(this->path, entry_name, entry_name_len); + this->path[this->path_len] = '\x00'; + } + + ~BuildDirectoryContext() { + if (this->path != nullptr) { + FreeTracked(AllocationType_DirName, this->path, this->path_len + 1); + this->path = nullptr; + } + } + + void operator delete(void *p) { + FreeTracked(AllocationType_BuildDirContext, p, sizeof(BuildDirectoryContext)); + } + + size_t GetPathLength() const { + if (this->parent == nullptr) { + return 0; + } + + return this->parent->GetPathLength() + 1 + this->path_len; + } + + size_t GetPath(char *dst) const { + if (this->parent == nullptr) { + dst[0] = '\x00'; + return 0; + } + + const size_t parent_len = this->parent->GetPath(dst); + dst[parent_len] = '/'; + std::memcpy(dst + parent_len + 1, this->path, this->path_len); + dst[parent_len + 1 + this->path_len] = '\x00'; + return parent_len + 1 + this->path_len; + } + + bool HasHashMark() const { + return reinterpret_cast<uintptr_t>(this->sibling) & UINT64_C(0x8000000000000000); + } + + void SetHashMark() { + this->sibling = reinterpret_cast<BuildDirectoryContext *>(reinterpret_cast<uintptr_t>(this->sibling) | UINT64_C(0x8000000000000000)); + } + + void ClearHashMark() { + this->sibling = reinterpret_cast<BuildDirectoryContext *>(reinterpret_cast<uintptr_t>(this->sibling) & ~UINT64_C(0x8000000000000000)); + } + }; + + struct BuildFileContext { + NON_COPYABLE(BuildFileContext); + NON_MOVEABLE(BuildFileContext); + + char *path; + BuildDirectoryContext *parent; + union { + BuildFileContext *sibling; + u32 sibling_offset; + }; + s64 offset; + s64 size; + s64 orig_offset; + u32 path_len; + u32 entry_offset; + u32 hash_value; + DataSourceType source_type; + + BuildFileContext(const char *entry_name, size_t entry_name_len, s64 sz, s64 o_o, DataSourceType type) : parent(nullptr), sibling(nullptr), offset(0), size(sz), orig_offset(o_o), entry_offset(0), hash_value(0xFFFFFFFF), source_type(type) { + this->path_len = entry_name_len; + this->path = static_cast<char *>(AllocateTracked(AllocationType_FileName, this->path_len + 1)); + std::memcpy(this->path, entry_name, entry_name_len); + this->path[this->path_len] = 0; + } + + ~BuildFileContext() { + if (this->path != nullptr) { + FreeTracked(AllocationType_FileName, this->path, this->path_len + 1); + this->path = nullptr; + } + } + + void operator delete(void *p) { + FreeTracked(AllocationType_BuildFileContext, p, sizeof(BuildFileContext)); + } + + size_t GetPathLength() const { + if (this->parent == nullptr) { + return 0; + } + + return this->parent->GetPathLength() + 1 + this->path_len; + } + + size_t GetPath(char *dst) const { + if (this->parent == nullptr) { + dst[0] = '\x00'; + return 0; + } + + const size_t parent_len = this->parent->GetPath(dst); + dst[parent_len] = '/'; + std::memcpy(dst + parent_len + 1, this->path, this->path_len); + dst[parent_len + 1 + this->path_len] = '\x00'; + return parent_len + 1 + this->path_len; + } + + bool HasHashMark() const { + return reinterpret_cast<uintptr_t>(this->sibling) & UINT64_C(0x8000000000000000); + } + + void SetHashMark() { + this->sibling = reinterpret_cast<BuildFileContext *>(reinterpret_cast<uintptr_t>(this->sibling) | UINT64_C(0x8000000000000000)); + } + + void ClearHashMark() { + this->sibling = reinterpret_cast<BuildFileContext *>(reinterpret_cast<uintptr_t>(this->sibling) & ~UINT64_C(0x8000000000000000)); + } + }; + + class DirectoryTableReader; + class FileTableReader; + + class Builder { + NON_COPYABLE(Builder); + NON_MOVEABLE(Builder); + public: + using SourceInfoVector = std::vector<SourceInfo, TrackedAllocator<AllocationType_SourceInfo, SourceInfo>>; + private: + template<typename T> + struct Comparator { + static constexpr inline int Compare(const char *a, const char *b) { + unsigned char c1{}, c2{}; + while ((c1 = *a++) == (c2 = *b++)) { + if (c1 == '\x00') { + return 0; + } + } + return (c1 - c2); + } + + constexpr bool operator()(const std::unique_ptr<T> &lhs, const std::unique_ptr<T> &rhs) const { + char lhs_path[ams::fs::EntryNameLengthMax + 1]; + char rhs_path[ams::fs::EntryNameLengthMax + 1]; + lhs->GetPath(lhs_path); + rhs->GetPath(rhs_path); + return Comparator::Compare(lhs_path, rhs_path) < 0; + } + }; + + template<AllocationType AllocType, typename T> + using ContextSet = std::set<std::unique_ptr<T>, Comparator<T>, TrackedAllocator<AllocType, std::unique_ptr<T>>>; + private: + ncm::ProgramId m_program_id; + BuildDirectoryContext *m_root; + ContextSet<AllocationType_DirContextSet, BuildDirectoryContext> m_directories; + ContextSet<AllocationType_FileContextSet, BuildFileContext> m_files; + size_t m_num_dirs; + size_t m_num_files; + size_t m_dir_table_size; + size_t m_file_table_size; + size_t m_dir_hash_table_size; + size_t m_file_hash_table_size; + size_t m_file_partition_size; + + ::FsDirectoryEntry m_dir_entry; + DataSourceType m_cur_source_type; + private: + void VisitDirectory(FsFileSystem *fs, BuildDirectoryContext *parent); + void VisitDirectory(BuildDirectoryContext *parent, u32 parent_offset, DirectoryTableReader &dir_table, FileTableReader &file_table); + + void AddDirectory(BuildDirectoryContext **out, BuildDirectoryContext *parent_ctx, std::unique_ptr<BuildDirectoryContext> file_ctx); + void AddFile(BuildDirectoryContext *parent_ctx, std::unique_ptr<BuildFileContext> file_ctx); + public: + Builder(ncm::ProgramId pr_id); + ~Builder(); + + void AddSdFiles(); + void AddStorageFiles(ams::fs::IStorage *storage, DataSourceType source_type); + + void Build(SourceInfoVector *out_infos); + }; + + Result ConfigureDynamicHeap(u64 *out_size, ncm::ProgramId program_id, const cfg::OverrideStatus &status, bool is_application); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_save_utils.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_save_utils.cpp new file mode 100644 index 00000000..db5a8835 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_save_utils.cpp @@ -0,0 +1,114 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fsmitm_save_utils.hpp" + +namespace ams::mitm::fs { + + using namespace ams::fs; + + namespace { + + Result GetSaveDataSpaceIdString(const char **out_str, u8 space_id) { + switch (static_cast<SaveDataSpaceId>(space_id)) { + case SaveDataSpaceId::System: + case SaveDataSpaceId::ProperSystem: + *out_str = "sys"; + break; + case SaveDataSpaceId::User: + *out_str = "user"; + break; + case SaveDataSpaceId::SdSystem: + *out_str = "sd_sys"; + break; + case SaveDataSpaceId::Temporary: + *out_str = "temp"; + break; + case SaveDataSpaceId::SdUser: + *out_str = "sd_user"; + break; + case SaveDataSpaceId::SafeMode: + *out_str = "safe"; + break; + default: + R_THROW(fs::ResultInvalidSaveDataSpaceId()); + } + + R_SUCCEED(); + } + + Result GetSaveDataTypeString(const char **out_str, SaveDataType save_data_type) { + switch (save_data_type) { + case SaveDataType::System: + *out_str = "system"; + break; + case SaveDataType::Account: + *out_str = "account"; + break; + case SaveDataType::Bcat: + *out_str = "bcat"; + break; + case SaveDataType::Device: + *out_str = "device"; + break; + case SaveDataType::Temporary: + *out_str = "temp"; + break; + case SaveDataType::Cache: + *out_str = "cache"; + break; + case SaveDataType::SystemBcat: + *out_str = "system_bcat"; + break; + default: + /* TODO: Better result? */ + R_THROW(fs::ResultInvalidArgument()); + } + + R_SUCCEED(); + } + + constexpr inline bool IsEmptyAccountId(const UserId &uid) { + return uid == InvalidUserId; + } + + + } + + Result SaveUtil::GetDirectorySaveDataPath(char *dst, size_t dst_size, ncm::ProgramId program_id, u8 space_id, const fs::SaveDataAttribute &attribute) { + /* Saves should be separate for emunand vs sysnand. */ + const char *emummc_str = emummc::IsActive() ? "emummc" : "sysmmc"; + + /* Get space_id, save_data_type strings. */ + const char *space_id_str, *save_type_str; + R_TRY(GetSaveDataSpaceIdString(&space_id_str, space_id)); + R_TRY(GetSaveDataTypeString(&save_type_str, attribute.type)); + + /* Initialize the path. */ + const bool is_system = attribute.system_save_data_id != InvalidSystemSaveDataId && IsEmptyAccountId(attribute.user_id); + size_t out_path_len; + if (is_system) { + out_path_len = static_cast<size_t>(util::SNPrintf(dst, dst_size, "/atmosphere/saves/%s/%s/%s/%016lx", emummc_str, space_id_str, save_type_str, attribute.system_save_data_id)); + } else { + out_path_len = static_cast<size_t>(util::SNPrintf(dst, dst_size, "/atmosphere/saves/%s/%s/%s/%016lx/%016lx%016lx", emummc_str, space_id_str, save_type_str, static_cast<u64>(program_id), attribute.user_id.data[1], attribute.user_id.data[0])); + } + + R_UNLESS(out_path_len < dst_size, fs::ResultTooLongPath()); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_save_utils.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_save_utils.hpp new file mode 100644 index 00000000..2dba1cd5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/fsmitm_save_utils.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::mitm::fs { + + class SaveUtil { + public: + static Result GetDirectorySaveDataPath(char *dst, size_t dst_size, ncm::ProgramId program_id, u8 space_id, const ams::fs::SaveDataAttribute &attribute); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/memlet/memlet.c b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/memlet/memlet.c new file mode 100644 index 00000000..ad14ebb2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/memlet/memlet.c @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#define NX_SERVICE_ASSUME_NON_DOMAIN +#include "service_guard.h" +#include "memlet.h" + +static Service g_memletSrv; + +NX_GENERATE_SERVICE_GUARD(memlet); + +Result _memletInitialize(void) { + return smGetService(&g_memletSrv, "memlet"); +} + +void _memletCleanup(void) { + serviceClose(&g_memletSrv); +} + +Service* memletGetServiceSession(void) { + return &g_memletSrv; +} + +Result memletCreateAppletSharedMemory(Handle *out_shmem_h, u64 *out_size, u64 desired_size) { + return serviceDispatchInOut(&g_memletSrv, 65000, desired_size, *out_size, + .out_handle_attrs = { SfOutHandleAttr_HipcMove }, + .out_handles = out_shmem_h, + ); +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/memlet/memlet.h b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/memlet/memlet.h new file mode 100644 index 00000000..21876193 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/memlet/memlet.h @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <switch.h> + +#ifdef __cplusplus +extern "C" { +#endif + +Result memletInitialize(void); +void memletExit(void); +Service* memletGetServiceSession(void); + +Result memletCreateAppletSharedMemory(Handle *out_shmem_h, u64 *out_size, u64 desired_size); + +#ifdef __cplusplus +} +#endif diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/memlet/service_guard.h b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/memlet/service_guard.h new file mode 100644 index 00000000..d7ce9a7a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/fs_mitm/memlet/service_guard.h @@ -0,0 +1,65 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include <switch/types.h> +#include <switch/result.h> +#include <switch/kernel/mutex.h> +#include <switch/sf/service.h> +#include <switch/services/sm.h> + +typedef struct ServiceGuard { + Mutex mutex; + u32 refCount; +} ServiceGuard; + +NX_INLINE bool serviceGuardBeginInit(ServiceGuard* g) +{ + mutexLock(&g->mutex); + return (g->refCount++) == 0; +} + +NX_INLINE Result serviceGuardEndInit(ServiceGuard* g, Result rc, void (*cleanupFunc)(void)) +{ + if (R_FAILED(rc)) { + cleanupFunc(); + --g->refCount; + } + mutexUnlock(&g->mutex); + return rc; +} + +NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void)) +{ + mutexLock(&g->mutex); + if (g->refCount && (--g->refCount) == 0) + cleanupFunc(); + mutexUnlock(&g->mutex); +} + +#define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \ +\ +static ServiceGuard g_##name##Guard; \ +NX_INLINE Result _##name##Initialize _paramdecl; \ +static void _##name##Cleanup(void); \ +\ +Result name##Initialize _paramdecl \ +{ \ + Result rc = 0; \ + if (serviceGuardBeginInit(&g_##name##Guard)) \ + rc = _##name##Initialize _parampass; \ + return serviceGuardEndInit(&g_##name##Guard, rc, _##name##Cleanup); \ +} \ +\ +void name##Exit(void) \ +{ \ + serviceGuardExit(&g_##name##Guard, _##name##Cleanup); \ +} + +#define NX_GENERATE_SERVICE_GUARD(name) NX_GENERATE_SERVICE_GUARD_PARAMS(name, (void), ()) + +#ifdef __cplusplus +} +#endif diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/mitm_pm/mitm_pm_module.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/mitm_pm/mitm_pm_module.cpp new file mode 100644 index 00000000..3de64b40 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/mitm_pm/mitm_pm_module.cpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../amsmitm_initialization.hpp" +#include "mitm_pm_module.hpp" +#include "mitm_pm_service.hpp" + +namespace ams::mitm::pm { + + namespace { + + constexpr sm::ServiceName PmServiceName = sm::ServiceName::Encode("mitm:pm"); + constexpr size_t PmMaxSessions = 1; + + constexpr size_t MaxServers = 1; + constexpr size_t MaxSessions = PmMaxSessions; + using ServerOptions = sf::hipc::DefaultServerManagerOptions; + sf::hipc::ServerManager<MaxServers, ServerOptions, MaxSessions> g_server_manager; + + constinit sf::UnmanagedServiceObject<mitm::pm::impl::IPmInterface, mitm::pm::PmService> g_pm_service_object; + + } + + void MitmModule::ThreadFunction(void *) { + /* Create bpc:ams. */ + R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_pm_service_object.GetShared(), PmServiceName, PmMaxSessions)); + + /* Loop forever, servicing our services. */ + g_server_manager.LoopProcess(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/mitm_pm/mitm_pm_module.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/mitm_pm/mitm_pm_module.hpp new file mode 100644 index 00000000..d4e061b8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/mitm_pm/mitm_pm_module.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../amsmitm_module.hpp" + +namespace ams::mitm::pm { + + DEFINE_MITM_MODULE_CLASS(0x1000, AMS_GET_SYSTEM_THREAD_PRIORITY(fs, WorkerThreadPool) - 2); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/mitm_pm/mitm_pm_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/mitm_pm/mitm_pm_service.cpp new file mode 100644 index 00000000..8428e74f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/mitm_pm/mitm_pm_service.cpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../amsmitm_initialization.hpp" +#include "mitm_pm_service.hpp" +#include "mitm_pm_service.hpp" +#include "../fs_mitm/fsmitm_romfs.hpp" + +namespace ams::mitm::pm { + + Result PmService::PrepareLaunchProgram(sf::Out<u64> out, ncm::ProgramId program_id, const cfg::OverrideStatus &status, bool is_application) { + /* Default to zero heap. */ + *out = 0; + + /* Actually configure the required boost size for romfs. */ + R_TRY(mitm::fs::romfs::ConfigureDynamicHeap(out.GetPointer(), program_id, status, is_application)); + + /* TODO: Is there anything else we should do, while we have the opportunity? */ + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/mitm_pm/mitm_pm_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/mitm_pm/mitm_pm_service.hpp new file mode 100644 index 00000000..2d28ed18 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/mitm_pm/mitm_pm_service.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::mitm::pm { + + class PmService { + public: + Result PrepareLaunchProgram(sf::Out<u64> out, ncm::ProgramId program_id, const cfg::OverrideStatus &status, bool is_application); + }; + static_assert(impl::IsIPmInterface<PmService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/ns_am_mitm_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/ns_am_mitm_service.cpp new file mode 100644 index 00000000..0754f8a7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/ns_am_mitm_service.cpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../amsmitm_fs_utils.hpp" +#include "ns_am_mitm_service.hpp" +#include "ns_shim.h" + +namespace ams::mitm::ns { + + Result NsAmMitmService::GetApplicationContentPath(const sf::OutBuffer &out_path, ncm::ProgramId application_id, u8 content_type) { + R_RETURN(nsamGetApplicationContentPathFwd(m_forward_service.get(), out_path.GetPointer(), out_path.GetSize(), static_cast<u64>(application_id), static_cast<NcmContentType>(content_type))); + } + + Result NsAmMitmService::ResolveApplicationContentPath(ncm::ProgramId application_id, u8 content_type) { + /* Always succeed for web applets asking about HBL to enable hbl_html, and applications with manual_html to allow custom manual data. */ + bool is_hbl = false; + if ((R_SUCCEEDED(ams::pm::info::IsHblProgramId(std::addressof(is_hbl), application_id)) && is_hbl) || (static_cast<ncm::ContentType>(content_type) == ncm::ContentType::HtmlDocument && mitm::fs::HasSdManualHtmlContent(application_id))) { + nsamResolveApplicationContentPathFwd(m_forward_service.get(), static_cast<u64>(application_id), static_cast<NcmContentType>(content_type)); + R_SUCCEED(); + } + R_RETURN(nsamResolveApplicationContentPathFwd(m_forward_service.get(), static_cast<u64>(application_id), static_cast<NcmContentType>(content_type))); + } + + Result NsAmMitmService::GetRunningApplicationProgramId(sf::Out<ncm::ProgramId> out, ncm::ProgramId application_id) { + R_RETURN(nsamGetRunningApplicationProgramIdFwd(m_forward_service.get(), reinterpret_cast<u64 *>(out.GetPointer()), static_cast<u64>(application_id))); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/ns_am_mitm_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/ns_am_mitm_service.hpp new file mode 100644 index 00000000..dcf6f032 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/ns_am_mitm_service.hpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#define AMS_NS_AM_MITM_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 21, Result, GetApplicationContentPath, (const sf::OutBuffer &out_path, ncm::ProgramId application_id, u8 content_type), (out_path, application_id, content_type)) \ + AMS_SF_METHOD_INFO(C, H, 23, Result, ResolveApplicationContentPath, (ncm::ProgramId application_id, u8 content_type), (application_id, content_type)) \ + AMS_SF_METHOD_INFO(C, H, 92, Result, GetRunningApplicationProgramId, (sf::Out<ncm::ProgramId> out, ncm::ProgramId application_id), (out, application_id), hos::Version_6_0_0) + +AMS_SF_DEFINE_MITM_INTERFACE(ams::mitm::ns::impl, IAmMitmInterface, AMS_NS_AM_MITM_INTERFACE_INFO, 0x059D2C39) + +namespace ams::mitm::ns { + + class NsAmMitmService : public sf::MitmServiceImplBase { + public: + using MitmServiceImplBase::MitmServiceImplBase; + public: + static bool ShouldMitm(const sm::MitmProcessInfo &client_info) { + /* We will mitm: + * - web applets, to facilitate hbl web browser launching. + */ + return ncm::IsWebAppletId(client_info.program_id); + } + public: + /* Actual command API. */ + Result GetApplicationContentPath(const sf::OutBuffer &out_path, ncm::ProgramId application_id, u8 content_type); + Result ResolveApplicationContentPath(ncm::ProgramId application_id, u8 content_type); + Result GetRunningApplicationProgramId(sf::Out<ncm::ProgramId> out, ncm::ProgramId application_id); + }; + static_assert(impl::IsIAmMitmInterface<NsAmMitmService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/ns_shim.c b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/ns_shim.c new file mode 100644 index 00000000..569ac2f6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/ns_shim.c @@ -0,0 +1,126 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <switch.h> +#include "ns_shim.h" + +/* Command forwarders. */ +Result nsGetDocumentInterfaceFwd(Service* s, NsDocumentInterface* out) { + return serviceDispatch(s, 7999, + .out_num_objects = 1, + .out_objects = &out->s, + ); +} + +static Result _nsGetApplicationContentPathOld(Service *s, void* out, size_t out_size, u64 app_id, NcmContentType content_type) { + const struct { + u8 content_type; + u64 app_id; + } in = { content_type, app_id }; + return serviceDispatchIn(s, 21, in, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + .buffers = { { out, out_size } }, + ); +} + +static Result _nsGetApplicationContentPath(Service *s, void* out, size_t out_size, u8 *out_attr, u64 app_id, NcmContentType content_type) { + const struct { + u8 content_type; + u64 app_id; + } in = { content_type, app_id }; + return serviceDispatchInOut(s, 21, in, *out_attr, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + .buffers = { { out, out_size } }, + ); +} + +static Result _nsGetApplicationContentPath2(Service *s, void* out_path, size_t out_size, u64* out_program_id, u8 *out_attr, u64 app_id, NcmContentType content_type) { + const struct { + u8 content_type; + u64 app_id; + } in = { content_type, app_id }; + + struct { + u8 attr; + u64 program_id; + } out; + + Result rc = serviceDispatchInOut(s, 2524, in, out, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + .buffers = { { out_path, out_size } }, + ); + if (R_SUCCEEDED(rc)) { + *out_program_id = out.program_id; + *out_attr = out.attr; + } + + return rc; +} + +static Result _nsResolveApplicationContentPath(Service* s, u64 app_id, NcmContentType content_type) { + const struct { + u8 content_type; + u64 app_id; + } in = { content_type, app_id }; + return serviceDispatchIn(s, 23, in); +} + +static Result _nsGetRunningApplicationProgramId(Service* s, u64* out_program_id, u64 app_id) { + if (hosversionBefore(6, 0, 0)) { + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + } + return serviceDispatchInOut(s, 92, app_id, *out_program_id); +} + +/* Application Manager forwarders. */ +Result nsamGetApplicationContentPathFwd(Service* s, void* out, size_t out_size, u64 app_id, NcmContentType content_type) { + return _nsGetApplicationContentPathOld(s, out, out_size, app_id, content_type); +} + +Result nsamResolveApplicationContentPathFwd(Service* s, u64 app_id, NcmContentType content_type) { + return _nsResolveApplicationContentPath(s, app_id, content_type); +} + +Result nsamGetRunningApplicationProgramIdFwd(Service* s, u64* out_program_id, u64 app_id) { + return _nsGetRunningApplicationProgramId(s, out_program_id, app_id); +} + +/* Web forwarders */ +Result nswebGetApplicationContentPath(NsDocumentInterface* doc, void* out, size_t out_size, u8 *out_attr, u64 app_id, NcmContentType content_type) { + if (hosversionAtLeast(16,0,0)) { + return _nsGetApplicationContentPath(&doc->s, out, out_size, out_attr, app_id, content_type); + } else { + return _nsGetApplicationContentPathOld(&doc->s, out, out_size, app_id, content_type); + } +} + +Result nswebResolveApplicationContentPath(NsDocumentInterface* doc, u64 app_id, NcmContentType content_type) { + return _nsResolveApplicationContentPath(&doc->s, app_id, content_type); +} + +Result nswebGetRunningApplicationProgramId(NsDocumentInterface* doc, u64* out_program_id, u64 app_id) { + return _nsGetRunningApplicationProgramId(&doc->s, out_program_id, app_id); +} + +Result nswebGetApplicationContentPath2(NsDocumentInterface* doc, void* out, size_t out_size, u64* out_program_id, u8 *out_attr, u64 app_id, NcmContentType content_type) { + return _nsGetApplicationContentPath2(&doc->s, out, out_size, out_program_id, out_attr, app_id, content_type); +} + +void nsDocumentInterfaceClose(NsDocumentInterface* doc) { + serviceClose(&doc->s); +} + diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/ns_shim.h b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/ns_shim.h new file mode 100644 index 00000000..9cfd2774 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/ns_shim.h @@ -0,0 +1,34 @@ +/** + * @file ns_shim.h + * @brief Nintendo Shell Services (ns) IPC wrapper. + * @author SciresM + * @copyright libnx Authors + */ +#pragma once +#include <switch.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + Service s; +} NsDocumentInterface; + +/* Command forwarders. */ +Result nsGetDocumentInterfaceFwd(Service* s, NsDocumentInterface* out); + +Result nsamGetApplicationContentPathFwd(Service* s, void* out, size_t out_size, u64 app_id, NcmContentType content_type); +Result nsamResolveApplicationContentPathFwd(Service* s, u64 app_id, NcmContentType content_type); +Result nsamGetRunningApplicationProgramIdFwd(Service* s, u64* out_program_id, u64 app_id); + +Result nswebGetApplicationContentPath(NsDocumentInterface* doc, void* out, size_t out_size, u8 *out_attr, u64 app_id, NcmContentType content_type); +Result nswebResolveApplicationContentPath(NsDocumentInterface* doc, u64 app_id, NcmContentType content_type); +Result nswebGetRunningApplicationProgramId(NsDocumentInterface* doc, u64* out_program_id, u64 app_id); +Result nswebGetApplicationContentPath2(NsDocumentInterface* doc, void* out, size_t out_size, u64* out_program_id, u8 *out_attr, u64 app_id, NcmContentType content_type); + +void nsDocumentInterfaceClose(NsDocumentInterface* doc); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/ns_web_mitm_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/ns_web_mitm_service.cpp new file mode 100644 index 00000000..5613d267 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/ns_web_mitm_service.cpp @@ -0,0 +1,56 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../amsmitm_fs_utils.hpp" +#include "ns_web_mitm_service.hpp" + +namespace ams::mitm::ns { + + Result NsDocumentService::GetApplicationContentPath(const sf::OutBuffer &out_path, sf::Out<ams::fs::ContentAttributes> out_attr, ncm::ProgramId application_id, u8 content_type) { + static_assert(sizeof(*out_attr.GetPointer()) == sizeof(u8)); + R_RETURN(nswebGetApplicationContentPath(m_srv.get(), out_path.GetPointer(), out_path.GetSize(), reinterpret_cast<u8 *>(out_attr.GetPointer()), static_cast<u64>(application_id), static_cast<NcmContentType>(content_type))); + } + + Result NsDocumentService::ResolveApplicationContentPath(ncm::ProgramId application_id, u8 content_type) { + /* Always succeed for web applets asking about HBL to enable hbl_html, and applications with manual_html to allow custom manual data. */ + bool is_hbl = false; + if ((R_SUCCEEDED(ams::pm::info::IsHblProgramId(std::addressof(is_hbl), application_id)) && is_hbl) || (static_cast<ncm::ContentType>(content_type) == ncm::ContentType::HtmlDocument && mitm::fs::HasSdManualHtmlContent(application_id))) { + nswebResolveApplicationContentPath(m_srv.get(), static_cast<u64>(application_id), static_cast<NcmContentType>(content_type)); + R_SUCCEED(); + } + R_RETURN(nswebResolveApplicationContentPath(m_srv.get(), static_cast<u64>(application_id), static_cast<NcmContentType>(content_type))); + } + + Result NsDocumentService::GetRunningApplicationProgramId(sf::Out<ncm::ProgramId> out, ncm::ProgramId application_id) { + R_RETURN(nswebGetRunningApplicationProgramId(m_srv.get(), reinterpret_cast<u64 *>(out.GetPointer()), static_cast<u64>(application_id))); + } + + Result NsDocumentService::GetApplicationContentPath2(const sf::OutBuffer &out_path, sf::Out<ncm::ProgramId> out_program_id, sf::Out<ams::fs::ContentAttributes> out_attr, ncm::ProgramId application_id, u8 content_type) { + static_assert(sizeof(*out_attr.GetPointer()) == sizeof(u8)); + R_RETURN(nswebGetApplicationContentPath2(m_srv.get(), out_path.GetPointer(), out_path.GetSize(), reinterpret_cast<u64 *>(out_program_id.GetPointer()), reinterpret_cast<u8 *>(out_attr.GetPointer()), static_cast<u64>(application_id), static_cast<NcmContentType>(content_type))); + } + + Result NsWebMitmService::GetDocumentInterface(sf::Out<sf::SharedPointer<impl::IDocumentInterface>> out) { + /* Open a document interface. */ + NsDocumentInterface doc; + R_TRY(nsGetDocumentInterfaceFwd(m_forward_service.get(), std::addressof(doc))); + const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(std::addressof(doc.s))}; + + out.SetValue(sf::CreateSharedObjectEmplaced<impl::IDocumentInterface, NsDocumentService>(m_client_info, std::make_unique<NsDocumentInterface>(doc)), target_object_id); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/ns_web_mitm_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/ns_web_mitm_service.hpp new file mode 100644 index 00000000..e9adbe46 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/ns_web_mitm_service.hpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#include "ns_shim.h" + +#define AMS_NS_DOCUMENT_MITM_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 21, Result, GetApplicationContentPath, (const sf::OutBuffer &out_path, sf::Out<ams::fs::ContentAttributes> out_attr, ncm::ProgramId application_id, u8 content_type), (out_path, out_attr, application_id, content_type)) \ + AMS_SF_METHOD_INFO(C, H, 23, Result, ResolveApplicationContentPath, (ncm::ProgramId application_id, u8 content_type), (application_id, content_type)) \ + AMS_SF_METHOD_INFO(C, H, 92, Result, GetRunningApplicationProgramId, (sf::Out<ncm::ProgramId> out, ncm::ProgramId application_id), (out, application_id), hos::Version_6_0_0) \ + AMS_SF_METHOD_INFO(C, H, 2524, Result, GetApplicationContentPath2, (const sf::OutBuffer &out_path, sf::Out<ncm::ProgramId> out_program_id, sf::Out<ams::fs::ContentAttributes> out_attr, ncm::ProgramId application_id, u8 content_type), (out_path, out_program_id, out_attr, application_id, content_type), hos::Version_19_0_0) + +AMS_SF_DEFINE_INTERFACE(ams::mitm::ns::impl, IDocumentInterface, AMS_NS_DOCUMENT_MITM_INTERFACE_INFO, 0x0F9B1C00) + +#define AMS_NS_WEB_MITM_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 7999, Result, GetDocumentInterface, (sf::Out<sf::SharedPointer<mitm::ns::impl::IDocumentInterface>> out), (out)) + +AMS_SF_DEFINE_MITM_INTERFACE(ams::mitm::ns::impl, IWebMitmInterface, AMS_NS_WEB_MITM_INTERFACE_INFO, 0xF4EC2D1A) + +namespace ams::mitm::ns { + + class NsDocumentService { + private: + sm::MitmProcessInfo m_client_info; + std::unique_ptr<::NsDocumentInterface> m_srv; + public: + NsDocumentService(const sm::MitmProcessInfo &cl, std::unique_ptr<::NsDocumentInterface> s) : m_client_info(cl), m_srv(std::move(s)) { /* .. */ } + + virtual ~NsDocumentService() { + nsDocumentInterfaceClose(m_srv.get()); + } + public: + /* Actual command API. */ + Result GetApplicationContentPath(const sf::OutBuffer &out_path, sf::Out<ams::fs::ContentAttributes> out_attr, ncm::ProgramId application_id, u8 content_type); + Result ResolveApplicationContentPath(ncm::ProgramId application_id, u8 content_type); + Result GetRunningApplicationProgramId(sf::Out<ncm::ProgramId> out, ncm::ProgramId application_id); + Result GetApplicationContentPath2(const sf::OutBuffer &out_path, sf::Out<ncm::ProgramId> out_program_id, sf::Out<ams::fs::ContentAttributes> out_attr, ncm::ProgramId application_id, u8 content_type); + }; + static_assert(impl::IsIDocumentInterface<NsDocumentService>); + + class NsWebMitmService : public sf::MitmServiceImplBase { + public: + using MitmServiceImplBase::MitmServiceImplBase; + public: + static bool ShouldMitm(const sm::MitmProcessInfo &client_info) { + /* We will mitm: + * - web applets, to facilitate hbl web browser launching. + */ + return ncm::IsWebAppletId(client_info.program_id); + } + public: + Result GetDocumentInterface(sf::Out<sf::SharedPointer<impl::IDocumentInterface>> out); + }; + static_assert(impl::IsIWebMitmInterface<NsWebMitmService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/nsmitm_module.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/nsmitm_module.cpp new file mode 100644 index 00000000..1148ddad --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/nsmitm_module.cpp @@ -0,0 +1,86 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../amsmitm_initialization.hpp" +#include "nsmitm_module.hpp" +#include "ns_am_mitm_service.hpp" +#include "ns_web_mitm_service.hpp" + +namespace ams::mitm::ns { + + namespace { + + enum PortIndex { + PortIndex_Mitm, + PortIndex_Count, + }; + + constexpr sm::ServiceName NsAmMitmServiceName = sm::ServiceName::Encode("ns:am"); + constexpr sm::ServiceName NsWebMitmServiceName = sm::ServiceName::Encode("ns:web"); + + constexpr size_t MaxSessions = 5; + + struct ServerOptions { + static constexpr size_t PointerBufferSize = sf::hipc::DefaultServerManagerOptions::PointerBufferSize; + static constexpr size_t MaxDomains = sf::hipc::DefaultServerManagerOptions::MaxDomains; + static constexpr size_t MaxDomainObjects = sf::hipc::DefaultServerManagerOptions::MaxDomainObjects; + static constexpr bool CanDeferInvokeRequest = sf::hipc::DefaultServerManagerOptions::CanDeferInvokeRequest; + static constexpr bool CanManageMitmServers = true; + }; + + class ServerManager final : public sf::hipc::ServerManager<PortIndex_Count, ServerOptions, MaxSessions> { + private: + virtual Result OnNeedsToAccept(int port_index, Server *server) override; + }; + + ServerManager g_server_manager; + + Result ServerManager::OnNeedsToAccept(int port_index, Server *server) { + /* Acknowledge the mitm session. */ + std::shared_ptr<::Service> fsrv; + sm::MitmProcessInfo client_info; + server->AcknowledgeMitmSession(std::addressof(fsrv), std::addressof(client_info)); + + switch (port_index) { + case PortIndex_Mitm: + if (hos::GetVersion() < hos::Version_3_0_0) { + R_RETURN(this->AcceptMitmImpl(server, sf::CreateSharedObjectEmplaced<impl::IAmMitmInterface, NsAmMitmService>(decltype(fsrv)(fsrv), client_info), fsrv)); + } else { + R_RETURN(this->AcceptMitmImpl(server, sf::CreateSharedObjectEmplaced<impl::IWebMitmInterface, NsWebMitmService>(decltype(fsrv)(fsrv), client_info), fsrv)); + } + + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + } + + void MitmModule::ThreadFunction(void *) { + /* Wait until initialization is complete. */ + mitm::WaitInitialized(); + + /* Create mitm servers. */ + if (hos::GetVersion() < hos::Version_3_0_0) { + R_ABORT_UNLESS((g_server_manager.RegisterMitmServer<NsAmMitmService>(PortIndex_Mitm, NsAmMitmServiceName))); + } else { + R_ABORT_UNLESS((g_server_manager.RegisterMitmServer<NsWebMitmService>(PortIndex_Mitm, NsWebMitmServiceName))); + } + + /* Loop forever, servicing our services. */ + g_server_manager.LoopProcess(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/nsmitm_module.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/nsmitm_module.hpp new file mode 100644 index 00000000..45a62bd3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/ns_mitm/nsmitm_module.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../amsmitm_module.hpp" + +namespace ams::mitm::ns { + + DEFINE_MITM_MODULE_CLASS(0x4000, AMS_GET_SYSTEM_THREAD_PRIORITY(ns, ApplicationManagerIpcSession) - 1); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/set_mitm_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/set_mitm_service.cpp new file mode 100644 index 00000000..0915a10a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/set_mitm_service.cpp @@ -0,0 +1,130 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "set_mitm_service.hpp" +#include "set_shim.h" + +namespace ams::mitm::settings { + + using namespace ams::settings; + + namespace { + + constinit os::ProcessId g_application_process_id = os::InvalidProcessId; + constinit cfg::OverrideLocale g_application_locale; + constinit bool g_valid_language; + constinit bool g_valid_region; + } + + SetMitmService::SetMitmService(std::shared_ptr<::Service> &&s, const sm::MitmProcessInfo &c) : sf::MitmServiceImplBase(std::forward<std::shared_ptr<::Service>>(s), c) { + if (m_client_info.program_id == ncm::SystemProgramId::Ns) { + os::ProcessId application_process_id; + if (R_SUCCEEDED(ams::pm::dmnt::GetApplicationProcessId(std::addressof(application_process_id))) && g_application_process_id == application_process_id) { + m_locale = g_application_locale; + m_is_valid_language = g_valid_language; + m_is_valid_region = g_valid_region; + m_got_locale = true; + } else { + this->InvalidateLocale(); + } + } else { + this->InvalidateLocale(); + } + } + + Result SetMitmService::EnsureLocale() { + /* Optimization: if locale has already been gotten, we can stop. */ + if (AMS_LIKELY(m_got_locale)) { + R_SUCCEED(); + } + + std::scoped_lock lk(m_lock); + + const bool is_ns = m_client_info.program_id == ncm::SystemProgramId::Ns; + + if (!m_got_locale) { + ncm::ProgramId program_id = m_client_info.program_id; + os::ProcessId application_process_id = os::InvalidProcessId; + + if (is_ns) { + /* When NS asks for a locale, refresh to get the current application locale. */ + R_TRY(ams::pm::dmnt::GetApplicationProcessId(std::addressof(application_process_id))); + R_TRY(ams::pm::info::GetProgramId(std::addressof(program_id), application_process_id)); + } + m_locale = cfg::GetOverrideLocale(program_id); + m_is_valid_language = settings::IsValidLanguageCode(m_locale.language_code); + m_is_valid_region = settings::IsValidRegionCode(m_locale.region_code); + m_got_locale = true; + + if (is_ns) { + g_application_locale = m_locale; + g_valid_language = m_is_valid_language; + g_valid_region = m_is_valid_region; + g_application_process_id = application_process_id; + } + } + + R_SUCCEED(); + } + + void SetMitmService::InvalidateLocale() { + std::scoped_lock lk(m_lock); + + std::memset(std::addressof(m_locale), 0xCC, sizeof(m_locale)); + m_is_valid_language = false; + m_is_valid_region = false; + m_got_locale = false; + } + + Result SetMitmService::GetLanguageCode(sf::Out<settings::LanguageCode> out) { + this->EnsureLocale(); + + /* If there's no override locale, just use the actual one. */ + if (AMS_UNLIKELY(!m_is_valid_language)) { + static_assert(sizeof(u64) == sizeof(settings::LanguageCode)); + R_TRY(setGetLanguageCodeFwd(m_forward_service.get(), reinterpret_cast<u64 *>(std::addressof(m_locale.language_code)))); + + m_is_valid_language = true; + if (m_client_info.program_id == ncm::SystemProgramId::Ns) { + g_application_locale.language_code = m_locale.language_code; + g_valid_language = true; + } + } + + out.SetValue(m_locale.language_code); + R_SUCCEED(); + } + + Result SetMitmService::GetRegionCode(sf::Out<settings::RegionCode> out) { + this->EnsureLocale(); + + /* If there's no override locale, just use the actual one. */ + if (AMS_UNLIKELY(!m_is_valid_region)) { + static_assert(sizeof(::SetRegion) == sizeof(settings::RegionCode)); + R_TRY(setGetRegionCodeFwd(m_forward_service.get(), reinterpret_cast<::SetRegion *>(std::addressof(m_locale.region_code)))); + + m_is_valid_region = true; + if (m_client_info.program_id == ncm::SystemProgramId::Ns) { + g_application_locale.region_code = m_locale.region_code; + g_valid_region = true; + } + } + + out.SetValue(m_locale.region_code); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/set_mitm_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/set_mitm_service.hpp new file mode 100644 index 00000000..15c78e3f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/set_mitm_service.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#define AMS_SETTINGS_MITM_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GetLanguageCode, (sf::Out<ams::settings::LanguageCode> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, GetRegionCode, (sf::Out<ams::settings::RegionCode> out), (out)) + +AMS_SF_DEFINE_MITM_INTERFACE(ams::mitm::settings, ISetMitmInterface, AMS_SETTINGS_MITM_INTERFACE_INFO, 0x7F7BAF0A) + +namespace ams::mitm::settings { + + class SetMitmService : public sf::MitmServiceImplBase { + private: + os::SdkMutex m_lock{}; + cfg::OverrideLocale m_locale; + bool m_got_locale = false; + bool m_is_valid_language = false; + bool m_is_valid_region = false; + public: + SetMitmService(std::shared_ptr<::Service> &&s, const sm::MitmProcessInfo &c); + public: + static bool ShouldMitm(const sm::MitmProcessInfo &client_info) { + /* We will mitm: + * - ns and games, to allow for overriding game locales. + */ + const bool is_game = (ncm::IsApplicationId(client_info.program_id) && !client_info.override_status.IsHbl()); + return client_info.program_id == ncm::SystemProgramId::Ns || is_game; + } + private: + void InvalidateLocale(); + Result EnsureLocale(); + public: + Result GetLanguageCode(sf::Out<ams::settings::LanguageCode> out); + Result GetRegionCode(sf::Out<ams::settings::RegionCode> out); + }; + static_assert(IsISetMitmInterface<SetMitmService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/set_shim.c b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/set_shim.c new file mode 100644 index 00000000..9a36a211 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/set_shim.c @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include "set_shim.h" + +static Result _setCmdNoInOut64(Service* srv, u64 *out, u32 cmd_id) { + return serviceDispatchOut(srv, cmd_id, *out); +} + +static Result _setCmdNoInOutU32(Service* srv, u32 *out, u32 cmd_id) { + return serviceDispatchOut(srv, cmd_id, *out); +} + +/* Forwarding shims. */ +Result setGetLanguageCodeFwd(Service *s, u64* out) { + return _setCmdNoInOut64(s, out, 0); +} + +Result setGetRegionCodeFwd(Service *s, SetRegion *out) { + s32 code=0; + Result rc = _setCmdNoInOutU32(s, (u32*)&code, 4); + if (R_SUCCEEDED(rc) && out) *out = code; + return rc; +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/set_shim.h b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/set_shim.h new file mode 100644 index 00000000..f2f3cad1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/set_shim.h @@ -0,0 +1,20 @@ +/** + * @file set_shim.h + * @brief Settings Services (fs) IPC wrapper for set.mitm. + * @author SciresM + * @copyright libnx Authors + */ +#pragma once +#include <switch.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Forwarding shims. */ +Result setGetLanguageCodeFwd(Service *s, u64* out); +Result setGetRegionCodeFwd(Service *s, SetRegion *out); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/setmitm_module.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/setmitm_module.cpp new file mode 100644 index 00000000..c756c028 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/setmitm_module.cpp @@ -0,0 +1,81 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../amsmitm_initialization.hpp" +#include "setmitm_module.hpp" +#include "set_mitm_service.hpp" +#include "setsys_mitm_service.hpp" + +namespace ams::mitm::settings { + + namespace { + + enum PortIndex { + PortIndex_SetMitm, + PortIndex_SetSysMitm, + PortIndex_Count, + }; + + constexpr sm::ServiceName SetMitmServiceName = sm::ServiceName::Encode("set"); + constexpr sm::ServiceName SetSysMitmServiceName = sm::ServiceName::Encode("set:sys"); + + struct ServerOptions { + static constexpr size_t PointerBufferSize = 0x200; + static constexpr size_t MaxDomains = 0; + static constexpr size_t MaxDomainObjects = 0; + static constexpr bool CanDeferInvokeRequest = false; + static constexpr bool CanManageMitmServers = true; + }; + + constexpr size_t MaxSessions = 60; + + class ServerManager final : public sf::hipc::ServerManager<PortIndex_Count, ServerOptions, MaxSessions> { + private: + virtual Result OnNeedsToAccept(int port_index, Server *server) override; + }; + + ServerManager g_server_manager; + + Result ServerManager::OnNeedsToAccept(int port_index, Server *server) { + /* Acknowledge the mitm session. */ + std::shared_ptr<::Service> fsrv; + sm::MitmProcessInfo client_info; + server->AcknowledgeMitmSession(std::addressof(fsrv), std::addressof(client_info)); + + switch (port_index) { + case PortIndex_SetMitm: + R_RETURN(this->AcceptMitmImpl(server, sf::CreateSharedObjectEmplaced<ISetMitmInterface, SetMitmService>(decltype(fsrv)(fsrv), client_info), fsrv)); + case PortIndex_SetSysMitm: + R_RETURN(this->AcceptMitmImpl(server, sf::CreateSharedObjectEmplaced<ISetSysMitmInterface, SetSysMitmService>(decltype(fsrv)(fsrv), client_info), fsrv)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + } + + void MitmModule::ThreadFunction(void *) { + /* Wait until initialization is complete. */ + mitm::WaitInitialized(); + + /* Create mitm servers. */ + R_ABORT_UNLESS((g_server_manager.RegisterMitmServer<SetMitmService>(PortIndex_SetMitm, SetMitmServiceName))); + R_ABORT_UNLESS((g_server_manager.RegisterMitmServer<SetSysMitmService>(PortIndex_SetSysMitm, SetSysMitmServiceName))); + + /* Loop forever, servicing our services. */ + g_server_manager.LoopProcess(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/setmitm_module.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/setmitm_module.hpp new file mode 100644 index 00000000..2bc19063 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/setmitm_module.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../amsmitm_module.hpp" + +namespace ams::mitm::settings { + + DEFINE_MITM_MODULE_CLASS(0x8000, AMS_GET_SYSTEM_THREAD_PRIORITY(settings, IpcServer) - 1); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.cpp new file mode 100644 index 00000000..e0ceb6a1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.cpp @@ -0,0 +1,272 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "setsys_mitm_service.hpp" +#include "settings_sd_kvs.hpp" +#include "setsys_shim.h" + +namespace ams::mitm::settings { + + using namespace ams::settings; + + namespace { + + constexpr const char ExternalBluetoothDatabasePath[] = "@Sdcard:/atmosphere/bluetooth_devices.db"; + + constinit os::SdkMutex g_firmware_version_lock; + constinit bool g_cached_firmware_version; + constinit settings::FirmwareVersion g_firmware_version; + constinit settings::FirmwareVersion g_ams_firmware_version; + + void CacheFirmwareVersion() { + if (AMS_LIKELY(g_cached_firmware_version)) { + return; + } + + std::scoped_lock lk(g_firmware_version_lock); + + if (AMS_UNLIKELY(g_cached_firmware_version)) { + return; + } + + /* Mount firmware version data archive. */ + { + R_ABORT_UNLESS(ams::fs::MountSystemData("sysver", ncm::SystemDataId::SystemVersion)); + ON_SCOPE_EXIT { ams::fs::Unmount("sysver"); }; + + /* Firmware version file must exist. */ + ams::fs::FileHandle file; + R_ABORT_UNLESS(ams::fs::OpenFile(std::addressof(file), "sysver:/file", fs::OpenMode_Read)); + ON_SCOPE_EXIT { ams::fs::CloseFile(file); }; + + /* Must be possible to read firmware version from file. */ + R_ABORT_UNLESS(ams::fs::ReadFile(file, 0, std::addressof(g_firmware_version), sizeof(g_firmware_version))); + + g_ams_firmware_version = g_firmware_version; + } + + /* Modify the atmosphere firmware version to display a custom version string. */ + { + const auto api_info = exosphere::GetApiInfo(); + const char emummc_char = emummc::IsActive() ? 'E' : 'S'; + + /* NOTE: We have carefully accounted for the size of the string we print. */ + /* No truncation occurs assuming two-digits for all version number components. */ + char display_version[sizeof(g_ams_firmware_version.display_version)]; + + util::SNPrintf(display_version, sizeof(display_version), "%s|AMS %u.%u.%u|%c", g_ams_firmware_version.display_version, api_info.GetMajorVersion(), api_info.GetMinorVersion(), api_info.GetMicroVersion(), emummc_char); + + std::memcpy(g_ams_firmware_version.display_version, display_version, sizeof(display_version)); + } + + g_cached_firmware_version = true; + } + + Result GetFirmwareVersionImpl(settings::FirmwareVersion *out, const sm::MitmProcessInfo &client_info) { + /* Ensure that we have the firmware version cached. */ + CacheFirmwareVersion(); + + /* We want to give a special firmware version to the home menu title, and nothing else. */ + /* This means qlaunch + maintenance menu, and nothing else. */ + if (client_info.program_id == ncm::SystemAppletId::Qlaunch || client_info.program_id == ncm::SystemAppletId::MaintenanceMenu) { + *out = g_ams_firmware_version; + } else { + *out = g_firmware_version; + } + + R_SUCCEED(); + } + + bool IsExternalBluetoothDatabaseEnabled() { + u8 en = 0; + settings::fwdbg::GetSettingsItemValue(std::addressof(en), sizeof(en), "atmosphere", "enable_external_bluetooth_db"); + return en; + } + + bool HasExternalBluetoothDatabase() { + bool file_exists; + R_ABORT_UNLESS(fs::HasFile(std::addressof(file_exists), ExternalBluetoothDatabasePath)); + return file_exists; + } + + Result ReadExternalBluetoothDatabase(s32 *entries_read, settings::BluetoothDevicesSettings *db, size_t db_max_size) { + /* Open the external database file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), ExternalBluetoothDatabasePath, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Read the number of database entries stored in external database. */ + u64 total_entries; + R_TRY(fs::ReadFile(file, 0, std::addressof(total_entries), sizeof(total_entries))); + + u64 db_offset = sizeof(total_entries); + if (total_entries > db_max_size) { + /* Pairings are stored from least to most recent. Add offset to skip the older entries that won't fit. */ + db_offset += (total_entries - db_max_size) * sizeof(settings::BluetoothDevicesSettings); + + /* Cap number of database entries read to size of database on this firmware. */ + total_entries = db_max_size; + } + + /* Read database entries. */ + R_TRY(fs::ReadFile(file, db_offset, db, total_entries * sizeof(settings::BluetoothDevicesSettings))); + + /* Convert entries to the old format if running on a firmware below 13.0.0. */ + if (hos::GetVersion() < hos::Version_13_0_0) { + for (size_t i = 0; i < total_entries; ++i) { + /* Copy the newer name field to the older one. */ + util::TSNPrintf(db[i].name, sizeof(db[i].name), "%s", db[i].name2); + + /* Clear the newer name field. */ + std::memset(db[i].name2, 0, sizeof(db[i].name2)); + } + } + + /* Set the output. */ + *entries_read = static_cast<s32>(total_entries); + R_SUCCEED(); + } + + Result StoreExternalBluetoothDatabase(const settings::BluetoothDevicesSettings *db, u64 total_entries) { + /* Open the external database file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), ExternalBluetoothDatabasePath, fs::OpenMode_Write)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Ensure the file is the appropriate size for the number of entries. */ + R_TRY(fs::SetFileSize(file, sizeof(total_entries) + total_entries * sizeof(settings::BluetoothDevicesSettings))); + + /* Write the number of database entries. */ + R_TRY(fs::WriteFile(file, 0, std::addressof(total_entries), sizeof(total_entries), fs::WriteOption::None)); + + /* Write the database entries. */ + u64 db_offset = sizeof(total_entries); + if (hos::GetVersion() < hos::Version_13_0_0) { + /* Convert entries to the new format if running on a firmware below 13.0.0. */ + for (size_t i = 0; i < total_entries; ++i) { + /* Make a copy of the current database entry. */ + settings::BluetoothDevicesSettings tmp = db[i]; + + /* Copy the older name field to the newer one. */ + util::TSNPrintf(tmp.name2, sizeof(tmp.name2), "%s", tmp.name); + + /* Clear the original name field. */ + std::memset(tmp.name, 0, sizeof(tmp.name)); + + /* Write the converted database entry. */ + R_TRY(fs::WriteFile(file, db_offset, std::addressof(tmp), sizeof(settings::BluetoothDevicesSettings), fs::WriteOption::None)); + + /* Advance to the next database entry. */ + db_offset += sizeof(settings::BluetoothDevicesSettings); + } + + /* Flush the data we've written. */ + R_TRY(fs::FlushFile(file)); + } else { + /* We can just write the database to the file. */ + R_TRY(fs::WriteFile(file, db_offset, db, total_entries * sizeof(settings::BluetoothDevicesSettings), fs::WriteOption::Flush)); + } + + R_SUCCEED(); + } + + } + + Result SetSysMitmService::GetFirmwareVersion(sf::Out<settings::FirmwareVersion> out) { + R_TRY(GetFirmwareVersionImpl(out.GetPointer(), m_client_info)); + + /* GetFirmwareVersion sanitizes the revision fields. */ + out.GetPointer()->revision_major = 0; + out.GetPointer()->revision_minor = 0; + R_SUCCEED(); + } + + Result SetSysMitmService::GetFirmwareVersion2(sf::Out<settings::FirmwareVersion> out) { + R_RETURN(GetFirmwareVersionImpl(out.GetPointer(), m_client_info)); + } + + Result SetSysMitmService::SetBluetoothDevicesSettings(const sf::InMapAliasArray<settings::BluetoothDevicesSettings> &settings) { + /* We only want to perform additional logic when the external database setting is enabled. */ + R_UNLESS(IsExternalBluetoothDatabaseEnabled(), sm::mitm::ResultShouldForwardToSession()); + + /* Create the external database if it doesn't exist. */ + if (!HasExternalBluetoothDatabase()) { + R_TRY(fs::CreateFile(ExternalBluetoothDatabasePath, 0)); + } + + /* Backup the local database to the sd card. */ + R_TRY(StoreExternalBluetoothDatabase(settings.GetPointer(), settings.GetSize())); + + /* Ensure that the updated database is stored to the system save as usual. */ + static_assert(sizeof(settings::BluetoothDevicesSettings) == sizeof(::SetSysBluetoothDevicesSettings)); + R_TRY(setsysSetBluetoothDevicesSettingsFwd(m_forward_service.get(), reinterpret_cast<const ::SetSysBluetoothDevicesSettings *>(settings.GetPointer()), settings.GetSize())); + + R_SUCCEED(); + } + + Result SetSysMitmService::GetBluetoothDevicesSettings(sf::Out<s32> out_count, const sf::OutMapAliasArray<settings::BluetoothDevicesSettings> &out) { + /* We only want to perform additional logic when the external database setting is enabled. */ + R_UNLESS(IsExternalBluetoothDatabaseEnabled(), sm::mitm::ResultShouldForwardToSession()); + + if (!HasExternalBluetoothDatabase()) { + /* Fetch the local database from the system save. */ + static_assert(sizeof(settings::BluetoothDevicesSettings) == sizeof(::SetSysBluetoothDevicesSettings)); + R_TRY(setsysGetBluetoothDevicesSettingsFwd(m_forward_service.get(), out_count.GetPointer(), reinterpret_cast<::SetSysBluetoothDevicesSettings *>(out.GetPointer()), out.GetSize())); + + /* Create the external database file. */ + R_TRY(fs::CreateFile(ExternalBluetoothDatabasePath, 0)); + + /* Backup the local database to the sd card. */ + R_TRY(StoreExternalBluetoothDatabase(out.GetPointer(), out_count.GetValue())); + } else { + /* Read the external database from the sd card. */ + R_TRY(ReadExternalBluetoothDatabase(out_count.GetPointer(), out.GetPointer(), out.GetSize())); + } + + R_SUCCEED(); + } + + Result SetSysMitmService::GetSettingsItemValueSize(sf::Out<u64> out_size, const settings::SettingsName &name, const settings::SettingsItemKey &key) { + R_TRY_CATCH(settings::fwdbg::GetSdCardKeyValueStoreSettingsItemValueSize(out_size.GetPointer(), name.value, key.value)) { + R_CATCH_RETHROW(sf::impl::ResultRequestContextChanged) + R_CONVERT_ALL(sm::mitm::ResultShouldForwardToSession()); + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + Result SetSysMitmService::GetSettingsItemValue(sf::Out<u64> out_size, const sf::OutBuffer &out, const settings::SettingsName &name, const settings::SettingsItemKey &key) { + R_TRY_CATCH(settings::fwdbg::GetSdCardKeyValueStoreSettingsItemValue(out_size.GetPointer(), out.GetPointer(), out.GetSize(), name.value, key.value)) { + R_CATCH_RETHROW(sf::impl::ResultRequestContextChanged) + R_CONVERT_ALL(sm::mitm::ResultShouldForwardToSession()); + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + Result SetSysMitmService::GetDebugModeFlag(sf::Out<bool> out) { + /* If we're not processing for am, just return the real flag value. */ + R_UNLESS(m_client_info.program_id == ncm::SystemProgramId::Am, sm::mitm::ResultShouldForwardToSession()); + + /* Retrieve the user configuration. */ + u8 en = 0; + settings::fwdbg::GetSettingsItemValue(std::addressof(en), sizeof(en), "atmosphere", "enable_am_debug_mode"); + + out.SetValue(en != 0); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.hpp new file mode 100644 index 00000000..f61a0bf2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.hpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#define AMS_SETTINGS_SYSTEM_MITM_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, GetFirmwareVersion, (sf::Out<ams::settings::FirmwareVersion> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, GetFirmwareVersion2, (sf::Out<ams::settings::FirmwareVersion> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 11, Result, SetBluetoothDevicesSettings, (const sf::InMapAliasArray<ams::settings::BluetoothDevicesSettings> &settings), (settings)) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, GetBluetoothDevicesSettings, (sf::Out<s32> out_count, const sf::OutMapAliasArray<ams::settings::BluetoothDevicesSettings> &out), (out_count, out)) \ + AMS_SF_METHOD_INFO(C, H, 37, Result, GetSettingsItemValueSize, (sf::Out<u64> out_size, const ams::settings::SettingsName &name, const ams::settings::SettingsItemKey &key), (out_size, name, key)) \ + AMS_SF_METHOD_INFO(C, H, 38, Result, GetSettingsItemValue, (sf::Out<u64> out_size, const sf::OutBuffer &out, const ams::settings::SettingsName &name, const ams::settings::SettingsItemKey &key), (out_size, out, name, key)) \ + AMS_SF_METHOD_INFO(C, H, 62, Result, GetDebugModeFlag, (sf::Out<bool> out), (out)) + +AMS_SF_DEFINE_MITM_INTERFACE(ams::mitm::settings, ISetSysMitmInterface, AMS_SETTINGS_SYSTEM_MITM_INTERFACE_INFO, 0x0E82ED13) + +namespace ams::mitm::settings { + + class SetSysMitmService : public sf::MitmServiceImplBase { + public: + using MitmServiceImplBase::MitmServiceImplBase; + public: + static bool ShouldMitm(const sm::MitmProcessInfo &client_info) { + /* We will mitm: + * - everything, because we want to intercept all settings requests. + */ + AMS_UNUSED(client_info); + return true; + } + public: + Result GetFirmwareVersion(sf::Out<ams::settings::FirmwareVersion> out); + Result GetFirmwareVersion2(sf::Out<ams::settings::FirmwareVersion> out); + Result SetBluetoothDevicesSettings(const sf::InMapAliasArray<ams::settings::BluetoothDevicesSettings> &settings); + Result GetBluetoothDevicesSettings(sf::Out<s32> out_count, const sf::OutMapAliasArray<ams::settings::BluetoothDevicesSettings> &out); + Result GetSettingsItemValueSize(sf::Out<u64> out_size, const ams::settings::SettingsName &name, const ams::settings::SettingsItemKey &key); + Result GetSettingsItemValue(sf::Out<u64> out_size, const sf::OutBuffer &out, const ams::settings::SettingsName &name, const ams::settings::SettingsItemKey &key); + Result GetDebugModeFlag(sf::Out<bool> out); + }; + static_assert(IsISetSysMitmInterface<SetSysMitmService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/setsys_shim.c b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/setsys_shim.c new file mode 100644 index 00000000..ff0244f6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/setsys_shim.c @@ -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 <http://www.gnu.org/licenses/>. + */ +#include "setsys_shim.h" + +Result setsysSetBluetoothDevicesSettingsFwd(Service *s, const SetSysBluetoothDevicesSettings *settings, s32 count) { + return serviceDispatch(s, 11, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, + .buffers = { { settings, count * sizeof(SetSysBluetoothDevicesSettings) } }, + ); +} + +Result setsysGetBluetoothDevicesSettingsFwd(Service *s, s32 *total_out, SetSysBluetoothDevicesSettings *settings, s32 count) { + return serviceDispatchOut(s, 12, *total_out, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + .buffers = { { settings, count * sizeof(SetSysBluetoothDevicesSettings) } }, + ); +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/setsys_shim.h b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/setsys_shim.h new file mode 100644 index 00000000..fab5e591 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/setsys_shim.h @@ -0,0 +1,20 @@ +/** + * @file setsys_shim.h + * @brief Settings Services (fs) IPC wrapper for setsys.mitm. + * @author ndeadly + * @copyright libnx Authors + */ +#pragma once +#include <switch.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Forwarding shims. */ +Result setsysSetBluetoothDevicesSettingsFwd(Service *s, const SetSysBluetoothDevicesSettings *settings, s32 count); +Result setsysGetBluetoothDevicesSettingsFwd(Service *s, s32 *total_out, SetSysBluetoothDevicesSettings *settings, s32 count); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/settings_fwdbg_api_override.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/settings_fwdbg_api_override.cpp new file mode 100644 index 00000000..fa24179f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/settings_fwdbg_api_override.cpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "settings_sd_kvs.hpp" + +namespace ams::settings::fwdbg { + + size_t GetSettingsItemValueSize(const char *name, const char *key) { + u64 size = 0; + + if (R_SUCCEEDED(GetSdCardKeyValueStoreSettingsItemValueSize(&size, name, key))) { + return size; + } + + R_ABORT_UNLESS(setsysGetSettingsItemValueSize(name, key, &size)); + return size; + } + + size_t GetSettingsItemValue(void *dst, size_t dst_size, const char *name, const char *key) { + u64 size = 0; + + if (R_SUCCEEDED(GetSdCardKeyValueStoreSettingsItemValue(&size, dst, dst_size, name, key))) { + return size; + } + + R_ABORT_UNLESS(setsysGetSettingsItemValue(name, key, dst, dst_size, &size)); + return size; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp new file mode 100644 index 00000000..bdb25a8e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp @@ -0,0 +1,460 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../amsmitm_fs_utils.hpp" +#include "settings_sd_kvs.hpp" + +namespace ams::settings::fwdbg { + + namespace { + + struct SdKeyValueStoreEntry { + const char *name; + const char *key; + void *value; + size_t value_size; + + constexpr inline bool HasValue() const { return this->value != nullptr; } + + constexpr inline void GetNameAndKey(char *dst) const { + size_t offset = 0; + for (size_t i = 0; i < std::strlen(this->name); i++) { + dst[offset++] = this->name[i]; + } + dst[offset++] = '!'; + for (size_t i = 0; i < std::strlen(this->key); i++) { + dst[offset++] = this->key[i]; + } + dst[offset] = 0; + } + }; + + static_assert(util::is_pod<SdKeyValueStoreEntry>::value); + + constexpr inline bool operator==(const SdKeyValueStoreEntry &lhs, const SdKeyValueStoreEntry &rhs) { + if (lhs.HasValue() != rhs.HasValue()) { + return false; + } + return lhs.HasValue() && std::strcmp(lhs.name, rhs.name) == 0 && std::strcmp(lhs.key, rhs.key) == 0; + } + + inline bool operator<(const SdKeyValueStoreEntry &lhs, const SdKeyValueStoreEntry &rhs) { + AMS_ABORT_UNLESS(lhs.HasValue()); + AMS_ABORT_UNLESS(rhs.HasValue()); + + char lhs_name_key[SettingsNameLengthMax + 1 + SettingsItemKeyLengthMax + 1]; + char rhs_name_key[SettingsNameLengthMax + 1 + SettingsItemKeyLengthMax + 1]; + + lhs.GetNameAndKey(lhs_name_key); + rhs.GetNameAndKey(rhs_name_key); + + return std::strcmp(lhs_name_key, rhs_name_key) < 0; + } + + constexpr size_t MaxEntries = 0x200; + constexpr size_t SettingsItemValueStorageSize = 0x10000; + + SettingsName g_names[MaxEntries]; + SettingsItemKey g_item_keys[MaxEntries]; + u8 g_value_storage[SettingsItemValueStorageSize]; + size_t g_allocated_value_storage_size; + + SdKeyValueStoreEntry g_entries[MaxEntries]; + size_t g_num_entries; + + constexpr bool IsValidSettingsFormat(const char *str, size_t len) { + AMS_ABORT_UNLESS(str != nullptr); + + if (len > 0 && str[len - 1] == '.') { + return false; + } + + for (size_t i = 0; i < len; i++) { + const char c = str[i]; + + if ('a' <= c && c <= 'z') { + continue; + } + + if ('0' <= c && c <= '9') { + continue; + } + + if (c == '-' || c == '.' || c == '_') { + continue; + } + + return false; + } + + return true; + } + + constexpr bool IsHexadecimal(const char *str) { + while (*str) { + if (std::isxdigit(static_cast<unsigned char>(*str))) { + str++; + } else { + return false; + } + } + return true; + } + + constexpr inline char hextoi(char c) { + if ('a' <= c && c <= 'f') return c - 'a' + 0xA; + if ('A' <= c && c <= 'F') return c - 'A' + 0xA; + if ('0' <= c && c <= '9') return c - '0'; + return 0; + } + + Result ValidateSettingsName(const char *name) { + R_UNLESS(name != nullptr, settings::ResultNullSettingsName()); + const size_t len = strnlen(name, SettingsNameLengthMax + 1); + R_UNLESS(len > 0, settings::ResultEmptySettingsName()); + R_UNLESS(len <= SettingsNameLengthMax, settings::ResultTooLongSettingsName()); + R_UNLESS(IsValidSettingsFormat(name, len), settings::ResultInvalidFormatSettingsName()); + R_SUCCEED(); + } + + Result ValidateSettingsItemKey(const char *key) { + R_UNLESS(key != nullptr, settings::ResultNullSettingsName()); + const size_t len = strnlen(key, SettingsItemKeyLengthMax + 1); + R_UNLESS(len > 0, settings::ResultEmptySettingsItemKey()); + R_UNLESS(len <= SettingsNameLengthMax, settings::ResultTooLongSettingsItemKey()); + R_UNLESS(IsValidSettingsFormat(key, len), settings::ResultInvalidFormatSettingsItemKey()); + R_SUCCEED(); + } + + Result AllocateValue(void **out, size_t size) { + R_UNLESS(g_allocated_value_storage_size + size <= sizeof(g_value_storage), settings::ResultSettingsItemValueAllocationFailed()); + + *out = g_value_storage + g_allocated_value_storage_size; + g_allocated_value_storage_size += size; + R_SUCCEED(); + } + + Result FindSettingsName(const char **out, const char *name) { + for (auto &stored : g_names) { + if (std::strcmp(stored.value, name) == 0) { + *out = stored.value; + R_SUCCEED(); + } else if (std::strcmp(stored.value, "") == 0) { + *out = stored.value; + std::strcpy(stored.value, name); + R_SUCCEED(); + } + } + R_THROW(settings::ResultSettingsItemKeyAllocationFailed()); + } + + Result FindSettingsItemKey(const char **out, const char *key) { + for (auto &stored : g_item_keys) { + if (std::strcmp(stored.value, key) == 0) { + *out = stored.value; + R_SUCCEED(); + } else if (std::strcmp(stored.value, "") == 0) { + std::strcpy(stored.value, key); + *out = stored.value; + R_SUCCEED(); + } + } + R_THROW(settings::ResultSettingsItemKeyAllocationFailed()); + } + + template<typename T> + Result ParseSettingsItemIntegralValue(SdKeyValueStoreEntry &out, const char *value_str) { + R_TRY(AllocateValue(std::addressof(out.value), sizeof(T))); + out.value_size = sizeof(T); + + T value = static_cast<T>(strtoul(value_str, nullptr, 0)); + std::memcpy(out.value, std::addressof(value), sizeof(T)); + R_SUCCEED(); + } + + Result GetEntry(SdKeyValueStoreEntry **out, const char *name, const char *key) { + /* Validate name/key. */ + R_TRY(ValidateSettingsName(name)); + R_TRY(ValidateSettingsItemKey(key)); + + u8 dummy_value = 0; + SdKeyValueStoreEntry test_entry { .name = name, .key = key, .value = std::addressof(dummy_value), .value_size = sizeof(dummy_value) }; + + auto *begin = g_entries; + auto *end = begin + g_num_entries; + auto it = std::lower_bound(begin, end, test_entry); + R_UNLESS(it != end, settings::ResultSettingsItemNotFound()); + R_UNLESS(*it == test_entry, settings::ResultSettingsItemNotFound()); + + *out = std::addressof(*it); + R_SUCCEED(); + } + + Result ParseSettingsItemValueImpl(const char *name, const char *key, const char *val_tup) { + const char *delimiter = strchr(val_tup, '!'); + const char *value_str = delimiter + 1; + const char *type = val_tup; + + R_UNLESS(delimiter != nullptr, settings::ResultInvalidFormatSettingsItemValue()); + + while (std::isspace(static_cast<unsigned char>(*type)) && type != delimiter) { + type++; + } + + const size_t type_len = delimiter - type; + const size_t value_len = strlen(value_str); + R_UNLESS(type_len > 0, settings::ResultInvalidFormatSettingsItemValue()); + R_UNLESS(value_len > 0, settings::ResultInvalidFormatSettingsItemValue()); + + /* Create new value. */ + SdKeyValueStoreEntry new_value = {}; + + /* Find name and key. */ + R_TRY(FindSettingsName(std::addressof(new_value.name), name)); + R_TRY(FindSettingsItemKey(std::addressof(new_value.key), key)); + + if (strncasecmp(type, "str", type_len) == 0 || strncasecmp(type, "string", type_len) == 0) { + const size_t size = value_len + 1; + R_TRY(AllocateValue(std::addressof(new_value.value), size)); + std::memcpy(new_value.value, value_str, size); + new_value.value_size = size; + } else if (strncasecmp(type, "hex", type_len) == 0 || strncasecmp(type, "bytes", type_len) == 0) { + R_UNLESS(value_len > 0, settings::ResultInvalidFormatSettingsItemValue()); + R_UNLESS(value_len % 2 == 0, settings::ResultInvalidFormatSettingsItemValue()); + R_UNLESS(IsHexadecimal(value_str), settings::ResultInvalidFormatSettingsItemValue()); + + const size_t size = value_len / 2; + R_TRY(AllocateValue(std::addressof(new_value.value), size)); + new_value.value_size = size; + + u8 *data = reinterpret_cast<u8 *>(new_value.value); + for (size_t i = 0; i < size; i++) { + data[i >> 1] = hextoi(value_str[i]) << (4 * (i & 1)); + } + } else if (strncasecmp(type, "u8", type_len) == 0) { + R_TRY((ParseSettingsItemIntegralValue<u8>(new_value, value_str))); + } else if (strncasecmp(type, "u16", type_len) == 0) { + R_TRY((ParseSettingsItemIntegralValue<u16>(new_value, value_str))); + } else if (strncasecmp(type, "u32", type_len) == 0) { + R_TRY((ParseSettingsItemIntegralValue<u32>(new_value, value_str))); + } else if (strncasecmp(type, "u64", type_len) == 0) { + R_TRY((ParseSettingsItemIntegralValue<u64>(new_value, value_str))); + } else { + R_THROW(settings::ResultInvalidFormatSettingsItemValue()); + } + + /* Insert the entry. */ + bool inserted = false; + for (auto &entry : g_entries) { + if (!entry.HasValue() || entry == new_value) { + entry = new_value; + inserted = true; + break; + } + } + + R_UNLESS(inserted, settings::ResultSettingsItemValueAllocationFailed()); + R_SUCCEED(); + } + + Result ParseSettingsItemValue(const char *name, const char *key, const char *value) { + R_TRY(ValidateSettingsName(name)); + R_TRY(ValidateSettingsItemKey(key)); + R_RETURN(ParseSettingsItemValueImpl(name, key, value)); + } + + static int SystemSettingsIniHandler(void *user, const char *name, const char *key, const char *value) { + Result *parse_res = reinterpret_cast<Result *>(user); + + /* Once we fail to parse a value, don't parse further. */ + if (R_FAILED(*parse_res)) { + return 0; + } + + *parse_res = ParseSettingsItemValue(name, key, value); + return R_SUCCEEDED(*parse_res) ? 1 : 0; + } + + Result LoadSdCardKeyValueStore() { + /* Open file. */ + /* It's okay if the file isn't readable/present, because we already loaded defaults. */ + std::unique_ptr<ams::fs::fsa::IFile> file; + { + FsFile f; + R_SUCCEED_IF(R_FAILED(ams::mitm::fs::OpenAtmosphereSdFile(std::addressof(f), "/config/system_settings.ini", fs::OpenMode_Read))); + file = std::make_unique<ams::fs::RemoteFile>(f); + } + AMS_ABORT_UNLESS(file != nullptr); + + Result parse_result = ResultSuccess(); + util::ini::ParseFile(file.get(), std::addressof(parse_result), SystemSettingsIniHandler); + R_TRY(parse_result); + + R_SUCCEED(); + } + + void LoadDefaultCustomSettings() { + /* Disable uploading error reports to Nintendo. */ + R_ABORT_UNLESS(ParseSettingsItemValue("eupld", "upload_enabled", "u8!0x0")); + + /* Enable USB 3.0 superspeed for homebrew */ + R_ABORT_UNLESS(ParseSettingsItemValue("usb", "usb30_force_enabled", spl::IsUsb30ForceEnabled() ? "u8!0x1" : "u8!0x0")); + + /* Control whether RO should ease its validation of NROs. */ + /* (note: this is normally not necessary, and ips patches can be used.) */ + R_ABORT_UNLESS(ParseSettingsItemValue("ro", "ease_nro_restriction", "u8!0x1")); + + /* Control whether lm should log to the SD card. */ + /* Note that this setting does nothing when log manager is not enabled. */ + R_ABORT_UNLESS(ParseSettingsItemValue("lm", "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. */ + R_ABORT_UNLESS(ParseSettingsItemValue("lm", "sd_card_log_output_directory", "str!atmosphere/binlogs")); + + /* Control whether erpt reports should always be preserved, instead of automatically cleaning periodically. */ + /* 0 = Disabled, 1 = Enabled */ + R_ABORT_UNLESS(ParseSettingsItemValue("erpt", "disable_automatic_report_cleanup", "u8!0x0")); + + /* Atmosphere custom settings. */ + + /* Reboot from fatal automatically after some number of milliseconds. */ + /* If field is not present or 0, fatal will wait indefinitely for user input. */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "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. */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "power_menu_reboot_function", "str!payload")); + + /* Enable writing to BIS partitions for HBL. */ + /* This is probably undesirable for normal usage. */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_hbl_bis_write", "u8!0x0")); + + /* Controls whether dmnt cheats should be toggled on or off by */ + /* default. 1 = toggled on by default, 0 = toggled off by default. */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "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. */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "dmnt_always_save_cheat_toggles", "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. */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "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) */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_am_debug_mode", "u8!0x0")); + + /* Controls whether dns.mitm is enabled. */ + /* 0 = Disabled, 1 = Enabled */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "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) */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "add_defaults_to_dns_hosts", "u8!0x1")); + + /* Controls whether dns.mitm logs to the sd card for debugging. */ + /* 0 = Disabled, 1 = Enabled */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_dns_mitm_debug_log", "u8!0x0")); + + /* Controls whether htc is enabled. */ + /* TODO: Change this to default 1 when tma2 is ready for inclusion in atmosphere releases. */ + /* 0 = Disabled, 1 = Enabled */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_htc", "u8!0x0")); + + /* Controls whether atmosphere's dmnt.gen2 gdbstub should run as a standalone via sockets. */ + /* Note that this setting is ignored (and treated as 0) when htc is enabled. */ + /* Note that this setting may disappear in the future. */ + /* 0 = Disabled, 1 = Enabled */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_standalone_gdbstub", "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 */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "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 */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_external_bluetooth_db", "u8!0x0")); + + /* Hbloader custom settings. */ + + /* 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. */ + R_ABORT_UNLESS(ParseSettingsItemValue("hbloader", "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. */ + R_ABORT_UNLESS(ParseSettingsItemValue("hbloader", "applet_heap_reservation_size", "u64!0x8600000")); + } + + } + + void InitializeSdCardKeyValueStore() { + /* Load in hardcoded defaults. */ + /* These will be overwritten if present on the SD card. */ + LoadDefaultCustomSettings(); + + /* Parse custom settings off the SD card. */ + R_ABORT_UNLESS(LoadSdCardKeyValueStore()); + + /* Determine how many custom settings are present. */ + for (size_t i = 0; i < util::size(g_entries); i++) { + if (!g_entries[i].HasValue()) { + g_num_entries = i; + break; + } + } + + /* Ensure that the custom settings entries are sorted. */ + if (g_num_entries) { + std::sort(g_entries, g_entries + g_num_entries); + } + } + + Result GetSdCardKeyValueStoreSettingsItemValueSize(size_t *out_size, const char *name, const char *key) { + SdKeyValueStoreEntry *entry = nullptr; + R_TRY(GetEntry(std::addressof(entry), name, key)); + + *out_size = entry->value_size; + R_SUCCEED(); + } + + Result GetSdCardKeyValueStoreSettingsItemValue(size_t *out_size, void *dst, size_t dst_size, const char *name, const char *key) { + R_UNLESS(dst != nullptr, settings::ResultNullSettingsItemValueBuffer()); + + SdKeyValueStoreEntry *entry = nullptr; + R_TRY(GetEntry(std::addressof(entry), name, key)); + + const size_t size = std::min(entry->value_size, dst_size); + if (size > 0) { + std::memcpy(dst, entry->value, size); + } + *out_size = size; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.hpp new file mode 100644 index 00000000..2eeb7ad5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::settings::fwdbg { + + void InitializeSdCardKeyValueStore(); + + Result GetSdCardKeyValueStoreSettingsItemValueSize(size_t *out_size, const char *name, const char *key); + Result GetSdCardKeyValueStoreSettingsItemValue(size_t *out_size, void *dst, size_t dst_size, const char *name, const char *key); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_apply_manager.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_apply_manager.cpp new file mode 100644 index 00000000..dd9d2169 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_apply_manager.cpp @@ -0,0 +1,98 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sysupdater_apply_manager.hpp" + +namespace ams::mitm::sysupdater { + + namespace { + + alignas(os::MemoryPageSize) u8 g_boot_image_update_buffer[64_KB]; + + updater::BootImageUpdateType GetBootImageUpdateType() { + /* NOTE: Here Nintendo uses the value of system setting systeminitializer!boot_image_update_type...but we prefer not to take the risk. */ + return updater::GetBootImageUpdateType(spl::GetHardwareType()); + } + + Result MarkPreCommitForBootImages() { + /* Set verification required for both normal and safe mode. */ + R_TRY(updater::MarkVerifyingRequired(updater::BootModeType::Normal, g_boot_image_update_buffer, sizeof(g_boot_image_update_buffer))); + R_TRY(updater::MarkVerifyingRequired(updater::BootModeType::Safe, g_boot_image_update_buffer, sizeof(g_boot_image_update_buffer))); + + /* Pre-commit is now marked. */ + R_SUCCEED(); + } + + Result UpdateBootImages() { + /* Define a helper to update the images. */ + auto UpdateBootImageImpl = [](updater::BootModeType boot_mode, updater::BootImageUpdateType boot_image_update_type) -> Result { + /* Get the boot image package id. */ + ncm::SystemDataId boot_image_package_id = {}; + R_TRY_CATCH(updater::GetBootImagePackageId(std::addressof(boot_image_package_id), boot_mode, g_boot_image_update_buffer, sizeof(g_boot_image_update_buffer))) { + R_CATCH(updater::ResultBootImagePackageNotFound) { + /* Nintendo simply falls through when the package is not found. */ + } + } R_END_TRY_CATCH; + + + /* Update the boot images. */ + R_TRY_CATCH(updater::UpdateBootImagesFromPackage(boot_image_package_id, boot_mode, g_boot_image_update_buffer, sizeof(g_boot_image_update_buffer), boot_image_update_type)) { + R_CATCH(updater::ResultBootImagePackageNotFound) { + /* Nintendo simply falls through when the package is not found. */ + } + } R_END_TRY_CATCH; + + /* Mark the images verified. */ + R_TRY(updater::MarkVerified(boot_mode, g_boot_image_update_buffer, sizeof(g_boot_image_update_buffer))); + + /* The boot images are updated. */ + R_SUCCEED(); + }; + + /* Get the boot image update type. */ + auto boot_image_update_type = GetBootImageUpdateType(); + + /* Update boot images for safe mode. */ + R_TRY(UpdateBootImageImpl(updater::BootModeType::Safe, boot_image_update_type)); + + /* Update boot images for normal mode. */ + R_TRY(UpdateBootImageImpl(updater::BootModeType::Normal, boot_image_update_type)); + + /* Both sets of images are updated. */ + R_SUCCEED(); + } + + } + + Result SystemUpdateApplyManager::ApplyPackageTask(ncm::PackageSystemDowngradeTask *task) { + /* Lock the apply mutex. */ + std::scoped_lock lk(m_apply_mutex); + + /* NOTE: Here, Nintendo creates a system report for the update. */ + + /* Mark boot images to note that we're updating. */ + R_TRY(MarkPreCommitForBootImages()); + + /* Commit the task. */ + R_TRY(task->Commit()); + + /* Update the boot images. */ + R_TRY(UpdateBootImages()); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_apply_manager.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_apply_manager.hpp new file mode 100644 index 00000000..9a71c948 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_apply_manager.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::mitm::sysupdater { + + class SystemUpdateApplyManager { + private: + os::SdkMutex m_apply_mutex; + public: + constexpr SystemUpdateApplyManager() : m_apply_mutex() { /* ... */ } + + Result ApplyPackageTask(ncm::PackageSystemDowngradeTask *task); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_impl.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_impl.cpp new file mode 100644 index 00000000..ffe82fd9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_impl.cpp @@ -0,0 +1,80 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sysupdater_async_impl.hpp" +#include "sysupdater_async_thread_allocator.hpp" + +namespace ams::mitm::sysupdater { + + Result AsyncBase::ToAsyncResult(Result result) { + R_TRY_CATCH(result) { + R_CONVERT(nim::ResultHttpConnectionCanceled, ns::ResultCanceled()); + R_CONVERT(ncm::ResultInstallTaskCancelled, ns::ResultCanceled()); + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + AsyncPrepareSdCardUpdateImpl::~AsyncPrepareSdCardUpdateImpl() { + if (m_thread_info) { + os::WaitThread(m_thread_info->thread); + os::DestroyThread(m_thread_info->thread); + GetAsyncThreadAllocator()->Free(*m_thread_info); + } + } + + Result AsyncPrepareSdCardUpdateImpl::Run() { + /* Get a thread info. */ + ThreadInfo info; + R_TRY(GetAsyncThreadAllocator()->Allocate(std::addressof(info))); + + /* Set the thread info's priority. */ + info.priority = AMS_GET_SYSTEM_THREAD_PRIORITY(mitm_sysupdater, AsyncPrepareSdCardUpdateTask); + + /* Ensure that we clean up appropriately. */ + ON_SCOPE_EXIT { + if (!m_thread_info) { + GetAsyncThreadAllocator()->Free(info); + } + }; + + /* Create a thread for the task. */ + R_TRY(os::CreateThread(info.thread, [](void *arg) { + auto *async = reinterpret_cast<AsyncPrepareSdCardUpdateImpl *>(arg); + async->m_result = async->Execute(); + async->m_event.Signal(); + }, this, info.stack, info.stack_size, info.priority)); + + /* Set the thread name. */ + os::SetThreadNamePointer(info.thread, AMS_GET_SYSTEM_THREAD_NAME(mitm_sysupdater, AsyncPrepareSdCardUpdateTask)); + + /* Start the thread. */ + os::StartThread(info.thread); + + /* Set our thread info. */ + m_thread_info = info; + R_SUCCEED(); + } + + Result AsyncPrepareSdCardUpdateImpl::Execute() { + R_RETURN(m_task->PrepareAndExecute()); + } + + void AsyncPrepareSdCardUpdateImpl::CancelImpl() { + m_task->Cancel(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_impl.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_impl.hpp new file mode 100644 index 00000000..f2b41c5c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_impl.hpp @@ -0,0 +1,112 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "sysupdater_thread_allocator.hpp" + +namespace ams::mitm::sysupdater { + + class ErrorContextHolder { + private: + err::ErrorContext m_error_context; + public: + constexpr ErrorContextHolder() : m_error_context{} { /* ... */ } + + virtual ~ErrorContextHolder() { /* ... */ } + + template<typename T> + Result SaveErrorContextIfFailed(T &async, Result result) { + ON_RESULT_FAILURE { async.GetErrorContext(std::addressof(m_error_context)); }; + + R_RETURN(result); + } + + template<typename T> + Result GetAndSaveErrorContext(T &async) { + R_RETURN(this->SaveErrorContextIfFailed(async, async.Get())); + } + + template<typename T> + Result SaveInternalTaskErrorContextIfFailed(T &async, Result result) { + ON_RESULT_FAILURE { async.CreateErrorContext(std::addressof(m_error_context)); }; + + R_RETURN(result); + } + + const err::ErrorContext &GetErrorContextImpl() { + return m_error_context; + } + }; + + class AsyncBase { + public: + virtual ~AsyncBase() { /* ... */ } + + static Result ToAsyncResult(Result result); + + Result Cancel() { + this->CancelImpl(); + R_SUCCEED(); + } + + virtual Result GetErrorContext(sf::Out<err::ErrorContext> out) { + *out = {}; + R_SUCCEED(); + } + private: + virtual void CancelImpl() = 0; + }; + + class AsyncResultBase : public AsyncBase { + public: + virtual ~AsyncResultBase() { /* ... */ } + + Result Get() { + R_RETURN(ToAsyncResult(this->GetImpl())); + } + private: + virtual Result GetImpl() = 0; + }; + static_assert(ns::impl::IsIAsyncResult<AsyncResultBase>); + + /* NOTE: Based off of ns AsyncPrepareCardUpdateImpl. */ + /* We don't implement the RequestServer::ManagedStop details, as we don't implement stoppable request list. */ + class AsyncPrepareSdCardUpdateImpl : public AsyncResultBase, private ErrorContextHolder { + private: + Result m_result; + os::SystemEvent m_event; + util::optional<ThreadInfo> m_thread_info; + ncm::InstallTaskBase *m_task; + public: + AsyncPrepareSdCardUpdateImpl(ncm::InstallTaskBase *task) : m_result(ResultSuccess()), m_event(os::EventClearMode_ManualClear, true), m_thread_info(), m_task(task) { /* ... */ } + virtual ~AsyncPrepareSdCardUpdateImpl(); + + os::SystemEvent &GetEvent() { return m_event; } + + virtual Result GetErrorContext(sf::Out<err::ErrorContext> out) override { + *out = ErrorContextHolder::GetErrorContextImpl(); + R_SUCCEED(); + } + + Result Run(); + private: + Result Execute(); + + virtual void CancelImpl() override; + virtual Result GetImpl() override { return m_result; } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_thread_allocator.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_thread_allocator.cpp new file mode 100644 index 00000000..43774e73 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_thread_allocator.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sysupdater_async_thread_allocator.hpp" + +namespace ams::mitm::sysupdater { + + namespace { + + constexpr inline int AsyncThreadCount = 1; + constexpr inline size_t AsyncThreadStackSize = 32_KB; + + os::ThreadType g_async_threads[AsyncThreadCount]; + alignas(os::ThreadStackAlignment) u8 g_async_thread_stack_heap[AsyncThreadCount * AsyncThreadStackSize]; + + constinit ThreadAllocator g_async_thread_allocator(g_async_threads, AsyncThreadCount, os::InvalidThreadPriority, g_async_thread_stack_heap, sizeof(g_async_thread_stack_heap), AsyncThreadStackSize); + + } + + ThreadAllocator *GetAsyncThreadAllocator() { + return std::addressof(g_async_thread_allocator); + } + + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_thread_allocator.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_thread_allocator.hpp new file mode 100644 index 00000000..6f28f3c8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_async_thread_allocator.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "sysupdater_thread_allocator.hpp" + +namespace ams::mitm::sysupdater { + + ThreadAllocator *GetAsyncThreadAllocator(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.cpp new file mode 100644 index 00000000..bb83af1b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.cpp @@ -0,0 +1,257 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sysupdater_fs_utils.hpp" + +namespace ams::mitm::sysupdater { + + namespace { + + constexpr inline const char * const NcaExtension = ".nca"; + constexpr inline const char * const NspExtension = ".nsp"; + constexpr inline const size_t NcaExtensionSize = 4; + constexpr inline const size_t NspExtensionSize = 4; + + constexpr const ams::fs::PathFlags SdCardContentMetaPathNormalizePathFlags = [] { + fs::PathFlags flags{}; + flags.AllowMountName(); + return flags; + }(); + + static_assert(NcaExtensionSize == NspExtensionSize); + constexpr inline const size_t NcaNspExtensionSize = NcaExtensionSize; + + Result CheckNcaOrNsp(const char **path) { + /* Ensure that the path is currently at the mount name delimeter. */ + R_UNLESS(util::Strncmp(*path, ams::fs::impl::MountNameDelimiter, util::Strnlen(ams::fs::impl::MountNameDelimiter, ams::fs::EntryNameLengthMax)) == 0, fs::ResultPathNotFound()); + + /* Advance past the :. */ + static_assert(ams::fs::impl::MountNameDelimiter[0] == ':'); + *path += 1; + + /* Ensure path is long enough for the extension. */ + const size_t path_len = util::Strnlen(*path, ams::fs::EntryNameLengthMax); + R_UNLESS(path_len > NcaNspExtensionSize, fs::ResultPathNotFound()); + + /* Get the extension. */ + const char * const extension = *path + path_len - NcaNspExtensionSize; + + /* Ensure nca or nsp. */ + const bool is_nca = util::Strnicmp(extension, NcaExtension, NcaNspExtensionSize) == 0; + const bool is_nsp = util::Strnicmp(extension, NspExtension, NcaNspExtensionSize) == 0; + R_UNLESS(is_nca || is_nsp, fs::ResultPathNotFound()); + + R_SUCCEED(); + } + + Result ParseMountName(const char **path, std::shared_ptr<ams::fs::fsa::IFileSystem> *out) { + /* The equivalent function here supports all the common mount names; we'll only support the SD card, system content storage. */ + if (const auto mount_len = util::Strnlen(ams::fs::impl::SdCardFileSystemMountName, ams::fs::MountNameLengthMax); util::Strncmp(*path, ams::fs::impl::SdCardFileSystemMountName, mount_len) == 0) { + /* Advance the path. */ + *path += mount_len; + + /* Open the SD card. This uses libnx bindings. */ + FsFileSystem fs; + R_TRY(fsOpenSdCardFileSystem(std::addressof(fs))); + + /* Allocate a new filesystem wrapper. */ + auto fsa = std::make_shared<ams::fs::RemoteFileSystem>(fs); + R_UNLESS(fsa != nullptr, fs::ResultAllocationMemoryFailedInSdCardA()); + + /* Set the output fs. */ + *out = std::move(fsa); + } else if (const auto mount_len = util::Strnlen(ams::fs::impl::ContentStorageSystemMountName, ams::fs::MountNameLengthMax); util::Strncmp(*path, ams::fs::impl::ContentStorageSystemMountName, mount_len) == 0) { + /* Advance the path. */ + *path += mount_len; + + /* Open the system content storage. This uses libnx bindings. */ + FsFileSystem fs; + R_TRY(fsOpenContentStorageFileSystem(std::addressof(fs), FsContentStorageId_System)); + + /* Allocate a new filesystem wrapper. */ + auto fsa = std::make_shared<ams::fs::RemoteFileSystem>(fs); + R_UNLESS(fsa != nullptr, fs::ResultAllocationMemoryFailedInContentStorageA()); + + /* Set the output fs. */ + *out = std::move(fsa); + } else { + R_THROW(fs::ResultPathNotFound()); + } + + /* Ensure that there's something that could be a mount name delimiter. */ + R_UNLESS(util::Strnlen(*path, fs::EntryNameLengthMax) != 0, fs::ResultPathNotFound()); + + R_SUCCEED(); + } + + Result ParseNsp(const char **path, std::shared_ptr<ams::fs::fsa::IFileSystem> *out, std::shared_ptr<ams::fs::fsa::IFileSystem> base_fs) { + const char *work_path = *path; + + /* Advance to the nsp extension. */ + while (true) { + if (util::Strnicmp(work_path, NspExtension, NspExtensionSize) == 0) { + if (work_path[NspExtensionSize] == '\x00' || work_path[NspExtensionSize] == '/') { + break; + } + work_path += NspExtensionSize; + } else { + R_UNLESS(*work_path != '\x00', fs::ResultPathNotFound()); + + work_path += 1; + } + } + + /* Advance past the extension. */ + work_path += NspExtensionSize; + + /* Get the nsp path. */ + ams::fs::Path nsp_path; + R_TRY(nsp_path.InitializeWithNormalization(*path, work_path - *path)); + + /* Open the file storage. */ + std::shared_ptr<ams::fs::FileStorageBasedFileSystem> file_storage = fssystem::AllocateShared<ams::fs::FileStorageBasedFileSystem>(); + R_UNLESS(file_storage != nullptr, fs::ResultAllocationMemoryFailedInNcaFileSystemServiceImplA()); + R_TRY(file_storage->Initialize(std::move(base_fs), nsp_path, ams::fs::OpenMode_Read)); + + /* Create a partition fs. */ + R_TRY(fssystem::GetFileSystemCreatorInterfaces()->partition_fs_creator->Create(out, std::move(file_storage))); + + /* Update the path. */ + *path = work_path; + + R_SUCCEED(); + } + + Result ParseNca(const char **path, std::shared_ptr<fssystem::NcaReader> *out, std::shared_ptr<ams::fs::fsa::IFileSystem> base_fs) { + /* Open the file storage. */ + std::shared_ptr<ams::fs::FileStorageBasedFileSystem> file_storage = fssystem::AllocateShared<ams::fs::FileStorageBasedFileSystem>(); + R_UNLESS(file_storage != nullptr, fs::ResultAllocationMemoryFailedInNcaFileSystemServiceImplB()); + + /* Get the nca path. */ + ams::fs::Path nca_path; + R_TRY(nca_path.InitializeWithNormalization(*path)); + + R_TRY(file_storage->Initialize(std::move(base_fs), nca_path, ams::fs::OpenMode_Read)); + + /* Create the nca reader. */ + std::shared_ptr<fssystem::NcaReader> nca_reader; + R_TRY(fssystem::GetFileSystemCreatorInterfaces()->storage_on_nca_creator->CreateNcaReader(std::addressof(nca_reader), file_storage)); + + /* NOTE: Here Nintendo validates program ID, but this does not need checking in the meta case. */ + + /* Set output reader. */ + *out = std::move(nca_reader); + R_SUCCEED(); + } + + Result OpenMetaStorage(std::shared_ptr<ams::fs::IStorage> *out, std::shared_ptr<fssystem::IAsynchronousAccessSplitter> *out_splitter, std::shared_ptr<fssystem::NcaReader> nca_reader, fssystem::NcaFsHeader::FsType *out_fs_type) { + /* Ensure the nca is a meta nca. */ + R_UNLESS(nca_reader->GetContentType() == fssystem::NcaHeader::ContentType::Meta, fs::ResultPreconditionViolation()); + + /* We only support SD card ncas, so ensure this isn't a gamecard nca. */ + R_UNLESS(nca_reader->GetDistributionType() != fssystem::NcaHeader::DistributionType::GameCard, fs::ResultPermissionDenied()); + + /* Here Nintendo would call GetPartitionIndex(), but we don't need to, because it's meta. */ + constexpr int MetaPartitionIndex = 0; + + /* Open fs header reader. */ + fssystem::NcaFsHeaderReader fs_header_reader; + R_TRY(fssystem::GetFileSystemCreatorInterfaces()->storage_on_nca_creator->Create(out, out_splitter, std::addressof(fs_header_reader), std::move(nca_reader), MetaPartitionIndex)); + + /* Set the output fs type. */ + *out_fs_type = fs_header_reader.GetFsType(); + R_SUCCEED(); + } + + Result OpenContentMetaFileSystem(std::shared_ptr<ams::fs::fsa::IFileSystem> *out, const char *path) { + /* Parse the mount name to get a filesystem. */ + const char *cur_path = path; + std::shared_ptr<ams::fs::fsa::IFileSystem> base_fs; + R_TRY(ParseMountName(std::addressof(cur_path), std::addressof(base_fs))); + + /* Ensure the path is an nca or nsp. */ + R_TRY(CheckNcaOrNsp(std::addressof(cur_path))); + + /* Try to parse as nsp. */ + std::shared_ptr<ams::fs::fsa::IFileSystem> nsp_fs; + if (R_SUCCEEDED(ParseNsp(std::addressof(cur_path), std::addressof(nsp_fs), base_fs))) { + /* nsp target is only allowed for type package, and we're assuming type meta. */ + R_UNLESS(*path != '\x00', fs::ResultInvalidArgument()); + + /* Use the nsp fs as the base fs. */ + base_fs = std::move(nsp_fs); + } + + /* Parse as nca. */ + std::shared_ptr<fssystem::NcaReader> nca_reader; + R_TRY(ParseNca(std::addressof(cur_path), std::addressof(nca_reader), std::move(base_fs))); + + /* Open meta storage. */ + std::shared_ptr<ams::fs::IStorage> storage; + std::shared_ptr<fssystem::IAsynchronousAccessSplitter> splitter; + fssystem::NcaFsHeader::FsType fs_type = static_cast<fssystem::NcaFsHeader::FsType>(~0); + R_TRY(OpenMetaStorage(std::addressof(storage), std::addressof(splitter), std::move(nca_reader), std::addressof(fs_type))); + + /* Open the appropriate interface. */ + const auto * const creator_intfs = fssystem::GetFileSystemCreatorInterfaces(); + switch (fs_type) { + case fssystem::NcaFsHeader::FsType::PartitionFs: R_RETURN(creator_intfs->partition_fs_creator->Create(out, std::move(storage))); + case fssystem::NcaFsHeader::FsType::RomFs: R_RETURN(creator_intfs->rom_fs_creator->Create(out, std::move(storage))); + default: + R_THROW(fs::ResultInvalidNcaFileSystemType()); + } + } + + } + + bool PathView::HasPrefix(util::string_view prefix) const { + return m_path.compare(0, prefix.length(), prefix) == 0; + } + + bool PathView::HasSuffix(util::string_view suffix) const { + return m_path.compare(m_path.length() - suffix.length(), suffix.length(), suffix) == 0; + } + + util::string_view PathView::GetFileName() const { + auto pos = m_path.find_last_of("/"); + return pos != util::string_view::npos ? m_path.substr(pos + 1) : m_path; + } + + Result MountSdCardContentMeta(const char *mount_name, const char *path, ams::fs::ContentAttributes attr) { + /* TODO: What does attributes actually get used for? */ + AMS_UNUSED(attr); + + /* Sanitize input. */ + /* NOTE: This is an internal API, so we won't bother with mount name sanitization. */ + R_UNLESS(path != nullptr, fs::ResultInvalidPath()); + + /* Normalize the path. */ + char normalized_path[fs::EntryNameLengthMax + 1]; + R_TRY(ams::fs::PathFormatter::Normalize(normalized_path, sizeof(normalized_path), path, std::strlen(path) + 1, SdCardContentMetaPathNormalizePathFlags)); + + /* Open the filesystem. */ + std::shared_ptr<ams::fs::fsa::IFileSystem> fs; + R_TRY(OpenContentMetaFileSystem(std::addressof(fs), normalized_path)); + + /* Create a holder for the fs. */ + std::unique_ptr unique_fs = std::make_unique<fssystem::ForwardingFileSystem>(std::move(fs)); + R_UNLESS(unique_fs != nullptr, fs::ResultAllocationMemoryFailedNew()); + + /* Register the fs. */ + R_RETURN(ams::fs::fsa::Register(mount_name, std::move(unique_fs))); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.hpp new file mode 100644 index 00000000..90405a3a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::mitm::sysupdater { + + class PathView { + private: + util::string_view m_path; + public: + PathView(util::string_view p) : m_path(p) { /* ...*/ } + bool HasPrefix(util::string_view prefix) const; + bool HasSuffix(util::string_view suffix) const; + util::string_view GetFileName() const; + }; + + Result MountSdCardContentMeta(const char *mount_name, const char *path, ams::fs::ContentAttributes attr); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.cpp new file mode 100644 index 00000000..74db5d5f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.cpp @@ -0,0 +1,65 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "../amsmitm_initialization.hpp" +#include "sysupdater_module.hpp" +#include "sysupdater_service.hpp" +#include "sysupdater_async_impl.hpp" + +namespace ams::mitm::sysupdater { + + namespace { + + enum PortIndex { + PortIndex_Sysupdater, + PortIndex_Count, + }; + + constexpr sm::ServiceName SystemUpdateServiceName = sm::ServiceName::Encode("ams:su"); + constexpr size_t SystemUpdateMaxSessions = 1; + + constexpr size_t MaxSessions = SystemUpdateMaxSessions + 3; + + struct ServerOptions { + static constexpr size_t PointerBufferSize = 1_KB; + static constexpr size_t MaxDomains = 0; + static constexpr size_t MaxDomainObjects = 0; + static constexpr bool CanDeferInvokeRequest = false; + static constexpr bool CanManageMitmServers = false; + }; + + sf::hipc::ServerManager<PortIndex_Count, ServerOptions, MaxSessions> g_server_manager; + + constinit sf::UnmanagedServiceObject<sysupdater::impl::ISystemUpdateInterface, sysupdater::SystemUpdateService> g_system_update_service_object; + + } + + void MitmModule::ThreadFunction(void *) { + /* Wait until initialization is complete. */ + mitm::WaitInitialized(); + + /* Connect to nim. */ + nim::InitializeForNetworkInstallManager(); + ON_SCOPE_EXIT { nim::FinalizeForNetworkInstallManager(); }; + + /* Register ams:su. */ + R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_system_update_service_object.GetShared(), SystemUpdateServiceName, SystemUpdateMaxSessions)); + + /* Loop forever, servicing our services. */ + g_server_manager.LoopProcess(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.hpp new file mode 100644 index 00000000..eb08283a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "../amsmitm_module.hpp" + +namespace ams::mitm::sysupdater { + + DEFINE_MITM_MODULE_CLASS(0x8000, AMS_GET_SYSTEM_THREAD_PRIORITY(mitm_sysupdater, IpcServer)); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.cpp new file mode 100644 index 00000000..c5c652b4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.cpp @@ -0,0 +1,522 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sysupdater_service.hpp" +#include "sysupdater_async_impl.hpp" +#include "sysupdater_fs_utils.hpp" + +namespace ams::mitm::sysupdater { + + namespace { + + /* ExFat NCAs prior to 2.0.0 do not actually include the exfat driver, and don't boot. */ + constexpr inline u32 MinimumVersionForExFatDriver = 65536; + + bool IsExFatDriverSupported(const ncm::ContentMetaInfo &info) { + return info.version >= MinimumVersionForExFatDriver && ((info.attributes & ncm::ContentMetaAttribute_IncludesExFatDriver) != 0); + } + + template<typename F> + Result ForEachFileInDirectory(const char *root_path, F f) { + /* Open the directory. */ + fs::DirectoryHandle dir; + R_TRY(fs::OpenDirectory(std::addressof(dir), root_path, fs::OpenDirectoryMode_File)); + ON_SCOPE_EXIT { fs::CloseDirectory(dir); }; + + while (true) { + /* Read the current entry. */ + s64 count; + fs::DirectoryEntry entry; + R_TRY(fs::ReadDirectory(std::addressof(count), std::addressof(entry), dir, 1)); + if (count == 0) { + break; + } + + /* Invoke our handler on the entry. */ + bool done; + R_TRY(f(std::addressof(done), entry)); + R_SUCCEED_IF(done); + } + + R_SUCCEED(); + } + + Result ConvertToFsCommonPath(char *dst, size_t dst_size, const char *package_root_path, const char *entry_path) { + char package_path[ams::fs::EntryNameLengthMax]; + + const size_t path_len = util::SNPrintf(package_path, sizeof(package_path), "%s%s", package_root_path, entry_path); + AMS_ABORT_UNLESS(path_len < ams::fs::EntryNameLengthMax); + + R_RETURN(ams::fs::ConvertToFsCommonPath(dst, dst_size, package_path)); + } + + Result LoadContentMeta(ncm::AutoBuffer *out, const char *package_root_path, const fs::DirectoryEntry &entry) { + AMS_ABORT_UNLESS(PathView(entry.name).HasSuffix(".cnmt.nca")); + + char path[ams::fs::EntryNameLengthMax]; + R_TRY(ConvertToFsCommonPath(path, sizeof(path), package_root_path, entry.name)); + + R_RETURN(ncm::TryReadContentMetaPath(out, path, ncm::ReadContentMetaPathAlongWithExtendedDataAndDigest)); + } + + Result ReadContentMetaPath(ncm::AutoBuffer *out, const char *package_root, const ncm::ContentInfo &content_info) { + /* Get the .cnmt.nca path for the info. */ + char cnmt_nca_name[ncm::ContentIdStringLength + 10]; + ncm::GetStringFromContentId(cnmt_nca_name, sizeof(cnmt_nca_name), content_info.GetId()); + std::memcpy(cnmt_nca_name + ncm::ContentIdStringLength, ".cnmt.nca", std::strlen(".cnmt.nca")); + cnmt_nca_name[sizeof(cnmt_nca_name) - 1] = '\x00'; + + /* Create a new path. */ + ncm::Path content_path; + R_TRY(ConvertToFsCommonPath(content_path.str, sizeof(content_path.str), package_root, cnmt_nca_name)); + + /* Read the content meta path. */ + R_RETURN(ncm::TryReadContentMetaPath(out, content_path.str, ncm::ReadContentMetaPathAlongWithExtendedDataAndDigest)); + } + + Result GetSystemUpdateUpdateContentInfoFromPackage(ncm::ContentInfo *out, const char *package_root) { + bool found_system_update = false; + + /* Iterate over all files to find the system update meta. */ + R_TRY(ForEachFileInDirectory(package_root, [&](bool *done, const fs::DirectoryEntry &entry) -> Result { + /* Don't early terminate by default. */ + *done = false; + + /* We have nothing to list if we're not looking at a meta. */ + R_SUCCEED_IF(!PathView(entry.name).HasSuffix(".cnmt.nca")); + + /* Read the content meta path, and build. */ + ncm::AutoBuffer package_meta; + R_TRY(LoadContentMeta(std::addressof(package_meta), package_root, entry)); + + /* Create a reader. */ + const auto reader = ncm::PackagedContentMetaReader(package_meta.Get(), package_meta.GetSize()); + + /* If we find a system update, we're potentially done. */ + if (reader.GetHeader()->type == ncm::ContentMetaType::SystemUpdate) { + /* Try to parse a content id from the name. */ + auto content_id = ncm::GetContentIdFromString(entry.name, sizeof(entry.name)); + R_UNLESS(content_id, ncm::ResultInvalidPackageFormat()); + + /* We're done. */ + *done = true; + found_system_update = true; + + *out = ncm::ContentInfo::Make(*content_id, entry.file_size, ncm::ContentInfo::DefaultContentAttributes, ncm::ContentType::Meta); + } + + R_SUCCEED(); + })); + + /* If we didn't find anything, error. */ + R_UNLESS(found_system_update, ncm::ResultSystemUpdateNotFoundInPackage()); + + R_SUCCEED(); + } + + Result ValidateSystemUpdate(Result *out_result, Result *out_exfat_result, UpdateValidationInfo *out_info, const ncm::PackagedContentMetaReader &update_reader, const char *package_root) { + /* Clear output. */ + *out_result = ResultSuccess(); + *out_exfat_result = ResultSuccess(); + + /* We want to track all content the update requires. */ + const size_t num_content_metas = update_reader.GetContentMetaCount(); + bool content_meta_valid[num_content_metas] = {}; + + /* Allocate a buffer to use for validation. */ + size_t data_buffer_size = 1_MB; + void *data_buffer; + do { + data_buffer = std::malloc(data_buffer_size); + if (data_buffer != nullptr) { + break; + } + + data_buffer_size /= 2; + } while (data_buffer_size >= 16_KB); + R_UNLESS(data_buffer != nullptr, fs::ResultAllocationMemoryFailedNew()); + + ON_SCOPE_EXIT { std::free(data_buffer); }; + + /* Declare helper for result validation. */ + auto ValidateResult = [&](Result result) ALWAYS_INLINE_LAMBDA -> Result { + *out_result = result; + R_RETURN(result); + }; + + /* Iterate over all files to find all content metas. */ + R_TRY(ForEachFileInDirectory(package_root, [&](bool *done, const fs::DirectoryEntry &entry) -> Result { + /* Clear output. */ + *out_info = {}; + + /* Don't early terminate by default. */ + *done = false; + + /* We have nothing to list if we're not looking at a meta. */ + R_SUCCEED_IF(!PathView(entry.name).HasSuffix(".cnmt.nca")); + + /* Read the content meta path, and build. */ + ncm::AutoBuffer package_meta; + R_TRY(LoadContentMeta(std::addressof(package_meta), package_root, entry)); + + /* Create a reader. */ + const auto reader = ncm::PackagedContentMetaReader(package_meta.Get(), package_meta.GetSize()); + + /* Get the key for the reader. */ + const auto key = reader.GetKey(); + + /* Check if we need to validate this content. */ + bool need_validate = false; + size_t validation_index = 0; + for (size_t i = 0; i < num_content_metas; ++i) { + if (update_reader.GetContentMetaInfo(i)->ToKey() == key) { + need_validate = true; + validation_index = i; + break; + } + } + + /* If we don't need to validate, continue. */ + R_SUCCEED_IF(!need_validate); + + /* We're validating. */ + out_info->invalid_key = key; + + /* Validate all contents. */ + for (size_t i = 0; i < reader.GetContentCount(); ++i) { + const auto *content_info = reader.GetContentInfo(i); + const auto &content_id = content_info->GetId(); + const s64 content_size = content_info->info.GetSize(); + out_info->invalid_content_id = content_id; + + /* Get the content id string. */ + auto content_id_str = ncm::GetContentIdString(content_id); + + /* Open the file. */ + fs::FileHandle file; + { + char path[fs::EntryNameLengthMax]; + util::SNPrintf(path, sizeof(path), "%s%s%s", package_root, content_id_str.data, content_info->GetType() == ncm::ContentType::Meta ? ".cnmt.nca" : ".nca"); + if (R_FAILED(ValidateResult(fs::OpenFile(std::addressof(file), path, ams::fs::OpenMode_Read)))) { + *done = true; + R_SUCCEED(); + } + } + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Validate the file size is correct. */ + s64 file_size; + if (R_FAILED(ValidateResult(fs::GetFileSize(std::addressof(file_size), file)))) { + *done = true; + R_SUCCEED(); + } + if (file_size != content_size) { + *out_result = ncm::ResultInvalidContentHash(); + *done = true; + R_SUCCEED(); + } + + /* Read and hash the file in chunks. */ + crypto::Sha256Generator sha; + sha.Initialize(); + + s64 ofs = 0; + while (ofs < content_size) { + const size_t cur_size = std::min(static_cast<size_t>(content_size - ofs), data_buffer_size); + if (R_FAILED(ValidateResult(fs::ReadFile(file, ofs, data_buffer, cur_size)))) { + *done = true; + R_SUCCEED(); + } + + sha.Update(data_buffer, cur_size); + + ofs += cur_size; + } + + /* Get the hash. */ + ncm::Digest calc_digest; + sha.GetHash(std::addressof(calc_digest), sizeof(calc_digest)); + + /* Validate the hash. */ + if (std::memcmp(std::addressof(calc_digest), std::addressof(content_info->digest), sizeof(ncm::Digest)) != 0) { + *out_result = ncm::ResultInvalidContentHash(); + *done = true; + R_SUCCEED(); + } + } + + /* Mark the relevant content as validated. */ + content_meta_valid[validation_index] = true; + *out_info = {}; + + R_SUCCEED(); + })); + + /* If we're otherwise going to succeed, ensure that every content was found. */ + if (R_SUCCEEDED(*out_result)) { + for (size_t i = 0; i < num_content_metas; ++i) { + if (!content_meta_valid[i]) { + const ncm::ContentMetaInfo *info = update_reader.GetContentMetaInfo(i); + + *out_info = { .invalid_key = info->ToKey(), }; + + if (IsExFatDriverSupported(*info)) { + *out_exfat_result = fs::ResultPathNotFound(); + /* Continue, in case there's a non-exFAT failure result. */ + } else { + *out_result = fs::ResultPathNotFound(); + break; + } + } + } + } + + R_SUCCEED(); + } + + Result FormatUserPackagePath(ncm::Path *out, const ncm::Path &user_path) { + /* Ensure that the user path is valid. */ + R_UNLESS(user_path.str[0] == '/', fs::ResultInvalidPath()); + + /* Print as @Sdcard:<user_path>/ */ + util::SNPrintf(out->str, sizeof(out->str), "%s:%s/", ams::fs::impl::SdCardFileSystemMountName, user_path.str); + + /* Normalize, if the user provided an ending / */ + const size_t len = std::strlen(out->str); + if (out->str[len - 1] == '/' && out->str[len - 2] == '/') { + out->str[len - 1] = '\x00'; + } + + R_SUCCEED(); + } + + const char *GetFirmwareVariationSettingName(settings::system::PlatformRegion region) { + switch (region) { + case settings::system::PlatformRegion_Global: return "firmware_variation"; + case settings::system::PlatformRegion_China: return "t_firmware_variation"; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + ncm::FirmwareVariationId GetFirmwareVariationId() { + /* Get the firmware variation setting name. */ + const char * const setting_name = GetFirmwareVariationSettingName(settings::system::GetPlatformRegion()); + + /* Retrieve the firmware variation id. */ + ncm::FirmwareVariationId id = {}; + settings::fwdbg::GetSettingsItemValue(std::addressof(id.value), sizeof(u8), "ns.systemupdate", setting_name); + + return id; + } + + } + + Result SystemUpdateService::GetUpdateInformation(sf::Out<UpdateInformation> out, const ncm::Path &path) { + /* Adjust the path. */ + ncm::Path package_root; + R_TRY(FormatUserPackagePath(std::addressof(package_root), path)); + + /* Create a new update information. */ + UpdateInformation update_info = {}; + + /* Parse the update. */ + { + /* Get the content info for the system update. */ + ncm::ContentInfo content_info; + R_TRY(GetSystemUpdateUpdateContentInfoFromPackage(std::addressof(content_info), package_root.str)); + + /* Read the content meta. */ + ncm::AutoBuffer content_meta_buffer; + R_TRY(ReadContentMetaPath(std::addressof(content_meta_buffer), package_root.str, content_info)); + + /* Create a reader. */ + const auto reader = ncm::PackagedContentMetaReader(content_meta_buffer.Get(), content_meta_buffer.GetSize()); + + /* Get the version from the header. */ + update_info.version = reader.GetHeader()->version; + + /* Iterate over infos to find the system update info. */ + for (size_t i = 0; i < reader.GetContentMetaCount(); ++i) { + const auto &meta_info = *reader.GetContentMetaInfo(i); + + switch (meta_info.type) { + case ncm::ContentMetaType::BootImagePackage: + /* Detect exFAT support. */ + update_info.exfat_supported |= IsExFatDriverSupported(meta_info); + break; + default: + break; + } + } + + /* Default to no firmware variations. */ + update_info.firmware_variation_count = 0; + + /* Parse firmware variations if relevant. */ + if (reader.GetExtendedDataSize() != 0) { + /* Get the actual firmware variation count. */ + ncm::SystemUpdateMetaExtendedDataReader extended_data_reader(reader.GetExtendedData(), reader.GetExtendedDataSize()); + update_info.firmware_variation_count = extended_data_reader.GetFirmwareVariationCount(); + + /* NOTE: Update this if Nintendo ever actually releases an update with this many variations? */ + R_UNLESS(update_info.firmware_variation_count <= FirmwareVariationCountMax, ncm::ResultInvalidFirmwareVariation()); + + for (size_t i = 0; i < update_info.firmware_variation_count; ++i) { + update_info.firmware_variation_ids[i] = *extended_data_reader.GetFirmwareVariationId(i); + } + } + } + + /* Set the parsed update info. */ + out.SetValue(update_info); + R_SUCCEED(); + } + + Result SystemUpdateService::ValidateUpdate(sf::Out<Result> out_validate_result, sf::Out<Result> out_validate_exfat_result, sf::Out<UpdateValidationInfo> out_validate_info, const ncm::Path &path) { + /* Adjust the path. */ + ncm::Path package_root; + R_TRY(FormatUserPackagePath(std::addressof(package_root), path)); + + /* Parse the update. */ + { + /* Get the content info for the system update. */ + ncm::ContentInfo content_info; + R_TRY(GetSystemUpdateUpdateContentInfoFromPackage(std::addressof(content_info), package_root.str)); + + /* Read the content meta. */ + ncm::AutoBuffer content_meta_buffer; + R_TRY(ReadContentMetaPath(std::addressof(content_meta_buffer), package_root.str, content_info)); + + /* Create a reader. */ + const auto reader = ncm::PackagedContentMetaReader(content_meta_buffer.Get(), content_meta_buffer.GetSize()); + + /* Validate the update. */ + R_TRY(ValidateSystemUpdate(out_validate_result.GetPointer(), out_validate_exfat_result.GetPointer(), out_validate_info.GetPointer(), reader, package_root.str)); + } + + R_SUCCEED(); + }; + + Result SystemUpdateService::SetupUpdate(sf::CopyHandle &&transfer_memory, u64 transfer_memory_size, const ncm::Path &path, bool exfat) { + R_RETURN(this->SetupUpdateImpl(std::move(transfer_memory), transfer_memory_size, path, exfat, GetFirmwareVariationId())); + } + + Result SystemUpdateService::SetupUpdateWithVariation(sf::CopyHandle &&transfer_memory, u64 transfer_memory_size, const ncm::Path &path, bool exfat, ncm::FirmwareVariationId firmware_variation_id) { + R_RETURN(this->SetupUpdateImpl(std::move(transfer_memory), transfer_memory_size, path, exfat, firmware_variation_id)); + } + + Result SystemUpdateService::RequestPrepareUpdate(sf::OutCopyHandle out_event_handle, sf::Out<sf::SharedPointer<ns::impl::IAsyncResult>> out_async) { + /* Ensure the update is setup but not prepared. */ + R_UNLESS(m_setup_update, ns::ResultCardUpdateNotSetup()); + R_UNLESS(!m_requested_update, ns::ResultPrepareCardUpdateAlreadyRequested()); + + /* Create the async result. */ + auto async_result = sf::CreateSharedObjectEmplaced<ns::impl::IAsyncResult, AsyncPrepareSdCardUpdateImpl>(std::addressof(*m_update_task)); + R_UNLESS(async_result != nullptr, ns::ResultOutOfMaxRunningTask()); + + /* Run the task. */ + R_TRY(async_result.GetImpl().Run()); + + /* We prepared the task! */ + m_requested_update = true; + out_event_handle.SetValue(async_result.GetImpl().GetEvent().GetReadableHandle(), false); + *out_async = std::move(async_result); + + R_SUCCEED(); + } + + Result SystemUpdateService::GetPrepareUpdateProgress(sf::Out<SystemUpdateProgress> out) { + /* Ensure the update is setup. */ + R_UNLESS(m_setup_update, ns::ResultCardUpdateNotSetup()); + + /* Get the progress. */ + auto install_progress = m_update_task->GetProgress(); + out.SetValue({ .current_size = install_progress.installed_size, .total_size = install_progress.total_size }); + R_SUCCEED(); + } + + Result SystemUpdateService::HasPreparedUpdate(sf::Out<bool> out) { + /* Ensure the update is setup. */ + R_UNLESS(m_setup_update, ns::ResultCardUpdateNotSetup()); + + out.SetValue(m_update_task->GetProgress().state == ncm::InstallProgressState::Downloaded); + R_SUCCEED(); + } + + Result SystemUpdateService::ApplyPreparedUpdate() { + /* Ensure the update is setup. */ + R_UNLESS(m_setup_update, ns::ResultCardUpdateNotSetup()); + + /* Ensure the update is prepared. */ + R_UNLESS(m_update_task->GetProgress().state == ncm::InstallProgressState::Downloaded, ns::ResultCardUpdateNotPrepared()); + + /* Apply the task. */ + R_TRY(m_apply_manager.ApplyPackageTask(std::addressof(*m_update_task))); + + R_SUCCEED(); + } + + Result SystemUpdateService::SetupUpdateImpl(sf::NativeHandle &&transfer_memory, u64 transfer_memory_size, const ncm::Path &path, bool exfat, ncm::FirmwareVariationId firmware_variation_id) { + /* Ensure we don't already have an update set up. */ + R_UNLESS(!m_setup_update, ns::ResultCardUpdateAlreadySetup()); + + /* Destroy any existing update tasks. */ + nim::SystemUpdateTaskId id; + auto count = nim::ListSystemUpdateTask(std::addressof(id), 1); + if (count > 0) { + R_TRY(nim::DestroySystemUpdateTask(id)); + } + + /* Initialize the update task. */ + R_TRY(InitializeUpdateTask(std::move(transfer_memory), transfer_memory_size, path, exfat, firmware_variation_id)); + + /* The update is now set up. */ + m_setup_update = true; + R_SUCCEED(); + } + + Result SystemUpdateService::InitializeUpdateTask(sf::NativeHandle &&transfer_memory, u64 transfer_memory_size, const ncm::Path &path, bool exfat, ncm::FirmwareVariationId firmware_variation_id) { + /* Map the transfer memory. */ + const size_t tmem_buffer_size = static_cast<size_t>(transfer_memory_size); + m_update_transfer_memory.emplace(tmem_buffer_size, transfer_memory.GetOsHandle(), transfer_memory.IsManaged()); + transfer_memory.Detach(); + + void *tmem_buffer; + R_TRY(m_update_transfer_memory->Map(std::addressof(tmem_buffer), os::MemoryPermission_None)); + auto tmem_guard = SCOPE_GUARD { + m_update_transfer_memory->Unmap(); + m_update_transfer_memory = util::nullopt; + }; + + /* Adjust the package root. */ + ncm::Path package_root; + R_TRY(FormatUserPackagePath(std::addressof(package_root), path)); + + /* Ensure that we can create an update context. */ + R_TRY(fs::EnsureDirectory("@Sdcard:/atmosphere/update/")); + const char *context_path = "@Sdcard:/atmosphere/update/cup.ctx"; + + /* Create and initialize the update task. */ + m_update_task.emplace(); + R_TRY(m_update_task->Initialize(package_root.str, context_path, tmem_buffer, tmem_buffer_size, exfat, firmware_variation_id)); + + /* We successfully setup the update. */ + tmem_guard.Cancel(); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.hpp new file mode 100644 index 00000000..57a3b000 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.hpp @@ -0,0 +1,81 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "sysupdater_apply_manager.hpp" + +namespace ams::mitm::sysupdater { + + constexpr inline size_t FirmwareVariationCountMax = 16; + + struct UpdateInformation { + u32 version; + bool exfat_supported; + u32 firmware_variation_count; + ncm::FirmwareVariationId firmware_variation_ids[FirmwareVariationCountMax]; + }; + + struct UpdateValidationInfo { + ncm::ContentMetaKey invalid_key; + ncm::ContentId invalid_content_id; + }; + + struct SystemUpdateProgress { + s64 current_size; + s64 total_size; + }; + +} + +#define AMS_SYSUPDATER_SYSTEM_UPDATE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, GetUpdateInformation, (sf::Out<mitm::sysupdater::UpdateInformation> out, const ncm::Path &path), (out, path)) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, ValidateUpdate, (sf::Out<Result> out_validate_result, sf::Out<Result> out_validate_exfat_result, sf::Out<mitm::sysupdater::UpdateValidationInfo> out_validate_info, const ncm::Path &path), (out_validate_result, out_validate_exfat_result, out_validate_info, path)) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, SetupUpdate, (sf::CopyHandle &&transfer_memory, u64 transfer_memory_size, const ncm::Path &path, bool exfat), (std::move(transfer_memory), transfer_memory_size, path, exfat)) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, SetupUpdateWithVariation, (sf::CopyHandle &&transfer_memory, u64 transfer_memory_size, const ncm::Path &path, bool exfat, ncm::FirmwareVariationId firmware_variation_id), (std::move(transfer_memory), transfer_memory_size, path, exfat, firmware_variation_id)) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, RequestPrepareUpdate, (sf::OutCopyHandle out_event_handle, sf::Out<sf::SharedPointer<ns::impl::IAsyncResult>> out_async), (out_event_handle, out_async)) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, GetPrepareUpdateProgress, (sf::Out<mitm::sysupdater::SystemUpdateProgress> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, HasPreparedUpdate, (sf::Out<bool> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, ApplyPreparedUpdate, (), ()) + +AMS_SF_DEFINE_INTERFACE(ams::mitm::sysupdater::impl, ISystemUpdateInterface, AMS_SYSUPDATER_SYSTEM_UPDATE_INTERFACE_INFO, 0x00000000) + +namespace ams::mitm::sysupdater { + + class SystemUpdateService { + private: + SystemUpdateApplyManager m_apply_manager; + util::optional<ncm::PackageSystemDowngradeTask> m_update_task; + util::optional<os::TransferMemory> m_update_transfer_memory; + bool m_setup_update; + bool m_requested_update; + public: + constexpr SystemUpdateService() : m_apply_manager(), m_update_task(), m_update_transfer_memory(), m_setup_update(false), m_requested_update(false) { /* ... */ } + private: + Result SetupUpdateImpl(sf::NativeHandle &&transfer_memory, u64 transfer_memory_size, const ncm::Path &path, bool exfat, ncm::FirmwareVariationId firmware_variation_id); + Result InitializeUpdateTask(sf::NativeHandle &&transfer_memory, u64 transfer_memory_size, const ncm::Path &path, bool exfat, ncm::FirmwareVariationId firmware_variation_id); + public: + Result GetUpdateInformation(sf::Out<UpdateInformation> out, const ncm::Path &path); + Result ValidateUpdate(sf::Out<Result> out_validate_result, sf::Out<Result> out_validate_exfat_result, sf::Out<UpdateValidationInfo> out_validate_info, const ncm::Path &path); + Result SetupUpdate(sf::CopyHandle &&transfer_memory, u64 transfer_memory_size, const ncm::Path &path, bool exfat); + Result SetupUpdateWithVariation(sf::CopyHandle &&transfer_memory, u64 transfer_memory_size, const ncm::Path &path, bool exfat, ncm::FirmwareVariationId firmware_variation_id); + Result RequestPrepareUpdate(sf::OutCopyHandle out_event_handle, sf::Out<sf::SharedPointer<ns::impl::IAsyncResult>> out_async); + Result GetPrepareUpdateProgress(sf::Out<SystemUpdateProgress> out); + Result HasPreparedUpdate(sf::Out<bool> out); + Result ApplyPreparedUpdate(); + }; + static_assert(impl::IsISystemUpdateInterface<SystemUpdateService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_thread_allocator.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_thread_allocator.cpp new file mode 100644 index 00000000..82d878f5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_thread_allocator.cpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sysupdater_thread_allocator.hpp" + +namespace ams::mitm::sysupdater { + + Result ThreadAllocator::Allocate(ThreadInfo *out) { + std::scoped_lock lk(m_mutex); + + for (int i = 0; i < m_thread_count; ++i) { + const u64 mask = (static_cast<u64>(1) << i); + if ((m_bitmap & mask) == 0) { + *out = { + .thread = m_thread_list + i, + .priority = m_thread_priority, + .stack = m_stack_heap + (m_stack_size * i), + .stack_size = m_stack_size, + }; + m_bitmap |= mask; + R_SUCCEED(); + } + } + + R_THROW(ns::ResultOutOfMaxRunningTask()); + } + + void ThreadAllocator::Free(const ThreadInfo &info) { + std::scoped_lock lk(m_mutex); + + for (int i = 0; i < m_thread_count; ++i) { + if (info.thread == std::addressof(m_thread_list[i])) { + const u64 mask = (static_cast<u64>(1) << i); + m_bitmap &= ~mask; + return; + } + } + + AMS_ABORT("Invalid thread passed to ThreadAllocator::Free"); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_thread_allocator.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_thread_allocator.hpp new file mode 100644 index 00000000..2dce16be --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/source/sysupdater/sysupdater_thread_allocator.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::mitm::sysupdater { + + struct ThreadInfo { + os::ThreadType *thread; + int priority; + void *stack; + size_t stack_size; + }; + + /* NOTE: Nintendo uses a util::BitArray, but this seems excessive. */ + class ThreadAllocator { + private: + os::ThreadType *m_thread_list; + const int m_thread_priority; + const int m_thread_count; + u8 *m_stack_heap; + const size_t m_stack_heap_size; + const size_t m_stack_size; + u64 m_bitmap; + os::SdkMutex m_mutex; + public: + constexpr ThreadAllocator(os::ThreadType *thread_list, int count, int priority, u8 *stack_heap, size_t stack_heap_size, size_t stack_size) + : m_thread_list(thread_list), m_thread_priority(priority), m_thread_count(count), m_stack_heap(stack_heap), m_stack_heap_size(stack_heap_size), m_stack_size(stack_size), m_bitmap() + { + AMS_ASSERT(count <= static_cast<int>(stack_heap_size / stack_size)); + AMS_ASSERT(count <= static_cast<int>(BITSIZEOF(m_bitmap))); + } + + Result Allocate(ThreadInfo *out); + void Free(const ThreadInfo &info); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/system_module.mk new file mode 100644 index 00000000..af1d8221 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ams_mitm/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := kip + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/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)/system_module.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)/system_module.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/stratosphere/boot/boot.json b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/boot.json new file mode 100644 index 00000000..13391f73 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/boot.json @@ -0,0 +1,177 @@ +{ + "name": "boot", + "title_id": "0x0100000000000005", + "main_thread_stack_size": "0x8000", + "main_thread_priority": 27, + "default_cpu_id": 3, + "process_category": 1, + "use_secure_memory": false, + "immortal": false, + "kernel_capabilities": [ + { + "type": "handle_table_size", + "value": 128 + }, + { + "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", + "svcSynchronizePreemptionState": "0x36", + "svcReadWriteRegister": "0x4E", + "svcCreateInterruptEvent": "0x53", + "svcQueryIoMapping": "0x55", + "svcCreateDeviceAddressSpace": "0x56", + "svcAttachDeviceAddressSpace": "0x57", + "svcDetachDeviceAddressSpace": "0x58", + "svcMapDeviceAddressSpaceAligned": "0x5A", + "svcUnmapDeviceAddressSpace": "0x5C", + "svcFlushProcessDataCache": "0x5F", + "svcCallSecureMonitor": "0x7F" + } + }, + { + "type": "map", + "value": { + "address": "0x50003000", + "size": "0x1000", + "is_ro": false, + "is_io": true + } + }, + { + "type": "map", + "value": { + "address": "0x54200000", + "size": "0x3000", + "is_ro": false, + "is_io": true + } + }, + { + "type": "map", + "value": { + "address": "0x54300000", + "size": "0x1000", + "is_ro": false, + "is_io": true + } + }, + { + "type": "map", + "value": { + "address": "0x60006000", + "size": "0x1000", + "is_ro": false, + "is_io": true + } + }, + { + "type": "map", + "value": { + "address": "0x6000D000", + "size": "0x1000", + "is_ro": false, + "is_io": true + } + }, + { + "type": "map", + "value": { + "address": "0x70000000", + "size": "0x4000", + "is_ro": false, + "is_io": true + } + }, + { + "type": "map", + "value": { + "address": "0x7000A000", + "size": "0x1000", + "is_ro": false, + "is_io": true + } + }, + { + "type": "map", + "value": { + "address": "0x7000C000", + "size": "0x2000", + "is_ro": false, + "is_io": true + } + }, + { + "type": "map", + "value": { + "address": "0x700E3000", + "size": "0x1000", + "is_ro": false, + "is_io": true + } + }, + { + "type": "irq_pair", + "value": [ + 70, + 116 + ] + }, + { + "type": "irq_pair", + "value": [ + 124, + 152 + ] + }, + { + "type": "irq_pair", + "value": [ + 85, + 95 + ] + } + ] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/api_overrides/pcv_clkrst_api_for_boot.board.nintendo_nx.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/api_overrides/pcv_clkrst_api_for_boot.board.nintendo_nx.cpp new file mode 100644 index 00000000..ed132c58 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/api_overrides/pcv_clkrst_api_for_boot.board.nintendo_nx.cpp @@ -0,0 +1,210 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams { + + namespace { + + constexpr inline dd::PhysicalAddress ClkRstRegistersPhysicalAddress = 0x60006000; + constexpr inline size_t ClkRstRegistersSize = 0x1000; + + uintptr_t g_clkrst_registers = dd::QueryIoMapping(ClkRstRegistersPhysicalAddress, ClkRstRegistersSize); + + struct ClkRstDefinition { + u32 clk_src_ofs; + u32 clk_en_ofs; + u32 rst_ofs; + u32 clk_en_index; + u32 rst_index; + u8 clk_src; + u8 clk_divisor; + }; + + constexpr inline const ClkRstDefinition Definitions[] = { + { CLK_RST_CONTROLLER_CLK_SOURCE_I2C1, CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_CONTROLLER_CLK_ENB_I2C1_INDEX, CLK_RST_CONTROLLER_RST_I2C1_INDEX, 0x00 /* PLLP_OUT0 */, 0x04 }, + { CLK_RST_CONTROLLER_CLK_SOURCE_I2C2, CLK_RST_CONTROLLER_CLK_OUT_ENB_H, CLK_RST_CONTROLLER_RST_DEVICES_H, CLK_RST_CONTROLLER_CLK_ENB_I2C2_INDEX, CLK_RST_CONTROLLER_RST_I2C2_INDEX, 0x00 /* PLLP_OUT0 */, 0x04 }, + { CLK_RST_CONTROLLER_CLK_SOURCE_I2C3, CLK_RST_CONTROLLER_CLK_OUT_ENB_U, CLK_RST_CONTROLLER_RST_DEVICES_U, CLK_RST_CONTROLLER_CLK_ENB_I2C3_INDEX, CLK_RST_CONTROLLER_RST_I2C3_INDEX, 0x00 /* PLLP_OUT0 */, 0x04 }, + { CLK_RST_CONTROLLER_CLK_SOURCE_I2C4, CLK_RST_CONTROLLER_CLK_OUT_ENB_V, CLK_RST_CONTROLLER_RST_DEVICES_V, CLK_RST_CONTROLLER_CLK_ENB_I2C4_INDEX, CLK_RST_CONTROLLER_RST_I2C4_INDEX, 0x00 /* PLLP_OUT0 */, 0x04 }, + { CLK_RST_CONTROLLER_CLK_SOURCE_I2C5, CLK_RST_CONTROLLER_CLK_OUT_ENB_H, CLK_RST_CONTROLLER_RST_DEVICES_H, CLK_RST_CONTROLLER_CLK_ENB_I2C5_INDEX, CLK_RST_CONTROLLER_RST_I2C5_INDEX, 0x00 /* PLLP_OUT0 */, 0x04 }, + { CLK_RST_CONTROLLER_CLK_SOURCE_I2C6, CLK_RST_CONTROLLER_CLK_OUT_ENB_X, CLK_RST_CONTROLLER_RST_DEVICES_X, CLK_RST_CONTROLLER_CLK_ENB_I2C6_INDEX, CLK_RST_CONTROLLER_RST_I2C6_INDEX, 0x00 /* PLLP_OUT0 */, 0x04 }, + { CLK_RST_CONTROLLER_CLK_SOURCE_PWM, CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_CONTROLLER_CLK_ENB_PWM_INDEX, CLK_RST_CONTROLLER_RST_PWM_INDEX, 0x00 /* PLLP_OUT0 */, 0x10 }, + }; + + constexpr inline const struct { + const ClkRstDefinition *definition; + DeviceCode device_code; + pcv::Module pcv_module; + } ClkRstDefinitionMap[] = { + { std::addressof(Definitions[0]), i2c::DeviceCode_I2c1, pcv::Module_I2c1 }, + { std::addressof(Definitions[1]), i2c::DeviceCode_I2c2, pcv::Module_I2c2 }, + { std::addressof(Definitions[2]), i2c::DeviceCode_I2c3, pcv::Module_I2c3 }, + { std::addressof(Definitions[3]), i2c::DeviceCode_I2c4, pcv::Module_I2c4 }, + { std::addressof(Definitions[4]), i2c::DeviceCode_I2c5, pcv::Module_I2c5 }, + { std::addressof(Definitions[5]), i2c::DeviceCode_I2c6, pcv::Module_I2c6 }, + { std::addressof(Definitions[6]), pwm::DeviceCode_LcdBacklight, pcv::Module_Pwm }, + }; + + ALWAYS_INLINE const ClkRstDefinition *GetDefinition(DeviceCode device_code) { + const ClkRstDefinition *def = nullptr; + + for (const auto &entry : ClkRstDefinitionMap) { + if (entry.device_code == device_code) { + def = entry.definition; + break; + } + } + + AMS_ABORT_UNLESS(def != nullptr); + return def; + } + + ALWAYS_INLINE const ClkRstDefinition &GetDefinition(pcv::Module module) { + const ClkRstDefinition *def = nullptr; + + for (const auto &entry : ClkRstDefinitionMap) { + if (entry.pcv_module == module) { + def = entry.definition; + break; + } + } + + AMS_ABORT_UNLESS(def != nullptr); + return *def; + } + + ALWAYS_INLINE const ClkRstDefinition &GetDefinition(clkrst::ClkRstSession *session) { + const ClkRstDefinition *def = nullptr; + + for (const auto &entry : ClkRstDefinitionMap) { + if (session->_session == entry.definition) { + def = entry.definition; + break; + } + } + + AMS_ABORT_UNLESS(def != nullptr); + return *def; + } + + void SetResetEnabled(const ClkRstDefinition &def, bool en) { + /* Set or clear reset. */ + reg::ReadWrite(g_clkrst_registers + def.rst_ofs, (en ? 1u : 0u) << def.rst_index, 1u << def.rst_index); + } + + void SetClockEnabled(const ClkRstDefinition &def, bool en) { + /* Set or clear reset. */ + reg::ReadWrite(g_clkrst_registers + def.clk_en_ofs, (en ? 1u : 0u) << def.clk_en_index, 1u << def.clk_en_index); + } + + void SetClockRate(const ClkRstDefinition &def, u32 hz) { + AMS_UNUSED(hz); + + /* Enable clock. */ + reg::ReadWrite(g_clkrst_registers + def.clk_en_ofs, 1u << def.clk_en_index, 1u << def.clk_en_index); + + /* Set the clock divisor. */ + reg::ReadWrite(g_clkrst_registers + def.clk_src_ofs, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_CLK_DIVISOR, def.clk_divisor)); + + /* Wait for 2us for clock setting to take. */ + os::SleepThread(TimeSpan::FromMicroSeconds(2)); + + /* Set the clock source. */ + reg::ReadWrite(g_clkrst_registers + def.clk_src_ofs, CLK_RST_REG_BITS_VALUE(CLK_SOURCE_CLK_SOURCE, def.clk_src)); + + /* Wait for 2us for clock setting to take. */ + os::SleepThread(TimeSpan::FromMicroSeconds(2)); + } + + } + + namespace clkrst { + + void Initialize() { + /* ... */ + } + + void Finalize() { + /* ... */ + } + + Result OpenSession(ClkRstSession *out, DeviceCode device_code) { + /* Get the relevant definition. */ + out->_session = const_cast<ClkRstDefinition *>(GetDefinition(device_code)); + R_SUCCEED(); + } + + void CloseSession(ClkRstSession *session) { + /* Clear the session. */ + session->_session = nullptr; + } + + void SetResetAsserted(ClkRstSession *session) { + /* Assert reset. */ + SetResetEnabled(GetDefinition(session), true); + } + + void SetResetDeasserted(ClkRstSession *session) { + /* Assert reset. */ + SetResetEnabled(GetDefinition(session), false); + } + + void SetClockRate(ClkRstSession *session, u32 hz) { + /* Set the clock rate. */ + SetClockRate(GetDefinition(session), hz); + } + + void SetClockDisabled(ClkRstSession *session) { + AMS_UNUSED(session); + AMS_ABORT("SetClockDisabled not implemented for boot system module"); + } + + } + + namespace pcv { + + void Initialize() { + /* ... */ + } + + void Finalize() { + /* ... */ + } + + Result SetClockEnabled(Module module, bool en) { + /* Set clock. */ + SetClockEnabled(GetDefinition(module), en); + + R_SUCCEED(); + } + + Result SetClockRate(Module module, ClockHz hz) { + /* Set the clock rate. */ + SetClockRate(GetDefinition(module), hz); + + R_SUCCEED(); + } + + Result SetReset(Module module, bool en) { + /* Set reset. */ + SetResetEnabled(GetDefinition(module), en); + + R_SUCCEED(); + } + + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/api_overrides/regulator_api_for_boot.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/api_overrides/regulator_api_for_boot.cpp new file mode 100644 index 00000000..b7c86a0b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/api_overrides/regulator_api_for_boot.cpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::regulator { + + void Initialize() { + /* ... */ + } + + void Finalize() { + /* ... */ + } + + Result OpenSession(RegulatorSession *out, DeviceCode device_code) { + AMS_UNUSED(out, device_code); + R_SUCCEED(); + } + + void CloseSession(RegulatorSession *session) { + AMS_UNUSED(session); + } + + bool GetVoltageEnabled(RegulatorSession *session) { + AMS_UNUSED(session); + return true; + } + + Result SetVoltageEnabled(RegulatorSession *session, bool enabled) { + AMS_UNUSED(session, enabled); + R_SUCCEED(); + } + + Result SetVoltageValue(RegulatorSession *session, u32 micro_volts) { + AMS_UNUSED(session, micro_volts); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_battery_driver.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_battery_driver.hpp new file mode 100644 index 00000000..00b74794 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_battery_driver.hpp @@ -0,0 +1,81 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::boot { + + class BatteryDriver { + NON_COPYABLE(BatteryDriver); + NON_MOVEABLE(BatteryDriver); + private: + static constinit inline powctl::Session s_battery_session{powctl::Session::ConstantInitializeTag{}}; + static constinit inline int s_reference_count{0}; + public: + BatteryDriver() { + if ((s_reference_count++) == 0) { + R_ABORT_UNLESS(powctl::OpenSession(std::addressof(s_battery_session), powctl::DeviceCode_Max17050, ddsf::AccessMode_ReadWrite)); + } + } + + ~BatteryDriver() { + if ((--s_reference_count) == 0) { + powctl::CloseSession(s_battery_session); + } + } + public: + Result IsBatteryRemoved(bool *out) { + bool present; + R_TRY(powctl::IsBatteryPresent(std::addressof(present), s_battery_session)); + + *out = !present; + R_SUCCEED(); + } + + Result GetChargePercentage(float *out) { + R_RETURN(powctl::GetBatteryChargePercentage(out, s_battery_session)); + } + + Result GetAverageVCell(int *out) { + R_RETURN(powctl::GetBatteryAverageVCell(out, s_battery_session)); + } + + Result GetVoltageFuelGaugePercentage(float *out) { + R_RETURN(powctl::GetBatteryVoltageFuelGaugePercentage(out, s_battery_session)); + } + + Result GetAverageCurrent(int *out) { + R_RETURN(powctl::GetBatteryAverageCurrent(out, s_battery_session)); + } + + Result GetCurrent(int *out) { + R_RETURN(powctl::GetBatteryCurrent(out, s_battery_session)); + } + + Result GetTemperature(float *out) { + R_RETURN(powctl::GetBatteryTemperature(out, s_battery_session)); + } + + Result IsI2cShutdownEnabled(bool *out) { + R_RETURN(powctl::IsBatteryI2cShutdownEnabled(out, s_battery_session)); + } + + Result SetI2cShutdownEnabled(bool en) { + R_RETURN(powctl::SetBatteryI2cShutdownEnabled(s_battery_session, en)); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_battery_icon_charging.inc b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_battery_icon_charging.inc new file mode 100644 index 00000000..2939a0ec --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_battery_icon_charging.inc @@ -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 <http://www.gnu.org/licenses/>. + */ + +constexpr size_t ChargingBatteryX = 26; +constexpr size_t ChargingBatteryY = 28; +constexpr size_t ChargingBatteryW = 76; +constexpr size_t ChargingBatteryH = 28; + +constexpr size_t ChargingBatteryMeterX = 7; +constexpr size_t ChargingBatteryMeterY = 8; +constexpr size_t ChargingBatteryMeterW = 31; +constexpr size_t ChargingBatteryMeterH = 12; +static_assert(ChargingBatteryMeterX + ChargingBatteryMeterW < ChargingBatteryW && ChargingBatteryMeterY + ChargingBatteryMeterH < ChargingBatteryH, "Incorrect ChargingBatteryMeter definition!"); + +constexpr u32 ChargingBattery[] = {0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF3D3D3D, 0xFFB2B2B2, 0xFFE6E6E6, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE6E6E6, 0xFFB2B2B2, 0xFF3D3D3D, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7A7A7A, 0xFFFEFEFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFDFDFD, 0xFF7A7A7A, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF3D3D3D, 0xFFFDFDFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFDFDFD, 0xFF3D3D3D, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFB2B2B2, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFB2B2B2, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF494949, 0xFFE2E2E2, 0xFFE3E3E3, 0xFF494949, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE6E6E6, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF4A4A4A, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF4B4B4B, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE6E6E6, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF0E0E0E, 0xFFEDEDED, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE2E2E2, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7C7C7C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE4E4E4, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF0E0E0E, 0xFFEEEEEE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF7C7C7C, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF2E9E1B, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF2E9E1B, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF494949, 0xFFE3E3E3, 0xFFE3E3E3, 0xFF494949, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7C7C7C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEEEEEE, 0xFF0E0E0E, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE3E3E3, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE3E3E3, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF0E0E0E, 0xFFEEEEEE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF7C7C7C, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7C7C7C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEEEEEE, 0xFF0E0E0E, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF0E0E0E, 0xFFEEEEEE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF7C7C7C, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7C7C7C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDEDEDE, 0xFF464646, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDEDEDE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDEDEDE, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF464646, 0xFFDEDEDE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF7C7C7C, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7C7C7C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEEEEEE, 0xFF0E0E0E, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF0E0E0E, 0xFFEEEEEE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF7C7C7C, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE3E3E3, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE3E3E3, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7C7C7C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEEEEEE, 0xFF0E0E0E, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF2E9E1B, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF40DE25, 0xFF2E9D1B, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF494949, 0xFFE3E3E3, 0xFFE3E3E3, 0xFF494949, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF0E0E0E, 0xFFEEEEEE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF7C7C7C, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7C7C7C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEEEEEE, 0xFF0E0E0E, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE4E4E4, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF7C7C7C, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE6E6E6, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF4B4B4B, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF4B4B4B, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE6E6E6, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE2E2E2, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEDEDED, 0xFF0E0E0E, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFB2B2B2, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFB2B2B2, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF494949, 0xFFE3E3E3, 0xFFE2E2E2, 0xFF494949, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF3D3D3D, 0xFFFDFDFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFDFDFD, 0xFF3D3D3D, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7B7B7B, 0xFFFDFDFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFDFDFD, 0xFF7A7A7A, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF3D3D3D, 0xFFB2B2B2, 0xFFE6E6E6, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE6E6E6, 0xFFB2B2B2, 0xFF3D3D3D, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000}; +static_assert(sizeof(ChargingBattery) == sizeof(u32) * ChargingBatteryW * ChargingBatteryH, "Incorrect ChargingBattery definition!"); diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_battery_icon_charging_red.inc b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_battery_icon_charging_red.inc new file mode 100644 index 00000000..6c2c7cad --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_battery_icon_charging_red.inc @@ -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 <http://www.gnu.org/licenses/>. + */ + +constexpr size_t ChargingRedBatteryX = 26; +constexpr size_t ChargingRedBatteryY = 28; +constexpr size_t ChargingRedBatteryW = 76; +constexpr size_t ChargingRedBatteryH = 28; + +constexpr size_t ChargingRedBatteryMeterX = 7; +constexpr size_t ChargingRedBatteryMeterY = 8; +constexpr size_t ChargingRedBatteryMeterW = 31; +constexpr size_t ChargingRedBatteryMeterH = 12; +static_assert(ChargingRedBatteryMeterX + ChargingRedBatteryMeterW < ChargingRedBatteryW && ChargingRedBatteryMeterY + ChargingRedBatteryMeterH < ChargingRedBatteryH, "Incorrect ChargingRedBatteryMeter definition!"); + +constexpr u32 ChargingRedBattery[] = {0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF3D3D3D, 0xFFB2B2B2, 0xFFE6E6E6, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE6E6E6, 0xFFB2B2B2, 0xFF3D3D3D, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7A7A7A, 0xFFFEFEFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFDFDFD, 0xFF7A7A7A, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF3D3D3D, 0xFFFDFDFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFDFDFD, 0xFF3D3D3D, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFB2B2B2, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFB2B2B2, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF494949, 0xFFE2E2E2, 0xFFE3E3E3, 0xFF494949, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE6E6E6, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF4A4A4A, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF4B4B4B, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE6E6E6, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF0E0E0E, 0xFFEDEDED, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE2E2E2, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7C7C7C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE4E4E4, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF0E0E0E, 0xFFEEEEEE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF7C7C7C, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFA62333, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFA62333, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF494949, 0xFFE3E3E3, 0xFFE3E3E3, 0xFF494949, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7C7C7C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEEEEEE, 0xFF0E0E0E, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE3E3E3, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE3E3E3, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF0E0E0E, 0xFFEEEEEE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF7C7C7C, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7C7C7C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEEEEEE, 0xFF0E0E0E, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF0E0E0E, 0xFFEEEEEE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF7C7C7C, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7C7C7C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDEDEDE, 0xFF464646, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDEDEDE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDEDEDE, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF464646, 0xFFDEDEDE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF7C7C7C, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7C7C7C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEEEEEE, 0xFF0E0E0E, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF0E0E0E, 0xFFEEEEEE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF7C7C7C, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE3E3E3, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE3E3E3, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7C7C7C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEEEEEE, 0xFF0E0E0E, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFA62333, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFA52233, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF494949, 0xFFE3E3E3, 0xFFE3E3E3, 0xFF494949, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF0E0E0E, 0xFFEEEEEE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF7C7C7C, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7C7C7C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEEEEEE, 0xFF0E0E0E, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE4E4E4, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF7C7C7C, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE6E6E6, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF4B4B4B, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF4B4B4B, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE6E6E6, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE2E2E2, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEDEDED, 0xFF0E0E0E, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFB2B2B2, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFB2B2B2, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF494949, 0xFFE3E3E3, 0xFFE2E2E2, 0xFF494949, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF3D3D3D, 0xFFFDFDFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFDFDFD, 0xFF3D3D3D, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7B7B7B, 0xFFFDFDFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFDFDFD, 0xFF7A7A7A, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF3D3D3D, 0xFFB2B2B2, 0xFFE6E6E6, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE6E6E6, 0xFFB2B2B2, 0xFF3D3D3D, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000}; +static_assert(sizeof(ChargingRedBattery) == sizeof(u32) * ChargingRedBatteryW * ChargingRedBatteryH, "Incorrect ChargingRedBattery definition!"); diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_battery_icon_low.inc b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_battery_icon_low.inc new file mode 100644 index 00000000..840cfe6c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_battery_icon_low.inc @@ -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 <http://www.gnu.org/licenses/>. + */ + +constexpr size_t LowBatteryX = 26; +constexpr size_t LowBatteryY = 28; +constexpr size_t LowBatteryW = 52; +constexpr size_t LowBatteryH = 28; + +constexpr u32 LowBattery[] = {0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF3D3D3D, 0xFFB2B2B2, 0xFFE6E6E6, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE6E6E6, 0xFFB2B2B2, 0xFF3D3D3D, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7A7A7A, 0xFFFEFEFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFDFDFD, 0xFF7A7A7A, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF3D3D3D, 0xFFFDFDFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFDFDFD, 0xFF3D3D3D, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFB2B2B2, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFB2B2B2, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE6E6E6, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF4A4A4A, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF4B4B4B, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE6E6E6, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFA62333, 0xFFE93047, 0xFFE93047, 0xFFA62333, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF494949, 0xFFE3E3E3, 0xFFE3E3E3, 0xFF494949, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE3E3E3, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE3E3E3, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFFE93047, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE3E3E3, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE3E3E3, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFA62333, 0xFFE93047, 0xFFE93047, 0xFFA52233, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF494949, 0xFFE3E3E3, 0xFFE3E3E3, 0xFF494949, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE6E6E6, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF4B4B4B, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF4B4B4B, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE6E6E6, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFB2B2B2, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFB2B2B2, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF3D3D3D, 0xFFFDFDFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFDFDFD, 0xFF3D3D3D, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7B7B7B, 0xFFFDFDFD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFDFDFD, 0xFF7A7A7A, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF3D3D3D, 0xFFB2B2B2, 0xFFE6E6E6, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE6E6E6, 0xFFB2B2B2, 0xFF3D3D3D, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000}; +static_assert(sizeof(LowBattery) == sizeof(u32) * LowBatteryW * LowBatteryH, "Incorrect LowBattery definition!"); diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_battery_icons.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_battery_icons.cpp new file mode 100644 index 00000000..02ca2593 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_battery_icons.cpp @@ -0,0 +1,95 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "boot_battery_icons.hpp" +#include "boot_display.hpp" + +namespace ams::boot { + + namespace { + + /* Pull in icon definitions. */ + #include "boot_battery_icon_low.inc" + #include "boot_battery_icon_charging.inc" + #include "boot_battery_icon_charging_red.inc" + + /* Helpers. */ + void FillBatteryMeter(u32 *icon, const size_t icon_w, const size_t icon_h, const size_t meter_x, const size_t meter_y, const size_t meter_w, const size_t meter_h, const size_t fill_w) { + const size_t fill_x = meter_x + meter_w - fill_w; + + if (fill_x + fill_w > icon_w || meter_y + meter_h > icon_h || fill_x == 0) { + return; + } + + u32 *cur_row = icon + meter_y * icon_w + fill_x; + for (size_t y = 0; y < meter_h; y++) { + /* Make last column of meter identical to first column of meter. */ + cur_row[-1] = icon[(meter_y + y) * icon_w + meter_x]; + + /* Black out further pixels. */ + for (size_t x = 0; x < fill_w; x++) { + cur_row[x] = 0xFF000000; + } + cur_row += icon_w; + } + } + + } + + void ShowLowBatteryIcon() { + InitializeDisplay(); + { + /* Low battery icon is shown for 5 seconds. */ + ShowDisplay(LowBatteryX, LowBatteryY, LowBatteryW, LowBatteryH, LowBattery); + os::SleepThread(TimeSpan::FromSeconds(5)); + } + FinalizeDisplay(); + } + + void StartShowChargingIcon(int battery_percentage, bool wait) { + const bool is_red = battery_percentage <= 15; + + const size_t IconX = is_red ? ChargingRedBatteryX : ChargingBatteryX; + const size_t IconY = is_red ? ChargingRedBatteryY : ChargingBatteryY; + const size_t IconW = is_red ? ChargingRedBatteryW : ChargingBatteryW; + const size_t IconH = is_red ? ChargingRedBatteryH : ChargingBatteryH; + const size_t IconMeterX = is_red ? ChargingRedBatteryMeterX : ChargingBatteryMeterX; + const size_t IconMeterY = is_red ? ChargingRedBatteryMeterY : ChargingBatteryMeterY; + const size_t IconMeterW = is_red ? ChargingRedBatteryMeterW : ChargingBatteryMeterW; + const size_t IconMeterH = is_red ? ChargingRedBatteryMeterH : ChargingBatteryMeterH; + const size_t MeterFillW = static_cast<size_t>(IconMeterW * (1.0 - (0.0404 + 0.0096 * static_cast<double>(battery_percentage))) + 0.5); + + /* Create stack buffer, copy icon into it, draw fill meter, draw. */ + { + u32 Icon[IconW * IconH]; + std::memcpy(Icon, is_red ? ChargingRedBattery : ChargingBattery, sizeof(Icon)); + FillBatteryMeter(Icon, IconW, IconH, IconMeterX, IconMeterY, IconMeterW, IconMeterH, MeterFillW); + + InitializeDisplay(); + ShowDisplay(IconX, IconY, IconW, IconH, Icon); + } + + /* Wait for 2 seconds if we're supposed to. */ + if (wait) { + os::SleepThread(TimeSpan::FromSeconds(2)); + } + } + + void EndShowChargingIcon() { + FinalizeDisplay(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_battery_icons.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_battery_icons.hpp new file mode 100644 index 00000000..6f1ff22d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_battery_icons.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "boot_display.hpp" + +namespace ams::boot { + + /* Battery Display utilities. */ + void ShowLowBatteryIcon(); + void StartShowChargingIcon(int battery_percentage, bool wait); + void EndShowChargingIcon(); + + inline void StartShowChargingIcon(int battery_percentage) { + return StartShowChargingIcon(battery_percentage, true); + } + + inline void StartShowLowBatteryChargingIcon() { + return StartShowChargingIcon(1, false); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_boot_reason.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_boot_reason.cpp new file mode 100644 index 00000000..bb623672 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_boot_reason.cpp @@ -0,0 +1,99 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "boot_boot_reason.hpp" +#include "boot_pmic_driver.hpp" +#include "boot_rtc_driver.hpp" + +namespace ams::boot { + + namespace { + + /* Globals. */ + constinit spl::BootReason g_boot_reason = spl::BootReason_Unknown; + constinit bool g_detected_boot_reason = false; + + /* Helpers. */ + spl::BootReason DetectBootReason(u32 power_intr, u8 rtc_intr, u8 nv_erc, bool ac_ok) { + if (power_intr & 0x08) { + return spl::BootReason_OnKey; + } + if (rtc_intr & 0x02) { + return spl::BootReason_RtcAlarm1; + } + if (power_intr & 0x80) { + return spl::BootReason_AcOk; + } + if (rtc_intr & 0x04) { + if (nv_erc != 0x80 && !spl::IsRecoveryBoot()) { + return spl::BootReason_RtcAlarm2; + } + } + if ((nv_erc & 0x40) && ac_ok) { + return spl::BootReason_AcOk; + } + return spl::BootReason_Unknown; + } + + } + + void DetectBootReason() { + u8 power_intr; + u8 rtc_intr; + u8 rtc_intr_m; + u8 nv_erc; + bool ac_ok; + + /* Get values from PMIC. */ + { + PmicDriver pmic_driver; + R_ABORT_UNLESS(pmic_driver.GetOnOffIrq(std::addressof(power_intr))); + R_ABORT_UNLESS(pmic_driver.GetNvErc(std::addressof(nv_erc))); + R_ABORT_UNLESS(pmic_driver.GetAcOk(std::addressof(ac_ok))); + } + + /* Get values from RTC. */ + { + RtcDriver rtc_driver; + R_ABORT_UNLESS(rtc_driver.GetRtcIntr(std::addressof(rtc_intr))); + R_ABORT_UNLESS(rtc_driver.GetRtcIntrM(std::addressof(rtc_intr_m))); + } + + /* Set global derived boot reason. */ + g_boot_reason = DetectBootReason(power_intr, rtc_intr & ~rtc_intr_m, nv_erc, ac_ok); + + /* Set boot reason for SPL. */ + if (hos::GetVersion() >= hos::Version_3_0_0) { + /* Create the boot reason value. */ + spl::BootReasonValue boot_reason_value = {}; + boot_reason_value.power_intr = power_intr; + boot_reason_value.rtc_intr = rtc_intr & ~rtc_intr_m; + boot_reason_value.nv_erc = nv_erc; + boot_reason_value.boot_reason = g_boot_reason; + + /* Set the boot reason value. */ + R_ABORT_UNLESS(spl::SetBootReason(boot_reason_value)); + } + + g_detected_boot_reason = true; + } + + spl::BootReason GetBootReason() { + AMS_ABORT_UNLESS(g_detected_boot_reason); + return g_boot_reason; + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_boot_reason.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_boot_reason.hpp new file mode 100644 index 00000000..d66d55e8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_boot_reason.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::boot { + + /* Boot Reason utilities. */ + void DetectBootReason(); + spl::BootReason GetBootReason(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_change_voltage.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_change_voltage.cpp new file mode 100644 index 00000000..57b3aa20 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_change_voltage.cpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "boot_change_voltage.hpp" + +namespace ams::boot { + + namespace { + + /* Convenience definitions. */ + constexpr u32 Sdmmc3VoltageBit = (1 << 13); /* SDMMC3 */ + constexpr u32 AudioVoltageBit = (1 << 18); /* AUDIO_HV */ + constexpr u32 GpioVoltageBit = (1 << 21); /* GPIO */ + constexpr u32 SpiVoltageBit = (1 << 23); /* SPI_HV */ + + constexpr u32 VoltageChangeMask = SpiVoltageBit | GpioVoltageBit | AudioVoltageBit | Sdmmc3VoltageBit; + + constexpr u32 PmcPwrDet = 0x7000E448; + constexpr u32 PmcPwrDetVal = 0x7000E4E4; + + } + + void ChangeGpioVoltageTo1_8v() { + /* Write mask to APBDEV_PMC_PWR_DET, then clear APBDEV_PMC_PWR_DET_VAL. */ + dd::ReadModifyWriteIoRegister(PmcPwrDet, VoltageChangeMask, VoltageChangeMask); + dd::ReadModifyWriteIoRegister(PmcPwrDetVal, 0, VoltageChangeMask); + + /* Sleep for 100 us. */ + os::SleepThread(TimeSpan::FromMicroSeconds(100)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_change_voltage.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_change_voltage.hpp new file mode 100644 index 00000000..7bbb6110 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_change_voltage.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::boot { + + void ChangeGpioVoltageTo1_8v();; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_charger_driver.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_charger_driver.hpp new file mode 100644 index 00000000..b0d41b02 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_charger_driver.hpp @@ -0,0 +1,130 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::boot { + + enum ChargerStatus { + ChargerStatus_Unknown = 0, + ChargerStatus_Charging = 1, + ChargerStatus_NotCharging = 2, + ChargerStatus_ChargeTerminationDone = 3, + }; + + class ChargerDriver { + private: + powctl::Session m_charger_session; + public: + ChargerDriver() : m_charger_session() { + R_ABORT_UNLESS(powctl::OpenSession(std::addressof(m_charger_session), powctl::DeviceCode_Bq24193, ddsf::AccessMode_ReadWrite)); + } + + ~ChargerDriver() { + powctl::CloseSession(m_charger_session); + } + + Result Initialize(bool set_input_current_limit) { + /* Configure PINMUX_AUX_CAM_FLASH_EN as tristate + passthrough. */ + { + const uintptr_t apb_regs = dd::QueryIoMapping(0x70000000ul, os::MemoryPageSize); + reg::ClearBits(apb_regs + PINMUX_AUX_CAM_FLASH_EN, reg::EncodeMask(PINMUX_REG_BITS_MASK(AUX_TRISTATE))); + } + + /* Set input current limit to 500 ma. */ + if (set_input_current_limit) { + R_TRY(powctl::SetChargerInputCurrentLimit(m_charger_session, 500)); + } + + /* Set boost mode current limit to 500 ma. */ + R_TRY(powctl::SetChargerBoostModeCurrentLimit(m_charger_session, 500)); + + /* Disable hi-z mode. */ + R_TRY(powctl::SetChargerHiZEnabled(m_charger_session, false)); + + /* Set configuration to charge battery. */ + R_TRY(powctl::SetChargerChargerConfiguration(m_charger_session, powctl::ChargerConfiguration_ChargeBattery)); + + R_SUCCEED(); + } + + Result GetChargeCurrentState(powctl::ChargeCurrentState *out) { + R_RETURN(powctl::GetChargerChargeCurrentState(out, m_charger_session)); + } + + Result SetChargeCurrentState(powctl::ChargeCurrentState state) { + R_RETURN(powctl::SetChargerChargeCurrentState(m_charger_session, state)); + } + + Result GetInputCurrentLimit(int *out) { + R_RETURN(powctl::GetChargerInputCurrentLimit(out, m_charger_session)); + } + + Result SetChargerConfiguration(powctl::ChargerConfiguration cfg) { + R_RETURN(powctl::SetChargerChargerConfiguration(m_charger_session, cfg)); + } + + Result GetFastChargeCurrentLimit(int *out) { + R_RETURN(powctl::GetChargerFastChargeCurrentLimit(out, m_charger_session)); + } + + Result SetFastChargeCurrentLimit(int limit) { + R_RETURN(powctl::SetChargerFastChargeCurrentLimit(m_charger_session, limit)); + } + + Result GetChargeVoltageLimit(int *out) { + R_RETURN(powctl::GetChargerChargeVoltageLimit(out, m_charger_session)); + } + + Result SetChargeVoltageLimit(int limit) { + R_RETURN(powctl::SetChargerChargeVoltageLimit(m_charger_session, limit)); + } + + Result GetChargerStatus(boot::ChargerStatus *out) { + /* Default to unknown. */ + *out = ChargerStatus_Unknown; + + /* Get the powctl status. */ + powctl::ChargerStatus powctl_status; + R_TRY(powctl::GetChargerChargerStatus(std::addressof(powctl_status), m_charger_session)); + + switch (powctl_status) { + case powctl::ChargerStatus_Charging: *out = boot::ChargerStatus_Charging; break; + case powctl::ChargerStatus_NotCharging: *out = boot::ChargerStatus_NotCharging; break; + case powctl::ChargerStatus_ChargeTerminationDone: *out = boot::ChargerStatus_ChargeTerminationDone; break; + } + + R_SUCCEED(); + } + + Result GetBatteryCompensation(int *out) { + R_RETURN(powctl::GetChargerBatteryCompensation(out, m_charger_session)); + } + + Result SetBatteryCompensation(int v) { + R_RETURN(powctl::SetChargerBatteryCompensation(m_charger_session, v)); + } + + Result GetVoltageClamp(int *out) { + R_RETURN(powctl::GetChargerVoltageClamp(out, m_charger_session)); + } + + Result SetVoltageClamp(int v) { + R_RETURN(powctl::SetChargerVoltageClamp(m_charger_session, v)); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_check_battery.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_check_battery.cpp new file mode 100644 index 00000000..33f2396f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_check_battery.cpp @@ -0,0 +1,529 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "boot_check_battery.hpp" +#include "boot_battery_icons.hpp" +#include "boot_boot_reason.hpp" +#include "boot_pmic_driver.hpp" +#include "boot_battery_driver.hpp" +#include "boot_charger_driver.hpp" +#include "boot_power_utils.hpp" + +namespace ams::boot { + + namespace { + + /* Value definitions. */ + constexpr inline double BatteryLevelThresholdForBoot = 3.0; + constexpr inline double BatteryLevelThresholdForFullCharge = 99.0; + + constexpr inline int BatteryVoltageThresholdConnected = 4000; + constexpr inline int BatteryVoltageThresholdDisconnected = 3650; + + /* Types. */ + enum class CheckBatteryResult { + Success, + Shutdown, + Reboot, + }; + + class BatteryChecker { + private: + boot::ChargerDriver &m_charger_driver; + boot::BatteryDriver &m_battery_driver; + const powctl::driver::impl::ChargeParameters &m_charge_parameters; + powctl::driver::impl::ChargeArbiter m_charge_arbiter; + powctl::ChargeCurrentState m_charge_current_state; + int m_fast_charge_current_limit; + int m_charge_voltage_limit; + int m_battery_compensation; + int m_voltage_clamp; + TimeSpan m_charging_done_interval; + bool m_has_start_time; + TimeSpan m_start_time; + private: + bool IsChargeDone(); + void UpdateChargeDoneCurrent(); + void ApplyArbiterRule(); + void PrintBatteryStatus(float raw_charge, int voltage, int voltage_threshold); + CheckBatteryResult LoopCheckBattery(bool reboot_on_power_button_press, bool return_on_enough_battery, bool shutdown_on_full_battery, bool show_display, bool show_charging_display); + + void UpdateStartTime() { + /* Update start time. */ + m_start_time = os::ConvertToTimeSpan(os::GetSystemTick()); + m_has_start_time = true; + } + public: + BatteryChecker(boot::ChargerDriver &cd, boot::BatteryDriver &bd, const powctl::driver::impl::ChargeParameters &cp, int cvl) : m_charger_driver(cd), m_battery_driver(bd), m_charge_parameters(cp), m_charge_arbiter(cp.rules, cp.num_rules, cvl), m_charging_done_interval(TimeSpan::FromSeconds(2)), m_has_start_time(false) { + /* Get parameters from charger. */ + if (R_FAILED(m_charger_driver.GetChargeCurrentState(std::addressof(m_charge_current_state)))) { + boot::ShutdownSystem(); + } + if (R_FAILED(m_charger_driver.GetFastChargeCurrentLimit(std::addressof(m_fast_charge_current_limit)))) { + boot::ShutdownSystem(); + } + if (R_FAILED(m_charger_driver.GetChargeVoltageLimit(std::addressof(m_charge_voltage_limit)))) { + boot::ShutdownSystem(); + } + if (R_FAILED(m_charger_driver.GetBatteryCompensation(std::addressof(m_battery_compensation)))) { + boot::ShutdownSystem(); + } + if (R_FAILED(m_charger_driver.GetVoltageClamp(std::addressof(m_voltage_clamp)))) { + boot::ShutdownSystem(); + } + + /* Update start time. */ + this->UpdateStartTime(); + } + + CheckBatteryResult LoopCheckBattery(spl::BootReason boot_reason) { + if (boot_reason == spl::BootReason_RtcAlarm2) { + /* RTC Alarm 2 boot (QuasiOff) */ + return this->LoopCheckBattery(true, false, true, false, false); + } else if (boot_reason == spl::BootReason_AcOk) { + /* ACOK boot */ + return this->LoopCheckBattery(true, true, false, true, true); + } else { + /* Normal boot */ + return this->LoopCheckBattery(false, true, false, true, false); + } + } + + void UpdateCharger(); + }; + + void BatteryChecker::PrintBatteryStatus(float raw_charge, int voltage, int voltage_threshold) { + /* TODO: Print charge/voltage/threshold. */ + AMS_UNUSED(raw_charge, voltage, voltage_threshold); + + /* Get various battery metrics. */ + int avg_current, current; + float temp, voltage_fuel_gauge_percentage; + if (R_FAILED(m_battery_driver.GetAverageCurrent(std::addressof(avg_current)))) { + return; + } + if (R_FAILED(m_battery_driver.GetCurrent(std::addressof(current)))) { + return; + } + if (R_FAILED(m_battery_driver.GetTemperature(std::addressof(temp)))) { + return; + } + if (R_FAILED(m_battery_driver.GetVoltageFuelGaugePercentage(std::addressof(voltage_fuel_gauge_percentage)))) { + return; + } + + /* TODO: Print the things we just got. */ + AMS_UNUSED(avg_current, current, temp, voltage_fuel_gauge_percentage); + } + + bool BatteryChecker::IsChargeDone() { + /* Get the charger status. */ + boot::ChargerStatus charger_status; + if (R_FAILED(m_charger_driver.GetChargerStatus(std::addressof(charger_status)))) { + boot::ShutdownSystem(); + } + + /* If charge status isn't done, we're not done. */ + if (charger_status != boot::ChargerStatus_ChargeTerminationDone) { + return false; + } + + /* Return whether a done current of zero is acceptable. */ + return m_charge_arbiter.IsBatteryDoneCurrentAcceptable(0); + } + + void BatteryChecker::UpdateChargeDoneCurrent() { + int done_current = 0; + if (m_has_start_time && (os::ConvertToTimeSpan(os::GetSystemTick()) - m_start_time) >= m_charging_done_interval) { + /* Get the current. */ + if (R_FAILED(m_battery_driver.GetCurrent(std::addressof(done_current)))) { + boot::ShutdownSystem(); + } + } else { + /* Get the charger status. */ + boot::ChargerStatus charger_status; + if (R_FAILED(m_charger_driver.GetChargerStatus(std::addressof(charger_status)))) { + boot::ShutdownSystem(); + } + + /* If the charger status isn't done, don't update. */ + if (charger_status != boot::ChargerStatus_ChargeTerminationDone) { + return; + } + } + + /* Update done current. */ + m_charge_arbiter.SetBatteryDoneCurrent(done_current); + } + + void BatteryChecker::UpdateCharger() { + /* Get the battery temperature. */ + float temp; + if (R_FAILED(m_battery_driver.GetTemperature(std::addressof(temp)))) { + boot::ShutdownSystem(); + } + + /* Update the temperature level. */ + powctl::BatteryTemperatureLevel temp_level; + if (temp < static_cast<float>(m_charge_parameters.temp_min)) { + temp_level = powctl::BatteryTemperatureLevel::TooLow; + } else if (temp < static_cast<float>(m_charge_parameters.temp_low)) { + temp_level = powctl::BatteryTemperatureLevel::Low; + } else if (temp < static_cast<float>(m_charge_parameters.temp_high)) { + temp_level = powctl::BatteryTemperatureLevel::Medium; + } else if (temp < static_cast<float>(m_charge_parameters.temp_max)) { + temp_level = powctl::BatteryTemperatureLevel::High; + } else { + temp_level = powctl::BatteryTemperatureLevel::TooHigh; + } + m_charge_arbiter.SetBatteryTemperatureLevel(temp_level); + + /* Update average voltage. */ + int avg_v_cell; + if (R_FAILED(m_battery_driver.GetAverageVCell(std::addressof(avg_v_cell)))) { + boot::ShutdownSystem(); + } + m_charge_arbiter.SetBatteryAverageVCell(avg_v_cell); + + /* Update voltage fuel gauge percentage. */ + float vfgp; + if (R_FAILED(m_battery_driver.GetVoltageFuelGaugePercentage(std::addressof(vfgp)))) { + boot::ShutdownSystem(); + } + m_charge_arbiter.SetBatteryVoltageFuelGaugePercentage(vfgp); + + /* Update charge done current. */ + this->UpdateChargeDoneCurrent(); + + /* Update arbiter power state. */ + m_charge_arbiter.SetPowerState(powctl::PowerState::ShutdownChargeMain); + + /* Apply the newly selected rule. */ + this->ApplyArbiterRule(); + } + + void BatteryChecker::ApplyArbiterRule() { + /* Get the selected rule. */ + const auto *rule = m_charge_arbiter.GetSelectedRule(); + AMS_ASSERT(rule != nullptr); + + /* Check if we need to perform charger initialization. */ + const bool reinit_charger = rule->reinitialize_charger; + const auto cur_charge_current_state = m_charge_current_state; + + /* Set the charger to not charging while we make changes. */ + if (!reinit_charger || cur_charge_current_state != powctl::ChargeCurrentState_NotCharging) { + if (R_FAILED(m_charger_driver.SetChargeCurrentState(powctl::ChargeCurrentState_NotCharging))) { + boot::ShutdownSystem(); + } + m_charge_current_state = powctl::ChargeCurrentState_NotCharging; + + /* Update start time. */ + this->UpdateStartTime(); + } + + /* Process fast charge current limit when rule is smaller. */ + const auto rule_fast_charge_current_limit = rule->fast_charge_current_limit; + const auto cur_fast_charge_current_limit = m_fast_charge_current_limit; + if (rule_fast_charge_current_limit < cur_fast_charge_current_limit) { + if (R_FAILED(m_charger_driver.SetFastChargeCurrentLimit(rule_fast_charge_current_limit))) { + boot::ShutdownSystem(); + } + m_fast_charge_current_limit = rule_fast_charge_current_limit; + + /* Update start time. */ + this->UpdateStartTime(); + } + + /* Process charge voltage limit when rule is smaller. */ + const auto rule_charge_voltage_limit = std::min(rule->charge_voltage_limit, m_charge_arbiter.GetChargeVoltageLimit()); + const auto cur_charge_voltage_limit = m_charge_voltage_limit; + if (rule_charge_voltage_limit < cur_charge_voltage_limit) { + if (R_FAILED(m_charger_driver.SetChargeVoltageLimit(rule_charge_voltage_limit))) { + boot::ShutdownSystem(); + } + m_charge_voltage_limit = rule_charge_voltage_limit; + + /* Update start time. */ + this->UpdateStartTime(); + } + + /* Process battery compensation when rule is smaller. */ + const auto rule_battery_compensation = rule->battery_compensation; + const auto cur_battery_compensation = m_battery_compensation; + if (rule_battery_compensation < cur_battery_compensation) { + if (R_FAILED(m_charger_driver.SetBatteryCompensation(rule_battery_compensation))) { + boot::ShutdownSystem(); + } + m_battery_compensation = rule_battery_compensation; + + /* Update start time. */ + this->UpdateStartTime(); + } + + /* Process voltage clamp when rule is smaller. */ + const auto rule_voltage_clamp = rule->voltage_clamp; + const auto cur_voltage_clamp = m_voltage_clamp; + if (rule_voltage_clamp < cur_voltage_clamp) { + if (R_FAILED(m_charger_driver.SetVoltageClamp(rule_voltage_clamp))) { + boot::ShutdownSystem(); + } + m_voltage_clamp = rule_voltage_clamp; + + /* Update start time. */ + this->UpdateStartTime(); + } + + /* Process voltage clamp when rule is larger. */ + if (rule_voltage_clamp > cur_voltage_clamp) { + if (R_FAILED(m_charger_driver.SetVoltageClamp(rule_voltage_clamp))) { + boot::ShutdownSystem(); + } + m_voltage_clamp = rule_voltage_clamp; + + /* Update start time. */ + this->UpdateStartTime(); + } + + /* Process battery compensation when rule is larger. */ + if (rule_battery_compensation > cur_battery_compensation) { + if (R_FAILED(m_charger_driver.SetBatteryCompensation(rule_battery_compensation))) { + boot::ShutdownSystem(); + } + m_battery_compensation = rule_battery_compensation; + + /* Update start time. */ + this->UpdateStartTime(); + } + + /* Process fast charge current limit when rule is larger. */ + if (rule_fast_charge_current_limit > cur_fast_charge_current_limit) { + if (R_FAILED(m_charger_driver.SetFastChargeCurrentLimit(rule_fast_charge_current_limit))) { + boot::ShutdownSystem(); + } + m_fast_charge_current_limit = rule_fast_charge_current_limit; + + /* Update start time. */ + this->UpdateStartTime(); + } + + /* Process charge voltage limit when rule is larger. */ + if (rule_charge_voltage_limit > cur_charge_voltage_limit) { + if (R_FAILED(m_charger_driver.SetChargeVoltageLimit(rule_charge_voltage_limit))) { + boot::ShutdownSystem(); + } + m_charge_voltage_limit = rule_charge_voltage_limit; + + /* Update start time. */ + this->UpdateStartTime(); + } + + /* If we're not charging and we expect to reinitialize the charger, do so. */ + if (cur_charge_current_state != powctl::ChargeCurrentState_Charging && reinit_charger) { + if (R_FAILED(m_charger_driver.SetChargeCurrentState(powctl::ChargeCurrentState_Charging))) { + boot::ShutdownSystem(); + } + m_charge_current_state = powctl::ChargeCurrentState_Charging; + + /* Update start time. */ + this->UpdateStartTime(); + } + } + + CheckBatteryResult BatteryChecker::LoopCheckBattery(bool reboot_on_power_button_press, bool return_on_enough_battery, bool shutdown_on_full_battery, bool show_display, bool show_charging_display) { + /* Ensure that if we show a charging icon, we stop showing it when we're done. */ + bool is_showing_charging_icon = false; + ON_SCOPE_EXIT { + if (is_showing_charging_icon) { + boot::EndShowChargingIcon(); + is_showing_charging_icon = false; + } + }; + + /* Show the charging display, if we should. */ + if (show_charging_display) { + /* Get the raw battery charge. */ + float raw_battery_charge; + if (R_FAILED(m_battery_driver.GetChargePercentage(std::addressof(raw_battery_charge)))) { + return CheckBatteryResult::Shutdown; + } + + /* Display the battery with the appropriate percentage. */ + const auto battery_charge = powctl::impl::ConvertBatteryChargePercentage(raw_battery_charge); + boot::StartShowChargingIcon(battery_charge); + is_showing_charging_icon = true; + } + + /* Loop, checking the battery status. */ + TimeSpan last_progress_time = TimeSpan(0); + while (true) { + /* Get the raw battery charge. */ + float raw_battery_charge; + if (R_FAILED(m_battery_driver.GetChargePercentage(std::addressof(raw_battery_charge)))) { + return CheckBatteryResult::Shutdown; + } + + /* Get the average vcell. */ + int battery_voltage; + if (R_FAILED(m_battery_driver.GetAverageVCell(std::addressof(battery_voltage)))) { + return CheckBatteryResult::Shutdown; + } + + /* Get whether we're connected to charger. */ + bool ac_ok; + if (R_FAILED((boot::PmicDriver().GetAcOk(std::addressof(ac_ok))))) { + return CheckBatteryResult::Shutdown; + } + + /* Decide on a battery voltage threshold. */ + const auto battery_voltage_threshold = ac_ok ? BatteryVoltageThresholdConnected : BatteryVoltageThresholdDisconnected; + + /* Check if we should return. */ + if (return_on_enough_battery) { + if (raw_battery_charge >= BatteryLevelThresholdForBoot || battery_voltage >= battery_voltage_threshold) { + this->PrintBatteryStatus(raw_battery_charge, battery_voltage, battery_voltage_threshold); + return CheckBatteryResult::Success; + } + } + + /* Otherwise, check if we should shut down. */ + if (shutdown_on_full_battery) { + if (raw_battery_charge >= BatteryLevelThresholdForFullCharge || this->IsChargeDone()) { + return CheckBatteryResult::Shutdown; + } + } + + /* Perform periodic printing. */ + constexpr TimeSpan PrintProgressInterval = TimeSpan::FromSeconds(10); + const auto cur_time = os::ConvertToTimeSpan(os::GetSystemTick()); + if ((cur_time - last_progress_time) >= PrintProgressInterval) { + last_progress_time = cur_time; + this->PrintBatteryStatus(raw_battery_charge, battery_voltage, battery_voltage_threshold); + } + + /* If we've gotten to this point, we have insufficient battery to boot. If we aren't charging, show low battery and shutdown. */ + if (!ac_ok) { + this->PrintBatteryStatus(raw_battery_charge, battery_voltage, battery_voltage_threshold); + if (show_display && !is_showing_charging_icon) { + boot::ShowLowBatteryIcon(); + } + return CheckBatteryResult::Shutdown; + } + + /* Check if we should reboot due to a power button press. */ + if (reboot_on_power_button_press) { + /* Get the power button value. */ + bool power_button_pressed; + if (R_FAILED((boot::PmicDriver().GetPowerButtonPressed(std::addressof(power_button_pressed))))) { + return CheckBatteryResult::Shutdown; + } + + /* Handle the press (or not). */ + if (power_button_pressed) { + return CheckBatteryResult::Reboot; + } + } + + /* If we got to this point, we should show the low-battery charging screen. */ + if (show_display && !is_showing_charging_icon) { + boot::StartShowLowBatteryChargingIcon(); + is_showing_charging_icon = true; + } + + /* Wait a bit before checking again. */ + constexpr auto BatteryChargeCheckInterval = TimeSpan::FromMilliSeconds(20); + os::SleepThread(BatteryChargeCheckInterval); + + /* Update the charger. */ + this->UpdateCharger(); + } + } + + } + + void CheckBatteryCharge() { + /* Open a sessions for the charger/battery. */ + boot::ChargerDriver charger_driver; + boot::BatteryDriver battery_driver; + + /* Check if the battery is removed. */ + { + bool removed = false; + if (R_FAILED(battery_driver.IsBatteryRemoved(std::addressof(removed))) || removed) { + boot::ShutdownSystem(); + } + } + + /* Get the boot reason. */ + const auto boot_reason = boot::GetBootReason(); + + /* Initialize the charger driver. */ + if (R_FAILED(charger_driver.Initialize(boot_reason != spl::BootReason_RtcAlarm2))) + + /* Check that the charger input limit is greater than 150 milli-amps. */ + { + int input_current_limit_ma; + if (R_FAILED(charger_driver.GetInputCurrentLimit(std::addressof(input_current_limit_ma)))) { + boot::ShutdownSystem(); + } + + if (input_current_limit_ma <= 150) { + charger_driver.SetChargerConfiguration(powctl::ChargerConfiguration_ChargeDisable); + boot::ShutdownSystem(); + } + } + + /* Get the charge parameters. */ + const auto &charge_parameters = powctl::driver::impl::GetChargeParameters(); + + /* Get the charge voltage limit. */ + int charge_voltage_limit_mv; + if (boot_reason != spl::BootReason_RtcAlarm2 || charge_parameters.unknown_x_table == nullptr || charge_parameters.x_table_size == 0) { + charge_voltage_limit_mv = charge_parameters.default_charge_voltage_limit; + } else { + if (R_FAILED(charger_driver.GetChargeVoltageLimit(std::addressof(charge_voltage_limit_mv)))) { + boot::ShutdownSystem(); + } + } + + /* Create and update a battery checker. */ + BatteryChecker battery_checker(charger_driver, battery_driver, charge_parameters, charge_voltage_limit_mv); + battery_checker.UpdateCharger(); + + /* Set the display brightness to 25%. */ + boot::SetDisplayBrightness(25); + + /* Check the battery. */ + const CheckBatteryResult check_result = battery_checker.LoopCheckBattery(boot_reason); + + /* Set the display brightness to 100%. */ + boot::SetDisplayBrightness(100); + + /* Handle the check result. */ + switch (check_result) { + case CheckBatteryResult::Success: + break; + case CheckBatteryResult::Shutdown: + boot::ShutdownSystem(); + break; + case CheckBatteryResult::Reboot: + boot::RebootSystem(); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_check_battery.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_check_battery.hpp new file mode 100644 index 00000000..442aa2cb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_check_battery.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::boot { + + void CheckBatteryCharge(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_check_clock.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_check_clock.cpp new file mode 100644 index 00000000..cc0098b8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_check_clock.cpp @@ -0,0 +1,56 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "boot_check_clock.hpp" +#include "boot_power_utils.hpp" + +namespace ams::boot { + + namespace { + + /* Convenience definitions. */ + constexpr u32 ExpectedPlluDivP = (1 << 16); + constexpr u32 ExpectedPlluDivN = (25 << 8); + constexpr u32 ExpectedPlluDivM = (2 << 0); + constexpr u32 ExpectedPlluVal = (ExpectedPlluDivP | ExpectedPlluDivN | ExpectedPlluDivM); + constexpr u32 ExpectedPlluMask = 0x1FFFFF; + + constexpr u32 ExpectedUtmipDivN = (25 << 16); + constexpr u32 ExpectedUtmipDivM = (1 << 8); + constexpr u32 ExpectedUtmipVal = (ExpectedUtmipDivN | ExpectedUtmipDivM); + constexpr u32 ExpectedUtmipMask = 0xFFFF00; + + /* Helpers. */ + bool IsUsbClockValid() { + uintptr_t car_regs = dd::QueryIoMapping(0x60006000ul, os::MemoryPageSize); + AMS_ASSERT(car_regs != 0); + + const u32 pllu = reg::Read(car_regs + 0xC0); + const u32 utmip = reg::Read(car_regs + 0x480); + return ((pllu & ExpectedPlluMask) == ExpectedPlluVal) && ((utmip & ExpectedUtmipMask) == ExpectedUtmipVal); + } + + } + + void CheckClock() { + if (!IsUsbClockValid()) { + /* Sleep for 1s, then reboot. */ + os::SleepThread(TimeSpan::FromSeconds(1)); + RebootSystem(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_check_clock.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_check_clock.hpp new file mode 100644 index 00000000..dd4bf23f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_check_clock.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::boot { + + void CheckClock(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_clock_initial_configuration.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_clock_initial_configuration.cpp new file mode 100644 index 00000000..0ac27205 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_clock_initial_configuration.cpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "boot_clock_initial_configuration.hpp" + +namespace ams::boot { + + namespace { + + constexpr inline dd::PhysicalAddress PmcBase = 0x7000E400; + + /* Convenience definitions. */ + constexpr u32 InitialClockOutMask1x = 0x00C4; + constexpr u32 InitialClockOutMask6x = 0xC4C4; + + } + + void SetInitialClockConfiguration() { + /* Write mask to APBDEV_PMC_PWR_DET, then clear APBDEV_PMC_PWR_DET_VAL. */ + const u32 mask = hos::GetVersion() >= hos::Version_6_0_0 ? InitialClockOutMask6x : InitialClockOutMask1x; + dd::ReadModifyWriteIoRegister(PmcBase + APBDEV_PMC_CLK_OUT_CNTRL, mask, mask); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_clock_initial_configuration.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_clock_initial_configuration.hpp new file mode 100644 index 00000000..8662d85a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_clock_initial_configuration.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::boot { + + void SetInitialClockConfiguration(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_display.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_display.cpp new file mode 100644 index 00000000..52470c97 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_display.cpp @@ -0,0 +1,653 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "boot_display.hpp" +#include "boot_i2c_utils.hpp" + +#include "boot_registers_di.hpp" + +namespace ams::boot { + + /* Display configuration included into anonymous namespace. */ + namespace { + + #include "boot_display_config.inc" + + } + + namespace { + + /* Helpful defines. */ + constexpr size_t DeviceAddressSpaceAlignSize = 4_MB; + + constexpr dd::DeviceVirtualAddress FrameBufferDeviceAddress = DisplayConfigFrameBufferAddress; + + constexpr size_t FrameBufferWidth = 768; + constexpr size_t FrameBufferHeight = 1280; + constexpr size_t FrameBufferSize = FrameBufferHeight * FrameBufferWidth * sizeof(u32); + + constexpr dd::PhysicalAddress PmcBase = 0x7000E400ul; + constexpr dd::PhysicalAddress Disp1Base = 0x54200000ul; + constexpr dd::PhysicalAddress DsiBase = 0x54300000ul; + constexpr dd::PhysicalAddress ClkRstBase = 0x60006000ul; + constexpr dd::PhysicalAddress GpioBase = 0x6000D000ul; + constexpr dd::PhysicalAddress ApbMiscBase = 0x70000000ul; + constexpr dd::PhysicalAddress MipiCalBase = 0x700E3000ul; + + constexpr size_t Disp1Size = 3 * os::MemoryPageSize; + constexpr size_t DsiSize = os::MemoryPageSize; + constexpr size_t ClkRstSize = os::MemoryPageSize; + constexpr size_t GpioSize = os::MemoryPageSize; + constexpr size_t ApbMiscSize = os::MemoryPageSize; + constexpr size_t MipiCalSize = os::MemoryPageSize; + + constexpr int DsiWaitForCommandMilliSecondsMax = 250; + constexpr int DsiWaitForCommandCompletionMilliSeconds = 5; + constexpr int DsiWaitForHostControlMilliSecondsMax = 150; + + 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. */ + constinit bool g_is_display_intialized = false; + constinit spl::SocType g_soc_type = spl::SocType_Erista; + + constinit u32 g_lcd_vendor = 0; + constinit int g_display_brightness = 100; + + constinit dd::DeviceAddressSpaceType g_device_address_space; + + constinit pwm::driver::ChannelSession g_lcd_backlight_session; + + constinit u32 *g_frame_buffer = nullptr; + constinit u8 g_frame_buffer_storage[DeviceAddressSpaceAlignSize + FrameBufferSize]; + + constinit uintptr_t g_disp1_regs = 0; + constinit uintptr_t g_dsi_regs = 0; + constinit uintptr_t g_clk_rst_regs = 0; + constinit uintptr_t g_gpio_regs = 0; + constinit uintptr_t g_apb_misc_regs = 0; + constinit uintptr_t g_mipi_cal_regs = 0; + + /* Helper functions. */ + void InitializeRegisterVirtualAddresses() { + g_disp1_regs = dd::QueryIoMapping(Disp1Base, Disp1Size); + g_dsi_regs = dd::QueryIoMapping(DsiBase, DsiSize); + g_clk_rst_regs = dd::QueryIoMapping(ClkRstBase, ClkRstSize); + g_gpio_regs = dd::QueryIoMapping(GpioBase, GpioSize); + g_apb_misc_regs = dd::QueryIoMapping(ApbMiscBase, ApbMiscSize); + g_mipi_cal_regs = dd::QueryIoMapping(MipiCalBase, MipiCalSize); + + AMS_ABORT_UNLESS(g_disp1_regs != 0); + AMS_ABORT_UNLESS(g_dsi_regs != 0); + AMS_ABORT_UNLESS(g_clk_rst_regs != 0); + AMS_ABORT_UNLESS(g_gpio_regs != 0); + AMS_ABORT_UNLESS(g_apb_misc_regs != 0); + AMS_ABORT_UNLESS(g_mipi_cal_regs != 0); + } + + 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 (g_soc_type) { + case spl::SocType_Erista: DoRegisterWrites(base_address, reg_writes_erista, num_writes_erista); break; + case spl::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: + os::SleepThread(TimeSpan::FromMilliSeconds(reg_writes[i].offset)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + #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, 0x00, FrameBufferSize); + dd::FlushDataCache(g_frame_buffer, FrameBufferSize); + } else { + const uintptr_t frame_buffer_aligned = util::AlignUp(reinterpret_cast<uintptr_t>(g_frame_buffer_storage), DeviceAddressSpaceAlignSize); + g_frame_buffer = reinterpret_cast<u32 *>(frame_buffer_aligned); + + std::memset(g_frame_buffer, 0x00, FrameBufferSize); + dd::FlushDataCache(g_frame_buffer, FrameBufferSize); + + /* Create Address Space. */ + R_ABORT_UNLESS(dd::CreateDeviceAddressSpace(std::addressof(g_device_address_space), 0, (UINT64_C(1) << 32))); + + /* Attach it to the DC. */ + R_ABORT_UNLESS(dd::AttachDeviceAddressSpace(std::addressof(g_device_address_space), svc::DeviceName_Dc)); + + /* Map the framebuffer for the DC as read-only. */ + R_ABORT_UNLESS(dd::MapDeviceAddressSpaceAligned(std::addressof(g_device_address_space), dd::GetCurrentProcessHandle(), frame_buffer_aligned, FrameBufferSize, FrameBufferDeviceAddress, dd::MemoryPermission_ReadOnly)); + } + } + + void FinalizeFrameBuffer() { + if (g_frame_buffer != nullptr) { + const uintptr_t frame_buffer_aligned = util::AlignUp(reinterpret_cast<uintptr_t>(g_frame_buffer_storage), DeviceAddressSpaceAlignSize); + + /* Unmap the framebuffer from the DC. */ + dd::UnmapDeviceAddressSpace(std::addressof(g_device_address_space), dd::GetCurrentProcessHandle(), frame_buffer_aligned, FrameBufferSize, FrameBufferDeviceAddress); + + /* Detach address space from the DC. */ + dd::DetachDeviceAddressSpace(std::addressof(g_device_address_space), svc::DeviceName_Dc); + + /* Destroy the address space. */ + dd::DestroyDeviceAddressSpace(std::addressof(g_device_address_space)); + + g_frame_buffer = nullptr; + } + } + + void WaitDsiTrigger() { + os::Tick timeout = os::GetSystemTick() + os::ConvertToTick(TimeSpan::FromMilliSeconds(DsiWaitForCommandMilliSecondsMax)); + + while (true) { + if (os::GetSystemTick() >= timeout) { + break; + } + if (reg::Read(g_dsi_regs + sizeof(u32) * DSI_TRIGGER) == 0) { + break; + } + } + + os::SleepThread(TimeSpan::FromMilliSeconds(DsiWaitForCommandCompletionMilliSeconds)); + } + + void WaitDsiHostControl() { + os::Tick timeout = os::GetSystemTick() + os::ConvertToTick(TimeSpan::FromMilliSeconds(DsiWaitForHostControlMilliSecondsMax)); + + while (true) { + if (os::GetSystemTick() >= 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); + os::SleepThread(TimeSpan::FromMilliSeconds(300)); + + /* Clear client sync point block resest. */ + reg::Write(g_dsi_regs + sizeof(u32) * DSI_INCR_SYNCPT_CNTRL, 0); + os::SleepThread(TimeSpan::FromMilliSeconds(300)); + + /* 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) { + pwm::driver::SetScale(g_lcd_backlight_session, static_cast<double>(brightness)); + pwm::driver::SetEnabled(g_lcd_backlight_session, true); + } + + } + + void InitializeDisplay() { + /* Setup globals. */ + InitializeRegisterVirtualAddresses(); + g_soc_type = spl::GetSocType(); + InitializeFrameBuffer(); + + /* Get the hardware type. */ + const auto hw_type = spl::GetHardwareType(); + + /* Turn on DSI/voltage rail. */ + { + i2c::driver::I2cSession i2c_session; + R_ABORT_UNLESS(i2c::driver::OpenSession(std::addressof(i2c_session), i2c::DeviceCode_Max77620Pmic)); + + if (g_soc_type == spl::SocType_Mariko) { + WriteI2cRegister(i2c_session, 0x18, 0x3A); + WriteI2cRegister(i2c_session, 0x1F, 0x71); + } + WriteI2cRegister(i2c_session, 0x23, 0xD0); + + i2c::driver::CloseSession(i2c_session); + } + + /* 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. */ + dd::WriteIoRegister(PmcBase + APBDEV_PMC_IO_DPD_REQ, reg::Encode(PMC_REG_BITS_ENUM(IO_DPD_REQ_CODE, DPD_OFF))); + dd::WriteIoRegister(PmcBase + APBDEV_PMC_IO_DPD2_REQ, reg::Encode(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 == spl::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); + os::SleepThread(TimeSpan::FromMilliSeconds(10)); + + reg::SetBits(g_gpio_regs + GPIO_PORT3_OUT_0, 0x2); + os::SleepThread(TimeSpan::FromMilliSeconds(10)); + + 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 (g_soc_type == spl::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); + os::SleepThread(TimeSpan::FromMilliSeconds(10)); + + /* Enable backlight reset. */ + reg::SetBits(g_gpio_regs + GPIO_PORT6_OUT_1, 0x4); + os::SleepThread(TimeSpan::FromMilliSeconds(60)); + + if (hw_type == spl::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(); + os::SleepThread(TimeSpan::FromMilliSeconds(5)); + + /* 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 + */ + if ((host_response[2] & 0xFF) == 0x10) { + g_lcd_vendor = 0; + } else { + g_lcd_vendor = (host_response[2] >> 8) & 0xFF00; + } + g_lcd_vendor = (g_lcd_vendor & 0xFFFFFF00) | (host_response[2] & 0xFF); + } + + /* LCD vendor specific configuration. */ + if (g_lcd_vendor != 0x2050) { + /* Configure LCD backlight to use PWM. */ + reg::ClearBits(g_gpio_regs + GPIO_PORT6_CNF_1, 0x1); + reg::Write(g_apb_misc_regs + PINMUX_AUX_LCD_BL_PWM, PINMUX_REG_BITS_ENUM(AUX_LCD_BL_PWM_PM, PWM0), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_DOWN)); + + /* Configure LCD backlight. */ + R_ABORT_UNLESS(pwm::driver::OpenSession(std::addressof(g_lcd_backlight_session), pwm::DeviceCode_LcdBacklight)); + pwm::driver::SetPeriod(g_lcd_backlight_session, TimeSpan::FromNanoSeconds(33898)); + + switch (g_lcd_vendor) { + 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); + os::SleepThread(TimeSpan::FromMilliSeconds(180)); + 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); + os::SleepThread(TimeSpan::FromMilliSeconds(5)); + 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); + os::SleepThread(TimeSpan::FromMilliSeconds(5)); + 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); + os::SleepThread(TimeSpan::FromMilliSeconds(180)); + 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); + os::SleepThread(TimeSpan::FromMilliSeconds(5)); + 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); + os::SleepThread(TimeSpan::FromMilliSeconds(5)); + 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); + os::SleepThread(TimeSpan::FromMilliSeconds(120)); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x2905); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST); + break; + } + } else { + /* LCD vendor 0x2050, unknown Aula (OLED) screen. */ + reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x1105); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST); + os::SleepThread(TimeSpan::FromMilliSeconds(180)); + 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); + os::SleepThread(TimeSpan::FromMilliSeconds(5)); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x2905); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST); + } + + os::SleepThread(TimeSpan::FromMilliSeconds(20)); + + 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); + os::SleepThread(TimeSpan::FromMilliSeconds(10)); + + /* 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 (g_soc_type == spl::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); + } + os::SleepThread(TimeSpan::FromMilliSeconds(10)); + + /* Write DISP1, FrameBuffer config. */ + DO_SLEEP_OR_REGISTER_WRITES(g_disp1_regs, DisplayConfigDc02); + DO_SLEEP_OR_REGISTER_WRITES(g_disp1_regs, DisplayConfigFrameBuffer); + if (g_lcd_vendor != 0x2050) { + os::SleepThread(TimeSpan::FromMilliSeconds(35)); + } + g_is_display_intialized = true; + } + + void ShowDisplay(size_t x, size_t y, size_t width, size_t height, const u32 *img) { + if (!g_is_display_intialized) { + return; + } + + /* Draw the image to the screen. */ + std::memset(g_frame_buffer, 0, FrameBufferSize); + { + for (size_t cur_y = 0; cur_y < height; cur_y++) { + for (size_t cur_x = 0; cur_x < width; cur_x++) { + g_frame_buffer[(FrameBufferHeight - (x + cur_x)) * FrameBufferWidth + y + cur_y] = img[cur_y * width + cur_x]; + } + } + } + dd::FlushDataCache(g_frame_buffer, FrameBufferSize); + + /* Enable backlight. */ + if (g_lcd_vendor == 0x2050) { + EnableBacklightForVendor2050ForAula(g_display_brightness); + } else { + EnableBacklightForGeneric(g_display_brightness); + } + } + + void FinalizeDisplay() { + if (!g_is_display_intialized) { + return; + } + + /* Disable backlight. */ + if (g_lcd_vendor == 0x2050) { + EnableBacklightForVendor2050ForAula(0); + } else { + pwm::driver::SetEnabled(g_lcd_backlight_session, false); + pwm::driver::CloseSession(g_lcd_backlight_session); + } + + reg::Write(g_disp1_regs + sizeof(u32) * DSI_VIDEO_MODE_CONTROL, 1); + reg::Write(g_disp1_regs + sizeof(u32) * DSI_WR_DATA, 0x2805); + + /* Nintendo waits 5 frames before continuing. */ + { + const uintptr_t host1x_vaddr = dd::GetIoMapping(0x500030A4, 4); + const u32 start_val = reg::Read(host1x_vaddr); + while (reg::Read(host1x_vaddr) < start_val + 5) { + /* spinlock here. */ + } + } + + reg::Write(g_disp1_regs + sizeof(u32) * DC_CMD_STATE_ACCESS, (READ_MUX | WRITE_MUX)); + reg::Write(g_disp1_regs + sizeof(u32) * DSI_VIDEO_MODE_CONTROL, 0); + + DO_REGISTER_WRITES(g_disp1_regs, DisplayConfigDc01Fini01); + os::SleepThread(TimeSpan::FromMilliSeconds(40)); + + DO_SOC_DEPENDENT_REGISTER_WRITES(g_clk_rst_regs, DisplayConfigPlld01); + DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Fini01); + DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsiPhyTiming); + DO_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Fini02); + + if (g_lcd_vendor != 0x2050) { + os::SleepThread(TimeSpan::FromMilliSeconds(10)); + } + + /* Vendor specific shutdown. */ + switch (g_lcd_vendor) { + case 0x10: /* Japan Display Inc screens. */ + DO_SLEEP_OR_REGISTER_WRITES(g_dsi_regs, DisplayConfigJdiSpecificFini01); + break; + case 0xF30: /* AUO first revision screens. */ + DO_SLEEP_OR_REGISTER_WRITES(g_dsi_regs, DisplayConfigAuoRev1SpecificFini01); + break; + case 0x1020: /* Innolux second revision screens. */ + 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); + os::SleepThread(TimeSpan::FromMilliSeconds(5)); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0xB39); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x751548B1); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x71143209); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x115631); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST); + os::SleepThread(TimeSpan::FromMilliSeconds(5)); + break; + case 0x1030: /* AUO second revision screens. */ + 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); + os::SleepThread(TimeSpan::FromMilliSeconds(5)); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0xB39); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x711148B1); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x71143209); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x114D31); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST); + os::SleepThread(TimeSpan::FromMilliSeconds(5)); + break; + case 0x1040: /* Unknown second revision screens. */ + 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); + os::SleepThread(TimeSpan::FromMilliSeconds(5)); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0xB39); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x731348B1); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x71243209); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x4C31); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST); + os::SleepThread(TimeSpan::FromMilliSeconds(5)); + break; + default: + break; + } + + reg::Write(g_dsi_regs + sizeof(u32) * DSI_WR_DATA, 0x1005); + reg::Write(g_dsi_regs + sizeof(u32) * DSI_TRIGGER, DSI_TRIGGER_HOST); + os::SleepThread(g_lcd_vendor == 0x2050 ? TimeSpan::FromMilliSeconds(120) : TimeSpan::FromMilliSeconds(50)); + + /* Disable backlight RST/Voltage. */ + reg::ClearBits(g_gpio_regs + GPIO_PORT6_OUT_1, 0x4); + if (g_lcd_vendor == 0x2050) { + os::SleepThread(TimeSpan::FromMilliSeconds(30)); + } else { + os::SleepThread(TimeSpan::FromMilliSeconds(10)); + reg::ClearBits(g_gpio_regs + GPIO_PORT3_OUT_0, 0x2); + os::SleepThread(TimeSpan::FromMilliSeconds(10)); + reg::ClearBits(g_gpio_regs + GPIO_PORT3_OUT_0, 0x1); + os::SleepThread(TimeSpan::FromMilliSeconds(10)); + } + + /* 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); + + /* Unmap framebuffer from DC virtual address space. */ + FinalizeFrameBuffer(); + g_is_display_intialized = false; + } + + void SetDisplayBrightness(int percentage) { + g_display_brightness = percentage; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_display.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_display.hpp new file mode 100644 index 00000000..ca06226d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_display.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::boot { + + /* Splash Screen/Display utilities. */ + void InitializeDisplay(); + void ShowDisplay(size_t x, size_t y, size_t width, size_t height, const u32 *img); + void FinalizeDisplay(); + + void SetDisplayBrightness(int percentage); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_display_config.inc b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_display_config.inc new file mode 100644 index 00000000..f8d336df --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_display_config.inc @@ -0,0 +1,683 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +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}, + {sizeof(u32) * DSI_CONTROL, 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, 0}, + {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/stratosphere/boot/source/boot_driver_management.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_driver_management.cpp new file mode 100644 index 00000000..869646f3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_driver_management.cpp @@ -0,0 +1,70 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "boot_driver_management.hpp" + +namespace ams::boot { + + void InitializeGpioDriverLibrary() { + /* Initialize the gpio client library with the server manager object. */ + gpio::InitializeWith(gpio::server::GetServiceObject()); + + /* Initialize the board driver without enabling interrupt handlers. */ + gpio::driver::board::Initialize(false); + + /* Initialize the driver library. */ + gpio::driver::Initialize(); + } + + void InitializeI2cDriverLibrary() { + /* Initialize the i2c client library with the server manager object. */ + i2c::InitializeWith(i2c::server::GetServiceObject(), i2c::server::GetServiceObjectPowerBus()); + + /* Initialize the board driver. */ + i2c::driver::board::Initialize(); + + /* Initialize the driver library. */ + i2c::driver::Initialize(); + + /* Initialize the pwm client library with the server manager object. */ + pwm::InitializeWith(pwm::server::GetServiceObject()); + + /* Initialize the pwm board driver. */ + pwm::driver::board::Initialize(); + + /* Initialize the pwm driver library. */ + pwm::driver::Initialize(); + } + + void FinalizeGpioDriverLibrary() { + /* Finalize the gpio client library. */ + gpio::Finalize(); + } + + void FinalizeI2cDriverLibrary() { + /* Finalize the i2c driver library. */ + i2c::driver::Finalize(); + + /* Finalize the i2c client library. */ + i2c::Finalize(); + + /* NOTE: Unknown finalize function is called here by Nintendo. */ + + /* Finalize the pwm client library. */ + pwm::Finalize(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_driver_management.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_driver_management.hpp new file mode 100644 index 00000000..e3b068a0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_driver_management.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +namespace ams::boot { + + void InitializeGpioDriverLibrary(); + void InitializeI2cDriverLibrary(); + void FinalizeGpioDriverLibrary(); + void FinalizeI2cDriverLibrary(); + + +} + diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_fan_enable.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_fan_enable.cpp new file mode 100644 index 00000000..e5c93d48 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_fan_enable.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "boot_fan_enable.hpp" + +namespace ams::boot { + + void SetFanPowerEnabled() { + if (spl::GetHardwareType() == spl::HardwareType::Copper) { + /* TODO */ + /* boot::gpio::Configure(GpioPadName_FanEnable); */ + /* boot::gpio::SetDirection(GpioPadName_FanEnable, GpioDirection_Output); */ + /* boot::gpio::SetValue(GpioPadName_FanEnable, GpioValue_High); */ + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_fan_enable.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_fan_enable.hpp new file mode 100644 index 00000000..e7ff800a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_fan_enable.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::boot { + + void SetFanPowerEnabled(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_i2c_utils.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_i2c_utils.cpp new file mode 100644 index 00000000..1652ccf0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_i2c_utils.cpp @@ -0,0 +1,72 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "boot_i2c_utils.hpp" + +namespace ams::boot { + + namespace { + + template<typename F> + constexpr Result RetryUntilSuccess(F f) { + constexpr auto Timeout = TimeSpan::FromSeconds(10); + constexpr auto RetryInterval = TimeSpan::FromMilliSeconds(20); + + TimeSpan cur_time = TimeSpan(0); + while (true) { + const auto retry_result = f(); + R_SUCCEED_IF(R_SUCCEEDED(retry_result)); + + cur_time += RetryInterval; + R_UNLESS(cur_time < Timeout, retry_result); + + os::SleepThread(RetryInterval); + } + } + + } + + Result ReadI2cRegister(i2c::driver::I2cSession &session, u8 *dst, size_t dst_size, const u8 *cmd, size_t cmd_size) { + AMS_ABORT_UNLESS(dst != nullptr && dst_size > 0); + AMS_ABORT_UNLESS(cmd != nullptr && cmd_size > 0); + + u8 cmd_list[i2c::CommandListLengthMax]; + i2c::CommandListFormatter formatter(cmd_list, sizeof(cmd_list)); + + R_ABORT_UNLESS(formatter.EnqueueSendCommand(i2c::TransactionOption_StartCondition, cmd, cmd_size)); + R_ABORT_UNLESS(formatter.EnqueueReceiveCommand(static_cast<i2c::TransactionOption>(i2c::TransactionOption_StartCondition | i2c::TransactionOption_StopCondition), dst_size)); + + R_RETURN(RetryUntilSuccess([&]() { R_RETURN(i2c::driver::ExecuteCommandList(dst, dst_size, session, cmd_list, formatter.GetCurrentLength())); })); + } + + Result WriteI2cRegister(i2c::driver::I2cSession &session, const u8 *src, size_t src_size, const u8 *cmd, size_t cmd_size) { + AMS_ABORT_UNLESS(src != nullptr && src_size > 0); + AMS_ABORT_UNLESS(cmd != nullptr && cmd_size > 0); + + u8 cmd_list[0x20]; + + /* N doesn't use a CommandListFormatter here... */ + std::memcpy(cmd_list + 0, cmd, cmd_size); + std::memcpy(cmd_list + cmd_size, src, src_size); + + R_RETURN(RetryUntilSuccess([&]() { R_RETURN(i2c::driver::Send(session, cmd_list, src_size + cmd_size, static_cast<i2c::TransactionOption>(i2c::TransactionOption_StartCondition | i2c::TransactionOption_StopCondition))); })); + } + + Result WriteI2cRegister(i2c::driver::I2cSession &session, const u8 address, const u8 value) { + R_RETURN(WriteI2cRegister(session, std::addressof(value), sizeof(value), &address, sizeof(address))); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_i2c_utils.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_i2c_utils.hpp new file mode 100644 index 00000000..b84ed6df --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_i2c_utils.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::boot { + + /* I2C Utilities. */ + Result ReadI2cRegister(i2c::driver::I2cSession &session, u8 *dst, size_t dst_size, const u8 *cmd, size_t cmd_size); + Result WriteI2cRegister(i2c::driver::I2cSession &session, const u8 *src, size_t src_size, const u8 *cmd, size_t cmd_size); + Result WriteI2cRegister(i2c::driver::I2cSession &session, const u8 address, const u8 value); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_main.cpp new file mode 100644 index 00000000..62163e28 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_main.cpp @@ -0,0 +1,224 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "boot_boot_reason.hpp" +#include "boot_change_voltage.hpp" +#include "boot_check_battery.hpp" +#include "boot_check_clock.hpp" +#include "boot_clock_initial_configuration.hpp" +#include "boot_driver_management.hpp" +#include "boot_fan_enable.hpp" +#include "boot_pinmux_initial_configuration.hpp" +#include "boot_repair_boot_images.hpp" +#include "boot_splash_screen.hpp" +#include "boot_power_utils.hpp" + +namespace ams { + + namespace boot { + + namespace { + + constinit u8 g_exp_heap_memory[20_KB]; + constinit u8 g_unit_heap_memory[5_KB]; + constinit lmem::HeapHandle g_exp_heap_handle; + constinit lmem::HeapHandle g_unit_heap_handle; + + constinit sf::ExpHeapMemoryResource g_exp_heap_memory_resource; + constinit sf::UnitHeapMemoryResource g_unit_heap_memory_resource; + + void *Allocate(size_t size) { + void *mem = lmem::AllocateFromExpHeap(g_exp_heap_handle, size); + return mem; + } + + void Deallocate(void *p, size_t size) { + AMS_UNUSED(size); + lmem::FreeToExpHeap(g_exp_heap_handle, p); + } + + void InitializeHeaps() { + /* Create the heaps. */ + g_exp_heap_handle = lmem::CreateExpHeap(g_exp_heap_memory, sizeof(g_exp_heap_memory), lmem::CreateOption_ThreadSafe); + g_unit_heap_handle = lmem::CreateUnitHeap(g_unit_heap_memory, sizeof(g_unit_heap_memory), sizeof(ddsf::DeviceCodeEntryHolder), lmem::CreateOption_ThreadSafe); + + /* Attach the memory resources. */ + g_exp_heap_memory_resource.Attach(g_exp_heap_handle); + g_unit_heap_memory_resource.Attach(g_unit_heap_handle); + + /* Register with ddsf. */ + ddsf::SetMemoryResource(std::addressof(g_exp_heap_memory_resource)); + ddsf::SetDeviceCodeEntryHolderMemoryResource(std::addressof(g_unit_heap_memory_resource)); + } + + } + + } + + namespace hos { + + void SetNonApproximateVersionInternal(); + + } + + namespace init { + + void InitializeSystemModule() { + /* Initialize heaps. */ + boot::InitializeHeaps(); + + /* Connect to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + + /* Initialize fs. */ + fs::InitializeForSystem(); + fs::SetAllocator(boot::Allocate, boot::Deallocate); + fs::SetEnabledAutoAbort(false); + + /* Initialize spl. */ + spl::Initialize(); + + /* Set the true hos version. */ + hos::SetNonApproximateVersionInternal(); + + /* Verify that we can sanely execute. */ + ams::CheckApiVersion(); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void ExceptionHandler(FatalErrorContext *ctx) { + /* We're boot sysmodule, so manually reboot to fatal error. */ + boot::RebootForFatalError(ctx); + } + + void Main() { + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(boot, Main)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(boot, Main)); + + /* Perform atmosphere-specific init. */ + ams::InitializeForBoot(); + + /* Set the reboot payload with ams.mitm. */ + boot::SetInitialRebootPayload(); + + /* Change voltage from 3.3v to 1.8v for select devices. */ + boot::ChangeGpioVoltageTo1_8v(); + + /* Setup gpio. */ + gpio::driver::SetInitialGpioConfig(); + + /* Initialize the gpio server library. */ + boot::InitializeGpioDriverLibrary(); + + /* Initialize the i2c server library. */ + boot::InitializeI2cDriverLibrary(); + + /* Get the hardware type. */ + const auto hw_type = spl::GetHardwareType(); + + /* Initialize the power control library without interrupt event handling. */ + if (hw_type != spl::HardwareType::Calcio) { + powctl::Initialize(false); + } + + /* Check USB PLL/UTMIP clock. */ + boot::CheckClock(); + + /* Talk to PMIC/RTC, set boot reason with SPL. */ + boot::DetectBootReason(); + + /* Display the splash screen and check the battery charge. */ + if (hw_type != spl::HardwareType::Calcio) { + /* Display splash screen for two seconds. */ + boot::ShowSplashScreen(); + + /* Check that the battery has enough to boot. */ + boot::CheckBatteryCharge(); + } + + /* Configure pinmux + drive pads. */ + boot::SetInitialPinmuxConfiguration(); + + /* Configure the PMC wake pin settings. */ + gpio::driver::SetInitialWakePinConfig(); + + /* Configure output clock. */ + if (hw_type != spl::HardwareType::Calcio) { + boot::SetInitialClockConfiguration(); + } + + /* Set Fan enable config (Copper only). */ + boot::SetFanPowerEnabled(); + + /* Repair boot partitions in NAND if needed. */ + boot::CheckAndRepairBootImages(); + + /* Finalize the power control library. */ + if (hw_type != spl::HardwareType::Calcio) { + powctl::Finalize(); + } + + /* Finalize the i2c server library. */ + boot::FinalizeI2cDriverLibrary(); + + /* Finalize the gpio server library. */ + boot::FinalizeGpioDriverLibrary(); + + /* Tell PM to start boot2. */ + R_ABORT_UNLESS(pmshellInitialize()); + R_ABORT_UNLESS(pmshellNotifyBootFinished()); + } + +} + +/* Override operator new. */ +void *operator new(size_t size) { + return ams::boot::Allocate(size); +} + +void *operator new(size_t size, const std::nothrow_t &) { + return ams::boot::Allocate(size); +} + +void operator delete(void *p) { + return ams::boot::Deallocate(p, 0); +} + +void operator delete(void *p, size_t size) { + return ams::boot::Deallocate(p, size); +} + +void *operator new[](size_t size) { + return ams::boot::Allocate(size); +} + +void *operator new[](size_t size, const std::nothrow_t &) { + return ams::boot::Allocate(size); +} + +void operator delete[](void *p) { + return ams::boot::Deallocate(p, 0); +} + +void operator delete[](void *p, size_t size) { + return ams::boot::Deallocate(p, size); +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_pinmux_initial_configuration.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_pinmux_initial_configuration.cpp new file mode 100644 index 00000000..1e19961e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_pinmux_initial_configuration.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "boot_pinmux_initial_configuration.hpp" + +namespace ams::boot { + + void SetInitialPinmuxConfiguration() { + pinmux::driver::Initialize(); + pinmux::driver::SetInitialConfig(); + pinmux::driver::SetInitialDrivePadConfig(); + pinmux::driver::Finalize(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_pinmux_initial_configuration.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_pinmux_initial_configuration.hpp new file mode 100644 index 00000000..b602ef6b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_pinmux_initial_configuration.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::boot { + + void SetInitialPinmuxConfiguration(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_pmic_driver.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_pmic_driver.cpp new file mode 100644 index 00000000..893726de --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_pmic_driver.cpp @@ -0,0 +1,123 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "boot_i2c_utils.hpp" +#include "boot_pmic_driver.hpp" + +namespace ams::boot { + + void PmicDriver::ShutdownSystem() { + this->ShutdownSystem(false); + } + + void PmicDriver::RebootSystem() { + this->ShutdownSystem(true); + } + + Result PmicDriver::GetAcOk(bool *out) { + u8 power_status; + R_TRY(this->GetPowerStatus(std::addressof(power_status))); + *out = (power_status & 0x02) != 0; + R_SUCCEED(); + } + + Result PmicDriver::GetOnOffIrq(u8 *out) { + const u8 addr = 0x0B; + R_RETURN(ReadI2cRegister(m_i2c_session, out, sizeof(*out), std::addressof(addr), sizeof(addr))); + } + + Result PmicDriver::GetPowerStatus(u8 *out) { + const u8 addr = 0x15; + R_RETURN(ReadI2cRegister(m_i2c_session, out, sizeof(*out), std::addressof(addr), sizeof(addr))); + } + + Result PmicDriver::GetNvErc(u8 *out) { + const u8 addr = 0x0C; + R_RETURN(ReadI2cRegister(m_i2c_session, out, sizeof(*out), std::addressof(addr), sizeof(addr))); + } + + Result PmicDriver::GetPowerButtonPressed(bool *out) { + u8 on_off_irq; + R_TRY(this->GetOnOffIrq(std::addressof(on_off_irq))); + *out = (on_off_irq & 0x08) != 0; + R_SUCCEED(); + } + + void PmicDriver::ShutdownSystem(bool reboot) { + const u8 on_off_1_addr = 0x41; + const u8 on_off_2_addr = 0x42; + + /* Get value, set or clear software reset mask. */ + u8 on_off_2_val = 0; + R_ABORT_UNLESS(ReadI2cRegister(m_i2c_session, std::addressof(on_off_2_val), sizeof(on_off_2_val), std::addressof(on_off_2_addr), sizeof(on_off_2_addr))); + if (reboot) { + on_off_2_val |= 0x80; + } else { + on_off_2_val &= ~0x80; + } + R_ABORT_UNLESS(WriteI2cRegister(m_i2c_session, std::addressof(on_off_2_val), sizeof(on_off_2_val), std::addressof(on_off_2_addr), sizeof(on_off_2_addr))); + + /* Get value, set software reset mask. */ + u8 on_off_1_val = 0; + R_ABORT_UNLESS(ReadI2cRegister(m_i2c_session, std::addressof(on_off_1_val), sizeof(on_off_1_val), std::addressof(on_off_1_addr), sizeof(on_off_1_addr))); + on_off_1_val |= 0x80; + + /* Finalize the battery on non-Calcio. */ + if (spl::GetHardwareType() != spl::HardwareType::Calcio) { + BatteryDriver battery_driver; + this->FinalizeBattery(battery_driver); + } + + /* Actually write the value to trigger shutdown/reset. */ + R_ABORT_UNLESS(WriteI2cRegister(m_i2c_session, std::addressof(on_off_1_val), sizeof(on_off_1_val), std::addressof(on_off_1_addr), sizeof(on_off_1_addr))); + + /* Allow up to 5 seconds for shutdown/reboot to take place. */ + os::SleepThread(TimeSpan::FromSeconds(5)); + AMS_ABORT("Shutdown failed"); + } + + void PmicDriver::FinalizeBattery(BatteryDriver &battery_driver) { + /* Get whether shutdown is enabled. */ + bool shutdown_enabled; + if (R_FAILED(battery_driver.IsI2cShutdownEnabled(std::addressof(shutdown_enabled)))) { + return; + } + + /* On Hoag, we don't want to use the desired shutdown value when battery charged. */ + bool use_desired_shutdown = true; + if (spl::GetHardwareType() == spl::HardwareType::Hoag) { + float battery_charge_raw; + if (R_FAILED(battery_driver.GetChargePercentage(std::addressof(battery_charge_raw))) || battery_charge_raw >= 80.0) { + use_desired_shutdown = false; + } + } + + bool ac_ok; + bool desired_shutdown_enabled; + if (R_FAILED(this->GetAcOk(&ac_ok)) || ac_ok) { + desired_shutdown_enabled = false; + } else { + desired_shutdown_enabled = true; + } + + desired_shutdown_enabled &= use_desired_shutdown; + + if (shutdown_enabled != desired_shutdown_enabled) { + battery_driver.SetI2cShutdownEnabled(desired_shutdown_enabled); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_pmic_driver.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_pmic_driver.hpp new file mode 100644 index 00000000..e54701ab --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_pmic_driver.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "boot_battery_driver.hpp" + +namespace ams::boot { + + /* Driver object. */ + class PmicDriver { + private: + i2c::driver::I2cSession m_i2c_session; + public: + PmicDriver() { + R_ABORT_UNLESS(i2c::driver::OpenSession(std::addressof(m_i2c_session), i2c::DeviceCode_Max77620Pmic)); + } + + ~PmicDriver() { + i2c::driver::CloseSession(m_i2c_session); + } + private: + Result GetPowerStatus(u8 *out); + void ShutdownSystem(bool reboot); + void FinalizeBattery(BatteryDriver &battery_driver); + public: + void ShutdownSystem(); + void RebootSystem(); + Result GetAcOk(bool *out); + Result GetOnOffIrq(u8 *out); + Result GetNvErc(u8 *out); + Result GetPowerButtonPressed(bool *out); + }; + +} + diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_power_utils.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_power_utils.cpp new file mode 100644 index 00000000..0dc71b84 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_power_utils.cpp @@ -0,0 +1,106 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "boot_power_utils.hpp" +#include "boot_pmic_driver.hpp" + +namespace ams::boot { + + namespace { + + /* Convenience definitions. */ + constexpr uintptr_t IramBase = 0x40000000ull; + constexpr uintptr_t IramPayloadBase = 0x40010000ull; + constexpr size_t IramSize = 0x40000; + constexpr size_t IramPayloadMaxSize = 0x24000; + constexpr size_t IramFatalErrorContextOffset = 0x2E000; + + /* Globals. */ + alignas(os::MemoryPageSize) u8 g_work_page[os::MemoryPageSize]; + + constexpr const u8 FuseeBin[] = { + #embed <fusee.bin> + }; + + /* Helpers. */ + void ClearIram() { + /* Make page FFs. */ + std::memset(g_work_page, 0xFF, sizeof(g_work_page)); + + /* Overwrite all of IRAM with FFs. */ + for (size_t ofs = 0; ofs < IramSize; ofs += sizeof(g_work_page)) { + exosphere::CopyToIram(IramBase + ofs, g_work_page, sizeof(g_work_page)); + } + } + + void DoRebootToPayload() { + /* Ensure clean IRAM state. */ + ClearIram(); + + /* Copy in payload. */ + for (size_t ofs = 0; ofs < sizeof(FuseeBin); ofs += sizeof(g_work_page)) { + std::memcpy(g_work_page, FuseeBin + ofs, std::min(static_cast<size_t>(sizeof(FuseeBin) - ofs), sizeof(g_work_page))); + exosphere::CopyToIram(IramPayloadBase + ofs, g_work_page, sizeof(g_work_page)); + } + + exosphere::ForceRebootToIramPayload(); + } + + void DoRebootToFatalError(const ams::FatalErrorContext *ctx) { + /* Ensure clean IRAM state. */ + ClearIram(); + + /* Copy in payload. */ + for (size_t ofs = 0; ofs < sizeof(FuseeBin); ofs += sizeof(g_work_page)) { + std::memcpy(g_work_page, FuseeBin + ofs, std::min(static_cast<size_t>(sizeof(FuseeBin) - ofs), sizeof(g_work_page))); + exosphere::CopyToIram(IramPayloadBase + ofs, g_work_page, sizeof(g_work_page)); + } + + + /* Copy in fatal error context, if relevant. */ + if (ctx != nullptr) { + std::memset(g_work_page, 0xCC, sizeof(g_work_page)); + std::memcpy(g_work_page, ctx, sizeof(*ctx)); + exosphere::CopyToIram(IramPayloadBase + IramFatalErrorContextOffset, g_work_page, sizeof(g_work_page)); + } + + exosphere::ForceRebootToFatalError(); + } + + } + + void RebootSystem() { + if (spl::GetSocType() == spl::SocType_Erista) { + DoRebootToPayload(); + } else { + /* On Mariko, we can't reboot to payload, so we should just do a reboot. */ + PmicDriver().RebootSystem(); + } + } + + void ShutdownSystem() { + PmicDriver().ShutdownSystem(); + } + + void SetInitialRebootPayload() { + ::ams::SetInitialRebootPayload(FuseeBin, sizeof(FuseeBin)); + } + + void RebootForFatalError(ams::FatalErrorContext *ctx) { + DoRebootToFatalError(ctx); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_power_utils.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_power_utils.hpp new file mode 100644 index 00000000..3d7c6729 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_power_utils.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::boot { + + /* Power utilities. */ + void RebootSystem(); + void ShutdownSystem(); + + /* Atmosphere power utilities. */ + void SetInitialRebootPayload(); + void RebootForFatalError(ams::FatalErrorContext *ctx); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_registers_di.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_registers_di.hpp new file mode 100644 index 00000000..89b285c3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#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/stratosphere/boot/source/boot_repair_boot_images.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_repair_boot_images.cpp new file mode 100644 index 00000000..05d52950 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_repair_boot_images.cpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "boot_power_utils.hpp" +#include "boot_repair_boot_images.hpp" + +namespace ams::boot { + + namespace { + + /* Globals. */ + alignas(os::MemoryPageSize) u8 g_boot_image_work_buffer[0x10000]; + + } + + void CheckAndRepairBootImages() { + const auto boot_image_update_type = updater::GetBootImageUpdateType(spl::GetHardwareType()); + + bool repaired_normal, repaired_safe; + if (R_SUCCEEDED(updater::VerifyBootImagesAndRepairIfNeeded(&repaired_normal, &repaired_safe, g_boot_image_work_buffer, sizeof(g_boot_image_work_buffer), boot_image_update_type)) && repaired_normal) { + /* Nintendo only performs a reboot on successful normal repair. */ + RebootSystem(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_repair_boot_images.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_repair_boot_images.hpp new file mode 100644 index 00000000..547cac85 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_repair_boot_images.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::boot { + + void CheckAndRepairBootImages(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_rtc_driver.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_rtc_driver.cpp new file mode 100644 index 00000000..2e0dce2d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_rtc_driver.cpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "boot_rtc_driver.hpp" + +namespace ams::boot { + + Result RtcDriver::ReadRtcRegister(u8 *out, u8 address) { + const u8 update_addr = 0x04; + const u8 update_val = 0x10; + R_TRY(WriteI2cRegister(m_i2c_session, &update_val, sizeof(update_val), &update_addr, sizeof(update_addr))); + os::SleepThread(TimeSpan::FromMilliSeconds(16)); + R_RETURN(ReadI2cRegister(m_i2c_session, out, sizeof(*out), &address, sizeof(address))); + } + + Result RtcDriver::GetRtcIntr(u8 *out) { + const u8 addr = 0x00; + R_RETURN(ReadI2cRegister(m_i2c_session, out, sizeof(*out), &addr, sizeof(addr))); + } + + Result RtcDriver::GetRtcIntrM(u8 *out) { + const u8 addr = 0x01; + R_RETURN(this->ReadRtcRegister(out, addr)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_rtc_driver.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_rtc_driver.hpp new file mode 100644 index 00000000..b9a08ce5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_rtc_driver.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "boot_i2c_utils.hpp" + +namespace ams::boot { + + class RtcDriver { + private: + i2c::driver::I2cSession m_i2c_session; + public: + RtcDriver() { + R_ABORT_UNLESS(i2c::driver::OpenSession(std::addressof(m_i2c_session), i2c::DeviceCode_Max77620Rtc)); + } + + ~RtcDriver() { + i2c::driver::CloseSession(m_i2c_session); + } + private: + Result ReadRtcRegister(u8 *out, u8 address); + public: + Result GetRtcIntr(u8 *out); + Result GetRtcIntrM(u8 *out); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_splash_screen.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_splash_screen.cpp new file mode 100644 index 00000000..8747b998 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_splash_screen.cpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "boot_boot_reason.hpp" +#include "boot_display.hpp" +#include "boot_splash_screen.hpp" + +namespace ams::boot { + + namespace { + +/* Include splash screen into anonymous namespace. */ +/* TODO: Compile-time switch for splash_screen_text.hpp? */ +#include "boot_splash_screen_notext.inc" + + } + + void ShowSplashScreen() { + const auto boot_reason = GetBootReason(); + if (boot_reason == spl::BootReason_AcOk || boot_reason == spl::BootReason_RtcAlarm2) { + return; + } + + InitializeDisplay(); + { + /* Splash screen is shown for 2 seconds. */ + ShowDisplay(SplashScreenX, SplashScreenY, SplashScreenW, SplashScreenH, SplashScreen); + os::SleepThread(TimeSpan::FromSeconds(2)); + } + FinalizeDisplay(); + } + +} + diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_splash_screen.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_splash_screen.hpp new file mode 100644 index 00000000..ddd3005f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_splash_screen.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::boot { + + void ShowSplashScreen(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_splash_screen_notext.inc b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_splash_screen_notext.inc new file mode 100644 index 00000000..57ab22ee --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_splash_screen_notext.inc @@ -0,0 +1,22 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +constexpr size_t SplashScreenX = 535; +constexpr size_t SplashScreenY = 274; +constexpr size_t SplashScreenW = 210; +constexpr size_t SplashScreenH = 172; + +constexpr u32 SplashScreen[] = {0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF404040, 0xFF666666, 0xFF666666, 0xFF707070, 0xFF666666, 0xFF666666, 0xFF404040, 0xFF333333, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF707070, 0xFFAFAFAF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFBFBFBF, 0xFF707070, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCCCCCC, 0xFF666666, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF505050, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF666666, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF505050, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF333333, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF505050, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF333333, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF333333, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF666666, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCCCCCC, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF505050, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCCCCCC, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF505050, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF333333, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF333333, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF333333, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF666666, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCCCCCC, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF505050, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF505050, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF333333, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFFDFDFDF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFF999999, 0xFF999999, 0xFF999999, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF999999, 0xFF999999, 0xFF999999, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFCCCCCC, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF202020, 0xFF404040, 0xFF505050, 0xFF808080, 0xFF999999, 0xFFBFBFBF, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF333333, 0xFF404040, 0xFF707070, 0xFFAFAFAF, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF333333, 0xFF666666, 0xFF999999, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF333333, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF404040, 0xFF999999, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF404040, 0xFF999999, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF333333, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF666666, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF505050, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF666666, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF666666, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCCCCCC, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCCCCCC, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF666666, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF666666, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF666666, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF666666, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCCCCCC, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF505050, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF666666, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF333333, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF333333, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF333333, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF333333, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF333333, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCCCCCC, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF101010, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF666666, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF666666, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF000000, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCCCCCC, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF101010, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF333333, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF505050, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCCCCCC, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCCCCCC, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF666666, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF333333, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF666666, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCCCCCC, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF505050, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF333333, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF333333, 0xFF202020, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF666666, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF333333, 0xFF666666, 0xFF707070, 0xFF999999, 0xFF999999, 0xFFBFBFBF, 0xFFDFDFDF, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFFCCCCCC, 0xFFBFBFBF, 0xFFAFAFAF, 0xFF999999, 0xFF808080, 0xFF505050, 0xFF404040, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF333333, 0xFF666666, 0xFF999999, 0xFF999999, 0xFFCCCCCC, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCCCCCC, 0xFFAFAFAF, 0xFF999999, 0xFF666666, 0xFF404040, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF404040, 0xFF707070, 0xFF999999, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCCCCCC, 0xFFAFAFAF, 0xFF808080, 0xFF404040, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF666666, 0xFF999999, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCCCCCC, 0xFFAFAFAF, 0xFF666666, 0xFF333333, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF666666, 0xFF999999, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFFDFDFDF, 0xFFDFDFDF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFCCCCCC, 0xFFDFDFDF, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFAFAFAF, 0xFF666666, 0xFF333333, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFF999999, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFDFDFDF, 0xFFAFAFAF, 0xFF999999, 0xFF808080, 0xFF666666, 0xFF505050, 0xFF333333, 0xFF202020, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF333333, 0xFF404040, 0xFF666666, 0xFF808080, 0xFF999999, 0xFFAFAFAF, 0xFFBFBFBF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF999999, 0xFF505050, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF505050, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF666666, 0xFF999999, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFFAFAFAF, 0xFF999999, 0xFF666666, 0xFF333333, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF404040, 0xFF505050, 0xFF808080, 0xFFAFAFAF, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF666666, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF666666, 0xFFBFBFBF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF999999, 0xFF707070, 0xFF404040, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF404040, 0xFF707070, 0xFF999999, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF666666, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFF999999, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF999999, 0xFF505050, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF404040, 0xFF808080, 0xFFBFBFBF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF666666, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF333333, 0xFF808080, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF999999, 0xFF404040, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFF808080, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF999999, 0xFF333333, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF505050, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF999999, 0xFF666666, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF505050, 0xFF999999, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCCCCCC, 0xFF666666, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF808080, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF999999, 0xFF404040, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFF999999, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF999999, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF999999, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF333333, 0xFF999999, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF404040, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFF999999, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF666666, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF333333, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCCCCCC, 0xFF808080, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF666666, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF666666, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF333333, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF505050, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF999999, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF666666, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF999999, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF808080, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF666666, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF666666, 0xFF101010, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF333333, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCCCCCC, 0xFF707070, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF999999, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF666666, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF808080, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF666666, 0xFFCCCCCC, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF666666, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF404040, 0xFF404040, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF404040, 0xFF333333, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000}; +static_assert(sizeof(SplashScreen) == sizeof(u32) * SplashScreenW * SplashScreenH, "Incorrect SplashScreen definition!"); \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_splash_screen_text.inc b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_splash_screen_text.inc new file mode 100644 index 00000000..377a2003 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/source/boot_splash_screen_text.inc @@ -0,0 +1,22 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +constexpr size_t SplashScreenX = 464; +constexpr size_t SplashScreenY = 274; +constexpr size_t SplashScreenW = 351; +constexpr size_t SplashScreenH = 172; + +constexpr u32 SplashScreen[] = {0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF707070, 0xFF9F9F9F, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFBFBFBF, 0xFF707070, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF8F8F8F, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF6F6F6F, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF8F8F8F, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7F7F7F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF303030, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7F7F7F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF303030, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF303030, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7F7F7F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF9F9F9F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF6F6F6F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF8F8F8F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF5F5F5F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF9F9F9F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF6F6F6F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF8F8F8F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF505050, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF8F8F8F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF8F8F8F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7F7F7F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF7F7F7F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7F7F7F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF505050, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7F7F7F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF303030, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7F7F7F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF303030, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF303030, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF9F9F9F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7F7F7F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF8F8F8F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF9F9F9F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF6F6F6F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF8F8F8F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF5F5F5F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF8F8F8F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF9F9F9F, 0xFF9F9F9F, 0xFF909090, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF9F9F9F, 0xFF9F9F9F, 0xFFAFAFAF, 0xFFBFBFBF, 0xFFCFCFCF, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF6F6F6F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF404040, 0xFF505050, 0xFF808080, 0xFFAFAFAF, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF8F8F8F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFF707070, 0xFFAFAFAF, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF505050, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF606060, 0xFFAFAFAF, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF8F8F8F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF909090, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFF909090, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF303030, 0xFFA0A0A0, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF8F8F8F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF909090, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7F7F7F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF606060, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF606060, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF7F7F7F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF606060, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF606060, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7F7F7F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF505050, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF303030, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF303030, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7F7F7F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF303030, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF7F7F7F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF505050, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF1F1F1F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF606060, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF303030, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF505050, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF909090, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF909090, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF303030, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF505050, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF303030, 0xFF606060, 0xFF707070, 0xFF8F8F8F, 0xFF9F9F9F, 0xFFAFAFAF, 0xFFBFBFBF, 0xFFDFDFDF, 0xFFDFDFDF, 0xFFDFDFDF, 0xFFDFDFDF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFFDFDFDF, 0xFFDFDFDF, 0xFFDFDFDF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFF9F9F9F, 0xFF8F8F8F, 0xFF808080, 0xFF606060, 0xFF404040, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF909090, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF8F8F8F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF505050, 0xFF707070, 0xFF9F9F9F, 0xFFCFCFCF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFFAFAFAF, 0xFF808080, 0xFF505050, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF707070, 0xFF9F9F9F, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFAFAFAF, 0xFF707070, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF606060, 0xFF9F9F9F, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFAFAFAF, 0xFF606060, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF606060, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF707070, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF8F8F8F, 0xFF303030, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF505050, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF909090, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF8F8F8F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF707070, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFCFCFCF, 0xFFAFAFAF, 0xFF909090, 0xFF808080, 0xFF606060, 0xFF505050, 0xFF404040, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF101010, 0xFF000000, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF303030, 0xFF404040, 0xFF404040, 0xFF606060, 0xFF808080, 0xFF909090, 0xFFAFAFAF, 0xFFBFBFBF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF707070, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF5F5F5F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFBFBFBF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF9F9F9F, 0xFF707070, 0xFF505050, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF404040, 0xFF707070, 0xFF909090, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF606060, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF303030, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF7F7F7F, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFBFBFBF, 0xFF808080, 0xFF404040, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFF707070, 0xFFAFAFAF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF9F9F9F, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF606060, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF7F7F7F, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF909090, 0xFF404040, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFF808080, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF202020, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF909090, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF8F8F8F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF9F9F9F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF808080, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF606060, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF505050, 0xFF101010, 0xFF8F8F8F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF808080, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF606060, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF303030, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF808080, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF707070, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF909090, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF808080, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF606060, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF808080, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF606060, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF808080, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF606060, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF303030, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFF606060, 0xFF606060, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF505050, 0xFF606060, 0xFF404040, 0xFF111111, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF505050, 0xFF606060, 0xFF606060, 0xFF515151, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF404040, 0xFF8F8F8F, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF404040, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF111111, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF404040, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF606060, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF909090, 0xFFDFDFDF, 0xFFDFDFDF, 0xFFDFDFDF, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF303030, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF909090, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFA0A0A0, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF111111, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF111111, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF111111, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF111111, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF111111, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF101010, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF111111, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF111111, 0xFF202020, 0xFF202020, 0xFF111111, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF212121, 0xFF111111, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF101010, 0xFF606060, 0xFF9F9F9F, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFBFBFBF, 0xFF606060, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF111111, 0xFF404040, 0xFF404040, 0xFF404040, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF505050, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF414141, 0xFF101010, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF505050, 0xFFAFAFAF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFAFAFAF, 0xFF606060, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF404040, 0xFFAFAFAF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFBFBFBF, 0xFF606060, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF404040, 0xFF909090, 0xFFCFCFCF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF9F9F9F, 0xFF505050, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF404040, 0xFF9F9F9F, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF9F9F9F, 0xFF505050, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF111111, 0xFF808080, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF9F9F9F, 0xFF404040, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF303030, 0xFF7F7F7F, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF9F9F9F, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF101010, 0xFF505050, 0xFF9F9F9F, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF909090, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF505050, 0xFFBFBFBF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF404040, 0xFF9F9F9F, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF909090, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF111111, 0xFF808080, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF505050, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF414141, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF010101, 0xFF404040, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF404040, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF404040, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF606060, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF606060, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF010101, 0xFF111111, 0xFF7F7F7F, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF202020, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF010101, 0xFF111111, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF8F8F8F, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF6F6F6F, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF010101, 0xFF303030, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF505050, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF707070, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF414141, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF303030, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF7F7F7F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF212121, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF202020, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF7F7F7F, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF101010, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF303030, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF111111, 0xFF9F9F9F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF303030, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF707070, 0xFF303030, 0xFF202020, 0xFF202020, 0xFF303030, 0xFF505050, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF303030, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF303030, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFF101010, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF505050, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF9F9F9F, 0xFF808080, 0xFF909090, 0xFFAFAFAF, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF9F9F9F, 0xFF808080, 0xFF808080, 0xFFAFAFAF, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF8F8F8F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF909090, 0xFF606060, 0xFF404040, 0xFF505050, 0xFF707070, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF707070, 0xFF202020, 0xFF111111, 0xFF010101, 0xFF303030, 0xFF505050, 0xFFA0A0A0, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF111111, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF808080, 0xFF505050, 0xFF303030, 0xFF404040, 0xFF505050, 0xFF909090, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF111111, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFBFBFBF, 0xFF9F9F9F, 0xFF9F9F9F, 0xFF9F9F9F, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF8F8F8F, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF707070, 0xFF303030, 0xFF202020, 0xFF202020, 0xFF303030, 0xFF505050, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF212121, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF9F9F9F, 0xFF808080, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF111111, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF808080, 0xFF404040, 0xFF202020, 0xFF202020, 0xFF303030, 0xFF505050, 0xFFA0A0A0, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF303030, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF808080, 0xFF111111, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF212121, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF414141, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFA0A0A0, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF606060, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF606060, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFF909090, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF111111, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF111111, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF606060, 0xFFDFDFDF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF909090, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF404040, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF606060, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF707070, 0xFF111111, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF202020, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF404040, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF212121, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF707070, 0xFF111111, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF505050, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF212121, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFBFBFBF, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF212121, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF212121, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF909090, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF404040, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF909090, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF505050, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFA0A0A0, 0xFF111111, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF111111, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF111111, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF909090, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF909090, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF606060, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFA0A0A0, 0xFF111111, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF606060, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF909090, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF212121, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF111111, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF909090, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF111111, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF111111, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF909090, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF909090, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF505050, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF111111, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF606060, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF606060, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF606060, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF909090, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF606060, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF111111, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFA0A0A0, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF606060, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF505050, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF303030, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF303030, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF202020, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF909090, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF101010, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFA0A0A0, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF909090, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFB0B0B0, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF303030, 0xFF606060, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF909090, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF1F1F1F, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFB0B0B0, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF606060, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF707070, 0xFF313131, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF111111, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF111111, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF505050, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF212121, 0xFF909090, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFB0B0B0, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF505050, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF505050, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF909090, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFB0B0B0, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFB0B0B0, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF111111, 0xFF000000, 0xFF010101, 0xFF707070, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF7F7F7F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFAFAFAF, 0xFF9F9F9F, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF414141, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF212121, 0xFF606060, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF303030, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF101010, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF606060, 0xFF101010, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF606060, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF111111, 0xFF606060, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF111111, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF303030, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFB0B0B0, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF505050, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF909090, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF202020, 0xFF000000, 0xFF909090, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF303030, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF212121, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF909090, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF111111, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF414141, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF505050, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF111111, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF909090, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF505050, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF111111, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFA0A0A0, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF111111, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF111111, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF909090, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF606060, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF111111, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF707070, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF111111, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF111111, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF111111, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF111111, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF9F9F9F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF505050, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF303030, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF111111, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF909090, 0xFF505050, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF9F9F9F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF414141, 0xFFBFBFBF, 0xFF101010, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF303030, 0xFFBFBFBF, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF707070, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF212121, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF606060, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF111111, 0xFF404040, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF414141, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF7F7F7F, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF404040, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF212121, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF101010, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF111111, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF303030, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF707070, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFF111111, 0xFF000000, 0xFF010101, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF606060, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF404040, 0xFF808080, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF101010, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF404040, 0xFF303030, 0xFF505050, 0xFF808080, 0xFFEFEFEF, 0xFFDFDFDF, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF101010, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF606060, 0xFF303030, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF202020, 0xFF606060, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF414141, 0xFF111111, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF111111, 0xFF707070, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF606060, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF212121, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF404040, 0xFF111111, 0xFF010101, 0xFF010101, 0xFF212121, 0xFF505050, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF404040, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFF808080, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF505050, 0xFF111111, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF303030, 0xFF7F7F7F, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF303030, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFFBFBFBF, 0xFFDFDFDF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFAFAFAF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF212121, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF404040, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF212121, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFCFCFCF, 0xFFBFBFBF, 0xFFBFBFBF, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFAFAFAF, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF303030, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF111111, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF404040, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFFDFDFDF, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFFDFDFDF, 0xFFDFDFDF, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF303030, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF505050, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF202020, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF202020, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF606060, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF808080, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF101010, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF909090, 0xFF101010, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF010101, 0xFF212121, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF707070, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF212121, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF808080, 0xFF111111, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF212121, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF212121, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF111111, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF202020, 0xFFA0A0A0, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFBFBFBF, 0xFF404040, 0xFF010101, 0xFF000000, 0xFF000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF212121, 0xFFA0A0A0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFBFBFBF, 0xFF404040, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF404040, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF808080, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF202020, 0xFF606060, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF909090, 0xFF404040, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF202020, 0xFF606060, 0xFFCFCFCF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFFBFBFBF, 0xFF404040, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF606060, 0xFFBFBFBF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFEFEFEF, 0xFF808080, 0xFF111111, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF808080, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF404040, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF404040, 0xFFA0A0A0, 0xFFEFEFEF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF808080, 0xFF111111, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF202020, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF9F9F9F, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF212121, 0xFFA0A0A0, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF909090, 0xFF202020, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF404040, 0xFF707070, 0xFF808080, 0xFF808080, 0xFF606060, 0xFF303030, 0xFF111111, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF212121, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF202020, 0xFF505050, 0xFF808080, 0xFF707070, 0xFF505050, 0xFF111111, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF101010, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF111111, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF202020, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF111111, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF212121, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF101010, 0xFF404040, 0xFF606060, 0xFF606060, 0xFF606060, 0xFF606060, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF111111, 0xFF404040, 0xFF606060, 0xFF606060, 0xFF606060, 0xFF606060, 0xFF303030, 0xFF111111, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF111111, 0xFF404040, 0xFF606060, 0xFF606060, 0xFF606060, 0xFF505050, 0xFF202020, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF303030, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF202020, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF111111, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF303030, 0xFF505050, 0xFF606060, 0xFF606060, 0xFF606060, 0xFF505050, 0xFF111111, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF111111, 0xFF404040, 0xFF404040, 0xFF404040, 0xFF212121, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF303030, 0xFF505050, 0xFF606060, 0xFF606060, 0xFF606060, 0xFF505050, 0xFF202020, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFFE0E0E0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFDFDFDF, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF909090, 0xFF9F9F9F, 0xFF9F9F9F, 0xFF909090, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF000000, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101, 0xFF010101, 0xFF000000, 0xFF010101}; +static_assert(sizeof(SplashScreen) == sizeof(u32) * SplashScreenW * SplashScreenH, "Incorrect SplashScreen definition!"); \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/system_module.mk new file mode 100644 index 00000000..2f28eeee --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot/system_module.mk @@ -0,0 +1,136 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := kip + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) $(ATMOSPHERE_LIBRARIES_DIR)/../fusee/$(ATMOSPHERE_BOOT_OUT_DIR) + +CFILES := $(call FIND_SOURCE_FILES,$(SOURCES),c) +CPPFILES := $(call FIND_SOURCE_FILES,$(SOURCES),cpp) +SFILES := $(call FIND_SOURCE_FILES,$(SOURCES),s) + +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib check_fusee + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a $(ATMOSPHERE_LIBRARIES_DIR)/../fusee/$(ATMOSPHERE_BOOT_OUT_DIR)/fusee.bin + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +$(ATMOSPHERE_LIBRARIES_DIR)/../fusee/$(ATMOSPHERE_BOOT_OUT_DIR)/fusee.bin: check_fusee + @$(SILENTCMD)echo "Checked fusee." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +ifeq ($(ATMOSPHERE_CHECKED_FUSEE),1) +check_fusee: +else +check_fusee: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/../fusee -f $(ATMOSPHERE_LIBRARIES_DIR)/../fusee/Makefile $(ATMOSPHERE_MAKEFILE_TARGET) + @$(SILENTCMD)echo $(VPATH) +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +boot_power_utils.o: CXXFLAGS += --embed-dir="$(ATMOSPHERE_LIBRARIES_DIR)/../fusee/$(ATMOSPHERE_BOOT_OUT_DIR)/" + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot2/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/boot2/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot2/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)/system_module.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)/system_module.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/stratosphere/boot2/boot2.json b/Source/Atmosphere-MTC-Unlock/stratosphere/boot2/boot2.json new file mode 100644 index 00000000..e98ff3d8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot2/boot2.json @@ -0,0 +1,95 @@ +{ + "name": "boot2", + "title_id": "0x0100000000000008", + "title_id_range_min": "0x0100000000000008", + "title_id_range_max": "0x0100000000000008", + "main_thread_stack_size": "0x00004000", + "main_thread_priority": 48, + "default_cpu_id": 3, + "process_category": 0, + "is_retail": true, + "pool_partition": 2, + "is_64_bit": true, + "address_space_type": 3, + "disable_device_address_space_merge": true, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_host": ["*"], + "service_access": ["fsp-srv", "gpio", "htc", "lr", "ncm", "pm:bm", "pm:shell", "pm:info", "set:sys"], + "kernel_capabilities": [ + { + "type": "kernel_flags", + "value": { + "highest_thread_priority": 63, + "lowest_thread_priority": 24, + "lowest_cpu_id": 3, + "highest_cpu_id": 3 + } + }, + { + "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", + "svcSynchronizePreemptionState": "0x36", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcGetSystemInfo": "0x6f", + "svcCallSecureMonitor": "0x7f" + } + }, + { + "type": "min_kernel_version", + "value": "0x0030" + }, { + "type": "handle_table_size", + "value": 128 + } + ] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot2/source/boot2_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/boot2/source/boot2_main.cpp new file mode 100644 index 00000000..6a09b136 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot2/source/boot2_main.cpp @@ -0,0 +1,88 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams { + + namespace boot2 { + + namespace { + + constinit u8 g_fs_heap_memory[2_KB]; + constinit lmem::HeapHandle g_fs_heap_handle; + + void *AllocateForFs(size_t size) { + return lmem::AllocateFromExpHeap(g_fs_heap_handle, size); + } + + void DeallocateForFs(void *p, size_t size) { + AMS_UNUSED(size); + return lmem::FreeToExpHeap(g_fs_heap_handle, p); + } + + void InitializeFsHeap() { + g_fs_heap_handle = lmem::CreateExpHeap(g_fs_heap_memory, sizeof(g_fs_heap_memory), lmem::CreateOption_None); + } + + } + + } + + namespace init { + + void InitializeSystemModule() { + /* Initialize heap. */ + boot2::InitializeFsHeap(); + + /* Initialize our connection to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + + /* Initialize fs. */ + fs::InitializeForSystem(); + fs::SetAllocator(boot2::AllocateForFs, boot2::DeallocateForFs); + fs::SetEnabledAutoAbort(false); + + /* Initialize other services we need. */ + R_ABORT_UNLESS(pmbmInitialize()); + R_ABORT_UNLESS(pminfoInitialize()); + R_ABORT_UNLESS(pmshellInitialize()); + R_ABORT_UNLESS(setsysInitialize()); + gpio::Initialize(); + ncm::Initialize(); + + /* Mount the SD card. */ + R_ABORT_UNLESS(fs::MountSdCard("sdmc")); + + /* Verify that we can sanely execute. */ + ams::CheckApiVersion(); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void Main() { + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(boot2, Main)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(boot2, Main)); + + /* Launch all programs off of SYSTEM/the SD. */ + boot2::LaunchPostSdCardBootPrograms(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/boot2/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/boot2/system_module.mk new file mode 100644 index 00000000..d6de42ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/boot2/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := nsp + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/creport/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/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)/system_module.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)/system_module.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/stratosphere/creport/creport.json b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/creport.json new file mode 100644 index 00000000..99859c3d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/creport.json @@ -0,0 +1,118 @@ +{ + "name": "creport", + "title_id": "0x0100000000000036", + "title_id_range_min": "0x0100000000000036", + "title_id_range_max": "0x0100000000000036", + "main_thread_stack_size": "0x00004000", + "main_thread_priority": 44, + "default_cpu_id": 3, + "process_category": 0, + "is_retail": true, + "pool_partition": 2, + "is_64_bit": true, + "address_space_type": 3, + "disable_device_address_space_merge": true, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_host": [ + "time:s" + ], + "service_access": [ + "csrng", + "spl:", + "caps:sc", + "erpt:c", + "fatal:u", + "ns:dev", + "pgl", + "time:*", + "fsp-srv" + ], + "kernel_capabilities": [ + { + "type": "kernel_flags", + "value": { + "highest_thread_priority": 63, + "lowest_thread_priority": 24, + "lowest_cpu_id": 3, + "highest_cpu_id": 3 + } + }, + { + "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", + "svcSynchronizePreemptionState": "0x36", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcDebugActiveProcess": "0x60", + "svcGetDebugEvent": "0x63", + "svcGetThreadList": "0x66", + "svcGetDebugThreadContext": "0x67", + "svcQueryDebugProcessMemory": "0x69", + "svcReadDebugProcessMemory": "0x6a", + "svcGetDebugThreadParam": "0x6d", + "svcCallSecureMonitor": "0x7F" + } + }, + { + "type": "min_kernel_version", + "value": "0x0060" + }, + { + "type": "debug_flags", + "value": { + "allow_debug": false, + "force_debug_prod": false, + "force_debug": true + } + } + ] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_crash_report.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_crash_report.cpp new file mode 100644 index 00000000..fec6cbc6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_crash_report.cpp @@ -0,0 +1,438 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "creport_crash_report.hpp" +#include "creport_utils.hpp" + +namespace ams::creport { + + namespace { + + /* Convenience definitions. */ + constexpr size_t DyingMessageAddressOffset = 0x1C0; + static_assert(DyingMessageAddressOffset == AMS_OFFSETOF(ams::svc::aarch64::ProcessLocalRegion, dying_message_region_address)); + static_assert(DyingMessageAddressOffset == AMS_OFFSETOF(ams::svc::aarch32::ProcessLocalRegion, dying_message_region_address)); + + constexpr size_t CrashReportDataCacheSize = 256_KB; + + /* Helper functions. */ + bool TryGetCurrentTimestamp(u64 *out) { + /* Clear output. */ + *out = 0; + + /* Check if we have time service. */ + { + bool has_time_service = false; + if (R_FAILED(sm::HasService(&has_time_service, sm::ServiceName::Encode("time:s"))) || !has_time_service) { + return false; + } + } + + /* Try to get the current time. */ + { + if (R_FAILED(::timeInitialize())) { + return false; + } + ON_SCOPE_EXIT { ::timeExit(); }; + + return R_SUCCEEDED(::timeGetCurrentTime(TimeType_LocalSystemClock, out)); + } + } + + void TryCreateReportDirectories() { + fs::EnsureDirectory("sdmc:/atmosphere/crash_reports/dumps"); + fs::EnsureDirectory("sdmc:/atmosphere/fatal_reports/dumps"); + } + + constexpr const char *GetDebugExceptionString(const svc::DebugException type) { + switch (type) { + case svc::DebugException_UndefinedInstruction: + return "Undefined Instruction"; + case svc::DebugException_InstructionAbort: + return "Instruction Abort"; + case svc::DebugException_DataAbort: + return "Data Abort"; + case svc::DebugException_AlignmentFault: + return "Alignment Fault"; + case svc::DebugException_DebuggerAttached: + return "Debugger Attached"; + case svc::DebugException_BreakPoint: + return "Break Point"; + case svc::DebugException_UserBreak: + return "User Break"; + case svc::DebugException_DebuggerBreak: + return "Debugger Break"; + case svc::DebugException_UndefinedSystemCall: + return "Undefined System Call"; + case svc::DebugException_MemorySystemError: + return "System Memory Error"; + default: + return "Unknown"; + } + } + + } + + void CrashReport::Initialize() { + /* Initialize the heap. */ + m_heap_handle = lmem::CreateExpHeap(m_heap_storage, sizeof(m_heap_storage), lmem::CreateOption_None); + + /* Allocate members. */ + m_module_list = std::construct_at(static_cast<ModuleList *>(lmem::AllocateFromExpHeap(m_heap_handle, sizeof(ModuleList)))); + m_thread_list = std::construct_at(static_cast<ThreadList *>(lmem::AllocateFromExpHeap(m_heap_handle, sizeof(ThreadList)))); + m_dying_message = static_cast<u8 *>(lmem::AllocateFromExpHeap(m_heap_handle, DyingMessageSizeMax)); + if (m_dying_message != nullptr) { + std::memset(m_dying_message, 0, DyingMessageSizeMax); + } + } + + void CrashReport::BuildReport(os::ProcessId process_id, bool has_extra_info) { + m_has_extra_info = has_extra_info; + + if (this->OpenProcess(process_id)) { + /* Parse info from the crashed process. */ + this->ProcessExceptions(); + m_module_list->FindModulesFromThreadInfo(m_debug_handle, m_crashed_thread, this->Is64Bit()); + m_thread_list->ReadFromProcess(m_debug_handle, m_thread_tls_map, this->Is64Bit()); + + /* Associate module list to threads. */ + m_crashed_thread.SetModuleList(m_module_list); + m_thread_list->SetModuleList(m_module_list); + + /* Process dying message for applications. */ + if (this->IsApplication()) { + this->ProcessDyingMessage(); + } + + /* Nintendo's creport finds extra modules by looking at all threads if application, */ + /* but there's no reason for us not to always go looking. */ + for (size_t i = 0; i < m_thread_list->GetThreadCount(); i++) { + m_module_list->FindModulesFromThreadInfo(m_debug_handle, m_thread_list->GetThreadInfo(i), this->Is64Bit()); + } + + /* Cache the module base address to send to fatal. */ + if (m_module_list->GetModuleCount()) { + m_module_base_address = m_module_list->GetModuleStartAddress(0); + } + + /* Nintendo's creport saves the report to erpt here, but we'll save to SD card later. */ + } + } + + void CrashReport::GetFatalContext(::FatalCpuContext *_out) const { + static_assert(sizeof(*_out) == sizeof(ams::fatal::CpuContext)); + ams::fatal::CpuContext *out = reinterpret_cast<ams::fatal::CpuContext *>(_out); + std::memset(out, 0, sizeof(*out)); + + /* TODO: Support generating 32-bit fatal contexts? */ + out->architecture = fatal::CpuContext::Architecture_Aarch64; + out->type = static_cast<u32>(m_exception_info.type); + + for (size_t i = 0; i < fatal::aarch64::RegisterName_FP; i++) { + out->aarch64_ctx.SetRegisterValue(static_cast<fatal::aarch64::RegisterName>(i), m_crashed_thread.GetGeneralPurposeRegister(i)); + } + out->aarch64_ctx.SetRegisterValue(fatal::aarch64::RegisterName_FP, m_crashed_thread.GetFP()); + out->aarch64_ctx.SetRegisterValue(fatal::aarch64::RegisterName_LR, m_crashed_thread.GetLR()); + out->aarch64_ctx.SetRegisterValue(fatal::aarch64::RegisterName_SP, m_crashed_thread.GetSP()); + out->aarch64_ctx.SetRegisterValue(fatal::aarch64::RegisterName_PC, m_crashed_thread.GetPC()); + + out->aarch64_ctx.stack_trace_size = m_crashed_thread.GetStackTraceSize(); + for (size_t i = 0; i < out->aarch64_ctx.stack_trace_size; i++) { + out->aarch64_ctx.stack_trace[i] = m_crashed_thread.GetStackTrace(i); + } + + if (m_module_base_address != 0) { + out->aarch64_ctx.SetBaseAddress(m_module_base_address); + } + + /* For ams fatal, which doesn't use afsr0, pass program_id instead. */ + out->aarch64_ctx.SetProgramIdForAtmosphere(ncm::ProgramId{m_process_info.program_id}); + } + + void CrashReport::ProcessExceptions() { + /* Loop all debug events. */ + svc::DebugEventInfo d; + while (R_SUCCEEDED(svc::GetDebugEvent(std::addressof(d), m_debug_handle))) { + switch (d.type) { + case svc::DebugEvent_CreateProcess: + this->HandleDebugEventInfoCreateProcess(d); + break; + case svc::DebugEvent_CreateThread: + this->HandleDebugEventInfoCreateThread(d); + break; + case svc::DebugEvent_Exception: + this->HandleDebugEventInfoException(d); + break; + case svc::DebugEvent_ExitProcess: + case svc::DebugEvent_ExitThread: + break; + } + } + + /* Parse crashed thread info. */ + m_crashed_thread.ReadFromProcess(m_debug_handle, m_thread_tls_map, m_crashed_thread_id, this->Is64Bit()); + } + + void CrashReport::HandleDebugEventInfoCreateProcess(const svc::DebugEventInfo &d) { + m_process_info = d.info.create_process; + + /* On 5.0.0+, we want to parse out a dying message from application crashes. */ + if (hos::GetVersion() < hos::Version_5_0_0 || !IsApplication()) { + return; + } + + /* Parse out user data. */ + const u64 address = m_process_info.user_exception_context_address + DyingMessageAddressOffset; + u64 userdata_address = 0; + u64 userdata_size = 0; + + /* Read userdata address. */ + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(userdata_address)), m_debug_handle, address, sizeof(userdata_address)))) { + return; + } + + /* Validate userdata address. */ + if (userdata_address == 0 || userdata_address & 0xFFF) { + return; + } + + /* Read userdata size. */ + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(userdata_size)), m_debug_handle, address + sizeof(userdata_address), sizeof(userdata_size)))) { + return; + } + + /* Cap userdata size. */ + userdata_size = std::min(size_t(userdata_size), DyingMessageSizeMax); + + m_dying_message_address = userdata_address; + m_dying_message_size = userdata_size; + } + + void CrashReport::HandleDebugEventInfoCreateThread(const svc::DebugEventInfo &d) { + /* Save info on the thread's TLS address for later. */ + m_thread_tls_map.SetThreadTls(d.info.create_thread.thread_id, d.info.create_thread.tls_address); + } + + void CrashReport::HandleDebugEventInfoException(const svc::DebugEventInfo &d) { + switch (d.info.exception.type) { + case svc::DebugException_UndefinedInstruction: + m_result = creport::ResultUndefinedInstruction(); + break; + case svc::DebugException_InstructionAbort: + m_result = creport::ResultInstructionAbort(); + break; + case svc::DebugException_DataAbort: + m_result = creport::ResultDataAbort(); + break; + case svc::DebugException_AlignmentFault: + m_result = creport::ResultAlignmentFault(); + break; + case svc::DebugException_UserBreak: + m_result = creport::ResultUserBreak(); + /* Try to parse out the user break result. */ + if (hos::GetVersion() >= hos::Version_5_0_0) { + svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(m_result)), m_debug_handle, d.info.exception.specific.user_break.address, sizeof(m_result)); + } + break; + case svc::DebugException_UndefinedSystemCall: + m_result = creport::ResultUndefinedSystemCall(); + break; + case svc::DebugException_MemorySystemError: + m_result = creport::ResultMemorySystemError(); + break; + case svc::DebugException_DebuggerAttached: + case svc::DebugException_BreakPoint: + case svc::DebugException_DebuggerBreak: + return; + } + + /* Save exception info. */ + m_exception_info = d.info.exception; + m_crashed_thread_id = d.thread_id; + } + + void CrashReport::ProcessDyingMessage() { + /* Dying message is only stored starting in 5.0.0. */ + if (hos::GetVersion() < hos::Version_5_0_0) { + return; + } + + /* Validate address/size. */ + if (m_dying_message_address == 0 || m_dying_message_address & 0xFFF) { + return; + } + if (m_dying_message_size > DyingMessageSizeMax) { + return; + } + + /* Validate that the current report isn't garbage. */ + if (!IsOpen() || !IsComplete()) { + return; + } + + /* Verify that we have a dying message buffer. */ + if (m_dying_message == nullptr) { + return; + } + + /* Read the dying message. */ + svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(m_dying_message), m_debug_handle, m_dying_message_address, m_dying_message_size); + } + + void CrashReport::SaveReport(bool enable_screenshot) { + /* Try to ensure path exists. */ + TryCreateReportDirectories(); + + /* Get a timestamp. */ + u64 timestamp; + if (!TryGetCurrentTimestamp(×tamp)) { + timestamp = os::GetSystemTick().GetInt64Value(); + } + + /* Save files. */ + { + char file_path[fs::EntryNameLengthMax + 1]; + + /* Save crash report. */ + util::SNPrintf(file_path, sizeof(file_path), "sdmc:/atmosphere/crash_reports/%011lu_%016lx.log", timestamp, m_process_info.program_id); + { + /* Try to allocate data cache. */ + void * const data_cache = lmem::AllocateFromExpHeap(m_heap_handle, CrashReportDataCacheSize + os::MemoryPageSize); + ON_SCOPE_EXIT { if (data_cache != nullptr) { lmem::FreeToExpHeap(m_heap_handle, data_cache); } }; + + /* Align up the data cache. This is safe because null will align up to null. */ + void * const aligned_cache = reinterpret_cast<void *>(util::AlignUp(reinterpret_cast<uintptr_t>(data_cache), os::MemoryPageSize)); + + /* Open and save the file using the cache. */ + ScopedFile file(file_path, aligned_cache, aligned_cache != nullptr ? CrashReportDataCacheSize : 0); + if (file.IsOpen()) { + this->SaveToFile(file); + } + } + + /* Dump threads. */ + util::SNPrintf(file_path, sizeof(file_path), "sdmc:/atmosphere/crash_reports/dumps/%011lu_%016lx_thread_info.bin", timestamp, m_process_info.program_id); + { + ScopedFile file(file_path); + if (file.IsOpen()) { + m_thread_list->DumpBinary(file, m_crashed_thread.GetThreadId()); + } + } + + /* If we're open, we need to close here. */ + if (this->IsOpen()) { + this->Close(); + } + + /* Finalize our heap. */ + std::destroy_at(m_module_list); + std::destroy_at(m_thread_list); + lmem::FreeToExpHeap(m_heap_handle, m_module_list); + lmem::FreeToExpHeap(m_heap_handle, m_thread_list); + if (m_dying_message != nullptr) { + lmem::FreeToExpHeap(m_heap_handle, m_dying_message); + } + m_module_list = nullptr; + m_thread_list = nullptr; + m_dying_message = nullptr; + + /* Try to take a screenshot. */ + /* NOTE: Nintendo validates that enable_screenshot is true here, and validates that the application id is not in a blacklist. */ + /* Since we save reports only locally and do not send them via telemetry, we will skip this. */ + AMS_UNUSED(enable_screenshot); + if (hos::GetVersion() >= hos::Version_9_0_0 && this->IsApplication()) { + if (R_SUCCEEDED(capsrv::InitializeScreenShotControl())) { + ON_SCOPE_EXIT { capsrv::FinalizeScreenShotControl(); }; + + u64 jpeg_size; + if (R_SUCCEEDED(capsrv::CaptureJpegScreenshot(std::addressof(jpeg_size), m_heap_storage, sizeof(m_heap_storage), vi::LayerStack_ApplicationForDebug, TimeSpan::FromSeconds(10)))) { + util::SNPrintf(file_path, sizeof(file_path), "sdmc:/atmosphere/crash_reports/%011lu_%016lx.jpg", timestamp, m_process_info.program_id); + ScopedFile file(file_path); + if (file.IsOpen()) { + file.Write(m_heap_storage, jpeg_size); + } + } + } + } + } + } + + void CrashReport::SaveToFile(ScopedFile &file) { + file.WriteFormat("Atmosphère Crash Report (v1.7):\n"); + + file.WriteFormat("Result: 0x%X (2%03d-%04d)\n\n", m_result.GetValue(), m_result.GetModule(), m_result.GetDescription()); + + /* Process Info. */ + char name_buf[0x10] = {}; + static_assert(sizeof(name_buf) >= sizeof(m_process_info.name), "buffer overflow!"); + std::memcpy(name_buf, m_process_info.name, sizeof(m_process_info.name)); + file.WriteFormat("Process Info:\n"); + file.WriteFormat(" Process Name: %s\n", name_buf); + file.WriteFormat(" Program ID: %016lx\n", m_process_info.program_id); + file.WriteFormat(" Process ID: %016lx\n", m_process_info.process_id); + file.WriteFormat(" Process Flags: %08x\n", m_process_info.flags); + if (hos::GetVersion() >= hos::Version_5_0_0) { + file.WriteFormat(" User Exception Address: %s\n", m_module_list->GetFormattedAddressString(m_process_info.user_exception_context_address)); + } + + /* Exception Info. */ + file.WriteFormat("Exception Info:\n"); + file.WriteFormat(" Type: %s\n", GetDebugExceptionString(m_exception_info.type)); + file.WriteFormat(" Address: %s\n", m_module_list->GetFormattedAddressString(m_exception_info.address)); + switch (m_exception_info.type) { + case svc::DebugException_UndefinedInstruction: + file.WriteFormat(" Opcode: %08x\n", m_exception_info.specific.undefined_instruction.insn); + break; + case svc::DebugException_DataAbort: + case svc::DebugException_AlignmentFault: + if (m_exception_info.specific.raw != m_exception_info.address) { + file.WriteFormat(" Fault Address: %s\n", m_module_list->GetFormattedAddressString(m_exception_info.specific.raw)); + } + break; + case svc::DebugException_UndefinedSystemCall: + file.WriteFormat(" Svc Id: 0x%02x\n", m_exception_info.specific.undefined_system_call.id); + break; + case svc::DebugException_UserBreak: + file.WriteFormat(" Break Reason: 0x%x\n", m_exception_info.specific.user_break.break_reason); + file.WriteFormat(" Break Address: %s\n", m_module_list->GetFormattedAddressString(m_exception_info.specific.user_break.address)); + file.WriteFormat(" Break Size: 0x%lx\n", m_exception_info.specific.user_break.size); + break; + default: + break; + } + + /* Crashed Thread Info. */ + file.WriteFormat("Crashed Thread Info:\n"); + m_crashed_thread.SaveToFile(file); + + /* Dying Message. */ + if (hos::GetVersion() >= hos::Version_5_0_0 && m_dying_message_size != 0) { + file.WriteFormat("Dying Message Info:\n"); + file.WriteFormat(" Address: 0x%s\n", m_module_list->GetFormattedAddressString(m_dying_message_address)); + file.WriteFormat(" Size: 0x%016lx\n", m_dying_message_size); + file.DumpMemory( " Dying Message: ", m_dying_message, m_dying_message_size); + } + + /* Module Info. */ + file.WriteFormat("Module Info:\n"); + m_module_list->SaveToFile(file); + + /* Thread Info. */ + file.WriteFormat("Thread Report:\n"); + m_thread_list->SaveToFile(file); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_crash_report.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_crash_report.hpp new file mode 100644 index 00000000..36aee6b1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_crash_report.hpp @@ -0,0 +1,105 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "creport_threads.hpp" +#include "creport_modules.hpp" + +namespace ams::creport { + + class CrashReport { + private: + static constexpr size_t DyingMessageSizeMax = os::MemoryPageSize; + static constexpr size_t MemoryHeapSize = 512_KB; + static_assert(MemoryHeapSize >= DyingMessageSizeMax + sizeof(ModuleList) + sizeof(ThreadList) + os::MemoryPageSize); + private: + os::NativeHandle m_debug_handle = os::InvalidNativeHandle; + bool m_has_extra_info = true; + Result m_result = creport::ResultIncompleteReport(); + + /* Meta, used for building module/thread list. */ + ThreadTlsMap m_thread_tls_map = {}; + + /* Attach process info. */ + svc::DebugInfoCreateProcess m_process_info = {}; + u64 m_dying_message_address = 0; + u64 m_dying_message_size = 0; + u8 *m_dying_message = nullptr; + + /* Exception info. */ + svc::DebugInfoException m_exception_info = {}; + u64 m_module_base_address = 0; + u64 m_crashed_thread_id = 0; + ThreadInfo m_crashed_thread; + + /* Lists. */ + ModuleList *m_module_list = nullptr; + ThreadList *m_thread_list = nullptr; + + /* Memory heap. */ + lmem::HeapHandle m_heap_handle = nullptr; + u8 m_heap_storage[MemoryHeapSize] = {}; + public: + constexpr CrashReport() = default; + + Result GetResult() const { + return m_result; + } + + bool IsComplete() const { + return !ResultIncompleteReport::Includes(m_result); + } + + bool IsOpen() const { + return m_debug_handle != os::InvalidNativeHandle; + } + + bool IsApplication() const { + return (m_process_info.flags & svc::CreateProcessFlag_IsApplication) != 0; + } + + bool Is64Bit() const { + return (m_process_info.flags & svc::CreateProcessFlag_Is64Bit) != 0; + } + + bool IsUserBreak() const { + return m_exception_info.type == svc::DebugException_UserBreak; + } + + bool OpenProcess(os::ProcessId process_id) { + return R_SUCCEEDED(svc::DebugActiveProcess(std::addressof(m_debug_handle), process_id.value)); + } + + void Close() { + os::CloseNativeHandle(m_debug_handle); + m_debug_handle = os::InvalidNativeHandle; + } + + void Initialize(); + + void BuildReport(os::ProcessId process_id, bool has_extra_info); + void GetFatalContext(::FatalCpuContext *out) const; + void SaveReport(bool enable_screenshot); + private: + void ProcessExceptions(); + void ProcessDyingMessage(); + void HandleDebugEventInfoCreateProcess(const svc::DebugEventInfo &d); + void HandleDebugEventInfoCreateThread(const svc::DebugEventInfo &d); + void HandleDebugEventInfoException(const svc::DebugEventInfo &d); + + void SaveToFile(ScopedFile &file); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_main.cpp new file mode 100644 index 00000000..5e5d38e0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_main.cpp @@ -0,0 +1,160 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "creport_crash_report.hpp" +#include "creport_utils.hpp" + +namespace ams { + + namespace creport { + + namespace { + + constinit u8 g_fs_heap_memory[4_KB]; + lmem::HeapHandle g_fs_heap_handle; + + void *AllocateForFs(size_t size) { + return lmem::AllocateFromExpHeap(g_fs_heap_handle, size); + } + + void DeallocateForFs(void *p, size_t size) { + AMS_UNUSED(size); + return lmem::FreeToExpHeap(g_fs_heap_handle, p); + } + + void InitializeFsHeap() { + g_fs_heap_handle = lmem::CreateExpHeap(g_fs_heap_memory, sizeof(g_fs_heap_memory), lmem::CreateOption_None); + } + + } + + } + + namespace init { + + void InitializeSystemModule() { + /* Initialize heap. */ + creport::InitializeFsHeap(); + + /* Initialize our connection to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + + /* Initialize fs. */ + fs::InitializeForSystem(); + fs::SetAllocator(creport::AllocateForFs, creport::DeallocateForFs); + fs::SetEnabledAutoAbort(false); + + /* Mount the SD card. */ + R_ABORT_UNLESS(fs::MountSdCard("sdmc")); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + namespace { + + constinit creport::CrashReport g_crash_report; + + } + + void Main() { + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(creport, Main)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(creport, Main)); + + /* Get arguments. */ + const int num_args = os::GetHostArgc(); + char ** const args = os::GetHostArgv(); + + /* Validate arguments. */ + if (num_args < 2) { + return; + } + + for (auto i = 0; i < num_args; ++i) { + if (args[i] == nullptr) { + return; + } + } + + /* Parse arguments. */ + const os::ProcessId crashed_pid = creport::ParseProcessIdArgument(args[0]); + const bool has_extra_info = args[1][0] == '1'; + const bool enable_screenshot = num_args >= 3 && args[2][0] == '1'; + const bool enable_jit_debug = num_args >= 4 && args[3][0] == '1'; + + /* Initialize the crash report. */ + g_crash_report.Initialize(); + + /* Try to debug the crashed process. */ + { + g_crash_report.BuildReport(crashed_pid, has_extra_info); + ON_SCOPE_EXIT { if (g_crash_report.IsOpen()) { g_crash_report.Close(); } }; + + if (!g_crash_report.IsComplete()) { + return; + } + + /* Save report to file. */ + g_crash_report.SaveReport(enable_screenshot); + } + + /* Try to terminate the process, if we should. */ + const auto fw_ver = hos::GetVersion(); + if (fw_ver < hos::Version_11_0_0 || !enable_jit_debug) { + if (fw_ver >= hos::Version_10_0_0) { + /* Use pgl to terminate. */ + if (R_SUCCEEDED(pgl::Initialize())) { + ON_SCOPE_EXIT { pgl::Finalize(); }; + + pgl::TerminateProcess(crashed_pid); + } + } else { + /* Use ns to terminate. */ + if (R_SUCCEEDED(::nsdevInitialize())) { + ON_SCOPE_EXIT { ::nsdevExit(); }; + + nsdevTerminateProcess(crashed_pid.value); + } + } + } + + /* If we're on 5.0.0+ and an application crashed, or if we have extra info, we don't need to fatal. */ + if (fw_ver >= hos::Version_5_0_0) { + if (g_crash_report.IsApplication()) { + return; + } + } else if (has_extra_info) { + return; + } + + /* We also don't need to fatal on user break. */ + if (g_crash_report.IsUserBreak()) { + return; + } + + /* Throw fatal error. */ + { + ::FatalCpuContext ctx; + g_crash_report.GetFatalContext(std::addressof(ctx)); + fatalThrowWithContext(g_crash_report.GetResult().GetValue(), FatalPolicy_ErrorScreen, std::addressof(ctx)); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_modules.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_modules.cpp new file mode 100644 index 00000000..7ea58e0d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_modules.cpp @@ -0,0 +1,430 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "creport_modules.hpp" +#include "creport_utils.hpp" + +namespace ams::creport { + + namespace { + + /* Convenience definitions/types. */ + constexpr size_t ModulePathLengthMax = 0x200; + constexpr u8 GnuSignature[4] = {'G', 'N', 'U', 0}; + + struct ModulePath { + u32 zero; + s32 path_length; + char path[ModulePathLengthMax]; + }; + static_assert(sizeof(ModulePath) == 0x208, "ModulePath definition!"); + + struct RoDataStart { + union { + u64 deprecated_rwdata_offset; + ModulePath module_path; + }; + }; + static_assert(sizeof(RoDataStart) == sizeof(ModulePath), "RoDataStart definition!"); + + /* Globals. */ + u8 g_last_rodata_pages[2 * os::MemoryPageSize]; + + } + + void ModuleList::SaveToFile(ScopedFile &file) { + file.WriteFormat(" Number of Modules: %02zu\n", m_num_modules); + for (size_t i = 0; i < m_num_modules; i++) { + const auto& module = m_modules[i]; + file.WriteFormat(" Module %02zu:\n", i); + file.WriteFormat(" Address: %016lx-%016lx\n", module.start_address, module.end_address); + if (std::strcmp(m_modules[i].name, "") != 0) { + file.WriteFormat(" Name: %s\n", module.name); + } + file.DumpMemory(" Module Id: ", module.module_id, sizeof(module.module_id)); + } + } + + void ModuleList::FindModulesFromThreadInfo(os::NativeHandle debug_handle, const ThreadInfo &thread, bool is_64_bit) { + /* Set the debug handle, for access in other member functions. */ + m_debug_handle = debug_handle; + + /* Try to add the thread's PC. */ + this->TryAddModule(thread.GetPC(), is_64_bit); + + /* Try to add the thread's LR. */ + this->TryAddModule(thread.GetLR(), is_64_bit); + + /* Try to add all the addresses in the thread's stacktrace. */ + for (size_t i = 0; i < thread.GetStackTraceSize(); i++) { + this->TryAddModule(thread.GetStackTrace(i), is_64_bit); + } + } + + void ModuleList::TryAddModule(uintptr_t guess, bool is_64_bit) { + /* Try to locate module from guess. */ + uintptr_t base_address = 0; + if (!this->TryFindModule(std::addressof(base_address), guess, is_64_bit)) { + return; + } + + /* Check whether we already have this module. */ + for (size_t i = 0; i < m_num_modules; i++) { + if (m_modules[i].start_address <= base_address && base_address < m_modules[i].end_address) { + return; + } + } + + /* Add all contiguous modules. */ + uintptr_t cur_address = base_address; + while (m_num_modules < ModuleCountMax) { + /* Get the region extents. */ + svc::MemoryInfo mi; + svc::PageInfo pi; + if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), m_debug_handle, cur_address))) { + break; + } + + /* Parse module. */ + if (mi.permission == svc::MemoryPermission_ReadExecute) { + auto& module = m_modules[m_num_modules++]; + module.start_address = mi.base_address; + module.end_address = mi.base_address + mi.size; + GetModuleName(module.name, module.start_address, module.end_address); + GetModuleId(module.module_id, module.end_address); + + /* Default to no symbol table. */ + module.has_sym_table = false; + + if (std::strcmp(module.name, "") == 0) { + /* Some homebrew won't have a name. Add a fake one for readability. */ + util::SNPrintf(module.name, sizeof(module.name), "[%02x%02x%02x%02x]", module.module_id[0], module.module_id[1], module.module_id[2], module.module_id[3]); + } else { + /* The module has a name, and so might have a symbol table. Try to add it, if it does. */ + if (is_64_bit) { + DetectModuleSymbolTable(module); + } + } + } + + /* If we're out of readable memory, we're done reading code. */ + if (mi.state == svc::MemoryState_Free || mi.state == svc::MemoryState_Inaccessible) { + break; + } + + /* Verify we're not getting stuck in an infinite loop. */ + if (mi.size == 0 || cur_address + mi.size <= cur_address) { + break; + } + + cur_address += mi.size; + } + } + + bool ModuleList::TryFindModule(uintptr_t *out_address, uintptr_t guess, bool is_64_bit) { + AMS_UNUSED(is_64_bit); + + /* Query the memory region our guess falls in. */ + svc::MemoryInfo mi; + svc::PageInfo pi; + if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), m_debug_handle, guess))) { + return false; + } + + /* If we fall into a RW region, it may be rwdata. Query the region before it, which may be rodata or text. */ + if (mi.permission == svc::MemoryPermission_ReadWrite) { + if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), m_debug_handle, mi.base_address - 4))) { + return false; + } + } + + /* If we fall into an RO region, it may be rodata. Query the region before it, which should be text. */ + if (mi.permission == svc::MemoryPermission_Read) { + if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), m_debug_handle, mi.base_address - 4))) { + return false; + } + } + + /* We should, at this point, be looking at an executable region (text). */ + if (mi.permission != svc::MemoryPermission_ReadExecute) { + return false; + } + + /* Modules are a series of contiguous (text/rodata/rwdata) regions. */ + /* Iterate backwards until we find unmapped memory, to find the start of the set of modules loaded here. */ + while (mi.base_address > 0) { + if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), m_debug_handle, mi.base_address - 4))) { + return false; + } + + if (mi.state == svc::MemoryState_Free) { + /* We've found unmapped memory, so output the mapped memory afterwards. */ + *out_address = mi.base_address + mi.size; + return true; + } + } + + /* Something weird happened here. */ + return false; + } + + void ModuleList::GetModuleName(char *out_name, uintptr_t text_start_address, uintptr_t ro_start_address) { + /* Clear output. */ + std::memset(out_name, 0, ModuleNameLengthMax); + + /* Read module path from process memory. */ + RoDataStart rodata_start; + { + svc::MemoryInfo mi; + svc::PageInfo pi; + + /* Verify .rodata is read-only. */ + if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), m_debug_handle, ro_start_address)) || mi.permission != svc::MemoryPermission_Read) { + return; + } + + /* Calculate start of rwdata. */ + const u64 rw_start_address = mi.base_address + mi.size; + + /* Read start of .rodata. */ + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(rodata_start)), m_debug_handle, ro_start_address, sizeof(rodata_start)))) { + return; + } + + /* If data is valid under deprecated format, there's no name. */ + if (text_start_address + rodata_start.deprecated_rwdata_offset == rw_start_address) { + return; + } + + /* Also validate that we're looking at a valid name. */ + if (rodata_start.module_path.zero != 0 || rodata_start.module_path.path_length <= 0) { + return; + } + } + + + /* Start after last slash in path. */ + const char *path = rodata_start.module_path.path; + int ofs; + for (ofs = std::min<size_t>(rodata_start.module_path.path_length, sizeof(rodata_start.module_path.path)); ofs >= 0; ofs--) { + if (path[ofs] == '/' || path[ofs] == '\\') { + break; + } + } + ofs++; + + /* Copy name to output. */ + const size_t name_size = std::min(ModuleNameLengthMax, std::min<size_t>(rodata_start.module_path.path_length, sizeof(rodata_start.module_path.path)) - ofs); + std::memcpy(out_name, path + ofs, name_size); + out_name[ModuleNameLengthMax - 1] = '\x00'; + } + + void ModuleList::GetModuleId(u8 *out, uintptr_t ro_start_address) { + /* Clear output. */ + std::memset(out, 0, ModuleIdSize); + + /* Verify .rodata is read-only. */ + svc::MemoryInfo mi; + svc::PageInfo pi; + if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), m_debug_handle, ro_start_address)) || mi.permission != svc::MemoryPermission_Read) { + return; + } + + /* We want to read the last two pages of .rodata. */ + const size_t read_size = mi.size >= sizeof(g_last_rodata_pages) ? sizeof(g_last_rodata_pages) : (sizeof(g_last_rodata_pages) / 2); + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(g_last_rodata_pages), m_debug_handle, mi.base_address + mi.size - read_size, read_size))) { + return; + } + + /* Find GNU\x00 to locate start of module id (GNU build id). */ + for (int ofs = read_size - sizeof(GnuSignature) - ModuleIdSize; ofs >= 0; ofs--) { + if (std::memcmp(g_last_rodata_pages + ofs, GnuSignature, sizeof(GnuSignature)) == 0) { + std::memcpy(out, g_last_rodata_pages + ofs + sizeof(GnuSignature), ModuleIdSize); + break; + } + } + } + + void ModuleList::DetectModuleSymbolTable(ModuleInfo &module) { + /* If we already have a symbol table, no more parsing is needed. */ + if (module.has_sym_table) { + return; + } + + /* Declare temporaries. */ + u64 temp_64; + u32 temp_32; + + /* Get module state. */ + svc::MemoryInfo mi; + svc::PageInfo pi; + if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), m_debug_handle, module.start_address))) { + return; + } + + const auto module_state = mi.state; + + /* Verify .rodata is read-only with same state as .text. */ + if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), m_debug_handle, module.end_address)) || mi.permission != svc::MemoryPermission_Read || mi.state != module_state) { + return; + } + + /* We want to find the symbol table/.dynamic. */ + uintptr_t dyn_address = 0; + uintptr_t sym_tab = 0; + uintptr_t str_tab = 0; + size_t num_sym = 0; + + /* Locate .dyn using rocrt::ModuleHeader. */ + { + /* Determine the ModuleHeader offset. */ + u32 mod_offset; + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(mod_offset)), m_debug_handle, module.start_address + sizeof(u32), sizeof(u32)))) { + return; + } + + /* Read the signature. */ + constexpr u32 SignatureFieldOffset = AMS_OFFSETOF(rocrt::ModuleHeader, signature); + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(temp_32)), m_debug_handle, module.start_address + mod_offset + SignatureFieldOffset, sizeof(u32)))) { + return; + } + + /* Check that the module signature is expected. */ + if (temp_32 != rocrt::ModuleHeaderVersion) { /* MOD0 */ + return; + } + + /* Determine the dynamic offset. */ + constexpr u32 DynamicFieldOffset = AMS_OFFSETOF(rocrt::ModuleHeader, dynamic_offset); + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(temp_32)), m_debug_handle, module.start_address + mod_offset + DynamicFieldOffset, sizeof(u32)))) { + return; + } + + dyn_address = module.start_address + mod_offset + temp_32; + } + + + /* Locate tables inside .dyn. */ + for (size_t ofs = 0; /* ... */; ofs += 0x10) { + /* Read the DynamicTag. */ + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(temp_64)), m_debug_handle, dyn_address + ofs, sizeof(u64)))) { + return; + } + + if (temp_64 == 0) { + /* We're done parsing .dyn. */ + break; + } else if (temp_64 == 4) { + /* We found DT_HASH */ + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(temp_64)), m_debug_handle, dyn_address + ofs + sizeof(u64), sizeof(u64)))) { + return; + } + + /* Read nchain, to get the number of symbols. */ + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(temp_32)), m_debug_handle, module.start_address + temp_64 + sizeof(u32), sizeof(u32)))) { + return; + } + + num_sym = temp_32; + } else if (temp_64 == 5) { + /* We found DT_STRTAB */ + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(temp_64)), m_debug_handle, dyn_address + ofs + sizeof(u64), sizeof(u64)))) { + return; + } + + str_tab = module.start_address + temp_64; + } else if (temp_64 == 6) { + /* We found DT_SYMTAB */ + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(temp_64)), m_debug_handle, dyn_address + ofs + sizeof(u64), sizeof(u64)))) { + return; + } + + sym_tab = module.start_address + temp_64; + } + } + + /* Check that we found all the tables. */ + if (!(sym_tab != 0 && str_tab != 0 && num_sym != 0)) { + return; + } + + module.has_sym_table = true; + module.sym_tab = sym_tab; + module.str_tab = str_tab; + module.num_sym = static_cast<u32>(num_sym); + } + + const char *ModuleList::GetFormattedAddressString(uintptr_t address) { + /* Print default formatted string. */ + util::SNPrintf(m_address_str_buf, sizeof(m_address_str_buf), "%016lx", address); + + /* See if the address is inside a module, for pretty-printing. */ + for (size_t i = 0; i < m_num_modules; i++) { + const auto& module = m_modules[i]; + if (module.start_address <= address && address < module.end_address) { + if (module.has_sym_table) { + /* Try to locate an appropriate symbol. */ + for (size_t j = 0; j < module.num_sym; ++j) { + /* Read symbol from the module's symbol table. */ + struct { + u32 st_name; + u8 st_info; + u8 st_other; + u16 st_shndx; + u64 st_value; + u64 st_size; + } sym; + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(sym)), m_debug_handle, module.sym_tab + j * sizeof(sym), sizeof(sym)))) { + break; + } + + /* Check the symbol is valid/STT_FUNC. */ + if (sym.st_shndx == 0 || ((sym.st_shndx & 0xFF00) == 0xFF00)) { + continue; + } + if ((sym.st_info & 0xF) != 2) { + continue; + } + + /* Check the address. */ + const uintptr_t func_start = module.start_address + sym.st_value; + if (func_start <= address && address < func_start + sym.st_size) { + /* Read the symbol name. */ + const uintptr_t sym_address = module.str_tab + sym.st_name; + char sym_name[0x80]; + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(sym_name), m_debug_handle, sym_address, sizeof(sym_name)))) { + break; + } + + /* Ensure null-termination. */ + sym_name[sizeof(sym_name) - 1] = '\x00'; + + /* Print the symbol. */ + util::SNPrintf(m_address_str_buf, sizeof(m_address_str_buf), "%016lx (%s + 0x%lx) (%s + 0x%lx)", address, module.name, address - module.start_address, sym_name, address - func_start); + return m_address_str_buf; + } + } + } + + util::SNPrintf(m_address_str_buf, sizeof(m_address_str_buf), "%016lx (%s + 0x%lx)", address, module.name, address - module.start_address); + return m_address_str_buf; + } + } + + return m_address_str_buf; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_modules.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_modules.hpp new file mode 100644 index 00000000..f23d3aa0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_modules.hpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "creport_scoped_file.hpp" +#include "creport_threads.hpp" + +namespace ams::creport { + + class ModuleList { + private: + static constexpr size_t ModuleCountMax = 0x60; + static constexpr size_t ModuleNameLengthMax = 0x20; + static constexpr size_t ModuleIdSize = 0x20; + + struct ModuleInfo { + char name[ModuleNameLengthMax]; + u8 module_id[ModuleIdSize]; + u64 start_address; + u64 end_address; + bool has_sym_table; + u64 sym_tab; + u64 str_tab; + u32 num_sym; + }; + private: + os::NativeHandle m_debug_handle; + size_t m_num_modules; + ModuleInfo m_modules[ModuleCountMax]; + + /* For pretty-printing. */ + char m_address_str_buf[1_KB]; + public: + ModuleList() : m_debug_handle(os::InvalidNativeHandle), m_num_modules(0) { + std::memset(m_modules, 0, sizeof(m_modules)); + } + + size_t GetModuleCount() const { + return m_num_modules; + } + + u64 GetModuleStartAddress(size_t i) const { + return m_modules[i].start_address; + } + + void FindModulesFromThreadInfo(os::NativeHandle debug_handle, const ThreadInfo &thread, bool is_64_bit); + const char *GetFormattedAddressString(uintptr_t address); + void SaveToFile(ScopedFile &file); + private: + bool TryFindModule(uintptr_t *out_address, uintptr_t guess, bool is_64_bit); + void TryAddModule(uintptr_t guess, bool is_64_bit); + void GetModuleName(char *out_name, uintptr_t text_start, uintptr_t ro_start); + void GetModuleId(u8 *out, uintptr_t ro_start); + void DetectModuleSymbolTable(ModuleInfo &module); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_scoped_file.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_scoped_file.cpp new file mode 100644 index 00000000..1b6341c9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_scoped_file.cpp @@ -0,0 +1,124 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "creport_scoped_file.hpp" + +namespace ams::creport { + + namespace { + + /* Convenience definitions. */ + constexpr size_t MaximumLineLength = 0x20; + + constinit os::SdkMutex g_format_lock; + constinit char g_format_buffer[2 * os::MemoryPageSize]; + + } + + void ScopedFile::WriteString(const char *str) { + this->Write(str, std::strlen(str)); + } + + void ScopedFile::WriteFormat(const char *fmt, ...) { + /* Acquire exclusive access to the format buffer. */ + std::scoped_lock lk(g_format_lock); + + /* Format to the buffer. */ + { + std::va_list vl; + va_start(vl, fmt); + util::VSNPrintf(g_format_buffer, sizeof(g_format_buffer), fmt, vl); + va_end(vl); + } + + /* Write data. */ + this->WriteString(g_format_buffer); + } + + void ScopedFile::DumpMemory(const char *prefix, const void *data, size_t size) { + const u8 *data_u8 = reinterpret_cast<const u8 *>(data); + const int prefix_len = std::strlen(prefix); + size_t data_ofs = 0; + size_t remaining = size; + bool first = true; + + while (remaining) { + const size_t cur_size = std::min(MaximumLineLength, remaining); + + /* Print the line prefix. */ + if (first) { + this->Write(prefix, prefix_len); + first = false; + } else { + std::memset(g_format_buffer, ' ', prefix_len); + this->Write(g_format_buffer, prefix_len); + } + + /* Dump up to 0x20 of hex memory. */ + { + char hex[MaximumLineLength * 2 + 2] = {}; + for (size_t i = 0; i < cur_size; i++) { + hex[i * 2 + 0] = "0123456789ABCDEF"[data_u8[data_ofs] >> 4]; + hex[i * 2 + 1] = "0123456789ABCDEF"[data_u8[data_ofs] & 0xF]; + ++data_ofs; + } + hex[cur_size * 2 + 0] = '\n'; + + this->Write(hex, cur_size * 2 + 1); + } + + /* Continue. */ + remaining -= cur_size; + } + } + + + void ScopedFile::Write(const void *data, size_t size) { + /* If we're not open, we can't write. */ + if (!this->IsOpen()) { + return; + } + + /* If we have a cache, write to it. */ + if (m_cache != nullptr) { + /* Write into the cache, if we can. */ + if (m_cache_size - m_cache_offset >= size || R_SUCCEEDED(this->TryWriteCache())) { + std::memcpy(m_cache + m_cache_offset, data, size); + m_cache_offset += size; + } + } else { + /* Advance, if we write successfully. */ + if (R_SUCCEEDED(fs::WriteFile(m_file, m_offset, data, size, fs::WriteOption::None))) { + m_offset += size; + } + } + } + + Result ScopedFile::TryWriteCache() { + /* If there's no cached data, there's nothing to do. */ + R_SUCCEED_IF(m_cache_offset == 0); + + /* Try to write any cached data. */ + R_TRY(fs::WriteFile(m_file, m_offset, m_cache, m_cache_offset, fs::WriteOption::None)); + + /* Update our extents. */ + m_offset += m_cache_offset; + m_cache_offset = 0; + + R_SUCCEED(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_scoped_file.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_scoped_file.hpp new file mode 100644 index 00000000..5adcbeb0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_scoped_file.hpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::creport { + + class ScopedFile { + NON_COPYABLE(ScopedFile); + NON_MOVEABLE(ScopedFile); + private: + fs::FileHandle m_file; + s64 m_offset; + bool m_opened; + u8 *m_cache; + const size_t m_cache_size; + s64 m_cache_offset; + public: + ScopedFile(const char *path, void *cache = nullptr, size_t cache_size = 0) : m_file(), m_offset(), m_opened(false), m_cache(static_cast<u8*>(cache)), m_cache_size(cache_size), m_cache_offset(0) { + if (R_SUCCEEDED(fs::CreateFile(path, 0))) { + m_opened = R_SUCCEEDED(fs::OpenFile(std::addressof(m_file), path, fs::OpenMode_Write | fs::OpenMode_AllowAppend)); + } + } + + ~ScopedFile() { + if (m_opened) { + if (m_cache != nullptr) { + this->TryWriteCache(); + } + fs::FlushFile(m_file); + fs::CloseFile(m_file); + } + } + + bool IsOpen() const { + return m_opened; + } + + void WriteString(const char *str); + void WriteFormat(const char *fmt, ...) __attribute__((format(printf, 2, 3))); + void DumpMemory(const char *prefix, const void *data, size_t size); + + void Write(const void *data, size_t size); + private: + Result TryWriteCache(); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_threads.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_threads.cpp new file mode 100644 index 00000000..bb08d859 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_threads.cpp @@ -0,0 +1,276 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "creport_threads.hpp" +#include "creport_modules.hpp" + +namespace ams::creport { + + namespace { + + /* Convenience definitions. */ + constexpr u32 LibnxThreadVarMagic = util::FourCC<'!','T','V','$'>::Code; + constexpr u32 DumpedThreadInfoMagic = util::FourCC<'D','T','I','2'>::Code; + + /* Types. */ + template<typename T> + struct StackFrame { + T fp; + T lr; + }; + + /* Helpers. */ + template<typename T> + void ReadStackTrace(size_t *out_trace_size, u64 *out_trace, size_t max_out_trace_size, os::NativeHandle debug_handle, u64 fp) { + size_t trace_size = 0; + u64 cur_fp = fp; + + for (size_t i = 0; i < max_out_trace_size; i++) { + /* Validate the current frame. */ + if (cur_fp == 0 || (cur_fp % sizeof(T) != 0)) { + break; + } + + /* Read a new frame. */ + StackFrame<T> cur_frame; + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(cur_frame)), debug_handle, cur_fp, sizeof(cur_frame)))) { + break; + } + + /* Advance to the next frame. */ + out_trace[trace_size++] = cur_frame.lr; + cur_fp = cur_frame.fp; + } + + *out_trace_size = trace_size; + } + + } + + void ThreadList::SaveToFile(ScopedFile &file) { + file.WriteFormat("Number of Threads: %02zu\n", m_thread_count); + for (size_t i = 0; i < m_thread_count; i++) { + file.WriteFormat("Threads[%02zu]:\n", i); + m_threads[i].SaveToFile(file); + } + } + + void ThreadInfo::SaveToFile(ScopedFile &file) { + file.WriteFormat(" Thread ID: %016lx\n", m_thread_id); + if (std::strcmp(m_name, "") != 0) { + file.WriteFormat(" Thread Name: %s\n", m_name); + } + if (m_stack_top != 0) { + file.WriteFormat(" Stack Region: %016lx-%016lx\n", m_stack_bottom, m_stack_top); + } + file.WriteFormat(" Registers:\n"); + { + for (unsigned int i = 0; i <= 28; i++) { + file.WriteFormat(" X[%02u]: %s\n", i, m_module_list->GetFormattedAddressString(m_context.r[i])); + } + file.WriteFormat(" FP: %s\n", m_module_list->GetFormattedAddressString(m_context.fp)); + file.WriteFormat(" LR: %s\n", m_module_list->GetFormattedAddressString(m_context.lr)); + file.WriteFormat(" SP: %s\n", m_module_list->GetFormattedAddressString(m_context.sp)); + file.WriteFormat(" PC: %s\n", m_module_list->GetFormattedAddressString(m_context.pc)); + } + if (m_stack_trace_size != 0) { + file.WriteFormat(" Stack Trace:\n"); + for (size_t i = 0; i < m_stack_trace_size; i++) { + file.WriteFormat(" ReturnAddress[%02zu]: %s\n", i, m_module_list->GetFormattedAddressString(m_stack_trace[i])); + } + } + if (m_stack_dump_base != 0) { + file.WriteFormat(" Stack Dump: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n"); + for (size_t i = 0; i < 0x10; i++) { + const size_t ofs = i * 0x10; + file.WriteFormat(" %012lx %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + m_stack_dump_base + ofs, m_stack_dump[ofs + 0], m_stack_dump[ofs + 1], m_stack_dump[ofs + 2], m_stack_dump[ofs + 3], m_stack_dump[ofs + 4], m_stack_dump[ofs + 5], m_stack_dump[ofs + 6], m_stack_dump[ofs + 7], + m_stack_dump[ofs + 8], m_stack_dump[ofs + 9], m_stack_dump[ofs + 10], m_stack_dump[ofs + 11], m_stack_dump[ofs + 12], m_stack_dump[ofs + 13], m_stack_dump[ofs + 14], m_stack_dump[ofs + 15]); + } + } + if (m_tls_address != 0) { + file.WriteFormat(" TLS Address: %016lx\n", m_tls_address); + file.WriteFormat(" TLS Dump: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n"); + for (size_t i = 0; i < 0x10; i++) { + const size_t ofs = i * 0x10; + file.WriteFormat(" %012lx %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + m_tls_address + ofs, m_tls[ofs + 0], m_tls[ofs + 1], m_tls[ofs + 2], m_tls[ofs + 3], m_tls[ofs + 4], m_tls[ofs + 5], m_tls[ofs + 6], m_tls[ofs + 7], + m_tls[ofs + 8], m_tls[ofs + 9], m_tls[ofs + 10], m_tls[ofs + 11], m_tls[ofs + 12], m_tls[ofs + 13], m_tls[ofs + 14], m_tls[ofs + 15]); + } + } + } + + bool ThreadInfo::ReadFromProcess(os::NativeHandle debug_handle, ThreadTlsMap &tls_map, u64 thread_id, bool is_64_bit) { + /* Set thread id. */ + m_thread_id = thread_id; + + /* Verify that the thread is running or waiting. */ + { + u64 _; + u32 _thread_state; + if (R_FAILED(svc::GetDebugThreadParam(&_, &_thread_state, debug_handle, m_thread_id, svc::DebugThreadParam_State))) { + return false; + } + + const svc::ThreadState thread_state = static_cast<svc::ThreadState>(_thread_state); + if (thread_state != svc::ThreadState_Waiting && thread_state != svc::ThreadState_Running) { + return false; + } + } + + /* Get the thread context. */ + if (R_FAILED(svc::GetDebugThreadContext(std::addressof(m_context), debug_handle, m_thread_id, svc::ThreadContextFlag_All))) { + return false; + } + + /* In aarch32 mode svc::GetDebugThreadContext does not set the LR, FP, and SP registers correctly. */ + if (!is_64_bit) { + m_context.fp = m_context.r[11]; + m_context.sp = m_context.r[13]; + m_context.lr = m_context.r[14]; + } + + /* Read TLS, if present. */ + /* TODO: struct definitions for nnSdk's ThreadType/TLS Layout? */ + m_tls_address = 0; + if (tls_map.GetThreadTls(std::addressof(m_tls_address), thread_id)) { + u8 thread_tls[sizeof(svc::ThreadLocalRegion)]; + if (R_SUCCEEDED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(thread_tls), debug_handle, m_tls_address, sizeof(thread_tls)))) { + std::memcpy(m_tls, thread_tls, sizeof(m_tls)); + /* Try to detect libnx threads, and skip name parsing then. */ + if (*(reinterpret_cast<u32 *>(std::addressof(thread_tls[0x1E0]))) != LibnxThreadVarMagic) { + u8 thread_type[0x1C0]; + const u64 thread_type_addr = *(reinterpret_cast<u64 *>(std::addressof(thread_tls[0x1F8]))); + if (R_SUCCEEDED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(thread_type), debug_handle, thread_type_addr, sizeof(thread_type)))) { + /* Get the thread version. */ + const u16 thread_version = *reinterpret_cast<u16 *>(std::addressof(thread_type[0x46])); + if (thread_version == 0 || thread_version == 0xFFFF) { + /* Check thread name is actually at thread name. */ + static_assert(0x1A8 - 0x188 == NameLengthMax, "NameLengthMax definition!"); + if (*(reinterpret_cast<u64 *>(std::addressof(thread_type[0x1A8]))) == thread_type_addr + 0x188) { + std::memcpy(m_name, thread_type + 0x188, NameLengthMax); + } + } else if (thread_version == 1) { + static_assert(0x1A0 - 0x180 == NameLengthMax, "NameLengthMax definition!"); + if (*(reinterpret_cast<u64 *>(std::addressof(thread_type[0x1A0]))) == thread_type_addr + 0x180) { + std::memcpy(m_name, thread_type + 0x180, NameLengthMax); + } + } + } + } + } + } + + /* Parse stack extents and dump stack. */ + this->TryGetStackInfo(debug_handle); + + /* Dump stack trace. */ + if (is_64_bit) { + ReadStackTrace<u64>(std::addressof(m_stack_trace_size), m_stack_trace, StackTraceSizeMax, debug_handle, m_context.fp); + } else { + ReadStackTrace<u32>(std::addressof(m_stack_trace_size), m_stack_trace, StackTraceSizeMax, debug_handle, m_context.fp); + } + + return true; + } + + void ThreadInfo::TryGetStackInfo(os::NativeHandle debug_handle) { + /* Query stack region. */ + svc::MemoryInfo mi; + svc::PageInfo pi; + if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), debug_handle, m_context.sp))) { + return; + } + + /* Check if sp points into the stack. */ + if (mi.state != svc::MemoryState_Stack) { + /* It's possible that sp is below the stack... */ + if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), debug_handle, mi.base_address + mi.size)) || mi.state != svc::MemoryState_Stack) { + return; + } + } + + /* Save stack extents. */ + m_stack_bottom = mi.base_address; + m_stack_top = mi.base_address + mi.size; + + /* We always want to dump 0x100 of stack, starting from the lowest 0x10-byte aligned address below the stack pointer. */ + /* Note: if the stack pointer is below the stack bottom, we will start dumping from the stack bottom. */ + m_stack_dump_base = std::min(std::max(m_context.sp & ~0xFul, m_stack_bottom), m_stack_top - sizeof(m_stack_dump)); + + /* Try to read stack. */ + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(m_stack_dump), debug_handle, m_stack_dump_base, sizeof(m_stack_dump)))) { + m_stack_dump_base = 0; + } + } + + void ThreadInfo::DumpBinary(ScopedFile &file) { + /* Dump id and context. */ + file.Write(std::addressof(m_thread_id), sizeof(m_thread_id)); + file.Write(std::addressof(m_context), sizeof(m_context)); + + /* Dump TLS info and name. */ + file.Write(std::addressof(m_tls_address), sizeof(m_tls_address)); + file.Write(std::addressof(m_tls), sizeof(m_tls)); + file.Write(std::addressof(m_name), sizeof(m_name)); + + /* Dump stack extents and stack dump. */ + file.Write(std::addressof(m_stack_bottom), sizeof(m_stack_bottom)); + file.Write(std::addressof(m_stack_top), sizeof(m_stack_top)); + file.Write(std::addressof(m_stack_dump_base), sizeof(m_stack_dump_base)); + file.Write(std::addressof(m_stack_dump), sizeof(m_stack_dump)); + + /* Dump stack trace. */ + { + const u64 sts = m_stack_trace_size; + file.Write(std::addressof(sts), sizeof(sts)); + } + file.Write(m_stack_trace, m_stack_trace_size); + } + + void ThreadList::DumpBinary(ScopedFile &file, u64 crashed_thread_id) { + const u32 magic = DumpedThreadInfoMagic; + const u32 count = m_thread_count; + file.Write(std::addressof(magic), sizeof(magic)); + file.Write(std::addressof(count), sizeof(count)); + file.Write(std::addressof(crashed_thread_id), sizeof(crashed_thread_id)); + for (size_t i = 0; i < m_thread_count; i++) { + m_threads[i].DumpBinary(file); + } + } + + void ThreadList::ReadFromProcess(os::NativeHandle debug_handle, ThreadTlsMap &tls_map, bool is_64_bit) { + m_thread_count = 0; + + /* Get thread list. */ + s32 num_threads; + u64 thread_ids[ThreadCountMax]; + { + if (R_FAILED(svc::GetThreadList(std::addressof(num_threads), thread_ids, ThreadCountMax, debug_handle))) { + return; + } + num_threads = std::min(size_t(num_threads), ThreadCountMax); + } + + /* Parse thread infos. */ + for (s32 i = 0; i < num_threads; i++) { + if (m_threads[m_thread_count].ReadFromProcess(debug_handle, tls_map, thread_ids[i], is_64_bit)) { + m_thread_count++; + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_threads.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_threads.hpp new file mode 100644 index 00000000..a47a9aeb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_threads.hpp @@ -0,0 +1,143 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "creport_scoped_file.hpp" + +namespace ams::creport { + + /* Forward declare ModuleList class. */ + class ModuleList; + + static constexpr size_t ThreadCountMax = 0x60; + + template<size_t MaxThreadCount> + class ThreadTlsMapImpl { + private: + std::pair<u64, u64> m_map[MaxThreadCount]; + size_t m_index; + public: + constexpr ThreadTlsMapImpl() : m_map(), m_index(0) { /* ... */ } + + constexpr void ResetThreadTlsMap() { + m_index = 0; + } + + constexpr void SetThreadTls(u64 thread_id, u64 tls) { + if (m_index < util::size(m_map)) { + m_map[m_index++] = std::make_pair(thread_id, tls); + } + } + + constexpr bool GetThreadTls(u64 *out, u64 thread_id) const { + for (size_t i = 0; i < m_index; ++i) { + if (m_map[i].first == thread_id) { + *out = m_map[i].second; + return true; + } + } + return false; + } + }; + + using ThreadTlsMap = ThreadTlsMapImpl<ThreadCountMax>; + + class ThreadInfo { + private: + static constexpr size_t StackTraceSizeMax = 0x20; + static constexpr size_t NameLengthMax = 0x20; + private: + svc::ThreadContext m_context = {}; + u64 m_thread_id = 0; + u64 m_stack_top = 0; + u64 m_stack_bottom = 0; + u64 m_stack_trace[StackTraceSizeMax] = {}; + size_t m_stack_trace_size = 0; + u64 m_tls_address = 0; + u8 m_tls[0x100] = {}; + u64 m_stack_dump_base = 0; + u8 m_stack_dump[0x100] = {}; + char m_name[NameLengthMax + 1] = {}; + ModuleList *m_module_list = nullptr; + public: + u64 GetGeneralPurposeRegister(size_t i) const { + return m_context.r[i]; + } + + u64 GetPC() const { + return m_context.pc; + } + + u64 GetLR() const { + return m_context.lr; + } + + u64 GetFP() const { + return m_context.fp; + } + + u64 GetSP() const { + return m_context.sp; + } + + u64 GetThreadId() const { + return m_thread_id; + } + + size_t GetStackTraceSize() const { + return m_stack_trace_size; + } + + u64 GetStackTrace(size_t i) const { + return m_stack_trace[i]; + } + + void SetModuleList(ModuleList *ml) { + m_module_list = ml; + } + + bool ReadFromProcess(os::NativeHandle debug_handle, ThreadTlsMap &tls_map, u64 thread_id, bool is_64_bit); + void SaveToFile(ScopedFile &file); + void DumpBinary(ScopedFile &file); + private: + void TryGetStackInfo(os::NativeHandle debug_handle); + }; + + class ThreadList { + private: + size_t m_thread_count = 0; + ThreadInfo m_threads[ThreadCountMax]; + public: + size_t GetThreadCount() const { + return m_thread_count; + } + + const ThreadInfo &GetThreadInfo(size_t i) const { + return m_threads[i]; + } + + void SetModuleList(ModuleList *ml) { + for (size_t i = 0; i < m_thread_count; i++) { + m_threads[i].SetModuleList(ml); + } + } + + void ReadFromProcess(os::NativeHandle debug_handle, ThreadTlsMap &tls_map, bool is_64_bit); + void SaveToFile(ScopedFile &file); + void DumpBinary(ScopedFile &file, u64 crashed_thread_id); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_utils.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_utils.cpp new file mode 100644 index 00000000..e8bae31e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_utils.cpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "creport_utils.hpp" + +namespace ams::creport { + + os::ProcessId ParseProcessIdArgument(const char *s) { + /* Official creport uses this custom parsing logic... */ + u64 out_val = 0; + + for (unsigned int i = 0; i < 20 && s[i]; i++) { + if ('0' <= s[i] && s[i] <= '9') { + out_val *= 10; + out_val += (s[i] - '0'); + } else { + break; + } + } + + return os::ProcessId{out_val}; + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_utils.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_utils.hpp new file mode 100644 index 00000000..3f29b2b1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/source/creport_utils.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::creport { + + /* Utility functions. */ + os::ProcessId ParseProcessIdArgument(const char *s); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/creport/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/system_module.mk new file mode 100644 index 00000000..d6de42ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/creport/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := nsp + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/cs/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/cs/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/cs/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)/system_module.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)/system_module.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/stratosphere/cs/cs.json b/Source/Atmosphere-MTC-Unlock/stratosphere/cs/cs.json new file mode 100644 index 00000000..fc33a52a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/cs/cs.json @@ -0,0 +1,90 @@ +{ + "name": "cs", + "title_id": "0x0100000000000017", + "title_id_range_min": "0x0100000000000017", + "title_id_range_max": "0x0100000000000017", + "main_thread_stack_size": "0x00004000", + "main_thread_priority": 48, + "default_cpu_id": 3, + "process_category": 0, + "is_retail": true, + "pool_partition": 2, + "is_64_bit": true, + "address_space_type": 3, + "disable_device_address_space_merge": true, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_access": ["appletAE", "lr", "ldr:shel", "pgl", "ns:am2", "ns:dev", "htcs", "hid", "hid:dbg", "hid:sys", "set", "set:sys", "lr", "ldr:shel", "ncm", "arp:r", "fsp-srv", "bpc", "tc", "fatal:u", "caps:sc", "audrec:u", "auddev", "grc:d"], + "service_host": ["htc:tenv"], + "kernel_capabilities": [{ + "type": "kernel_flags", + "value": { + "highest_thread_priority": 63, + "lowest_thread_priority": 24, + "lowest_cpu_id": 3, + "highest_cpu_id": 3 + } + }, { + "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", + "svcSynchronizePreemptionState": "0x36", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcCallSecureMonitor": "0x7f" + } + }, { + "type": "min_kernel_version", + "value": "0x0030" + }, { + "type": "handle_table_size", + "value": 0 + }] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/cs/source/cs_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/cs/source/cs_main.cpp new file mode 100644 index 00000000..44e9e30f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/cs/source/cs_main.cpp @@ -0,0 +1,151 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams { + + namespace cs { + + namespace { + + alignas(os::ThreadStackAlignment) constinit u8 g_shell_stack[4_KB]; + alignas(os::ThreadStackAlignment) constinit u8 g_runner_stack[4_KB]; + + alignas(os::MemoryPageSize) constinit u8 g_heap_memory[32_KB]; + + alignas(0x40) constinit u8 g_htcs_buffer[2_KB]; + + constinit os::SdkMutex g_heap_mutex; + constinit lmem::HeapHandle g_heap_handle; + + void *Allocate(size_t size) { + std::scoped_lock lk(g_heap_mutex); + void *mem = lmem::AllocateFromExpHeap(g_heap_handle, size); + return mem; + } + + void Deallocate(void *p, size_t size) { + AMS_UNUSED(size); + + std::scoped_lock lk(g_heap_mutex); + lmem::FreeToExpHeap(g_heap_handle, p); + } + + void InitializeHeap() { + std::scoped_lock lk(g_heap_mutex); + g_heap_handle = lmem::CreateExpHeap(g_heap_memory, sizeof(g_heap_memory), lmem::CreateOption_None); + } + + } + + namespace { + + constinit ::ams::cs::CommandProcessor g_command_processor; + + constinit ::ams::scs::ShellServer g_shell_server; + constinit ::ams::scs::ShellServer g_runner_server; + + constinit sf::UnmanagedServiceObject<htc::tenv::IServiceManager, htc::tenv::ServiceManager> g_tenv_service_manager; + + void InitializeCommandProcessor() { + g_command_processor.Initialize(); + } + + void InitializeShellServers() { + g_shell_server.Initialize("iywys@$cs", g_shell_stack, sizeof(g_shell_stack), std::addressof(g_command_processor)); + g_shell_server.Start(); + + g_runner_server.Initialize("iywys@$csForRunnerTools", g_runner_stack, sizeof(g_runner_stack), std::addressof(g_command_processor)); + g_runner_server.Start(); + } + + } + + } + + namespace init { + + void InitializeSystemModule() { + /* Initialize heap. */ + cs::InitializeHeap(); + + /* Initialize our connection to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + + /* Initialize fs. */ + fs::InitializeForSystem(); + fs::SetAllocator(cs::Allocate, cs::Deallocate); + fs::SetEnabledAutoAbort(false); + + /* Initialize other services we need. */ + lr::Initialize(); + R_ABORT_UNLESS(ldr::InitializeForShell()); + R_ABORT_UNLESS(pgl::Initialize()); + R_ABORT_UNLESS(setsysInitialize()); + + /* Verify that we can sanely execute. */ + ams::CheckApiVersion(); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void Main() { + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(cs, Main)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(cs, Main)); + + /* Initialize htcs. */ + constexpr auto HtcsSocketCountMax = 6; + const size_t buffer_size = htcs::GetWorkingMemorySize(2 * HtcsSocketCountMax); + AMS_ABORT_UNLESS(sizeof(cs::g_htcs_buffer) >= buffer_size); + htcs::InitializeForSystem(cs::g_htcs_buffer, buffer_size, HtcsSocketCountMax); + + /* Initialize audio server. */ + cs::InitializeAudioServer(); + + /* Initialize remote video server. */ + cs::InitializeRemoteVideoServer(); + + /* Initialize hid server. */ + cs::InitializeHidServer(); + + /* Initialize target io server. */ + cs::InitializeTargetIoServer(); + + /* Initialize command processor. */ + cs::InitializeCommandProcessor(); + + /* Setup scs. */ + scs::InitializeShell(); + + /* Setup target environment service. */ + scs::InitializeTenvServiceManager(); + + /* Initialize the shell servers. */ + cs::InitializeShellServers(); + + /* Register htc:tenv. */ + R_ABORT_UNLESS(scs::GetServerManager()->RegisterObjectForServer(cs::g_tenv_service_manager.GetShared(), htc::tenv::ServiceName, scs::SessionCount[scs::Port_HtcTenv])); + + /* Start the scs ipc server. */ + scs::StartServer(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/cs/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/cs/system_module.mk new file mode 100644 index 00000000..d6de42ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/cs/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := nsp + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/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)/system_module.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)/system_module.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/stratosphere/dmnt.gen2/dmnt.gen2.json b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/dmnt.gen2.json new file mode 100644 index 00000000..e0d97078 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/dmnt.gen2.json @@ -0,0 +1,112 @@ +{ + "name": "dmnt.gen2", + "title_id": "0x010000000000D609", + "title_id_range_min": "0x010000000000D609", + "title_id_range_max": "0x010000000000D609", + "main_thread_stack_size": "0x00001000", + "main_thread_priority": 39, + "default_cpu_id": 3, + "process_category": 0, + "is_retail": true, + "pool_partition": 2, + "is_64_bit": true, + "address_space_type": 3, + "disable_device_address_space_merge": true, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_access": ["pm:dmnt", "ldr:dmnt", "ro:dmnt", "ns:dev", "lr", "fsp-srv", "fatal:u", "pgl", "htcs", "bsd:s", "set:sys"], + "service_host": [], + "kernel_capabilities": [{ + "type": "kernel_flags", + "value": { + "highest_thread_priority": 63, + "lowest_thread_priority": 24, + "lowest_cpu_id": 0, + "highest_cpu_id": 3 + } + }, { + "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", + "svcSynchronizePreemptionState": "0x36", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcDebugActiveProcess": "0x60", + "svcBreakDebugProcess": "0x61", + "svcTerminateDebugProcess": "0x62", + "svcGetDebugEvent": "0x63", + "svcContinueDebugEvent": "0x64", + "svcGetProcessList": "0x65", + "svcGetThreadList": "0x66", + "svcGetDebugThreadContext": "0x67", + "svcSetDebugThreadContext": "0x68", + "svcQueryDebugProcessMemory": "0x69", + "svcReadDebugProcessMemory": "0x6a", + "svcWriteDebugProcessMemory": "0x6b", + "svcSetHardwareBreakPoint": "0x6c", + "svcGetDebugThreadParam": "0x6d", + "svcCallSecureMonitor": "0x7f" + } + }, { + "type": "min_kernel_version", + "value": "0x0030" + }, { + "type": "handle_table_size", + "value": 0 + }, + { + "type": "debug_flags", + "value": { + "allow_debug": false, + "force_debug_prod": false, + "force_debug": true + } + }] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_breakpoint_manager.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_breakpoint_manager.cpp new file mode 100644 index 00000000..95622644 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_breakpoint_manager.cpp @@ -0,0 +1,54 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dmnt2_breakpoint_manager.hpp" +#include "dmnt2_debug_process.hpp" +#include "dmnt2_debug_log.hpp" + +namespace ams::dmnt { + + BreakPointManager::BreakPointManager(DebugProcess *debug_process) : BreakPointManagerBase(debug_process) { + /* ... */ + } + + void BreakPointManager::ClearStep() { + BreakPoint *bp = nullptr; + for (size_t i = 0; (bp = static_cast<BreakPoint *>(this->GetBreakPoint(i))) != nullptr; ++i) { + if (bp->m_in_use && bp->m_is_step) { + AMS_DMNT2_GDB_LOG_DEBUG("BreakPointManager::ClearStep %p 0x%lx (idx=%zu)\n", bp, bp->m_address, i); + bp->Clear(m_debug_process); + } + } + } + + Result BreakPointManager::SetBreakPoint(uintptr_t address, size_t size, bool is_step) { + /* Get a free breakpoint. */ + BreakPoint *bp = static_cast<BreakPoint *>(this->GetFreeBreakPoint()); + + /* Set the breakpoint. */ + Result result = svc::ResultOutOfHandles(); + if (bp != nullptr) { + result = bp->Set(m_debug_process, address, size, is_step); + } + + if (R_FAILED(result)) { + AMS_DMNT2_GDB_LOG_DEBUG("BreakPointManager::SetBreakPoint %p 0x%lx !!! Fail 0x%08x !!!\n", bp, address, result.GetValue()); + } + + R_RETURN(result); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_breakpoint_manager.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_breakpoint_manager.hpp new file mode 100644 index 00000000..0d8d48ba --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_breakpoint_manager.hpp @@ -0,0 +1,41 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "dmnt2_breakpoint_manager_base.hpp" + +namespace ams::dmnt { + + class DebugProcess; + + struct BreakPoint : public BreakPointBase { + bool m_is_step; + + BreakPoint() { /* ... */ } + + virtual Result Set(DebugProcess *debug_process, uintptr_t address, size_t size, bool is_step) = 0; + }; + + class BreakPointManager : public BreakPointManagerBase { + public: + explicit BreakPointManager(DebugProcess *debug_process); + + void ClearStep(); + + Result SetBreakPoint(uintptr_t address, size_t size, bool is_step); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_breakpoint_manager_base.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_breakpoint_manager_base.cpp new file mode 100644 index 00000000..0819798e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_breakpoint_manager_base.cpp @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dmnt2_breakpoint_manager_base.hpp" +#include "dmnt2_debug_process.hpp" +#include "dmnt2_debug_log.hpp" + +namespace ams::dmnt { + + BreakPointManagerBase::BreakPointManagerBase(DebugProcess *debug_process) : m_debug_process(debug_process) { + /* ... */ + } + + void BreakPointManagerBase::ClearAll() { + BreakPointBase *bp = nullptr; + for (size_t i = 0; (bp = static_cast<BreakPointBase *>(this->GetBreakPoint(i))) != nullptr; ++i) { + if (bp->m_in_use) { + bp->Clear(m_debug_process); + } + } + } + + void BreakPointManagerBase::Reset() { + BreakPointBase *bp = nullptr; + for (size_t i = 0; (bp = static_cast<BreakPointBase *>(this->GetBreakPoint(i))) != nullptr; ++i) { + bp->Reset(); + } + } + + Result BreakPointManagerBase::ClearBreakPoint(uintptr_t address, size_t size) { + BreakPointBase *bp = nullptr; + for (size_t i = 0; (bp = static_cast<BreakPointBase *>(this->GetBreakPoint(i))) != nullptr; ++i) { + if (bp->m_in_use && bp->m_address == address) { + AMS_ABORT_UNLESS(bp->m_size == size); + R_RETURN(bp->Clear(m_debug_process)); + } + } + R_SUCCEED(); + } + + BreakPointBase *BreakPointManagerBase::GetFreeBreakPoint() { + BreakPointBase *bp = nullptr; + for (size_t i = 0; (bp = static_cast<BreakPointBase *>(this->GetBreakPoint(i))) != nullptr; ++i) { + if (!bp->m_in_use) { + return bp; + } + } + return nullptr; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_breakpoint_manager_base.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_breakpoint_manager_base.hpp new file mode 100644 index 00000000..51a827a9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_breakpoint_manager_base.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::dmnt { + + class DebugProcess; + + struct BreakPointBase { + bool m_in_use; + uintptr_t m_address; + size_t m_size; + + BreakPointBase() : m_in_use(false) { /* ... */ } + + void Reset() { m_in_use = false; } + + virtual Result Clear(DebugProcess *debug_process) = 0; + }; + + class BreakPointManagerBase { + protected: + DebugProcess *m_debug_process; + public: + explicit BreakPointManagerBase(DebugProcess *debug_process); + + void ClearAll(); + void Reset(); + + Result ClearBreakPoint(uintptr_t address, size_t size); + protected: + virtual BreakPointBase *GetBreakPoint(size_t index) = 0; + BreakPointBase *GetFreeBreakPoint(); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_debug_log.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_debug_log.cpp new file mode 100644 index 00000000..0d217f96 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_debug_log.cpp @@ -0,0 +1,243 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dmnt2_debug_log.hpp" + +// TODO: This should be converted to use log manager. */ +//#define AMS_DMNT2_ENABLE_HTCS_DEBUG_LOG + +#if defined(AMS_DMNT2_ENABLE_HTCS_DEBUG_LOG) + +namespace ams::dmnt { + + namespace { + + constexpr size_t NumLogBuffers = 0x200; + constexpr size_t LogBufferSize = 0x100; + + constexpr inline auto LogThreadPriority = os::HighestThreadPriority - 2; + + constinit os::MessageQueueType g_buf_mq; + constinit os::MessageQueueType g_req_mq; + + alignas(os::ThreadStackAlignment) u8 g_log_thread_stack[os::MemoryPageSize]; + + constinit char g_log_buffers[NumLogBuffers][LogBufferSize]; + + constinit uintptr_t g_buf_mq_storage[NumLogBuffers]; + constinit uintptr_t g_req_mq_storage[NumLogBuffers]; + + constinit os::ThreadType g_log_thread; + + util::optional<char *> GetLogBuffer() { + /* Try to get a log buffer. */ + uintptr_t address; + if (os::TryReceiveMessageQueue(std::addressof(address), std::addressof(g_buf_mq))) { + return reinterpret_cast<char *>(address); + } else { + return util::nullopt; + } + } + + /* TODO: This is a hack because we don't have a proper LogManager system module. */ + /* Eventually, this should be refactored to use normal logging. */ + void DebugLogThreadFunction(void *) { + /* Loop forever, servicing our log server. */ + while (true) { + /* Get a socket. */ + int fd; + while ((fd = htcs::Socket()) == -1) { + os::SleepThread(TimeSpan::FromSeconds(1)); + } + + /* Ensure we cleanup the socket when we're done with it. */ + ON_SCOPE_EXIT { + htcs::Close(fd); + os::SleepThread(TimeSpan::FromSeconds(1)); + }; + + /* Create a sock addr for our server. */ + htcs::SockAddrHtcs addr; + addr.family = htcs::HTCS_AF_HTCS; + addr.peer_name = htcs::GetPeerNameAny(); + std::strcpy(addr.port_name.name, "iywys@$dmnt2_log"); + + /* Bind. */ + if (htcs::Bind(fd, std::addressof(addr)) == -1) { + continue; + } + + /* Listen on our port. */ + while (htcs::Listen(fd, 0) == 0) { + /* Continue accepting clients, so long as we can. */ + int client_fd; + while (true) { + /* Try to accept a client. */ + if (client_fd = htcs::Accept(fd, std::addressof(addr)); client_fd < 0) { + break; + } + + /* Handle the client. */ + while (true) { + /* Receive a log request. */ + uintptr_t log_buffer_address; + os::ReceiveMessageQueue(std::addressof(log_buffer_address), std::addressof(g_req_mq)); + + /* Ensure we manage our request properly. */ + ON_SCOPE_EXIT { os::SendMessageQueue(std::addressof(g_buf_mq), log_buffer_address); }; + + /* Send the data. */ + const char * const log_buffer = reinterpret_cast<const char *>(log_buffer_address); + if (htcs::Send(client_fd, log_buffer, std::strlen(log_buffer), 0) < 0) { + break; + } + } + + /* Close the client socket. */ + htcs::Close(client_fd); + } + } + } + } + + + } + + + void InitializeDebugLog() { + /* Initialize logging message queues. */ + os::InitializeMessageQueue(std::addressof(g_buf_mq), g_buf_mq_storage, NumLogBuffers); + os::InitializeMessageQueue(std::addressof(g_req_mq), g_req_mq_storage, NumLogBuffers); + + /* Initially make all log buffers available. */ + for (size_t i = 0; i < NumLogBuffers; ++i) { + os::SendMessageQueue(std::addressof(g_buf_mq), reinterpret_cast<uintptr_t>(g_log_buffers[i])); + } + + /* Create and start the debug log thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(g_log_thread), DebugLogThreadFunction, nullptr, g_log_thread_stack, sizeof(g_log_thread_stack), LogThreadPriority)); + + os::StartThread(std::addressof(g_log_thread)); + } + + void DebugLog(const char *prefix, const char *fmt, ...) { + /* Try to get a log buffer. */ + const auto log_buffer_holder = GetLogBuffer(); + if (!log_buffer_holder) { + return; + } + + + /* Format into the log buffer. */ + char * const log_buffer = *log_buffer_holder; + { + const auto prefix_len = std::strlen(prefix); + std::memcpy(log_buffer, prefix, prefix_len); + + { + std::va_list vl; + va_start(vl, fmt); + util::VSNPrintf(log_buffer + prefix_len, LogBufferSize - prefix_len, fmt, vl); + va_end(vl); + } + } + + /* Request to log. */ + os::SendMessageQueue(std::addressof(g_req_mq), reinterpret_cast<uintptr_t>(log_buffer)); + } + +} + +#else + +namespace ams::dmnt { + + //#define AMS_DMNT2_ENABLE_SD_CARD_DEBUG_LOG + + #if defined(AMS_DMNT2_ENABLE_SD_CARD_DEBUG_LOG) + + namespace { + + alignas(0x40) constinit u8 g_buffer[os::MemoryPageSize * 4]; + constinit lmem::HeapHandle g_debug_log_heap; + constinit fs::FileHandle g_debug_log_file; + + constinit os::SdkMutex g_fs_mutex; + constinit s64 g_fs_offset = 0; + + void *Allocate(size_t size) { + return lmem::AllocateFromExpHeap(g_debug_log_heap, size); + } + + void Deallocate(void *p, size_t size) { + AMS_UNUSED(size); + return lmem::FreeToExpHeap(g_debug_log_heap, p); + } + + } + + void InitializeDebugLog() { + g_debug_log_heap = lmem::CreateExpHeap(g_buffer, sizeof(g_buffer), lmem::CreateOption_ThreadSafe); + + fs::SetAllocator(Allocate, Deallocate); + fs::InitializeForSystem(); + fs::SetEnabledAutoAbort(false); + + R_ABORT_UNLESS(fs::MountSdCard("sdmc")); + + fs::DeleteFile("sdmc:/dmnt2.log"); + R_ABORT_UNLESS(fs::CreateFile("sdmc:/dmnt2.log", 0)); + R_ABORT_UNLESS(fs::OpenFile(std::addressof(g_debug_log_file), "sdmc:/dmnt2.log", fs::OpenMode_Write | fs::OpenMode_AllowAppend)); + } + + void DebugLog(const char *prefix, const char *fmt, ...) { + /* Do nothing. */ + char buffer[0x200]; + { + const auto prefix_len = std::strlen(prefix); + std::memcpy(buffer, prefix, prefix_len); + + std::va_list vl; + va_start(vl, fmt); + util::VSNPrintf(buffer + prefix_len, sizeof(buffer) - prefix_len, fmt, vl); + va_end(vl); + } + + const auto len = std::strlen(buffer); + + std::scoped_lock lk(g_fs_mutex); + R_ABORT_UNLESS(fs::WriteFile(g_debug_log_file, g_fs_offset, buffer, len, fs::WriteOption::Flush)); + g_fs_offset += len; + } + + #else + + void InitializeDebugLog() { + /* ... */ + } + + void DebugLog(const char *prefix, const char *fmt, ...) { + AMS_UNUSED(prefix, fmt); + } + + #endif + + + +} + + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_debug_log.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_debug_log.hpp new file mode 100644 index 00000000..071aa0cb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_debug_log.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::dmnt { + + void InitializeDebugLog(); + + void DebugLog(const char *prefix, const char *fmt, ...) __attribute((format(printf, 2, 3))); + + #define AMS_DMNT2_DEBUG_LOG(fmt, ...) ::ams::dmnt::DebugLog("[dmnt2] ", fmt, ## __VA_ARGS__) + + #define AMS_DMNT2_GDB_LOG_INFO(fmt, ...) ::ams::dmnt::DebugLog("[gdb-i] ", fmt, ## __VA_ARGS__) + #define AMS_DMNT2_GDB_LOG_WARN(fmt, ...) ::ams::dmnt::DebugLog("[gdb-w] ", fmt, ## __VA_ARGS__) + #define AMS_DMNT2_GDB_LOG_ERROR(fmt, ...) ::ams::dmnt::DebugLog("[gdb-e] ", fmt, ## __VA_ARGS__) + #define AMS_DMNT2_GDB_LOG_DEBUG(fmt, ...) ::ams::dmnt::DebugLog("[gdb-d] ", fmt, ## __VA_ARGS__) + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_debug_process.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_debug_process.cpp new file mode 100644 index 00000000..90505a4a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_debug_process.cpp @@ -0,0 +1,619 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dmnt2_debug_log.hpp" +#include "dmnt2_debug_process.hpp" + +namespace ams::dmnt { + + namespace { + + s32 SignExtend(u32 value, u32 bits) { + return static_cast<s32>(value << (32 - bits)) >> (32 - bits); + } + + } + + Result DebugProcess::Attach(os::ProcessId process_id, bool start_process) { + /* Attach to the process. */ + R_TRY(svc::DebugActiveProcess(std::addressof(m_debug_handle), process_id.value)); + + /* If necessary, start the process. */ + if (start_process) { + R_ABORT_UNLESS(pm::dmnt::StartProcess(process_id)); + } + + /* Collect initial information. */ + R_TRY(this->Start()); + + /* Get the attached modules. */ + R_TRY(this->CollectModules()); + + /* Get our process id. */ + u64 pid_value = 0; + svc::GetProcessId(std::addressof(pid_value), m_debug_handle); + + m_process_id = { pid_value }; + + /* Get process info. */ + this->CollectProcessInfo(); + + R_SUCCEED(); + } + + void DebugProcess::Detach() { + if (m_is_valid) { + m_software_breakpoints.ClearAll(); + m_hardware_breakpoints.ClearAll(); + m_hardware_watchpoints.ClearAll(); + + R_ABORT_UNLESS(svc::CloseHandle(m_debug_handle)); + m_debug_handle = svc::InvalidHandle; + } + + m_is_valid = false; + } + + Result DebugProcess::Start() { + /* Process the initial debug events. */ + s32 num_threads = 0; + bool attached = false; + while (num_threads == 0 || !attached) { + /* Wait for debug events to be available. */ + s32 dummy_index; + R_ABORT_UNLESS(svc::WaitSynchronization(std::addressof(dummy_index), std::addressof(m_debug_handle), 1, svc::WaitInfinite)); + + /* Get debug event. */ + svc::DebugEventInfo d; + R_ABORT_UNLESS(svc::GetDebugEvent(std::addressof(d), m_debug_handle)); + + /* Handle the debug event. */ + switch (d.type) { + case svc::DebugEvent_CreateProcess: + { + /* Set our create process info. */ + m_create_process_info = d.info.create_process; + + /* Cache our bools. */ + m_is_64_bit = (m_create_process_info.flags & svc::CreateProcessFlag_Is64Bit); + m_is_64_bit_address_space = (m_create_process_info.flags & svc::CreateProcessFlag_AddressSpaceMask) == svc::CreateProcessFlag_AddressSpace64Bit; + } + break; + case svc::DebugEvent_CreateThread: + { + ++num_threads; + + if (const s32 index = this->ThreadCreate(d.thread_id); index >= 0) { + const Result result = osdbg::InitializeThreadInfo(std::addressof(m_thread_infos[index]), m_debug_handle, std::addressof(m_create_process_info), std::addressof(d.info.create_thread)); + + if (R_FAILED(result)) { + AMS_DMNT2_GDB_LOG_WARN("DebugProcess::Start: InitializeThreadInfo(%lx) failed: %08x\n", d.thread_id, result.GetValue()); + } + } + } + break; + case svc::DebugEvent_ExitThread: + { + --num_threads; + this->ThreadExit(d.thread_id); + } + break; + case svc::DebugEvent_Exception: + { + if (d.info.exception.type == svc::DebugException_DebuggerAttached) { + attached = true; + } + } + break; + default: + break; + } + } + + /* Set ourselves as valid. */ + m_is_valid = true; + this->SetDebugBreaked(); + + R_SUCCEED(); + } + + s32 DebugProcess::ThreadCreate(u64 thread_id) { + for (size_t i = 0; i < ThreadCountMax; ++i) { + if (!m_thread_valid[i]) { + m_thread_valid[i] = true; + m_thread_ids[i] = thread_id; + + this->SetLastThreadId(thread_id); + this->SetLastSignal(GdbSignal_BreakpointTrap); + + ++m_thread_count; + + return i; + } + } + + return -1; + } + + void DebugProcess::ThreadExit(u64 thread_id) { + for (size_t i = 0; i < ThreadCountMax; ++i) { + if (m_thread_valid[i] && m_thread_ids[i] == thread_id) { + m_thread_valid[i] = false; + m_thread_ids[i] = 0; + + this->SetLastThreadId(thread_id); + this->SetLastSignal(GdbSignal_BreakpointTrap); + + --m_thread_count; + break; + } + } + } + + Result DebugProcess::CollectModules() { + /* Reset our module count. */ + m_module_count = 0; + + /* Traverse the address space, looking for modules. */ + uintptr_t address = 0; + + while (true) { + /* Query the current address. */ + svc::MemoryInfo memory_info; + svc::PageInfo page_info; + if (R_SUCCEEDED(svc::QueryDebugProcessMemory(std::addressof(memory_info), std::addressof(page_info), m_debug_handle, address))) { + if (memory_info.permission == svc::MemoryPermission_ReadExecute && (memory_info.state == svc::MemoryState_Code || memory_info.state == svc::MemoryState_AliasCode)) { + /* Check that we can add the module. */ + AMS_ABORT_UNLESS(m_module_count < ModuleCountMax); + + /* Get module definition. */ + auto &module = m_module_definitions[m_module_count++]; + + /* Set module address/size. */ + module.SetAddressSize(memory_info.base_address, memory_info.size); + + /* Get module name buffer. */ + char *module_name = module.GetNameBuffer(); + std::memset(module_name, 0, ModuleDefinition::PathLengthMax); + + /* Read module path. */ + struct { + u32 zero; + s32 path_length; + char path[ModuleDefinition::PathLengthMax]; + } module_path; + if (R_SUCCEEDED(this->ReadMemory(std::addressof(module_path), memory_info.base_address + memory_info.size, sizeof(module_path)))) { + if (module_path.zero == 0 && module_path.path_length > 0) { + std::memcpy(module_name, module_path.path, std::min<size_t>(ModuleDefinition::PathLengthMax, module_path.path_length)); + } + } else { + module_path.path_length = 0; + } + + /* Truncate module name. */ + module_name[ModuleDefinition::PathLengthMax - 1] = 0; + + /* Set default module name start. */ + module.SetNameStart(0); + + /* Ignore leading directories. */ + for (size_t i = 0; i < std::min<size_t>(ModuleDefinition::PathLengthMax, module_path.path_length) && module_name[i] != 0; ++i) { + if (module_name[i] == '/' || module_name[i] == '\\') { + module.SetNameStart(i + 1); + } + } + } + } + + /* Check if we're done. */ + const uintptr_t next_address = memory_info.base_address + memory_info.size; + if (memory_info.state == svc::MemoryState_Inaccessible) { + break; + } + if (next_address <= address) { + break; + } + + address = next_address; + } + + R_SUCCEED(); + } + + void DebugProcess::CollectProcessInfo() { + /* Define helper for getting process info. */ + auto CollectProcessInfoImpl = [&](os::NativeHandle handle) -> Result { + /* Collect all values. */ + R_TRY(svc::GetInfo(std::addressof(m_process_alias_address), svc::InfoType_AliasRegionAddress, handle, 0)); + R_TRY(svc::GetInfo(std::addressof(m_process_alias_size), svc::InfoType_AliasRegionSize, handle, 0)); + R_TRY(svc::GetInfo(std::addressof(m_process_heap_address), svc::InfoType_HeapRegionAddress, handle, 0)); + R_TRY(svc::GetInfo(std::addressof(m_process_heap_size), svc::InfoType_HeapRegionSize, handle, 0)); + R_TRY(svc::GetInfo(std::addressof(m_process_aslr_address), svc::InfoType_AslrRegionAddress, handle, 0)); + R_TRY(svc::GetInfo(std::addressof(m_process_aslr_size), svc::InfoType_AslrRegionSize, handle, 0)); + R_TRY(svc::GetInfo(std::addressof(m_process_stack_address), svc::InfoType_StackRegionAddress, handle, 0)); + R_TRY(svc::GetInfo(std::addressof(m_process_stack_size), svc::InfoType_StackRegionSize, handle, 0)); + + if (m_program_location.program_id == ncm::InvalidProgramId) { + R_TRY(svc::GetInfo(std::addressof(m_program_location.program_id.value), svc::InfoType_ProgramId, handle, 0)); + } + + u64 value; + R_TRY(svc::GetInfo(std::addressof(value), svc::InfoType_IsApplication, handle, 0)); + m_is_application = value != 0; + + R_SUCCEED(); + }; + + /* Get process info/status. */ + os::NativeHandle process_handle; + if (R_FAILED(pm::dmnt::AtmosphereGetProcessInfo(std::addressof(process_handle), std::addressof(m_program_location), std::addressof(m_process_override_status), m_process_id))) { + process_handle = os::InvalidNativeHandle; + m_program_location = { ncm::InvalidProgramId, }; + m_process_override_status = {}; + } + ON_SCOPE_EXIT { os::CloseNativeHandle(process_handle); }; + + /* Try collecting from our debug handle, then the process handle. */ + if (R_FAILED(CollectProcessInfoImpl(m_debug_handle)) && R_FAILED(CollectProcessInfoImpl(process_handle))) { + m_process_alias_address = 0; + m_process_alias_size = 0; + m_process_heap_address = 0; + m_process_heap_size = 0; + m_process_aslr_address = 0; + m_process_aslr_size = 0; + m_process_stack_address = 0; + m_process_stack_size = 0; + m_is_application = false; + } + } + + Result DebugProcess::GetThreadContext(svc::ThreadContext *out, u64 thread_id, u32 flags) { + R_RETURN(svc::GetDebugThreadContext(out, m_debug_handle, thread_id, flags)); + } + + Result DebugProcess::SetThreadContext(const svc::ThreadContext *ctx, u64 thread_id, u32 flags) { + R_RETURN(svc::SetDebugThreadContext(m_debug_handle, thread_id, ctx, flags)); + } + + Result DebugProcess::ReadMemory(void *dst, uintptr_t address, size_t size) { + R_RETURN(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(dst), m_debug_handle, address, size)); + } + + Result DebugProcess::WriteMemory(const void *src, uintptr_t address, size_t size) { + R_RETURN(svc::WriteDebugProcessMemory(m_debug_handle, reinterpret_cast<uintptr_t>(src), address, size)); + } + + Result DebugProcess::QueryMemory(svc::MemoryInfo *out, uintptr_t address) { + svc::PageInfo dummy; + R_RETURN(svc::QueryDebugProcessMemory(out, std::addressof(dummy), m_debug_handle, address)); + } + + Result DebugProcess::Continue() { + AMS_DMNT2_GDB_LOG_DEBUG("DebugProcess::Continue() all\n"); + + u64 thread_ids[] = { 0 }; + R_TRY(svc::ContinueDebugEvent(m_debug_handle, svc::ContinueFlag_ExceptionHandled | svc::ContinueFlag_EnableExceptionEvent | svc::ContinueFlag_ContinueAll, thread_ids, util::size(thread_ids))); + + m_continue_thread_id = 0; + m_status = ProcessStatus_Running; + + this->SetLastThreadId(0); + this->SetLastSignal(GdbSignal_Signal0); + + R_SUCCEED(); + } + + Result DebugProcess::Continue(u64 thread_id) { + AMS_DMNT2_GDB_LOG_DEBUG("DebugProcess::Continue() thread_id=%lx\n", thread_id); + + u64 thread_ids[] = { thread_id }; + R_TRY(svc::ContinueDebugEvent(m_debug_handle, svc::ContinueFlag_ExceptionHandled | svc::ContinueFlag_EnableExceptionEvent, thread_ids, util::size(thread_ids))); + + m_continue_thread_id = thread_id; + m_status = ProcessStatus_Running; + + this->SetLastThreadId(0); + this->SetLastSignal(GdbSignal_Signal0); + + R_SUCCEED(); + } + + Result DebugProcess::Step() { + AMS_DMNT2_GDB_LOG_DEBUG("DebugProcess::Step() all\n"); + R_RETURN(this->Step(this->GetLastThreadId())); + } + + Result DebugProcess::Step(u64 thread_id) { + AMS_DMNT2_GDB_LOG_DEBUG("DebugProcess::Step() thread_id=%lx\n", thread_id); + + /* Get the thread context. */ + svc::ThreadContext ctx; + R_TRY(this->GetThreadContext(std::addressof(ctx), thread_id, svc::ThreadContextFlag_Control)); + + /* Note that we're stepping. */ + m_stepping = true; + + if (m_use_hardware_single_step) { + /* Set thread single step. */ + R_TRY(this->SetThreadContext(std::addressof(ctx), thread_id, svc::ThreadContextFlag_SetSingleStep)); + } else { + /* Determine where we're stepping to. */ + u64 current_pc = ctx.pc; + u64 step_target = 0; + this->GetBranchTarget(ctx, thread_id, current_pc, step_target); + + /* Ensure we end with valid breakpoints. */ + auto bp_guard = SCOPE_GUARD { this->ClearStep(); }; + + /* Set step breakpoint on current pc. */ + /* TODO: aarch32 breakpoints. */ + if (current_pc) { + R_TRY(m_step_breakpoints.SetBreakPoint(current_pc, sizeof(u32), true)); + } + + if (step_target) { + R_TRY(m_step_breakpoints.SetBreakPoint(step_target, sizeof(u32), true)); + } + + bp_guard.Cancel(); + } + + R_SUCCEED(); + } + + void DebugProcess::ClearStep() { + /* If we should, clear our step breakpoints. */ + if (m_stepping) { + m_step_breakpoints.ClearStep(); + m_stepping = false; + } + } + + Result DebugProcess::Break() { + if (this->GetStatus() == ProcessStatus_Running) { + AMS_DMNT2_GDB_LOG_DEBUG("DebugProcess::Break\n"); + R_RETURN(svc::BreakDebugProcess(m_debug_handle)); + } else { + AMS_DMNT2_GDB_LOG_ERROR("DebugProcess::Break called on non-running process!\n"); + R_SUCCEED(); + } + } + + Result DebugProcess::Terminate() { + if (this->IsValid()) { + R_ABORT_UNLESS(svc::TerminateDebugProcess(m_debug_handle)); + this->Detach(); + } + + R_SUCCEED(); + } + + void DebugProcess::GetBranchTarget(svc::ThreadContext &ctx, u64 thread_id, u64 ¤t_pc, u64 &target) { + /* Save pc, in case we modify it. */ + const u64 pc = current_pc; + + /* Clear the target. */ + target = 0; + + /* By default, we advance by four. */ + current_pc += 4; + + /* Get the instruction where we were. */ + u32 insn = 0; + this->ReadMemory(std::addressof(insn), pc, sizeof(insn)); + + /* Handle by architecture. */ + bool is_call = false; + if (this->Is64Bit()) { + if ((insn & 0x7C000000) == 0x14000000) { + /* Unconditional branch (b/bl) */ + if (insn != 0x14000001) { + is_call = (insn & 0x80000000) == 0x80000000; + current_pc = 0; + target = SignExtend(((insn & 0x03FFFFFF) << 2), 28) + pc; + } + } else if ((insn & 0x7E000000) == 0x34000000) { + /* Compare/Branch (cbz/cbnz) */ + target = SignExtend(((insn & 0x00FFFFE0) >> 3), 21) + pc; + } else if ((insn & 0x7E000000) == 0x36000000) { + /* Test and branch (tbz/tbnz) */ + target = SignExtend(((insn & 0x0007FFE0) >> 3), 16) + pc; + } else if ((insn & 0xFF000010) == 0x54000000) { + /* Conditional branch (b.*) */ + if ((insn & 0xF) == 0xE) { + /* Unconditional. */ + current_pc = 0; + } + target = SignExtend(((insn & 0x00FFFFE0) >> 3), 21) + pc; + } else if ((insn & 0xFF8FFC1F) == 0xD60F0000) { + /* Unconditional branch */ + is_call = (insn & 0x00F00000) == 0x00300000; + if (!is_call) { + current_pc = 0; + } + + /* Get the register. */ + svc::ThreadContext new_ctx; + if (R_SUCCEEDED(this->GetThreadContext(std::addressof(new_ctx), thread_id, svc::ThreadContextFlag_Control | svc::ThreadContextFlag_General))) { + const int reg = (insn & 0x03E0) >> 5; + if (reg < 29) { + target = new_ctx.r[reg]; + } else if (reg == 29) { + target = new_ctx.fp; + } else if (reg == 30) { + target = new_ctx.lr; + } else if (reg == 31) { + target = new_ctx.sp; + } + } + } + } else { + /* TODO aarch32 branch decoding */ + AMS_UNUSED(ctx); + } + } + + Result DebugProcess::SetBreakPoint(uintptr_t address, size_t size, bool is_step) { + R_RETURN(m_software_breakpoints.SetBreakPoint(address, size, is_step)); + } + + Result DebugProcess::ClearBreakPoint(uintptr_t address, size_t size) { + m_software_breakpoints.ClearBreakPoint(address, size); + R_SUCCEED(); + } + + Result DebugProcess::SetHardwareBreakPoint(uintptr_t address, size_t size, bool is_step) { + R_RETURN(m_hardware_breakpoints.SetBreakPoint(address, size, is_step)); + } + + Result DebugProcess::ClearHardwareBreakPoint(uintptr_t address, size_t size) { + m_hardware_breakpoints.ClearBreakPoint(address, size); + R_SUCCEED(); + } + + Result DebugProcess::SetWatchPoint(u64 address, u64 size, bool read, bool write) { + R_RETURN(m_hardware_watchpoints.SetWatchPoint(address, size, read, write)); + } + + Result DebugProcess::ClearWatchPoint(u64 address, u64 size) { + R_RETURN(m_hardware_watchpoints.ClearBreakPoint(address, size)); + } + + Result DebugProcess::GetWatchPointInfo(u64 address, bool &read, bool &write) { + R_RETURN(m_hardware_watchpoints.GetWatchPointInfo(address, read, write)); + } + + bool DebugProcess::IsValidWatchPoint(u64 address, u64 size) { + return HardwareWatchPointManager::IsValidWatchPoint(address, size); + } + + Result DebugProcess::GetThreadCurrentCore(u32 *out, u64 thread_id) { + u64 dummy_value; + u32 val32 = 0; + + R_TRY(svc::GetDebugThreadParam(std::addressof(dummy_value), std::addressof(val32), m_debug_handle, thread_id, svc::DebugThreadParam_CurrentCore)); + + *out = val32; + R_SUCCEED(); + } + + Result DebugProcess::GetProcessDebugEvent(svc::DebugEventInfo *out) { + /* Get the event. */ + R_TRY(svc::GetDebugEvent(out, m_debug_handle)); + + /* Process the event. */ + switch (out->type) { + case svc::DebugEvent_CreateProcess: + { + /* Set our create process info. */ + m_create_process_info = out->info.create_process; + + /* Cache our bools. */ + m_is_64_bit = (m_create_process_info.flags & svc::CreateProcessFlag_Is64Bit); + m_is_64_bit_address_space = (m_create_process_info.flags & svc::CreateProcessFlag_AddressSpaceMask) == svc::CreateProcessFlag_AddressSpace64Bit; + } + break; + case svc::DebugEvent_CreateThread: + { + if (const s32 index = this->ThreadCreate(out->thread_id); index >= 0) { + const Result result = osdbg::InitializeThreadInfo(std::addressof(m_thread_infos[index]), m_debug_handle, std::addressof(m_create_process_info), std::addressof(out->info.create_thread)); + + if (R_FAILED(result)) { + AMS_DMNT2_GDB_LOG_WARN("DebugProcess::GetProcessDebugEvent: InitializeThreadInfo(%lx) failed: %08x\n", out->thread_id, result.GetValue()); + } + } + } + break; + case svc::DebugEvent_ExitThread: + { + this->ThreadExit(out->thread_id); + } + break; + default: + break; + } + + if (out->flags & svc::DebugEventFlag_Stopped) { + this->SetDebugBreaked(); + } + + R_SUCCEED(); + } + + u64 DebugProcess::GetLastThreadId() { + /* Select our first valid thread id. */ + if (m_last_thread_id == 0) { + for (size_t i = 0; i < ThreadCountMax; ++i) { + if (m_thread_valid[i]) { + SetLastThreadId(m_thread_ids[i]); + break; + } + } + } + + return m_last_thread_id; + } + + Result DebugProcess::GetThreadList(s32 *out_count, u64 *out_thread_ids, size_t max_count) { + s32 count = 0; + for (size_t i = 0; i < ThreadCountMax; ++i) { + if (m_thread_valid[i]) { + if (count < static_cast<s32>(max_count)) { + out_thread_ids[count++] = m_thread_ids[i]; + } + } + } + + *out_count = count; + R_SUCCEED(); + } + + Result DebugProcess::GetThreadInfoList(s32 *out_count, osdbg::ThreadInfo **out_infos, size_t max_count) { + s32 count = 0; + for (size_t i = 0; i < ThreadCountMax; ++i) { + if (m_thread_valid[i]) { + if (count < static_cast<s32>(max_count)) { + out_infos[count++] = std::addressof(m_thread_infos[i]); + } + } + } + + *out_count = count; + R_SUCCEED(); + } + + void DebugProcess::GetThreadName(char *dst, u64 thread_id) const { + for (size_t i = 0; i < ThreadCountMax; ++i) { + if (m_thread_valid[i] && m_thread_ids[i] == thread_id) { + if (R_FAILED(osdbg::GetThreadName(dst, std::addressof(m_thread_infos[i])))) { + if (m_thread_infos[i]._thread_type != 0) { + if (m_thread_infos[i]._thread_type_type == osdbg::ThreadTypeType_Libnx) { + util::TSNPrintf(dst, os::ThreadNameLengthMax, "libnx Thread_0x%010lx", reinterpret_cast<uintptr_t>(m_thread_infos[i]._thread_type)); + } else { + util::TSNPrintf(dst, os::ThreadNameLengthMax, "Thread_0x%010lx", reinterpret_cast<uintptr_t>(m_thread_infos[i]._thread_type)); + } + } else { + break; + } + } + return; + } + } + + util::TSNPrintf(dst, os::ThreadNameLengthMax, "Thread_ID=%lu", thread_id); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_debug_process.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_debug_process.hpp new file mode 100644 index 00000000..0f8685ee --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_debug_process.hpp @@ -0,0 +1,192 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "dmnt2_gdb_signal.hpp" +#include "dmnt2_module_definition.hpp" +#include "dmnt2_software_breakpoint.hpp" +#include "dmnt2_hardware_breakpoint.hpp" +#include "dmnt2_hardware_watchpoint.hpp" + +namespace ams::dmnt { + + class DebugProcess { + public: + static constexpr size_t ThreadCountMax = 0x100; + static constexpr size_t ModuleCountMax = 0x60; + + enum ProcessStatus { + ProcessStatus_DebugBreak, + ProcessStatus_Running, + ProcessStatus_Exited, + }; + + enum ContinueMode { + ContinueMode_Stopped, + ContinueMode_Continue, + ContinueMode_Step, + }; + private: + os::NativeHandle m_debug_handle{os::InvalidNativeHandle}; + s32 m_thread_count{0}; + bool m_is_valid{false}; + bool m_is_64_bit{false}; + bool m_is_64_bit_address_space{false}; + ProcessStatus m_status{ProcessStatus_DebugBreak}; + os::ProcessId m_process_id{os::InvalidProcessId}; + u64 m_last_thread_id{}; + u64 m_thread_id_override{}; + u64 m_continue_thread_id{}; + u64 m_preferred_debug_break_thread_id{}; + GdbSignal m_last_signal{}; + bool m_stepping{false}; + bool m_use_hardware_single_step{false}; + bool m_thread_valid[ThreadCountMax]{}; + u64 m_thread_ids[ThreadCountMax]{}; + osdbg::ThreadInfo m_thread_infos[ThreadCountMax]{}; + svc::DebugInfoCreateProcess m_create_process_info{}; + SoftwareBreakPointManager m_software_breakpoints; + HardwareBreakPointManager m_hardware_breakpoints; + HardwareWatchPointManager m_hardware_watchpoints; + BreakPointManager &m_step_breakpoints; + ModuleDefinition m_module_definitions[ModuleCountMax]{}; + size_t m_module_count{}; + size_t m_main_module{}; + u64 m_process_alias_address{}; + u64 m_process_alias_size{}; + u64 m_process_heap_address{}; + u64 m_process_heap_size{}; + u64 m_process_aslr_address{}; + u64 m_process_aslr_size{}; + u64 m_process_stack_address{}; + u64 m_process_stack_size{}; + ncm::ProgramLocation m_program_location{}; + cfg::OverrideStatus m_process_override_status{}; + bool m_is_application{false}; + public: + DebugProcess() : m_software_breakpoints(this), m_hardware_breakpoints(this), m_hardware_watchpoints(this), m_step_breakpoints(m_software_breakpoints) { + if (svc::IsKernelMesosphere()) { + uint64_t value = 0; + m_use_hardware_single_step = R_SUCCEEDED(::ams::svc::GetInfo(std::addressof(value), ::ams::svc::InfoType_MesosphereMeta, ::ams::svc::InvalidHandle, ::ams::svc::MesosphereMetaInfo_IsSingleStepEnabled)) && value != 0; + } + } + ~DebugProcess() { this->Detach(); } + + os::NativeHandle GetHandle() const { return m_debug_handle; } + bool IsValid() const { return m_is_valid; } + bool Is64Bit() const { return m_is_64_bit; } + bool Is64BitAddressSpace() const { return m_is_64_bit_address_space; } + size_t GetModuleCount() const { return m_module_count; } + size_t GetMainModuleIndex() const { return m_main_module; } + const char *GetModuleName(size_t ix) const { return m_module_definitions[ix].GetName(); } + uintptr_t GetModuleBaseAddress(size_t ix) const { return m_module_definitions[ix].GetAddress(); } + uintptr_t GetModuleSize(size_t ix) const { return m_module_definitions[ix].GetSize(); } + ProcessStatus GetStatus() const { return m_status; } + os::ProcessId GetProcessId() const { return m_process_id; } + + const char *GetProcessName() const { return m_create_process_info.name; } + + void SetLastSignal(GdbSignal signal) { m_last_signal = signal; } + GdbSignal GetLastSignal() const { return m_last_signal; } + + Result GetThreadList(s32 *out_count, u64 *out_thread_ids, size_t max_count); + Result GetThreadInfoList(s32 *out_count, osdbg::ThreadInfo **out_infos, size_t max_count); + + u64 GetLastThreadId(); + u64 GetThreadIdOverride() { this->GetLastThreadId(); return m_thread_id_override; } + + u64 GetPreferredDebuggerBreakThreadId() { return m_preferred_debug_break_thread_id; } + + void SetLastThreadId(u64 tid) { + m_last_thread_id = tid; + m_thread_id_override = tid; + } + + void SetThreadIdOverride(u64 tid) { + m_thread_id_override = tid; + m_preferred_debug_break_thread_id = tid; + } + + void SetDebugBreaked() { + m_status = ProcessStatus_DebugBreak; + } + + u64 GetAliasRegionAddress() const { return m_process_alias_address; } + u64 GetAliasRegionSize() const { return m_process_alias_size; } + u64 GetHeapRegionAddress() const { return m_process_heap_address; } + u64 GetHeapRegionSize() const { return m_process_heap_size; } + u64 GetAslrRegionAddress() const { return m_process_aslr_address; } + u64 GetAslrRegionSize() const { return m_process_aslr_size; } + u64 GetStackRegionAddress() const { return m_process_stack_address; } + u64 GetStackRegionSize() const { return m_process_stack_size; } + + const ncm::ProgramLocation &GetProgramLocation() const { return m_program_location; } + const cfg::OverrideStatus &GetOverrideStatus() const { return m_process_override_status; } + + bool IsApplication() const { return m_is_application; } + public: + Result Attach(os::ProcessId process_id, bool start_process = false); + void Detach(); + + Result GetThreadContext(svc::ThreadContext *out, u64 thread_id, u32 flags); + Result SetThreadContext(const svc::ThreadContext *ctx, u64 thread_id, u32 flags); + + Result ReadMemory(void *dst, uintptr_t address, size_t size); + Result WriteMemory(const void *src, uintptr_t address, size_t size); + + Result QueryMemory(svc::MemoryInfo *out, uintptr_t address); + + Result Continue(); + Result Continue(u64 thread_id); + Result Step(); + Result Step(u64 thread_id); + void ClearStep(); + + Result Break(); + + Result Terminate(); + + Result SetBreakPoint(uintptr_t address, size_t size, bool is_step); + Result ClearBreakPoint(uintptr_t address, size_t size); + + Result SetHardwareBreakPoint(uintptr_t address, size_t size, bool is_step); + Result ClearHardwareBreakPoint(uintptr_t address, size_t size); + + Result SetWatchPoint(u64 address, u64 size, bool read, bool write); + Result ClearWatchPoint(u64 address, u64 size); + Result GetWatchPointInfo(u64 address, bool &read, bool &write); + + static bool IsValidWatchPoint(u64 address, u64 size); + + Result GetThreadCurrentCore(u32 *out, u64 thread_id); + + Result GetProcessDebugEvent(svc::DebugEventInfo *out); + + void GetBranchTarget(svc::ThreadContext &ctx, u64 thread_id, u64 ¤t_pc, u64 &target); + + Result CollectModules(); + + void GetThreadName(char *dst, u64 thread_id) const; + private: + Result Start(); + + void CollectProcessInfo(); + + s32 ThreadCreate(u64 thread_id); + void ThreadExit(u64 thread_id); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_packet_io.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_packet_io.cpp new file mode 100644 index 00000000..da1e0272 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_packet_io.cpp @@ -0,0 +1,194 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dmnt2_gdb_packet_io.hpp" +#include "dmnt2_debug_log.hpp" + +namespace ams::dmnt { + + namespace { + + constexpr char BreakCharacter = '\x03'; /* ctrl-c */ + + constexpr int DecodeHex(char c) { + if ('a' <= c && c <= 'f') { + return 10 + (c - 'a'); + } else if ('A' <= c && c <= 'F') { + return 10 + (c - 'A'); + } else if ('0' <= c && c <= '9') { + return 0 + (c - '0'); + } else { + return -1; + } + } + + constexpr char EncodeHex(u8 v) { + return "0123456789abcdef"[v & 0xF]; + } + + } + + void GdbPacketIo::SendPacket(bool *out_break, const char *src, TransportSession *session) { + /* Default to not breaked. */ + *out_break = false; + + /* Send a packet. */ + while (true) { + std::scoped_lock lk(m_mutex); + + size_t len = 0; + u8 checksum = 0; + + while (src[len] != 0) { + checksum += static_cast<u8>(src[len++]); + } + + char buffer[1 + GdbPacketBufferSize + 4]; + buffer[0] = '$'; + std::memcpy(buffer + 1, src, len); + buffer[1 + len] = '#'; + buffer[2 + len] = EncodeHex(checksum >> 4); + buffer[3 + len] = EncodeHex(checksum >> 0); + buffer[4 + len] = 0; + + if (session->PutString(buffer) < 0) { + /* Log (truncated) copy of packet. */ + AMS_DMNT2_GDB_LOG_ERROR("Failed to send packet %s\n", buffer); + return; + } + + if (m_no_ack) { + return; + } + + /* Check to see if we need to retransmit. */ + bool retransmit = false; + do { + if (const auto char_holder = session->GetChar(); char_holder) { + switch (*char_holder) { + case BreakCharacter: + *out_break = true; + return; + case '+': + return; + case '-': + retransmit = true; + break; + default: + break; + } + } else { + /* Log (truncated) copy of packet. */ + AMS_DMNT2_GDB_LOG_ERROR("Failed to receive ack for %s\n", buffer); + return; + } + } while (!retransmit); + } + } + + char *GdbPacketIo::ReceivePacket(bool *out_break, char *dst, size_t size, TransportSession *session) { + /* Default to not breaked. */ + *out_break = false; + + /* Receive a packet. */ + while (true) { + /* Wait for data to be available. */ + if (!session->WaitToBeReadable()) { + return nullptr; + } + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Verify that we still have data immediately available. */ + if (!session->WaitToBeReadable(0)) { + continue; + } + + /* Prepare to parse. */ + enum class State { + Initial, + PacketData, + ChecksumHigh, + ChecksumLow + }; + + State state = State::Initial; + u8 checksum = 0; + int csum_high = -1, csum_low = -1; + size_t count = 0; + + /* Read characters. */ + while (true) { + if (const auto char_holder = session->GetChar(); char_holder) { + const auto c = *char_holder; + + switch (state) { + case State::Initial: + if (c == '$') { + state = State::PacketData; + } else if (c == BreakCharacter) { + *out_break = true; + return nullptr; + } + break; + case State::PacketData: + /* TODO: Escaped characters. */ + if (c == '#') { + dst[count] = 0; + state = State::ChecksumHigh; + } else { + AMS_ABORT_UNLESS(count < size - 1); + checksum += static_cast<u8>(c); + dst[count++] = c; + } + break; + case State::ChecksumHigh: + csum_high = DecodeHex(c); + state = State::ChecksumLow; + break; + case State::ChecksumLow: + csum_low = DecodeHex(c); + + if (m_no_ack) { + return dst; + } else { + const u8 expectsum = (static_cast<u8>(csum_high) << 4) | (static_cast<u8>(csum_low) << 0); + + if (csum_high < 0 || csum_low < 0 || checksum != expectsum) { + /* Request retransmission. */ + state = State::Initial; + checksum = 0; + csum_high = -1; + csum_low = -1; + count = 0; + session->PutChar('-'); + } else { + session->PutChar('+'); + return dst; + } + } + + break; + } + } else { + return nullptr; + } + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_packet_io.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_packet_io.hpp new file mode 100644 index 00000000..50f782fb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_packet_io.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "dmnt2_transport_session.hpp" + +namespace ams::dmnt { + + static constexpr size_t GdbPacketBufferSize = 32_KB; + + class GdbPacketIo { + private: + os::SdkMutex m_mutex; + bool m_no_ack; + public: + GdbPacketIo() : m_mutex(), m_no_ack(false) { /* ... */ } + + void SetNoAck() { m_no_ack = true; } + + void SendPacket(bool *out_break, const char *src, TransportSession *session); + char *ReceivePacket(bool *out_break, char *dst, size_t size, TransportSession *session); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_server.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_server.cpp new file mode 100644 index 00000000..82572bab --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_server.cpp @@ -0,0 +1,89 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dmnt2_debug_log.hpp" +#include "dmnt2_transport_layer.hpp" +#include "dmnt2_gdb_server.hpp" +#include "dmnt2_gdb_server_impl.hpp" + +namespace ams::dmnt { + + namespace { + + constexpr size_t ServerThreadStackSize = util::AlignUp(4 * GdbPacketBufferSize + os::MemoryPageSize, os::ThreadStackAlignment); + + alignas(os::ThreadStackAlignment) constinit u8 g_server_thread_stack[ServerThreadStackSize]; + alignas(os::ThreadStackAlignment) constinit u8 g_events_thread_stack[util::AlignUp(2 * GdbPacketBufferSize + os::MemoryPageSize, os::ThreadStackAlignment)]; + + constinit os::ThreadType g_server_thread; + + constinit util::TypedStorage<GdbServerImpl> g_gdb_server; + + void GdbServerThreadFunction(void *) { + /* Loop forever, servicing our gdb server. */ + while (true) { + /* Get a socket. */ + int fd; + while ((fd = transport::Socket()) == -1) { + os::SleepThread(TimeSpan::FromSeconds(1)); + } + + /* Ensure we cleanup the socket when we're done with it. */ + ON_SCOPE_EXIT { + transport::Close(fd); + os::SleepThread(TimeSpan::FromSeconds(1)); + }; + + /* Bind. */ + if (transport::Bind(fd, transport::PortName_GdbServer) == -1) { + continue; + } + + /* Listen on our port. */ + while (transport::Listen(fd, 0) == 0) { + /* Continue accepting clients, so long as we can. */ + int client_fd; + while (true) { + /* Try to accept a client. */ + if (client_fd = transport::Accept(fd); client_fd < 0) { + break; + } + + { + /* Create gdb server for the socket. */ + util::ConstructAt(g_gdb_server, client_fd, g_events_thread_stack, sizeof(g_events_thread_stack)); + ON_SCOPE_EXIT { util::DestroyAt(g_gdb_server); }; + + /* Process for the server. */ + util::GetReference(g_gdb_server).LoopProcess(); + } + + /* Close the client socket. */ + transport::Close(client_fd); + } + } + } + } + + } + + void InitializeGdbServer() { + /* Create and start gdb server threads. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(g_server_thread), GdbServerThreadFunction, nullptr, g_server_thread_stack, sizeof(g_server_thread_stack), os::HighestThreadPriority - 1)); + os::StartThread(std::addressof(g_server_thread)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_server.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_server.hpp new file mode 100644 index 00000000..4fd4e1c3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_server.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::dmnt { + + void InitializeGdbServer(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_server_impl.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_server_impl.cpp new file mode 100644 index 00000000..757a237a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_server_impl.cpp @@ -0,0 +1,2494 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dmnt2_debug_log.hpp" +#include "dmnt2_gdb_server_impl.hpp" + +namespace ams::dmnt { + + namespace { + + constexpr const u32 SdkBreakPoint = 0xE7FFFFFF; + constexpr const u32 SdkBreakPointMask = 0xFFFFFFFF; + + constexpr const u32 ArmBreakPoint = 0xE7FFDEFE; + constexpr const u32 ArmBreakPointMask = 0xFFFFFFFF; + + constexpr const u32 A64BreakPoint = 0xD4200000; + constexpr const u32 A64BreakPointMask = 0xFFE0001F; + + constexpr const u32 A64Halt = 0xD4400000; + constexpr const u32 A64HaltMask = 0xFFE0001F; + + constexpr const u32 A32BreakPoint = 0xE1200070; + constexpr const u32 A32BreakPointMask = 0xFFF000F0; + + constexpr const u32 T16BreakPoint = 0x0000BE00; + constexpr const u32 T16BreakPointMask = 0x0000FF00; + + constexpr const char TargetXmlAarch64[] = + "l<?xml version=\"1.0\"?>" + "<!DOCTYPE target SYSTEM \"gdb-target.dtd\">" + "<target>" + "<architecture>aarch64</architecture>" + "<xi:include href=\"aarch64-core.xml\"/>" + "<xi:include href=\"aarch64-fpu.xml\"/>" + "</target>"; + + constexpr const char Aarch64CoreXml[] = + "l<?xml version=\"1.0\"?>\n" + "<!DOCTYPE feature SYSTEM \"gdb-target.dtd\">\n" + "<feature name=\"org.gnu.gdb.aarch64.core\">\n" + "\t<reg name=\"x0\" bitsize=\"64\"/>\n" + "\t<reg name=\"x1\" bitsize=\"64\"/>\n" + "\t<reg name=\"x2\" bitsize=\"64\"/>\n" + "\t<reg name=\"x3\" bitsize=\"64\"/>\n" + "\t<reg name=\"x4\" bitsize=\"64\"/>\n" + "\t<reg name=\"x5\" bitsize=\"64\"/>\n" + "\t<reg name=\"x6\" bitsize=\"64\"/>\n" + "\t<reg name=\"x7\" bitsize=\"64\"/>\n" + "\t<reg name=\"x8\" bitsize=\"64\"/>\n" + "\t<reg name=\"x9\" bitsize=\"64\"/>\n" + "\t<reg name=\"x10\" bitsize=\"64\"/>\n" + "\t<reg name=\"x11\" bitsize=\"64\"/>\n" + "\t<reg name=\"x12\" bitsize=\"64\"/>\n" + "\t<reg name=\"x13\" bitsize=\"64\"/>\n" + "\t<reg name=\"x14\" bitsize=\"64\"/>\n" + "\t<reg name=\"x15\" bitsize=\"64\"/>\n" + "\t<reg name=\"x16\" bitsize=\"64\"/>\n" + "\t<reg name=\"x17\" bitsize=\"64\"/>\n" + "\t<reg name=\"x18\" bitsize=\"64\"/>\n" + "\t<reg name=\"x19\" bitsize=\"64\"/>\n" + "\t<reg name=\"x20\" bitsize=\"64\"/>\n" + "\t<reg name=\"x21\" bitsize=\"64\"/>\n" + "\t<reg name=\"x22\" bitsize=\"64\"/>\n" + "\t<reg name=\"x23\" bitsize=\"64\"/>\n" + "\t<reg name=\"x24\" bitsize=\"64\"/>\n" + "\t<reg name=\"x25\" bitsize=\"64\"/>\n" + "\t<reg name=\"x26\" bitsize=\"64\"/>\n" + "\t<reg name=\"x27\" bitsize=\"64\"/>\n" + "\t<reg name=\"x28\" bitsize=\"64\"/>\n" + "\t<reg name=\"x29\" bitsize=\"64\"/>\n" + "\t<reg name=\"x30\" bitsize=\"64\"/>\n" + "\t<reg name=\"sp\" bitsize=\"64\" type=\"data_ptr\"/>\n" + "\t<reg name=\"pc\" bitsize=\"64\" type=\"code_ptr\"/>\n" + "\t<flags id=\"cpsr_flags\" size=\"4\">\n" + "\t\t<field name=\"SP\" start=\"0\" end=\"0\" /><!-- Stack Pointer selection -->\n" + "\t\t<field name=\"EL\" start=\"2\" end=\"3\" /><!-- Exception level -->\n" + "\t\t<field name=\"nRW\" start=\"4\" end=\"4\" /><!-- Execution state (not Register Width) 0=64-bit 1-32-bit -->\n" + "\n" + "\t\t<field name=\"F\" start=\"6\" end=\"6\" /><!-- FIQ mask -->\n" + "\t\t<field name=\"I\" start=\"7\" end=\"7\" /><!-- IRQ mask -->\n" + "\t\t<field name=\"A\" start=\"8\" end=\"8\" /><!-- SError interrupt mask -->\n" + "\t\t<field name=\"D\" start=\"9\" end=\"9\" /><!-- Debug mask bit -->\n" + "\n" + "\t\t<field name=\"SSBS\" start=\"12\" end=\"12\"/><!-- Speculative Store Bypass Safe -->\n" + "\n" + "\t\t<field name=\"IL\" start=\"20\" end=\"20\"/><!-- Illegal execution state -->\n" + "\t\t<field name=\"SS\" start=\"21\" end=\"21\"/><!-- Software step -->\n" + "\t\t<field name=\"PAN\" start=\"22\" end=\"22\"/><!-- v8.1 Privileged Access Never -->\n" + "\t\t<field name=\"UAO\" start=\"23\" end=\"23\"/><!-- v8.2 User Access Override -->\n" + "\t\t<field name=\"DIT\" start=\"24\" end=\"24\"/><!-- v8.4 Data Independent Timing -->\n" + "\t\t<field name=\"TCO\" start=\"25\" end=\"25\"/><!-- v8.5 Tag Check Override -->\n" + "\n" + "\t\t<field name=\"V\" start=\"28\" end=\"28\"/><!-- oVerflow condition flag -->\n" + "\t\t<field name=\"C\" start=\"29\" end=\"29\"/><!-- Carry condition flag -->\n" + "\t\t<field name=\"Z\" start=\"30\" end=\"30\"/><!-- Zero condition flag -->\n" + "\t\t<field name=\"N\" start=\"31\" end=\"31\"/><!-- Negative condition flag -->\n" + "\t</flags>\n" + "\t<reg name=\"cpsr\" bitsize=\"32\" type=\"cpsr_flags\"/>\n" + "</feature>"; + + constexpr const char Aarch64FpuXml[] = + "l<?xml version=\"1.0\"?>\n" + "<!DOCTYPE feature SYSTEM \"gdb-target.dtd\">\n" + "<feature name=\"org.gnu.gdb.aarch64.fpu\">\n" + "\t<vector id=\"v2d\" type=\"ieee_double\" count=\"2\"/>\n" + "\t<vector id=\"v2u\" type=\"uint64\" count=\"2\"/>\n" + "\t<vector id=\"v2i\" type=\"int64\" count=\"2\"/>\n" + "\t<vector id=\"v4f\" type=\"ieee_single\" count=\"4\"/>\n" + "\t<vector id=\"v4u\" type=\"uint32\" count=\"4\"/>\n" + "\t<vector id=\"v4i\" type=\"int32\" count=\"4\"/>\n" + "\t<!-- <vector id=\"v8f\" type=\"ieee_half\" count=\"8\"/> -->\n" + "\t<vector id=\"v8u\" type=\"uint16\" count=\"8\"/>\n" + "\t<vector id=\"v8i\" type=\"int16\" count=\"8\"/>\n" + "\t<vector id=\"v16u\" type=\"uint8\" count=\"16\"/>\n" + "\t<vector id=\"v16i\" type=\"int8\" count=\"16\"/>\n" + "\t<vector id=\"v1u\" type=\"uint128\" count=\"1\"/>\n" + "\t<vector id=\"v1i\" type=\"int128\" count=\"1\"/>\n" + "\t<union id=\"vnd\">\n" + "\t\t<field name=\"f\" type=\"v2d\"/>\n" + "\t\t<field name=\"u\" type=\"v2u\"/>\n" + "\t\t<field name=\"s\" type=\"v2i\"/>\n" + "\t</union>\n" + "\t<union id=\"vns\">\n" + "\t\t<field name=\"f\" type=\"v4f\"/>\n" + "\t\t<field name=\"u\" type=\"v4u\"/>\n" + "\t\t<field name=\"s\" type=\"v4i\"/>\n" + "\t</union>\n" + "\t<union id=\"vnh\">\n" + "\t\t<!-- <field name=\"f\" type=\"v8f\"/> -->\n" + "\t\t<field name=\"u\" type=\"v8u\"/>\n" + "\t\t<field name=\"s\" type=\"v8i\"/>\n" + "\t</union>\n" + "\t<union id=\"vnb\">\n" + "\t\t<field name=\"u\" type=\"v16u\"/>\n" + "\t\t<field name=\"s\" type=\"v16i\"/>\n" + "\t</union>\n" + "\t<union id=\"vnq\">\n" + "\t\t<field name=\"u\" type=\"v1u\"/>\n" + "\t\t<field name=\"s\" type=\"v1i\"/>\n" + "\t</union>\n" + "\t<union id=\"aarch64v\">\n" + "\t\t<field name=\"d\" type=\"vnd\"/>\n" + "\t\t<field name=\"s\" type=\"vns\"/>\n" + "\t\t<field name=\"h\" type=\"vnh\"/>\n" + "\t\t<field name=\"b\" type=\"vnb\"/>\n" + "\t\t<field name=\"q\" type=\"vnq\"/>\n" + "\t</union>\n" + "\t<reg name=\"v0\" bitsize=\"128\" type=\"aarch64v\" regnum=\"34\"/>\n" + "\t<reg name=\"v1\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v2\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v3\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v4\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v5\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v6\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v7\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v8\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v9\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v10\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v11\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v12\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v13\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v14\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v15\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v16\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v17\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v18\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v19\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v20\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v21\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v22\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v23\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v24\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v25\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v26\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v27\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v28\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v29\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v30\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"v31\" bitsize=\"128\" type=\"aarch64v\"/>\n" + "\t<reg name=\"fpsr\" bitsize=\"32\"/>\n" + "\t<reg name=\"fpcr\" bitsize=\"32\"/>\n" + "</feature>"; + + constexpr const char TargetXmlAarch32[] = + "l<?xml version=\"1.0\"?>" + "<!DOCTYPE target SYSTEM \"gdb-target.dtd\">" + "<target>" + "<architecture>arm</architecture>" + "<xi:include href=\"arm-core.xml\"/>" + "<xi:include href=\"arm-vfp.xml\"/>" + "</target>"; + + constexpr const char ArmCoreXml[] = + "l<?xml version=\"1.0\"?>\n" + "<!DOCTYPE target SYSTEM \"gdb-target.dtd\">\n" + "<feature name=\"org.gnu.gdb.arm.core\">\n" + "\t<reg name=\"r0\" bitsize=\"32\"/>\n" + "\t<reg name=\"r1\" bitsize=\"32\"/>\n" + "\t<reg name=\"r2\" bitsize=\"32\"/>\n" + "\t<reg name=\"r3\" bitsize=\"32\"/>\n" + "\t<reg name=\"r4\" bitsize=\"32\"/>\n" + "\t<reg name=\"r5\" bitsize=\"32\"/>\n" + "\t<reg name=\"r6\" bitsize=\"32\"/>\n" + "\t<reg name=\"r7\" bitsize=\"32\"/>\n" + "\t<reg name=\"r8\" bitsize=\"32\"/>\n" + "\t<reg name=\"r9\" bitsize=\"32\"/>\n" + "\t<reg name=\"r10\" bitsize=\"32\"/>\n" + "\t<reg name=\"r11\" bitsize=\"32\"/>\n" + "\t<reg name=\"r12\" bitsize=\"32\"/>\n" + "\t<reg name=\"sp\" bitsize=\"32\" type=\"data_ptr\"/>\n" + "\t<reg name=\"lr\" bitsize=\"32\"/>\n" + "\t<reg name=\"pc\" bitsize=\"32\" type=\"code_ptr\"/>\n" + "\t<reg name=\"cpsr\" bitsize=\"32\" regnum=\"25\"/>\n" + "</feature>\n"; + + constexpr const char ArmVfpXml[] = + "l<?xml version=\"1.0\"?>\n" + "<!DOCTYPE target SYSTEM \"gdb-target.dtd\">\n" + "<feature name=\"org.gnu.gdb.arm.vfp\">\n" + "\t<reg name=\"d0\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d1\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d2\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d3\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d4\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d5\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d6\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d7\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d8\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d9\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d10\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d11\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d12\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d13\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d14\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d15\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d16\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d17\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d18\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d19\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d20\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d21\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d22\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d23\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d24\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d25\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d26\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d27\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d28\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d29\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d30\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"d31\" bitsize=\"64\" type=\"ieee_double\"/>\n" + "\t<reg name=\"fpscr\" bitsize=\"32\" type=\"int\" group=\"float\"/>\n" + "</feature>\n"; + + constexpr const char * const MemoryStateNames[svc::MemoryState_Coverage + 1] = { + "----- Free -----", /* svc::MemoryState_Free */ + "Io ", /* svc::MemoryState_Io */ + "Static ", /* svc::MemoryState_Static */ + "Code ", /* svc::MemoryState_Code */ + "CodeData ", /* svc::MemoryState_CodeData */ + "Normal ", /* svc::MemoryState_Normal */ + "Shared ", /* svc::MemoryState_Shared */ + "Alias ", /* svc::MemoryState_Alias */ + "AliasCode ", /* svc::MemoryState_AliasCode */ + "AliasCodeData ", /* svc::MemoryState_AliasCodeData */ + "Ipc ", /* svc::MemoryState_Ipc */ + "Stack ", /* svc::MemoryState_Stack */ + "ThreadLocal ", /* svc::MemoryState_ThreadLocal */ + "Transfered ", /* svc::MemoryState_Transfered */ + "SharedTransfered", /* svc::MemoryState_SharedTransfered */ + "SharedCode ", /* svc::MemoryState_SharedCode */ + "Inaccessible ", /* svc::MemoryState_Inaccessible */ + "NonSecureIpc ", /* svc::MemoryState_NonSecureIpc */ + "NonDeviceIpc ", /* svc::MemoryState_NonDeviceIpc */ + "Kernel ", /* svc::MemoryState_Kernel */ + "GeneratedCode ", /* svc::MemoryState_GeneratedCode */ + "CodeOut ", /* svc::MemoryState_CodeOut */ + "Coverage ", /* svc::MemoryState_Coverage */ + }; + + constexpr const char *GetMemoryStateName(svc::MemoryState state) { + if (static_cast<size_t>(state) < util::size(MemoryStateNames)) { + return MemoryStateNames[static_cast<size_t>(state)]; + } else { + return "Unknown "; + } + } + + constexpr const char *GetMemoryPermissionString(const svc::MemoryInfo &info) { + if (info.state == svc::MemoryState_Free) { + return " "; + } else { + switch (info.permission) { + case svc::MemoryPermission_ReadExecute: + return "r-x"; + case svc::MemoryPermission_Read: + return "r--"; + case svc::MemoryPermission_ReadWrite: + return "rw-"; + default: + return "---"; + } + } + } + + bool ParsePrefix(char *&packet, const char *prefix) { + const auto len = std::strlen(prefix); + if (std::strncmp(packet, prefix, len) == 0) { + packet += len; + return true; + } else { + return false; + } + } + + void AppendReplyOk(char * &reply, char * const reply_end) { + AMS_UNUSED(reply_end); + std::strcpy(reply, "OK"); + reply += 2; + } + + void AppendReplyError(char * reply, char * const reply_end, const char *err) { + AMS_UNUSED(reply_end); + std::strcpy(reply, err); + reply += std::strlen(err); + } + + void AppendReplyFormat(char * &reply, char * const reply_end, const char *fmt, ...) __attribute__((format(printf, 3, 4))); + + void AppendReplyFormat(char * &reply, char * const reply_end, const char *fmt, ...) { + if (const auto remaining_size = reply_end - reply; remaining_size > 0) { + std::va_list vl; + va_start(vl, fmt); + reply += util::VSNPrintf(reply, remaining_size, fmt, vl); + va_end(vl); + } + } + + constexpr int DecodeHex(char c) { + if ('a' <= c && c <= 'f') { + return 10 + (c - 'a'); + } else if ('A' <= c && c <= 'F') { + return 10 + (c - 'A'); + } else if ('0' <= c && c <= '9') { + return 0 + (c - '0'); + } else { + return -1; + } + } + + constexpr u64 DecodeHex(const char *s) { + u64 value = 0; + + while (true) { + const char c = *(s++); + + if (int v = DecodeHex(c); v >= 0) { + value <<= 4; + value |= v & 0xF; + } else { + break; + } + } + + return value; + } + + void MemoryToHex(char *dst, char * const dst_end, const void *mem, size_t size) { + const u8 *mem_u8 = static_cast<const u8 *>(mem); + + while (size-- > 0 && dst < dst_end - 1) { + const u8 v = *(mem_u8++); + *(dst++) = "0123456789abcdef"[v >> 4]; + *(dst++) = "0123456789abcdef"[v & 0xF]; + } + *dst = 0; + } + + void HexToMemory(void *dst, const char *src, size_t size) { + u8 *dst_u8 = static_cast<u8 *>(dst); + + for (size_t i = 0; i < size; ++i) { + u8 v = DecodeHex(*(src++)) << 4; + v |= DecodeHex(*(src++)) & 0xF; + + *(dst_u8++) = v; + } + } + + void ParseOffsetLength(const char *packet, u32 &offset, u32 &length) { + /* Default to zero. */ + offset = 0; + length = 0; + + bool parsed_offset = false; + while (*packet) { + const char c = *(packet++); + if (c == ',') { + parsed_offset = true; + } else if (auto hex = DecodeHex(c); hex >= 0) { + if (parsed_offset) { + length <<= 4; + length |= hex; + } else { + offset <<= 4; + offset |= hex; + } + } + } + + AMS_DMNT2_GDB_LOG_DEBUG("Offset/Length %x/%x\n", offset, length); + } + + s32 FindThreadIdIndex(u64 *thread_ids, s32 num_threads, u64 thread_id) { + for (auto i = 0; i < num_threads; ++i) { + if (thread_ids[i] == thread_id) { + return i; + } + } + return 0; + } + + void SetGdbRegister32(char * &dst, char * const dst_end, u32 value) { + if (value != 0) { + AppendReplyFormat(dst, dst_end, "%08x", util::ConvertToBigEndian(value)); + } else { + AppendReplyFormat(dst, dst_end, "0*\"00"); + } + } + + void SetGdbRegister64(char * &dst, char * const dst_end, u64 value) { + if (value != 0) { + AppendReplyFormat(dst, dst_end, "%016lx", util::ConvertToBigEndian(value)); + } else { + AppendReplyFormat(dst, dst_end, "0*,"); + } + } + + void SetGdbRegister128(char * &dst, char * const dst_end, u128 value) { + if (value != 0) { + AppendReplyFormat(dst, dst_end, "%016lx%016lx", util::ConvertToBigEndian(static_cast<u64>(value >> 0)), util::ConvertToBigEndian(static_cast<u64>(value >> BITSIZEOF(u64)))); + } else { + AppendReplyFormat(dst, dst_end, "0*<"); + } + } + + void SetGdbRegisterPacket(char * &dst, char * const dst_end, const svc::ThreadContext &thread_context, bool is_64_bit) { + /* Clear packet. */ + dst[0] = 0; + + if (is_64_bit) { + /* Copy general purpose registers. */ + for (size_t i = 0; i < util::size(thread_context.r); ++i) { + SetGdbRegister64(dst, dst_end, thread_context.r[i]); + } + + /* Copy special registers. */ + SetGdbRegister64(dst, dst_end, thread_context.fp); + SetGdbRegister64(dst, dst_end, thread_context.lr); + SetGdbRegister64(dst, dst_end, thread_context.sp); + SetGdbRegister64(dst, dst_end, thread_context.pc); + + SetGdbRegister32(dst, dst_end, thread_context.pstate); + + /* Copy FPU registers. */ + for (size_t i = 0; i < util::size(thread_context.v); ++i) { + SetGdbRegister128(dst, dst_end, thread_context.v[i]); + } + + SetGdbRegister32(dst, dst_end, thread_context.fpsr); + SetGdbRegister32(dst, dst_end, thread_context.fpcr); + } else { + /* Copy general purpose registers. */ + for (size_t i = 0; i < 15; ++i) { + SetGdbRegister32(dst, dst_end, thread_context.r[i]); + } + + /* Copy special registers. */ + SetGdbRegister32(dst, dst_end, thread_context.pc); + SetGdbRegister32(dst, dst_end, thread_context.pstate); + + /* Copy FPU registers. */ + for (size_t i = 0; i < util::size(thread_context.v) / 2; ++i) { + SetGdbRegister128(dst, dst_end, thread_context.v[i]); + } + + const u32 fpscr = (thread_context.fpsr & 0xF80000FF) | (thread_context.fpcr & 0x07FFFF00); + SetGdbRegister32(dst, dst_end, fpscr); + } + } + + void SetGdbRegisterPacket(char * &dst, char * const dst_end, const svc::ThreadContext &thread_context, u64 reg_num, bool is_64_bit) { + /* Clear packet. */ + dst[0] = 0; + + union { + u32 v32; + u64 v64; + u128 v128; + } v; + size_t reg_size = 0; + + if (is_64_bit) { + if (reg_num < 29) { + v.v64 = thread_context.r[reg_num]; + reg_size = sizeof(u64); + } else if (reg_num == 29) { + v.v64 = thread_context.fp; + reg_size = sizeof(u64); + } else if (reg_num == 30) { + v.v64 = thread_context.lr; + reg_size = sizeof(u64); + } else if (reg_num == 31) { + v.v64 = thread_context.sp; + reg_size = sizeof(u64); + } else if (reg_num == 32) { + v.v64 = thread_context.pc; + reg_size = sizeof(u64); + } else if (reg_num == 33) { + v.v32 = thread_context.pstate; + reg_size = sizeof(u32); + } else if (reg_num < 66) { + v.v128 = thread_context.v[reg_num - 34]; + reg_size = sizeof(u128); + } else if (reg_num == 66) { + v.v32 = thread_context.fpsr; + reg_size = sizeof(u32); + } else if (reg_num == 67) { + v.v32 = thread_context.fpcr; + reg_size = sizeof(u32); + } + } else { + if (reg_num < 15) { + v.v32 = thread_context.r[reg_num]; + reg_size = sizeof(u32); + } else if (reg_num == 15) { + v.v32 = thread_context.pc; + reg_size = sizeof(u32); + } else if (reg_num == 25) { + v.v32 = thread_context.pstate; + reg_size = sizeof(u32); + } else if (26 <= reg_num && reg_num < 58) { + const union { + u64 v64[2]; + u128 v128; + } fpu_reg = { .v128 = thread_context.v[(reg_num - 26) / 2] }; + + v.v64 = fpu_reg.v64[(reg_num - 26) % 2]; + reg_size = sizeof(u64); + } else if (reg_num == 58) { + const u32 fpscr = (thread_context.fpsr & 0xF80000FF) | (thread_context.fpcr & 0x07FFFF00); + + v.v32 = fpscr; + reg_size = sizeof(u32); + } + } + + switch (reg_size) { + case sizeof(u32): SetGdbRegister32 (dst, dst_end, v.v32 ); break; + case sizeof(u64): SetGdbRegister64 (dst, dst_end, v.v64 ); break; + case sizeof(u128): SetGdbRegister128(dst, dst_end, v.v128); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + u64 Aarch32RegisterToAarch64Register(u64 reg_num) { + if (reg_num < 15) { + return reg_num; + } else if (reg_num == 15) { + return 32; + } else if (reg_num == 25) { + return 33; + } else if (26 <= reg_num && reg_num <= 57) { + return 34 + (reg_num - 26); + } else if (reg_num == 58) { + return 66; + } else { + AMS_ABORT("Unknown register number %lu\n", reg_num); + } + } + + template<typename IntType> requires std::unsigned_integral<IntType> + util::optional<IntType> ParseGdbRegister(const char *&src) { + union { + IntType v; + u8 bytes[sizeof(v)]; + } reg; + for (size_t i = 0; i < util::size(reg.bytes); ++i) { + const auto high = DecodeHex(*(src++)); + const auto low = DecodeHex(*(src++)); + if (high < 0 || low < 0) { + return util::nullopt; + } + reg.bytes[i] = (high << 4) | low; + } + + return reg.v; + } + + ALWAYS_INLINE util::optional<u32> ParseGdbRegister32(const char *&src) { return ParseGdbRegister<u32>(src); } + ALWAYS_INLINE util::optional<u64> ParseGdbRegister64(const char *&src) { return ParseGdbRegister<u64>(src); } + ALWAYS_INLINE util::optional<u128> ParseGdbRegister128(const char *&src) { return ParseGdbRegister<u128>(src); } + + void ParseGdbRegisterPacket(svc::ThreadContext &thread_context, const char *src, bool is_64_bit) { + if (is_64_bit) { + /* Copy general purpose registers. */ + for (size_t i = 0; i < util::size(thread_context.r); ++i) { + if (const auto v = ParseGdbRegister64(src); v.has_value()) { + thread_context.r[i] = *v; + } else { + return; + } + } + + /* Copy special registers. */ + if (const auto v = ParseGdbRegister64(src); v.has_value()) { + thread_context.fp = *v; + } else { + return; + } + + if (const auto v = ParseGdbRegister64(src); v.has_value()) { + thread_context.lr = *v; + } else { + return; + } + + if (const auto v = ParseGdbRegister64(src); v.has_value()) { + thread_context.sp = *v; + } else { + return; + } + + if (const auto v = ParseGdbRegister64(src); v.has_value()) { + thread_context.pc = *v; + } else { + return; + } + + if (const auto v = ParseGdbRegister32(src); v.has_value()) { + thread_context.pstate = *v; + } else { + return; + } + + /* Copy FPU registers. */ + for (size_t i = 0; i < util::size(thread_context.v); ++i) { + if (const auto v = ParseGdbRegister128(src); v.has_value()) { + thread_context.v[i] = *v; + } else { + return; + } + } + + if (const auto v = ParseGdbRegister32(src); v.has_value()) { + thread_context.fpsr = *v; + } else { + return; + } + + if (const auto v = ParseGdbRegister32(src); v.has_value()) { + thread_context.fpcr = *v; + } else { + return; + } + } else { + /* Copy general purpose registers. */ + for (size_t i = 0; i < 15; ++i) { + if (const auto v = ParseGdbRegister32(src); v.has_value()) { + thread_context.r[i] = *v; + } else { + return; + } + } + + /* Copy special registers. */ + if (const auto v = ParseGdbRegister32(src); v.has_value()) { + thread_context.pc = *v; + } else { + return; + } + + if (const auto v = ParseGdbRegister32(src); v.has_value()) { + thread_context.pstate = *v; + } else { + return; + } + + /* Copy FPU registers. */ + for (size_t i = 0; i < util::size(thread_context.v) / 2; ++i) { + if (const auto v = ParseGdbRegister128(src); v.has_value()) { + thread_context.v[i] = *v; + } else { + return; + } + } + + if (const auto v = ParseGdbRegister32(src); v.has_value()) { + thread_context.fpsr = *v & 0xF80000FF; + thread_context.fpcr = *v & 0x07FFFF00; + } else { + return; + } + } + } + + void ParseGdbRegisterPacket(svc::ThreadContext &thread_context, const char *src, u64 reg_num, bool is_64_bit) { + if (is_64_bit) { + if (reg_num < 29) { + if (const auto v = ParseGdbRegister64(src); v.has_value()) { + thread_context.r[reg_num] = *v; + } + } else if (reg_num == 29) { + if (const auto v = ParseGdbRegister64(src); v.has_value()) { + thread_context.fp = *v; + } + } else if (reg_num == 30) { + if (const auto v = ParseGdbRegister64(src); v.has_value()) { + thread_context.lr = *v; + } + } else if (reg_num == 31) { + if (const auto v = ParseGdbRegister64(src); v.has_value()) { + thread_context.sp = *v; + } + } else if (reg_num == 32) { + if (const auto v = ParseGdbRegister64(src); v.has_value()) { + thread_context.pc = *v; + } + } else if (reg_num == 33) { + if (const auto v = ParseGdbRegister32(src); v.has_value()) { + thread_context.pstate = *v; + } + } else if (reg_num < 66) { + if (const auto v = ParseGdbRegister128(src); v.has_value()) { + thread_context.v[reg_num - 34] = *v; + } + } else if (reg_num == 66) { + if (const auto v = ParseGdbRegister32(src); v.has_value()) { + thread_context.fpsr = *v; + } + } else if (reg_num == 67) { + if (const auto v = ParseGdbRegister32(src); v.has_value()) { + thread_context.fpcr = *v; + } + } + } else { + if (reg_num < 15) { + if (const auto v = ParseGdbRegister32(src); v.has_value()) { + thread_context.r[reg_num] = *v; + } + } else if (reg_num == 15) { + if (const auto v = ParseGdbRegister32(src); v.has_value()) { + thread_context.pc = *v; + } + } else if (reg_num == 25) { + if (const auto v = ParseGdbRegister32(src); v.has_value()) { + thread_context.pstate = *v; + } + } else if (26 <= reg_num && reg_num < 58) { + union { + u64 v64[2]; + u128 v128; + } fpu_reg = { .v128 = thread_context.v[(reg_num - 26) / 2] }; + + if (const auto v = ParseGdbRegister64(src); v.has_value()) { + fpu_reg.v64[(reg_num - 26) % 2] = *v; + thread_context.v[(reg_num - 26) / 2] = fpu_reg.v128; + } + } else if (reg_num == 58) { + if (const auto v = ParseGdbRegister32(src); v.has_value()) { + thread_context.fpsr = *v & 0xF80000FF; + thread_context.fpcr = *v & 0x07FFFF00; + } + } + } + } + + u32 RegisterToContextFlags(u64 reg_num, bool is_64_bit) { + /* Convert register number. */ + if (!is_64_bit) { + reg_num = Aarch32RegisterToAarch64Register(reg_num); + } + + /* Get flags. */ + u32 flags = 0; + if (reg_num < 29) { + flags = svc::ThreadContextFlag_General; + } else if (reg_num < 34) { + flags = svc::ThreadContextFlag_Control; + } else if (reg_num < 66) { + flags = svc::ThreadContextFlag_Fpu; + } else if (reg_num == 66) { + flags = svc::ThreadContextFlag_FpuControl; + } + + return flags; + } + + constinit os::SdkMutex g_annex_buffer_lock; + constinit char g_annex_buffer[2 * GdbPacketBufferSize]; + + enum AnnexBufferContents { + AnnexBufferContents_Invalid, + AnnexBufferContents_Processes, + AnnexBufferContents_Threads, + AnnexBufferContents_Libraries, + }; + + constinit AnnexBufferContents g_annex_buffer_contents = AnnexBufferContents_Invalid; + + void GetAnnexBufferContents(char * &dst, u32 offset, u32 length) { + const u32 annex_len = std::strlen(g_annex_buffer); + if (offset <= annex_len) { + if (offset + length < annex_len) { + dst[0] = 'm'; + std::memcpy(dst + 1, g_annex_buffer + offset, length); + dst[1 + length] = 0; + + dst += 1 + length; + } else { + const auto size = annex_len - offset; + + dst[0] = 'l'; + std::memcpy(dst + 1, g_annex_buffer + offset, size); + dst[1 + size] = 0; + + dst += 1 + size; + } + } else { + dst[0] = '1'; + dst[1] = 0; + + dst += 1; + } + } + + constinit os::SdkMutex g_event_request_lock; + constinit os::SdkMutex g_event_lock; + constinit os::SdkConditionVariable g_event_request_cv; + constinit os::SdkConditionVariable g_event_done_cv; + + } + + GdbServerImpl::GdbServerImpl(int socket, void *stack, size_t stack_size) : m_socket(socket), m_session(socket), m_packet_io(), m_state(State::Initial), m_debug_process(), m_event(os::EventClearMode_AutoClear) { + /* Create and start the events thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_events_thread), DebugEventsThreadEntry, this, stack, stack_size, os::HighestThreadPriority - 1)); + os::StartThread(std::addressof(m_events_thread)); + + /* Set our state. */ + m_state = State::Running; + } + + GdbServerImpl::~GdbServerImpl() { + /* Set ourselves as killed. */ + m_killed = true; + + /* Signal to our events thread. */ + { + std::scoped_lock lk(g_event_request_lock); + g_event_request_cv.Signal(); + } + + /* Signal our event. */ + m_event.Signal(); + + /* Wait for our thread to finish. */ + os::WaitThread(std::addressof(m_events_thread)); + os::DestroyThread(std::addressof(m_events_thread)); + + /* Clear our state. */ + m_state = State::Destroyed; + + /* Detach. */ + if (this->HasDebugProcess()) { + m_debug_process.Detach(); + } + } + + void GdbServerImpl::DebugEventsThread() { + /* Process events. */ + { + std::scoped_lock lk(g_event_lock); + + /* Loop while we're not killed. */ + while (!m_killed) { + /* Wait for a request to come in. */ + g_event_request_cv.Wait(g_event_lock); + + /* Check that we're not killed now. */ + if (m_killed) { + break; + } + + /* Detach. */ + m_debug_process.Detach(); + + /* Check if we need to start the process. */ + const bool start_process = m_process_id == m_wait_process_id; + { + /* Clear our wait process id. */ + ON_SCOPE_EXIT { if (start_process) { m_wait_process_id = os::InvalidProcessId; } }; + + /* If we have a process id, attach. */ + if (R_FAILED(m_debug_process.Attach(m_process_id, m_process_id == m_wait_process_id))) { + AMS_DMNT2_GDB_LOG_DEBUG("Failed to attach to %016lx\n", m_process_id.value); + g_event_done_cv.Signal(); + continue; + } + } + + /* Set our process id. */ + m_process_id = m_debug_process.GetProcessId(); + + /* Signal that we're done attaching. */ + g_event_done_cv.Signal(); + + /* Process debug events without the lock held. */ + { + g_event_lock.Unlock(); + this->ProcessDebugEvents(); + g_event_lock.Lock(); + } + + /* Clear our process id and detach. */ + m_process_id = os::InvalidProcessId; + m_debug_process.Detach(); + } + } + + /* Set our state. */ + m_state = State::Exited; + } + + void GdbServerImpl::ProcessDebugEvents() { + AMS_DMNT2_GDB_LOG_DEBUG("Processing debug events for %016lx\n", m_process_id.value); + + u64 new_hb_nro_addr = 0; + u32 new_hb_nro_insn = 0; + + while (true) { + /* Wait for an event to come in. */ + const Result wait_result = [&] ALWAYS_INLINE_LAMBDA { + std::scoped_lock lk(g_event_lock); + + s32 dummy = -1; + svc::Handle handle = m_debug_process.GetHandle(); + R_RETURN(svc::WaitSynchronization(std::addressof(dummy), std::addressof(handle), 1, TimeSpan::FromMilliSeconds(20).GetNanoSeconds())); + }(); + + /* Check if we're killed. */ + if (m_killed || !m_debug_process.IsValid()) { + break; + } + + /* If we didn't get an event, try again. */ + if (svc::ResultTimedOut::Includes(wait_result)) { + continue; + } + + /* Try to get the event. */ + svc::DebugEventInfo d; + if (R_FAILED(m_debug_process.GetProcessDebugEvent(std::addressof(d)))) { + continue; + } + + /* Process the event. */ + GdbSignal signal; + char send_buffer[GdbPacketBufferSize]; + u64 thread_id = d.thread_id; + m_debug_process.ClearStep(); + + char * reply_cur = send_buffer; + char * const reply_end = std::end(send_buffer); + + send_buffer[0] = 0; + switch (d.type) { + case svc::DebugEvent_Exception: + { + switch (d.info.exception.type) { + case svc::DebugException_BreakPoint: + { + signal = GdbSignal_BreakpointTrap; + + const uintptr_t address = d.info.exception.address; + const bool is_instr = d.info.exception.specific.break_point.type == svc::BreakPointType_HardwareInstruction; + AMS_DMNT2_GDB_LOG_DEBUG("BreakPoint %lx, addr=%lx, type=%s\n", thread_id, address, is_instr ? "Instr" : "Data"); + + if (is_instr) { + AppendReplyFormat(reply_cur, reply_end, "T%02Xthread:p%lx.%lx;hwbreak:;", static_cast<u32>(signal), m_process_id.value, thread_id); + } else { + bool read = false, write = false; + const char *type = "watch"; + if (R_SUCCEEDED(m_debug_process.GetWatchPointInfo(address, read, write))) { + if (read && write) { + type = "awatch"; + } else if (read) { + type = "rwatch"; + } + } else { + AMS_DMNT2_GDB_LOG_DEBUG("GetWatchPointInfo FAIL %lx, addr=%lx, type=%s\n", thread_id, address, is_instr ? "Instr" : "Data"); + } + + AppendReplyFormat(reply_cur, reply_end, "T%02Xthread:p%lx.%lx;%s:%lx;", static_cast<u32>(signal), m_process_id.value, thread_id, type, address); + } + } + break; + case svc::DebugException_UserBreak: + { + const uintptr_t address = d.info.exception.address; + const auto &info = d.info.exception.specific.user_break; + AMS_DMNT2_GDB_LOG_DEBUG("UserBreak %lx, addr=%lx, reason=%x, data=0x%lx, size=0x%lx\n", thread_id, address, info.break_reason, info.address, info.size); + + /* Check reason. */ + /* TODO: libnx/Nintendo provide addresses in different ways, but we could optimize to avoid iterating all memory repeatedly. */ + if ((info.break_reason & svc::BreakReason_NotificationOnlyFlag) != 0) { + const auto reason = info.break_reason & ~svc::BreakReason_NotificationOnlyFlag; + if (reason == svc::BreakReason_PostLoadDll || reason == svc::BreakReason_PostUnloadDll) { + /* Re-collect the process's modules. */ + m_debug_process.CollectModules(); + } + + if (m_debug_process.GetOverrideStatus().IsHbl() && reason == svc::BreakReason_PostLoadDll) { + if (R_SUCCEEDED(m_debug_process.ReadMemory(std::addressof(new_hb_nro_insn), info.address, sizeof(new_hb_nro_insn)))) { + const u32 break_insn = SdkBreakPoint; + if (R_SUCCEEDED(m_debug_process.WriteMemory(std::addressof(break_insn), info.address, sizeof(break_insn)))) { + AMS_DMNT2_GDB_LOG_DEBUG("Set automatic break on new homebrew NRO (%lx, %lx)\n", info.address, info.size); + + new_hb_nro_addr = info.address; + } else { + AMS_DMNT2_GDB_LOG_DEBUG("Failed to set automatic break on new homebrew NRO (%lx, %lx)\n", info.address, info.size); + } + } else { + AMS_DMNT2_GDB_LOG_DEBUG("Failed to read first insn on new homebrew NRO (%lx, %lx)\n", info.address, info.size); + } + } + + /* This was just a notification, so we should continue. */ + m_debug_process.Continue(); + continue; + } + + /* Check if we should automatically continue. */ + svc::ThreadContext ctx; + if (R_SUCCEEDED(m_debug_process.GetThreadContext(std::addressof(ctx), thread_id, svc::ThreadContextFlag_Control))) { + u32 insn = 0; + if (R_SUCCEEDED(m_debug_process.ReadMemory(std::addressof(insn), ctx.pc, sizeof(insn)))) { + constexpr u32 Aarch64SvcBreakValue = 0xD4000001 | (svc::SvcId_Break << 5); + constexpr u32 Aarch32SvcBreakValue = 0xEF000000 | (svc::SvcId_Break); + bool is_svc_break = m_debug_process.Is64Bit() ? (insn == Aarch64SvcBreakValue) : (insn == Aarch32SvcBreakValue); + + if (!is_svc_break) { + AMS_DMNT2_GDB_LOG_ERROR("UserBreak from non-SvcBreak (%08x)\n", insn); + m_debug_process.Continue(); + continue; + } + } + } + + signal = GdbSignal_BreakpointTrap; + } + break; + case svc::DebugException_DebuggerBreak: + { + signal = GdbSignal_Interrupt; + + thread_id = m_debug_process.GetPreferredDebuggerBreakThreadId(); + svc::ThreadContext ctx; + if (thread_id == 0 || thread_id == static_cast<u64>(-1) || R_FAILED(m_debug_process.GetThreadContext(std::addressof(ctx), thread_id, svc::ThreadContextFlag_Control))) { + thread_id = m_debug_process.GetLastThreadId(); + } + + AMS_DMNT2_GDB_LOG_DEBUG("DebuggerBreak %lx, last=%lx\n", thread_id, m_debug_process.GetLastThreadId()); + + m_debug_process.SetLastThreadId(thread_id); + m_debug_process.SetThreadIdOverride(thread_id); + } + break; + case svc::DebugException_UndefinedInstruction: + { + signal = GdbSignal_IllegalInstruction; + + uintptr_t address = d.info.exception.address; + const u32 insn = d.info.exception.specific.undefined_instruction.insn; + u32 new_insn = 0; + + AMS_DMNT2_GDB_LOG_DEBUG("Undefined Instruction %lx, address=%p, insn=%08x\n", thread_id, reinterpret_cast<void *>(address), insn); + + bool is_new_hb_nro = false; + + svc::ThreadContext ctx; + if (R_SUCCEEDED(m_debug_process.GetThreadContext(std::addressof(ctx), thread_id, svc::ThreadContextFlag_Control))) { + bool insn_changed = false; + if (ctx.pstate & 0x20) { + /* Thumb mode. */ + address &= ~1; + + if (R_SUCCEEDED(m_debug_process.ReadMemory(std::addressof(new_insn), address, 2))) { + switch ((new_insn >> 11) & 0x1F) { + case 0x1D: + case 0x1E: + case 0x1F: + { + if (R_SUCCEEDED(m_debug_process.ReadMemory(reinterpret_cast<u8 *>(std::addressof(new_insn)) + 2, address + 2, 2))) { + insn_changed = (new_insn != insn); + } + } + break; + default: + insn_changed = (new_insn != insn); + break; + } + + if ((insn & T16BreakPointMask) == T16BreakPoint) { + signal = GdbSignal_BreakpointTrap; + } + } + } else { + /* Non-thumb. */ + if (R_SUCCEEDED(m_debug_process.ReadMemory(std::addressof(new_insn), address, sizeof(new_insn)))) { + insn_changed = (new_insn != insn); + } + + if (((insn & SdkBreakPointMask) == SdkBreakPoint) || + ((insn & ArmBreakPointMask) == ArmBreakPoint) || + ((insn & A64BreakPointMask) == A64BreakPoint) || + ((insn & A32BreakPointMask) == A32BreakPoint) || + ((insn & A64HaltMask) == A64Halt)) + { + signal = GdbSignal_BreakpointTrap; + } + + if (m_debug_process.GetOverrideStatus().IsHbl() && address == new_hb_nro_addr && insn == SdkBreakPoint) { + if (R_SUCCEEDED(m_debug_process.WriteMemory(std::addressof(new_hb_nro_insn), new_hb_nro_addr, sizeof(new_hb_nro_insn)))) { + AMS_DMNT2_GDB_LOG_DEBUG("Did automatic break on new homebrew NRO (%lx)\n", address); + + new_hb_nro_addr = 0; + new_hb_nro_insn = 0; + + is_new_hb_nro = true; + } else { + AMS_DMNT2_GDB_LOG_ERROR("Failed to restore instruction for new homebrew NRO (%lx)!\n", address); + } + } + } + + if (insn_changed) { + AMS_DMNT2_GDB_LOG_DEBUG("Instruction Changed %lx, address=%p, insn=%08x, new_insn=%08x\n", thread_id, reinterpret_cast<void *>(address), insn, new_insn); + } + } + + if (signal == GdbSignal_IllegalInstruction) { + AMS_DMNT2_GDB_LOG_DEBUG("Illegal Instruction %lx, address=%p, insn=%08x\n", thread_id, reinterpret_cast<void *>(address), insn); + } else if (signal == GdbSignal_BreakpointTrap && ((insn & SdkBreakPointMask) != SdkBreakPoint)) { + AMS_DMNT2_GDB_LOG_DEBUG("Non-SDK BreakPoint %lx, address=%p, insn=%08x\n", thread_id, reinterpret_cast<void *>(address), insn); + } + + if (signal == GdbSignal_BreakpointTrap && !is_new_hb_nro) { + AppendReplyFormat(reply_cur, reply_end, "T%02Xthread:p%lx.%lx;swbreak:;", static_cast<u32>(signal), m_process_id.value, thread_id); + } + + m_debug_process.ClearStep(); + } + break; + default: + AMS_DMNT2_GDB_LOG_DEBUG("Unhandled Exception %u %lx\n", static_cast<u32>(d.info.exception.type), thread_id); + signal = GdbSignal_SegmentationFault; + break; + } + + if (reply_cur == send_buffer) { + AppendReplyFormat(reply_cur, reply_end, "T%02Xthread:p%lx.%lx;", static_cast<u32>(signal), m_process_id.value, thread_id); + } + + m_debug_process.SetLastThreadId(thread_id); + m_debug_process.SetLastSignal(signal); + } + break; + case svc::DebugEvent_CreateThread: + { + AMS_DMNT2_GDB_LOG_DEBUG("CreateThread %lx\n", thread_id); + + if (m_debug_process.IsValid()) { + m_debug_process.Continue(); + } else { + AppendReplyFormat(reply_cur, reply_end, "W00"); + } + } + break; + case svc::DebugEvent_ExitThread: + { + AMS_DMNT2_GDB_LOG_DEBUG("ExitThread %lx\n", thread_id); + + if (m_debug_process.IsValid()) { + m_debug_process.Continue(); + } else { + AppendReplyFormat(reply_cur, reply_end, "W00"); + } + } + break; + case svc::DebugEvent_ExitProcess: + { + m_killed = true; + AMS_DMNT2_GDB_LOG_DEBUG("ExitProcess\n"); + + if (d.info.exit_process.reason == svc::ProcessExitReason_ExitProcess) { + AppendReplyFormat(reply_cur, reply_end, "W00"); + } else { + AppendReplyFormat(reply_cur, reply_end, "X%02X", GdbSignal_Killed); + } + + m_debug_process.Detach(); + } + break; + default: + AMS_DMNT2_GDB_LOG_DEBUG("Unhandled ProcessEvent %u %lx\n", static_cast<u32>(d.type), thread_id); + m_debug_process.Continue(); + break; + } + + if (reply_cur != send_buffer) { + bool do_break; + this->SendPacket(std::addressof(do_break), send_buffer); + if (do_break) { + m_debug_process.Break(); + } + } + } + } + + void GdbServerImpl::AppendStopReplyPacket(GdbSignal signal) { + /* Set the signal. */ + AppendReplyFormat(m_reply_cur, m_reply_end, "T%02X", static_cast<u32>(signal)); + + /* Get the last thread id. */ + const u64 thread_id = m_debug_process.GetLastThreadId(); + + /* Get the thread context. */ + svc::ThreadContext thread_context = {}; + m_debug_process.GetThreadContext(std::addressof(thread_context), thread_id, svc::ThreadContextFlag_General | svc::ThreadContextFlag_Control); + + /* Add important registers. */ + /* TODO: aarch32 */ + { + if (thread_context.fp != 0) { + AppendReplyFormat(m_reply_cur, m_reply_end, "1d:%016lx", util::ConvertToBigEndian(thread_context.fp)); + } else { + AppendReplyFormat(m_reply_cur, m_reply_end, "1d:0*,"); + } + + if (thread_context.sp != 0) { + AppendReplyFormat(m_reply_cur, m_reply_end, ";1f:%016lx", util::ConvertToBigEndian(thread_context.sp)); + } else { + AppendReplyFormat(m_reply_cur, m_reply_end, ";1f:0*,"); + } + + if (thread_context.pc != 0) { + AppendReplyFormat(m_reply_cur, m_reply_end, ";20:%016lx", util::ConvertToBigEndian(thread_context.pc)); + } else { + AppendReplyFormat(m_reply_cur, m_reply_end, ";20:0*,"); + } + } + + /* Add the thread id. */ + AppendReplyFormat(m_reply_cur, m_reply_end, ";thread:p%lx.%lx", m_process_id.value, thread_id); + + /* Add the thread core. */ + { + u32 core = 0; + m_debug_process.GetThreadCurrentCore(std::addressof(core), thread_id); + + AppendReplyFormat(m_reply_cur, m_reply_end, ";core:%u;", core); + } + } + + void GdbServerImpl::LoopProcess() { + /* Process packets. */ + while (m_session.IsValid()) { + /* Receive a packet. */ + bool do_break = false; + char recv_buf[GdbPacketBufferSize]; + char *packet = this->ReceivePacket(std::addressof(do_break), recv_buf, sizeof(recv_buf)); + + if (!do_break && packet != nullptr) { + /* Process the packet. */ + char reply_buffer[GdbPacketBufferSize]; + this->ProcessPacket(packet, reply_buffer); + + /* Send packet. */ + this->SendPacket(std::addressof(do_break), reply_buffer); + } + + /* If we should, break the process. */ + if (do_break) { + m_debug_process.Break(); + } + } + } + + void GdbServerImpl::ProcessPacket(char *receive, char *reply) { + /* Set our fields. */ + m_receive_packet = receive; + m_reply_cur = reply; + m_reply_end = reply + GdbPacketBufferSize; + + /* Log the packet we're processing. */ + AMS_DMNT2_GDB_LOG_DEBUG("Receive: %s\n", m_receive_packet); + + /* Clear our reply packet. */ + reply[0] = 0; + + /* Handle the received packet. */ + switch (m_receive_packet[0]) { + case 'D': + this->D(); + break; + case 'G': + this->G(); + break; + case 'H': + this->H(); + break; + case 'M': + this->M(); + break; + case 'P': + this->P(); + break; + case 'Q': + this->Q(); + break; + case 'T': + this->T(); + break; + case 'Z': + this->Z(); + break; + case 'c': + this->c(); + break; + case 'k': + this->k(); + break; + case 'g': + if (!this->g()) { + m_killed = true; + } + break; + case 'm': + this->m(); + break; + case 'p': + this->p(); + break; + case 'v': + this->v(); + break; + case 'q': + this->q(); + break; + case 'z': + this->z(); + break; + case '!': + AppendReplyOk(m_reply_cur, m_reply_end); + break; + case '?': + this->QuestionMark(); + break; + default: + AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented: %s\n", m_receive_packet); + break; + } + } + + void GdbServerImpl::D() { + m_debug_process.Detach(); + AppendReplyOk(m_reply_cur, m_reply_end); + } + + void GdbServerImpl::G() { + /* Get thread id. */ + u32 thread_id = m_debug_process.GetThreadIdOverride(); + if (thread_id == 0 || thread_id == static_cast<u32>(-1)) { + thread_id = m_debug_process.GetLastThreadId(); + } + + /* Get thread context. */ + svc::ThreadContext ctx; + if (R_FAILED(m_debug_process.GetThreadContext(std::addressof(ctx), thread_id, svc::ThreadContextFlag_All))) { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + return; + } + + /* Update the thread context. */ + ParseGdbRegisterPacket(ctx, m_receive_packet, m_debug_process.Is64Bit()); + + /* Set the thread context. */ + if (R_SUCCEEDED(m_debug_process.SetThreadContext(std::addressof(ctx), thread_id, svc::ThreadContextFlag_All))) { + AppendReplyOk(m_reply_cur, m_reply_end); + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + + void GdbServerImpl::H() { + if (this->HasDebugProcess()) { + if (ParsePrefix(m_receive_packet, "Hg") || ParsePrefix(m_receive_packet, "HG")) { + this->Hg(); + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + + void GdbServerImpl::Hg() { + bool success = false; + s64 thread_id; + if (const char *dot = std::strchr(m_receive_packet, '.'); dot != nullptr) { + thread_id = std::strcmp(dot + 1, "-1") == 0 ? -1 : static_cast<s64>(DecodeHex(dot + 1)); + AMS_DMNT2_GDB_LOG_DEBUG("Set thread id = %lx\n", thread_id); + + u64 thread_ids[DebugProcess::ThreadCountMax]; + s32 num_threads; + if (R_SUCCEEDED(m_debug_process.GetThreadList(std::addressof(num_threads), thread_ids, util::size(thread_ids)))) { + if (thread_id == 0) { + thread_id = thread_ids[0]; + } + for (auto i = 0; i < num_threads; ++i) { + if (thread_id == -1 || static_cast<u64>(thread_id) == thread_ids[i]) { + svc::ThreadContext context; + if (R_SUCCEEDED(m_debug_process.GetThreadContext(std::addressof(context), thread_ids[i], svc::ThreadContextFlag_Control))) { + success = true; + if (thread_id != -1) { + m_debug_process.SetThreadIdOverride(thread_ids[i]); + } + } + } + } + } + } + + if (success) { + AppendReplyOk(m_reply_cur, m_reply_end); + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + + void GdbServerImpl::M() { + ++m_receive_packet; + + /* Validate format. */ + char *comma = std::strchr(m_receive_packet, ','); + if (comma == nullptr) { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + return; + } + *comma = 0; + + char *colon = std::strchr(comma + 1, ':'); + if (colon == nullptr) { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + return; + } + *colon = 0; + + /* Parse address/length. */ + const u64 address = DecodeHex(m_receive_packet); + const u64 length = DecodeHex(comma + 1); + if (length >= sizeof(m_buffer)) { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + return; + } + + /* Decode the memory. */ + HexToMemory(m_buffer, colon + 1, length); + + /* Write the memory. */ + if (R_SUCCEEDED(m_debug_process.WriteMemory(m_buffer, address, length))) { + AppendReplyOk(m_reply_cur, m_reply_end); + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + + void GdbServerImpl::P() { + ++m_receive_packet; + + /* Validate format. */ + char *equal = std::strchr(m_receive_packet, '='); + if (equal == nullptr) { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + return; + } + *equal = 0; + + /* Decode the register. */ + const u64 reg_num = DecodeHex(m_receive_packet); + + /* Get the flags. */ + const u32 flags = RegisterToContextFlags(reg_num, m_debug_process.Is64Bit()); + + /* Determine thread id. */ + u32 thread_id = m_debug_process.GetThreadIdOverride(); + if (thread_id == 0 || thread_id == static_cast<u32>(-1)) { + thread_id = m_debug_process.GetLastThreadId(); + } + + /* Update the register. */ + svc::ThreadContext ctx; + if (R_SUCCEEDED(m_debug_process.GetThreadContext(std::addressof(ctx), thread_id, flags))) { + ParseGdbRegisterPacket(ctx, equal + 1, reg_num, m_debug_process.Is64Bit()); + + if (R_SUCCEEDED(m_debug_process.SetThreadContext(std::addressof(ctx), thread_id, flags))) { + AppendReplyOk(m_reply_cur, m_reply_end); + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + + void GdbServerImpl::Q() { + if (ParsePrefix(m_receive_packet, "QStartNoAckMode")) { + this->QStartNoAckMode(); + } else { + AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented Q: %s\n", m_receive_packet); + } + } + + void GdbServerImpl::QStartNoAckMode() { + m_packet_io.SetNoAck(); + AppendReplyOk(m_reply_cur, m_reply_end); + } + + void GdbServerImpl::T() { + if (const char *dot = std::strchr(m_receive_packet, '.'); dot != nullptr) { + const u64 thread_id = DecodeHex(dot + 1); + + svc::ThreadContext ctx; + if (R_SUCCEEDED(m_debug_process.GetThreadContext(std::addressof(ctx), thread_id, svc::ThreadContextFlag_Control))) { + AppendReplyOk(m_reply_cur, m_reply_end); + } else { + AppendReplyFormat(m_reply_cur, m_reply_end, "E01"); + } + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + + void GdbServerImpl::Z() { + /* Increment past the 'Z'. */ + ++m_receive_packet; + + /* Decode the type. */ + if (!('0' <= m_receive_packet[0] && m_receive_packet[0] <= '4') || m_receive_packet[1] != ',') { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + return; + } + + const auto type = m_receive_packet[0] - '0'; + m_receive_packet += 2; + + /* Decode the address/length. */ + const char *comma = std::strchr(m_receive_packet, ','); + if (comma == nullptr) { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + return; + } + + /* Parse address/length. */ + const u64 address = DecodeHex(m_receive_packet); + const u64 length = DecodeHex(comma + 1); + + switch (type) { + case 0: /* SW */ + { + if (length == 2 || length == 4) { + if (R_SUCCEEDED(m_debug_process.SetBreakPoint(address, length, false))) { + AppendReplyOk(m_reply_cur, m_reply_end); + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + } + break; + case 1: /* HW */ + { + if (length == 2 || length == 4) { + if (R_SUCCEEDED(m_debug_process.SetHardwareBreakPoint(address, length, false))) { + AppendReplyOk(m_reply_cur, m_reply_end); + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + } + break; + case 2: /* Watch-W */ + { + if (m_debug_process.IsValidWatchPoint(address, length)) { + if (R_SUCCEEDED(m_debug_process.SetWatchPoint(address, length, false, true))) { + AppendReplyOk(m_reply_cur, m_reply_end); + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + } + break; + case 3: /* Watch-R */ + { + if (m_debug_process.IsValidWatchPoint(address, length)) { + if (R_SUCCEEDED(m_debug_process.SetWatchPoint(address, length, true, false))) { + AppendReplyOk(m_reply_cur, m_reply_end); + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + } + break; + case 4: /* Watch-A */ + { + if (m_debug_process.IsValidWatchPoint(address, length)) { + if (R_SUCCEEDED(m_debug_process.SetWatchPoint(address, length, true, true))) { + AppendReplyOk(m_reply_cur, m_reply_end); + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + } + break; + default: + break; + } + } + + void GdbServerImpl::c() { + /* Get thread id. */ + u64 thread_id = m_debug_process.GetThreadIdOverride(); + if (thread_id == 0 || thread_id == static_cast<u64>(-1)) { + thread_id = m_debug_process.GetLastThreadId(); + } + + /* Continue the thread. */ + Result result; + if (thread_id == m_debug_process.GetLastThreadId()) { + result = m_debug_process.Continue(thread_id); + } else { + result = m_debug_process.Continue(); + } + + if (R_SUCCEEDED(result)) { + AppendReplyOk(m_reply_cur, m_reply_end); + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + + bool GdbServerImpl::g() { + /* Get thread id. */ + u64 thread_id = m_debug_process.GetThreadIdOverride(); + if (thread_id == 0 || thread_id == static_cast<u64>(-1)) { + thread_id = m_debug_process.GetLastThreadId(); + } + + /* Get thread context. */ + svc::ThreadContext thread_context; + if (R_FAILED(m_debug_process.GetThreadContext(std::addressof(thread_context), thread_id, svc::ThreadContextFlag_All))) { + return false; + } + + /* Populate reply packet. */ + SetGdbRegisterPacket(m_reply_cur, m_reply_end, thread_context, m_debug_process.Is64Bit()); + + return true; + } + + void GdbServerImpl::m() { + ++m_receive_packet; + + /* Validate format. */ + const char *comma = std::strchr(m_receive_packet, ','); + if (comma == nullptr) { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + return; + } + + /* Parse address/length. */ + const u64 address = DecodeHex(m_receive_packet); + const u64 length = DecodeHex(comma + 1); + if (length >= sizeof(m_buffer)) { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + return; + } + + /* Read the memory. */ + /* TODO: Detect partial readability? */ + if (R_FAILED(m_debug_process.ReadMemory(m_buffer, address, length))) { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + return; + } + + /* Encode the memory. */ + MemoryToHex(m_reply_cur, m_reply_end, m_buffer, length); + } + + void GdbServerImpl::k() { + m_debug_process.Terminate(); + m_killed = true; + } + + void GdbServerImpl::p() { + ++m_receive_packet; + + /* Decode the register. */ + const u64 reg_num = DecodeHex(m_receive_packet); + + /* Get the flags. */ + const u32 flags = RegisterToContextFlags(reg_num, m_debug_process.Is64Bit()); + + /* Determine thread id. */ + u32 thread_id = m_debug_process.GetThreadIdOverride(); + if (thread_id == 0 || thread_id == static_cast<u32>(-1)) { + thread_id = m_debug_process.GetLastThreadId(); + } + + /* Get the register. */ + svc::ThreadContext ctx; + if (R_SUCCEEDED(m_debug_process.GetThreadContext(std::addressof(ctx), thread_id, flags))) { + SetGdbRegisterPacket(m_reply_cur, m_reply_end, ctx, reg_num, m_debug_process.Is64Bit()); + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + + void GdbServerImpl::v() { + if (ParsePrefix(m_receive_packet, "vAttach;")) { + this->vAttach(); + } else if (ParsePrefix(m_receive_packet, "vCont")) { + this->vCont(); + } else { + AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented v: %s\n", m_receive_packet); + } + } + + void GdbServerImpl::vAttach() { + if (!this->HasDebugProcess()) { + /* Get the process id. */ + if (const u64 process_id = DecodeHex(m_receive_packet); process_id != 0) { + /* Set our process id. */ + m_process_id = { process_id }; + + /* Wait for us to be attached. */ + { + std::scoped_lock lk(g_event_request_lock); + g_event_request_cv.Signal(); + if (!g_event_done_cv.TimedWait(g_event_request_lock, TimeSpan::FromSeconds(2))) { + m_event.Signal(); + } + } + + /* If we're attached, send a stop reply packet. */ + if (m_debug_process.IsValid()) { + /* Set the stop reply packet. */ + this->AppendStopReplyPacket(m_debug_process.GetLastSignal()); + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + + void GdbServerImpl::vCont() { + /* Check if this is a query about what we support. */ + if (ParsePrefix(m_receive_packet, "?")) { + AppendReplyFormat(m_reply_cur, m_reply_end, "vCont;c;C;s;S;"); + return; + } + + /* We want to parse semicolon separated fields repeatedly. */ + char *saved; + char *token = strtok_r(m_receive_packet, ";", std::addressof(saved)); + + /* Validate the initial token. */ + if (token == nullptr) { + return; + } + + /* Prepare to parse threads. */ + u64 thread_ids[DebugProcess::ThreadCountMax] = {}; + u8 continue_modes[DebugProcess::ThreadCountMax] = {}; + + s32 num_threads; + if (R_FAILED(m_debug_process.GetThreadList(std::addressof(num_threads), thread_ids, util::size(thread_ids)))) { + AMS_DMNT2_GDB_LOG_ERROR("vCont: Failed to get thread list\n"); + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + return; + } + + /* Handle each token. */ + Result result = ResultSuccess(); + DebugProcess::ContinueMode default_continue_mode = DebugProcess::ContinueMode_Stopped; + while (token != nullptr && R_SUCCEEDED(result)) { + result = this->ParseVCont(token, thread_ids, continue_modes, num_threads, default_continue_mode); + token = strtok_r(nullptr, ";", std::addressof(saved)); + } + + AMS_DMNT2_GDB_LOG_DEBUG("vCont: NumThreads=%d, Default Continue Mode=%d\n", num_threads, static_cast<int>(default_continue_mode)); + + /* Act on all threads. */ + s64 thread_id = -1; + for (auto i = 0; i < num_threads; ++i) { + if (continue_modes[i] == DebugProcess::ContinueMode_Step || (continue_modes[i] == DebugProcess::ContinueMode_Stopped && default_continue_mode == DebugProcess::ContinueMode_Step)) { + thread_id = thread_ids[i]; + result = m_debug_process.Step(thread_ids[i]); + } + } + + /* Continue the last thread. */ + if (static_cast<u64>(thread_id) == m_debug_process.GetLastThreadId() && default_continue_mode != DebugProcess::ContinueMode_Continue) { + result = m_debug_process.Continue(thread_id); + } else { + result = m_debug_process.Continue(); + } + + /* Set reply. */ + if (R_SUCCEEDED(result)) { + AppendReplyOk(m_reply_cur, m_reply_end); + } else { + AMS_DMNT2_GDB_LOG_ERROR("vCont: Failed %08x\n", result.GetValue()); + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + + Result GdbServerImpl::ParseVCont(char * const token, u64 *thread_ids, u8 *continue_modes, s32 num_threads, DebugProcess::ContinueMode &default_continue_mode) { + /* Parse the thread id. */ + s64 thread_id = -1; + s32 signal = -1; + s32 thread_ix = -1; + + if (token[0] && token[1]) { + if (char *colon = std::strchr(token, ':'); colon != nullptr) { + *colon = 0; + if (char *dot = std::strchr(colon + 1, '.'); dot != nullptr) { + *dot = 0; + thread_id = std::strcmp(dot + 1, "-1") == 0 ? -1 : static_cast<s64>(DecodeHex(dot + 1)); + thread_ix = FindThreadIdIndex(thread_ids, num_threads, static_cast<u64>(thread_id)); + } + } + } + + /* Check that we don't already have a default mode. */ + if (thread_id == -1 && default_continue_mode != DebugProcess::ContinueMode_Stopped) { + AMS_DMNT2_GDB_LOG_ERROR("vCont: Too many defaults specified\n"); + } + + /* Handle the action. */ + switch (token[0]) { + case 'c': + if (thread_id > 0) { + AMS_DMNT2_GDB_LOG_DEBUG("vCont: Continue %lx\n", static_cast<u64>(thread_id)); + continue_modes[thread_ix] = DebugProcess::ContinueMode_Continue; + } else { + default_continue_mode = DebugProcess::ContinueMode_Continue; + } + break; + case 'C': + if (token[1]) { + signal = std::strcmp(token + 1, "-1") == 0 ? -1 : static_cast<s32>(DecodeHex(token + 1)); + } + AMS_DMNT2_GDB_LOG_WARN("vCont: Ignoring C, signal=%d\n", signal); + if (thread_id > 0) { + AMS_DMNT2_GDB_LOG_DEBUG("vCont: Continue %lx, signal=%d\n", static_cast<u64>(thread_id), signal); + continue_modes[thread_ix] = DebugProcess::ContinueMode_Continue; + } else { + default_continue_mode = DebugProcess::ContinueMode_Continue; + } + break; + case 's': + if (thread_id > 0) { + AMS_DMNT2_GDB_LOG_DEBUG("vCont: Step %lx\n", static_cast<u64>(thread_id)); + continue_modes[thread_ix] = DebugProcess::ContinueMode_Step; + } else { + default_continue_mode = DebugProcess::ContinueMode_Step; + } + break; + case 'S': + if (token[1]) { + signal = std::strcmp(token + 1, "-1") == 0 ? -1 : static_cast<s32>(DecodeHex(token + 1)); + } + AMS_DMNT2_GDB_LOG_WARN("vCont: Ignoring S, signal=%d\n", signal); + if (thread_id > 0) { + AMS_DMNT2_GDB_LOG_DEBUG("vCont: Step %lx, signal=%d\n", static_cast<u64>(thread_id), signal); + continue_modes[thread_ix] = DebugProcess::ContinueMode_Step; + } else { + default_continue_mode = DebugProcess::ContinueMode_Step; + } + break; + default: + AMS_DMNT2_GDB_LOG_WARN("vCont: Ignoring %c\n", token[0]); + break; + } + + R_SUCCEED(); + } + + + void GdbServerImpl::q() { + if (ParsePrefix(m_receive_packet, "qAttached:")) { + this->qAttached(); + } else if (ParsePrefix(m_receive_packet, "qC")) { + this->qC(); + } else if (ParsePrefix(m_receive_packet, "qRcmd,")) { + this->qRcmd(); + } else if (ParsePrefix(m_receive_packet, "qSupported:")) { + this->qSupported(); + } else if (ParsePrefix(m_receive_packet, "qXfer:")) { + this->qXfer(); + } else { + AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented q: %s\n", m_receive_packet); + } + } + + void GdbServerImpl::qAttached() { + if (this->HasDebugProcess()) { + AppendReplyFormat(m_reply_cur, m_reply_end, "1"); + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + + void GdbServerImpl::qC() { + if (this->HasDebugProcess()) { + /* Send the thread id. */ + AppendReplyFormat(m_reply_cur, m_reply_end, "QCp%lx.%lx", m_process_id.value, m_debug_process.GetLastThreadId()); + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + + void GdbServerImpl::qRcmd() { + /* Decode the command. */ + const auto packet_len = std::strlen(m_receive_packet); + HexToMemory(m_buffer, m_receive_packet, packet_len); + m_buffer[packet_len / 2] = '\x00'; + + /* Convert our response to hex, on complete. */ + ON_SCOPE_EXIT { + /* Convert response to hex. */ + MemoryToHex(m_reply_cur, m_reply_end, m_buffer, std::strlen(m_buffer)); + }; + + char *reply_cur = m_buffer; + char *reply_end = m_buffer + sizeof(m_buffer); + + /* Parse the command. */ + char *command = reinterpret_cast<char *>(m_buffer); + if (ParsePrefix(command, "help")) { + AppendReplyFormat(reply_cur, reply_end, "get info\n" + "get mappings\n" + "get mappings {address}\n" + "get mapping {address}\n" + "wait application\n" + "wait {program id}\n" + "wait homebrew\n"); + } else if (ParsePrefix(command, "get base") || ParsePrefix(command, "get info") || ParsePrefix(command, "get modules")) { + if (!this->HasDebugProcess()) { + AppendReplyFormat(reply_cur, reply_end, "Not attached.\n"); + return; + } + + AppendReplyFormat(reply_cur, reply_end, "Process: 0x%lx (%s)\n" + "Program Id: 0x%016lx\n" + "Application: %d\n" + "Hbl: %d\n" + "Layout:\n", m_process_id.value, m_debug_process.GetProcessName(), m_debug_process.GetProgramLocation().program_id.value, m_debug_process.IsApplication(), m_debug_process.GetOverrideStatus().IsHbl()); + + AppendReplyFormat(reply_cur, reply_end, " Alias: 0x%010lx - 0x%010lx\n" + " Heap: 0x%010lx - 0x%010lx\n" + " Aslr: 0x%010lx - 0x%010lx\n" + " Stack: 0x%010lx - 0x%010lx\n" + "Modules:\n", m_debug_process.GetAliasRegionAddress(), m_debug_process.GetAliasRegionAddress() + m_debug_process.GetAliasRegionSize() - 1, + m_debug_process.GetHeapRegionAddress(), m_debug_process.GetHeapRegionAddress() + m_debug_process.GetHeapRegionSize() - 1, + m_debug_process.GetAslrRegionAddress(), m_debug_process.GetAslrRegionAddress() + m_debug_process.GetAslrRegionSize() - 1, + m_debug_process.GetStackRegionAddress(), m_debug_process.GetStackRegionAddress() + m_debug_process.GetStackRegionSize() - 1); + + /* Get the module list. */ + for (size_t i = 0; i < m_debug_process.GetModuleCount(); ++i) { + const char *module_name = m_debug_process.GetModuleName(i); + const auto name_len = std::strlen(module_name); + if (name_len < 5 || (std::strcmp(module_name + name_len - 4, ".elf") != 0 && std::strcmp(module_name + name_len - 4, ".nss") != 0)) { + AppendReplyFormat(reply_cur, reply_end, " 0x%010lx - 0x%010lx %s.elf\n", m_debug_process.GetModuleBaseAddress(i), m_debug_process.GetModuleBaseAddress(i) + m_debug_process.GetModuleSize(i) - 1, module_name); + } else { + AppendReplyFormat(reply_cur, reply_end, " 0x%010lx - 0x%010lx %s\n", m_debug_process.GetModuleBaseAddress(i), m_debug_process.GetModuleBaseAddress(i) + m_debug_process.GetModuleSize(i) - 1, module_name); + } + } + } else if (ParsePrefix(command, "get mappings")) { + if (!this->HasDebugProcess()) { + AppendReplyFormat(reply_cur, reply_end, "Not attached.\n"); + return; + } + + uintptr_t cur_addr = 0; + if (ParsePrefix(command, " ")) { + ParsePrefix(command, "0x"); + + cur_addr = DecodeHex(command); + } + + AppendReplyFormat(reply_cur, reply_end, "Mappings (starting from 0x%010lx):\n", cur_addr); + + while (true) { + /* Check if we have room for a mapping. */ + if (reply_end - reply_cur < 0x48 * 2) { + AppendReplyFormat(reply_cur, reply_end, "<-- Send `monitor get mappings 0x%010lx` to continue -->\n", cur_addr); + break; + } + + /* Get mapping. */ + svc::MemoryInfo mem_info; + if (R_FAILED(m_debug_process.QueryMemory(std::addressof(mem_info), cur_addr))) { + break; + } + + if (mem_info.state != svc::MemoryState_Inaccessible || mem_info.base_address + mem_info.size - 1 != std::numeric_limits<u64>::max()) { + const char *state = GetMemoryStateName(mem_info.state); + const char *perm = GetMemoryPermissionString(mem_info); + + const char l = (mem_info.attribute & svc::MemoryAttribute_Locked) ? 'L' : '-'; + const char i = (mem_info.attribute & svc::MemoryAttribute_IpcLocked) ? 'I' : '-'; + const char d = (mem_info.attribute & svc::MemoryAttribute_DeviceShared) ? 'D' : '-'; + const char u = (mem_info.attribute & svc::MemoryAttribute_Uncached) ? 'U' : '-'; + + AppendReplyFormat(reply_cur, reply_end, " 0x%010lx - 0x%010lx %s %s %c%c%c%c [%d, %d]\n", mem_info.base_address, mem_info.base_address + mem_info.size - 1, perm, state, l, i, d, u, mem_info.ipc_count, mem_info.device_count); + } + + /* Advance. */ + const uintptr_t next_address = mem_info.base_address + mem_info.size; + if (next_address <= cur_addr) { + break; + } + + cur_addr = next_address; + } + } else if (ParsePrefix(command, "get mapping ")) { + if (!this->HasDebugProcess()) { + AppendReplyFormat(reply_cur, reply_end, "Not attached.\n"); + return; + } + + /* Allow optional "0x" prefix. */ + ParsePrefix(command, "0x"); + + /* Decode address. */ + const u64 address = DecodeHex(command); + + /* Get mapping. */ + svc::MemoryInfo mem_info; + if (R_FAILED(m_debug_process.QueryMemory(std::addressof(mem_info), address))) { + AppendReplyFormat(reply_cur, reply_end, "0x%016lx: No mapping.\n", address); + } + + const char *state = GetMemoryStateName(mem_info.state); + const char *perm = GetMemoryPermissionString(mem_info); + + const char l = (mem_info.attribute & svc::MemoryAttribute_Locked) ? 'L' : '-'; + const char i = (mem_info.attribute & svc::MemoryAttribute_IpcLocked) ? 'I' : '-'; + const char d = (mem_info.attribute & svc::MemoryAttribute_DeviceShared) ? 'D' : '-'; + const char u = (mem_info.attribute & svc::MemoryAttribute_Uncached) ? 'U' : '-'; + + AppendReplyFormat(reply_cur, reply_end, "0x%010lx - 0x%010lx %s %s %c%c%c%c [%d, %d]\n", mem_info.base_address, mem_info.base_address + mem_info.size - 1, perm, state, l, i, d, u, mem_info.ipc_count, mem_info.device_count); + } else if (ParsePrefix(command, "wait application") || ParsePrefix(command, "wait app")) { + /* Wait for an application process. */ + { + /* Get hook to creation of application process. */ + os::NativeHandle h; + R_ABORT_UNLESS(pm::dmnt::HookToCreateApplicationProcess(std::addressof(h))); + + /* Wait for event. */ + os::SystemEvent hook_event(h, true, os::InvalidNativeHandle, false, os::EventClearMode_AutoClear); + hook_event.Wait(); + } + + /* Get application process id. */ + R_ABORT_UNLESS(pm::dmnt::GetApplicationProcessId(std::addressof(m_wait_process_id))); + + /* Note that we're attaching. */ + AppendReplyFormat(reply_cur, reply_end, "Send `attach 0x%lx` to attach.\n", m_wait_process_id.value); + } else if (ParsePrefix(command, "wait ")) { + /* Allow optional "0x" prefix. */ + ParsePrefix(command, "0x"); + + /* Decode program id. */ + const ncm::ProgramId program_id(DecodeHex(command)); + + /* Wait for the process to be created. */ + { + /* Get hook to creation of process with program id. */ + os::NativeHandle h; + R_ABORT_UNLESS(pm::dmnt::HookToCreateProcess(std::addressof(h), program_id)); + + /* Wait for event. */ + os::SystemEvent hook_event(h, true, os::InvalidNativeHandle, false, os::EventClearMode_AutoClear); + hook_event.Wait(); + } + + /* Get process id. */ + R_ABORT_UNLESS(pm::dmnt::GetProcessId(std::addressof(m_wait_process_id), program_id)); + + /* Note that we're attaching. */ + AppendReplyFormat(reply_cur, reply_end, "Send `attach 0x%lx` to attach.\n", m_wait_process_id.value); + } else { + std::memcpy(m_reply_cur, command, std::strlen(command) + 1); + AppendReplyFormat(reply_cur, reply_end, "Unknown command `%s`\n", m_reply_cur); + } + } + + void GdbServerImpl::qSupported() { + /* Current string from devkita64-none-elf-gdb: */ + /* qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+ */ + + AppendReplyFormat(m_reply_cur, m_reply_end, "PacketSize=%lx", GdbPacketBufferSize - 1); + AppendReplyFormat(m_reply_cur, m_reply_end, ";multiprocess+"); + AppendReplyFormat(m_reply_cur, m_reply_end, ";qXfer:osdata:read+"); + AppendReplyFormat(m_reply_cur, m_reply_end, ";qXfer:features:read+"); + AppendReplyFormat(m_reply_cur, m_reply_end, ";qXfer:libraries:read+"); + // TODO: AppendReplyFormat(m_reply_cur, m_reply_end, ";qXfer:libraries-svr4:read+"); + // TODO: AppendReplyFormat(m_reply_cur, m_reply_end, ";augmented-libraries-svr4-read+"); + AppendReplyFormat(m_reply_cur, m_reply_end, ";qXfer:threads:read+"); + AppendReplyFormat(m_reply_cur, m_reply_end, ";qXfer:exec-file:read+"); + AppendReplyFormat(m_reply_cur, m_reply_end, ";swbreak+"); + AppendReplyFormat(m_reply_cur, m_reply_end, ";hwbreak+"); + AppendReplyFormat(m_reply_cur, m_reply_end, ";vContSupported+"); + AppendReplyFormat(m_reply_cur, m_reply_end, ";QStartNoAckMode+"); + } + + void GdbServerImpl::qXfer() { + /* Check for osdata. */ + if (ParsePrefix(m_receive_packet, "osdata:read:")) { + this->qXferOsdataRead(); + } else { + /* All other qXfer require debug process. */ + if (!this->HasDebugProcess()) { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + return; + } + + /* Process. */ + if (ParsePrefix(m_receive_packet, "features:read:")) { + this->qXferFeaturesRead(); + } else if (ParsePrefix(m_receive_packet, "threads:read::")) { + if (!this->qXferThreadsRead()) { + m_killed = true; + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } else if (ParsePrefix(m_receive_packet, "libraries:read::")) { + this->qXferLibrariesRead(); + } else if (ParsePrefix(m_receive_packet, "exec-file:read:")) { + AppendReplyFormat(m_reply_cur, m_reply_end, "l%s", m_debug_process.GetProcessName()); + } else { + AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented qxfer: %s\n", m_receive_packet); + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + } + + void GdbServerImpl::qXferFeaturesRead() { + /* Handle the qXfer. */ + u32 offset, length; + + if (ParsePrefix(m_receive_packet, "target.xml:")) { + /* Parse offset/length. */ + ParseOffsetLength(m_receive_packet, offset, length); + + /* Send the desired xml. */ + std::strncpy(m_reply_cur, (this->Is64Bit() ? TargetXmlAarch64 : TargetXmlAarch32) + offset, length); + m_reply_cur[length] = 0; + m_reply_cur += std::strlen(m_reply_cur); + } else if (ParsePrefix(m_receive_packet, "aarch64-core.xml:")) { + /* Parse offset/length. */ + ParseOffsetLength(m_receive_packet, offset, length); + + /* Send the desired xml. */ + std::strncpy(m_reply_cur, Aarch64CoreXml + offset, length); + m_reply_cur[length] = 0; + m_reply_cur += std::strlen(m_reply_cur); + } else if (ParsePrefix(m_receive_packet, "aarch64-fpu.xml:")) { + /* Parse offset/length. */ + ParseOffsetLength(m_receive_packet, offset, length); + + /* Send the desired xml. */ + std::strncpy(m_reply_cur, Aarch64FpuXml + offset, length); + m_reply_cur[length] = 0; + m_reply_cur += std::strlen(m_reply_cur); + } else if (ParsePrefix(m_receive_packet, "arm-core.xml:")) { + /* Parse offset/length. */ + ParseOffsetLength(m_receive_packet, offset, length); + + /* Send the desired xml. */ + std::strncpy(m_reply_cur, ArmCoreXml + offset, length); + m_reply_cur[length] = 0; + m_reply_cur += std::strlen(m_reply_cur); + } else if (ParsePrefix(m_receive_packet, "arm-vfp.xml:")) { + /* Parse offset/length. */ + ParseOffsetLength(m_receive_packet, offset, length); + + /* Send the desired xml. */ + std::strncpy(m_reply_cur, ArmVfpXml + offset, length); + m_reply_cur[length] = 0; + m_reply_cur += std::strlen(m_reply_cur); + } else { + AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented qxfer:features:read: %s\n", m_receive_packet); + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + + void GdbServerImpl::qXferLibrariesRead() { + /* Handle the qXfer. */ + u32 offset, length; + + /* Parse offset/length. */ + ParseOffsetLength(m_receive_packet, offset, length); + + /* Acquire access to the annex buffer. */ + std::scoped_lock lk(g_annex_buffer_lock); + + /* If doing a fresh read, generate the module list. */ + if (offset == 0 || g_annex_buffer_contents != AnnexBufferContents_Libraries) { + /* Prepare to write to annex buffer. */ + char *dst_cur = g_annex_buffer; + char *dst_end = g_annex_buffer + sizeof(g_annex_buffer); + + /* Set header. */ + AppendReplyFormat(dst_cur, dst_end, "<library-list>"); + + /* Get the module list. */ + for (size_t i = 0; i < m_debug_process.GetModuleCount(); ++i) { + AMS_DMNT2_GDB_LOG_DEBUG("Module[%zu]: %p, %s\n", i, reinterpret_cast<void *>(m_debug_process.GetModuleBaseAddress(i)), m_debug_process.GetModuleName(i)); + + const char *module_name = m_debug_process.GetModuleName(i); + const auto name_len = std::strlen(module_name); + if (name_len < 5 || (std::strcmp(module_name + name_len - 4, ".elf") != 0 && std::strcmp(module_name + name_len - 4, ".nss") != 0)) { + AppendReplyFormat(dst_cur, dst_end, "<library name=\"%s.elf\"><segment address=\"0x%lx\" /></library>", module_name, m_debug_process.GetModuleBaseAddress(i)); + } else { + AppendReplyFormat(dst_cur, dst_end, "<library name=\"%s\"><segment address=\"0x%lx\" /></library>", module_name, m_debug_process.GetModuleBaseAddress(i)); + } + } + + AppendReplyFormat(dst_cur, dst_end, "</library-list>"); + + g_annex_buffer_contents = AnnexBufferContents_Libraries; + } + + /* Copy out the module list. */ + GetAnnexBufferContents(m_reply_cur, offset, length); + } + + void GdbServerImpl::qXferOsdataRead() { + /* Handle the qXfer. */ + u32 offset, length; + + if (ParsePrefix(m_receive_packet, "processes:")) { + /* Parse offset/length. */ + ParseOffsetLength(m_receive_packet, offset, length); + + /* Acquire access to the annex buffer. */ + std::scoped_lock lk(g_annex_buffer_lock); + + /* If doing a fresh read, generate the process list. */ + if (offset == 0 || g_annex_buffer_contents != AnnexBufferContents_Processes) { + /* Prepare to write to annex buffer. */ + char *dst_cur = g_annex_buffer; + char *dst_end = g_annex_buffer + sizeof(g_annex_buffer); + + /* Clear the process list buffer. */ + dst_cur[0] = 0; + + /* Set header. */ + AppendReplyFormat(dst_cur, dst_end, "<?xml version=\"1.0\"?>\n<!DOCTYPE target SYSTEM \"osdata.dtd\">\n<osdata type=\"processes\">\n"); + + /* Get all processes. */ + { + /* Get all process ids. */ + u64 process_ids[0x50]; + s32 num_process_ids; + R_ABORT_UNLESS(svc::GetProcessList(std::addressof(num_process_ids), process_ids, util::size(process_ids))); + + /* Send all processes. */ + for (s32 i = 0; i < num_process_ids; ++i) { + svc::Handle handle; + if (R_SUCCEEDED(svc::DebugActiveProcess(std::addressof(handle), process_ids[i]))) { + ON_SCOPE_EXIT { R_ABORT_UNLESS(svc::CloseHandle(handle)); }; + + /* Get the create process event. */ + svc::DebugEventInfo d; + R_ABORT_UNLESS(svc::GetDebugEvent(std::addressof(d), handle)); + AMS_ABORT_UNLESS(d.type == svc::DebugEvent_CreateProcess); + + AppendReplyFormat(dst_cur, dst_end, "<item>\n<column name=\"pid\">%lu</column>\n<column name=\"command\">%s</column>\n</item>\n", d.info.create_process.process_id, d.info.create_process.name); + } + } + } + + /* Set footer. */ + AppendReplyFormat(dst_cur, dst_end, "</osdata>"); + + g_annex_buffer_contents = AnnexBufferContents_Processes; + } + + /* Copy out the process list. */ + GetAnnexBufferContents(m_reply_cur, offset, length); + } else { + AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented qxfer:osdata:read: %s\n", m_receive_packet); + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + + bool GdbServerImpl::qXferThreadsRead() { + /* Handle the qXfer. */ + u32 offset, length; + + /* Parse offset/length. */ + ParseOffsetLength(m_receive_packet, offset, length); + + /* Acquire access to the annex buffer. */ + std::scoped_lock lk(g_annex_buffer_lock); + + /* If doing a fresh read, generate the thread list. */ + if (offset == 0 || g_annex_buffer_contents != AnnexBufferContents_Threads) { + /* Prepare to write to annex buffer. */ + char *dst_cur = g_annex_buffer; + char *dst_end = g_annex_buffer + sizeof(g_annex_buffer); + + /* Set header. */ + AppendReplyFormat(dst_cur, dst_end, "<threads>"); + + /* Get the thread list. */ + u64 thread_ids[DebugProcess::ThreadCountMax]; + s32 num_threads; + if (R_SUCCEEDED(m_debug_process.GetThreadList(std::addressof(num_threads), thread_ids, util::size(thread_ids)))) { + for (auto i = 0; i < num_threads; ++i) { + /* Check that we can get the thread context. */ + { + svc::ThreadContext dummy_context; + if (R_FAILED(m_debug_process.GetThreadContext(std::addressof(dummy_context), thread_ids[i], svc::ThreadContextFlag_All))) { + continue; + } + } + + /* Get the thread core. */ + u32 core = 0; + m_debug_process.GetThreadCurrentCore(std::addressof(core), thread_ids[i]); + + /* Get the thread name. */ + char name[os::ThreadNameLengthMax + 1]; + m_debug_process.GetThreadName(name, thread_ids[i]); + name[sizeof(name) - 1] = '\x00'; + + /* Sanitize the thread name. */ + char sanitized[os::ThreadNameLengthMax * 4 + 1]; + { + char *cur = sanitized; + for (size_t i = 0; i < util::size(name); ++i) { + if (name[i] == '<') { + *(cur++) = '&'; + *(cur++) = 'l'; + *(cur++) = 't'; + *(cur++) = ';'; + } else if (name[i] == '>') { + *(cur++) = '&'; + *(cur++) = 'g'; + *(cur++) = 't'; + *(cur++) = ';'; + } else if (name[i] == '*' || name[i] == '#' || name[i] == '$' || name[i] == '}') { + *(cur++) = '}'; + *(cur++) = name[i] ^ 0x20; + } else { + *(cur++) = name[i]; + + if (name[i] == '\x00') { + break; + } + } + } + } + + /* Set the thread entry */ + AppendReplyFormat(dst_cur, dst_end, "<thread id=\"p%lx.%lx\" core=\"%u\" name=\"%s\" />", m_process_id.value, thread_ids[i], core, sanitized); + } + } + + AppendReplyFormat(dst_cur, dst_end, "</threads>"); + + g_annex_buffer_contents = AnnexBufferContents_Threads; + } + + /* Copy out the threads list. */ + GetAnnexBufferContents(m_reply_cur, offset, length); + + return true; + } + + void GdbServerImpl::z() { + /* Increment past the 'z'. */ + ++m_receive_packet; + + /* Decode the type. */ + if (!('0' <= m_receive_packet[0] && m_receive_packet[0] <= '4') || m_receive_packet[1] != ',') { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + return; + } + + const auto type = m_receive_packet[0] - '0'; + m_receive_packet += 2; + + /* Decode the address/length. */ + const char *comma = std::strchr(m_receive_packet, ','); + if (comma == nullptr) { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + return; + } + + /* Parse address/length. */ + const u64 address = DecodeHex(m_receive_packet); + const u64 length = DecodeHex(comma + 1); + + switch (type) { + case 0: /* SW */ + { + if (R_SUCCEEDED(m_debug_process.ClearBreakPoint(address, length))) { + AppendReplyOk(m_reply_cur, m_reply_end); + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + break; + case 1: /* HW */ + { + if (R_SUCCEEDED(m_debug_process.ClearHardwareBreakPoint(address, length))) { + AppendReplyOk(m_reply_cur, m_reply_end); + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + break; + case 2: /* Watch-W */ + case 3: /* Watch-R */ + case 4: /* Watch-A */ + { + if (R_SUCCEEDED(m_debug_process.ClearWatchPoint(address, length))) { + AppendReplyOk(m_reply_cur, m_reply_end); + } else { + AppendReplyError(m_reply_cur, m_reply_end, "E01"); + } + } + break; + default: + break; + } + } + + void GdbServerImpl::QuestionMark() { + if (m_debug_process.IsValid()) { + if (m_debug_process.GetLastThreadId() == 0) { + AppendReplyFormat(m_reply_cur, m_reply_end, "X01"); + } else { + this->AppendStopReplyPacket(m_debug_process.GetLastSignal()); + } + } else { + AppendReplyFormat(m_reply_cur, m_reply_end, "W00"); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_server_impl.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_server_impl.hpp new file mode 100644 index 00000000..e0648cd1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_server_impl.hpp @@ -0,0 +1,120 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "dmnt2_gdb_packet_io.hpp" +#include "dmnt2_gdb_signal.hpp" +#include "dmnt2_debug_process.hpp" + +namespace ams::dmnt { + + class GdbServerImpl { + private: + enum class State { + Initial, + Running, + Exited, + Destroyed, + }; + private: + int m_socket; + TransportSession m_session; + GdbPacketIo m_packet_io; + char *m_receive_packet{nullptr}; + char *m_reply_cur{nullptr}; + char *m_reply_end{nullptr}; + char m_buffer[GdbPacketBufferSize / 2]; + bool m_killed{false}; + os::ThreadType m_events_thread; + State m_state; + DebugProcess m_debug_process; + os::ProcessId m_process_id{os::InvalidProcessId}; + os::Event m_event; + os::ProcessId m_wait_process_id{os::InvalidProcessId}; + public: + GdbServerImpl(int socket, void *thread_stack, size_t stack_size); + ~GdbServerImpl(); + + void LoopProcess(); + private: + void ProcessPacket(char *receive, char *reply); + + void SendPacket(bool *out_break, const char *src) { return m_packet_io.SendPacket(out_break, src, std::addressof(m_session)); } + char *ReceivePacket(bool *out_break, char *dst, size_t size) { return m_packet_io.ReceivePacket(out_break, dst, size, std::addressof(m_session)); } + private: + bool HasDebugProcess() const { return m_debug_process.IsValid(); } + bool Is64Bit() const { return m_debug_process.Is64Bit(); } + bool Is64BitAddressSpace() const { return m_debug_process.Is64BitAddressSpace(); } + private: + static void DebugEventsThreadEntry(void *arg) { static_cast<GdbServerImpl *>(arg)->DebugEventsThread(); } + void DebugEventsThread(); + void ProcessDebugEvents(); + void AppendStopReplyPacket(GdbSignal signal); + private: + void D(); + + void G(); + + void H(); + void Hg(); + + void M(); + + void P(); + + void Q(); + + void QStartNoAckMode(); + + void T(); + + void Z(); + + void c(); + + bool g(); + + void k(); + + void m(); + + void p(); + + void v(); + + void vAttach(); + void vCont(); + + void q(); + + void qAttached(); + void qC(); + void qRcmd(); + void qSupported(); + void qXfer(); + void qXferFeaturesRead(); + void qXferLibrariesRead(); + void qXferOsdataRead(); + bool qXferThreadsRead(); + + void z(); + + void QuestionMark(); + private: + Result ParseVCont(char * const token, u64 *thread_ids, u8 *continue_modes, s32 num_threads, DebugProcess::ContinueMode &default_continue_mode); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_signal.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_signal.hpp new file mode 100644 index 00000000..4135f263 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_gdb_signal.hpp @@ -0,0 +1,34 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::dmnt { + + enum GdbSignal { + GdbSignal_Signal0 = 0, + GdbSignal_Interrupt = 2, + GdbSignal_IllegalInstruction = 4, + GdbSignal_BreakpointTrap = 5, + GdbSignal_EmulationTrap = 7, + GdbSignal_ArithmeticException = 8, + GdbSignal_Killed = 9, + GdbSignal_BusError = 10, + GdbSignal_SegmentationFault = 11, + GdbSignal_BadSystemCall = 12, + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_hardware_breakpoint.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_hardware_breakpoint.cpp new file mode 100644 index 00000000..56391882 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_hardware_breakpoint.cpp @@ -0,0 +1,237 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dmnt2_hardware_breakpoint.hpp" +#include "dmnt2_debug_process.hpp" +#include "dmnt2_debug_log.hpp" + +namespace ams::dmnt { + + namespace { + + constinit auto g_last_bp_register = -1; + constinit auto g_first_bp_ctx_register = -1; + constinit auto g_last_bp_ctx_register = -1; + constinit auto g_last_wp_register = -1; + + constinit os::SdkMutex g_multicore_lock; + constinit bool g_multicore_started = false; + + alignas(os::ThreadStackAlignment) constinit u8 g_multicore_thread_stack[16_KB]; + constinit os::ThreadType g_multicore_thread; + constinit os::MessageQueueType g_multicore_request_queue; + constinit os::MessageQueueType g_multicore_response_queue; + constinit uintptr_t g_multicore_request_storage[2]; + constinit uintptr_t g_multicore_response_storage[2]; + + void MultiCoreThread(void *) { + /* Get thread core mask. */ + s32 cur_core; + u64 core_mask; + R_ABORT_UNLESS(svc::GetThreadCoreMask(std::addressof(cur_core), std::addressof(core_mask), svc::PseudoHandle::CurrentThread)); + + /* Service requests. */ + while (true) { + /* Wait for a request to come in. */ + uintptr_t request_v; + os::ReceiveMessageQueue(std::addressof(request_v), std::addressof(g_multicore_request_queue)); + + /* Process the request. */ + const uintptr_t *request = reinterpret_cast<const uintptr_t *>(request_v); + bool success = true; + if (request[0] == 1) { + /* Set on each core. */ + for (s32 core = 0; core < 4; ++core) { + /* Switch to the desired core. */ + R_ABORT_UNLESS(svc::SetThreadCoreMask(svc::PseudoHandle::CurrentThread, core, (1 << core))); + + /* Get the core mask. */ + R_ABORT_UNLESS(svc::GetThreadCoreMask(std::addressof(cur_core), std::addressof(core_mask), svc::PseudoHandle::CurrentThread)); + + /* Set the breakpoint. */ + const Result result = svc::SetHardwareBreakPoint(static_cast<svc::HardwareBreakPointRegisterName>(request[1]), request[2], request[3]); + if (R_FAILED(result)) { + success = false; + AMS_DMNT2_GDB_LOG_ERROR("SetHardwareBreakPoint FAIL 0x%08x, core=%d, reg=%lu, ctrl=%lx, val=%lx\n", result.GetValue(), core, request[1], request[2], request[3]); + break; + } + } + } + + os::SendMessageQueue(std::addressof(g_multicore_response_queue), static_cast<uintptr_t>(success)); + } + } + + void EnsureMultiCoreStarted() { + std::scoped_lock lk(g_multicore_lock); + if (!g_multicore_started) { + os::InitializeMessageQueue(std::addressof(g_multicore_request_queue), g_multicore_request_storage, util::size(g_multicore_request_storage)); + os::InitializeMessageQueue(std::addressof(g_multicore_response_queue), g_multicore_response_storage, util::size(g_multicore_response_storage)); + + R_ABORT_UNLESS(os::CreateThread(std::addressof(g_multicore_thread), MultiCoreThread, nullptr, g_multicore_thread_stack, sizeof(g_multicore_thread_stack), os::HighestThreadPriority - 1)); + os::StartThread(std::addressof(g_multicore_thread)); + + g_multicore_started = true; + } + } + + } + + Result HardwareBreakPoint::Clear(DebugProcess *debug_process) { + AMS_UNUSED(debug_process); + + Result result = svc::ResultInvalidArgument(); + if (m_in_use) { + AMS_DMNT2_GDB_LOG_DEBUG("HardwareBreakPoint::Clear %p 0x%lx\n", this, m_address); + result = HardwareBreakPointManager::SetExecutionBreakPoint(m_reg, m_ctx, 0); + this->Reset(); + } + R_RETURN(result); + } + + Result HardwareBreakPoint::Set(DebugProcess *debug_process, uintptr_t address, size_t size, bool is_step) { + /* Set fields. */ + m_is_step = is_step; + m_address = address; + m_size = size; + + /* Set context breakpoint. */ + R_TRY(HardwareBreakPointManager::SetContextBreakPoint(m_ctx, debug_process)); + + /* Set execution breakpoint. */ + R_TRY(HardwareBreakPointManager::SetExecutionBreakPoint(m_reg, m_ctx, address)); + + /* Set as in-use. */ + m_in_use = true; + R_SUCCEED(); + } + + HardwareBreakPointManager::HardwareBreakPointManager(DebugProcess *debug_process) : BreakPointManager(debug_process) { + /* Determine the number of breakpoint registers. */ + CountBreakPointRegisters(); + + /* Initialize all breakpoints. */ + for (size_t i = 0; i < util::size(m_breakpoints); ++i) { + m_breakpoints[i].Initialize(static_cast<svc::HardwareBreakPointRegisterName>(svc::HardwareBreakPointRegisterName_I0 + i), static_cast<svc::HardwareBreakPointRegisterName>(g_first_bp_ctx_register)); + } + } + + BreakPointBase *HardwareBreakPointManager::GetBreakPoint(size_t index) { + if (index < util::size(m_breakpoints)) { + return m_breakpoints + index; + } else { + return nullptr; + } + } + + Result HardwareBreakPointManager::SetHardwareBreakPoint(u32 r, u64 dbgbcr, u64 value) { + /* Send request. */ + const uintptr_t request[4] = { + 1, + r, + dbgbcr, + value + }; + R_UNLESS(SendMultiCoreRequest(request), dmnt::ResultUnknown()); + + R_SUCCEED(); + } + + Result HardwareBreakPointManager::SetContextBreakPoint(svc::HardwareBreakPointRegisterName ctx, DebugProcess *debug_process) { + /* Encode the register. */ + const u64 dbgbcr = (0x3 << 20) | (0 << 16) | (0xF << 5) | 1; + + const Result result = SetHardwareBreakPoint(ctx, dbgbcr, debug_process->GetHandle()); + if (R_FAILED(result)) { + AMS_DMNT2_GDB_LOG_ERROR("SetContextBreakPoint FAIL 0x%08x ctx=%d\n", result.GetValue(), ctx); + } + + R_RETURN(result); + } + + svc::HardwareBreakPointRegisterName HardwareBreakPointManager::GetWatchPointContextRegister() { + CountBreakPointRegisters(); + return static_cast<svc::HardwareBreakPointRegisterName>(g_first_bp_ctx_register + 1); + } + + Result HardwareBreakPointManager::SetExecutionBreakPoint(svc::HardwareBreakPointRegisterName reg, svc::HardwareBreakPointRegisterName ctx, u64 address) { + /* Encode the register. */ + const u64 dbgbcr = (0x1 << 20) | (ctx << 16) | (0xF << 5) | ((address != 0) ? 1 : 0); + + const Result result = SetHardwareBreakPoint(reg, dbgbcr, address); + if (R_FAILED(result)) { + AMS_DMNT2_GDB_LOG_ERROR("SetContextBreakPoint FAIL 0x%08x reg=%d, ctx=%d, address=%lx\n", result.GetValue(), reg, ctx, address); + } + + R_RETURN(result); + } + + void HardwareBreakPointManager::CountBreakPointRegisters() { + /* Determine the valid breakpoint extents. */ + if (g_last_bp_ctx_register == -1) { + /* Keep setting until we see a failure. */ + for (int i = svc::HardwareBreakPointRegisterName_I0; i <= static_cast<int>(svc::HardwareBreakPointRegisterName_I15); ++i) { + if (R_FAILED(svc::SetHardwareBreakPoint(static_cast<svc::HardwareBreakPointRegisterName>(i), 0, 0))) { + break; + } + g_last_bp_register = i; + } + AMS_DMNT2_GDB_LOG_DEBUG("Last valid breakpoint=%d\n", g_last_bp_register); + + /* Determine the context register range. */ + const u64 dbgbcr = (0x3 << 20) | (0x0 << 16) | (0xF << 5) | 1; + + g_last_bp_ctx_register = g_last_bp_register; + for (int i = g_last_bp_ctx_register; i >= static_cast<int>(svc::HardwareBreakPointRegisterName_I0); --i) { + const Result result = svc::SetHardwareBreakPoint(static_cast<svc::HardwareBreakPointRegisterName>(i), dbgbcr, svc::PseudoHandle::CurrentProcess); + svc::SetHardwareBreakPoint(static_cast<svc::HardwareBreakPointRegisterName>(i), 0, 0); + + if (R_FAILED(result)) { + if (!svc::ResultInvalidHandle::Includes(result)) { + break; + } + } + + g_first_bp_ctx_register = i; + } + AMS_DMNT2_GDB_LOG_DEBUG("Context BreakPoints = %d-%d\n", g_first_bp_ctx_register, g_last_bp_ctx_register); + + /* Determine valid watchpoint registers. */ + for (int i = svc::HardwareBreakPointRegisterName_D0; i <= static_cast<int>(svc::HardwareBreakPointRegisterName_D15); ++i) { + if (R_FAILED(svc::SetHardwareBreakPoint(static_cast<svc::HardwareBreakPointRegisterName>(i), 0, 0))) { + break; + } + g_last_wp_register = i - svc::HardwareBreakPointRegisterName_D0; + } + AMS_DMNT2_GDB_LOG_DEBUG("Last valid watchpoint=%d\n", g_last_wp_register); + } + } + + bool HardwareBreakPointManager::SendMultiCoreRequest(const void *request) { + /* Ensure the multi core thread is active. */ + EnsureMultiCoreStarted(); + + /* Send the request. */ + os::SendMessageQueue(std::addressof(g_multicore_request_queue), reinterpret_cast<uintptr_t>(request)); + + /* Get the response. */ + uintptr_t response; + os::ReceiveMessageQueue(std::addressof(response), std::addressof(g_multicore_response_queue)); + + return static_cast<bool>(response); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_hardware_breakpoint.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_hardware_breakpoint.hpp new file mode 100644 index 00000000..8b32c50c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_hardware_breakpoint.hpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "dmnt2_breakpoint_manager.hpp" + +namespace ams::dmnt { + + struct HardwareBreakPoint : public BreakPoint { + svc::HardwareBreakPointRegisterName m_reg; + svc::HardwareBreakPointRegisterName m_ctx; + + void Initialize(svc::HardwareBreakPointRegisterName r, svc::HardwareBreakPointRegisterName c) { + m_reg = r; + m_ctx = c; + } + + virtual Result Clear(DebugProcess *debug_process) override; + virtual Result Set(DebugProcess *debug_process, uintptr_t address, size_t size, bool is_step) override; + }; + + class HardwareBreakPointManager : public BreakPointManager { + public: + static constexpr size_t BreakPointCountMax = 0x10; + private: + HardwareBreakPoint m_breakpoints[BreakPointCountMax]; + public: + static Result SetHardwareBreakPoint(u32 r, u64 dbgbcr, u64 value); + static Result SetContextBreakPoint(svc::HardwareBreakPointRegisterName ctx, DebugProcess *debug_process); + static svc::HardwareBreakPointRegisterName GetWatchPointContextRegister(); + static Result SetExecutionBreakPoint(svc::HardwareBreakPointRegisterName reg, svc::HardwareBreakPointRegisterName ctx, u64 address); + public: + explicit HardwareBreakPointManager(DebugProcess *debug_process); + private: + virtual BreakPointBase *GetBreakPoint(size_t index) override; + private: + static void CountBreakPointRegisters(); + + static bool SendMultiCoreRequest(const void *request); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_hardware_watchpoint.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_hardware_watchpoint.cpp new file mode 100644 index 00000000..00804dfa --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_hardware_watchpoint.cpp @@ -0,0 +1,169 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dmnt2_hardware_watchpoint.hpp" +#include "dmnt2_hardware_breakpoint.hpp" +#include "dmnt2_debug_process.hpp" +#include "dmnt2_debug_log.hpp" + +namespace ams::dmnt { + + namespace { + + Result SetDataBreakPoint(svc::HardwareBreakPointRegisterName reg, svc::HardwareBreakPointRegisterName ctx, u64 address, u64 size, bool read, bool write) { + /* Determine lsc. */ + const u8 lsc = (read ? 1 : 0) | (write ? 2 : 0); + + /* Check that the watchpoint is valid. */ + if (lsc != 0) { + R_UNLESS(HardwareWatchPointManager::IsValidWatchPoint(address, size), svc::ResultInvalidArgument()); + } + + /* Determine bas/mask. */ + u8 bas = 0, mask = 0; + if (size <= 8) { + bas = ((1 << size) - 1) << (address & 7); + address = util::AlignDown(address, 8); + } else { + bas = 0xFF; + mask = util::PopCount(size - 1); + } + + /* Build dbgbcr value. */ + const u64 dbgbcr = (mask << 24) | (ctx << 16) | (bas << 5) | (lsc << 3) | ((lsc != 0) ? 1 : 0); + + /* Set the breakpoint. */ + const Result result = HardwareBreakPointManager::SetHardwareBreakPoint(reg, dbgbcr, address); + if (R_FAILED(result)) { + AMS_DMNT2_GDB_LOG_ERROR("SetDataBreakPoint FAIL 0x%08x, reg=%d, address=0x%lx\n", result.GetValue(), reg, address); + } + R_RETURN(result); + } + + } + + bool HardwareWatchPointManager::IsValidWatchPoint(u64 address, u64 size) { + /* Check size. */ + if (size == 0) { + AMS_DMNT2_GDB_LOG_ERROR("HardwareWatchPointManager::IsValidWatchPoint(%lx, %lx) FAIL size == 0\n", address, size); + return false; + } + + /* Validate. */ + if (size <= 8) { + /* Check that address is aligned. */ + if (util::AlignDown(address, 8) != util::AlignDown(address + size - 1, 8)) { + AMS_DMNT2_GDB_LOG_ERROR("HardwareWatchPointManager::IsValidWatchPoint(%lx, %lx) FAIL range crosses qword boundary\n", address, size); + return false; + } + } else { + /* Check size is small enough. */ + if (size > 0x80000000) { + AMS_DMNT2_GDB_LOG_ERROR("HardwareWatchPointManager::IsValidWatchPoint(%lx, %lx) FAIL size too big\n", address, size); + return false; + } + + /* Check size is power of two. */ + if (!util::IsPowerOfTwo(size)) { + AMS_DMNT2_GDB_LOG_ERROR("HardwareWatchPointManager::IsValidWatchPoint(%lx, %lx) FAIL size not power of two\n", address, size); + return false; + } + + /* Check alignment. */ + if (!util::IsAligned(address, size)) { + AMS_DMNT2_GDB_LOG_ERROR("HardwareWatchPointManager::IsValidWatchPoint(%lx, %lx) FAIL address not size-aligned\n", address, size); + return false; + } + } + + return true; + } + + Result WatchPoint::Clear(DebugProcess *debug_process) { + AMS_UNUSED(debug_process); + + Result result = svc::ResultInvalidArgument(); + if (m_in_use) { + AMS_DMNT2_GDB_LOG_DEBUG("WatchPoint::Clear %p 0x%lx\n", this, m_address); + result = SetDataBreakPoint(m_reg, m_ctx, 0, 0, false, false); + this->Reset(); + } + R_RETURN(result); + } + + Result WatchPoint::Set(DebugProcess *debug_process, uintptr_t address, size_t size, bool read, bool write) { + /* Set fields. */ + m_address = address; + m_size = size; + m_read = read; + m_write = write; + + /* Set context breakpoint. */ + R_TRY(HardwareBreakPointManager::SetContextBreakPoint(m_ctx, debug_process)); + + /* Set watchpoint. */ + R_TRY(SetDataBreakPoint(m_reg, m_ctx, address, size, read, write)); + + /* Set as in-use. */ + m_in_use = true; + R_SUCCEED(); + } + + HardwareWatchPointManager::HardwareWatchPointManager(DebugProcess *debug_process) : BreakPointManagerBase(debug_process) { + const svc::HardwareBreakPointRegisterName ctx = HardwareBreakPointManager::GetWatchPointContextRegister(); + for (size_t i = 0; i < util::size(m_breakpoints); ++i) { + m_breakpoints[i].Initialize(static_cast<svc::HardwareBreakPointRegisterName>(svc::HardwareBreakPointRegisterName_D0 + i), ctx); + } + } + + BreakPointBase *HardwareWatchPointManager::GetBreakPoint(size_t index) { + if (index < util::size(m_breakpoints)) { + return m_breakpoints + index; + } else { + return nullptr; + } + } + + Result HardwareWatchPointManager::SetWatchPoint(u64 address, u64 size, bool read, bool write) { + /* Get a free watchpoint. */ + auto *bp = static_cast<WatchPoint *>(this->GetFreeBreakPoint()); + R_UNLESS(bp != nullptr, svc::ResultOutOfHandles()); + + /* Set the watchpoint. */ + R_RETURN(bp->Set(m_debug_process, address, size, read, write)); + } + + Result HardwareWatchPointManager::GetWatchPointInfo(u64 address, bool &read, bool &write) { + /* Find a matching watchpoint. */ + for (const auto &bp : m_breakpoints) { + if (bp.m_in_use) { + if (bp.m_address <= address && address < bp.m_address + bp.m_size) { + read = bp.m_read; + write = bp.m_write; + R_SUCCEED(); + } + } + } + + /* Otherwise, we failed. */ + AMS_DMNT2_GDB_LOG_ERROR("HardwareWatchPointManager::GetWatchPointInfo FAIL 0x%lx\n", address); + read = false; + write = false; + + R_THROW(svc::ResultInvalidArgument()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_hardware_watchpoint.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_hardware_watchpoint.hpp new file mode 100644 index 00000000..d04c5ff4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_hardware_watchpoint.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "dmnt2_breakpoint_manager_base.hpp" + +namespace ams::dmnt { + + struct WatchPoint : public BreakPointBase { + svc::HardwareBreakPointRegisterName m_reg; + svc::HardwareBreakPointRegisterName m_ctx; + bool m_read; + bool m_write; + + void Initialize(svc::HardwareBreakPointRegisterName r, svc::HardwareBreakPointRegisterName c) { + m_reg = r; + m_ctx = c; + } + + virtual Result Clear(DebugProcess *debug_process) override; + Result Set(DebugProcess *debug_process, uintptr_t address, size_t size, bool read, bool write); + }; + + class HardwareWatchPointManager : public BreakPointManagerBase { + public: + static constexpr size_t BreakPointCountMax = 0x10; + private: + WatchPoint m_breakpoints[BreakPointCountMax]; + public: + static bool IsValidWatchPoint(u64 address, u64 size); + public: + explicit HardwareWatchPointManager(DebugProcess *debug_process); + + Result SetWatchPoint(u64 address, u64 size, bool read, bool write); + Result GetWatchPointInfo(u64 address, bool &read, bool &write); + private: + virtual BreakPointBase *GetBreakPoint(size_t index) override; + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_main.cpp new file mode 100644 index 00000000..a5144848 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_main.cpp @@ -0,0 +1,93 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dmnt2_debug_log.hpp" +#include "dmnt2_gdb_server.hpp" +#include "dmnt2_transport_layer.hpp" + +namespace ams { + + namespace { + + bool IsHtcEnabled() { + u8 enable_htc = 0; + settings::fwdbg::GetSettingsItemValue(std::addressof(enable_htc), sizeof(enable_htc), "atmosphere", "enable_htc"); + return enable_htc != 0; + } + + bool IsStandaloneGdbstubEnabled() { + u8 enable_gdbstub = 0; + settings::fwdbg::GetSettingsItemValue(std::addressof(enable_gdbstub), sizeof(enable_gdbstub), "atmosphere", "enable_standalone_gdbstub"); + return enable_gdbstub != 0; + } + + } + + namespace init { + + void InitializeSystemModule() { + /* Initialize our connection to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + + /* Initialize other services we need. */ + R_ABORT_UNLESS(pmdmntInitialize()); + + /* Verify that we can sanely execute. */ + ams::CheckApiVersion(); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void Main() { + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(dmnt, Main)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(dmnt, Main)); + + bool use_htcs = false, use_tcp = false; + { + R_ABORT_UNLESS(::setsysInitialize()); + ON_SCOPE_EXIT { ::setsysExit(); }; + + use_htcs = IsHtcEnabled(); + use_tcp = IsStandaloneGdbstubEnabled(); + } + + /* Initialize transport layer. */ + if (use_htcs) { + dmnt::transport::InitializeByHtcs(); + } else if (use_tcp) { + dmnt::transport::InitializeByTcp(); + } else { + return; + } + + /* Initialize debug log thread. */ + dmnt::InitializeDebugLog(); + + /* Start GdbServer. */ + dmnt::InitializeGdbServer(); + + /* TODO */ + while (true) { + os::SleepThread(TimeSpan::FromDays(1)); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_module_definition.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_module_definition.hpp new file mode 100644 index 00000000..91324a59 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_module_definition.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::dmnt { + + class ModuleDefinition { + NON_MOVEABLE(ModuleDefinition); + public: + static constexpr size_t PathLengthMax = 0x200; + private: + char m_name[PathLengthMax]; + u64 m_address; + u64 m_size; + size_t m_name_start; + public: + constexpr ModuleDefinition() : m_name(), m_address(), m_size() { /* ... */ } + constexpr ~ModuleDefinition() { /* ... */ } + + constexpr ModuleDefinition(const ModuleDefinition &rhs) = default; + constexpr ModuleDefinition &operator=(const ModuleDefinition &rhs) = default; + + constexpr void Reset() { + m_name[0] = 0; + m_address = 0; + m_size = 0; + m_name_start = 0; + } + + constexpr bool operator==(const ModuleDefinition &rhs) const { + return m_address == rhs.m_address && m_size == rhs.m_size && std::strcmp(m_name, rhs.m_name) == 0; + } + + constexpr bool operator!=(const ModuleDefinition &rhs) const { + return !(*this == rhs); + } + + constexpr char *GetNameBuffer() { return m_name; } + constexpr const char *GetName() const { return m_name + m_name_start; } + + constexpr u64 GetAddress() const { return m_address; } + constexpr u64 GetSize() const { return m_size; } + + constexpr void SetAddressSize(u64 address, u64 size) { + m_address = address; + m_size = size; + } + + constexpr void SetNameStart(size_t offset) { + m_name_start = offset; + } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_software_breakpoint.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_software_breakpoint.cpp new file mode 100644 index 00000000..c772d284 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_software_breakpoint.cpp @@ -0,0 +1,95 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dmnt2_software_breakpoint.hpp" +#include "dmnt2_debug_process.hpp" +#include "dmnt2_debug_log.hpp" + +namespace ams::dmnt { + + namespace { + + constexpr const u8 Aarch64BreakInstruction[] = {0xFF, 0xFF, 0xFF, 0xE7}; + constexpr const u8 Aarch32BreakInstruction[] = {0xFE, 0xDE, 0xFF, 0xE7}; + constexpr const u8 Aarch32ThumbBreakInstruction[] = {0x80, 0xB6}; + + } + + Result SoftwareBreakPoint::Clear(DebugProcess *debug_process) { + Result result = svc::ResultInvalidArgument(); + if (m_in_use) { + if (m_address) { + result = debug_process->WriteMemory(std::addressof(m_insn), m_address, m_size); + if (R_SUCCEEDED(result)) { + AMS_DMNT2_GDB_LOG_DEBUG("SoftwareBreakPoint::Clear %p 0x%lx, insn=0x%x\n", this, m_address, m_insn); + } else { + AMS_DMNT2_GDB_LOG_DEBUG("SoftwareBreakPoint::Clear %p 0x%lx, insn=0x%x, !!! Fail %08x !!!\n", this, m_address, m_insn, result.GetValue()); + } + this->Reset(); + } else { + AMS_DMNT2_GDB_LOG_ERROR("SoftwareBreakPoint::Clear %p 0x%lx, insn=0x%x, !!! Null Address !!!\n", this, m_address, m_insn); + } + } + R_RETURN(result); + } + + Result SoftwareBreakPoint::Set(DebugProcess *debug_process, uintptr_t address, size_t size, bool is_step) { + /* Set fields. */ + m_is_step = is_step; + m_address = address; + m_size = size; + + /* Read our instruction. */ + Result result = debug_process->ReadMemory(std::addressof(m_insn), m_address, m_size); + AMS_DMNT2_GDB_LOG_DEBUG("SoftwareBreakPoint::Set %p 0x%lx, insn=0x%x\n", this, m_address, m_insn); + + /* Set the breakpoint. */ + if (debug_process->Is64Bit()) { + if (m_size == sizeof(Aarch64BreakInstruction)) { + result = debug_process->WriteMemory(Aarch64BreakInstruction, m_address, m_size); + } else { + result = svc::ResultInvalidArgument(); + } + } else { + if (m_size == sizeof(Aarch32BreakInstruction) || m_size == sizeof(Aarch32ThumbBreakInstruction)) { + result = debug_process->WriteMemory(m_size == sizeof(Aarch32BreakInstruction) ? Aarch32BreakInstruction : Aarch32ThumbBreakInstruction, m_address, m_size); + } else { + result = svc::ResultInvalidArgument(); + } + } + + /* Check that we succeeded. */ + if (R_SUCCEEDED(result)) { + m_in_use = true; + } + + R_RETURN(result); + } + + SoftwareBreakPointManager::SoftwareBreakPointManager(DebugProcess *debug_process) : BreakPointManager(debug_process) { + /* ... */ + } + + BreakPointBase *SoftwareBreakPointManager::GetBreakPoint(size_t index) { + if (index < util::size(m_breakpoints)) { + return m_breakpoints + index; + } else { + return nullptr; + } + } + + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_software_breakpoint.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_software_breakpoint.hpp new file mode 100644 index 00000000..1550851b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_software_breakpoint.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "dmnt2_breakpoint_manager.hpp" + +namespace ams::dmnt { + + struct SoftwareBreakPoint : public BreakPoint { + u32 m_insn; + + virtual Result Clear(DebugProcess *debug_process) override; + virtual Result Set(DebugProcess *debug_process, uintptr_t address, size_t size, bool is_step) override; + }; + + class SoftwareBreakPointManager : public BreakPointManager { + public: + static constexpr size_t BreakPointCountMax = 0x80; + private: + SoftwareBreakPoint m_breakpoints[BreakPointCountMax]; + public: + explicit SoftwareBreakPointManager(DebugProcess *debug_process); + private: + virtual BreakPointBase *GetBreakPoint(size_t index) override; + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_transport_layer.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_transport_layer.cpp new file mode 100644 index 00000000..b6d473b4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_transport_layer.cpp @@ -0,0 +1,196 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dmnt2_transport_layer.hpp" + +namespace ams::dmnt::transport { + + namespace { + + enum SocketMode { + SocketMode_Invalid, + SocketMode_Htcs, + SocketMode_Tcp, + }; + + constexpr inline const u16 ListenPort_GdbServer = 22225; + constexpr inline const u16 ListenPort_GdbDebugLog = 22227; + + constinit os::SdkMutex g_socket_init_mutex; + constinit SocketMode g_socket_mode = SocketMode_Invalid; + + constexpr inline size_t RequiredAlignment = std::max(os::ThreadStackAlignment, os::MemoryPageSize); + + using SocketConfigType = socket::SystemConfigLightDefault; + + /* TODO: If we ever use resolvers, increase this. */ + constexpr inline size_t SocketAllocatorSize = 4_KB; + constexpr inline size_t SocketMemoryPoolSize = util::AlignUp(SocketConfigType::PerTcpSocketWorstCaseMemoryPoolSize + SocketConfigType::PerUdpSocketWorstCaseMemoryPoolSize, os::MemoryPageSize); + + constexpr inline size_t SocketRequiredSize = util::AlignUp(SocketMemoryPoolSize + SocketAllocatorSize, os::MemoryPageSize); + + /* Declare the memory pool. */ + alignas(RequiredAlignment) constinit u8 g_socket_memory[SocketRequiredSize]; + + constexpr inline const SocketConfigType SocketConfig(g_socket_memory, SocketRequiredSize, SocketAllocatorSize, 2); + + } + + void InitializeByHtcs() { + std::scoped_lock lk(g_socket_init_mutex); + AMS_ABORT_UNLESS(g_socket_mode == SocketMode_Invalid); + + constexpr auto HtcsSocketCountMax = 8; + const size_t buffer_size = htcs::GetWorkingMemorySize(HtcsSocketCountMax); + AMS_ABORT_UNLESS(sizeof(g_socket_memory) >= buffer_size); + htcs::InitializeForSystem(g_socket_memory, sizeof(g_socket_memory), HtcsSocketCountMax); + + g_socket_mode = SocketMode_Htcs; + } + + void InitializeByTcp() { + std::scoped_lock lk(g_socket_init_mutex); + AMS_ABORT_UNLESS(g_socket_mode == SocketMode_Invalid); + + R_ABORT_UNLESS(socket::Initialize(SocketConfig)); + + g_socket_mode = SocketMode_Tcp; + } + + s32 Socket() { + switch (g_socket_mode) { + case SocketMode_Htcs: return htcs::Socket(); + case SocketMode_Tcp: return socket::Socket(socket::Family::Af_Inet, socket::Type::Sock_Stream, socket::Protocol::IpProto_Tcp); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + s32 Close(s32 desc) { + switch (g_socket_mode) { + case SocketMode_Htcs: return htcs::Close(desc); + case SocketMode_Tcp: return socket::Close(desc); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + s32 Bind(s32 desc, PortName port_name) { + switch (g_socket_mode) { + case SocketMode_Htcs: + { + htcs::SockAddrHtcs addr; + addr.family = htcs::HTCS_AF_HTCS; + addr.peer_name = htcs::GetPeerNameAny(); + switch (port_name) { + case PortName_GdbServer: std::strcpy(addr.port_name.name, "iywys@$gdb"); break; + case PortName_GdbDebugLog: std::strcpy(addr.port_name.name, "iywys@$dmnt2_log"); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + return htcs::Bind(desc, std::addressof(addr)); + } + break; + case SocketMode_Tcp: + { + socket::SockAddrIn addr = {}; + addr.sin_family = socket::Family::Af_Inet; + addr.sin_addr.s_addr = socket::InAddr_Any; + + switch (port_name){ + case PortName_GdbServer: addr.sin_port = socket::InetHtons(static_cast<u16>(ListenPort_GdbServer)); break; + case PortName_GdbDebugLog: addr.sin_port = socket::InetHtons(static_cast<u16>(ListenPort_GdbDebugLog)); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + return socket::Bind(desc, reinterpret_cast<socket::SockAddr *>(std::addressof(addr)), sizeof(addr)); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + s32 Listen(s32 desc, s32 backlog_count) { + switch (g_socket_mode) { + case SocketMode_Htcs: return htcs::Listen(desc, backlog_count); + case SocketMode_Tcp: return socket::Listen(desc, backlog_count); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + s32 Accept(s32 desc) { + switch (g_socket_mode) { + case SocketMode_Htcs: + { + htcs::SockAddrHtcs addr; + addr.family = htcs::HTCS_AF_HTCS; + addr.peer_name = htcs::GetPeerNameAny(); + addr.port_name.name[0] = '\x00'; + + return htcs::Accept(desc, std::addressof(addr)); + } + break; + case SocketMode_Tcp: + { + socket::SockAddrIn addr = {}; + socket::SockLenT addr_len = sizeof(addr); + + return socket::Accept(desc, reinterpret_cast<socket::SockAddr *>(std::addressof(addr)), std::addressof(addr_len)); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + s32 Shutdown(s32 desc) { + switch (g_socket_mode) { + case SocketMode_Htcs: return htcs::Shutdown(desc, htcs::HTCS_SHUT_RDWR); + case SocketMode_Tcp: return socket::Shutdown(desc, socket::ShutdownMethod::Shut_RdWr); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + ssize_t Recv(s32 desc, void *buffer, size_t buffer_size, s32 flags) { + switch (g_socket_mode) { + case SocketMode_Htcs: return htcs::Recv(desc, buffer, buffer_size, flags); + case SocketMode_Tcp: return socket::Recv(desc, buffer, buffer_size, static_cast<socket::MsgFlag>(flags)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + ssize_t Send(s32 desc, const void *buffer, size_t buffer_size, s32 flags) { + switch (g_socket_mode) { + case SocketMode_Htcs: return htcs::Send(desc, buffer, buffer_size, flags); + case SocketMode_Tcp: return socket::Send(desc, buffer, buffer_size, static_cast<socket::MsgFlag>(flags)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + s32 GetLastError() { + switch (g_socket_mode) { + case SocketMode_Htcs: return htcs::GetLastError(); + case SocketMode_Tcp: return static_cast<s32>(socket::GetLastError()); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + bool IsLastErrorEAgain() { + switch (g_socket_mode) { + case SocketMode_Htcs: return htcs::GetLastError() == htcs::HTCS_EAGAIN; + case SocketMode_Tcp: return socket::GetLastError() == socket::Errno::EAgain; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_transport_layer.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_transport_layer.hpp new file mode 100644 index 00000000..364b7283 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_transport_layer.hpp @@ -0,0 +1,42 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::dmnt::transport { + + void InitializeByHtcs(); + void InitializeByTcp(); + + enum PortName { + PortName_GdbServer, + PortName_GdbDebugLog, + }; + + s32 Socket(); + s32 Close(s32 desc); + s32 Bind(s32 desc, PortName port_name); + s32 Listen(s32 desc, s32 backlog_count); + s32 Accept(s32 desc); + s32 Shutdown(s32 desc); + + ssize_t Recv(s32 desc, void *buffer, size_t buffer_size, s32 flags); + ssize_t Send(s32 desc, const void *buffer, size_t buffer_size, s32 flags); + + s32 GetLastError(); + bool IsLastErrorEAgain(); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_transport_receive_buffer.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_transport_receive_buffer.cpp new file mode 100644 index 00000000..93ebffe6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_transport_receive_buffer.cpp @@ -0,0 +1,146 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dmnt2_transport_receive_buffer.hpp" + +namespace ams::dmnt { + + ssize_t TransportReceiveBuffer::Read(void *dst, size_t size) { + /* Acquire exclusive access to ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're readable and valid. */ + if (!(this->IsValid() && this->IsReadable())) { + return -1; + } + + /* Check that we have data to read. */ + const size_t readable = std::min(size, m_readable_size); + if (readable <= 0) { + return -1; + } + + /* Copy the data. */ + std::memcpy(dst, m_buffer + m_offset, readable); + + /* Advance our pointers. */ + m_readable_size -= readable; + m_offset += readable; + + /* Handle the case where we're done consuming. */ + if (m_readable_size == 0) { + m_offset = 0; + m_readable_event.Clear(); + m_writable_event.Signal(); + } + + return readable; + } + + ssize_t TransportReceiveBuffer::Write(const void *src, size_t size) { + /* Acquire exclusive access to ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that we're readable and valid. */ + if (!(this->IsValid() && this->IsWritable())) { + return -1; + } + + /* Copy the data to our buffer. */ + std::memcpy(m_buffer, src, size); + + /* Set our fields. */ + m_readable_size = size; + m_offset = 0; + m_writable_event.Clear(); + m_readable_event.Signal(); + + return size; + } + + bool TransportReceiveBuffer::WaitToBeReadable() { + /* Check if we're already readable. */ + { + std::scoped_lock lk(m_mutex); + + if (this->IsReadable()) { + return true; + } else if (!this->IsValid()) { + return false; + } else { + m_readable_event.Clear(); + } + } + + /* Wait for us to be readable. */ + m_readable_event.Wait(); + + return this->IsValid(); + } + + bool TransportReceiveBuffer::WaitToBeReadable(TimeSpan timeout) { + /* Check if we're already readable. */ + { + std::scoped_lock lk(m_mutex); + + if (this->IsReadable()) { + return true; + } else if (!this->IsValid()) { + return false; + } else { + m_readable_event.Clear(); + } + } + + /* Wait for us to be readable. */ + const bool res = m_readable_event.TimedWait(timeout); + + return res && this->IsValid(); + } + + bool TransportReceiveBuffer::WaitToBeWritable() { + /* Check if we're already writable. */ + { + std::scoped_lock lk(m_mutex); + + if (this->IsWritable()) { + return true; + } else if (!this->IsValid()) { + return false; + } else { + m_writable_event.Clear(); + } + } + + /* Wait for us to be writable. */ + m_writable_event.Wait(); + + return this->IsValid(); + } + + void TransportReceiveBuffer::Invalidate() { + /* Acquire exclusive access to ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Set ourselves as invalid. */ + m_valid = false; + + /* Signal our events. */ + m_readable_event.Signal(); + m_writable_event.Signal(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_transport_receive_buffer.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_transport_receive_buffer.hpp new file mode 100644 index 00000000..363ff79a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_transport_receive_buffer.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::dmnt { + + class TransportReceiveBuffer { + public: + static constexpr size_t ReceiveBufferSize = 4_KB; + private: + u8 m_buffer[ReceiveBufferSize]; + os::Event m_readable_event; + os::Event m_writable_event; + os::SdkMutex m_mutex; + size_t m_readable_size; + size_t m_offset; + bool m_valid; + public: + TransportReceiveBuffer() : m_readable_event(os::EventClearMode_ManualClear), m_writable_event(os::EventClearMode_ManualClear), m_mutex(), m_readable_size(), m_offset(), m_valid(true) { /* ... */ } + + ALWAYS_INLINE bool IsReadable() const { return m_readable_size != 0; } + ALWAYS_INLINE bool IsWritable() const { return m_readable_size == 0; } + ALWAYS_INLINE bool IsValid() const { return m_valid; } + + ssize_t Read(void *dst, size_t size); + ssize_t Write(const void *src, size_t size); + + bool WaitToBeReadable(); + bool WaitToBeReadable(TimeSpan timeout); + bool WaitToBeWritable(); + + void Invalidate(); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_transport_session.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_transport_session.cpp new file mode 100644 index 00000000..a500f717 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_transport_session.cpp @@ -0,0 +1,128 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dmnt2_transport_layer.hpp" +#include "dmnt2_transport_session.hpp" +#include "dmnt2_debug_log.hpp" + +namespace ams::dmnt { + + TransportSession::TransportSession(int fd) : m_socket(fd), m_valid(true) { + /* Create our thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_receive_thread), ReceiveThreadEntry, this, m_receive_thread_stack, sizeof(m_receive_thread_stack), os::HighestThreadPriority - 1)); + + /* Start our thread. */ + os::StartThread(std::addressof(m_receive_thread)); + + /* Note that we connected. */ + AMS_DMNT2_GDB_LOG_INFO("Created Session %d\n", m_socket); + } + + TransportSession::~TransportSession() { + /* Note that we connected. */ + AMS_DMNT2_GDB_LOG_INFO("Closing Session %d\n", m_socket); + + /* Invalidate our receive buffer. */ + m_receive_buffer.Invalidate(); + + /* Shutdown our socket. */ + transport::Shutdown(m_socket); + + /* Wait for our thread. */ + os::WaitThread(std::addressof(m_receive_thread)); + os::DestroyThread(std::addressof(m_receive_thread)); + + /* Close our socket. */ + transport::Close(m_socket); + } + + bool TransportSession::WaitToBeReadable() { + return m_receive_buffer.WaitToBeReadable(); + } + + bool TransportSession::WaitToBeReadable(TimeSpan timeout) { + return m_receive_buffer.WaitToBeReadable(timeout); + } + + util::optional<char> TransportSession::GetChar() { + /* Wait for us to have data. */ + m_receive_buffer.WaitToBeReadable(); + + /* Get our data. */ + char c; + if (m_receive_buffer.Read(std::addressof(c), sizeof(c)) > 0) { + return c; + } else { + return util::nullopt; + } + } + + ssize_t TransportSession::PutChar(char c) { + /* Send the character. */ + const auto sent = transport::Send(m_socket, std::addressof(c), sizeof(c), 0); + if (sent < 0) { + m_valid = false; + } + + return sent; + } + + ssize_t TransportSession::PutString(const char *str) { + /* Repeatedly send until all is sent. */ + const size_t len = std::strlen(str); + + size_t remaining = len; + while (remaining > 0) { + const auto sent = transport::Send(m_socket, str, remaining, 0); + if (sent >= 0) { + remaining -= sent; + str += sent; + } else { + m_valid = false; + return sent; + } + } + + return len; + } + + void TransportSession::ReceiveThreadFunction() { + /* Create temporary buffer. */ + u8 buffer[TransportReceiveBuffer::ReceiveBufferSize]; + + /* Loop receiving data. */ + while (true) { + /* Receive data. */ + const auto res = transport::Recv(m_socket, buffer, sizeof(buffer), 0); + if (res > 0) { + /* Write the data to our buffer. */ + m_receive_buffer.WaitToBeWritable(); + m_receive_buffer.Write(buffer, res); + } else { + /* Otherwise, if we got an error other than "try again", we're done. */ + if (!transport::IsLastErrorEAgain()) { + AMS_DMNT2_GDB_LOG_INFO("Session %d invalid, res=%ld, err=%d\n", m_socket, res, static_cast<int>(transport::GetLastError())); + m_valid = false; + break; + } + } + } + + /* Invalidate our receive buffer. */ + m_receive_buffer.Invalidate(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_transport_session.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_transport_session.hpp new file mode 100644 index 00000000..e19fec38 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/source/dmnt2_transport_session.hpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "dmnt2_transport_receive_buffer.hpp" + +namespace ams::dmnt { + + class TransportSession { + private: + alignas(os::ThreadStackAlignment) u8 m_receive_thread_stack[util::AlignUp(os::MemoryPageSize + TransportReceiveBuffer::ReceiveBufferSize, os::ThreadStackAlignment)]; + TransportReceiveBuffer m_receive_buffer; + os::ThreadType m_receive_thread; + int m_socket; + bool m_valid; + public: + TransportSession(int fd); + ~TransportSession(); + + ALWAYS_INLINE bool IsValid() const { return m_valid; } + + bool WaitToBeReadable(); + bool WaitToBeReadable(TimeSpan timeout); + + util::optional<char> GetChar(); + ssize_t PutChar(char c); + ssize_t PutString(const char *str); + + private: + static void ReceiveThreadEntry(void *arg) { + static_cast<TransportSession *>(arg)->ReceiveThreadFunction(); + } + + void ReceiveThreadFunction(); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/system_module.mk new file mode 100644 index 00000000..d6de42ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt.gen2/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := nsp + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/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)/system_module.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)/system_module.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/stratosphere/dmnt/dmnt.json b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/dmnt.json new file mode 100644 index 00000000..ecd42b1a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/dmnt.json @@ -0,0 +1,124 @@ +{ + "name": "dmnt", + "title_id": "0x010000000000000d", + "title_id_range_min": "0x010000000000000d", + "title_id_range_max": "0x010000000000000d", + "main_thread_stack_size": "0x00004000", + "main_thread_priority": 39, + "default_cpu_id": 3, + "process_category": 0, + "is_retail": true, + "pool_partition": 2, + "is_64_bit": true, + "address_space_type": 3, + "disable_device_address_space_merge": true, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_access": [ + "pm:dmnt", + "pm:info", + "ldr:dmnt", + "ro:dmnt", + "ns:dev", + "spl:", + "lr", + "htc", + "bsd:s", + "sfdnsres", + "bsdcfg", + "set", + "set:sys", + "fsp-srv", + "fatal:u", + "hid" + ], + "service_host": [ + "dmnt:-", + "dmnt:cht" + ], + "kernel_capabilities": [{ + "type": "kernel_flags", + "value": { + "highest_thread_priority": 63, + "lowest_thread_priority": 24, + "lowest_cpu_id": 0, + "highest_cpu_id": 3 + } + }, { + "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", + "svcSynchronizePreemptionState": "0x36", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcDebugActiveProcess": "0x60", + "svcBreakDebugProcess": "0x61", + "svcTerminateDebugProcess": "0x62", + "svcGetDebugEvent": "0x63", + "svcContinueDebugEvent": "0x64", + "svcGetProcessList": "0x65", + "svcGetThreadList": "0x66", + "svcGetDebugThreadContext": "0x67", + "svcSetDebugThreadContext": "0x68", + "svcQueryDebugProcessMemory": "0x69", + "svcReadDebugProcessMemory": "0x6a", + "svcWriteDebugProcessMemory": "0x6b", + "svcSetHardwareBreakPoint": "0x6c", + "svcGetDebugThreadParam": "0x6d", + "svcCallSecureMonitor": "0x7f" + } + }, { + "type": "min_kernel_version", + "value": "0x0030" + }, { + "type": "handle_table_size", + "value": 0 + }] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/dmnt_cheat_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/dmnt_cheat_service.cpp new file mode 100644 index 00000000..3327dfa4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/dmnt_cheat_service.cpp @@ -0,0 +1,156 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dmnt_cheat_service.hpp" +#include "impl/dmnt_cheat_api.hpp" + +namespace ams::dmnt::cheat { + + /* ========================================================================================= */ + /* ==================================== Meta Commands ==================================== */ + /* ========================================================================================= */ + + void CheatService::HasCheatProcess(sf::Out<bool> out) { + out.SetValue(dmnt::cheat::impl::GetHasActiveCheatProcess()); + } + + void CheatService::GetCheatProcessEvent(sf::OutCopyHandle out_event) { + out_event.SetValue(dmnt::cheat::impl::GetCheatProcessEventHandle(), false); + } + + Result CheatService::GetCheatProcessMetadata(sf::Out<CheatProcessMetadata> out_metadata) { + R_RETURN(dmnt::cheat::impl::GetCheatProcessMetadata(out_metadata.GetPointer())); + } + + Result CheatService::ForceOpenCheatProcess() { + R_UNLESS(R_SUCCEEDED(dmnt::cheat::impl::ForceOpenCheatProcess()), dmnt::cheat::ResultCheatNotAttached()); + R_SUCCEED(); + } + + Result CheatService::PauseCheatProcess() { + R_RETURN(dmnt::cheat::impl::PauseCheatProcess()); + } + + Result CheatService::ResumeCheatProcess() { + R_RETURN(dmnt::cheat::impl::ResumeCheatProcess()); + } + + Result CheatService::ForceCloseCheatProcess() { + R_RETURN(dmnt::cheat::impl::ForceCloseCheatProcess()); + } + + /* ========================================================================================= */ + /* =================================== Memory Commands =================================== */ + /* ========================================================================================= */ + + Result CheatService::GetCheatProcessMappingCount(sf::Out<u64> out_count) { + R_RETURN(dmnt::cheat::impl::GetCheatProcessMappingCount(out_count.GetPointer())); + } + + Result CheatService::GetCheatProcessMappings(const sf::OutArray<svc::MemoryInfo> &mappings, sf::Out<u64> out_count, u64 offset) { + R_UNLESS(mappings.GetPointer() != nullptr, dmnt::cheat::ResultCheatNullBuffer()); + R_RETURN(dmnt::cheat::impl::GetCheatProcessMappings(mappings.GetPointer(), mappings.GetSize(), out_count.GetPointer(), offset)); + } + + Result CheatService::ReadCheatProcessMemory(const sf::OutBuffer &buffer, u64 address, u64 out_size) { + R_UNLESS(buffer.GetPointer() != nullptr, dmnt::cheat::ResultCheatNullBuffer()); + R_RETURN(dmnt::cheat::impl::ReadCheatProcessMemory(address, buffer.GetPointer(), std::min(out_size, buffer.GetSize()))); + } + + Result CheatService::WriteCheatProcessMemory(const sf::InBuffer &buffer, u64 address, u64 in_size) { + R_UNLESS(buffer.GetPointer() != nullptr, dmnt::cheat::ResultCheatNullBuffer()); + R_RETURN(dmnt::cheat::impl::WriteCheatProcessMemory(address, buffer.GetPointer(), std::min(in_size, buffer.GetSize()))); + } + + Result CheatService::QueryCheatProcessMemory(sf::Out<svc::MemoryInfo> mapping, u64 address) { + R_RETURN(dmnt::cheat::impl::QueryCheatProcessMemory(mapping.GetPointer(), address)); + } + + /* ========================================================================================= */ + /* =================================== Cheat Commands ==================================== */ + /* ========================================================================================= */ + + Result CheatService::GetCheatCount(sf::Out<u64> out_count) { + R_RETURN(dmnt::cheat::impl::GetCheatCount(out_count.GetPointer())); + } + + Result CheatService::GetCheats(const sf::OutArray<CheatEntry> &cheats, sf::Out<u64> out_count, u64 offset) { + R_UNLESS(cheats.GetPointer() != nullptr, dmnt::cheat::ResultCheatNullBuffer()); + R_RETURN(dmnt::cheat::impl::GetCheats(cheats.GetPointer(), cheats.GetSize(), out_count.GetPointer(), offset)); + } + + Result CheatService::GetCheatById(sf::Out<CheatEntry> cheat, u32 cheat_id) { + R_RETURN(dmnt::cheat::impl::GetCheatById(cheat.GetPointer(), cheat_id)); + } + + Result CheatService::ToggleCheat(u32 cheat_id) { + R_RETURN(dmnt::cheat::impl::ToggleCheat(cheat_id)); + } + + Result CheatService::AddCheat(const CheatDefinition &cheat, sf::Out<u32> out_cheat_id, bool enabled) { + R_RETURN(dmnt::cheat::impl::AddCheat(out_cheat_id.GetPointer(), cheat, enabled)); + } + + Result CheatService::RemoveCheat(u32 cheat_id) { + R_RETURN(dmnt::cheat::impl::RemoveCheat(cheat_id)); + } + + Result CheatService::ReadStaticRegister(sf::Out<u64> out, u8 which) { + R_RETURN(dmnt::cheat::impl::ReadStaticRegister(out.GetPointer(), which)); + } + + Result CheatService::WriteStaticRegister(u8 which, u64 value) { + R_RETURN(dmnt::cheat::impl::WriteStaticRegister(which, value)); + } + + Result CheatService::ResetStaticRegisters() { + R_RETURN(dmnt::cheat::impl::ResetStaticRegisters()); + } + + Result CheatService::SetMasterCheat(const CheatDefinition &cheat) { + R_RETURN(dmnt::cheat::impl::SetMasterCheat(cheat)); + } + + /* ========================================================================================= */ + /* =================================== Address Commands ================================== */ + /* ========================================================================================= */ + + Result CheatService::GetFrozenAddressCount(sf::Out<u64> out_count) { + R_RETURN(dmnt::cheat::impl::GetFrozenAddressCount(out_count.GetPointer())); + } + + Result CheatService::GetFrozenAddresses(const sf::OutArray<FrozenAddressEntry> &addresses, sf::Out<u64> out_count, u64 offset) { + R_UNLESS(addresses.GetPointer() != nullptr, dmnt::cheat::ResultCheatNullBuffer()); + R_RETURN(dmnt::cheat::impl::GetFrozenAddresses(addresses.GetPointer(), addresses.GetSize(), out_count.GetPointer(), offset)); + } + + Result CheatService::GetFrozenAddress(sf::Out<FrozenAddressEntry> entry, u64 address) { + R_RETURN(dmnt::cheat::impl::GetFrozenAddress(entry.GetPointer(), address)); + } + + Result CheatService::EnableFrozenAddress(sf::Out<u64> out_value, u64 address, u64 width) { + /* Width needs to be a power of two <= 8. */ + R_UNLESS(width > 0, dmnt::cheat::ResultFrozenAddressInvalidWidth()); + R_UNLESS(width <= sizeof(u64), dmnt::cheat::ResultFrozenAddressInvalidWidth()); + R_UNLESS((width & (width - 1)) == 0, dmnt::cheat::ResultFrozenAddressInvalidWidth()); + R_RETURN(dmnt::cheat::impl::EnableFrozenAddress(out_value.GetPointer(), address, width)); + } + + Result CheatService::DisableFrozenAddress(u64 address) { + R_RETURN(dmnt::cheat::impl::DisableFrozenAddress(address)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/dmnt_cheat_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/dmnt_cheat_service.hpp new file mode 100644 index 00000000..74b26e4a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/dmnt_cheat_service.hpp @@ -0,0 +1,88 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +/* TODO: In libstratosphere, eventually? */ +#define AMS_DMNT_I_CHEAT_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 65000, void, HasCheatProcess, (sf::Out<bool> out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 65001, void, GetCheatProcessEvent, (sf::OutCopyHandle out_event), (out_event)) \ + AMS_SF_METHOD_INFO(C, H, 65002, Result, GetCheatProcessMetadata, (sf::Out<dmnt::cheat::CheatProcessMetadata> out_metadata), (out_metadata)) \ + AMS_SF_METHOD_INFO(C, H, 65003, Result, ForceOpenCheatProcess, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 65004, Result, PauseCheatProcess, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 65005, Result, ResumeCheatProcess, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 65006, Result, ForceCloseCheatProcess, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 65100, Result, GetCheatProcessMappingCount, (sf::Out<u64> out_count), (out_count)) \ + AMS_SF_METHOD_INFO(C, H, 65101, Result, GetCheatProcessMappings, (const sf::OutArray<svc::MemoryInfo> &mappings, sf::Out<u64> out_count, u64 offset), (mappings, out_count, offset)) \ + AMS_SF_METHOD_INFO(C, H, 65102, Result, ReadCheatProcessMemory, (const sf::OutBuffer &buffer, u64 address, u64 out_size), (buffer, address, out_size)) \ + AMS_SF_METHOD_INFO(C, H, 65103, Result, WriteCheatProcessMemory, (const sf::InBuffer &buffer, u64 address, u64 in_size), (buffer, address, in_size)) \ + AMS_SF_METHOD_INFO(C, H, 65104, Result, QueryCheatProcessMemory, (sf::Out<svc::MemoryInfo> mapping, u64 address), (mapping, address)) \ + AMS_SF_METHOD_INFO(C, H, 65200, Result, GetCheatCount, (sf::Out<u64> out_count), (out_count)) \ + AMS_SF_METHOD_INFO(C, H, 65201, Result, GetCheats, (const sf::OutArray<dmnt::cheat::CheatEntry> &cheats, sf::Out<u64> out_count, u64 offset), (cheats, out_count, offset)) \ + AMS_SF_METHOD_INFO(C, H, 65202, Result, GetCheatById, (sf::Out<dmnt::cheat::CheatEntry> cheat, u32 cheat_id), (cheat, cheat_id)) \ + AMS_SF_METHOD_INFO(C, H, 65203, Result, ToggleCheat, (u32 cheat_id), (cheat_id)) \ + AMS_SF_METHOD_INFO(C, H, 65204, Result, AddCheat, (const dmnt::cheat::CheatDefinition &cheat, sf::Out<u32> out_cheat_id, bool enabled), (cheat, out_cheat_id, enabled)) \ + AMS_SF_METHOD_INFO(C, H, 65205, Result, RemoveCheat, (u32 cheat_id), (cheat_id)) \ + AMS_SF_METHOD_INFO(C, H, 65206, Result, ReadStaticRegister, (sf::Out<u64> out, u8 which), (out, which)) \ + AMS_SF_METHOD_INFO(C, H, 65207, Result, WriteStaticRegister, (u8 which, u64 value), (which, value)) \ + AMS_SF_METHOD_INFO(C, H, 65208, Result, ResetStaticRegisters, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 65209, Result, SetMasterCheat, (const dmnt::cheat::CheatDefinition &cheat), (cheat)) \ + AMS_SF_METHOD_INFO(C, H, 65300, Result, GetFrozenAddressCount, (sf::Out<u64> out_count), (out_count)) \ + AMS_SF_METHOD_INFO(C, H, 65301, Result, GetFrozenAddresses, (const sf::OutArray<dmnt::cheat::FrozenAddressEntry> &addresses, sf::Out<u64> out_count, u64 offset), (addresses, out_count, offset)) \ + AMS_SF_METHOD_INFO(C, H, 65302, Result, GetFrozenAddress, (sf::Out<dmnt::cheat::FrozenAddressEntry> entry, u64 address), (entry, address)) \ + AMS_SF_METHOD_INFO(C, H, 65303, Result, EnableFrozenAddress, (sf::Out<u64> out_value, u64 address, u64 width), (out_value, address, width)) \ + AMS_SF_METHOD_INFO(C, H, 65304, Result, DisableFrozenAddress, (u64 address), (address)) + +AMS_SF_DEFINE_INTERFACE(ams::dmnt::cheat::impl, ICheatInterface, AMS_DMNT_I_CHEAT_INTERFACE_INTERFACE_INFO, 0x00000000) + +namespace ams::dmnt::cheat { + + class CheatService { + public: + void HasCheatProcess(sf::Out<bool> out); + void GetCheatProcessEvent(sf::OutCopyHandle out_event); + Result GetCheatProcessMetadata(sf::Out<CheatProcessMetadata> out_metadata); + Result ForceOpenCheatProcess(); + Result PauseCheatProcess(); + Result ResumeCheatProcess(); + Result ForceCloseCheatProcess(); + + Result GetCheatProcessMappingCount(sf::Out<u64> out_count); + Result GetCheatProcessMappings(const sf::OutArray<svc::MemoryInfo> &mappings, sf::Out<u64> out_count, u64 offset); + Result ReadCheatProcessMemory(const sf::OutBuffer &buffer, u64 address, u64 out_size); + Result WriteCheatProcessMemory(const sf::InBuffer &buffer, u64 address, u64 in_size); + Result QueryCheatProcessMemory(sf::Out<svc::MemoryInfo> mapping, u64 address); + + Result GetCheatCount(sf::Out<u64> out_count); + Result GetCheats(const sf::OutArray<CheatEntry> &cheats, sf::Out<u64> out_count, u64 offset); + Result GetCheatById(sf::Out<CheatEntry> cheat, u32 cheat_id); + Result ToggleCheat(u32 cheat_id); + Result AddCheat(const CheatDefinition &cheat, sf::Out<u32> out_cheat_id, bool enabled); + Result RemoveCheat(u32 cheat_id); + Result ReadStaticRegister(sf::Out<u64> out, u8 which); + Result WriteStaticRegister(u8 which, u64 value); + Result ResetStaticRegisters(); + Result SetMasterCheat(const CheatDefinition &cheat); + + Result GetFrozenAddressCount(sf::Out<u64> out_count); + Result GetFrozenAddresses(const sf::OutArray<FrozenAddressEntry> &addresses, sf::Out<u64> out_count, u64 offset); + Result GetFrozenAddress(sf::Out<FrozenAddressEntry> entry, u64 address); + Result EnableFrozenAddress(sf::Out<u64> out_value, u64 address, u64 width); + Result DisableFrozenAddress(u64 address); + }; + static_assert(impl::IsICheatInterface<CheatService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.cpp new file mode 100644 index 00000000..627c56bd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.cpp @@ -0,0 +1,1319 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dmnt_cheat_api.hpp" +#include "dmnt_cheat_vm.hpp" +#include "dmnt_cheat_debug_events_manager.hpp" + +namespace ams::dmnt::cheat::impl { + + namespace { + + /* Helper definitions. */ + constexpr size_t MaxCheatCount = 0x80; + constexpr size_t MaxFrozenAddressCount = 0x80; + + class FrozenAddressMapEntry : public util::IntrusiveRedBlackTreeBaseNode<FrozenAddressMapEntry> { + public: + using RedBlackKeyType = u64; + private: + u64 m_address; + FrozenAddressValue m_value; + public: + FrozenAddressMapEntry(u64 address, FrozenAddressValue value) : m_address(address), m_value(value) { /* ... */ } + + constexpr u64 GetAddress() const { return m_address; } + + constexpr const FrozenAddressValue &GetValue() const { return m_value; } + constexpr FrozenAddressValue &GetValue() { return m_value; } + + static constexpr ALWAYS_INLINE int Compare(const RedBlackKeyType &lval, const FrozenAddressMapEntry &rhs) { + const auto rval = rhs.GetAddress(); + + if (lval < rval) { + return -1; + } else if (lval == rval) { + return 0; + } else { + return 1; + } + } + + static constexpr ALWAYS_INLINE int Compare(const FrozenAddressMapEntry &lhs, const FrozenAddressMapEntry &rhs) { + return Compare(lhs.GetAddress(), rhs); + } + }; + + constinit os::SdkMutex g_text_file_buffer_lock; + constinit char g_text_file_buffer[64_KB]; + + constinit u8 g_frozen_address_map_memory[sizeof(FrozenAddressMapEntry) * MaxFrozenAddressCount]; + constinit lmem::HeapHandle g_frozen_address_map_heap; + + FrozenAddressMapEntry *AllocateFrozenAddress(u64 address, FrozenAddressValue value) { + FrozenAddressMapEntry *entry = static_cast<FrozenAddressMapEntry *>(lmem::AllocateFromUnitHeap(g_frozen_address_map_heap)); + if (entry != nullptr) { + std::construct_at(entry, address, value); + } + return entry; + } + + void DeallocateFrozenAddress(FrozenAddressMapEntry *entry) { + std::destroy_at(entry); + lmem::FreeToUnitHeap(g_frozen_address_map_heap, entry); + } + + using FrozenAddressMap = typename util::IntrusiveRedBlackTreeBaseTraits<FrozenAddressMapEntry>::TreeType<FrozenAddressMapEntry>; + + /* Manager class. */ + class CheatProcessManager { + private: + static constexpr size_t ThreadStackSize = 0x4000; + private: + os::SdkMutex m_cheat_lock; + os::Event m_unsafe_break_event; + os::Event m_debug_events_event; /* Autoclear. */ + os::ThreadType m_detect_thread, m_debug_events_thread; + os::SystemEvent m_cheat_process_event; + os::NativeHandle m_cheat_process_debug_handle = os::InvalidNativeHandle; + CheatProcessMetadata m_cheat_process_metadata = {}; + + os::ThreadType m_vm_thread; + bool m_broken_unsafe = false; + bool m_needs_reload_vm = false; + CheatVirtualMachine m_cheat_vm; + + bool m_enable_cheats_by_default = true; + bool m_always_save_cheat_toggles = false; + bool m_should_save_cheat_toggles = false; + CheatEntry m_cheat_entries[MaxCheatCount] = {}; + FrozenAddressMap m_frozen_addresses_map = {}; + + alignas(os::MemoryPageSize) u8 m_detect_thread_stack[ThreadStackSize] = {}; + alignas(os::MemoryPageSize) u8 m_debug_events_thread_stack[ThreadStackSize] = {}; + alignas(os::MemoryPageSize) u8 m_vm_thread_stack[ThreadStackSize] = {}; + private: + static void DetectLaunchThread(void *_this); + static void VirtualMachineThread(void *_this); + static void DebugEventsThread(void *_this); + + Result AttachToApplicationProcess(bool on_process_launch); + + bool ParseCheats(const char *s, size_t len); + bool LoadCheats(const ncm::ProgramId program_id, const u8 *module_id); + bool ParseCheatToggles(const char *s, size_t len); + bool LoadCheatToggles(const ncm::ProgramId program_id); + void SaveCheatToggles(const ncm::ProgramId program_id); + + bool GetNeedsReloadVm() const { + return m_needs_reload_vm; + } + + void SetNeedsReloadVm(bool reload) { + m_needs_reload_vm = reload; + } + + + void ResetCheatEntry(size_t i) { + if (i < MaxCheatCount) { + std::memset(m_cheat_entries + i, 0, sizeof(m_cheat_entries[i])); + m_cheat_entries[i].cheat_id = i; + + this->SetNeedsReloadVm(true); + } + } + + void ResetAllCheatEntries() { + for (size_t i = 0; i < MaxCheatCount; i++) { + this->ResetCheatEntry(i); + } + + m_cheat_vm.ResetStaticRegisters(); + } + + CheatEntry *GetCheatEntryById(size_t i) { + if (i < MaxCheatCount) { + return m_cheat_entries + i; + } + + return nullptr; + } + + CheatEntry *GetCheatEntryByReadableName(const char *readable_name) { + /* Check all non-master cheats for match. */ + for (size_t i = 1; i < MaxCheatCount; i++) { + if (std::strncmp(m_cheat_entries[i].definition.readable_name, readable_name, sizeof(m_cheat_entries[i].definition.readable_name)) == 0) { + return m_cheat_entries + i; + } + } + + return nullptr; + } + + CheatEntry *GetFreeCheatEntry() { + /* Check all non-master cheats for availability. */ + for (size_t i = 1; i < MaxCheatCount; i++) { + if (m_cheat_entries[i].definition.num_opcodes == 0) { + return m_cheat_entries + i; + } + } + + return nullptr; + } + + void CloseActiveCheatProcess() { + if (m_cheat_process_debug_handle != os::InvalidNativeHandle) { + /* We don't need to do any unsafe brekaing. */ + m_broken_unsafe = false; + m_unsafe_break_event.Signal(); + + /* Knock out the debug events thread. */ + { + std::scoped_lock lk(util::GetReference(m_debug_events_thread.cs_thread)); + + R_ABORT_UNLESS(svc::CancelSynchronization(m_debug_events_thread.thread_impl->handle)); + } + + /* Close resources. */ + R_ABORT_UNLESS(svc::CloseHandle(m_cheat_process_debug_handle)); + m_cheat_process_debug_handle = os::InvalidNativeHandle; + + /* Save cheat toggles. */ + if (m_always_save_cheat_toggles || m_should_save_cheat_toggles) { + this->SaveCheatToggles(m_cheat_process_metadata.program_id); + m_should_save_cheat_toggles = false; + } + + /* Clear metadata. */ + static_assert(util::is_pod<decltype(m_cheat_process_metadata)>::value, "CheatProcessMetadata definition!"); + std::memset(std::addressof(m_cheat_process_metadata), 0, sizeof(m_cheat_process_metadata)); + + /* Clear cheat list. */ + this->ResetAllCheatEntries(); + + /* Clear frozen addresses. */ + { + auto it = m_frozen_addresses_map.begin(); + while (it != m_frozen_addresses_map.end()) { + FrozenAddressMapEntry *entry = std::addressof(*it); + it = m_frozen_addresses_map.erase(it); + DeallocateFrozenAddress(entry); + } + } + + /* Signal to our fans. */ + m_cheat_process_event.Signal(); + } + } + + bool HasActiveCheatProcess() { + /* Note: This function *MUST* be called only with the cheat lock held. */ + os::ProcessId pid; + bool has_cheat_process = m_cheat_process_debug_handle != os::InvalidNativeHandle; + has_cheat_process &= R_SUCCEEDED(os::GetProcessId(std::addressof(pid), m_cheat_process_debug_handle)); + has_cheat_process &= R_SUCCEEDED(pm::dmnt::GetApplicationProcessId(std::addressof(pid))); + has_cheat_process &= (pid == m_cheat_process_metadata.process_id); + + if (!has_cheat_process) { + this->CloseActiveCheatProcess(); + } + + return has_cheat_process; + } + + Result EnsureCheatProcess() { + R_UNLESS(this->HasActiveCheatProcess(), dmnt::cheat::ResultCheatNotAttached()); + R_SUCCEED(); + } + + os::NativeHandle GetCheatProcessHandle() const { + return m_cheat_process_debug_handle; + } + + os::NativeHandle HookToCreateApplicationProcess() const { + os::NativeHandle h; + R_ABORT_UNLESS(pm::dmnt::HookToCreateApplicationProcess(std::addressof(h))); + return h; + } + + void StartProcess(os::ProcessId process_id) const { + R_ABORT_UNLESS(pm::dmnt::StartProcess(process_id)); + } + public: + CheatProcessManager() : m_cheat_lock(), m_unsafe_break_event(os::EventClearMode_ManualClear), m_debug_events_event(os::EventClearMode_AutoClear), m_cheat_process_event(os::EventClearMode_AutoClear, true) { + /* Learn whether we should enable cheats by default. */ + { + u8 en = 0; + if (settings::fwdbg::GetSettingsItemValue(std::addressof(en), sizeof(en), "atmosphere", "dmnt_cheats_enabled_by_default") == sizeof(en)) { + m_enable_cheats_by_default = (en != 0); + } + + en = 0; + if (settings::fwdbg::GetSettingsItemValue( std::addressof(en), sizeof(en), "atmosphere", "dmnt_always_save_cheat_toggles") == sizeof(en)) { + m_always_save_cheat_toggles = (en != 0); + } + } + + /* Spawn application detection thread, spawn cheat vm thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_detect_thread), DetectLaunchThread, this, m_detect_thread_stack, ThreadStackSize, AMS_GET_SYSTEM_THREAD_PRIORITY(dmnt, CheatDetect))); + os::SetThreadNamePointer(std::addressof(m_detect_thread), AMS_GET_SYSTEM_THREAD_NAME(dmnt, CheatDetect)); + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_vm_thread), VirtualMachineThread, this, m_vm_thread_stack, ThreadStackSize, AMS_GET_SYSTEM_THREAD_PRIORITY(dmnt, CheatVirtualMachine))); + os::SetThreadNamePointer(std::addressof(m_vm_thread), AMS_GET_SYSTEM_THREAD_NAME(dmnt, CheatVirtualMachine)); + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_debug_events_thread), DebugEventsThread, this, m_debug_events_thread_stack, ThreadStackSize, AMS_GET_SYSTEM_THREAD_PRIORITY(dmnt, CheatDebugEvents))); + os::SetThreadNamePointer(std::addressof(m_debug_events_thread), AMS_GET_SYSTEM_THREAD_NAME(dmnt, CheatDebugEvents)); + + /* Start threads. */ + os::StartThread(std::addressof(m_detect_thread)); + os::StartThread(std::addressof(m_vm_thread)); + os::StartThread(std::addressof(m_debug_events_thread)); + } + + bool GetHasActiveCheatProcess() { + std::scoped_lock lk(m_cheat_lock); + + return this->HasActiveCheatProcess(); + } + + os::NativeHandle GetCheatProcessEventHandle() const { + return m_cheat_process_event.GetReadableHandle(); + } + + Result GetCheatProcessMetadata(CheatProcessMetadata *out) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + std::memcpy(out, std::addressof(m_cheat_process_metadata), sizeof(*out)); + R_SUCCEED(); + } + + Result ForceOpenCheatProcess() { + R_RETURN(this->AttachToApplicationProcess(false)); + } + + Result ForceCloseCheatProcess() { + this->CloseActiveCheatProcess(); + R_SUCCEED(); + } + + Result ReadCheatProcessMemoryUnsafe(u64 proc_addr, void *out_data, size_t size) { + R_RETURN(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(out_data), this->GetCheatProcessHandle(), proc_addr, size)); + } + + Result WriteCheatProcessMemoryUnsafe(u64 proc_addr, const void *data, size_t size) { + R_TRY(svc::WriteDebugProcessMemory(this->GetCheatProcessHandle(), reinterpret_cast<uintptr_t>(data), proc_addr, size)); + + for (auto &entry : m_frozen_addresses_map) { + /* Get address/value. */ + const u64 address = entry.GetAddress(); + auto &value = entry.GetValue(); + + /* Map is ordered, so break when we can. */ + if (address >= proc_addr + size) { + break; + } + + /* Check if we need to write. */ + if (proc_addr <= address && address < proc_addr + size) { + const size_t offset = (address - proc_addr); + const size_t copy_size = std::min(sizeof(value.value), size - offset); + std::memcpy(std::addressof(value.value), reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(data) + offset), copy_size); + } + } + + R_SUCCEED(); + } + + Result PauseCheatProcessUnsafe() { + m_broken_unsafe = true; + m_unsafe_break_event.Clear(); + R_RETURN(svc::BreakDebugProcess(this->GetCheatProcessHandle())); + } + + Result ResumeCheatProcessUnsafe() { + m_broken_unsafe = false; + m_unsafe_break_event.Signal(); + dmnt::cheat::impl::ContinueCheatProcess(this->GetCheatProcessHandle()); + R_SUCCEED(); + } + + Result GetCheatProcessMappingCount(u64 *out_count) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + u64 address = 0, count = 0; + do { + if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mem_info), std::addressof(page_info), this->GetCheatProcessHandle(), address))) { + break; + } + + if (mem_info.permission != svc::MemoryPermission_None) { + count++; + } + + address = mem_info.base_address + mem_info.size; + } while (address != 0); + + *out_count = count; + R_SUCCEED(); + } + + Result GetCheatProcessMappings(svc::MemoryInfo *mappings, size_t max_count, u64 *out_count, u64 offset) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + u64 address = 0, total_count = 0, written_count = 0; + do { + if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mem_info), std::addressof(page_info), this->GetCheatProcessHandle(), address))) { + break; + } + + if (mem_info.permission != svc::MemoryPermission_None) { + if (offset <= total_count && written_count < max_count) { + mappings[written_count++] = mem_info; + } + total_count++; + } + + address = mem_info.base_address + mem_info.size; + } while (address != 0 && written_count < max_count); + + *out_count = written_count; + R_SUCCEED(); + } + + Result ReadCheatProcessMemory(u64 proc_addr, void *out_data, size_t size) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + R_RETURN(this->ReadCheatProcessMemoryUnsafe(proc_addr, out_data, size)); + } + + Result WriteCheatProcessMemory(u64 proc_addr, const void *data, size_t size) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + R_RETURN(this->WriteCheatProcessMemoryUnsafe(proc_addr, data, size)); + } + + Result QueryCheatProcessMemory(svc::MemoryInfo *mapping, u64 address) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + svc::PageInfo page_info; + R_RETURN(svc::QueryDebugProcessMemory(mapping, std::addressof(page_info), this->GetCheatProcessHandle(), address)); + } + + Result PauseCheatProcess() { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + R_RETURN(this->PauseCheatProcessUnsafe()); + } + + Result ResumeCheatProcess() { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + R_RETURN(this->ResumeCheatProcessUnsafe()); + } + + Result GetCheatCount(u64 *out_count) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + size_t count = 0; + for (size_t i = 0; i < MaxCheatCount; i++) { + if (m_cheat_entries[i].definition.num_opcodes) { + count++; + } + } + + *out_count = count; + R_SUCCEED(); + } + + Result GetCheats(CheatEntry *out_cheats, size_t max_count, u64 *out_count, u64 offset) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + size_t count = 0, total_count = 0; + for (size_t i = 0; i < MaxCheatCount && count < max_count; i++) { + if (m_cheat_entries[i].definition.num_opcodes) { + total_count++; + if (total_count > offset) { + out_cheats[count++] = m_cheat_entries[i]; + } + } + } + + *out_count = count; + R_SUCCEED(); + } + + Result GetCheatById(CheatEntry *out_cheat, u32 cheat_id) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + const CheatEntry *entry = this->GetCheatEntryById(cheat_id); + R_UNLESS(entry != nullptr, dmnt::cheat::ResultCheatUnknownId()); + R_UNLESS(entry->definition.num_opcodes != 0, dmnt::cheat::ResultCheatUnknownId()); + + *out_cheat = *entry; + R_SUCCEED(); + } + + Result ToggleCheat(u32 cheat_id) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + CheatEntry *entry = this->GetCheatEntryById(cheat_id); + R_UNLESS(entry != nullptr, dmnt::cheat::ResultCheatUnknownId()); + R_UNLESS(entry->definition.num_opcodes != 0, dmnt::cheat::ResultCheatUnknownId()); + + R_UNLESS(cheat_id != 0, dmnt::cheat::ResultCheatCannotDisable()); + + entry->enabled = !entry->enabled; + + /* Trigger a VM reload. */ + this->SetNeedsReloadVm(true); + + R_SUCCEED(); + } + + Result AddCheat(u32 *out_id, const CheatDefinition &def, bool enabled) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + R_UNLESS(def.num_opcodes != 0, dmnt::cheat::ResultCheatInvalid()); + R_UNLESS(def.num_opcodes <= util::size(def.opcodes), dmnt::cheat::ResultCheatInvalid()); + + CheatEntry *new_entry = this->GetFreeCheatEntry(); + R_UNLESS(new_entry != nullptr, dmnt::cheat::ResultCheatOutOfResource()); + + new_entry->enabled = enabled; + new_entry->definition = def; + + /* Trigger a VM reload. */ + this->SetNeedsReloadVm(true); + + /* Set output id. */ + *out_id = new_entry->cheat_id; + + R_SUCCEED(); + } + + Result RemoveCheat(u32 cheat_id) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + R_UNLESS(cheat_id < MaxCheatCount, dmnt::cheat::ResultCheatUnknownId()); + + this->ResetCheatEntry(cheat_id); + + /* Trigger a VM reload. */ + this->SetNeedsReloadVm(true); + + R_SUCCEED(); + } + + Result SetMasterCheat(const CheatDefinition &def) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + R_UNLESS(def.num_opcodes != 0, dmnt::cheat::ResultCheatInvalid()); + R_UNLESS(def.num_opcodes <= util::size(def.opcodes), dmnt::cheat::ResultCheatInvalid()); + + CheatEntry *master_entry = m_cheat_entries + 0; + + master_entry->enabled = true; + master_entry->definition = def; + + /* Trigger a VM reload. */ + this->SetNeedsReloadVm(true); + + R_SUCCEED(); + } + + Result ReadStaticRegister(u64 *out, size_t which) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + R_UNLESS(which < CheatVirtualMachine::NumStaticRegisters, dmnt::cheat::ResultCheatInvalid()); + + *out = m_cheat_vm.GetStaticRegister(which); + R_SUCCEED(); + } + + Result WriteStaticRegister(size_t which, u64 value) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + R_UNLESS(which < CheatVirtualMachine::NumStaticRegisters, dmnt::cheat::ResultCheatInvalid()); + + m_cheat_vm.SetStaticRegister(which, value); + R_SUCCEED(); + } + + Result ResetStaticRegisters() { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + m_cheat_vm.ResetStaticRegisters(); + R_SUCCEED(); + } + + Result GetFrozenAddressCount(u64 *out_count) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + *out_count = std::distance(m_frozen_addresses_map.begin(), m_frozen_addresses_map.end()); + R_SUCCEED(); + } + + Result GetFrozenAddresses(FrozenAddressEntry *frz_addrs, size_t max_count, u64 *out_count, u64 offset) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + u64 total_count = 0, written_count = 0; + for (const auto &entry : m_frozen_addresses_map) { + if (written_count >= max_count) { + break; + } + + if (offset <= total_count) { + frz_addrs[written_count].address = entry.GetAddress(); + frz_addrs[written_count].value = entry.GetValue(); + written_count++; + } + total_count++; + } + + *out_count = written_count; + R_SUCCEED(); + } + + Result GetFrozenAddress(FrozenAddressEntry *frz_addr, u64 address) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + const auto it = m_frozen_addresses_map.find_key(address); + R_UNLESS(it != m_frozen_addresses_map.end(), dmnt::cheat::ResultFrozenAddressNotFound()); + + frz_addr->address = it->GetAddress(); + frz_addr->value = it->GetValue(); + R_SUCCEED(); + } + + Result EnableFrozenAddress(u64 *out_value, u64 address, u64 width) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + const auto it = m_frozen_addresses_map.find_key(address); + R_UNLESS(it == m_frozen_addresses_map.end(), dmnt::cheat::ResultFrozenAddressAlreadyExists()); + + FrozenAddressValue value = {}; + value.width = width; + R_TRY(this->ReadCheatProcessMemoryUnsafe(address, std::addressof(value.value), width)); + + FrozenAddressMapEntry *entry = AllocateFrozenAddress(address, value); + R_UNLESS(entry != nullptr, dmnt::cheat::ResultFrozenAddressOutOfResource()); + + m_frozen_addresses_map.insert(*entry); + *out_value = value.value; + R_SUCCEED(); + } + + Result DisableFrozenAddress(u64 address) { + std::scoped_lock lk(m_cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + const auto it = m_frozen_addresses_map.find_key(address); + R_UNLESS(it != m_frozen_addresses_map.end(), dmnt::cheat::ResultFrozenAddressNotFound()); + + FrozenAddressMapEntry *entry = std::addressof(*it); + m_frozen_addresses_map.erase(it); + DeallocateFrozenAddress(entry); + + R_SUCCEED(); + } + + }; + + void CheatProcessManager::DetectLaunchThread(void *_this) { + CheatProcessManager *manager = reinterpret_cast<CheatProcessManager *>(_this); + Event hook; + while (true) { + eventLoadRemote(std::addressof(hook), manager->HookToCreateApplicationProcess(), true); + if (R_SUCCEEDED(eventWait(std::addressof(hook), std::numeric_limits<u64>::max()))) { + manager->AttachToApplicationProcess(true); + } + eventClose(std::addressof(hook)); + } + } + + void CheatProcessManager::DebugEventsThread(void *_this) { + CheatProcessManager *manager = reinterpret_cast<CheatProcessManager *>(_this); + while (true) { + /* Atomically wait (and clear) signal for new process. */ + manager->m_debug_events_event.Wait(); + while (true) { + os::NativeHandle cheat_process_handle = manager->GetCheatProcessHandle(); + s32 dummy; + while (cheat_process_handle != os::InvalidNativeHandle && R_SUCCEEDED(svc::WaitSynchronization(std::addressof(dummy), std::addressof(cheat_process_handle), 1, std::numeric_limits<u64>::max()))) { + manager->m_cheat_lock.Lock(); + ON_SCOPE_EXIT { manager->m_cheat_lock.Unlock(); }; + { + ON_SCOPE_EXIT { cheat_process_handle = manager->GetCheatProcessHandle(); }; + + /* If we did an unsafe break, wait until we're not broken. */ + if (manager->m_broken_unsafe) { + manager->m_cheat_lock.Unlock(); + manager->m_unsafe_break_event.Wait(); + manager->m_cheat_lock.Lock(); + if (manager->GetCheatProcessHandle() != os::InvalidNativeHandle) { + continue; + } else { + break; + } + } + + /* Handle any pending debug events. */ + if (manager->HasActiveCheatProcess()) { + R_TRY_CATCH(dmnt::cheat::impl::ContinueCheatProcess(manager->GetCheatProcessHandle())) { + R_CATCH(svc::ResultProcessTerminated) { + manager->CloseActiveCheatProcess(); + break; + } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + } + } + } + + /* WaitSynchronization failed. This means someone canceled our synchronization, possibly us. */ + /* Let's check if we should quit! */ + std::scoped_lock lk(manager->m_cheat_lock); + if (!manager->HasActiveCheatProcess()) { + break; + } + } + } + } + + void CheatProcessManager::VirtualMachineThread(void *_this) { + CheatProcessManager *manager = reinterpret_cast<CheatProcessManager *>(_this); + while (true) { + /* Apply cheats. */ + { + std::scoped_lock lk(manager->m_cheat_lock); + + if (manager->HasActiveCheatProcess()) { + /* Execute VM. */ + if (!manager->GetNeedsReloadVm() || manager->m_cheat_vm.LoadProgram(manager->m_cheat_entries, util::size(manager->m_cheat_entries))) { + manager->SetNeedsReloadVm(false); + + /* Execute program only if it has opcodes. */ + if (manager->m_cheat_vm.GetProgramSize()) { + manager->m_cheat_vm.Execute(std::addressof(manager->m_cheat_process_metadata)); + } + } + + /* Apply frozen addresses. */ + for (const auto &entry : manager->m_frozen_addresses_map) { + const auto address = entry.GetAddress(); + const auto &value = entry.GetValue(); + + /* Use Write SVC directly, to avoid the usual frozen address update logic. */ + svc::WriteDebugProcessMemory(manager->GetCheatProcessHandle(), reinterpret_cast<uintptr_t>(std::addressof(value.value)), address, value.width); + } + } + } + + /* Sleep until next potential execution. */ + constexpr s64 TimesPerSecond = 12; + constexpr s64 DelayNanoSeconds = TimeSpan::FromSeconds(1).GetNanoSeconds() / TimesPerSecond; + constexpr TimeSpan Delay = TimeSpan::FromNanoSeconds(DelayNanoSeconds); + os::SleepThread(Delay); + } + } + + #define R_ABORT_UNLESS_IF_NEW_PROCESS(res) \ + if (on_process_launch) { \ + R_ABORT_UNLESS(res); \ + } else { \ + R_TRY(res); \ + } + + Result CheatProcessManager::AttachToApplicationProcess(bool on_process_launch) { + std::scoped_lock lk(m_cheat_lock); + + /* Close the active process, if needed. */ + { + if (this->HasActiveCheatProcess()) { + /* When forcing attach, we're done. */ + R_SUCCEED_IF(!on_process_launch); + } + + /* Detach from the current process, if it's open. */ + this->CloseActiveCheatProcess(); + } + + /* Get the application process's ID. */ + R_ABORT_UNLESS_IF_NEW_PROCESS(pm::dmnt::GetApplicationProcessId(std::addressof(m_cheat_process_metadata.process_id))); + auto proc_guard = SCOPE_GUARD { + if (on_process_launch) { + this->StartProcess(m_cheat_process_metadata.process_id); + } + m_cheat_process_metadata.process_id = os::ProcessId{}; + }; + + /* Get process handle, use it to learn memory extents. */ + { + os::NativeHandle proc_h = os::InvalidNativeHandle; + ncm::ProgramLocation loc = {}; + cfg::OverrideStatus status = {}; + + R_ABORT_UNLESS_IF_NEW_PROCESS(pm::dmnt::AtmosphereGetProcessInfo(std::addressof(proc_h), std::addressof(loc), std::addressof(status), m_cheat_process_metadata.process_id)); + ON_SCOPE_EXIT { os::CloseNativeHandle(proc_h); }; + + m_cheat_process_metadata.program_id = loc.program_id; + + R_ABORT_UNLESS(svc::GetInfo(std::addressof(m_cheat_process_metadata.heap_extents.base), svc::InfoType_HeapRegionAddress, proc_h, 0)); + R_ABORT_UNLESS(svc::GetInfo(std::addressof(m_cheat_process_metadata.heap_extents.size), svc::InfoType_HeapRegionSize, proc_h, 0)); + R_ABORT_UNLESS(svc::GetInfo(std::addressof(m_cheat_process_metadata.alias_extents.base), svc::InfoType_AliasRegionAddress, proc_h, 0)); + R_ABORT_UNLESS(svc::GetInfo(std::addressof(m_cheat_process_metadata.alias_extents.size), svc::InfoType_AliasRegionSize, proc_h, 0)); + R_ABORT_UNLESS(svc::GetInfo(std::addressof(m_cheat_process_metadata.aslr_extents.base), svc::InfoType_AslrRegionAddress, proc_h, 0)); + R_ABORT_UNLESS(svc::GetInfo(std::addressof(m_cheat_process_metadata.aslr_extents.size), svc::InfoType_AslrRegionSize, proc_h, 0)); + + /* If new process launch, we may not want to actually attach. */ + if (on_process_launch) { + R_UNLESS(status.IsCheatEnabled(), dmnt::cheat::ResultCheatNotAttached()); + } + } + + /* Get module information from loader. */ + { + ldr::ModuleInfo proc_modules[2]; + s32 num_modules; + + /* TODO: ldr::dmnt:: */ + R_ABORT_UNLESS_IF_NEW_PROCESS(ldrDmntGetProcessModuleInfo(static_cast<u64>(m_cheat_process_metadata.process_id), reinterpret_cast<LoaderModuleInfo *>(proc_modules), util::size(proc_modules), std::addressof(num_modules))); + + /* All applications must have two modules. */ + /* Only accept one (which means we're attaching to HBL) */ + /* if we aren't auto-attaching. */ + const ldr::ModuleInfo *proc_module = nullptr; + if (num_modules == 2) { + proc_module = std::addressof(proc_modules[1]); + } else if (num_modules == 1 && !on_process_launch) { + proc_module = std::addressof(proc_modules[0]); + } else { + R_THROW(dmnt::cheat::ResultCheatNotAttached()); + } + + m_cheat_process_metadata.main_nso_extents.base = proc_module->address; + m_cheat_process_metadata.main_nso_extents.size = proc_module->size; + std::memcpy(m_cheat_process_metadata.main_nso_module_id, proc_module->module_id, sizeof(m_cheat_process_metadata.main_nso_module_id)); + } + + /* Read cheats off the SD. */ + if (!this->LoadCheats(m_cheat_process_metadata.program_id, m_cheat_process_metadata.main_nso_module_id) || + !this->LoadCheatToggles(m_cheat_process_metadata.program_id)) { + /* If new process launch, require success. */ + R_UNLESS(!on_process_launch, dmnt::cheat::ResultCheatNotAttached()); + } + + /* Open a debug handle. */ + svc::Handle debug_handle = svc::InvalidHandle; + R_ABORT_UNLESS_IF_NEW_PROCESS(svc::DebugActiveProcess(std::addressof(debug_handle), m_cheat_process_metadata.process_id.value)); + + /* Set our debug handle. */ + m_cheat_process_debug_handle = debug_handle; + + /* Cancel process guard. */ + proc_guard.Cancel(); + + /* Reset broken state. */ + m_broken_unsafe = false; + m_unsafe_break_event.Signal(); + + /* If new process, start the process. */ + if (on_process_launch) { + this->StartProcess(m_cheat_process_metadata.process_id); + } + + /* Signal to the debug events thread. */ + m_debug_events_event.Signal(); + + /* Signal to our fans. */ + m_cheat_process_event.Signal(); + + R_SUCCEED(); + } + + #undef R_ABORT_UNLESS_IF_NEW_PROCESS + + bool CheatProcessManager::ParseCheats(const char *s, size_t len) { + /* Trigger a VM reload. */ + this->SetNeedsReloadVm(true); + + /* Parse the input string. */ + size_t i = 0; + CheatEntry *cur_entry = nullptr; + while (i < len) { + if (std::isspace(static_cast<unsigned char>(s[i]))) { + /* Just ignore whitespace. */ + i++; + } else if (s[i] == '[') { + /* Parse a readable cheat name. */ + cur_entry = this->GetFreeCheatEntry(); + if (cur_entry == nullptr) { + return false; + } + + /* Extract name bounds. */ + size_t j = i + 1; + while (s[j] != ']') { + j++; + if (j >= len) { + return false; + } + } + + /* s[i+1:j] is cheat name. */ + const size_t cheat_name_len = std::min(j - i - 1, sizeof(cur_entry->definition.readable_name)); + std::memcpy(cur_entry->definition.readable_name, s + (i + 1), cheat_name_len); + cur_entry->definition.readable_name[cheat_name_len] = 0; + + /* Skip onwards. */ + i = j + 1; + } else if (s[i] == '{') { + /* We're parsing a master cheat. */ + cur_entry = std::addressof(m_cheat_entries[0]); + + /* There can only be one master cheat. */ + if (cur_entry->definition.num_opcodes > 0) { + return false; + } + + /* Extract name bounds */ + size_t j = i + 1; + while (s[j] != '}') { + j++; + if (j >= len) { + return false; + } + } + + /* s[i+1:j] is cheat name. */ + const size_t cheat_name_len = std::min(j - i - 1, sizeof(cur_entry->definition.readable_name)); + memcpy(cur_entry->definition.readable_name, s + (i + 1), cheat_name_len); + cur_entry->definition.readable_name[cheat_name_len] = 0; + + /* Skip onwards. */ + i = j + 1; + } else if (std::isxdigit(static_cast<unsigned char>(s[i]))) { + /* Make sure that we have a cheat open. */ + if (cur_entry == nullptr) { + return false; + } + + /* Bounds check the opcode count. */ + if (cur_entry->definition.num_opcodes >= util::size(cur_entry->definition.opcodes)) { + return false; + } + + /* We're parsing an instruction, so validate it's 8 hex digits. */ + for (size_t j = 1; j < 8; j++) { + /* Validate 8 hex chars. */ + if (i + j >= len || !std::isxdigit(static_cast<unsigned char>(s[i+j]))) { + return false; + } + } + + /* Parse the new opcode. */ + char hex_str[9] = {0}; + std::memcpy(hex_str, s + i, 8); + cur_entry->definition.opcodes[cur_entry->definition.num_opcodes++] = std::strtoul(hex_str, NULL, 16); + + /* Skip onwards. */ + i += 8; + } else { + /* Unexpected character encountered. */ + return false; + } + } + + /* Master cheat can't be disabled. */ + if (m_cheat_entries[0].definition.num_opcodes > 0) { + m_cheat_entries[0].enabled = true; + } + + /* Enable all entries we parsed. */ + for (size_t i = 1; i < MaxCheatCount; i++) { + if (m_cheat_entries[i].definition.num_opcodes > 0) { + m_cheat_entries[i].enabled = m_enable_cheats_by_default; + } + } + + return true; + } + + bool CheatProcessManager::ParseCheatToggles(const char *s, size_t len) { + size_t i = 0; + char cur_cheat_name[sizeof(CheatEntry::definition.readable_name)]; + char toggle[8]; + + while (i < len) { + if (std::isspace(static_cast<unsigned char>(s[i]))) { + /* Just ignore whitespace. */ + i++; + } else if (s[i] == '[') { + /* Extract name bounds. */ + size_t j = i + 1; + while (s[j] != ']') { + j++; + if (j >= len) { + return false; + } + } + + /* s[i+1:j] is cheat name. */ + const size_t cheat_name_len = std::min(j - i - 1, sizeof(cur_cheat_name)); + std::memcpy(cur_cheat_name, s + (i + 1), cheat_name_len); + cur_cheat_name[cheat_name_len] = 0; + + /* Skip onwards. */ + i = j + 1; + + /* Skip whitespace. */ + while (std::isspace(static_cast<unsigned char>(s[i]))) { + i++; + if (i >= len) { + return false; + } + } + + /* Parse whether to toggle. */ + j = i + 1; + while (j < len && !std::isspace(static_cast<unsigned char>(s[j]))) { + j++; + if ((j - i) >= sizeof(toggle)) { + return false; + } + } + + /* s[i:j] is toggle. */ + const size_t toggle_len = (j - i); + std::memcpy(toggle, s + i, toggle_len); + toggle[toggle_len] = 0; + + /* Allow specifying toggle for not present cheat. */ + CheatEntry *entry = this->GetCheatEntryByReadableName(cur_cheat_name); + if (entry != nullptr) { + if (strcasecmp(toggle, "1") == 0 || strcasecmp(toggle, "true") == 0 || strcasecmp(toggle, "on") == 0) { + entry->enabled = true; + } else if (strcasecmp(toggle, "0") == 0 || strcasecmp(toggle, "false") == 0 || strcasecmp(toggle, "off") == 0) { + entry->enabled = false; + } + } + + /* Skip onwards. */ + i = j + 1; + } else { + /* Unexpected character encountered. */ + return false; + } + } + + return true; + } + + bool CheatProcessManager::LoadCheats(const ncm::ProgramId program_id, const u8 *module_id) { + /* Reset existing entries. */ + this->ResetAllCheatEntries(); + + /* Open the file for program/module_id. */ + fs::FileHandle file; + { + char path[fs::EntryNameLengthMax + 1]; + util::SNPrintf(path, sizeof(path), "sdmc:/atmosphere/contents/%016lx/cheats/%02x%02x%02x%02x%02x%02x%02x%02x.txt", program_id.value, + module_id[0], module_id[1], module_id[2], module_id[3], module_id[4], module_id[5], module_id[6], module_id[7]); + if (R_FAILED(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Read))) { + return false; + } + } + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Get file size. */ + s64 file_size; + if (R_FAILED(fs::GetFileSize(std::addressof(file_size), file))) { + return false; + } + if (file_size < 0 || file_size >= static_cast<s64>(sizeof(g_text_file_buffer))) { + return false; + } + + std::scoped_lock lk(g_text_file_buffer_lock); + + /* Read cheats into buffer. */ + if (R_FAILED(fs::ReadFile(file, 0, g_text_file_buffer, file_size))) { + return false; + } + g_text_file_buffer[file_size] = '\x00'; + + /* Parse cheat buffer. */ + return this->ParseCheats(g_text_file_buffer, std::strlen(g_text_file_buffer)); + } + + bool CheatProcessManager::LoadCheatToggles(const ncm::ProgramId program_id) { + /* Unless we successfully parse, don't save toggles on close. */ + m_should_save_cheat_toggles = false; + + /* Open the file for program_id. */ + fs::FileHandle file; + { + char path[fs::EntryNameLengthMax + 1]; + util::SNPrintf(path, sizeof(path), "sdmc:/atmosphere/contents/%016lx/cheats/toggles.txt", program_id.value); + if (R_FAILED(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Read))) { + /* No file presence is allowed. */ + return true; + } + } + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Get file size. */ + s64 file_size; + if (R_FAILED(fs::GetFileSize(std::addressof(file_size), file))) { + return false; + } + if (file_size < 0 || file_size >= static_cast<s64>(sizeof(g_text_file_buffer))) { + return false; + } + + std::scoped_lock lk(g_text_file_buffer_lock); + + /* Read cheats into buffer. */ + if (R_FAILED(fs::ReadFile(file, 0, g_text_file_buffer, file_size))) { + return false; + } + g_text_file_buffer[file_size] = '\x00'; + + /* Parse toggle buffer. */ + m_should_save_cheat_toggles = this->ParseCheatToggles(g_text_file_buffer, std::strlen(g_text_file_buffer)); + return m_should_save_cheat_toggles; + } + + void CheatProcessManager::SaveCheatToggles(const ncm::ProgramId program_id) { + /* Open the file for program_id. */ + fs::FileHandle file; + { + char path[fs::EntryNameLengthMax + 1]; + util::SNPrintf(path, sizeof(path), "sdmc:/atmosphere/contents/%016lx/cheats/toggles.txt", program_id.value); + fs::DeleteFile(path); + fs::CreateFile(path, 0); + if (R_FAILED(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Write | fs::OpenMode_AllowAppend))) { + return; + } + } + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + s64 offset = 0; + char buf[0x100]; + + /* Save all non-master cheats. */ + for (size_t i = 1; i < MaxCheatCount; i++) { + if (m_cheat_entries[i].definition.num_opcodes != 0) { + util::SNPrintf(buf, sizeof(buf), "[%s]\n", m_cheat_entries[i].definition.readable_name); + const size_t name_len = std::strlen(buf); + if (R_SUCCEEDED(fs::WriteFile(file, offset, buf, name_len, fs::WriteOption::Flush))) { + offset += name_len; + } + + const char *entry = m_cheat_entries[i].enabled ? "true\n" : "false\n"; + const size_t entry_len = std::strlen(entry); + if (R_SUCCEEDED(fs::WriteFile(file, offset, entry, entry_len, fs::WriteOption::Flush))) { + offset += entry_len; + } + } + } + } + + + /* Manager global. */ + util::TypedStorage<CheatProcessManager> g_cheat_process_manager; + + } + + void InitializeCheatManager() { + /* Initialize the debug events manager (spawning its threads). */ + InitializeDebugEventsManager(); + + /* Initialize the frozen address map heap. */ + g_frozen_address_map_heap = lmem::CreateUnitHeap(g_frozen_address_map_memory, sizeof(g_frozen_address_map_memory), sizeof(FrozenAddressMapEntry), lmem::CreateOption_ThreadSafe); + + /* Create the cheat process manager (spawning its threads). */ + util::ConstructAt(g_cheat_process_manager); + } + + bool GetHasActiveCheatProcess() { + return GetReference(g_cheat_process_manager).GetHasActiveCheatProcess(); + } + + os::NativeHandle GetCheatProcessEventHandle() { + return GetReference(g_cheat_process_manager).GetCheatProcessEventHandle(); + } + + Result GetCheatProcessMetadata(CheatProcessMetadata *out) { + R_RETURN(GetReference(g_cheat_process_manager).GetCheatProcessMetadata(out)); + } + + Result ForceOpenCheatProcess() { + R_RETURN(GetReference(g_cheat_process_manager).ForceOpenCheatProcess()); + } + + Result PauseCheatProcess() { + R_RETURN(GetReference(g_cheat_process_manager).PauseCheatProcess()); + } + + Result ResumeCheatProcess() { + R_RETURN(GetReference(g_cheat_process_manager).ResumeCheatProcess()); + } + + Result ForceCloseCheatProcess() { + R_RETURN(GetReference(g_cheat_process_manager).ForceCloseCheatProcess()); + } + + Result ReadCheatProcessMemoryUnsafe(u64 process_addr, void *out_data, size_t size) { + R_RETURN(GetReference(g_cheat_process_manager).ReadCheatProcessMemoryUnsafe(process_addr, out_data, size)); + } + + Result WriteCheatProcessMemoryUnsafe(u64 process_addr, void *data, size_t size) { + R_RETURN(GetReference(g_cheat_process_manager).WriteCheatProcessMemoryUnsafe(process_addr, data, size)); + } + + Result PauseCheatProcessUnsafe() { + R_RETURN(GetReference(g_cheat_process_manager).PauseCheatProcessUnsafe()); + } + + Result ResumeCheatProcessUnsafe() { + R_RETURN(GetReference(g_cheat_process_manager).ResumeCheatProcessUnsafe()); + } + + Result GetCheatProcessMappingCount(u64 *out_count) { + R_RETURN(GetReference(g_cheat_process_manager).GetCheatProcessMappingCount(out_count)); + } + + Result GetCheatProcessMappings(svc::MemoryInfo *mappings, size_t max_count, u64 *out_count, u64 offset) { + R_RETURN(GetReference(g_cheat_process_manager).GetCheatProcessMappings(mappings, max_count, out_count, offset)); + } + + Result ReadCheatProcessMemory(u64 proc_addr, void *out_data, size_t size) { + R_RETURN(GetReference(g_cheat_process_manager).ReadCheatProcessMemory(proc_addr, out_data, size)); + } + + Result WriteCheatProcessMemory(u64 proc_addr, const void *data, size_t size) { + R_RETURN(GetReference(g_cheat_process_manager).WriteCheatProcessMemory(proc_addr, data, size)); + } + + Result QueryCheatProcessMemory(svc::MemoryInfo *mapping, u64 address) { + R_RETURN(GetReference(g_cheat_process_manager).QueryCheatProcessMemory(mapping, address)); + } + + Result GetCheatCount(u64 *out_count) { + R_RETURN(GetReference(g_cheat_process_manager).GetCheatCount(out_count)); + } + + Result GetCheats(CheatEntry *cheats, size_t max_count, u64 *out_count, u64 offset) { + R_RETURN(GetReference(g_cheat_process_manager).GetCheats(cheats, max_count, out_count, offset)); + } + + Result GetCheatById(CheatEntry *out_cheat, u32 cheat_id) { + R_RETURN(GetReference(g_cheat_process_manager).GetCheatById(out_cheat, cheat_id)); + } + + Result ToggleCheat(u32 cheat_id) { + R_RETURN(GetReference(g_cheat_process_manager).ToggleCheat(cheat_id)); + } + + Result AddCheat(u32 *out_id, const CheatDefinition &def, bool enabled) { + R_RETURN(GetReference(g_cheat_process_manager).AddCheat(out_id, def, enabled)); + } + + Result RemoveCheat(u32 cheat_id) { + R_RETURN(GetReference(g_cheat_process_manager).RemoveCheat(cheat_id)); + } + + Result SetMasterCheat(const CheatDefinition &def) { + R_RETURN(GetReference(g_cheat_process_manager).SetMasterCheat(def)); + } + + Result ReadStaticRegister(u64 *out, size_t which) { + R_RETURN(GetReference(g_cheat_process_manager).ReadStaticRegister(out, which)); + } + + Result WriteStaticRegister(size_t which, u64 value) { + R_RETURN(GetReference(g_cheat_process_manager).WriteStaticRegister(which, value)); + } + + Result ResetStaticRegisters() { + R_RETURN(GetReference(g_cheat_process_manager).ResetStaticRegisters()); + } + + Result GetFrozenAddressCount(u64 *out_count) { + R_RETURN(GetReference(g_cheat_process_manager).GetFrozenAddressCount(out_count)); + } + + Result GetFrozenAddresses(FrozenAddressEntry *frz_addrs, size_t max_count, u64 *out_count, u64 offset) { + R_RETURN(GetReference(g_cheat_process_manager).GetFrozenAddresses(frz_addrs, max_count, out_count, offset)); + } + + Result GetFrozenAddress(FrozenAddressEntry *frz_addr, u64 address) { + R_RETURN(GetReference(g_cheat_process_manager).GetFrozenAddress(frz_addr, address)); + } + + Result EnableFrozenAddress(u64 *out_value, u64 address, u64 width) { + R_RETURN(GetReference(g_cheat_process_manager).EnableFrozenAddress(out_value, address, width)); + } + + Result DisableFrozenAddress(u64 address) { + R_RETURN(GetReference(g_cheat_process_manager).DisableFrozenAddress(address)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.hpp new file mode 100644 index 00000000..663cef4e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.hpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::dmnt::cheat::impl { + + void InitializeCheatManager(); + + bool GetHasActiveCheatProcess(); + os::NativeHandle GetCheatProcessEventHandle(); + Result GetCheatProcessMetadata(CheatProcessMetadata *out); + Result ForceOpenCheatProcess(); + Result PauseCheatProcess(); + Result ResumeCheatProcess(); + Result ForceCloseCheatProcess(); + + Result ReadCheatProcessMemoryUnsafe(u64 process_addr, void *out_data, size_t size); + Result WriteCheatProcessMemoryUnsafe(u64 process_addr, void *data, size_t size); + + Result PauseCheatProcessUnsafe(); + Result ResumeCheatProcessUnsafe(); + + Result GetCheatProcessMappingCount(u64 *out_count); + Result GetCheatProcessMappings(svc::MemoryInfo *mappings, size_t max_count, u64 *out_count, u64 offset); + Result ReadCheatProcessMemory(u64 proc_addr, void *out_data, size_t size); + Result WriteCheatProcessMemory(u64 proc_addr, const void *data, size_t size); + Result QueryCheatProcessMemory(svc::MemoryInfo *mapping, u64 address); + + Result GetCheatCount(u64 *out_count); + Result GetCheats(CheatEntry *cheats, size_t max_count, u64 *out_count, u64 offset); + Result GetCheatById(CheatEntry *out_cheat, u32 cheat_id); + Result ToggleCheat(u32 cheat_id); + Result AddCheat(u32 *out_id, const CheatDefinition &def, bool enabled); + Result RemoveCheat(u32 cheat_id); + Result SetMasterCheat(const CheatDefinition &def); + Result ReadStaticRegister(u64 *out, size_t which); + Result WriteStaticRegister(size_t which, u64 value); + Result ResetStaticRegisters(); + + Result GetFrozenAddressCount(u64 *out_count); + Result GetFrozenAddresses(FrozenAddressEntry *frz_addrs, size_t max_count, u64 *out_count, u64 offset); + Result GetFrozenAddress(FrozenAddressEntry *frz_addr, u64 address); + Result EnableFrozenAddress(u64 *out_value, u64 address, u64 width); + Result DisableFrozenAddress(u64 address); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_debug_events_manager.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_debug_events_manager.cpp new file mode 100644 index 00000000..fc65620c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_debug_events_manager.cpp @@ -0,0 +1,158 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dmnt_cheat_debug_events_manager.hpp" + +/* WORKAROUND: This design prevents a kernel deadlock from occurring on 6.0.0+ */ + +namespace ams::dmnt::cheat::impl { + + namespace { + + class DebugEventsManager { + public: + static constexpr size_t NumCores = 4; + static constexpr size_t ThreadStackSize = os::MemoryPageSize; + private: + std::array<uintptr_t, NumCores> m_handle_message_queue_buffers; + std::array<uintptr_t, NumCores> m_result_message_queue_buffers; + std::array<os::MessageQueue, NumCores> m_handle_message_queues; + std::array<os::MessageQueue, NumCores> m_result_message_queues; + std::array<os::ThreadType, NumCores> m_threads; + + alignas(os::MemoryPageSize) u8 m_thread_stacks[NumCores][ThreadStackSize]; + private: + static void PerCoreThreadFunction(void *_this) { + /* This thread will wait on the appropriate message queue. */ + DebugEventsManager *this_ptr = reinterpret_cast<DebugEventsManager *>(_this); + const size_t current_core = svc::GetCurrentProcessorNumber(); + while (true) { + /* Receive handle. */ + os::NativeHandle debug_handle = this_ptr->WaitReceiveHandle(current_core); + + /* Continue events on the correct core. */ + Result result = this_ptr->ContinueDebugEvent(debug_handle); + + /* Return our result. */ + this_ptr->SendContinueResult(current_core, result); + } + } + + Result GetTargetCore(size_t *out, const svc::DebugEventInfo &dbg_event, os::NativeHandle debug_handle) { + /* If we don't need to continue on a specific core, use the system core. */ + size_t target_core = NumCores - 1; + + /* Retrieve correct core for new thread event. */ + if (dbg_event.type == svc::DebugEvent_CreateThread) { + u64 out64 = 0; + u32 out32 = 0; + + R_TRY_CATCH(svc::GetDebugThreadParam(&out64, &out32, debug_handle, dbg_event.info.create_thread.thread_id, svc::DebugThreadParam_CurrentCore)) { + R_CATCH_RETHROW(svc::ResultProcessTerminated) + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + target_core = out32; + } + + /* Set the target core. */ + *out = target_core; + R_SUCCEED(); + } + + void SendHandle(size_t target_core, os::NativeHandle debug_handle) { + m_handle_message_queues[target_core].Send(static_cast<uintptr_t>(debug_handle)); + } + + os::NativeHandle WaitReceiveHandle(size_t core_id) { + uintptr_t x = 0; + m_handle_message_queues[core_id].Receive(&x); + return static_cast<os::NativeHandle>(x); + } + + Result ContinueDebugEvent(os::NativeHandle debug_handle) { + if (hos::GetVersion() >= hos::Version_3_0_0) { + R_RETURN(svc::ContinueDebugEvent(debug_handle, svc::ContinueFlag_ExceptionHandled | svc::ContinueFlag_ContinueAll, nullptr, 0)); + } else { + R_RETURN(svc::LegacyContinueDebugEvent(debug_handle, svc::ContinueFlag_ExceptionHandled | svc::ContinueFlag_ContinueAll, 0)); + } + } + + void SendContinueResult(size_t target_core, Result result) { + m_result_message_queues[target_core].Send(static_cast<uintptr_t>(result.GetValue())); + } + + Result GetContinueResult(size_t core_id) { + uintptr_t x = 0; + m_result_message_queues[core_id].Receive(&x); + return static_cast<Result>(x); + } + public: + DebugEventsManager() + : m_handle_message_queues{ + os::MessageQueue(std::addressof(m_handle_message_queue_buffers[0]), 1), + os::MessageQueue(std::addressof(m_handle_message_queue_buffers[1]), 1), + os::MessageQueue(std::addressof(m_handle_message_queue_buffers[2]), 1), + os::MessageQueue(std::addressof(m_handle_message_queue_buffers[3]), 1)}, + m_result_message_queues{ + os::MessageQueue(std::addressof(m_result_message_queue_buffers[0]), 1), + os::MessageQueue(std::addressof(m_result_message_queue_buffers[1]), 1), + os::MessageQueue(std::addressof(m_result_message_queue_buffers[2]), 1), + os::MessageQueue(std::addressof(m_result_message_queue_buffers[3]), 1)}, + m_thread_stacks{} + { + for (size_t i = 0; i < NumCores; i++) { + /* Create thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_threads[i]), PerCoreThreadFunction, this, m_thread_stacks[i], ThreadStackSize, AMS_GET_SYSTEM_THREAD_PRIORITY(dmnt, MultiCoreEventManager), i)); + os::SetThreadNamePointer(std::addressof(m_threads[i]), AMS_GET_SYSTEM_THREAD_NAME(dmnt, MultiCoreEventManager)); + + /* Set core mask. */ + os::SetThreadCoreMask(std::addressof(m_threads[i]), i, (1u << i)); + + /* Start thread. */ + os::StartThread(std::addressof(m_threads[i])); + } + } + + Result ContinueCheatProcess(os::NativeHandle cheat_dbg_hnd) { + /* Loop getting all debug events. */ + svc::DebugEventInfo d; + size_t target_core = NumCores - 1; + while (R_SUCCEEDED(svc::GetDebugEvent(std::addressof(d), cheat_dbg_hnd))) { + if (d.type == svc::DebugEvent_CreateThread) { + R_TRY(GetTargetCore(std::addressof(target_core), d, cheat_dbg_hnd)); + } + } + + /* Send handle to correct core, wait for continue to finish. */ + this->SendHandle(target_core, cheat_dbg_hnd); + R_RETURN(this->GetContinueResult(target_core)); + } + }; + + /* Manager global. */ + util::TypedStorage<DebugEventsManager> g_events_manager; + + } + + void InitializeDebugEventsManager() { + util::ConstructAt(g_events_manager); + } + + Result ContinueCheatProcess(os::NativeHandle cheat_dbg_hnd) { + R_RETURN(GetReference(g_events_manager).ContinueCheatProcess(cheat_dbg_hnd)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_debug_events_manager.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_debug_events_manager.hpp new file mode 100644 index 00000000..58fc90ca --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_debug_events_manager.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::dmnt::cheat::impl { + + void InitializeDebugEventsManager(); + + Result ContinueCheatProcess(os::NativeHandle cheat_dbg_hnd); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.cpp new file mode 100644 index 00000000..c1ecfb67 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.cpp @@ -0,0 +1,1376 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "dmnt_cheat_vm.hpp" +#include "dmnt_cheat_api.hpp" + +namespace ams::dmnt::cheat::impl { + + void CheatVirtualMachine::DebugLog(u32 log_id, u64 value) { + /* Just unconditionally try to create the log folder. */ + fs::EnsureDirectory("sdmc:/atmosphere/cheat_vm_logs"); + + fs::FileHandle log_file; + { + char log_path[fs::EntryNameLengthMax + 1]; + util::SNPrintf(log_path, sizeof(log_path), "sdmc:/atmosphere/cheat_vm_logs/%08x.log", log_id); + if (R_FAILED(fs::OpenFile(std::addressof(log_file), log_path, fs::OpenMode_Write | fs::OpenMode_AllowAppend))) { + return; + } + } + ON_SCOPE_EXIT { fs::CloseFile(log_file); }; + + s64 log_offset; + if (R_FAILED(fs::GetFileSize(std::addressof(log_offset), log_file))) { + return; + } + + char log_value[18]; + util::SNPrintf(log_value, sizeof(log_value), "%016lx\n", value); + fs::WriteFile(log_file, log_offset, log_value, std::strlen(log_value), fs::WriteOption::Flush); + } + + void CheatVirtualMachine::OpenDebugLogFile() { + #ifdef DMNT_CHEAT_VM_DEBUG_LOG + CloseDebugLogFile(); + fs::EnsureDirectory("sdmc:/atmosphere/cheat_vm_logs"); + fs::CreateFile("sdmc:/atmosphere/cheat_vm_logs/debug_log.txt", 0); + R_ABORT_UNLESS(fs::OpenFile(std::addressof(m_debug_log_file), "sdmc:/atmosphere/cheat_vm_logs/debug_log.txt", fs::OpenMode_Write | fs::OpenMode_AllowAppend)); + m_debug_log_file_offset = 0; + m_has_debug_log_file = true; + #endif + } + + void CheatVirtualMachine::CloseDebugLogFile() { + #ifdef DMNT_CHEAT_VM_DEBUG_LOG + if (m_has_debug_log_file) { + fs::CloseFile(m_debug_log_file); + } + m_has_debug_log_file = false; + #endif + } + + void CheatVirtualMachine::LogToDebugFile(const char *format, ...) { + #ifdef DMNT_CHEAT_VM_DEBUG_LOG + if (!m_has_debug_log_file) { + return; + } + + { + std::va_list vl; + va_start(vl, format); + util::VSNPrintf(m_debug_log_format_buf, sizeof(m_debug_log_format_buf) - 1, format, vl); + va_end(vl); + } + + size_t fmt_len = std::strlen(m_debug_log_format_buf); + if (m_debug_log_format_buf[fmt_len - 1] != '\n') { + m_debug_log_format_buf[fmt_len + 0] = '\n'; + m_debug_log_format_buf[fmt_len + 1] = '\x00'; + fmt_len += 1; + } + + fs::WriteFile(m_debug_log_file, m_debug_log_file_offset, m_debug_log_format_buf, fmt_len, fs::WriteOption::Flush); + m_debug_log_file_offset += fmt_len; + #else + AMS_UNUSED(format); + #endif + } + + void CheatVirtualMachine::LogOpcode(const CheatVmOpcode *opcode) { + #ifndef DMNT_CHEAT_VM_DEBUG_LOG + return; + #endif + switch (opcode->opcode) { + case CheatVmOpcodeType_StoreStatic: + this->LogToDebugFile("Opcode: Store Static\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode->store_static.bit_width); + this->LogToDebugFile("Mem Type: %x\n", opcode->store_static.mem_type); + this->LogToDebugFile("Reg Idx: %x\n", opcode->store_static.offset_register); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->store_static.rel_address); + this->LogToDebugFile("Value: %lx\n", opcode->store_static.value.bit64); + break; + case CheatVmOpcodeType_BeginConditionalBlock: + this->LogToDebugFile("Opcode: Begin Conditional\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode->begin_cond.bit_width); + this->LogToDebugFile("Mem Type: %x\n", opcode->begin_cond.mem_type); + this->LogToDebugFile("Cond Type: %x\n", opcode->begin_cond.cond_type); + this->LogToDebugFile("Inc Ofs reg: %d\n", opcode->begin_cond.include_ofs_reg); + this->LogToDebugFile("Ofs Reg Idx: %x\n", opcode->begin_cond.ofs_reg_index); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->begin_cond.rel_address); + this->LogToDebugFile("Value: %lx\n", opcode->begin_cond.value.bit64); + break; + case CheatVmOpcodeType_EndConditionalBlock: + this->LogToDebugFile("Opcode: End Conditional\n"); + break; + case CheatVmOpcodeType_ControlLoop: + if (opcode->ctrl_loop.start_loop) { + this->LogToDebugFile("Opcode: Start Loop\n"); + this->LogToDebugFile("Reg Idx: %x\n", opcode->ctrl_loop.reg_index); + this->LogToDebugFile("Num Iters: %x\n", opcode->ctrl_loop.num_iters); + } else { + this->LogToDebugFile("Opcode: End Loop\n"); + this->LogToDebugFile("Reg Idx: %x\n", opcode->ctrl_loop.reg_index); + } + break; + case CheatVmOpcodeType_LoadRegisterStatic: + this->LogToDebugFile("Opcode: Load Register Static\n"); + this->LogToDebugFile("Reg Idx: %x\n", opcode->ldr_static.reg_index); + this->LogToDebugFile("Value: %lx\n", opcode->ldr_static.value); + break; + case CheatVmOpcodeType_LoadRegisterMemory: + this->LogToDebugFile("Opcode: Load Register Memory\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode->ldr_memory.bit_width); + this->LogToDebugFile("Reg Idx: %x\n", opcode->ldr_memory.reg_index); + this->LogToDebugFile("Mem Type: %x\n", opcode->ldr_memory.mem_type); + this->LogToDebugFile("From Reg: %d\n", opcode->ldr_memory.load_from_reg); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->ldr_memory.rel_address); + break; + case CheatVmOpcodeType_StoreStaticToAddress: + this->LogToDebugFile("Opcode: Store Static to Address\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode->str_static.bit_width); + this->LogToDebugFile("Reg Idx: %x\n", opcode->str_static.reg_index); + if (opcode->str_static.add_offset_reg) { + this->LogToDebugFile("O Reg Idx: %x\n", opcode->str_static.offset_reg_index); + } + this->LogToDebugFile("Incr Reg: %d\n", opcode->str_static.increment_reg); + this->LogToDebugFile("Value: %lx\n", opcode->str_static.value); + break; + case CheatVmOpcodeType_PerformArithmeticStatic: + this->LogToDebugFile("Opcode: Perform Static Arithmetic\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode->perform_math_static.bit_width); + this->LogToDebugFile("Reg Idx: %x\n", opcode->perform_math_static.reg_index); + this->LogToDebugFile("Math Type: %x\n", opcode->perform_math_static.math_type); + this->LogToDebugFile("Value: %lx\n", opcode->perform_math_static.value); + break; + case CheatVmOpcodeType_BeginKeypressConditionalBlock: + this->LogToDebugFile("Opcode: Begin Keypress Conditional\n"); + this->LogToDebugFile("Key Mask: %x\n", opcode->begin_keypress_cond.key_mask); + break; + case CheatVmOpcodeType_BeginExtendedKeypressConditionalBlock: + this->LogToDebugFile("Opcode: Begin Extended Keypress Conditional\n"); + this->LogToDebugFile("Key Mask: %x\n", opcode->begin_ext_keypress_cond.key_mask); + this->LogToDebugFile("Auto Repeat: %d\n", opcode->begin_ext_keypress_cond.auto_repeat); + break; + case CheatVmOpcodeType_PerformArithmeticRegister: + this->LogToDebugFile("Opcode: Perform Register Arithmetic\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode->perform_math_reg.bit_width); + this->LogToDebugFile("Dst Idx: %x\n", opcode->perform_math_reg.dst_reg_index); + this->LogToDebugFile("Src1 Idx: %x\n", opcode->perform_math_reg.src_reg_1_index); + if (opcode->perform_math_reg.has_immediate) { + this->LogToDebugFile("Value: %lx\n", opcode->perform_math_reg.value.bit64); + } else { + this->LogToDebugFile("Src2 Idx: %x\n", opcode->perform_math_reg.src_reg_2_index); + } + break; + case CheatVmOpcodeType_StoreRegisterToAddress: + this->LogToDebugFile("Opcode: Store Register to Address\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode->str_register.bit_width); + this->LogToDebugFile("S Reg Idx: %x\n", opcode->str_register.str_reg_index); + this->LogToDebugFile("A Reg Idx: %x\n", opcode->str_register.addr_reg_index); + this->LogToDebugFile("Incr Reg: %d\n", opcode->str_register.increment_reg); + switch (opcode->str_register.ofs_type) { + case StoreRegisterOffsetType_None: + break; + case StoreRegisterOffsetType_Reg: + this->LogToDebugFile("O Reg Idx: %x\n", opcode->str_register.ofs_reg_index); + break; + case StoreRegisterOffsetType_Imm: + this->LogToDebugFile("Rel Addr: %lx\n", opcode->str_register.rel_address); + break; + case StoreRegisterOffsetType_MemReg: + this->LogToDebugFile("Mem Type: %x\n", opcode->str_register.mem_type); + break; + case StoreRegisterOffsetType_MemImm: + case StoreRegisterOffsetType_MemImmReg: + this->LogToDebugFile("Mem Type: %x\n", opcode->str_register.mem_type); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->str_register.rel_address); + break; + } + break; + case CheatVmOpcodeType_BeginRegisterConditionalBlock: + this->LogToDebugFile("Opcode: Begin Register Conditional\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode->begin_reg_cond.bit_width); + this->LogToDebugFile("Cond Type: %x\n", opcode->begin_reg_cond.cond_type); + this->LogToDebugFile("V Reg Idx: %x\n", opcode->begin_reg_cond.val_reg_index); + switch (opcode->begin_reg_cond.comp_type) { + case CompareRegisterValueType_StaticValue: + this->LogToDebugFile("Comp Type: Static Value\n"); + this->LogToDebugFile("Value: %lx\n", opcode->begin_reg_cond.value.bit64); + break; + case CompareRegisterValueType_OtherRegister: + this->LogToDebugFile("Comp Type: Other Register\n"); + this->LogToDebugFile("X Reg Idx: %x\n", opcode->begin_reg_cond.other_reg_index); + break; + case CompareRegisterValueType_MemoryRelAddr: + this->LogToDebugFile("Comp Type: Memory Relative Address\n"); + this->LogToDebugFile("Mem Type: %x\n", opcode->begin_reg_cond.mem_type); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->begin_reg_cond.rel_address); + break; + case CompareRegisterValueType_MemoryOfsReg: + this->LogToDebugFile("Comp Type: Memory Offset Register\n"); + this->LogToDebugFile("Mem Type: %x\n", opcode->begin_reg_cond.mem_type); + this->LogToDebugFile("O Reg Idx: %x\n", opcode->begin_reg_cond.ofs_reg_index); + break; + case CompareRegisterValueType_RegisterRelAddr: + this->LogToDebugFile("Comp Type: Register Relative Address\n"); + this->LogToDebugFile("A Reg Idx: %x\n", opcode->begin_reg_cond.addr_reg_index); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->begin_reg_cond.rel_address); + break; + case CompareRegisterValueType_RegisterOfsReg: + this->LogToDebugFile("Comp Type: Register Offset Register\n"); + this->LogToDebugFile("A Reg Idx: %x\n", opcode->begin_reg_cond.addr_reg_index); + this->LogToDebugFile("O Reg Idx: %x\n", opcode->begin_reg_cond.ofs_reg_index); + break; + } + break; + case CheatVmOpcodeType_SaveRestoreRegister: + this->LogToDebugFile("Opcode: Save or Restore Register\n"); + this->LogToDebugFile("Dst Idx: %x\n", opcode->save_restore_reg.dst_index); + this->LogToDebugFile("Src Idx: %x\n", opcode->save_restore_reg.src_index); + this->LogToDebugFile("Op Type: %d\n", opcode->save_restore_reg.op_type); + break; + case CheatVmOpcodeType_SaveRestoreRegisterMask: + this->LogToDebugFile("Opcode: Save or Restore Register Mask\n"); + this->LogToDebugFile("Op Type: %d\n", opcode->save_restore_regmask.op_type); + for (size_t i = 0; i < NumRegisters; i++) { + this->LogToDebugFile("Act[%02x]: %d\n", i, opcode->save_restore_regmask.should_operate[i]); + } + break; + case CheatVmOpcodeType_ReadWriteStaticRegister: + this->LogToDebugFile("Opcode: Read/Write Static Register\n"); + if (opcode->rw_static_reg.static_idx < NumReadableStaticRegisters) { + this->LogToDebugFile("Op Type: ReadStaticRegister\n"); + } else { + this->LogToDebugFile("Op Type: WriteStaticRegister\n"); + } + this->LogToDebugFile("Reg Idx: %x\n", opcode->rw_static_reg.idx); + this->LogToDebugFile("Stc Idx: %x\n", opcode->rw_static_reg.static_idx); + break; + case CheatVmOpcodeType_PauseProcess: + this->LogToDebugFile("Opcode: Pause Cheat Process\n"); + break; + case CheatVmOpcodeType_ResumeProcess: + this->LogToDebugFile("Opcode: Resume Cheat Process\n"); + break; + case CheatVmOpcodeType_DebugLog: + this->LogToDebugFile("Opcode: Debug Log\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode->debug_log.bit_width); + this->LogToDebugFile("Log ID: %x\n", opcode->debug_log.log_id); + this->LogToDebugFile("Val Type: %x\n", opcode->debug_log.val_type); + switch (opcode->debug_log.val_type) { + case DebugLogValueType_RegisterValue: + this->LogToDebugFile("Val Type: Register Value\n"); + this->LogToDebugFile("X Reg Idx: %x\n", opcode->debug_log.val_reg_index); + break; + case DebugLogValueType_MemoryRelAddr: + this->LogToDebugFile("Val Type: Memory Relative Address\n"); + this->LogToDebugFile("Mem Type: %x\n", opcode->debug_log.mem_type); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->debug_log.rel_address); + break; + case DebugLogValueType_MemoryOfsReg: + this->LogToDebugFile("Val Type: Memory Offset Register\n"); + this->LogToDebugFile("Mem Type: %x\n", opcode->debug_log.mem_type); + this->LogToDebugFile("O Reg Idx: %x\n", opcode->debug_log.ofs_reg_index); + break; + case DebugLogValueType_RegisterRelAddr: + this->LogToDebugFile("Val Type: Register Relative Address\n"); + this->LogToDebugFile("A Reg Idx: %x\n", opcode->debug_log.addr_reg_index); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->debug_log.rel_address); + break; + case DebugLogValueType_RegisterOfsReg: + this->LogToDebugFile("Val Type: Register Offset Register\n"); + this->LogToDebugFile("A Reg Idx: %x\n", opcode->debug_log.addr_reg_index); + this->LogToDebugFile("O Reg Idx: %x\n", opcode->debug_log.ofs_reg_index); + break; + } + break; + default: + this->LogToDebugFile("Unknown opcode: %x\n", opcode->opcode); + break; + } + } + + bool CheatVirtualMachine::DecodeNextOpcode(CheatVmOpcode *out) { + /* If we've ever seen a decode failure, return false. */ + bool valid = m_decode_success; + CheatVmOpcode opcode = {}; + ON_SCOPE_EXIT { + m_decode_success &= valid; + if (valid) { + *out = opcode; + } + }; + + /* Helper function for getting instruction dwords. */ + auto GetNextDword = [&]() { + if (m_instruction_ptr >= m_num_opcodes) { + valid = false; + return static_cast<u32>(0); + } + return m_program[m_instruction_ptr++]; + }; + + /* Helper function for parsing a VmInt. */ + auto GetNextVmInt = [&](const u32 bit_width) { + VmInt val = {0}; + + const u32 first_dword = GetNextDword(); + switch (bit_width) { + case 1: + val.bit8 = (u8)first_dword; + break; + case 2: + val.bit16 = (u16)first_dword; + break; + case 4: + val.bit32 = first_dword; + break; + case 8: + val.bit64 = (((u64)first_dword) << 32ul) | ((u64)GetNextDword()); + break; + } + + return val; + }; + + /* Read opcode. */ + const u32 first_dword = GetNextDword(); + if (!valid) { + return valid; + } + + opcode.opcode = (CheatVmOpcodeType)(((first_dword >> 28) & 0xF)); + if (opcode.opcode >= CheatVmOpcodeType_ExtendedWidth) { + opcode.opcode = (CheatVmOpcodeType)((((u32)opcode.opcode) << 4) | ((first_dword >> 24) & 0xF)); + } + if (opcode.opcode >= CheatVmOpcodeType_DoubleExtendedWidth) { + opcode.opcode = (CheatVmOpcodeType)((((u32)opcode.opcode) << 4) | ((first_dword >> 20) & 0xF)); + } + + /* detect condition start. */ + switch (opcode.opcode) { + case CheatVmOpcodeType_BeginConditionalBlock: + case CheatVmOpcodeType_BeginKeypressConditionalBlock: + case CheatVmOpcodeType_BeginExtendedKeypressConditionalBlock: + case CheatVmOpcodeType_BeginRegisterConditionalBlock: + opcode.begin_conditional_block = true; + break; + default: + opcode.begin_conditional_block = false; + break; + } + + switch (opcode.opcode) { + case CheatVmOpcodeType_StoreStatic: + { + /* 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY) */ + /* Read additional words. */ + const u32 second_dword = GetNextDword(); + opcode.store_static.bit_width = (first_dword >> 24) & 0xF; + opcode.store_static.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF); + opcode.store_static.offset_register = ((first_dword >> 16) & 0xF); + opcode.store_static.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword); + opcode.store_static.value = GetNextVmInt(opcode.store_static.bit_width); + } + break; + case CheatVmOpcodeType_BeginConditionalBlock: + { + /* 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY) */ + /* Read additional words. */ + const u32 second_dword = GetNextDword(); + opcode.begin_cond.bit_width = (first_dword >> 24) & 0xF; + opcode.begin_cond.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF); + opcode.begin_cond.cond_type = (ConditionalComparisonType)((first_dword >> 16) & 0xF); + opcode.begin_cond.include_ofs_reg = ((first_dword >> 12) & 0xF) != 0; + opcode.begin_cond.ofs_reg_index = ((first_dword >> 8) & 0xF); + opcode.begin_cond.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword); + opcode.begin_cond.value = GetNextVmInt(opcode.begin_cond.bit_width); + } + break; + case CheatVmOpcodeType_EndConditionalBlock: + { + /* 2X000000 */ + opcode.end_cond.is_else = ((first_dword >> 24) & 0xF) == 1; + } + break; + case CheatVmOpcodeType_ControlLoop: + { + /* 300R0000 VVVVVVVV */ + /* 310R0000 */ + /* Parse register, whether loop start or loop end. */ + opcode.ctrl_loop.start_loop = ((first_dword >> 24) & 0xF) == 0; + opcode.ctrl_loop.reg_index = ((first_dword >> 16) & 0xF); + + /* Read number of iters if loop start. */ + if (opcode.ctrl_loop.start_loop) { + opcode.ctrl_loop.num_iters = GetNextDword(); + } + } + break; + case CheatVmOpcodeType_LoadRegisterStatic: + { + /* 400R0000 VVVVVVVV VVVVVVVV */ + /* Read additional words. */ + opcode.ldr_static.reg_index = ((first_dword >> 16) & 0xF); + opcode.ldr_static.value = (((u64)GetNextDword()) << 32ul) | ((u64)GetNextDword()); + } + break; + case CheatVmOpcodeType_LoadRegisterMemory: + { + /* 5TMRI0AA AAAAAAAA */ + /* Read additional words. */ + const u32 second_dword = GetNextDword(); + opcode.ldr_memory.bit_width = (first_dword >> 24) & 0xF; + opcode.ldr_memory.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF); + opcode.ldr_memory.reg_index = ((first_dword >> 16) & 0xF); + opcode.ldr_memory.load_from_reg = ((first_dword >> 12) & 0xF); + opcode.ldr_memory.offset_register = ((first_dword >> 8) & 0xF); + opcode.ldr_memory.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword); + } + break; + case CheatVmOpcodeType_StoreStaticToAddress: + { + /* 6T0RIor0 VVVVVVVV VVVVVVVV */ + /* Read additional words. */ + opcode.str_static.bit_width = (first_dword >> 24) & 0xF; + opcode.str_static.reg_index = ((first_dword >> 16) & 0xF); + opcode.str_static.increment_reg = ((first_dword >> 12) & 0xF) != 0; + opcode.str_static.add_offset_reg = ((first_dword >> 8) & 0xF) != 0; + opcode.str_static.offset_reg_index = ((first_dword >> 4) & 0xF); + opcode.str_static.value = (((u64)GetNextDword()) << 32ul) | ((u64)GetNextDword()); + } + break; + case CheatVmOpcodeType_PerformArithmeticStatic: + { + /* 7T0RC000 VVVVVVVV */ + /* Read additional words. */ + opcode.perform_math_static.bit_width = (first_dword >> 24) & 0xF; + opcode.perform_math_static.reg_index = ((first_dword >> 16) & 0xF); + opcode.perform_math_static.math_type = (RegisterArithmeticType)((first_dword >> 12) & 0xF); + opcode.perform_math_static.value = GetNextDword(); + } + break; + case CheatVmOpcodeType_BeginKeypressConditionalBlock: + { + /* 8kkkkkkk */ + /* Just parse the mask. */ + opcode.begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF; + } + break; + case CheatVmOpcodeType_BeginExtendedKeypressConditionalBlock: + { + /* C4r00000 kkkkkkkk kkkkkkkk */ + /* Read additional words. */ + opcode.begin_ext_keypress_cond.key_mask = (u64)GetNextDword() << 32ul | (u64)GetNextDword(); + opcode.begin_ext_keypress_cond.auto_repeat = ((first_dword >> 20) & 0xF) != 0; + } + break; + case CheatVmOpcodeType_PerformArithmeticRegister: + { + /* 9TCRSIs0 (VVVVVVVV (VVVVVVVV)) */ + opcode.perform_math_reg.bit_width = (first_dword >> 24) & 0xF; + opcode.perform_math_reg.math_type = (RegisterArithmeticType)((first_dword >> 20) & 0xF); + opcode.perform_math_reg.dst_reg_index = ((first_dword >> 16) & 0xF); + opcode.perform_math_reg.src_reg_1_index = ((first_dword >> 12) & 0xF); + opcode.perform_math_reg.has_immediate = ((first_dword >> 8) & 0xF) != 0; + if (opcode.perform_math_reg.has_immediate) { + opcode.perform_math_reg.src_reg_2_index = 0; + opcode.perform_math_reg.value = GetNextVmInt(opcode.perform_math_reg.bit_width); + } else { + opcode.perform_math_reg.src_reg_2_index = ((first_dword >> 4) & 0xF); + } + } + break; + case CheatVmOpcodeType_StoreRegisterToAddress: + { + /* ATSRIOxa (aaaaaaaa) */ + /* A = opcode 10 */ + /* T = bit width */ + /* S = src register index */ + /* R = address register index */ + /* I = 1 if increment address register, 0 if not increment address register */ + /* O = offset type, 0 = None, 1 = Register, 2 = Immediate, 3 = Memory Region, + 4 = Memory Region + Relative Address (ignore address register), 5 = Memory Region + Relative Address */ + /* x = offset register (for offset type 1), memory type (for offset type 3) */ + /* a = relative address (for offset type 2+3) */ + opcode.str_register.bit_width = (first_dword >> 24) & 0xF; + opcode.str_register.str_reg_index = ((first_dword >> 20) & 0xF); + opcode.str_register.addr_reg_index = ((first_dword >> 16) & 0xF); + opcode.str_register.increment_reg = ((first_dword >> 12) & 0xF) != 0; + opcode.str_register.ofs_type = (StoreRegisterOffsetType)(((first_dword >> 8) & 0xF)); + opcode.str_register.ofs_reg_index = ((first_dword >> 4) & 0xF); + switch (opcode.str_register.ofs_type) { + case StoreRegisterOffsetType_None: + case StoreRegisterOffsetType_Reg: + /* Nothing more to do */ + break; + case StoreRegisterOffsetType_Imm: + opcode.str_register.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + case StoreRegisterOffsetType_MemReg: + opcode.str_register.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + break; + case StoreRegisterOffsetType_MemImm: + case StoreRegisterOffsetType_MemImmReg: + opcode.str_register.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + opcode.str_register.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + default: + opcode.str_register.ofs_type = StoreRegisterOffsetType_None; + break; + } + } + break; + case CheatVmOpcodeType_BeginRegisterConditionalBlock: + { + /* C0TcSX## */ + /* C0TcS0Ma aaaaaaaa */ + /* C0TcS1Mr */ + /* C0TcS2Ra aaaaaaaa */ + /* C0TcS3Rr */ + /* C0TcS400 VVVVVVVV (VVVVVVVV) */ + /* C0TcS5X0 */ + /* C0 = opcode 0xC0 */ + /* T = bit width */ + /* c = condition type. */ + /* S = source register. */ + /* X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset register, */ + /* 2 = register with relative offset, 3 = register with offset register, 4 = static value, 5 = other register. */ + /* M = memory type. */ + /* R = address register. */ + /* a = relative address. */ + /* r = offset register. */ + /* X = other register. */ + /* V = value. */ + opcode.begin_reg_cond.bit_width = (first_dword >> 20) & 0xF; + opcode.begin_reg_cond.cond_type = (ConditionalComparisonType)((first_dword >> 16) & 0xF); + opcode.begin_reg_cond.val_reg_index = ((first_dword >> 12) & 0xF); + opcode.begin_reg_cond.comp_type = (CompareRegisterValueType)((first_dword >> 8) & 0xF); + + switch (opcode.begin_reg_cond.comp_type) { + case CompareRegisterValueType_StaticValue: + opcode.begin_reg_cond.value = GetNextVmInt(opcode.begin_reg_cond.bit_width); + break; + case CompareRegisterValueType_OtherRegister: + opcode.begin_reg_cond.other_reg_index = ((first_dword >> 4) & 0xF); + break; + case CompareRegisterValueType_MemoryRelAddr: + opcode.begin_reg_cond.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + opcode.begin_reg_cond.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + case CompareRegisterValueType_MemoryOfsReg: + opcode.begin_reg_cond.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + opcode.begin_reg_cond.ofs_reg_index = (first_dword & 0xF); + break; + case CompareRegisterValueType_RegisterRelAddr: + opcode.begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF); + opcode.begin_reg_cond.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + case CompareRegisterValueType_RegisterOfsReg: + opcode.begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF); + opcode.begin_reg_cond.ofs_reg_index = (first_dword & 0xF); + break; + } + } + break; + case CheatVmOpcodeType_SaveRestoreRegister: + { + /* C10D0Sx0 */ + /* C1 = opcode 0xC1 */ + /* D = destination index. */ + /* S = source index. */ + /* x = 3 if clearing reg, 2 if clearing saved value, 1 if saving a register, 0 if restoring a register. */ + /* NOTE: If we add more save slots later, current encoding is backwards compatible. */ + opcode.save_restore_reg.dst_index = (first_dword >> 16) & 0xF; + opcode.save_restore_reg.src_index = (first_dword >> 8) & 0xF; + opcode.save_restore_reg.op_type = (SaveRestoreRegisterOpType)((first_dword >> 4) & 0xF); + } + break; + case CheatVmOpcodeType_SaveRestoreRegisterMask: + { + /* C2x0XXXX */ + /* C2 = opcode 0xC2 */ + /* x = 3 if clearing reg, 2 if clearing saved value, 1 if saving, 0 if restoring. */ + /* X = 16-bit bitmask, bit i --> save or restore register i. */ + opcode.save_restore_regmask.op_type = (SaveRestoreRegisterOpType)((first_dword >> 20) & 0xF); + for (size_t i = 0; i < NumRegisters; i++) { + opcode.save_restore_regmask.should_operate[i] = (first_dword & (1u << i)) != 0; + } + } + break; + case CheatVmOpcodeType_ReadWriteStaticRegister: + { + /* C3000XXx */ + /* C3 = opcode 0xC3. */ + /* XX = static register index. */ + /* x = register index. */ + opcode.rw_static_reg.static_idx = ((first_dword >> 4) & 0xFF); + opcode.rw_static_reg.idx = (first_dword & 0xF); + } + break; + case CheatVmOpcodeType_PauseProcess: + { + /* FF0????? */ + /* FF0 = opcode 0xFF0 */ + /* Pauses the current process. */ + } + break; + case CheatVmOpcodeType_ResumeProcess: + { + /* FF1????? */ + /* FF1 = opcode 0xFF1 */ + /* Resumes the current process. */ + } + break; + case CheatVmOpcodeType_DebugLog: + { + /* FFFTIX## */ + /* FFFTI0Ma aaaaaaaa */ + /* FFFTI1Mr */ + /* FFFTI2Ra aaaaaaaa */ + /* FFFTI3Rr */ + /* FFFTI4X0 */ + /* FFF = opcode 0xFFF */ + /* T = bit width. */ + /* I = log id. */ + /* X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset register, */ + /* 2 = register with relative offset, 3 = register with offset register, 4 = register value. */ + /* M = memory type. */ + /* R = address register. */ + /* a = relative address. */ + /* r = offset register. */ + /* X = value register. */ + opcode.debug_log.bit_width = (first_dword >> 16) & 0xF; + opcode.debug_log.log_id = ((first_dword >> 12) & 0xF); + opcode.debug_log.val_type = (DebugLogValueType)((first_dword >> 8) & 0xF); + + switch (opcode.debug_log.val_type) { + case DebugLogValueType_RegisterValue: + opcode.debug_log.val_reg_index = ((first_dword >> 4) & 0xF); + break; + case DebugLogValueType_MemoryRelAddr: + opcode.debug_log.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + opcode.debug_log.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + case DebugLogValueType_MemoryOfsReg: + opcode.debug_log.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + opcode.debug_log.ofs_reg_index = (first_dword & 0xF); + break; + case DebugLogValueType_RegisterRelAddr: + opcode.debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); + opcode.debug_log.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + case DebugLogValueType_RegisterOfsReg: + opcode.debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); + opcode.debug_log.ofs_reg_index = (first_dword & 0xF); + break; + } + } + break; + case CheatVmOpcodeType_ExtendedWidth: + case CheatVmOpcodeType_DoubleExtendedWidth: + default: + /* Unrecognized instruction cannot be decoded. */ + valid = false; + break; + } + + /* End decoding. */ + return valid; + } + + void CheatVirtualMachine::SkipConditionalBlock(bool is_if) { + if (m_condition_depth > 0) { + /* We want to continue until we're out of the current block. */ + const size_t desired_depth = m_condition_depth - 1; + + CheatVmOpcode skip_opcode; + while (m_condition_depth > desired_depth && this->DecodeNextOpcode(std::addressof(skip_opcode))) { + /* Decode instructions until we see end of the current conditional block. */ + /* NOTE: This is broken in gateway's implementation. */ + /* Gateway currently checks for "0x2" instead of "0x20000000" */ + /* In addition, they do a linear scan instead of correctly decoding opcodes. */ + /* This causes issues if "0x2" appears as an immediate in the conditional block... */ + + /* We also support nesting of conditional blocks, and Gateway does not. */ + if (skip_opcode.begin_conditional_block) { + m_condition_depth++; + } else if (skip_opcode.opcode == CheatVmOpcodeType_EndConditionalBlock) { + if (!skip_opcode.end_cond.is_else) { + m_condition_depth--; + } else if (is_if && m_condition_depth - 1 == desired_depth) { + /* An if will continue to an else at the same depth. */ + break; + } + } + } + } else { + /* Skipping, but m_condition_depth = 0. */ + /* This is an error condition. */ + /* This could occur with a mismatched "else" opcode, for example. */ + R_ABORT_UNLESS(ResultVirtualMachineInvalidConditionDepth()); + } + } + + u64 CheatVirtualMachine::GetVmInt(VmInt value, u32 bit_width) { + switch (bit_width) { + case 1: + return value.bit8; + case 2: + return value.bit16; + case 4: + return value.bit32; + case 8: + return value.bit64; + default: + /* Invalid bit width -> return 0. */ + return 0; + } + } + + u64 CheatVirtualMachine::GetCheatProcessAddress(const CheatProcessMetadata* metadata, MemoryAccessType mem_type, u64 rel_address) { + switch (mem_type) { + case MemoryAccessType_MainNso: + default: + return metadata->main_nso_extents.base + rel_address; + case MemoryAccessType_Heap: + return metadata->heap_extents.base + rel_address; + case MemoryAccessType_Alias: + return metadata->alias_extents.base + rel_address; + case MemoryAccessType_Aslr: + return metadata->aslr_extents.base + rel_address; + case MemoryAccessType_NonRelative: + return rel_address; + } + } + + void CheatVirtualMachine::ResetState() { + for (size_t i = 0; i < CheatVirtualMachine::NumRegisters; i++) { + m_registers[i] = 0; + m_saved_values[i] = 0; + m_loop_tops[i] = 0; + } + m_instruction_ptr = 0; + m_condition_depth = 0; + m_decode_success = true; + } + + bool CheatVirtualMachine::LoadProgram(const CheatEntry *cheats, size_t num_cheats) { + /* Reset opcode count. */ + m_num_opcodes = 0; + + for (size_t i = 0; i < num_cheats; i++) { + if (cheats[i].enabled) { + /* Bounds check. */ + if (cheats[i].definition.num_opcodes + m_num_opcodes > MaximumProgramOpcodeCount) { + m_num_opcodes = 0; + return false; + } + + for (size_t n = 0; n < cheats[i].definition.num_opcodes; n++) { + m_program[m_num_opcodes++] = cheats[i].definition.opcodes[n]; + } + } + } + + return true; + } + + static u64 s_keyold = 0; + void CheatVirtualMachine::Execute(const CheatProcessMetadata *metadata) { + CheatVmOpcode cur_opcode; + u64 kHeld = 0; + + /* Get Keys held. */ + hid::GetKeysHeld(std::addressof(kHeld)); + + this->OpenDebugLogFile(); + ON_SCOPE_EXIT { this->CloseDebugLogFile(); }; + + this->LogToDebugFile("Started VM execution.\n"); + this->LogToDebugFile("Main NSO: %012lx\n", metadata->main_nso_extents.base); + this->LogToDebugFile("Heap: %012lx\n", metadata->main_nso_extents.base); + this->LogToDebugFile("Keys Held: %08x\n", (u32)(kHeld & 0x0FFFFFFF)); + + /* Clear VM state. */ + this->ResetState(); + + /* Loop until program finishes. */ + while (this->DecodeNextOpcode(std::addressof(cur_opcode))) { + this->LogToDebugFile("Instruction Ptr: %04x\n", (u32)m_instruction_ptr); + + for (size_t i = 0; i < NumRegisters; i++) { + this->LogToDebugFile("Registers[%02x]: %016lx\n", i, m_registers[i]); + } + + for (size_t i = 0; i < NumRegisters; i++) { + this->LogToDebugFile("SavedRegs[%02x]: %016lx\n", i, m_saved_values[i]); + } + this->LogOpcode(std::addressof(cur_opcode)); + + /* Increment conditional depth, if relevant. */ + if (cur_opcode.begin_conditional_block) { + m_condition_depth++; + } + + switch (cur_opcode.opcode) { + case CheatVmOpcodeType_StoreStatic: + { + /* Calculate address, write value to memory. */ + u64 dst_address = GetCheatProcessAddress(metadata, cur_opcode.store_static.mem_type, cur_opcode.store_static.rel_address + m_registers[cur_opcode.store_static.offset_register]); + u64 dst_value = GetVmInt(cur_opcode.store_static.value, cur_opcode.store_static.bit_width); + switch (cur_opcode.store_static.bit_width) { + case 1: + case 2: + case 4: + case 8: + dmnt::cheat::impl::WriteCheatProcessMemoryUnsafe(dst_address, std::addressof(dst_value), cur_opcode.store_static.bit_width); + break; + } + } + break; + case CheatVmOpcodeType_BeginConditionalBlock: + { + /* Read value from memory. */ + u64 src_address = GetCheatProcessAddress(metadata, cur_opcode.begin_cond.mem_type, (cur_opcode.begin_cond.include_ofs_reg) ? m_registers[cur_opcode.begin_cond.ofs_reg_index] + cur_opcode.begin_cond.rel_address : cur_opcode.begin_cond.rel_address); + u64 src_value = 0; + switch (cur_opcode.store_static.bit_width) { + case 1: + case 2: + case 4: + case 8: + dmnt::cheat::impl::ReadCheatProcessMemoryUnsafe(src_address, std::addressof(src_value), cur_opcode.begin_cond.bit_width); + break; + } + /* Check against condition. */ + u64 cond_value = GetVmInt(cur_opcode.begin_cond.value, cur_opcode.begin_cond.bit_width); + bool cond_met = false; + switch (cur_opcode.begin_cond.cond_type) { + case ConditionalComparisonType_GT: + cond_met = src_value > cond_value; + break; + case ConditionalComparisonType_GE: + cond_met = src_value >= cond_value; + break; + case ConditionalComparisonType_LT: + cond_met = src_value < cond_value; + break; + case ConditionalComparisonType_LE: + cond_met = src_value <= cond_value; + break; + case ConditionalComparisonType_EQ: + cond_met = src_value == cond_value; + break; + case ConditionalComparisonType_NE: + cond_met = src_value != cond_value; + break; + } + /* Skip conditional block if condition not met. */ + if (!cond_met) { + this->SkipConditionalBlock(true); + } + } + break; + case CheatVmOpcodeType_EndConditionalBlock: + if (cur_opcode.end_cond.is_else) { + /* Skip to the end of the conditional block. */ + this->SkipConditionalBlock(false); + } else { + /* Decrement the condition depth. */ + /* We will assume, graciously, that mismatched conditional block ends are a nop. */ + if (m_condition_depth > 0) { + m_condition_depth--; + } + } + break; + case CheatVmOpcodeType_ControlLoop: + if (cur_opcode.ctrl_loop.start_loop) { + /* Start a loop. */ + m_registers[cur_opcode.ctrl_loop.reg_index] = cur_opcode.ctrl_loop.num_iters; + m_loop_tops[cur_opcode.ctrl_loop.reg_index] = m_instruction_ptr; + } else { + /* End a loop. */ + m_registers[cur_opcode.ctrl_loop.reg_index]--; + if (m_registers[cur_opcode.ctrl_loop.reg_index] != 0) { + m_instruction_ptr = m_loop_tops[cur_opcode.ctrl_loop.reg_index]; + } + } + break; + case CheatVmOpcodeType_LoadRegisterStatic: + /* Set a register to a static value. */ + m_registers[cur_opcode.ldr_static.reg_index] = cur_opcode.ldr_static.value; + break; + case CheatVmOpcodeType_LoadRegisterMemory: + { + /* Choose source address. */ + u64 src_address; + if (cur_opcode.ldr_memory.load_from_reg == 1) { + src_address = m_registers[cur_opcode.ldr_memory.reg_index] + cur_opcode.ldr_memory.rel_address; + } else if (cur_opcode.ldr_memory.load_from_reg == 2) { + src_address = m_registers[cur_opcode.ldr_memory.offset_register] + cur_opcode.ldr_memory.rel_address; + } else if (cur_opcode.ldr_memory.load_from_reg == 3) { + src_address = GetCheatProcessAddress(metadata, cur_opcode.ldr_memory.mem_type, m_registers[cur_opcode.ldr_memory.offset_register] + cur_opcode.ldr_memory.rel_address); + } else { + src_address = GetCheatProcessAddress(metadata, cur_opcode.ldr_memory.mem_type, cur_opcode.ldr_memory.rel_address); + } + /* Read into register. Gateway only reads on valid bitwidth. */ + switch (cur_opcode.ldr_memory.bit_width) { + case 1: + case 2: + case 4: + case 8: + dmnt::cheat::impl::ReadCheatProcessMemoryUnsafe(src_address, std::addressof(m_registers[cur_opcode.ldr_memory.reg_index]), cur_opcode.ldr_memory.bit_width); + break; + } + } + break; + case CheatVmOpcodeType_StoreStaticToAddress: + { + /* Calculate address. */ + u64 dst_address = m_registers[cur_opcode.str_static.reg_index]; + u64 dst_value = cur_opcode.str_static.value; + if (cur_opcode.str_static.add_offset_reg) { + dst_address += m_registers[cur_opcode.str_static.offset_reg_index]; + } + /* Write value to memory. Gateway only writes on valid bitwidth. */ + switch (cur_opcode.str_static.bit_width) { + case 1: + case 2: + case 4: + case 8: + dmnt::cheat::impl::WriteCheatProcessMemoryUnsafe(dst_address, std::addressof(dst_value), cur_opcode.str_static.bit_width); + break; + } + /* Increment register if relevant. */ + if (cur_opcode.str_static.increment_reg) { + m_registers[cur_opcode.str_static.reg_index] += cur_opcode.str_static.bit_width; + } + } + break; + case CheatVmOpcodeType_PerformArithmeticStatic: + { + /* Do requested math. */ + switch (cur_opcode.perform_math_static.math_type) { + case RegisterArithmeticType_Addition: + m_registers[cur_opcode.perform_math_static.reg_index] += (u64)cur_opcode.perform_math_static.value; + break; + case RegisterArithmeticType_Subtraction: + m_registers[cur_opcode.perform_math_static.reg_index] -= (u64)cur_opcode.perform_math_static.value; + break; + case RegisterArithmeticType_Multiplication: + m_registers[cur_opcode.perform_math_static.reg_index] *= (u64)cur_opcode.perform_math_static.value; + break; + case RegisterArithmeticType_LeftShift: + m_registers[cur_opcode.perform_math_static.reg_index] <<= (u64)cur_opcode.perform_math_static.value; + break; + case RegisterArithmeticType_RightShift: + m_registers[cur_opcode.perform_math_static.reg_index] >>= (u64)cur_opcode.perform_math_static.value; + break; + default: + /* Do not handle extensions here. */ + break; + } + /* Apply bit width. */ + switch (cur_opcode.perform_math_static.bit_width) { + case 1: + m_registers[cur_opcode.perform_math_static.reg_index] = static_cast<u8>(m_registers[cur_opcode.perform_math_static.reg_index]); + break; + case 2: + m_registers[cur_opcode.perform_math_static.reg_index] = static_cast<u16>(m_registers[cur_opcode.perform_math_static.reg_index]); + break; + case 4: + m_registers[cur_opcode.perform_math_static.reg_index] = static_cast<u32>(m_registers[cur_opcode.perform_math_static.reg_index]); + break; + case 8: + m_registers[cur_opcode.perform_math_static.reg_index] = static_cast<u64>(m_registers[cur_opcode.perform_math_static.reg_index]); + break; + } + } + break; + case CheatVmOpcodeType_BeginKeypressConditionalBlock: + /* Check for keypress. */ + if ((cur_opcode.begin_keypress_cond.key_mask & kHeld) != cur_opcode.begin_keypress_cond.key_mask) { + /* Keys not pressed. Skip conditional block. */ + this->SkipConditionalBlock(true); + } + break; + case CheatVmOpcodeType_BeginExtendedKeypressConditionalBlock: + /* Check for keypress. */ + if (!cur_opcode.begin_ext_keypress_cond.auto_repeat) { + if ((cur_opcode.begin_ext_keypress_cond.key_mask & kHeld) != (cur_opcode.begin_ext_keypress_cond.key_mask) || (cur_opcode.begin_ext_keypress_cond.key_mask & s_keyold) == (cur_opcode.begin_ext_keypress_cond.key_mask)) { + /* Keys not pressed. Skip conditional block. */ + this->SkipConditionalBlock(true); + } + } else if ((cur_opcode.begin_ext_keypress_cond.key_mask & kHeld) != cur_opcode.begin_ext_keypress_cond.key_mask) { + /* Keys not pressed. Skip conditional block. */ + this->SkipConditionalBlock(true); + } + break; + case CheatVmOpcodeType_PerformArithmeticRegister: + { + const u64 operand_1_value = m_registers[cur_opcode.perform_math_reg.src_reg_1_index]; + const u64 operand_2_value = cur_opcode.perform_math_reg.has_immediate ? + GetVmInt(cur_opcode.perform_math_reg.value, cur_opcode.perform_math_reg.bit_width) : + m_registers[cur_opcode.perform_math_reg.src_reg_2_index]; + + u64 res_val = 0; + /* Do requested math. */ + switch (cur_opcode.perform_math_reg.math_type) { + case RegisterArithmeticType_Addition: + res_val = operand_1_value + operand_2_value; + break; + case RegisterArithmeticType_Subtraction: + res_val = operand_1_value - operand_2_value; + break; + case RegisterArithmeticType_Multiplication: + res_val = operand_1_value * operand_2_value; + break; + case RegisterArithmeticType_LeftShift: + res_val = operand_1_value << operand_2_value; + break; + case RegisterArithmeticType_RightShift: + res_val = operand_1_value >> operand_2_value; + break; + case RegisterArithmeticType_LogicalAnd: + res_val = operand_1_value & operand_2_value; + break; + case RegisterArithmeticType_LogicalOr: + res_val = operand_1_value | operand_2_value; + break; + case RegisterArithmeticType_LogicalNot: + res_val = ~operand_1_value; + break; + case RegisterArithmeticType_LogicalXor: + res_val = operand_1_value ^ operand_2_value; + break; + case RegisterArithmeticType_None: + res_val = operand_1_value; + break; + case RegisterArithmeticType_FloatAddition: + if (cur_opcode.perform_math_reg.bit_width == 4) { + res_val = std::bit_cast<std::uint32_t>(std::bit_cast<float>(static_cast<uint32_t>(operand_1_value)) + std::bit_cast<float>(static_cast<uint32_t>(operand_2_value))); + } else if (cur_opcode.perform_math_reg.bit_width == 8) { + res_val = std::bit_cast<std::uint64_t>(std::bit_cast<double>(operand_1_value) + std::bit_cast<double>(operand_2_value)); + } + break; + case RegisterArithmeticType_FloatSubtraction: + if (cur_opcode.perform_math_reg.bit_width == 4) { + res_val = std::bit_cast<std::uint32_t>(std::bit_cast<float>(static_cast<uint32_t>(operand_1_value)) - std::bit_cast<float>(static_cast<uint32_t>(operand_2_value))); + } else if (cur_opcode.perform_math_reg.bit_width == 8) { + res_val = std::bit_cast<std::uint64_t>(std::bit_cast<double>(operand_1_value) - std::bit_cast<double>(operand_2_value)); + } + break; + case RegisterArithmeticType_FloatMultiplication: + if (cur_opcode.perform_math_reg.bit_width == 4) { + res_val = std::bit_cast<std::uint32_t>(std::bit_cast<float>(static_cast<uint32_t>(operand_1_value)) * std::bit_cast<float>(static_cast<uint32_t>(operand_2_value))); + } else if (cur_opcode.perform_math_reg.bit_width == 8) { + res_val = std::bit_cast<std::uint64_t>(std::bit_cast<double>(operand_1_value) * std::bit_cast<double>(operand_2_value)); + } + break; + case RegisterArithmeticType_FloatDivision: + if (cur_opcode.perform_math_reg.bit_width == 4) { + res_val = std::bit_cast<std::uint32_t>(std::bit_cast<float>(static_cast<uint32_t>(operand_1_value)) / std::bit_cast<float>(static_cast<uint32_t>(operand_2_value))); + } else if (cur_opcode.perform_math_reg.bit_width == 8) { + res_val = std::bit_cast<std::uint64_t>(std::bit_cast<double>(operand_1_value) / std::bit_cast<double>(operand_2_value)); + } + break; + } + + + /* Apply bit width. */ + switch (cur_opcode.perform_math_reg.bit_width) { + case 1: + res_val = static_cast<u8>(res_val); + break; + case 2: + res_val = static_cast<u16>(res_val); + break; + case 4: + res_val = static_cast<u32>(res_val); + break; + case 8: + res_val = static_cast<u64>(res_val); + break; + } + + /* Save to register. */ + m_registers[cur_opcode.perform_math_reg.dst_reg_index] = res_val; + } + break; + case CheatVmOpcodeType_StoreRegisterToAddress: + { + /* Calculate address. */ + u64 dst_value = m_registers[cur_opcode.str_register.str_reg_index]; + u64 dst_address = m_registers[cur_opcode.str_register.addr_reg_index]; + switch (cur_opcode.str_register.ofs_type) { + case StoreRegisterOffsetType_None: + /* Nothing more to do */ + break; + case StoreRegisterOffsetType_Reg: + dst_address += m_registers[cur_opcode.str_register.ofs_reg_index]; + break; + case StoreRegisterOffsetType_Imm: + dst_address += cur_opcode.str_register.rel_address; + break; + case StoreRegisterOffsetType_MemReg: + dst_address = GetCheatProcessAddress(metadata, cur_opcode.str_register.mem_type, m_registers[cur_opcode.str_register.addr_reg_index]); + break; + case StoreRegisterOffsetType_MemImm: + dst_address = GetCheatProcessAddress(metadata, cur_opcode.str_register.mem_type, cur_opcode.str_register.rel_address); + break; + case StoreRegisterOffsetType_MemImmReg: + dst_address = GetCheatProcessAddress(metadata, cur_opcode.str_register.mem_type, m_registers[cur_opcode.str_register.addr_reg_index] + cur_opcode.str_register.rel_address); + break; + } + + /* Write value to memory. Write only on valid bitwidth. */ + switch (cur_opcode.str_register.bit_width) { + case 1: + case 2: + case 4: + case 8: + dmnt::cheat::impl::WriteCheatProcessMemoryUnsafe(dst_address, std::addressof(dst_value), cur_opcode.str_register.bit_width); + break; + } + + /* Increment register if relevant. */ + if (cur_opcode.str_register.increment_reg) { + m_registers[cur_opcode.str_register.addr_reg_index] += cur_opcode.str_register.bit_width; + } + } + break; + case CheatVmOpcodeType_BeginRegisterConditionalBlock: + { + /* Get value from register. */ + u64 src_value = 0; + switch (cur_opcode.begin_reg_cond.bit_width) { + case 1: + src_value = static_cast<u8>(m_registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFul); + break; + case 2: + src_value = static_cast<u16>(m_registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFFFul); + break; + case 4: + src_value = static_cast<u32>(m_registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFFFFFFFul); + break; + case 8: + src_value = static_cast<u64>(m_registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFFFFFFFFFFFFFFFul); + break; + } + + /* Read value from memory. */ + u64 cond_value = 0; + if (cur_opcode.begin_reg_cond.comp_type == CompareRegisterValueType_StaticValue) { + cond_value = GetVmInt(cur_opcode.begin_reg_cond.value, cur_opcode.begin_reg_cond.bit_width); + } else if (cur_opcode.begin_reg_cond.comp_type == CompareRegisterValueType_OtherRegister) { + switch (cur_opcode.begin_reg_cond.bit_width) { + case 1: + cond_value = static_cast<u8>(m_registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFul); + break; + case 2: + cond_value = static_cast<u16>(m_registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFFFul); + break; + case 4: + cond_value = static_cast<u32>(m_registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFFFFFFFul); + break; + case 8: + cond_value = static_cast<u64>(m_registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFFFFFFFFFFFFFFFul); + break; + } + } else { + u64 cond_address = 0; + switch (cur_opcode.begin_reg_cond.comp_type) { + case CompareRegisterValueType_MemoryRelAddr: + cond_address = GetCheatProcessAddress(metadata, cur_opcode.begin_reg_cond.mem_type, cur_opcode.begin_reg_cond.rel_address); + break; + case CompareRegisterValueType_MemoryOfsReg: + cond_address = GetCheatProcessAddress(metadata, cur_opcode.begin_reg_cond.mem_type, m_registers[cur_opcode.begin_reg_cond.ofs_reg_index]); + break; + case CompareRegisterValueType_RegisterRelAddr: + cond_address = m_registers[cur_opcode.begin_reg_cond.addr_reg_index] + cur_opcode.begin_reg_cond.rel_address; + break; + case CompareRegisterValueType_RegisterOfsReg: + cond_address = m_registers[cur_opcode.begin_reg_cond.addr_reg_index] + m_registers[cur_opcode.begin_reg_cond.ofs_reg_index]; + break; + default: + break; + } + switch (cur_opcode.begin_reg_cond.bit_width) { + case 1: + case 2: + case 4: + case 8: + dmnt::cheat::impl::ReadCheatProcessMemoryUnsafe(cond_address, std::addressof(cond_value), cur_opcode.begin_reg_cond.bit_width); + break; + } + } + + /* Check against condition. */ + bool cond_met = false; + switch (cur_opcode.begin_reg_cond.cond_type) { + case ConditionalComparisonType_GT: + cond_met = src_value > cond_value; + break; + case ConditionalComparisonType_GE: + cond_met = src_value >= cond_value; + break; + case ConditionalComparisonType_LT: + cond_met = src_value < cond_value; + break; + case ConditionalComparisonType_LE: + cond_met = src_value <= cond_value; + break; + case ConditionalComparisonType_EQ: + cond_met = src_value == cond_value; + break; + case ConditionalComparisonType_NE: + cond_met = src_value != cond_value; + break; + } + + /* Skip conditional block if condition not met. */ + if (!cond_met) { + this->SkipConditionalBlock(true); + } + } + break; + case CheatVmOpcodeType_SaveRestoreRegister: + /* Save or restore a register. */ + switch (cur_opcode.save_restore_reg.op_type) { + case SaveRestoreRegisterOpType_ClearRegs: + m_registers[cur_opcode.save_restore_reg.dst_index] = 0ul; + break; + case SaveRestoreRegisterOpType_ClearSaved: + m_saved_values[cur_opcode.save_restore_reg.dst_index] = 0ul; + break; + case SaveRestoreRegisterOpType_Save: + m_saved_values[cur_opcode.save_restore_reg.dst_index] = m_registers[cur_opcode.save_restore_reg.src_index]; + break; + case SaveRestoreRegisterOpType_Restore: + default: + m_registers[cur_opcode.save_restore_reg.dst_index] = m_saved_values[cur_opcode.save_restore_reg.src_index]; + break; + } + break; + case CheatVmOpcodeType_SaveRestoreRegisterMask: + /* Save or restore register mask. */ + u64 *src; + u64 *dst; + switch (cur_opcode.save_restore_regmask.op_type) { + case SaveRestoreRegisterOpType_ClearSaved: + case SaveRestoreRegisterOpType_Save: + src = m_registers; + dst = m_saved_values; + break; + case SaveRestoreRegisterOpType_ClearRegs: + case SaveRestoreRegisterOpType_Restore: + default: + src = m_saved_values; + dst = m_registers; + break; + } + for (size_t i = 0; i < NumRegisters; i++) { + if (cur_opcode.save_restore_regmask.should_operate[i]) { + switch (cur_opcode.save_restore_regmask.op_type) { + case SaveRestoreRegisterOpType_ClearSaved: + case SaveRestoreRegisterOpType_ClearRegs: + dst[i] = 0ul; + break; + case SaveRestoreRegisterOpType_Save: + case SaveRestoreRegisterOpType_Restore: + default: + dst[i] = src[i]; + break; + } + } + } + break; + case CheatVmOpcodeType_ReadWriteStaticRegister: + if (cur_opcode.rw_static_reg.static_idx < NumReadableStaticRegisters) { + /* Load a register with a static register. */ + m_registers[cur_opcode.rw_static_reg.idx] = m_static_registers[cur_opcode.rw_static_reg.static_idx]; + } else { + /* Store a register to a static register. */ + m_static_registers[cur_opcode.rw_static_reg.static_idx] = m_registers[cur_opcode.rw_static_reg.idx]; + } + break; + case CheatVmOpcodeType_PauseProcess: + dmnt::cheat::impl::PauseCheatProcessUnsafe(); + break; + case CheatVmOpcodeType_ResumeProcess: + dmnt::cheat::impl::ResumeCheatProcessUnsafe(); + break; + case CheatVmOpcodeType_DebugLog: + { + /* Read value from memory. */ + u64 log_value = 0; + if (cur_opcode.debug_log.val_type == DebugLogValueType_RegisterValue) { + switch (cur_opcode.debug_log.bit_width) { + case 1: + log_value = static_cast<u8>(m_registers[cur_opcode.debug_log.val_reg_index] & 0xFFul); + break; + case 2: + log_value = static_cast<u16>(m_registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFul); + break; + case 4: + log_value = static_cast<u32>(m_registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFFFFFul); + break; + case 8: + log_value = static_cast<u64>(m_registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFFFFFFFFFFFFFul); + break; + } + } else { + u64 val_address = 0; + switch (cur_opcode.debug_log.val_type) { + case DebugLogValueType_MemoryRelAddr: + val_address = GetCheatProcessAddress(metadata, cur_opcode.debug_log.mem_type, cur_opcode.debug_log.rel_address); + break; + case DebugLogValueType_MemoryOfsReg: + val_address = GetCheatProcessAddress(metadata, cur_opcode.debug_log.mem_type, m_registers[cur_opcode.debug_log.ofs_reg_index]); + break; + case DebugLogValueType_RegisterRelAddr: + val_address = m_registers[cur_opcode.debug_log.addr_reg_index] + cur_opcode.debug_log.rel_address; + break; + case DebugLogValueType_RegisterOfsReg: + val_address = m_registers[cur_opcode.debug_log.addr_reg_index] + m_registers[cur_opcode.debug_log.ofs_reg_index]; + break; + default: + break; + } + switch (cur_opcode.debug_log.bit_width) { + case 1: + case 2: + case 4: + case 8: + dmnt::cheat::impl::ReadCheatProcessMemoryUnsafe(val_address, std::addressof(log_value), cur_opcode.debug_log.bit_width); + break; + } + } + + /* Log value. */ + this->DebugLog(cur_opcode.debug_log.log_id, log_value); + } + break; + default: + /* By default, we do a no-op. */ + break; + } + } + s_keyold = kHeld; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.hpp new file mode 100644 index 00000000..2dd958d5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.hpp @@ -0,0 +1,350 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::dmnt::cheat::impl { + + enum CheatVmOpcodeType : u32 { + CheatVmOpcodeType_StoreStatic = 0, + CheatVmOpcodeType_BeginConditionalBlock = 1, + CheatVmOpcodeType_EndConditionalBlock = 2, + CheatVmOpcodeType_ControlLoop = 3, + CheatVmOpcodeType_LoadRegisterStatic = 4, + CheatVmOpcodeType_LoadRegisterMemory = 5, + CheatVmOpcodeType_StoreStaticToAddress = 6, + CheatVmOpcodeType_PerformArithmeticStatic = 7, + CheatVmOpcodeType_BeginKeypressConditionalBlock = 8, + + /* These are not implemented by Gateway's VM. */ + CheatVmOpcodeType_PerformArithmeticRegister = 9, + CheatVmOpcodeType_StoreRegisterToAddress = 10, + CheatVmOpcodeType_Reserved11 = 11, + + /* This is a meta entry, and not a real opcode. */ + /* This is to facilitate multi-nybble instruction decoding. */ + CheatVmOpcodeType_ExtendedWidth = 12, + + /* Extended width opcodes. */ + CheatVmOpcodeType_BeginRegisterConditionalBlock = 0xC0, + CheatVmOpcodeType_SaveRestoreRegister = 0xC1, + CheatVmOpcodeType_SaveRestoreRegisterMask = 0xC2, + CheatVmOpcodeType_ReadWriteStaticRegister = 0xC3, + CheatVmOpcodeType_BeginExtendedKeypressConditionalBlock = 0xC4, + + /* This is a meta entry, and not a real opcode. */ + /* This is to facilitate multi-nybble instruction decoding. */ + CheatVmOpcodeType_DoubleExtendedWidth = 0xF0, + + /* Double-extended width opcodes. */ + CheatVmOpcodeType_PauseProcess = 0xFF0, + CheatVmOpcodeType_ResumeProcess = 0xFF1, + CheatVmOpcodeType_DebugLog = 0xFFF, + }; + + enum MemoryAccessType : u32 { + MemoryAccessType_MainNso = 0, + MemoryAccessType_Heap = 1, + MemoryAccessType_Alias = 2, + MemoryAccessType_Aslr = 3, + MemoryAccessType_NonRelative = 4, + }; + + enum ConditionalComparisonType : u32 { + ConditionalComparisonType_GT = 1, + ConditionalComparisonType_GE = 2, + ConditionalComparisonType_LT = 3, + ConditionalComparisonType_LE = 4, + ConditionalComparisonType_EQ = 5, + ConditionalComparisonType_NE = 6, + }; + + enum RegisterArithmeticType : u32 { + RegisterArithmeticType_Addition = 0, + RegisterArithmeticType_Subtraction = 1, + RegisterArithmeticType_Multiplication = 2, + RegisterArithmeticType_LeftShift = 3, + RegisterArithmeticType_RightShift = 4, + + /* These are not supported by Gateway's VM. */ + RegisterArithmeticType_LogicalAnd = 5, + RegisterArithmeticType_LogicalOr = 6, + RegisterArithmeticType_LogicalNot = 7, + RegisterArithmeticType_LogicalXor = 8, + + RegisterArithmeticType_None = 9, + RegisterArithmeticType_FloatAddition = 10, + RegisterArithmeticType_FloatSubtraction = 11, + RegisterArithmeticType_FloatMultiplication = 12, + RegisterArithmeticType_FloatDivision = 13, + }; + + enum StoreRegisterOffsetType : u32 { + StoreRegisterOffsetType_None = 0, + StoreRegisterOffsetType_Reg = 1, + StoreRegisterOffsetType_Imm = 2, + StoreRegisterOffsetType_MemReg = 3, + StoreRegisterOffsetType_MemImm = 4, + StoreRegisterOffsetType_MemImmReg = 5, + }; + + enum CompareRegisterValueType : u32 { + CompareRegisterValueType_MemoryRelAddr = 0, + CompareRegisterValueType_MemoryOfsReg = 1, + CompareRegisterValueType_RegisterRelAddr = 2, + CompareRegisterValueType_RegisterOfsReg = 3, + CompareRegisterValueType_StaticValue = 4, + CompareRegisterValueType_OtherRegister = 5, + }; + + enum SaveRestoreRegisterOpType : u32 { + SaveRestoreRegisterOpType_Restore = 0, + SaveRestoreRegisterOpType_Save = 1, + SaveRestoreRegisterOpType_ClearSaved = 2, + SaveRestoreRegisterOpType_ClearRegs = 3, + }; + + enum DebugLogValueType : u32 { + DebugLogValueType_MemoryRelAddr = 0, + DebugLogValueType_MemoryOfsReg = 1, + DebugLogValueType_RegisterRelAddr = 2, + DebugLogValueType_RegisterOfsReg = 3, + DebugLogValueType_RegisterValue = 4, + }; + + union VmInt { + u8 bit8; + u16 bit16; + u32 bit32; + u64 bit64; + }; + + struct StoreStaticOpcode { + u32 bit_width; + MemoryAccessType mem_type; + u32 offset_register; + u64 rel_address; + VmInt value; + }; + + struct BeginConditionalOpcode { + u32 bit_width; + MemoryAccessType mem_type; + ConditionalComparisonType cond_type; + bool include_ofs_reg; + u32 ofs_reg_index; + u64 rel_address; + VmInt value; + }; + + struct EndConditionalOpcode { + bool is_else; + }; + + struct ControlLoopOpcode { + bool start_loop; + u32 reg_index; + u32 num_iters; + }; + + struct LoadRegisterStaticOpcode { + u32 reg_index; + u64 value; + }; + + struct LoadRegisterMemoryOpcode { + u32 bit_width; + MemoryAccessType mem_type; + u32 reg_index; + u8 load_from_reg; + u8 offset_register; + u64 rel_address; + }; + + struct StoreStaticToAddressOpcode { + u32 bit_width; + u32 reg_index; + bool increment_reg; + bool add_offset_reg; + u32 offset_reg_index; + u64 value; + }; + + struct PerformArithmeticStaticOpcode { + u32 bit_width; + u32 reg_index; + RegisterArithmeticType math_type; + u32 value; + }; + + struct BeginKeypressConditionalOpcode { + u32 key_mask; + }; + + struct BeginExtendedKeypressConditionalOpcode { + u64 key_mask; + bool auto_repeat; + }; + + struct PerformArithmeticRegisterOpcode { + u32 bit_width; + RegisterArithmeticType math_type; + u32 dst_reg_index; + u32 src_reg_1_index; + u32 src_reg_2_index; + bool has_immediate; + VmInt value; + }; + + struct StoreRegisterToAddressOpcode { + u32 bit_width; + u32 str_reg_index; + u32 addr_reg_index; + bool increment_reg; + StoreRegisterOffsetType ofs_type; + MemoryAccessType mem_type; + u32 ofs_reg_index; + u64 rel_address; + }; + + struct BeginRegisterConditionalOpcode { + u32 bit_width; + ConditionalComparisonType cond_type; + u32 val_reg_index; + CompareRegisterValueType comp_type; + MemoryAccessType mem_type; + u32 addr_reg_index; + u32 other_reg_index; + u32 ofs_reg_index; + u64 rel_address; + VmInt value; + }; + + struct SaveRestoreRegisterOpcode { + u32 dst_index; + u32 src_index; + SaveRestoreRegisterOpType op_type; + }; + + struct SaveRestoreRegisterMaskOpcode { + SaveRestoreRegisterOpType op_type; + bool should_operate[0x10]; + }; + + struct ReadWriteStaticRegisterOpcode { + u32 static_idx; + u32 idx; + }; + + struct DebugLogOpcode { + u32 bit_width; + u32 log_id; + DebugLogValueType val_type; + MemoryAccessType mem_type; + u32 addr_reg_index; + u32 val_reg_index; + u32 ofs_reg_index; + u64 rel_address; + }; + + struct CheatVmOpcode { + CheatVmOpcodeType opcode; + bool begin_conditional_block; + union { + StoreStaticOpcode store_static; + BeginConditionalOpcode begin_cond; + EndConditionalOpcode end_cond; + ControlLoopOpcode ctrl_loop; + LoadRegisterStaticOpcode ldr_static; + LoadRegisterMemoryOpcode ldr_memory; + StoreStaticToAddressOpcode str_static; + PerformArithmeticStaticOpcode perform_math_static; + BeginKeypressConditionalOpcode begin_keypress_cond; + BeginExtendedKeypressConditionalOpcode begin_ext_keypress_cond; + PerformArithmeticRegisterOpcode perform_math_reg; + StoreRegisterToAddressOpcode str_register; + BeginRegisterConditionalOpcode begin_reg_cond; + SaveRestoreRegisterOpcode save_restore_reg; + SaveRestoreRegisterMaskOpcode save_restore_regmask; + ReadWriteStaticRegisterOpcode rw_static_reg; + DebugLogOpcode debug_log; + }; + }; + + class CheatVirtualMachine { + public: + constexpr static size_t MaximumProgramOpcodeCount = 0x400; + constexpr static size_t NumRegisters = 0x10; + constexpr static size_t NumReadableStaticRegisters = 0x80; + constexpr static size_t NumWritableStaticRegisters = 0x80; + constexpr static size_t NumStaticRegisters = NumReadableStaticRegisters + NumWritableStaticRegisters; + private: + size_t m_num_opcodes = 0; + size_t m_instruction_ptr = 0; + size_t m_condition_depth = 0; + bool m_decode_success = false; + u32 m_program[MaximumProgramOpcodeCount] = {0}; + u64 m_registers[NumRegisters] = {0}; + u64 m_saved_values[NumRegisters] = {0}; + u64 m_static_registers[NumStaticRegisters] = {0}; + size_t m_loop_tops[NumRegisters] = {0}; + private: + bool DecodeNextOpcode(CheatVmOpcode *out); + void SkipConditionalBlock(bool is_if); + void ResetState(); + + /* For implementing the DebugLog opcode. */ + void DebugLog(u32 log_id, u64 value); + + /* For debugging. These will be IFDEF'd out normally. */ + void OpenDebugLogFile(); + void CloseDebugLogFile(); + void LogToDebugFile(const char *format, ...); + void LogOpcode(const CheatVmOpcode *opcode); + + static u64 GetVmInt(VmInt value, u32 bit_width); + static u64 GetCheatProcessAddress(const CheatProcessMetadata* metadata, MemoryAccessType mem_type, u64 rel_address); + public: + constexpr CheatVirtualMachine() = default; + + size_t GetProgramSize() { + return m_num_opcodes; + } + + bool LoadProgram(const CheatEntry *cheats, size_t num_cheats); + void Execute(const CheatProcessMetadata *metadata); + + u64 GetStaticRegister(size_t which) const { + return m_static_registers[which]; + } + + void SetStaticRegister(size_t which, u64 value) { + m_static_registers[which] = value; + } + + void ResetStaticRegisters() { + std::memset(m_static_registers, 0, sizeof(m_static_registers)); + } + #ifdef DMNT_CHEAT_VM_DEBUG_LOG + private: + fs::FileHandle m_debug_log_file = {}; + s64 m_debug_log_file_offset = 0; + bool m_has_debug_log_file = false; + char m_debug_log_format_buf[0x100] = {0}; + #endif + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/dmnt_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/dmnt_main.cpp new file mode 100644 index 00000000..baca39d3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/source/dmnt_main.cpp @@ -0,0 +1,165 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "cheat/dmnt_cheat_service.hpp" +#include "cheat/impl/dmnt_cheat_api.hpp" + +namespace ams { + + namespace dmnt { + + namespace { + + constinit u8 g_fs_heap_memory[4_KB]; + lmem::HeapHandle g_fs_heap_handle; + + void *AllocateForFs(size_t size) { + return lmem::AllocateFromExpHeap(g_fs_heap_handle, size); + } + + void DeallocateForFs(void *p, size_t size) { + AMS_UNUSED(size); + return lmem::FreeToExpHeap(g_fs_heap_handle, p); + } + + void InitializeFsHeap() { + g_fs_heap_handle = lmem::CreateExpHeap(g_fs_heap_memory, sizeof(g_fs_heap_memory), lmem::CreateOption_None); + } + + } + + namespace { + + using ServerOptions = sf::hipc::DefaultServerManagerOptions; + + constexpr sm::ServiceName DebugMonitorServiceName = sm::ServiceName::Encode("dmnt:-"); + constexpr size_t DebugMonitorMaxSessions = 4; + + constexpr sm::ServiceName CheatServiceName = sm::ServiceName::Encode("dmnt:cht"); + constexpr size_t CheatMaxSessions = 2; + + /* dmnt:-, dmnt:cht. */ + constexpr size_t NumServers = 2; + constexpr size_t NumSessions = DebugMonitorMaxSessions + CheatMaxSessions; + + sf::hipc::ServerManager<NumServers, ServerOptions, NumSessions> g_server_manager; + + constinit sf::UnmanagedServiceObject<dmnt::cheat::impl::ICheatInterface, dmnt::cheat::CheatService> g_cheat_service; + + void LoopServerThread(void *) { + g_server_manager.LoopProcess(); + } + + /* NOTE: Nintendo loops four threads processing on the manager -- we'll loop an extra fifth for our cheat service. */ + constexpr size_t TotalThreads = DebugMonitorMaxSessions + 1; + static_assert(TotalThreads >= 1, "TotalThreads"); + constexpr size_t NumExtraThreads = TotalThreads - 1; + constexpr size_t ThreadStackSize = 0x4000; + alignas(os::MemoryPageSize) u8 g_extra_thread_stacks[NumExtraThreads][ThreadStackSize]; + + os::ThreadType g_extra_threads[NumExtraThreads]; + + void InitializeIpcServer() { + /* Create services. */ + R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_cheat_service.GetShared(), CheatServiceName, CheatMaxSessions)); + } + + void LoopProcessIpcServer() { + /* Initialize threads. */ + if constexpr (NumExtraThreads > 0) { + static_assert(AMS_GET_SYSTEM_THREAD_PRIORITY(dmnt, Main) == AMS_GET_SYSTEM_THREAD_PRIORITY(dmnt, Ipc)); + for (size_t i = 0; i < NumExtraThreads; i++) { + R_ABORT_UNLESS(os::CreateThread(std::addressof(g_extra_threads[i]), LoopServerThread, nullptr, g_extra_thread_stacks[i], ThreadStackSize, AMS_GET_SYSTEM_THREAD_PRIORITY(dmnt, Ipc))); + os::SetThreadNamePointer(std::addressof(g_extra_threads[i]), AMS_GET_SYSTEM_THREAD_NAME(dmnt, Ipc)); + } + } + + /* Start extra threads. */ + if constexpr (NumExtraThreads > 0) { + for (size_t i = 0; i < NumExtraThreads; i++) { + os::StartThread(std::addressof(g_extra_threads[i])); + } + } + + /* Loop this thread. */ + LoopServerThread(nullptr); + + /* Wait for extra threads to finish. */ + if constexpr (NumExtraThreads > 0) { + for (size_t i = 0; i < NumExtraThreads; i++) { + os::WaitThread(std::addressof(g_extra_threads[i])); + } + } + } + + } + + } + + namespace init { + + void InitializeSystemModule() { + /* Initialize heap. */ + dmnt::InitializeFsHeap(); + + /* Initialize our connection to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + + /* Initialize fs. */ + fs::InitializeForSystem(); + fs::SetAllocator(dmnt::AllocateForFs, dmnt::DeallocateForFs); + fs::SetEnabledAutoAbort(false); + + /* Initialize other services we need. */ + R_ABORT_UNLESS(pmdmntInitialize()); + R_ABORT_UNLESS(pminfoInitialize()); + R_ABORT_UNLESS(ldrDmntInitialize()); + R_ABORT_UNLESS(roDmntInitialize()); + R_ABORT_UNLESS(nsdevInitialize()); + lr::Initialize(); + R_ABORT_UNLESS(setInitialize()); + R_ABORT_UNLESS(setsysInitialize()); + R_ABORT_UNLESS(hidInitialize()); + + /* Mount the SD card. */ + R_ABORT_UNLESS(fs::MountSdCard("sdmc")); + + /* Verify that we can sanely execute. */ + ams::CheckApiVersion(); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void Main() { + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(dmnt, Main)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(dmnt, Main)); + + /* Initialize the cheat manager. */ + dmnt::cheat::impl::InitializeCheatManager(); + + /* Initialize ipc server. */ + dmnt::InitializeIpcServer(); + + /* Loop processing ipc server. */ + dmnt::LoopProcessIpcServer(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/system_module.mk new file mode 100644 index 00000000..f002f764 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/dmnt/system_module.mk @@ -0,0 +1,132 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := nsp + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/eclct.stub/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/eclct.stub/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/eclct.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)/system_module.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)/system_module.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/stratosphere/eclct.stub/eclct.stub.json b/Source/Atmosphere-MTC-Unlock/stratosphere/eclct.stub/eclct.stub.json new file mode 100644 index 00000000..24404ad3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/eclct.stub/eclct.stub.json @@ -0,0 +1,100 @@ +{ + "name": "eclct.stub", + "title_id": "0x0100000000000032", + "title_id_range_min": "0x0100000000000032", + "title_id_range_max": "0x0100000000000032", + "main_thread_stack_size": "0x00004000", + "main_thread_priority": 49, + "default_cpu_id": 3, + "process_category": 0, + "is_retail": true, + "pool_partition": 2, + "is_64_bit": true, + "address_space_type": 3, + "disable_device_address_space_merge": true, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_host": [], + "service_access": ["fatal:u"], + "kernel_capabilities": [ + { + "type": "kernel_flags", + "value": { + "highest_thread_priority": 63, + "lowest_thread_priority": 24, + "lowest_cpu_id": 3, + "highest_cpu_id": 3 + } + }, + { + "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", + "svcSynchronizePreemptionState": "0x36", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcCreateInterruptEvent": "0x53", + "svcReadWriteRegister": "0x4E", + "svcQueryIoMapping": "0x55", + "svcCreateDeviceAddressSpace": "0x56", + "svcAttachDeviceAddressSpace": "0x57", + "svcDetachDeviceAddressSpace": "0x58", + "svcMapDeviceAddressSpaceAligned": "0x5a", + "svcUnmapDeviceAddressSpace": "0x5c", + "svcGetSystemInfo": "0x6f", + "svcCallSecureMonitor": "0x7F" + } + }, + { + "type": "min_kernel_version", + "value": "0x0030" + } + ] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/eclct.stub/source/eclct_stub.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/eclct.stub/source/eclct_stub.cpp new file mode 100644 index 00000000..36bb07d7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/eclct.stub/source/eclct_stub.cpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams { + + namespace init { + + void InitializeSystemModule() { /* ... */ } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void Main() { + /* ... */ + } + +} + diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/eclct.stub/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/eclct.stub/system_module.mk new file mode 100644 index 00000000..d6de42ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/eclct.stub/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := nsp + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/erpt/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/erpt/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/erpt/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)/system_module.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)/system_module.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/stratosphere/erpt/erpt.json b/Source/Atmosphere-MTC-Unlock/stratosphere/erpt/erpt.json new file mode 100644 index 00000000..f848978e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/erpt/erpt.json @@ -0,0 +1,92 @@ +{ + "name": "erpt", + "title_id": "0x010000000000002b", + "title_id_range_min": "0x010000000000002b", + "title_id_range_max": "0x010000000000002b", + "main_thread_stack_size": "0x00002000", + "main_thread_priority": 49, + "default_cpu_id": 3, + "process_category": 0, + "is_retail": true, + "pool_partition": 2, + "is_64_bit": true, + "address_space_type": 3, + "disable_device_address_space_merge": true, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_access": ["lm", "fsp-srv", "time:u", "set", "set:sys", "srepo:u", "psc:m", "csrng", "ectx:r"], + "service_host": ["erpt:r", "erpt:c", "sprof:sp", "sprof:bg"], + "kernel_capabilities": [{ + "type": "kernel_flags", + "value": { + "highest_thread_priority": 63, + "lowest_thread_priority": 24, + "lowest_cpu_id": 3, + "highest_cpu_id": 3 + } + }, { + "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", + "svcGetResourceLimitLimitValue": "0x30", + "svcSignalToAddress": "0x35", + "svcSynchronizePreemptionState": "0x36", + "svcGetResourceLimitPeakValue": "0x37", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcCallSecureMonitor": "0x7f" + } + }, { + "type": "min_kernel_version", + "value": "0x0030" + }, { + "type": "handle_table_size", + "value": 256 + }] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/erpt/source/erpt_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/erpt/source/erpt_main.cpp new file mode 100644 index 00000000..d95a0ae6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/erpt/source/erpt_main.cpp @@ -0,0 +1,151 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams { + + namespace erpt { + + namespace { + + constexpr size_t MemoryHeapSize = 196_KB; + alignas(os::MemoryPageSize) u8 g_memory_heap[MemoryHeapSize]; + + } + + int MakeProductModelString(char *dst, size_t dst_size, settings::system::ProductModel model) { + switch (model) { + case settings::system::ProductModel_Invalid: return util::Strlcpy(dst, "Invalid", static_cast<int>(dst_size)); + case settings::system::ProductModel_Nx: return util::Strlcpy(dst, "NX", static_cast<int>(dst_size)); + default: return util::SNPrintf(dst, dst_size, "%d", static_cast<int>(model)); + } + } + + const char *GetRegionString(settings::system::RegionCode code) { + switch (code) { + case settings::system::RegionCode_Japan: return "Japan"; + case settings::system::RegionCode_Usa: return "Usa"; + case settings::system::RegionCode_Europe: return "Europe"; + case settings::system::RegionCode_Australia: return "Australia"; + case settings::system::RegionCode_HongKongTaiwanKorea: return "HongKongTaiwanKorea"; + case settings::system::RegionCode_China: return "China"; + default: return "RegionUnknown"; + } + } + + } + + namespace init { + + void InitializeSystemModule() { + /* Initialize our connection to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + + /* Initialize services we need (which won't be initialized later). */ + R_ABORT_UNLESS(setInitialize()); + R_ABORT_UNLESS(setsysInitialize()); + R_ABORT_UNLESS(pscmInitialize()); + R_ABORT_UNLESS(time::Initialize()); + if (hos::GetVersion() >= hos::Version_11_0_0) { + R_ABORT_UNLESS(ectxrInitialize()); + } + + /* Verify that we can sanely execute. */ + ams::CheckApiVersion(); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void Main() { + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(erpt, Main)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(erpt, Main)); + + /* Decide whether or not to clean up reports periodically. */ + { + u8 disable_report_cleanup = 0; + if (settings::fwdbg::GetSettingsItemValue(std::addressof(disable_report_cleanup), sizeof(disable_report_cleanup), "erpt", "disable_automatic_report_cleanup") == sizeof(disable_report_cleanup)) { + erpt::srv::SetEnabledAutomaticReportCleanup(disable_report_cleanup == 0); + } else { + erpt::srv::SetEnabledAutomaticReportCleanup(true); + } + } + + /* Set the memory heap for erpt::srv namespace, perform other service init/etc. */ + R_ABORT_UNLESS(erpt::srv::Initialize(erpt::g_memory_heap, erpt::MemoryHeapSize)); + + /* Atmosphere always wants to redirect new reports to the SD card, to prevent them from being logged. */ + erpt::srv::SetRedirectNewReportsToSdCard(true); + + /* Configure the OS version. */ + { + settings::system::FirmwareVersion firmware_version = {}; + settings::system::SerialNumber serial_number = {}; + settings::system::GetFirmwareVersion(std::addressof(firmware_version)); + settings::system::GetSerialNumber(std::addressof(serial_number)); + + char os_private[0x60]; + const auto os_priv_len = util::SNPrintf(os_private, sizeof(os_private), "%s (%.8s)", firmware_version.display_name, firmware_version.revision); + AMS_ASSERT(static_cast<size_t>(os_priv_len) < sizeof(os_private)); + AMS_UNUSED(os_priv_len); + + R_ABORT_UNLESS(erpt::srv::SetSerialNumberAndOsVersion(serial_number.str, + strnlen(serial_number.str, sizeof(serial_number.str) - 1) + 1, + firmware_version.display_version, + strnlen(firmware_version.display_version, sizeof(firmware_version.display_version) - 1) + 1, + os_private, + strnlen(os_private, sizeof(os_private) - 1) + 1)); + } + + /* Configure the product model. */ + { + char product_model[0x10]; + const auto pm_len = erpt::MakeProductModelString(product_model, sizeof(product_model), settings::system::GetProductModel()); + AMS_ASSERT(static_cast<size_t>(pm_len) < sizeof(product_model)); + AMS_UNUSED(pm_len); + + R_ABORT_UNLESS(erpt::srv::SetProductModel(product_model, static_cast<u32>(std::strlen(product_model)))); + } + + /* Configure the region. */ + { + settings::system::RegionCode code; + settings::system::GetRegionCode(std::addressof(code)); + const char *region_str = erpt::GetRegionString(code); + R_ABORT_UNLESS(erpt::srv::SetRegionSetting(region_str, static_cast<u32>(std::strlen(region_str)))); + } + + /* Start the erpt server. */ + R_ABORT_UNLESS(erpt::srv::InitializeAndStartService()); + + /* Launch sprofile on 13.0.0+ */ + if (hos::GetVersion() >= hos::Version_13_0_0) { + /* Initialize the sprofile server. */ + sprofile::srv::Initialize(); + + /* Start the sprofile ipc server. */ + sprofile::srv::StartIpcServer(); + } + + /* Wait forever. */ + erpt::srv::Wait(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/erpt/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/erpt/system_module.mk new file mode 100644 index 00000000..d6de42ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/erpt/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := nsp + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/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)/system_module.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)/system_module.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/stratosphere/fatal/fatal.json b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/fatal.json new file mode 100644 index 00000000..aef307a6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/fatal.json @@ -0,0 +1,111 @@ +{ + "name": "fatal", + "title_id": "0x0100000000000034", + "title_id_range_min": "0x0100000000000034", + "title_id_range_max": "0x0100000000000034", + "main_thread_stack_size": "0x00008000", + "main_thread_priority": 15, + "default_cpu_id": 3, + "process_category": 0, + "is_retail": true, + "pool_partition": 2, + "is_64_bit": true, + "address_space_type": 3, + "disable_device_address_space_merge": true, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_access": ["bpc", "bpc:c", "erpt:c", "fsp-srv", "gpio", "i2c", "lbl", "lm", "nvdrv:s", "clkrst", "pcv", "pl:u", "pm:info", "psm", "set", "set:sys", "spsm", "spl:", "time:*", "vi:m", "vi:s"], + "service_host": ["fatal:p", "fatal:u", "time:s"], + "kernel_capabilities": [{ + "type": "kernel_flags", + "value": { + "highest_thread_priority": 63, + "lowest_thread_priority": 12, + "lowest_cpu_id": 0, + "highest_cpu_id": 3 + } + }, { + "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", + "svcSynchronizePreemptionState": "0x36", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcMapPhysicalMemoryUnsafe": "0x48", + "svcUnmapPhysicalMemoryUnsafe": "0x49", + "svcSetUnsafeLimit": "0x4A", + "svcReadWriteRegister": "0x4E", + "svcDebugActiveProcess": "0x60", + "svcGetDebugEvent": "0x63", + "svcGetThreadList": "0x66", + "svcGetDebugThreadContext": "0x67", + "svcQueryDebugProcessMemory": "0x69", + "svcReadDebugProcessMemory": "0x6a", + "svcGetDebugThreadParam": "0x6d", + "svcCallSecureMonitor": "0x7f", + "svcMapInsecureMemory": "0x90", + "svcUnmapInsecureMemory": "0x91" + } + }, { + "type": "min_kernel_version", + "value": "0x0030" + }, { + "type": "handle_table_size", + "value": 128 + }, + { + "type": "debug_flags", + "value": { + "allow_debug": false, + "force_debug_prod": false, + "force_debug": true + } + }] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_ams_logo.inc b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_ams_logo.inc new file mode 100644 index 00000000..f2c8c8bb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_ams_logo.inc @@ -0,0 +1,1303 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +constexpr size_t AtmosphereLogoWidth = 0xA0; +constexpr size_t AtmosphereLogoHeight = 0x80; + +static constexpr u16 AtmosphereLogoData[] = { + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39EA, 0x6B4F, 0x9494, 0xB597, 0xC619, 0xD69B, + 0xDEDB, 0xCE7A, 0xBDB8, 0x9CD4, 0x6B4F, 0x39EA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x6B6F, 0xBDD8, 0xFFBF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF79E, 0xB577, 0x5AED, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x630E, 0xD67A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC639, 0x5ACD, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x420A, 0xAD76, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x9CD4, 0x39EA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x528C, 0xDEDC, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xCE5A, 0x422B, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x5ACD, 0xEF5D, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xDEFC, 0x4A6C, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x528C, 0xEF7E, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE6FC, 0x422B, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x420A, 0xDEDB, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xCE5A, 0x39EA, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0xBDD8, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xAD36, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x8C73, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0x7BF1, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x6B4F, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFBF, 0x5ACD, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x4A6C, 0xEF7E, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xE71C, 0x422B, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39EA, 0xD69B, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xC619, 0x39CA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0xAD56, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0x94B4, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x8412, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7390, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x5AED, 0xFFBF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF79E, 0x528C, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x422B, 0xE71D, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xDEBB, 0x39EA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39CA, 0xC619, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xB577, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x9CD4, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8432, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x7390, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFDF, 0x630E, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x52AC, 0xF79E, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xEF5D, 0x4A4B, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x420A, 0xDEDC, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xCE5A, 0x39EA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0xB597, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x9CF5, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x8C32, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7BD1, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x630E, 0xFFDF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7BE, 0x52AD, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x4A4B, 0xEF5D, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xDEFC, 0x420A, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39EA, 0xCE7A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xBDD8, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0xA515, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8C73, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x7BD1, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x6B4F, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x5ACD, + 0xFFBF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xEF7E, + 0x4A6C, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x420A, 0xDEFC, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xD69B, 0x39EA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0xBDF8, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xAD36, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x9493, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0x7BF1, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x6B4F, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFBF, 0x5ACD, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x4A6C, 0xF77E, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xE71D, 0x422B, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39EA, 0xD6BB, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC619, 0x39CA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0xAD56, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x9CB4, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x8412, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7390, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x5AED, 0xFFBF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF79E, 0x52AC, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x422B, 0xE73D, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xDEDC, 0x420A, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39EA, 0xC639, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xB597, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x9CD4, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8C32, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x7390, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFDF, 0x630E, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x52AC, 0xF79E, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xEF5D, 0x4A4B, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x420A, 0xDEDC, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xCE7A, 0x39EA, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0xBDD8, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xA515, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x8C52, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7BD1, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x630E, 0xFFDF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFBF, 0x5ACD, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x4A4B, 0xEF5D, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xDEFC, 0x420A, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39EA, + 0xCE7A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xBDD8, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0xA515, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0x9473, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x7BD1, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0x6B4F, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x5ACD, 0xFFBF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xF77E, 0x4A6C, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x422B, 0xE71C, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xD6BB, 0x39EA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0xBDF8, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xAD56, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x9493, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8412, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x6B4F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFBF, 0x5AED, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x4A6C, 0xF77E, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE71D, 0x422B, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39EA, 0xD6BB, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC639, 0x39CA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0xAD56, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x9CD4, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x8432, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7390, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x630E, 0xFFDF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF79E, 0x52AC, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x422B, 0xE73D, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xDEDC, 0x420A, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39EA, 0xB5FA, 0xD71E, 0xD71E, 0xDF3E, 0xDF3E, 0xE75E, 0xE77E, 0xE77F, 0xEF9F, 0xEF9F, 0xF7DF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xB597, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x6BD2, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, + 0x95BB, 0x9DFB, 0xAE3C, 0xB67C, 0xC6BD, 0xD6FE, 0xDF3E, 0xEF7F, 0xFFDF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8412, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x52EE, 0x8D7B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, + 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x95DB, 0xA61C, 0xBE9D, 0xCEFD, 0xE75E, 0xF7BF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFDF, 0x5AEE, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x424B, 0x8539, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, + 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x9DDB, + 0xB65C, 0xCEFE, 0xEF9F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE73D, 0x422B, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39EA, 0x7CD7, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, + 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, + 0x8D9B, 0x8D9B, 0x8D9B, 0x95BB, 0xA61C, 0xC6BD, 0xDF5E, 0xFFDF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC639, 0x39EA, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x6BF4, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, + 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D9B, 0x8D9B, + 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x8D9B, 0x95BB, 0xAE3C, 0xD71E, 0xF7DF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xA515, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x5B30, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D7B, 0x8D7B, + 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, + 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D9B, 0x8D9B, 0x8D9B, 0xA5FC, 0xCEDD, 0xEF9F, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7390, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x4A6D, + 0x853A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5B, 0x8D5B, + 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, + 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0xA5FC, 0xC6BD, + 0xEF9F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF79E, + 0x52AC, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x420B, 0x7CD8, + 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, + 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, + 0x8D5B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, + 0x8D7B, 0xA5DC, 0xCEDD, 0xFFDF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xDEDC, 0x420A, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39EA, 0x7456, 0x8D3A, + 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, + 0x8D3A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, + 0x8D5A, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, + 0x8D7B, 0x8D7B, 0x8D7B, 0x8D7B, 0xAE3C, 0xDF3E, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xBDF8, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x6372, 0x851A, 0x851A, + 0x851A, 0x853A, 0x853A, 0x853A, 0x853A, 0x853A, 0x853A, 0x853A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, + 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, + 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, + 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D7B, 0x8D7B, 0x9DDB, 0xCEDD, 0xFFDF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0x8C73, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x52EF, 0x851A, 0x851A, 0x851A, + 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x853A, 0x853A, 0x853A, 0x853A, 0x853A, + 0x853A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, + 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, + 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D5B, 0x8D7B, 0xB65C, 0xEF9F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFDF, 0x632E, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x424C, 0x84F9, 0x851A, 0x851A, 0x851A, + 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, + 0x851A, 0x851A, 0x851A, 0x851A, 0x853A, 0x853A, 0x853A, 0x853A, 0x853A, 0x853A, 0x853A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, + 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D5A, 0x8D5A, 0x8D5A, + 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0xAE1C, 0xDF3E, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xF77E, 0x4A6C, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x3A0A, 0x7C78, 0x84FA, 0x84FA, 0x84FA, 0x84FA, + 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, + 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x853A, 0x853A, 0x853A, + 0x853A, 0x853A, 0x853A, 0x853A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, + 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0x8D5A, 0xA5DB, 0xD71E, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xCE7A, 0x39EA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x6BD4, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, + 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x851A, 0x851A, 0x851A, + 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, + 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x853A, 0x853A, 0x853A, 0x853A, 0x853A, 0x853A, 0x853A, 0x8D3A, 0x8D3A, 0x8D3A, + 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0xA5DB, + 0xE73E, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xAD56, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x5B31, 0x84DA, 0x84DA, 0x84DA, 0x84FA, 0x84FA, 0x84FA, + 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, + 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, + 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, + 0x853A, 0x853A, 0x853A, 0x853A, 0x853A, 0x853A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, 0x8D3A, + 0x8D3A, 0xA5DC, 0xE75E, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8412, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x4A8D, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, + 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, + 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, + 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, + 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x853A, 0x853A, 0x853A, 0x853A, 0x853A, 0x853A, 0x853A, + 0x8D3A, 0x8D3A, 0x8D3A, 0xB61C, 0xEF9F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7BF, 0x5ACD, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x422B, 0x7C78, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, + 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, + 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, + 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, + 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, + 0x851A, 0x851A, 0x853A, 0x853A, 0x8D3A, 0xB65C, 0xF7BF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE71C, 0x422B, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39CA, 0x6BF6, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, + 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, + 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, + 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, + 0x84FA, 0x84FA, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, + 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x955B, 0xD6DE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xBDF8, 0x39CA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x6373, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x84BA, 0x84BA, + 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, + 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, + 0x84DA, 0x84DA, 0x84DA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, + 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x851A, 0x851A, 0x851A, 0x851A, + 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0xA5BB, 0xEF7F, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, + 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, + 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, + 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, + 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0x94D4, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x52F0, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, + 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, + 0x84BA, 0x84BA, 0x84BA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, + 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, + 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, + 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x851A, 0x851A, 0x851A, 0x851A, 0x851A, 0xBE5D, 0xF7BF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, + 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, + 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, + 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, + 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xF7DF, 0x7390, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x424D, 0x7C79, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, + 0x7C9A, 0x7C9A, 0x7C9A, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, + 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, + 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, + 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, + 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x957B, 0xDF1E, 0xFFDF, 0xFFDF, 0xFFDF, + 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, + 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, + 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, + 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xFFDF, 0xEF5D, 0x4A6C, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39EA, 0x7417, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, + 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, + 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, + 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, + 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84FA, + 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0xB63C, 0xF7BF, 0xF7DF, + 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, + 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, + 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, + 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xD6BB, 0x420A, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39CA, 0x63B5, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, + 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, + 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, + 0x7CBA, 0x7CBA, 0x7CBA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84DA, + 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, + 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x84FA, 0x955B, 0xE73E, + 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, + 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, + 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, + 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xF7DF, 0xB597, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x5B11, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, + 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, + 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7CBA, + 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, + 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, + 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84FA, + 0xC69D, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, + 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, + 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, + 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0x7C11, 0x39C9, 0x39C9, + 0x39C9, 0x424D, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, + 0x7C79, 0x7C79, 0x7C79, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C9A, + 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, + 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, + 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, + 0x84BA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, + 0x84DA, 0xA5BB, 0xEF7F, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, + 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, + 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, + 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xEF7E, 0x528C, 0x39C9, + 0x39C9, 0x6B95, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C79, + 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C7A, 0x7C7A, 0x7C7A, + 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, + 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, + 0x7C9A, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x84BA, + 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, 0x84DA, + 0x84DA, 0x84DA, 0x953B, 0xDF1E, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, + 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, + 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, + 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xAD77, 0x39C9, + 0x422C, 0x7C39, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, + 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, + 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, + 0x7C7A, 0x7C7A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, + 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, + 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, 0x84BA, + 0x84BA, 0x84BA, 0x84BA, 0x84DA, 0xBE7D, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, + 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, + 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, + 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xEF9E, 0x422B, + 0x52D0, 0x7439, 0x7439, 0x7439, 0x7C39, 0x7C39, 0x7C39, 0x7C39, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, + 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, + 0x7C59, 0x7C59, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C7A, + 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, + 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, + 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, + 0x7CBA, 0x7CBA, 0x84BA, 0x84BA, 0x84BA, 0xA5BC, 0xEF9F, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, + 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, + 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, + 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0xF7BF, 0x73B0, + 0x5B33, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7C39, 0x7C39, 0x7C39, + 0x7C39, 0x7C39, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, + 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, + 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, + 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, + 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7CBA, 0x7CBA, 0x7CBA, + 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x7CBA, 0x9D5B, 0xE75F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, + 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, + 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, + 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0x94B4, + 0x6354, 0x7419, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, + 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7C39, 0x7C39, 0x7C39, 0x7C39, 0x7C39, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, + 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, + 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, + 0x7C79, 0x7C79, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C9A, 0x7C9A, 0x7C9A, + 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, + 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x8D1A, 0xDF1E, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, + 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, + 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, + 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0x9CF5, + 0x5B33, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, + 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, + 0x7C39, 0x7C39, 0x7C39, 0x7C39, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, + 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C79, 0x7C79, + 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, + 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, + 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x84BA, 0xC69D, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, + 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, + 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, + 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0x94B4, + 0x52D0, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, + 0x7419, 0x7419, 0x7419, 0x7419, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, + 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7C39, 0x7C39, 0x7C39, 0x7C39, 0x7C39, 0x7C59, 0x7C59, + 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, + 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, + 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, + 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0x7C9A, 0xBE3C, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, + 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, + 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, + 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0x73D1, + 0x422C, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, + 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7439, 0x7439, + 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, + 0x7439, 0x7439, 0x7C39, 0x7C39, 0x7C39, 0x7C39, 0x7C39, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, + 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, + 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C7A, 0x7C7A, + 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C9A, 0x7C9A, 0xADFC, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, + 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, + 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, + 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xEF9F, 0xE77E, 0x4A4B, + 0x39C9, 0x6375, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x7419, 0x7419, + 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, + 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, + 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7C39, 0x7C39, 0x7C39, 0x7C39, 0x7C39, + 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, + 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, + 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0x7C7A, 0xADBC, 0xE77F, 0xEF7F, 0xEF7F, + 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, + 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, + 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xEF7F, 0xAD97, 0x39C9, + 0x39C9, 0x422D, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, + 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, + 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, + 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, + 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7C39, 0x7C39, 0x7C39, 0x7C39, 0x7C39, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, + 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, + 0x7C59, 0x7C59, 0x7C59, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x7C79, 0x9D5B, 0xE77E, 0xE77F, + 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, + 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, + 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xE77F, 0xDF3E, 0x4A8C, 0x39C9, + 0x39C9, 0x39C9, 0x5AF2, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, + 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, + 0x73F9, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, + 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, + 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7C39, 0x7C39, + 0x7C39, 0x7C39, 0x7C39, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, + 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C79, 0x7C79, 0x9D5B, 0xE75E, + 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, + 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, + 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0x8C73, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39CA, 0x6355, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, + 0x73D9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, + 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, + 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, + 0x7419, 0x7419, 0x7419, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, + 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7C39, 0x7C39, 0x7C39, 0x7C39, 0x7C39, 0x7C59, 0x7C59, 0x7C59, 0x7C59, + 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x94FB, + 0xDF3E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, + 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, + 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xAD97, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x420B, 0x6B97, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, + 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, + 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, + 0x73F9, 0x73F9, 0x73F9, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, + 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7439, 0x7439, 0x7439, 0x7439, + 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, + 0x7439, 0x7C39, 0x7C39, 0x7C39, 0x7C39, 0x7C39, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, 0x7C59, + 0x8CFA, 0xDF3E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, + 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, + 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xE77E, 0xC67B, 0x420A, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x422D, 0x6BB8, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BD9, 0x6BD9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, + 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, + 0x73D9, 0x73D9, 0x73D9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, + 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x7419, 0x7419, 0x7419, 0x7419, + 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, + 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, + 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7C39, 0x7C39, 0x7C39, 0x7C39, 0x7C39, 0x7C59, + 0x7C59, 0x8CFA, 0xDF3E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, + 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, + 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xDF1D, 0x52AD, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x4A90, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, + 0x6BB9, 0x6BB9, 0x6BB9, 0x6BD9, 0x6BD9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, + 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73F9, 0x73F9, 0x73F9, + 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, + 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, + 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7439, + 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, + 0x7439, 0x7439, 0x951B, 0xDF5E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, + 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, + 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0x6B90, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x5AF3, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, + 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BD9, 0x6BD9, 0x73D9, + 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, + 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, + 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x7419, + 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, + 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, 0x7439, + 0x7439, 0x7439, 0x7439, 0x9D3B, 0xDF5E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, + 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, + 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0xE75E, 0x94B4, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39EA, 0x6355, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6BB9, 0x6BB9, 0x6BB9, + 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, + 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BD9, 0x6BD9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, + 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73F9, + 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, + 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, + 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, + 0x7419, 0x7419, 0x7439, 0x7439, 0x9D5B, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, + 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, + 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xB5B8, 0x39EA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x420B, 0x6B77, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, + 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, + 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BD9, + 0x6BD9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, + 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, + 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, + 0x73F9, 0x73F9, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, + 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0xA57B, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, + 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, + 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xDF5E, 0xCE9B, 0x422B, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x4A4E, 0x6B78, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, + 0x6B98, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6BB9, + 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, + 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BD9, 0x6BD9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, + 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, + 0x73D9, 0x73D9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, + 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, + 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0x7419, 0xB5FC, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, + 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, + 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xD71D, 0x5ACD, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x5291, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, + 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, + 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, + 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, + 0x6BB9, 0x6BB9, 0x6BD9, 0x6BD9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, + 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, + 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, + 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x7419, 0x7419, 0x7419, 0xBE5C, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, + 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, + 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0x7C12, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39CA, 0x5AF3, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, + 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, + 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, + 0x6B99, 0x6B99, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, + 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BD9, 0x6BD9, 0x73D9, 0x73D9, 0x73D9, + 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, + 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, + 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x7C39, 0xCEBD, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, + 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, + 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0x9D16, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39EA, 0x6336, 0x6B78, 0x6B78, 0x6B78, 0x6B78, + 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, + 0x6B78, 0x6B78, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B99, 0x6B99, 0x6B99, + 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, + 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, + 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BD9, 0x6BD9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, + 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73F9, 0x73F9, + 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x73F9, 0x847A, 0xD6FE, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, + 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, + 0xDF3E, 0xDF3E, 0xDF3E, 0xDF3E, 0xB619, 0x39EA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x420C, 0x6358, 0x6B58, 0x6B58, 0x6B58, + 0x6B58, 0x6B58, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, + 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, + 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, + 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, + 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BD9, 0x6BD9, + 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, + 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73F9, 0x73F9, 0x8CDA, 0xD71E, 0xD73E, 0xD73E, 0xD73E, 0xD73E, 0xD73E, + 0xD73E, 0xD73E, 0xD73E, 0xD73E, 0xD73E, 0xD73E, 0xD73E, 0xD73E, 0xD73E, 0xD73E, 0xD73E, 0xD73E, 0xD73E, 0xD73E, 0xD73E, 0xD73E, + 0xD73E, 0xD73E, 0xD73E, 0xCEBC, 0x4A6C, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x4A4F, 0x6B58, 0x6B58, 0x6B58, + 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B78, 0x6B78, 0x6B78, 0x6B78, + 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, + 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, + 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6BB9, 0x6BB9, + 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, + 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BD9, 0x6BD9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, + 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0xA57B, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, + 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, + 0xD71E, 0xD71E, 0xD71D, 0x632F, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x52B2, 0x6358, 0x6358, + 0x6358, 0x6358, 0x6358, 0x6358, 0x6358, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, + 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, + 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B98, 0x6B98, + 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, + 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, + 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, + 0x6BB9, 0x6BD9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0x73D9, 0xB61C, 0xD71E, 0xD71E, 0xD71E, 0xD71E, + 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, + 0xD71E, 0xD71E, 0x8453, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39CA, 0x5AD4, 0x6338, + 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6358, 0x6358, 0x6358, 0x6358, 0x6358, 0x6358, 0x6358, 0x6358, 0x6358, 0x6B58, 0x6B58, + 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B78, 0x6B78, + 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, + 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, + 0x6B98, 0x6B98, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, + 0x6B99, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, + 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BD9, 0x73F9, 0xC69D, 0xD71E, 0xD71E, 0xD71E, + 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, + 0xD71E, 0xA557, 0x39CA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39EB, 0x6317, + 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6358, 0x6358, + 0x6358, 0x6358, 0x6358, 0x6358, 0x6358, 0x6358, 0x6358, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, + 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, + 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, + 0x6B78, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B99, 0x6B99, 0x6B99, 0x6B99, + 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, + 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x847A, 0xD6FD, 0xD71E, 0xD71E, + 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, + 0xBE5B, 0x422B, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x422D, + 0x6318, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, + 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6358, 0x6358, 0x6358, 0x6358, 0x6358, 0x6358, 0x6358, 0x6358, + 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, + 0x6B58, 0x6B58, 0x6336, 0x5AF4, 0x52D3, 0x52B1, 0x4A70, 0x4A4E, 0x424E, 0x422D, 0x420C, 0x39EB, 0x39EA, 0x39EA, 0x39EA, 0x39EA, + 0x39EA, 0x39EB, 0x420B, 0x420C, 0x422C, 0x422D, 0x424D, 0x4A4E, 0x4A6F, 0x52B1, 0x52D2, 0x5AF4, 0x6315, 0x6356, 0x6B78, 0x6B98, + 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, + 0x6B99, 0x6B99, 0x6B99, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0x6BB9, 0xA55B, 0xD71E, 0xD71E, + 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xD71E, 0xCEBC, + 0x528C, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x4A70, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, + 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, + 0x6338, 0x6358, 0x6358, 0x6358, 0x6358, 0x6358, 0x6358, 0x6358, 0x6358, 0x6358, 0x6B58, 0x6337, 0x5AF4, 0x52B2, 0x4A6F, 0x422D, + 0x41EB, 0x39CA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39EB, + 0x422D, 0x4A90, 0x52D2, 0x6315, 0x6378, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B99, 0x6B99, + 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6BB9, 0xBE3C, 0xD6FD, + 0xD6FD, 0xD6FD, 0xD6FD, 0xD6FD, 0xD6FD, 0xD6FD, 0xD6FD, 0xD6FD, 0xD6FD, 0xD6FD, 0xD6FD, 0xD6FD, 0xD6FD, 0xD6FD, 0xCEFD, 0x6B70, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39CA, 0x52B3, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, + 0x6318, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, + 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x5AD4, 0x5291, 0x422D, 0x39EA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x3A0B, 0x424E, 0x52B1, 0x6315, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B98, 0x6B98, 0x6B98, + 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x6B99, 0x7C19, 0xCEDD, + 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0x8C94, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39CA, 0x5AD6, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, + 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, 0x6338, + 0x6338, 0x6338, 0x6317, 0x5AD4, 0x4A70, 0x420D, 0x39CA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39EA, 0x424D, 0x52B1, 0x5B15, 0x6B78, 0x6B78, 0x6B78, + 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x6B98, 0x8CBA, + 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xA598, 0x39EA, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x41EC, 0x62F7, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, + 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x5AF6, + 0x5291, 0x422D, 0x39CA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39EA, 0x4A4E, 0x52D2, + 0x6337, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B98, + 0xADBC, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xBE5B, 0x424B, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x422E, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, + 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x6318, 0x5AD5, 0x4A70, 0x420C, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x420C, 0x5291, 0x6316, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, + 0x73B9, 0xC6BD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEFD, 0xCEDD, 0x5B0E, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x4A51, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, + 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x5AD6, 0x4A50, 0x39EB, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x3A0B, 0x5290, 0x6316, 0x6B58, 0x6B58, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, 0x6B78, + 0x6B78, 0x8CBA, 0xCEDD, 0xCEDD, 0xCEDD, 0xCEDD, 0xCEDD, 0xCEDD, 0xCEDD, 0xCEDD, 0xCEDD, 0x73D1, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39CA, 0x5294, 0x62D8, 0x62D8, 0x62D8, 0x62D8, 0x62D8, 0x62D8, 0x62D8, 0x62F8, 0x62F8, 0x62F8, + 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x62F8, 0x5AD7, 0x5292, 0x420D, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39CA, 0x422D, 0x5AD3, 0x6358, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, 0x6B58, + 0x6B58, 0x6B78, 0xB61C, 0xCEDD, 0xCEDD, 0xCEDD, 0xCEDD, 0xCEDD, 0xCEDD, 0xCEDD, 0x9516, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39EA, 0x5AB5, 0x5AD8, 0x5AD8, 0x5AD8, 0x5AD8, 0x5AD8, 0x5AD8, 0x62D8, 0x62D8, 0x62D8, + 0x62D8, 0x62D8, 0x62D8, 0x62D8, 0x5AB5, 0x4A2F, 0x39CA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39EB, 0x4A90, 0x6316, 0x6B58, 0x6B58, 0x6B58, 0x6B58, + 0x6B58, 0x6B58, 0x73F9, 0xC6BD, 0xC6DD, 0xC6DD, 0xC6DD, 0xC6DD, 0xC6DD, 0xA598, 0x39EA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39EB, 0x5274, 0x5AD8, 0x5AD8, 0x5AD8, 0x5AD8, 0x5AD8, 0x5AD8, 0x5AD8, 0x5AD8, + 0x5AD8, 0x5AD8, 0x5294, 0x420E, 0x39CA, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39CA, 0x422E, 0x5AF5, 0x6358, 0x6358, + 0x6358, 0x6358, 0x6358, 0x951B, 0xC6DD, 0xC6DD, 0xC6DD, 0xC6DD, 0x9D37, 0x420A, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39CA, 0x4A2F, 0x5A95, 0x5AB8, 0x5AB8, 0x5AB8, 0x5AB8, 0x5AB8, 0x5AB7, + 0x5273, 0x420C, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x422D, 0x5AD3, + 0x6338, 0x6338, 0x6338, 0x6B58, 0xB63C, 0xC6DD, 0xADD9, 0x6350, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39CA, 0x41EC, 0x420E, 0x4A30, 0x4A30, 0x422E, 0x39EB, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x420C, 0x4A70, 0x5291, 0x73B5, 0x6BB3, 0x52AD, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, + 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9, 0x39C9 +}; + +static_assert(util::size(AtmosphereLogoData) == AtmosphereLogoWidth * AtmosphereLogoHeight, "Logo definition!"); \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_config.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_config.cpp new file mode 100644 index 00000000..e079d1ca --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_config.cpp @@ -0,0 +1,126 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fatal_config.hpp" + +namespace ams::fatal::srv { + + namespace { + + /* Global config. */ + constinit os::SdkMutex g_config_mutex; + constinit bool g_initialized_config; + constinit util::TypedStorage<FatalConfig> g_config; + + FatalConfig &GetFatalConfigImpl() { + if (AMS_UNLIKELY(!g_initialized_config)) { + std::scoped_lock lk(g_config_mutex); + + if (AMS_LIKELY(!g_initialized_config)) { + util::ConstructAt(g_config); + + g_initialized_config = true; + } + } + + return util::GetReference(g_config); + } + + + /* Event creator. */ + os::NativeHandle GetFatalDirtyEventReadableHandle() { + Event evt; + R_ABORT_UNLESS(setsysAcquireFatalDirtyFlagEventHandle(std::addressof(evt))); + return evt.revent; + } + + /* Global event. */ + constinit os::SystemEventType g_fatal_dirty_event; + constinit os::MultiWaitHolderType g_fatal_dirty_multi_wait_holder; + constinit bool g_initialized_fatal_dirty_event; + + } + + os::MultiWaitHolderType *GetFatalDirtyMultiWaitHolder() { + if (AMS_UNLIKELY(!g_initialized_fatal_dirty_event)) { + os::AttachReadableHandleToSystemEvent(std::addressof(g_fatal_dirty_event), GetFatalDirtyEventReadableHandle(), true, os::EventClearMode_ManualClear); + os::InitializeMultiWaitHolder(std::addressof(g_fatal_dirty_multi_wait_holder), std::addressof(g_fatal_dirty_event)); + os::SetMultiWaitHolderUserData(std::addressof(g_fatal_dirty_multi_wait_holder), reinterpret_cast<uintptr_t>(std::addressof(g_fatal_dirty_multi_wait_holder))); + g_initialized_fatal_dirty_event = true; + } + return std::addressof(g_fatal_dirty_multi_wait_holder); + } + + void OnFatalDirtyEvent() { + os::ClearSystemEvent(std::addressof(g_fatal_dirty_event)); + + u64 flags_0, flags_1; + if (R_SUCCEEDED(setsysGetFatalDirtyFlags(std::addressof(flags_0), std::addressof(flags_1))) && (flags_0 & 1)) { + GetFatalConfigImpl().UpdateLanguageCode(); + } + } + + FatalConfig::FatalConfig() { + /* Get information from set. */ + settings::system::GetSerialNumber(std::addressof(m_serial_number)); + settings::system::GetFirmwareVersion(std::addressof(m_firmware_version)); + setsysGetQuestFlag(std::addressof(m_quest_flag)); + this->UpdateLanguageCode(); + + /* Read information from settings. */ + settings::fwdbg::GetSettingsItemValue(std::addressof(m_transition_to_fatal), sizeof(m_transition_to_fatal), "fatal", "transition_to_fatal"); + settings::fwdbg::GetSettingsItemValue(std::addressof(m_show_extra_info), sizeof(m_show_extra_info), "fatal", "show_extra_info"); + + u64 quest_interval_second; + settings::fwdbg::GetSettingsItemValue(std::addressof(quest_interval_second), sizeof(quest_interval_second), "fatal", "quest_reboot_interval_second"); + m_quest_reboot_interval = TimeSpan::FromSeconds(quest_interval_second); + + /* Atmosphere extension for automatic reboot. */ + u64 auto_reboot_ms; + if (settings::fwdbg::GetSettingsItemValue(std::addressof(auto_reboot_ms), sizeof(auto_reboot_ms), "atmosphere", "fatal_auto_reboot_interval") == sizeof(auto_reboot_ms)) { + m_fatal_auto_reboot_interval = TimeSpan::FromMilliSeconds(auto_reboot_ms); + m_fatal_auto_reboot_enabled = auto_reboot_ms != 0; + } + + /* Setup messages. */ + { + m_error_msg = "Error Code: 2%03d-%04d (0x%x)\n"; + + m_error_desc = "An error has occurred.\n\n" + "Please press the POWER Button to restart the console normally, or a VOL button\n" + "to reboot to a payload (or RCM, if none is present). If you are unable to\n" + "restart the console, hold the POWER Button for 12 seconds to turn the console off.\n\n" + "If the problem persists, refer to the Nintendo Support Website.\n" + "support.nintendo.com/switch/error\n"; + + /* If you're running Atmosphere on a quest unit for some reason, talk to me on discord. */ + m_quest_desc = "Please call 1-800-875-1852 for service.\n\n" + "Also, please be aware that running Atmosphere on a Quest device is not fully\n" + "supported. Perhaps try booting your device without Atmosphere before calling\n" + "an official Nintendo service hotline. If you encounter further issues, please\n" + "contact SciresM#0524 on Discord, or via some other means.\n"; + + /* TODO: Try to load dynamically? */ + /* FsStorage message_storage; */ + /* TODO: if (R_SUCCEEDED(fsOpenDataStorageByDataId(0x010000000000081D, "fatal_msg"))) { ... } */ + } + } + + const FatalConfig &GetFatalConfig() { + return GetFatalConfigImpl(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_config.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_config.hpp new file mode 100644 index 00000000..f44b119a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_config.hpp @@ -0,0 +1,95 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fatal::srv { + + class FatalConfig { + private: + settings::system::SerialNumber m_serial_number{}; + settings::system::FirmwareVersion m_firmware_version{}; + u64 m_language_code{}; + TimeSpan m_quest_reboot_interval{}; + bool m_transition_to_fatal{}; + bool m_show_extra_info{}; + bool m_quest_flag{}; + const char *m_error_msg{}; + const char *m_error_desc{}; + const char *m_quest_desc{}; + TimeSpan m_fatal_auto_reboot_interval{}; + bool m_fatal_auto_reboot_enabled{}; + public: + FatalConfig(); + + const settings::system::SerialNumber &GetSerialNumber() const { + return m_serial_number; + } + + const settings::system::FirmwareVersion &GetFirmwareVersion() const { + return m_firmware_version; + } + + void UpdateLanguageCode() { + setGetLanguageCode(&m_language_code); + } + + u64 GetLanguageCode() const { + return m_language_code; + } + + bool ShouldTransitionToFatal() const { + return m_transition_to_fatal; + } + + bool ShouldShowExtraInfo() const { + return m_show_extra_info; + } + + bool IsQuest() const { + return m_quest_flag; + } + + bool IsFatalRebootEnabled() const { + return m_fatal_auto_reboot_enabled; + } + + TimeSpan GetQuestRebootTimeoutInterval() const { + return m_quest_reboot_interval; + } + + TimeSpan GetFatalRebootTimeoutInterval() const { + return m_fatal_auto_reboot_interval; + } + + const char *GetErrorMessage() const { + return m_error_msg; + } + + const char *GetErrorDescription() const { + if (this->IsQuest()) { + return m_quest_desc; + } else { + return m_error_desc; + } + } + }; + + os::MultiWaitHolderType *GetFatalDirtyMultiWaitHolder(); + void OnFatalDirtyEvent(); + const FatalConfig &GetFatalConfig(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_debug.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_debug.cpp new file mode 100644 index 00000000..defe3289 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_debug.cpp @@ -0,0 +1,331 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fatal_debug.hpp" +#include "fatal_config.hpp" + +namespace ams::fatal::srv { + + namespace { + + constexpr u32 SvcSendSyncRequestInstruction = 0xD4000421; + + struct StackFrame { + u64 fp; + u64 lr; + }; + + constexpr inline size_t MaxThreads = 0x60; + + template<size_t MaxThreadCount> + class ThreadTlsMapImpl { + private: + std::pair<u64, u64> m_map[MaxThreadCount]; + size_t m_index; + public: + constexpr ThreadTlsMapImpl() : m_map(), m_index(0) { /* ... */ } + + constexpr void ResetThreadTlsMap() { + m_index = 0; + } + + constexpr void SetThreadTls(u64 thread_id, u64 tls) { + if (m_index < util::size(m_map)) { + m_map[m_index++] = std::make_pair(thread_id, tls); + } + } + + constexpr bool GetThreadTls(u64 *out, u64 thread_id) const { + for (size_t i = 0; i < m_index; ++i) { + if (m_map[i].first == thread_id) { + *out = m_map[i].second; + return true; + } + } + return false; + } + }; + + using ThreadTlsMap = ThreadTlsMapImpl<MaxThreads>; + + constinit ThreadTlsMap g_thread_id_to_tls_map; + + bool IsThreadFatalCaller(Result result, os::NativeHandle debug_handle, u64 thread_id, u64 thread_tls_addr, svc::ThreadContext *thread_ctx) { + /* Verify that the thread is running or waiting. */ + { + u64 _; + u32 _thread_state; + if (R_FAILED(svc::GetDebugThreadParam(&_, &_thread_state, debug_handle, thread_id, svc::DebugThreadParam_State))) { + return false; + } + + const auto thread_state = static_cast<svc::ThreadState>(_thread_state); + if (thread_state != svc::ThreadState_Waiting && thread_state != svc::ThreadState_Running) { + return false; + } + } + + /* Get the thread context. */ + if (R_FAILED(svc::GetDebugThreadContext(thread_ctx, debug_handle, thread_id, svc::ThreadContextFlag_All))) { + return false; + } + + /* Try to read the current instruction. */ + u32 insn; + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(insn)), debug_handle, thread_ctx->pc, sizeof(insn)))) { + return false; + } + + /* If the instruction isn't svc::SendSyncRequest, it's not the fatal caller. */ + if (insn != SvcSendSyncRequestInstruction) { + return false; + } + + /* Read in the fatal caller's TLS. */ + u8 thread_tls[sizeof(svc::ThreadLocalRegion::message_buffer)]; + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(thread_tls), debug_handle, thread_tls_addr, sizeof(thread_tls)))) { + return false; + } + + /* We want to parse the command the fatal caller sent. */ + { + const auto request = hipcParseRequest(thread_tls); + + const struct { + CmifInHeader header; + Result result; + } *in_data = decltype(in_data)(request.data.data_words); + static_assert(sizeof(*in_data) == 0x14, "InData!"); + + /* Fatal command takes in a PID, only one buffer max. */ + if ((request.meta.type != CmifCommandType_Request && request.meta.type != CmifCommandType_RequestWithContext) || + !request.meta.send_pid || + request.meta.num_send_statics || + request.meta.num_recv_statics || + request.meta.num_recv_buffers || + request.meta.num_exch_buffers || + request.meta.num_copy_handles || + request.meta.num_move_handles || + request.meta.num_data_words < ((sizeof(*in_data) + 0x10) / sizeof(u32))) + { + return false; + } + + if (in_data->header.magic != CMIF_IN_HEADER_MAGIC) { + return false; + } + + if (in_data->header.version > 1) { + return false; + } + + switch (in_data->header.command_id) { + case 0: + case 1: + if (request.meta.num_send_buffers != 0) { + return false; + } + break; + case 2: + if (request.meta.num_send_buffers != 1) { + return false; + } + break; + default: + return false; + } + + if (in_data->result.GetValue() != result.GetValue()) { + return false; + } + } + + /* We found our caller. */ + return true; + } + + bool TryGuessBaseAddress(u64 *out_base_address, os::NativeHandle debug_handle, u64 guess) { + svc::MemoryInfo mi; + svc::PageInfo pi; + if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), debug_handle, guess)) || mi.permission != svc::MemoryPermission_ReadExecute) { + return false; + } + + /* Iterate backwards until we find the memory before the code region. */ + while (mi.base_address > 0) { + if (R_FAILED(svc::QueryDebugProcessMemory(std::addressof(mi), std::addressof(pi), debug_handle, guess))) { + return false; + } + + if (mi.state == svc::MemoryState_Free) { + /* Code region will be at the end of the unmapped region preceding it. */ + *out_base_address = mi.base_address + mi.size; + return true; + } + + guess = mi.base_address - 4; + } + + return false; + } + + u64 GetBaseAddress(const ThrowContext *throw_ctx, const svc::ThreadContext *thread_ctx, os::NativeHandle debug_handle) { + u64 base_address = 0; + + if (TryGuessBaseAddress(std::addressof(base_address), debug_handle, thread_ctx->pc)) { + return base_address; + } + + if (TryGuessBaseAddress(std::addressof(base_address), debug_handle, thread_ctx->lr)) { + return base_address; + } + + for (size_t i = 0; i < throw_ctx->cpu_ctx.aarch64_ctx.stack_trace_size; i++) { + if (TryGuessBaseAddress(std::addressof(base_address), debug_handle, throw_ctx->cpu_ctx.aarch64_ctx.stack_trace[i])) { + return base_address; + } + } + + return base_address; + } + + } + + void TryCollectDebugInformation(ThrowContext *ctx, os::ProcessId process_id) { + /* Try to debug the process. This may fail, if we called into ourself. */ + os::NativeHandle debug_handle; + if (R_FAILED(svc::DebugActiveProcess(std::addressof(debug_handle), process_id.value))) { + return; + } + ON_SCOPE_EXIT { os::CloseNativeHandle(debug_handle); }; + + /* First things first, check if process is 64 bits, and get list of thread infos. */ + g_thread_id_to_tls_map.ResetThreadTlsMap(); + { + bool got_create_process = false; + svc::DebugEventInfo d; + while (R_SUCCEEDED(svc::GetDebugEvent(std::addressof(d), debug_handle))) { + switch (d.type) { + case svc::DebugEvent_CreateProcess: + ctx->cpu_ctx.architecture = (d.info.create_process.flags & 1) ? CpuContext::Architecture_Aarch64 : CpuContext::Architecture_Aarch32; + std::memcpy(ctx->proc_name, d.info.create_process.name, sizeof(d.info.create_process.name)); + got_create_process = true; + break; + case svc::DebugEvent_CreateThread: + g_thread_id_to_tls_map.SetThreadTls(d.info.create_thread.thread_id, d.info.create_thread.tls_address); + break; + case svc::DebugEvent_Exception: + case svc::DebugEvent_ExitProcess: + case svc::DebugEvent_ExitThread: + break; + } + } + + if (!got_create_process) { + return; + } + } + + /* TODO: Try to collect information on 32-bit fatals. This shouldn't really matter for any real use case. */ + if (ctx->cpu_ctx.architecture == CpuContext::Architecture_Aarch32) { + return; + } + + /* Welcome to hell. Here, we try to identify which thread called into fatal. */ + bool found_fatal_caller = false; + u64 thread_id = 0; + u64 thread_tls = 0; + svc::ThreadContext thread_ctx; + { + /* We start by trying to get a list of threads. */ + s32 thread_count; + u64 thread_ids[0x60]; + if (R_FAILED(svc::GetThreadList(std::addressof(thread_count), thread_ids, 0x60, debug_handle))) { + return; + } + + /* We need to locate the thread that's called fatal. */ + for (s32 i = 0; i < thread_count; i++) { + const u64 cur_thread_id = thread_ids[i]; + u64 cur_thread_tls; + if (!g_thread_id_to_tls_map.GetThreadTls(std::addressof(cur_thread_tls), cur_thread_id)) { + continue; + } + + if (IsThreadFatalCaller(ctx->result, debug_handle, cur_thread_id, cur_thread_tls, std::addressof(thread_ctx))) { + thread_id = cur_thread_id; + thread_tls = cur_thread_tls; + found_fatal_caller = true; + break; + } + } + if (!found_fatal_caller) { + return; + } + } + if (R_FAILED(svc::GetDebugThreadContext(std::addressof(thread_ctx), debug_handle, thread_id, svc::ThreadContextFlag_All))) { + return; + } + + /* Set register states. */ + ctx->cpu_ctx.aarch64_ctx.SetRegisterValue(aarch64::RegisterName_FP, thread_ctx.fp); + ctx->cpu_ctx.aarch64_ctx.SetRegisterValue(aarch64::RegisterName_LR, thread_ctx.lr); + ctx->cpu_ctx.aarch64_ctx.SetRegisterValue(aarch64::RegisterName_SP, thread_ctx.sp); + ctx->cpu_ctx.aarch64_ctx.SetRegisterValue(aarch64::RegisterName_PC, thread_ctx.pc); + + /* Parse a stack trace. */ + u64 cur_fp = thread_ctx.fp; + ctx->cpu_ctx.aarch64_ctx.stack_trace_size = 0; + for (unsigned int i = 0; i < aarch64::CpuContext::MaxStackTraceDepth; i++) { + /* Validate the current frame. */ + if (cur_fp == 0 || (cur_fp & 0xF)) { + break; + } + + /* Read a new frame. */ + StackFrame cur_frame = {}; + if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(std::addressof(cur_frame)), debug_handle, cur_fp, sizeof(StackFrame)))) { + break; + } + + /* Advance to the next frame. */ + ctx->cpu_ctx.aarch64_ctx.stack_trace[ctx->cpu_ctx.aarch64_ctx.stack_trace_size++] = cur_frame.lr; + cur_fp = cur_frame.fp; + } + + /* Try to read up to 0x100 of stack. */ + ctx->stack_dump_base = 0; + for (size_t sz = 0x100; sz > 0; sz -= 0x10) { + if (R_SUCCEEDED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(ctx->stack_dump), debug_handle, thread_ctx.sp, sz))) { + ctx->stack_dump_base = thread_ctx.sp; + ctx->stack_dump_size = sz; + break; + } + } + + /* Try to read the first 0x100 of TLS. */ + if (R_SUCCEEDED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(ctx->tls_dump), debug_handle, thread_tls, sizeof(ctx->tls_dump)))) { + ctx->tls_address = thread_tls; + } else { + ctx->tls_address = 0; + std::memset(ctx->tls_dump, 0xCC, sizeof(ctx->tls_dump)); + } + + /* Parse the base address. */ + ctx->cpu_ctx.aarch64_ctx.SetBaseAddress(GetBaseAddress(ctx, std::addressof(thread_ctx), debug_handle)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_debug.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_debug.hpp new file mode 100644 index 00000000..851f3d60 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_debug.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fatal::srv { + + void TryCollectDebugInformation(ThrowContext *ctx, os::ProcessId process_id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_event_manager.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_event_manager.cpp new file mode 100644 index 00000000..c1ab1c3c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_event_manager.cpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fatal_event_manager.hpp" + +namespace ams::fatal::srv { + + FatalEventManager::FatalEventManager() : m_lock() { + /* Just create all the events. */ + for (size_t i = 0; i < FatalEventManager::NumFatalEvents; i++) { + R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(m_events[i]), os::EventClearMode_AutoClear, true)); + } + } + + Result FatalEventManager::GetEvent(const os::SystemEventType **out) { + std::scoped_lock lk{m_lock}; + + /* Only allow GetEvent to succeed NumFatalEvents times. */ + R_UNLESS(m_num_events_gotten < FatalEventManager::NumFatalEvents, fatal::ResultTooManyEvents()); + + *out = std::addressof(m_events[m_num_events_gotten++]); + R_SUCCEED(); + } + + void FatalEventManager::SignalEvents() { + for (size_t i = 0; i < FatalEventManager::NumFatalEvents; i++) { + os::SignalSystemEvent(std::addressof(m_events[i])); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_event_manager.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_event_manager.hpp new file mode 100644 index 00000000..99a93906 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_event_manager.hpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fatal::srv { + + class FatalEventManager { + NON_COPYABLE(FatalEventManager); + NON_MOVEABLE(FatalEventManager); + public: + static constexpr size_t NumFatalEvents = 3; + private: + os::SdkMutex m_lock; + size_t m_num_events_gotten = 0; + os::SystemEventType m_events[NumFatalEvents]; + public: + FatalEventManager(); + Result GetEvent(const os::SystemEventType **out); + void SignalEvents(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_font.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_font.cpp new file mode 100644 index 00000000..f1c9be83 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_font.cpp @@ -0,0 +1,260 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fatal_config.hpp" +#include "fatal_font.hpp" + +namespace ams::fatal::srv::font { + + constinit lmem::HeapHandle g_font_heap_handle; + + void SetHeapMemory(void *memory, size_t memory_size) { + g_font_heap_handle = lmem::CreateExpHeap(memory, memory_size, lmem::CreateOption_None); + } + + void *AllocateForFont(size_t size) { + return lmem::AllocateFromExpHeap(g_font_heap_handle, size); + } + + void DeallocateForFont(void *p) { + if (p != nullptr) { + return lmem::FreeToExpHeap(g_font_heap_handle, p); + } + } + + +} + +#define STBTT_assert(x) AMS_ASSERT(x) +#define STBTT_malloc(x,u) ((void)(u),ams::fatal::srv::font::AllocateForFont(x)) +#define STBTT_free(x,u) ((void)(u),ams::fatal::srv::font::DeallocateForFont(x)) + +#define STBTT_STATIC +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" +#undef STBTT_STATIC +#undef STB_TRUETYPE_IMPLEMENTATION + +#undef STBTT_malloc +#undef STBTT_free +#undef STBTT_assert + +/* Define color conversion macros. */ +#define RGB888_TO_RGB565(r, g, b) ((((r >> 3) << 11) & 0xF800) | (((g >> 2) << 5) & 0x7E0) | ((b >> 3) & 0x1F)) +#define RGB565_GET_R8(c) ((((c >> 11) & 0x1F) << 3) | ((c >> 13) & 7)) +#define RGB565_GET_G8(c) ((((c >> 5) & 0x3F) << 2) | ((c >> 9) & 3)) +#define RGB565_GET_B8(c) ((((c >> 0) & 0x1F) << 3) | ((c >> 2) & 7)) + +namespace ams::fatal::srv::font { + + namespace { + + /* Font state globals. */ + u16 *g_frame_buffer = nullptr; + u32 (*g_unswizzle_func)(u32, u32) = nullptr; + u16 g_font_color = 0xFFFF; /* White. */ + float g_font_line_pixels = 16.0f; + float g_font_size = 16.0f; + u32 g_line_x = 0, g_cur_x = 0, g_cur_y = 0; + + u32 g_mono_adv = 0; + + PlFontData g_font; + + stbtt_fontinfo g_stb_font; + + /* Helpers. */ + u16 Blend(u16 color, u16 bg, u8 alpha) { + const u32 c_r = RGB565_GET_R8(color); + const u32 c_g = RGB565_GET_G8(color); + const u32 c_b = RGB565_GET_B8(color); + const u32 b_r = RGB565_GET_R8(bg); + const u32 b_g = RGB565_GET_G8(bg); + const u32 b_b = RGB565_GET_B8(bg); + + const u32 r = ((alpha * c_r) + ((0xFF - alpha) * b_r)) / 0xFF; + const u32 g = ((alpha * c_g) + ((0xFF - alpha) * b_g)) / 0xFF; + const u32 b = ((alpha * c_b) + ((0xFF - alpha) * b_b)) / 0xFF; + + return RGB888_TO_RGB565(r, g, b); + } + + void DrawCodePoint(u32 codepoint, u32 x, u32 y) { + int width = 0, height = 0; + u8* imageptr = stbtt_GetCodepointBitmap(std::addressof(g_stb_font), g_font_size, g_font_size, codepoint, std::addressof(width), std::addressof(height), 0, 0); + ON_SCOPE_EXIT { DeallocateForFont(imageptr); }; + + for (int tmpy = 0; tmpy < height; tmpy++) { + for (int tmpx = 0; tmpx < width; tmpx++) { + /* Implement very simple blending, as the bitmap value is an alpha value. */ + u16 *ptr = g_frame_buffer + g_unswizzle_func(x + tmpx, y + tmpy); + *ptr = Blend(g_font_color, *ptr, imageptr[width * tmpy + tmpx]); + } + } + } + + void DrawString(const char *str, bool add_line, bool mono = false) { + const size_t len = strlen(str); + + u32 cur_x = g_cur_x, cur_y = g_cur_y; + + u32 prev_char = 0; + for (u32 i = 0; i < len; ) { + u32 cur_char; + ssize_t unit_count = decode_utf8(std::addressof(cur_char), reinterpret_cast<const u8 *>(str + i)); + if (unit_count <= 0) break; + + if (!g_mono_adv && i > 0) { + cur_x += g_font_size * stbtt_GetCodepointKernAdvance(std::addressof(g_stb_font), prev_char, cur_char); + } + + i += unit_count; + + if (cur_char == '\n') { + cur_x = g_line_x; + cur_y += g_font_line_pixels; + continue; + } + + int adv_width, left_side_bearing; + stbtt_GetCodepointHMetrics(std::addressof(g_stb_font), cur_char, std::addressof(adv_width), std::addressof(left_side_bearing)); + const u32 cur_width = static_cast<u32>(adv_width) * g_font_size; + + int x0, y0, x1, y1; + stbtt_GetCodepointBitmapBoxSubpixel(std::addressof(g_stb_font), cur_char, g_font_size, g_font_size, 0, 0, std::addressof(x0), std::addressof(y0), std::addressof(x1), std::addressof(y1)); + + DrawCodePoint(cur_char, cur_x + x0 + ((mono && g_mono_adv > cur_width) ? ((g_mono_adv - cur_width) / 2) : 0), cur_y + y0); + + cur_x += (mono ? g_mono_adv : cur_width); + + prev_char = cur_char; + } + + if (add_line) { + /* Advance to next line. */ + g_cur_x = g_line_x; + g_cur_y = cur_y + g_font_line_pixels; + } else { + g_cur_x = cur_x; + g_cur_y = cur_y; + } + } + + } + + void PrintLine(const char *str) { + return DrawString(str, true); + } + + void PrintFormatLine(const char *format, ...) { + char char_buf[0x400]; + + std::va_list va_arg; + va_start(va_arg, format); + util::VSNPrintf(char_buf, sizeof(char_buf), format, va_arg); + va_end(va_arg); + + PrintLine(char_buf); + } + + void Print(const char *str) { + return DrawString(str, false); + } + + void PrintFormat(const char *format, ...) { + char char_buf[0x400]; + + std::va_list va_arg; + va_start(va_arg, format); + util::VSNPrintf(char_buf, sizeof(char_buf), format, va_arg); + va_end(va_arg); + + Print(char_buf); + } + + void PrintMonospaceU64(u64 x) { + char char_buf[0x400]; + util::SNPrintf(char_buf, sizeof(char_buf), "%016lX", x); + + DrawString(char_buf, false, true); + } + + void PrintMonospaceU32(u32 x) { + char char_buf[0x400]; + util::SNPrintf(char_buf, sizeof(char_buf), "%08X", x); + + DrawString(char_buf, false, true); + } + + void PrintMonospaceBlank(u32 width) { + char char_buf[0x400] = {0}; + std::memset(char_buf, ' ', std::min(size_t(width), sizeof(char_buf))); + + DrawString(char_buf, false, true); + } + + void SetFontColor(u16 color) { + g_font_color = color; + } + + void SetPosition(u32 x, u32 y) { + g_line_x = x; + g_cur_x = x; + g_cur_y = y; + } + + u32 GetX() { + return g_cur_x; + } + + u32 GetY() { + return g_cur_y; + } + + void SetFontSize(float fsz) { + g_font_size = stbtt_ScaleForPixelHeight(std::addressof(g_stb_font), fsz * 1.375); + + int ascent; + stbtt_GetFontVMetrics(std::addressof(g_stb_font), std::addressof(ascent),0,0); + g_font_line_pixels = ascent * g_font_size * 1.125; + + int adv_width, left_side_bearing; + stbtt_GetCodepointHMetrics(std::addressof(g_stb_font), 'A', std::addressof(adv_width), std::addressof(left_side_bearing)); + + g_mono_adv = adv_width * g_font_size; + } + + void AddSpacingLines(float num_lines) { + g_cur_x = g_line_x; + g_cur_y += static_cast<u32>(g_font_line_pixels * num_lines); + } + + void ConfigureFontFramebuffer(u16 *fb, u32 (*unswizzle_func)(u32, u32)) { + g_frame_buffer = fb; + g_unswizzle_func = unswizzle_func; + } + + Result InitializeSharedFont() { + R_TRY(plGetSharedFontByType(std::addressof(g_font), PlSharedFontType_Standard)); + + u8 *font_buffer = reinterpret_cast<u8 *>(g_font.address); + stbtt_InitFont(std::addressof(g_stb_font), font_buffer, stbtt_GetFontOffsetForIndex(font_buffer, 0)); + + SetFontSize(16.0f); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_font.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_font.hpp new file mode 100644 index 00000000..cb33aadc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_font.hpp @@ -0,0 +1,39 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fatal::srv::font { + + Result InitializeSharedFont(); + void ConfigureFontFramebuffer(u16 *fb, u32 (*unswizzle_func)(u32, u32)); + void SetHeapMemory(void *memory, size_t memory_size); + + void SetFontColor(u16 color); + void SetPosition(u32 x, u32 y); + u32 GetX(); + u32 GetY(); + void SetFontSize(float fsz); + void AddSpacingLines(float num_lines); + void PrintLine(const char *str); + void PrintFormatLine(const char *format, ...); + void Print(const char *str); + void PrintFormat(const char *format, ...); + void PrintMonospaceU64(u64 x); + void PrintMonospaceU32(u32 x); + void PrintMonospaceBlank(u32 width); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_main.cpp new file mode 100644 index 00000000..5d075080 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_main.cpp @@ -0,0 +1,192 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fatal_service.hpp" +#include "fatal_config.hpp" +#include "fatal_repair.hpp" +#include "fatal_font.hpp" + +/* Set libnx graphics globals. */ +extern "C" { + + u32 __nx_nv_transfermem_size = 0x40000; + ViLayerFlags __nx_vi_stray_layer_flags = (ViLayerFlags)0; + +} + +namespace ams { + + namespace fatal::srv { + + namespace { + + constinit u8 g_fs_heap_memory[2_KB]; + lmem::HeapHandle g_fs_heap_handle; + + void *AllocateForFs(size_t size) { + return lmem::AllocateFromExpHeap(g_fs_heap_handle, size); + } + + void DeallocateForFs(void *p, size_t size) { + AMS_UNUSED(size); + return lmem::FreeToExpHeap(g_fs_heap_handle, p); + } + + void InitializeFsHeap() { + g_fs_heap_handle = lmem::CreateExpHeap(g_fs_heap_memory, sizeof(g_fs_heap_memory), lmem::CreateOption_ThreadSafe); + } + + } + + namespace { + + using ServerOptions = sf::hipc::DefaultServerManagerOptions; + + constexpr sm::ServiceName UserServiceName = sm::ServiceName::Encode("fatal:u"); + constexpr size_t UserMaxSessions = 4; + + constexpr sm::ServiceName PrivateServiceName = sm::ServiceName::Encode("fatal:p"); + constexpr size_t PrivateMaxSessions = 4; + + /* fatal:u, fatal:p. */ + constexpr size_t NumServers = 2; + constexpr size_t NumSessions = UserMaxSessions + PrivateMaxSessions; + + sf::hipc::ServerManager<NumServers, ServerOptions, NumSessions> g_server_manager; + + constinit sf::UnmanagedServiceObject<fatal::impl::IService, fatal::srv::Service> g_user_service_object; + constinit sf::UnmanagedServiceObject<fatal::impl::IPrivateService, fatal::srv::Service> g_private_service_object; + + void InitializeAndLoopIpcServer() { + /* Create services. */ + R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_user_service_object.GetShared(), UserServiceName, UserMaxSessions)); + R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_private_service_object.GetShared(), PrivateServiceName, PrivateMaxSessions)); + + /* Add dirty event holder. */ + auto *dirty_event_holder = fatal::srv::GetFatalDirtyMultiWaitHolder(); + g_server_manager.AddUserMultiWaitHolder(dirty_event_holder); + + /* Loop forever, servicing our services. */ + /* Because fatal has a user wait holder, we need to specify how to process manually. */ + while (auto *signaled_holder = g_server_manager.WaitSignaled()) { + if (signaled_holder == dirty_event_holder) { + /* Dirty event holder was signaled. */ + fatal::srv::OnFatalDirtyEvent(); + g_server_manager.AddUserMultiWaitHolder(signaled_holder); + } else { + /* A server/session was signaled. Have the manager handle it. */ + R_ABORT_UNLESS(g_server_manager.Process(signaled_holder)); + } + } + } + + } + + } + + namespace init { + + namespace { + + Result InitializePlatformServiceWithoutSessionCountForHomebrewCompatibility() { + /* NOTE: This implements a hack, to keep a session to pl:u without counting against the global limit. */ + /* This is done because as of 16.0.0, pl:u is the only way to get shared font access, and there are */ + /* not enough sessions for all reasonable the clients when homebrew gets involved. */ + /* Please do not do similar things for other services; this is a hack which may not always work, */ + /* and which could cause problems in other contexts where the ServerManager doesn't have enough */ + /* slots in truth. */ + + /* Initialize pl. */ + R_ABORT_UNLESS(plInitialize(::PlServiceType_User)); + + /* Get the service session for pl. */ + Service *srv = plGetServiceSession(); + + /* Next, create a clone. */ + /* Because this doesn't go through sm, this does not count against the session limit. */ + Service clone; + R_TRY(serviceClone(srv, std::addressof(clone))); + + /* Next, close the pl service session from sm. */ + /* This decrements the used session count by one, since the session is from sm. */ + serviceClose(srv); + + /* HACK: replace the session with the clone we made, to restore functionality. */ + *srv = clone; + + return 0; + + } + + } + + + void InitializeSystemModule() { + /* Initialize heap. */ + fatal::srv::InitializeFsHeap(); + + /* Initialize our connection to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + + /* Initialize fs. */ + fs::InitializeForSystem(); + fs::SetAllocator(fatal::srv::AllocateForFs, fatal::srv::DeallocateForFs); + fs::SetEnabledAutoAbort(false); + + /* Initialize other services we need. */ + R_ABORT_UNLESS(setInitialize()); + R_ABORT_UNLESS(setsysInitialize()); + R_ABORT_UNLESS(pminfoInitialize()); + R_ABORT_UNLESS(i2cInitialize()); + R_ABORT_UNLESS(bpcInitialize()); + + if (hos::GetVersion() >= hos::Version_8_0_0) { + R_ABORT_UNLESS(clkrstInitialize()); + } else { + R_ABORT_UNLESS(pcvInitialize()); + } + + R_ABORT_UNLESS(psmInitialize()); + R_ABORT_UNLESS(spsmInitialize()); + R_ABORT_UNLESS(InitializePlatformServiceWithoutSessionCountForHomebrewCompatibility()); + gpio::Initialize(); + + /* Mount the SD card. */ + R_ABORT_UNLESS(fs::MountSdCard("sdmc")); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void Main() { + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(fatal, Main)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(fatal, Main)); + + /* Load shared font. */ + R_ABORT_UNLESS(fatal::srv::font::InitializeSharedFont()); + + /* Check whether we should throw fatal due to repair process. */ + fatal::srv::CheckRepairStatus(); + + /* Loop processing the IPC server. */ + fatal::srv::InitializeAndLoopIpcServer(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_repair.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_repair.cpp new file mode 100644 index 00000000..201ec4ba --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_repair.cpp @@ -0,0 +1,116 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fatal_repair.hpp" +#include "fatal_service_for_self.hpp" + +namespace ams::fatal::srv { + + namespace { + + bool IsInRepair() { + /* Before firmware 3.0.0, this wasn't implemented. */ + if (hos::GetVersion() < hos::Version_3_0_0) { + return false; + } + + bool in_repair; + return R_SUCCEEDED(setsysGetInRepairProcessEnableFlag(std::addressof(in_repair))) && in_repair; + } + + bool IsInRepairWithoutVolHeld() { + if (IsInRepair()) { + gpio::GpioPadSession vol_btn; + if (R_FAILED(gpio::OpenSession(std::addressof(vol_btn), gpio::DeviceCode_ButtonVolUp))) { + return true; + } + + /* Ensure we close even on early return. */ + ON_SCOPE_EXIT { gpio::CloseSession(std::addressof(vol_btn)); }; + + /* Set direction input. */ + gpio::SetDirection(std::addressof(vol_btn), gpio::Direction_Input); + + /* Ensure that we're holding the volume button for a full second. */ + auto start = os::GetSystemTick(); + do { + if (gpio::GetValue(std::addressof(vol_btn)) != gpio::GpioValue_Low) { + return true; + } + + /* Sleep for 100 ms. */ + os::SleepThread(TimeSpan::FromMilliSeconds(100)); + } while (os::ConvertToTimeSpan(os::GetSystemTick() - start) < TimeSpan::FromSeconds(1)); + } + + return false; + } + + bool NeedsRunTimeReviser() { + /* Before firmware 5.0.0, this wasn't implemented. */ + if (hos::GetVersion() < hos::Version_5_0_0) { + return false; + } + + bool requires_time_reviser; + return R_SUCCEEDED(setsysGetRequiresRunRepairTimeReviser(std::addressof(requires_time_reviser))) && requires_time_reviser; + } + + bool IsTimeReviserCartridgeInserted() { + FsGameCardHandle gc_hnd; + u8 gc_attr; + { + FsDeviceOperator devop; + if (R_FAILED(fsOpenDeviceOperator(std::addressof(devop)))) { + return false; + } + + /* Ensure we close even on early return. */ + ON_SCOPE_EXIT { fsDeviceOperatorClose(std::addressof(devop)); }; + + /* Check that a gamecard is inserted. */ + bool inserted; + if (R_FAILED(fsDeviceOperatorIsGameCardInserted(std::addressof(devop), std::addressof(inserted))) || !inserted) { + return false; + } + + /* Check that we can retrieve the gamecard's attributes. */ + if (R_FAILED(fsDeviceOperatorGetGameCardHandle(std::addressof(devop), std::addressof(gc_hnd))) || R_FAILED(fsDeviceOperatorGetGameCardAttribute(std::addressof(devop), std::addressof(gc_hnd), std::addressof(gc_attr)))) { + return false; + } + } + + /* Check that the gamecard is a repair tool. */ + return (gc_attr & FsGameCardAttribute_RepairToolFlag); + } + + bool IsInRepairWithoutTimeReviserCartridge() { + return NeedsRunTimeReviser() && IsTimeReviserCartridgeInserted(); + } + + } + + void CheckRepairStatus() { + if (IsInRepairWithoutVolHeld()) { + ThrowFatalForSelf(ResultInRepairWithoutVolHeld()); + } + + if (IsInRepairWithoutTimeReviserCartridge()) { + ThrowFatalForSelf(ResultInRepairWithoutTimeReviserCartridge()); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_repair.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_repair.hpp new file mode 100644 index 00000000..d5bb986d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_repair.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fatal::srv { + + void CheckRepairStatus(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_scoped_file.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_scoped_file.cpp new file mode 100644 index 00000000..8dc0282d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_scoped_file.cpp @@ -0,0 +1,98 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fatal_scoped_file.hpp" + +namespace ams::fatal::srv { + + namespace { + + /* Convenience definitions. */ + constexpr size_t MaximumLineLength = 0x20; + + constinit os::SdkMutex g_format_lock; + constinit char g_format_buffer[2 * os::MemoryPageSize]; + + } + + void ScopedFile::WriteString(const char *str) { + this->Write(str, std::strlen(str)); + } + + void ScopedFile::WriteFormat(const char *fmt, ...) { + /* Acquire exclusive access to the format buffer. */ + std::scoped_lock lk(g_format_lock); + + /* Format to the buffer. */ + { + std::va_list vl; + va_start(vl, fmt); + util::VSNPrintf(g_format_buffer, sizeof(g_format_buffer), fmt, vl); + va_end(vl); + } + + /* Write data. */ + this->WriteString(g_format_buffer); + } + + void ScopedFile::DumpMemory(const char *prefix, const void *data, size_t size) { + const u8 *data_u8 = reinterpret_cast<const u8 *>(data); + const int prefix_len = std::strlen(prefix); + size_t data_ofs = 0; + size_t remaining = size; + bool first = true; + + while (remaining) { + const size_t cur_size = std::min(MaximumLineLength, remaining); + + /* Print the line prefix. */ + if (first) { + this->WriteFormat("%s", prefix); + first = false; + } else { + this->WriteFormat("%*s", prefix_len, ""); + } + + /* Dump up to 0x20 of hex memory. */ + { + char hex[MaximumLineLength * 2 + 2] = {}; + for (size_t i = 0; i < cur_size; i++) { + util::SNPrintf(hex + i * 2, 3, "%02X", data_u8[data_ofs++]); + } + hex[cur_size * 2 + 0] = '\n'; + hex[cur_size * 2 + 1] = '\x00'; + + this->WriteString(hex); + } + + /* Continue. */ + remaining -= cur_size; + } + } + + void ScopedFile::Write(const void *data, size_t size) { + /* If we're not open, we can't write. */ + if (!this->IsOpen()) { + return; + } + + /* Advance, if we write successfully. */ + if (R_SUCCEEDED(fs::WriteFile(m_file, m_offset, data, size, fs::WriteOption::Flush))) { + m_offset += size; + } + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_scoped_file.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_scoped_file.hpp new file mode 100644 index 00000000..80ce73bd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_scoped_file.hpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fatal::srv { + + class ScopedFile { + NON_COPYABLE(ScopedFile); + NON_MOVEABLE(ScopedFile); + private: + fs::FileHandle m_file; + s64 m_offset; + bool m_opened; + public: + ScopedFile(const char *path) : m_file(), m_offset(), m_opened(false) { + if (R_SUCCEEDED(fs::CreateFile(path, 0))) { + m_opened = R_SUCCEEDED(fs::OpenFile(std::addressof(m_file), path, fs::OpenMode_Write | fs::OpenMode_AllowAppend)); + } + } + + ~ScopedFile() { + if (m_opened) { + fs::CloseFile(m_file); + } + } + + bool IsOpen() const { + return m_opened; + } + + void WriteString(const char *str); + void WriteFormat(const char *fmt, ...) __attribute__((format(printf, 2, 3))); + void DumpMemory(const char *prefix, const void *data, size_t size); + + void Write(const void *data, size_t size); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_service.cpp new file mode 100644 index 00000000..3668dfce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_service.cpp @@ -0,0 +1,173 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fatal_config.hpp" +#include "fatal_debug.hpp" +#include "fatal_service.hpp" +#include "fatal_service_for_self.hpp" +#include "fatal_event_manager.hpp" +#include "fatal_task.hpp" + +namespace ams::fatal::srv { + + namespace { + + /* Service Context. */ + class ServiceContext { + private: + os::Event m_erpt_event; + os::Event m_battery_event; + ThrowContext m_context; + FatalEventManager m_event_manager; + bool m_has_thrown; + private: + Result TrySetHasThrown() { + R_UNLESS(!m_has_thrown, fatal::ResultAlreadyThrown()); + m_has_thrown = true; + R_SUCCEED(); + } + public: + ServiceContext() + : m_erpt_event(os::EventClearMode_ManualClear), m_battery_event(os::EventClearMode_ManualClear), + m_context(std::addressof(m_erpt_event), std::addressof(m_battery_event)), m_has_thrown(false) + { + /* ... */ + } + + Result GetEvent(const os::SystemEventType **out) { + R_RETURN(m_event_manager.GetEvent(out)); + } + + Result GetThrowContext(Result *out_error, ncm::ProgramId *out_program_id, FatalPolicy *out_policy, CpuContext *out_ctx) { + /* Set the output. */ + *out_error = m_context.result; + *out_program_id = m_context.throw_program_id; + *out_policy = m_context.policy; + *out_ctx = m_context.cpu_ctx; + + R_SUCCEED(); + } + + Result ThrowFatal(Result result, os::ProcessId process_id) { + R_RETURN(this->ThrowFatalWithCpuContext(result, process_id, FatalPolicy_ErrorReportAndErrorScreen, {})); + } + + Result ThrowFatalWithPolicy(Result result, os::ProcessId process_id, FatalPolicy policy) { + R_RETURN(this->ThrowFatalWithCpuContext(result, process_id, policy, {})); + } + + Result ThrowFatalWithCpuContext(Result result, os::ProcessId process_id, FatalPolicy policy, const CpuContext &cpu_ctx); + }; + + /* Context global. */ + ServiceContext g_context; + + /* Throw implementation. */ + Result ServiceContext::ThrowFatalWithCpuContext(Result result, os::ProcessId process_id, FatalPolicy policy, const CpuContext &cpu_ctx) { + /* We don't support Error-Report-only fatals. */ + R_SUCCEED_IF(policy == FatalPolicy_ErrorReport); + + /* Note that we've thrown fatal. */ + R_TRY(this->TrySetHasThrown()); + + /* At this point we have exclusive access to m_context. */ + m_context.result = result; + m_context.cpu_ctx = cpu_ctx; + m_context.policy = policy; + + /* Cap the stack trace to a sane limit. */ + if (cpu_ctx.architecture == CpuContext::Architecture_Aarch64) { + m_context.cpu_ctx.aarch64_ctx.stack_trace_size = std::max(size_t(m_context.cpu_ctx.aarch64_ctx.stack_trace_size), aarch64::CpuContext::MaxStackTraceDepth); + } else { + m_context.cpu_ctx.aarch32_ctx.stack_trace_size = std::max(size_t(m_context.cpu_ctx.aarch32_ctx.stack_trace_size), aarch32::CpuContext::MaxStackTraceDepth); + } + + /* Get program id. */ + pm::info::GetProgramId(std::addressof(m_context.throw_program_id), process_id); + m_context.is_creport = (m_context.throw_program_id == ncm::SystemProgramId::Creport); + + + if (!m_context.is_creport) { + m_context.program_id = m_context.throw_program_id; + + /* On firmware version 2.0.0, use debugging SVCs to collect information. */ + if (hos::GetVersion() >= hos::Version_2_0_0) { + fatal::srv::TryCollectDebugInformation(std::addressof(m_context), process_id); + } + } else { + /* We received info from creport. Parse program id from afsr0. */ + if (cpu_ctx.architecture == CpuContext::Architecture_Aarch64) { + m_context.program_id = cpu_ctx.aarch64_ctx.GetProgramIdForAtmosphere(); + } else { + m_context.program_id = cpu_ctx.aarch32_ctx.GetProgramIdForAtmosphere(); + } + } + + /* Decide whether to generate a report. */ + m_context.generate_error_report = (policy == FatalPolicy_ErrorReportAndErrorScreen); + + /* Adjust error code (ResultSuccess()/2000-0000 -> err::ResultSystemProgramAbort()/2162-0002). */ + if (R_SUCCEEDED(m_context.result)) { + m_context.result = err::ResultSystemProgramAbort(); + } + + switch (policy) { + case FatalPolicy_ErrorReportAndErrorScreen: + case FatalPolicy_ErrorScreen: + /* Signal that we're throwing. */ + m_event_manager.SignalEvents(); + + if (GetFatalConfig().ShouldTransitionToFatal()) { + RunTasks(std::addressof(m_context)); + } + break; + /* N aborts here. Should we just return an error code? */ + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + R_SUCCEED(); + } + + } + + Result ThrowFatalForSelf(Result result) { + R_RETURN(g_context.ThrowFatalWithPolicy(result, os::GetCurrentProcessId(), FatalPolicy_ErrorScreen)); + } + + Result Service::ThrowFatal(Result result, const sf::ClientProcessId &client_pid) { + R_RETURN(g_context.ThrowFatal(result, client_pid.GetValue())); + } + + Result Service::ThrowFatalWithPolicy(Result result, const sf::ClientProcessId &client_pid, FatalPolicy policy) { + R_RETURN(g_context.ThrowFatalWithPolicy(result, client_pid.GetValue(), policy)); + } + + Result Service::ThrowFatalWithCpuContext(Result result, const sf::ClientProcessId &client_pid, FatalPolicy policy, const CpuContext &cpu_ctx) { + R_RETURN(g_context.ThrowFatalWithCpuContext(result, client_pid.GetValue(), policy, cpu_ctx)); + } + + Result Service::GetFatalEvent(sf::OutCopyHandle out_h) { + const os::SystemEventType *event; + R_TRY(g_context.GetEvent(std::addressof(event))); + out_h.SetValue(os::GetReadableHandleOfSystemEvent(event), false); + R_SUCCEED(); + } + + Result Service::GetFatalContext(sf::Out<Result> out_error, sf::Out<ncm::ProgramId> out_program_id, sf::Out<fatal::FatalPolicy> out_policy, sf::Out<fatal::CpuContext> out_ctx) { + R_RETURN(g_context.GetThrowContext(out_error.GetPointer(), out_program_id.GetPointer(), out_policy.GetPointer(), out_ctx.GetPointer())); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_service.hpp new file mode 100644 index 00000000..99e7d8d6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_service.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fatal::srv { + + class Service { + public: + Result ThrowFatal(Result error, const sf::ClientProcessId &client_pid); + Result ThrowFatalWithPolicy(Result error, const sf::ClientProcessId &client_pid, FatalPolicy policy); + Result ThrowFatalWithCpuContext(Result error, const sf::ClientProcessId &client_pid, FatalPolicy policy, const CpuContext &cpu_ctx); + Result GetFatalEvent(sf::OutCopyHandle out_h); + Result GetFatalContext(sf::Out<Result> out_error, sf::Out<ncm::ProgramId> out_program_id, sf::Out<fatal::FatalPolicy> out_policy, sf::Out<fatal::CpuContext> out_ctx); + }; + static_assert(fatal::impl::IsIService<Service>); + static_assert(fatal::impl::IsIPrivateService<Service>); + +} + diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_service_for_self.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_service_for_self.hpp new file mode 100644 index 00000000..67341c10 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_service_for_self.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::fatal::srv { + + Result ThrowFatalForSelf(Result error_code); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task.cpp new file mode 100644 index 00000000..99be1f1a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task.cpp @@ -0,0 +1,81 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fatal_task.hpp" + +#include "fatal_task_error_report.hpp" +#include "fatal_task_screen.hpp" +#include "fatal_task_sound.hpp" +#include "fatal_task_clock.hpp" +#include "fatal_task_power.hpp" + +namespace ams::fatal::srv { + + namespace { + + class TaskThread { + NON_COPYABLE(TaskThread); + private: + os::ThreadType m_thread; + private: + static void RunTaskImpl(void *arg) { + ITask *task = reinterpret_cast<ITask *>(arg); + + if (R_FAILED(task->Run())) { + /* TODO: Log task failure, somehow? */ + } + } + public: + TaskThread() { /* ... */ } + void StartTask(ITask *task) { + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_thread), RunTaskImpl, task, task->GetStack(), task->GetStackSize(), AMS_GET_SYSTEM_THREAD_PRIORITY(fatalsrv, FatalTaskThread), 3)); + os::SetThreadNamePointer(std::addressof(m_thread), AMS_GET_SYSTEM_THREAD_NAME(fatalsrv, FatalTaskThread)); + os::StartThread(std::addressof(m_thread)); + } + }; + + class TaskManager { + NON_COPYABLE(TaskManager); + private: + static constexpr size_t MaxTasks = 8; + private: + TaskThread m_task_threads[MaxTasks]; + size_t m_task_count = 0; + public: + TaskManager() { /* ... */ } + void StartTask(ITask *task) { + AMS_ABORT_UNLESS(m_task_count < MaxTasks); + m_task_threads[m_task_count++].StartTask(task); + } + }; + + /* Global task manager. */ + TaskManager g_task_manager; + + } + + void RunTasks(const ThrowContext *ctx) { + g_task_manager.StartTask(GetErrorReportTask(ctx)); + g_task_manager.StartTask(GetPowerControlTask(ctx)); + g_task_manager.StartTask(GetShowFatalTask(ctx)); + g_task_manager.StartTask(GetStopSoundTask(ctx)); + g_task_manager.StartTask(GetBacklightControlTask(ctx)); + g_task_manager.StartTask(GetAdjustClockTask(ctx)); + g_task_manager.StartTask(GetPowerButtonObserveTask(ctx)); + g_task_manager.StartTask(GetStateTransitionStopTask(ctx)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task.hpp new file mode 100644 index 00000000..b48cd910 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task.hpp @@ -0,0 +1,56 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "fatal_service.hpp" + +namespace ams::fatal::srv { + + class ITask { + protected: + const ThrowContext *m_context = nullptr; + public: + void Initialize(const ThrowContext *context) { + m_context = context; + } + + virtual Result Run() = 0; + virtual const char *GetName() const = 0; + virtual u8 *GetStack() = 0; + virtual size_t GetStackSize() const = 0; + }; + + template<size_t _StackSize> + class ITaskWithStack : public ITask { + public: + static constexpr size_t StackSize = _StackSize; + static_assert(util::IsAligned(StackSize, os::MemoryPageSize), "StackSize alignment"); + protected: + alignas(os::MemoryPageSize) u8 m_stack_mem[StackSize] = {}; + public: + virtual u8 *GetStack() override final { + return m_stack_mem; + } + + virtual size_t GetStackSize() const override final { + return StackSize; + } + }; + + using ITaskWithDefaultStack = ITaskWithStack<0x2000>; + + void RunTasks(const ThrowContext *ctx); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_clock.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_clock.cpp new file mode 100644 index 00000000..f15e5f6f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_clock.cpp @@ -0,0 +1,83 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fatal_task_clock.hpp" + +namespace ams::fatal::srv { + + + namespace { + + /* Task definition. */ + class AdjustClockTask : public ITaskWithDefaultStack { + private: + Result AdjustClockForModule(PcvModule module, u32 hz); + Result AdjustClock(); + public: + virtual Result Run() override; + virtual const char *GetName() const override { + return "AdjustClockTask"; + } + }; + + /* Task global. */ + AdjustClockTask g_adjust_clock_task; + + /* Task implementation. */ + Result AdjustClockTask::AdjustClockForModule(PcvModule module, u32 hz) { + if (hos::GetVersion() >= hos::Version_8_0_0) { + /* On 8.0.0+, convert to module id + use clkrst API. */ + PcvModuleId module_id; + R_TRY(pcvGetModuleId(std::addressof(module_id), module)); + + ClkrstSession session; + R_TRY(clkrstOpenSession(std::addressof(session), module_id, 3)); + ON_SCOPE_EXIT { clkrstCloseSession(std::addressof(session)); }; + + R_TRY(clkrstSetClockRate(std::addressof(session), hz)); + } else { + /* On 1.0.0-7.0.1, use pcv API. */ + R_TRY(pcvSetClockRate(module, hz)); + } + + R_SUCCEED(); + } + + Result AdjustClockTask::AdjustClock() { + /* Fatal sets the CPU to 1020MHz, the GPU to 307 MHz, and the EMC to 1331MHz. */ + constexpr u32 CPU_CLOCK_1020MHZ = 0x3CCBF700L; + constexpr u32 GPU_CLOCK_307MHZ = 0x124F8000L; + constexpr u32 EMC_CLOCK_1331MHZ = 0x4F588000L; + + R_TRY(AdjustClockForModule(PcvModule_CpuBus, CPU_CLOCK_1020MHZ)); + R_TRY(AdjustClockForModule(PcvModule_GPU, GPU_CLOCK_307MHZ)); + R_TRY(AdjustClockForModule(PcvModule_EMC, EMC_CLOCK_1331MHZ)); + + R_SUCCEED(); + } + + Result AdjustClockTask::Run() { + R_RETURN(AdjustClock()); + } + + } + + ITask *GetAdjustClockTask(const ThrowContext *ctx) { + g_adjust_clock_task.Initialize(ctx); + return std::addressof(g_adjust_clock_task); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_clock.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_clock.hpp new file mode 100644 index 00000000..463ac201 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_clock.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "fatal_task.hpp" + +namespace ams::fatal::srv { + + ITask *GetAdjustClockTask(const ThrowContext *ctx); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_error_report.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_error_report.cpp new file mode 100644 index 00000000..cac9db28 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_error_report.cpp @@ -0,0 +1,178 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fatal_config.hpp" +#include "fatal_task_error_report.hpp" +#include "fatal_scoped_file.hpp" + +namespace ams::fatal::srv { + + namespace { + + /* Helpers. */ + void TryEnsureReportDirectories() { + fs::EnsureDirectory("sdmc:/atmosphere/fatal_reports/dumps"); + } + + bool TryGetCurrentTimestamp(u64 *out) { + /* Clear output. */ + *out = 0; + + /* Check if we have time service. */ + { + bool has_time_service = false; + if (R_FAILED(sm::HasService(std::addressof(has_time_service), sm::ServiceName::Encode("time:s"))) || !has_time_service) { + return false; + } + } + + /* Try to get the current time. */ + { + if (R_FAILED(::timeInitialize())) { + return false; + } + ON_SCOPE_EXIT { ::timeExit(); }; + + return R_SUCCEEDED(::timeGetCurrentTime(TimeType_LocalSystemClock, out)); + } + } + + /* Task definition. */ + class ErrorReportTask : public ITaskWithDefaultStack { + private: + void SaveReportToSdCard(); + public: + virtual Result Run() override; + virtual const char *GetName() const override { + return "WriteErrorReport"; + } + }; + + /* Task globals. */ + ErrorReportTask g_error_report_task; + + /* Task Implementation. */ + void ErrorReportTask::SaveReportToSdCard() { + char file_path[fs::EntryNameLengthMax + 1]; + + /* Try to Ensure path exists. */ + TryEnsureReportDirectories(); + + /* Get a timestamp. */ + u64 timestamp; + if (!TryGetCurrentTimestamp(std::addressof(timestamp))) { + timestamp = os::GetSystemTick().GetInt64Value(); + } + + /* Open report file. */ + { + util::SNPrintf(file_path, sizeof(file_path) - 1, "sdmc:/atmosphere/fatal_reports/%011lu_%016lx.log", timestamp, static_cast<u64>(m_context->program_id)); + ScopedFile file(file_path); + if (file.IsOpen()) { + file.WriteFormat("Atmosphère Fatal Report (v1.1):\n"); + file.WriteFormat("Result: 0x%X (2%03d-%04d)\n\n", m_context->result.GetValue(), m_context->result.GetModule(), m_context->result.GetDescription()); + file.WriteFormat("Program ID: %016lx\n", static_cast<u64>(m_context->program_id)); + if (strlen(m_context->proc_name)) { + file.WriteFormat("Process Name: %s\n", m_context->proc_name); + } + file.WriteFormat("Firmware: %s (Atmosphère %u.%u.%u-%s)\n", GetFatalConfig().GetFirmwareVersion().display_version, ATMOSPHERE_RELEASE_VERSION, ams::GetGitRevision()); + + if (m_context->cpu_ctx.architecture == CpuContext::Architecture_Aarch32) { + file.WriteFormat("General Purpose Registers:\n"); + for (size_t i = 0; i <= aarch32::RegisterName_PC; i++) { + if (m_context->cpu_ctx.aarch32_ctx.HasRegisterValue(static_cast<aarch32::RegisterName>(i))) { + file.WriteFormat( " %3s: %08x\n", aarch32::CpuContext::RegisterNameStrings[i], m_context->cpu_ctx.aarch32_ctx.r[i]); + } + } + file.WriteFormat("Start Address: %08x\n", m_context->cpu_ctx.aarch32_ctx.base_address); + file.WriteFormat("Stack Trace:\n"); + for (unsigned int i = 0; i < m_context->cpu_ctx.aarch32_ctx.stack_trace_size; i++) { + file.WriteFormat(" ReturnAddress[%02u]: %08x\n", i, m_context->cpu_ctx.aarch32_ctx.stack_trace[i]); + } + } else { + file.WriteFormat("General Purpose Registers:\n"); + for (size_t i = 0; i <= aarch64::RegisterName_PC; i++) { + if (m_context->cpu_ctx.aarch64_ctx.HasRegisterValue(static_cast<aarch64::RegisterName>(i))) { + file.WriteFormat( " %3s: %016lx\n", aarch64::CpuContext::RegisterNameStrings[i], m_context->cpu_ctx.aarch64_ctx.x[i]); + } + } + file.WriteFormat("Start Address: %016lx\n", m_context->cpu_ctx.aarch64_ctx.base_address); + file.WriteFormat("Stack Trace:\n"); + for (unsigned int i = 0; i < m_context->cpu_ctx.aarch64_ctx.stack_trace_size; i++) { + file.WriteFormat(" ReturnAddress[%02u]: %016lx\n", i, m_context->cpu_ctx.aarch64_ctx.stack_trace[i]); + } + } + + if (m_context->stack_dump_size != 0) { + file.WriteFormat("Stack Dump: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n"); + for (size_t i = 0; i < 0x10; i++) { + const size_t ofs = i * 0x10; + file.WriteFormat(" %012lx %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + m_context->stack_dump_base + ofs, m_context->stack_dump[ofs + 0], m_context->stack_dump[ofs + 1], m_context->stack_dump[ofs + 2], m_context->stack_dump[ofs + 3], m_context->stack_dump[ofs + 4], m_context->stack_dump[ofs + 5], m_context->stack_dump[ofs + 6], m_context->stack_dump[ofs + 7], + m_context->stack_dump[ofs + 8], m_context->stack_dump[ofs + 9], m_context->stack_dump[ofs + 10], m_context->stack_dump[ofs + 11], m_context->stack_dump[ofs + 12], m_context->stack_dump[ofs + 13], m_context->stack_dump[ofs + 14], m_context->stack_dump[ofs + 15]); + } + } + + if (m_context->tls_address != 0) { + file.WriteFormat("TLS Address: %016lx\n", m_context->tls_address); + file.WriteFormat("TLS Dump: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f\n"); + for (size_t i = 0; i < 0x10; i++) { + const size_t ofs = i * 0x10; + file.WriteFormat(" %012lx %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + m_context->tls_address + ofs, m_context->tls_dump[ofs + 0], m_context->tls_dump[ofs + 1], m_context->tls_dump[ofs + 2], m_context->tls_dump[ofs + 3], m_context->tls_dump[ofs + 4], m_context->tls_dump[ofs + 5], m_context->tls_dump[ofs + 6], m_context->tls_dump[ofs + 7], + m_context->tls_dump[ofs + 8], m_context->tls_dump[ofs + 9], m_context->tls_dump[ofs + 10], m_context->tls_dump[ofs + 11], m_context->tls_dump[ofs + 12], m_context->tls_dump[ofs + 13], m_context->tls_dump[ofs + 14], m_context->tls_dump[ofs + 15]); + } + } + } + } + + /* Dump data to file. */ + { + util::SNPrintf(file_path, sizeof(file_path) - 1, "sdmc:/atmosphere/fatal_reports/dumps/%011lu_%016lx.bin", timestamp, static_cast<u64>(m_context->program_id)); + ScopedFile file(file_path); + if (file.IsOpen()) { + file.Write(m_context->tls_dump, sizeof(m_context->tls_dump)); + if (m_context->stack_dump_size) { + file.Write(m_context->stack_dump, m_context->stack_dump_size); + } + } + } + } + + Result ErrorReportTask::Run() { + if (m_context->generate_error_report) { + /* Here, Nintendo creates an error report with erpt. AMS will not do that. */ + } + + /* Save report to SD card. */ + if (!m_context->is_creport) { + this->SaveReportToSdCard(); + } + + /* Signal we're done with our job. */ + m_context->erpt_event->Signal(); + + R_SUCCEED(); + } + + } + + ITask *GetErrorReportTask(const ThrowContext *ctx) { + g_error_report_task.Initialize(ctx); + return std::addressof(g_error_report_task); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_error_report.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_error_report.hpp new file mode 100644 index 00000000..6cb093f6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_error_report.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "fatal_task.hpp" + +namespace ams::fatal::srv { + + ITask *GetErrorReportTask(const ThrowContext *ctx); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_power.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_power.cpp new file mode 100644 index 00000000..9abfae8e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_power.cpp @@ -0,0 +1,241 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fatal_config.hpp" +#include "fatal_task_power.hpp" + +namespace ams::fatal::srv { + + namespace { + + /* Task types. */ + class PowerControlTask : public ITaskWithDefaultStack { + private: + bool TryShutdown(); + void MonitorBatteryState(); + public: + virtual Result Run() override; + virtual const char *GetName() const override { + return "PowerControlTask"; + } + }; + + class PowerButtonObserveTask : public ITaskWithDefaultStack { + private: + void WaitForPowerButton(); + public: + virtual Result Run() override; + virtual const char *GetName() const override { + return "PowerButtonObserveTask"; + } + }; + + class StateTransitionStopTask : public ITaskWithDefaultStack { + public: + virtual Result Run() override; + virtual const char *GetName() const override { + return "StateTransitionStopTask"; + } + }; + + class RebootTimingObserver { + private: + os::Tick m_start_tick; + TimeSpan m_interval; + bool m_flag; + public: + RebootTimingObserver(bool flag, TimeSpan iv) : m_start_tick(os::GetSystemTick()), m_interval(iv), m_flag(flag) { + /* ... */ + } + + bool IsRebootTiming() const { + auto current_tick = os::GetSystemTick(); + return m_flag && (current_tick - m_start_tick).ToTimeSpan() >= m_interval; + } + }; + + /* Task globals. */ + PowerControlTask g_power_control_task; + PowerButtonObserveTask g_power_button_observe_task; + StateTransitionStopTask g_state_transition_stop_task; + + /* Task Implementations. */ + bool PowerControlTask::TryShutdown() { + /* Set a timeout of 30 seconds. */ + constexpr auto MaxShutdownWaitInterval = TimeSpan::FromSeconds(30); + + auto start_tick = os::GetSystemTick(); + + bool perform_shutdown = true; + PsmBatteryVoltageState bv_state = PsmBatteryVoltageState_Normal; + + while (true) { + auto cur_tick = os::GetSystemTick(); + if ((cur_tick - start_tick).ToTimeSpan() > MaxShutdownWaitInterval) { + break; + } + + if (R_FAILED(psmGetBatteryVoltageState(std::addressof(bv_state))) || bv_state == PsmBatteryVoltageState_NeedsShutdown) { + break; + } + + if (bv_state == PsmBatteryVoltageState_Normal) { + perform_shutdown = false; + break; + } + + /* Query voltage state every 1 seconds, for 30 seconds. */ + os::SleepThread(TimeSpan::FromSeconds(1)); + } + + if (perform_shutdown) { + bpcShutdownSystem(); + } + + return perform_shutdown; + } + + void PowerControlTask::MonitorBatteryState() { + PsmBatteryVoltageState bv_state = PsmBatteryVoltageState_Normal; + + /* Check the battery state, and shutdown on low voltage. */ + if (R_FAILED(psmGetBatteryVoltageState(std::addressof(bv_state))) || bv_state == PsmBatteryVoltageState_NeedsShutdown) { + /* Wait a second for the error report task to finish. */ + m_context->erpt_event->TimedWait(TimeSpan::FromSeconds(1)); + this->TryShutdown(); + return; + } + + /* Signal we've checked the battery at least once. */ + m_context->battery_event->Signal(); + + /* Loop querying voltage state every 5 seconds. */ + while (true) { + if (R_FAILED(psmGetBatteryVoltageState(std::addressof(bv_state)))) { + bv_state = PsmBatteryVoltageState_NeedsShutdown; + } + + switch (bv_state) { + case PsmBatteryVoltageState_NeedsShutdown: + case PsmBatteryVoltageState_NeedsSleep: + { + bool shutdown = this->TryShutdown(); + if (shutdown) { + return; + } + } + break; + default: + break; + } + + os::SleepThread(TimeSpan::FromSeconds(5)); + } + } + + bool IsPowerButtonHeld() { + if (hos::GetVersion() >= hos::Version_14_0_0) { + bool held = false; + return R_SUCCEEDED(bpcGetPowerButton(std::addressof(held))) && held; + } else if (hos::GetVersion() >= hos::Version_2_0_0) { + BpcSleepButtonState state; + return R_SUCCEEDED(bpcGetSleepButtonState(std::addressof(state))) && state == BpcSleepButtonState_Held; + } else { + return false; + } + } + + void PowerButtonObserveTask::WaitForPowerButton() { + /* Wait up to a second for error report generation to finish. */ + m_context->erpt_event->TimedWait(TimeSpan::FromSeconds(1)); + + /* Force a reboot after some time if kiosk unit. */ + const auto &config = GetFatalConfig(); + RebootTimingObserver quest_reboot_helper(config.IsQuest(), config.GetQuestRebootTimeoutInterval()); + RebootTimingObserver fatal_reboot_helper(config.IsFatalRebootEnabled(), config.GetFatalRebootTimeoutInterval()); + + bool check_vol_up = true, check_vol_down = true; + gpio::GpioPadSession vol_up_btn, vol_down_btn; + if (R_FAILED(gpio::OpenSession(std::addressof(vol_up_btn), gpio::DeviceCode_ButtonVolUp))) { + check_vol_up = false; + } + if (R_FAILED(gpio::OpenSession(std::addressof(vol_down_btn), gpio::DeviceCode_ButtonVolDn))) { + check_vol_down = false; + } + + /* Ensure we close on early return. */ + ON_SCOPE_EXIT { if (check_vol_up) { gpio::CloseSession(std::addressof(vol_up_btn)); } }; + ON_SCOPE_EXIT { if (check_vol_down) { gpio::CloseSession(std::addressof(vol_down_btn)); } }; + + /* Set direction input. */ + if (check_vol_up) { + gpio::SetDirection(std::addressof(vol_up_btn), gpio::Direction_Input); + } + if (check_vol_down) { + gpio::SetDirection(std::addressof(vol_down_btn), gpio::Direction_Input); + } + + while (true) { + if (fatal_reboot_helper.IsRebootTiming() || (quest_reboot_helper.IsRebootTiming()) || + (check_vol_up && gpio::GetValue(std::addressof(vol_up_btn)) == gpio::GpioValue_Low) || + (check_vol_down && gpio::GetValue(std::addressof(vol_down_btn)) == gpio::GpioValue_Low) || + IsPowerButtonHeld()) + { + /* If any of the above conditions succeeded, we should reboot. */ + bpcRebootSystem(); + return; + } + + + /* Wait 100 ms between button checks. */ + os::SleepThread(TimeSpan::FromMilliSeconds(100)); + } + } + + Result PowerControlTask::Run() { + this->MonitorBatteryState(); + R_SUCCEED(); + } + + Result PowerButtonObserveTask::Run() { + this->WaitForPowerButton(); + R_SUCCEED(); + } + + Result StateTransitionStopTask::Run() { + /* Nintendo ignores the output of this call... */ + spsmPutErrorState(); + R_SUCCEED(); + } + + } + + ITask *GetPowerControlTask(const ThrowContext *ctx) { + g_power_control_task.Initialize(ctx); + return std::addressof(g_power_control_task); + } + + ITask *GetPowerButtonObserveTask(const ThrowContext *ctx) { + g_power_button_observe_task.Initialize(ctx); + return std::addressof(g_power_button_observe_task); + } + + ITask *GetStateTransitionStopTask(const ThrowContext *ctx) { + g_state_transition_stop_task.Initialize(ctx); + return std::addressof(g_state_transition_stop_task); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_power.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_power.hpp new file mode 100644 index 00000000..8a976187 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_power.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "fatal_task.hpp" + +namespace ams::fatal::srv { + + ITask *GetPowerControlTask(const ThrowContext *ctx); + ITask *GetPowerButtonObserveTask(const ThrowContext *ctx); + ITask *GetStateTransitionStopTask(const ThrowContext *ctx); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_screen.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_screen.cpp new file mode 100644 index 00000000..cae3996c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_screen.cpp @@ -0,0 +1,567 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fatal_task_screen.hpp" +#include "fatal_config.hpp" +#include "fatal_font.hpp" + +namespace ams::fatal::srv { + + /* Include Atmosphere logo into its own anonymous namespace. */ + namespace { + + #include "fatal_ams_logo.inc" + + } + + namespace { + + /* Screen definitions. */ + constexpr u32 FatalScreenWidth = 1280; + constexpr u32 FatalScreenHeight = 720; + constexpr u32 FatalScreenBpp = 2; + constexpr u32 FatalLayerZ = 100; + + constexpr u32 FatalScreenWidthAlignedBytes = util::AlignUp(FatalScreenWidth * FatalScreenBpp, 64); + constexpr u32 FatalScreenWidthAligned = FatalScreenWidthAlignedBytes / FatalScreenBpp; + + /* There should only be a single transfer memory (for nv). */ + alignas(os::MemoryPageSize) constinit u8 g_nv_transfer_memory[0x40000]; + + /* There should only be a single (1280*768) framebuffer. */ + constexpr size_t FrameBufferRequiredSizeBytes = FatalScreenWidthAlignedBytes * util::AlignUp(FatalScreenHeight, 128); + constexpr size_t FrameBufferRequiredSizePageAligned = util::AlignUp(FrameBufferRequiredSizeBytes, os::MemoryPageSize); + constexpr size_t FrameBufferRequiredSizeHeapAligned = util::AlignUp(FrameBufferRequiredSizeBytes, os::MemoryHeapUnitSize); + + constinit u8 *g_framebuffer_pointer = nullptr; + + void InitializeFrameBufferPointer() { + /* Try to get a framebuffer from heap. */ + { + if (R_SUCCEEDED(os::SetMemoryHeapSize(FrameBufferRequiredSizeHeapAligned))) { + g_framebuffer_pointer = reinterpret_cast<u8 *>(os::GetMemoryHeapAddress()); + return; + } + } + + /* We couldn't use heap, so try insecure memory, from the system nonsecure pool. */ + { + uintptr_t address = 0; + if (R_SUCCEEDED(os::AllocateInsecureMemory(std::addressof(address), FrameBufferRequiredSizePageAligned))) { + g_framebuffer_pointer = reinterpret_cast<u8 *>(address); + return; + } + } + + /* Neither heap nor insecure is available, so we're going to have to try to raid the unsafe pool. */ + { + /* First, increase the limit to an extremely high value. */ + size_t large_size = std::max(128_MB, FrameBufferRequiredSizeHeapAligned); + while (svc::ResultLimitReached::Includes(svc::SetUnsafeLimit(large_size))) { + large_size *= 2; + } + + /* Next, map some unsafe memory. */ + uintptr_t address = 0; + if (R_SUCCEEDED(os::AllocateUnsafeMemory(std::addressof(address), FrameBufferRequiredSizePageAligned))) { + g_framebuffer_pointer = reinterpret_cast<u8 *>(address); + return; + } + } + } + + } + +} + +extern "C" ::Result __nx_nv_create_tmem(TransferMemory *t, u32 *out_size, Permission perm) { + *out_size = sizeof(ams::fatal::srv::g_nv_transfer_memory); + return tmemCreateFromMemory(t, ams::fatal::srv::g_nv_transfer_memory, sizeof(ams::fatal::srv::g_nv_transfer_memory), perm); +} + +namespace ams::fatal::srv { + + namespace { + + /* Pixel calculation helper. */ + constexpr u32 GetPixelOffset(u32 x, u32 y) { + u32 tmp_pos = ((y & 127) / 16) + (x/32*8) + ((y/16/8)*(((FatalScreenWidthAligned/2)/16*8))); + tmp_pos *= 16*16 * 4; + + tmp_pos += ((y%16)/8)*512 + ((x%32)/16)*256 + ((y%8)/2)*64 + ((x%16)/8)*32 + (y%2)*16 + (x%8)*2;//This line is a modified version of code from the Tegra X1 datasheet. + + return tmp_pos / 2; + } + + /* Task definitions. */ + class ShowFatalTask : public ITaskWithStack<0x8000> { + private: + ViDisplay m_display; + ViLayer m_layer; + NWindow m_win; + NvMap m_map; + private: + Result SetupDisplayInternal(); + Result SetupDisplayExternal(); + Result PrepareScreenForDrawing(); + void PreRenderFrameBuffer(); + Result InitializeNativeWindow(); + void DisplayPreRenderedFrame(); + Result ShowFatal(); + public: + virtual Result Run() override; + virtual const char *GetName() const override { + return "ShowFatal"; + } + }; + + class BacklightControlTask : public ITaskWithDefaultStack { + private: + void TurnOnBacklight(); + public: + virtual Result Run() override; + virtual const char *GetName() const override { + return "BacklightControlTask"; + } + }; + + /* Task globals. */ + ShowFatalTask g_show_fatal_task; + BacklightControlTask g_backlight_control_task; + + /* Task implementations. */ + Result ShowFatalTask::SetupDisplayInternal() { + ViDisplay temp_display; + /* Try to open the display. */ + R_TRY_CATCH(viOpenDisplay("Internal", std::addressof(temp_display))) { + R_CONVERT(vi::ResultNotFound, ResultSuccess()); + } R_END_TRY_CATCH; + + /* Guarantee we close the display. */ + ON_SCOPE_EXIT { viCloseDisplay(std::addressof(temp_display)); }; + + /* Turn on the screen. */ + if (hos::GetVersion() >= hos::Version_3_0_0) { + R_TRY(viSetDisplayPowerState(std::addressof(temp_display), ViPowerState_On)); + } else { + /* Prior to 3.0.0, the ViPowerState enum was different (0 = Off, 1 = On). */ + R_TRY(viSetDisplayPowerState(std::addressof(temp_display), ViPowerState_On_Deprecated)); + } + + /* Set alpha to 1.0f. */ + R_TRY(viSetDisplayAlpha(std::addressof(temp_display), 1.0f)); + + R_SUCCEED(); + } + + Result ShowFatalTask::SetupDisplayExternal() { + ViDisplay temp_display; + /* Try to open the display. */ + R_TRY_CATCH(viOpenDisplay("External", std::addressof(temp_display))) { + R_CONVERT(vi::ResultNotFound, ResultSuccess()); + } R_END_TRY_CATCH; + + /* Guarantee we close the display. */ + ON_SCOPE_EXIT { viCloseDisplay(std::addressof(temp_display)); }; + + /* Set alpha to 1.0f. */ + R_TRY(viSetDisplayAlpha(std::addressof(temp_display), 1.0f)); + + R_SUCCEED(); + } + + Result ShowFatalTask::PrepareScreenForDrawing() { + /* Connect to vi. */ + R_TRY(viInitialize(ViServiceType_Manager)); + + /* Close other content. */ + viSetContentVisibility(false); + + /* Setup the two displays. */ + R_TRY(SetupDisplayInternal()); + R_TRY(SetupDisplayExternal()); + + /* Open the default display. */ + R_TRY(viOpenDefaultDisplay(std::addressof(m_display))); + + /* Reset the display magnification to its default value. */ + s32 display_width, display_height; + R_TRY(viGetDisplayLogicalResolution(std::addressof(m_display), std::addressof(display_width), std::addressof(display_height))); + + /* viSetDisplayMagnification was added in 3.0.0. */ + if (hos::GetVersion() >= hos::Version_3_0_0) { + R_TRY(viSetDisplayMagnification(std::addressof(m_display), 0, 0, display_width, display_height)); + } + + /* Create layer to draw to. */ + R_TRY(viCreateLayer(std::addressof(m_display), std::addressof(m_layer))); + + /* Setup the layer. */ + { + /* Display a layer of 1280 x 720 at 1.5x magnification */ + /* NOTE: N uses 2 (770x400) RGBA4444 buffers (tiled buffer + linear). */ + /* We use a single 1280x720 tiled RGB565 buffer. */ + constexpr s32 RawWidth = FatalScreenWidth; + constexpr s32 RawHeight = FatalScreenHeight; + constexpr s32 LayerWidth = ((RawWidth) * 3) / 2; + constexpr s32 LayerHeight = ((RawHeight) * 3) / 2; + + const float layer_x = static_cast<float>((display_width - LayerWidth) / 2); + const float layer_y = static_cast<float>((display_height - LayerHeight) / 2); + + R_TRY(viSetLayerSize(std::addressof(m_layer), LayerWidth, LayerHeight)); + + /* Set the layer's Z at display maximum, to be above everything else .*/ + R_TRY(viSetLayerZ(std::addressof(m_layer), FatalLayerZ)); + + /* Center the layer in the screen. */ + R_TRY(viSetLayerPosition(std::addressof(m_layer), layer_x, layer_y)); + + /* Create framebuffer. */ + R_TRY(nwindowCreateFromLayer(std::addressof(m_win), std::addressof(m_layer))); + R_TRY(this->InitializeNativeWindow()); + } + + R_SUCCEED(); + } + + void ShowFatalTask::PreRenderFrameBuffer() { + const FatalConfig &config = GetFatalConfig(); + + /* Allocate a frame buffer. */ + InitializeFrameBufferPointer(); + AMS_ABORT_UNLESS(g_framebuffer_pointer != nullptr); + + /* Pre-render the image into the static framebuffer. */ + u16 *tiled_buf = reinterpret_cast<u16 *>(g_framebuffer_pointer); + + /* Temporarily use the NV transfer memory as font backing heap. */ + font::SetHeapMemory(g_nv_transfer_memory, sizeof(g_nv_transfer_memory)); + ON_SCOPE_EXIT { std::memset(g_nv_transfer_memory, 0, sizeof(g_nv_transfer_memory)); }; + + /* Let the font manager know about our framebuffer. */ + font::ConfigureFontFramebuffer(tiled_buf, GetPixelOffset); + font::SetFontColor(0xFFFF); + + /* Draw a background. */ + for (size_t i = 0; i < FrameBufferRequiredSizeBytes / sizeof(*tiled_buf); i++) { + tiled_buf[i] = AtmosphereLogoData[0]; + } + + /* Draw the atmosphere logo in the upper right corner. */ + const u32 start_x = 32, start_y = 64; + for (size_t y = 0; y < AtmosphereLogoHeight; y++) { + for (size_t x = 0; x < AtmosphereLogoWidth; x++) { + tiled_buf[GetPixelOffset(FatalScreenWidth - AtmosphereLogoWidth - start_x + x, start_x + y)] = AtmosphereLogoData[y * AtmosphereLogoWidth + x]; + } + } + + /* Draw error message and firmware. */ + font::SetPosition(start_x, start_y); + font::SetFontSize(16.0f); + font::PrintFormat(config.GetErrorMessage(), m_context->result.GetModule(), m_context->result.GetDescription(), m_context->result.GetValue()); + font::AddSpacingLines(0.5f); + font::PrintFormatLine( "Program: %016lX", static_cast<u64>(m_context->program_id)); + font::AddSpacingLines(0.5f); + + font::PrintFormatLine("Firmware: %s (Atmosphère %u.%u.%u-%s)", config.GetFirmwareVersion().display_version, ATMOSPHERE_RELEASE_VERSION, ams::GetGitRevision()); + font::AddSpacingLines(1.5f); + if (!exosphere::ResultVersionMismatch::Includes(m_context->result)) { + font::Print(config.GetErrorDescription()); + } else { + /* Print a special message for atmosphere version mismatch. */ + font::Print("Atmosphère version mismatch detected.\n\n" + "Please press the POWER Button to restart the console normally, or a VOL button\n" + "to reboot to a payload (or RCM, if none is present). If you are unable to\n" + "restart the console, hold the POWER Button for 12 seconds to turn the console off.\n\n" + "Please ensure that all Atmosphère components are updated.\n" + "github.com/Atmosphere-NX/Atmosphere/releases\n"); + } + + /* Add a line. */ + for (size_t x = start_x; x < FatalScreenWidth - start_x; x++) { + tiled_buf[GetPixelOffset(x, font::GetY())] = 0xFFFF; + } + + font::AddSpacingLines(1.5f); + + u32 backtrace_y = font::GetY(); + u32 backtrace_x = 0; + u32 pc_x = 0; + + /* Note architecutre. */ + const bool is_aarch32 = m_context->cpu_ctx.architecture == CpuContext::Architecture_Aarch32; + + /* Print GPRs. */ + font::SetFontSize(14.0f); + font::Print("General Purpose Registers "); + font::PrintLine(""); + font::SetPosition(start_x, font::GetY()); + font::AddSpacingLines(0.5f); + if (is_aarch32) { + for (size_t i = 0; i < (aarch32::RegisterName_GeneralPurposeCount / 2); i++) { + u32 x = font::GetX(); + font::PrintFormat("%s:", aarch32::CpuContext::RegisterNameStrings[i]); + font::SetPosition(x + 47, font::GetY()); + if (m_context->cpu_ctx.aarch32_ctx.HasRegisterValue(static_cast<aarch32::RegisterName>(i))) { + font::PrintMonospaceU32(m_context->cpu_ctx.aarch32_ctx.r[i]); + font::PrintMonospaceBlank(8); + } else { + font::PrintMonospaceBlank(16); + } + font::Print(" "); + pc_x = font::GetX(); + font::PrintFormat("%s:", aarch32::CpuContext::RegisterNameStrings[i + (aarch32::RegisterName_GeneralPurposeCount / 2)]); + font::SetPosition(pc_x + 47, font::GetY()); + if (m_context->cpu_ctx.aarch32_ctx.HasRegisterValue(static_cast<aarch32::RegisterName>(i + (aarch32::RegisterName_GeneralPurposeCount / 2)))) { + font::PrintMonospaceU32(m_context->cpu_ctx.aarch32_ctx.r[i + (aarch32::RegisterName_GeneralPurposeCount / 2)]); + font::PrintMonospaceBlank(8); + } else { + font::PrintMonospaceBlank(16); + } + + if (i == (aarch32::RegisterName_GeneralPurposeCount / 2) - 1) { + font::Print(" "); + backtrace_x = font::GetX(); + } + + font::PrintLine(""); + font::SetPosition(start_x, font::GetY()); + } + } else { + for (size_t i = 0; i < aarch64::RegisterName_GeneralPurposeCount / 2; i++) { + u32 x = font::GetX(); + font::PrintFormat("%s:", aarch64::CpuContext::RegisterNameStrings[i]); + font::SetPosition(x + 47, font::GetY()); + if (m_context->cpu_ctx.aarch64_ctx.HasRegisterValue(static_cast<aarch64::RegisterName>(i))) { + font::PrintMonospaceU64(m_context->cpu_ctx.aarch64_ctx.x[i]); + } else { + font::PrintMonospaceBlank(16); + } + font::Print(" "); + pc_x = font::GetX(); + font::PrintFormat("%s:", aarch64::CpuContext::RegisterNameStrings[i + (aarch64::RegisterName_GeneralPurposeCount / 2)]); + font::SetPosition(pc_x + 47, font::GetY()); + if (m_context->cpu_ctx.aarch64_ctx.HasRegisterValue(static_cast<aarch64::RegisterName>(i + (aarch64::RegisterName_GeneralPurposeCount / 2)))) { + font::PrintMonospaceU64(m_context->cpu_ctx.aarch64_ctx.x[i + (aarch64::RegisterName_GeneralPurposeCount / 2)]); + } else { + font::PrintMonospaceBlank(16); + } + + if (i == (aarch64::RegisterName_GeneralPurposeCount / 2) - 1) { + font::Print(" "); + backtrace_x = font::GetX(); + } + + font::PrintLine(""); + font::SetPosition(start_x, font::GetY()); + } + } + + /* Print PC. */ + { + font::SetPosition(pc_x, backtrace_y); + const u32 x = font::GetX(); + font::Print("PC: "); + font::SetPosition(x + 47, font::GetY()); + } + if (is_aarch32) { + font::PrintMonospaceU32(m_context->cpu_ctx.aarch32_ctx.pc); + } else { + font::PrintMonospaceU64(m_context->cpu_ctx.aarch64_ctx.pc); + } + + /* Print Backtrace. */ + u32 bt_size; + if (is_aarch32) { + bt_size = m_context->cpu_ctx.aarch32_ctx.stack_trace_size; + } else { + bt_size = m_context->cpu_ctx.aarch64_ctx.stack_trace_size; + } + + + font::SetPosition(backtrace_x, backtrace_y); + if (bt_size == 0) { + if (is_aarch32) { + font::Print("Start Address: "); + font::PrintMonospaceU32(m_context->cpu_ctx.aarch32_ctx.base_address); + font::PrintLine(""); + } else { + font::Print("Start Address: "); + font::PrintMonospaceU64(m_context->cpu_ctx.aarch64_ctx.base_address); + font::PrintLine(""); + } + } else { + if (is_aarch32) { + font::Print("Backtrace - Start Address: "); + font::PrintMonospaceU32(m_context->cpu_ctx.aarch32_ctx.base_address); + font::PrintLine(""); + font::AddSpacingLines(0.5f); + for (u32 i = 0; i < aarch32::CpuContext::MaxStackTraceDepth / 2; i++) { + u32 bt_cur = 0, bt_next = 0; + if (i < m_context->cpu_ctx.aarch32_ctx.stack_trace_size) { + bt_cur = m_context->cpu_ctx.aarch32_ctx.stack_trace[i]; + } + if (i + aarch32::CpuContext::MaxStackTraceDepth / 2 < m_context->cpu_ctx.aarch32_ctx.stack_trace_size) { + bt_next = m_context->cpu_ctx.aarch32_ctx.stack_trace[i + aarch32::CpuContext::MaxStackTraceDepth / 2]; + } + + if (i < m_context->cpu_ctx.aarch32_ctx.stack_trace_size) { + u32 x = font::GetX(); + font::PrintFormat("BT[%02d]: ", i); + font::SetPosition(x + 72, font::GetY()); + font::PrintMonospaceU32(bt_cur); + font::PrintMonospaceBlank(8); + font::Print(" "); + } + + if (i + aarch32::CpuContext::MaxStackTraceDepth / 2 < m_context->cpu_ctx.aarch32_ctx.stack_trace_size) { + u32 x = font::GetX(); + font::PrintFormat("BT[%02d]: ", i + aarch32::CpuContext::MaxStackTraceDepth / 2); + font::SetPosition(x + 72, font::GetY()); + font::PrintMonospaceU32(bt_next); + font::PrintMonospaceBlank(8); + } + + font::PrintLine(""); + font::SetPosition(backtrace_x, font::GetY()); + } + } else { + font::Print("Backtrace - Start Address: "); + font::PrintMonospaceU64(m_context->cpu_ctx.aarch64_ctx.base_address); + font::PrintLine(""); + font::AddSpacingLines(0.5f); + for (u32 i = 0; i < aarch64::CpuContext::MaxStackTraceDepth / 2; i++) { + u64 bt_cur = 0, bt_next = 0; + if (i < m_context->cpu_ctx.aarch64_ctx.stack_trace_size) { + bt_cur = m_context->cpu_ctx.aarch64_ctx.stack_trace[i]; + } + if (i + aarch64::CpuContext::MaxStackTraceDepth / 2 < m_context->cpu_ctx.aarch64_ctx.stack_trace_size) { + bt_next = m_context->cpu_ctx.aarch64_ctx.stack_trace[i + aarch64::CpuContext::MaxStackTraceDepth / 2]; + } + + if (i < m_context->cpu_ctx.aarch64_ctx.stack_trace_size) { + u32 x = font::GetX(); + font::PrintFormat("BT[%02d]: ", i); + font::SetPosition(x + 72, font::GetY()); + font::PrintMonospaceU64(bt_cur); + font::Print(" "); + } + + if (i + aarch64::CpuContext::MaxStackTraceDepth / 2 < m_context->cpu_ctx.aarch64_ctx.stack_trace_size) { + u32 x = font::GetX(); + font::PrintFormat("BT[%02d]: ", i + aarch64::CpuContext::MaxStackTraceDepth / 2); + font::SetPosition(x + 72, font::GetY()); + font::PrintMonospaceU64(bt_next); + } + + font::PrintLine(""); + font::SetPosition(backtrace_x, font::GetY()); + } + } + } + } + + Result ShowFatalTask::InitializeNativeWindow() { + /* Setup nv driver. */ + R_TRY(nvInitialize()); + R_TRY(nvMapInit()); + R_TRY(nvFenceInit()); + + /* Create nvmap. */ + R_TRY(nvMapCreate(std::addressof(m_map), g_framebuffer_pointer, FrameBufferRequiredSizeBytes, 0x20000, NvKind_Pitch, true)); + + /* Setup graphics buffer. */ + { + NvGraphicBuffer grbuf = {}; + grbuf.header.num_ints = (sizeof(NvGraphicBuffer) - sizeof(NativeHandle)) / 4; + grbuf.unk0 = -1; + grbuf.magic = 0xDAFFCAFF; + grbuf.pid = 42; + grbuf.usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE; + grbuf.format = PIXEL_FORMAT_RGB_565; + grbuf.ext_format = PIXEL_FORMAT_RGB_565; + grbuf.num_planes = 1; + grbuf.planes[0].width = FatalScreenWidth; + grbuf.planes[0].height = FatalScreenHeight; + grbuf.planes[0].color_format = NvColorFormat_R5G6B5; + grbuf.planes[0].layout = NvLayout_BlockLinear; + grbuf.planes[0].kind = NvKind_Generic_16BX2; + grbuf.planes[0].block_height_log2 = 4; + grbuf.nvmap_id = nvMapGetId(std::addressof(m_map)); + grbuf.stride = FatalScreenWidthAligned; + grbuf.total_size = FrameBufferRequiredSizeBytes; + grbuf.planes[0].pitch = FatalScreenWidthAlignedBytes; + grbuf.planes[0].size = FrameBufferRequiredSizeBytes; + grbuf.planes[0].offset = 0; + + R_TRY(nwindowConfigureBuffer(std::addressof(m_win), 0, std::addressof(grbuf))); + } + + R_SUCCEED(); + } + + void ShowFatalTask::DisplayPreRenderedFrame() { + s32 slot; + R_ABORT_UNLESS(nwindowDequeueBuffer(std::addressof(m_win), std::addressof(slot), nullptr)); + dd::FlushDataCache(g_framebuffer_pointer, FrameBufferRequiredSizeBytes); + R_ABORT_UNLESS(nwindowQueueBuffer(std::addressof(m_win), m_win.cur_slot, NULL)); + } + + Result ShowFatalTask::ShowFatal() { + /* Pre-render the framebuffer. */ + PreRenderFrameBuffer(); + + /* Prepare screen for drawing. */ + R_ABORT_UNLESS(PrepareScreenForDrawing()); + + /* Display the pre-rendered frame. */ + this->DisplayPreRenderedFrame(); + + R_SUCCEED(); + } + + Result ShowFatalTask::Run() { + /* Don't show the fatal error screen until we've verified the battery is okay. */ + m_context->battery_event->Wait(); + + R_RETURN(ShowFatal()); + } + + void BacklightControlTask::TurnOnBacklight() { + R_ABORT_UNLESS(::lblInitialize()); + ::lblSwitchBacklightOn(0); + ::lblExit(); + } + + Result BacklightControlTask::Run() { + TurnOnBacklight(); + R_SUCCEED(); + } + + } + + ITask *GetShowFatalTask(const ThrowContext *ctx) { + g_show_fatal_task.Initialize(ctx); + return std::addressof(g_show_fatal_task); + } + + ITask *GetBacklightControlTask(const ThrowContext *ctx) { + g_backlight_control_task.Initialize(ctx); + return std::addressof(g_backlight_control_task); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_screen.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_screen.hpp new file mode 100644 index 00000000..76f9e49c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_screen.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "fatal_task.hpp" + +namespace ams::fatal::srv { + + ITask *GetShowFatalTask(const ThrowContext *ctx); + ITask *GetBacklightControlTask(const ThrowContext *ctx); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_sound.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_sound.cpp new file mode 100644 index 00000000..276c1ade --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_sound.cpp @@ -0,0 +1,95 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "fatal_task_sound.hpp" + +namespace ams::fatal::srv { + + namespace { + + /* Task definition. */ + class StopSoundTask : public ITaskWithDefaultStack { + private: + void StopSound(); + public: + virtual Result Run() override; + virtual const char *GetName() const override { + return "SoundTask"; + } + }; + + /* Task global. */ + StopSoundTask g_stop_sound_task; + + /* Task implementation. */ + void StopSoundTask::StopSound() { + /* Talk to the ALC5639 over I2C, and disable audio output. */ + { + I2cSession audio; + if (R_SUCCEEDED(i2cOpenSession(std::addressof(audio), I2cDevice_Alc5639))) { + ON_SCOPE_EXIT { i2csessionClose(std::addressof(audio)); }; + + struct { + u8 reg; + u16 val; + } __attribute__((packed)) cmd; + static_assert(sizeof(cmd) == 3, "I2C command definition!"); + + cmd.reg = 0x01; + cmd.val = 0xC8C8; + i2csessionSendAuto(std::addressof(audio), std::addressof(cmd), sizeof(cmd), I2cTransactionOption_All); + + cmd.reg = 0x02; + cmd.val = 0xC8C8; + i2csessionSendAuto(std::addressof(audio), std::addressof(cmd), sizeof(cmd), I2cTransactionOption_All); + + for (u8 reg = 97; reg <= 102; reg++) { + cmd.reg = reg; + cmd.val = 0; + i2csessionSendAuto(std::addressof(audio), std::addressof(cmd), sizeof(cmd), I2cTransactionOption_All); + } + } + } + + /* Talk to the ALC5639 over GPIO, and disable audio output */ + { + gpio::GpioPadSession audio; + if (R_SUCCEEDED(gpio::OpenSession(std::addressof(audio), gpio::DeviceCode_CodecLdoEnTemp))) { + ON_SCOPE_EXIT { gpio::CloseSession(std::addressof(audio)); }; + + /* Set direction output, sleep 200 ms so it can take effect. */ + gpio::SetDirection(std::addressof(audio), gpio::Direction_Output); + os::SleepThread(TimeSpan::FromMilliSeconds(200)); + + /* Pull audio codec low. */ + gpio::SetValue(std::addressof(audio), gpio::GpioValue_Low); + } + } + } + + Result StopSoundTask::Run() { + StopSound(); + R_SUCCEED(); + } + + } + + ITask *GetStopSoundTask(const ThrowContext *ctx) { + g_stop_sound_task.Initialize(ctx); + return std::addressof(g_stop_sound_task); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_sound.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_sound.hpp new file mode 100644 index 00000000..c1ba5c32 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/fatal_task_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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "fatal_task.hpp" + +namespace ams::fatal::srv { + + ITask *GetStopSoundTask(const ThrowContext *ctx); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/stb_truetype.h b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/stb_truetype.h new file mode 100644 index 00000000..ca82fb8c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/source/stb_truetype.h @@ -0,0 +1,4888 @@ +// stb_truetype.h - v1.22 - public domain +// authored from 2009-2019 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen +// Cass Everitt Martins Mozeiko +// stoiko (Haemimont Games) Cap Petschulat +// Brian Hook Omar Cornut +// Walter van Niftrik github:aloucks +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. github:oyvindjam +// Brian Costabile github:vassvik +// Ken Voskuil (kaesve) Ryan Griege +// +// VERSION HISTORY +// +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to <current_point, baseline>. I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// <current_point+SF*x0, baseline+SF*y0> to <current_point+SF*x1,baseline+SF*y1). +// +// Advancing for the next character: +// Call GlyphHMetrics, and compute 'current_point += SF * advance'. +// +// +// ADVANCED USAGE +// +// Quality: +// +// - Use the functions with Subpixel at the end to allow your characters +// to have subpixel positioning. Since the font is anti-aliased, not +// hinted, this is very import for quality. (This is not possible with +// baked fonts.) +// +// - Kerning is now supported, and if you're supporting subpixel rendering +// then kerning is worth using to give your text a polished look. +// +// Performance: +// +// - Convert Unicode codepoints to glyph indexes and operate on the glyphs; +// if you don't do this, stb_truetype is forced to do the conversion on +// every call. +// +// - There are a lot of memory allocations. We should modify it to take +// a temp buffer and allocate from the temp buffer (without freeing), +// should help performance a lot. +// +// NOTES +// +// The system uses the raw data found in the .ttf file without changing it +// and without building auxiliary data structures. This is a bit inefficient +// on little-endian systems (the data is big-endian), but assuming you're +// caching the bitmaps or glyph shapes this shouldn't be a big deal. +// +// It appears to be very hard to programmatically determine what font a +// given file is in a general way. I provide an API for this, but I don't +// recommend it. +// +// +// PERFORMANCE MEASUREMENTS FOR 1.06: +// +// 32-bit 64-bit +// Previous release: 8.83 s 7.68 s +// Pool allocations: 7.72 s 6.34 s +// Inline sort : 6.54 s 5.65 s +// New rasterizer : 5.63 s 5.00 s + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// SAMPLE PROGRAMS +//// +// +// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless +// +#if 0 +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +unsigned char ttf_buffer[1<<20]; +unsigned char temp_bitmap[512*512]; + +stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs +GLuint ftex; + +void my_stbtt_initfont(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(ttf_buffer,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! + // can free ttf_buffer at this point + glGenTextures(1, &ftex); + glBindTexture(GL_TEXTURE_2D, ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + // can free temp_bitmap at this point + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void my_stbtt_print(float x, float y, char *text) +{ + // assume orthographic projection with units = screen pixels, origin at top left + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, ftex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include <stdio.h> +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include <math.h> + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include <math.h> + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include <math.h> + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include <math.h> + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include <math.h> + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include <stdlib.h> + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include <assert.h> + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include <string.h> + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include <string.h> + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static __attribute__((unused)) +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. +// Returns 0 if the character codepoint is not defined in the font. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of contours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch(coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + } break; + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch(classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + + classDefTable = classDef1ValueArray + 2 * glyphCount; + } break; + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + + classDefTable = classRangeRecords + 6 * classRangeCount; + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i<lookupCount; ++i) { + stbtt_uint16 lookupOffset = ttUSHORT(lookupList + 2 + 2 * i); + stbtt_uint8 *lookupTable = lookupList + lookupOffset; + + stbtt_uint16 lookupType = ttUSHORT(lookupTable); + stbtt_uint16 subTableCount = ttUSHORT(lookupTable + 4); + stbtt_uint8 *subTableOffsets = lookupTable + 6; + switch(lookupType) { + case 2: { // Pair Adjustment Positioning Subtable + stbtt_int32 sti; + for (sti=0; sti<subTableCount; sti++) { + stbtt_uint16 subtableOffset = ttUSHORT(subTableOffsets + 2 * sti); + stbtt_uint8 *table = lookupTable + subtableOffset; + stbtt_uint16 posFormat = ttUSHORT(table); + stbtt_uint16 coverageOffset = ttUSHORT(table + 2); + stbtt_int32 coverageIndex = stbtt__GetCoverageIndex(table + coverageOffset, glyph1); + if (coverageIndex == -1) continue; + + switch (posFormat) { + case 1: { + stbtt_int32 l, r, m; + int straw, needle; + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + stbtt_int32 valueRecordPairSizeInBytes = 2; + stbtt_uint16 pairSetCount = ttUSHORT(table + 8); + stbtt_uint16 pairPosOffset = ttUSHORT(table + 10 + 2 * coverageIndex); + stbtt_uint8 *pairValueTable = table + pairPosOffset; + stbtt_uint16 pairValueCount = ttUSHORT(pairValueTable); + stbtt_uint8 *pairValueArray = pairValueTable + 2; + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + STBTT_assert(coverageIndex < pairSetCount); + STBTT__NOTUSED(pairSetCount); + + needle=glyph2; + r=pairValueCount-1; + l=0; + + // Binary search. + while (l <= r) { + stbtt_uint16 secondGlyph; + stbtt_uint8 *pairValue; + m = (l + r) >> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } break; + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + STBTT_assert(glyph1class < class1Count); + STBTT_assert(glyph2class < class2Count); + + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { + stbtt_uint8 *class1Records = table + 16; + stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count); + stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + break; + }; + } + } + break; + }; + + default: + // TODO: Implement other stuff. + break; + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) { + STBTT_assert(x1 <= x+1); + } else if (x0 == x+1) { + STBTT_assert(x1 >= x); + } else if (x0 <= x) { + STBTT_assert(x1 <= x); + } else if (x0 >= x+1) { + STBTT_assert(x1 >= x+1); + } else { + STBTT_assert(x1 >= x && x1 <= x+1); + } + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && mid<n: 0>n => n; 0<n => 0 */ + /* 0<mid && mid>n: 0>n => 0; 0<n => n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + orig[1] = y; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + c*x^2 + b*x + a = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +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. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +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 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/stratosphere/fatal/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/system_module.mk new file mode 100644 index 00000000..d6de42ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fatal/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := nsp + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fs/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/fs/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fs/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)/system_module.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)/system_module.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/stratosphere/fs/fs.json b/Source/Atmosphere-MTC-Unlock/stratosphere/fs/fs.json new file mode 100644 index 00000000..0cc6f0dd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fs/fs.json @@ -0,0 +1,103 @@ +{ + "name": "FS", + "title_id": "0x0100000000000000", + "main_thread_stack_size": "0xC000", + "main_thread_priority": 45, + "default_cpu_id": 3, + "process_category": 1, + "use_secure_memory": true, + "immortal": true, + "kernel_capabilities": [{ + "type": "map_page", + "value": "0x60006000" + }, { + "type": "map", + "value": { + "address": "0x700b0000", + "is_ro": false, + "size": "0x00005000", + "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", + "svcMapTransferMemory": "0x51", + "svcUnmapTransferMemory": "0x52", + "svcCreateInterruptEvent": "0x53", + "svcQueryIoMapping": "0x55", + "svcCreateDeviceAddressSpace": "0x56", + "svcAttachDeviceAddressSpace": "0x57", + "svcDetachDeviceAddressSpace": "0x58", + "svcMapDeviceAddressSpaceAligned": "0x5a", + "svcUnmapDeviceAddressSpace": "0x5c", + "svcGetSystemInfo": "0x6f", + "svcCallSecureMonitor": "0x7F" + } + }] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fs/source/fs_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/fs/source/fs_main.cpp new file mode 100644 index 00000000..4456c1f9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fs/source/fs_main.cpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams { + + namespace init { + + void InitializeSystemModule() { + /* TODO? */ + + /* Initialize services we need. */ + R_ABORT_UNLESS(sm::Initialize()); + + /* TODO? */ + + /* Verify that we can sanely execute. */ + ams::CheckApiVersion(); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void NORETURN Exit(int rc) { + AMS_UNUSED(rc); + AMS_ABORT("Exit called by immortal process"); + } + + void Main() { + /* Initialize fssystem library for FS. */ + fssystem::InitializeForFileSystemProxy(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/fs/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/fs/system_module.mk new file mode 100644 index 00000000..19c207d9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/fs/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := kip + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/htc/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/htc/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/htc/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)/system_module.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)/system_module.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/stratosphere/htc/htc.json b/Source/Atmosphere-MTC-Unlock/stratosphere/htc/htc.json new file mode 100644 index 00000000..9e246c36 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/htc/htc.json @@ -0,0 +1,107 @@ +{ + "name": "htc", + "title_id": "0x010000000000b240", + "title_id_range_min": "0x010000000000b240", + "title_id_range_max": "0x010000000000b240", + "main_thread_stack_size": "0x00004000", + "main_thread_priority": 38, + "default_cpu_id": 3, + "process_category": 0, + "is_retail": true, + "pool_partition": 2, + "is_64_bit": true, + "address_space_type": 3, + "disable_device_address_space_merge": true, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_access": ["pcie", "psc:m", "set:cal", "set:fd", "set:sys", "usb:ds", "fsp-srv", "bsd:s"], + "service_host": ["file_io", "htc", "htcs"], + "kernel_capabilities": [{ + "type": "kernel_flags", + "value": { + "highest_thread_priority": 63, + "lowest_thread_priority": 20, + "lowest_cpu_id": 3, + "highest_cpu_id": 3 + } + }, { + "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", + "svcSynchronizePreemptionState": "0x36", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcMapTransferMemory": "0x51", + "svcUnmapTransferMemory": "0x52", + "svcQueryIoMapping": "0x55", + "svcCallSecureMonitor": "0x7f" + } + }, { + "type": "map", + "value": { + "address": "0x12000000", + "is_ro": false, + "size": "0x04010000", + "is_io": true + } + }, { + "type": "irq_pair", + "value": [130, null] + }, { + "type": "irq_pair", + "value": [131, 132] + }, { + "type": "min_kernel_version", + "value": "0x0030" + }, { + "type": "handle_table_size", + "value": 0 + }] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/htc/source/htc_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/htc/source/htc_main.cpp new file mode 100644 index 00000000..5477bc11 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/htc/source/htc_main.cpp @@ -0,0 +1,231 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams { + + namespace htc { + + namespace { + + alignas(0x40) constinit u8 g_heap_buffer[4_KB]; + lmem::HeapHandle g_heap_handle; + + void *Allocate(size_t size) { + return lmem::AllocateFromExpHeap(g_heap_handle, size); + } + + void Deallocate(void *p, size_t size) { + AMS_UNUSED(size); + + return lmem::FreeToExpHeap(g_heap_handle, p); + } + + void InitializeHeap() { + /* Setup server allocator. */ + g_heap_handle = lmem::CreateExpHeap(g_heap_buffer, sizeof(g_heap_buffer), lmem::CreateOption_ThreadSafe); + } + + } + + namespace { + + constexpr htclow::impl::DriverType DefaultHtclowDriverType = htclow::impl::DriverType::Usb; + + constexpr inline size_t NumHtcsIpcThreads = 8; + + alignas(os::ThreadStackAlignment) u8 g_htc_ipc_thread_stack[4_KB]; + alignas(os::ThreadStackAlignment) u8 g_htcfs_ipc_thread_stack[4_KB]; + alignas(os::ThreadStackAlignment) u8 g_htcs_ipc_thread_stack[NumHtcsIpcThreads][4_KB]; + + htclow::impl::DriverType GetHtclowDriverType() { + /* Get the transport type. */ + char transport[0x10]; + if (settings::fwdbg::GetSettingsItemValue(transport, sizeof(transport), "bsp0", "tm_transport") == 0) { + return DefaultHtclowDriverType; + } + + /* Make the transport type case insensitive. */ + transport[util::size(transport) - 1] = '\x00'; + for (size_t i = 0; i < util::size(transport); ++i) { + transport[i] = std::tolower(static_cast<unsigned char>(transport[i])); + } + + /* Select the transport. */ + if (std::strstr(transport, "usb")) { + return htclow::impl::DriverType::Usb; + } else if (std::strstr(transport, "hb")) { + return htclow::impl::DriverType::HostBridge; + } else if (std::strstr(transport, "plainchannel")) { + return htclow::impl::DriverType::PlainChannel; + } else if (std::strstr(transport, "socket")) { + /* NOTE: Nintendo does not actually allow socket driver to be selected. */ + /* Should we disallow this? Undesirable, because people will want to use docked tma. */ + + /* TODO: Right now, SocketDriver causes a hang on init. This is because */ + /* the socket driver requires wi-fi, but wi-fi can't happen until the system is fully up. */ + /* The system can't initialize fully until we acknowledge power state events. */ + /* We can't acknowledge power state events until our driver is online. */ + /* Resolving this chicken-and-egg problem without compromising design will require thought. */ + + //return htclow::impl::DriverType::Socket; + return DefaultHtclowDriverType; + } else { + return DefaultHtclowDriverType; + } + } + + void HtcIpcThreadFunction(void *) { + htc::server::LoopHtcmiscServer(); + } + + void HtcfsIpcThreadFunction(void *) { + htcfs::LoopHipcServer(); + } + + void HtcsIpcThreadFunction(void *) { + htcs::server::LoopHipcServer(); + } + + } + + namespace server { + + void InitializePowerStateMonitor(htclow::impl::DriverType driver_type, htclow::HtclowManager *htclow_manager); + void FinalizePowerStateMonitor(); + + void LoopMonitorPowerState(); + + } + + } + + namespace htclow::driver { + + void InitializeSocketApiForSocketDriver(); + + } + + namespace init { + + void InitializeSystemModule() { + /* Initialize heap. */ + htc::InitializeHeap(); + + /* Initialize our connection to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + + /* Initialize fs. */ + fs::InitializeForSystem(); + fs::SetAllocator(htc::Allocate, htc::Deallocate); + fs::SetEnabledAutoAbort(false); + + /* Initialize other services we need. */ + R_ABORT_UNLESS(setsysInitialize()); + R_ABORT_UNLESS(setcalInitialize()); + R_ABORT_UNLESS(pscmInitialize()); + + /* Mount the SD card. */ + R_ABORT_UNLESS(fs::MountSdCard("sdmc")); + + /* Verify that we can sanely execute. */ + ams::CheckApiVersion(); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void Main() { + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(htc, Main)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(htc, Main)); + + /* Get and set the default driver type. */ + const auto driver_type = htc::GetHtclowDriverType(); + htclow::HtclowManagerHolder::SetDefaultDriver(driver_type); + + /* If necessary, initialize the socket driver. */ + if (driver_type == htclow::impl::DriverType::Socket) { + htclow::driver::InitializeSocketApiForSocketDriver(); + } + + /* Initialize the htclow manager. */ + htclow::HtclowManagerHolder::AddReference(); + ON_SCOPE_EXIT { htclow::HtclowManagerHolder::Release(); }; + + /* Get the htclow manager. */ + auto *htclow_manager = htclow::HtclowManagerHolder::GetHtclowManager(); + + /* Initialize the htc misc server. */ + htc::server::InitializeHtcmiscServer(htclow_manager); + + /* Create the htc misc ipc thread. */ + os::ThreadType htc_ipc_thread; + os::CreateThread(std::addressof(htc_ipc_thread), htc::HtcIpcThreadFunction, nullptr, htc::g_htc_ipc_thread_stack, sizeof(htc::g_htc_ipc_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtcIpc)); + os::SetThreadNamePointer(std::addressof(htc_ipc_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtcIpc)); + + /* Initialize the htcfs server. */ + htcfs::Initialize(htclow_manager); + htcfs::RegisterHipcServer(); + + /* Create the htcfs ipc thread. */ + os::ThreadType htcfs_ipc_thread; + os::CreateThread(std::addressof(htcfs_ipc_thread), htc::HtcfsIpcThreadFunction, nullptr, htc::g_htcfs_ipc_thread_stack, sizeof(htc::g_htcfs_ipc_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtcfsIpc)); + os::SetThreadNamePointer(std::addressof(htcfs_ipc_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtcfsIpc)); + + /* Initialize the htcs server. */ + htcs::server::Initialize(); + htcs::server::RegisterHipcServer(); + + /* Create the htcs ipc threads. */ + os::ThreadType htcs_ipc_threads[htc::NumHtcsIpcThreads]; + for (size_t i = 0; i < htc::NumHtcsIpcThreads; ++i) { + os::CreateThread(std::addressof(htcs_ipc_threads[i]), htc::HtcsIpcThreadFunction, nullptr, htc::g_htcs_ipc_thread_stack[i], sizeof(htc::g_htcs_ipc_thread_stack[i]), AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtcsIpc)); + os::SetThreadNamePointer(std::addressof(htcs_ipc_threads[i]), AMS_GET_SYSTEM_THREAD_NAME(htc, HtcsIpc)); + } + + /* Initialize psc. */ + htc::server::InitializePowerStateMonitor(driver_type, htclow_manager); + + /* Start all threads. */ + os::StartThread(std::addressof(htc_ipc_thread)); + os::StartThread(std::addressof(htcfs_ipc_thread)); + for (size_t i = 0; i < htc::NumHtcsIpcThreads; ++i) { + os::StartThread(std::addressof(htcs_ipc_threads[i])); + } + + /* Loop psc monitor. */ + htc::server::LoopMonitorPowerState(); + + /* Destroy all threads. */ + for (size_t i = 0; i < htc::NumHtcsIpcThreads; ++i) { + os::WaitThread(std::addressof(htcs_ipc_threads[i])); + os::DestroyThread(std::addressof(htcs_ipc_threads[i])); + } + + os::WaitThread(std::addressof(htcfs_ipc_thread)); + os::DestroyThread(std::addressof(htcfs_ipc_thread)); + os::WaitThread(std::addressof(htc_ipc_thread)); + os::DestroyThread(std::addressof(htc_ipc_thread)); + + /* Finalize psc monitor. */ + htc::server::FinalizePowerStateMonitor(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/htc/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/htc/system_module.mk new file mode 100644 index 00000000..d6de42ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/htc/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := nsp + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/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)/system_module.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)/system_module.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/stratosphere/jpegdec/jpegdec.json b/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/jpegdec.json new file mode 100644 index 00000000..9968c6b2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/jpegdec.json @@ -0,0 +1,99 @@ +{ + "name": "jpegdec", + "title_id": "0x010000000000003c", + "title_id_range_min": "0x010000000000003c", + "title_id_range_max": "0x010000000000003c", + "main_thread_stack_size": "0x00004000", + "main_thread_priority": 49, + "default_cpu_id": 3, + "process_category": 0, + "is_retail": true, + "pool_partition": 2, + "is_64_bit": true, + "address_space_type": 3, + "disable_device_address_space_merge": true, + "filesystem_access": { + "permissions": "0x0" + }, + "service_access": ["fatal:u", "lm"], + "service_host": ["caps:dc"], + "kernel_capabilities": [{ + "type": "kernel_flags", + "value": { + "highest_thread_priority": 63, + "lowest_thread_priority": 24, + "lowest_cpu_id": 3, + "highest_cpu_id": 3 + } + }, { + "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", + "svcSynchronizePreemptionState": "0x36", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcCreateInterruptEvent": "0x53", + "svcReadWriteRegister": "0x4E", + "svcQueryIoMapping": "0x55", + "svcCreateDeviceAddressSpace": "0x56", + "svcAttachDeviceAddressSpace": "0x57", + "svcDetachDeviceAddressSpace": "0x58", + "svcMapDeviceAddressSpaceAligned": "0x5a", + "svcUnmapDeviceAddressSpace": "0x5c", + "svcGetSystemInfo": "0x6f", + "svcCallSecureMonitor": "0x7F" + } + }, { + "type": "min_kernel_version", + "value": "0x0030" + }, { + "type": "handle_table_size", + "value": 16 + }] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/source/jpegdec_environment.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/source/jpegdec_environment.cpp new file mode 100644 index 00000000..6c5a67de --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/source/jpegdec_environment.cpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +extern "C" int __wrap_sprintf (char *str, const char *fmt, ...) { + AMS_UNUSED(fmt); + + str[0] = '\x00'; + return 0; +} + +extern "C" int __wrap_fprintf(void *stream, const char *fmt, ...) { + AMS_UNUSED(stream, fmt); + return 0; +} + +extern "C" void *__wrap_getenv(const char *) { + return nullptr; +} + +extern "C" void __wrap_sscanf(const char *str, const char *format, ...) { + AMS_UNUSED(str, format); + AMS_ABORT("sscanf was called\n"); +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/source/jpegdec_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/source/jpegdec_main.cpp new file mode 100644 index 00000000..712b9823 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/source/jpegdec_main.cpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "jpegdec_memory_management.hpp" + +namespace ams { + + namespace init { + + void InitializeSystemModule() { + /* Initialize heap. */ + jpegdec::InitializeJpegHeap(); + + /* Initialize our connection to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + + /* Verify that we can sanely execute. */ + ams::CheckApiVersion(); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void Main() { + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(jpegdec, Main)); + + /* Official jpegdec changes its thread priority to 21 in main. */ + /* This is because older versions of the sysmodule had priority 20 in npdm. */ + os::ChangeThreadPriority(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_PRIORITY(jpegdec, Main)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(jpegdec, Main)); + + /* Initialize the capsrv library. */ + R_ABORT_UNLESS(capsrv::server::InitializeForDecoderServer()); + + /* Service the decoder server. */ + capsrv::server::DecoderControlServerThreadFunction(nullptr); + + /* Finalize the capsrv library. */ + capsrv::server::FinalizeForDecoderServer(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/source/jpegdec_memory_management.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/source/jpegdec_memory_management.cpp new file mode 100644 index 00000000..965db07e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/source/jpegdec_memory_management.cpp @@ -0,0 +1,55 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "jpegdec_memory_management.hpp" + +namespace ams::jpegdec { + + namespace { + + /* TODO: Update libjpeg-turbo to include Nintendo's changes (support for work buffer, rather than malloc) */ + constexpr size_t JpegHeapSize = 96_KB; + alignas(0x10) constinit u8 g_jpeg_heap_memory[JpegHeapSize]; + + constinit lmem::HeapHandle g_jpeg_heap_handle; + + } + + void InitializeJpegHeap() { + g_jpeg_heap_handle = lmem::CreateExpHeap(g_jpeg_heap_memory, sizeof(g_jpeg_heap_memory), lmem::CreateOption_None); + } + +} + +extern "C" void *__wrap_jpeg_get_small(void *cinfo, size_t size) { + AMS_UNUSED(cinfo); + return ::ams::lmem::AllocateFromExpHeap(::ams::jpegdec::g_jpeg_heap_handle, size); +} + +extern "C" void __wrap_jpeg_free_small(void *cinfo, void *ptr, size_t size) { + AMS_UNUSED(cinfo, size); + return ::ams::lmem::FreeToExpHeap(::ams::jpegdec::g_jpeg_heap_handle, ptr); +} + +extern "C" void *__wrap_jpeg_get_large(void *cinfo, size_t size) { + AMS_UNUSED(cinfo); + return ::ams::lmem::AllocateFromExpHeap(::ams::jpegdec::g_jpeg_heap_handle, size); +} + +extern "C" void __wrap_jpeg_free_large(void *cinfo, void *ptr, size_t size) { + AMS_UNUSED(cinfo, size); + return ::ams::lmem::FreeToExpHeap(::ams::jpegdec::g_jpeg_heap_handle, ptr); +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/source/jpegdec_memory_management.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/source/jpegdec_memory_management.hpp new file mode 100644 index 00000000..4b24fb1c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/source/jpegdec_memory_management.hpp @@ -0,0 +1,22 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams::jpegdec { + + void InitializeJpegHeap(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/system_module.mk new file mode 100644 index 00000000..5c301390 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/jpegdec/system_module.mk @@ -0,0 +1,148 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := nsp + +#--------------------------------------------------------------------------------- +# jpegdec uses libjpeg-turbo. +#--------------------------------------------------------------------------------- +LIBS += -ljpeg + +#--------------------------------------------------------------------------------- +# jpegdec overrides libjpeg-turbo's memory allocation routines. +#--------------------------------------------------------------------------------- +CXXWRAPS += -Wl,--wrap,jpeg_get_small +CXXWRAPS += -Wl,--wrap,jpeg_get_large +CXXWRAPS += -Wl,--wrap,jpeg_free_small +CXXWRAPS += -Wl,--wrap,jpeg_free_large +CXXWRAPS += -Wl,--wrap,sprintf +CXXWRAPS += -Wl,--wrap,fprintf +CXXWRAPS += -Wl,--wrap,getenv +CXXWRAPS += -Wl,--wrap,sscanf + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/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)/system_module.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)/system_module.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/stratosphere/loader/loader.json b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/loader.json new file mode 100644 index 00000000..49f61caf --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/loader.json @@ -0,0 +1,80 @@ +{ + "name": "Loader", + "title_id": "0x0100000000000001", + "main_thread_stack_size": "0x4000", + "main_thread_priority": 49, + "default_cpu_id": 3, + "process_category": 1, + "use_secure_memory": true, + "immortal": true, + "kernel_capabilities": [ + { + "type": "handle_table_size", + "value": 128 + }, + { + "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", + "svcSynchronizePreemptionState" : "0x36", + "svcCreateSession" : "0x40", + "svcAcceptSession" : "0x41", + "svcReplyAndReceiveLight" : "0x42", + "svcReplyAndReceive" : "0x43", + "svcReplyAndReceiveWithUserBuffer" : "0x44", + "svcCreateEvent" : "0x45", + "svcReadWriteRegister" : "0x4E", + "svcQueryIoMapping" : "0x55", + "svcSetProcessMemoryPermission" : "0x73", + "svcMapProcessMemory" : "0x74", + "svcUnmapProcessMemory" : "0x75", + "svcMapProcessCodeMemory" : "0x77", + "svcUnmapProcessCodeMemory" : "0x78", + "svcCreateProcess" : "0x79", + "svcCallSecureMonitor" : "0x7F" + } + } + ] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_argument_store.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_argument_store.cpp new file mode 100644 index 00000000..8bdaf62f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_argument_store.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ldr_argument_store.hpp" + +namespace ams::ldr { + + int ArgumentStore::FindIndex(Entry *map, ncm::ProgramId program_id) { + for (auto i = 0; i < ArgumentMapCount; ++i) { + if (map[i].program_id == program_id) { + return i; + } + } + + return -1; + } + + const ArgumentStore::Entry *ArgumentStore::Get(ncm::ProgramId program_id) { + /* Find the matching arguments entry. */ + Entry *argument = nullptr; + if (auto idx = FindIndex(m_argument_map, program_id); idx >= 0) { + argument = m_argument_map + idx; + } + + return argument; + } + + Result ArgumentStore::Set(ncm::ProgramId program_id, const void *argument, size_t size) { + /* Check that the argument size is within bounds. */ + R_UNLESS(size < ArgumentBufferSize, ldr::ResultArgumentOverflow()); + + /* Find either a matching arguments entry, or an empty entry. */ + auto idx = FindIndex(m_argument_map, program_id); + if (idx < 0) { + idx = FindIndex(m_argument_map, ncm::InvalidProgramId); + } + R_UNLESS(idx >= 0, ldr::ResultArgumentCountOverflow()); + + /* Set the arguments in the entry. */ + auto &entry = m_argument_map[idx]; + + entry.program_id = program_id; + entry.argument_size = size; + std::memcpy(entry.argument, argument, entry.argument_size); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_argument_store.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_argument_store.hpp new file mode 100644 index 00000000..cc7bee4b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_argument_store.hpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ldr { + + class ArgumentStore { + public: + static constexpr size_t ArgumentBufferSize = 32_KB; + + struct Entry { + ncm::ProgramId program_id; + size_t argument_size; + u8 argument[ArgumentBufferSize]; + }; + private: + static constexpr int ArgumentMapCount = 10; + private: + Entry m_argument_map[ArgumentMapCount]; + public: + constexpr ArgumentStore() : m_argument_map{} { + this->Flush(); + } + public: + const Entry *Get(ncm::ProgramId program_id); + Result Set(ncm::ProgramId program_id, const void *argument, size_t size); + + constexpr Result Flush() { + for (auto &entry : m_argument_map) { + entry.program_id = ncm::InvalidProgramId; + } + R_SUCCEED(); + } + private: + static int FindIndex(Entry *map, ncm::ProgramId program_id); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_capabilities.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_capabilities.cpp new file mode 100644 index 00000000..b3bc25e6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_capabilities.cpp @@ -0,0 +1,453 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ldr_capabilities.hpp" + +namespace ams::ldr { + + namespace { + + /* Types. */ + enum class CapabilityId { + KernelFlags = 3, + SyscallMask = 4, + MapRange = 6, + MapPage = 7, + MapRegion = 10, + InterruptPair = 11, + ApplicationType = 13, + KernelVersion = 14, + HandleTable = 15, + DebugFlags = 16, + + Empty = 32, + }; + + template<size_t Index, size_t Count, typename T = u32> + using CapabilityField = util::BitPack32::Field<Index, Count, T>; + + #define DEFINE_CAPABILITY_FIELD(name, prev, ...) \ + using name = CapabilityField<prev::Next, __VA_ARGS__>; \ + constexpr ALWAYS_INLINE typename name::Type Get##name() const { return this->Get<name>(); } + + constexpr ALWAYS_INLINE CapabilityId GetCapabilityId(util::BitPack32 cap) { + return static_cast<CapabilityId>(util::CountTrailingZeros<u32>(~cap.value)); + } + + constexpr inline util::BitPack32 EmptyCapability = {~u32{}}; + static_assert(GetCapabilityId(EmptyCapability) == CapabilityId::Empty); + +#define CAPABILITY_CLASS_NAME(id) Capability##id + +#define DEFINE_CAPABILITY_CLASS(id, member_functions) \ + class CAPABILITY_CLASS_NAME(id) { \ + public: \ + static constexpr CapabilityId Id = CapabilityId::id; \ + using IdBits = CapabilityField<0, static_cast<size_t>(Id) + 1>; \ + static constexpr u32 IdBitsValue = (static_cast<u32>(1) << static_cast<size_t>(Id)) - 1; \ + private: \ + util::BitPack32 m_value; \ + private: \ + template<typename FieldType> \ + constexpr ALWAYS_INLINE typename FieldType::Type Get() const { return m_value.Get<FieldType>(); } \ + template<typename FieldType> \ + constexpr ALWAYS_INLINE void Set(typename FieldType::Type fv) { m_value.Set<FieldType>(fv); } \ + constexpr ALWAYS_INLINE u32 GetValue() const { return m_value.value; } \ + public: \ + constexpr ALWAYS_INLINE CAPABILITY_CLASS_NAME(id)(util::BitPack32 v) : m_value{v} { /* ... */ } \ + \ + static constexpr CAPABILITY_CLASS_NAME(id) Decode(util::BitPack32 v) { return CAPABILITY_CLASS_NAME(id)(v); } \ + \ + member_functions \ + }; \ + static_assert(std::is_trivially_destructible<CAPABILITY_CLASS_NAME(id)>::value) + + /* Class definitions. */ + DEFINE_CAPABILITY_CLASS(KernelFlags, + DEFINE_CAPABILITY_FIELD(MaximumThreadPriority, IdBits, 6); + DEFINE_CAPABILITY_FIELD(MinimumThreadPriority, MaximumThreadPriority, 6); + DEFINE_CAPABILITY_FIELD(MinimumCoreId, MinimumThreadPriority, 8); + DEFINE_CAPABILITY_FIELD(MaximumCoreId, MinimumCoreId, 8); + + bool IsValid(const util::BitPack32 *kac, size_t kac_count) const { + for (size_t i = 0; i < kac_count; i++) { + if (GetCapabilityId(kac[i]) == Id) { + const auto restriction = Decode(kac[i]); + + if (this->GetMinimumThreadPriority() < restriction.GetMinimumThreadPriority() || + this->GetMaximumThreadPriority() > restriction.GetMaximumThreadPriority() || + this->GetMinimumThreadPriority() > this->GetMaximumThreadPriority()) { + return false; + } + + if (this->GetMinimumCoreId() < restriction.GetMinimumCoreId() || + this->GetMaximumCoreId() > restriction.GetMaximumCoreId() || + this->GetMinimumCoreId() > this->GetMaximumCoreId()) { + return false; + } + + return true; + } + } + return false; + } + ); + + DEFINE_CAPABILITY_CLASS(SyscallMask, + DEFINE_CAPABILITY_FIELD(Mask, IdBits, 24); + DEFINE_CAPABILITY_FIELD(Index, Mask, 3); + + bool IsValid(const util::BitPack32 *kac, size_t kac_count) const { + for (size_t i = 0; i < kac_count; i++) { + if (GetCapabilityId(kac[i]) == Id) { + const auto restriction = Decode(kac[i]); + + if (this->GetIndex() == restriction.GetIndex() && this->GetMask() == restriction.GetMask()) { + return true; + } + } + } + return false; + } + ); + + DEFINE_CAPABILITY_CLASS(MapRange, + DEFINE_CAPABILITY_FIELD(AddressSize, IdBits, 24); + DEFINE_CAPABILITY_FIELD(Flag, AddressSize, 1, bool); + static constexpr size_t SizeMax = 0x100000; + + bool IsValid(const util::BitPack32 next_cap, const util::BitPack32 *kac, size_t kac_count) const { + if (GetCapabilityId(next_cap) != Id) { + return false; + } + + const auto next = Decode(next_cap); + const u32 start = this->GetAddressSize(); + const u32 size = next.GetAddressSize(); + const u32 end = start + size; + if (size >= SizeMax) { + return false; + } + + for (size_t i = 0; i < kac_count; i++) { + if (GetCapabilityId(kac[i]) == Id) { + const auto restriction = Decode(kac[i++]); + if (i >= kac_count || GetCapabilityId(kac[i]) != Id) { + return false; + } + const auto restriction_next = Decode(kac[i]); + const u32 restriction_start = restriction.GetAddressSize(); + const u32 restriction_size = restriction_next.GetAddressSize(); + const u32 restriction_end = restriction_start + restriction_size; + + if (restriction_size >= SizeMax) { + continue; + } + + if (this->GetFlag() == restriction.GetFlag() && next.GetFlag() == restriction_next.GetFlag()) { + if (restriction_start <= start && start <= restriction_end && end <= restriction_end) { + return true; + } + } + } + } + return false; + } + ); + + DEFINE_CAPABILITY_CLASS(MapPage, + DEFINE_CAPABILITY_FIELD(Address, IdBits, 24); + + bool IsValid(const util::BitPack32 *kac, size_t kac_count) const { + for (size_t i = 0; i < kac_count; i++) { + if (GetCapabilityId(kac[i]) == Id) { + const auto restriction = Decode(kac[i]); + + if (this->GetValue() == restriction.GetValue()) { + return true; + } + } + } + return false; + } + ); + + enum class MemoryRegionType : u32 { + NoMapping = 0, + KernelTraceBuffer = 1, + OnMemoryBootImage = 2, + DTB = 3, + }; + + DEFINE_CAPABILITY_CLASS(MapRegion, + DEFINE_CAPABILITY_FIELD(Region0, IdBits, 6, MemoryRegionType); + DEFINE_CAPABILITY_FIELD(ReadOnly0, Region0, 1, bool); + DEFINE_CAPABILITY_FIELD(Region1, ReadOnly0, 6, MemoryRegionType); + DEFINE_CAPABILITY_FIELD(ReadOnly1, Region1, 1, bool); + DEFINE_CAPABILITY_FIELD(Region2, ReadOnly1, 6, MemoryRegionType); + DEFINE_CAPABILITY_FIELD(ReadOnly2, Region2, 1, bool); + + static bool IsValidRegionType(const util::BitPack32 *kac, size_t kac_count, MemoryRegionType region_type, bool is_read_only) { + if (region_type != MemoryRegionType::NoMapping) { + for (size_t i = 0; i < kac_count; i++) { + if (GetCapabilityId(kac[i]) == Id) { + const auto restriction = Decode(kac[i]); + + if ((restriction.GetRegion0() == region_type && (is_read_only || !restriction.GetReadOnly0())) || + (restriction.GetRegion1() == region_type && (is_read_only || !restriction.GetReadOnly1())) || + (restriction.GetRegion2() == region_type && (is_read_only || !restriction.GetReadOnly2()))) + { + return true; + } + } + } + + return false; + } else { + return true; + } + } + + bool IsValid(const util::BitPack32 *kac, size_t kac_count) const { + return IsValidRegionType(kac, kac_count, this->GetRegion0(), this->GetReadOnly0()) && + IsValidRegionType(kac, kac_count, this->GetRegion1(), this->GetReadOnly1()) && + IsValidRegionType(kac, kac_count, this->GetRegion2(), this->GetReadOnly2()); + } + ); + + DEFINE_CAPABILITY_CLASS(InterruptPair, + DEFINE_CAPABILITY_FIELD(InterruptId0, IdBits, 10); + DEFINE_CAPABILITY_FIELD(InterruptId1, InterruptId0, 10); + static constexpr u32 EmptyInterruptId = 0x3FF; + + bool IsSingleIdValid(const u32 id, const util::BitPack32 *kac, size_t kac_count) const { + for (size_t i = 0; i < kac_count; i++) { + if (GetCapabilityId(kac[i]) == Id) { + const auto restriction = Decode(kac[i]); + + if (restriction.GetInterruptId0() == EmptyInterruptId && restriction.GetInterruptId1() == EmptyInterruptId) { + return true; + } + + if (restriction.GetInterruptId0() == id || restriction.GetInterruptId1() == id) { + return true; + } + } + } + return false; + } + + bool IsValid(const util::BitPack32 *kac, size_t kac_count) const { + return IsSingleIdValid(this->GetInterruptId0(), kac, kac_count) && IsSingleIdValid(this->GetInterruptId1(), kac, kac_count); + } + ); + + DEFINE_CAPABILITY_CLASS(ApplicationType, + DEFINE_CAPABILITY_FIELD(ApplicationType, IdBits, 3); + + bool IsValid(const util::BitPack32 *kac, size_t kac_count) const { + for (size_t i = 0; i < kac_count; i++) { + if (GetCapabilityId(kac[i]) == Id) { + const auto restriction = Decode(kac[i]); + + return restriction.GetValue() == this->GetValue(); + } + } + return false; + } + + static constexpr util::BitPack32 Encode(u32 app_type) { + util::BitPack32 encoded{IdBitsValue}; + encoded.Set<ApplicationType>(app_type); + return encoded; + } + ); + + DEFINE_CAPABILITY_CLASS(KernelVersion, + DEFINE_CAPABILITY_FIELD(MinorVersion, IdBits, 4); + DEFINE_CAPABILITY_FIELD(MajorVersion, MinorVersion, 13); + + bool IsValid(const util::BitPack32 *kac, size_t kac_count) const { + for (size_t i = 0; i < kac_count; i++) { + if (GetCapabilityId(kac[i]) == Id) { + const auto restriction = Decode(kac[i]); + + return restriction.GetValue() == this->GetValue(); + } + } + return false; + } + ); + + DEFINE_CAPABILITY_CLASS(HandleTable, + DEFINE_CAPABILITY_FIELD(Size, IdBits, 10); + + bool IsValid(const util::BitPack32 *kac, size_t kac_count) const { + for (size_t i = 0; i < kac_count; i++) { + if (GetCapabilityId(kac[i]) == Id) { + const auto restriction = Decode(kac[i]); + + return this->GetSize() <= restriction.GetSize(); + } + } + return false; + } + ); + + DEFINE_CAPABILITY_CLASS(DebugFlags, + DEFINE_CAPABILITY_FIELD(AllowDebug, IdBits, 1, bool); + DEFINE_CAPABILITY_FIELD(ForceDebugProd, AllowDebug, 1, bool); + DEFINE_CAPABILITY_FIELD(ForceDebug, ForceDebugProd, 1, bool); + + bool IsValid(const util::BitPack32 *kac, size_t kac_count) const { + u32 total = 0; + if (this->GetAllowDebug()) { ++total; } + if (this->GetForceDebugProd()) { ++total; } + if (this->GetForceDebug()) { ++total; } + if (total > 1) { + return false; + } + + for (size_t i = 0; i < kac_count; i++) { + if (GetCapabilityId(kac[i]) == Id) { + const auto restriction = Decode(kac[i]); + + return (restriction.GetValue() & this->GetValue()) == this->GetValue(); + } + } + + return false; + } + + static constexpr util::BitPack32 Encode(bool allow_debug, bool force_debug_prod, bool force_debug) { + util::BitPack32 encoded{IdBitsValue}; + encoded.Set<AllowDebug>(allow_debug); + encoded.Set<ForceDebugProd>(force_debug_prod); + encoded.Set<ForceDebug>(force_debug); + return encoded; + } + ); + + } + + /* Capabilities API. */ + Result TestCapability(const util::BitPack32 *kacd, size_t kacd_count, const util::BitPack32 *kac, size_t kac_count) { + for (size_t i = 0; i < kac_count; i++) { + const auto cap = kac[i]; + const auto id = GetCapabilityId(cap); + +#define VALIDATE_CASE(id) \ + case CapabilityId::id: \ + R_UNLESS(Capability##id::Decode(cap).IsValid(kacd, kacd_count), ldr::ResultInvalidCapability##id()); \ + break + switch (id) { + VALIDATE_CASE(KernelFlags); + VALIDATE_CASE(SyscallMask); + VALIDATE_CASE(MapPage); + VALIDATE_CASE(MapRegion); + VALIDATE_CASE(InterruptPair); + VALIDATE_CASE(ApplicationType); + VALIDATE_CASE(KernelVersion); + VALIDATE_CASE(HandleTable); + VALIDATE_CASE(DebugFlags); + case CapabilityId::MapRange: + { + /* Map Range needs extra logic because there it involves two sequential caps. */ + i++; + R_UNLESS(i < kac_count, ldr::ResultInvalidCapabilityMapRange()); + R_UNLESS(CapabilityMapRange::Decode(cap).IsValid(kac[i], kacd, kacd_count), ldr::ResultInvalidCapabilityMapRange()); + } + break; + default: + R_UNLESS(id == CapabilityId::Empty, ldr::ResultUnknownCapability()); + break; + } +#undef VALIDATE_CASE + } + + R_SUCCEED(); + } + + u16 MakeProgramInfoFlag(const util::BitPack32 *kac, size_t count) { + u16 flags = 0; + + for (size_t i = 0; i < count; ++i) { + const auto cap = kac[i]; + + switch (GetCapabilityId(cap)) { + case CapabilityId::ApplicationType: + { + const auto app_type = CapabilityApplicationType::Decode(cap).GetApplicationType() & ProgramInfoFlag_ApplicationTypeMask; + if (app_type != ProgramInfoFlag_InvalidType) { + flags |= app_type; + } + } + break; + case CapabilityId::DebugFlags: + if (CapabilityDebugFlags::Decode(cap).GetAllowDebug()) { + flags |= ProgramInfoFlag_AllowDebug; + } + break; + default: + break; + } + } + + return flags; + } + + void UpdateProgramInfoFlag(u16 flags, util::BitPack32 *kac, size_t count) { + for (size_t i = 0; i < count; ++i) { + const auto cap = kac[i]; + switch (GetCapabilityId(cap)) { + case CapabilityId::ApplicationType: + kac[i] = CapabilityApplicationType::Encode(flags & ProgramInfoFlag_ApplicationTypeMask); + break; + case CapabilityId::DebugFlags: + kac[i] = CapabilityDebugFlags::Encode((flags & ProgramInfoFlag_AllowDebug) != 0, CapabilityDebugFlags::Decode(cap).GetForceDebugProd(), CapabilityDebugFlags::Decode(cap).GetForceDebug()); + break; + default: + break; + } + } + } + + void FixDebugCapabilityForHbl(util::BitPack32 *kac, size_t count) { + for (size_t i = 0; i < count; ++i) { + const auto cap = kac[i]; + switch (GetCapabilityId(cap)) { + case CapabilityId::DebugFlags: + /* 19.0.0+ disallows more than one flag set; we are always DebugMode for kernel, so ForceDebug is the most powerful/flexible flag to set. */ + kac[i] = CapabilityDebugFlags::Encode(false, false, true); + break; + default: + break; + } + } + } + + void PreProcessCapability(util::BitPack32 *kac, size_t count) { + for (size_t i = 0; i < count; ++i) { + const auto cap = kac[i]; + switch (GetCapabilityId(cap)) { + /* NOTE: Currently, there is no pre-processing necessary. */ + default: + break; + } + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_capabilities.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_capabilities.hpp new file mode 100644 index 00000000..21748a7f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_capabilities.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ldr { + + Result TestCapability(const util::BitPack32 *kacd, size_t kacd_count, const util::BitPack32 *kac, size_t kac_count); + + u16 MakeProgramInfoFlag(const util::BitPack32 *kac, size_t count); + void UpdateProgramInfoFlag(u16 flags, util::BitPack32 *kac, size_t count); + + void FixDebugCapabilityForHbl(util::BitPack32 *kac, size_t count); + + void PreProcessCapability(util::BitPack32 *kac, size_t count); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_content_management.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_content_management.cpp new file mode 100644 index 00000000..f1d0dfa1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_content_management.cpp @@ -0,0 +1,169 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ldr_content_management.hpp" + +namespace ams::ldr { + + namespace { + + constinit os::SdkMutex g_scoped_code_mount_lock; + + } + + /* ScopedCodeMount functionality. */ + ScopedCodeMount::ScopedCodeMount(const ncm::ProgramLocation &loc, const ldr::ProgramAttributes &attrs) : m_lk(g_scoped_code_mount_lock), m_has_status(false), m_mounted_ams(false), m_mounted_sd_or_code(false), m_mounted_code(false) { + m_result = this->Initialize(loc, attrs); + } + + ScopedCodeMount::ScopedCodeMount(const ncm::ProgramLocation &loc, const cfg::OverrideStatus &o, const ldr::ProgramAttributes &attrs) : m_lk(g_scoped_code_mount_lock), m_override_status(o), m_has_status(true), m_mounted_ams(false), m_mounted_sd_or_code(false), m_mounted_code(false) { + m_result = this->Initialize(loc, attrs); + } + + ScopedCodeMount::~ScopedCodeMount() { + /* Unmount filesystems. */ + if (m_mounted_ams) { + fs::Unmount(AtmosphereCodeMountName); + } + if (m_mounted_sd_or_code) { + fs::Unmount(SdOrCodeMountName); + } + if (m_mounted_code) { + fs::Unmount(CodeMountName); + } + } + + Result ScopedCodeMount::Initialize(const ncm::ProgramLocation &loc, const ldr::ProgramAttributes &attrs) { + /* Capture override status, if necessary. */ + this->EnsureOverrideStatus(loc); + AMS_ABORT_UNLESS(m_has_status); + + /* Get the content path. */ + char content_path[fs::EntryNameLengthMax + 1]; + R_TRY(GetProgramPath(content_path, sizeof(content_path), loc, attrs)); + + /* Get the content attributes. */ + const auto content_attributes = attrs.content_attributes; + + /* Mount the atmosphere code file system. */ + R_TRY(fs::MountCodeForAtmosphereWithRedirection(std::addressof(m_ams_code_verification_data), AtmosphereCodeMountName, content_path, content_attributes, loc.program_id, static_cast<ncm::StorageId>(loc.storage_id), m_override_status.IsHbl(), m_override_status.IsProgramSpecific())); + m_mounted_ams = true; + + /* Mount the sd or base code file system. */ + R_TRY(fs::MountCodeForAtmosphere(std::addressof(m_sd_or_base_code_verification_data), SdOrCodeMountName, content_path, content_attributes, loc.program_id, static_cast<ncm::StorageId>(loc.storage_id))); + m_mounted_sd_or_code = true; + + /* Mount the base code file system. */ + if (R_SUCCEEDED(fs::MountCode(std::addressof(m_base_code_verification_data), CodeMountName, content_path, content_attributes, loc.program_id, static_cast<ncm::StorageId>(loc.storage_id)))) { + m_mounted_code = true; + } + + R_SUCCEED(); + } + + void ScopedCodeMount::EnsureOverrideStatus(const ncm::ProgramLocation &loc) { + if (m_has_status) { + return; + } + m_override_status = cfg::CaptureOverrideStatus(loc.program_id); + m_has_status = true; + } + + /* Redirection API. */ + Result GetProgramPath(char *out_path, size_t out_size, const ncm::ProgramLocation &loc, const ldr::ProgramAttributes &attrs) { + /* Check for storage id none. */ + if (static_cast<ncm::StorageId>(loc.storage_id) == ncm::StorageId::None) { + std::memset(out_path, 0, out_size); + std::memcpy(out_path, "/", std::min<size_t>(out_size, 2)); + R_SUCCEED(); + } + + /* Get the content attributes. */ + const auto content_attributes = attrs.content_attributes; + AMS_UNUSED(content_attributes); + + lr::Path path; + + /* Check that path registration is allowable. */ + if (static_cast<ncm::StorageId>(loc.storage_id) == ncm::StorageId::Host) { + AMS_ABORT_UNLESS(spl::IsDevelopment()); + } + + /* Try to get the path from the registered resolver. */ + lr::RegisteredLocationResolver reg; + R_TRY(lr::OpenRegisteredLocationResolver(std::addressof(reg))); + + R_TRY_CATCH(reg.ResolveProgramPath(std::addressof(path), loc.program_id)) { + R_CATCH(lr::ResultProgramNotFound) { + /* Program wasn't found via registered resolver, fall back to the normal resolver. */ + lr::LocationResolver lr; + R_TRY(lr::OpenLocationResolver(std::addressof(lr), static_cast<ncm::StorageId>(loc.storage_id))); + R_TRY(lr.ResolveProgramPath(std::addressof(path), loc.program_id)); + } + } R_END_TRY_CATCH; + + /* Fix directory separators in path. */ + fs::Replace(path.str, sizeof(path.str), fs::StringTraits::AlternateDirectorySeparator, fs::StringTraits::DirectorySeparator); + + /* Check that the path is valid. */ + AMS_ABORT_UNLESS(path.IsValid()); + + /* Copy the output path. */ + std::memset(out_path, 0, out_size); + std::memcpy(out_path, path.str, std::min(out_size, sizeof(path))); + + R_SUCCEED(); + } + + Result RedirectProgramPath(const char *path, size_t size, const ncm::ProgramLocation &loc) { + /* Check for storage id none. */ + if (static_cast<ncm::StorageId>(loc.storage_id) == ncm::StorageId::None) { + R_SUCCEED(); + } + + /* Open location resolver. */ + lr::LocationResolver lr; + R_TRY(lr::OpenLocationResolver(std::addressof(lr), static_cast<ncm::StorageId>(loc.storage_id))); + + /* Copy in path. */ + lr::Path lr_path; + std::memcpy(lr_path.str, path, std::min(size, sizeof(lr_path.str))); + lr_path.str[sizeof(lr_path.str) - 1] = '\x00'; + + /* Redirect the path. */ + lr.RedirectProgramPath(lr_path, loc.program_id); + + R_SUCCEED(); + } + + Result RedirectHtmlDocumentPathForHbl(const ncm::ProgramLocation &loc) { + lr::Path path; + + /* Open a location resolver. */ + lr::LocationResolver lr; + R_TRY(lr::OpenLocationResolver(std::addressof(lr), static_cast<ncm::StorageId>(loc.storage_id))); + + /* If there's already a Html Document path, we don't need to set one. */ + R_SUCCEED_IF(R_SUCCEEDED(lr.ResolveApplicationHtmlDocumentPath(std::addressof(path), loc.program_id))); + + /* We just need to set this to any valid NCA path. Let's use the executable path. */ + R_TRY(lr.ResolveProgramPath(std::addressof(path), loc.program_id)); + lr.RedirectApplicationHtmlDocumentPath(path, loc.program_id, loc.program_id); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_content_management.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_content_management.hpp new file mode 100644 index 00000000..d20c8070 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_content_management.hpp @@ -0,0 +1,83 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ldr { + + /* Utility reference to make code mounting automatic. */ + class ScopedCodeMount { + NON_COPYABLE(ScopedCodeMount); + NON_MOVEABLE(ScopedCodeMount); + private: + std::scoped_lock<os::SdkMutex> m_lk; + cfg::OverrideStatus m_override_status; + fs::CodeVerificationData m_ams_code_verification_data; + fs::CodeVerificationData m_sd_or_base_code_verification_data; + fs::CodeVerificationData m_base_code_verification_data; + Result m_result; + bool m_has_status; + bool m_mounted_ams; + bool m_mounted_sd_or_code; + bool m_mounted_code; + public: + ScopedCodeMount(const ncm::ProgramLocation &loc, const ldr::ProgramAttributes &attrs); + ScopedCodeMount(const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status, const ldr::ProgramAttributes &attrs); + ~ScopedCodeMount(); + + Result GetResult() const { + return m_result; + } + + const cfg::OverrideStatus &GetOverrideStatus() const { + AMS_ABORT_UNLESS(m_has_status); + return m_override_status; + } + + const fs::CodeVerificationData &GetAtmosphereCodeVerificationData() const { + return m_ams_code_verification_data; + } + + const fs::CodeVerificationData &GetSdOrBaseCodeVerificationData() const { + return m_sd_or_base_code_verification_data; + } + + const fs::CodeVerificationData &GetCodeVerificationData() const { + return m_base_code_verification_data; + } + private: + Result Initialize(const ncm::ProgramLocation &loc, const ldr::ProgramAttributes &attrs); + void EnsureOverrideStatus(const ncm::ProgramLocation &loc); + }; + + constexpr inline const char * const AtmosphereCodeMountName = "ams-code"; + constexpr inline const char * const AtmosphereCompatMountName = "ams-cmpt"; + constexpr inline const char * const SdOrCodeMountName = "sd-code"; + constexpr inline const char * const CodeMountName = "code"; + constexpr inline const char * const CompatMountName = "cmpt"; + + #define ENCODE_ATMOSPHERE_CODE_PATH(relative) "ams-code:" relative + #define ENCODE_ATMOSPHERE_CMPT_PATH(relative) "ams-cmpt:" relative + #define ENCODE_SD_OR_CODE_PATH(relative) "sd-code:" relative + #define ENCODE_CODE_PATH(relative) "code:" relative + #define ENCODE_CMPT_PATH(relative) "cmpt:" relative + + /* Redirection API. */ + Result GetProgramPath(char *out_path, size_t out_size, const ncm::ProgramLocation &loc, const ldr::ProgramAttributes &attrs); + Result RedirectProgramPath(const char *path, size_t size, const ncm::ProgramLocation &loc); + Result RedirectHtmlDocumentPathForHbl(const ncm::ProgramLocation &loc); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_development_manager.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_development_manager.cpp new file mode 100644 index 00000000..e49ac72e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_development_manager.cpp @@ -0,0 +1,64 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ldr_launch_record.hpp" + +namespace ams::ldr { + + namespace { + + bool g_development_for_acid_production_check = false; + bool g_development_for_anti_downgrade_check = false; + bool g_development_for_acid_signature_check = false; + bool g_enabled_program_verification = true; + + } + + void SetDevelopmentForAcidProductionCheck(bool development) { + g_development_for_acid_production_check = development; + } + + void SetDevelopmentForAntiDowngradeCheck(bool development) { + g_development_for_anti_downgrade_check = development; + } + + void SetDevelopmentForAcidSignatureCheck(bool development) { + g_development_for_acid_signature_check = development; + } + + void SetEnabledProgramVerification(bool enabled) { + if (g_development_for_acid_signature_check) { + g_enabled_program_verification = enabled; + } + } + + bool IsDevelopmentForAcidProductionCheck() { + return g_development_for_acid_production_check; + } + + bool IsDevelopmentForAntiDowngradeCheck() { + return g_development_for_anti_downgrade_check; + } + + bool IsDevelopmentForAcidSignatureCheck() { + return g_development_for_acid_signature_check; + } + + bool IsEnabledProgramVerification() { + return g_enabled_program_verification; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_development_manager.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_development_manager.hpp new file mode 100644 index 00000000..8a730f38 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_development_manager.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ldr { + + /* Development Manager API. */ + void SetDevelopmentForAcidProductionCheck(bool development); + void SetDevelopmentForAntiDowngradeCheck(bool development); + void SetDevelopmentForAcidSignatureCheck(bool development); + void SetEnabledProgramVerification(bool enabled); + + bool IsDevelopmentForAcidProductionCheck(); + bool IsDevelopmentForAntiDowngradeCheck(); + bool IsDevelopmentForAcidSignatureCheck(); + bool IsEnabledProgramVerification(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_embedded_usb_patches.inc b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_embedded_usb_patches.inc new file mode 100644 index 00000000..efb05efe --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_embedded_usb_patches.inc @@ -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 <http://www.gnu.org/licenses/>. + */ + +/* Patch fallback case to mov w0, #1 rather than retrieving settings flag. */ +constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_9_0_0[] = { + { 0x521C, "\x20\x00\x80\x52", 4 }, +}; + +constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_10_0_0[] = { + { 0x5494, "\x20\x00\x80\x52", 4 }, +}; + +/* Patch getter functions to return 1. */ +constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_11_0_0[] = { + { 0x85DC, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, + { 0x866C, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, +}; + +constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_12_0_0[] = { + { 0x840C, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, + { 0x849C, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, +}; + +constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_13_0_0[] = { + { 0x8424, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, + { 0x84B4, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, +}; + +constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_14_0_0[] = { + { 0x7414, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, + { 0x74A4, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, +}; + +constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_15_0_0[] = { + { 0x7230, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, + { 0x72AC, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, +}; + +constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_16_0_0[] = { + { 0x718C, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, + { 0x7208, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, +}; + +constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_17_0_0[] = { + { 0x7170, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, + { 0x71EC, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, +}; + +constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_18_0_0[] = { + { 0x6DCC, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, + { 0x6E48, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, +}; + +constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_19_0_0[] = { + { 0x6D90, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, + { 0x6E10, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, +}; + +constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_20_0_0[] = { + { 0x7010, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, + { 0x7090, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, +}; + +constexpr inline const EmbeddedPatchEntry Usb30ForceEnablePatches_20_1_0[] = { + { 0x7010, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, + { 0x7090, "\x20\x00\x80\x52\xC0\x03\x5F\xD6", 8 }, +}; + +constexpr inline const EmbeddedPatch Usb30ForceEnablePatches[] = { + { ParseModuleId("C0D3F4E87E8B0FE9BBE9F1968A20767F3DC08E03"), util::size(Usb30ForceEnablePatches_9_0_0), Usb30ForceEnablePatches_9_0_0 }, + { ParseModuleId("B9C700CA8335F8BAA0D2041D8D09F772890BA988"), util::size(Usb30ForceEnablePatches_10_0_0), Usb30ForceEnablePatches_10_0_0 }, + { ParseModuleId("95BAF06A69650C215A5DD50CF8BD2A603E7AD3C2"), util::size(Usb30ForceEnablePatches_11_0_0), Usb30ForceEnablePatches_11_0_0 }, + { ParseModuleId("F3F88D90EF6B3B6358EDEBAF87D56FA88D3A081C"), util::size(Usb30ForceEnablePatches_12_0_0), Usb30ForceEnablePatches_12_0_0 }, /* 12.0.0 - 12.0.3 */ + { ParseModuleId("A109046278AA99353C20EC38B57C495B74A06D91"), util::size(Usb30ForceEnablePatches_12_0_0), Usb30ForceEnablePatches_12_0_0 }, /* 12.1.0 */ + { ParseModuleId("69384BF4A543C62C4342C94E5E2CED3C5A675251"), util::size(Usb30ForceEnablePatches_13_0_0), Usb30ForceEnablePatches_13_0_0 }, /* 13.0.0 - 13.1.0 */ + { ParseModuleId("1C97AFF30D48AFFEB74B28A530D30ABA0ABB9FFF"), util::size(Usb30ForceEnablePatches_14_0_0), Usb30ForceEnablePatches_14_0_0 }, /* 14.0.0 */ + { ParseModuleId("30B15A83E94D91750E7470795414AD1AE9C6A8DB"), util::size(Usb30ForceEnablePatches_15_0_0), Usb30ForceEnablePatches_15_0_0 }, /* 15.0.0 */ + { ParseModuleId("225865A442B4B66E8BD14B3E9450B901BDF29A40"), util::size(Usb30ForceEnablePatches_16_0_0), Usb30ForceEnablePatches_16_0_0 }, /* 16.0.0 */ + { ParseModuleId("70D4C2ABCD049F16B301186924367F813DA70248"), util::size(Usb30ForceEnablePatches_17_0_0), Usb30ForceEnablePatches_17_0_0 }, /* 17.0.0 */ + { ParseModuleId("4F21AE15E814FA46515C0401BB23D4F7ADCBF3F4"), util::size(Usb30ForceEnablePatches_18_0_0), Usb30ForceEnablePatches_18_0_0 }, /* 18.0.0 */ + { ParseModuleId("54BB9BB32C958E02752DC5E4AE8D016BFE1F5418"), util::size(Usb30ForceEnablePatches_19_0_0), Usb30ForceEnablePatches_19_0_0 }, /* 19.0.0 */ + { ParseModuleId("40E80E7442C0DFC985315E6F9E8C77229818AC0F"), util::size(Usb30ForceEnablePatches_20_0_0), Usb30ForceEnablePatches_20_0_0 }, /* 20.0.0 */ + { ParseModuleId("A5EF8D22EDF8A384E4135270ED596C1D2D524159"), util::size(Usb30ForceEnablePatches_20_1_0), Usb30ForceEnablePatches_20_1_0 }, /* 20.1.0 - 20.1.1 */ +}; diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_launch_record.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_launch_record.cpp new file mode 100644 index 00000000..b5e4439f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_launch_record.cpp @@ -0,0 +1,80 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ldr_launch_record.hpp" + +namespace ams::ldr { + + namespace { + + static constexpr size_t MaxBootPrograms = 0x50; + constinit std::array<ncm::ProgramId, MaxBootPrograms> g_launched_boot_programs = [] { + std::array<ncm::ProgramId, MaxBootPrograms> arr = {}; + for (size_t i = 0; i < MaxBootPrograms; ++i) { + arr[i] = ncm::InvalidProgramId; + } + return arr; + }(); + + constinit bool g_boot_programs_done = false; + + bool HasLaunchedBootProgramImpl(ncm::ProgramId program_id) { + for (const auto &launched : g_launched_boot_programs) { + if (launched == program_id) { + return true; + } + } + return false; + } + + void SetLaunchedBootProgramImpl(ncm::ProgramId program_id) { + for (size_t i = 0; i < MaxBootPrograms; ++i) { + if (g_launched_boot_programs[i] == ncm::InvalidProgramId) { + g_launched_boot_programs[i] = program_id; + return; + } + } + AMS_ABORT("Too many boot programs"); + } + + } + + /* Launch Record API. */ + bool HasLaunchedBootProgram(ncm::ProgramId program_id) { + return HasLaunchedBootProgramImpl(program_id); + } + + void SetLaunchedBootProgram(ncm::ProgramId program_id) { + if (!g_boot_programs_done) { + SetLaunchedBootProgramImpl(program_id); + if (program_id == ncm::SystemAppletId::Qlaunch) { + g_boot_programs_done = true; + } + } + } + +} + +/* Loader wants to override this libstratosphere function, which is weakly linked. */ +/* This is necessary to prevent circular dependencies. */ +namespace ams::pm::info { + + Result HasLaunchedBootProgram(bool *out, ncm::ProgramId program_id) { + *out = ldr::HasLaunchedBootProgram(program_id); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_launch_record.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_launch_record.hpp new file mode 100644 index 00000000..d4a566df --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_launch_record.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ldr { + + /* Launch Record API. */ + bool HasLaunchedBootProgram(ncm::ProgramId program_id); + void SetLaunchedBootProgram(ncm::ProgramId program_id); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_loader_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_loader_service.cpp new file mode 100644 index 00000000..17a4a916 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_loader_service.cpp @@ -0,0 +1,138 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ldr_argument_store.hpp" +#include "ldr_content_management.hpp" +#include "ldr_development_manager.hpp" +#include "ldr_process_creation.hpp" +#include "ldr_launch_record.hpp" +#include "ldr_loader_service.hpp" + +namespace ams::ldr { + + namespace { + + constinit ArgumentStore g_argument_store; + + bool IsValidProgramAttributes(const ldr::ProgramAttributes &attrs) { + /* Check that the attributes are valid; on NX, this means Platform = NX + no content attrs. */ + if (attrs.platform != ncm::ContentMetaPlatform::Nx) { + return false; + } + if (attrs.content_attributes != fs::ContentAttributes_None) { + return false; + } + + return true; + } + + } + + + Result LoaderService::CreateProcess(os::NativeHandle *out, PinId pin_id, u32 flags, os::NativeHandle resource_limit, const ProgramAttributes &attrs) { + /* Check that the program attributes are valid. */ + R_UNLESS(IsValidProgramAttributes(attrs), ldr::ResultInvalidProgramAttributes()); + + /* Get the location and override status. */ + ncm::ProgramLocation loc; + cfg::OverrideStatus override_status; + R_TRY(ldr::GetProgramLocationAndOverrideStatusFromPinId(std::addressof(loc), std::addressof(override_status), pin_id)); + + /* Get the program path. */ + char path[fs::EntryNameLengthMax]; + R_TRY(GetProgramPath(path, sizeof(path), loc, attrs)); + path[sizeof(path) - 1] = '\x00'; + + /* Create the process. */ + R_RETURN(ldr::CreateProcess(out, pin_id, loc, override_status, path, g_argument_store.Get(loc.program_id), flags, resource_limit, attrs)); + } + + Result LoaderService::GetProgramInfo(ProgramInfo *out, cfg::OverrideStatus *out_status, const ncm::ProgramLocation &loc, const ProgramAttributes &attrs) { + /* Check that the program attributes are valid. */ + R_UNLESS(IsValidProgramAttributes(attrs), ldr::ResultInvalidProgramAttributes()); + + /* Zero output. */ + std::memset(out, 0, sizeof(*out)); + + /* Get the program path. */ + char path[fs::EntryNameLengthMax]; + R_TRY(GetProgramPath(path, sizeof(path), loc, attrs)); + path[sizeof(path) - 1] = '\x00'; + + /* Get the program info. */ + cfg::OverrideStatus status; + R_TRY(ldr::GetProgramInfo(out, std::addressof(status), loc, path, attrs)); + + if (loc.program_id != out->program_id) { + /* Redirect the program path. */ + const ncm::ProgramLocation new_loc = ncm::ProgramLocation::Make(out->program_id, static_cast<ncm::StorageId>(loc.storage_id)); + R_TRY(RedirectProgramPath(path, sizeof(path), new_loc)); + + /* Update the arguments, as needed. */ + if (const auto *entry = g_argument_store.Get(loc.program_id); entry != nullptr) { + R_TRY(g_argument_store.Set(new_loc.program_id, entry->argument, entry->argument_size)); + } + } + + /* If we should, set the output status. */ + if (out_status != nullptr) { + *out_status = status; + } + + R_SUCCEED(); + } + + Result LoaderService::PinProgram(PinId *out, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status) { + *out = {}; + R_RETURN(ldr::PinProgram(out, loc, status)); + } + + Result LoaderService::UnpinProgram(PinId id) { + R_RETURN(ldr::UnpinProgram(id)); + } + + Result LoaderService::SetProgramArgument(ncm::ProgramId program_id, const void *argument, size_t size) { + R_RETURN(g_argument_store.Set(program_id, argument, size)); + } + + Result LoaderService::FlushArguments() { + R_RETURN(g_argument_store.Flush()); + } + + Result LoaderService::GetProcessModuleInfo(u32 *out_count, ModuleInfo *out, size_t max_out_count, os::ProcessId process_id) { + *out_count = 0; + std::memset(out, 0, max_out_count * sizeof(*out)); + R_RETURN(ldr::GetProcessModuleInfo(out_count, out, max_out_count, process_id)); + } + + Result LoaderService::RegisterExternalCode(os::NativeHandle *out, ncm::ProgramId program_id) { + R_RETURN(fssystem::CreateExternalCode(out, program_id)); + } + + void LoaderService::UnregisterExternalCode(ncm::ProgramId program_id) { + fssystem::DestroyExternalCode(program_id); + } + + void LoaderService::HasLaunchedBootProgram(bool *out, ncm::ProgramId program_id) { + *out = ldr::HasLaunchedBootProgram(program_id); + } + + Result LoaderService::SetEnabledProgramVerification(bool enabled) { + ldr::SetEnabledProgramVerification(enabled); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_loader_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_loader_service.hpp new file mode 100644 index 00000000..9f5314c5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_loader_service.hpp @@ -0,0 +1,99 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ldr { + + class LoaderService { + public: + /* Official commands. */ + Result CreateProcess(sf::OutMoveHandle proc_h, PinId id, u32 flags, sf::CopyHandle &&reslimit_h, const ProgramAttributes &attrs) { + /* Create a handle to set the output to when done. */ + os::NativeHandle handle = os::InvalidNativeHandle; + ON_SCOPE_EXIT { proc_h.SetValue(handle, true); }; + + R_RETURN(this->CreateProcess(std::addressof(handle), id, flags, reslimit_h.GetOsHandle(), attrs)); + } + + Result GetProgramInfo(sf::Out<ProgramInfo> out_program_info, const ncm::ProgramLocation &loc, const ProgramAttributes &attrs) { + R_RETURN(this->GetProgramInfo(out_program_info.GetPointer(), nullptr, loc, attrs)); + } + + Result PinProgram(sf::Out<PinId> out_id, const ncm::ProgramLocation &loc) { + R_RETURN(this->PinProgram(out_id.GetPointer(), loc, cfg::OverrideStatus{})); + } + + Result UnpinProgram(PinId id); + + Result SetProgramArgumentDeprecated(ncm::ProgramId program_id, const sf::InPointerBuffer &args, u32 args_size) { + AMS_UNUSED(args_size); + R_RETURN(this->SetProgramArgument(program_id, args.GetPointer(), std::min<size_t>(args_size, args.GetSize()))); + } + + Result SetProgramArgument(ncm::ProgramId program_id, const sf::InPointerBuffer &args) { + R_RETURN(this->SetProgramArgument(program_id, args.GetPointer(), args.GetSize())); + } + + Result FlushArguments(); + + Result GetProcessModuleInfo(sf::Out<u32> count, const sf::OutPointerArray<ModuleInfo> &out, os::ProcessId process_id) { + R_UNLESS(out.GetSize() <= std::numeric_limits<s32>::max(), ldr::ResultInvalidSize()); + + R_RETURN(this->GetProcessModuleInfo(count.GetPointer(), out.GetPointer(), out.GetSize(), process_id)); + } + + Result SetEnabledProgramVerification(bool enabled); + + /* Atmosphere commands. */ + Result AtmosphereRegisterExternalCode(sf::OutMoveHandle out, ncm::ProgramId program_id) { + /* Create a handle to set the output to when done. */ + os::NativeHandle handle = os::InvalidNativeHandle; + ON_SCOPE_EXIT { out.SetValue(handle, true); }; + + R_RETURN(this->RegisterExternalCode(std::addressof(handle), program_id)); + } + + void AtmosphereUnregisterExternalCode(ncm::ProgramId program_id) { + return this->UnregisterExternalCode(program_id); + } + + void AtmosphereHasLaunchedBootProgram(sf::Out<bool> out, ncm::ProgramId program_id) { + return this->HasLaunchedBootProgram(out.GetPointer(), program_id); + } + + Result AtmosphereGetProgramInfo(sf::Out<ProgramInfo> out_program_info, sf::Out<cfg::OverrideStatus> out_status, const ncm::ProgramLocation &loc, const ProgramAttributes &attrs) { + R_RETURN(this->GetProgramInfo(out_program_info.GetPointer(), out_status.GetPointer(), loc, attrs)); + } + + Result AtmospherePinProgram(sf::Out<PinId> out_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status) { + R_RETURN(this->PinProgram(out_id.GetPointer(), loc, override_status)); + } + private: + Result CreateProcess(os::NativeHandle *out, PinId pin_id, u32 flags, os::NativeHandle resource_limit, const ProgramAttributes &attrs); + Result GetProgramInfo(ProgramInfo *out, cfg::OverrideStatus *out_status, const ncm::ProgramLocation &loc, const ProgramAttributes &attrs); + Result PinProgram(PinId *out, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status); + Result SetProgramArgument(ncm::ProgramId program_id, const void *argument, size_t size); + Result GetProcessModuleInfo(u32 *out_count, ModuleInfo *out, size_t max_out_count, os::ProcessId process_id); + Result RegisterExternalCode(os::NativeHandle *out, ncm::ProgramId program_id); + void UnregisterExternalCode(ncm::ProgramId program_id); + void HasLaunchedBootProgram(bool *out, ncm::ProgramId program_id); + }; + static_assert(ams::ldr::impl::IsIProcessManagerInterface<LoaderService>); + static_assert(ams::ldr::impl::IsIDebugMonitorInterface<LoaderService>); + static_assert(ams::ldr::impl::IsIShellInterface<LoaderService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_main.cpp new file mode 100644 index 00000000..5e4d8ca0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_main.cpp @@ -0,0 +1,170 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ldr_development_manager.hpp" +#include "ldr_loader_service.hpp" + +namespace ams { + + namespace ldr { + + namespace { + + constinit u8 g_heap_memory[16_KB]; + lmem::HeapHandle g_server_heap_handle; + constinit ams::sf::ExpHeapAllocator g_server_allocator; + + void *Allocate(size_t size) { + return lmem::AllocateFromExpHeap(g_server_heap_handle, size); + } + + void Deallocate(void *p, size_t size) { + AMS_UNUSED(size); + return lmem::FreeToExpHeap(g_server_heap_handle, p); + } + + void InitializeHeap() { + g_server_heap_handle = lmem::CreateExpHeap(g_heap_memory, sizeof(g_heap_memory), lmem::CreateOption_None); + g_server_allocator.Attach(g_server_heap_handle); + } + + } + + namespace { + + struct ServerOptions { + static constexpr size_t PointerBufferSize = 0x420; + static constexpr size_t MaxDomains = 0; + static constexpr size_t MaxDomainObjects = 0; + static constexpr bool CanDeferInvokeRequest = false; + static constexpr bool CanManageMitmServers = false; + }; + + /* ldr:pm, ldr:shel, ldr:dmnt. */ + enum PortIndex { + PortIndex_ProcessManager, + PortIndex_Shell, + PortIndex_DebugMonitor, + PortIndex_Count, + }; + + constexpr sm::ServiceName ProcessManagerServiceName = sm::ServiceName::Encode("ldr:pm"); + constexpr size_t ProcessManagerMaxSessions = 1; + + constexpr sm::ServiceName ShellServiceName = sm::ServiceName::Encode("ldr:shel"); + constexpr size_t ShellMaxSessions = 3; + + constexpr sm::ServiceName DebugMonitorServiceName = sm::ServiceName::Encode("ldr:dmnt"); + constexpr size_t DebugMonitorMaxSessions = 3; + + constinit sf::UnmanagedServiceObject<impl::IProcessManagerInterface, LoaderService> g_pm_service; + constinit sf::UnmanagedServiceObject<impl::IShellInterface, LoaderService> g_shell_service; + constinit sf::UnmanagedServiceObject<impl::IDebugMonitorInterface, LoaderService> g_dmnt_service; + + constexpr size_t MaxSessions = ProcessManagerMaxSessions + ShellMaxSessions + DebugMonitorMaxSessions + 1; + + using ServerManager = ams::sf::hipc::ServerManager<PortIndex_Count, ServerOptions, MaxSessions>; + + ServerManager g_server_manager; + + void RegisterServiceSessions() { + R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_pm_service.GetShared(), ProcessManagerServiceName, ProcessManagerMaxSessions)); + R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_shell_service.GetShared(), ShellServiceName, ShellMaxSessions)); + R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_dmnt_service.GetShared(), DebugMonitorServiceName, DebugMonitorMaxSessions)); + } + + void LoopProcess() { + g_server_manager.LoopProcess(); + } + + } + + } + + namespace init { + + void InitializeSystemModule() { + /* Initialize heap. */ + ldr::InitializeHeap(); + + /* Set fs allocator. */ + fs::SetAllocator(ldr::Allocate, ldr::Deallocate); + + /* Initialize services we need. */ + R_ABORT_UNLESS(sm::Initialize()); + + fs::InitializeForSystem(); + lr::Initialize(); + R_ABORT_UNLESS(fsldrInitialize()); + spl::Initialize(); + + /* Verify that we can sanely execute. */ + ams::CheckApiVersion(); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void NORETURN Exit(int rc) { + AMS_UNUSED(rc); + AMS_ABORT("Exit called by immortal process"); + } + + void Main() { + /* Disable auto-abort in fs operations. */ + fs::SetEnabledAutoAbort(false); + + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(ldr, Main)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(ldr, Main)); + + /* Configure development. */ + /* NOTE: Nintendo really does call the getter function three times instead of caching the value. */ + ldr::SetDevelopmentForAcidProductionCheck(spl::IsDevelopment()); + ldr::SetDevelopmentForAntiDowngradeCheck(spl::IsDevelopment()); + ldr::SetDevelopmentForAcidSignatureCheck(spl::IsDevelopment()); + + /* Register the loader services. */ + ldr::RegisterServiceSessions(); + + /* Loop forever, servicing our services. */ + ldr::LoopProcess(); + + /* This can never be reached. */ + AMS_ASSUME(false); + } + +} + +/* Override operator new. */ +void *operator new(size_t size) { + return ams::ldr::Allocate(size); +} + +void *operator new(size_t size, const std::nothrow_t &) { + return ams::ldr::Allocate(size); +} + +void operator delete(void *p) { + return ams::ldr::Deallocate(p, 0); +} + +void operator delete(void *p, size_t size) { + return ams::ldr::Deallocate(p, size); +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_meta.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_meta.cpp new file mode 100644 index 00000000..04e71d38 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_meta.cpp @@ -0,0 +1,298 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ldr_capabilities.hpp" +#include "ldr_content_management.hpp" +#include "ldr_development_manager.hpp" +#include "ldr_meta.hpp" + +namespace ams::ldr { + + namespace { + + /* Convenience definitions. */ + constexpr size_t MetaCacheBufferSize = 0x8000; + constexpr inline const char AtmosphereMetaPath[] = ENCODE_ATMOSPHERE_CODE_PATH("/main.npdm"); + constexpr inline const char SdOrBaseMetaPath[] = ENCODE_SD_OR_CODE_PATH("/main.npdm"); + constexpr inline const char BaseMetaPath[] = ENCODE_CODE_PATH("/main.npdm"); + + /* Types. */ + struct MetaCache { + Meta meta; + u8 buffer[MetaCacheBufferSize]; + }; + + /* Global storage. */ + ncm::ProgramId g_cached_program_id; + cfg::OverrideStatus g_cached_override_status; + MetaCache g_meta_cache; + MetaCache g_original_meta_cache; + + /* Helpers. */ + Result ValidateSubregion(size_t allowed_start, size_t allowed_end, size_t start, size_t size, size_t min_size = 0) { + R_UNLESS(size >= min_size, ldr::ResultInvalidMeta()); + R_UNLESS(allowed_start <= start, ldr::ResultInvalidMeta()); + R_UNLESS(start <= allowed_end, ldr::ResultInvalidMeta()); + R_UNLESS(start + size <= allowed_end, ldr::ResultInvalidMeta()); + R_SUCCEED(); + } + + Result ValidateNpdm(const Npdm *npdm, size_t size) { + /* Validate magic. */ + R_UNLESS(npdm->magic == Npdm::Magic, ldr::ResultInvalidMeta()); + + /* Validate flags. */ + constexpr u32 InvalidMetaFlagMask = 0x80000000; + R_UNLESS(!(npdm->flags & InvalidMetaFlagMask), ldr::ResultInvalidMeta()); + + /* Validate Acid extents. */ + R_TRY(ValidateSubregion(sizeof(Npdm), size, npdm->acid_offset, npdm->acid_size, sizeof(Acid))); + + /* Validate Aci extends. */ + R_TRY(ValidateSubregion(sizeof(Npdm), size, npdm->aci_offset, npdm->aci_size, sizeof(Aci))); + + R_SUCCEED(); + } + + Result ValidateAcid(const Acid *acid, size_t size) { + /* Validate magic. */ + R_UNLESS(acid->magic == Acid::Magic, ldr::ResultInvalidMeta()); + + /* Validate that the acid is for production if not development. */ + if (!IsDevelopmentForAcidProductionCheck()) { + R_UNLESS((acid->flags & Acid::AcidFlag_Production) != 0, ldr::ResultInvalidMeta()); + } + + /* Validate that the acid version is correct. */ + constexpr u8 SupportedSdkMajorVersion = ams::svc::ConvertToSdkMajorVersion(ams::svc::SupportedKernelMajorVersion); + if (acid->unknown_209 < SupportedSdkMajorVersion) { + R_UNLESS(acid->version == 0, ldr::ResultInvalidMeta()); + R_UNLESS(acid->unknown_209 == 0, ldr::ResultInvalidMeta()); + } + + /* Validate Fac, Sac, Kac. */ + R_TRY(ValidateSubregion(sizeof(Acid), size, acid->fac_offset, acid->fac_size)); + R_TRY(ValidateSubregion(sizeof(Acid), size, acid->sac_offset, acid->sac_size)); + R_TRY(ValidateSubregion(sizeof(Acid), size, acid->kac_offset, acid->kac_size)); + + R_SUCCEED(); + } + + Result ValidateAci(const Aci *aci, size_t size) { + /* Validate magic. */ + R_UNLESS(aci->magic == Aci::Magic, ldr::ResultInvalidMeta()); + + /* Validate Fah, Sac, Kac. */ + R_TRY(ValidateSubregion(sizeof(Aci), size, aci->fah_offset, aci->fah_size)); + R_TRY(ValidateSubregion(sizeof(Aci), size, aci->sac_offset, aci->sac_size)); + R_TRY(ValidateSubregion(sizeof(Aci), size, aci->kac_offset, aci->kac_size)); + + R_SUCCEED(); + } + + const u8 *GetAcidSignatureModulus(ncm::ContentMetaPlatform platform, u8 key_generation, bool unk_unused) { + return fssystem::GetAcidSignatureKeyModulus(platform, !IsDevelopmentForAcidSignatureCheck(), key_generation, unk_unused); + } + + size_t GetAcidSignatureModulusSize(ncm::ContentMetaPlatform platform, bool unk_unused) { + return fssystem::GetAcidSignatureKeyModulusSize(platform, unk_unused); + } + + Result ValidateAcidSignature(Meta *meta, ncm::ContentMetaPlatform platform, bool unk_unused) { + /* Loader did not check signatures prior to 10.0.0. */ + if (hos::GetVersion() < hos::Version_10_0_0) { + meta->check_verification_data = false; + R_SUCCEED(); + } + + /* Get the signature key generation. */ + const auto signature_key_generation = meta->npdm->signature_key_generation; + R_UNLESS(fssystem::IsValidSignatureKeyGeneration(platform, signature_key_generation), ldr::ResultInvalidMeta()); + + /* Verify the signature. */ + const u8 *sig = meta->acid->signature; + const size_t sig_size = sizeof(meta->acid->signature); + const u8 *mod = GetAcidSignatureModulus(platform, signature_key_generation, unk_unused); + const size_t mod_size = GetAcidSignatureModulusSize(platform, unk_unused); + const u8 *exp = fssystem::GetAcidSignatureKeyPublicExponent(); + const size_t exp_size = fssystem::AcidSignatureKeyPublicExponentSize; + const u8 *msg = meta->acid->modulus; + const size_t msg_size = meta->acid->size; + const bool is_signature_valid = crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size); + R_UNLESS(is_signature_valid || !IsEnabledProgramVerification(), ldr::ResultInvalidAcidSignature()); + + meta->check_verification_data = is_signature_valid; + R_SUCCEED(); + } + + Result LoadMetaFromFile(fs::FileHandle file, MetaCache *cache) { + /* Reset cache. */ + cache->meta = {}; + + /* Read from file. */ + s64 npdm_size = 0; + { + /* Get file size. */ + R_TRY(fs::GetFileSize(std::addressof(npdm_size), file)); + + /* Read data into cache buffer. */ + R_UNLESS(npdm_size <= static_cast<s64>(MetaCacheBufferSize), ldr::ResultMetaOverflow()); + R_TRY(fs::ReadFile(file, 0, cache->buffer, npdm_size)); + } + + /* Ensure size is big enough. */ + R_UNLESS(npdm_size >= static_cast<s64>(sizeof(Npdm)), ldr::ResultInvalidMeta()); + + /* Validate the meta. */ + { + Meta *meta = std::addressof(cache->meta); + + Npdm *npdm = reinterpret_cast<Npdm *>(cache->buffer); + R_TRY(ValidateNpdm(npdm, npdm_size)); + + Acid *acid = reinterpret_cast<Acid *>(cache->buffer + npdm->acid_offset); + Aci *aci = reinterpret_cast<Aci *>(cache->buffer + npdm->aci_offset); + R_TRY(ValidateAcid(acid, npdm->acid_size)); + R_TRY(ValidateAci(aci, npdm->aci_size)); + + /* Set Meta members. */ + meta->npdm = npdm; + meta->acid = acid; + meta->aci = aci; + + meta->acid_fac = reinterpret_cast<u8 *>(acid) + acid->fac_offset; + meta->acid_sac = reinterpret_cast<u8 *>(acid) + acid->sac_offset; + meta->acid_kac = reinterpret_cast<u8 *>(acid) + acid->kac_offset; + + meta->aci_fah = reinterpret_cast<u8 *>(aci) + aci->fah_offset; + meta->aci_sac = reinterpret_cast<u8 *>(aci) + aci->sac_offset; + meta->aci_kac = reinterpret_cast<u8 *>(aci) + aci->kac_offset; + + meta->modulus = acid->modulus; + } + + R_SUCCEED(); + } + + } + + /* API. */ + Result LoadMeta(Meta *out_meta, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status, ncm::ContentMetaPlatform platform, bool unk_unused) { + /* Set the cached program id back to zero. */ + g_cached_program_id = {}; + + /* Try to load meta from file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), AtmosphereMetaPath, fs::OpenMode_Read)); + { + ON_SCOPE_EXIT { fs::CloseFile(file); }; + R_TRY(LoadMetaFromFile(file, std::addressof(g_meta_cache))); + } + + /* Patch meta. Start by setting all program ids to the current program id. */ + Meta *meta = std::addressof(g_meta_cache.meta); + meta->acid->program_id_min = loc.program_id; + meta->acid->program_id_max = loc.program_id; + meta->aci->program_id = loc.program_id; + + /* For HBL, we need to copy some information from the base meta. */ + if (status.IsHbl()) { + if (R_SUCCEEDED(fs::OpenFile(std::addressof(file), SdOrBaseMetaPath, fs::OpenMode_Read))) { + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + + if (R_SUCCEEDED(LoadMetaFromFile(file, std::addressof(g_original_meta_cache)))) { + Meta *o_meta = std::addressof(g_original_meta_cache.meta); + + /* Fix pool partition. */ + if (hos::GetVersion() >= hos::Version_5_0_0) { + meta->acid->flags = (meta->acid->flags & 0xFFFFFFC3) | (o_meta->acid->flags & 0x0000003C); + } + + /* Fix flags. */ + const u16 program_info_flags = MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(o_meta->aci_kac), o_meta->aci->kac_size / sizeof(util::BitPack32)); + UpdateProgramInfoFlag(program_info_flags, static_cast<util::BitPack32 *>(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32)); + UpdateProgramInfoFlag(program_info_flags, static_cast<util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)); + } + } + + /* Perform address space override. */ + if (status.HasOverrideAddressSpace()) { + /* Clear the existing address space. */ + meta->npdm->flags &= ~Npdm::MetaFlag_AddressSpaceTypeMask; + + /* Set the new address space flag. */ + switch (status.GetOverrideAddressSpaceFlags()) { + case cfg::impl::OverrideStatusFlag_AddressSpace32Bit: meta->npdm->flags |= (Npdm::AddressSpaceType_32Bit) << Npdm::MetaFlag_AddressSpaceTypeShift; break; + case cfg::impl::OverrideStatusFlag_AddressSpace64BitDeprecated: meta->npdm->flags |= (Npdm::AddressSpaceType_64BitDeprecated) << Npdm::MetaFlag_AddressSpaceTypeShift; break; + case cfg::impl::OverrideStatusFlag_AddressSpace32BitWithoutAlias: meta->npdm->flags |= (Npdm::AddressSpaceType_32BitWithoutAlias) << Npdm::MetaFlag_AddressSpaceTypeShift; break; + case cfg::impl::OverrideStatusFlag_AddressSpace64Bit: meta->npdm->flags |= (Npdm::AddressSpaceType_64Bit) << Npdm::MetaFlag_AddressSpaceTypeShift; break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + /* When hbl is applet, adjust main thread priority. */ + if ((MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Applet) { + constexpr auto HblMainThreadPriorityApplication = 44; + constexpr auto HblMainThreadPriorityApplet = 40; + if (meta->npdm->main_thread_priority == HblMainThreadPriorityApplication) { + meta->npdm->main_thread_priority = HblMainThreadPriorityApplet; + } + } + + /* Fix the debug capabilities, to prevent needing a hbl recompilation. */ + FixDebugCapabilityForHbl(static_cast<util::BitPack32 *>(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32)); + FixDebugCapabilityForHbl(static_cast<util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)); + } else if (hos::GetVersion() >= hos::Version_10_0_0) { + /* If storage id is none, there is no base code filesystem, and thus it is impossible for us to validate. */ + /* However, if we're an application, we are guaranteed a base code filesystem. */ + if (static_cast<ncm::StorageId>(loc.storage_id) != ncm::StorageId::None || ncm::IsApplicationId(loc.program_id)) { + R_TRY(fs::OpenFile(std::addressof(file), BaseMetaPath, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + R_TRY(LoadMetaFromFile(file, std::addressof(g_original_meta_cache))); + R_TRY(ValidateAcidSignature(std::addressof(g_original_meta_cache.meta), platform, unk_unused)); + meta->modulus = g_original_meta_cache.meta.modulus; + meta->check_verification_data = g_original_meta_cache.meta.check_verification_data; + } + } + + /* Pre-process the capabilities. */ + /* This is used to e.g. avoid passing memory region descriptor to older kernels. */ + PreProcessCapability(static_cast<util::BitPack32 *>(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32)); + PreProcessCapability(static_cast<util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)); + + /* Set output. */ + g_cached_program_id = loc.program_id; + g_cached_override_status = status; + *out_meta = *meta; + + R_SUCCEED(); + } + + Result LoadMetaFromCache(Meta *out_meta, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status, ncm::ContentMetaPlatform platform) { + if (g_cached_program_id != loc.program_id || g_cached_override_status != status) { + R_RETURN(LoadMeta(out_meta, loc, status, platform, false)); + } + *out_meta = g_meta_cache.meta; + R_SUCCEED(); + } + + void InvalidateMetaCache() { + /* Set the cached program id back to zero. */ + g_cached_program_id = {}; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_meta.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_meta.hpp new file mode 100644 index 00000000..4d3ce3fb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_meta.hpp @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ldr { + + struct Meta { + Npdm *npdm; + Acid *acid; + Aci *aci; + + void *acid_fac; + void *acid_sac; + void *acid_kac; + + void *aci_fah; + void *aci_sac; + void *aci_kac; + + void *modulus; + bool check_verification_data; + }; + + /* Meta API. */ + Result LoadMeta(Meta *out_meta, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status, ncm::ContentMetaPlatform platform, bool unk_unused); + Result LoadMetaFromCache(Meta *out_meta, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status, ncm::ContentMetaPlatform platform); + void InvalidateMetaCache(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_patcher.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_patcher.cpp new file mode 100644 index 00000000..a014067c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_patcher.cpp @@ -0,0 +1,145 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ldr_patcher.hpp" + +namespace ams::ldr { + + namespace { + + constexpr const char *NsoPatchesDirectory = "exefs_patches"; + + /* Exefs patches want to prevent modification of header, */ + /* and also want to adjust offset relative to mapped location. */ + constexpr size_t NsoPatchesProtectedSize = sizeof(NsoHeader); + constexpr size_t NsoPatchesProtectedOffset = sizeof(NsoHeader); + + constexpr const char * const LoaderSdMountName = "#amsldr-sdpatch"; + static_assert(sizeof(LoaderSdMountName) <= fs::MountNameLengthMax); + + constinit os::SdkMutex g_ldr_sd_lock; + constinit bool g_mounted_sd; + + constinit os::SdkMutex g_embedded_patch_lock; + constinit bool g_got_embedded_patch_settings; + constinit bool g_force_enable_usb30; + + bool EnsureSdCardMounted() { + std::scoped_lock lk(g_ldr_sd_lock); + + if (g_mounted_sd) { + return true; + } + + if (!cfg::IsSdCardInitialized()) { + return false; + } + + if (R_FAILED(fs::MountSdCard(LoaderSdMountName))) { + return false; + } + + return (g_mounted_sd = true); + } + + bool IsUsb30ForceEnabled() { + std::scoped_lock lk(g_embedded_patch_lock); + + if (!g_got_embedded_patch_settings) { + g_force_enable_usb30 = spl::IsUsb30ForceEnabled(); + g_got_embedded_patch_settings = true; + } + + return g_force_enable_usb30; + } + + consteval u8 ParseNybble(char c) { + AMS_ASSUME(('0' <= c && c <= '9') || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f')); + if ('0' <= c && c <= '9') { + return c - '0' + 0x0; + } else if ('A' <= c && c <= 'F') { + return c - 'A' + 0xA; + } else /* if ('a' <= c && c <= 'f') */ { + return c - 'a' + 0xa; + } + } + + consteval ro::ModuleId ParseModuleId(const char *str) { + /* Parse a static module id. */ + ro::ModuleId module_id = {}; + + size_t ofs = 0; + while (str[0] != 0) { + AMS_ASSUME(ofs < sizeof(module_id)); + AMS_ASSUME(str[1] != 0); + + module_id.data[ofs] = (ParseNybble(str[0]) << 4) | (ParseNybble(str[1]) << 0); + + str += 2; + ofs++; + } + + return module_id; + } + + struct EmbeddedPatchEntry { + uintptr_t offset; + const void * const data; + size_t size; + }; + + struct EmbeddedPatch { + ro::ModuleId module_id; + size_t num_entries; + const EmbeddedPatchEntry *entries; + }; + + #include "ldr_embedded_usb_patches.inc" + + } + + /* Apply IPS patches. */ + void LocateAndApplyIpsPatchesToModule(const u8 *module_id_data, uintptr_t mapped_nso, size_t mapped_size) { + if (!EnsureSdCardMounted()) { + return; + } + + ro::ModuleId module_id; + std::memcpy(std::addressof(module_id.data), module_id_data, sizeof(module_id.data)); + ams::patcher::LocateAndApplyIpsPatchesToModule(LoaderSdMountName, NsoPatchesDirectory, NsoPatchesProtectedSize, NsoPatchesProtectedOffset, std::addressof(module_id), reinterpret_cast<u8 *>(mapped_nso), mapped_size); + } + + /* Apply embedded patches. */ + void ApplyEmbeddedPatchesToModule(const u8 *module_id_data, uintptr_t mapped_nso, size_t mapped_size) { + /* Make module id. */ + ro::ModuleId module_id; + std::memcpy(std::addressof(module_id.data), module_id_data, sizeof(module_id.data)); + + if (IsUsb30ForceEnabled()) { + for (const auto &patch : Usb30ForceEnablePatches) { + if (std::memcmp(std::addressof(patch.module_id), std::addressof(module_id), sizeof(module_id)) == 0) { + for (size_t i = 0; i < patch.num_entries; ++i) { + const auto &entry = patch.entries[i]; + if (entry.offset + entry.size <= mapped_size) { + std::memcpy(reinterpret_cast<void *>(mapped_nso + entry.offset), entry.data, entry.size); + } + } + } + } + } + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_patcher.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_patcher.hpp new file mode 100644 index 00000000..9aa37216 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_patcher.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ldr { + + /* Apply IPS patches. */ + void LocateAndApplyIpsPatchesToModule(const u8 *module_id_data, uintptr_t mapped_nso, size_t mapped_size); + + /* Apply embedded patches. */ + void ApplyEmbeddedPatchesToModule(const u8 *module_id_data, uintptr_t mapped_nso, size_t mapped_size); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_process_creation.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_process_creation.cpp new file mode 100644 index 00000000..5f6f2226 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_process_creation.cpp @@ -0,0 +1,761 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ldr_capabilities.hpp" +#include "ldr_content_management.hpp" +#include "ldr_development_manager.hpp" +#include "ldr_launch_record.hpp" +#include "ldr_meta.hpp" +#include "ldr_patcher.hpp" +#include "ldr_process_creation.hpp" +#include "ldr_ro_manager.hpp" + +namespace ams::ldr { + + namespace { + + /* Convenience defines. */ + constexpr size_t SystemResourceSizeMax = 0x1FE00000; + + /* Types. */ + enum NsoIndex { + Nso_Rtld = 0, + Nso_Main = 1, + Nso_Compat0 = 2, + Nso_Compat1 = 3, + Nso_Compat2 = 4, + Nso_Compat3 = 5, + Nso_Compat4 = 6, + Nso_Compat5 = 7, + Nso_Compat6 = 8, + Nso_Compat7 = 9, + Nso_Compat8 = 10, + Nso_Compat9 = 11, + Nso_SubSdk0 = 12, + Nso_SubSdk1 = 13, + Nso_SubSdk2 = 14, + Nso_SubSdk3 = 15, + Nso_SubSdk4 = 16, + Nso_SubSdk5 = 17, + Nso_SubSdk6 = 18, + Nso_SubSdk7 = 19, + Nso_SubSdk8 = 20, + Nso_SubSdk9 = 21, + Nso_Sdk = 22, + Nso_Count, + }; + + constexpr inline const char *NsoPaths[Nso_Count] = { + ENCODE_ATMOSPHERE_CODE_PATH("/rtld"), + ENCODE_ATMOSPHERE_CODE_PATH("/main"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat0"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat1"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat2"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat3"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat4"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat5"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat6"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat7"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat8"), + ENCODE_ATMOSPHERE_CMPT_PATH("/compat9"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk0"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk1"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk2"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk3"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk4"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk5"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk6"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk7"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk8"), + ENCODE_ATMOSPHERE_CODE_PATH("/subsdk9"), + ENCODE_ATMOSPHERE_CODE_PATH("/sdk"), + }; + + constexpr const char *GetNsoPath(size_t idx) { + AMS_ABORT_UNLESS(idx < Nso_Count); + return NsoPaths[idx]; + } + + struct ProcessInfo { + os::NativeHandle process_handle; + uintptr_t args_address; + size_t args_size; + uintptr_t nso_address[Nso_Count]; + size_t nso_size[Nso_Count]; + }; + + /* Global NSO header cache. */ + bool g_has_nso[Nso_Count]; + NsoHeader g_nso_headers[Nso_Count]; + + Result ValidateProgramVersion(ncm::ProgramId program_id, u32 version) { + /* No version verification is done before 8.1.0. */ + R_SUCCEED_IF(hos::GetVersion() < hos::Version_8_1_0); + + /* No verification is done if development. */ + R_SUCCEED_IF(IsDevelopmentForAntiDowngradeCheck()); + + /* TODO: Anti-downgrade checking does not make very much sense for us. Should we do anything? */ + AMS_UNUSED(program_id, version); + + R_SUCCEED(); + } + + /* Helpers. */ + Result GetProgramInfoFromMeta(ProgramInfo *out, const Meta *meta) { + /* Copy basic info. */ + out->main_thread_priority = meta->npdm->main_thread_priority; + out->default_cpu_id = meta->npdm->default_cpu_id; + out->main_thread_stack_size = meta->npdm->main_thread_stack_size; + out->program_id = meta->aci->program_id; + + /* Copy access controls. */ + size_t offset = 0; +#define COPY_ACCESS_CONTROL(source, which) \ + ({ \ + const size_t size = meta->source->which##_size; \ + R_UNLESS(offset + size <= sizeof(out->ac_buffer), ldr::ResultInternalError()); \ + out->source##_##which##_size = size; \ + std::memcpy(out->ac_buffer + offset, meta->source##_##which, size); \ + offset += size; \ + }) + + /* Copy all access controls to buffer. */ + COPY_ACCESS_CONTROL(acid, sac); + COPY_ACCESS_CONTROL(aci, sac); + COPY_ACCESS_CONTROL(acid, fac); + COPY_ACCESS_CONTROL(aci, fah); +#undef COPY_ACCESS_CONTROL + + /* Copy flags. */ + out->flags = MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)); + R_SUCCEED(); + } + + bool IsApplet(const Meta *meta) { + return (MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Applet; + } + + bool IsApplication(const Meta *meta) { + return (MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Application; + } + + Npdm::AddressSpaceType GetAddressSpaceType(const Meta *meta) { + return static_cast<Npdm::AddressSpaceType>((meta->npdm->flags & Npdm::MetaFlag_AddressSpaceTypeMask) >> Npdm::MetaFlag_AddressSpaceTypeShift); + } + + Acid::PoolPartition GetPoolPartition(const Meta *meta) { + return static_cast<Acid::PoolPartition>((meta->acid->flags & Acid::AcidFlag_PoolPartitionMask) >> Acid::AcidFlag_PoolPartitionShift); + } + + Result LoadAutoLoadHeaders(NsoHeader *nso_headers, bool *has_nso) { + /* Clear NSOs. */ + std::memset(nso_headers, 0, sizeof(*nso_headers) * Nso_Count); + std::memset(has_nso, 0, sizeof(*has_nso) * Nso_Count); + + for (size_t i = 0; i < Nso_Count; i++) { + fs::FileHandle file; + if (R_SUCCEEDED(fs::OpenFile(std::addressof(file), GetNsoPath(i), fs::OpenMode_Read))) { + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Read NSO header. */ + size_t read_size; + R_TRY(fs::ReadFile(std::addressof(read_size), file, 0, nso_headers + i, sizeof(*nso_headers))); + R_UNLESS(read_size == sizeof(*nso_headers), ldr::ResultInvalidNso()); + + has_nso[i] = true; + } + } + + R_SUCCEED(); + } + + Result CheckAutoLoad(const NsoHeader *nso_headers, const bool *has_nso) { + /* We must always have a main. */ + R_UNLESS(has_nso[Nso_Main], ldr::ResultInvalidNso()); + + /* If we don't have an RTLD, we must only have a main. */ + if (!has_nso[Nso_Rtld]) { + for (size_t i = Nso_Main + 1; i < Nso_Count; i++) { + R_UNLESS(!has_nso[i], ldr::ResultInvalidNso()); + } + } + + /* All NSOs must have zero text offset. */ + for (size_t i = 0; i < Nso_Count; i++) { + R_UNLESS(nso_headers[i].text_dst_offset == 0, ldr::ResultInvalidNso()); + } + + R_SUCCEED(); + } + + constexpr const ncm::ProgramId UnqualifiedApprovalProgramIds[] = { + { 0x010003F003A34000 }, /* Pokemon: Let's Go, Pikachu! */ + { 0x0100152000022000 }, /* Mario Kart 8 Deluxe */ + { 0x0100165003504000 }, /* Nintendo Labo Toy-Con 04: VR Kit */ + { 0x0100187003A36000 }, /* Pokemon: Let's Go, Eevee! */ + { 0x01002E5008C56000 }, /* Pokemon Sword [Live Tournament] */ + { 0x01002FF008C24000 }, /* Ring Fit Adventure */ + { 0x010049900F546001 }, /* Super Mario 3D All-Stars: Super Mario 64 */ + { 0x010057D00ECE4000 }, /* Nintendo Switch Online (Nintendo 64) [for Japan] */ + { 0x01006F8002326000 }, /* Animal Crossing: New Horizons */ + { 0x01006FB00F50E000 }, /* [???] */ + { 0x010070300F50C000 }, /* [???] */ + { 0x010075100E8EC000 }, /* 马力欧卡丁车8 豪华版 [Mario Kart 8 Deluxe for China] */ + { 0x01008DB008C2C000 }, /* Pokemon Shield */ + { 0x01009AD008C4C000 }, /* Pokemon: Let's Go, Pikachu! [Kiosk] */ + { 0x0100A66003384000 }, /* Hulu */ + { 0x0100ABF008968000 }, /* Pokemon Sword */ + { 0x0100C9A00ECE6000 }, /* Nintendo Switch Online (Nintendo 64) [for America] */ + { 0x0100ED100BA3A000 }, /* Mario Kart Live: Home Circuit */ + { 0x0100F38011CFE000 }, /* Animal Crossing: New Horizons Island Transfer Tool */ + { 0x0100F6B011028000 }, /* 健身环大冒险 [Ring Fit Adventure for China] */ + }; + + /* Check that the unqualified approval programs are sorted. */ + static_assert([]() -> bool { + for (size_t i = 0; i < util::size(UnqualifiedApprovalProgramIds) - 1; ++i) { + if (UnqualifiedApprovalProgramIds[i].value >= UnqualifiedApprovalProgramIds[i + 1].value) { + return false; + } + } + + return true; + }()); + + bool IsUnqualifiedApprovalProgramId(ncm::ProgramId program_id) { + /* Check if the program id is one with unqualified approval. */ + return std::binary_search(std::begin(UnqualifiedApprovalProgramIds), std::end(UnqualifiedApprovalProgramIds), program_id); + } + + bool IsUnqualifiedApproval(const Meta *meta) { + /* If the meta has unqualified approval flag, it's unqualified approval. */ + if (meta->acid->flags & ldr::Acid::AcidFlag_UnqualifiedApproval) { + return true; + } + + /* If the unqualified approval flag is not set, the program must be an application. */ + if (!IsApplication(meta)) { + return false; + } + + /* The program id must be a force unqualified approval program id. */ + return IsUnqualifiedApprovalProgramId(meta->acid->program_id_min) && meta->acid->program_id_min == meta->acid->program_id_max; + } + + Result ValidateMeta(const Meta *meta, const ncm::ProgramLocation &loc, const fs::CodeVerificationData &code_verification_data) { + /* Validate version. */ + R_TRY(ValidateProgramVersion(loc.program_id, meta->npdm->version)); + + /* Validate program id. */ + R_UNLESS(meta->aci->program_id >= meta->acid->program_id_min, ldr::ResultInvalidProgramId()); + R_UNLESS(meta->aci->program_id <= meta->acid->program_id_max, ldr::ResultInvalidProgramId()); + + /* Validate the kernel capabilities. */ + R_TRY(TestCapability(static_cast<const util::BitPack32 *>(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32), static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32))); + + /* If we have data to validate, validate it. */ + if (meta->check_verification_data) { + const u8 *sig = code_verification_data.signature; + const size_t sig_size = sizeof(code_verification_data.signature); + const u8 *mod = static_cast<u8 *>(meta->modulus); + const size_t mod_size = crypto::Rsa2048PssSha256Verifier::ModulusSize; + const u8 *exp = fssystem::GetAcidSignatureKeyPublicExponent(); + const size_t exp_size = fssystem::AcidSignatureKeyPublicExponentSize; + const u8 *hsh = code_verification_data.target_hash; + const size_t hsh_size = sizeof(code_verification_data.target_hash); + const bool is_signature_valid = crypto::VerifyRsa2048PssSha256WithHash(sig, sig_size, mod, mod_size, exp, exp_size, hsh, hsh_size); + + /* If the signature check fails, we need to check if this is allowable. */ + if (!is_signature_valid) { + /* We have to enforce signature checks on prod and when we have a signature to check on dev. */ + R_UNLESS(IsDevelopmentForAcidProductionCheck(), ldr::ResultInvalidNcaSignature()); + R_UNLESS(!code_verification_data.has_data, ldr::ResultInvalidNcaSignature()); + + /* There was no signature to check on dev. Check if this is acceptable. */ + R_UNLESS(IsUnqualifiedApproval(meta), ldr::ResultInvalidNcaSignature()); + } + } + + /* All good. */ + R_SUCCEED(); + } + + Result GetCreateProcessFlags(u32 *out, const Meta *meta, const u32 ldr_flags) { + const u8 meta_flags = meta->npdm->flags; + + u32 flags = 0; + + /* Set Is64Bit. */ + if (meta_flags & Npdm::MetaFlag_Is64Bit) { + flags |= svc::CreateProcessFlag_Is64Bit; + } + + /* Set AddressSpaceType. */ + switch (GetAddressSpaceType(meta)) { + case Npdm::AddressSpaceType_32Bit: + flags |= svc::CreateProcessFlag_AddressSpace32Bit; + break; + case Npdm::AddressSpaceType_64BitDeprecated: + flags |= svc::CreateProcessFlag_AddressSpace64BitDeprecated; + break; + case Npdm::AddressSpaceType_32BitWithoutAlias: + flags |= svc::CreateProcessFlag_AddressSpace32BitWithoutAlias; + break; + case Npdm::AddressSpaceType_64Bit: + flags |= svc::CreateProcessFlag_AddressSpace64Bit; + break; + default: + R_THROW(ldr::ResultInvalidMeta()); + } + + /* Set Enable Debug. */ + if (ldr_flags & CreateProcessFlag_EnableDebug) { + flags |= svc::CreateProcessFlag_EnableDebug; + } + + /* Set Enable ASLR. */ + if (!(ldr_flags & CreateProcessFlag_DisableAslr)) { + flags |= svc::CreateProcessFlag_EnableAslr; + } + + /* Set Is Application. */ + if (IsApplication(meta)) { + flags |= svc::CreateProcessFlag_IsApplication; + + /* 7.0.0+: Set OptimizeMemoryAllocation if relevant. */ + if (hos::GetVersion() >= hos::Version_7_0_0) { + if (meta_flags & Npdm::MetaFlag_OptimizeMemoryAllocation) { + flags |= svc::CreateProcessFlag_OptimizeMemoryAllocation; + } + } + } + + /* 5.0.0+ Set Pool Partition. */ + if (hos::GetVersion() >= hos::Version_5_0_0) { + /* TODO: Nintendo no longer accepts Applet when pool partition == application. Would this break hbl/anything else in the hb ecosystem? */ + /* TODO: Nintendo uses a helper bool MakeSvcPoolPartitionFlag(u32 *out, Acid::PoolPartition partition); */ + switch (GetPoolPartition(meta)) { + case Acid::PoolPartition_Application: + if (IsApplet(meta)) { + flags |= svc::CreateProcessFlag_PoolPartitionApplet; + } else { + flags |= svc::CreateProcessFlag_PoolPartitionApplication; + } + break; + case Acid::PoolPartition_Applet: + flags |= svc::CreateProcessFlag_PoolPartitionApplet; + break; + case Acid::PoolPartition_System: + flags |= svc::CreateProcessFlag_PoolPartitionSystem; + break; + case Acid::PoolPartition_SystemNonSecure: + flags |= svc::CreateProcessFlag_PoolPartitionSystemNonSecure; + break; + default: + R_THROW(ldr::ResultInvalidMeta()); + } + } else if (hos::GetVersion() >= hos::Version_4_0_0) { + /* On 4.0.0+, the corresponding bit was simply "UseSecureMemory". */ + if (meta->acid->flags & Acid::AcidFlag_DeprecatedUseSecureMemory) { + flags |= svc::CreateProcessFlag_DeprecatedUseSecureMemory; + } + } + + /* 11.0.0+/meso Set Disable DAS merge. */ + if (meta_flags & Npdm::MetaFlag_DisableDeviceAddressSpaceMerge) { + flags |= svc::CreateProcessFlag_DisableDeviceAddressSpaceMerge; + } + + /* 18.0.0+/meso Set Alias region extra size. */ + if (meta_flags & Npdm::MetaFlag_EnableAliasRegionExtraSize) { + flags |= svc::CreateProcessFlag_EnableAliasRegionExtraSize; + } + + *out = flags; + R_SUCCEED(); + } + + Result GetCreateProcessParameter(svc::CreateProcessParameter *out, const Meta *meta, u32 flags, os::NativeHandle resource_limit) { + /* Clear output. */ + std::memset(out, 0, sizeof(*out)); + + /* Set name, version, program id, resource limit handle. */ + std::memcpy(out->name, meta->npdm->program_name, sizeof(out->name) - 1); + out->version = meta->npdm->version; + out->program_id = meta->aci->program_id.value; + out->reslimit = resource_limit; + + /* Set flags. */ + R_TRY(GetCreateProcessFlags(std::addressof(out->flags), meta, flags)); + + /* 3.0.0+ System Resource Size. */ + if (hos::GetVersion() >= hos::Version_3_0_0) { + /* Validate size is aligned. */ + R_UNLESS(util::IsAligned(meta->npdm->system_resource_size, os::MemoryBlockUnitSize), ldr::ResultInvalidSize()); + + /* Validate system resource usage. */ + if (meta->npdm->system_resource_size) { + /* Process must be 64-bit. */ + R_UNLESS((out->flags & svc::CreateProcessFlag_AddressSpace64Bit), ldr::ResultInvalidMeta()); + + /* Process must be application or applet. */ + R_UNLESS(IsApplication(meta) || IsApplet(meta), ldr::ResultInvalidMeta()); + + /* Size must be less than or equal to max. */ + R_UNLESS(meta->npdm->system_resource_size <= SystemResourceSizeMax, ldr::ResultInvalidMeta()); + } + out->system_resource_num_pages = meta->npdm->system_resource_size >> 12; + } + + R_SUCCEED(); + } + + u64 GenerateSecureRandom(u64 max) { + /* Generate a cryptographically random number. */ + u64 rand; + crypto::GenerateCryptographicallyRandomBytes(std::addressof(rand), sizeof(rand)); + + /* Coerce into range. */ + return rand % (max + 1); + } + + Result DecideAddressSpaceLayout(ProcessInfo *out, svc::CreateProcessParameter *out_param, const NsoHeader *nso_headers, const bool *has_nso, const ArgumentStore::Entry *argument) { + /* Clear output. */ + out->args_address = 0; + out->args_size = 0; + std::memset(out->nso_address, 0, sizeof(out->nso_address)); + std::memset(out->nso_size, 0, sizeof(out->nso_size)); + + size_t total_size = 0; + bool argument_allocated = false; + + /* Calculate base offsets. */ + for (size_t i = 0; i < Nso_Count; i++) { + if (has_nso[i]) { + out->nso_address[i] = total_size; + const size_t text_end = nso_headers[i].text_dst_offset + nso_headers[i].text_size; + const size_t ro_end = nso_headers[i].ro_dst_offset + nso_headers[i].ro_size; + const size_t rw_end = nso_headers[i].rw_dst_offset + nso_headers[i].rw_size + nso_headers[i].bss_size; + out->nso_size[i] = text_end; + out->nso_size[i] = std::max(out->nso_size[i], ro_end); + out->nso_size[i] = std::max(out->nso_size[i], rw_end); + out->nso_size[i] = util::AlignUp(out->nso_size[i], os::MemoryPageSize); + + total_size += out->nso_size[i]; + + if (!argument_allocated && argument != nullptr) { + out->args_address = total_size; + out->args_size = util::AlignUp(2 * sizeof(u32) + argument->argument_size * 2 + ArgumentStore::ArgumentBufferSize, os::MemoryPageSize); + total_size += out->args_size; + argument_allocated = true; + } + } + } + + /* Calculate ASLR. */ + uintptr_t aslr_start = 0; + size_t aslr_size = 0; + if (hos::GetVersion() >= hos::Version_2_0_0) { + switch (out_param->flags & svc::CreateProcessFlag_AddressSpaceMask) { + case svc::CreateProcessFlag_AddressSpace32Bit: + case svc::CreateProcessFlag_AddressSpace32BitWithoutAlias: + aslr_start = svc::AddressSmallMap32Start; + aslr_size = svc::AddressSmallMap32Size; + break; + case svc::CreateProcessFlag_AddressSpace64BitDeprecated: + aslr_start = svc::AddressSmallMap36Start; + aslr_size = svc::AddressSmallMap36Size; + break; + case svc::CreateProcessFlag_AddressSpace64Bit: + aslr_start = svc::AddressMap39Start; + aslr_size = svc::AddressMap39Size; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } else { + /* On 1.0.0, only 2 address space types existed. */ + if (out_param->flags & svc::CreateProcessFlag_AddressSpace64BitDeprecated) { + aslr_start = svc::AddressSmallMap36Start; + aslr_size = svc::AddressSmallMap36Size; + } else { + aslr_start = svc::AddressSmallMap32Start; + aslr_size = svc::AddressSmallMap32Size; + } + } + R_UNLESS(total_size <= aslr_size, svc::ResultOutOfMemory()); + + /* Set Create Process output. */ + uintptr_t aslr_slide = 0; + size_t free_size = (aslr_size - total_size); + if (out_param->flags & svc::CreateProcessFlag_EnableAslr) { + aslr_slide = GenerateSecureRandom(free_size / os::MemoryBlockUnitSize) * os::MemoryBlockUnitSize; + } + + /* Set out. */ + aslr_start += aslr_slide; + for (size_t i = 0; i < Nso_Count; i++) { + if (has_nso[i]) { + out->nso_address[i] += aslr_start; + } + } + if (out->args_address) { + out->args_address += aslr_start; + } + + out_param->code_address = aslr_start; + out_param->code_num_pages = total_size >> 12; + + R_SUCCEED(); + } + + Result LoadAutoLoadModuleSegment(fs::FileHandle file, const NsoHeader::SegmentInfo *segment, size_t file_size, const u8 *file_hash, bool is_compressed, bool check_hash, uintptr_t map_base, uintptr_t map_end) { + /* Select read size based on compression. */ + if (!is_compressed) { + file_size = segment->size; + } + + /* Validate size. */ + R_UNLESS(file_size <= segment->size, ldr::ResultInvalidNso()); + R_UNLESS(segment->size <= std::numeric_limits<s32>::max(), ldr::ResultInvalidNso()); + + /* Load data from file. */ + uintptr_t load_address = is_compressed ? map_end - file_size : map_base; + size_t read_size; + R_TRY(fs::ReadFile(std::addressof(read_size), file, segment->file_offset, reinterpret_cast<void *>(load_address), file_size)); + R_UNLESS(read_size == file_size, ldr::ResultInvalidNso()); + + /* Uncompress if necessary. */ + if (is_compressed) { + bool decompressed = (util::DecompressLZ4(reinterpret_cast<void *>(map_base), segment->size, reinterpret_cast<const void *>(load_address), file_size) == static_cast<int>(segment->size)); + R_UNLESS(decompressed, ldr::ResultInvalidNso()); + } + + /* Check hash if necessary. */ + if (check_hash) { + u8 hash[crypto::Sha256Generator::HashSize]; + crypto::GenerateSha256(hash, sizeof(hash), reinterpret_cast<void *>(map_base), segment->size); + + R_UNLESS(std::memcmp(hash, file_hash, sizeof(hash)) == 0, ldr::ResultInvalidNso()); + } + + R_SUCCEED(); + } + + Result LoadAutoLoadModule(os::NativeHandle process_handle, fs::FileHandle file, const NsoHeader *nso_header, uintptr_t nso_address, size_t nso_size, bool prevent_code_reads) { + /* Map and read data from file. */ + { + /* Map the process memory. */ + void *mapped_memory = nullptr; + R_TRY(os::MapProcessMemory(std::addressof(mapped_memory), process_handle, nso_address, nso_size, GenerateSecureRandom)); + ON_SCOPE_EXIT { os::UnmapProcessMemory(mapped_memory, process_handle, nso_address, nso_size); }; + + const uintptr_t map_address = reinterpret_cast<uintptr_t>(mapped_memory); + + /* Load NSO segments. */ + R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Text]), nso_header->text_compressed_size, nso_header->text_hash, (nso_header->flags & NsoHeader::Flag_CompressedText) != 0, + (nso_header->flags & NsoHeader::Flag_CheckHashText) != 0, map_address + nso_header->text_dst_offset, map_address + nso_size)); + R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Ro]), nso_header->ro_compressed_size, nso_header->ro_hash, (nso_header->flags & NsoHeader::Flag_CompressedRo) != 0, + (nso_header->flags & NsoHeader::Flag_CheckHashRo) != 0, map_address + nso_header->ro_dst_offset, map_address + nso_size)); + R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Rw]), nso_header->rw_compressed_size, nso_header->rw_hash, (nso_header->flags & NsoHeader::Flag_CompressedRw) != 0, + (nso_header->flags & NsoHeader::Flag_CheckHashRw) != 0, map_address + nso_header->rw_dst_offset, map_address + nso_size)); + + /* Clear unused space to zero. */ + const size_t text_end = nso_header->text_dst_offset + nso_header->text_size; + const size_t ro_end = nso_header->ro_dst_offset + nso_header->ro_size; + const size_t rw_end = nso_header->rw_dst_offset + nso_header->rw_size; + std::memset(reinterpret_cast<void *>(map_address + 0), 0, nso_header->text_dst_offset); + std::memset(reinterpret_cast<void *>(map_address + text_end), 0, nso_header->ro_dst_offset - text_end); + std::memset(reinterpret_cast<void *>(map_address + ro_end), 0, nso_header->rw_dst_offset - ro_end); + std::memset(reinterpret_cast<void *>(map_address + rw_end), 0, nso_header->bss_size); + + /* Apply embedded patches. */ + ApplyEmbeddedPatchesToModule(nso_header->module_id, map_address, nso_size); + + /* Apply IPS patches. */ + LocateAndApplyIpsPatchesToModule(nso_header->module_id, map_address, nso_size); + } + + /* Set permissions. */ + const size_t text_size = util::AlignUp(nso_header->text_size, os::MemoryPageSize); + const size_t ro_size = util::AlignUp(nso_header->ro_size, os::MemoryPageSize); + const size_t rw_size = util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize); + if (text_size) { + R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->text_dst_offset, text_size, prevent_code_reads ? os::MemoryPermission_ExecuteOnly : os::MemoryPermission_ReadExecute)); + } + if (ro_size) { + R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->ro_dst_offset, ro_size, os::MemoryPermission_ReadOnly)); + } + if (rw_size) { + R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->rw_dst_offset, rw_size, os::MemoryPermission_ReadWrite)); + } + + R_SUCCEED(); + } + + Result LoadAutoLoadModules(const ProcessInfo *process_info, const NsoHeader *nso_headers, const bool *has_nso, const ArgumentStore::Entry *argument, bool prevent_code_reads) { + /* Load each NSO. */ + for (size_t i = 0; i < Nso_Count; i++) { + if (has_nso[i]) { + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), GetNsoPath(i), fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + R_TRY(LoadAutoLoadModule(process_info->process_handle, file, nso_headers + i, process_info->nso_address[i], process_info->nso_size[i], prevent_code_reads)); + } + } + + /* Load arguments, if present. */ + if (argument != nullptr) { + /* Write argument data into memory. */ + { + void *map_address = nullptr; + R_TRY(os::MapProcessMemory(std::addressof(map_address), process_info->process_handle, process_info->args_address, process_info->args_size, GenerateSecureRandom)); + ON_SCOPE_EXIT { os::UnmapProcessMemory(map_address, process_info->process_handle, process_info->args_address, process_info->args_size); }; + + ProgramArguments *args = static_cast<ProgramArguments *>(map_address); + std::memset(args, 0, sizeof(*args)); + args->allocated_size = process_info->args_size; + args->arguments_size = argument->argument_size; + std::memcpy(args->arguments, argument->argument, argument->argument_size); + } + + /* Set argument region permissions. */ + /* NOTE: Nintendo uses svc::SetProcessMemoryPermission directly here. */ + R_TRY(os::SetProcessMemoryPermission(process_info->process_handle, process_info->args_address, process_info->args_size, os::MemoryPermission_ReadWrite)); + } + + R_SUCCEED(); + } + + Result CreateProcessAndLoadAutoLoadModules(ProcessInfo *out, const Meta *meta, const NsoHeader *nso_headers, const bool *has_nso, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit) { + /* Get CreateProcessParameter. */ + svc::CreateProcessParameter param; + R_TRY(GetCreateProcessParameter(std::addressof(param), meta, flags, resource_limit)); + + /* Decide on an NSO layout. */ + R_TRY(DecideAddressSpaceLayout(out, std::addressof(param), nso_headers, has_nso, argument)); + + /* Actually create process. */ + svc::Handle process_handle; + R_TRY(svc::CreateProcess(std::addressof(process_handle), std::addressof(param), static_cast<const u32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(u32))); + + /* Set the output handle, and ensure that if we fail after this point we clean it up. */ + out->process_handle = process_handle; + ON_RESULT_FAILURE { svc::CloseHandle(process_handle); }; + + /* Load all auto load modules. */ + R_RETURN(LoadAutoLoadModules(out, nso_headers, has_nso, argument, (meta->npdm->flags & ldr::Npdm::MetaFlag_PreventCodeReads) != 0)); + } + + } + + /* Process Creation API. */ + Result CreateProcess(os::NativeHandle *out, PinId pin_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status, const char *path, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit, const ldr::ProgramAttributes &attrs) { + /* Mount code. */ + AMS_UNUSED(path); + ScopedCodeMount mount(loc, override_status, attrs); + R_TRY(mount.GetResult()); + + /* Load meta, possibly from cache. */ + Meta meta; + R_TRY(LoadMetaFromCache(std::addressof(meta), loc, override_status, attrs.platform)); + + /* Validate meta. */ + R_TRY(ValidateMeta(std::addressof(meta), loc, mount.GetCodeVerificationData())); + + /* Load, validate NSO headers. */ + R_TRY(LoadAutoLoadHeaders(g_nso_headers, g_has_nso)); + R_TRY(CheckAutoLoad(g_nso_headers, g_has_nso)); + + /* Actually create the process and load NSOs into process memory. */ + ProcessInfo info; + R_TRY(CreateProcessAndLoadAutoLoadModules(std::addressof(info), std::addressof(meta), g_nso_headers, g_has_nso, argument, flags, resource_limit)); + + /* Register NSOs with the RoManager. */ + { + /* Nintendo doesn't validate this get, but we do. */ + os::ProcessId process_id = os::GetProcessId(info.process_handle); + + /* Register new process. */ + const auto as_type = GetAddressSpaceType(std::addressof(meta)); + RoManager::GetInstance().RegisterProcess(pin_id, process_id, meta.aci->program_id, as_type == Npdm::AddressSpaceType_64Bit || as_type == Npdm::AddressSpaceType_64BitDeprecated); + + /* Register all NSOs. */ + for (size_t i = 0; i < Nso_Count; i++) { + if (g_has_nso[i]) { + RoManager::GetInstance().AddNso(pin_id, g_nso_headers[i].module_id, info.nso_address[i], info.nso_size[i]); + } + } + } + + /* If we're overriding for HBL, perform HTML document redirection. */ + if (override_status.IsHbl()) { + /* Don't validate result, failure is okay. */ + RedirectHtmlDocumentPathForHbl(loc); + } + + /* Clear the external code for the program. */ + fssystem::DestroyExternalCode(loc.program_id); + + /* Note that we've created the program. */ + SetLaunchedBootProgram(loc.program_id); + + /* Move the process handle to output. */ + *out = info.process_handle; + + R_SUCCEED(); + } + + Result GetProgramInfo(ProgramInfo *out, cfg::OverrideStatus *out_status, const ncm::ProgramLocation &loc, const char *path, const ldr::ProgramAttributes &attrs) { + Meta meta; + + /* Load Meta. */ + { + AMS_UNUSED(path); + + ScopedCodeMount mount(loc, attrs); + R_TRY(mount.GetResult()); + R_TRY(LoadMeta(std::addressof(meta), loc, mount.GetOverrideStatus(), attrs.platform, false)); + if (out_status != nullptr) { + *out_status = mount.GetOverrideStatus(); + } + } + + return GetProgramInfoFromMeta(out, std::addressof(meta)); + } + + Result PinProgram(PinId *out_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status) { + R_UNLESS(RoManager::GetInstance().Allocate(out_id, loc, override_status), ldr::ResultMaxProcess()); + R_SUCCEED(); + } + + Result UnpinProgram(PinId id) { + R_UNLESS(RoManager::GetInstance().Free(id), ldr::ResultNotPinned()); + R_SUCCEED(); + } + + Result GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out, size_t max_out_count, os::ProcessId process_id) { + R_UNLESS(RoManager::GetInstance().GetProcessModuleInfo(out_count, out, max_out_count, process_id), ldr::ResultNotPinned()); + R_SUCCEED(); + } + + Result GetProgramLocationAndOverrideStatusFromPinId(ncm::ProgramLocation *out, cfg::OverrideStatus *out_status, PinId pin_id) { + R_UNLESS(RoManager::GetInstance().GetProgramLocationAndStatus(out, out_status, pin_id), ldr::ResultNotPinned()); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_process_creation.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_process_creation.hpp new file mode 100644 index 00000000..0d24c772 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_process_creation.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "ldr_argument_store.hpp" + +namespace ams::ldr { + + /* Process Creation API. */ + Result CreateProcess(os::NativeHandle *out, PinId pin_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status, const char *path, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit, const ldr::ProgramAttributes &attrs); + Result GetProgramInfo(ProgramInfo *out, cfg::OverrideStatus *out_status, const ncm::ProgramLocation &loc, const char *path, const ldr::ProgramAttributes &attrs); + + Result PinProgram(PinId *out_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status); + Result UnpinProgram(PinId id); + + Result GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out, size_t max_out_count, os::ProcessId process_id); + + Result GetProgramLocationAndOverrideStatusFromPinId(ncm::ProgramLocation *out, cfg::OverrideStatus *out_status, PinId pin_id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_ro_manager.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_ro_manager.cpp new file mode 100644 index 00000000..35ec0045 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_ro_manager.cpp @@ -0,0 +1,181 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ldr_ro_manager.hpp" + +namespace ams::ldr { + + bool RoManager::Allocate(PinId *out, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status) { + /* Ensure that output pin id is set. */ + *out = InvalidPinId; + + /* Allocate a process info. */ + auto *found = this->AllocateProcessInfo(); + if (found == nullptr) { + return false; + } + + /* Setup the process info. */ + std::memset(found, 0, sizeof(*found)); + found->pin_id = { ++m_pin_id }; + found->program_location = loc; + found->override_status = status; + found->in_use = true; + + /* Set the output pin id. */ + *out = found->pin_id; + return true; + } + + bool RoManager::Free(PinId pin_id) { + /* Find the process. */ + auto *found = this->FindProcessInfo(pin_id); + if (found == nullptr) { + return false; + } + + /* Set the process as not in use. */ + found->in_use = false; + + /* Set all the process's nsos as not in use. */ + for (auto i = 0; i < NsoCount; ++i) { + found->nso_infos[i].in_use = false; + } + + return true; + } + + void RoManager::RegisterProcess(PinId pin_id, os::ProcessId process_id, ncm::ProgramId program_id, bool is_64_bit_address_space) { + /* Find the process. */ + auto *found = this->FindProcessInfo(pin_id); + if (found == nullptr) { + return; + } + + /* Set the process id and program id. */ + found->process_id = process_id; + found->program_id = program_id; + AMS_UNUSED(is_64_bit_address_space); + } + + bool RoManager::GetProgramLocationAndStatus(ncm::ProgramLocation *out, cfg::OverrideStatus *out_status, PinId pin_id) { + /* Find the process. */ + auto *found = this->FindProcessInfo(pin_id); + if (found == nullptr) { + return false; + } + + /* Set the output location/status. */ + *out = found->program_location; + *out_status = found->override_status; + return true; + } + + void RoManager::AddNso(PinId pin_id, const u8 *module_id, u64 address, u64 size) { + /* Find the process. */ + auto *found = this->FindProcessInfo(pin_id); + if (found == nullptr) { + return; + } + + /* Allocate an nso. */ + auto *info = this->AllocateNsoInfo(found); + if (info == nullptr) { + return; + } + + /* Copy the information into the nso info. */ + std::memcpy(info->module_info.module_id, module_id, sizeof(info->module_info.module_id)); + info->module_info.address = address; + info->module_info.size = size; + info->in_use = true; + } + + bool RoManager::GetProcessModuleInfo(u32 *out_count, ModuleInfo *out, size_t max_out_count, os::ProcessId process_id) { + /* Find the process. */ + auto *found = this->FindProcessInfo(process_id); + if (found == nullptr) { + return false; + } + + /* Copy allocated nso module infos. */ + size_t count = 0; + for (auto i = 0; i < NsoCount && count < max_out_count; ++i) { + /* Skip unallocated nsos. */ + if (!found->nso_infos[i].in_use) { + continue; + } + + /* Copy out the module info. */ + out[count++] = found->nso_infos[i].module_info; + } + + /* Set the output count. */ + *out_count = count; + return true; + } + + RoManager::ProcessInfo *RoManager::AllocateProcessInfo() { + for (auto i = 0; i < ProcessCount; ++i) { + if (!m_processes[i].in_use) { + return m_processes + i; + } + } + + return nullptr; + } + + RoManager::ProcessInfo *RoManager::FindProcessInfo(PinId pin_id) { + for (auto i = 0; i < ProcessCount; ++i) { + if (m_processes[i].in_use && m_processes[i].pin_id == pin_id) { + return m_processes + i; + } + } + + return nullptr; + } + + RoManager::ProcessInfo *RoManager::FindProcessInfo(os::ProcessId process_id) { + for (auto i = 0; i < ProcessCount; ++i) { + if (m_processes[i].in_use && m_processes[i].process_id == process_id) { + return m_processes + i; + } + } + + return nullptr; + } + + RoManager::ProcessInfo *RoManager::FindProcessInfo(ncm::ProgramId program_id) { + for (auto i = 0; i < ProcessCount; ++i) { + if (m_processes[i].in_use && m_processes[i].program_id == program_id) { + return m_processes + i; + } + } + + return nullptr; + } + + RoManager::NsoInfo *RoManager::AllocateNsoInfo(ProcessInfo *info) { + for (auto i = 0; i < NsoCount; ++i) { + if (!info->nso_infos[i].in_use) { + return info->nso_infos + i; + } + } + + return nullptr; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_ro_manager.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_ro_manager.hpp new file mode 100644 index 00000000..4d9e99f4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/source/ldr_ro_manager.hpp @@ -0,0 +1,65 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ldr { + + class RoManager { + AMS_CONSTINIT_SINGLETON_TRAITS(RoManager); + public: + static constexpr PinId InvalidPinId = {}; + static constexpr int ProcessCount = 0x40; + static constexpr int NsoCount = 0x20; + private: + struct NsoInfo { + bool in_use; + ldr::ModuleInfo module_info; + }; + + struct ProcessInfo { + bool in_use; + PinId pin_id; + os::ProcessId process_id; + ncm::ProgramId program_id; + cfg::OverrideStatus override_status; + ncm::ProgramLocation program_location; + NsoInfo nso_infos[NsoCount]; + }; + private: + ProcessInfo m_processes[ProcessCount]; + u64 m_pin_id; + public: + bool Allocate(PinId *out, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status); + bool Free(PinId pin_id); + + void RegisterProcess(PinId pin_id, os::ProcessId process_id, ncm::ProgramId program_id, bool is_64_bit_address_space); + + bool GetProgramLocationAndStatus(ncm::ProgramLocation *out, cfg::OverrideStatus *out_status, PinId pin_id); + + void AddNso(PinId pin_id, const u8 *module_id, u64 address, u64 size); + + bool GetProcessModuleInfo(u32 *out_count, ModuleInfo *out, size_t max_out_count, os::ProcessId process_id); + private: + ProcessInfo *AllocateProcessInfo(); + ProcessInfo *FindProcessInfo(PinId pin_id); + ProcessInfo *FindProcessInfo(os::ProcessId process_id); + ProcessInfo *FindProcessInfo(ncm::ProgramId program_id); + + NsoInfo *AllocateNsoInfo(ProcessInfo *info); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/loader/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/system_module.mk new file mode 100644 index 00000000..19c207d9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/loader/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := kip + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/memlet/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/memlet/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/memlet/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)/system_module.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)/system_module.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/stratosphere/memlet/memlet.json b/Source/Atmosphere-MTC-Unlock/stratosphere/memlet/memlet.json new file mode 100644 index 00000000..8f44c0ce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/memlet/memlet.json @@ -0,0 +1,93 @@ +{ + "name": "memlet", + "title_id": "0x0100000000000421", + "title_id_range_min": "0x0100000000000421", + "title_id_range_max": "0x0100000000000421", + "main_thread_stack_size": "0x00002000", + "main_thread_priority": 44, + "default_cpu_id": 3, + "process_category": 0, + "is_retail": true, + "pool_partition": 1, + "is_64_bit": true, + "address_space_type": 3, + "disable_device_address_space_merge": true, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_access": ["fatal:u"], + "service_host": ["memlet"], + "kernel_capabilities": [{ + "type": "kernel_flags", + "value": { + "highest_thread_priority": 63, + "lowest_thread_priority": 24, + "lowest_cpu_id": 3, + "highest_cpu_id": 3 + } + }, { + "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", + "svcSynchronizePreemptionState": "0x36", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateSharedMemory": "0x50", + "svcCallSecureMonitor": "0x7f" + } + }, { + "type": "application_type", + "value": 2 + }, { + "type": "min_kernel_version", + "value": "0x0030" + }, { + "type": "handle_table_size", + "value": 0 + }] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/memlet/source/memlet_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/memlet/source/memlet_main.cpp new file mode 100644 index 00000000..ea0ee7c1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/memlet/source/memlet_main.cpp @@ -0,0 +1,75 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "memlet_service.hpp" + +namespace ams { + + namespace memlet { + + namespace { + + using ServerOptions = sf::hipc::DefaultServerManagerOptions; + + constexpr sm::ServiceName MemServiceName = sm::ServiceName::Encode("memlet"); + constexpr size_t MemMaxSessions = 1; + + /* memlet. */ + constexpr size_t NumServers = 1; + constexpr size_t NumSessions = MemMaxSessions; + + sf::hipc::ServerManager<NumServers, ServerOptions, NumSessions> g_server_manager; + + constinit sf::UnmanagedServiceObject<memlet::impl::IService, memlet::Service> g_mem_service_object; + + void InitializeAndLoopIpcServer() { + /* Create services. */ + R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_mem_service_object.GetShared(), MemServiceName, MemMaxSessions)); + + /* Loop forever, servicing our services. */ + g_server_manager.LoopProcess(); + } + + } + + } + + namespace init { + + void InitializeSystemModule() { + /* Initialize our connection to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + + /* Verify that we can sanely execute. */ + ams::CheckApiVersion(); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void Main() { + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(memlet, Main)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(memlet, Main)); + + /* Initialize and service our ipc service. */ + memlet::InitializeAndLoopIpcServer(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/memlet/source/memlet_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/memlet/source/memlet_service.cpp new file mode 100644 index 00000000..ffccc6e8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/memlet/source/memlet_service.cpp @@ -0,0 +1,58 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "memlet_service.hpp" + +namespace ams::memlet { + + Result Service::CreateAppletSharedMemory(sf::Out<u64> out_size, sf::OutMoveHandle out_handle, u64 desired_size) { + /* Create a handle to set the output to when done. */ + os::NativeHandle handle = os::InvalidNativeHandle; + ON_SCOPE_EXIT { out_handle.SetValue(handle, true); }; + + /* Check that the requested size has megabyte alignment and isn't too big. */ + R_UNLESS(util::IsAligned(desired_size, 1_MB), os::ResultInvalidParameter()); + R_UNLESS(desired_size <= 128_MB, os::ResultInvalidParameter()); + + /* Try to create a shared memory of the desired size, giving up 1 MB each iteration. */ + os::SharedMemoryType shmem = {}; + while (true) { + /* If we have zero desired-size left, we've failed. */ + R_UNLESS(desired_size > 0, os::ResultOutOfMemory()); + + /* Try to create a shared memory. */ + if (R_FAILED(os::CreateSharedMemory(std::addressof(shmem), desired_size, os::MemoryPermission_ReadWrite, os::MemoryPermission_ReadWrite))) { + /* We failed, so decrease the size to see if that works. */ + desired_size -= 1_MB; + continue; + } + + /* We successfully created the shared memory. */ + break; + } + + /* Get the native handle for the shared memory we created. */ + handle = os::GetSharedMemoryHandle(std::addressof(shmem)); + + /* HACK: Clear the shared memory object, since we've stolen its handle, and there's no "correct" way to detach. */ + shmem = {}; + + /* We successfully created a shared memory! */ + *out_size = desired_size; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/memlet/source/memlet_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/memlet/source/memlet_service.hpp new file mode 100644 index 00000000..6d003753 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/memlet/source/memlet_service.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#define AMS_MEMLET_I_SERVICE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 65000, Result, CreateAppletSharedMemory, (sf::Out<u64> out_size, sf::OutMoveHandle out_handle, u64 desired_size), (out_size, out_handle, desired_size)) + +AMS_SF_DEFINE_INTERFACE(ams::memlet::impl, IService, AMS_MEMLET_I_SERVICE_INTERFACE_INFO, 0x00000000) + +namespace ams::memlet { + + class Service { + public: + Result CreateAppletSharedMemory(sf::Out<u64> out_size, sf::OutMoveHandle out_handle, u64 desired_size); + }; + static_assert(impl::IsIService<Service>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/memlet/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/memlet/system_module.mk new file mode 100644 index 00000000..d6de42ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/memlet/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := nsp + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ncm/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/ncm/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ncm/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)/system_module.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)/system_module.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/stratosphere/ncm/ncm.json b/Source/Atmosphere-MTC-Unlock/stratosphere/ncm/ncm.json new file mode 100644 index 00000000..40a35d90 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ncm/ncm.json @@ -0,0 +1,70 @@ +{ + "name": "NCM", + "title_id": "0x0100000000000002", + "main_thread_stack_size": "0x00004000", + "main_thread_priority": 49, + "default_cpu_id": 3, + "process_category": 1, + "use_secure_memory": true, + "immortal": true, + "kernel_capabilities": [ + { + "type": "handle_table_size", + "value": 128 + }, { + "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", + "svcSynchronizePreemptionState" : "0x36", + "svcCreateSession" : "0x40", + "svcAcceptSession" : "0x41", + "svcReplyAndReceiveLight" : "0x42", + "svcReplyAndReceive" : "0x43", + "svcReplyAndReceiveWithUserBuffer" : "0x44", + "svcCallSecureMonitor" : "0x7F" + } + } + ] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ncm/source/ncm_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ncm/source/ncm_main.cpp new file mode 100644 index 00000000..a5f1688c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ncm/source/ncm_main.cpp @@ -0,0 +1,262 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams { + + namespace ncm { + + namespace { + + u8 g_heap_memory[1_MB]; + lmem::HeapHandle g_heap_handle; + + void *Allocate(size_t size) { + void *mem = lmem::AllocateFromExpHeap(g_heap_handle, size); + ncm::GetHeapState().Allocate(size); + return mem; + } + + void Deallocate(void *p, size_t size) { + ncm::GetHeapState().Free(size != 0 ? size : lmem::GetExpHeapMemoryBlockSize(p)); + lmem::FreeToExpHeap(g_heap_handle, p); + } + + void InitializeHeap() { + g_heap_handle = lmem::CreateExpHeap(g_heap_memory, sizeof(g_heap_memory), lmem::CreateOption_ThreadSafe); + ncm::GetHeapState().Initialize(g_heap_handle); + } + + } + + namespace { + + struct ContentManagerServerOptions { + static constexpr size_t PointerBufferSize = 0x400; + static constexpr size_t MaxDomains = 0; + static constexpr size_t MaxDomainObjects = 0; + static constexpr bool CanDeferInvokeRequest = false; + static constexpr bool CanManageMitmServers = false; + }; + + constexpr inline size_t ContentManagerNumServers = 1; + constexpr inline size_t ContentManagerManagerSessions = 16; + constexpr inline size_t ContentManagerExtraSessions = 16; + constexpr inline size_t ContentManagerMaxSessions = ContentManagerManagerSessions + ContentManagerExtraSessions; + + constexpr inline sm::ServiceName ContentManagerServiceName = sm::ServiceName::Encode("ncm"); + + alignas(os::ThreadStackAlignment) u8 g_content_manager_thread_stack[16_KB]; + alignas(os::ThreadStackAlignment) u8 g_location_resolver_thread_stack[16_KB]; + + class ContentManagerServerManager : public sf::hipc::ServerManager<ContentManagerNumServers, ContentManagerServerOptions, ContentManagerMaxSessions> { + private: + using ServiceImpl = ncm::ContentManagerImpl; + private: + os::ThreadType m_thread; + sf::SharedPointer<ncm::IContentManager> m_manager; + private: + static void ThreadFunction(void *_this) { + reinterpret_cast<ContentManagerServerManager *>(_this)->LoopProcess(); + } + public: + explicit ContentManagerServerManager(sf::SharedPointer<ncm::IContentManager> manager) : m_manager(manager) { /* ... */ } + + ams::Result Initialize() { + R_RETURN(this->RegisterObjectForServer(m_manager, ContentManagerServiceName, ContentManagerManagerSessions)); + } + + ams::Result StartThreads() { + R_TRY(os::CreateThread(std::addressof(m_thread), ThreadFunction, this, g_content_manager_thread_stack, sizeof(g_content_manager_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(ncm, ContentManagerServerIpcSession))); + os::SetThreadNamePointer(std::addressof(m_thread), AMS_GET_SYSTEM_THREAD_NAME(ncm, ContentManagerServerIpcSession)); + os::StartThread(std::addressof(m_thread)); + R_SUCCEED(); + } + + void Wait() { + os::WaitThread(std::addressof(m_thread)); + } + }; + + struct LocationResolverServerOptions { + static constexpr size_t PointerBufferSize = 0x400; + static constexpr size_t MaxDomains = 0; + static constexpr size_t MaxDomainObjects = 0; + static constexpr bool CanDeferInvokeRequest = false; + static constexpr bool CanManageMitmServers = false; + }; + + constexpr inline size_t LocationResolverNumServers = 1; + constexpr inline size_t LocationResolverManagerSessions = 16; + constexpr inline size_t LocationResolverExtraSessions = 16; + constexpr inline size_t LocationResolverMaxSessions = LocationResolverManagerSessions + LocationResolverExtraSessions; + + constexpr inline sm::ServiceName LocationResolverServiceName = sm::ServiceName::Encode("lr"); + + class LocationResolverServerManager : public sf::hipc::ServerManager<LocationResolverNumServers, LocationResolverServerOptions, LocationResolverMaxSessions> { + private: + using ServiceImpl = lr::LocationResolverManagerImpl; + private: + os::ThreadType m_thread; + sf::SharedPointer<lr::ILocationResolverManager> m_manager; + private: + static void ThreadFunction(void *_this) { + reinterpret_cast<LocationResolverServerManager *>(_this)->LoopProcess(); + } + public: + LocationResolverServerManager(sf::SharedPointer<lr::ILocationResolverManager> manager) : m_manager(manager) { /* ... */ } + + ams::Result Initialize() { + R_RETURN(this->RegisterObjectForServer(m_manager, LocationResolverServiceName, LocationResolverManagerSessions)); + } + + ams::Result StartThreads() { + R_TRY(os::CreateThread(std::addressof(m_thread), ThreadFunction, this, g_location_resolver_thread_stack, sizeof(g_location_resolver_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(ncm, LocationResolverServerIpcSession))); + os::SetThreadNamePointer(std::addressof(m_thread), AMS_GET_SYSTEM_THREAD_NAME(ncm, LocationResolverServerIpcSession)); + os::StartThread(std::addressof(m_thread)); + R_SUCCEED(); + } + + void Wait() { + os::WaitThread(std::addressof(m_thread)); + } + }; + + sf::UnmanagedServiceObject<ncm::IContentManager, ncm::ContentManagerImpl> g_ncm_manager_service_object; + ContentManagerServerManager g_ncm_server_manager(g_ncm_manager_service_object.GetShared()); + + sf::UnmanagedServiceObject<lr::ILocationResolverManager, lr::LocationResolverManagerImpl> g_lr_manager_service_object; + LocationResolverServerManager g_lr_server_manager(g_lr_manager_service_object.GetShared()); + + /* Compile-time configuration. */ + #ifdef NCM_BUILD_FOR_INTITIALIZE + constexpr inline bool BuildSystemDatabase = true; + #else + constexpr inline bool BuildSystemDatabase = false; + #endif + + #ifdef NCM_BUILD_FOR_SAFEMODE + constexpr inline bool ImportSystemDatabaseFromSignedSystemPartitionOnSdCard = true; + #else + constexpr inline bool ImportSystemDatabaseFromSignedSystemPartitionOnSdCard = false; + #endif + + static_assert(!(BuildSystemDatabase && ImportSystemDatabaseFromSignedSystemPartitionOnSdCard), "Invalid NCM build configuration!"); + + constexpr inline ncm::ContentManagerConfig ManagerConfig = { BuildSystemDatabase, ImportSystemDatabaseFromSignedSystemPartitionOnSdCard }; + + } + + void NcmMain() { + /* Initialize spl. */ + spl::Initialize(); + ON_SCOPE_EXIT { spl::Finalize(); }; + + /* Initialize fs. */ + fs::InitializeWithMultiSessionForSystem(); + fs::SetAllocator(Allocate, Deallocate); + fs::SetEnabledAutoAbort(false); + + /* Initialize ncm api. */ + /* NOTE: Nintendo does this after initializing and starting threads. */ + ncm::InitializeWithObject(g_ncm_manager_service_object.GetShared()); + + /* Create and initialize the content manager. */ + R_ABORT_UNLESS(g_ncm_manager_service_object.GetImpl().Initialize(ManagerConfig)); + + + /* Initialize ncm's server and start threads. */ + R_ABORT_UNLESS(g_ncm_server_manager.Initialize()); + R_ABORT_UNLESS(g_ncm_server_manager.StartThreads()); + + /* Initialize lr's server and start threads. */ + R_ABORT_UNLESS(g_lr_server_manager.Initialize()); + R_ABORT_UNLESS(g_lr_server_manager.StartThreads()); + + /* Wait indefinitely. */ + g_ncm_server_manager.Wait(); + g_lr_server_manager.Wait(); + } + + } + + namespace init { + + void InitializeSystemModule() { + /* Initialize heap. */ + ncm::InitializeHeap(); + + /* Initialize our connection to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void NORETURN Exit(int rc) { + AMS_UNUSED(rc); + AMS_ABORT("Exit called by immortal process"); + } + + void Main() { + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(ncm, MainWaitThreads)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(ncm, MainWaitThreads)); + + /* Invoke NCM main. */ + ncm::NcmMain(); + + /* This can never be reached. */ + AMS_ASSUME(false); + } + +} + +/* Override operator new. */ +void *operator new(size_t size) { + return ams::ncm::Allocate(size); +} + +void *operator new(size_t size, const std::nothrow_t &) { + return ams::ncm::Allocate(size); +} + +void operator delete(void *p) { + return ams::ncm::Deallocate(p, 0); +} + +void operator delete(void *p, size_t size) { + return ams::ncm::Deallocate(p, size); +} + +void *operator new[](size_t size) { + return ams::ncm::Allocate(size); +} + +void *operator new[](size_t size, const std::nothrow_t &) { + return ams::ncm::Allocate(size); +} + +void operator delete[](void *p) { + return ams::ncm::Deallocate(p, 0); +} + +void operator delete[](void *p, size_t size) { + return ams::ncm::Deallocate(p, size); +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ncm/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/ncm/system_module.mk new file mode 100644 index 00000000..19c207d9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ncm/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := kip + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pgl/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/pgl/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pgl/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)/system_module.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)/system_module.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/stratosphere/pgl/pgl.json b/Source/Atmosphere-MTC-Unlock/stratosphere/pgl/pgl.json new file mode 100644 index 00000000..d1c4a257 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pgl/pgl.json @@ -0,0 +1,90 @@ +{ + "name": "pgl", + "title_id": "0x0100000000000042", + "title_id_range_min": "0x0100000000000042", + "title_id_range_max": "0x0100000000000042", + "main_thread_stack_size": "0x00004000", + "main_thread_priority": 49, + "default_cpu_id": 3, + "process_category": 0, + "is_retail": true, + "pool_partition": 2, + "is_64_bit": true, + "address_space_type": 3, + "disable_device_address_space_merge": true, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_access": ["erpt:c", "fatal:u", "fsp-srv", "ldr:shel", "lm", "lr", "pm:shell", "set", "set:sys"], + "service_host": ["pgl"], + "kernel_capabilities": [{ + "type": "kernel_flags", + "value": { + "highest_thread_priority": 63, + "lowest_thread_priority": 24, + "lowest_cpu_id": 3, + "highest_cpu_id": 3 + } + }, { + "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", + "svcSynchronizePreemptionState": "0x36", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcCallSecureMonitor": "0x7F" + } + }, { + "type": "min_kernel_version", + "value": "0x0091" + }, { + "type": "handle_table_size", + "value": 256 + }] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pgl/source/pgl_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/pgl/source/pgl_main.cpp new file mode 100644 index 00000000..50d5ca11 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pgl/source/pgl_main.cpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams { + + namespace init { + + void InitializeSystemModule() { + /* Initialize heap. */ + pgl::srv::InitializeHeap(); + + /* Initialize our connection to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + + /* Initialize fs. */ + fs::InitializeForSystem(); + fs::SetAllocator(pgl::srv::Allocate, pgl::srv::Deallocate); + fs::SetEnabledAutoAbort(false); + + /* Initialize other services we need. */ + R_ABORT_UNLESS(setInitialize()); + R_ABORT_UNLESS(setsysInitialize()); + R_ABORT_UNLESS(pmshellInitialize()); + R_ABORT_UNLESS(ldrShellInitialize()); + R_ABORT_UNLESS(lrInitialize()); + + /* Verify that we can sanely execute. */ + ams::CheckApiVersion(); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void Main() { + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(pgl, Main)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(pgl, Main)); + + /* Initialize and start the server. */ + pgl::srv::StartServer(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pgl/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/pgl/system_module.mk new file mode 100644 index 00000000..d6de42ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pgl/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := nsp + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/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)/system_module.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)/system_module.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/stratosphere/pm/pm.json b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/pm.json new file mode 100644 index 00000000..4ffe5e94 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/pm.json @@ -0,0 +1,93 @@ +{ + "name": "ProcessManager", + "title_id": "0x0100000000000003", + "main_thread_stack_size": "0x00003000", + "main_thread_priority": 49, + "default_cpu_id": 3, + "process_category": 1, + "use_secure_memory": true, + "immortal": true, + "kernel_capabilities": [ + { + "type": "handle_table_size", + "value": 128 + }, + { + "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", + "svcGetResourceLimitLimitValue": "0x30", + "svcGetResourceLimitCurrentValue": "0x31", + "svcWaitForAddress": "0x34", + "svcSignalToAddress": "0x35", + "svcSynchronizePreemptionState": "0x36", + "svcGetResourceLimitPeakValue": "0x37", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcSetUnsafeLimit": "0x4a", + "svcDebugActiveProcess": "0x60", + "svcGetDebugEvent": "0x63", + "svcGetProcessList": "0x65", + "svcStartProcess": "0x7a", + "svcTerminateProcess": "0x7b", + "svcGetProcessInfo": "0x7c", + "svcCreateResourceLimit": "0x7d", + "svcSetResourceLimitLimitValue": "0x7e", + "svcGetSystemInfo": "0x6f", + "svcCallSecureMonitor": "0x7F" + } + }, + { + "type": "debug_flags", + "value": { + "allow_debug": false, + "force_debug_prod": false, + "force_debug": true + } + } + ] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_attributes.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_attributes.hpp new file mode 100644 index 00000000..b622be26 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_attributes.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::pm::impl { + + struct ProcessAttributes { + u8 unknown[3]; + ldr::ProgramAttributes program_attrs; + }; + static_assert(sizeof(ProcessAttributes) == 5); + + constexpr inline ProcessAttributes ProcessAttributes_Nx = { + .unknown = {}, + .program_attrs = { + .platform = ncm::ContentMetaPlatform::Nx, + .content_attributes = fs::ContentAttributes_None, + }, + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_info.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_info.cpp new file mode 100644 index 00000000..d472803d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_info.cpp @@ -0,0 +1,121 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pm_process_info.hpp" + +namespace ams::pm::impl { + + namespace { + + template<size_t MaxProcessInfos> + class ProcessInfoAllocator { + NON_COPYABLE(ProcessInfoAllocator); + NON_MOVEABLE(ProcessInfoAllocator); + static_assert(MaxProcessInfos >= 0x40, "MaxProcessInfos is too small."); + private: + util::TypedStorage<ProcessInfo> m_process_info_storages[MaxProcessInfos]{}; + bool m_process_info_allocated[MaxProcessInfos]{}; + os::SdkMutex m_lock{}; + private: + constexpr inline size_t GetProcessInfoIndex(ProcessInfo *process_info) const { + return process_info - GetPointer(m_process_info_storages[0]); + } + public: + constexpr ProcessInfoAllocator() = default; + + template<typename... Args> + ProcessInfo *AllocateProcessInfo(Args &&... args) { + std::scoped_lock lk(m_lock); + + for (size_t i = 0; i < MaxProcessInfos; i++) { + if (!m_process_info_allocated[i]) { + m_process_info_allocated[i] = true; + + std::memset(m_process_info_storages + i, 0, sizeof(m_process_info_storages[i])); + + return util::ConstructAt(m_process_info_storages[i], std::forward<Args>(args)...); + } + } + + return nullptr; + } + + void FreeProcessInfo(ProcessInfo *process_info) { + std::scoped_lock lk(m_lock); + + const size_t index = this->GetProcessInfoIndex(process_info); + AMS_ABORT_UNLESS(index < MaxProcessInfos); + AMS_ABORT_UNLESS(m_process_info_allocated[index]); + + util::DestroyAt(m_process_info_storages[index]); + m_process_info_allocated[index] = false; + } + }; + + /* Process lists. */ + constinit ProcessList g_process_list; + constinit ProcessList g_exit_list; + + /* Process Info Allocation. */ + /* Note: The kernel slabheap is size 0x50 -- we allow slightly larger to account for the dead process list. */ + constexpr size_t MaxProcessCount = 0x60; + constinit ProcessInfoAllocator<MaxProcessCount> g_process_info_allocator; + + } + + ProcessInfo::ProcessInfo(os::NativeHandle h, os::ProcessId pid, ldr::PinId pin, const ncm::ProgramLocation &l, const cfg::OverrideStatus &s, const ProcessAttributes &attrs) : m_process_id(pid), m_pin_id(pin), m_loc(l), m_status(s), m_handle(h), m_state(svc::ProcessState_Created), m_flags(0), m_attrs(attrs) { + os::InitializeMultiWaitHolder(std::addressof(m_multi_wait_holder), m_handle); + os::SetMultiWaitHolderUserData(std::addressof(m_multi_wait_holder), reinterpret_cast<uintptr_t>(this)); + } + + ProcessInfo::~ProcessInfo() { + this->Cleanup(); + } + + void ProcessInfo::Cleanup() { + if (m_handle != os::InvalidNativeHandle) { + /* Unregister the process. */ + fsprUnregisterProgram(m_process_id.value); + sm::manager::UnregisterProcess(m_process_id); + ldr::pm::UnpinProgram(m_pin_id); + + /* Close the process's handle. */ + os::CloseNativeHandle(m_handle); + m_handle = os::InvalidNativeHandle; + } + } + + ProcessListAccessor GetProcessList() { + return ProcessListAccessor(g_process_list); + } + + ProcessListAccessor GetExitList() { + return ProcessListAccessor(g_exit_list); + } + + ProcessInfo *AllocateProcessInfo(svc::Handle process_handle, os::ProcessId process_id, ldr::PinId pin_id, const ncm::ProgramLocation &location, const cfg::OverrideStatus &override_status, const ProcessAttributes &attrs) { + return g_process_info_allocator.AllocateProcessInfo(process_handle, process_id, pin_id, location, override_status, attrs); + } + + void CleanupProcessInfo(ProcessListAccessor &list, ProcessInfo *process_info) { + /* Remove the process from the list. */ + list->Remove(process_info); + + /* Delete the process. */ + g_process_info_allocator.FreeProcessInfo(process_info); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_info.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_info.hpp new file mode 100644 index 00000000..66e64c43 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_info.hpp @@ -0,0 +1,247 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "pm_process_manager.hpp" +#include "pm_process_attributes.hpp" + +namespace ams::pm::impl { + + class ProcessList; + + class ProcessInfo { + friend class ProcessList; + NON_COPYABLE(ProcessInfo); + NON_MOVEABLE(ProcessInfo); + private: + enum Flag : u32 { + Flag_SignalOnExit = (1 << 0), + Flag_ExceptionOccurred = (1 << 1), + Flag_ExceptionWaitingAttach = (1 << 2), + Flag_SignalOnDebugEvent = (1 << 3), + Flag_SuspendedStateChanged = (1 << 4), + Flag_Suspended = (1 << 5), + Flag_Application = (1 << 6), + Flag_SignalOnStart = (1 << 7), + Flag_StartedStateChanged = (1 << 8), + Flag_UnhandledException = (1 << 9), + }; + private: + util::IntrusiveListNode m_list_node; + const os::ProcessId m_process_id; + const ldr::PinId m_pin_id; + const ncm::ProgramLocation m_loc; + const cfg::OverrideStatus m_status; + os::NativeHandle m_handle; + svc::ProcessState m_state; + u32 m_flags; + ProcessAttributes m_attrs; + os::MultiWaitHolderType m_multi_wait_holder; + private: + void SetFlag(Flag flag) { + m_flags |= flag; + } + + void ClearFlag(Flag flag) { + m_flags &= ~flag; + } + + bool HasFlag(Flag flag) const { + return (m_flags & flag); + } + public: + ProcessInfo(os::NativeHandle h, os::ProcessId pid, ldr::PinId pin, const ncm::ProgramLocation &l, const cfg::OverrideStatus &s, const ProcessAttributes &attrs); + ~ProcessInfo(); + void Cleanup(); + + os::MultiWaitHolderType *GetMultiWaitHolder() { + return std::addressof(m_multi_wait_holder); + } + + os::NativeHandle GetHandle() const { + return m_handle; + } + + os::ProcessId GetProcessId() const { + return m_process_id; + } + + ldr::PinId GetPinId() const { + return m_pin_id; + } + + const ncm::ProgramLocation &GetProgramLocation() const { + return m_loc; + } + + const cfg::OverrideStatus &GetOverrideStatus() const { + return m_status; + } + + const ProcessAttributes &GetProcessAttributes() const { + return m_attrs; + } + + svc::ProcessState GetState() const { + return m_state; + } + + void SetState(svc::ProcessState state) { + m_state = state; + } + + bool HasStarted() const { + return m_state != svc::ProcessState_Created && m_state != svc::ProcessState_CreatedAttached; + } + + bool HasTerminated() const { + return m_state == svc::ProcessState_Terminated; + } + +#define DEFINE_FLAG_SET(flag) \ + void Set##flag() { \ + this->SetFlag(Flag_##flag); \ + } + +#define DEFINE_FLAG_GET(get, flag) \ + bool get##flag() const { \ + return this->HasFlag(Flag_##flag); \ + } + +#define DEFINE_FLAG_CLEAR(flag) \ + void Clear##flag() { \ + this->ClearFlag(Flag_##flag); \ + } + + DEFINE_FLAG_SET(SignalOnExit) + DEFINE_FLAG_GET(Should, SignalOnExit) + + /* This needs a manual setter, because it sets two flags. */ + void SetExceptionOccurred() { + this->SetFlag(Flag_ExceptionOccurred); + this->SetFlag(Flag_UnhandledException); + } + + DEFINE_FLAG_GET(Has, ExceptionOccurred) + DEFINE_FLAG_GET(Has, ExceptionWaitingAttach) + DEFINE_FLAG_GET(Has, UnhandledException) + + DEFINE_FLAG_SET(ExceptionWaitingAttach) + + DEFINE_FLAG_CLEAR(ExceptionOccurred) + DEFINE_FLAG_CLEAR(ExceptionWaitingAttach) + DEFINE_FLAG_CLEAR(UnhandledException) + + DEFINE_FLAG_SET(SignalOnDebugEvent) + DEFINE_FLAG_GET(Should, SignalOnDebugEvent) + + DEFINE_FLAG_SET(SuspendedStateChanged) + DEFINE_FLAG_GET(Has, SuspendedStateChanged) + DEFINE_FLAG_CLEAR(SuspendedStateChanged) + + DEFINE_FLAG_SET(Suspended) + DEFINE_FLAG_GET(Is, Suspended) + DEFINE_FLAG_CLEAR(Suspended) + + DEFINE_FLAG_SET(Application) + DEFINE_FLAG_GET(Is, Application) + + DEFINE_FLAG_SET(SignalOnStart) + DEFINE_FLAG_GET(Should, SignalOnStart) + DEFINE_FLAG_CLEAR(SignalOnStart) + + DEFINE_FLAG_SET(StartedStateChanged) + DEFINE_FLAG_GET(Has, StartedStateChanged) + DEFINE_FLAG_CLEAR(StartedStateChanged) + +#undef DEFINE_FLAG_SET +#undef DEFINE_FLAG_GET +#undef DEFINE_FLAG_CLEAR + }; + + class ProcessList final : public util::IntrusiveListMemberTraits<&ProcessInfo::m_list_node>::ListType { + private: + os::SdkMutex m_lock; + public: + constexpr ProcessList() : m_lock() { /* ... */ } + + void Lock() { + m_lock.Lock(); + } + + void Unlock() { + m_lock.Unlock(); + } + + void Remove(ProcessInfo *process_info) { + this->erase(this->iterator_to(*process_info)); + } + + ProcessInfo *Find(os::ProcessId process_id) { + for (auto &info : *this) { + if (info.GetProcessId() == process_id) { + return std::addressof(info); + } + } + return nullptr; + } + + ProcessInfo *Find(ncm::ProgramId program_id) { + for (auto &info : *this) { + if (info.GetProgramLocation().program_id == program_id) { + return std::addressof(info); + } + } + return nullptr; + } + + }; + + class ProcessListAccessor final { + private: + ProcessList &m_list; + public: + explicit ProcessListAccessor(ProcessList &l) : m_list(l) { + m_list.Lock(); + } + + ~ProcessListAccessor() { + m_list.Unlock(); + } + + ProcessList *operator->() { + return std::addressof(m_list); + } + + const ProcessList *operator->() const { + return std::addressof(m_list); + } + + ProcessList &operator*() { + return m_list; + } + + const ProcessList &operator*() const { + return m_list; + } + }; + + ProcessListAccessor GetProcessList(); + ProcessListAccessor GetExitList(); + + ProcessInfo *AllocateProcessInfo(svc::Handle process_handle, os::ProcessId process_id, ldr::PinId pin_id, const ncm::ProgramLocation &location, const cfg::OverrideStatus &override_status, const ProcessAttributes &attrs); + void CleanupProcessInfo(ProcessListAccessor &list, ProcessInfo *process_info); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_manager.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_manager.cpp new file mode 100644 index 00000000..8a4d7ba1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_manager.cpp @@ -0,0 +1,510 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pm_process_manager.hpp" +#include "pm_process_tracker.hpp" +#include "pm_process_info.hpp" +#include "pm_spec.hpp" + +namespace ams::pm::impl { + + namespace { + + /* Types. */ + enum HookType { + HookType_ProgramId = (1 << 0), + HookType_Application = (1 << 1), + }; + + #define GET_FLAG_MASK(flag) (hos_version >= hos::Version_5_0_0 ? static_cast<u32>(LaunchFlags_##flag) : static_cast<u32>(LaunchFlagsDeprecated_##flag)) + + inline bool ShouldSignalOnExit(u32 launch_flags) { + const auto hos_version = hos::GetVersion(); + return launch_flags & GET_FLAG_MASK(SignalOnExit); + } + + inline bool ShouldSignalOnStart(u32 launch_flags) { + const auto hos_version = hos::GetVersion(); + if (hos_version < hos::Version_2_0_0) { + return false; + } + return launch_flags & GET_FLAG_MASK(SignalOnStart); + } + + inline bool ShouldSignalOnException(u32 launch_flags) { + const auto hos_version = hos::GetVersion(); + return launch_flags & GET_FLAG_MASK(SignalOnException); + } + + inline bool ShouldSignalOnDebugEvent(u32 launch_flags) { + const auto hos_version = hos::GetVersion(); + return launch_flags & GET_FLAG_MASK(SignalOnDebugEvent); + } + + inline bool ShouldStartSuspended(u32 launch_flags) { + const auto hos_version = hos::GetVersion(); + return launch_flags & GET_FLAG_MASK(StartSuspended); + } + + inline bool ShouldDisableAslr(u32 launch_flags) { + const auto hos_version = hos::GetVersion(); + return launch_flags & GET_FLAG_MASK(DisableAslr); + } + + #undef GET_FLAG_MASK + + /* Process Tracking globals. */ + constinit ProcessTracker g_process_tracker; + alignas(os::ThreadStackAlignment) constinit u8 g_process_track_thread_stack[8_KB]; + + /* Global events. */ + constinit os::SystemEventType g_hook_to_create_process_event; + constinit os::SystemEventType g_hook_to_create_application_process_event; + constinit os::SystemEventType g_boot_finished_event; + + /* Hook globals. */ + constinit std::atomic<ncm::ProgramId> g_program_id_hook; + constinit std::atomic<bool> g_application_hook; + + /* Helpers. */ + void CreateDebuggerEvent() { + /* Create debugger hook events. */ + R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_hook_to_create_process_event), os::EventClearMode_AutoClear, true)); + R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_hook_to_create_application_process_event), os::EventClearMode_AutoClear, true)); + } + + inline u32 GetLoaderCreateProcessFlags(u32 launch_flags) { + u32 ldr_flags = 0; + + if (ShouldSignalOnException(launch_flags) || (hos::GetVersion() >= hos::Version_2_0_0 && !ShouldStartSuspended(launch_flags))) { + ldr_flags |= ldr::CreateProcessFlag_EnableDebug; + } + if (ShouldDisableAslr(launch_flags)) { + ldr_flags |= ldr::CreateProcessFlag_DisableAslr; + } + + return ldr_flags; + } + + bool HasApplicationProcess() { + auto list = GetProcessList(); + + for (auto &process : *list) { + if (process.IsApplication()) { + return true; + } + } + + return false; + } + + Result StartProcess(ProcessInfo *process_info, const ldr::ProgramInfo *program_info) { + R_TRY(svc::StartProcess(process_info->GetHandle(), program_info->main_thread_priority, program_info->default_cpu_id, program_info->main_thread_stack_size)); + process_info->SetState(svc::ProcessState_Running); + R_SUCCEED(); + } + + Result LaunchProgramImpl(ProcessInfo **out_process_info, os::ProcessId *out_process_id, const ncm::ProgramLocation &loc, u32 flags, const ProcessAttributes &attrs) { + /* Set the output to nullptr, if we fail. */ + *out_process_info = nullptr; + + /* Get Program Info. */ + ldr::ProgramInfo program_info; + cfg::OverrideStatus override_status; + R_TRY(ldr::pm::AtmosphereGetProgramInfo(std::addressof(program_info), std::addressof(override_status), loc, attrs.program_attrs)); + const bool is_application = (program_info.flags & ldr::ProgramInfoFlag_ApplicationTypeMask) == ldr::ProgramInfoFlag_Application; + const bool allow_debug = (program_info.flags & ldr::ProgramInfoFlag_AllowDebug) || hos::GetVersion() < hos::Version_2_0_0; + + /* Ensure we only try to run one application. */ + R_UNLESS(!is_application || !HasApplicationProcess(), pm::ResultApplicationRunning()); + + /* Fix the program location to use the right program id. */ + const ncm::ProgramLocation fixed_location = ncm::ProgramLocation::Make(program_info.program_id, static_cast<ncm::StorageId>(loc.storage_id)); + + /* Pin and create the process. */ + os::NativeHandle process_handle; + ldr::PinId pin_id; + { + /* Pin the program with loader. */ + R_TRY(ldr::pm::AtmospherePinProgram(std::addressof(pin_id), fixed_location, override_status)); + + /* If we fail after now, unpin. */ + ON_RESULT_FAILURE { ldr::pm::UnpinProgram(pin_id); }; + + /* Ensure we can talk to mitm services. */ + { + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(bool, s_initialized_mitm, false); + if (!s_initialized_mitm) { + mitm::pm::Initialize(); + s_initialized_mitm = true; + } + } + + /* Determine boost size for mitm. */ + u64 mitm_boost_size = 0; + R_TRY(mitm::pm::PrepareLaunchProgram(std::addressof(mitm_boost_size), program_info.program_id, override_status, is_application)); + + if (mitm_boost_size > 0 || is_application) { + R_ABORT_UNLESS(BoostSystemMemoryResourceLimitForMitm(mitm_boost_size)); + } + ON_RESULT_FAILURE_2 { if (mitm_boost_size > 0 || is_application) { R_ABORT_UNLESS(BoostSystemMemoryResourceLimitForMitm(0)); } }; + + /* Ensure resources are available. */ + WaitResourceAvailable(std::addressof(program_info)); + + /* Actually create the process. */ + R_TRY(ldr::pm::CreateProcess(std::addressof(process_handle), pin_id, GetLoaderCreateProcessFlags(flags), GetResourceLimitHandle(std::addressof(program_info)), attrs.program_attrs)); + } + + /* Get the process id. */ + os::ProcessId process_id = os::GetProcessId(process_handle); + + /* Make new process info. */ + ProcessInfo *process_info = AllocateProcessInfo(process_handle, process_id, pin_id, fixed_location, override_status, attrs); + AMS_ABORT_UNLESS(process_info != nullptr); + + /* Add the new process info to the process list. */ + { + auto list = GetProcessList(); + list->push_back(*process_info); + } + + /* Prevent resource leakage if register fails. */ + ON_RESULT_FAILURE { + auto list = GetProcessList(); + process_info->Cleanup(); + CleanupProcessInfo(list, process_info); + }; + + const u8 *acid_sac = program_info.ac_buffer; + const u8 *aci_sac = acid_sac + program_info.acid_sac_size; + const u8 *acid_fac = aci_sac + program_info.aci_sac_size; + const u8 *aci_fah = acid_fac + program_info.acid_fac_size; + + /* Register with FS and SM. */ + R_TRY(fsprRegisterProgram(static_cast<u64>(process_id), static_cast<u64>(fixed_location.program_id), static_cast<NcmStorageId>(fixed_location.storage_id), aci_fah, program_info.aci_fah_size, acid_fac, program_info.acid_fac_size, 0)); + R_TRY(sm::manager::RegisterProcess(process_id, fixed_location.program_id, override_status, acid_sac, program_info.acid_sac_size, aci_sac, program_info.aci_sac_size)); + + /* Set flags. */ + if (is_application) { + process_info->SetApplication(); + } + if (ShouldSignalOnStart(flags) && allow_debug) { + process_info->SetSignalOnStart(); + } + if (ShouldSignalOnExit(flags)) { + process_info->SetSignalOnExit(); + } + if (ShouldSignalOnDebugEvent(flags) && allow_debug) { + process_info->SetSignalOnDebugEvent(); + } + + /* Process hooks/signaling. */ + if (fixed_location.program_id == g_program_id_hook) { + os::SignalSystemEvent(std::addressof(g_hook_to_create_process_event)); + g_program_id_hook = ncm::InvalidProgramId; + } else if (is_application && g_application_hook) { + os::SignalSystemEvent(std::addressof(g_hook_to_create_application_process_event)); + g_application_hook = false; + } else if (!ShouldStartSuspended(flags)) { + R_TRY(StartProcess(process_info, std::addressof(program_info))); + } + + *out_process_id = process_id; + *out_process_info = process_info; + R_SUCCEED(); + } + + } + + /* Initialization. */ + Result InitializeProcessManager() { + /* Create events. */ + CreateProcessEvent(); + CreateDebuggerEvent(); + R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_boot_finished_event), os::EventClearMode_AutoClear, true)); + + /* Initialize resource limits. */ + R_TRY(InitializeSpec()); + + /* Initialize the process tracker. */ + g_process_tracker.Initialize(g_process_track_thread_stack, sizeof(g_process_track_thread_stack)); + + /* Start the process tracker thread. */ + g_process_tracker.StartThread(); + + R_SUCCEED(); + } + + /* Process Management. */ + Result LaunchProgram(os::ProcessId *out_process_id, const ncm::ProgramLocation &loc, u32 flags) { + /* Launch the program. */ + ProcessInfo *process_info = nullptr; + R_TRY(LaunchProgramImpl(std::addressof(process_info), out_process_id, loc, flags, ProcessAttributes_Nx)); + + /* Register the process info with the tracker. */ + g_process_tracker.QueueEntry(process_info); + R_SUCCEED(); + } + + Result StartProcess(os::ProcessId process_id) { + auto list = GetProcessList(); + + auto process_info = list->Find(process_id); + R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); + R_UNLESS(!process_info->HasStarted(), pm::ResultAlreadyStarted()); + + ldr::ProgramInfo program_info; + R_TRY(ldr::pm::GetProgramInfo(std::addressof(program_info), process_info->GetProgramLocation(), process_info->GetProcessAttributes().program_attrs)); + R_RETURN(StartProcess(process_info, std::addressof(program_info))); + } + + Result TerminateProcess(os::ProcessId process_id) { + auto list = GetProcessList(); + + auto process_info = list->Find(process_id); + R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); + + R_RETURN(svc::TerminateProcess(process_info->GetHandle())); + } + + Result TerminateProgram(ncm::ProgramId program_id) { + auto list = GetProcessList(); + + auto process_info = list->Find(program_id); + R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); + + R_RETURN(svc::TerminateProcess(process_info->GetHandle())); + } + + Result GetProcessEventInfo(ProcessEventInfo *out) { + /* Check for event from current process. */ + { + auto list = GetProcessList(); + + + for (auto &process : *list) { + if (process.HasStarted() && process.HasStartedStateChanged()) { + process.ClearStartedStateChanged(); + out->event = GetProcessEventValue(ProcessEvent::Started); + out->process_id = process.GetProcessId(); + R_SUCCEED(); + } + if (process.HasSuspendedStateChanged()) { + process.ClearSuspendedStateChanged(); + if (process.IsSuspended()) { + out->event = GetProcessEventValue(ProcessEvent::DebugBreak); + } else { + out->event = GetProcessEventValue(ProcessEvent::DebugRunning); + } + out->process_id = process.GetProcessId(); + R_SUCCEED(); + } + if (process.HasExceptionOccurred()) { + process.ClearExceptionOccurred(); + out->event = GetProcessEventValue(ProcessEvent::Exception); + out->process_id = process.GetProcessId(); + R_SUCCEED(); + } + if (hos::GetVersion() < hos::Version_5_0_0 && process.ShouldSignalOnExit() && process.HasTerminated()) { + out->event = GetProcessEventValue(ProcessEvent::Exited); + out->process_id = process.GetProcessId(); + R_SUCCEED(); + } + } + } + + /* Check for event from exited process. */ + if (hos::GetVersion() >= hos::Version_5_0_0) { + auto exit_list = GetExitList(); + + if (!exit_list->empty()) { + auto &process_info = exit_list->front(); + out->event = GetProcessEventValue(ProcessEvent::Exited); + out->process_id = process_info.GetProcessId(); + + CleanupProcessInfo(exit_list, std::addressof(process_info)); + R_SUCCEED(); + } + } + + out->process_id = os::ProcessId{}; + out->event = GetProcessEventValue(ProcessEvent::None); + R_SUCCEED(); + } + + Result CleanupProcess(os::ProcessId process_id) { + auto list = GetProcessList(); + + auto process_info = list->Find(process_id); + R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); + R_UNLESS(process_info->HasTerminated(), pm::ResultNotTerminated()); + + CleanupProcessInfo(list, process_info); + R_SUCCEED(); + } + + Result ClearExceptionOccurred(os::ProcessId process_id) { + auto list = GetProcessList(); + + auto process_info = list->Find(process_id); + R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); + + process_info->ClearExceptionOccurred(); + R_SUCCEED(); + } + + /* Information Getters. */ + Result GetModuleIdList(u32 *out_count, u8 *out_buf, size_t max_out_count, u64 unused) { + /* This function was always stubbed... */ + AMS_UNUSED(out_buf, max_out_count, unused); + *out_count = 0; + R_SUCCEED(); + } + + Result GetExceptionProcessIdList(u32 *out_count, os::ProcessId *out_process_ids, size_t max_out_count) { + auto list = GetProcessList(); + + size_t count = 0; + + if (max_out_count > 0) { + for (auto &process : *list) { + if (process.HasExceptionWaitingAttach()) { + out_process_ids[count++] = process.GetProcessId(); + + if (count >= max_out_count) { + break; + } + } + } + } + + *out_count = static_cast<u32>(count); + R_SUCCEED(); + } + + Result GetProcessId(os::ProcessId *out, ncm::ProgramId program_id) { + auto list = GetProcessList(); + + auto process_info = list->Find(program_id); + R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); + + *out = process_info->GetProcessId(); + R_SUCCEED(); + } + + Result GetProgramId(ncm::ProgramId *out, os::ProcessId process_id) { + auto list = GetProcessList(); + + auto process_info = list->Find(process_id); + R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); + + *out = process_info->GetProgramLocation().program_id; + R_SUCCEED(); + } + + Result GetApplicationProcessId(os::ProcessId *out_process_id) { + auto list = GetProcessList(); + + for (auto &process : *list) { + if (process.IsApplication()) { + *out_process_id = process.GetProcessId(); + R_SUCCEED(); + } + } + + R_THROW(pm::ResultProcessNotFound()); + } + + Result AtmosphereGetProcessInfo(os::NativeHandle *out_process_handle, ncm::ProgramLocation *out_loc, cfg::OverrideStatus *out_status, os::ProcessId process_id) { + auto list = GetProcessList(); + + auto process_info = list->Find(process_id); + R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound()); + + *out_process_handle = process_info->GetHandle(); + *out_loc = process_info->GetProgramLocation(); + *out_status = process_info->GetOverrideStatus(); + R_SUCCEED(); + } + + /* Hook API. */ + Result HookToCreateProcess(os::NativeHandle *out_hook, ncm::ProgramId program_id) { + *out_hook = os::InvalidNativeHandle; + + { + ncm::ProgramId old_value = ncm::InvalidProgramId; + R_UNLESS(g_program_id_hook.compare_exchange_strong(old_value, program_id), pm::ResultDebugHookInUse()); + } + + *out_hook = os::GetReadableHandleOfSystemEvent(std::addressof(g_hook_to_create_process_event)); + R_SUCCEED(); + } + + Result HookToCreateApplicationProcess(os::NativeHandle *out_hook) { + *out_hook = os::InvalidNativeHandle; + + { + bool old_value = false; + R_UNLESS(g_application_hook.compare_exchange_strong(old_value, true), pm::ResultDebugHookInUse()); + } + + *out_hook = os::GetReadableHandleOfSystemEvent(std::addressof(g_hook_to_create_application_process_event)); + R_SUCCEED(); + } + + Result ClearHook(u32 which) { + if (which & HookType_ProgramId) { + g_program_id_hook = ncm::InvalidProgramId; + } + if (which & HookType_Application) { + g_application_hook = false; + } + R_SUCCEED(); + } + + /* Boot API. */ + Result NotifyBootFinished() { + AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(bool, s_has_boot_finished, false); + if (!s_has_boot_finished) { + /* Set program verification disabled, if we should. */ + /* NOTE: Nintendo does not check the result of this. */ + if (spl::IsDisabledProgramVerification()) { + if (hos::GetVersion() >= hos::Version_10_0_0) { + ldr::pm::SetEnabledProgramVerification(false); + } else { + fsprSetEnabledProgramVerification(false); + } + } + + boot2::LaunchPreSdCardBootProgramsAndBoot2(); + + s_has_boot_finished = true; + os::SignalSystemEvent(std::addressof(g_boot_finished_event)); + } + R_SUCCEED(); + } + + Result GetBootFinishedEventHandle(os::NativeHandle *out) { + /* In 8.0.0, Nintendo added this command, which signals that the boot sysmodule has finished. */ + /* Nintendo only signals it in safe mode FIRM, and this function aborts on normal FIRM. */ + /* We will signal it always, but only allow this function to succeed on safe mode. */ + AMS_ABORT_UNLESS(spl::IsRecoveryBoot()); + *out = os::GetReadableHandleOfSystemEvent(std::addressof(g_boot_finished_event)); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_manager.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_manager.hpp new file mode 100644 index 00000000..61acbe1b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_manager.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::pm::impl { + + /* Initialization. */ + Result InitializeProcessManager(); + + /* Process Management. */ + Result LaunchProgram(os::ProcessId *out_process_id, const ncm::ProgramLocation &loc, u32 flags); + Result StartProcess(os::ProcessId process_id); + Result TerminateProcess(os::ProcessId process_id); + Result TerminateProgram(ncm::ProgramId program_id); + Result GetProcessEventHandle(os::NativeHandle *out); + Result GetProcessEventInfo(ProcessEventInfo *out); + Result CleanupProcess(os::ProcessId process_id); + Result ClearExceptionOccurred(os::ProcessId process_id); + + /* Information Getters. */ + Result GetModuleIdList(u32 *out_count, u8 *out_buf, size_t max_out_count, u64 unused); + Result GetExceptionProcessIdList(u32 *out_count, os::ProcessId *out_process_ids, size_t max_out_count); + Result GetProcessId(os::ProcessId *out, ncm::ProgramId program_id); + Result GetProgramId(ncm::ProgramId *out, os::ProcessId process_id); + Result GetApplicationProcessId(os::ProcessId *out_process_id); + Result AtmosphereGetProcessInfo(os::NativeHandle *out_process_handle, ncm::ProgramLocation *out_loc, cfg::OverrideStatus *out_status, os::ProcessId process_id); + + /* Hook API. */ + Result HookToCreateProcess(os::NativeHandle *out_hook, ncm::ProgramId program_id); + Result HookToCreateApplicationProcess(os::NativeHandle *out_hook); + Result ClearHook(u32 which); + + /* Boot API. */ + Result NotifyBootFinished(); + Result GetBootFinishedEventHandle(os::NativeHandle *out); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_tracker.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_tracker.cpp new file mode 100644 index 00000000..5d1c3b41 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_tracker.cpp @@ -0,0 +1,193 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pm_process_tracker.hpp" +#include "pm_process_info.hpp" +#include "pm_spec.hpp" + +namespace ams::pm::impl { + + namespace { + + /* Global process event. */ + constinit os::SystemEventType g_process_event; + + } + + void ProcessTracker::Initialize(void *stack, size_t stack_size) { + /* Initialize our events. */ + os::InitializeEvent(std::addressof(m_request_event), false, os::EventClearMode_AutoClear); + os::InitializeEvent(std::addressof(m_reply_event), false, os::EventClearMode_AutoClear); + + /* Our process count should initially be zero. */ + m_process_count = 0; + + /* Create the process tracking thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_thread), ProcessTracker::ThreadFunction, this, stack, stack_size, AMS_GET_SYSTEM_THREAD_PRIORITY(pm, ProcessTrack))); + os::SetThreadNamePointer(std::addressof(m_thread), AMS_GET_SYSTEM_THREAD_NAME(pm, ProcessTrack)); + } + + void ProcessTracker::StartThread() { + /* Start thread. */ + os::StartThread(std::addressof(m_thread)); + } + + void ProcessTracker::ThreadBody() { + /* This is the main loop of the process tracking thread. */ + + /* Setup multi wait/holders. */ + os::MultiWaitType process_multi_wait; + os::MultiWaitHolderType enqueue_event_holder; + os::InitializeMultiWait(std::addressof(process_multi_wait)); + os::InitializeMultiWaitHolder(std::addressof(enqueue_event_holder), std::addressof(m_request_event)); + os::LinkMultiWaitHolder(std::addressof(process_multi_wait), std::addressof(enqueue_event_holder)); + + while (true) { + auto signaled_holder = os::WaitAny(std::addressof(process_multi_wait)); + if (signaled_holder == std::addressof(enqueue_event_holder)) { + /* TryWait will clear signaled, preventing duplicate notifications. */ + if (os::TryWaitEvent(std::addressof(m_request_event))) { + /* Link the process to our multi-wait. */ + os::LinkMultiWaitHolder(std::addressof(process_multi_wait), m_queued_process_info->GetMultiWaitHolder()); + m_queued_process_info = nullptr; + + /* Increment our process count. */ + ++m_process_count; + + /* Reply. */ + os::SignalEvent(std::addressof(m_reply_event)); + } + } else { + /* Some process was signaled. */ + this->OnProcessSignaled(reinterpret_cast<ProcessInfo *>(os::GetMultiWaitHolderUserData(signaled_holder))); + } + } + } + + void ProcessTracker::QueueEntry(ProcessInfo *process_info) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Request to enqueue the process info. */ + m_queued_process_info = process_info; + os::SignalEvent(std::addressof(m_request_event)); + + /* Wait for acknowledgement. */ + os::WaitEvent(std::addressof(m_reply_event)); + } + + void ProcessTracker::OnProcessSignaled(ProcessInfo *process_info) { + /* Get the process list. */ + auto list = GetProcessList(); + + /* Reset the process's signal. */ + svc::ResetSignal(process_info->GetHandle()); + + /* Update the process's state. */ + const svc::ProcessState old_state = process_info->GetState(); + { + s64 tmp = 0; + R_ABORT_UNLESS(svc::GetProcessInfo(std::addressof(tmp), process_info->GetHandle(), svc::ProcessInfoType_ProcessState)); + process_info->SetState(static_cast<svc::ProcessState>(tmp)); + } + const svc::ProcessState new_state = process_info->GetState(); + + /* If we're transitioning away from crashed, clear waiting attached. */ + if (old_state == svc::ProcessState_Crashed && new_state != svc::ProcessState_Crashed) { + process_info->ClearExceptionWaitingAttach(); + } + + switch (new_state) { + case svc::ProcessState_Created: + case svc::ProcessState_CreatedAttached: + case svc::ProcessState_Terminating: + break; + case svc::ProcessState_Running: + if (process_info->ShouldSignalOnDebugEvent()) { + process_info->ClearSuspended(); + process_info->SetSuspendedStateChanged(); + os::SignalSystemEvent(std::addressof(g_process_event)); + } else if (hos::GetVersion() >= hos::Version_2_0_0 && process_info->ShouldSignalOnStart()) { + process_info->SetStartedStateChanged(); + process_info->ClearSignalOnStart(); + os::SignalSystemEvent(std::addressof(g_process_event)); + } + process_info->ClearUnhandledException(); + break; + case svc::ProcessState_Crashed: + if (!process_info->HasUnhandledException()) { + process_info->SetExceptionOccurred(); + os::SignalSystemEvent(std::addressof(g_process_event)); + } + process_info->SetExceptionWaitingAttach(); + break; + case svc::ProcessState_RunningAttached: + if (process_info->ShouldSignalOnDebugEvent()) { + process_info->ClearSuspended(); + process_info->SetSuspendedStateChanged(); + os::SignalSystemEvent(std::addressof(g_process_event)); + } + process_info->ClearUnhandledException(); + break; + case svc::ProcessState_Terminated: + /* Unlink from multi wait. */ + os::UnlinkMultiWaitHolder(process_info->GetMultiWaitHolder()); + + /* Free process resources. */ + process_info->Cleanup(); + + if (hos::GetVersion() < hos::Version_5_0_0 && process_info->ShouldSignalOnExit()) { + os::SignalSystemEvent(std::addressof(g_process_event)); + } else { + /* Handle the case where we need to keep the process alive some time longer. */ + if (hos::GetVersion() >= hos::Version_5_0_0 && process_info->ShouldSignalOnExit()) { + /* Remove from the living list. */ + list->Remove(process_info); + + /* Add the process to the list of dead processes. */ + { + GetExitList()->push_back(*process_info); + } + + /* Signal. */ + os::SignalSystemEvent(std::addressof(g_process_event)); + } else { + /* Actually delete process. */ + CleanupProcessInfo(list, process_info); + } + } + break; + case svc::ProcessState_DebugBreak: + if (process_info->ShouldSignalOnDebugEvent()) { + process_info->SetSuspended(); + process_info->SetSuspendedStateChanged(); + os::SignalSystemEvent(std::addressof(g_process_event)); + } + break; + } + } + + void CreateProcessEvent() { + /* Create process event. */ + R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_process_event), os::EventClearMode_AutoClear, true)); + } + + Result GetProcessEventHandle(os::NativeHandle *out) { + *out = os::GetReadableHandleOfSystemEvent(std::addressof(g_process_event)); + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_tracker.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_tracker.hpp new file mode 100644 index 00000000..c1de7c2a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_process_tracker.hpp @@ -0,0 +1,57 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "pm_process_info.hpp" + +namespace ams::pm::impl { + + class ProcessTracker { + NON_COPYABLE(ProcessTracker); + NON_MOVEABLE(ProcessTracker); + private: + os::ThreadType m_thread; + os::EventType m_request_event; + os::EventType m_reply_event; + os::SdkMutex m_mutex; + ProcessInfo *m_queued_process_info; + util::Atomic<u32> m_process_count; + public: + constexpr ProcessTracker() : m_thread(), m_request_event(), m_reply_event(), m_mutex(), m_queued_process_info(nullptr), m_process_count(0) { + /* ... */ + } + + void Initialize(void *stack, size_t stack_size); + void StartThread(); + + void QueueEntry(ProcessInfo *process_info); + + u32 GetProcessCount() const { + return m_process_count; + } + private: + void OnProcessSignaled(ProcessInfo *process_info); + private: + static void ThreadFunction(void *_this) { + static_cast<ProcessTracker *>(_this)->ThreadBody(); + } + + void ThreadBody(); + }; + + void CreateProcessEvent(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_spec.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_spec.cpp new file mode 100644 index 00000000..ee0e3806 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_spec.cpp @@ -0,0 +1,491 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pm_spec.hpp" + +namespace ams::pm::impl { + + namespace { + + constexpr svc::LimitableResource LimitableResources[] = { + svc::LimitableResource_PhysicalMemoryMax, + svc::LimitableResource_ThreadCountMax, + svc::LimitableResource_EventCountMax, + svc::LimitableResource_TransferMemoryCountMax, + svc::LimitableResource_SessionCountMax, + }; + + /* Definitions for limit differences over time. */ + constexpr size_t ExtraSystemMemorySize400 = 10_MB; + constexpr size_t ReservedMemorySize600 = 5_MB; + + /* Atmosphere always allocates extra memory for system usage. */ + constexpr size_t ExtraSystemMemorySizeAtmosphere = 32_MB; + + /* Desired extra threads. */ + constexpr u64 BaseApplicationThreads = 96; + constexpr u64 BaseAppletThreads = 96; + constexpr u64 BaseSystemThreads = 800 - BaseAppletThreads - BaseApplicationThreads; + + constexpr s64 ExtraSystemThreads = 1024 - BaseSystemThreads; + constexpr s64 ExtraApplicationThreads = 256 - BaseApplicationThreads; + constexpr s64 ExtraAppletThreads = 256 - BaseAppletThreads; + + static_assert(ExtraSystemThreads >= 0); + static_assert(ExtraApplicationThreads >= 0); + static_assert(ExtraAppletThreads >= 0); + + /* Globals. */ + constinit os::SdkMutex g_resource_limit_lock; + constinit os::NativeHandle g_resource_limit_handles[ResourceLimitGroup_Count]; + constinit spl::MemoryArrangement g_memory_arrangement = spl::MemoryArrangement_Standard; + constinit u64 g_extra_threads_available[ResourceLimitGroup_Count]; + + constinit os::SdkMutex g_system_memory_boost_lock; + constinit u64 g_system_memory_boost_size = 0; + constinit u64 g_system_memory_boost_size_for_mitm = 0; + + ALWAYS_INLINE u64 GetCurrentSystemMemoryBoostSize() { + return g_system_memory_boost_size + g_system_memory_boost_size_for_mitm; + } + + constinit u64 g_resource_limits[ResourceLimitGroup_Count][svc::LimitableResource_Count] = { + [ResourceLimitGroup_System] = { + [svc::LimitableResource_PhysicalMemoryMax] = 0, /* Initialized dynamically later. */ + [svc::LimitableResource_ThreadCountMax] = BaseSystemThreads, + [svc::LimitableResource_EventCountMax] = 0, /* Initialized dynamically later. */ + [svc::LimitableResource_TransferMemoryCountMax] = 0, /* Initialized dynamically later. */ + [svc::LimitableResource_SessionCountMax] = 0, /* Initialized dynamically later. */ + }, + [ResourceLimitGroup_Application] = { + [svc::LimitableResource_PhysicalMemoryMax] = 0, /* Initialized dynamically later. */ + [svc::LimitableResource_ThreadCountMax] = BaseApplicationThreads, + [svc::LimitableResource_EventCountMax] = 0, + [svc::LimitableResource_TransferMemoryCountMax] = 32, + [svc::LimitableResource_SessionCountMax] = 1, + }, + [ResourceLimitGroup_Applet] = { + [svc::LimitableResource_PhysicalMemoryMax] = 0, /* Initialized dynamically later. */ + [svc::LimitableResource_ThreadCountMax] = BaseAppletThreads, + [svc::LimitableResource_EventCountMax] = 0, + [svc::LimitableResource_TransferMemoryCountMax] = 32, + [svc::LimitableResource_SessionCountMax] = 5 + 1, /* Add a session for atmosphere's memlet system module. */ + }, + }; + + constinit u64 g_memory_resource_limits[spl::MemoryArrangement_Count][ResourceLimitGroup_Count] = { + [spl::MemoryArrangement_Standard] = { + [ResourceLimitGroup_System] = 269_MB, + [ResourceLimitGroup_Application] = 3285_MB, + [ResourceLimitGroup_Applet] = 535_MB, + }, + [spl::MemoryArrangement_StandardForAppletDev] = { + [ResourceLimitGroup_System] = 481_MB, + [ResourceLimitGroup_Application] = 2048_MB, + [ResourceLimitGroup_Applet] = 1560_MB, + }, + [spl::MemoryArrangement_StandardForSystemDev] = { + [ResourceLimitGroup_System] = 328_MB, + [ResourceLimitGroup_Application] = 3285_MB, + [ResourceLimitGroup_Applet] = 476_MB, + }, + [spl::MemoryArrangement_Expanded] = { + [ResourceLimitGroup_System] = 653_MB, + [ResourceLimitGroup_Application] = 4916_MB, + [ResourceLimitGroup_Applet] = 568_MB, + }, + [spl::MemoryArrangement_ExpandedForAppletDev] = { + [ResourceLimitGroup_System] = 653_MB, + [ResourceLimitGroup_Application] = 3285_MB, + [ResourceLimitGroup_Applet] = 2199_MB, + }, + }; + + /* Helpers. */ + Result SetMemoryResourceLimitLimitValue(ResourceLimitGroup group, u64 new_memory_limit) { + const u64 old_memory_limit = g_resource_limits[group][svc::LimitableResource_PhysicalMemoryMax]; + g_resource_limits[group][svc::LimitableResource_PhysicalMemoryMax] = new_memory_limit; + + /* Restore the old memory limit if we fail. */ + ON_RESULT_FAILURE { g_resource_limits[group][svc::LimitableResource_PhysicalMemoryMax] = old_memory_limit; }; + + /* Set the resource limit. */ + R_RETURN(svc::SetResourceLimitLimitValue(GetResourceLimitHandle(group), svc::LimitableResource_PhysicalMemoryMax, g_resource_limits[group][svc::LimitableResource_PhysicalMemoryMax])); + } + + Result SetResourceLimitLimitValues(ResourceLimitGroup group, u64 new_memory_limit) { + /* First, set memory limit. */ + R_TRY(SetMemoryResourceLimitLimitValue(group, new_memory_limit)); + + /* Set other limit values. */ + for (size_t i = 0; i < svc::LimitableResource_Count; i++) { + const auto resource = LimitableResources[i]; + if (resource == svc::LimitableResource_PhysicalMemoryMax) { + continue; + } + R_TRY(svc::SetResourceLimitLimitValue(GetResourceLimitHandle(group), resource, g_resource_limits[group][resource])); + } + + R_SUCCEED(); + } + + inline ResourceLimitGroup GetResourceLimitGroup(const ldr::ProgramInfo *info) { + switch (info->flags & ldr::ProgramInfoFlag_ApplicationTypeMask) { + case ldr::ProgramInfoFlag_Application: + return ResourceLimitGroup_Application; + case ldr::ProgramInfoFlag_Applet: + return ResourceLimitGroup_Applet; + default: + return ResourceLimitGroup_System; + } + } + + void WaitResourceAvailable(ResourceLimitGroup group) { + const auto reslimit_hnd = GetResourceLimitHandle(group); + for (size_t i = 0; i < svc::LimitableResource_Count; i++) { + const auto resource = LimitableResources[i]; + + s64 value = 0; + while (true) { + R_ABORT_UNLESS(svc::GetResourceLimitCurrentValue(&value, reslimit_hnd, resource)); + if (value == 0) { + break; + } + os::SleepThread(TimeSpan::FromMilliSeconds(1)); + } + } + } + + void WaitApplicationMemoryAvailable() { + /* Get firmware version. */ + const auto fw_ver = hos::GetVersion(); + + /* On 15.0.0+, pm considers application memory to be available if there is exactly 96 MB outstanding. */ + /* This is probably because this corresponds to the gameplay-recording memory. */ + constexpr u64 AllowedUsedApplicationMemory = 96_MB; + + /* Wait for memory to be available. */ + u64 value = 0; + while (true) { + R_ABORT_UNLESS(svc::GetSystemInfo(&value, svc::SystemInfoType_UsedPhysicalMemorySize, svc::InvalidHandle, svc::PhysicalMemorySystemInfo_Application)); + if (value == 0 || (fw_ver >= hos::Version_15_0_0 && value == AllowedUsedApplicationMemory)) { + break; + } + os::SleepThread(TimeSpan::FromMilliSeconds(1)); + } + } + + bool IsKTraceEnabled() { + u64 value = 0; + R_ABORT_UNLESS(svc::GetInfo(std::addressof(value), svc::InfoType_MesosphereMeta, svc::InvalidHandle, svc::MesosphereMetaInfo_IsKTraceEnabled)); + + return value != 0; + } + + ALWAYS_INLINE Result BoostThreadResourceLimitLocked(ResourceLimitGroup group) { + AMS_ASSERT(g_resource_limit_lock.IsLockedByCurrentThread()); + + /* Set new limit. */ + const s64 new_thread_count = g_resource_limits[group][svc::LimitableResource_ThreadCountMax] + g_extra_threads_available[group]; + R_TRY(svc::SetResourceLimitLimitValue(GetResourceLimitHandle(group), svc::LimitableResource_ThreadCountMax, new_thread_count)); + + /* Record that we did so. */ + g_resource_limits[group][svc::LimitableResource_ThreadCountMax] = new_thread_count; + g_extra_threads_available[group] = 0; + + R_SUCCEED(); + } + + template<auto ImplFunction> + ALWAYS_INLINE Result GetResourceLimitValueImpl(pm::ResourceLimitValue *out, ResourceLimitGroup group) { + /* Sanity check group. */ + AMS_ABORT_UNLESS(group < ResourceLimitGroup_Count); + + /* Get handle. */ + const auto handle = GetResourceLimitHandle(group); + + /* Get values. */ + int64_t values[svc::LimitableResource_Count]; + R_ABORT_UNLESS(ImplFunction(std::addressof(values[svc::LimitableResource_PhysicalMemoryMax]), handle, svc::LimitableResource_PhysicalMemoryMax)); + R_ABORT_UNLESS(ImplFunction(std::addressof(values[svc::LimitableResource_ThreadCountMax]), handle, svc::LimitableResource_ThreadCountMax)); + R_ABORT_UNLESS(ImplFunction(std::addressof(values[svc::LimitableResource_EventCountMax]), handle, svc::LimitableResource_EventCountMax)); + R_ABORT_UNLESS(ImplFunction(std::addressof(values[svc::LimitableResource_TransferMemoryCountMax]), handle, svc::LimitableResource_TransferMemoryCountMax)); + R_ABORT_UNLESS(ImplFunction(std::addressof(values[svc::LimitableResource_SessionCountMax]), handle, svc::LimitableResource_SessionCountMax)); + + /* Set to output. */ + out->physical_memory = values[svc::LimitableResource_PhysicalMemoryMax]; + out->thread_count = values[svc::LimitableResource_ThreadCountMax]; + out->event_count = values[svc::LimitableResource_EventCountMax]; + out->transfer_memory_count = values[svc::LimitableResource_TransferMemoryCountMax]; + out->session_count = values[svc::LimitableResource_SessionCountMax]; + + R_SUCCEED(); + } + + Result BoostSystemMemoryResourceLimitLocked(u64 normal_boost, u64 mitm_boost) { + /* Check pre-conditions. */ + AMS_ASSERT(g_system_memory_boost_lock.IsLockedByCurrentThread()); + + /* Determine total boost. */ + const u64 boost_size = normal_boost + mitm_boost; + + /* Don't allow all application memory to be taken away. */ + R_UNLESS(boost_size < g_memory_resource_limits[g_memory_arrangement][ResourceLimitGroup_Application], pm::ResultInvalidSize()); + + const u64 new_app_size = g_memory_resource_limits[g_memory_arrangement][ResourceLimitGroup_Application] - boost_size; + { + std::scoped_lock lk(g_resource_limit_lock); + + const auto cur_boost_size = GetCurrentSystemMemoryBoostSize(); + + if (hos::GetVersion() >= hos::Version_5_0_0) { + /* Starting in 5.0.0, PM does not allow for only one of the sets to fail. */ + if (boost_size < cur_boost_size) { + R_TRY(svc::SetUnsafeLimit(boost_size)); + R_ABORT_UNLESS(SetMemoryResourceLimitLimitValue(ResourceLimitGroup_Application, new_app_size)); + } else if (boost_size > cur_boost_size) { + R_TRY(SetMemoryResourceLimitLimitValue(ResourceLimitGroup_Application, new_app_size)); + R_ABORT_UNLESS(svc::SetUnsafeLimit(boost_size)); + } else { + /* If the boost size is equal, there's nothing to do. */ + } + } else { + const u64 new_sys_size = g_memory_resource_limits[g_memory_arrangement][ResourceLimitGroup_System] + boost_size; + if (boost_size < cur_boost_size) { + R_TRY(SetMemoryResourceLimitLimitValue(ResourceLimitGroup_System, new_sys_size)); + R_TRY(SetMemoryResourceLimitLimitValue(ResourceLimitGroup_Application, new_app_size)); + } else if (boost_size > cur_boost_size) { + R_TRY(SetMemoryResourceLimitLimitValue(ResourceLimitGroup_Application, new_app_size)); + R_TRY(SetMemoryResourceLimitLimitValue(ResourceLimitGroup_System, new_sys_size)); + } else { + /* If the boost size is equal, there's nothing to do. */ + } + } + + g_system_memory_boost_size = normal_boost; + g_system_memory_boost_size_for_mitm = mitm_boost; + } + + R_SUCCEED(); + } + + } + + Result InitializeSpec() { + /* Create resource limit handles. */ + for (size_t i = 0; i < ResourceLimitGroup_Count; i++) { + if (i == ResourceLimitGroup_System) { + u64 value = 0; + R_ABORT_UNLESS(svc::GetInfo(&value, svc::InfoType_ResourceLimit, svc::InvalidHandle, 0)); + g_resource_limit_handles[i] = static_cast<svc::Handle>(value); + } else { + R_ABORT_UNLESS(svc::CreateResourceLimit(g_resource_limit_handles + i)); + } + } + + /* Adjust memory limits based on hos firmware version. */ + const auto hos_version = hos::GetVersion(); + if (hos_version >= hos::Version_4_0_0) { + /* 4.0.0 took memory away from applet and gave it to system, for the Standard and StandardForSystemDev profiles. */ + g_memory_resource_limits[spl::MemoryArrangement_Standard][ResourceLimitGroup_System] += ExtraSystemMemorySize400; + g_memory_resource_limits[spl::MemoryArrangement_Standard][ResourceLimitGroup_Applet] -= ExtraSystemMemorySize400; + g_memory_resource_limits[spl::MemoryArrangement_StandardForSystemDev][ResourceLimitGroup_System] += ExtraSystemMemorySize400; + g_memory_resource_limits[spl::MemoryArrangement_StandardForSystemDev][ResourceLimitGroup_Applet] -= ExtraSystemMemorySize400; + } + + /* Determine system resource counts. */ + { + /* Get the total resource counts. */ + s64 total_events, total_transfer_memories, total_sessions; + R_ABORT_UNLESS(svc::GetResourceLimitLimitValue(std::addressof(total_events), GetResourceLimitHandle(ResourceLimitGroup_System), svc::LimitableResource_EventCountMax)); + R_ABORT_UNLESS(svc::GetResourceLimitLimitValue(std::addressof(total_transfer_memories), GetResourceLimitHandle(ResourceLimitGroup_System), svc::LimitableResource_TransferMemoryCountMax)); + R_ABORT_UNLESS(svc::GetResourceLimitLimitValue(std::addressof(total_sessions), GetResourceLimitHandle(ResourceLimitGroup_System), svc::LimitableResource_SessionCountMax)); + + /* Determine system counts. */ + const s64 sys_events = total_events - (g_resource_limits[ResourceLimitGroup_Application][svc::LimitableResource_EventCountMax] + g_resource_limits[ResourceLimitGroup_Applet][svc::LimitableResource_EventCountMax]); + const s64 sys_transfer_memories = total_transfer_memories - (g_resource_limits[ResourceLimitGroup_Application][svc::LimitableResource_TransferMemoryCountMax] + g_resource_limits[ResourceLimitGroup_Applet][svc::LimitableResource_TransferMemoryCountMax]); + const s64 sys_sessions = total_sessions - (g_resource_limits[ResourceLimitGroup_Application][svc::LimitableResource_SessionCountMax] + g_resource_limits[ResourceLimitGroup_Applet][svc::LimitableResource_SessionCountMax]); + + /* Check system counts. */ + AMS_ABORT_UNLESS(sys_events >= 0); + AMS_ABORT_UNLESS(sys_transfer_memories >= 0); + AMS_ABORT_UNLESS(sys_sessions >= 0); + + /* Set system counts. */ + g_resource_limits[ResourceLimitGroup_System][svc::LimitableResource_EventCountMax] = sys_events; + g_resource_limits[ResourceLimitGroup_System][svc::LimitableResource_TransferMemoryCountMax] = sys_transfer_memories; + g_resource_limits[ResourceLimitGroup_System][svc::LimitableResource_SessionCountMax] = sys_sessions; + } + + /* Determine extra application threads. */ + { + /* Get total threads available. */ + s64 total_threads; + R_ABORT_UNLESS(svc::GetResourceLimitLimitValue(std::addressof(total_threads), GetResourceLimitHandle(ResourceLimitGroup_System), svc::LimitableResource_ThreadCountMax)); + + /* Check that we have enough threads. */ + const s64 required_threads = g_resource_limits[ResourceLimitGroup_System][svc::LimitableResource_ThreadCountMax] + g_resource_limits[ResourceLimitGroup_Application][svc::LimitableResource_ThreadCountMax] + g_resource_limits[ResourceLimitGroup_Applet][svc::LimitableResource_ThreadCountMax]; + AMS_ABORT_UNLESS(total_threads >= required_threads); + + /* Set the number of extra threads. */ + const s64 extra_threads = total_threads - required_threads; + if constexpr (true /* TODO: Should we expose the old "all extra threads are application" behavior? Seems pointless. */) { + if (extra_threads > 0) { + /* If we have any extra threads at all, require that we have enough. */ + AMS_ABORT_UNLESS(extra_threads >= (ExtraSystemThreads + ExtraApplicationThreads + ExtraAppletThreads)); + + g_extra_threads_available[ResourceLimitGroup_System] += ExtraSystemThreads; + g_extra_threads_available[ResourceLimitGroup_Application] += ExtraApplicationThreads; + g_extra_threads_available[ResourceLimitGroup_Applet] += ExtraAppletThreads; + } + } else { + g_extra_threads_available[ResourceLimitGroup_Application] = extra_threads; + } + } + + /* Choose and initialize memory arrangement. */ + const bool use_dynamic_memory_arrangement = (hos_version >= hos::Version_5_0_0); + if (use_dynamic_memory_arrangement) { + /* 6.0.0 retrieves memory limit information from the kernel, rather than using a hardcoded profile. */ + g_memory_arrangement = spl::MemoryArrangement_Dynamic; + + /* Get total memory available. */ + s64 total_memory = 0; + R_ABORT_UNLESS(svc::GetResourceLimitLimitValue(&total_memory, GetResourceLimitHandle(ResourceLimitGroup_System), svc::LimitableResource_PhysicalMemoryMax)); + + /* Get and save application + applet memory. */ + R_ABORT_UNLESS(svc::GetSystemInfo(std::addressof(g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_Application]), svc::SystemInfoType_TotalPhysicalMemorySize, svc::InvalidHandle, svc::PhysicalMemorySystemInfo_Application)); + R_ABORT_UNLESS(svc::GetSystemInfo(std::addressof(g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_Applet]), svc::SystemInfoType_TotalPhysicalMemorySize, svc::InvalidHandle, svc::PhysicalMemorySystemInfo_Applet)); + + const s64 application_size = g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_Application]; + const s64 applet_size = g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_Applet]; + const s64 reserved_non_system_size = (application_size + applet_size + ReservedMemorySize600); + + /* Ensure there's enough memory for the system region. */ + AMS_ABORT_UNLESS(reserved_non_system_size < total_memory); + + g_memory_resource_limits[spl::MemoryArrangement_Dynamic][ResourceLimitGroup_System] = total_memory - reserved_non_system_size; + } else { + /* Older system versions retrieve memory arrangement from spl, and use hardcoded profiles. */ + g_memory_arrangement = spl::GetMemoryArrangement(); + + /* Adjust memory limits for atmosphere. */ + /* We take memory away from applet normally, but away from application on < 3.0.0 to avoid a rare hang on boot. */ + const size_t extra_memory_size = ExtraSystemMemorySizeAtmosphere; + const auto src_group = hos_version >= hos::Version_3_0_0 ? ResourceLimitGroup_Applet : ResourceLimitGroup_Application; + for (size_t i = 0; i < spl::MemoryArrangement_Count; i++) { + g_memory_resource_limits[i][ResourceLimitGroup_System] += extra_memory_size; + g_memory_resource_limits[i][src_group] -= extra_memory_size; + } + + /* If KTrace is enabled, account for that by subtracting the memory from the applet pool. */ + if (IsKTraceEnabled()) { + constexpr size_t KTraceBufferSize = 16_MB; + for (size_t i = 0; i < spl::MemoryArrangement_Count; i++) { + g_memory_resource_limits[i][ResourceLimitGroup_Applet] -= KTraceBufferSize; + } + } + } + + /* Actually set resource limits. */ + { + std::scoped_lock lk(g_resource_limit_lock); + + for (size_t group = 0; group < ResourceLimitGroup_Count; group++) { + R_ABORT_UNLESS(SetResourceLimitLimitValues(static_cast<ResourceLimitGroup>(group), g_memory_resource_limits[g_memory_arrangement][group])); + } + } + + R_SUCCEED(); + } + + Result BoostSystemMemoryResourceLimit(u64 boost_size) { + /* Ensure only one boost change happens at a time. */ + std::scoped_lock lk(g_system_memory_boost_lock); + + /* Boost to the appropriate total amount. */ + R_RETURN(BoostSystemMemoryResourceLimitLocked(boost_size, g_system_memory_boost_size_for_mitm)); + } + + Result BoostSystemMemoryResourceLimitForMitm(u64 boost_size) { + /* Ensure only one boost change happens at a time. */ + std::scoped_lock lk(g_system_memory_boost_lock); + + /* Boost to the appropriate total amount. */ + R_RETURN(BoostSystemMemoryResourceLimitLocked(g_system_memory_boost_size, boost_size)); + } + + Result BoostApplicationThreadResourceLimit() { + std::scoped_lock lk(g_resource_limit_lock); + + /* Boost the limit. */ + R_TRY(BoostThreadResourceLimitLocked(ResourceLimitGroup_Application)); + + R_SUCCEED(); + } + + Result BoostSystemThreadResourceLimit() { + std::scoped_lock lk(g_resource_limit_lock); + + /* Boost the limits. */ + R_TRY(BoostThreadResourceLimitLocked(ResourceLimitGroup_Applet)); + R_TRY(BoostThreadResourceLimitLocked(ResourceLimitGroup_System)); + + R_SUCCEED(); + } + + os::NativeHandle GetResourceLimitHandle(ResourceLimitGroup group) { + return g_resource_limit_handles[group]; + } + + os::NativeHandle GetResourceLimitHandle(const ldr::ProgramInfo *info) { + return GetResourceLimitHandle(GetResourceLimitGroup(info)); + } + + void WaitResourceAvailable(const ldr::ProgramInfo *info) { + if (GetResourceLimitGroup(info) == ResourceLimitGroup_Application) { + WaitResourceAvailable(ResourceLimitGroup_Application); + if (hos::GetVersion() >= hos::Version_5_0_0) { + WaitApplicationMemoryAvailable(); + } + } + } + + Result GetResourceLimitCurrentValue(pm::ResourceLimitValue *out, ResourceLimitGroup group) { + R_RETURN(GetResourceLimitValueImpl<::ams::svc::GetResourceLimitCurrentValue>(out, group)); + } + + Result GetResourceLimitPeakValue(pm::ResourceLimitValue *out, ResourceLimitGroup group) { + R_RETURN(GetResourceLimitValueImpl<::ams::svc::GetResourceLimitPeakValue>(out, group)); + } + + Result GetResourceLimitLimitValue(pm::ResourceLimitValue *out, ResourceLimitGroup group) { + R_RETURN(GetResourceLimitValueImpl<::ams::svc::GetResourceLimitLimitValue>(out, group)); + } + + Result GetResourceLimitValues(s64 *out_cur, s64 *out_lim, ResourceLimitGroup group, svc::LimitableResource resource) { + /* Do not allow out of bounds access. */ + AMS_ABORT_UNLESS(group < ResourceLimitGroup_Count); + AMS_ABORT_UNLESS(resource < svc::LimitableResource_Count); + + const auto reslimit_hnd = GetResourceLimitHandle(group); + R_TRY(svc::GetResourceLimitCurrentValue(out_cur, reslimit_hnd, resource)); + R_TRY(svc::GetResourceLimitLimitValue(out_lim, reslimit_hnd, resource)); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_spec.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_spec.hpp new file mode 100644 index 00000000..1f49282f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/impl/pm_spec.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::pm::impl { + + Result InitializeSpec(); + + Result BoostSystemMemoryResourceLimit(u64 boost_size); + Result BoostApplicationThreadResourceLimit(); + Result BoostSystemThreadResourceLimit(); + + Result BoostSystemMemoryResourceLimitForMitm(u64 boost_size); + + os::NativeHandle GetResourceLimitHandle(ResourceLimitGroup group); + os::NativeHandle GetResourceLimitHandle(const ldr::ProgramInfo *info); + + void WaitResourceAvailable(const ldr::ProgramInfo *info); + + Result GetResourceLimitCurrentValue(pm::ResourceLimitValue *out, ResourceLimitGroup group); + Result GetResourceLimitPeakValue(pm::ResourceLimitValue *outm, ResourceLimitGroup group); + Result GetResourceLimitLimitValue(pm::ResourceLimitValue *out, ResourceLimitGroup group); + + Result GetResourceLimitValues(s64 *out_cur, s64 *out_lim, ResourceLimitGroup group, svc::LimitableResource resource); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_boot_mode_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_boot_mode_service.cpp new file mode 100644 index 00000000..a1a762d3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_boot_mode_service.cpp @@ -0,0 +1,61 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pm_boot_mode_service.hpp" + +namespace ams::pm { + + namespace { + + /* Global bootmode. */ + constinit BootMode g_boot_mode = BootMode::Normal; + constinit u32 g_unknown = 0; + + } + + /* Override of weakly linked boot_mode_api functions. */ + namespace bm { + + BootMode GetBootMode() { + return g_boot_mode; + } + + void SetMaintenanceBoot() { + g_boot_mode = BootMode::Maintenance; + } + + } + + /* Service command implementations. */ + void BootModeService::GetBootMode(sf::Out<u32> out) { + out.SetValue(static_cast<u32>(pm::bm::GetBootMode())); + } + + void BootModeService::SetMaintenanceBoot() { + pm::bm::SetMaintenanceBoot(); + } + + void BootModeService::GetUnknown(sf::Out<u32> out) { + out.SetValue(g_unknown); + } + + Result BootModeService::SetUnknown(u32 val) { + R_UNLESS(val <= 3, pm::ResultUnknown7()); + g_unknown = val; + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_boot_mode_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_boot_mode_service.hpp new file mode 100644 index 00000000..b47536ed --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_boot_mode_service.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::pm { + + class BootModeService { + public: + void GetBootMode(sf::Out<u32> out); + void SetMaintenanceBoot(); + void GetUnknown(sf::Out<u32> out); + Result SetUnknown(u32 val); + }; + static_assert(pm::impl::IsIBootModeInterface<BootModeService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_debug_monitor_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_debug_monitor_service.cpp new file mode 100644 index 00000000..d9c3b162 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_debug_monitor_service.cpp @@ -0,0 +1,83 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pm_debug_monitor_service.hpp" +#include "impl/pm_process_manager.hpp" +#include "impl/pm_spec.hpp" + +namespace ams::pm { + + /* Actual command implementations. */ + Result DebugMonitorService::GetModuleIdList(sf::Out<u32> out_count, const sf::OutBuffer &out_buf, u64 unused) { + R_UNLESS(out_buf.GetSize() <= std::numeric_limits<s32>::max(), pm::ResultInvalidSize()); + R_RETURN(impl::GetModuleIdList(out_count.GetPointer(), out_buf.GetPointer(), out_buf.GetSize(), unused)); + } + + Result DebugMonitorService::GetExceptionProcessIdList(sf::Out<u32> out_count, const sf::OutArray<os::ProcessId> &out_process_ids) { + R_UNLESS(out_process_ids.GetSize() <= std::numeric_limits<s32>::max(), pm::ResultInvalidSize()); + R_RETURN(impl::GetExceptionProcessIdList(out_count.GetPointer(), out_process_ids.GetPointer(), out_process_ids.GetSize())); + } + + Result DebugMonitorService::StartProcess(os::ProcessId process_id) { + R_RETURN(impl::StartProcess(process_id)); + } + + Result DebugMonitorService::GetProcessId(sf::Out<os::ProcessId> out, ncm::ProgramId program_id) { + R_RETURN(impl::GetProcessId(out.GetPointer(), program_id)); + } + + Result DebugMonitorService::HookToCreateProcess(sf::OutCopyHandle out_hook, ncm::ProgramId program_id) { + os::NativeHandle event_handle; + R_TRY(impl::HookToCreateProcess(std::addressof(event_handle), program_id)); + + out_hook.SetValue(event_handle, false); + R_SUCCEED(); + } + + Result DebugMonitorService::GetApplicationProcessId(sf::Out<os::ProcessId> out) { + R_RETURN(impl::GetApplicationProcessId(out.GetPointer())); + } + + Result DebugMonitorService::HookToCreateApplicationProcess(sf::OutCopyHandle out_hook) { + os::NativeHandle event_handle; + R_TRY(impl::HookToCreateApplicationProcess(std::addressof(event_handle))); + + out_hook.SetValue(event_handle, false); + R_SUCCEED(); + } + + Result DebugMonitorService::ClearHook(u32 which) { + R_RETURN(impl::ClearHook(which)); + } + + Result DebugMonitorService::GetProgramId(sf::Out<ncm::ProgramId> out, os::ProcessId process_id) { + R_RETURN(impl::GetProgramId(out.GetPointer(), process_id)); + } + + /* Atmosphere extension commands. */ + Result DebugMonitorService::AtmosphereGetProcessInfo(sf::OutCopyHandle out_process_handle, sf::Out<ncm::ProgramLocation> out_loc, sf::Out<cfg::OverrideStatus> out_status, os::ProcessId process_id) { + os::NativeHandle process_handle; + R_TRY(impl::AtmosphereGetProcessInfo(std::addressof(process_handle), out_loc.GetPointer(), out_status.GetPointer(), process_id)); + + out_process_handle.SetValue(process_handle, false); + R_SUCCEED(); + } + + Result DebugMonitorService::AtmosphereGetCurrentLimitInfo(sf::Out<s64> out_cur_val, sf::Out<s64> out_lim_val, u32 group, u32 resource) { + R_RETURN(impl::GetResourceLimitValues(out_cur_val.GetPointer(), out_lim_val.GetPointer(), static_cast<ResourceLimitGroup>(group), static_cast<svc::LimitableResource>(resource))); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_debug_monitor_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_debug_monitor_service.hpp new file mode 100644 index 00000000..052e611c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_debug_monitor_service.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::pm { + + class DebugMonitorService { + public: + /* Actual command implementations. */ + Result GetModuleIdList(sf::Out<u32> out_count, const sf::OutBuffer &out_buf, u64 unused); + Result GetExceptionProcessIdList(sf::Out<u32> out_count, const sf::OutArray<os::ProcessId> &out_process_ids); + Result StartProcess(os::ProcessId process_id); + Result GetProcessId(sf::Out<os::ProcessId> out, ncm::ProgramId program_id); + Result HookToCreateProcess(sf::OutCopyHandle out_hook, ncm::ProgramId program_id); + Result GetApplicationProcessId(sf::Out<os::ProcessId> out); + Result HookToCreateApplicationProcess(sf::OutCopyHandle out_hook); + Result ClearHook(u32 which); + Result GetProgramId(sf::Out<ncm::ProgramId> out, os::ProcessId process_id); + + /* Atmosphere extension commands. */ + Result AtmosphereGetProcessInfo(sf::OutCopyHandle out_process_handle, sf::Out<ncm::ProgramLocation> out_loc, sf::Out<cfg::OverrideStatus> out_status, os::ProcessId process_id); + Result AtmosphereGetCurrentLimitInfo(sf::Out<s64> out_cur_val, sf::Out<s64> out_lim_val, u32 group, u32 resource); + }; + static_assert(pm::impl::IsIDebugMonitorInterface<DebugMonitorService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_info_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_info_service.cpp new file mode 100644 index 00000000..29ae868c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_info_service.cpp @@ -0,0 +1,60 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pm_info_service.hpp" +#include "impl/pm_process_manager.hpp" +#include "impl/pm_spec.hpp" + +namespace ams::pm { + + /* Overrides for libstratosphere pm::info commands. */ + namespace info { + + Result HasLaunchedBootProgram(bool *out, ncm::ProgramId program_id) { + R_RETURN(ldr::pm::HasLaunchedBootProgram(out, program_id)); + } + + } + + /* Actual command implementations. */ + Result InformationService::GetProgramId(sf::Out<ncm::ProgramId> out, os::ProcessId process_id) { + R_RETURN(impl::GetProgramId(out.GetPointer(), process_id)); + } + + Result InformationService::GetAppletResourceLimitCurrentValue(sf::Out<pm::ResourceLimitValue> out) { + R_RETURN(impl::GetResourceLimitCurrentValue(out.GetPointer(), ResourceLimitGroup_Applet)); + } + + Result InformationService::GetAppletResourceLimitPeakValue(sf::Out<pm::ResourceLimitValue> out) { + R_RETURN(impl::GetResourceLimitPeakValue(out.GetPointer(), ResourceLimitGroup_Applet)); + } + + /* Atmosphere extension commands. */ + Result InformationService::AtmosphereGetProcessId(sf::Out<os::ProcessId> out, ncm::ProgramId program_id) { + R_RETURN(impl::GetProcessId(out.GetPointer(), program_id)); + } + + Result InformationService::AtmosphereHasLaunchedBootProgram(sf::Out<bool> out, ncm::ProgramId program_id) { + R_RETURN(pm::info::HasLaunchedBootProgram(out.GetPointer(), program_id)); + } + + Result InformationService::AtmosphereGetProcessInfo(sf::Out<ncm::ProgramLocation> out_loc, sf::Out<cfg::OverrideStatus> out_status, os::ProcessId process_id) { + /* NOTE: We don't need to worry about closing this handle, because it's an in-process copy, not a newly allocated handle. */ + os::NativeHandle dummy_handle; + R_RETURN(impl::AtmosphereGetProcessInfo(&dummy_handle, out_loc.GetPointer(), out_status.GetPointer(), process_id)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_info_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_info_service.hpp new file mode 100644 index 00000000..1fd89254 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_info_service.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::pm { + + class InformationService { + public: + /* Actual command implementations. */ + Result GetProgramId(sf::Out<ncm::ProgramId> out, os::ProcessId process_id); + Result GetAppletResourceLimitCurrentValue(sf::Out<pm::ResourceLimitValue> out); + Result GetAppletResourceLimitPeakValue(sf::Out<pm::ResourceLimitValue> out); + + /* Atmosphere extension commands. */ + Result AtmosphereGetProcessId(sf::Out<os::ProcessId> out, ncm::ProgramId program_id); + Result AtmosphereHasLaunchedBootProgram(sf::Out<bool> out, ncm::ProgramId program_id); + Result AtmosphereGetProcessInfo(sf::Out<ncm::ProgramLocation> out_loc, sf::Out<cfg::OverrideStatus> out_status, os::ProcessId process_id); + }; + static_assert(pm::impl::IsIInformationInterface<InformationService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_main.cpp new file mode 100644 index 00000000..5bef871c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_main.cpp @@ -0,0 +1,235 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pm_boot_mode_service.hpp" +#include "pm_debug_monitor_service.hpp" +#include "pm_info_service.hpp" +#include "pm_shell_service.hpp" +#include "impl/pm_process_manager.hpp" + +namespace ams { + + namespace pm { + + namespace { + + constexpr u32 PrivilegedFileAccessHeader[0x1C / sizeof(u32)] = {0x00000001, 0x00000000, 0x80000000, 0x0000001C, 0x00000000, 0x0000001C, 0x00000000}; + constexpr u32 PrivilegedFileAccessControl[0x2C / sizeof(u32)] = {0x00000001, 0x00000000, 0x80000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF}; + constexpr u8 PrivilegedServiceAccessControl[] = {0x80, '*', 0x00, '*'}; + constexpr size_t ProcessCountMax = 0x40; + + /* This uses debugging SVCs to retrieve a process's program id. */ + ncm::ProgramId GetProcessProgramId(os::ProcessId process_id) { + /* Get a debug handle. */ + svc::Handle debug_handle; + R_ABORT_UNLESS(svc::DebugActiveProcess(std::addressof(debug_handle), process_id.value)); + ON_SCOPE_EXIT { R_ABORT_UNLESS(svc::CloseHandle(debug_handle)); }; + + /* Loop until we get the event that tells us about the process. */ + svc::DebugEventInfo d; + while (true) { + R_ABORT_UNLESS(svc::GetDebugEvent(std::addressof(d), debug_handle)); + if (d.type == svc::DebugEvent_CreateProcess) { + return ncm::ProgramId{d.info.create_process.program_id}; + } + } + } + + /* This works around a bug fixed by FS in 4.0.0. */ + /* Not doing so will cause KIPs with higher process IDs than 7 to be unable to use filesystem services. */ + /* It also registers privileged processes with SM, so that their program ids can be known. */ + void RegisterPrivilegedProcess(os::ProcessId process_id, ncm::ProgramId program_id) { + fsprUnregisterProgram(process_id.value); + fsprRegisterProgram(process_id.value, process_id.value, NcmStorageId_BuiltInSystem, PrivilegedFileAccessHeader, sizeof(PrivilegedFileAccessHeader), PrivilegedFileAccessControl, sizeof(PrivilegedFileAccessControl), 0); + sm::manager::UnregisterProcess(process_id); + sm::manager::RegisterProcess(process_id, program_id, cfg::OverrideStatus{}, PrivilegedServiceAccessControl, sizeof(PrivilegedServiceAccessControl), PrivilegedServiceAccessControl, sizeof(PrivilegedServiceAccessControl)); + } + + void RegisterPrivilegedProcesses() { + /* Get privileged process range. */ + os::ProcessId min_priv_process_id, max_priv_process_id; + R_ABORT_UNLESS(svc::GetSystemInfo(std::addressof(min_priv_process_id.value), svc::SystemInfoType_InitialProcessIdRange, svc::InvalidHandle, svc::InitialProcessIdRangeInfo_Minimum)); + R_ABORT_UNLESS(svc::GetSystemInfo(std::addressof(max_priv_process_id.value), svc::SystemInfoType_InitialProcessIdRange, svc::InvalidHandle, svc::InitialProcessIdRangeInfo_Maximum)); + + /* Get current process id/program id. */ + const auto cur_process_id = os::GetCurrentProcessId(); + const auto cur_program_id = os::GetCurrentProgramId(); + + /* Get list of processes, register all privileged ones. */ + s32 num_pids; + os::ProcessId pids[ProcessCountMax]; + R_ABORT_UNLESS(svc::GetProcessList(std::addressof(num_pids), reinterpret_cast<u64 *>(pids), ProcessCountMax)); + for (s32 i = 0; i < num_pids; i++) { + if (min_priv_process_id <= pids[i] && pids[i] <= max_priv_process_id) { + RegisterPrivilegedProcess(pids[i], pids[i] == cur_process_id ? cur_program_id : GetProcessProgramId(pids[i])); + } + } + } + + } + + namespace { + + /* pm:shell, pm:dmnt, pm:bm, pm:info. */ + enum PortIndex { + PortIndex_Shell, + PortIndex_DebugMonitor, + PortIndex_BootMode, + PortIndex_Information, + PortIndex_Count, + }; + + using ServerOptions = sf::hipc::DefaultServerManagerOptions; + + constexpr sm::ServiceName ShellServiceName = sm::ServiceName::Encode("pm:shell"); + constexpr size_t ShellMaxSessions = 8; /* Official maximum is 3. */ + + constexpr sm::ServiceName DebugMonitorServiceName = sm::ServiceName::Encode("pm:dmnt"); + constexpr size_t DebugMonitorMaxSessions = 16; + + constexpr sm::ServiceName BootModeServiceName = sm::ServiceName::Encode("pm:bm"); + constexpr size_t BootModeMaxSessions = 8; /* Official maximum is 4. */ + + constexpr sm::ServiceName InformationServiceName = sm::ServiceName::Encode("pm:info"); + constexpr size_t InformationMaxSessions = 48 - (ShellMaxSessions + DebugMonitorMaxSessions + BootModeMaxSessions); + + static_assert(InformationMaxSessions >= 16, "InformationMaxSessions"); + + constexpr size_t MaxSessions = ShellMaxSessions + DebugMonitorMaxSessions + BootModeMaxSessions + InformationMaxSessions; + static_assert(MaxSessions == 48, "MaxSessions"); + + class ServerManager final : public sf::hipc::ServerManager<PortIndex_Count, ServerOptions, MaxSessions> { + private: + virtual ams::Result OnNeedsToAccept(int port_index, Server *server) override; + }; + + ServerManager g_server_manager; + + /* NOTE: Nintendo only uses an unmanaged object for boot mode service, but no pm service has any class members/state, so we'll do it for all. */ + sf::UnmanagedServiceObject<pm::impl::IShellInterface, pm::ShellService> g_shell_service; + sf::UnmanagedServiceObject<pm::impl::IDeprecatedShellInterface, pm::ShellService> g_deprecated_shell_service; + + sf::UnmanagedServiceObject<pm::impl::IDebugMonitorInterface, pm::DebugMonitorService> g_dmnt_service; + sf::UnmanagedServiceObject<pm::impl::IDeprecatedDebugMonitorInterface, pm::DebugMonitorService> g_deprecated_dmnt_service; + + sf::UnmanagedServiceObject<pm::impl::IBootModeInterface, pm::BootModeService> g_boot_mode_service; + sf::UnmanagedServiceObject<pm::impl::IInformationInterface, pm::InformationService> g_information_service; + + ams::Result ServerManager::OnNeedsToAccept(int port_index, Server *server) { + switch (port_index) { + case PortIndex_Shell: + if (hos::GetVersion() >= hos::Version_5_0_0) { + R_RETURN(this->AcceptImpl(server, g_shell_service.GetShared())); + } else { + R_RETURN(this->AcceptImpl(server, g_deprecated_shell_service.GetShared())); + } + case PortIndex_DebugMonitor: + if (hos::GetVersion() >= hos::Version_5_0_0) { + R_RETURN(this->AcceptImpl(server, g_dmnt_service.GetShared())); + } else { + R_RETURN(this->AcceptImpl(server, g_deprecated_dmnt_service.GetShared())); + } + case PortIndex_BootMode: + R_RETURN(this->AcceptImpl(server, g_boot_mode_service.GetShared())); + case PortIndex_Information: + R_RETURN(this->AcceptImpl(server, g_information_service.GetShared())); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void RegisterServices() { + /* NOTE: Extra sessions have been added to pm:bm and pm:info to facilitate access by the rest of stratosphere. */ + R_ABORT_UNLESS(g_server_manager.RegisterServer(PortIndex_Shell, ShellServiceName, ShellMaxSessions)); + R_ABORT_UNLESS(g_server_manager.RegisterServer(PortIndex_DebugMonitor, DebugMonitorServiceName, DebugMonitorMaxSessions)); + R_ABORT_UNLESS(g_server_manager.RegisterServer(PortIndex_BootMode, BootModeServiceName, BootModeMaxSessions)); + R_ABORT_UNLESS(g_server_manager.RegisterServer(PortIndex_Information, InformationServiceName, InformationMaxSessions)); + } + + void LoopProcess() { + g_server_manager.LoopProcess(); + } + + } + + } + + namespace hos { + + void InitializeVersionInternal(bool allow_approximate); + + } + + namespace init { + + void InitializeSystemModule() { + /* Initialize our connection to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + + /* Initialize manager interfaces for fs and sm. */ + R_ABORT_UNLESS(fsprInitialize()); + R_ABORT_UNLESS(smManagerInitialize()); + + /* Work around a bug with process permissions on < 4.0.0. */ + /* This registers all initial processes explicitly with both fs and sm. */ + pm::RegisterPrivilegedProcesses(); + + /* Use our manager extension to tell SM that the FS bug has been worked around. */ + R_ABORT_UNLESS(sm::manager::EndInitialDefers()); + + /* Wait for the true hos version to be available. */ + hos::InitializeVersionInternal(false); + + /* Now that the true hos version is available, we should once more end defers (alerting sm to the available hos version). */ + R_ABORT_UNLESS(sm::manager::EndInitialDefers()); + + /* Initialize remaining services we need. */ + R_ABORT_UNLESS(ldrPmInitialize()); + spl::Initialize(); + + /* Verify that we can sanely execute. */ + ams::CheckApiVersion(); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void NORETURN Exit(int rc) { + AMS_UNUSED(rc); + AMS_ABORT("Exit called by immortal process"); + } + + void Main() { + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(pm, Main)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(pm, Main)); + + /* Initialize process manager implementation. */ + R_ABORT_UNLESS(pm::impl::InitializeProcessManager()); + + /* Create Services. */ + pm::RegisterServices(); + + /* Loop forever, servicing our services. */ + pm::LoopProcess(); + + /* This can never be reached. */ + AMS_ASSUME(false); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_shell_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_shell_service.cpp new file mode 100644 index 00000000..db69c6f1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_shell_service.cpp @@ -0,0 +1,91 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "pm_shell_service.hpp" +#include "impl/pm_process_manager.hpp" +#include "impl/pm_spec.hpp" + +namespace ams::pm { + + /* Overrides for libstratosphere pm::shell commands. */ + namespace shell { + + Result LaunchProgram(os::ProcessId *out_process_id, const ncm::ProgramLocation &loc, u32 launch_flags) { + R_RETURN(impl::LaunchProgram(out_process_id, loc, launch_flags)); + } + + } + + /* Service command implementations. */ + Result ShellService::LaunchProgram(sf::Out<os::ProcessId> out_process_id, const ncm::ProgramLocation &loc, u32 flags) { + R_RETURN(pm::shell::LaunchProgram(out_process_id.GetPointer(), loc, flags)); + } + + Result ShellService::TerminateProcess(os::ProcessId process_id) { + R_RETURN(impl::TerminateProcess(process_id)); + } + + Result ShellService::TerminateProgram(ncm::ProgramId program_id) { + R_RETURN(impl::TerminateProgram(program_id)); + } + + void ShellService::GetProcessEventHandle(sf::OutCopyHandle out) { + os::NativeHandle event_handle; + R_ABORT_UNLESS(impl::GetProcessEventHandle(std::addressof(event_handle))); + + out.SetValue(event_handle, false); + } + + void ShellService::GetProcessEventInfo(sf::Out<ProcessEventInfo> out) { + R_ABORT_UNLESS(impl::GetProcessEventInfo(out.GetPointer())); + } + + Result ShellService::CleanupProcess(os::ProcessId process_id) { + R_RETURN(impl::CleanupProcess(process_id)); + } + + Result ShellService::ClearExceptionOccurred(os::ProcessId process_id) { + R_RETURN(impl::ClearExceptionOccurred(process_id)); + } + + void ShellService::NotifyBootFinished() { + R_ABORT_UNLESS(impl::NotifyBootFinished()); + } + + Result ShellService::GetApplicationProcessIdForShell(sf::Out<os::ProcessId> out) { + R_RETURN(impl::GetApplicationProcessId(out.GetPointer())); + } + + Result ShellService::BoostSystemMemoryResourceLimit(u64 boost_size) { + R_RETURN(impl::BoostSystemMemoryResourceLimit(boost_size)); + } + + Result ShellService::BoostApplicationThreadResourceLimit() { + R_RETURN(impl::BoostApplicationThreadResourceLimit()); + } + + void ShellService::GetBootFinishedEventHandle(sf::OutCopyHandle out) { + os::NativeHandle event_handle; + R_ABORT_UNLESS(impl::GetBootFinishedEventHandle(std::addressof(event_handle))); + + out.SetValue(event_handle, false); + } + + Result ShellService::BoostSystemThreadResourceLimit() { + R_RETURN(impl::BoostSystemThreadResourceLimit()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_shell_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_shell_service.hpp new file mode 100644 index 00000000..bb08d80b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/source/pm_shell_service.hpp @@ -0,0 +1,40 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::pm { + + class ShellService { + public: + /* Actual command implementations. */ + Result LaunchProgram(sf::Out<os::ProcessId> out_process_id, const ncm::ProgramLocation &loc, u32 flags); + Result TerminateProcess(os::ProcessId process_id); + Result TerminateProgram(ncm::ProgramId program_id); + void GetProcessEventHandle(sf::OutCopyHandle out); + void GetProcessEventInfo(sf::Out<ProcessEventInfo> out); + Result CleanupProcess(os::ProcessId process_id); + Result ClearExceptionOccurred(os::ProcessId process_id); + void NotifyBootFinished(); + Result GetApplicationProcessIdForShell(sf::Out<os::ProcessId> out); + Result BoostSystemMemoryResourceLimit(u64 boost_size); + Result BoostApplicationThreadResourceLimit(); + void GetBootFinishedEventHandle(sf::OutCopyHandle out); + Result BoostSystemThreadResourceLimit(); + }; + static_assert(pm::impl::IsIShellInterface<ShellService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/pm/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/system_module.mk new file mode 100644 index 00000000..19c207d9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/pm/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := kip + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ro/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/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)/system_module.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)/system_module.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/stratosphere/ro/ro.json b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/ro.json new file mode 100644 index 00000000..fe547227 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/ro.json @@ -0,0 +1,96 @@ +{ + "name": "ro", + "title_id": "0x0100000000000037", + "title_id_range_min": "0x0100000000000037", + "title_id_range_max": "0x0100000000000037", + "main_thread_stack_size": "0x00008000", + "main_thread_priority": 49, + "default_cpu_id": 3, + "process_category": 0, + "is_retail": true, + "pool_partition": 2, + "is_64_bit": true, + "address_space_type": 3, + "disable_device_address_space_merge": true, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_access": ["fatal:u", "spl:", "set:sys", "fsp-srv", "csrng"], + "service_host": ["ldr:ro", "ro:dmnt", "ro:1"], + "kernel_capabilities": [{ + "type": "kernel_flags", + "value": { + "highest_thread_priority": 59, + "lowest_thread_priority": 28, + "lowest_cpu_id": 3, + "highest_cpu_id": 3 + } + }, { + "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", + "svcSynchronizePreemptionState": "0x36", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcSetProcessMemoryPermission": "0x73", + "svcMapProcessMemory": "0x74", + "svcUnmapProcessMemory": "0x75", + "svcQueryProcessMemory": "0x76", + "svcMapProcessCodeMemory": "0x77", + "svcUnmapProcessCodeMemory": "0x78", + "svcCallSecureMonitor": "0x7f" + } + }, { + "type": "min_kernel_version", + "value": "0x0030" + }, { + "type": "handle_table_size", + "value": 0 + }] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_nro_utils.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_nro_utils.cpp new file mode 100644 index 00000000..3f01a1c9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_nro_utils.cpp @@ -0,0 +1,79 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ro_nro_utils.hpp" +#include "ro_random.hpp" + +namespace ams::ro::impl { + + namespace { + + ALWAYS_INLINE size_t SetupNroProcessMemoryRegions(os::ProcessMemoryRegion *regions, u64 nro_heap_address, u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size) { + /* Reset region count. */ + size_t num_regions = 0; + + /* We always want a region for the nro. */ + regions[num_regions++] = { nro_heap_address, nro_heap_size }; + + /* If we have bss, create a region for bss. */ + if (bss_heap_size > 0) { + regions[num_regions++] = { bss_heap_address, bss_heap_size }; + } + + return num_regions; + } + + } + + Result MapNro(u64 *out_base_address, os::NativeHandle process_handle, u64 nro_heap_address, u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size) { + /* Set up the process memory regions. */ + os::ProcessMemoryRegion regions[2]; + const size_t num_regions = SetupNroProcessMemoryRegions(regions, nro_heap_address, nro_heap_size, bss_heap_address, bss_heap_size); + + /* Re-map the nro/bss as code memory in the destination process. */ + R_TRY_CATCH(os::MapProcessCodeMemory(out_base_address, process_handle, regions, num_regions, ro::impl::GenerateSecureRandom)) { + R_CONVERT(os::ResultOutOfAddressSpace, ro::ResultOutOfAddressSpace()) + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + Result SetNroPerms(os::NativeHandle process_handle, u64 base_address, u64 rx_size, u64 ro_size, u64 rw_size, bool is_aligned_header) { + const u64 rx_offset = is_aligned_header ? os::MemoryPageSize : 0; + const u64 ro_offset = rx_offset + rx_size; + const u64 rw_offset = ro_offset + ro_size; + + if (is_aligned_header) { + R_TRY(os::SetProcessMemoryPermission(process_handle, base_address, os::MemoryPageSize, os::MemoryPermission_ReadOnly)); + } + + R_TRY(os::SetProcessMemoryPermission(process_handle, base_address + rx_offset, rx_size, os::MemoryPermission_ReadExecute)); + R_TRY(os::SetProcessMemoryPermission(process_handle, base_address + ro_offset, ro_size, os::MemoryPermission_ReadOnly)); + R_TRY(os::SetProcessMemoryPermission(process_handle, base_address + rw_offset, rw_size, os::MemoryPermission_ReadWrite)); + + R_SUCCEED(); + } + + Result UnmapNro(os::NativeHandle process_handle, u64 base_address, u64 nro_heap_address, u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size) { + /* Set up the process memory regions. */ + os::ProcessMemoryRegion regions[2]; + const size_t num_regions = SetupNroProcessMemoryRegions(regions, nro_heap_address, nro_heap_size, bss_heap_address, bss_heap_size); + + /* Unmap the nro/bss. */ + R_RETURN(os::UnmapProcessCodeMemory(process_handle, base_address, regions, num_regions)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_nro_utils.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_nro_utils.hpp new file mode 100644 index 00000000..9ea2f7b3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_nro_utils.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> + +namespace ams::ro::impl { + + /* Utilities for working with NROs. */ + Result MapNro(u64 *out_base_address, os::NativeHandle process_handle, u64 nro_heap_address, u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size); + Result SetNroPerms(os::NativeHandle process_handle, u64 base_address, u64 rx_size, u64 ro_size, u64 rw_size, bool is_aligned_header); + Result UnmapNro(os::NativeHandle process_handle, u64 base_address, u64 nro_heap_address, u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_nrr_utils.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_nrr_utils.cpp new file mode 100644 index 00000000..60b69095 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_nrr_utils.cpp @@ -0,0 +1,287 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ro_nrr_utils.hpp" +#include "ro_random.hpp" +#include "ro_service_impl.hpp" + +namespace ams::ro::impl { + + namespace { + + constexpr size_t KeyGenerationMax100 = 0; + constexpr size_t KeyGenerationMax910 = 1; + constexpr size_t KeyGenerationMax = std::max(KeyGenerationMax100, KeyGenerationMax910); + constexpr size_t KeyGenerationCount = KeyGenerationMax + 1; + + constexpr size_t RsaKeySize = 0x100; + + constexpr const u8 Exponent[3] = { 0x01, 0x00, 0x01 }; + + constexpr const u8 DevModuli[KeyGenerationCount][RsaKeySize] = { + { + 0xC1, 0x15, 0x7C, 0x02, 0x26, 0xE5, 0x35, 0x6F, 0x99, 0xDB, 0xBE, 0xBD, 0xD7, 0x01, 0x07, 0x1C, + 0xC2, 0x3D, 0x1E, 0x6B, 0x7E, 0x08, 0x07, 0xBC, 0xE2, 0x6D, 0x49, 0xEC, 0x0B, 0xFF, 0xE4, 0x91, + 0x8C, 0x62, 0xB9, 0xFC, 0x69, 0xBF, 0x3A, 0xB6, 0x6C, 0x3A, 0x5D, 0x0E, 0x31, 0x5E, 0x6C, 0x1D, + 0x9A, 0x21, 0xD9, 0x9D, 0xD3, 0xB8, 0x50, 0x5F, 0x27, 0x7C, 0x4A, 0xD2, 0xFE, 0xE8, 0xDA, 0x1C, + 0xB9, 0x9E, 0x7E, 0x1E, 0x2F, 0x7D, 0xF9, 0x70, 0xA2, 0x98, 0x19, 0x6A, 0x53, 0x40, 0x64, 0xE7, + 0xC0, 0x92, 0xAE, 0x64, 0xD2, 0x01, 0xB2, 0x49, 0x30, 0x19, 0x7F, 0xF8, 0x6E, 0x0D, 0x49, 0x35, + 0xE9, 0x95, 0x77, 0x00, 0x65, 0xC5, 0x1E, 0xF5, 0x2A, 0xF9, 0xA1, 0x52, 0xA0, 0xA4, 0xFA, 0x87, + 0x3D, 0x8F, 0x51, 0xEC, 0x02, 0x80, 0xA4, 0xC7, 0x22, 0x74, 0xEF, 0x56, 0x61, 0x71, 0x39, 0xE2, + 0x2F, 0x03, 0x82, 0xDB, 0x50, 0xE9, 0xCC, 0x60, 0x48, 0x46, 0x71, 0xE2, 0xC6, 0x71, 0xF3, 0xF9, + 0x85, 0x52, 0x1A, 0xE2, 0xA8, 0x18, 0x77, 0x86, 0xD0, 0x12, 0xEB, 0x4F, 0x81, 0xA0, 0xDF, 0x20, + 0x42, 0xF0, 0xF8, 0xE3, 0x00, 0xE6, 0xFC, 0xA7, 0x44, 0xF0, 0xDC, 0x2B, 0x5B, 0xA0, 0xD3, 0x01, + 0x34, 0xD0, 0xD7, 0xFD, 0xEF, 0x66, 0x92, 0xB3, 0x87, 0x64, 0xD9, 0x76, 0xDA, 0x6E, 0x3A, 0x19, + 0x98, 0x1F, 0xBD, 0x1F, 0x25, 0x69, 0x9F, 0x28, 0xE6, 0x9E, 0xB7, 0x38, 0x92, 0x12, 0x16, 0xDE, + 0xDA, 0xE2, 0xB9, 0x7E, 0xFA, 0x98, 0x94, 0xF4, 0x9A, 0xDF, 0x2D, 0xC0, 0x99, 0x83, 0x61, 0xAD, + 0xB8, 0x3E, 0x27, 0x3F, 0x0E, 0xB8, 0x9E, 0x9B, 0x11, 0x78, 0xF1, 0x06, 0x30, 0x5B, 0xCA, 0xF4, + 0xEB, 0x72, 0x20, 0xD3, 0x15, 0x15, 0xC0, 0xC7, 0x1A, 0x08, 0xAE, 0x6E, 0xB2, 0x02, 0x43, 0xE9, + }, + { + 0xB0, 0x3A, 0xC3, 0x11, 0x58, 0xAC, 0x95, 0x9D, 0xED, 0x88, 0x80, 0xD9, 0x93, 0x71, 0x8E, 0xA0, + 0xBD, 0x19, 0x68, 0x6A, 0x06, 0x63, 0x7F, 0x06, 0x93, 0xBA, 0x43, 0x24, 0x5E, 0xD0, 0x54, 0x76, + 0x2F, 0x8D, 0x6A, 0xF4, 0x7B, 0x16, 0x7A, 0x68, 0xE2, 0xE6, 0x95, 0x82, 0x57, 0xD9, 0x01, 0x34, + 0x87, 0x42, 0x24, 0x83, 0x91, 0x7F, 0xE7, 0xB4, 0xB7, 0xE5, 0x8B, 0x4B, 0x08, 0x1F, 0x70, 0x58, + 0x5A, 0x30, 0xE0, 0xC9, 0xAB, 0xB8, 0xB1, 0x96, 0xFA, 0x45, 0xA2, 0xF7, 0x3C, 0x94, 0x65, 0x97, + 0xE2, 0x72, 0x7A, 0x19, 0xE2, 0x5F, 0x30, 0xF0, 0xA9, 0x44, 0xD8, 0x4B, 0x00, 0xEE, 0xD0, 0x99, + 0xA7, 0xAB, 0xC7, 0x14, 0x94, 0xE8, 0xD8, 0x70, 0x8F, 0xC7, 0x3C, 0x95, 0x0F, 0xE9, 0x52, 0x44, + 0x7A, 0xA4, 0xCA, 0xB4, 0xCE, 0x31, 0xCD, 0xC8, 0xC0, 0xF3, 0x08, 0x51, 0xA3, 0xC1, 0x5D, 0xA5, + 0x3E, 0x4A, 0xCE, 0xE8, 0x17, 0xA2, 0xED, 0xB8, 0x94, 0xD7, 0x3C, 0xF0, 0x42, 0x5A, 0xC1, 0x83, + 0xE8, 0x65, 0x28, 0x79, 0x43, 0x2C, 0x17, 0xBB, 0x68, 0x91, 0x80, 0xE3, 0xDA, 0x6B, 0xD4, 0x0B, + 0xA3, 0x6A, 0x8B, 0xDB, 0x1E, 0x2E, 0x16, 0xFE, 0xAB, 0x81, 0x16, 0x84, 0x6E, 0x53, 0xFD, 0xB6, + 0x1F, 0x12, 0xDE, 0x77, 0x79, 0xE6, 0x9A, 0x02, 0x95, 0x6B, 0x22, 0xFF, 0xE3, 0x10, 0xD2, 0x0D, + 0x5B, 0x33, 0xB1, 0xC0, 0x5C, 0xF6, 0xAC, 0x69, 0xAF, 0xCF, 0xD2, 0x34, 0x4B, 0x76, 0x88, 0xC1, + 0x91, 0x66, 0x6C, 0x2F, 0x06, 0xBA, 0x2B, 0xC7, 0x35, 0x0A, 0xD8, 0xD7, 0x5C, 0xF1, 0x20, 0x67, + 0x31, 0x4A, 0x9B, 0xB7, 0xCA, 0x4A, 0x3F, 0xAE, 0x4F, 0xF3, 0x02, 0x71, 0x97, 0xD6, 0x78, 0xC6, + 0x8E, 0x4B, 0x41, 0x78, 0x5B, 0x56, 0x6F, 0x6C, 0xA0, 0x0D, 0x83, 0x28, 0x25, 0x07, 0x75, 0x15, + } + }; + + constexpr const u8 ProdModuli[KeyGenerationCount][RsaKeySize] = { + { + 0xCF, 0x2F, 0x54, 0xF1, 0x7D, 0xA3, 0x11, 0x47, 0xC3, 0xBD, 0xED, 0x10, 0x3D, 0x64, 0xF8, 0x6B, + 0x51, 0x62, 0x05, 0x6F, 0x10, 0x5F, 0x50, 0x76, 0xF2, 0x40, 0x09, 0xA3, 0x16, 0xFC, 0xC3, 0xA8, + 0x75, 0xFD, 0x7C, 0xB4, 0x38, 0x9D, 0x30, 0x5C, 0x02, 0x6E, 0x2C, 0xCF, 0x03, 0x84, 0x23, 0x64, + 0x4A, 0xF9, 0x90, 0x44, 0x58, 0xAF, 0x43, 0x19, 0xD2, 0x27, 0xF1, 0x60, 0x28, 0xB8, 0x7B, 0x26, + 0xD3, 0x21, 0xE8, 0xF0, 0xED, 0x65, 0x01, 0x31, 0x51, 0x45, 0xD3, 0x93, 0xCF, 0x07, 0x65, 0xF1, + 0x8A, 0x72, 0x61, 0x55, 0x63, 0x91, 0x84, 0x63, 0x2C, 0x0B, 0x3E, 0xF6, 0x7E, 0x9B, 0x19, 0x4C, + 0x42, 0xE3, 0xB9, 0x83, 0xF3, 0xA1, 0x2B, 0x7B, 0xA1, 0x5F, 0x00, 0xDC, 0x80, 0x1A, 0x6D, 0x6C, + 0x22, 0x20, 0x2F, 0x19, 0x61, 0x96, 0x5E, 0xD9, 0x93, 0x45, 0x62, 0x48, 0xC1, 0xB1, 0x48, 0x8A, + 0x07, 0x6C, 0xBB, 0x3D, 0xCC, 0xE0, 0xEF, 0x43, 0x56, 0x1D, 0x2A, 0x1D, 0x8E, 0x19, 0xBF, 0x99, + 0x7A, 0x56, 0x9B, 0x72, 0xB9, 0x49, 0x86, 0xD4, 0x5D, 0xA0, 0xE5, 0x40, 0x2C, 0x2D, 0x31, 0x5A, + 0x68, 0x90, 0xA4, 0xDA, 0xED, 0x52, 0xCF, 0xE4, 0x3C, 0x35, 0x5E, 0x94, 0xC8, 0x36, 0x6E, 0x5F, + 0xF5, 0x50, 0x9D, 0xBD, 0x3F, 0x86, 0x28, 0xC4, 0xF7, 0x0D, 0xFA, 0x1D, 0x65, 0xCE, 0x7F, 0x83, + 0x8E, 0xEE, 0x75, 0x8B, 0x8D, 0xF3, 0x1C, 0x3E, 0x8B, 0xB7, 0x31, 0x60, 0x0E, 0x33, 0xC0, 0x8C, + 0xEF, 0xBA, 0xD0, 0xF0, 0x56, 0x35, 0x11, 0x70, 0x3D, 0x3A, 0x60, 0xFF, 0xBB, 0xC8, 0x49, 0xB3, + 0x50, 0x93, 0xB5, 0x0C, 0x4D, 0x62, 0x2A, 0x53, 0x76, 0x69, 0x5D, 0x81, 0x35, 0x79, 0x08, 0xAE, + 0x08, 0x7D, 0x51, 0x72, 0xE4, 0xE9, 0xD9, 0xBE, 0x91, 0xB9, 0x29, 0x89, 0xDD, 0xC9, 0xF9, 0xDB, + }, + { + 0xBD, 0x8E, 0x75, 0x8D, 0x6A, 0x5E, 0xCF, 0x1A, 0x68, 0x7D, 0x9D, 0x06, 0x80, 0x60, 0x48, 0x56, + 0x73, 0x13, 0xB8, 0x8D, 0xA5, 0x83, 0x11, 0xE3, 0xF4, 0xB5, 0xBF, 0x21, 0x56, 0x50, 0x6F, 0x68, + 0xFD, 0x2E, 0xEF, 0x3A, 0xE1, 0x7D, 0x5D, 0xA2, 0x02, 0x21, 0xAD, 0x57, 0x7F, 0xA9, 0xD1, 0x95, + 0xA6, 0x5C, 0x80, 0x67, 0x2A, 0xAE, 0x29, 0xCD, 0x98, 0x4C, 0x71, 0x98, 0x22, 0xD3, 0x39, 0x77, + 0xA3, 0x39, 0xE8, 0xB0, 0x7E, 0x77, 0x4D, 0x54, 0xA5, 0x9F, 0xF3, 0xF1, 0xF0, 0x85, 0x74, 0x64, + 0xF7, 0x17, 0xAC, 0xEB, 0x95, 0xAD, 0x01, 0x3A, 0xA2, 0xFB, 0x18, 0xEC, 0x6C, 0x06, 0xD5, 0xBD, + 0x0F, 0x66, 0x99, 0x2C, 0xEE, 0x81, 0x87, 0x40, 0xDA, 0xBC, 0x30, 0xC9, 0x54, 0x21, 0x46, 0x6B, + 0xD5, 0xAE, 0xDF, 0xA8, 0x8F, 0x61, 0x19, 0x5E, 0x75, 0x68, 0xED, 0x80, 0x73, 0x5F, 0x4D, 0xF2, + 0xC2, 0x09, 0xC8, 0x2D, 0x73, 0x12, 0x18, 0x35, 0x7F, 0x5E, 0xA8, 0xAD, 0x37, 0x6B, 0x9C, 0x14, + 0x8C, 0x4E, 0x7D, 0xC0, 0xDC, 0x42, 0x45, 0xDD, 0x84, 0x5A, 0x0B, 0xED, 0x97, 0xBE, 0x66, 0xD1, + 0xC0, 0xC4, 0x7C, 0x48, 0x66, 0x22, 0xAA, 0xFB, 0x47, 0x95, 0x84, 0x45, 0x2E, 0x70, 0x35, 0x68, + 0x04, 0xF0, 0xBC, 0xEE, 0xCB, 0x6E, 0xB7, 0x09, 0xBA, 0xD9, 0xEA, 0xF9, 0x78, 0x32, 0x4C, 0xD5, + 0x0F, 0x6E, 0xEF, 0x4D, 0x21, 0xE0, 0xE7, 0xEA, 0x17, 0xA3, 0xEC, 0x0D, 0x76, 0xC0, 0xB9, 0xCC, + 0x1B, 0x8C, 0x7E, 0xFB, 0x56, 0x5B, 0x60, 0x94, 0x26, 0xB7, 0x9F, 0x2B, 0xAD, 0xC6, 0x1F, 0xBD, + 0xB6, 0xFA, 0x33, 0xDB, 0x1C, 0xBC, 0x28, 0xE1, 0x85, 0x1E, 0x2F, 0xF9, 0xFD, 0xEA, 0x83, 0x52, + 0x2F, 0xEE, 0x07, 0x7C, 0x1E, 0x2F, 0xCD, 0x76, 0x91, 0xEA, 0x27, 0xE6, 0x50, 0x9A, 0x86, 0x23, + } + }; + + /* Helper functions. */ + + Result GetCertificationModulus(const u8 **out, u32 key_generation) { + if (hos::GetVersion() >= hos::Version_9_1_0) { + R_UNLESS(key_generation <= KeyGenerationMax910, ro::ResultNotAuthorized()); + } else { + R_UNLESS(key_generation <= KeyGenerationMax100, ro::ResultNotAuthorized()); + } + + *out = IsDevelopmentHardware() ? DevModuli[key_generation] : ProdModuli[key_generation]; + R_SUCCEED(); + } + + Result ValidateNrrCertification(const NrrHeader *header, const u8 *mod) { + static_assert(RsaKeySize == NrrCertification::RsaKeySize); + + /* Verify the signature. */ + const u8 *sig = header->GetCertificationSignature(); + const size_t sig_size = RsaKeySize; + const size_t mod_size = RsaKeySize; + const u8 *exp = Exponent; + const size_t exp_size = util::size(Exponent); + const u8 *msg = header->GetCertificationSignedArea(); + const size_t msg_size = NrrCertification::SignedSize; + R_UNLESS(crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size), ro::ResultNotAuthorized()); + + /* Check ProgramId pattern is valid. */ + R_UNLESS(header->IsProgramIdValid(), ro::ResultNotAuthorized()); + + R_SUCCEED(); + } + + Result ValidateNrrSignature(const NrrHeader *header) { + static_assert(RsaKeySize == NrrCertification::RsaKeySize); + + /* Verify the signature. */ + const u8 *sig = header->GetSignature(); + const size_t sig_size = RsaKeySize; + const u8 *mod = header->GetCertificationModulus(); + const size_t mod_size = RsaKeySize; + const u8 *exp = Exponent; + const size_t exp_size = util::size(Exponent); + const u8 *msg = header->GetSignedArea(); + const size_t msg_size = header->GetSignedAreaSize(); + R_UNLESS(crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size), ro::ResultNotAuthorized()); + + R_SUCCEED(); + } + + Result ValidateNrr(const NrrHeader *header, u64 size, ncm::ProgramId program_id, NrrKind nrr_kind, bool enforce_nrr_kind) { + /* Check magic. */ + R_UNLESS(header->IsMagicValid(), ro::ResultInvalidNrr()); + + /* Check size. */ + R_UNLESS(header->GetSize() == size, ro::ResultInvalidSize()); + + /* Only perform checks if we must. */ + const bool ease_nro_restriction = ShouldEaseNroRestriction(); + + /* Get certification modulus. */ + /* NOTE: This must succeed even when ease_nro_restriction is true. */ + const u8 *modulus; + R_TRY(GetCertificationModulus(std::addressof(modulus), header->GetKeyGeneration())); + + if (!ease_nro_restriction) { + /* Check certification signature. */ + R_TRY(ValidateNrrCertification(header, modulus)); + + /* Check NRR signature. */ + R_TRY(ValidateNrrSignature(header)); + + /* Check program id. */ + R_UNLESS(header->GetProgramId() == program_id, ro::ResultInvalidNrr()); + + /* Check nrr kind. */ + if (hos::GetVersion() >= hos::Version_7_0_0 && enforce_nrr_kind) { + R_UNLESS(header->GetNrrKind() == nrr_kind, ro::ResultInvalidNrrKind()); + } + } + + R_SUCCEED(); + } + + } + + /* Utilities for working with NRRs. */ + Result MapAndValidateNrr(NrrHeader **out_header, u64 *out_mapped_code_address, void *out_hash, size_t out_hash_size, os::NativeHandle process_handle, ncm::ProgramId program_id, u64 nrr_heap_address, u64 nrr_heap_size, NrrKind nrr_kind, bool enforce_nrr_kind) { + /* Re-map the nrr as code memory in the destination process. */ + u64 code_address = 0; + const os::ProcessMemoryRegion region = { nrr_heap_address, nrr_heap_size }; + R_TRY_CATCH(os::MapProcessCodeMemory(std::addressof(code_address), process_handle, std::addressof(region), 1, ro::impl::GenerateSecureRandom)) { + R_CONVERT(os::ResultOutOfAddressSpace, ro::ResultOutOfAddressSpace()) + } R_END_TRY_CATCH; + + /* If we fail, unmap the nrr code memory. */ + ON_RESULT_FAILURE { R_ABORT_UNLESS(os::UnmapProcessCodeMemory(process_handle, code_address, std::addressof(region), 1)); }; + + /* Map the nrr in our process. */ + void *mapped_memory = nullptr; + R_TRY_CATCH(os::MapProcessMemory(std::addressof(mapped_memory), process_handle, code_address, region.size, ro::impl::GenerateSecureRandom)) { + R_CONVERT(os::ResultOutOfAddressSpace, ro::ResultOutOfAddressSpace()) + } R_END_TRY_CATCH; + + /* If we fail, unmap the nrr memory. */ + ON_RESULT_FAILURE_2 { os::UnmapProcessMemory(mapped_memory, process_handle, code_address, region.size); }; + + /* Validate the nrr header. */ + NrrHeader *nrr_header = static_cast<NrrHeader *>(mapped_memory); + R_TRY(ValidateNrr(nrr_header, nrr_heap_size, program_id, nrr_kind, enforce_nrr_kind)); + + /* Save a copy of the hash that we verified. */ + crypto::GenerateSha256(out_hash, out_hash_size, nrr_header->GetSignedArea(), nrr_header->GetSignedAreaSize()); + + *out_header = nrr_header; + *out_mapped_code_address = code_address; + R_SUCCEED(); + } + + Result UnmapNrr(os::NativeHandle process_handle, const NrrHeader *header, u64 nrr_heap_address, u64 nrr_heap_size, u64 mapped_code_address) { + /* Unmap our process mapping. */ + os::UnmapProcessMemory(const_cast<NrrHeader *>(header), process_handle, mapped_code_address, nrr_heap_size); + + /* Unmap the code memory mapping. */ + const os::ProcessMemoryRegion region = { nrr_heap_address, nrr_heap_size }; + R_RETURN(os::UnmapProcessCodeMemory(process_handle, mapped_code_address, std::addressof(region), 1)); + } + + bool ValidateNrrHashTableEntry(const void *signed_area, size_t signed_area_size, size_t hashes_offset, size_t num_hashes, const void *nrr_hash, const u8 *hash_table, const void *desired_hash) { + crypto::Sha256Generator sha256; + sha256.Initialize(); + + /* Hash data before the hash table. */ + const size_t pre_hash_table_size = hashes_offset - NrrHeader::GetSignedAreaOffset(); + sha256.Update(signed_area, pre_hash_table_size); + + /* Hash the hash table, checking if the desired hash exists inside it. */ + size_t remaining_size = signed_area_size - pre_hash_table_size; + bool found_hash = false; + for (size_t i = 0; i < num_hashes; i++) { + /* Get the current hash. */ + u8 cur_hash[crypto::Sha256Generator::HashSize]; + std::memcpy(cur_hash, hash_table, sizeof(cur_hash)); + + /* Hash the current hash. */ + sha256.Update(cur_hash, sizeof(cur_hash)); + + /* Check if the current hash is our target. */ + found_hash |= std::memcmp(cur_hash, desired_hash, sizeof(cur_hash)) == 0; + + /* Advance our pointers. */ + hash_table += sizeof(cur_hash); + remaining_size -= sizeof(cur_hash); + } + + /* Data after the hash table should be all zeroes. */ + u8 work_buf[crypto::Sha256Generator::HashSize]; + { + crypto::ClearMemory(work_buf, sizeof(work_buf)); + while (remaining_size > 0) { + const size_t cur_size = std::min(remaining_size, sizeof(work_buf)); + sha256.Update(work_buf, cur_size); + remaining_size -= cur_size; + } + } + + /* Validate the final hash. */ + sha256.GetHash(work_buf, sizeof(work_buf)); + + /* Use & operator to avoid short circuiting. */ + const bool is_valid = found_hash & (std::memcmp(work_buf, nrr_hash, sizeof(work_buf)) == 0); + + /* Return result. */ + return is_valid; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_nrr_utils.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_nrr_utils.hpp new file mode 100644 index 00000000..11e2ca80 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_nrr_utils.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> + +namespace ams::ro::impl { + + /* Utilities for working with NRRs. */ + Result MapAndValidateNrr(NrrHeader **out_header, u64 *out_mapped_code_address, void *out_hash, size_t out_hash_size, os::NativeHandle process_handle, ncm::ProgramId program_id, u64 nrr_heap_address, u64 nrr_heap_size, NrrKind nrr_kind, bool enforce_nrr_kind); + Result UnmapNrr(os::NativeHandle process_handle, const NrrHeader *header, u64 nrr_heap_address, u64 nrr_heap_size, u64 mapped_code_address); + + bool ValidateNrrHashTableEntry(const void *signed_area, size_t signed_area_size, size_t hashes_offset, size_t num_hashes, const void *nrr_hash, const u8 *hash_table, const void *desired_hash); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_patcher.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_patcher.cpp new file mode 100644 index 00000000..b2ee971f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_patcher.cpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ro_patcher.hpp" + +namespace ams::ro::impl { + + namespace { + + constexpr const char *NroPatchesDirectory = "nro_patches"; + + /* NRO patches want to prevent modification of header, */ + /* but don't want to adjust offset relative to mapped location. */ + constexpr size_t NroPatchesProtectedSize = sizeof(NroHeader); + constexpr size_t NroPatchesProtectedOffset = 0; + + } + + /* Apply IPS patches. */ + void LocateAndApplyIpsPatchesToModule(const ModuleId *module_id, u8 *mapped_nro, size_t mapped_size) { + ams::patcher::LocateAndApplyIpsPatchesToModule("sdmc", NroPatchesDirectory, NroPatchesProtectedSize, NroPatchesProtectedOffset, module_id, mapped_nro, mapped_size); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_patcher.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_patcher.hpp new file mode 100644 index 00000000..26f76a83 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_patcher.hpp @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> + +namespace ams::ro::impl { + + /* Apply IPS patches. */ + void LocateAndApplyIpsPatchesToModule(const ModuleId *module_id, u8 *mapped_nro, size_t mapped_size); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_random.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_random.cpp new file mode 100644 index 00000000..618b3967 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_random.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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ro_random.hpp" + +namespace ams::ro::impl { + + u64 GenerateSecureRandom(u64 max) { + /* Generate a cryptographically random number. */ + u64 rand; + crypto::GenerateCryptographicallyRandomBytes(std::addressof(rand), sizeof(rand)); + + /* Coerce into range. */ + return rand % (max + 1); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_random.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_random.hpp new file mode 100644 index 00000000..db4ee9e1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_random.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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> + +namespace ams::ro::impl { + + u64 GenerateSecureRandom(u64 max); + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_service_impl.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_service_impl.cpp new file mode 100644 index 00000000..0eee275e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_service_impl.cpp @@ -0,0 +1,605 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ro_nrr_utils.hpp" +#include "ro_nro_utils.hpp" +#include "ro_patcher.hpp" +#include "ro_random.hpp" +#include "ro_service_impl.hpp" + +namespace ams::ro::impl { + + namespace { + + /* Convenience definitions. */ + constexpr size_t MaxSessions = 0x3; /* 2 official sessions (applet + application, 1 homebrew session). */ + constexpr size_t MaxNrrInfos = 0x40; + constexpr size_t MaxNroInfos = 0x40; + + /* Types. */ + struct Sha256Hash { + u8 hash[crypto::Sha256Generator::HashSize]; + + bool operator==(const Sha256Hash &o) const { + return std::memcmp(this, std::addressof(o), sizeof(*this)) == 0; + } + bool operator!=(const Sha256Hash &o) const { + return std::memcmp(this, std::addressof(o), sizeof(*this)) != 0; + } + bool operator<(const Sha256Hash &o) const { + return std::memcmp(this, std::addressof(o), sizeof(*this)) < 0; + } + bool operator>(const Sha256Hash &o) const { + return std::memcmp(this, std::addressof(o), sizeof(*this)) > 0; + } + }; + static_assert(sizeof(Sha256Hash) == sizeof(Sha256Hash::hash)); + + struct NroInfo { + u64 base_address; + u64 nro_heap_address; + u64 nro_heap_size; + u64 bss_heap_address; + u64 bss_heap_size; + u64 code_size; + u64 rw_size; + ModuleId module_id; + }; + + struct NrrInfo { + const NrrHeader *mapped_header; + u64 nrr_heap_address; + u64 nrr_heap_size; + u64 mapped_code_address; + + /* Verification. */ + u32 cached_signed_area_size; + u32 cached_hashes_offset; + u32 cached_num_hashes; + u8 cached_signed_area[sizeof(NrrHeader) - NrrHeader::GetSignedAreaOffset()]; + Sha256Hash signed_area_hash; + }; + + struct ProcessContext { + private: + bool m_nro_in_use[MaxNroInfos]{}; + bool m_nrr_in_use[MaxNrrInfos]{}; + NroInfo m_nro_infos[MaxNroInfos]{}; + NrrInfo m_nrr_infos[MaxNrrInfos]{}; + os::NativeHandle m_process_handle = os::InvalidNativeHandle; + os::ProcessId m_process_id = os::InvalidProcessId; + bool m_in_use{}; + public: + constexpr ProcessContext() = default; + + void Initialize(os::NativeHandle process_handle, os::ProcessId process_id) { + AMS_ABORT_UNLESS(!m_in_use); + + std::memset(m_nro_in_use, 0, sizeof(m_nro_in_use)); + std::memset(m_nrr_in_use, 0, sizeof(m_nrr_in_use)); + std::memset(m_nro_infos, 0, sizeof(m_nro_infos)); + std::memset(m_nrr_infos, 0, sizeof(m_nrr_infos)); + + m_process_handle = process_handle; + m_process_id = process_id; + m_in_use = true; + } + + void Finalize() { + AMS_ABORT_UNLESS(m_in_use); + + if (m_process_handle != os::InvalidNativeHandle) { + for (size_t i = 0; i < MaxNrrInfos; i++) { + if (m_nrr_in_use[i]) { + UnmapNrr(m_process_handle, m_nrr_infos[i].mapped_header, m_nrr_infos[i].nrr_heap_address, m_nrr_infos[i].nrr_heap_size, m_nrr_infos[i].mapped_code_address); + } + } + os::CloseNativeHandle(m_process_handle); + } + + std::memset(m_nro_in_use, 0, sizeof(m_nro_in_use)); + std::memset(m_nrr_in_use, 0, sizeof(m_nrr_in_use)); + std::memset(m_nro_infos, 0, sizeof(m_nro_infos)); + std::memset(m_nrr_infos, 0, sizeof(m_nrr_infos)); + + m_process_handle = os::InvalidNativeHandle; + m_process_id = os::InvalidProcessId; + + m_in_use = false; + } + + os::NativeHandle GetProcessHandle() const { + return m_process_handle; + } + + os::ProcessId GetProcessId() const { + return m_process_id; + } + + bool IsFree() const { + return !m_in_use; + } + + ncm::ProgramId GetProgramId(os::NativeHandle other_process_h) const { + /* Automatically select a handle, allowing for override. */ + if (other_process_h != os::InvalidNativeHandle) { + return os::GetProgramId(other_process_h); + } else { + return os::GetProgramId(m_process_handle); + } + } + + Result GetNrrInfoByAddress(NrrInfo **out, u64 nrr_heap_address) { + for (size_t i = 0; i < MaxNrrInfos; i++) { + if (m_nrr_in_use[i] && m_nrr_infos[i].nrr_heap_address == nrr_heap_address) { + if (out != nullptr) { + *out = m_nrr_infos + i; + } + R_SUCCEED(); + } + } + R_THROW(ro::ResultNotRegistered()); + } + + Result GetFreeNrrInfo(NrrInfo **out) { + for (size_t i = 0; i < MaxNrrInfos; i++) { + if (!m_nrr_in_use[i]) { + if (out != nullptr) { + *out = m_nrr_infos + i; + } + R_SUCCEED(); + } + } + R_THROW(ro::ResultTooManyNrr()); + } + + Result GetNroInfoByAddress(NroInfo **out, u64 nro_address) { + for (size_t i = 0; i < MaxNroInfos; i++) { + if (m_nro_in_use[i] && m_nro_infos[i].base_address == nro_address) { + if (out != nullptr) { + *out = m_nro_infos + i; + } + R_SUCCEED(); + } + } + R_THROW(ro::ResultNotLoaded()); + } + + Result GetNroInfoByModuleId(NroInfo **out, const ModuleId *module_id) { + for (size_t i = 0; i < MaxNroInfos; i++) { + if (m_nro_in_use[i] && std::memcmp(std::addressof(m_nro_infos[i].module_id), module_id, sizeof(*module_id)) == 0) { + if (out != nullptr) { + *out = m_nro_infos + i; + } + R_SUCCEED(); + } + } + R_THROW(ro::ResultNotLoaded()); + } + + Result GetFreeNroInfo(NroInfo **out) { + for (size_t i = 0; i < MaxNroInfos; i++) { + if (!m_nro_in_use[i]) { + if (out != nullptr) { + *out = m_nro_infos + i; + } + R_SUCCEED(); + } + } + R_THROW(ro::ResultTooManyNro()); + } + + Result ValidateHasNroHash(const NroHeader *nro_header) const { + /* Calculate hash. */ + Sha256Hash hash; + crypto::GenerateSha256(std::addressof(hash), sizeof(hash), nro_header, nro_header->GetSize()); + + for (size_t i = 0; i < MaxNrrInfos; i++) { + /* Ensure we only check NRRs that are used. */ + if (!m_nrr_in_use[i]) { + continue; + } + + /* Get the mapped header, ensure that it has hashes. */ + const NrrHeader *mapped_nrr_header = m_nrr_infos[i].mapped_header; + const size_t mapped_num_hashes = mapped_nrr_header->GetNumHashes(); + if (mapped_num_hashes == 0) { + continue; + } + + /* Locate the hash within the mapped array. */ + const Sha256Hash *mapped_nro_hashes_start = reinterpret_cast<const Sha256Hash *>(mapped_nrr_header->GetHashes()); + const Sha256Hash *mapped_nro_hashes_end = mapped_nro_hashes_start + mapped_nrr_header->GetNumHashes(); + + const Sha256Hash *mapped_lower_bound = std::lower_bound(mapped_nro_hashes_start, mapped_nro_hashes_end, hash); + if (mapped_lower_bound == mapped_nro_hashes_end || (*mapped_lower_bound != hash)) { + continue; + } + + /* Check that the hash entry is valid, since our heuristic passed. */ + const void *nrr_hash = std::addressof(m_nrr_infos[i].signed_area_hash); + const void *signed_area = m_nrr_infos[i].cached_signed_area; + const size_t signed_area_size = m_nrr_infos[i].cached_signed_area_size; + const size_t hashes_offset = m_nrr_infos[i].cached_hashes_offset; + const size_t num_hashes = m_nrr_infos[i].cached_num_hashes; + const u8 *hash_table = reinterpret_cast<const u8 *>(mapped_nro_hashes_start); + if (!ValidateNrrHashTableEntry(signed_area, signed_area_size, hashes_offset, num_hashes, nrr_hash, hash_table, std::addressof(hash))) { + continue; + } + + /* The hash is valid! */ + R_SUCCEED(); + } + + R_THROW(ro::ResultNotAuthorized()); + } + + Result ValidateNro(ModuleId *out_module_id, u64 *out_rx_size, u64 *out_ro_size, u64 *out_rw_size, bool *out_aligned_header, u64 base_address, u64 expected_nro_size, u64 expected_bss_size) { + /* Map the NRO. */ + void *mapped_memory = nullptr; + R_TRY_CATCH(os::MapProcessMemory(std::addressof(mapped_memory), m_process_handle, base_address, expected_nro_size, ro::impl::GenerateSecureRandom)) { + R_CONVERT(os::ResultOutOfAddressSpace, ro::ResultOutOfAddressSpace()) + } R_END_TRY_CATCH; + + /* When we're done, unmap the memory. */ + ON_SCOPE_EXIT { os::UnmapProcessMemory(mapped_memory, m_process_handle, base_address, expected_nro_size); }; + + /* Validate header. */ + const NroHeader *header = static_cast<const NroHeader *>(mapped_memory); + R_UNLESS(header->IsMagicValid(), ro::ResultInvalidNro()); + + /* Read sizes from header. */ + const u64 nro_size = header->GetSize(); + const u64 text_ofs = header->GetTextOffset(); + const u64 text_size = header->GetTextSize(); + const u64 ro_ofs = header->GetRoOffset(); + const u64 ro_size = header->GetRoSize(); + const u64 rw_ofs = header->GetRwOffset(); + const u64 rw_size = header->GetRwSize(); + const u64 bss_size = header->GetBssSize(); + + /* Validate sizes meet expected. */ + R_UNLESS(nro_size == expected_nro_size, ro::ResultInvalidNro()); + R_UNLESS(bss_size == expected_bss_size, ro::ResultInvalidNro()); + + /* Validate all sizes are aligned. */ + R_UNLESS(util::IsAligned(text_size, os::MemoryPageSize), ro::ResultInvalidNro()); + R_UNLESS(util::IsAligned(ro_size, os::MemoryPageSize), ro::ResultInvalidNro()); + R_UNLESS(util::IsAligned(rw_size, os::MemoryPageSize), ro::ResultInvalidNro()); + R_UNLESS(util::IsAligned(bss_size, os::MemoryPageSize), ro::ResultInvalidNro()); + + /* Validate sections are in order. */ + R_UNLESS(text_ofs <= ro_ofs, ro::ResultInvalidNro()); + R_UNLESS(ro_ofs <= rw_ofs, ro::ResultInvalidNro()); + + /* Validate sections are sequential and contiguous. */ + R_UNLESS(text_ofs == 0, ro::ResultInvalidNro()); + R_UNLESS(text_ofs + text_size == ro_ofs, ro::ResultInvalidNro()); + R_UNLESS(ro_ofs + ro_size == rw_ofs, ro::ResultInvalidNro()); + R_UNLESS(rw_ofs + rw_size == nro_size, ro::ResultInvalidNro()); + + /* Verify NRO hash. */ + R_TRY(this->ValidateHasNroHash(header)); + + /* Check if NRO has already been loaded. */ + const ModuleId *module_id = header->GetModuleId(); + R_UNLESS(R_FAILED(this->GetNroInfoByModuleId(nullptr, module_id)), ro::ResultAlreadyLoaded()); + + /* Apply patches to NRO. */ + LocateAndApplyIpsPatchesToModule(module_id, static_cast<u8 *>(mapped_memory), nro_size); + + /* Copy to output. */ + *out_module_id = *module_id; + *out_rx_size = text_size; + *out_ro_size = ro_size; + *out_rw_size = rw_size; + *out_aligned_header = header->IsAlignedHeader(); + R_SUCCEED(); + } + + void SetNrrInfoInUse(const NrrInfo *info, bool in_use) { + AMS_ASSERT(std::addressof(m_nrr_infos[0]) <= info && info <= std::addressof(m_nrr_infos[MaxNrrInfos - 1])); + const size_t index = info - std::addressof(m_nrr_infos[0]); + m_nrr_in_use[index] = in_use; + } + + void SetNroInfoInUse(const NroInfo *info, bool in_use) { + AMS_ASSERT(std::addressof(m_nro_infos[0]) <= info && info <= std::addressof(m_nro_infos[MaxNroInfos - 1])); + const size_t index = info - std::addressof(m_nro_infos[0]); + m_nro_in_use[index] = in_use; + } + + void GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out_infos, size_t max_out_count) const { + size_t count = 0; + + for (size_t i = 0; i < MaxNroInfos && count < max_out_count; i++) { + if (!m_nro_in_use[i]) { + continue; + } + + const NroInfo *nro_info = m_nro_infos + i; + + /* Just copy out the info. */ + auto &out_info = out_infos[count++]; + + std::memcpy(out_info.module_id, nro_info->module_id.data, sizeof(out_info.module_id)); + out_info.address = nro_info->base_address; + out_info.size = nro_info->nro_heap_size + nro_info->bss_heap_size; + } + + *out_count = static_cast<u32>(count); + } + }; + + /* Globals. */ + constinit ProcessContext g_process_contexts[MaxSessions] = {}; + constinit bool g_is_development_hardware = false; + constinit bool g_is_development_function_enabled = false; + + /* Context Helpers. */ + ProcessContext *GetContextById(size_t context_id) { + if (context_id == InvalidContextId) { + return nullptr; + } + + AMS_ABORT_UNLESS(context_id < MaxSessions); + return g_process_contexts + context_id; + } + + ProcessContext *GetContextByProcessId(os::ProcessId process_id) { + for (size_t i = 0; i < MaxSessions; i++) { + if (g_process_contexts[i].GetProcessId() == process_id) { + return g_process_contexts + i; + } + } + return nullptr; + } + + size_t AllocateContext(os::NativeHandle process_handle, os::ProcessId process_id) { + /* Find a free process context. */ + for (size_t i = 0; i < MaxSessions; i++) { + ProcessContext *context = g_process_contexts + i; + + if (context->IsFree()) { + context->Initialize(process_handle, process_id); + return i; + } + } + /* Failure to find a free context is actually an abort condition. */ + AMS_ABORT_UNLESS(false); + } + + void FreeContext(size_t context_id) { + if (ProcessContext *context = GetContextById(context_id); context != nullptr) { + context->Finalize(); + } + } + + constexpr inline Result ValidateAddressAndNonZeroSize(u64 address, u64 size) { + R_UNLESS(util::IsAligned(address, os::MemoryPageSize), ro::ResultInvalidAddress()); + R_UNLESS(size != 0, ro::ResultInvalidSize()); + R_UNLESS(util::IsAligned(size, os::MemoryPageSize), ro::ResultInvalidSize()); + R_UNLESS(address < address + size, ro::ResultInvalidSize()); + R_SUCCEED(); + } + + constexpr inline Result ValidateAddressAndSize(u64 address, u64 size) { + R_UNLESS(util::IsAligned(address, os::MemoryPageSize), ro::ResultInvalidAddress()); + R_UNLESS(util::IsAligned(size, os::MemoryPageSize), ro::ResultInvalidSize()); + R_UNLESS(size == 0 || address < address + size, ro::ResultInvalidSize()); + R_SUCCEED(); + } + + } + + /* Access utilities. */ + void SetDevelopmentHardware(bool is_development_hardware) { + g_is_development_hardware = is_development_hardware; + } + + void SetDevelopmentFunctionEnabled(bool is_development_function_enabled) { + g_is_development_function_enabled = is_development_function_enabled; + } + + bool IsDevelopmentHardware() { + return g_is_development_hardware; + } + + bool IsDevelopmentFunctionEnabled() { + return g_is_development_function_enabled; + } + + bool ShouldEaseNroRestriction() { + /* Retrieve whether we should ease restrictions from set:sys. */ + u8 should_ease = 0; + if (settings::fwdbg::GetSettingsItemValue(std::addressof(should_ease), sizeof(should_ease), "ro", "ease_nro_restriction") != sizeof(should_ease)) { + return false; + } + + /* Nintendo only allows easing restriction on dev, we will allow on production, as well. */ + /* should_ease &= IsDevelopmentFunctionEnabled(); */ + return should_ease != 0; + } + + /* Context utilities. */ + Result RegisterProcess(size_t *out_context_id, sf::NativeHandle &&process_handle, os::ProcessId process_id) { + /* Validate process handle. */ + { + /* Validate handle is a valid process handle. */ + os::ProcessId handle_pid; + R_UNLESS(R_SUCCEEDED(os::GetProcessId(std::addressof(handle_pid), process_handle.GetOsHandle())), ro::ResultInvalidProcess()); + + /* Validate process id. */ + R_UNLESS(handle_pid == process_id, ro::ResultInvalidProcess()); + } + + /* Check if a process context already exists. */ + R_UNLESS(GetContextByProcessId(process_id) == nullptr, ro::ResultInvalidSession()); + + /* Allocate a context to manage the process handle. */ + *out_context_id = AllocateContext(process_handle.GetOsHandle(), process_id); + process_handle.Detach(); + + R_SUCCEED(); + } + + Result ValidateProcess(size_t context_id, os::ProcessId process_id) { + const ProcessContext *ctx = GetContextById(context_id); + R_UNLESS(ctx != nullptr, ro::ResultInvalidProcess()); + R_UNLESS(ctx->GetProcessId() == process_id, ro::ResultInvalidProcess()); + R_SUCCEED(); + } + + void UnregisterProcess(size_t context_id) { + FreeContext(context_id); + } + + /* Service implementations. */ + Result RegisterModuleInfo(size_t context_id, os::NativeHandle process_handle, u64 nrr_address, u64 nrr_size, NrrKind nrr_kind, bool enforce_nrr_kind) { + /* Get context. */ + ProcessContext *context = GetContextById(context_id); + AMS_ABORT_UNLESS(context != nullptr); + + /* Get program id. */ + const ncm::ProgramId program_id = context->GetProgramId(process_handle); + + /* Validate address/size. */ + R_TRY(ValidateAddressAndNonZeroSize(nrr_address, nrr_size)); + + /* Check we have space for a new NRR. */ + NrrInfo *nrr_info = nullptr; + R_TRY(context->GetFreeNrrInfo(std::addressof(nrr_info))); + + /* Prepare to cache the NRR's signature hash. */ + Sha256Hash signed_area_hash; + ON_SCOPE_EXIT { crypto::ClearMemory(std::addressof(signed_area_hash), sizeof(signed_area_hash)); }; + + /* Map. */ + NrrHeader *header = nullptr; + u64 mapped_code_address = 0; + R_TRY(MapAndValidateNrr(std::addressof(header), std::addressof(mapped_code_address), std::addressof(signed_area_hash), sizeof(signed_area_hash), context->GetProcessHandle(), program_id, nrr_address, nrr_size, nrr_kind, enforce_nrr_kind)); + + /* Set NRR info. */ + context->SetNrrInfoInUse(nrr_info, true); + nrr_info->mapped_header = header; + nrr_info->nrr_heap_address = nrr_address; + nrr_info->nrr_heap_size = nrr_size; + nrr_info->mapped_code_address = mapped_code_address; + + nrr_info->cached_signed_area_size = header->GetSignedAreaSize(); + nrr_info->cached_hashes_offset = header->GetHashesOffset(); + nrr_info->cached_num_hashes = header->GetNumHashes(); + + std::memcpy(nrr_info->cached_signed_area, header->GetSignedArea(), std::min(sizeof(nrr_info->cached_signed_area), header->GetHashesOffset() - header->GetSignedAreaOffset())); + std::memcpy(std::addressof(nrr_info->signed_area_hash), std::addressof(signed_area_hash), sizeof(signed_area_hash)); + + R_SUCCEED(); + } + + Result UnregisterModuleInfo(size_t context_id, u64 nrr_address) { + /* Get context. */ + ProcessContext *context = GetContextById(context_id); + AMS_ABORT_UNLESS(context != nullptr); + + /* Validate address. */ + R_UNLESS(util::IsAligned(nrr_address, os::MemoryPageSize), ro::ResultInvalidAddress()); + + /* Check the NRR is loaded. */ + NrrInfo *nrr_info = nullptr; + R_TRY(context->GetNrrInfoByAddress(std::addressof(nrr_info), nrr_address)); + + /* Unmap. */ + const NrrInfo nrr_backup = *nrr_info; + { + /* Nintendo does this unconditionally, whether or not the actual unmap succeeds. */ + context->SetNrrInfoInUse(nrr_info, false); + std::memset(nrr_info, 0, sizeof(*nrr_info)); + } + R_RETURN(UnmapNrr(context->GetProcessHandle(), nrr_backup.mapped_header, nrr_backup.nrr_heap_address, nrr_backup.nrr_heap_size, nrr_backup.mapped_code_address)); + } + + Result MapManualLoadModuleMemory(u64 *out_address, size_t context_id, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size) { + /* Get context. */ + ProcessContext *context = GetContextById(context_id); + AMS_ABORT_UNLESS(context != nullptr); + + /* Validate address/size. */ + R_TRY(ValidateAddressAndNonZeroSize(nro_address, nro_size)); + R_TRY(ValidateAddressAndSize(bss_address, bss_size)); + + const u64 total_size = nro_size + bss_size; + R_UNLESS(total_size >= nro_size, ro::ResultInvalidSize()); + R_UNLESS(total_size >= bss_size, ro::ResultInvalidSize()); + + /* Check we have space for a new NRO. */ + NroInfo *nro_info = nullptr; + R_TRY(context->GetFreeNroInfo(std::addressof(nro_info))); + nro_info->nro_heap_address = nro_address; + nro_info->nro_heap_size = nro_size; + nro_info->bss_heap_address = bss_address; + nro_info->bss_heap_size = bss_size; + + /* Map the NRO. */ + R_TRY(MapNro(std::addressof(nro_info->base_address), context->GetProcessHandle(), nro_address, nro_size, bss_address, bss_size)); + ON_RESULT_FAILURE { UnmapNro(context->GetProcessHandle(), nro_info->base_address, nro_address, nro_size, bss_address, bss_size); }; + + /* Validate the NRO (parsing region extents). */ + u64 rx_size = 0, ro_size = 0, rw_size = 0; + bool aligned_header = false; + R_TRY(context->ValidateNro(std::addressof(nro_info->module_id), std::addressof(rx_size), std::addressof(ro_size), std::addressof(rw_size), std::addressof(aligned_header), nro_info->base_address, nro_size, bss_size)); + + /* Set NRO perms. */ + R_TRY(SetNroPerms(context->GetProcessHandle(), nro_info->base_address, rx_size, ro_size, rw_size + bss_size, aligned_header)); + + context->SetNroInfoInUse(nro_info, true); + nro_info->code_size = rx_size + ro_size; + nro_info->rw_size = rw_size; + *out_address = nro_info->base_address; + R_SUCCEED(); + } + + Result UnmapManualLoadModuleMemory(size_t context_id, u64 nro_address) { + /* Get context. */ + ProcessContext *context = GetContextById(context_id); + AMS_ABORT_UNLESS(context != nullptr); + + /* Validate address. */ + R_UNLESS(util::IsAligned(nro_address, os::MemoryPageSize), ro::ResultInvalidAddress()); + + /* Check the NRO is loaded. */ + NroInfo *nro_info = nullptr; + R_TRY(context->GetNroInfoByAddress(std::addressof(nro_info), nro_address)); + + /* Unmap. */ + const NroInfo nro_backup = *nro_info; + { + /* Nintendo does this unconditionally, whether or not the actual unmap succeeds. */ + context->SetNroInfoInUse(nro_info, false); + std::memset(nro_info, 0, sizeof(*nro_info)); + } + R_RETURN(UnmapNro(context->GetProcessHandle(), nro_backup.base_address, nro_backup.nro_heap_address, nro_backup.code_size + nro_backup.rw_size, nro_backup.bss_heap_address, nro_backup.bss_heap_size)); + } + + /* Debug service implementations. */ + Result GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out_infos, size_t max_out_count, os::ProcessId process_id) { + if (const ProcessContext *context = GetContextByProcessId(process_id); context != nullptr) { + context->GetProcessModuleInfo(out_count, out_infos, max_out_count); + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_service_impl.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_service_impl.hpp new file mode 100644 index 00000000..ffc92f02 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/impl/ro_service_impl.hpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> + +namespace ams::ro::impl { + + /* Definitions. */ + constexpr size_t InvalidContextId = static_cast<size_t>(-1); + + /* Access utilities. */ + void SetDevelopmentHardware(bool is_development_hardware); + void SetDevelopmentFunctionEnabled(bool is_development_function_enabled); + bool IsDevelopmentHardware(); + bool IsDevelopmentFunctionEnabled(); + bool ShouldEaseNroRestriction(); + + /* Context utilities. */ + Result RegisterProcess(size_t *out_context_id, sf::NativeHandle &&process_handle, os::ProcessId process_id); + Result ValidateProcess(size_t context_id, os::ProcessId process_id); + void UnregisterProcess(size_t context_id); + + /* Service implementations. */ + Result RegisterModuleInfo(size_t context_id, os::NativeHandle process_h, u64 nrr_address, u64 nrr_size, NrrKind nrr_kind, bool enforce_nrr_kind); + Result UnregisterModuleInfo(size_t context_id, u64 nrr_address); + Result MapManualLoadModuleMemory(u64 *out_address, size_t context_id, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size); + Result UnmapManualLoadModuleMemory(size_t context_id, u64 nro_address); + + /* Debug service implementations. */ + Result GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out_infos, size_t max_out_count, os::ProcessId process_id); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/ro_debug_monitor_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/ro_debug_monitor_service.cpp new file mode 100644 index 00000000..af4a25da --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/ro_debug_monitor_service.cpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ro_debug_monitor_service.hpp" +#include "impl/ro_service_impl.hpp" + +namespace ams::ro { + + Result DebugMonitorService::GetProcessModuleInfo(sf::Out<u32> out_count, const sf::OutArray<ldr::ModuleInfo> &out_infos, os::ProcessId process_id) { + R_UNLESS(out_infos.GetSize() <= std::numeric_limits<s32>::max(), ro::ResultInvalidSize()); + R_RETURN(impl::GetProcessModuleInfo(out_count.GetPointer(), out_infos.GetPointer(), out_infos.GetSize(), process_id)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/ro_debug_monitor_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/ro_debug_monitor_service.hpp new file mode 100644 index 00000000..94df39e1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/ro_debug_monitor_service.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::ro { + + class DebugMonitorService { + public: + Result GetProcessModuleInfo(sf::Out<u32> out_count, const sf::OutArray<ldr::ModuleInfo> &out_infos, os::ProcessId process_id); + }; + static_assert(ro::impl::IsIDebugMonitorInterface<DebugMonitorService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/ro_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/ro_main.cpp new file mode 100644 index 00000000..25b38a6f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/ro_main.cpp @@ -0,0 +1,157 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ro_debug_monitor_service.hpp" +#include "ro_ro_service.hpp" + +namespace ams { + + namespace ro { + + namespace { + + /* ldr:ro, ro:dmnt, ro:1. */ + enum PortIndex { + PortIndex_DebugMonitor, + PortIndex_User, + PortIndex_JitPlugin, + PortIndex_Count, + }; + + constexpr sm::ServiceName DebugMonitorServiceName = sm::ServiceName::Encode("ro:dmnt"); + constexpr size_t DebugMonitorMaxSessions = 2; + + /* NOTE: Official code passes 32 for ldr:ro max sessions. We will pass 2, because that's the actual limit. */ + constexpr sm::ServiceName UserServiceName = sm::ServiceName::Encode("ldr:ro"); + constexpr size_t UserMaxSessions = 2; + + constexpr sm::ServiceName JitPluginServiceName = sm::ServiceName::Encode("ro:1"); + constexpr size_t JitPluginMaxSessions = 2; + + static constexpr size_t MaxSessions = DebugMonitorMaxSessions + UserMaxSessions + JitPluginMaxSessions; + + using ServerOptions = sf::hipc::DefaultServerManagerOptions; + + class ServerManager final : public sf::hipc::ServerManager<PortIndex_Count, ServerOptions, MaxSessions> { + private: + virtual ams::Result OnNeedsToAccept(int port_index, Server *server) override; + }; + + using Allocator = sf::ExpHeapAllocator; + using ObjectFactory = sf::ObjectFactory<sf::ExpHeapAllocator::Policy>; + + alignas(0x40) constinit u8 g_server_allocator_buffer[4_KB]; + lmem::HeapHandle g_server_heap_handle; + Allocator g_server_allocator; + + ServerManager g_server_manager; + + ams::Result ServerManager::OnNeedsToAccept(int port_index, Server *server) { + switch (port_index) { + case PortIndex_DebugMonitor: + R_RETURN(this->AcceptImpl(server, ObjectFactory::CreateSharedEmplaced<ro::impl::IDebugMonitorInterface, ro::DebugMonitorService>(std::addressof(g_server_allocator)))); + case PortIndex_User: + R_RETURN(this->AcceptImpl(server, ObjectFactory::CreateSharedEmplaced<ro::impl::IRoInterface, ro::RoService>(std::addressof(g_server_allocator), ro::NrrKind_User))); + case PortIndex_JitPlugin: + R_RETURN(this->AcceptImpl(server, ObjectFactory::CreateSharedEmplaced<ro::impl::IRoInterface, ro::RoService>(std::addressof(g_server_allocator), ro::NrrKind_JitPlugin))); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void *Allocate(size_t size) { + return lmem::AllocateFromExpHeap(g_server_heap_handle, size); + } + + void Deallocate(void *p, size_t size) { + AMS_UNUSED(size); + + return lmem::FreeToExpHeap(g_server_heap_handle, p); + } + + void InitializeHeap() { + /* Setup server allocator. */ + g_server_heap_handle = lmem::CreateExpHeap(g_server_allocator_buffer, sizeof(g_server_allocator_buffer), lmem::CreateOption_None); + } + + void LoopServer() { + /* Create services. */ + R_ABORT_UNLESS(ro::g_server_manager.RegisterServer(PortIndex_DebugMonitor, DebugMonitorServiceName, DebugMonitorMaxSessions)); + + R_ABORT_UNLESS(ro::g_server_manager.RegisterServer(PortIndex_User, UserServiceName, UserMaxSessions)); + if (hos::GetVersion() >= hos::Version_7_0_0) { + R_ABORT_UNLESS(ro::g_server_manager.RegisterServer(PortIndex_JitPlugin, JitPluginServiceName, JitPluginMaxSessions)); + } + + /* Loop forever, servicing our services. */ + ro::g_server_manager.LoopProcess(); + } + + } + + } + + namespace init { + + void InitializeSystemModule() { + /* Initialize heap. */ + ro::InitializeHeap(); + + /* Initialize our connection to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + + /* Initialize fs. */ + fs::InitializeForSystem(); + fs::SetAllocator(ro::Allocate, ro::Deallocate); + fs::SetEnabledAutoAbort(false); + + /* Initialize other services we need. */ + R_ABORT_UNLESS(setsysInitialize()); + + /* Mount the SD card. */ + R_ABORT_UNLESS(fs::MountSdCard("sdmc")); + + /* Verify that we can sanely execute. */ + ams::CheckApiVersion(); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void Main() { + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(ro, Main)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(ro, Main)); + + /* Attach the server allocator. */ + ro::g_server_allocator.Attach(ro::g_server_heap_handle); + + /* Initialize Debug config. */ + { + spl::Initialize(); + ON_SCOPE_EXIT { spl::Finalize(); }; + + ro::SetDevelopmentHardware(spl::IsDevelopment()); + ro::SetDevelopmentFunctionEnabled(spl::IsDevelopmentFunctionEnabled()); + } + + /* Run the ro server. */ + ro::LoopServer(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/ro_ro_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/ro_ro_service.cpp new file mode 100644 index 00000000..ec309fb6 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/ro_ro_service.cpp @@ -0,0 +1,71 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "ro_ro_service.hpp" +#include "impl/ro_service_impl.hpp" + +namespace ams::ro { + + void SetDevelopmentHardware(bool is_development_hardware) { + impl::SetDevelopmentHardware(is_development_hardware); + } + + void SetDevelopmentFunctionEnabled(bool is_development_function_enabled) { + impl::SetDevelopmentFunctionEnabled(is_development_function_enabled); + } + + RoService::RoService(NrrKind k) : m_context_id(impl::InvalidContextId), m_nrr_kind(k) { + /* ... */ + } + + RoService::~RoService() { + impl::UnregisterProcess(m_context_id); + } + + Result RoService::MapManualLoadModuleMemory(sf::Out<u64> load_address, const sf::ClientProcessId &client_pid, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size) { + R_TRY(impl::ValidateProcess(m_context_id, client_pid.GetValue())); + R_RETURN(impl::MapManualLoadModuleMemory(load_address.GetPointer(), m_context_id, nro_address, nro_size, bss_address, bss_size)); + } + + Result RoService::UnmapManualLoadModuleMemory(const sf::ClientProcessId &client_pid, u64 nro_address) { + R_TRY(impl::ValidateProcess(m_context_id, client_pid.GetValue())); + R_RETURN(impl::UnmapManualLoadModuleMemory(m_context_id, nro_address)); + } + + Result RoService::RegisterModuleInfo(const sf::ClientProcessId &client_pid, u64 nrr_address, u64 nrr_size) { + R_TRY(impl::ValidateProcess(m_context_id, client_pid.GetValue())); + R_RETURN(impl::RegisterModuleInfo(m_context_id, os::InvalidNativeHandle, nrr_address, nrr_size, NrrKind_User, true)); + } + + Result RoService::UnregisterModuleInfo(const sf::ClientProcessId &client_pid, u64 nrr_address) { + R_TRY(impl::ValidateProcess(m_context_id, client_pid.GetValue())); + R_RETURN(impl::UnregisterModuleInfo(m_context_id, nrr_address)); + } + + Result RoService::RegisterProcessHandle(const sf::ClientProcessId &client_pid, sf::CopyHandle &&process_h) { + /* Register the process. */ + R_RETURN(impl::RegisterProcess(std::addressof(m_context_id), std::move(process_h), client_pid.GetValue())); + } + + Result RoService::RegisterProcessModuleInfo(const sf::ClientProcessId &client_pid, u64 nrr_address, u64 nrr_size, sf::CopyHandle &&process_h) { + /* Validate the process. */ + R_TRY(impl::ValidateProcess(m_context_id, client_pid.GetValue())); + + /* Register the module. */ + R_RETURN(impl::RegisterModuleInfo(m_context_id, process_h.GetOsHandle(), nrr_address, nrr_size, m_nrr_kind, m_nrr_kind == NrrKind_JitPlugin)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/ro_ro_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/ro_ro_service.hpp new file mode 100644 index 00000000..a97a9fe1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/source/ro_ro_service.hpp @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> + +namespace ams::ro { + + /* Access utilities. */ + void SetDevelopmentHardware(bool is_development_hardware); + void SetDevelopmentFunctionEnabled(bool is_development_function_enabled); + + class RoService { + private: + size_t m_context_id; + NrrKind m_nrr_kind; + protected: + explicit RoService(NrrKind k); + public: + virtual ~RoService(); + public: + /* Actual commands. */ + Result MapManualLoadModuleMemory(sf::Out<u64> out_load_address, const sf::ClientProcessId &client_pid, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size); + Result UnmapManualLoadModuleMemory(const sf::ClientProcessId &client_pid, u64 nro_address); + Result RegisterModuleInfo(const sf::ClientProcessId &client_pid, u64 nrr_address, u64 nrr_size); + Result UnregisterModuleInfo(const sf::ClientProcessId &client_pid, u64 nrr_address); + Result RegisterProcessHandle(const sf::ClientProcessId &client_pid, sf::CopyHandle &&process_h); + Result RegisterProcessModuleInfo(const sf::ClientProcessId &client_pid, u64 nrr_address, u64 nrr_size, sf::CopyHandle &&process_h); + }; + static_assert(ro::impl::IsIRoInterface<RoService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/ro/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/system_module.mk new file mode 100644 index 00000000..d6de42ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/ro/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := nsp + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/sm/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/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)/system_module.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)/system_module.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/stratosphere/sm/sm.json b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/sm.json new file mode 100644 index 00000000..d07b646f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/sm.json @@ -0,0 +1,76 @@ +{ + "name": "sm", + "title_id": "0x0100000000000004", + "main_thread_stack_size": "0x2000", + "main_thread_priority": 27, + "default_cpu_id": 3, + "process_category": 1, + "use_secure_memory": true, + "immortal": true, + "kernel_capabilities": [ + { + "type": "handle_table_size", + "value": 512 + }, + { + "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", + "svcSynchronizePreemptionState" : "0x36", + "svcCreateSession" : "0x40", + "svcAcceptSession" : "0x41", + "svcReplyAndReceiveLight" : "0x42", + "svcReplyAndReceive" : "0x43", + "svcReplyAndReceiveWithUserBuffer" : "0x44", + "svcCreateEvent" : "0x45", + "svcGetMemoryInfo" : "0x6F", + "svcCreatePort" : "0x70", + "svcManageNamedPort" : "0x71", + "svcConnectToPort" : "0x72", + "svcCallSecureMonitor" : "0x7F" + } + } + ] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/impl/sm_service_manager.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/impl/sm_service_manager.cpp new file mode 100644 index 00000000..a645d534 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/impl/sm_service_manager.cpp @@ -0,0 +1,957 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sm_service_manager.hpp" +#include "../sm_wait_list.hpp" + +namespace ams::hos { + + void InitializeVersionInternal(bool allow_approximate); + +} + +namespace ams::sm::impl { + + namespace { + + /* Constexpr definitions. */ + static constexpr size_t ProcessCountMax = 0x50; + static constexpr size_t ServiceCountMax = 0x180; + static constexpr size_t MitmCountMax = 0x20; + static constexpr size_t AccessControlSizeMax = 0x200; + + constexpr const sm::ServiceName InitiallyDeferredServices[] = { + ServiceName::Encode("fsp-srv") + }; + + /* Types. */ + struct ProcessInfo { + os::ProcessId process_id; + ncm::ProgramId program_id; + cfg::OverrideStatus override_status; + size_t access_control_size; + u8 access_control[AccessControlSizeMax]; + }; + + constexpr const ProcessInfo InvalidProcessInfo = { + .process_id = os::InvalidProcessId, + .program_id = ncm::InvalidProgramId, + .override_status = {}, + .access_control_size = 0, + .access_control = {}, + }; + + struct ServiceInfo { + ServiceName name; + os::ProcessId owner_process_id; + os::NativeHandle port_h; + bool is_light; + u8 max_sessions; + u8 mitm_index; + }; + + struct MitmInfo { + os::ProcessId process_id; + os::ProcessId waiting_ack_process_id; + os::NativeHandle port_h; + os::NativeHandle query_h; + os::NativeHandle fwd_sess_h; + bool waiting_ack; + }; + + constexpr const ServiceInfo InvalidServiceInfo = { + .name = sm::InvalidServiceName, + .owner_process_id = os::InvalidProcessId, + .port_h = os::InvalidNativeHandle, + .is_light = false, + .max_sessions = 0, + .mitm_index = MitmCountMax, + }; + + constexpr const MitmInfo InvalidMitmInfo = { + .process_id = os::InvalidProcessId, + .waiting_ack_process_id = os::InvalidProcessId, + .port_h = os::InvalidNativeHandle, + .query_h = os::InvalidNativeHandle, + .fwd_sess_h = os::InvalidNativeHandle, + .waiting_ack = false, + }; + + class AccessControlEntry { + private: + const u8 *m_entry; + size_t m_capacity; + public: + AccessControlEntry(const void *e, size_t c) : m_entry(static_cast<const u8 *>(e)), m_capacity(c) { + /* ... */ + } + + AccessControlEntry GetNextEntry() const { + return AccessControlEntry(m_entry + this->GetSize(), m_capacity - this->GetSize()); + } + + size_t GetSize() const { + return this->GetServiceNameSize() + 1; + } + + size_t GetServiceNameSize() const { + return (m_entry[0] & 7) + 1; + } + + ServiceName GetServiceName() const { + return ServiceName::Encode(reinterpret_cast<const char *>(m_entry + 1), this->GetServiceNameSize()); + } + + bool IsHost() const { + return (m_entry[0] & 0x80) != 0; + } + + bool IsWildcard() const { + return m_entry[this->GetServiceNameSize()] == '*'; + } + + bool IsValid() const { + /* Validate that we can access data. */ + if (m_entry == nullptr || m_capacity == 0) { + return false; + } + + /* Validate that the size is correct. */ + return this->GetSize() <= m_capacity; + } + }; + + class InitialProcessIdLimits { + private: + os::ProcessId m_min; + os::ProcessId m_max; + public: + InitialProcessIdLimits() { + /* Retrieve process limits. */ + R_ABORT_UNLESS(svc::GetSystemInfo(std::addressof(m_min.value), svc::SystemInfoType_InitialProcessIdRange, svc::InvalidHandle, svc::InitialProcessIdRangeInfo_Minimum)); + R_ABORT_UNLESS(svc::GetSystemInfo(std::addressof(m_max.value), svc::SystemInfoType_InitialProcessIdRange, svc::InvalidHandle, svc::InitialProcessIdRangeInfo_Maximum)); + + /* Ensure range is sane. */ + AMS_ABORT_UNLESS(m_min <= m_max); + } + + bool IsInitialProcess(os::ProcessId process_id) const { + AMS_ABORT_UNLESS(process_id != os::InvalidProcessId); + return m_min <= process_id && process_id <= m_max; + } + }; + + /* Static members. */ + + /* NOTE: In 12.0.0, Nintendo added multithreaded processing to sm; however, official sm does not do */ + /* any kind of mutual exclusivity when accessing (and modifying) global state. Previously, this was */ + /* not a problem, because sm was strictly single-threaded, and so two threads could not race eachother. */ + /* We will add a mutex (and perform locking) in order to prevent simultaneous access to global state. */ + constinit os::SdkRecursiveMutex g_mutex; + + constinit std::array<ProcessInfo, ProcessCountMax> g_process_list = [] { + std::array<ProcessInfo, ProcessCountMax> list = {}; + + /* Initialize each info. */ + for (auto &process_info : list) { + process_info = InvalidProcessInfo; + } + + return list; + }(); + + constinit std::array<ServiceInfo, ServiceCountMax> g_service_list = [] { + std::array<ServiceInfo, ServiceCountMax> list = {}; + + /* Initialize each info. */ + for (auto &service_info : list) { + service_info = InvalidServiceInfo; + } + + return list; + }(); + + constinit std::array<ServiceName, MitmCountMax> g_future_mitm_list = [] { + std::array<ServiceName, MitmCountMax> list = {}; + + /* Initialize each info. */ + for (auto &name : list) { + name = InvalidServiceName; + } + + return list; + }(); + + constinit std::array<MitmInfo, MitmCountMax> g_mitm_list = [] { + std::array<MitmInfo, MitmCountMax> list = {}; + + /* Initialize each info. */ + for (auto &mitm_info : list) { + mitm_info = InvalidMitmInfo; + } + + return list; + }(); + + constinit bool g_ended_initial_defers = false; + + const InitialProcessIdLimits g_initial_process_id_limits; + + /* Helper functionality. */ + bool IsInitialProcess(os::ProcessId process_id) { + return g_initial_process_id_limits.IsInitialProcess(process_id); + } + + constexpr inline bool IsValidProcessId(os::ProcessId process_id) { + return process_id != os::InvalidProcessId; + } + + Result ValidateAccessControl(AccessControlEntry access_control, ServiceName service, bool is_host, bool is_wildcard) { + /* Iterate over all entries in the access control, checking to see if we have a match. */ + while (access_control.IsValid()) { + if (access_control.IsHost() == is_host) { + bool is_valid = true; + + if (access_control.IsWildcard() == is_wildcard) { + /* Check for exact match. */ + is_valid &= access_control.GetServiceName() == service; + } else if (access_control.IsWildcard()) { + /* Also allow fuzzy match for wildcard. */ + ServiceName ac_service = access_control.GetServiceName(); + is_valid &= std::memcmp(std::addressof(ac_service), std::addressof(service), access_control.GetServiceNameSize() - 1) == 0; + } + + R_SUCCEED_IF(is_valid); + } + access_control = access_control.GetNextEntry(); + } + + R_THROW(sm::ResultNotAllowed()); + } + + Result ValidateAccessControl(AccessControlEntry restriction, AccessControlEntry access) { + /* Ensure that every entry in the access control is allowed by the restriction control. */ + while (access.IsValid()) { + R_TRY(ValidateAccessControl(restriction, access.GetServiceName(), access.IsHost(), access.IsWildcard())); + access = access.GetNextEntry(); + } + + R_SUCCEED(); + } + + Result ValidateServiceName(ServiceName service) { + /* Service names must be non-empty. */ + R_UNLESS(service.name[0] != 0, sm::ResultInvalidServiceName()); + + /* Get name length. */ + size_t name_len; + for (name_len = 1; name_len < sizeof(service); name_len++) { + if (service.name[name_len] == 0) { + break; + } + } + + /* Names must be all-zero after they end. */ + while (name_len < sizeof(service)) { + R_UNLESS(service.name[name_len++] == 0, sm::ResultInvalidServiceName()); + } + + R_SUCCEED(); + } + + bool ShouldDeferForInit(ServiceName service) { + /* Once end has been called, we're done. */ + if (g_ended_initial_defers) { + return false; + } + + /* This is a mechanism by which certain services will always be deferred until sm:m receives a special command. */ + /* This can be extended with more services as needed at a later date. */ + for (const auto &service_name : InitiallyDeferredServices) { + if (service == service_name) { + return true; + } + } + + return false; + } + + ProcessInfo *GetProcessInfo(os::ProcessId process_id) { + /* Find a process info with a matching id. */ + for (auto &process_info : g_process_list) { + if (process_info.process_id == process_id) { + return std::addressof(process_info); + } + } + + return nullptr; + } + + ProcessInfo *GetFreeProcessInfo() { + return GetProcessInfo(os::InvalidProcessId); + } + + bool HasProcessInfo(os::ProcessId process_id) { + return GetProcessInfo(process_id) != nullptr; + } + + ServiceInfo *GetServiceInfo(ServiceName service_name) { + /* Find a service with a matching name. */ + for (auto &service_info : g_service_list) { + if (service_info.name == service_name) { + return std::addressof(service_info); + } + } + + return nullptr; + } + + ServiceInfo *GetFreeServiceInfo() { + return GetServiceInfo(InvalidServiceName); + } + + bool HasServiceInfo(ServiceName service) { + return GetServiceInfo(service) != nullptr; + } + + MitmInfo *GetMitmInfo(const ServiceInfo *service_info) { + if (service_info->mitm_index < MitmCountMax) { + return std::addressof(g_mitm_list[service_info->mitm_index]); + } else { + return nullptr; + } + } + + MitmInfo *GetFreeMitmInfo() { + /* Find a mitm info without an owner. */ + for (auto &mitm_info : g_mitm_list) { + if (!IsValidProcessId(mitm_info.process_id)) { + return std::addressof(mitm_info); + } + } + + return nullptr; + } + + bool HasMitm(ServiceName service) { + const ServiceInfo *service_info = GetServiceInfo(service); + return service_info != nullptr && GetMitmInfo(service_info) != nullptr; + } + + Result AddFutureMitmDeclaration(ServiceName service) { + for (auto &future_mitm : g_future_mitm_list) { + if (future_mitm == InvalidServiceName) { + future_mitm = service; + R_SUCCEED(); + } + } + + R_THROW(sm::ResultOutOfServices()); + } + + bool HasFutureMitmDeclaration(ServiceName service) { + for (const auto &future_mitm : g_future_mitm_list){ + if (future_mitm == service) { + return true; + } + } + + return false; + } + + void ClearFutureMitmDeclaration(ServiceName service) { + for (auto &future_mitm : g_future_mitm_list) { + if (future_mitm == service) { + future_mitm = InvalidServiceName; + } + } + + /* This might undefer some requests. */ + TriggerResume(service); + } + + void GetMitmProcessInfo(MitmProcessInfo *out_info, os::ProcessId process_id) { + /* Anything that can request a mitm session must have a process info. */ + const auto process_info = GetProcessInfo(process_id); + AMS_ABORT_UNLESS(process_info != nullptr); + + /* Write to output. */ + out_info->process_id = process_id; + out_info->program_id = process_info->program_id; + out_info->override_status = process_info->override_status; + } + + bool IsMitmDisallowed(ncm::ProgramId program_id) { + /* Mitm used on certain programs can prevent the boot process from completing. */ + /* TODO: Is there a way to do this that's less hardcoded? Needs design thought. */ + return program_id == ncm::SystemProgramId::Loader || + program_id == ncm::SystemProgramId::Pm || + program_id == ncm::SystemProgramId::Spl || + program_id == ncm::SystemProgramId::Boot || + program_id == ncm::SystemProgramId::Ncm || + program_id == ncm::AtmosphereProgramId::Mitm || + program_id == ncm::SystemProgramId::Creport; + } + + Result CreatePortImpl(os::NativeHandle *out_server, os::NativeHandle *out_client, size_t max_sessions, bool is_light, sm::ServiceName &name) { + /* Create the port. */ + svc::Handle server_port, client_port; + R_TRY(svc::CreatePort(std::addressof(server_port), std::addressof(client_port), max_sessions, is_light, reinterpret_cast<uintptr_t>(name.name))); + + /* Set the output handles. */ + *out_server = server_port; + *out_client = client_port; + R_SUCCEED(); + } + + Result ConnectToPortImpl(os::NativeHandle *out, os::NativeHandle port) { + /* Connect to the port. */ + svc::Handle session; + R_TRY(svc::ConnectToPort(std::addressof(session), port)); + + /* Set the output handle. */ + *out = session; + R_SUCCEED(); + } + + Result GetMitmServiceHandleImpl(os::NativeHandle *out, ServiceInfo *service_info, const MitmProcessInfo &client_info) { + /* Get the mitm info. */ + MitmInfo *mitm_info = GetMitmInfo(service_info); + AMS_ABORT_UNLESS(mitm_info != nullptr); + + /* Send command to query if we should mitm. */ + bool should_mitm; + { + /* TODO: Convert mitm internal messaging to use tipc? */ + ::Service srv { .session = mitm_info->query_h }; + R_ABORT_UNLESS((serviceDispatchInOut(std::addressof(srv), 65000, client_info, should_mitm))); + } + + /* If we shouldn't mitm, give normal session. */ + R_UNLESS(should_mitm, ConnectToPortImpl(out, service_info->port_h)); + + /* Create both handles. */ + { + /* Get the forward handle. */ + R_TRY(ConnectToPortImpl(std::addressof(mitm_info->fwd_sess_h), service_info->port_h)); + + /* Get the mitm handle. */ + /* This should be guaranteed to succeed, since we got a forward handle. */ + R_ABORT_UNLESS(ConnectToPortImpl(out, mitm_info->port_h)); + } + + mitm_info->waiting_ack_process_id = client_info.process_id; + mitm_info->waiting_ack = true; + + R_SUCCEED(); + } + + Result GetServiceHandleImpl(os::NativeHandle *out, ServiceInfo *service_info, os::ProcessId process_id) { + /* Get the mitm info. */ + MitmInfo *mitm_info = GetMitmInfo(service_info); + + /* Check if we should return a mitm handle. */ + if (mitm_info != nullptr && mitm_info->process_id != process_id) { + /* Get mitm process info, ensure that we're allowed to mitm the given program. */ + MitmProcessInfo client_info; + GetMitmProcessInfo(std::addressof(client_info), process_id); + if (!IsMitmDisallowed(client_info.program_id)) { + /* Get a mitm service handle. */ + R_RETURN(GetMitmServiceHandleImpl(out, service_info, client_info)); + } + } + + /* We're not returning a mitm handle, so just return a normal port handle. */ + R_RETURN(ConnectToPortImpl(out, service_info->port_h)); + } + + Result RegisterServiceImpl(os::NativeHandle *out, os::ProcessId process_id, ServiceName service, size_t max_sessions, bool is_light) { + /* Validate service name. */ + R_TRY(ValidateServiceName(service)); + + /* Don't try to register something already registered. */ + R_UNLESS(!HasServiceInfo(service), sm::ResultAlreadyRegistered()); + + /* Get free service. */ + ServiceInfo *free_service = GetFreeServiceInfo(); + R_UNLESS(free_service != nullptr, sm::ResultOutOfServices()); + + /* Create the new service. */ + R_TRY(CreatePortImpl(out, std::addressof(free_service->port_h), max_sessions, is_light, free_service->name)); + + /* Save info. */ + free_service->name = service; + free_service->owner_process_id = process_id; + free_service->max_sessions = max_sessions; + free_service->is_light = is_light; + + /* This might undefer some requests. */ + TriggerResume(service); + + R_SUCCEED(); + } + + void UnregisterServiceImpl(ServiceInfo *service_info) { + /* Get the mitm info. */ + MitmInfo *mitm_info = GetMitmInfo(service_info); + + /* Close all valid handles. */ + os::CloseNativeHandle(service_info->port_h); + + /* Reset the info's state. */ + *service_info = InvalidServiceInfo; + + /* Reset the mitm info, if necessary. */ + if (mitm_info != nullptr) { + os::CloseNativeHandle(mitm_info->port_h); + os::CloseNativeHandle(mitm_info->query_h); + os::CloseNativeHandle(mitm_info->fwd_sess_h); + + *mitm_info = InvalidMitmInfo; + } + } + + } + + /* Client disconnection callback. */ + void OnClientDisconnected(os::ProcessId process_id) { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_mutex); + + /* Ensure that the process id is valid. */ + if (process_id == os::InvalidProcessId) { + return; + } + + /* Unregister all services a client hosts, on attached-client-close. */ + for (auto &service_info : g_service_list) { + if (service_info.name != InvalidServiceName && service_info.owner_process_id == process_id) { + UnregisterServiceImpl(std::addressof(service_info)); + } + } + } + + /* Process management. */ + Result RegisterProcess(os::ProcessId process_id, ncm::ProgramId program_id, cfg::OverrideStatus override_status, const void *acid_sac, size_t acid_sac_size, const void *aci_sac, size_t aci_sac_size) { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_mutex); + + /* Check that access control will fit in the ServiceInfo. */ + R_UNLESS(aci_sac_size <= AccessControlSizeMax, sm::ResultTooLargeAccessControl()); + + /* Get free process. */ + ProcessInfo *proc = GetFreeProcessInfo(); + R_UNLESS(proc != nullptr, sm::ResultOutOfProcesses()); + + /* Validate restrictions. */ + R_UNLESS(aci_sac_size != 0, sm::ResultNotAllowed()); + R_TRY(ValidateAccessControl(AccessControlEntry(acid_sac, acid_sac_size), AccessControlEntry(aci_sac, aci_sac_size))); + + /* Save info. */ + proc->process_id = process_id; + proc->program_id = program_id; + proc->override_status = override_status; + proc->access_control_size = aci_sac_size; + std::memcpy(proc->access_control, aci_sac, proc->access_control_size); + + R_SUCCEED(); + } + + Result UnregisterProcess(os::ProcessId process_id) { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_mutex); + + /* Find the process. */ + ProcessInfo *proc = GetProcessInfo(process_id); + R_UNLESS(proc != nullptr, sm::ResultInvalidClient()); + + /* Free the process. */ + *proc = InvalidProcessInfo; + + R_SUCCEED(); + } + + /* Service management. */ + Result HasService(bool *out, ServiceName service) { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_mutex); + + /* Validate service name. */ + R_TRY(ValidateServiceName(service)); + + *out = HasServiceInfo(service); + R_SUCCEED(); + } + + Result WaitService(ServiceName service) { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_mutex); + + /* Check that we have the service. */ + bool has_service = false; + R_TRY(impl::HasService(std::addressof(has_service), service)); + + /* If we don't, we want to wait until the service is registered. */ + R_UNLESS(has_service, tipc::ResultRequestDeferred()); + + R_SUCCEED(); + } + + Result GetServiceHandle(os::NativeHandle *out, os::ProcessId process_id, ServiceName service) { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_mutex); + + /* Validate service name. */ + R_TRY(ValidateServiceName(service)); + + /* Check that the process is registered and allowed to get the service. */ + if (!IsInitialProcess(process_id)) { + ProcessInfo *proc = GetProcessInfo(process_id); + R_UNLESS(proc != nullptr, sm::ResultInvalidClient()); + R_TRY(ValidateAccessControl(AccessControlEntry(proc->access_control, proc->access_control_size), service, false, false)); + } + + /* Get service info/mitm info. */ + ServiceInfo *service_info = GetServiceInfo(service); + MitmInfo *mitm_info = service_info != nullptr ? GetMitmInfo(service_info) : nullptr; + + /* Check to see if we need to defer until later. */ + R_UNLESS(service_info != nullptr, tipc::ResultRequestDeferred()); + R_UNLESS(!ShouldDeferForInit(service), tipc::ResultRequestDeferred()); + R_UNLESS(!HasFutureMitmDeclaration(service), tipc::ResultRequestDeferred()); + R_UNLESS((mitm_info == nullptr || !mitm_info->waiting_ack), tipc::ResultRequestDeferred()); + + /* Get a handle from the service info. */ + R_TRY_CATCH(GetServiceHandleImpl(out, service_info, process_id)) { + R_CONVERT(svc::ResultOutOfSessions, sm::ResultOutOfSessions()) + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + Result RegisterService(os::NativeHandle *out, os::ProcessId process_id, ServiceName service, size_t max_sessions, bool is_light) { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_mutex); + + /* Validate service name. */ + R_TRY(ValidateServiceName(service)); + + /* Check that the process is registered and allowed to register the service. */ + if (!IsInitialProcess(process_id)) { + ProcessInfo *proc = GetProcessInfo(process_id); + R_UNLESS(proc != nullptr, sm::ResultInvalidClient()); + + R_TRY(ValidateAccessControl(AccessControlEntry(proc->access_control, proc->access_control_size), service, true, false)); + } + + /* Check that the service isn't already registered. */ + R_UNLESS(!HasServiceInfo(service), sm::ResultAlreadyRegistered()); + + R_RETURN(RegisterServiceImpl(out, process_id, service, max_sessions, is_light)); + } + + Result RegisterServiceForSelf(os::NativeHandle *out, ServiceName service, size_t max_sessions) { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_mutex); + + R_RETURN(RegisterServiceImpl(out, os::GetCurrentProcessId(), service, max_sessions, false)); + } + + Result UnregisterService(os::ProcessId process_id, ServiceName service) { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_mutex); + + /* Validate service name. */ + R_TRY(ValidateServiceName(service)); + + /* Check that the process is registered. */ + if (!IsInitialProcess(process_id)) { + R_UNLESS(HasProcessInfo(process_id), sm::ResultInvalidClient()); + } + + /* Ensure that the service is actually registered. */ + ServiceInfo *service_info = GetServiceInfo(service); + R_UNLESS(service_info != nullptr, sm::ResultNotRegistered()); + + /* Check if we have permission to do this. */ + R_UNLESS(service_info->owner_process_id == process_id, sm::ResultNotAllowed()); + + /* Unregister the service. */ + UnregisterServiceImpl(service_info); + + R_SUCCEED(); + } + + /* Mitm extensions. */ + Result HasMitm(bool *out, ServiceName service) { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_mutex); + + /* Validate service name. */ + R_TRY(ValidateServiceName(service)); + + /* Get whether we have a mitm. */ + *out = HasMitm(service); + + R_SUCCEED(); + } + + Result WaitMitm(ServiceName service) { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_mutex); + + /* Check that we have the mitm. */ + bool has_mitm = false; + R_TRY(impl::HasMitm(std::addressof(has_mitm), service)); + + /* If we don't, we want to wait until the mitm is installed. */ + R_UNLESS(has_mitm, tipc::ResultRequestDeferred()); + + R_SUCCEED(); + } + + Result InstallMitm(os::NativeHandle *out, os::NativeHandle *out_query, os::ProcessId process_id, ServiceName service) { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_mutex); + + /* Validate service name. */ + R_TRY(ValidateServiceName(service)); + + /* Check that the process is registered and allowed to register the service. */ + if (!IsInitialProcess(process_id)) { + ProcessInfo *proc = GetProcessInfo(process_id); + R_UNLESS(proc != nullptr, sm::ResultInvalidClient()); + R_TRY(ValidateAccessControl(AccessControlEntry(proc->access_control, proc->access_control_size), service, true, false)); + } + + /* Validate that the service exists. */ + ServiceInfo *service_info = GetServiceInfo(service); + + /* If it doesn't exist, defer until it does. */ + R_UNLESS(service_info != nullptr, tipc::ResultRequestDeferred()); + + /* Validate that the service isn't already being mitm'd. */ + R_UNLESS(GetMitmInfo(service_info) == nullptr, sm::ResultAlreadyRegistered()); + + /* Validate that we can create a new mitm. */ + MitmInfo *mitm_info = GetFreeMitmInfo(); + R_UNLESS(mitm_info != nullptr, sm::ResultOutOfServices()); + + /* If we don't have a future mitm declaration, add one. */ + /* Client will clear this when ready to process. */ + const bool has_existing_future_declaration = HasFutureMitmDeclaration(service); + if (!has_existing_future_declaration) { + R_TRY(AddFutureMitmDeclaration(service)); + } + + /* If we fail past this point, clean up the future mitm declaration we just added. */ + ON_RESULT_FAILURE { if (!has_existing_future_declaration) { ClearFutureMitmDeclaration(service); } }; + + /* Create mitm handles. */ + { + /* Get the port handles. */ + os::NativeHandle hnd, port_hnd; + R_TRY(CreatePortImpl(std::addressof(hnd), std::addressof(port_hnd), service_info->max_sessions, service_info->is_light, service_info->name)); + + /* Ensure that we clean up the port handles, if something goes wrong creating the query sessions. */ + ON_RESULT_FAILURE { os::CloseNativeHandle(hnd); os::CloseNativeHandle(port_hnd); }; + + /* Create the session for our query service. */ + os::NativeHandle qry_hnd, mitm_qry_hnd; + R_TRY(svc::CreateSession(std::addressof(qry_hnd), std::addressof(mitm_qry_hnd), false, 0)); + + /* Setup the mitm info. */ + mitm_info->process_id = process_id; + mitm_info->port_h = port_hnd; + mitm_info->query_h = mitm_qry_hnd; + + /* Setup the service info. */ + service_info->mitm_index = mitm_info - g_mitm_list.data(); + + /* Copy to output. */ + *out = hnd; + *out_query = qry_hnd; + + /* This might undefer some requests. */ + TriggerResume(service); + } + + R_SUCCEED(); + } + + Result UninstallMitm(os::ProcessId process_id, ServiceName service) { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_mutex); + + /* Validate service name. */ + R_TRY(ValidateServiceName(service)); + + /* Check that the process is registered. */ + if (!IsInitialProcess(process_id)) { + ProcessInfo *proc = GetProcessInfo(process_id); + R_UNLESS(proc != nullptr, sm::ResultInvalidClient()); + } + + /* Validate that the service exists. */ + ServiceInfo *service_info = GetServiceInfo(service); + R_UNLESS(service_info != nullptr, sm::ResultNotRegistered()); + + /* Validate that the service is mitm'd. */ + MitmInfo *mitm_info = GetMitmInfo(service_info); + R_UNLESS(mitm_info != nullptr, sm::ResultNotRegistered()); + + /* Validate that the client process_id is the mitm process. */ + R_UNLESS(mitm_info->process_id == process_id, sm::ResultNotAllowed()); + + /* Uninstall the mitm. */ + { + /* Close mitm handles. */ + os::CloseNativeHandle(mitm_info->port_h); + os::CloseNativeHandle(mitm_info->query_h); + os::CloseNativeHandle(mitm_info->fwd_sess_h); + + /* Reset mitm members. */ + *mitm_info = InvalidMitmInfo; + + /* Reset service info. */ + service_info->mitm_index = MitmCountMax; + } + + R_SUCCEED(); + } + + Result DeclareFutureMitm(os::ProcessId process_id, ServiceName service) { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_mutex); + + /* Validate service name. */ + R_TRY(ValidateServiceName(service)); + + /* Check that the process is registered and allowed to register the service. */ + if (!IsInitialProcess(process_id)) { + ProcessInfo *proc = GetProcessInfo(process_id); + R_UNLESS(proc != nullptr, sm::ResultInvalidClient()); + R_TRY(ValidateAccessControl(AccessControlEntry(proc->access_control, proc->access_control_size), service, true, false)); + } + + /* Check that mitm hasn't already been registered or declared. */ + R_UNLESS(!HasMitm(service), sm::ResultAlreadyRegistered()); + R_UNLESS(!HasFutureMitmDeclaration(service), sm::ResultAlreadyRegistered()); + + /* Try to forward declare it. */ + R_TRY(AddFutureMitmDeclaration(service)); + + R_SUCCEED(); + } + + Result ClearFutureMitm(os::ProcessId process_id, ServiceName service) { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_mutex); + + /* Validate service name. */ + R_TRY(ValidateServiceName(service)); + + /* Check that the process is registered and allowed to register the service. */ + if (!IsInitialProcess(process_id)) { + ProcessInfo *proc = GetProcessInfo(process_id); + R_UNLESS(proc != nullptr, sm::ResultInvalidClient()); + R_TRY(ValidateAccessControl(AccessControlEntry(proc->access_control, proc->access_control_size), service, true, false)); + } + + /* Validate that the service exists. */ + ServiceInfo *service_info = GetServiceInfo(service); + R_UNLESS(service_info != nullptr, sm::ResultNotRegistered()); + + /* Check that we have a mitm or a future declaration. */ + if (MitmInfo *mitm_info = GetMitmInfo(service_info); mitm_info != nullptr) { + /* Validate that the client process_id is the mitm process. */ + R_UNLESS(mitm_info->process_id == process_id, sm::ResultNotAllowed()); + } else { + R_UNLESS(HasFutureMitmDeclaration(service), sm::ResultNotRegistered()); + } + + /* Clear the forward declaration. */ + ClearFutureMitmDeclaration(service); + + R_SUCCEED(); + } + + Result AcknowledgeMitmSession(MitmProcessInfo *out_info, os::NativeHandle *out_hnd, os::ProcessId process_id, ServiceName service) { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_mutex); + + /* Validate service name. */ + R_TRY(ValidateServiceName(service)); + + /* Check that the process is registered. */ + if (!IsInitialProcess(process_id)) { + ProcessInfo *proc = GetProcessInfo(process_id); + R_UNLESS(proc != nullptr, sm::ResultInvalidClient()); + } + + /* Validate that the service exists. */ + ServiceInfo *service_info = GetServiceInfo(service); + R_UNLESS(service_info != nullptr, sm::ResultNotRegistered()); + + /* Get the mitm info. */ + MitmInfo *mitm_info = GetMitmInfo(service_info); + R_UNLESS(mitm_info != nullptr, sm::ResultNotRegistered()); + + /* Validate that the client process_id is the mitm process, and that an acknowledgement is waiting. */ + R_UNLESS(mitm_info->process_id == process_id, sm::ResultNotAllowed()); + R_UNLESS(mitm_info->waiting_ack, sm::ResultNotAllowed()); + + /* Acknowledge. */ + { + /* Copy the mitm info to output. */ + GetMitmProcessInfo(out_info, mitm_info->waiting_ack_process_id); + + /* Set the output handle. */ + *out_hnd = mitm_info->fwd_sess_h; + mitm_info->fwd_sess_h = os::InvalidNativeHandle; + + /* Clear acknowledgement-related fields. */ + mitm_info->waiting_ack = false; + mitm_info->waiting_ack_process_id = os::InvalidProcessId; + } + + /* Undefer requests to the session. */ + TriggerResume(service); + + R_SUCCEED(); + } + + /* Deferral extension (works around FS bug). */ + Result EndInitialDefers() { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_mutex); + + /* Note that we have ended the initial deferral period. */ + const bool had_ended_defers = g_ended_initial_defers; + g_ended_initial_defers = true; + + /* Something about deferral state has changed, so we should refresh our hos version. */ + hos::InitializeVersionInternal(!had_ended_defers); + + /* This might undefer some requests. */ + for (const auto &service_name : InitiallyDeferredServices) { + TriggerResume(service_name); + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/impl/sm_service_manager.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/impl/sm_service_manager.hpp new file mode 100644 index 00000000..4a4ae9ee --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/impl/sm_service_manager.hpp @@ -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 <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <stratosphere.hpp> + +namespace ams::sm::impl { + + /* Client disconnection callback. */ + void OnClientDisconnected(os::ProcessId process_id); + + /* Process management. */ + Result RegisterProcess(os::ProcessId process_id, ncm::ProgramId program_id, cfg::OverrideStatus override_status, const void *acid_sac, size_t acid_sac_size, const void *aci_sac, size_t aci_sac_size); + Result UnregisterProcess(os::ProcessId process_id); + + /* Service management. */ + Result HasService(bool *out, ServiceName service); + Result WaitService(ServiceName service); + Result GetServiceHandle(os::NativeHandle *out, os::ProcessId process_id, ServiceName service); + Result RegisterService(os::NativeHandle *out, os::ProcessId process_id, ServiceName service, size_t max_sessions, bool is_light); + Result RegisterServiceForSelf(os::NativeHandle *out, ServiceName service, size_t max_sessions); + Result UnregisterService(os::ProcessId process_id, ServiceName service); + + /* Mitm extensions. */ + Result HasMitm(bool *out, ServiceName service); + Result WaitMitm(ServiceName service); + Result InstallMitm(os::NativeHandle *out, os::NativeHandle *out_query, os::ProcessId process_id, ServiceName service); + Result UninstallMitm(os::ProcessId process_id, ServiceName service); + Result DeclareFutureMitm(os::ProcessId process_id, ServiceName service); + Result ClearFutureMitm(os::ProcessId process_id, ServiceName service); + Result AcknowledgeMitmSession(MitmProcessInfo *out_info, os::NativeHandle *out_hnd, os::ProcessId process_id, ServiceName service); + + /* Deferral extension (works around FS bug). */ + Result EndInitialDefers(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_main.cpp new file mode 100644 index 00000000..1c026e46 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sm_tipc_server.hpp" + +namespace ams { + + namespace init { + + void InitializeSystemModule() { /* ... */ } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void NORETURN Exit(int rc) { + AMS_UNUSED(rc); + AMS_ABORT("Exit called by immortal process"); + } + + void Main() { + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(sm, Main)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(sm, Main)); + + /* Initialize the server. */ + sm::InitializeTipcServer(); + + /* Loop forever, processing our services. */ + sm::LoopProcessTipcServer(); + + /* This can never be reached. */ + AMS_ASSUME(false); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_manager_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_manager_service.hpp new file mode 100644 index 00000000..a31ecb44 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_manager_service.hpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "impl/sm_service_manager.hpp" + +namespace ams::sm { + + /* Service definition. */ + class ManagerService { + public: + Result RegisterProcess(os::ProcessId process_id, const tipc::InBuffer acid_sac, const tipc::InBuffer aci_sac) { + R_RETURN(impl::RegisterProcess(process_id, ncm::InvalidProgramId, cfg::OverrideStatus{}, acid_sac.GetPointer(), acid_sac.GetSize(), aci_sac.GetPointer(), aci_sac.GetSize())); + } + + Result UnregisterProcess(os::ProcessId process_id) { + R_RETURN(impl::UnregisterProcess(process_id)); + } + + void AtmosphereEndInitDefers() { + R_ABORT_UNLESS(impl::EndInitialDefers()); + } + + void AtmosphereHasMitm(tipc::Out<bool> out, ServiceName service) { + R_ABORT_UNLESS(impl::HasMitm(out.GetPointer(), service)); + } + + Result AtmosphereRegisterProcess(os::ProcessId process_id, ncm::ProgramId program_id, cfg::OverrideStatus override_status, const tipc::InBuffer acid_sac, const tipc::InBuffer aci_sac) { + /* This takes in a program id and override status, unlike RegisterProcess. */ + R_RETURN(impl::RegisterProcess(process_id, program_id, override_status, acid_sac.GetPointer(), acid_sac.GetSize(), aci_sac.GetPointer(), aci_sac.GetSize())); + } + }; + static_assert(sm::impl::IsIManagerInterface<ManagerService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_tipc_server.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_tipc_server.cpp new file mode 100644 index 00000000..e207308c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_tipc_server.cpp @@ -0,0 +1,83 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sm_tipc_server.hpp" +#include "sm_user_service.hpp" +#include "sm_manager_service.hpp" +#include "sm_wait_list.hpp" +#include "impl/sm_service_manager.hpp" + +namespace ams::sm { + + namespace { + + /* Server limit definitions. */ + enum PortIndex : size_t { + PortIndex_Manager, + PortIndex_User, + PortIndex_Count, + }; + + constexpr inline size_t NumTipcPorts = static_cast<size_t>(PortIndex_Count); + constexpr inline size_t MaxSessionsManager = 1; + constexpr inline size_t MaxSessionsUser = 0x50 + 8 - MaxSessionsManager; + + constexpr inline size_t MaxSessionsTotal = MaxSessionsManager + MaxSessionsUser; + + + static_assert(MaxSessionsTotal % NumTipcPorts == 0); + + /* Define port metadata. */ + using UserPortMeta = tipc::PortMeta<MaxSessionsUser, impl::IUserInterface, UserService, tipc::SlabAllocator>; + using ManagerPortMeta = tipc::PortMeta<MaxSessionsManager, impl::IManagerInterface, ManagerService, tipc::SingletonAllocator>; + + /* Define server manager global. */ + using ServerManager = tipc::ServerManager<ManagerPortMeta, UserPortMeta>; + + ServerManager g_server_manager; + + } + + void InitializeTipcServer() { + /* Initialize the server manager. */ + g_server_manager.Initialize(); + + /* Create the handles for our ports. */ + os::NativeHandle user_port_handle, manager_port_handle; + { + /* Create the user port handle. */ + R_ABORT_UNLESS(svc::ManageNamedPort(std::addressof(user_port_handle), "sm:", MaxSessionsUser)); + + /* Create the manager port handle. */ + R_ABORT_UNLESS(impl::RegisterServiceForSelf(std::addressof(manager_port_handle), sm::ServiceName::Encode("sm:m"), MaxSessionsManager)); + } + + /* Register the ports. */ + g_server_manager.RegisterPort<PortIndex_Manager>(manager_port_handle); + g_server_manager.RegisterPort<PortIndex_User >(user_port_handle); + } + + void LoopProcessTipcServer() { + /* Loop processing the server on all threads. */ + g_server_manager.LoopAuto(AMS_GET_SYSTEM_THREAD_PRIORITY(sm, DispatcherThread), AMS_GET_SYSTEM_THREAD_NAME(sm, DispatcherThread)); + } + + void TriggerResume(sm::ServiceName service_name) { + /* Trigger a resumption. */ + g_server_manager.TriggerResume(service_name); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_tipc_server.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_tipc_server.hpp new file mode 100644 index 00000000..ae9f1df0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_tipc_server.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::sm { + + void InitializeTipcServer(); + void LoopProcessTipcServer(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_user_service.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_user_service.cpp new file mode 100644 index 00000000..d719970c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_user_service.cpp @@ -0,0 +1,198 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "sm_user_service.hpp" +#include "impl/sm_service_manager.hpp" + +namespace ams::sm { + + namespace { + + constexpr const u8 CmifResponseToQueryPointerBufferSize[] = { + 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x53, 0x46, 0x43, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + + static_assert(CmifCommandType_Request == 4); + + constexpr const u8 CmifExpectedRequestHeaderForRegisterClientAndDetachClient[] = { + 0x04, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, + }; + + constexpr const u8 CmifResponseToRegisterClientAndDetachClient[] = { + 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x53, 0x46, 0x43, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + constexpr const u8 CmifExpectedRequestHeaderForGetServiceHandleAndUnregisterService[] = { + 0x04, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00 + }; + + constexpr const u8 CmifResponseToGetServiceHandleAndRegisterService[] = { + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x80, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x53, 0x46, 0x43, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + constexpr const u8 CmifResponseToUnregisterService[] = { + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x53, 0x46, 0x43, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + constexpr const u8 CmifExpectedRequestHeaderForRegisterService[] = { + 0x04, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00 + }; + + static_assert(tipc::ResultRequestDeferred().GetValue() == 0xC823); + constexpr const u8 CmifResponseToForceProcessorDeferral[] = { + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0xC8, 0x00, 0x00, + }; + + } + + Result UserService::ProcessDefaultServiceCommand(const svc::ipc::MessageBuffer &message_buffer) { + /* Parse the hipc headers. */ + const svc::ipc::MessageBuffer::MessageHeader message_header(message_buffer); + const svc::ipc::MessageBuffer::SpecialHeader special_header(message_buffer, message_header); + + /* Get the request tag. */ + const auto tag = message_header.GetTag(); + + /* Handle the case where we received a control request. */ + if (tag == CmifCommandType_Control || tag == CmifCommandType_ControlWithContext) { + /* The only control/control with context command which we support is QueryPointerBufferSize. */ + /* We do not have a pointer buffer, and so our pointer buffer size is zero. */ + /* Return the relevant hardcoded response. */ + std::memcpy(message_buffer.GetBufferForDebug(), CmifResponseToQueryPointerBufferSize, sizeof(CmifResponseToQueryPointerBufferSize)); + R_SUCCEED(); + } + + /* We only support request (with context), from this point forwards. */ + R_UNLESS((tag == CmifCommandType_Request || tag == CmifCommandType_RequestWithContext), tipc::ResultInvalidMethod()); + + /* For ease of parsing, we will alter the tag to be Request when it is RequestWithContext. */ + if (tag == CmifCommandType_RequestWithContext) { + message_buffer.Set(svc::ipc::MessageBuffer::MessageHeader(CmifCommandType_Request, + message_header.GetHasSpecialHeader(), + message_header.GetPointerCount(), + message_header.GetSendCount(), + message_header.GetReceiveCount(), + message_header.GetExchangeCount(), + message_header.GetRawCount(), + message_header.GetReceiveListCount())); + } + + /* NOTE: Nintendo only supports RegisterClient and GetServiceHandle. However, it would break */ + /* a substantial amount of homebrew system modules, if we were to not provide shims for some */ + /* other commands (RegisterService, and UnregisterService, in particular). As such, we will */ + /* do the nice thing, and accommodate this by providing backwards compatibility shims for */ + /* those commands. */ + + /* Please note, though, that the atmosphere extensions are not shimmed, as performing all the required */ + /* parsing for that seems unreasonable. Also, if you are using atmosphere extensions, you are probably */ + /* used to my breaking shit regularly anyway, and I don't expect much of a fuss to be raised by you. */ + /* I invite you to demonstrate to me that this expectation does not reflect reality. */ + const u8 * const raw_message_buffer = static_cast<const u8 *>(message_buffer.GetBufferForDebug()); + u8 * const out_message_buffer = static_cast<u8 *>(message_buffer.GetBufferForDebug()); + + if (std::memcmp(raw_message_buffer, CmifExpectedRequestHeaderForRegisterClientAndDetachClient, sizeof(CmifExpectedRequestHeaderForRegisterClientAndDetachClient)) == 0) { + /* Get the command id. */ + const u32 command_id = *reinterpret_cast<const u32 *>(raw_message_buffer + 0x28); + + /* Get the client process id. */ + u64 client_process_id; + std::memcpy(std::addressof(client_process_id), raw_message_buffer + 0xC, sizeof(client_process_id)); + + if (command_id == 0) { + /* Invoke the handler for RegisterClient. */ + R_ABORT_UNLESS(this->RegisterClient(tipc::ClientProcessId{ client_process_id })); + } else if (command_id == 4) { + /* Invoke the handler for DetachClient. */ + R_ABORT_UNLESS(this->DetachClient(tipc::ClientProcessId{ client_process_id })); + } else { + R_THROW(tipc::ResultInvalidMethod()); + } + + /* Serialize output. */ + std::memcpy(out_message_buffer, CmifResponseToRegisterClientAndDetachClient, sizeof(CmifResponseToRegisterClientAndDetachClient)); + } else if (std::memcmp(raw_message_buffer, CmifExpectedRequestHeaderForGetServiceHandleAndUnregisterService, sizeof(CmifExpectedRequestHeaderForGetServiceHandleAndUnregisterService)) == 0) { + /* Get the command id. */ + const u32 command_id = *reinterpret_cast<const u32 *>(raw_message_buffer + 0x18); + + /* Get the service_name. */ + sm::ServiceName service_name; + std::memcpy(std::addressof(service_name), raw_message_buffer + 0x20, sizeof(service_name)); + + if (command_id == 1) { + /* Invoke the handler for GetServiceHandle. */ + svc::Handle out_handle = svc::InvalidHandle; + const Result result = this->GetServiceHandle(std::addressof(out_handle), service_name); + + /* Serialize output. */ + if (!tipc::ResultRequestDeferred::Includes(result)) { + std::memcpy(out_message_buffer + 0x00, CmifResponseToGetServiceHandleAndRegisterService, sizeof(CmifResponseToGetServiceHandleAndRegisterService)); + std::memcpy(out_message_buffer + 0x0C, std::addressof(out_handle), sizeof(out_handle)); + std::memcpy(out_message_buffer + 0x18, std::addressof(result), sizeof(result)); + } else { + std::memcpy(out_message_buffer, CmifResponseToForceProcessorDeferral, sizeof(CmifResponseToForceProcessorDeferral)); + } + } else if (command_id == 3) { + /* Invoke the handler for UnregisterService. */ + const Result result = this->UnregisterService(service_name); + + /* Serialize output. */ + std::memcpy(out_message_buffer + 0x00, CmifResponseToUnregisterService, sizeof(CmifResponseToUnregisterService)); + std::memcpy(out_message_buffer + 0x18, std::addressof(result), sizeof(result)); + } else { + R_THROW(tipc::ResultInvalidMethod()); + } + } else if (std::memcmp(raw_message_buffer, CmifExpectedRequestHeaderForRegisterService, sizeof(CmifExpectedRequestHeaderForRegisterService)) == 0) { + /* Get the command id. */ + const u32 command_id = *reinterpret_cast<const u32 *>(raw_message_buffer + 0x18); + + /* Get the service_name. */ + sm::ServiceName service_name; + std::memcpy(std::addressof(service_name), raw_message_buffer + 0x20, sizeof(service_name)); + + /* Get "is light". */ + bool is_light; + is_light = (raw_message_buffer[0x28] & 0x01) != 0; + + /* Get the max sessions. */ + u32 max_sessions; + std::memcpy(std::addressof(max_sessions), raw_message_buffer + 0x2C, sizeof(max_sessions)); + + if (command_id == 2) { + /* Invoke the handler for RegisterService. */ + svc::Handle out_handle = svc::InvalidHandle; + const Result result = this->RegisterService(std::addressof(out_handle), service_name, max_sessions, is_light); + + /* Serialize output. */ + std::memcpy(out_message_buffer + 0x00, CmifResponseToGetServiceHandleAndRegisterService, sizeof(CmifResponseToGetServiceHandleAndRegisterService)); + std::memcpy(out_message_buffer + 0x0C, std::addressof(out_handle), sizeof(out_handle)); + std::memcpy(out_message_buffer + 0x18, std::addressof(result), sizeof(result)); + } else { + R_THROW(tipc::ResultInvalidMethod()); + } + } else { + R_THROW(tipc::ResultInvalidMethod()); + } + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_user_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_user_service.hpp new file mode 100644 index 00000000..43fdde74 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_user_service.hpp @@ -0,0 +1,138 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "impl/sm_service_manager.hpp" + +namespace ams::sm { + + /* Service definition. */ + class UserService : public tipc::DeferrableBase<sm::impl::IUserInterface, /* Maximum deferrable CMIF message size: */ 0x20 + util::AlignUp(sizeof(sm::ServiceName), sizeof(u32))> { + private: + os::ProcessId m_process_id; + bool m_initialized; + public: + UserService() : m_process_id{os::InvalidProcessId}, m_initialized{false} { /* ... */ } + ~UserService() { + if (m_initialized) { + impl::OnClientDisconnected(m_process_id); + } + } + public: + /* Official commands. */ + Result RegisterClient(const tipc::ClientProcessId client_process_id) { + m_process_id = client_process_id.value; + m_initialized = true; + R_SUCCEED(); + } + + Result GetServiceHandle(tipc::OutMoveHandle out_h, ServiceName service) { + R_UNLESS(m_initialized, sm::ResultInvalidClient()); + + TIPC_REGISTER_RETRY_ON_RESULT_REQUEST_DEFERRED(service); + + R_RETURN(impl::GetServiceHandle(out_h.GetPointer(), m_process_id, service)); + } + + Result RegisterService(tipc::OutMoveHandle out_h, ServiceName service, u32 max_sessions, bool is_light) { + R_UNLESS(m_initialized, sm::ResultInvalidClient()); + + R_RETURN(impl::RegisterService(out_h.GetPointer(), m_process_id, service, max_sessions, is_light)); + } + + Result UnregisterService(ServiceName service) { + R_UNLESS(m_initialized, sm::ResultInvalidClient()); + + R_RETURN(impl::UnregisterService(m_process_id, service)); + } + + Result DetachClient(const tipc::ClientProcessId client_process_id) { + AMS_UNUSED(client_process_id); + + m_initialized = false; + + R_SUCCEED(); + } + + /* Atmosphere commands. */ + Result AtmosphereInstallMitm(tipc::OutMoveHandle srv_h, tipc::OutMoveHandle qry_h, ServiceName service) { + R_UNLESS(m_initialized, sm::ResultInvalidClient()); + + TIPC_REGISTER_RETRY_ON_RESULT_REQUEST_DEFERRED(service); + + R_RETURN(impl::InstallMitm(srv_h.GetPointer(), qry_h.GetPointer(), m_process_id, service)); + } + + Result AtmosphereUninstallMitm(ServiceName service) { + R_UNLESS(m_initialized, sm::ResultInvalidClient()); + + R_RETURN(impl::UninstallMitm(m_process_id, service)); + } + + Result AtmosphereAcknowledgeMitmSession(tipc::Out<MitmProcessInfo> client_info, tipc::OutMoveHandle fwd_h, ServiceName service) { + R_UNLESS(m_initialized, sm::ResultInvalidClient()); + + R_RETURN(impl::AcknowledgeMitmSession(client_info.GetPointer(), fwd_h.GetPointer(), m_process_id, service)); + } + + Result AtmosphereHasMitm(tipc::Out<bool> out, ServiceName service) { + R_UNLESS(m_initialized, sm::ResultInvalidClient()); + + R_RETURN(impl::HasMitm(out.GetPointer(), service)); + } + + Result AtmosphereWaitMitm(ServiceName service) { + R_UNLESS(m_initialized, sm::ResultInvalidClient()); + + TIPC_REGISTER_RETRY_ON_RESULT_REQUEST_DEFERRED(service); + + R_RETURN(impl::WaitMitm(service)); + } + + Result AtmosphereDeclareFutureMitm(ServiceName service) { + R_UNLESS(m_initialized, sm::ResultInvalidClient()); + + R_RETURN(impl::DeclareFutureMitm(m_process_id, service)); + } + + Result AtmosphereClearFutureMitm(ServiceName service) { + R_UNLESS(m_initialized, sm::ResultInvalidClient()); + + R_RETURN(impl::ClearFutureMitm(m_process_id, service)); + } + + Result AtmosphereHasService(tipc::Out<bool> out, ServiceName service) { + R_UNLESS(m_initialized, sm::ResultInvalidClient()); + + R_RETURN(impl::HasService(out.GetPointer(), service)); + } + + Result AtmosphereWaitService(ServiceName service) { + R_UNLESS(m_initialized, sm::ResultInvalidClient()); + + TIPC_REGISTER_RETRY_ON_RESULT_REQUEST_DEFERRED(service); + + R_RETURN(impl::WaitService(service)); + } + public: + /* Backwards compatibility layer for cmif. */ + Result ProcessDefaultServiceCommand(const svc::ipc::MessageBuffer &message_buffer); + }; + static_assert(sm::impl::IsIUserInterface<UserService>); + static_assert(tipc::IsDeferrable<UserService>); + /* TODO: static assert that this is a tipc interface with default prototyping. */ + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_wait_list.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_wait_list.hpp new file mode 100644 index 00000000..5d2da578 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/source/sm_wait_list.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::sm { + + void TriggerResume(sm::ServiceName service_name); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/sm/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/system_module.mk new file mode 100644 index 00000000..19c207d9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/sm/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := kip + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/spl/Makefile b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/Makefile new file mode 100644 index 00000000..d681240e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/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)/system_module.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)/system_module.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/stratosphere/spl/source/spl_crypto_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_crypto_service.hpp new file mode 100644 index 00000000..462f9adb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_crypto_service.hpp @@ -0,0 +1,71 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "spl_general_service.hpp" + +namespace ams::spl { + + class CryptoService : public GeneralService { + public: + explicit CryptoService(SecureMonitorManager *manager) : GeneralService(manager) { /* ... */ } + public: + virtual ~CryptoService(){ + /* Free any keyslots this service is using. */ + m_manager.DeallocateAesKeySlots(this); + } + public: + /* Actual commands. */ + Result GenerateAesKek(sf::Out<AccessKey> out_access_key, KeySource key_source, u32 generation, u32 option) { + R_RETURN(m_manager.GenerateAesKek(out_access_key.GetPointer(), key_source, generation, option)); + } + + Result LoadAesKey(s32 keyslot, AccessKey access_key, KeySource key_source) { + R_RETURN(m_manager.LoadAesKey(keyslot, this, access_key, key_source)); + } + + Result GenerateAesKey(sf::Out<AesKey> out_key, AccessKey access_key, KeySource key_source) { + R_RETURN(m_manager.GenerateAesKey(out_key.GetPointer(), access_key, key_source)); + } + + Result DecryptAesKey(sf::Out<AesKey> out_key, KeySource key_source, u32 generation, u32 option) { + R_RETURN(m_manager.DecryptAesKey(out_key.GetPointer(), key_source, generation, option)); + } + + Result ComputeCtr(const sf::OutNonSecureBuffer &out_buf, s32 keyslot, const sf::InNonSecureBuffer &in_buf, IvCtr iv_ctr) { + R_RETURN(m_manager.ComputeCtr(out_buf.GetPointer(), out_buf.GetSize(), keyslot, this, in_buf.GetPointer(), in_buf.GetSize(), iv_ctr)); + } + + Result ComputeCmac(sf::Out<Cmac> out_cmac, s32 keyslot, const sf::InPointerBuffer &in_buf) { + R_RETURN(m_manager.ComputeCmac(out_cmac.GetPointer(), keyslot, this, in_buf.GetPointer(), in_buf.GetSize())); + } + + Result AllocateAesKeySlot(sf::Out<s32> out_keyslot) { + R_RETURN(m_manager.AllocateAesKeySlot(out_keyslot.GetPointer(), this)); + } + + Result DeallocateAesKeySlot(s32 keyslot) { + R_RETURN(m_manager.DeallocateAesKeySlot(keyslot, this)); + } + + Result GetAesKeySlotAvailableEvent(sf::OutCopyHandle out_hnd) { + out_hnd.SetValue(m_manager.GetAesKeySlotAvailableEvent()->GetReadableHandle(), false); + R_SUCCEED(); + } + }; + static_assert(spl::impl::IsICryptoInterface<CryptoService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_deprecated_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_deprecated_service.hpp new file mode 100644 index 00000000..1f638ee3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_deprecated_service.hpp @@ -0,0 +1,145 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "spl_secure_monitor_manager.hpp" + +namespace ams::spl { + + class DeprecatedService { + protected: + SecureMonitorManager &m_manager; + public: + explicit DeprecatedService(SecureMonitorManager *manager) : m_manager(*manager) { /* ... */ } + public: + virtual ~DeprecatedService() { + /* Free any keyslots this service is using. */ + m_manager.DeallocateAesKeySlots(this); + } + public: + /* Actual commands. */ + Result GetConfig(sf::Out<u64> out, u32 which) { + R_RETURN(m_manager.GetConfig(out.GetPointer(), static_cast<spl::ConfigItem>(which))); + } + + Result ModularExponentiate(const sf::OutPointerBuffer &out, const sf::InPointerBuffer &base, const sf::InPointerBuffer &exp, const sf::InPointerBuffer &mod) { + R_RETURN(m_manager.ModularExponentiate(out.GetPointer(), out.GetSize(), base.GetPointer(), base.GetSize(), exp.GetPointer(), exp.GetSize(), mod.GetPointer(), mod.GetSize())); + } + + Result GenerateAesKek(sf::Out<AccessKey> out_access_key, KeySource key_source, u32 generation, u32 option) { + R_RETURN(m_manager.GenerateAesKek(out_access_key.GetPointer(), key_source, generation, option)); + } + + Result LoadAesKey(s32 keyslot, AccessKey access_key, KeySource key_source) { + R_RETURN(m_manager.LoadAesKey(keyslot, this, access_key, key_source)); + } + + Result GenerateAesKey(sf::Out<AesKey> out_key, AccessKey access_key, KeySource key_source) { + R_RETURN(m_manager.GenerateAesKey(out_key.GetPointer(), access_key, key_source)); + } + + Result SetConfig(u32 which, u64 value) { + R_RETURN(m_manager.SetConfig(static_cast<spl::ConfigItem>(which), value)); + } + + Result GenerateRandomBytes(const sf::OutPointerBuffer &out) { + R_RETURN(m_manager.GenerateRandomBytes(out.GetPointer(), out.GetSize())); + } + + Result DecryptAndStoreGcKey(const sf::InPointerBuffer &src, AccessKey access_key, KeySource key_source, u32 option) { + R_RETURN(m_manager.DecryptAndStoreGcKey(src.GetPointer(), src.GetSize(), access_key, key_source, option)); + } + + Result DecryptGcMessage(sf::Out<u32> out_size, const sf::OutPointerBuffer &out, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod, const sf::InPointerBuffer &label_digest) { + R_RETURN(m_manager.DecryptGcMessage(out_size.GetPointer(), out.GetPointer(), out.GetSize(), base.GetPointer(), base.GetSize(), mod.GetPointer(), mod.GetSize(), label_digest.GetPointer(), label_digest.GetSize())); + } + + Result IsDevelopment(sf::Out<bool> is_dev) { + R_RETURN(m_manager.IsDevelopment(is_dev.GetPointer())); + } + + Result GenerateSpecificAesKey(sf::Out<AesKey> out_key, KeySource key_source, u32 generation, u32 which) { + R_RETURN(m_manager.GenerateSpecificAesKey(out_key.GetPointer(), key_source, generation, which)); + } + + Result DecryptDeviceUniqueData(const sf::OutPointerBuffer &dst, const sf::InPointerBuffer &src, AccessKey access_key, KeySource key_source, u32 option) { + R_RETURN(m_manager.DecryptDeviceUniqueData(dst.GetPointer(), dst.GetSize(), src.GetPointer(), src.GetSize(), access_key, key_source, option)); + } + + Result DecryptAesKey(sf::Out<AesKey> out_key, KeySource key_source, u32 generation, u32 option) { + R_RETURN(m_manager.DecryptAesKey(out_key.GetPointer(), key_source, generation, option)); + } + + Result ComputeCtrDeprecated(const sf::OutBuffer &out_buf, s32 keyslot, const sf::InBuffer &in_buf, IvCtr iv_ctr) { + R_RETURN(m_manager.ComputeCtr(out_buf.GetPointer(), out_buf.GetSize(), keyslot, this, in_buf.GetPointer(), in_buf.GetSize(), iv_ctr)); + } + + Result ComputeCtr(const sf::OutNonSecureBuffer &out_buf, s32 keyslot, const sf::InNonSecureBuffer &in_buf, IvCtr iv_ctr) { + R_RETURN(m_manager.ComputeCtr(out_buf.GetPointer(), out_buf.GetSize(), keyslot, this, in_buf.GetPointer(), in_buf.GetSize(), iv_ctr)); + } + + Result ComputeCmac(sf::Out<Cmac> out_cmac, s32 keyslot, const sf::InPointerBuffer &in_buf) { + R_RETURN(m_manager.ComputeCmac(out_cmac.GetPointer(), keyslot, this, in_buf.GetPointer(), in_buf.GetSize())); + } + + Result LoadEsDeviceKey(const sf::InPointerBuffer &src, AccessKey access_key, KeySource key_source, u32 option) { + R_RETURN(m_manager.LoadEsDeviceKey(src.GetPointer(), src.GetSize(), access_key, key_source, option)); + } + + Result PrepareEsTitleKeyDeprecated(sf::Out<AccessKey> out_access_key, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod, const sf::InPointerBuffer &label_digest) { + R_RETURN(m_manager.PrepareEsTitleKey(out_access_key.GetPointer(), base.GetPointer(), base.GetSize(), mod.GetPointer(), mod.GetSize(), label_digest.GetPointer(), label_digest.GetSize(), 0)); + } + + Result PrepareEsTitleKey(sf::Out<AccessKey> out_access_key, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod, const sf::InPointerBuffer &label_digest, u32 generation) { + R_RETURN(m_manager.PrepareEsTitleKey(out_access_key.GetPointer(), base.GetPointer(), base.GetSize(), mod.GetPointer(), mod.GetSize(), label_digest.GetPointer(), label_digest.GetSize(), generation)); + } + + Result LoadPreparedAesKey(s32 keyslot, AccessKey access_key) { + R_RETURN(m_manager.LoadPreparedAesKey(keyslot, this, access_key)); + } + + Result PrepareCommonEsTitleKeyDeprecated(sf::Out<AccessKey> out_access_key, KeySource key_source) { + R_RETURN(m_manager.PrepareCommonEsTitleKey(out_access_key.GetPointer(), key_source, 0)); + } + + Result PrepareCommonEsTitleKey(sf::Out<AccessKey> out_access_key, KeySource key_source, u32 generation) { + R_RETURN(m_manager.PrepareCommonEsTitleKey(out_access_key.GetPointer(), key_source, generation)); + } + + Result AllocateAesKeySlot(sf::Out<s32> out_keyslot) { + R_RETURN(m_manager.AllocateAesKeySlot(out_keyslot.GetPointer(), this)); + } + + Result DeallocateAesKeySlot(s32 keyslot) { + R_RETURN(m_manager.DeallocateAesKeySlot(keyslot, this)); + } + + Result GetAesKeySlotAvailableEvent(sf::OutCopyHandle out_hnd) { + out_hnd.SetValue(m_manager.GetAesKeySlotAvailableEvent()->GetReadableHandle(), false); + R_SUCCEED(); + } + + Result SetBootReason(BootReasonValue boot_reason) { + R_RETURN(m_manager.SetBootReason(boot_reason)); + } + + Result GetBootReason(sf::Out<BootReasonValue> out) { + R_RETURN(m_manager.GetBootReason(out.GetPointer())); + } + }; + static_assert(spl::impl::IsIDeprecatedGeneralInterface<DeprecatedService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_device_unique_data_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_device_unique_data_service.hpp new file mode 100644 index 00000000..53ba6bd5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_device_unique_data_service.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "spl_crypto_service.hpp" + +namespace ams::spl { + + class DeviceUniqueDataService : public CryptoService { + public: + explicit DeviceUniqueDataService(SecureMonitorManager *manager) : CryptoService(manager) { /* ... */ } + public: + /* Actual commands. */ + Result DecryptDeviceUniqueDataDeprecated(const sf::OutPointerBuffer &dst, const sf::InPointerBuffer &src, AccessKey access_key, KeySource key_source, u32 option) { + R_RETURN(m_manager.DecryptDeviceUniqueData(dst.GetPointer(), dst.GetSize(), src.GetPointer(), src.GetSize(), access_key, key_source, option)); + } + + Result DecryptDeviceUniqueData(const sf::OutPointerBuffer &dst, const sf::InPointerBuffer &src, AccessKey access_key, KeySource key_source) { + R_RETURN(m_manager.DecryptDeviceUniqueData(dst.GetPointer(), dst.GetSize(), src.GetPointer(), src.GetSize(), access_key, key_source, static_cast<u32>(smc::DeviceUniqueDataMode::DecryptDeviceUniqueData))); + } + }; + static_assert(spl::impl::IsIDeviceUniqueDataInterface<DeviceUniqueDataService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_es_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_es_service.hpp new file mode 100644 index 00000000..57943ec2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_es_service.hpp @@ -0,0 +1,65 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "spl_device_unique_data_service.hpp" + +namespace ams::spl { + + class EsService : public DeviceUniqueDataService { + public: + explicit EsService(SecureMonitorManager *manager) : DeviceUniqueDataService(manager) { /* ... */ } + public: + /* Actual commands. */ + Result LoadEsDeviceKeyDeprecated(const sf::InPointerBuffer &src, AccessKey access_key, KeySource key_source, u32 option) { + R_RETURN(m_manager.LoadEsDeviceKey(src.GetPointer(), src.GetSize(), access_key, key_source, option)); + } + + Result LoadEsDeviceKey(const sf::InPointerBuffer &src, AccessKey access_key, KeySource key_source) { + R_RETURN(m_manager.LoadEsDeviceKey(src.GetPointer(), src.GetSize(), access_key, key_source, static_cast<u32>(smc::DeviceUniqueDataMode::DecryptAndStoreEsDeviceKey))); + } + + Result PrepareEsTitleKey(sf::Out<AccessKey> out_access_key, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod, const sf::InPointerBuffer &label_digest, u32 generation) { + R_RETURN(m_manager.PrepareEsTitleKey(out_access_key.GetPointer(), base.GetPointer(), base.GetSize(), mod.GetPointer(), mod.GetSize(), label_digest.GetPointer(), label_digest.GetSize(), generation)); + } + + Result PrepareCommonEsTitleKey(sf::Out<AccessKey> out_access_key, KeySource key_source, u32 generation) { + R_RETURN(m_manager.PrepareCommonEsTitleKey(out_access_key.GetPointer(), key_source, generation)); + } + + Result DecryptAndStoreDrmDeviceCertKey(const sf::InPointerBuffer &src, AccessKey access_key, KeySource key_source) { + R_RETURN(m_manager.DecryptAndStoreDrmDeviceCertKey(src.GetPointer(), src.GetSize(), access_key, key_source)); + } + + Result ModularExponentiateWithDrmDeviceCertKey(const sf::OutPointerBuffer &out, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod) { + R_RETURN(m_manager.ModularExponentiateWithDrmDeviceCertKey(out.GetPointer(), out.GetSize(), base.GetPointer(), base.GetSize(), mod.GetPointer(), mod.GetSize())); + } + + Result PrepareEsArchiveKey(sf::Out<AccessKey> out_access_key, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod, const sf::InPointerBuffer &label_digest, u32 generation) { + R_RETURN(m_manager.PrepareEsArchiveKey(out_access_key.GetPointer(), base.GetPointer(), base.GetSize(), mod.GetPointer(), mod.GetSize(), label_digest.GetPointer(), label_digest.GetSize(), generation)); + } + + Result LoadPreparedAesKey(s32 keyslot, AccessKey access_key) { + R_RETURN(m_manager.LoadPreparedAesKey(keyslot, this, access_key)); + } + + Result PrepareEsUnknown2Key(sf::Out<AccessKey> out_access_key, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod, const sf::InPointerBuffer &label_digest, u32 generation) { + R_RETURN(m_manager.PrepareEsUnknown2Key(out_access_key.GetPointer(), base.GetPointer(), base.GetSize(), mod.GetPointer(), mod.GetSize(), label_digest.GetPointer(), label_digest.GetSize(), generation)); + } + }; + static_assert(spl::impl::IsIEsInterface<EsService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_fs_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_fs_service.hpp new file mode 100644 index 00000000..c7f00125 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_fs_service.hpp @@ -0,0 +1,53 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "spl_crypto_service.hpp" + +namespace ams::spl { + + class FsService : public CryptoService { + public: + explicit FsService(SecureMonitorManager *manager) : CryptoService(manager) { /* ... */ } + public: + /* Actual commands. */ + Result DecryptAndStoreGcKeyDeprecated(const sf::InPointerBuffer &src, AccessKey access_key, KeySource key_source, u32 option) { + R_RETURN(m_manager.DecryptAndStoreGcKey(src.GetPointer(), src.GetSize(), access_key, key_source, option)); + } + + Result DecryptAndStoreGcKey(const sf::InPointerBuffer &src, AccessKey access_key, KeySource key_source) { + R_RETURN(m_manager.DecryptAndStoreGcKey(src.GetPointer(), src.GetSize(), access_key, key_source, static_cast<u32>(smc::DeviceUniqueDataMode::DecryptAndStoreGcKey))); + } + + Result DecryptGcMessage(sf::Out<u32> out_size, const sf::OutPointerBuffer &out, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod, const sf::InPointerBuffer &label_digest) { + R_RETURN(m_manager.DecryptGcMessage(out_size.GetPointer(), out.GetPointer(), out.GetSize(), base.GetPointer(), base.GetSize(), mod.GetPointer(), mod.GetSize(), label_digest.GetPointer(), label_digest.GetSize())); + } + + Result GenerateSpecificAesKey(sf::Out<AesKey> out_key, KeySource key_source, u32 generation, u32 which) { + R_RETURN(m_manager.GenerateSpecificAesKey(out_key.GetPointer(), key_source, generation, which)); + } + + Result LoadPreparedAesKey(s32 keyslot, AccessKey access_key) { + R_RETURN(m_manager.LoadPreparedAesKey(keyslot, this, access_key)); + } + + Result GetPackage2Hash(const sf::OutPointerBuffer &dst) { + R_RETURN(m_manager.GetPackage2Hash(dst.GetPointer(), dst.GetSize())); + } + }; + static_assert(spl::impl::IsIFsInterface<FsService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_general_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_general_service.hpp new file mode 100644 index 00000000..184569d1 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_general_service.hpp @@ -0,0 +1,59 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "spl_secure_monitor_manager.hpp" + +namespace ams::spl { + + class GeneralService { + protected: + SecureMonitorManager &m_manager; + public: + explicit GeneralService(SecureMonitorManager *manager) : m_manager(*manager) { /* ... */ } + public: + /* Actual commands. */ + Result GetConfig(sf::Out<u64> out, u32 key) { + R_RETURN(m_manager.GetConfig(out.GetPointer(), static_cast<spl::ConfigItem>(key))); + } + + Result ModularExponentiate(const sf::OutPointerBuffer &out, const sf::InPointerBuffer &base, const sf::InPointerBuffer &exp, const sf::InPointerBuffer &mod) { + R_RETURN(m_manager.ModularExponentiate(out.GetPointer(), out.GetSize(), base.GetPointer(), base.GetSize(), exp.GetPointer(), exp.GetSize(), mod.GetPointer(), mod.GetSize())); + } + + Result SetConfig(u32 key, u64 value) { + R_RETURN(m_manager.SetConfig(static_cast<spl::ConfigItem>(key), value)); + } + + Result GenerateRandomBytes(const sf::OutPointerBuffer &out) { + R_RETURN(m_manager.GenerateRandomBytes(out.GetPointer(), out.GetSize())); + } + + Result IsDevelopment(sf::Out<bool> is_dev) { + R_RETURN(m_manager.IsDevelopment(is_dev.GetPointer())); + } + + Result SetBootReason(BootReasonValue boot_reason) { + R_RETURN(m_manager.SetBootReason(boot_reason)); + } + + Result GetBootReason(sf::Out<BootReasonValue> out) { + R_RETURN(m_manager.GetBootReason(out.GetPointer())); + } + }; + static_assert(spl::impl::IsIGeneralInterface<GeneralService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_main.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_main.cpp new file mode 100644 index 00000000..a6e1134a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_main.cpp @@ -0,0 +1,192 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#include "spl_random_service.hpp" +#include "spl_general_service.hpp" +#include "spl_crypto_service.hpp" +#include "spl_ssl_service.hpp" +#include "spl_es_service.hpp" +#include "spl_fs_service.hpp" +#include "spl_manu_service.hpp" + +#include "spl_deprecated_service.hpp" + +namespace ams { + + namespace spl { + + namespace { + + struct SplServerOptions { + static constexpr size_t PointerBufferSize = 0x800; + static constexpr size_t MaxDomains = 0; + static constexpr size_t MaxDomainObjects = 0; + static constexpr bool CanDeferInvokeRequest = false; + static constexpr bool CanManageMitmServers = false; + }; + + enum PortIndex { + PortIndex_General, + PortIndex_Random, + PortIndex_Crypto, + PortIndex_Fs, + PortIndex_Ssl, + PortIndex_Es, + PortIndex_Manu, + PortIndex_Count, + }; + + constexpr sm::ServiceName RandomServiceName = sm::ServiceName::Encode("csrng"); + constexpr size_t RandomMaxSessions = 10; /* NOTE: Official is 9. */ + + constexpr sm::ServiceName GeneralServiceName = sm::ServiceName::Encode("spl:"); + constexpr size_t DeprecatedMaxSessions = 13; + constexpr size_t GeneralMaxSessions = 9; /* NOTE: Official is 8. */ + + constexpr sm::ServiceName CryptoServiceName = sm::ServiceName::Encode("spl:mig"); + constexpr size_t CryptoMaxSessions = 7; /* NOTE: Official is 6. */ + + constexpr sm::ServiceName SslServiceName = sm::ServiceName::Encode("spl:ssl"); + constexpr size_t SslMaxSessions = 2; /* NOTE: Official is 2. */ + + constexpr sm::ServiceName EsServiceName = sm::ServiceName::Encode("spl:es"); + constexpr size_t EsMaxSessions = 2; /* NOTE: Official is 2. */ + + constexpr sm::ServiceName FsServiceName = sm::ServiceName::Encode("spl:fs"); + constexpr size_t FsMaxSessions = 3; /* NOTE: Official is 1. */ + + constexpr sm::ServiceName ManuServiceName = sm::ServiceName::Encode("spl:manu"); + constexpr size_t ManuMaxSessions = 1; /* NOTE: Official is 1. */ + + /* csrng, spl:, spl:mig, spl:ssl, spl:es, spl:fs, spl:manu. */ + /* TODO: Consider max sessions enforcement? */ + constexpr size_t ModernMaxSessions = GeneralMaxSessions + CryptoMaxSessions + SslMaxSessions + EsMaxSessions + FsMaxSessions + ManuMaxSessions; + constexpr size_t NumSessions = RandomMaxSessions + std::max(DeprecatedMaxSessions, ModernMaxSessions) + 1; + + class ServerManager final : public sf::hipc::ServerManager<PortIndex_Count, SplServerOptions, NumSessions> { + private: + sf::ExpHeapAllocator *m_allocator; + spl::SecureMonitorManager *m_secure_monitor_manager; + spl::GeneralService m_general_service; + sf::UnmanagedServiceObjectByPointer<spl::impl::IGeneralInterface, spl::GeneralService> m_general_service_object; + spl::RandomService m_random_service; + sf::UnmanagedServiceObjectByPointer<spl::impl::IRandomInterface, spl::RandomService> m_random_service_object; + public: + ServerManager(sf::ExpHeapAllocator *allocator, spl::SecureMonitorManager *manager) : m_allocator(allocator), m_secure_monitor_manager(manager), m_general_service(manager), m_general_service_object(std::addressof(m_general_service)), m_random_service(manager), m_random_service_object(std::addressof(m_random_service)) { + /* ... */ + } + private: + virtual ams::Result OnNeedsToAccept(int port_index, Server *server) override; + }; + + using Allocator = sf::ExpHeapAllocator; + using ObjectFactory = sf::ObjectFactory<sf::ExpHeapAllocator::Policy>; + + alignas(0x40) constinit u8 g_server_allocator_buffer[8_KB]; + Allocator g_server_allocator; + constinit SecureMonitorManager g_secure_monitor_manager; + + constinit bool g_use_new_server = false; + + ServerManager g_server_manager(std::addressof(g_server_allocator), std::addressof(g_secure_monitor_manager)); + + ams::Result ServerManager::OnNeedsToAccept(int port_index, Server *server) { + switch (port_index) { + case PortIndex_General: + if (g_use_new_server) { + R_RETURN(this->AcceptImpl(server, m_general_service_object.GetShared())); + } else { + R_RETURN(this->AcceptImpl(server, ObjectFactory::CreateSharedEmplaced<spl::impl::IDeprecatedGeneralInterface, spl::DeprecatedService>(m_allocator, m_secure_monitor_manager))); + } + case PortIndex_Random: + R_RETURN(this->AcceptImpl(server, m_random_service_object.GetShared())); + case PortIndex_Crypto: + R_RETURN(this->AcceptImpl(server, ObjectFactory::CreateSharedEmplaced<spl::impl::ICryptoInterface, spl::CryptoService>(m_allocator, m_secure_monitor_manager))); + case PortIndex_Fs: + R_RETURN(this->AcceptImpl(server, ObjectFactory::CreateSharedEmplaced<spl::impl::IFsInterface, spl::FsService>(m_allocator, m_secure_monitor_manager))); + case PortIndex_Ssl: + R_RETURN(this->AcceptImpl(server, ObjectFactory::CreateSharedEmplaced<spl::impl::ISslInterface, spl::SslService>(m_allocator, m_secure_monitor_manager))); + case PortIndex_Es: + R_RETURN(this->AcceptImpl(server, ObjectFactory::CreateSharedEmplaced<spl::impl::IEsInterface, spl::EsService>(m_allocator, m_secure_monitor_manager))); + case PortIndex_Manu: + R_RETURN(this->AcceptImpl(server, ObjectFactory::CreateSharedEmplaced<spl::impl::IManuInterface, spl::ManuService>(m_allocator, m_secure_monitor_manager))); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void SplMain() { + /* Setup server allocator. */ + g_server_allocator.Attach(lmem::CreateExpHeap(g_server_allocator_buffer, sizeof(g_server_allocator_buffer), lmem::CreateOption_None)); + + /* Initialize secure monitor manager. */ + g_secure_monitor_manager.Initialize(); + + g_use_new_server = hos::GetVersion() >= hos::Version_4_0_0; + + /* Create services. */ + const auto fw_ver = hos::GetVersion(); + R_ABORT_UNLESS(g_server_manager.RegisterServer(PortIndex_General, GeneralServiceName, fw_ver >= hos::Version_4_0_0 ? GeneralMaxSessions : DeprecatedMaxSessions)); + R_ABORT_UNLESS(g_server_manager.RegisterServer(PortIndex_Random, RandomServiceName, RandomMaxSessions)); + if (fw_ver >= hos::Version_4_0_0) { + R_ABORT_UNLESS(g_server_manager.RegisterServer(PortIndex_Crypto, CryptoServiceName, CryptoMaxSessions)); + R_ABORT_UNLESS(g_server_manager.RegisterServer(PortIndex_Fs, FsServiceName, FsMaxSessions)); + R_ABORT_UNLESS(g_server_manager.RegisterServer(PortIndex_Ssl, SslServiceName, SslMaxSessions)); + R_ABORT_UNLESS(g_server_manager.RegisterServer(PortIndex_Es, EsServiceName, EsMaxSessions)); + if (fw_ver >= hos::Version_5_0_0) { + g_server_manager.RegisterServer(PortIndex_Manu, ManuServiceName, ManuMaxSessions); + } + } + + /* Loop forever, servicing our services. */ + g_server_manager.LoopProcess(); + } + + } + + } + + namespace init { + + void InitializeSystemModule() { + /* Initialize our connection to sm. */ + R_ABORT_UNLESS(sm::Initialize()); + } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void NORETURN Exit(int rc) { + AMS_UNUSED(rc); + AMS_ABORT("Exit called by immortal process"); + } + + void Main() { + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(spl, Main)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(spl, Main)); + + /* Invoke SPL main. */ + spl::SplMain(); + + /* This can never be reached. */ + AMS_ASSUME(false); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_manu_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_manu_service.hpp new file mode 100644 index 00000000..bc0459f5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_manu_service.hpp @@ -0,0 +1,33 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "spl_device_unique_data_service.hpp" + +namespace ams::spl { + + class ManuService : public DeviceUniqueDataService { + public: + explicit ManuService(SecureMonitorManager *manager) : DeviceUniqueDataService(manager) { /* ... */ } + public: + /* Actual commands. */ + Result ReencryptDeviceUniqueData(const sf::OutPointerBuffer &out, const sf::InPointerBuffer &src, AccessKey access_key_dec, KeySource source_dec, AccessKey access_key_enc, KeySource source_enc, u32 option) { + R_RETURN(m_manager.ReencryptDeviceUniqueData(out.GetPointer(), out.GetSize(), src.GetPointer(), src.GetSize(), access_key_dec, source_dec, access_key_enc, source_enc, option)); + } + }; + static_assert(spl::impl::IsIManuInterface<ManuService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_random_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_random_service.hpp new file mode 100644 index 00000000..89c32f1f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_random_service.hpp @@ -0,0 +1,35 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "spl_secure_monitor_manager.hpp" + +namespace ams::spl { + + class RandomService final { + protected: + SecureMonitorManager &m_manager; + public: + explicit RandomService(SecureMonitorManager *manager) : m_manager(*manager) { /* ... */ } + public: + /* Actual commands. */ + Result GenerateRandomBytes(const sf::OutBuffer &out) { + R_RETURN(m_manager.GenerateRandomBytes(out.GetPointer(), out.GetSize())); + } + }; + static_assert(spl::impl::IsIRandomInterface<RandomService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_secure_monitor_manager.cpp b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_secure_monitor_manager.cpp new file mode 100644 index 00000000..c31bae8b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_secure_monitor_manager.cpp @@ -0,0 +1,201 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "spl_secure_monitor_manager.hpp" + +namespace ams::spl { + + void SecureMonitorManager::Initialize() { + return impl::Initialize(); + } + + Result SecureMonitorManager::ModularExponentiate(void *out, size_t out_size, const void *base, size_t base_size, const void *exp, size_t exp_size, const void *mod, size_t mod_size) { + R_RETURN(impl::ModularExponentiate(out, out_size, base, base_size, exp, exp_size, mod, mod_size)); + } + + Result SecureMonitorManager::GenerateAesKek(AccessKey *out_access_key, const KeySource &key_source, u32 generation, u32 option) { + R_RETURN(impl::GenerateAesKek(out_access_key, key_source, generation, option)); + } + + Result SecureMonitorManager::LoadAesKey(s32 keyslot, const void *owner, const AccessKey &access_key, const KeySource &key_source) { + R_TRY(this->TestAesKeySlot(nullptr, keyslot, owner)); + R_RETURN(impl::LoadAesKey(keyslot, access_key, key_source)); + } + + Result SecureMonitorManager::GenerateAesKey(AesKey *out_key, const AccessKey &access_key, const KeySource &key_source) { + R_RETURN(impl::GenerateAesKey(out_key, access_key, key_source)); + } + + Result SecureMonitorManager::DecryptDeviceUniqueData(void *dst, size_t dst_size, const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source, u32 option) { + R_RETURN(impl::DecryptDeviceUniqueData(dst, dst_size, src, src_size, access_key, key_source, option)); + } + + Result SecureMonitorManager::ReencryptDeviceUniqueData(void *dst, size_t dst_size, const void *src, size_t src_size, const AccessKey &access_key_dec, const KeySource &source_dec, const AccessKey &access_key_enc, const KeySource &source_enc, u32 option) { + R_RETURN(impl::ReencryptDeviceUniqueData(dst, dst_size, src, src_size, access_key_dec, source_dec, access_key_enc, source_enc, option)); + } + + Result SecureMonitorManager::GetConfig(u64 *out, spl::ConfigItem key) { + R_RETURN(impl::GetConfig(out, key)); + } + + Result SecureMonitorManager::SetConfig(spl::ConfigItem key, u64 value) { + R_RETURN(impl::SetConfig(key, value)); + } + + Result SecureMonitorManager::GetPackage2Hash(void *dst, const size_t size) { + R_RETURN(impl::GetPackage2Hash(dst, size)); + } + + Result SecureMonitorManager::GenerateRandomBytes(void *out, size_t size) { + R_RETURN(impl::GenerateRandomBytes(out, size)); + } + + Result SecureMonitorManager::DecryptAndStoreGcKey(const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source, u32 option) { + R_RETURN(impl::DecryptAndStoreGcKey(src, src_size, access_key, key_source, option)); + } + + Result SecureMonitorManager::DecryptGcMessage(u32 *out_size, void *dst, size_t dst_size, const void *base, size_t base_size, const void *mod, size_t mod_size, const void *label_digest, size_t label_digest_size) { + R_RETURN(impl::DecryptGcMessage(out_size, dst, dst_size, base, base_size, mod, mod_size, label_digest, label_digest_size)); + } + + Result SecureMonitorManager::DecryptAndStoreSslClientCertKey(const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source) { + R_RETURN(impl::DecryptAndStoreSslClientCertKey(src, src_size, access_key, key_source)); + } + + Result SecureMonitorManager::ModularExponentiateWithSslClientCertKey(void *out, size_t out_size, const void *base, size_t base_size, const void *mod, size_t mod_size) { + R_RETURN(impl::ModularExponentiateWithSslClientCertKey(out, out_size, base, base_size, mod, mod_size)); + } + + Result SecureMonitorManager::DecryptAndStoreDrmDeviceCertKey(const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source) { + R_RETURN(impl::DecryptAndStoreDrmDeviceCertKey(src, src_size, access_key, key_source)); + } + + Result SecureMonitorManager::ModularExponentiateWithDrmDeviceCertKey(void *out, size_t out_size, const void *base, size_t base_size, const void *mod, size_t mod_size) { + R_RETURN(impl::ModularExponentiateWithDrmDeviceCertKey(out, out_size, base, base_size, mod, mod_size)); + } + + Result SecureMonitorManager::IsDevelopment(bool *out) { + R_RETURN(impl::IsDevelopment(out)); + } + + Result SecureMonitorManager::GenerateSpecificAesKey(AesKey *out_key, const KeySource &key_source, u32 generation, u32 which) { + R_RETURN(impl::GenerateSpecificAesKey(out_key, key_source, generation, which)); + } + + Result SecureMonitorManager::DecryptAesKey(AesKey *out_key, const KeySource &key_source, u32 generation, u32 option) { + R_RETURN(impl::DecryptAesKey(out_key, key_source, generation, option)); + } + + Result SecureMonitorManager::ComputeCtr(void *dst, size_t dst_size, s32 keyslot, const void *owner, const void *src, size_t src_size, const IvCtr &iv_ctr) { + R_TRY(this->TestAesKeySlot(nullptr, keyslot, owner)); + R_RETURN(impl::ComputeCtr(dst, dst_size, keyslot, src, src_size, iv_ctr)); + } + + Result SecureMonitorManager::ComputeCmac(Cmac *out_cmac, s32 keyslot, const void *owner, const void *data, size_t size) { + R_TRY(this->TestAesKeySlot(nullptr, keyslot, owner)); + R_RETURN(impl::ComputeCmac(out_cmac, keyslot, data, size)); + } + + Result SecureMonitorManager::LoadEsDeviceKey(const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source, u32 option) { + R_RETURN(impl::LoadEsDeviceKey(src, src_size, access_key, key_source, option)); + } + + Result SecureMonitorManager::PrepareEsTitleKey(AccessKey *out_access_key, const void *base, size_t base_size, const void *mod, size_t mod_size, const void *label_digest, size_t label_digest_size, u32 generation) { + R_RETURN(impl::PrepareEsTitleKey(out_access_key, base, base_size, mod, mod_size, label_digest, label_digest_size, generation)); + } + + Result SecureMonitorManager::PrepareEsArchiveKey(AccessKey *out_access_key, const void *base, size_t base_size, const void *mod, size_t mod_size, const void *label_digest, size_t label_digest_size, u32 generation) { + R_RETURN(impl::PrepareEsArchiveKey(out_access_key, base, base_size, mod, mod_size, label_digest, label_digest_size, generation)); + } + + Result SecureMonitorManager::PrepareEsUnknown2Key(AccessKey *out_access_key, const void *base, size_t base_size, const void *mod, size_t mod_size, const void *label_digest, size_t label_digest_size, u32 generation) { + R_RETURN(impl::PrepareEsUnknown2Key(out_access_key, base, base_size, mod, mod_size, label_digest, label_digest_size, generation)); + } + + Result SecureMonitorManager::PrepareCommonEsTitleKey(AccessKey *out_access_key, const KeySource &key_source, u32 generation) { + R_RETURN(impl::PrepareCommonEsTitleKey(out_access_key, key_source, generation)); + } + + Result SecureMonitorManager::LoadPreparedAesKey(s32 keyslot, const void *owner, const AccessKey &access_key) { + R_TRY(this->TestAesKeySlot(nullptr, keyslot, owner)); + R_RETURN(impl::LoadPreparedAesKey(keyslot, access_key)); + } + + Result SecureMonitorManager::AllocateAesKeySlot(s32 *out_keyslot, const void *owner) { + /* Allocate a new virtual keyslot. */ + s32 keyslot; + R_TRY(impl::AllocateAesKeySlot(std::addressof(keyslot))); + + /* Get the keyslot's index. */ + s32 index; + bool virt; + R_ABORT_UNLESS(impl::TestAesKeySlot(std::addressof(index), std::addressof(virt), keyslot)); + + /* All allocated keyslots must be virtual. */ + AMS_ABORT_UNLESS(virt); + + m_aes_keyslot_owners[index] = owner; + *out_keyslot = keyslot; + R_SUCCEED(); + } + + Result SecureMonitorManager::DeallocateAesKeySlot(s32 keyslot, const void *owner) { + s32 index; + R_TRY(this->TestAesKeySlot(std::addressof(index), keyslot, owner)); + + m_aes_keyslot_owners[index] = nullptr; + R_RETURN(impl::DeallocateAesKeySlot(keyslot)); + } + + void SecureMonitorManager::DeallocateAesKeySlots(const void *owner) { + for (auto i = 0; i < impl::AesKeySlotCount; ++i) { + if (m_aes_keyslot_owners[i] == owner) { + m_aes_keyslot_owners[i] = nullptr; + impl::DeallocateAesKeySlot(impl::AesKeySlotMin + i); + } + } + } + + Result SecureMonitorManager::SetBootReason(BootReasonValue boot_reason) { + R_RETURN(impl::SetBootReason(boot_reason)); + } + + Result SecureMonitorManager::GetBootReason(BootReasonValue *out) { + R_RETURN(impl::GetBootReason(out)); + } + + os::SystemEvent *SecureMonitorManager::GetAesKeySlotAvailableEvent() { + return impl::GetAesKeySlotAvailableEvent(); + } + + Result SecureMonitorManager::TestAesKeySlot(s32 *out_index, s32 keyslot, const void *owner) { + /* Validate the keyslot (and get the index). */ + s32 index; + bool virt; + R_TRY(impl::TestAesKeySlot(std::addressof(index), std::addressof(virt), keyslot)); + + /* Check that the keyslot is physical (for legacy compat) or owned by the request maker. */ + R_UNLESS(!virt || m_aes_keyslot_owners[index] == owner, spl::ResultInvalidKeySlot()); + + /* Set output index. */ + if (out_index != nullptr) { + *out_index = index; + } + R_SUCCEED(); + } + + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_secure_monitor_manager.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_secure_monitor_manager.hpp new file mode 100644 index 00000000..c01311ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_secure_monitor_manager.hpp @@ -0,0 +1,67 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +namespace ams::spl { + + class SecureMonitorManager { + private: + const void *m_aes_keyslot_owners[impl::AesKeySlotCount]{}; + public: + constexpr SecureMonitorManager() = default; + public: + void Initialize(); + private: + Result TestAesKeySlot(s32 *out_index, s32 keyslot, const void *owner); + public: + Result ModularExponentiate(void *out, size_t out_size, const void *base, size_t base_size, const void *exp, size_t exp_size, const void *mod, size_t mod_size); + Result GenerateAesKek(AccessKey *out_access_key, const KeySource &key_source, u32 generation, u32 option); + Result LoadAesKey(s32 keyslot, const void *owner, const AccessKey &access_key, const KeySource &key_source); + Result GenerateAesKey(AesKey *out_key, const AccessKey &access_key, const KeySource &key_source); + Result DecryptDeviceUniqueData(void *dst, size_t dst_size, const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source, u32 option); + Result ReencryptDeviceUniqueData(void *dst, size_t dst_size, const void *src, size_t src_size, const AccessKey &access_key_dec, const KeySource &source_dec, const AccessKey &access_key_enc, const KeySource &source_enc, u32 option); + Result GetConfig(u64 *out, spl::ConfigItem key); + Result SetConfig(spl::ConfigItem key, u64 value); + Result GetPackage2Hash(void *dst, const size_t size); + Result GenerateRandomBytes(void *out, size_t size); + Result DecryptAndStoreGcKey(const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source, u32 option); + Result DecryptGcMessage(u32 *out_size, void *dst, size_t dst_size, const void *base, size_t base_size, const void *mod, size_t mod_size, const void *label_digest, size_t label_digest_size); + Result DecryptAndStoreSslClientCertKey(const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source); + Result ModularExponentiateWithSslClientCertKey(void *out, size_t out_size, const void *base, size_t base_size, const void *mod, size_t mod_size); + Result DecryptAndStoreDrmDeviceCertKey(const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source); + Result ModularExponentiateWithDrmDeviceCertKey(void *out, size_t out_size, const void *base, size_t base_size, const void *mod, size_t mod_size); + Result IsDevelopment(bool *out); + Result GenerateSpecificAesKey(AesKey *out_key, const KeySource &key_source, u32 generation, u32 which); + Result DecryptAesKey(AesKey *out_key, const KeySource &key_source, u32 generation, u32 option); + Result ComputeCtr(void *dst, size_t dst_size, s32 keyslot, const void *owner, const void *src, size_t src_size, const IvCtr &iv_ctr); + Result ComputeCmac(Cmac *out_cmac, s32 keyslot, const void *owner, const void *data, size_t size); + Result LoadEsDeviceKey(const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source, u32 option); + Result PrepareEsTitleKey(AccessKey *out_access_key, const void *base, size_t base_size, const void *mod, size_t mod_size, const void *label_digest, size_t label_digest_size, u32 generation); + Result PrepareEsArchiveKey(AccessKey *out_access_key, const void *base, size_t base_size, const void *mod, size_t mod_size, const void *label_digest, size_t label_digest_size, u32 generation); + Result PrepareEsUnknown2Key(AccessKey *out_access_key, const void *base, size_t base_size, const void *mod, size_t mod_size, const void *label_digest, size_t label_digest_size, u32 generation); + Result PrepareCommonEsTitleKey(AccessKey *out_access_key, const KeySource &key_source, u32 generation); + Result LoadPreparedAesKey(s32 keyslot, const void *owner, const AccessKey &access_key); + Result AllocateAesKeySlot(s32 *out_keyslot, const void *owner); + Result DeallocateAesKeySlot(s32 keyslot, const void *owner); + void DeallocateAesKeySlots(const void *owner); + Result SetBootReason(BootReasonValue boot_reason); + Result GetBootReason(BootReasonValue *out); + + os::SystemEvent *GetAesKeySlotAvailableEvent(); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_ssl_service.hpp b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_ssl_service.hpp new file mode 100644 index 00000000..9e979e46 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/source/spl_ssl_service.hpp @@ -0,0 +1,37 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> +#include "spl_device_unique_data_service.hpp" + +namespace ams::spl { + + class SslService : public DeviceUniqueDataService { + public: + explicit SslService(SecureMonitorManager *manager) : DeviceUniqueDataService(manager) { /* ... */ } + public: + /* Actual commands. */ + Result DecryptAndStoreSslClientCertKey(const sf::InPointerBuffer &src, AccessKey access_key, KeySource key_source) { + R_RETURN(m_manager.DecryptAndStoreSslClientCertKey(src.GetPointer(), src.GetSize(), access_key, key_source)); + } + + Result ModularExponentiateWithSslClientCertKey(const sf::OutPointerBuffer &out, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod) { + R_RETURN(m_manager.ModularExponentiateWithSslClientCertKey(out.GetPointer(), out.GetSize(), base.GetPointer(), base.GetSize(), mod.GetPointer(), mod.GetSize())); + } + }; + static_assert(spl::impl::IsISslInterface<SslService>); + +} diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/spl/spl.json b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/spl.json new file mode 100644 index 00000000..e5601a3c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/spl.json @@ -0,0 +1,77 @@ +{ + "name": "spl", + "title_id": "0x0100000000000028", + "main_thread_stack_size": "0x00002000", + "main_thread_priority": 27, + "default_cpu_id": 3, + "process_category": 1, + "use_secure_memory": true, + "immortal": true, + "kernel_capabilities": [{ + "type": "handle_table_size", + "value": 128 + }, { + "type": "irq_pair", + "value": [44, null] + }, { + "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", + "svcSynchronizePreemptionState": "0x36", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcCreateInterruptEvent": "0x53", + "svcCreateDeviceAddressSpace": "0x56", + "svcAttachDeviceAddressSpace": "0x57", + "svcMapDeviceAddressSpaceAligned": "0x5a", + "svcUnmapDeviceAddressSpace": "0x5c", + "svcCallSecureMonitor": "0x7f" + } + }] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/spl/system_module.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/system_module.mk new file mode 100644 index 00000000..19c207d9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/spl/system_module.mk @@ -0,0 +1,131 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ATMOSPHERE_SYSTEM_MODULE_TARGETS := kip + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target)) + +$(OUTPUT).kip : $(OUTPUT).elf +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/stratosphere/stratosphere.mk b/Source/Atmosphere-MTC-Unlock/stratosphere/stratosphere.mk new file mode 100644 index 00000000..b20244ec --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/stratosphere/stratosphere.mk @@ -0,0 +1,29 @@ +#--------------------------------------------------------------------------------- +# 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_MODULES := loader boot ncm pm sm ams_mitm spl eclct.stub ro creport fatal dmnt boot2 erpt pgl jpegdec LogManager cs htc TioServer dmnt.gen2 memlet + +all: $(ALL_MODULES) + +$(ALL_MODULES): check_lib + @$(SILENTCMD)echo Checking $@... + @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/$@ -f $(CURRENT_DIRECTORY)/$@/system_module.mk ATMOSPHERE_CHECKED_LIBSTRATOSPHERE=1 + +$(foreach module,$(ALL_MODULES),clean-$(module)): + @$(SILENTCMD)echo Cleaning $(@:clean-%=%)... + @$(MAKE) --no-print-directory -C $(CURRENT_DIRECTORY)/$(@:clean-%=%) -f $(CURRENT_DIRECTORY)/$(@:clean-%=%)/system_module.mk clean + +ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1) +check_lib: +else +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk +endif + +clean: $(foreach module,$(ALL_MODULES),clean-$(module)) + +.PHONY: all clean check_lib $(foreach module,$(ALL_MODULES),$(module) clean-$(module)) \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/tests/Licensing/Catch2-LICENSE.txt b/Source/Atmosphere-MTC-Unlock/tests/Licensing/Catch2-LICENSE.txt new file mode 100644 index 00000000..36b7cd93 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/Licensing/Catch2-LICENSE.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN 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/tests/TestFs/Makefile b/Source/Atmosphere-MTC-Unlock/tests/TestFs/Makefile new file mode 100644 index 00000000..4f16374d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestFs/Makefile @@ -0,0 +1,51 @@ +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)/unit_test.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)/unit_test.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, $(strip $2)release, $(strip $3), $(strip $4), \ + ATMOSPHERE_BUILD_SETTINGS="$(strip $5)" $(strip $6) \ +)) + +$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_debug, $(strip $2)debug, $(strip $3), $(strip $4), \ + ATMOSPHERE_BUILD_SETTINGS="$(strip $5) -DAMS_BUILD_FOR_DEBUGGING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 $(strip $6) \ +)) + +$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_audit, $(strip $2)audit, $(strip $3), $(strip $4), \ + ATMOSPHERE_BUILD_SETTINGS="$(strip $5) -DAMS_BUILD_FOR_AUDITING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 ATMOSPHERE_BUILD_FOR_AUDITING=1 $(strip $6) \ +)) + +endef + + +$(eval $(call ATMOSPHERE_ADD_TARGETS, nx, , nx-hac-001, arm-cortex-a57,,)) + +$(eval $(call ATMOSPHERE_ADD_TARGETS, win_x64, , generic_windows, generic_x64,,)) + +$(eval $(call ATMOSPHERE_ADD_TARGETS, linux_x64, , generic_linux, generic_x64,,)) +$(eval $(call ATMOSPHERE_ADD_TARGETS, linux_x64_clang, clang_, generic_linux, generic_x64,, ATMOSPHERE_COMPILER_NAME="clang")) +$(eval $(call ATMOSPHERE_ADD_TARGETS, linux_arm64_clang, clang_, generic_linux, generic_arm64,, ATMOSPHERE_COMPILER_NAME="clang")) + +$(eval $(call ATMOSPHERE_ADD_TARGETS, macos_x64, , generic_macos, generic_x64,,)) +$(eval $(call ATMOSPHERE_ADD_TARGETS, macos_arm64, , generic_macos, generic_arm64,,)) + +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/tests/TestFs/source/test.cpp b/Source/Atmosphere-MTC-Unlock/tests/TestFs/source/test.cpp new file mode 100644 index 00000000..96c20fb7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestFs/source/test.cpp @@ -0,0 +1,867 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams { + + namespace fssrv::impl { + + const char *GetExecutionDirectoryPath(); + + } + + namespace { + + void GetPath(char *dst, size_t dst_size, const char *src) { + if (fs::IsPathAbsolute(src)) { + util::SNPrintf(dst, dst_size, "%s", src); + } else { + util::SNPrintf(dst, dst_size, "%s%s", fssrv::impl::GetExecutionDirectoryPath(), src); + } + } + + #define TEST_R_EXPECT(__EXPR__, __EXPECTED__) \ + ({ \ + const Result __test_result = (__EXPR__); \ + if (!(__EXPECTED__ ::Includes(__test_result))) { \ + printf("Unexpected result: %s gave 0x%08x (2%03d-%04d)\n", # __EXPR__, __test_result.GetValue(), __test_result.GetModule(), __test_result.GetDescription()); \ + return; \ + } \ + __test_result; \ + }) + + #define TEST_R_TRY(__EXPR__) \ + ({ \ + const Result __test_result = (__EXPR__); \ + if (R_FAILED(__test_result)) { \ + printf("Unexpected result: %s gave 0x%08x (2%03d-%04d)\n", # __EXPR__, __test_result.GetValue(), __test_result.GetModule(), __test_result.GetDescription()); \ + return; \ + } \ + __test_result; \ + }) + + u8 g_buffer[64_KB]; + + void DoFsTests() { + /* Declare buffer to hold any work paths we have. */ + char path_buf[fs::EntryNameLengthMax + 1]; + char path_buf2[fs::EntryNameLengthMax + 1]; + #define FORMAT_PATH(S) ({ GetPath(path_buf, sizeof(path_buf), S); path_buf; }) + #define FORMAT_PATH2(S) ({ GetPath(path_buf2, sizeof(path_buf2), S); path_buf2; }) + AMS_UNUSED(path_buf); + AMS_UNUSED(path_buf2); + + /* Clear anything from a previous test run, no obligation for this to succeed. */ + fs::DeleteDirectoryRecursively(FORMAT_PATH("./test_dir/")); + + /* Verify that the test directory does not exist. */ + fs::DirectoryEntryType entry_type; + TEST_R_EXPECT(fs::GetEntryType(std::addressof(entry_type), FORMAT_PATH("./test_dir/")), fs::ResultPathNotFound); + + /* Create the subdirectory. */ + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/"))); + + /* Verify the test directory exists and is a directory. */ + TEST_R_TRY(fs::GetEntryType(std::addressof(entry_type), FORMAT_PATH("./test_dir/"))); + AMS_ABORT_UNLESS(entry_type == fs::DirectoryEntryType_Directory); + + /* ==================================================================================================================== */ + /* Create File */ + /* ==================================================================================================================== */ + + /* Create a file. */ + TEST_R_EXPECT(fs::GetEntryType(std::addressof(entry_type), FORMAT_PATH("./test_dir/test_rand.bin")), fs::ResultPathNotFound); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/test_rand.bin"), sizeof(g_buffer))); + + /* Check the file has correct entry type. */ + TEST_R_TRY(fs::GetEntryType(std::addressof(entry_type), FORMAT_PATH("./test_dir/test_rand.bin"))); + AMS_ABORT_UNLESS(entry_type == fs::DirectoryEntryType_File); + + /* Create already existing file -> fs::ResultPathAlreadyExists(). */ + TEST_R_EXPECT(fs::CreateFile(FORMAT_PATH("./test_dir/test_rand.bin"), sizeof(g_buffer)), fs::ResultPathAlreadyExists); + + /* Create already existing dir -> fs::ResultPathAlreadyExists(). */ + TEST_R_EXPECT(fs::CreateFile(FORMAT_PATH("./test_dir/"), sizeof(g_buffer)), fs::ResultPathAlreadyExists); + + /* Create file without parent existing -> fs::ResultPathNotFound(). */ + TEST_R_EXPECT(fs::CreateFile(FORMAT_PATH("./test_dir/aaa/bbb.bin"), sizeof(g_buffer)), fs::ResultPathNotFound); + + /* ==================================================================================================================== */ + /* Create Directory */ + /* ==================================================================================================================== */ + + /* Create the subdirectory. */ + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/test_subdir/"))); + + /* Verify the test directory exists and is a directory. */ + TEST_R_TRY(fs::GetEntryType(std::addressof(entry_type), FORMAT_PATH("./test_dir/test_subdir/"))); + AMS_ABORT_UNLESS(entry_type == fs::DirectoryEntryType_Directory); + + /* Create already existing file -> fs::ResultPathAlreadyExists(). */ + TEST_R_EXPECT(fs::CreateDirectory(FORMAT_PATH("./test_dir/test_rand.bin")), fs::ResultPathAlreadyExists); + + /* Create already existing dir -> fs::ResultPathAlreadyExists(). */ + TEST_R_EXPECT(fs::CreateDirectory(FORMAT_PATH("./test_dir/")), fs::ResultPathAlreadyExists); + + /* Create dir without parent existing -> fs::ResultPathAlreadyExists(). */ + TEST_R_EXPECT(fs::CreateDirectory(FORMAT_PATH("./test_dir/aaa/bbb/")), fs::ResultPathNotFound); + + /* ==================================================================================================================== */ + /* Delete File */ + /* ==================================================================================================================== */ + + /* Delete file succeeds. */ + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/tmp_for_delete.bin"), sizeof(g_buffer))); + TEST_R_TRY(fs::DeleteFile(FORMAT_PATH("./test_dir/tmp_for_delete.bin"))); + + /* Delete on invalid path -> fs::ResultPathNotFound(). */ + TEST_R_EXPECT(fs::DeleteFile(FORMAT_PATH("./test_dir/invalid")), fs::ResultPathNotFound); + + /* Delete on directory -> fs::ResultPathNotFound(). */ + TEST_R_EXPECT(fs::DeleteFile(FORMAT_PATH("./test_dir/test_subdir/")), fs::ResultPathNotFound); + + /* ==================================================================================================================== */ + /* Delete Directory */ + /* ==================================================================================================================== */ + + /* Delete dir succeeds. */ + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/tmp_for_delete/"))); + TEST_R_TRY(fs::DeleteDirectory(FORMAT_PATH("./test_dir/tmp_for_delete/"))); + + /* Delete on invalid path -> fs::ResultPathNotFound(). */ + TEST_R_EXPECT(fs::DeleteDirectory(FORMAT_PATH("./test_dir/invalid/")), fs::ResultPathNotFound); + + /* Delete on file -> fs::ResultPathNotFound(). */ + TEST_R_EXPECT(fs::DeleteDirectory(FORMAT_PATH("./test_dir/test_rand.bin")), fs::ResultPathNotFound); + + /* Delete on non-empty directory -> fs::ResultDirectoryNotEmpty(). */ + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/tmp_for_delete/"))); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/tmp_for_delete/tmp_for_delete.bin"), sizeof(g_buffer))); + TEST_R_EXPECT(fs::DeleteDirectory(FORMAT_PATH("./test_dir/tmp_for_delete/")), fs::ResultDirectoryNotEmpty); + TEST_R_TRY(fs::DeleteFile(FORMAT_PATH("./test_dir/tmp_for_delete/tmp_for_delete.bin"))); + TEST_R_TRY(fs::DeleteDirectory(FORMAT_PATH("./test_dir/tmp_for_delete/"))); + + /* ==================================================================================================================== */ + /* Delete Directory Recursively */ + /* ==================================================================================================================== */ + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/b/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/b/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/b/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/b/0/0/0/"))); + + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/0/y.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/0/z.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/b/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/b/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/b/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/b/0/0/0/x.bin"), 0)); + + TEST_R_TRY(fs::DeleteDirectoryRecursively(FORMAT_PATH("./test_dir/0/"))); + + /* Verify the test directory still exists and is a directory. */ + TEST_R_TRY(fs::GetEntryType(std::addressof(entry_type), FORMAT_PATH("./test_dir/"))); + AMS_ABORT_UNLESS(entry_type == fs::DirectoryEntryType_Directory); + + /* Verify the recursive directory doesn't. */ + TEST_R_EXPECT(fs::GetEntryType(std::addressof(entry_type), FORMAT_PATH("./test_dir/0/")), fs::ResultPathNotFound); + + /* Delete recursive on invalid path -> fs::ResultPathNotFound(). */ + TEST_R_EXPECT(fs::DeleteDirectoryRecursively(FORMAT_PATH("./test_dir/invalid/")), fs::ResultPathNotFound); + + /* Delete recursive on file -> fs::ResultPathNotFound(). */ + TEST_R_EXPECT(fs::DeleteDirectoryRecursively(FORMAT_PATH("./test_dir/test_rand.bin")), fs::ResultPathNotFound); + + /* ==================================================================================================================== */ + /* Clean Directory Recursively */ + /* ==================================================================================================================== */ + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/b/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/b/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/b/0/0/"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/b/0/0/0/"))); + + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/0/y.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/0/z.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/b/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/b/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/b/0/0/x.bin"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/0/0/0/0/0/0/0/0/0/1/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/000/b/0/0/0/x.bin"), 0)); + + TEST_R_TRY(fs::CleanDirectoryRecursively(FORMAT_PATH("./test_dir/0/"))); + + /* Verify the recursive directory still exists and is a directory. */ + TEST_R_TRY(fs::GetEntryType(std::addressof(entry_type), FORMAT_PATH("./test_dir/0/"))); + AMS_ABORT_UNLESS(entry_type == fs::DirectoryEntryType_Directory); + + /* Delete the recursive directory. */ + TEST_R_TRY(fs::DeleteDirectory(FORMAT_PATH("./test_dir/0/"))); + + /* Clean recursive on invalid path -> fs::ResultPathNotFound(). */ + TEST_R_EXPECT(fs::CleanDirectoryRecursively(FORMAT_PATH("./test_dir/invalid/")), fs::ResultPathNotFound); + + /* Clean recursive on file -> fs::ResultPathNotFound(). */ + TEST_R_EXPECT(fs::CleanDirectoryRecursively(FORMAT_PATH("./test_dir/test_rand.bin")), fs::ResultPathNotFound); + + /* ==================================================================================================================== */ + /* Rename File */ + /* ==================================================================================================================== */ + + /* Rename succeeds. */ + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/a.bin"), 1_KB)); + TEST_R_TRY(fs::RenameFile(FORMAT_PATH("./test_dir/a.bin"), FORMAT_PATH2("./test_dir/b.bin"))); + + TEST_R_EXPECT(fs::GetEntryType(std::addressof(entry_type), FORMAT_PATH("./test_dir/a.bin")), fs::ResultPathNotFound); + TEST_R_TRY(fs::GetEntryType(std::addressof(entry_type), FORMAT_PATH("./test_dir/b.bin"))); + AMS_ABORT_UNLESS(entry_type == fs::DirectoryEntryType_File); + + /* Rename non-existing -> fs::ResultPathNotFound */ + TEST_R_EXPECT(fs::RenameFile(FORMAT_PATH("./test_dir/invalid"), FORMAT_PATH2("./test_dir/invalid2")), fs::ResultPathNotFound); + + /* Rename valid -> already existing gives fs::ResultPathAlreadyExists */ + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/a.bin"), 1_KB)); + TEST_R_EXPECT(fs::RenameFile(FORMAT_PATH("./test_dir/a.bin"), FORMAT_PATH2("./test_dir/b.bin")), fs::ResultPathAlreadyExists); + + /* Rename valid -> directory gives fs::ResultPathAlreadyExists */ + TEST_R_EXPECT(fs::RenameFile(FORMAT_PATH("./test_dir/a.bin"), FORMAT_PATH2("./test_dir/test_subdir/")), fs::ResultPathAlreadyExists); + + /* Rename directory -> fs::ResultPathNotFound */ + TEST_R_EXPECT(fs::RenameFile(FORMAT_PATH("./test_dir/test_subdir/"), FORMAT_PATH2("./test_dir/c.bin")), fs::ResultPathNotFound); + + /* Invalid doesn't affect the file/dir. */ + TEST_R_TRY(fs::GetEntryType(std::addressof(entry_type), FORMAT_PATH("./test_dir/a.bin"))); + AMS_ABORT_UNLESS(entry_type == fs::DirectoryEntryType_File); + TEST_R_TRY(fs::GetEntryType(std::addressof(entry_type), FORMAT_PATH("./test_dir/test_subdir/"))); + AMS_ABORT_UNLESS(entry_type == fs::DirectoryEntryType_Directory); + + /* ==================================================================================================================== */ + /* Rename Directory */ + /* ==================================================================================================================== */ + + /* Rename succeeds. */ + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/dir_a/"))); + TEST_R_TRY(fs::RenameDirectory(FORMAT_PATH("./test_dir/dir_a/"), FORMAT_PATH2("./test_dir/dir_b/"))); + + TEST_R_EXPECT(fs::GetEntryType(std::addressof(entry_type), FORMAT_PATH("./test_dir/dir_a/")), fs::ResultPathNotFound); + TEST_R_TRY(fs::GetEntryType(std::addressof(entry_type), FORMAT_PATH("./test_dir/dir_b/"))); + AMS_ABORT_UNLESS(entry_type == fs::DirectoryEntryType_Directory); + + /* Rename non-existing -> fs::ResultPathNotFound */ + TEST_R_EXPECT(fs::RenameDirectory(FORMAT_PATH("./test_dir/invalid"), FORMAT_PATH2("./test_dir/invalid2")), fs::ResultPathNotFound); + + /* Rename valid -> already existing gives fs::ResultPathAlreadyExists */ + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/dir_a/"))); + TEST_R_EXPECT(fs::RenameDirectory(FORMAT_PATH("./test_dir/dir_a/"), FORMAT_PATH2("./test_dir/dir_b/")), fs::ResultPathAlreadyExists); + + /* Rename valid -> file gives fs::ResultPathAlreadyExists */ + TEST_R_EXPECT(fs::RenameDirectory(FORMAT_PATH("./test_dir/dir_a/"), FORMAT_PATH2("./test_dir/a.bin")), fs::ResultPathAlreadyExists); + + /* Rename file -> fs::ResultPathNotFound */ + TEST_R_EXPECT(fs::RenameDirectory(FORMAT_PATH("./test_dir/a.bin"), FORMAT_PATH2("./test_dir/dir_c/")), fs::ResultPathNotFound); + + /* Invalid doesn't affect the file/dir. */ + TEST_R_TRY(fs::GetEntryType(std::addressof(entry_type), FORMAT_PATH("./test_dir/a.bin"))); + AMS_ABORT_UNLESS(entry_type == fs::DirectoryEntryType_File); + TEST_R_TRY(fs::GetEntryType(std::addressof(entry_type), FORMAT_PATH("./test_dir/dir_a/"))); + AMS_ABORT_UNLESS(entry_type == fs::DirectoryEntryType_Directory); + + /* ==================================================================================================================== */ + /* Get Entry Type */ + /* ==================================================================================================================== */ + + /* File -> file. */ + TEST_R_TRY(fs::GetEntryType(std::addressof(entry_type), FORMAT_PATH("./test_dir/a.bin"))); + AMS_ABORT_UNLESS(entry_type == fs::DirectoryEntryType_File); + + /* Dir -> dir */ + TEST_R_TRY(fs::GetEntryType(std::addressof(entry_type), FORMAT_PATH("./test_dir/dir_a/"))); + AMS_ABORT_UNLESS(entry_type == fs::DirectoryEntryType_Directory); + + /* Invalid -> fs::ResultPathNotFound */ + TEST_R_EXPECT(fs::GetEntryType(std::addressof(entry_type), FORMAT_PATH("./test_dir/invalid")), fs::ResultPathNotFound); + + /* ==================================================================================================================== */ + /* Get Free Space Size */ + /* ==================================================================================================================== */ + + s64 free_size = 0; + TEST_R_TRY(fs::GetFreeSpaceSize(std::addressof(free_size), FORMAT_PATH("./test_dir/"))); + AMS_ABORT_UNLESS(free_size > 0); + + /* ==================================================================================================================== */ + /* Get Total Space Size */ + /* ==================================================================================================================== */ + + s64 total_size = 0; + TEST_R_TRY(fs::GetTotalSpaceSize(std::addressof(total_size), FORMAT_PATH("./test_dir/"))); + AMS_ABORT_UNLESS(total_size >= free_size); + + /* ==================================================================================================================== */ + /* Get File Time Stamp */ + /* ==================================================================================================================== */ + + /* Get timestamp succeeds. */ + fs::FileTimeStamp timestamp; + TEST_R_TRY(fs::GetFileTimeStamp(std::addressof(timestamp), FORMAT_PATH("./test_dir/a.bin"))); + AMS_ABORT_UNLESS(timestamp.create.value > 0); + AMS_ABORT_UNLESS(timestamp.access.value > 0); + AMS_ABORT_UNLESS(timestamp.modify.value > 0); + AMS_ABORT_UNLESS(!timestamp.is_local_time); + + /* Invalid -> fs::ResultPathNotFound */ + TEST_R_EXPECT(fs::GetFileTimeStamp(std::addressof(timestamp), FORMAT_PATH("./test_dir/invalid")), fs::ResultPathNotFound); + + /* Directory -> fs::ResultPathNotFound */ + TEST_R_EXPECT(fs::GetFileTimeStamp(std::addressof(timestamp), FORMAT_PATH("./test_dir/dir_a/")), fs::ResultPathNotFound); + + /* ==================================================================================================================== */ + /* Query Entry */ + /* ==================================================================================================================== */ + + TEST_R_EXPECT(fs::SetConcatenationFileAttribute(FORMAT_PATH("./test_dir/")), fs::ResultUnsupportedOperation); + + /* ==================================================================================================================== */ + /* Open File */ + /* ==================================================================================================================== */ + + /* Open valid succeeds. */ + fs::FileHandle file; + TEST_R_TRY(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/a.bin"), fs::OpenMode_ReadWrite | fs::OpenMode_AllowAppend)); + fs::CloseFile(file); + + /* Open invalid -> path not found. */ + TEST_R_EXPECT(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/invalid"), fs::OpenMode_ReadWrite | fs::OpenMode_AllowAppend), fs::ResultPathNotFound); + + /* Open directory -> path not found. */ + TEST_R_EXPECT(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/dir_a/"), fs::OpenMode_ReadWrite | fs::OpenMode_AllowAppend), fs::ResultPathNotFound); + + /* Open with invalid mode -> fs::ResultInvalidOpenMode */ + TEST_R_EXPECT(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/a.bin"), static_cast<fs::OpenMode>(~0u)), fs::ResultInvalidOpenMode); + + /* Read only file is read only. */ + { + s64 file_size; + u8 buf[1_KB]; + + { + TEST_R_TRY(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/a.bin"), fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* File size matches create. */ + TEST_R_TRY(fs::GetFileSize(std::addressof(file_size), file)); + AMS_ABORT_UNLESS(file_size == 1_KB); + + /* Read succeeds. */ + TEST_R_TRY(fs::ReadFile(file, 0, buf, sizeof(buf))); + + /* Completely empty read ok. */ + TEST_R_TRY(fs::ReadFile(file, 0, nullptr, 0)); + + /* Flush succeeds. */ + TEST_R_TRY(fs::FlushFile(file)); + } + + /* Incorrect arguments return incorrect results. */ + { + TEST_R_TRY(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/a.bin"), fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + TEST_R_EXPECT(fs::ReadFile(file, -1, buf, sizeof(buf)), fs::ResultOutOfRange); + } + { + TEST_R_TRY(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/a.bin"), fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + TEST_R_EXPECT(fs::ReadFile(file, 0, buf, -1), fs::ResultOutOfRange); + } + { + TEST_R_TRY(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/a.bin"), fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + TEST_R_EXPECT(fs::ReadFile(file, 0, nullptr, sizeof(buf)), fs::ResultNullptrArgument); + } + + /* Write fails. */ + { + TEST_R_TRY(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/a.bin"), fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + TEST_R_EXPECT(fs::WriteFile(file, 0, g_buffer, sizeof(g_buffer), fs::WriteOption::None), fs::ResultWriteNotPermitted); + } + + /* Set size fails. */ + { + TEST_R_TRY(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/a.bin"), fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + TEST_R_EXPECT(fs::SetFileSize(file, 2_KB), fs::ResultWriteNotPermitted); + } + + /* File size unchanged by bad set size. */ + { + TEST_R_TRY(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/a.bin"), fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + TEST_R_TRY(fs::GetFileSize(std::addressof(file_size), file)); + AMS_ABORT_UNLESS(file_size == 1_KB); + } + } + + /* Write only file is writable but not readable. */ + { + s64 file_size; + u8 buf[1_KB]; + + { + TEST_R_TRY(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/a.bin"), fs::OpenMode_Write)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Write succeeds. */ + std::memset(buf, 0xcc, sizeof(buf)); + TEST_R_TRY(fs::WriteFile(file, 0, buf, sizeof(buf), fs::WriteOption::None)); + + /* Flush succeeds. */ + TEST_R_TRY(fs::FlushFile(file)); + + /* Write with flush succeeds. */ + TEST_R_TRY(fs::WriteFile(file, 0, buf, sizeof(buf), fs::WriteOption::Flush)); + + /* Get size succeeds. */ + TEST_R_TRY(fs::GetFileSize(std::addressof(file_size), file)); + AMS_ABORT_UNLESS(file_size == 1_KB); + + /* Set size succeeds. */ + TEST_R_TRY(fs::SetFileSize(file, 2_KB)); + TEST_R_TRY(fs::GetFileSize(std::addressof(file_size), file)); + AMS_ABORT_UNLESS(file_size == 2_KB); + + /* Write at updated size works. */ + TEST_R_TRY(fs::WriteFile(file, 1_KB, buf, sizeof(buf), fs::WriteOption::Flush)); + + /* Truncate down succeeds. */ + TEST_R_TRY(fs::SetFileSize(file, 1_KB)); + TEST_R_TRY(fs::GetFileSize(std::addressof(file_size), file)); + AMS_ABORT_UNLESS(file_size == 1_KB); + + /* Completely empty write ok. */ + TEST_R_TRY(fs::WriteFile(file, 0, nullptr, 0, fs::WriteOption::Flush)); + } + + /* Incorrect arguments return incorrect results. */ + { + TEST_R_TRY(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/a.bin"), fs::OpenMode_Write)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + TEST_R_EXPECT(fs::WriteFile(file, -1, buf, sizeof(buf), fs::WriteOption::None), fs::ResultOutOfRange); + } + { + TEST_R_TRY(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/a.bin"), fs::OpenMode_Write)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + TEST_R_EXPECT(fs::WriteFile(file, 0, buf, -1, fs::WriteOption::None), fs::ResultOutOfRange); + } + { + TEST_R_TRY(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/a.bin"), fs::OpenMode_Write)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + TEST_R_EXPECT(fs::WriteFile(file, 0, nullptr, sizeof(buf), fs::WriteOption::None), fs::ResultNullptrArgument); + } + { + TEST_R_TRY(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/a.bin"), fs::OpenMode_Write)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + TEST_R_EXPECT(fs::WriteFile(file, 1_KB, buf, sizeof(buf), fs::WriteOption::None), fs::ResultFileExtensionWithoutOpenModeAllowAppend); + } + + /* Read fails. */ + { + TEST_R_TRY(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/a.bin"), fs::OpenMode_Write)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + TEST_R_EXPECT(fs::ReadFile(file, 0, buf, sizeof(buf)), fs::ResultReadNotPermitted); + } + + /* Appending works with OpenMode_AllowAppend. */ + { + TEST_R_TRY(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/a.bin"), fs::OpenMode_Write | fs::OpenMode_AllowAppend)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + TEST_R_TRY(fs::GetFileSize(std::addressof(file_size), file)); + AMS_ABORT_UNLESS(file_size == 1_KB); + + for (size_t i = 0; i < sizeof(buf); ++i) { + buf[i] = static_cast<u8>(i); + } + TEST_R_TRY(fs::WriteFile(file, 1_KB, buf, sizeof(buf), fs::WriteOption::Flush)); + + TEST_R_TRY(fs::GetFileSize(std::addressof(file_size), file)); + AMS_ABORT_UNLESS(file_size == 2_KB); + } + + /* Data is persistent. */ + { + TEST_R_TRY(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/a.bin"), fs::OpenMode_ReadWrite)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + TEST_R_TRY(fs::ReadFile(file, 0, buf, sizeof(buf))); + + for (size_t i = 0; i < 1_KB; ++i) { + AMS_ABORT_UNLESS(buf[i] == 0xCC); + } + + TEST_R_TRY(fs::ReadFile(file, 1_KB, buf, sizeof(buf))); + + for (size_t i = 0; i < 1_KB; ++i) { + AMS_ABORT_UNLESS(buf[i] == static_cast<u8>(i)); + } + + TEST_R_TRY(fs::WriteFile(file, 0, buf, sizeof(buf), fs::WriteOption::Flush)); + + TEST_R_TRY(fs::SetFileSize(file, 1_KB)); + + TEST_R_TRY(fs::GetFileSize(std::addressof(file_size), file)); + AMS_ABORT_UNLESS(file_size == 1_KB); + + TEST_R_TRY(fs::ReadFile(file, 0, buf, sizeof(buf))); + + for (size_t i = 0; i < 1_KB; ++i) { + AMS_ABORT_UNLESS(buf[i] == static_cast<u8>(i)); + } + } + } + + /* More involved file data test using random buffer. */ + { + u8 buf[1_KB]; + /* Write random data. */ + { + TEST_R_TRY(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/test_rand.bin"), fs::OpenMode_Write)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Get a bunch of random data. */ + os::GenerateRandomBytes(g_buffer, sizeof(g_buffer)); + + /* Write it to disk. */ + TEST_R_TRY(fs::WriteFile(file, 0, g_buffer, sizeof(g_buffer), fs::WriteOption::None)); + TEST_R_TRY(fs::FlushFile(file)); + } + + /* Read and verify random data. */ + { + TEST_R_TRY(fs::OpenFile(std::addressof(file), FORMAT_PATH("./test_dir/test_rand.bin"), fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + u32 ofs; + for (size_t i = 0; i < 1000; ++i) { + os::GenerateRandomBytes(std::addressof(ofs), sizeof(ofs)); + ofs %= (sizeof(g_buffer) - sizeof(buf)); + + TEST_R_TRY(fs::ReadFile(file, ofs, buf, sizeof(buf))); + AMS_ABORT_UNLESS(std::memcmp(buf, g_buffer + ofs, sizeof(buf)) == 0); + } + } + } + + /* ==================================================================================================================== */ + /* Open Directory */ + /* ==================================================================================================================== */ + fs::DirectoryHandle dir; + TEST_R_TRY(fs::OpenDirectory(std::addressof(dir), FORMAT_PATH("./test_dir/dir_a/"), fs::OpenDirectoryMode_All | fs::OpenDirectoryMode_NotRequireFileSize)); + fs::CloseDirectory(dir); + + /* Open invalid -> path not found. */ + TEST_R_EXPECT(fs::OpenDirectory(std::addressof(dir), FORMAT_PATH("./test_dir/invalid"), fs::OpenDirectoryMode_All | fs::OpenDirectoryMode_NotRequireFileSize), fs::ResultPathNotFound); + + /* Open file -> path not found. */ + TEST_R_EXPECT(fs::OpenDirectory(std::addressof(dir), FORMAT_PATH("./test_dir/a.bin"), fs::OpenDirectoryMode_All | fs::OpenDirectoryMode_NotRequireFileSize), fs::ResultPathNotFound); + + /* Open with invalid mode -> fs::ResultInvalidOpenMode */ + TEST_R_EXPECT(fs::OpenDirectory(std::addressof(dir), FORMAT_PATH("./test_dir/dir_a/"), static_cast<fs::OpenDirectoryMode>(~0u)), fs::ResultInvalidOpenMode); + + /* Populate test directory with three files and two dirs. */ + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/dir_a/f0"), 0)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/dir_a/f1"), 1_KB)); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/dir_a/f2"), 0x42069)); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/dir_a/d0"))); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/dir_a/d1"))); + TEST_R_TRY(fs::CreateFile(FORMAT_PATH("./test_dir/dir_a/d0/file"), 0)); + TEST_R_TRY(fs::CreateDirectory(FORMAT_PATH("./test_dir/dir_a/d0/dir/"))); + + /* Directory tests. */ + { + bool seen_file[3]; + bool seen_dir[2]; + + constexpr s64 NumFiles = util::size(seen_file); + constexpr s64 NumDirs = util::size(seen_dir); + constexpr s64 NumAll = NumFiles + NumDirs; + + fs::DirectoryEntry entries[2 * NumAll]; + s64 entry_count; + + auto ResetSeenFiles = [&] () { for (auto &b : seen_file) { b = false; } }; + auto ResetSeenDirs = [&] () { for (auto &b : seen_dir) { b = false; } }; + auto ResetSeenAll = [&] () { ResetSeenFiles(); ResetSeenDirs(); }; + + auto CheckSeenFiles = [&] () { for (const auto b : seen_file) { AMS_ABORT_UNLESS(b); } }; + auto CheckSeenDirs = [&] () { for (const auto b : seen_dir) { AMS_ABORT_UNLESS(b); } }; + auto CheckSeenAll = [&] () { CheckSeenFiles(); CheckSeenDirs(); }; + + auto CheckNotSeenFiles = [&] () { for (const auto b : seen_file) { AMS_ABORT_UNLESS(!b); } }; + auto CheckNotSeenDirs = [&] () { for (const auto b : seen_dir) { AMS_ABORT_UNLESS(!b); } }; + + auto CheckDirectoryEntry = [&] (const fs::DirectoryEntry &entry) { + /* Check name. */ + AMS_ABORT_UNLESS(entry.name[0] == 'f' || entry.name[0] == 'd'); + AMS_ABORT_UNLESS('0' <= entry.name[1] && entry.name[1] <= '9'); + AMS_ABORT_UNLESS(entry.name[2] == 0); + + /* Check type. */ + if (entry.name[0] == 'f') { + AMS_ABORT_UNLESS(entry.type == fs::DirectoryEntryType_File); + + /* If file, check size. */ + switch (entry.name[1]) { + case '0': AMS_ABORT_UNLESS(entry.file_size == 0); break; + case '1': AMS_ABORT_UNLESS(entry.file_size == 1_KB); break; + case '2': AMS_ABORT_UNLESS(entry.file_size == 0x42069); break; + } + + AMS_ABORT_UNLESS(!seen_file[(entry.name[1] - '0')]); + seen_file[(entry.name[1] - '0')] = true; + } else { + AMS_ABORT_UNLESS(entry.type == fs::DirectoryEntryType_Directory); + + AMS_ABORT_UNLESS(!seen_dir[(entry.name[1] - '0')]); + seen_dir[(entry.name[1] - '0')] = true; + } + }; + + /* Get EntryCount is correct. */ + { + /* All returns all entries. */ + { + TEST_R_TRY(fs::OpenDirectory(std::addressof(dir), FORMAT_PATH("./test_dir/dir_a/"), fs::OpenDirectoryMode_All)); + ON_SCOPE_EXIT { fs::CloseDirectory(dir); }; + + TEST_R_TRY(fs::GetDirectoryEntryCount(std::addressof(entry_count), dir)); + AMS_ABORT_UNLESS(entry_count == NumAll); + } + + /* File returns only files, and does not count things in subdirectories. */ + { + TEST_R_TRY(fs::OpenDirectory(std::addressof(dir), FORMAT_PATH("./test_dir/dir_a/"), fs::OpenDirectoryMode_File)); + ON_SCOPE_EXIT { fs::CloseDirectory(dir); }; + + TEST_R_TRY(fs::GetDirectoryEntryCount(std::addressof(entry_count), dir)); + AMS_ABORT_UNLESS(entry_count == NumFiles); + } + + /* Dir returns only dirs, and does not count things in subdirectories. */ + { + TEST_R_TRY(fs::OpenDirectory(std::addressof(dir), FORMAT_PATH("./test_dir/dir_a/"), fs::OpenDirectoryMode_Directory)); + ON_SCOPE_EXIT { fs::CloseDirectory(dir); }; + + TEST_R_TRY(fs::GetDirectoryEntryCount(std::addressof(entry_count), dir)); + AMS_ABORT_UNLESS(entry_count == NumDirs); + } + } + + /* Read is correct, N at a time. */ + for (s64 at_a_time = 1; at_a_time <= 2 * NumAll; ++at_a_time) { + /* All returns all entries. */ + { + ResetSeenAll(); + + TEST_R_TRY(fs::OpenDirectory(std::addressof(dir), FORMAT_PATH("./test_dir/dir_a/"), fs::OpenDirectoryMode_All)); + ON_SCOPE_EXIT { fs::CloseDirectory(dir); }; + + TEST_R_TRY(fs::GetDirectoryEntryCount(std::addressof(entry_count), dir)); + AMS_ABORT_UNLESS(entry_count == NumAll); + + s64 remaining = entry_count; + while (remaining > 0) { + s64 cur; + TEST_R_TRY(fs::ReadDirectory(std::addressof(cur), entries, dir, at_a_time)); + AMS_ABORT_UNLESS(cur <= remaining); + AMS_ABORT_UNLESS(cur == std::min<s64>(at_a_time, remaining)); + + for (s64 i = 0; i < cur; ++i) { + CheckDirectoryEntry(entries[i]); + } + + remaining -= cur; + } + + CheckSeenAll(); + + /* Read succeeds at end of dir. */ + s64 cur; + TEST_R_TRY(fs::ReadDirectory(std::addressof(cur), entries, dir, at_a_time)); + AMS_ABORT_UNLESS(cur == 0); + + /* Get entry count still shows correct value. */ + s64 entry_count2; + TEST_R_TRY(fs::GetDirectoryEntryCount(std::addressof(entry_count2), dir)); + AMS_ABORT_UNLESS(entry_count2 == entry_count); + } + + /* File returns only files. */ + { + ResetSeenAll(); + + TEST_R_TRY(fs::OpenDirectory(std::addressof(dir), FORMAT_PATH("./test_dir/dir_a/"), fs::OpenDirectoryMode_File)); + ON_SCOPE_EXIT { fs::CloseDirectory(dir); }; + + TEST_R_TRY(fs::GetDirectoryEntryCount(std::addressof(entry_count), dir)); + AMS_ABORT_UNLESS(entry_count == NumFiles); + + s64 remaining = entry_count; + while (remaining > 0) { + s64 cur; + TEST_R_TRY(fs::ReadDirectory(std::addressof(cur), entries, dir, at_a_time)); + AMS_ABORT_UNLESS(cur <= remaining); + AMS_ABORT_UNLESS(cur == std::min<s64>(at_a_time, remaining)); + + for (s64 i = 0; i < cur; ++i) { + CheckDirectoryEntry(entries[i]); + } + + remaining -= cur; + } + + CheckSeenFiles(); + CheckNotSeenDirs(); + + /* Read succeeds at end of dir. */ + s64 cur; + TEST_R_TRY(fs::ReadDirectory(std::addressof(cur), entries, dir, at_a_time)); + AMS_ABORT_UNLESS(cur == 0); + + /* Get entry count still shows correct value. */ + s64 entry_count2; + TEST_R_TRY(fs::GetDirectoryEntryCount(std::addressof(entry_count2), dir)); + AMS_ABORT_UNLESS(entry_count2 == entry_count); + } + + /* Directory returns only dirs. */ + { + ResetSeenAll(); + + TEST_R_TRY(fs::OpenDirectory(std::addressof(dir), FORMAT_PATH("./test_dir/dir_a/"), fs::OpenDirectoryMode_Directory)); + ON_SCOPE_EXIT { fs::CloseDirectory(dir); }; + + TEST_R_TRY(fs::GetDirectoryEntryCount(std::addressof(entry_count), dir)); + AMS_ABORT_UNLESS(entry_count == NumDirs); + + s64 remaining = entry_count; + while (remaining > 0) { + s64 cur; + TEST_R_TRY(fs::ReadDirectory(std::addressof(cur), entries, dir, at_a_time)); + AMS_ABORT_UNLESS(cur <= remaining); + AMS_ABORT_UNLESS(cur == std::min<s64>(at_a_time, remaining)); + + for (s64 i = 0; i < cur; ++i) { + CheckDirectoryEntry(entries[i]); + } + + remaining -= cur; + } + + CheckSeenDirs(); + CheckNotSeenFiles(); + + /* Read succeeds at end of dir. */ + s64 cur; + TEST_R_TRY(fs::ReadDirectory(std::addressof(cur), entries, dir, at_a_time)); + AMS_ABORT_UNLESS(cur == 0); + + /* Get entry count still shows correct value. */ + s64 entry_count2; + TEST_R_TRY(fs::GetDirectoryEntryCount(std::addressof(entry_count2), dir)); + AMS_ABORT_UNLESS(entry_count2 == entry_count); + } + } + } + + /* ==================================================================================================================== */ + /* Cleanup */ + /* ==================================================================================================================== */ + TEST_R_TRY(fs::DeleteDirectoryRecursively(FORMAT_PATH("./test_dir/"))); + } + + } + + + void Main() { + fs::SetEnabledAutoAbort(false); + + printf("Doing FS test!\n"); + DoFsTests(); + printf("All tests completed!\n"); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestFs/unit_test.mk b/Source/Atmosphere-MTC-Unlock/tests/TestFs/unit_test.mk new file mode 100644 index 00000000..ed1457ce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestFs/unit_test.mk @@ -0,0 +1,155 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ifeq ($(ATMOSPHERE_BOARD),nx-hac-001) +export BOARD_TARGET_SUFFIX := .kip +else ifeq ($(ATMOSPHERE_BOARD),generic_windows) +export BOARD_TARGET_SUFFIX := .exe +else ifeq ($(ATMOSPHERE_BOARD),generic_linux) +export BOARD_TARGET_SUFFIX := +else ifeq ($(ATMOSPHERE_BOARD),generic_macos) +export BOARD_TARGET_SUFFIX := +else +export BOARD_TARGET_SUFFIX := $(TARGET) +endif + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(BOARD_TARGET) $(TARGET).elf + @for i in $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR); do [ -d $$i ] && rmdir $$i 2>/dev/null || true; done + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT)$(BOARD_TARGET_SUFFIX) + +%.kip : %.elf + +%.nsp : %.nso %.npdm + +%.nso: %.elf + + +#--------------------------------------------------------------------------------- +$(OUTPUT).elf: $(OFILES) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @echo linking $(notdir $@) + $(SILENTCMD)$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + $(SILENTCMD)$(NM) -CSn $@ > $(notdir $(OUTPUT).lst) + +$(OUTPUT).exe: $(OFILES) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @echo linking $(notdir $@) + $(SILENTCMD)$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + $(SILENTCMD)$(NM) -CSn $@ > $(notdir $*.lst) + + +ifeq ($(strip $(BOARD_TARGET_SUFFIX)),) +$(OUTPUT): $(OFILES) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @echo linking $(notdir $@) + $(SILENTCMD)$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + $(SILENTCMD)$(NM) -CSn $@ > $(notdir $@.lst) +endif + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestOsEvents/Makefile b/Source/Atmosphere-MTC-Unlock/tests/TestOsEvents/Makefile new file mode 100644 index 00000000..4f16374d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestOsEvents/Makefile @@ -0,0 +1,51 @@ +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)/unit_test.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)/unit_test.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, $(strip $2)release, $(strip $3), $(strip $4), \ + ATMOSPHERE_BUILD_SETTINGS="$(strip $5)" $(strip $6) \ +)) + +$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_debug, $(strip $2)debug, $(strip $3), $(strip $4), \ + ATMOSPHERE_BUILD_SETTINGS="$(strip $5) -DAMS_BUILD_FOR_DEBUGGING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 $(strip $6) \ +)) + +$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_audit, $(strip $2)audit, $(strip $3), $(strip $4), \ + ATMOSPHERE_BUILD_SETTINGS="$(strip $5) -DAMS_BUILD_FOR_AUDITING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 ATMOSPHERE_BUILD_FOR_AUDITING=1 $(strip $6) \ +)) + +endef + + +$(eval $(call ATMOSPHERE_ADD_TARGETS, nx, , nx-hac-001, arm-cortex-a57,,)) + +$(eval $(call ATMOSPHERE_ADD_TARGETS, win_x64, , generic_windows, generic_x64,,)) + +$(eval $(call ATMOSPHERE_ADD_TARGETS, linux_x64, , generic_linux, generic_x64,,)) +$(eval $(call ATMOSPHERE_ADD_TARGETS, linux_x64_clang, clang_, generic_linux, generic_x64,, ATMOSPHERE_COMPILER_NAME="clang")) +$(eval $(call ATMOSPHERE_ADD_TARGETS, linux_arm64_clang, clang_, generic_linux, generic_arm64,, ATMOSPHERE_COMPILER_NAME="clang")) + +$(eval $(call ATMOSPHERE_ADD_TARGETS, macos_x64, , generic_macos, generic_x64,,)) +$(eval $(call ATMOSPHERE_ADD_TARGETS, macos_arm64, , generic_macos, generic_arm64,,)) + +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/tests/TestOsEvents/source/test.cpp b/Source/Atmosphere-MTC-Unlock/tests/TestOsEvents/source/test.cpp new file mode 100644 index 00000000..4ff3442b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestOsEvents/source/test.cpp @@ -0,0 +1,476 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams { + + namespace { + + struct InterThreadSync { + util::Atomic<int> reader_state; + util::Atomic<int> writer_state; + os::EventType writer_ready_event; + os::EventType reader_ready_event; + union { + struct { + os::SystemEventType system_event_as_manual_clear_event; + os::SystemEventType system_event_as_manual_clear_interprocess_event; + os::SystemEventType system_event_as_auto_clear_event; + os::SystemEventType system_event_as_auto_clear_interprocess_event; + }; + os::SystemEventType system_events[4]; + }; + }; + + bool IsManualClearEventIndex(size_t i) { + return i == 0 || i == 1; + } + + alignas(os::MemoryPageSize) constinit u8 g_writer_thread_stack[16_KB]; + alignas(os::MemoryPageSize) constinit u8 g_reader_thread_stack[16_KB]; + + void TestWriterThread(void *arg) { + /* Get the synchronization arguments. */ + auto &sync = *static_cast<InterThreadSync *>(arg); + AMS_UNUSED(sync); + + /* Wait for reader to be ready. */ + os::WaitEvent(std::addressof(sync.reader_ready_event)); + AMS_ABORT_UNLESS(sync.reader_state == 1); + + /* Verify that all events can be signaled. */ + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + /* Set the event for this go. */ + os::SignalSystemEvent(sync.system_events + i); + + sync.writer_state = 1; + os::SignalEvent(std::addressof(sync.writer_ready_event)); + + /* Wait for the reader to finish. */ + os::WaitEvent(std::addressof(sync.reader_ready_event)); + AMS_ABORT_UNLESS(sync.reader_state == 1); + } + + /* Verify that all events can be signaled (for TimedWait 0). */ + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + /* Set the event for this go. */ + os::SignalSystemEvent(sync.system_events + i); + + sync.writer_state = 2; + os::SignalEvent(std::addressof(sync.writer_ready_event)); + + /* Wait for the reader to finish. */ + os::WaitEvent(std::addressof(sync.reader_ready_event)); + AMS_ABORT_UNLESS(sync.reader_state == 2); + } + + /* Verify that all events can be signaled (for TimedWait 2). */ + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + /* Set the event for this go. */ + os::SignalSystemEvent(sync.system_events + i); + + sync.writer_state = 3; + os::SignalEvent(std::addressof(sync.writer_ready_event)); + + /* Wait for the reader to finish. */ + os::WaitEvent(std::addressof(sync.reader_ready_event)); + AMS_ABORT_UNLESS(sync.reader_state == 3); + } + + /* Verify that all events can be signaled (for True Wait). */ + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + /* Set the event for this go. */ + os::SignalSystemEvent(sync.system_events + i); + + sync.writer_state = 4; + os::SignalEvent(std::addressof(sync.writer_ready_event)); + + /* Wait for the reader to finish. */ + os::WaitEvent(std::addressof(sync.reader_ready_event)); + AMS_ABORT_UNLESS(sync.reader_state == 4); + } + + /* Verify that all events can be signaled (TryWaitAny). */ + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + /* Set the event for this go. */ + os::SignalSystemEvent(sync.system_events + i); + + sync.writer_state = 5; + os::SignalEvent(std::addressof(sync.writer_ready_event)); + + /* Wait for the reader to finish. */ + os::WaitEvent(std::addressof(sync.reader_ready_event)); + AMS_ABORT_UNLESS(sync.reader_state == 5); + } + + /* Verify that all events can be signaled (TimedWaitAny 0). */ + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + /* Set the event for this go. */ + os::SignalSystemEvent(sync.system_events + i); + + sync.writer_state = 6; + os::SignalEvent(std::addressof(sync.writer_ready_event)); + + /* Wait for the reader to finish. */ + os::WaitEvent(std::addressof(sync.reader_ready_event)); + AMS_ABORT_UNLESS(sync.reader_state == 6); + } + + /* Verify that all events can be signaled (TimedWaitAny 2). */ + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + /* Set the event for this go. */ + os::SignalSystemEvent(sync.system_events + i); + + sync.writer_state = 7; + os::SignalEvent(std::addressof(sync.writer_ready_event)); + + /* Wait for the reader to finish. */ + os::WaitEvent(std::addressof(sync.reader_ready_event)); + AMS_ABORT_UNLESS(sync.reader_state == 7); + } + + /* Verify that all events can be signaled (TrueWaitAny). */ + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + /* Set the event for this go. */ + os::SignalSystemEvent(sync.system_events + i); + + sync.writer_state = 8; + os::SignalEvent(std::addressof(sync.writer_ready_event)); + + /* Wait for the reader to finish. */ + os::WaitEvent(std::addressof(sync.reader_ready_event)); + AMS_ABORT_UNLESS(sync.reader_state == 8); + } + + /* Verify that reader can receive without explicit sync. */ + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + /* Set the event for this go. */ + os::SignalSystemEvent(sync.system_events + i); + + sync.writer_state = 9; + + } + + /* Wait for the reader to finish. */ + os::WaitEvent(std::addressof(sync.reader_ready_event)); + AMS_ABORT_UNLESS(sync.reader_state == 9); + } + + void TestReaderThread(void *arg) { + /* Get the synchronization arguments. */ + auto &sync = *static_cast<InterThreadSync *>(arg); + AMS_UNUSED(sync); + + /* Set up multi-wait objects. */ + os::MultiWaitType mw; + os::MultiWaitHolderType holders[util::size(sync.system_events)]; + os::InitializeMultiWait(std::addressof(mw)); + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + os::InitializeMultiWaitHolder(holders + i, sync.system_events + i); + os::LinkMultiWaitHolder(std::addressof(mw), holders + i); + } + ON_SCOPE_EXIT { + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + os::UnlinkMultiWaitHolder(holders + i); + os::FinalizeMultiWaitHolder(holders + i); + } + os::FinalizeMultiWait(std::addressof(mw)); + }; + + /* Sanity check: all events are non-signaled. */ + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + i) == false); + AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + i, TimeSpan::FromNanoSeconds(0)) == false); + AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + i, TimeSpan::FromMilliSeconds(2)) == false); + } + + /* Sanity check that wait any does the right thing when nothing is signaled. */ + AMS_ABORT_UNLESS(os::TryWaitAny(std::addressof(mw)) == nullptr); + AMS_ABORT_UNLESS(os::TimedWaitAny(std::addressof(mw), TimeSpan::FromNanoSeconds(0)) == nullptr); + AMS_ABORT_UNLESS(os::TimedWaitAny(std::addressof(mw), TimeSpan::FromNanoSeconds(2)) == nullptr); + + /* Let writer know that we're ready. */ + sync.reader_state = 1; + os::SignalEvent(std::addressof(sync.reader_ready_event)); + + /* Verify that we can receive signal on each event. */ + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + /* Wait for writer to do the relevant work */ + os::WaitEvent(std::addressof(sync.writer_ready_event)); + AMS_ABORT_UNLESS(sync.writer_state == 1); + + /* Test all events. */ + for (size_t n = 0; n < util::size(sync.system_events); ++n) { + if (i == n) { + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == true); + if (IsManualClearEventIndex(n)) { + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == true); + os::ClearSystemEvent(sync.system_events + n); + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false); + } else { + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false); + } + } else { + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false); + } + } + + /* Let writer know we're done. */ + sync.reader_state = 1; + os::SignalEvent(std::addressof(sync.reader_ready_event)); + } + + /* Verify that we can receive signal on each event (Timed Wait 0). */ + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + /* Wait for writer to do the relevant work */ + os::WaitEvent(std::addressof(sync.writer_ready_event)); + AMS_ABORT_UNLESS(sync.writer_state == 2); + + /* Test all events. */ + for (size_t n = 0; n < util::size(sync.system_events); ++n) { + if (i == n) { + AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(0)) == true); + if (IsManualClearEventIndex(n)) { + AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(0)) == true); + os::ClearSystemEvent(sync.system_events + n); + AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(0)) == false); + } else { + AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(0)) == false); + } + } else { + AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(0)) == false); + } + } + + /* Let writer know we're done. */ + sync.reader_state = 2; + os::SignalEvent(std::addressof(sync.reader_ready_event)); + } + + /* Verify that we can receive signal on each event (Timed Wait 2). */ + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + /* Wait for writer to do the relevant work */ + os::WaitEvent(std::addressof(sync.writer_ready_event)); + AMS_ABORT_UNLESS(sync.writer_state == 3); + + /* Test all events. */ + for (size_t n = 0; n < util::size(sync.system_events); ++n) { + if (i == n) { + AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(2)) == true); + if (IsManualClearEventIndex(n)) { + AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(2)) == true); + os::ClearSystemEvent(sync.system_events + n); + AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(2)) == false); + } else { + AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(2)) == false); + } + } else { + AMS_ABORT_UNLESS(os::TimedWaitSystemEvent(sync.system_events + n, TimeSpan::FromMilliSeconds(2)) == false); + } + } + + /* Let writer know we're done. */ + sync.reader_state = 3; + os::SignalEvent(std::addressof(sync.reader_ready_event)); + } + + /* Verify that we can receive signal on each event. */ + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + /* Wait for writer to do the relevant work */ + os::WaitEvent(std::addressof(sync.writer_ready_event)); + AMS_ABORT_UNLESS(sync.writer_state == 4); + + /* Test all events. */ + for (size_t n = 0; n < util::size(sync.system_events); ++n) { + if (i == n) { + os::WaitSystemEvent(sync.system_events + n); + if (IsManualClearEventIndex(n)) { + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == true); + os::WaitSystemEvent(sync.system_events + n); + os::ClearSystemEvent(sync.system_events + n); + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false); + } else { + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false); + } + } else { + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false); + } + } + + /* Let writer know we're done. */ + sync.reader_state = 4; + os::SignalEvent(std::addressof(sync.reader_ready_event)); + } + + /* Verify that we can receive signal on each event (TryWaitAny) */ + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + /* Wait for writer to do the relevant work */ + os::WaitEvent(std::addressof(sync.writer_ready_event)); + AMS_ABORT_UNLESS(sync.writer_state == 5); + + /* Get the signaled holder. */ + auto *signaled = os::TryWaitAny(std::addressof(mw)); + AMS_ABORT_UNLESS(signaled == holders + i); + + /* Test all events. */ + for (size_t n = 0; n < util::size(sync.system_events); ++n) { + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == (i == n)); + os::ClearSystemEvent(sync.system_events + n); + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false); + } + + /* Let writer know we're done. */ + sync.reader_state = 5; + os::SignalEvent(std::addressof(sync.reader_ready_event)); + } + + /* Verify that we can receive signal on each event (TimedWaitAny 0) */ + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + /* Wait for writer to do the relevant work */ + os::WaitEvent(std::addressof(sync.writer_ready_event)); + AMS_ABORT_UNLESS(sync.writer_state == 6); + + /* Get the signaled holder. */ + auto *signaled = os::TimedWaitAny(std::addressof(mw), TimeSpan::FromMilliSeconds(0)); + AMS_ABORT_UNLESS(signaled == holders + i); + + /* Test all events. */ + for (size_t n = 0; n < util::size(sync.system_events); ++n) { + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == (i == n)); + os::ClearSystemEvent(sync.system_events + n); + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false); + } + + /* Let writer know we're done. */ + sync.reader_state = 6; + os::SignalEvent(std::addressof(sync.reader_ready_event)); + } + + /* Verify that we can receive signal on each event (TimedWaitAny 2) */ + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + /* Wait for writer to do the relevant work */ + os::WaitEvent(std::addressof(sync.writer_ready_event)); + AMS_ABORT_UNLESS(sync.writer_state == 7); + + /* Get the signaled holder. */ + auto *signaled = os::TimedWaitAny(std::addressof(mw), TimeSpan::FromMilliSeconds(2)); + AMS_ABORT_UNLESS(signaled == holders + i); + + /* Test all events. */ + for (size_t n = 0; n < util::size(sync.system_events); ++n) { + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == (i == n)); + os::ClearSystemEvent(sync.system_events + n); + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false); + } + + /* Let writer know we're done. */ + sync.reader_state = 7; + os::SignalEvent(std::addressof(sync.reader_ready_event)); + } + + /* Verify that we can receive signal on each event (True WaitAny) */ + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + /* Wait for writer to do the relevant work */ + os::WaitEvent(std::addressof(sync.writer_ready_event)); + AMS_ABORT_UNLESS(sync.writer_state == 8); + + /* Get the signaled holder. */ + auto *signaled = os::WaitAny(std::addressof(mw)); + AMS_ABORT_UNLESS(signaled == holders + i); + + /* Test all events. */ + for (size_t n = 0; n < util::size(sync.system_events); ++n) { + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == (i == n)); + os::ClearSystemEvent(sync.system_events + n); + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false); + } + + /* Let writer know we're done. */ + sync.reader_state = 8; + os::SignalEvent(std::addressof(sync.reader_ready_event)); + } + + /* Verify that we can receive wait-any signals without sync. */ + for (size_t i = 0; i < util::size(sync.system_events); ++i) { + auto *signaled = os::WaitAny(std::addressof(mw)); + AMS_ABORT_UNLESS(signaled != nullptr); + const size_t n = signaled - holders; + AMS_ABORT_UNLESS(n < util::size(sync.system_events)); + + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == true); + os::ClearSystemEvent(sync.system_events + n); + AMS_ABORT_UNLESS(os::TryWaitSystemEvent(sync.system_events + n) == false); + } + + AMS_ABORT_UNLESS(os::TryWaitAny(std::addressof(mw)) == nullptr); + + /* Let writer know we're done. */ + sync.reader_state = 9; + os::SignalEvent(std::addressof(sync.reader_ready_event)); + } + + } + + + void Main() { + printf("Doing OS Event tests!\n"); + { + /* Create the synchronization state. */ + InterThreadSync sync_state; + sync_state.reader_state = 0; + sync_state.writer_state = 0; + os::InitializeEvent(std::addressof(sync_state.writer_ready_event), false, os::EventClearMode_AutoClear); + os::InitializeEvent(std::addressof(sync_state.reader_ready_event), false, os::EventClearMode_AutoClear); + + R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(sync_state.system_event_as_manual_clear_event), os::EventClearMode_ManualClear, false)); + R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(sync_state.system_event_as_manual_clear_interprocess_event), os::EventClearMode_ManualClear, true)); + R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(sync_state.system_event_as_auto_clear_event), os::EventClearMode_AutoClear, false)); + R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(sync_state.system_event_as_auto_clear_interprocess_event), os::EventClearMode_AutoClear, true)); + + /* Ensure we clean up the sync-state when done. */ + ON_SCOPE_EXIT { + os::FinalizeEvent(std::addressof(sync_state.writer_ready_event)); + os::FinalizeEvent(std::addressof(sync_state.reader_ready_event)); + + os::DestroySystemEvent(std::addressof(sync_state.system_event_as_manual_clear_event)); + os::DestroySystemEvent(std::addressof(sync_state.system_event_as_manual_clear_interprocess_event)); + os::DestroySystemEvent(std::addressof(sync_state.system_event_as_auto_clear_event)); + os::DestroySystemEvent(std::addressof(sync_state.system_event_as_auto_clear_interprocess_event)); + }; + + /* Create the threads. */ + os::ThreadType reader_thread, writer_thread; + R_ABORT_UNLESS(os::CreateThread(std::addressof(reader_thread), TestReaderThread, std::addressof(sync_state), g_reader_thread_stack, sizeof(g_reader_thread_stack), os::DefaultThreadPriority)); + R_ABORT_UNLESS(os::CreateThread(std::addressof(writer_thread), TestWriterThread, std::addressof(sync_state), g_writer_thread_stack, sizeof(g_writer_thread_stack), os::DefaultThreadPriority)); + os::SetThreadNamePointer(std::addressof(reader_thread), "ReaderThread"); + os::SetThreadNamePointer(std::addressof(writer_thread), "WriterThread"); + + /* Start the threads. */ + os::StartThread(std::addressof(reader_thread)); + os::StartThread(std::addressof(writer_thread)); + + /* Wait for the threads to complete. */ + os::WaitThread(std::addressof(reader_thread)); + os::WaitThread(std::addressof(writer_thread)); + + /* Destroy the threads. */ + os::WaitThread(std::addressof(reader_thread)); + os::WaitThread(std::addressof(writer_thread)); + } + printf("All tests completed!\n"); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestOsEvents/unit_test.mk b/Source/Atmosphere-MTC-Unlock/tests/TestOsEvents/unit_test.mk new file mode 100644 index 00000000..ed1457ce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestOsEvents/unit_test.mk @@ -0,0 +1,155 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ifeq ($(ATMOSPHERE_BOARD),nx-hac-001) +export BOARD_TARGET_SUFFIX := .kip +else ifeq ($(ATMOSPHERE_BOARD),generic_windows) +export BOARD_TARGET_SUFFIX := .exe +else ifeq ($(ATMOSPHERE_BOARD),generic_linux) +export BOARD_TARGET_SUFFIX := +else ifeq ($(ATMOSPHERE_BOARD),generic_macos) +export BOARD_TARGET_SUFFIX := +else +export BOARD_TARGET_SUFFIX := $(TARGET) +endif + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(BOARD_TARGET) $(TARGET).elf + @for i in $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR); do [ -d $$i ] && rmdir $$i 2>/dev/null || true; done + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT)$(BOARD_TARGET_SUFFIX) + +%.kip : %.elf + +%.nsp : %.nso %.npdm + +%.nso: %.elf + + +#--------------------------------------------------------------------------------- +$(OUTPUT).elf: $(OFILES) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @echo linking $(notdir $@) + $(SILENTCMD)$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + $(SILENTCMD)$(NM) -CSn $@ > $(notdir $(OUTPUT).lst) + +$(OUTPUT).exe: $(OFILES) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @echo linking $(notdir $@) + $(SILENTCMD)$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + $(SILENTCMD)$(NM) -CSn $@ > $(notdir $*.lst) + + +ifeq ($(strip $(BOARD_TARGET_SUFFIX)),) +$(OUTPUT): $(OFILES) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @echo linking $(notdir $@) + $(SILENTCMD)$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + $(SILENTCMD)$(NM) -CSn $@ > $(notdir $@.lst) +endif + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestSocket/Makefile b/Source/Atmosphere-MTC-Unlock/tests/TestSocket/Makefile new file mode 100644 index 00000000..4f16374d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestSocket/Makefile @@ -0,0 +1,51 @@ +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)/unit_test.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)/unit_test.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, $(strip $2)release, $(strip $3), $(strip $4), \ + ATMOSPHERE_BUILD_SETTINGS="$(strip $5)" $(strip $6) \ +)) + +$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_debug, $(strip $2)debug, $(strip $3), $(strip $4), \ + ATMOSPHERE_BUILD_SETTINGS="$(strip $5) -DAMS_BUILD_FOR_DEBUGGING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 $(strip $6) \ +)) + +$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_audit, $(strip $2)audit, $(strip $3), $(strip $4), \ + ATMOSPHERE_BUILD_SETTINGS="$(strip $5) -DAMS_BUILD_FOR_AUDITING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 ATMOSPHERE_BUILD_FOR_AUDITING=1 $(strip $6) \ +)) + +endef + + +$(eval $(call ATMOSPHERE_ADD_TARGETS, nx, , nx-hac-001, arm-cortex-a57,,)) + +$(eval $(call ATMOSPHERE_ADD_TARGETS, win_x64, , generic_windows, generic_x64,,)) + +$(eval $(call ATMOSPHERE_ADD_TARGETS, linux_x64, , generic_linux, generic_x64,,)) +$(eval $(call ATMOSPHERE_ADD_TARGETS, linux_x64_clang, clang_, generic_linux, generic_x64,, ATMOSPHERE_COMPILER_NAME="clang")) +$(eval $(call ATMOSPHERE_ADD_TARGETS, linux_arm64_clang, clang_, generic_linux, generic_arm64,, ATMOSPHERE_COMPILER_NAME="clang")) + +$(eval $(call ATMOSPHERE_ADD_TARGETS, macos_x64, , generic_macos, generic_x64,,)) +$(eval $(call ATMOSPHERE_ADD_TARGETS, macos_arm64, , generic_macos, generic_arm64,,)) + +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/tests/TestSocket/source/test.cpp b/Source/Atmosphere-MTC-Unlock/tests/TestSocket/source/test.cpp new file mode 100644 index 00000000..b10c2438 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestSocket/source/test.cpp @@ -0,0 +1,145 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams { + + namespace { + + constinit u8 g_socket_config_memory[2_MB]; + + alignas(os::MemoryPageSize) constinit u8 g_server_thread_stack[16_KB]; + + constexpr const u8 TestMessage[0x10] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, + }; + + void TestServerThread(void *arg) { + os::EventType *server_ready_event = reinterpret_cast<os::EventType *>(arg); + + s32 listen_fd = socket::Socket(socket::Family::Af_Inet, socket::Type::Sock_Stream, socket::Protocol::IpProto_Ip); + AMS_ABORT_UNLESS(listen_fd >= 0); + printf("[Server]: Listen fd=%d\n", static_cast<int>(listen_fd)); + + socket::SockAddrIn s_addr = {}; + s_addr.sin_family = socket::Family::Af_Inet; + s_addr.sin_addr.s_addr = socket::InAddr_Any; + s_addr.sin_port = socket::InetHtons(23337); + + /* Bind. */ + const auto bind_res = socket::Bind(listen_fd, reinterpret_cast<socket::SockAddr *>(std::addressof(s_addr)), sizeof(s_addr)); + printf("[Server]: Bind=%d\n", static_cast<int>(bind_res)); + AMS_ABORT_UNLESS(bind_res == 0); + + /* Listen. */ + const auto listen_res = socket::Listen(listen_fd, 1); + printf("[Server]: Listen=%d\n", static_cast<int>(listen_res)); + AMS_ABORT_UNLESS(listen_res >= 0); + + printf("[Server]: Ready\n"); + os::SignalEvent(server_ready_event); + + /* Accept. */ + s32 conn_fd = socket::Accept(listen_fd, nullptr, nullptr); + AMS_ABORT_UNLESS(conn_fd >= 0); + printf("[Server]: Conn fd=%d\n", conn_fd); + + /* Receive. */ + u8 received[sizeof(TestMessage)] = {}; + AMS_ABORT_UNLESS(socket::Recv(conn_fd, received, sizeof(received), socket::MsgFlag::Msg_None) == sizeof(received)); + printf("[Server]: Received\n"); + + AMS_ABORT_UNLESS(std::memcmp(received, TestMessage, sizeof(TestMessage)) == 0); + + /* Calculate hash. */ + u8 hash[crypto::Sha256Generator::HashSize]; + crypto::GenerateSha256(hash, sizeof(hash), received, sizeof(received)); + + /* Send hash. */ + AMS_ABORT_UNLESS(socket::Send(conn_fd, hash, sizeof(hash), socket::MsgFlag::Msg_None) == sizeof(hash)); + printf("[Server]: Sent\n"); + + /* Close sockets. */ + AMS_ABORT_UNLESS(socket::Close(conn_fd) == 0); + AMS_ABORT_UNLESS(socket::Close(listen_fd) == 0); + printf("[Server]: Closed\n"); + } + + } + + void Main() { + auto cfg = socket::SystemConfigDefault(g_socket_config_memory, sizeof(g_socket_config_memory) / 2, sizeof(g_socket_config_memory) / 2); + R_ABORT_UNLESS(socket::Initialize(cfg)); + { + /* Set up for the server thread. */ + os::EventType server_ready_event; + os::InitializeEvent(std::addressof(server_ready_event), false, os::EventClearMode_AutoClear); + ON_SCOPE_EXIT { os::FinalizeEvent(std::addressof(server_ready_event)); }; + + /* Wait for the server thread to be ready */ + os::ThreadType server_thread; + R_ABORT_UNLESS(os::CreateThread(std::addressof(server_thread), TestServerThread, std::addressof(server_ready_event), g_server_thread_stack, sizeof(g_server_thread_stack), os::DefaultThreadPriority)); + os::SetThreadNamePointer(std::addressof(server_thread), "ServerThread"); + os::StartThread(std::addressof(server_thread)); + + /* Wait for the server thread to be ready. */ + os::WaitEvent(std::addressof(server_ready_event)); + + { + /* Create socket. */ + s32 conn_fd = socket::Socket(socket::Family::Af_Inet, socket::Type::Sock_Stream, socket::Protocol::IpProto_Ip); + AMS_ABORT_UNLESS(conn_fd >= 0); + printf("[Client]: Conn fd=%d\n", static_cast<int>(conn_fd)); + + socket::SockAddrIn s_addr = {}; + s_addr.sin_family = socket::Family::Af_Inet; + s_addr.sin_addr.s_addr = socket::InAddr_Loopback; + s_addr.sin_port = socket::InetHtons(23337); + + /* Connect. */ + const auto connect_res = socket::Connect(conn_fd, reinterpret_cast<socket::SockAddr *>(std::addressof(s_addr)), sizeof(s_addr)); + printf("[Client]: Connect=%d, last_err=%d\n", connect_res, static_cast<int>(socket::GetLastError())); + AMS_ABORT_UNLESS(connect_res == 0); + + /* Send test. */ + AMS_ABORT_UNLESS(socket::Send(conn_fd, TestMessage, sizeof(TestMessage), socket::MsgFlag::Msg_None) == sizeof(TestMessage)); + printf("[Client]: Sent\n"); + + /* Receive. */ + u8 received[crypto::Sha256Generator::HashSize] = {}; + AMS_ABORT_UNLESS(socket::Recv(conn_fd, received, sizeof(received), socket::MsgFlag::Msg_None) == sizeof(received)); + printf("[Client]: Received\n"); + + /* Calculate hash. */ + u8 hash[crypto::Sha256Generator::HashSize]; + crypto::GenerateSha256(hash, sizeof(hash), TestMessage, sizeof(TestMessage)); + + AMS_ABORT_UNLESS(std::memcmp(received, hash, sizeof(hash)) == 0); + + /* Close sockets. */ + AMS_ABORT_UNLESS(socket::Close(conn_fd) == 0); + printf("[Client]: Closed\n"); + } + + /* Wait for the server thread to complete. */ + os::WaitThread(std::addressof(server_thread)); + } + printf("Successfully performed socket test!\n"); + + socket::Finalize(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestSocket/unit_test.mk b/Source/Atmosphere-MTC-Unlock/tests/TestSocket/unit_test.mk new file mode 100644 index 00000000..ed1457ce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestSocket/unit_test.mk @@ -0,0 +1,155 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ifeq ($(ATMOSPHERE_BOARD),nx-hac-001) +export BOARD_TARGET_SUFFIX := .kip +else ifeq ($(ATMOSPHERE_BOARD),generic_windows) +export BOARD_TARGET_SUFFIX := .exe +else ifeq ($(ATMOSPHERE_BOARD),generic_linux) +export BOARD_TARGET_SUFFIX := +else ifeq ($(ATMOSPHERE_BOARD),generic_macos) +export BOARD_TARGET_SUFFIX := +else +export BOARD_TARGET_SUFFIX := $(TARGET) +endif + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(BOARD_TARGET) $(TARGET).elf + @for i in $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR); do [ -d $$i ] && rmdir $$i 2>/dev/null || true; done + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT)$(BOARD_TARGET_SUFFIX) + +%.kip : %.elf + +%.nsp : %.nso %.npdm + +%.nso: %.elf + + +#--------------------------------------------------------------------------------- +$(OUTPUT).elf: $(OFILES) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @echo linking $(notdir $@) + $(SILENTCMD)$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + $(SILENTCMD)$(NM) -CSn $@ > $(notdir $(OUTPUT).lst) + +$(OUTPUT).exe: $(OFILES) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @echo linking $(notdir $@) + $(SILENTCMD)$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + $(SILENTCMD)$(NM) -CSn $@ > $(notdir $*.lst) + + +ifeq ($(strip $(BOARD_TARGET_SUFFIX)),) +$(OUTPUT): $(OFILES) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @echo linking $(notdir $@) + $(SILENTCMD)$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + $(SILENTCMD)$(NM) -CSn $@ > $(notdir $@.lst) +endif + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestStack/Makefile b/Source/Atmosphere-MTC-Unlock/tests/TestStack/Makefile new file mode 100644 index 00000000..4f16374d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestStack/Makefile @@ -0,0 +1,51 @@ +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)/unit_test.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)/unit_test.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, $(strip $2)release, $(strip $3), $(strip $4), \ + ATMOSPHERE_BUILD_SETTINGS="$(strip $5)" $(strip $6) \ +)) + +$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_debug, $(strip $2)debug, $(strip $3), $(strip $4), \ + ATMOSPHERE_BUILD_SETTINGS="$(strip $5) -DAMS_BUILD_FOR_DEBUGGING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 $(strip $6) \ +)) + +$(eval $(call ATMOSPHERE_ADD_TARGET, $(strip $1)_audit, $(strip $2)audit, $(strip $3), $(strip $4), \ + ATMOSPHERE_BUILD_SETTINGS="$(strip $5) -DAMS_BUILD_FOR_AUDITING" ATMOSPHERE_BUILD_FOR_DEBUGGING=1 ATMOSPHERE_BUILD_FOR_AUDITING=1 $(strip $6) \ +)) + +endef + + +$(eval $(call ATMOSPHERE_ADD_TARGETS, nx, , nx-hac-001, arm-cortex-a57,,)) + +$(eval $(call ATMOSPHERE_ADD_TARGETS, win_x64, , generic_windows, generic_x64,,)) + +$(eval $(call ATMOSPHERE_ADD_TARGETS, linux_x64, , generic_linux, generic_x64,,)) +$(eval $(call ATMOSPHERE_ADD_TARGETS, linux_x64_clang, clang_, generic_linux, generic_x64,, ATMOSPHERE_COMPILER_NAME="clang")) +$(eval $(call ATMOSPHERE_ADD_TARGETS, linux_arm64_clang, clang_, generic_linux, generic_arm64,, ATMOSPHERE_COMPILER_NAME="clang")) + +$(eval $(call ATMOSPHERE_ADD_TARGETS, macos_x64, , generic_macos, generic_x64,,)) +$(eval $(call ATMOSPHERE_ADD_TARGETS, macos_arm64, , generic_macos, generic_arm64,,)) + +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/tests/TestStack/source/test.cpp b/Source/Atmosphere-MTC-Unlock/tests/TestStack/source/test.cpp new file mode 100644 index 00000000..7847bf36 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestStack/source/test.cpp @@ -0,0 +1,36 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +namespace ams { + + void Main() { + printf("Getting thread stack\n"); + { + uintptr_t stack = 0; + size_t stack_size = 0; + os::GetCurrentStackInfo(std::addressof(stack), std::addressof(stack_size)); + + printf("Got thread stack: %p-%p\n", reinterpret_cast<void *>(stack), reinterpret_cast<void *>(stack + stack_size)); + + const uintptr_t stack_var_addr = reinterpret_cast<uintptr_t>(std::addressof(stack)); + printf("&stack variable address: %p\n", reinterpret_cast<void *>(stack_var_addr)); + AMS_ASSERT(stack <= stack_var_addr && stack_var_addr < stack + stack_size); + } + printf("All tests completed!\n"); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestStack/unit_test.mk b/Source/Atmosphere-MTC-Unlock/tests/TestStack/unit_test.mk new file mode 100644 index 00000000..ed1457ce --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestStack/unit_test.mk @@ -0,0 +1,155 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST))) +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +ifeq ($(ATMOSPHERE_BOARD),nx-hac-001) +export BOARD_TARGET_SUFFIX := .kip +else ifeq ($(ATMOSPHERE_BOARD),generic_windows) +export BOARD_TARGET_SUFFIX := .exe +else ifeq ($(ATMOSPHERE_BOARD),generic_linux) +export BOARD_TARGET_SUFFIX := +else ifeq ($(ATMOSPHERE_BOARD),generic_macos) +export BOARD_TARGET_SUFFIX := +else +export BOARD_TARGET_SUFFIX := $(TARGET) +endif + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(__RECURSIVE__),1) +#--------------------------------------------------------------------------------- + +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(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 := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +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: clean all check_lib + +#--------------------------------------------------------------------------------- +all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \ + DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \ + --no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \ + -f $(THIS_MAKEFILE) + +$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib + @$(SILENTCMD)echo "Checked library." + +check_lib: + @$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk + +$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR): + @[ -d $@ ] || mkdir -p $@ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(BOARD_TARGET) $(TARGET).elf + @for i in $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR); do [ -d $$i ] && rmdir $$i 2>/dev/null || true; done + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT)$(BOARD_TARGET_SUFFIX) + +%.kip : %.elf + +%.nsp : %.nso %.npdm + +%.nso: %.elf + + +#--------------------------------------------------------------------------------- +$(OUTPUT).elf: $(OFILES) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @echo linking $(notdir $@) + $(SILENTCMD)$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + $(SILENTCMD)$(NM) -CSn $@ > $(notdir $(OUTPUT).lst) + +$(OUTPUT).exe: $(OFILES) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @echo linking $(notdir $@) + $(SILENTCMD)$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + $(SILENTCMD)$(NM) -CSn $@ > $(notdir $*.lst) + + +ifeq ($(strip $(BOARD_TARGET_SUFFIX)),) +$(OUTPUT): $(OFILES) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a + @echo linking $(notdir $@) + $(SILENTCMD)$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + $(SILENTCMD)$(NM) -CSn $@ > $(notdir $@.lst) +endif + +%.npdm : %.npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestSvc/Makefile b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/Makefile new file mode 100644 index 00000000..bf1bef94 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/Makefile @@ -0,0 +1,116 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(call FIND_SOURCE_FILES,$(SOURCES),c) +CPPFILES := $(call FIND_SOURCE_FILES,$(SOURCES),cpp) +SFILES := $(call FIND_SOURCE_FILES,$(SOURCES),s) + +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +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).kip $(TARGET).elf + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT).kip $(OUTPUT).nsp + +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm + +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).kip : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OUTPUT).npdm : $(OUTPUT).npdm.json + @echo built ... $< $@ + @npdmtool $< $@ + @echo built ... $(notdir $@) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestSvc/TestSvc.json b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/TestSvc.json new file mode 100644 index 00000000..795e4db5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/TestSvc.json @@ -0,0 +1,128 @@ +{ + "name": "TestSvc", + "title_id": "0x5555555555555555", + "main_thread_stack_size": "0x8000", + "main_thread_priority": 28, + "default_cpu_id": 3, + "process_category": 1, + "use_secure_memory": true, + "immortal": true, + "kernel_capabilities": [ + { + "type": "handle_table_size", + "value": 0 + }, + { + "type": "syscalls", + "value": { + "svcUnknown00": "0x00", + "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", + "svcFlushEntireDataCache": "0x2A", + "svcFlushDataCache": "0x2B", + "svcMapPhysicalMemory": "0x2C", + "svcUnmapPhysicalMemory": "0x2D", + "svcGetDebugFutureThreadInfo": "0x2E", + "svcGetLastThreadInfo": "0x2F", + "svcGetResourceLimitLimitValue": "0x30", + "svcGetResourceLimitCurrentValue": "0x31", + "svcSetThreadActivity": "0x32", + "svcGetThreadContext3": "0x33", + "svcWaitForAddress": "0x34", + "svcSignalToAddress": "0x35", + "svcSynchronizePreemptionState": "0x36", + "svcGetResourceLimitPeakValue": "0x37", + "svcUnknown38": "0x38", + "svcUnknown39": "0x39", + "svcUnknown3a": "0x3A", + "svcUnknown3b": "0x3B", + "svcKernelDebug": "0x3C", + "svcChangeKernelTraceState": "0x3D", + "svcUnknown3e": "0x3E", + "svcUnknown3f": "0x3F", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcUnknown46": "0x46", + "svcUnknown47": "0x47", + "svcMapPhysicalMemoryUnsafe": "0x48", + "svcUnmapPhysicalMemoryUnsafe": "0x49", + "svcSetUnsafeLimit": "0x4A", + "svcCreateCodeMemory": "0x4B", + "svcControlCodeMemory": "0x4C", + "svcSleepSystem": "0x4D", + "svcReadWriteRegister": "0x4E", + "svcSetProcessActivity": "0x4F", + "svcCreateSharedMemory": "0x50", + "svcMapTransferMemory": "0x51", + "svcUnmapTransferMemory": "0x52", + "svcQueryIoMapping": "0x55", + "svcDebugActiveProcess": "0x60", + "svcBreakDebugProcess": "0x61", + "svcTerminateDebugProcess": "0x62", + "svcGetDebugEvent": "0x63", + "svcContinueDebugEvent": "0x64", + "svcGetProcessList": "0x65", + "svcGetThreadList": "0x66", + "svcGetDebugThreadContext": "0x67", + "svcSetDebugThreadContext": "0x68", + "svcQueryDebugProcessMemory": "0x69", + "svcReadDebugProcessMemory": "0x6A", + "svcWriteDebugProcessMemory": "0x6B", + "svcSetHardwareBreakPoint": "0x6C", + "svcGetDebugThreadParam": "0x6D", + "svcGetSystemInfo": "0x6F", + "svcConnectToPort": "0x72", + "svcSetProcessMemoryPermission": "0x73", + "svcMapProcessMemory": "0x74", + "svcUnmapProcessMemory": "0x75", + "svcQueryProcessMemory": "0x76", + "svcMapProcessCodeMemory": "0x77", + "svcUnmapProcessCodeMemory": "0x78", + "svcCallSecureMonitor": "0x7F" + } + } + ] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestSvc/TestSvc.npdm.json b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/TestSvc.npdm.json new file mode 100644 index 00000000..9592ba60 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/TestSvc.npdm.json @@ -0,0 +1,147 @@ +{ + "name": "TestSvc", + "title_id": "0x5555555555555555", + "title_id_range_min": "0x5555555555555555", + "title_id_range_max": "0x5555555555555555", + "main_thread_stack_size": "0x8000", + "main_thread_priority": 28, + "default_cpu_id": 3, + "process_category": 0, + "is_retail": true, + "pool_partition": 2, + "is_64_bit": true, + "address_space_type": 3, + "disable_device_address_space_merge": true, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_access": ["*"], + "service_host": ["*"], + "kernel_capabilities": [ + { + "type": "kernel_flags", + "value": { + "highest_thread_priority": 63, + "lowest_thread_priority": 16, + "lowest_cpu_id": 0, + "highest_cpu_id": 3 + } + }, + { + "type": "handle_table_size", + "value": 0 + }, + { + "type": "syscalls", + "value": { + "svcUnknown00": "0x00", + "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", + "svcFlushEntireDataCache": "0x2A", + "svcFlushDataCache": "0x2B", + "svcMapPhysicalMemory": "0x2C", + "svcUnmapPhysicalMemory": "0x2D", + "svcGetDebugFutureThreadInfo": "0x2E", + "svcGetLastThreadInfo": "0x2F", + "svcGetResourceLimitLimitValue": "0x30", + "svcGetResourceLimitCurrentValue": "0x31", + "svcSetThreadActivity": "0x32", + "svcGetThreadContext3": "0x33", + "svcWaitForAddress": "0x34", + "svcSignalToAddress": "0x35", + "svcSynchronizePreemptionState": "0x36", + "svcGetResourceLimitPeakValue": "0x37", + "svcUnknown38": "0x38", + "svcUnknown39": "0x39", + "svcUnknown3a": "0x3A", + "svcUnknown3b": "0x3B", + "svcKernelDebug": "0x3C", + "svcChangeKernelTraceState": "0x3D", + "svcUnknown3e": "0x3E", + "svcUnknown3f": "0x3F", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcUnknown46": "0x46", + "svcUnknown47": "0x47", + "svcMapPhysicalMemoryUnsafe": "0x48", + "svcUnmapPhysicalMemoryUnsafe": "0x49", + "svcSetUnsafeLimit": "0x4A", + "svcCreateCodeMemory": "0x4B", + "svcControlCodeMemory": "0x4C", + "svcSleepSystem": "0x4D", + "svcReadWriteRegister": "0x4E", + "svcSetProcessActivity": "0x4F", + "svcCreateSharedMemory": "0x50", + "svcMapTransferMemory": "0x51", + "svcUnmapTransferMemory": "0x52", + "svcQueryIoMapping": "0x55", + "svcDebugActiveProcess": "0x60", + "svcBreakDebugProcess": "0x61", + "svcTerminateDebugProcess": "0x62", + "svcGetDebugEvent": "0x63", + "svcContinueDebugEvent": "0x64", + "svcGetProcessList": "0x65", + "svcGetThreadList": "0x66", + "svcGetDebugThreadContext": "0x67", + "svcSetDebugThreadContext": "0x68", + "svcQueryDebugProcessMemory": "0x69", + "svcReadDebugProcessMemory": "0x6A", + "svcWriteDebugProcessMemory": "0x6B", + "svcSetHardwareBreakPoint": "0x6C", + "svcGetDebugThreadParam": "0x6D", + "svcGetSystemInfo": "0x6F", + "svcConnectToPort": "0x72", + "svcSetProcessMemoryPermission": "0x73", + "svcMapProcessMemory": "0x74", + "svcUnmapProcessMemory": "0x75", + "svcQueryProcessMemory": "0x76", + "svcMapProcessCodeMemory": "0x77", + "svcUnmapProcessCodeMemory": "0x78", + "svcCallSecureMonitor": "0x7F" + } + } + ] +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/doctest.h b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/doctest.h new file mode 100644 index 00000000..32c9e608 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/doctest.h @@ -0,0 +1,6583 @@ +// ====================================================================== lgtm [cpp/missing-header-guard] +// == DO NOT MODIFY THIS FILE BY HAND - IT IS AUTO GENERATED BY CMAKE! == +// ====================================================================== +// +// doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD +// +// Copyright (c) 2016-2021 Viktor Kirilov +// +// Distributed under the MIT Software License +// See accompanying file LICENSE.txt or copy at +// https://opensource.org/licenses/MIT +// +// The documentation can be found at the library's page: +// https://github.com/onqtam/doctest/blob/master/doc/markdown/readme.md +// +// ================================================================================================= +// ================================================================================================= +// ================================================================================================= +// +// The library is heavily influenced by Catch - https://github.com/catchorg/Catch2 +// which uses the Boost Software License - Version 1.0 +// see here - https://github.com/catchorg/Catch2/blob/master/LICENSE.txt +// +// The concept of subcases (sections in Catch) and expression decomposition are from there. +// Some parts of the code are taken directly: +// - stringification - the detection of "ostream& operator<<(ostream&, const T&)" and StringMaker<> +// - the Approx() helper class for floating point comparison +// - colors in the console +// - breaking into a debugger +// - signal / SEH handling +// - timer +// - XmlWriter class - thanks to Phil Nash for allowing the direct reuse (AKA copy/paste) +// +// The expression decomposing templates are taken from lest - https://github.com/martinmoene/lest +// which uses the Boost Software License - Version 1.0 +// see here - https://github.com/martinmoene/lest/blob/master/LICENSE.txt +// +// ================================================================================================= +// ================================================================================================= +// ================================================================================================= + +#ifndef DOCTEST_LIBRARY_INCLUDED +#define DOCTEST_LIBRARY_INCLUDED + +// ================================================================================================= +// == VERSION ====================================================================================== +// ================================================================================================= + +#define DOCTEST_VERSION_MAJOR 2 +#define DOCTEST_VERSION_MINOR 4 +#define DOCTEST_VERSION_PATCH 6 +#define DOCTEST_VERSION_STR "2.4.6" + +#define DOCTEST_VERSION \ + (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH) + +// ================================================================================================= +// == COMPILER VERSION ============================================================================= +// ================================================================================================= + +// ideas for the version stuff are taken from here: https://github.com/cxxstuff/cxx_detect + +#define DOCTEST_COMPILER(MAJOR, MINOR, PATCH) ((MAJOR)*10000000 + (MINOR)*100000 + (PATCH)) + +// GCC/Clang and GCC/MSVC are mutually exclusive, but Clang/MSVC are not because of clang-cl... +#if defined(_MSC_VER) && defined(_MSC_FULL_VER) +#if _MSC_VER == _MSC_FULL_VER / 10000 +#define DOCTEST_MSVC DOCTEST_COMPILER(_MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 10000) +#else // MSVC +#define DOCTEST_MSVC \ + DOCTEST_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 100, _MSC_FULL_VER % 100000) +#endif // MSVC +#endif // MSVC +#if defined(__clang__) && defined(__clang_minor__) +#define DOCTEST_CLANG DOCTEST_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__) +#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && \ + !defined(__INTEL_COMPILER) +#define DOCTEST_GCC DOCTEST_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#endif // GCC + +#ifndef DOCTEST_MSVC +#define DOCTEST_MSVC 0 +#endif // DOCTEST_MSVC +#ifndef DOCTEST_CLANG +#define DOCTEST_CLANG 0 +#endif // DOCTEST_CLANG +#ifndef DOCTEST_GCC +#define DOCTEST_GCC 0 +#endif // DOCTEST_GCC + +// ================================================================================================= +// == COMPILER WARNINGS HELPERS ==================================================================== +// ================================================================================================= + +#if DOCTEST_CLANG +#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) +#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH _Pragma("clang diagnostic push") +#define DOCTEST_CLANG_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(clang diagnostic ignored w) +#define DOCTEST_CLANG_SUPPRESS_WARNING_POP _Pragma("clang diagnostic pop") +#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING(w) +#else // DOCTEST_CLANG +#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +#define DOCTEST_CLANG_SUPPRESS_WARNING(w) +#define DOCTEST_CLANG_SUPPRESS_WARNING_POP +#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_CLANG + +#if DOCTEST_GCC +#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) +#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH _Pragma("GCC diagnostic push") +#define DOCTEST_GCC_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(GCC diagnostic ignored w) +#define DOCTEST_GCC_SUPPRESS_WARNING_POP _Pragma("GCC diagnostic pop") +#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING(w) +#else // DOCTEST_GCC +#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH +#define DOCTEST_GCC_SUPPRESS_WARNING(w) +#define DOCTEST_GCC_SUPPRESS_WARNING_POP +#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_GCC + +#if DOCTEST_MSVC +#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH __pragma(warning(push)) +#define DOCTEST_MSVC_SUPPRESS_WARNING(w) __pragma(warning(disable : w)) +#define DOCTEST_MSVC_SUPPRESS_WARNING_POP __pragma(warning(pop)) +#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH DOCTEST_MSVC_SUPPRESS_WARNING(w) +#else // DOCTEST_MSVC +#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +#define DOCTEST_MSVC_SUPPRESS_WARNING(w) +#define DOCTEST_MSVC_SUPPRESS_WARNING_POP +#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_MSVC + +// ================================================================================================= +// == COMPILER WARNINGS ============================================================================ +// ================================================================================================= + +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wnon-virtual-dtor") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wdeprecated") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") + +DOCTEST_GCC_SUPPRESS_WARNING_PUSH +DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") +DOCTEST_GCC_SUPPRESS_WARNING("-Wctor-dtor-privacy") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") +DOCTEST_GCC_SUPPRESS_WARNING("-Wnon-virtual-dtor") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") +DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") +DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-promo") + +DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration +DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression +DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated +DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant +DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding +DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe +// static analysis +DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept' +DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable +DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ... +DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtr... +DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' + +// 4548 - expression before comma has no effect; expected expression with side - effect +// 4265 - class has virtual functions, but destructor is not virtual +// 4986 - exception specification does not match previous declaration +// 4350 - behavior change: 'member1' called instead of 'member2' +// 4668 - 'x' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' +// 4365 - conversion from 'int' to 'unsigned long', signed/unsigned mismatch +// 4774 - format string expected in argument 'x' is not a string literal +// 4820 - padding in structs + +// only 4 should be disabled globally: +// - 4514 # unreferenced inline function has been removed +// - 4571 # SEH related +// - 4710 # function not inlined +// - 4711 # function 'x' selected for automatic inline expansion + +#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ + DOCTEST_MSVC_SUPPRESS_WARNING(4548) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4265) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4986) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4350) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4668) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4365) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4774) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4820) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4625) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4626) \ + DOCTEST_MSVC_SUPPRESS_WARNING(5027) \ + DOCTEST_MSVC_SUPPRESS_WARNING(5026) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4623) \ + DOCTEST_MSVC_SUPPRESS_WARNING(5039) \ + DOCTEST_MSVC_SUPPRESS_WARNING(5045) \ + DOCTEST_MSVC_SUPPRESS_WARNING(5105) + +#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP + +// ================================================================================================= +// == FEATURE DETECTION ============================================================================ +// ================================================================================================= + +// general compiler feature support table: https://en.cppreference.com/w/cpp/compiler_support +// MSVC C++11 feature support table: https://msdn.microsoft.com/en-us/library/hh567368.aspx +// GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html +// MSVC version table: +// https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering +// MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019) +// MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017) +// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) +// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013) +// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012) +// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010) +// MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) +// MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) + +#if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH) +#define DOCTEST_CONFIG_WINDOWS_SEH +#endif // MSVC +#if defined(DOCTEST_CONFIG_NO_WINDOWS_SEH) && defined(DOCTEST_CONFIG_WINDOWS_SEH) +#undef DOCTEST_CONFIG_WINDOWS_SEH +#endif // DOCTEST_CONFIG_NO_WINDOWS_SEH + +#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && \ + !defined(__EMSCRIPTEN__) +#define DOCTEST_CONFIG_POSIX_SIGNALS +#endif // _WIN32 +#if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS) +#undef DOCTEST_CONFIG_POSIX_SIGNALS +#endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) +#define DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // no exceptions +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +#define DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#if defined(DOCTEST_CONFIG_NO_EXCEPTIONS) && !defined(DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS) +#define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS && !DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS + +#if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_CONFIG_IMPLEMENT) +#define DOCTEST_CONFIG_IMPLEMENT +#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +#if defined(_WIN32) || defined(__CYGWIN__) +#if DOCTEST_MSVC +#define DOCTEST_SYMBOL_EXPORT __declspec(dllexport) +#define DOCTEST_SYMBOL_IMPORT __declspec(dllimport) +#else // MSVC +#define DOCTEST_SYMBOL_EXPORT __attribute__((dllexport)) +#define DOCTEST_SYMBOL_IMPORT __attribute__((dllimport)) +#endif // MSVC +#else // _WIN32 +#define DOCTEST_SYMBOL_EXPORT __attribute__((visibility("default"))) +#define DOCTEST_SYMBOL_IMPORT +#endif // _WIN32 + +#ifdef DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL +#ifdef DOCTEST_CONFIG_IMPLEMENT +#define DOCTEST_INTERFACE DOCTEST_SYMBOL_EXPORT +#else // DOCTEST_CONFIG_IMPLEMENT +#define DOCTEST_INTERFACE DOCTEST_SYMBOL_IMPORT +#endif // DOCTEST_CONFIG_IMPLEMENT +#else // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL +#define DOCTEST_INTERFACE +#endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL + +#define DOCTEST_EMPTY + +#if DOCTEST_MSVC +#define DOCTEST_NOINLINE __declspec(noinline) +#define DOCTEST_UNUSED +#define DOCTEST_ALIGNMENT(x) +#elif DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 5, 0) +#define DOCTEST_NOINLINE +#define DOCTEST_UNUSED +#define DOCTEST_ALIGNMENT(x) +#else +#define DOCTEST_NOINLINE __attribute__((noinline)) +#define DOCTEST_UNUSED __attribute__((unused)) +#define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x))) +#endif + +#ifndef DOCTEST_NORETURN +#define DOCTEST_NORETURN [[noreturn]] +#endif // DOCTEST_NORETURN + +#ifndef DOCTEST_NOEXCEPT +#define DOCTEST_NOEXCEPT noexcept +#endif // DOCTEST_NOEXCEPT + +// ================================================================================================= +// == FEATURE DETECTION END ======================================================================== +// ================================================================================================= + +// internal macros for string concatenation and anonymous variable name generation +#define DOCTEST_CAT_IMPL(s1, s2) s1##s2 +#define DOCTEST_CAT(s1, s2) DOCTEST_CAT_IMPL(s1, s2) +#ifdef __COUNTER__ // not standard and may be missing for some compilers +#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __COUNTER__) +#else // __COUNTER__ +#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__) +#endif // __COUNTER__ + +#define DOCTEST_TOSTR(x) #x + +#ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE +#define DOCTEST_REF_WRAP(x) x& +#else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE +#define DOCTEST_REF_WRAP(x) x +#endif // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE + +// not using __APPLE__ because... this is how Catch does it +#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED +#define DOCTEST_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#define DOCTEST_PLATFORM_IPHONE +#elif defined(_WIN32) +#define DOCTEST_PLATFORM_WINDOWS +#else // DOCTEST_PLATFORM +#define DOCTEST_PLATFORM_LINUX +#endif // DOCTEST_PLATFORM + +#define DOCTEST_GLOBAL_NO_WARNINGS(var) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-variable") \ + static const int var DOCTEST_UNUSED // NOLINT(fuchsia-statically-constructed-objects,cert-err58-cpp) +#define DOCTEST_GLOBAL_NO_WARNINGS_END() DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#ifndef DOCTEST_BREAK_INTO_DEBUGGER +// should probably take a look at https://github.com/scottt/debugbreak +#ifdef DOCTEST_PLATFORM_LINUX +#if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) +// Break at the location of the failing check if possible +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT (hicpp-no-assembler) +#else +#include <signal.h> +#define DOCTEST_BREAK_INTO_DEBUGGER() raise(SIGTRAP) +#endif +#elif defined(DOCTEST_PLATFORM_MAC) +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(__i386) +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT (hicpp-no-assembler) +#else +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); // NOLINT (hicpp-no-assembler) +#endif +#elif DOCTEST_MSVC +#define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak() +#elif defined(__MINGW32__) +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wredundant-decls") +extern "C" __declspec(dllimport) void __stdcall DebugBreak(); +DOCTEST_GCC_SUPPRESS_WARNING_POP +#define DOCTEST_BREAK_INTO_DEBUGGER() ::DebugBreak() +#else // linux +#define DOCTEST_BREAK_INTO_DEBUGGER() (static_cast<void>(0)) +#endif // linux +#endif // DOCTEST_BREAK_INTO_DEBUGGER + +// this is kept here for backwards compatibility since the config option was changed +#ifdef DOCTEST_CONFIG_USE_IOSFWD +#define DOCTEST_CONFIG_USE_STD_HEADERS +#endif // DOCTEST_CONFIG_USE_IOSFWD + +#ifdef DOCTEST_CONFIG_USE_STD_HEADERS +#ifndef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#define DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#include <iosfwd> +#include <cstddef> +#include <ostream> +#else // DOCTEST_CONFIG_USE_STD_HEADERS + +#if DOCTEST_CLANG +// to detect if libc++ is being used with clang (the _LIBCPP_VERSION identifier) +#include <ciso646> +#endif // clang + +#ifdef _LIBCPP_VERSION +#define DOCTEST_STD_NAMESPACE_BEGIN _LIBCPP_BEGIN_NAMESPACE_STD +#define DOCTEST_STD_NAMESPACE_END _LIBCPP_END_NAMESPACE_STD +#else // _LIBCPP_VERSION +#define DOCTEST_STD_NAMESPACE_BEGIN namespace std { +#define DOCTEST_STD_NAMESPACE_END } +#endif // _LIBCPP_VERSION + +// Forward declaring 'X' in namespace std is not permitted by the C++ Standard. +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643) + +DOCTEST_STD_NAMESPACE_BEGIN // NOLINT (cert-dcl58-cpp) +typedef decltype(nullptr) nullptr_t; +template <class charT> +struct char_traits; +template <> +struct char_traits<char>; +template <class charT, class traits> +class basic_ostream; +typedef basic_ostream<char, char_traits<char>> ostream; +template <class... Types> +class tuple; +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 +template <class _Ty> +class allocator; +template <class _Elem, class _Traits, class _Alloc> +class basic_string; +using string = basic_string<char, char_traits<char>, allocator<char>>; +#endif // VS 2019 +DOCTEST_STD_NAMESPACE_END + +DOCTEST_MSVC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_USE_STD_HEADERS + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#include <type_traits> +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + +namespace doctest { + +DOCTEST_INTERFACE extern bool is_running_in_test; + +// A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length +// of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for: +// - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128) +// - if small - capacity left before going on the heap - using the lowest 5 bits +// - if small - 2 bits are left unused - the second and third highest ones +// - if small - acts as a null terminator if strlen() is 23 (24 including the null terminator) +// and the "is small" bit remains "0" ("as well as the capacity left") so its OK +// Idea taken from this lecture about the string implementation of facebook/folly - fbstring +// https://www.youtube.com/watch?v=kPR8h4-qZdk +// TODO: +// - optimizations - like not deleting memory unnecessarily in operator= and etc. +// - resize/reserve/clear +// - substr +// - replace +// - back/front +// - iterator stuff +// - find & friends +// - push_back/pop_back +// - assign/insert/erase +// - relational operators as free functions - taking const char* as one of the params +class DOCTEST_INTERFACE String +{ + static const unsigned len = 24; //!OCLINT avoid private static members + static const unsigned last = len - 1; //!OCLINT avoid private static members + + struct view // len should be more than sizeof(view) - because of the final byte for flags + { + char* ptr; + unsigned size; + unsigned capacity; + }; + + union + { + char buf[len]; + view data; + }; + + bool isOnStack() const { return (buf[last] & 128) == 0; } + void setOnHeap(); + void setLast(unsigned in = last); + + void copy(const String& other); + +public: + String(); + ~String(); + + // cppcheck-suppress noExplicitConstructor + String(const char* in); + String(const char* in, unsigned in_size); + + String(const String& other); + String& operator=(const String& other); + + String& operator+=(const String& other); + String operator+(const String& other) const; + + String(String&& other); + String& operator=(String&& other); + + char operator[](unsigned i) const; + char& operator[](unsigned i); + + // the only functions I'm willing to leave in the interface - available for inlining + const char* c_str() const { return const_cast<String*>(this)->c_str(); } // NOLINT + char* c_str() { + if(isOnStack()) + return reinterpret_cast<char*>(buf); + return data.ptr; + } + + unsigned size() const; + unsigned capacity() const; + + int compare(const char* other, bool no_case = false) const; + int compare(const String& other, bool no_case = false) const; +}; + +DOCTEST_INTERFACE bool operator==(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator!=(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator<(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator>(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator<=(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator>=(const String& lhs, const String& rhs); + +DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in); + +namespace Color { + enum Enum + { + None = 0, + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White + }; + + DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, Color::Enum code); +} // namespace Color + +namespace assertType { + enum Enum + { + // macro traits + + is_warn = 1, + is_check = 2 * is_warn, + is_require = 2 * is_check, + + is_normal = 2 * is_require, + is_throws = 2 * is_normal, + is_throws_as = 2 * is_throws, + is_throws_with = 2 * is_throws_as, + is_nothrow = 2 * is_throws_with, + + is_false = 2 * is_nothrow, + is_unary = 2 * is_false, // not checked anywhere - used just to distinguish the types + + is_eq = 2 * is_unary, + is_ne = 2 * is_eq, + + is_lt = 2 * is_ne, + is_gt = 2 * is_lt, + + is_ge = 2 * is_gt, + is_le = 2 * is_ge, + + // macro types + + DT_WARN = is_normal | is_warn, + DT_CHECK = is_normal | is_check, + DT_REQUIRE = is_normal | is_require, + + DT_WARN_FALSE = is_normal | is_false | is_warn, + DT_CHECK_FALSE = is_normal | is_false | is_check, + DT_REQUIRE_FALSE = is_normal | is_false | is_require, + + DT_WARN_THROWS = is_throws | is_warn, + DT_CHECK_THROWS = is_throws | is_check, + DT_REQUIRE_THROWS = is_throws | is_require, + + DT_WARN_THROWS_AS = is_throws_as | is_warn, + DT_CHECK_THROWS_AS = is_throws_as | is_check, + DT_REQUIRE_THROWS_AS = is_throws_as | is_require, + + DT_WARN_THROWS_WITH = is_throws_with | is_warn, + DT_CHECK_THROWS_WITH = is_throws_with | is_check, + DT_REQUIRE_THROWS_WITH = is_throws_with | is_require, + + DT_WARN_THROWS_WITH_AS = is_throws_with | is_throws_as | is_warn, + DT_CHECK_THROWS_WITH_AS = is_throws_with | is_throws_as | is_check, + DT_REQUIRE_THROWS_WITH_AS = is_throws_with | is_throws_as | is_require, + + DT_WARN_NOTHROW = is_nothrow | is_warn, + DT_CHECK_NOTHROW = is_nothrow | is_check, + DT_REQUIRE_NOTHROW = is_nothrow | is_require, + + DT_WARN_EQ = is_normal | is_eq | is_warn, + DT_CHECK_EQ = is_normal | is_eq | is_check, + DT_REQUIRE_EQ = is_normal | is_eq | is_require, + + DT_WARN_NE = is_normal | is_ne | is_warn, + DT_CHECK_NE = is_normal | is_ne | is_check, + DT_REQUIRE_NE = is_normal | is_ne | is_require, + + DT_WARN_GT = is_normal | is_gt | is_warn, + DT_CHECK_GT = is_normal | is_gt | is_check, + DT_REQUIRE_GT = is_normal | is_gt | is_require, + + DT_WARN_LT = is_normal | is_lt | is_warn, + DT_CHECK_LT = is_normal | is_lt | is_check, + DT_REQUIRE_LT = is_normal | is_lt | is_require, + + DT_WARN_GE = is_normal | is_ge | is_warn, + DT_CHECK_GE = is_normal | is_ge | is_check, + DT_REQUIRE_GE = is_normal | is_ge | is_require, + + DT_WARN_LE = is_normal | is_le | is_warn, + DT_CHECK_LE = is_normal | is_le | is_check, + DT_REQUIRE_LE = is_normal | is_le | is_require, + + DT_WARN_UNARY = is_normal | is_unary | is_warn, + DT_CHECK_UNARY = is_normal | is_unary | is_check, + DT_REQUIRE_UNARY = is_normal | is_unary | is_require, + + DT_WARN_UNARY_FALSE = is_normal | is_false | is_unary | is_warn, + DT_CHECK_UNARY_FALSE = is_normal | is_false | is_unary | is_check, + DT_REQUIRE_UNARY_FALSE = is_normal | is_false | is_unary | is_require, + }; +} // namespace assertType + +DOCTEST_INTERFACE const char* assertString(assertType::Enum at); +DOCTEST_INTERFACE const char* failureString(assertType::Enum at); +DOCTEST_INTERFACE const char* skipPathFromFilename(const char* file); + +struct DOCTEST_INTERFACE TestCaseData +{ + String m_file; // the file in which the test was registered (using String - see #350) + unsigned m_line; // the line where the test was registered + const char* m_name; // name of the test case + const char* m_test_suite; // the test suite in which the test was added + const char* m_description; + bool m_skip; + bool m_no_breaks; + bool m_no_output; + bool m_may_fail; + bool m_should_fail; + int m_expected_failures; + double m_timeout; +}; + +struct DOCTEST_INTERFACE AssertData +{ + // common - for all asserts + const TestCaseData* m_test_case; + assertType::Enum m_at; + const char* m_file; + int m_line; + const char* m_expr; + bool m_failed; + + // exception-related - for all asserts + bool m_threw; + String m_exception; + + // for normal asserts + String m_decomp; + + // for specific exception-related asserts + bool m_threw_as; + const char* m_exception_type; + const char* m_exception_string; +}; + +struct DOCTEST_INTERFACE MessageData +{ + String m_string; + const char* m_file; + int m_line; + assertType::Enum m_severity; +}; + +struct DOCTEST_INTERFACE SubcaseSignature +{ + String m_name; + const char* m_file; + int m_line; + + bool operator<(const SubcaseSignature& other) const; +}; + +struct DOCTEST_INTERFACE IContextScope +{ + IContextScope(); + virtual ~IContextScope(); + virtual void stringify(std::ostream*) const = 0; +}; + +namespace detail { + struct DOCTEST_INTERFACE TestCase; +} // namespace detail + +std::ostream& get_cout(); +std::ostream& get_cerr(); + +struct ContextOptions //!OCLINT too many fields +{ + std::ostream* cout; // stdout stream - ::doctest::get_cout() by default + std::ostream* cerr; // stderr stream - ::doctest::get_cerr() by default + String binary_name; // the test binary name + + const detail::TestCase* currentTest = nullptr; + + // == parameters from the command line + String out; // output filename + String order_by; // how tests should be ordered + unsigned rand_seed; // the seed for rand ordering + + unsigned first; // the first (matching) test to be executed + unsigned last; // the last (matching) test to be executed + + int abort_after; // stop tests after this many failed assertions + int subcase_filter_levels; // apply the subcase filters for the first N levels + + bool success; // include successful assertions in output + bool case_sensitive; // if filtering should be case sensitive + bool exit; // if the program should be exited after the tests are ran/whatever + bool duration; // print the time duration of each test case + bool no_throw; // to skip exceptions-related assertion macros + bool no_exitcode; // if the framework should return 0 as the exitcode + bool no_run; // to not run the tests at all (can be done with an "*" exclude) + bool no_version; // to not print the version of the framework + bool no_colors; // if output to the console should be colorized + bool force_colors; // forces the use of colors even when a tty cannot be detected + bool no_breaks; // to not break into the debugger + bool no_skip; // don't skip test cases which are marked to be skipped + bool gnu_file_line; // if line numbers should be surrounded with :x: and not (x): + bool no_path_in_filenames; // if the path to files should be removed from the output + bool no_line_numbers; // if source code line numbers should be omitted from the output + bool no_debug_output; // no output in the debug console when a debugger is attached + bool no_skipped_summary; // don't print "skipped" in the summary !!! UNDOCUMENTED !!! + bool no_time_in_output; // omit any time/timestamps from output !!! UNDOCUMENTED !!! + + bool help; // to print the help + bool version; // to print the version + bool count; // if only the count of matching tests is to be retrieved + bool list_test_cases; // to list all tests matching the filters + bool list_test_suites; // to list all suites matching the filters + bool list_reporters; // lists all registered reporters +}; + +namespace detail { + template <bool CONDITION, typename TYPE = void> + struct enable_if + {}; + + template <typename TYPE> + struct enable_if<true, TYPE> + { typedef TYPE type; }; + + // clang-format off + template<class T> struct remove_reference { typedef T type; }; + template<class T> struct remove_reference<T&> { typedef T type; }; + template<class T> struct remove_reference<T&&> { typedef T type; }; + + template<typename T, typename U = T&&> U declval(int); + + template<typename T> T declval(long); + + template<typename T> auto declval() DOCTEST_NOEXCEPT -> decltype(declval<T>(0)) ; + + template<class T> struct is_lvalue_reference { const static bool value=false; }; + template<class T> struct is_lvalue_reference<T&> { const static bool value=true; }; + + template <class T> + inline T&& forward(typename remove_reference<T>::type& t) DOCTEST_NOEXCEPT + { + return static_cast<T&&>(t); + } + + template <class T> + inline T&& forward(typename remove_reference<T>::type&& t) DOCTEST_NOEXCEPT + { + static_assert(!is_lvalue_reference<T>::value, + "Can not forward an rvalue as an lvalue."); + return static_cast<T&&>(t); + } + + template<class T> struct remove_const { typedef T type; }; + template<class T> struct remove_const<const T> { typedef T type; }; +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template<class T> struct is_enum : public std::is_enum<T> {}; + template<class T> struct underlying_type : public std::underlying_type<T> {}; +#else + // Use compiler intrinsics + template<class T> struct is_enum { constexpr static bool value = __is_enum(T); }; + template<class T> struct underlying_type { typedef __underlying_type(T) type; }; +#endif + // clang-format on + + template <typename T> + struct deferred_false + // cppcheck-suppress unusedStructMember + { static const bool value = false; }; + + namespace has_insertion_operator_impl { + std::ostream &os(); + template<class T> + DOCTEST_REF_WRAP(T) val(); + + template<class, class = void> + struct check { + static constexpr bool value = false; + }; + + template<class T> + struct check<T, decltype(os() << val<T>(), void())> { + static constexpr bool value = true; + }; + } // namespace has_insertion_operator_impl + + template<class T> + using has_insertion_operator = has_insertion_operator_impl::check<const T>; + + DOCTEST_INTERFACE void my_memcpy(void* dest, const void* src, unsigned num); + + DOCTEST_INTERFACE std::ostream* getTlsOss(); // returns a thread-local ostringstream + DOCTEST_INTERFACE String getTlsOssResult(); + + template <bool C> + struct StringMakerBase + { + template <typename T> + static String convert(const DOCTEST_REF_WRAP(T)) { + return "{?}"; + } + }; + + template <> + struct StringMakerBase<true> + { + template <typename T> + static String convert(const DOCTEST_REF_WRAP(T) in) { + *getTlsOss() << in; + return getTlsOssResult(); + } + }; + + DOCTEST_INTERFACE String rawMemoryToString(const void* object, unsigned size); + + template <typename T> + String rawMemoryToString(const DOCTEST_REF_WRAP(T) object) { + return rawMemoryToString(&object, sizeof(object)); + } + + template <typename T> + const char* type_to_string() { + return "<>"; + } +} // namespace detail + +template <typename T> +struct StringMaker : public detail::StringMakerBase<detail::has_insertion_operator<T>::value> +{}; + +template <typename T> +struct StringMaker<T*> +{ + template <typename U> + static String convert(U* p) { + if(p) + return detail::rawMemoryToString(p); + return "NULL"; + } +}; + +template <typename R, typename C> +struct StringMaker<R C::*> +{ + static String convert(R C::*p) { + if(p) + return detail::rawMemoryToString(p); + return "NULL"; + } +}; + +template <typename T, typename detail::enable_if<!detail::is_enum<T>::value, bool>::type = true> +String toString(const DOCTEST_REF_WRAP(T) value) { + return StringMaker<T>::convert(value); +} + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +DOCTEST_INTERFACE String toString(char* in); +DOCTEST_INTERFACE String toString(const char* in); +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +DOCTEST_INTERFACE String toString(bool in); +DOCTEST_INTERFACE String toString(float in); +DOCTEST_INTERFACE String toString(double in); +DOCTEST_INTERFACE String toString(double long in); + +DOCTEST_INTERFACE String toString(char in); +DOCTEST_INTERFACE String toString(char signed in); +DOCTEST_INTERFACE String toString(char unsigned in); +DOCTEST_INTERFACE String toString(int short in); +DOCTEST_INTERFACE String toString(int short unsigned in); +DOCTEST_INTERFACE String toString(int in); +DOCTEST_INTERFACE String toString(int unsigned in); +DOCTEST_INTERFACE String toString(int long in); +DOCTEST_INTERFACE String toString(int long unsigned in); +DOCTEST_INTERFACE String toString(int long long in); +DOCTEST_INTERFACE String toString(int long long unsigned in); +DOCTEST_INTERFACE String toString(std::nullptr_t in); + +template <typename T, typename detail::enable_if<detail::is_enum<T>::value, bool>::type = true> +String toString(const DOCTEST_REF_WRAP(T) value) { + typedef typename detail::underlying_type<T>::type UT; + return toString(static_cast<UT>(value)); +} + +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 +DOCTEST_INTERFACE String toString(const std::string& in); +#endif // VS 2019 + +class DOCTEST_INTERFACE Approx +{ +public: + explicit Approx(double value); + + Approx operator()(double value) const; + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template <typename T> + explicit Approx(const T& value, + typename detail::enable_if<std::is_constructible<double, T>::value>::type* = + static_cast<T*>(nullptr)) { + *this = Approx(static_cast<double>(value)); + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + Approx& epsilon(double newEpsilon); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template <typename T> + typename detail::enable_if<std::is_constructible<double, T>::value, Approx&>::type epsilon( + const T& newEpsilon) { + m_epsilon = static_cast<double>(newEpsilon); + return *this; + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + Approx& scale(double newScale); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template <typename T> + typename detail::enable_if<std::is_constructible<double, T>::value, Approx&>::type scale( + const T& newScale) { + m_scale = static_cast<double>(newScale); + return *this; + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + // clang-format off + DOCTEST_INTERFACE friend bool operator==(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator==(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator!=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator!=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator<=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator<=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator>=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator>=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator< (double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator< (const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator> (double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator> (const Approx & lhs, double rhs); + + DOCTEST_INTERFACE friend String toString(const Approx& in); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#define DOCTEST_APPROX_PREFIX \ + template <typename T> friend typename detail::enable_if<std::is_constructible<double, T>::value, bool>::type + + DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(double(lhs), rhs); } + DOCTEST_APPROX_PREFIX operator==(const Approx& lhs, const T& rhs) { return operator==(rhs, lhs); } + DOCTEST_APPROX_PREFIX operator!=(const T& lhs, const Approx& rhs) { return !operator==(lhs, rhs); } + DOCTEST_APPROX_PREFIX operator!=(const Approx& lhs, const T& rhs) { return !operator==(rhs, lhs); } + DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) && lhs != rhs; } +#undef DOCTEST_APPROX_PREFIX +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + // clang-format on + +private: + double m_epsilon; + double m_scale; + double m_value; +}; + +DOCTEST_INTERFACE String toString(const Approx& in); + +DOCTEST_INTERFACE const ContextOptions* getContextOptions(); + +#if !defined(DOCTEST_CONFIG_DISABLE) + +namespace detail { + // clang-format off +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + template<class T> struct decay_array { typedef T type; }; + template<class T, unsigned N> struct decay_array<T[N]> { typedef T* type; }; + template<class T> struct decay_array<T[]> { typedef T* type; }; + + template<class T> struct not_char_pointer { enum { value = 1 }; }; + template<> struct not_char_pointer<char*> { enum { value = 0 }; }; + template<> struct not_char_pointer<const char*> { enum { value = 0 }; }; + + template<class T> struct can_use_op : public not_char_pointer<typename decay_array<T>::type> {}; +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + // clang-format on + + struct DOCTEST_INTERFACE TestFailureException + { + }; + + DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum at); + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + DOCTEST_NORETURN +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + DOCTEST_INTERFACE void throwException(); + + struct DOCTEST_INTERFACE Subcase + { + SubcaseSignature m_signature; + bool m_entered = false; + + Subcase(const String& name, const char* file, int line); + ~Subcase(); + + operator bool() const; + }; + + template <typename L, typename R> + String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op, + const DOCTEST_REF_WRAP(R) rhs) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + return toString(lhs) + op + toString(rhs); + } + +#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0) +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison") +#endif + +// This will check if there is any way it could find a operator like member or friend and uses it. +// If not it doesn't find the operator or if the operator at global scope is defined after +// this template, the template won't be instantiated due to SFINAE. Once the template is not +// instantiated it can look for global operator using normal conversions. +#define SFINAE_OP(ret,op) decltype(doctest::detail::declval<L>() op doctest::detail::declval<R>(),static_cast<ret>(0)) + +#define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \ + template <typename R> \ + DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(R&& rhs) { \ + bool res = op_macro(doctest::detail::forward<L>(lhs), doctest::detail::forward<R>(rhs)); \ + if(m_at & assertType::is_false) \ + res = !res; \ + if(!res || doctest::getContextOptions()->success) \ + return Result(res, stringifyBinaryExpr(lhs, op_str, rhs)); \ + return Result(res); \ + } + + // more checks could be added - like in Catch: + // https://github.com/catchorg/Catch2/pull/1480/files + // https://github.com/catchorg/Catch2/pull/1481/files +#define DOCTEST_FORBIT_EXPRESSION(rt, op) \ + template <typename R> \ + rt& operator op(const R&) { \ + static_assert(deferred_false<R>::value, \ + "Expression Too Complex Please Rewrite As Binary Comparison!"); \ + return *this; \ + } + + struct DOCTEST_INTERFACE Result + { + bool m_passed; + String m_decomp; + + Result(bool passed, const String& decomposition = String()); + + // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence + DOCTEST_FORBIT_EXPRESSION(Result, &) + DOCTEST_FORBIT_EXPRESSION(Result, ^) + DOCTEST_FORBIT_EXPRESSION(Result, |) + DOCTEST_FORBIT_EXPRESSION(Result, &&) + DOCTEST_FORBIT_EXPRESSION(Result, ||) + DOCTEST_FORBIT_EXPRESSION(Result, ==) + DOCTEST_FORBIT_EXPRESSION(Result, !=) + DOCTEST_FORBIT_EXPRESSION(Result, <) + DOCTEST_FORBIT_EXPRESSION(Result, >) + DOCTEST_FORBIT_EXPRESSION(Result, <=) + DOCTEST_FORBIT_EXPRESSION(Result, >=) + DOCTEST_FORBIT_EXPRESSION(Result, =) + DOCTEST_FORBIT_EXPRESSION(Result, +=) + DOCTEST_FORBIT_EXPRESSION(Result, -=) + DOCTEST_FORBIT_EXPRESSION(Result, *=) + DOCTEST_FORBIT_EXPRESSION(Result, /=) + DOCTEST_FORBIT_EXPRESSION(Result, %=) + DOCTEST_FORBIT_EXPRESSION(Result, <<=) + DOCTEST_FORBIT_EXPRESSION(Result, >>=) + DOCTEST_FORBIT_EXPRESSION(Result, &=) + DOCTEST_FORBIT_EXPRESSION(Result, ^=) + DOCTEST_FORBIT_EXPRESSION(Result, |=) + }; + +#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH + DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") + DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-compare") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wdouble-promotion") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wconversion") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wfloat-equal") + + DOCTEST_GCC_SUPPRESS_WARNING_PUSH + DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") + DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-compare") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wdouble-promotion") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wfloat-equal") + + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH + // https://stackoverflow.com/questions/39479163 what's the difference between 4018 and 4389 + DOCTEST_MSVC_SUPPRESS_WARNING(4388) // signed/unsigned mismatch + DOCTEST_MSVC_SUPPRESS_WARNING(4389) // 'operator' : signed/unsigned mismatch + DOCTEST_MSVC_SUPPRESS_WARNING(4018) // 'expression' : signed/unsigned mismatch + //DOCTEST_MSVC_SUPPRESS_WARNING(4805) // 'operation' : unsafe mix of type 'type' and type 'type' in operation + +#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + // clang-format off +#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_COMPARISON_RETURN_TYPE bool +#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_COMPARISON_RETURN_TYPE typename enable_if<can_use_op<L>::value || can_use_op<R>::value, bool>::type + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); } + inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); } + inline bool lt(const char* lhs, const char* rhs) { return String(lhs) < String(rhs); } + inline bool gt(const char* lhs, const char* rhs) { return String(lhs) > String(rhs); } + inline bool le(const char* lhs, const char* rhs) { return String(lhs) <= String(rhs); } + inline bool ge(const char* lhs, const char* rhs) { return String(lhs) >= String(rhs); } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + // clang-format on + +#define DOCTEST_RELATIONAL_OP(name, op) \ + template <typename L, typename R> \ + DOCTEST_COMPARISON_RETURN_TYPE name(const DOCTEST_REF_WRAP(L) lhs, \ + const DOCTEST_REF_WRAP(R) rhs) { \ + return lhs op rhs; \ + } + + DOCTEST_RELATIONAL_OP(eq, ==) + DOCTEST_RELATIONAL_OP(ne, !=) + DOCTEST_RELATIONAL_OP(lt, <) + DOCTEST_RELATIONAL_OP(gt, >) + DOCTEST_RELATIONAL_OP(le, <=) + DOCTEST_RELATIONAL_OP(ge, >=) + +#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_CMP_EQ(l, r) l == r +#define DOCTEST_CMP_NE(l, r) l != r +#define DOCTEST_CMP_GT(l, r) l > r +#define DOCTEST_CMP_LT(l, r) l < r +#define DOCTEST_CMP_GE(l, r) l >= r +#define DOCTEST_CMP_LE(l, r) l <= r +#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_CMP_EQ(l, r) eq(l, r) +#define DOCTEST_CMP_NE(l, r) ne(l, r) +#define DOCTEST_CMP_GT(l, r) gt(l, r) +#define DOCTEST_CMP_LT(l, r) lt(l, r) +#define DOCTEST_CMP_GE(l, r) ge(l, r) +#define DOCTEST_CMP_LE(l, r) le(l, r) +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + + template <typename L> + // cppcheck-suppress copyCtorAndEqOperator + struct Expression_lhs + { + L lhs; + assertType::Enum m_at; + + explicit Expression_lhs(L&& in, assertType::Enum at) + : lhs(doctest::detail::forward<L>(in)) + , m_at(at) {} + + DOCTEST_NOINLINE operator Result() { +// this is needed only foc MSVC 2015: +// https://ci.appveyor.com/project/onqtam/doctest/builds/38181202 +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4800) // 'int': forcing value to bool + bool res = static_cast<bool>(lhs); +DOCTEST_MSVC_SUPPRESS_WARNING_POP + if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional + res = !res; + + if(!res || getContextOptions()->success) + return Result(res, toString(lhs)); + return Result(res); + } + + /* This is required for user-defined conversions from Expression_lhs to L */ + //operator L() const { return lhs; } + operator L() const { return lhs; } + + // clang-format off + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>, " > ", DOCTEST_CMP_GT) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<, " < ", DOCTEST_CMP_LT) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>=, " >= ", DOCTEST_CMP_GE) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<=, " <= ", DOCTEST_CMP_LE) //!OCLINT bitwise operator in conditional + // clang-format on + + // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &&) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ||) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, =) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, +=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, -=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, *=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, /=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, %=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |=) + // these 2 are unfortunate because they should be allowed - they have higher precedence over the comparisons, but the + // ExpressionDecomposer class uses the left shift operator to capture the left operand of the binary expression... + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>) + }; + +#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_MSVC_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + +#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0) +DOCTEST_CLANG_SUPPRESS_WARNING_POP +#endif + + struct DOCTEST_INTERFACE ExpressionDecomposer + { + assertType::Enum m_at; + + ExpressionDecomposer(assertType::Enum at); + + // The right operator for capturing expressions is "<=" instead of "<<" (based on the operator precedence table) + // but then there will be warnings from GCC about "-Wparentheses" and since "_Pragma()" is problematic this will stay for now... + // https://github.com/catchorg/Catch2/issues/870 + // https://github.com/catchorg/Catch2/issues/565 + template <typename L> + Expression_lhs<L> operator<<(L &&operand) { + return Expression_lhs<L>(doctest::detail::forward<L>(operand), m_at); + } + }; + + struct DOCTEST_INTERFACE TestSuite + { + const char* m_test_suite; + const char* m_description; + bool m_skip; + bool m_no_breaks; + bool m_no_output; + bool m_may_fail; + bool m_should_fail; + int m_expected_failures; + double m_timeout; + + TestSuite& operator*(const char* in); + + template <typename T> + TestSuite& operator*(const T& in) { + in.fill(*this); + return *this; + } + }; + + typedef void (*funcType)(); + + struct DOCTEST_INTERFACE TestCase : public TestCaseData + { + funcType m_test; // a function pointer to the test case + + const char* m_type; // for templated test cases - gets appended to the real name + int m_template_id; // an ID used to distinguish between the different versions of a templated test case + String m_full_name; // contains the name (only for templated test cases!) + the template type + + TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, + const char* type = "", int template_id = -1); + + TestCase(const TestCase& other); + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function + TestCase& operator=(const TestCase& other); + DOCTEST_MSVC_SUPPRESS_WARNING_POP + + TestCase& operator*(const char* in); + + template <typename T> + TestCase& operator*(const T& in) { + in.fill(*this); + return *this; + } + + bool operator<(const TestCase& other) const; + }; + + // forward declarations of functions used by the macros + DOCTEST_INTERFACE int regTest(const TestCase& tc); + DOCTEST_INTERFACE int setTestSuite(const TestSuite& ts); + DOCTEST_INTERFACE bool isDebuggerActive(); + + template<typename T> + int instantiationHelper(const T&) { return 0; } + + namespace binaryAssertComparison { + enum Enum + { + eq = 0, + ne, + gt, + lt, + ge, + le + }; + } // namespace binaryAssertComparison + + // clang-format off + template <int, class L, class R> struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L), const DOCTEST_REF_WRAP(R) ) const { return false; } }; + +#define DOCTEST_BINARY_RELATIONAL_OP(n, op) \ + template <class L, class R> struct RelationalComparator<n, L, R> { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return op(lhs, rhs); } }; + // clang-format on + + DOCTEST_BINARY_RELATIONAL_OP(0, doctest::detail::eq) + DOCTEST_BINARY_RELATIONAL_OP(1, doctest::detail::ne) + DOCTEST_BINARY_RELATIONAL_OP(2, doctest::detail::gt) + DOCTEST_BINARY_RELATIONAL_OP(3, doctest::detail::lt) + DOCTEST_BINARY_RELATIONAL_OP(4, doctest::detail::ge) + DOCTEST_BINARY_RELATIONAL_OP(5, doctest::detail::le) + + struct DOCTEST_INTERFACE ResultBuilder : public AssertData + { + ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type = "", const char* exception_string = ""); + + void setResult(const Result& res); + + template <int comparison, typename L, typename R> + DOCTEST_NOINLINE void binary_assert(const DOCTEST_REF_WRAP(L) lhs, + const DOCTEST_REF_WRAP(R) rhs) { + m_failed = !RelationalComparator<comparison, L, R>()(lhs, rhs); + if(m_failed || getContextOptions()->success) + m_decomp = stringifyBinaryExpr(lhs, ", ", rhs); + } + + template <typename L> + DOCTEST_NOINLINE void unary_assert(const DOCTEST_REF_WRAP(L) val) { + m_failed = !val; + + if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional + m_failed = !m_failed; + + if(m_failed || getContextOptions()->success) + m_decomp = toString(val); + } + + void translateException(); + + bool log(); + void react() const; + }; + + namespace assertAction { + enum Enum + { + nothing = 0, + dbgbreak = 1, + shouldthrow = 2 + }; + } // namespace assertAction + + DOCTEST_INTERFACE void failed_out_of_a_testing_context(const AssertData& ad); + + DOCTEST_INTERFACE void decomp_assert(assertType::Enum at, const char* file, int line, + const char* expr, Result result); + +#define DOCTEST_ASSERT_OUT_OF_TESTS(decomp) \ + do { \ + if(!is_running_in_test) { \ + if(failed) { \ + ResultBuilder rb(at, file, line, expr); \ + rb.m_failed = failed; \ + rb.m_decomp = decomp; \ + failed_out_of_a_testing_context(rb); \ + if(isDebuggerActive() && !getContextOptions()->no_breaks) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + if(checkIfShouldThrow(at)) \ + throwException(); \ + } \ + return; \ + } \ + } while(false) + +#define DOCTEST_ASSERT_IN_TESTS(decomp) \ + ResultBuilder rb(at, file, line, expr); \ + rb.m_failed = failed; \ + if(rb.m_failed || getContextOptions()->success) \ + rb.m_decomp = decomp; \ + if(rb.log()) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + if(rb.m_failed && checkIfShouldThrow(at)) \ + throwException() + + template <int comparison, typename L, typename R> + DOCTEST_NOINLINE void binary_assert(assertType::Enum at, const char* file, int line, + const char* expr, const DOCTEST_REF_WRAP(L) lhs, + const DOCTEST_REF_WRAP(R) rhs) { + bool failed = !RelationalComparator<comparison, L, R>()(lhs, rhs); + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); + DOCTEST_ASSERT_IN_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); + } + + template <typename L> + DOCTEST_NOINLINE void unary_assert(assertType::Enum at, const char* file, int line, + const char* expr, const DOCTEST_REF_WRAP(L) val) { + bool failed = !val; + + if(at & assertType::is_false) //!OCLINT bitwise operator in conditional + failed = !failed; + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS(toString(val)); + DOCTEST_ASSERT_IN_TESTS(toString(val)); + } + + struct DOCTEST_INTERFACE IExceptionTranslator + { + IExceptionTranslator(); + virtual ~IExceptionTranslator(); + virtual bool translate(String&) const = 0; + }; + + template <typename T> + class ExceptionTranslator : public IExceptionTranslator //!OCLINT destructor of virtual class + { + public: + explicit ExceptionTranslator(String (*translateFunction)(T)) + : m_translateFunction(translateFunction) {} + + bool translate(String& res) const override { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + try { + throw; // lgtm [cpp/rethrow-no-exception] + // cppcheck-suppress catchExceptionByValue + } catch(T ex) { // NOLINT + res = m_translateFunction(ex); //!OCLINT parameter reassignment + return true; + } catch(...) {} //!OCLINT - empty catch statement +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + static_cast<void>(res); // to silence -Wunused-parameter + return false; + } + + private: + String (*m_translateFunction)(T); + }; + + DOCTEST_INTERFACE void registerExceptionTranslatorImpl(const IExceptionTranslator* et); + + template <bool C> + struct StringStreamBase + { + template <typename T> + static void convert(std::ostream* s, const T& in) { + *s << toString(in); + } + + // always treat char* as a string in this context - no matter + // if DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING is defined + static void convert(std::ostream* s, const char* in) { *s << String(in); } + }; + + template <> + struct StringStreamBase<true> + { + template <typename T> + static void convert(std::ostream* s, const T& in) { + *s << in; + } + }; + + template <typename T> + struct StringStream : public StringStreamBase<has_insertion_operator<T>::value> + {}; + + template <typename T> + void toStream(std::ostream* s, const T& value) { + StringStream<T>::convert(s, value); + } + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + DOCTEST_INTERFACE void toStream(std::ostream* s, char* in); + DOCTEST_INTERFACE void toStream(std::ostream* s, const char* in); +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + DOCTEST_INTERFACE void toStream(std::ostream* s, bool in); + DOCTEST_INTERFACE void toStream(std::ostream* s, float in); + DOCTEST_INTERFACE void toStream(std::ostream* s, double in); + DOCTEST_INTERFACE void toStream(std::ostream* s, double long in); + + DOCTEST_INTERFACE void toStream(std::ostream* s, char in); + DOCTEST_INTERFACE void toStream(std::ostream* s, char signed in); + DOCTEST_INTERFACE void toStream(std::ostream* s, char unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int short in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int short unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long long in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long long unsigned in); + + // ContextScope base class used to allow implementing methods of ContextScope + // that don't depend on the template parameter in doctest.cpp. + class DOCTEST_INTERFACE ContextScopeBase : public IContextScope { + protected: + ContextScopeBase(); + + void destroy(); + }; + + template <typename L> class ContextScope : public ContextScopeBase + { + const L lambda_; + + public: + explicit ContextScope(const L &lambda) : lambda_(lambda) {} + + ContextScope(ContextScope &&other) : lambda_(other.lambda_) {} + + void stringify(std::ostream* s) const override { lambda_(s); } + + ~ContextScope() override { destroy(); } + }; + + struct DOCTEST_INTERFACE MessageBuilder : public MessageData + { + std::ostream* m_stream; + + MessageBuilder(const char* file, int line, assertType::Enum severity); + MessageBuilder() = delete; + ~MessageBuilder(); + + // the preferred way of chaining parameters for stringification + template <typename T> + MessageBuilder& operator,(const T& in) { + toStream(m_stream, in); + return *this; + } + + // kept here just for backwards-compatibility - the comma operator should be preferred now + template <typename T> + MessageBuilder& operator<<(const T& in) { return this->operator,(in); } + + // the `,` operator has the lowest operator precedence - if `<<` is used by the user then + // the `,` operator will be called last which is not what we want and thus the `*` operator + // is used first (has higher operator precedence compared to `<<`) so that we guarantee that + // an operator of the MessageBuilder class is called first before the rest of the parameters + template <typename T> + MessageBuilder& operator*(const T& in) { return this->operator,(in); } + + bool log(); + void react(); + }; + + template <typename L> + ContextScope<L> MakeContextScope(const L &lambda) { + return ContextScope<L>(lambda); + } +} // namespace detail + +#define DOCTEST_DEFINE_DECORATOR(name, type, def) \ + struct name \ + { \ + type data; \ + name(type in = def) \ + : data(in) {} \ + void fill(detail::TestCase& state) const { state.DOCTEST_CAT(m_, name) = data; } \ + void fill(detail::TestSuite& state) const { state.DOCTEST_CAT(m_, name) = data; } \ + } + +DOCTEST_DEFINE_DECORATOR(test_suite, const char*, ""); +DOCTEST_DEFINE_DECORATOR(description, const char*, ""); +DOCTEST_DEFINE_DECORATOR(skip, bool, true); +DOCTEST_DEFINE_DECORATOR(no_breaks, bool, true); +DOCTEST_DEFINE_DECORATOR(no_output, bool, true); +DOCTEST_DEFINE_DECORATOR(timeout, double, 0); +DOCTEST_DEFINE_DECORATOR(may_fail, bool, true); +DOCTEST_DEFINE_DECORATOR(should_fail, bool, true); +DOCTEST_DEFINE_DECORATOR(expected_failures, int, 0); + +template <typename T> +int registerExceptionTranslator(String (*translateFunction)(T)) { + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") + static detail::ExceptionTranslator<T> exceptionTranslator(translateFunction); + DOCTEST_CLANG_SUPPRESS_WARNING_POP + detail::registerExceptionTranslatorImpl(&exceptionTranslator); + return 0; +} + +} // namespace doctest + +// in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro +// introduces an anonymous namespace in which getCurrentTestSuite gets overridden +namespace doctest_detail_test_suite_ns { +DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite(); +} // namespace doctest_detail_test_suite_ns + +namespace doctest { +#else // DOCTEST_CONFIG_DISABLE +template <typename T> +int registerExceptionTranslator(String (*)(T)) { + return 0; +} +#endif // DOCTEST_CONFIG_DISABLE + +namespace detail { + typedef void (*assert_handler)(const AssertData&); + struct ContextState; +} // namespace detail + +class DOCTEST_INTERFACE Context +{ + detail::ContextState* p; + + void parseArgs(int argc, const char* const* argv, bool withDefaults = false); + +public: + explicit Context(int argc = 0, const char* const* argv = nullptr); + + ~Context(); + + void applyCommandLine(int argc, const char* const* argv); + + void addFilter(const char* filter, const char* value); + void clearFilters(); + void setOption(const char* option, int value); + void setOption(const char* option, const char* value); + + bool shouldExit(); + + void setAsDefaultForAssertsOutOfTestCases(); + + void setAssertHandler(detail::assert_handler ah); + + int run(); +}; + +namespace TestCaseFailureReason { + enum Enum + { + None = 0, + AssertFailure = 1, // an assertion has failed in the test case + Exception = 2, // test case threw an exception + Crash = 4, // a crash... + TooManyFailedAsserts = 8, // the abort-after option + Timeout = 16, // see the timeout decorator + ShouldHaveFailedButDidnt = 32, // see the should_fail decorator + ShouldHaveFailedAndDid = 64, // see the should_fail decorator + DidntFailExactlyNumTimes = 128, // see the expected_failures decorator + FailedExactlyNumTimes = 256, // see the expected_failures decorator + CouldHaveFailedAndDid = 512 // see the may_fail decorator + }; +} // namespace TestCaseFailureReason + +struct DOCTEST_INTERFACE CurrentTestCaseStats +{ + int numAssertsCurrentTest; + int numAssertsFailedCurrentTest; + double seconds; + int failure_flags; // use TestCaseFailureReason::Enum +}; + +struct DOCTEST_INTERFACE TestCaseException +{ + String error_string; + bool is_crash; +}; + +struct DOCTEST_INTERFACE TestRunStats +{ + unsigned numTestCases; + unsigned numTestCasesPassingFilters; + unsigned numTestSuitesPassingFilters; + unsigned numTestCasesFailed; + int numAsserts; + int numAssertsFailed; +}; + +struct QueryData +{ + const TestRunStats* run_stats = nullptr; + const TestCaseData** data = nullptr; + unsigned num_data = 0; +}; + +struct DOCTEST_INTERFACE IReporter +{ + // The constructor has to accept "const ContextOptions&" as a single argument + // which has most of the options for the run + a pointer to the stdout stream + // Reporter(const ContextOptions& in) + + // called when a query should be reported (listing test cases, printing the version, etc.) + virtual void report_query(const QueryData&) = 0; + + // called when the whole test run starts + virtual void test_run_start() = 0; + // called when the whole test run ends (caching a pointer to the input doesn't make sense here) + virtual void test_run_end(const TestRunStats&) = 0; + + // called when a test case is started (safe to cache a pointer to the input) + virtual void test_case_start(const TestCaseData&) = 0; + // called when a test case is reentered because of unfinished subcases (safe to cache a pointer to the input) + virtual void test_case_reenter(const TestCaseData&) = 0; + // called when a test case has ended + virtual void test_case_end(const CurrentTestCaseStats&) = 0; + + // called when an exception is thrown from the test case (or it crashes) + virtual void test_case_exception(const TestCaseException&) = 0; + + // called whenever a subcase is entered (don't cache pointers to the input) + virtual void subcase_start(const SubcaseSignature&) = 0; + // called whenever a subcase is exited (don't cache pointers to the input) + virtual void subcase_end() = 0; + + // called for each assert (don't cache pointers to the input) + virtual void log_assert(const AssertData&) = 0; + // called for each message (don't cache pointers to the input) + virtual void log_message(const MessageData&) = 0; + + // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator + // or isn't in the execution range (between first and last) (safe to cache a pointer to the input) + virtual void test_case_skipped(const TestCaseData&) = 0; + + // doctest will not be managing the lifetimes of reporters given to it but this would still be nice to have + virtual ~IReporter(); + + // can obtain all currently active contexts and stringify them if one wishes to do so + static int get_num_active_contexts(); + static const IContextScope* const* get_active_contexts(); + + // can iterate through contexts which have been stringified automatically in their destructors when an exception has been thrown + static int get_num_stringified_contexts(); + static const String* get_stringified_contexts(); +}; + +namespace detail { + typedef IReporter* (*reporterCreatorFunc)(const ContextOptions&); + + DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c, bool isReporter); + + template <typename Reporter> + IReporter* reporterCreator(const ContextOptions& o) { + return new Reporter(o); + } +} // namespace detail + +template <typename Reporter> +int registerReporter(const char* name, int priority, bool isReporter) { + detail::registerReporterImpl(name, priority, detail::reporterCreator<Reporter>, isReporter); + return 0; +} +} // namespace doctest + +// if registering is not disabled +#if !defined(DOCTEST_CONFIG_DISABLE) + +// common code in asserts - for convenience +#define DOCTEST_ASSERT_LOG_AND_REACT(b) \ + if(b.log()) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + b.react() + +#ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#define DOCTEST_WRAP_IN_TRY(x) x; +#else // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#define DOCTEST_WRAP_IN_TRY(x) \ + try { \ + x; \ + } catch(...) { _DOCTEST_RB.translateException(); } +#endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS + +#ifdef DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS +#define DOCTEST_CAST_TO_VOID(...) \ + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wuseless-cast") \ + static_cast<void>(__VA_ARGS__); \ + DOCTEST_GCC_SUPPRESS_WARNING_POP +#else // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS +#define DOCTEST_CAST_TO_VOID(...) __VA_ARGS__; +#endif // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS + +// registers the test by initializing a dummy var with a function +#define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators) \ + global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ + doctest::detail::regTest( \ + doctest::detail::TestCase( \ + f, __FILE__, __LINE__, \ + doctest_detail_test_suite_ns::getCurrentTestSuite()) * \ + decorators); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() + +#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators) \ + namespace { \ + struct der : public base \ + { \ + void f(); \ + }; \ + static void func() { \ + der v; \ + v.f(); \ + } \ + DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators) \ + } \ + inline DOCTEST_NOINLINE void der::f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators) \ + static void f(); \ + DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, f, decorators) \ + static void f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators) \ + static doctest::detail::funcType proxy() { return f; } \ + DOCTEST_REGISTER_FUNCTION(inline const, proxy(), decorators) \ + static void f() + +// for registering tests +#define DOCTEST_TEST_CASE(decorators) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) + +// for registering tests in classes - requires C++17 for inline variables! +#if __cplusplus >= 201703L || (DOCTEST_MSVC >= DOCTEST_COMPILER(19, 12, 0) && _MSVC_LANG >= 201703L) +#define DOCTEST_TEST_CASE_CLASS(decorators) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), \ + DOCTEST_ANONYMOUS(_DOCTEST_ANON_PROXY_), \ + decorators) +#else // DOCTEST_TEST_CASE_CLASS +#define DOCTEST_TEST_CASE_CLASS(...) \ + TEST_CASES_CAN_BE_REGISTERED_IN_CLASSES_ONLY_IN_CPP17_MODE_OR_WITH_VS_2017_OR_NEWER +#endif // DOCTEST_TEST_CASE_CLASS + +// for registering tests with a fixture +#define DOCTEST_TEST_CASE_FIXTURE(c, decorators) \ + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), c, \ + DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) + +// for converting types to strings without the <typeinfo> header and demangling +#define DOCTEST_TYPE_TO_STRING_IMPL(...) \ + template <> \ + inline const char* type_to_string<__VA_ARGS__>() { \ + return "<" #__VA_ARGS__ ">"; \ + } +#define DOCTEST_TYPE_TO_STRING(...) \ + namespace doctest { namespace detail { \ + DOCTEST_TYPE_TO_STRING_IMPL(__VA_ARGS__) \ + } \ + } \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func) \ + template <typename T> \ + static void func(); \ + namespace { \ + template <typename Tuple> \ + struct iter; \ + template <typename Type, typename... Rest> \ + struct iter<std::tuple<Type, Rest...>> \ + { \ + iter(const char* file, unsigned line, int index) { \ + doctest::detail::regTest(doctest::detail::TestCase(func<Type>, file, line, \ + doctest_detail_test_suite_ns::getCurrentTestSuite(), \ + doctest::detail::type_to_string<Type>(), \ + int(line) * 1000 + index) \ + * dec); \ + iter<std::tuple<Rest...>>(file, line, index + 1); \ + } \ + }; \ + template <> \ + struct iter<std::tuple<>> \ + { \ + iter(const char*, unsigned, int) {} \ + }; \ + } \ + template <typename T> \ + static void func() + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id) \ + DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR), \ + DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)) + +#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, anon, ...) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY)) = \ + doctest::detail::instantiationHelper(DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0));\ + DOCTEST_GLOBAL_NO_WARNINGS_END() + +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon); \ + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(anon, anon, std::tuple<__VA_ARGS__>) \ + template <typename T> \ + static void anon() + +#define DOCTEST_TEST_CASE_TEMPLATE(dec, T, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) + +// for subcases +#define DOCTEST_SUBCASE(name) \ + if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \ + doctest::detail::Subcase(name, __FILE__, __LINE__)) + +// for grouping tests in test suites by using code blocks +#define DOCTEST_TEST_SUITE_IMPL(decorators, ns_name) \ + namespace ns_name { namespace doctest_detail_test_suite_ns { \ + static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() { \ + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") \ + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmissing-field-initializers") \ + static doctest::detail::TestSuite data{}; \ + static bool inited = false; \ + DOCTEST_MSVC_SUPPRESS_WARNING_POP \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP \ + DOCTEST_GCC_SUPPRESS_WARNING_POP \ + if(!inited) { \ + data* decorators; \ + inited = true; \ + } \ + return data; \ + } \ + } \ + } \ + namespace ns_name + +#define DOCTEST_TEST_SUITE(decorators) \ + DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUITE_)) + +// for starting a testsuite block +#define DOCTEST_TEST_SUITE_BEGIN(decorators) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for ending a testsuite block +#define DOCTEST_TEST_SUITE_END \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * ""); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for registering exception translators +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature) \ + inline doctest::String translatorName(signature); \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)) = \ + doctest::registerExceptionTranslator(translatorName); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() \ + doctest::String translatorName(signature) + +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ + DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_), \ + signature) + +// for registering reporters +#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \ + doctest::registerReporter<reporter>(name, priority, true); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for registering listeners +#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \ + doctest::registerReporter<reporter>(name, priority, false); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for logging +#define DOCTEST_INFO(...) \ + DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), \ + __VA_ARGS__) + +#define DOCTEST_INFO_IMPL(mb_name, s_name, ...) \ + auto DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope( \ + [&](std::ostream* s_name) { \ + doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \ + mb_name.m_stream = s_name; \ + mb_name * __VA_ARGS__; \ + }) + +#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := ", x) + +#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, ...) \ + do { \ + doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \ + mb * __VA_ARGS__; \ + DOCTEST_ASSERT_LOG_AND_REACT(mb); \ + } while(false) + +// clang-format off +#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), __VA_ARGS__) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), __VA_ARGS__) +#define DOCTEST_ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), __VA_ARGS__) +// clang-format on + +#define DOCTEST_MESSAGE(...) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, __VA_ARGS__) +#define DOCTEST_FAIL_CHECK(...) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, __VA_ARGS__) +#define DOCTEST_FAIL(...) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, __VA_ARGS__) + +#define DOCTEST_TO_LVALUE(...) __VA_ARGS__ // Not removed to keep backwards compatibility. + +#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_ASSERT_IMPLEMENT_2(assert_type, ...) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.setResult( \ + doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ + << __VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB) \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ + do { \ + DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__); \ + } while(false) + +#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +// necessary for <ASSERT>_MESSAGE +#define DOCTEST_ASSERT_IMPLEMENT_2 DOCTEST_ASSERT_IMPLEMENT_1 + +#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ + doctest::detail::decomp_assert( \ + doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, \ + doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ + << __VA_ARGS__) DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_WARN(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN, __VA_ARGS__) +#define DOCTEST_CHECK(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK, __VA_ARGS__) +#define DOCTEST_REQUIRE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE, __VA_ARGS__) +#define DOCTEST_WARN_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN_FALSE, __VA_ARGS__) +#define DOCTEST_CHECK_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK_FALSE, __VA_ARGS__) +#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__) + +// clang-format off +#define DOCTEST_WARN_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } while(false) +#define DOCTEST_CHECK_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } while(false) +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } while(false) +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } while(false) +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } while(false) +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } while(false) +// clang-format on + +#define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, message, ...) \ + do { \ + if(!doctest::getContextOptions()->no_throw) { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #expr, #__VA_ARGS__, message); \ + try { \ + DOCTEST_CAST_TO_VOID(expr) \ + } catch(const typename doctest::detail::remove_const< \ + typename doctest::detail::remove_reference<__VA_ARGS__>::type>::type&) { \ + _DOCTEST_RB.translateException(); \ + _DOCTEST_RB.m_threw_as = true; \ + } catch(...) { _DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } \ + } while(false) + +#define DOCTEST_ASSERT_THROWS_WITH(expr, expr_str, assert_type, ...) \ + do { \ + if(!doctest::getContextOptions()->no_throw) { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, expr_str, "", __VA_ARGS__); \ + try { \ + DOCTEST_CAST_TO_VOID(expr) \ + } catch(...) { _DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } \ + } while(false) + +#define DOCTEST_ASSERT_NOTHROW(assert_type, ...) \ + do { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + try { \ + DOCTEST_CAST_TO_VOID(__VA_ARGS__) \ + } catch(...) { _DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } while(false) + +// clang-format off +#define DOCTEST_WARN_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_WARN_THROWS, "") +#define DOCTEST_CHECK_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_CHECK_THROWS, "") +#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_REQUIRE_THROWS, "") + +#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_AS, "", __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_AS, "", __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_AS, "", __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_WARN_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_CHECK_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_REQUIRE_THROWS_WITH, __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_WITH_AS, message, __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_WITH_AS, message, __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_WITH_AS, message, __VA_ARGS__) + +#define DOCTEST_WARN_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_WARN_NOTHROW, __VA_ARGS__) +#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_CHECK_NOTHROW, __VA_ARGS__) +#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_REQUIRE_NOTHROW, __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS(expr); } while(false) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS(expr); } while(false) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS(expr); } while(false) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_AS(expr, ex); } while(false) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_AS(expr, ex); } while(false) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } while(false) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH(expr, with); } while(false) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH(expr, with); } while(false) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } while(false) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } while(false) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } while(false) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } while(false) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_NOTHROW(expr); } while(false) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_NOTHROW(expr); } while(false) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_NOTHROW(expr); } while(false) +// clang-format on + +#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...) \ + do { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY( \ + _DOCTEST_RB.binary_assert<doctest::detail::binaryAssertComparison::comp>( \ + __VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } while(false) + +#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ + do { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.unary_assert(__VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } while(false) + +#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...) \ + doctest::detail::binary_assert<doctest::detail::binaryAssertComparison::comparison>( \ + doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__) + +#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ + doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \ + #__VA_ARGS__, __VA_ARGS__) + +#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__) +#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__) +#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__) +#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__) +#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__) +#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__) +#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__) +#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__) +#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__) +#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__) +#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__) +#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__) +#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__) +#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__) +#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__) +#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__) +#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__) +#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__) + +#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__) +#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__) + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS + +#undef DOCTEST_WARN_THROWS +#undef DOCTEST_CHECK_THROWS +#undef DOCTEST_REQUIRE_THROWS +#undef DOCTEST_WARN_THROWS_AS +#undef DOCTEST_CHECK_THROWS_AS +#undef DOCTEST_REQUIRE_THROWS_AS +#undef DOCTEST_WARN_THROWS_WITH +#undef DOCTEST_CHECK_THROWS_WITH +#undef DOCTEST_REQUIRE_THROWS_WITH +#undef DOCTEST_WARN_THROWS_WITH_AS +#undef DOCTEST_CHECK_THROWS_WITH_AS +#undef DOCTEST_REQUIRE_THROWS_WITH_AS +#undef DOCTEST_WARN_NOTHROW +#undef DOCTEST_CHECK_NOTHROW +#undef DOCTEST_REQUIRE_NOTHROW + +#undef DOCTEST_WARN_THROWS_MESSAGE +#undef DOCTEST_CHECK_THROWS_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_MESSAGE +#undef DOCTEST_WARN_THROWS_AS_MESSAGE +#undef DOCTEST_CHECK_THROWS_AS_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_AS_MESSAGE +#undef DOCTEST_WARN_THROWS_WITH_MESSAGE +#undef DOCTEST_CHECK_THROWS_WITH_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_WITH_MESSAGE +#undef DOCTEST_WARN_THROWS_WITH_AS_MESSAGE +#undef DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE +#undef DOCTEST_WARN_NOTHROW_MESSAGE +#undef DOCTEST_CHECK_NOTHROW_MESSAGE +#undef DOCTEST_REQUIRE_NOTHROW_MESSAGE + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#define DOCTEST_WARN_THROWS(...) (static_cast<void>(0)) +#define DOCTEST_CHECK_THROWS(...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_THROWS(...) (static_cast<void>(0)) +#define DOCTEST_WARN_THROWS_AS(expr, ...) (static_cast<void>(0)) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) (static_cast<void>(0)) +#define DOCTEST_WARN_THROWS_WITH(expr, ...) (static_cast<void>(0)) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) (static_cast<void>(0)) +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0)) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0)) +#define DOCTEST_WARN_NOTHROW(...) (static_cast<void>(0)) +#define DOCTEST_CHECK_NOTHROW(...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_NOTHROW(...) (static_cast<void>(0)) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) (static_cast<void>(0)) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) (static_cast<void>(0)) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0)) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0)) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0)) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0)) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0)) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0)) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0)) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0)) + +#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#undef DOCTEST_REQUIRE +#undef DOCTEST_REQUIRE_FALSE +#undef DOCTEST_REQUIRE_MESSAGE +#undef DOCTEST_REQUIRE_FALSE_MESSAGE +#undef DOCTEST_REQUIRE_EQ +#undef DOCTEST_REQUIRE_NE +#undef DOCTEST_REQUIRE_GT +#undef DOCTEST_REQUIRE_LT +#undef DOCTEST_REQUIRE_GE +#undef DOCTEST_REQUIRE_LE +#undef DOCTEST_REQUIRE_UNARY +#undef DOCTEST_REQUIRE_UNARY_FALSE + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +// ================================================================================================= +// == WHAT FOLLOWS IS VERSIONS OF THE MACROS THAT DO NOT DO ANY REGISTERING! == +// == THIS CAN BE ENABLED BY DEFINING DOCTEST_CONFIG_DISABLE GLOBALLY! == +// ================================================================================================= +#else // DOCTEST_CONFIG_DISABLE + +#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name) \ + namespace { \ + template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \ + struct der : public base \ + { void f(); }; \ + } \ + template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \ + inline void der<DOCTEST_UNUSED_TEMPLATE_TYPE>::f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, name) \ + template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \ + static inline void f() + +// for registering tests +#define DOCTEST_TEST_CASE(name) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + +// for registering tests in classes +#define DOCTEST_TEST_CASE_CLASS(name) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + +// for registering tests with a fixture +#define DOCTEST_TEST_CASE_FIXTURE(x, name) \ + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), x, \ + DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + +// for converting types to strings without the <typeinfo> header and demangling +#define DOCTEST_TYPE_TO_STRING(...) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) +#define DOCTEST_TYPE_TO_STRING_IMPL(...) + +// for typed tests +#define DOCTEST_TEST_CASE_TEMPLATE(name, type, ...) \ + template <typename type> \ + inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id) \ + template <typename type> \ + inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() + +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for subcases +#define DOCTEST_SUBCASE(name) + +// for a testsuite block +#define DOCTEST_TEST_SUITE(name) namespace + +// for starting a testsuite block +#define DOCTEST_TEST_SUITE_BEGIN(name) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for ending a testsuite block +#define DOCTEST_TEST_SUITE_END typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ + template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \ + static inline doctest::String DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)(signature) + +#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) +#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) + +#define DOCTEST_INFO(...) (static_cast<void>(0)) +#define DOCTEST_CAPTURE(x) (static_cast<void>(0)) +#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) (static_cast<void>(0)) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) (static_cast<void>(0)) +#define DOCTEST_ADD_FAIL_AT(file, line, ...) (static_cast<void>(0)) +#define DOCTEST_MESSAGE(...) (static_cast<void>(0)) +#define DOCTEST_FAIL_CHECK(...) (static_cast<void>(0)) +#define DOCTEST_FAIL(...) (static_cast<void>(0)) + +#define DOCTEST_WARN(...) (static_cast<void>(0)) +#define DOCTEST_CHECK(...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE(...) (static_cast<void>(0)) +#define DOCTEST_WARN_FALSE(...) (static_cast<void>(0)) +#define DOCTEST_CHECK_FALSE(...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_FALSE(...) (static_cast<void>(0)) + +#define DOCTEST_WARN_MESSAGE(cond, ...) (static_cast<void>(0)) +#define DOCTEST_CHECK_MESSAGE(cond, ...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) (static_cast<void>(0)) +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) (static_cast<void>(0)) +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) (static_cast<void>(0)) + +#define DOCTEST_WARN_THROWS(...) (static_cast<void>(0)) +#define DOCTEST_CHECK_THROWS(...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_THROWS(...) (static_cast<void>(0)) +#define DOCTEST_WARN_THROWS_AS(expr, ...) (static_cast<void>(0)) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) (static_cast<void>(0)) +#define DOCTEST_WARN_THROWS_WITH(expr, ...) (static_cast<void>(0)) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) (static_cast<void>(0)) +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0)) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) (static_cast<void>(0)) +#define DOCTEST_WARN_NOTHROW(...) (static_cast<void>(0)) +#define DOCTEST_CHECK_NOTHROW(...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_NOTHROW(...) (static_cast<void>(0)) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) (static_cast<void>(0)) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) (static_cast<void>(0)) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0)) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast<void>(0)) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0)) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast<void>(0)) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0)) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast<void>(0)) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0)) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) (static_cast<void>(0)) + +#define DOCTEST_WARN_EQ(...) (static_cast<void>(0)) +#define DOCTEST_CHECK_EQ(...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_EQ(...) (static_cast<void>(0)) +#define DOCTEST_WARN_NE(...) (static_cast<void>(0)) +#define DOCTEST_CHECK_NE(...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_NE(...) (static_cast<void>(0)) +#define DOCTEST_WARN_GT(...) (static_cast<void>(0)) +#define DOCTEST_CHECK_GT(...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_GT(...) (static_cast<void>(0)) +#define DOCTEST_WARN_LT(...) (static_cast<void>(0)) +#define DOCTEST_CHECK_LT(...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_LT(...) (static_cast<void>(0)) +#define DOCTEST_WARN_GE(...) (static_cast<void>(0)) +#define DOCTEST_CHECK_GE(...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_GE(...) (static_cast<void>(0)) +#define DOCTEST_WARN_LE(...) (static_cast<void>(0)) +#define DOCTEST_CHECK_LE(...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_LE(...) (static_cast<void>(0)) + +#define DOCTEST_WARN_UNARY(...) (static_cast<void>(0)) +#define DOCTEST_CHECK_UNARY(...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_UNARY(...) (static_cast<void>(0)) +#define DOCTEST_WARN_UNARY_FALSE(...) (static_cast<void>(0)) +#define DOCTEST_CHECK_UNARY_FALSE(...) (static_cast<void>(0)) +#define DOCTEST_REQUIRE_UNARY_FALSE(...) (static_cast<void>(0)) + +#endif // DOCTEST_CONFIG_DISABLE + +// clang-format off +// KEPT FOR BACKWARDS COMPATIBILITY - FORWARDING TO THE RIGHT MACROS +#define DOCTEST_FAST_WARN_EQ DOCTEST_WARN_EQ +#define DOCTEST_FAST_CHECK_EQ DOCTEST_CHECK_EQ +#define DOCTEST_FAST_REQUIRE_EQ DOCTEST_REQUIRE_EQ +#define DOCTEST_FAST_WARN_NE DOCTEST_WARN_NE +#define DOCTEST_FAST_CHECK_NE DOCTEST_CHECK_NE +#define DOCTEST_FAST_REQUIRE_NE DOCTEST_REQUIRE_NE +#define DOCTEST_FAST_WARN_GT DOCTEST_WARN_GT +#define DOCTEST_FAST_CHECK_GT DOCTEST_CHECK_GT +#define DOCTEST_FAST_REQUIRE_GT DOCTEST_REQUIRE_GT +#define DOCTEST_FAST_WARN_LT DOCTEST_WARN_LT +#define DOCTEST_FAST_CHECK_LT DOCTEST_CHECK_LT +#define DOCTEST_FAST_REQUIRE_LT DOCTEST_REQUIRE_LT +#define DOCTEST_FAST_WARN_GE DOCTEST_WARN_GE +#define DOCTEST_FAST_CHECK_GE DOCTEST_CHECK_GE +#define DOCTEST_FAST_REQUIRE_GE DOCTEST_REQUIRE_GE +#define DOCTEST_FAST_WARN_LE DOCTEST_WARN_LE +#define DOCTEST_FAST_CHECK_LE DOCTEST_CHECK_LE +#define DOCTEST_FAST_REQUIRE_LE DOCTEST_REQUIRE_LE + +#define DOCTEST_FAST_WARN_UNARY DOCTEST_WARN_UNARY +#define DOCTEST_FAST_CHECK_UNARY DOCTEST_CHECK_UNARY +#define DOCTEST_FAST_REQUIRE_UNARY DOCTEST_REQUIRE_UNARY +#define DOCTEST_FAST_WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE +#define DOCTEST_FAST_CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE +#define DOCTEST_FAST_REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE + +#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id,__VA_ARGS__) +// clang-format on + +// BDD style macros +// clang-format off +#define DOCTEST_SCENARIO(name) DOCTEST_TEST_CASE(" Scenario: " name) +#define DOCTEST_SCENARIO_CLASS(name) DOCTEST_TEST_CASE_CLASS(" Scenario: " name) +#define DOCTEST_SCENARIO_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(" Scenario: " name, T, __VA_ARGS__) +#define DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(" Scenario: " name, T, id) + +#define DOCTEST_GIVEN(name) DOCTEST_SUBCASE(" Given: " name) +#define DOCTEST_WHEN(name) DOCTEST_SUBCASE(" When: " name) +#define DOCTEST_AND_WHEN(name) DOCTEST_SUBCASE("And when: " name) +#define DOCTEST_THEN(name) DOCTEST_SUBCASE(" Then: " name) +#define DOCTEST_AND_THEN(name) DOCTEST_SUBCASE(" And: " name) +// clang-format on + +// == SHORT VERSIONS OF THE MACROS +#if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES) + +#define TEST_CASE(name) DOCTEST_TEST_CASE(name) +#define TEST_CASE_CLASS(name) DOCTEST_TEST_CASE_CLASS(name) +#define TEST_CASE_FIXTURE(x, name) DOCTEST_TEST_CASE_FIXTURE(x, name) +#define TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING(__VA_ARGS__) +#define TEST_CASE_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(name, T, __VA_ARGS__) +#define TEST_CASE_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, T, id) +#define TEST_CASE_TEMPLATE_INVOKE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, __VA_ARGS__) +#define TEST_CASE_TEMPLATE_APPLY(id, ...) DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, __VA_ARGS__) +#define SUBCASE(name) DOCTEST_SUBCASE(name) +#define TEST_SUITE(decorators) DOCTEST_TEST_SUITE(decorators) +#define TEST_SUITE_BEGIN(name) DOCTEST_TEST_SUITE_BEGIN(name) +#define TEST_SUITE_END DOCTEST_TEST_SUITE_END +#define REGISTER_EXCEPTION_TRANSLATOR(signature) DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) +#define REGISTER_REPORTER(name, priority, reporter) DOCTEST_REGISTER_REPORTER(name, priority, reporter) +#define REGISTER_LISTENER(name, priority, reporter) DOCTEST_REGISTER_LISTENER(name, priority, reporter) +#define INFO(...) DOCTEST_INFO(__VA_ARGS__) +#define CAPTURE(x) DOCTEST_CAPTURE(x) +#define ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_MESSAGE_AT(file, line, __VA_ARGS__) +#define ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_FAIL_CHECK_AT(file, line, __VA_ARGS__) +#define ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_FAIL_AT(file, line, __VA_ARGS__) +#define MESSAGE(...) DOCTEST_MESSAGE(__VA_ARGS__) +#define FAIL_CHECK(...) DOCTEST_FAIL_CHECK(__VA_ARGS__) +#define FAIL(...) DOCTEST_FAIL(__VA_ARGS__) +#define TO_LVALUE(...) DOCTEST_TO_LVALUE(__VA_ARGS__) + +#define WARN(...) DOCTEST_WARN(__VA_ARGS__) +#define WARN_FALSE(...) DOCTEST_WARN_FALSE(__VA_ARGS__) +#define WARN_THROWS(...) DOCTEST_WARN_THROWS(__VA_ARGS__) +#define WARN_THROWS_AS(expr, ...) DOCTEST_WARN_THROWS_AS(expr, __VA_ARGS__) +#define WARN_THROWS_WITH(expr, ...) DOCTEST_WARN_THROWS_WITH(expr, __VA_ARGS__) +#define WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_WARN_THROWS_WITH_AS(expr, with, __VA_ARGS__) +#define WARN_NOTHROW(...) DOCTEST_WARN_NOTHROW(__VA_ARGS__) +#define CHECK(...) DOCTEST_CHECK(__VA_ARGS__) +#define CHECK_FALSE(...) DOCTEST_CHECK_FALSE(__VA_ARGS__) +#define CHECK_THROWS(...) DOCTEST_CHECK_THROWS(__VA_ARGS__) +#define CHECK_THROWS_AS(expr, ...) DOCTEST_CHECK_THROWS_AS(expr, __VA_ARGS__) +#define CHECK_THROWS_WITH(expr, ...) DOCTEST_CHECK_THROWS_WITH(expr, __VA_ARGS__) +#define CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_AS(expr, with, __VA_ARGS__) +#define CHECK_NOTHROW(...) DOCTEST_CHECK_NOTHROW(__VA_ARGS__) +#define REQUIRE(...) DOCTEST_REQUIRE(__VA_ARGS__) +#define REQUIRE_FALSE(...) DOCTEST_REQUIRE_FALSE(__VA_ARGS__) +#define REQUIRE_THROWS(...) DOCTEST_REQUIRE_THROWS(__VA_ARGS__) +#define REQUIRE_THROWS_AS(expr, ...) DOCTEST_REQUIRE_THROWS_AS(expr, __VA_ARGS__) +#define REQUIRE_THROWS_WITH(expr, ...) DOCTEST_REQUIRE_THROWS_WITH(expr, __VA_ARGS__) +#define REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, __VA_ARGS__) +#define REQUIRE_NOTHROW(...) DOCTEST_REQUIRE_NOTHROW(__VA_ARGS__) + +#define WARN_MESSAGE(cond, ...) DOCTEST_WARN_MESSAGE(cond, __VA_ARGS__) +#define WARN_FALSE_MESSAGE(cond, ...) DOCTEST_WARN_FALSE_MESSAGE(cond, __VA_ARGS__) +#define WARN_THROWS_MESSAGE(expr, ...) DOCTEST_WARN_THROWS_MESSAGE(expr, __VA_ARGS__) +#define WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) +#define WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) +#define WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) +#define WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_WARN_NOTHROW_MESSAGE(expr, __VA_ARGS__) +#define CHECK_MESSAGE(cond, ...) DOCTEST_CHECK_MESSAGE(cond, __VA_ARGS__) +#define CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_CHECK_FALSE_MESSAGE(cond, __VA_ARGS__) +#define CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_CHECK_THROWS_MESSAGE(expr, __VA_ARGS__) +#define CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) +#define CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) +#define CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) +#define CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_CHECK_NOTHROW_MESSAGE(expr, __VA_ARGS__) +#define REQUIRE_MESSAGE(cond, ...) DOCTEST_REQUIRE_MESSAGE(cond, __VA_ARGS__) +#define REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_REQUIRE_FALSE_MESSAGE(cond, __VA_ARGS__) +#define REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_REQUIRE_THROWS_MESSAGE(expr, __VA_ARGS__) +#define REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) +#define REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) +#define REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) +#define REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, __VA_ARGS__) + +#define SCENARIO(name) DOCTEST_SCENARIO(name) +#define SCENARIO_CLASS(name) DOCTEST_SCENARIO_CLASS(name) +#define SCENARIO_TEMPLATE(name, T, ...) DOCTEST_SCENARIO_TEMPLATE(name, T, __VA_ARGS__) +#define SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) +#define GIVEN(name) DOCTEST_GIVEN(name) +#define WHEN(name) DOCTEST_WHEN(name) +#define AND_WHEN(name) DOCTEST_AND_WHEN(name) +#define THEN(name) DOCTEST_THEN(name) +#define AND_THEN(name) DOCTEST_AND_THEN(name) + +#define WARN_EQ(...) DOCTEST_WARN_EQ(__VA_ARGS__) +#define CHECK_EQ(...) DOCTEST_CHECK_EQ(__VA_ARGS__) +#define REQUIRE_EQ(...) DOCTEST_REQUIRE_EQ(__VA_ARGS__) +#define WARN_NE(...) DOCTEST_WARN_NE(__VA_ARGS__) +#define CHECK_NE(...) DOCTEST_CHECK_NE(__VA_ARGS__) +#define REQUIRE_NE(...) DOCTEST_REQUIRE_NE(__VA_ARGS__) +#define WARN_GT(...) DOCTEST_WARN_GT(__VA_ARGS__) +#define CHECK_GT(...) DOCTEST_CHECK_GT(__VA_ARGS__) +#define REQUIRE_GT(...) DOCTEST_REQUIRE_GT(__VA_ARGS__) +#define WARN_LT(...) DOCTEST_WARN_LT(__VA_ARGS__) +#define CHECK_LT(...) DOCTEST_CHECK_LT(__VA_ARGS__) +#define REQUIRE_LT(...) DOCTEST_REQUIRE_LT(__VA_ARGS__) +#define WARN_GE(...) DOCTEST_WARN_GE(__VA_ARGS__) +#define CHECK_GE(...) DOCTEST_CHECK_GE(__VA_ARGS__) +#define REQUIRE_GE(...) DOCTEST_REQUIRE_GE(__VA_ARGS__) +#define WARN_LE(...) DOCTEST_WARN_LE(__VA_ARGS__) +#define CHECK_LE(...) DOCTEST_CHECK_LE(__VA_ARGS__) +#define REQUIRE_LE(...) DOCTEST_REQUIRE_LE(__VA_ARGS__) +#define WARN_UNARY(...) DOCTEST_WARN_UNARY(__VA_ARGS__) +#define CHECK_UNARY(...) DOCTEST_CHECK_UNARY(__VA_ARGS__) +#define REQUIRE_UNARY(...) DOCTEST_REQUIRE_UNARY(__VA_ARGS__) +#define WARN_UNARY_FALSE(...) DOCTEST_WARN_UNARY_FALSE(__VA_ARGS__) +#define CHECK_UNARY_FALSE(...) DOCTEST_CHECK_UNARY_FALSE(__VA_ARGS__) +#define REQUIRE_UNARY_FALSE(...) DOCTEST_REQUIRE_UNARY_FALSE(__VA_ARGS__) + +// KEPT FOR BACKWARDS COMPATIBILITY +#define FAST_WARN_EQ(...) DOCTEST_FAST_WARN_EQ(__VA_ARGS__) +#define FAST_CHECK_EQ(...) DOCTEST_FAST_CHECK_EQ(__VA_ARGS__) +#define FAST_REQUIRE_EQ(...) DOCTEST_FAST_REQUIRE_EQ(__VA_ARGS__) +#define FAST_WARN_NE(...) DOCTEST_FAST_WARN_NE(__VA_ARGS__) +#define FAST_CHECK_NE(...) DOCTEST_FAST_CHECK_NE(__VA_ARGS__) +#define FAST_REQUIRE_NE(...) DOCTEST_FAST_REQUIRE_NE(__VA_ARGS__) +#define FAST_WARN_GT(...) DOCTEST_FAST_WARN_GT(__VA_ARGS__) +#define FAST_CHECK_GT(...) DOCTEST_FAST_CHECK_GT(__VA_ARGS__) +#define FAST_REQUIRE_GT(...) DOCTEST_FAST_REQUIRE_GT(__VA_ARGS__) +#define FAST_WARN_LT(...) DOCTEST_FAST_WARN_LT(__VA_ARGS__) +#define FAST_CHECK_LT(...) DOCTEST_FAST_CHECK_LT(__VA_ARGS__) +#define FAST_REQUIRE_LT(...) DOCTEST_FAST_REQUIRE_LT(__VA_ARGS__) +#define FAST_WARN_GE(...) DOCTEST_FAST_WARN_GE(__VA_ARGS__) +#define FAST_CHECK_GE(...) DOCTEST_FAST_CHECK_GE(__VA_ARGS__) +#define FAST_REQUIRE_GE(...) DOCTEST_FAST_REQUIRE_GE(__VA_ARGS__) +#define FAST_WARN_LE(...) DOCTEST_FAST_WARN_LE(__VA_ARGS__) +#define FAST_CHECK_LE(...) DOCTEST_FAST_CHECK_LE(__VA_ARGS__) +#define FAST_REQUIRE_LE(...) DOCTEST_FAST_REQUIRE_LE(__VA_ARGS__) + +#define FAST_WARN_UNARY(...) DOCTEST_FAST_WARN_UNARY(__VA_ARGS__) +#define FAST_CHECK_UNARY(...) DOCTEST_FAST_CHECK_UNARY(__VA_ARGS__) +#define FAST_REQUIRE_UNARY(...) DOCTEST_FAST_REQUIRE_UNARY(__VA_ARGS__) +#define FAST_WARN_UNARY_FALSE(...) DOCTEST_FAST_WARN_UNARY_FALSE(__VA_ARGS__) +#define FAST_CHECK_UNARY_FALSE(...) DOCTEST_FAST_CHECK_UNARY_FALSE(__VA_ARGS__) +#define FAST_REQUIRE_UNARY_FALSE(...) DOCTEST_FAST_REQUIRE_UNARY_FALSE(__VA_ARGS__) + +#define TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, __VA_ARGS__) + +#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES + +#if !defined(DOCTEST_CONFIG_DISABLE) + +// this is here to clear the 'current test suite' for the current translation unit - at the top +DOCTEST_TEST_SUITE_END(); + +// add stringification for primitive/fundamental types +namespace doctest { namespace detail { + DOCTEST_TYPE_TO_STRING_IMPL(bool) + DOCTEST_TYPE_TO_STRING_IMPL(float) + DOCTEST_TYPE_TO_STRING_IMPL(double) + DOCTEST_TYPE_TO_STRING_IMPL(long double) + DOCTEST_TYPE_TO_STRING_IMPL(char) + DOCTEST_TYPE_TO_STRING_IMPL(signed char) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned char) +#if !DOCTEST_MSVC || defined(_NATIVE_WCHAR_T_DEFINED) + DOCTEST_TYPE_TO_STRING_IMPL(wchar_t) +#endif // not MSVC or wchar_t support enabled + DOCTEST_TYPE_TO_STRING_IMPL(short int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned short int) + DOCTEST_TYPE_TO_STRING_IMPL(int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned int) + DOCTEST_TYPE_TO_STRING_IMPL(long int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned long int) + DOCTEST_TYPE_TO_STRING_IMPL(long long int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned long long int) +}} // namespace doctest::detail + +#endif // DOCTEST_CONFIG_DISABLE + +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_LIBRARY_INCLUDED + +#ifndef DOCTEST_SINGLE_HEADER +#define DOCTEST_SINGLE_HEADER +#endif // DOCTEST_SINGLE_HEADER + +#if defined(DOCTEST_CONFIG_IMPLEMENT) || !defined(DOCTEST_SINGLE_HEADER) + +#ifndef DOCTEST_SINGLE_HEADER +#include "doctest_fwd.h" +#endif // DOCTEST_SINGLE_HEADER + +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-macros") + +#ifndef DOCTEST_LIBRARY_IMPLEMENTATION +#define DOCTEST_LIBRARY_IMPLEMENTATION + +DOCTEST_CLANG_SUPPRESS_WARNING_POP + +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-variable-declarations") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch-enum") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wcovered-switch-default") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-noreturn") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wdisabled-macro-expansion") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wnonportable-system-include-path") + +DOCTEST_GCC_SUPPRESS_WARNING_PUSH +DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") +DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-field-initializers") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-braces") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-enum") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-default") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations") +DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") +DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmultiple-inheritance") +DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsuggest-attribute") + +DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration +DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'x' to 'y', possible loss of data +DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression +DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated +DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant +DOCTEST_MSVC_SUPPRESS_WARNING(4530) // C++ exception handler used, but unwind semantics not enabled +DOCTEST_MSVC_SUPPRESS_WARNING(4577) // 'noexcept' used with no exception handling mode specified +DOCTEST_MSVC_SUPPRESS_WARNING(4774) // format string expected in argument is not a string literal +DOCTEST_MSVC_SUPPRESS_WARNING(4365) // conversion from 'int' to 'unsigned', signed/unsigned mismatch +DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding in structs +DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe +DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C +DOCTEST_MSVC_SUPPRESS_WARNING(5045) // Spectre mitigation stuff +DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning) +// static analysis +DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept' +DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable +DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ... +DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtor... +DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' + +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN + +// required includes - will go only in one translation unit! +#include <ctime> +#include <cmath> +#include <climits> +// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/onqtam/doctest/pull/37 +#ifdef __BORLANDC__ +#include <math.h> +#endif // __BORLANDC__ +#include <new> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <limits> +#include <utility> +#include <fstream> +#include <sstream> +#include <iostream> +#include <algorithm> +#include <iomanip> +#include <vector> +#include <atomic> +#include <mutex> +#include <set> +#include <map> +#include <exception> +#include <stdexcept> +#include <csignal> +#include <cfloat> +#include <cctype> +#include <cstdint> + +#ifdef DOCTEST_PLATFORM_MAC +#include <sys/types.h> +#include <unistd.h> +#include <sys/sysctl.h> +#endif // DOCTEST_PLATFORM_MAC + +#ifdef DOCTEST_PLATFORM_WINDOWS + +// defines for a leaner windows.h +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +// not sure what AfxWin.h is for - here I do what Catch does +#ifdef __AFXDLL +#include <AfxWin.h> +#else +#include <windows.h> +#endif +#include <io.h> + +#else // DOCTEST_PLATFORM_WINDOWS + +#include <sys/time.h> +#include <unistd.h> + +#endif // DOCTEST_PLATFORM_WINDOWS + +// this is a fix for https://github.com/onqtam/doctest/issues/348 +// https://mail.gnome.org/archives/xml/2012-January/msg00000.html +#if !defined(HAVE_UNISTD_H) && !defined(STDOUT_FILENO) +#define STDOUT_FILENO fileno(stdout) +#endif // HAVE_UNISTD_H + +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END + +// counts the number of elements in a C array +#define DOCTEST_COUNTOF(x) (sizeof(x) / sizeof(x[0])) + +#ifdef DOCTEST_CONFIG_DISABLE +#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_disabled +#else // DOCTEST_CONFIG_DISABLE +#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_not_disabled +#endif // DOCTEST_CONFIG_DISABLE + +#ifndef DOCTEST_CONFIG_OPTIONS_PREFIX +#define DOCTEST_CONFIG_OPTIONS_PREFIX "dt-" +#endif + +#ifndef DOCTEST_THREAD_LOCAL +#define DOCTEST_THREAD_LOCAL thread_local +#endif + +#ifndef DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES +#define DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES 32 +#endif + +#ifndef DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE +#define DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE 64 +#endif + +#ifdef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS +#define DOCTEST_OPTIONS_PREFIX_DISPLAY DOCTEST_CONFIG_OPTIONS_PREFIX +#else +#define DOCTEST_OPTIONS_PREFIX_DISPLAY "" +#endif + +#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +#define DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS +#endif + +namespace doctest { + +bool is_running_in_test = false; + +namespace { + using namespace detail; + // case insensitive strcmp + int stricmp(const char* a, const char* b) { + for(;; a++, b++) { + const int d = tolower(*a) - tolower(*b); + if(d != 0 || !*a) + return d; + } + } + + template <typename T> + String fpToString(T value, int precision) { + std::ostringstream oss; + oss << std::setprecision(precision) << std::fixed << value; + std::string d = oss.str(); + size_t i = d.find_last_not_of('0'); + if(i != std::string::npos && i != d.size() - 1) { + if(d[i] == '.') + i++; + d = d.substr(0, i + 1); + } + return d.c_str(); + } + + struct Endianness + { + enum Arch + { + Big, + Little + }; + + static Arch which() { + int x = 1; + // casting any data pointer to char* is allowed + auto ptr = reinterpret_cast<char*>(&x); + if(*ptr) + return Little; + return Big; + } + }; +} // namespace + +namespace detail { + void my_memcpy(void* dest, const void* src, unsigned num) { memcpy(dest, src, num); } + + String rawMemoryToString(const void* object, unsigned size) { + // Reverse order for little endian architectures + int i = 0, end = static_cast<int>(size), inc = 1; + if(Endianness::which() == Endianness::Little) { + i = end - 1; + end = inc = -1; + } + + unsigned const char* bytes = static_cast<unsigned const char*>(object); + std::ostringstream oss; + oss << "0x" << std::setfill('0') << std::hex; + for(; i != end; i += inc) + oss << std::setw(2) << static_cast<unsigned>(bytes[i]); + return oss.str().c_str(); + } + + DOCTEST_THREAD_LOCAL std::ostringstream g_oss; // NOLINT(cert-err58-cpp) + + std::ostream* getTlsOss() { + g_oss.clear(); // there shouldn't be anything worth clearing in the flags + g_oss.str(""); // the slow way of resetting a string stream + //g_oss.seekp(0); // optimal reset - as seen here: https://stackoverflow.com/a/624291/3162383 + return &g_oss; + } + + String getTlsOssResult() { + //g_oss << std::ends; // needed - as shown here: https://stackoverflow.com/a/624291/3162383 + return g_oss.str().c_str(); + } + +#ifndef DOCTEST_CONFIG_DISABLE + +namespace timer_large_integer +{ + +#if defined(DOCTEST_PLATFORM_WINDOWS) + typedef ULONGLONG type; +#else // DOCTEST_PLATFORM_WINDOWS + using namespace std; + typedef uint64_t type; +#endif // DOCTEST_PLATFORM_WINDOWS +} + +typedef timer_large_integer::type ticks_t; + +#ifdef DOCTEST_CONFIG_GETCURRENTTICKS + ticks_t getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); } +#elif defined(DOCTEST_PLATFORM_WINDOWS) + ticks_t getCurrentTicks() { + static LARGE_INTEGER hz = {0}, hzo = {0}; + if(!hz.QuadPart) { + QueryPerformanceFrequency(&hz); + QueryPerformanceCounter(&hzo); + } + LARGE_INTEGER t; + QueryPerformanceCounter(&t); + return ((t.QuadPart - hzo.QuadPart) * LONGLONG(1000000)) / hz.QuadPart; + } +#else // DOCTEST_PLATFORM_WINDOWS + ticks_t getCurrentTicks() { + timeval t; + gettimeofday(&t, nullptr); + return static_cast<ticks_t>(t.tv_sec) * 1000000 + static_cast<ticks_t>(t.tv_usec); + } +#endif // DOCTEST_PLATFORM_WINDOWS + + struct Timer + { + void start() { m_ticks = getCurrentTicks(); } + unsigned int getElapsedMicroseconds() const { + return static_cast<unsigned int>(getCurrentTicks() - m_ticks); + } + //unsigned int getElapsedMilliseconds() const { + // return static_cast<unsigned int>(getElapsedMicroseconds() / 1000); + //} + double getElapsedSeconds() const { return static_cast<double>(getCurrentTicks() - m_ticks) / 1000000.0; } + + private: + ticks_t m_ticks = 0; + }; + +#ifdef DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS + template <typename T> + using AtomicOrMultiLaneAtomic = std::atomic<T>; +#else // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS + // Provides a multilane implementation of an atomic variable that supports add, sub, load, + // store. Instead of using a single atomic variable, this splits up into multiple ones, + // each sitting on a separate cache line. The goal is to provide a speedup when most + // operations are modifying. It achieves this with two properties: + // + // * Multiple atomics are used, so chance of congestion from the same atomic is reduced. + // * Each atomic sits on a separate cache line, so false sharing is reduced. + // + // The disadvantage is that there is a small overhead due to the use of TLS, and load/store + // is slower because all atomics have to be accessed. + template <typename T> + class MultiLaneAtomic + { + struct CacheLineAlignedAtomic + { + std::atomic<T> atomic{}; + char padding[DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE - sizeof(std::atomic<T>)]; + }; + CacheLineAlignedAtomic m_atomics[DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES]; + + static_assert(sizeof(CacheLineAlignedAtomic) == DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE, + "guarantee one atomic takes exactly one cache line"); + + public: + T operator++() DOCTEST_NOEXCEPT { return fetch_add(1) + 1; } + + T operator++(int) DOCTEST_NOEXCEPT { return fetch_add(1); } + + T fetch_add(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { + return myAtomic().fetch_add(arg, order); + } + + T fetch_sub(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { + return myAtomic().fetch_sub(arg, order); + } + + operator T() const DOCTEST_NOEXCEPT { return load(); } + + T load(std::memory_order order = std::memory_order_seq_cst) const DOCTEST_NOEXCEPT { + auto result = T(); + for(auto const& c : m_atomics) { + result += c.atomic.load(order); + } + return result; + } + + T operator=(T desired) DOCTEST_NOEXCEPT { + store(desired); + return desired; + } + + void store(T desired, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { + // first value becomes desired", all others become 0. + for(auto& c : m_atomics) { + c.atomic.store(desired, order); + desired = {}; + } + } + + private: + // Each thread has a different atomic that it operates on. If more than NumLanes threads + // use this, some will use the same atomic. So performance will degrate a bit, but still + // everything will work. + // + // The logic here is a bit tricky. The call should be as fast as possible, so that there + // is minimal to no overhead in determining the correct atomic for the current thread. + // + // 1. A global static counter laneCounter counts continuously up. + // 2. Each successive thread will use modulo operation of that counter so it gets an atomic + // assigned in a round-robin fashion. + // 3. This tlsLaneIdx is stored in the thread local data, so it is directly available with + // little overhead. + std::atomic<T>& myAtomic() DOCTEST_NOEXCEPT { + static std::atomic<size_t> laneCounter; + DOCTEST_THREAD_LOCAL size_t tlsLaneIdx = + laneCounter++ % DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES; + + return m_atomics[tlsLaneIdx].atomic; + } + }; + + template <typename T> + using AtomicOrMultiLaneAtomic = MultiLaneAtomic<T>; +#endif // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS + + // this holds both parameters from the command line and runtime data for tests + struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats + { + AtomicOrMultiLaneAtomic<int> numAssertsCurrentTest_atomic; + AtomicOrMultiLaneAtomic<int> numAssertsFailedCurrentTest_atomic; + + std::vector<std::vector<String>> filters = decltype(filters)(9); // 9 different filters + + std::vector<IReporter*> reporters_currently_used; + + assert_handler ah = nullptr; + + Timer timer; + + std::vector<String> stringifiedContexts; // logging from INFO() due to an exception + + // stuff for subcases + std::vector<SubcaseSignature> subcasesStack; + std::set<decltype(subcasesStack)> subcasesPassed; + int subcasesCurrentMaxLevel; + bool should_reenter; + std::atomic<bool> shouldLogCurrentException; + + void resetRunData() { + numTestCases = 0; + numTestCasesPassingFilters = 0; + numTestSuitesPassingFilters = 0; + numTestCasesFailed = 0; + numAsserts = 0; + numAssertsFailed = 0; + numAssertsCurrentTest = 0; + numAssertsFailedCurrentTest = 0; + } + + void finalizeTestCaseData() { + seconds = timer.getElapsedSeconds(); + + // update the non-atomic counters + numAsserts += numAssertsCurrentTest_atomic; + numAssertsFailed += numAssertsFailedCurrentTest_atomic; + numAssertsCurrentTest = numAssertsCurrentTest_atomic; + numAssertsFailedCurrentTest = numAssertsFailedCurrentTest_atomic; + + if(numAssertsFailedCurrentTest) + failure_flags |= TestCaseFailureReason::AssertFailure; + + if(Approx(currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 && + Approx(seconds).epsilon(DBL_EPSILON) > currentTest->m_timeout) + failure_flags |= TestCaseFailureReason::Timeout; + + if(currentTest->m_should_fail) { + if(failure_flags) { + failure_flags |= TestCaseFailureReason::ShouldHaveFailedAndDid; + } else { + failure_flags |= TestCaseFailureReason::ShouldHaveFailedButDidnt; + } + } else if(failure_flags && currentTest->m_may_fail) { + failure_flags |= TestCaseFailureReason::CouldHaveFailedAndDid; + } else if(currentTest->m_expected_failures > 0) { + if(numAssertsFailedCurrentTest == currentTest->m_expected_failures) { + failure_flags |= TestCaseFailureReason::FailedExactlyNumTimes; + } else { + failure_flags |= TestCaseFailureReason::DidntFailExactlyNumTimes; + } + } + + bool ok_to_fail = (TestCaseFailureReason::ShouldHaveFailedAndDid & failure_flags) || + (TestCaseFailureReason::CouldHaveFailedAndDid & failure_flags) || + (TestCaseFailureReason::FailedExactlyNumTimes & failure_flags); + + // if any subcase has failed - the whole test case has failed + if(failure_flags && !ok_to_fail) + numTestCasesFailed++; + } + }; + + ContextState* g_cs = nullptr; + + // used to avoid locks for the debug output + // TODO: figure out if this is indeed necessary/correct - seems like either there still + // could be a race or that there wouldn't be a race even if using the context directly + DOCTEST_THREAD_LOCAL bool g_no_colors; + +#endif // DOCTEST_CONFIG_DISABLE +} // namespace detail + +void String::setOnHeap() { *reinterpret_cast<unsigned char*>(&buf[last]) = 128; } +void String::setLast(unsigned in) { buf[last] = char(in); } + +void String::copy(const String& other) { + using namespace std; + if(other.isOnStack()) { + memcpy(buf, other.buf, len); + } else { + setOnHeap(); + data.size = other.data.size; + data.capacity = data.size + 1; + data.ptr = new char[data.capacity]; + memcpy(data.ptr, other.data.ptr, data.size + 1); + } +} + +String::String() { + buf[0] = '\0'; + setLast(); +} + +String::~String() { + if(!isOnStack()) + delete[] data.ptr; + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) +} + +String::String(const char* in) + : String(in, strlen(in)) {} + +String::String(const char* in, unsigned in_size) { + using namespace std; + if(in_size <= last) { + memcpy(buf, in, in_size); + buf[in_size] = '\0'; + setLast(last - in_size); + } else { + setOnHeap(); + data.size = in_size; + data.capacity = data.size + 1; + data.ptr = new char[data.capacity]; + memcpy(data.ptr, in, in_size); + data.ptr[in_size] = '\0'; + } +} + +String::String(const String& other) { copy(other); } + +String& String::operator=(const String& other) { + if(this != &other) { + if(!isOnStack()) + delete[] data.ptr; + + copy(other); + } + + return *this; +} + +String& String::operator+=(const String& other) { + const unsigned my_old_size = size(); + const unsigned other_size = other.size(); + const unsigned total_size = my_old_size + other_size; + using namespace std; + if(isOnStack()) { + if(total_size < len) { + // append to the current stack space + memcpy(buf + my_old_size, other.c_str(), other_size + 1); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + setLast(last - total_size); + } else { + // alloc new chunk + char* temp = new char[total_size + 1]; + // copy current data to new location before writing in the union + memcpy(temp, buf, my_old_size); // skip the +1 ('\0') for speed + // update data in union + setOnHeap(); + data.size = total_size; + data.capacity = data.size + 1; + data.ptr = temp; + // transfer the rest of the data + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } + } else { + if(data.capacity > total_size) { + // append to the current heap block + data.size = total_size; + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } else { + // resize + data.capacity *= 2; + if(data.capacity <= total_size) + data.capacity = total_size + 1; + // alloc new chunk + char* temp = new char[data.capacity]; + // copy current data to new location before releasing it + memcpy(temp, data.ptr, my_old_size); // skip the +1 ('\0') for speed + // release old chunk + delete[] data.ptr; + // update the rest of the union members + data.size = total_size; + data.ptr = temp; + // transfer the rest of the data + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } + } + + return *this; +} + +// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) +String String::operator+(const String& other) const { return String(*this) += other; } + +String::String(String&& other) { + using namespace std; + memcpy(buf, other.buf, len); + other.buf[0] = '\0'; + other.setLast(); +} + +String& String::operator=(String&& other) { + using namespace std; + if(this != &other) { + if(!isOnStack()) + delete[] data.ptr; + memcpy(buf, other.buf, len); + other.buf[0] = '\0'; + other.setLast(); + } + return *this; +} + +char String::operator[](unsigned i) const { + return const_cast<String*>(this)->operator[](i); // NOLINT +} + +char& String::operator[](unsigned i) { + if(isOnStack()) + return reinterpret_cast<char*>(buf)[i]; + return data.ptr[i]; +} + +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmaybe-uninitialized") +unsigned String::size() const { + if(isOnStack()) + return last - (unsigned(buf[last]) & 31); // using "last" would work only if "len" is 32 + return data.size; +} +DOCTEST_GCC_SUPPRESS_WARNING_POP + +unsigned String::capacity() const { + if(isOnStack()) + return len; + return data.capacity; +} + +int String::compare(const char* other, bool no_case) const { + if(no_case) + return doctest::stricmp(c_str(), other); + return std::strcmp(c_str(), other); +} + +int String::compare(const String& other, bool no_case) const { + return compare(other.c_str(), no_case); +} + +// clang-format off +bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; } +bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; } +bool operator< (const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; } +bool operator> (const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; } +bool operator<=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) < 0 : true; } +bool operator>=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) > 0 : true; } +// clang-format on + +std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); } + +namespace { + void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;) +} // namespace + +namespace Color { + std::ostream& operator<<(std::ostream& s, Color::Enum code) { + color_to_stream(s, code); + return s; + } +} // namespace Color + +// clang-format off +const char* assertString(assertType::Enum at) { + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4062) // enum 'x' in switch of enum 'y' is not handled + switch(at) { //!OCLINT missing default in switch statements + case assertType::DT_WARN : return "WARN"; + case assertType::DT_CHECK : return "CHECK"; + case assertType::DT_REQUIRE : return "REQUIRE"; + + case assertType::DT_WARN_FALSE : return "WARN_FALSE"; + case assertType::DT_CHECK_FALSE : return "CHECK_FALSE"; + case assertType::DT_REQUIRE_FALSE : return "REQUIRE_FALSE"; + + case assertType::DT_WARN_THROWS : return "WARN_THROWS"; + case assertType::DT_CHECK_THROWS : return "CHECK_THROWS"; + case assertType::DT_REQUIRE_THROWS : return "REQUIRE_THROWS"; + + case assertType::DT_WARN_THROWS_AS : return "WARN_THROWS_AS"; + case assertType::DT_CHECK_THROWS_AS : return "CHECK_THROWS_AS"; + case assertType::DT_REQUIRE_THROWS_AS : return "REQUIRE_THROWS_AS"; + + case assertType::DT_WARN_THROWS_WITH : return "WARN_THROWS_WITH"; + case assertType::DT_CHECK_THROWS_WITH : return "CHECK_THROWS_WITH"; + case assertType::DT_REQUIRE_THROWS_WITH : return "REQUIRE_THROWS_WITH"; + + case assertType::DT_WARN_THROWS_WITH_AS : return "WARN_THROWS_WITH_AS"; + case assertType::DT_CHECK_THROWS_WITH_AS : return "CHECK_THROWS_WITH_AS"; + case assertType::DT_REQUIRE_THROWS_WITH_AS : return "REQUIRE_THROWS_WITH_AS"; + + case assertType::DT_WARN_NOTHROW : return "WARN_NOTHROW"; + case assertType::DT_CHECK_NOTHROW : return "CHECK_NOTHROW"; + case assertType::DT_REQUIRE_NOTHROW : return "REQUIRE_NOTHROW"; + + case assertType::DT_WARN_EQ : return "WARN_EQ"; + case assertType::DT_CHECK_EQ : return "CHECK_EQ"; + case assertType::DT_REQUIRE_EQ : return "REQUIRE_EQ"; + case assertType::DT_WARN_NE : return "WARN_NE"; + case assertType::DT_CHECK_NE : return "CHECK_NE"; + case assertType::DT_REQUIRE_NE : return "REQUIRE_NE"; + case assertType::DT_WARN_GT : return "WARN_GT"; + case assertType::DT_CHECK_GT : return "CHECK_GT"; + case assertType::DT_REQUIRE_GT : return "REQUIRE_GT"; + case assertType::DT_WARN_LT : return "WARN_LT"; + case assertType::DT_CHECK_LT : return "CHECK_LT"; + case assertType::DT_REQUIRE_LT : return "REQUIRE_LT"; + case assertType::DT_WARN_GE : return "WARN_GE"; + case assertType::DT_CHECK_GE : return "CHECK_GE"; + case assertType::DT_REQUIRE_GE : return "REQUIRE_GE"; + case assertType::DT_WARN_LE : return "WARN_LE"; + case assertType::DT_CHECK_LE : return "CHECK_LE"; + case assertType::DT_REQUIRE_LE : return "REQUIRE_LE"; + + case assertType::DT_WARN_UNARY : return "WARN_UNARY"; + case assertType::DT_CHECK_UNARY : return "CHECK_UNARY"; + case assertType::DT_REQUIRE_UNARY : return "REQUIRE_UNARY"; + case assertType::DT_WARN_UNARY_FALSE : return "WARN_UNARY_FALSE"; + case assertType::DT_CHECK_UNARY_FALSE : return "CHECK_UNARY_FALSE"; + case assertType::DT_REQUIRE_UNARY_FALSE : return "REQUIRE_UNARY_FALSE"; + } + DOCTEST_MSVC_SUPPRESS_WARNING_POP + return ""; +} +// clang-format on + +const char* failureString(assertType::Enum at) { + if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional + return "WARNING"; + if(at & assertType::is_check) //!OCLINT bitwise operator in conditional + return "ERROR"; + if(at & assertType::is_require) //!OCLINT bitwise operator in conditional + return "FATAL ERROR"; + return ""; +} + +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") +// depending on the current options this will remove the path of filenames +const char* skipPathFromFilename(const char* file) { +#ifndef DOCTEST_CONFIG_DISABLE + if(getContextOptions()->no_path_in_filenames) { + auto back = std::strrchr(file, '\\'); + auto forward = std::strrchr(file, '/'); + if(back || forward) { + if(back > forward) + forward = back; + return forward + 1; + } + } +#endif // DOCTEST_CONFIG_DISABLE + return file; +} +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +bool SubcaseSignature::operator<(const SubcaseSignature& other) const { + if(m_line != other.m_line) + return m_line < other.m_line; + if(std::strcmp(m_file, other.m_file) != 0) + return std::strcmp(m_file, other.m_file) < 0; + return m_name.compare(other.m_name) < 0; +} + +IContextScope::IContextScope() = default; +IContextScope::~IContextScope() = default; + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +String toString(char* in) { return toString(static_cast<const char*>(in)); } +// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) +String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +String toString(bool in) { return in ? "true" : "false"; } +String toString(float in) { return fpToString(in, 5) + "f"; } +String toString(double in) { return fpToString(in, 10); } +String toString(double long in) { return fpToString(in, 15); } + +#define DOCTEST_TO_STRING_OVERLOAD(type, fmt) \ + String toString(type in) { \ + char buf[64]; \ + std::sprintf(buf, fmt, in); \ + return buf; \ + } + +DOCTEST_TO_STRING_OVERLOAD(char, "%d") +DOCTEST_TO_STRING_OVERLOAD(char signed, "%d") +DOCTEST_TO_STRING_OVERLOAD(char unsigned, "%u") +DOCTEST_TO_STRING_OVERLOAD(int short, "%d") +DOCTEST_TO_STRING_OVERLOAD(int short unsigned, "%u") +DOCTEST_TO_STRING_OVERLOAD(int, "%d") +DOCTEST_TO_STRING_OVERLOAD(unsigned, "%u") +DOCTEST_TO_STRING_OVERLOAD(int long, "%ld") +DOCTEST_TO_STRING_OVERLOAD(int long unsigned, "%lu") +DOCTEST_TO_STRING_OVERLOAD(int long long, "%lld") +DOCTEST_TO_STRING_OVERLOAD(int long long unsigned, "%llu") + +String toString(std::nullptr_t) { return "NULL"; } + +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 +String toString(const std::string& in) { return in.c_str(); } +#endif // VS 2019 + +Approx::Approx(double value) + : m_epsilon(static_cast<double>(std::numeric_limits<float>::epsilon()) * 100) + , m_scale(1.0) + , m_value(value) {} + +Approx Approx::operator()(double value) const { + Approx approx(value); + approx.epsilon(m_epsilon); + approx.scale(m_scale); + return approx; +} + +Approx& Approx::epsilon(double newEpsilon) { + m_epsilon = newEpsilon; + return *this; +} +Approx& Approx::scale(double newScale) { + m_scale = newScale; + return *this; +} + +bool operator==(double lhs, const Approx& rhs) { + // Thanks to Richard Harris for his help refining this formula + return std::fabs(lhs - rhs.m_value) < + rhs.m_epsilon * (rhs.m_scale + std::max<double>(std::fabs(lhs), std::fabs(rhs.m_value))); +} +bool operator==(const Approx& lhs, double rhs) { return operator==(rhs, lhs); } +bool operator!=(double lhs, const Approx& rhs) { return !operator==(lhs, rhs); } +bool operator!=(const Approx& lhs, double rhs) { return !operator==(rhs, lhs); } +bool operator<=(double lhs, const Approx& rhs) { return lhs < rhs.m_value || lhs == rhs; } +bool operator<=(const Approx& lhs, double rhs) { return lhs.m_value < rhs || lhs == rhs; } +bool operator>=(double lhs, const Approx& rhs) { return lhs > rhs.m_value || lhs == rhs; } +bool operator>=(const Approx& lhs, double rhs) { return lhs.m_value > rhs || lhs == rhs; } +bool operator<(double lhs, const Approx& rhs) { return lhs < rhs.m_value && lhs != rhs; } +bool operator<(const Approx& lhs, double rhs) { return lhs.m_value < rhs && lhs != rhs; } +bool operator>(double lhs, const Approx& rhs) { return lhs > rhs.m_value && lhs != rhs; } +bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; } + +String toString(const Approx& in) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + return String("Approx( ") + doctest::toString(in.m_value) + " )"; +} +const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); } + +} // namespace doctest + +#ifdef DOCTEST_CONFIG_DISABLE +namespace doctest { +Context::Context(int, const char* const*) {} +Context::~Context() = default; +void Context::applyCommandLine(int, const char* const*) {} +void Context::addFilter(const char*, const char*) {} +void Context::clearFilters() {} +void Context::setOption(const char*, int) {} +void Context::setOption(const char*, const char*) {} +bool Context::shouldExit() { return false; } +void Context::setAsDefaultForAssertsOutOfTestCases() {} +void Context::setAssertHandler(detail::assert_handler) {} +int Context::run() { return 0; } + +IReporter::~IReporter() = default; + +int IReporter::get_num_active_contexts() { return 0; } +const IContextScope* const* IReporter::get_active_contexts() { return nullptr; } +int IReporter::get_num_stringified_contexts() { return 0; } +const String* IReporter::get_stringified_contexts() { return nullptr; } + +int registerReporter(const char*, int, IReporter*) { return 0; } + +} // namespace doctest +#else // DOCTEST_CONFIG_DISABLE + +#if !defined(DOCTEST_CONFIG_COLORS_NONE) +#if !defined(DOCTEST_CONFIG_COLORS_WINDOWS) && !defined(DOCTEST_CONFIG_COLORS_ANSI) +#ifdef DOCTEST_PLATFORM_WINDOWS +#define DOCTEST_CONFIG_COLORS_WINDOWS +#else // linux +#define DOCTEST_CONFIG_COLORS_ANSI +#endif // platform +#endif // DOCTEST_CONFIG_COLORS_WINDOWS && DOCTEST_CONFIG_COLORS_ANSI +#endif // DOCTEST_CONFIG_COLORS_NONE + +namespace doctest_detail_test_suite_ns { +// holds the current test suite +doctest::detail::TestSuite& getCurrentTestSuite() { + static doctest::detail::TestSuite data{}; + return data; +} +} // namespace doctest_detail_test_suite_ns + +namespace doctest { +namespace { + // the int (priority) is part of the key for automatic sorting - sadly one can register a + // reporter with a duplicate name and a different priority but hopefully that won't happen often :| + typedef std::map<std::pair<int, String>, reporterCreatorFunc> reporterMap; + + reporterMap& getReporters() { + static reporterMap data; + return data; + } + reporterMap& getListeners() { + static reporterMap data; + return data; + } +} // namespace +namespace detail { +#define DOCTEST_ITERATE_THROUGH_REPORTERS(function, ...) \ + for(auto& curr_rep : g_cs->reporters_currently_used) \ + curr_rep->function(__VA_ARGS__) + + bool checkIfShouldThrow(assertType::Enum at) { + if(at & assertType::is_require) //!OCLINT bitwise operator in conditional + return true; + + if((at & assertType::is_check) //!OCLINT bitwise operator in conditional + && getContextOptions()->abort_after > 0 && + (g_cs->numAssertsFailed + g_cs->numAssertsFailedCurrentTest_atomic) >= + getContextOptions()->abort_after) + return true; + + return false; + } + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + DOCTEST_NORETURN void throwException() { + g_cs->shouldLogCurrentException = false; + throw TestFailureException(); + } // NOLINT(cert-err60-cpp) +#else // DOCTEST_CONFIG_NO_EXCEPTIONS + void throwException() {} +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +} // namespace detail + +namespace { + using namespace detail; + // matching of a string against a wildcard mask (case sensitivity configurable) taken from + // https://www.codeproject.com/Articles/1088/Wildcard-string-compare-globbing + int wildcmp(const char* str, const char* wild, bool caseSensitive) { + const char* cp = str; + const char* mp = wild; + + while((*str) && (*wild != '*')) { + if((caseSensitive ? (*wild != *str) : (tolower(*wild) != tolower(*str))) && + (*wild != '?')) { + return 0; + } + wild++; + str++; + } + + while(*str) { + if(*wild == '*') { + if(!*++wild) { + return 1; + } + mp = wild; + cp = str + 1; + } else if((caseSensitive ? (*wild == *str) : (tolower(*wild) == tolower(*str))) || + (*wild == '?')) { + wild++; + str++; + } else { + wild = mp; //!OCLINT parameter reassignment + str = cp++; //!OCLINT parameter reassignment + } + } + + while(*wild == '*') { + wild++; + } + return !*wild; + } + + //// C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html + //unsigned hashStr(unsigned const char* str) { + // unsigned long hash = 5381; + // char c; + // while((c = *str++)) + // hash = ((hash << 5) + hash) + c; // hash * 33 + c + // return hash; + //} + + // checks if the name matches any of the filters (and can be configured what to do when empty) + bool matchesAny(const char* name, const std::vector<String>& filters, bool matchEmpty, + bool caseSensitive) { + if(filters.empty() && matchEmpty) + return true; + for(auto& curr : filters) + if(wildcmp(name, curr.c_str(), caseSensitive)) + return true; + return false; + } +} // namespace +namespace detail { + + Subcase::Subcase(const String& name, const char* file, int line) + : m_signature({name, file, line}) { + auto* s = g_cs; + + // check subcase filters + if(s->subcasesStack.size() < size_t(s->subcase_filter_levels)) { + if(!matchesAny(m_signature.m_name.c_str(), s->filters[6], true, s->case_sensitive)) + return; + if(matchesAny(m_signature.m_name.c_str(), s->filters[7], false, s->case_sensitive)) + return; + } + + // if a Subcase on the same level has already been entered + if(s->subcasesStack.size() < size_t(s->subcasesCurrentMaxLevel)) { + s->should_reenter = true; + return; + } + + // push the current signature to the stack so we can check if the + // current stack + the current new subcase have been traversed + s->subcasesStack.push_back(m_signature); + if(s->subcasesPassed.count(s->subcasesStack) != 0) { + // pop - revert to previous stack since we've already passed this + s->subcasesStack.pop_back(); + return; + } + + s->subcasesCurrentMaxLevel = s->subcasesStack.size(); + m_entered = true; + + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); + } + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + + Subcase::~Subcase() { + if(m_entered) { + // only mark the subcase stack as passed if no subcases have been skipped + if(g_cs->should_reenter == false) + g_cs->subcasesPassed.insert(g_cs->subcasesStack); + g_cs->subcasesStack.pop_back(); + +#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) + if(std::uncaught_exceptions() > 0 +#else + if(std::uncaught_exception() +#endif + && g_cs->shouldLogCurrentException) { + DOCTEST_ITERATE_THROUGH_REPORTERS( + test_case_exception, {"exception thrown in subcase - will translate later " + "when the whole test case has been exited (cannot " + "translate while there is an active exception)", + false}); + g_cs->shouldLogCurrentException = false; + } + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); + } + } + + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP + DOCTEST_MSVC_SUPPRESS_WARNING_POP + + Subcase::operator bool() const { return m_entered; } + + Result::Result(bool passed, const String& decomposition) + : m_passed(passed) + , m_decomp(decomposition) {} + + ExpressionDecomposer::ExpressionDecomposer(assertType::Enum at) + : m_at(at) {} + + TestSuite& TestSuite::operator*(const char* in) { + m_test_suite = in; + // clear state + m_description = nullptr; + m_skip = false; + m_no_breaks = false; + m_no_output = false; + m_may_fail = false; + m_should_fail = false; + m_expected_failures = 0; + m_timeout = 0; + return *this; + } + + TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, + const char* type, int template_id) { + m_file = file; + m_line = line; + m_name = nullptr; // will be later overridden in operator* + m_test_suite = test_suite.m_test_suite; + m_description = test_suite.m_description; + m_skip = test_suite.m_skip; + m_no_breaks = test_suite.m_no_breaks; + m_no_output = test_suite.m_no_output; + m_may_fail = test_suite.m_may_fail; + m_should_fail = test_suite.m_should_fail; + m_expected_failures = test_suite.m_expected_failures; + m_timeout = test_suite.m_timeout; + + m_test = test; + m_type = type; + m_template_id = template_id; + } + + TestCase::TestCase(const TestCase& other) + : TestCaseData() { + *this = other; + } + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function + DOCTEST_MSVC_SUPPRESS_WARNING(26437) // Do not slice + TestCase& TestCase::operator=(const TestCase& other) { + static_cast<TestCaseData&>(*this) = static_cast<const TestCaseData&>(other); + + m_test = other.m_test; + m_type = other.m_type; + m_template_id = other.m_template_id; + m_full_name = other.m_full_name; + + if(m_template_id != -1) + m_name = m_full_name.c_str(); + return *this; + } + DOCTEST_MSVC_SUPPRESS_WARNING_POP + + TestCase& TestCase::operator*(const char* in) { + m_name = in; + // make a new name with an appended type for templated test case + if(m_template_id != -1) { + m_full_name = String(m_name) + m_type; + // redirect the name to point to the newly constructed full name + m_name = m_full_name.c_str(); + } + return *this; + } + + bool TestCase::operator<(const TestCase& other) const { + // this will be used only to differentiate between test cases - not relevant for sorting + if(m_line != other.m_line) + return m_line < other.m_line; + const int name_cmp = strcmp(m_name, other.m_name); + if(name_cmp != 0) + return name_cmp < 0; + const int file_cmp = m_file.compare(other.m_file); + if(file_cmp != 0) + return file_cmp < 0; + return m_template_id < other.m_template_id; + } + + // all the registered tests + std::set<TestCase>& getRegisteredTests() { + static std::set<TestCase> data; + return data; + } +} // namespace detail +namespace { + using namespace detail; + // for sorting tests by file/line + bool fileOrderComparator(const TestCase* lhs, const TestCase* rhs) { + // this is needed because MSVC gives different case for drive letters + // for __FILE__ when evaluated in a header and a source file + const int res = lhs->m_file.compare(rhs->m_file, bool(DOCTEST_MSVC)); + if(res != 0) + return res < 0; + if(lhs->m_line != rhs->m_line) + return lhs->m_line < rhs->m_line; + return lhs->m_template_id < rhs->m_template_id; + } + + // for sorting tests by suite/file/line + bool suiteOrderComparator(const TestCase* lhs, const TestCase* rhs) { + const int res = std::strcmp(lhs->m_test_suite, rhs->m_test_suite); + if(res != 0) + return res < 0; + return fileOrderComparator(lhs, rhs); + } + + // for sorting tests by name/suite/file/line + bool nameOrderComparator(const TestCase* lhs, const TestCase* rhs) { + const int res = std::strcmp(lhs->m_name, rhs->m_name); + if(res != 0) + return res < 0; + return suiteOrderComparator(lhs, rhs); + } + +#ifdef DOCTEST_CONFIG_COLORS_WINDOWS + HANDLE g_stdoutHandle; + WORD g_origFgAttrs; + WORD g_origBgAttrs; + bool g_attrsInitted = false; + + int colors_init() { + if(!g_attrsInitted) { + g_stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); + g_attrsInitted = true; + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo(g_stdoutHandle, &csbiInfo); + g_origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED | + BACKGROUND_BLUE | BACKGROUND_INTENSITY); + g_origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED | + FOREGROUND_BLUE | FOREGROUND_INTENSITY); + } + return 0; + } + + int dumy_init_console_colors = colors_init(); +#endif // DOCTEST_CONFIG_COLORS_WINDOWS + + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + void color_to_stream(std::ostream& s, Color::Enum code) { + static_cast<void>(s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS + static_cast<void>(code); // for DOCTEST_CONFIG_COLORS_NONE +#ifdef DOCTEST_CONFIG_COLORS_ANSI + if(g_no_colors || + (isatty(STDOUT_FILENO) == false && getContextOptions()->force_colors == false)) + return; + + auto col = ""; + // clang-format off + switch(code) { //!OCLINT missing break in switch statement / unnecessary default statement in covered switch statement + case Color::Red: col = "[0;31m"; break; + case Color::Green: col = "[0;32m"; break; + case Color::Blue: col = "[0;34m"; break; + case Color::Cyan: col = "[0;36m"; break; + case Color::Yellow: col = "[0;33m"; break; + case Color::Grey: col = "[1;30m"; break; + case Color::LightGrey: col = "[0;37m"; break; + case Color::BrightRed: col = "[1;31m"; break; + case Color::BrightGreen: col = "[1;32m"; break; + case Color::BrightWhite: col = "[1;37m"; break; + case Color::Bright: // invalid + case Color::None: + case Color::White: + default: col = "[0m"; + } + // clang-format on + s << "\033" << col; +#endif // DOCTEST_CONFIG_COLORS_ANSI + +#ifdef DOCTEST_CONFIG_COLORS_WINDOWS + if(g_no_colors || + (isatty(fileno(stdout)) == false && getContextOptions()->force_colors == false)) + return; + +#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(g_stdoutHandle, x | g_origBgAttrs) + + // clang-format off + switch (code) { + case Color::White: DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; + case Color::Red: DOCTEST_SET_ATTR(FOREGROUND_RED); break; + case Color::Green: DOCTEST_SET_ATTR(FOREGROUND_GREEN); break; + case Color::Blue: DOCTEST_SET_ATTR(FOREGROUND_BLUE); break; + case Color::Cyan: DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN); break; + case Color::Yellow: DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN); break; + case Color::Grey: DOCTEST_SET_ATTR(0); break; + case Color::LightGrey: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY); break; + case Color::BrightRed: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED); break; + case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN); break; + case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; + case Color::None: + case Color::Bright: // invalid + default: DOCTEST_SET_ATTR(g_origFgAttrs); + } + // clang-format on +#endif // DOCTEST_CONFIG_COLORS_WINDOWS + } + DOCTEST_CLANG_SUPPRESS_WARNING_POP + + std::vector<const IExceptionTranslator*>& getExceptionTranslators() { + static std::vector<const IExceptionTranslator*> data; + return data; + } + + String translateActiveException() { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + String res; + auto& translators = getExceptionTranslators(); + for(auto& curr : translators) + if(curr->translate(res)) + return res; + // clang-format off + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcatch-value") + try { + throw; + } catch(std::exception& ex) { + return ex.what(); + } catch(std::string& msg) { + return msg.c_str(); + } catch(const char* msg) { + return msg; + } catch(...) { + return "unknown exception"; + } + DOCTEST_GCC_SUPPRESS_WARNING_POP +// clang-format on +#else // DOCTEST_CONFIG_NO_EXCEPTIONS + return ""; +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + } +} // namespace + +namespace detail { + // used by the macros for registering tests + int regTest(const TestCase& tc) { + getRegisteredTests().insert(tc); + return 0; + } + + // sets the current test suite + int setTestSuite(const TestSuite& ts) { + doctest_detail_test_suite_ns::getCurrentTestSuite() = ts; + return 0; + } + +#ifdef DOCTEST_IS_DEBUGGER_ACTIVE + bool isDebuggerActive() { return DOCTEST_IS_DEBUGGER_ACTIVE(); } +#else // DOCTEST_IS_DEBUGGER_ACTIVE +#ifdef DOCTEST_PLATFORM_LINUX + class ErrnoGuard { + public: + ErrnoGuard() : m_oldErrno(errno) {} + ~ErrnoGuard() { errno = m_oldErrno; } + private: + int m_oldErrno; + }; + // See the comments in Catch2 for the reasoning behind this implementation: + // https://github.com/catchorg/Catch2/blob/v2.13.1/include/internal/catch_debugger.cpp#L79-L102 + bool isDebuggerActive() { + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for(std::string line; std::getline(in, line);) { + static const int PREFIX_LEN = 11; + if(line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0) { + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + return false; + } +#elif defined(DOCTEST_PLATFORM_MAC) + // The following function is taken directly from the following technical note: + // https://developer.apple.com/library/archive/qa/qa1361/_index.html + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive() { + int mib[4]; + kinfo_proc info; + size_t size; + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + info.kp_proc.p_flag = 0; + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + // Call sysctl. + size = sizeof(info); + if(sysctl(mib, DOCTEST_COUNTOF(mib), &info, &size, 0, 0) != 0) { + ::doctest::get_cerr() << "\nCall to sysctl failed - unable to determine if debugger is active **\n"; + return false; + } + // We're being debugged if the P_TRACED flag is set. + return ((info.kp_proc.p_flag & P_TRACED) != 0); + } +#elif DOCTEST_MSVC || defined(__MINGW32__) || defined(__MINGW64__) + bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; } +#else + bool isDebuggerActive() { return false; } +#endif // Platform +#endif // DOCTEST_IS_DEBUGGER_ACTIVE + + void registerExceptionTranslatorImpl(const IExceptionTranslator* et) { + if(std::find(getExceptionTranslators().begin(), getExceptionTranslators().end(), et) == + getExceptionTranslators().end()) + getExceptionTranslators().push_back(et); + } + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + void toStream(std::ostream* s, char* in) { *s << in; } + void toStream(std::ostream* s, const char* in) { *s << in; } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + void toStream(std::ostream* s, bool in) { *s << std::boolalpha << in << std::noboolalpha; } + void toStream(std::ostream* s, float in) { *s << in; } + void toStream(std::ostream* s, double in) { *s << in; } + void toStream(std::ostream* s, double long in) { *s << in; } + + void toStream(std::ostream* s, char in) { *s << in; } + void toStream(std::ostream* s, char signed in) { *s << in; } + void toStream(std::ostream* s, char unsigned in) { *s << in; } + void toStream(std::ostream* s, int short in) { *s << in; } + void toStream(std::ostream* s, int short unsigned in) { *s << in; } + void toStream(std::ostream* s, int in) { *s << in; } + void toStream(std::ostream* s, int unsigned in) { *s << in; } + void toStream(std::ostream* s, int long in) { *s << in; } + void toStream(std::ostream* s, int long unsigned in) { *s << in; } + void toStream(std::ostream* s, int long long in) { *s << in; } + void toStream(std::ostream* s, int long long unsigned in) { *s << in; } + + DOCTEST_THREAD_LOCAL std::vector<IContextScope*> g_infoContexts; // for logging with INFO() + + ContextScopeBase::ContextScopeBase() { + g_infoContexts.push_back(this); + } + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17 + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + + // destroy cannot be inlined into the destructor because that would mean calling stringify after + // ContextScope has been destroyed (base class destructors run after derived class destructors). + // Instead, ContextScope calls this method directly from its destructor. + void ContextScopeBase::destroy() { +#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) + if(std::uncaught_exceptions() > 0) { +#else + if(std::uncaught_exception()) { +#endif + std::ostringstream s; + this->stringify(&s); + g_cs->stringifiedContexts.push_back(s.str().c_str()); + } + g_infoContexts.pop_back(); + } + + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP + DOCTEST_MSVC_SUPPRESS_WARNING_POP +} // namespace detail +namespace { + using namespace detail; + +#if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH) + struct FatalConditionHandler + { + static void reset() {} + static void allocateAltStackMem() {} + static void freeAltStackMem() {} + }; +#else // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH + + void reportFatal(const std::string&); + +#ifdef DOCTEST_PLATFORM_WINDOWS + + struct SignalDefs + { + DWORD id; + const char* name; + }; + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + SignalDefs signalDefs[] = { + {static_cast<DWORD>(EXCEPTION_ILLEGAL_INSTRUCTION), + "SIGILL - Illegal instruction signal"}, + {static_cast<DWORD>(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow"}, + {static_cast<DWORD>(EXCEPTION_ACCESS_VIOLATION), + "SIGSEGV - Segmentation violation signal"}, + {static_cast<DWORD>(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error"}, + }; + + struct FatalConditionHandler + { + static LONG CALLBACK handleException(PEXCEPTION_POINTERS ExceptionInfo) { + // Multiple threads may enter this filter/handler at once. We want the error message to be printed on the + // console just once no matter how many threads have crashed. + static std::mutex mutex; + static bool execute = true; + { + std::lock_guard<std::mutex> lock(mutex); + if(execute) { + bool reported = false; + for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { + reportFatal(signalDefs[i].name); + reported = true; + break; + } + } + if(reported == false) + reportFatal("Unhandled SEH exception caught"); + if(isDebuggerActive() && !g_cs->no_breaks) + DOCTEST_BREAK_INTO_DEBUGGER(); + } + execute = false; + } + std::exit(EXIT_FAILURE); + } + + static void allocateAltStackMem() {} + static void freeAltStackMem() {} + + FatalConditionHandler() { + isSet = true; + // 32k seems enough for doctest to handle stack overflow, + // but the value was found experimentally, so there is no strong guarantee + guaranteeSize = 32 * 1024; + // Register an unhandled exception filter + previousTop = SetUnhandledExceptionFilter(handleException); + // Pass in guarantee size to be filled + SetThreadStackGuarantee(&guaranteeSize); + + // On Windows uncaught exceptions from another thread, exceptions from + // destructors, or calls to std::terminate are not a SEH exception + + // The terminal handler gets called when: + // - std::terminate is called FROM THE TEST RUNNER THREAD + // - an exception is thrown from a destructor FROM THE TEST RUNNER THREAD + original_terminate_handler = std::get_terminate(); + std::set_terminate([]() DOCTEST_NOEXCEPT { + reportFatal("Terminate handler called"); + if(isDebuggerActive() && !g_cs->no_breaks) + DOCTEST_BREAK_INTO_DEBUGGER(); + std::exit(EXIT_FAILURE); // explicitly exit - otherwise the SIGABRT handler may be called as well + }); + + // SIGABRT is raised when: + // - std::terminate is called FROM A DIFFERENT THREAD + // - an exception is thrown from a destructor FROM A DIFFERENT THREAD + // - an uncaught exception is thrown FROM A DIFFERENT THREAD + prev_sigabrt_handler = std::signal(SIGABRT, [](int signal) DOCTEST_NOEXCEPT { + if(signal == SIGABRT) { + reportFatal("SIGABRT - Abort (abnormal termination) signal"); + if(isDebuggerActive() && !g_cs->no_breaks) + DOCTEST_BREAK_INTO_DEBUGGER(); + std::exit(EXIT_FAILURE); + } + }); + + // The following settings are taken from google test, and more + // specifically from UnitTest::Run() inside of gtest.cc + + // the user does not want to see pop-up dialogs about crashes + prev_error_mode_1 = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | + SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); + // This forces the abort message to go to stderr in all circumstances. + prev_error_mode_2 = _set_error_mode(_OUT_TO_STDERR); + // In the debug version, Visual Studio pops up a separate dialog + // offering a choice to debug the aborted program - we want to disable that. + prev_abort_behavior = _set_abort_behavior(0x0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + // In debug mode, the Windows CRT can crash with an assertion over invalid + // input (e.g. passing an invalid file descriptor). The default handling + // for these assertions is to pop up a dialog and wait for user input. + // Instead ask the CRT to dump such assertions to stderr non-interactively. + prev_report_mode = _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + prev_report_file = _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + } + + static void reset() { + if(isSet) { + // Unregister handler and restore the old guarantee + SetUnhandledExceptionFilter(previousTop); + SetThreadStackGuarantee(&guaranteeSize); + std::set_terminate(original_terminate_handler); + std::signal(SIGABRT, prev_sigabrt_handler); + SetErrorMode(prev_error_mode_1); + _set_error_mode(prev_error_mode_2); + _set_abort_behavior(prev_abort_behavior, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + static_cast<void>(_CrtSetReportMode(_CRT_ASSERT, prev_report_mode)); + static_cast<void>(_CrtSetReportFile(_CRT_ASSERT, prev_report_file)); + isSet = false; + } + } + + ~FatalConditionHandler() { reset(); } + + private: + static UINT prev_error_mode_1; + static int prev_error_mode_2; + static unsigned int prev_abort_behavior; + static int prev_report_mode; + static _HFILE prev_report_file; + static void (*prev_sigabrt_handler)(int); + static std::terminate_handler original_terminate_handler; + static bool isSet; + static ULONG guaranteeSize; + static LPTOP_LEVEL_EXCEPTION_FILTER previousTop; + }; + + UINT FatalConditionHandler::prev_error_mode_1; + int FatalConditionHandler::prev_error_mode_2; + unsigned int FatalConditionHandler::prev_abort_behavior; + int FatalConditionHandler::prev_report_mode; + _HFILE FatalConditionHandler::prev_report_file; + void (*FatalConditionHandler::prev_sigabrt_handler)(int); + std::terminate_handler FatalConditionHandler::original_terminate_handler; + bool FatalConditionHandler::isSet = false; + ULONG FatalConditionHandler::guaranteeSize = 0; + LPTOP_LEVEL_EXCEPTION_FILTER FatalConditionHandler::previousTop = nullptr; + +#else // DOCTEST_PLATFORM_WINDOWS + + struct SignalDefs + { + int id; + const char* name; + }; + SignalDefs signalDefs[] = {{SIGINT, "SIGINT - Terminal interrupt signal"}, + {SIGILL, "SIGILL - Illegal instruction signal"}, + {SIGFPE, "SIGFPE - Floating point error signal"}, + {SIGSEGV, "SIGSEGV - Segmentation violation signal"}, + {SIGTERM, "SIGTERM - Termination request signal"}, + {SIGABRT, "SIGABRT - Abort (abnormal termination) signal"}}; + + struct FatalConditionHandler + { + static bool isSet; + static struct sigaction oldSigActions[DOCTEST_COUNTOF(signalDefs)]; + static stack_t oldSigStack; + static size_t altStackSize; + static char* altStackMem; + + static void handleSignal(int sig) { + const char* name = "<unknown signal>"; + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + SignalDefs& def = signalDefs[i]; + if(sig == def.id) { + name = def.name; + break; + } + } + reset(); + reportFatal(name); + raise(sig); + } + + static void allocateAltStackMem() { + altStackMem = new char[altStackSize]; + } + + static void freeAltStackMem() { + delete[] altStackMem; + } + + FatalConditionHandler() { + isSet = true; + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = altStackSize; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = {}; + sa.sa_handler = handleSignal; // NOLINT + sa.sa_flags = SA_ONSTACK; + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + + ~FatalConditionHandler() { reset(); } + static void reset() { + if(isSet) { + // Set signals back to previous values -- hopefully nobody overwrote them in the meantime + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + isSet = false; + } + } + }; + + bool FatalConditionHandler::isSet = false; + struct sigaction FatalConditionHandler::oldSigActions[DOCTEST_COUNTOF(signalDefs)] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + size_t FatalConditionHandler::altStackSize = 4 * SIGSTKSZ; + char* FatalConditionHandler::altStackMem = nullptr; + +#endif // DOCTEST_PLATFORM_WINDOWS +#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH + +} // namespace + +namespace { + using namespace detail; + +#ifdef DOCTEST_PLATFORM_WINDOWS +#define DOCTEST_OUTPUT_DEBUG_STRING(text) ::OutputDebugStringA(text) +#else + // TODO: integration with XCode and other IDEs +#define DOCTEST_OUTPUT_DEBUG_STRING(text) // NOLINT(clang-diagnostic-unused-macros) +#endif // Platform + + void addAssert(assertType::Enum at) { + if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional + g_cs->numAssertsCurrentTest_atomic++; + } + + void addFailedAssert(assertType::Enum at) { + if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional + g_cs->numAssertsFailedCurrentTest_atomic++; + } + +#if defined(DOCTEST_CONFIG_POSIX_SIGNALS) || defined(DOCTEST_CONFIG_WINDOWS_SEH) + void reportFatal(const std::string& message) { + g_cs->failure_flags |= TestCaseFailureReason::Crash; + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {message.c_str(), true}); + + while(g_cs->subcasesStack.size()) { + g_cs->subcasesStack.pop_back(); + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); + } + + g_cs->finalizeTestCaseData(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); + } +#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH +} // namespace +namespace detail { + + ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const char* exception_string) { + m_test_case = g_cs->currentTest; + m_at = at; + m_file = file; + m_line = line; + m_expr = expr; + m_failed = true; + m_threw = false; + m_threw_as = false; + m_exception_type = exception_type; + m_exception_string = exception_string; +#if DOCTEST_MSVC + if(m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC + ++m_expr; +#endif // MSVC + } + + void ResultBuilder::setResult(const Result& res) { + m_decomp = res.m_decomp; + m_failed = !res.m_passed; + } + + void ResultBuilder::translateException() { + m_threw = true; + m_exception = translateActiveException(); + } + + bool ResultBuilder::log() { + if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional + m_failed = !m_threw; + } else if((m_at & assertType::is_throws_as) && (m_at & assertType::is_throws_with)) { //!OCLINT + m_failed = !m_threw_as || (m_exception != m_exception_string); + } else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional + m_failed = !m_threw_as; + } else if(m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional + m_failed = m_exception != m_exception_string; + } else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional + m_failed = m_threw; + } + + if(m_exception.size()) + m_exception = String("\"") + m_exception + "\""; + + if(is_running_in_test) { + addAssert(m_at); + DOCTEST_ITERATE_THROUGH_REPORTERS(log_assert, *this); + + if(m_failed) + addFailedAssert(m_at); + } else if(m_failed) { + failed_out_of_a_testing_context(*this); + } + + return m_failed && isDebuggerActive() && !getContextOptions()->no_breaks && + (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger + } + + void ResultBuilder::react() const { + if(m_failed && checkIfShouldThrow(m_at)) + throwException(); + } + + void failed_out_of_a_testing_context(const AssertData& ad) { + if(g_cs->ah) + g_cs->ah(ad); + else + std::abort(); + } + + void decomp_assert(assertType::Enum at, const char* file, int line, const char* expr, + Result result) { + bool failed = !result.m_passed; + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp); + DOCTEST_ASSERT_IN_TESTS(result.m_decomp); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) + } + + MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) { + m_stream = getTlsOss(); + m_file = file; + m_line = line; + m_severity = severity; + } + + IExceptionTranslator::IExceptionTranslator() = default; + IExceptionTranslator::~IExceptionTranslator() = default; + + bool MessageBuilder::log() { + m_string = getTlsOssResult(); + DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this); + + const bool isWarn = m_severity & assertType::is_warn; + + // warn is just a message in this context so we don't treat it as an assert + if(!isWarn) { + addAssert(m_severity); + addFailedAssert(m_severity); + } + + return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn && + (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger + } + + void MessageBuilder::react() { + if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional + throwException(); + } + + MessageBuilder::~MessageBuilder() = default; +} // namespace detail +namespace { + using namespace detail; + + template <typename Ex> + DOCTEST_NORETURN void throw_exception(Ex const& e) { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + throw e; +#else // DOCTEST_CONFIG_NO_EXCEPTIONS + ::doctest::get_cerr() << "doctest will terminate because it needed to throw an exception.\n" + << "The message was: " << e.what() << '\n'; + std::terminate(); +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + } + +#ifndef DOCTEST_INTERNAL_ERROR +#define DOCTEST_INTERNAL_ERROR(msg) \ + throw_exception(std::logic_error( \ + __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg)) +#endif // DOCTEST_INTERNAL_ERROR + + // clang-format off + +// ================================================================================================= +// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp +// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. +// ================================================================================================= + + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); + + void encodeTo( std::ostream& os ) const; + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); + + private: + std::string m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ); + + ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT; + ScopedElement& operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT; + + ~ScopedElement(); + + ScopedElement& writeText( std::string const& text, bool indent = true ); + + template<typename T> + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer = nullptr; + }; + + XmlWriter( std::ostream& os = ::doctest::get_cout() ); + ~XmlWriter(); + + XmlWriter( XmlWriter const& ) = delete; + XmlWriter& operator=( XmlWriter const& ) = delete; + + XmlWriter& startElement( std::string const& name ); + + ScopedElement scopedElement( std::string const& name ); + + XmlWriter& endElement(); + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); + + XmlWriter& writeAttribute( std::string const& name, const char* attribute ); + + XmlWriter& writeAttribute( std::string const& name, bool attribute ); + + template<typename T> + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + std::stringstream rss; + rss << attribute; + return writeAttribute( name, rss.str() ); + } + + XmlWriter& writeText( std::string const& text, bool indent = true ); + + //XmlWriter& writeComment( std::string const& text ); + + //void writeStylesheetRef( std::string const& url ); + + //XmlWriter& writeBlankLine(); + + void ensureTagClosed(); + + private: + + void writeDeclaration(); + + void newlineIfNecessary(); + + bool m_tagIsOpen = false; + bool m_needsNewline = false; + std::vector<std::string> m_tags; + std::string m_indent; + std::ostream& m_os; + }; + +// ================================================================================================= +// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp +// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. +// ================================================================================================= + +using uchar = unsigned char; + +namespace { + + size_t trailingBytes(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return 2; + } + if ((c & 0xF0) == 0xE0) { + return 3; + } + if ((c & 0xF8) == 0xF0) { + return 4; + } + DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + uint32_t headerValue(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return c & 0x1F; + } + if ((c & 0xF0) == 0xE0) { + return c & 0x0F; + } + if ((c & 0xF8) == 0xF0) { + return c & 0x07; + } + DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + void hexEscapeChar(std::ostream& os, unsigned char c) { + std::ios_base::fmtflags f(os.flags()); + os << "\\x" + << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast<int>(c); + os.flags(f); + } + +} // anonymous namespace + + XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void XmlEncode::encodeTo( std::ostream& os ) const { + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: https://www.w3.org/TR/xml/#syntax) + + for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { + uchar c = m_str[idx]; + switch (c) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: https://www.w3.org/TR/xml/#syntax + if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') + os << ">"; + else + os << c; + break; + + case '\"': + if (m_forWhat == ForAttributes) + os << """; + else + os << c; + break; + + default: + // Check for control characters and invalid utf-8 + + // Escape control characters in standard ascii + // see https://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { + hexEscapeChar(os, c); + break; + } + + // Plain ASCII: Write it to stream + if (c < 0x7F) { + os << c; + break; + } + + // UTF-8 territory + // Check if the encoding is valid and if it is not, hex escape bytes. + // Important: We do not check the exact decoded values for validity, only the encoding format + // First check that this bytes is a valid lead byte: + // This means that it is not encoded as 1111 1XXX + // Or as 10XX XXXX + if (c < 0xC0 || + c >= 0xF8) { + hexEscapeChar(os, c); + break; + } + + auto encBytes = trailingBytes(c); + // Are there enough bytes left to avoid accessing out-of-bounds memory? + if (idx + encBytes - 1 >= m_str.size()) { + hexEscapeChar(os, c); + break; + } + // The header is valid, check data + // The next encBytes bytes must together be a valid utf-8 + // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + bool valid = true; + uint32_t value = headerValue(c); + for (std::size_t n = 1; n < encBytes; ++n) { + uchar nc = m_str[idx + n]; + valid &= ((nc & 0xC0) == 0x80); + value = (value << 6) | (nc & 0x3F); + } + + if ( + // Wrong bit pattern of following bytes + (!valid) || + // Overlong encodings + (value < 0x80) || + ( value < 0x800 && encBytes > 2) || // removed "0x80 <= value &&" because redundant + (0x800 < value && value < 0x10000 && encBytes > 3) || + // Encoded value out of range + (value >= 0x110000) + ) { + hexEscapeChar(os, c); + break; + } + + // If we got here, this is in fact a valid(ish) utf-8 sequence + for (std::size_t n = 0; n < encBytes; ++n) { + os << m_str[idx + n]; + } + idx += encBytes - 1; + break; + } + } + } + + std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT + : m_writer( other.m_writer ){ + other.m_writer = nullptr; + } + XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT { + if ( m_writer ) { + m_writer->endElement(); + } + m_writer = other.m_writer; + other.m_writer = nullptr; + return *this; + } + + + XmlWriter::ScopedElement::~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { + m_writer->writeText( text, indent ); + return *this; + } + + XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) + { + writeDeclaration(); + } + + XmlWriter::~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& XmlWriter::startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + m_os << m_indent << '<' << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& XmlWriter::endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } + else { + m_os << m_indent << "</" << m_tags.back() << ">"; + } + m_os << std::endl; + m_tags.pop_back(); + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, const char* attribute ) { + if( !name.empty() && attribute && attribute[0] != '\0' ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { + m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + m_os << m_indent; + m_os << XmlEncode( text ); + m_needsNewline = true; + } + return *this; + } + + //XmlWriter& XmlWriter::writeComment( std::string const& text ) { + // ensureTagClosed(); + // m_os << m_indent << "<!--" << text << "-->"; + // m_needsNewline = true; + // return *this; + //} + + //void XmlWriter::writeStylesheetRef( std::string const& url ) { + // m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n"; + //} + + //XmlWriter& XmlWriter::writeBlankLine() { + // ensureTagClosed(); + // m_os << '\n'; + // return *this; + //} + + void XmlWriter::ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << ">" << std::endl; + m_tagIsOpen = false; + } + } + + void XmlWriter::writeDeclaration() { + m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; + } + + void XmlWriter::newlineIfNecessary() { + if( m_needsNewline ) { + m_os << std::endl; + m_needsNewline = false; + } + } + +// ================================================================================================= +// End of copy-pasted code from Catch +// ================================================================================================= + + // clang-format on + + struct XmlReporter : public IReporter + { + XmlWriter xml; + std::mutex mutex; + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc = nullptr; + + XmlReporter(const ContextOptions& co) + : xml(*co.cout) + , opt(co) {} + + void log_contexts() { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + std::stringstream ss; + for(int i = 0; i < num_contexts; ++i) { + contexts[i]->stringify(&ss); + xml.scopedElement("Info").writeText(ss.str()); + ss.str(""); + } + } + } + + unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } + + void test_case_start_impl(const TestCaseData& in) { + bool open_ts_tag = false; + if(tc != nullptr) { // we have already opened a test suite + if(std::strcmp(tc->m_test_suite, in.m_test_suite) != 0) { + xml.endElement(); + open_ts_tag = true; + } + } + else { + open_ts_tag = true; // first test case ==> first test suite + } + + if(open_ts_tag) { + xml.startElement("TestSuite"); + xml.writeAttribute("name", in.m_test_suite); + } + + tc = ∈ + xml.startElement("TestCase") + .writeAttribute("name", in.m_name) + .writeAttribute("filename", skipPathFromFilename(in.m_file.c_str())) + .writeAttribute("line", line(in.m_line)) + .writeAttribute("description", in.m_description); + + if(Approx(in.m_timeout) != 0) + xml.writeAttribute("timeout", in.m_timeout); + if(in.m_may_fail) + xml.writeAttribute("may_fail", true); + if(in.m_should_fail) + xml.writeAttribute("should_fail", true); + } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData& in) override { + test_run_start(); + if(opt.list_reporters) { + for(auto& curr : getListeners()) + xml.scopedElement("Listener") + .writeAttribute("priority", curr.first.first) + .writeAttribute("name", curr.first.second); + for(auto& curr : getReporters()) + xml.scopedElement("Reporter") + .writeAttribute("priority", curr.first.first) + .writeAttribute("name", curr.first.second); + } else if(opt.count || opt.list_test_cases) { + for(unsigned i = 0; i < in.num_data; ++i) { + xml.scopedElement("TestCase").writeAttribute("name", in.data[i]->m_name) + .writeAttribute("testsuite", in.data[i]->m_test_suite) + .writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file.c_str())) + .writeAttribute("line", line(in.data[i]->m_line)); + } + xml.scopedElement("OverallResultsTestCases") + .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); + } else if(opt.list_test_suites) { + for(unsigned i = 0; i < in.num_data; ++i) + xml.scopedElement("TestSuite").writeAttribute("name", in.data[i]->m_test_suite); + xml.scopedElement("OverallResultsTestCases") + .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); + xml.scopedElement("OverallResultsTestSuites") + .writeAttribute("unskipped", in.run_stats->numTestSuitesPassingFilters); + } + xml.endElement(); + } + + void test_run_start() override { + // remove .exe extension - mainly to have the same output on UNIX and Windows + std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); +#ifdef DOCTEST_PLATFORM_WINDOWS + if(binary_name.rfind(".exe") != std::string::npos) + binary_name = binary_name.substr(0, binary_name.length() - 4); +#endif // DOCTEST_PLATFORM_WINDOWS + + xml.startElement("doctest").writeAttribute("binary", binary_name); + if(opt.no_version == false) + xml.writeAttribute("version", DOCTEST_VERSION_STR); + + // only the consequential ones (TODO: filters) + xml.scopedElement("Options") + .writeAttribute("order_by", opt.order_by.c_str()) + .writeAttribute("rand_seed", opt.rand_seed) + .writeAttribute("first", opt.first) + .writeAttribute("last", opt.last) + .writeAttribute("abort_after", opt.abort_after) + .writeAttribute("subcase_filter_levels", opt.subcase_filter_levels) + .writeAttribute("case_sensitive", opt.case_sensitive) + .writeAttribute("no_throw", opt.no_throw) + .writeAttribute("no_skip", opt.no_skip); + } + + void test_run_end(const TestRunStats& p) override { + if(tc) // the TestSuite tag - only if there has been at least 1 test case + xml.endElement(); + + xml.scopedElement("OverallResultsAsserts") + .writeAttribute("successes", p.numAsserts - p.numAssertsFailed) + .writeAttribute("failures", p.numAssertsFailed); + + xml.startElement("OverallResultsTestCases") + .writeAttribute("successes", + p.numTestCasesPassingFilters - p.numTestCasesFailed) + .writeAttribute("failures", p.numTestCasesFailed); + if(opt.no_skipped_summary == false) + xml.writeAttribute("skipped", p.numTestCases - p.numTestCasesPassingFilters); + xml.endElement(); + + xml.endElement(); + } + + void test_case_start(const TestCaseData& in) override { + test_case_start_impl(in); + xml.ensureTagClosed(); + } + + void test_case_reenter(const TestCaseData&) override {} + + void test_case_end(const CurrentTestCaseStats& st) override { + xml.startElement("OverallResultsAsserts") + .writeAttribute("successes", + st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest) + .writeAttribute("failures", st.numAssertsFailedCurrentTest); + if(opt.duration) + xml.writeAttribute("duration", st.seconds); + if(tc->m_expected_failures) + xml.writeAttribute("expected_failures", tc->m_expected_failures); + xml.endElement(); + + xml.endElement(); + } + + void test_case_exception(const TestCaseException& e) override { + std::lock_guard<std::mutex> lock(mutex); + + xml.scopedElement("Exception") + .writeAttribute("crash", e.is_crash) + .writeText(e.error_string.c_str()); + } + + void subcase_start(const SubcaseSignature& in) override { + std::lock_guard<std::mutex> lock(mutex); + + xml.startElement("SubCase") + .writeAttribute("name", in.m_name) + .writeAttribute("filename", skipPathFromFilename(in.m_file)) + .writeAttribute("line", line(in.m_line)); + xml.ensureTagClosed(); + } + + void subcase_end() override { xml.endElement(); } + + void log_assert(const AssertData& rb) override { + if(!rb.m_failed && !opt.success) + return; + + std::lock_guard<std::mutex> lock(mutex); + + xml.startElement("Expression") + .writeAttribute("success", !rb.m_failed) + .writeAttribute("type", assertString(rb.m_at)) + .writeAttribute("filename", skipPathFromFilename(rb.m_file)) + .writeAttribute("line", line(rb.m_line)); + + xml.scopedElement("Original").writeText(rb.m_expr); + + if(rb.m_threw) + xml.scopedElement("Exception").writeText(rb.m_exception.c_str()); + + if(rb.m_at & assertType::is_throws_as) + xml.scopedElement("ExpectedException").writeText(rb.m_exception_type); + if(rb.m_at & assertType::is_throws_with) + xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string); + if((rb.m_at & assertType::is_normal) && !rb.m_threw) + xml.scopedElement("Expanded").writeText(rb.m_decomp.c_str()); + + log_contexts(); + + xml.endElement(); + } + + void log_message(const MessageData& mb) override { + std::lock_guard<std::mutex> lock(mutex); + + xml.startElement("Message") + .writeAttribute("type", failureString(mb.m_severity)) + .writeAttribute("filename", skipPathFromFilename(mb.m_file)) + .writeAttribute("line", line(mb.m_line)); + + xml.scopedElement("Text").writeText(mb.m_string.c_str()); + + log_contexts(); + + xml.endElement(); + } + + void test_case_skipped(const TestCaseData& in) override { + if(opt.no_skipped_summary == false) { + test_case_start_impl(in); + xml.writeAttribute("skipped", "true"); + xml.endElement(); + } + } + }; + + DOCTEST_REGISTER_REPORTER("xml", 0, XmlReporter); + + void fulltext_log_assert_to_stream(std::ostream& s, const AssertData& rb) { + if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) == + 0) //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) " + << Color::None; + + if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional + s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n"; + } else if((rb.m_at & assertType::is_throws_as) && + (rb.m_at & assertType::is_throws_with)) { //!OCLINT + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" + << rb.m_exception_string << "\", " << rb.m_exception_type << " ) " << Color::None; + if(rb.m_threw) { + if(!rb.m_failed) { + s << "threw as expected!\n"; + } else { + s << "threw a DIFFERENT exception! (contents: " << rb.m_exception << ")\n"; + } + } else { + s << "did NOT throw at all!\n"; + } + } else if(rb.m_at & + assertType::is_throws_as) { //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", " + << rb.m_exception_type << " ) " << Color::None + << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" : + "threw a DIFFERENT exception: ") : + "did NOT throw at all!") + << Color::Cyan << rb.m_exception << "\n"; + } else if(rb.m_at & + assertType::is_throws_with) { //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" + << rb.m_exception_string << "\" ) " << Color::None + << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" : + "threw a DIFFERENT exception: ") : + "did NOT throw at all!") + << Color::Cyan << rb.m_exception << "\n"; + } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional + s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan + << rb.m_exception << "\n"; + } else { + s << (rb.m_threw ? "THREW exception: " : + (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n")); + if(rb.m_threw) + s << rb.m_exception << "\n"; + else + s << " values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n"; + } + } + + // TODO: + // - log_message() + // - respond to queries + // - honor remaining options + // - more attributes in tags + struct JUnitReporter : public IReporter + { + XmlWriter xml; + std::mutex mutex; + Timer timer; + std::vector<String> deepestSubcaseStackNames; + + struct JUnitTestCaseData + { + static std::string getCurrentTimestamp() { + // Beware, this is not reentrant because of backward compatibility issues + // Also, UTC only, again because of backward compatibility (%z is C++11) + time_t rawtime; + std::time(&rawtime); + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + + std::tm timeInfo; +#ifdef DOCTEST_PLATFORM_WINDOWS + gmtime_s(&timeInfo, &rawtime); +#else // DOCTEST_PLATFORM_WINDOWS + gmtime_r(&rawtime, &timeInfo); +#endif // DOCTEST_PLATFORM_WINDOWS + + char timeStamp[timeStampSize]; + const char* const fmt = "%Y-%m-%dT%H:%M:%SZ"; + + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); + return std::string(timeStamp); + } + + struct JUnitTestMessage + { + JUnitTestMessage(const std::string& _message, const std::string& _type, const std::string& _details) + : message(_message), type(_type), details(_details) {} + + JUnitTestMessage(const std::string& _message, const std::string& _details) + : message(_message), type(), details(_details) {} + + std::string message, type, details; + }; + + struct JUnitTestCase + { + JUnitTestCase(const std::string& _classname, const std::string& _name) + : classname(_classname), name(_name), time(0), failures() {} + + std::string classname, name; + double time; + std::vector<JUnitTestMessage> failures, errors; + }; + + void add(const std::string& classname, const std::string& name) { + testcases.emplace_back(classname, name); + } + + void appendSubcaseNamesToLastTestcase(std::vector<String> nameStack) { + for(auto& curr: nameStack) + if(curr.size()) + testcases.back().name += std::string("/") + curr.c_str(); + } + + void addTime(double time) { + if(time < 1e-4) + time = 0; + testcases.back().time = time; + totalSeconds += time; + } + + void addFailure(const std::string& message, const std::string& type, const std::string& details) { + testcases.back().failures.emplace_back(message, type, details); + ++totalFailures; + } + + void addError(const std::string& message, const std::string& details) { + testcases.back().errors.emplace_back(message, details); + ++totalErrors; + } + + std::vector<JUnitTestCase> testcases; + double totalSeconds = 0; + int totalErrors = 0, totalFailures = 0; + }; + + JUnitTestCaseData testCaseData; + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc = nullptr; + + JUnitReporter(const ContextOptions& co) + : xml(*co.cout) + , opt(co) {} + + unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData&) override {} + + void test_run_start() override {} + + void test_run_end(const TestRunStats& p) override { + // remove .exe extension - mainly to have the same output on UNIX and Windows + std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); +#ifdef DOCTEST_PLATFORM_WINDOWS + if(binary_name.rfind(".exe") != std::string::npos) + binary_name = binary_name.substr(0, binary_name.length() - 4); +#endif // DOCTEST_PLATFORM_WINDOWS + xml.startElement("testsuites"); + xml.startElement("testsuite").writeAttribute("name", binary_name) + .writeAttribute("errors", testCaseData.totalErrors) + .writeAttribute("failures", testCaseData.totalFailures) + .writeAttribute("tests", p.numAsserts); + if(opt.no_time_in_output == false) { + xml.writeAttribute("time", testCaseData.totalSeconds); + xml.writeAttribute("timestamp", JUnitTestCaseData::getCurrentTimestamp()); + } + if(opt.no_version == false) + xml.writeAttribute("doctest_version", DOCTEST_VERSION_STR); + + for(const auto& testCase : testCaseData.testcases) { + xml.startElement("testcase") + .writeAttribute("classname", testCase.classname) + .writeAttribute("name", testCase.name); + if(opt.no_time_in_output == false) + xml.writeAttribute("time", testCase.time); + // This is not ideal, but it should be enough to mimic gtest's junit output. + xml.writeAttribute("status", "run"); + + for(const auto& failure : testCase.failures) { + xml.scopedElement("failure") + .writeAttribute("message", failure.message) + .writeAttribute("type", failure.type) + .writeText(failure.details, false); + } + + for(const auto& error : testCase.errors) { + xml.scopedElement("error") + .writeAttribute("message", error.message) + .writeText(error.details); + } + + xml.endElement(); + } + xml.endElement(); + xml.endElement(); + } + + void test_case_start(const TestCaseData& in) override { + testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); + timer.start(); + } + + void test_case_reenter(const TestCaseData& in) override { + testCaseData.addTime(timer.getElapsedSeconds()); + testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); + deepestSubcaseStackNames.clear(); + + timer.start(); + testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); + } + + void test_case_end(const CurrentTestCaseStats&) override { + testCaseData.addTime(timer.getElapsedSeconds()); + testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); + deepestSubcaseStackNames.clear(); + } + + void test_case_exception(const TestCaseException& e) override { + std::lock_guard<std::mutex> lock(mutex); + testCaseData.addError("exception", e.error_string.c_str()); + } + + void subcase_start(const SubcaseSignature& in) override { + std::lock_guard<std::mutex> lock(mutex); + deepestSubcaseStackNames.push_back(in.m_name); + } + + void subcase_end() override {} + + void log_assert(const AssertData& rb) override { + if(!rb.m_failed) // report only failures & ignore the `success` option + return; + + std::lock_guard<std::mutex> lock(mutex); + + std::ostringstream os; + os << skipPathFromFilename(rb.m_file) << (opt.gnu_file_line ? ":" : "(") + << line(rb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl; + + fulltext_log_assert_to_stream(os, rb); + log_contexts(os); + testCaseData.addFailure(rb.m_decomp.c_str(), assertString(rb.m_at), os.str()); + } + + void log_message(const MessageData&) override {} + + void test_case_skipped(const TestCaseData&) override {} + + void log_contexts(std::ostringstream& s) { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + + s << " logged: "; + for(int i = 0; i < num_contexts; ++i) { + s << (i == 0 ? "" : " "); + contexts[i]->stringify(&s); + s << std::endl; + } + } + } + }; + + DOCTEST_REGISTER_REPORTER("junit", 0, JUnitReporter); + + struct Whitespace + { + int nrSpaces; + explicit Whitespace(int nr) + : nrSpaces(nr) {} + }; + + std::ostream& operator<<(std::ostream& out, const Whitespace& ws) { + if(ws.nrSpaces != 0) + out << std::setw(ws.nrSpaces) << ' '; + return out; + } + + struct ConsoleReporter : public IReporter + { + std::ostream& s; + bool hasLoggedCurrentTestStart; + std::vector<SubcaseSignature> subcasesStack; + size_t currentSubcaseLevel; + std::mutex mutex; + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc; + + ConsoleReporter(const ContextOptions& co) + : s(*co.cout) + , opt(co) {} + + ConsoleReporter(const ContextOptions& co, std::ostream& ostr) + : s(ostr) + , opt(co) {} + + // ========================================================================================= + // WHAT FOLLOWS ARE HELPERS USED BY THE OVERRIDES OF THE VIRTUAL METHODS OF THE INTERFACE + // ========================================================================================= + + void separator_to_stream() { + s << Color::Yellow + << "===============================================================================" + "\n"; + } + + const char* getSuccessOrFailString(bool success, assertType::Enum at, + const char* success_str) { + if(success) + return success_str; + return failureString(at); + } + + Color::Enum getSuccessOrFailColor(bool success, assertType::Enum at) { + return success ? Color::BrightGreen : + (at & assertType::is_warn) ? Color::Yellow : Color::Red; + } + + void successOrFailColoredStringToStream(bool success, assertType::Enum at, + const char* success_str = "SUCCESS") { + s << getSuccessOrFailColor(success, at) + << getSuccessOrFailString(success, at, success_str) << ": "; + } + + void log_contexts() { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + + s << Color::None << " logged: "; + for(int i = 0; i < num_contexts; ++i) { + s << (i == 0 ? "" : " "); + contexts[i]->stringify(&s); + s << "\n"; + } + } + + s << "\n"; + } + + // this was requested to be made virtual so users could override it + virtual void file_line_to_stream(const char* file, int line, + const char* tail = "") { + s << Color::LightGrey << skipPathFromFilename(file) << (opt.gnu_file_line ? ":" : "(") + << (opt.no_line_numbers ? 0 : line) // 0 or the real num depending on the option + << (opt.gnu_file_line ? ":" : "):") << tail; + } + + void logTestStart() { + if(hasLoggedCurrentTestStart) + return; + + separator_to_stream(); + file_line_to_stream(tc->m_file.c_str(), tc->m_line, "\n"); + if(tc->m_description) + s << Color::Yellow << "DESCRIPTION: " << Color::None << tc->m_description << "\n"; + if(tc->m_test_suite && tc->m_test_suite[0] != '\0') + s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n"; + if(strncmp(tc->m_name, " Scenario:", 11) != 0) + s << Color::Yellow << "TEST CASE: "; + s << Color::None << tc->m_name << "\n"; + + for(size_t i = 0; i < currentSubcaseLevel; ++i) { + if(subcasesStack[i].m_name[0] != '\0') + s << " " << subcasesStack[i].m_name << "\n"; + } + + if(currentSubcaseLevel != subcasesStack.size()) { + s << Color::Yellow << "\nDEEPEST SUBCASE STACK REACHED (DIFFERENT FROM THE CURRENT ONE):\n" << Color::None; + for(size_t i = 0; i < subcasesStack.size(); ++i) { + if(subcasesStack[i].m_name[0] != '\0') + s << " " << subcasesStack[i].m_name << "\n"; + } + } + + s << "\n"; + + hasLoggedCurrentTestStart = true; + } + + void printVersion() { + if(opt.no_version == false) + s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \"" + << DOCTEST_VERSION_STR << "\"\n"; + } + + void printIntro() { + printVersion(); + s << Color::Cyan << "[doctest] " << Color::None + << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n"; + } + + void printHelp() { + int sizePrefixDisplay = static_cast<int>(strlen(DOCTEST_OPTIONS_PREFIX_DISPLAY)); + printVersion(); + // clang-format off + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n"; + s << Color::Cyan << "[doctest] " << Color::None; + s << "filter values: \"str1,str2,str3\" (comma separated strings)\n"; + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "filters use wildcards for matching strings\n"; + s << Color::Cyan << "[doctest] " << Color::None; + s << "something passes a filter if any of the strings in a filter matches\n"; +#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"" DOCTEST_CONFIG_OPTIONS_PREFIX "\" PREFIX!!!\n"; +#endif + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "Query flags - the program quits after them. Available:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "?, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "help, -" DOCTEST_OPTIONS_PREFIX_DISPLAY "h " + << Whitespace(sizePrefixDisplay*0) << "prints this message\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "v, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "version " + << Whitespace(sizePrefixDisplay*1) << "prints the version\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "c, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "count " + << Whitespace(sizePrefixDisplay*1) << "prints the number of matching tests\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ltc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-cases " + << Whitespace(sizePrefixDisplay*1) << "lists all matching tests by name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-suites " + << Whitespace(sizePrefixDisplay*1) << "lists all matching test suites\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-reporters " + << Whitespace(sizePrefixDisplay*1) << "lists all registered reporters\n\n"; + // ================================================================================== << 79 + s << Color::Cyan << "[doctest] " << Color::None; + s << "The available <int>/<string> options/filters are:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case=<filters> " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case-exclude=<filters> " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file=<filters> " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their file\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sfe, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file-exclude=<filters> " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their file\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite=<filters> " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their test suite\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tse, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite-exclude=<filters> " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their test suite\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase=<filters> " + << Whitespace(sizePrefixDisplay*1) << "filters subcases by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-exclude=<filters> " + << Whitespace(sizePrefixDisplay*1) << "filters OUT subcases by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "r, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "reporters=<filters> " + << Whitespace(sizePrefixDisplay*1) << "reporters to use (console is default)\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out=<string> " + << Whitespace(sizePrefixDisplay*1) << "output filename\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by=<string> " + << Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n"; + s << Whitespace(sizePrefixDisplay*3) << " <string> - [file/suite/name/rand/none]\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "rs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "rand-seed=<int> " + << Whitespace(sizePrefixDisplay*1) << "seed for random ordering\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "f, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "first=<int> " + << Whitespace(sizePrefixDisplay*1) << "the first test passing the filters to\n"; + s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "l, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "last=<int> " + << Whitespace(sizePrefixDisplay*1) << "the last test passing the filters to\n"; + s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "aa, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "abort-after=<int> " + << Whitespace(sizePrefixDisplay*1) << "stop after <int> failed assertions\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "scfl,--" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-filter-levels=<int> " + << Whitespace(sizePrefixDisplay*1) << "apply filters for the first <int> levels\n"; + s << Color::Cyan << "\n[doctest] " << Color::None; + s << "Bool options - can be used like flags and true is assumed. Available:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "s, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "success=<bool> " + << Whitespace(sizePrefixDisplay*1) << "include successful assertions in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "cs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "case-sensitive=<bool> " + << Whitespace(sizePrefixDisplay*1) << "filters being treated as case sensitive\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "e, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "exit=<bool> " + << Whitespace(sizePrefixDisplay*1) << "exits after the tests finish\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration=<bool> " + << Whitespace(sizePrefixDisplay*1) << "prints the time duration of each test\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw=<bool> " + << Whitespace(sizePrefixDisplay*1) << "skips exceptions-related assert checks\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode=<bool> " + << Whitespace(sizePrefixDisplay*1) << "returns (or exits) always with success\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run=<bool> " + << Whitespace(sizePrefixDisplay*1) << "skips all runtime doctest operations\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version=<bool> " + << Whitespace(sizePrefixDisplay*1) << "omit the framework version in the output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors=<bool> " + << Whitespace(sizePrefixDisplay*1) << "disables colors in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "fc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "force-colors=<bool> " + << Whitespace(sizePrefixDisplay*1) << "use colors even when not in a tty\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nb, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-breaks=<bool> " + << Whitespace(sizePrefixDisplay*1) << "disables breakpoints in debuggers\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ns, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-skip=<bool> " + << Whitespace(sizePrefixDisplay*1) << "don't skip test cases marked as skip\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "gfl, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "gnu-file-line=<bool> " + << Whitespace(sizePrefixDisplay*1) << ":n: vs (n): for line numbers in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "npf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-path-filenames=<bool> " + << Whitespace(sizePrefixDisplay*1) << "only filenames and no paths in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nln, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-line-numbers=<bool> " + << Whitespace(sizePrefixDisplay*1) << "0 instead of real line numbers in output\n"; + // ================================================================================== << 79 + // clang-format on + + s << Color::Cyan << "\n[doctest] " << Color::None; + s << "for more information visit the project documentation\n\n"; + } + + void printRegisteredReporters() { + printVersion(); + auto printReporters = [this] (const reporterMap& reporters, const char* type) { + if(reporters.size()) { + s << Color::Cyan << "[doctest] " << Color::None << "listing all registered " << type << "\n"; + for(auto& curr : reporters) + s << "priority: " << std::setw(5) << curr.first.first + << " name: " << curr.first.second << "\n"; + } + }; + printReporters(getListeners(), "listeners"); + printReporters(getReporters(), "reporters"); + } + + void list_query_results() { + separator_to_stream(); + if(opt.count || opt.list_test_cases) { + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + } else if(opt.list_test_suites) { + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + s << Color::Cyan << "[doctest] " << Color::None + << "test suites with unskipped test cases passing the current filters: " + << g_cs->numTestSuitesPassingFilters << "\n"; + } + } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData& in) override { + if(opt.version) { + printVersion(); + } else if(opt.help) { + printHelp(); + } else if(opt.list_reporters) { + printRegisteredReporters(); + } else if(opt.count || opt.list_test_cases) { + if(opt.list_test_cases) { + s << Color::Cyan << "[doctest] " << Color::None + << "listing all test case names\n"; + separator_to_stream(); + } + + for(unsigned i = 0; i < in.num_data; ++i) + s << Color::None << in.data[i]->m_name << "\n"; + + separator_to_stream(); + + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + + } else if(opt.list_test_suites) { + s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n"; + separator_to_stream(); + + for(unsigned i = 0; i < in.num_data; ++i) + s << Color::None << in.data[i]->m_test_suite << "\n"; + + separator_to_stream(); + + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + s << Color::Cyan << "[doctest] " << Color::None + << "test suites with unskipped test cases passing the current filters: " + << g_cs->numTestSuitesPassingFilters << "\n"; + } + } + + void test_run_start() override { printIntro(); } + + void test_run_end(const TestRunStats& p) override { + separator_to_stream(); + s << std::dec; + + auto totwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters, static_cast<unsigned>(p.numAsserts))) + 1))); + auto passwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters - p.numTestCasesFailed, static_cast<unsigned>(p.numAsserts - p.numAssertsFailed))) + 1))); + auto failwidth = int(std::ceil(log10((std::max(p.numTestCasesFailed, static_cast<unsigned>(p.numAssertsFailed))) + 1))); + const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0; + s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(totwidth) + << p.numTestCasesPassingFilters << " | " + << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None : + Color::Green) + << std::setw(passwidth) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed" + << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None) + << std::setw(failwidth) << p.numTestCasesFailed << " failed" << Color::None << " |"; + if(opt.no_skipped_summary == false) { + const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters; + s << " " << (numSkipped == 0 ? Color::None : Color::Yellow) << numSkipped + << " skipped" << Color::None; + } + s << "\n"; + s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(totwidth) + << p.numAsserts << " | " + << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green) + << std::setw(passwidth) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None + << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(failwidth) + << p.numAssertsFailed << " failed" << Color::None << " |\n"; + s << Color::Cyan << "[doctest] " << Color::None + << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green) + << ((p.numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl; + } + + void test_case_start(const TestCaseData& in) override { + hasLoggedCurrentTestStart = false; + tc = ∈ + subcasesStack.clear(); + currentSubcaseLevel = 0; + } + + void test_case_reenter(const TestCaseData&) override { + subcasesStack.clear(); + } + + void test_case_end(const CurrentTestCaseStats& st) override { + if(tc->m_no_output) + return; + + // log the preamble of the test case only if there is something + // else to print - something other than that an assert has failed + if(opt.duration || + (st.failure_flags && st.failure_flags != TestCaseFailureReason::AssertFailure)) + logTestStart(); + + if(opt.duration) + s << Color::None << std::setprecision(6) << std::fixed << st.seconds + << " s: " << tc->m_name << "\n"; + + if(st.failure_flags & TestCaseFailureReason::Timeout) + s << Color::Red << "Test case exceeded time limit of " << std::setprecision(6) + << std::fixed << tc->m_timeout << "!\n"; + + if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedButDidnt) { + s << Color::Red << "Should have failed but didn't! Marking it as failed!\n"; + } else if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedAndDid) { + s << Color::Yellow << "Failed as expected so marking it as not failed\n"; + } else if(st.failure_flags & TestCaseFailureReason::CouldHaveFailedAndDid) { + s << Color::Yellow << "Allowed to fail so marking it as not failed\n"; + } else if(st.failure_flags & TestCaseFailureReason::DidntFailExactlyNumTimes) { + s << Color::Red << "Didn't fail exactly " << tc->m_expected_failures + << " times so marking it as failed!\n"; + } else if(st.failure_flags & TestCaseFailureReason::FailedExactlyNumTimes) { + s << Color::Yellow << "Failed exactly " << tc->m_expected_failures + << " times as expected so marking it as not failed!\n"; + } + if(st.failure_flags & TestCaseFailureReason::TooManyFailedAsserts) { + s << Color::Red << "Aborting - too many failed asserts!\n"; + } + s << Color::None; // lgtm [cpp/useless-expression] + } + + void test_case_exception(const TestCaseException& e) override { + if(tc->m_no_output) + return; + + logTestStart(); + + file_line_to_stream(tc->m_file.c_str(), tc->m_line, " "); + successOrFailColoredStringToStream(false, e.is_crash ? assertType::is_require : + assertType::is_check); + s << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ") + << Color::Cyan << e.error_string << "\n"; + + int num_stringified_contexts = get_num_stringified_contexts(); + if(num_stringified_contexts) { + auto stringified_contexts = get_stringified_contexts(); + s << Color::None << " logged: "; + for(int i = num_stringified_contexts; i > 0; --i) { + s << (i == num_stringified_contexts ? "" : " ") + << stringified_contexts[i - 1] << "\n"; + } + } + s << "\n" << Color::None; + } + + void subcase_start(const SubcaseSignature& subc) override { + std::lock_guard<std::mutex> lock(mutex); + subcasesStack.push_back(subc); + ++currentSubcaseLevel; + hasLoggedCurrentTestStart = false; + } + + void subcase_end() override { + std::lock_guard<std::mutex> lock(mutex); + --currentSubcaseLevel; + hasLoggedCurrentTestStart = false; + } + + void log_assert(const AssertData& rb) override { + if((!rb.m_failed && !opt.success) || tc->m_no_output) + return; + + std::lock_guard<std::mutex> lock(mutex); + + logTestStart(); + + file_line_to_stream(rb.m_file, rb.m_line, " "); + successOrFailColoredStringToStream(!rb.m_failed, rb.m_at); + + fulltext_log_assert_to_stream(s, rb); + + log_contexts(); + } + + void log_message(const MessageData& mb) override { + if(tc->m_no_output) + return; + + std::lock_guard<std::mutex> lock(mutex); + + logTestStart(); + + file_line_to_stream(mb.m_file, mb.m_line, " "); + s << getSuccessOrFailColor(false, mb.m_severity) + << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity, + "MESSAGE") << ": "; + s << Color::None << mb.m_string << "\n"; + log_contexts(); + } + + void test_case_skipped(const TestCaseData&) override {} + }; + + DOCTEST_REGISTER_REPORTER("console", 0, ConsoleReporter); + +#ifdef DOCTEST_PLATFORM_WINDOWS + struct DebugOutputWindowReporter : public ConsoleReporter + { + DOCTEST_THREAD_LOCAL static std::ostringstream oss; + + DebugOutputWindowReporter(const ContextOptions& co) + : ConsoleReporter(co, oss) {} + +#define DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(func, type, arg) \ + void func(type arg) override { \ + bool with_col = g_no_colors; \ + g_no_colors = false; \ + ConsoleReporter::func(arg); \ + if(oss.tellp() != std::streampos{}) { \ + DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str()); \ + oss.str(""); \ + } \ + g_no_colors = with_col; \ + } + + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_start, DOCTEST_EMPTY, DOCTEST_EMPTY) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_end, const TestRunStats&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_start, const TestCaseData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_reenter, const TestCaseData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_end, const CurrentTestCaseStats&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_exception, const TestCaseException&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_start, const SubcaseSignature&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_end, DOCTEST_EMPTY, DOCTEST_EMPTY) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_assert, const AssertData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_message, const MessageData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_skipped, const TestCaseData&, in) + }; + + DOCTEST_THREAD_LOCAL std::ostringstream DebugOutputWindowReporter::oss; +#endif // DOCTEST_PLATFORM_WINDOWS + + // the implementation of parseOption() + bool parseOptionImpl(int argc, const char* const* argv, const char* pattern, String* value) { + // going from the end to the beginning and stopping on the first occurrence from the end + for(int i = argc; i > 0; --i) { + auto index = i - 1; + auto temp = std::strstr(argv[index], pattern); + if(temp && (value || strlen(temp) == strlen(pattern))) { //!OCLINT prefer early exits and continue + // eliminate matches in which the chars before the option are not '-' + bool noBadCharsFound = true; + auto curr = argv[index]; + while(curr != temp) { + if(*curr++ != '-') { + noBadCharsFound = false; + break; + } + } + if(noBadCharsFound && argv[index][0] == '-') { + if(value) { + // parsing the value of an option + temp += strlen(pattern); + const unsigned len = strlen(temp); + if(len) { + *value = temp; + return true; + } + } else { + // just a flag - no value + return true; + } + } + } + } + return false; + } + + // parses an option and returns the string after the '=' character + bool parseOption(int argc, const char* const* argv, const char* pattern, String* value = nullptr, + const String& defaultVal = String()) { + if(value) + *value = defaultVal; +#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + // offset (normally 3 for "dt-") to skip prefix + if(parseOptionImpl(argc, argv, pattern + strlen(DOCTEST_CONFIG_OPTIONS_PREFIX), value)) + return true; +#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + return parseOptionImpl(argc, argv, pattern, value); + } + + // locates a flag on the command line + bool parseFlag(int argc, const char* const* argv, const char* pattern) { + return parseOption(argc, argv, pattern); + } + + // parses a comma separated list of words after a pattern in one of the arguments in argv + bool parseCommaSepArgs(int argc, const char* const* argv, const char* pattern, + std::vector<String>& res) { + String filtersString; + if(parseOption(argc, argv, pattern, &filtersString)) { + // tokenize with "," as a separator + // cppcheck-suppress strtokCalled + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + auto pch = std::strtok(filtersString.c_str(), ","); // modifies the string + while(pch != nullptr) { + if(strlen(pch)) + res.push_back(pch); + // uses the strtok() internal state to go to the next token + // cppcheck-suppress strtokCalled + pch = std::strtok(nullptr, ","); + } + DOCTEST_CLANG_SUPPRESS_WARNING_POP + return true; + } + return false; + } + + enum optionType + { + option_bool, + option_int + }; + + // parses an int/bool option from the command line + bool parseIntOption(int argc, const char* const* argv, const char* pattern, optionType type, + int& res) { + String parsedValue; + if(!parseOption(argc, argv, pattern, &parsedValue)) + return false; + + if(type == 0) { + // boolean + const char positive[][5] = {"1", "true", "on", "yes"}; // 5 - strlen("true") + 1 + const char negative[][6] = {"0", "false", "off", "no"}; // 6 - strlen("false") + 1 + + // if the value matches any of the positive/negative possibilities + for(unsigned i = 0; i < 4; i++) { + if(parsedValue.compare(positive[i], true) == 0) { + res = 1; //!OCLINT parameter reassignment + return true; + } + if(parsedValue.compare(negative[i], true) == 0) { + res = 0; //!OCLINT parameter reassignment + return true; + } + } + } else { + // integer + // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse... + int theInt = std::atoi(parsedValue.c_str()); // NOLINT + if(theInt != 0) { + res = theInt; //!OCLINT parameter reassignment + return true; + } + } + return false; + } +} // namespace + +Context::Context(int argc, const char* const* argv) + : p(new detail::ContextState) { + parseArgs(argc, argv, true); + if(argc) + p->binary_name = argv[0]; +} + +Context::~Context() { + if(g_cs == p) + g_cs = nullptr; + delete p; +} + +void Context::applyCommandLine(int argc, const char* const* argv) { + parseArgs(argc, argv); + if(argc) + p->binary_name = argv[0]; +} + +// parses args +void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { + using namespace detail; + + // clang-format off + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file=", p->filters[0]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sf=", p->filters[0]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file-exclude=",p->filters[1]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sfe=", p->filters[1]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite=", p->filters[2]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ts=", p->filters[2]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite-exclude=", p->filters[3]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tse=", p->filters[3]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case=", p->filters[4]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tc=", p->filters[4]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case-exclude=", p->filters[5]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tce=", p->filters[5]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase=", p->filters[6]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sc=", p->filters[6]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase-exclude=", p->filters[7]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sce=", p->filters[7]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "reporters=", p->filters[8]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "r=", p->filters[8]); + // clang-format on + + int intRes = 0; + String strRes; + +#define DOCTEST_PARSE_AS_BOOL_OR_FLAG(name, sname, var, default) \ + if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_bool, intRes) || \ + parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_bool, intRes)) \ + p->var = static_cast<bool>(intRes); \ + else if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name) || \ + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname)) \ + p->var = true; \ + else if(withDefaults) \ + p->var = default + +#define DOCTEST_PARSE_INT_OPTION(name, sname, var, default) \ + if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_int, intRes) || \ + parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_int, intRes)) \ + p->var = intRes; \ + else if(withDefaults) \ + p->var = default + +#define DOCTEST_PARSE_STR_OPTION(name, sname, var, default) \ + if(parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", &strRes, default) || \ + parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", &strRes, default) || \ + withDefaults) \ + p->var = strRes + + // clang-format off + DOCTEST_PARSE_STR_OPTION("out", "o", out, ""); + DOCTEST_PARSE_STR_OPTION("order-by", "ob", order_by, "file"); + DOCTEST_PARSE_INT_OPTION("rand-seed", "rs", rand_seed, 0); + + DOCTEST_PARSE_INT_OPTION("first", "f", first, 0); + DOCTEST_PARSE_INT_OPTION("last", "l", last, UINT_MAX); + + DOCTEST_PARSE_INT_OPTION("abort-after", "aa", abort_after, 0); + DOCTEST_PARSE_INT_OPTION("subcase-filter-levels", "scfl", subcase_filter_levels, INT_MAX); + + DOCTEST_PARSE_AS_BOOL_OR_FLAG("success", "s", success, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("exit", "e", exit, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("duration", "d", duration, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-throw", "nt", no_throw, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-exitcode", "ne", no_exitcode, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-run", "nr", no_run, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-version", "nv", no_version, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-colors", "nc", no_colors, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("force-colors", "fc", force_colors, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-breaks", "nb", no_breaks, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skip", "ns", no_skip, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("gnu-file-line", "gfl", gnu_file_line, !bool(DOCTEST_MSVC)); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-path-filenames", "npf", no_path_in_filenames, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-line-numbers", "nln", no_line_numbers, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-debug-output", "ndo", no_debug_output, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skipped-summary", "nss", no_skipped_summary, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-time-in-output", "ntio", no_time_in_output, false); + // clang-format on + + if(withDefaults) { + p->help = false; + p->version = false; + p->count = false; + p->list_test_cases = false; + p->list_test_suites = false; + p->list_reporters = false; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "help") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "h") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "?")) { + p->help = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "version") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "v")) { + p->version = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "count") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "c")) { + p->count = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-cases") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ltc")) { + p->list_test_cases = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-suites") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lts")) { + p->list_test_suites = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-reporters") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lr")) { + p->list_reporters = true; + p->exit = true; + } +} + +// allows the user to add procedurally to the filters from the command line +void Context::addFilter(const char* filter, const char* value) { setOption(filter, value); } + +// allows the user to clear all filters from the command line +void Context::clearFilters() { + for(auto& curr : p->filters) + curr.clear(); +} + +// allows the user to override procedurally the int/bool options from the command line +void Context::setOption(const char* option, int value) { + setOption(option, toString(value).c_str()); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) +} + +// allows the user to override procedurally the string options from the command line +void Context::setOption(const char* option, const char* value) { + auto argv = String("-") + option + "=" + value; + auto lvalue = argv.c_str(); + parseArgs(1, &lvalue); +} + +// users should query this in their main() and exit the program if true +bool Context::shouldExit() { return p->exit; } + +void Context::setAsDefaultForAssertsOutOfTestCases() { g_cs = p; } + +void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; } + +// the main function that does all the filtering and test running +int Context::run() { + using namespace detail; + + // save the old context state in case such was setup - for using asserts out of a testing context + auto old_cs = g_cs; + // this is the current contest + g_cs = p; + is_running_in_test = true; + + g_no_colors = p->no_colors; + p->resetRunData(); + + // stdout by default + p->cout = &::doctest::get_cout(); + p->cerr = &::doctest::get_cerr(); + + // or to a file if specified + std::fstream fstr; + if(p->out.size()) { + fstr.open(p->out.c_str(), std::fstream::out); + p->cout = &fstr; + } + + FatalConditionHandler::allocateAltStackMem(); + + auto cleanup_and_return = [&]() { + FatalConditionHandler::freeAltStackMem(); + + if(fstr.is_open()) + fstr.close(); + + // restore context + g_cs = old_cs; + is_running_in_test = false; + + // we have to free the reporters which were allocated when the run started + for(auto& curr : p->reporters_currently_used) + delete curr; + p->reporters_currently_used.clear(); + + if(p->numTestCasesFailed && !p->no_exitcode) + return EXIT_FAILURE; + return EXIT_SUCCESS; + }; + + // setup default reporter if none is given through the command line + if(p->filters[8].empty()) + p->filters[8].push_back("console"); + + // check to see if any of the registered reporters has been selected + for(auto& curr : getReporters()) { + if(matchesAny(curr.first.second.c_str(), p->filters[8], false, p->case_sensitive)) + p->reporters_currently_used.push_back(curr.second(*g_cs)); + } + + // TODO: check if there is nothing in reporters_currently_used + + // prepend all listeners + for(auto& curr : getListeners()) + p->reporters_currently_used.insert(p->reporters_currently_used.begin(), curr.second(*g_cs)); + +#ifdef DOCTEST_PLATFORM_WINDOWS + if(isDebuggerActive() && p->no_debug_output == false) + p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs)); +#endif // DOCTEST_PLATFORM_WINDOWS + + // handle version, help and no_run + if(p->no_run || p->version || p->help || p->list_reporters) { + DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, QueryData()); + + return cleanup_and_return(); + } + + std::vector<const TestCase*> testArray; + for(auto& curr : getRegisteredTests()) + testArray.push_back(&curr); + p->numTestCases = testArray.size(); + + // sort the collected records + if(!testArray.empty()) { + if(p->order_by.compare("file", true) == 0) { + std::sort(testArray.begin(), testArray.end(), fileOrderComparator); + } else if(p->order_by.compare("suite", true) == 0) { + std::sort(testArray.begin(), testArray.end(), suiteOrderComparator); + } else if(p->order_by.compare("name", true) == 0) { + std::sort(testArray.begin(), testArray.end(), nameOrderComparator); + } else if(p->order_by.compare("rand", true) == 0) { + std::srand(p->rand_seed); + + // random_shuffle implementation + const auto first = &testArray[0]; + for(size_t i = testArray.size() - 1; i > 0; --i) { + int idxToSwap = std::rand() % (i + 1); // NOLINT + + const auto temp = first[i]; + + first[i] = first[idxToSwap]; + first[idxToSwap] = temp; + } + } else if(p->order_by.compare("none", true) == 0) { + // means no sorting - beneficial for death tests which call into the executable + // with a specific test case in mind - we don't want to slow down the startup times + } + } + + std::set<String> testSuitesPassingFilt; + + bool query_mode = p->count || p->list_test_cases || p->list_test_suites; + std::vector<const TestCaseData*> queryResults; + + if(!query_mode) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, DOCTEST_EMPTY); + + // invoke the registered functions if they match the filter criteria (or just count them) + for(auto& curr : testArray) { + const auto& tc = *curr; + + bool skip_me = false; + if(tc.m_skip && !p->no_skip) + skip_me = true; + + if(!matchesAny(tc.m_file.c_str(), p->filters[0], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_file.c_str(), p->filters[1], false, p->case_sensitive)) + skip_me = true; + if(!matchesAny(tc.m_test_suite, p->filters[2], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_test_suite, p->filters[3], false, p->case_sensitive)) + skip_me = true; + if(!matchesAny(tc.m_name, p->filters[4], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_name, p->filters[5], false, p->case_sensitive)) + skip_me = true; + + if(!skip_me) + p->numTestCasesPassingFilters++; + + // skip the test if it is not in the execution range + if((p->last < p->numTestCasesPassingFilters && p->first <= p->last) || + (p->first > p->numTestCasesPassingFilters)) + skip_me = true; + + if(skip_me) { + if(!query_mode) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_skipped, tc); + continue; + } + + // do not execute the test if we are to only count the number of filter passing tests + if(p->count) + continue; + + // print the name of the test and don't execute it + if(p->list_test_cases) { + queryResults.push_back(&tc); + continue; + } + + // print the name of the test suite if not done already and don't execute it + if(p->list_test_suites) { + if((testSuitesPassingFilt.count(tc.m_test_suite) == 0) && tc.m_test_suite[0] != '\0') { + queryResults.push_back(&tc); + testSuitesPassingFilt.insert(tc.m_test_suite); + p->numTestSuitesPassingFilters++; + } + continue; + } + + // execute the test if it passes all the filtering + { + p->currentTest = &tc; + + p->failure_flags = TestCaseFailureReason::None; + p->seconds = 0; + + // reset atomic counters + p->numAssertsFailedCurrentTest_atomic = 0; + p->numAssertsCurrentTest_atomic = 0; + + p->subcasesPassed.clear(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc); + + p->timer.start(); + + bool run_test = true; + + do { + // reset some of the fields for subcases (except for the set of fully passed ones) + p->should_reenter = false; + p->subcasesCurrentMaxLevel = 0; + p->subcasesStack.clear(); + + p->shouldLogCurrentException = true; + + // reset stuff for logging with INFO() + p->stringifiedContexts.clear(); + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + try { +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +// MSVC 2015 diagnoses fatalConditionHandler as unused (because reset() is a static method) +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4101) // unreferenced local variable + FatalConditionHandler fatalConditionHandler; // Handle signals + // execute the test + tc.m_test(); + fatalConditionHandler.reset(); +DOCTEST_MSVC_SUPPRESS_WARNING_POP +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + } catch(const TestFailureException&) { + p->failure_flags |= TestCaseFailureReason::AssertFailure; + } catch(...) { + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, + {translateActiveException(), false}); + p->failure_flags |= TestCaseFailureReason::Exception; + } +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + + // exit this loop if enough assertions have failed - even if there are more subcases + if(p->abort_after > 0 && + p->numAssertsFailed + p->numAssertsFailedCurrentTest_atomic >= p->abort_after) { + run_test = false; + p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts; + } + + if(p->should_reenter && run_test) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_reenter, tc); + if(!p->should_reenter) + run_test = false; + } while(run_test); + + p->finalizeTestCaseData(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); + + p->currentTest = nullptr; + + // stop executing tests if enough assertions have failed + if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after) + break; + } + } + + if(!query_mode) { + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); + } else { + QueryData qdata; + qdata.run_stats = g_cs; + qdata.data = queryResults.data(); + qdata.num_data = unsigned(queryResults.size()); + DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata); + } + + // see these issues on the reasoning for this: + // - https://github.com/onqtam/doctest/issues/143#issuecomment-414418903 + // - https://github.com/onqtam/doctest/issues/126 + auto DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS = []() DOCTEST_NOINLINE + { ::doctest::get_cout() << std::string(); }; + DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS(); + + return cleanup_and_return(); +} + +IReporter::~IReporter() = default; + +int IReporter::get_num_active_contexts() { return detail::g_infoContexts.size(); } +const IContextScope* const* IReporter::get_active_contexts() { + return get_num_active_contexts() ? &detail::g_infoContexts[0] : nullptr; +} + +int IReporter::get_num_stringified_contexts() { return detail::g_cs->stringifiedContexts.size(); } +const String* IReporter::get_stringified_contexts() { + return get_num_stringified_contexts() ? &detail::g_cs->stringifiedContexts[0] : nullptr; +} + +namespace detail { + void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c, bool isReporter) { + if(isReporter) + getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); + else + getListeners().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); + } +} // namespace detail + +} // namespace doctest + +#endif // DOCTEST_CONFIG_DISABLE + +#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) // 'function' : must be 'attribute' - see issue #182 +int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); } +DOCTEST_MSVC_SUPPRESS_WARNING_POP +#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_LIBRARY_IMPLEMENTATION +#endif // DOCTEST_CONFIG_IMPLEMENT diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_main.cpp b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_main.cpp new file mode 100644 index 00000000..b8cba74a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_main.cpp @@ -0,0 +1,107 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> + +#define DOCTEST_CONFIG_IMPLEMENT +#include "util_test_framework.hpp" + +namespace ams { + + namespace { + + constexpr size_t MallocBufferSize = 16_MB; + alignas(os::MemoryPageSize) constinit u8 g_malloc_buffer[MallocBufferSize]; + + } + + namespace hos { + + bool IsUnitTestProgramForSetVersion() { return true; } + + } + + namespace init { + + void InitializeSystemModuleBeforeConstructors() { + /* Catch has global-ctors which allocate, so we need to do this earlier than normal. */ + init::InitializeAllocator(g_malloc_buffer, sizeof(g_malloc_buffer)); + } + + void InitializeSystemModule() { /* ... */ } + + void FinalizeSystemModule() { /* ... */ } + + void Startup() { /* ... */ } + + } + + void NORETURN Exit(int rc) { + AMS_UNUSED(rc); + AMS_ABORT("Exit called by immortal process"); + } + + void Main() { + /* Ensure our thread priority and core mask is correct. */ + { + auto * const cur_thread = os::GetCurrentThread(); + os::SetThreadCoreMask(cur_thread, 3, (1ul << 3)); + os::ChangeThreadPriority(cur_thread, 0); + } + + /* Run tests. */ + { + doctest::Context ctx; + + ctx.applyCommandLine(os::GetHostArgc(), os::GetHostArgv()); + + ctx.run(); + } + + AMS_INFINITE_LOOP(); + + /* This can never be reached. */ + AMS_ASSUME(false); + } + +} + +namespace doctest { + + namespace { + + class OutputDebugStringStream : public std::stringbuf { + public: + OutputDebugStringStream() = default; + ~OutputDebugStringStream() { pubsync(); } + + int sync() override { + const auto message = str(); + return R_SUCCEEDED(ams::svc::OutputDebugString(message.c_str(), message.length())) ? 0 : -1; + } + }; + + } + + std::ostream& get_cout() { + static std::ostream ret(new OutputDebugStringStream); + return ret; + } + + std::ostream& get_cerr() { + return get_cout(); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_preemption_priority.cpp b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_preemption_priority.cpp new file mode 100644 index 00000000..f74d57f9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_preemption_priority.cpp @@ -0,0 +1,91 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "util_common.hpp" +#include "util_scoped_heap.hpp" + +namespace ams::test { + + namespace { + + constinit volatile bool g_spinloop; + + void TestPreemptionPriorityThreadFunction(volatile bool *executed) { + /* While we should, note that we're executing. */ + while (g_spinloop) { + __asm__ __volatile__("" ::: "memory"); + *executed = true; + __asm__ __volatile__("" ::: "memory"); + } + + /* Exit the thread. */ + svc::ExitThread(); + } + + } + + DOCTEST_TEST_CASE( "The scheduler is preemptive at the preemptive priority and cooperative for all other priorities" ) { + /* Create heap. */ + ScopedHeap heap(3 * os::MemoryPageSize); + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(heap.GetAddress() + os::MemoryPageSize, os::MemoryPageSize, svc::MemoryPermission_None))); + ON_SCOPE_EXIT { + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(heap.GetAddress() + os::MemoryPageSize, os::MemoryPageSize, svc::MemoryPermission_ReadWrite))); + }; + const uintptr_t sp_0 = heap.GetAddress() + 1 * os::MemoryPageSize; + const uintptr_t sp_1 = heap.GetAddress() + 3 * os::MemoryPageSize; + + for (s32 core = 0; core < NumCores; ++core) { + for (s32 priority = HighestTestPriority; priority <= LowestTestPriority; ++priority) { + svc::Handle thread_handles[2]; + volatile bool thread_executed[2] = { false, false }; + + /* Start spinlooping. */ + g_spinloop = true; + + /* Create threads. */ + DOCTEST_CHECK(R_SUCCEEDED(svc::CreateThread(thread_handles + 0, reinterpret_cast<uintptr_t>(&TestPreemptionPriorityThreadFunction), reinterpret_cast<uintptr_t>(thread_executed + 0), sp_0, priority, core))); + DOCTEST_CHECK(R_SUCCEEDED(svc::CreateThread(thread_handles + 1, reinterpret_cast<uintptr_t>(&TestPreemptionPriorityThreadFunction), reinterpret_cast<uintptr_t>(thread_executed + 1), sp_1, priority, core))); + + /* Start threads. */ + DOCTEST_CHECK(R_SUCCEEDED(svc::StartThread(thread_handles[0]))); + DOCTEST_CHECK(R_SUCCEEDED(svc::StartThread(thread_handles[1]))); + + /* Wait long enough that we can be confident the threads have been balanced. */ + svc::SleepThread(PreemptionTimeSpan.GetNanoSeconds() * 10); + + /* Check that we're in a coherent state. */ + if (IsPreemptionPriority(core, priority)) { + DOCTEST_CHECK((thread_executed[0] & thread_executed[1])); + } else { + DOCTEST_CHECK((thread_executed[0] ^ thread_executed[1])); + } + + /* Stop spinlooping. */ + g_spinloop = false; + + /* Wait for threads to exit. */ + s32 dummy; + DOCTEST_CHECK(R_SUCCEEDED(svc::WaitSynchronization(std::addressof(dummy), thread_handles + 0, 1, -1))); + DOCTEST_CHECK(R_SUCCEEDED(svc::WaitSynchronization(std::addressof(dummy), thread_handles + 1, 1, -1))); + + /* Close thread handles. */ + DOCTEST_CHECK(R_SUCCEEDED(svc::CloseHandle(thread_handles[0]))); + DOCTEST_CHECK(R_SUCCEEDED(svc::CloseHandle(thread_handles[1]))); + } + } + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_set_heap_size.cpp b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_set_heap_size.cpp new file mode 100644 index 00000000..121b2c4e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_set_heap_size.cpp @@ -0,0 +1,133 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "util_common.hpp" +#include "util_check_memory.hpp" + +namespace ams::test { + + namespace { + + size_t GetPhysicalMemorySizeMax() { + u64 v; + R_ABORT_UNLESS(svc::GetInfo(std::addressof(v), svc::InfoType_ResourceLimit, svc::InvalidHandle, 0)); + + const svc::Handle resource_limit = v; + ON_SCOPE_EXIT { svc::CloseHandle(resource_limit); }; + + s64 size; + R_ABORT_UNLESS(svc::GetResourceLimitLimitValue(std::addressof(size), resource_limit, svc::LimitableResource_PhysicalMemoryMax)); + + return static_cast<size_t>(size); + } + + size_t GetPhysicalMemorySizeAvailable() { + u64 v; + R_ABORT_UNLESS(svc::GetInfo(std::addressof(v), svc::InfoType_ResourceLimit, svc::InvalidHandle, 0)); + + const svc::Handle resource_limit = v; + ON_SCOPE_EXIT { svc::CloseHandle(resource_limit); }; + + s64 total; + R_ABORT_UNLESS(svc::GetResourceLimitLimitValue(std::addressof(total), resource_limit, svc::LimitableResource_PhysicalMemoryMax)); + + s64 current; + R_ABORT_UNLESS(svc::GetResourceLimitCurrentValue(std::addressof(current), resource_limit, svc::LimitableResource_PhysicalMemoryMax)); + + return static_cast<size_t>(total - current); + } + + } + + DOCTEST_TEST_CASE("svc::SetHeapSize") { + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + uintptr_t dummy; + + /* Reset the heap. */ + uintptr_t addr; + DOCTEST_CHECK(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), 0))); + + /* Ensure that we don't leak memory. */ + const size_t initial_memory = GetPhysicalMemorySizeAvailable(); + ON_SCOPE_EXIT { DOCTEST_CHECK(initial_memory == GetPhysicalMemorySizeAvailable()); }; + + DOCTEST_SUBCASE("Unaligned and too big sizes fail") { + for (size_t i = 1; i < svc::HeapSizeAlignment; i = util::AlignUp(i + 1, os::MemoryPageSize)){ + DOCTEST_CHECK(svc::ResultInvalidSize::Includes(svc::SetHeapSize(std::addressof(dummy), i))); + } + DOCTEST_CHECK(svc::ResultInvalidSize::Includes(svc::SetHeapSize(std::addressof(dummy), 64_GB))); + } + + DOCTEST_SUBCASE("Larger size than address space fails") { + DOCTEST_CHECK(svc::ResultOutOfMemory::Includes(svc::SetHeapSize(std::addressof(dummy), util::AlignUp(svc::AddressMemoryRegionHeap39Size + 1, svc::HeapSizeAlignment)))); + } + + DOCTEST_SUBCASE("Bounded by resource limit") { + DOCTEST_CHECK(svc::ResultLimitReached::Includes(svc::SetHeapSize(std::addressof(dummy), util::AlignUp(GetPhysicalMemorySizeMax() + 1, svc::HeapSizeAlignment)))); + DOCTEST_CHECK(svc::ResultLimitReached::Includes(svc::SetHeapSize(std::addressof(dummy), util::AlignUp(GetPhysicalMemorySizeAvailable() + 1, svc::HeapSizeAlignment)))); + } + + DOCTEST_SUBCASE("SetHeapSize gives heap memory") { + DOCTEST_CHECK(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), svc::HeapSizeAlignment))); + TestMemory(addr, svc::HeapSizeAlignment, svc::MemoryState_Normal, svc::MemoryPermission_ReadWrite, 0); + + DOCTEST_CHECK(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), 0))); + } + + DOCTEST_SUBCASE("SetHeapSize cannot remove read-only heap") { + DOCTEST_CHECK(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), svc::HeapSizeAlignment))); + + DOCTEST_CHECK(R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), addr))); + TestMemory(addr, svc::HeapSizeAlignment, svc::MemoryState_Normal, svc::MemoryPermission_ReadWrite, 0); + + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(addr, svc::HeapSizeAlignment, svc::MemoryPermission_Read))); + TestMemory(addr, svc::HeapSizeAlignment, svc::MemoryState_Normal, svc::MemoryPermission_Read, 0); + + DOCTEST_CHECK(svc::ResultInvalidCurrentMemory::Includes(svc::SetHeapSize(std::addressof(dummy), 0))); + + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(addr, svc::HeapSizeAlignment, svc::MemoryPermission_ReadWrite))); + TestMemory(addr, svc::HeapSizeAlignment, svc::MemoryState_Normal, svc::MemoryPermission_ReadWrite, 0); + + DOCTEST_CHECK(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), 0))); + } + + DOCTEST_SUBCASE("Heap memory does not survive unmap/re-map") { + DOCTEST_CHECK(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), 2 * svc::HeapSizeAlignment))); + + u8 * const heap = reinterpret_cast<u8 *>(addr); + + std::memset(heap, 0xAA, svc::HeapSizeAlignment); + std::memset(heap + svc::HeapSizeAlignment, 0xBB, svc::HeapSizeAlignment); + + DOCTEST_CHECK(heap[svc::HeapSizeAlignment] == 0xBB); + DOCTEST_CHECK(std::memcmp(heap + svc::HeapSizeAlignment, heap + svc::HeapSizeAlignment + 1, svc::HeapSizeAlignment - 1) == 0); + + DOCTEST_CHECK(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), svc::HeapSizeAlignment))); + + DOCTEST_CHECK(heap[0] == 0xAA); + DOCTEST_CHECK(std::memcmp(heap, heap + 1, svc::HeapSizeAlignment - 1) == 0); + + DOCTEST_CHECK(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), 2 * svc::HeapSizeAlignment))); + + DOCTEST_CHECK(heap[svc::HeapSizeAlignment] == 0x00); + DOCTEST_CHECK(std::memcmp(heap + svc::HeapSizeAlignment, heap + svc::HeapSizeAlignment + 1, svc::HeapSizeAlignment - 1) == 0); + + DOCTEST_CHECK(R_SUCCEEDED(svc::SetHeapSize(std::addressof(addr), 0))); + } + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_set_memory_permission.cpp b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_set_memory_permission.cpp new file mode 100644 index 00000000..c1401d9d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_set_memory_permission.cpp @@ -0,0 +1,156 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "util_common.hpp" +#include "util_check_memory.hpp" +#include "util_scoped_heap.hpp" + +namespace ams::test { + + namespace { + + bool CanSetMemoryPermission(u8 state) { + return state == svc::MemoryState_CodeData || state == svc::MemoryState_AliasCodeData || state == svc::MemoryState_Normal; + } + + } + + alignas(os::MemoryPageSize) constinit u8 g_memory_permission_buffer[2 * os::MemoryPageSize]; + + DOCTEST_TEST_CASE("svc::SetMemoryPermission invalid arguments") { + const uintptr_t buffer = reinterpret_cast<uintptr_t>(g_memory_permission_buffer); + + for (size_t i = 1; i < os::MemoryPageSize; ++i) { + DOCTEST_CHECK(svc::ResultInvalidAddress::Includes(svc::SetMemoryPermission(buffer + i, os::MemoryPageSize, svc::MemoryPermission_Read))); + DOCTEST_CHECK(svc::ResultInvalidSize::Includes(svc::SetMemoryPermission(buffer, os::MemoryPageSize + i, svc::MemoryPermission_Read))); + } + + DOCTEST_CHECK(svc::ResultInvalidSize::Includes(svc::SetMemoryPermission(buffer, 0, svc::MemoryPermission_Read))); + + { + const u64 vmem_end = util::AlignDown(std::numeric_limits<u64>::max(), os::MemoryPageSize); + DOCTEST_CHECK(svc::ResultInvalidCurrentMemory::Includes(svc::SetMemoryPermission(vmem_end, 2 * os::MemoryPageSize, svc::MemoryPermission_Read))); + } + + DOCTEST_CHECK(svc::ResultInvalidCurrentMemory::Includes(svc::SetMemoryPermission(svc::AddressMap39End, os::MemoryPageSize, svc::MemoryPermission_Read))); + + for (size_t i = 0; i < 0x100; ++i) { + const auto perm = static_cast<svc::MemoryPermission>(i); + if (perm == svc::MemoryPermission_None || perm == svc::MemoryPermission_Read || perm == svc::MemoryPermission_ReadWrite) { + continue; + } + + DOCTEST_CHECK(svc::ResultInvalidNewMemoryPermission::Includes(svc::SetMemoryPermission(buffer, os::MemoryPageSize, perm))); + } + DOCTEST_CHECK(svc::ResultInvalidNewMemoryPermission::Includes(svc::SetMemoryPermission(buffer, os::MemoryPageSize, svc::MemoryPermission_ReadExecute))); + DOCTEST_CHECK(svc::ResultInvalidNewMemoryPermission::Includes(svc::SetMemoryPermission(buffer, os::MemoryPageSize, svc::MemoryPermission_Write))); + DOCTEST_CHECK(svc::ResultInvalidNewMemoryPermission::Includes(svc::SetMemoryPermission(buffer, os::MemoryPageSize, svc::MemoryPermission_DontCare))); + } + + DOCTEST_TEST_CASE("svc::SetMemoryPermission works on specific states") { + /* Check that we have CodeData. */ + const uintptr_t bss_buffer = reinterpret_cast<uintptr_t>(g_memory_permission_buffer); + TestMemory(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryState_CodeData, svc::MemoryPermission_ReadWrite, 0); + + /* Create a heap. */ + ScopedHeap scoped_heap(2 * svc::HeapSizeAlignment); + TestMemory(scoped_heap.GetAddress(), scoped_heap.GetSize(), svc::MemoryState_Normal, svc::MemoryPermission_ReadWrite, 0); + + /* TODO: Ensure we have alias code data? */ + + uintptr_t addr = 0; + while (true) { + /* Get current mapping. */ + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + DOCTEST_CHECK(R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), addr))); + + /* Try to set permission. */ + if (CanSetMemoryPermission(mem_info.state) && mem_info.attribute == 0) { + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(mem_info.base_address, mem_info.size, svc::MemoryPermission_ReadWrite))); + TestMemory(mem_info.base_address, mem_info.size, mem_info.state, svc::MemoryPermission_ReadWrite, mem_info.attribute); + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(mem_info.base_address, mem_info.size, mem_info.permission))); + } else { + DOCTEST_CHECK(svc::ResultInvalidCurrentMemory::Includes(svc::SetMemoryPermission(mem_info.base_address, mem_info.size, svc::MemoryPermission_Read))); + } + + const uintptr_t next_address = mem_info.base_address + mem_info.size; + if (next_address <= addr) { + break; + } + + addr = next_address; + } + } + + DOCTEST_TEST_CASE("svc::SetMemoryPermission allows for free movement between RW-, R--, ---") { + /* Define helper. */ + auto test_set_memory_permission = [](uintptr_t address, size_t size){ + /* Get the permission. */ + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + DOCTEST_CHECK(R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), address))); + + const svc::MemoryPermission legal_states[] = { svc::MemoryPermission_None, svc::MemoryPermission_Read, svc::MemoryPermission_ReadWrite }; + for (const auto src_state : legal_states) { + for (const auto dst_state : legal_states) { + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(address, size, svc::MemoryPermission_None))); + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(address, size, src_state))); + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(address, size, dst_state))); + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(address, size, svc::MemoryPermission_None))); + } + } + + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(address, size, mem_info.permission))); + }; + + /* Test that we can freely move about .bss buffers. */ + test_set_memory_permission(reinterpret_cast<uintptr_t>(g_memory_permission_buffer), sizeof(g_memory_permission_buffer)); + + /* Create a heap. */ + ScopedHeap scoped_heap(svc::HeapSizeAlignment); + TestMemory(scoped_heap.GetAddress(), scoped_heap.GetSize(), svc::MemoryState_Normal, svc::MemoryPermission_ReadWrite, 0); + + /* Test that we can freely move about heap. */ + test_set_memory_permission(scoped_heap.GetAddress(), scoped_heap.GetSize()); + + /* TODO: AliasCodeData */ + } + + DOCTEST_TEST_CASE("svc::SetMemoryPermission fails when the memory has non-zero attribute") { + const uintptr_t bss_buffer = reinterpret_cast<uintptr_t>(g_memory_permission_buffer); + TestMemory(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryState_CodeData, svc::MemoryPermission_ReadWrite, 0); + + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_None))); + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_Read))); + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_ReadWrite))); + + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryAttribute(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryAttribute_Uncached, svc::MemoryAttribute_Uncached))); + TestMemory(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryState_CodeData, svc::MemoryPermission_ReadWrite, svc::MemoryAttribute_Uncached); + + DOCTEST_CHECK(svc::ResultInvalidCurrentMemory::Includes(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_None))); + DOCTEST_CHECK(svc::ResultInvalidCurrentMemory::Includes(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_Read))); + DOCTEST_CHECK(svc::ResultInvalidCurrentMemory::Includes(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_ReadWrite))); + + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryAttribute(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryAttribute_Uncached, 0))); + TestMemory(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryState_CodeData, svc::MemoryPermission_ReadWrite, 0); + + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_None))); + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_Read))); + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(bss_buffer, sizeof(g_memory_permission_buffer), svc::MemoryPermission_ReadWrite))); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_sleep_thread.cpp b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_sleep_thread.cpp new file mode 100644 index 00000000..7cf3f4d4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_sleep_thread.cpp @@ -0,0 +1,253 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "util_common.hpp" +#include "util_scoped_heap.hpp" + +namespace ams::test { + + namespace { + + constinit svc::Handle g_read_handles[3] = { svc::InvalidHandle, svc::InvalidHandle, svc::InvalidHandle }; + constinit svc::Handle g_write_handles[3] = { svc::InvalidHandle, svc::InvalidHandle, svc::InvalidHandle }; + + constinit s64 g_thread_wait_ns; + constinit bool g_should_switch_threads; + constinit bool g_switched_threads; + constinit bool g_correct_switch_threads; + + void WaitSynchronization(svc::Handle handle) { + s32 dummy; + R_ABORT_UNLESS(svc::WaitSynchronization(std::addressof(dummy), std::addressof(handle), 1, -1)); + } + + void TestYieldHigherOrSamePriorityThread() { + /* Wait to run. */ + WaitSynchronization(g_read_handles[0]); + + /* Reset our event. */ + R_ABORT_UNLESS(svc::ClearEvent(g_read_handles[0])); + + /* Signal the other thread's event. */ + R_ABORT_UNLESS(svc::SignalEvent(g_write_handles[1])); + + /* Wait, potentially yielding to the lower/same priority thread. */ + g_switched_threads = false; + svc::SleepThread(g_thread_wait_ns); + + /* Check whether we switched correctly. */ + g_correct_switch_threads = g_should_switch_threads == g_switched_threads; + + /* Exit. */ + svc::ExitThread(); + } + + void TestYieldLowerOrSamePriorityThread() { + /* Signal thread the higher/same priority thread to run. */ + R_ABORT_UNLESS(svc::SignalEvent(g_write_handles[0])); + + /* Wait to run. */ + WaitSynchronization(g_read_handles[1]); + + /* Reset our event. */ + R_ABORT_UNLESS(svc::ClearEvent(g_read_handles[1])); + + /* We've switched to the lower/same priority thread. */ + g_switched_threads = true; + + /* Wait to be instructed to exit. */ + WaitSynchronization(g_read_handles[2]); + + /* Reset the exit signal. */ + R_ABORT_UNLESS(svc::ClearEvent(g_read_handles[2])); + + /* Exit. */ + svc::ExitThread(); + } + + void TestYieldSamePriority(uintptr_t sp_higher, uintptr_t sp_lower) { + /* Test each core. */ + for (s32 core = 0; core < NumCores; ++core) { + for (s32 priority = HighestTestPriority; priority <= LowestTestPriority && !IsPreemptionPriority(core, priority); ++priority) { + + svc::Handle thread_handles[2]; + + /* Create threads. */ + DOCTEST_CHECK(R_SUCCEEDED(svc::CreateThread(thread_handles + 0, reinterpret_cast<uintptr_t>(&TestYieldHigherOrSamePriorityThread), 0, sp_higher, priority, core))); + DOCTEST_CHECK(R_SUCCEEDED(svc::CreateThread(thread_handles + 1, reinterpret_cast<uintptr_t>(&TestYieldLowerOrSamePriorityThread), 0, sp_lower, priority, core))); + + /* Start threads. */ + DOCTEST_CHECK(R_SUCCEEDED(svc::StartThread(thread_handles[1]))); + DOCTEST_CHECK(R_SUCCEEDED(svc::StartThread(thread_handles[0]))); + + /* Wait for higher priority thread. */ + WaitSynchronization(thread_handles[0]); + DOCTEST_CHECK(R_SUCCEEDED(svc::CloseHandle(thread_handles[0]))); + + /* Signal the lower priority thread to exit. */ + DOCTEST_CHECK(R_SUCCEEDED(svc::SignalEvent(g_write_handles[2]))); + + /* Wait for the lower priority thread. */ + WaitSynchronization(thread_handles[1]); + DOCTEST_CHECK(R_SUCCEEDED(svc::CloseHandle(thread_handles[1]))); + + /* Check that the switch was correct. */ + DOCTEST_CHECK(g_correct_switch_threads); + } + } + } + + void TestYieldDifferentPriority(uintptr_t sp_higher, uintptr_t sp_lower) { + /* Test each core. */ + for (s32 core = 0; core < NumCores; ++core) { + for (s32 priority = HighestTestPriority; priority < LowestTestPriority && !IsPreemptionPriority(core, priority); ++priority) { + + svc::Handle thread_handles[2]; + + /* Create threads. */ + DOCTEST_CHECK(R_SUCCEEDED(svc::CreateThread(thread_handles + 0, reinterpret_cast<uintptr_t>(&TestYieldHigherOrSamePriorityThread), 0, sp_higher, priority, core))); + DOCTEST_CHECK(R_SUCCEEDED(svc::CreateThread(thread_handles + 1, reinterpret_cast<uintptr_t>(&TestYieldLowerOrSamePriorityThread), 0, sp_lower, priority + 1, core))); + + /* Start threads. */ + DOCTEST_CHECK(R_SUCCEEDED(svc::StartThread(thread_handles[1]))); + DOCTEST_CHECK(R_SUCCEEDED(svc::StartThread(thread_handles[0]))); + + /* Wait for higher priority thread. */ + WaitSynchronization(thread_handles[0]); + DOCTEST_CHECK(R_SUCCEEDED(svc::CloseHandle(thread_handles[0]))); + + /* Signal the lower priority thread to exit. */ + DOCTEST_CHECK(R_SUCCEEDED(svc::SignalEvent(g_write_handles[2]))); + + /* Wait for the lower priority thread. */ + WaitSynchronization(thread_handles[1]); + DOCTEST_CHECK(R_SUCCEEDED(svc::CloseHandle(thread_handles[1]))); + + /* Check that the switch was correct. */ + DOCTEST_CHECK(g_correct_switch_threads); + } + } + } + + } + + DOCTEST_TEST_CASE( "svc::SleepThread: Thread sleeps for time specified" ) { + for (s64 ns = 1; ns < TimeSpan::FromSeconds(1).GetNanoSeconds(); ns *= 2) { + const auto start = os::GetSystemTickOrdered(); + svc::SleepThread(ns); + const auto end = os::GetSystemTickOrdered(); + + const s64 taken_ns = (end - start).ToTimeSpan().GetNanoSeconds(); + DOCTEST_CHECK( taken_ns >= ns ); + } + } + + DOCTEST_TEST_CASE( "svc::SleepThread: Yield is behaviorally correct" ) { + /* Create events. */ + for (size_t i = 0; i < util::size(g_write_handles); ++i) { + g_read_handles[i] = svc::InvalidHandle; + g_write_handles[i] = svc::InvalidHandle; + DOCTEST_CHECK(R_SUCCEEDED(svc::CreateEvent(g_write_handles + i, g_read_handles + i))); + } + + ON_SCOPE_EXIT { + for (size_t i = 0; i < util::size(g_write_handles); ++i) { + DOCTEST_CHECK(R_SUCCEEDED(svc::CloseHandle(g_read_handles[i]))); + DOCTEST_CHECK(R_SUCCEEDED(svc::CloseHandle(g_write_handles[i]))); + g_read_handles[i] = svc::InvalidHandle; + g_write_handles[i] = svc::InvalidHandle; + } + }; + + /* Create heap. */ + ScopedHeap heap(3 * os::MemoryPageSize); + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(heap.GetAddress() + os::MemoryPageSize, os::MemoryPageSize, svc::MemoryPermission_None))); + ON_SCOPE_EXIT { + DOCTEST_CHECK(R_SUCCEEDED(svc::SetMemoryPermission(heap.GetAddress() + os::MemoryPageSize, os::MemoryPageSize, svc::MemoryPermission_ReadWrite))); + }; + const uintptr_t sp_higher = heap.GetAddress() + 1 * os::MemoryPageSize; + const uintptr_t sp_lower = heap.GetAddress() + 3 * os::MemoryPageSize; + + DOCTEST_SUBCASE("svc::SleepThread: Yields do not switch to a thread of lower priority.") { + /* Test yield without migration. */ + { + /* Configure for yield test. */ + g_should_switch_threads = false; + g_thread_wait_ns = static_cast<s64>(svc::YieldType_WithoutCoreMigration); + + TestYieldDifferentPriority(sp_higher, sp_lower); + } + + /* Test yield with migration. */ + { + /* Configure for yield test. */ + g_should_switch_threads = false; + g_thread_wait_ns = static_cast<s64>(svc::YieldType_WithoutCoreMigration); + + TestYieldDifferentPriority(sp_higher, sp_lower); + } + } + + DOCTEST_SUBCASE("svc::SleepThread: ToAnyThread switches to a thread of same or lower priority.") { + /* Test to same priority. */ + { + /* Configure for yield test. */ + g_should_switch_threads = true; + g_thread_wait_ns = static_cast<s64>(svc::YieldType_ToAnyThread); + + TestYieldSamePriority(sp_higher, sp_lower); + } + + /* Test to lower priority. */ + { + /* Configure for yield test. */ + g_should_switch_threads = true; + g_thread_wait_ns = static_cast<s64>(svc::YieldType_ToAnyThread); + + TestYieldDifferentPriority(sp_higher, sp_lower); + } + } + + DOCTEST_SUBCASE("svc::SleepThread: Yield switches to another thread of same priority.") { + /* Test yield without migration. */ + { + /* Configure for yield test. */ + g_should_switch_threads = true; + g_thread_wait_ns = static_cast<s64>(svc::YieldType_WithoutCoreMigration); + + TestYieldSamePriority(sp_higher, sp_lower); + } + + /* Test yield with migration. */ + { + /* Configure for yield test. */ + g_should_switch_threads = true; + g_thread_wait_ns = static_cast<s64>(svc::YieldType_WithCoreMigration); + + TestYieldSamePriority(sp_higher, sp_lower); + } + } + + DOCTEST_SUBCASE("svc::SleepThread: Yield with bogus timeout does not switch to another thread same priority") { + /* Configure for yield test. */ + g_should_switch_threads = false; + g_thread_wait_ns = INT64_C(-5); + + TestYieldSamePriority(sp_higher, sp_lower); + } + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_thread_creation.arch.arm64.s b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_thread_creation.arch.arm64.s new file mode 100644 index 00000000..22aa7fcd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_thread_creation.arch.arm64.s @@ -0,0 +1,43 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +/* ams::test::TestThreadCreateRegistersOnFunctionEntry(void *ctx) */ +.section .text._ZN3ams4test40TestThreadCreateRegistersOnFunctionEntryEPv, "ax", %progbits +.global _ZN3ams4test40TestThreadCreateRegistersOnFunctionEntryEPv +.type _ZN3ams4test40TestThreadCreateRegistersOnFunctionEntryEPv, %function +_ZN3ams4test40TestThreadCreateRegistersOnFunctionEntryEPv: + /* Save all registers to our context. */ + stp x0, x1, [x0, #0x00] + stp x2, x3, [x0, #0x10] + stp x4, x5, [x0, #0x20] + stp x6, x7, [x0, #0x30] + stp x8, x9, [x0, #0x40] + stp x10, x11, [x0, #0x50] + stp x12, x13, [x0, #0x60] + stp x14, x15, [x0, #0x70] + stp x16, x17, [x0, #0x80] + stp x18, x19, [x0, #0x90] + stp x20, x21, [x0, #0xA0] + stp x22, x23, [x0, #0xB0] + stp x24, x25, [x0, #0xC0] + stp x26, x27, [x0, #0xD0] + stp x28, x29, [x0, #0xE0] + + mov x1, sp + stp x30, x1, [x0, #0xF0] + + /* Exit the thread. */ + svc 0xa \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_thread_creation.cpp b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_thread_creation.cpp new file mode 100644 index 00000000..e5a1e715 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_thread_creation.cpp @@ -0,0 +1,65 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "util_common.hpp" +#include "util_scoped_heap.hpp" + +namespace ams::test { + + void TestThreadCreateRegistersOnFunctionEntry(void *ctx); + + DOCTEST_TEST_CASE( "Creating a thread results in fixed register contents." ) { + /* Create heap. */ + ScopedHeap heap(os::MemoryPageSize); + + /* Create register buffer. */ + u64 thread_registers[32]; + std::memset(thread_registers, 0xCC, sizeof(thread_registers)); + + /* Create thread. */ + svc::Handle thread_handle; + DOCTEST_CHECK(R_SUCCEEDED(svc::CreateThread(std::addressof(thread_handle), reinterpret_cast<uintptr_t>(&TestThreadCreateRegistersOnFunctionEntry), reinterpret_cast<uintptr_t>(thread_registers), heap.GetAddress() + os::MemoryPageSize, HighestTestPriority, NumCores - 1))); + + /* Start thread. */ + DOCTEST_CHECK(R_SUCCEEDED(svc::StartThread(thread_handle))); + + /* Wait for thread to exit. */ + s32 dummy; + DOCTEST_CHECK(R_SUCCEEDED(svc::WaitSynchronization(std::addressof(dummy), std::addressof(thread_handle), 1, -1))); + + /* Close thread handle. */ + DOCTEST_CHECK(R_SUCCEEDED(svc::CloseHandle(thread_handle))); + + /* Check thread initial registers. */ + for (size_t i = 0; i < util::size(thread_registers); ++i) { + if (i == 0) { + /* X0 is argument. */ + DOCTEST_CHECK(thread_registers[i] == reinterpret_cast<uintptr_t>(thread_registers)); + } else if (i == 18) { + /* X18 is an odd cfi value. */ + DOCTEST_CHECK(thread_registers[i] != 0); + DOCTEST_CHECK((thread_registers[i] & 0x1) != 0); + } else if (i == 31) { + /* SP is user-provided sp. */ + DOCTEST_CHECK(thread_registers[i] == (heap.GetAddress() + os::MemoryPageSize)); + } else { + /* All other registers are zero. */ + DOCTEST_CHECK(thread_registers[i] == 0); + } + } + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_thread_pinning.cpp b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_thread_pinning.cpp new file mode 100644 index 00000000..35fb9d4b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/test_thread_pinning.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <stratosphere.hpp> +#include "util_common.hpp" +#include "util_scoped_heap.hpp" + +namespace ams::test { + + DOCTEST_TEST_CASE( "Setting a thread's disable count will cause it to become pinned." ) { + DoWithThreadPinning([]() { + __asm__ __volatile__("" ::: "memory"); + }); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/util_check_memory.hpp b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/util_check_memory.hpp new file mode 100644 index 00000000..92bbddf0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/util_check_memory.hpp @@ -0,0 +1,46 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "util_test_framework.hpp" + +namespace ams::test { + + inline void TestMemory(uintptr_t address, svc::MemoryState state, svc::MemoryPermission perm, u32 attr) { + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + DOCTEST_CHECK(R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), address))); + + DOCTEST_CHECK(mem_info.base_address <= address); + DOCTEST_CHECK(address < (mem_info.base_address + mem_info.size)); + DOCTEST_CHECK(mem_info.state == state); + DOCTEST_CHECK(mem_info.permission == perm); + DOCTEST_CHECK(mem_info.attribute == attr); + } + + inline void TestMemory(uintptr_t address, size_t size, svc::MemoryState state, svc::MemoryPermission perm, u32 attr) { + svc::MemoryInfo mem_info; + svc::PageInfo page_info; + DOCTEST_CHECK(R_SUCCEEDED(svc::QueryMemory(std::addressof(mem_info), std::addressof(page_info), address))); + + DOCTEST_CHECK(mem_info.base_address <= address); + DOCTEST_CHECK(mem_info.base_address < (address + size)); + DOCTEST_CHECK((address + size) <= (mem_info.base_address + mem_info.size)); + DOCTEST_CHECK(mem_info.state == state); + DOCTEST_CHECK(mem_info.permission == perm); + DOCTEST_CHECK(mem_info.attribute == attr); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/util_common.hpp b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/util_common.hpp new file mode 100644 index 00000000..86bfa9f9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/util_common.hpp @@ -0,0 +1,101 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "util_test_framework.hpp" + +namespace ams::test { + + static constexpr s32 NumCores = 4; + static constexpr s32 DpcManagerNormalThreadPriority = 59; + static constexpr s32 DpcManagerPreemptionThreadPriority = 63; + + static constexpr s32 HighestTestPriority = 32; + static constexpr s32 LowestTestPriority = svc::LowestThreadPriority; + static_assert(HighestTestPriority < LowestTestPriority); + + static constexpr TimeSpan PreemptionTimeSpan = TimeSpan::FromMilliSeconds(10); + + constexpr inline bool IsPreemptionPriority(s32 core, s32 priority) { + return priority == ((core == (NumCores - 1)) ? DpcManagerPreemptionThreadPriority : DpcManagerNormalThreadPriority); + } + + template<typename F> + void DoWithThreadPinning(F f) { + /* Get the thread local region. */ + auto * const tlr = svc::GetThreadLocalRegion(); + + /* Require that we're not currently pinned. */ + DOCTEST_CHECK((tlr->disable_count == 0)); + DOCTEST_CHECK((!tlr->interrupt_flag)); + + /* Request to pin ourselves. */ + tlr->disable_count = 1; + + /* Wait long enough that we can be confident preemption will occur, and therefore our interrupt flag will be set. */ + { + constexpr auto MinimumTicksToGuaranteeInterruptFlag = ::ams::svc::Tick(PreemptionTimeSpan) + ::ams::svc::Tick(PreemptionTimeSpan) + 2; + + auto GetSystemTickForPinnedThread = []() ALWAYS_INLINE_LAMBDA -> ::ams::svc::Tick { + s64 v; + __asm__ __volatile__ ("mrs %x[v], cntpct_el0" : [v]"=r"(v) :: "memory"); + return ::ams::svc::Tick(v); + }; + + const auto start_tick = GetSystemTickForPinnedThread(); + while (true) { + if (tlr->interrupt_flag) { + break; + } + + if (const auto cur_tick = GetSystemTickForPinnedThread(); (cur_tick - start_tick) > MinimumTicksToGuaranteeInterruptFlag) { + break; + } + } + } + + /* We're pinned. Execute the user callback. */ + bool callback_succeeded = true; + { + if constexpr (requires { { f() } -> std::convertible_to<bool>; }) { + callback_succeeded = f(); + } else { + f(); + } + } + + /* Clear our disable count. */ + tlr->disable_count = 0; + + /* Get our interrupt flag. */ + const auto interrupt_flag_while_pinned = tlr->interrupt_flag; + + /* Unpin ourselves. */ + if (interrupt_flag_while_pinned) { + svc::SynchronizePreemptionState(); + } + + /* Get our interrupt flag. */ + const auto interrupt_flag_after_unpin = tlr->interrupt_flag; + + /* We have access to all SVCs again. Check that our pinning happened as expected. */ + DOCTEST_CHECK(interrupt_flag_while_pinned); + DOCTEST_CHECK(!interrupt_flag_after_unpin); + + /* Check that our callback succeeded. */ + DOCTEST_CHECK(callback_succeeded); + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/util_scoped_heap.hpp b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/util_scoped_heap.hpp new file mode 100644 index 00000000..b1b2b78a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/util_scoped_heap.hpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include "util_test_framework.hpp" + +namespace ams::test { + + class ScopedHeap { + NON_COPYABLE(ScopedHeap); + NON_MOVEABLE(ScopedHeap); + private: + uintptr_t m_address; + size_t m_size; + public: + explicit ScopedHeap(size_t size) { + this->SetHeapSize(size); + } + + ~ScopedHeap() { + const auto result = svc::SetHeapSize(std::addressof(m_address), 0); + DOCTEST_CHECK(R_SUCCEEDED(result)); + } + + void SetHeapSize(size_t size) { + m_size = util::AlignUp(size, svc::HeapSizeAlignment); + + const auto result = svc::SetHeapSize(std::addressof(m_address), m_size); + DOCTEST_CHECK(R_SUCCEEDED(result)); + } + + uintptr_t GetAddress() const { return m_address; } + size_t GetSize() const { return m_size; } + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/util_test_framework.hpp b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/util_test_framework.hpp new file mode 100644 index 00000000..0972c387 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/tests/TestSvc/source/util_test_framework.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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <stratosphere.hpp> + +#define CATCH_CONFIG_NOSTDOUT +#define DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES +#define DOCTEST_CONFIG_SUPER_FAST_ASSERTS +#define DOCTEST_CONFIG_NO_EXCEPTIONS +#define DOCTEST_CONFIG_NO_POSIX_SIGNALS +#include "doctest.h" diff --git a/Source/Atmosphere-MTC-Unlock/thermosphere/.gitignore b/Source/Atmosphere-MTC-Unlock/thermosphere/.gitignore new file mode 100644 index 00000000..1fcb1529 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/thermosphere/.gitignore @@ -0,0 +1 @@ +out diff --git a/Source/Atmosphere-MTC-Unlock/thermosphere/Makefile b/Source/Atmosphere-MTC-Unlock/thermosphere/Makefile new file mode 100644 index 00000000..7f2634bb --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/thermosphere/Makefile @@ -0,0 +1,163 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/devkitA64/base_rules + +AMSBRANCH := $(shell git symbolic-ref --short HEAD) +AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD) + +ifneq (, $(strip $(shell git status --porcelain 2>/dev/null))) + AMSREV := $(AMSREV)-dirty +endif + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := src src/lib +DATA := data +INCLUDES := include ../common/include + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a -mtune=cortex-a57 +DEFINES := -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\" + +CFLAGS := \ + -g \ + -O2 \ + -ffunction-sections \ + -fdata-sections \ + -mgeneral-regs-only \ + -fomit-frame-pointer \ + -std=gnu11 \ + -Werror \ + -Wall \ + -Wno-main \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) -D__CCPLEX__ + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(TOPDIR)/linker.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +.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).bin $(TARGET).elf + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT).bin + +$(OUTPUT).bin : $(OUTPUT).elf + $(OBJCOPY) -S -O binary $< $@ + @echo built ... $(notdir $@) + +$(OUTPUT).elf : $(OFILES) + +%.elf: + @echo linking $(notdir $@) + $(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + @$(NM) -CSn $@ > $(notdir $*.lst) + +$(OFILES_SRC) : $(HFILES_BIN) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/thermosphere/README.md b/Source/Atmosphere-MTC-Unlock/thermosphere/README.md new file mode 100644 index 00000000..cb8f6d5d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/thermosphere/README.md @@ -0,0 +1,6 @@ +Thermosphère +===== + +![License](https://img.shields.io/badge/License-GPLv2-blue.svg) + +Thermosphère is a hypervisor for the Nintendo Switch. diff --git a/Source/Atmosphere-MTC-Unlock/thermosphere/linker.ld b/Source/Atmosphere-MTC-Unlock/thermosphere/linker.ld new file mode 100644 index 00000000..9e8374ef --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/thermosphere/linker.ld @@ -0,0 +1,55 @@ +OUTPUT_FORMAT("elf64-littleaarch64") +OUTPUT_ARCH(aarch64) +ENTRY(_start) + +SECTIONS +{ + . = 0x800D0000; + + . = ALIGN(4); + .text : { + PROVIDE(lds_thermo_start = .); + start.o (.text*) + *(.text*) + } + + . = ALIGN(8); + .rodata : { + *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) + } + + . = ALIGN(8); + .data : { + *(.data*) + } + + /* Uninitialised data */ + . = ALIGN(8); + PROVIDE(lds_bss_start = .); + .bss (NOLOAD) : { + *(.bss*) . = ALIGN(8); + } + PROVIDE(lds_bss_end = .); + + /* EL2 stack */ + . = ALIGN(16); + . += 0x10000; /* 64 KiB stack */ + el2_stack_end = .; + + /* Page align the end of binary */ + . = ALIGN(512); + PROVIDE(lds_el2_thermo_end = .); + + /* EL1 stack */ + . = ALIGN(16); + . += 0x10000; /* 64 KiB stack */ + el1_stack_end = .; + + lds_thermo_end = .; + + /DISCARD/ : { *(.dynstr*) } + /DISCARD/ : { *(.dynamic*) } + /DISCARD/ : { *(.plt*) } + /DISCARD/ : { *(.interp*) } + /DISCARD/ : { *(.gnu*) } +} diff --git a/Source/Atmosphere-MTC-Unlock/thermosphere/linker.specs b/Source/Atmosphere-MTC-Unlock/thermosphere/linker.specs new file mode 100644 index 00000000..30099041 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/thermosphere/linker.specs @@ -0,0 +1,7 @@ +%rename link old_link + +*link: +%(old_link) -T %:getenv(TOPDIR /linker.ld) --nmagic --gc-sections + +*startfile: +crti%O%s crtbegin%O%s diff --git a/Source/Atmosphere-MTC-Unlock/thermosphere/src/exceptions.c b/Source/Atmosphere-MTC-Unlock/thermosphere/src/exceptions.c new file mode 100644 index 00000000..300463f5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/thermosphere/src/exceptions.c @@ -0,0 +1,108 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdint.h> +#include <stddef.h> +#include "exceptions.h" + +#include "lib/printk.h" + +/** + * Simple debug function that prints all of our saved registers. + */ +static void print_registers(struct guest_state *regs) +{ + // print x0-29 + for(int i = 0; i < 30; i += 2) { + printk("x%d:\t0x%p\t", i, regs->x[i]); + printk("x%d:\t0x%p\n", i + 1, regs->x[i + 1]); + } + + // print x30; don't bother with x31 (SP), as it's used by the stack that's + // storing this stuff; we really care about the saved SP anyways + printk("x30:\t0x%p\n", regs->x[30]); + + // Special registers. + printk("pc:\t0x%p\tcpsr:\t0x%p\n", regs->pc, regs->cpsr); + printk("sp_el1:\t0x%p\tsp_el0:\t0x%p\n", regs->sp_el1, regs->sp_el0); + printk("elr_el1:0x%p\tspsr_el1:0x%p\n", regs->elr_el1, regs->spsr_el1); + + // Note that we don't print ESR_EL2, as this isn't really part of the saved state. +} + +/** + * Placeholder function that triggers whenever a vector happens we're not + * expecting. Currently prints out some debug information. + */ +void unhandled_vector(struct guest_state *regs) +{ + printk("\nAn unexpected vector happened!\n"); + print_registers(regs); + printk("\n\n"); +} + + +/** + * Handles an HVC call. + */ +static void handle_hvc(struct guest_state *regs, int call_number) +{ + + switch(call_number) { + + + default: + printk("Got a HVC call from 64-bit code.\n"); + printk("Calling instruction was: hvc %d\n\n", call_number); + printk("Calling context (you can use these regs as hypercall args!):\n"); + print_registers(regs); + printk("\n\n"); + break; + } +} + + +/** + * Placeholder function that triggers whenever a user event triggers a + * synchronous interrupt. Currently, we really only care about 'hvc', + * so that's all we're going to handle here. + */ + +void handle_hypercall(struct guest_state *regs) +{ + // This is demonstration code. + // In the future, you'd stick your hypercall table here. + + switch (regs->esr_el2.ec) { + + case HSR_EC_HVC64: { + // Read the hypercall number. + int hvc_nr = regs->esr_el2.iss & 0xFFFF; + + // ... and handle the hypercall. + handle_hvc(regs, hvc_nr); + break; + } + default: + printk("Unexpected hypercall! ESR=%p\n", regs->esr_el2.bits); + print_registers(regs); + printk("\n\n"); + break; + + } +} + + diff --git a/Source/Atmosphere-MTC-Unlock/thermosphere/src/exceptions.h b/Source/Atmosphere-MTC-Unlock/thermosphere/src/exceptions.h new file mode 100644 index 00000000..5405d4a9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/thermosphere/src/exceptions.h @@ -0,0 +1,185 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef __EXCEPTION_H__ +#define __EXCEPTION_H__ + +/** + * Borrowed fom Xen (not copyrightable as these are facts). + * Description of the EL2 exception syndrome register. + */ +#define HSR_EC_UNKNOWN 0x00 +#define HSR_EC_WFI_WFE 0x01 +#define HSR_EC_CP15_32 0x03 +#define HSR_EC_CP15_64 0x04 +#define HSR_EC_CP14_32 0x05 /* Trapped MCR or MRC access to CP14 */ +#define HSR_EC_CP14_DBG 0x06 /* Trapped LDC/STC access to CP14 (only for debug registers) */ +#define HSR_EC_CP 0x07 /* HCPTR-trapped access to CP0-CP13 */ +#define HSR_EC_CP10 0x08 +#define HSR_EC_JAZELLE 0x09 +#define HSR_EC_BXJ 0x0a +#define HSR_EC_CP14_64 0x0c +#define HSR_EC_SVC32 0x11 +#define HSR_EC_HVC32 0x12 +#define HSR_EC_SMC32 0x13 +#define HSR_EC_HVC64 0x16 +#define HSR_EC_SMC64 0x17 +#define HSR_EC_SYSREG 0x18 +#define HSR_EC_INSTR_ABORT_LOWER_EL 0x20 +#define HSR_EC_INSTR_ABORT_CURR_EL 0x21 +#define HSR_EC_DATA_ABORT_LOWER_EL 0x24 +#define HSR_EC_DATA_ABORT_CURR_EL 0x25 +#define HSR_EC_BRK 0x3c + +/** + * Borrowed fom Xen (not copyrightable as these are facts). + * Description of the EL2 exception syndrome register. + */ +union esr { + uint32_t bits; + struct { + unsigned long iss:25; /* Instruction Specific Syndrome */ + unsigned long len:1; /* Instruction length */ + unsigned long ec:6; /* Exception Class */ + }; + + /* Common to all conditional exception classes (0x0N, except 0x00). */ + struct hsr_cond { + unsigned long iss:20; /* Instruction Specific Syndrome */ + unsigned long cc:4; /* Condition Code */ + unsigned long ccvalid:1;/* CC Valid */ + unsigned long len:1; /* Instruction length */ + unsigned long ec:6; /* Exception Class */ + } cond; + + struct hsr_wfi_wfe { + unsigned long ti:1; /* Trapped instruction */ + unsigned long sbzp:19; + unsigned long cc:4; /* Condition Code */ + unsigned long ccvalid:1;/* CC Valid */ + unsigned long len:1; /* Instruction length */ + unsigned long ec:6; /* Exception Class */ + } wfi_wfe; + + /* reg, reg0, reg1 are 4 bits on AArch32, the fifth bit is sbzp. */ + struct hsr_cp32 { + unsigned long read:1; /* Direction */ + unsigned long crm:4; /* CRm */ + unsigned long reg:5; /* Rt */ + unsigned long crn:4; /* CRn */ + unsigned long op1:3; /* Op1 */ + unsigned long op2:3; /* Op2 */ + unsigned long cc:4; /* Condition Code */ + unsigned long ccvalid:1;/* CC Valid */ + unsigned long len:1; /* Instruction length */ + unsigned long ec:6; /* Exception Class */ + } cp32; /* HSR_EC_CP15_32, CP14_32, CP10 */ + + struct hsr_cp64 { + unsigned long read:1; /* Direction */ + unsigned long crm:4; /* CRm */ + unsigned long reg1:5; /* Rt1 */ + unsigned long reg2:5; /* Rt2 */ + unsigned long sbzp2:1; + unsigned long op1:4; /* Op1 */ + unsigned long cc:4; /* Condition Code */ + unsigned long ccvalid:1;/* CC Valid */ + unsigned long len:1; /* Instruction length */ + unsigned long ec:6; /* Exception Class */ + } cp64; /* HSR_EC_CP15_64, HSR_EC_CP14_64 */ + + struct hsr_cp { + unsigned long coproc:4; /* Number of coproc accessed */ + unsigned long sbz0p:1; + unsigned long tas:1; /* Trapped Advanced SIMD */ + unsigned long res0:14; + unsigned long cc:4; /* Condition Code */ + unsigned long ccvalid:1;/* CC Valid */ + unsigned long len:1; /* Instruction length */ + unsigned long ec:6; /* Exception Class */ + } cp; /* HSR_EC_CP */ + + struct hsr_sysreg { + unsigned long read:1; /* Direction */ + unsigned long crm:4; /* CRm */ + unsigned long reg:5; /* Rt */ + unsigned long crn:4; /* CRn */ + unsigned long op1:3; /* Op1 */ + unsigned long op2:3; /* Op2 */ + unsigned long op0:2; /* Op0 */ + unsigned long res0:3; + unsigned long len:1; /* Instruction length */ + unsigned long ec:6; + } sysreg; /* HSR_EC_SYSREG */ + + struct hsr_iabt { + unsigned long ifsc:6; /* Instruction fault status code */ + unsigned long res0:1; + unsigned long s1ptw:1; /* Stage 2 fault during stage 1 translation */ + unsigned long res1:1; + unsigned long eat:1; /* External abort type */ + unsigned long res2:15; + unsigned long len:1; /* Instruction length */ + unsigned long ec:6; /* Exception Class */ + } iabt; /* HSR_EC_INSTR_ABORT_* */ + + struct hsr_dabt { + unsigned long dfsc:6; /* Data Fault Status Code */ + unsigned long write:1; /* Write / not Read */ + unsigned long s1ptw:1; /* Stage 2 fault during stage 1 translation */ + unsigned long cache:1; /* Cache Maintenance */ + unsigned long eat:1; /* External Abort Type */ + unsigned long sbzp0:4; + unsigned long ar:1; /* Acquire Release */ + unsigned long sf:1; /* Sixty Four bit register */ + unsigned long reg:5; /* Register */ + unsigned long sign:1; /* Sign extend */ + unsigned long size:2; /* Access Size */ + unsigned long valid:1; /* Syndrome Valid */ + unsigned long len:1; /* Instruction length */ + unsigned long ec:6; /* Exception Class */ + } dabt; /* HSR_EC_DATA_ABORT_* */ + + struct hsr_brk { + unsigned long comment:16; /* Comment */ + unsigned long res0:9; + unsigned long len:1; /* Instruction length */ + unsigned long ec:6; /* Exception Class */ + } brk; + +}; + +/** + * Structure that stores the saved register values on a hypercall. + */ +struct guest_state { + uint64_t pc; + uint64_t cpsr; + + uint64_t elr_el1; + uint64_t spsr_el1; + + uint64_t sp_el0; + uint64_t sp_el1; + + union esr esr_el2; + uint64_t x[31]; +} +__attribute__((packed)); + + + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/thermosphere/src/main.c b/Source/Atmosphere-MTC-Unlock/thermosphere/src/main.c new file mode 100644 index 00000000..e37fc7e2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/thermosphere/src/main.c @@ -0,0 +1,145 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <stdint.h> +#include <stddef.h> +#include <string.h> + +#include "regs.h" +#include "lib/printk.h" + +/** + * Switches to EL1, and then calls main_el1. + * Implemented in assembly in entry.S. + */ +void switch_to_el1(); + +/** + * C entry point for execution at EL1. + */ +void main_el1(void * fdt); + +/** + * Reference to the EL2 vector table. + * Note that the type here isn't reprsentative-- we just need the address of the label. + */ +extern uint64_t el2_vector_table; + +/** + * Clear out the system's bss. + */ +void _clear_bss(void) +{ + // These symbols don't actually have a meaningful type-- instead, + // we care about the locations at which the linker /placed/ these + // symbols, which happen to be at the start and end of the BSS. + // We use chars here to make the math easy. :) + extern char lds_bss_start, lds_bss_end; + + memset(&lds_bss_start, 0, &lds_bss_end - &lds_bss_start); +} + + +/** + * Triggered on an unrecoverable condition; prints an error message + * and terminates execution. + */ +void panic(const char * message) +{ + printk("\n\n"); + printk("-----------------------------------------------------------------\n"); + printk("PANIC: %s\n", message); + printk("-----------------------------------------------------------------\n"); + + // TODO: This should probably induce a reboot, + // rather than sticking here. + while(1); +} + + + +/** + * Launch an executable kernel image. Should be the last thing called by + * Discharge, as it does not return. + * + * @param kernel The kernel to be executed. + * @param fdt The device tree to be passed to the given kernel. + */ +void launch_kernel(const void *kernel) +{ + // Construct a function pointer to our kernel, which will allow us to + // jump there immediately. Note that we don't care what this leaves on + // the stack, as either our entire stack will be ignored, or it'll + // be torn down by the target kernel anyways. + void (*target_kernel)(void) = kernel; + + printk("Launching Horizon kernel...\n"); + target_kernel(); +} + + + +/** + * Core section of the stub-- sets up the hypervisor from up in EL2. + */ +int main(int argc, void **argv) +{ + // Read the currrent execution level... + uint32_t el = get_current_el(); + + /* Say hello. */ + printk("Welcome to Atmosph\xe8re Thermosph\xe8" "re!\n"); + printk("Running at EL%d.\n", el); + + // ... and ensure we're in EL2. + if (el != 2) { + panic("Thermosph\xe8" "re must be launched from EL2!"); + } + + // Set up the vector table for EL2, so that the HVC instruction can be used + // from EL1. This allows us to return to EL2 after starting the EL1 guest. + set_vbar_el2(&el2_vector_table); + + // TODO: + // Insert any setup you want done in EL2, here. For now, EL2 is set up + // to do almost nothing-- it doesn't take control of any hardware, + // and it hasn't set up any trap-to-hypervisor features. + printk("\nSwitching to EL1...\n"); + switch_to_el1(); +} + + +/** + * Secondary section of the stub, executed once we've surrendered + * hypervisor privileges. + */ +void main_el1(void * fdt) +{ + int rc; + (void)rc; + + // Read the currrent execution level... + uint32_t el = get_current_el(); + + // Validate that we're in EL1. + printk("Now executing from EL%d!\n", el); + if(el != 1) { + panic("Executing with more privilege than we expect!"); + } + + // If we've made it here, we failed to boot, and we can't recover. + panic("We should launch Horizon here!"); +} diff --git a/Source/Atmosphere-MTC-Unlock/thermosphere/src/regs.h b/Source/Atmosphere-MTC-Unlock/thermosphere/src/regs.h new file mode 100644 index 00000000..2e468178 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/thermosphere/src/regs.h @@ -0,0 +1,73 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef __REGS_H__ +#define __REGS_H__ + +/** + * Access to system registers. + */ +#define WRITE_SYSREG(sysreg, val, type) \ + asm volatile ("msr "#sysreg", %0\n" : : "r"((type)(val))) +#define READ_SYSREG(sysreg, val, type) \ + asm volatile ("mrs %0, "#sysreg"\n" : "=r"((type)(val))) + +#define READ_SYSREG_32(sysreg, val) READ_SYSREG(sysreg, val, uint32_t) +#define WRITE_SYSREG_32(sysreg, val) WRITE_SYSREG(sysreg, val, uint32_t) + +#define READ_SYSREG_64(sysreg, val) READ_SYSREG(sysreg, val, uint64_t) +#define WRITE_SYSREG_64(sysreg, val) WRITE_SYSREG(sysreg, val, uint64_t) + + +/** + * Returns the system's current Execution Level (EL). + */ +inline static uint32_t get_current_el(void) { + uint32_t val; + + // Read the CurrentEl register, and extract the bits that tell us our EL. + READ_SYSREG_32(CurrentEl, val); + return val >> 2; +} + +/** + * Sets the base address of the EL2 exception table. + */ +inline static void set_vbar_el2(void * address) { + WRITE_SYSREG_64(vbar_el2, (uint64_t)address); +} + + +/** + * Sets the address to 'return to' when leaving EL2. + */ +inline static void set_elr_el2(void * address) { + WRITE_SYSREG_64(elr_el2, (uint64_t)address); +} + + +/** + * Returns the MMU status bit from the SCTLR register. + */ +inline static uint32_t get_el2_mmu_status(void) { + uint32_t val; + + // Read the CurrentEl register, and extract the bits that tell us our EL. + READ_SYSREG_32(sctlr_el2, val); + return val & 1; +} + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/thermosphere/src/start.s b/Source/Atmosphere-MTC-Unlock/thermosphere/src/start.s new file mode 100644 index 00000000..1b43a293 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/thermosphere/src/start.s @@ -0,0 +1,263 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +.section ".text" +.global _start + + +/** + * Simple macro to help with generating vector table entries. + */ +.macro ventry label + .align 7 + b \label +.endm + + +/** + * Start of day code. This is the first code that executes after we're launched + * by the bootloader. We use this only to set up a C environment. + */ +_start: + + // Create a simple stack for the hypervisor, while executing in EL2. + ldr x1, =el2_stack_end + mov sp, x1 + + // Clear out our binary's bss. + stp x0, x1, [sp, #-16]! + bl _clear_bss + ldp x0, x1, [sp], #16 + + // Run the main routine. This shouldn't return. + b main + + // We shouldn't ever reach here; trap. +1: b 1b + + +/* + * Vector table for interrupts/exceptions that reach EL2. + */ +.align 11 +.global el2_vector_table; +el2_vector_table: + ventry _unhandled_vector // Synchronous EL2t + ventry _unhandled_vector // IRQ EL2t + ventry _unhandled_vector // FIQ EL2t + ventry _unhandled_vector // Error EL2t + + ventry _unhandled_vector // Synchronous EL2h + ventry _unhandled_vector // IRQ EL2h + ventry _unhandled_vector // FIQ EL2h + ventry _unhandled_vector // Error EL2h + + ventry _handle_hypercall // Synchronous 64-bit EL0/EL1 + ventry _unhandled_vector // IRQ 64-bit EL0/EL1 + ventry _unhandled_vector // FIQ 64-bit EL0/EL1 + ventry _unhandled_vector // Error 64-bit EL0/EL1 + + ventry _unhandled_vector // Synchronous 32-bit EL0/EL1 + ventry _unhandled_vector // IRQ 32-bit EL0/EL1 + ventry _unhandled_vector // FIQ 32-bit EL0/EL1 + ventry _unhandled_vector // Error 32-bit EL0/EL1 + + + +/* + * Switch down to EL1 and then execute the second half of our stub. + * Implemented in assembly, as this manipulates the stack. + * + * Obliterates the stack, but leaves the rest of memory intact. This should be + * fine, as we should be hiding the EL2 memory from the rest of the system. + * + * x0: The location of the device tree to be passed into EL0. + */ +.global switch_to_el1 +switch_to_el1: + + // Set up a post-EL1-switch return address... + ldr x2, =_post_el1_switch + msr elr_el2, x2 + + // .. and set up the CPSR after we switch to EL1. + // We overwrite the saved program status register. Note that setting + // this with the EL = EL1 is what actually causes the switch. + mov x2, #0x3c5 // EL1_SP1 | D | A | I | F + msr spsr_el2, x2 + + // Reset the stack pointer to the very end of the stack, so it's + // fresh and clean for when we jump back up into EL2. + ldr x2, =el2_stack_end + mov sp, x2 + + // ... and switch down to EL1. (This essentially asks the processor + // to switch down to EL1 and then load ELR_EL2 to the PC.) + eret + + +/* + * Entry point after the switch to EL1. + */ +.global _post_el1_switch +_post_el1_switch: + + // Create a simple stack for us to use while at EL1. + // We use this temporarily to launch Horizon. + ldr x2, =el1_stack_end + mov sp, x2 + + // Run the main routine. This shouldn't return. + b main_el1 + + // We shouldn't ever reach here; trap. +1: b 1b + + +/** + * Push and pop 'psuedo-op' macros that simplify the ARM syntax to make the below pretty. + */ +.macro push, xreg1, xreg2 + stp \xreg1, \xreg2, [sp, #-16]! +.endm +.macro pop, xreg1, xreg2 + ldp \xreg1, \xreg2, [sp], #16 +.endm + +/** + * Macro that saves registers onto the stack when entering an exception handler-- + * effectively saving the guest state. Once this method is complete, *sp will + * point to a struct guest_state. + * + * You can modify this to save whatever you'd like, but: + * 1) We can only push in pairs due to armv8 architecture quirks. + * 2) Be careful not to trounce registers until after you've saved them. + * 3) r31 is your stack pointer, and doesn't need to be saved. You'll want to + * save the lesser EL's stack pointers separately. + * 4) Make sure any changes you make are reflected both in _restore_registers_ + * and in struct guest_state, or things will break pretty badly. + */ +.macro save_registers + // General purpose registers x1 - x30 + push x29, x30 + push x27, x28 + push x25, x26 + push x23, x24 + push x21, x22 + push x19, x20 + push x17, x18 + push x15, x16 + push x13, x14 + push x11, x12 + push x9, x10 + push x7, x8 + push x5, x6 + push x3, x4 + push x1, x2 + + // x0 and the el2_esr + mrs x20, esr_el2 + push x20, x0 + + // the el1_sp and el0_sp + mrs x0, sp_el0 + mrs x1, sp_el1 + push x0, x1 + + // the el1 elr/spsr + mrs x0, elr_el1 + mrs x1, spsr_el1 + push x0, x1 + + // the el2 elr/spsr + mrs x0, elr_el2 + mrs x1, spsr_el2 + push x0, x1 + +.endm + +/** + * Macro that restores registers when returning from EL2. + * Mirrors save_registers. + */ +.macro restore_registers + // the el2 elr/spsr + pop x0, x1 + msr elr_el2, x0 + msr spsr_el2, x1 + + // the el1 elr/spsr + pop x0, x1 + msr elr_el1, x0 + msr spsr_el1, x1 + + // the el1_sp and el0_sp + pop x0, x1 + msr sp_el0, x0 + msr sp_el1, x1 + + // x0, and the el2_esr + // Note that we don't restore el2_esr, as this wouldn't + // have any meaning. + pop x20, x0 + + // General purpose registers x1 - x30 + pop x1, x2 + pop x3, x4 + pop x5, x6 + pop x7, x8 + pop x9, x10 + pop x11, x12 + pop x13, x14 + pop x15, x16 + pop x17, x18 + pop x19, x20 + pop x21, x22 + pop x23, x24 + pop x25, x26 + pop x27, x28 + pop x29, x30 +.endm + +/* + * Handler for any vector we're not equipped to handle. + */ +_unhandled_vector: + // TODO: Save interrupt state and turn off interrupts. + save_registers + + // Point x0 at our saved registers, and then call our C handler. + mov x0, sp + bl unhandled_vector + + restore_registers + eret + + +/* + * Handler for any synchronous event coming from the guest (any trap-to-EL2). + * This _stub_ only uses this to handle hypercalls-- hence the name. + */ +_handle_hypercall: + // TODO: Save interrupt state and turn off interrupts. + save_registers + + // Point x0 at our saved registers, and then call our C handler. + mov x0, sp + bl handle_hypercall + + restore_registers + eret diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/Makefile b/Source/Atmosphere-MTC-Unlock/troposphere/Makefile new file mode 100644 index 00000000..6a67c9e7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/Makefile @@ -0,0 +1,12 @@ +APPLICATIONS := daybreak haze reboot_to_payload + +SUBFOLDERS := $(APPLICATIONS) + +TOPTARGETS := all clean + +$(TOPTARGETS): $(SUBFOLDERS) + +$(SUBFOLDERS): + $(MAKE) -C $@ $(MAKECMDGOALS) + +.PHONY: $(TOPTARGETS) $(SUBFOLDERS) diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/Makefile b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/Makefile new file mode 100644 index 00000000..adb6339f --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/Makefile @@ -0,0 +1,282 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) +# +# NO_ICON: if set to anything, do not use icon. +# NO_NACP: if set to anything, no .nacp file is generated. +# APP_TITLE is the name of the app stored in the .nacp file (Optional) +# APP_AUTHOR is the author of the app stored in the .nacp file (Optional) +# APP_VERSION is the version of the app stored in the .nacp file (Optional) +# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) +# ICON is the filename of the icon (.jpg), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - <Project name>.jpg +# - icon.jpg +# - <libnx folder>/default_icon.jpg +# +# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - <Project name>.json +# - config.json +# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead +# of a homebrew executable (.nro). This is intended to be used for sysmodules. +# NACP building is skipped as well. +#--------------------------------------------------------------------------------- +TARGET := daybreak +BUILD := build +SOURCES := source nanovg/shaders +DATA := data +INCLUDES := include ../include +ROMFS := romfs + +# Output folders for autogenerated files in romfs +OUT_SHADERS := shaders + +APP_TITLE := Daybreak +APP_AUTHOR := Atmosphere-NX +APP_VERSION := 1.0.0 + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE + +CFLAGS := -g -Wall -O2 -ffunction-sections \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) -D__SWITCH__ + +CXXFLAGS := $(CFLAGS) -std=gnu++23 -fno-exceptions -fno-rtti + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lnanovg -ldeko3d -lnx + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) $(CURDIR)/nanovg/ + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +SUBFOLDERS := nanovg + +TOPTARGETS := all clean + +$(TOPTARGETS): $(SUBFOLDERS) + +$(SUBFOLDERS): + $(MAKE) -C $@ $(MAKECMDGOALS) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +GLSLFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.glsl))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +ifneq ($(strip $(ROMFS)),) + ROMFS_TARGETS := + ROMFS_FOLDERS := + ifneq ($(strip $(OUT_SHADERS)),) + ROMFS_SHADERS := $(ROMFS)/$(OUT_SHADERS) + ROMFS_TARGETS += $(patsubst %.glsl, $(ROMFS_SHADERS)/%.dksh, $(GLSLFILES)) + ROMFS_FOLDERS += $(ROMFS_SHADERS) + endif + + export ROMFS_DEPS := $(foreach file,$(ROMFS_TARGETS),$(CURDIR)/$(file)) +endif + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +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 + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.jpg) + ifneq (,$(findstring $(TARGET).jpg,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).jpg + else + ifneq (,$(findstring icon.jpg,$(icons))) + export APP_ICON := $(TOPDIR)/icon.jpg + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_ICON)),) + export NROFLAGS += --icon=$(APP_ICON) +endif + +ifeq ($(strip $(NO_NACP)),) + export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp +endif + +ifneq ($(APP_TITLEID),) + export NACPFLAGS += --titleid=$(APP_TITLEID) +endif + +ifneq ($(ROMFS),) + export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) +endif + +.PHONY: $(TOPTARGETS) $(SUBFOLDERS) all clean + +#--------------------------------------------------------------------------------- +all: $(ROMFS_TARGETS) | $(BUILD) + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +$(BUILD): + @mkdir -p $@ + +ifneq ($(strip $(ROMFS_TARGETS)),) + +$(ROMFS_TARGETS): | $(ROMFS_FOLDERS) + +$(ROMFS_FOLDERS): + @mkdir -p $@ + +$(ROMFS_SHADERS)/%_vsh.dksh: %_vsh.glsl + @echo {vert} $(notdir $<) + @uam -s vert -o $@ $< + +$(ROMFS_SHADERS)/%_tcsh.dksh: %_tcsh.glsl + @echo {tess_ctrl} $(notdir $<) + @uam -s tess_ctrl -o $@ $< + +$(ROMFS_SHADERS)/%_tesh.dksh: %_tesh.glsl + @echo {tess_eval} $(notdir $<) + @uam -s tess_eval -o $@ $< + +$(ROMFS_SHADERS)/%_gsh.dksh: %_gsh.glsl + @echo {geom} $(notdir $<) + @uam -s geom -o $@ $< + +$(ROMFS_SHADERS)/%_fsh.dksh: %_fsh.glsl + @echo {frag} $(notdir $<) + @uam -s frag -o $@ $< + +$(ROMFS_SHADERS)/%.dksh: %.glsl + @echo {comp} $(notdir $<) + @uam -s comp -o $@ $< + +endif + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... +ifeq ($(strip $(APP_JSON)),) + @rm -fr $(BUILD) $(ROMFS_FOLDERS) $(TARGET).nro $(TARGET).nacp $(TARGET).elf +else + @rm -fr $(BUILD) $(ROMFS_FOLDERS) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf +endif + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +ifeq ($(strip $(APP_JSON)),) + +all : $(OUTPUT).nro + +ifeq ($(strip $(NO_NACP)),) +$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp $(ROMFS_DEPS) +else +$(OUTPUT).nro : $(OUTPUT).elf $(ROMFS_DEPS) +endif + +else + +all : $(OUTPUT).nsp + +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm + +$(OUTPUT).nso : $(OUTPUT).elf + +endif + +$(OUTPUT).elf : $(OFILES) + +$(OFILES_SRC) : $(HFILES_BIN) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/icon.jpg b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/icon.jpg new file mode 100644 index 00000000..867e5d53 Binary files /dev/null and b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/icon.jpg differ diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/.gitignore b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/.gitignore new file mode 100644 index 00000000..b0b4735b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/.gitignore @@ -0,0 +1,92 @@ +# deko3d shaders +*.dksh + +# 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 + +# 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 + +.**/ + +# NOTE: make sure to make exceptions to this pattern when needed! +*.bin +*.enc + +**/out +**/build diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/.gitrepo b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/.gitrepo new file mode 100644 index 00000000..62fc7a58 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/.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/Adubbz/nanovg-deko3d.git + branch = master + commit = a8c9778aff08420b5b4af7b54bef5d4f3b5ac568 + parent = 797e3651d5e425231dd7f252489338e38872b116 + method = merge + cmdver = 0.4.3 diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/LICENSE b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/LICENSE new file mode 100644 index 00000000..53562c09 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) 2020 Adubbz, Mikko Mononen memon@inside.org + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you use + this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source + distribution. diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/Makefile b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/Makefile new file mode 100644 index 00000000..d3363cdd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/Makefile @@ -0,0 +1,170 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) +# +# NO_ICON: if set to anything, do not use icon. +# NO_NACP: if set to anything, no .nacp file is generated. +# APP_TITLE is the name of the app stored in the .nacp file (Optional) +# APP_AUTHOR is the author of the app stored in the .nacp file (Optional) +# APP_VERSION is the version of the app stored in the .nacp file (Optional) +# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) +# ICON is the filename of the icon (.jpg), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - <Project name>.jpg +# - icon.jpg +# - <libnx folder>/default_icon.jpg +# +# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - <Project name>.json +# - config.json +# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead +# of a homebrew executable (.nro). This is intended to be used for sysmodules. +# NACP building is skipped as well. +#--------------------------------------------------------------------------------- +TARGET := libnanovg +BUILD := build +SOURCES := source source/framework +INCLUDES := include include/nanovg include/nanovg/framework + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE + +CFLAGS := -g -Wall -O2 -ffunction-sections \ + $(ARCH) $(DEFINES) + +CFLAGS += -Wno-misleading-indentation -Wno-use-after-free -Wno-unused-function + +CFLAGS += $(INCLUDE) -D__SWITCH__ + +CXXFLAGS := $(CFLAGS) -std=gnu++17 -fno-exceptions -fno-rtti + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +#LIBS := -ldeko3dd -lglad -lEGL -lglapi -ldrm_nouveau +LIBS := -ldeko3d + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +GLSLFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.glsl))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +.PHONY: all clean + +#--------------------------------------------------------------------------------- +all: lib/$(TARGET).a + +lib: + @[ -d $@ ] || mkdir -p $@ + +release: + @[ -d $@ ] || mkdir -p $@ + +lib/$(TARGET).a : lib release $(SOURCES) $(INCLUDES) + @$(MAKE) BUILD=release OUTPUT=$(CURDIR)/$@ \ + BUILD_CFLAGS="-DNDEBUG=1 -O2" \ + DEPSDIR=$(CURDIR)/release \ + --no-print-directory -C release \ + -f $(CURDIR)/Makefile + +dist-bin: all + @tar --exclude=*~ -cjf $(TARGET).tar.bz2 include lib + +dist-src: + @tar --exclude=*~ -cjf $(TARGET)-src.tar.bz2 include source Makefile + +dist: dist-src dist-bin +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr release lib *.bz2 +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT) : $(OFILES) + +$(OFILES) : $(GCH_FILES) + +$(OFILES_SRC) : $(HFILES_BIN) + +#--------------------------------------------------------------------------------- +# 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 +#--------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/README.md b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/README.md new file mode 100644 index 00000000..96943b45 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/README.md @@ -0,0 +1,18 @@ +NanoVG for Deko3D +========== + +NanoVG is small antialiased vector graphics rendering library. This is a port to [deko3d](https://github.com/devkitPro/deko3d), a low level 3D graphics API targetting the Nvidia Tegra X1 found inside the Nintendo Switch. + +## Example +An example of using this library can be found [here](https://github.com/Adubbz/nanovg-deko3d-example). + +## License +The library is licensed under [zlib license](LICENSE). + +Dependencies: +- fincs' deko3d framework is licensed under [zlib license](source/framework/LICENSE). + +## Links +The original [nanovg project](https://github.com/memononen/nanovg). +Uses [stb_truetype](http://nothings.org) for font rendering. +Uses [stb_image](http://nothings.org) for image loading. diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg.h b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg.h new file mode 100644 index 00000000..646b4242 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg.h @@ -0,0 +1,697 @@ +// +// Copyright (c) 2013 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef NANOVG_H +#define NANOVG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define NVG_PI 3.14159265358979323846264338327f + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4201) // nonstandard extension used : nameless struct/union +#endif + +typedef struct NVGcontext NVGcontext; + +struct NVGcolor { + union { + float rgba[4]; + struct { + float r,g,b,a; + }; + }; +}; +typedef struct NVGcolor NVGcolor; + +struct NVGpaint { + float xform[6]; + float extent[2]; + float radius; + float feather; + NVGcolor innerColor; + NVGcolor outerColor; + int image; +}; +typedef struct NVGpaint NVGpaint; + +enum NVGwinding { + NVG_CCW = 1, // Winding for solid shapes + NVG_CW = 2, // Winding for holes +}; + +enum NVGsolidity { + NVG_SOLID = 1, // CCW + NVG_HOLE = 2, // CW +}; + +enum NVGlineCap { + NVG_BUTT, + NVG_ROUND, + NVG_SQUARE, + NVG_BEVEL, + NVG_MITER, +}; + +enum NVGalign { + // Horizontal align + NVG_ALIGN_LEFT = 1<<0, // Default, align text horizontally to left. + NVG_ALIGN_CENTER = 1<<1, // Align text horizontally to center. + NVG_ALIGN_RIGHT = 1<<2, // Align text horizontally to right. + // Vertical align + NVG_ALIGN_TOP = 1<<3, // Align text vertically to top. + NVG_ALIGN_MIDDLE = 1<<4, // Align text vertically to middle. + NVG_ALIGN_BOTTOM = 1<<5, // Align text vertically to bottom. + NVG_ALIGN_BASELINE = 1<<6, // Default, align text vertically to baseline. +}; + +enum NVGblendFactor { + NVG_ZERO = 1<<0, + NVG_ONE = 1<<1, + NVG_SRC_COLOR = 1<<2, + NVG_ONE_MINUS_SRC_COLOR = 1<<3, + NVG_DST_COLOR = 1<<4, + NVG_ONE_MINUS_DST_COLOR = 1<<5, + NVG_SRC_ALPHA = 1<<6, + NVG_ONE_MINUS_SRC_ALPHA = 1<<7, + NVG_DST_ALPHA = 1<<8, + NVG_ONE_MINUS_DST_ALPHA = 1<<9, + NVG_SRC_ALPHA_SATURATE = 1<<10, +}; + +enum NVGcompositeOperation { + NVG_SOURCE_OVER, + NVG_SOURCE_IN, + NVG_SOURCE_OUT, + NVG_ATOP, + NVG_DESTINATION_OVER, + NVG_DESTINATION_IN, + NVG_DESTINATION_OUT, + NVG_DESTINATION_ATOP, + NVG_LIGHTER, + NVG_COPY, + NVG_XOR, +}; + +struct NVGcompositeOperationState { + int srcRGB; + int dstRGB; + int srcAlpha; + int dstAlpha; +}; +typedef struct NVGcompositeOperationState NVGcompositeOperationState; + +struct NVGglyphPosition { + const char* str; // Position of the glyph in the input string. + float x; // The x-coordinate of the logical glyph position. + float minx, maxx; // The bounds of the glyph shape. +}; +typedef struct NVGglyphPosition NVGglyphPosition; + +struct NVGtextRow { + const char* start; // Pointer to the input text where the row starts. + const char* end; // Pointer to the input text where the row ends (one past the last character). + const char* next; // Pointer to the beginning of the next row. + float width; // Logical width of the row. + float minx, maxx; // Actual bounds of the row. Logical with and bounds can differ because of kerning and some parts over extending. +}; +typedef struct NVGtextRow NVGtextRow; + +enum NVGimageFlags { + NVG_IMAGE_GENERATE_MIPMAPS = 1<<0, // Generate mipmaps during creation of the image. + NVG_IMAGE_REPEATX = 1<<1, // Repeat image in X direction. + NVG_IMAGE_REPEATY = 1<<2, // Repeat image in Y direction. + NVG_IMAGE_FLIPY = 1<<3, // Flips (inverses) image in Y direction when rendered. + NVG_IMAGE_PREMULTIPLIED = 1<<4, // Image data has premultiplied alpha. + NVG_IMAGE_NEAREST = 1<<5, // Image interpolation is Nearest instead Linear +}; + +// Begin drawing a new frame +// Calls to nanovg drawing API should be wrapped in nvgBeginFrame() & nvgEndFrame() +// nvgBeginFrame() defines the size of the window to render to in relation currently +// set viewport (i.e. glViewport on GL backends). Device pixel ration allows to +// control the rendering on Hi-DPI devices. +// For example, GLFW returns two dimension for an opened window: window size and +// frame buffer size. In that case you would set windowWidth/Height to the window size +// devicePixelRatio to: frameBufferWidth / windowWidth. +void nvgBeginFrame(NVGcontext* ctx, float windowWidth, float windowHeight, float devicePixelRatio); + +// Cancels drawing the current frame. +void nvgCancelFrame(NVGcontext* ctx); + +// Ends drawing flushing remaining render state. +void nvgEndFrame(NVGcontext* ctx); + +// +// Composite operation +// +// The composite operations in NanoVG are modeled after HTML Canvas API, and +// the blend func is based on OpenGL (see corresponding manuals for more info). +// The colors in the blending state have premultiplied alpha. + +// Sets the composite operation. The op parameter should be one of NVGcompositeOperation. +void nvgGlobalCompositeOperation(NVGcontext* ctx, int op); + +// Sets the composite operation with custom pixel arithmetic. The parameters should be one of NVGblendFactor. +void nvgGlobalCompositeBlendFunc(NVGcontext* ctx, int sfactor, int dfactor); + +// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately. The parameters should be one of NVGblendFactor. +void nvgGlobalCompositeBlendFuncSeparate(NVGcontext* ctx, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha); + +// +// Color utils +// +// Colors in NanoVG are stored as unsigned ints in ABGR format. + +// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0f). +NVGcolor nvgRGB(unsigned char r, unsigned char g, unsigned char b); + +// Returns a color value from red, green, blue values. Alpha will be set to 1.0f. +NVGcolor nvgRGBf(float r, float g, float b); + + +// Returns a color value from red, green, blue and alpha values. +NVGcolor nvgRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a); + +// Returns a color value from red, green, blue and alpha values. +NVGcolor nvgRGBAf(float r, float g, float b, float a); + + +// Linearly interpolates from color c0 to c1, and returns resulting color value. +NVGcolor nvgLerpRGBA(NVGcolor c0, NVGcolor c1, float u); + +// Sets transparency of a color value. +NVGcolor nvgTransRGBA(NVGcolor c0, unsigned char a); + +// Sets transparency of a color value. +NVGcolor nvgTransRGBAf(NVGcolor c0, float a); + +// Returns color value specified by hue, saturation and lightness. +// HSL values are all in range [0..1], alpha will be set to 255. +NVGcolor nvgHSL(float h, float s, float l); + +// Returns color value specified by hue, saturation and lightness and alpha. +// HSL values are all in range [0..1], alpha in range [0..255] +NVGcolor nvgHSLA(float h, float s, float l, unsigned char a); + +// +// State Handling +// +// NanoVG contains state which represents how paths will be rendered. +// The state contains transform, fill and stroke styles, text and font styles, +// and scissor clipping. + +// Pushes and saves the current render state into a state stack. +// A matching nvgRestore() must be used to restore the state. +void nvgSave(NVGcontext* ctx); + +// Pops and restores current render state. +void nvgRestore(NVGcontext* ctx); + +// Resets current render state to default values. Does not affect the render state stack. +void nvgReset(NVGcontext* ctx); + +// +// Render styles +// +// Fill and stroke render style can be either a solid color or a paint which is a gradient or a pattern. +// Solid color is simply defined as a color value, different kinds of paints can be created +// using nvgLinearGradient(), nvgBoxGradient(), nvgRadialGradient() and nvgImagePattern(). +// +// Current render style can be saved and restored using nvgSave() and nvgRestore(). + +// Sets whether to draw antialias for nvgStroke() and nvgFill(). It's enabled by default. +void nvgShapeAntiAlias(NVGcontext* ctx, int enabled); + +// Sets current stroke style to a solid color. +void nvgStrokeColor(NVGcontext* ctx, NVGcolor color); + +// Sets current stroke style to a paint, which can be a one of the gradients or a pattern. +void nvgStrokePaint(NVGcontext* ctx, NVGpaint paint); + +// Sets current fill style to a solid color. +void nvgFillColor(NVGcontext* ctx, NVGcolor color); + +// Sets current fill style to a paint, which can be a one of the gradients or a pattern. +void nvgFillPaint(NVGcontext* ctx, NVGpaint paint); + +// Sets the miter limit of the stroke style. +// Miter limit controls when a sharp corner is beveled. +void nvgMiterLimit(NVGcontext* ctx, float limit); + +// Sets the stroke width of the stroke style. +void nvgStrokeWidth(NVGcontext* ctx, float size); + +// Sets how the end of the line (cap) is drawn, +// Can be one of: NVG_BUTT (default), NVG_ROUND, NVG_SQUARE. +void nvgLineCap(NVGcontext* ctx, int cap); + +// Sets how sharp path corners are drawn. +// Can be one of NVG_MITER (default), NVG_ROUND, NVG_BEVEL. +void nvgLineJoin(NVGcontext* ctx, int join); + +// Sets the transparency applied to all rendered shapes. +// Already transparent paths will get proportionally more transparent as well. +void nvgGlobalAlpha(NVGcontext* ctx, float alpha); + +// +// Transforms +// +// The paths, gradients, patterns and scissor region are transformed by an transformation +// matrix at the time when they are passed to the API. +// The current transformation matrix is a affine matrix: +// [sx kx tx] +// [ky sy ty] +// [ 0 0 1] +// Where: sx,sy define scaling, kx,ky skewing, and tx,ty translation. +// The last row is assumed to be 0,0,1 and is not stored. +// +// Apart from nvgResetTransform(), each transformation function first creates +// specific transformation matrix and pre-multiplies the current transformation by it. +// +// Current coordinate system (transformation) can be saved and restored using nvgSave() and nvgRestore(). + +// Resets current transform to a identity matrix. +void nvgResetTransform(NVGcontext* ctx); + +// Premultiplies current coordinate system by specified matrix. +// The parameters are interpreted as matrix as follows: +// [a c e] +// [b d f] +// [0 0 1] +void nvgTransform(NVGcontext* ctx, float a, float b, float c, float d, float e, float f); + +// Translates current coordinate system. +void nvgTranslate(NVGcontext* ctx, float x, float y); + +// Rotates current coordinate system. Angle is specified in radians. +void nvgRotate(NVGcontext* ctx, float angle); + +// Skews the current coordinate system along X axis. Angle is specified in radians. +void nvgSkewX(NVGcontext* ctx, float angle); + +// Skews the current coordinate system along Y axis. Angle is specified in radians. +void nvgSkewY(NVGcontext* ctx, float angle); + +// Scales the current coordinate system. +void nvgScale(NVGcontext* ctx, float x, float y); + +// Stores the top part (a-f) of the current transformation matrix in to the specified buffer. +// [a c e] +// [b d f] +// [0 0 1] +// There should be space for 6 floats in the return buffer for the values a-f. +void nvgCurrentTransform(NVGcontext* ctx, float* xform); + + +// The following functions can be used to make calculations on 2x3 transformation matrices. +// A 2x3 matrix is represented as float[6]. + +// Sets the transform to identity matrix. +void nvgTransformIdentity(float* dst); + +// Sets the transform to translation matrix matrix. +void nvgTransformTranslate(float* dst, float tx, float ty); + +// Sets the transform to scale matrix. +void nvgTransformScale(float* dst, float sx, float sy); + +// Sets the transform to rotate matrix. Angle is specified in radians. +void nvgTransformRotate(float* dst, float a); + +// Sets the transform to skew-x matrix. Angle is specified in radians. +void nvgTransformSkewX(float* dst, float a); + +// Sets the transform to skew-y matrix. Angle is specified in radians. +void nvgTransformSkewY(float* dst, float a); + +// Sets the transform to the result of multiplication of two transforms, of A = A*B. +void nvgTransformMultiply(float* dst, const float* src); + +// Sets the transform to the result of multiplication of two transforms, of A = B*A. +void nvgTransformPremultiply(float* dst, const float* src); + +// Sets the destination to inverse of specified transform. +// Returns 1 if the inverse could be calculated, else 0. +int nvgTransformInverse(float* dst, const float* src); + +// Transform a point by given transform. +void nvgTransformPoint(float* dstx, float* dsty, const float* xform, float srcx, float srcy); + +// Converts degrees to radians and vice versa. +float nvgDegToRad(float deg); +float nvgRadToDeg(float rad); + +// +// Images +// +// NanoVG allows you to load jpg, png, psd, tga, pic and gif files to be used for rendering. +// In addition you can upload your own image. The image loading is provided by stb_image. +// The parameter imageFlags is combination of flags defined in NVGimageFlags. + +// Creates image by loading it from the disk from specified file name. +// Returns handle to the image. +int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags); + +// Creates image by loading it from the specified chunk of memory. +// Returns handle to the image. +int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int ndata); + +// Creates image from specified image data. +// Returns handle to the image. +int nvgCreateImageRGBA(NVGcontext* ctx, int w, int h, int imageFlags, const unsigned char* data); + +// Updates image data specified by image handle. +void nvgUpdateImage(NVGcontext* ctx, int image, const unsigned char* data); + +// Returns the dimensions of a created image. +void nvgImageSize(NVGcontext* ctx, int image, int* w, int* h); + +// Deletes created image. +void nvgDeleteImage(NVGcontext* ctx, int image); + +// +// Paints +// +// NanoVG supports four types of paints: linear gradient, box gradient, radial gradient and image pattern. +// These can be used as paints for strokes and fills. + +// Creates and returns a linear gradient. Parameters (sx,sy)-(ex,ey) specify the start and end coordinates +// of the linear gradient, icol specifies the start color and ocol the end color. +// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). +NVGpaint nvgLinearGradient(NVGcontext* ctx, float sx, float sy, float ex, float ey, + NVGcolor icol, NVGcolor ocol); + +// Creates and returns a box gradient. Box gradient is a feathered rounded rectangle, it is useful for rendering +// drop shadows or highlights for boxes. Parameters (x,y) define the top-left corner of the rectangle, +// (w,h) define the size of the rectangle, r defines the corner radius, and f feather. Feather defines how blurry +// the border of the rectangle is. Parameter icol specifies the inner color and ocol the outer color of the gradient. +// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). +NVGpaint nvgBoxGradient(NVGcontext* ctx, float x, float y, float w, float h, + float r, float f, NVGcolor icol, NVGcolor ocol); + +// Creates and returns a radial gradient. Parameters (cx,cy) specify the center, inr and outr specify +// the inner and outer radius of the gradient, icol specifies the start color and ocol the end color. +// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). +NVGpaint nvgRadialGradient(NVGcontext* ctx, float cx, float cy, float inr, float outr, + NVGcolor icol, NVGcolor ocol); + +// Creates and returns an image patter. Parameters (ox,oy) specify the left-top location of the image pattern, +// (ex,ey) the size of one image, angle rotation around the top-left corner, image is handle to the image to render. +// The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). +NVGpaint nvgImagePattern(NVGcontext* ctx, float ox, float oy, float ex, float ey, + float angle, int image, float alpha); + +// +// Scissoring +// +// Scissoring allows you to clip the rendering into a rectangle. This is useful for various +// user interface cases like rendering a text edit or a timeline. + +// Sets the current scissor rectangle. +// The scissor rectangle is transformed by the current transform. +void nvgScissor(NVGcontext* ctx, float x, float y, float w, float h); + +// Intersects current scissor rectangle with the specified rectangle. +// The scissor rectangle is transformed by the current transform. +// Note: in case the rotation of previous scissor rect differs from +// the current one, the intersection will be done between the specified +// rectangle and the previous scissor rectangle transformed in the current +// transform space. The resulting shape is always rectangle. +void nvgIntersectScissor(NVGcontext* ctx, float x, float y, float w, float h); + +// Reset and disables scissoring. +void nvgResetScissor(NVGcontext* ctx); + +// +// Paths +// +// Drawing a new shape starts with nvgBeginPath(), it clears all the currently defined paths. +// Then you define one or more paths and sub-paths which describe the shape. The are functions +// to draw common shapes like rectangles and circles, and lower level step-by-step functions, +// which allow to define a path curve by curve. +// +// NanoVG uses even-odd fill rule to draw the shapes. Solid shapes should have counter clockwise +// winding and holes should have counter clockwise order. To specify winding of a path you can +// call nvgPathWinding(). This is useful especially for the common shapes, which are drawn CCW. +// +// Finally you can fill the path using current fill style by calling nvgFill(), and stroke it +// with current stroke style by calling nvgStroke(). +// +// The curve segments and sub-paths are transformed by the current transform. + +// Clears the current path and sub-paths. +void nvgBeginPath(NVGcontext* ctx); + +// Starts new sub-path with specified point as first point. +void nvgMoveTo(NVGcontext* ctx, float x, float y); + +// Adds line segment from the last point in the path to the specified point. +void nvgLineTo(NVGcontext* ctx, float x, float y); + +// Adds cubic bezier segment from last point in the path via two control points to the specified point. +void nvgBezierTo(NVGcontext* ctx, float c1x, float c1y, float c2x, float c2y, float x, float y); + +// Adds quadratic bezier segment from last point in the path via a control point to the specified point. +void nvgQuadTo(NVGcontext* ctx, float cx, float cy, float x, float y); + +// Adds an arc segment at the corner defined by the last path point, and two specified points. +void nvgArcTo(NVGcontext* ctx, float x1, float y1, float x2, float y2, float radius); + +// Closes current sub-path with a line segment. +void nvgClosePath(NVGcontext* ctx); + +// Sets the current sub-path winding, see NVGwinding and NVGsolidity. +void nvgPathWinding(NVGcontext* ctx, int dir); + +// Creates new circle arc shaped sub-path. The arc center is at cx,cy, the arc radius is r, +// and the arc is drawn from angle a0 to a1, and swept in direction dir (NVG_CCW, or NVG_CW). +// Angles are specified in radians. +void nvgArc(NVGcontext* ctx, float cx, float cy, float r, float a0, float a1, int dir); + +// Creates new rectangle shaped sub-path. +void nvgRect(NVGcontext* ctx, float x, float y, float w, float h); + +// Creates new rounded rectangle shaped sub-path. +void nvgRoundedRect(NVGcontext* ctx, float x, float y, float w, float h, float r); + +// Creates new rounded rectangle shaped sub-path with varying radii for each corner. +void nvgRoundedRectVarying(NVGcontext* ctx, float x, float y, float w, float h, float radTopLeft, float radTopRight, float radBottomRight, float radBottomLeft); + +// Creates new ellipse shaped sub-path. +void nvgEllipse(NVGcontext* ctx, float cx, float cy, float rx, float ry); + +// Creates new circle shaped sub-path. +void nvgCircle(NVGcontext* ctx, float cx, float cy, float r); + +// Fills the current path with current fill style. +void nvgFill(NVGcontext* ctx); + +// Fills the current path with current stroke style. +void nvgStroke(NVGcontext* ctx); + + +// +// Text +// +// NanoVG allows you to load .ttf files and use the font to render text. +// +// The appearance of the text can be defined by setting the current text style +// and by specifying the fill color. Common text and font settings such as +// font size, letter spacing and text align are supported. Font blur allows you +// to create simple text effects such as drop shadows. +// +// At render time the font face can be set based on the font handles or name. +// +// Font measure functions return values in local space, the calculations are +// carried in the same resolution as the final rendering. This is done because +// the text glyph positions are snapped to the nearest pixels sharp rendering. +// +// The local space means that values are not rotated or scale as per the current +// transformation. For example if you set font size to 12, which would mean that +// line height is 16, then regardless of the current scaling and rotation, the +// returned line height is always 16. Some measures may vary because of the scaling +// since aforementioned pixel snapping. +// +// While this may sound a little odd, the setup allows you to always render the +// same way regardless of scaling. I.e. following works regardless of scaling: +// +// const char* txt = "Text me up."; +// nvgTextBounds(vg, x,y, txt, NULL, bounds); +// nvgBeginPath(vg); +// nvgRoundedRect(vg, bounds[0],bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); +// nvgFill(vg); +// +// Note: currently only solid color fill is supported for text. + +// Creates font by loading it from the disk from specified file name. +// Returns handle to the font. +int nvgCreateFont(NVGcontext* ctx, const char* name, const char* filename); + +// fontIndex specifies which font face to load from a .ttf/.ttc file. +int nvgCreateFontAtIndex(NVGcontext* ctx, const char* name, const char* filename, const int fontIndex); + +// Creates font by loading it from the specified memory chunk. +// Returns handle to the font. +int nvgCreateFontMem(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData); + +// fontIndex specifies which font face to load from a .ttf/.ttc file. +int nvgCreateFontMemAtIndex(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData, const int fontIndex); + +// Finds a loaded font of specified name, and returns handle to it, or -1 if the font is not found. +int nvgFindFont(NVGcontext* ctx, const char* name); + +// Adds a fallback font by handle. +int nvgAddFallbackFontId(NVGcontext* ctx, int baseFont, int fallbackFont); + +// Adds a fallback font by name. +int nvgAddFallbackFont(NVGcontext* ctx, const char* baseFont, const char* fallbackFont); + +// Resets fallback fonts by handle. +void nvgResetFallbackFontsId(NVGcontext* ctx, int baseFont); + +// Resets fallback fonts by name. +void nvgResetFallbackFonts(NVGcontext* ctx, const char* baseFont); + +// Sets the font size of current text style. +void nvgFontSize(NVGcontext* ctx, float size); + +// Sets the blur of current text style. +void nvgFontBlur(NVGcontext* ctx, float blur); + +// Sets the letter spacing of current text style. +void nvgTextLetterSpacing(NVGcontext* ctx, float spacing); + +// Sets the proportional line height of current text style. The line height is specified as multiple of font size. +void nvgTextLineHeight(NVGcontext* ctx, float lineHeight); + +// Sets the text align of current text style, see NVGalign for options. +void nvgTextAlign(NVGcontext* ctx, int align); + +// Sets the font face based on specified id of current text style. +void nvgFontFaceId(NVGcontext* ctx, int font); + +// Sets the font face based on specified name of current text style. +void nvgFontFace(NVGcontext* ctx, const char* font); + +// Draws text string at specified location. If end is specified only the sub-string up to the end is drawn. +float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* end); + +// Draws multi-line text string at specified location wrapped at the specified width. If end is specified only the sub-string up to the end is drawn. +// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. +// Words longer than the max width are slit at nearest character (i.e. no hyphenation). +void nvgTextBox(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end); + +// Measures the specified text string. Parameter bounds should be a pointer to float[4], +// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] +// Returns the horizontal advance of the measured text (i.e. where the next character should drawn). +// Measured values are returned in local coordinate space. +float nvgTextBounds(NVGcontext* ctx, float x, float y, const char* string, const char* end, float* bounds); + +// Measures the specified multi-text string. Parameter bounds should be a pointer to float[4], +// if the bounding box of the text should be returned. The bounds value are [xmin,ymin, xmax,ymax] +// Measured values are returned in local coordinate space. +void nvgTextBoxBounds(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end, float* bounds); + +// Calculates the glyph x positions of the specified text. If end is specified only the sub-string will be used. +// Measured values are returned in local coordinate space. +int nvgTextGlyphPositions(NVGcontext* ctx, float x, float y, const char* string, const char* end, NVGglyphPosition* positions, int maxPositions); + +// Returns the vertical metrics based on the current text style. +// Measured values are returned in local coordinate space. +void nvgTextMetrics(NVGcontext* ctx, float* ascender, float* descender, float* lineh); + +// Breaks the specified text into lines. If end is specified only the sub-string will be used. +// White space is stripped at the beginning of the rows, the text is split at word boundaries or when new-line characters are encountered. +// Words longer than the max width are slit at nearest character (i.e. no hyphenation). +int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, float breakRowWidth, NVGtextRow* rows, int maxRows); + +// +// Internal Render API +// +enum NVGtexture { + NVG_TEXTURE_ALPHA = 0x01, + NVG_TEXTURE_RGBA = 0x02, +}; + +struct NVGscissor { + float xform[6]; + float extent[2]; +}; +typedef struct NVGscissor NVGscissor; + +struct NVGvertex { + float x,y,u,v; +}; +typedef struct NVGvertex NVGvertex; + +struct NVGpath { + int first; + int count; + unsigned char closed; + int nbevel; + NVGvertex* fill; + int nfill; + NVGvertex* stroke; + int nstroke; + int winding; + int convex; +}; +typedef struct NVGpath NVGpath; + +struct NVGparams { + void* userPtr; + int edgeAntiAlias; + int (*renderCreate)(void* uptr); + int (*renderCreateTexture)(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data); + int (*renderDeleteTexture)(void* uptr, int image); + int (*renderUpdateTexture)(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data); + int (*renderGetTextureSize)(void* uptr, int image, int* w, int* h); + void (*renderViewport)(void* uptr, float width, float height, float devicePixelRatio); + void (*renderCancel)(void* uptr); + void (*renderFlush)(void* uptr); + void (*renderFill)(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe, const float* bounds, const NVGpath* paths, int npaths); + void (*renderStroke)(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe, float strokeWidth, const NVGpath* paths, int npaths); + void (*renderTriangles)(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, const NVGvertex* verts, int nverts, float fringe); + void (*renderDelete)(void* uptr); +}; +typedef struct NVGparams NVGparams; + +// Constructor and destructor, called by the render back-end. +NVGcontext* nvgCreateInternal(NVGparams* params); +void nvgDeleteInternal(NVGcontext* ctx); + +NVGparams* nvgInternalParams(NVGcontext* ctx); + +// Debug function to dump cached path data. +void nvgDebugDumpPathCache(NVGcontext* ctx); + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#define NVG_NOTUSED(v) for (;;) { (void)(1 ? (void)0 : ( (void)(v) ) ); break; } + +#ifdef __cplusplus +} +#endif + +#endif // NANOVG_H diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/dk_renderer.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/dk_renderer.hpp new file mode 100644 index 00000000..f251ade5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/dk_renderer.hpp @@ -0,0 +1,208 @@ +#pragma once + +#include <deko3d.hpp> +#include <map> +#include <memory> +#include <vector> +#include <optional> + +#include "framework/CDescriptorSet.h" +#include "framework/CMemPool.h" +#include "framework/CShader.h" +#include "framework/CCmdMemRing.h" +#include "nanovg.h" + +// Create flags +enum NVGcreateFlags { + // Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA). + NVG_ANTIALIAS = 1<<0, + // Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little + // slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. + NVG_STENCIL_STROKES = 1<<1, + // Flag indicating that additional debug checks are done. + NVG_DEBUG = 1<<2, +}; + +enum DKNVGuniformLoc +{ + DKNVG_LOC_VIEWSIZE, + DKNVG_LOC_TEX, + DKNVG_LOC_FRAG, + DKNVG_MAX_LOCS +}; + +enum VKNVGshaderType { + NSVG_SHADER_FILLGRAD, + NSVG_SHADER_FILLIMG, + NSVG_SHADER_SIMPLE, + NSVG_SHADER_IMG +}; + +struct DKNVGtextureDescriptor { + int width, height; + int type; + int flags; +}; + +struct DKNVGblend { + int srcRGB; + int dstRGB; + int srcAlpha; + int dstAlpha; +}; + +enum DKNVGcallType { + DKNVG_NONE = 0, + DKNVG_FILL, + DKNVG_CONVEXFILL, + DKNVG_STROKE, + DKNVG_TRIANGLES, +}; + +struct DKNVGcall { + int type; + int image; + int pathOffset; + int pathCount; + int triangleOffset; + int triangleCount; + int uniformOffset; + DKNVGblend blendFunc; +}; + +struct DKNVGpath { + int fillOffset; + int fillCount; + int strokeOffset; + int strokeCount; +}; + +struct DKNVGfragUniforms { + float scissorMat[12]; // matrices are actually 3 vec4s + float paintMat[12]; + struct NVGcolor innerCol; + struct NVGcolor outerCol; + float scissorExt[2]; + float scissorScale[2]; + float extent[2]; + float radius; + float feather; + float strokeMult; + float strokeThr; + int texType; + int type; +}; + +namespace nvg { + class DkRenderer; +} + +struct DKNVGcontext { + nvg::DkRenderer *renderer; + float view[2]; + int fragSize; + int flags; + // Per frame buffers + DKNVGcall* calls; + int ccalls; + int ncalls; + DKNVGpath* paths; + int cpaths; + int npaths; + struct NVGvertex* verts; + int cverts; + int nverts; + unsigned char* uniforms; + int cuniforms; + int nuniforms; +}; + +namespace nvg { + + class Texture { + private: + const int m_id; + dk::Image m_image; + dk::ImageDescriptor m_image_descriptor; + CMemPool::Handle m_image_mem; + DKNVGtextureDescriptor m_texture_descriptor; + public: + Texture(int id); + ~Texture(); + + void Initialize(CMemPool &image_pool, CMemPool &scratch_pool, dk::Device device, dk::Queue transfer_queue, int type, int w, int h, int image_flags, const u8 *data); + void Update(CMemPool &image_pool, CMemPool &scratch_pool, dk::Device device, dk::Queue transfer_queue, int type, int w, int h, int image_flags, const u8 *data); + + int GetId(); + const DKNVGtextureDescriptor &GetDescriptor(); + + dk::Image &GetImage(); + dk::ImageDescriptor &GetImageDescriptor(); + }; + + class DkRenderer { + private: + enum SamplerType : u8 { + SamplerType_MipFilter = 1 << 0, + SamplerType_Nearest = 1 << 1, + SamplerType_RepeatX = 1 << 2, + SamplerType_RepeatY = 1 << 3, + SamplerType_Total = 0x10, + }; + private: + static constexpr size_t DynamicCmdSize = 0x20000; + static constexpr size_t FragmentUniformSize = sizeof(DKNVGfragUniforms) + 4 - sizeof(DKNVGfragUniforms) % 4; + static constexpr size_t MaxImages = 0x1000; + + /* From the application. */ + u32 m_view_width; + u32 m_view_height; + dk::Device m_device; + dk::Queue m_queue; + CMemPool &m_image_mem_pool; + CMemPool &m_code_mem_pool; + CMemPool &m_data_mem_pool; + + /* State. */ + dk::UniqueCmdBuf m_dyn_cmd_buf; + CCmdMemRing<1> m_dyn_cmd_mem; + std::optional<CMemPool::Handle> m_vertex_buffer; + CShader m_vertex_shader; + CShader m_fragment_shader; + CMemPool::Handle m_view_uniform_buffer; + CMemPool::Handle m_frag_uniform_buffer; + + u32 m_next_texture_id = 1; + std::vector<std::shared_ptr<Texture>> m_textures; + CDescriptorSet<MaxImages> m_image_descriptor_set; + CDescriptorSet<SamplerType_Total> m_sampler_descriptor_set; + std::array<int, MaxImages> m_image_descriptor_mappings; + int m_last_image_descriptor = 0; + + int AcquireImageDescriptor(std::shared_ptr<Texture> texture, int image); + void FreeImageDescriptor(int image); + void SetUniforms(const DKNVGcontext &ctx, int offset, int image); + + void UpdateVertexBuffer(const void *data, size_t size); + + void DrawFill(const DKNVGcontext &ctx, const DKNVGcall &call); + void DrawConvexFill(const DKNVGcontext &ctx, const DKNVGcall &call); + void DrawStroke(const DKNVGcontext &ctx, const DKNVGcall &call); + void DrawTriangles(const DKNVGcontext &ctx, const DKNVGcall &call); + + std::shared_ptr<Texture> FindTexture(int id); + public: + DkRenderer(unsigned int view_width, unsigned int view_height, dk::Device device, dk::Queue queue, CMemPool &image_mem_pool, CMemPool &code_mem_pool, CMemPool &data_mem_pool); + ~DkRenderer(); + + int Create(DKNVGcontext &ctx); + int CreateTexture(const DKNVGcontext &ctx, int type, int w, int h, int image_flags, const u8 *data); + int DeleteTexture(const DKNVGcontext &ctx, int id); + int UpdateTexture(const DKNVGcontext &ctx, int id, int x, int y, int w, int h, const u8 *data); + int GetTextureSize(const DKNVGcontext &ctx, int id, int *w, int *h); + const DKNVGtextureDescriptor *GetTextureDescriptor(const DKNVGcontext &ctx, int id); + + void Flush(DKNVGcontext &ctx); + }; + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/fontstash.h b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/fontstash.h new file mode 100644 index 00000000..e7da6019 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/fontstash.h @@ -0,0 +1,1785 @@ +// +// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef FONS_H +#define FONS_H + +#define FONS_INVALID -1 + +enum FONSflags { + FONS_ZERO_TOPLEFT = 1, + FONS_ZERO_BOTTOMLEFT = 2, +}; + +enum FONSalign { + // Horizontal align + FONS_ALIGN_LEFT = 1<<0, // Default + FONS_ALIGN_CENTER = 1<<1, + FONS_ALIGN_RIGHT = 1<<2, + // Vertical align + FONS_ALIGN_TOP = 1<<3, + FONS_ALIGN_MIDDLE = 1<<4, + FONS_ALIGN_BOTTOM = 1<<5, + FONS_ALIGN_BASELINE = 1<<6, // Default +}; + +enum FONSglyphBitmap { + FONS_GLYPH_BITMAP_OPTIONAL = 1, + FONS_GLYPH_BITMAP_REQUIRED = 2, +}; + +enum FONSerrorCode { + // Font atlas is full. + FONS_ATLAS_FULL = 1, + // Scratch memory used to render glyphs is full, requested size reported in 'val', you may need to bump up FONS_SCRATCH_BUF_SIZE. + FONS_SCRATCH_FULL = 2, + // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES. + FONS_STATES_OVERFLOW = 3, + // Trying to pop too many states fonsPopState(). + FONS_STATES_UNDERFLOW = 4, +}; + +struct FONSparams { + int width, height; + unsigned char flags; + void* userPtr; + int (*renderCreate)(void* uptr, int width, int height); + int (*renderResize)(void* uptr, int width, int height); + void (*renderUpdate)(void* uptr, int* rect, const unsigned char* data); + void (*renderDraw)(void* uptr, const float* verts, const float* tcoords, const unsigned int* colors, int nverts); + void (*renderDelete)(void* uptr); +}; +typedef struct FONSparams FONSparams; + +struct FONSquad +{ + float x0,y0,s0,t0; + float x1,y1,s1,t1; +}; +typedef struct FONSquad FONSquad; + +struct FONStextIter { + float x, y, nextx, nexty, scale, spacing; + unsigned int codepoint; + short isize, iblur; + struct FONSfont* font; + int prevGlyphIndex; + const char* str; + const char* next; + const char* end; + unsigned int utf8state; + int bitmapOption; +}; +typedef struct FONStextIter FONStextIter; + +typedef struct FONScontext FONScontext; + +// Constructor and destructor. +FONScontext* fonsCreateInternal(FONSparams* params); +void fonsDeleteInternal(FONScontext* s); + +void fonsSetErrorCallback(FONScontext* s, void (*callback)(void* uptr, int error, int val), void* uptr); +// Returns current atlas size. +void fonsGetAtlasSize(FONScontext* s, int* width, int* height); +// Expands the atlas size. +int fonsExpandAtlas(FONScontext* s, int width, int height); +// Resets the whole stash. +int fonsResetAtlas(FONScontext* stash, int width, int height); + +// Add fonts +int fonsAddFont(FONScontext* s, const char* name, const char* path, int fontIndex); +int fonsAddFontMem(FONScontext* s, const char* name, unsigned char* data, int ndata, int freeData, int fontIndex); +int fonsGetFontByName(FONScontext* s, const char* name); + +// State handling +void fonsPushState(FONScontext* s); +void fonsPopState(FONScontext* s); +void fonsClearState(FONScontext* s); + +// State setting +void fonsSetSize(FONScontext* s, float size); +void fonsSetColor(FONScontext* s, unsigned int color); +void fonsSetSpacing(FONScontext* s, float spacing); +void fonsSetBlur(FONScontext* s, float blur); +void fonsSetAlign(FONScontext* s, int align); +void fonsSetFont(FONScontext* s, int font); + +// Draw text +float fonsDrawText(FONScontext* s, float x, float y, const char* string, const char* end); + +// Measure text +float fonsTextBounds(FONScontext* s, float x, float y, const char* string, const char* end, float* bounds); +void fonsLineBounds(FONScontext* s, float y, float* miny, float* maxy); +void fonsVertMetrics(FONScontext* s, float* ascender, float* descender, float* lineh); + +// Text iterator +int fonsTextIterInit(FONScontext* stash, FONStextIter* iter, float x, float y, const char* str, const char* end, int bitmapOption); +int fonsTextIterNext(FONScontext* stash, FONStextIter* iter, struct FONSquad* quad); + +// Pull texture changes +const unsigned char* fonsGetTextureData(FONScontext* stash, int* width, int* height); +int fonsValidateTexture(FONScontext* s, int* dirty); + +// Draws the stash texture for debugging +void fonsDrawDebug(FONScontext* s, float x, float y); + +#endif // FONTSTASH_H + + +#ifdef FONTSTASH_IMPLEMENTATION + +#define FONS_NOTUSED(v) (void)sizeof(v) + +#ifdef FONS_USE_FREETYPE + +#include <ft2build.h> +#include FT_FREETYPE_H +#include FT_ADVANCES_H +#include <math.h> + +struct FONSttFontImpl { + FT_Face font; +}; +typedef struct FONSttFontImpl FONSttFontImpl; + +static FT_Library ftLibrary; + +int fons__tt_init(FONScontext *context) +{ + FT_Error ftError; + FONS_NOTUSED(context); + ftError = FT_Init_FreeType(&ftLibrary); + return ftError == 0; +} + +int fons__tt_done(FONScontext *context) +{ + FT_Error ftError; + FONS_NOTUSED(context); + ftError = FT_Done_FreeType(ftLibrary); + return ftError == 0; +} + +int fons__tt_loadFont(FONScontext *context, FONSttFontImpl *font, unsigned char *data, int dataSize, int fontIndex) +{ + FT_Error ftError; + FONS_NOTUSED(context); + + //font->font.userdata = stash; + ftError = FT_New_Memory_Face(ftLibrary, (const FT_Byte*)data, dataSize, fontIndex, &font->font); + return ftError == 0; +} + +void fons__tt_getFontVMetrics(FONSttFontImpl *font, int *ascent, int *descent, int *lineGap) +{ + *ascent = font->font->ascender; + *descent = font->font->descender; + *lineGap = font->font->height - (*ascent - *descent); +} + +float fons__tt_getPixelHeightScale(FONSttFontImpl *font, float size) +{ + return size / font->font->units_per_EM; +} + +int fons__tt_getGlyphIndex(FONSttFontImpl *font, int codepoint) +{ + return FT_Get_Char_Index(font->font, codepoint); +} + +int fons__tt_buildGlyphBitmap(FONSttFontImpl *font, int glyph, float size, float scale, + int *advance, int *lsb, int *x0, int *y0, int *x1, int *y1) +{ + FT_Error ftError; + FT_GlyphSlot ftGlyph; + FT_Fixed advFixed; + FONS_NOTUSED(scale); + + ftError = FT_Set_Pixel_Sizes(font->font, 0, size); + if (ftError) return 0; + ftError = FT_Load_Glyph(font->font, glyph, FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT); + if (ftError) return 0; + ftError = FT_Get_Advance(font->font, glyph, FT_LOAD_NO_SCALE, &advFixed); + if (ftError) return 0; + ftGlyph = font->font->glyph; + *advance = (int)advFixed; + *lsb = (int)ftGlyph->metrics.horiBearingX; + *x0 = ftGlyph->bitmap_left; + *x1 = *x0 + ftGlyph->bitmap.width; + *y0 = -ftGlyph->bitmap_top; + *y1 = *y0 + ftGlyph->bitmap.rows; + return 1; +} + +void fons__tt_renderGlyphBitmap(FONSttFontImpl *font, unsigned char *output, int outWidth, int outHeight, int outStride, + float scaleX, float scaleY, int glyph) +{ + FT_GlyphSlot ftGlyph = font->font->glyph; + int ftGlyphOffset = 0; + unsigned int x, y; + FONS_NOTUSED(outWidth); + FONS_NOTUSED(outHeight); + FONS_NOTUSED(scaleX); + FONS_NOTUSED(scaleY); + FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap + + for ( y = 0; y < ftGlyph->bitmap.rows; y++ ) { + for ( x = 0; x < ftGlyph->bitmap.width; x++ ) { + output[(y * outStride) + x] = ftGlyph->bitmap.buffer[ftGlyphOffset++]; + } + } +} + +int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2) +{ + FT_Vector ftKerning; + FT_Get_Kerning(font->font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning); + return (int)((ftKerning.x + 32) >> 6); // Round up and convert to integer +} + +#else + +#define STB_TRUETYPE_IMPLEMENTATION +static void* fons__tmpalloc(size_t size, void* up); +static void fons__tmpfree(void* ptr, void* up); +#define STBTT_malloc(x,u) fons__tmpalloc(x,u) +#define STBTT_free(x,u) fons__tmpfree(x,u) +#include "stb_truetype.h" + +struct FONSttFontImpl { + stbtt_fontinfo font; +}; +typedef struct FONSttFontImpl FONSttFontImpl; + +int fons__tt_init(FONScontext *context) +{ + FONS_NOTUSED(context); + return 1; +} + +int fons__tt_done(FONScontext *context) +{ + FONS_NOTUSED(context); + return 1; +} + +int fons__tt_loadFont(FONScontext *context, FONSttFontImpl *font, unsigned char *data, int dataSize, int fontIndex) +{ + int offset, stbError; + FONS_NOTUSED(dataSize); + + font->font.userdata = context; + offset = stbtt_GetFontOffsetForIndex(data, fontIndex); + if (offset == -1) { + stbError = 0; + } else { + stbError = stbtt_InitFont(&font->font, data, offset); + } + return stbError; +} + +void fons__tt_getFontVMetrics(FONSttFontImpl *font, int *ascent, int *descent, int *lineGap) +{ + stbtt_GetFontVMetrics(&font->font, ascent, descent, lineGap); +} + +float fons__tt_getPixelHeightScale(FONSttFontImpl *font, float size) +{ + return stbtt_ScaleForMappingEmToPixels(&font->font, size); +} + +int fons__tt_getGlyphIndex(FONSttFontImpl *font, int codepoint) +{ + return stbtt_FindGlyphIndex(&font->font, codepoint); +} + +int fons__tt_buildGlyphBitmap(FONSttFontImpl *font, int glyph, float size, float scale, + int *advance, int *lsb, int *x0, int *y0, int *x1, int *y1) +{ + FONS_NOTUSED(size); + stbtt_GetGlyphHMetrics(&font->font, glyph, advance, lsb); + stbtt_GetGlyphBitmapBox(&font->font, glyph, scale, scale, x0, y0, x1, y1); + return 1; +} + +void fons__tt_renderGlyphBitmap(FONSttFontImpl *font, unsigned char *output, int outWidth, int outHeight, int outStride, + float scaleX, float scaleY, int glyph) +{ + stbtt_MakeGlyphBitmap(&font->font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); +} + +int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2) +{ + return stbtt_GetGlyphKernAdvance(&font->font, glyph1, glyph2); +} + +#endif + +#ifndef FONS_SCRATCH_BUF_SIZE +# define FONS_SCRATCH_BUF_SIZE 96000 +#endif +#ifndef FONS_HASH_LUT_SIZE +# define FONS_HASH_LUT_SIZE 256 +#endif +#ifndef FONS_INIT_FONTS +# define FONS_INIT_FONTS 4 +#endif +#ifndef FONS_INIT_GLYPHS +# define FONS_INIT_GLYPHS 256 +#endif +#ifndef FONS_INIT_ATLAS_NODES +# define FONS_INIT_ATLAS_NODES 256 +#endif +#ifndef FONS_VERTEX_COUNT +# define FONS_VERTEX_COUNT 1024 +#endif +#ifndef FONS_MAX_STATES +# define FONS_MAX_STATES 20 +#endif +#ifndef FONS_MAX_FALLBACKS +# define FONS_MAX_FALLBACKS 20 +#endif + +static unsigned int fons__hashint(unsigned int a) +{ + a += ~(a<<15); + a ^= (a>>10); + a += (a<<3); + a ^= (a>>6); + a += ~(a<<11); + a ^= (a>>16); + return a; +} + +static int fons__mini(int a, int b) +{ + return a < b ? a : b; +} + +static int fons__maxi(int a, int b) +{ + return a > b ? a : b; +} + +struct FONSglyph +{ + unsigned int codepoint; + int index; + int next; + short size, blur; + short x0,y0,x1,y1; + short xadv,xoff,yoff; +}; +typedef struct FONSglyph FONSglyph; + +struct FONSfont +{ + FONSttFontImpl font; + char name[64]; + unsigned char* data; + int dataSize; + unsigned char freeData; + float ascender; + float descender; + float lineh; + FONSglyph* glyphs; + int cglyphs; + int nglyphs; + int lut[FONS_HASH_LUT_SIZE]; + int fallbacks[FONS_MAX_FALLBACKS]; + int nfallbacks; +}; +typedef struct FONSfont FONSfont; + +struct FONSstate +{ + int font; + int align; + float size; + unsigned int color; + float blur; + float spacing; +}; +typedef struct FONSstate FONSstate; + +struct FONSatlasNode { + short x, y, width; +}; +typedef struct FONSatlasNode FONSatlasNode; + +struct FONSatlas +{ + int width, height; + FONSatlasNode* nodes; + int nnodes; + int cnodes; +}; +typedef struct FONSatlas FONSatlas; + +struct FONScontext +{ + FONSparams params; + float itw,ith; + unsigned char* texData; + int dirtyRect[4]; + FONSfont** fonts; + FONSatlas* atlas; + int cfonts; + int nfonts; + float verts[FONS_VERTEX_COUNT*2]; + float tcoords[FONS_VERTEX_COUNT*2]; + unsigned int colors[FONS_VERTEX_COUNT]; + int nverts; + unsigned char* scratch; + int nscratch; + FONSstate states[FONS_MAX_STATES]; + int nstates; + void (*handleError)(void* uptr, int error, int val); + void* errorUptr; +}; + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +static void* fons__tmpalloc(size_t size, void* up) +{ + unsigned char* ptr; + FONScontext* stash = (FONScontext*)up; + + // 16-byte align the returned pointer + size = (size + 0xf) & ~0xf; + + if (stash->nscratch+(int)size > FONS_SCRATCH_BUF_SIZE) { + if (stash->handleError) + stash->handleError(stash->errorUptr, FONS_SCRATCH_FULL, stash->nscratch+(int)size); + return NULL; + } + ptr = stash->scratch + stash->nscratch; + stash->nscratch += (int)size; + return ptr; +} + +static void fons__tmpfree(void* ptr, void* up) +{ + (void)ptr; + (void)up; + // empty +} + +#endif // STB_TRUETYPE_IMPLEMENTATION + +// Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de> +// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. + +#define FONS_UTF8_ACCEPT 0 +#define FONS_UTF8_REJECT 12 + +static unsigned int fons__decutf8(unsigned int* state, unsigned int* codep, unsigned int byte) +{ + static const unsigned char utf8d[] = { + // The first part of the table maps bytes to character classes that + // to reduce the size of the transition table and create bitmasks. + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + + // The second part is a transition table that maps a combination + // of a state of the automaton and a character class to a state. + 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, + 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, + 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, + 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, + 12,36,12,12,12,12,12,12,12,12,12,12, + }; + + unsigned int type = utf8d[byte]; + + *codep = (*state != FONS_UTF8_ACCEPT) ? + (byte & 0x3fu) | (*codep << 6) : + (0xff >> type) & (byte); + + *state = utf8d[256 + *state + type]; + return *state; +} + +// Atlas based on Skyline Bin Packer by Jukka Jylänki + +static void fons__deleteAtlas(FONSatlas* atlas) +{ + if (atlas == NULL) return; + if (atlas->nodes != NULL) free(atlas->nodes); + free(atlas); +} + +static FONSatlas* fons__allocAtlas(int w, int h, int nnodes) +{ + FONSatlas* atlas = NULL; + + // Allocate memory for the font stash. + atlas = (FONSatlas*)malloc(sizeof(FONSatlas)); + if (atlas == NULL) goto error; + memset(atlas, 0, sizeof(FONSatlas)); + + atlas->width = w; + atlas->height = h; + + // Allocate space for skyline nodes + atlas->nodes = (FONSatlasNode*)malloc(sizeof(FONSatlasNode) * nnodes); + if (atlas->nodes == NULL) goto error; + memset(atlas->nodes, 0, sizeof(FONSatlasNode) * nnodes); + atlas->nnodes = 0; + atlas->cnodes = nnodes; + + // Init root node. + atlas->nodes[0].x = 0; + atlas->nodes[0].y = 0; + atlas->nodes[0].width = (short)w; + atlas->nnodes++; + + return atlas; + +error: + if (atlas) fons__deleteAtlas(atlas); + return NULL; +} + +static int fons__atlasInsertNode(FONSatlas* atlas, int idx, int x, int y, int w) +{ + int i; + // Insert node + if (atlas->nnodes+1 > atlas->cnodes) { + atlas->cnodes = atlas->cnodes == 0 ? 8 : atlas->cnodes * 2; + atlas->nodes = (FONSatlasNode*)realloc(atlas->nodes, sizeof(FONSatlasNode) * atlas->cnodes); + if (atlas->nodes == NULL) + return 0; + } + for (i = atlas->nnodes; i > idx; i--) + atlas->nodes[i] = atlas->nodes[i-1]; + atlas->nodes[idx].x = (short)x; + atlas->nodes[idx].y = (short)y; + atlas->nodes[idx].width = (short)w; + atlas->nnodes++; + + return 1; +} + +static void fons__atlasRemoveNode(FONSatlas* atlas, int idx) +{ + int i; + if (atlas->nnodes == 0) return; + for (i = idx; i < atlas->nnodes-1; i++) + atlas->nodes[i] = atlas->nodes[i+1]; + atlas->nnodes--; +} + +static void fons__atlasExpand(FONSatlas* atlas, int w, int h) +{ + // Insert node for empty space + if (w > atlas->width) + fons__atlasInsertNode(atlas, atlas->nnodes, atlas->width, 0, w - atlas->width); + atlas->width = w; + atlas->height = h; +} + +static void fons__atlasReset(FONSatlas* atlas, int w, int h) +{ + atlas->width = w; + atlas->height = h; + atlas->nnodes = 0; + + // Init root node. + atlas->nodes[0].x = 0; + atlas->nodes[0].y = 0; + atlas->nodes[0].width = (short)w; + atlas->nnodes++; +} + +static int fons__atlasAddSkylineLevel(FONSatlas* atlas, int idx, int x, int y, int w, int h) +{ + int i; + + // Insert new node + if (fons__atlasInsertNode(atlas, idx, x, y+h, w) == 0) + return 0; + + // Delete skyline segments that fall under the shadow of the new segment. + for (i = idx+1; i < atlas->nnodes; i++) { + if (atlas->nodes[i].x < atlas->nodes[i-1].x + atlas->nodes[i-1].width) { + int shrink = atlas->nodes[i-1].x + atlas->nodes[i-1].width - atlas->nodes[i].x; + atlas->nodes[i].x += (short)shrink; + atlas->nodes[i].width -= (short)shrink; + if (atlas->nodes[i].width <= 0) { + fons__atlasRemoveNode(atlas, i); + i--; + } else { + break; + } + } else { + break; + } + } + + // Merge same height skyline segments that are next to each other. + for (i = 0; i < atlas->nnodes-1; i++) { + if (atlas->nodes[i].y == atlas->nodes[i+1].y) { + atlas->nodes[i].width += atlas->nodes[i+1].width; + fons__atlasRemoveNode(atlas, i+1); + i--; + } + } + + return 1; +} + +static int fons__atlasRectFits(FONSatlas* atlas, int i, int w, int h) +{ + // Checks if there is enough space at the location of skyline span 'i', + // and return the max height of all skyline spans under that at that location, + // (think tetris block being dropped at that position). Or -1 if no space found. + int x = atlas->nodes[i].x; + int y = atlas->nodes[i].y; + int spaceLeft; + if (x + w > atlas->width) + return -1; + spaceLeft = w; + while (spaceLeft > 0) { + if (i == atlas->nnodes) return -1; + y = fons__maxi(y, atlas->nodes[i].y); + if (y + h > atlas->height) return -1; + spaceLeft -= atlas->nodes[i].width; + ++i; + } + return y; +} + +static int fons__atlasAddRect(FONSatlas* atlas, int rw, int rh, int* rx, int* ry) +{ + int besth = atlas->height, bestw = atlas->width, besti = -1; + int bestx = -1, besty = -1, i; + + // Bottom left fit heuristic. + for (i = 0; i < atlas->nnodes; i++) { + int y = fons__atlasRectFits(atlas, i, rw, rh); + if (y != -1) { + if (y + rh < besth || (y + rh == besth && atlas->nodes[i].width < bestw)) { + besti = i; + bestw = atlas->nodes[i].width; + besth = y + rh; + bestx = atlas->nodes[i].x; + besty = y; + } + } + } + + if (besti == -1) + return 0; + + // Perform the actual packing. + if (fons__atlasAddSkylineLevel(atlas, besti, bestx, besty, rw, rh) == 0) + return 0; + + *rx = bestx; + *ry = besty; + + return 1; +} + +static void fons__addWhiteRect(FONScontext* stash, int w, int h) +{ + int x, y, gx, gy; + unsigned char* dst; + if (fons__atlasAddRect(stash->atlas, w, h, &gx, &gy) == 0) + return; + + // Rasterize + dst = &stash->texData[gx + gy * stash->params.width]; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) + dst[x] = 0xff; + dst += stash->params.width; + } + + stash->dirtyRect[0] = fons__mini(stash->dirtyRect[0], gx); + stash->dirtyRect[1] = fons__mini(stash->dirtyRect[1], gy); + stash->dirtyRect[2] = fons__maxi(stash->dirtyRect[2], gx+w); + stash->dirtyRect[3] = fons__maxi(stash->dirtyRect[3], gy+h); +} + +FONScontext* fonsCreateInternal(FONSparams* params) +{ + FONScontext* stash = NULL; + + // Allocate memory for the font stash. + stash = (FONScontext*)malloc(sizeof(FONScontext)); + if (stash == NULL) goto error; + memset(stash, 0, sizeof(FONScontext)); + + stash->params = *params; + + // Allocate scratch buffer. + stash->scratch = (unsigned char*)malloc(FONS_SCRATCH_BUF_SIZE); + if (stash->scratch == NULL) goto error; + + // Initialize implementation library + if (!fons__tt_init(stash)) goto error; + + if (stash->params.renderCreate != NULL) { + if (stash->params.renderCreate(stash->params.userPtr, stash->params.width, stash->params.height) == 0) + goto error; + } + + stash->atlas = fons__allocAtlas(stash->params.width, stash->params.height, FONS_INIT_ATLAS_NODES); + if (stash->atlas == NULL) goto error; + + // Allocate space for fonts. + stash->fonts = (FONSfont**)malloc(sizeof(FONSfont*) * FONS_INIT_FONTS); + if (stash->fonts == NULL) goto error; + memset(stash->fonts, 0, sizeof(FONSfont*) * FONS_INIT_FONTS); + stash->cfonts = FONS_INIT_FONTS; + stash->nfonts = 0; + + // Create texture for the cache. + stash->itw = 1.0f/stash->params.width; + stash->ith = 1.0f/stash->params.height; + stash->texData = (unsigned char*)malloc(stash->params.width * stash->params.height); + if (stash->texData == NULL) goto error; + memset(stash->texData, 0, stash->params.width * stash->params.height); + + stash->dirtyRect[0] = stash->params.width; + stash->dirtyRect[1] = stash->params.height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + + // Add white rect at 0,0 for debug drawing. + fons__addWhiteRect(stash, 2,2); + + fonsPushState(stash); + fonsClearState(stash); + + return stash; + +error: + fonsDeleteInternal(stash); + return NULL; +} + +static FONSstate* fons__getState(FONScontext* stash) +{ + return &stash->states[stash->nstates-1]; +} + +int fonsAddFallbackFont(FONScontext* stash, int base, int fallback) +{ + FONSfont* baseFont = stash->fonts[base]; + if (baseFont->nfallbacks < FONS_MAX_FALLBACKS) { + baseFont->fallbacks[baseFont->nfallbacks++] = fallback; + return 1; + } + return 0; +} + +void fonsResetFallbackFont(FONScontext* stash, int base) +{ + int i; + + FONSfont* baseFont = stash->fonts[base]; + baseFont->nfallbacks = 0; + baseFont->nglyphs = 0; + for (i = 0; i < FONS_HASH_LUT_SIZE; i++) + baseFont->lut[i] = -1; +} + +void fonsSetSize(FONScontext* stash, float size) +{ + fons__getState(stash)->size = size; +} + +void fonsSetColor(FONScontext* stash, unsigned int color) +{ + fons__getState(stash)->color = color; +} + +void fonsSetSpacing(FONScontext* stash, float spacing) +{ + fons__getState(stash)->spacing = spacing; +} + +void fonsSetBlur(FONScontext* stash, float blur) +{ + fons__getState(stash)->blur = blur; +} + +void fonsSetAlign(FONScontext* stash, int align) +{ + fons__getState(stash)->align = align; +} + +void fonsSetFont(FONScontext* stash, int font) +{ + fons__getState(stash)->font = font; +} + +void fonsPushState(FONScontext* stash) +{ + if (stash->nstates >= FONS_MAX_STATES) { + if (stash->handleError) + stash->handleError(stash->errorUptr, FONS_STATES_OVERFLOW, 0); + return; + } + if (stash->nstates > 0) + memcpy(&stash->states[stash->nstates], &stash->states[stash->nstates-1], sizeof(FONSstate)); + stash->nstates++; +} + +void fonsPopState(FONScontext* stash) +{ + if (stash->nstates <= 1) { + if (stash->handleError) + stash->handleError(stash->errorUptr, FONS_STATES_UNDERFLOW, 0); + return; + } + stash->nstates--; +} + +void fonsClearState(FONScontext* stash) +{ + FONSstate* state = fons__getState(stash); + state->size = 12.0f; + state->color = 0xffffffff; + state->font = 0; + state->blur = 0; + state->spacing = 0; + state->align = FONS_ALIGN_LEFT | FONS_ALIGN_BASELINE; +} + +static void fons__freeFont(FONSfont* font) +{ + if (font == NULL) return; + if (font->glyphs) free(font->glyphs); + if (font->freeData && font->data) free(font->data); + free(font); +} + +static int fons__allocFont(FONScontext* stash) +{ + FONSfont* font = NULL; + if (stash->nfonts+1 > stash->cfonts) { + stash->cfonts = stash->cfonts == 0 ? 8 : stash->cfonts * 2; + stash->fonts = (FONSfont**)realloc(stash->fonts, sizeof(FONSfont*) * stash->cfonts); + if (stash->fonts == NULL) + return -1; + } + font = (FONSfont*)malloc(sizeof(FONSfont)); + if (font == NULL) goto error; + memset(font, 0, sizeof(FONSfont)); + + font->glyphs = (FONSglyph*)malloc(sizeof(FONSglyph) * FONS_INIT_GLYPHS); + if (font->glyphs == NULL) goto error; + font->cglyphs = FONS_INIT_GLYPHS; + font->nglyphs = 0; + + stash->fonts[stash->nfonts++] = font; + return stash->nfonts-1; + +error: + fons__freeFont(font); + + return FONS_INVALID; +} + +int fonsAddFont(FONScontext* stash, const char* name, const char* path, int fontIndex) +{ + FILE* fp = 0; + int dataSize = 0; + size_t readed; + unsigned char* data = NULL; + + // Read in the font data. + fp = fopen(path, "rb"); + if (fp == NULL) goto error; + fseek(fp,0,SEEK_END); + dataSize = (int)ftell(fp); + fseek(fp,0,SEEK_SET); + data = (unsigned char*)malloc(dataSize); + if (data == NULL) goto error; + readed = fread(data, 1, dataSize, fp); + fclose(fp); + fp = 0; + if (readed != (size_t)dataSize) goto error; + + return fonsAddFontMem(stash, name, data, dataSize, 1, fontIndex); + +error: + if (data) free(data); + if (fp) fclose(fp); + return FONS_INVALID; +} + +int fonsAddFontMem(FONScontext* stash, const char* name, unsigned char* data, int dataSize, int freeData, int fontIndex) +{ + int i, ascent, descent, fh, lineGap; + FONSfont* font; + + int idx = fons__allocFont(stash); + if (idx == FONS_INVALID) + return FONS_INVALID; + + font = stash->fonts[idx]; + + strncpy(font->name, name, sizeof(font->name)); + font->name[sizeof(font->name)-1] = '\0'; + + // Init hash lookup. + for (i = 0; i < FONS_HASH_LUT_SIZE; ++i) + font->lut[i] = -1; + + // Read in the font data. + font->dataSize = dataSize; + font->data = data; + font->freeData = (unsigned char)freeData; + + // Init font + stash->nscratch = 0; + if (!fons__tt_loadFont(stash, &font->font, data, dataSize, fontIndex)) goto error; + + // Store normalized line height. The real line height is got + // by multiplying the lineh by font size. + fons__tt_getFontVMetrics( &font->font, &ascent, &descent, &lineGap); + ascent += lineGap; + fh = ascent - descent; + font->ascender = (float)ascent / (float)fh; + font->descender = (float)descent / (float)fh; + font->lineh = font->ascender - font->descender; + + return idx; + +error: + fons__freeFont(font); + stash->nfonts--; + return FONS_INVALID; +} + +int fonsGetFontByName(FONScontext* s, const char* name) +{ + int i; + for (i = 0; i < s->nfonts; i++) { + if (strcmp(s->fonts[i]->name, name) == 0) + return i; + } + return FONS_INVALID; +} + + +static FONSglyph* fons__allocGlyph(FONSfont* font) +{ + if (font->nglyphs+1 > font->cglyphs) { + font->cglyphs = font->cglyphs == 0 ? 8 : font->cglyphs * 2; + font->glyphs = (FONSglyph*)realloc(font->glyphs, sizeof(FONSglyph) * font->cglyphs); + if (font->glyphs == NULL) return NULL; + } + font->nglyphs++; + return &font->glyphs[font->nglyphs-1]; +} + + +// Based on Exponential blur, Jani Huhtanen, 2006 + +#define APREC 16 +#define ZPREC 7 + +static void fons__blurCols(unsigned char* dst, int w, int h, int dstStride, int alpha) +{ + int x, y; + for (y = 0; y < h; y++) { + int z = 0; // force zero border + for (x = 1; x < w; x++) { + z += (alpha * (((int)(dst[x]) << ZPREC) - z)) >> APREC; + dst[x] = (unsigned char)(z >> ZPREC); + } + dst[w-1] = 0; // force zero border + z = 0; + for (x = w-2; x >= 0; x--) { + z += (alpha * (((int)(dst[x]) << ZPREC) - z)) >> APREC; + dst[x] = (unsigned char)(z >> ZPREC); + } + dst[0] = 0; // force zero border + dst += dstStride; + } +} + +static void fons__blurRows(unsigned char* dst, int w, int h, int dstStride, int alpha) +{ + int x, y; + for (x = 0; x < w; x++) { + int z = 0; // force zero border + for (y = dstStride; y < h*dstStride; y += dstStride) { + z += (alpha * (((int)(dst[y]) << ZPREC) - z)) >> APREC; + dst[y] = (unsigned char)(z >> ZPREC); + } + dst[(h-1)*dstStride] = 0; // force zero border + z = 0; + for (y = (h-2)*dstStride; y >= 0; y -= dstStride) { + z += (alpha * (((int)(dst[y]) << ZPREC) - z)) >> APREC; + dst[y] = (unsigned char)(z >> ZPREC); + } + dst[0] = 0; // force zero border + dst++; + } +} + + +static void fons__blur(FONScontext* stash, unsigned char* dst, int w, int h, int dstStride, int blur) +{ + int alpha; + float sigma; + (void)stash; + + if (blur < 1) + return; + // Calculate the alpha such that 90% of the kernel is within the radius. (Kernel extends to infinity) + sigma = (float)blur * 0.57735f; // 1 / sqrt(3) + alpha = (int)((1<<APREC) * (1.0f - expf(-2.3f / (sigma+1.0f)))); + fons__blurRows(dst, w, h, dstStride, alpha); + fons__blurCols(dst, w, h, dstStride, alpha); + fons__blurRows(dst, w, h, dstStride, alpha); + fons__blurCols(dst, w, h, dstStride, alpha); +// fons__blurrows(dst, w, h, dstStride, alpha); +// fons__blurcols(dst, w, h, dstStride, alpha); +} + +static FONSglyph* fons__getGlyph(FONScontext* stash, FONSfont* font, unsigned int codepoint, + short isize, short iblur, int bitmapOption) +{ + int i, g, advance, lsb, x0, y0, x1, y1, gw, gh, gx, gy, x, y; + float scale; + FONSglyph* glyph = NULL; + unsigned int h; + float size = isize/10.0f; + int pad, added; + unsigned char* bdst; + unsigned char* dst; + FONSfont* renderFont = font; + + if (isize < 2) return NULL; + if (iblur > 20) iblur = 20; + pad = iblur+2; + + // Reset allocator. + stash->nscratch = 0; + + // Find code point and size. + h = fons__hashint(codepoint) & (FONS_HASH_LUT_SIZE-1); + i = font->lut[h]; + while (i != -1) { + if (font->glyphs[i].codepoint == codepoint && font->glyphs[i].size == isize && font->glyphs[i].blur == iblur) { + glyph = &font->glyphs[i]; + if (bitmapOption == FONS_GLYPH_BITMAP_OPTIONAL || (glyph->x0 >= 0 && glyph->y0 >= 0)) { + return glyph; + } + // At this point, glyph exists but the bitmap data is not yet created. + break; + } + i = font->glyphs[i].next; + } + + // Create a new glyph or rasterize bitmap data for a cached glyph. + g = fons__tt_getGlyphIndex(&font->font, codepoint); + // Try to find the glyph in fallback fonts. + if (g == 0) { + for (i = 0; i < font->nfallbacks; ++i) { + FONSfont* fallbackFont = stash->fonts[font->fallbacks[i]]; + int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont->font, codepoint); + if (fallbackIndex != 0) { + g = fallbackIndex; + renderFont = fallbackFont; + break; + } + } + // It is possible that we did not find a fallback glyph. + // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph. + } + scale = fons__tt_getPixelHeightScale(&renderFont->font, size); + fons__tt_buildGlyphBitmap(&renderFont->font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1); + gw = x1-x0 + pad*2; + gh = y1-y0 + pad*2; + + // Determines the spot to draw glyph in the atlas. + if (bitmapOption == FONS_GLYPH_BITMAP_REQUIRED) { + // Find free spot for the rect in the atlas + added = fons__atlasAddRect(stash->atlas, gw, gh, &gx, &gy); + if (added == 0 && stash->handleError != NULL) { + // Atlas is full, let the user to resize the atlas (or not), and try again. + stash->handleError(stash->errorUptr, FONS_ATLAS_FULL, 0); + added = fons__atlasAddRect(stash->atlas, gw, gh, &gx, &gy); + } + if (added == 0) return NULL; + } else { + // Negative coordinate indicates there is no bitmap data created. + gx = -1; + gy = -1; + } + + // Init glyph. + if (glyph == NULL) { + glyph = fons__allocGlyph(font); + glyph->codepoint = codepoint; + glyph->size = isize; + glyph->blur = iblur; + glyph->next = 0; + + // Insert char to hash lookup. + glyph->next = font->lut[h]; + font->lut[h] = font->nglyphs-1; + } + glyph->index = g; + glyph->x0 = (short)gx; + glyph->y0 = (short)gy; + glyph->x1 = (short)(glyph->x0+gw); + glyph->y1 = (short)(glyph->y0+gh); + glyph->xadv = (short)(scale * advance * 10.0f); + glyph->xoff = (short)(x0 - pad); + glyph->yoff = (short)(y0 - pad); + + if (bitmapOption == FONS_GLYPH_BITMAP_OPTIONAL) { + return glyph; + } + + // Rasterize + dst = &stash->texData[(glyph->x0+pad) + (glyph->y0+pad) * stash->params.width]; + fons__tt_renderGlyphBitmap(&renderFont->font, dst, gw-pad*2,gh-pad*2, stash->params.width, scale, scale, g); + + // Make sure there is one pixel empty border. + dst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; + for (y = 0; y < gh; y++) { + dst[y*stash->params.width] = 0; + dst[gw-1 + y*stash->params.width] = 0; + } + for (x = 0; x < gw; x++) { + dst[x] = 0; + dst[x + (gh-1)*stash->params.width] = 0; + } + + // Debug code to color the glyph background +/* unsigned char* fdst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; + for (y = 0; y < gh; y++) { + for (x = 0; x < gw; x++) { + int a = (int)fdst[x+y*stash->params.width] + 20; + if (a > 255) a = 255; + fdst[x+y*stash->params.width] = a; + } + }*/ + + // Blur + if (iblur > 0) { + stash->nscratch = 0; + bdst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; + fons__blur(stash, bdst, gw, gh, stash->params.width, iblur); + } + + stash->dirtyRect[0] = fons__mini(stash->dirtyRect[0], glyph->x0); + stash->dirtyRect[1] = fons__mini(stash->dirtyRect[1], glyph->y0); + stash->dirtyRect[2] = fons__maxi(stash->dirtyRect[2], glyph->x1); + stash->dirtyRect[3] = fons__maxi(stash->dirtyRect[3], glyph->y1); + + return glyph; +} + +static void fons__getQuad(FONScontext* stash, FONSfont* font, + int prevGlyphIndex, FONSglyph* glyph, + float scale, float spacing, float* x, float* y, FONSquad* q) +{ + float rx,ry,xoff,yoff,x0,y0,x1,y1; + + if (prevGlyphIndex != -1) { + float adv = fons__tt_getGlyphKernAdvance(&font->font, prevGlyphIndex, glyph->index) * scale; + *x += (int)(adv + spacing + 0.5f); + } + + // Each glyph has 2px border to allow good interpolation, + // one pixel to prevent leaking, and one to allow good interpolation for rendering. + // Inset the texture region by one pixel for correct interpolation. + xoff = (short)(glyph->xoff+1); + yoff = (short)(glyph->yoff+1); + x0 = (float)(glyph->x0+1); + y0 = (float)(glyph->y0+1); + x1 = (float)(glyph->x1-1); + y1 = (float)(glyph->y1-1); + + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + rx = floorf(*x + xoff); + ry = floorf(*y + yoff); + + q->x0 = rx; + q->y0 = ry; + q->x1 = rx + x1 - x0; + q->y1 = ry + y1 - y0; + + q->s0 = x0 * stash->itw; + q->t0 = y0 * stash->ith; + q->s1 = x1 * stash->itw; + q->t1 = y1 * stash->ith; + } else { + rx = floorf(*x + xoff); + ry = floorf(*y - yoff); + + q->x0 = rx; + q->y0 = ry; + q->x1 = rx + x1 - x0; + q->y1 = ry - y1 + y0; + + q->s0 = x0 * stash->itw; + q->t0 = y0 * stash->ith; + q->s1 = x1 * stash->itw; + q->t1 = y1 * stash->ith; + } + + *x += (int)(glyph->xadv / 10.0f + 0.5f); +} + +static void fons__flush(FONScontext* stash) +{ + // Flush texture + if (stash->dirtyRect[0] < stash->dirtyRect[2] && stash->dirtyRect[1] < stash->dirtyRect[3]) { + if (stash->params.renderUpdate != NULL) + stash->params.renderUpdate(stash->params.userPtr, stash->dirtyRect, stash->texData); + // Reset dirty rect + stash->dirtyRect[0] = stash->params.width; + stash->dirtyRect[1] = stash->params.height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + } + + // Flush triangles + if (stash->nverts > 0) { + if (stash->params.renderDraw != NULL) + stash->params.renderDraw(stash->params.userPtr, stash->verts, stash->tcoords, stash->colors, stash->nverts); + stash->nverts = 0; + } +} + +static __inline void fons__vertex(FONScontext* stash, float x, float y, float s, float t, unsigned int c) +{ + stash->verts[stash->nverts*2+0] = x; + stash->verts[stash->nverts*2+1] = y; + stash->tcoords[stash->nverts*2+0] = s; + stash->tcoords[stash->nverts*2+1] = t; + stash->colors[stash->nverts] = c; + stash->nverts++; +} + +static float fons__getVertAlign(FONScontext* stash, FONSfont* font, int align, short isize) +{ + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + if (align & FONS_ALIGN_TOP) { + return font->ascender * (float)isize/10.0f; + } else if (align & FONS_ALIGN_MIDDLE) { + return (font->ascender + font->descender) / 2.0f * (float)isize/10.0f; + } else if (align & FONS_ALIGN_BASELINE) { + return 0.0f; + } else if (align & FONS_ALIGN_BOTTOM) { + return font->descender * (float)isize/10.0f; + } + } else { + if (align & FONS_ALIGN_TOP) { + return -font->ascender * (float)isize/10.0f; + } else if (align & FONS_ALIGN_MIDDLE) { + return -(font->ascender + font->descender) / 2.0f * (float)isize/10.0f; + } else if (align & FONS_ALIGN_BASELINE) { + return 0.0f; + } else if (align & FONS_ALIGN_BOTTOM) { + return -font->descender * (float)isize/10.0f; + } + } + return 0.0; +} + +float fonsDrawText(FONScontext* stash, + float x, float y, + const char* str, const char* end) +{ + FONSstate* state = fons__getState(stash); + unsigned int codepoint; + unsigned int utf8state = 0; + FONSglyph* glyph = NULL; + FONSquad q; + int prevGlyphIndex = -1; + short isize = (short)(state->size*10.0f); + short iblur = (short)state->blur; + float scale; + FONSfont* font; + float width; + + if (stash == NULL) return x; + if (state->font < 0 || state->font >= stash->nfonts) return x; + font = stash->fonts[state->font]; + if (font->data == NULL) return x; + + scale = fons__tt_getPixelHeightScale(&font->font, (float)isize/10.0f); + + if (end == NULL) + end = str + strlen(str); + + // Align horizontally + if (state->align & FONS_ALIGN_LEFT) { + // empty + } else if (state->align & FONS_ALIGN_RIGHT) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width; + } else if (state->align & FONS_ALIGN_CENTER) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width * 0.5f; + } + // Align vertically. + y += fons__getVertAlign(stash, font, state->align, isize); + + for (; str != end; ++str) { + if (fons__decutf8(&utf8state, &codepoint, *(const unsigned char*)str)) + continue; + glyph = fons__getGlyph(stash, font, codepoint, isize, iblur, FONS_GLYPH_BITMAP_REQUIRED); + if (glyph != NULL) { + fons__getQuad(stash, font, prevGlyphIndex, glyph, scale, state->spacing, &x, &y, &q); + + if (stash->nverts+6 > FONS_VERTEX_COUNT) + fons__flush(stash); + + fons__vertex(stash, q.x0, q.y0, q.s0, q.t0, state->color); + fons__vertex(stash, q.x1, q.y1, q.s1, q.t1, state->color); + fons__vertex(stash, q.x1, q.y0, q.s1, q.t0, state->color); + + fons__vertex(stash, q.x0, q.y0, q.s0, q.t0, state->color); + fons__vertex(stash, q.x0, q.y1, q.s0, q.t1, state->color); + fons__vertex(stash, q.x1, q.y1, q.s1, q.t1, state->color); + } + prevGlyphIndex = glyph != NULL ? glyph->index : -1; + } + fons__flush(stash); + + return x; +} + +int fonsTextIterInit(FONScontext* stash, FONStextIter* iter, + float x, float y, const char* str, const char* end, int bitmapOption) +{ + FONSstate* state = fons__getState(stash); + float width; + + memset(iter, 0, sizeof(*iter)); + + if (stash == NULL) return 0; + if (state->font < 0 || state->font >= stash->nfonts) return 0; + iter->font = stash->fonts[state->font]; + if (iter->font->data == NULL) return 0; + + iter->isize = (short)(state->size*10.0f); + iter->iblur = (short)state->blur; + iter->scale = fons__tt_getPixelHeightScale(&iter->font->font, (float)iter->isize/10.0f); + + // Align horizontally + if (state->align & FONS_ALIGN_LEFT) { + // empty + } else if (state->align & FONS_ALIGN_RIGHT) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width; + } else if (state->align & FONS_ALIGN_CENTER) { + width = fonsTextBounds(stash, x,y, str, end, NULL); + x -= width * 0.5f; + } + // Align vertically. + y += fons__getVertAlign(stash, iter->font, state->align, iter->isize); + + if (end == NULL) + end = str + strlen(str); + + iter->x = iter->nextx = x; + iter->y = iter->nexty = y; + iter->spacing = state->spacing; + iter->str = str; + iter->next = str; + iter->end = end; + iter->codepoint = 0; + iter->prevGlyphIndex = -1; + iter->bitmapOption = bitmapOption; + + return 1; +} + +int fonsTextIterNext(FONScontext* stash, FONStextIter* iter, FONSquad* quad) +{ + FONSglyph* glyph = NULL; + const char* str = iter->next; + iter->str = iter->next; + + if (str == iter->end) + return 0; + + for (; str != iter->end; str++) { + if (fons__decutf8(&iter->utf8state, &iter->codepoint, *(const unsigned char*)str)) + continue; + str++; + // Get glyph and quad + iter->x = iter->nextx; + iter->y = iter->nexty; + glyph = fons__getGlyph(stash, iter->font, iter->codepoint, iter->isize, iter->iblur, iter->bitmapOption); + // If the iterator was initialized with FONS_GLYPH_BITMAP_OPTIONAL, then the UV coordinates of the quad will be invalid. + if (glyph != NULL) + fons__getQuad(stash, iter->font, iter->prevGlyphIndex, glyph, iter->scale, iter->spacing, &iter->nextx, &iter->nexty, quad); + iter->prevGlyphIndex = glyph != NULL ? glyph->index : -1; + break; + } + iter->next = str; + + return 1; +} + +void fonsDrawDebug(FONScontext* stash, float x, float y) +{ + int i; + int w = stash->params.width; + int h = stash->params.height; + float u = w == 0 ? 0 : (1.0f / w); + float v = h == 0 ? 0 : (1.0f / h); + + if (stash->nverts+6+6 > FONS_VERTEX_COUNT) + fons__flush(stash); + + // Draw background + fons__vertex(stash, x+0, y+0, u, v, 0x0fffffff); + fons__vertex(stash, x+w, y+h, u, v, 0x0fffffff); + fons__vertex(stash, x+w, y+0, u, v, 0x0fffffff); + + fons__vertex(stash, x+0, y+0, u, v, 0x0fffffff); + fons__vertex(stash, x+0, y+h, u, v, 0x0fffffff); + fons__vertex(stash, x+w, y+h, u, v, 0x0fffffff); + + // Draw texture + fons__vertex(stash, x+0, y+0, 0, 0, 0xffffffff); + fons__vertex(stash, x+w, y+h, 1, 1, 0xffffffff); + fons__vertex(stash, x+w, y+0, 1, 0, 0xffffffff); + + fons__vertex(stash, x+0, y+0, 0, 0, 0xffffffff); + fons__vertex(stash, x+0, y+h, 0, 1, 0xffffffff); + fons__vertex(stash, x+w, y+h, 1, 1, 0xffffffff); + + // Drawbug draw atlas + for (i = 0; i < stash->atlas->nnodes; i++) { + FONSatlasNode* n = &stash->atlas->nodes[i]; + + if (stash->nverts+6 > FONS_VERTEX_COUNT) + fons__flush(stash); + + fons__vertex(stash, x+n->x+0, y+n->y+0, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+n->width, y+n->y+1, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+n->width, y+n->y+0, u, v, 0xc00000ff); + + fons__vertex(stash, x+n->x+0, y+n->y+0, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+0, y+n->y+1, u, v, 0xc00000ff); + fons__vertex(stash, x+n->x+n->width, y+n->y+1, u, v, 0xc00000ff); + } + + fons__flush(stash); +} + +float fonsTextBounds(FONScontext* stash, + float x, float y, + const char* str, const char* end, + float* bounds) +{ + FONSstate* state = fons__getState(stash); + unsigned int codepoint; + unsigned int utf8state = 0; + FONSquad q; + FONSglyph* glyph = NULL; + int prevGlyphIndex = -1; + short isize = (short)(state->size*10.0f); + short iblur = (short)state->blur; + float scale; + FONSfont* font; + float startx, advance; + float minx, miny, maxx, maxy; + + if (stash == NULL) return 0; + if (state->font < 0 || state->font >= stash->nfonts) return 0; + font = stash->fonts[state->font]; + if (font->data == NULL) return 0; + + scale = fons__tt_getPixelHeightScale(&font->font, (float)isize/10.0f); + + // Align vertically. + y += fons__getVertAlign(stash, font, state->align, isize); + + minx = maxx = x; + miny = maxy = y; + startx = x; + + if (end == NULL) + end = str + strlen(str); + + for (; str != end; ++str) { + if (fons__decutf8(&utf8state, &codepoint, *(const unsigned char*)str)) + continue; + glyph = fons__getGlyph(stash, font, codepoint, isize, iblur, FONS_GLYPH_BITMAP_OPTIONAL); + if (glyph != NULL) { + fons__getQuad(stash, font, prevGlyphIndex, glyph, scale, state->spacing, &x, &y, &q); + if (q.x0 < minx) minx = q.x0; + if (q.x1 > maxx) maxx = q.x1; + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + if (q.y0 < miny) miny = q.y0; + if (q.y1 > maxy) maxy = q.y1; + } else { + if (q.y1 < miny) miny = q.y1; + if (q.y0 > maxy) maxy = q.y0; + } + } + prevGlyphIndex = glyph != NULL ? glyph->index : -1; + } + + advance = x - startx; + + // Align horizontally + if (state->align & FONS_ALIGN_LEFT) { + // empty + } else if (state->align & FONS_ALIGN_RIGHT) { + minx -= advance; + maxx -= advance; + } else if (state->align & FONS_ALIGN_CENTER) { + minx -= advance * 0.5f; + maxx -= advance * 0.5f; + } + + if (bounds) { + bounds[0] = minx; + bounds[1] = miny; + bounds[2] = maxx; + bounds[3] = maxy; + } + + return advance; +} + +void fonsVertMetrics(FONScontext* stash, + float* ascender, float* descender, float* lineh) +{ + FONSfont* font; + FONSstate* state = fons__getState(stash); + short isize; + + if (stash == NULL) return; + if (state->font < 0 || state->font >= stash->nfonts) return; + font = stash->fonts[state->font]; + isize = (short)(state->size*10.0f); + if (font->data == NULL) return; + + if (ascender) + *ascender = font->ascender*isize/10.0f; + if (descender) + *descender = font->descender*isize/10.0f; + if (lineh) + *lineh = font->lineh*isize/10.0f; +} + +void fonsLineBounds(FONScontext* stash, float y, float* miny, float* maxy) +{ + FONSfont* font; + FONSstate* state = fons__getState(stash); + short isize; + + if (stash == NULL) return; + if (state->font < 0 || state->font >= stash->nfonts) return; + font = stash->fonts[state->font]; + isize = (short)(state->size*10.0f); + if (font->data == NULL) return; + + y += fons__getVertAlign(stash, font, state->align, isize); + + if (stash->params.flags & FONS_ZERO_TOPLEFT) { + *miny = y - font->ascender * (float)isize/10.0f; + *maxy = *miny + font->lineh*isize/10.0f; + } else { + *maxy = y + font->descender * (float)isize/10.0f; + *miny = *maxy - font->lineh*isize/10.0f; + } +} + +const unsigned char* fonsGetTextureData(FONScontext* stash, int* width, int* height) +{ + if (width != NULL) + *width = stash->params.width; + if (height != NULL) + *height = stash->params.height; + return stash->texData; +} + +int fonsValidateTexture(FONScontext* stash, int* dirty) +{ + if (stash->dirtyRect[0] < stash->dirtyRect[2] && stash->dirtyRect[1] < stash->dirtyRect[3]) { + dirty[0] = stash->dirtyRect[0]; + dirty[1] = stash->dirtyRect[1]; + dirty[2] = stash->dirtyRect[2]; + dirty[3] = stash->dirtyRect[3]; + // Reset dirty rect + stash->dirtyRect[0] = stash->params.width; + stash->dirtyRect[1] = stash->params.height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + return 1; + } + return 0; +} + +void fonsDeleteInternal(FONScontext* stash) +{ + int i; + if (stash == NULL) return; + + if (stash->params.renderDelete) + stash->params.renderDelete(stash->params.userPtr); + + for (i = 0; i < stash->nfonts; ++i) + fons__freeFont(stash->fonts[i]); + + if (stash->atlas) fons__deleteAtlas(stash->atlas); + if (stash->fonts) free(stash->fonts); + if (stash->texData) free(stash->texData); + if (stash->scratch) free(stash->scratch); + free(stash); + fons__tt_done(stash); +} + +void fonsSetErrorCallback(FONScontext* stash, void (*callback)(void* uptr, int error, int val), void* uptr) +{ + if (stash == NULL) return; + stash->handleError = callback; + stash->errorUptr = uptr; +} + +void fonsGetAtlasSize(FONScontext* stash, int* width, int* height) +{ + if (stash == NULL) return; + *width = stash->params.width; + *height = stash->params.height; +} + +int fonsExpandAtlas(FONScontext* stash, int width, int height) +{ + int i, maxy = 0; + unsigned char* data = NULL; + if (stash == NULL) return 0; + + width = fons__maxi(width, stash->params.width); + height = fons__maxi(height, stash->params.height); + + if (width == stash->params.width && height == stash->params.height) + return 1; + + // Flush pending glyphs. + fons__flush(stash); + + // Create new texture + if (stash->params.renderResize != NULL) { + if (stash->params.renderResize(stash->params.userPtr, width, height) == 0) + return 0; + } + // Copy old texture data over. + data = (unsigned char*)malloc(width * height); + if (data == NULL) + return 0; + for (i = 0; i < stash->params.height; i++) { + unsigned char* dst = &data[i*width]; + unsigned char* src = &stash->texData[i*stash->params.width]; + memcpy(dst, src, stash->params.width); + if (width > stash->params.width) + memset(dst+stash->params.width, 0, width - stash->params.width); + } + if (height > stash->params.height) + memset(&data[stash->params.height * width], 0, (height - stash->params.height) * width); + + free(stash->texData); + stash->texData = data; + + // Increase atlas size + fons__atlasExpand(stash->atlas, width, height); + + // Add existing data as dirty. + for (i = 0; i < stash->atlas->nnodes; i++) + maxy = fons__maxi(maxy, stash->atlas->nodes[i].y); + stash->dirtyRect[0] = 0; + stash->dirtyRect[1] = 0; + stash->dirtyRect[2] = stash->params.width; + stash->dirtyRect[3] = maxy; + + stash->params.width = width; + stash->params.height = height; + stash->itw = 1.0f/stash->params.width; + stash->ith = 1.0f/stash->params.height; + + return 1; +} + +int fonsResetAtlas(FONScontext* stash, int width, int height) +{ + int i, j; + if (stash == NULL) return 0; + + // Flush pending glyphs. + fons__flush(stash); + + // Create new texture + if (stash->params.renderResize != NULL) { + if (stash->params.renderResize(stash->params.userPtr, width, height) == 0) + return 0; + } + + // Reset atlas + fons__atlasReset(stash->atlas, width, height); + + // Clear texture data. + stash->texData = (unsigned char*)realloc(stash->texData, width * height); + if (stash->texData == NULL) return 0; + memset(stash->texData, 0, width * height); + + // Reset dirty rect + stash->dirtyRect[0] = width; + stash->dirtyRect[1] = height; + stash->dirtyRect[2] = 0; + stash->dirtyRect[3] = 0; + + // Reset cached glyphs + for (i = 0; i < stash->nfonts; i++) { + FONSfont* font = stash->fonts[i]; + font->nglyphs = 0; + for (j = 0; j < FONS_HASH_LUT_SIZE; j++) + font->lut[j] = -1; + } + + stash->params.width = width; + stash->params.height = height; + stash->itw = 1.0f/stash->params.width; + stash->ith = 1.0f/stash->params.height; + + // Add white rect at 0,0 for debug drawing. + fons__addWhiteRect(stash, 2,2); + + return 1; +} + + +#endif diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CApplication.h b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CApplication.h new file mode 100644 index 00000000..b575e3bc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CApplication.h @@ -0,0 +1,38 @@ +/* +** Sample Framework for deko3d Applications +** CApplication.h: Wrapper class containing common application boilerplate +*/ +#pragma once +#include "common.h" + +class CApplication +{ +protected: + virtual void onFocusState(AppletFocusState) { } + virtual void onOperationMode(AppletOperationMode) { } + virtual bool onFrame(u64) { return true; } + +public: + CApplication(); + ~CApplication(); + + void run(); + + static constexpr void chooseFramebufferSize(uint32_t& width, uint32_t& height, AppletOperationMode mode); +}; + +constexpr void CApplication::chooseFramebufferSize(uint32_t& width, uint32_t& height, AppletOperationMode mode) +{ + switch (mode) + { + default: + case AppletOperationMode_Handheld: + width = 1280; + height = 720; + break; + case AppletOperationMode_Console: + width = 1920; + height = 1080; + break; + } +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CCmdMemRing.h b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CCmdMemRing.h new file mode 100644 index 00000000..3a8e1a00 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CCmdMemRing.h @@ -0,0 +1,57 @@ +/* +** Sample Framework for deko3d Applications +** CCmdMemRing.h: Memory provider class for dynamic command buffers +*/ +#pragma once +#include "common.h" +#include "CMemPool.h" + +template <unsigned NumSlices> +class CCmdMemRing +{ + static_assert(NumSlices > 0, "Need a non-zero number of slices..."); + CMemPool::Handle m_mem; + unsigned m_curSlice; + dk::Fence m_fences[NumSlices]; +public: + CCmdMemRing() : m_mem{}, m_curSlice{}, m_fences{} { } + ~CCmdMemRing() + { + m_mem.destroy(); + } + + bool allocate(CMemPool& pool, uint32_t sliceSize) + { + sliceSize = (sliceSize + DK_CMDMEM_ALIGNMENT - 1) &~ (DK_CMDMEM_ALIGNMENT - 1); + m_mem = pool.allocate(NumSlices*sliceSize); + return m_mem; + } + + void begin(dk::CmdBuf cmdbuf) + { + // Clear/reset the command buffer, which also destroys all command list handles + // (but remember: it does *not* in fact destroy the command data) + cmdbuf.clear(); + + // Wait for the current slice of memory to be available, and feed it to the command buffer + uint32_t sliceSize = m_mem.getSize() / NumSlices; + m_fences[m_curSlice].wait(); + + // Feed the memory to the command buffer + cmdbuf.addMemory(m_mem.getMemBlock(), m_mem.getOffset() + m_curSlice * sliceSize, sliceSize); + } + + DkCmdList end(dk::CmdBuf cmdbuf) + { + // Signal the fence corresponding to the current slice; so that in the future when we want + // to use it again, we can wait for the completion of the commands we've just submitted + // (and as such we don't overwrite in-flight command data with new one) + cmdbuf.signalFence(m_fences[m_curSlice]); + + // Advance the current slice counter; wrapping around when we reach the end + m_curSlice = (m_curSlice + 1) % NumSlices; + + // Finish off the command list, returning it to the caller + return cmdbuf.finishList(); + } +}; diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CDescriptorSet.h b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CDescriptorSet.h new file mode 100644 index 00000000..6e464791 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CDescriptorSet.h @@ -0,0 +1,71 @@ +/* +** Sample Framework for deko3d Applications +** CDescriptorSet.h: Image/Sampler descriptor set class +*/ +#pragma once +#include "common.h" +#include "CMemPool.h" + +template <unsigned NumDescriptors> +class CDescriptorSet +{ + static_assert(NumDescriptors > 0, "Need a non-zero number of descriptors..."); + static_assert(sizeof(DkImageDescriptor) == sizeof(DkSamplerDescriptor), "shouldn't happen"); + static_assert(DK_IMAGE_DESCRIPTOR_ALIGNMENT == DK_SAMPLER_DESCRIPTOR_ALIGNMENT, "shouldn't happen"); + static constexpr size_t DescriptorSize = sizeof(DkImageDescriptor); + static constexpr size_t DescriptorAlign = DK_IMAGE_DESCRIPTOR_ALIGNMENT; + + CMemPool::Handle m_mem; +public: + CDescriptorSet() : m_mem{} { } + ~CDescriptorSet() + { + m_mem.destroy(); + } + + bool allocate(CMemPool& pool) + { + m_mem = pool.allocate(NumDescriptors*DescriptorSize, DescriptorAlign); + return m_mem; + } + + void bindForImages(dk::CmdBuf cmdbuf) + { + cmdbuf.bindImageDescriptorSet(m_mem.getGpuAddr(), NumDescriptors); + } + + void bindForSamplers(dk::CmdBuf cmdbuf) + { + cmdbuf.bindSamplerDescriptorSet(m_mem.getGpuAddr(), NumDescriptors); + } + + template <typename T> + void update(dk::CmdBuf cmdbuf, uint32_t id, T const& descriptor) + { + static_assert(sizeof(T) == DescriptorSize); + cmdbuf.pushData(m_mem.getGpuAddr() + id*DescriptorSize, &descriptor, DescriptorSize); + } + + template <typename T, size_t N> + void update(dk::CmdBuf cmdbuf, uint32_t id, std::array<T, N> const& descriptors) + { + static_assert(sizeof(T) == DescriptorSize); + cmdbuf.pushData(m_mem.getGpuAddr() + id*DescriptorSize, descriptors.data(), descriptors.size()*DescriptorSize); + } + +#ifdef DK_HPP_SUPPORT_VECTOR + template <typename T, typename Allocator = std::allocator<T>> + void update(dk::CmdBuf cmdbuf, uint32_t id, std::vector<T,Allocator> const& descriptors) + { + static_assert(sizeof(T) == DescriptorSize); + cmdbuf.pushData(m_mem.getGpuAddr() + id*DescriptorSize, descriptors.data(), descriptors.size()*DescriptorSize); + } +#endif + + template <typename T> + void update(dk::CmdBuf cmdbuf, uint32_t id, std::initializer_list<T const> const& descriptors) + { + static_assert(sizeof(T) == DescriptorSize); + cmdbuf.pushData(m_mem.getGpuAddr() + id*DescriptorSize, descriptors.data(), descriptors.size()*DescriptorSize); + } +}; diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CExternalImage.h b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CExternalImage.h new file mode 100644 index 00000000..230e2e91 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CExternalImage.h @@ -0,0 +1,37 @@ +/* +** Sample Framework for deko3d Applications +** CExternalImage.h: Utility class for loading images from the filesystem +*/ +#pragma once +#include "common.h" +#include "CMemPool.h" + +class CExternalImage +{ + dk::Image m_image; + dk::ImageDescriptor m_descriptor; + CMemPool::Handle m_mem; +public: + CExternalImage() : m_image{}, m_descriptor{}, m_mem{} { } + ~CExternalImage() + { + m_mem.destroy(); + } + + constexpr operator bool() const + { + return m_mem; + } + + constexpr dk::Image& get() + { + return m_image; + } + + constexpr dk::ImageDescriptor const& getDescriptor() const + { + return m_descriptor; + } + + bool load(CMemPool& imagePool, CMemPool& scratchPool, dk::Device device, dk::Queue transferQueue, const char* path, uint32_t width, uint32_t height, DkImageFormat format, uint32_t flags = 0); +}; diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CIntrusiveList.h b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CIntrusiveList.h new file mode 100644 index 00000000..73eb5c8b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CIntrusiveList.h @@ -0,0 +1,119 @@ +/* +** Sample Framework for deko3d Applications +** CIntrusiveList.h: Intrusive doubly-linked list helper class +*/ +#pragma once +#include "common.h" + +template <typename T> +struct CIntrusiveListNode +{ + T *m_next, *m_prev; + + constexpr CIntrusiveListNode() : m_next{}, m_prev{} { } + constexpr operator bool() const { return m_next || m_prev; } +}; + +template <typename T, CIntrusiveListNode<T> T::* node_ptr> +class CIntrusiveList +{ + T *m_first, *m_last; + +public: + constexpr CIntrusiveList() : m_first{}, m_last{} { } + + constexpr T* first() const { return m_first; } + constexpr T* last() const { return m_last; } + constexpr bool empty() const { return !m_first; } + constexpr void clear() { m_first = m_last = nullptr; } + + constexpr bool isLinked(T* obj) const { return obj->*node_ptr || m_first == obj; } + constexpr T* prev(T* obj) const { return (obj->*node_ptr).m_prev; } + constexpr T* next(T* obj) const { return (obj->*node_ptr).m_next; } + + void add(T* obj) + { + return addBefore(nullptr, obj); + } + + void addBefore(T* pos, T* obj) + { + auto& node = obj->*node_ptr; + node.m_next = pos; + node.m_prev = pos ? (pos->*node_ptr).m_prev : m_last; + + if (pos) + (pos->*node_ptr).m_prev = obj; + else + m_last = obj; + + if (node.m_prev) + (node.m_prev->*node_ptr).m_next = obj; + else + m_first = obj; + } + + void addAfter(T* pos, T* obj) + { + auto& node = obj->*node_ptr; + node.m_next = pos ? (pos->*node_ptr).m_next : m_first; + node.m_prev = pos; + + if (pos) + (pos->*node_ptr).m_next = obj; + else + m_first = obj; + + if (node.m_next) + (node.m_next->*node_ptr).m_prev = obj; + else + m_last = obj; + } + + T* pop() + { + T* ret = m_first; + if (ret) + { + m_first = (ret->*node_ptr).m_next; + if (m_first) + (m_first->*node_ptr).m_prev = nullptr; + else + m_last = nullptr; + } + return ret; + } + + void remove(T* obj) + { + auto& node = obj->*node_ptr; + if (node.m_prev) + { + (node.m_prev->*node_ptr).m_next = node.m_next; + if (node.m_next) + (node.m_next->*node_ptr).m_prev = node.m_prev; + else + m_last = node.m_prev; + } else + { + m_first = node.m_next; + if (m_first) + (m_first->*node_ptr).m_prev = nullptr; + else + m_last = nullptr; + } + + node.m_next = node.m_prev = 0; + } + + template <typename L> + void iterate(L lambda) const + { + T* next = nullptr; + for (T* cur = m_first; cur; cur = next) + { + next = (cur->*node_ptr).m_next; + lambda(cur); + } + } +}; diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CIntrusiveTree.h b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CIntrusiveTree.h new file mode 100644 index 00000000..9796ee6b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CIntrusiveTree.h @@ -0,0 +1,250 @@ +/* +** Sample Framework for deko3d Applications +** CIntrusiveTree.h: Intrusive red-black tree helper class +*/ +#pragma once +#include "common.h" + +#include <functional> + +struct CIntrusiveTreeNode +{ + enum Color + { + Red, + Black, + }; + + enum Leaf + { + Left, + Right, + }; + +private: + uintptr_t m_parent_color; + CIntrusiveTreeNode* m_children[2]; + +public: + constexpr CIntrusiveTreeNode() : m_parent_color{}, m_children{} { } + + constexpr CIntrusiveTreeNode* getParent() const + { + return reinterpret_cast<CIntrusiveTreeNode*>(m_parent_color &~ 1); + } + + void setParent(CIntrusiveTreeNode* parent) + { + m_parent_color = (m_parent_color & 1) | reinterpret_cast<uintptr_t>(parent); + } + + constexpr Color getColor() const + { + return static_cast<Color>(m_parent_color & 1); + } + + void setColor(Color color) + { + m_parent_color = (m_parent_color &~ 1) | static_cast<uintptr_t>(color); + } + + constexpr CIntrusiveTreeNode*& child(Leaf leaf) + { + return m_children[leaf]; + } + + constexpr CIntrusiveTreeNode* const& child(Leaf leaf) const + { + return m_children[leaf]; + } + + //-------------------------------------- + + constexpr bool isRed() const { return getColor() == Red; } + constexpr bool isBlack() const { return getColor() == Black; } + void setRed() { setColor(Red); } + void setBlack() { setColor(Black); } + + constexpr CIntrusiveTreeNode*& left() { return child(Left); } + constexpr CIntrusiveTreeNode*& right() { return child(Right); } + constexpr CIntrusiveTreeNode* const& left() const { return child(Left); } + constexpr CIntrusiveTreeNode* const& right() const { return child(Right); } +}; + +NX_CONSTEXPR CIntrusiveTreeNode::Leaf operator!(CIntrusiveTreeNode::Leaf val) noexcept +{ + return static_cast<CIntrusiveTreeNode::Leaf>(!static_cast<unsigned>(val)); +} + +class CIntrusiveTreeBase +{ + using N = CIntrusiveTreeNode; + + void rotate(N* node, N::Leaf leaf); + void recolor(N* parent, N* node); +protected: + N* m_root; + + constexpr CIntrusiveTreeBase() : m_root{} { } + + N* walk(N* node, N::Leaf leaf) const; + void insert(N* node, N* parent); + void remove(N* node); + + N* minmax(N::Leaf leaf) const + { + N* p = m_root; + if (!p) + return nullptr; + while (p->child(leaf)) + p = p->child(leaf); + return p; + } + + template <typename H> + N*& navigate(N*& node, N*& parent, N::Leaf leafOnEqual, H helm) const + { + node = nullptr; + parent = nullptr; + + N** point = const_cast<N**>(&m_root); + while (*point) + { + int direction = helm(*point); + parent = *point; + if (direction < 0) + point = &(*point)->left(); + else if (direction > 0) + point = &(*point)->right(); + else + { + node = *point; + point = &(*point)->child(leafOnEqual); + } + } + return *point; + } +}; + +template <typename ClassT, typename MemberT> +constexpr ClassT* parent_obj(MemberT* member, MemberT ClassT::* ptr) +{ + union whatever + { + MemberT ClassT::* ptr; + intptr_t offset; + }; + // This is technically UB, but basically every compiler worth using admits it as an extension + return (ClassT*)((intptr_t)member - whatever{ptr}.offset); +} + +template < + typename T, + CIntrusiveTreeNode T::* node_ptr, + typename Comparator = std::less<> +> +class CIntrusiveTree final : protected CIntrusiveTreeBase +{ + using N = CIntrusiveTreeNode; + + static constexpr T* toType(N* m) + { + return m ? parent_obj(m, node_ptr) : nullptr; + } + + static constexpr N* toNode(T* m) + { + return m ? &(m->*node_ptr) : nullptr; + } + + template <typename A, typename B> + static int compare(A const& a, B const& b) + { + Comparator comp; + if (comp(a, b)) + return -1; + if (comp(b, a)) + return 1; + return 0; + } + +public: + constexpr CIntrusiveTree() : CIntrusiveTreeBase{} { } + + T* first() const { return toType(minmax(N::Left)); } + T* last() const { return toType(minmax(N::Right)); } + bool empty() const { return m_root != nullptr; } + void clear() { m_root = nullptr; } + + T* prev(T* node) const { return toType(walk(toNode(node), N::Left)); } + T* next(T* node) const { return toType(walk(toNode(node), N::Right)); } + + enum SearchMode + { + Exact = 0, + LowerBound = 1, + UpperBound = 2, + }; + + template <typename Lambda> + T* search(SearchMode mode, Lambda lambda) const + { + N *node, *parent; + N*& point = navigate(node, parent, + mode != UpperBound ? N::Left : N::Right, + [&lambda](N* curnode) { return lambda(toType(curnode)); }); + + switch (mode) + { + default: + case Exact: + break; + case LowerBound: + if (!node && parent) + { + if (&parent->left() == &point) + node = parent; + else + node = walk(parent, N::Right); + } + break; + case UpperBound: + if (node) + node = walk(node, N::Right); + else if (parent) + { + if (&parent->right() == &point) + node = walk(parent, N::Right); + else + node = parent; + } + break; + } + return toType(node); + } + + template <typename K> + T* find(K const& key, SearchMode mode = Exact) const + { + return search(mode, [&key](T* obj) { return compare(key, *obj); }); + } + + T* insert(T* obj, bool allow_dupes = false) + { + N *node, *parent; + N*& point = navigate(node, parent, N::Right, + [obj](N* curnode) { return compare(*obj, *toType(curnode)); }); + + if (node && !allow_dupes) + return toType(node); + + point = toNode(obj); + CIntrusiveTreeBase::insert(point, parent); + return obj; + } + + void remove(T* obj) + { + CIntrusiveTreeBase::remove(toNode(obj)); + } +}; diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CMemPool.h b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CMemPool.h new file mode 100644 index 00000000..978755cd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CMemPool.h @@ -0,0 +1,120 @@ +/* +** Sample Framework for deko3d Applications +** CMemPool.h: Pooled dynamic memory allocation manager class +*/ +#pragma once +#include "common.h" +#include "CIntrusiveList.h" +#include "CIntrusiveTree.h" + +class CMemPool +{ + dk::Device m_dev; + uint32_t m_flags; + uint32_t m_blockSize; + + struct Block + { + CIntrusiveListNode<Block> m_node; + dk::MemBlock m_obj; + void* m_cpuAddr; + DkGpuAddr m_gpuAddr; + + constexpr void* cpuOffset(uint32_t offset) const + { + return m_cpuAddr ? ((u8*)m_cpuAddr + offset) : nullptr; + } + + constexpr DkGpuAddr gpuOffset(uint32_t offset) const + { + return m_gpuAddr != DK_GPU_ADDR_INVALID ? (m_gpuAddr + offset) : DK_GPU_ADDR_INVALID; + } + }; + + CIntrusiveList<Block, &Block::m_node> m_blocks; + + struct Slice + { + CIntrusiveListNode<Slice> m_node; + CIntrusiveTreeNode m_treenode; + CMemPool* m_pool; + Block* m_block; + uint32_t m_start; + uint32_t m_end; + + constexpr uint32_t getSize() const { return m_end - m_start; } + constexpr bool canCoalesce(Slice const& rhs) const { return m_pool == rhs.m_pool && m_block == rhs.m_block && m_end == rhs.m_start; } + + constexpr bool operator<(Slice const& rhs) const { return getSize() < rhs.getSize(); } + constexpr bool operator<(uint32_t rhs) const { return getSize() < rhs; } + }; + + friend constexpr bool operator<(uint32_t lhs, Slice const& rhs); + + CIntrusiveList<Slice, &Slice::m_node> m_memMap, m_sliceHeap; + CIntrusiveTree<Slice, &Slice::m_treenode> m_freeList; + + Slice* _newSlice(); + void _deleteSlice(Slice*); + + void _destroy(Slice* slice); + +public: + static constexpr uint32_t DefaultBlockSize = 0x800000; + class Handle + { + Slice* m_slice; + public: + constexpr Handle(Slice* slice = nullptr) : m_slice{slice} { } + constexpr operator bool() const { return m_slice != nullptr; } + constexpr operator Slice*() const { return m_slice; } + constexpr bool operator!() const { return !m_slice; } + constexpr bool operator==(Handle const& rhs) const { return m_slice == rhs.m_slice; } + constexpr bool operator!=(Handle const& rhs) const { return m_slice != rhs.m_slice; } + + void destroy() + { + if (m_slice) + { + m_slice->m_pool->_destroy(m_slice); + m_slice = nullptr; + } + } + + constexpr dk::MemBlock getMemBlock() const + { + return m_slice->m_block->m_obj; + } + + constexpr uint32_t getOffset() const + { + return m_slice->m_start; + } + + constexpr uint32_t getSize() const + { + return m_slice->getSize(); + } + + constexpr void* getCpuAddr() const + { + return m_slice->m_block->cpuOffset(m_slice->m_start); + } + + constexpr DkGpuAddr getGpuAddr() const + { + return m_slice->m_block->gpuOffset(m_slice->m_start); + } + }; + + CMemPool(dk::Device dev, uint32_t flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached, uint32_t blockSize = DefaultBlockSize) : + m_dev{dev}, m_flags{flags}, m_blockSize{blockSize}, m_blocks{}, m_memMap{}, m_sliceHeap{}, m_freeList{} { } + ~CMemPool(); + + Handle allocate(uint32_t size, uint32_t alignment = DK_CMDMEM_ALIGNMENT); +}; + +constexpr bool operator<(uint32_t lhs, CMemPool::Slice const& rhs) +{ + return lhs < rhs.getSize(); +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CShader.h b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CShader.h new file mode 100644 index 00000000..b39dfe01 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/CShader.h @@ -0,0 +1,31 @@ +/* +** Sample Framework for deko3d Applications +** CShader.h: Utility class for loading shaders from the filesystem +*/ +#pragma once +#include "common.h" +#include "CMemPool.h" + +class CShader +{ + dk::Shader m_shader; + CMemPool::Handle m_codemem; +public: + CShader() : m_shader{}, m_codemem{} { } + ~CShader() + { + m_codemem.destroy(); + } + + constexpr operator bool() const + { + return m_codemem; + } + + constexpr operator dk::Shader const*() const + { + return &m_shader; + } + + bool load(CMemPool& pool, const char* path); +}; diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/FileLoader.h b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/FileLoader.h new file mode 100644 index 00000000..3455c87d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/FileLoader.h @@ -0,0 +1,9 @@ +/* +** Sample Framework for deko3d Applications +** FileLoader.h: Helpers for loading data from the filesystem directly into GPU memory +*/ +#pragma once +#include "common.h" +#include "CMemPool.h" + +CMemPool::Handle LoadFile(CMemPool& pool, const char* path, uint32_t alignment = DK_CMDMEM_ALIGNMENT); diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/common.h b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/common.h new file mode 100644 index 00000000..814e4992 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/framework/common.h @@ -0,0 +1,12 @@ +/* +** Sample Framework for deko3d Applications +** common.h: Common includes +*/ +#pragma once +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <switch.h> + +#include <deko3d.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/nanovg_gl_utils.h b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/nanovg_gl_utils.h new file mode 100644 index 00000000..f7d73ee5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/nanovg_gl_utils.h @@ -0,0 +1,158 @@ +// +// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// +#ifndef NANOVG_GL_UTILS_H +#define NANOVG_GL_UTILS_H + +#ifdef USE_OPENGL + +struct NVGLUframebuffer { + NVGcontext* ctx; + GLuint fbo; + GLuint rbo; + GLuint texture; + int image; +}; +typedef struct NVGLUframebuffer NVGLUframebuffer; + +// Helper function to create GL frame buffer to render to. +void nvgluBindFramebuffer(NVGLUframebuffer* fb); +NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imageFlags); +void nvgluDeleteFramebuffer(NVGLUframebuffer* fb); + +#endif // NANOVG_GL_UTILS_H + +#ifdef NANOVG_GL_IMPLEMENTATION + +#if defined(NANOVG_GL3) || defined(NANOVG_GLES2) || defined(NANOVG_GLES3) +// FBO is core in OpenGL 3>. +# define NANOVG_FBO_VALID 1 +#elif defined(NANOVG_GL2) +// On OS X including glext defines FBO on GL2 too. +# ifdef __APPLE__ +# include <OpenGL/glext.h> +# define NANOVG_FBO_VALID 1 +# endif +#endif + +static GLint defaultFBO = -1; + +NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imageFlags) +{ +#ifdef NANOVG_FBO_VALID + GLint defaultFBO; + GLint defaultRBO; + NVGLUframebuffer* fb = NULL; + + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO); + glGetIntegerv(GL_RENDERBUFFER_BINDING, &defaultRBO); + + fb = (NVGLUframebuffer*)malloc(sizeof(NVGLUframebuffer)); + if (fb == NULL) goto error; + memset(fb, 0, sizeof(NVGLUframebuffer)); + + fb->image = nvgCreateImageRGBA(ctx, w, h, imageFlags | NVG_IMAGE_FLIPY | NVG_IMAGE_PREMULTIPLIED, NULL); + +#if defined NANOVG_GL2 + fb->texture = nvglImageHandleGL2(ctx, fb->image); +#elif defined NANOVG_GL3 + fb->texture = nvglImageHandleGL3(ctx, fb->image); +#elif defined NANOVG_GLES2 + fb->texture = nvglImageHandleGLES2(ctx, fb->image); +#elif defined NANOVG_GLES3 + fb->texture = nvglImageHandleGLES3(ctx, fb->image); +#endif + + fb->ctx = ctx; + + // frame buffer object + glGenFramebuffers(1, &fb->fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fb->fbo); + + // render buffer object + glGenRenderbuffers(1, &fb->rbo); + glBindRenderbuffer(GL_RENDERBUFFER, fb->rbo); + glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, w, h); + + // combine all + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->texture, 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->rbo); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { +#ifdef GL_DEPTH24_STENCIL8 + // If GL_STENCIL_INDEX8 is not supported, try GL_DEPTH24_STENCIL8 as a fallback. + // Some graphics cards require a depth buffer along with a stencil. + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, w, h); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->texture, 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->rbo); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) +#endif // GL_DEPTH24_STENCIL8 + goto error; + } + + glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO); + glBindRenderbuffer(GL_RENDERBUFFER, defaultRBO); + return fb; +error: + glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO); + glBindRenderbuffer(GL_RENDERBUFFER, defaultRBO); + nvgluDeleteFramebuffer(fb); + return NULL; +#else + NVG_NOTUSED(ctx); + NVG_NOTUSED(w); + NVG_NOTUSED(h); + NVG_NOTUSED(imageFlags); + return NULL; +#endif +} + +void nvgluBindFramebuffer(NVGLUframebuffer* fb) +{ +#ifdef NANOVG_FBO_VALID + if (defaultFBO == -1) glGetIntegerv(GL_FRAMEBUFFER_BINDING, &defaultFBO); + glBindFramebuffer(GL_FRAMEBUFFER, fb != NULL ? fb->fbo : defaultFBO); +#else + NVG_NOTUSED(fb); +#endif +} + +void nvgluDeleteFramebuffer(NVGLUframebuffer* fb) +{ +#ifdef NANOVG_FBO_VALID + if (fb == NULL) return; + if (fb->fbo != 0) + glDeleteFramebuffers(1, &fb->fbo); + if (fb->rbo != 0) + glDeleteRenderbuffers(1, &fb->rbo); + if (fb->image >= 0) + nvgDeleteImage(fb->ctx, fb->image); + fb->ctx = NULL; + fb->fbo = 0; + fb->rbo = 0; + fb->texture = 0; + fb->image = -1; + free(fb); +#else + NVG_NOTUSED(fb); +#endif +} + +#endif + +#endif // NANOVG_GL_IMPLEMENTATION diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/stb_image.h b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/stb_image.h new file mode 100644 index 00000000..e06f7a1d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/stb_image.h @@ -0,0 +1,6614 @@ +/* stb_image - v2.10 - public domain image loader - http://nothings.org/stb_image.h + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8-bit-per-channel (16 bpc not supported) + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + + Revision 2.00 release notes: + + - Progressive JPEG is now supported. + + - PPM and PGM binary formats are now supported, thanks to Ken Miller. + + - x86 platforms now make use of SSE2 SIMD instructions for + JPEG decoding, and ARM platforms can use NEON SIMD if requested. + This work was done by Fabian "ryg" Giesen. SSE2 is used by + default, but NEON must be enabled explicitly; see docs. + + With other JPEG optimizations included in this version, we see + 2x speedup on a JPEG on an x86 machine, and a 1.5x speedup + on a JPEG on an ARM machine, relative to previous versions of this + library. The same results will not obtain for all JPGs and for all + x86/ARM machines. (Note that progressive JPEGs are significantly + slower to decode than regular JPEGs.) This doesn't mean that this + is the fastest JPEG decoder in the land; rather, it brings it + closer to parity with standard libraries. If you want the fastest + decode, look elsewhere. (See "Philosophy" section of docs below.) + + See final bullet items below for more info on SIMD. + + - Added STBI_MALLOC, STBI_REALLOC, and STBI_FREE macros for replacing + the memory allocator. Unlike other STBI libraries, these macros don't + support a context parameter, so if you need to pass a context in to + the allocator, you'll have to store it in a global or a thread-local + variable. + + - Split existing STBI_NO_HDR flag into two flags, STBI_NO_HDR and + STBI_NO_LINEAR. + STBI_NO_HDR: suppress implementation of .hdr reader format + STBI_NO_LINEAR: suppress high-dynamic-range light-linear float API + + - You can suppress implementation of any of the decoders to reduce + your code footprint by #defining one or more of the following + symbols before creating the implementation. + + STBI_NO_JPEG + STBI_NO_PNG + STBI_NO_BMP + STBI_NO_PSD + STBI_NO_TGA + STBI_NO_GIF + STBI_NO_HDR + STBI_NO_PIC + STBI_NO_PNM (.ppm and .pgm) + + - You can request *only* certain decoders and suppress all other ones + (this will be more forward-compatible, as addition of new decoders + doesn't require you to disable them explicitly): + + STBI_ONLY_JPEG + STBI_ONLY_PNG + STBI_ONLY_BMP + STBI_ONLY_PSD + STBI_ONLY_TGA + STBI_ONLY_GIF + STBI_ONLY_HDR + STBI_ONLY_PIC + STBI_ONLY_PNM (.ppm and .pgm) + + Note that you can define multiples of these, and you will get all + of them ("only x" and "only y" is interpreted to mean "only x&y"). + + - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still + want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB + + - Compilation of all SIMD code can be suppressed with + #define STBI_NO_SIMD + It should not be necessary to disable SIMD unless you have issues + compiling (e.g. using an x86 compiler which doesn't support SSE + intrinsics or that doesn't support the method used to detect + SSE2 support at run-time), and even those can be reported as + bugs so I can refine the built-in compile-time checking to be + smarter. + + - The old STBI_SIMD system which allowed installing a user-defined + IDCT etc. has been removed. If you need this, don't upgrade. My + assumption is that almost nobody was doing this, and those who + were will find the built-in SIMD more satisfactory anyway. + + - RGB values computed for JPEG images are slightly different from + previous versions of stb_image. (This is due to using less + integer precision in SIMD.) The C code has been adjusted so + that the same RGB values will be computed regardless of whether + SIMD support is available, so your app should always produce + consistent results. But these results are slightly different from + previous versions. (Specifically, about 3% of available YCbCr values + will compute different RGB results from pre-1.49 versions by +-1; + most of the deviating values are one smaller in the G channel.) + + - If you must produce consistent results with previous versions of + stb_image, #define STBI_JPEG_OLD and you will get the same results + you used to; however, you will not get the SIMD speedups for + the YCbCr-to-RGB conversion step (although you should still see + significant JPEG speedup from the other changes). + + Please note that STBI_JPEG_OLD is a temporary feature; it will be + removed in future versions of the library. It is only intended for + near-term back-compatibility use. + + + Latest revision history: + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) partial animated GIF support + limited 16-bit PSD support + minor bugs, code cleanup, and compiler warnings + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) additional corruption checking + stbi_set_flip_vertically_on_load + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPEG, including x86 SSE2 & ARM NEON SIMD + progressive JPEG + PGM/PPM support + STBI_MALLOC,STBI_REALLOC,STBI_FREE + STBI_NO_*, STBI_ONLY_* + GIF bugfix + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support (both grayscale and paletted) + optimize PNG + fix bug in interlaced PNG with user-specified channel count + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + urraka@github (animated gif) Junggon Kim (PNM comments) + Daniel Gibson (16-bit TGA) + + Optimizations & bugfixes + Fabian "ryg" Giesen + Arseny Kapoulkine + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Martin Golini Jerry Jansson Joseph Thomson + Dave Moore Roy Eltham Hayaki Saito Phil Jordan + Won Chun Luke Graham Johan Duparc Nathan Reed + the Horde3D community Thomas Ruf Ronny Chevalier Nick Verigakis + Janez Zemva John Bartholomew Michal Cichon svdijk@github + Jonathan Blow Ken Hamada Tero Hanninen Baldur Karlsson + Laurent Gomila Cort Stratton Sergio Gonzalez romigrou@github + Aruelien Pocheville Thibault Reuille Cass Everitt + Ryamond Barbiero Paul Du Bois Engin Manap + Blazej Dariusz Roszkowski + Michaelangel007@github + + +LICENSE + +This software is in the public domain. Where that dedication is not +recognized, you are granted a perpetual, irrevocable license to copy, +distribute, and modify this file as you see fit. + +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 16-bit-per-channel PNG +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - no 1-bit BMP +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *comp -- outputs # of image components in image file +// int req_comp -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'req_comp' if req_comp is non-zero, or *comp otherwise. +// If req_comp is non-zero, *comp has the number of components that _would_ +// have been output otherwise. E.g. if you set req_comp to 4, you will always +// get RGBA output, but you can check *comp to see if it's trivially opaque +// because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *comp will be unchanged. The function stbi_failure_reason() +// can be queried for an extremely brief, end-user unfriendly explanation +// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid +// compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy to use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries do not emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// make more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// The output of the JPEG decoder is slightly different from versions where +// SIMD support was introduced (that is, for versions before 1.49). The +// difference is only +-1 in the 8-bit RGB channels, and only on a small +// fraction of pixels. You can force the pre-1.49 behavior by defining +// STBI_JPEG_OLD, but this will disable some of the SIMD decoding path +// and hence cost some performance. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image now supports loading HDR images in general, and currently +// the Radiance .HDR file format, although the support is provided +// generically. You can still load any file through the existing interface; +// if you attempt to load an HDR file, it will be automatically remapped to +// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// By default we convert iphone-formatted PNGs back to RGB, even though +// they are internally encoded differently. You can disable this conversion +// by by calling stbi_convert_iphone_png_to_rgb(0), in which case +// you will always just get the native iphone "format" through (which +// is BGR stored in RGB). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// + + +#ifndef STBI_NO_STDIO +#include <stdio.h> +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for req_comp + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +typedef unsigned char stbi_uc; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp); +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *comp, int req_comp); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *comp, int req_comp); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp); + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// NOT THREADSAFE +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); + +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include <stdarg.h> +#include <stddef.h> // ptrdiff_t on osx +#include <stdlib.h> +#include <string.h> + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include <math.h> // ldexp +#endif + +#ifndef STBI_NO_STDIO +#include <stdio.h> +#endif + +#ifndef STBI_ASSERT +#include <assert.h> +#define STBI_ASSERT(x) assert(x) +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include <stdint.h> +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// NOTE: not clear do we actually need this for the 64-bit path? +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// (but compiling with -msse2 allows the compiler to use SSE2 everywhere; +// this is just broken and gcc are jerks for not fixing it properly +// http://www.virtualdub.org/blog/pivot/entry.php?id=363 ) +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && defined(STBI__X86_TARGET) +#define STBI_SSE2 +#include <emmintrin.h> + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include <intrin.h> // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +static int stbi__sse2_available() +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +static int stbi__sse2_available() +{ +#if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 // GCC 4.8 or later + // GCC 4.8+ has a nice way to do this + return __builtin_cpu_supports("sse2"); +#else + // portable way to do this, preferably without using GCC inline ASM? + // just bail for now. + return 0; +#endif +} +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include <arm_neon.h> +// assume GCC or Clang on ARM targets +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + fseek((FILE*) user, n, SEEK_CUR); +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static stbi_uc *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static stbi_uc *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static stbi_uc *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +// this is not threadsafe +static const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load = flag_true_if_should_flip; +} + +static unsigned char *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static unsigned char *stbi__load_flip(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result = stbi__load_main(s, x, y, comp, req_comp); + + if (stbi__vertically_flip_on_load && result != NULL) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + stbi_uc temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } + + return result; +} + +#ifndef STBI_NO_HDR +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + float temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } +} +#endif + +#ifndef STBI_NO_STDIO + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_flip(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} +#endif //!STBI_NO_STDIO + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_flip(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_flip(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_flip(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_file(&s,f); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} + +static void stbi__skip(stbi__context *s, int n) +{ + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} + +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} + +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} + +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + return z + (stbi__get16le(s) << 16); +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + + +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc(req_comp * x * y); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define COMBO(a,b) ((a)*8+(b)) + #define CASE(a,b) case COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (COMBO(img_n, req_comp)) { + CASE(1,2) dest[0]=src[0], dest[1]=255; break; + CASE(1,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(1,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; break; + CASE(2,1) dest[0]=src[0]; break; + CASE(2,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(2,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; break; + CASE(3,4) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; break; + CASE(3,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break; + CASE(3,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; break; + CASE(4,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break; + CASE(4,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; break; + CASE(4,3) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; break; + default: STBI_ASSERT(0); + } + #undef CASE + } + + STBI_FREE(data); + return good; +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output = (float *) stbi__malloc(x * y * comp * sizeof(float)); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output = (stbi_uc *) stbi__malloc(x * y * comp); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi_uc dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0,code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1 << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (-1 << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k << 8) + (run << 4) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<<n) + 1 +static int const stbi__jbias[16] = {0,-1,-3,-7,-15,-31,-63,-127,-255,-511,-1023,-2047,-4095,-8191,-16383,-32767}; + +// combined JPEG 'receive' and JPEG 'extend', since baseline +// always extends everything it receives. +stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n) +{ + unsigned int k; + int sgn; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + + sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB + k = stbi_lrot(j->code_buffer, n); + STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & ~sgn); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi_uc *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc << j->succ_low); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) << shift); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) << shift); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) << 12) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0] << 2; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi_uc *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4; + int t = q & 15,i; + if (p != 0) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = stbi__get8(z->s); + L -= 65; + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + stbi__skip(z->s, stbi__get16be(z->s)-2); + return 1; + } + return 0; +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + c = stbi__get8(s); + if (c != 3 && c != 1) return stbi__err("bad component count","Corrupt JPEG"); // JFIF requires + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + for (i=0; i < s->img_n; ++i) { + z->img_comp[i].id = stbi__get8(s); + if (z->img_comp[i].id != i+1) // JFIF requires + if (z->img_comp[i].id != i) // some version of jpegtran outputs non-JFIF-compliant files! + return stbi__err("bad component ID","Corrupt JPEG"); + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].raw_data = stbi__malloc(z->img_comp[i].w2 * z->img_comp[i].h2+15); + + if (z->img_comp[i].raw_data == NULL) { + for(--i; i >= 0; --i) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + } + return stbi__err("outofmem", "Out of memory"); + } + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + z->img_comp[i].linebuf = NULL; + if (z->progressive) { + z->img_comp[i].coeff_w = (z->img_comp[i].w2 + 7) >> 3; + z->img_comp[i].coeff_h = (z->img_comp[i].h2 + 7) >> 3; + z->img_comp[i].raw_coeff = STBI_MALLOC(z->img_comp[i].coeff_w * z->img_comp[i].coeff_h * 64 * sizeof(short) + 15); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } else { + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } else if (x != 0) { + return stbi__err("junk before marker", "Corrupt JPEG"); + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else { + if (!stbi__process_marker(j, m)) return 0; + } + m = stbi__get_marker(j); + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +#ifdef STBI_JPEG_OLD +// this is the same YCbCr-to-RGB calculation that stb_image has used +// historically before the algorithm changes in 1.49 +#define float2fixed(x) ((int) ((x) * 65536 + 0.5)) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 16) + 32768; // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr*float2fixed(1.40200f); + g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f); + b = y_fixed + cb*float2fixed(1.77200f); + r >>= 16; + g >>= 16; + b >>= 16; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#else +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* float2fixed(1.40200f); + g = y_fixed + (cr*-float2fixed(0.71414f)) + ((cb*-float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* float2fixed(1.40200f); + g = y_fixed + cr*-float2fixed(0.71414f) + ((cb*-float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + #ifndef STBI_JPEG_OLD + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + #endif + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + #ifndef STBI_JPEG_OLD + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + #endif + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + int i; + for (i=0; i < j->s->img_n; ++i) { + if (j->img_comp[i].raw_data) { + STBI_FREE(j->img_comp[i].raw_data); + j->img_comp[i].raw_data = NULL; + j->img_comp[i].data = NULL; + } + if (j->img_comp[i].raw_coeff) { + STBI_FREE(j->img_comp[i].raw_coeff); + j->img_comp[i].raw_coeff = 0; + j->img_comp[i].coeff = 0; + } + if (j->img_comp[i].linebuf) { + STBI_FREE(j->img_comp[i].linebuf); + j->img_comp[i].linebuf = NULL; + } + } +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n; + + if (z->s->img_n == 3 && n < 3) + decode_n = 1; + else + decode_n = z->s->img_n; + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4]; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc(n * z->s->img_x * z->s->img_y + 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n; // report original components, not output + return output; + } +} + +static unsigned char *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__jpeg j; + j.s = s; + stbi__setup_jpeg(&j); + return load_jpeg_image(&j, x,y,comp,req_comp); +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg j; + j.s = s; + stbi__setup_jpeg(&j); + r = stbi__decode_jpeg_header(&j, STBI__SCAN_type); + stbi__rewind(s); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__jpeg j; + j.s = s; + return stbi__jpeg_info_raw(&j, x, y, comp); +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[288]; + stbi__uint16 value[288]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + STBI_ASSERT(z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) stbi__fill_bits(a); + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = old_limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < hlit + hdist) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else if (c == 16) { + c = stbi__zreceive(a,2)+3; + memset(lencodes+n, lencodes[n-1], c); + n += c; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + memset(lencodes+n, 0, c); + n += c; + } else { + STBI_ASSERT(c == 18); + c = stbi__zreceive(a,7)+11; + memset(lencodes+n, 0, c); + n += c; + } + } + if (n != hlit+hdist) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncomperssed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + STBI_ASSERT(a->num_bits == 0); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +// @TODO: should statically initialize these for optimal thread safety +static stbi_uc stbi__zdefault_length[288], stbi__zdefault_distance[32]; +static void stbi__init_zdefaults(void) +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncomperssed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zdefault_distance[31]) stbi__init_zdefaults(); + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc(x * y * out_n); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + if (s->img_x == x && s->img_y == y) { + if (raw_len != img_len) return stbi__err("not enough pixels","Corrupt PNG"); + } else { // interlaced: + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + } + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior = cur - stride; + int filter = *raw++; + int filter_bytes = img_n; + int width = x; + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + STBI_ASSERT(img_width_bytes <= x); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*img_n; + #define CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); break; + CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); break; + CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); break; + CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); break; + CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); break; + } + #undef CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[img_n]=255,raw+=img_n,cur+=out_n,prior+=out_n) \ + for (k=0; k < img_n; ++k) + switch (filter) { + CASE(STBI__F_none) cur[k] = raw[k]; break; + CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k-out_n]); break; + CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-out_n])>>1)); break; + CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-out_n],prior[k],prior[k-out_n])); break; + CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-out_n] >> 1)); break; + CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-out_n],0,0)); break; + } + #undef CASE + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc(a->s->img_x * a->s->img_y * out_n); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_n + out_x*out_n, + a->out + (j*x+i)*out_n, out_n); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc(pixel_count * pal_img_n); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load = 0; +static int stbi__de_iphone_flag = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag = flag_true_if_should_convert; +} + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + p[0] = p[2] * 255 / a; + p[1] = p[1] * 255 / a; + p[2] = t * 255 / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, depth=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + depth = stbi__get8(s); if (depth != 1 && depth != 2 && depth != 4 && depth != 8) return stbi__err("1/2/4/8-bit only","PNG not supported: 1/2/4/8-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + for (k=0; k < s->img_n; ++k) + tc[k] = (stbi_uc) (stbi__get16be(s) & 255) * stbi__depth_scale_table[depth]; // non 8-bit images will be larger + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, depth, color, interlace)) return 0; + if (has_trans) + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } + STBI_FREE(z->expanded); z->expanded = NULL; + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static unsigned char *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp) +{ + unsigned char *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + result = stbi__convert_format(result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_out_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static unsigned char *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) n += 16, z >>= 16; + if (z >= 0x00100) n += 8, z >>= 8; + if (z >= 0x00010) n += 4, z >>= 4; + if (z >= 0x00004) n += 2, z >>= 2; + if (z >= 0x00002) n += 1, z >>= 1; + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +static int stbi__shiftsigned(int v, int shift, int bits) +{ + int result; + int z=0; + + if (shift < 0) v <<= -shift; + else v >>= shift; + result = v; + + z = bits; + while (z < 8) { + result += v >> z; + z += bits; + } + return result; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; +} stbi__bmp_data; + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (info->bpp == 1) return stbi__errpuc("monochrome", "BMP type not supported: 1-bit"); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + info->mr = info->mg = info->mb = 0; + if (compress == 0) { + if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - 14 - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - 14 - info.hsz) >> 2; + } + + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + out = (stbi_uc *) stbi__malloc(target * s->img_x * s->img_y); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - 14 - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i], p1[i] = p2[i], p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if(is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // else: fall-through + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fall-through + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (r * 255)/31; + out[1] = (g * 255)/31; + out[2] = (b * 255)/31; + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4]; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + tga_data = (unsigned char*)stbi__malloc( (size_t)tga_width * tga_height * tga_comp ); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc( tga_palette_len * tga_comp ); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + int pixelCount; + int channelCount, compression; + int channel, i, count, len; + int bitdepth; + int w,h; + stbi_uc *out; + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Create the destination image. + out = (stbi_uc *) stbi__malloc(4 * w*h); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + count = 0; + while (count < pixelCount) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len ^= 0x0FF; + len += 2; + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out + channel; + if (channel >= channelCount) { + // Fill this channel with default data. + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } else { + // Read the data. + if (bitdepth == 16) { + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + + if (req_comp && req_comp != 4) { + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; y<height; ++y) { + int packet_idx; + + for(packet_idx=0; packet_idx < num_packets; ++packet_idx) { + stbi__pic_packet *packet = &packets[packet_idx]; + stbi_uc *dest = result+y*width*4; + + switch (packet->type) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;x<width;++x, dest+=4) + if (!stbi__readval(s,packet->channel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; i<count; ++i,dest+=4) + stbi__copyval(packet->channel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;i<count;++i, dest += 4) + stbi__copyval(packet->channel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;i<count;++i, dest+=4) + if (!stbi__readval(s,packet->channel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static stbi_uc *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp) +{ + stbi_uc *result; + int i, x,y; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if ((1 << 28) / x < y) return stbi__errpuc("too large", "Image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc(x*y*4); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out, *old_out; // output buffer (always 4 components) + int flags, bgindex, ratio, transparent, eflags, delay; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[4096]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif g; + if (!stbi__gif_header(s, &g, comp, 1)) { + stbi__rewind( s ); + return 0; + } + if (x) *x = g.w; + if (y) *y = g.h; + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + p = &g->out[g->cur_x + g->cur_y]; + c = &g->color_table[g->codes[code].suffix * 4]; + + if (c[3] >= 128) { + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) return stbi__errpuc("no clear code", "Corrupt GIF"); + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 4096) return stbi__errpuc("too many codes", "Corrupt GIF"); + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +static void stbi__fill_gif_background(stbi__gif *g, int x0, int y0, int x1, int y1) +{ + int x, y; + stbi_uc *c = g->pal[g->bgindex]; + for (y = y0; y < y1; y += 4 * g->w) { + for (x = x0; x < x1; x += 4) { + stbi_uc *p = &g->out[y + x]; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = 0; + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp) +{ + int i; + stbi_uc *prev_out = 0; + + if (g->out == 0 && !stbi__gif_header(s, g, comp,0)) + return 0; // stbi__g_failure_reason set by stbi__gif_header + + prev_out = g->out; + g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h); + if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); + + switch ((g->eflags & 0x1C) >> 2) { + case 0: // unspecified (also always used on 1st frame) + stbi__fill_gif_background(g, 0, 0, 4 * g->w, 4 * g->w * g->h); + break; + case 1: // do not dispose + if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); + g->old_out = prev_out; + break; + case 2: // dispose to background + if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); + stbi__fill_gif_background(g, g->start_x, g->start_y, g->max_x, g->max_y); + break; + case 3: // dispose to previous + if (g->old_out) { + for (i = g->start_y; i < g->max_y; i += 4 * g->w) + memcpy(&g->out[i + g->start_x], &g->old_out[i + g->start_x], g->max_x - g->start_x); + } + break; + } + + for (;;) { + switch (stbi__get8(s)) { + case 0x2C: /* Image Descriptor */ + { + int prev_trans = -1; + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + if (g->transparent >= 0 && (g->eflags & 0x01)) { + prev_trans = g->pal[g->transparent][3]; + g->pal[g->transparent][3] = 0; + } + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (o == NULL) return NULL; + + if (prev_trans != -1) + g->pal[g->transparent][3] = (stbi_uc) prev_trans; + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + if (stbi__get8(s) == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = stbi__get16le(s); + g->transparent = stbi__get8(s); + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) + stbi__skip(s, len); + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } + + STBI_NOTUSED(req_comp); +} + +static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + + u = stbi__gif_load_next(s, &g, comp, req_comp); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } + else if (g.out) + STBI_FREE(g.out); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s) +{ + const char *signature = "#?RADIANCE\n"; + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s); + stbi__rewind(s); + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + + + // Check identifier + if (strcmp(stbi__hdr_gettoken(s,buffer), "#?RADIANCE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + // Read data + hdr_data = (float *) stbi__malloc(height * width * req_comp * sizeof(float)); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) scanline = (stbi_uc *) stbi__malloc(width * 4); + + for (k = 0; k < 4; ++k) { + i = 0; + while (i < width) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + stbi__rewind( s ); + if (p == NULL) + return 0; + *x = s->img_x; + *y = s->img_y; + *comp = info.ma ? 4 : 3; + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + if (stbi__get16be(s) != 8) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained; + stbi__pic_packet packets[10]; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) +// Does not support 16-bit-per-channel + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *out; + if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) + return 0; + *x = s->img_x; + *y = s->img_y; + *comp = s->img_n; + + out = (stbi_uc *) stbi__malloc(s->img_n * s->img_x * s->img_y); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv; + char c, p, t; + + stbi__rewind( s ); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + + if (maxv > 255) + return stbi__err("max value > 255", "PPM image not 8-bit"); + else + return 1; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/stb_truetype.h b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/stb_truetype.h new file mode 100644 index 00000000..52e1c946 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg/stb_truetype.h @@ -0,0 +1,5011 @@ +// stb_truetype.h - v1.24 - public domain +// authored from 2009-2020 by Sean Barrett / RAD Game Tools +// +// ======================================================================= +// +// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES +// +// This library does no range checking of the offsets found in the file, +// meaning an attacker can use it to read arbitrary memory. +// +// ======================================================================= +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe +// Cass Everitt Martins Mozeiko github:aloucks +// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam +// Brian Hook Omar Cornut github:vassvik +// Walter van Niftrik Ryan Griege +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. Brian Costabile +// Ken Voskuil (kaesve) +// +// VERSION HISTORY +// +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to <current_point, baseline>. I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// <current_point+SF*x0, baseline+SF*y0> to <current_point+SF*x1,baseline+SF*y1). +// +// Advancing for the next character: +// Call GlyphHMetrics, and compute 'current_point += SF * advance'. +// +// +// ADVANCED USAGE +// +// Quality: +// +// - Use the functions with Subpixel at the end to allow your characters +// to have subpixel positioning. Since the font is anti-aliased, not +// hinted, this is very import for quality. (This is not possible with +// baked fonts.) +// +// - Kerning is now supported, and if you're supporting subpixel rendering +// then kerning is worth using to give your text a polished look. +// +// Performance: +// +// - Convert Unicode codepoints to glyph indexes and operate on the glyphs; +// if you don't do this, stb_truetype is forced to do the conversion on +// every call. +// +// - There are a lot of memory allocations. We should modify it to take +// a temp buffer and allocate from the temp buffer (without freeing), +// should help performance a lot. +// +// NOTES +// +// The system uses the raw data found in the .ttf file without changing it +// and without building auxiliary data structures. This is a bit inefficient +// on little-endian systems (the data is big-endian), but assuming you're +// caching the bitmaps or glyph shapes this shouldn't be a big deal. +// +// It appears to be very hard to programmatically determine what font a +// given file is in a general way. I provide an API for this, but I don't +// recommend it. +// +// +// PERFORMANCE MEASUREMENTS FOR 1.06: +// +// 32-bit 64-bit +// Previous release: 8.83 s 7.68 s +// Pool allocations: 7.72 s 6.34 s +// Inline sort : 6.54 s 5.65 s +// New rasterizer : 5.63 s 5.00 s + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// SAMPLE PROGRAMS +//// +// +// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless +// +#if 0 +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +unsigned char ttf_buffer[1<<20]; +unsigned char temp_bitmap[512*512]; + +stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs +GLuint ftex; + +void my_stbtt_initfont(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(ttf_buffer,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! + // can free ttf_buffer at this point + glGenTextures(1, &ftex); + glBindTexture(GL_TEXTURE_2D, ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + // can free temp_bitmap at this point + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void my_stbtt_print(float x, float y, char *text) +{ + // assume orthographic projection with units = screen pixels, origin at top left + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, ftex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include <stdio.h> +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include <math.h> + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include <math.h> + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include <math.h> + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include <math.h> + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include <math.h> + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include <stdlib.h> + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include <assert.h> + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include <string.h> + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include <string.h> + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. +// Returns 0 if the character codepoint is not defined in the font. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + +typedef struct stbtt_kerningentry +{ + int glyph1; // use stbtt_FindGlyphIndex + int glyph2; + int advance; +} stbtt_kerningentry; + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); +// Retrieves a complete list of all of the kerning pairs provided by the font +// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. +// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of contours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); +// fills svg with the character's SVG data. +// returns data size or 0 if SVG not found. + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +// since most people won't use this, find this table the first time it's needed +static int stbtt__get_svg(stbtt_fontinfo *info) +{ + stbtt_uint32 t; + if (info->svg < 0) { + t = stbtt__find_table(info->data, info->fontstart, "SVG "); + if (t) { + stbtt_uint32 offset = ttULONG(info->data + t + 2); + info->svg = t + offset; + } else { + info->svg = 0; + } + } + return info->svg; +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + info->svg = -1; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours < 0) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) +{ + stbtt_uint8 *data = info->data + info->kern; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + return ttUSHORT(data+10); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) +{ + stbtt_uint8 *data = info->data + info->kern; + int k, length; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + length = ttUSHORT(data+10); + if (table_length < length) + length = table_length; + + for (k = 0; k < length; k++) + { + table[k].glyph1 = ttUSHORT(data+18+(k*6)); + table[k].glyph2 = ttUSHORT(data+20+(k*6)); + table[k].advance = ttSHORT(data+22+(k*6)); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch(coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + } break; + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch(classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + + classDefTable = classDef1ValueArray + 2 * glyphCount; + } break; + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + + classDefTable = classRangeRecords + 6 * classRangeCount; + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i<lookupCount; ++i) { + stbtt_uint16 lookupOffset = ttUSHORT(lookupList + 2 + 2 * i); + stbtt_uint8 *lookupTable = lookupList + lookupOffset; + + stbtt_uint16 lookupType = ttUSHORT(lookupTable); + stbtt_uint16 subTableCount = ttUSHORT(lookupTable + 4); + stbtt_uint8 *subTableOffsets = lookupTable + 6; + switch(lookupType) { + case 2: { // Pair Adjustment Positioning Subtable + stbtt_int32 sti; + for (sti=0; sti<subTableCount; sti++) { + stbtt_uint16 subtableOffset = ttUSHORT(subTableOffsets + 2 * sti); + stbtt_uint8 *table = lookupTable + subtableOffset; + stbtt_uint16 posFormat = ttUSHORT(table); + stbtt_uint16 coverageOffset = ttUSHORT(table + 2); + stbtt_int32 coverageIndex = stbtt__GetCoverageIndex(table + coverageOffset, glyph1); + if (coverageIndex == -1) continue; + + switch (posFormat) { + case 1: { + stbtt_int32 l, r, m; + int straw, needle; + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + stbtt_int32 valueRecordPairSizeInBytes = 2; + stbtt_uint16 pairSetCount = ttUSHORT(table + 8); + stbtt_uint16 pairPosOffset = ttUSHORT(table + 10 + 2 * coverageIndex); + stbtt_uint8 *pairValueTable = table + pairPosOffset; + stbtt_uint16 pairValueCount = ttUSHORT(pairValueTable); + stbtt_uint8 *pairValueArray = pairValueTable + 2; + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + STBTT_assert(coverageIndex < pairSetCount); + STBTT__NOTUSED(pairSetCount); + + needle=glyph2; + r=pairValueCount-1; + l=0; + + // Binary search. + while (l <= r) { + stbtt_uint16 secondGlyph; + stbtt_uint8 *pairValue; + m = (l + r) >> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } break; + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + STBTT_assert(glyph1class < class1Count); + STBTT_assert(glyph2class < class2Count); + + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { + stbtt_uint8 *class1Records = table + 16; + stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count); + stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + break; + }; + } + } + break; + }; + + default: + // TODO: Implement other stuff. + break; + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) +{ + int i; + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); + + int numEntries = ttUSHORT(svg_doc_list); + stbtt_uint8 *svg_docs = svg_doc_list + 2; + + for(i=0; i<numEntries; i++) { + stbtt_uint8 *svg_doc = svg_docs + (12 * i); + if ((gl >= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) +{ + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc; + + if (info->svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != NULL) { + *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && mid<n: 0>n => n; 0<n => 0 */ + /* 0<mid && mid>n: 0>n => 0; 0<n => n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + orig[1] = y; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + c*x^2 + b*x + a = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +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. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +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 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. +------------------------------------------------------------------------------ +*/ \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg_dk.h b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg_dk.h new file mode 100644 index 00000000..a0669f5b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg_dk.h @@ -0,0 +1,520 @@ +#pragma once + +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <math.h> + +#include "nanovg.h" +#include "nanovg/dk_renderer.hpp" + +#ifdef __cplusplus +extern "C" { +#endif + +static int dknvg__maxi(int a, int b) { return a > b ? a : b; } + +static const DKNVGtextureDescriptor* dknvg__findTexture(DKNVGcontext* dk, int id) { + return dk->renderer->GetTextureDescriptor(*dk, id); +} + +static int dknvg__renderCreate(void* uptr) +{ + DKNVGcontext *dk = (DKNVGcontext*)uptr; + return dk->renderer->Create(*dk); +} + +static int dknvg__renderCreateTexture(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data) +{ + DKNVGcontext *dk = (DKNVGcontext*)uptr; + return dk->renderer->CreateTexture(*dk, type, w, h, imageFlags, data); +} + +static int dknvg__renderDeleteTexture(void* uptr, int image) { + DKNVGcontext *dk = (DKNVGcontext*)uptr; + return dk->renderer->DeleteTexture(*dk, image); +} + +static int dknvg__renderUpdateTexture(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data) { + DKNVGcontext *dk = (DKNVGcontext*)uptr; + return dk->renderer->UpdateTexture(*dk, image, x, y, w, h, data); +} + +static int dknvg__renderGetTextureSize(void* uptr, int image, int* w, int* h) { + DKNVGcontext *dk = (DKNVGcontext*)uptr; + return dk->renderer->GetTextureSize(*dk, image, w, h); +} + +static void dknvg__xformToMat3x4(float* m3, float* t) { + m3[0] = t[0]; + m3[1] = t[1]; + m3[2] = 0.0f; + m3[3] = 0.0f; + m3[4] = t[2]; + m3[5] = t[3]; + m3[6] = 0.0f; + m3[7] = 0.0f; + m3[8] = t[4]; + m3[9] = t[5]; + m3[10] = 1.0f; + m3[11] = 0.0f; +} + +static NVGcolor dknvg__premulColor(NVGcolor c) { + c.r *= c.a; + c.g *= c.a; + c.b *= c.a; + return c; +} + +static int dknvg__convertPaint(DKNVGcontext* dk, DKNVGfragUniforms* frag, NVGpaint* paint, + NVGscissor* scissor, float width, float fringe, float strokeThr) +{ + const DKNVGtextureDescriptor *tex = NULL; + float invxform[6]; + + memset(frag, 0, sizeof(*frag)); + + frag->innerCol = dknvg__premulColor(paint->innerColor); + frag->outerCol = dknvg__premulColor(paint->outerColor); + + if (scissor->extent[0] < -0.5f || scissor->extent[1] < -0.5f) { + memset(frag->scissorMat, 0, sizeof(frag->scissorMat)); + frag->scissorExt[0] = 1.0f; + frag->scissorExt[1] = 1.0f; + frag->scissorScale[0] = 1.0f; + frag->scissorScale[1] = 1.0f; + } else { + nvgTransformInverse(invxform, scissor->xform); + dknvg__xformToMat3x4(frag->scissorMat, invxform); + frag->scissorExt[0] = scissor->extent[0]; + frag->scissorExt[1] = scissor->extent[1]; + frag->scissorScale[0] = sqrtf(scissor->xform[0]*scissor->xform[0] + scissor->xform[2]*scissor->xform[2]) / fringe; + frag->scissorScale[1] = sqrtf(scissor->xform[1]*scissor->xform[1] + scissor->xform[3]*scissor->xform[3]) / fringe; + } + + memcpy(frag->extent, paint->extent, sizeof(frag->extent)); + frag->strokeMult = (width*0.5f + fringe*0.5f) / fringe; + frag->strokeThr = strokeThr; + + if (paint->image != 0) { + tex = dknvg__findTexture(dk, paint->image); + if (tex == NULL) return 0; + if ((tex->flags & NVG_IMAGE_FLIPY) != 0) { + float m1[6], m2[6]; + nvgTransformTranslate(m1, 0.0f, frag->extent[1] * 0.5f); + nvgTransformMultiply(m1, paint->xform); + nvgTransformScale(m2, 1.0f, -1.0f); + nvgTransformMultiply(m2, m1); + nvgTransformTranslate(m1, 0.0f, -frag->extent[1] * 0.5f); + nvgTransformMultiply(m1, m2); + nvgTransformInverse(invxform, m1); + } else { + nvgTransformInverse(invxform, paint->xform); + } + frag->type = NSVG_SHADER_FILLIMG; + + if (tex->type == NVG_TEXTURE_RGBA) + frag->texType = (tex->flags & NVG_IMAGE_PREMULTIPLIED) ? 0 : 1; + else + frag->texType = 2; +// printf("frag->texType = %d\n", frag->texType); + } else { + frag->type = NSVG_SHADER_FILLGRAD; + frag->radius = paint->radius; + frag->feather = paint->feather; + nvgTransformInverse(invxform, paint->xform); + } + + dknvg__xformToMat3x4(frag->paintMat, invxform); + + return 1; +} + +static DKNVGfragUniforms* nvg__fragUniformPtr(DKNVGcontext* dk, int i); + +static void dknvg__renderViewport(void* uptr, float width, float height, float devicePixelRatio) +{ + NVG_NOTUSED(devicePixelRatio); + DKNVGcontext* dk = (DKNVGcontext*)uptr; + dk->view[0] = width; + dk->view[1] = height; +} + +static void dknvg__renderCancel(void* uptr) { + DKNVGcontext* dk = (DKNVGcontext*)uptr; + dk->nverts = 0; + dk->npaths = 0; + dk->ncalls = 0; + dk->nuniforms = 0; +} + +static int dknvg_convertBlendFuncFactor(int factor) { + switch (factor) { + case NVG_ZERO: + return DkBlendFactor_Zero; + case NVG_ONE: + return DkBlendFactor_One; + case NVG_SRC_COLOR: + return DkBlendFactor_SrcColor; + case NVG_ONE_MINUS_SRC_COLOR: + return DkBlendFactor_InvSrcColor; + case NVG_DST_COLOR: + return DkBlendFactor_DstColor; + case NVG_ONE_MINUS_DST_COLOR: + return DkBlendFactor_InvDstColor; + case NVG_SRC_ALPHA: + return DkBlendFactor_SrcAlpha; + case NVG_ONE_MINUS_SRC_ALPHA: + return DkBlendFactor_InvSrcAlpha; + case NVG_DST_ALPHA: + return DkBlendFactor_DstAlpha; + case NVG_ONE_MINUS_DST_ALPHA: + return DkBlendFactor_InvDstAlpha; + case NVG_SRC_ALPHA_SATURATE: + return DkBlendFactor_SrcAlphaSaturate; + default: + return -1; + } +} + +static DKNVGblend dknvg__blendCompositeOperation(NVGcompositeOperationState op) { + DKNVGblend blend; + blend.srcRGB = dknvg_convertBlendFuncFactor(op.srcRGB); + blend.dstRGB = dknvg_convertBlendFuncFactor(op.dstRGB); + blend.srcAlpha = dknvg_convertBlendFuncFactor(op.srcAlpha); + blend.dstAlpha = dknvg_convertBlendFuncFactor(op.dstAlpha); + + if (blend.srcRGB == -1 || blend.dstRGB == -1 || blend.srcAlpha == -1 || blend.dstAlpha == -1) { + blend.srcRGB = DkBlendFactor_One; + blend.dstRGB = DkBlendFactor_InvSrcAlpha; + blend.srcAlpha = DkBlendFactor_One; + blend.dstAlpha = DkBlendFactor_InvSrcAlpha; + } + return blend; +} + +static void dknvg__renderFlush(void* uptr) { + DKNVGcontext *dk = (DKNVGcontext*)uptr; + dk->renderer->Flush(*dk); +} + +static int dknvg__maxVertCount(const NVGpath* paths, int npaths) { + int i, count = 0; + for (i = 0; i < npaths; i++) { + count += paths[i].nfill; + count += paths[i].nstroke; + } + return count; +} + +static DKNVGcall* dknvg__allocCall(DKNVGcontext* dk) +{ + DKNVGcall* ret = NULL; + if (dk->ncalls+1 > dk->ccalls) { + DKNVGcall* calls; + int ccalls = dknvg__maxi(dk->ncalls+1, 128) + dk->ccalls/2; // 1.5x Overallocate + calls = (DKNVGcall*)realloc(dk->calls, sizeof(DKNVGcall) * ccalls); + if (calls == NULL) return NULL; + dk->calls = calls; + dk->ccalls = ccalls; + } + ret = &dk->calls[dk->ncalls++]; + memset(ret, 0, sizeof(DKNVGcall)); + return ret; +} + +static int dknvg__allocPaths(DKNVGcontext* dk, int n) +{ + int ret = 0; + if (dk->npaths+n > dk->cpaths) { + DKNVGpath* paths; + int cpaths = dknvg__maxi(dk->npaths + n, 128) + dk->cpaths/2; // 1.5x Overallocate + paths = (DKNVGpath*)realloc(dk->paths, sizeof(DKNVGpath) * cpaths); + if (paths == NULL) return -1; + dk->paths = paths; + dk->cpaths = cpaths; + } + ret = dk->npaths; + dk->npaths += n; + return ret; +} + +static int dknvg__allocVerts(DKNVGcontext* dk, int n) +{ + int ret = 0; + if (dk->nverts+n > dk->cverts) { + NVGvertex* verts; + int cverts = dknvg__maxi(dk->nverts + n, 4096) + dk->cverts/2; // 1.5x Overallocate + verts = (NVGvertex*)realloc(dk->verts, sizeof(NVGvertex) * cverts); + if (verts == NULL) return -1; + dk->verts = verts; + dk->cverts = cverts; + } + ret = dk->nverts; + dk->nverts += n; + return ret; +} + +static int dknvg__allocFragUniforms(DKNVGcontext* dk, int n) +{ + int ret = 0, structSize = dk->fragSize; + if (dk->nuniforms+n > dk->cuniforms) { + unsigned char* uniforms; + int cuniforms = dknvg__maxi(dk->nuniforms+n, 128) + dk->cuniforms/2; // 1.5x Overallocate + uniforms = (unsigned char*)realloc(dk->uniforms, structSize * cuniforms); + if (uniforms == NULL) return -1; + dk->uniforms = uniforms; + dk->cuniforms = cuniforms; + } + ret = dk->nuniforms * structSize; + dk->nuniforms += n; + return ret; +} + +static DKNVGfragUniforms* nvg__fragUniformPtr(DKNVGcontext* dk, int i) +{ + return (DKNVGfragUniforms*)&dk->uniforms[i]; +} + +static void dknvg__vset(NVGvertex* vtx, float x, float y, float u, float v) +{ + vtx->x = x; + vtx->y = y; + vtx->u = u; + vtx->v = v; +} + +static void dknvg__renderFill(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe, + const float* bounds, const NVGpath* paths, int npaths) +{ + DKNVGcontext* dk = (DKNVGcontext*)uptr; + DKNVGcall* call = dknvg__allocCall(dk); + NVGvertex* quad; + DKNVGfragUniforms* frag; + int i, maxverts, offset; + + if (call == NULL) return; + + call->type = DKNVG_FILL; + call->triangleCount = 4; + call->pathOffset = dknvg__allocPaths(dk, npaths); + if (call->pathOffset == -1) goto error; + call->pathCount = npaths; + call->image = paint->image; + call->blendFunc = dknvg__blendCompositeOperation(compositeOperation); + + if (npaths == 1 && paths[0].convex) + { + call->type = DKNVG_CONVEXFILL; + call->triangleCount = 0; // Bounding box fill quad not needed for convex fill + } + + // Allocate vertices for all the paths. + maxverts = dknvg__maxVertCount(paths, npaths) + call->triangleCount; + offset = dknvg__allocVerts(dk, maxverts); + if (offset == -1) goto error; + + for (i = 0; i < npaths; i++) { + DKNVGpath* copy = &dk->paths[call->pathOffset + i]; + const NVGpath* path = &paths[i]; + memset(copy, 0, sizeof(DKNVGpath)); + if (path->nfill > 0) { + copy->fillOffset = offset; + copy->fillCount = path->nfill; + memcpy(&dk->verts[offset], path->fill, sizeof(NVGvertex) * path->nfill); + offset += path->nfill; + } + if (path->nstroke > 0) { + copy->strokeOffset = offset; + copy->strokeCount = path->nstroke; + memcpy(&dk->verts[offset], path->stroke, sizeof(NVGvertex) * path->nstroke); + offset += path->nstroke; + } + } + + // Setup uniforms for draw calls + if (call->type == DKNVG_FILL) { + // Quad + call->triangleOffset = offset; + quad = &dk->verts[call->triangleOffset]; + dknvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f); + dknvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f); + dknvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f); + dknvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f); + + call->uniformOffset = dknvg__allocFragUniforms(dk, 2); + if (call->uniformOffset == -1) goto error; + // Simple shader for stencil + frag = nvg__fragUniformPtr(dk, call->uniformOffset); + memset(frag, 0, sizeof(*frag)); + frag->strokeThr = -1.0f; + frag->type = NSVG_SHADER_SIMPLE; + // Fill shader + dknvg__convertPaint(dk, nvg__fragUniformPtr(dk, call->uniformOffset + dk->fragSize), paint, scissor, fringe, fringe, -1.0f); + } else { + call->uniformOffset = dknvg__allocFragUniforms(dk, 1); + if (call->uniformOffset == -1) goto error; + // Fill shader + dknvg__convertPaint(dk, nvg__fragUniformPtr(dk, call->uniformOffset), paint, scissor, fringe, fringe, -1.0f); + } + + return; + +error: + // We get here if call alloc was ok, but something else is not. + // Roll back the last call to prevent drawing it. + if (dk->ncalls > 0) dk->ncalls--; +} + +static void dknvg__renderStroke(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe, + float strokeWidth, const NVGpath* paths, int npaths) +{ + DKNVGcontext* dk = (DKNVGcontext*)uptr; + DKNVGcall* call = dknvg__allocCall(dk); + int i, maxverts, offset; + + if (call == NULL) { + return; + } + + call->type = DKNVG_STROKE; + call->pathOffset = dknvg__allocPaths(dk, npaths); + if (call->pathOffset == -1) goto error; + call->pathCount = npaths; + call->image = paint->image; + call->blendFunc = dknvg__blendCompositeOperation(compositeOperation); + + // Allocate vertices for all the paths. + maxverts = dknvg__maxVertCount(paths, npaths); + offset = dknvg__allocVerts(dk, maxverts); + if (offset == -1) goto error; + + for (i = 0; i < npaths; i++) { + DKNVGpath* copy = &dk->paths[call->pathOffset + i]; + const NVGpath* path = &paths[i]; + memset(copy, 0, sizeof(DKNVGpath)); + if (path->nstroke) { + copy->strokeOffset = offset; + copy->strokeCount = path->nstroke; + memcpy(&dk->verts[offset], path->stroke, sizeof(NVGvertex) * path->nstroke); + offset += path->nstroke; + } + } + + if (dk->flags & NVG_STENCIL_STROKES) { + // Fill shader + call->uniformOffset = dknvg__allocFragUniforms(dk, 2); + if (call->uniformOffset == -1) goto error; + + dknvg__convertPaint(dk, nvg__fragUniformPtr(dk, call->uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); + dknvg__convertPaint(dk, nvg__fragUniformPtr(dk, call->uniformOffset + dk->fragSize), paint, scissor, strokeWidth, fringe, 1.0f - 0.5f/255.0f); + } else { + // Fill shader + call->uniformOffset = dknvg__allocFragUniforms(dk, 1); + if (call->uniformOffset == -1) goto error; + + dknvg__convertPaint(dk, nvg__fragUniformPtr(dk, call->uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); + } + + return; + +error: + // We get here if call alloc was ok, but something else is not. + // Roll back the last call to prevent drawing it. + if (dk->ncalls > 0) dk->ncalls--; +} + +static void dknvg__renderTriangles(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, + const NVGvertex* verts, int nverts, float fringe) +{ + DKNVGcontext* dk = (DKNVGcontext*)uptr; + DKNVGcall* call = dknvg__allocCall(dk); + DKNVGfragUniforms* frag; + + if (call == NULL) return; + + call->type = DKNVG_TRIANGLES; + call->image = paint->image; + call->blendFunc = dknvg__blendCompositeOperation(compositeOperation); + + // Allocate vertices for all the paths. + call->triangleOffset = dknvg__allocVerts(dk, nverts); + if (call->triangleOffset == -1) goto error; + call->triangleCount = nverts; + + memcpy(&dk->verts[call->triangleOffset], verts, sizeof(NVGvertex) * nverts); + + // Fill shader + call->uniformOffset = dknvg__allocFragUniforms(dk, 1); + if (call->uniformOffset == -1) goto error; + frag = nvg__fragUniformPtr(dk, call->uniformOffset); + dknvg__convertPaint(dk, frag, paint, scissor, 1.0f, fringe, -1.0f); + frag->type = NSVG_SHADER_IMG; + + return; + +error: + // We get here if call alloc was ok, but something else is not. + // Roll back the last call to prevent drawing it. + if (dk->ncalls > 0) dk->ncalls--; +} + +static void dknvg__renderDelete(void* uptr) { + DKNVGcontext* dk = (DKNVGcontext*)uptr; + if (dk == NULL) return; + + free(dk->paths); + free(dk->verts); + free(dk->uniforms); + free(dk->calls); + + free(dk); +} + +NVGcontext* nvgCreateDk(nvg::DkRenderer *renderer, int flags) { + NVGparams params; + NVGcontext* ctx = NULL; + DKNVGcontext* dk = (DKNVGcontext*)malloc(sizeof(DKNVGcontext)); + if (dk == NULL) goto error; + memset(dk, 0, sizeof(DKNVGcontext)); + + memset(¶ms, 0, sizeof(params)); + params.renderCreate = dknvg__renderCreate; + params.renderCreateTexture = dknvg__renderCreateTexture; + params.renderDeleteTexture = dknvg__renderDeleteTexture; + params.renderUpdateTexture = dknvg__renderUpdateTexture; + params.renderGetTextureSize = dknvg__renderGetTextureSize; + params.renderViewport = dknvg__renderViewport; + params.renderCancel = dknvg__renderCancel; + params.renderFlush = dknvg__renderFlush; + params.renderFill = dknvg__renderFill; + params.renderStroke = dknvg__renderStroke; + params.renderTriangles = dknvg__renderTriangles; + params.renderDelete = dknvg__renderDelete; + params.userPtr = dk; + params.edgeAntiAlias = flags & NVG_ANTIALIAS ? 1 : 0; + + dk->renderer = renderer; + dk->flags = flags; + + ctx = nvgCreateInternal(¶ms); + if (ctx == NULL) goto error; + + return ctx; + +error: + // 'dk' is freed by nvgDeleteInternal. + if (ctx != NULL) nvgDeleteInternal(ctx); + return NULL; +} + +void nvgDeleteDk(NVGcontext* ctx) +{ + nvgDeleteInternal(ctx); +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg_gl.h b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg_gl.h new file mode 100644 index 00000000..69506e7b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/include/nanovg_gl.h @@ -0,0 +1,1672 @@ +// +// Copyright (c) 2009-2013 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#ifndef NANOVG_GL_H +#define NANOVG_GL_H + +#ifdef USE_OPENGL + +#ifdef __cplusplus +extern "C" { +#endif + +// Create flags + +enum NVGcreateFlags { + // Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA). + NVG_ANTIALIAS = 1<<0, + // Flag indicating if strokes should be drawn using stencil buffer. The rendering will be a little + // slower, but path overlaps (i.e. self-intersecting or sharp turns) will be drawn just once. + NVG_STENCIL_STROKES = 1<<1, + // Flag indicating that additional debug checks are done. + NVG_DEBUG = 1<<2, +}; + +#if defined NANOVG_GL2_IMPLEMENTATION +# define NANOVG_GL2 1 +# define NANOVG_GL_IMPLEMENTATION 1 +#elif defined NANOVG_GL3_IMPLEMENTATION +# define NANOVG_GL3 1 +# define NANOVG_GL_IMPLEMENTATION 1 +# define NANOVG_GL_USE_UNIFORMBUFFER 1 +#elif defined NANOVG_GLES2_IMPLEMENTATION +# define NANOVG_GLES2 1 +# define NANOVG_GL_IMPLEMENTATION 1 +#elif defined NANOVG_GLES3_IMPLEMENTATION +# define NANOVG_GLES3 1 +# define NANOVG_GL_IMPLEMENTATION 1 +#endif + +#define NANOVG_GL_USE_STATE_FILTER (1) + +// Creates NanoVG contexts for different OpenGL (ES) versions. +// Flags should be combination of the create flags above. + +#if defined NANOVG_GL2 + +NVGcontext* nvgCreateGL2(int flags); +void nvgDeleteGL2(NVGcontext* ctx); + +int nvglCreateImageFromHandleGL2(NVGcontext* ctx, GLuint textureId, int w, int h, int flags); +GLuint nvglImageHandleGL2(NVGcontext* ctx, int image); + +#endif + +#if defined NANOVG_GL3 + +NVGcontext* nvgCreateGL3(int flags); +void nvgDeleteGL3(NVGcontext* ctx); + +int nvglCreateImageFromHandleGL3(NVGcontext* ctx, GLuint textureId, int w, int h, int flags); +GLuint nvglImageHandleGL3(NVGcontext* ctx, int image); + +#endif + +#if defined NANOVG_GLES2 + +NVGcontext* nvgCreateGLES2(int flags); +void nvgDeleteGLES2(NVGcontext* ctx); + +int nvglCreateImageFromHandleGLES2(NVGcontext* ctx, GLuint textureId, int w, int h, int flags); +GLuint nvglImageHandleGLES2(NVGcontext* ctx, int image); + +#endif + +#if defined NANOVG_GLES3 + +NVGcontext* nvgCreateGLES3(int flags); +void nvgDeleteGLES3(NVGcontext* ctx); + +int nvglCreateImageFromHandleGLES3(NVGcontext* ctx, GLuint textureId, int w, int h, int flags); +GLuint nvglImageHandleGLES3(NVGcontext* ctx, int image); + +#endif + +// These are additional flags on top of NVGimageFlags. +enum NVGimageFlagsGL { + NVG_IMAGE_NODELETE = 1<<16, // Do not delete GL texture handle. +}; + +#ifdef __cplusplus +} +#endif + +#endif /* NANOVG_GL_H */ + +#ifdef NANOVG_GL_IMPLEMENTATION + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <math.h> +#include "nanovg.h" + +enum GLNVGuniformLoc { + GLNVG_LOC_VIEWSIZE, + GLNVG_LOC_TEX, + GLNVG_LOC_FRAG, + GLNVG_MAX_LOCS +}; + +enum GLNVGshaderType { + NSVG_SHADER_FILLGRAD, + NSVG_SHADER_FILLIMG, + NSVG_SHADER_SIMPLE, + NSVG_SHADER_IMG +}; + +#if NANOVG_GL_USE_UNIFORMBUFFER +enum GLNVGuniformBindings { + GLNVG_FRAG_BINDING = 0, +}; +#endif + +struct GLNVGshader { + GLuint prog; + GLuint frag; + GLuint vert; + GLint loc[GLNVG_MAX_LOCS]; +}; +typedef struct GLNVGshader GLNVGshader; + +struct GLNVGtexture { + int id; + GLuint tex; + int width, height; + int type; + int flags; +}; +typedef struct GLNVGtexture GLNVGtexture; + +struct GLNVGblend +{ + GLenum srcRGB; + GLenum dstRGB; + GLenum srcAlpha; + GLenum dstAlpha; +}; +typedef struct GLNVGblend GLNVGblend; + +enum GLNVGcallType { + GLNVG_NONE = 0, + GLNVG_FILL, + GLNVG_CONVEXFILL, + GLNVG_STROKE, + GLNVG_TRIANGLES, +}; + +struct GLNVGcall { + int type; + int image; + int pathOffset; + int pathCount; + int triangleOffset; + int triangleCount; + int uniformOffset; + GLNVGblend blendFunc; +}; +typedef struct GLNVGcall GLNVGcall; + +struct GLNVGpath { + int fillOffset; + int fillCount; + int strokeOffset; + int strokeCount; +}; +typedef struct GLNVGpath GLNVGpath; + +struct GLNVGfragUniforms { + #if NANOVG_GL_USE_UNIFORMBUFFER + float scissorMat[12]; // matrices are actually 3 vec4s + float paintMat[12]; + struct NVGcolor innerCol; + struct NVGcolor outerCol; + float scissorExt[2]; + float scissorScale[2]; + float extent[2]; + float radius; + float feather; + float strokeMult; + float strokeThr; + int texType; + int type; + #else + // note: after modifying layout or size of uniform array, + // don't forget to also update the fragment shader source! + #define NANOVG_GL_UNIFORMARRAY_SIZE 11 + union { + struct { + float scissorMat[12]; // matrices are actually 3 vec4s + float paintMat[12]; + struct NVGcolor innerCol; + struct NVGcolor outerCol; + float scissorExt[2]; + float scissorScale[2]; + float extent[2]; + float radius; + float feather; + float strokeMult; + float strokeThr; + float texType; + float type; + }; + float uniformArray[NANOVG_GL_UNIFORMARRAY_SIZE][4]; + }; + #endif +}; +typedef struct GLNVGfragUniforms GLNVGfragUniforms; + +struct GLNVGcontext { + GLNVGshader shader; + GLNVGtexture* textures; + float view[2]; + int ntextures; + int ctextures; + int textureId; + GLuint vertBuf; +#if defined NANOVG_GL3 + GLuint vertArr; +#endif +#if NANOVG_GL_USE_UNIFORMBUFFER + GLuint fragBuf; +#endif + int fragSize; + int flags; + + // Per frame buffers + GLNVGcall* calls; + int ccalls; + int ncalls; + GLNVGpath* paths; + int cpaths; + int npaths; + struct NVGvertex* verts; + int cverts; + int nverts; + unsigned char* uniforms; + int cuniforms; + int nuniforms; + + // cached state + #if NANOVG_GL_USE_STATE_FILTER + GLuint boundTexture; + GLuint stencilMask; + GLenum stencilFunc; + GLint stencilFuncRef; + GLuint stencilFuncMask; + GLNVGblend blendFunc; + #endif + + int dummyTex; +}; +typedef struct GLNVGcontext GLNVGcontext; + +static int glnvg__maxi(int a, int b) { return a > b ? a : b; } + +#ifdef NANOVG_GLES2 +static unsigned int glnvg__nearestPow2(unsigned int num) +{ + unsigned n = num > 0 ? num - 1 : 0; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + n++; + return n; +} +#endif + +static void glnvg__bindTexture(GLNVGcontext* gl, GLuint tex) +{ +#if NANOVG_GL_USE_STATE_FILTER + if (gl->boundTexture != tex) { + gl->boundTexture = tex; + glBindTexture(GL_TEXTURE_2D, tex); + } +#else + glBindTexture(GL_TEXTURE_2D, tex); +#endif +} + +static void glnvg__stencilMask(GLNVGcontext* gl, GLuint mask) +{ +#if NANOVG_GL_USE_STATE_FILTER + if (gl->stencilMask != mask) { + gl->stencilMask = mask; + glStencilMask(mask); + } +#else + glStencilMask(mask); +#endif +} + +static void glnvg__stencilFunc(GLNVGcontext* gl, GLenum func, GLint ref, GLuint mask) +{ +#if NANOVG_GL_USE_STATE_FILTER + if ((gl->stencilFunc != func) || + (gl->stencilFuncRef != ref) || + (gl->stencilFuncMask != mask)) { + + gl->stencilFunc = func; + gl->stencilFuncRef = ref; + gl->stencilFuncMask = mask; + glStencilFunc(func, ref, mask); + } +#else + glStencilFunc(func, ref, mask); +#endif +} +static void glnvg__blendFuncSeparate(GLNVGcontext* gl, const GLNVGblend* blend) +{ +#if NANOVG_GL_USE_STATE_FILTER + if ((gl->blendFunc.srcRGB != blend->srcRGB) || + (gl->blendFunc.dstRGB != blend->dstRGB) || + (gl->blendFunc.srcAlpha != blend->srcAlpha) || + (gl->blendFunc.dstAlpha != blend->dstAlpha)) { + + gl->blendFunc = *blend; + glBlendFuncSeparate(blend->srcRGB, blend->dstRGB, blend->srcAlpha,blend->dstAlpha); + } +#else + glBlendFuncSeparate(blend->srcRGB, blend->dstRGB, blend->srcAlpha,blend->dstAlpha); +#endif +} + +static GLNVGtexture* glnvg__allocTexture(GLNVGcontext* gl) +{ + GLNVGtexture* tex = NULL; + int i; + + for (i = 0; i < gl->ntextures; i++) { + if (gl->textures[i].id == 0) { + tex = &gl->textures[i]; + break; + } + } + if (tex == NULL) { + if (gl->ntextures+1 > gl->ctextures) { + GLNVGtexture* textures; + int ctextures = glnvg__maxi(gl->ntextures+1, 4) + gl->ctextures/2; // 1.5x Overallocate + textures = (GLNVGtexture*)realloc(gl->textures, sizeof(GLNVGtexture)*ctextures); + if (textures == NULL) return NULL; + gl->textures = textures; + gl->ctextures = ctextures; + } + tex = &gl->textures[gl->ntextures++]; + } + + memset(tex, 0, sizeof(*tex)); + tex->id = ++gl->textureId; + + return tex; +} + +static GLNVGtexture* glnvg__findTexture(GLNVGcontext* gl, int id) +{ + int i; + for (i = 0; i < gl->ntextures; i++) + if (gl->textures[i].id == id) + return &gl->textures[i]; + return NULL; +} + +static int glnvg__deleteTexture(GLNVGcontext* gl, int id) +{ + int i; + for (i = 0; i < gl->ntextures; i++) { + if (gl->textures[i].id == id) { + if (gl->textures[i].tex != 0 && (gl->textures[i].flags & NVG_IMAGE_NODELETE) == 0) + glDeleteTextures(1, &gl->textures[i].tex); + memset(&gl->textures[i], 0, sizeof(gl->textures[i])); + return 1; + } + } + return 0; +} + +static void glnvg__dumpShaderError(GLuint shader, const char* name, const char* type) +{ + GLchar str[512+1]; + GLsizei len = 0; + glGetShaderInfoLog(shader, 512, &len, str); + if (len > 512) len = 512; + str[len] = '\0'; + printf("Shader %s/%s error:\n%s\n", name, type, str); +} + +static void glnvg__dumpProgramError(GLuint prog, const char* name) +{ + GLchar str[512+1]; + GLsizei len = 0; + glGetProgramInfoLog(prog, 512, &len, str); + if (len > 512) len = 512; + str[len] = '\0'; + printf("Program %s error:\n%s\n", name, str); +} + +static void glnvg__checkError(GLNVGcontext* gl, const char* str) +{ + GLenum err; + if ((gl->flags & NVG_DEBUG) == 0) return; + err = glGetError(); + if (err != GL_NO_ERROR) { + printf("Error %08x after %s\n", err, str); + return; + } +} + +static int glnvg__createShader(GLNVGshader* shader, const char* name, const char* header, const char* opts, const char* vshader, const char* fshader) +{ + GLint status; + GLuint prog, vert, frag; + const char* str[3]; + str[0] = header; + str[1] = opts != NULL ? opts : ""; + + memset(shader, 0, sizeof(*shader)); + + prog = glCreateProgram(); + vert = glCreateShader(GL_VERTEX_SHADER); + frag = glCreateShader(GL_FRAGMENT_SHADER); + str[2] = vshader; + glShaderSource(vert, 3, str, 0); + str[2] = fshader; + glShaderSource(frag, 3, str, 0); + + glCompileShader(vert); + glGetShaderiv(vert, GL_COMPILE_STATUS, &status); + if (status != GL_TRUE) { + glnvg__dumpShaderError(vert, name, "vert"); + return 0; + } + + glCompileShader(frag); + glGetShaderiv(frag, GL_COMPILE_STATUS, &status); + if (status != GL_TRUE) { + glnvg__dumpShaderError(frag, name, "frag"); + return 0; + } + + glAttachShader(prog, vert); + glAttachShader(prog, frag); + + glBindAttribLocation(prog, 0, "vertex"); + glBindAttribLocation(prog, 1, "tcoord"); + + glLinkProgram(prog); + glGetProgramiv(prog, GL_LINK_STATUS, &status); + if (status != GL_TRUE) { + glnvg__dumpProgramError(prog, name); + return 0; + } + + shader->prog = prog; + shader->vert = vert; + shader->frag = frag; + + return 1; +} + +static void glnvg__deleteShader(GLNVGshader* shader) +{ + if (shader->prog != 0) + glDeleteProgram(shader->prog); + if (shader->vert != 0) + glDeleteShader(shader->vert); + if (shader->frag != 0) + glDeleteShader(shader->frag); +} + +static void glnvg__getUniforms(GLNVGshader* shader) +{ + shader->loc[GLNVG_LOC_VIEWSIZE] = glGetUniformLocation(shader->prog, "viewSize"); + shader->loc[GLNVG_LOC_TEX] = glGetUniformLocation(shader->prog, "tex"); + +#if NANOVG_GL_USE_UNIFORMBUFFER + shader->loc[GLNVG_LOC_FRAG] = glGetUniformBlockIndex(shader->prog, "frag"); +#else + shader->loc[GLNVG_LOC_FRAG] = glGetUniformLocation(shader->prog, "frag"); +#endif +} + +static int glnvg__renderCreateTexture(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data); + +static int glnvg__renderCreate(void* uptr) +{ + GLNVGcontext* gl = (GLNVGcontext*)uptr; + int align = 4; + + // TODO: mediump float may not be enough for GLES2 in iOS. + // see the following discussion: https://github.com/memononen/nanovg/issues/46 + static const char* shaderHeader = +#if defined NANOVG_GL2 + "#define NANOVG_GL2 1\n" +#elif defined NANOVG_GL3 + "#version 150 core\n" + "#define NANOVG_GL3 1\n" +#elif defined NANOVG_GLES2 + "#version 100\n" + "#define NANOVG_GL2 1\n" +#elif defined NANOVG_GLES3 + "#version 300 es\n" + "#define NANOVG_GL3 1\n" +#endif + +#if NANOVG_GL_USE_UNIFORMBUFFER + "#define USE_UNIFORMBUFFER 1\n" +#else + "#define UNIFORMARRAY_SIZE 11\n" +#endif + "\n"; + + static const char* fillVertShader = + "#ifdef NANOVG_GL3\n" + " uniform vec2 viewSize;\n" + " in vec2 vertex;\n" + " in vec2 tcoord;\n" + " out vec2 ftcoord;\n" + " out vec2 fpos;\n" + "#else\n" + " uniform vec2 viewSize;\n" + " attribute vec2 vertex;\n" + " attribute vec2 tcoord;\n" + " varying vec2 ftcoord;\n" + " varying vec2 fpos;\n" + "#endif\n" + "void main(void) {\n" + " ftcoord = tcoord;\n" + " fpos = vertex;\n" + " gl_Position = vec4(2.0*vertex.x/viewSize.x - 1.0, 1.0 - 2.0*vertex.y/viewSize.y, 0, 1);\n" + "}\n"; + + static const char* fillFragShader = + "#ifdef GL_ES\n" + "#if defined(GL_FRAGMENT_PRECISION_HIGH) || defined(NANOVG_GL3)\n" + " precision highp float;\n" + "#else\n" + " precision mediump float;\n" + "#endif\n" + "#endif\n" + "#ifdef NANOVG_GL3\n" + "#ifdef USE_UNIFORMBUFFER\n" + " layout(std140) uniform frag {\n" + " mat3 scissorMat;\n" + " mat3 paintMat;\n" + " vec4 innerCol;\n" + " vec4 outerCol;\n" + " vec2 scissorExt;\n" + " vec2 scissorScale;\n" + " vec2 extent;\n" + " float radius;\n" + " float feather;\n" + " float strokeMult;\n" + " float strokeThr;\n" + " int texType;\n" + " int type;\n" + " };\n" + "#else\n" // NANOVG_GL3 && !USE_UNIFORMBUFFER + " uniform vec4 frag[UNIFORMARRAY_SIZE];\n" + "#endif\n" + " uniform sampler2D tex;\n" + " in vec2 ftcoord;\n" + " in vec2 fpos;\n" + " out vec4 outColor;\n" + "#else\n" // !NANOVG_GL3 + " uniform vec4 frag[UNIFORMARRAY_SIZE];\n" + " uniform sampler2D tex;\n" + " varying vec2 ftcoord;\n" + " varying vec2 fpos;\n" + "#endif\n" + "#ifndef USE_UNIFORMBUFFER\n" + " #define scissorMat mat3(frag[0].xyz, frag[1].xyz, frag[2].xyz)\n" + " #define paintMat mat3(frag[3].xyz, frag[4].xyz, frag[5].xyz)\n" + " #define innerCol frag[6]\n" + " #define outerCol frag[7]\n" + " #define scissorExt frag[8].xy\n" + " #define scissorScale frag[8].zw\n" + " #define extent frag[9].xy\n" + " #define radius frag[9].z\n" + " #define feather frag[9].w\n" + " #define strokeMult frag[10].x\n" + " #define strokeThr frag[10].y\n" + " #define texType int(frag[10].z)\n" + " #define type int(frag[10].w)\n" + "#endif\n" + "\n" + "float sdroundrect(vec2 pt, vec2 ext, float rad) {\n" + " vec2 ext2 = ext - vec2(rad,rad);\n" + " vec2 d = abs(pt) - ext2;\n" + " return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rad;\n" + "}\n" + "\n" + "// Scissoring\n" + "float scissorMask(vec2 p) {\n" + " vec2 sc = (abs((scissorMat * vec3(p,1.0)).xy) - scissorExt);\n" + " sc = vec2(0.5,0.5) - sc * scissorScale;\n" + " return clamp(sc.x,0.0,1.0) * clamp(sc.y,0.0,1.0);\n" + "}\n" + "#ifdef EDGE_AA\n" + "// Stroke - from [0..1] to clipped pyramid, where the slope is 1px.\n" + "float strokeMask() {\n" + " return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult) * min(1.0, ftcoord.y);\n" + "}\n" + "#endif\n" + "\n" + "void main(void) {\n" + " vec4 result;\n" + " float scissor = scissorMask(fpos);\n" + "#ifdef EDGE_AA\n" + " float strokeAlpha = strokeMask();\n" + " if (strokeAlpha < strokeThr) discard;\n" + "#else\n" + " float strokeAlpha = 1.0;\n" + "#endif\n" + " if (type == 0) { // Gradient\n" + " // Calculate gradient color using box gradient\n" + " vec2 pt = (paintMat * vec3(fpos,1.0)).xy;\n" + " float d = clamp((sdroundrect(pt, extent, radius) + feather*0.5) / feather, 0.0, 1.0);\n" + " vec4 color = mix(innerCol,outerCol,d);\n" + " // Combine alpha\n" + " color *= strokeAlpha * scissor;\n" + " result = color;\n" + " } else if (type == 1) { // Image\n" + " // Calculate color fron texture\n" + " vec2 pt = (paintMat * vec3(fpos,1.0)).xy / extent;\n" + "#ifdef NANOVG_GL3\n" + " vec4 color = texture(tex, pt);\n" + "#else\n" + " vec4 color = texture2D(tex, pt);\n" + "#endif\n" + " if (texType == 1) color = vec4(color.xyz*color.w,color.w);" + " if (texType == 2) color = vec4(color.x);" + " // Apply color tint and alpha.\n" + " color *= innerCol;\n" + " // Combine alpha\n" + " color *= strokeAlpha * scissor;\n" + " result = color;\n" + " } else if (type == 2) { // Stencil fill\n" + " result = vec4(1,1,1,1);\n" + " } else if (type == 3) { // Textured tris\n" + "#ifdef NANOVG_GL3\n" + " vec4 color = texture(tex, ftcoord);\n" + "#else\n" + " vec4 color = texture2D(tex, ftcoord);\n" + "#endif\n" + " if (texType == 1) color = vec4(color.xyz*color.w,color.w);" + " if (texType == 2) color = vec4(color.x);" + " color *= scissor;\n" + " result = color * innerCol;\n" + " }\n" + "#ifdef NANOVG_GL3\n" + " outColor = result;\n" + "#else\n" + " gl_FragColor = result;\n" + "#endif\n" + "}\n"; + + glnvg__checkError(gl, "init"); + + if (gl->flags & NVG_ANTIALIAS) { + if (glnvg__createShader(&gl->shader, "shader", shaderHeader, "#define EDGE_AA 1\n", fillVertShader, fillFragShader) == 0) + return 0; + } else { + if (glnvg__createShader(&gl->shader, "shader", shaderHeader, NULL, fillVertShader, fillFragShader) == 0) + return 0; + } + + glnvg__checkError(gl, "uniform locations"); + glnvg__getUniforms(&gl->shader); + + // Create dynamic vertex array +#if defined NANOVG_GL3 + glGenVertexArrays(1, &gl->vertArr); +#endif + glGenBuffers(1, &gl->vertBuf); + +#if NANOVG_GL_USE_UNIFORMBUFFER + // Create UBOs + glUniformBlockBinding(gl->shader.prog, gl->shader.loc[GLNVG_LOC_FRAG], GLNVG_FRAG_BINDING); + glGenBuffers(1, &gl->fragBuf); + glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &align); +#endif + gl->fragSize = sizeof(GLNVGfragUniforms) + align - sizeof(GLNVGfragUniforms) % align; + + // Some platforms does not allow to have samples to unset textures. + // Create empty one which is bound when there's no texture specified. + gl->dummyTex = glnvg__renderCreateTexture(gl, NVG_TEXTURE_ALPHA, 1, 1, 0, NULL); + + glnvg__checkError(gl, "create done"); + + glFinish(); + + return 1; +} + +static int glnvg__renderCreateTexture(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data) +{ + GLNVGcontext* gl = (GLNVGcontext*)uptr; + GLNVGtexture* tex = glnvg__allocTexture(gl); + + if (tex == NULL) return 0; + + printf("CreateTexture: Data is null %d\n", (data == NULL)); + +#ifdef NANOVG_GLES2 + // Check for non-power of 2. + if (glnvg__nearestPow2(w) != (unsigned int)w || glnvg__nearestPow2(h) != (unsigned int)h) { + // No repeat + if ((imageFlags & NVG_IMAGE_REPEATX) != 0 || (imageFlags & NVG_IMAGE_REPEATY) != 0) { + printf("Repeat X/Y is not supported for non power-of-two textures (%d x %d)\n", w, h); + imageFlags &= ~(NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY); + } + // No mips. + if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) { + printf("Mip-maps is not support for non power-of-two textures (%d x %d)\n", w, h); + imageFlags &= ~NVG_IMAGE_GENERATE_MIPMAPS; + } + } +#endif + + glGenTextures(1, &tex->tex); + tex->width = w; + tex->height = h; + tex->type = type; + tex->flags = imageFlags; + glnvg__bindTexture(gl, tex->tex); + + glPixelStorei(GL_UNPACK_ALIGNMENT,1); +#ifndef NANOVG_GLES2 + glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->width); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); +#endif + +#if defined (NANOVG_GL2) + // GL 1.4 and later has support for generating mipmaps using a tex parameter. + if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) { + glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); + } +#endif + + if (type == NVG_TEXTURE_RGBA) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + else +#if defined(NANOVG_GLES2) || defined (NANOVG_GL2) + glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w, h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); +#elif defined(NANOVG_GLES3) + glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, data); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, data); +#endif + + if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) { + if (imageFlags & NVG_IMAGE_NEAREST) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } + } else { + if (imageFlags & NVG_IMAGE_NEAREST) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + } + + if (imageFlags & NVG_IMAGE_NEAREST) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + + if (imageFlags & NVG_IMAGE_REPEATX) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + else + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + + if (imageFlags & NVG_IMAGE_REPEATY) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + else + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); +#ifndef NANOVG_GLES2 + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); +#endif + + // The new way to build mipmaps on GLES and GL3 +#if !defined(NANOVG_GL2) + if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) { + glGenerateMipmap(GL_TEXTURE_2D); + } +#endif + + glnvg__checkError(gl, "create tex"); + glnvg__bindTexture(gl, 0); + + return tex->id; +} + + +static int glnvg__renderDeleteTexture(void* uptr, int image) +{ + GLNVGcontext* gl = (GLNVGcontext*)uptr; + return glnvg__deleteTexture(gl, image); +} + +static int glnvg__renderUpdateTexture(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data) +{ + GLNVGcontext* gl = (GLNVGcontext*)uptr; + GLNVGtexture* tex = glnvg__findTexture(gl, image); + + if (tex == NULL) return 0; + glnvg__bindTexture(gl, tex->tex); + + glPixelStorei(GL_UNPACK_ALIGNMENT,1); + +#ifndef NANOVG_GLES2 + glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->width); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, x); + glPixelStorei(GL_UNPACK_SKIP_ROWS, y); +#else + // No support for all of skip, need to update a whole row at a time. + if (tex->type == NVG_TEXTURE_RGBA) + data += y*tex->width*4; + else + data += y*tex->width; + x = 0; + w = tex->width; +#endif + + if (tex->type == NVG_TEXTURE_RGBA) + glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RGBA, GL_UNSIGNED_BYTE, data); + else +#if defined(NANOVG_GLES2) || defined(NANOVG_GL2) + glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); +#else + glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RED, GL_UNSIGNED_BYTE, data); +#endif + + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); +#ifndef NANOVG_GLES2 + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); +#endif + + glnvg__bindTexture(gl, 0); + + return 1; +} + +static int glnvg__renderGetTextureSize(void* uptr, int image, int* w, int* h) +{ + GLNVGcontext* gl = (GLNVGcontext*)uptr; + GLNVGtexture* tex = glnvg__findTexture(gl, image); + if (tex == NULL) return 0; + *w = tex->width; + *h = tex->height; + return 1; +} + +static void glnvg__xformToMat3x4(float* m3, float* t) +{ + m3[0] = t[0]; + m3[1] = t[1]; + m3[2] = 0.0f; + m3[3] = 0.0f; + m3[4] = t[2]; + m3[5] = t[3]; + m3[6] = 0.0f; + m3[7] = 0.0f; + m3[8] = t[4]; + m3[9] = t[5]; + m3[10] = 1.0f; + m3[11] = 0.0f; +} + +static NVGcolor glnvg__premulColor(NVGcolor c) +{ + c.r *= c.a; + c.g *= c.a; + c.b *= c.a; + return c; +} + +static int glnvg__convertPaint(GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGpaint* paint, + NVGscissor* scissor, float width, float fringe, float strokeThr) +{ + GLNVGtexture* tex = NULL; + float invxform[6]; + + memset(frag, 0, sizeof(*frag)); + + frag->innerCol = glnvg__premulColor(paint->innerColor); + frag->outerCol = glnvg__premulColor(paint->outerColor); + + if (scissor->extent[0] < -0.5f || scissor->extent[1] < -0.5f) { + memset(frag->scissorMat, 0, sizeof(frag->scissorMat)); + frag->scissorExt[0] = 1.0f; + frag->scissorExt[1] = 1.0f; + frag->scissorScale[0] = 1.0f; + frag->scissorScale[1] = 1.0f; + } else { + nvgTransformInverse(invxform, scissor->xform); + glnvg__xformToMat3x4(frag->scissorMat, invxform); + frag->scissorExt[0] = scissor->extent[0]; + frag->scissorExt[1] = scissor->extent[1]; + frag->scissorScale[0] = sqrtf(scissor->xform[0]*scissor->xform[0] + scissor->xform[2]*scissor->xform[2]) / fringe; + frag->scissorScale[1] = sqrtf(scissor->xform[1]*scissor->xform[1] + scissor->xform[3]*scissor->xform[3]) / fringe; + } + + memcpy(frag->extent, paint->extent, sizeof(frag->extent)); + frag->strokeMult = (width*0.5f + fringe*0.5f) / fringe; + frag->strokeThr = strokeThr; + + if (paint->image != 0) { + tex = glnvg__findTexture(gl, paint->image); + if (tex == NULL) return 0; + if ((tex->flags & NVG_IMAGE_FLIPY) != 0) { + float m1[6], m2[6]; + nvgTransformTranslate(m1, 0.0f, frag->extent[1] * 0.5f); + nvgTransformMultiply(m1, paint->xform); + nvgTransformScale(m2, 1.0f, -1.0f); + nvgTransformMultiply(m2, m1); + nvgTransformTranslate(m1, 0.0f, -frag->extent[1] * 0.5f); + nvgTransformMultiply(m1, m2); + nvgTransformInverse(invxform, m1); + } else { + nvgTransformInverse(invxform, paint->xform); + } + frag->type = NSVG_SHADER_FILLIMG; + + #if NANOVG_GL_USE_UNIFORMBUFFER + if (tex->type == NVG_TEXTURE_RGBA) + frag->texType = (tex->flags & NVG_IMAGE_PREMULTIPLIED) ? 0 : 1; + else + frag->texType = 2; + #else + if (tex->type == NVG_TEXTURE_RGBA) + frag->texType = (tex->flags & NVG_IMAGE_PREMULTIPLIED) ? 0.0f : 1.0f; + else + frag->texType = 2.0f; + #endif +// printf("frag->texType = %d\n", frag->texType); + } else { + frag->type = NSVG_SHADER_FILLGRAD; + frag->radius = paint->radius; + frag->feather = paint->feather; + nvgTransformInverse(invxform, paint->xform); + } + + glnvg__xformToMat3x4(frag->paintMat, invxform); + + return 1; +} + +static GLNVGfragUniforms* nvg__fragUniformPtr(GLNVGcontext* gl, int i); + +static void glnvg__setUniforms(GLNVGcontext* gl, int uniformOffset, int image) +{ + GLNVGtexture* tex = NULL; +#if NANOVG_GL_USE_UNIFORMBUFFER + glBindBufferRange(GL_UNIFORM_BUFFER, GLNVG_FRAG_BINDING, gl->fragBuf, uniformOffset, sizeof(GLNVGfragUniforms)); +#else + GLNVGfragUniforms* frag = nvg__fragUniformPtr(gl, uniformOffset); + glUniform4fv(gl->shader.loc[GLNVG_LOC_FRAG], NANOVG_GL_UNIFORMARRAY_SIZE, &(frag->uniformArray[0][0])); +#endif + + if (image != 0) { + tex = glnvg__findTexture(gl, image); + } + // If no image is set, use empty texture + if (tex == NULL) { + tex = glnvg__findTexture(gl, gl->dummyTex); + } + glnvg__bindTexture(gl, tex != NULL ? tex->tex : 0); + glnvg__checkError(gl, "tex paint tex"); +} + +static void glnvg__renderViewport(void* uptr, float width, float height, float devicePixelRatio) +{ + NVG_NOTUSED(devicePixelRatio); + GLNVGcontext* gl = (GLNVGcontext*)uptr; + gl->view[0] = width; + gl->view[1] = height; +} + +static void glnvg__fill(GLNVGcontext* gl, GLNVGcall* call) +{ + GLNVGpath* paths = &gl->paths[call->pathOffset]; + int i, npaths = call->pathCount; + + // Draw shapes + glEnable(GL_STENCIL_TEST); + glnvg__stencilMask(gl, 0xff); + glnvg__stencilFunc(gl, GL_ALWAYS, 0, 0xff); + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + + // set bindpoint for solid loc + glnvg__setUniforms(gl, call->uniformOffset, 0); + glnvg__checkError(gl, "fill simple"); + + glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_INCR_WRAP); + glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_DECR_WRAP); + glDisable(GL_CULL_FACE); + for (i = 0; i < npaths; i++) + glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); + glEnable(GL_CULL_FACE); + + // Draw anti-aliased pixels + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + glnvg__setUniforms(gl, call->uniformOffset + gl->fragSize, call->image); + glnvg__checkError(gl, "fill fill"); + + if (gl->flags & NVG_ANTIALIAS) { + glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + // Draw fringes + for (i = 0; i < npaths; i++) + glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); + } + + // Draw fill + glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xff); + glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); + glDrawArrays(GL_TRIANGLE_STRIP, call->triangleOffset, call->triangleCount); + + glDisable(GL_STENCIL_TEST); +} + +static void glnvg__convexFill(GLNVGcontext* gl, GLNVGcall* call) +{ + GLNVGpath* paths = &gl->paths[call->pathOffset]; + int i, npaths = call->pathCount; + + glnvg__setUniforms(gl, call->uniformOffset, call->image); + glnvg__checkError(gl, "convex fill"); + + for (i = 0; i < npaths; i++) { + glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); + // Draw fringes + if (paths[i].strokeCount > 0) { + glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); + } + } +} + +static void glnvg__stroke(GLNVGcontext* gl, GLNVGcall* call) +{ + GLNVGpath* paths = &gl->paths[call->pathOffset]; + int npaths = call->pathCount, i; + + if (gl->flags & NVG_STENCIL_STROKES) { + + glEnable(GL_STENCIL_TEST); + glnvg__stencilMask(gl, 0xff); + + // Fill the stroke base without overlap + glnvg__stencilFunc(gl, GL_EQUAL, 0x0, 0xff); + glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); + glnvg__setUniforms(gl, call->uniformOffset + gl->fragSize, call->image); + glnvg__checkError(gl, "stroke fill 0"); + for (i = 0; i < npaths; i++) + glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); + + // Draw anti-aliased pixels. + glnvg__setUniforms(gl, call->uniformOffset, call->image); + glnvg__stencilFunc(gl, GL_EQUAL, 0x00, 0xff); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + for (i = 0; i < npaths; i++) + glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); + + // Clear stencil buffer. + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff); + glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); + glnvg__checkError(gl, "stroke fill 1"); + for (i = 0; i < npaths; i++) { + glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); + } + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + + glDisable(GL_STENCIL_TEST); + +// glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset + gl->fragSize), paint, scissor, strokeWidth, fringe, 1.0f - 0.5f/255.0f); + + } else { + glnvg__setUniforms(gl, call->uniformOffset, call->image); + glnvg__checkError(gl, "stroke fill"); + // Draw Strokes + for (i = 0; i < npaths; i++) + glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); + } +} + +static void glnvg__triangles(GLNVGcontext* gl, GLNVGcall* call) +{ + glnvg__setUniforms(gl, call->uniformOffset, call->image); + glnvg__checkError(gl, "triangles fill"); + + glDrawArrays(GL_TRIANGLES, call->triangleOffset, call->triangleCount); +} + +static void glnvg__renderCancel(void* uptr) { + GLNVGcontext* gl = (GLNVGcontext*)uptr; + gl->nverts = 0; + gl->npaths = 0; + gl->ncalls = 0; + gl->nuniforms = 0; +} + +static GLenum glnvg_convertBlendFuncFactor(int factor) +{ + if (factor == NVG_ZERO) + return GL_ZERO; + if (factor == NVG_ONE) + return GL_ONE; + if (factor == NVG_SRC_COLOR) + return GL_SRC_COLOR; + if (factor == NVG_ONE_MINUS_SRC_COLOR) + return GL_ONE_MINUS_SRC_COLOR; + if (factor == NVG_DST_COLOR) + return GL_DST_COLOR; + if (factor == NVG_ONE_MINUS_DST_COLOR) + return GL_ONE_MINUS_DST_COLOR; + if (factor == NVG_SRC_ALPHA) + return GL_SRC_ALPHA; + if (factor == NVG_ONE_MINUS_SRC_ALPHA) + return GL_ONE_MINUS_SRC_ALPHA; + if (factor == NVG_DST_ALPHA) + return GL_DST_ALPHA; + if (factor == NVG_ONE_MINUS_DST_ALPHA) + return GL_ONE_MINUS_DST_ALPHA; + if (factor == NVG_SRC_ALPHA_SATURATE) + return GL_SRC_ALPHA_SATURATE; + return GL_INVALID_ENUM; +} + +static GLNVGblend glnvg__blendCompositeOperation(NVGcompositeOperationState op) +{ + GLNVGblend blend; + blend.srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB); + blend.dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB); + blend.srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha); + blend.dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha); + if (blend.srcRGB == GL_INVALID_ENUM || blend.dstRGB == GL_INVALID_ENUM || blend.srcAlpha == GL_INVALID_ENUM || blend.dstAlpha == GL_INVALID_ENUM) + { + blend.srcRGB = GL_ONE; + blend.dstRGB = GL_ONE_MINUS_SRC_ALPHA; + blend.srcAlpha = GL_ONE; + blend.dstAlpha = GL_ONE_MINUS_SRC_ALPHA; + } + return blend; +} + +static void glnvg__renderFlush(void* uptr) +{ + GLNVGcontext* gl = (GLNVGcontext*)uptr; + int i; + + if (gl->ncalls > 0) { + + // Setup require GL state. + glUseProgram(gl->shader.prog); + + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + glFrontFace(GL_CCW); + glEnable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glDisable(GL_SCISSOR_TEST); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glStencilMask(0xffffffff); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + glStencilFunc(GL_ALWAYS, 0, 0xffffffff); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); + #if NANOVG_GL_USE_STATE_FILTER + gl->boundTexture = 0; + gl->stencilMask = 0xffffffff; + gl->stencilFunc = GL_ALWAYS; + gl->stencilFuncRef = 0; + gl->stencilFuncMask = 0xffffffff; + gl->blendFunc.srcRGB = GL_INVALID_ENUM; + gl->blendFunc.srcAlpha = GL_INVALID_ENUM; + gl->blendFunc.dstRGB = GL_INVALID_ENUM; + gl->blendFunc.dstAlpha = GL_INVALID_ENUM; + #endif + +#if NANOVG_GL_USE_UNIFORMBUFFER + // Upload ubo for frag shaders + glBindBuffer(GL_UNIFORM_BUFFER, gl->fragBuf); + glBufferData(GL_UNIFORM_BUFFER, gl->nuniforms * gl->fragSize, gl->uniforms, GL_STREAM_DRAW); +#endif + + // Upload vertex data +#if defined NANOVG_GL3 + glBindVertexArray(gl->vertArr); +#endif + glBindBuffer(GL_ARRAY_BUFFER, gl->vertBuf); + glBufferData(GL_ARRAY_BUFFER, gl->nverts * sizeof(NVGvertex), gl->verts, GL_STREAM_DRAW); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(NVGvertex), (const GLvoid*)(size_t)0); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(NVGvertex), (const GLvoid*)(0 + 2*sizeof(float))); + + // Set view and texture just once per frame. + glUniform1i(gl->shader.loc[GLNVG_LOC_TEX], 0); + glUniform2fv(gl->shader.loc[GLNVG_LOC_VIEWSIZE], 1, gl->view); + +#if NANOVG_GL_USE_UNIFORMBUFFER + glBindBuffer(GL_UNIFORM_BUFFER, gl->fragBuf); +#endif + + for (i = 0; i < gl->ncalls; i++) { + GLNVGcall* call = &gl->calls[i]; + glnvg__blendFuncSeparate(gl,&call->blendFunc); + if (call->type == GLNVG_FILL) { + glnvg__fill(gl, call); + } + else if (call->type == GLNVG_CONVEXFILL) { + glnvg__convexFill(gl, call); + } + else if (call->type == GLNVG_STROKE) { + glnvg__stroke(gl, call); + } + else if (call->type == GLNVG_TRIANGLES) { + glnvg__triangles(gl, call); + } + } + + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); +#if defined NANOVG_GL3 + glBindVertexArray(0); +#endif + glDisable(GL_CULL_FACE); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glUseProgram(0); + glnvg__bindTexture(gl, 0); + } + + // Reset calls + gl->nverts = 0; + gl->npaths = 0; + gl->ncalls = 0; + gl->nuniforms = 0; +} + +static int glnvg__maxVertCount(const NVGpath* paths, int npaths) +{ + int i, count = 0; + for (i = 0; i < npaths; i++) { + count += paths[i].nfill; + count += paths[i].nstroke; + } + return count; +} + +static GLNVGcall* glnvg__allocCall(GLNVGcontext* gl) +{ + GLNVGcall* ret = NULL; + if (gl->ncalls+1 > gl->ccalls) { + GLNVGcall* calls; + int ccalls = glnvg__maxi(gl->ncalls+1, 128) + gl->ccalls/2; // 1.5x Overallocate + calls = (GLNVGcall*)realloc(gl->calls, sizeof(GLNVGcall) * ccalls); + if (calls == NULL) return NULL; + gl->calls = calls; + gl->ccalls = ccalls; + } + ret = &gl->calls[gl->ncalls++]; + memset(ret, 0, sizeof(GLNVGcall)); + return ret; +} + +static int glnvg__allocPaths(GLNVGcontext* gl, int n) +{ + int ret = 0; + if (gl->npaths+n > gl->cpaths) { + GLNVGpath* paths; + int cpaths = glnvg__maxi(gl->npaths + n, 128) + gl->cpaths/2; // 1.5x Overallocate + paths = (GLNVGpath*)realloc(gl->paths, sizeof(GLNVGpath) * cpaths); + if (paths == NULL) return -1; + gl->paths = paths; + gl->cpaths = cpaths; + } + ret = gl->npaths; + gl->npaths += n; + return ret; +} + +static int glnvg__allocVerts(GLNVGcontext* gl, int n) +{ + int ret = 0; + if (gl->nverts+n > gl->cverts) { + NVGvertex* verts; + int cverts = glnvg__maxi(gl->nverts + n, 4096) + gl->cverts/2; // 1.5x Overallocate + verts = (NVGvertex*)realloc(gl->verts, sizeof(NVGvertex) * cverts); + if (verts == NULL) return -1; + gl->verts = verts; + gl->cverts = cverts; + } + ret = gl->nverts; + gl->nverts += n; + return ret; +} + +static int glnvg__allocFragUniforms(GLNVGcontext* gl, int n) +{ + int ret = 0, structSize = gl->fragSize; + if (gl->nuniforms+n > gl->cuniforms) { + unsigned char* uniforms; + int cuniforms = glnvg__maxi(gl->nuniforms+n, 128) + gl->cuniforms/2; // 1.5x Overallocate + uniforms = (unsigned char*)realloc(gl->uniforms, structSize * cuniforms); + if (uniforms == NULL) return -1; + gl->uniforms = uniforms; + gl->cuniforms = cuniforms; + } + ret = gl->nuniforms * structSize; + gl->nuniforms += n; + return ret; +} + +static GLNVGfragUniforms* nvg__fragUniformPtr(GLNVGcontext* gl, int i) +{ + return (GLNVGfragUniforms*)&gl->uniforms[i]; +} + +static void glnvg__vset(NVGvertex* vtx, float x, float y, float u, float v) +{ + vtx->x = x; + vtx->y = y; + vtx->u = u; + vtx->v = v; +} + +static void glnvg__renderFill(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe, + const float* bounds, const NVGpath* paths, int npaths) +{ + GLNVGcontext* gl = (GLNVGcontext*)uptr; + GLNVGcall* call = glnvg__allocCall(gl); + NVGvertex* quad; + GLNVGfragUniforms* frag; + int i, maxverts, offset; + + if (call == NULL) return; + + call->type = GLNVG_FILL; + call->triangleCount = 4; + call->pathOffset = glnvg__allocPaths(gl, npaths); + if (call->pathOffset == -1) goto error; + call->pathCount = npaths; + call->image = paint->image; + call->blendFunc = glnvg__blendCompositeOperation(compositeOperation); + + if (npaths == 1 && paths[0].convex) + { + call->type = GLNVG_CONVEXFILL; + call->triangleCount = 0; // Bounding box fill quad not needed for convex fill + } + + // Allocate vertices for all the paths. + maxverts = glnvg__maxVertCount(paths, npaths) + call->triangleCount; + offset = glnvg__allocVerts(gl, maxverts); + if (offset == -1) goto error; + + for (i = 0; i < npaths; i++) { + GLNVGpath* copy = &gl->paths[call->pathOffset + i]; + const NVGpath* path = &paths[i]; + memset(copy, 0, sizeof(GLNVGpath)); + if (path->nfill > 0) { + copy->fillOffset = offset; + copy->fillCount = path->nfill; + memcpy(&gl->verts[offset], path->fill, sizeof(NVGvertex) * path->nfill); + offset += path->nfill; + } + if (path->nstroke > 0) { + copy->strokeOffset = offset; + copy->strokeCount = path->nstroke; + memcpy(&gl->verts[offset], path->stroke, sizeof(NVGvertex) * path->nstroke); + offset += path->nstroke; + } + } + + // Setup uniforms for draw calls + if (call->type == GLNVG_FILL) { + // Quad + call->triangleOffset = offset; + quad = &gl->verts[call->triangleOffset]; + glnvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f); + glnvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f); + glnvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f); + glnvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f); + + call->uniformOffset = glnvg__allocFragUniforms(gl, 2); + if (call->uniformOffset == -1) goto error; + // Simple shader for stencil + frag = nvg__fragUniformPtr(gl, call->uniformOffset); + memset(frag, 0, sizeof(*frag)); + frag->strokeThr = -1.0f; + frag->type = NSVG_SHADER_SIMPLE; + // Fill shader + glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset + gl->fragSize), paint, scissor, fringe, fringe, -1.0f); + } else { + call->uniformOffset = glnvg__allocFragUniforms(gl, 1); + if (call->uniformOffset == -1) goto error; + // Fill shader + glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset), paint, scissor, fringe, fringe, -1.0f); + } + + return; + +error: + // We get here if call alloc was ok, but something else is not. + // Roll back the last call to prevent drawing it. + if (gl->ncalls > 0) gl->ncalls--; +} + +static void glnvg__renderStroke(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe, + float strokeWidth, const NVGpath* paths, int npaths) +{ + GLNVGcontext* gl = (GLNVGcontext*)uptr; + GLNVGcall* call = glnvg__allocCall(gl); + int i, maxverts, offset; + + if (call == NULL) return; + + call->type = GLNVG_STROKE; + call->pathOffset = glnvg__allocPaths(gl, npaths); + if (call->pathOffset == -1) goto error; + call->pathCount = npaths; + call->image = paint->image; + call->blendFunc = glnvg__blendCompositeOperation(compositeOperation); + + // Allocate vertices for all the paths. + maxverts = glnvg__maxVertCount(paths, npaths); + offset = glnvg__allocVerts(gl, maxverts); + if (offset == -1) goto error; + + for (i = 0; i < npaths; i++) { + GLNVGpath* copy = &gl->paths[call->pathOffset + i]; + const NVGpath* path = &paths[i]; + memset(copy, 0, sizeof(GLNVGpath)); + if (path->nstroke) { + copy->strokeOffset = offset; + copy->strokeCount = path->nstroke; + memcpy(&gl->verts[offset], path->stroke, sizeof(NVGvertex) * path->nstroke); + offset += path->nstroke; + } + } + + if (gl->flags & NVG_STENCIL_STROKES) { + // Fill shader + call->uniformOffset = glnvg__allocFragUniforms(gl, 2); + if (call->uniformOffset == -1) goto error; + + glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); + glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset + gl->fragSize), paint, scissor, strokeWidth, fringe, 1.0f - 0.5f/255.0f); + + } else { + // Fill shader + call->uniformOffset = glnvg__allocFragUniforms(gl, 1); + if (call->uniformOffset == -1) goto error; + glnvg__convertPaint(gl, nvg__fragUniformPtr(gl, call->uniformOffset), paint, scissor, strokeWidth, fringe, -1.0f); + } + + return; + +error: + // We get here if call alloc was ok, but something else is not. + // Roll back the last call to prevent drawing it. + if (gl->ncalls > 0) gl->ncalls--; +} + +static void glnvg__renderTriangles(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, + const NVGvertex* verts, int nverts, float fringe) +{ + GLNVGcontext* gl = (GLNVGcontext*)uptr; + GLNVGcall* call = glnvg__allocCall(gl); + GLNVGfragUniforms* frag; + + if (call == NULL) return; + + call->type = GLNVG_TRIANGLES; + call->image = paint->image; + call->blendFunc = glnvg__blendCompositeOperation(compositeOperation); + + // Allocate vertices for all the paths. + call->triangleOffset = glnvg__allocVerts(gl, nverts); + if (call->triangleOffset == -1) goto error; + call->triangleCount = nverts; + + memcpy(&gl->verts[call->triangleOffset], verts, sizeof(NVGvertex) * nverts); + + // Fill shader + call->uniformOffset = glnvg__allocFragUniforms(gl, 1); + if (call->uniformOffset == -1) goto error; + frag = nvg__fragUniformPtr(gl, call->uniformOffset); + glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, fringe, -1.0f); + frag->type = NSVG_SHADER_IMG; + + return; + +error: + // We get here if call alloc was ok, but something else is not. + // Roll back the last call to prevent drawing it. + if (gl->ncalls > 0) gl->ncalls--; +} + +static void glnvg__renderDelete(void* uptr) +{ + GLNVGcontext* gl = (GLNVGcontext*)uptr; + int i; + if (gl == NULL) return; + + glnvg__deleteShader(&gl->shader); + +#if NANOVG_GL3 +#if NANOVG_GL_USE_UNIFORMBUFFER + if (gl->fragBuf != 0) + glDeleteBuffers(1, &gl->fragBuf); +#endif + if (gl->vertArr != 0) + glDeleteVertexArrays(1, &gl->vertArr); +#endif + if (gl->vertBuf != 0) + glDeleteBuffers(1, &gl->vertBuf); + + for (i = 0; i < gl->ntextures; i++) { + if (gl->textures[i].tex != 0 && (gl->textures[i].flags & NVG_IMAGE_NODELETE) == 0) + glDeleteTextures(1, &gl->textures[i].tex); + } + free(gl->textures); + + free(gl->paths); + free(gl->verts); + free(gl->uniforms); + free(gl->calls); + + free(gl); +} + + +#if defined NANOVG_GL2 +NVGcontext* nvgCreateGL2(int flags) +#elif defined NANOVG_GL3 +NVGcontext* nvgCreateGL3(int flags) +#elif defined NANOVG_GLES2 +NVGcontext* nvgCreateGLES2(int flags) +#elif defined NANOVG_GLES3 +NVGcontext* nvgCreateGLES3(int flags) +#endif +{ + NVGparams params; + NVGcontext* ctx = NULL; + GLNVGcontext* gl = (GLNVGcontext*)malloc(sizeof(GLNVGcontext)); + if (gl == NULL) goto error; + memset(gl, 0, sizeof(GLNVGcontext)); + + memset(¶ms, 0, sizeof(params)); + params.renderCreate = glnvg__renderCreate; + params.renderCreateTexture = glnvg__renderCreateTexture; + params.renderDeleteTexture = glnvg__renderDeleteTexture; + params.renderUpdateTexture = glnvg__renderUpdateTexture; + params.renderGetTextureSize = glnvg__renderGetTextureSize; + params.renderViewport = glnvg__renderViewport; + params.renderCancel = glnvg__renderCancel; + params.renderFlush = glnvg__renderFlush; + params.renderFill = glnvg__renderFill; + params.renderStroke = glnvg__renderStroke; + params.renderTriangles = glnvg__renderTriangles; + params.renderDelete = glnvg__renderDelete; + params.userPtr = gl; + params.edgeAntiAlias = flags & NVG_ANTIALIAS ? 1 : 0; + + gl->flags = flags; + + ctx = nvgCreateInternal(¶ms); + if (ctx == NULL) goto error; + + return ctx; + +error: + // 'gl' is freed by nvgDeleteInternal. + if (ctx != NULL) nvgDeleteInternal(ctx); + return NULL; +} + +#if defined NANOVG_GL2 +void nvgDeleteGL2(NVGcontext* ctx) +#elif defined NANOVG_GL3 +void nvgDeleteGL3(NVGcontext* ctx) +#elif defined NANOVG_GLES2 +void nvgDeleteGLES2(NVGcontext* ctx) +#elif defined NANOVG_GLES3 +void nvgDeleteGLES3(NVGcontext* ctx) +#endif +{ + nvgDeleteInternal(ctx); +} + +#if defined NANOVG_GL2 +int nvglCreateImageFromHandleGL2(NVGcontext* ctx, GLuint textureId, int w, int h, int imageFlags) +#elif defined NANOVG_GL3 +int nvglCreateImageFromHandleGL3(NVGcontext* ctx, GLuint textureId, int w, int h, int imageFlags) +#elif defined NANOVG_GLES2 +int nvglCreateImageFromHandleGLES2(NVGcontext* ctx, GLuint textureId, int w, int h, int imageFlags) +#elif defined NANOVG_GLES3 +int nvglCreateImageFromHandleGLES3(NVGcontext* ctx, GLuint textureId, int w, int h, int imageFlags) +#endif +{ + GLNVGcontext* gl = (GLNVGcontext*)nvgInternalParams(ctx)->userPtr; + GLNVGtexture* tex = glnvg__allocTexture(gl); + + if (tex == NULL) return 0; + + tex->type = NVG_TEXTURE_RGBA; + tex->tex = textureId; + tex->flags = imageFlags; + tex->width = w; + tex->height = h; + + return tex->id; +} + +#if defined NANOVG_GL2 +GLuint nvglImageHandleGL2(NVGcontext* ctx, int image) +#elif defined NANOVG_GL3 +GLuint nvglImageHandleGL3(NVGcontext* ctx, int image) +#elif defined NANOVG_GLES2 +GLuint nvglImageHandleGLES2(NVGcontext* ctx, int image) +#elif defined NANOVG_GLES3 +GLuint nvglImageHandleGLES3(NVGcontext* ctx, int image) +#endif +{ + GLNVGcontext* gl = (GLNVGcontext*)nvgInternalParams(ctx)->userPtr; + GLNVGtexture* tex = glnvg__findTexture(gl, image); + return tex->tex; +} + +#endif /* USE_OPENGL */ + +#endif /* NANOVG_GL_IMPLEMENTATION */ diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/shaders/fill_aa_fsh.glsl b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/shaders/fill_aa_fsh.glsl new file mode 100644 index 00000000..01604450 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/shaders/fill_aa_fsh.glsl @@ -0,0 +1,83 @@ +#version 460 + +layout(binding = 0) uniform sampler2D tex; + +layout(std140, binding = 0) uniform frag { + mat3 scissorMat; + mat3 paintMat; + vec4 innerCol; + vec4 outerCol; + vec2 scissorExt; + vec2 scissorScale; + vec2 extent; + float radius; + float feather; + float strokeMult; + float strokeThr; + int texType; + int type; +}; + +layout(location = 0) in vec2 ftcoord; +layout(location = 1) in vec2 fpos; +layout(location = 0) out vec4 outColor; + +float sdroundrect(vec2 pt, vec2 ext, float rad) { + vec2 ext2 = ext - vec2(rad,rad); + vec2 d = abs(pt) - ext2; + return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rad; +} + +// Scissoring +float scissorMask(vec2 p) { + vec2 sc = (abs((scissorMat * vec3(p,1.0)).xy) - scissorExt); + sc = vec2(0.5,0.5) - sc * scissorScale; + return clamp(sc.x,0.0,1.0) * clamp(sc.y,0.0,1.0); +} + +// Stroke - from [0..1] to clipped pyramid, where the slope is 1px. +float strokeMask() { + return min(1.0, (1.0-abs(ftcoord.x*2.0-1.0))*strokeMult) * min(1.0, ftcoord.y); +} + +void main(void) { + vec4 result; + float scissor = scissorMask(fpos); + float strokeAlpha = strokeMask(); + + if (strokeAlpha < strokeThr) discard; + + if (type == 0) { // Gradient + // Calculate gradient color using box gradient + vec2 pt = (paintMat * vec3(fpos,1.0)).xy; + float d = clamp((sdroundrect(pt, extent, radius) + feather*0.5) / feather, 0.0, 1.0); + vec4 color = mix(innerCol,outerCol,d); + // Combine alpha + color *= strokeAlpha * scissor; + result = color; + } else if (type == 1) { // Image + // Calculate color fron texture + vec2 pt = (paintMat * vec3(fpos,1.0)).xy / extent; + vec4 color = texture(tex, pt); + + if (texType == 1) color = vec4(color.xyz*color.w,color.w); + if (texType == 2) color = vec4(color.x); + // Apply color tint and alpha. + color *= innerCol; + // Combine alpha + color *= strokeAlpha * scissor; + result = color; + } else if (type == 2) { // Stencil fill + result = vec4(1,1,1,1); + } else if (type == 3) { // Textured tris + + vec4 color = texture(tex, ftcoord); + + if (texType == 1) color = vec4(color.xyz*color.w,color.w); + if (texType == 2) color = vec4(color.x); + color *= scissor; + result = color * innerCol; + } + + outColor = result; +}; \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/shaders/fill_fsh.glsl b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/shaders/fill_fsh.glsl new file mode 100644 index 00000000..6b6e2cc8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/shaders/fill_fsh.glsl @@ -0,0 +1,76 @@ +#version 460 + +layout(binding = 0) uniform sampler2D tex; + +layout(std140, binding = 0) uniform frag { + mat3 scissorMat; + mat3 paintMat; + vec4 innerCol; + vec4 outerCol; + vec2 scissorExt; + vec2 scissorScale; + vec2 extent; + float radius; + float feather; + float strokeMult; + float strokeThr; + int texType; + int type; +}; + +layout(location = 0) in vec2 ftcoord; +layout(location = 1) in vec2 fpos; +layout(location = 0) out vec4 outColor; + +float sdroundrect(vec2 pt, vec2 ext, float rad) { + vec2 ext2 = ext - vec2(rad,rad); + vec2 d = abs(pt) - ext2; + return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rad; +} + +// Scissoring +float scissorMask(vec2 p) { + vec2 sc = (abs((scissorMat * vec3(p,1.0)).xy) - scissorExt); + sc = vec2(0.5,0.5) - sc * scissorScale; + return clamp(sc.x,0.0,1.0) * clamp(sc.y,0.0,1.0); +} + +void main(void) { + vec4 result; + float scissor = scissorMask(fpos); + float strokeAlpha = 1.0; + + if (type == 0) { // Gradient + // Calculate gradient color using box gradient + vec2 pt = (paintMat * vec3(fpos,1.0)).xy; + float d = clamp((sdroundrect(pt, extent, radius) + feather*0.5) / feather, 0.0, 1.0); + vec4 color = mix(innerCol,outerCol,d); + // Combine alpha + color *= strokeAlpha * scissor; + result = color; + } else if (type == 1) { // Image + // Calculate color fron texture + vec2 pt = (paintMat * vec3(fpos,1.0)).xy / extent; + vec4 color = texture(tex, pt); + + if (texType == 1) color = vec4(color.xyz*color.w,color.w); + if (texType == 2) color = vec4(color.x); + // Apply color tint and alpha. + color *= innerCol; + // Combine alpha + color *= strokeAlpha * scissor; + result = color; + } else if (type == 2) { // Stencil fill + result = vec4(1,1,1,1); + } else if (type == 3) { // Textured tris + + vec4 color = texture(tex, ftcoord); + + if (texType == 1) color = vec4(color.xyz*color.w,color.w); + if (texType == 2) color = vec4(color.x); + color *= scissor; + result = color * innerCol; + } + + outColor = result; +}; \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/shaders/fill_vsh.glsl b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/shaders/fill_vsh.glsl new file mode 100644 index 00000000..1e711a71 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/shaders/fill_vsh.glsl @@ -0,0 +1,17 @@ +#version 460 + +layout (location = 0) in vec2 vertex; +layout (location = 1) in vec2 tcoord; +layout (location = 0) out vec2 ftcoord; +layout (location = 1) out vec2 fpos; + +layout (std140, binding = 0) uniform View +{ + vec2 size; +} view; + +void main(void) { + ftcoord = tcoord; + fpos = vertex; + gl_Position = vec4(2.0*vertex.x/view.size.x - 1.0, 1.0 - 2.0*vertex.y/view.size.y, 0, 1); +}; \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/dk_renderer.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/dk_renderer.cpp new file mode 100644 index 00000000..c9706a4d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/dk_renderer.cpp @@ -0,0 +1,545 @@ +#include "dk_renderer.hpp" + +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <math.h> +#include <switch.h> + +#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES /* Enforces GLSL std140/std430 alignment rules for glm types. */ +#define GLM_FORCE_INTRINSICS /* Enables usage of SIMD CPU instructions (requiring the above as well). */ +#include <glm/vec2.hpp> + +namespace nvg { + + namespace { + + constexpr std::array VertexBufferState = { DkVtxBufferState{sizeof(NVGvertex), 0}, }; + + constexpr std::array VertexAttribState = { + DkVtxAttribState{0, 0, offsetof(NVGvertex, x), DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0}, + DkVtxAttribState{0, 0, offsetof(NVGvertex, u), DkVtxAttribSize_2x32, DkVtxAttribType_Float, 0}, + }; + + struct View { + glm::vec2 size; + }; + + void UpdateImage(dk::Image &image, CMemPool &scratchPool, dk::Device device, dk::Queue transferQueue, int type, int x, int y, int w, int h, const u8 *data) { + /* Do not proceed if no data is provided upfront. */ + if (data == nullptr) { + return; + } + + /* Allocate memory from the pool for the image. */ + const size_t imageSize = type == NVG_TEXTURE_RGBA ? w * h * 4 : w * h; + CMemPool::Handle tempimgmem = scratchPool.allocate(imageSize, DK_IMAGE_LINEAR_STRIDE_ALIGNMENT); + memcpy(tempimgmem.getCpuAddr(), data, imageSize); + + dk::UniqueCmdBuf tempcmdbuf = dk::CmdBufMaker{device}.create(); + CMemPool::Handle tempcmdmem = scratchPool.allocate(DK_MEMBLOCK_ALIGNMENT); + tempcmdbuf.addMemory(tempcmdmem.getMemBlock(), tempcmdmem.getOffset(), tempcmdmem.getSize()); + + dk::ImageView imageView{image}; + tempcmdbuf.copyBufferToImage({ tempimgmem.getGpuAddr() }, imageView, { static_cast<uint32_t>(x), static_cast<uint32_t>(y), 0, static_cast<uint32_t>(w), static_cast<uint32_t>(h), 1 }); + + transferQueue.submitCommands(tempcmdbuf.finishList()); + transferQueue.waitIdle(); + + /* Destroy temp mem. */ + tempcmdmem.destroy(); + tempimgmem.destroy(); + } + + } + + Texture::Texture(int id) : m_id(id) { /* ... */ } + + Texture::~Texture() { + m_image_mem.destroy(); + } + + void Texture::Initialize(CMemPool &image_pool, CMemPool &scratch_pool, dk::Device device, dk::Queue queue, int type, int w, int h, int image_flags, const u8 *data) { + m_texture_descriptor = { + .width = w, + .height = h, + .type = type, + .flags = image_flags, + }; + + /* Create an image layout. */ + dk::ImageLayout layout; + auto layout_maker = dk::ImageLayoutMaker{device}.setFlags(0).setDimensions(w, h); + if (type == NVG_TEXTURE_RGBA) { + layout_maker.setFormat(DkImageFormat_RGBA8_Unorm); + } else { + layout_maker.setFormat(DkImageFormat_R8_Unorm); + } + layout_maker.initialize(layout); + + /* Initialize image. */ + m_image_mem = image_pool.allocate(layout.getSize(), layout.getAlignment()); + m_image.initialize(layout, m_image_mem.getMemBlock(), m_image_mem.getOffset()); + m_image_descriptor.initialize(m_image); + + /* Only update the image if the data isn't null. */ + if (data != nullptr) { + UpdateImage(m_image, scratch_pool, device, queue, type, 0, 0, w, h, data); + } + } + + int Texture::GetId() { + return m_id; + } + + const DKNVGtextureDescriptor &Texture::GetDescriptor() { + return m_texture_descriptor; + } + + dk::Image &Texture::GetImage() { + return m_image; + } + + dk::ImageDescriptor &Texture::GetImageDescriptor() { + return m_image_descriptor; + } + + DkRenderer::DkRenderer(unsigned int view_width, unsigned int view_height, dk::Device device, dk::Queue queue, CMemPool &image_mem_pool, CMemPool &code_mem_pool, CMemPool &data_mem_pool) : + m_view_width(view_width), m_view_height(view_height), m_device(device), m_queue(queue), m_image_mem_pool(image_mem_pool), m_code_mem_pool(code_mem_pool), m_data_mem_pool(data_mem_pool), m_image_descriptor_mappings({0}) + { + /* Create a dynamic command buffer and allocate memory for it. */ + m_dyn_cmd_buf = dk::CmdBufMaker{m_device}.create(); + m_dyn_cmd_mem.allocate(m_data_mem_pool, DynamicCmdSize); + + m_image_descriptor_set.allocate(m_data_mem_pool); + m_sampler_descriptor_set.allocate(m_data_mem_pool); + + m_view_uniform_buffer = m_data_mem_pool.allocate(sizeof(View), DK_UNIFORM_BUF_ALIGNMENT); + m_frag_uniform_buffer = m_data_mem_pool.allocate(sizeof(FragmentUniformSize), DK_UNIFORM_BUF_ALIGNMENT); + + /* Create and bind preset samplers. */ + dk::UniqueCmdBuf init_cmd_buf = dk::CmdBufMaker{m_device}.create(); + CMemPool::Handle init_cmd_mem = m_data_mem_pool.allocate(DK_MEMBLOCK_ALIGNMENT); + init_cmd_buf.addMemory(init_cmd_mem.getMemBlock(), init_cmd_mem.getOffset(), init_cmd_mem.getSize()); + + for (u8 i = 0; i < SamplerType_Total; i++) { + const DkFilter filter = (i & SamplerType_Nearest) ? DkFilter_Nearest : DkFilter_Linear; + const DkMipFilter mip_filter = (i & SamplerType_Nearest) ? DkMipFilter_Nearest : DkMipFilter_Linear; + const DkWrapMode u_wrap_mode = (i & SamplerType_RepeatX) ? DkWrapMode_Repeat : DkWrapMode_ClampToEdge; + const DkWrapMode v_wrap_mode = (i & SamplerType_RepeatY) ? DkWrapMode_Repeat : DkWrapMode_ClampToEdge; + + auto sampler = dk::Sampler{}; + auto sampler_descriptor = dk::SamplerDescriptor{}; + sampler.setFilter(filter, filter, (i & SamplerType_MipFilter) ? mip_filter : DkMipFilter_None); + sampler.setWrapMode(u_wrap_mode, v_wrap_mode); + sampler_descriptor.initialize(sampler); + m_sampler_descriptor_set.update(init_cmd_buf, i, sampler_descriptor); + } + + /* Flush the descriptor cache. */ + init_cmd_buf.barrier(DkBarrier_None, DkInvalidateFlags_Descriptors); + + m_sampler_descriptor_set.bindForSamplers(init_cmd_buf); + m_image_descriptor_set.bindForImages(init_cmd_buf); + + m_queue.submitCommands(init_cmd_buf.finishList()); + m_queue.waitIdle(); + + init_cmd_mem.destroy(); + init_cmd_buf.destroy(); + } + + DkRenderer::~DkRenderer() { + if (m_vertex_buffer) { + m_vertex_buffer->destroy(); + } + + m_view_uniform_buffer.destroy(); + m_frag_uniform_buffer.destroy(); + m_textures.clear(); + } + + int DkRenderer::AcquireImageDescriptor(std::shared_ptr<Texture> texture, int image) { + int free_image_descriptor = m_last_image_descriptor + 1; + int mapping = 0; + + for (int desc = 0; desc <= m_last_image_descriptor; desc++) { + mapping = m_image_descriptor_mappings[desc]; + + /* We've found the image descriptor requested. */ + if (mapping == image) { + return desc; + } + + /* Update the free image descriptor. */ + if (mapping == 0 && free_image_descriptor == m_last_image_descriptor + 1) { + free_image_descriptor = desc; + } + } + + /* No descriptors are free. */ + if (free_image_descriptor >= static_cast<int>(MaxImages)) { + return -1; + } + + /* Update descriptor sets. */ + m_image_descriptor_set.update(m_dyn_cmd_buf, free_image_descriptor, texture->GetImageDescriptor()); + + /* Flush the descriptor cache. */ + m_dyn_cmd_buf.barrier(DkBarrier_None, DkInvalidateFlags_Descriptors); + + /* Update the map. */ + m_image_descriptor_mappings[free_image_descriptor] = image; + m_last_image_descriptor = free_image_descriptor; + return free_image_descriptor; + } + + void DkRenderer::FreeImageDescriptor(int image) { + for (int desc = 0; desc <= m_last_image_descriptor; desc++) { + if (m_image_descriptor_mappings[desc] == image) { + m_image_descriptor_mappings[desc] = 0; + } + } + } + + void DkRenderer::UpdateVertexBuffer(const void *data, size_t size) { + /* Destroy the existing vertex buffer if it is too small. */ + if (m_vertex_buffer && m_vertex_buffer->getSize() < size) { + m_vertex_buffer->destroy(); + m_vertex_buffer.reset(); + } + + /* Create a new buffer if needed. */ + if (!m_vertex_buffer) { + m_vertex_buffer = m_data_mem_pool.allocate(size); + } + + /* Copy data to the vertex buffer if it exists. */ + if (m_vertex_buffer) { + memcpy(m_vertex_buffer->getCpuAddr(), data, size); + } + } + + void DkRenderer::SetUniforms(const DKNVGcontext &ctx, int offset, int image) { + m_dyn_cmd_buf.pushConstants(m_frag_uniform_buffer.getGpuAddr(), m_frag_uniform_buffer.getSize(), 0, ctx.fragSize, ctx.uniforms + offset); + m_dyn_cmd_buf.bindUniformBuffer(DkStage_Fragment, 0, m_frag_uniform_buffer.getGpuAddr(), m_frag_uniform_buffer.getSize()); + + /* Attempt to find a texture. */ + const auto texture = this->FindTexture(image); + if (texture == nullptr) { + return; + } + + /* Acquire an image descriptor. */ + const int image_desc_id = this->AcquireImageDescriptor(texture, image); + if (image_desc_id == -1) { + return; + } + + const int image_flags = texture->GetDescriptor().flags; + uint32_t sampler_id = 0; + + if (image_flags & NVG_IMAGE_GENERATE_MIPMAPS) sampler_id |= SamplerType_MipFilter; + if (image_flags & NVG_IMAGE_NEAREST) sampler_id |= SamplerType_Nearest; + if (image_flags & NVG_IMAGE_REPEATX) sampler_id |= SamplerType_RepeatX; + if (image_flags & NVG_IMAGE_REPEATY) sampler_id |= SamplerType_RepeatY; + + m_dyn_cmd_buf.bindTextures(DkStage_Fragment, 0, dkMakeTextureHandle(image_desc_id, sampler_id)); + } + + void DkRenderer::DrawFill(const DKNVGcontext &ctx, const DKNVGcall &call) { + DKNVGpath *paths = &ctx.paths[call.pathOffset]; + int npaths = call.pathCount; + + /* Set the stencils to be used. */ + m_dyn_cmd_buf.setStencil(DkFace_FrontAndBack, 0xFF, 0x0, 0xFF); + + /* Set the depth stencil state. */ + auto depth_stencil_state = dk::DepthStencilState{} + .setStencilTestEnable(true) + .setStencilFrontCompareOp(DkCompareOp_Always) + .setStencilFrontFailOp(DkStencilOp_Keep) + .setStencilFrontDepthFailOp(DkStencilOp_Keep) + .setStencilFrontPassOp(DkStencilOp_IncrWrap) + .setStencilBackCompareOp(DkCompareOp_Always) + .setStencilBackFailOp(DkStencilOp_Keep) + .setStencilBackDepthFailOp(DkStencilOp_Keep) + .setStencilBackPassOp(DkStencilOp_DecrWrap); + m_dyn_cmd_buf.bindDepthStencilState(depth_stencil_state); + + /* Configure for shape drawing. */ + m_dyn_cmd_buf.bindColorWriteState(dk::ColorWriteState{}.setMask(0, 0)); + this->SetUniforms(ctx, call.uniformOffset, 0); + m_dyn_cmd_buf.bindRasterizerState(dk::RasterizerState{}.setCullMode(DkFace_None)); + + /* Draw vertices. */ + for (int i = 0; i < npaths; i++) { + m_dyn_cmd_buf.draw(DkPrimitive_TriangleFan, paths[i].fillCount, 1, paths[i].fillOffset, 0); + } + + m_dyn_cmd_buf.bindColorWriteState(dk::ColorWriteState{}); + this->SetUniforms(ctx, call.uniformOffset + ctx.fragSize, call.image); + m_dyn_cmd_buf.bindRasterizerState(dk::RasterizerState{}); + + if (ctx.flags & NVG_ANTIALIAS) { + /* Configure stencil anti-aliasing. */ + depth_stencil_state + .setStencilFrontCompareOp(DkCompareOp_Equal) + .setStencilFrontFailOp(DkStencilOp_Keep) + .setStencilFrontDepthFailOp(DkStencilOp_Keep) + .setStencilFrontPassOp(DkStencilOp_Keep) + .setStencilBackCompareOp(DkCompareOp_Equal) + .setStencilBackFailOp(DkStencilOp_Keep) + .setStencilBackDepthFailOp(DkStencilOp_Keep) + .setStencilBackPassOp(DkStencilOp_Keep); + m_dyn_cmd_buf.bindDepthStencilState(depth_stencil_state); + + /* Draw fringes. */ + for (int i = 0; i < npaths; i++) { + m_dyn_cmd_buf.draw(DkPrimitive_TriangleStrip, paths[i].strokeCount, 1, paths[i].strokeOffset, 0); + } + } + + /* Configure and draw fill. */ + depth_stencil_state + .setStencilFrontCompareOp(DkCompareOp_NotEqual) + .setStencilFrontFailOp(DkStencilOp_Zero) + .setStencilFrontDepthFailOp(DkStencilOp_Zero) + .setStencilFrontPassOp(DkStencilOp_Zero) + .setStencilBackCompareOp(DkCompareOp_NotEqual) + .setStencilBackFailOp(DkStencilOp_Zero) + .setStencilBackDepthFailOp(DkStencilOp_Zero) + .setStencilBackPassOp(DkStencilOp_Zero); + m_dyn_cmd_buf.bindDepthStencilState(depth_stencil_state); + + m_dyn_cmd_buf.draw(DkPrimitive_TriangleStrip, call.triangleCount, 1, call.triangleOffset, 0); + + /* Reset the depth stencil state to default. */ + m_dyn_cmd_buf.bindDepthStencilState(dk::DepthStencilState{}); + } + + void DkRenderer::DrawConvexFill(const DKNVGcontext &ctx, const DKNVGcall &call) { + DKNVGpath *paths = &ctx.paths[call.pathOffset]; + int npaths = call.pathCount; + + this->SetUniforms(ctx, call.uniformOffset, call.image); + + for (int i = 0; i < npaths; i++) { + m_dyn_cmd_buf.draw(DkPrimitive_TriangleFan, paths[i].fillCount, 1, paths[i].fillOffset, 0); + + /* Draw fringes. */ + if (paths[i].strokeCount > 0) { + m_dyn_cmd_buf.draw(DkPrimitive_TriangleStrip, paths[i].strokeCount, 1, paths[i].strokeOffset, 0); + } + } + } + + void DkRenderer::DrawStroke(const DKNVGcontext &ctx, const DKNVGcall &call) { + DKNVGpath* paths = &ctx.paths[call.pathOffset]; + int npaths = call.pathCount; + + if (ctx.flags & NVG_STENCIL_STROKES) { + /* Set the stencil to be used. */ + m_dyn_cmd_buf.setStencil(DkFace_Front, 0xFF, 0x0, 0xFF); + + /* Configure for filling the stroke base without overlap. */ + auto depth_stencil_state = dk::DepthStencilState{} + .setStencilTestEnable(true) + .setStencilFrontCompareOp(DkCompareOp_Equal) + .setStencilFrontFailOp(DkStencilOp_Keep) + .setStencilFrontDepthFailOp(DkStencilOp_Keep) + .setStencilFrontPassOp(DkStencilOp_Incr); + m_dyn_cmd_buf.bindDepthStencilState(depth_stencil_state); + this->SetUniforms(ctx, call.uniformOffset + ctx.fragSize, call.image); + + /* Draw vertices. */ + for (int i = 0; i < npaths; i++) { + m_dyn_cmd_buf.draw(DkPrimitive_TriangleStrip, paths[i].strokeCount, 1, paths[i].strokeOffset, 0); + } + + /* Configure for drawing anti-aliased pixels. */ + depth_stencil_state.setStencilFrontPassOp(DkStencilOp_Keep); + m_dyn_cmd_buf.bindDepthStencilState(depth_stencil_state); + this->SetUniforms(ctx, call.uniformOffset, call.image); + + /* Draw vertices. */ + for (int i = 0; i < npaths; i++) { + m_dyn_cmd_buf.draw(DkPrimitive_TriangleStrip, paths[i].strokeCount, 1, paths[i].strokeOffset, 0); + } + + /* Configure for clearing the stencil buffer. */ + depth_stencil_state + .setStencilTestEnable(true) + .setStencilFrontCompareOp(DkCompareOp_Always) + .setStencilFrontFailOp(DkStencilOp_Zero) + .setStencilFrontDepthFailOp(DkStencilOp_Zero) + .setStencilFrontPassOp(DkStencilOp_Zero); + m_dyn_cmd_buf.bindDepthStencilState(depth_stencil_state); + + /* Draw vertices. */ + for (int i = 0; i < npaths; i++) { + m_dyn_cmd_buf.draw(DkPrimitive_TriangleStrip, paths[i].strokeCount, 1, paths[i].strokeOffset, 0); + } + + /* Reset the depth stencil state to default. */ + m_dyn_cmd_buf.bindDepthStencilState(dk::DepthStencilState{}); + } else { + this->SetUniforms(ctx, call.uniformOffset, call.image); + + /* Draw vertices. */ + for (int i = 0; i < npaths; i++) { + m_dyn_cmd_buf.draw(DkPrimitive_TriangleStrip, paths[i].strokeCount, 1, paths[i].strokeOffset, 0); + } + } + } + + void DkRenderer::DrawTriangles(const DKNVGcontext &ctx, const DKNVGcall &call) { + this->SetUniforms(ctx, call.uniformOffset, call.image); + m_dyn_cmd_buf.draw(DkPrimitive_Triangles, call.triangleCount, 1, call.triangleOffset, 0); + } + + int DkRenderer::Create(DKNVGcontext &ctx) { + m_vertex_shader.load(m_code_mem_pool, "romfs:/shaders/fill_vsh.dksh"); + + /* Load the appropriate fragment shader depending on whether AA is enabled. */ + if (ctx.flags & NVG_ANTIALIAS) { + m_fragment_shader.load(m_code_mem_pool, "romfs:/shaders/fill_aa_fsh.dksh"); + } else { + m_fragment_shader.load(m_code_mem_pool, "romfs:/shaders/fill_fsh.dksh"); + } + + /* Set the size of fragment uniforms. */ + ctx.fragSize = FragmentUniformSize; + return 1; + } + + std::shared_ptr<Texture> DkRenderer::FindTexture(int id) { + for (auto it = m_textures.begin(); it != m_textures.end(); it++) { + if ((*it)->GetId() == id) { + return *it; + } + } + + return nullptr; + } + + int DkRenderer::CreateTexture(const DKNVGcontext &ctx, int type, int w, int h, int image_flags, const unsigned char* data) { + const auto texture_id = m_next_texture_id++; + auto texture = std::make_shared<Texture>(texture_id); + texture->Initialize(m_image_mem_pool, m_data_mem_pool, m_device, m_queue, type, w, h, image_flags, data); + m_textures.push_back(texture); + return texture->GetId(); + } + + int DkRenderer::DeleteTexture(const DKNVGcontext &ctx, int image) { + bool found = false; + + for (auto it = m_textures.begin(); it != m_textures.end();) { + /* Remove textures with the given id. */ + if ((*it)->GetId() == image) { + it = m_textures.erase(it); + found = true; + } else { + ++it; + } + } + + /* Free any used image descriptors. */ + this->FreeImageDescriptor(image); + return found; + } + + int DkRenderer::UpdateTexture(const DKNVGcontext &ctx, int image, int x, int y, int w, int h, const unsigned char *data) { + const std::shared_ptr<Texture> texture = this->FindTexture(image); + + /* Could not find a texture. */ + if (texture == nullptr) { + return 0; + } + + const DKNVGtextureDescriptor &tex_desc = texture->GetDescriptor(); + if (tex_desc.type == NVG_TEXTURE_RGBA) { + data += y * tex_desc.width*4; + } else { + data += y * tex_desc.width; + } + x = 0; + w = tex_desc.width; + + UpdateImage(texture->GetImage(), m_data_mem_pool, m_device, m_queue, tex_desc.type, x, y, w, h, data); + return 1; + } + + int DkRenderer::GetTextureSize(const DKNVGcontext &ctx, int image, int *w, int *h) { + const auto descriptor = this->GetTextureDescriptor(ctx, image); + if (descriptor == nullptr) { + return 0; + } + + *w = descriptor->width; + *h = descriptor->height; + return 1; + } + + const DKNVGtextureDescriptor *DkRenderer::GetTextureDescriptor(const DKNVGcontext &ctx, int id) { + for (auto it = m_textures.begin(); it != m_textures.end(); it++) { + if ((*it)->GetId() == id) { + return &(*it)->GetDescriptor(); + } + } + + return nullptr; + } + + void DkRenderer::Flush(DKNVGcontext &ctx) { + if (ctx.ncalls > 0) { + /* Prepare dynamic command buffer. */ + m_dyn_cmd_mem.begin(m_dyn_cmd_buf); + + /* Update buffers with data. */ + this->UpdateVertexBuffer(ctx.verts, ctx.nverts * sizeof(NVGvertex)); + + /* Enable blending. */ + m_dyn_cmd_buf.bindColorState(dk::ColorState{}.setBlendEnable(0, true)); + + /* Setup. */ + m_dyn_cmd_buf.bindShaders(DkStageFlag_GraphicsMask, { m_vertex_shader, m_fragment_shader }); + m_dyn_cmd_buf.bindVtxAttribState(VertexAttribState); + m_dyn_cmd_buf.bindVtxBufferState(VertexBufferState); + m_dyn_cmd_buf.bindVtxBuffer(0, m_vertex_buffer->getGpuAddr(), m_vertex_buffer->getSize()); + + /* Push the view size to the uniform buffer and bind it. */ + const auto view = View{glm::vec2{m_view_width, m_view_height}}; + m_dyn_cmd_buf.pushConstants(m_view_uniform_buffer.getGpuAddr(), m_view_uniform_buffer.getSize(), 0, sizeof(view), &view); + m_dyn_cmd_buf.bindUniformBuffer(DkStage_Vertex, 0, m_view_uniform_buffer.getGpuAddr(), m_view_uniform_buffer.getSize()); + + /* Iterate over calls. */ + for (int i = 0; i < ctx.ncalls; i++) { + const DKNVGcall &call = ctx.calls[i]; + + /* Perform blending. */ + m_dyn_cmd_buf.bindBlendStates(0, { dk::BlendState{}.setFactors(static_cast<DkBlendFactor>(call.blendFunc.srcRGB), static_cast<DkBlendFactor>(call.blendFunc.dstRGB), static_cast<DkBlendFactor>(call.blendFunc.srcAlpha), static_cast<DkBlendFactor>(call.blendFunc.dstRGB)) }); + + if (call.type == DKNVG_FILL) { + this->DrawFill(ctx, call); + } else if (call.type == DKNVG_CONVEXFILL) { + this->DrawConvexFill(ctx, call); + } else if (call.type == DKNVG_STROKE) { + this->DrawStroke(ctx, call); + } else if (call.type == DKNVG_TRIANGLES) { + this->DrawTriangles(ctx, call); + } + } + + m_queue.submitCommands(m_dyn_cmd_mem.end(m_dyn_cmd_buf)); + } + + /* Reset calls. */ + ctx.nverts = 0; + ctx.npaths = 0; + ctx.ncalls = 0; + ctx.nuniforms = 0; + } + +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/CApplication.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/CApplication.cpp new file mode 100644 index 00000000..2d6c87ff --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/CApplication.cpp @@ -0,0 +1,69 @@ +/* +** Sample Framework for deko3d Applications +** CApplication.cpp: Wrapper class containing common application boilerplate +*/ +#include "CApplication.h" + +CApplication::CApplication() +{ + appletLockExit(); + appletSetFocusHandlingMode(AppletFocusHandlingMode_NoSuspend); +} + +CApplication::~CApplication() +{ + appletSetFocusHandlingMode(AppletFocusHandlingMode_SuspendHomeSleep); + appletUnlockExit(); +} + +void CApplication::run() +{ + u64 tick_ref = armGetSystemTick(); + u64 tick_saved = tick_ref; + bool focused = appletGetFocusState() == AppletFocusState_InFocus; + + onOperationMode(appletGetOperationMode()); + + for (;;) + { + u32 msg = 0; + Result rc = appletGetMessage(&msg); + if (R_SUCCEEDED(rc)) + { + bool should_close = !appletProcessMessage(msg); + if (should_close) + return; + + switch (msg) + { + case AppletMessage_FocusStateChanged: + { + bool old_focused = focused; + AppletFocusState state = appletGetFocusState(); + focused = state == AppletFocusState_InFocus; + + onFocusState(state); + if (focused == old_focused) + break; + if (focused) + { + appletSetFocusHandlingMode(AppletFocusHandlingMode_NoSuspend); + tick_ref += armGetSystemTick() - tick_saved; + } + else + { + tick_saved = armGetSystemTick(); + appletSetFocusHandlingMode(AppletFocusHandlingMode_SuspendHomeSleepNotify); + } + break; + } + case AppletMessage_OperationModeChanged: + onOperationMode(appletGetOperationMode()); + break; + } + } + + if (focused && !onFrame(armTicksToNs(armGetSystemTick() - tick_ref))) + break; + } +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/CExternalImage.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/CExternalImage.cpp new file mode 100644 index 00000000..37b6a26b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/CExternalImage.cpp @@ -0,0 +1,37 @@ +/* +** Sample Framework for deko3d Applications +** CExternalImage.cpp: Utility class for loading images from the filesystem +*/ +#include "CExternalImage.h" +#include "FileLoader.h" + +bool CExternalImage::load(CMemPool& imagePool, CMemPool& scratchPool, dk::Device device, dk::Queue transferQueue, const char* path, uint32_t width, uint32_t height, DkImageFormat format, uint32_t flags) +{ + CMemPool::Handle tempimgmem = LoadFile(scratchPool, path, DK_IMAGE_LINEAR_STRIDE_ALIGNMENT); + if (!tempimgmem) + return false; + + dk::UniqueCmdBuf tempcmdbuf = dk::CmdBufMaker{device}.create(); + CMemPool::Handle tempcmdmem = scratchPool.allocate(DK_MEMBLOCK_ALIGNMENT); + tempcmdbuf.addMemory(tempcmdmem.getMemBlock(), tempcmdmem.getOffset(), tempcmdmem.getSize()); + + dk::ImageLayout layout; + dk::ImageLayoutMaker{device} + .setFlags(flags) + .setFormat(format) + .setDimensions(width, height) + .initialize(layout); + + m_mem = imagePool.allocate(layout.getSize(), layout.getAlignment()); + m_image.initialize(layout, m_mem.getMemBlock(), m_mem.getOffset()); + m_descriptor.initialize(m_image); + + dk::ImageView imageView{m_image}; + tempcmdbuf.copyBufferToImage({ tempimgmem.getGpuAddr() }, imageView, { 0, 0, 0, width, height, 1 }); + transferQueue.submitCommands(tempcmdbuf.finishList()); + transferQueue.waitIdle(); + + tempcmdmem.destroy(); + tempimgmem.destroy(); + return true; +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/CIntrusiveTree.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/CIntrusiveTree.cpp new file mode 100644 index 00000000..9f21b63d --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/CIntrusiveTree.cpp @@ -0,0 +1,214 @@ +/* +** Sample Framework for deko3d Applications +** CIntrusiveTree.cpp: Intrusive red-black tree helper class +*/ +#include "CIntrusiveTree.h" + +// This red-black tree implementation is mostly based on mtheall's work, +// which can be found here: +// https://github.com/smealum/ctrulib/tree/master/libctru/source/util/rbtree + +void CIntrusiveTreeBase::rotate(N* node, N::Leaf leaf) +{ + N *tmp = node->child(leaf); + N *parent = node->getParent(); + + node->child(leaf) = tmp->child(!leaf); + if (tmp->child(!leaf)) + tmp->child(!leaf)->setParent(node); + + tmp->child(!leaf) = node; + tmp->setParent(parent); + + if (parent) + { + if (node == parent->child(!leaf)) + parent->child(!leaf) = tmp; + else + parent->child(leaf) = tmp; + } + else + m_root = tmp; + + node->setParent(tmp); +} + +void CIntrusiveTreeBase::recolor(N* parent, N* node) +{ + N *sibling; + + while ((!node || node->isBlack()) && node != m_root) + { + N::Leaf leaf = node == parent->left() ? N::Right : N::Left; + sibling = parent->child(leaf); + + if (sibling->isRed()) + { + sibling->setBlack(); + parent->setRed(); + rotate(parent, leaf); + sibling = parent->child(leaf); + } + + N::Color clr[2]; + clr[N::Left] = sibling->left() ? sibling->left()->getColor() : N::Black; + clr[N::Right] = sibling->right() ? sibling->right()->getColor() : N::Black; + + if (clr[N::Left] == N::Black && clr[N::Right] == N::Black) + { + sibling->setRed(); + node = parent; + parent = node->getParent(); + } + else + { + if (clr[leaf] == N::Black) + { + sibling->child(!leaf)->setBlack(); + sibling->setRed(); + rotate(sibling, !leaf); + sibling = parent->child(leaf); + } + + sibling->setColor(parent->getColor()); + parent->setBlack(); + sibling->child(leaf)->setBlack(); + rotate(parent, leaf); + + node = m_root; + } + } + + if (node) + node->setBlack(); +} + +auto CIntrusiveTreeBase::walk(N* node, N::Leaf leaf) const -> N* +{ + if (node->child(leaf)) + { + node = node->child(leaf); + while (node->child(!leaf)) + node = node->child(!leaf); + } + else + { + N *parent = node->getParent(); + while (parent && node == parent->child(leaf)) + { + node = parent; + parent = node->getParent(); + } + node = parent; + } + + return node; +} + +void CIntrusiveTreeBase::insert(N* node, N* parent) +{ + node->left() = node->right() = nullptr; + node->setParent(parent); + node->setRed(); + + while ((parent = node->getParent()) && parent->isRed()) + { + N *grandparent = parent->getParent(); + N::Leaf leaf = parent == grandparent->left() ? N::Right : N::Left; + N *uncle = grandparent->child(leaf); + + if (uncle && uncle->isRed()) + { + uncle->setBlack(); + parent->setBlack(); + grandparent->setRed(); + + node = grandparent; + } + else + { + if (parent->child(leaf) == node) + { + rotate(parent, leaf); + + N* tmp = parent; + parent = node; + node = tmp; + } + + parent->setBlack(); + grandparent->setRed(); + rotate(grandparent, !leaf); + } + } + + m_root->setBlack(); +} + +void CIntrusiveTreeBase::remove(N* node) +{ + N::Color color; + N *child, *parent; + + if (node->left() && node->right()) + { + N *old = node; + + node = node->right(); + while (node->left()) + node = node->left(); + + parent = old->getParent(); + if (parent) + { + if (parent->left() == old) + parent->left() = node; + else + parent->right() = node; + } + else + m_root = node; + + child = node->right(); + parent = node->getParent(); + color = node->getColor(); + + if (parent == old) + parent = node; + else + { + if (child) + child->setParent(parent); + parent->left() = child; + + node->right() = old->right(); + old->right()->setParent(node); + } + + node->setParent(old->getParent()); + node->setColor(old->getColor()); + node->left() = old->left(); + old->left()->setParent(node); + } + else + { + child = node->left() ? node->right() : node->left(); + parent = node->getParent(); + color = node->getColor(); + + if (child) + child->setParent(parent); + if (parent) + { + if (parent->left() == node) + parent->left() = child; + else + parent->right() = child; + } + else + m_root = child; + } + + if (color == N::Black) + recolor(parent, child); +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/CMemPool.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/CMemPool.cpp new file mode 100644 index 00000000..fb3bd10c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/CMemPool.cpp @@ -0,0 +1,175 @@ +/* +** Sample Framework for deko3d Applications +** CMemPool.cpp: Pooled dynamic memory allocation manager class +*/ +#include "CMemPool.h" + +inline auto CMemPool::_newSlice() -> Slice* +{ + Slice* ret = m_sliceHeap.pop(); + if (!ret) ret = (Slice*)::malloc(sizeof(Slice)); + return ret; +} + +inline void CMemPool::_deleteSlice(Slice* s) +{ + if (!s) return; + m_sliceHeap.add(s); +} + +CMemPool::~CMemPool() +{ + m_memMap.iterate([](Slice* s) { ::free(s); }); + m_sliceHeap.iterate([](Slice* s) { ::free(s); }); + m_blocks.iterate([](Block* blk) { + blk->m_obj.destroy(); + ::free(blk); + }); +} + +auto CMemPool::allocate(uint32_t size, uint32_t alignment) -> Handle +{ + if (!size) return nullptr; + if (alignment & (alignment - 1)) return nullptr; + size = (size + alignment - 1) &~ (alignment - 1); +#ifdef DEBUG_CMEMPOOL + printf("Allocating size=%u alignment=0x%x\n", size, alignment); + { + Slice* temp = /*m_freeList*/m_memMap.first(); + while (temp) + { + printf("-- blk %p | 0x%08x-0x%08x | %s used\n", temp->m_block, temp->m_start, temp->m_end, temp->m_pool ? " " : "not"); + temp = /*m_freeList*/m_memMap.next(temp); + } + } +#endif + + uint32_t start_offset = 0; + uint32_t end_offset = 0; + Slice* slice = m_freeList.find(size, decltype(m_freeList)::LowerBound); + while (slice) + { +#ifdef DEBUG_CMEMPOOL + printf(" * Checking slice 0x%x - 0x%x\n", slice->m_start, slice->m_end); +#endif + start_offset = (slice->m_start + alignment - 1) &~ (alignment - 1); + end_offset = start_offset + size; + if (end_offset <= slice->m_end) + break; + slice = m_freeList.next(slice); + } + + if (!slice) + { + Block* blk = (Block*)::malloc(sizeof(Block)); + if (!blk) + return nullptr; + + uint32_t unusableSize = (m_flags & DkMemBlockFlags_Code) ? DK_SHADER_CODE_UNUSABLE_SIZE : 0; + uint32_t blkSize = m_blockSize - unusableSize; + blkSize = size > blkSize ? size : blkSize; + blkSize = (blkSize + unusableSize + DK_MEMBLOCK_ALIGNMENT - 1) &~ (DK_MEMBLOCK_ALIGNMENT - 1); +#ifdef DEBUG_CMEMPOOL + printf(" ! Allocating block of size 0x%x\n", blkSize); +#endif + blk->m_obj = dk::MemBlockMaker{m_dev, blkSize}.setFlags(m_flags).create(); + if (!blk->m_obj) + { + ::free(blk); + return nullptr; + } + + slice = _newSlice(); + if (!slice) + { + blk->m_obj.destroy(); + ::free(blk); + return nullptr; + } + + slice->m_pool = nullptr; + slice->m_block = blk; + slice->m_start = 0; + slice->m_end = blkSize - unusableSize; + m_memMap.add(slice); + + blk->m_cpuAddr = blk->m_obj.getCpuAddr(); + blk->m_gpuAddr = blk->m_obj.getGpuAddr(); + m_blocks.add(blk); + + start_offset = 0; + end_offset = size; + } + else + { +#ifdef DEBUG_CMEMPOOL + printf(" * found it\n"); +#endif + m_freeList.remove(slice); + } + + if (start_offset != slice->m_start) + { + Slice* t = _newSlice(); + if (!t) goto _bad; + t->m_pool = nullptr; + t->m_block = slice->m_block; + t->m_start = slice->m_start; + t->m_end = start_offset; +#ifdef DEBUG_CMEMPOOL + printf("-> subdivide left: %08x-%08x\n", t->m_start, t->m_end); +#endif + m_memMap.addBefore(slice, t); + m_freeList.insert(t, true); + slice->m_start = start_offset; + } + + if (end_offset != slice->m_end) + { + Slice* t = _newSlice(); + if (!t) goto _bad; + t->m_pool = nullptr; + t->m_block = slice->m_block; + t->m_start = end_offset; + t->m_end = slice->m_end; +#ifdef DEBUG_CMEMPOOL + printf("-> subdivide right: %08x-%08x\n", t->m_start, t->m_end); +#endif + m_memMap.addAfter(slice, t); + m_freeList.insert(t, true); + slice->m_end = end_offset; + } + + slice->m_pool = this; + return slice; + +_bad: + m_freeList.insert(slice, true); + return nullptr; +} + +void CMemPool::_destroy(Slice* slice) +{ + slice->m_pool = nullptr; + + Slice* left = m_memMap.prev(slice); + Slice* right = m_memMap.next(slice); + + if (left && left->canCoalesce(*slice)) + { + slice->m_start = left->m_start; + m_freeList.remove(left); + m_memMap.remove(left); + _deleteSlice(left); + } + + if (right && slice->canCoalesce(*right)) + { + slice->m_end = right->m_end; + m_freeList.remove(right); + m_memMap.remove(right); + _deleteSlice(right); + } + + m_freeList.insert(slice, true); +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/CShader.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/CShader.cpp new file mode 100644 index 00000000..6c5361c2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/CShader.cpp @@ -0,0 +1,62 @@ +/* +** Sample Framework for deko3d Applications +** CShader.cpp: Utility class for loading shaders from the filesystem +*/ +#include "CShader.h" + +struct DkshHeader +{ + uint32_t magic; // DKSH_MAGIC + uint32_t header_sz; // sizeof(DkshHeader) + uint32_t control_sz; + uint32_t code_sz; + uint32_t programs_off; + uint32_t num_programs; +}; + +bool CShader::load(CMemPool& pool, const char* path) +{ + FILE* f; + DkshHeader hdr; + void* ctrlmem; + + m_codemem.destroy(); + + f = fopen(path, "rb"); + if (!f) return false; + + if (!fread(&hdr, sizeof(hdr), 1, f)) + goto _fail0; + + ctrlmem = malloc(hdr.control_sz); + if (!ctrlmem) + goto _fail0; + + rewind(f); + if (!fread(ctrlmem, hdr.control_sz, 1, f)) + goto _fail1; + + m_codemem = pool.allocate(hdr.code_sz, DK_SHADER_CODE_ALIGNMENT); + if (!m_codemem) + goto _fail1; + + if (!fread(m_codemem.getCpuAddr(), hdr.code_sz, 1, f)) + goto _fail2; + + dk::ShaderMaker{m_codemem.getMemBlock(), m_codemem.getOffset()} + .setControl(ctrlmem) + .setProgramId(0) + .initialize(m_shader); + + free(ctrlmem); + fclose(f); + return true; + +_fail2: + m_codemem.destroy(); +_fail1: + free(ctrlmem); +_fail0: + fclose(f); + return false; +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/FileLoader.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/FileLoader.cpp new file mode 100644 index 00000000..a9651bf9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/FileLoader.cpp @@ -0,0 +1,27 @@ +/* +** Sample Framework for deko3d Applications +** FileLoader.cpp: Helpers for loading data from the filesystem directly into GPU memory +*/ +#include "FileLoader.h" + +CMemPool::Handle LoadFile(CMemPool& pool, const char* path, uint32_t alignment) +{ + FILE *f = fopen(path, "rb"); + if (!f) return nullptr; + + fseek(f, 0, SEEK_END); + uint32_t fsize = ftell(f); + rewind(f); + + CMemPool::Handle mem = pool.allocate(fsize, alignment); + if (!mem) + { + fclose(f); + return nullptr; + } + + fread(mem.getCpuAddr(), fsize, 1, f); + fclose(f); + + return mem; +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/LICENSE b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/LICENSE new file mode 100644 index 00000000..183debc3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/framework/LICENSE @@ -0,0 +1,18 @@ +Copyright (C) 2020 fincs + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you use + this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source + distribution. diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/nanovg.c b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/nanovg.c new file mode 100644 index 00000000..aa863547 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/nanovg/source/nanovg.c @@ -0,0 +1,2926 @@ +// +// Copyright (c) 2013 Mikko Mononen memon@inside.org +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// + +#include <stdlib.h> +#include <stdio.h> +#include <math.h> +#include <memory.h> + +#include "nanovg.h" +#define FONTSTASH_IMPLEMENTATION +#include "fontstash.h" +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +#ifdef _MSC_VER +#pragma warning(disable: 4100) // unreferenced formal parameter +#pragma warning(disable: 4127) // conditional expression is constant +#pragma warning(disable: 4204) // nonstandard extension used : non-constant aggregate initializer +#pragma warning(disable: 4706) // assignment within conditional expression +#endif + +#define NVG_INIT_FONTIMAGE_SIZE 512 +#define NVG_MAX_FONTIMAGE_SIZE 2048 +#define NVG_MAX_FONTIMAGES 4 + +#define NVG_INIT_COMMANDS_SIZE 256 +#define NVG_INIT_POINTS_SIZE 128 +#define NVG_INIT_PATHS_SIZE 16 +#define NVG_INIT_VERTS_SIZE 256 +#define NVG_MAX_STATES 32 + +#define NVG_KAPPA90 0.5522847493f // Length proportional to radius of a cubic bezier handle for 90deg arcs. + +#define NVG_COUNTOF(arr) (sizeof(arr) / sizeof(0[arr])) + + +enum NVGcommands { + NVG_MOVETO = 0, + NVG_LINETO = 1, + NVG_BEZIERTO = 2, + NVG_CLOSE = 3, + NVG_WINDING = 4, +}; + +enum NVGpointFlags +{ + NVG_PT_CORNER = 0x01, + NVG_PT_LEFT = 0x02, + NVG_PT_BEVEL = 0x04, + NVG_PR_INNERBEVEL = 0x08, +}; + +struct NVGstate { + NVGcompositeOperationState compositeOperation; + int shapeAntiAlias; + NVGpaint fill; + NVGpaint stroke; + float strokeWidth; + float miterLimit; + int lineJoin; + int lineCap; + float alpha; + float xform[6]; + NVGscissor scissor; + float fontSize; + float letterSpacing; + float lineHeight; + float fontBlur; + int textAlign; + int fontId; +}; +typedef struct NVGstate NVGstate; + +struct NVGpoint { + float x,y; + float dx, dy; + float len; + float dmx, dmy; + unsigned char flags; +}; +typedef struct NVGpoint NVGpoint; + +struct NVGpathCache { + NVGpoint* points; + int npoints; + int cpoints; + NVGpath* paths; + int npaths; + int cpaths; + NVGvertex* verts; + int nverts; + int cverts; + float bounds[4]; +}; +typedef struct NVGpathCache NVGpathCache; + +struct NVGcontext { + NVGparams params; + float* commands; + int ccommands; + int ncommands; + float commandx, commandy; + NVGstate states[NVG_MAX_STATES]; + int nstates; + NVGpathCache* cache; + float tessTol; + float distTol; + float fringeWidth; + float devicePxRatio; + struct FONScontext* fs; + int fontImages[NVG_MAX_FONTIMAGES]; + int fontImageIdx; + int drawCallCount; + int fillTriCount; + int strokeTriCount; + int textTriCount; +}; + +static float nvg__sqrtf(float a) { return sqrtf(a); } +static float nvg__modf(float a, float b) { return fmodf(a, b); } +static float nvg__sinf(float a) { return sinf(a); } +static float nvg__cosf(float a) { return cosf(a); } +static float nvg__tanf(float a) { return tanf(a); } +static float nvg__atan2f(float a,float b) { return atan2f(a, b); } +static float nvg__acosf(float a) { return acosf(a); } + +static int nvg__mini(int a, int b) { return a < b ? a : b; } +static int nvg__maxi(int a, int b) { return a > b ? a : b; } +static int nvg__clampi(int a, int mn, int mx) { return a < mn ? mn : (a > mx ? mx : a); } +static float nvg__minf(float a, float b) { return a < b ? a : b; } +static float nvg__maxf(float a, float b) { return a > b ? a : b; } +static float nvg__absf(float a) { return a >= 0.0f ? a : -a; } +static float nvg__signf(float a) { return a >= 0.0f ? 1.0f : -1.0f; } +static float nvg__clampf(float a, float mn, float mx) { return a < mn ? mn : (a > mx ? mx : a); } +static float nvg__cross(float dx0, float dy0, float dx1, float dy1) { return dx1*dy0 - dx0*dy1; } + +static float nvg__normalize(float *x, float* y) +{ + float d = nvg__sqrtf((*x)*(*x) + (*y)*(*y)); + if (d > 1e-6f) { + float id = 1.0f / d; + *x *= id; + *y *= id; + } + return d; +} + + +static void nvg__deletePathCache(NVGpathCache* c) +{ + if (c == NULL) return; + if (c->points != NULL) free(c->points); + if (c->paths != NULL) free(c->paths); + if (c->verts != NULL) free(c->verts); + free(c); +} + +static NVGpathCache* nvg__allocPathCache(void) +{ + NVGpathCache* c = (NVGpathCache*)malloc(sizeof(NVGpathCache)); + if (c == NULL) goto error; + memset(c, 0, sizeof(NVGpathCache)); + + c->points = (NVGpoint*)malloc(sizeof(NVGpoint)*NVG_INIT_POINTS_SIZE); + if (!c->points) goto error; + c->npoints = 0; + c->cpoints = NVG_INIT_POINTS_SIZE; + + c->paths = (NVGpath*)malloc(sizeof(NVGpath)*NVG_INIT_PATHS_SIZE); + if (!c->paths) goto error; + c->npaths = 0; + c->cpaths = NVG_INIT_PATHS_SIZE; + + c->verts = (NVGvertex*)malloc(sizeof(NVGvertex)*NVG_INIT_VERTS_SIZE); + if (!c->verts) goto error; + c->nverts = 0; + c->cverts = NVG_INIT_VERTS_SIZE; + + return c; +error: + nvg__deletePathCache(c); + return NULL; +} + +static void nvg__setDevicePixelRatio(NVGcontext* ctx, float ratio) +{ + ctx->tessTol = 0.25f / ratio; + ctx->distTol = 0.01f / ratio; + ctx->fringeWidth = 1.0f / ratio; + ctx->devicePxRatio = ratio; +} + +static NVGcompositeOperationState nvg__compositeOperationState(int op) +{ + int sfactor, dfactor; + + if (op == NVG_SOURCE_OVER) + { + sfactor = NVG_ONE; + dfactor = NVG_ONE_MINUS_SRC_ALPHA; + } + else if (op == NVG_SOURCE_IN) + { + sfactor = NVG_DST_ALPHA; + dfactor = NVG_ZERO; + } + else if (op == NVG_SOURCE_OUT) + { + sfactor = NVG_ONE_MINUS_DST_ALPHA; + dfactor = NVG_ZERO; + } + else if (op == NVG_ATOP) + { + sfactor = NVG_DST_ALPHA; + dfactor = NVG_ONE_MINUS_SRC_ALPHA; + } + else if (op == NVG_DESTINATION_OVER) + { + sfactor = NVG_ONE_MINUS_DST_ALPHA; + dfactor = NVG_ONE; + } + else if (op == NVG_DESTINATION_IN) + { + sfactor = NVG_ZERO; + dfactor = NVG_SRC_ALPHA; + } + else if (op == NVG_DESTINATION_OUT) + { + sfactor = NVG_ZERO; + dfactor = NVG_ONE_MINUS_SRC_ALPHA; + } + else if (op == NVG_DESTINATION_ATOP) + { + sfactor = NVG_ONE_MINUS_DST_ALPHA; + dfactor = NVG_SRC_ALPHA; + } + else if (op == NVG_LIGHTER) + { + sfactor = NVG_ONE; + dfactor = NVG_ONE; + } + else if (op == NVG_COPY) + { + sfactor = NVG_ONE; + dfactor = NVG_ZERO; + } + else if (op == NVG_XOR) + { + sfactor = NVG_ONE_MINUS_DST_ALPHA; + dfactor = NVG_ONE_MINUS_SRC_ALPHA; + } + else + { + sfactor = NVG_ONE; + dfactor = NVG_ZERO; + } + + NVGcompositeOperationState state; + state.srcRGB = sfactor; + state.dstRGB = dfactor; + state.srcAlpha = sfactor; + state.dstAlpha = dfactor; + return state; +} + +static NVGstate* nvg__getState(NVGcontext* ctx) +{ + return &ctx->states[ctx->nstates-1]; +} + +NVGcontext* nvgCreateInternal(NVGparams* params) +{ + FONSparams fontParams; + NVGcontext* ctx = (NVGcontext*)malloc(sizeof(NVGcontext)); + int i; + if (ctx == NULL) goto error; + memset(ctx, 0, sizeof(NVGcontext)); + + ctx->params = *params; + for (i = 0; i < NVG_MAX_FONTIMAGES; i++) + ctx->fontImages[i] = 0; + + ctx->commands = (float*)malloc(sizeof(float)*NVG_INIT_COMMANDS_SIZE); + if (!ctx->commands) goto error; + ctx->ncommands = 0; + ctx->ccommands = NVG_INIT_COMMANDS_SIZE; + + ctx->cache = nvg__allocPathCache(); + if (ctx->cache == NULL) goto error; + + nvgSave(ctx); + nvgReset(ctx); + + nvg__setDevicePixelRatio(ctx, 1.0f); + + if (ctx->params.renderCreate(ctx->params.userPtr) == 0) goto error; + + // Init font rendering + memset(&fontParams, 0, sizeof(fontParams)); + fontParams.width = NVG_INIT_FONTIMAGE_SIZE; + fontParams.height = NVG_INIT_FONTIMAGE_SIZE; + fontParams.flags = FONS_ZERO_TOPLEFT; + fontParams.renderCreate = NULL; + fontParams.renderUpdate = NULL; + fontParams.renderDraw = NULL; + fontParams.renderDelete = NULL; + fontParams.userPtr = NULL; + ctx->fs = fonsCreateInternal(&fontParams); + if (ctx->fs == NULL) goto error; + + // Create font texture + ctx->fontImages[0] = ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_ALPHA, fontParams.width, fontParams.height, 0, NULL); + if (ctx->fontImages[0] == 0) goto error; + ctx->fontImageIdx = 0; + + return ctx; + +error: + nvgDeleteInternal(ctx); + return 0; +} + +NVGparams* nvgInternalParams(NVGcontext* ctx) +{ + return &ctx->params; +} + +void nvgDeleteInternal(NVGcontext* ctx) +{ + int i; + if (ctx == NULL) return; + if (ctx->commands != NULL) free(ctx->commands); + if (ctx->cache != NULL) nvg__deletePathCache(ctx->cache); + + if (ctx->fs) + fonsDeleteInternal(ctx->fs); + + for (i = 0; i < NVG_MAX_FONTIMAGES; i++) { + if (ctx->fontImages[i] != 0) { + nvgDeleteImage(ctx, ctx->fontImages[i]); + ctx->fontImages[i] = 0; + } + } + + if (ctx->params.renderDelete != NULL) + ctx->params.renderDelete(ctx->params.userPtr); + + free(ctx); +} + +void nvgBeginFrame(NVGcontext* ctx, float windowWidth, float windowHeight, float devicePixelRatio) +{ +/* printf("Tris: draws:%d fill:%d stroke:%d text:%d TOT:%d\n", + ctx->drawCallCount, ctx->fillTriCount, ctx->strokeTriCount, ctx->textTriCount, + ctx->fillTriCount+ctx->strokeTriCount+ctx->textTriCount);*/ + + ctx->nstates = 0; + nvgSave(ctx); + nvgReset(ctx); + + nvg__setDevicePixelRatio(ctx, devicePixelRatio); + + ctx->params.renderViewport(ctx->params.userPtr, windowWidth, windowHeight, devicePixelRatio); + + ctx->drawCallCount = 0; + ctx->fillTriCount = 0; + ctx->strokeTriCount = 0; + ctx->textTriCount = 0; +} + +void nvgCancelFrame(NVGcontext* ctx) +{ + ctx->params.renderCancel(ctx->params.userPtr); +} + +void nvgEndFrame(NVGcontext* ctx) +{ + ctx->params.renderFlush(ctx->params.userPtr); + if (ctx->fontImageIdx != 0) { + int fontImage = ctx->fontImages[ctx->fontImageIdx]; + int i, j, iw, ih; + // delete images that smaller than current one + if (fontImage == 0) + return; + nvgImageSize(ctx, fontImage, &iw, &ih); + for (i = j = 0; i < ctx->fontImageIdx; i++) { + if (ctx->fontImages[i] != 0) { + int nw, nh; + nvgImageSize(ctx, ctx->fontImages[i], &nw, &nh); + if (nw < iw || nh < ih) + nvgDeleteImage(ctx, ctx->fontImages[i]); + else + ctx->fontImages[j++] = ctx->fontImages[i]; + } + } + // make current font image to first + ctx->fontImages[j++] = ctx->fontImages[0]; + ctx->fontImages[0] = fontImage; + ctx->fontImageIdx = 0; + // clear all images after j + for (i = j; i < NVG_MAX_FONTIMAGES; i++) + ctx->fontImages[i] = 0; + } +} + +NVGcolor nvgRGB(unsigned char r, unsigned char g, unsigned char b) +{ + return nvgRGBA(r,g,b,255); +} + +NVGcolor nvgRGBf(float r, float g, float b) +{ + return nvgRGBAf(r,g,b,1.0f); +} + +NVGcolor nvgRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + NVGcolor color; + // Use longer initialization to suppress warning. + color.r = r / 255.0f; + color.g = g / 255.0f; + color.b = b / 255.0f; + color.a = a / 255.0f; + return color; +} + +NVGcolor nvgRGBAf(float r, float g, float b, float a) +{ + NVGcolor color; + // Use longer initialization to suppress warning. + color.r = r; + color.g = g; + color.b = b; + color.a = a; + return color; +} + +NVGcolor nvgTransRGBA(NVGcolor c, unsigned char a) +{ + c.a = a / 255.0f; + return c; +} + +NVGcolor nvgTransRGBAf(NVGcolor c, float a) +{ + c.a = a; + return c; +} + +NVGcolor nvgLerpRGBA(NVGcolor c0, NVGcolor c1, float u) +{ + int i; + float oneminu; + NVGcolor cint = {{{0}}}; + + u = nvg__clampf(u, 0.0f, 1.0f); + oneminu = 1.0f - u; + for( i = 0; i <4; i++ ) + { + cint.rgba[i] = c0.rgba[i] * oneminu + c1.rgba[i] * u; + } + + return cint; +} + +NVGcolor nvgHSL(float h, float s, float l) +{ + return nvgHSLA(h,s,l,255); +} + +static float nvg__hue(float h, float m1, float m2) +{ + if (h < 0) h += 1; + if (h > 1) h -= 1; + if (h < 1.0f/6.0f) + return m1 + (m2 - m1) * h * 6.0f; + else if (h < 3.0f/6.0f) + return m2; + else if (h < 4.0f/6.0f) + return m1 + (m2 - m1) * (2.0f/3.0f - h) * 6.0f; + return m1; +} + +NVGcolor nvgHSLA(float h, float s, float l, unsigned char a) +{ + float m1, m2; + NVGcolor col; + h = nvg__modf(h, 1.0f); + if (h < 0.0f) h += 1.0f; + s = nvg__clampf(s, 0.0f, 1.0f); + l = nvg__clampf(l, 0.0f, 1.0f); + m2 = l <= 0.5f ? (l * (1 + s)) : (l + s - l * s); + m1 = 2 * l - m2; + col.r = nvg__clampf(nvg__hue(h + 1.0f/3.0f, m1, m2), 0.0f, 1.0f); + col.g = nvg__clampf(nvg__hue(h, m1, m2), 0.0f, 1.0f); + col.b = nvg__clampf(nvg__hue(h - 1.0f/3.0f, m1, m2), 0.0f, 1.0f); + col.a = a/255.0f; + return col; +} + +void nvgTransformIdentity(float* t) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = 0.0f; t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + +void nvgTransformTranslate(float* t, float tx, float ty) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = 0.0f; t[3] = 1.0f; + t[4] = tx; t[5] = ty; +} + +void nvgTransformScale(float* t, float sx, float sy) +{ + t[0] = sx; t[1] = 0.0f; + t[2] = 0.0f; t[3] = sy; + t[4] = 0.0f; t[5] = 0.0f; +} + +void nvgTransformRotate(float* t, float a) +{ + float cs = nvg__cosf(a), sn = nvg__sinf(a); + t[0] = cs; t[1] = sn; + t[2] = -sn; t[3] = cs; + t[4] = 0.0f; t[5] = 0.0f; +} + +void nvgTransformSkewX(float* t, float a) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = nvg__tanf(a); t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + +void nvgTransformSkewY(float* t, float a) +{ + t[0] = 1.0f; t[1] = nvg__tanf(a); + t[2] = 0.0f; t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + +void nvgTransformMultiply(float* t, const float* s) +{ + float t0 = t[0] * s[0] + t[1] * s[2]; + float t2 = t[2] * s[0] + t[3] * s[2]; + float t4 = t[4] * s[0] + t[5] * s[2] + s[4]; + t[1] = t[0] * s[1] + t[1] * s[3]; + t[3] = t[2] * s[1] + t[3] * s[3]; + t[5] = t[4] * s[1] + t[5] * s[3] + s[5]; + t[0] = t0; + t[2] = t2; + t[4] = t4; +} + +void nvgTransformPremultiply(float* t, const float* s) +{ + float s2[6]; + memcpy(s2, s, sizeof(float)*6); + nvgTransformMultiply(s2, t); + memcpy(t, s2, sizeof(float)*6); +} + +int nvgTransformInverse(float* inv, const float* t) +{ + double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1]; + if (det > -1e-6 && det < 1e-6) { + nvgTransformIdentity(inv); + return 0; + } + invdet = 1.0 / det; + inv[0] = (float)(t[3] * invdet); + inv[2] = (float)(-t[2] * invdet); + inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet); + inv[1] = (float)(-t[1] * invdet); + inv[3] = (float)(t[0] * invdet); + inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet); + return 1; +} + +void nvgTransformPoint(float* dx, float* dy, const float* t, float sx, float sy) +{ + *dx = sx*t[0] + sy*t[2] + t[4]; + *dy = sx*t[1] + sy*t[3] + t[5]; +} + +float nvgDegToRad(float deg) +{ + return deg / 180.0f * NVG_PI; +} + +float nvgRadToDeg(float rad) +{ + return rad / NVG_PI * 180.0f; +} + +static void nvg__setPaintColor(NVGpaint* p, NVGcolor color) +{ + memset(p, 0, sizeof(*p)); + nvgTransformIdentity(p->xform); + p->radius = 0.0f; + p->feather = 1.0f; + p->innerColor = color; + p->outerColor = color; +} + + +// State handling +void nvgSave(NVGcontext* ctx) +{ + if (ctx->nstates >= NVG_MAX_STATES) + return; + if (ctx->nstates > 0) + memcpy(&ctx->states[ctx->nstates], &ctx->states[ctx->nstates-1], sizeof(NVGstate)); + ctx->nstates++; +} + +void nvgRestore(NVGcontext* ctx) +{ + if (ctx->nstates <= 1) + return; + ctx->nstates--; +} + +void nvgReset(NVGcontext* ctx) +{ + NVGstate* state = nvg__getState(ctx); + memset(state, 0, sizeof(*state)); + + nvg__setPaintColor(&state->fill, nvgRGBA(255,255,255,255)); + nvg__setPaintColor(&state->stroke, nvgRGBA(0,0,0,255)); + state->compositeOperation = nvg__compositeOperationState(NVG_SOURCE_OVER); + state->shapeAntiAlias = 1; + state->strokeWidth = 1.0f; + state->miterLimit = 10.0f; + state->lineCap = NVG_BUTT; + state->lineJoin = NVG_MITER; + state->alpha = 1.0f; + nvgTransformIdentity(state->xform); + + state->scissor.extent[0] = -1.0f; + state->scissor.extent[1] = -1.0f; + + state->fontSize = 16.0f; + state->letterSpacing = 0.0f; + state->lineHeight = 1.0f; + state->fontBlur = 0.0f; + state->textAlign = NVG_ALIGN_LEFT | NVG_ALIGN_BASELINE; + state->fontId = 0; +} + +// State setting +void nvgShapeAntiAlias(NVGcontext* ctx, int enabled) +{ + NVGstate* state = nvg__getState(ctx); + state->shapeAntiAlias = enabled; +} + +void nvgStrokeWidth(NVGcontext* ctx, float width) +{ + NVGstate* state = nvg__getState(ctx); + state->strokeWidth = width; +} + +void nvgMiterLimit(NVGcontext* ctx, float limit) +{ + NVGstate* state = nvg__getState(ctx); + state->miterLimit = limit; +} + +void nvgLineCap(NVGcontext* ctx, int cap) +{ + NVGstate* state = nvg__getState(ctx); + state->lineCap = cap; +} + +void nvgLineJoin(NVGcontext* ctx, int join) +{ + NVGstate* state = nvg__getState(ctx); + state->lineJoin = join; +} + +void nvgGlobalAlpha(NVGcontext* ctx, float alpha) +{ + NVGstate* state = nvg__getState(ctx); + state->alpha = alpha; +} + +void nvgTransform(NVGcontext* ctx, float a, float b, float c, float d, float e, float f) +{ + NVGstate* state = nvg__getState(ctx); + float t[6] = { a, b, c, d, e, f }; + nvgTransformPremultiply(state->xform, t); +} + +void nvgResetTransform(NVGcontext* ctx) +{ + NVGstate* state = nvg__getState(ctx); + nvgTransformIdentity(state->xform); +} + +void nvgTranslate(NVGcontext* ctx, float x, float y) +{ + NVGstate* state = nvg__getState(ctx); + float t[6]; + nvgTransformTranslate(t, x,y); + nvgTransformPremultiply(state->xform, t); +} + +void nvgRotate(NVGcontext* ctx, float angle) +{ + NVGstate* state = nvg__getState(ctx); + float t[6]; + nvgTransformRotate(t, angle); + nvgTransformPremultiply(state->xform, t); +} + +void nvgSkewX(NVGcontext* ctx, float angle) +{ + NVGstate* state = nvg__getState(ctx); + float t[6]; + nvgTransformSkewX(t, angle); + nvgTransformPremultiply(state->xform, t); +} + +void nvgSkewY(NVGcontext* ctx, float angle) +{ + NVGstate* state = nvg__getState(ctx); + float t[6]; + nvgTransformSkewY(t, angle); + nvgTransformPremultiply(state->xform, t); +} + +void nvgScale(NVGcontext* ctx, float x, float y) +{ + NVGstate* state = nvg__getState(ctx); + float t[6]; + nvgTransformScale(t, x,y); + nvgTransformPremultiply(state->xform, t); +} + +void nvgCurrentTransform(NVGcontext* ctx, float* xform) +{ + NVGstate* state = nvg__getState(ctx); + if (xform == NULL) return; + memcpy(xform, state->xform, sizeof(float)*6); +} + +void nvgStrokeColor(NVGcontext* ctx, NVGcolor color) +{ + NVGstate* state = nvg__getState(ctx); + nvg__setPaintColor(&state->stroke, color); +} + +void nvgStrokePaint(NVGcontext* ctx, NVGpaint paint) +{ + NVGstate* state = nvg__getState(ctx); + state->stroke = paint; + nvgTransformMultiply(state->stroke.xform, state->xform); +} + +void nvgFillColor(NVGcontext* ctx, NVGcolor color) +{ + NVGstate* state = nvg__getState(ctx); + nvg__setPaintColor(&state->fill, color); +} + +void nvgFillPaint(NVGcontext* ctx, NVGpaint paint) +{ + NVGstate* state = nvg__getState(ctx); + state->fill = paint; + nvgTransformMultiply(state->fill.xform, state->xform); +} + +int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags) +{ + int w, h, n, image; + unsigned char* img; + stbi_set_unpremultiply_on_load(1); + stbi_convert_iphone_png_to_rgb(1); + img = stbi_load(filename, &w, &h, &n, 4); + if (img == NULL) { +// printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); + return 0; + } + image = nvgCreateImageRGBA(ctx, w, h, imageFlags, img); + stbi_image_free(img); + return image; +} + +int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int ndata) +{ + int w, h, n, image; + unsigned char* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4); + if (img == NULL) { +// printf("Failed to load %s - %s\n", filename, stbi_failure_reason()); + return 0; + } + image = nvgCreateImageRGBA(ctx, w, h, imageFlags, img); + stbi_image_free(img); + return image; +} + +int nvgCreateImageRGBA(NVGcontext* ctx, int w, int h, int imageFlags, const unsigned char* data) +{ + return ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_RGBA, w, h, imageFlags, data); +} + +void nvgUpdateImage(NVGcontext* ctx, int image, const unsigned char* data) +{ + int w, h; + ctx->params.renderGetTextureSize(ctx->params.userPtr, image, &w, &h); + ctx->params.renderUpdateTexture(ctx->params.userPtr, image, 0,0, w,h, data); +} + +void nvgImageSize(NVGcontext* ctx, int image, int* w, int* h) +{ + ctx->params.renderGetTextureSize(ctx->params.userPtr, image, w, h); +} + +void nvgDeleteImage(NVGcontext* ctx, int image) +{ + ctx->params.renderDeleteTexture(ctx->params.userPtr, image); +} + +NVGpaint nvgLinearGradient(NVGcontext* ctx, + float sx, float sy, float ex, float ey, + NVGcolor icol, NVGcolor ocol) +{ + NVGpaint p; + float dx, dy, d; + const float large = 1e5; + NVG_NOTUSED(ctx); + memset(&p, 0, sizeof(p)); + + // Calculate transform aligned to the line + dx = ex - sx; + dy = ey - sy; + d = sqrtf(dx*dx + dy*dy); + if (d > 0.0001f) { + dx /= d; + dy /= d; + } else { + dx = 0; + dy = 1; + } + + p.xform[0] = dy; p.xform[1] = -dx; + p.xform[2] = dx; p.xform[3] = dy; + p.xform[4] = sx - dx*large; p.xform[5] = sy - dy*large; + + p.extent[0] = large; + p.extent[1] = large + d*0.5f; + + p.radius = 0.0f; + + p.feather = nvg__maxf(1.0f, d); + + p.innerColor = icol; + p.outerColor = ocol; + + return p; +} + +NVGpaint nvgRadialGradient(NVGcontext* ctx, + float cx, float cy, float inr, float outr, + NVGcolor icol, NVGcolor ocol) +{ + NVGpaint p; + float r = (inr+outr)*0.5f; + float f = (outr-inr); + NVG_NOTUSED(ctx); + memset(&p, 0, sizeof(p)); + + nvgTransformIdentity(p.xform); + p.xform[4] = cx; + p.xform[5] = cy; + + p.extent[0] = r; + p.extent[1] = r; + + p.radius = r; + + p.feather = nvg__maxf(1.0f, f); + + p.innerColor = icol; + p.outerColor = ocol; + + return p; +} + +NVGpaint nvgBoxGradient(NVGcontext* ctx, + float x, float y, float w, float h, float r, float f, + NVGcolor icol, NVGcolor ocol) +{ + NVGpaint p; + NVG_NOTUSED(ctx); + memset(&p, 0, sizeof(p)); + + nvgTransformIdentity(p.xform); + p.xform[4] = x+w*0.5f; + p.xform[5] = y+h*0.5f; + + p.extent[0] = w*0.5f; + p.extent[1] = h*0.5f; + + p.radius = r; + + p.feather = nvg__maxf(1.0f, f); + + p.innerColor = icol; + p.outerColor = ocol; + + return p; +} + + +NVGpaint nvgImagePattern(NVGcontext* ctx, + float cx, float cy, float w, float h, float angle, + int image, float alpha) +{ + NVGpaint p; + NVG_NOTUSED(ctx); + memset(&p, 0, sizeof(p)); + + nvgTransformRotate(p.xform, angle); + p.xform[4] = cx; + p.xform[5] = cy; + + p.extent[0] = w; + p.extent[1] = h; + + p.image = image; + + p.innerColor = p.outerColor = nvgRGBAf(1,1,1,alpha); + + return p; +} + +// Scissoring +void nvgScissor(NVGcontext* ctx, float x, float y, float w, float h) +{ + NVGstate* state = nvg__getState(ctx); + + w = nvg__maxf(0.0f, w); + h = nvg__maxf(0.0f, h); + + nvgTransformIdentity(state->scissor.xform); + state->scissor.xform[4] = x+w*0.5f; + state->scissor.xform[5] = y+h*0.5f; + nvgTransformMultiply(state->scissor.xform, state->xform); + + state->scissor.extent[0] = w*0.5f; + state->scissor.extent[1] = h*0.5f; +} + +static void nvg__isectRects(float* dst, + float ax, float ay, float aw, float ah, + float bx, float by, float bw, float bh) +{ + float minx = nvg__maxf(ax, bx); + float miny = nvg__maxf(ay, by); + float maxx = nvg__minf(ax+aw, bx+bw); + float maxy = nvg__minf(ay+ah, by+bh); + dst[0] = minx; + dst[1] = miny; + dst[2] = nvg__maxf(0.0f, maxx - minx); + dst[3] = nvg__maxf(0.0f, maxy - miny); +} + +void nvgIntersectScissor(NVGcontext* ctx, float x, float y, float w, float h) +{ + NVGstate* state = nvg__getState(ctx); + float pxform[6], invxorm[6]; + float rect[4]; + float ex, ey, tex, tey; + + // If no previous scissor has been set, set the scissor as current scissor. + if (state->scissor.extent[0] < 0) { + nvgScissor(ctx, x, y, w, h); + return; + } + + // Transform the current scissor rect into current transform space. + // If there is difference in rotation, this will be approximation. + memcpy(pxform, state->scissor.xform, sizeof(float)*6); + ex = state->scissor.extent[0]; + ey = state->scissor.extent[1]; + nvgTransformInverse(invxorm, state->xform); + nvgTransformMultiply(pxform, invxorm); + tex = ex*nvg__absf(pxform[0]) + ey*nvg__absf(pxform[2]); + tey = ex*nvg__absf(pxform[1]) + ey*nvg__absf(pxform[3]); + + // Intersect rects. + nvg__isectRects(rect, pxform[4]-tex,pxform[5]-tey,tex*2,tey*2, x,y,w,h); + + nvgScissor(ctx, rect[0], rect[1], rect[2], rect[3]); +} + +void nvgResetScissor(NVGcontext* ctx) +{ + NVGstate* state = nvg__getState(ctx); + memset(state->scissor.xform, 0, sizeof(state->scissor.xform)); + state->scissor.extent[0] = -1.0f; + state->scissor.extent[1] = -1.0f; +} + +// Global composite operation. +void nvgGlobalCompositeOperation(NVGcontext* ctx, int op) +{ + NVGstate* state = nvg__getState(ctx); + state->compositeOperation = nvg__compositeOperationState(op); +} + +void nvgGlobalCompositeBlendFunc(NVGcontext* ctx, int sfactor, int dfactor) +{ + nvgGlobalCompositeBlendFuncSeparate(ctx, sfactor, dfactor, sfactor, dfactor); +} + +void nvgGlobalCompositeBlendFuncSeparate(NVGcontext* ctx, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) +{ + NVGcompositeOperationState op; + op.srcRGB = srcRGB; + op.dstRGB = dstRGB; + op.srcAlpha = srcAlpha; + op.dstAlpha = dstAlpha; + + NVGstate* state = nvg__getState(ctx); + state->compositeOperation = op; +} + +static int nvg__ptEquals(float x1, float y1, float x2, float y2, float tol) +{ + float dx = x2 - x1; + float dy = y2 - y1; + return dx*dx + dy*dy < tol*tol; +} + +static float nvg__distPtSeg(float x, float y, float px, float py, float qx, float qy) +{ + float pqx, pqy, dx, dy, d, t; + pqx = qx-px; + pqy = qy-py; + dx = x-px; + dy = y-py; + d = pqx*pqx + pqy*pqy; + t = pqx*dx + pqy*dy; + if (d > 0) t /= d; + if (t < 0) t = 0; + else if (t > 1) t = 1; + dx = px + t*pqx - x; + dy = py + t*pqy - y; + return dx*dx + dy*dy; +} + +static void nvg__appendCommands(NVGcontext* ctx, float* vals, int nvals) +{ + NVGstate* state = nvg__getState(ctx); + int i; + + if (ctx->ncommands+nvals > ctx->ccommands) { + float* commands; + int ccommands = ctx->ncommands+nvals + ctx->ccommands/2; + commands = (float*)realloc(ctx->commands, sizeof(float)*ccommands); + if (commands == NULL) return; + ctx->commands = commands; + ctx->ccommands = ccommands; + } + + if ((int)vals[0] != NVG_CLOSE && (int)vals[0] != NVG_WINDING) { + ctx->commandx = vals[nvals-2]; + ctx->commandy = vals[nvals-1]; + } + + // transform commands + i = 0; + while (i < nvals) { + int cmd = (int)vals[i]; + switch (cmd) { + case NVG_MOVETO: + nvgTransformPoint(&vals[i+1],&vals[i+2], state->xform, vals[i+1],vals[i+2]); + i += 3; + break; + case NVG_LINETO: + nvgTransformPoint(&vals[i+1],&vals[i+2], state->xform, vals[i+1],vals[i+2]); + i += 3; + break; + case NVG_BEZIERTO: + nvgTransformPoint(&vals[i+1],&vals[i+2], state->xform, vals[i+1],vals[i+2]); + nvgTransformPoint(&vals[i+3],&vals[i+4], state->xform, vals[i+3],vals[i+4]); + nvgTransformPoint(&vals[i+5],&vals[i+6], state->xform, vals[i+5],vals[i+6]); + i += 7; + break; + case NVG_CLOSE: + i++; + break; + case NVG_WINDING: + i += 2; + break; + default: + i++; + } + } + + memcpy(&ctx->commands[ctx->ncommands], vals, nvals*sizeof(float)); + + ctx->ncommands += nvals; +} + + +static void nvg__clearPathCache(NVGcontext* ctx) +{ + ctx->cache->npoints = 0; + ctx->cache->npaths = 0; +} + +static NVGpath* nvg__lastPath(NVGcontext* ctx) +{ + if (ctx->cache->npaths > 0) + return &ctx->cache->paths[ctx->cache->npaths-1]; + return NULL; +} + +static void nvg__addPath(NVGcontext* ctx) +{ + NVGpath* path; + if (ctx->cache->npaths+1 > ctx->cache->cpaths) { + NVGpath* paths; + int cpaths = ctx->cache->npaths+1 + ctx->cache->cpaths/2; + paths = (NVGpath*)realloc(ctx->cache->paths, sizeof(NVGpath)*cpaths); + if (paths == NULL) return; + ctx->cache->paths = paths; + ctx->cache->cpaths = cpaths; + } + path = &ctx->cache->paths[ctx->cache->npaths]; + memset(path, 0, sizeof(*path)); + path->first = ctx->cache->npoints; + path->winding = NVG_CCW; + + ctx->cache->npaths++; +} + +static NVGpoint* nvg__lastPoint(NVGcontext* ctx) +{ + if (ctx->cache->npoints > 0) + return &ctx->cache->points[ctx->cache->npoints-1]; + return NULL; +} + +static void nvg__addPoint(NVGcontext* ctx, float x, float y, int flags) +{ + NVGpath* path = nvg__lastPath(ctx); + NVGpoint* pt; + if (path == NULL) return; + + if (path->count > 0 && ctx->cache->npoints > 0) { + pt = nvg__lastPoint(ctx); + if (nvg__ptEquals(pt->x,pt->y, x,y, ctx->distTol)) { + pt->flags |= flags; + return; + } + } + + if (ctx->cache->npoints+1 > ctx->cache->cpoints) { + NVGpoint* points; + int cpoints = ctx->cache->npoints+1 + ctx->cache->cpoints/2; + points = (NVGpoint*)realloc(ctx->cache->points, sizeof(NVGpoint)*cpoints); + if (points == NULL) return; + ctx->cache->points = points; + ctx->cache->cpoints = cpoints; + } + + pt = &ctx->cache->points[ctx->cache->npoints]; + memset(pt, 0, sizeof(*pt)); + pt->x = x; + pt->y = y; + pt->flags = (unsigned char)flags; + + ctx->cache->npoints++; + path->count++; +} + +static void nvg__closePath(NVGcontext* ctx) +{ + NVGpath* path = nvg__lastPath(ctx); + if (path == NULL) return; + path->closed = 1; +} + +static void nvg__pathWinding(NVGcontext* ctx, int winding) +{ + NVGpath* path = nvg__lastPath(ctx); + if (path == NULL) return; + path->winding = winding; +} + +static float nvg__getAverageScale(float *t) +{ + float sx = sqrtf(t[0]*t[0] + t[2]*t[2]); + float sy = sqrtf(t[1]*t[1] + t[3]*t[3]); + return (sx + sy) * 0.5f; +} + +static NVGvertex* nvg__allocTempVerts(NVGcontext* ctx, int nverts) +{ + if (nverts > ctx->cache->cverts) { + NVGvertex* verts; + int cverts = (nverts + 0xff) & ~0xff; // Round up to prevent allocations when things change just slightly. + verts = (NVGvertex*)realloc(ctx->cache->verts, sizeof(NVGvertex)*cverts); + if (verts == NULL) return NULL; + ctx->cache->verts = verts; + ctx->cache->cverts = cverts; + } + + return ctx->cache->verts; +} + +static float nvg__triarea2(float ax, float ay, float bx, float by, float cx, float cy) +{ + float abx = bx - ax; + float aby = by - ay; + float acx = cx - ax; + float acy = cy - ay; + return acx*aby - abx*acy; +} + +static float nvg__polyArea(NVGpoint* pts, int npts) +{ + int i; + float area = 0; + for (i = 2; i < npts; i++) { + NVGpoint* a = &pts[0]; + NVGpoint* b = &pts[i-1]; + NVGpoint* c = &pts[i]; + area += nvg__triarea2(a->x,a->y, b->x,b->y, c->x,c->y); + } + return area * 0.5f; +} + +static void nvg__polyReverse(NVGpoint* pts, int npts) +{ + NVGpoint tmp; + int i = 0, j = npts-1; + while (i < j) { + tmp = pts[i]; + pts[i] = pts[j]; + pts[j] = tmp; + i++; + j--; + } +} + + +static void nvg__vset(NVGvertex* vtx, float x, float y, float u, float v) +{ + vtx->x = x; + vtx->y = y; + vtx->u = u; + vtx->v = v; +} + +static void nvg__tesselateBezier(NVGcontext* ctx, + float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4, + int level, int type) +{ + float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234; + float dx,dy,d2,d3; + + if (level > 10) return; + + x12 = (x1+x2)*0.5f; + y12 = (y1+y2)*0.5f; + x23 = (x2+x3)*0.5f; + y23 = (y2+y3)*0.5f; + x34 = (x3+x4)*0.5f; + y34 = (y3+y4)*0.5f; + x123 = (x12+x23)*0.5f; + y123 = (y12+y23)*0.5f; + + dx = x4 - x1; + dy = y4 - y1; + d2 = nvg__absf(((x2 - x4) * dy - (y2 - y4) * dx)); + d3 = nvg__absf(((x3 - x4) * dy - (y3 - y4) * dx)); + + if ((d2 + d3)*(d2 + d3) < ctx->tessTol * (dx*dx + dy*dy)) { + nvg__addPoint(ctx, x4, y4, type); + return; + } + +/* if (nvg__absf(x1+x3-x2-x2) + nvg__absf(y1+y3-y2-y2) + nvg__absf(x2+x4-x3-x3) + nvg__absf(y2+y4-y3-y3) < ctx->tessTol) { + nvg__addPoint(ctx, x4, y4, type); + return; + }*/ + + x234 = (x23+x34)*0.5f; + y234 = (y23+y34)*0.5f; + x1234 = (x123+x234)*0.5f; + y1234 = (y123+y234)*0.5f; + + nvg__tesselateBezier(ctx, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, 0); + nvg__tesselateBezier(ctx, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type); +} + +static void nvg__flattenPaths(NVGcontext* ctx) +{ + NVGpathCache* cache = ctx->cache; +// NVGstate* state = nvg__getState(ctx); + NVGpoint* last; + NVGpoint* p0; + NVGpoint* p1; + NVGpoint* pts; + NVGpath* path; + int i, j; + float* cp1; + float* cp2; + float* p; + float area; + + if (cache->npaths > 0) + return; + + // Flatten + i = 0; + while (i < ctx->ncommands) { + int cmd = (int)ctx->commands[i]; + switch (cmd) { + case NVG_MOVETO: + nvg__addPath(ctx); + p = &ctx->commands[i+1]; + nvg__addPoint(ctx, p[0], p[1], NVG_PT_CORNER); + i += 3; + break; + case NVG_LINETO: + p = &ctx->commands[i+1]; + nvg__addPoint(ctx, p[0], p[1], NVG_PT_CORNER); + i += 3; + break; + case NVG_BEZIERTO: + last = nvg__lastPoint(ctx); + if (last != NULL) { + cp1 = &ctx->commands[i+1]; + cp2 = &ctx->commands[i+3]; + p = &ctx->commands[i+5]; + nvg__tesselateBezier(ctx, last->x,last->y, cp1[0],cp1[1], cp2[0],cp2[1], p[0],p[1], 0, NVG_PT_CORNER); + } + i += 7; + break; + case NVG_CLOSE: + nvg__closePath(ctx); + i++; + break; + case NVG_WINDING: + nvg__pathWinding(ctx, (int)ctx->commands[i+1]); + i += 2; + break; + default: + i++; + } + } + + cache->bounds[0] = cache->bounds[1] = 1e6f; + cache->bounds[2] = cache->bounds[3] = -1e6f; + + // Calculate the direction and length of line segments. + for (j = 0; j < cache->npaths; j++) { + path = &cache->paths[j]; + pts = &cache->points[path->first]; + + // If the first and last points are the same, remove the last, mark as closed path. + p0 = &pts[path->count-1]; + p1 = &pts[0]; + if (nvg__ptEquals(p0->x,p0->y, p1->x,p1->y, ctx->distTol)) { + path->count--; + p0 = &pts[path->count-1]; + path->closed = 1; + } + + // Enforce winding. + if (path->count > 2) { + area = nvg__polyArea(pts, path->count); + if (path->winding == NVG_CCW && area < 0.0f) + nvg__polyReverse(pts, path->count); + if (path->winding == NVG_CW && area > 0.0f) + nvg__polyReverse(pts, path->count); + } + + for(i = 0; i < path->count; i++) { + // Calculate segment direction and length + p0->dx = p1->x - p0->x; + p0->dy = p1->y - p0->y; + p0->len = nvg__normalize(&p0->dx, &p0->dy); + // Update bounds + cache->bounds[0] = nvg__minf(cache->bounds[0], p0->x); + cache->bounds[1] = nvg__minf(cache->bounds[1], p0->y); + cache->bounds[2] = nvg__maxf(cache->bounds[2], p0->x); + cache->bounds[3] = nvg__maxf(cache->bounds[3], p0->y); + // Advance + p0 = p1++; + } + } +} + +static int nvg__curveDivs(float r, float arc, float tol) +{ + float da = acosf(r / (r + tol)) * 2.0f; + return nvg__maxi(2, (int)ceilf(arc / da)); +} + +static void nvg__chooseBevel(int bevel, NVGpoint* p0, NVGpoint* p1, float w, + float* x0, float* y0, float* x1, float* y1) +{ + if (bevel) { + *x0 = p1->x + p0->dy * w; + *y0 = p1->y - p0->dx * w; + *x1 = p1->x + p1->dy * w; + *y1 = p1->y - p1->dx * w; + } else { + *x0 = p1->x + p1->dmx * w; + *y0 = p1->y + p1->dmy * w; + *x1 = p1->x + p1->dmx * w; + *y1 = p1->y + p1->dmy * w; + } +} + +static NVGvertex* nvg__roundJoin(NVGvertex* dst, NVGpoint* p0, NVGpoint* p1, + float lw, float rw, float lu, float ru, int ncap, + float fringe) +{ + int i, n; + float dlx0 = p0->dy; + float dly0 = -p0->dx; + float dlx1 = p1->dy; + float dly1 = -p1->dx; + NVG_NOTUSED(fringe); + + if (p1->flags & NVG_PT_LEFT) { + float lx0,ly0,lx1,ly1,a0,a1; + nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, lw, &lx0,&ly0, &lx1,&ly1); + a0 = atan2f(-dly0, -dlx0); + a1 = atan2f(-dly1, -dlx1); + if (a1 > a0) a1 -= NVG_PI*2; + + nvg__vset(dst, lx0, ly0, lu,1); dst++; + nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++; + + n = nvg__clampi((int)ceilf(((a0 - a1) / NVG_PI) * ncap), 2, ncap); + for (i = 0; i < n; i++) { + float u = i/(float)(n-1); + float a = a0 + u*(a1-a0); + float rx = p1->x + cosf(a) * rw; + float ry = p1->y + sinf(a) * rw; + nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++; + nvg__vset(dst, rx, ry, ru,1); dst++; + } + + nvg__vset(dst, lx1, ly1, lu,1); dst++; + nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++; + + } else { + float rx0,ry0,rx1,ry1,a0,a1; + nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, -rw, &rx0,&ry0, &rx1,&ry1); + a0 = atan2f(dly0, dlx0); + a1 = atan2f(dly1, dlx1); + if (a1 < a0) a1 += NVG_PI*2; + + nvg__vset(dst, p1->x + dlx0*rw, p1->y + dly0*rw, lu,1); dst++; + nvg__vset(dst, rx0, ry0, ru,1); dst++; + + n = nvg__clampi((int)ceilf(((a1 - a0) / NVG_PI) * ncap), 2, ncap); + for (i = 0; i < n; i++) { + float u = i/(float)(n-1); + float a = a0 + u*(a1-a0); + float lx = p1->x + cosf(a) * lw; + float ly = p1->y + sinf(a) * lw; + nvg__vset(dst, lx, ly, lu,1); dst++; + nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++; + } + + nvg__vset(dst, p1->x + dlx1*rw, p1->y + dly1*rw, lu,1); dst++; + nvg__vset(dst, rx1, ry1, ru,1); dst++; + + } + return dst; +} + +static NVGvertex* nvg__bevelJoin(NVGvertex* dst, NVGpoint* p0, NVGpoint* p1, + float lw, float rw, float lu, float ru, float fringe) +{ + float rx0,ry0,rx1,ry1; + float lx0,ly0,lx1,ly1; + float dlx0 = p0->dy; + float dly0 = -p0->dx; + float dlx1 = p1->dy; + float dly1 = -p1->dx; + NVG_NOTUSED(fringe); + + if (p1->flags & NVG_PT_LEFT) { + nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, lw, &lx0,&ly0, &lx1,&ly1); + + nvg__vset(dst, lx0, ly0, lu,1); dst++; + nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++; + + if (p1->flags & NVG_PT_BEVEL) { + nvg__vset(dst, lx0, ly0, lu,1); dst++; + nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++; + + nvg__vset(dst, lx1, ly1, lu,1); dst++; + nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++; + } else { + rx0 = p1->x - p1->dmx * rw; + ry0 = p1->y - p1->dmy * rw; + + nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++; + nvg__vset(dst, p1->x - dlx0*rw, p1->y - dly0*rw, ru,1); dst++; + + nvg__vset(dst, rx0, ry0, ru,1); dst++; + nvg__vset(dst, rx0, ry0, ru,1); dst++; + + nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++; + nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++; + } + + nvg__vset(dst, lx1, ly1, lu,1); dst++; + nvg__vset(dst, p1->x - dlx1*rw, p1->y - dly1*rw, ru,1); dst++; + + } else { + nvg__chooseBevel(p1->flags & NVG_PR_INNERBEVEL, p0, p1, -rw, &rx0,&ry0, &rx1,&ry1); + + nvg__vset(dst, p1->x + dlx0*lw, p1->y + dly0*lw, lu,1); dst++; + nvg__vset(dst, rx0, ry0, ru,1); dst++; + + if (p1->flags & NVG_PT_BEVEL) { + nvg__vset(dst, p1->x + dlx0*lw, p1->y + dly0*lw, lu,1); dst++; + nvg__vset(dst, rx0, ry0, ru,1); dst++; + + nvg__vset(dst, p1->x + dlx1*lw, p1->y + dly1*lw, lu,1); dst++; + nvg__vset(dst, rx1, ry1, ru,1); dst++; + } else { + lx0 = p1->x + p1->dmx * lw; + ly0 = p1->y + p1->dmy * lw; + + nvg__vset(dst, p1->x + dlx0*lw, p1->y + dly0*lw, lu,1); dst++; + nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++; + + nvg__vset(dst, lx0, ly0, lu,1); dst++; + nvg__vset(dst, lx0, ly0, lu,1); dst++; + + nvg__vset(dst, p1->x + dlx1*lw, p1->y + dly1*lw, lu,1); dst++; + nvg__vset(dst, p1->x, p1->y, 0.5f,1); dst++; + } + + nvg__vset(dst, p1->x + dlx1*lw, p1->y + dly1*lw, lu,1); dst++; + nvg__vset(dst, rx1, ry1, ru,1); dst++; + } + + return dst; +} + +static NVGvertex* nvg__buttCapStart(NVGvertex* dst, NVGpoint* p, + float dx, float dy, float w, float d, + float aa, float u0, float u1) +{ + float px = p->x - dx*d; + float py = p->y - dy*d; + float dlx = dy; + float dly = -dx; + nvg__vset(dst, px + dlx*w - dx*aa, py + dly*w - dy*aa, u0,0); dst++; + nvg__vset(dst, px - dlx*w - dx*aa, py - dly*w - dy*aa, u1,0); dst++; + nvg__vset(dst, px + dlx*w, py + dly*w, u0,1); dst++; + nvg__vset(dst, px - dlx*w, py - dly*w, u1,1); dst++; + return dst; +} + +static NVGvertex* nvg__buttCapEnd(NVGvertex* dst, NVGpoint* p, + float dx, float dy, float w, float d, + float aa, float u0, float u1) +{ + float px = p->x + dx*d; + float py = p->y + dy*d; + float dlx = dy; + float dly = -dx; + nvg__vset(dst, px + dlx*w, py + dly*w, u0,1); dst++; + nvg__vset(dst, px - dlx*w, py - dly*w, u1,1); dst++; + nvg__vset(dst, px + dlx*w + dx*aa, py + dly*w + dy*aa, u0,0); dst++; + nvg__vset(dst, px - dlx*w + dx*aa, py - dly*w + dy*aa, u1,0); dst++; + return dst; +} + + +static NVGvertex* nvg__roundCapStart(NVGvertex* dst, NVGpoint* p, + float dx, float dy, float w, int ncap, + float aa, float u0, float u1) +{ + int i; + float px = p->x; + float py = p->y; + float dlx = dy; + float dly = -dx; + NVG_NOTUSED(aa); + for (i = 0; i < ncap; i++) { + float a = i/(float)(ncap-1)*NVG_PI; + float ax = cosf(a) * w, ay = sinf(a) * w; + nvg__vset(dst, px - dlx*ax - dx*ay, py - dly*ax - dy*ay, u0,1); dst++; + nvg__vset(dst, px, py, 0.5f,1); dst++; + } + nvg__vset(dst, px + dlx*w, py + dly*w, u0,1); dst++; + nvg__vset(dst, px - dlx*w, py - dly*w, u1,1); dst++; + return dst; +} + +static NVGvertex* nvg__roundCapEnd(NVGvertex* dst, NVGpoint* p, + float dx, float dy, float w, int ncap, + float aa, float u0, float u1) +{ + int i; + float px = p->x; + float py = p->y; + float dlx = dy; + float dly = -dx; + NVG_NOTUSED(aa); + nvg__vset(dst, px + dlx*w, py + dly*w, u0,1); dst++; + nvg__vset(dst, px - dlx*w, py - dly*w, u1,1); dst++; + for (i = 0; i < ncap; i++) { + float a = i/(float)(ncap-1)*NVG_PI; + float ax = cosf(a) * w, ay = sinf(a) * w; + nvg__vset(dst, px, py, 0.5f,1); dst++; + nvg__vset(dst, px - dlx*ax + dx*ay, py - dly*ax + dy*ay, u0,1); dst++; + } + return dst; +} + + +static void nvg__calculateJoins(NVGcontext* ctx, float w, int lineJoin, float miterLimit) +{ + NVGpathCache* cache = ctx->cache; + int i, j; + float iw = 0.0f; + + if (w > 0.0f) iw = 1.0f / w; + + // Calculate which joins needs extra vertices to append, and gather vertex count. + for (i = 0; i < cache->npaths; i++) { + NVGpath* path = &cache->paths[i]; + NVGpoint* pts = &cache->points[path->first]; + NVGpoint* p0 = &pts[path->count-1]; + NVGpoint* p1 = &pts[0]; + int nleft = 0; + + path->nbevel = 0; + + for (j = 0; j < path->count; j++) { + float dlx0, dly0, dlx1, dly1, dmr2, cross, limit; + dlx0 = p0->dy; + dly0 = -p0->dx; + dlx1 = p1->dy; + dly1 = -p1->dx; + // Calculate extrusions + p1->dmx = (dlx0 + dlx1) * 0.5f; + p1->dmy = (dly0 + dly1) * 0.5f; + dmr2 = p1->dmx*p1->dmx + p1->dmy*p1->dmy; + if (dmr2 > 0.000001f) { + float scale = 1.0f / dmr2; + if (scale > 600.0f) { + scale = 600.0f; + } + p1->dmx *= scale; + p1->dmy *= scale; + } + + // Clear flags, but keep the corner. + p1->flags = (p1->flags & NVG_PT_CORNER) ? NVG_PT_CORNER : 0; + + // Keep track of left turns. + cross = p1->dx * p0->dy - p0->dx * p1->dy; + if (cross > 0.0f) { + nleft++; + p1->flags |= NVG_PT_LEFT; + } + + // Calculate if we should use bevel or miter for inner join. + limit = nvg__maxf(1.01f, nvg__minf(p0->len, p1->len) * iw); + if ((dmr2 * limit*limit) < 1.0f) + p1->flags |= NVG_PR_INNERBEVEL; + + // Check to see if the corner needs to be beveled. + if (p1->flags & NVG_PT_CORNER) { + if ((dmr2 * miterLimit*miterLimit) < 1.0f || lineJoin == NVG_BEVEL || lineJoin == NVG_ROUND) { + p1->flags |= NVG_PT_BEVEL; + } + } + + if ((p1->flags & (NVG_PT_BEVEL | NVG_PR_INNERBEVEL)) != 0) + path->nbevel++; + + p0 = p1++; + } + + path->convex = (nleft == path->count) ? 1 : 0; + } +} + + +static int nvg__expandStroke(NVGcontext* ctx, float w, float fringe, int lineCap, int lineJoin, float miterLimit) +{ + NVGpathCache* cache = ctx->cache; + NVGvertex* verts; + NVGvertex* dst; + int cverts, i, j; + float aa = fringe;//ctx->fringeWidth; + float u0 = 0.0f, u1 = 1.0f; + int ncap = nvg__curveDivs(w, NVG_PI, ctx->tessTol); // Calculate divisions per half circle. + + w += aa * 0.5f; + + // Disable the gradient used for antialiasing when antialiasing is not used. + if (aa == 0.0f) { + u0 = 0.5f; + u1 = 0.5f; + } + + nvg__calculateJoins(ctx, w, lineJoin, miterLimit); + + // Calculate max vertex usage. + cverts = 0; + for (i = 0; i < cache->npaths; i++) { + NVGpath* path = &cache->paths[i]; + int loop = (path->closed == 0) ? 0 : 1; + if (lineJoin == NVG_ROUND) + cverts += (path->count + path->nbevel*(ncap+2) + 1) * 2; // plus one for loop + else + cverts += (path->count + path->nbevel*5 + 1) * 2; // plus one for loop + if (loop == 0) { + // space for caps + if (lineCap == NVG_ROUND) { + cverts += (ncap*2 + 2)*2; + } else { + cverts += (3+3)*2; + } + } + } + + verts = nvg__allocTempVerts(ctx, cverts); + if (verts == NULL) return 0; + + for (i = 0; i < cache->npaths; i++) { + NVGpath* path = &cache->paths[i]; + NVGpoint* pts = &cache->points[path->first]; + NVGpoint* p0; + NVGpoint* p1; + int s, e, loop; + float dx, dy; + + path->fill = 0; + path->nfill = 0; + + // Calculate fringe or stroke + loop = (path->closed == 0) ? 0 : 1; + dst = verts; + path->stroke = dst; + + if (loop) { + // Looping + p0 = &pts[path->count-1]; + p1 = &pts[0]; + s = 0; + e = path->count; + } else { + // Add cap + p0 = &pts[0]; + p1 = &pts[1]; + s = 1; + e = path->count-1; + } + + if (loop == 0) { + // Add cap + dx = p1->x - p0->x; + dy = p1->y - p0->y; + nvg__normalize(&dx, &dy); + if (lineCap == NVG_BUTT) + dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa, u0, u1); + else if (lineCap == NVG_BUTT || lineCap == NVG_SQUARE) + dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa, u0, u1); + else if (lineCap == NVG_ROUND) + dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa, u0, u1); + } + + for (j = s; j < e; ++j) { + if ((p1->flags & (NVG_PT_BEVEL | NVG_PR_INNERBEVEL)) != 0) { + if (lineJoin == NVG_ROUND) { + dst = nvg__roundJoin(dst, p0, p1, w, w, u0, u1, ncap, aa); + } else { + dst = nvg__bevelJoin(dst, p0, p1, w, w, u0, u1, aa); + } + } else { + nvg__vset(dst, p1->x + (p1->dmx * w), p1->y + (p1->dmy * w), u0,1); dst++; + nvg__vset(dst, p1->x - (p1->dmx * w), p1->y - (p1->dmy * w), u1,1); dst++; + } + p0 = p1++; + } + + if (loop) { + // Loop it + nvg__vset(dst, verts[0].x, verts[0].y, u0,1); dst++; + nvg__vset(dst, verts[1].x, verts[1].y, u1,1); dst++; + } else { + // Add cap + dx = p1->x - p0->x; + dy = p1->y - p0->y; + nvg__normalize(&dx, &dy); + if (lineCap == NVG_BUTT) + dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa, u0, u1); + else if (lineCap == NVG_BUTT || lineCap == NVG_SQUARE) + dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa, u0, u1); + else if (lineCap == NVG_ROUND) + dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa, u0, u1); + } + + path->nstroke = (int)(dst - verts); + + verts = dst; + } + + return 1; +} + +static int nvg__expandFill(NVGcontext* ctx, float w, int lineJoin, float miterLimit) +{ + NVGpathCache* cache = ctx->cache; + NVGvertex* verts; + NVGvertex* dst; + int cverts, convex, i, j; + float aa = ctx->fringeWidth; + int fringe = w > 0.0f; + + nvg__calculateJoins(ctx, w, lineJoin, miterLimit); + + // Calculate max vertex usage. + cverts = 0; + for (i = 0; i < cache->npaths; i++) { + NVGpath* path = &cache->paths[i]; + cverts += path->count + path->nbevel + 1; + if (fringe) + cverts += (path->count + path->nbevel*5 + 1) * 2; // plus one for loop + } + + verts = nvg__allocTempVerts(ctx, cverts); + if (verts == NULL) return 0; + + convex = cache->npaths == 1 && cache->paths[0].convex; + + for (i = 0; i < cache->npaths; i++) { + NVGpath* path = &cache->paths[i]; + NVGpoint* pts = &cache->points[path->first]; + NVGpoint* p0; + NVGpoint* p1; + float rw, lw, woff; + float ru, lu; + + // Calculate shape vertices. + woff = 0.5f*aa; + dst = verts; + path->fill = dst; + + if (fringe) { + // Looping + p0 = &pts[path->count-1]; + p1 = &pts[0]; + for (j = 0; j < path->count; ++j) { + if (p1->flags & NVG_PT_BEVEL) { + float dlx0 = p0->dy; + float dly0 = -p0->dx; + float dlx1 = p1->dy; + float dly1 = -p1->dx; + if (p1->flags & NVG_PT_LEFT) { + float lx = p1->x + p1->dmx * woff; + float ly = p1->y + p1->dmy * woff; + nvg__vset(dst, lx, ly, 0.5f,1); dst++; + } else { + float lx0 = p1->x + dlx0 * woff; + float ly0 = p1->y + dly0 * woff; + float lx1 = p1->x + dlx1 * woff; + float ly1 = p1->y + dly1 * woff; + nvg__vset(dst, lx0, ly0, 0.5f,1); dst++; + nvg__vset(dst, lx1, ly1, 0.5f,1); dst++; + } + } else { + nvg__vset(dst, p1->x + (p1->dmx * woff), p1->y + (p1->dmy * woff), 0.5f,1); dst++; + } + p0 = p1++; + } + } else { + for (j = 0; j < path->count; ++j) { + nvg__vset(dst, pts[j].x, pts[j].y, 0.5f,1); + dst++; + } + } + + path->nfill = (int)(dst - verts); + verts = dst; + + // Calculate fringe + if (fringe) { + lw = w + woff; + rw = w - woff; + lu = 0; + ru = 1; + dst = verts; + path->stroke = dst; + + // Create only half a fringe for convex shapes so that + // the shape can be rendered without stenciling. + if (convex) { + lw = woff; // This should generate the same vertex as fill inset above. + lu = 0.5f; // Set outline fade at middle. + } + + // Looping + p0 = &pts[path->count-1]; + p1 = &pts[0]; + + for (j = 0; j < path->count; ++j) { + if ((p1->flags & (NVG_PT_BEVEL | NVG_PR_INNERBEVEL)) != 0) { + dst = nvg__bevelJoin(dst, p0, p1, lw, rw, lu, ru, ctx->fringeWidth); + } else { + nvg__vset(dst, p1->x + (p1->dmx * lw), p1->y + (p1->dmy * lw), lu,1); dst++; + nvg__vset(dst, p1->x - (p1->dmx * rw), p1->y - (p1->dmy * rw), ru,1); dst++; + } + p0 = p1++; + } + + // Loop it + nvg__vset(dst, verts[0].x, verts[0].y, lu,1); dst++; + nvg__vset(dst, verts[1].x, verts[1].y, ru,1); dst++; + + path->nstroke = (int)(dst - verts); + verts = dst; + } else { + path->stroke = NULL; + path->nstroke = 0; + } + } + + return 1; +} + + +// Draw +void nvgBeginPath(NVGcontext* ctx) +{ + ctx->ncommands = 0; + nvg__clearPathCache(ctx); +} + +void nvgMoveTo(NVGcontext* ctx, float x, float y) +{ + float vals[] = { NVG_MOVETO, x, y }; + nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); +} + +void nvgLineTo(NVGcontext* ctx, float x, float y) +{ + float vals[] = { NVG_LINETO, x, y }; + nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); +} + +void nvgBezierTo(NVGcontext* ctx, float c1x, float c1y, float c2x, float c2y, float x, float y) +{ + float vals[] = { NVG_BEZIERTO, c1x, c1y, c2x, c2y, x, y }; + nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); +} + +void nvgQuadTo(NVGcontext* ctx, float cx, float cy, float x, float y) +{ + float x0 = ctx->commandx; + float y0 = ctx->commandy; + float vals[] = { NVG_BEZIERTO, + x0 + 2.0f/3.0f*(cx - x0), y0 + 2.0f/3.0f*(cy - y0), + x + 2.0f/3.0f*(cx - x), y + 2.0f/3.0f*(cy - y), + x, y }; + nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); +} + +void nvgArcTo(NVGcontext* ctx, float x1, float y1, float x2, float y2, float radius) +{ + float x0 = ctx->commandx; + float y0 = ctx->commandy; + float dx0,dy0, dx1,dy1, a, d, cx,cy, a0,a1; + int dir; + + if (ctx->ncommands == 0) { + return; + } + + // Handle degenerate cases. + if (nvg__ptEquals(x0,y0, x1,y1, ctx->distTol) || + nvg__ptEquals(x1,y1, x2,y2, ctx->distTol) || + nvg__distPtSeg(x1,y1, x0,y0, x2,y2) < ctx->distTol*ctx->distTol || + radius < ctx->distTol) { + nvgLineTo(ctx, x1,y1); + return; + } + + // Calculate tangential circle to lines (x0,y0)-(x1,y1) and (x1,y1)-(x2,y2). + dx0 = x0-x1; + dy0 = y0-y1; + dx1 = x2-x1; + dy1 = y2-y1; + nvg__normalize(&dx0,&dy0); + nvg__normalize(&dx1,&dy1); + a = nvg__acosf(dx0*dx1 + dy0*dy1); + d = radius / nvg__tanf(a/2.0f); + +// printf("a=%f° d=%f\n", a/NVG_PI*180.0f, d); + + if (d > 10000.0f) { + nvgLineTo(ctx, x1,y1); + return; + } + + if (nvg__cross(dx0,dy0, dx1,dy1) > 0.0f) { + cx = x1 + dx0*d + dy0*radius; + cy = y1 + dy0*d + -dx0*radius; + a0 = nvg__atan2f(dx0, -dy0); + a1 = nvg__atan2f(-dx1, dy1); + dir = NVG_CW; +// printf("CW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); + } else { + cx = x1 + dx0*d + -dy0*radius; + cy = y1 + dy0*d + dx0*radius; + a0 = nvg__atan2f(-dx0, dy0); + a1 = nvg__atan2f(dx1, -dy1); + dir = NVG_CCW; +// printf("CCW c=(%f, %f) a0=%f° a1=%f°\n", cx, cy, a0/NVG_PI*180.0f, a1/NVG_PI*180.0f); + } + + nvgArc(ctx, cx, cy, radius, a0, a1, dir); +} + +void nvgClosePath(NVGcontext* ctx) +{ + float vals[] = { NVG_CLOSE }; + nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); +} + +void nvgPathWinding(NVGcontext* ctx, int dir) +{ + float vals[] = { NVG_WINDING, (float)dir }; + nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); +} + +void nvgArc(NVGcontext* ctx, float cx, float cy, float r, float a0, float a1, int dir) +{ + float a = 0, da = 0, hda = 0, kappa = 0; + float dx = 0, dy = 0, x = 0, y = 0, tanx = 0, tany = 0; + float px = 0, py = 0, ptanx = 0, ptany = 0; + float vals[3 + 5*7 + 100]; + int i, ndivs, nvals; + int move = ctx->ncommands > 0 ? NVG_LINETO : NVG_MOVETO; + + // Clamp angles + da = a1 - a0; + if (dir == NVG_CW) { + if (nvg__absf(da) >= NVG_PI*2) { + da = NVG_PI*2; + } else { + while (da < 0.0f) da += NVG_PI*2; + } + } else { + if (nvg__absf(da) >= NVG_PI*2) { + da = -NVG_PI*2; + } else { + while (da > 0.0f) da -= NVG_PI*2; + } + } + + // Split arc into max 90 degree segments. + ndivs = nvg__maxi(1, nvg__mini((int)(nvg__absf(da) / (NVG_PI*0.5f) + 0.5f), 5)); + hda = (da / (float)ndivs) / 2.0f; + kappa = nvg__absf(4.0f / 3.0f * (1.0f - nvg__cosf(hda)) / nvg__sinf(hda)); + + if (dir == NVG_CCW) + kappa = -kappa; + + nvals = 0; + for (i = 0; i <= ndivs; i++) { + a = a0 + da * (i/(float)ndivs); + dx = nvg__cosf(a); + dy = nvg__sinf(a); + x = cx + dx*r; + y = cy + dy*r; + tanx = -dy*r*kappa; + tany = dx*r*kappa; + + if (i == 0) { + vals[nvals++] = (float)move; + vals[nvals++] = x; + vals[nvals++] = y; + } else { + vals[nvals++] = NVG_BEZIERTO; + vals[nvals++] = px+ptanx; + vals[nvals++] = py+ptany; + vals[nvals++] = x-tanx; + vals[nvals++] = y-tany; + vals[nvals++] = x; + vals[nvals++] = y; + } + px = x; + py = y; + ptanx = tanx; + ptany = tany; + } + + nvg__appendCommands(ctx, vals, nvals); +} + +void nvgRect(NVGcontext* ctx, float x, float y, float w, float h) +{ + float vals[] = { + NVG_MOVETO, x,y, + NVG_LINETO, x,y+h, + NVG_LINETO, x+w,y+h, + NVG_LINETO, x+w,y, + NVG_CLOSE + }; + nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); +} + +void nvgRoundedRect(NVGcontext* ctx, float x, float y, float w, float h, float r) +{ + nvgRoundedRectVarying(ctx, x, y, w, h, r, r, r, r); +} + +void nvgRoundedRectVarying(NVGcontext* ctx, float x, float y, float w, float h, float radTopLeft, float radTopRight, float radBottomRight, float radBottomLeft) +{ + if(radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) { + nvgRect(ctx, x, y, w, h); + return; + } else { + float halfw = nvg__absf(w)*0.5f; + float halfh = nvg__absf(h)*0.5f; + float rxBL = nvg__minf(radBottomLeft, halfw) * nvg__signf(w), ryBL = nvg__minf(radBottomLeft, halfh) * nvg__signf(h); + float rxBR = nvg__minf(radBottomRight, halfw) * nvg__signf(w), ryBR = nvg__minf(radBottomRight, halfh) * nvg__signf(h); + float rxTR = nvg__minf(radTopRight, halfw) * nvg__signf(w), ryTR = nvg__minf(radTopRight, halfh) * nvg__signf(h); + float rxTL = nvg__minf(radTopLeft, halfw) * nvg__signf(w), ryTL = nvg__minf(radTopLeft, halfh) * nvg__signf(h); + float vals[] = { + NVG_MOVETO, x, y + ryTL, + NVG_LINETO, x, y + h - ryBL, + NVG_BEZIERTO, x, y + h - ryBL*(1 - NVG_KAPPA90), x + rxBL*(1 - NVG_KAPPA90), y + h, x + rxBL, y + h, + NVG_LINETO, x + w - rxBR, y + h, + NVG_BEZIERTO, x + w - rxBR*(1 - NVG_KAPPA90), y + h, x + w, y + h - ryBR*(1 - NVG_KAPPA90), x + w, y + h - ryBR, + NVG_LINETO, x + w, y + ryTR, + NVG_BEZIERTO, x + w, y + ryTR*(1 - NVG_KAPPA90), x + w - rxTR*(1 - NVG_KAPPA90), y, x + w - rxTR, y, + NVG_LINETO, x + rxTL, y, + NVG_BEZIERTO, x + rxTL*(1 - NVG_KAPPA90), y, x, y + ryTL*(1 - NVG_KAPPA90), x, y + ryTL, + NVG_CLOSE + }; + nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); + } +} + +void nvgEllipse(NVGcontext* ctx, float cx, float cy, float rx, float ry) +{ + float vals[] = { + NVG_MOVETO, cx-rx, cy, + NVG_BEZIERTO, cx-rx, cy+ry*NVG_KAPPA90, cx-rx*NVG_KAPPA90, cy+ry, cx, cy+ry, + NVG_BEZIERTO, cx+rx*NVG_KAPPA90, cy+ry, cx+rx, cy+ry*NVG_KAPPA90, cx+rx, cy, + NVG_BEZIERTO, cx+rx, cy-ry*NVG_KAPPA90, cx+rx*NVG_KAPPA90, cy-ry, cx, cy-ry, + NVG_BEZIERTO, cx-rx*NVG_KAPPA90, cy-ry, cx-rx, cy-ry*NVG_KAPPA90, cx-rx, cy, + NVG_CLOSE + }; + nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); +} + +void nvgCircle(NVGcontext* ctx, float cx, float cy, float r) +{ + nvgEllipse(ctx, cx,cy, r,r); +} + +void nvgDebugDumpPathCache(NVGcontext* ctx) +{ + const NVGpath* path; + int i, j; + + printf("Dumping %d cached paths\n", ctx->cache->npaths); + for (i = 0; i < ctx->cache->npaths; i++) { + path = &ctx->cache->paths[i]; + printf(" - Path %d\n", i); + if (path->nfill) { + printf(" - fill: %d\n", path->nfill); + for (j = 0; j < path->nfill; j++) + printf("%f\t%f\n", path->fill[j].x, path->fill[j].y); + } + if (path->nstroke) { + printf(" - stroke: %d\n", path->nstroke); + for (j = 0; j < path->nstroke; j++) + printf("%f\t%f\n", path->stroke[j].x, path->stroke[j].y); + } + } +} + +void nvgFill(NVGcontext* ctx) +{ + NVGstate* state = nvg__getState(ctx); + const NVGpath* path; + NVGpaint fillPaint = state->fill; + int i; + + nvg__flattenPaths(ctx); + if (ctx->params.edgeAntiAlias && state->shapeAntiAlias) + nvg__expandFill(ctx, ctx->fringeWidth, NVG_MITER, 2.4f); + else + nvg__expandFill(ctx, 0.0f, NVG_MITER, 2.4f); + + // Apply global alpha + fillPaint.innerColor.a *= state->alpha; + fillPaint.outerColor.a *= state->alpha; + + ctx->params.renderFill(ctx->params.userPtr, &fillPaint, state->compositeOperation, &state->scissor, ctx->fringeWidth, + ctx->cache->bounds, ctx->cache->paths, ctx->cache->npaths); + + // Count triangles + for (i = 0; i < ctx->cache->npaths; i++) { + path = &ctx->cache->paths[i]; + ctx->fillTriCount += path->nfill-2; + ctx->fillTriCount += path->nstroke-2; + ctx->drawCallCount += 2; + } +} + +void nvgStroke(NVGcontext* ctx) +{ + NVGstate* state = nvg__getState(ctx); + float scale = nvg__getAverageScale(state->xform); + float strokeWidth = nvg__clampf(state->strokeWidth * scale, 0.0f, 200.0f); + NVGpaint strokePaint = state->stroke; + const NVGpath* path; + int i; + + + if (strokeWidth < ctx->fringeWidth) { + // If the stroke width is less than pixel size, use alpha to emulate coverage. + // Since coverage is area, scale by alpha*alpha. + float alpha = nvg__clampf(strokeWidth / ctx->fringeWidth, 0.0f, 1.0f); + strokePaint.innerColor.a *= alpha*alpha; + strokePaint.outerColor.a *= alpha*alpha; + strokeWidth = ctx->fringeWidth; + } + + // Apply global alpha + strokePaint.innerColor.a *= state->alpha; + strokePaint.outerColor.a *= state->alpha; + + nvg__flattenPaths(ctx); + + if (ctx->params.edgeAntiAlias && state->shapeAntiAlias) + nvg__expandStroke(ctx, strokeWidth*0.5f, ctx->fringeWidth, state->lineCap, state->lineJoin, state->miterLimit); + else + nvg__expandStroke(ctx, strokeWidth*0.5f, 0.0f, state->lineCap, state->lineJoin, state->miterLimit); + + ctx->params.renderStroke(ctx->params.userPtr, &strokePaint, state->compositeOperation, &state->scissor, ctx->fringeWidth, + strokeWidth, ctx->cache->paths, ctx->cache->npaths); + + // Count triangles + for (i = 0; i < ctx->cache->npaths; i++) { + path = &ctx->cache->paths[i]; + ctx->strokeTriCount += path->nstroke-2; + ctx->drawCallCount++; + } +} + +// Add fonts +int nvgCreateFont(NVGcontext* ctx, const char* name, const char* filename) +{ + return fonsAddFont(ctx->fs, name, filename, 0); +} + +int nvgCreateFontAtIndex(NVGcontext* ctx, const char* name, const char* filename, const int fontIndex) +{ + return fonsAddFont(ctx->fs, name, filename, fontIndex); +} + +int nvgCreateFontMem(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData) +{ + return fonsAddFontMem(ctx->fs, name, data, ndata, freeData, 0); +} + +int nvgCreateFontMemAtIndex(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData, const int fontIndex) +{ + return fonsAddFontMem(ctx->fs, name, data, ndata, freeData, fontIndex); +} + +int nvgFindFont(NVGcontext* ctx, const char* name) +{ + if (name == NULL) return -1; + return fonsGetFontByName(ctx->fs, name); +} + + +int nvgAddFallbackFontId(NVGcontext* ctx, int baseFont, int fallbackFont) +{ + if(baseFont == -1 || fallbackFont == -1) return 0; + return fonsAddFallbackFont(ctx->fs, baseFont, fallbackFont); +} + +int nvgAddFallbackFont(NVGcontext* ctx, const char* baseFont, const char* fallbackFont) +{ + return nvgAddFallbackFontId(ctx, nvgFindFont(ctx, baseFont), nvgFindFont(ctx, fallbackFont)); +} + +void nvgResetFallbackFontsId(NVGcontext* ctx, int baseFont) +{ + fonsResetFallbackFont(ctx->fs, baseFont); +} + +void nvgResetFallbackFonts(NVGcontext* ctx, const char* baseFont) +{ + nvgResetFallbackFontsId(ctx, nvgFindFont(ctx, baseFont)); +} + +// State setting +void nvgFontSize(NVGcontext* ctx, float size) +{ + NVGstate* state = nvg__getState(ctx); + state->fontSize = size; +} + +void nvgFontBlur(NVGcontext* ctx, float blur) +{ + NVGstate* state = nvg__getState(ctx); + state->fontBlur = blur; +} + +void nvgTextLetterSpacing(NVGcontext* ctx, float spacing) +{ + NVGstate* state = nvg__getState(ctx); + state->letterSpacing = spacing; +} + +void nvgTextLineHeight(NVGcontext* ctx, float lineHeight) +{ + NVGstate* state = nvg__getState(ctx); + state->lineHeight = lineHeight; +} + +void nvgTextAlign(NVGcontext* ctx, int align) +{ + NVGstate* state = nvg__getState(ctx); + state->textAlign = align; +} + +void nvgFontFaceId(NVGcontext* ctx, int font) +{ + NVGstate* state = nvg__getState(ctx); + state->fontId = font; +} + +void nvgFontFace(NVGcontext* ctx, const char* font) +{ + NVGstate* state = nvg__getState(ctx); + state->fontId = fonsGetFontByName(ctx->fs, font); +} + +static float nvg__quantize(float a, float d) +{ + return ((int)(a / d + 0.5f)) * d; +} + +static float nvg__getFontScale(NVGstate* state) +{ + return nvg__minf(nvg__quantize(nvg__getAverageScale(state->xform), 0.01f), 4.0f); +} + +static void nvg__flushTextTexture(NVGcontext* ctx) +{ + int dirty[4]; + + if (fonsValidateTexture(ctx->fs, dirty)) { + int fontImage = ctx->fontImages[ctx->fontImageIdx]; + // Update texture + if (fontImage != 0) { + int iw, ih; + const unsigned char* data = fonsGetTextureData(ctx->fs, &iw, &ih); + int x = dirty[0]; + int y = dirty[1]; + int w = dirty[2] - dirty[0]; + int h = dirty[3] - dirty[1]; + ctx->params.renderUpdateTexture(ctx->params.userPtr, fontImage, x,y, w,h, data); + } + } +} + +static int nvg__allocTextAtlas(NVGcontext* ctx) +{ + int iw, ih; + nvg__flushTextTexture(ctx); + if (ctx->fontImageIdx >= NVG_MAX_FONTIMAGES-1) + return 0; + // if next fontImage already have a texture + if (ctx->fontImages[ctx->fontImageIdx+1] != 0) + nvgImageSize(ctx, ctx->fontImages[ctx->fontImageIdx+1], &iw, &ih); + else { // calculate the new font image size and create it. + nvgImageSize(ctx, ctx->fontImages[ctx->fontImageIdx], &iw, &ih); + if (iw > ih) + ih *= 2; + else + iw *= 2; + if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) + iw = ih = NVG_MAX_FONTIMAGE_SIZE; + ctx->fontImages[ctx->fontImageIdx+1] = ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_ALPHA, iw, ih, 0, NULL); + } + ++ctx->fontImageIdx; + fonsResetAtlas(ctx->fs, iw, ih); + return 1; +} + +static void nvg__renderText(NVGcontext* ctx, NVGvertex* verts, int nverts) +{ + NVGstate* state = nvg__getState(ctx); + NVGpaint paint = state->fill; + + // Render triangles. + paint.image = ctx->fontImages[ctx->fontImageIdx]; + + // Apply global alpha + paint.innerColor.a *= state->alpha; + paint.outerColor.a *= state->alpha; + + ctx->params.renderTriangles(ctx->params.userPtr, &paint, state->compositeOperation, &state->scissor, verts, nverts, ctx->fringeWidth); + + ctx->drawCallCount++; + ctx->textTriCount += nverts/3; +} + +float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* end) +{ + NVGstate* state = nvg__getState(ctx); + FONStextIter iter, prevIter; + FONSquad q; + NVGvertex* verts; + float scale = nvg__getFontScale(state) * ctx->devicePxRatio; + float invscale = 1.0f / scale; + int cverts = 0; + int nverts = 0; + + if (end == NULL) + end = string + strlen(string); + + if (state->fontId == FONS_INVALID) return x; + + fonsSetSize(ctx->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fs, state->textAlign); + fonsSetFont(ctx->fs, state->fontId); + + cverts = nvg__maxi(2, (int)(end - string)) * 6; // conservative estimate. + verts = nvg__allocTempVerts(ctx, cverts); + if (verts == NULL) return x; + + fonsTextIterInit(ctx->fs, &iter, x*scale, y*scale, string, end, FONS_GLYPH_BITMAP_REQUIRED); + prevIter = iter; + while (fonsTextIterNext(ctx->fs, &iter, &q)) { + float c[4*2]; + if (iter.prevGlyphIndex == -1) { // can not retrieve glyph? + if (nverts != 0) { + nvg__renderText(ctx, verts, nverts); + nverts = 0; + } + if (!nvg__allocTextAtlas(ctx)) + break; // no memory :( + iter = prevIter; + fonsTextIterNext(ctx->fs, &iter, &q); // try again + if (iter.prevGlyphIndex == -1) // still can not find glyph? + break; + } + prevIter = iter; + // Transform corners. + nvgTransformPoint(&c[0],&c[1], state->xform, q.x0*invscale, q.y0*invscale); + nvgTransformPoint(&c[2],&c[3], state->xform, q.x1*invscale, q.y0*invscale); + nvgTransformPoint(&c[4],&c[5], state->xform, q.x1*invscale, q.y1*invscale); + nvgTransformPoint(&c[6],&c[7], state->xform, q.x0*invscale, q.y1*invscale); + // Create triangles + if (nverts+6 <= cverts) { + nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); nverts++; + nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); nverts++; + nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); nverts++; + nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); nverts++; + nvg__vset(&verts[nverts], c[6], c[7], q.s0, q.t1); nverts++; + nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); nverts++; + } + } + + // TODO: add back-end bit to do this just once per frame. + nvg__flushTextTexture(ctx); + + nvg__renderText(ctx, verts, nverts); + + return iter.nextx / scale; +} + +void nvgTextBox(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end) +{ + NVGstate* state = nvg__getState(ctx); + NVGtextRow rows[2]; + int nrows = 0, i; + int oldAlign = state->textAlign; + int haling = state->textAlign & (NVG_ALIGN_LEFT | NVG_ALIGN_CENTER | NVG_ALIGN_RIGHT); + int valign = state->textAlign & (NVG_ALIGN_TOP | NVG_ALIGN_MIDDLE | NVG_ALIGN_BOTTOM | NVG_ALIGN_BASELINE); + float lineh = 0; + + if (state->fontId == FONS_INVALID) return; + + nvgTextMetrics(ctx, NULL, NULL, &lineh); + + state->textAlign = NVG_ALIGN_LEFT | valign; + + while ((nrows = nvgTextBreakLines(ctx, string, end, breakRowWidth, rows, 2))) { + for (i = 0; i < nrows; i++) { + NVGtextRow* row = &rows[i]; + if (haling & NVG_ALIGN_LEFT) + nvgText(ctx, x, y, row->start, row->end); + else if (haling & NVG_ALIGN_CENTER) + nvgText(ctx, x + breakRowWidth*0.5f - row->width*0.5f, y, row->start, row->end); + else if (haling & NVG_ALIGN_RIGHT) + nvgText(ctx, x + breakRowWidth - row->width, y, row->start, row->end); + y += lineh * state->lineHeight; + } + string = rows[nrows-1].next; + } + + state->textAlign = oldAlign; +} + +int nvgTextGlyphPositions(NVGcontext* ctx, float x, float y, const char* string, const char* end, NVGglyphPosition* positions, int maxPositions) +{ + NVGstate* state = nvg__getState(ctx); + float scale = nvg__getFontScale(state) * ctx->devicePxRatio; + float invscale = 1.0f / scale; + FONStextIter iter, prevIter; + FONSquad q; + int npos = 0; + + if (state->fontId == FONS_INVALID) return 0; + + if (end == NULL) + end = string + strlen(string); + + if (string == end) + return 0; + + fonsSetSize(ctx->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fs, state->textAlign); + fonsSetFont(ctx->fs, state->fontId); + + fonsTextIterInit(ctx->fs, &iter, x*scale, y*scale, string, end, FONS_GLYPH_BITMAP_OPTIONAL); + prevIter = iter; + while (fonsTextIterNext(ctx->fs, &iter, &q)) { + if (iter.prevGlyphIndex < 0 && nvg__allocTextAtlas(ctx)) { // can not retrieve glyph? + iter = prevIter; + fonsTextIterNext(ctx->fs, &iter, &q); // try again + } + prevIter = iter; + positions[npos].str = iter.str; + positions[npos].x = iter.x * invscale; + positions[npos].minx = nvg__minf(iter.x, q.x0) * invscale; + positions[npos].maxx = nvg__maxf(iter.nextx, q.x1) * invscale; + npos++; + if (npos >= maxPositions) + break; + } + + return npos; +} + +enum NVGcodepointType { + NVG_SPACE, + NVG_NEWLINE, + NVG_CHAR, + NVG_CJK_CHAR, +}; + +int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, float breakRowWidth, NVGtextRow* rows, int maxRows) +{ + NVGstate* state = nvg__getState(ctx); + float scale = nvg__getFontScale(state) * ctx->devicePxRatio; + float invscale = 1.0f / scale; + FONStextIter iter, prevIter; + FONSquad q; + int nrows = 0; + float rowStartX = 0; + float rowWidth = 0; + float rowMinX = 0; + float rowMaxX = 0; + const char* rowStart = NULL; + const char* rowEnd = NULL; + const char* wordStart = NULL; + float wordStartX = 0; + float wordMinX = 0; + const char* breakEnd = NULL; + float breakWidth = 0; + float breakMaxX = 0; + int type = NVG_SPACE, ptype = NVG_SPACE; + unsigned int pcodepoint = 0; + + if (maxRows == 0) return 0; + if (state->fontId == FONS_INVALID) return 0; + + if (end == NULL) + end = string + strlen(string); + + if (string == end) return 0; + + fonsSetSize(ctx->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fs, state->textAlign); + fonsSetFont(ctx->fs, state->fontId); + + breakRowWidth *= scale; + + fonsTextIterInit(ctx->fs, &iter, 0, 0, string, end, FONS_GLYPH_BITMAP_OPTIONAL); + prevIter = iter; + while (fonsTextIterNext(ctx->fs, &iter, &q)) { + if (iter.prevGlyphIndex < 0 && nvg__allocTextAtlas(ctx)) { // can not retrieve glyph? + iter = prevIter; + fonsTextIterNext(ctx->fs, &iter, &q); // try again + } + prevIter = iter; + switch (iter.codepoint) { + case 9: // \t + case 11: // \v + case 12: // \f + case 32: // space + case 0x00a0: // NBSP + type = NVG_SPACE; + break; + case 10: // \n + type = pcodepoint == 13 ? NVG_SPACE : NVG_NEWLINE; + break; + case 13: // \r + type = pcodepoint == 10 ? NVG_SPACE : NVG_NEWLINE; + break; + case 0x0085: // NEL + type = NVG_NEWLINE; + break; + default: + if ((iter.codepoint >= 0x4E00 && iter.codepoint <= 0x9FFF) || + (iter.codepoint >= 0x3000 && iter.codepoint <= 0x30FF) || + (iter.codepoint >= 0xFF00 && iter.codepoint <= 0xFFEF) || + (iter.codepoint >= 0x1100 && iter.codepoint <= 0x11FF) || + (iter.codepoint >= 0x3130 && iter.codepoint <= 0x318F) || + (iter.codepoint >= 0xAC00 && iter.codepoint <= 0xD7AF)) + type = NVG_CJK_CHAR; + else + type = NVG_CHAR; + break; + } + + if (type == NVG_NEWLINE) { + // Always handle new lines. + rows[nrows].start = rowStart != NULL ? rowStart : iter.str; + rows[nrows].end = rowEnd != NULL ? rowEnd : iter.str; + rows[nrows].width = rowWidth * invscale; + rows[nrows].minx = rowMinX * invscale; + rows[nrows].maxx = rowMaxX * invscale; + rows[nrows].next = iter.next; + nrows++; + if (nrows >= maxRows) + return nrows; + // Set null break point + breakEnd = rowStart; + breakWidth = 0.0; + breakMaxX = 0.0; + // Indicate to skip the white space at the beginning of the row. + rowStart = NULL; + rowEnd = NULL; + rowWidth = 0; + rowMinX = rowMaxX = 0; + } else { + if (rowStart == NULL) { + // Skip white space until the beginning of the line + if (type == NVG_CHAR || type == NVG_CJK_CHAR) { + // The current char is the row so far + rowStartX = iter.x; + rowStart = iter.str; + rowEnd = iter.next; + rowWidth = iter.nextx - rowStartX; + rowMinX = q.x0 - rowStartX; + rowMaxX = q.x1 - rowStartX; + wordStart = iter.str; + wordStartX = iter.x; + wordMinX = q.x0 - rowStartX; + // Set null break point + breakEnd = rowStart; + breakWidth = 0.0; + breakMaxX = 0.0; + } + } else { + float nextWidth = iter.nextx - rowStartX; + + // track last non-white space character + if (type == NVG_CHAR || type == NVG_CJK_CHAR) { + rowEnd = iter.next; + rowWidth = iter.nextx - rowStartX; + rowMaxX = q.x1 - rowStartX; + } + // track last end of a word + if (((ptype == NVG_CHAR || ptype == NVG_CJK_CHAR) && type == NVG_SPACE) || type == NVG_CJK_CHAR) { + breakEnd = iter.str; + breakWidth = rowWidth; + breakMaxX = rowMaxX; + } + // track last beginning of a word + if ((ptype == NVG_SPACE && (type == NVG_CHAR || type == NVG_CJK_CHAR)) || type == NVG_CJK_CHAR) { + wordStart = iter.str; + wordStartX = iter.x; + wordMinX = q.x0; + } + + // Break to new line when a character is beyond break width. + if ((type == NVG_CHAR || type == NVG_CJK_CHAR) && nextWidth > breakRowWidth) { + // The run length is too long, need to break to new line. + if (breakEnd == rowStart) { + // The current word is longer than the row length, just break it from here. + rows[nrows].start = rowStart; + rows[nrows].end = iter.str; + rows[nrows].width = rowWidth * invscale; + rows[nrows].minx = rowMinX * invscale; + rows[nrows].maxx = rowMaxX * invscale; + rows[nrows].next = iter.str; + nrows++; + if (nrows >= maxRows) + return nrows; + rowStartX = iter.x; + rowStart = iter.str; + rowEnd = iter.next; + rowWidth = iter.nextx - rowStartX; + rowMinX = q.x0 - rowStartX; + rowMaxX = q.x1 - rowStartX; + wordStart = iter.str; + wordStartX = iter.x; + wordMinX = q.x0 - rowStartX; + } else { + // Break the line from the end of the last word, and start new line from the beginning of the new. + rows[nrows].start = rowStart; + rows[nrows].end = breakEnd; + rows[nrows].width = breakWidth * invscale; + rows[nrows].minx = rowMinX * invscale; + rows[nrows].maxx = breakMaxX * invscale; + rows[nrows].next = wordStart; + nrows++; + if (nrows >= maxRows) + return nrows; + // Update row + rowStartX = wordStartX; + rowStart = wordStart; + rowEnd = iter.next; + rowWidth = iter.nextx - rowStartX; + rowMinX = wordMinX - rowStartX; + rowMaxX = q.x1 - rowStartX; + } + // Set null break point + breakEnd = rowStart; + breakWidth = 0.0; + breakMaxX = 0.0; + } + } + } + + pcodepoint = iter.codepoint; + ptype = type; + } + + // Break the line from the end of the last word, and start new line from the beginning of the new. + if (rowStart != NULL) { + rows[nrows].start = rowStart; + rows[nrows].end = rowEnd; + rows[nrows].width = rowWidth * invscale; + rows[nrows].minx = rowMinX * invscale; + rows[nrows].maxx = rowMaxX * invscale; + rows[nrows].next = end; + nrows++; + } + + return nrows; +} + +float nvgTextBounds(NVGcontext* ctx, float x, float y, const char* string, const char* end, float* bounds) +{ + NVGstate* state = nvg__getState(ctx); + float scale = nvg__getFontScale(state) * ctx->devicePxRatio; + float invscale = 1.0f / scale; + float width; + + if (state->fontId == FONS_INVALID) return 0; + + fonsSetSize(ctx->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fs, state->textAlign); + fonsSetFont(ctx->fs, state->fontId); + + width = fonsTextBounds(ctx->fs, x*scale, y*scale, string, end, bounds); + if (bounds != NULL) { + // Use line bounds for height. + fonsLineBounds(ctx->fs, y*scale, &bounds[1], &bounds[3]); + bounds[0] *= invscale; + bounds[1] *= invscale; + bounds[2] *= invscale; + bounds[3] *= invscale; + } + return width * invscale; +} + +void nvgTextBoxBounds(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end, float* bounds) +{ + NVGstate* state = nvg__getState(ctx); + NVGtextRow rows[2]; + float scale = nvg__getFontScale(state) * ctx->devicePxRatio; + float invscale = 1.0f / scale; + int nrows = 0, i; + int oldAlign = state->textAlign; + int haling = state->textAlign & (NVG_ALIGN_LEFT | NVG_ALIGN_CENTER | NVG_ALIGN_RIGHT); + int valign = state->textAlign & (NVG_ALIGN_TOP | NVG_ALIGN_MIDDLE | NVG_ALIGN_BOTTOM | NVG_ALIGN_BASELINE); + float lineh = 0, rminy = 0, rmaxy = 0; + float minx, miny, maxx, maxy; + + if (state->fontId == FONS_INVALID) { + if (bounds != NULL) + bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0f; + return; + } + + nvgTextMetrics(ctx, NULL, NULL, &lineh); + + state->textAlign = NVG_ALIGN_LEFT | valign; + + minx = maxx = x; + miny = maxy = y; + + fonsSetSize(ctx->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fs, state->textAlign); + fonsSetFont(ctx->fs, state->fontId); + fonsLineBounds(ctx->fs, 0, &rminy, &rmaxy); + rminy *= invscale; + rmaxy *= invscale; + + while ((nrows = nvgTextBreakLines(ctx, string, end, breakRowWidth, rows, 2))) { + for (i = 0; i < nrows; i++) { + NVGtextRow* row = &rows[i]; + float rminx, rmaxx, dx = 0; + // Horizontal bounds + if (haling & NVG_ALIGN_LEFT) + dx = 0; + else if (haling & NVG_ALIGN_CENTER) + dx = breakRowWidth*0.5f - row->width*0.5f; + else if (haling & NVG_ALIGN_RIGHT) + dx = breakRowWidth - row->width; + rminx = x + row->minx + dx; + rmaxx = x + row->maxx + dx; + minx = nvg__minf(minx, rminx); + maxx = nvg__maxf(maxx, rmaxx); + // Vertical bounds. + miny = nvg__minf(miny, y + rminy); + maxy = nvg__maxf(maxy, y + rmaxy); + + y += lineh * state->lineHeight; + } + string = rows[nrows-1].next; + } + + state->textAlign = oldAlign; + + if (bounds != NULL) { + bounds[0] = minx; + bounds[1] = miny; + bounds[2] = maxx; + bounds[3] = maxy; + } +} + +void nvgTextMetrics(NVGcontext* ctx, float* ascender, float* descender, float* lineh) +{ + NVGstate* state = nvg__getState(ctx); + float scale = nvg__getFontScale(state) * ctx->devicePxRatio; + float invscale = 1.0f / scale; + + if (state->fontId == FONS_INVALID) return; + + fonsSetSize(ctx->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fs, state->textAlign); + fonsSetFont(ctx->fs, state->fontId); + + fonsVertMetrics(ctx->fs, ascender, descender, lineh); + if (ascender != NULL) + *ascender *= invscale; + if (descender != NULL) + *descender *= invscale; + if (lineh != NULL) + *lineh *= invscale; +} +// vim: ft=c nu noet ts=4 diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/ams_su.c b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/ams_su.c new file mode 100644 index 00000000..1b3838e9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/ams_su.c @@ -0,0 +1,158 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <switch.h> +#include <string.h> +#include "ams_su.h" +#include "service_guard.h" + +static Service g_amssuSrv; +static TransferMemory g_tmem; + +NX_GENERATE_SERVICE_GUARD(amssu); + +Result _amssuInitialize(void) { + return smGetService(&g_amssuSrv, "ams:su"); +} + +void _amssuCleanup(void) { + serviceClose(&g_amssuSrv); + tmemClose(&g_tmem); +} + +Service *amssuGetServiceSession(void) { + return &g_amssuSrv; +} + +Result amssuGetUpdateInformation(AmsSuUpdateInformation *out, const char *path) { + char send_path[FS_MAX_PATH] = {0}; + strncpy(send_path, path, FS_MAX_PATH-1); + send_path[FS_MAX_PATH-1] = 0; + + return serviceDispatchOut(&g_amssuSrv, 0, *out, + .buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcPointer | SfBufferAttr_FixedSize }, + .buffers = { { send_path, FS_MAX_PATH } }, + ); +} + +Result amssuValidateUpdate(AmsSuUpdateValidationInfo *out, const char *path) { + char send_path[FS_MAX_PATH] = {0}; + strncpy(send_path, path, FS_MAX_PATH-1); + send_path[FS_MAX_PATH-1] = 0; + + return serviceDispatchOut(&g_amssuSrv, 1, *out, + .buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcPointer | SfBufferAttr_FixedSize }, + .buffers = { { send_path, FS_MAX_PATH } }, + ); +} + +Result amssuSetupUpdate(void *buffer, size_t size, const char *path, bool exfat) { + Result rc = 0; + + if (buffer == NULL) { + rc = tmemCreate(&g_tmem, size, Perm_None); + } else { + rc = tmemCreateFromMemory(&g_tmem, buffer, size, Perm_None); + } + if (R_FAILED(rc)) return rc; + + char send_path[FS_MAX_PATH] = {0}; + strncpy(send_path, path, FS_MAX_PATH-1); + send_path[FS_MAX_PATH-1] = 0; + + const struct { + u8 exfat; + u64 size; + } in = { exfat, g_tmem.size }; + + rc = serviceDispatchIn(&g_amssuSrv, 2, in, + .in_num_handles = 1, + .in_handles = { g_tmem.handle }, + .buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcPointer | SfBufferAttr_FixedSize }, + .buffers = { { send_path, FS_MAX_PATH } }, + ); + if (R_FAILED((rc))) { + tmemClose(&g_tmem); + } + + return rc; +} + +Result amssuSetupUpdateWithVariation(void *buffer, size_t size, const char *path, bool exfat, u32 variation) { + Result rc = 0; + + if (buffer == NULL) { + rc = tmemCreate(&g_tmem, size, Perm_None); + } else { + rc = tmemCreateFromMemory(&g_tmem, buffer, size, Perm_None); + } + if (R_FAILED(rc)) return rc; + + char send_path[FS_MAX_PATH] = {0}; + strncpy(send_path, path, FS_MAX_PATH-1); + send_path[FS_MAX_PATH-1] = 0; + + const struct { + u8 exfat; + u32 variation; + u64 size; + } in = { exfat, variation, g_tmem.size }; + + rc = serviceDispatchIn(&g_amssuSrv, 3, in, + .in_num_handles = 1, + .in_handles = { g_tmem.handle }, + .buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcPointer | SfBufferAttr_FixedSize }, + .buffers = { { send_path, FS_MAX_PATH } }, + ); + if (R_FAILED((rc))) { + tmemClose(&g_tmem); + } + + return rc; +} + +Result amssuRequestPrepareUpdate(AsyncResult *a) { + memset(a, 0, sizeof(*a)); + + Handle event = INVALID_HANDLE; + Result rc = serviceDispatch(&g_amssuSrv, 4, + .out_num_objects = 1, + .out_objects = &a->s, + .out_handle_attrs = { SfOutHandleAttr_HipcCopy }, + .out_handles = &event, + ); + + if (R_SUCCEEDED(rc)) + eventLoadRemote(&a->event, event, false); + + return rc; +} + +Result amssuGetPrepareUpdateProgress(NsSystemUpdateProgress *out) { + return serviceDispatchOut(&g_amssuSrv, 5, *out); +} + +Result amssuHasPreparedUpdate(bool *out) { + u8 outval = 0; + Result rc = serviceDispatchOut(&g_amssuSrv, 6, outval); + if (R_SUCCEEDED(rc)) { + if (out) *out = outval & 1; + } + return rc; +} + +Result amssuApplyPreparedUpdate() { + return serviceDispatch(&g_amssuSrv, 7); +} \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/ams_su.h b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/ams_su.h new file mode 100644 index 00000000..652d4143 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/ams_su.h @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <switch.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + u32 version; + bool exfat_supported; + u32 num_firmware_variations; + u32 firmware_variation_ids[16]; +} AmsSuUpdateInformation; + +typedef struct { + Result result; + Result exfat_result; + NcmContentMetaKey invalid_key; + NcmContentId invalid_content_id; +} AmsSuUpdateValidationInfo; + +Result amssuInitialize(); +void amssuExit(); +Service *amssuGetServiceSession(void); + +Result amssuGetUpdateInformation(AmsSuUpdateInformation *out, const char *path); +Result amssuValidateUpdate(AmsSuUpdateValidationInfo *out, const char *path); +Result amssuSetupUpdate(void *buffer, size_t size, const char *path, bool exfat); +Result amssuSetupUpdateWithVariation(void *buffer, size_t size, const char *path, bool exfat, u32 variation); +Result amssuRequestPrepareUpdate(AsyncResult *a); +Result amssuGetPrepareUpdateProgress(NsSystemUpdateProgress *out); +Result amssuHasPreparedUpdate(bool *out); +Result amssuApplyPreparedUpdate(); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/assert.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/assert.hpp new file mode 100644 index 00000000..8e503aa9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/assert.hpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) Adubbz + * + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <cstdlib> + +#define DBK_ABORT_UNLESS(expr) \ + if (!static_cast<bool>(expr)) { \ + std::abort(); \ + } diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/main.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/main.cpp new file mode 100644 index 00000000..5bbeac4a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/main.cpp @@ -0,0 +1,272 @@ +/* + * Copyright (c) Adubbz + * + * 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 <http://www.gnu.org/licenses/>. + */ +#include <optional> +#include <switch.h> +#include <nanovg.h> +#include <nanovg_dk.h> +#include <nanovg/framework/CApplication.h> +#include "ui.hpp" +#include "ams_su.h" + +extern "C" { + + void userAppInit(void) { + Result rc = 0; + + if (R_FAILED(rc = romfsInit())) { + fatalThrow(rc); + } + + if (R_FAILED(rc = spsmInitialize())) { + fatalThrow(rc); + } + + if (R_FAILED(rc = plInitialize(PlServiceType_User))) { + fatalThrow(rc); + } + + if (R_FAILED(rc = splInitialize())) { + fatalThrow(rc); + } + + if (R_FAILED(rc = nsInitialize())) { + fatalThrow(rc); + } + + if (R_FAILED(rc = hiddbgInitialize())) { + fatalThrow(rc); + } + + } + + void userAppExit(void) { + hiddbgExit(); + nsExit(); + splExit(); + plExit(); + spsmExit(); + romfsExit(); + amssuExit(); + } + +} + +namespace { + + static constexpr u32 FramebufferWidth = 1280; + static constexpr u32 FramebufferHeight = 720; + +} + +class Daybreak : public CApplication { + private: + static constexpr unsigned NumFramebuffers = 2; + static constexpr unsigned StaticCmdSize = 0x1000; + + dk::UniqueDevice m_device; + dk::UniqueQueue m_queue; + dk::UniqueSwapchain m_swapchain; + + std::optional<CMemPool> m_pool_images; + std::optional<CMemPool> m_pool_code; + std::optional<CMemPool> m_pool_data; + + dk::UniqueCmdBuf m_cmd_buf; + DkCmdList m_render_cmdlist; + + dk::Image m_depth_buffer; + CMemPool::Handle m_depth_buffer_mem; + dk::Image m_framebuffers[NumFramebuffers]; + CMemPool::Handle m_framebuffers_mem[NumFramebuffers]; + DkCmdList m_framebuffer_cmdlists[NumFramebuffers]; + + std::optional<nvg::DkRenderer> m_renderer; + NVGcontext *m_vg; + int m_standard_font; + public: + Daybreak() { + Result rc = 0; + + /* Create the deko3d device. */ + m_device = dk::DeviceMaker{}.create(); + + /* Create the main queue. */ + m_queue = dk::QueueMaker{m_device}.setFlags(DkQueueFlags_Graphics).create(); + + /* Create the memory pools. */ + m_pool_images.emplace(m_device, DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image, 16*1024*1024); + m_pool_code.emplace(m_device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code, 128*1024); + m_pool_data.emplace(m_device, DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached, 1*1024*1024); + + /* Create the static command buffer and feed it freshly allocated memory. */ + m_cmd_buf = dk::CmdBufMaker{m_device}.create(); + CMemPool::Handle cmdmem = m_pool_data->allocate(StaticCmdSize); + m_cmd_buf.addMemory(cmdmem.getMemBlock(), cmdmem.getOffset(), cmdmem.getSize()); + + /* Create the framebuffer resources. */ + this->CreateFramebufferResources(); + + m_renderer.emplace(FramebufferWidth, FramebufferHeight, m_device, m_queue, *m_pool_images, *m_pool_code, *m_pool_data); + m_vg = nvgCreateDk(&*m_renderer, NVG_ANTIALIAS | NVG_STENCIL_STROKES); + + + PlFontData font; + if (R_FAILED(rc = plGetSharedFontByType(&font, PlSharedFontType_Standard))) { + fatalThrow(rc); + } + + m_standard_font = nvgCreateFontMem(m_vg, "switch-standard", static_cast<u8 *>(font.address), font.size, 0); + } + + ~Daybreak() { + /* Destroy the framebuffer resources. This should be done first. */ + this->DestroyFramebufferResources(); + + /* Cleanup vg. */ + nvgDeleteDk(m_vg); + + /* Destroy the renderer. */ + m_renderer.reset(); + } + private: + void CreateFramebufferResources() { + /* Create layout for the depth buffer. */ + dk::ImageLayout layout_depth_buffer; + dk::ImageLayoutMaker{m_device} + .setFlags(DkImageFlags_UsageRender | DkImageFlags_HwCompression) + .setFormat(DkImageFormat_S8) + .setDimensions(FramebufferWidth, FramebufferHeight) + .initialize(layout_depth_buffer); + + /* Create the depth buffer. */ + m_depth_buffer_mem = m_pool_images->allocate(layout_depth_buffer.getSize(), layout_depth_buffer.getAlignment()); + m_depth_buffer.initialize(layout_depth_buffer, m_depth_buffer_mem.getMemBlock(), m_depth_buffer_mem.getOffset()); + + /* Create layout for the framebuffers. */ + dk::ImageLayout layout_framebuffer; + dk::ImageLayoutMaker{m_device} + .setFlags(DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression) + .setFormat(DkImageFormat_RGBA8_Unorm) + .setDimensions(FramebufferWidth, FramebufferHeight) + .initialize(layout_framebuffer); + + /* Create the framebuffers. */ + std::array<DkImage const*, NumFramebuffers> fb_array; + const u64 fb_size = layout_framebuffer.getSize(); + const u32 fb_align = layout_framebuffer.getAlignment(); + + for (unsigned int i = 0; i < NumFramebuffers; i++) { + /* Allocate a framebuffer. */ + m_framebuffers_mem[i] = m_pool_images->allocate(fb_size, fb_align); + m_framebuffers[i].initialize(layout_framebuffer, m_framebuffers_mem[i].getMemBlock(), m_framebuffers_mem[i].getOffset()); + + /* Generate a command list that binds it. */ + dk::ImageView color_target{ m_framebuffers[i] }, depth_target{ m_depth_buffer }; + m_cmd_buf.bindRenderTargets(&color_target, &depth_target); + m_framebuffer_cmdlists[i] = m_cmd_buf.finishList(); + + /* Fill in the array for use later by the swapchain creation code. */ + fb_array[i] = &m_framebuffers[i]; + } + + /* Create the swapchain using the framebuffers. */ + m_swapchain = dk::SwapchainMaker{m_device, nwindowGetDefault(), fb_array}.create(); + + /* Generate the main rendering cmdlist. */ + this->RecordStaticCommands(); + } + + void DestroyFramebufferResources() { + /* Return early if we have nothing to destroy. */ + if (!m_swapchain) return; + + /* Make sure the queue is idle before destroying anything. */ + m_queue.waitIdle(); + + /* Clear the static cmdbuf, destroying the static cmdlists in the process. */ + m_cmd_buf.clear(); + + /* Destroy the swapchain. */ + m_swapchain.destroy(); + + /* Destroy the framebuffers. */ + for (unsigned int i = 0; i < NumFramebuffers; i ++) { + m_framebuffers_mem[i].destroy(); + } + + /* Destroy the depth buffer. */ + m_depth_buffer_mem.destroy(); + } + + void RecordStaticCommands() { + /* Initialize state structs with deko3d defaults. */ + dk::RasterizerState rasterizer_state; + dk::ColorState color_state; + dk::ColorWriteState color_write_state; + + /* Configure the viewport and scissor. */ + m_cmd_buf.setViewports(0, { { 0.0f, 0.0f, FramebufferWidth, FramebufferHeight, 0.0f, 1.0f } }); + m_cmd_buf.setScissors(0, { { 0, 0, FramebufferWidth, FramebufferHeight } }); + + /* Clear the color and depth buffers. */ + m_cmd_buf.clearColor(0, DkColorMask_RGBA, 0.f, 0.f, 0.f, 1.0f); + m_cmd_buf.clearDepthStencil(true, 1.0f, 0xFF, 0); + + /* Bind required state. */ + m_cmd_buf.bindRasterizerState(rasterizer_state); + m_cmd_buf.bindColorState(color_state); + m_cmd_buf.bindColorWriteState(color_write_state); + + m_render_cmdlist = m_cmd_buf.finishList(); + } + + void Render(u64 ns) { + /* Acquire a framebuffer from the swapchain (and wait for it to be available). */ + int slot = m_queue.acquireImage(m_swapchain); + + /* Run the command list that attaches said framebuffer to the queue. */ + m_queue.submitCommands(m_framebuffer_cmdlists[slot]); + + /* Run the main rendering command list. */ + m_queue.submitCommands(m_render_cmdlist); + + nvgBeginFrame(m_vg, FramebufferWidth, FramebufferHeight, 1.0f); + dbk::RenderMenu(m_vg, ns); + nvgEndFrame(m_vg); + + /* Now that we are done rendering, present it to the screen. */ + m_queue.presentImage(m_swapchain, slot); + } + + public: + bool onFrame(u64 ns) override { + dbk::UpdateMenu(ns); + this->Render(ns); + return !dbk::IsExitRequested(); + } +}; + +int main(int argc, char **argv) { + /* Initialize the menu. */ + if (argc > 1) + dbk::InitializeMenu(FramebufferWidth, FramebufferHeight, argv[1]); + else + dbk::InitializeMenu(FramebufferWidth, FramebufferHeight); + + Daybreak daybreak; + daybreak.run(); + return 0; +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/service_guard.h b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/service_guard.h new file mode 100644 index 00000000..5fbc5fca --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/service_guard.h @@ -0,0 +1,56 @@ +#pragma once +#include <switch/types.h> +#include <switch/result.h> +#include <switch/kernel/mutex.h> +#include <switch/sf/service.h> +#include <switch/services/sm.h> + +typedef struct ServiceGuard { + Mutex mutex; + u32 refCount; +} ServiceGuard; + +NX_INLINE bool serviceGuardBeginInit(ServiceGuard* g) +{ + mutexLock(&g->mutex); + return (g->refCount++) == 0; +} + +NX_INLINE Result serviceGuardEndInit(ServiceGuard* g, Result rc, void (*cleanupFunc)(void)) +{ + if (R_FAILED(rc)) { + cleanupFunc(); + --g->refCount; + } + mutexUnlock(&g->mutex); + return rc; +} + +NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void)) +{ + mutexLock(&g->mutex); + if (g->refCount && (--g->refCount) == 0) + cleanupFunc(); + mutexUnlock(&g->mutex); +} + +#define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \ +\ +static ServiceGuard g_##name##Guard; \ +NX_INLINE Result _##name##Initialize _paramdecl; \ +static void _##name##Cleanup(void); \ +\ +Result name##Initialize _paramdecl \ +{ \ + Result rc = 0; \ + if (serviceGuardBeginInit(&g_##name##Guard)) \ + rc = _##name##Initialize _parampass; \ + return serviceGuardEndInit(&g_##name##Guard, rc, _##name##Cleanup); \ +} \ +\ +void name##Exit(void) \ +{ \ + serviceGuardExit(&g_##name##Guard, _##name##Cleanup); \ +} + +#define NX_GENERATE_SERVICE_GUARD(name) NX_GENERATE_SERVICE_GUARD_PARAMS(name, (void), ()) \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/ui.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/ui.cpp new file mode 100644 index 00000000..1e63f881 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/ui.cpp @@ -0,0 +1,1324 @@ +/* + * Copyright (c) Adubbz + * + * 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 <http://www.gnu.org/licenses/>. + */ +#include <algorithm> +#include <cstdarg> +#include <cstdio> +#include <cstring> +#include <limits> +#include <dirent.h> +#include "ui.hpp" +#include "ui_util.hpp" +#include "assert.hpp" + +namespace dbk { + + namespace { + + static constexpr u32 ExosphereApiVersionConfigItem = 65000; + static constexpr u32 ExosphereHasRcmBugPatch = 65004; + static constexpr u32 ExosphereEmummcType = 65007; + static constexpr u32 ExosphereSupportedHosVersion = 65011; + + /* Insets of content within windows. */ + static constexpr float HorizontalInset = 20.0f; + static constexpr float BottomInset = 20.0f; + + /* Insets of content within text areas. */ + static constexpr float TextHorizontalInset = 8.0f; + static constexpr float TextVerticalInset = 8.0f; + + static constexpr float ButtonHeight = 60.0f; + static constexpr float ButtonHorizontalGap = 10.0f; + + static constexpr float VerticalGap = 10.0f; + + u32 g_screen_width; + u32 g_screen_height; + + constinit u32 g_supported_version = std::numeric_limits<u32>::max(); + + std::shared_ptr<Menu> g_current_menu; + bool g_initialized = false; + bool g_exit_requested = false; + + PadState g_pad; + + u32 g_prev_touch_count = -1; + HidTouchScreenState g_start_touch; + bool g_started_touching = false; + bool g_tapping = false; + bool g_touches_moving = false; + bool g_finished_touching = false; + + /* Update install state. */ + char g_update_path[FS_MAX_PATH]; + bool g_reset_to_factory = false; + bool g_exfat_supported = false; + bool g_use_exfat = false; + + constexpr u32 MaxTapMovement = 20; + + void UpdateInput() { + /* Scan for input and update touch state. */ + padUpdate(&g_pad); + HidTouchScreenState current_touch; + hidGetTouchScreenStates(¤t_touch, 1); + const u32 touch_count = current_touch.count; + + if (g_prev_touch_count == 0 && touch_count > 0) { + hidGetTouchScreenStates(&g_start_touch, 1); + g_started_touching = true; + g_tapping = true; + } else { + g_started_touching = false; + } + + if (g_prev_touch_count > 0 && touch_count == 0) { + g_finished_touching = true; + g_tapping = false; + } else { + g_finished_touching = false; + } + + /* Check if currently moving. */ + if (g_prev_touch_count > 0 && touch_count > 0) { + if ((abs(current_touch.touches[0].x - g_start_touch.touches[0].x) > MaxTapMovement || abs(current_touch.touches[0].y - g_start_touch.touches[0].y) > MaxTapMovement)) { + g_touches_moving = true; + g_tapping = false; + } else { + g_touches_moving = false; + } + } else { + g_touches_moving = false; + } + + /* Update the previous touch count. */ + g_prev_touch_count = current_touch.count; + } + + void ChangeMenu(std::shared_ptr<Menu> menu) { + g_current_menu = menu; + } + + void ReturnToPreviousMenu() { + /* Go to the previous menu if there is one. */ + if (g_current_menu->GetPrevMenu() != nullptr) { + g_current_menu = g_current_menu->GetPrevMenu(); + } + } + + Result IsPathBottomLevel(const char *path, bool *out) { + Result rc = 0; + FsFileSystem *fs; + char translated_path[FS_MAX_PATH] = {}; + DBK_ABORT_UNLESS(fsdevTranslatePath(path, &fs, translated_path) != -1); + + FsDir dir; + if (R_FAILED(rc = fsFsOpenDirectory(fs, translated_path, FsDirOpenMode_ReadDirs, &dir))) { + return rc; + } + + s64 entry_count; + if (R_FAILED(rc = fsDirGetEntryCount(&dir, &entry_count))) { + return rc; + } + + *out = entry_count == 0; + fsDirClose(&dir); + return rc; + } + + u32 EncodeVersion(u32 major, u32 minor, u32 micro, u32 relstep = 0) { + return ((major & 0xFF) << 24) | ((minor & 0xFF) << 16) | ((micro & 0xFF) << 8) | ((relstep & 0xFF) << 8); + } + + } + + void Menu::AddButton(u32 id, const char *text, float x, float y, float w, float h) { + DBK_ABORT_UNLESS(id < MaxButtons); + Button button = { + .id = id, + .selected = false, + .enabled = true, + .x = x, + .y = y, + .w = w, + .h = h, + }; + + strncpy(button.text, text, sizeof(button.text)-1); + m_buttons[id] = button; + } + + void Menu::SetButtonSelected(u32 id, bool selected) { + DBK_ABORT_UNLESS(id < MaxButtons); + auto &button = m_buttons[id]; + + if (button) { + button->selected = selected; + } + } + + void Menu::DeselectAllButtons() { + for (auto &button : m_buttons) { + /* Ensure button is present. */ + if (!button) { + continue; + } + button->selected = false; + } + } + + void Menu::SetButtonEnabled(u32 id, bool enabled) { + DBK_ABORT_UNLESS(id < MaxButtons); + auto &button = m_buttons[id]; + button->enabled = enabled; + } + + Button *Menu::GetButton(u32 id) { + DBK_ABORT_UNLESS(id < MaxButtons); + return !m_buttons[id] ? nullptr : &(*m_buttons[id]); + } + + Button *Menu::GetSelectedButton() { + for (auto &button : m_buttons) { + if (button && button->enabled && button->selected) { + return &(*button); + } + } + + return nullptr; + } + + Button *Menu::GetClosestButtonToSelection(Direction direction) { + const Button *selected_button = this->GetSelectedButton(); + + if (selected_button == nullptr || direction == Direction::Invalid) { + return nullptr; + } + + Button *closest_button = nullptr; + float closest_distance = 0.0f; + + for (auto &button : m_buttons) { + /* Skip absent button. */ + if (!button || !button->enabled) { + continue; + } + + /* Skip buttons that are in the wrong direction. */ + if ((direction == Direction::Down && button->y <= selected_button->y) || + (direction == Direction::Up && button->y >= selected_button->y) || + (direction == Direction::Right && button->x <= selected_button->x) || + (direction == Direction::Left && button->x >= selected_button->x)) { + continue; + } + + const float x_dist = button->x - selected_button->x; + const float y_dist = button->y - selected_button->y; + const float sq_dist = x_dist * x_dist + y_dist * y_dist; + + /* If we don't already have a closest button, set it. */ + if (closest_button == nullptr) { + closest_button = &(*button); + closest_distance = sq_dist; + continue; + } + + /* Update the closest button if this one is closer. */ + if (sq_dist < closest_distance) { + closest_button = &(*button); + closest_distance = sq_dist; + } + } + + return closest_button; + } + + Button *Menu::GetTouchedButton() { + HidTouchScreenState current_touch; + hidGetTouchScreenStates(¤t_touch, 1); + const u32 touch_count = current_touch.count; + + for (u32 i = 0; i < touch_count && g_started_touching; i++) { + for (auto &button : m_buttons) { + if (button && button->enabled && button->IsPositionInBounds(current_touch.touches[i].x, current_touch.touches[i].y)) { + return &(*button); + } + } + } + + return nullptr; + } + + Button *Menu::GetActivatedButton() { + Button *selected_button = this->GetSelectedButton(); + + if (selected_button == nullptr) { + return nullptr; + } + + const u64 k_down = padGetButtonsDown(&g_pad); + + if (k_down & HidNpadButton_A || this->GetTouchedButton() == selected_button) { + return selected_button; + } + + return nullptr; + } + + void Menu::UpdateButtons() { + const u64 k_down = padGetButtonsDown(&g_pad); + Direction direction = Direction::Invalid; + + if (k_down & HidNpadButton_AnyDown) { + direction = Direction::Down; + } else if (k_down & HidNpadButton_AnyUp) { + direction = Direction::Up; + } else if (k_down & HidNpadButton_AnyLeft) { + direction = Direction::Left; + } else if (k_down & HidNpadButton_AnyRight) { + direction = Direction::Right; + } + + /* Select the closest button. */ + if (const Button *closest_button = this->GetClosestButtonToSelection(direction); closest_button != nullptr) { + this->DeselectAllButtons(); + this->SetButtonSelected(closest_button->id, true); + } + + /* Select the touched button. */ + if (const Button *touched_button = this->GetTouchedButton(); touched_button != nullptr) { + this->DeselectAllButtons(); + this->SetButtonSelected(touched_button->id, true); + } + } + + void Menu::DrawButtons(NVGcontext *vg, u64 ns) { + for (auto &button : m_buttons) { + /* Ensure button is present. */ + if (!button) { + continue; + } + + /* Set the button style. */ + auto style = ButtonStyle::StandardDisabled; + if (button->enabled) { + style = button->selected ? ButtonStyle::StandardSelected : ButtonStyle::Standard; + } + + DrawButton(vg, button->text, button->x, button->y, button->w, button->h, style, ns); + } + } + + void Menu::LogText(const char *format, ...) { + /* Create a temporary string. */ + char tmp[0x100]; + va_list args; + va_start(args, format); + vsnprintf(tmp, sizeof(tmp), format, args); + va_end(args); + + /* Append the text to the log buffer. */ + strncat(m_log_buffer, tmp, sizeof(m_log_buffer)-1); + } + + std::shared_ptr<Menu> Menu::GetPrevMenu() { + return m_prev_menu; + } + + AlertMenu::AlertMenu(std::shared_ptr<Menu> prev_menu, const char *text, const char *subtext, Result rc) : Menu(prev_menu), m_text{}, m_subtext{}, m_result_text{}, m_rc(rc){ + /* Copy the input text. */ + strncpy(m_text, text, sizeof(m_text)-1); + strncpy(m_subtext, subtext, sizeof(m_subtext)-1); + + /* Copy result text if there is a result. */ + if (R_FAILED(rc)) { + snprintf(m_result_text, sizeof(m_result_text), "Result: 0x%08x", rc); + } + } + + void AlertMenu::Draw(NVGcontext *vg, u64 ns) { + const float window_height = WindowHeight + (R_FAILED(m_rc) ? SubTextHeight : 0.0f); + const float x = g_screen_width / 2.0f - WindowWidth / 2.0f; + const float y = g_screen_height / 2.0f - window_height / 2.0f; + + DrawWindow(vg, m_text, x, y, WindowWidth, window_height); + DrawText(vg, x + HorizontalInset, y + TitleGap, WindowWidth - HorizontalInset * 2.0f, m_subtext); + + /* Draw the result if there is one. */ + if (R_FAILED(m_rc)) { + DrawText(vg, x + HorizontalInset, y + TitleGap + SubTextHeight, WindowWidth - HorizontalInset * 2.0f, m_result_text); + } + + this->DrawButtons(vg, ns); + } + + ErrorMenu::ErrorMenu(const char *text, const char *subtext, Result rc) : AlertMenu(nullptr, text, subtext, rc) { + const float window_height = WindowHeight + (R_FAILED(m_rc) ? SubTextHeight : 0.0f); + const float x = g_screen_width / 2.0f - WindowWidth / 2.0f; + const float y = g_screen_height / 2.0f - window_height / 2.0f; + const float button_y = y + TitleGap + SubTextHeight + VerticalGap * 2.0f + (R_FAILED(m_rc) ? SubTextHeight : 0.0f); + const float button_width = WindowWidth - HorizontalInset * 2.0f; + + /* Add buttons. */ + this->AddButton(ExitButtonId, "Exit", x + HorizontalInset, button_y, button_width, ButtonHeight); + this->SetButtonSelected(ExitButtonId, true); + } + + void ErrorMenu::Update(u64 ns) { + u64 k_down = padGetButtonsDown(&g_pad); + + /* Go back if B is pressed. */ + if (k_down & HidNpadButton_B) { + g_exit_requested = true; + return; + } + + /* Take action if a button has been activated. */ + if (const Button *activated_button = this->GetActivatedButton(); activated_button != nullptr) { + switch (activated_button->id) { + case ExitButtonId: + g_exit_requested = true; + break; + } + } + + this->UpdateButtons(); + + /* Fallback on selecting the exfat button. */ + if (const Button *selected_button = this->GetSelectedButton(); k_down && selected_button == nullptr) { + this->SetButtonSelected(ExitButtonId, true); + } + } + + WarningMenu::WarningMenu(std::shared_ptr<Menu> prev_menu, std::shared_ptr<Menu> next_menu, const char *text, const char *subtext, Result rc) : AlertMenu(prev_menu, text, subtext, rc), m_next_menu(next_menu) { + const float window_height = WindowHeight + (R_FAILED(m_rc) ? SubTextHeight : 0.0f); + const float x = g_screen_width / 2.0f - WindowWidth / 2.0f; + const float y = g_screen_height / 2.0f - window_height / 2.0f; + + const float button_y = y + TitleGap + SubTextHeight + VerticalGap * 2.0f + (R_FAILED(m_rc) ? SubTextHeight : 0.0f); + const float button_width = (WindowWidth - HorizontalInset * 2.0f) / 2.0f - ButtonHorizontalGap; + this->AddButton(BackButtonId, "Back", x + HorizontalInset, button_y, button_width, ButtonHeight); + this->AddButton(ContinueButtonId, "Continue", x + HorizontalInset + button_width + ButtonHorizontalGap, button_y, button_width, ButtonHeight); + this->SetButtonSelected(ContinueButtonId, true); + } + + void WarningMenu::Update(u64 ns) { + u64 k_down = padGetButtonsDown(&g_pad); + + /* Go back if B is pressed. */ + if (k_down & HidNpadButton_B) { + ReturnToPreviousMenu(); + return; + } + + /* Take action if a button has been activated. */ + if (const Button *activated_button = this->GetActivatedButton(); activated_button != nullptr) { + switch (activated_button->id) { + case BackButtonId: + ReturnToPreviousMenu(); + return; + case ContinueButtonId: + ChangeMenu(m_next_menu); + return; + } + } + + this->UpdateButtons(); + + /* Fallback on selecting the exfat button. */ + if (const Button *selected_button = this->GetSelectedButton(); k_down && selected_button == nullptr) { + this->SetButtonSelected(ContinueButtonId, true); + } + } + + MainMenu::MainMenu() : Menu(nullptr) { + const float x = g_screen_width / 2.0f - WindowWidth / 2.0f; + const float y = g_screen_height / 2.0f - WindowHeight / 2.0f; + + this->AddButton(InstallButtonId, "Install", x + HorizontalInset, y + TitleGap, WindowWidth - HorizontalInset * 2, ButtonHeight); + this->AddButton(ExitButtonId, "Exit", x + HorizontalInset, y + TitleGap + ButtonHeight + VerticalGap, WindowWidth - HorizontalInset * 2, ButtonHeight); + this->SetButtonSelected(InstallButtonId, true); + } + + void MainMenu::Update(u64 ns) { + u64 k_down = padGetButtonsDown(&g_pad); + + if (k_down & HidNpadButton_B) { + g_exit_requested = true; + } + + /* Take action if a button has been activated. */ + if (const Button *activated_button = this->GetActivatedButton(); activated_button != nullptr) { + switch (activated_button->id) { + case InstallButtonId: + { + const auto file_menu = std::make_shared<FileMenu>(g_current_menu, "/"); + + Result rc = 0; + u64 hardware_type; + u64 has_rcm_bug_patch; + u64 is_emummc; + + if (R_FAILED(rc = splGetConfig(SplConfigItem_HardwareType, &hardware_type))) { + ChangeMenu(std::make_shared<ErrorMenu>("An error has occurred", "Failed to get hardware type.", rc)); + return; + } + + if (R_FAILED(rc = splGetConfig(static_cast<SplConfigItem>(ExosphereHasRcmBugPatch), &has_rcm_bug_patch))) { + ChangeMenu(std::make_shared<ErrorMenu>("An error has occurred", "Failed to check RCM bug status.", rc)); + return; + } + + if (R_FAILED(rc = splGetConfig(static_cast<SplConfigItem>(ExosphereEmummcType), &is_emummc))) { + ChangeMenu(std::make_shared<ErrorMenu>("An error has occurred", "Failed to check emuMMC status.", rc)); + return; + } + + /* Warn if we're working with a patched unit. */ + const bool is_erista = hardware_type == 0 || hardware_type == 1; + if (is_erista && has_rcm_bug_patch && !is_emummc) { + ChangeMenu(std::make_shared<WarningMenu>(g_current_menu, file_menu, "Warning: Patched unit detected", "You may burn fuses or render your switch inoperable.")); + } else { + ChangeMenu(file_menu); + } + + return; + } + case ExitButtonId: + g_exit_requested = true; + return; + } + } + + this->UpdateButtons(); + + /* Fallback on selecting the install button. */ + if (const Button *selected_button = this->GetSelectedButton(); k_down && selected_button == nullptr) { + this->SetButtonSelected(InstallButtonId, true); + } + } + + void MainMenu::Draw(NVGcontext *vg, u64 ns) { + DrawWindow(vg, "Daybreak", g_screen_width / 2.0f - WindowWidth / 2.0f, g_screen_height / 2.0f - WindowHeight / 2.0f, WindowWidth, WindowHeight); + this->DrawButtons(vg, ns); + } + + FileMenu::FileMenu(std::shared_ptr<Menu> prev_menu, const char *root) : Menu(prev_menu), m_current_index(0), m_scroll_offset(0), m_touch_start_scroll_offset(0), m_touch_finalize_selection(false) { + Result rc = 0; + + strncpy(m_root, root, sizeof(m_root)-1); + + if (R_FAILED(rc = this->PopulateFileEntries())) { + fatalThrow(rc); + } + } + + Result FileMenu::PopulateFileEntries() { + /* Open the directory. */ + DIR *dir = opendir(m_root); + if (dir == nullptr) { + return fsdevGetLastResult(); + } + + /* Add file entries to the list. */ + struct dirent *ent; + while ((ent = readdir(dir)) != nullptr) { + if (ent->d_type == DT_DIR) { + FileEntry file_entry = {}; + strncpy(file_entry.name, ent->d_name, sizeof(file_entry.name)); + m_file_entries.push_back(file_entry); + } + } + + /* Close the directory. */ + closedir(dir); + + /* Sort the file entries. */ + std::sort(m_file_entries.begin(), m_file_entries.end(), [](const FileEntry &a, const FileEntry &b) { + return strncmp(a.name, b.name, sizeof(a.name)) < 0; + }); + + return 0; + } + + bool FileMenu::IsSelectionVisible() { + const float visible_start = m_scroll_offset; + const float visible_end = visible_start + FileListHeight; + const float entry_start = static_cast<float>(m_current_index) * (FileRowHeight + FileRowGap); + const float entry_end = entry_start + (FileRowHeight + FileRowGap); + return entry_start >= visible_start && entry_end <= visible_end; + } + + void FileMenu::ScrollToSelection() { + const float visible_start = m_scroll_offset; + const float visible_end = visible_start + FileListHeight; + const float entry_start = static_cast<float>(m_current_index) * (FileRowHeight + FileRowGap); + const float entry_end = entry_start + (FileRowHeight + FileRowGap); + + if (entry_end > visible_end) { + m_scroll_offset += entry_end - visible_end; + } else if (entry_end < visible_end) { + m_scroll_offset = entry_start; + } + } + + bool FileMenu::IsEntryTouched(u32 i) { + const float x = g_screen_width / 2.0f - WindowWidth / 2.0f; + const float y = g_screen_height / 2.0f - WindowHeight / 2.0f; + + HidTouchScreenState current_touch; + hidGetTouchScreenStates(¤t_touch, 1); + + /* Check if the tap is within the x bounds. */ + if (current_touch.touches[0].x >= x + TextBackgroundOffset + FileRowHorizontalInset && current_touch.touches[0].x <= WindowWidth - (TextBackgroundOffset + FileRowHorizontalInset) * 2.0f) { + const float y_min = y + TitleGap + FileRowGap + i * (FileRowHeight + FileRowGap) - m_scroll_offset; + const float y_max = y_min + FileRowHeight; + + /* Check if the tap is within the y bounds. */ + if (current_touch.touches[0].y >= y_min && current_touch.touches[0].y <= y_max) { + return true; + } + } + + return false; + } + + void FileMenu::UpdateTouches() { + /* Setup values on initial touch. */ + if (g_started_touching) { + m_touch_start_scroll_offset = m_scroll_offset; + + /* We may potentially finalize the selection later if we start off touching it. */ + if (this->IsEntryTouched(m_current_index)) { + m_touch_finalize_selection = true; + } + } + + /* Scroll based on touch movement. */ + if (g_touches_moving) { + HidTouchScreenState current_touch; + hidGetTouchScreenStates(¤t_touch, 1); + + const int dist_y = current_touch.touches[0].y - g_start_touch.touches[0].y; + float new_scroll_offset = m_touch_start_scroll_offset - static_cast<float>(dist_y); + float max_scroll = (FileRowHeight + FileRowGap) * static_cast<float>(m_file_entries.size()) - FileListHeight; + + /* Don't allow scrolling if there is not enough elements. */ + if (max_scroll < 0.0f) { + max_scroll = 0.0f; + } + + /* Don't allow scrolling before the first element. */ + if (new_scroll_offset < 0.0f) { + new_scroll_offset = 0.0f; + } + + /* Don't allow scrolling past the last element. */ + if (new_scroll_offset > max_scroll) { + new_scroll_offset = max_scroll; + } + + m_scroll_offset = new_scroll_offset; + } + + /* Select any tapped entries. */ + if (g_tapping) { + for (u32 i = 0; i < m_file_entries.size(); i++) { + if (this->IsEntryTouched(i)) { + /* The current index is checked later. */ + if (i == m_current_index) { + continue; + } + + m_current_index = i; + + /* Don't finalize selection if we touch something else. */ + m_touch_finalize_selection = false; + break; + } + } + } + + /* Don't finalize selection if we aren't finished and we've either stopped tapping or are no longer touching the selection. */ + if (!g_finished_touching && (!g_tapping || !this->IsEntryTouched(m_current_index))) { + m_touch_finalize_selection = false; + } + + /* Finalize selection if the currently selected entry is touched for the second time. */ + if (g_finished_touching && m_touch_finalize_selection) { + this->FinalizeSelection(); + m_touch_finalize_selection = false; + } + } + + void FileMenu::FinalizeSelection() { + DBK_ABORT_UNLESS(m_current_index < m_file_entries.size()); + FileEntry &entry = m_file_entries[m_current_index]; + + /* Determine the selected path. */ + char current_path[FS_MAX_PATH] = {}; + const int path_len = snprintf(current_path, sizeof(current_path), "%s%s/", m_root, entry.name); + DBK_ABORT_UNLESS(path_len >= 0 && path_len < static_cast<int>(sizeof(current_path))); + + /* Determine if the chosen path is the bottom level. */ + Result rc = 0; + bool bottom_level; + if (R_FAILED(rc = IsPathBottomLevel(current_path, &bottom_level))) { + fatalThrow(rc); + } + + /* Show exfat settings or the next file menu. */ + if (bottom_level) { + /* Set the update path. */ + snprintf(g_update_path, sizeof(g_update_path), "%s", current_path); + + /* Change the menu. */ + ChangeMenu(std::make_shared<ValidateUpdateMenu>(g_current_menu)); + } else { + ChangeMenu(std::make_shared<FileMenu>(g_current_menu, current_path)); + } + } + + void FileMenu::Update(u64 ns) { + u64 k_down = padGetButtonsDown(&g_pad); + + /* Go back if B is pressed. */ + if (k_down & HidNpadButton_B) { + ReturnToPreviousMenu(); + return; + } + + /* Finalize selection on pressing A. */ + if (k_down & HidNpadButton_A) { + this->FinalizeSelection(); + } + + /* Update touch input. */ + this->UpdateTouches(); + + const u32 prev_index = m_current_index; + + if (k_down & HidNpadButton_AnyDown) { + /* Scroll down. */ + if (m_current_index >= (m_file_entries.size() - 1)) { + m_current_index = 0; + } else { + m_current_index++; + } + } else if (k_down & HidNpadButton_AnyUp) { + /* Scroll up. */ + if (m_current_index == 0) { + m_current_index = m_file_entries.size() - 1; + } else { + m_current_index--; + } + } + + /* Scroll to the selection if it isn't visible. */ + if (prev_index != m_current_index && !this->IsSelectionVisible()) { + this->ScrollToSelection(); + } + } + + void FileMenu::Draw(NVGcontext *vg, u64 ns) { + const float x = g_screen_width / 2.0f - WindowWidth / 2.0f; + const float y = g_screen_height / 2.0f - WindowHeight / 2.0f; + + DrawWindow(vg, "Select an update directory", x, y, WindowWidth, WindowHeight); + DrawTextBackground(vg, x + TextBackgroundOffset, y + TitleGap, WindowWidth - TextBackgroundOffset * 2.0f, (FileRowHeight + FileRowGap) * MaxFileRows + FileRowGap); + + nvgSave(vg); + nvgScissor(vg, x + TextBackgroundOffset, y + TitleGap, WindowWidth - TextBackgroundOffset * 2.0f, (FileRowHeight + FileRowGap) * MaxFileRows + FileRowGap); + + for (u32 i = 0; i < m_file_entries.size(); i++) { + FileEntry &entry = m_file_entries[i]; + auto style = ButtonStyle::FileSelect; + + if (i == m_current_index) { + style = ButtonStyle::FileSelectSelected; + } + + DrawButton(vg, entry.name, x + TextBackgroundOffset + FileRowHorizontalInset, y + TitleGap + FileRowGap + i * (FileRowHeight + FileRowGap) - m_scroll_offset, WindowWidth - (TextBackgroundOffset + FileRowHorizontalInset) * 2.0f, FileRowHeight, style, ns); + } + + nvgRestore(vg); + } + + ValidateUpdateMenu::ValidateUpdateMenu(std::shared_ptr<Menu> prev_menu) : Menu(prev_menu), m_has_drawn(false), m_has_info(false), m_has_validated(false) { + const float x = g_screen_width / 2.0f - WindowWidth / 2.0f; + const float y = g_screen_height / 2.0f - WindowHeight / 2.0f; + const float button_width = (WindowWidth - HorizontalInset * 2.0f) / 2.0f - ButtonHorizontalGap; + + /* Add buttons. */ + this->AddButton(BackButtonId, "Back", x + HorizontalInset, y + WindowHeight - BottomInset - ButtonHeight, button_width, ButtonHeight); + this->AddButton(ContinueButtonId, "Continue", x + HorizontalInset + button_width + ButtonHorizontalGap, y + WindowHeight - BottomInset - ButtonHeight, button_width, ButtonHeight); + this->SetButtonEnabled(BackButtonId, false); + this->SetButtonEnabled(ContinueButtonId, false); + + /* Obtain update information. */ + if (R_FAILED(this->GetUpdateInformation())) { + this->SetButtonEnabled(BackButtonId, true); + this->SetButtonSelected(BackButtonId, true); + } else { + /* Log this early so it is printed out before validation causes stalling. */ + this->LogText("Validating update, this may take a moment...\n"); + } + } + + Result ValidateUpdateMenu::GetUpdateInformation() { + Result rc = 0; + this->LogText("Directory %s\n", g_update_path); + + /* Attempt to get the update information. */ + if (R_FAILED(rc = amssuGetUpdateInformation(&m_update_info, g_update_path))) { + if (rc == 0x1a405) { + this->LogText("No update found in folder.\nEnsure your ncas are named correctly!\nResult: 0x%08x\n", rc); + } else { + this->LogText("Failed to get update information.\nResult: 0x%08x\n", rc); + } + return rc; + } + + /* Print update information. */ + this->LogText("- Version: %d.%d.%d\n", (m_update_info.version >> 26) & 0x1f, (m_update_info.version >> 20) & 0x1f, (m_update_info.version >> 16) & 0xf); + if (m_update_info.exfat_supported) { + this->LogText("- exFAT: Supported\n"); + } else { + this->LogText("- exFAT: Unsupported\n"); + } + this->LogText("- Firmware variations: %d\n", m_update_info.num_firmware_variations); + + /* Mark as having obtained update info. */ + m_has_info = true; + return rc; + } + + void ValidateUpdateMenu::ValidateUpdate() { + Result rc = 0; + + /* Validate the update. */ + if (R_FAILED(rc = amssuValidateUpdate(&m_validation_info, g_update_path))) { + this->LogText("Failed to validate update.\nResult: 0x%08x\n", rc); + return; + } + + /* Check the result. */ + if (R_SUCCEEDED(m_validation_info.result)) { + this->LogText("Update is valid!\n"); + + if (R_FAILED(m_validation_info.exfat_result)) { + const u32 version = m_validation_info.invalid_key.version; + this->LogText("exFAT Validation failed with result: 0x%08x\n", m_validation_info.exfat_result); + this->LogText("Missing content:\n- Program id: %016lx\n- Version: %d.%d.%d\n", m_validation_info.invalid_key.id, (version >> 26) & 0x1f, (version >> 20) & 0x1f, (version >> 16) & 0xf); + + /* Log the missing content id. */ + this->LogText("- Content id: "); + for (size_t i = 0; i < sizeof(NcmContentId); i++) { + this->LogText("%02x", m_validation_info.invalid_content_id.c[i]); + } + this->LogText("\n"); + } + + /* Enable the back and continue buttons and select the continue button. */ + this->SetButtonEnabled(BackButtonId, true); + this->SetButtonEnabled(ContinueButtonId, true); + this->SetButtonSelected(ContinueButtonId, true); + } else { + /* Log the missing content info. */ + const u32 version = m_validation_info.invalid_key.version; + this->LogText("Validation failed with result: 0x%08x\n", m_validation_info.result); + this->LogText("Missing content:\n- Program id: %016lx\n- Version: %d.%d.%d\n", m_validation_info.invalid_key.id, (version >> 26) & 0x1f, (version >> 20) & 0x1f, (version >> 16) & 0xf); + + /* Log the missing content id. */ + this->LogText("- Content id: "); + for (size_t i = 0; i < sizeof(NcmContentId); i++) { + this->LogText("%02x", m_validation_info.invalid_content_id.c[i]); + } + this->LogText("\n"); + + /* Enable the back button and select it. */ + this->SetButtonEnabled(BackButtonId, true); + this->SetButtonSelected(BackButtonId, true); + } + + /* Mark validation as being complete. */ + m_has_validated = true; + } + + void ValidateUpdateMenu::Update(u64 ns) { + /* Perform validation if it hasn't been done already. */ + if (m_has_info && m_has_drawn && !m_has_validated) { + this->ValidateUpdate(); + } + + u64 k_down = padGetButtonsDown(&g_pad); + + /* Go back if B is pressed. */ + if (k_down & HidNpadButton_B) { + ReturnToPreviousMenu(); + return; + } + + /* Take action if a button has been activated. */ + if (const Button *activated_button = this->GetActivatedButton(); activated_button != nullptr) { + switch (activated_button->id) { + case BackButtonId: + ReturnToPreviousMenu(); + return; + case ContinueButtonId: + /* Don't continue if validation hasn't been done or has failed. */ + if (!m_has_validated || R_FAILED(m_validation_info.result)) { + break; + } + + /* Check if exfat is supported. */ + g_exfat_supported = m_update_info.exfat_supported && R_SUCCEEDED(m_validation_info.exfat_result); + if (!g_exfat_supported) { + g_use_exfat = false; + } + + /* Create the next menu. */ + std::shared_ptr<Menu> next_menu = std::make_shared<ChooseResetMenu>(g_current_menu); + + /* Warn the user if they're updating with exFAT supposed to be supported but not present/corrupted. */ + if (m_update_info.exfat_supported && R_FAILED(m_validation_info.exfat_result)) { + next_menu = std::make_shared<WarningMenu>(g_current_menu, next_menu, "Warning: exFAT firmware is missing or corrupt", "Are you sure you want to proceed?"); + } + + /* Warn the user if they're updating to a version higher than supported. */ + const u32 version = m_validation_info.invalid_key.version; + if (EncodeVersion((version >> 26) & 0x1f, (version >> 20) & 0x1f, (version >> 16) & 0xf) > g_supported_version) { + next_menu = std::make_shared<WarningMenu>(g_current_menu, next_menu, "Warning: firmware is too new and not known to be supported", "Are you sure you want to proceed?"); + } + + /* Change to the next menu. */ + ChangeMenu(next_menu); + return; + } + } + + this->UpdateButtons(); + } + + void ValidateUpdateMenu::Draw(NVGcontext *vg, u64 ns) { + const float x = g_screen_width / 2.0f - WindowWidth / 2.0f; + const float y = g_screen_height / 2.0f - WindowHeight / 2.0f; + + DrawWindow(vg, "Update information", x, y, WindowWidth, WindowHeight); + DrawTextBackground(vg, x + HorizontalInset, y + TitleGap, WindowWidth - HorizontalInset * 2.0f, TextAreaHeight); + DrawTextBlock(vg, m_log_buffer, x + HorizontalInset + TextHorizontalInset, y + TitleGap + TextVerticalInset, WindowWidth - (HorizontalInset + TextHorizontalInset) * 2.0f, TextAreaHeight - TextVerticalInset * 2.0f); + + this->DrawButtons(vg, ns); + m_has_drawn = true; + } + + ChooseResetMenu::ChooseResetMenu(std::shared_ptr<Menu> prev_menu) : Menu(prev_menu) { + const float x = g_screen_width / 2.0f - WindowWidth / 2.0f; + const float y = g_screen_height / 2.0f - WindowHeight / 2.0f; + const float button_width = (WindowWidth - HorizontalInset * 2.0f) / 2.0f - ButtonHorizontalGap; + + /* Add buttons. */ + this->AddButton(ResetToFactorySettingsButtonId, "Reset to factory settings", x + HorizontalInset, y + TitleGap, button_width, ButtonHeight); + this->AddButton(PreserveSettingsButtonId, "Preserve settings", x + HorizontalInset + button_width + ButtonHorizontalGap, y + TitleGap, button_width, ButtonHeight); + this->SetButtonSelected(PreserveSettingsButtonId, true); + } + + void ChooseResetMenu::Update(u64 ns) { + u64 k_down = padGetButtonsDown(&g_pad); + + /* Go back if B is pressed. */ + if (k_down & HidNpadButton_B) { + ReturnToPreviousMenu(); + return; + } + + /* Take action if a button has been activated. */ + if (const Button *activated_button = this->GetActivatedButton(); activated_button != nullptr) { + switch (activated_button->id) { + case ResetToFactorySettingsButtonId: + g_reset_to_factory = true; + break; + case PreserveSettingsButtonId: + g_reset_to_factory = false; + break; + } + + std::shared_ptr<Menu> next_menu; + + if (g_exfat_supported) { + next_menu = std::make_shared<ChooseExfatMenu>(g_current_menu); + } else { + next_menu = std::make_shared<WarningMenu>(g_current_menu, std::make_shared<InstallUpdateMenu>(g_current_menu), "Ready to begin update installation", "Are you sure you want to proceed?"); + } + + if (g_reset_to_factory) { + ChangeMenu(std::make_shared<WarningMenu>(g_current_menu, next_menu, "Warning: Factory reset selected", "Saves and installed games will be permanently deleted.")); + } else { + ChangeMenu(next_menu); + } + } + + this->UpdateButtons(); + + /* Fallback on selecting the exfat button. */ + if (const Button *selected_button = this->GetSelectedButton(); k_down && selected_button == nullptr) { + this->SetButtonSelected(PreserveSettingsButtonId, true); + } + } + + void ChooseResetMenu::Draw(NVGcontext *vg, u64 ns) { + const float x = g_screen_width / 2.0f - WindowWidth / 2.0f; + const float y = g_screen_height / 2.0f - WindowHeight / 2.0f; + + DrawWindow(vg, "Select settings mode", x, y, WindowWidth, WindowHeight); + this->DrawButtons(vg, ns); + } + + ChooseExfatMenu::ChooseExfatMenu(std::shared_ptr<Menu> prev_menu) : Menu(prev_menu) { + const float x = g_screen_width / 2.0f - WindowWidth / 2.0f; + const float y = g_screen_height / 2.0f - WindowHeight / 2.0f; + const float button_width = (WindowWidth - HorizontalInset * 2.0f) / 2.0f - ButtonHorizontalGap; + + /* Add buttons. */ + this->AddButton(Fat32ButtonId, "Install (FAT32)", x + HorizontalInset, y + TitleGap, button_width, ButtonHeight); + this->AddButton(ExFatButtonId, "Install (FAT32 + exFAT)", x + HorizontalInset + button_width + ButtonHorizontalGap, y + TitleGap, button_width, ButtonHeight); + + /* Set the default selected button based on the user's current install. We aren't particularly concerned if fsIsExFatSupported fails. */ + bool exfat_supported = false; + fsIsExFatSupported(&exfat_supported); + + if (exfat_supported) { + this->SetButtonSelected(ExFatButtonId, true); + } else { + this->SetButtonSelected(Fat32ButtonId, true); + } + } + + void ChooseExfatMenu::Update(u64 ns) { + u64 k_down = padGetButtonsDown(&g_pad); + + /* Go back if B is pressed. */ + if (k_down & HidNpadButton_B) { + ReturnToPreviousMenu(); + return; + } + + /* Take action if a button has been activated. */ + if (const Button *activated_button = this->GetActivatedButton(); activated_button != nullptr) { + switch (activated_button->id) { + case Fat32ButtonId: + g_use_exfat = false; + break; + case ExFatButtonId: + g_use_exfat = true; + break; + } + + ChangeMenu(std::make_shared<WarningMenu>(g_current_menu, std::make_shared<InstallUpdateMenu>(g_current_menu), "Ready to begin update installation", "Are you sure you want to proceed?")); + } + + this->UpdateButtons(); + + /* Fallback on selecting the exfat button. */ + if (const Button *selected_button = this->GetSelectedButton(); k_down && selected_button == nullptr) { + this->SetButtonSelected(ExFatButtonId, true); + } + } + + void ChooseExfatMenu::Draw(NVGcontext *vg, u64 ns) { + const float x = g_screen_width / 2.0f - WindowWidth / 2.0f; + const float y = g_screen_height / 2.0f - WindowHeight / 2.0f; + + DrawWindow(vg, "Select driver variant", x, y, WindowWidth, WindowHeight); + this->DrawButtons(vg, ns); + } + + InstallUpdateMenu::InstallUpdateMenu(std::shared_ptr<Menu> prev_menu) : Menu(prev_menu), m_install_state(InstallState::NeedsDraw), m_progress_percent(0.0f) { + const float x = g_screen_width / 2.0f - WindowWidth / 2.0f; + const float y = g_screen_height / 2.0f - WindowHeight / 2.0f; + const float button_width = (WindowWidth - HorizontalInset * 2.0f) / 2.0f - ButtonHorizontalGap; + + /* Add buttons. */ + this->AddButton(ShutdownButtonId, "Shutdown", x + HorizontalInset, y + WindowHeight - BottomInset - ButtonHeight, button_width, ButtonHeight); + this->AddButton(RebootButtonId, "Reboot", x + HorizontalInset + button_width + ButtonHorizontalGap, y + WindowHeight - BottomInset - ButtonHeight, button_width, ButtonHeight); + this->SetButtonEnabled(ShutdownButtonId, false); + this->SetButtonEnabled(RebootButtonId, false); + + /* Prevent the home button from being pressed during installation. */ + hiddbgDeactivateHomeButton(); + } + + void InstallUpdateMenu::MarkForReboot() { + this->SetButtonEnabled(ShutdownButtonId, true); + this->SetButtonEnabled(RebootButtonId, true); + this->SetButtonSelected(RebootButtonId, true); + m_install_state = InstallState::AwaitingReboot; + } + + Result InstallUpdateMenu::TransitionUpdateState() { + Result rc = 0; + if (m_install_state == InstallState::NeedsSetup) { + /* Setup the update. */ + if (R_FAILED(rc = amssuSetupUpdate(nullptr, UpdateTaskBufferSize, g_update_path, g_use_exfat))) { + this->LogText("Failed to setup update.\nResult: 0x%08x\n", rc); + this->MarkForReboot(); + return rc; + } + + /* Log setup completion. */ + this->LogText("Update setup complete.\n"); + m_install_state = InstallState::NeedsPrepare; + } else if (m_install_state == InstallState::NeedsPrepare) { + /* Request update preparation. */ + if (R_FAILED(rc = amssuRequestPrepareUpdate(&m_prepare_result))) { + this->LogText("Failed to request update preparation.\nResult: 0x%08x\n", rc); + this->MarkForReboot(); + return rc; + } + + /* Log awaiting prepare. */ + this->LogText("Preparing update...\n"); + m_install_state = InstallState::AwaitingPrepare; + } else if (m_install_state == InstallState::AwaitingPrepare) { + /* Check if preparation has a result. */ + if (R_FAILED(rc = asyncResultWait(&m_prepare_result, 0)) && rc != 0xea01) { + this->LogText("Failed to check update preparation result.\nResult: 0x%08x\n", rc); + this->MarkForReboot(); + return rc; + } else if (R_SUCCEEDED(rc)) { + if (R_FAILED(rc = asyncResultGet(&m_prepare_result))) { + this->LogText("Failed to prepare update.\nResult: 0x%08x\n", rc); + this->MarkForReboot(); + return rc; + } + } + + /* Check if the update has been prepared. */ + bool prepared; + if (R_FAILED(rc = amssuHasPreparedUpdate(&prepared))) { + this->LogText("Failed to check if update has been prepared.\nResult: 0x%08x\n", rc); + this->MarkForReboot(); + return rc; + } + + /* Mark for application if preparation complete. */ + if (prepared) { + this->LogText("Update preparation complete.\nApplying update...\n"); + m_install_state = InstallState::NeedsApply; + return rc; + } + + /* Check update progress. */ + NsSystemUpdateProgress update_progress = {}; + if (R_FAILED(rc = amssuGetPrepareUpdateProgress(&update_progress))) { + this->LogText("Failed to check update progress.\nResult: 0x%08x\n", rc); + this->MarkForReboot(); + return rc; + } + + /* Update progress percent. */ + if (update_progress.total_size > 0.0f) { + m_progress_percent = static_cast<float>(update_progress.current_size) / static_cast<float>(update_progress.total_size); + } else { + m_progress_percent = 0.0f; + } + } else if (m_install_state == InstallState::NeedsApply) { + /* Apply the prepared update. */ + if (R_FAILED(rc = amssuApplyPreparedUpdate())) { + this->LogText("Failed to apply update.\nResult: 0x%08x\n", rc); + } else { + /* Log success. */ + this->LogText("Update applied successfully.\n"); + + if (g_reset_to_factory) { + if (R_FAILED(rc = nsResetToFactorySettingsForRefurbishment())) { + /* Fallback on ResetToFactorySettings. */ + if (rc == MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer)) { + if (R_FAILED(rc = nsResetToFactorySettings())) { + this->LogText("Failed to reset to factory settings.\nResult: 0x%08x\n", rc); + this->MarkForReboot(); + return rc; + } + } else { + this->LogText("Failed to reset to factory settings for refurbishment.\nResult: 0x%08x\n", rc); + this->MarkForReboot(); + return rc; + } + } + + this->LogText("Successfully reset to factory settings.\n", rc); + } + } + + this->MarkForReboot(); + return rc; + } + + return rc; + } + + void InstallUpdateMenu::Update(u64 ns) { + /* Transition to the next update state. */ + if (m_install_state != InstallState::NeedsDraw && m_install_state != InstallState::AwaitingReboot) { + this->TransitionUpdateState(); + } + + /* Take action if a button has been activated. */ + if (const Button *activated_button = this->GetActivatedButton(); activated_button != nullptr) { + switch (activated_button->id) { + case ShutdownButtonId: + if (R_FAILED(appletRequestToShutdown())) { + spsmShutdown(false); + } + break; + case RebootButtonId: + if (R_FAILED(appletRequestToReboot())) { + spsmShutdown(true); + } + break; + } + } + + this->UpdateButtons(); + } + + void InstallUpdateMenu::Draw(NVGcontext *vg, u64 ns) { + const float x = g_screen_width / 2.0f - WindowWidth / 2.0f; + const float y = g_screen_height / 2.0f - WindowHeight / 2.0f; + + DrawWindow(vg, "Installing update", x, y, WindowWidth, WindowHeight); + DrawProgressText(vg, x + HorizontalInset, y + TitleGap, m_progress_percent); + DrawProgressBar(vg, x + HorizontalInset, y + TitleGap + ProgressTextHeight, WindowWidth - HorizontalInset * 2.0f, ProgressBarHeight, m_progress_percent); + DrawTextBackground(vg, x + HorizontalInset, y + TitleGap + ProgressTextHeight + ProgressBarHeight + VerticalGap, WindowWidth - HorizontalInset * 2.0f, TextAreaHeight); + DrawTextBlock(vg, m_log_buffer, x + HorizontalInset + TextHorizontalInset, y + TitleGap + ProgressTextHeight + ProgressBarHeight + VerticalGap + TextVerticalInset, WindowWidth - (HorizontalInset + TextHorizontalInset) * 2.0f, TextAreaHeight - TextVerticalInset * 2.0f); + + this->DrawButtons(vg, ns); + + /* We have drawn now, allow setup to occur. */ + if (m_install_state == InstallState::NeedsDraw) { + this->LogText("Beginning update setup...\n"); + m_install_state = InstallState::NeedsSetup; + } + } + + bool InitializeMenu(u32 screen_width, u32 screen_height) { + Result rc = 0; + + /* Configure and initialize the gamepad. */ + padConfigureInput(1, HidNpadStyleSet_NpadStandard); + padInitializeDefault(&g_pad); + + /* Initialize the touch screen. */ + hidInitializeTouchScreen(); + + /* Set the screen width and height. */ + g_screen_width = screen_width; + g_screen_height = screen_height; + + /* Mark as initialized. */ + g_initialized = true; + + /* Attempt to get the exosphere version. */ + u64 version; + if (R_FAILED(rc = splGetConfig(static_cast<SplConfigItem>(ExosphereApiVersionConfigItem), &version))) { + ChangeMenu(std::make_shared<ErrorMenu>("Atmosphere not found", "Daybreak requires Atmosphere to be installed.", rc)); + return false; + } + + const u32 version_micro = (version >> 40) & 0xff; + const u32 version_minor = (version >> 48) & 0xff; + const u32 version_major = (version >> 56) & 0xff; + + /* Validate the exosphere version. */ + const bool ams_supports_sysupdate_api = EncodeVersion(version_major, version_minor, version_micro) >= EncodeVersion(0, 14, 0); + if (!ams_supports_sysupdate_api) { + ChangeMenu(std::make_shared<ErrorMenu>("Outdated Atmosphere version", "Daybreak requires Atmosphere 0.14.0 or later.", rc)); + return false; + } + + /* Ensure DayBreak is ran as a NRO. */ + if (envIsNso()) { + ChangeMenu(std::make_shared<ErrorMenu>("Unsupported Environment", "Please launch Daybreak via the Homebrew menu.", rc)); + return false; + } + + /* Attempt to get the supported version. */ + if (R_SUCCEEDED(rc = splGetConfig(static_cast<SplConfigItem>(ExosphereSupportedHosVersion), &version))) { + g_supported_version = static_cast<u32>(version); + } + + /* Initialize ams:su. */ + if (R_FAILED(rc = amssuInitialize())) { + fatalThrow(rc); + } + + /* Change the current menu to the main menu. */ + g_current_menu = std::make_shared<MainMenu>(); + + return true; + } + + bool InitializeMenu(u32 screen_width, u32 screen_height, const char *update_path) { + if (InitializeMenu(screen_width, screen_height)) { + + /* Set the update path. */ + strncpy(g_update_path, update_path, sizeof(g_update_path)-1); + + /* Change the menu. */ + ChangeMenu(std::make_shared<ValidateUpdateMenu>(g_current_menu)); + + return true; + } + + return false; + } + + void UpdateMenu(u64 ns) { + DBK_ABORT_UNLESS(g_initialized); + DBK_ABORT_UNLESS(g_current_menu != nullptr); + UpdateInput(); + g_current_menu->Update(ns); + } + + void RenderMenu(NVGcontext *vg, u64 ns) { + DBK_ABORT_UNLESS(g_initialized); + DBK_ABORT_UNLESS(g_current_menu != nullptr); + + /* Draw background. */ + DrawBackground(vg, g_screen_width, g_screen_height); + + /* Draw stars. */ + DrawStar(vg, 40.0f, 64.0f, 3.0f); + DrawStar(vg, 110.0f, 300.0f, 3.0f); + DrawStar(vg, 200.0f, 150.0f, 4.0f); + DrawStar(vg, 370.0f, 280.0f, 3.0f); + DrawStar(vg, 450.0f, 40.0f, 3.5f); + DrawStar(vg, 710.0f, 90.0f, 3.0f); + DrawStar(vg, 900.0f, 240.0f, 3.0f); + DrawStar(vg, 970.0f, 64.0f, 4.0f); + DrawStar(vg, 1160.0f, 160.0f, 3.5f); + DrawStar(vg, 1210.0f, 350.0f, 3.0f); + + g_current_menu->Draw(vg, ns); + } + + bool IsExitRequested() { + return g_exit_requested; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/ui.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/ui.hpp new file mode 100644 index 00000000..7b0a30a0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/ui.hpp @@ -0,0 +1,272 @@ +/* + * Copyright (c) Adubbz + * + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <array> +#include <memory> +#include <vector> +#include <optional> +#include <nanovg.h> +#include <switch.h> +#include "ams_su.h" + +namespace dbk { + + struct Button { + static constexpr u32 InvalidButtonId = -1; + + u32 id; + bool selected; + bool enabled; + char text[256]; + float x; + float y; + float w; + float h; + + inline bool IsPositionInBounds(float x, float y) { + return x >= this->x && y >= this->y && x < (this->x + this->w) && y < (this->y + this->h); + } + }; + + enum class Direction { + Up, + Down, + Left, + Right, + Invalid, + }; + + class Menu { + protected: + static constexpr size_t MaxButtons = 32; + static constexpr size_t LogBufferSize = 0x1000; + protected: + std::array<std::optional<Button>, MaxButtons> m_buttons; + const std::shared_ptr<Menu> m_prev_menu; + char m_log_buffer[LogBufferSize]; + protected: + void AddButton(u32 id, const char *text, float x, float y, float w, float h); + void SetButtonSelected(u32 id, bool selected); + void DeselectAllButtons(); + void SetButtonEnabled(u32 id, bool enabled); + + Button *GetButton(u32 id); + Button *GetSelectedButton(); + Button *GetClosestButtonToSelection(Direction direction); + Button *GetTouchedButton(); + Button *GetActivatedButton(); + + void UpdateButtons(); + void DrawButtons(NVGcontext *vg, u64 ns); + + void LogText(const char *format, ...); + public: + Menu(std::shared_ptr<Menu> prev_menu) : m_buttons({}), m_prev_menu(prev_menu), m_log_buffer{} { /* ... */ } + + std::shared_ptr<Menu> GetPrevMenu(); + virtual void Update(u64 ns) = 0; + virtual void Draw(NVGcontext *vg, u64 ns) = 0; + }; + + class AlertMenu : public Menu { + protected: + static constexpr float WindowWidth = 600.0f; + static constexpr float WindowHeight = 214.0f; + static constexpr float TitleGap = 90.0f; + static constexpr float SubTextHeight = 24.0f; + protected: + char m_text[0x100]; + char m_subtext[0x100]; + char m_result_text[0x20]; + Result m_rc; + public: + AlertMenu(std::shared_ptr<Menu> prev_menu, const char *text, const char *subtext, Result rc = 0); + + virtual void Draw(NVGcontext *vg, u64 ns) override; + }; + + class ErrorMenu : public AlertMenu { + private: + static constexpr u32 ExitButtonId = 0; + public: + ErrorMenu(const char *text, const char *subtext, Result rc = 0); + + virtual void Update(u64 ns) override; + }; + + class WarningMenu : public AlertMenu { + private: + static constexpr u32 BackButtonId = 0; + static constexpr u32 ContinueButtonId = 1; + private: + const std::shared_ptr<Menu> m_next_menu; + public: + WarningMenu(std::shared_ptr<Menu> prev_menu, std::shared_ptr<Menu> next_menu, const char *text, const char *subtext, Result rc = 0); + + virtual void Update(u64 ns) override; + }; + + class MainMenu : public Menu { + private: + static constexpr u32 InstallButtonId = 0; + static constexpr u32 ExitButtonId = 1; + + static constexpr float WindowWidth = 400.0f; + static constexpr float WindowHeight = 240.0f; + static constexpr float TitleGap = 90.0f; + public: + MainMenu(); + + virtual void Update(u64 ns) override; + virtual void Draw(NVGcontext *vg, u64 ns) override; + }; + + class FileMenu : public Menu { + private: + struct FileEntry { + char name[FS_MAX_PATH]; + }; + private: + static constexpr size_t MaxFileRows = 11; + + static constexpr float WindowWidth = 1200.0f; + static constexpr float WindowHeight = 680.0f; + static constexpr float TitleGap = 90.0f; + static constexpr float TextBackgroundOffset = 20.0f; + static constexpr float FileRowHeight = 40.0f; + static constexpr float FileRowGap = 10.0f; + static constexpr float FileRowHorizontalInset = 10.0f; + static constexpr float FileListHeight = MaxFileRows * (FileRowHeight + FileRowGap); + private: + char m_root[FS_MAX_PATH]; + std::vector<FileEntry> m_file_entries; + u32 m_current_index; + float m_scroll_offset; + float m_touch_start_scroll_offset; + bool m_touch_finalize_selection; + + Result PopulateFileEntries(); + bool IsSelectionVisible(); + void ScrollToSelection(); + bool IsEntryTouched(u32 i); + void UpdateTouches(); + void FinalizeSelection(); + public: + FileMenu(std::shared_ptr<Menu> prev_menu, const char *root); + + virtual void Update(u64 ns) override; + virtual void Draw(NVGcontext *vg, u64 ns) override; + }; + + class ValidateUpdateMenu : public Menu { + private: + static constexpr u32 BackButtonId = 0; + static constexpr u32 ContinueButtonId = 1; + + static constexpr float WindowWidth = 600.0f; + static constexpr float WindowHeight = 600.0f; + static constexpr float TitleGap = 90.0f; + static constexpr float TextAreaHeight = 410.0f; + private: + AmsSuUpdateInformation m_update_info; + AmsSuUpdateValidationInfo m_validation_info; + bool m_has_drawn; + bool m_has_info; + bool m_has_validated; + + Result GetUpdateInformation(); + void ValidateUpdate(); + public: + ValidateUpdateMenu(std::shared_ptr<Menu> prev_menu); + + virtual void Update(u64 ns) override; + virtual void Draw(NVGcontext *vg, u64 ns) override; + }; + + class ChooseResetMenu : public Menu { + private: + static constexpr u32 ResetToFactorySettingsButtonId = 0; + static constexpr u32 PreserveSettingsButtonId = 1; + + static constexpr float WindowWidth = 600.0f; + static constexpr float WindowHeight = 170.0f; + static constexpr float TitleGap = 90.0f; + public: + ChooseResetMenu(std::shared_ptr<Menu> prev_menu); + + virtual void Update(u64 ns) override; + virtual void Draw(NVGcontext *vg, u64 ns) override; + }; + + class ChooseExfatMenu : public Menu { + private: + static constexpr u32 Fat32ButtonId = 0; + static constexpr u32 ExFatButtonId = 1; + + static constexpr float WindowWidth = 600.0f; + static constexpr float WindowHeight = 170.0f; + static constexpr float TitleGap = 90.0f; + public: + ChooseExfatMenu(std::shared_ptr<Menu> prev_menu); + + virtual void Update(u64 ns) override; + virtual void Draw(NVGcontext *vg, u64 ns) override; + }; + + class InstallUpdateMenu : public Menu { + private: + enum class InstallState { + NeedsDraw, + NeedsSetup, + NeedsPrepare, + AwaitingPrepare, + NeedsApply, + AwaitingReboot, + }; + private: + static constexpr u32 ShutdownButtonId = 0; + static constexpr u32 RebootButtonId = 1; + + static constexpr float WindowWidth = 600.0f; + static constexpr float WindowHeight = 600.0f; + static constexpr float TitleGap = 120.0f; + static constexpr float ProgressTextHeight = 20.0f; + static constexpr float ProgressBarHeight = 30.0f; + static constexpr float TextAreaHeight = 320.0f; + + static constexpr size_t UpdateTaskBufferSize = 0x100000; + private: + InstallState m_install_state; + AsyncResult m_prepare_result; + float m_progress_percent; + + void MarkForReboot(); + Result TransitionUpdateState(); + public: + InstallUpdateMenu(std::shared_ptr<Menu> prev_menu); + + virtual void Update(u64 ns) override; + virtual void Draw(NVGcontext *vg, u64 ns) override; + }; + + bool InitializeMenu(u32 screen_width, u32 screen_height); + bool InitializeMenu(u32 screen_width, u32 screen_height, const char *update_path); + void UpdateMenu(u64 ns); + void RenderMenu(NVGcontext *vg, u64 ns); + bool IsExitRequested(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/ui_util.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/ui_util.cpp new file mode 100644 index 00000000..224ab5c5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/ui_util.cpp @@ -0,0 +1,204 @@ +/* + * Copyright (c) Adubbz + * + * 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 <http://www.gnu.org/licenses/>. + */ +#include "ui_util.hpp" +#include <cstdio> +#include <math.h> +#include <cstring> + +namespace dbk { + + namespace { + + constexpr const char *SwitchStandardFont = "switch-standard"; + constexpr float WindowCornerRadius = 20.0f; + constexpr float TextAreaCornerRadius = 10.0f; + constexpr float ButtonCornerRaidus = 3.0f; + + NVGcolor GetSelectionRGB2(u64 ns) { + /* Calculate the rgb values for the breathing colour effect. */ + const double t = static_cast<double>(ns) / 1'000'000'000.0d; + const float d = -0.5 * cos(3.0f*t) + 0.5f; + const int r2 = 83 + (float)(128 - 83) * (d * 0.7f + 0.3f); + const int g2 = 71 + (float)(126 - 71) * (d * 0.7f + 0.3f); + const int b2 = 185 + (float)(230 - 185) * (d * 0.7f + 0.3f); + return nvgRGB(r2, g2, b2); + } + + } + + void DrawStar(NVGcontext *vg, float x, float y, float width) { + nvgBeginPath(vg); + nvgEllipse(vg, x, y, width, width * 3.0f); + nvgEllipse(vg, x, y, width * 3.0f, width); + nvgFillColor(vg, nvgRGB(65, 71, 115)); + nvgFill(vg); + } + + void DrawBackground(NVGcontext *vg, float w, float h) { + /* Draw the background gradient. */ + const NVGpaint bg_paint = nvgLinearGradient(vg, w / 2.0f, 0, w / 2.0f, h + 20.0f, nvgRGB(20, 24, 50), nvgRGB(46, 57, 127)); + nvgBeginPath(vg); + nvgRect(vg, 0, 0, w, h); + nvgFillPaint(vg, bg_paint); + nvgFill(vg); + } + + void DrawWindow(NVGcontext *vg, const char *title, float x, float y, float w, float h) { + /* Draw the window background. */ + const NVGpaint window_bg_paint = nvgLinearGradient(vg, x + w / 2.0f, y, x + w / 2.0f, y + h + h / 4.0f, nvgRGB(255, 255, 255), nvgRGB(188, 214, 234)); + nvgBeginPath(vg); + nvgRoundedRect(vg, x, y, w, h, WindowCornerRadius); + nvgFillPaint(vg, window_bg_paint); + nvgFill(vg); + + /* Draw the shadow surrounding the window. */ + NVGpaint shadowPaint = nvgBoxGradient(vg, x, y + 2, w, h, WindowCornerRadius * 2, 10, nvgRGBA(0, 0, 0, 128), nvgRGBA(0, 0, 0, 0)); + nvgBeginPath(vg); + nvgRect(vg, x - 10, y - 10, w + 20, h + 30); + nvgRoundedRect(vg, x, y, w, h, WindowCornerRadius); + nvgPathWinding(vg, NVG_HOLE); + nvgFillPaint(vg, shadowPaint); + nvgFill(vg); + + /* Setup the font. */ + nvgFontSize(vg, std::min(32.0f, -(strlen(title)*0.5f) + 47.0f)); + nvgFontFace(vg, SwitchStandardFont); + nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); + nvgFillColor(vg, nvgRGB(0, 0, 0)); + + /* Draw the title. */ + const float tw = nvgTextBounds(vg, 0, 0, title, nullptr, nullptr); + nvgText(vg, x + w * 0.5f - tw * 0.5f, y + 40.0f, title, nullptr); + } + + void DrawButton(NVGcontext *vg, const char *text, float x, float y, float w, float h, ButtonStyle style, u64 ns) { + /* Fill the background if selected. */ + if (style == ButtonStyle::StandardSelected || style == ButtonStyle::FileSelectSelected) { + NVGpaint bg_paint = nvgLinearGradient(vg, x, y + h / 2.0f, x + w, y + h / 2.0f, nvgRGB(83, 71, 185), GetSelectionRGB2(ns)); + nvgBeginPath(vg); + nvgRoundedRect(vg, x, y, w, h, ButtonCornerRaidus); + nvgFillPaint(vg, bg_paint); + nvgFill(vg); + } + + /* Draw the shadow surrounding the button. */ + if (style == ButtonStyle::Standard || style == ButtonStyle::StandardSelected || style == ButtonStyle::StandardDisabled || style == ButtonStyle::FileSelectSelected) { + const unsigned char shadow_color = style == ButtonStyle::Standard ? 128 : 64; + NVGpaint shadow_paint = nvgBoxGradient(vg, x, y, w, h, ButtonCornerRaidus, 5, nvgRGBA(0, 0, 0, shadow_color), nvgRGBA(0, 0, 0, 0)); + nvgBeginPath(vg); + nvgRect(vg, x - 10, y - 10, w + 20, h + 30); + nvgRoundedRect(vg, x, y, w, h, ButtonCornerRaidus); + nvgPathWinding(vg, NVG_HOLE); + nvgFillPaint(vg, shadow_paint); + nvgFill(vg); + } + + /* Setup the font. */ + nvgFontSize(vg, 20.0f); + nvgFontFace(vg, SwitchStandardFont); + nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); + + /* Set the text colour. */ + if (style == ButtonStyle::StandardSelected || style == ButtonStyle::FileSelectSelected) { + nvgFillColor(vg, nvgRGB(255, 255, 255)); + } else { + const unsigned char alpha = style == ButtonStyle::StandardDisabled ? 64 : 255; + nvgFillColor(vg, nvgRGBA(0, 0, 0, alpha)); + } + + /* Draw the button text. */ + const float tw = nvgTextBounds(vg, 0, 0, text, nullptr, nullptr); + + if (style == ButtonStyle::Standard || style == ButtonStyle::StandardSelected || style == ButtonStyle::StandardDisabled) { + nvgText(vg, x + w * 0.5f - tw * 0.5f, y + h * 0.5f, text, nullptr); + } else { + nvgText(vg, x + 10.0f, y + h * 0.5f, text, nullptr); + } + } + + void DrawTextBackground(NVGcontext *vg, float x, float y, float w, float h) { + nvgBeginPath(vg); + nvgRoundedRect(vg, x, y, w, h, TextAreaCornerRadius); + nvgFillColor(vg, nvgRGBA(0, 0, 0, 16)); + nvgFill(vg); + } + + void DrawText(NVGcontext *vg, float x, float y, float w, const char *text) { + nvgFontSize(vg, 20.0f); + nvgFontFace(vg, SwitchStandardFont); + nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); + nvgFillColor(vg, nvgRGB(0, 0, 0)); + + const float tw = nvgTextBounds(vg, 0, 0, text, nullptr, nullptr); + nvgText(vg, x + w * 0.5f - tw * 0.5f, y, text, nullptr); + } + + void DrawProgressText(NVGcontext *vg, float x, float y, float progress) { + char progress_text[32] = {}; + snprintf(progress_text, sizeof(progress_text)-1, "%d%% complete", static_cast<int>(progress * 100.0f)); + + nvgFontSize(vg, 24.0f); + nvgFontFace(vg, SwitchStandardFont); + nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); + nvgFillColor(vg, nvgRGB(0, 0, 0)); + nvgText(vg, x, y, progress_text, nullptr); + } + + void DrawProgressBar(NVGcontext *vg, float x, float y, float w, float h, float progress) { + /* Draw the progress bar background. */ + nvgBeginPath(vg); + nvgRoundedRect(vg, x, y, w, h, WindowCornerRadius); + nvgFillColor(vg, nvgRGBA(0, 0, 0, 128)); + nvgFill(vg); + + /* Draw the progress bar fill. */ + if (progress > 0.0f) { + NVGpaint progress_fill_paint = nvgLinearGradient(vg, x, y + 0.5f * h, x + w, y + 0.5f * h, nvgRGB(83, 71, 185), nvgRGB(128, 126, 230)); + nvgBeginPath(vg); + nvgRoundedRect(vg, x, y, WindowCornerRadius + (w - WindowCornerRadius) * progress, h, WindowCornerRadius); + nvgFillPaint(vg, progress_fill_paint); + nvgFill(vg); + } + } + + void DrawTextBlock(NVGcontext *vg, const char *text, float x, float y, float w, float h) { + /* Save state and scissor. */ + nvgSave(vg); + nvgScissor(vg, x, y, w, h); + + /* Configure the text. */ + nvgFontSize(vg, 18.0f); + nvgFontFace(vg, SwitchStandardFont); + nvgTextLineHeight(vg, 1.3f); + nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); + nvgFillColor(vg, nvgRGB(0, 0, 0)); + + /* Determine the bounds of the text box. */ + float bounds[4]; + nvgTextBoxBounds(vg, 0, 0, w, text, nullptr, bounds); + + /* Adjust the y to only show the last part of the text that fits. */ + float y_adjustment = 0.0f; + if (bounds[3] > h) { + y_adjustment = bounds[3] - h; + } + + /* Draw the text box and restore state. */ + nvgTextBox(vg, x, y - y_adjustment, w, text, nullptr); + nvgRestore(vg); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/ui_util.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/ui_util.hpp new file mode 100644 index 00000000..81a5e6e2 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/daybreak/source/ui_util.hpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) Adubbz + * + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <nanovg.h> +#include <switch.h> + +namespace dbk { + + enum class ButtonStyle { + Standard, + StandardSelected, + StandardDisabled, + FileSelect, + FileSelectSelected, + }; + + void DrawStar(NVGcontext *vg, float x, float y, float width); + void DrawBackground(NVGcontext *vg, float w, float h); + void DrawWindow(NVGcontext *vg, const char *title, float x, float y, float w, float h); + void DrawButton(NVGcontext *vg, const char *text, float x, float y, float w, float h, ButtonStyle style, u64 ns); + void DrawTextBackground(NVGcontext *vg, float x, float y, float w, float h); + void DrawText(NVGcontext *vg, float x, float y, float w, const char *text); + void DrawProgressText(NVGcontext *vg, float x, float y, float progress); + void DrawProgressBar(NVGcontext *vg, float x, float y, float w, float h, float progress); + void DrawTextBlock(NVGcontext *vg, const char *text, float x, float y, float w, float h); + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/Makefile b/Source/Atmosphere-MTC-Unlock/troposphere/haze/Makefile new file mode 100644 index 00000000..e1e29949 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/Makefile @@ -0,0 +1,272 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) +# +# NO_ICON: if set to anything, do not use icon. +# NO_NACP: if set to anything, no .nacp file is generated. +# APP_TITLE is the name of the app stored in the .nacp file (Optional) +# APP_AUTHOR is the author of the app stored in the .nacp file (Optional) +# APP_VERSION is the version of the app stored in the .nacp file (Optional) +# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) +# ICON is the filename of the icon (.jpg), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - <Project name>.jpg +# - icon.jpg +# - <libnx folder>/default_icon.jpg +# +# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - <Project name>.json +# - config.json +# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead +# of a homebrew executable (.nro). This is intended to be used for sysmodules. +# NACP building is skipped as well. +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source +DATA := data +INCLUDES := include ../../libraries/libvapours/include +ROMFS := romfs + +# Output folders for autogenerated files in romfs +OUT_SHADERS := shaders + +APP_TITLE := USB File Transfer +APP_AUTHOR := Atmosphere-NX +APP_VERSION := 1.0.0 + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE + +CFLAGS := -g -Wall -O2 -ffunction-sections \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) -D__SWITCH__ + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++23 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -ldeko3d -lnx -lm + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +GLSLFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.glsl))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +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 + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.jpg) + ifneq (,$(findstring $(TARGET).jpg,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).jpg + else + ifneq (,$(findstring icon.jpg,$(icons))) + export APP_ICON := $(TOPDIR)/icon.jpg + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_ICON)),) + export NROFLAGS += --icon=$(APP_ICON) +endif + +ifeq ($(strip $(NO_NACP)),) + export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp +endif + +ifneq ($(APP_TITLEID),) + export NACPFLAGS += --titleid=$(APP_TITLEID) +endif + +ifneq ($(strip $(ROMFS)),) + ROMFS_TARGETS := + ROMFS_FOLDERS := + ifneq ($(strip $(OUT_SHADERS)),) + ROMFS_SHADERS := $(ROMFS)/$(OUT_SHADERS) + ROMFS_TARGETS += $(patsubst %.glsl, $(ROMFS_SHADERS)/%.dksh, $(GLSLFILES)) + ROMFS_FOLDERS += $(ROMFS_SHADERS) + endif + + export ROMFS_DEPS := $(foreach file,$(ROMFS_TARGETS),$(CURDIR)/$(file)) + export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) +endif + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(ROMFS_TARGETS) | $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +ifneq ($(strip $(ROMFS_TARGETS)),) + +$(ROMFS_TARGETS): | $(ROMFS_FOLDERS) + +$(ROMFS_FOLDERS): + @mkdir -p $@ + +$(ROMFS_SHADERS)/%_vsh.dksh: %_vsh.glsl + @echo {vert} $(notdir $<) + @uam -s vert -o $@ $< + +$(ROMFS_SHADERS)/%_tcsh.dksh: %_tcsh.glsl + @echo {tess_ctrl} $(notdir $<) + @uam -s tess_ctrl -o $@ $< + +$(ROMFS_SHADERS)/%_tesh.dksh: %_tesh.glsl + @echo {tess_eval} $(notdir $<) + @uam -s tess_eval -o $@ $< + +$(ROMFS_SHADERS)/%_gsh.dksh: %_gsh.glsl + @echo {geom} $(notdir $<) + @uam -s geom -o $@ $< + +$(ROMFS_SHADERS)/%_fsh.dksh: %_fsh.glsl + @echo {frag} $(notdir $<) + @uam -s frag -o $@ $< + +$(ROMFS_SHADERS)/%.dksh: %.glsl + @echo {comp} $(notdir $<) + @uam -s comp -o $@ $< + +endif + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... +ifeq ($(strip $(APP_JSON)),) + @rm -fr $(BUILD) $(TARGET).nro $(TARGET).nacp $(TARGET).elf +else + @rm -fr $(BUILD) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf +endif + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +ifeq ($(strip $(APP_JSON)),) + +all : $(OUTPUT).nro + +ifeq ($(strip $(NO_NACP)),) +$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp $(ROMFS_DEPS) +else +$(OUTPUT).nro : $(OUTPUT).elf $(ROMFS_DEPS) +endif + +else + +all : $(OUTPUT).nsp + +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm + +$(OUTPUT).nso : $(OUTPUT).elf + +endif + +$(OUTPUT).elf : $(OFILES) + +$(OFILES_SRC) : $(HFILES_BIN) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/icon.jpg b/Source/Atmosphere-MTC-Unlock/troposphere/haze/icon.jpg new file mode 100644 index 00000000..6c64f740 Binary files /dev/null and b/Source/Atmosphere-MTC-Unlock/troposphere/haze/icon.jpg differ diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/icon.svg b/Source/Atmosphere-MTC-Unlock/troposphere/haze/icon.svg new file mode 100644 index 00000000..e1478a7e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/icon.svg @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg id="svg1500" width="182" height="182" version="1.1" viewBox="0 0 182 182" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs id="defs1484"><linearGradient id="a" x1="36.532" x2="36.532" y1="39.898" y2="80.671" gradientTransform="scale(1.0484 .95387)" gradientUnits="userSpaceOnUse"><stop id="stop1474" stop-color="#fff" offset="0"/><stop id="stop1476" stop-color="#9FC1DB" offset="1"/></linearGradient><linearGradient id="linearGradient2139" x1="451.42" x2="426.88" y1="93.955" y2="145.12" gradientTransform="matrix(1.4611 0 0 1.4611 -557.72 -38.455)" gradientUnits="userSpaceOnUse"><stop id="stop1479" stop-color="#90B9D9" offset="0"/><stop id="stop1481" stop-color="#554BBA" offset="1"/></linearGradient><linearGradient id="linearGradient2147" x1="436.58" x2="451.74" y1="96.001" y2="108.43" gradientTransform="matrix(1.4611 0 0 1.4611 -557.72 -34.137)" gradientUnits="userSpaceOnUse" xlink:href="#a"/><linearGradient id="linearGradient5197" x1="438.54" x2="455.23" y1="117.18" y2="124.38" gradientTransform="matrix(1.5947 0 0 1.5947 -617.05 -51.062)" gradientUnits="userSpaceOnUse" xlink:href="#a"/></defs><rect id="rect6884" width="182" height="182" ry="0" fill="#37394c" stop-color="#000000"/><g id="g1716" transform="matrix(1.8352 0 0 1.8352 -75.998 -148.92)" stroke-width=".54491"><rect id="rect1998" x="80.151" y="91.556" width="21.697" height="21.879" ry="1.4596" fill="url(#linearGradient2147)" stop-color="#000000"/><path id="rect1828" d="m107.41 151.77v-41.952c0-1.4858-1.1967-2.6824-2.6825-2.6824h-27.447c-1.4858 0-2.6825 1.1967-2.6825 2.6824v41.952c0 13.67 13.523 9.3797 13.523 23.423v10.594h5.7652v-10.594c0-14.043 13.523-9.7531 13.523-23.423" fill="url(#linearGradient2139)" stop-color="#000000"/><path id="path1334" d="m91 116.2-2.5153 4.3556h1.7943v22.263l-4.5798-4.3348c-0.2957-0.3689-0.50311-0.85159-0.51463-1.3481 0-2.0086-5.2e-4 -3.2015-8.82e-4 -3.6405 0.84792-0.29761 1.46-1.0971 1.46-2.0475 0-1.2023-0.97564-2.178-2.1784-2.178-1.2033 0-2.1786 0.97563-2.1786 2.178 0 0.9504 0.61169 1.7499 1.4589 2.0475l-6.01e-4 3.5979c0 0.97511 0.53498 1.9969 1.1622 2.6472-0.01854-0.0177-0.0384-0.0363 3.53e-4 8.9e-4 0.01589 0.0143 4.8585 4.5991 4.8585 4.5991 0.29527 0.36811 0.50139 0.85054 0.51324 1.3467v2.5185c-1.6637 0.33382-2.9172 1.803-2.9172 3.5654 0 2.0093 1.6288 3.6381 3.6375 3.6381 2.0093 0 3.6382-1.6288 3.6382-3.6381 0-1.7627-1.2545-3.2319-2.9197-3.5657v-2.4743c0-6e-3 3.53e-4 -0.0121 0-0.0191v-5.4728c0.01209-0.49523 0.21903-0.97704 0.51464-1.3448 0 0 4.8427-4.5843 4.8583-4.5985 0.03901-0.0367 0.01854-0.0184 3.54e-4 -3.5e-4 0.62708-0.65035 1.1617-1.6726 1.1617-2.6478l-7.86e-4 -3.4673h1.4604v-4.3568h-4.3563v4.3568h1.4585s-8.83e-4 0.91324-8.83e-4 3.5098c-0.01095 0.49662-0.21857 0.9798-0.51427 1.3486l-4.5807 4.3358v-16.818h1.7972z" fill="url(#linearGradient5197)" stroke-width=".54491"/></g></svg> diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze.hpp new file mode 100644 index 00000000..be2dca8c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze.hpp @@ -0,0 +1,27 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <haze/async_usb_server.hpp> +#include <haze/common.hpp> +#include <haze/device_properties.hpp> +#include <haze/event_reactor.hpp> +#include <haze/file_system_proxy.hpp> +#include <haze/ptp.hpp> +#include <haze/ptp_object_database.hpp> +#include <haze/ptp_object_heap.hpp> +#include <haze/ptp_responder.hpp> +#include <haze/usb_session.hpp> diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/assert.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/assert.hpp new file mode 100644 index 00000000..d7178276 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/assert.hpp @@ -0,0 +1,32 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#define HAZE_ASSERT(expr) \ +{ \ + const bool __tmp_haze_assert_val = static_cast<bool>(expr); \ + if (AMS_UNLIKELY(!__tmp_haze_assert_val)) { \ + svcBreak(BreakReason_Assert, 0, 0); \ + } \ +} + +#define HAZE_R_ABORT_UNLESS(res_expr) \ +{ \ + const auto _tmp_r_abort_rc = (res_expr); \ + HAZE_ASSERT(R_SUCCEEDED(_tmp_r_abort_rc)); \ +} + +#define HAZE_UNREACHABLE_DEFAULT_CASE() default: HAZE_ASSERT(false) diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/async_usb_server.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/async_usb_server.hpp new file mode 100644 index 00000000..c99d299b --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/async_usb_server.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <haze/common.hpp> +#include <haze/event_reactor.hpp> + +namespace haze { + + class AsyncUsbServer final { + private: + EventReactor *m_reactor; + public: + constexpr explicit AsyncUsbServer() : m_reactor() { /* ... */ } + + Result Initialize(const UsbCommsInterfaceInfo *interface_info, u16 id_vendor, u16 id_product, EventReactor *reactor); + void Finalize(); + private: + Result TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred) const; + public: + Result ReadPacket(void *page, u32 size, u32 *out_size_transferred) const { + R_RETURN(this->TransferPacketImpl(true, page, size, out_size_transferred)); + } + + Result WritePacket(void *page, u32 size) const { + u32 size_transferred; + R_RETURN(this->TransferPacketImpl(false, page, size, std::addressof(size_transferred))); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/common.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/common.hpp new file mode 100644 index 00000000..e15ec99c --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/common.hpp @@ -0,0 +1,47 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#define ATMOSPHERE_IS_TROPOSPHERE +#define ATMOSPHERE_OS_HORIZON +#define ATMOSPHERE_BOARD_NINTENDO_NX +#define ATMOSPHERE_ARCH_ARM64 +#define ATMOSPHERE_ARCH_ARM_V8A + +#include <algorithm> +#include <cstring> +#include <bit> +#include <memory> +#include <stdint.h> +#include <stdio.h> +#include <stddef.h> + +#include <switch.h> +#include <haze/results.hpp> +#include <haze/assert.hpp> + +#include <vapours/literals.hpp> +#include <vapours/svc/svc_common.hpp> +#include <vapours/svc/svc_types_common.hpp> + +namespace haze { + + using namespace ::ams::literals; + using namespace ::ams; + + using Result = ::ams::Result; + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/console_main_loop.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/console_main_loop.hpp new file mode 100644 index 00000000..184c36fc --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/console_main_loop.hpp @@ -0,0 +1,249 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <haze/event_reactor.hpp> +#include <haze/ptp_object_heap.hpp> + +namespace haze { + + class ConsoleMainLoop : public EventConsumer { + private: + static constexpr size_t FrameDelayNs = 33'333'333; + private: + EventReactor *m_reactor; + PtpObjectHeap *m_object_heap; + + PadState m_pad; + + Thread m_thread; + UEvent m_event; + UEvent m_cancel_event; + + u32 m_last_heap_used; + u32 m_last_heap_total; + bool m_is_applet_mode; + private: + static void Run(void *arg) { + static_cast<ConsoleMainLoop *>(arg)->Run(); + } + + void Run() { + int idx; + + while (true) { + /* Wait for up to 1 frame delay time to be cancelled. */ + Waiter cancel_waiter = waiterForUEvent(std::addressof(m_cancel_event)); + Result rc = waitObjects(std::addressof(idx), std::addressof(cancel_waiter), 1, FrameDelayNs); + + /* Finish if we were cancelled. */ + if (R_SUCCEEDED(rc)) { + break; + } + + /* Otherwise, signal the console update event. */ + if (svc::ResultTimedOut::Includes(rc)) { + ueventSignal(std::addressof(m_event)); + } + } + } + public: + explicit ConsoleMainLoop() : m_reactor(), m_pad(), m_thread(), m_event(), m_cancel_event(), m_last_heap_used(), m_last_heap_total(), m_is_applet_mode() { /* ... */ } + + Result Initialize(EventReactor *reactor, PtpObjectHeap *object_heap) { + /* Register event reactor and heap. */ + m_reactor = reactor; + m_object_heap = object_heap; + + /* Set cached use amounts to invalid values. */ + m_last_heap_used = 0xffffffffu; + m_last_heap_total = 0xffffffffu; + + /* Get whether we are launched in applet mode. */ + AppletType applet_type = appletGetAppletType(); + m_is_applet_mode = applet_type != AppletType_Application && applet_type != AppletType_SystemApplication; + + /* Initialize events. */ + ueventCreate(std::addressof(m_event), true); + ueventCreate(std::addressof(m_cancel_event), true); + + /* Set up pad inputs to allow exiting the program. */ + padConfigureInput(1, HidNpadStyleSet_NpadStandard); + padInitializeAny(std::addressof(m_pad)); + + /* Create the delay thread with higher priority than the main thread (which runs at priority 0x2c). */ + R_TRY(threadCreate(std::addressof(m_thread), ConsoleMainLoop::Run, this, nullptr, 4_KB, 0x2b, svc::IdealCoreUseProcessValue)); + + /* Ensure we close the thread on failure. */ + ON_RESULT_FAILURE { threadClose(std::addressof(m_thread)); }; + + /* Connect ourselves to the event loop. */ + R_UNLESS(m_reactor->AddConsumer(this, waiterForUEvent(std::addressof(m_event))), haze::ResultRegistrationFailed()); + + /* Start the delay thread. */ + R_RETURN(threadStart(std::addressof(m_thread))); + } + + void Finalize() { + /* Signal the delay thread to shut down. */ + ueventSignal(std::addressof(m_cancel_event)); + + /* Wait for the delay thread to exit and close it. */ + HAZE_R_ABORT_UNLESS(threadWaitForExit(std::addressof(m_thread))); + + HAZE_R_ABORT_UNLESS(threadClose(std::addressof(m_thread))); + + /* Disconnect from the event loop.*/ + m_reactor->RemoveConsumer(this); + } + private: + void RedrawConsole() { + /* Get use amounts from the heap. */ + u32 heap_used = m_object_heap->GetUsedSize(); + u32 heap_total = m_object_heap->GetTotalSize(); + u32 heap_pct = heap_total > 0 ? static_cast<u32>((heap_used * 100ul) / heap_total) : 0; + + if (heap_used == m_last_heap_used && heap_total == m_last_heap_total) { + /* If usage didn't change, skip redrawing the console. */ + /* This provides a substantial performance improvement in file transfer speed. */ + return; + } + + /* Update cached use amounts. */ + m_last_heap_used = heap_used; + m_last_heap_total = heap_total; + + /* Determine units to use for printing to the console. */ + const char *used_unit = "B"; + if (heap_used >= 1_KB) { heap_used >>= 10; used_unit = "KiB"; } + if (heap_used >= (1_MB / 1_KB)) { heap_used >>= 10; used_unit = "MiB"; } + + const char *total_unit = "B"; + if (heap_total >= 1_KB) { heap_total >>= 10; total_unit = "KiB"; } + if (heap_total >= (1_MB / 1_KB)) { heap_total >>= 10; total_unit = "MiB"; } + + /* Draw the console UI. */ + consoleClear(); + printf("USB File Transfer\n\n"); + printf("Connect console to computer. Press [+] to exit.\n"); + printf("Heap used: %u %s / %u %s (%u%%)\n", heap_used, used_unit, heap_total, total_unit, heap_pct); + + if (m_is_applet_mode) { + /* Print "Applet Mode" in red text. */ + printf("\n" CONSOLE_ESC(31;1m) "Applet Mode" CONSOLE_ESC(0m) "\n"); + } + + consoleUpdate(nullptr); + } + protected: + void ProcessEvent() override { + /* Update the console. */ + this->RedrawConsole(); + + /* Check buttons. */ + padUpdate(std::addressof(m_pad)); + + /* If the plus button is held, request immediate exit. */ + if (padGetButtonsDown(std::addressof(m_pad)) & HidNpadButton_Plus) { + m_reactor->SetResult(haze::ResultStopRequested()); + } + + /* Pump applet events, and check if exit was requested. */ + if (!appletMainLoop()) { + m_reactor->SetResult(haze::ResultStopRequested()); + } + + /* Check if focus was lost. */ + if (appletGetFocusState() == AppletFocusState_Background) { + m_reactor->SetResult(haze::ResultFocusLost()); + } + } + private: + static bool SuspendAndWaitForFocus() { + /* Enable suspension with resume notification. */ + appletSetFocusHandlingMode(AppletFocusHandlingMode_SuspendHomeSleepNotify); + + /* Pump applet events. */ + while (appletMainLoop()) { + /* Check if focus was regained. */ + if (appletGetFocusState() != AppletFocusState_Background) { + return true; + } + } + + /* Exit was requested. */ + return false; + } + public: + static void RunApplication() { + /* Declare the object heap, to hold the database for an active session. */ + PtpObjectHeap ptp_object_heap; + + /* Declare the event reactor, and components which use it. */ + EventReactor event_reactor; + PtpResponder ptp_responder; + ConsoleMainLoop console_main_loop; + + /* Initialize the console.*/ + consoleInit(nullptr); + + while (true) { + /* Disable suspension. */ + appletSetFocusHandlingMode(AppletFocusHandlingMode_NoSuspend); + + /* Declare result from serving to use. */ + Result rc; + { + /* Ensure we don't go to sleep while transferring files. */ + appletSetAutoSleepDisabled(true); + + /* Clear the event reactor. */ + event_reactor.SetResult(ResultSuccess()); + + /* Configure the PTP responder and console main loop. */ + ptp_responder.Initialize(std::addressof(event_reactor), std::addressof(ptp_object_heap)); + console_main_loop.Initialize(std::addressof(event_reactor), std::addressof(ptp_object_heap)); + + /* Ensure we maintain a clean state on exit. */ + ON_SCOPE_EXIT { + /* Finalize the console main loop and PTP responder. */ + console_main_loop.Finalize(); + ptp_responder.Finalize(); + + /* Restore auto sleep setting. */ + appletSetAutoSleepDisabled(false); + }; + + /* Begin processing requests. */ + rc = ptp_responder.LoopProcess(); + } + + /* If focus was lost, try to pump the applet main loop until we receive focus again. */ + if (haze::ResultFocusLost::Includes(rc) && SuspendAndWaitForFocus()) { + continue; + } + + /* Otherwise, enable suspension and finish. */ + appletSetFocusHandlingMode(AppletFocusHandlingMode_SuspendHomeSleep); + break; + } + + /* Finalize the console. */ + consoleExit(nullptr); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/device_properties.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/device_properties.hpp new file mode 100644 index 00000000..fa66ffe0 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/device_properties.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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +namespace haze { + + Result LoadDeviceProperties(); + + const char *GetSerialNumber(); + + const char *GetFirmwareVersion(); + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/event_reactor.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/event_reactor.hpp new file mode 100644 index 00000000..563f9cb8 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/event_reactor.hpp @@ -0,0 +1,52 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <haze/common.hpp> + +namespace haze { + + class EventConsumer { + public: + virtual ~EventConsumer() = default; + virtual void ProcessEvent() = 0; + }; + + class EventReactor { + private: + EventConsumer *m_consumers[svc::ArgumentHandleCountMax]; + Waiter m_waiters[svc::ArgumentHandleCountMax]; + s32 m_num_wait_objects; + Result m_result; + public: + constexpr explicit EventReactor() : m_consumers(), m_waiters(), m_num_wait_objects(), m_result(ResultSuccess()) { /* ... */ } + + bool AddConsumer(EventConsumer *consumer, Waiter waiter); + void RemoveConsumer(EventConsumer *consumer); + public: + void SetResult(Result r) { m_result = r; } + Result GetResult() const { return m_result; } + public: + template <typename... Args> requires (sizeof...(Args) > 0) + Result WaitFor(s32 *out_arg_waiter, Args &&... arg_waiters) { + const Waiter arg_waiter_array[] = { arg_waiters... }; + return this->WaitForImpl(out_arg_waiter, arg_waiter_array, sizeof...(Args)); + } + private: + Result WaitForImpl(s32 *out_arg_waiter, const Waiter *arg_waiters, s32 num_arg_waiters); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/file_system_proxy.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/file_system_proxy.hpp new file mode 100644 index 00000000..516ba111 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/file_system_proxy.hpp @@ -0,0 +1,131 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <haze/common.hpp> +#include <haze/event_reactor.hpp> + +namespace haze { + + class FileSystemProxy final { + private: + EventReactor *m_reactor; + FsFileSystem *m_filesystem; + public: + constexpr explicit FileSystemProxy() : m_reactor(), m_filesystem() { /* ... */ } + + void Initialize(EventReactor *reactor, FsFileSystem *fs) { + HAZE_ASSERT(fs != nullptr); + + m_reactor = reactor; + m_filesystem = fs; + } + + void Finalize() { + m_reactor = nullptr; + m_filesystem = nullptr; + } + private: + template <typename F, typename... Args> + Result ForwardResult(F func, Args &&... args) { + /* Perform the method call, collecting its result. */ + const Result rc = func(std::forward<Args>(args)...); + + /* If the event loop was stopped, return that here. */ + R_TRY(m_reactor->GetResult()); + + /* Otherwise, return the call result. */ + R_RETURN(rc); + } + public: + Result GetTotalSpace(const char *path, s64 *out) { + R_RETURN(this->ForwardResult(fsFsGetTotalSpace, m_filesystem, path, out)); + } + + Result GetFreeSpace(const char *path, s64 *out) { + R_RETURN(this->ForwardResult(fsFsGetFreeSpace, m_filesystem, path, out)); + } + + Result GetEntryType(const char *path, FsDirEntryType *out_entry_type) { + R_RETURN(this->ForwardResult(fsFsGetEntryType, m_filesystem, path, out_entry_type)); + } + + Result CreateFile(const char* path, s64 size, u32 option) { + R_RETURN(this->ForwardResult(fsFsCreateFile, m_filesystem, path, size, option)); + } + + Result DeleteFile(const char* path) { + R_RETURN(this->ForwardResult(fsFsDeleteFile, m_filesystem, path)); + } + + Result RenameFile(const char *old_path, const char *new_path) { + R_RETURN(this->ForwardResult(fsFsRenameFile, m_filesystem, old_path, new_path)); + } + + Result OpenFile(const char *path, u32 mode, FsFile *out_file) { + R_RETURN(this->ForwardResult(fsFsOpenFile, m_filesystem, path, mode, out_file)); + } + + Result GetFileSize(FsFile *file, s64 *out_size) { + R_RETURN(this->ForwardResult(fsFileGetSize, file, out_size)); + } + + Result SetFileSize(FsFile *file, s64 size) { + R_RETURN(this->ForwardResult(fsFileSetSize, file, size)); + } + + Result ReadFile(FsFile *file, s64 off, void *buf, u64 read_size, u32 option, u64 *out_bytes_read) { + R_RETURN(this->ForwardResult(fsFileRead, file, off, buf, read_size, option, out_bytes_read)); + } + + Result WriteFile(FsFile *file, s64 off, const void *buf, u64 write_size, u32 option) { + R_RETURN(this->ForwardResult(fsFileWrite, file, off, buf, write_size, option)); + } + + void CloseFile(FsFile *file) { + fsFileClose(file); + } + + Result CreateDirectory(const char* path) { + R_RETURN(this->ForwardResult(fsFsCreateDirectory, m_filesystem, path)); + } + + Result DeleteDirectoryRecursively(const char* path) { + R_RETURN(this->ForwardResult(fsFsDeleteDirectoryRecursively, m_filesystem, path)); + } + + Result RenameDirectory(const char *old_path, const char *new_path) { + R_RETURN(this->ForwardResult(fsFsRenameDirectory, m_filesystem, old_path, new_path)); + } + + Result OpenDirectory(const char *path, u32 mode, FsDir *out_dir) { + R_RETURN(this->ForwardResult(fsFsOpenDirectory, m_filesystem, path, mode, out_dir)); + } + + Result ReadDirectory(FsDir *d, s64 *out_total_entries, size_t max_entries, FsDirectoryEntry *buf) { + R_RETURN(this->ForwardResult(fsDirRead, d, out_total_entries, max_entries, buf)); + } + + Result GetDirectoryEntryCount(FsDir *d, s64 *out_count) { + R_RETURN(this->ForwardResult(fsDirGetEntryCount, d, out_count)); + } + + void CloseDirectory(FsDir *d) { + fsDirClose(d); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp.hpp new file mode 100644 index 00000000..beca1684 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp.hpp @@ -0,0 +1,493 @@ +/* + * Copyright (c) Atmosphère-NX + * Copyright (c) libmtp project + * + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <haze/common.hpp> + +namespace haze { + + constexpr inline u32 PtpUsbBulkHighSpeedMaxPacketLength = 0x200; + constexpr inline u32 PtpUsbBulkSuperSpeedMaxPacketLength = 0x400; + constexpr inline u32 PtpUsbBulkHeaderLength = 2 * sizeof(u32) + 2 * sizeof(u16); + constexpr inline u32 PtpStringMaxLength = 255; + + enum PtpUsbBulkContainerType : u16 { + PtpUsbBulkContainerType_Undefined = 0x0000, + PtpUsbBulkContainerType_Command = 0x0001, + PtpUsbBulkContainerType_Data = 0x0002, + PtpUsbBulkContainerType_Response = 0x0003, + PtpUsbBulkContainerType_Event = 0x0004, + }; + + enum PtpOperationCode : u16 { + PtpOperationCode_Undefined = 0x1000, + PtpOperationCode_GetDeviceInfo = 0x1001, + PtpOperationCode_OpenSession = 0x1002, + PtpOperationCode_CloseSession = 0x1003, + PtpOperationCode_GetStorageIds = 0x1004, + PtpOperationCode_GetStorageInfo = 0x1005, + PtpOperationCode_GetNumObjects = 0x1006, + PtpOperationCode_GetObjectHandles = 0x1007, + PtpOperationCode_GetObjectInfo = 0x1008, + PtpOperationCode_GetObject = 0x1009, + PtpOperationCode_GetThumb = 0x100a, + PtpOperationCode_DeleteObject = 0x100b, + PtpOperationCode_SendObjectInfo = 0x100c, + PtpOperationCode_SendObject = 0x100d, + PtpOperationCode_InitiateCapture = 0x100e, + PtpOperationCode_FormatStore = 0x100f, + PtpOperationCode_ResetDevice = 0x1010, + PtpOperationCode_SelfTest = 0x1011, + PtpOperationCode_SetObjectProtection = 0x1012, + PtpOperationCode_PowerDown = 0x1013, + PtpOperationCode_GetDevicePropDesc = 0x1014, + PtpOperationCode_GetDevicePropValue = 0x1015, + PtpOperationCode_SetDevicePropValue = 0x1016, + PtpOperationCode_ResetDevicePropValue = 0x1017, + PtpOperationCode_TerminateOpenCapture = 0x1018, + PtpOperationCode_MoveObject = 0x1019, + PtpOperationCode_CopyObject = 0x101a, + PtpOperationCode_GetPartialObject = 0x101b, + PtpOperationCode_InitiateOpenCapture = 0x101c, + PtpOperationCode_StartEnumHandles = 0x101d, + PtpOperationCode_EnumHandles = 0x101e, + PtpOperationCode_StopEnumHandles = 0x101f, + PtpOperationCode_GetVendorExtensionMaps = 0x1020, + PtpOperationCode_GetVendorDeviceInfo = 0x1021, + PtpOperationCode_GetResizedImageObject = 0x1022, + PtpOperationCode_GetFilesystemManifest = 0x1023, + PtpOperationCode_GetStreamInfo = 0x1024, + PtpOperationCode_GetStream = 0x1025, + PtpOperationCode_AndroidGetPartialObject64 = 0x95c1, + PtpOperationCode_AndroidSendPartialObject = 0x95c2, + PtpOperationCode_AndroidTruncateObject = 0x95c3, + PtpOperationCode_AndroidBeginEditObject = 0x95c4, + PtpOperationCode_AndroidEndEditObject = 0x95c5, + PtpOperationCode_MtpGetObjectPropsSupported = 0x9801, + PtpOperationCode_MtpGetObjectPropDesc = 0x9802, + PtpOperationCode_MtpGetObjectPropValue = 0x9803, + PtpOperationCode_MtpSetObjectPropValue = 0x9804, + PtpOperationCode_MtpGetObjPropList = 0x9805, + PtpOperationCode_MtpSetObjPropList = 0x9806, + PtpOperationCode_MtpGetInterdependendPropdesc = 0x9807, + PtpOperationCode_MtpSendObjectPropList = 0x9808, + PtpOperationCode_MtpGetObjectReferences = 0x9810, + PtpOperationCode_MtpSetObjectReferences = 0x9811, + PtpOperationCode_MtpUpdateDeviceFirmware = 0x9812, + PtpOperationCode_MtpSkip = 0x9820, + }; + + enum PtpResponseCode : u16 { + PtpResponseCode_Undefined = 0x2000, + PtpResponseCode_Ok = 0x2001, + PtpResponseCode_GeneralError = 0x2002, + PtpResponseCode_SessionNotOpen = 0x2003, + PtpResponseCode_InvalidTransactionId = 0x2004, + PtpResponseCode_OperationNotSupported = 0x2005, + PtpResponseCode_ParameterNotSupported = 0x2006, + PtpResponseCode_IncompleteTransfer = 0x2007, + PtpResponseCode_InvalidStorageId = 0x2008, + PtpResponseCode_InvalidObjectHandle = 0x2009, + PtpResponseCode_DevicePropNotSupported = 0x200a, + PtpResponseCode_InvalidObjectFormatCode = 0x200b, + PtpResponseCode_StoreFull = 0x200c, + PtpResponseCode_ObjectWriteProtected = 0x200d, + PtpResponseCode_StoreReadOnly = 0x200e, + PtpResponseCode_AccessDenied = 0x200f, + PtpResponseCode_NoThumbnailPresent = 0x2010, + PtpResponseCode_SelfTestFailed = 0x2011, + PtpResponseCode_PartialDeletion = 0x2012, + PtpResponseCode_StoreNotAvailable = 0x2013, + PtpResponseCode_SpecificationByFormatUnsupported = 0x2014, + PtpResponseCode_NoValidObjectInfo = 0x2015, + PtpResponseCode_InvalidCodeFormat = 0x2016, + PtpResponseCode_UnknownVendorCode = 0x2017, + PtpResponseCode_CaptureAlreadyTerminated = 0x2018, + PtpResponseCode_DeviceBusy = 0x2019, + PtpResponseCode_InvalidParentObject = 0x201a, + PtpResponseCode_InvalidDevicePropFormat = 0x201b, + PtpResponseCode_InvalidDevicePropValue = 0x201c, + PtpResponseCode_InvalidParameter = 0x201d, + PtpResponseCode_SessionAlreadyOpened = 0x201e, + PtpResponseCode_TransactionCanceled = 0x201f, + PtpResponseCode_SpecificationOfDestinationUnsupported = 0x2020, + PtpResponseCode_InvalidEnumHandle = 0x2021, + PtpResponseCode_NoStreamEnabled = 0x2022, + PtpResponseCode_InvalidDataSet = 0x2023, + PtpResponseCode_MtpUndefined = 0xa800, + PtpResponseCode_MtpInvalidObjectPropCode = 0xa801, + PtpResponseCode_MtpInvalidObjectPropFormat = 0xa802, + PtpResponseCode_MtpInvalidObjectPropValue = 0xa803, + PtpResponseCode_MtpInvalidObjectReference = 0xa804, + PtpResponseCode_MtpInvalidDataset = 0xa806, + PtpResponseCode_MtpSpecificationByGroupUnsupported = 0xa807, + PtpResponseCode_MtpSpecificationByDepthUnsupported = 0xa808, + PtpResponseCode_MtpObjectTooLarge = 0xa809, + PtpResponseCode_MtpObjectPropNotSupported = 0xa80a, + }; + + enum PtpEventCode : u16 { + PtpEventCode_Undefined = 0x4000, + PtpEventCode_CancelTransaction = 0x4001, + PtpEventCode_ObjectAdded = 0x4002, + PtpEventCode_ObjectRemoved = 0x4003, + PtpEventCode_StoreAdded = 0x4004, + PtpEventCode_StoreRemoved = 0x4005, + PtpEventCode_DevicePropChanged = 0x4006, + PtpEventCode_ObjectInfoChanged = 0x4007, + PtpEventCode_DeviceInfoChanged = 0x4008, + PtpEventCode_RequestObjectTransfer = 0x4009, + PtpEventCode_StoreFull = 0x400a, + PtpEventCode_DeviceReset = 0x400b, + PtpEventCode_StorageInfoChanged = 0x400c, + PtpEventCode_CaptureComplete = 0x400d, + PtpEventCode_UnreportedStatus = 0x400e, + }; + + enum PtpDataTypeCode : u16 { + PtpDataTypeCode_Undefined = 0x0000, + PtpDataTypeCode_S8 = 0x0001, + PtpDataTypeCode_U8 = 0x0002, + PtpDataTypeCode_S16 = 0x0003, + PtpDataTypeCode_U16 = 0x0004, + PtpDataTypeCode_S32 = 0x0005, + PtpDataTypeCode_U32 = 0x0006, + PtpDataTypeCode_S64 = 0x0007, + PtpDataTypeCode_U64 = 0x0008, + PtpDataTypeCode_S128 = 0x0009, + PtpDataTypeCode_U128 = 0x000a, + + PtpDataTypeCode_ArrayMask = (1u << 14), + + PtpDataTypeCode_S8Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_S8, + PtpDataTypeCode_U8Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_U8, + PtpDataTypeCode_S16Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_S16, + PtpDataTypeCode_U16Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_U16, + PtpDataTypeCode_S32Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_S32, + PtpDataTypeCode_U32Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_U32, + PtpDataTypeCode_S64Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_S64, + PtpDataTypeCode_U64Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_U64, + PtpDataTypeCode_S128Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_S128, + PtpDataTypeCode_U128Array = PtpDataTypeCode_ArrayMask | PtpDataTypeCode_U128, + + PtpDataTypeCode_String = 0xffff, + }; + + enum PtpPropertyGetSetFlag : u8 { + PtpPropertyGetSetFlag_Get = 0x00, + PtpPropertyGetSetFlag_GetSet = 0x01, + }; + + enum PtpPropertyGroupCode : u32 { + PtpPropertyGroupCode_Default = 0x00000000, + }; + + enum PtpPropertyFormFlag : u8 { + PtpPropertyFormFlag_None = 0x00, + PtpPropertyFormFlag_Range = 0x01, + PtpPropertyFormFlag_Enumeration = 0x02, + PtpPropertyFormFlag_DateTime = 0x03, + PtpPropertyFormFlag_FixedLengthArray = 0x04, + PtpPropertyFormFlag_RegularExpression = 0x05, + PtpPropertyFormFlag_ByteArray = 0x06, + PtpPropertyFormFlag_LongString = 0xff, + }; + + enum PtpObjectPropertyCode : u16 { + PtpObjectPropertyCode_StorageId = 0xdc01, + PtpObjectPropertyCode_ObjectFormat = 0xdc02, + PtpObjectPropertyCode_ProtectionStatus = 0xdc03, + PtpObjectPropertyCode_ObjectSize = 0xdc04, + PtpObjectPropertyCode_AssociationType = 0xdc05, + PtpObjectPropertyCode_AssociationDesc = 0xdc06, + PtpObjectPropertyCode_ObjectFileName = 0xdc07, + PtpObjectPropertyCode_DateCreated = 0xdc08, + PtpObjectPropertyCode_DateModified = 0xdc09, + PtpObjectPropertyCode_Keywords = 0xdc0a, + PtpObjectPropertyCode_ParentObject = 0xdc0b, + PtpObjectPropertyCode_AllowedFolderContents = 0xdc0c, + PtpObjectPropertyCode_Hidden = 0xdc0d, + PtpObjectPropertyCode_SystemObject = 0xdc0e, + PtpObjectPropertyCode_PersistentUniqueObjectIdentifier = 0xdc41, + PtpObjectPropertyCode_SyncId = 0xdc42, + PtpObjectPropertyCode_PropertyBag = 0xdc43, + PtpObjectPropertyCode_Name = 0xdc44, + PtpObjectPropertyCode_CreatedBy = 0xdc45, + PtpObjectPropertyCode_Artist = 0xdc46, + PtpObjectPropertyCode_DateAuthored = 0xdc47, + PtpObjectPropertyCode_Description = 0xdc48, + PtpObjectPropertyCode_UrlReference = 0xdc49, + PtpObjectPropertyCode_LanguageLocale = 0xdc4a, + PtpObjectPropertyCode_CopyrightInformation = 0xdc4b, + PtpObjectPropertyCode_Source = 0xdc4c, + PtpObjectPropertyCode_OriginLocation = 0xdc4d, + PtpObjectPropertyCode_DateAdded = 0xdc4e, + PtpObjectPropertyCode_NonConsumable = 0xdc4f, + PtpObjectPropertyCode_CorruptOrUnplayable = 0xdc50, + PtpObjectPropertyCode_ProducerSerialNumber = 0xdc51, + PtpObjectPropertyCode_RepresentativeSampleFormat = 0xdc81, + PtpObjectPropertyCode_RepresentativeSampleSize = 0xdc82, + PtpObjectPropertyCode_RepresentativeSampleHeight = 0xdc83, + PtpObjectPropertyCode_RepresentativeSampleWidth = 0xdc84, + PtpObjectPropertyCode_RepresentativeSampleDuration = 0xdc85, + PtpObjectPropertyCode_RepresentativeSampleData = 0xdc86, + PtpObjectPropertyCode_Width = 0xdc87, + PtpObjectPropertyCode_Height = 0xdc88, + PtpObjectPropertyCode_Duration = 0xdc89, + PtpObjectPropertyCode_Rating = 0xdc8a, + PtpObjectPropertyCode_Track = 0xdc8b, + PtpObjectPropertyCode_Genre = 0xdc8c, + PtpObjectPropertyCode_Credits = 0xdc8d, + PtpObjectPropertyCode_Lyrics = 0xdc8e, + PtpObjectPropertyCode_SubscriptionContentId = 0xdc8f, + PtpObjectPropertyCode_ProducedBy = 0xdc90, + PtpObjectPropertyCode_UseCount = 0xdc91, + PtpObjectPropertyCode_SkipCount = 0xdc92, + PtpObjectPropertyCode_LastAccessed = 0xdc93, + PtpObjectPropertyCode_ParentalRating = 0xdc94, + PtpObjectPropertyCode_MetaGenre = 0xdc95, + PtpObjectPropertyCode_Composer = 0xdc96, + PtpObjectPropertyCode_EffectiveRating = 0xdc97, + PtpObjectPropertyCode_Subtitle = 0xdc98, + PtpObjectPropertyCode_OriginalReleaseDate = 0xdc99, + PtpObjectPropertyCode_AlbumName = 0xdc9a, + PtpObjectPropertyCode_AlbumArtist = 0xdc9b, + PtpObjectPropertyCode_Mood = 0xdc9c, + PtpObjectPropertyCode_DrmStatus = 0xdc9d, + PtpObjectPropertyCode_SubDescription = 0xdc9e, + PtpObjectPropertyCode_IsCropped = 0xdcd1, + PtpObjectPropertyCode_IsColorCorrected = 0xdcd2, + PtpObjectPropertyCode_ImageBitDepth = 0xdcd3, + PtpObjectPropertyCode_Fnumber = 0xdcd4, + PtpObjectPropertyCode_ExposureTime = 0xdcd5, + PtpObjectPropertyCode_ExposureIndex = 0xdcd6, + PtpObjectPropertyCode_DisplayName = 0xdce0, + PtpObjectPropertyCode_BodyText = 0xdce1, + PtpObjectPropertyCode_Subject = 0xdce2, + PtpObjectPropertyCode_Priority = 0xdce3, + PtpObjectPropertyCode_GivenName = 0xdd00, + PtpObjectPropertyCode_MiddleNames = 0xdd01, + PtpObjectPropertyCode_FamilyName = 0xdd02, + PtpObjectPropertyCode_Prefix = 0xdd03, + PtpObjectPropertyCode_Suffix = 0xdd04, + PtpObjectPropertyCode_PhoneticGivenName = 0xdd05, + PtpObjectPropertyCode_PhoneticFamilyName = 0xdd06, + PtpObjectPropertyCode_EmailPrimary = 0xdd07, + PtpObjectPropertyCode_EmailPersonal1 = 0xdd08, + PtpObjectPropertyCode_EmailPersonal2 = 0xdd09, + PtpObjectPropertyCode_EmailBusiness1 = 0xdd0a, + PtpObjectPropertyCode_EmailBusiness2 = 0xdd0b, + PtpObjectPropertyCode_EmailOthers = 0xdd0c, + PtpObjectPropertyCode_PhoneNumberPrimary = 0xdd0d, + PtpObjectPropertyCode_PhoneNumberPersonal = 0xdd0e, + PtpObjectPropertyCode_PhoneNumberPersonal2 = 0xdd0f, + PtpObjectPropertyCode_PhoneNumberBusiness = 0xdd10, + PtpObjectPropertyCode_PhoneNumberBusiness2 = 0xdd11, + PtpObjectPropertyCode_PhoneNumberMobile = 0xdd12, + PtpObjectPropertyCode_PhoneNumberMobile2 = 0xdd13, + PtpObjectPropertyCode_FaxNumberPrimary = 0xdd14, + PtpObjectPropertyCode_FaxNumberPersonal = 0xdd15, + PtpObjectPropertyCode_FaxNumberBusiness = 0xdd16, + PtpObjectPropertyCode_PagerNumber = 0xdd17, + PtpObjectPropertyCode_PhoneNumberOthers = 0xdd18, + PtpObjectPropertyCode_PrimaryWebAddress = 0xdd19, + PtpObjectPropertyCode_PersonalWebAddress = 0xdd1a, + PtpObjectPropertyCode_BusinessWebAddress = 0xdd1b, + PtpObjectPropertyCode_InstantMessengerAddress = 0xdd1c, + PtpObjectPropertyCode_InstantMessengerAddress2 = 0xdd1d, + PtpObjectPropertyCode_InstantMessengerAddress3 = 0xdd1e, + PtpObjectPropertyCode_PostalAddressPersonalFull = 0xdd1f, + PtpObjectPropertyCode_PostalAddressPersonalFullLine1 = 0xdd20, + PtpObjectPropertyCode_PostalAddressPersonalFullLine2 = 0xdd21, + PtpObjectPropertyCode_PostalAddressPersonalFullCity = 0xdd22, + PtpObjectPropertyCode_PostalAddressPersonalFullRegion = 0xdd23, + PtpObjectPropertyCode_PostalAddressPersonalFullPostalCode = 0xdd24, + PtpObjectPropertyCode_PostalAddressPersonalFullCountry = 0xdd25, + PtpObjectPropertyCode_PostalAddressBusinessFull = 0xdd26, + PtpObjectPropertyCode_PostalAddressBusinessLine1 = 0xdd27, + PtpObjectPropertyCode_PostalAddressBusinessLine2 = 0xdd28, + PtpObjectPropertyCode_PostalAddressBusinessCity = 0xdd29, + PtpObjectPropertyCode_PostalAddressBusinessRegion = 0xdd2a, + PtpObjectPropertyCode_PostalAddressBusinessPostalCode = 0xdd2b, + PtpObjectPropertyCode_PostalAddressBusinessCountry = 0xdd2c, + PtpObjectPropertyCode_PostalAddressOtherFull = 0xdd2d, + PtpObjectPropertyCode_PostalAddressOtherLine1 = 0xdd2e, + PtpObjectPropertyCode_PostalAddressOtherLine2 = 0xdd2f, + PtpObjectPropertyCode_PostalAddressOtherCity = 0xdd30, + PtpObjectPropertyCode_PostalAddressOtherRegion = 0xdd31, + PtpObjectPropertyCode_PostalAddressOtherPostalCode = 0xdd32, + PtpObjectPropertyCode_PostalAddressOtherCountry = 0xdd33, + PtpObjectPropertyCode_OrganizationName = 0xdd34, + PtpObjectPropertyCode_PhoneticOrganizationName = 0xdd35, + PtpObjectPropertyCode_Role = 0xdd36, + PtpObjectPropertyCode_Birthdate = 0xdd37, + PtpObjectPropertyCode_MessageTo = 0xdd40, + PtpObjectPropertyCode_MessageCC = 0xdd41, + PtpObjectPropertyCode_MessageBCC = 0xdd42, + PtpObjectPropertyCode_MessageRead = 0xdd43, + PtpObjectPropertyCode_MessageReceivedTime = 0xdd44, + PtpObjectPropertyCode_MessageSender = 0xdd45, + PtpObjectPropertyCode_ActivityBeginTime = 0xdd50, + PtpObjectPropertyCode_ActivityEndTime = 0xdd51, + PtpObjectPropertyCode_ActivityLocation = 0xdd52, + PtpObjectPropertyCode_ActivityRequiredAttendees = 0xdd54, + PtpObjectPropertyCode_ActivityOptionalAttendees = 0xdd55, + PtpObjectPropertyCode_ActivityResources = 0xdd56, + PtpObjectPropertyCode_ActivityAccepted = 0xdd57, + PtpObjectPropertyCode_Owner = 0xdd5d, + PtpObjectPropertyCode_Editor = 0xdd5e, + PtpObjectPropertyCode_Webmaster = 0xdd5f, + PtpObjectPropertyCode_UrlSource = 0xdd60, + PtpObjectPropertyCode_UrlDestination = 0xdd61, + PtpObjectPropertyCode_TimeBookmark = 0xdd62, + PtpObjectPropertyCode_ObjectBookmark = 0xdd63, + PtpObjectPropertyCode_ByteBookmark = 0xdd64, + PtpObjectPropertyCode_LastBuildDate = 0xdd70, + PtpObjectPropertyCode_TimetoLive = 0xdd71, + PtpObjectPropertyCode_MediaGuid = 0xdd72, + PtpObjectPropertyCode_TotalBitRate = 0xde91, + PtpObjectPropertyCode_BitRateType = 0xde92, + PtpObjectPropertyCode_SampleRate = 0xde93, + PtpObjectPropertyCode_NumberOfChannels = 0xde94, + PtpObjectPropertyCode_AudioBitDepth = 0xde95, + PtpObjectPropertyCode_ScanDepth = 0xde97, + PtpObjectPropertyCode_AudioWaveCodec = 0xde99, + PtpObjectPropertyCode_AudioBitRate = 0xde9a, + PtpObjectPropertyCode_VideoFourCcCodec = 0xde9b, + PtpObjectPropertyCode_VideoBitRate = 0xde9c, + PtpObjectPropertyCode_FramesPerThousandSeconds = 0xde9d, + PtpObjectPropertyCode_KeyFrameDistance = 0xde9e, + PtpObjectPropertyCode_BufferSize = 0xde9f, + PtpObjectPropertyCode_EncodingQuality = 0xdea0, + PtpObjectPropertyCode_EncodingProfile = 0xdea1, + PtpObjectPropertyCode_BuyFlag = 0xd901, + }; + + enum PtpDevicePropertyCode : u16 { + PtpDevicePropertyCode_Undefined = 0x5000, + PtpDevicePropertyCode_BatteryLevel = 0x5001, + PtpDevicePropertyCode_FunctionalMode = 0x5002, + PtpDevicePropertyCode_ImageSize = 0x5003, + PtpDevicePropertyCode_CompressionSetting = 0x5004, + PtpDevicePropertyCode_WhiteBalance = 0x5005, + PtpDevicePropertyCode_RgbGain = 0x5006, + PtpDevicePropertyCode_FNumber = 0x5007, + PtpDevicePropertyCode_FocalLength = 0x5008, + PtpDevicePropertyCode_FocusDistance = 0x5009, + PtpDevicePropertyCode_FocusMode = 0x500a, + PtpDevicePropertyCode_ExposureMeteringMode = 0x500b, + PtpDevicePropertyCode_FlashMode = 0x500c, + PtpDevicePropertyCode_ExposureTime = 0x500d, + PtpDevicePropertyCode_ExposureProgramMode = 0x500e, + PtpDevicePropertyCode_ExposureIndex = 0x500f, + PtpDevicePropertyCode_ExposureBiasCompensation = 0x5010, + PtpDevicePropertyCode_DateTime = 0x5011, + PtpDevicePropertyCode_CaptureDelay = 0x5012, + PtpDevicePropertyCode_StillCaptureMode = 0x5013, + PtpDevicePropertyCode_Contrast = 0x5014, + PtpDevicePropertyCode_Sharpness = 0x5015, + PtpDevicePropertyCode_DigitalZoom = 0x5016, + PtpDevicePropertyCode_EffectMode = 0x5017, + PtpDevicePropertyCode_BurstNumber = 0x5018, + PtpDevicePropertyCode_BurstInterval = 0x5019, + PtpDevicePropertyCode_TimelapseNumber = 0x501a, + PtpDevicePropertyCode_TimelapseInterval = 0x501b, + PtpDevicePropertyCode_FocusMeteringMode = 0x501c, + PtpDevicePropertyCode_UploadUrl = 0x501d, + PtpDevicePropertyCode_Artist = 0x501e, + PtpDevicePropertyCode_CopyrightInfo = 0x501f, + PtpDevicePropertyCode_SupportedStreams = 0x5020, + PtpDevicePropertyCode_EnabledStreams = 0x5021, + PtpDevicePropertyCode_VideoFormat = 0x5022, + PtpDevicePropertyCode_VideoResolution = 0x5023, + PtpDevicePropertyCode_VideoQuality = 0x5024, + PtpDevicePropertyCode_VideoFrameRate = 0x5025, + PtpDevicePropertyCode_VideoContrast = 0x5026, + PtpDevicePropertyCode_VideoBrightness = 0x5027, + PtpDevicePropertyCode_AudioFormat = 0x5028, + PtpDevicePropertyCode_AudioBitrate = 0x5029, + PtpDevicePropertyCode_AudioSamplingRate = 0x502a, + PtpDevicePropertyCode_AudioBitPerSample = 0x502b, + PtpDevicePropertyCode_AudioVolume = 0x502c, + }; + + enum PtpObjectFormatCode : u16 { + PtpObjectFormatCode_Undefined = 0x3000, + PtpObjectFormatCode_Association = 0x3001, + PtpObjectFormatCode_Defined = 0x3800, + PtpObjectFormatCode_MtpMediaCard = 0xb211, + }; + + enum PtpAssociationType : u16 { + PtpAssociationType_Undefined = 0x0000, + PtpAssociationType_GenericFolder = 0x0001, + }; + + enum PtpGetObjectHandles : u32 { + PtpGetObjectHandles_AllFormats = 0x00000000, + PtpGetObjectHandles_AllAssocs = 0x00000000, + PtpGetObjectHandles_AllStorage = 0xffffffff, + PtpGetObjectHandles_RootParent = 0xffffffff, + }; + + enum PtpStorageType : u16 { + PtpStorageType_Undefined = 0x0000, + PtpStorageType_FixedRom = 0x0001, + PtpStorageType_RemovableRom = 0x0002, + PtpStorageType_FixedRam = 0x0003, + PtpStorageType_RemovableRam = 0x0004, + }; + + enum PtpFilesystemType : u16 { + PtpFilesystemType_Undefined = 0x0000, + PtpFilesystemType_GenericFlat = 0x0001, + PtpFilesystemType_GenericHierarchical = 0x0002, + PtpFilesystemType_Dcf = 0x0003, + }; + + enum PtpAccessCapability : u16 { + PtpAccessCapability_ReadWrite = 0x0000, + PtpAccessCapability_ReadOnly = 0x0001, + PtpAccessCapability_ReadOnlyWithObjectDeletion = 0x0002, + }; + + enum PtpProtectionStatus : u16 { + PtpProtectionStatus_NoProtection = 0x0000, + PtpProtectionStatus_ReadOnly = 0x0001, + PtpProtectionStatus_MtpReadOnlyData = 0x8002, + PtpProtectionStatus_MtpNonTransferableData = 0x8003, + }; + + enum PtpThumbFormat : u16 { + PtpThumbFormat_Undefined = 0x0000, + }; + + struct PtpUsbBulkContainer { + u32 length; + u16 type; + u16 code; + u32 trans_id; + }; + static_assert(sizeof(PtpUsbBulkContainer) == PtpUsbBulkHeaderLength); + + struct PtpNewObjectInfo { + u32 storage_id; + u32 parent_object_id; + u32 object_id; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp_data_builder.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp_data_builder.hpp new file mode 100644 index 00000000..6e1baa06 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp_data_builder.hpp @@ -0,0 +1,174 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <haze/async_usb_server.hpp> +#include <haze/common.hpp> +#include <haze/ptp.hpp> + +namespace haze { + + class PtpDataBuilder final { + private: + AsyncUsbServer *m_server; + u32 m_transmitted_size; + u32 m_offset; + u8 *m_data; + bool m_disabled; + private: + Result Flush() { + ON_SCOPE_EXIT { + m_transmitted_size += m_offset; + m_offset = 0; + }; + + /* If we're disabled, we have nothing to do. */ + R_SUCCEED_IF(m_disabled); + + /* Otherwise, we should write our buffered data. */ + R_RETURN(m_server->WritePacket(m_data, m_offset)); + } + public: + constexpr explicit PtpDataBuilder(void *data, AsyncUsbServer *server) : m_server(server), m_transmitted_size(), m_offset(), m_data(static_cast<u8 *>(data)), m_disabled() { /* ... */ } + + Result Commit() { + if (m_offset > 0) { + /* If there is remaining data left to write, write it now. */ + R_TRY(this->Flush()); + } + + if (util::IsAligned(m_transmitted_size, PtpUsbBulkHighSpeedMaxPacketLength)) { + /* If the transmission size was a multiple of wMaxPacketSize, send a zero length packet. */ + R_TRY(this->Flush()); + } + + R_SUCCEED(); + } + + Result AddBuffer(const u8 *buffer, u32 count) { + while (count > 0) { + /* Calculate how many bytes we can write now. */ + const u32 write_size = std::min<u32>(count, haze::UsbBulkPacketBufferSize - m_offset); + + /* Write this number of bytes. */ + std::memcpy(m_data + m_offset, buffer, write_size); + m_offset += write_size; + buffer += write_size; + count -= write_size; + + /* If our buffer is full, flush it. */ + if (m_offset == haze::UsbBulkPacketBufferSize) { + R_TRY(this->Flush()); + } + } + + R_SUCCEED(); + } + + template <typename T> + Result Add(T value) { + u8 bytes[sizeof(T)]; + + std::memcpy(bytes, std::addressof(value), sizeof(T)); + + R_RETURN(this->AddBuffer(bytes, sizeof(T))); + } + + Result AddDataHeader(PtpUsbBulkContainer &request, u32 data_size) { + R_TRY(this->Add<u32>(PtpUsbBulkHeaderLength + data_size)); + R_TRY(this->Add<u16>(PtpUsbBulkContainerType_Data)); + R_TRY(this->Add<u16>(request.code)); + R_TRY(this->Add<u32>(request.trans_id)); + + R_SUCCEED(); + } + + Result AddResponseHeader(PtpUsbBulkContainer &request, PtpResponseCode code, u32 params_size) { + R_TRY(this->Add<u32>(PtpUsbBulkHeaderLength + params_size)); + R_TRY(this->Add<u16>(PtpUsbBulkContainerType_Response)); + R_TRY(this->Add<u16>(code)); + R_TRY(this->Add<u32>(request.trans_id)); + + R_SUCCEED(); + } + + template <typename F> + Result WriteVariableLengthData(PtpUsbBulkContainer &request, F &&func) { + HAZE_ASSERT(m_offset == 0 && m_transmitted_size == 0); + + /* Declare how many bytes the data will require to write. */ + u32 data_size = 0; + { + /* Temporarily disable writing to calculate the size.*/ + m_disabled = true; + + ON_SCOPE_EXIT { + /* Report how many bytes were required. */ + data_size = m_transmitted_size; + + /* On exit, enable writing and reset sizes. */ + m_transmitted_size = 0; + m_disabled = false; + m_offset = 0; + }; + + R_TRY(func()); + R_TRY(this->Commit()); + } + + /* Actually copy and write the data. */ + R_TRY(this->AddDataHeader(request, data_size)); + R_TRY(func()); + R_TRY(this->Commit()); + + /* We succeeded. */ + R_SUCCEED(); + } + + template <typename T> + Result AddString(const T *str) { + /* Use one less than the maximum string length for maximum length with null terminator. */ + const u8 len = static_cast<u8>(std::min<s32>(util::Strlen(str), PtpStringMaxLength - 1)); + + if (len > 0) { + /* Length is padded by null terminator for non-empty strings. */ + R_TRY(this->Add<u8>(len + 1)); + + for (size_t i = 0; i < len; i++) { + R_TRY(this->Add<u16>(str[i])); + } + + R_TRY(this->Add<u16>(0)); + } else { + R_TRY(this->Add<u8>(len)); + } + + R_SUCCEED(); + } + + template <typename T> + Result AddArray(const T *arr, u32 count) { + R_TRY(this->Add<u32>(count)); + + for (size_t i = 0; i < count; i++) { + R_TRY(this->Add<T>(arr[i])); + } + + R_SUCCEED(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp_data_parser.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp_data_parser.hpp new file mode 100644 index 00000000..f151f237 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp_data_parser.hpp @@ -0,0 +1,114 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <haze/async_usb_server.hpp> +#include <haze/common.hpp> +#include <haze/ptp.hpp> + +namespace haze { + + class PtpDataParser final { + private: + AsyncUsbServer *m_server; + u32 m_received_size; + u32 m_offset; + u8 *m_data; + bool m_eot; + private: + Result Flush() { + R_UNLESS(!m_eot, haze::ResultEndOfTransmission()); + + m_received_size = 0; + m_offset = 0; + + ON_SCOPE_EXIT { + /* End of transmission occurs when receiving a bulk transfer less than the buffer size. */ + /* PTP uses zero-length termination, so zero is a possible size to receive. */ + m_eot = m_received_size < haze::UsbBulkPacketBufferSize; + }; + + R_RETURN(m_server->ReadPacket(m_data, haze::UsbBulkPacketBufferSize, std::addressof(m_received_size))); + } + public: + constexpr explicit PtpDataParser(void *data, AsyncUsbServer *server) : m_server(server), m_received_size(), m_offset(), m_data(static_cast<u8 *>(data)), m_eot() { /* ... */ } + + Result Finalize() { + /* Read until the transmission completes. */ + while (true) { + Result rc = this->Flush(); + + R_SUCCEED_IF(m_eot || haze::ResultEndOfTransmission::Includes(rc)); + R_TRY(rc); + } + } + + Result ReadBuffer(u8 *buffer, u32 count, u32 *out_read_count) { + *out_read_count = 0; + + while (count > 0) { + /* If we cannot read more bytes now, flush. */ + if (m_offset == m_received_size) { + R_TRY(this->Flush()); + } + + /* Calculate how many bytes we can read now. */ + u32 read_size = std::min<u32>(count, m_received_size - m_offset); + + /* Read this number of bytes. */ + std::memcpy(buffer + *out_read_count, m_data + m_offset, read_size); + *out_read_count += read_size; + m_offset += read_size; + count -= read_size; + } + + R_SUCCEED(); + } + + template <typename T> + Result Read(T *out_t) { + u32 read_count; + u8 bytes[sizeof(T)]; + + R_TRY(this->ReadBuffer(bytes, sizeof(T), std::addressof(read_count))); + + std::memcpy(out_t, bytes, sizeof(T)); + + R_SUCCEED(); + } + + /* NOTE: out_string must contain room for 256 bytes. */ + /* The result will be null-terminated on successful completion. */ + Result ReadString(char *out_string) { + u8 len; + R_TRY(this->Read(std::addressof(len))); + + /* Read characters one by one. */ + for (size_t i = 0; i < len; i++) { + u16 chr; + R_TRY(this->Read(std::addressof(chr))); + + *out_string++ = static_cast<char>(chr); + } + + /* Write null terminator. */ + *out_string++ = '\x00'; + + R_SUCCEED(); + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp_object_database.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp_object_database.hpp new file mode 100644 index 00000000..7443bab4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp_object_database.hpp @@ -0,0 +1,107 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <haze/common.hpp> +#include <haze/ptp_object_heap.hpp> +#include <vapours/util.hpp> + +namespace haze { + + struct PtpObject { + public: + util::IntrusiveRedBlackTreeNode m_name_node; + util::IntrusiveRedBlackTreeNode m_object_id_node; + u32 m_parent_id; + u32 m_object_id; + char m_name[]; + public: + const char *GetName() const { return m_name; } + u32 GetParentId() const { return m_parent_id; } + u32 GetObjectId() const { return m_object_id; } + public: + bool GetIsRegistered() const { return m_object_id != 0; } + void Register(u32 object_id) { m_object_id = object_id; } + void Unregister() { m_object_id = 0; } + public: + struct NameComparator { + struct RedBlackKeyType { + const char *m_name; + + constexpr RedBlackKeyType(const char *name) : m_name(name) { /* ... */ } + + constexpr const char *GetName() const { + return m_name; + } + }; + + template<typename T> requires (std::same_as<T, PtpObject> || std::same_as<T, RedBlackKeyType>) + static constexpr int Compare(const T &lhs, const PtpObject &rhs) { + /* All SD card filesystems supported by fs are case-insensitive and case-preserving. */ + /* Account for that in collation here. */ + return strcasecmp(lhs.GetName(), rhs.GetName()); + } + }; + + struct ObjectIdComparator { + struct RedBlackKeyType { + u32 m_object_id; + + constexpr RedBlackKeyType(u32 object_id) : m_object_id(object_id) { /* ... */ } + + constexpr u32 GetObjectId() const { + return m_object_id; + } + }; + + template<typename T> requires (std::same_as<T, PtpObject> || std::same_as<T, RedBlackKeyType>) + static constexpr int Compare(const T &lhs, const PtpObject &rhs) { + return lhs.GetObjectId() - rhs.GetObjectId(); + } + }; + }; + + class PtpObjectDatabase { + private: + using ObjectNameTreeTraits = util::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&PtpObject::m_name_node>; + using ObjectNameTree = ObjectNameTreeTraits::TreeType<PtpObject::NameComparator>; + + using ObjectIdTreeTraits = util::IntrusiveRedBlackTreeMemberTraitsDeferredAssert<&PtpObject::m_object_id_node>; + using ObjectIdTree = ObjectIdTreeTraits::TreeType<PtpObject::ObjectIdComparator>; + + PtpObjectHeap *m_object_heap; + ObjectNameTree m_name_tree; + ObjectIdTree m_object_id_tree; + u32 m_next_object_id; + public: + constexpr explicit PtpObjectDatabase() : m_object_heap(), m_name_tree(), m_object_id_tree(), m_next_object_id() { /* ... */ } + + void Initialize(PtpObjectHeap *object_heap); + void Finalize(); + public: + /* Object database API. */ + Result CreateOrFindObject(const char *parent_name, const char *name, u32 parent_id, PtpObject **out_object); + void RegisterObject(PtpObject *object, u32 desired_id = 0); + void UnregisterObject(PtpObject *object); + void DeleteObject(PtpObject *obj); + + Result CreateAndRegisterObjectId(const char *parent_name, const char *name, u32 parent_id, u32 *out_object_id); + public: + PtpObject *GetObjectById(u32 object_id); + PtpObject *GetObjectByName(const char *name); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp_object_heap.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp_object_heap.hpp new file mode 100644 index 00000000..ac3484e5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp_object_heap.hpp @@ -0,0 +1,132 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <haze/common.hpp> + +namespace haze { + + /* This simple linear allocator implementation allows us to rapidly reclaim the entire object graph. */ + /* This is critical for maintaining interactivity when a session is closed. */ + class PtpObjectHeap { + private: + static constexpr size_t NumHeapBlocks = 2; + private: + void *m_heap_blocks[NumHeapBlocks]; + void *m_next_address; + u32 m_heap_block_size; + u32 m_current_heap_block; + public: + constexpr explicit PtpObjectHeap() : m_heap_blocks(), m_next_address(), m_heap_block_size(), m_current_heap_block() { /* ... */ } + + void Initialize(); + void Finalize(); + public: + constexpr size_t GetTotalSize() const { + return m_heap_block_size * NumHeapBlocks; + } + + constexpr size_t GetUsedSize() const { + return (m_heap_block_size * m_current_heap_block) + this->GetNextAddress() - this->GetFirstAddress(); + } + private: + constexpr u8 *GetNextAddress() const { return static_cast<u8 *>(m_next_address); } + constexpr u8 *GetFirstAddress() const { return static_cast<u8 *>(m_heap_blocks[m_current_heap_block]); } + + constexpr u8 *GetCurrentBlockEnd() const { + return this->GetFirstAddress() + m_heap_block_size; + } + + constexpr bool AllocationIsPossible(size_t n) const { + return n <= m_heap_block_size; + } + + constexpr bool AllocationIsSatisfyable(size_t n) const { + /* Check for overflow. */ + if (!util::CanAddWithoutOverflow(reinterpret_cast<uintptr_t>(this->GetNextAddress()), n)) { + return false; + } + + /* Check if we would exceed the size of the current block. */ + if (this->GetNextAddress() + n > this->GetCurrentBlockEnd()) { + return false; + } + + return true; + } + + constexpr bool AdvanceToNextBlock() { + if (m_current_heap_block < NumHeapBlocks - 1) { + m_next_address = m_heap_blocks[++m_current_heap_block]; + return true; + } + + return false; + } + + constexpr void *AllocateFromCurrentBlock(size_t n) { + void *result = this->GetNextAddress(); + + m_next_address = this->GetNextAddress() + n; + + return result; + } + public: + template <typename T = void> + constexpr T *Allocate(size_t n) { + /* Check for overflow in alignment. */ + if (!util::CanAddWithoutOverflow(n, alignof(u64) - 1)) { + return nullptr; + } + + /* Align the amount to satisfy allocation for u64. */ + n = util::AlignUp(n, alignof(u64)); + + /* Check if the allocation is possible. */ + if (!this->AllocationIsPossible(n)) { + return nullptr; + } + + /* If the allocation is not satisfyable now, we might be able to satisfy it on the next block. */ + /* However, if the next block would be empty, we won't be able to satisfy the request. */ + if (!this->AllocationIsSatisfyable(n) && !this->AdvanceToNextBlock()) { + return nullptr; + } + + /* Allocate the memory. */ + return static_cast<T *>(this->AllocateFromCurrentBlock(n)); + } + + constexpr void Deallocate(void *p, size_t n) { + /* Check for overflow in alignment. */ + if (!util::CanAddWithoutOverflow(n, alignof(u64) - 1)) { + return; + } + + /* Align the amount to satisfy allocation for u64. */ + n = util::AlignUp(n, alignof(u64)); + + /* If the pointer was the last allocation, return the memory to the heap. */ + if (static_cast<u8 *>(p) + n == this->GetNextAddress()) { + m_next_address = this->GetNextAddress() - n; + } + + /* Otherwise, do nothing. */ + /* ... */ + } + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp_responder.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp_responder.hpp new file mode 100644 index 00000000..8dc996cd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp_responder.hpp @@ -0,0 +1,89 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <haze/common.hpp> +#include <haze/async_usb_server.hpp> +#include <haze/ptp_object_heap.hpp> +#include <haze/ptp_object_database.hpp> +#include <haze/ptp_responder_types.hpp> + +namespace haze { + + class PtpDataParser; + + class PtpResponder final { + private: + AsyncUsbServer m_usb_server; + FileSystemProxy m_fs; + PtpUsbBulkContainer m_request_header; + PtpObjectHeap *m_object_heap; + PtpBuffers* m_buffers; + u32 m_send_object_id; + bool m_session_open; + + PtpObjectDatabase m_object_database; + public: + constexpr explicit PtpResponder() : m_usb_server(), m_fs(), m_request_header(), m_object_heap(), m_buffers(), m_send_object_id(), m_session_open(), m_object_database() { /* ... */ } + + Result Initialize(EventReactor *reactor, PtpObjectHeap *object_heap); + void Finalize(); + public: + Result LoopProcess(); + private: + /* Request handling. */ + Result HandleRequest(); + Result HandleRequestImpl(); + Result HandleCommandRequest(PtpDataParser &dp); + void ForceCloseSession(); + + Result WriteResponse(PtpResponseCode code, const void* data, size_t size); + Result WriteResponse(PtpResponseCode code); + + template <typename Data> requires (util::is_pod<Data>::value) + Result WriteResponse(PtpResponseCode code, const Data &data) { + R_RETURN(this->WriteResponse(code, std::addressof(data), sizeof(data))); + } + + /* PTP operations. */ + Result GetDeviceInfo(PtpDataParser &dp); + Result OpenSession(PtpDataParser &dp); + Result CloseSession(PtpDataParser &dp); + Result GetStorageIds(PtpDataParser &dp); + Result GetStorageInfo(PtpDataParser &dp); + Result GetObjectHandles(PtpDataParser &dp); + Result GetObjectInfo(PtpDataParser &dp); + Result GetObject(PtpDataParser &dp); + Result SendObjectInfo(PtpDataParser &dp); + Result SendObject(PtpDataParser &dp); + Result DeleteObject(PtpDataParser &dp); + + /* Android operations. */ + Result GetPartialObject64(PtpDataParser &dp); + Result SendPartialObject(PtpDataParser &dp); + Result TruncateObject(PtpDataParser &dp); + Result BeginEditObject(PtpDataParser &dp); + Result EndEditObject(PtpDataParser &dp); + + /* MTP operations. */ + Result GetObjectPropsSupported(PtpDataParser &dp); + Result GetObjectPropDesc(PtpDataParser &dp); + Result GetObjectPropValue(PtpDataParser &dp); + Result SetObjectPropValue(PtpDataParser &dp); + Result GetObjectPropList(PtpDataParser &dp); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp_responder_types.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp_responder_types.hpp new file mode 100644 index 00000000..fd91e6d7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/ptp_responder_types.hpp @@ -0,0 +1,183 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <haze.hpp> + +namespace haze { + + constexpr UsbCommsInterfaceInfo MtpInterfaceInfo = { + .bInterfaceClass = 0x06, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x01, + }; + + /* This is a VID:PID recognized by libmtp. */ + constexpr u16 SwitchMtpIdVendor = 0x057e; + constexpr u16 SwitchMtpIdProduct = 0x201d; + + /* Constants used for MTP GetDeviceInfo response. */ + constexpr u16 MtpStandardVersion = 100; + constexpr u32 MtpVendorExtensionId = 6; + constexpr auto MtpVendorExtensionDesc = "microsoft.com: 1.0;"; + constexpr u16 MtpFunctionalModeDefault = 0; + constexpr auto MtpDeviceManufacturer = "Nintendo"; + constexpr auto MtpDeviceModel = "Nintendo Switch"; + + enum StorageId : u32 { + StorageId_SdmcFs = 0xffffffffu - 1, + }; + + constexpr PtpOperationCode SupportedOperationCodes[] = { + PtpOperationCode_GetDeviceInfo, + PtpOperationCode_OpenSession, + PtpOperationCode_CloseSession, + PtpOperationCode_GetStorageIds, + PtpOperationCode_GetStorageInfo, + PtpOperationCode_GetObjectHandles, + PtpOperationCode_GetObjectInfo, + PtpOperationCode_GetObject, + PtpOperationCode_SendObjectInfo, + PtpOperationCode_SendObject, + PtpOperationCode_DeleteObject, + PtpOperationCode_MtpGetObjectPropsSupported, + PtpOperationCode_MtpGetObjectPropDesc, + PtpOperationCode_MtpGetObjectPropValue, + PtpOperationCode_MtpSetObjectPropValue, + PtpOperationCode_MtpGetObjPropList, + PtpOperationCode_AndroidGetPartialObject64, + PtpOperationCode_AndroidSendPartialObject, + PtpOperationCode_AndroidTruncateObject, + PtpOperationCode_AndroidBeginEditObject, + PtpOperationCode_AndroidEndEditObject, + }; + + constexpr const PtpEventCode SupportedEventCodes[] = { /* ... */ }; + constexpr const PtpDevicePropertyCode SupportedDeviceProperties[] = { /* ... */ }; + constexpr const PtpObjectFormatCode SupportedCaptureFormats[] = { /* ... */ }; + + constexpr const PtpObjectFormatCode SupportedPlaybackFormats[] = { + PtpObjectFormatCode_Undefined, + PtpObjectFormatCode_Association, + }; + + constexpr const PtpObjectPropertyCode SupportedObjectProperties[] = { + PtpObjectPropertyCode_StorageId, + PtpObjectPropertyCode_ObjectFormat, + PtpObjectPropertyCode_ObjectSize, + PtpObjectPropertyCode_ObjectFileName, + PtpObjectPropertyCode_ParentObject, + PtpObjectPropertyCode_PersistentUniqueObjectIdentifier, + }; + + constexpr bool IsSupportedObjectPropertyCode(PtpObjectPropertyCode c) { + for (size_t i = 0; i < util::size(SupportedObjectProperties); i++) { + if (SupportedObjectProperties[i] == c) { + return true; + } + } + + return false; + } + + constexpr const StorageId SupportedStorageIds[] = { + StorageId_SdmcFs, + }; + + struct PtpStorageInfo { + PtpStorageType storage_type; + PtpFilesystemType filesystem_type; + PtpAccessCapability access_capability; + u64 max_capacity; + u64 free_space_in_bytes; + u32 free_space_in_images; + const char *storage_description; + const char *volume_label; + }; + + constexpr PtpStorageInfo DefaultStorageInfo = { + .storage_type = PtpStorageType_FixedRam, + .filesystem_type = PtpFilesystemType_GenericHierarchical, + .access_capability = PtpAccessCapability_ReadWrite, + .max_capacity = 0, + .free_space_in_bytes = 0, + .free_space_in_images = 0, + .storage_description = "", + .volume_label = "", + }; + + struct PtpObjectInfo { + StorageId storage_id; + PtpObjectFormatCode object_format; + PtpProtectionStatus protection_status; + u32 object_compressed_size; + u16 thumb_format; + u32 thumb_compressed_size; + u32 thumb_width; + u32 thumb_height; + u32 image_width; + u32 image_height; + u32 image_depth; + u32 parent_object; + PtpAssociationType association_type; + u32 association_desc; + u32 sequence_number; + const char *filename; + const char *capture_date; + const char *modification_date; + const char *keywords; + }; + + constexpr PtpObjectInfo DefaultObjectInfo = { + .storage_id = StorageId_SdmcFs, + .object_format = {}, + .protection_status = PtpProtectionStatus_NoProtection, + .object_compressed_size = 0, + .thumb_format = 0, + .thumb_compressed_size = 0, + .thumb_width = 0, + .thumb_height = 0, + .image_width = 0, + .image_height = 0, + .image_depth = 0, + .parent_object = PtpGetObjectHandles_RootParent, + .association_type = PtpAssociationType_Undefined, + .association_desc = 0, + .sequence_number = 0, + .filename = nullptr, + .capture_date = "", + .modification_date = "", + .keywords = "", + }; + + constexpr u32 UsbBulkPacketBufferSize = 1_MB; + constexpr u64 FsBufferSize = UsbBulkPacketBufferSize; + constexpr s64 DirectoryReadSize = 32; + + struct PtpBuffers { + char filename_string_buffer[PtpStringMaxLength + 1]; + char capture_date_string_buffer[PtpStringMaxLength + 1]; + char modification_date_string_buffer[PtpStringMaxLength + 1]; + char keywords_string_buffer[PtpStringMaxLength + 1]; + + FsDirectoryEntry file_system_entry_buffer[DirectoryReadSize]; + u8 file_system_data_buffer[FsBufferSize]; + + alignas(4_KB) u8 usb_bulk_write_buffer[UsbBulkPacketBufferSize]; + alignas(4_KB) u8 usb_bulk_read_buffer[UsbBulkPacketBufferSize]; + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/results.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/results.hpp new file mode 100644 index 00000000..ea3933dd --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/results.hpp @@ -0,0 +1,44 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <vapours/results.hpp> + +/* NOTE: These results are all custom, and not official. */ +R_DEFINE_NAMESPACE_RESULT_MODULE(haze, 420); + +namespace haze { + + R_DEFINE_ERROR_RESULT(RegistrationFailed, 1); + R_DEFINE_ERROR_RESULT(NotConfigured, 2); + R_DEFINE_ERROR_RESULT(TransferFailed, 3); + R_DEFINE_ERROR_RESULT(StopRequested, 4); + R_DEFINE_ERROR_RESULT(FocusLost, 5); + R_DEFINE_ERROR_RESULT(EndOfTransmission, 6); + R_DEFINE_ERROR_RESULT(UnknownPacketType, 7); + R_DEFINE_ERROR_RESULT(SessionNotOpen, 8); + R_DEFINE_ERROR_RESULT(OutOfMemory, 9); + R_DEFINE_ERROR_RESULT(InvalidObjectId, 10); + R_DEFINE_ERROR_RESULT(InvalidStorageId, 11); + R_DEFINE_ERROR_RESULT(OperationNotSupported, 12); + R_DEFINE_ERROR_RESULT(UnknownRequestType, 13); + R_DEFINE_ERROR_RESULT(UnknownPropertyCode, 14); + R_DEFINE_ERROR_RESULT(InvalidPropertyValue, 15); + R_DEFINE_ERROR_RESULT(InvalidArgument, 16); + R_DEFINE_ERROR_RESULT(GroupSpecified, 17); + R_DEFINE_ERROR_RESULT(DepthSpecified, 18); + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/usb_session.hpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/usb_session.hpp new file mode 100644 index 00000000..e5874617 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/include/haze/usb_session.hpp @@ -0,0 +1,48 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <haze/common.hpp> + +namespace haze { + + enum UsbSessionEndpoint { + UsbSessionEndpoint_Read = 0, + UsbSessionEndpoint_Write = 1, + UsbSessionEndpoint_Interrupt = 2, + UsbSessionEndpoint_Count = 3, + }; + + class UsbSession { + private: + UsbDsInterface *m_interface; + UsbDsEndpoint *m_endpoints[UsbSessionEndpoint_Count]; + private: + Result Initialize1x(const UsbCommsInterfaceInfo *info); + Result Initialize5x(const UsbCommsInterfaceInfo *info); + public: + constexpr explicit UsbSession() : m_interface(), m_endpoints() { /* ... */ } + + Result Initialize(const UsbCommsInterfaceInfo *info, u16 id_vendor, u16 id_product); + void Finalize(); + + bool GetConfigured() const; + Event *GetCompletionEvent(UsbSessionEndpoint ep) const; + Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_urb_id); + Result GetTransferResult(UsbSessionEndpoint ep, u32 urb_id, u32 *out_transferred_size); + }; + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/async_usb_server.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/async_usb_server.cpp new file mode 100644 index 00000000..8d63468a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/async_usb_server.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 <http://www.gnu.org/licenses/>. + */ +#include <haze.hpp> + +namespace haze { + + namespace { + + constinit UsbSession g_usb_session; + + } + + Result AsyncUsbServer::Initialize(const UsbCommsInterfaceInfo *interface_info, u16 id_vendor, u16 id_product, EventReactor *reactor) { + m_reactor = reactor; + + /* Set up a new USB session. */ + R_TRY(g_usb_session.Initialize(interface_info, id_vendor, id_product)); + + R_SUCCEED(); + } + + void AsyncUsbServer::Finalize() { + g_usb_session.Finalize(); + } + + Result AsyncUsbServer::TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred) const { + u32 urb_id; + s32 waiter_idx; + + /* If we're not configured yet, wait to become configured first. */ + if (!g_usb_session.GetConfigured()) { + R_TRY(m_reactor->WaitFor(std::addressof(waiter_idx), waiterForEvent(usbDsGetStateChangeEvent()))); + R_TRY(eventClear(usbDsGetStateChangeEvent())); + + R_THROW(haze::ResultNotConfigured()); + } + + /* Select the appropriate endpoint and begin a transfer. */ + UsbSessionEndpoint ep = read ? UsbSessionEndpoint_Read : UsbSessionEndpoint_Write; + R_TRY(g_usb_session.TransferAsync(ep, page, size, std::addressof(urb_id))); + + /* Try to wait for the event. */ + R_TRY(m_reactor->WaitFor(std::addressof(waiter_idx), waiterForEvent(g_usb_session.GetCompletionEvent(ep)))); + + /* Return what we transferred. */ + R_RETURN(g_usb_session.GetTransferResult(ep, urb_id, out_size_transferred)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/console_fsh.glsl b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/console_fsh.glsl new file mode 100644 index 00000000..2875883e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/console_fsh.glsl @@ -0,0 +1,15 @@ +#version 460 + +layout (location = 0) noperspective in vec3 inTexCoord; +layout (location = 1) flat in vec4 inFrontPal; +layout (location = 2) flat in vec4 inBackPal; + +layout (location = 0) out vec4 outColor; + +layout (binding = 0) uniform sampler2DArray tileset; + +void main() +{ + float value = texture(tileset, inTexCoord).r; + outColor = mix(inBackPal, inFrontPal, value); +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/console_vsh.glsl b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/console_vsh.glsl new file mode 100644 index 00000000..59a5b79a --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/console_vsh.glsl @@ -0,0 +1,35 @@ +#version 460 + +layout (location = 0) in float inTileId; +layout (location = 1) in uvec2 inColorId; + +layout (location = 0) out vec3 outTexCoord; +layout (location = 1) out vec4 outFrontPal; +layout (location = 2) out vec4 outBackPal; + +layout (std140, binding = 0) uniform Config +{ + vec4 dimensions; + vec4 vertices[3]; + vec4 palettes[24]; +} u; + +void main() +{ + float id = float(gl_InstanceID); + float tileRow = floor(id / u.dimensions.z); + float tileCol = id - tileRow * u.dimensions.z; + + vec2 basePos; + basePos.x = 2.0 * (tileCol + 0.5) / u.dimensions.z - 1.0; + basePos.y = 2.0 * (1.0 - (tileRow + 0.5) / u.dimensions.w) - 1.0; + + vec2 vtxData = u.vertices[gl_VertexID].xy; + vec2 scale = vec2(1.0) / u.dimensions.zw; + gl_Position.xy = vtxData * scale + basePos; + gl_Position.zw = vec2(0.5, 1.0); + + outTexCoord = vec3(u.vertices[gl_VertexID].zw, inTileId); + outFrontPal = u.palettes[inColorId.x]; + outBackPal = u.palettes[inColorId.y]; +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/device_properties.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/device_properties.cpp new file mode 100644 index 00000000..8021fd72 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/device_properties.cpp @@ -0,0 +1,50 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <haze.hpp> + +namespace haze { + + namespace { + + constinit SetSysSerialNumber g_serial_number = {}; + constinit SetSysFirmwareVersion g_firmware_version = {}; + + } + + Result LoadDeviceProperties() { + /* Initialize set:sys. */ + R_TRY(setsysInitialize()); + + /* Ensure we maintain a clean state on exit. */ + ON_SCOPE_EXIT { setsysExit(); }; + + /* Get the serial number and firmware version. */ + R_TRY(setsysGetSerialNumber(std::addressof(g_serial_number))); + R_TRY(setsysGetFirmwareVersion(std::addressof(g_firmware_version))); + + /* We succeeded. */ + R_SUCCEED(); + } + + const char *GetSerialNumber() { + return g_serial_number.number; + } + + const char *GetFirmwareVersion() { + return g_firmware_version.display_version; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/event_reactor.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/event_reactor.cpp new file mode 100644 index 00000000..f43d7735 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/event_reactor.cpp @@ -0,0 +1,78 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <haze.hpp> + +namespace haze { + + bool EventReactor::AddConsumer(EventConsumer *consumer, Waiter waiter) { + HAZE_ASSERT(m_num_wait_objects + 1 <= svc::ArgumentHandleCountMax); + + /* Add to the end of the list. */ + m_consumers[m_num_wait_objects] = consumer; + m_waiters[m_num_wait_objects] = waiter; + m_num_wait_objects++; + + return true; + } + + void EventReactor::RemoveConsumer(EventConsumer *consumer) { + s32 output_index = 0; + + /* Remove the consumer. */ + for (s32 input_index = 0; input_index < m_num_wait_objects; input_index++) { + if (m_consumers[input_index] == consumer) { + continue; + } + + if (output_index != input_index) { + m_consumers[output_index] = m_consumers[input_index]; + m_waiters[output_index] = m_waiters[input_index]; + } + + output_index++; + } + + m_num_wait_objects = output_index; + } + + Result EventReactor::WaitForImpl(s32 *out_arg_waiter, const Waiter *arg_waiters, s32 num_arg_waiters) { + HAZE_ASSERT(0 < num_arg_waiters && num_arg_waiters <= svc::ArgumentHandleCountMax); + HAZE_ASSERT(m_num_wait_objects + num_arg_waiters <= svc::ArgumentHandleCountMax); + + while (true) { + /* Check if we should wait for an event. */ + R_TRY(m_result); + + /* Insert waiters from argument list. */ + for (s32 i = 0; i < num_arg_waiters; i++) { + m_waiters[i + m_num_wait_objects] = arg_waiters[i]; + } + + s32 idx; + HAZE_R_ABORT_UNLESS(waitObjects(std::addressof(idx), m_waiters, m_num_wait_objects + num_arg_waiters, svc::WaitInfinite)); + + /* If a waiter in the argument list was signaled, return it. */ + if (idx >= m_num_wait_objects) { + *out_arg_waiter = idx - m_num_wait_objects; + R_SUCCEED(); + } + + /* Otherwise, process the event as normal. */ + m_consumers[idx]->ProcessEvent(); + } + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/gpu_console.c b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/gpu_console.c new file mode 100644 index 00000000..cbfe6287 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/gpu_console.c @@ -0,0 +1,487 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/iosupport.h> + +#include <switch.h> +#include <deko3d.h> + +// Define the desired number of framebuffers +#define FB_NUM 2 + +// Define the size of the memory block that will hold code +#define CODEMEMSIZE (64*1024) + +// Define the size of the memory block that will hold command lists +#define CMDMEMSIZE (64*1024) + +#define NUM_IMAGE_SLOTS 1 +#define NUM_SAMPLER_SLOTS 1 + +typedef struct { + float pos[2]; + float tex[2]; +} VertexDef; + +typedef struct { + float red; + float green; + float blue; + float alpha; +} PaletteColor; + +typedef struct { + float dimensions[4]; + VertexDef vertices[3]; + PaletteColor palettes[24]; +} ConsoleConfig; + +static const VertexDef g_vertexData[3] = { + { { 0.0f, +1.0f }, { 0.5f, 0.0f, } }, + { { -1.0f, -1.0f }, { 0.0f, 1.0f, } }, + { { +1.0f, -1.0f }, { 1.0f, 1.0f, } }, +}; + +static const PaletteColor g_paletteData[24] = { + { 0.0f, 0.0f, 0.0f, 0.0f }, // black + { 0.5f, 0.0f, 0.0f, 1.0f }, // red + { 0.0f, 0.5f, 0.0f, 1.0f }, // green + { 0.5f, 0.5f, 0.0f, 1.0f }, // yellow + { 0.0f, 0.0f, 0.5f, 1.0f }, // blue + { 0.5f, 0.0f, 0.5f, 1.0f }, // magenta + { 0.0f, 0.5f, 0.5f, 1.0f }, // cyan + { 0.75f, 0.75f, 0.75f, 1.0f }, // white + + { 0.5f, 0.5f, 0.5f, 1.0f }, // bright black + { 1.0f, 0.0f, 0.0f, 1.0f }, // bright red + { 0.0f, 1.0f, 0.0f, 1.0f }, // bright green + { 1.0f, 1.0f, 0.0f, 1.0f }, // bright yellow + { 0.0f, 0.0f, 1.0f, 1.0f }, // bright blue + { 1.0f, 0.0f, 1.0f, 1.0f }, // bright magenta + { 0.0f, 1.0f, 1.0f, 1.0f }, // bright cyan + { 1.0f, 1.0f, 1.0f, 1.0f }, // bright white + + { 0.0f, 0.0f, 0.0f, 0.0f }, // faint black + { 0.25f, 0.0f, 0.0f, 1.0f }, // faint red + { 0.0f, 0.25f, 0.0f, 1.0f }, // faint green + { 0.25f, 0.25f, 0.0f, 1.0f }, // faint yellow + { 0.0f, 0.0f, 0.25f, 1.0f }, // faint blue + { 0.25f, 0.0f, 0.25f, 1.0f }, // faint magenta + { 0.0f, 0.25f, 0.25f, 1.0f }, // faint cyan + { 0.375f, 0.375f, 0.375f, 1.0f }, // faint white +}; + +typedef struct { + uint16_t tileId; + uint8_t frontPal; + uint8_t backPal; +} ConsoleChar; + +static const DkVtxAttribState g_attribState[] = { + { .bufferId=0, .isFixed=0, .offset=offsetof(ConsoleChar,tileId), .size=DkVtxAttribSize_1x16, .type=DkVtxAttribType_Uscaled, .isBgra=0 }, + { .bufferId=0, .isFixed=0, .offset=offsetof(ConsoleChar,frontPal), .size=DkVtxAttribSize_2x8, .type=DkVtxAttribType_Uint, .isBgra=0 }, +}; + +static const DkVtxBufferState g_vtxbufState[] = { + { .stride=sizeof(ConsoleChar), .divisor=1 }, +}; + +struct GpuRenderer { + ConsoleRenderer base; + + bool initialized; + + DkDevice device; + DkQueue queue; + + DkMemBlock imageMemBlock; + DkMemBlock codeMemBlock; + DkMemBlock dataMemBlock; + + DkSwapchain swapchain; + DkImage framebuffers[FB_NUM]; + DkImage tileset; + ConsoleChar* charBuf; + + uint32_t codeMemOffset; + DkShader vertexShader; + DkShader fragmentShader; + + DkCmdBuf cmdbuf; + DkCmdList cmdsBindFramebuffer[FB_NUM]; + DkCmdList cmdsRender; + + DkFence lastRenderFence; +}; + +static struct GpuRenderer* GpuRenderer(PrintConsole* con) +{ + return (struct GpuRenderer*)con->renderer; +} + +static void GpuRenderer_destroy(struct GpuRenderer* r) +{ + // Make sure the queue is idle before destroying anything + dkQueueWaitIdle(r->queue); + + // Destroy all the resources we've created + dkQueueDestroy(r->queue); + dkCmdBufDestroy(r->cmdbuf); + dkSwapchainDestroy(r->swapchain); + dkMemBlockDestroy(r->dataMemBlock); + dkMemBlockDestroy(r->codeMemBlock); + dkMemBlockDestroy(r->imageMemBlock); + dkDeviceDestroy(r->device); + + // Clear out all state + memset(&r->initialized, 0, sizeof(*r) - offsetof(struct GpuRenderer, initialized)); +} + +// Simple function for loading a shader from the filesystem +static void GpuRenderer_loadShader(struct GpuRenderer* r, DkShader* pShader, const char* path) +{ + // Open the file, and retrieve its size + FILE* f = fopen(path, "rb"); + fseek(f, 0, SEEK_END); + uint32_t size = ftell(f); + rewind(f); + + // Look for a spot in the code memory block for loading this shader. Note that + // we are just using a simple incremental offset; this isn't a general purpose + // allocation algorithm. + uint32_t codeOffset = r->codeMemOffset; + r->codeMemOffset += (size + DK_SHADER_CODE_ALIGNMENT - 1) &~ (DK_SHADER_CODE_ALIGNMENT - 1); + + // Read the file into memory, and close the file + fread((uint8_t*)dkMemBlockGetCpuAddr(r->codeMemBlock) + codeOffset, size, 1, f); + fclose(f); + + // Initialize the user provided shader object with the code we've just loaded + DkShaderMaker shaderMaker; + dkShaderMakerDefaults(&shaderMaker, r->codeMemBlock, codeOffset); + dkShaderInitialize(pShader, &shaderMaker); +} + +static bool GpuRenderer_init(PrintConsole* con) +{ + struct GpuRenderer* r = GpuRenderer(con); + + if (r->initialized) { + // We're already initialized + return true; + } + + // Create the deko3d device, which is the root object + DkDeviceMaker deviceMaker; + dkDeviceMakerDefaults(&deviceMaker); + r->device = dkDeviceCreate(&deviceMaker); + + // Create the queue + DkQueueMaker queueMaker; + dkQueueMakerDefaults(&queueMaker, r->device); + queueMaker.flags = DkQueueFlags_Graphics; + r->queue = dkQueueCreate(&queueMaker); + + // Calculate required width/height for the framebuffers + u32 width = con->font.tileWidth * con->consoleWidth; + u32 height = con->font.tileHeight * con->consoleHeight; + u32 totalConSize = con->consoleWidth * con->consoleHeight; + + // Calculate layout for the framebuffers + DkImageLayoutMaker imageLayoutMaker; + dkImageLayoutMakerDefaults(&imageLayoutMaker, r->device); + imageLayoutMaker.flags = DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression; + imageLayoutMaker.format = DkImageFormat_RGBA8_Unorm; + imageLayoutMaker.dimensions[0] = width; + imageLayoutMaker.dimensions[1] = height; + + // Calculate layout for the framebuffers + DkImageLayout framebufferLayout; + dkImageLayoutInitialize(&framebufferLayout, &imageLayoutMaker); + + // Calculate layout for the tileset + dkImageLayoutMakerDefaults(&imageLayoutMaker, r->device); + imageLayoutMaker.type = DkImageType_2DArray; + imageLayoutMaker.format = DkImageFormat_R32_Float; + imageLayoutMaker.dimensions[0] = con->font.tileWidth; + imageLayoutMaker.dimensions[1] = con->font.tileHeight; + imageLayoutMaker.dimensions[2] = con->font.numChars; + + // Calculate layout for the tileset + DkImageLayout tilesetLayout; + dkImageLayoutInitialize(&tilesetLayout, &imageLayoutMaker); + + // Retrieve necessary size and alignment for the framebuffers + uint32_t framebufferSize = dkImageLayoutGetSize(&framebufferLayout); + uint32_t framebufferAlign = dkImageLayoutGetAlignment(&framebufferLayout); + framebufferSize = (framebufferSize + framebufferAlign - 1) &~ (framebufferAlign - 1); + + // Retrieve necessary size and alignment for the tileset + uint32_t tilesetSize = dkImageLayoutGetSize(&tilesetLayout); + uint32_t tilesetAlign = dkImageLayoutGetAlignment(&tilesetLayout); + tilesetSize = (tilesetSize + tilesetAlign - 1) &~ (tilesetAlign - 1); + + // Create a memory block that will host the framebuffers and the tileset + DkMemBlockMaker memBlockMaker; + dkMemBlockMakerDefaults(&memBlockMaker, r->device, FB_NUM*framebufferSize + tilesetSize); + memBlockMaker.flags = DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image; + r->imageMemBlock = dkMemBlockCreate(&memBlockMaker); + + // Initialize the framebuffers with the layout and backing memory we've just created + DkImage const* swapchainImages[FB_NUM]; + for (unsigned i = 0; i < FB_NUM; i ++) { + swapchainImages[i] = &r->framebuffers[i]; + dkImageInitialize(&r->framebuffers[i], &framebufferLayout, r->imageMemBlock, i*framebufferSize); + } + + // Create a swapchain out of the framebuffers we've just initialized + DkSwapchainMaker swapchainMaker; + dkSwapchainMakerDefaults(&swapchainMaker, r->device, nwindowGetDefault(), swapchainImages, FB_NUM); + r->swapchain = dkSwapchainCreate(&swapchainMaker); + + // Initialize the tileset + dkImageInitialize(&r->tileset, &tilesetLayout, r->imageMemBlock, FB_NUM*framebufferSize); + + // Create a memory block onto which we will load shader code + dkMemBlockMakerDefaults(&memBlockMaker, r->device, CODEMEMSIZE); + memBlockMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code; + r->codeMemBlock = dkMemBlockCreate(&memBlockMaker); + r->codeMemOffset = 0; + + // Load our shaders (both vertex and fragment) + romfsInit(); + GpuRenderer_loadShader(r, &r->vertexShader, "romfs:/shaders/console_vsh.dksh"); + GpuRenderer_loadShader(r, &r->fragmentShader, "romfs:/shaders/console_fsh.dksh"); + + // Generate the descriptors + struct { + DkImageDescriptor images[NUM_IMAGE_SLOTS]; + DkSamplerDescriptor samplers[NUM_SAMPLER_SLOTS]; + } descriptors; + + // Generate a image descriptor for the tileset + DkImageView tilesetView; + dkImageViewDefaults(&tilesetView, &r->tileset); + dkImageDescriptorInitialize(&descriptors.images[0], &tilesetView, false, false); + + // Generate a sampler descriptor for the tileset + DkSampler sampler; + dkSamplerDefaults(&sampler); + sampler.wrapMode[0] = DkWrapMode_ClampToEdge; + sampler.wrapMode[1] = DkWrapMode_ClampToEdge; + sampler.minFilter = DkFilter_Nearest; + sampler.magFilter = DkFilter_Nearest; + dkSamplerDescriptorInitialize(&descriptors.samplers[0], &sampler); + + uint32_t descriptorsOffset = CMDMEMSIZE; + uint32_t configOffset = (descriptorsOffset + sizeof(descriptors) + DK_UNIFORM_BUF_ALIGNMENT - 1) &~ (DK_UNIFORM_BUF_ALIGNMENT - 1); + uint32_t configSize = (sizeof(ConsoleConfig) + DK_UNIFORM_BUF_ALIGNMENT - 1) &~ (DK_UNIFORM_BUF_ALIGNMENT - 1); + + uint32_t charBufOffset = configOffset + configSize; + uint32_t charBufSize = totalConSize * sizeof(ConsoleChar); + + // Create a memory block which will be used for recording command lists using a command buffer + dkMemBlockMakerDefaults(&memBlockMaker, r->device, + (charBufOffset + charBufSize + DK_MEMBLOCK_ALIGNMENT - 1) &~ (DK_MEMBLOCK_ALIGNMENT - 1) + ); + memBlockMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached; + r->dataMemBlock = dkMemBlockCreate(&memBlockMaker); + + // Create a command buffer object + DkCmdBufMaker cmdbufMaker; + dkCmdBufMakerDefaults(&cmdbufMaker, r->device); + r->cmdbuf = dkCmdBufCreate(&cmdbufMaker); + + // Feed our memory to the command buffer so that we can start recording commands + dkCmdBufAddMemory(r->cmdbuf, r->dataMemBlock, 0, CMDMEMSIZE); + + // Create a temporary buffer that will hold the tileset + dkMemBlockMakerDefaults(&memBlockMaker, r->device, + (sizeof(float)*con->font.tileWidth*con->font.tileHeight*con->font.numChars + DK_MEMBLOCK_ALIGNMENT - 1) &~ (DK_MEMBLOCK_ALIGNMENT - 1) + ); + memBlockMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached; + DkMemBlock scratchMemBlock = dkMemBlockCreate(&memBlockMaker); + float* scratchMem = (float*)dkMemBlockGetCpuAddr(scratchMemBlock); + + // Unpack 1bpp tileset into a texture image the GPU can read + unsigned packedTileWidth = (con->font.tileWidth+7)/8; + for (unsigned tile = 0; tile < con->font.numChars; tile ++) { + const uint8_t* data = (const uint8_t*)con->font.gfx + con->font.tileHeight*packedTileWidth*tile; + for (unsigned y = 0; y < con->font.tileHeight; y ++) { + const uint8_t* row = &data[packedTileWidth*(y+1)]; + uint8_t c = 0; + for (unsigned x = 0; x < con->font.tileWidth; x ++) { + if (!(x & 7)) + c = *--row; + *scratchMem++ = (c & 0x80) ? 1.0f : 0.0f; + c <<= 1; + } + } + } + + // Set up configuration + DkGpuAddr configAddr = dkMemBlockGetGpuAddr(r->dataMemBlock) + configOffset; + ConsoleConfig consoleConfig = {}; + consoleConfig.dimensions[0] = width; + consoleConfig.dimensions[1] = height; + consoleConfig.dimensions[2] = con->consoleWidth; + consoleConfig.dimensions[3] = con->consoleHeight; + memcpy(consoleConfig.vertices, g_vertexData, sizeof(g_vertexData)); + memcpy(consoleConfig.palettes, g_paletteData, sizeof(g_paletteData)); + + // Generate a temporary command list for uploading stuff and run it + DkGpuAddr descriptorSet = dkMemBlockGetGpuAddr(r->dataMemBlock) + descriptorsOffset; + DkCopyBuf copySrc = { dkMemBlockGetGpuAddr(scratchMemBlock), 0, 0 }; + DkImageRect copyDst = { 0, 0, 0, con->font.tileWidth, con->font.tileHeight, con->font.numChars }; + dkCmdBufPushData(r->cmdbuf, descriptorSet, &descriptors, sizeof(descriptors)); + dkCmdBufPushConstants(r->cmdbuf, configAddr, configSize, 0, sizeof(consoleConfig), &consoleConfig); + dkCmdBufBindImageDescriptorSet(r->cmdbuf, descriptorSet, NUM_IMAGE_SLOTS); + dkCmdBufBindSamplerDescriptorSet(r->cmdbuf, descriptorSet + NUM_IMAGE_SLOTS*sizeof(DkImageDescriptor), NUM_SAMPLER_SLOTS); + dkCmdBufCopyBufferToImage(r->cmdbuf, ©Src, &tilesetView, ©Dst, 0); + dkQueueSubmitCommands(r->queue, dkCmdBufFinishList(r->cmdbuf)); + dkQueueFlush(r->queue); + dkQueueWaitIdle(r->queue); + dkCmdBufClear(r->cmdbuf); + + // Destroy the scratch memory block since we don't need it anymore + dkMemBlockDestroy(scratchMemBlock); + + // Retrieve the address of the character buffer + DkGpuAddr charBufAddr = dkMemBlockGetGpuAddr(r->dataMemBlock) + charBufOffset; + r->charBuf = (ConsoleChar*)((uint8_t*)dkMemBlockGetCpuAddr(r->dataMemBlock) + charBufOffset); + memset(r->charBuf, 0, charBufSize); + + // Generate a command list for each framebuffer, which will bind each of them as a render target + for (unsigned i = 0; i < FB_NUM; i ++) { + DkImageView imageView; + dkImageViewDefaults(&imageView, &r->framebuffers[i]); + dkCmdBufBindRenderTarget(r->cmdbuf, &imageView, NULL); + r->cmdsBindFramebuffer[i] = dkCmdBufFinishList(r->cmdbuf); + } + + // Declare structs that will be used for binding state + DkViewport viewport = { 0.0f, 0.0f, (float)width, (float)height, 0.0f, 1.0f }; + DkScissor scissor = { 0, 0, width, height }; + DkShader const* shaders[] = { &r->vertexShader, &r->fragmentShader }; + DkRasterizerState rasterizerState; + DkColorState colorState; + DkColorWriteState colorWriteState; + + // Initialize state structs with the deko3d defaults + dkRasterizerStateDefaults(&rasterizerState); + dkColorStateDefaults(&colorState); + dkColorWriteStateDefaults(&colorWriteState); + + rasterizerState.fillRectangleEnable = true; + colorState.alphaCompareOp = DkCompareOp_Greater; + + // Generate the main rendering command list + dkCmdBufSetViewports(r->cmdbuf, 0, &viewport, 1); + dkCmdBufSetScissors(r->cmdbuf, 0, &scissor, 1); + //dkCmdBufClearColorFloat(r->cmdbuf, 0, DkColorMask_RGBA, 0.125f, 0.294f, 0.478f, 0.0f); + dkCmdBufClearColorFloat(r->cmdbuf, 0, DkColorMask_RGBA, 0.0f, 0.0f, 0.0f, 0.0f); + dkCmdBufBindShaders(r->cmdbuf, DkStageFlag_GraphicsMask, shaders, sizeof(shaders)/sizeof(shaders[0])); + dkCmdBufBindRasterizerState(r->cmdbuf, &rasterizerState); + dkCmdBufBindColorState(r->cmdbuf, &colorState); + dkCmdBufBindColorWriteState(r->cmdbuf, &colorWriteState); + dkCmdBufBindUniformBuffer(r->cmdbuf, DkStage_Vertex, 0, configAddr, configSize); + dkCmdBufBindTexture(r->cmdbuf, DkStage_Fragment, 0, dkMakeTextureHandle(0, 0)); + dkCmdBufBindVtxAttribState(r->cmdbuf, g_attribState, sizeof(g_attribState)/sizeof(g_attribState[0])); + dkCmdBufBindVtxBufferState(r->cmdbuf, g_vtxbufState, sizeof(g_vtxbufState)/sizeof(g_vtxbufState[0])); + dkCmdBufBindVtxBuffer(r->cmdbuf, 0, charBufAddr, charBufSize); + dkCmdBufSetAlphaRef(r->cmdbuf, 0.0f); + dkCmdBufDraw(r->cmdbuf, DkPrimitive_Triangles, 3, totalConSize, 0, 0); + r->cmdsRender = dkCmdBufFinishList(r->cmdbuf); + + r->initialized = true; + return true; +} + +static void GpuRenderer_deinit(PrintConsole* con) +{ + struct GpuRenderer* r = GpuRenderer(con); + + if (r->initialized) { + GpuRenderer_destroy(r); + } +} + +static void GpuRenderer_drawChar(PrintConsole* con, int x, int y, int c) +{ + struct GpuRenderer* r = GpuRenderer(con); + + int writingColor = con->fg; + int screenColor = con->bg; + + if (con->flags & CONSOLE_COLOR_BOLD) { + writingColor += 8; + } else if (con->flags & CONSOLE_COLOR_FAINT) { + writingColor += 16; + } + + if (con->flags & CONSOLE_COLOR_REVERSE) { + int tmp = writingColor; + writingColor = screenColor; + screenColor = tmp; + } + + // Wait for the fence + dkFenceWait(&r->lastRenderFence, UINT64_MAX); + + ConsoleChar* pos = &r->charBuf[y*con->consoleWidth+x]; + pos->tileId = c; + pos->frontPal = writingColor; + pos->backPal = screenColor; +} + +static void GpuRenderer_scrollWindow(PrintConsole* con) +{ + struct GpuRenderer* r = GpuRenderer(con); + + // Wait for the fence + dkFenceWait(&r->lastRenderFence, UINT64_MAX); + + // Perform the scrolling + for (int y = 0; y < con->windowHeight-1; y ++) { + memcpy( + &r->charBuf[(con->windowY+y+0)*con->consoleWidth + con->windowX], + &r->charBuf[(con->windowY+y+1)*con->consoleWidth + con->windowX], + sizeof(ConsoleChar)*con->windowWidth); + } +} + +static void GpuRenderer_flushAndSwap(PrintConsole* con) +{ + struct GpuRenderer* r = GpuRenderer(con); + + // Acquire a framebuffer from the swapchain (and wait for it to be available) + int slot = dkQueueAcquireImage(r->queue, r->swapchain); + + // Run the command list that binds said framebuffer as a render target + dkQueueSubmitCommands(r->queue, r->cmdsBindFramebuffer[slot]); + + // Run the main rendering command list + dkQueueSubmitCommands(r->queue, r->cmdsRender); + + // Signal the fence + dkQueueSignalFence(r->queue, &r->lastRenderFence, false); + + // Now that we are done rendering, present it to the screen + dkQueuePresentImage(r->queue, r->swapchain, slot); +} + +static struct GpuRenderer s_gpuRenderer = +{ + { + GpuRenderer_init, + GpuRenderer_deinit, + GpuRenderer_drawChar, + GpuRenderer_scrollWindow, + GpuRenderer_flushAndSwap, + } +}; + +ConsoleRenderer* getDefaultConsoleRenderer(void) +{ + return &s_gpuRenderer.base; +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/main.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/main.cpp new file mode 100644 index 00000000..1cab75b9 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/main.cpp @@ -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 <http://www.gnu.org/licenses/>. + */ +#include <haze.hpp> +#include <haze/console_main_loop.hpp> + +int main(int argc, char **argv) { + /* Load device firmware version and serial number. */ + HAZE_R_ABORT_UNLESS(haze::LoadDeviceProperties()); + + /* Run the application. */ + haze::ConsoleMainLoop::RunApplication(); + + /* Return to the loader. */ + return 0; +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/ptp_object_database.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/ptp_object_database.cpp new file mode 100644 index 00000000..7e71a307 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/ptp_object_database.cpp @@ -0,0 +1,152 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <haze.hpp> + +namespace haze { + + void PtpObjectDatabase::Initialize(PtpObjectHeap *object_heap) { + m_object_heap = object_heap; + m_object_heap->Initialize(); + + std::construct_at(std::addressof(m_name_tree)); + std::construct_at(std::addressof(m_object_id_tree)); + + m_next_object_id = 1; + } + + void PtpObjectDatabase::Finalize() { + std::destroy_at(std::addressof(m_object_id_tree)); + std::destroy_at(std::addressof(m_name_tree)); + + m_next_object_id = 0; + + m_object_heap->Finalize(); + m_object_heap = nullptr; + } + + Result PtpObjectDatabase::CreateOrFindObject(const char *parent_name, const char *name, u32 parent_id, PtpObject **out_object) { + constexpr auto separator = "/"; + + /* Calculate length of the new name with null terminator. */ + const size_t parent_name_len = util::Strlen(parent_name); + const size_t separator_len = util::Strlen(separator); + const size_t name_len = util::Strlen(name); + const size_t terminator_len = 1; + const size_t alloc_len = sizeof(PtpObject) + parent_name_len + separator_len + name_len + terminator_len; + + /* Allocate memory for the object. */ + PtpObject * const object = m_object_heap->Allocate<PtpObject>(alloc_len); + R_UNLESS(object != nullptr, haze::ResultOutOfMemory()); + + /* Build the object name. */ + std::strncpy(object->m_name, parent_name, parent_name_len + terminator_len); + std::strncpy(object->m_name + parent_name_len, separator, separator_len + terminator_len); + std::strncpy(object->m_name + parent_name_len + separator_len, name, name_len + terminator_len); + + { + /* Ensure we maintain a clean state on failure. */ + auto guard = SCOPE_GUARD { m_object_heap->Deallocate(object, alloc_len); }; + + /* Check if an object with this name already exists. If it does, we can just return it here. */ + if (auto * const existing = this->GetObjectByName(object->GetName()); existing != nullptr) { + *out_object = existing; + R_SUCCEED(); + } + + /* Persist the reference to the object. */ + guard.Cancel(); + } + + /* Set object properties. */ + object->m_parent_id = parent_id; + object->m_object_id = 0; + + /* Set output. */ + *out_object = object; + + /* We succeeded. */ + R_SUCCEED(); + } + + void PtpObjectDatabase::RegisterObject(PtpObject *object, u32 desired_id) { + /* If the object is already registered, skip registration. */ + if (object->GetIsRegistered()) { + return; + } + + /* Set desired object ID. */ + if (desired_id == 0) { + desired_id = m_next_object_id++; + } + + /* Insert object into trees. */ + object->Register(desired_id); + m_object_id_tree.insert(*object); + m_name_tree.insert(*object); + } + + void PtpObjectDatabase::UnregisterObject(PtpObject *object) { + /* If the object is not registered, skip trying to unregister. */ + if (!object->GetIsRegistered()) { + return; + } + + /* Remove object from trees. */ + m_object_id_tree.erase(m_object_id_tree.iterator_to(*object)); + m_name_tree.erase(m_name_tree.iterator_to(*object)); + object->Unregister(); + } + + void PtpObjectDatabase::DeleteObject(PtpObject *object) { + /* Unregister the object as required. */ + this->UnregisterObject(object); + + /* Free the object. */ + m_object_heap->Deallocate(object, sizeof(PtpObject) + std::strlen(object->GetName()) + 1); + } + + Result PtpObjectDatabase::CreateAndRegisterObjectId(const char *parent_name, const char *name, u32 parent_id, u32 *out_object_id) { + /* Try to create the object. */ + PtpObject *object; + R_TRY(this->CreateOrFindObject(parent_name, name, parent_id, std::addressof(object))); + + /* We succeeded, so register it. */ + this->RegisterObject(object); + + /* Set the output ID. */ + *out_object_id = object->GetObjectId(); + + R_SUCCEED(); + } + + PtpObject *PtpObjectDatabase::GetObjectById(u32 object_id) { + /* Find in ID mapping. */ + if (auto it = m_object_id_tree.find_key(object_id); it != m_object_id_tree.end()) { + return std::addressof(*it); + } else { + return nullptr; + } + } + + PtpObject *PtpObjectDatabase::GetObjectByName(const char *name) { + /* Find in name mapping. */ + if (auto it = m_name_tree.find_key(name); it != m_name_tree.end()) { + return std::addressof(*it); + } else { + return nullptr; + } + } +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/ptp_object_heap.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/ptp_object_heap.cpp new file mode 100644 index 00000000..c3d94734 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/ptp_object_heap.cpp @@ -0,0 +1,69 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <haze.hpp> + +namespace haze { + + namespace { + + /* Allow 30MiB for use by libnx. */ + static constexpr size_t LibnxReservedMemorySize = 30_MB; + + } + + void PtpObjectHeap::Initialize() { + /* If we're already initialized, skip re-initialization. */ + if (m_heap_block_size != 0) { + return; + } + + /* Estimate how much memory we can reserve. */ + size_t mem_used = 0; + HAZE_R_ABORT_UNLESS(svcGetInfo(std::addressof(mem_used), InfoType_UsedMemorySize, svc::CurrentProcess, 0)); + HAZE_ASSERT(mem_used > LibnxReservedMemorySize); + mem_used -= LibnxReservedMemorySize; + + /* Calculate size of blocks. */ + m_heap_block_size = mem_used / NumHeapBlocks; + HAZE_ASSERT(m_heap_block_size > 0); + + /* Allocate the memory. */ + for (size_t i = 0; i < NumHeapBlocks; i++) { + m_heap_blocks[i] = std::malloc(m_heap_block_size); + HAZE_ASSERT(m_heap_blocks[i] != nullptr); + } + + /* Set the address to allocate from. */ + m_next_address = m_heap_blocks[0]; + } + + void PtpObjectHeap::Finalize() { + if (m_heap_block_size == 0) { + return; + } + + /* Tear down the heap, allowing a subsequent call to Initialize() if desired. */ + for (size_t i = 0; i < NumHeapBlocks; i++) { + std::free(m_heap_blocks[i]); + m_heap_blocks[i] = nullptr; + } + + m_next_address = nullptr; + m_heap_block_size = 0; + m_current_heap_block = 0; + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/ptp_responder.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/ptp_responder.cpp new file mode 100644 index 00000000..8194aaf7 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/ptp_responder.cpp @@ -0,0 +1,173 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <haze.hpp> +#include <haze/ptp_data_builder.hpp> +#include <haze/ptp_data_parser.hpp> +#include <haze/ptp_responder_types.hpp> + +namespace haze { + + namespace { + + PtpBuffers *GetBuffers() { + static constinit PtpBuffers buffers = {}; + return std::addressof(buffers); + } + + } + + Result PtpResponder::Initialize(EventReactor *reactor, PtpObjectHeap *object_heap) { + m_object_heap = object_heap; + m_buffers = GetBuffers(); + + /* Configure fs proxy. */ + m_fs.Initialize(reactor, fsdevGetDeviceFileSystem("sdmc")); + + R_RETURN(m_usb_server.Initialize(std::addressof(MtpInterfaceInfo), SwitchMtpIdVendor, SwitchMtpIdProduct, reactor)); + } + + void PtpResponder::Finalize() { + m_usb_server.Finalize(); + m_fs.Finalize(); + } + + Result PtpResponder::LoopProcess() { + while (true) { + /* Try to handle a request. */ + R_TRY_CATCH(this->HandleRequest()) { + R_CATCH(haze::ResultStopRequested, haze::ResultFocusLost) { + /* If we encountered a stop condition, we're done.*/ + R_THROW(R_CURRENT_RESULT); + } + R_CATCH_ALL() { + /* On other failures, try to handle another request. */ + continue; + } + } R_END_TRY_CATCH; + + /* Otherwise, handle the next request. */ + /* ... */ + } + } + + Result PtpResponder::HandleRequest() { + ON_RESULT_FAILURE { + /* For general failure modes, the failure is unrecoverable. Close the session. */ + this->ForceCloseSession(); + }; + + R_TRY_CATCH(this->HandleRequestImpl()) { + R_CATCH(haze::ResultUnknownRequestType) { + R_TRY(this->WriteResponse(PtpResponseCode_GeneralError)); + } + R_CATCH(haze::ResultSessionNotOpen) { + R_TRY(this->WriteResponse(PtpResponseCode_SessionNotOpen)); + } + R_CATCH(haze::ResultOperationNotSupported) { + R_TRY(this->WriteResponse(PtpResponseCode_OperationNotSupported)); + } + R_CATCH(haze::ResultInvalidStorageId) { + R_TRY(this->WriteResponse(PtpResponseCode_InvalidStorageId)); + } + R_CATCH(haze::ResultInvalidObjectId) { + R_TRY(this->WriteResponse(PtpResponseCode_InvalidObjectHandle)); + } + R_CATCH(haze::ResultUnknownPropertyCode) { + R_TRY(this->WriteResponse(PtpResponseCode_MtpObjectPropNotSupported)); + } + R_CATCH(haze::ResultInvalidPropertyValue) { + R_TRY(this->WriteResponse(PtpResponseCode_MtpInvalidObjectPropValue)); + } + R_CATCH(haze::ResultGroupSpecified) { + R_TRY(this->WriteResponse(PtpResponseCode_MtpSpecificationByGroupUnsupported)); + } + R_CATCH(haze::ResultDepthSpecified) { + R_TRY(this->WriteResponse(PtpResponseCode_MtpSpecificationByDepthUnsupported)); + } + R_CATCH(haze::ResultInvalidArgument) { + R_TRY(this->WriteResponse(PtpResponseCode_GeneralError)); + } + R_CATCH_MODULE(fs) { + /* Errors from fs are typically recoverable. */ + R_TRY(this->WriteResponse(PtpResponseCode_GeneralError)); + } + } R_END_TRY_CATCH; + + R_SUCCEED(); + } + + Result PtpResponder::HandleRequestImpl() { + PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server)); + R_TRY(dp.Read(std::addressof(m_request_header))); + + switch (m_request_header.type) { + case PtpUsbBulkContainerType_Command: R_RETURN(this->HandleCommandRequest(dp)); + default: R_THROW(haze::ResultUnknownRequestType()); + } + } + + Result PtpResponder::HandleCommandRequest(PtpDataParser &dp) { + if (!m_session_open && m_request_header.code != PtpOperationCode_OpenSession && m_request_header.code != PtpOperationCode_GetDeviceInfo) { + R_THROW(haze::ResultSessionNotOpen()); + } + + switch (m_request_header.code) { + case PtpOperationCode_GetDeviceInfo: R_RETURN(this->GetDeviceInfo(dp)); break; + case PtpOperationCode_OpenSession: R_RETURN(this->OpenSession(dp)); break; + case PtpOperationCode_CloseSession: R_RETURN(this->CloseSession(dp)); break; + case PtpOperationCode_GetStorageIds: R_RETURN(this->GetStorageIds(dp)); break; + case PtpOperationCode_GetStorageInfo: R_RETURN(this->GetStorageInfo(dp)); break; + case PtpOperationCode_GetObjectHandles: R_RETURN(this->GetObjectHandles(dp)); break; + case PtpOperationCode_GetObjectInfo: R_RETURN(this->GetObjectInfo(dp)); break; + case PtpOperationCode_GetObject: R_RETURN(this->GetObject(dp)); break; + case PtpOperationCode_SendObjectInfo: R_RETURN(this->SendObjectInfo(dp)); break; + case PtpOperationCode_SendObject: R_RETURN(this->SendObject(dp)); break; + case PtpOperationCode_DeleteObject: R_RETURN(this->DeleteObject(dp)); break; + case PtpOperationCode_MtpGetObjectPropsSupported: R_RETURN(this->GetObjectPropsSupported(dp)); break; + case PtpOperationCode_MtpGetObjectPropDesc: R_RETURN(this->GetObjectPropDesc(dp)); break; + case PtpOperationCode_MtpGetObjectPropValue: R_RETURN(this->GetObjectPropValue(dp)); break; + case PtpOperationCode_MtpSetObjectPropValue: R_RETURN(this->SetObjectPropValue(dp)); break; + case PtpOperationCode_MtpGetObjPropList: R_RETURN(this->GetObjectPropList(dp)); break; + case PtpOperationCode_AndroidGetPartialObject64: R_RETURN(this->GetPartialObject64(dp)); break; + case PtpOperationCode_AndroidSendPartialObject: R_RETURN(this->SendPartialObject(dp)); break; + case PtpOperationCode_AndroidTruncateObject: R_RETURN(this->TruncateObject(dp)); break; + case PtpOperationCode_AndroidBeginEditObject: R_RETURN(this->BeginEditObject(dp)); break; + case PtpOperationCode_AndroidEndEditObject: R_RETURN(this->EndEditObject(dp)); break; + default: R_THROW(haze::ResultOperationNotSupported()); + } + } + + void PtpResponder::ForceCloseSession() { + if (m_session_open) { + m_session_open = false; + m_object_database.Finalize(); + } + } + + Result PtpResponder::WriteResponse(PtpResponseCode code, const void* data, size_t size) { + PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server)); + R_TRY(db.AddResponseHeader(m_request_header, code, size)); + R_TRY(db.AddBuffer(reinterpret_cast<const u8*>(data), size)); + R_RETURN(db.Commit()); + } + + Result PtpResponder::WriteResponse(PtpResponseCode code) { + PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server)); + R_TRY(db.AddResponseHeader(m_request_header, code, 0)); + R_RETURN(db.Commit()); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/ptp_responder_android_operations.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/ptp_responder_android_operations.cpp new file mode 100644 index 00000000..aef13011 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/ptp_responder_android_operations.cpp @@ -0,0 +1,203 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <haze.hpp> +#include <haze/ptp_data_builder.hpp> +#include <haze/ptp_data_parser.hpp> +#include <haze/ptp_responder_types.hpp> + +namespace haze { + + Result PtpResponder::GetPartialObject64(PtpDataParser &dp) { + PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server)); + + /* Get the object ID, offset, and size for the file we want to read. */ + u32 object_id, size; + u64 offset; + R_TRY(dp.Read(std::addressof(object_id))); + R_TRY(dp.Read(std::addressof(offset))); + R_TRY(dp.Read(std::addressof(size))); + R_TRY(dp.Finalize()); + + /* Check if we know about the object. If we don't, it's an error. */ + auto * const obj = m_object_database.GetObjectById(object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); + + /* Lock the object as a file. */ + FsFile file; + R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file))); + + /* Ensure we maintain a clean state on exit. */ + ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); }; + + /* Get the file's size. */ + s64 file_size = 0; + R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(file_size))); + + /* Ensure the requested offset and size are within range. */ + R_UNLESS(offset + size > offset, haze::ResultInvalidArgument()); + R_UNLESS(static_cast<u64>(file_size) <= offset + size, haze::ResultInvalidArgument()); + + /* Send the header and data size. */ + R_TRY(db.AddDataHeader(m_request_header, size)); + + /* Begin reading the file, writing data to the builder as we progress. */ + s64 size_remaining = size; + while (true) { + /* Get the next batch. */ + u64 bytes_to_read = std::min<s64>(FsBufferSize, size_remaining); + u64 bytes_read; + + R_TRY(m_fs.ReadFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, bytes_to_read, FsReadOption_None, std::addressof(bytes_read))); + + size_remaining -= bytes_read; + offset += bytes_read; + + /* Write to output. */ + R_TRY(db.AddBuffer(m_buffers->file_system_data_buffer, bytes_read)); + + /* If we read fewer bytes than the batch size, or have read enough data, we're done. */ + if (bytes_read < FsBufferSize || size_remaining == 0) { + break; + } + } + + /* Flush the data response. */ + R_TRY(db.Commit()); + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::SendPartialObject(PtpDataParser &rdp) { + /* Get the object ID, offset, and size for the file we want to write. */ + u32 object_id, size; + u64 offset; + R_TRY(rdp.Read(std::addressof(object_id))); + R_TRY(rdp.Read(std::addressof(size))); + R_TRY(rdp.Read(std::addressof(offset))); + R_TRY(rdp.Finalize()); + + /* Check if we know about the object. If we don't, it's an error. */ + auto * const obj = m_object_database.GetObjectById(m_send_object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); + + /* Lock the object as a file. */ + FsFile file; + R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write | FsOpenMode_Append, std::addressof(file))); + + /* Ensure we maintain a clean state on exit. */ + ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); }; + + /* Get the file's size. */ + s64 file_size = 0; + R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(file_size))); + + /* Ensure the requested offset and size are within range. */ + R_UNLESS(offset + size > offset, haze::ResultInvalidArgument()); + R_UNLESS(static_cast<u64>(file_size) <= offset, haze::ResultInvalidArgument()); + + /* Prepare a data parser for the data we are about to receive. */ + PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server)); + + /* Ensure we have a data header. */ + PtpUsbBulkContainer data_header; + R_TRY(dp.Read(std::addressof(data_header))); + R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType()); + R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported()); + R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported()); + + /* Begin writing to the filesystem. */ + s64 size_remaining = size; + while (true) { + /* Read as many bytes as we can. */ + u32 bytes_received; + const Result read_res = dp.ReadBuffer(m_buffers->file_system_data_buffer, FsBufferSize, std::addressof(bytes_received)); + + /* Write to the file. */ + u32 bytes_to_write = std::min<s64>(size_remaining, bytes_received); + R_TRY(m_fs.WriteFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, bytes_to_write, 0)); + + size_remaining -= bytes_to_write; + offset += bytes_to_write; + + /* If we received fewer bytes than the batch size, or have written enough data, we're done. */ + if (haze::ResultEndOfTransmission::Includes(read_res) || size_remaining == 0) { + break; + } + + R_TRY(read_res); + } + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::TruncateObject(PtpDataParser &dp) { + /* Get the object ID and size for the file we want to truncate. */ + u32 object_id; + u64 size; + R_TRY(dp.Read(std::addressof(object_id))); + R_TRY(dp.Read(std::addressof(size))); + R_TRY(dp.Finalize()); + + /* Check if we know about the object. If we don't, it's an error. */ + auto * const obj = m_object_database.GetObjectById(object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); + + /* Lock the object as a file. */ + FsFile file; + R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write, std::addressof(file))); + + /* Ensure we maintain a clean state on exit. */ + ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); }; + + /* Truncate the file. */ + R_TRY(m_fs.SetFileSize(std::addressof(file), size)); + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::BeginEditObject(PtpDataParser &dp) { + /* Get the object ID we are going to begin editing. */ + u32 object_id; + R_TRY(dp.Read(std::addressof(object_id))); + R_TRY(dp.Finalize()); + + /* Check if we know about the object. If we don't, it's an error. */ + auto * const obj = m_object_database.GetObjectById(object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); + + /* We don't implement transactions, so write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::EndEditObject(PtpDataParser &dp) { + /* Get the object ID we are going to finish editing. */ + u32 object_id; + R_TRY(dp.Read(std::addressof(object_id))); + R_TRY(dp.Finalize()); + + /* Check if we know about the object. If we don't, it's an error. */ + auto * const obj = m_object_database.GetObjectById(object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); + + /* We don't implement transactions, so write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/ptp_responder_mtp_operations.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/ptp_responder_mtp_operations.cpp new file mode 100644 index 00000000..eea3e5f4 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/ptp_responder_mtp_operations.cpp @@ -0,0 +1,418 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <haze.hpp> +#include <haze/ptp_data_builder.hpp> +#include <haze/ptp_data_parser.hpp> +#include <haze/ptp_responder_types.hpp> + +namespace haze { + + Result PtpResponder::GetObjectPropsSupported(PtpDataParser &dp) { + R_TRY(dp.Finalize()); + + PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server)); + + /* Write information about all object properties we can support. */ + R_TRY(db.WriteVariableLengthData(m_request_header, [&] { + R_RETURN(db.AddArray(SupportedObjectProperties, util::size(SupportedObjectProperties))); + })); + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::GetObjectPropDesc(PtpDataParser &dp) { + PtpObjectPropertyCode property_code; + u16 object_format; + + R_TRY(dp.Read(std::addressof(property_code))); + R_TRY(dp.Read(std::addressof(object_format))); + R_TRY(dp.Finalize()); + + /* Ensure we have a valid property code before continuing. */ + R_UNLESS(IsSupportedObjectPropertyCode(property_code), haze::ResultUnknownPropertyCode()); + + /* Begin writing information about the property code. */ + PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server)); + + R_TRY(db.WriteVariableLengthData(m_request_header, [&] { + R_TRY(db.Add(property_code)); + + /* Each property code corresponds to a different pattern, which contains the data type, */ + /* whether the property can be set for an object, and the default value of the property. */ + switch (property_code) { + case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier: + { + R_TRY(db.Add(PtpDataTypeCode_U128)); + R_TRY(db.Add(PtpPropertyGetSetFlag_Get)); + R_TRY(db.Add<u128>(0)); + } + case PtpObjectPropertyCode_ObjectSize: + { + R_TRY(db.Add(PtpDataTypeCode_U64)); + R_TRY(db.Add(PtpPropertyGetSetFlag_Get)); + R_TRY(db.Add<u64>(0)); + } + break; + case PtpObjectPropertyCode_StorageId: + case PtpObjectPropertyCode_ParentObject: + { + R_TRY(db.Add(PtpDataTypeCode_U32)); + R_TRY(db.Add(PtpPropertyGetSetFlag_Get)); + R_TRY(db.Add(StorageId_SdmcFs)); + } + break; + case PtpObjectPropertyCode_ObjectFormat: + { + R_TRY(db.Add(PtpDataTypeCode_U16)); + R_TRY(db.Add(PtpPropertyGetSetFlag_Get)); + R_TRY(db.Add(PtpObjectFormatCode_Undefined)); + } + break; + case PtpObjectPropertyCode_ObjectFileName: + { + R_TRY(db.Add(PtpDataTypeCode_String)); + R_TRY(db.Add(PtpPropertyGetSetFlag_GetSet)); + R_TRY(db.AddString("")); + } + break; + HAZE_UNREACHABLE_DEFAULT_CASE(); + } + + /* Group code is a required part of the response, but doesn't seem to be used for anything. */ + R_TRY(db.Add(PtpPropertyGroupCode_Default)); + + /* We don't use the form flag. */ + R_TRY(db.Add(PtpPropertyFormFlag_None)); + + R_SUCCEED(); + })); + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::GetObjectPropValue(PtpDataParser &dp) { + u32 object_id; + PtpObjectPropertyCode property_code; + + R_TRY(dp.Read(std::addressof(object_id))); + R_TRY(dp.Read(std::addressof(property_code))); + R_TRY(dp.Finalize()); + + /* Ensure we have a valid property code before continuing. */ + R_UNLESS(IsSupportedObjectPropertyCode(property_code), haze::ResultUnknownPropertyCode()); + + /* Check if we know about the object. If we don't, it's an error. */ + auto * const obj = m_object_database.GetObjectById(object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); + + /* Define helper for getting the object type. */ + const auto GetObjectType = [&] (FsDirEntryType *out_entry_type) { + R_RETURN(m_fs.GetEntryType(obj->GetName(), out_entry_type)); + }; + + /* Define helper for getting the object size. */ + const auto GetObjectSize = [&] (s64 *out_size) { + *out_size = 0; + + /* Check if this is a directory. */ + FsDirEntryType entry_type; + R_TRY(GetObjectType(std::addressof(entry_type))); + + /* If it is, we're done. */ + R_SUCCEED_IF(entry_type == FsDirEntryType_Dir); + + /* Otherwise, open as a file. */ + FsFile file; + R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file))); + + /* Ensure we maintain a clean state on exit. */ + ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); }; + + R_RETURN(m_fs.GetFileSize(std::addressof(file), out_size)); + }; + + /* Begin writing the requested object property. */ + PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server)); + + R_TRY(db.WriteVariableLengthData(m_request_header, [&] { + switch (property_code) { + case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier: + { + R_TRY(db.Add<u128>(object_id)); + } + break; + case PtpObjectPropertyCode_ObjectSize: + { + s64 size; + R_TRY(GetObjectSize(std::addressof(size))); + R_TRY(db.Add<u64>(size)); + } + break; + case PtpObjectPropertyCode_StorageId: + { + R_TRY(db.Add(StorageId_SdmcFs)); + } + break; + case PtpObjectPropertyCode_ParentObject: + { + R_TRY(db.Add(obj->GetParentId())); + } + break; + case PtpObjectPropertyCode_ObjectFormat: + { + FsDirEntryType entry_type; + R_TRY(GetObjectType(std::addressof(entry_type))); + R_TRY(db.Add(entry_type == FsDirEntryType_File ? PtpObjectFormatCode_Undefined : PtpObjectFormatCode_Association)); + } + break; + case PtpObjectPropertyCode_ObjectFileName: + { + R_TRY(db.AddString(std::strrchr(obj->GetName(), '/') + 1)); + } + break; + HAZE_UNREACHABLE_DEFAULT_CASE(); + } + + R_SUCCEED(); + })); + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::GetObjectPropList(PtpDataParser &dp) { + u32 object_id; + u32 object_format; + s32 property_code; + s32 group_code; + s32 depth; + + R_TRY(dp.Read(std::addressof(object_id))); + R_TRY(dp.Read(std::addressof(object_format))); + R_TRY(dp.Read(std::addressof(property_code))); + R_TRY(dp.Read(std::addressof(group_code))); + R_TRY(dp.Read(std::addressof(depth))); + R_TRY(dp.Finalize()); + + /* Ensure format is unspecified. */ + R_UNLESS(object_format == 0, haze::ResultInvalidArgument()); + + /* Ensure we have a valid property code. */ + R_UNLESS(property_code == -1 || IsSupportedObjectPropertyCode(PtpObjectPropertyCode(property_code)), haze::ResultUnknownPropertyCode()); + + /* Ensure group code is the default. */ + R_UNLESS(group_code == PtpPropertyGroupCode_Default, haze::ResultGroupSpecified()); + + /* Ensure depth is 0. */ + R_UNLESS(depth == 0, haze::ResultDepthSpecified()); + + /* Check if we know about the object. If we don't, it's an error. */ + auto * const obj = m_object_database.GetObjectById(object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); + + /* Define helper for getting the object type. */ + const auto GetObjectType = [&] (FsDirEntryType *out_entry_type) { + R_RETURN(m_fs.GetEntryType(obj->GetName(), out_entry_type)); + }; + + /* Define helper for getting the object size. */ + const auto GetObjectSize = [&] (s64 *out_size) { + *out_size = 0; + + /* Check if this is a directory. */ + FsDirEntryType entry_type; + R_TRY(GetObjectType(std::addressof(entry_type))); + + /* If it is, we're done. */ + R_SUCCEED_IF(entry_type == FsDirEntryType_Dir); + + /* Otherwise, open as a file. */ + FsFile file; + R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file))); + + /* Ensure we maintain a clean state on exit. */ + ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); }; + + R_RETURN(m_fs.GetFileSize(std::addressof(file), out_size)); + }; + + /* Define helper for determining if the property should be included. */ + const auto ShouldIncludeProperty = [&] (PtpObjectPropertyCode code) { + /* If all properties were requested, or it was the requested property, we should include the property. */ + return property_code == -1 || code == property_code; + }; + + /* Determine how many output elements we will report. */ + u32 num_output_elements = 0; + for (const auto obj_property : SupportedObjectProperties) { + if (ShouldIncludeProperty(obj_property)) { + num_output_elements++; + } + } + + /* Begin writing the requested object properties. */ + PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server)); + + R_TRY(db.WriteVariableLengthData(m_request_header, [&] { + /* Report the number of elements. */ + R_TRY(db.Add(num_output_elements)); + + for (const auto obj_property : SupportedObjectProperties) { + if (!ShouldIncludeProperty(obj_property)) { + continue; + } + + /* Write the object handle. */ + R_TRY(db.Add<u32>(object_id)); + + /* Write the property code. */ + R_TRY(db.Add<u16>(obj_property)); + + /* Write the property value. */ + switch (obj_property) { + case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier: + { + R_TRY(db.Add(PtpDataTypeCode_U128)); + R_TRY(db.Add<u128>(object_id)); + } + break; + case PtpObjectPropertyCode_ObjectSize: + { + s64 size; + R_TRY(GetObjectSize(std::addressof(size))); + R_TRY(db.Add(PtpDataTypeCode_U64)); + R_TRY(db.Add<u64>(size)); + } + break; + case PtpObjectPropertyCode_StorageId: + { + R_TRY(db.Add(PtpDataTypeCode_U32)); + R_TRY(db.Add(StorageId_SdmcFs)); + } + break; + case PtpObjectPropertyCode_ParentObject: + { + R_TRY(db.Add(PtpDataTypeCode_U32)); + R_TRY(db.Add(obj->GetParentId())); + } + break; + case PtpObjectPropertyCode_ObjectFormat: + { + FsDirEntryType entry_type; + R_TRY(GetObjectType(std::addressof(entry_type))); + R_TRY(db.Add(PtpDataTypeCode_U16)); + R_TRY(db.Add(entry_type == FsDirEntryType_File ? PtpObjectFormatCode_Undefined : PtpObjectFormatCode_Association)); + } + break; + case PtpObjectPropertyCode_ObjectFileName: + { + R_TRY(db.Add(PtpDataTypeCode_String)); + R_TRY(db.AddString(std::strrchr(obj->GetName(), '/') + 1)); + } + break; + HAZE_UNREACHABLE_DEFAULT_CASE(); + } + } + + R_SUCCEED(); + })); + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::SetObjectPropValue(PtpDataParser &rdp) { + u32 object_id; + PtpObjectPropertyCode property_code; + + R_TRY(rdp.Read(std::addressof(object_id))); + R_TRY(rdp.Read(std::addressof(property_code))); + R_TRY(rdp.Finalize()); + + PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server)); + + /* Ensure we have a data header. */ + PtpUsbBulkContainer data_header; + R_TRY(dp.Read(std::addressof(data_header))); + R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType()); + R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported()); + R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported()); + + /* Ensure we have a valid property code before continuing. */ + R_UNLESS(property_code == PtpObjectPropertyCode_ObjectFileName, haze::ResultUnknownPropertyCode()); + + /* Check if we know about the object. If we don't, it's an error. */ + auto * const obj = m_object_database.GetObjectById(object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); + + /* We are reading a file name. */ + R_TRY(dp.ReadString(m_buffers->filename_string_buffer)); + R_TRY(dp.Finalize()); + + /* Ensure we can actually process the new name. */ + const bool is_empty = m_buffers->filename_string_buffer[0] == '\x00'; + const bool contains_slashes = std::strchr(m_buffers->filename_string_buffer, '/') != nullptr; + R_UNLESS(!is_empty && !contains_slashes, haze::ResultInvalidPropertyValue()); + + /* Add a new object in the database with the new name. */ + PtpObject *newobj; + { + /* Find the last path separator in the existing object name. */ + char *pathsep = std::strrchr(obj->m_name, '/'); + HAZE_ASSERT(pathsep != nullptr); + + /* Temporarily mark the path separator as null to facilitate processing. */ + *pathsep = '\x00'; + ON_SCOPE_EXIT { *pathsep = '/'; }; + + R_TRY(m_object_database.CreateOrFindObject(obj->GetName(), m_buffers->filename_string_buffer, obj->GetParentId(), std::addressof(newobj))); + } + + { + /* Ensure we maintain a clean state on failure. */ + ON_RESULT_FAILURE { + if (!newobj->GetIsRegistered()) { + /* Only delete if the object was not registered. */ + /* Otherwise, we would remove an object that still exists. */ + m_object_database.DeleteObject(newobj); + } + }; + + /* Get the old object type. */ + FsDirEntryType entry_type; + R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type))); + + /* Attempt to rename the object on the filesystem. */ + if (entry_type == FsDirEntryType_Dir) { + R_TRY(m_fs.RenameDirectory(obj->GetName(), newobj->GetName())); + } else { + R_TRY(m_fs.RenameFile(obj->GetName(), newobj->GetName())); + } + } + + /* Unregister and free the old object. */ + m_object_database.DeleteObject(obj); + + /* Register the new object. */ + m_object_database.RegisterObject(newobj, object_id); + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/ptp_responder_ptp_operations.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/ptp_responder_ptp_operations.cpp new file mode 100644 index 00000000..4213a506 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/ptp_responder_ptp_operations.cpp @@ -0,0 +1,507 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <haze.hpp> +#include <haze/ptp_data_builder.hpp> +#include <haze/ptp_data_parser.hpp> +#include <haze/ptp_responder_types.hpp> + +namespace haze { + + Result PtpResponder::GetDeviceInfo(PtpDataParser &dp) { + PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server)); + + /* Write the device info data. */ + R_TRY(db.WriteVariableLengthData(m_request_header, [&] () { + R_TRY(db.Add(MtpStandardVersion)); + R_TRY(db.Add(MtpVendorExtensionId)); + R_TRY(db.Add(MtpStandardVersion)); + R_TRY(db.AddString(MtpVendorExtensionDesc)); + R_TRY(db.Add(MtpFunctionalModeDefault)); + R_TRY(db.AddArray(SupportedOperationCodes, util::size(SupportedOperationCodes))); + R_TRY(db.AddArray(SupportedEventCodes, util::size(SupportedEventCodes))); + R_TRY(db.AddArray(SupportedDeviceProperties, util::size(SupportedDeviceProperties))); + R_TRY(db.AddArray(SupportedCaptureFormats, util::size(SupportedCaptureFormats))); + R_TRY(db.AddArray(SupportedPlaybackFormats, util::size(SupportedPlaybackFormats))); + R_TRY(db.AddString(MtpDeviceManufacturer)); + R_TRY(db.AddString(MtpDeviceModel)); + R_TRY(db.AddString(GetFirmwareVersion())); + R_TRY(db.AddString(GetSerialNumber())); + + R_SUCCEED(); + })); + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::OpenSession(PtpDataParser &dp) { + R_TRY(dp.Finalize()); + + /* Close, if we're already open. */ + this->ForceCloseSession(); + + /* Initialize the database. */ + m_session_open = true; + m_object_database.Initialize(m_object_heap); + + /* Create the root storages. */ + PtpObject *object; + R_TRY(m_object_database.CreateOrFindObject("", "", PtpGetObjectHandles_RootParent, std::addressof(object))); + + /* Register the root storages. */ + m_object_database.RegisterObject(object, StorageId_SdmcFs); + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::CloseSession(PtpDataParser &dp) { + R_TRY(dp.Finalize()); + + this->ForceCloseSession(); + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::GetStorageIds(PtpDataParser &dp) { + R_TRY(dp.Finalize()); + + PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server)); + + /* Write the storage ID array. */ + R_TRY(db.WriteVariableLengthData(m_request_header, [&] { + R_RETURN(db.AddArray(SupportedStorageIds, util::size(SupportedStorageIds))); + })); + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::GetStorageInfo(PtpDataParser &dp) { + PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server)); + PtpStorageInfo storage_info(DefaultStorageInfo); + + /* Get the storage ID the client requested information for. */ + u32 storage_id; + R_TRY(dp.Read(std::addressof(storage_id))); + R_TRY(dp.Finalize()); + + /* Get the info from fs. */ + switch (storage_id) { + case StorageId_SdmcFs: + { + s64 total_space, free_space; + R_TRY(m_fs.GetTotalSpace("/", std::addressof(total_space))); + R_TRY(m_fs.GetFreeSpace("/", std::addressof(free_space))); + + storage_info.max_capacity = total_space; + storage_info.free_space_in_bytes = free_space; + storage_info.free_space_in_images = 0; + storage_info.storage_description = "SD Card"; + } + break; + default: + R_THROW(haze::ResultInvalidStorageId()); + } + + /* Write the storage info data. */ + R_TRY(db.WriteVariableLengthData(m_request_header, [&] () { + R_TRY(db.Add(storage_info.storage_type)); + R_TRY(db.Add(storage_info.filesystem_type)); + R_TRY(db.Add(storage_info.access_capability)); + R_TRY(db.Add(storage_info.max_capacity)); + R_TRY(db.Add(storage_info.free_space_in_bytes)); + R_TRY(db.Add(storage_info.free_space_in_images)); + R_TRY(db.AddString(storage_info.storage_description)); + R_TRY(db.AddString(storage_info.volume_label)); + + R_SUCCEED(); + })); + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::GetObjectHandles(PtpDataParser &dp) { + PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server)); + + /* Get the object ID the client requested enumeration for. */ + u32 storage_id, object_format_code, association_object_handle; + R_TRY(dp.Read(std::addressof(storage_id))); + R_TRY(dp.Read(std::addressof(object_format_code))); + R_TRY(dp.Read(std::addressof(association_object_handle))); + R_TRY(dp.Finalize()); + + /* Handle top-level requests. */ + if (storage_id == PtpGetObjectHandles_AllStorage) { + storage_id = StorageId_SdmcFs; + } + + /* Rewrite requests for enumerating storage directories. */ + if (association_object_handle == PtpGetObjectHandles_RootParent) { + association_object_handle = storage_id; + } + + /* Check if we know about the object. If we don't, it's an error. */ + auto * const obj = m_object_database.GetObjectById(association_object_handle); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); + + /* Try to read the object as a directory. */ + FsDir dir; + R_TRY(m_fs.OpenDirectory(obj->GetName(), FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles, std::addressof(dir))); + + /* Ensure we maintain a clean state on exit. */ + ON_SCOPE_EXIT { m_fs.CloseDirectory(std::addressof(dir)); }; + + /* Count how many entries are in the directory. */ + s64 entry_count = 0; + R_TRY(m_fs.GetDirectoryEntryCount(std::addressof(dir), std::addressof(entry_count))); + + /* Begin writing. */ + R_TRY(db.AddDataHeader(m_request_header, sizeof(u32) + (entry_count * sizeof(u32)))); + R_TRY(db.Add(static_cast<u32>(entry_count))); + + /* Enumerate the directory, writing results to the data builder as we progress. */ + /* TODO: How should we handle the directory contents changing during enumeration? */ + /* Is this even feasible to handle? */ + while (true) { + /* Get the next batch. */ + s64 read_count = 0; + R_TRY(m_fs.ReadDirectory(std::addressof(dir), std::addressof(read_count), DirectoryReadSize, m_buffers->file_system_entry_buffer)); + + /* Write to output. */ + for (s64 i = 0; i < read_count; i++) { + const char *name = m_buffers->file_system_entry_buffer[i].name; + u32 handle; + + R_TRY(m_object_database.CreateAndRegisterObjectId(obj->GetName(), name, obj->GetObjectId(), std::addressof(handle))); + R_TRY(db.Add(handle)); + } + + /* If we read fewer than the batch size, we're done. */ + if (read_count < DirectoryReadSize) { + break; + } + } + + /* Flush the data response. */ + R_TRY(db.Commit()); + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::GetObjectInfo(PtpDataParser &dp) { + PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server)); + + /* Get the object ID the client requested info for. */ + u32 object_id; + R_TRY(dp.Read(std::addressof(object_id))); + R_TRY(dp.Finalize()); + + /* Check if we know about the object. If we don't, it's an error. */ + auto * const obj = m_object_database.GetObjectById(object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); + + /* Build info about the object. */ + PtpObjectInfo object_info(DefaultObjectInfo); + + if (object_id == StorageId_SdmcFs) { + /* The SD Card directory has some special properties. */ + object_info.object_format = PtpObjectFormatCode_Association; + object_info.association_type = PtpAssociationType_GenericFolder; + object_info.filename = "SD Card"; + } else { + /* Figure out what type of object this is. */ + FsDirEntryType entry_type; + R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type))); + + /* Get the size, if we are requesting info about a file. */ + s64 size = 0; + if (entry_type == FsDirEntryType_File) { + FsFile file; + R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file))); + + /* Ensure we maintain a clean state on exit. */ + ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); }; + + R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(size))); + } + + object_info.filename = std::strrchr(obj->GetName(), '/') + 1; + object_info.object_compressed_size = size; + object_info.parent_object = obj->GetParentId(); + + if (entry_type == FsDirEntryType_Dir) { + object_info.object_format = PtpObjectFormatCode_Association; + object_info.association_type = PtpAssociationType_GenericFolder; + } else { + object_info.object_format = PtpObjectFormatCode_Undefined; + object_info.association_type = PtpAssociationType_Undefined; + } + } + + /* Write the object info data. */ + R_TRY(db.WriteVariableLengthData(m_request_header, [&] () { + R_TRY(db.Add(object_info.storage_id)); + R_TRY(db.Add(object_info.object_format)); + R_TRY(db.Add(object_info.protection_status)); + R_TRY(db.Add(object_info.object_compressed_size)); + R_TRY(db.Add(object_info.thumb_format)); + R_TRY(db.Add(object_info.thumb_compressed_size)); + R_TRY(db.Add(object_info.thumb_width)); + R_TRY(db.Add(object_info.thumb_height)); + R_TRY(db.Add(object_info.image_width)); + R_TRY(db.Add(object_info.image_height)); + R_TRY(db.Add(object_info.image_depth)); + R_TRY(db.Add(object_info.parent_object)); + R_TRY(db.Add(object_info.association_type)); + R_TRY(db.Add(object_info.association_desc)); + R_TRY(db.Add(object_info.sequence_number)); + R_TRY(db.AddString(object_info.filename)); + R_TRY(db.AddString(object_info.capture_date)); + R_TRY(db.AddString(object_info.modification_date)); + R_TRY(db.AddString(object_info.keywords)); + + R_SUCCEED(); + })); + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::GetObject(PtpDataParser &dp) { + PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server)); + + /* Get the object ID the client requested. */ + u32 object_id; + R_TRY(dp.Read(std::addressof(object_id))); + R_TRY(dp.Finalize()); + + /* Check if we know about the object. If we don't, it's an error. */ + auto * const obj = m_object_database.GetObjectById(object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); + + /* Lock the object as a file. */ + FsFile file; + R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file))); + + /* Ensure we maintain a clean state on exit. */ + ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); }; + + /* Get the file's size. */ + s64 size = 0; + R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(size))); + + /* Send the header and file size. */ + R_TRY(db.AddDataHeader(m_request_header, size)); + + /* Begin reading the file, writing data to the builder as we progress. */ + s64 offset = 0; + while (true) { + /* Get the next batch. */ + u64 bytes_read; + R_TRY(m_fs.ReadFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, FsBufferSize, FsReadOption_None, std::addressof(bytes_read))); + + offset += bytes_read; + + /* Write to output. */ + R_TRY(db.AddBuffer(m_buffers->file_system_data_buffer, bytes_read)); + + /* If we read fewer bytes than the batch size, we're done. */ + if (bytes_read < FsBufferSize) { + break; + } + } + + /* Flush the data response. */ + R_TRY(db.Commit()); + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::SendObjectInfo(PtpDataParser &rdp) { + /* Get the storage ID and parent object and flush the request packet. */ + u32 storage_id, parent_object; + R_TRY(rdp.Read(std::addressof(storage_id))); + R_TRY(rdp.Read(std::addressof(parent_object))); + R_TRY(rdp.Finalize()); + + PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server)); + PtpObjectInfo info(DefaultObjectInfo); + + /* Ensure we have a data header. */ + PtpUsbBulkContainer data_header; + R_TRY(dp.Read(std::addressof(data_header))); + R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType()); + R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported()); + R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported()); + + /* Read in the object info. */ + R_TRY(dp.Read(std::addressof(info.storage_id))); + R_TRY(dp.Read(std::addressof(info.object_format))); + R_TRY(dp.Read(std::addressof(info.protection_status))); + R_TRY(dp.Read(std::addressof(info.object_compressed_size))); + R_TRY(dp.Read(std::addressof(info.thumb_format))); + R_TRY(dp.Read(std::addressof(info.thumb_compressed_size))); + R_TRY(dp.Read(std::addressof(info.thumb_width))); + R_TRY(dp.Read(std::addressof(info.thumb_height))); + R_TRY(dp.Read(std::addressof(info.image_width))); + R_TRY(dp.Read(std::addressof(info.image_height))); + R_TRY(dp.Read(std::addressof(info.image_depth))); + R_TRY(dp.Read(std::addressof(info.parent_object))); + R_TRY(dp.Read(std::addressof(info.association_type))); + R_TRY(dp.Read(std::addressof(info.association_desc))); + R_TRY(dp.Read(std::addressof(info.sequence_number))); + R_TRY(dp.ReadString(m_buffers->filename_string_buffer)); + R_TRY(dp.ReadString(m_buffers->capture_date_string_buffer)); + R_TRY(dp.ReadString(m_buffers->modification_date_string_buffer)); + R_TRY(dp.ReadString(m_buffers->keywords_string_buffer)); + R_TRY(dp.Finalize()); + + /* Rewrite requests for creating in storage directories. */ + if (parent_object == PtpGetObjectHandles_RootParent) { + parent_object = storage_id; + } + + /* Check if we know about the parent object. If we don't, it's an error. */ + auto * const parentobj = m_object_database.GetObjectById(parent_object); + R_UNLESS(parentobj != nullptr, haze::ResultInvalidObjectId()); + + /* Make a new object with the intended name. */ + PtpNewObjectInfo new_object_info; + new_object_info.storage_id = StorageId_SdmcFs; + new_object_info.parent_object_id = parent_object == storage_id ? 0 : parent_object; + + /* Create the object in the database. */ + PtpObject *obj; + R_TRY(m_object_database.CreateOrFindObject(parentobj->GetName(), m_buffers->filename_string_buffer, parentobj->GetObjectId(), std::addressof(obj))); + + /* Ensure we maintain a clean state on failure. */ + ON_RESULT_FAILURE { m_object_database.DeleteObject(obj); }; + + /* Register the object with a new ID. */ + m_object_database.RegisterObject(obj); + new_object_info.object_id = obj->GetObjectId(); + + /* Create the object on the filesystem. */ + if (info.object_format == PtpObjectFormatCode_Association) { + R_TRY(m_fs.CreateDirectory(obj->GetName())); + m_send_object_id = 0; + } else { + R_TRY(m_fs.CreateFile(obj->GetName(), 0, 0)); + m_send_object_id = new_object_info.object_id; + } + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok, new_object_info)); + } + + Result PtpResponder::SendObject(PtpDataParser &rdp) { + /* Reset SendObject object ID on exit. */ + ON_SCOPE_EXIT { m_send_object_id = 0; }; + + R_TRY(rdp.Finalize()); + + PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server)); + + /* Ensure we have a data header. */ + PtpUsbBulkContainer data_header; + R_TRY(dp.Read(std::addressof(data_header))); + R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType()); + R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported()); + R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported()); + + /* Check if we know about the object. If we don't, it's an error. */ + auto * const obj = m_object_database.GetObjectById(m_send_object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); + + /* Lock the object as a file. */ + FsFile file; + R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write | FsOpenMode_Append, std::addressof(file))); + + /* Ensure we maintain a clean state on exit. */ + ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); }; + + /* Truncate the file after locking for write. */ + s64 offset = 0; + R_TRY(m_fs.SetFileSize(std::addressof(file), 0)); + + /* Expand to the needed size. */ + if (data_header.length > sizeof(PtpUsbBulkContainer)) { + R_TRY(m_fs.SetFileSize(std::addressof(file), data_header.length - sizeof(PtpUsbBulkContainer))); + } + + /* Begin writing to the filesystem. */ + while (true) { + /* Read as many bytes as we can. */ + u32 bytes_received; + const Result read_res = dp.ReadBuffer(m_buffers->file_system_data_buffer, FsBufferSize, std::addressof(bytes_received)); + + /* Write to the file. */ + R_TRY(m_fs.WriteFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, bytes_received, 0)); + + offset += bytes_received; + + /* If we received fewer bytes than the batch size, we're done. */ + if (haze::ResultEndOfTransmission::Includes(read_res)) { + break; + } + + R_TRY(read_res); + } + + /* Truncate the file to the received size. */ + R_TRY(m_fs.SetFileSize(std::addressof(file), offset)); + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::DeleteObject(PtpDataParser &dp) { + /* Get the object ID and flush the request packet. */ + u32 object_id; + R_TRY(dp.Read(std::addressof(object_id))); + R_TRY(dp.Finalize()); + + /* Disallow deleting the storage root. */ + R_UNLESS(object_id != StorageId_SdmcFs, haze::ResultInvalidObjectId()); + + /* Check if we know about the object. If we don't, it's an error. */ + auto * const obj = m_object_database.GetObjectById(object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); + + /* Figure out what type of object this is. */ + FsDirEntryType entry_type; + R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type))); + + /* Remove the object from the filesystem. */ + if (entry_type == FsDirEntryType_Dir) { + R_TRY(m_fs.DeleteDirectoryRecursively(obj->GetName())); + } else { + R_TRY(m_fs.DeleteFile(obj->GetName())); + } + + /* Remove the object from the database. */ + m_object_database.DeleteObject(obj); + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/usb_session.cpp b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/usb_session.cpp new file mode 100644 index 00000000..581edf19 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/haze/source/usb_session.cpp @@ -0,0 +1,263 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <haze.hpp> + +namespace haze { + + namespace { + + constexpr const u32 DefaultInterfaceNumber = 0; + + } + + Result UsbSession::Initialize1x(const UsbCommsInterfaceInfo *info) { + struct usb_interface_descriptor interface_descriptor = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = DefaultInterfaceNumber, + .bInterfaceClass = info->bInterfaceClass, + .bInterfaceSubClass = info->bInterfaceSubClass, + .bInterfaceProtocol = info->bInterfaceProtocol, + }; + + struct usb_endpoint_descriptor endpoint_descriptor_in = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_ENDPOINT_IN, + .bmAttributes = USB_TRANSFER_TYPE_BULK, + .wMaxPacketSize = PtpUsbBulkHighSpeedMaxPacketLength, + }; + + struct usb_endpoint_descriptor endpoint_descriptor_out = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_ENDPOINT_OUT, + .bmAttributes = USB_TRANSFER_TYPE_BULK, + .wMaxPacketSize = PtpUsbBulkHighSpeedMaxPacketLength, + }; + + struct usb_endpoint_descriptor endpoint_descriptor_interrupt = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_ENDPOINT_IN, + .bmAttributes = USB_TRANSFER_TYPE_INTERRUPT, + .wMaxPacketSize = 0x18, + .bInterval = 0x4, + }; + + /* Set up interface. */ + R_TRY(usbDsGetDsInterface(std::addressof(m_interface), std::addressof(interface_descriptor), "usb")); + + /* Set up endpoints. */ + R_TRY(usbDsInterface_GetDsEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Write]), std::addressof(endpoint_descriptor_in))); + R_TRY(usbDsInterface_GetDsEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Read]), std::addressof(endpoint_descriptor_out))); + R_TRY(usbDsInterface_GetDsEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Interrupt]), std::addressof(endpoint_descriptor_interrupt))); + + R_RETURN(usbDsInterface_EnableInterface(m_interface)); + } + + Result UsbSession::Initialize5x(const UsbCommsInterfaceInfo *info) { + struct usb_interface_descriptor interface_descriptor = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = DefaultInterfaceNumber, + .bNumEndpoints = 3, + .bInterfaceClass = info->bInterfaceClass, + .bInterfaceSubClass = info->bInterfaceSubClass, + .bInterfaceProtocol = info->bInterfaceProtocol, + }; + + struct usb_endpoint_descriptor endpoint_descriptor_in = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_ENDPOINT_IN, + .bmAttributes = USB_TRANSFER_TYPE_BULK, + .wMaxPacketSize = PtpUsbBulkHighSpeedMaxPacketLength, + }; + + struct usb_endpoint_descriptor endpoint_descriptor_out = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_ENDPOINT_OUT, + .bmAttributes = USB_TRANSFER_TYPE_BULK, + .wMaxPacketSize = PtpUsbBulkHighSpeedMaxPacketLength, + }; + + struct usb_endpoint_descriptor endpoint_descriptor_interrupt = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_ENDPOINT_IN, + .bmAttributes = USB_TRANSFER_TYPE_INTERRUPT, + .wMaxPacketSize = 0x18, + .bInterval = 0x6, + }; + + struct usb_ss_endpoint_companion_descriptor endpoint_companion = { + .bLength = sizeof(struct usb_ss_endpoint_companion_descriptor), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMPANION, + .bMaxBurst = 0x0f, + .bmAttributes = 0x00, + .wBytesPerInterval = 0x00, + }; + + struct usb_ss_endpoint_companion_descriptor endpoint_companion_interrupt = { + .bLength = sizeof(struct usb_ss_endpoint_companion_descriptor), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMPANION, + .bMaxBurst = 0x00, + .bmAttributes = 0x00, + .wBytesPerInterval = 0x00, + }; + + R_TRY(usbDsRegisterInterface(std::addressof(m_interface))); + + u8 iInterface; + R_TRY(usbDsAddUsbStringDescriptor(std::addressof(iInterface), "MTP")); + + interface_descriptor.bInterfaceNumber = m_interface->interface_index; + interface_descriptor.iInterface = iInterface; + endpoint_descriptor_in.bEndpointAddress += interface_descriptor.bInterfaceNumber + 1; + endpoint_descriptor_out.bEndpointAddress += interface_descriptor.bInterfaceNumber + 1; + endpoint_descriptor_interrupt.bEndpointAddress += interface_descriptor.bInterfaceNumber + 2; + + /* High speed config. */ + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, std::addressof(interface_descriptor), USB_DT_INTERFACE_SIZE)); + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, std::addressof(endpoint_descriptor_in), USB_DT_ENDPOINT_SIZE)); + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, std::addressof(endpoint_descriptor_out), USB_DT_ENDPOINT_SIZE)); + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, std::addressof(endpoint_descriptor_interrupt), USB_DT_ENDPOINT_SIZE)); + + /* Super speed config. */ + endpoint_descriptor_in.wMaxPacketSize = PtpUsbBulkSuperSpeedMaxPacketLength; + endpoint_descriptor_out.wMaxPacketSize = PtpUsbBulkSuperSpeedMaxPacketLength; + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(interface_descriptor), USB_DT_INTERFACE_SIZE)); + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_descriptor_in), USB_DT_ENDPOINT_SIZE)); + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_companion), USB_DT_SS_ENDPOINT_COMPANION_SIZE)); + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_descriptor_out), USB_DT_ENDPOINT_SIZE)); + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_companion), USB_DT_SS_ENDPOINT_COMPANION_SIZE)); + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_descriptor_interrupt), USB_DT_ENDPOINT_SIZE)); + R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_companion_interrupt), USB_DT_SS_ENDPOINT_COMPANION_SIZE)); + + /* Set up endpoints. */ + R_TRY(usbDsInterface_RegisterEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Write]), endpoint_descriptor_in.bEndpointAddress)); + R_TRY(usbDsInterface_RegisterEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Read]), endpoint_descriptor_out.bEndpointAddress)); + R_TRY(usbDsInterface_RegisterEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Interrupt]), endpoint_descriptor_interrupt.bEndpointAddress)); + + R_RETURN(usbDsInterface_EnableInterface(m_interface)); + } + + Result UsbSession::Initialize(const UsbCommsInterfaceInfo *info, u16 id_vendor, u16 id_product) { + R_TRY(usbDsInitialize()); + + if (hosversionAtLeast(5, 0, 0)) { + /* Report language as US English. */ + static const u16 supported_langs[1] = { 0x0409 }; + R_TRY(usbDsAddUsbLanguageStringDescriptor(nullptr, supported_langs, util::size(supported_langs))); + + /* Report strings. */ + u8 iManufacturer, iProduct, iSerialNumber; + R_TRY(usbDsAddUsbStringDescriptor(std::addressof(iManufacturer), "Nintendo")); + R_TRY(usbDsAddUsbStringDescriptor(std::addressof(iProduct), "Nintendo Switch")); + R_TRY(usbDsAddUsbStringDescriptor(std::addressof(iSerialNumber), GetSerialNumber())); + + /* Send device descriptors */ + struct usb_device_descriptor device_descriptor = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 0x40, + .idVendor = id_vendor, + .idProduct = id_product, + .bcdDevice = 0x0100, + .iManufacturer = iManufacturer, + .iProduct = iProduct, + .iSerialNumber = iSerialNumber, + .bNumConfigurations = 0x01 + }; + R_TRY(usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_High, std::addressof(device_descriptor))); + + device_descriptor.bcdUSB = 0x0300; + device_descriptor.bMaxPacketSize0 = 0x09; + R_TRY(usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Super, std::addressof(device_descriptor))); + + /* Binary Object Store */ + u8 bos[0x16] = { + 0x05, /* .bLength */ + USB_DT_BOS, /* .bDescriptorType */ + 0x16, 0x00, /* .wTotalLength */ + 0x02, /* .bNumDeviceCaps */ + + /* USB 2.0 */ + 0x07, /* .bLength */ + USB_DT_DEVICE_CAPABILITY, /* .bDescriptorType */ + 0x02, /* .bDevCapabilityType */ + 0x02, 0x00, 0x00, 0x00, /* .bmAttributes */ + + /* USB 3.0 */ + 0x0a, /* .bLength */ + USB_DT_DEVICE_CAPABILITY, /* .bDescriptorType */ + 0x03, /* .bDevCapabilityType */ + 0x00, /* .bmAttributes */ + 0x0c, 0x00, /* .wSpeedSupported */ + 0x03, /* .bFunctionalitySupport */ + 0x00, /* .bU1DevExitLat */ + 0x00, 0x00 /* .bU2DevExitLat */ + }; + R_TRY(usbDsSetBinaryObjectStore(bos, sizeof(bos))); + } + + if (hosversionAtLeast(5, 0, 0)) { + R_TRY(this->Initialize5x(info)); + R_TRY(usbDsEnable()); + } else { + R_TRY(this->Initialize1x(info)); + } + + R_SUCCEED(); + } + + void UsbSession::Finalize() { + usbDsExit(); + } + + bool UsbSession::GetConfigured() const { + UsbState usb_state; + + HAZE_R_ABORT_UNLESS(usbDsGetState(std::addressof(usb_state))); + + return usb_state == UsbState_Configured; + } + + Event *UsbSession::GetCompletionEvent(UsbSessionEndpoint ep) const { + return std::addressof(m_endpoints[ep]->CompletionEvent); + } + + Result UsbSession::TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_urb_id) { + R_RETURN(usbDsEndpoint_PostBufferAsync(m_endpoints[ep], buffer, size, out_urb_id)); + } + + Result UsbSession::GetTransferResult(UsbSessionEndpoint ep, u32 urb_id, u32 *out_transferred_size) { + UsbDsReportData report_data; + + R_TRY(eventClear(std::addressof(m_endpoints[ep]->CompletionEvent))); + R_TRY(usbDsEndpoint_GetReportData(m_endpoints[ep], std::addressof(report_data))); + R_TRY(usbDsParseReportData(std::addressof(report_data), urb_id, nullptr, out_transferred_size)); + + R_SUCCEED(); + } + +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/reboot_to_payload/Makefile b/Source/Atmosphere-MTC-Unlock/troposphere/reboot_to_payload/Makefile new file mode 100644 index 00000000..410dae26 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/reboot_to_payload/Makefile @@ -0,0 +1,192 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm". +# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) +# +# NO_ICON: if set to anything, do not use icon. +# NO_NACP: if set to anything, no .nacp file is generated. +# APP_TITLE is the name of the app stored in the .nacp file (Optional) +# APP_AUTHOR is the author of the app stored in the .nacp file (Optional) +# APP_VERSION is the version of the app stored in the .nacp file (Optional) +# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) +# ICON is the filename of the icon (.jpg), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - <Project name>.jpg +# - icon.jpg +# - <libnx folder>/default_icon.jpg +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source +DATA := data +INCLUDES := include +EXEFS_SRC := exefs_src +APP_TITLE := Reboot to Payload +APP_AUTHOR := Atmosphere-NX +APP_VERSION := 1.0.0 +#ROMFS := romfs + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE + +CFLAGS := -g -Wall -O2 -ffunction-sections \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) -D__SWITCH__ + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lnx + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC) + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.jpg) + ifneq (,$(findstring $(TARGET).jpg,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).jpg + else + ifneq (,$(findstring icon.jpg,$(icons))) + export APP_ICON := $(TOPDIR)/icon.jpg + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_ICON)),) + export NROFLAGS += --icon=$(APP_ICON) +endif + +ifeq ($(strip $(NO_NACP)),) + export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp +endif + +ifneq ($(APP_TITLEID),) + export NACPFLAGS += --titleid=$(APP_TITLEID) +endif + +ifneq ($(ROMFS),) + export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) +endif + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).pfs0 $(TARGET).nso $(TARGET).nro $(TARGET).nacp $(TARGET).elf + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT).nro + +ifeq ($(strip $(NO_NACP)),) +$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp +else +$(OUTPUT).nro : $(OUTPUT).elf +endif + +$(OUTPUT).elf : $(OFILES) + +$(OFILES_SRC) : $(HFILES_BIN) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/reboot_to_payload/reboot_to_payload.jpg b/Source/Atmosphere-MTC-Unlock/troposphere/reboot_to_payload/reboot_to_payload.jpg new file mode 100644 index 00000000..29a8f90e Binary files /dev/null and b/Source/Atmosphere-MTC-Unlock/troposphere/reboot_to_payload/reboot_to_payload.jpg differ diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/reboot_to_payload/source/ams_bpc.c b/Source/Atmosphere-MTC-Unlock/troposphere/reboot_to_payload/source/ams_bpc.c new file mode 100644 index 00000000..4f02f969 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/reboot_to_payload/source/ams_bpc.c @@ -0,0 +1,45 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <switch.h> +#include <string.h> +#include "ams_bpc.h" +#include "service_guard.h" + +static Service g_amsBpcSrv; + +NX_GENERATE_SERVICE_GUARD(amsBpc); + +Result _amsBpcInitialize(void) { + Handle h; + Result rc = svcConnectToNamedPort(&h, "bpc:ams"); /* TODO: ams:bpc */ + if (R_SUCCEEDED(rc)) serviceCreate(&g_amsBpcSrv, h); + return rc; +} + +void _amsBpcCleanup(void) { + serviceClose(&g_amsBpcSrv); +} + +Service *amsBpcGetServiceSession(void) { + return &g_amsBpcSrv; +} + +Result amsBpcSetRebootPayload(const void *src, size_t src_size) { + return serviceDispatch(&g_amsBpcSrv, 65001, + .buffer_attrs = { SfBufferAttr_In | SfBufferAttr_HipcMapAlias }, + .buffers = { { src, src_size } }, + ); +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/reboot_to_payload/source/ams_bpc.h b/Source/Atmosphere-MTC-Unlock/troposphere/reboot_to_payload/source/ams_bpc.h new file mode 100644 index 00000000..ec389f36 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/reboot_to_payload/source/ams_bpc.h @@ -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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <switch.h> + +#ifdef __cplusplus +extern "C" { +#endif + +Result amsBpcInitialize(); +void amsBpcExit(); +Service *amsBpcGetServiceSession(void); + +Result amsBpcSetRebootPayload(const void *src, size_t src_size); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/reboot_to_payload/source/main.c b/Source/Atmosphere-MTC-Unlock/troposphere/reboot_to_payload/source/main.c new file mode 100644 index 00000000..ebae553e --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/reboot_to_payload/source/main.c @@ -0,0 +1,96 @@ +#include <string.h> +#include <stdio.h> +#include <stdbool.h> + +#include <switch.h> +#include "ams_bpc.h" + +#define IRAM_PAYLOAD_MAX_SIZE 0x24000 +static u8 g_reboot_payload[IRAM_PAYLOAD_MAX_SIZE]; + +void userAppExit(void) +{ + amsBpcExit(); + setsysExit(); + spsmExit(); +} + +static void reboot_to_payload(void) { + Result rc = amsBpcSetRebootPayload(g_reboot_payload, IRAM_PAYLOAD_MAX_SIZE); + if (R_FAILED(rc)) { + printf("Failed to set reboot payload: 0x%x\n", rc); + } + else { + spsmShutdown(true); + } +} + +int main(int argc, char **argv) +{ + consoleInit(NULL); + + padConfigureInput(8, HidNpadStyleSet_NpadStandard); + + PadState pad; + padInitializeAny(&pad); + + Result rc = 0; + bool can_reboot = true; + + if (R_FAILED(rc = setsysInitialize())) { + printf("Failed to initialize set:sys: 0x%x\n", rc); + can_reboot = false; + } + else { + SetSysProductModel model; + setsysGetProductModel(&model); + if (model != SetSysProductModel_Nx && model != SetSysProductModel_Copper) { + printf("Reboot to payload cannot be used on a Mariko system\n"); + can_reboot = false; + } + } + + if (can_reboot && R_FAILED(rc = spsmInitialize())) { + printf("Failed to initialize spsm: 0x%x\n", rc); + can_reboot = false; + } + + if (can_reboot) { + smExit(); //Required to connect to ams:bpc + if R_FAILED(rc = amsBpcInitialize()) { + printf("Failed to initialize ams:bpc: 0x%x\n", rc); + can_reboot = false; + } + } + + if (can_reboot) { + FILE *f = fopen("sdmc:/atmosphere/reboot_payload.bin", "rb"); + if (f == NULL) { + printf("Failed to open atmosphere/reboot_payload.bin!\n"); + can_reboot = false; + } else { + fread(g_reboot_payload, 1, sizeof(g_reboot_payload), f); + fclose(f); + printf("Press [-] to reboot to payload\n"); + } + } + + printf("Press [L] to exit\n"); + + // Main loop + while(appletMainLoop()) + { + padUpdate(&pad); + u64 kDown = padGetButtonsDown(&pad); + + if (can_reboot && (kDown & HidNpadButton_Minus)) { + reboot_to_payload(); + } + if (kDown & HidNpadButton_L) { break; } // break in order to return to hbmenu + + consoleUpdate(NULL); + } + + consoleExit(NULL); + return 0; +} diff --git a/Source/Atmosphere-MTC-Unlock/troposphere/reboot_to_payload/source/service_guard.h b/Source/Atmosphere-MTC-Unlock/troposphere/reboot_to_payload/source/service_guard.h new file mode 100644 index 00000000..5fbc5fca --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/troposphere/reboot_to_payload/source/service_guard.h @@ -0,0 +1,56 @@ +#pragma once +#include <switch/types.h> +#include <switch/result.h> +#include <switch/kernel/mutex.h> +#include <switch/sf/service.h> +#include <switch/services/sm.h> + +typedef struct ServiceGuard { + Mutex mutex; + u32 refCount; +} ServiceGuard; + +NX_INLINE bool serviceGuardBeginInit(ServiceGuard* g) +{ + mutexLock(&g->mutex); + return (g->refCount++) == 0; +} + +NX_INLINE Result serviceGuardEndInit(ServiceGuard* g, Result rc, void (*cleanupFunc)(void)) +{ + if (R_FAILED(rc)) { + cleanupFunc(); + --g->refCount; + } + mutexUnlock(&g->mutex); + return rc; +} + +NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void)) +{ + mutexLock(&g->mutex); + if (g->refCount && (--g->refCount) == 0) + cleanupFunc(); + mutexUnlock(&g->mutex); +} + +#define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \ +\ +static ServiceGuard g_##name##Guard; \ +NX_INLINE Result _##name##Initialize _paramdecl; \ +static void _##name##Cleanup(void); \ +\ +Result name##Initialize _paramdecl \ +{ \ + Result rc = 0; \ + if (serviceGuardBeginInit(&g_##name##Guard)) \ + rc = _##name##Initialize _parampass; \ + return serviceGuardEndInit(&g_##name##Guard, rc, _##name##Cleanup); \ +} \ +\ +void name##Exit(void) \ +{ \ + serviceGuardExit(&g_##name##Guard, _##name##Cleanup); \ +} + +#define NX_GENERATE_SERVICE_GUARD(name) NX_GENERATE_SERVICE_GUARD_PARAMS(name, (void), ()) \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/utilities/erpt.py b/Source/Atmosphere-MTC-Unlock/utilities/erpt.py new file mode 100644 index 00000000..a69e6e91 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/utilities/erpt.py @@ -0,0 +1,489 @@ +# +# 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 <http://www.gnu.org/licenses/>. + +# erpt.py: Autogeneration tool for <stratosphere/erpt/erpt_ids.autogen.hpp> + +import nxo64 +import sys, os, string +from struct import unpack as up, pack as pk + +LOAD_BASE = 0x7100000000 + +HEADER = '''/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> + +/* NOTE: This file is auto-generated. */ +/* Do not make edits to this file by hand. */ + +''' + +if sys.version_info[0] == 3: + iter_range = range + int_types = (int,) + ascii_string = lambda b: b.decode('ascii') + bytes_to_list = lambda b: list(b) + list_to_bytes = lambda l: bytes(l) +else: + iter_range = xrange + int_types = (int, long) + ascii_string = lambda b: str(b) + bytes_to_list = lambda b: map(ord, b) + list_to_bytes = lambda l: ''.join(map(chr, l)) + +(DT_NULL, DT_NEEDED, DT_PLTRELSZ, DT_PLTGOT, DT_HASH, DT_STRTAB, DT_SYMTAB, DT_RELA, DT_RELASZ, + DT_RELAENT, DT_STRSZ, DT_SYMENT, DT_INIT, DT_FINI, DT_SONAME, DT_RPATH, DT_SYMBOLIC, DT_REL, + DT_RELSZ, DT_RELENT, DT_PLTREL, DT_DEBUG, DT_TEXTREL, DT_JMPREL, DT_BIND_NOW, DT_INIT_ARRAY, + DT_FINI_ARRAY, DT_INIT_ARRAYSZ, DT_FINI_ARRAYSZ, DT_RUNPATH, DT_FLAGS) = iter_range(31) + +DT_RELRSZ, DT_RELR, DT_RELRENT = 0x23, 0x24, 0x25 + +DT_GNU_HASH = 0x6ffffef5 +DT_VERSYM = 0x6ffffff0 +DT_RELACOUNT = 0x6ffffff9 +DT_RELCOUNT = 0x6ffffffa +DT_FLAGS_1 = 0x6ffffffb +DT_VERDEF = 0x6ffffffc +DT_VERDEFNUM = 0x6ffffffd + +STT_NOTYPE = 0 +STT_OBJECT = 1 +STT_FUNC = 2 +STT_SECTION = 3 + +STB_LOCAL = 0 +STB_GLOBAL = 1 +STB_WEAK = 2 + +R_ARM_ABS32 = 2 +R_ARM_TLS_DESC = 13 +R_ARM_GLOB_DAT = 21 +R_ARM_JUMP_SLOT = 22 +R_ARM_RELATIVE = 23 + +R_FAKE_RELR = -1 + +R_AARCH64_ABS64 = 257 +R_AARCH64_GLOB_DAT = 1025 +R_AARCH64_JUMP_SLOT = 1026 +R_AARCH64_RELATIVE = 1027 +R_AARCH64_TLSDESC = 1031 + +CATEGORIES = { + 0 : 'Test', + 1 : 'ErrorInfo', + 2 : 'ConnectionStatusInfo', + 3 : 'NetworkInfo', + 4 : 'NXMacAddressInfo', + 5 : 'StealthNetworkInfo', + 6 : 'LimitHighCapacityInfo', + 7 : 'NATTypeInfo', + 8 : 'WirelessAPMacAddressInfo', + 9 : 'GlobalIPAddressInfo', + 10 : 'EnableWirelessInterfaceInfo', + 11 : 'EnableWifiInfo', + 12 : 'EnableBluetoothInfo', + 13 : 'EnableNFCInfo', + 14 : 'NintendoZoneSSIDListVersionInfo', + 15 : 'LANAdapterMacAddressInfo', + 16 : 'ApplicationInfo', + 17 : 'OccurrenceInfo', + 18 : 'ProductModelInfo', + 19 : 'CurrentLanguageInfo', + 20 : 'UseNetworkTimeProtocolInfo', + 21 : 'TimeZoneInfo', + 22 : 'ControllerFirmwareInfo', + 23 : 'VideoOutputInfo', + 24 : 'NANDFreeSpaceInfo', + 25 : 'SDCardFreeSpaceInfo', + 26 : 'ScreenBrightnessInfo', + 27 : 'AudioFormatInfo', + 28 : 'MuteOnHeadsetUnpluggedInfo', + 29 : 'NumUserRegisteredInfo', + 30 : 'DataDeletionInfo', + 31 : 'ControllerVibrationInfo', + 32 : 'LockScreenInfo', + 33 : 'InternalBatteryLotNumberInfo', + 34 : 'LeftControllerSerialNumberInfo', + 35 : 'RightControllerSerialNumberInfo', + 36 : 'NotificationInfo', + 37 : 'TVInfo', + 38 : 'SleepInfo', + 39 : 'ConnectionInfo', + 40 : 'NetworkErrorInfo', + 41 : 'FileAccessPathInfo', + 42 : 'GameCardCIDInfo', + 43 : 'NANDCIDInfo', + 44 : 'MicroSDCIDInfo', + 45 : 'NANDSpeedModeInfo', + 46 : 'MicroSDSpeedModeInfo', + 47 : 'GameCardSpeedModeInfo', + 48 : 'UserAccountInternalIDInfo', + 49 : 'NetworkServiceAccountInternalIDInfo', + 50 : 'NintendoAccountInternalIDInfo', + 51 : 'USB3AvailableInfo', + 52 : 'CallStackInfo', + 53 : 'SystemStartupLogInfo', + 54 : 'RegionSettingInfo', + 55 : 'NintendoZoneConnectedInfo', + 56 : 'ForceSleepInfo', + 57 : 'ChargerInfo', + 58 : 'RadioStrengthInfo', + 59 : 'ErrorInfoAuto', + 60 : 'AccessPointInfo', + 61 : 'ErrorInfoDefaults', + 62 : 'SystemPowerStateInfo', + 63 : 'PerformanceInfo', + 64 : 'ThrottlingInfo', + 65 : 'GameCardErrorInfo', + 66 : 'EdidInfo', + 67 : 'ThermalInfo', + 68 : 'CradleFirmwareInfo', + 69 : 'RunningApplicationInfo', + 70 : 'RunningAppletInfo', + 71 : 'FocusedAppletHistoryInfo', + 72 : 'CompositorInfo', + 73 : 'BatteryChargeInfo', + 74 : 'NANDExtendedCsd', + 75 : 'NANDPatrolInfo', + 76 : 'NANDErrorInfo', + 77 : 'NANDDriverLog', + 78 : 'SdCardSizeSpec', + 79 : 'SdCardErrorInfo', + 80 : 'SdCardDriverLog ', + 81 : 'FsProxyErrorInfo', + 82 : 'SystemAppletSceneInfo', + 83 : 'VideoInfo', + 84 : 'GpuErrorInfo', + 85 : 'PowerClockInfo', + 86 : 'AdspErrorInfo', + 87 : 'NvDispDeviceInfo', + 88 : 'NvDispDcWindowInfo', + 89 : 'NvDispDpModeInfo', + 90 : 'NvDispDpLinkSpec', + 91 : 'NvDispDpLinkStatus', + 92 : 'NvDispDpHdcpInfo', + 93 : 'NvDispDpAuxCecInfo', + 94 : 'NvDispDcInfo', + 95 : 'NvDispDsiInfo', + 96 : 'NvDispErrIDInfo', + 97 : 'SdCardMountInfo', + 98 : 'RetailInteractiveDisplayInfo', + 99 : 'CompositorStateInfo', + 100 : 'CompositorLayerInfo', + 101 : 'CompositorDisplayInfo', + 102 : 'CompositorHWCInfo', + 103 : 'MonitorCapability', + 104 : 'ErrorReportSharePermissionInfo', + 105 : 'MultimediaInfo', + 106 : 'ConnectedControllerInfo', + 107 : 'FsMemoryInfo', + 108 : 'UserClockContextInfo', + 109 : 'NetworkClockContextInfo', + 110 : 'AcpGeneralSettingsInfo', + 111 : 'AcpPlayLogSettingsInfo', + 112 : 'AcpAocSettingsInfo', + 113 : 'AcpBcatSettingsInfo', + 114 : 'AcpStorageSettingsInfo', + 115 : 'AcpRatingSettingsInfo', + 116 : 'MonitorSettings', + 117 : 'RebootlessSystemUpdateVersionInfo', + 118 : 'NifmConnectionTestInfo', + 119 : 'PcieLoggedStateInfo', + 120 : 'NetworkSecurityCertificateInfo', + 121 : 'AcpNeighborDetectionInfo', + 122 : 'GpuCrashInfo', + 123 : 'UsbStateInfo', + 124 : 'NvHostErrInfo', + 125 : 'RunningUlaInfo', + 126 : 'InternalPanelInfo', + 127 : 'ResourceLimitLimitInfo', + 128 : 'ResourceLimitPeakInfo', + 129 : 'TouchScreenInfo', + 130 : 'AcpUserAccountSettingsInfo', + 131 : 'AudioDeviceInfo', + 132 : 'AbnormalWakeInfo', + 133 : 'ServiceProfileInfo', + 134 : 'BluetoothAudioInfo', + 135 : 'BluetoothPairingCountInfo', + 136 : 'FsProxyErrorInfo2', + 137 : 'BuiltInWirelessOUIInfo', + 138 : 'WirelessAPOUIInfo', + 139 : 'EthernetAdapterOUIInfo', + 140 : 'NANDTypeInfo', + 141 : 'MicroSDTypeInfo', + 142 : 'AttachmentFileInfo', + 143 : 'WlanInfo', + 144 : 'HalfAwakeStateInfo', + 145 : 'PctlSettingInfo', + 146 : 'GameCardLogInfo', + 147 : 'WlanIoctlErrorInfo', + 148 : 'SdCardActivationInfo', + 149 : 'GameCardDetailedErrorInfo', + 1000 : 'TestNx', + 1001 : 'NANDTypeInfo', + 1002 : 'NANDExtendedCsd', +} + +FIELD_TYPES = { + 0 : 'FieldType_NumericU64', + 1 : 'FieldType_NumericU32', + 2 : 'FieldType_NumericI64', + 3 : 'FieldType_NumericI32', + 4 : 'FieldType_String', + 5 : 'FieldType_U8Array', + 6 : 'FieldType_U32Array', + 7 : 'FieldType_U64Array', + 8 : 'FieldType_I32Array', + 9 : 'FieldType_I64Array', + 10 : 'FieldType_Bool', + 11 : 'FieldType_NumericU16', + 12 : 'FieldType_NumericU8', + 13 : 'FieldType_NumericI16', + 14 : 'FieldType_NumericI8', + 15 : 'FieldType_I8Array', +} + +FIELD_FLAGS = { + 0 : 'FieldFlag_None', + 1 : 'FieldFlag_Encrypt', +} + +def get_full(nxo): + full = nxo.full[:] + + undef_count = 0 + for s in nxo.symbols: + if not s.shndx and s.name: + undef_count += 1 + last_ea = max(LOAD_BASE + end for start, end, name, kind in nxo.sections) + undef_entry_size = 8 + undef_ea = ((last_ea + 0xFFF) & ~0xFFF) + undef_entry_size # plus 8 so we don't end up on the "end" symbol + + for i,s in enumerate(nxo.symbols): + if not s.shndx and s.name: + #idaapi.create_data(undef_ea, idc.FF_QWORD, 8, idaapi.BADADDR) + #idaapi.force_name(undef_ea, s.name) + s.resolved = undef_ea + undef_ea += undef_entry_size + elif i != 0: + assert s.shndx + s.resolved = LOAD_BASE + s.value + if s.name: + if s.type == STT_FUNC: + #print(hex(s.resolved), s.name) + #idaapi.add_entry(s.resolved, s.resolved, s.name, 0) + pass + else: + #idaapi.force_name(s.resolved, s.name) + pass + else: + # NULL symbol + s.resolved = 0 + + def put_dword(z, target, val): + return z[:target] + pk('<I', val) + z[target+4:] + def put_qword(z, target, val): + return z[:target] + pk('<Q', val) + z[target+8:] + def get_dword(z, target): + return up('<I', z[target:target+4])[0] + def get_qword(z, target): + return up('<Q', z[target:target+8])[0] + + for offset, r_type, sym, addend in nxo.relocations: + #print offset, r_type, sym, addend + target = offset + LOAD_BASE + if r_type in (R_ARM_GLOB_DAT, R_ARM_JUMP_SLOT, R_ARM_ABS32): + if not sym: + print('error: relocation at %X failed' % target) + else: + full = put_dword(full, target, sym.resolved) + elif r_type == R_ARM_RELATIVE: + full = put_dword(full, target, get_dword(full, target) + LOAD_BASE) + elif r_type in (R_AARCH64_GLOB_DAT, R_AARCH64_JUMP_SLOT, R_AARCH64_ABS64): + full = put_qword(full, target, sym.resolved + addend) + elif r_type == R_AARCH64_RELATIVE: + full = put_qword(full, target, LOAD_BASE + addend) + elif r_type == R_FAKE_RELR: + addend = get_qword(full, offset) + #print '%X %X %x' % (offset, target, addend) + full = put_qword(full, offset, addend + LOAD_BASE) + else: + print('TODO r_type %d' % (r_type,)) + with open('E:\\full.bin', 'wb') as f: + f.write(full) + return full + +def locate_fields(full): + start = ['TestU64', 'TestU32', 'TestI64', 'TestI32'] + inds = [full.index('%s\x00' % s) for s in start] + target = pk('<QQQQ', LOAD_BASE + inds[0], LOAD_BASE + inds[1], LOAD_BASE + inds[2], LOAD_BASE + inds[3]) + if target in full: + return 0, full.index(target) + else: + # 17.0.0 + ofs = 0 + while ofs < len(full) - 0x10: + test = full[ofs:ofs + 0x10] + if test == pk('<IIII', *[((x - ofs) & 0xFFFFFFFF) for x in inds]): + return 1, ofs + ofs += 4 + +def read_string(full, ofs): + s = '' + if ofs >= len(full): + return '' + while full[ofs] != '\x00': + s += full[ofs] + ofs += 1 + if ofs >= len(full): + return '' + return s + +def is_valid_field_name(s): + ALLOWED = string.lowercase + string.uppercase + string.digits + '_' + if not s: + return False + for c in s: + if not c in ALLOWED: + return False + return True + +def parse_fields(full, table, format_version): + fields = [] + ofs = 0 + if format_version == 0: + while True: + val = up('<Q', full[table + ofs:table + ofs + 8])[0] + if (val & 0xFFFFFFFF00000000) != LOAD_BASE: + break + s = read_string(full, val - LOAD_BASE) + if not is_valid_field_name(s): + break + fields.append(s) + ofs += 8 + elif format_version == 1: + while True: + val = up('<I', full[table + ofs:table + ofs + 4])[0] + s = read_string(full, (table + val) & 0xFFFFFFFF) + if not is_valid_field_name(s): + break + fields.append(s) + ofs += 4 + return fields + +def find_categories(full, num_fields): + KNOWN = [0] * 10 + [1] * 2 + [0x3B] * 2 + ind = full.index(''.join(pk('<I', i) for i in KNOWN)) + return list(up('<'+'I'*num_fields, full[ind:ind+4*num_fields])) + +def find_types(full, num_fields): + KNOWN = range(10) + [4, 4, 2, 4] + KNOWN_OLD = range(10) + [4, 4, 0, 4] + try: + ind = full.index(''.join(pk('<I', i) for i in KNOWN)) + except ValueError: + ind = full.index(''.join(pk('<I', i) for i in KNOWN_OLD)) + return list(up('<'+'I'*num_fields, full[ind:ind+4*num_fields])) + +def find_flags(full, num_fields, magic_idx): + KNOWN = '\x00' + ('\x01'*6) + '\x00\x01\x01\x00' + if num_fields < magic_idx + len(KNOWN): + return [0] * num_fields + ind = full.index(KNOWN) - magic_idx + return list(up('<'+'B'*num_fields, full[ind:ind+num_fields])) + +def find_id_array(full, num_fields, magic_idx, table_format): + if table_format == 0: + return list(range(num_fields)) + else: + KNOWN = pk('<IIIIII', *range(444, 450)) + ind = full.index(KNOWN) - 4 * magic_idx + return list(up('<' + 'I'*num_fields, full[ind:ind+4*num_fields])) + +def cat_to_string(c): + return CATEGORIES[c] if c in CATEGORIES else 'Category_Unknown%d' % c + +def typ_to_string(t): + return FIELD_TYPES[t] if t in FIELD_TYPES else 'FieldType_Unknown%d' % t + +def flg_to_string(f): + return FIELD_FLAGS[f] if f in FIELD_FLAGS else 'FieldFlag_Unknown%d' % f + +def main(argc, argv): + if argc != 2 and not (argc == 3 and argv[1] == '-f'): + print 'Usage: %s [-f] erpt_nso' % argv[0] + return 1 + f = open(argv[-1], 'rb') + nxo = nxo64.load_nxo(f) + full = get_full(nxo) + table_format, field_table = locate_fields(full) + fields = parse_fields(full, field_table, table_format) + NUM_FIELDS = len(fields) + cats = find_categories(full, NUM_FIELDS) + types = find_types(full, NUM_FIELDS) + flags = find_flags(full, NUM_FIELDS, fields.index('TestStringEncrypt') - 1) + ids = find_id_array(full, NUM_FIELDS, fields.index('TestStringEncrypt'), table_format) + assert ids[:4] == [0, 1, 2, 3] + print 'Identified %d fields.' % NUM_FIELDS + mf = max(len(s) for s in fields) + mc = max(len(cat_to_string(c)) for c in cats) + mt = max(len(typ_to_string(t)) for t in types) + ml = max(len(flg_to_string(f)) for f in flags) + if argc == 3: + mf, mc, mt, ml = (64, 48, 32, 32) + format_string = '- %%-%ds %%-%ds %%-%ds %%-%ds' % (mf+1, mc+1, mt+1, ml) + for i in xrange(NUM_FIELDS): + f, c, t, l = fields[i], cat_to_string(cats[i]), typ_to_string(types[i]), flg_to_string(flags[i]) + print format_string % (f+',', c+',', t+',', l) + with open(argv[-1]+'.hpp', 'w') as out: + out.write(HEADER) + out.write('#define AMS_ERPT_FOREACH_FIELD_TYPE(HANDLER) \\\n') + for tp in sorted(list(set(types + FIELD_TYPES.keys()))): + out.write((' HANDLER(%%-%ds %%-2d) \\\n' % (mt+1)) % (typ_to_string(tp)+',', tp)) + out.write('\n') + out.write('#define AMS_ERPT_FOREACH_CATEGORY(HANDLER) \\\n') + for ct in sorted(list(set(cats + CATEGORIES.keys()))): + out.write((' HANDLER(%%-%ds %%-3d) \\\n' % (mc+1)) % (cat_to_string(ct)+',', ct)) + out.write('\n') + out.write('#define AMS_ERPT_FOREACH_FIELD(HANDLER) \\\n') + for i in xrange(NUM_FIELDS): + f, c, t, l, d = fields[i], cats[i], types[i], flags[i], ids[i] + out.write((' HANDLER(%%-%ds %%-4s %%-%ds %%-%ds %%-%ds) \\\n' % (mf+1, mc+1, mt+1, ml)) % (f+',', '%d,'%d, cat_to_string(c)+',', typ_to_string(t)+',', flg_to_string(l))) + out.write('\n') + return 0 + +if __name__ == '__main__': + try: + ret = main(len(sys.argv), sys.argv) + except Exception as e: + print e + ret = 1 + print 'exception' + sys.exit(ret) \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/utilities/insert_splash_screen.py b/Source/Atmosphere-MTC-Unlock/utilities/insert_splash_screen.py new file mode 100644 index 00000000..07be27a5 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/utilities/insert_splash_screen.py @@ -0,0 +1,44 @@ +from PIL import Image +import sys, os +from struct import pack as pk + +SPLASH_SCREEN_WIDTH = 1280 +SPLASH_SCREEN_HEIGHT = 720 + +SPLASH_SCREEN_STRIDE = 768 + +def convert_image(image_fn): + splash = Image.open(image_fn, 'r') + w, h = splash.size + if w == 1280 and h == 720: + splash = splash.transpose(Image.ROTATE_90) + w, h = splash.size + assert w == 720 + assert h == 1280 + splash = splash.convert('RGBA') + splash_bin = bytearray() + for row in range(SPLASH_SCREEN_WIDTH): + for col in range(SPLASH_SCREEN_HEIGHT): + r, g, b, a = splash.getpixel((col, row)) + splash_bin += pk('<BBBB', b, g, r, a) + splash_bin += b'\x00' * ((SPLASH_SCREEN_STRIDE - SPLASH_SCREEN_HEIGHT) * 4) + return splash_bin + +def main(argc, argv): + if argc != 3: + print('Usage: %s image package3' % argv[0]) + return 1 + with open(argv[2], 'rb') as f: + package3 = f.read() + assert package3[:4] == b'PK31' + assert len(package3) == 0x800000 + splash_bin = convert_image(argv[1]) + assert len(splash_bin) == 0x3C0000 + with open(argv[2], 'wb') as f: + f.write(package3[:0x400000]) + f.write(splash_bin) + f.write(package3[0x7C0000:]) + return 0 + +if __name__ == '__main__': + sys.exit(main(len(sys.argv), sys.argv)) \ No newline at end of file diff --git a/Source/Atmosphere-MTC-Unlock/utilities/nxo64.py b/Source/Atmosphere-MTC-Unlock/utilities/nxo64.py new file mode 100644 index 00000000..6fad52f3 --- /dev/null +++ b/Source/Atmosphere-MTC-Unlock/utilities/nxo64.py @@ -0,0 +1,1082 @@ +# Copyright 2017 Reswitched Team +# +# Permission to use, copy, modify, and/or distribute this software for any purpose with or +# without fee is hereby granted, provided that the above copyright notice and this permission +# notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS +# SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL +# THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE +# OR PERFORMANCE OF THIS SOFTWARE. + +# nxo64.py: IDA loader and library for reading nso/nro files + +import gzip, math, os, re, struct, sys + +from io import BytesIO +from cStringIO import StringIO + +import lz4.block + +uncompress = lz4.block.decompress + + +def get_file_size(f): + filesize = 0 + try: + filesize = f.size() + except: + ptell = f.tell() + f.seek(0, 2) + filesize = f.tell() + f.seek(ptell) + return filesize + +class BinFile(object): + def __init__(self, li): + self._f = li + + def read(self, arg): + if isinstance(arg, str): + fmt = '<' + arg + size = struct.calcsize(fmt) + raw = self._f.read(size) + out = struct.unpack(fmt, raw) + if len(out) == 1: + return out[0] + return out + elif arg is None: + return self.read_to_end() + else: + out = self._f.read(arg) + if isinstance(arg, (int,long)) and len(out) != arg: + pass #print 'warning: read of %d bytes got %d bytes' % (arg, len(out)) + return out + + def read_to_end(self): + return self.read(self.size()-self.tell()) + + def size(self): + return get_file_size(self._f) + + def read_from(self, arg, offset): + old = self.tell() + try: + self.seek(offset) + out = self.read(arg) + finally: + self.seek(old) + return out + + def seek(self, off): + self._f.seek(off) + + def skip(self, dist): + self.seek(self.tell()+dist) + + def close(self): + self._f.close() + + def tell(self): + return self._f.tell() + + +(DT_NULL, DT_NEEDED, DT_PLTRELSZ, DT_PLTGOT, DT_HASH, DT_STRTAB, DT_SYMTAB, DT_RELA, DT_RELASZ, + DT_RELAENT, DT_STRSZ, DT_SYMENT, DT_INIT, DT_FINI, DT_SONAME, DT_RPATH, DT_SYMBOLIC, DT_REL, + DT_RELSZ, DT_RELENT, DT_PLTREL, DT_DEBUG, DT_TEXTREL, DT_JMPREL, DT_BIND_NOW, DT_INIT_ARRAY, + DT_FINI_ARRAY, DT_INIT_ARRAYSZ, DT_FINI_ARRAYSZ, DT_RUNPATH, DT_FLAGS) = xrange(31) + +DT_RELRSZ, DT_RELR, DT_RELRENT = 0x23, 0x24, 0x25 + +DT_GNU_HASH = 0x6ffffef5 +DT_VERSYM = 0x6ffffff0 +DT_RELACOUNT = 0x6ffffff9 +DT_RELCOUNT = 0x6ffffffa +DT_FLAGS_1 = 0x6ffffffb +DT_VERDEF = 0x6ffffffc +DT_VERDEFNUM = 0x6ffffffd + +STT_NOTYPE = 0 +STT_OBJECT = 1 +STT_FUNC = 2 +STT_SECTION = 3 + +STB_LOCAL = 0 +STB_GLOBAL = 1 +STB_WEAK = 2 + +R_ARM_ABS32 = 2 +R_ARM_TLS_DESC = 13 +R_ARM_GLOB_DAT = 21 +R_ARM_JUMP_SLOT = 22 +R_ARM_RELATIVE = 23 + +R_FAKE_RELR = -1 + +R_AARCH64_ABS64 = 257 +R_AARCH64_GLOB_DAT = 1025 +R_AARCH64_JUMP_SLOT = 1026 +R_AARCH64_RELATIVE = 1027 +R_AARCH64_TLSDESC = 1031 + +MULTIPLE_DTS = set([DT_NEEDED]) + + +class Range(object): + def __init__(self, start, size): + self.start = start + self.size = size + self.end = start+size + self._inclend = start+size-1 + + def overlaps(self, other): + return self.start <= other._inclend and other.start <= self._inclend + + def includes(self, other): + return other.start >= self.start and other._inclend <= self._inclend + + def __repr__(self): + return 'Range(0x%X -> 0x%X)' % (self.start, self.end) + + +class Segment(object): + def __init__(self, r, name, kind): + self.range = r + self.name = name + self.kind = kind + self.sections = [] + + def add_section(self, s): + for i in self.sections: + assert not i.range.overlaps(s.range), '%r overlaps %r' % (s, i) + self.sections.append(s) + + +class Section(object): + def __init__(self, r, name): + self.range = r + self.name = name + + def __repr__(self): + return 'Section(%r, %r)' % (self.range, self.name) + + +def suffixed_name(name, suffix): + if suffix == 0: + return name + return '%s.%d' % (name, suffix) + + +class SegmentBuilder(object): + def __init__(self): + self.segments = [] + self._sections = [] + + def add_segment(self, start, size, name, kind): + r = Range(start, size) + for i in self.segments: + assert not r.overlaps(i.range) + self.segments.append(Segment(r, name, kind)) + + def add_section(self, name, start, end=None, size=None): + assert end is None or size is None + if size is None: + size = end-start + if size > 0: + #assert size > 0 + r = Range(start, size) + self._sections.append((r, name)) + + def _add_sections_to_segments(self): + for r, name in self._sections: + for i in self.segments: + if i.range.includes(r): + i.add_section(Section(r, name)) + break + else: + assert False, 'no containing segment for %r' % (name,) + + def flatten(self): + self._add_sections_to_segments() + self.segments.sort(key=lambda s: s.range.start) + parts = [] + for segment in self.segments: + suffix = 0 + segment.sections.sort(key=lambda s: s.range.start) + pos = segment.range.start + for section in segment.sections: + if pos < section.range.start: + parts.append((pos, section.range.start, suffixed_name(segment.name, suffix), segment.kind)) + suffix += 1 + pos = section.range.start + parts.append((section.range.start, section.range.end, section.name, segment.kind)) + pos = section.range.end + if pos < segment.range.end: + parts.append((pos, segment.range.end, suffixed_name(segment.name, suffix), segment.kind)) + suffix += 1 + pos = segment.range.end + return parts + + +class ElfSym(object): + def __init__(self, name, info, other, shndx, value, size): + self.name = name + self.shndx = shndx + self.value = value + self.size = size + + self.vis = other & 3 + self.type = info & 0xF + self.bind = info >> 4 + + def __repr__(self): + return 'Sym(name=%r, shndx=0x%X, value=0x%X, size=0x%X, vis=%r, type=%r, bind=%r)' % ( + self.name, self.shndx, self.value, self.size, self.vis, self.type, self.bind) + + +class NxoFileBase(object): + def __init__(self, f, segment_data=None): + self.binfile = f + + # read MOD + self.modoff = f.read_from('I', 4) + + f.seek(self.modoff) + if f.read('4s') != 'MOD0': + raise NxoException('invalid MOD0 magic') + + self.dynamicoff = self.modoff + f.read('i') + self.bssoff = self.modoff + f.read('i') + self.bssend = self.modoff + f.read('i') + self.unwindoff = self.modoff + f.read('i') + self.unwindend = self.modoff + f.read('i') + self.moduleoff = self.modoff + f.read('i') + + + builder = SegmentBuilder() + + # read dynamic + self.armv7 = (f.read_from('Q', self.dynamicoff) > 0xFFFFFFFF or f.read_from('Q', self.dynamicoff+0x10) > 0xFFFFFFFF) + self.offsize = 4 if self.armv7 else 8 + + f.seek(self.dynamicoff) + self.dynamic = dynamic = {} + for i in MULTIPLE_DTS: + dynamic[i] = [] + for i in xrange((f.size() - self.dynamicoff) / 0x10): + tag, val = f.read('II' if self.armv7 else 'QQ') + if tag == DT_NULL: + break + if tag in MULTIPLE_DTS: + dynamic[tag].append(val) + else: + dynamic[tag] = val + self.dynamicsize = f.tell() - self.dynamicoff + builder.add_section('.dynamic', self.dynamicoff, end=self.dynamicoff + self.dynamicsize) + builder.add_section('.eh_frame_hdr', self.unwindoff, end=self.unwindend) + + # read .dynstr + if DT_STRTAB in dynamic and DT_STRSZ in dynamic: + f.seek(dynamic[DT_STRTAB]) + self.dynstr = f.read(dynamic[DT_STRSZ]) + else: + self.dynstr = '\0' + print 'warning: no dynstr' + + for startkey, szkey, name in [ + (DT_STRTAB, DT_STRSZ, '.dynstr'), + (DT_INIT_ARRAY, DT_INIT_ARRAYSZ, '.init_array'), + (DT_FINI_ARRAY, DT_FINI_ARRAYSZ, '.fini_array'), + (DT_RELA, DT_RELASZ, '.rela.dyn'), + (DT_REL, DT_RELSZ, '.rel.dyn'), + (DT_RELR, DT_RELRSZ, '.relr.dyn'), + (DT_JMPREL, DT_PLTRELSZ, ('.rel.plt' if self.armv7 else '.rela.plt')), + ]: + if startkey in dynamic and szkey in dynamic: + builder.add_section(name, dynamic[startkey], size=dynamic[szkey]) + + # TODO + #build_id = content.find('\x04\x00\x00\x00\x14\x00\x00\x00\x03\x00\x00\x00GNU\x00') + #if build_id >= 0: + # builder.add_section('.note.gnu.build-id', build_id, size=0x24) + #else: + # build_id = content.index('\x04\x00\x00\x00\x10\x00\x00\x00\x03\x00\x00\x00GNU\x00') + # if build_id >= 0: + # builder.add_section('.note.gnu.build-id', build_id, size=0x20) + + if DT_HASH in dynamic: + hash_start = dynamic[DT_HASH] + f.seek(hash_start) + nbucket, nchain = f.read('II') + f.skip(nbucket * 4) + f.skip(nchain * 4) + hash_end = f.tell() + builder.add_section('.hash', hash_start, end=hash_end) + + if DT_GNU_HASH in dynamic: + gnuhash_start = dynamic[DT_GNU_HASH] + f.seek(gnuhash_start) + nbuckets, symoffset, bloom_size, bloom_shift = f.read('IIII') + f.skip(bloom_size * self.offsize) + buckets = [f.read('I') for i in range(nbuckets)] + + max_symix = max(buckets) if buckets else 0 + if max_symix >= symoffset: + f.skip((max_symix - symoffset) * 4) + while (f.read('I') & 1) == 0: + pass + gnuhash_end = f.tell() + builder.add_section('.gnu.hash', gnuhash_start, end=gnuhash_end) + + self.needed = [self.get_dynstr(i) for i in self.dynamic[DT_NEEDED]] + + # load .dynsym + self.symbols = symbols = [] + f.seek(dynamic[DT_SYMTAB]) + while True: + if dynamic[DT_SYMTAB] < dynamic[DT_STRTAB] and f.tell() >= dynamic[DT_STRTAB]: + break + if self.armv7: + st_name, st_value, st_size, st_info, st_other, st_shndx = f.read('IIIBBH') + else: + st_name, st_info, st_other, st_shndx, st_value, st_size = f.read('IBBHQQ') + if st_name > len(self.dynstr): + break + symbols.append(ElfSym(self.get_dynstr(st_name), st_info, st_other, st_shndx, st_value, st_size)) + builder.add_section('.dynsym', dynamic[DT_SYMTAB], end=f.tell()) + + self.plt_entries = [] + self.relocations = [] + locations = set() + if DT_REL in dynamic: + locations |= self.process_relocations(f, symbols, dynamic[DT_REL], dynamic[DT_RELSZ]) + + if DT_RELA in dynamic: + locations |= self.process_relocations(f, symbols, dynamic[DT_RELA], dynamic[DT_RELASZ]) + + if DT_RELR in dynamic: + locations |= self.process_relocations_relr(f, dynamic[DT_RELR], dynamic[DT_RELRSZ]) + + if segment_data is None: + # infer segment info + rloc_guess = (dynamic[DT_REL if DT_REL in dynamic else DT_RELA] & ~0xFFF) + dloc_guess = (min(i for i in locations if i != 0) & ~0xFFF) + dloc_guess2 = None + modoff = f.read_from('I', 4) + if self.modoff != 8: + search_start = (self.modoff + 0xFFF) & ~0xFFF + for i in range(search_start, f.size(), 0x1000): + count = 0 + for j in range(4, 0x1000, 4): + if f.read_from('I', i - j) != 0: + break + count += 1 + if count > 6: + dloc_guess2 = i + break + if dloc_guess2 is not None and dloc_guess2 < dloc_guess: + dloc_guess = dloc_guess2 + + if segment_data: + tloc, tsize, rloc, rsize, dloc, dsize = segment_data + assert rloc_guess == rloc + assert dloc_guess == dloc + + self.textoff = 0 + self.textsize = rloc_guess + self.rodataoff = rloc_guess + self.rodatasize = dloc_guess - rloc_guess + self.dataoff = dloc_guess + else: + tloc, tsize, rloc, rsize, dloc, dsize = segment_data + self.textoff = tloc + self.textsize = tsize + self.rodataoff = rloc + self.rodatasize = rsize + self.dataoff = dloc + + self.datasize = self.bssoff - self.dataoff + self.bsssize = self.bssend - self.bssoff + + plt_got_end = None + if DT_JMPREL in dynamic: + pltlocations = self.process_relocations(f, symbols, dynamic[DT_JMPREL], dynamic[DT_PLTRELSZ]) + locations |= pltlocations + + plt_got_start = min(pltlocations) + plt_got_end = max(pltlocations) + self.offsize + if DT_PLTGOT in dynamic: + builder.add_section('.got.plt', dynamic[DT_PLTGOT], end=plt_got_end) + + if not self.armv7: + f.seek(0) + text = f.read(self.textsize) + last = 12 + while True: + pos = text.find(struct.pack('<I', 0xD61F0220), last) + if pos == -1: break + last = pos+1 + if (pos % 4) != 0: continue + off = pos - 12 + a, b, c, d = struct.unpack_from('<IIII', text, off) + if d == 0xD61F0220 and (a & 0x9f00001f) == 0x90000010 and (b & 0xffe003ff) == 0xf9400211: + base = off & ~0xFFF + immhi = (a >> 5) & 0x7ffff + immlo = (a >> 29) & 3 + paddr = base + ((immlo << 12) | (immhi << 14)) + poff = ((b >> 10) & 0xfff) << 3 + target = paddr + poff + if plt_got_start <= target < plt_got_end: + self.plt_entries.append((off, target)) + if len(self.plt_entries) > 0: + builder.add_section('.plt', min(self.plt_entries)[0], end=max(self.plt_entries)[0] + 0x10) + + # try to find the ".got" which should follow the ".got.plt" + good = False + got_start = (plt_got_end if plt_got_end is not None else self.dynamicoff + self.dynamicsize) + got_end = self.offsize + got_start + while (got_end in locations or (plt_got_end is None and got_end < dynamic[DT_INIT_ARRAY])) and (DT_INIT_ARRAY not in dynamic or got_end < dynamic[DT_INIT_ARRAY] or dynamic[DT_INIT_ARRAY] < got_start): + good = True + got_end += self.offsize + #print 'got_start got_end %X %X %s %X' % (got_start, got_end, str(got_end in locations), dynamic[DT_INIT_ARRAY]) + + if good: + self.got_start = got_start + self.got_end = got_end + builder.add_section('.got', self.got_start, end=self.got_end) + + self.eh_table = [] + if not self.armv7: + f.seek(self.unwindoff) + version, eh_frame_ptr_enc, fde_count_enc, table_enc = f.read('BBBB') + if not any(i == 0xff for i in (eh_frame_ptr_enc, fde_count_enc, table_enc)): # DW_EH_PE_omit + #assert eh_frame_ptr_enc == 0x1B # DW_EH_PE_pcrel | DW_EH_PE_sdata4 + #assert fde_count_enc == 0x03 # DW_EH_PE_absptr | DW_EH_PE_udata4 + #assert table_enc == 0x3B # DW_EH_PE_datarel | DW_EH_PE_sdata4 + if eh_frame_ptr_enc == 0x1B and fde_count_enc == 0x03 and table_enc == 0x3B: + base_offset = f.tell() + eh_frame = base_offset + f.read('i') + + fde_count = f.read('I') + #assert 8 * fde_count == self.unwindend - f.tell() + if 8 * fde_count == self.unwindend - f.tell(): + for i in range(fde_count): + pc = self.unwindoff + f.read('i') + entry = self.unwindoff + f.read('i') + self.eh_table.append((pc, entry)) + + # TODO: we miss the last one, but better than nothing + last_entry = sorted(self.eh_table, key=lambda x: x[1])[-1][1] + builder.add_section('.eh_frame', eh_frame, end=last_entry) + + + for off,sz,name,kind in [ + (self.textoff, self.textsize, '.text', 'CODE'), + (self.rodataoff, self.rodatasize, '.rodata', 'CONST'), + (self.dataoff, self.datasize, '.data', 'DATA'), + (self.bssoff, self.bsssize, '.bss', 'BSS'), + ]: + builder.add_segment(off, sz, name, kind) + + self.sections = [] + for start, end, name, kind in builder.flatten(): + self.sections.append((start, end, name, kind)) + + self._addr_to_name = None + self._plt_lookup = None + + @property + def addr_to_name(self): + if self._addr_to_name is None: + d = {} + for sym in self.symbols: + if sym.shndx: + d[sym.value] = sym.name + self._addr_to_name = d + return self._addr_to_name + + @property + def plt_lookup(self): + if self._plt_lookup is None: + # generate lookups for .plt call redirection + got_value_lookup = {} + for offset, r_type, sym, addend in self.relocations: + if r_type in (R_AARCH64_GLOB_DAT, R_AARCH64_JUMP_SLOT, R_AARCH64_ABS64) and addend == 0 and sym.shndx: + got_value_lookup[offset] = sym.value + + self._plt_lookup = {} + for func, target in self.plt_entries: + if target in got_value_lookup: + self._plt_lookup[func] = got_value_lookup[target] + + return self._plt_lookup + + def process_relocations(self, f, symbols, offset, size): + locations = set() + f.seek(offset) + relocsize = 8 if self.armv7 else 0x18 + for i in xrange(size / relocsize): + # NOTE: currently assumes all armv7 relocs have no addends, + # and all 64-bit ones do. + if self.armv7: + offset, info = f.read('II') + addend = None + r_type = info & 0xff + r_sym = info >> 8 + else: + offset, info, addend = f.read('QQq') + r_type = info & 0xffffffff + r_sym = info >> 32 + + sym = symbols[r_sym] if r_sym != 0 else None + + if r_type != R_AARCH64_TLSDESC and r_type != R_ARM_TLS_DESC: + locations.add(offset) + self.relocations.append((offset, r_type, sym, addend)) + return locations + + def process_relocations_relr(self, f, offset, size): + locations = set() + f.seek(offset) + relocsize = 8 + for i in xrange(size / relocsize): + entry = f.read('Q') + if entry & 1: + entry >>= 1 + i = 0 + while i < (relocsize * 8) - 1: + if entry & (1 << i): + locations.add(where + i * relocsize) + self.relocations.append((where + i * relocsize, R_FAKE_RELR, None, 0)) + i += 1 + where += relocsize * ((relocsize * 8) - 1) + else: + # Where + where = entry + locations.add(where) + self.relocations.append((where, R_FAKE_RELR, None, 0)) + where += relocsize + return locations + + def get_dynstr(self, o): + return self.dynstr[o:self.dynstr.index('\0', o)] + + def get_path_or_name(self): + path = None + for off, end, name, class_ in self.sections: + if name == '.rodata' and end-off < 0x1000 and end-off > 8: + id_ = self.binfile.read_from(end-off, off) + length = struct.unpack_from('<I', id_, 4)[0] + if length + 8 <= len(id_): + id_ = id_[8:length + 8] + return id_ + + self.binfile.seek(self.rodataoff) + as_string = self.binfile.read(self.rodatasize) + if path is None: + strs = re.findall(r'[a-z]:[\\/][ -~]{5,}\.n[rs]s', as_string, flags=re.IGNORECASE) + if strs: + return strs[-1] + + return None + + def get_name(self): + name = self.get_path_or_name() + if name is not None: + name = name.split('/')[-1].split('\\')[-1] + if name.lower().endswith(('.nss', '.nrs')): + name = name[:-4] + return name + +class NsoFile(NxoFileBase): + def __init__(self, fileobj): + f = BinFile(fileobj) + + if f.read_from('4s', 0) != 'NSO0': + raise NxoException('Invalid NSO magic') + + flags = f.read_from('I', 0xC) + + toff, tloc, tsize = f.read_from('III', 0x10) + roff, rloc, rsize = f.read_from('III', 0x20) + doff, dloc, dsize = f.read_from('III', 0x30) + + tfilesize, rfilesize, dfilesize = f.read_from('III', 0x60) + bsssize = f.read_from('I', 0x3C) + + text = uncompress(f.read_from(tfilesize, toff), uncompressed_size=tsize) if flags & 1 else f.read_from(tfilesize, toff) + ro = uncompress(f.read_from(rfilesize, roff), uncompressed_size=rsize) if flags & 2 else f.read_from(rfilesize, roff) + data = uncompress(f.read_from(dfilesize, doff), uncompressed_size=dsize) if flags & 4 else f.read_from(dfilesize, doff) + + full = text + if rloc >= len(full): + full += '\0' * (rloc - len(full)) + else: + print 'truncating?' + full = full[:rloc] + full += ro + if dloc >= len(full): + full += '\0' * (dloc - len(full)) + else: + print 'truncating?' + full = full[:dloc] + full += data + self.full = full + + super(NsoFile, self).__init__(BinFile(StringIO(full)), (tloc, tsize, rloc, rsize, dloc, dsize)) + + +class NroFile(NxoFileBase): + def __init__(self, fileobj): + f = BinFile(fileobj) + + if f.read_from('4s', 0x10) != 'NRO0': + raise NxoException('Invalid NRO magic') + + f.seek(0x20) + + tloc, tsize = f.read('II') + rloc, rsize = f.read('II') + dloc, dsize = f.read('II') + + # copy f, since all other formats allow the original file to be closed + f.seek(0) + full = f.read(f.size()) + + filecopy = BinFile(StringIO(full)) + super(NroFile, self).__init__(filecopy, (tloc, tsize, rloc, rsize, dloc, dsize)) + +def kip1_blz_decompress(compressed): + compressed_size, init_index, uncompressed_addl_size = struct.unpack('<III', compressed[-0xC:]) + decompressed = compressed[:] + '\x00' * uncompressed_addl_size + decompressed_size = len(decompressed) + if not (compressed_size + uncompressed_addl_size): + return '' + decompressed = map(ord, decompressed) + cmp_start = len(compressed) - compressed_size + cmp_ofs = compressed_size - init_index + out_ofs = compressed_size + uncompressed_addl_size + while out_ofs > 0: + cmp_ofs -= 1 + control = decompressed[cmp_start + cmp_ofs] + for i in xrange(8): + if control & 0x80: + if cmp_ofs < 2 - cmp_start: + raise ValueError('Compression out of bounds!') + cmp_ofs -= 2 + segmentoffset = decompressed[cmp_start + cmp_ofs] | (decompressed[cmp_start + cmp_ofs + 1] << 8) + segmentsize = ((segmentoffset >> 12) & 0xF) + 3 + segmentoffset &= 0x0FFF + segmentoffset += 2 + if out_ofs < segmentsize - cmp_start: + raise ValueError('Compression out of bounds!') + for j in xrange(segmentsize): + if out_ofs + segmentoffset >= decompressed_size: + raise ValueError('Compression out of bounds!') + data = decompressed[cmp_start + out_ofs + segmentoffset] + out_ofs -= 1 + decompressed[cmp_start + out_ofs] = data + else: + if out_ofs < 1 - cmp_start: + raise ValueError('Compression out of bounds!') + out_ofs -= 1 + cmp_ofs -= 1 + decompressed[cmp_start + out_ofs] = decompressed[cmp_start + cmp_ofs] + control <<= 1 + control &= 0xFF + if not out_ofs: + break + return ''.join(map(chr, decompressed)) + +class KipFile(NxoFileBase): + def __init__(self, fileobj): + f = BinFile(fileobj) + + if f.read_from('4s', 0) != 'KIP1': + raise NxoException('Invalid KIP magic') + + flags = f.read_from('b', 0x1F) + + tloc, tsize, tfilesize = f.read_from('III', 0x20) + rloc, rsize, rfilesize = f.read_from('III', 0x30) + dloc, dsize, dfilesize = f.read_from('III', 0x40) + + toff = 0x100 + roff = toff + tfilesize + doff = roff + rfilesize + + bsssize = f.read_from('I', 0x18) + + + text = kip1_blz_decompress(str(f.read_from(tfilesize, toff))) if flags & 1 else f.read_from(tfilesize, toff) + ro = kip1_blz_decompress(str(f.read_from(rfilesize, roff))) if flags & 2 else f.read_from(rfilesize, roff) + data = kip1_blz_decompress(str(f.read_from(dfilesize, doff))) if flags & 4 else f.read_from(dfilesize, doff) + + full = text + if rloc >= len(full): + full += '\0' * (rloc - len(full)) + else: + print 'truncating?' + full = full[:rloc] + full += ro + if dloc >= len(full): + full += '\0' * (dloc - len(full)) + else: + print 'truncating?' + full = full[:dloc] + full += data + + super(KipFile, self).__init__(BinFile(StringIO(full)), (tloc, tsize, rloc, rsize, dloc, dsize)) + +class MemoryDumpFile(NxoFileBase): + def __init__(self, fileobj): + f = BinFile(fileobj) + + if f.read_from('4s', f.read_from('I', 4)) != 'MOD0': + raise NxoException('Invalid MOD0 magic') + + f.seek(0) + full = f.read(f.size()) + + filecopy = BinFile(StringIO(full)) + super(MemoryDumpFile, self).__init__(filecopy) + +class NxoException(Exception): + pass + +def looks_like_memory_dump(fileobj): + fileobj.seek(0) + header = fileobj.read(8) + if len(header) < 8: + return False + modoff = struct.unpack_from('<I', header, 4)[0] + if modoff + 0x1c < get_file_size(fileobj): + fileobj.seek(modoff) + if fileobj.read(4) == 'MOD0': + return True + return False + + +def load_nxo(fileobj): + fileobj.seek(0) + header = fileobj.read(0x14) + + if header[:4] == 'NSO0': + return NsoFile(fileobj) + elif header[:4] == 'KIP1': + return KipFile(fileobj) + elif header[0x10:0x14] == 'NRO0': + return NroFile(fileobj) + elif looks_like_memory_dump(fileobj): + return MemoryDumpFile(fileobj) + + raise NxoException('not an NRO or NSO file') + + +try: + import idaapi + import idc +except ImportError: + pass +else: + # IDA specific code + NRO_FORMAT = 'Switch binary (NRO)' + NSO_FORMAT = 'Switch binary (NSO)' + KIP_FORMAT = 'Switch binary (KIP)' + RAW_FORMAT = 'Switch raw binary memory dump' + SDK_FORMAT = 'Switch SDK binary (NSO, with arm64 .plt call rewriting hack)' + RAW_SDK_FORMAT = 'Switch SDK (raw memory dump, with arm64 .plt call rewriting hack)' + EXEFS_FORMAT = 'Switch exefs (multiple files, for use with Mephisto)' + EXEFS_LOW_FORMAT = 'Switch exefs with 31-bit addressing (multiple files, for use with Mephisto)' + + OPT_BYPASS_PLT = 'BYPASS_PLT' + OPT_EXEFS_LOAD = 'EXEFS_LOAD' + OPT_LOAD_31_BIT = 'LOAD_31_BIT' + + FORMAT_OPTIONS = { + NRO_FORMAT: [], + NSO_FORMAT: [], + KIP_FORMAT: [], + RAW_FORMAT: [], + SDK_FORMAT: [OPT_BYPASS_PLT], + RAW_SDK_FORMAT: [OPT_BYPASS_PLT], + EXEFS_FORMAT: [OPT_EXEFS_LOAD], + EXEFS_LOW_FORMAT: [OPT_EXEFS_LOAD, OPT_LOAD_31_BIT], + } + + PRIMARY_EXEFS_NAMES = ['main', 'rtld', 'sdk'] + LOAD_EXEFS_NAMES = PRIMARY_EXEFS_NAMES + ['subsdk%d' % i for i in range(10)] + ALL_EXEFS_NAMES = LOAD_EXEFS_NAMES + ['main.npdm'] + + def get_load_formats(li, path): + li.seek(0) + if li.read(4) == 'NSO0': + if 'sdk' in os.path.basename(path): + yield { 'format': SDK_FORMAT, 'options': idaapi.ACCEPT_FIRST } + yield { 'format': NSO_FORMAT } + else: + yield { 'format': NSO_FORMAT, 'options': idaapi.ACCEPT_FIRST } + yield { 'format': SDK_FORMAT } + + li.seek(0) + if li.read(4) == 'KIP1': + yield { 'format': KIP_FORMAT, 'options': idaapi.ACCEPT_FIRST } + + li.seek(0x10) + if li.read(4) == 'NRO0': + yield { 'format': NRO_FORMAT, 'options': idaapi.ACCEPT_FIRST } + elif looks_like_memory_dump(li): + yield { 'format': RAW_FORMAT, 'options': idaapi.ACCEPT_FIRST } + yield { 'format': RAW_SDK_FORMAT } + + if os.path.basename(path) in ALL_EXEFS_NAMES: + dirname = os.path.dirname(path) + if all(os.path.exists(os.path.join(dirname, i)) for i in PRIMARY_EXEFS_NAMES): + yield { 'format': EXEFS_FORMAT } + yield { 'format': EXEFS_LOW_FORMAT } + + + + # this is mostly just to work with the IDA API + accept_formats_list = None + accept_formats_index = 0 + + def accept_file(li, path): + global accept_formats_list + global accept_formats_index + + if accept_formats_list is None: + accept_formats_list = list(get_load_formats(li, path)) + accept_formats_index = 0 + + if accept_formats_index >= len(accept_formats_list): + accept_formats_list = None + accept_formats_index = 0 + return 0 + + ret = accept_formats_list[accept_formats_index] + if not isinstance(ret, dict): + ret = { 'format': ret } + if 'options' not in ret: + ret['options'] = 1 + if 'processor' not in ret: + ret['processor'] = 'arm' + ret['options'] |= 1 | idaapi.ACCEPT_CONTINUE + + accept_formats_index += 1 + + return ret + + def ida_make_offset(f, ea): + if f.armv7: + idc.MakeDword(ea) + else: + idc.MakeQword(ea) + idc.OpOff(ea, 0, 0) + + def find_bl_targets(text_start, text_end): + targets = set() + for pco in xrange(0, text_end - text_start, 4): + pc = text_start + pco + d = Dword(pc) + if (d & 0xfc000000) == 0x94000000: + imm = d & 0x3ffffff + if imm & 0x2000000: + imm |= ~0x1ffffff + if 0 <= imm <= 2: + continue + target = pc + imm * 4 + if target >= text_start and target < text_end: + targets.add(target) + return targets + + def load_file(li, neflags, fmt): + idaapi.set_processor_type('arm', SETPROC_ALL|SETPROC_FATAL) + + options = FORMAT_OPTIONS[fmt] + + if OPT_EXEFS_LOAD in options: + ret = load_as_exefs(li, options) + else: + ret = load_one_file(li, options, 0) + + eh_parse = idaapi.find_plugin('eh_parse', True) + if eh_parse: + print 'eh_parse ->', idaapi.run_plugin(eh_parse, 0) + else: + print 'warning: eh_parse missing' + + return ret + + def load_as_exefs(li, options): + dirname = os.path.dirname(idc.get_input_file_path()) + binaries = LOAD_EXEFS_NAMES + binaries = [os.path.join(dirname, i) for i in binaries] + binaries = [i for i in binaries if os.path.exists(i)] + for idx, fname in enumerate(binaries): + with open(fname, 'rb') as f: + if not load_one_file(f, options, idx, os.path.basename(fname)): + return False + return True + + def load_one_file(li, options, idx, basename=None): + bypass_plt = OPT_BYPASS_PLT in options + + f = load_nxo(li) + + if idx == 0: + if f.armv7: + idc.SetShortPrm(idc.INF_LFLAGS, idc.GetShortPrm(idc.INF_LFLAGS) | idc.LFLG_PC_FLAT) + else: + idc.SetShortPrm(idc.INF_LFLAGS, idc.GetShortPrm(idc.INF_LFLAGS) | idc.LFLG_64BIT) + + idc.SetCharPrm(idc.INF_DEMNAMES, idaapi.DEMNAM_GCC3) + idaapi.set_compiler_id(idaapi.COMP_GNU) + idaapi.add_til2('gnulnx_arm' if f.armv7 else 'gnulnx_arm64', 1) + # don't create tails + idc.set_inf_attr(idc.INF_AF, idc.get_inf_attr(idc.INF_AF) & ~idc.AF_FTAIL) + + if OPT_LOAD_31_BIT in options: + loadbase = 0x8000000 + step = 0x1000000 + elif f.armv7: + loadbase = 0x60000000 + step = 0x10000000 + else: + loadbase = 0x7100000000 + step = 0x100000000 + loadbase += idx * step + + f.binfile.seek(0) + as_string = f.binfile.read(f.bssoff) + idaapi.mem2base(as_string, loadbase) + + seg_prefix = basename if basename is not None else '' + for start, end, name, kind in f.sections: + if name.startswith('.got'): + kind = 'CONST' + idaapi.add_segm(0, loadbase+start, loadbase+end, seg_prefix+name, kind) + segm = idaapi.get_segm_by_name(seg_prefix+name) + if kind == 'CONST': + segm.perm = idaapi.SEGPERM_READ + elif kind == 'CODE': + segm.perm = idaapi.SEGPERM_READ | idaapi.SEGPERM_EXEC + elif kind == 'DATA': + segm.perm = idaapi.SEGPERM_READ | idaapi.SEGPERM_WRITE + elif kind == 'BSS': + segm.perm = idaapi.SEGPERM_READ | idaapi.SEGPERM_WRITE + idaapi.update_segm(segm) + idaapi.set_segm_addressing(segm, 1 if f.armv7 else 2) + + # do imports + # TODO: can we make imports show up in "Imports" window? + undef_count = 0 + for s in f.symbols: + if not s.shndx and s.name: + undef_count += 1 + last_ea = max(loadbase + end for start, end, name, kind in f.sections) + undef_entry_size = 8 + undef_ea = ((last_ea + 0xFFF) & ~0xFFF) + undef_entry_size # plus 8 so we don't end up on the "end" symbol + + undef_seg = basename + '.UNDEF' if basename is not None else 'UNDEF' + idaapi.add_segm(0, undef_ea, undef_ea+undef_count*undef_entry_size, undef_seg, 'XTRN') + segm = idaapi.get_segm_by_name(undef_seg) + segm.type = idaapi.SEG_XTRN + idaapi.update_segm(segm) + for i,s in enumerate(f.symbols): + if not s.shndx and s.name: + idc.MakeQword(undef_ea) + idaapi.do_name_anyway(undef_ea, s.name) + s.resolved = undef_ea + undef_ea += undef_entry_size + elif i != 0: + assert s.shndx + s.resolved = loadbase + s.value + if s.name: + if s.type == STT_FUNC: + idaapi.add_entry(s.resolved, s.resolved, s.name, 0) + else: + idaapi.do_name_anyway(s.resolved, s.name) + + else: + # NULL symbol + s.resolved = 0 + + funcs = set() + for s in f.symbols: + if s.name and s.shndx and s.value: + if s.type == STT_FUNC: + funcs.add(loadbase+s.value) + symend = loadbase+s.value+s.size + if Dword(symend) != 0: + funcs.add(symend) + + got_name_lookup = {} + for offset, r_type, sym, addend in f.relocations: + target = offset + loadbase + if r_type in (R_ARM_GLOB_DAT, R_ARM_JUMP_SLOT, R_ARM_ABS32): + if not sym: + print 'error: relocation at %X failed' % target + else: + idaapi.put_long(target, sym.resolved) + elif r_type == R_ARM_RELATIVE: + idaapi.put_long(target, idaapi.get_long(target) + loadbase) + elif r_type in (R_AARCH64_GLOB_DAT, R_AARCH64_JUMP_SLOT, R_AARCH64_ABS64): + idaapi.put_qword(target, sym.resolved + addend) + if addend == 0: + got_name_lookup[offset] = sym.name + elif r_type == R_AARCH64_RELATIVE: + idaapi.put_qword(target, loadbase + addend) + if addend < f.textsize: + funcs.add(loadbase + addend) + elif r_type == R_FAKE_RELR: + assert not f.armv7 # TODO + addend = idaapi.get_qword(target) + idaapi.put_qword(target, addend + loadbase) + if addend < f.textsize: + funcs.add(loadbase + addend) + else: + print 'TODO r_type %d' % (r_type,) + ida_make_offset(f, target) + + for func, target in f.plt_entries: + if target in got_name_lookup: + addr = loadbase + func + funcs.add(addr) + idaapi.do_name_anyway(addr, got_name_lookup[target]) + + if not f.armv7: + funcs |= find_bl_targets(loadbase, loadbase+f.textsize) + + if bypass_plt: + plt_lookup = f.plt_lookup + for pco in xrange(0, f.textsize, 4): + pc = loadbase + pco + d = Dword(pc) + if (d & 0x7c000000) == (0x94000000 & 0x7c000000): + imm = d & 0x3ffffff + if imm & 0x2000000: + imm |= ~0x1ffffff + if 0 <= imm <= 2: + continue + target = (pc + imm * 4) - loadbase + if target in plt_lookup: + new_target = plt_lookup[target] + loadbase + new_instr = (d & ~0x3ffffff) | (((new_target - pc) / 4) & 0x3ffffff) + idaapi.put_long(pc, new_instr) + + for pco in xrange(0, f.textsize, 4): + pc = loadbase + pco + d = Dword(pc) + if d == 0x14000001: + funcs.add(pc + 4) + + for pc, _ in f.eh_table: + funcs.add(loadbase + pc) + + for addr in sorted(funcs, reverse=True): + idaapi.auto_make_proc(addr) + + return 1 diff --git a/Source/Atmosphere-Patches/secmon_define_emc_access_table.inc b/Source/Atmosphere-Patches/secmon_define_emc_access_table.inc new file mode 100644 index 00000000..161f77c6 --- /dev/null +++ b/Source/Atmosphere-Patches/secmon_define_emc_access_table.inc @@ -0,0 +1,25 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#define __ACCESS_TABLE_NAME__ EmcAccessTable +#define __ACCESS_TABLE_ADDRESS__ MemoryRegionPhysicalDeviceExternalMemoryController.GetAddress() +#define __ACCESS_TABLE_INC__ "secmon_emc_access_table_data.inc" + +#include "secmon_define_access_table.inc" + +#undef __ACCESS_TABLE_INC__ +#undef __ACCESS_TABLE_ADDRESS__ +#undef __ACCESS_TABLE_NAME__ diff --git a/Source/Atmosphere-Patches/secmon_emc_access_table_data.inc b/Source/Atmosphere-Patches/secmon_emc_access_table_data.inc new file mode 100644 index 00000000..6089047e --- /dev/null +++ b/Source/Atmosphere-Patches/secmon_emc_access_table_data.inc @@ -0,0 +1,967 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +SetRegisterAllowed(0x0); +SetRegisterAllowed(0x4); +SetRegisterAllowed(0x8); +SetRegisterAllowed(0xC); +SetRegisterAllowed(0x10); +SetRegisterAllowed(0x14); +SetRegisterAllowed(0x18); +SetRegisterAllowed(0x1C); +SetRegisterAllowed(0x20); +SetRegisterAllowed(0x24); +SetRegisterAllowed(0x28); +SetRegisterAllowed(0x2C); +SetRegisterAllowed(0x30); +SetRegisterAllowed(0x34); +SetRegisterAllowed(0x38); +SetRegisterAllowed(0x3C); +SetRegisterAllowed(0x40); +SetRegisterAllowed(0x44); +SetRegisterAllowed(0x48); +SetRegisterAllowed(0x4C); +SetRegisterAllowed(0x50); +SetRegisterAllowed(0x54); +SetRegisterAllowed(0x58); +SetRegisterAllowed(0x5C); +SetRegisterAllowed(0x60); +SetRegisterAllowed(0x64); +SetRegisterAllowed(0x68); +SetRegisterAllowed(0x6C); +SetRegisterAllowed(0x70); +SetRegisterAllowed(0x74); +SetRegisterAllowed(0x78); +SetRegisterAllowed(0x7C); +SetRegisterAllowed(0x80); +SetRegisterAllowed(0x84); +SetRegisterAllowed(0x88); +SetRegisterAllowed(0x8C); +SetRegisterAllowed(0x90); +SetRegisterAllowed(0x94); +SetRegisterAllowed(0x98); +SetRegisterAllowed(0x9C); +SetRegisterAllowed(0xA0); +SetRegisterAllowed(0xA4); +SetRegisterAllowed(0xA8); +SetRegisterAllowed(0xAC); +SetRegisterAllowed(0xB0); +SetRegisterAllowed(0xB4); +SetRegisterAllowed(0xB8); +SetRegisterAllowed(0xBC); +SetRegisterAllowed(0xC0); +SetRegisterAllowed(0xC4); +SetRegisterAllowed(0xC8); +SetRegisterAllowed(0xCC); +SetRegisterAllowed(0xD0); +SetRegisterAllowed(0xD4); +SetRegisterAllowed(0xD8); +SetRegisterAllowed(0xDC); +SetRegisterAllowed(0xE0); +SetRegisterAllowed(0xE4); +SetRegisterAllowed(0xE8); +SetRegisterAllowed(0xEC); +SetRegisterAllowed(0xF0); +SetRegisterAllowed(0xF4); +SetRegisterAllowed(0xF8); +SetRegisterAllowed(0xFC); +SetRegisterAllowed(0x100); +SetRegisterAllowed(0x104); +SetRegisterAllowed(0x108); +SetRegisterAllowed(0x10C); +SetRegisterAllowed(0x110); +SetRegisterAllowed(0x114); +SetRegisterAllowed(0x118); +SetRegisterAllowed(0x11C); +SetRegisterAllowed(0x120); +SetRegisterAllowed(0x124); +SetRegisterAllowed(0x128); +SetRegisterAllowed(0x12C); +SetRegisterAllowed(0x130); +SetRegisterAllowed(0x134); +SetRegisterAllowed(0x138); +SetRegisterAllowed(0x13C); +SetRegisterAllowed(0x140); +SetRegisterAllowed(0x144); +SetRegisterAllowed(0x148); +SetRegisterAllowed(0x14C); +SetRegisterAllowed(0x150); +SetRegisterAllowed(0x154); +SetRegisterAllowed(0x158); +SetRegisterAllowed(0x15C); +SetRegisterAllowed(0x160); +SetRegisterAllowed(0x164); +SetRegisterAllowed(0x168); +SetRegisterAllowed(0x16C); +SetRegisterAllowed(0x170); +SetRegisterAllowed(0x174); +SetRegisterAllowed(0x178); +SetRegisterAllowed(0x17C); +SetRegisterAllowed(0x180); +SetRegisterAllowed(0x184); +SetRegisterAllowed(0x188); +SetRegisterAllowed(0x18C); +SetRegisterAllowed(0x190); +SetRegisterAllowed(0x194); +SetRegisterAllowed(0x198); +SetRegisterAllowed(0x19C); +SetRegisterAllowed(0x1A0); +SetRegisterAllowed(0x1A4); +SetRegisterAllowed(0x1A8); +SetRegisterAllowed(0x1AC); +SetRegisterAllowed(0x1B0); +SetRegisterAllowed(0x1B4); +SetRegisterAllowed(0x1B8); +SetRegisterAllowed(0x1BC); +SetRegisterAllowed(0x1C0); +SetRegisterAllowed(0x1C4); +SetRegisterAllowed(0x1C8); +SetRegisterAllowed(0x1CC); +SetRegisterAllowed(0x1D0); +SetRegisterAllowed(0x1D4); +SetRegisterAllowed(0x1D8); +SetRegisterAllowed(0x1DC); +SetRegisterAllowed(0x1E0); +SetRegisterAllowed(0x1E4); +SetRegisterAllowed(0x1E8); +SetRegisterAllowed(0x1EC); +SetRegisterAllowed(0x1F0); +SetRegisterAllowed(0x1F4); +SetRegisterAllowed(0x1F8); +SetRegisterAllowed(0x1FC); +SetRegisterAllowed(0x200); +SetRegisterAllowed(0x204); +SetRegisterAllowed(0x208); +SetRegisterAllowed(0x20C); +SetRegisterAllowed(0x210); +SetRegisterAllowed(0x214); +SetRegisterAllowed(0x218); +SetRegisterAllowed(0x21C); +SetRegisterAllowed(0x220); +SetRegisterAllowed(0x224); +SetRegisterAllowed(0x228); +SetRegisterAllowed(0x22C); +SetRegisterAllowed(0x230); +SetRegisterAllowed(0x234); +SetRegisterAllowed(0x238); +SetRegisterAllowed(0x23C); +SetRegisterAllowed(0x240); +SetRegisterAllowed(0x244); +SetRegisterAllowed(0x248); +SetRegisterAllowed(0x24C); +SetRegisterAllowed(0x250); +SetRegisterAllowed(0x254); +SetRegisterAllowed(0x258); +SetRegisterAllowed(0x25C); +SetRegisterAllowed(0x260); +SetRegisterAllowed(0x264); +SetRegisterAllowed(0x268); +SetRegisterAllowed(0x26C); +SetRegisterAllowed(0x270); +SetRegisterAllowed(0x274); +SetRegisterAllowed(0x278); +SetRegisterAllowed(0x27C); +SetRegisterAllowed(0x280); +SetRegisterAllowed(0x284); +SetRegisterAllowed(0x288); +SetRegisterAllowed(0x28C); +SetRegisterAllowed(0x290); +SetRegisterAllowed(0x294); +SetRegisterAllowed(0x298); +SetRegisterAllowed(0x29C); +SetRegisterAllowed(0x2A0); +SetRegisterAllowed(0x2A4); +SetRegisterAllowed(0x2A8); +SetRegisterAllowed(0x2AC); +SetRegisterAllowed(0x2B0); +SetRegisterAllowed(0x2B4); +SetRegisterAllowed(0x2B8); +SetRegisterAllowed(0x2BC); +SetRegisterAllowed(0x2C0); +SetRegisterAllowed(0x2C4); +SetRegisterAllowed(0x2C8); +SetRegisterAllowed(0x2CC); +SetRegisterAllowed(0x2D0); +SetRegisterAllowed(0x2D4); +SetRegisterAllowed(0x2D8); +SetRegisterAllowed(0x2DC); +SetRegisterAllowed(0x2E0); +SetRegisterAllowed(0x2E4); +SetRegisterAllowed(0x2E8); +SetRegisterAllowed(0x2EC); +SetRegisterAllowed(0x2F0); +SetRegisterAllowed(0x2F4); +SetRegisterAllowed(0x2F8); +SetRegisterAllowed(0x2FC); +SetRegisterAllowed(0x300); +SetRegisterAllowed(0x304); +SetRegisterAllowed(0x308); +SetRegisterAllowed(0x30C); +SetRegisterAllowed(0x310); +SetRegisterAllowed(0x314); +SetRegisterAllowed(0x318); +SetRegisterAllowed(0x31C); +SetRegisterAllowed(0x320); +SetRegisterAllowed(0x324); +SetRegisterAllowed(0x328); +SetRegisterAllowed(0x32C); +SetRegisterAllowed(0x330); +SetRegisterAllowed(0x334); +SetRegisterAllowed(0x338); +SetRegisterAllowed(0x33C); +SetRegisterAllowed(0x340); +SetRegisterAllowed(0x344); +SetRegisterAllowed(0x348); +SetRegisterAllowed(0x34C); +SetRegisterAllowed(0x350); +SetRegisterAllowed(0x354); +SetRegisterAllowed(0x358); +SetRegisterAllowed(0x35C); +SetRegisterAllowed(0x360); +SetRegisterAllowed(0x364); +SetRegisterAllowed(0x368); +SetRegisterAllowed(0x36C); +SetRegisterAllowed(0x370); +SetRegisterAllowed(0x374); +SetRegisterAllowed(0x378); +SetRegisterAllowed(0x37C); +SetRegisterAllowed(0x380); +SetRegisterAllowed(0x384); +SetRegisterAllowed(0x388); +SetRegisterAllowed(0x38C); +SetRegisterAllowed(0x390); +SetRegisterAllowed(0x394); +SetRegisterAllowed(0x398); +SetRegisterAllowed(0x39C); +SetRegisterAllowed(0x3A0); +SetRegisterAllowed(0x3A4); +SetRegisterAllowed(0x3A8); +SetRegisterAllowed(0x3AC); +SetRegisterAllowed(0x3B0); +SetRegisterAllowed(0x3B4); +SetRegisterAllowed(0x3B8); +SetRegisterAllowed(0x3BC); +SetRegisterAllowed(0x3C0); +SetRegisterAllowed(0x3C4); +SetRegisterAllowed(0x3C8); +SetRegisterAllowed(0x3CC); +SetRegisterAllowed(0x3D0); +SetRegisterAllowed(0x3D4); +SetRegisterAllowed(0x3D8); +SetRegisterAllowed(0x3DC); +SetRegisterAllowed(0x3E0); +SetRegisterAllowed(0x3E4); +SetRegisterAllowed(0x3E8); +SetRegisterAllowed(0x3EC); +SetRegisterAllowed(0x3F0); +SetRegisterAllowed(0x3F4); +SetRegisterAllowed(0x3F8); +SetRegisterAllowed(0x3FC); +SetRegisterAllowed(0x400); +SetRegisterAllowed(0x404); +SetRegisterAllowed(0x408); +SetRegisterAllowed(0x40C); +SetRegisterAllowed(0x410); +SetRegisterAllowed(0x414); +SetRegisterAllowed(0x418); +SetRegisterAllowed(0x41C); +SetRegisterAllowed(0x420); +SetRegisterAllowed(0x424); +SetRegisterAllowed(0x428); +SetRegisterAllowed(0x42C); +SetRegisterAllowed(0x430); +SetRegisterAllowed(0x434); +SetRegisterAllowed(0x438); +SetRegisterAllowed(0x43C); +SetRegisterAllowed(0x440); +SetRegisterAllowed(0x444); +SetRegisterAllowed(0x448); +SetRegisterAllowed(0x44C); +SetRegisterAllowed(0x450); +SetRegisterAllowed(0x454); +SetRegisterAllowed(0x458); +SetRegisterAllowed(0x45C); +SetRegisterAllowed(0x460); +SetRegisterAllowed(0x464); +SetRegisterAllowed(0x468); +SetRegisterAllowed(0x46C); +SetRegisterAllowed(0x470); +SetRegisterAllowed(0x474); +SetRegisterAllowed(0x478); +SetRegisterAllowed(0x47C); +SetRegisterAllowed(0x480); +SetRegisterAllowed(0x484); +SetRegisterAllowed(0x488); +SetRegisterAllowed(0x48C); +SetRegisterAllowed(0x490); +SetRegisterAllowed(0x494); +SetRegisterAllowed(0x498); +SetRegisterAllowed(0x49C); +SetRegisterAllowed(0x4A0); +SetRegisterAllowed(0x4A4); +SetRegisterAllowed(0x4A8); +SetRegisterAllowed(0x4AC); +SetRegisterAllowed(0x4B0); +SetRegisterAllowed(0x4B4); +SetRegisterAllowed(0x4B8); +SetRegisterAllowed(0x4BC); +SetRegisterAllowed(0x4C0); +SetRegisterAllowed(0x4C4); +SetRegisterAllowed(0x4C8); +SetRegisterAllowed(0x4CC); +SetRegisterAllowed(0x4D0); +SetRegisterAllowed(0x4D4); +SetRegisterAllowed(0x4D8); +SetRegisterAllowed(0x4DC); +SetRegisterAllowed(0x4E0); +SetRegisterAllowed(0x4E4); +SetRegisterAllowed(0x4E8); +SetRegisterAllowed(0x4EC); +SetRegisterAllowed(0x4F0); +SetRegisterAllowed(0x4F4); +SetRegisterAllowed(0x4F8); +SetRegisterAllowed(0x4FC); +SetRegisterAllowed(0x500); +SetRegisterAllowed(0x504); +SetRegisterAllowed(0x508); +SetRegisterAllowed(0x50C); +SetRegisterAllowed(0x510); +SetRegisterAllowed(0x514); +SetRegisterAllowed(0x518); +SetRegisterAllowed(0x51C); +SetRegisterAllowed(0x520); +SetRegisterAllowed(0x524); +SetRegisterAllowed(0x528); +SetRegisterAllowed(0x52C); +SetRegisterAllowed(0x530); +SetRegisterAllowed(0x534); +SetRegisterAllowed(0x538); +SetRegisterAllowed(0x53C); +SetRegisterAllowed(0x540); +SetRegisterAllowed(0x544); +SetRegisterAllowed(0x548); +SetRegisterAllowed(0x54C); +SetRegisterAllowed(0x550); +SetRegisterAllowed(0x554); +SetRegisterAllowed(0x558); +SetRegisterAllowed(0x55C); +SetRegisterAllowed(0x560); +SetRegisterAllowed(0x564); +SetRegisterAllowed(0x568); +SetRegisterAllowed(0x56C); +SetRegisterAllowed(0x570); +SetRegisterAllowed(0x574); +SetRegisterAllowed(0x578); +SetRegisterAllowed(0x57C); +SetRegisterAllowed(0x580); +SetRegisterAllowed(0x584); +SetRegisterAllowed(0x588); +SetRegisterAllowed(0x58C); +SetRegisterAllowed(0x590); +SetRegisterAllowed(0x594); +SetRegisterAllowed(0x598); +SetRegisterAllowed(0x59C); +SetRegisterAllowed(0x5A0); +SetRegisterAllowed(0x5A4); +SetRegisterAllowed(0x5A8); +SetRegisterAllowed(0x5AC); +SetRegisterAllowed(0x5B0); +SetRegisterAllowed(0x5B4); +SetRegisterAllowed(0x5B8); +SetRegisterAllowed(0x5BC); +SetRegisterAllowed(0x5C0); +SetRegisterAllowed(0x5C4); +SetRegisterAllowed(0x5C8); +SetRegisterAllowed(0x5CC); +SetRegisterAllowed(0x5D0); +SetRegisterAllowed(0x5D4); +SetRegisterAllowed(0x5D8); +SetRegisterAllowed(0x5DC); +SetRegisterAllowed(0x5E0); +SetRegisterAllowed(0x5E4); +SetRegisterAllowed(0x5E8); +SetRegisterAllowed(0x5EC); +SetRegisterAllowed(0x5F0); +SetRegisterAllowed(0x5F4); +SetRegisterAllowed(0x5F8); +SetRegisterAllowed(0x5FC); +SetRegisterAllowed(0x600); +SetRegisterAllowed(0x604); +SetRegisterAllowed(0x608); +SetRegisterAllowed(0x60C); +SetRegisterAllowed(0x610); +SetRegisterAllowed(0x614); +SetRegisterAllowed(0x618); +SetRegisterAllowed(0x61C); +SetRegisterAllowed(0x620); +SetRegisterAllowed(0x624); +SetRegisterAllowed(0x628); +SetRegisterAllowed(0x62C); +SetRegisterAllowed(0x630); +SetRegisterAllowed(0x634); +SetRegisterAllowed(0x638); +SetRegisterAllowed(0x63C); +SetRegisterAllowed(0x640); +SetRegisterAllowed(0x644); +SetRegisterAllowed(0x648); +SetRegisterAllowed(0x64C); +SetRegisterAllowed(0x650); +SetRegisterAllowed(0x654); +SetRegisterAllowed(0x658); +SetRegisterAllowed(0x65C); +SetRegisterAllowed(0x660); +SetRegisterAllowed(0x664); +SetRegisterAllowed(0x668); +SetRegisterAllowed(0x66C); +SetRegisterAllowed(0x670); +SetRegisterAllowed(0x674); +SetRegisterAllowed(0x678); +SetRegisterAllowed(0x67C); +SetRegisterAllowed(0x680); +SetRegisterAllowed(0x684); +SetRegisterAllowed(0x688); +SetRegisterAllowed(0x68C); +SetRegisterAllowed(0x690); +SetRegisterAllowed(0x694); +SetRegisterAllowed(0x698); +SetRegisterAllowed(0x69C); +SetRegisterAllowed(0x6A0); +SetRegisterAllowed(0x6A4); +SetRegisterAllowed(0x6A8); +SetRegisterAllowed(0x6AC); +SetRegisterAllowed(0x6B0); +SetRegisterAllowed(0x6B4); +SetRegisterAllowed(0x6B8); +SetRegisterAllowed(0x6BC); +SetRegisterAllowed(0x6C0); +SetRegisterAllowed(0x6C4); +SetRegisterAllowed(0x6C8); +SetRegisterAllowed(0x6CC); +SetRegisterAllowed(0x6D0); +SetRegisterAllowed(0x6D4); +SetRegisterAllowed(0x6D8); +SetRegisterAllowed(0x6DC); +SetRegisterAllowed(0x6E0); +SetRegisterAllowed(0x6E4); +SetRegisterAllowed(0x6E8); +SetRegisterAllowed(0x6EC); +SetRegisterAllowed(0x6F0); +SetRegisterAllowed(0x6F4); +SetRegisterAllowed(0x6F8); +SetRegisterAllowed(0x6FC); +SetRegisterAllowed(0x700); +SetRegisterAllowed(0x704); +SetRegisterAllowed(0x708); +SetRegisterAllowed(0x70C); +SetRegisterAllowed(0x710); +SetRegisterAllowed(0x714); +SetRegisterAllowed(0x718); +SetRegisterAllowed(0x71C); +SetRegisterAllowed(0x720); +SetRegisterAllowed(0x724); +SetRegisterAllowed(0x728); +SetRegisterAllowed(0x72C); +SetRegisterAllowed(0x730); +SetRegisterAllowed(0x734); +SetRegisterAllowed(0x738); +SetRegisterAllowed(0x73C); +SetRegisterAllowed(0x740); +SetRegisterAllowed(0x744); +SetRegisterAllowed(0x748); +SetRegisterAllowed(0x74C); +SetRegisterAllowed(0x750); +SetRegisterAllowed(0x754); +SetRegisterAllowed(0x758); +SetRegisterAllowed(0x75C); +SetRegisterAllowed(0x760); +SetRegisterAllowed(0x764); +SetRegisterAllowed(0x768); +SetRegisterAllowed(0x76C); +SetRegisterAllowed(0x770); +SetRegisterAllowed(0x774); +SetRegisterAllowed(0x778); +SetRegisterAllowed(0x77C); +SetRegisterAllowed(0x780); +SetRegisterAllowed(0x784); +SetRegisterAllowed(0x788); +SetRegisterAllowed(0x78C); +SetRegisterAllowed(0x790); +SetRegisterAllowed(0x794); +SetRegisterAllowed(0x798); +SetRegisterAllowed(0x79C); +SetRegisterAllowed(0x7A0); +SetRegisterAllowed(0x7A4); +SetRegisterAllowed(0x7A8); +SetRegisterAllowed(0x7AC); +SetRegisterAllowed(0x7B0); +SetRegisterAllowed(0x7B4); +SetRegisterAllowed(0x7B8); +SetRegisterAllowed(0x7BC); +SetRegisterAllowed(0x7C0); +SetRegisterAllowed(0x7C4); +SetRegisterAllowed(0x7C8); +SetRegisterAllowed(0x7CC); +SetRegisterAllowed(0x7D0); +SetRegisterAllowed(0x7D4); +SetRegisterAllowed(0x7D8); +SetRegisterAllowed(0x7DC); +SetRegisterAllowed(0x7E0); +SetRegisterAllowed(0x7E4); +SetRegisterAllowed(0x7E8); +SetRegisterAllowed(0x7EC); +SetRegisterAllowed(0x7F0); +SetRegisterAllowed(0x7F4); +SetRegisterAllowed(0x7F8); +SetRegisterAllowed(0x7FC); +SetRegisterAllowed(0x800); +SetRegisterAllowed(0x804); +SetRegisterAllowed(0x808); +SetRegisterAllowed(0x80C); +SetRegisterAllowed(0x810); +SetRegisterAllowed(0x814); +SetRegisterAllowed(0x818); +SetRegisterAllowed(0x81C); +SetRegisterAllowed(0x820); +SetRegisterAllowed(0x824); +SetRegisterAllowed(0x828); +SetRegisterAllowed(0x82C); +SetRegisterAllowed(0x830); +SetRegisterAllowed(0x834); +SetRegisterAllowed(0x838); +SetRegisterAllowed(0x83C); +SetRegisterAllowed(0x840); +SetRegisterAllowed(0x844); +SetRegisterAllowed(0x848); +SetRegisterAllowed(0x84C); +SetRegisterAllowed(0x850); +SetRegisterAllowed(0x854); +SetRegisterAllowed(0x858); +SetRegisterAllowed(0x85C); +SetRegisterAllowed(0x860); +SetRegisterAllowed(0x864); +SetRegisterAllowed(0x868); +SetRegisterAllowed(0x86C); +SetRegisterAllowed(0x870); +SetRegisterAllowed(0x874); +SetRegisterAllowed(0x878); +SetRegisterAllowed(0x87C); +SetRegisterAllowed(0x880); +SetRegisterAllowed(0x884); +SetRegisterAllowed(0x888); +SetRegisterAllowed(0x88C); +SetRegisterAllowed(0x890); +SetRegisterAllowed(0x894); +SetRegisterAllowed(0x898); +SetRegisterAllowed(0x89C); +SetRegisterAllowed(0x8A0); +SetRegisterAllowed(0x8A4); +SetRegisterAllowed(0x8A8); +SetRegisterAllowed(0x8AC); +SetRegisterAllowed(0x8B0); +SetRegisterAllowed(0x8B4); +SetRegisterAllowed(0x8B8); +SetRegisterAllowed(0x8BC); +SetRegisterAllowed(0x8C0); +SetRegisterAllowed(0x8C4); +SetRegisterAllowed(0x8C8); +SetRegisterAllowed(0x8CC); +SetRegisterAllowed(0x8D0); +SetRegisterAllowed(0x8D4); +SetRegisterAllowed(0x8D8); +SetRegisterAllowed(0x8DC); +SetRegisterAllowed(0x8E0); +SetRegisterAllowed(0x8E4); +SetRegisterAllowed(0x8E8); +SetRegisterAllowed(0x8EC); +SetRegisterAllowed(0x8F0); +SetRegisterAllowed(0x8F4); +SetRegisterAllowed(0x8F8); +SetRegisterAllowed(0x8FC); +SetRegisterAllowed(0x900); +SetRegisterAllowed(0x904); +SetRegisterAllowed(0x908); +SetRegisterAllowed(0x90C); +SetRegisterAllowed(0x910); +SetRegisterAllowed(0x914); +SetRegisterAllowed(0x918); +SetRegisterAllowed(0x91C); +SetRegisterAllowed(0x920); +SetRegisterAllowed(0x924); +SetRegisterAllowed(0x928); +SetRegisterAllowed(0x92C); +SetRegisterAllowed(0x930); +SetRegisterAllowed(0x934); +SetRegisterAllowed(0x938); +SetRegisterAllowed(0x93C); +SetRegisterAllowed(0x940); +SetRegisterAllowed(0x944); +SetRegisterAllowed(0x948); +SetRegisterAllowed(0x94C); +SetRegisterAllowed(0x950); +SetRegisterAllowed(0x954); +SetRegisterAllowed(0x958); +SetRegisterAllowed(0x95C); +SetRegisterAllowed(0x960); +SetRegisterAllowed(0x964); +SetRegisterAllowed(0x968); +SetRegisterAllowed(0x96C); +SetRegisterAllowed(0x970); +SetRegisterAllowed(0x974); +SetRegisterAllowed(0x978); +SetRegisterAllowed(0x97C); +SetRegisterAllowed(0x980); +SetRegisterAllowed(0x984); +SetRegisterAllowed(0x988); +SetRegisterAllowed(0x98C); +SetRegisterAllowed(0x990); +SetRegisterAllowed(0x994); +SetRegisterAllowed(0x998); +SetRegisterAllowed(0x99C); +SetRegisterAllowed(0x9A0); +SetRegisterAllowed(0x9A4); +SetRegisterAllowed(0x9A8); +SetRegisterAllowed(0x9AC); +SetRegisterAllowed(0x9B0); +SetRegisterAllowed(0x9B4); +SetRegisterAllowed(0x9B8); +SetRegisterAllowed(0x9BC); +SetRegisterAllowed(0x9C0); +SetRegisterAllowed(0x9C4); +SetRegisterAllowed(0x9C8); +SetRegisterAllowed(0x9CC); +SetRegisterAllowed(0x9D0); +SetRegisterAllowed(0x9D4); +SetRegisterAllowed(0x9D8); +SetRegisterAllowed(0x9DC); +SetRegisterAllowed(0x9E0); +SetRegisterAllowed(0x9E4); +SetRegisterAllowed(0x9E8); +SetRegisterAllowed(0x9EC); +SetRegisterAllowed(0x9F0); +SetRegisterAllowed(0x9F4); +SetRegisterAllowed(0x9F8); +SetRegisterAllowed(0x9FC); +SetRegisterAllowed(0xA00); +SetRegisterAllowed(0xA04); +SetRegisterAllowed(0xA08); +SetRegisterAllowed(0xA0C); +SetRegisterAllowed(0xA10); +SetRegisterAllowed(0xA14); +SetRegisterAllowed(0xA18); +SetRegisterAllowed(0xA1C); +SetRegisterAllowed(0xA20); +SetRegisterAllowed(0xA24); +SetRegisterAllowed(0xA28); +SetRegisterAllowed(0xA2C); +SetRegisterAllowed(0xA30); +SetRegisterAllowed(0xA34); +SetRegisterAllowed(0xA38); +SetRegisterAllowed(0xA3C); +SetRegisterAllowed(0xA40); +SetRegisterAllowed(0xA44); +SetRegisterAllowed(0xA48); +SetRegisterAllowed(0xA4C); +SetRegisterAllowed(0xA50); +SetRegisterAllowed(0xA54); +SetRegisterAllowed(0xA58); +SetRegisterAllowed(0xA5C); +SetRegisterAllowed(0xA60); +SetRegisterAllowed(0xA64); +SetRegisterAllowed(0xA68); +SetRegisterAllowed(0xA6C); +SetRegisterAllowed(0xA70); +SetRegisterAllowed(0xA74); +SetRegisterAllowed(0xA78); +SetRegisterAllowed(0xA7C); +SetRegisterAllowed(0xA80); +SetRegisterAllowed(0xA84); +SetRegisterAllowed(0xA88); +SetRegisterAllowed(0xA8C); +SetRegisterAllowed(0xA90); +SetRegisterAllowed(0xA94); +SetRegisterAllowed(0xA98); +SetRegisterAllowed(0xA9C); +SetRegisterAllowed(0xAA0); +SetRegisterAllowed(0xAA4); +SetRegisterAllowed(0xAA8); +SetRegisterAllowed(0xAAC); +SetRegisterAllowed(0xAB0); +SetRegisterAllowed(0xAB4); +SetRegisterAllowed(0xAB8); +SetRegisterAllowed(0xABC); +SetRegisterAllowed(0xAC0); +SetRegisterAllowed(0xAC4); +SetRegisterAllowed(0xAC8); +SetRegisterAllowed(0xACC); +SetRegisterAllowed(0xAD0); +SetRegisterAllowed(0xAD4); +SetRegisterAllowed(0xAD8); +SetRegisterAllowed(0xADC); +SetRegisterAllowed(0xAE0); +SetRegisterAllowed(0xAE4); +SetRegisterAllowed(0xAE8); +SetRegisterAllowed(0xAEC); +SetRegisterAllowed(0xAF0); +SetRegisterAllowed(0xAF4); +SetRegisterAllowed(0xAF8); +SetRegisterAllowed(0xAFC); +SetRegisterAllowed(0xB00); +SetRegisterAllowed(0xB04); +SetRegisterAllowed(0xB08); +SetRegisterAllowed(0xB0C); +SetRegisterAllowed(0xB10); +SetRegisterAllowed(0xB14); +SetRegisterAllowed(0xB18); +SetRegisterAllowed(0xB1C); +SetRegisterAllowed(0xB20); +SetRegisterAllowed(0xB24); +SetRegisterAllowed(0xB28); +SetRegisterAllowed(0xB2C); +SetRegisterAllowed(0xB30); +SetRegisterAllowed(0xB34); +SetRegisterAllowed(0xB38); +SetRegisterAllowed(0xB3C); +SetRegisterAllowed(0xB40); +SetRegisterAllowed(0xB44); +SetRegisterAllowed(0xB48); +SetRegisterAllowed(0xB4C); +SetRegisterAllowed(0xB50); +SetRegisterAllowed(0xB54); +SetRegisterAllowed(0xB58); +SetRegisterAllowed(0xB5C); +SetRegisterAllowed(0xB60); +SetRegisterAllowed(0xB64); +SetRegisterAllowed(0xB68); +SetRegisterAllowed(0xB6C); +SetRegisterAllowed(0xB70); +SetRegisterAllowed(0xB74); +SetRegisterAllowed(0xB78); +SetRegisterAllowed(0xB7C); +SetRegisterAllowed(0xB80); +SetRegisterAllowed(0xB84); +SetRegisterAllowed(0xB88); +SetRegisterAllowed(0xB8C); +SetRegisterAllowed(0xB90); +SetRegisterAllowed(0xB94); +SetRegisterAllowed(0xB98); +SetRegisterAllowed(0xB9C); +SetRegisterAllowed(0xBA0); +SetRegisterAllowed(0xBA4); +SetRegisterAllowed(0xBA8); +SetRegisterAllowed(0xBAC); +SetRegisterAllowed(0xBB0); +SetRegisterAllowed(0xBB4); +SetRegisterAllowed(0xBB8); +SetRegisterAllowed(0xBBC); +SetRegisterAllowed(0xBC0); +SetRegisterAllowed(0xBC4); +SetRegisterAllowed(0xBC8); +SetRegisterAllowed(0xBCC); +SetRegisterAllowed(0xBD0); +SetRegisterAllowed(0xBD4); +SetRegisterAllowed(0xBD8); +SetRegisterAllowed(0xBDC); +SetRegisterAllowed(0xBE0); +SetRegisterAllowed(0xBE4); +SetRegisterAllowed(0xBE8); +SetRegisterAllowed(0xBEC); +SetRegisterAllowed(0xBF0); +SetRegisterAllowed(0xBF4); +SetRegisterAllowed(0xBF8); +SetRegisterAllowed(0xBFC); +SetRegisterAllowed(0xC00); +SetRegisterAllowed(0xC04); +SetRegisterAllowed(0xC08); +SetRegisterAllowed(0xC0C); +SetRegisterAllowed(0xC10); +SetRegisterAllowed(0xC14); +SetRegisterAllowed(0xC18); +SetRegisterAllowed(0xC1C); +SetRegisterAllowed(0xC20); +SetRegisterAllowed(0xC24); +SetRegisterAllowed(0xC28); +SetRegisterAllowed(0xC2C); +SetRegisterAllowed(0xC30); +SetRegisterAllowed(0xC34); +SetRegisterAllowed(0xC38); +SetRegisterAllowed(0xC3C); +SetRegisterAllowed(0xC40); +SetRegisterAllowed(0xC44); +SetRegisterAllowed(0xC48); +SetRegisterAllowed(0xC4C); +SetRegisterAllowed(0xC50); +SetRegisterAllowed(0xC54); +SetRegisterAllowed(0xC58); +SetRegisterAllowed(0xC5C); +SetRegisterAllowed(0xC60); +SetRegisterAllowed(0xC64); +SetRegisterAllowed(0xC68); +SetRegisterAllowed(0xC6C); +SetRegisterAllowed(0xC70); +SetRegisterAllowed(0xC74); +SetRegisterAllowed(0xC78); +SetRegisterAllowed(0xC7C); +SetRegisterAllowed(0xC80); +SetRegisterAllowed(0xC84); +SetRegisterAllowed(0xC88); +SetRegisterAllowed(0xC8C); +SetRegisterAllowed(0xC90); +SetRegisterAllowed(0xC94); +SetRegisterAllowed(0xC98); +SetRegisterAllowed(0xC9C); +SetRegisterAllowed(0xCA0); +SetRegisterAllowed(0xCA4); +SetRegisterAllowed(0xCA8); +SetRegisterAllowed(0xCAC); +SetRegisterAllowed(0xCB0); +SetRegisterAllowed(0xCB4); +SetRegisterAllowed(0xCB8); +SetRegisterAllowed(0xCBC); +SetRegisterAllowed(0xCC0); +SetRegisterAllowed(0xCC4); +SetRegisterAllowed(0xCC8); +SetRegisterAllowed(0xCCC); +SetRegisterAllowed(0xCD0); +SetRegisterAllowed(0xCD4); +SetRegisterAllowed(0xCD8); +SetRegisterAllowed(0xCDC); +SetRegisterAllowed(0xCE0); +SetRegisterAllowed(0xCE4); +SetRegisterAllowed(0xCE8); +SetRegisterAllowed(0xCEC); +SetRegisterAllowed(0xCF0); +SetRegisterAllowed(0xCF4); +SetRegisterAllowed(0xCF8); +SetRegisterAllowed(0xCFC); +SetRegisterAllowed(0xD00); +SetRegisterAllowed(0xD04); +SetRegisterAllowed(0xD08); +SetRegisterAllowed(0xD0C); +SetRegisterAllowed(0xD10); +SetRegisterAllowed(0xD14); +SetRegisterAllowed(0xD18); +SetRegisterAllowed(0xD1C); +SetRegisterAllowed(0xD20); +SetRegisterAllowed(0xD24); +SetRegisterAllowed(0xD28); +SetRegisterAllowed(0xD2C); +SetRegisterAllowed(0xD30); +SetRegisterAllowed(0xD34); +SetRegisterAllowed(0xD38); +SetRegisterAllowed(0xD3C); +SetRegisterAllowed(0xD40); +SetRegisterAllowed(0xD44); +SetRegisterAllowed(0xD48); +SetRegisterAllowed(0xD4C); +SetRegisterAllowed(0xD50); +SetRegisterAllowed(0xD54); +SetRegisterAllowed(0xD58); +SetRegisterAllowed(0xD5C); +SetRegisterAllowed(0xD60); +SetRegisterAllowed(0xD64); +SetRegisterAllowed(0xD68); +SetRegisterAllowed(0xD6C); +SetRegisterAllowed(0xD70); +SetRegisterAllowed(0xD74); +SetRegisterAllowed(0xD78); +SetRegisterAllowed(0xD7C); +SetRegisterAllowed(0xD80); +SetRegisterAllowed(0xD84); +SetRegisterAllowed(0xD88); +SetRegisterAllowed(0xD8C); +SetRegisterAllowed(0xD90); +SetRegisterAllowed(0xD94); +SetRegisterAllowed(0xD98); +SetRegisterAllowed(0xD9C); +SetRegisterAllowed(0xDA0); +SetRegisterAllowed(0xDA4); +SetRegisterAllowed(0xDA8); +SetRegisterAllowed(0xDAC); +SetRegisterAllowed(0xDB0); +SetRegisterAllowed(0xDB4); +SetRegisterAllowed(0xDB8); +SetRegisterAllowed(0xDBC); +SetRegisterAllowed(0xDC0); +SetRegisterAllowed(0xDC4); +SetRegisterAllowed(0xDC8); +SetRegisterAllowed(0xDCC); +SetRegisterAllowed(0xDD0); +SetRegisterAllowed(0xDD4); +SetRegisterAllowed(0xDD8); +SetRegisterAllowed(0xDDC); +SetRegisterAllowed(0xDE0); +SetRegisterAllowed(0xDE4); +SetRegisterAllowed(0xDE8); +SetRegisterAllowed(0xDEC); +SetRegisterAllowed(0xDF0); +SetRegisterAllowed(0xDF4); +SetRegisterAllowed(0xDF8); +SetRegisterAllowed(0xDFC); +SetRegisterAllowed(0xE00); +SetRegisterAllowed(0xE04); +SetRegisterAllowed(0xE08); +SetRegisterAllowed(0xE0C); +SetRegisterAllowed(0xE10); +SetRegisterAllowed(0xE14); +SetRegisterAllowed(0xE18); +SetRegisterAllowed(0xE1C); +SetRegisterAllowed(0xE20); +SetRegisterAllowed(0xE24); +SetRegisterAllowed(0xE28); +SetRegisterAllowed(0xE2C); +SetRegisterAllowed(0xE30); +SetRegisterAllowed(0xE34); +SetRegisterAllowed(0xE38); +SetRegisterAllowed(0xE3C); +SetRegisterAllowed(0xE40); +SetRegisterAllowed(0xE44); +SetRegisterAllowed(0xE48); +SetRegisterAllowed(0xE4C); +SetRegisterAllowed(0xE50); +SetRegisterAllowed(0xE54); +SetRegisterAllowed(0xE58); +SetRegisterAllowed(0xE5C); +SetRegisterAllowed(0xE60); +SetRegisterAllowed(0xE64); +SetRegisterAllowed(0xE68); +SetRegisterAllowed(0xE6C); +SetRegisterAllowed(0xE70); +SetRegisterAllowed(0xE74); +SetRegisterAllowed(0xE78); +SetRegisterAllowed(0xE7C); +SetRegisterAllowed(0xE80); +SetRegisterAllowed(0xE84); +SetRegisterAllowed(0xE88); +SetRegisterAllowed(0xE8C); +SetRegisterAllowed(0xE90); +SetRegisterAllowed(0xE94); +SetRegisterAllowed(0xE98); +SetRegisterAllowed(0xE9C); +SetRegisterAllowed(0xEA0); +SetRegisterAllowed(0xEA4); +SetRegisterAllowed(0xEA8); +SetRegisterAllowed(0xEAC); +SetRegisterAllowed(0xEB0); +SetRegisterAllowed(0xEB4); +SetRegisterAllowed(0xEB8); +SetRegisterAllowed(0xEBC); +SetRegisterAllowed(0xEC0); +SetRegisterAllowed(0xEC4); +SetRegisterAllowed(0xEC8); +SetRegisterAllowed(0xECC); +SetRegisterAllowed(0xED0); +SetRegisterAllowed(0xED4); +SetRegisterAllowed(0xED8); diff --git a/Source/Atmosphere-Patches/secmon_memory_layout.hpp b/Source/Atmosphere-Patches/secmon_memory_layout.hpp new file mode 100644 index 00000000..891d9d98 --- /dev/null +++ b/Source/Atmosphere-Patches/secmon_memory_layout.hpp @@ -0,0 +1,348 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#pragma once +#include <vapours.hpp> +#include <exosphere/mmu.hpp> + +namespace ams::secmon { + + using Address = u64; + + struct MemoryRegion { + private: + Address m_start_address; + Address m_end_address; + public: + consteval MemoryRegion(Address address, size_t size) : m_start_address(address), m_end_address(address + size) { + if (m_end_address < m_start_address) { + __builtin_unreachable(); + } + } + + constexpr Address GetStartAddress() const { + return m_start_address; + } + + constexpr Address GetAddress() const { + return this->GetStartAddress(); + } + + constexpr Address GetEndAddress() const { + return m_end_address; + } + + constexpr Address GetLastAddress() const { + return m_end_address - 1; + } + + constexpr size_t GetSize() const { + return m_end_address - m_start_address; + } + + constexpr bool Contains(Address address, size_t size) const { + return m_start_address <= address && (address + size - 1) <= this->GetLastAddress(); + } + + constexpr bool Contains(const MemoryRegion &rhs) const { + return this->Contains(rhs.GetStartAddress(), rhs.GetSize()); + } + + template<typename T = void> requires (std::is_same<T, void>::value || util::is_pod<T>::value) + ALWAYS_INLINE T *GetPointer() const { + return reinterpret_cast<T *>(this->GetAddress()); + } + + template<typename T = void> requires (std::is_same<T, void>::value || util::is_pod<T>::value) + ALWAYS_INLINE T *GetEndPointer() const { + return reinterpret_cast<T *>(this->GetEndAddress()); + } + }; + + constexpr inline const MemoryRegion MemoryRegionVirtual = MemoryRegion(UINT64_C(0x1F0000000), 2_MB); + constexpr inline const MemoryRegion MemoryRegionPhysical = MemoryRegion(UINT64_C( 0x40000000), 1_GB); + constexpr inline const MemoryRegion MemoryRegionDram = MemoryRegion(UINT64_C( 0x80000000), 2_GB); + constexpr inline const MemoryRegion MemoryRegionDramHigh = MemoryRegion(MemoryRegionDram.GetEndAddress(), 2_GB); + + constexpr inline const MemoryRegion MemoryRegionDramForMarikoProgram = MemoryRegion(UINT64_C(0xC0000000), 1_GB); + constexpr inline const MemoryRegion MemoryRegionDramDcFramebuffer = MemoryRegion(UINT64_C(0xC0000000), 4_MB); + static_assert(MemoryRegionDram.Contains(MemoryRegionDramForMarikoProgram)); + static_assert(MemoryRegionDramForMarikoProgram.Contains(MemoryRegionDramDcFramebuffer)); + + constexpr inline const MemoryRegion MemoryRegionDramGpuCarveout = MemoryRegion(UINT64_C(0x80020000), UINT64_C(0x40000)); + static_assert(MemoryRegionDram.Contains(MemoryRegionDramGpuCarveout)); + + constexpr inline const MemoryRegion MemoryRegionDramDefaultKernelCarveout = MemoryRegion(UINT64_C(0x80060000), UINT64_C(0x1FFE0000)); + static_assert(MemoryRegionDram.Contains(MemoryRegionDramDefaultKernelCarveout)); + + constexpr inline const MemoryRegion MemoryRegionDramPackage2Payloads = MemoryRegion(MemoryRegionDram.GetAddress(), 8_MB); + static_assert(MemoryRegionDram.Contains(MemoryRegionDramPackage2Payloads)); + + constexpr inline const MemoryRegion MemoryRegionDramPackage2 = MemoryRegion(UINT64_C(0xA9800000), UINT64_C(0x07FC0000)); + static_assert(MemoryRegionDram.Contains(MemoryRegionDramPackage2)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalIram = MemoryRegion(UINT64_C(0x40000000), 0x40000); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzram = MemoryRegion(UINT64_C(0x7C010000), 0x10000); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramMariko = MemoryRegion(UINT64_C(0x7C010000), 0x40000); + static_assert(MemoryRegionPhysical.Contains(MemoryRegionPhysicalIram)); + static_assert(MemoryRegionPhysical.Contains(MemoryRegionPhysicalTzram)); + static_assert(MemoryRegionPhysicalTzramMariko.Contains(MemoryRegionPhysicalTzram)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramVolatile(UINT64_C(0x7C010000), 0x2000); + static_assert(MemoryRegionPhysicalTzram.Contains(MemoryRegionPhysicalTzramVolatile)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramNonVolatile(UINT64_C(0x7C012000), 0xE000); + static_assert(MemoryRegionPhysicalTzram.Contains(MemoryRegionPhysicalTzramNonVolatile)); + + static_assert(MemoryRegionPhysicalTzram.GetSize() == MemoryRegionPhysicalTzramNonVolatile.GetSize() + MemoryRegionPhysicalTzramVolatile.GetSize()); + + constexpr inline const MemoryRegion MemoryRegionVirtualL1 = MemoryRegion(util::AlignDown(MemoryRegionVirtual.GetAddress(), mmu::L1EntrySize), mmu::L1EntrySize); + constexpr inline const MemoryRegion MemoryRegionPhysicalL1 = MemoryRegion(util::AlignDown(MemoryRegionPhysical.GetAddress(), mmu::L1EntrySize), mmu::L1EntrySize); + static_assert(MemoryRegionVirtualL1.Contains(MemoryRegionVirtual)); + static_assert(MemoryRegionPhysicalL1.Contains(MemoryRegionPhysical)); + + constexpr inline const MemoryRegion MemoryRegionVirtualL2 = MemoryRegion(util::AlignDown(MemoryRegionVirtual.GetAddress(), mmu::L2EntrySize), mmu::L2EntrySize); + constexpr inline const MemoryRegion MemoryRegionPhysicalIramL2 = MemoryRegion(util::AlignDown(MemoryRegionPhysicalIram.GetAddress(), mmu::L2EntrySize), mmu::L2EntrySize); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramL2 = MemoryRegion(util::AlignDown(MemoryRegionPhysicalTzram.GetAddress(), mmu::L2EntrySize), mmu::L2EntrySize); + static_assert(MemoryRegionVirtualL2.Contains(MemoryRegionVirtual)); + static_assert(MemoryRegionPhysicalIramL2.Contains(MemoryRegionPhysicalIram)); + static_assert(MemoryRegionPhysicalTzramL2.Contains(MemoryRegionPhysicalTzram)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalIramBootCode = MemoryRegion(UINT64_C(0x40020000), 0x20000); + static_assert(MemoryRegionPhysicalIram.Contains(MemoryRegionPhysicalIramBootCode)); + + constexpr inline const MemoryRegion MemoryRegionVirtualDevice = MemoryRegion(UINT64_C(0x1F0040000), UINT64_C(0x40000)); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualDevice)); + + constexpr inline const MemoryRegion MemoryRegionVirtualDeviceEmpty = MemoryRegion(MemoryRegionVirtualDevice.GetStartAddress(), 0); + + #define AMS_SECMON_FOREACH_DEVICE_REGION(HANDLER, ...) \ + HANDLER(GicDistributor, Empty, UINT64_C(0x50041000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(GicCpuInterface, GicDistributor, UINT64_C(0x50042000), UINT64_C(0x2000), true, ## __VA_ARGS__) \ + HANDLER(Uart, GicCpuInterface, UINT64_C(0x70006000), UINT64_C(0x1000), false, ## __VA_ARGS__) \ + HANDLER(ClkRst, Uart, UINT64_C(0x60006000), UINT64_C(0x1000), false, ## __VA_ARGS__) \ + HANDLER(RtcPmc, ClkRst, UINT64_C(0x7000E000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(Timer, RtcPmc, UINT64_C(0x60005000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(System, Timer, UINT64_C(0x6000C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(SecurityEngine, System, UINT64_C(0x70012000), UINT64_C(0x2000), true, ## __VA_ARGS__) \ + HANDLER(SecurityEngine2, SecurityEngine, UINT64_C(0x70412000), UINT64_C(0x2000), true, ## __VA_ARGS__) \ + HANDLER(SysCtr0, SecurityEngine2, UINT64_C(0x700F0000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(MemoryController, SysCtr0, UINT64_C(0x70019000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(ExternalMemoryController, MemoryController, UINT64_C(0x7001b000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(FuseKFuse, ExternalMemoryController, UINT64_C(0x7000F000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(ApbMisc, FuseKFuse, UINT64_C(0x70000000), UINT64_C(0x4000), true, ## __VA_ARGS__) \ + HANDLER(FlowController, ApbMisc, UINT64_C(0x60007000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(BootloaderParams, FlowController, UINT64_C(0x40000000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(I2c5, BootloaderParams, UINT64_C(0x7000D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(Gpio, I2c5, UINT64_C(0x6000D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(I2c1, Gpio, UINT64_C(0x7000C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(ExceptionVectors, I2c1, UINT64_C(0x6000F000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(MemoryController0, ExceptionVectors, UINT64_C(0x7001C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(MemoryController1, MemoryController0, UINT64_C(0x7001D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(Sdmmc, MemoryController1, UINT64_C(0x700B0000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(Disp1, Sdmmc, UINT64_C(0x54200000), UINT64_C(0x3000), true, ## __VA_ARGS__) \ + HANDLER(Dsi, Disp1, UINT64_C(0x54300000), UINT64_C(0x1000), true, ## __VA_ARGS__) \ + HANDLER(MipiCal, Dsi, UINT64_C(0x700E3000), UINT64_C(0x1000), true, ## __VA_ARGS__) + + #define DEFINE_DEVICE_REGION(_NAME_, _PREV_, _ADDRESS_, _SIZE_, _SECURE_) \ + constexpr inline const MemoryRegion MemoryRegionVirtualDevice##_NAME_ = MemoryRegion(MemoryRegionVirtualDevice##_PREV_.GetEndAddress() + 0x1000, _SIZE_); \ + constexpr inline const MemoryRegion MemoryRegionPhysicalDevice##_NAME_ = MemoryRegion(_ADDRESS_, _SIZE_); \ + static_assert(MemoryRegionVirtualDevice.Contains(MemoryRegionVirtualDevice##_NAME_)); \ + static_assert(MemoryRegionPhysical.Contains(MemoryRegionPhysicalDevice##_NAME_)); + + AMS_SECMON_FOREACH_DEVICE_REGION(DEFINE_DEVICE_REGION) + + #undef DEFINE_DEVICE_REGION + + constexpr inline const MemoryRegion MemoryRegionVirtualDeviceFuses = MemoryRegion(MemoryRegionVirtualDeviceFuseKFuse.GetAddress() + 0x800, 0x400); + constexpr inline const MemoryRegion MemoryRegionPhysicalDeviceFuses = MemoryRegion(MemoryRegionPhysicalDeviceFuseKFuse.GetAddress() + 0x800, 0x400); + static_assert(MemoryRegionVirtualDeviceFuseKFuse.Contains(MemoryRegionVirtualDeviceFuses)); + static_assert(MemoryRegionPhysicalDeviceFuseKFuse.Contains(MemoryRegionPhysicalDeviceFuses)); + + constexpr inline const MemoryRegion MemoryRegionVirtualDeviceActivityMonitor = MemoryRegion(MemoryRegionVirtualDeviceSystem.GetAddress() + 0x800, 0x400); + constexpr inline const MemoryRegion MemoryRegionPhysicalDeviceActivityMonitor = MemoryRegion(MemoryRegionPhysicalDeviceSystem.GetAddress() + 0x800, 0x400); + static_assert(MemoryRegionVirtualDeviceSystem.Contains(MemoryRegionVirtualDeviceActivityMonitor)); + static_assert(MemoryRegionPhysicalDeviceSystem.Contains(MemoryRegionPhysicalDeviceActivityMonitor)); + + constexpr inline const MemoryRegion MemoryRegionVirtualDeviceUartA = MemoryRegion(MemoryRegionVirtualDeviceUart.GetAddress() + 0x000, 0x040); + constexpr inline const MemoryRegion MemoryRegionVirtualDeviceUartB = MemoryRegion(MemoryRegionVirtualDeviceUart.GetAddress() + 0x040, 0x040); + constexpr inline const MemoryRegion MemoryRegionVirtualDeviceUartC = MemoryRegion(MemoryRegionVirtualDeviceUart.GetAddress() + 0x200, 0x100); + static_assert(MemoryRegionVirtualDeviceUart.Contains(MemoryRegionVirtualDeviceUartA)); + static_assert(MemoryRegionVirtualDeviceUart.Contains(MemoryRegionVirtualDeviceUartB)); + static_assert(MemoryRegionVirtualDeviceUart.Contains(MemoryRegionVirtualDeviceUartC)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalDeviceUartA = MemoryRegion(MemoryRegionPhysicalDeviceUart.GetAddress() + 0x000, 0x040); + constexpr inline const MemoryRegion MemoryRegionPhysicalDeviceUartB = MemoryRegion(MemoryRegionPhysicalDeviceUart.GetAddress() + 0x040, 0x040); + constexpr inline const MemoryRegion MemoryRegionPhysicalDeviceUartC = MemoryRegion(MemoryRegionPhysicalDeviceUart.GetAddress() + 0x200, 0x100); + static_assert(MemoryRegionPhysicalDeviceUart.Contains(MemoryRegionPhysicalDeviceUartA)); + static_assert(MemoryRegionPhysicalDeviceUart.Contains(MemoryRegionPhysicalDeviceUartB)); + static_assert(MemoryRegionPhysicalDeviceUart.Contains(MemoryRegionPhysicalDeviceUartC)); + + constexpr inline const MemoryRegion MemoryRegionVirtualDevicePmc = MemoryRegion(MemoryRegionVirtualDeviceRtcPmc.GetAddress() + 0x400, 0xC00); + constexpr inline const MemoryRegion MemoryRegionPhysicalDevicePmc = MemoryRegion(MemoryRegionPhysicalDeviceRtcPmc.GetAddress() + 0x400, 0xC00); + static_assert(MemoryRegionVirtualDeviceRtcPmc.Contains(MemoryRegionVirtualDevicePmc)); + static_assert(MemoryRegionPhysicalDeviceRtcPmc.Contains(MemoryRegionPhysicalDevicePmc)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramReadOnlyAlias = MemoryRegion(UINT64_C(0x1F00A0000), MemoryRegionPhysicalTzram.GetSize()); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramReadOnlyAlias = MemoryRegion(MemoryRegionPhysicalTzram.GetAddress(), MemoryRegionPhysicalTzram.GetSize()); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualTzramReadOnlyAlias)); + static_assert(MemoryRegionPhysicalTzram.Contains(MemoryRegionPhysicalTzramReadOnlyAlias)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramProgram(UINT64_C(0x1F00C0000), 0xC000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualTzramProgram)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramProgramExceptionVectors(UINT64_C(0x1F00C0000), 0x800); + static_assert(MemoryRegionVirtualTzramProgram.Contains(MemoryRegionVirtualTzramProgramExceptionVectors)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramMarikoProgram(UINT64_C(0x1F00D0000), 0x20000); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramMarikoProgram(UINT64_C(0x7C020000), 0x20000); + static_assert(MemoryRegionPhysicalTzramMariko.Contains(MemoryRegionPhysicalTzramMarikoProgram)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramMarikoProgramFatalErrorContext(UINT64_C(0x1F00EF000), 0x1000); + static_assert(MemoryRegionVirtualTzramMarikoProgram.Contains(MemoryRegionVirtualTzramMarikoProgramFatalErrorContext)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalIramFatalErrorContext(UINT64_C(0x4003E000), 0x1000); + static_assert(MemoryRegionPhysicalIram.Contains(MemoryRegionPhysicalIramFatalErrorContext)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramMarikoProgramStack(UINT64_C(0x1F00F4000), 0x8000); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramMarikoProgramStack(UINT64_C(0x7C040000), 0x8000); + static_assert(MemoryRegionPhysicalTzramMariko.Contains(MemoryRegionPhysicalTzramMarikoProgramStack)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalMarikoProgramImage(UINT64_C(0x80020000), 0x20000); + static_assert(MemoryRegionDram.Contains(MemoryRegionPhysicalMarikoProgramImage)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramProgramMain(UINT64_C(0x1F00C0800), 0xB800); + static_assert(MemoryRegionVirtualTzramProgram.Contains(MemoryRegionVirtualTzramProgramMain)); + + static_assert(MemoryRegionVirtualTzramProgram.GetSize() == MemoryRegionVirtualTzramProgramExceptionVectors.GetSize() + MemoryRegionVirtualTzramProgramMain.GetSize()); + + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramProgram(UINT64_C(0x7C012000), 0xC000); + static_assert(MemoryRegionPhysicalTzramNonVolatile.Contains(MemoryRegionPhysicalTzramProgram)); + + constexpr inline const Address PhysicalTzramProgramResetVector = MemoryRegionPhysicalTzramProgram.GetAddress() + MemoryRegionVirtualTzramProgramExceptionVectors.GetSize(); + static_assert(static_cast<u32>(PhysicalTzramProgramResetVector) == PhysicalTzramProgramResetVector); + + constexpr uintptr_t GetPhysicalTzramProgramAddress(uintptr_t virtual_address) { + return virtual_address - MemoryRegionVirtualTzramProgram.GetStartAddress() + MemoryRegionPhysicalTzramNonVolatile.GetStartAddress(); + } + + constexpr inline const MemoryRegion MemoryRegionVirtualIramSc7Work = MemoryRegion(UINT64_C(0x1F0120000), MemoryRegionPhysicalTzram.GetSize()); + constexpr inline const MemoryRegion MemoryRegionPhysicalIramSc7Work = MemoryRegion( UINT64_C(0x40020000), MemoryRegionPhysicalTzram.GetSize()); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualIramSc7Work)); + static_assert(MemoryRegionPhysicalIram.Contains(MemoryRegionPhysicalIramSc7Work)); + + constexpr inline const MemoryRegion MemoryRegionVirtualIramSc7Firmware = MemoryRegion(UINT64_C(0x1F0140000), 0x1000); + constexpr inline const MemoryRegion MemoryRegionPhysicalIramSc7Firmware = MemoryRegion( UINT64_C(0x40003000), 0x1000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualIramSc7Firmware)); + static_assert(MemoryRegionPhysicalIram.Contains(MemoryRegionPhysicalIramSc7Firmware)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalIramSecureMonitorDebug(UINT64_C(0x40034000), 0x4000); + static_assert(MemoryRegionPhysicalIram.Contains(MemoryRegionPhysicalIramSecureMonitorDebug)); + + constexpr inline const MemoryRegion MemoryRegionVirtualDebugCode = MemoryRegion(UINT64_C(0x1F0150000), 0x4000); + constexpr inline const MemoryRegion MemoryRegionPhysicalDebugCode = MemoryRegion(UINT64_C(0x40034000), 0x4000); + static_assert(MemoryRegionPhysicalIramSecureMonitorDebug.Contains(MemoryRegionPhysicalDebugCode)); + + constexpr inline const MemoryRegion MemoryRegionVirtualDebug = MemoryRegion(UINT64_C(0x1F0160000), 0x10000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualDebug)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramBootCode = MemoryRegion(UINT64_C(0x1F01C0000), 0x2000); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramBootCode = MemoryRegion( UINT64_C(0x7C010000), 0x2000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualTzramBootCode)); + static_assert(MemoryRegionPhysicalTzramVolatile.Contains(MemoryRegionPhysicalTzramBootCode)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalDramMonitorConfiguration = MemoryRegion( UINT64_C(0x8000F000), 0x1000); + + constexpr inline const MemoryRegion MemoryRegionVirtualDramSecureDataStore = MemoryRegion(UINT64_C(0x1F0100000), 0x10000); + constexpr inline const MemoryRegion MemoryRegionPhysicalDramSecureDataStore = MemoryRegion( UINT64_C(0x80010000), 0x10000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualDramSecureDataStore)); + static_assert(MemoryRegionDram.Contains(MemoryRegionPhysicalDramSecureDataStore)); + + constexpr inline const MemoryRegion MemoryRegionVirtualDramDebugDataStore = MemoryRegion(UINT64_C(0x1F0110000), 0x4000); + constexpr inline const MemoryRegion MemoryRegionPhysicalDramDebugDataStore = MemoryRegion( UINT64_C(0x8000C000), 0x4000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualDramSecureDataStore)); + static_assert(MemoryRegionDram.Contains(MemoryRegionPhysicalDramSecureDataStore)); + + constexpr inline const MemoryRegion MemoryRegionVirtualDramSdmmcMappedData = MemoryRegion(UINT64_C(0x1F0100000), 0xC000); + constexpr inline const MemoryRegion MemoryRegionPhysicalDramSdmmcMappedData = MemoryRegion(UINT64_C(0x80010000), 0xC000); + + constexpr inline const MemoryRegion MemoryRegionVirtualDramDcL0DevicePageTable = MemoryRegion(UINT64_C(0x1F010C000), 0x1000); + constexpr inline const MemoryRegion MemoryRegionPhysicalDramDcL0DevicePageTable = MemoryRegion( UINT64_C(0x8001C000), 0x1000); + + constexpr inline const MemoryRegion MemoryRegionVirtualDramSdmmc1L0DevicePageTable = MemoryRegion(UINT64_C(0x1F010E000), 0x1000); + constexpr inline const MemoryRegion MemoryRegionPhysicalDramSdmmc1L0DevicePageTable = MemoryRegion( UINT64_C(0x8001E000), 0x1000); + + constexpr inline const MemoryRegion MemoryRegionVirtualDramSdmmc1L1DevicePageTable = MemoryRegion(UINT64_C(0x1F010F000), 0x1000); + constexpr inline const MemoryRegion MemoryRegionPhysicalDramSdmmc1L1DevicePageTable = MemoryRegion( UINT64_C(0x8001F000), 0x1000); + + constexpr inline const MemoryRegion MemoryRegionVirtualDramSecureDataStoreTzram = MemoryRegion(UINT64_C(0x1F0100000), 0xE000); + constexpr inline const MemoryRegion MemoryRegionVirtualDramSecureDataStoreWarmbootFirmware = MemoryRegion(UINT64_C(0x1F010E000), 0x17C0); + constexpr inline const MemoryRegion MemoryRegionVirtualDramSecureDataStoreSecurityEngineState = MemoryRegion(UINT64_C(0x1F010F7C0), 0x0840); + static_assert(MemoryRegionVirtualDramSecureDataStore.Contains(MemoryRegionVirtualDramSecureDataStoreTzram)); + static_assert(MemoryRegionVirtualDramSecureDataStore.Contains(MemoryRegionVirtualDramSecureDataStoreWarmbootFirmware)); + static_assert(MemoryRegionVirtualDramSecureDataStore.Contains(MemoryRegionVirtualDramSecureDataStoreSecurityEngineState)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalDramSecureDataStoreTzram = MemoryRegion(UINT64_C(0x80010000), 0xE000); + constexpr inline const MemoryRegion MemoryRegionPhysicalDramSecureDataStoreWarmbootFirmware = MemoryRegion(UINT64_C(0x8001E000), 0x17C0); + constexpr inline const MemoryRegion MemoryRegionPhysicalDramSecureDataStoreSecurityEngineState = MemoryRegion(UINT64_C(0x8001F7C0), 0x0840); + static_assert(MemoryRegionPhysicalDramSecureDataStore.Contains(MemoryRegionPhysicalDramSecureDataStoreTzram)); + static_assert(MemoryRegionPhysicalDramSecureDataStore.Contains(MemoryRegionPhysicalDramSecureDataStoreWarmbootFirmware)); + static_assert(MemoryRegionPhysicalDramSecureDataStore.Contains(MemoryRegionPhysicalDramSecureDataStoreSecurityEngineState)); + + constexpr inline const MemoryRegion MemoryRegionVirtualAtmosphereIramPage = MemoryRegion(UINT64_C(0x1F01F0000), 0x1000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualAtmosphereIramPage)); + + constexpr inline const MemoryRegion MemoryRegionVirtualAtmosphereUserPage = MemoryRegion(UINT64_C(0x1F01F2000), 0x1000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualAtmosphereUserPage)); + + constexpr inline const MemoryRegion MemoryRegionVirtualSmcUserPage = MemoryRegion(UINT64_C(0x1F01F4000), 0x1000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualSmcUserPage)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramVolatileData = MemoryRegion(UINT64_C(0x1F01F6000), 0x1000); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramVolatileData = MemoryRegion( UINT64_C(0x7C010000), 0x1000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualTzramVolatileData)); + static_assert(MemoryRegionPhysicalTzramVolatile.Contains(MemoryRegionPhysicalTzramVolatileData)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramVolatileStack = MemoryRegion(UINT64_C(0x1F01F8000), 0x1000); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramVolatileStack = MemoryRegion( UINT64_C(0x7C011000), 0x1000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualTzramVolatileStack)); + static_assert(MemoryRegionPhysicalTzramVolatile.Contains(MemoryRegionPhysicalTzramVolatileStack)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramConfigurationData = MemoryRegion(UINT64_C(0x1F01FA000), 0x1000); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramConfigurationData = MemoryRegion( UINT64_C(0x7C01E000), 0x1000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualTzramConfigurationData)); + static_assert(MemoryRegionPhysicalTzramNonVolatile.Contains(MemoryRegionPhysicalTzramConfigurationData)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramL1PageTable = MemoryRegion(UINT64_C(0x1F01FCFC0), 0x40); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramL1PageTable = MemoryRegion( UINT64_C(0x7C01EFC0), 0x40); + static_assert(MemoryRegionPhysicalTzramConfigurationData.Contains(MemoryRegionPhysicalTzramL1PageTable)); + + constexpr inline const MemoryRegion MemoryRegionVirtualTzramL2L3PageTable = MemoryRegion(UINT64_C(0x1F01FE000), 0x1000); + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramL2L3PageTable = MemoryRegion( UINT64_C(0x7C01F000), 0x1000); + static_assert(MemoryRegionVirtual.Contains(MemoryRegionVirtualTzramL2L3PageTable)); + static_assert(MemoryRegionPhysicalTzramNonVolatile.Contains(MemoryRegionPhysicalTzramL2L3PageTable)); + + constexpr inline const MemoryRegion MemoryRegionPhysicalTzramFullProgramImage = MemoryRegion(UINT64_C(0x7C010800), 0xD800); + constexpr inline const MemoryRegion MemoryRegionPhysicalIramBootCodeImage = MemoryRegion(UINT64_C(0x40032000), 0xC000); + + constexpr inline const MemoryRegion MemoryRegionPhysicalIramBootCodeCode = MemoryRegion(UINT64_C(0x40032000), 0x1000); + constexpr inline const MemoryRegion MemoryRegionPhysicalIramBootCodeKeys = MemoryRegion(UINT64_C(0x40033000), 0x1000); + + constexpr inline const MemoryRegion MemoryRegionPhysicalIramWarmbootBin = MemoryRegion(UINT64_C(0x4003E000), 0x17F0); + constexpr inline const MemoryRegion MemoryRegionPhysicalIramBootConfig = MemoryRegion(UINT64_C(0x4003F800), 0x400); + + constexpr inline const MemoryRegion MemoryRegionPhysicalIramRebootStub = MemoryRegion(UINT64_C(0x4003F000), 0x1000); + +} diff --git a/Source/Atmosphere-Patches/secmon_smc_register_access.cpp b/Source/Atmosphere-Patches/secmon_smc_register_access.cpp new file mode 100644 index 00000000..eae2933c --- /dev/null +++ b/Source/Atmosphere-Patches/secmon_smc_register_access.cpp @@ -0,0 +1,196 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ +#include <exosphere.hpp> +#include "../secmon_error.hpp" +#include "secmon_smc_register_access.hpp" + +namespace ams::secmon::smc { + + namespace { + + template<size_t N> + constexpr void SetRegisterTableAllowed(std::array<u8, N> &arr, uintptr_t reg) { + /* All registers should be four byte aligned. */ + AMS_ASSUME(reg % sizeof(u32) == 0); + + /* Reduce the register to an index. */ + reg /= sizeof(u32); + + /* Get the index and mask. */ + const auto index = reg / BITSIZEOF(u8); + const auto mask = (1u << (reg % BITSIZEOF(u8))); + + /* Check that the permission bit isn't already set. */ + AMS_ASSUME((arr[index] & mask) == 0); + + /* Set the permission bit. */ + arr[index] |= mask; + + /* Ensure that indices are set in sorted order. */ + for (auto i = (reg % BITSIZEOF(u8)) + 1; i < 8; ++i) { + AMS_ASSUME((arr[index] & (1u << i)) == 0); + } + + for (auto i = index + 1; i < arr.size(); ++i) { + AMS_ASSUME(arr[i] == 0); + } + } + + template<size_t N> + consteval std::pair<size_t, size_t> GetReducedAccessTableInfo(const std::array<u8, N> &arr) { + for (int last = arr.size() - 1; last >= 0; --last) { + if (arr[last] != 0) { + const int end = last + 1; + for (int start = 0; start < end; ++start) { + if (arr[start] != 0) { + return std::make_pair(static_cast<size_t>(start), static_cast<size_t>(end)); + } + } + return std::make_pair(static_cast<size_t>(0), static_cast<size_t>(end)); + } + } + + /* All empty perm table is disallowed. */ + AMS_ASSUME(false); + } + + + template<u32 _Address, auto RawTable> + struct AccessTable { + static constexpr inline auto ReducedAccessTableInfo = GetReducedAccessTableInfo(RawTable); + static constexpr inline size_t ReducedAccessTableSize = ReducedAccessTableInfo.second - ReducedAccessTableInfo.first; + static constexpr inline auto ReducedAccessTable = []() -> std::array<u8, ReducedAccessTableSize> { + std::array<u8, ReducedAccessTableSize> reduced = {}; + + for (size_t i = ReducedAccessTableInfo.first; i < ReducedAccessTableInfo.second; ++i) { + reduced[i - ReducedAccessTableInfo.first] = RawTable[i]; + } + + return reduced; + }(); + + static constexpr u32 Address = _Address + (ReducedAccessTableInfo.first * sizeof(u32) * BITSIZEOF(u8)); + static constexpr u32 Size = static_cast<u32>(ReducedAccessTableSize * sizeof(u32) * BITSIZEOF(u8)); + + static_assert(Size <= 0x1000); + }; + + struct AccessTableEntry { + const u8 * const table; + uintptr_t virtual_address; + u32 address; + u32 size; + }; + + /* Include the access tables. */ + #include "secmon_define_pmc_access_table.inc" + #include "secmon_define_mc_access_table.inc" + #include "secmon_define_emc_access_table.inc" + #include "secmon_define_mc01_access_table.inc" + + constexpr const AccessTableEntry AccessTables[] = { + { PmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDevicePmc.GetAddress(), PmcAccessTable::Address, PmcAccessTable::Size, }, + { McAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceMemoryController.GetAddress(), McAccessTable::Address, McAccessTable::Size, }, + { EmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceExternalMemoryController.GetAddress(), EmcAccessTable::Address, EmcAccessTable::Size, }, + { Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController0.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController0.GetAddress(), Mc01AccessTable::Size, }, + { Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController1.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController1.GetAddress(), Mc01AccessTable::Size, }, + }; + + constexpr bool IsAccessAllowed(const AccessTableEntry &entry, uintptr_t address) { + /* Check if the address is within range. */ + if (!(entry.address <= address && address < entry.address + entry.size)) { + return false; + } + + /* Get the offset. */ + const auto offset = address - entry.address; + + /* Convert it to an index. */ + const auto reg_index = offset / sizeof(u32); + + /* Get the bit fields. */ + const auto index = reg_index / BITSIZEOF(u8); + const auto mask = (1u << (reg_index % BITSIZEOF(u8))); + + /* Validate that we're not going out of bounds. */ + if (index >= entry.size / sizeof(u32)) { + return false; + } + + return (entry.table[index] & mask) != 0; + } + + constexpr const AccessTableEntry *GetAccessTableEntry(uintptr_t address) { + for (const auto &entry : AccessTables) { + if (IsAccessAllowed(entry, address)) { + return std::addressof(entry); + } + } + + return nullptr; + } + + } + + SmcResult SmcReadWriteRegister(SmcArguments &args) { + /* Get the arguments. */ + const uintptr_t address = args.r[1]; + const u32 mask = args.r[2]; + const u32 value = args.r[3]; + + /* Validate that the address is aligned. */ + SMC_R_UNLESS(util::IsAligned(address, alignof(u32)), InvalidArgument); + + /* Find the access table. */ + const AccessTableEntry * const entry = GetAccessTableEntry(address); + + /* Translate our entry into an address to access. */ + uintptr_t virtual_address = 0; + if (entry != nullptr) { + /* Get the address to read or write. */ + virtual_address = entry->virtual_address + (address - entry->address); + } else { + /* For no clearly discernable reason, SmcReadWriteRegister returns success despite not doing the read/write */ + /* when accessing the SMMU controls for the BPMP and for APB-DMA. */ + /* This is "probably" to fuck with hackers who got access to the SMC and are trying to get control of the */ + /* BPMP to exploit jamais vu, deja vu, or other related DMA/wake-from-sleep vulnerabilities. */ + constexpr uintptr_t MC = MemoryRegionPhysicalDeviceMemoryController.GetAddress(); + SMC_R_UNLESS((address == (MC + MC_SMMU_AVPC_ASID) || address == (MC + MC_SMMU_PPCS1_ASID)), InvalidArgument); + + /* For backwards compatibility, we'll allow access to these devices on 1.0.0. */ + if (GetTargetFirmware() < TargetFirmware_2_0_0) { + virtual_address = MemoryRegionVirtualDeviceMemoryController.GetAddress() + (address - MC); + } + } + + /* Perform the read or write, if we should. */ + if (virtual_address != 0) { + u32 out = 0; + + if (mask != ~static_cast<u32>(0)) { + out = reg::Read(virtual_address); + } + if (mask != static_cast<u32>(0)) { + reg::Write(virtual_address, (out & ~mask) | (value & mask)); + } + + args.r[1] = out; + } + + return SmcResult::Success; + } + +} diff --git a/Source/Atmosphere/stratosphere/loader/source/ldr_meta.cpp b/Source/KIP/stratosphere/loader/source/ldr_meta.cpp similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/ldr_meta.cpp rename to Source/KIP/stratosphere/loader/source/ldr_meta.cpp diff --git a/Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp b/Source/KIP/stratosphere/loader/source/ldr_process_creation.cpp similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/ldr_process_creation.cpp rename to Source/KIP/stratosphere/loader/source/ldr_process_creation.cpp diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/Makefile b/Source/KIP/stratosphere/loader/source/oc/Makefile similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/oc/Makefile rename to Source/KIP/stratosphere/loader/source/oc/Makefile diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp b/Source/KIP/stratosphere/loader/source/oc/customize.cpp similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp rename to Source/KIP/stratosphere/loader/source/oc/customize.cpp diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp b/Source/KIP/stratosphere/loader/source/oc/customize.hpp similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/oc/customize.hpp rename to Source/KIP/stratosphere/loader/source/oc/customize.hpp diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_table.hpp b/Source/KIP/stratosphere/loader/source/oc/mtc_timing_table.hpp similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_table.hpp rename to Source/KIP/stratosphere/loader/source/oc/mtc_timing_table.hpp diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_value.hpp b/Source/KIP/stratosphere/loader/source/oc/mtc_timing_value.hpp similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/oc/mtc_timing_value.hpp rename to Source/KIP/stratosphere/loader/source/oc/mtc_timing_value.hpp diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_common.hpp b/Source/KIP/stratosphere/loader/source/oc/oc_common.hpp similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/oc/oc_common.hpp rename to Source/KIP/stratosphere/loader/source/oc/oc_common.hpp diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_loader.hpp b/Source/KIP/stratosphere/loader/source/oc/oc_loader.hpp similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/oc/oc_loader.hpp rename to Source/KIP/stratosphere/loader/source/oc/oc_loader.hpp diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_test.cpp b/Source/KIP/stratosphere/loader/source/oc/oc_test.cpp similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/oc/oc_test.cpp rename to Source/KIP/stratosphere/loader/source/oc/oc_test.cpp diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/oc_test.hpp b/Source/KIP/stratosphere/loader/source/oc/oc_test.hpp similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/oc/oc_test.hpp rename to Source/KIP/stratosphere/loader/source/oc/oc_test.hpp diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp b/Source/KIP/stratosphere/loader/source/oc/pcv/pcv.cpp similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp rename to Source/KIP/stratosphere/loader/source/oc/pcv/pcv.cpp diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp b/Source/KIP/stratosphere/loader/source/oc/pcv/pcv.hpp similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.hpp rename to Source/KIP/stratosphere/loader/source/oc/pcv/pcv.hpp diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_common.hpp b/Source/KIP/stratosphere/loader/source/oc/pcv/pcv_common.hpp similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_common.hpp rename to Source/KIP/stratosphere/loader/source/oc/pcv/pcv_common.hpp diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.cpp b/Source/KIP/stratosphere/loader/source/oc/pcv/pcv_erista.cpp similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_erista.cpp rename to Source/KIP/stratosphere/loader/source/oc/pcv/pcv_erista.cpp diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp b/Source/KIP/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp rename to Source/KIP/stratosphere/loader/source/oc/pcv/pcv_mariko.cpp diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/ptm/ptm.cpp b/Source/KIP/stratosphere/loader/source/oc/ptm/ptm.cpp similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/oc/ptm/ptm.cpp rename to Source/KIP/stratosphere/loader/source/oc/ptm/ptm.cpp diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/ptm/ptm.hpp b/Source/KIP/stratosphere/loader/source/oc/ptm/ptm.hpp similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/oc/ptm/ptm.hpp rename to Source/KIP/stratosphere/loader/source/oc/ptm/ptm.hpp diff --git a/Source/Atmosphere/stratosphere/loader/source/patch.py b/Source/KIP/stratosphere/loader/source/patch.py similarity index 100% rename from Source/Atmosphere/stratosphere/loader/source/patch.py rename to Source/KIP/stratosphere/loader/source/patch.py diff --git a/Source/ams_patch.bat b/Source/ams_patch.bat new file mode 100644 index 00000000..02428619 --- /dev/null +++ b/Source/ams_patch.bat @@ -0,0 +1,10 @@ +@echo off +set ROOT=Atmosphere-MTC-Unlock +set PATCHES=Atmosphere-Patches +copy "%PATCHES%\secmon_memory_layout.hpp" "%ROOT%\libraries\libexosphere/include/exosphere/secmon/" /Y +copy "%PATCHES%\secmon_emc_access_table_data.inc" "%ROOT%\exosphere/program/source/smc/" /Y +copy "%PATCHES%\secmon_define_emc_access_table.inc" "%ROOT%\exosphere/program/source/smc/" /Y +copy "%PATCHES%\secmon_smc_register_access.cpp" "%ROOT%\exosphere/program/source/smc/" /Y + +echo Patched! +pause diff --git a/Source/sys-clk/common/include/sysclk/board.h b/Source/sys-clk/common/include/sysclk/board.h index 04dbbe2c..85dd484c 100644 --- a/Source/sys-clk/common/include/sysclk/board.h +++ b/Source/sys-clk/common/include/sysclk/board.h @@ -62,6 +62,9 @@ typedef enum SysClkThermalSensor_SOC = 0, SysClkThermalSensor_PCB, SysClkThermalSensor_Skin, +// HocClkThermalSensor_BQ24193, + HocClkThermalSensor_Battery, + HocClkThermalSensor_PMIC, SysClkThermalSensor_EnumMax } SysClkThermalSensor; @@ -87,6 +90,16 @@ typedef enum ReverseNX_Docked, } ReverseNXMode; +typedef enum { + HocClkConsoleType_V1 = 0, + HocClkConsoleType_UnreleasedErista, + HocClkConsoleType_UnreleasedMariko, + HocClkConsoleType_V2, + HocClkConsoleType_Lite, + HocClkConsoleType_UnreleasedMariko2, + HocClkConsoleType_OLED, + HocClkConsoleType_EnumMax, +} HocClkConsoleType; #define SYSCLK_ENUM_VALID(n, v) ((v) < n##_EnumMax) diff --git a/Source/sys-clk/common/include/sysclk/config.h b/Source/sys-clk/common/include/sysclk/config.h index a29137ec..1d8f6bc7 100644 --- a/Source/sys-clk/common/include/sysclk/config.h +++ b/Source/sys-clk/common/include/sysclk/config.h @@ -60,6 +60,15 @@ typedef enum { HocClkConfigValue_EnforceBoardLimit, + HocClkConfigValue_EristaBoostClock, + HocClkConfigValue_MarikoBoostClock, + + HocClkConfigValue_EMCEnableUnsafeVoltages, + HocClkConfigValue_EMCVdd2VoltageuV, + HocClkConfigValue_EMCVddqVoltageuV, + + HocClkConfigValue_PWMDimming, + SysClkConfigValue_EnumMax, } SysClkConfigValue; @@ -122,7 +131,20 @@ static inline const char* sysclkFormatConfigValue(SysClkConfigValue val, bool pr case HocClkConfigValue_TDPCycleLimit: return pretty ? "TDP Cycle Limit" : "tdp_limit_c"; - + case HocClkConfigValue_EnforceBoardLimit: + return pretty ? "Enforce Board Limit" : "enforce_board_limit"; + case HocClkConfigValue_EristaBoostClock: + return pretty ? "Boost Clock" : "e_boost_clock"; + case HocClkConfigValue_MarikoBoostClock: + return pretty ? "Boost Clock" : "m_boost_clock"; + case HocClkConfigValue_EMCEnableUnsafeVoltages: + return pretty ? "EMC Unsafe Voltages" : "emc_unsafe_voltages"; + case HocClkConfigValue_EMCVdd2VoltageuV: + return pretty ? "EMC VDD2 Voltage (mV)" : "emc_vdd2_voltage_uv"; + case HocClkConfigValue_EMCVddqVoltageuV: + return pretty ? "EMC VDDQ Voltage (mV)" : "emc_vddq_voltage_uv"; + case HocClkConfigValue_PWMDimming: + return pretty ? "PWM Dimming" : "pwm_dimming"; default: return pretty ? "Null" : "null"; } @@ -140,8 +162,10 @@ static inline uint64_t sysclkDefaultConfigValue(SysClkConfigValue val) case SysClkConfigValue_CsvWriteIntervalMs: case HocClkConfigValue_UncappedClocks: case HocClkConfigValue_OverwriteBoostMode: + case HocClkConfigValue_EMCEnableUnsafeVoltages: return 0ULL; case HocClkConfigValue_EristaMaxCpuClock: + case HocClkConfigValue_EristaBoostClock: return 1785ULL; case HocClkConfigValue_EristaMaxGpuClock: return 921ULL; @@ -149,16 +173,18 @@ static inline uint64_t sysclkDefaultConfigValue(SysClkConfigValue val) return 1600ULL; case HocClkConfigValue_MarikoMaxCpuClock: + case HocClkConfigValue_MarikoBoostClock: return 1963ULL; case HocClkConfigValue_MarikoMaxGpuClock: return 1075ULL; case HocClkConfigValue_MarikoMaxMemClock: return 1862ULL; - case HocClkConfigValue_ThermalThrottle: case HocClkConfigValue_DockedGovernor: case HocClkConfigValue_HandheldGovernor: case HocClkConfigValue_HandheldTDP: + case HocClkConfigValue_EnforceBoardLimit: + case HocClkConfigValue_PWMDimming: return 1ULL; case HocClkConfigValue_ThermalThrottleThreshold: return 70ULL; @@ -168,6 +194,10 @@ static inline uint64_t sysclkDefaultConfigValue(SysClkConfigValue val) return 6400ULL; case HocClkConfigValue_TDPCycleLimit: return 10ULL; + case HocClkConfigValue_EMCVdd2VoltageuV: + return 1175000ULL; + case HocClkConfigValue_EMCVddqVoltageuV: + return 600000ULL; default: return 0ULL; } @@ -193,6 +223,10 @@ static inline uint64_t sysclkValidConfigValue(SysClkConfigValue val, uint64_t in case SysClkConfigValue_PowerLogIntervalMs: case SysClkConfigValue_CsvWriteIntervalMs: case HocClkConfigValue_TDPCycleLimit: + case HocClkConfigValue_EristaBoostClock: + case HocClkConfigValue_MarikoBoostClock: + case HocClkConfigValue_EMCVdd2VoltageuV: + case HocClkConfigValue_EMCVddqVoltageuV: return input >= 0; case HocClkConfigValue_UncappedClocks: case HocClkConfigValue_OverwriteBoostMode: @@ -200,7 +234,11 @@ static inline uint64_t sysclkValidConfigValue(SysClkConfigValue val, uint64_t in case HocClkConfigValue_DockedGovernor: case HocClkConfigValue_HandheldGovernor: case HocClkConfigValue_HandheldTDP: + case HocClkConfigValue_EnforceBoardLimit: + case HocClkConfigValue_EMCEnableUnsafeVoltages: + case HocClkConfigValue_PWMDimming: return (input & 0x1) == input; + default: return false; } diff --git a/Source/sys-clk/common/include/sysclk/ipc.h b/Source/sys-clk/common/include/sysclk/ipc.h index 51474458..72f11ee3 100644 --- a/Source/sys-clk/common/include/sysclk/ipc.h +++ b/Source/sys-clk/common/include/sysclk/ipc.h @@ -49,7 +49,7 @@ enum SysClkIpcCmd SysClkIpcCmd_SetConfigValues = 10, SysClkIpcCmd_GetFreqList = 11, SysClkIpcCmd_SetReverseNXRTMode = 12, - HocClkIpcCmd_UpdateEMCRegs = 13, + HocClkIpcCmd_UpdateEMC = 13, }; diff --git a/Source/sys-clk/common/src/client/ipc.c b/Source/sys-clk/common/src/client/ipc.c index c982759e..bd77738b 100644 --- a/Source/sys-clk/common/src/client/ipc.c +++ b/Source/sys-clk/common/src/client/ipc.c @@ -152,5 +152,5 @@ Result sysclkIpcSetReverseNXRTMode(ReverseNXMode mode) Result hocClkIpcUpdateEmcRegs() { int nil = 0; - return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetReverseNXRTMode, nil); + return serviceDispatchIn(&g_sysclkSrv, HocClkIpcCmd_UpdateEMC, nil); } diff --git a/Source/sys-clk/overlay/src/ui/gui/app_profile_gui.cpp b/Source/sys-clk/overlay/src/ui/gui/app_profile_gui.cpp index 2f68228e..e282b669 100644 --- a/Source/sys-clk/overlay/src/ui/gui/app_profile_gui.cpp +++ b/Source/sys-clk/overlay/src/ui/gui/app_profile_gui.cpp @@ -1,4 +1,8 @@ /* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * * Copyright (c) Souldbminer and Horizon OC Contributors * * This program is free software; you can redistribute it and/or modify it diff --git a/Source/sys-clk/overlay/src/ui/gui/app_profile_gui.h b/Source/sys-clk/overlay/src/ui/gui/app_profile_gui.h index 5cf87d9c..23939394 100644 --- a/Source/sys-clk/overlay/src/ui/gui/app_profile_gui.h +++ b/Source/sys-clk/overlay/src/ui/gui/app_profile_gui.h @@ -1,4 +1,8 @@ /* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * * Copyright (c) Souldbminer and Horizon OC Contributors * * This program is free software; you can redistribute it and/or modify it diff --git a/Source/sys-clk/overlay/src/ui/gui/base_gui.cpp b/Source/sys-clk/overlay/src/ui/gui/base_gui.cpp index 545f402a..7d38134f 100644 --- a/Source/sys-clk/overlay/src/ui/gui/base_gui.cpp +++ b/Source/sys-clk/overlay/src/ui/gui/base_gui.cpp @@ -1,4 +1,8 @@ /* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * * Copyright (c) Souldbminer and Horizon OC Contributors * * This program is free software; you can redistribute it and/or modify it diff --git a/Source/sys-clk/overlay/src/ui/gui/base_gui.h b/Source/sys-clk/overlay/src/ui/gui/base_gui.h index eac03aee..a5a00045 100644 --- a/Source/sys-clk/overlay/src/ui/gui/base_gui.h +++ b/Source/sys-clk/overlay/src/ui/gui/base_gui.h @@ -1,4 +1,8 @@ /* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * * Copyright (c) Souldbminer and Horizon OC Contributors * * This program is free software; you can redistribute it and/or modify it diff --git a/Source/sys-clk/overlay/src/ui/gui/base_menu_gui.cpp b/Source/sys-clk/overlay/src/ui/gui/base_menu_gui.cpp index 21f3e0bb..9b906bc6 100644 --- a/Source/sys-clk/overlay/src/ui/gui/base_menu_gui.cpp +++ b/Source/sys-clk/overlay/src/ui/gui/base_menu_gui.cpp @@ -1,4 +1,8 @@ /* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * * Copyright (c) Souldbminer and Horizon OC Contributors * * This program is free software; you can redistribute it and/or modify it diff --git a/Source/sys-clk/overlay/src/ui/gui/base_menu_gui.h b/Source/sys-clk/overlay/src/ui/gui/base_menu_gui.h index 22a76c2a..461926c4 100644 --- a/Source/sys-clk/overlay/src/ui/gui/base_menu_gui.h +++ b/Source/sys-clk/overlay/src/ui/gui/base_menu_gui.h @@ -1,4 +1,8 @@ /* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * * Copyright (c) Souldbminer and Horizon OC Contributors * * This program is free software; you can redistribute it and/or modify it diff --git a/Source/sys-clk/overlay/src/ui/gui/fatal_gui.cpp b/Source/sys-clk/overlay/src/ui/gui/fatal_gui.cpp index e2ce452e..bd5760a7 100644 --- a/Source/sys-clk/overlay/src/ui/gui/fatal_gui.cpp +++ b/Source/sys-clk/overlay/src/ui/gui/fatal_gui.cpp @@ -1,4 +1,8 @@ /* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * * Copyright (c) Souldbminer and Horizon OC Contributors * * This program is free software; you can redistribute it and/or modify it diff --git a/Source/sys-clk/overlay/src/ui/gui/fatal_gui.h b/Source/sys-clk/overlay/src/ui/gui/fatal_gui.h index 0c5c3784..77d04136 100644 --- a/Source/sys-clk/overlay/src/ui/gui/fatal_gui.h +++ b/Source/sys-clk/overlay/src/ui/gui/fatal_gui.h @@ -1,4 +1,8 @@ /* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * * Copyright (c) Souldbminer and Horizon OC Contributors * * This program is free software; you can redistribute it and/or modify it diff --git a/Source/sys-clk/overlay/src/ui/gui/freq_choice_gui.cpp b/Source/sys-clk/overlay/src/ui/gui/freq_choice_gui.cpp index 0adfc561..912266ce 100644 --- a/Source/sys-clk/overlay/src/ui/gui/freq_choice_gui.cpp +++ b/Source/sys-clk/overlay/src/ui/gui/freq_choice_gui.cpp @@ -1,4 +1,8 @@ /* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * * Copyright (c) Souldbminer and Horizon OC Contributors * * This program is free software; you can redistribute it and/or modify it diff --git a/Source/sys-clk/overlay/src/ui/gui/freq_choice_gui.h b/Source/sys-clk/overlay/src/ui/gui/freq_choice_gui.h index cc13e2e9..466bb9bb 100644 --- a/Source/sys-clk/overlay/src/ui/gui/freq_choice_gui.h +++ b/Source/sys-clk/overlay/src/ui/gui/freq_choice_gui.h @@ -1,4 +1,8 @@ /* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * * Copyright (c) Souldbminer and Horizon OC Contributors * * This program is free software; you can redistribute it and/or modify it diff --git a/Source/sys-clk/overlay/src/ui/gui/global_override_gui.cpp b/Source/sys-clk/overlay/src/ui/gui/global_override_gui.cpp index eac22cee..2e49ac6c 100644 --- a/Source/sys-clk/overlay/src/ui/gui/global_override_gui.cpp +++ b/Source/sys-clk/overlay/src/ui/gui/global_override_gui.cpp @@ -1,4 +1,8 @@ /* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * * Copyright (c) Souldbminer and Horizon OC Contributors * * This program is free software; you can redistribute it and/or modify it diff --git a/Source/sys-clk/overlay/src/ui/gui/global_override_gui.h b/Source/sys-clk/overlay/src/ui/gui/global_override_gui.h index 477ff7cc..bb4dcafa 100644 --- a/Source/sys-clk/overlay/src/ui/gui/global_override_gui.h +++ b/Source/sys-clk/overlay/src/ui/gui/global_override_gui.h @@ -1,4 +1,8 @@ /* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * * Copyright (c) Souldbminer and Horizon OC Contributors * * This program is free software; you can redistribute it and/or modify it diff --git a/Source/sys-clk/overlay/src/ui/gui/main_gui.cpp b/Source/sys-clk/overlay/src/ui/gui/main_gui.cpp index d8c29923..0524f4fa 100644 --- a/Source/sys-clk/overlay/src/ui/gui/main_gui.cpp +++ b/Source/sys-clk/overlay/src/ui/gui/main_gui.cpp @@ -1,4 +1,8 @@ /* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * * Copyright (c) Souldbminer and Horizon OC Contributors * * This program is free software; you can redistribute it and/or modify it diff --git a/Source/sys-clk/overlay/src/ui/gui/main_gui.h b/Source/sys-clk/overlay/src/ui/gui/main_gui.h index 77b98a2e..07d3db2d 100644 --- a/Source/sys-clk/overlay/src/ui/gui/main_gui.h +++ b/Source/sys-clk/overlay/src/ui/gui/main_gui.h @@ -1,4 +1,8 @@ /* + * Copyright (C) Switch-OC-Suite + * + * Copyright (c) 2023 hanai3Bi + * * Copyright (c) Souldbminer and Horizon OC Contributors * * This program is free software; you can redistribute it and/or modify it diff --git a/Source/sys-clk/overlay/src/ui/gui/misc_gui.cpp b/Source/sys-clk/overlay/src/ui/gui/misc_gui.cpp index 4b997b07..59e49424 100644 --- a/Source/sys-clk/overlay/src/ui/gui/misc_gui.cpp +++ b/Source/sys-clk/overlay/src/ui/gui/misc_gui.cpp @@ -205,14 +205,18 @@ void MiscGui::listUI() addConfigToggle(HocClkConfigValue_OverwriteBoostMode, nullptr); this->listElement->addItem(new tsl::elm::CategoryHeader("Experimental")); + addConfigToggle(HocClkConfigValue_EnforceBoardLimit, nullptr); addConfigToggle(HocClkConfigValue_ThermalThrottle, nullptr); addConfigToggle(HocClkConfigValue_HandheldTDP, nullptr); - + addConfigToggle(HocClkConfigValue_EnforceBoardLimit, nullptr); + if(IsAula()) { + addConfigToggle(HocClkConfigValue_PWMDimming, "PWM Dimming"); + } ValueThresholds tdpThresholds(8600, 9500); addConfigButton( HocClkConfigValue_HandheldTDPLimit, "TDP Threshold", - ValueRange(5000, 10000, 100, "mW", 1), + ValueRange(5000, 10000, 200, "mW", 1, 0), "Power", &tdpThresholds ); @@ -221,7 +225,7 @@ void MiscGui::listUI() addConfigButton( HocClkConfigValue_LiteTDPLimit, "Lite TDP Threshold", - ValueRange(4000, 8000, 100, "mW", 1), + ValueRange(4000, 8000, 200, "mW", 1, 0), "Power", &tdpThresholdsLite ); @@ -230,11 +234,11 @@ void MiscGui::listUI() addConfigButton( HocClkConfigValue_ThermalThrottleThreshold, "Thermal Throttle Limit", - ValueRange(50, 85, 1, "°C", 1), + ValueRange(50, 85, 5, "°C", 1, 0), "Temp", &throttleThresholds ); - this->listElement->addItem(new tsl::elm::CategoryHeader("Max Clocks")); + this->listElement->addItem(new tsl::elm::CategoryHeader("Clocks")); if(IsMariko()) { addFreqButton(HocClkConfigValue_MarikoMaxCpuClock, nullptr, SysClkModule_CPU); addFreqButton(HocClkConfigValue_MarikoMaxGpuClock, nullptr, SysClkModule_GPU); @@ -244,6 +248,62 @@ void MiscGui::listUI() addFreqButton(HocClkConfigValue_EristaMaxGpuClock, nullptr, SysClkModule_GPU); addFreqButton(HocClkConfigValue_EristaMaxMemClock, nullptr, SysClkModule_MEM); } + + // addFreqButton(HocClkConfigValue_CustomBoostClock, nullptr, S); + if(IsMariko()) { + ValueThresholds BoostThresholds(1963, 2397); + addConfigButton( + HocClkConfigValue_MarikoBoostClock, + "CPU Boost Clock", + ValueRange(1785, 2805, 102, "MHz", 1, 0), + "CPU Boost Clock", + &BoostThresholds + ); + } else { + ValueThresholds BoostThresholds(1785, 2091); + addConfigButton( + HocClkConfigValue_EristaBoostClock, + "CPU Boost Clock", + ValueRange(1785, 2295, 102, "MHz", 1, 0), + "CPU Boost Clock", + &BoostThresholds + ); + } + + this->listElement->addItem(new tsl::elm::CategoryHeader("EMC and MTC")); + + addConfigToggle(HocClkConfigValue_EMCEnableUnsafeVoltages, nullptr); + + if(this->configList->values[HocClkConfigValue_EMCEnableUnsafeVoltages]) { + ValueThresholds emcUvThresholds(1212500, 1250000); + addConfigButton( + HocClkConfigValue_EMCVdd2VoltageuV, + "EMC VDD2 Voltage", + ValueRange(918500, 1350000, 12500, "µV", 1, 1), + "EMC VDD2 Voltage", + &emcUvThresholds + ); + } else { + if(IsMariko()) { + ValueThresholds emcUvThresholds(1212500, 1250000); + addConfigButton( + HocClkConfigValue_EMCVdd2VoltageuV, + "EMC VDD2 Voltage", + ValueRange(1100000, 1212500, 12500, "µV", 1, 1), + "EMC VDD2 Voltage", + &emcUvThresholds + ); + } else { + ValueThresholds emcUvThresholds(1237500, 1300000); + addConfigButton( + HocClkConfigValue_EMCVdd2VoltageuV, + "EMC VDD2 Voltage", + ValueRange(1125000, 1237500, 12500, "mV", 1000, 1), + "EMC VDD2 Voltage", + &emcUvThresholds + ); + } + } tsl::elm::ListItem* applyBtn = new tsl::elm::ListItem("Apply EMC Regs"); applyBtn->setClickListener([](u64 keys) { if (keys & HidNpadButton_A) { diff --git a/Source/sys-clk/overlay/src/ui/gui/value_choice_gui.cpp b/Source/sys-clk/overlay/src/ui/gui/value_choice_gui.cpp index ed5108df..0ff3d3f1 100644 --- a/Source/sys-clk/overlay/src/ui/gui/value_choice_gui.cpp +++ b/Source/sys-clk/overlay/src/ui/gui/value_choice_gui.cpp @@ -1,145 +1,118 @@ -/* - * Copyright (c) Souldbminer and Horizon OC Contributors - * - * 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 <http://www.gnu.org/licenses/>. - * - */ - -/* -------------------------------------------------------------------------- - * "THE BEER-WARE LICENSE" (Revision 42): - * <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net> - * wrote this file. As long as you retain this notice you can do whatever you - * want with this stuff. If you meet any of us some day, and you think this - * stuff is worth it, you can buy us a beer in return. - The sys-clk authors - * -------------------------------------------------------------------------- - */ +#include "value_choice_gui.h" +#include "../format.h" +#include "fatal_gui.h" +#include <sstream> +#include <iomanip> +ValueChoiceGui::ValueChoiceGui(std::uint32_t selectedValue, + const ValueRange& range, + const std::string& categoryName, + ValueChoiceListener listener, + const ValueThresholds& thresholds, + bool enableThresholds) + : selectedValue(selectedValue), + range(range), + categoryName(categoryName), + listener(listener), + thresholds(thresholds), + enableThresholds(enableThresholds) +{ +} - #include "value_choice_gui.h" - #include "../format.h" - #include "fatal_gui.h" - #include <sstream> - #include <iomanip> +ValueChoiceGui::~ValueChoiceGui() +{ +} - ValueChoiceGui::ValueChoiceGui(std::uint32_t selectedValue, - const ValueRange& range, - const std::string& categoryName, - ValueChoiceListener listener, - const ValueThresholds& thresholds, - bool enableThresholds) - : selectedValue(selectedValue), - range(range), - categoryName(categoryName), - listener(listener), - thresholds(thresholds), - enableThresholds(enableThresholds) - { - } +std::string ValueChoiceGui::formatValue(std::uint32_t value) +{ + std::ostringstream oss; - ValueChoiceGui::~ValueChoiceGui() - { - } + if (value == 0) { + return VALUE_DEFAULT_TEXT; + } - std::string ValueChoiceGui::formatValue(std::uint32_t value) - { - std::ostringstream oss; + // Convert to floating point for division + double displayValue = static_cast<double>(value) / static_cast<double>(range.divisor); - if (value == 0) { + // Set precision and formatting + oss << std::fixed << std::setprecision(range.decimalPlaces) << displayValue; + + if (!range.suffix.empty()) { + oss << " " << range.suffix; + } + return oss.str(); +} - return VALUE_DEFAULT_TEXT; - } +int ValueChoiceGui::getSafetyLevel(std::uint32_t value) +{ + if (!enableThresholds) { + return 0; + } - std::uint32_t displayValue = value / range.divisor; + std::uint32_t scaledValue = value / range.divisor; - oss << displayValue; - if (!range.suffix.empty()) { - oss << " " << range.suffix; - } - return oss.str(); - } + if (scaledValue > thresholds.danger) { + return 2; + } + if (scaledValue > thresholds.warning) { + return 1; + } + return 0; +} - int ValueChoiceGui::getSafetyLevel(std::uint32_t value) - { - if (!enableThresholds) { - return 0; - } +tsl::elm::ListItem* ValueChoiceGui::createValueListItem(std::uint32_t value, bool selected, int safety) +{ + std::string text = formatValue(value); + if (selected) { + text += " \uE14B"; + } - std::uint32_t scaledValue = value / range.divisor; + tsl::elm::ListItem* listItem = new tsl::elm::ListItem(text, "", false); - if (scaledValue > thresholds.danger) { - return 2; - } - if (scaledValue > thresholds.warning) { - return 1; - } - return 0; - } + switch (safety) + { + case 0: + listItem->setTextColor(tsl::Color(255, 255, 255, 255)); + listItem->setValueColor(tsl::Color(255, 255, 255, 255)); + break; + case 1: + listItem->setTextColor(tsl::Color(255, 165, 0, 255)); + listItem->setValueColor(tsl::Color(255, 165, 0, 255)); + break; + case 2: + listItem->setTextColor(tsl::Color(255, 0, 0, 255)); + listItem->setValueColor(tsl::Color(255, 0, 0, 255)); + break; + } - tsl::elm::ListItem* ValueChoiceGui::createValueListItem(std::uint32_t value, bool selected, int safety) - { - std::string text = formatValue(value); - if (selected) { - text += " \uE14B"; - } + listItem->setClickListener([this, value](u64 keys) + { + if ((keys & HidNpadButton_A) == HidNpadButton_A && this->listener) { + if (this->listener(value)) { + tsl::goBack(); + } + return true; + } + return false; + }); - tsl::elm::ListItem* listItem = new tsl::elm::ListItem(text, "", false); + return listItem; +} - switch (safety) - { - case 0: - listItem->setTextColor(tsl::Color(255, 255, 255, 255)); - listItem->setValueColor(tsl::Color(255, 255, 255, 255)); - break; - case 1: - listItem->setTextColor(tsl::Color(255, 165, 0, 255)); - listItem->setValueColor(tsl::Color(255, 165, 0, 255)); - break; - case 2: - listItem->setTextColor(tsl::Color(255, 0, 0, 255)); - listItem->setValueColor(tsl::Color(255, 0, 0, 255)); - break; - } +void ValueChoiceGui::listUI() +{ + if (!categoryName.empty()) { + this->listElement->addItem(new tsl::elm::CategoryHeader(categoryName)); + } - listItem->setClickListener([this, value](u64 keys) - { - if ((keys & HidNpadButton_A) == HidNpadButton_A && this->listener) { + this->listElement->addItem(this->createValueListItem(0, this->selectedValue == 0, 0)); - if (this->listener(value)) { - tsl::goBack(); - } - return true; - } - return false; - }); + for (std::uint32_t value = range.min; value <= range.max; value += range.step) + { + int safety = getSafetyLevel(value); + bool selected = (value == this->selectedValue); + this->listElement->addItem(this->createValueListItem(value, selected, safety)); + } - return listItem; - } - - void ValueChoiceGui::listUI() - { - - if (!categoryName.empty()) { - this->listElement->addItem(new tsl::elm::CategoryHeader(categoryName)); - } - - this->listElement->addItem(this->createValueListItem(0, this->selectedValue == 0, 0)); - - for (std::uint32_t value = range.min; value <= range.max; value += range.step) - { - int safety = getSafetyLevel(value); - bool selected = (value == this->selectedValue); - this->listElement->addItem(this->createValueListItem(value, selected, safety)); - } - - this->listElement->jumpToItem("", "\uE14B"); - } \ No newline at end of file + this->listElement->jumpToItem("", "\uE14B"); +} \ No newline at end of file diff --git a/Source/sys-clk/overlay/src/ui/gui/value_choice_gui.h b/Source/sys-clk/overlay/src/ui/gui/value_choice_gui.h index 160de655..2c9b122e 100644 --- a/Source/sys-clk/overlay/src/ui/gui/value_choice_gui.h +++ b/Source/sys-clk/overlay/src/ui/gui/value_choice_gui.h @@ -37,20 +37,20 @@ #define VALUE_DEFAULT_TEXT "Default" struct ValueRange { - std::uint32_t min; - std::uint32_t max; - std::uint32_t step; - std::string suffix; - std::uint32_t divisor; // Divide input values by this for display - - // Default constructor - ValueRange() : min(0), max(0), step(1), suffix(""), divisor(1) {} - - ValueRange(std::uint32_t min, std::uint32_t max, std::uint32_t step, - const std::string& suffix = "", std::uint32_t divisor = 1) - : min(min), max(max), step(step), suffix(suffix), divisor(divisor) {} - }; - + std::uint32_t min; + std::uint32_t max; + std::uint32_t step; + std::string suffix; + std::uint32_t divisor; // Divide input values by this for display + int decimalPlaces; // Number of decimal places to display (0-6) + + ValueRange() : min(0), max(0), step(1), suffix(""), divisor(1), decimalPlaces(0) {} + + ValueRange(std::uint32_t min, std::uint32_t max, std::uint32_t step, + const std::string& suffix = "", std::uint32_t divisor = 1, int decimalPlaces = 0) + : min(min), max(max), step(step), suffix(suffix), divisor(divisor), decimalPlaces(decimalPlaces) {} +}; + struct ValueThresholds { std::uint32_t warning; // Values >= this show orange std::uint32_t danger; // Values >= this show red diff --git a/Source/sys-clk/sysmodule/src/board.cpp b/Source/sys-clk/sysmodule/src/board.cpp index f7b92991..2b5552c7 100644 --- a/Source/sys-clk/sysmodule/src/board.cpp +++ b/Source/sys-clk/sysmodule/src/board.cpp @@ -12,9 +12,9 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * */ - + /* -------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net> @@ -24,35 +24,39 @@ * -------------------------------------------------------------------------- */ - #include <nxExt.h> +#include <switch.h> + #include "board.h" #include "errors.h" +#include "maxXXXXX.h" -#define HOSSVC_HAS_CLKRST (hosversionAtLeast(8,0,0)) -#define HOSSVC_HAS_TC (hosversionAtLeast(5,0,0)) +#define HOSSVC_HAS_CLKRST (hosversionAtLeast(8, 0, 0)) +#define HOSSVC_HAS_TC (hosversionAtLeast(5, 0, 0)) +#define BQ24193_I2C_ADDR 0x6B +#define BQ24193_FaultReg 0x09 static SysClkSocType g_socType = SysClkSocType_Erista; -const char* Board::GetModuleName(SysClkModule module, bool pretty) +const char *Board::GetModuleName(SysClkModule module, bool pretty) { ASSERT_ENUM_VALID(SysClkModule, module); return sysclkFormatModule(module, pretty); } -const char* Board::GetProfileName(SysClkProfile profile, bool pretty) +const char *Board::GetProfileName(SysClkProfile profile, bool pretty) { ASSERT_ENUM_VALID(SysClkProfile, profile); return sysclkFormatProfile(profile, pretty); } -const char* Board::GetThermalSensorName(SysClkThermalSensor sensor, bool pretty) +const char *Board::GetThermalSensorName(SysClkThermalSensor sensor, bool pretty) { ASSERT_ENUM_VALID(SysClkThermalSensor, sensor); return sysclkFormatThermalSensor(sensor, pretty); } -const char* Board::GetPowerSensorName(SysClkPowerSensor sensor, bool pretty) +const char *Board::GetPowerSensorName(SysClkPowerSensor sensor, bool pretty) { ASSERT_ENUM_VALID(SysClkPowerSensor, sensor); return sysclkFormatPowerSensor(sensor, pretty); @@ -60,19 +64,18 @@ const char* Board::GetPowerSensorName(SysClkPowerSensor sensor, bool pretty) PcvModule Board::GetPcvModule(SysClkModule sysclkModule) { - switch(sysclkModule) - { - case SysClkModule_CPU: - return PcvModule_CpuBus; - case SysClkModule_GPU: - return PcvModule_GPU; - case SysClkModule_MEM: - return PcvModule_EMC; - default: - ASSERT_ENUM_VALID(SysClkModule, sysclkModule); + switch (sysclkModule) { + case SysClkModule_CPU: + return PcvModule_CpuBus; + case SysClkModule_GPU: + return PcvModule_GPU; + case SysClkModule_MEM: + return PcvModule_EMC; + default: + ASSERT_ENUM_VALID(SysClkModule, sysclkModule); } - return (PcvModule)0; + return (PcvModule) 0; } PcvModuleId Board::GetPcvModuleId(SysClkModule sysclkModule) @@ -88,13 +91,10 @@ void Board::Initialize() { Result rc = 0; - if(HOSSVC_HAS_CLKRST) - { + if (HOSSVC_HAS_CLKRST) { rc = clkrstInitialize(); ASSERT_RESULT_OK(rc, "clkrstInitialize"); - } - else - { + } else { rc = pcvInitialize(); ASSERT_RESULT_OK(rc, "pcvInitialize"); } @@ -105,8 +105,7 @@ void Board::Initialize() rc = psmInitialize(); ASSERT_RESULT_OK(rc, "psmInitialize"); - if(HOSSVC_HAS_TC) - { + if (HOSSVC_HAS_TC) { rc = tcInitialize(); ASSERT_RESULT_OK(rc, "tcInitialize"); } @@ -122,20 +121,16 @@ void Board::Initialize() void Board::Exit() { - if(HOSSVC_HAS_CLKRST) - { + if (HOSSVC_HAS_CLKRST) { clkrstExit(); - } - else - { + } else { pcvExit(); } apmExtExit(); psmExit(); - if(HOSSVC_HAS_TC) - { + if (HOSSVC_HAS_TC) { tcExit(); } @@ -149,8 +144,7 @@ SysClkProfile Board::GetProfile() Result rc = apmExtGetPerformanceMode(&mode); ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode"); - if(mode) - { + if (mode) { return SysClkProfile_Docked; } @@ -159,12 +153,9 @@ SysClkProfile Board::GetProfile() rc = psmGetChargerType(&chargerType); ASSERT_RESULT_OK(rc, "psmGetChargerType"); - if(chargerType == PsmChargerType_EnoughPower) - { + if (chargerType == PsmChargerType_EnoughPower) { return SysClkProfile_HandheldChargingOfficial; - } - else if(chargerType == PsmChargerType_LowPower) - { + } else if (chargerType == PsmChargerType_LowPower) { return SysClkProfile_HandheldChargingUSB; } @@ -175,9 +166,8 @@ void Board::SetHz(SysClkModule module, std::uint32_t hz) { Result rc = 0; - if(HOSSVC_HAS_CLKRST) - { - ClkrstSession session = {0}; + if (HOSSVC_HAS_CLKRST) { + ClkrstSession session = { 0 }; rc = clkrstOpenSession(&session, Board::GetPcvModuleId(module), 3); ASSERT_RESULT_OK(rc, "clkrstOpenSession"); @@ -186,9 +176,7 @@ void Board::SetHz(SysClkModule module, std::uint32_t hz) ASSERT_RESULT_OK(rc, "clkrstSetClockRate"); clkrstCloseSession(&session); - } - else - { + } else { rc = pcvSetClockRate(Board::GetPcvModule(module), hz); ASSERT_RESULT_OK(rc, "pcvSetClockRate"); } @@ -199,9 +187,8 @@ std::uint32_t Board::GetHz(SysClkModule module) Result rc = 0; std::uint32_t hz = 0; - if(HOSSVC_HAS_CLKRST) - { - ClkrstSession session = {0}; + if (HOSSVC_HAS_CLKRST) { + ClkrstSession session = { 0 }; rc = clkrstOpenSession(&session, Board::GetPcvModuleId(module), 3); ASSERT_RESULT_OK(rc, "clkrstOpenSession"); @@ -210,9 +197,7 @@ std::uint32_t Board::GetHz(SysClkModule module) ASSERT_RESULT_OK(rc, "clkrstSetClockRate"); clkrstCloseSession(&session); - } - else - { + } else { rc = pcvGetClockRate(Board::GetPcvModule(module), &hz); ASSERT_RESULT_OK(rc, "pcvGetClockRate"); } @@ -222,49 +207,48 @@ std::uint32_t Board::GetHz(SysClkModule module) std::uint32_t Board::GetRealHz(SysClkModule module) { - switch(module) - { - case SysClkModule_CPU: - return t210ClkCpuFreq(); - case SysClkModule_GPU: - return t210ClkGpuFreq(); - case SysClkModule_MEM: - return t210ClkMemFreq(); - default: - ASSERT_ENUM_VALID(SysClkModule, module); + switch (module) { + case SysClkModule_CPU: + return t210ClkCpuFreq(); + case SysClkModule_GPU: + return t210ClkGpuFreq(); + case SysClkModule_MEM: + return t210ClkMemFreq(); + default: + ASSERT_ENUM_VALID(SysClkModule, module); } return 0; } -void Board::GetFreqList(SysClkModule module, std::uint32_t* outList, std::uint32_t maxCount, std::uint32_t* outCount) +void Board::GetFreqList(SysClkModule module, std::uint32_t *outList, +std::uint32_t maxCount, std::uint32_t *outCount) { Result rc = 0; PcvClockRatesListType type; s32 tmpInMaxCount = maxCount; s32 tmpOutCount = 0; - if(HOSSVC_HAS_CLKRST) - { - ClkrstSession session = {0}; + if (HOSSVC_HAS_CLKRST) { + ClkrstSession session = { 0 }; rc = clkrstOpenSession(&session, Board::GetPcvModuleId(module), 3); ASSERT_RESULT_OK(rc, "clkrstOpenSession"); - rc = clkrstGetPossibleClockRates(&session, outList, tmpInMaxCount, &type, &tmpOutCount); + rc = clkrstGetPossibleClockRates( + &session, outList, tmpInMaxCount, &type, &tmpOutCount); ASSERT_RESULT_OK(rc, "clkrstGetPossibleClockRates"); clkrstCloseSession(&session); - } - else - { - rc = pcvGetPossibleClockRates(Board::GetPcvModule(module), outList, tmpInMaxCount, &type, &tmpOutCount); + } else { + rc = pcvGetPossibleClockRates(Board::GetPcvModule(module), outList, + tmpInMaxCount, &type, &tmpOutCount); ASSERT_RESULT_OK(rc, "pcvGetPossibleClockRates"); } - if(type != PcvClockRatesListType_Discrete) - { - ERROR_THROW("Unexpected PcvClockRatesListType: %u (module = %s)", type, Board::GetModuleName(module, false)); + if (type != PcvClockRatesListType_Discrete) { + ERROR_THROW("Unexpected PcvClockRatesListType: %u (module = %s)", type, + Board::GetModuleName(module, false)); } *outCount = tmpOutCount; @@ -273,33 +257,27 @@ void Board::GetFreqList(SysClkModule module, std::uint32_t* outList, std::uint32 void Board::ResetToStock() { Result rc = 0; - if(hosversionAtLeast(9,0,0)) - { + if (hosversionAtLeast(9, 0, 0)) { std::uint32_t confId = 0; rc = apmExtGetCurrentPerformanceConfiguration(&confId); ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration"); - SysClkApmConfiguration* apmConfiguration = NULL; - for(size_t i = 0; sysclk_g_apm_configurations[i].id; i++) - { - if(sysclk_g_apm_configurations[i].id == confId) - { + SysClkApmConfiguration *apmConfiguration = NULL; + for (size_t i = 0; sysclk_g_apm_configurations[i].id; i++) { + if (sysclk_g_apm_configurations[i].id == confId) { apmConfiguration = &sysclk_g_apm_configurations[i]; break; } } - if(!apmConfiguration) - { + if (!apmConfiguration) { ERROR_THROW("Unknown apm configuration: %x", confId); } Board::SetHz(SysClkModule_CPU, apmConfiguration->cpu_hz); Board::SetHz(SysClkModule_GPU, apmConfiguration->gpu_hz); Board::SetHz(SysClkModule_MEM, apmConfiguration->mem_hz); - } - else - { + } else { std::uint32_t mode = 0; rc = apmExtGetPerformanceMode(&mode); ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode"); @@ -312,31 +290,25 @@ void Board::ResetToStock() void Board::ResetToStockCpu() { Result rc = 0; - if(hosversionAtLeast(9,0,0)) - { + if (hosversionAtLeast(9, 0, 0)) { std::uint32_t confId = 0; rc = apmExtGetCurrentPerformanceConfiguration(&confId); ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration"); - SysClkApmConfiguration* apmConfiguration = NULL; - for(size_t i = 0; sysclk_g_apm_configurations[i].id; i++) - { - if(sysclk_g_apm_configurations[i].id == confId) - { + SysClkApmConfiguration *apmConfiguration = NULL; + for (size_t i = 0; sysclk_g_apm_configurations[i].id; i++) { + if (sysclk_g_apm_configurations[i].id == confId) { apmConfiguration = &sysclk_g_apm_configurations[i]; break; } } - if(!apmConfiguration) - { + if (!apmConfiguration) { ERROR_THROW("Unknown apm configuration: %x", confId); } Board::SetHz(SysClkModule_CPU, apmConfiguration->cpu_hz); - } - else - { + } else { std::uint32_t mode = 0; rc = apmExtGetPerformanceMode(&mode); ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode"); @@ -349,31 +321,25 @@ void Board::ResetToStockCpu() void Board::ResetToStockMem() { Result rc = 0; - if(hosversionAtLeast(9,0,0)) - { + if (hosversionAtLeast(9, 0, 0)) { std::uint32_t confId = 0; rc = apmExtGetCurrentPerformanceConfiguration(&confId); ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration"); - SysClkApmConfiguration* apmConfiguration = NULL; - for(size_t i = 0; sysclk_g_apm_configurations[i].id; i++) - { - if(sysclk_g_apm_configurations[i].id == confId) - { + SysClkApmConfiguration *apmConfiguration = NULL; + for (size_t i = 0; sysclk_g_apm_configurations[i].id; i++) { + if (sysclk_g_apm_configurations[i].id == confId) { apmConfiguration = &sysclk_g_apm_configurations[i]; break; } } - if(!apmConfiguration) - { + if (!apmConfiguration) { ERROR_THROW("Unknown apm configuration: %x", confId); } Board::SetHz(SysClkModule_MEM, apmConfiguration->mem_hz); - } - else - { + } else { std::uint32_t mode = 0; rc = apmExtGetPerformanceMode(&mode); ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode"); @@ -386,31 +352,25 @@ void Board::ResetToStockMem() void Board::ResetToStockGpu() { Result rc = 0; - if(hosversionAtLeast(9,0,0)) - { + if (hosversionAtLeast(9, 0, 0)) { std::uint32_t confId = 0; rc = apmExtGetCurrentPerformanceConfiguration(&confId); ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration"); - SysClkApmConfiguration* apmConfiguration = NULL; - for(size_t i = 0; sysclk_g_apm_configurations[i].id; i++) - { - if(sysclk_g_apm_configurations[i].id == confId) - { + SysClkApmConfiguration *apmConfiguration = NULL; + for (size_t i = 0; sysclk_g_apm_configurations[i].id; i++) { + if (sysclk_g_apm_configurations[i].id == confId) { apmConfiguration = &sysclk_g_apm_configurations[i]; break; } } - if(!apmConfiguration) - { + if (!apmConfiguration) { ERROR_THROW("Unknown apm configuration: %x", confId); } Board::SetHz(SysClkModule_GPU, apmConfiguration->gpu_hz); - } - else - { + } else { std::uint32_t mode = 0; rc = apmExtGetPerformanceMode(&mode); ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode"); @@ -419,29 +379,56 @@ void Board::ResetToStockGpu() ASSERT_RESULT_OK(rc, "apmExtSysRequestPerformanceMode"); } } + std::uint32_t Board::GetTemperatureMilli(SysClkThermalSensor sensor) { std::int32_t millis = 0; + Result res; + u16 data; + u8 reg = MAX17050_TEMP; - if(sensor == SysClkThermalSensor_SOC) - { + switch (sensor) { + case SysClkThermalSensor_SOC: millis = tmp451TempSoc(); - } - else if(sensor == SysClkThermalSensor_PCB) - { + break; + case SysClkThermalSensor_PCB: millis = tmp451TempPcb(); - } - else if(sensor == SysClkThermalSensor_Skin) - { - if(HOSSVC_HAS_TC) - { + break; + case SysClkThermalSensor_Skin: + + if (HOSSVC_HAS_TC) { Result rc; rc = tcGetSkinTemperatureMilliC(&millis); ASSERT_RESULT_OK(rc, "tcGetSkinTemperatureMilliC"); } - } - else - { + break; + case HocClkThermalSensor_Battery: + I2cSession session; + res = i2cOpenSession(&session, I2cDevice_Max17050); + if (R_FAILED(res)) { + millis = -1; + break; + } + + data = 0; + + res = i2csessionSendAuto(&session, ®, 1, I2cTransactionOption_Start); + if (R_FAILED(res)) { + i2csessionClose(&session); + millis = -1; + break; + } + + res = i2csessionReceiveAuto(&session, (u8 *) &data, 2, I2cTransactionOption_Stop); + + i2csessionClose(&session); + + millis = (std::int32_t) data * 10 / 256; + break; + case HocClkThermalSensor_PMIC: + millis = 50000; // Literally the only reasonable value the PMIC can return + break; + default: ASSERT_ENUM_VALID(SysClkThermalSensor, sensor); } @@ -450,14 +437,13 @@ std::uint32_t Board::GetTemperatureMilli(SysClkThermalSensor sensor) std::int32_t Board::GetPowerMw(SysClkPowerSensor sensor) { - switch(sensor) - { - case SysClkPowerSensor_Now: - return max17050PowerNow(); - case SysClkPowerSensor_Avg: - return max17050PowerAvg(); - default: - ASSERT_ENUM_VALID(SysClkPowerSensor, sensor); + switch (sensor) { + case SysClkPowerSensor_Now: + return max17050PowerNow(); + case SysClkPowerSensor_Avg: + return max17050PowerAvg(); + default: + ASSERT_ENUM_VALID(SysClkPowerSensor, sensor); } return 0; @@ -465,24 +451,23 @@ std::int32_t Board::GetPowerMw(SysClkPowerSensor sensor) std::uint32_t Board::GetRamLoad(SysClkRamLoad loadSource) { - switch(loadSource) - { - case SysClkRamLoad_All: - return t210EmcLoadAll(); - case SysClkRamLoad_Cpu: - return t210EmcLoadCpu(); - default: - ASSERT_ENUM_VALID(SysClkRamLoad, loadSource); + switch (loadSource) { + case SysClkRamLoad_All: + return t210EmcLoadAll(); + case SysClkRamLoad_Cpu: + return t210EmcLoadCpu(); + default: + ASSERT_ENUM_VALID(SysClkRamLoad, loadSource); } return 0; } -SysClkSocType Board::GetSocType() { +SysClkSocType Board::GetSocType() +{ return g_socType; } - void Board::FetchHardwareInfos() { u64 sku = 0; @@ -494,17 +479,16 @@ void Board::FetchHardwareInfos() splExit(); - switch(sku) - { - case 2: - case 3: - case 5: - g_socType = SysClkSocType_Mariko; - break; - case 4: - g_socType = SysClkSocType_MarikoLite; - break; - default: - g_socType = SysClkSocType_Erista; + switch (sku) { + case 2: + case 3: + case 5: + g_socType = SysClkSocType_Mariko; + break; + case 4: + g_socType = SysClkSocType_MarikoLite; + break; + default: + g_socType = SysClkSocType_Erista; } } \ No newline at end of file diff --git a/Source/sys-clk/sysmodule/src/board.h b/Source/sys-clk/sysmodule/src/board.h index 21b9c227..3162c371 100644 --- a/Source/sys-clk/sysmodule/src/board.h +++ b/Source/sys-clk/sysmodule/src/board.h @@ -33,6 +33,7 @@ class Board { public: + u16 max17050_get_reg(u8 reg); static const char* GetProfileName(SysClkProfile profile, bool pretty); static const char* GetModuleName(SysClkModule module, bool pretty); static const char* GetThermalSensorName(SysClkThermalSensor sensor, bool pretty); diff --git a/Source/sys-clk/sysmodule/src/clock_manager.cpp b/Source/sys-clk/sysmodule/src/clock_manager.cpp index b82dd89d..5cf68469 100644 --- a/Source/sys-clk/sysmodule/src/clock_manager.cpp +++ b/Source/sys-clk/sysmodule/src/clock_manager.cpp @@ -12,9 +12,9 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * */ - + /* -------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * <p-sam@d3vs.net>, <natinusala@gmail.com>, <m4x@m4xw.net> @@ -32,6 +32,7 @@ #include "process_management.h" #include "errors.h" #include "ipc_service.h" +#include "emc_patcher.h" #define HOSPPC_HAS_BOOST (hosversionAtLeast(7,0,0)) @@ -235,27 +236,41 @@ void ClockManager::Tick() std::uint32_t maxHz = 0; std::uint32_t nearestHz = 0; std::uint32_t mode = 0; - - AppletOperationMode opMode = appletGetOperationMode(); + + #define EMC_MAX_VOLTAGE_SAFETY_CHECK 1400000 + + if(this->config->GetConfigValue(HocClkConfigValue_EMCVdd2VoltageuV) < EMC_MAX_VOLTAGE_SAFETY_CHECK) { + EMCpatcher::set_sd1_voltage((u32)this->config->GetConfigValue(HocClkConfigValue_EMCVdd2VoltageuV)); + } else { + ResetToStockClocks(); // Clean up after boost mode + } + + + AppletOperationMode opMode = appletGetOperationMode(); // Used to get if docked or handheld Result rc = apmExtGetCurrentPerformanceConfiguration(&mode); ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration"); if(this->config->GetConfigValue(HocClkConfigValue_HandheldTDP) && opMode == AppletOperationMode_Handheld) { if(Board::GetSocType() == SysClkSocType_MarikoLite) { - if(Board::GetPowerMw(SysClkPowerSensor_Now) < -(int)this->config->GetConfigValue(HocClkConfigValue_LiteTDPLimit)) { + if(Board::GetPowerMw(SysClkPowerSensor_Avg) < -(int)this->config->GetConfigValue(HocClkConfigValue_LiteTDPLimit)) { ResetToStockClocks(); return; } } else { - if(Board::GetPowerMw(SysClkPowerSensor_Now) < -(int)this->config->GetConfigValue(HocClkConfigValue_HandheldTDPLimit)) { + if(Board::GetPowerMw(SysClkPowerSensor_Avg) < -(int)this->config->GetConfigValue(HocClkConfigValue_HandheldTDPLimit)) { ResetToStockClocks(); return; } } } - if(apmExtIsBoostMode(mode) && !this->config->GetConfigValue(HocClkConfigValue_OverwriteBoostMode)) { - ResetToStockClocks(); + if(apmExtIsBoostMode(mode) && !this->config->GetConfigValue(HocClkConfigValue_OverwriteBoostMode)) { // Stock boost mode + // ResetToStockClocks(); + if(Board::GetSocType() == SysClkSocType_MarikoLite || Board::GetSocType() == SysClkSocType_Mariko) { + Board::SetHz((SysClkModule)SysClkModule_CPU, (u32)this->config->GetConfigValue(HocClkConfigValue_MarikoBoostClock) * 1000000); + } else { + Board::SetHz((SysClkModule)SysClkModule_CPU, (u32)this->config->GetConfigValue(HocClkConfigValue_EristaBoostClock) * 1000000); + } return; } @@ -442,4 +457,52 @@ bool ClockManager::RefreshContext() void ClockManager::SetRNXRTMode(ReverseNXMode mode) { this->rnxSync->SetRTMode(mode); -} \ No newline at end of file +} + +// void ClockManager::set_sd1_voltage(uint32_t voltage_uv) +// { +// // SD1 parameters +// const u32 uv_step = 12500; +// const u32 uv_min = 600000; +// const u32 uv_max = 1237500; +// const u8 volt_addr = 0x17; // MAX77620_REG_SD1 +// const u8 volt_mask = 0x7F; // MAX77620_SD1_VOLT_MASK + +// // Validate input voltage +// if (voltage_uv < uv_min || voltage_uv > uv_max) +// return; + +// // Calculate voltage multiplier +// u32 mult = (voltage_uv + uv_step - 1 - uv_min) / uv_step; +// mult = mult & volt_mask; + +// // Open I2C session to MAX77620 PMIC +// I2cSession session; +// Result res = i2cOpenSession(&session, I2cDevice_Max77620Pmic); +// if (R_FAILED(res)) { +// return; +// } + +// // Read current register value +// u8 current_val = 0; +// res = i2csessionSendAuto(&session, &volt_addr, 1, I2cTransactionOption_Start); +// if (R_FAILED(res)) { +// i2csessionClose(&session); +// return; +// } + +// res = i2csessionReceiveAuto(&session, ¤t_val, 1, I2cTransactionOption_Stop); +// if (R_FAILED(res)) { +// i2csessionClose(&session); +// return; +// } + +// // Mask in the new voltage bits, preserving other bits +// u8 new_val = (current_val & ~volt_mask) | mult; + +// // Write back register with START and STOP conditions +// u8 write_buf[2] = {volt_addr, new_val}; +// res = i2csessionSendAuto(&session, write_buf, sizeof(write_buf), I2cTransactionOption_All); + +// i2csessionClose(&session); +// } \ No newline at end of file diff --git a/Source/sys-clk/sysmodule/src/clock_manager.h b/Source/sys-clk/sysmodule/src/clock_manager.h index c29681d8..543c9ea5 100644 --- a/Source/sys-clk/sysmodule/src/clock_manager.h +++ b/Source/sys-clk/sysmodule/src/clock_manager.h @@ -62,7 +62,7 @@ class ClockManager std::uint32_t count; std::uint32_t list[SYSCLK_FREQ_LIST_MAX]; } freqTable[SysClkModule_EnumMax]; - + // void set_sd1_voltage(uint32_t uv); protected: bool IsAssignableHz(SysClkModule module, std::uint32_t hz); std::uint32_t GetMaxAllowedHz(SysClkModule module, SysClkProfile profile); diff --git a/Source/sys-clk/sysmodule/src/emc_patcher.cpp b/Source/sys-clk/sysmodule/src/emc_patcher.cpp index 6b5c7135..21b301e8 100644 --- a/Source/sys-clk/sysmodule/src/emc_patcher.cpp +++ b/Source/sys-clk/sysmodule/src/emc_patcher.cpp @@ -1,7 +1,26 @@ +/* + * Copyright (c) Souldbminer and Horizon OC Contributors + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <switch.h> #include "emc_patcher.h" #include "file_utils.h" #include "board.h" - +#include "i2c.h" +#include "maxXXXXX.h" #define MC_BASE 0x70019000 #define EMC_BASE 0x7001B000 @@ -60,32 +79,98 @@ void EMCpatcher::Run() { std::scoped_lock lock{this->patcherMutex}; this->config->Refresh(); - this->ApplyEMCPatch(); + // this->ApplyEMCPatch(); + this->I2CApplyVoltage(); } void EMCpatcher::ApplyEMCPatch() { - if(HOSSVC_HAS_MM) { // only for 10.0.0+, older versions need rewrites - u64 mc_virt_addr = 0; - u64 mc_out_size = 0; - u64 emc_virt_addr = 0; - u64 emc_out_size = 0; - Result rc; + // if(HOSSVC_HAS_MM) { // only for 10.0.0+, older versions need rewrites + // u64 mc_virt_addr = 0; + // u64 mc_out_size = 0; + // u64 emc_virt_addr = 0; + // u64 emc_out_size = 0; + // Result rc; - // rc = svcQueryMemoryMapping(&mc_virt_addr, &mc_out_size, MC_BASE, MC_EMC_BASE_SIZE); // map mc - // ASSERT_RESULT_OK(rc, "svcQueryMemoryMapping"); + // rc = svcQueryMemoryMapping(&mc_virt_addr, &mc_out_size, MC_BASE, MC_EMC_BASE_SIZE); // map mc + // ASSERT_RESULT_OK(rc, "svcQueryMemoryMapping"); - // rc = svcQueryMemoryMapping(&emc_virt_addr, &emc_out_size, EMC_BASE, MC_EMC_BASE_SIZE); // map emc - // ASSERT_RESULT_OK(rc, "svcQueryMemoryMapping"); + // rc = svcQueryMemoryMapping(&emc_virt_addr, &emc_out_size, EMC_BASE, MC_EMC_BASE_SIZE); // map emc + // ASSERT_RESULT_OK(rc, "svcQueryMemoryMapping"); - write_reg64(EMC_BASE, EMC_RAS_0, 1); + // write_reg64(EMC_BASE, EMC_RAS_0, 1); - write_reg64(EMC_BASE, EMC_TIMING_CONTROL_0, 0x1); // apply shadow regs + // write_reg64(EMC_BASE, EMC_TIMING_CONTROL_0, 0x1); // apply shadow regs - // svcUnmapMemory((void *)mc_virt_addr, (void *)MC_BASE, MC_EMC_BASE_SIZE); // clean up - // svcUnmapMemory((void *)emc_virt_addr, (void *)EMC_BASE, MC_EMC_BASE_SIZE); + // svcUnmapMemory((void *)mc_virt_addr, (void *)MC_BASE, MC_EMC_BASE_SIZE); // clean up + // svcUnmapMemory((void *)emc_virt_addr, (void *)EMC_BASE, MC_EMC_BASE_SIZE); - } + // } } + +#define EMC_MAX_VOLTAGE_SAFETY_CHECK 1350000 +#define SD3_UV_MIN 600000 +#define SD3_UV_STEP 12500 +#define I2C_DEVICE(bus, addr) ((I2cDevice)(((bus) << 8) | (addr))) + +#define I2C_5 5 +#define MAX77620_I2C_ADDR 0x3C + +#define I2cDevice_MAX77620 I2C_DEVICE(I2C_5, MAX77620_I2C_ADDR) + +void EMCpatcher::I2CApplyVoltage() +{ + if(this->config->GetConfigValue(HocClkConfigValue_EMCVdd2VoltageuV) < EMC_MAX_VOLTAGE_SAFETY_CHECK) { + set_sd1_voltage((u32)this->config->GetConfigValue(HocClkConfigValue_EMCVdd2VoltageuV)); + } +} + +void EMCpatcher::set_sd1_voltage(uint32_t voltage_uv) +{ + // SD1 parameters + const u32 uv_step = 12500; + const u32 uv_min = 600000; + const u32 uv_max = 1237500; + const u8 volt_addr = 0x17; // MAX77620_REG_SD1 + const u8 volt_mask = 0x7F; // MAX77620_SD1_VOLT_MASK + + // Validate input voltage + if (voltage_uv < uv_min || voltage_uv > uv_max) + return; + + // Calculate voltage multiplier + u32 mult = (voltage_uv + uv_step - 1 - uv_min) / uv_step; + mult = mult & volt_mask; + + // Open I2C session to MAX77620 PMIC + I2cSession session; + Result res = i2cOpenSession(&session, I2cDevice_Max77620Pmic); + if (R_FAILED(res)) { + return; + } + + // Read current register value + u8 current_val = 0; + res = i2csessionSendAuto(&session, &volt_addr, 1, I2cTransactionOption_Start); + if (R_FAILED(res)) { + i2csessionClose(&session); + return; + } + + res = i2csessionReceiveAuto(&session, ¤t_val, 1, I2cTransactionOption_Stop); + if (R_FAILED(res)) { + i2csessionClose(&session); + return; + } + + // Mask in the new voltage bits, preserving other bits + u8 new_val = (current_val & ~volt_mask) | mult; + + // Write back register with START and STOP conditions + u8 write_buf[2] = {volt_addr, new_val}; + res = i2csessionSendAuto(&session, write_buf, sizeof(write_buf), I2cTransactionOption_All); + + i2csessionClose(&session); +} \ No newline at end of file diff --git a/Source/sys-clk/sysmodule/src/emc_patcher.h b/Source/sys-clk/sysmodule/src/emc_patcher.h index 6c288333..ef1e026c 100644 --- a/Source/sys-clk/sysmodule/src/emc_patcher.h +++ b/Source/sys-clk/sysmodule/src/emc_patcher.h @@ -1,3 +1,20 @@ +/* + * Copyright (c) Souldbminer and Horizon OC Contributors + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + #pragma once #include <mutex> @@ -67,4 +84,6 @@ public: void Run(); void ApplyEMCPatch(); + void I2CApplyVoltage(); + static void set_sd1_voltage(uint32_t uv); }; diff --git a/Source/sys-clk/sysmodule/src/fancontrol.c b/Source/sys-clk/sysmodule/src/fancontrol.c index 11e1be1d..98d59bdd 100644 --- a/Source/sys-clk/sysmodule/src/fancontrol.c +++ b/Source/sys-clk/sysmodule/src/fancontrol.c @@ -1,3 +1,20 @@ +/* + * Copyright (c) Souldbminer and Horizon OC Contributors + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + #include "fancontrol.h" #include "tmp451.h" diff --git a/Source/sys-clk/sysmodule/src/fancontrol.h b/Source/sys-clk/sysmodule/src/fancontrol.h index d7d16030..d9fd471f 100644 --- a/Source/sys-clk/sysmodule/src/fancontrol.h +++ b/Source/sys-clk/sysmodule/src/fancontrol.h @@ -1,3 +1,20 @@ +/* + * Copyright (c) Souldbminer and Horizon OC Contributors + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + #pragma once #ifdef __cplusplus diff --git a/Source/sys-clk/sysmodule/src/i2c.h b/Source/sys-clk/sysmodule/src/i2c.h index 653e7e1e..047cd14d 100644 --- a/Source/sys-clk/sysmodule/src/i2c.h +++ b/Source/sys-clk/sysmodule/src/i2c.h @@ -1,3 +1,20 @@ +/* + * Copyright (c) Souldbminer and Horizon OC Contributors + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + #ifndef I2C_H #define I2C_H diff --git a/Source/sys-clk/sysmodule/src/integrations.cpp b/Source/sys-clk/sysmodule/src/integrations.cpp index 7ac0b04e..d6f91e7a 100644 --- a/Source/sys-clk/sysmodule/src/integrations.cpp +++ b/Source/sys-clk/sysmodule/src/integrations.cpp @@ -1,3 +1,20 @@ +/* + * Copyright (c) Souldbminer and Horizon OC Contributors + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + #include "integrations.h" ReverseNXSync::ReverseNXSync() diff --git a/Source/sys-clk/sysmodule/src/integrations.h b/Source/sys-clk/sysmodule/src/integrations.h index 19678641..893f925d 100644 --- a/Source/sys-clk/sysmodule/src/integrations.h +++ b/Source/sys-clk/sysmodule/src/integrations.h @@ -1,3 +1,20 @@ +/* + * Copyright (c) Souldbminer and Horizon OC Contributors + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + #pragma once #include <atomic> #include <cstdio> diff --git a/Source/sys-clk/sysmodule/src/ipc_service.cpp b/Source/sys-clk/sysmodule/src/ipc_service.cpp index 3d3dfcb7..9e936792 100644 --- a/Source/sys-clk/sysmodule/src/ipc_service.cpp +++ b/Source/sys-clk/sysmodule/src/ipc_service.cpp @@ -191,7 +191,7 @@ Result IpcService::ServiceHandlerFunc(void* arg, const IpcServerRequest* r, u8* return ipcSrv->SetReverseNXRTMode(mode); } break; - case HocClkIpcCmd_UpdateEMCRegs: // Trigger, not data + case HocClkIpcCmd_UpdateEMC: // Trigger, not data return ipcSrv->PatchEmcRegs(); break; } @@ -354,6 +354,6 @@ Result IpcService::SetReverseNXRTMode(ReverseNXMode mode) { Result IpcService::PatchEmcRegs() { - EMCpatcher::GetInstance()->Run(); +// EMCpatcher::GetInstance()->Run(); return 0; } diff --git a/Source/sys-clk/sysmodule/src/main.cpp b/Source/sys-clk/sysmodule/src/main.cpp index 5c10a49f..9820cdfb 100644 --- a/Source/sys-clk/sysmodule/src/main.cpp +++ b/Source/sys-clk/sysmodule/src/main.cpp @@ -38,6 +38,9 @@ #include "clock_manager.h" #include "ipc_service.h" #include "fancontrol.h" +#include "emc_patcher.h" +#include "pwm_dimming.h" + #define INNER_HEAP_SIZE 0x30000 extern "C" @@ -113,17 +116,25 @@ int main(int argc, char** argv) { Board::Initialize(); ProcessManagement::Initialize(); - + PWMDimmer::Initialize(); ProcessManagement::WaitForQLaunch(); ClockManager* clockMgr = new ClockManager(); IpcService* ipcSrv = new IpcService(clockMgr); + EMCpatcher* emcPatcher = new EMCpatcher(); + PWMDimmer* pwmDimmer = PWMDimmer::GetInstance(); FileUtils::LogLine("Ready"); clockMgr->SetRunning(true); clockMgr->GetConfig()->SetEnabled(true); ipcSrv->SetRunning(true); + pwmDimmer->Initialize(); + + emcPatcher->GetConfig()->SetEnabled(true); + emcPatcher->Run(); + pwmDimmer->Start(); + TemperaturePoint *table; ReadConfigFile(&table); InitFanController(table); diff --git a/Source/sys-clk/sysmodule/src/maxXXXXX.h b/Source/sys-clk/sysmodule/src/maxXXXXX.h new file mode 100644 index 00000000..2da06670 --- /dev/null +++ b/Source/sys-clk/sysmodule/src/maxXXXXX.h @@ -0,0 +1,691 @@ +/* + * Copyright (c) Souldbminer and Horizon OC Contributors + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + + +/* + * Defining registers address and its bit definitions of MAX77620 and MAX20024 + * + * Copyright (c) 2019-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. + */ + +#include <switch.h> + +#ifndef MAX77XXX_H +#define MAX77XXX_H + + +#define MAX17050_BOARD_CGAIN 2 /* Actual: 1.99993 */ +#define MAX17050_BOARD_SNS_RESISTOR_UOHM 5000 /* 0.005 Ohm */ + +#define MAX17050_STATUS_BattAbsent BIT(3) + +/* Consider RepCap which is less then 10 units below FullCAP full */ +#define MAX17050_FULL_THRESHOLD 10 + +#define MAX17050_CHARACTERIZATION_DATA_SIZE 48 + +#define MAXIM17050_I2C_ADDR 0x36 + +enum MAX17050_reg { + MAX17050_STATUS = 0x00, + MAX17050_VALRT_Th = 0x01, + MAX17050_TALRT_Th = 0x02, + MAX17050_SALRT_Th = 0x03, + MAX17050_AtRate = 0x04, + MAX17050_RepCap = 0x05, + MAX17050_RepSOC = 0x06, + MAX17050_Age = 0x07, + MAX17050_TEMP = 0x08, + MAX17050_VCELL = 0x09, + MAX17050_Current = 0x0A, + MAX17050_AvgCurrent = 0x0B, + + MAX17050_SOC = 0x0D, + MAX17050_AvSOC = 0x0E, + MAX17050_RemCap = 0x0F, + MAX17050_FullCAP = 0x10, + MAX17050_TTE = 0x11, + MAX17050_QRTbl00 = 0x12, + MAX17050_FullSOCThr = 0x13, + MAX17050_RSLOW = 0x14, + + MAX17050_AvgTA = 0x16, + MAX17050_Cycles = 0x17, + MAX17050_DesignCap = 0x18, + MAX17050_AvgVCELL = 0x19, + MAX17050_MinMaxTemp = 0x1A, + MAX17050_MinMaxVolt = 0x1B, + MAX17050_MinMaxCurr = 0x1C, + MAX17050_CONFIG = 0x1D, + MAX17050_ICHGTerm = 0x1E, + MAX17050_AvCap = 0x1F, + MAX17050_ManName = 0x20, + MAX17050_DevName = 0x21, + MAX17050_QRTbl10 = 0x22, + MAX17050_FullCAPNom = 0x23, + MAX17050_TempNom = 0x24, + MAX17050_TempLim = 0x25, + MAX17050_TempHot = 0x26, + MAX17050_AIN = 0x27, + MAX17050_LearnCFG = 0x28, + MAX17050_FilterCFG = 0x29, + MAX17050_RelaxCFG = 0x2A, + MAX17050_MiscCFG = 0x2B, + MAX17050_TGAIN = 0x2C, + MAX17050_TOFF = 0x2D, + MAX17050_CGAIN = 0x2E, + MAX17050_COFF = 0x2F, + + MAX17050_QRTbl20 = 0x32, + MAX17050_SOC_empty = 0x33, + MAX17050_T_empty = 0x34, + MAX17050_FullCAP0 = 0x35, + MAX17050_LAvg_empty = 0x36, + MAX17050_FCTC = 0x37, + MAX17050_RCOMP0 = 0x38, + MAX17050_TempCo = 0x39, + MAX17050_V_empty = 0x3A, + MAX17050_K_empty0 = 0x3B, + MAX17050_TaskPeriod = 0x3C, + MAX17050_FSTAT = 0x3D, + MAX17050_TIMER = 0x3E, + MAX17050_SHDNTIMER = 0x3F, + + MAX17050_QRTbl30 = 0x42, + + MAX17050_dQacc = 0x45, + MAX17050_dPacc = 0x46, + + MAX17050_VFSOC0 = 0x48, + + Max17050_QH0 = 0x4C, + MAX17050_QH = 0x4D, + MAX17050_QL = 0x4E, + + MAX17050_MinVolt = 0x50, // Custom ID. Not to be sent to i2c. + MAX17050_MaxVolt = 0x51, // Custom ID. Not to be sent to i2c. + + MAX17050_VFSOC0Enable = 0x60, + MAX17050_MODELEnable1 = 0x62, + MAX17050_MODELEnable2 = 0x63, + + MAX17050_MODELChrTbl = 0x80, + + MAX17050_OCV = 0xEE, + + MAX17050_OCVInternal = 0xFB, + + MAX17050_VFSOC = 0xFF, +}; + +#define MAX77620_I2C_ADDR 0x3C + +/* GLOBAL, PMIC, GPIO, FPS, ONOFFC, CID Registers */ +#define MAX77620_REG_CNFGGLBL1 0x00 +#define MAX77620_CNFGGLBL1_LBRSTEN BIT(0) +#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_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_MPPLD BIT(6) +#define MAX77620_CNFGGLBL1_LBDAC_EN BIT(7) + +#define MAX77620_REG_CNFGGLBL2 0x01 +#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_WDTEN BIT(2) +#define MAX77620_WDTSLPC BIT(3) +#define MAX77620_WDTOFFC BIT(4) +#define MAX77620_GLBL_LPM BIT(5) +#define MAX77620_I2CTWD_MASK 0xC0 +#define MAX77620_I2CTWD_DISABLED 0x00 +#define MAX77620_I2CTWD_1_33ms 0x40 +#define MAX77620_I2CTWD_35_7ms 0x80 +#define MAX77620_I2CTWD_41_7ms 0xC0 + +#define MAX77620_REG_CNFGGLBL3 0x02 +#define MAX77620_WDTC_MASK 0x3 + +#define MAX77620_REG_CNFG1_32K 0x03 +#define MAX77620_CNFG1_PWR_MD_32K_MASK 0x3 +#define MAX77620_CNFG1_32K_OUT0_EN BIT(2) +#define MAX77620_CNFG1_32KLOAD_MASK 0x30 +#define MAX77620_CNFG1_32K_OK BIT(7) + +#define MAX77620_REG_CNFGBBC 0x04 +#define MAX77620_CNFGBBC_ENABLE BIT(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 BIT(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_REG_IRQTOPM 0x0D +#define MAX77620_IRQ_TOP_ONOFF_MASK BIT(1) +#define MAX77620_IRQ_TOP_32K_MASK BIT(2) +#define MAX77620_IRQ_TOP_RTC_MASK BIT(3) +#define MAX77620_IRQ_TOP_GPIO_MASK BIT(4) +#define MAX77620_IRQ_TOP_LDO_MASK BIT(5) +#define MAX77620_IRQ_TOP_SD_MASK BIT(6) +#define MAX77620_IRQ_TOP_GLBL_MASK BIT(7) + +#define MAX77620_REG_INTLBT 0x06 +#define MAX77620_REG_INTENLBT 0x0E +#define MAX77620_IRQ_GLBLM_MASK BIT(0) +#define MAX77620_IRQ_TJALRM2_MASK BIT(1) +#define MAX77620_IRQ_TJALRM1_MASK BIT(2) +#define MAX77620_IRQ_LBM_MASK BIT(3) + +#define MAX77620_REG_IRQSD 0x07 +#define MAX77620_REG_IRQMASKSD 0x0F +#define MAX77620_IRQSD_PFI_SD3 BIT(4) +#define MAX77620_IRQSD_PFI_SD2 BIT(5) +#define MAX77620_IRQSD_PFI_SD1 BIT(6) +#define MAX77620_IRQSD_PFI_SD0 BIT(7) + +#define MAX77620_REG_IRQ_LVL2_L0_7 0x08 // LDO number that irq occurred. +#define MAX77620_REG_IRQ_MSK_L0_7 0x10 +#define MAX77620_REG_IRQ_LVL2_L8 \ + 0x09 // LDO number that irq occurred. Only bit0: LDO8 is valid. +#define MAX77620_REG_IRQ_MSK_L8 0x11 +#define MAX77620_REG_IRQ_LVL2_GPIO 0x0A // Edge detection interrupt. + +#define MAX77620_REG_ONOFFIRQ 0x0B +#define MAX77620_REG_ONOFFIRQM 0x12 +#define MAX77620_ONOFFIRQ_MRWRN BIT(0) +#define MAX77620_ONOFFIRQ_EN0_1SEC BIT(1) +#define MAX77620_ONOFFIRQ_EN0_F BIT(2) +#define MAX77620_ONOFFIRQ_EN0_R BIT(3) +#define MAX77620_ONOFFIRQ_LID_F BIT(4) +#define MAX77620_ONOFFIRQ_LID_R BIT(5) +#define MAX77620_ONOFFIRQ_ACOK_F BIT(6) +#define MAX77620_ONOFFIRQ_ACOK_R BIT(7) + +#define MAX77620_REG_NVERC 0x0C // Shutdown reason (non-volatile). +#define MAX77620_NVERC_SHDN BIT(0) +#define MAX77620_NVERC_WTCHDG BIT(1) +#define MAX77620_NVERC_HDRST BIT(2) +#define MAX77620_NVERC_TOVLD BIT(3) +#define MAX77620_NVERC_MBLSD BIT(4) +#define MAX77620_NVERC_MBO BIT(5) +#define MAX77620_NVERC_MBU BIT(6) +#define MAX77620_NVERC_RSTIN BIT(7) + +#define MAX77620_REG_STATLBT 0x13 +#define MAX77620_REG_STATSD 0x14 + +#define MAX77620_REG_ONOFFSTAT 0x15 +#define MAX77620_ONOFFSTAT_LID BIT(0) +#define MAX77620_ONOFFSTAT_ACOK BIT(1) +#define MAX77620_ONOFFSTAT_EN0 BIT(2) + +/* 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_REG_DVSSD0 0x1B +#define MAX77620_REG_DVSSD1 0x1C +#define MAX77620_SDX_VOLT_MASK 0xFF +#define MAX77620_SD0_VOLT_MASK 0x7F // Max is 0x40. +#define MAX77620_SD1_VOLT_MASK 0x7F // Max is 0x4C. +#define MAX77620_LDO_VOLT_MASK 0x3F + +#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_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 BIT(3) +#define MAX77620_SD_CFG1_ADE_DISABLE 0 +#define MAX77620_SD_CFG1_ADE_ENABLE BIT(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 BIT(2) +#define MAX77620_SD_CFG1_FPWM_SD_SKIP 0 +#define MAX77620_SD_CFG1_FPWM_SD_FPWM BIT(2) +#define MAX77620_SD_CFG1_MPOK_MASK BIT(1) +#define MAX77620_SD_CFG1_FSRADE_SD_MASK BIT(0) +#define MAX77620_SD_CFG1_FSRADE_SD_DISABLE 0 +#define MAX77620_SD_CFG1_FSRADE_SD_ENABLE BIT(0) + +#define MAX77620_REG_SD_CFG2 0x22 +#define MAX77620_SD_CNF2_RSVD BIT(0) +#define MAX77620_SD_CNF2_ROVS_EN_SD1 BIT(1) +#define MAX77620_SD_CNF2_ROVS_EN_SD0 BIT(2) + +#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 +/*! LDO CFG */ +#define MAX77620_LDO_POWER_MODE_SHIFT 6 +#define MAX77620_LDO_POWER_MODE_MASK (3 << MAX77620_LDO_POWER_MODE_SHIFT) +#define MAX77620_POWER_MODE_NORMAL 3 +#define MAX77620_POWER_MODE_LPM 2 +#define MAX77620_POWER_MODE_GLPM 1 +#define MAX77620_POWER_MODE_DISABLE 0 +/*! LDO CFG2 */ +#define MAX77620_LDO_CFG2_SS_MASK (1 << 0) +#define MAX77620_LDO_CFG2_SS_FAST (0 << 0) +#define MAX77620_LDO_CFG2_SS_SLOW (1 << 0) +#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_MPOK_MASK BIT(2) +#define MAX77620_LDO_CFG2_POK_MASK BIT(3) +#define MAX77620_LDO_CFG2_COMP_SHIFT 4 +#define MAX77620_LDO_CFG2_COMP_MASK (3 << MAX77620_LDO_COMP_SHIFT) +#define MAX77620_LDO_CFG2_COMP_SLOW 3 +#define MAX77620_LDO_CFG2_COMP_MID_SLOW 2 +#define MAX77620_LDO_CFG2_COMP_MID_FAST 1 +#define MAX77620_LDO_CFG2_COMP_FAST 0 +#define MAX77620_LDO_CFG2_ALPM_EN_MASK BIT(6) +#define MAX77620_LDO_CFG2_OVCLMP_MASK BIT(7) + +#define MAX77620_REG_LDO_CFG3 0x35 +#define MAX77620_LDO_BIAS_EN BIT(0) +#define MAX77620_TRACK4_SHIFT 5 +#define MAX77620_TRACK4_MASK (1 << MAX77620_TRACK4_SHIFT) + +#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_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_GPIO_OUTPUT_DISABLE 0 +#define MAX77620_GPIO_OUTPUT_ENABLE 1 + +#define MAX77620_REG_PUE_GPIO 0x3E // Gpio Pullup resistor enable. +#define MAX77620_REG_PDE_GPIO 0x3F // Gpio Pulldown resistor enable. + +#define MAX77620_REG_AME_GPIO \ + 0x40 // Gpio pinmuxing. Clear bits are Standard GPIO. + +#define MAX77620_REG_ONOFFCNFG1 0x41 +#define MAX20024_ONOFFCNFG1_CLRSE 0x18 +#define MAX77620_ONOFFCNFG1_PWR_OFF BIT(1) +#define MAX77620_ONOFFCNFG1_SLPEN BIT(2) +#define MAX77620_ONOFFCNFG1_MRT_SHIFT 0x3 +#define MAX77620_ONOFFCNFG1_MRT_MASK 0x38 +#define MAX77620_ONOFFCNFG1_RSVD BIT(6) +#define MAX77620_ONOFFCNFG1_SFT_RST BIT(7) + +#define MAX77620_REG_ONOFFCNFG2 0x42 +#define MAX77620_ONOFFCNFG2_WK_EN0 BIT(0) +#define MAX77620_ONOFFCNFG2_WK_ALARM2 BIT(1) +#define MAX77620_ONOFFCNFG2_WK_ALARM1 BIT(2) +#define MAX77620_ONOFFCNFG2_WK_MBATT \ + BIT(3) // MBATT event generates a wakeup signal. use it in android/l4t? +#define MAX77620_ONOFFCNFG2_WK_ACOK BIT(4) +#define MAX77620_ONOFFCNFG2_SLP_LPM_MSK BIT(5) +#define MAX77620_ONOFFCNFG2_WD_RST_WK BIT(6) +#define MAX77620_ONOFFCNFG2_SFT_RST_WK BIT(7) + +/* FPS Registers */ +#define MAX77620_REG_FPS_CFG0 0x43 // FPS0. +#define MAX77620_REG_FPS_CFG1 0x44 // FPS1. +#define MAX77620_REG_FPS_CFG2 0x45 // FPS2. +#define MAX77620_FPS_ENFPS_SW_MASK 0x01 +#define MAX77620_FPS_ENFPS_SW 0x01 +#define MAX77620_FPS_EN_SRC_SHIFT 1 +#define MAX77620_FPS_EN_SRC_MASK 0x06 +#define MAX77620_FPS_TIME_PERIOD_SHIFT 3 +#define MAX77620_FPS_TIME_PERIOD_MASK 0x38 + +#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_GPIO1 0x54 +#define MAX77620_REG_FPS_GPIO2 0x55 +#define MAX77620_REG_FPS_GPIO3 0x56 +#define MAX77620_REG_FPS_RSO 0x57 +#define MAX77620_FPS_PD_PERIOD_SHIFT 0 +#define MAX77620_FPS_PD_PERIOD_MASK 0x07 +#define MAX77620_FPS_PU_PERIOD_SHIFT 3 +#define MAX77620_FPS_PU_PERIOD_MASK 0x38 +#define MAX77620_FPS_SRC_SHIFT 6 +#define MAX77620_FPS_SRC_MASK 0xC0 + +#define MAX77620_FPS_COUNT 3 +#define MAX77620_FPS_PERIOD_MIN_US 40 +#define MAX77620_FPS_PERIOD_MAX_US 2560 + +#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 // OTP version. +#define MAX77620_REG_CID5 0x5D // ES version. +#define MAX77620_CID_DIDO_MASK 0xF +#define MAX77620_CID_DIDO_SHIFT 0 +#define MAX77620_CID_DIDM_MASK 0xF0 +#define MAX77620_CID_DIDM_SHIFT 4 + +/* Device Identification Metal */ +#define MAX77620_CID5_DIDM(n) (((n) >> 4) & 0xF) +/* Device Indentification OTP */ +#define MAX77620_CID5_DIDO(n) ((n) & 0xF) + +#define MAX77620_REG_DVSSD4 0x5E +#define MAX20024_REG_MAX_ADD 0x70 + +#define MAX77620_IRQ_LVL2_GPIO_EDGE0 BIT(0) +#define MAX77620_IRQ_LVL2_GPIO_EDGE1 BIT(1) +#define MAX77620_IRQ_LVL2_GPIO_EDGE2 BIT(2) +#define MAX77620_IRQ_LVL2_GPIO_EDGE3 BIT(3) +#define MAX77620_IRQ_LVL2_GPIO_EDGE4 BIT(4) +#define MAX77620_IRQ_LVL2_GPIO_EDGE5 BIT(5) +#define MAX77620_IRQ_LVL2_GPIO_EDGE6 BIT(6) +#define MAX77620_IRQ_LVL2_GPIO_EDGE7 BIT(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, +}; + +#define MAX77812_PHASE31_CPU_I2C_ADDR \ + 0x31 // High power GPU. 2 Outputs: 3-phase M1 + 1-phase M4. +#define MAX77812_PHASE211_CPU_I2C_ADDR \ + 0x33 // Low power GPU. 3 Outputs: 2-phase M1 + 1-phase M3 + 1-phase M4. + +#define MAX77812_REG_RSET 0x00 +#define MAX77812_REG_INT_SRC 0x01 +#define MAX77812_REG_INT_SRC_M 0x02 +#define MAX77812_REG_TOPSYS_INT 0x03 +#define MAX77812_REG_TOPSYS_INT_M 0x04 +#define MAX77812_REG_TOPSYS_STAT 0x05 +#define MAX77812_REG_EN_CTRL 0x06 +#define MAX77812_EN_CTRL_ENABLE 1 +#define MAX77812_EN_CTRL_EN_M1_SHIFT 0 +#define MAX77812_EN_CTRL_EN_M1_MASK (1 << MAX77812_EN_CTRL_EN_M1_SHIFT) +#define MAX77812_EN_CTRL_EN_M2_SHIFT 2 +#define MAX77812_EN_CTRL_EN_M2_MASK (1 << MAX77812_EN_CTRL_EN_M2_SHIFT) +#define MAX77812_EN_CTRL_EN_M3_SHIFT 4 +#define MAX77812_EN_CTRL_EN_M3_MASK (1 << MAX77812_EN_CTRL_EN_M3_SHIFT) +#define MAX77812_EN_CTRL_EN_M4_SHIFT 6 +#define MAX77812_EN_CTRL_EN_M4_MASK (1 << MAX77812_EN_CTRL_EN_M4_SHIFT) +#define MAX77812_REG_STUP_DLY2 0x07 +#define MAX77812_REG_STUP_DLY3 0x08 +#define MAX77812_REG_STUP_DLY4 0x09 +#define MAX77812_REG_SHDN_DLY1 0x0A +#define MAX77812_REG_SHDN_DLY2 0x0B +#define MAX77812_REG_SHDN_DLY3 0x0C +#define MAX77812_REG_SHDN_DLY4 0x0D +#define MAX77812_REG_WDTRSTB_DEB 0x0E +#define MAX77812_REG_GPI_FUNC 0x0F +#define MAX77812_REG_GPI_DEB1 0x10 +#define MAX77812_REG_GPI_DEB2 0x11 +#define MAX77812_REG_GPI_PD_CTRL 0x12 +#define MAX77812_REG_PROT_CFG 0x13 +#define MAX77812_REG_VERSION 0x14 +#define MAX77812_REG_I2C_CFG 0x15 +#define MAX77812_REG_BUCK_INT 0x20 +#define MAX77812_REG_BUCK_INT_M 0x21 +#define MAX77812_REG_BUCK_STAT 0x22 +#define MAX77812_REG_M1_VOUT 0x23 // GPU. +#define MAX77812_REG_M2_VOUT 0x24 +#define MAX77812_REG_M3_VOUT 0x25 // DRAM on PHASE211. +#define MAX77812_REG_M4_VOUT 0x26 // CPU. +#define MAX77812_REG_M1_VOUT_D 0x27 +#define MAX77812_REG_M2_VOUT_D 0x28 +#define MAX77812_REG_M3_VOUT_D 0x29 +#define MAX77812_REG_M4_VOUT_D 0x2A +#define MAX77812_REG_M1_VOUT_S 0x2B +#define MAX77812_REG_M2_VOUT_S 0x2C +#define MAX77812_REG_M3_VOUT_S 0x2D +#define MAX77812_REG_M4_VOUT_S 0x2E +#define MAX77812_REG_M1_CFG 0x2F // HOS: M1_ILIM - 7.2A/4.8A. +#define MAX77812_REG_M2_CFG 0x30 // HOS: M2_ILIM - 7.2A/4.8A. +#define MAX77812_REG_M3_CFG 0x31 // HOS: M3_ILIM - 7.2A/4.8A. +#define MAX77812_REG_M4_CFG 0x32 // HOS: M4_ILIM - 7.2A/4.8A. +#define MAX77812_REG_GLB_CFG1 0x33 // HOS: B_SD_SR/B_SS_SR - 5mV/us. +#define MAX77812_REG_GLB_CFG2 0x34 // HOS: B_RD_SR/B_RU_SR - 5mV/us +#define MAX77812_REG_GLB_CFG3 0x35 + +/*! Protected area and settings only for MAX77812_ES2_VERSION */ +#define MAX77812_REG_GLB_CFG4 0x36 // QS: 0xBB. +#define MAX77812_REG_GLB_CFG5 0x37 // QS: 0x39. ES2: Set to 0x3E. +#define MAX77812_REG_GLB_CFG6 0x38 // QS: 0x88. ES2: Set to 0x90. +#define MAX77812_REG_GLB_CFG7 0x39 // QS: 0x04. +#define MAX77812_REG_GLB_CFG8 0x3A // QS: 0x3A. ES2: Set to 0x3A. + +#define MAX77812_REG_PROT_ACCESS 0xFD // 0x00: Lock, 0x5A: Unlock. +#define MAX77812_REG_UNKNOWN 0xFE + +#define MAX77812_REG_EN_CTRL_MASK(n) BIT(n) +#define MAX77812_START_SLEW_RATE_MASK 0x07 +#define MAX77812_SHDN_SLEW_RATE_MASK 0x70 +#define MAX77812_RAMPUP_SLEW_RATE_MASK 0x07 +#define MAX77812_RAMPDOWN_SLEW_RATE_MASK 0x70 +#define MAX77812_SLEW_RATE_SHIFT 4 + +#define MAX77812_OP_ACTIVE_DISCHARGE_MASK BIT(7) +#define MAX77812_PEAK_CURRENT_LMT_MASK 0x70 +#define MAX77812_SWITCH_FREQ_MASK 0x0C +#define MAX77812_FORCED_PWM_MASK BIT(1) +#define MAX77812_SLEW_RATE_CNTRL_MASK BIT(0) +#define MAX77812_START_SHD_DELAY_MASK 0x1F +#define MAX77812_VERSION_MASK 0x07 +#define MAX77812_ES2_VERSION 0x04 +#define MAX77812_QS_VERSION 0x05 + +#define MAX77812_BUCK_VOLT_MASK 0xFF + +#define BQ24193_I2C_ADDR 0x6B + +// REG 0 masks. +#define BQ24193_INCONFIG_INLIMIT_MASK (7 << 0) +#define BQ24193_INCONFIG_VINDPM_MASK 0x78 +#define BQ24193_INCONFIG_HIZ_EN_MASK (1 << 7) + +// REG 1 masks. +#define BQ24193_PORCONFIG_BOOST_MASK (1 << 0) +#define BQ24193_PORCONFIG_SYSMIN_MASK (7 << 1) // 3000uV HOS default. +#define BQ24193_PORCONFIG_CHGCONFIG_MASK (3 << 4) +#define BQ24193_PORCONFIG_CHGCONFIG_CHARGER_EN (1 << 4) +#define BQ24193_PORCONFIG_I2CWATCHDOG_MASK (1 << 6) +#define BQ24193_PORCONFIG_RESET_MASK (1 << 7) + +// REG 2 masks. +#define BQ24193_CHRGCURR_20PCT_MASK (1 << 0) +#define BQ24193_CHRGCURR_ICHG_MASK 0xFC + +// REG 3 masks. +#define BQ24193_PRECHRG_ITERM 0x0F +#define BQ24193_PRECHRG_IPRECHG 0xF0 + +// REG 4 masks. +#define BQ24193_CHRGVOLT_VTHRES (1 << 0) +#define BQ24193_CHRGVOLT_BATTLOW (1 << 1) +#define BQ24193_CHRGVOLT_VREG 0xFC + +// REG 5 masks. +#define BQ24193_CHRGTERM_ISET_MASK (1 << 0) +#define BQ24193_CHRGTERM_CHGTIMER_MASK (3 << 1) +#define BQ24193_CHRGTERM_ENTIMER_MASK (1 << 3) +#define BQ24193_CHRGTERM_WATCHDOG_MASK (3 << 4) +#define BQ24193_CHRGTERM_TERM_ST_MASK (1 << 6) +#define BQ24193_CHRGTERM_TERM_EN_MASK (1 << 7) + +// REG 6 masks. +#define BQ24193_IRTHERMAL_THERM_MASK (3 << 0) +#define BQ24193_IRTHERMAL_VCLAMP_MASK (7 << 2) +#define BQ24193_IRTHERMAL_BATTCOMP_MASK (7 << 5) + +// REG 7 masks. +#define BQ24193_MISC_INT_MASK (3 << 0) +#define BQ24193_MISC_VSET_MASK (1 << 4) +#define BQ24193_MISC_BATFET_DI_MASK (1 << 5) +#define BQ24193_MISC_TMR2X_EN_MASK (1 << 6) +#define BQ24193_MISC_DPDM_EN_MASK (1 << 7) + +// REG 8 masks. +#define BQ24193_STATUS_VSYS_MASK (1 << 0) +#define BQ24193_STATUS_THERM_MASK (1 << 1) +#define BQ24193_STATUS_PG_MASK (1 << 2) +#define BQ24193_STATUS_DPM_MASK (1 << 3) +#define BQ24193_STATUS_CHRG_MASK (3 << 4) +#define BQ24193_STATUS_VBUS_MASK (3 << 6) + +// REG 9 masks. +#define BQ24193_FAULT_THERM_MASK (7 << 0) +#define BQ24193_FAULT_BATT_OVP_MASK (1 << 3) +#define BQ24193_FAULT_CHARGE_MASK (3 << 4) +#define BQ24193_FAULT_BOOST_MASK (1 << 6) +#define BQ24193_FAULT_WATCHDOG_MASK (1 << 7) + +// REG A masks. +#define BQ24193_VENDORPART_DEV_MASK (3 << 0) +#define BQ24193_VENDORPART_PN_MASK (7 << 3) + +enum BQ24193_reg { + BQ24193_InputSource = 0x00, + BQ24193_PORConfig = 0x01, + BQ24193_ChrgCurr = 0x02, + BQ24193_PreChrgTerm = 0x03, + BQ24193_ChrgVolt = 0x04, + BQ24193_ChrgTermTimer = 0x05, + BQ24193_IRCompThermal = 0x06, + BQ24193_Misc = 0x07, + BQ24193_Status = 0x08, + BQ24193_FaultReg = 0x09, + BQ24193_VendorPart = 0x0A, +}; + +enum BQ24193_reg_prop { + BQ24193_InputVoltageLimit, // REG 0. + BQ24193_InputCurrentLimit, // REG 0. + BQ24193_SystemMinimumVoltage, // REG 1. + BQ24193_FastChargeCurrentLimit, // REG 2. + BQ24193_ChargeVoltageLimit, // REG 4. + BQ24193_RechargeThreshold, // REG 4. + BQ24193_ThermalRegulation, // REG 6. + BQ24193_ChargeStatus, // REG 8. + BQ24193_TempStatus, // REG 9. + BQ24193_DevID, // REG A. + BQ24193_ProductNumber, // REG A. +}; + +#endif /* MAX77XXX_H */ diff --git a/Source/sys-clk/sysmodule/src/pwm_dimming.cpp b/Source/sys-clk/sysmodule/src/pwm_dimming.cpp new file mode 100644 index 00000000..9755b94a --- /dev/null +++ b/Source/sys-clk/sysmodule/src/pwm_dimming.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) Souldbminer and Horizon OC Contributors + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#include <cstdint> +#include <mutex> +#include <switch.h> + +#include "config.h" +#include "errors.h" +#include "file_utils.h" +#include "pwm_dimming.h" +#define PWM_DIMMING_HZ_RATE 5e3 // 5KHz PWM dimming, should be decent for most people +#define DUTY_CYCLE_LEGNTH_NS (1e9 / (double)(PWM_DIMMING_HZ_RATE)) + +float initial_brightness = 1.0f; +float current_brightness = 1.0f; +Thread pwmThread; + +PWMDimmer *PWMDimmer::instance = nullptr; + +PWMDimmer *PWMDimmer::GetInstance() +{ + return instance; +} + +void PWMDimmer::Initialize() +{ + if (!instance) { + instance = new PWMDimmer(); + FileUtils::LogLine("[pwmDim] Initialized PWMDimmer"); + } +} + +Config *PWMDimmer::GetConfig() +{ + return this->config; +} + +void PWMDimmer::Exit() +{ + if (instance) { + FileUtils::LogLine("[pwmDim] Exiting PWMDimmer"); + delete instance; + instance = nullptr; + } +} + +PWMDimmer::PWMDimmer() +{ + this->config = Config::CreateDefault(); +} + +PWMDimmer::~PWMDimmer() +{ + delete this->config; +} + +void PWMDimmer::Start() +{ + std::scoped_lock lock{ this->patcherMutex }; + this->config->Refresh(); + this->StartPWMDimming(); + + u64 sku = 0; + Result rc = splInitialize(); + ASSERT_RESULT_OK(rc, "splInitialize"); + + rc = splGetConfig(SplConfigItem_HardwareType, &sku); + ASSERT_RESULT_OK(rc, "splGetConfig"); + + splExit(); + if (sku == HocClkConsoleType_OLED && this->config->GetConfigValue(HocClkConfigValue_PWMDimming)) { + lblGetCurrentBrightnessSetting(&initial_brightness); + Result rc = + threadCreate(&pwmThread, ThreadEntry, this, NULL, 0x4000, 0x2B, -2); + if (R_FAILED(rc)) { + return; + } + + rc = threadStart(&pwmThread); + if (R_FAILED(rc)) { + threadClose(&pwmThread); + return; + } + } +} + +void PWMDimmer::ThreadEntry(void *arg) +{ + auto *self = static_cast<PWMDimmer *>(arg); + self->StartPWMDimming(); +} + +void PWMDimmer::StartPWMDimming() +{ + Result rc = lblInitialize(); + if (R_FAILED(rc)) { + return; + } + for (;;) { + lblGetCurrentBrightnessSetting(¤t_brightness); + lblSetCurrentBrightnessSetting(current_brightness); + svcSleepThread(DUTY_CYCLE_LEGNTH_NS / 2); + lblSetCurrentBrightnessSetting(0.0f); + svcSleepThread(DUTY_CYCLE_LEGNTH_NS / 2); + } +} diff --git a/Source/sys-clk/sysmodule/src/pwm_dimming.h b/Source/sys-clk/sysmodule/src/pwm_dimming.h new file mode 100644 index 00000000..555552a3 --- /dev/null +++ b/Source/sys-clk/sysmodule/src/pwm_dimming.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) Souldbminer and Horizon OC Contributors + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include <mutex> +#include <cstdint> +#include "config.h" +#include "errors.h" + +class PWMDimmer +{ + public: + static PWMDimmer* GetInstance(); + static void Initialize(); + Config *GetConfig(); + static void Exit(); + + PWMDimmer(); + ~PWMDimmer(); + static void ThreadEntry(void* arg); + + void Start(); + void StartPWMDimming(); + + private: + static PWMDimmer* instance; + Config* config; + std::mutex patcherMutex; + +}; diff --git a/build.sh b/build.sh index d06a4550..ec836fbe 100644 --- a/build.sh +++ b/build.sh @@ -1,6 +1,6 @@ #!/bin/sh -SRC="Source/Atmosphere/stratosphere/loader/source/oc" +SRC="Source/KIP/stratosphere/loader/source/oc" DEST="build/stratosphere/loader/source/oc" mkdir -p "dist/atmosphere/kips/" mkdir -p "$DEST"